From af9910962abf2438458ffd41228a39532804c328 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 10 Apr 2021 17:34:24 +0100 Subject: Separate the functions of licence.pl. Now you can run it with --header, --copyrightdoc or --licencedoc depending on which file you want it to generate. mkfiles.pl only runs the header mode; the other two modes have become rules in Makefile.doc. --- doc/Makefile | 5 ++ licence.pl | 149 +++++++++++++++++++++++++++++++++-------------------------- mkfiles.pl | 3 ++ 3 files changed, 92 insertions(+), 65 deletions(-) mode change 100644 => 100755 licence.pl diff --git a/doc/Makefile b/doc/Makefile index 5ff25d54..d04be776 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -58,6 +58,11 @@ putty.hlp: $(INPUTS) putty.info: $(INPUTS) $(HALIBUT) --info $(INPUTS) +licence.but: ../licence.pl ../LICENCE + perl $< --licencedoc -o $@ +copy.but: ../licence.pl ../LICENCE + perl $< --copyrightdoc -o $@ + MKMAN = $(HALIBUT) --man=$@ mancfg.but $< MANPAGES = putty.1 puttygen.1 plink.1 pscp.1 psftp.1 puttytel.1 pterm.1 \ pageant.1 psocks.1 psusan.1 diff --git a/licence.pl b/licence.pl old mode 100644 new mode 100755 index e1d3a51f..f927bcb2 --- a/licence.pl +++ b/licence.pl @@ -1,17 +1,31 @@ -#!/usr/bin/env perl -w +#!/usr/bin/env perl # This script generates licence.h (containing the PuTTY licence in the # form of macros expanding to C string literals) from the LICENCE # master file. It also regenerates the licence-related Halibut input # files. +use warnings; use File::Basename; - -# Read the input file. -$infile = "LICENCE"; +use Getopt::Long; + +my $usage = "usage: licence.pl (--header|--licencedoc|--copyrightdoc) " . + "[-o OUTFILE]\n"; +my $mode = undef; +my $output = undef; +GetOptions("--header" => sub {$mode = "header"}, + "--licencedoc" => sub {$mode = "licencedoc"}, + "--copyrightdoc" => sub {$mode = "copyrightdoc"}, + "o|output=s" => \$output) + and defined $mode + or die $usage; + +# Read the input file. We expect to find that alongside this script. +my $infile = (dirname __FILE__) . "/LICENCE"; open my $in, $infile or die "$infile: open: $!\n"; my @lines = (); while (<$in>) { + y/\r//d; chomp; push @lines, $_; } @@ -36,76 +50,68 @@ die "bad format of first paragraph\n" unless $paras[0] =~ m!copyright ([^\.]*)\.!i; $shortdetails = $1; -# Write out licence.h. - -$outfile = "licence.h"; -open my $out, ">", $outfile or die "$outfile: open: $!\n"; -select $out; - -print "/*\n"; -print " * $outfile - macro definitions for the PuTTY licence.\n"; -print " *\n"; -print " * Generated by @{[basename __FILE__]} from $infile.\n"; -print " * You should edit those files rather than editing this one.\n"; -print " */\n"; -print "\n"; - -print "#define LICENCE_TEXT(parsep) \\\n"; -for my $i (0..$#paras) { - my $lit = &stringlit($paras[$i]); - print " parsep \\\n" if $i > 0; - print " \"$lit\""; - print " \\" if $i < $#paras; - print "\n"; -} -print "\n"; - -printf "#define SHORT_COPYRIGHT_DETAILS \"%s\"\n", &stringlit($shortdetails); - -sub stringlit { - my ($lit) = @_; - $lit =~ s!\\!\\\\!g; - $lit =~ s!"!\\"!g; - return $lit; -} - -close $out; - -# Write out doc/licence.but. +my $out = ""; + +if ($mode eq "header") { + $out .= "/*\n"; + $out .= " * licence.h - macro definitions for the PuTTY licence.\n"; + $out .= " *\n"; + $out .= " * Generated by @{[basename __FILE__]} from $infile.\n"; + $out .= " * You should edit those files rather than editing this one.\n"; + $out .= " */\n"; + $out .= "\n"; + + $out .= "#define LICENCE_TEXT(parsep) \\\n"; + for my $i (0..$#paras) { + my $lit = &stringlit($paras[$i]); + $out .= " parsep \\\n" if $i > 0; + $out .= " \"$lit\""; + $out .= " \\" if $i < $#paras; + $out .= "\n"; + } + $out .= "\n"; -$outfile = "doc/licence.but"; -open $out, ">", $outfile or die "$outfile: open: $!\n"; -select $out; + $out .= sprintf "#define SHORT_COPYRIGHT_DETAILS \"%s\"\n", + &stringlit($shortdetails); +} elsif ($mode eq "licencedoc") { + # Write out doc/licence.but. -print "\\# Generated by @{[basename __FILE__]} from $infile.\n"; -print "\\# You should edit those files rather than editing this one.\n\n"; + $out .= "\\# Generated by @{[basename __FILE__]} from $infile.\n"; + $out .= "\\# You should edit those files rather than editing this one.\n\n"; -print "\\A{licence} PuTTY \\ii{Licence}\n\n"; + $out .= "\\A{licence} PuTTY \\ii{Licence}\n\n"; -for my $i (0..$#paras) { - my $para = &halibutescape($paras[$i]); - if ($i == 0) { - $para =~ s!copyright!\\i{copyright}!; # index term in paragraph 1 + for my $i (0..$#paras) { + my $para = &halibutescape($paras[$i]); + if ($i == 0) { + $para =~ s!copyright!\\i{copyright}!; # index term in paragraph 1 + } + $out .= "$para\n\n"; } - print "$para\n\n"; -} - -close $out; +} elsif ($mode eq "copyrightdoc") { + # Write out doc/copy.but, which defines a macro used in the manual + # preamble blurb. -# And write out doc/copy.but, which defines a macro used in the manual -# preamble blurb. + $out .= "\\# Generated by @{[basename __FILE__]} from $infile.\n"; + $out .= "\\# You should edit those files rather than editing this one.\n\n"; -$outfile = "doc/copy.but"; -open $out, ">", $outfile or die "$outfile: open: $!\n"; -select $out; - -print "\\# Generated by @{[basename __FILE__]} from $infile.\n"; -print "\\# You should edit those files rather than editing this one.\n\n"; + $out .= sprintf "\\define{shortcopyrightdetails} %s\n\n", + &halibutescape($shortdetails); +} -printf "\\define{shortcopyrightdetails} %s\n\n", - &halibutescape($shortdetails); +my $outfile; +my $opened = (defined $output) ? + (open $outfile, ">", $output) : (open $outfile, ">-"); +$opened or die "$output: open: $!\n"; +print $outfile $out; +close $outfile; -close $out; +sub stringlit { + my ($lit) = @_; + $lit =~ s!\\!\\\\!g; + $lit =~ s!"!\\"!g; + return $lit; +} sub halibutescape { my ($text) = @_; @@ -113,3 +119,16 @@ sub halibutescape { $text =~ s!"([^"]*)"!\\q{$1}!g; # convert quoted strings to \q{} return $text; } + +sub write { + my ($filename, $newcontents) = @_; + if (open my $fh, "<", $filename) { + my $oldcontents = ""; + $oldcontents .= $_ while <$fh>; + close $fh; + return if $oldcontents eq $newcontents; + } + open my $fh, ">", $filename or die "$filename: open: $!\n"; + print $fh $newcontents; + close $fh; +} diff --git a/mkfiles.pl b/mkfiles.pl index 3282f359..8c048c33 100755 --- a/mkfiles.pl +++ b/mkfiles.pl @@ -48,7 +48,10 @@ open IN, "Recipe" or do { # sbcsgen.pl, and licence.h is likewise generated by licence.pl. We # need to generate those _now_, before attempting dependency analysis. eval 'chdir "charset"; require "./sbcsgen.pl"; chdir ".."; select STDOUT;'; +@orig_ARGV = @ARGV; +@ARGV = ("--header", "-o", "licence.h"); eval 'require "./licence.pl"; select STDOUT;'; +@ARGV = @orig_ARGV; @srcdirs = ("./"); -- cgit v1.2.3 From 97f7a7cb4deacbc92a9dbdc1b9394e4e1358e47a Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 10 Apr 2021 14:46:36 +0100 Subject: Enforce that NDEBUG is not defined. PuTTY is a security project, so assertions are important - if an assumption is violated, proceeding anyway may have far worse consequences than simple program termination. This check and #error should arrange that we don't ever accidentally compile assertions out. --- defs.h | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/defs.h b/defs.h index 3ba43c4f..86f8edde 100644 --- a/defs.h +++ b/defs.h @@ -11,6 +11,17 @@ #ifndef PUTTY_DEFS_H #define PUTTY_DEFS_H +#ifdef NDEBUG +/* + * PuTTY is a security project, so assertions are important - if an + * assumption is violated, proceeding anyway may have far worse + * consequences than simple program termination. This check and #error + * should arrange that we don't ever accidentally compile assertions + * out. + */ +#error Do not compile this code base with NDEBUG defined! +#endif + #include #include #include /* for __MINGW_PRINTF_FORMAT */ -- cgit v1.2.3 From c19e7215ddd1d6a890fdb94d89bc5ccb46151363 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 10 Apr 2021 15:21:11 +0100 Subject: Replace mkfiles.pl with a CMake build system. This brings various concrete advantages over the previous system: - consistent support for out-of-tree builds on all platforms - more thorough support for Visual Studio IDE project files - support for Ninja-based builds, which is particularly useful on Windows where the alternative nmake has no parallel option - a really simple set of build instructions that work the same way on all the major platforms (look how much shorter README is!) - better decoupling of the project configuration from the toolchain configuration, so that my Windows cross-building doesn't need (much) special treatment in CMakeLists.txt - configure-time tests on Windows as well as Linux, so that a lot of ad-hoc #ifdefs second-guessing a particular feature's presence from the compiler version can now be replaced by tests of the feature itself Also some longer-term software-engineering advantages: - other people have actually heard of CMake, so they'll be able to produce patches to the new build setup more easily - unlike the old mkfiles.pl, CMake is not my personal problem to maintain - most importantly, mkfiles.pl was just a horrible pile of unmaintainable cruft, which even I found it painful to make changes to or to use, and desperately needed throwing in the bin. I've already thrown away all the variants of it I had in other projects of mine, and was only delaying this one so we could make the 0.75 release branch first. This change comes with a noticeable build-level restructuring. The previous Recipe worked by compiling every object file exactly once, and then making each executable by linking a precisely specified subset of the same object files. But in CMake, that's not the natural way to work - if you write the obvious command that puts the same source file into two executable targets, CMake generates a makefile that compiles it once per target. That can be an advantage, because it gives you the freedom to compile it differently in each case (e.g. with a #define telling it which program it's part of). But in a project that has many executable targets and had carefully contrived to _never_ need to build any module more than once, all it does is bloat the build time pointlessly! To avoid slowing down the build by a large factor, I've put most of the modules of the code base into a collection of static libraries organised vaguely thematically (SSH, other backends, crypto, network, ...). That means all those modules can still be compiled just once each, because once each library is built it's reused unchanged for all the executable targets. One upside of this library-based structure is that now I don't have to manually specify exactly which objects go into which programs any more - it's enough to specify which libraries are needed, and the linker will figure out the fine detail automatically. So there's less maintenance to do in CMakeLists.txt when the source code changes. But that reorganisation also adds fragility, because of the trad Unix linker semantics of walking along the library list once each, so that cyclic references between your libraries will provoke link errors. The current setup builds successfully, but I suspect it only just manages it. (In particular, I've found that MinGW is the most finicky on this score of the Windows compilers I've tried building with. So I've included a MinGW test build in the new-look Buildscr, because otherwise I think there'd be a significant risk of introducing MinGW-only build failures due to library search order, which wasn't a risk in the previous library-free build organisation.) In the longer term I hope to be able to reduce the risk of that, via gradual reorganisation (in particular, breaking up too-monolithic modules, to reduce the risk of knock-on references when you included a module for function A and it also contains function B with an unsatisfied dependency you didn't really need). Ideally I want to reach a state in which the libraries all have sensibly described purposes, a clearly documented (partial) order in which they're permitted to depend on each other, and a specification of what stubs you have to put where if you're leaving one of them out (e.g. nocrypto) and what callbacks you have to define in your non-library objects to satisfy dependencies from things low in the stack (e.g. out_of_memory()). One thing that's gone completely missing in this migration, unfortunately, is the unfinished MacOS port linked against Quartz GTK. That's because it turned out that I can't currently build it myself, on my own Mac: my previous installation of GTK had bit-rotted as a side effect of an Xcode upgrade, and I haven't yet been able to persuade jhbuild to make me a new one. So I can't even build the MacOS port with the _old_ makefiles, and hence, I have no way of checking that the new ones also work. I hope to bring that port back to life at some point, but I don't want it to block the rest of this change. --- .gitignore | 32 - Buildscr | 140 +-- Buildscr.cv | 14 +- CMakeLists.txt | 101 ++ README | 125 +-- Recipe | 431 --------- charset/CMakeLists.txt | 29 + charset/sbcsgen.pl | 12 +- cmake/cmake.h.in | 35 + cmake/gitcommit.cmake | 48 + cmake/gtk.cmake | 85 ++ cmake/licence.cmake | 39 + cmake/platforms/unix.cmake | 121 +++ cmake/platforms/windows.cmake | 171 ++++ cmake/setup.cmake | 78 ++ cmake/toolchain-mingw.cmake | 10 + cmake/toolchain-winegcc.cmake | 33 + cmake/winegcc | 29 + configure.ac | 264 ------ doc/udp.but | 39 - mkauto.sh | 11 - mkfiles.pl | 2095 ----------------------------------------- mksrcarc.sh | 1 - mkunxarc.sh | 10 +- release.pl | 6 +- unix/CMakeLists.txt | 204 ++++ unix/configure | 3 - unix/gtkapp.c | 15 +- unix/unix.h | 4 +- unix/uxgss.c | 2 +- unix/uxpeer.c | 8 +- unix/uxpty.c | 14 +- unix/uxutils.h | 10 +- version.c | 5 - version.h | 22 - windows/CMakeLists.txt | 142 +++ windows/rcstuff.h | 15 +- windows/window.c | 22 +- windows/wingss.c | 2 +- windows/winhelp.rc2 | 4 +- windows/winhsock.c | 2 +- windows/winmisc.c | 2 +- windows/winmiscs.c | 2 +- windows/winstuff.h | 11 +- 44 files changed, 1272 insertions(+), 3176 deletions(-) create mode 100644 CMakeLists.txt delete mode 100644 Recipe create mode 100644 charset/CMakeLists.txt mode change 100644 => 100755 charset/sbcsgen.pl create mode 100644 cmake/cmake.h.in create mode 100644 cmake/gitcommit.cmake create mode 100644 cmake/gtk.cmake create mode 100644 cmake/licence.cmake create mode 100644 cmake/platforms/unix.cmake create mode 100644 cmake/platforms/windows.cmake create mode 100644 cmake/setup.cmake create mode 100644 cmake/toolchain-mingw.cmake create mode 100644 cmake/toolchain-winegcc.cmake create mode 100755 cmake/winegcc delete mode 100644 configure.ac delete mode 100755 mkauto.sh delete mode 100755 mkfiles.pl create mode 100644 unix/CMakeLists.txt delete mode 100755 unix/configure create mode 100644 windows/CMakeLists.txt diff --git a/.gitignore b/.gitignore index 92b7c10a..68729ab3 100644 --- a/.gitignore +++ b/.gitignore @@ -19,9 +19,6 @@ /*.tds /*.td2 /*.map -/Makefile.mgw -/Makefile.vc -/Makefile.lcc /MSVC /*.log /*.GID @@ -56,27 +53,9 @@ /.bmake /build.log /build.out -/uxconfig.h /empty.h -/config.status -/Makefile.am -/Makefile.in /Makefile /compile -/config.status -/configure -/stamp-h1 -/aclocal.m4 -/ar-lib -/autom4te.cache -/depcomp -/install-sh -/local -/missing -/uxconfig.in -/uxconfig.in~ -/uxconfig.h -/licence.h /*.a /charset/sbcsdat.c /contrib/cygtermd/cygtermd.exe @@ -102,9 +81,6 @@ /icons/*.icns /icons/*.xpm /icons/*.c -/unix/Makefile.gtk -/unix/Makefile.ux -/unix/Makefile.local /unix/empty.h /unix/plink /unix/pterm @@ -133,14 +109,6 @@ /windows/*.td2 /windows/*.map /windows/*.rcpp -/windows/Makefile.clangcl -/windows/Makefile.mgw -/windows/Makefile.vc -/windows/Makefile.lcc -/windows/MSVC -/windows/DEVCPP -/windows/VS2010 -/windows/VS2012 /windows/*.log /windows/*.GID /windows/local diff --git a/Buildscr b/Buildscr index a41fad3c..84465ac0 100644 --- a/Buildscr +++ b/Buildscr @@ -74,12 +74,6 @@ ifneq "$(PRERELEASE)" "" set Uxarcsuffix -$(PRERELEASE)~pre$(Ndate).$(vcsid) ifneq "$(SNAPSHOT)" "" set Uxarcsuffix -$(Lastver)-$(Date).$(vcsid) ifeq "$(RELEASE)$(PRERELEASE)$(SNAPSHOT)" "" set Uxarcsuffix -custom-$(Date).$(vcsid) -# Set up the version number for the autoconf system. -ifneq "$(RELEASE)" "" set Autoconfver $(RELEASE) -ifneq "$(PRERELEASE)" "" set Autoconfver $(PRERELEASE)~pre$(Ndate).$(vcsid) -ifneq "$(SNAPSHOT)" "" set Autoconfver $(Lastver)-$(Date).$(vcsid) -ifeq "$(RELEASE)$(PRERELEASE)$(SNAPSHOT)" "" set Autoconfver Custom.$(Date).$(vcsid) - # Set up the filenames for the Windows installers (minus extension, # which goes on later). ifneq "$(RELEASE)" "" set Isuffix $(RELEASE)-installer @@ -122,31 +116,37 @@ ifneq "$(SNAPSHOT)" "" in putty do echo '$#define SNAPSHOT' >> version.h in putty do echo '$#define TEXTVER "$(Textver)"' >> version.h in putty do echo '$#define SSHVER "$(Sshver)"' >> version.h in putty do echo '$#define BINARY_VERSION $(Winvercommas)' >> version.h -in putty do echo '$#define SOURCE_COMMIT "$(vcsfullid)"' >> version.h -# Set up the extra arguments for the main Windows nmake command. The -# user can define XFLAGS and MAKEARGS on the bob command line, to pass -# in extra compile and make options respectively (e.g. to do a -# debugging or Minefield build). -set Makeargs -ifneq "$(XFLAGS)" "" set Makeargs $(Makeargs) XFLAGS="$(XFLAGS)" -ifneq "$(MAKEARGS)" "" set Makeargs $(Makeargs) $(MAKEARGS) +# In cmake/gitcommit.cmake, replace the default output "unavailable" +# with the commit string generated by bob, so that people rebuilding +# the source archive will still get a useful value. +in putty do sed -i '/set(DEFAULT_COMMIT/s/unavailable/$(vcsfullid)/' cmake/gitcommit.cmake in putty do ./mksrcarc.sh -in putty do ./mkunxarc.sh '$(Autoconfver)' '$(Uxarcsuffix)' $(Docmakever) -in putty do perl mkfiles.pl +in putty do ./mkunxarc.sh '$(Uxarcsuffix)' $(Docmakever) in putty/doc do make $(Docmakever) putty.chm -j$(nproc) delegate - # Run the test suite, under self-delegation so that we don't leave any # cruft lying around. This involves doing a build of the Unix tools # (which is a useful double-check anyway to pick up any build failures) -in putty do ./mkauto.sh -in putty do ./configure CC=clang CFLAGS="-fsanitize=address -fsanitize=leak" -in putty do make -j$(nproc) +in putty do cmake . -DCMAKE_C_COMPILER=clang -DCMAKE_C_FLAGS="-fsanitize=address -fsanitize=leak" -DSTRICT=ON +in putty do make -j$(nproc) VERBOSE=1 in putty do python3 test/cryptsuite.py enddelegate +delegate - +# Also, test-build the Windows tools using MinGW. This is important if +# we want the MinGW build to carry on working, partly because of the +# chance of compiler compatibility issues, but mostly because MinGW's +# linker uses Unix-style library search semantics (once down the +# library list), and no other Windows toolchain we build with is that +# picky. So this ensures the Windows library structure continues to +# work in the most difficult circumstance we expect it to encounter. +in putty do cmake . -DCMAKE_TOOLCHAIN_FILE=cmake/toolchain-mingw.cmake +in putty do make -j$(nproc) VERBOSE=1 +enddelegate + # Windowsify LICENCE, since it's going in the Windows installers. in putty do perl -i~ -pe 'y/\015//d;s/$$/\015/' LICENCE @@ -165,20 +165,20 @@ mkdir putty/windows/abuild64 # # For the 32-bit ones, we set a subsystem version of 5.01, which # allows the resulting files to still run on Windows XP. -in putty/windows with clangcl32 do Platform=x86 make -f Makefile.clangcl BUILDDIR=build32/ SUBSYSVER=,5.01 $(Makeargs) all -j$(nproc) -in putty/windows with clangcl64 do Platform=x64 make -f Makefile.clangcl BUILDDIR=build64/ $(Makeargs) all -j$(nproc) +in putty/windows/build32 with cmake_at_least_3.20 do cmake ../.. -DCMAKE_TOOLCHAIN_FILE=$(cmake_toolchain_clangcl32) -DCMAKE_BUILD_TYPE=Release -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DPUTTY_LINK_MAPS=ON +in putty/windows/build32 with cmake_at_least_3.20 do make -j$(nproc) VERBOSE=1 +in putty/windows/build64 with cmake_at_least_3.20 do cmake ../.. -DCMAKE_TOOLCHAIN_FILE=$(cmake_toolchain_clangcl64) -DCMAKE_BUILD_TYPE=Release -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DPUTTY_LINK_MAPS=ON +in putty/windows/build64 with cmake_at_least_3.20 do make -j$(nproc) VERBOSE=1 # Build experimental Arm Windows binaries. -in putty/windows with clangcl_a32 do Platform=arm make -f Makefile.clangcl BUILDDIR=abuild32/ SUBSYSVER=,5.01 $(Makeargs) all -j$(nproc) -in putty/windows with clangcl_a64 do Platform=arm64 make -f Makefile.clangcl BUILDDIR=abuild64/ $(Makeargs) all -j$(nproc) +in putty/windows/abuild32 with cmake_at_least_3.20 do cmake ../.. -DCMAKE_TOOLCHAIN_FILE=$(cmake_toolchain_clangcl_a32) -DCMAKE_BUILD_TYPE=Release -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DPUTTY_LINK_MAPS=ON +in putty/windows/abuild32 with cmake_at_least_3.20 do make -j$(nproc) VERBOSE=1 +in putty/windows/abuild64 with cmake_at_least_3.20 do cmake ../.. -DCMAKE_TOOLCHAIN_FILE=$(cmake_toolchain_clangcl_a64) -DCMAKE_BUILD_TYPE=Release -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DPUTTY_LINK_MAPS=ON +in putty/windows/abuild64 with cmake_at_least_3.20 do make -j$(nproc) VERBOSE=1 -# Remove Windows binaries for the test programs we don't want to ship, -# like testcrypt.exe. (But we still _built_ them, to ensure the build -# worked.) -in putty/windows do make -f Makefile.clangcl BUILDDIR=build32/ cleantestprogs -in putty/windows do make -f Makefile.clangcl BUILDDIR=build64/ cleantestprogs -in putty/windows do make -f Makefile.clangcl BUILDDIR=abuild32/ cleantestprogs -in putty/windows do make -f Makefile.clangcl BUILDDIR=abuild64/ cleantestprogs +# Make a list of the Windows binaries we're going to ship, so that we +# can sign them. +in putty/windows do for subdir in build32 abuild32 build64 abuild64; do sed "s!^!$$subdir/!" $$subdir/shipped.txt; done > to-sign.txt # Code-sign the Windows binaries, if the local bob config provides a # script to do so in a cross-compiling way. We assume here that the @@ -187,7 +187,7 @@ in putty/windows do make -f Makefile.clangcl BUILDDIR=abuild64/ cleantestprogs # take the program name from an .exe's version resource, and that it # can accept multiple .exe or .msi filename arguments and sign them # all in place. -ifneq "$(cross_winsigncode)" "" in putty/windows do $(cross_winsigncode) -N -i https://www.chiark.greenend.org.uk/~sgtatham/putty/ build*/*.exe abuild*/*.exe +ifneq "$(cross_winsigncode)" "" in putty/windows do $(cross_winsigncode) -N -i https://www.chiark.greenend.org.uk/~sgtatham/putty/ $$(cat to-sign.txt) # Make a preliminary set of cryptographic checksums giving the hashes # of these versions of the binaries. We'll make the rest below. @@ -217,19 +217,16 @@ in putty/windows do ./msifixup.py installera64.msi --dialog-bmp-width=123 --plat # Sign the Windows installers. ifneq "$(cross_winsigncode)" "" in putty/windows do $(cross_winsigncode) -i https://www.chiark.greenend.org.uk/~sgtatham/putty/ -n "PuTTY Installer" installer32.msi installer64.msi installera32.msi installera64.msi -# Delete the binaries and resource files from the build directories, -# so that we can rebuild them differently. -in putty/windows/build32 do rm -f *.exe *.res *.rcpp -in putty/windows/build64 do rm -f *.exe *.res *.rcpp -in putty/windows/abuild32 do rm -f *.exe *.res *.rcpp -in putty/windows/abuild64 do rm -f *.exe *.res *.rcpp - # Build the standalone binaries, in both 32- and 64-bit flavours. # These differ from the previous set in that they embed the help file. -in putty/windows with clangcl32 do Platform=x86 make -f Makefile.clangcl BUILDDIR=build32/ RCFL=-DEMBED_CHM SUBSYSVER=,5.01 $(Makeargs) all -j$(nproc) -in putty/windows with clangcl64 do Platform=x64 make -f Makefile.clangcl BUILDDIR=build64/ RCFL=-DEMBED_CHM $(Makeargs) all -j$(nproc) -in putty/windows with clangcl_a32 do Platform=arm make -f Makefile.clangcl BUILDDIR=abuild32/ RCFL=-DEMBED_CHM SUBSYSVER=,5.01 $(Makeargs) all -j$(nproc) -in putty/windows with clangcl_a64 do Platform=arm64 make -f Makefile.clangcl BUILDDIR=abuild64/ RCFL=-DEMBED_CHM $(Makeargs) all -j$(nproc) +in putty/windows/build32 with cmake_at_least_3.20 do cmake . -DPUTTY_EMBEDDED_CHM_FILE=$$(realpath ../../doc/putty.chm) +in putty/windows/build32 with cmake_at_least_3.20 do make -j$(nproc) VERBOSE=1 +in putty/windows/build64 with cmake_at_least_3.20 do cmake . -DPUTTY_EMBEDDED_CHM_FILE=$$(realpath ../../doc/putty.chm) +in putty/windows/build64 with cmake_at_least_3.20 do make -j$(nproc) VERBOSE=1 +in putty/windows/abuild32 with cmake_at_least_3.20 do cmake . -DPUTTY_EMBEDDED_CHM_FILE=$$(realpath ../../doc/putty.chm) +in putty/windows/abuild32 with cmake_at_least_3.20 do make -j$(nproc) VERBOSE=1 +in putty/windows/abuild64 with cmake_at_least_3.20 do cmake . -DPUTTY_EMBEDDED_CHM_FILE=$$(realpath ../../doc/putty.chm) +in putty/windows/abuild64 with cmake_at_least_3.20 do make -j$(nproc) VERBOSE=1 # Build the 'old' binaries, which should still run on all 32-bit # versions of Windows back to Win95 (but not Win32s). These link @@ -241,42 +238,45 @@ in putty/windows with clangcl_a64 do Platform=arm64 make -f Makefile.clangcl BUI # # There's no installer to go with these, so they must also embed the # help file. -in putty/windows with clangcl32_2003 do Platform=x86 make -f Makefile.clangcl BUILDDIR=buildold/ RCFL=-DEMBED_CHM $(Makeargs) CCTARGET=i386-pc-windows-msvc13.0.0 SUBSYSVER=,4.0 EXTRA_windows=wincrt0.obj EXTRA_console=crt0.obj EXTRA_libs=libcpmt.lib XFLAGS="/arch:IA32 -Wno-pragma-pack" all -j$(nproc) +in putty/windows/buildold with cmake_at_least_3.20 do cmake ../.. -DCMAKE_TOOLCHAIN_FILE=$(cmake_toolchain_clangcl32_2003) -DCMAKE_BUILD_TYPE=Release -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DPUTTY_LINK_MAPS=ON +in putty/windows/buildold with cmake_at_least_3.20 do make -j$(nproc) VERBOSE=1 -# Remove test programs again. -in putty/windows do make -f Makefile.clangcl BUILDDIR=build32/ cleantestprogs -in putty/windows do make -f Makefile.clangcl BUILDDIR=build64/ cleantestprogs -in putty/windows do make -f Makefile.clangcl BUILDDIR=abuild32/ cleantestprogs -in putty/windows do make -f Makefile.clangcl BUILDDIR=abuild64/ cleantestprogs -in putty/windows do make -f Makefile.clangcl BUILDDIR=buildold/ cleantestprogs +# Regenerate to-sign.txt with the 'old' binaries included. +in putty/windows do for subdir in build32 abuild32 build64 abuild64 buildold; do sed "s!^!$$subdir/!" $$subdir/shipped.txt; done > to-sign.txt # Code-sign the standalone versions of the binaries. -ifneq "$(cross_winsigncode)" "" in putty/windows do $(cross_winsigncode) -N -i https://www.chiark.greenend.org.uk/~sgtatham/putty/ build*/*.exe abuild*/*.exe +ifneq "$(cross_winsigncode)" "" in putty/windows do $(cross_winsigncode) -N -i https://www.chiark.greenend.org.uk/~sgtatham/putty/ $$(cat to-sign.txt) + +# Move the shipped (and signed) binaries into another directory to +# deliver them from, so that we omit testcrypt and its ilk. +in putty/windows do mkdir deliver +in putty/windows do for subdir in build32 abuild32 build64 abuild64 buildold; do mkdir deliver/$$subdir; done +in putty/windows do while read x; do mv $$x deliver/$$x; mv $$x.map deliver/$$x.map; done < to-sign.txt in putty/doc do make mostlyclean in putty/doc do make $(Docmakever) -j$(nproc) -in putty/windows/buildold do zip -k -j putty.zip `ls *.exe | grep -v puttytel` ../../doc/putty.chm -in putty/windows/build32 do zip -k -j putty.zip `ls *.exe | grep -v puttytel` ../../doc/putty.chm -in putty/windows/build64 do zip -k -j putty.zip `ls *.exe | grep -v puttytel` ../../doc/putty.chm -in putty/windows/abuild32 do zip -k -j putty.zip `ls *.exe | grep -v puttytel` ../../doc/putty.chm -in putty/windows/abuild64 do zip -k -j putty.zip `ls *.exe | grep -v puttytel` ../../doc/putty.chm +in putty/windows/deliver/buildold do zip -k -j putty.zip `ls *.exe | grep -v puttytel` ../../doc/putty.chm +in putty/windows/deliver/build32 do zip -k -j putty.zip `ls *.exe | grep -v puttytel` ../../doc/putty.chm +in putty/windows/deliver/build64 do zip -k -j putty.zip `ls *.exe | grep -v puttytel` ../../doc/putty.chm +in putty/windows/deliver/abuild32 do zip -k -j putty.zip `ls *.exe | grep -v puttytel` ../../doc/putty.chm +in putty/windows/deliver/abuild64 do zip -k -j putty.zip `ls *.exe | grep -v puttytel` ../../doc/putty.chm in putty/doc do zip puttydoc.zip *.html # Deliver the actual PuTTY release directory into a subdir `putty'. -deliver putty/windows/buildold/*.exe putty/w32old/$@ -deliver putty/windows/buildold/putty.zip putty/w32old/$@ -deliver putty/windows/build32/*.exe putty/w32/$@ -deliver putty/windows/build32/putty.zip putty/w32/$@ -deliver putty/windows/build64/*.exe putty/w64/$@ -deliver putty/windows/build64/putty.zip putty/w64/$@ +deliver putty/windows/deliver/buildold/*.exe putty/w32old/$@ +deliver putty/windows/deliver/buildold/putty.zip putty/w32old/$@ +deliver putty/windows/deliver/build32/*.exe putty/w32/$@ +deliver putty/windows/deliver/build32/putty.zip putty/w32/$@ +deliver putty/windows/deliver/build64/*.exe putty/w64/$@ +deliver putty/windows/deliver/build64/putty.zip putty/w64/$@ deliver putty/windows/installer32.msi putty/w32/$(Ifilename32).msi deliver putty/windows/installer64.msi putty/w64/$(Ifilename64).msi deliver putty/windows/installera32.msi putty/wa32/$(Ifilenamea32).msi deliver putty/windows/installera64.msi putty/wa64/$(Ifilenamea64).msi -deliver putty/windows/abuild32/*.exe putty/wa32/$@ -deliver putty/windows/abuild32/putty.zip putty/wa32/$@ -deliver putty/windows/abuild64/*.exe putty/wa64/$@ -deliver putty/windows/abuild64/putty.zip putty/wa64/$@ +deliver putty/windows/deliver/abuild32/*.exe putty/wa32/$@ +deliver putty/windows/deliver/abuild32/putty.zip putty/wa32/$@ +deliver putty/windows/deliver/abuild64/*.exe putty/wa64/$@ +deliver putty/windows/deliver/abuild64/putty.zip putty/wa64/$@ deliver putty/doc/puttydoc.zip putty/$@ deliver putty/doc/putty.chm putty/$@ deliver putty/doc/puttydoc.txt putty/$@ @@ -285,11 +285,11 @@ deliver putty/putty-src.zip putty/$@ deliver putty/*.tar.gz putty/$@ # Deliver the map files alongside the `proper' release deliverables. -deliver putty/windows/buildold/*.map maps/w32old/$@ -deliver putty/windows/build32/*.map maps/w32/$@ -deliver putty/windows/build64/*.map maps/w64/$@ -deliver putty/windows/abuild32/*.map maps/wa32/$@ -deliver putty/windows/abuild64/*.map maps/wa64/$@ +deliver putty/windows/deliver/buildold/*.map maps/w32old/$@ +deliver putty/windows/deliver/build32/*.map maps/w32/$@ +deliver putty/windows/deliver/build64/*.map maps/w64/$@ +deliver putty/windows/deliver/abuild32/*.map maps/wa32/$@ +deliver putty/windows/deliver/abuild64/*.map maps/wa64/$@ # Deliver sign.sh, so that whoever has just built PuTTY (the # snapshot scripts or me, depending) can conveniently sign it with diff --git a/Buildscr.cv b/Buildscr.cv index fab61dc0..ab3107f2 100644 --- a/Buildscr.cv +++ b/Buildscr.cv @@ -6,16 +6,12 @@ module putty -# Preparations. -in putty do ./mkfiles.pl -in putty do ./mkauto.sh -in putty/doc do make - # Scan the Unix build, on a 64-bit system to differentiate as much as # possible from the other scan of the cross-platform files. delegate covscan64 - in putty do ./configure - in putty do cov-build --dir cov-int make + in putty do mkdir linbuild + in putty/linbuild do cmake .. + in putty/linbuild do cov-build --dir ../cov-int make -j$(nproc) VERBOSE=1 in putty do tar czvf cov-int.tar.gz cov-int return putty/cov-int.tar.gz enddelegate @@ -25,7 +21,9 @@ enddelegate # Windows scanner for download). delegate covscan32wine in putty do tar xzvf cov-int.tar.gz - in putty/windows do cov-build --dir ../cov-int make -f Makefile.mgw CC=winegcc RC=wrc + in putty do mkdir winbuild + in putty/winbuild do cmake .. -DCMAKE_TOOLCHAIN_FILE=../cmake/toolchain-winegcc.cmake + in putty/winbuild do cov-build --dir ../cov-int make -j$(nproc) VERBOSE=1 in putty do tar czvf cov-int.tar.gz cov-int return putty/cov-int.tar.gz enddelegate diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..1c467760 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,101 @@ +cmake_minimum_required(VERSION 3.12) +project(putty LANGUAGES C) + +include(cmake/setup.cmake) + +add_library(utils STATIC + memory.c marshal.c utils.c conf.c sshutils.c tree234.c version.c + wildcard.c wcwidth.c misc.c miscucs.c stripctrl.c sessprep.c + ${GENERATED_COMMIT_C}) + +add_library(logging OBJECT + logging.c) + +add_library(eventloop STATIC + callback.c timing.c) + +add_library(console STATIC + clicons.c console.c) + +add_library(settings STATIC + cmdline.c settings.c) + +add_library(crypto STATIC + sshaes.c sshauxcrypt.c sshdes.c sshdss.c sshecc.c sshhmac.c sshmd5.c sshrsa.c + sshsh256.c sshsh512.c sshsha.c sshsha3.c + ecc.c mpint.c + sshprng.c + sshcrc.c + sshdh.c sshmac.c + ssharcf.c sshblowf.c sshccp.c + sshblake2.c sshargon2.c sshbcrypt.c + cproxy.c) + +add_library(network STATIC + be_misc.c nullplug.c errsock.c proxy.c logging.c) + +add_library(keygen STATIC + millerrabin.c mpunsafe.c pockle.c primecandidate.c smallprimes.c + sshdssg.c sshecdsag.c sshprime.c sshrsag.c + import.c) + +add_library(agent STATIC + sshpubk.c pageant.c aqsync.c) + +add_library(guiterminal STATIC + terminal.c ldisc.c minibidi.c config.c dialog.c + $) + +add_library(noterminal STATIC + noterm.c ldisc.c) + +add_library(sshcommon OBJECT + ssh1bpp.c ssh1censor.c + ssh1connection.c ssh1login.c ssh2bpp-bare.c ssh2bpp.c ssh2censor.c + ssh2connection.c ssh2transhk.c ssh2transport.c ssh2userauth.c + sshcommon.c sshcrcda.c sshgssc.c sshpubk.c sshrand.c + sshverstring.c sshzlib.c + pgssapi.c portfwd.c x11fwd.c) + +add_library(sftpcommon OBJECT + sftpcommon.c) + +add_library(all-backends OBJECT + pinger.c) + +add_library(sshclient STATIC + ssh1connection-client.c ssh2connection-client.c ssh2kex-client.c + sshshare.c ssh.c + mainchan.c agentf.c + $ + $ + $) + +add_library(sshserver STATIC + ssh1connection-server.c ssh1login-server.c ssh2connection-server.c + ssh2kex-server.c ssh2userauth-server.c sshserver.c + sesschan.c + sftpserver.c + $ + $) + +add_library(sftpclient STATIC + psftpcommon.c sftp.c $) + +add_library(otherbackends STATIC + telnet.c rlogin.c raw.c supdup.c + $ + $) + +add_executable(testcrypt + testcrypt.c sshpubk.c sshcrcda.c) +target_link_libraries(testcrypt + keygen crypto utils ${platform_libraries}) + +add_compile_definitions(HAVE_CMAKE_H) + +foreach(subdir ${PLATFORM_SUBDIRS}) + add_subdirectory(${subdir}) +endforeach() + +configure_file(cmake/cmake.h.in ${GENERATED_SOURCES_DIR}/cmake.h) diff --git a/README b/README index de6eb9b0..252fc6be 100644 --- a/README +++ b/README @@ -1,123 +1,12 @@ -This is the README for the source archive of PuTTY, a free Windows -and Unix Telnet and SSH client. +This is the README for PuTTY, a free Windows 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 -Git, you'll have to generate the Makefiles yourself -- see -below.) +PuTTY is built using CMake . To compile in the +simplest way (on any of Linux, Windows or Mac), run these commands in +the source directory: -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. - - As of 2017, we successfully compile PuTTY with both Visual Studio - 7 (2003) and Visual Studio 14 (2015), so our guess is that it will - probably build with versions in between those as well. - - (The binaries from Visual Studio 14 are only compatible with - Windows XP and up. Binaries from Visual Studio 7 ought to work - with anything from Windows 95 onward.) - - - 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 7 and 10. - - 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.mgw is for MinGW / Cygwin installations. Type - `make -f Makefile.mgw' while in the `windows' subdirectory to - build all the PuTTY binaries. - - MinGW and friends can lag behind other toolchains in their support - for the Windows API. Compile-time levers are provided to exclude - some features; the defaults are set appropriately for the - 'mingw-w64' cross-compiler provided with Ubuntu 14.04. If you are - using an older toolchain, you may need to exclude more features; - alternatively, you may find that upgrading to a recent version of - the 'w32api' package helps. - - - 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/10) and Makefile.mgw -(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'. Or you - can do the same in the top-level directory (we provide a little - wrapper that invokes configure one level down), which is more like - a normal Unix source archive but doesn't do so well at keeping the - per-platform stuff in each platform's subdirectory; it's up to you. - - - 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, any of Gtk+-1.2, Gtk+-2.0, and Gtk+-3.0 - should be supported. If you have more than one installed, you can - manually specify which one you want by giving the option - '--with-gtk=N' to the configure script where N is 1, 2, or 3. - (The default is the newest available, of course.) In the absence - of any Gtk version, the configure script will automatically - construct a Makefile which builds only the command-line utilities; - you can manually create this condition by giving configure the - option '--without-gtk'. - - - pterm would like to be setuid or setgid, as appropriate, to permit - it to write records of user logins to /var/run/utmp and - /var/log/wtmp. (Of course it will not use this privilege for - anything else, and in particular it will drop all privileges before - starting up complex subsystems like GTK.) By default the makefile - will not attempt to add privileges to the pterm executable at 'make - install' time, but you can ask it to do so by running configure - with the option '--enable-setuid=USER' or '--enable-setgid=GROUP'. - - - The Unix Makefiles have an `install' target. Note that by default - it tries to install `man' pages; if you have fetched the source via - Git then you will need to have built these using Halibut - first - see below. - - - It's also possible to build the Windows version of PuTTY to run - on Unix by using Winelib. To do this, change to the `windows' - directory and run `make -f Makefile.mgw CC=winegcc RC=wrc'. - -All of the Makefiles are generated automatically from the file -`Recipe' by the Perl script `mkfiles.pl' (except for the Unix one, -which is generated by the `configure' script; mkfiles.pl only -generates the input to automake). Additions and corrections to Recipe, -mkfiles.pl and/or configure.ac are much more useful than additions and -corrections to the actual Makefiles, Makefile.am or Makefile.in. - -The Unix `configure' script and its various requirements are generated -by the shell script `mkauto.sh', which requires GNU Autoconf, GNU -Automake, and Gtk; if you've got the source from Git rather -than using one of our source snapshots, you'll need to run this -yourself. The input file to Automake is generated by mkfiles.pl along -with all the rest of the makefiles, so you will need to run mkfiles.pl -and then mkauto.sh. + cmake . + cmake --build . Documentation (in various formats including Windows Help and Unix `man' pages) is built from the Halibut (`.but') files in the `doc' diff --git a/Recipe b/Recipe deleted file mode 100644 index 49f6d1b9..00000000 --- a/Recipe +++ /dev/null @@ -1,431 +0,0 @@ -# -*- 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 clangcl windows/Makefile.clangcl -!makefile vc windows/Makefile.vc -!makefile vcproj windows/MSVC -!makefile cygwin windows/Makefile.mgw -!makefile lcc windows/Makefile.lcc -!makefile gtk unix/Makefile.gtk -!makefile unix unix/Makefile.ux -!makefile am Makefile.am -!makefile devcppproj windows/DEVCPP -!makefile vstudio10 windows/VS2010 -!makefile vstudio12 windows/VS2012 -# Source directories. -!srcdir charset/ -!srcdir windows/ -!srcdir unix/ - -# 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: -# -# - COMPAT=/DAUTO_WINSOCK (Windows only) -# Causes PuTTY to assume that includes its own WinSock -# header file, so that it won't try to include . -# -# - COMPAT=/DWINSOCK_TWO (Windows only) -# Causes the PuTTY utilities to include instead of -# , except Plink which _needs_ WinSock 2 so it already -# does this. -# -# - COMPAT=/DNO_SECURITY (Windows only) -# Disables use of , which is not available with some -# development environments (such as very old versions of the -# mingw/Cygwin GNU toolchain). This has the following effects: -# - 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). -# - SSH connection sharing is disabled. -# - There is no support for restriction of the process ACLs. -# -# - COMPAT=/DNO_MULTIMON (Windows only) -# Disables PuTTY's use of , 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. -# -# - COMPAT=/DNO_HTMLHELP (Windows only) -# Disables PuTTY's use of , which is not available -# with some development environments. -# -# If you don't have this header, you may be able to use the copy -# 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. -# -# - COMPAT=/DNO_SECUREZEROMEMORY (Windows only) -# Disables PuTTY's use of SecureZeroMemory(), which is missing -# from some environments' header files. -# -# - 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. -# -# - XFLAGS=/DFUZZING -# Builds a version of PuTTY with some tweaks to make fuzz testing -# easier: the SSH random number generator is replaced by one that -# always returns the same thing. Note that this makes SSH -# completely insecure -- a FUZZING build should never be used to -# connect to a real server. -!end - -# ------------------------------------------------------------ -# Additional text added verbatim to each individual Makefile. - -!cflags am version -!begin am -if AUTO_GIT_COMMIT -BUILT_SOURCES = empty.h -CLEANFILES = empty.h -libversion_a_CFLAGS += -DSOURCE_COMMIT=\"`git --git-dir=$(srcdir)/.git rev-parse HEAD 2>/dev/null`\" -empty.h: $(allsources) - echo '/* Empty file touched by automake makefile to force rebuild of version.o */' >$@ -endif - -# Run the cryptsuite tests as part of 'make check'. Override -# PUTTY_TESTCRYPT so that cryptsuite will take the testcrypt binary -# from the build directory instead of the source directory, in case -# this is an out-of-tree build. -check-local: testcrypt - PUTTY_TESTCRYPT=./testcrypt $(srcdir)/test/cryptsuite.py - -!end -!begin >empty.h -/* Empty file touched by automake makefile to force rebuild of version.o */ -!end - -!begin vc vars -CFLAGS = $(CFLAGS) /DHAS_GSSAPI -!end - -!begin clangcl vars -CFLAGS += /DHAS_GSSAPI -!end - -# `make install' target for Unix. -!begin gtk -install: - mkdir -p $(DESTDIR)$(bindir) $(DESTDIR)$(man1dir) - $(INSTALL_PROGRAM) -m 755 pageant $(DESTDIR)$(bindir)/pageant - $(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/pageant.1 $(DESTDIR)$(man1dir)/pageant.1 - $(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 - -# List the man pages for the automake makefile. -!begin am -if HAVE_GTK -man1_MANS = doc/plink.1 doc/pscp.1 doc/psftp.1 doc/puttygen.1 \ - doc/pageant.1 doc/pterm.1 doc/putty.1 doc/puttytel.1 -else -man1_MANS = doc/plink.1 doc/pscp.1 doc/psftp.1 doc/puttygen.1 -endif -!end - -# In automake, chgrp/chmod pterm after installation, if configured to. -!begin am -if HAVE_SETID_CMD -install-exec-local: - @SETID_CMD@ $(bindir)/pterm - chmod @SETID_MODE@ $(bindir)/pterm -endif -!end - -# In automake makefile, build the OS X app bundle, if configured in -# Quartz mode. -!begin am -if HAVE_QUARTZ -noinst_SCRIPTS = unix/PuTTY.app unix/Pterm.app -unix/PuTTY.app: unix/putty.bundle puttyapp osxlaunch - rm -rf $@ && PUTTY_GTK_PREFIX_FROM_MAKEFILE=$$(pkg-config --variable=prefix gtk+-3.0) gtk-mac-bundler $< -unix/Pterm.app: unix/pterm.bundle ptermapp osxlaunch - rm -rf $@ && PUTTY_GTK_PREFIX_FROM_MAKEFILE=$$(pkg-config --variable=prefix gtk+-3.0) gtk-mac-bundler $< -endif -!end - -# Random symbols. -!begin cygwin vars -# _WIN32_IE is required to expose identifiers that only make sense on -# systems with IE5+ installed, such as some arguments to SHGetFolderPath(). -# 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. - -# conf.c and its dependencies. -CONF = conf marshal - -# Terminal emulator and its (platform-independent) dependencies. -TERMINAL = terminal stripctrl wcwidth logging tree234 minibidi - + config dialog CONF - -# GUI front end and terminal emulator (putty, puttytel). -GUITERM = TERMINAL window windlg winctrls sizetip winprint winutils - + wincfg winhelp winjump sessprep winselgui - -# Same thing on Unix. -UXTERM = TERMINAL uxcfg uxucs uxprint timing callback miscucs -GTKTERM = UXTERM gtkwin gtkcfg gtkdlg gtkfont gtkcols gtkmisc xkeysym - + x11misc gtkcomm sessprep -GTKMAIN = gtkmain cmdline - -# Non-SSH back ends (putty, puttytel, plink). -NONSSH = telnet raw rlogin supdup ldisc pinger - -# SSH back end (putty, plink, pscp, psftp). -ARITH = mpint ecc -SSHCRYPTO = ARITH sshmd5 sshsha sshsh256 sshsh512 sshsha3 sshblake2 sshargon2 - + sshrsa sshdss sshecc - + sshdes sshblowf sshaes sshccp ssharcf - + sshdh sshcrc sshcrcda sshauxcrypt - + sshhmac -SSHCOMMON = sshcommon sshutils sshprng sshrand SSHCRYPTO - + sshverstring - + sshpubk sshzlib - + sshmac marshal nullplug - + sshgssc pgssapi wildcard ssh1censor ssh2censor ssh2bpp - + ssh2transport ssh2transhk ssh2connection portfwd x11fwd - + ssh1connection ssh1bpp ssh2bpp-bare -SSH = SSHCOMMON ssh - + ssh1login ssh2userauth - + pinger - + sshshare aqsync agentf - + mainchan ssh2kex-client ssh2connection-client ssh1connection-client -WINSSH = SSH winnoise wincapi winpgntc wingss winshare winnps winnpc - + winhsock errsock -UXSSH = SSH uxnoise uxagentc uxgss uxshare - -# SFTP implementation (pscp, psftp). -SFTP = psftpcommon sftp sftpcommon logging cmdline - -# Components of the prime-generation system. -SSHPRIME = sshprime smallprimes primecandidate millerrabin pockle mpunsafe - -# Miscellaneous objects appearing in all the utilities, or all the -# network ones, or the Unix or Windows subsets of those in turn. -MISC = misc utils marshal memory stripctrl wcwidth -MISCNETCOMMON = timing callback MISC version tree234 CONF -MISCNET = MISCNETCOMMON be_misc settings proxy -WINMISC = MISCNET winstore winnet winhandl cmdline windefs winmisc winproxy - + wintime winhsock errsock winsecur winucs miscucs winmiscs -UXMISCCOMMON = MISCNETCOMMON uxstore uxsel uxpoll uxnet uxpeer uxmisc time - + uxfdsock errsock -UXMISC = MISCNET UXMISCCOMMON uxproxy uxutils - -# SSH server. -SSHSERVER = SSHCOMMON sshserver settings be_none logging ssh2kex-server - + ssh2userauth-server sshrsag SSHPRIME ssh2connection-server - + sesschan sftpcommon sftpserver proxy cproxy ssh1login-server - + ssh1connection-server scpserver - -# import.c and dependencies, for PuTTYgen-like utilities that have to -# load foreign key files. -IMPORT = import sshbcrypt sshblowf marshal - -# 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 comdlg32.lib - + shell32.lib imm32.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 norand nocproxy -BE_SSH = be_ssh 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 norand winser nocproxy -# And with the Unix serial-port module. -U_BE_ALL = be_all_s uxser cproxy -U_BE_NOSSH = be_nos_s norand uxser nocproxy - -# Auxiliary crypto modules used by key generators. -KEYGEN = sshrsag sshdssg sshecdsag - -# ------------------------------------------------------------ -# 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 console NONSSH WINSSH W_BE_ALL logging WINMISC - + winx11 plink.res winnojmp sessprep noterm winnohlp winselcli - + clicons wincliloop console LIBS -pscp : [C] pscp winsftp wincons WINSSH BE_SSH SFTP wildcard WINMISC - + pscp.res winnojmp winnohlp winselcli clicons wincliloop - + console LIBS -psftp : [C] psftp winsftp wincons WINSSH BE_SSH SFTP wildcard WINMISC - + psftp.res winnojmp winnohlp winselcli clicons wincliloop - + console LIBS - -pageant : [G] winpgnt pageant sshrsa sshpubk sshdes ARITH sshmd5 version - + tree234 MISC sshaes sshsha winsecur winpgntc aqsync sshdss sshsh256 - + sshsh512 winutils sshecc winmisc winmiscs winhelp conf pageant.res - + sshauxcrypt sshhmac wincapi winnps winnpc winhsock errsock winnet - + winhandl callback be_misc winselgui winhandl sshsha3 sshblake2 - + sshargon2 LIBS - -puttygen : [G] winpgen KEYGEN SSHPRIME sshdes ARITH sshmd5 version - + sshrand winnoise sshsha winstore MISC winctrls sshrsa sshdss winmisc - + sshpubk sshaes sshsh256 sshsh512 IMPORT winutils puttygen.res - + tree234 notiming winhelp winnojmp CONF LIBS wintime sshecc sshprng - + sshauxcrypt sshhmac winsecur winmiscs sshsha3 sshblake2 sshargon2 - -pterm : [X] GTKTERM uxmisc misc ldisc settings uxpty uxsel BE_NONE uxstore - + uxsignal CHARSET cmdline uxpterm version time xpmpterm xpmptcfg - + nogss utils memory GTKMAIN -putty : [X] GTKTERM uxmisc misc ldisc settings uxsel U_BE_ALL uxstore - + uxsignal CHARSET uxputty NONSSH UXSSH UXMISC ux_x11 xpmputty - + xpmpucfg utils memory GTKMAIN -puttytel : [X] GTKTERM uxmisc misc ldisc settings uxsel U_BE_NOSSH - + uxstore uxsignal CHARSET uxputty NONSSH UXMISC xpmputty xpmpucfg - + nogss utils memory GTKMAIN - -plink : [U] uxplink uxcons NONSSH UXSSH U_BE_ALL logging UXMISC uxsignal - + ux_x11 noterm uxnogtk sessprep cmdline clicons uxcliloop console - -PUTTYGEN_UNIX = KEYGEN SSHPRIME sshdes ARITH sshmd5 version sshprng - + sshrand uxnoise sshsha MISC sshrsa sshdss uxcons uxstore uxmisc - + sshpubk sshaes sshsh256 sshsh512 IMPORT puttygen.res time tree234 - + uxgen notiming CONF sshecc sshsha3 uxnogtk sshauxcrypt sshhmac - + uxpoll uxutils sshblake2 sshargon2 console -puttygen : [U] cmdgen PUTTYGEN_UNIX -cgtest : [UT] cgtest PUTTYGEN_UNIX - -pscp : [U] pscp uxsftp uxcons UXSSH BE_SSH SFTP wildcard UXMISC uxnogtk - + clicons uxcliloop console -psftp : [U] psftp uxsftp uxcons UXSSH BE_SSH SFTP wildcard UXMISC uxnogtk - + clicons uxcliloop console - -pageant : [X] uxpgnt uxagentc aqsync pageant sshrsa sshpubk sshdes ARITH - + sshmd5 version tree234 misc sshaes sshsha sshdss sshsh256 sshsh512 - + sshecc CONF uxsignal nocproxy nogss be_none x11fwd ux_x11 uxcons - + gtkask gtkmisc nullplug logging UXMISC uxagentsock utils memory - + sshauxcrypt sshhmac sshprng uxnoise uxcliloop sshsha3 sshblake2 - + sshargon2 console - -ptermapp : [XT] GTKTERM uxmisc misc ldisc settings uxpty uxsel BE_NONE uxstore - + uxsignal CHARSET uxpterm version time xpmpterm xpmptcfg - + nogss gtkapp nocmdline utils memory -puttyapp : [XT] GTKTERM uxmisc misc ldisc settings uxsel U_BE_ALL uxstore - + uxsignal CHARSET uxputty NONSSH UXSSH UXMISC ux_x11 xpmputty - + xpmpucfg gtkapp nocmdline utils memory -osxlaunch : [UT] osxlaunch - -fuzzterm : [UT] UXTERM CHARSET MISC version uxmisc uxucs fuzzterm time settings - + uxstore be_none uxnogtk memory -testcrypt : [UT] testcrypt SSHCRYPTO sshprng SSHPRIME sshpubk marshal utils - + memory tree234 uxutils KEYGEN -testcrypt : [C] testcrypt SSHCRYPTO sshprng SSHPRIME sshpubk marshal utils - + memory tree234 winmiscs KEYGEN -testsc : [UT] testsc SSHCRYPTO marshal utils memory tree234 wildcard - + sshmac uxutils sshpubk -testzlib : [UT] testzlib sshzlib utils marshal memory - -uppity : [UT] uxserver SSHSERVER UXMISC uxsignal uxnoise uxgss uxnogtk - + uxpty uxsftpserver ux_x11 uxagentsock procnet uxcliloop -psusan : [U] uxpsusan SSHSERVER UXMISC uxsignal uxnoise nogss uxnogtk - + uxpty uxsftpserver ux_x11 uxagentsock procnet uxcliloop - -PSOCKS = psocks portfwd conf sshutils logging proxy nocproxy timing callback - + time tree234 version errsock be_misc norand MISC -psocks : [C] PSOCKS winsocks wincons winproxy winnet winmisc winselcli - + winhsock winhandl winmiscs winnohlp wincliloop console LIBS -psocks : [UT] PSOCKS uxsocks uxcons uxproxy uxnet uxmisc uxpoll uxsel uxnogtk - + uxpeer uxfdsock uxcliloop uxsignal console - -# ---------------------------------------------------------------------- -# On Windows, provide a means of removing local test binaries that we -# aren't going to actually ship. (I prefer this to not building them -# in the first place, so that we find out about build breakage early.) -!begin vc -cleantestprogs: - -del $(BUILDDIR)testcrypt.exe $(BUILDDIR)psocks.exe -!end -!begin clangcl -cleantestprogs: - -rm -f $(BUILDDIR)testcrypt.exe $(BUILDDIR)psocks.exe -!end diff --git a/charset/CMakeLists.txt b/charset/CMakeLists.txt new file mode 100644 index 00000000..22cc7a14 --- /dev/null +++ b/charset/CMakeLists.txt @@ -0,0 +1,29 @@ +include(FindPerl) +if(NOT PERL_EXECUTABLE) + message(FATAL_ERROR "Perl is required to autogenerate sbcsdat.c") +endif() + +set(GENERATED_SBCSDAT_C ${GENERATED_SOURCES_DIR}/sbcsdat.c) +add_custom_command(OUTPUT ${GENERATED_SBCSDAT_C}.tmp + COMMAND ${PERL_EXECUTABLE} ${CMAKE_SOURCE_DIR}/charset/sbcsgen.pl + -o ${GENERATED_SBCSDAT_C}.tmp + DEPENDS ${CMAKE_SOURCE_DIR}/charset/sbcsgen.pl + ${CMAKE_SOURCE_DIR}/charset/sbcs.dat) +add_custom_target(generated_sbcsdat_c + BYPRODUCTS ${GENERATED_SBCSDAT_C} + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${GENERATED_SBCSDAT_C}.tmp ${GENERATED_SBCSDAT_C} + DEPENDS ${GENERATED_SBCSDAT_C}.tmp + COMMENT "Updating sbcsdat.c") + +add_library(charset STATIC + fromucs.c + localenc.c + macenc.c + mimeenc.c + sbcs.c + ${GENERATED_SBCSDAT_C} + slookup.c + toucs.c + utf8.c + xenc.c) diff --git a/charset/sbcsgen.pl b/charset/sbcsgen.pl old mode 100644 new mode 100755 index 078aebf7..bedf6b36 --- a/charset/sbcsgen.pl +++ b/charset/sbcsgen.pl @@ -1,11 +1,19 @@ -#!/usr/bin/env perl -w +#!/usr/bin/env perl # This script generates sbcsdat.c (the data for all the SBCSes) from its # source form sbcs.dat. -$infile = "sbcs.dat"; +use warnings; +use Getopt::Long; +use File::Basename; + +$infile = (dirname __FILE__) . "/sbcs.dat"; $outfile = "sbcsdat.c"; +my $usage = "usage: sbcsgen.pl [-o OUTFILE]\n"; +GetOptions("o|output=s" => \$outfile) + or die $usage; + open FOO, $infile; open BAR, ">$outfile"; select BAR; diff --git a/cmake/cmake.h.in b/cmake/cmake.h.in new file mode 100644 index 00000000..3ddf25fc --- /dev/null +++ b/cmake/cmake.h.in @@ -0,0 +1,35 @@ +#cmakedefine NO_IPV6 +#cmakedefine NO_GSSAPI +#cmakedefine STATIC_GSSAPI + +#cmakedefine NO_MULTIMON + +#cmakedefine01 HAVE_WINRESRC_H +#cmakedefine01 HAVE_WINRES_H +#cmakedefine01 HAVE_WIN_H +#cmakedefine01 HAVE_NO_STDINT_H +#cmakedefine01 HAVE_GCP_RESULTSW +#cmakedefine01 HAVE_ADDDLLDIRECTORY +#cmakedefine01 HAVE_GETNAMEDPIPECLIENTPROCESSID +#cmakedefine01 HAVE_SETDEFAULTDLLDIRECTORIES +#cmakedefine01 HAVE_STRTOUMAX + +#cmakedefine NOT_X_WINDOWS + +#cmakedefine01 HAVE_FUTIMES +#cmakedefine01 HAVE_GETADDRINFO +#cmakedefine01 HAVE_POSIX_OPENPT +#cmakedefine01 HAVE_PTSNAME +#cmakedefine01 HAVE_SETRESUID +#cmakedefine01 HAVE_STRSIGNAL +#cmakedefine01 HAVE_UPDWTMPX +#cmakedefine01 HAVE_FSTATAT +#cmakedefine01 HAVE_DIRFD +#cmakedefine01 HAVE_SETPWENT +#cmakedefine01 HAVE_ENDPWENT +#cmakedefine01 HAVE_GETAUXVAL +#cmakedefine01 HAVE_CLOCK_MONOTONIC +#cmakedefine01 HAVE_CLOCK_GETTIME +#cmakedefine01 HAVE_SO_PEERCRED +#cmakedefine01 HAVE_PANGO_FONT_FAMILY_IS_MONOSPACE +#cmakedefine01 HAVE_PANGO_FONT_MAP_LIST_FAMILIES diff --git a/cmake/gitcommit.cmake b/cmake/gitcommit.cmake new file mode 100644 index 00000000..76753aa7 --- /dev/null +++ b/cmake/gitcommit.cmake @@ -0,0 +1,48 @@ +# Pure cmake script to write out cmake_commit.h + +set(DEFAULT_COMMIT "unavailable") +set(commit "${DEFAULT_COMMIT}") + +execute_process( + COMMAND ${GIT_EXECUTABLE} -C ${TOPLEVEL_SOURCE_DIR} + rev-parse --show-toplevel + OUTPUT_VARIABLE git_worktree + ERROR_VARIABLE stderr + RESULT_VARIABLE status) +string(REGEX REPLACE "\n$" "" git_worktree "${git_worktree}") + +if(status EQUAL 0) + if(git_worktree STREQUAL TOPLEVEL_SOURCE_DIR) + execute_process( + COMMAND ${GIT_EXECUTABLE} -C ${TOPLEVEL_SOURCE_DIR} + rev-parse HEAD + OUTPUT_VARIABLE git_commit + ERROR_VARIABLE stderr + RESULT_VARIABLE status) + if(status EQUAL 0) + string(REGEX REPLACE "\n$" "" commit "${git_commit}") + else() + if(commit STREQUAL "unavailable") + message("Unable to determine git commit: 'git rev-parse HEAD' returned status ${status} and error output:\n${stderr}\n") + endif() + endif() + else() + if(commit STREQUAL "unavailable") + message("Unable to determine git commit: top-level source dir ${TOPLEVEL_SOURCE_DIR} is not the root of a repository") + endif() + endif() +else() + if(commit STREQUAL "unavailable") + message("Unable to determine git commit: 'git rev-parse --show-toplevel' returned status ${status} and error output:\n${stderr}\n") + endif() +endif() + +file(WRITE "${OUTPUT_FILE}" "\ +/* + * cmake_commit.h - string literal giving the source git commit, if known. + * + * Generated by cmake/gitcommit.cmake. + */ + +const char commitid[] = \"${commit}\"; +") diff --git a/cmake/gtk.cmake b/cmake/gtk.cmake new file mode 100644 index 00000000..145f214b --- /dev/null +++ b/cmake/gtk.cmake @@ -0,0 +1,85 @@ +# Look for GTK, of any version. + +set(PUTTY_GTK_VERSION "ANY" + CACHE STRING "Which major version of GTK to build with") +set_property(CACHE PUTTY_GTK_VERSION + PROPERTY STRINGS ANY 3 2 1) + +set(GTK_FOUND FALSE) + +macro(try_pkg_config_gtk VER PACKAGENAME) + if(NOT GTK_FOUND AND + (PUTTY_GTK_VERSION STREQUAL ANY OR PUTTY_GTK_VERSION STREQUAL ${VER})) + find_package(PkgConfig) + pkg_check_modules(GTK ${PACKAGENAME}) + endif() +endmacro() +try_pkg_config_gtk(3 gtk+-3.0) +try_pkg_config_gtk(2 gtk+-2.0) + +if(NOT GTK_FOUND AND + (PUTTY_GTK_VERSION STREQUAL ANY OR PUTTY_GTK_VERSION STREQUAL 1)) + message("-- Checking for GTK1 (via gtk-config)") + find_program(GTK_CONFIG gtk-config) + if(GTK_CONFIG) + execute_process(COMMAND ${GTK_CONFIG} --cflags + OUTPUT_VARIABLE gtk_config_cflags + OUTPUT_STRIP_TRAILING_WHITESPACE + RESULT_VARIABLE gtk_config_cflags_result) + execute_process(COMMAND ${GTK_CONFIG} --libs + OUTPUT_VARIABLE gtk_config_libs + OUTPUT_STRIP_TRAILING_WHITESPACE + RESULT_VARIABLE gtk_config_libs_result) + + if(gtk_config_cflags_result EQUAL 0 AND gtk_config_libs_result EQUAL 0) + + set(GTK_INCLUDE_DIRS) + set(GTK_LIBRARY_DIRS) + set(GTK_LIBRARIES) + + separate_arguments(gtk_config_cflags NATIVE_COMMAND + ${gtk_config_cflags}) + foreach(opt ${gtk_config_cflags}) + string(REGEX MATCH "^-I" ok ${opt}) + if(ok) + string(REGEX REPLACE "^-I" "" optval ${opt}) + list(APPEND GTK_INCLUDE_DIRS ${optval}) + endif() + endforeach() + + separate_arguments(gtk_config_libs NATIVE_COMMAND + ${gtk_config_libs}) + foreach(opt ${gtk_config_libs}) + string(REGEX MATCH "^-l" ok ${opt}) + if(ok) + list(APPEND GTK_LIBRARIES ${opt}) + endif() + string(REGEX MATCH "^-L" ok ${opt}) + if(ok) + string(REGEX REPLACE "^-L" "" optval ${opt}) + list(APPEND GTK_LIBRARY_DIRS ${optval}) + endif() + endforeach() + + message("-- Found GTK1") + set(GTK_FOUND TRUE) + endif() + endif() +endif() + +if(GTK_FOUND) + # Check for some particular Pango functions. + function(pango_check_subscope) + set(CMAKE_REQUIRED_INCLUDES ${GTK_INCLUDE_DIRS}) + set(CMAKE_REQUIRED_LIBRARIES ${GTK_LIBRARIES}) + check_symbol_exists(pango_font_family_is_monospace "pango/pango.h" + HAVE_PANGO_FONT_FAMILY_IS_MONOSPACE) + check_symbol_exists(pango_font_map_list_families "pango/pango.h" + HAVE_PANGO_FONT_MAP_LIST_FAMILIES) + set(HAVE_PANGO_FONT_FAMILY_IS_MONOSPACE + ${HAVE_PANGO_FONT_FAMILY_IS_MONOSPACE} PARENT_SCOPE) + set(HAVE_PANGO_FONT_MAP_LIST_FAMILIES + ${HAVE_PANGO_FONT_MAP_LIST_FAMILIES} PARENT_SCOPE) + endfunction() + pango_check_subscope() +endif() diff --git a/cmake/licence.cmake b/cmake/licence.cmake new file mode 100644 index 00000000..e356ae93 --- /dev/null +++ b/cmake/licence.cmake @@ -0,0 +1,39 @@ +# Pure cmake script to generate licence.h from LICENCE + +file(READ "${LICENCE_FILE}" LICENCE_TEXT) + +function(c_string_escape outvar value) + string(REPLACE "\\" "\\\\" value "${value}") + string(REPLACE "\"" "\\\"" value "${value}") + set("${outvar}" "${value}" PARENT_SCOPE) +endfunction() + +set(copyright_regex "PuTTY is copyright ([0-9]+-[0-9]+ [^\n]*[^\n.])\\.?\n") +string(REGEX MATCH "${copyright_regex}" COPYRIGHT_NOTICE "${LICENCE_TEXT}") +string(REGEX REPLACE "${copyright_regex}" "\\1" + COPYRIGHT_NOTICE "${COPYRIGHT_NOTICE}") +c_string_escape(COPYRIGHT_NOTICE "${COPYRIGHT_NOTICE}") + +string(REGEX REPLACE "\n$" "" LICENCE_TEXT "${LICENCE_TEXT}") +string(REPLACE "\r" "" LICENCE_TEXT "${LICENCE_TEXT}") +string(REPLACE "\n\n" "\r" LICENCE_TEXT "${LICENCE_TEXT}") +string(REPLACE "\n" " " LICENCE_TEXT "${LICENCE_TEXT}") +string(REPLACE "\r" "\n" LICENCE_TEXT "${LICENCE_TEXT}") + +c_string_escape(LICENCE_TEXT "${LICENCE_TEXT}") +string(REPLACE "\n" "\" \\\n parsep \\\n \"" + LICENCE_TEXT "${LICENCE_TEXT}") + +file(WRITE "${OUTPUT_FILE}" "\ +/* + * licence.h - macro definitions for the PuTTY licence. + * + * Generated by cmake/licence.cmake from ./LICENCE. + * You should edit those files rather than editing this one. + */ + +#define LICENCE_TEXT(parsep) \\ + \"${LICENCE_TEXT}\" + +#define SHORT_COPYRIGHT_DETAILS \"${COPYRIGHT_NOTICE}\" +") diff --git a/cmake/platforms/unix.cmake b/cmake/platforms/unix.cmake new file mode 100644 index 00000000..1bd905c6 --- /dev/null +++ b/cmake/platforms/unix.cmake @@ -0,0 +1,121 @@ +set(PLATFORM_SUBDIRS charset unix) + +set(PUTTY_GSSAPI DYNAMIC + CACHE STRING "Build PuTTY with dynamically or statically linked \ +Kerberos / GSSAPI support, if possible") +set_property(CACHE PUTTY_GSSAPI + PROPERTY STRINGS DYNAMIC STATIC OFF) + +include(CheckIncludeFile) +include(CheckLibraryExists) +include(CheckSymbolExists) +include(CheckCSourceCompiles) + +set(CMAKE_REQUIRED_DEFINITIONS ${CMAKE_REQUIRED_DEFINITIONS} + -D_DEFAULT_SOURCE -D_GNU_SOURCE) + +check_include_file(sys/auxv.h HAVE_SYS_AUXV_H) +check_include_file(asm/hwcap.h HAVE_ASM_HWCAP_H) +check_include_file(sys/sysctl.h HAVE_SYS_SYSCTL_H) +check_include_file(sys/types.h HAVE_SYS_TYPES_H) +check_include_file(glob.h HAVE_GLOB_H) + +check_symbol_exists(futimes "sys/time.h" HAVE_FUTIMES) +check_symbol_exists(getaddrinfo "sys/types.h;sys/socket.h;netdb.h" + HAVE_GETADDRINFO) +check_symbol_exists(posix_openpt "stdlib.h;fcntl.h" HAVE_POSIX_OPENPT) +check_symbol_exists(ptsname "stdlib.h" HAVE_PTSNAME) +check_symbol_exists(setresuid "unistd.h" HAVE_SETRESUID) +check_symbol_exists(setresgid "unistd.h" HAVE_SETRESGID) +check_symbol_exists(strsignal "string.h" HAVE_STRSIGNAL) +check_symbol_exists(updwtmpx "utmpx.h" HAVE_UPDWTMPX) +check_symbol_exists(fstatat "sys/types.h;sys/stat.h;unistd.h" HAVE_FSTATAT) +check_symbol_exists(dirfd "sys/types.h;dirent.h" HAVE_DIRFD) +check_symbol_exists(setpwent "sys/types.h;pwd.h" HAVE_SETPWENT) +check_symbol_exists(endpwent "sys/types.h;pwd.h" HAVE_ENDPWENT) +check_symbol_exists(getauxval "sys/auxv.h" HAVE_GETAUXVAL) +check_symbol_exists(CLOCK_MONOTONIC "time.h" HAVE_CLOCK_MONOTONIC) +check_symbol_exists(clock_gettime "time.h" HAVE_CLOCK_GETTIME) + +check_c_source_compiles(" +#define _GNU_SOURCE +#include +#include +int main(int argc, char **argv) { + struct ucred cr; + socklen_t crlen = sizeof(cr); + return getsockopt(0, SOL_SOCKET, SO_PEERCRED, &cr, &crlen) + + cr.pid + cr.uid + cr.gid; +}" HAVE_SO_PEERCRED) + +if(HAVE_GETADDRINFO AND PUTTY_IPV6) + set(NO_IPV6 OFF) +else() + set(NO_IPV6 ON) +endif() + +include(cmake/gtk.cmake) + +find_package(X11) +if(NOT X11_FOUND) + set(NOT_X_WINDOWS ON) +else() + set(NOT_X_WINDOWS OFF) +endif() + +include_directories(${CMAKE_SOURCE_DIR}/charset ${GTK_INCLUDE_DIRS} ${X11_INCLUDE_DIR}) +link_directories(${GTK_LIBRARY_DIRS}) + +function(add_optional_system_lib library testfn) + check_library_exists(${library} ${testfn} "" HAVE_LIB${library}) + if (HAVE_LIB${library}) + set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES};-l${library}) + link_libraries(-l${library}) + endif() +endfunction() + +add_optional_system_lib(m pow) +add_optional_system_lib(rt clock_gettime) +add_optional_system_lib(xnet socket) + +if(PUTTY_GSSAPI STREQUAL DYNAMIC) + add_optional_system_lib(dl dlopen) + if(HAVE_NO_LIBdl) + message(WARNING + "Could not find libdl -- cannot provide dynamic GSSAPI support") + set(NO_GSSAPI ON) + endif() +endif() + +if(PUTTY_GSSAPI STREQUAL STATIC) + find_package(PkgConfig) + pkg_check_modules(KRB5 krb5-gssapi) + if(KRB5_FOUND) + include_directories(${KRB5_INCLUDE_DIRS}) + link_directories(${KRB5_LIBRARY_DIRS}) + link_libraries(${KRB5_LIBRARIES}) + set(STATIC_GSSAPI ON) + else() + message(WARNING + "Could not find krb5 via pkg-config -- \ +cannot provide static GSSAPI support") + set(NO_GSSAPI ON) + endif() +endif() + +if(STRICT AND (CMAKE_C_COMPILER_ID MATCHES "GNU" OR + CMAKE_C_COMPILER_ID MATCHES "Clang")) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Werror -Wpointer-arith -Wvla") +endif() + +function(installed_program target) + if(CMAKE_VERSION VERSION_LESS 3.14) + # CMake 3.13 and earlier required an explicit install destination. + install(TARGETS ${target} RUNTIME DESTINATION bin) + else() + # 3.14 and above selects a sensible default, which we should avoid + # overriding here so that end users can override it using + # CMAKE_INSTALL_BINDIR. + install(TARGETS ${target}) + endif() +endfunction() diff --git a/cmake/platforms/windows.cmake b/cmake/platforms/windows.cmake new file mode 100644 index 00000000..3a73c6b9 --- /dev/null +++ b/cmake/platforms/windows.cmake @@ -0,0 +1,171 @@ +set(PLATFORM_SUBDIRS windows) + +# I copied this over from the pre-CMake build system just to prove it +# still worked, but I should probably remove it now, together with all +# the #ifdefs that depend on it. +# +# Rationale: it was there so that you could do dev builds of PuTTY on +# compilers designed for the pre-NT single-user versions of Windows +# (Win95, Win98 etc). But we're not supporting those development +# environments any more! +set(PUTTY_NO_SECURITY OFF + CACHE BOOL "OBSOLETE AND DANGEROUS - DO NOT DEFINE! \ +Build PuTTY without any use of the Windows security APIs.") +set(PUTTY_MINEFIELD OFF + CACHE BOOL "Build PuTTY with its built-in memory debugger 'Minefield'") +set(PUTTY_GSSAPI ON + CACHE BOOL "Build PuTTY with GSSAPI support") +set(PUTTY_LINK_MAPS OFF + CACHE BOOL "Attempt to generate link maps") +set(PUTTY_EMBEDDED_CHM_FILE "" + CACHE FILEPATH "Path to a .chm help file to embed in the binaries") + +function(define_negation newvar oldvar) + if(${oldvar}) + set(${newvar} OFF PARENT_SCOPE) + else() + set(${newvar} ON PARENT_SCOPE) + endif() +endfunction() + +include(CheckIncludeFiles) +include(CheckSymbolExists) +include(CheckCSourceCompiles) + +# Still needed for AArch32 Windows builds +set(CMAKE_REQUIRED_DEFINITIONS -D_ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE) + +check_include_files("windows.h;winresrc.h" HAVE_WINRESRC_H) +check_include_files("windows.h;winres.h" HAVE_WINRES_H) +check_include_files("windows.h;win.h" HAVE_WIN_H) +check_include_files("stdint.h" HAVE_STDINT_H) +define_negation(HAVE_NO_STDINT_H HAVE_STDINT_H) + +check_include_files("windows.h;multimon.h" HAVE_MULTIMON_H) +define_negation(NO_MULTIMON HAVE_MULTIMON_H) + +check_include_files("windows.h;htmlhelp.h" HAVE_HTMLHELP_H) +define_negation(NO_HTMLHELP HAVE_HTMLHELP_H) + +check_symbol_exists(SecureZeroMemory "windows.h" HAVE_SECUREZEROMEMORY) +define_negation(NO_SECUREZEROMEMORY HAVE_SECUREZEROMEMORY) + +check_symbol_exists(strtoumax "inttypes.h" HAVE_STRTOUMAX) +check_symbol_exists(AddDllDirectory "windows.h" HAVE_ADDDLLDIRECTORY) +check_symbol_exists(SetDefaultDllDirectories "windows.h" + HAVE_SETDEFAULTDLLDIRECTORIES) +check_symbol_exists(GetNamedPipeClientProcessId "windows.h" + HAVE_GETNAMEDPIPECLIENTPROCESSID) + +check_c_source_compiles(" +#include +GCP_RESULTSW gcpw; +int main(void) { return 0; } +" HAVE_GCP_RESULTSW) + +set(NO_SECURITY ${PUTTY_NO_SECURITY}) + +add_compile_definitions( + _WINDOWS + _CRT_SECURE_NO_WARNINGS + _WINSOCK_DEPRECATED_NO_WARNINGS + _ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE) + +if(PUTTY_MINEFIELD) + add_compile_definitions(MINEFIELD) +endif() +if(NOT PUTTY_GSSAPI) + add_compile_definitions(NO_GSSAPI) +endif() +if(PUTTY_EMBEDDED_CHM_FILE) + add_compile_definitions("EMBEDDED_CHM_FILE=\"${PUTTY_EMBEDDED_CHM_FILE}\"") +endif() + +if(WINELIB) + enable_language(RC) + set(LFLAG_MANIFEST_NO "") +elseif(CMAKE_C_COMPILER_ID MATCHES "MSVC" OR + CMAKE_C_COMPILER_FRONTEND_VARIANT MATCHES "MSVC") + set(CMAKE_RC_FLAGS "${CMAKE_RC_FLAGS} /nologo /C1252") + set(LFLAG_MANIFEST_NO "/manifest:no") +else() + set(CMAKE_RC_FLAGS "${CMAKE_RC_FLAGS} -c1252") + set(LFLAG_MANIFEST_NO "") +endif() + +if(STRICT AND (CMAKE_C_COMPILER_ID MATCHES "GNU" OR + CMAKE_C_COMPILER_ID MATCHES "Clang")) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Werror -Wpointer-arith -Wvla") +endif() + +if(CMAKE_C_COMPILER_ID MATCHES "MSVC") + # Turn off some warnings that I've just found too noisy. + # + # - 4244, 4267: "possible loss of data" when narrowing an integer + # type (separate warning numbers for initialisers and + # assignments). Every time I spot-check instances of this, they + # turn out to be sensible (e.g. something was already checked, or + # was assigned from a previous variable that must have been in + # range). I don't think putting a warning-suppression idiom at + # every one of these sites would improve code legibility. + # + # - 4018: "signed/unsigned mismatch" in integer comparison. Again, + # comes up a lot, and generally my spot checks make it look as if + # it's OK. + # + # - 4235: applying unary '-' to an unsigned type. We do that all + # the time in deliberate bit-twiddling code like mpint.c or + # crypto implementations. + # + # - 4293: warning about undefined behaviour if a shift count is too + # big. We often do this inside a ?: clause which doesn't evaluate + # the overlong shift unless the shift count _isn't_ too big. When + # the shift count is constant, MSVC spots the potential problem + # in one branch of the ?:, but doesn't also spot that that branch + # isn't ever taken, so it complains about a thing that's already + # guarded. + # + # - 4090: different 'const' qualifiers. It's a shame to suppress + # this one, because const mismatches really are a thing I'd + # normally like to be warned about. But MSVC (as of 2017 at + # least) seems to have a bug in which assigning a 'void *' into a + # 'const char **' thinks there's a const-qualifier mismatch. + # There isn't! Both are pointers to modifiable objects. The fact + # that in one case, the modifiable object is a pointer to + # something _else_ const should make no difference. + + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} \ +/wd4244 /wd4267 /wd4018 /wd4146 /wd4293 /wd4090") +endif() + +if(CMAKE_C_COMPILER_FRONTEND_VARIANT MATCHES "MSVC") + set(CMAKE_C_LINK_FLAGS "${CMAKE_C_LINK_FLAGS} /dynamicbase /nxcompat") +endif() + +set(platform_libraries + advapi32.lib comdlg32.lib gdi32.lib imm32.lib + ole32.lib shell32.lib user32.lib ws2_32.lib kernel32.lib) + +# Generate link maps +if(PUTTY_LINK_MAPS) + if(CMAKE_C_COMPILER_ID MATCHES "Clang" AND + "x${CMAKE_C_COMPILER_FRONTEND_VARIANT}" STREQUAL "xMSVC") + set(CMAKE_C_LINK_EXECUTABLE + "${CMAKE_C_LINK_EXECUTABLE} /lldmap:.map") + elseif(CMAKE_C_COMPILER_ID MATCHES "MSVC") + set(CMAKE_C_LINK_EXECUTABLE + "${CMAKE_C_LINK_EXECUTABLE} /map:.map") + else() + message(WARNING + "Don't know how to generate link maps on this toolchain") + endif() +endif() + +# Write out a file in the cmake output directory listing the +# executables that are 'official' enough to want to code-sign and +# ship. +file(WRITE ${CMAKE_BINARY_DIR}/shipped.txt "") +function(installed_program target) + file(APPEND ${CMAKE_BINARY_DIR}/shipped.txt + "${target}${CMAKE_EXECUTABLE_SUFFIX}\n") +endfunction() diff --git a/cmake/setup.cmake b/cmake/setup.cmake new file mode 100644 index 00000000..d55248d9 --- /dev/null +++ b/cmake/setup.cmake @@ -0,0 +1,78 @@ +# Forcibly re-enable assertions, even if we're building in release +# mode. This is a security project - assertions may be enforcing +# security-critical constraints. A backstop #ifdef in defs.h should +# give a #error if this manoeuvre doesn't do what it needs to. +string(REPLACE "/DNDEBUG" "" CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}") +string(REPLACE "-DNDEBUG" "" CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}") + +set(PUTTY_IPV6 ON + CACHE BOOL "Build PuTTY with IPv6 support if possible") +set(PUTTY_DEBUG OFF + CACHE BOOL "Build PuTTY with debug() statements enabled") +set(PUTTY_FUZZING OFF + CACHE BOOL "Build PuTTY binaries suitable for fuzzing, NOT FOR REAL USE") + +set(STRICT OFF + CACHE BOOL "Enable extra compiler warnings and make them errors") + +include(FindGit) + +set(GENERATED_SOURCES_DIR ${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}) + +set(GENERATED_LICENCE_H ${GENERATED_SOURCES_DIR}/licence.h) +set(INTERMEDIATE_LICENCE_H ${GENERATED_LICENCE_H}.tmp) +add_custom_command(OUTPUT ${INTERMEDIATE_LICENCE_H} + COMMAND ${CMAKE_COMMAND} + -DLICENCE_FILE=${CMAKE_SOURCE_DIR}/LICENCE + -DOUTPUT_FILE=${INTERMEDIATE_LICENCE_H} + -P ${CMAKE_SOURCE_DIR}/cmake/licence.cmake + DEPENDS ${CMAKE_SOURCE_DIR}/cmake/licence.cmake ${CMAKE_SOURCE_DIR}/LICENCE) +add_custom_target(generated_licence_h + BYPRODUCTS ${GENERATED_LICENCE_H} + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${INTERMEDIATE_LICENCE_H} ${GENERATED_LICENCE_H} + DEPENDS ${INTERMEDIATE_LICENCE_H} + COMMENT "Updating licence.h") + +set(GENERATED_COMMIT_C ${GENERATED_SOURCES_DIR}/cmake_commit.c) +set(INTERMEDIATE_COMMIT_C ${GENERATED_COMMIT_C}.tmp) +add_custom_target(check_git_commit + BYPRODUCTS ${INTERMEDIATE_COMMIT_C} + COMMAND ${CMAKE_COMMAND} + -DGIT_EXECUTABLE=${GIT_EXECUTABLE} + -DTOPLEVEL_SOURCE_DIR=${CMAKE_SOURCE_DIR} + -DOUTPUT_FILE=${INTERMEDIATE_COMMIT_C} + -P ${CMAKE_SOURCE_DIR}/cmake/gitcommit.cmake + DEPENDS ${CMAKE_SOURCE_DIR}/cmake/gitcommit.cmake + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + COMMENT "Checking current git commit") +add_custom_target(cmake_commit_c + BYPRODUCTS ${GENERATED_COMMIT_C} + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${INTERMEDIATE_COMMIT_C} ${GENERATED_COMMIT_C} + DEPENDS check_git_commit ${INTERMEDIATE_COMMIT_C} + COMMENT "Updating cmake_commit.c") + +function(add_platform_sources_to_library target) + set(sources ${ARGN}) + list(TRANSFORM sources PREPEND ${CMAKE_CURRENT_SOURCE_DIR}/) + target_sources(${target} PRIVATE ${sources}) +endfunction() + +if(CMAKE_SYSTEM_NAME MATCHES "Windows" OR WINELIB) + include(cmake/platforms/windows.cmake) +else() + include(cmake/platforms/unix.cmake) +endif() + +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR} + ${GENERATED_SOURCES_DIR} + ${PLATFORM_SUBDIRS}) + +if(PUTTY_DEBUG) + add_compile_definitions(DEBUG) +endif() +if(PUTTY_FUZZING) + add_compile_definitions(FUZZING) +endif() diff --git a/cmake/toolchain-mingw.cmake b/cmake/toolchain-mingw.cmake new file mode 100644 index 00000000..013dbeb5 --- /dev/null +++ b/cmake/toolchain-mingw.cmake @@ -0,0 +1,10 @@ +# Simple toolchain file for cross-building Windows PuTTY on Linux +# using MinGW (tested on Ubuntu). + +set(CMAKE_SYSTEM_NAME Windows) +set(CMAKE_SYSTEM_PROCESSOR x86_64) + +set(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc) +set(CMAKE_RC_COMPILER x86_64-w64-mingw32-windres) +set(CMAKE_AR x86_64-w64-mingw32-ar) +set(CMAKE_RANLIB x86_64-w64-mingw32-ranlib) diff --git a/cmake/toolchain-winegcc.cmake b/cmake/toolchain-winegcc.cmake new file mode 100644 index 00000000..73a9e53e --- /dev/null +++ b/cmake/toolchain-winegcc.cmake @@ -0,0 +1,33 @@ +# Toolchain file for cross-building a Winelib version of Windows PuTTY +# on Linux, using winegcc (tested on Ubuntu). + +# Winelib is weird because it's basically compiling ordinary Linux +# objects and executables, but we want to pretend to be Windows for +# purposes of (a) having resource files, and (b) selecting the Windows +# platform subdirectory. +# +# So, do we tag this as a weird kind of Windows build, or a weird kind +# of Linux build? Either way we have to do _something_ out of the +# ordinary. +# +# After some experimentation, it seems to make more sense to treat +# Winelib builds as basically Linux, and set a flag WINELIB that +# PuTTY's main build scripts will detect and handle specially. +# Specifically, that flag will cause cmake/setup.cmake to select the +# Windows platform (overriding the usual check of CMAKE_SYSTEM_NAME), +# and also trigger a call to enable_language(RC), which for some kind +# of cmake re-entrancy reason we can't do in this toolchain file +# itself. +set(CMAKE_SYSTEM_NAME Linux) +set(WINELIB ON) + +# We need a wrapper script around winegcc proper, because cmake's link +# command lines will refer to system libraries as "-lkernel32.lib" +# rather than the required "-lkernel32". The winegcc script alongside +# this toolchain file bodges that command-line translation. +set(CMAKE_C_COMPILER ${CMAKE_SOURCE_DIR}/cmake/winegcc) + +set(CMAKE_RC_COMPILER wrc) +set(CMAKE_RC_OUTPUT_EXTENSION .res.o) +set(CMAKE_RC_COMPILE_OBJECT + " -o ") diff --git a/cmake/winegcc b/cmake/winegcc new file mode 100755 index 00000000..fb298ad1 --- /dev/null +++ b/cmake/winegcc @@ -0,0 +1,29 @@ +#!/bin/sh + +# Wrapper for winegcc that allows it to be used in a build generated +# from PuTTY's CMakeLists.txt, by bodging around the command-line +# options that CMake gets wrong. + +init=true +for arg in init "$@"; do + if $init; then + set -- + init=false + continue + fi + + case "$arg" in + # The Windows build definition for PuTTY specifies all the + # system API libraries by names like kernel32.lib. When CMake + # reads that file and thinks it's compiling for Linux, it will + # generate link options such as -lkernel32.lib. But in fact + # winegcc expects -lkernel32, so we need to strip the ".lib" + # suffix. + -l*.lib) set -- "$@" "${arg%.lib}";; + + # Anything else, we leave unchanged. + *) set -- "$@" "$arg";; + esac +done + +exec winegcc "$@" diff --git a/configure.ac b/configure.ac deleted file mode 100644 index 72874f32..00000000 --- a/configure.ac +++ /dev/null @@ -1,264 +0,0 @@ -# To compile this into a configure script, you need: -# * Autoconf 2.59c or newer -# * Gtk (for $prefix/share/aclocal/gtk.m4) -# * Automake (for aclocal) -# If you've got them, running "autoreconf" should work. - -# Version number is substituted by Buildscr for releases, snapshots -# and custom builds out of svn; X.XX shows up in ad-hoc developer -# builds, which shouldn't matter -AC_INIT(putty, X.XX) -AC_CONFIG_FILES([Makefile]) -AC_CONFIG_HEADERS([uxconfig.h:uxconfig.in]) -AM_INIT_AUTOMAKE([-Wall -Werror foreign]) - -AC_PROG_INSTALL -AC_PROG_RANLIB -ifdef([AM_PROG_AR],[AM_PROG_AR]) -AM_PROG_CC_C_O - -AC_PROG_CC_C99 - -# Mild abuse of the '--enable' option format to allow manual -# specification of setuid or setgid setup in pterm. -setidtype=none -AC_ARG_ENABLE([setuid], - [AS_HELP_STRING([--enable-setuid=USER], - [make pterm setuid to a given user])], - [case "$enableval" in - no) setidtype=none;; - *) setidtype=setuid; setidval="$enableval";; - esac]) -AC_ARG_ENABLE([setgid], - [AS_HELP_STRING([--enable-setgid=GROUP], - [make pterm setgid to a given group])], - [case "$enableval" in - no) setidtype=none;; - *) setidtype=setgid; setidval="$enableval";; - esac]) -AM_CONDITIONAL(HAVE_SETID_CMD, [test "$setidtype" != "none"]) -AS_IF([test "x$setidtype" = "xsetuid"], - [SETID_CMD="chown $setidval"; SETID_MODE="4755"]) -AS_IF([test "x$setidtype" = "xsetgid"], - [SETID_CMD="chgrp $setidval"; SETID_MODE="2755"]) -AC_SUBST(SETID_CMD) -AC_SUBST(SETID_MODE) - -AC_ARG_ENABLE([git-commit], - [AS_HELP_STRING([--disable-git-commit], - [disable embedding current git HEAD in binaries])], - [], - [if test -d "$srcdir/.git"; then - enable_git_commit=yes; else enable_git_commit=no; fi]) - -if test "x$enable_git_commit" = "xyes" -a ! -d "$srcdir/.git"; then - AC_ERROR([Cannot --enable-git-commit when source tree is not a git checkout]) -fi -AM_CONDITIONAL(AUTO_GIT_COMMIT, [test "x$enable_git_commit" = "xyes"]) - -AC_ARG_WITH([gssapi], - [AS_HELP_STRING([--without-gssapi], - [disable GSSAPI support])], - [], - [with_gssapi=yes]) - -AC_ARG_WITH([quartz], - [AS_HELP_STRING([--with-quartz], - [build for the MacOS Quartz GTK back end])], - [AC_DEFINE([OSX_GTK], [1], [Define if building with GTK for MacOS.]) - with_quartz=yes], - [with_quartz=no]) - -AM_CONDITIONAL([HAVE_QUARTZ],[test "x$with_quartz" = "xyes"]) - -WITH_GSSAPI= -AS_IF([test "x$with_gssapi" != xno], - [AC_DEFINE([WITH_GSSAPI], [1], [Define if building with GSSAPI support.])]) - -AC_ARG_WITH([gtk], - [AS_HELP_STRING([--with-gtk=VER], - [specify GTK version to use (`1', `2' or `3')]) -AS_HELP_STRING([--without-gtk], - [do not use GTK (build command-line tools only)])], - [gtk_version_desired="$withval"], - [gtk_version_desired="any"]) - -case "$gtk_version_desired" in - 1 | 2 | 3 | any | no) ;; - yes) gtk_version_desired="any" ;; - *) AC_ERROR([Invalid GTK version specified]) -esac - -AC_CHECK_HEADERS([utmpx.h],,,[ -#include -#include ]) - -# Look for GTK 3, GTK 2 and GTK 1, in descending order of preference. -# If we can't find any, have the makefile only build the CLI programs. - -gtk=none - -case "$gtk_version_desired:$gtk" in - 3:none | any:none) - ifdef([AM_PATH_GTK_3_0],[AM_PATH_GTK_3_0([3.0.0], [gtk=3], [])], - [AC_WARNING([generating configure script without GTK 3 autodetection])]) - ;; -esac - -case "$gtk_version_desired:$gtk" in - 2:none | any:none) - ifdef([AM_PATH_GTK_2_0],[AM_PATH_GTK_2_0([2.0.0], [gtk=2], [])], - [AC_WARNING([generating configure script without GTK 2 autodetection])]) - ;; -esac - -case "$gtk_version_desired:$gtk" in - 1:none | any:none) - ifdef([AM_PATH_GTK],[AM_PATH_GTK([1.2.0], [gtk=1], [])],[ - # manual check for gtk1 - AC_PATH_PROG(GTK1_CONFIG, gtk-config, absent) - if test "$GTK1_CONFIG" != "absent"; then - GTK_CFLAGS="`"$GTK1_CONFIG" --cflags`" - GTK_LIBS=`"$GTK1_CONFIG" --libs` - AC_SUBST(GTK_CFLAGS) - AC_SUBST(GTK_LIBS) - gtk=1 - fi - ]) - ;; -esac - -case "$gtk" in - 1) - # Add some manual #defines to make the GTK 1 headers work when - # compiling in C99 mode. Left to themselves, they'll expect the - # old-style pre-C99 GNU semantics of 'inline' and 'extern inline', - # with the effect that they'll end up defining out-of-line - # versions of the inlined functions in more than one translation - # unit and cause a link failure. Override them to 'static inline', - # which is safe. - GTK_CFLAGS="$GTK_CFLAGS -DG_INLINE_FUNC='static inline' -DG_CAN_INLINE=1" -esac - -AM_CONDITIONAL(HAVE_GTK, [test "$gtk" != "none"]) - -if test "$gtk" = "2" -o "$gtk" = "3"; 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_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, - [GTK_LIBS="-lX11 $GTK_LIBS" - AC_DEFINE([HAVE_LIBX11],[],[Define if libX11.a is available])]) - -AC_CHECK_FUNCS([getaddrinfo posix_openpt ptsname setresuid strsignal updwtmpx fstatat dirfd futimes setpwent endpwent getauxval elf_aux_info sysctlbyname]) -AC_CHECK_DECLS([CLOCK_MONOTONIC], [], [], [[#include ]]) -AC_CHECK_HEADERS([sys/auxv.h asm/hwcap.h sys/sysctl.h sys/types.h glob.h]) -AC_SEARCH_LIBS([clock_gettime], [rt], [AC_DEFINE([HAVE_CLOCK_GETTIME],[],[Define if clock_gettime() is available])]) - -AC_CACHE_CHECK([for SO_PEERCRED and dependencies], [x_cv_linux_so_peercred], [ - AC_COMPILE_IFELSE([ - AC_LANG_PROGRAM([[ - #define _GNU_SOURCE - #include - #include - ]],[[ - struct ucred cr; - socklen_t crlen = sizeof(cr); - return getsockopt(0, SOL_SOCKET, SO_PEERCRED, &cr, &crlen) + - cr.pid + cr.uid + cr.gid; - ]] - )], - AS_VAR_SET(x_cv_linux_so_peercred, yes), - AS_VAR_SET(x_cv_linux_so_peercred, no) - ) -]) -AS_IF([test AS_VAR_GET(x_cv_linux_so_peercred) = yes], - [AC_DEFINE([HAVE_SO_PEERCRED], [1], - [Define if SO_PEERCRED works in the Linux fashion.])] -) - -if test "x$GCC" = "xyes"; then - : - AC_SUBST(WARNINGOPTS, ['-Wall -Werror -Wpointer-arith -Wvla']) -else - : - AC_SUBST(WARNINGOPTS, []) -fi - -AC_SEARCH_LIBS([pow], [m]) - -AC_OUTPUT - -if test "$gtk_version_desired" = "no"; then cat <= 0 and ($ARGV[0] eq "-u" or $ARGV[0] eq "-U")) { - # Convenience for Unix users: -u means that after we finish what - # we're doing here, we also run mkauto.sh and then 'configure' in - # the Unix subdirectory. So it's a one-stop shop for regenerating - # the actual end-product Unix makefile. - # - # Arguments supplied after -u go to configure. - # - # -U is identical, but runs 'configure' at the _top_ level, for - # people who habitually do that. - $do_unix = ($ARGV[0] eq "-U" ? 2 : 1); - shift @ARGV; - @confargs = @ARGV; -} - -open IN, "Recipe" or do { - # We want to deal correctly with being run from one of the - # subdirs in the source tree. So if we can't find Recipe here, - # try one level up. - 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, and licence.h is likewise generated by licence.pl. We -# need to generate those _now_, before attempting dependency analysis. -eval 'chdir "charset"; require "./sbcsgen.pl"; chdir ".."; select STDOUT;'; -@orig_ARGV = @ARGV; -@ARGV = ("--header", "-o", "licence.h"); -eval 'require "./licence.pl"; select STDOUT;'; -@ARGV = @orig_ARGV; - -@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 () { - 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 "!cflags" and &mfval($_[1])) { - ($rest = $_) =~ s/^\s*\S+\s+\S+\s+\S+\s*//; # find rest of input line - if ($rest eq "") { - # Make sure this file doesn't get lumped together with any - # other file's cflags. - $rest = "F" . $_[2]; - } else { - # Give this file a specific set of cflags, but permit it to - # go together with other files using the same set. - $rest = "C" . $rest; - } - $cflags{$_[1]}->{$_[2]} = $rest; - next; - } - if ($_[0] eq "!forceobj") { $forceobj{$_[1]} = 1; next; } - if ($_[0] eq "!begin") { - if ($_[1] =~ /^>(.*)/) { - $divert = \$auxfiles{$1}; - } elsif (&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 && $_[1] eq "=") { - $groups{$_[0]} = [] if !defined $groups{$_[0]}; - $listref = $groups{$_[0]}; - $prog = undef; - shift @objs; # eat the group name - } elsif ($#_ >= 1 && $_[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 =~ /^\[([A-Z]*)\]$/) and defined $prog) { - $type = substr($i,1,(length $i)-2); - die "unrecognised program type for $prog [$type]\n" - if ! grep { $type eq $_ } qw(G C X U MX XT UT); - } 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; - -foreach $aux (sort keys %auxfiles) { - open AUX, ">$aux"; - print AUX $auxfiles{$aux}; - close AUX; -} - -# Now retrieve the complete list of objects and resource files, and -# construct dependency data for them. While we're here, expand the -# object list for each program, and complain if its type isn't set. -@prognames = sort keys %programs; -%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 = (); -%allsourcefiles = (); # this is wanted by some makefiles -while (scalar @scanlist > 0) { - $file = shift @scanlist; - next if defined $further{$file}; # skip if we've already done it - $further{$file} = []; - $dirfile = &findfile($file); - $allsourcefiles{$dirfile} = 1; - open IN, "$dirfile" or die "unable to open source file $file\n"; - while () { - chomp; - /^\s*#include\s+\"([^\"]+)\"/ and do { - push @{$further{$file}}, $1; - 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","lcc","devcppproj","gtk","unix", - "am","osx","vstudio10","vstudio12","clangcl")) { - 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 (); -} - -$orig_dir = cwd; - -# Now we're ready to output the actual Makefiles. - -if (defined $makefiles{'clangcl'}) { - $dirpfx = &dirpfx($makefiles{'clangcl'}, "/"); - - ##-- Makefile for cross-compiling using clang-cl, lld-link, and - ## MinGW's windres for resource compilation. - # - # This makefile allows a complete Linux-based cross-compile, but - # using the real Visual Studio header files and libraries. In - # order to run it, you will need: - # - # - clang-cl, llvm-rc and lld-link on your PATH. - # * I built these from the up-to-date LLVM project trunk git - # repositories, as of 2018-05-29. - # - case-mashed copies of the Visual Studio include directories. - # * On a real VS installation, run vcvars32.bat and look at - # the resulting value of %INCLUDE%. Take a full copy of each - # of those directories, and inside the copy, for each - # include file that has an uppercase letter in its name, - # make a lowercased symlink to it. Additionally, one of the - # directories will contain files called driverspecs.h and - # specstrings.h, and those will need symlinks called - # DriverSpecs.h and SpecStrings.h. - # * Now, on Linux, define the environment variable INCLUDE to - # be a list, separated by *semicolons* (in the Windows - # style), of those directories, but before all of them you - # must also include lib/clang/5.0.0/include from the clang - # installation area (which contains in particular a - # clang-compatible stdarg.h overriding the Visual Studio - # one). - # - similarly case-mashed copies of the library directories. - # * Again, on a real VS installation, run vcvars32 or - # vcvarsx86_amd64 (as appropriate), look at %LIB%, make a - # copy of each directory, and provide symlinks within that - # directory so that all the files can be opened as - # lowercase. - # * Then set LIB to be a semicolon-separated list of those - # directories (but you'll need to change which set of - # directories depending on whether you want to do a 32-bit - # or 64-bit build). - # - for a 64-bit build, set 'Platform=x64' in the environment as - # well, or else on the make command line. - # * This is a variable understood only by this makefile - none - # of the tools we invoke will know it - but it's consistent - # with the way the VS scripts like vcvarsx86_amd64.bat set - # things up, and since the environment has to change - # _anyway_ between 32- and 64-bit builds (different set of - # paths in $LIB) it's reasonable to have the choice of - # compilation target driven by another environment variable - # set in parallel with that one. - # - for older versions of the VS libraries you may also have to - # set EXTRA_console and/or EXTRA_windows to the name of an - # object file manually extracted from one of those libraries. - # * This is because old VS seems to manage its startup code by - # having libcmt.lib contain lots of *crt0.obj objects, one - # for each possible user entry point (main, WinMain and the - # wide-char versions of both), of which the linker arranges - # to include the right one by special-case code. But lld - # only seems to mimic half of that code - it does include - # the right crt0 object, but it doesn't also deliberately - # _avoid_ including the _wrong_ ones, and since all those - # objects define a common set of global symbols for other - # parts of the library to use, lld may well select an - # arbitrary one of them the first time it sees a reference - # to one of those global symbols, and then later also select - # the _right_ one for the application's entry point, causing - # a multiple-definitions crash. - # * So the workaround is to explicitly include the right - # *crt0.obj file on the linker command line before lld even - # begins searching libraries. Hence, for a console - # application, you might extract crt0.obj from the library - # in question and set EXTRA_console=crt0.obj, and for a GUI - # application, do the same with wincrt0.obj. Then this - # makefile will include the right one of those objects - # alongside the matching /subsystem linker option. - # - also for older versions of the VS libraries, you may also - # have to set EXTRA_libs to include extra library files. - - open OUT, ">$makefiles{'clangcl'}"; select OUT; - print - "# Makefile for cross-compiling $project_name using clang-cl, lld-link,\n". - "# and llvm-rc, using GNU make on Linux.\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". - "CCCMD = clang-cl\n". - "RCCMD = llvm-rc\n". - "ifeq (\$(Platform),x64)\n". - "CCTARGET = x86_64-pc-windows-msvc18.0.0\n". - "PLATFORMCFLAGS =\n". - "else ifeq (\$(Platform),arm)\n". - "CCTARGET = arm-pc-windows-msvc18.0.0\n". - "PLATFORMCFLAGS = /D_ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE /GS-\n". - "else ifeq (\$(Platform),arm64)\n". - "CCTARGET = arm64-pc-windows-msvc18.0.0\n". - "PLATFORMCFLAGS = /D_ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE /GS-\n". - "else\n". - "CCTARGET = i386-pc-windows-msvc18.0.0\n". - "PLATFORMCFLAGS =\n". - "endif\n". - "CC = \$(CCCMD)\n". - "RC = \$(RCCMD) /c 1252 \n". - "RCPREPROC = \$(CCCMD) /P /TC\n". - "LD = lld-link\n". - "\n". - "# C compilation flags\n". - &splitline("CFLAGS = --target=\$(CCTARGET) /nologo /W3 /O1 -Wvla " . - (join " ", map {"-I$dirpfx$_"} @srcdirs) . - " /D_WINDOWS /D_WIN32_WINDOWS=0x500 /DWINVER=0x500 ". - "/D_CRT_SECURE_NO_WARNINGS /D_WINSOCK_DEPRECATED_NO_WARNINGS"). - " -Werror \$(PLATFORMCFLAGS)\n". - "LFLAGS = /incremental:no /dynamicbase /nxcompat\n". - &splitline("RCPPFLAGS = ".(join " ", map {"-I$dirpfx$_"} @srcdirs). - " -DWIN32 -D_WIN32 -DWINVER=0x0400")." \$(RCFL)\n". - "\n". - &def($makefile_extra{'clangcl'}->{'vars'}) . - "\n". - "\n"; - print &splitline("all:" . join "", map { " \$(BUILDDIR)$_.exe" } &progrealnames("G:C")); - print "\n\n"; - foreach $p (&prognames("G:C")) { - ($prog, $type) = split ",", $p; - $objstr = &objects($p, "\$(BUILDDIR)X.obj", "\$(BUILDDIR)X.res", undef); - print &splitline("\$(BUILDDIR)$prog.exe: " . $objstr), "\n"; - - $objstr = &objects($p, "\$(BUILDDIR)X.obj", "\$(BUILDDIR)X.res", "X.lib"); - $subsys = ($type eq "G") ? "windows" : "console"; - print &splitline("\t\$(LD) \$(LFLAGS) \$(XLFLAGS) ". - "/out:\$(BUILDDIR)$prog.exe ". - "/lldmap:\$(BUILDDIR)$prog.map ". - "/subsystem:$subsys\$(SUBSYSVER) ". - "\$(EXTRA_$subsys) $objstr \$(EXTRA_libs)")."\n\n"; - } - my $rc_pp_rules = ""; - foreach $d (&deps("\$(BUILDDIR)X.obj", "\$(BUILDDIR)X.res", $dirpfx, "/", "vc")) { - $extradeps = $forceobj{$d->{obj_orig}} ? ["*.c","*.h","*.rc"] : []; - my $rule; - my @deps = @{$d->{deps}}; - my @incdeps = grep { m!\.rc2?$! } @deps; - my @rcdeps = grep { ! m!\.rc2$! } @deps; - if ($d->{obj} =~ /\.res$/) { - my $rc = $deps[0]; - my $rcpp = $rc; - $rcpp =~ s!.*/!!; - $rcpp =~ s/\.rc$/.rcpp/; - $rcpp = "\$(BUILDDIR)" . $rcpp; - $rule = "\$(RC) ".$rcpp." /FO ".$d->{obj}; - $rc_pp_rules .= &splitline( - sprintf("%s: %s", $rcpp, join " ", @incdeps)) ."\n" . - "\t\$(RCPREPROC) \$(RCPPFLAGS) /Fi\$\@ \$<\n\n"; - $rcdeps[0] = $rcpp; - } else { - $rule = "\$(CC) /Fo\$(BUILDDIR) \$(COMPAT) \$(CFLAGS) \$(XFLAGS) /c \$<"; - } - print &splitline(sprintf("%s: %s", $d->{obj}, - join " ", @$extradeps, @rcdeps)), "\n"; - print "\t" . $rule . "\n\n"; - } - print "\n" . $rc_pp_rules; - print &def($makefile_extra{'clangcl'}->{'end'}); - print "\nclean:\n". - &splitline("\trm -f \$(BUILDDIR)*.obj \$(BUILDDIR)*.exe ". - "\$(BUILDDIR)*.rcpp \$(BUILDDIR)*.res \$(BUILDDIR)*.map ". - "\$(BUILDDIR)*.exe.manifest")."\n"; - select STDOUT; close OUT; -} - -if (defined $makefiles{'cygwin'}) { - $dirpfx = &dirpfx($makefiles{'cygwin'}, "/"); - - ##-- MinGW/CygWin makefile (called 'cygwin' for historical reasons) - open OUT, ">$makefiles{'cygwin'}"; select OUT; - print - "# Makefile for $project_name under MinGW, Cygwin, or Winelib.\n". - "#\n# This file was created by `mkfiles.pl' from the `Recipe' file.\n". - "# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead.\n"; - # gcc command line option is -D not /D - ($_ = $help) =~ s/([=" ])\/D/$1-D/gs; - print $_; - 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". - "# TOOLPATH = i686-w64-mingw32-\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 = -Wall -O2 -std=gnu99 -Wvla -D_WINDOWS". - " -DWIN32S_COMPAT -D_NO_OLDNAMES -D__USE_MINGW_ANSI_STDIO=1 " . - (join " ", map {"-I$dirpfx$_"} @srcdirs)) . - "\n". - "LDFLAGS = -s\n". - &splitline("RCFLAGS = \$(RCINC) --define WIN32=1 --define _WIN32=1 ". - "--define WINVER=0x0400 ".(join " ", map {"-I$dirpfx$_"} @srcdirs))."\n". - "\n". - &def($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]." -o ".$d->{obj}."\n\n"; - } else { - print "\t\$(CC) \$(COMPAT) \$(CFLAGS) \$(XFLAGS) -c ".$d->{deps}->[0]."\n\n"; - } - } - print "\n"; - print &def($makefile_extra{'cygwin'}->{'end'}); - print "\nclean:\n". - "\trm -f *.o *.exe *.res.o *.so *.map\n". - "\n". - "FORCE:\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 /D_CRT_SECURE_NO_WARNINGS /D_ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE\n". - "LFLAGS = /incremental:no /dynamicbase /nxcompat\n". - "RCFLAGS = ".(join " ", map {"-I$dirpfx$_"} @srcdirs). - " -DWIN32 -D_WIN32 -DWINVER=0x0400\n". - "\n". - &def($makefile_extra{'vc'}->{'vars'}) . - "\n". - "\n"; - print &splitline("all:" . join "", map { " \$(BUILDDIR)$_.exe" } &progrealnames("G:C")); - print "\n\n"; - foreach $p (&prognames("G:C")) { - ($prog, $type) = split ",", $p; - $objstr = &objects($p, "\$(BUILDDIR)X.obj", "\$(BUILDDIR)X.res", undef); - print &splitline("\$(BUILDDIR)$prog.exe: " . $objstr), "\n"; - - $objstr = &objects($p, "\$(BUILDDIR)X.obj", "\$(BUILDDIR)X.res", "X.lib"); - $subsys = ($type eq "G") ? "windows" : "console"; - $inlinefilename = "link_$prog"; - print "\ttype <<$inlinefilename\n"; - @objlist = split " ", $objstr; - @objlines = (""); - foreach $i (@objlist) { - if (length($objlines[$#objlines] . " $i") > 72) { - push @objlines, ""; - } - $objlines[$#objlines] .= " $i"; - } - for ($i=0; $i<=$#objlines; $i++) { - print "$objlines[$i]\n"; - } - print "<<\n"; - print "\tlink \$(LFLAGS) \$(XLFLAGS) -out:\$(BUILDDIR)$prog.exe -map:\$(BUILDDIR)$prog.map -nologo -subsystem:$subsys\$(SUBSYSVER) \@$inlinefilename\n\n"; - } - foreach $d (&deps("\$(BUILDDIR)X.obj", "\$(BUILDDIR)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} =~ /.res$/) { - print "\trc /Fo@{[$d->{obj}]} \$(RCFL) -r \$(RCFLAGS) ".$d->{deps}->[0],"\n\n"; - } - } - print "\n"; - foreach $real_srcdir ("", @srcdirs) { - $srcdir = $real_srcdir; - if ($srcdir ne "") { - $srcdir =~ s!/!\\!g; - $srcdir = $dirpfx . $srcdir; - $srcdir =~ s!\\\.\\!\\!; - $srcdir = "{$srcdir}"; - } - # The double colon at the end of the line makes this a - # 'batch-mode inference rule', which means that nmake will - # aggregate multiple invocations of the rule and issue just - # one cl command with multiple source-file arguments. That - # noticeably speeds up builds, since starting up the cl - # process is a noticeable overhead and now has to be done far - # fewer times. - print "${srcdir}.c.obj::\n\tcl /Fo\$(BUILDDIR) \$(COMPAT) \$(CFLAGS) \$(XFLAGS) /c \$<\n\n"; - } - print &def($makefile_extra{'vc'}->{'end'}); - print "\nclean: tidy\n". - "\t-del \$(BUILDDIR)*.exe\n\n". - "tidy:\n". - "\t-del \$(BUILDDIR)*.obj\n". - "\t-del \$(BUILDDIR)*.res\n". - "\t-del \$(BUILDDIR)*.pch\n". - "\t-del \$(BUILDDIR)*.aps\n". - "\t-del \$(BUILDDIR)*.ilk\n". - "\t-del \$(BUILDDIR)*.pdb\n". - "\t-del \$(BUILDDIR)*.rsp\n". - "\t-del \$(BUILDDIR)*.dsp\n". - "\t-del \$(BUILDDIR)*.dsw\n". - "\t-del \$(BUILDDIR)*.ncb\n". - "\t-del \$(BUILDDIR)*.opt\n". - "\t-del \$(BUILDDIR)*.plg\n". - "\t-del \$(BUILDDIR)*.map\n". - "\t-del \$(BUILDDIR)*.idb\n". - "\t-del \$(BUILDDIR)debug.log\n"; - select STDOUT; close OUT; -} - -if (defined $makefiles{'vcproj'}) { - $dirpfx = &dirpfx($makefiles{'vcproj'}, "\\"); - - ##-- 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{'vstudio10'} || defined $makefiles{'vstudio12'}) { - - ##-- Visual Studio 2010+ Solution and Projects - - if (defined $makefiles{'vstudio10'}) { - create_vs_solution('vstudio10', "2010", "11.00", "v100"); - } - - if (defined $makefiles{'vstudio12'}) { - create_vs_solution('vstudio12', "2012", "12.00", "v110"); - } - - sub create_vs_solution { - my ($makefilename, $name, $version, $toolsver) = @_; - - $dirpfx = &dirpfx($makefiles{$makefilename}, "\\"); - - @deps = &deps("X.obj", "X.res", $dirpfx, "\\", $makefilename); - %all_object_deps = map {$_->{obj} => $_->{deps}} @deps; - - my @prognames = &prognames("G:C"); - - # Create the solution file. - mkdir $makefiles{$makefilename} - if(! -f $makefiles{$makefilename}); - chdir $makefiles{$makefilename}; - - open OUT, ">$project_name.sln"; select OUT; - - print - "Microsoft Visual Studio Solution File, Format Version $version\n" . - "# Visual Studio $name\n"; - - my %projguids = (); - foreach $progname (@prognames) { - ($windows_project, $type) = split ",", $progname; - - $projguids{$windows_project} = $guid = - &invent_guid("project:$progname"); - - print - "Project(\"{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}\") = \"$windows_project\", \"$windows_project\\$windows_project.vcxproj\", \"{$guid}\"\n" . - "EndProject\n"; - } - - print - "Global\n" . - " GlobalSection(SolutionConfigurationPlatforms) = preSolution\n" . - " Debug|Win32 = Debug|Win32\n" . - " Release|Win32 = Release|Win32\n" . - " EndGlobalSection\n" . - " GlobalSection(ProjectConfigurationPlatforms) = postSolution\n" ; - - foreach my $projguid (values %projguids) { - print - " {$projguid}.Debug|Win32.ActiveCfg = Debug|Win32\n" . - " {$projguid}.Debug|Win32.Build.0 = Debug|Win32\n" . - " {$projguid}.Release|Win32.ActiveCfg = Release|Win32\n" . - " {$projguid}.Release|Win32.Build.0 = Release|Win32\n"; - } - - print - " EndGlobalSection\n" . - " GlobalSection(SolutionProperties) = preSolution\n" . - " HideSolutionNode = FALSE\n" . - " EndGlobalSection\n" . - "EndGlobal\n"; - - select STDOUT; close OUT; - - foreach $progname (@prognames) { - ($windows_project, $type) = split ",", $progname; - create_vs_project(\%all_object_deps, $windows_project, $type, $projguids{$windows_project}, $toolsver); - } - - chdir $orig_dir; - } - - sub create_vs_project { - my ($all_object_deps, $windows_project, $type, $projguid, $toolsver) = @_; - - # Break down the project's dependency information into the appropriate - # groups. - %seen_objects = (); - %lib_files = (); - %source_files = (); - %header_files = (); - %resource_files = (); - %icon_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 eq $object_deps->[0]) { - if($object_dep =~ /\.c$/io) { - $source_files{$object_dep} = 1; - } elsif($object_dep =~ /\.rc$/io) { - $resource_files{$object_dep} = 1; - } - } elsif ($object_dep =~ /\.[ch]$/io) { - $header_files{$object_dep} = 1; - } elsif ($object_dep =~ /\.ico$/io) { - $icon_files{$object_dep} = 1; - } - } - } - - $libs = join ";", sort keys %lib_files; - @source_files = sort keys %source_files; - @header_files = sort keys %header_files; - @resources = sort keys %resource_files; - @icons = sort keys %icon_files; - $subsystem = ($type eq "G") ? "Windows" : "Console"; - - mkdir $windows_project - if(! -d $windows_project); - chdir $windows_project; - open OUT, ">$windows_project.vcxproj"; select OUT; - open FILTERS, ">$windows_project.vcxproj.filters"; - - # The bulk of the project file is just boilerplate stuff, so we - # can mostly just dump it out here. Note, buried in the ClCompile - # item definition, that we use a debug information format of - # ProgramDatabase, which disables the edit-and-continue support - # that breaks most of the project builds. - print - "\n" . - "\n" . - " \n" . - " \n" . - " Debug\n" . - " Win32\n" . - " \n" . - " \n" . - " Release\n" . - " Win32\n" . - " \n" . - " \n" . - " \n" . - " \n" . - " \n" . - " {$projguid}\n" . - " \n" . - " \n" . - " \n" . - " Application\n" . - " false\n" . - " MultiByte\n" . - " $toolsver\n" . - " \n" . - " \n" . - " Application\n" . - " false\n" . - " MultiByte\n" . - " $toolsver\n" . - " \n" . - " \n" . - " \n" . - " \n" . - " \n" . - " \n" . - " \n" . - " \n" . - " \n" . - " \n" . - " \n" . - " \n" . - " .\\Release\\\n" . - " .\\Release\\\n" . - " false\n" . - " \n" . - " \n" . - " .\\Debug\\\n" . - " .\\Debug\\\n" . - " true\n" . - " \n" . - " \n" . - " \n" . - " MultiThreaded\n" . - " OnlyExplicitInline\n" . - " true\n" . - " true\n" . - " MaxSpeed\n" . - " true\n" . - " Level3\n" . - " " . (join ";", map {"..\\..\\$dirpfx$_"} @srcdirs) . ";%(AdditionalIncludeDirectories)\n" . - " WIN32;NDEBUG;_WINDOWS;POSIX;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_DEPRECATE;%(PreprocessorDefinitions)\n" . - " .\\Release\\\n" . - " .\\Release\\$windows_project.pch\n" . - " .\\Release\\\n" . - " .\\Release\\\n" . - " \n" . - " \n" . - " true\n" . - " NDEBUG;%(PreprocessorDefinitions)\n" . - " .\\Release\\$windows_project.tlb\n" . - " true\n" . - " Win32\n" . - " \n" . - " \n" . - " 0x0809\n" . - " NDEBUG;%(PreprocessorDefinitions)\n" . - " \n" . - " \n" . - " true\n" . - " .\\Release\\$windows_project.bsc\n" . - " \n" . - " \n" . - " true\n" . - " $subsystem\n" . - " .\\Release\\$windows_project.exe\n" . - " $libs;%(AdditionalDependencies)\n" . - " \n" . - " \n" . - " \n" . - " \n" . - " MultiThreadedDebug\n" . - " Default\n" . - " false\n" . - " Disabled\n" . - " true\n" . - " Level3\n" . - " true\n" . - " ProgramDatabase\n" . - " " . (join ";", map {"..\\..\\$dirpfx$_"} @srcdirs) . ";%(AdditionalIncludeDirectories)\n" . - " WIN32;_DEBUG;_WINDOWS;POSIX;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_DEPRECATE;%(PreprocessorDefinitions)\n" . - " .\\Debug\\\n" . - " .\\Debug\\$windows_project.pch\n" . - " .\\Debug\\\n" . - " .\\Debug\\\n" . - " EnableFastChecks\n" . - " \n" . - " \n" . - " true\n" . - " _DEBUG;%(PreprocessorDefinitions)\n" . - " .\\Debug\\$windows_project.tlb\n" . - " true\n" . - " Win32\n" . - " \n" . - " \n" . - " 0x0809\n" . - " _DEBUG;%(PreprocessorDefinitions)\n" . - " \n" . - " \n" . - " true\n" . - " .\\Debug\\$windows_project.bsc\n" . - " \n" . - " \n" . - " true\n" . - " true\n" . - " $subsystem\n" . - " \$(TargetPath)\n" . - " $libs;%(AdditionalDependencies)\n" . - " \n" . - " \n"; - - # The VC++ projects don't have physical structure to them, instead - # the files are organized by logical "filters" that are stored in - # a separate file, so different users can organize things differently. - # The filters file contains a copy of the ItemGroup elements from - # the main project file that list the included items, but tack - # on a filter name where needed. - print FILTERS - "\n" . - "\n"; - - print " \n"; - print FILTERS " \n"; - foreach $icon_file (@icons) { - $icon_file =~ s/..\\windows\\//; - print " \n"; - print FILTERS - " \n" . - " Resource Files\n" . - " \n"; - } - print FILTERS " \n"; - print " \n"; - - print " \n"; - print FILTERS " \n"; - foreach $resource_file (@resources) { - $resource_file =~ s/..\\windows\\//; - print - " \n" . - " ..\\..;%(AdditionalIncludeDirectories)\n" . - " ..\\..;%(AdditionalIncludeDirectories)\n" . - " \n"; - print FILTERS - " \n" . - " Resource Files\n" . - " \n"; - } - print FILTERS " \n"; - print " \n"; - - print " \n"; - print FILTERS " \n"; - foreach $source_file (@source_files) { - $source_file =~ s/..\\windows\\//; - print " \n"; - print FILTERS - " \n" . - " Source Files\n" . - " "; - } - print FILTERS " \n"; - print " \n"; - - print " \n"; - print FILTERS " \n"; - foreach $header_file (@header_files) { - $header_file =~ s/..\\windows\\//; - print " \n"; - print FILTERS - " \n" . - " Header Files\n" . - " "; - } - print FILTERS " \n"; - print " \n"; - - print - " \n" . - ""; - - print FILTERS - " \n" . - " \n" . - " {" . &invent_guid("sources:$windows_project") . "}\n" . - " \n" . - " \n" . - " {" . &invent_guid("headers:$windows_project") . "}\n" . - " \n" . - " \n" . - " {" . &invent_guid("resources:$windows_project") . "}\n" . - " \n" . - " \n" . - ""; - - select STDOUT; close OUT; close FILTERS; - 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+-3.0 x11 \$\$0 2>/dev/null || 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 -std=gnu99 -Wvla -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:XT:U:UT")); - print "\n\n"; - foreach $p (&prognames("X:XT:U:UT")) { - ($prog, $type) = split ",", $p; - ($ldflags = $type) =~ s/T$//; - $objstr = &objects($p, "X.o", undef, undef); - print &splitline($prog . ": " . $objstr), "\n"; - $libstr = &objects($p, undef, undef, "-lX"); - print &splitline("\t\$(CC) -o \$@ " . - $objstr . " \$(${ldflags}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 &def($makefile_extra{'gtk'}->{'end'}); - print "\nclean:\n". - "\trm -f *.o". (join "", map { " $_" } &progrealnames("X:XT:U:UT")) . "\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 -std=gnu99 -Wvla -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:UT")); - print "\n\n"; - foreach $p (&prognames("U:UT")) { - ($prog, $type) = split ",", $p; - ($ldflags = $type) =~ s/T$//; - $objstr = &objects($p, "X.o", undef, undef); - print &splitline($prog . ": " . $objstr), "\n"; - $libstr = &objects($p, undef, undef, "-lX"); - print &splitline("\t\$(CC) -o \$@ " . - $objstr . " \$(${ldflags}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:UT")) . "\n"; - print "\nFORCE:\n"; - select STDOUT; close OUT; -} - -if (defined $makefiles{'am'}) { - die "Makefile.am in a subdirectory is not supported\n" - if &dirpfx($makefiles{'am'}, "/") ne ""; - - ##-- Unix/autoconf Makefile.am - open OUT, ">$makefiles{'am'}"; select OUT; - print - "# Makefile.am for $project_name under Unix with Autoconf/Automake.\n". - "#\n# This file was created by `mkfiles.pl' from the `Recipe' file.\n". - "# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead.\n\n"; - - # 2014-02-22: as of automake-1.14 we begin to get complained at if - # we don't use this option - print "AUTOMAKE_OPTIONS = subdir-objects\n\n"; - - # Complete list of source and header files. Not used by the - # auto-generated parts of this makefile, but Recipe might like to - # have it available as a variable so that mandatory-rebuild things - # (version.o) can conveniently be made to depend on it. - @sources = ("allsources", "=", - sort grep {$_ ne "empty.h"} keys %allsourcefiles); - print &splitline(join " ", @sources), "\n\n"; - - @cliprogs = ("bin_PROGRAMS", "="); - foreach $p (&prognames("U")) { - ($prog, $type) = split ",", $p; - push @cliprogs, $prog; - } - @allprogs = @cliprogs; - foreach $p (&prognames("X")) { - ($prog, $type) = split ",", $p; - push @allprogs, $prog; - } - print "if HAVE_GTK\n"; - print &splitline(join " ", @allprogs), "\n"; - print "else\n"; - print &splitline(join " ", @cliprogs), "\n"; - print "endif\n\n"; - - @noinstcliprogs = ("noinst_PROGRAMS", "="); - foreach $p (&prognames("UT")) { - ($prog, $type) = split ",", $p; - push @noinstcliprogs, $prog; - } - @noinstallprogs = @noinstcliprogs; - foreach $p (&prognames("XT")) { - ($prog, $type) = split ",", $p; - push @noinstallprogs, $prog; - } - print "if HAVE_GTK\n"; - print &splitline(join " ", @noinstallprogs), "\n"; - print "else\n"; - print &splitline(join " ", @noinstcliprogs), "\n"; - print "endif\n\n"; - - %objtosrc = (); - foreach $d (&deps("X", undef, "", "/", "am")) { - $objtosrc{$d->{obj}} = $d->{deps}->[0]; - } - - print &splitline(join " ", "AM_CPPFLAGS", "=", - map {"-I\$(srcdir)/$_"} @srcdirs), "\n"; - - @amcflags = ("\$(COMPAT)", "\$(XFLAGS)", "\$(WARNINGOPTS)"); - print "if HAVE_GTK\n"; - print &splitline(join " ", "AM_CFLAGS", "=", - "\$(GTK_CFLAGS)", @amcflags), "\n"; - print "else\n"; - print &splitline(join " ", "AM_CFLAGS", "=", @amcflags), "\n"; - print "endif\n\n"; - - %amspeciallibs = (); - foreach $obj (sort { $a cmp $b } keys %{$cflags{'am'}}) { - my $flags = $cflags{'am'}->{$obj}; - $flags = "" if $flags !~ s/^C//; - print "lib${obj}_a_SOURCES = ", $objtosrc{$obj}, "\n"; - print &splitline(join " ", "lib${obj}_a_CFLAGS", "=", @amcflags, - $flags), "\n"; - $amspeciallibs{$obj} = "lib${obj}.a"; - } - print &splitline(join " ", "noinst_LIBRARIES", "=", - sort { $a cmp $b } values %amspeciallibs), "\n\n"; - - foreach $p (&prognames("X:XT:U:UT")) { - ($prog, $type) = split ",", $p; - print "if HAVE_GTK\n" if $type eq "X" || $type eq "XT"; - @progsources = ("${prog}_SOURCES", "="); - %sourcefiles = (); - @ldadd = (); - $objstr = &objects($p, "X", undef, undef); - foreach $obj (split / /,$objstr) { - if ($amspeciallibs{$obj}) { - push @ldadd, $amspeciallibs{$obj}; - } else { - $sourcefiles{$objtosrc{$obj}} = 1; - } - } - push @progsources, sort { $a cmp $b } keys %sourcefiles; - print &splitline(join " ", @progsources), "\n"; - if ($type eq "X" || $type eq "XT") { - push @ldadd, "\$(GTK_LIBS)"; - } - if (@ldadd) { - print &splitline(join " ", "${prog}_LDADD", "=", @ldadd), "\n"; - } - print "endif\n" if $type eq "X" || $type eq "XT"; - print "\n"; - } - print &def($makefile_extra{'am'}->{'end'}); - 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 = ".(join " ", map {"-I$dirpfx$_"} @srcdirs)."\n". - "\n". - "# Get include directory for resource compiler\n". - "\n". - &def($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 &def($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 -std=gnu99 -Wvla -g " . - (join " ", map {"-I$dirpfx$_"} @srcdirs))."\n". - "MLDFLAGS = -framework Cocoa\n". - "ULDFLAGS =\n". - "\n" . - &def($makefile_extra{'osx'}->{'vars'}) . - "\n" . - &splitline("all:" . join "", map { " $_" } &progrealnames("MX:U:UT")) . - "\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:UT")) { - ($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:UT")) . "\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); - } - - chdir $orig_dir; - - sub create_devcpp_project { - my ($all_object_deps, $progname) = @_; - # Construct program's dependency info (Taken from 'vcproj', seems to work right here, too.) - %seen_objects = (); - %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 multiple 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 ".."; - } -} - -# All done, so do the Unix postprocessing if asked to. - -if ($do_unix) { - chdir $orig_dir; - system "./mkauto.sh"; - die "mkfiles.pl: mkauto.sh returned $?\n" if $? > 0; - if ($do_unix == 1) { - chdir ($targetdir = "unix") - or die "$targetdir: chdir: $!\n"; - } - system "./configure", @confargs; - die "mkfiles.pl: configure returned $?\n" if $? > 0; -} - -sub invent_guid($) { - my ($name) = @_; - - # Invent a GUID for use in Visual Studio project files. We need - # a few of these for every executable file we build. - # - # In order to avoid having to use the non-core Perl module - # Data::GUID, and also arrange for GUIDs to be stable, we generate - # our GUIDs by hashing a pile of fixed (but originally randomly - # generated) data with the filename for which we need an id. - # - # Hashing _just_ the filenames would clearly be cheating (it's - # quite conceivable that someone might hash the same string for - # another reason and so generate a colliding GUID), but hashing a - # whole SHA-512 data block of random gibberish as well should make - # these GUIDs pseudo-random enough to not collide with anyone - # else's. - - my $randdata = pack "N*", - 0xD4AB035F,0x76998BA0,0x2DCCB0BD,0x6D3FA320,0x53638051,0xFE312F35, - 0xDE1CECC0,0x784DF852,0x6C9F4589,0x54B7AC23,0x14E7A1C4,0xF9BF04DF, - 0x19C08B6D,0x3FB69EF1,0xB2DA9043,0xDB5362F3,0x25718DB6,0x733560DA, - 0xFEF871B0,0xFECF7A0C,0x67D19C95,0xB492E911,0xF5D562A3,0xFCE1D478, - 0x02C50434,0xF7326B7E,0x93D39872,0xCF0D0269,0x9EF24C0F,0x827689AD, - 0x88BD20BC,0x74EA6AFE,0x29223682,0xB9AB9287,0x7EA7CE4F,0xCF81B379, - 0x9AE4A954,0x81C7AD97,0x2FF2F031,0xC51DA3C2,0xD311CCE7,0x0A31EB8B, - 0x1AB04242,0xAF53B714,0xFC574D40,0x8CB4ED01,0x29FEB16F,0x4904D7ED, - 0xF5C5F5E1,0xF138A4C2,0xA9D881CE,0xCEA65187,0x4421BA97,0x0EE8428E, - 0x9556E384,0x6D0484C9,0x561BD84B,0xD9516A40,0x6B4FD33F,0xDDFFE4C8, - 0x3D5DF8A5,0xFE6B7D99,0x3443371B,0xF4E30A3E,0xE62B9FDA,0x6BAA75DB, - 0x9EF3C2C7,0x6815CA42,0xE6536076,0xF851E6E2,0x39D16E69,0xBCDF3BB6, - 0x50EFFA41,0x378CDF2A,0xB5EC0D0C,0x1E94C433,0xE818241A,0x2689EB1F, - 0xB649CEF9,0xD7344D46,0x59C1BB13,0x27511FDF,0x7DAD1768,0xB355E29E, - 0xDFAE550C,0x2433005B,0x09DE10B0,0xAA00BA6B,0xC144ED2D,0x8513D007, - 0xB0315232,0x7A10DAB6,0x1D97654E,0xF048214D,0xE3059E75,0x83C225D1, - 0xFC7AB177,0x83F2B553,0x79F7A0AF,0x1C94582C,0xF5E4AF4B,0xFB39C865, - 0x58ABEB27,0xAAB28058,0x52C15A89,0x0EBE9741,0x343F4D26,0xF941202A, - 0xA32FD32F,0xDCC055B8,0x64281BF3,0x468BD7BA,0x0CEE09D3,0xBB5FD2B6, - 0xA528D412,0xA6A6967E,0xEAAF5DAE,0xDE7B2FAE,0xCA36887B,0x0DE196EB, - 0x74B95EF0,0x9EB8B7C2,0x020BFC83,0x1445086F,0xBF4B61B2,0x89AFACEC, - 0x80A5CD69,0xC790F744,0x435A6998,0x8DE7AC48,0x32F31BC9,0x8F760D3D, - 0xF02A74CB,0xD7B47E20,0x9EC91035,0x70FDE74D,0x9B531362,0x9D81739A, - 0x59ADC2EB,0x511555B5,0xCA84B8D5,0x3EC325FF,0x2E442A4C,0x82AF30D9, - 0xBFD3EC87,0x90C59E07,0x1C6DC991,0x2D16B822,0x7EA44EB5,0x3A655A39, - 0xAB640886,0x09311821,0x777801D9,0x489DBE61,0xA1FFEC65,0x978B49B1, - 0x7DB700CD,0x263CF3D6,0xF977E89F,0xBA0B3D01,0x6C6CED19,0x1BE6F23A, - 0x19E0ED98,0x8E71A499,0x70BA3271,0x3FB7EE98,0xABA46848,0x2B797959, - 0x72C6DE59,0xE08B795C,0x02936C39,0x02185CCB,0xD6F3CE18,0xD0157A40, - 0x833DEC3F,0x319B00C4,0x97B59513,0x900B81FD,0x9A022379,0x16E44E1A, - 0x0C4CC540,0xCA98E7F9,0xF9431A26,0x290BCFAC,0x406B82C0,0xBC1C4585, - 0x55C54528,0x811EBB77,0xD4EDD4F3,0xA70DC02E,0x8AD5C0D1,0x28D64EF4, - 0xBEFF5C69,0x99852C4A,0xB4BBFF7B,0x069230AC,0xA3E141FA,0x4E99FB0E, - 0xBC154DAA,0x323C7F15,0x86E0247E,0x2EEA3054,0xC9CA1D32,0x8964A006, - 0xC93978AC,0xF9B2C159,0x03F2079E,0xB051D284,0x4A7EA9A9,0xF001DA1F, - 0xD47A0DAA,0xCF7B6B73,0xF18293B2,0x84303E34,0xF8BC76C4,0xAFBEE24F, - 0xB589CA80,0x77B5BF86,0x21B9FD5B,0x1A5071DF,0xA3863110,0x0E50CA61, - 0x939151A5,0xD2A59021,0x83A9CDCE,0xCEC69767,0xC906BB16,0x3EE1FF4D, - 0x1321EAE4,0x0BF940D6,0x52471E61,0x8A087056,0x66E54293,0xF84AAB9B, - 0x08835EF1,0x8F12B77A,0xD86935A5,0x200281D7,0xCD3C37C9,0x30ABEC05, - 0x7067E8A0,0x608C4838,0xC9F51CDE,0xA6D318DE,0x41C05B2A,0x694CCE0E, - 0xC7842451,0xA3194393,0xFBDC2C84,0xA6D2B577,0xC91E7924,0x01EDA708, - 0x22FBB61E,0x662F9B7B,0xDE3150C3,0x2397058C; - my $digest = sha512_hex($name . "\0" . $randdata); - return sprintf("%s-%s-%04x-%04x-%s", - substr($digest,0,8), - substr($digest,8,4), - 0x4000 | (0xFFF & hex(substr($digest,12,4))), - 0x8000 | (0x3FFF & hex(substr($digest,16,4))), - substr($digest,20,12)); -} diff --git a/mksrcarc.sh b/mksrcarc.sh index 9b533174..22337677 100755 --- a/mksrcarc.sh +++ b/mksrcarc.sh @@ -2,7 +2,6 @@ set -e -perl mkfiles.pl # These are text files. text=`{ find . -name CVS -prune -o \ -name .cvsignore -prune -o \ diff --git a/mkunxarc.sh b/mkunxarc.sh index 34aee8b2..75790b65 100755 --- a/mkunxarc.sh +++ b/mkunxarc.sh @@ -3,15 +3,12 @@ # Build a Unix source distribution from the PuTTY CVS area. # # Expects the following arguments: -# - the version number to write into configure.ac # - the suffix to put on the Unix source tarball # - the options to put on the 'make' command line for the docs -autoconfver="$1" -arcsuffix="$2" -docver="$3" +arcsuffix="$1" +docver="$2" -perl mkfiles.pl (cd doc && make -s ${docver:+"$docver"}) relver=`cat LATEST.VER` @@ -27,12 +24,9 @@ find . -name uxarc -prune -o \ -name CVS -prune -o \ -name .cvsignore -prune -o \ -name .svn -prune -o \ - -name configure.ac -prune -o \ -name '*.zip' -prune -o \ -name '*.tar.gz' -prune -o \ -type f -exec ln -s $PWD/{} uxarc/$arcname/{} \; -sed "s/^AC_INIT(putty,.*/AC_INIT(putty, $autoconfver)/" configure.ac > uxarc/$arcname/configure.ac -(cd uxarc/$arcname && sh mkauto.sh) 2>errors || { cat errors >&2; exit 1; } tar -C uxarc -chzof $arcname.tar.gz $arcname rm -rf uxarc diff --git a/release.pl b/release.pl index c4e9822b..122cc027 100755 --- a/release.pl +++ b/release.pl @@ -34,10 +34,8 @@ if ($setver) { my $builddir = tempdir(DIR => ".", CLEANUP => 1); 0 == system "git archive --format=tar HEAD | ( cd $builddir && tar xf - )" or die; - 0 == system "cd $builddir && ./mkfiles.pl" or die; - 0 == system "cd $builddir && ./mkauto.sh" or die; - 0 == system "cd $builddir && ./configure" or die; - 0 == system "cd $builddir && make pscp plink RELEASE=${version}" or die; + 0 == system "cd $builddir && cmake . -DCMAKE_C_FLAGS=-DRELEASE=${version}" or die; + 0 == system "cd $builddir && cmake --build . -t pscp -t plink -j" or die; our $pscp_transcript = `cd $builddir && ./pscp --help`; $pscp_transcript =~ s/^Unidentified build/Release ${version}/m or die; $pscp_transcript =~ s/^/\\c /mg; diff --git a/unix/CMakeLists.txt b/unix/CMakeLists.txt new file mode 100644 index 00000000..2932d853 --- /dev/null +++ b/unix/CMakeLists.txt @@ -0,0 +1,204 @@ +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) + +add_platform_sources_to_library(utils + uxutils.c uxsignal.c uxpoll.c xkeysym.c uxmisc.c xpmpucfg.c + xpmputty.c xpmptcfg.c xpmpterm.c x11misc.c ../time.c) +add_platform_sources_to_library(eventloop + uxcliloop.c uxsel.c) +add_platform_sources_to_library(console + uxcons.c) +add_platform_sources_to_library(settings + uxstore.c) +add_platform_sources_to_library(network + uxnet.c uxfdsock.c uxagentsock.c uxpeer.c uxproxy.c) +add_platform_sources_to_library(sshcommon + uxnoise.c ux_x11.c) +add_platform_sources_to_library(sshclient + uxgss.c uxagentc.c uxshare.c) +add_platform_sources_to_library(sshserver + uxsftpserver.c procnet.c) +add_platform_sources_to_library(sftpclient + uxsftp.c) +add_platform_sources_to_library(otherbackends + uxser.c) +add_platform_sources_to_library(agent + uxagentc.c) + +add_executable(fuzzterm + ${CMAKE_SOURCE_DIR}/fuzzterm.c + ${CMAKE_SOURCE_DIR}/be_none.c + ${CMAKE_SOURCE_DIR}/logging.c + ${CMAKE_SOURCE_DIR}/noprint.c + uxucs.c + uxnogtk.c) +add_dependencies(fuzzterm generated_licence_h) +target_link_libraries(fuzzterm + guiterminal eventloop charset settings utils) + +add_executable(osxlaunch + osxlaunch.c) + +add_executable(plink + uxplink.c + ${CMAKE_SOURCE_DIR}/be_all_s.c + uxnogtk.c) +target_link_libraries(plink + eventloop noterminal console sshclient otherbackends settings network crypto + utils) +installed_program(plink) + +add_executable(pscp + ${CMAKE_SOURCE_DIR}/pscp.c + ${CMAKE_SOURCE_DIR}/be_ssh.c + uxnogtk.c) +target_link_libraries(pscp + sftpclient eventloop console sshclient settings network crypto utils) +installed_program(pscp) + +add_executable(psftp + ${CMAKE_SOURCE_DIR}/psftp.c + ${CMAKE_SOURCE_DIR}/be_ssh.c + uxnogtk.c) +target_link_libraries(psftp + sftpclient eventloop console sshclient settings network crypto utils) +installed_program(psftp) + +add_executable(psocks + uxsocks.c + ${CMAKE_SOURCE_DIR}/psocks.c + ${CMAKE_SOURCE_DIR}/norand.c + ${CMAKE_SOURCE_DIR}/nocproxy.c + ${CMAKE_SOURCE_DIR}/portfwd.c + uxnogtk.c) +target_link_libraries(psocks + eventloop console network utils) + +add_executable(psusan + uxpsusan.c + ${CMAKE_SOURCE_DIR}/be_none.c + ${CMAKE_SOURCE_DIR}/nogss.c + ${CMAKE_SOURCE_DIR}/scpserver.c + uxnogtk.c + uxpty.c) +target_link_libraries(psusan + eventloop sshserver keygen settings network crypto utils) +installed_program(psusan) + +add_library(puttygen-common OBJECT + ${CMAKE_SOURCE_DIR}/notiming.c + uxgen.c + uxnogtk.c + uxnoise.c + uxstore.c + ${CMAKE_SOURCE_DIR}/sshpubk.c + ${CMAKE_SOURCE_DIR}/sshrand.c) + +add_executable(puttygen + ${CMAKE_SOURCE_DIR}/cmdgen.c) +target_link_libraries(puttygen + puttygen-common keygen console crypto utils) +installed_program(puttygen) + +add_executable(cgtest + ${CMAKE_SOURCE_DIR}/cgtest.c) +target_link_libraries(cgtest + puttygen-common keygen console crypto utils) + +add_executable(testsc + ${CMAKE_SOURCE_DIR}/testsc.c) +target_link_libraries(testsc crypto utils) + +add_executable(testzlib + ${CMAKE_SOURCE_DIR}/testzlib.c + ${CMAKE_SOURCE_DIR}/sshzlib.c) +target_link_libraries(testzlib utils) + +add_executable(uppity + uxserver.c + ${CMAKE_SOURCE_DIR}/be_none.c + ${CMAKE_SOURCE_DIR}/scpserver.c + uxnogtk.c + uxpty.c + ${CMAKE_SOURCE_DIR}/nogss.c) +target_link_libraries(uppity + eventloop sshserver keygen settings network crypto utils) + +if(GTK_FOUND) + add_platform_sources_to_library(utils + gtkcols.c) + add_platform_sources_to_library(guiterminal + gtkwin.c gtkfont.c gtkdlg.c gtkcfg.c gtkcomm.c uxcfg.c uxucs.c uxprint.c) + add_dependencies(guiterminal generated_licence_h) # gtkdlg.c uses licence.h + + add_library(guimisc STATIC + gtkmisc.c) + + add_executable(pageant + uxpgnt.c + ${CMAKE_SOURCE_DIR}/be_misc.c + ${CMAKE_SOURCE_DIR}/be_none.c + ${CMAKE_SOURCE_DIR}/nogss.c + gtkask.c + ux_x11.c + uxnoise.c + ${CMAKE_SOURCE_DIR}/x11fwd.c) + target_link_libraries(pageant + guimisc eventloop console agent settings network crypto utils + ${GTK_LIBRARIES}) + installed_program(pageant) + + add_executable(pterm + uxpterm.c + gtkmain.c + ${CMAKE_SOURCE_DIR}/be_none.c + ${CMAKE_SOURCE_DIR}/nogss.c + uxpty.c) + target_link_libraries(pterm + guiterminal guimisc eventloop settings charset utils + ${GTK_LIBRARIES} ${X11_LIBRARIES}) + installed_program(pterm) + + add_executable(ptermapp + uxpterm.c + gtkapp.c + ${CMAKE_SOURCE_DIR}/nocmdline.c + ${CMAKE_SOURCE_DIR}/be_none.c + ${CMAKE_SOURCE_DIR}/nogss.c + uxpty.c) + target_link_libraries(ptermapp + guiterminal guimisc eventloop settings charset utils + ${GTK_LIBRARIES} ${X11_LIBRARIES}) + + add_executable(putty + uxputty.c + gtkmain.c + ${CMAKE_SOURCE_DIR}/be_all_s.c) + target_link_libraries(putty + guiterminal guimisc eventloop sshclient otherbackends settings + network crypto charset utils + ${GTK_LIBRARIES} ${X11_LIBRARIES}) + set_target_properties(putty + PROPERTIES LINK_INTERFACE_MULTIPLICITY 2) + installed_program(putty) + + add_executable(puttyapp + uxputty.c + gtkapp.c + ${CMAKE_SOURCE_DIR}/nocmdline.c + ${CMAKE_SOURCE_DIR}/be_all_s.c) + target_link_libraries(puttyapp + guiterminal guimisc eventloop sshclient otherbackends settings + network crypto charset utils + ${GTK_LIBRARIES} ${X11_LIBRARIES}) + + add_executable(puttytel + uxputty.c + gtkmain.c + ${CMAKE_SOURCE_DIR}/be_nos_s.c + ${CMAKE_SOURCE_DIR}/nogss.c + ${CMAKE_SOURCE_DIR}/norand.c + ${CMAKE_SOURCE_DIR}/nocproxy.c) + target_link_libraries(puttytel + guiterminal guimisc eventloop otherbackends settings network charset utils + ${GTK_LIBRARIES} ${X11_LIBRARIES}) +endif() diff --git a/unix/configure b/unix/configure deleted file mode 100755 index b2b033d8..00000000 --- a/unix/configure +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -$(echo "$0" | sed '$s!configure$!../configure!') "$@" diff --git a/unix/gtkapp.c b/unix/gtkapp.c index e7f49df5..fb924009 100644 --- a/unix/gtkapp.c +++ b/unix/gtkapp.c @@ -9,17 +9,10 @@ /* -To build on OS X, you will need a build environment with GTK 3 and -gtk-mac-bundler, and also Halibut on the path (to build the man pages, -without which the standard Makefile will complain). Then, from a clean -checkout, do this: - -./mkfiles.pl -U --with-quartz -make -C icons icns -make -C doc -make - -and you should get unix/PuTTY.app and unix/PTerm.app as output. +Building this for OS X is currently broken, because the new +CMake-based build system doesn't support it yet. Probably what needs +doing is to add it back in to unix/CMakeLists.txt under a condition +like if(CMAKE_SYSTEM_NAME MATCHES "Darwin"). */ diff --git a/unix/unix.h b/unix/unix.h index 2c0664b2..d62c42f3 100644 --- a/unix/unix.h +++ b/unix/unix.h @@ -1,8 +1,8 @@ #ifndef PUTTY_UNIX_H #define PUTTY_UNIX_H -#ifdef HAVE_CONFIG_H -# include "uxconfig.h" /* Space to hide it from mkfiles.pl */ +#if HAVE_CMAKE_H +#include "cmake.h" #endif #include /* for FILENAME_MAX */ diff --git a/unix/uxgss.c b/unix/uxgss.c index 2d71c543..2b4e8537 100644 --- a/unix/uxgss.c +++ b/unix/uxgss.c @@ -6,7 +6,7 @@ /* Unix code to set up the GSSAPI library list. */ -#if !defined NO_LIBDL && !defined NO_GSSAPI +#if !defined NO_LIBDL && !defined STATIC_GSSAPI && !defined NO_GSSAPI const int ngsslibs = 4; const char *const gsslibnames[4] = { diff --git a/unix/uxpeer.c b/unix/uxpeer.c index 4ad26322..11f03291 100644 --- a/unix/uxpeer.c +++ b/unix/uxpeer.c @@ -3,11 +3,11 @@ * appropriate autoconfery. */ -#ifdef HAVE_CONFIG_H -# include "uxconfig.h" /* leading space prevents mkfiles.pl trying to follow */ +#if HAVE_CMAKE_H +#include "cmake.h" #endif -#ifdef HAVE_SO_PEERCRED +#if HAVE_SO_PEERCRED #define _GNU_SOURCE #include #endif @@ -18,7 +18,7 @@ bool so_peercred(int fd, int *pid, int *uid, int *gid) { -#ifdef HAVE_SO_PEERCRED +#if HAVE_SO_PEERCRED struct ucred cr; socklen_t crlen = sizeof(cr); if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cr, &crlen) == 0) { diff --git a/unix/uxpty.c b/unix/uxpty.c index bfd0de57..d693463f 100644 --- a/unix/uxpty.c +++ b/unix/uxpty.c @@ -198,7 +198,7 @@ static void pty_try_write(Pty *pty); #ifndef OMIT_UTMP static void setup_utmp(char *ttyname, char *location) { -#ifdef HAVE_LASTLOG +#if HAVE_LASTLOG struct lastlog lastlog_entry; FILE *lastlog; #endif @@ -235,9 +235,11 @@ static void setup_utmp(char *ttyname, char *location) pututxline(&utmp_entry); endutxent(); +#if HAVE_UPDWTMPX updwtmpx(WTMPX_FILE, &utmp_entry); +#endif -#ifdef HAVE_LASTLOG +#if 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)); @@ -266,7 +268,9 @@ static void cleanup_utmp(void) utmp_entry.ut_tv.tv_sec = tv.tv_sec; utmp_entry.ut_tv.tv_usec = tv.tv_usec; +#if HAVE_UPDWTMPX updwtmpx(WTMPX_FILE, &utmp_entry); +#endif memset(utmp_entry.ut_line, 0, lenof(utmp_entry.ut_line)); utmp_entry.ut_tv.tv_sec = 0; @@ -371,7 +375,7 @@ static void pty_open_master(Pty *pty) #endif ; -#ifdef HAVE_POSIX_OPENPT +#if HAVE_POSIX_OPENPT #ifdef SET_NONBLOCK_VIA_OPENPT /* * OS X, as of 10.10 at least, doesn't permit me to set O_NONBLOCK @@ -567,7 +571,7 @@ void pty_pre_init(void) /* Drop privs. */ { -#ifndef HAVE_NO_SETRESUID +#if HAVE_SETRESUID && HAVE_SETRESGID int gid = getgid(), uid = getuid(); int setresgid(gid_t, gid_t, gid_t); int setresuid(uid_t, uid_t, uid_t); @@ -717,7 +721,7 @@ static void pty_real_select_result(Pty *pty, int fd, int event, int status) "\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 +#if !HAVE_STRSIGNAL message = dupprintf( "\r\n[pterm: process terminated on signal %d]\r\n", WTERMSIG(pty->exit_code)); diff --git a/unix/uxutils.h b/unix/uxutils.h index c9acff53..8c70a8a1 100644 --- a/unix/uxutils.h +++ b/unix/uxutils.h @@ -11,22 +11,22 @@ #define PUTTY_UXUTILS_H #if defined __APPLE__ -#ifdef HAVE_SYS_SYSCTL_H +#if HAVE_SYS_SYSCTL_H #include #endif #endif /* defined __APPLE__ */ #if defined __arm__ || defined __aarch64__ -#ifdef HAVE_SYS_TYPES_H +#if HAVE_SYS_TYPES_H #include #endif -#ifdef HAVE_SYS_AUXV_H +#if HAVE_SYS_AUXV_H #include #endif -#ifdef HAVE_ASM_HWCAP_H +#if HAVE_ASM_HWCAP_H #include #endif @@ -51,7 +51,7 @@ static inline u_long getauxval(int which) { return 0; } #if defined __APPLE__ static inline bool test_sysctl_flag(const char *flagname) { -#ifdef HAVE_SYSCTLBYNAME +#if HAVE_SYSCTLBYNAME int value; size_t size = sizeof(value); return (sysctlbyname(flagname, &value, &size, NULL, 0) == 0 && diff --git a/version.c b/version.c index 620879c3..edc4da99 100644 --- a/version.c +++ b/version.c @@ -11,15 +11,10 @@ #include "putty.h" #include "ssh.h" -#ifdef SOURCE_COMMIT -#include "empty.h" -#endif - #include "version.h" const char ver[] = TEXTVER; const char sshver[] = SSHVER; -const char commitid[] = SOURCE_COMMIT; /* * SSH local version string MUST be under 40 characters. Here's a diff --git a/version.h b/version.h index 74be18f4..a45eef1d 100644 --- a/version.h +++ b/version.h @@ -11,25 +11,3 @@ #define TEXTVER "Unidentified build" #define SSHVER "-Unidentified-Local-Build" #define BINARY_VERSION 0,0,0,0 - -#ifndef SOURCE_COMMIT -/* - * git commit id from which this build was made. This is defined by - * Buildscr for official builds - both source archives and prebuilt - * binaries - in the course of overwriting this file as described - * above. But we put it here under ifdef, so that it can also be - * passed in on the command line for Unix local development builds, - * which I treat specially because Unix developers - e.g. me - are - * quite likely to run 'make install' straight out of their dev - * directory so as to use the bleeding-edge code for day-to-day - * running. - * - * Windows doesn't really need the same treatment, because the easiest - * way to install a build properly on Windows is to run the installer, - * and the easiest way to do that is to run Buildscr, which will - * populate this field its own way. It's only the Unix automake build - * where you might go straight from local 'make' to 'make install' - * without going through Buildscr. - */ -#define SOURCE_COMMIT "unavailable" -#endif diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt new file mode 100644 index 00000000..4db250f8 --- /dev/null +++ b/windows/CMakeLists.txt @@ -0,0 +1,142 @@ +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) + +add_platform_sources_to_library(utils + wincapi.c winutils.c winucs.c winmisc.c winmiscs.c wintime.c windefs.c + winsecur.c) +add_platform_sources_to_library(eventloop + wincliloop.c winhandl.c) +add_platform_sources_to_library(console + winselcli.c winnohlp.c wincons.c) +add_platform_sources_to_library(settings + winstore.c) +add_platform_sources_to_library(network + winnet.c winhsock.c winnpc.c winnps.c winproxy.c) +add_platform_sources_to_library(sshcommon + winnoise.c winx11.c) +add_platform_sources_to_library(sshclient + winpgntc.c wingss.c winshare.c) +add_platform_sources_to_library(sftpclient + winsftp.c) +add_platform_sources_to_library(otherbackends + winser.c) +add_platform_sources_to_library(agent + winpgntc.c) +add_platform_sources_to_library(guiterminal + windlg.c winctrls.c wincfg.c winprint.c winjump.c sizetip.c) +add_dependencies(guiterminal generated_licence_h) # windlg.c uses licence.h + +add_library(guimisc STATIC + winselgui.c) + +add_executable(pageant + winpgnt.c + winhelp.c + pageant.rc) +add_dependencies(pageant generated_licence_h) +target_link_libraries(pageant + guimisc eventloop agent network crypto utils + ${platform_libraries}) +set_target_properties(pageant PROPERTIES + WIN32_EXECUTABLE ON + LINK_FLAGS "${LFLAG_MANIFEST_NO}") +installed_program(pageant) + +add_executable(plink + winplink.c + ${CMAKE_SOURCE_DIR}/be_all_s.c + winnojmp.c + winnohlp.c + plink.rc) +add_dependencies(plink generated_licence_h) +target_link_libraries(plink + eventloop console noterminal sshclient otherbackends settings network crypto + utils + ${platform_libraries}) +installed_program(plink) + +add_executable(pscp + ${CMAKE_SOURCE_DIR}/pscp.c + ${CMAKE_SOURCE_DIR}/be_ssh.c + winnojmp.c + winnohlp.c + pscp.rc) +add_dependencies(pscp generated_licence_h) +target_link_libraries(pscp + sftpclient eventloop console sshclient settings network crypto utils + ${platform_libraries}) +installed_program(pscp) + +add_executable(psftp + ${CMAKE_SOURCE_DIR}/psftp.c + ${CMAKE_SOURCE_DIR}/be_ssh.c + winnojmp.c + winnohlp.c + psftp.rc) +add_dependencies(psftp generated_licence_h) +target_link_libraries(psftp + sftpclient eventloop console sshclient settings network crypto utils + ${platform_libraries}) +installed_program(psftp) + +add_executable(psocks + winsocks.c + winnohlp.c + ${CMAKE_SOURCE_DIR}/psocks.c + ${CMAKE_SOURCE_DIR}/norand.c + ${CMAKE_SOURCE_DIR}/nocproxy.c + ${CMAKE_SOURCE_DIR}/portfwd.c) +target_link_libraries(psocks + eventloop console network utils + ${platform_libraries}) + +add_executable(putty + window.c + winhelp.c + ${CMAKE_SOURCE_DIR}/be_all_s.c + putty.rc) +add_dependencies(putty generated_licence_h) +target_link_libraries(putty + guiterminal guimisc eventloop sshclient otherbackends settings network crypto + utils + ${platform_libraries}) +set_target_properties(putty PROPERTIES + WIN32_EXECUTABLE ON + LINK_FLAGS "${LFLAG_MANIFEST_NO}") +installed_program(putty) + +add_executable(puttytel + window.c + winhelp.c + ${CMAKE_SOURCE_DIR}/be_nos_s.c + ${CMAKE_SOURCE_DIR}/nogss.c + ${CMAKE_SOURCE_DIR}/norand.c + ${CMAKE_SOURCE_DIR}/nocproxy.c + puttytel.rc) +add_dependencies(puttytel generated_licence_h) +target_link_libraries(puttytel + guiterminal guimisc eventloop otherbackends settings network utils + ${platform_libraries}) +set_target_properties(puttytel PROPERTIES + WIN32_EXECUTABLE ON + LINK_FLAGS "${LFLAG_MANIFEST_NO}") +installed_program(puttytel) + +add_executable(puttygen + winpgen.c + ${CMAKE_SOURCE_DIR}/notiming.c + winnoise.c + winnojmp.c + winstore.c + winhelp.c + ${CMAKE_SOURCE_DIR}/sshpubk.c + ${CMAKE_SOURCE_DIR}/sshrand.c + winctrls.c + puttygen.rc) +add_dependencies(puttygen generated_licence_h) +target_link_libraries(puttygen + keygen guimisc crypto utils + ${platform_libraries}) +set_target_properties(puttygen PROPERTIES + WIN32_EXECUTABLE ON + LINK_FLAGS "${LFLAG_MANIFEST_NO}") +installed_program(puttygen) diff --git a/windows/rcstuff.h b/windows/rcstuff.h index ee2c7696..dbace3f5 100644 --- a/windows/rcstuff.h +++ b/windows/rcstuff.h @@ -5,20 +5,15 @@ #ifndef PUTTY_RCSTUFF_H #define PUTTY_RCSTUFF_H -#ifdef __LCC__ -#include -#else +#ifdef HAVE_CMAKE_H +#include "cmake.h" +#endif -/* Some compilers don't have winresrc.h */ -#ifndef NO_WINRESRC_H -#ifndef MSVC4 +#if HAVE_WINRESRC_H #include -#else +#elif HAVE_WINRES_H #include #endif -#endif - -#endif /* end #ifdef __LCC__ */ /* Some systems don't define this, so I do it myself if necessary */ #ifndef TCS_MULTILINE diff --git a/windows/window.c b/windows/window.c index aa0be484..eab0aff9 100644 --- a/windows/window.c +++ b/windows/window.c @@ -10,13 +10,7 @@ #include #include -#ifdef __WINE__ -#define NO_MULTIMON /* winelib doesn't have this */ -#endif - -#ifndef NO_MULTIMON #define COMPILE_MULTIMON_STUBS -#endif #include "putty.h" #include "terminal.h" @@ -26,7 +20,7 @@ #include "winseat.h" #include "tree234.h" -#ifndef NO_MULTIMON +#ifdef NO_MULTIMON #include #endif @@ -1245,16 +1239,16 @@ static void exact_textout(HDC hdc, int x, int y, CONST RECT *lprc, unsigned short *lpString, UINT cbCount, CONST INT *lpDx, bool opaque) { -#ifdef __LCC__ +#if HAVE_GCP_RESULTSW + GCP_RESULTSW gcpr; +#else /* - * 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). + * If building against old enough headers that the GCP_RESULTSW + * type isn't available, 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); diff --git a/windows/wingss.c b/windows/wingss.c index 10e6d2db..ae36eb13 100644 --- a/windows/wingss.c +++ b/windows/wingss.c @@ -105,7 +105,7 @@ struct ssh_gss_liblist *ssh_gss_setup(Conf *conf) if (!kernel32_module) { kernel32_module = load_system32_dll("kernel32.dll"); } -#if defined _MSC_VER && _MSC_VER < 1900 +#if !HAVE_ADDDLLDIRECTORY /* Omit the type-check because older MSVCs don't have this function */ GET_WINDOWS_FUNCTION_NO_TYPECHECK(kernel32_module, AddDllDirectory); #else diff --git a/windows/winhelp.rc2 b/windows/winhelp.rc2 index 3499d25e..e331629e 100644 --- a/windows/winhelp.rc2 +++ b/windows/winhelp.rc2 @@ -1,7 +1,7 @@ #include "win_res.h" -#ifdef EMBED_CHM -ID_CUSTOM_CHMFILE TYPE_CUSTOM_CHMFILE "../doc/putty.chm" +#ifdef EMBEDDED_CHM_FILE +ID_CUSTOM_CHMFILE TYPE_CUSTOM_CHMFILE EMBEDDED_CHM_FILE #define HELPVER " (with embedded help)" #else #define HELPVER " (without embedded help)" diff --git a/windows/winhsock.c b/windows/winhsock.c index 543b77b6..93bb500a 100644 --- a/windows/winhsock.c +++ b/windows/winhsock.c @@ -272,7 +272,7 @@ static SocketPeerInfo *sk_handle_peer_info(Socket *s) if (!kernel32_module) { kernel32_module = load_system32_dll("kernel32.dll"); -#if (defined _MSC_VER && _MSC_VER < 1900) || defined __MINGW32__ +#if !HAVE_GETNAMEDPIPECLIENTPROCESSID /* For older Visual Studio, and MinGW too (at least as of * Ubuntu 16.04), this function isn't available in the header * files to type-check. Ditto the toolchain I use for diff --git a/windows/winmisc.c b/windows/winmisc.c index 759df011..4fa55398 100644 --- a/windows/winmisc.c +++ b/windows/winmisc.c @@ -157,7 +157,7 @@ void dll_hijacking_protection(void) if (!kernel32_module) { kernel32_module = load_system32_dll("kernel32.dll"); -#if (defined _MSC_VER && _MSC_VER < 1900) +#if !HAVE_SETDEFAULTDLLDIRECTORIES /* For older Visual Studio, this function isn't available in * the header files to type-check */ GET_WINDOWS_FUNCTION_NO_TYPECHECK( diff --git a/windows/winmiscs.c b/windows/winmiscs.c index 571a9122..335711b1 100644 --- a/windows/winmiscs.c +++ b/windows/winmiscs.c @@ -237,7 +237,7 @@ void *minefield_c_realloc(void *p, size_t size) #endif /* MINEFIELD */ -#if defined _MSC_VER && _MSC_VER < 1800 +#if !HAVE_STRTOUMAX /* * Work around lack of strtoumax in older MSVC libraries diff --git a/windows/winstuff.h b/windows/winstuff.h index c0df5a31..ddd3277a 100644 --- a/windows/winstuff.h +++ b/windows/winstuff.h @@ -5,21 +5,22 @@ #ifndef PUTTY_WINSTUFF_H #define PUTTY_WINSTUFF_H -#ifndef AUTO_WINSOCK -#include +#if HAVE_CMAKE_H +#include "cmake.h" #endif + +#include #include #include /* for FILENAME_MAX */ /* We use uintptr_t for Win32/Win64 portability, so we should in * principle include stdint.h, which defines it according to the C - * standard. But older versions of Visual Studio - including the one - * used for official PuTTY builds as of 2015-09-28 - don't provide + * standard. But older versions of Visual Studio don't provide * stdint.h at all, but do (non-standardly) define uintptr_t in * stddef.h. So here we try to make sure _some_ standard header is * included which defines uintptr_t. */ #include -#if !defined _MSC_VER || _MSC_VER >= 1600 || defined __clang__ +#if !HAVE_NO_STDINT_H #include #endif -- cgit v1.2.3 From 6c783f9ad0d2e34db0cbf3147ee872402c90dcd2 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 10 Apr 2021 15:26:53 +0100 Subject: Remove the NO_SECURITY compile-time option. It's had its day. It was there to support pre-WinNT platforms, on which the security APIs don't exist - but more specifically, it was there to support _build tools_ that only knew about pre-WinNT versions of Windows, so that you couldn't even compile a program that would _try_ to refer to the interprocess security APIs. But we don't support those build systems any more in any case: more recent changes like the assumption of (most of) C99 will have stopped this code from building with compilers that old. So there's no reason to clutter the code with backwards compatibility features that won't help. I left NO_SECURITY in place during the CMake migration, so that _just_ in case it needs resurrecting, some version of it will be available in the git history. But I don't expect it to be needed, and I'm deleting the whole thing now. The _runtime_ check for interprocess security libraries is still in place. So PuTTY tools built with a modern toolchain can still at least try to run on the Win95/98/ME series, and they should detect that those system DLLs don't exist and proceed sensibly in their absence. That may also be a thing to throw out sooner or later, but I haven't thrown it out as part of this commit. --- cmake/platforms/windows.cmake | 11 ----------- misc.c | 3 --- windows/wincapi.c | 4 ---- windows/wincapi.h | 4 ---- windows/winnpc.c | 4 ---- windows/winnps.c | 4 ---- windows/winpgnt.c | 20 -------------------- windows/winpgntc.c | 42 ------------------------------------------ windows/winsecur.c | 8 -------- windows/winsecur.h | 4 ---- windows/winshare.c | 8 -------- 11 files changed, 112 deletions(-) diff --git a/cmake/platforms/windows.cmake b/cmake/platforms/windows.cmake index 3a73c6b9..2adda297 100644 --- a/cmake/platforms/windows.cmake +++ b/cmake/platforms/windows.cmake @@ -1,16 +1,5 @@ set(PLATFORM_SUBDIRS windows) -# I copied this over from the pre-CMake build system just to prove it -# still worked, but I should probably remove it now, together with all -# the #ifdefs that depend on it. -# -# Rationale: it was there so that you could do dev builds of PuTTY on -# compilers designed for the pre-NT single-user versions of Windows -# (Win95, Win98 etc). But we're not supporting those development -# environments any more! -set(PUTTY_NO_SECURITY OFF - CACHE BOOL "OBSOLETE AND DANGEROUS - DO NOT DEFINE! \ -Build PuTTY without any use of the Windows security APIs.") set(PUTTY_MINEFIELD OFF CACHE BOOL "Build PuTTY with its built-in memory debugger 'Minefield'") set(PUTTY_GSSAPI ON diff --git a/misc.c b/misc.c index 56f2ba93..f1bb176f 100644 --- a/misc.c +++ b/misc.c @@ -337,9 +337,6 @@ char *buildinfo(const char *newline) #if defined _WINDOWS && defined MINEFIELD strbuf_catf(buf, "%sBuild option: MINEFIELD", newline); #endif -#ifdef NO_SECURITY - strbuf_catf(buf, "%sBuild option: NO_SECURITY", newline); -#endif #ifdef NO_SECUREZEROMEMORY strbuf_catf(buf, "%sBuild option: NO_SECUREZEROMEMORY", newline); #endif diff --git a/windows/wincapi.c b/windows/wincapi.c index 8059f753..d90a634f 100644 --- a/windows/wincapi.c +++ b/windows/wincapi.c @@ -4,8 +4,6 @@ #include "putty.h" -#if !defined NO_SECURITY - #include "putty.h" #include "ssh.h" @@ -85,5 +83,3 @@ char *capi_obfuscate_string(const char *realname) return dupstr(retbuf); } - -#endif /* !defined NO_SECURITY */ diff --git a/windows/wincapi.h b/windows/wincapi.h index 732412e2..07f48cbe 100644 --- a/windows/wincapi.h +++ b/windows/wincapi.h @@ -5,8 +5,6 @@ * in turn. */ -#if !defined NO_SECURITY - DECL_WINDOWS_FUNCTION(extern, BOOL, CryptProtectMemory, (LPVOID,DWORD,DWORD)); bool got_crypt(void); @@ -27,5 +25,3 @@ bool got_crypt(void); * The returned string is dynamically allocated. */ char *capi_obfuscate_string(const char *realname); - -#endif diff --git a/windows/winnpc.c b/windows/winnpc.c index eabfb4bc..9f7fcb1c 100644 --- a/windows/winnpc.c +++ b/windows/winnpc.c @@ -11,8 +11,6 @@ #include "proxy.h" #include "ssh.h" -#if !defined NO_SECURITY - #include "winsecur.h" HANDLE connect_to_named_pipe(const char *pipename, char **err) @@ -94,5 +92,3 @@ Socket *new_named_pipe_client(const char *pipename, Plug *plug) else return make_handle_socket(pipehandle, pipehandle, NULL, plug, true); } - -#endif /* !defined NO_SECURITY */ diff --git a/windows/winnps.c b/windows/winnps.c index 1757cdbb..4f9b638d 100644 --- a/windows/winnps.c +++ b/windows/winnps.c @@ -11,8 +11,6 @@ #include "proxy.h" #include "ssh.h" -#if !defined NO_SECURITY - #include "winsecur.h" typedef struct NamedPipeServerSocket { @@ -236,5 +234,3 @@ Socket *new_named_pipe_listener(const char *pipename, Plug *plug) cleanup: return &ret->sock; } - -#endif /* !defined NO_SECURITY */ diff --git a/windows/winpgnt.c b/windows/winpgnt.c index 38e79149..e96a432e 100644 --- a/windows/winpgnt.c +++ b/windows/winpgnt.c @@ -21,13 +21,11 @@ #include -#ifndef NO_SECURITY #include #ifdef DEBUG_IPC #define _WIN32_WINNT 0x0500 /* for ConvertSidToStringSid */ #include #endif -#endif #define WM_SYSTRAY (WM_APP + 6) #define WM_SYSTRAY2 (WM_APP + 7) @@ -816,7 +814,6 @@ static void update_sessions(void) } } -#ifndef NO_SECURITY /* * Versions of Pageant prior to 0.61 expected this SID on incoming * communications. For backwards compatibility, and more particularly @@ -861,7 +858,6 @@ PSID get_default_sid(void) return ret; } -#endif struct WmCopydataTransaction { char *length, *body; @@ -970,12 +966,10 @@ static char *answer_filemapping_message(const char *mapname) size_t mapsize; unsigned msglen; -#ifndef NO_SECURITY PSID mapsid = NULL; PSID expectedsid = NULL; PSID expectedsid_bc = NULL; PSECURITY_DESCRIPTOR psd = NULL; -#endif wmct.length = wmct.body = NULL; @@ -994,7 +988,6 @@ static char *answer_filemapping_message(const char *mapname) debug("maphandle = %p\n", maphandle); #endif -#ifndef NO_SECURITY if (has_security) { DWORD retd; @@ -1037,7 +1030,6 @@ static char *answer_filemapping_message(const char *mapname) goto cleanup; } } else -#endif /* NO_SECURITY */ { #ifdef DEBUG_IPC debug("security APIs not present\n"); @@ -1395,7 +1387,6 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) has_security = (osPlatformId == VER_PLATFORM_WIN32_NT); if (has_security) { -#ifndef NO_SECURITY /* * Attempt to get the security API we need. */ @@ -1406,13 +1397,6 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) "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 } /* @@ -1543,8 +1527,6 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) return 0; } -#if !defined NO_SECURITY - /* * Set up a named-pipe listener. */ @@ -1567,8 +1549,6 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) sfree(pipename); } -#endif /* !defined NO_SECURITY */ - /* * Set up window classes for two hidden windows: one that receives * all the messages to do with our presence in the system tray, diff --git a/windows/winpgntc.c b/windows/winpgntc.c index 07f75ce6..795dbf88 100644 --- a/windows/winpgntc.c +++ b/windows/winpgntc.c @@ -9,10 +9,8 @@ #include "putty.h" #include "pageant.h" /* for AGENT_MAX_MSGLEN */ -#ifndef NO_SECURITY #include "winsecur.h" #include "wincapi.h" -#endif #define AGENT_COPYDATA_ID 0x804e50ba /* random goop */ @@ -50,7 +48,6 @@ static void wm_copydata_agent_query(strbuf *query, void **out, int *outlen) mapname = dupprintf("PageantRequest%08x", (unsigned)GetCurrentThreadId()); psa = NULL; -#ifndef NO_SECURITY if (got_advapi()) { /* * Make the file mapping we create for communication with @@ -81,7 +78,6 @@ static void wm_copydata_agent_query(strbuf *query, void **out, int *outlen) } } } -#endif /* NO_SECURITY */ filemap = CreateFileMapping(INVALID_HANDLE_VALUE, psa, PAGE_READWRITE, 0, AGENT_MAX_MSGLEN, mapname); @@ -129,8 +125,6 @@ static void wm_copydata_agent_query(strbuf *query, void **out, int *outlen) LocalFree(psd); } -#ifndef NO_SECURITY - char *agent_named_pipe_name(void) { char *username, *suffix, *pipename; @@ -303,39 +297,3 @@ agent_pending_query *agent_query( wm_copydata_agent_query(query, out, outlen); return NULL; } - -#else /* NO_SECURITY */ - -Socket *agent_connect(void *vctx, Plug *plug) -{ - unreachable("no agent_connect_ctx can be constructed on this platform"); -} - -agent_connect_ctx *agent_get_connect_ctx(void) -{ - return NULL; -} - -void agent_free_connect_ctx(agent_connect_ctx *ctx) -{ -} - -bool agent_exists(void) -{ - return wm_copydata_agent_exists(); -} - -agent_pending_query *agent_query( - strbuf *query, void **out, int *outlen, - void (*callback)(void *, void *, int), void *callback_ctx) -{ - wm_copydata_agent_query(query, out, outlen); - return NULL; -} - -void agent_cancel_query(agent_pending_query *q) -{ - unreachable("Windows agent queries are never asynchronous!"); -} - -#endif /* NO_SECURITY */ diff --git a/windows/winsecur.c b/windows/winsecur.c index a1164af5..c5251c31 100644 --- a/windows/winsecur.c +++ b/windows/winsecur.c @@ -7,8 +7,6 @@ #include "putty.h" -#if !defined NO_SECURITY - #include "winsecur.h" /* Initialised once, then kept around to reuse forever */ @@ -299,7 +297,6 @@ static bool really_restrict_process_acl(char **error) } return ret; } -#endif /* !defined NO_SECURITY */ /* * Lock down our process's ACL, to present an obstacle to malware @@ -323,12 +320,7 @@ void restrict_process_acl(void) char *error = NULL; bool ret; -#if !defined NO_SECURITY ret = really_restrict_process_acl(&error); -#else - ret = false; - error = dupstr("ACL restrictions not compiled into this binary"); -#endif if (!ret) modalfatalbox("Could not restrict process ACL: %s", error); } diff --git a/windows/winsecur.h b/windows/winsecur.h index fdd39d81..6ca736a3 100644 --- a/windows/winsecur.h +++ b/windows/winsecur.h @@ -4,8 +4,6 @@ * centralises the machinery for dynamically loading that library. */ -#if !defined NO_SECURITY - #include /* @@ -49,5 +47,3 @@ PSID get_user_sid(void); */ bool make_private_security_descriptor( DWORD permissions, PSECURITY_DESCRIPTOR *psd, PACL *acl, char **error); - -#endif diff --git a/windows/winshare.c b/windows/winshare.c index f0e409ac..51a84438 100644 --- a/windows/winshare.c +++ b/windows/winshare.c @@ -5,8 +5,6 @@ #include #include -#if !defined NO_SECURITY - #include "tree234.h" #include "putty.h" #include "network.h" @@ -143,9 +141,3 @@ int platform_ssh_share(const char *pi_name, Conf *conf, void platform_ssh_share_cleanup(const char *name) { } - -#else /* !defined NO_SECURITY */ - -#include "noshare.c" - -#endif /* !defined NO_SECURITY */ -- cgit v1.2.3 From 3996919f5eb1baa5dad989cb0bdbbabbdbf99bcc Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 17 Apr 2021 19:17:28 +0100 Subject: Fix a few cmake configure-time checks. A couple of actual checks were missing (elf_aux_info, sysctlbyname). Several more were accidentally left out of cmake.h.in, meaning they wouldn't be propagated from cmake's variable space into the actual compilation. And a handful of checks in the C source were still using the autotools-style 'if defined' in place of the cmake-style "it's always 0 or 1" plain #if. --- cmake/cmake.h.in | 7 +++++++ cmake/platforms/unix.cmake | 2 ++ unix/uxmisc.c | 2 +- unix/uxutils.h | 4 ++-- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/cmake/cmake.h.in b/cmake/cmake.h.in index 3ddf25fc..f051c759 100644 --- a/cmake/cmake.h.in +++ b/cmake/cmake.h.in @@ -16,6 +16,11 @@ #cmakedefine NOT_X_WINDOWS +#cmakedefine01 HAVE_ASM_HWCAP_H +#cmakedefine01 HAVE_SYS_AUXV_H +#cmakedefine01 HAVE_SYS_SYSCTL_H +#cmakedefine01 HAVE_SYS_TYPES_H +#cmakedefine01 HAVE_GLOB_H #cmakedefine01 HAVE_FUTIMES #cmakedefine01 HAVE_GETADDRINFO #cmakedefine01 HAVE_POSIX_OPENPT @@ -28,6 +33,8 @@ #cmakedefine01 HAVE_SETPWENT #cmakedefine01 HAVE_ENDPWENT #cmakedefine01 HAVE_GETAUXVAL +#cmakedefine01 HAVE_ELF_AUX_INFO +#cmakedefine01 HAVE_SYSCTLBYNAME #cmakedefine01 HAVE_CLOCK_MONOTONIC #cmakedefine01 HAVE_CLOCK_GETTIME #cmakedefine01 HAVE_SO_PEERCRED diff --git a/cmake/platforms/unix.cmake b/cmake/platforms/unix.cmake index 1bd905c6..c09d5d8f 100644 --- a/cmake/platforms/unix.cmake +++ b/cmake/platforms/unix.cmake @@ -34,6 +34,8 @@ check_symbol_exists(dirfd "sys/types.h;dirent.h" HAVE_DIRFD) check_symbol_exists(setpwent "sys/types.h;pwd.h" HAVE_SETPWENT) check_symbol_exists(endpwent "sys/types.h;pwd.h" HAVE_ENDPWENT) check_symbol_exists(getauxval "sys/auxv.h" HAVE_GETAUXVAL) +check_symbol_exists(elf_aux_info "sys/auxv.h" HAVE_ELF_AUX_INFO) +check_symbol_exists(sysctlbyname "sys/types.h;sys/sysctl.h" HAVE_SYSCTLBYNAME) check_symbol_exists(CLOCK_MONOTONIC "time.h" HAVE_CLOCK_MONOTONIC) check_symbol_exists(clock_gettime "time.h" HAVE_CLOCK_GETTIME) diff --git a/unix/uxmisc.c b/unix/uxmisc.c index 57df7b7a..876725bd 100644 --- a/unix/uxmisc.c +++ b/unix/uxmisc.c @@ -24,7 +24,7 @@ unsigned long getticks(void) * need a decent number of them to fit into a 32-bit word so it * can be used for keepalives. */ -#if defined HAVE_CLOCK_GETTIME && defined HAVE_DECL_CLOCK_MONOTONIC +#if HAVE_CLOCK_GETTIME && HAVE_CLOCK_MONOTONIC { /* Use CLOCK_MONOTONIC if available, so as to be unconfused if * the system clock changes. */ diff --git a/unix/uxutils.h b/unix/uxutils.h index 8c70a8a1..fcb7e9b7 100644 --- a/unix/uxutils.h +++ b/unix/uxutils.h @@ -30,9 +30,9 @@ #include #endif -#if defined HAVE_GETAUXVAL +#if HAVE_GETAUXVAL /* No code needed: getauxval has just the API we want already */ -#elif defined HAVE_ELF_AUX_INFO +#elif HAVE_ELF_AUX_INFO /* Implement the simple getauxval API in terms of FreeBSD elf_aux_info */ static inline u_long getauxval(int which) { -- cgit v1.2.3 From 9469fa38f15ba4022a73e998ee80e9eaf718e52d Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 17 Apr 2021 19:03:53 +0100 Subject: Remove weird test and definition of HAVE_PUTUTLINE. I don't know what that was doing there - not only was defining it on purpose a strange idea, but nothing ever tested it afterwards! --- unix/uxpty.c | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/unix/uxpty.c b/unix/uxpty.c index d693463f..7d65a4fc 100644 --- a/unix/uxpty.c +++ b/unix/uxpty.c @@ -49,20 +49,6 @@ #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 Pty; /* -- cgit v1.2.3 From 0881c9d2b8de7d563e222042cdb65c422a12caa6 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 18 Apr 2021 07:32:25 +0100 Subject: Delete obsolete and unused resource.h. I have no idea what it was doing there! It's been there, unmodified apart from whitespace tidying, since the very start of the VCS history; it's Windows-specific but never got moved into the windows subdirectory; and nothing even includes it. Throw it away! --- resource.h | 15 --------------- 1 file changed, 15 deletions(-) delete mode 100644 resource.h diff --git a/resource.h b/resource.h deleted file mode 100644 index e856d6b9..00000000 --- a/resource.h +++ /dev/null @@ -1,15 +0,0 @@ -//{{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 -- cgit v1.2.3 From 3396c97da9d658570a4b80bd3221cefdc0b96dbf Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 17 Apr 2021 15:22:20 +0100 Subject: New library-style 'utils' subdirectories. Now that the new CMake build system is encouraging us to lay out the code like a set of libraries, it seems like a good idea to make them look more _like_ libraries, by putting things into separate modules as far as possible. This fixes several previous annoyances in which you had to link against some object in order to get a function you needed, but that object also contained other functions you didn't need which included link-time symbol references you didn't want to have to deal with. The usual offender was subsidiary supporting programs including misc.c for some innocuous function and then finding they had to deal with the requirements of buildinfo(). This big reorganisation introduces three new subdirectories called 'utils', one at the top level and one in each platform subdir. In each case, the directory contains basically the same files that were previously placed in the 'utils' build-time library, except that the ones that were extremely miscellaneous (misc.c, utils.c, uxmisc.c, winmisc.c, winmiscs.c, winutils.c) have been split up into much smaller pieces. --- CMakeLists.txt | 50 +- conf.c | 593 --------- marshal.c | 318 ----- memory.c | 134 -- misc.c | 419 ------ miscucs.c | 28 - sessprep.c | 84 -- sshutils.c | 128 -- stripctrl.c | 476 ------- time.c | 16 - tree234.c | 1611 ------------------------ unix/CMakeLists.txt | 25 +- unix/utils/arm_arch_queries.c | 80 ++ unix/utils/arm_arch_queries.h | 65 + unix/utils/block_signal.c | 21 + unix/utils/cloexec.c | 49 + unix/utils/dputs.c | 24 + unix/utils/filename.c | 72 ++ unix/utils/fontspec.c | 35 + unix/utils/get_username.c | 52 + unix/utils/getticks.c | 33 + unix/utils/keysym_to_unicode.c | 1011 +++++++++++++++ unix/utils/make_dir_and_check_ours.c | 60 + unix/utils/make_dir_path.c | 39 + unix/utils/nonblock.c | 55 + unix/utils/open_for_write_would_lose_data.c | 44 + unix/utils/pgp_fingerprints.c | 23 + unix/utils/pollwrap.c | 190 +++ unix/utils/signal.c | 30 + unix/utils/x11_ignore_error.c | 88 ++ unix/uxmisc.c | 371 ------ unix/uxpoll.c | 169 --- unix/uxsignal.c | 47 - unix/uxutils.c | 65 - unix/uxutils.h | 65 - unix/x11misc.c | 83 -- unix/xkeysym.c | 1011 --------------- utils.c | 1122 ----------------- utils/base64_decode_atom.c | 54 + utils/base64_encode_atom.c | 30 + utils/bufchain.c | 173 +++ utils/buildinfo.c | 158 +++ utils/burnstr.c | 15 + utils/chomp.c | 26 + utils/conf.c | 593 +++++++++ utils/conf_dest.c | 15 + utils/conf_launchable.c | 14 + utils/ctrlparse.c | 49 + utils/debug.c | 56 + utils/dupcat.c | 48 + utils/dupprintf.c | 100 ++ utils/dupstr.c | 19 + utils/encode_utf8.c | 29 + utils/fgetline.c | 25 + utils/host_strchr.c | 18 + utils/host_strchr_internal.c | 80 ++ utils/host_strcspn.c | 19 + utils/host_strduptrim.c | 51 + utils/host_strrchr.c | 18 + utils/ltime.c | 16 + utils/marshal.c | 318 +++++ utils/memory.c | 134 ++ utils/memxor.c | 34 + utils/miscucs.c | 28 + utils/null_lp.c | 8 + utils/nullseat.c | 42 + utils/nullstrcmp.c | 21 + utils/out_of_memory.c | 11 + utils/parse_blocksize.c | 40 + utils/prompts.c | 59 + utils/ptrlen.c | 95 ++ utils/read_file_into.c | 19 + utils/seat_connection_fatal.c | 20 + utils/sessprep.c | 84 ++ utils/sk_free_peer_info.c | 14 + utils/smemclr.c | 42 + utils/smemeq.c | 25 + utils/ssh2_pick_fingerprint.c | 27 + utils/sshutils.c | 128 ++ utils/strbuf.c | 118 ++ utils/string_length_for_printf.c | 21 + utils/stripctrl.c | 476 +++++++ utils/tree234.c | 1611 ++++++++++++++++++++++++ utils/utils.h | 12 + utils/validate_manual_hostkey.c | 116 ++ utils/version.c | 23 + utils/wcwidth.c | 558 ++++++++ utils/wildcard.c | 486 +++++++ utils/write_c_string_literal.c | 31 + version.c | 23 - wcwidth.c | 558 -------- wildcard.c | 486 ------- windows/CMakeLists.txt | 34 +- windows/utils/arm_arch_queries.c | 39 + windows/utils/capi.c | 85 ++ windows/utils/defaults.c | 40 + windows/utils/dll_hijacking_protection.c | 43 + windows/utils/dputs.c | 37 + windows/utils/escape_registry_key.c | 48 + windows/utils/filename.c | 54 + windows/utils/fontspec.c | 43 + windows/utils/get_username.c | 77 ++ windows/utils/getdlgitemtext_alloc.c | 20 + windows/utils/is_console_handle.c | 13 + windows/utils/load_system32_dll.c | 26 + windows/utils/ltime.c | 27 + windows/utils/makedlgitemborderless.c | 19 + windows/utils/message_box.c | 49 + windows/utils/minefield.c | 227 ++++ windows/utils/open_for_write_would_lose_data.c | 38 + windows/utils/pgp_fingerprints_msgbox.c | 25 + windows/utils/platform_get_x_display.c | 12 + windows/utils/registry_get_string.c | 43 + windows/utils/request_file.c | 71 ++ windows/utils/security.c | 326 +++++ windows/utils/split_into_argv.c | 459 +++++++ windows/utils/strtoumax.c | 12 + windows/utils/version.c | 40 + windows/utils/win_strerror.c | 72 ++ windows/wincapi.c | 85 -- windows/windefs.c | 40 - windows/winmisc.c | 467 ------- windows/winmiscs.c | 285 ----- windows/winsecur.c | 326 ----- windows/winstuff.h | 6 + windows/wintime.c | 26 - windows/winutils.c | 650 ---------- 127 files changed, 10232 insertions(+), 9692 deletions(-) delete mode 100644 conf.c delete mode 100644 marshal.c delete mode 100644 memory.c delete mode 100644 misc.c delete mode 100644 miscucs.c delete mode 100644 sessprep.c delete mode 100644 sshutils.c delete mode 100644 stripctrl.c delete mode 100644 time.c delete mode 100644 tree234.c create mode 100644 unix/utils/arm_arch_queries.c create mode 100644 unix/utils/arm_arch_queries.h create mode 100644 unix/utils/block_signal.c create mode 100644 unix/utils/cloexec.c create mode 100644 unix/utils/dputs.c create mode 100644 unix/utils/filename.c create mode 100644 unix/utils/fontspec.c create mode 100644 unix/utils/get_username.c create mode 100644 unix/utils/getticks.c create mode 100644 unix/utils/keysym_to_unicode.c create mode 100644 unix/utils/make_dir_and_check_ours.c create mode 100644 unix/utils/make_dir_path.c create mode 100644 unix/utils/nonblock.c create mode 100644 unix/utils/open_for_write_would_lose_data.c create mode 100644 unix/utils/pgp_fingerprints.c create mode 100644 unix/utils/pollwrap.c create mode 100644 unix/utils/signal.c create mode 100644 unix/utils/x11_ignore_error.c delete mode 100644 unix/uxmisc.c delete mode 100644 unix/uxpoll.c delete mode 100644 unix/uxsignal.c delete mode 100644 unix/uxutils.c delete mode 100644 unix/uxutils.h delete mode 100644 unix/x11misc.c delete mode 100644 unix/xkeysym.c delete mode 100644 utils.c create mode 100644 utils/base64_decode_atom.c create mode 100644 utils/base64_encode_atom.c create mode 100644 utils/bufchain.c create mode 100644 utils/buildinfo.c create mode 100644 utils/burnstr.c create mode 100644 utils/chomp.c create mode 100644 utils/conf.c create mode 100644 utils/conf_dest.c create mode 100644 utils/conf_launchable.c create mode 100644 utils/ctrlparse.c create mode 100644 utils/debug.c create mode 100644 utils/dupcat.c create mode 100644 utils/dupprintf.c create mode 100644 utils/dupstr.c create mode 100644 utils/encode_utf8.c create mode 100644 utils/fgetline.c create mode 100644 utils/host_strchr.c create mode 100644 utils/host_strchr_internal.c create mode 100644 utils/host_strcspn.c create mode 100644 utils/host_strduptrim.c create mode 100644 utils/host_strrchr.c create mode 100644 utils/ltime.c create mode 100644 utils/marshal.c create mode 100644 utils/memory.c create mode 100644 utils/memxor.c create mode 100644 utils/miscucs.c create mode 100644 utils/null_lp.c create mode 100644 utils/nullseat.c create mode 100644 utils/nullstrcmp.c create mode 100644 utils/out_of_memory.c create mode 100644 utils/parse_blocksize.c create mode 100644 utils/prompts.c create mode 100644 utils/ptrlen.c create mode 100644 utils/read_file_into.c create mode 100644 utils/seat_connection_fatal.c create mode 100644 utils/sessprep.c create mode 100644 utils/sk_free_peer_info.c create mode 100644 utils/smemclr.c create mode 100644 utils/smemeq.c create mode 100644 utils/ssh2_pick_fingerprint.c create mode 100644 utils/sshutils.c create mode 100644 utils/strbuf.c create mode 100644 utils/string_length_for_printf.c create mode 100644 utils/stripctrl.c create mode 100644 utils/tree234.c create mode 100644 utils/utils.h create mode 100644 utils/validate_manual_hostkey.c create mode 100644 utils/version.c create mode 100644 utils/wcwidth.c create mode 100644 utils/wildcard.c create mode 100644 utils/write_c_string_literal.c delete mode 100644 version.c delete mode 100644 wcwidth.c delete mode 100644 wildcard.c create mode 100644 windows/utils/arm_arch_queries.c create mode 100644 windows/utils/capi.c create mode 100644 windows/utils/defaults.c create mode 100644 windows/utils/dll_hijacking_protection.c create mode 100644 windows/utils/dputs.c create mode 100644 windows/utils/escape_registry_key.c create mode 100644 windows/utils/filename.c create mode 100644 windows/utils/fontspec.c create mode 100644 windows/utils/get_username.c create mode 100644 windows/utils/getdlgitemtext_alloc.c create mode 100644 windows/utils/is_console_handle.c create mode 100644 windows/utils/load_system32_dll.c create mode 100644 windows/utils/ltime.c create mode 100644 windows/utils/makedlgitemborderless.c create mode 100644 windows/utils/message_box.c create mode 100644 windows/utils/minefield.c create mode 100644 windows/utils/open_for_write_would_lose_data.c create mode 100644 windows/utils/pgp_fingerprints_msgbox.c create mode 100644 windows/utils/platform_get_x_display.c create mode 100644 windows/utils/registry_get_string.c create mode 100644 windows/utils/request_file.c create mode 100644 windows/utils/security.c create mode 100644 windows/utils/split_into_argv.c create mode 100644 windows/utils/strtoumax.c create mode 100644 windows/utils/version.c create mode 100644 windows/utils/win_strerror.c delete mode 100644 windows/wincapi.c delete mode 100644 windows/windefs.c delete mode 100644 windows/winmisc.c delete mode 100644 windows/winmiscs.c delete mode 100644 windows/winsecur.c delete mode 100644 windows/wintime.c delete mode 100644 windows/winutils.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 1c467760..b1050f2d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,8 +4,54 @@ project(putty LANGUAGES C) include(cmake/setup.cmake) add_library(utils STATIC - memory.c marshal.c utils.c conf.c sshutils.c tree234.c version.c - wildcard.c wcwidth.c misc.c miscucs.c stripctrl.c sessprep.c + utils/base64_decode_atom.c + utils/base64_encode_atom.c + utils/bufchain.c + utils/buildinfo.c + utils/burnstr.c + utils/chomp.c + utils/conf.c + utils/conf_dest.c + utils/conf_launchable.c + utils/ctrlparse.c + utils/debug.c + utils/dupcat.c + utils/dupprintf.c + utils/dupstr.c + utils/encode_utf8.c + utils/fgetline.c + utils/host_strchr.c + utils/host_strchr_internal.c + utils/host_strcspn.c + utils/host_strduptrim.c + utils/host_strrchr.c + utils/marshal.c + utils/memory.c + utils/memxor.c + utils/miscucs.c + utils/null_lp.c + utils/nullseat.c + utils/nullstrcmp.c + utils/out_of_memory.c + utils/parse_blocksize.c + utils/prompts.c + utils/ptrlen.c + utils/read_file_into.c + utils/seat_connection_fatal.c + utils/sessprep.c + utils/sk_free_peer_info.c + utils/smemeq.c + utils/ssh2_pick_fingerprint.c + utils/sshutils.c + utils/strbuf.c + utils/string_length_for_printf.c + utils/stripctrl.c + utils/tree234.c + utils/validate_manual_hostkey.c + utils/version.c + utils/wcwidth.c + utils/wildcard.c + utils/write_c_string_literal.c ${GENERATED_COMMIT_C}) add_library(logging OBJECT diff --git a/conf.c b/conf.c deleted file mode 100644 index ecd26cd0..00000000 --- a/conf.c +++ /dev/null @@ -1,593 +0,0 @@ -/* - * conf.c: implementation of the internal storage format used for - * the configuration of a PuTTY session. - */ - -#include -#include -#include - -#include "tree234.h" -#include "putty.h" - -/* - * Enumeration of types used in keys and values. - */ -typedef enum { - TYPE_NONE, TYPE_BOOL, TYPE_INT, TYPE_STR, TYPE_FILENAME, TYPE_FONT -} Type; - -/* - * Arrays which allow us to look up the subkey and value types for a - * given primary key id. - */ -#define CONF_SUBKEYTYPE_DEF(valtype, keytype, keyword) TYPE_ ## keytype, -static int subkeytypes[] = { CONFIG_OPTIONS(CONF_SUBKEYTYPE_DEF) }; -#define CONF_VALUETYPE_DEF(valtype, keytype, keyword) TYPE_ ## valtype, -static int valuetypes[] = { CONFIG_OPTIONS(CONF_VALUETYPE_DEF) }; - -/* - * Configuration keys are primarily integers (big enum of all the - * different configurable options); some keys have string-designated - * subkeys, such as the list of environment variables (subkeys - * defined by the variable names); some have integer-designated - * subkeys (wordness, colours, preference lists). - */ -struct key { - int primary; - union { - int i; - char *s; - } secondary; -}; - -/* Variant form of struct key which doesn't contain dynamic data, used - * for lookups. */ -struct constkey { - int primary; - union { - int i; - const char *s; - } secondary; -}; - -struct value { - union { - bool boolval; - int intval; - char *stringval; - Filename *fileval; - FontSpec *fontval; - } u; -}; - -struct conf_entry { - struct key key; - struct value value; -}; - -struct conf_tag { - tree234 *tree; -}; - -/* - * Because 'struct key' is the first element in 'struct conf_entry', - * it's safe (guaranteed by the C standard) to cast arbitrarily back - * and forth between the two types. Therefore, we only need one - * comparison function, which can double as a main sort function for - * the tree (comparing two conf_entry structures with each other) - * and a search function (looking up an externally supplied key). - */ -static int conf_cmp(void *av, void *bv) -{ - struct key *a = (struct key *)av; - struct key *b = (struct key *)bv; - - if (a->primary < b->primary) - return -1; - else if (a->primary > b->primary) - return +1; - switch (subkeytypes[a->primary]) { - case TYPE_INT: - if (a->secondary.i < b->secondary.i) - return -1; - else if (a->secondary.i > b->secondary.i) - return +1; - return 0; - case TYPE_STR: - return strcmp(a->secondary.s, b->secondary.s); - default: - return 0; - } -} - -static int conf_cmp_constkey(void *av, void *bv) -{ - struct key *a = (struct key *)av; - struct constkey *b = (struct constkey *)bv; - - if (a->primary < b->primary) - return -1; - else if (a->primary > b->primary) - return +1; - switch (subkeytypes[a->primary]) { - case TYPE_INT: - if (a->secondary.i < b->secondary.i) - return -1; - else if (a->secondary.i > b->secondary.i) - return +1; - return 0; - case TYPE_STR: - return strcmp(a->secondary.s, b->secondary.s); - default: - return 0; - } -} - -/* - * Free any dynamic data items pointed to by a 'struct key'. We - * don't free the structure itself, since it's probably part of a - * larger allocated block. - */ -static void free_key(struct key *key) -{ - if (subkeytypes[key->primary] == TYPE_STR) - sfree(key->secondary.s); -} - -/* - * Copy a 'struct key' into another one, copying its dynamic data - * if necessary. - */ -static void copy_key(struct key *to, struct key *from) -{ - to->primary = from->primary; - switch (subkeytypes[to->primary]) { - case TYPE_INT: - to->secondary.i = from->secondary.i; - break; - case TYPE_STR: - to->secondary.s = dupstr(from->secondary.s); - break; - } -} - -/* - * Free any dynamic data items pointed to by a 'struct value'. We - * don't free the value itself, since it's probably part of a larger - * allocated block. - */ -static void free_value(struct value *val, int type) -{ - if (type == TYPE_STR) - sfree(val->u.stringval); - else if (type == TYPE_FILENAME) - filename_free(val->u.fileval); - else if (type == TYPE_FONT) - fontspec_free(val->u.fontval); -} - -/* - * Copy a 'struct value' into another one, copying its dynamic data - * if necessary. - */ -static void copy_value(struct value *to, struct value *from, int type) -{ - switch (type) { - case TYPE_BOOL: - to->u.boolval = from->u.boolval; - break; - case TYPE_INT: - to->u.intval = from->u.intval; - break; - case TYPE_STR: - to->u.stringval = dupstr(from->u.stringval); - break; - case TYPE_FILENAME: - to->u.fileval = filename_copy(from->u.fileval); - break; - case TYPE_FONT: - to->u.fontval = fontspec_copy(from->u.fontval); - break; - } -} - -/* - * Free an entire 'struct conf_entry' and its dynamic data. - */ -static void free_entry(struct conf_entry *entry) -{ - free_key(&entry->key); - free_value(&entry->value, valuetypes[entry->key.primary]); - sfree(entry); -} - -Conf *conf_new(void) -{ - Conf *conf = snew(struct conf_tag); - - conf->tree = newtree234(conf_cmp); - - return conf; -} - -static void conf_clear(Conf *conf) -{ - struct conf_entry *entry; - - while ((entry = delpos234(conf->tree, 0)) != NULL) - free_entry(entry); -} - -void conf_free(Conf *conf) -{ - conf_clear(conf); - freetree234(conf->tree); - sfree(conf); -} - -static void conf_insert(Conf *conf, struct conf_entry *entry) -{ - struct conf_entry *oldentry = add234(conf->tree, entry); - if (oldentry && oldentry != entry) { - del234(conf->tree, oldentry); - free_entry(oldentry); - oldentry = add234(conf->tree, entry); - assert(oldentry == entry); - } -} - -void conf_copy_into(Conf *newconf, Conf *oldconf) -{ - struct conf_entry *entry, *entry2; - int i; - - conf_clear(newconf); - - for (i = 0; (entry = index234(oldconf->tree, i)) != NULL; i++) { - entry2 = snew(struct conf_entry); - copy_key(&entry2->key, &entry->key); - copy_value(&entry2->value, &entry->value, - valuetypes[entry->key.primary]); - add234(newconf->tree, entry2); - } -} - -Conf *conf_copy(Conf *oldconf) -{ - Conf *newconf = conf_new(); - - conf_copy_into(newconf, oldconf); - - return newconf; -} - -bool conf_get_bool(Conf *conf, int primary) -{ - struct key key; - struct conf_entry *entry; - - assert(subkeytypes[primary] == TYPE_NONE); - assert(valuetypes[primary] == TYPE_BOOL); - key.primary = primary; - entry = find234(conf->tree, &key, NULL); - assert(entry); - return entry->value.u.boolval; -} - -int conf_get_int(Conf *conf, int primary) -{ - struct key key; - struct conf_entry *entry; - - assert(subkeytypes[primary] == TYPE_NONE); - assert(valuetypes[primary] == TYPE_INT); - key.primary = primary; - entry = find234(conf->tree, &key, NULL); - assert(entry); - return entry->value.u.intval; -} - -int conf_get_int_int(Conf *conf, int primary, int secondary) -{ - struct key key; - struct conf_entry *entry; - - assert(subkeytypes[primary] == TYPE_INT); - assert(valuetypes[primary] == TYPE_INT); - key.primary = primary; - key.secondary.i = secondary; - entry = find234(conf->tree, &key, NULL); - assert(entry); - return entry->value.u.intval; -} - -char *conf_get_str(Conf *conf, int primary) -{ - struct key key; - struct conf_entry *entry; - - assert(subkeytypes[primary] == TYPE_NONE); - assert(valuetypes[primary] == TYPE_STR); - key.primary = primary; - entry = find234(conf->tree, &key, NULL); - assert(entry); - return entry->value.u.stringval; -} - -char *conf_get_str_str_opt(Conf *conf, int primary, const char *secondary) -{ - struct key key; - struct conf_entry *entry; - - assert(subkeytypes[primary] == TYPE_STR); - assert(valuetypes[primary] == TYPE_STR); - key.primary = primary; - key.secondary.s = (char *)secondary; - entry = find234(conf->tree, &key, NULL); - return entry ? entry->value.u.stringval : NULL; -} - -char *conf_get_str_str(Conf *conf, int primary, const char *secondary) -{ - char *ret = conf_get_str_str_opt(conf, primary, secondary); - assert(ret); - return ret; -} - -char *conf_get_str_strs(Conf *conf, int primary, - char *subkeyin, char **subkeyout) -{ - struct constkey key; - struct conf_entry *entry; - - assert(subkeytypes[primary] == TYPE_STR); - assert(valuetypes[primary] == TYPE_STR); - key.primary = primary; - if (subkeyin) { - key.secondary.s = subkeyin; - entry = findrel234(conf->tree, &key, NULL, REL234_GT); - } else { - key.secondary.s = ""; - entry = findrel234(conf->tree, &key, conf_cmp_constkey, REL234_GE); - } - if (!entry || entry->key.primary != primary) - return NULL; - *subkeyout = entry->key.secondary.s; - return entry->value.u.stringval; -} - -char *conf_get_str_nthstrkey(Conf *conf, int primary, int n) -{ - struct constkey key; - struct conf_entry *entry; - int index; - - assert(subkeytypes[primary] == TYPE_STR); - assert(valuetypes[primary] == TYPE_STR); - key.primary = primary; - key.secondary.s = ""; - entry = findrelpos234(conf->tree, &key, conf_cmp_constkey, - REL234_GE, &index); - if (!entry || entry->key.primary != primary) - return NULL; - entry = index234(conf->tree, index + n); - if (!entry || entry->key.primary != primary) - return NULL; - return entry->key.secondary.s; -} - -Filename *conf_get_filename(Conf *conf, int primary) -{ - struct key key; - struct conf_entry *entry; - - assert(subkeytypes[primary] == TYPE_NONE); - assert(valuetypes[primary] == TYPE_FILENAME); - key.primary = primary; - entry = find234(conf->tree, &key, NULL); - assert(entry); - return entry->value.u.fileval; -} - -FontSpec *conf_get_fontspec(Conf *conf, int primary) -{ - struct key key; - struct conf_entry *entry; - - assert(subkeytypes[primary] == TYPE_NONE); - assert(valuetypes[primary] == TYPE_FONT); - key.primary = primary; - entry = find234(conf->tree, &key, NULL); - assert(entry); - return entry->value.u.fontval; -} - -void conf_set_bool(Conf *conf, int primary, bool value) -{ - struct conf_entry *entry = snew(struct conf_entry); - - assert(subkeytypes[primary] == TYPE_NONE); - assert(valuetypes[primary] == TYPE_BOOL); - entry->key.primary = primary; - entry->value.u.boolval = value; - conf_insert(conf, entry); -} - -void conf_set_int(Conf *conf, int primary, int value) -{ - struct conf_entry *entry = snew(struct conf_entry); - - assert(subkeytypes[primary] == TYPE_NONE); - assert(valuetypes[primary] == TYPE_INT); - entry->key.primary = primary; - entry->value.u.intval = value; - conf_insert(conf, entry); -} - -void conf_set_int_int(Conf *conf, int primary, - int secondary, int value) -{ - struct conf_entry *entry = snew(struct conf_entry); - - assert(subkeytypes[primary] == TYPE_INT); - assert(valuetypes[primary] == TYPE_INT); - entry->key.primary = primary; - entry->key.secondary.i = secondary; - entry->value.u.intval = value; - conf_insert(conf, entry); -} - -void conf_set_str(Conf *conf, int primary, const char *value) -{ - struct conf_entry *entry = snew(struct conf_entry); - - assert(subkeytypes[primary] == TYPE_NONE); - assert(valuetypes[primary] == TYPE_STR); - entry->key.primary = primary; - entry->value.u.stringval = dupstr(value); - conf_insert(conf, entry); -} - -void conf_set_str_str(Conf *conf, int primary, const char *secondary, - const char *value) -{ - struct conf_entry *entry = snew(struct conf_entry); - - assert(subkeytypes[primary] == TYPE_STR); - assert(valuetypes[primary] == TYPE_STR); - entry->key.primary = primary; - entry->key.secondary.s = dupstr(secondary); - entry->value.u.stringval = dupstr(value); - conf_insert(conf, entry); -} - -void conf_del_str_str(Conf *conf, int primary, const char *secondary) -{ - struct key key; - struct conf_entry *entry; - - assert(subkeytypes[primary] == TYPE_STR); - assert(valuetypes[primary] == TYPE_STR); - key.primary = primary; - key.secondary.s = (char *)secondary; - entry = find234(conf->tree, &key, NULL); - if (entry) { - del234(conf->tree, entry); - free_entry(entry); - } - } - -void conf_set_filename(Conf *conf, int primary, const Filename *value) -{ - struct conf_entry *entry = snew(struct conf_entry); - - assert(subkeytypes[primary] == TYPE_NONE); - assert(valuetypes[primary] == TYPE_FILENAME); - entry->key.primary = primary; - entry->value.u.fileval = filename_copy(value); - conf_insert(conf, entry); -} - -void conf_set_fontspec(Conf *conf, int primary, const FontSpec *value) -{ - struct conf_entry *entry = snew(struct conf_entry); - - assert(subkeytypes[primary] == TYPE_NONE); - assert(valuetypes[primary] == TYPE_FONT); - entry->key.primary = primary; - entry->value.u.fontval = fontspec_copy(value); - conf_insert(conf, entry); -} - -void conf_serialise(BinarySink *bs, Conf *conf) -{ - int i; - struct conf_entry *entry; - - for (i = 0; (entry = index234(conf->tree, i)) != NULL; i++) { - put_uint32(bs, entry->key.primary); - - switch (subkeytypes[entry->key.primary]) { - case TYPE_INT: - put_uint32(bs, entry->key.secondary.i); - break; - case TYPE_STR: - put_asciz(bs, entry->key.secondary.s); - break; - } - switch (valuetypes[entry->key.primary]) { - case TYPE_BOOL: - put_bool(bs, entry->value.u.boolval); - break; - case TYPE_INT: - put_uint32(bs, entry->value.u.intval); - break; - case TYPE_STR: - put_asciz(bs, entry->value.u.stringval); - break; - case TYPE_FILENAME: - filename_serialise(bs, entry->value.u.fileval); - break; - case TYPE_FONT: - fontspec_serialise(bs, entry->value.u.fontval); - break; - } - } - - put_uint32(bs, 0xFFFFFFFFU); -} - -bool conf_deserialise(Conf *conf, BinarySource *src) -{ - struct conf_entry *entry; - unsigned primary; - - while (1) { - primary = get_uint32(src); - - if (get_err(src)) - return false; - if (primary == 0xFFFFFFFFU) - return true; - if (primary >= N_CONFIG_OPTIONS) - return false; - - entry = snew(struct conf_entry); - entry->key.primary = primary; - - switch (subkeytypes[entry->key.primary]) { - case TYPE_INT: - entry->key.secondary.i = toint(get_uint32(src)); - break; - case TYPE_STR: - entry->key.secondary.s = dupstr(get_asciz(src)); - break; - } - - switch (valuetypes[entry->key.primary]) { - case TYPE_BOOL: - entry->value.u.boolval = get_bool(src); - break; - case TYPE_INT: - entry->value.u.intval = toint(get_uint32(src)); - break; - case TYPE_STR: - entry->value.u.stringval = dupstr(get_asciz(src)); - break; - case TYPE_FILENAME: - entry->value.u.fileval = filename_deserialise(src); - break; - case TYPE_FONT: - entry->value.u.fontval = fontspec_deserialise(src); - break; - } - - if (get_err(src)) { - free_entry(entry); - return false; - } - - conf_insert(conf, entry); - } -} diff --git a/marshal.c b/marshal.c deleted file mode 100644 index ff9bb851..00000000 --- a/marshal.c +++ /dev/null @@ -1,318 +0,0 @@ -#include -#include -#include - -#include "marshal.h" -#include "misc.h" - -void BinarySink_put_data(BinarySink *bs, const void *data, size_t len) -{ - bs->write(bs, data, len); -} - -void BinarySink_put_datapl(BinarySink *bs, ptrlen pl) -{ - BinarySink_put_data(bs, pl.ptr, pl.len); -} - -void BinarySink_put_padding(BinarySink *bs, size_t len, unsigned char padbyte) -{ - char buf[16]; - memset(buf, padbyte, sizeof(buf)); - while (len > 0) { - size_t thislen = len < sizeof(buf) ? len : sizeof(buf); - bs->write(bs, buf, thislen); - len -= thislen; - } -} - -void BinarySink_put_byte(BinarySink *bs, unsigned char val) -{ - bs->write(bs, &val, 1); -} - -void BinarySink_put_bool(BinarySink *bs, bool val) -{ - unsigned char cval = val ? 1 : 0; - bs->write(bs, &cval, 1); -} - -void BinarySink_put_uint16(BinarySink *bs, unsigned long val) -{ - unsigned char data[2]; - PUT_16BIT_MSB_FIRST(data, val); - bs->write(bs, data, sizeof(data)); -} - -void BinarySink_put_uint32(BinarySink *bs, unsigned long val) -{ - unsigned char data[4]; - PUT_32BIT_MSB_FIRST(data, val); - bs->write(bs, data, sizeof(data)); -} - -void BinarySink_put_uint64(BinarySink *bs, uint64_t val) -{ - unsigned char data[8]; - PUT_64BIT_MSB_FIRST(data, val); - bs->write(bs, data, sizeof(data)); -} - -void BinarySink_put_string(BinarySink *bs, const void *data, size_t len) -{ - /* Check that the string length fits in a uint32, without doing a - * potentially implementation-defined shift of more than 31 bits */ - assert((len >> 31) < 2); - - BinarySink_put_uint32(bs, len); - bs->write(bs, data, len); -} - -void BinarySink_put_stringpl(BinarySink *bs, ptrlen pl) -{ - BinarySink_put_string(bs, pl.ptr, pl.len); -} - -void BinarySink_put_stringz(BinarySink *bs, const char *str) -{ - BinarySink_put_string(bs, str, strlen(str)); -} - -void BinarySink_put_stringsb(BinarySink *bs, struct strbuf *buf) -{ - BinarySink_put_string(bs, buf->s, buf->len); - strbuf_free(buf); -} - -void BinarySink_put_asciz(BinarySink *bs, const char *str) -{ - bs->write(bs, str, strlen(str) + 1); -} - -bool BinarySink_put_pstring(BinarySink *bs, const char *str) -{ - size_t len = strlen(str); - if (len > 255) - return false; /* can't write a Pascal-style string this long */ - BinarySink_put_byte(bs, len); - bs->write(bs, str, len); - return true; -} - -/* ---------------------------------------------------------------------- */ - -static bool BinarySource_data_avail(BinarySource *src, size_t wanted) -{ - if (src->err) - return false; - - if (wanted <= src->len - src->pos) - return true; - - src->err = BSE_OUT_OF_DATA; - return false; -} - -#define avail(wanted) BinarySource_data_avail(src, wanted) -#define advance(dist) (src->pos += dist) -#define here ((const void *)((const unsigned char *)src->data + src->pos)) -#define consume(dist) \ - ((const void *)((const unsigned char *)src->data + \ - ((src->pos += dist) - dist))) - -ptrlen BinarySource_get_data(BinarySource *src, size_t wanted) -{ - if (!avail(wanted)) - return make_ptrlen("", 0); - - return make_ptrlen(consume(wanted), wanted); -} - -unsigned char BinarySource_get_byte(BinarySource *src) -{ - const unsigned char *ucp; - - if (!avail(1)) - return 0; - - ucp = consume(1); - return *ucp; -} - -bool BinarySource_get_bool(BinarySource *src) -{ - const unsigned char *ucp; - - if (!avail(1)) - return false; - - ucp = consume(1); - return *ucp != 0; -} - -unsigned BinarySource_get_uint16(BinarySource *src) -{ - const unsigned char *ucp; - - if (!avail(2)) - return 0; - - ucp = consume(2); - return GET_16BIT_MSB_FIRST(ucp); -} - -unsigned long BinarySource_get_uint32(BinarySource *src) -{ - const unsigned char *ucp; - - if (!avail(4)) - return 0; - - ucp = consume(4); - return GET_32BIT_MSB_FIRST(ucp); -} - -uint64_t BinarySource_get_uint64(BinarySource *src) -{ - const unsigned char *ucp; - - if (!avail(8)) - return 0; - - ucp = consume(8); - return GET_64BIT_MSB_FIRST(ucp); -} - -ptrlen BinarySource_get_string(BinarySource *src) -{ - const unsigned char *ucp; - size_t len; - - if (!avail(4)) - return make_ptrlen("", 0); - - ucp = consume(4); - len = GET_32BIT_MSB_FIRST(ucp); - - if (!avail(len)) - return make_ptrlen("", 0); - - return make_ptrlen(consume(len), len); -} - -const char *BinarySource_get_asciz(BinarySource *src) -{ - const char *start, *end; - - if (src->err) - return ""; - - start = here; - end = memchr(start, '\0', src->len - src->pos); - if (!end) { - src->err = BSE_OUT_OF_DATA; - return ""; - } - - advance(end + 1 - start); - return start; -} - -static ptrlen BinarySource_get_chars_internal( - BinarySource *src, const char *set, bool include) -{ - const char *start = here; - while (avail(1)) { - bool present = NULL != strchr(set, *(const char *)consume(0)); - if (present != include) - break; - (void) consume(1); - } - const char *end = here; - return make_ptrlen(start, end - start); -} - -ptrlen BinarySource_get_chars(BinarySource *src, const char *include_set) -{ - return BinarySource_get_chars_internal(src, include_set, true); -} - -ptrlen BinarySource_get_nonchars(BinarySource *src, const char *exclude_set) -{ - return BinarySource_get_chars_internal(src, exclude_set, false); -} - -ptrlen BinarySource_get_chomped_line(BinarySource *src) -{ - const char *start, *end; - - if (src->err) - return make_ptrlen(here, 0); - - start = here; - end = memchr(start, '\n', src->len - src->pos); - if (end) - advance(end + 1 - start); - else - advance(src->len - src->pos); - end = here; - - if (end > start && end[-1] == '\n') - end--; - if (end > start && end[-1] == '\r') - end--; - - return make_ptrlen(start, end - start); -} - -ptrlen BinarySource_get_pstring(BinarySource *src) -{ - const unsigned char *ucp; - size_t len; - - if (!avail(1)) - return make_ptrlen("", 0); - - ucp = consume(1); - len = *ucp; - - if (!avail(len)) - return make_ptrlen("", 0); - - return make_ptrlen(consume(len), len); -} - -void BinarySource_REWIND_TO__(BinarySource *src, size_t pos) -{ - if (pos <= src->len) { - src->pos = pos; - src->err = BSE_NO_ERROR; /* clear any existing error */ - } else { - src->pos = src->len; - src->err = BSE_OUT_OF_DATA; /* new error if we rewind out of range */ - } -} - -static void stdio_sink_write(BinarySink *bs, const void *data, size_t len) -{ - stdio_sink *sink = BinarySink_DOWNCAST(bs, stdio_sink); - fwrite(data, 1, len, sink->fp); -} - -void stdio_sink_init(stdio_sink *sink, FILE *fp) -{ - sink->fp = fp; - BinarySink_INIT(sink, stdio_sink_write); -} - -static void bufchain_sink_write(BinarySink *bs, const void *data, size_t len) -{ - bufchain_sink *sink = BinarySink_DOWNCAST(bs, bufchain_sink); - bufchain_add(sink->ch, data, len); -} - -void bufchain_sink_init(bufchain_sink *sink, bufchain *ch) -{ - sink->ch = ch; - BinarySink_INIT(sink, bufchain_sink_write); -} diff --git a/memory.c b/memory.c deleted file mode 100644 index 97ae9401..00000000 --- a/memory.c +++ /dev/null @@ -1,134 +0,0 @@ -/* - * PuTTY's memory allocation wrappers. - */ - -#include -#include -#include - -#include "defs.h" -#include "puttymem.h" -#include "misc.h" - -void *safemalloc(size_t factor1, size_t factor2, size_t addend) -{ - if (factor1 > SIZE_MAX / factor2) - goto fail; - size_t product = factor1 * factor2; - - if (addend > SIZE_MAX) - goto fail; - if (product > SIZE_MAX - addend) - goto fail; - size_t size = product + addend; - - if (size == 0) - size = 1; - - void *p; -#ifdef MINEFIELD - p = minefield_c_malloc(size); -#else - p = malloc(size); -#endif - - if (!p) - goto fail; - - return p; - - fail: - out_of_memory(); -} - -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) - out_of_memory(); - - return p; -} - -void safefree(void *ptr) -{ - if (ptr) { -#ifdef MINEFIELD - minefield_c_free(ptr); -#else - free(ptr); -#endif - } -} - -void *safegrowarray(void *ptr, size_t *allocated, size_t eltsize, - size_t oldlen, size_t extralen, bool secret) -{ - /* The largest value we can safely multiply by eltsize */ - assert(eltsize > 0); - size_t maxsize = (~(size_t)0) / eltsize; - - size_t oldsize = *allocated; - - /* Range-check the input values */ - assert(oldsize <= maxsize); - assert(oldlen <= maxsize); - assert(extralen <= maxsize - oldlen); - - /* If the size is already enough, don't bother doing anything! */ - if (oldsize > oldlen + extralen) - return ptr; - - /* Find out how much we need to grow the array by. */ - size_t increment = (oldlen + extralen) - oldsize; - - /* Invent a new size. We want to grow the array by at least - * 'increment' elements; by at least a fixed number of bytes (to - * get things started when sizes are small); and by some constant - * factor of its old size (to avoid repeated calls to this - * function taking quadratic time overall). */ - if (increment < 256 / eltsize) - increment = 256 / eltsize; - if (increment < oldsize / 16) - increment = oldsize / 16; - - /* But we also can't grow beyond maxsize. */ - size_t maxincr = maxsize - oldsize; - if (increment > maxincr) - increment = maxincr; - - size_t newsize = oldsize + increment; - void *toret; - if (secret) { - toret = safemalloc(newsize, eltsize, 0); - if (oldsize) { - memcpy(toret, ptr, oldsize * eltsize); - smemclr(ptr, oldsize * eltsize); - sfree(ptr); - } - } else { - toret = saferealloc(ptr, newsize, eltsize); - } - *allocated = newsize; - return toret; -} diff --git a/misc.c b/misc.c deleted file mode 100644 index f1bb176f..00000000 --- a/misc.c +++ /dev/null @@ -1,419 +0,0 @@ -/* - * Platform-independent routines shared between all PuTTY programs. - * - * This file contains functions that use the kind of infrastructure - * like conf.c that tends to only live in the main applications, or - * that do things that only something like a main PuTTY application - * would need. So standalone test programs should generally be able to - * avoid linking against it. - * - * More standalone functions that depend on nothing but the C library - * live in utils.c. - */ - -#include -#include -#include -#include -#include -#include - -#include "defs.h" -#include "putty.h" -#include "misc.h" - -#define BASE64_CHARS_NOEQ \ - "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+/" -#define BASE64_CHARS_ALL BASE64_CHARS_NOEQ "=" - -void seat_connection_fatal(Seat *seat, const char *fmt, ...) -{ - va_list ap; - char *msg; - - va_start(ap, fmt); - msg = dupvprintf(fmt, ap); - va_end(ap); - - seat->vt->connection_fatal(seat, msg); - sfree(msg); /* if we return */ -} - -prompts_t *new_prompts(void) -{ - prompts_t *p = snew(prompts_t); - p->prompts = NULL; - p->n_prompts = p->prompts_size = 0; - 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, bool echo) -{ - prompt_t *pr = snew(prompt_t); - pr->prompt = promptstr; - pr->echo = echo; - pr->result = strbuf_new_nm(); - sgrowarray(p->prompts, p->prompts_size, p->n_prompts); - p->prompts[p->n_prompts++] = pr; -} -void prompt_set_result(prompt_t *pr, const char *newstr) -{ - strbuf_clear(pr->result); - put_datapl(pr->result, ptrlen_from_asciz(newstr)); -} -const char *prompt_get_result_ref(prompt_t *pr) -{ - return pr->result->s; -} -char *prompt_get_result(prompt_t *pr) -{ - return dupstr(pr->result->s); -} -void free_prompts(prompts_t *p) -{ - size_t i; - for (i=0; i < p->n_prompts; i++) { - prompt_t *pr = p->prompts[i]; - strbuf_free(pr->result); - sfree(pr->prompt); - sfree(pr); - } - sfree(p->prompts); - sfree(p->name); - sfree(p->instruction); - sfree(p); -} - -/* - * Determine whether or not a Conf represents a session which can - * sensibly be launched right now. - */ -bool conf_launchable(Conf *conf) -{ - if (conf_get_int(conf, CONF_protocol) == PROT_SERIAL) - return conf_get_str(conf, CONF_serline)[0] != 0; - else - return conf_get_str(conf, CONF_host)[0] != 0; -} - -char const *conf_dest(Conf *conf) -{ - if (conf_get_int(conf, CONF_protocol) == PROT_SERIAL) - return conf_get_str(conf, CONF_serline); - else - return conf_get_str(conf, CONF_host); -} - -/* - * Validate a manual host key specification (either entered in the - * GUI, or via -hostkey). If valid, we return true, and update 'key' - * to contain a canonicalised version of the key string in 'key' - * (which is guaranteed to take up at most as much space as the - * original version), suitable for putting into the Conf. If not - * valid, we return false. - */ -bool validate_manual_hostkey(char *key) -{ - char *p, *q, *r, *s; - - /* - * Step through the string word by word, looking for a word that's - * in one of the formats we like. - */ - p = key; - while ((p += strspn(p, " \t"))[0]) { - q = p; - p += strcspn(p, " \t"); - if (*p) *p++ = '\0'; - - /* - * Now q is our word. - */ - - if (strstartswith(q, "SHA256:")) { - /* Test for a valid SHA256 key fingerprint. */ - r = q + 7; - if (strlen(r) == 43 && r[strspn(r, BASE64_CHARS_NOEQ)] == 0) - return true; - } - - r = q; - if (strstartswith(r, "MD5:")) - r += 4; - if (strlen(r) == 16*3 - 1 && - r[strspn(r, "0123456789abcdefABCDEF:")] == 0) { - /* - * Test for a valid MD5 key fingerprint. Check the colons - * are in the right places, and if so, return the same - * fingerprint canonicalised into lowercase. - */ - int i; - for (i = 0; i < 16; i++) - if (r[3*i] == ':' || r[3*i+1] == ':') - goto not_fingerprint; /* sorry */ - for (i = 0; i < 15; i++) - if (r[3*i+2] != ':') - goto not_fingerprint; /* sorry */ - for (i = 0; i < 16*3 - 1; i++) - key[i] = tolower(r[i]); - key[16*3 - 1] = '\0'; - return true; - } - not_fingerprint:; - - /* - * Before we check for a public-key blob, trim newlines out of - * the middle of the word, in case someone's managed to paste - * in a public-key blob _with_ them. - */ - for (r = s = q; *r; r++) - if (*r != '\n' && *r != '\r') - *s++ = *r; - *s = '\0'; - - if (strlen(q) % 4 == 0 && strlen(q) > 2*4 && - q[strspn(q, BASE64_CHARS_ALL)] == 0) { - /* - * Might be a base64-encoded SSH-2 public key blob. Check - * that it starts with a sensible algorithm string. No - * canonicalisation is necessary for this string type. - * - * The algorithm string must be at most 64 characters long - * (RFC 4251 section 6). - */ - unsigned char decoded[6]; - unsigned alglen; - int minlen; - int len = 0; - - len += base64_decode_atom(q, decoded+len); - if (len < 3) - goto not_ssh2_blob; /* sorry */ - len += base64_decode_atom(q+4, decoded+len); - if (len < 4) - goto not_ssh2_blob; /* sorry */ - - alglen = GET_32BIT_MSB_FIRST(decoded); - if (alglen > 64) - goto not_ssh2_blob; /* sorry */ - - minlen = ((alglen + 4) + 2) / 3; - if (strlen(q) < minlen) - goto not_ssh2_blob; /* sorry */ - - strcpy(key, q); - return true; - } - not_ssh2_blob:; - } - - return false; -} - -char *buildinfo(const char *newline) -{ - strbuf *buf = strbuf_new(); - - strbuf_catf(buf, "Build platform: %d-bit %s", - (int)(CHAR_BIT * sizeof(void *)), - BUILDINFO_PLATFORM); - -#ifdef __clang_version__ -#define FOUND_COMPILER - strbuf_catf(buf, "%sCompiler: clang %s", newline, __clang_version__); -#elif defined __GNUC__ && defined __VERSION__ -#define FOUND_COMPILER - strbuf_catf(buf, "%sCompiler: gcc %s", newline, __VERSION__); -#endif - -#if defined _MSC_VER -#ifndef FOUND_COMPILER -#define FOUND_COMPILER - strbuf_catf(buf, "%sCompiler: ", newline); -#else - strbuf_catf(buf, ", emulating "); -#endif - strbuf_catf(buf, "Visual Studio"); - -#if 0 - /* - * List of _MSC_VER values and their translations taken from - * https://docs.microsoft.com/en-us/cpp/preprocessor/predefined-macros - * - * The pointless #if 0 branch containing this comment is there so - * that every real clause can start with #elif and there's no - * anomalous first clause. That way the patch looks nicer when you - * add extra ones. - */ -#elif _MSC_VER == 1928 && _MSC_FULL_VER >= 192829500 - /* - * 16.9 and 16.8 have the same _MSC_VER value, and have to be - * distinguished by _MSC_FULL_VER. As of 2021-03-04 that is not - * mentioned on the above page, but see e.g. - * https://developercommunity.visualstudio.com/t/the-169-cc-compiler-still-uses-the-same-version-nu/1335194#T-N1337120 - * which says that 16.9 builds will have versions starting at - * 19.28.29500.* and going up. Hence, 19 28 29500 is what we - * compare _MSC_FULL_VER against above. - */ - strbuf_catf(buf, " 2019 (16.9)"); -#elif _MSC_VER == 1928 - strbuf_catf(buf, " 2019 (16.8)"); -#elif _MSC_VER == 1927 - strbuf_catf(buf, " 2019 (16.7)"); -#elif _MSC_VER == 1926 - strbuf_catf(buf, " 2019 (16.6)"); -#elif _MSC_VER == 1925 - strbuf_catf(buf, " 2019 (16.5)"); -#elif _MSC_VER == 1924 - strbuf_catf(buf, " 2019 (16.4)"); -#elif _MSC_VER == 1923 - strbuf_catf(buf, " 2019 (16.3)"); -#elif _MSC_VER == 1922 - strbuf_catf(buf, " 2019 (16.2)"); -#elif _MSC_VER == 1921 - strbuf_catf(buf, " 2019 (16.1)"); -#elif _MSC_VER == 1920 - strbuf_catf(buf, " 2019 (16.0)"); -#elif _MSC_VER == 1916 - strbuf_catf(buf, " 2017 version 15.9"); -#elif _MSC_VER == 1915 - strbuf_catf(buf, " 2017 version 15.8"); -#elif _MSC_VER == 1914 - strbuf_catf(buf, " 2017 version 15.7"); -#elif _MSC_VER == 1913 - strbuf_catf(buf, " 2017 version 15.6"); -#elif _MSC_VER == 1912 - strbuf_catf(buf, " 2017 version 15.5"); -#elif _MSC_VER == 1911 - strbuf_catf(buf, " 2017 version 15.3"); -#elif _MSC_VER == 1910 - strbuf_catf(buf, " 2017 RTW (15.0)"); -#elif _MSC_VER == 1900 - strbuf_catf(buf, " 2015 (14.0)"); -#elif _MSC_VER == 1800 - strbuf_catf(buf, " 2013 (12.0)"); -#elif _MSC_VER == 1700 - strbuf_catf(buf, " 2012 (11.0)"); -#elif _MSC_VER == 1600 - strbuf_catf(buf, " 2010 (10.0)"); -#elif _MSC_VER == 1500 - strbuf_catf(buf, " 2008 (9.0)"); -#elif _MSC_VER == 1400 - strbuf_catf(buf, " 2005 (8.0)"); -#elif _MSC_VER == 1310 - strbuf_catf(buf, " .NET 2003 (7.1)"); -#elif _MSC_VER == 1300 - strbuf_catf(buf, " .NET 2002 (7.0)"); -#elif _MSC_VER == 1200 - strbuf_catf(buf, " 6.0"); -#else - strbuf_catf(buf, ", unrecognised version"); -#endif - strbuf_catf(buf, ", _MSC_VER=%d", (int)_MSC_VER); -#endif - -#ifdef BUILDINFO_GTK - { - char *gtk_buildinfo = buildinfo_gtk_version(); - if (gtk_buildinfo) { - strbuf_catf(buf, "%sCompiled against GTK version %s", - newline, gtk_buildinfo); - sfree(gtk_buildinfo); - } - } -#endif -#if defined _WINDOWS - { - int echm = has_embedded_chm(); - if (echm >= 0) - strbuf_catf(buf, "%sEmbedded HTML Help file: %s", newline, - echm ? "yes" : "no"); - } -#endif - -#if defined _WINDOWS && defined MINEFIELD - strbuf_catf(buf, "%sBuild option: MINEFIELD", newline); -#endif -#ifdef NO_SECUREZEROMEMORY - strbuf_catf(buf, "%sBuild option: NO_SECUREZEROMEMORY", newline); -#endif -#ifdef NO_IPV6 - strbuf_catf(buf, "%sBuild option: NO_IPV6", newline); -#endif -#ifdef NO_GSSAPI - strbuf_catf(buf, "%sBuild option: NO_GSSAPI", newline); -#endif -#ifdef STATIC_GSSAPI - strbuf_catf(buf, "%sBuild option: STATIC_GSSAPI", newline); -#endif -#ifdef UNPROTECT - strbuf_catf(buf, "%sBuild option: UNPROTECT", newline); -#endif -#ifdef FUZZING - strbuf_catf(buf, "%sBuild option: FUZZING", newline); -#endif -#ifdef DEBUG - strbuf_catf(buf, "%sBuild option: DEBUG", newline); -#endif - - strbuf_catf(buf, "%sSource commit: %s", newline, commitid); - - return strbuf_to_str(buf); -} - -size_t nullseat_output( - Seat *seat, bool is_stderr, const void *data, size_t len) { return 0; } -bool nullseat_eof(Seat *seat) { return true; } -int nullseat_get_userpass_input( - Seat *seat, prompts_t *p, bufchain *input) { return 0; } -void nullseat_notify_remote_exit(Seat *seat) {} -void nullseat_connection_fatal(Seat *seat, const char *message) {} -void nullseat_update_specials_menu(Seat *seat) {} -char *nullseat_get_ttymode(Seat *seat, const char *mode) { return NULL; } -void nullseat_set_busy_status(Seat *seat, BusyStatus status) {} -int nullseat_verify_ssh_host_key( - Seat *seat, const char *host, int port, const char *keytype, - char *keystr, const char *keydisp, char **key_fingerprints, - void (*callback)(void *ctx, int result), void *ctx) { return 0; } -int nullseat_confirm_weak_crypto_primitive( - Seat *seat, const char *algtype, const char *algname, - void (*callback)(void *ctx, int result), void *ctx) { return 0; } -int nullseat_confirm_weak_cached_hostkey( - Seat *seat, const char *algname, const char *betteralgs, - void (*callback)(void *ctx, int result), void *ctx) { return 0; } -bool nullseat_is_never_utf8(Seat *seat) { return false; } -bool nullseat_is_always_utf8(Seat *seat) { return true; } -void nullseat_echoedit_update(Seat *seat, bool echoing, bool editing) {} -const char *nullseat_get_x_display(Seat *seat) { return NULL; } -bool nullseat_get_windowid(Seat *seat, long *id_out) { return false; } -bool nullseat_get_window_pixel_size( - Seat *seat, int *width, int *height) { return false; } -StripCtrlChars *nullseat_stripctrl_new( - Seat *seat, BinarySink *bs_out, SeatInteractionContext sic) {return NULL;} -bool nullseat_set_trust_status(Seat *seat, bool tr) { return false; } -bool nullseat_set_trust_status_vacuously(Seat *seat, bool tr) { return true; } -bool nullseat_verbose_no(Seat *seat) { return false; } -bool nullseat_verbose_yes(Seat *seat) { return true; } -bool nullseat_interactive_no(Seat *seat) { return false; } -bool nullseat_interactive_yes(Seat *seat) { return true; } -bool nullseat_get_cursor_position(Seat *seat, int *x, int *y) { return false; } - -bool null_lp_verbose_no(LogPolicy *lp) { return false; } -bool null_lp_verbose_yes(LogPolicy *lp) { return true; } - -void sk_free_peer_info(SocketPeerInfo *pi) -{ - if (pi) { - sfree((char *)pi->addr_text); - sfree((char *)pi->log_text); - sfree(pi); - } -} - -void out_of_memory(void) -{ - modalfatalbox("Out of memory"); -} diff --git a/miscucs.c b/miscucs.c deleted file mode 100644 index 7785f9b6..00000000 --- a/miscucs.c +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Centralised Unicode-related helper functions, separate from misc.c - * so that they can be omitted from tools that aren't including - * Unicode handling. - */ - -#include "putty.h" -#include "misc.h" - -wchar_t *dup_mb_to_wc_c(int codepage, int flags, const char *string, int len) -{ - int mult; - for (mult = 1 ;; mult++) { - wchar_t *ret = snewn(mult*len + 2, wchar_t); - int outlen; - outlen = mb_to_wc(codepage, flags, string, len, ret, mult*len + 1); - if (outlen < mult*len+1) { - ret[outlen] = L'\0'; - return ret; - } - sfree(ret); - } -} - -wchar_t *dup_mb_to_wc(int codepage, int flags, const char *string) -{ - return dup_mb_to_wc_c(codepage, flags, string, strlen(string)); -} diff --git a/sessprep.c b/sessprep.c deleted file mode 100644 index 95e7b658..00000000 --- a/sessprep.c +++ /dev/null @@ -1,84 +0,0 @@ -/* - * sessprep.c: centralise some preprocessing done on Conf objects - * before launching them. - */ - -#include "putty.h" - -void prepare_session(Conf *conf) -{ - char *hostbuf = dupstr(conf_get_str(conf, CONF_host)); - char *host = hostbuf; - char *p, *q; - - /* - * Trim leading whitespace from the hostname. - */ - host += strspn(host, " \t"); - - /* - * See if host is of the form user@host, and separate out the - * username if so. - */ - if (host[0] != '\0') { - /* - * Use strrchr, in case the _username_ in turn is of the form - * user@host, which has been known. - */ - char *atsign = strrchr(host, '@'); - if (atsign) { - *atsign = '\0'; - conf_set_str(conf, CONF_username, host); - host = atsign + 1; - } - } - - /* - * Trim a colon suffix off the hostname if it's there, and discard - * the text after it. - * - * The exact reason why we _ignore_ this text, rather than - * treating it as a port number, is unfortunately lost in the - * mists of history: the commit which originally introduced this - * change on 2001-05-06 was clear on _what_ it was doing but - * didn't bother to explain _why_. But I [SGT, 2017-12-03] suspect - * it has to do with priority order: what should a saved session - * do if its CONF_host contains 'server.example.com:123' and its - * CONF_port contains 456? If CONF_port contained the _default_ - * port number then it might be a good guess that the colon suffix - * on the host name was intended to override that, but you don't - * really want to get into making heuristic judgments on that - * basis. - * - * (Then again, you could just as easily make the same argument - * about whether a 'user@' prefix on the host name should override - * CONF_username, which this code _does_ do. I don't have a good - * answer, sadly. Both these pieces of behaviour have been around - * for years and it would probably cause subtle breakage in all - * sorts of long-forgotten scripting to go changing things around - * now.) - * - * In order to protect unbracketed IPv6 address literals against - * this treatment, we do not make this change at all if there's - * _more_ than one (un-IPv6-bracketed) colon. - */ - p = host_strchr(host, ':'); - if (p && p == host_strrchr(host, ':')) { - *p = '\0'; - } - - /* - * Remove any remaining whitespace. - */ - p = hostbuf; - q = host; - while (*q) { - if (*q != ' ' && *q != '\t') - *p++ = *q; - q++; - } - *p = '\0'; - - conf_set_str(conf, CONF_host, hostbuf); - sfree(hostbuf); -} diff --git a/sshutils.c b/sshutils.c deleted file mode 100644 index 1ee3342b..00000000 --- a/sshutils.c +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Supporting routines used in common by all the various components of - * the SSH system. - */ - -#include -#include - -#include "putty.h" -#include "ssh.h" -#include "sshchan.h" - -/* ---------------------------------------------------------------------- - * Centralised standard methods for other channel implementations to - * borrow. - */ - -void chan_remotely_opened_confirmation(Channel *chan) -{ - unreachable("this channel type should never receive OPEN_CONFIRMATION"); -} - -void chan_remotely_opened_failure(Channel *chan, const char *errtext) -{ - unreachable("this channel type should never receive OPEN_FAILURE"); -} - -bool chan_default_want_close( - Channel *chan, bool sent_local_eof, bool rcvd_remote_eof) -{ - /* - * Default close policy: we start initiating the CHANNEL_CLOSE - * procedure as soon as both sides of the channel have seen EOF. - */ - return sent_local_eof && rcvd_remote_eof; -} - -bool chan_no_exit_status(Channel *chan, int status) -{ - return false; -} - -bool chan_no_exit_signal( - Channel *chan, ptrlen signame, bool core_dumped, ptrlen msg) -{ - return false; -} - -bool chan_no_exit_signal_numeric( - Channel *chan, int signum, bool core_dumped, ptrlen msg) -{ - return false; -} - -bool chan_no_run_shell(Channel *chan) -{ - return false; -} - -bool chan_no_run_command(Channel *chan, ptrlen command) -{ - return false; -} - -bool chan_no_run_subsystem(Channel *chan, ptrlen subsys) -{ - return false; -} - -bool chan_no_enable_x11_forwarding( - Channel *chan, bool oneshot, ptrlen authproto, ptrlen authdata, - unsigned screen_number) -{ - return false; -} - -bool chan_no_enable_agent_forwarding(Channel *chan) -{ - return false; -} - -bool chan_no_allocate_pty( - Channel *chan, ptrlen termtype, unsigned width, unsigned height, - unsigned pixwidth, unsigned pixheight, struct ssh_ttymodes modes) -{ - return false; -} - -bool chan_no_set_env(Channel *chan, ptrlen var, ptrlen value) -{ - return false; -} - -bool chan_no_send_break(Channel *chan, unsigned length) -{ - return false; -} - -bool chan_no_send_signal(Channel *chan, ptrlen signame) -{ - return false; -} - -bool chan_no_change_window_size( - Channel *chan, unsigned width, unsigned height, - unsigned pixwidth, unsigned pixheight) -{ - return false; -} - -void chan_no_request_response(Channel *chan, bool success) -{ - unreachable("this channel type should never send a want-reply request"); -} - -/* ---------------------------------------------------------------------- - * Other miscellaneous utility functions. - */ - -void free_rportfwd(struct ssh_rportfwd *rpf) -{ - if (rpf) { - sfree(rpf->log_description); - sfree(rpf->shost); - sfree(rpf->dhost); - sfree(rpf); - } -} diff --git a/stripctrl.c b/stripctrl.c deleted file mode 100644 index 58289b10..00000000 --- a/stripctrl.c +++ /dev/null @@ -1,476 +0,0 @@ -/* - * stripctrl.c: a facility for stripping control characters out of a - * data stream (defined as any multibyte character in the system - * locale which is neither printable nor \n), using the standard C - * library multibyte character facilities. - */ - -#include -#include -#include -#include -#include - -#include "putty.h" -#include "terminal.h" -#include "misc.h" -#include "marshal.h" - -#define SCC_BUFSIZE 64 -#define LINE_LIMIT 77 - -typedef struct StripCtrlCharsImpl StripCtrlCharsImpl; -struct StripCtrlCharsImpl { - mbstate_t mbs_in, mbs_out; - - bool permit_cr; - wchar_t substitution; - - char buf[SCC_BUFSIZE]; - size_t buflen; - - Terminal *term; - bool last_term_utf; - struct term_utf8_decode utf8; - unsigned long (*translate)(Terminal *, term_utf8_decode *, unsigned char); - - bool line_limit; - bool line_start; - size_t line_chars_remaining; - - BinarySink *bs_out; - - StripCtrlChars public; -}; - -static void stripctrl_locale_BinarySink_write( - BinarySink *bs, const void *vp, size_t len); -static void stripctrl_term_BinarySink_write( - BinarySink *bs, const void *vp, size_t len); - -static StripCtrlCharsImpl *stripctrl_new_common( - BinarySink *bs_out, bool permit_cr, wchar_t substitution) -{ - StripCtrlCharsImpl *scc = snew(StripCtrlCharsImpl); - memset(scc, 0, sizeof(StripCtrlCharsImpl)); /* zeroes mbstates */ - scc->bs_out = bs_out; - scc->permit_cr = permit_cr; - scc->substitution = substitution; - return scc; -} - -StripCtrlChars *stripctrl_new( - BinarySink *bs_out, bool permit_cr, wchar_t substitution) -{ - StripCtrlCharsImpl *scc = stripctrl_new_common( - bs_out, permit_cr, substitution); - BinarySink_INIT(&scc->public, stripctrl_locale_BinarySink_write); - return &scc->public; -} - -StripCtrlChars *stripctrl_new_term_fn( - BinarySink *bs_out, bool permit_cr, wchar_t substitution, - Terminal *term, unsigned long (*translate)( - Terminal *, term_utf8_decode *, unsigned char)) -{ - StripCtrlCharsImpl *scc = stripctrl_new_common( - bs_out, permit_cr, substitution); - scc->term = term; - scc->translate = translate; - BinarySink_INIT(&scc->public, stripctrl_term_BinarySink_write); - return &scc->public; -} - -void stripctrl_retarget(StripCtrlChars *sccpub, BinarySink *new_bs_out) -{ - StripCtrlCharsImpl *scc = - container_of(sccpub, StripCtrlCharsImpl, public); - scc->bs_out = new_bs_out; - stripctrl_reset(sccpub); -} - -void stripctrl_reset(StripCtrlChars *sccpub) -{ - StripCtrlCharsImpl *scc = - container_of(sccpub, StripCtrlCharsImpl, public); - - /* - * Clear all the fields that might have been in the middle of a - * multibyte character or non-default shift state, so that we can - * start converting a fresh piece of data to send to a channel - * that hasn't seen the previous output. - */ - memset(&scc->utf8, 0, sizeof(scc->utf8)); - memset(&scc->mbs_in, 0, sizeof(scc->mbs_in)); - memset(&scc->mbs_out, 0, sizeof(scc->mbs_out)); - - /* - * Also, reset the line-limiting system to its starting state. - */ - scc->line_start = true; -} - -void stripctrl_free(StripCtrlChars *sccpub) -{ - StripCtrlCharsImpl *scc = - container_of(sccpub, StripCtrlCharsImpl, public); - smemclr(scc, sizeof(StripCtrlCharsImpl)); - sfree(scc); -} - -void stripctrl_enable_line_limiting(StripCtrlChars *sccpub) -{ - StripCtrlCharsImpl *scc = - container_of(sccpub, StripCtrlCharsImpl, public); - scc->line_limit = true; - scc->line_start = true; -} - -static inline bool stripctrl_ctrlchar_ok(StripCtrlCharsImpl *scc, wchar_t wc) -{ - return wc == L'\n' || (wc == L'\r' && scc->permit_cr); -} - -static inline void stripctrl_check_line_limit( - StripCtrlCharsImpl *scc, wchar_t wc, size_t width) -{ - if (!scc->line_limit) - return; /* nothing to do */ - - if (scc->line_start) { - put_datapl(scc->bs_out, PTRLEN_LITERAL("| ")); - scc->line_start = false; - scc->line_chars_remaining = LINE_LIMIT; - } - - if (wc == '\n') { - scc->line_start = true; - return; - } - - if (scc->line_chars_remaining < width) { - put_datapl(scc->bs_out, PTRLEN_LITERAL("\r\n> ")); - scc->line_chars_remaining = LINE_LIMIT; - } - - assert(width <= scc->line_chars_remaining); - scc->line_chars_remaining -= width; -} - -static inline void stripctrl_locale_put_wc(StripCtrlCharsImpl *scc, wchar_t wc) -{ - int width = mk_wcwidth(wc); - if ((iswprint(wc) && width >= 0) || stripctrl_ctrlchar_ok(scc, wc)) { - /* Printable character, or one we're going to let through anyway. */ - if (width < 0) - width = 0; /* sanitise for stripctrl_check_line_limit */ - } else if (scc->substitution) { - wc = scc->substitution; - width = mk_wcwidth(wc); - assert(width >= 0); - } else { - /* No defined substitution, so don't write any output wchar_t. */ - return; - } - - stripctrl_check_line_limit(scc, wc, width); - - char outbuf[MB_LEN_MAX]; - size_t produced = wcrtomb(outbuf, wc, &scc->mbs_out); - if (produced > 0) - put_data(scc->bs_out, outbuf, produced); -} - -static inline void stripctrl_term_put_wc( - StripCtrlCharsImpl *scc, unsigned long wc) -{ - ptrlen prefix = PTRLEN_LITERAL(""); - int width = term_char_width(scc->term, wc); - - if (!(wc & ~0x9F) || width < 0) { - /* This is something the terminal interprets as a control - * character. */ - if (!stripctrl_ctrlchar_ok(scc, wc)) { - if (!scc->substitution) { - return; - } else { - wc = scc->substitution; - width = term_char_width(scc->term, wc); - assert(width >= 0); - } - } else { - if (width < 0) - width = 0; /* sanitise for stripctrl_check_line_limit */ - } - - if (wc == '\012') { - /* Precede \n with \r, because our terminal will not - * generally be in the ONLCR mode where it assumes that - * internally, and any \r on input has been stripped - * out. */ - prefix = PTRLEN_LITERAL("\r"); - } - } - - stripctrl_check_line_limit(scc, wc, width); - - if (prefix.len) - put_datapl(scc->bs_out, prefix); - - char outbuf[6]; - size_t produced; - - /* - * The Terminal implementation encodes 7-bit ASCII characters in - * UTF-8 mode, and all printing characters in non-UTF-8 (i.e. - * single-byte character set) mode, as values in the surrogate - * range (a conveniently unused piece of space in this context) - * whose low byte is the original 1-byte representation of the - * character. - */ - if ((wc - 0xD800) < (0xE000 - 0xD800)) - wc &= 0xFF; - - if (in_utf(scc->term)) { - produced = encode_utf8(outbuf, wc); - } else { - outbuf[0] = wc; - produced = 1; - } - - if (produced > 0) - put_data(scc->bs_out, outbuf, produced); -} - -static inline size_t stripctrl_locale_try_consume( - StripCtrlCharsImpl *scc, const char *p, size_t len) -{ - wchar_t wc; - mbstate_t mbs_orig = scc->mbs_in; - size_t consumed = mbrtowc(&wc, p, len, &scc->mbs_in); - - if (consumed == (size_t)-2) { - /* - * The buffer is too short to see the end of the multibyte - * character that it appears to be starting with. We return 0 - * for 'no data consumed', restore the conversion state from - * before consuming the partial character, and our caller will - * come back when it has more data available. - */ - scc->mbs_in = mbs_orig; - return 0; - } - - if (consumed == (size_t)-1) { - /* - * The buffer contains an illegal multibyte sequence. There's - * no really good way to recover from this, so we'll just - * reset our input state, consume a single byte without - * emitting anything, and hope we can resynchronise to - * _something_ sooner or later. - */ - memset(&scc->mbs_in, 0, sizeof(scc->mbs_in)); - return 1; - } - - if (consumed == 0) { - /* - * A zero wide character is encoded by the data, but mbrtowc - * hasn't told us how many input bytes it takes. There isn't - * really anything good we can do here, so we just advance by - * one byte in the hope that that was the NUL. - * - * (If it wasn't - that is, if we're in a multibyte encoding - * in which the terminator of a normal C string is encoded in - * some way other than a single zero byte - then probably lots - * of other things will have gone wrong before we get here!) - */ - stripctrl_locale_put_wc(scc, L'\0'); - return 1; - } - - /* - * Otherwise, this is the easy case: consumed > 0, and we've eaten - * a valid multibyte character. - */ - stripctrl_locale_put_wc(scc, wc); - return consumed; -} - -static void stripctrl_locale_BinarySink_write( - BinarySink *bs, const void *vp, size_t len) -{ - StripCtrlChars *sccpub = BinarySink_DOWNCAST(bs, StripCtrlChars); - StripCtrlCharsImpl *scc = - container_of(sccpub, StripCtrlCharsImpl, public); - const char *p = (const char *)vp; - - const char *previous_locale = setlocale(LC_CTYPE, NULL); - setlocale(LC_CTYPE, ""); - - /* - * Deal with any partial multibyte character buffered from last - * time. - */ - while (scc->buflen > 0) { - size_t to_copy = SCC_BUFSIZE - scc->buflen; - if (to_copy > len) - to_copy = len; - - memcpy(scc->buf + scc->buflen, p, to_copy); - size_t consumed = stripctrl_locale_try_consume( - scc, scc->buf, scc->buflen + to_copy); - - if (consumed >= scc->buflen) { - /* - * We've consumed a multibyte character that includes all - * the data buffered from last time. So we can clear our - * buffer and move on to processing the main input string - * in situ, having first discarded whatever initial - * segment of it completed our previous character. - */ - size_t consumed_from_main_string = consumed - scc->buflen; - assert(consumed_from_main_string <= len); - p += consumed_from_main_string; - len -= consumed_from_main_string; - scc->buflen = 0; - break; - } - - if (consumed == 0) { - /* - * If we didn't manage to consume anything, i.e. the whole - * buffer contains an incomplete sequence, it had better - * be because our entire input string _this_ time plus - * whatever leftover data we had from _last_ time still - * comes to less than SCC_BUFSIZE. In other words, we've - * already copied all the new data on to the end of our - * buffer, and it still hasn't helped. So increment buflen - * to reflect the new data, and return. - */ - assert(to_copy == len); - scc->buflen += to_copy; - goto out; - } - - /* - * Otherwise, we've somehow consumed _less_ data than we had - * buffered, and yet we weren't able to consume that data in - * the last call to this function. That sounds impossible, but - * I can think of one situation in which it could happen: if - * we had an incomplete MB sequence last time, and now more - * data has arrived, it turns out to be an _illegal_ one, so - * we consume one byte in the hope of resynchronising. - * - * Anyway, in this case we move the buffer up and go back - * round this initial loop. - */ - scc->buflen -= consumed; - memmove(scc->buf, scc->buf + consumed, scc->buflen); - } - - /* - * Now charge along the main string. - */ - while (len > 0) { - size_t consumed = stripctrl_locale_try_consume(scc, p, len); - if (consumed == 0) - break; - assert(consumed <= len); - p += consumed; - len -= consumed; - } - - /* - * Any data remaining should be copied into our buffer, to keep - * for next time. - */ - assert(len <= SCC_BUFSIZE); - memcpy(scc->buf, p, len); - scc->buflen = len; - - out: - setlocale(LC_CTYPE, previous_locale); -} - -static void stripctrl_term_BinarySink_write( - BinarySink *bs, const void *vp, size_t len) -{ - StripCtrlChars *sccpub = BinarySink_DOWNCAST(bs, StripCtrlChars); - StripCtrlCharsImpl *scc = - container_of(sccpub, StripCtrlCharsImpl, public); - - bool utf = in_utf(scc->term); - if (utf != scc->last_term_utf) { - scc->last_term_utf = utf; - scc->utf8.state = 0; - } - - for (const unsigned char *p = (const unsigned char *)vp; - len > 0; len--, p++) { - unsigned long t = scc->translate(scc->term, &scc->utf8, *p); - if (t == UCSTRUNCATED) { - stripctrl_term_put_wc(scc, 0xFFFD); - /* go round again */ - t = scc->translate(scc->term, &scc->utf8, *p); - } - if (t == UCSINCOMPLETE) - continue; - if (t == UCSINVALID) - t = 0xFFFD; - - stripctrl_term_put_wc(scc, t); - } -} - -char *stripctrl_string_ptrlen(StripCtrlChars *sccpub, ptrlen str) -{ - strbuf *out = strbuf_new(); - stripctrl_retarget(sccpub, BinarySink_UPCAST(out)); - put_datapl(sccpub, str); - stripctrl_retarget(sccpub, NULL); - return strbuf_to_str(out); -} - -#ifdef STRIPCTRL_TEST - -/* -gcc -std=c99 -DSTRIPCTRL_TEST -o scctest stripctrl.c marshal.c utils.c memory.c wcwidth.c -I . -I unix -I charset -*/ - -void out_of_memory(void) { fprintf(stderr, "out of memory\n"); abort(); } - -void stripctrl_write(BinarySink *bs, const void *vdata, size_t len) -{ - const uint8_t *p = vdata; - printf("["); - for (size_t i = 0; i < len; i++) - printf("%*s%02x", i?1:0, "", (unsigned)p[i]); - printf("]"); -} - -void stripctrl_test(StripCtrlChars *scc, ptrlen pl) -{ - stripctrl_write(NULL, pl.ptr, pl.len); - printf(" -> "); - put_datapl(scc, pl); - printf("\n"); -} - -int main(void) -{ - struct foo { BinarySink_IMPLEMENTATION; } foo; - BinarySink_INIT(&foo, stripctrl_write); - StripCtrlChars *scc = stripctrl_new(BinarySink_UPCAST(&foo), false, '?'); - stripctrl_test(scc, PTRLEN_LITERAL("a\033[1mb")); - stripctrl_test(scc, PTRLEN_LITERAL("a\xC2\x9B[1mb")); - stripctrl_test(scc, PTRLEN_LITERAL("a\xC2\xC2[1mb")); - stripctrl_test(scc, PTRLEN_LITERAL("\xC3")); - stripctrl_test(scc, PTRLEN_LITERAL("\xA9")); - stripctrl_test(scc, PTRLEN_LITERAL("\xE2\x80\x8F")); - stripctrl_test(scc, PTRLEN_LITERAL("a\0b")); - stripctrl_free(scc); - return 0; -} - -#endif /* STRIPCTRL_TEST */ diff --git a/time.c b/time.c deleted file mode 100644 index 75b89535..00000000 --- a/time.c +++ /dev/null @@ -1,16 +0,0 @@ -/* - * 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 -#include - -struct tm ltime(void) -{ - time_t t; - time(&t); - assert (t != ((time_t)-1)); - return *localtime(&t); -} diff --git a/tree234.c b/tree234.c deleted file mode 100644 index a590382b..00000000 --- a/tree234.c +++ /dev/null @@ -1,1611 +0,0 @@ -/* - * 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 -#include -#include - -#include "defs.h" -#include "tree234.h" - -#ifdef TEST -#define LOG(x) (printf x) -#define snew(type) ((type *)malloc(sizeof(type))) -#define snewn(n, type) ((type *)malloc((n) * sizeof(type))) -#define sresize(ptr, n, type) \ - ((type *)realloc(sizeof((type *)0 == (ptr)) ? (ptr) : (ptr), \ - (n) * sizeof(type))) -#define sfree(ptr) free(ptr) -#else -#include "puttymem.h" -#define LOG(x) -#endif - -typedef struct node234_Tag node234; - -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; -} - -/* - * Internal function to return the number of elements in a node. - */ -static int elements234(node234 *n) -{ - int i; - for (i = 0; i < 3; i++) - if (!n->elems[i]) - break; - return i; -} - -/* - * 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, (int)(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) -{ - search234_state ss; - int reldir = (relation == REL234_LT || relation == REL234_LE ? -1 : - relation == REL234_GT || relation == REL234_GE ? +1 : 0); - bool equal_permitted = (relation != REL234_LT && relation != REL234_GT); - void *toret; - - /* Only LT / GT relations are permitted with a null query element. */ - assert(!(equal_permitted && !e)); - - if (cmp == NULL) - cmp = t->cmp; - - search234_start(&ss, t); - while (ss.element) { - int cmpret; - - if (e) { - cmpret = cmp(e, ss.element); - } else { - cmpret = -reldir; /* invent a fixed compare result */ - } - - if (cmpret == 0) { - /* - * We've found an element that compares exactly equal to - * the query element. - */ - if (equal_permitted) { - /* If our search relation permits equality, we've - * finished already. */ - if (index) - *index = ss.index; - return ss.element; - } else { - /* Otherwise, pretend this element was slightly too - * big/small, according to the direction of search. */ - cmpret = reldir; - } - } - - search234_step(&ss, cmpret); - } - - /* - * No element compares equal to the one we were after, but - * ss.index indicates the index that element would have if it were - * inserted. - * - * So if our search relation is EQ, we must simply return failure. - */ - if (relation == REL234_EQ) - return NULL; - - /* - * Otherwise, we must do an index lookup for the previous index - * (if we're going left - LE or LT) or this index (if we're going - * right - GE or GT). - */ - if (relation == REL234_LT || relation == REL234_LE) { - ss.index--; - } - - /* - * 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. - */ - toret = index234(t, ss.index); - if (toret && index) - *index = ss.index; - return toret; -} -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); -} - -void search234_start(search234_state *state, tree234 *t) -{ - state->_node = t->root; - state->_base = 0; /* index of first element in this node's subtree */ - state->_last = -1; /* indicate that this node is not previously visted */ - search234_step(state, 0); -} -void search234_step(search234_state *state, int direction) -{ - node234 *node = state->_node; - int i; - - if (!node) { - state->element = NULL; - state->index = 0; - return; - } - - if (state->_last != -1) { - /* - * We're already pointing at some element of a node, so we - * should restrict to the elements left or right of it, - * depending on the requested search direction. - */ - assert(direction); - assert(node); - - if (direction > 0) - state->_lo = state->_last + 1; - else - state->_hi = state->_last - 1; - - if (state->_lo > state->_hi) { - /* - * We've run out of elements in this node, i.e. we've - * narrowed to nothing but a child pointer. Descend to - * that child, and update _base to the leftmost index of - * its subtree. - */ - for (i = 0; i < state->_lo; i++) - state->_base += 1 + node->counts[i]; - state->_node = node = node->kids[state->_lo]; - state->_last = -1; - } - } - - if (state->_last == -1) { - /* - * We've just entered a new node - either because of the above - * code, or because we were called from search234_start - and - * anything in that node is a viable answer. - */ - state->_lo = 0; - state->_hi = node ? elements234(node)-1 : 0; - } - - /* - * Now we've got something we can return. - */ - if (!node) { - state->element = NULL; - state->index = state->_base; - } else { - state->_last = (state->_lo + state->_hi) / 2; - state->element = node->elems[state->_last]; - state->index = state->_base + state->_last; - for (i = 0; i <= state->_last; i++) - state->index += node->counts[i]; - } -} - -/* - * 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")); - 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 -#include - -int n_errors = 0; - -/* - * Error reporting function. - */ -PRINTF_LIKE(1, 2) void error(char *fmt, ...) -{ - va_list ap; - printf("ERROR: "); - va_start(ap, fmt); - vfprintf(stdout, fmt, ap); - va_end(ap); - printf("\n"); - n_errors++; -} - -/* 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[1]; - 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); - } -} - -void searchtest_recurse(search234_state ss, int lo, int hi, - char **expected, char *directionbuf, - char *directionptr) -{ - *directionptr = '\0'; - - if (!ss.element) { - if (lo != hi) { - error("search234(%s) gave NULL for non-empty interval [%d,%d)", - directionbuf, lo, hi); - } else if (ss.index != lo) { - error("search234(%s) gave index %d should be %d", - directionbuf, ss.index, lo); - } else { - printf("%*ssearch234(%s) gave NULL,%d\n", - (int)(directionptr-directionbuf) * 2, "", directionbuf, - ss.index); - } - } else if (lo == hi) { - error("search234(%s) gave %s for empty interval [%d,%d)", - directionbuf, (char *)ss.element, lo, hi); - } else if (ss.element != expected[ss.index]) { - error("search234(%s) gave element %s should be %s", - directionbuf, (char *)ss.element, expected[ss.index]); - } else if (ss.index < lo || ss.index >= hi) { - error("search234(%s) gave index %d should be in [%d,%d)", - directionbuf, ss.index, lo, hi); - return; - } else { - search234_state next; - - printf("%*ssearch234(%s) gave %s,%d\n", - (int)(directionptr-directionbuf) * 2, "", directionbuf, - (char *)ss.element, ss.index); - - next = ss; - search234_step(&next, -1); - *directionptr = '-'; - searchtest_recurse(next, lo, ss.index, - expected, directionbuf, directionptr+1); - - next = ss; - search234_step(&next, +1); - *directionptr = '+'; - searchtest_recurse(next, ss.index+1, hi, - expected, directionbuf, directionptr+1); - } -} - -void searchtest(void) -{ - char *expected[NSTR], *p; - char directionbuf[NSTR * 10]; - int n; - search234_state ss; - - printf("beginning searchtest:"); - for (n = 0; (p = index234(tree, n)) != NULL; n++) { - expected[n] = p; - printf(" %d=%s", n, p); - } - printf(" count=%d\n", n); - - search234_start(&ss, tree); - searchtest_recurse(ss, 0, n, expected, directionbuf, directionbuf); -} - -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(); - searchtest(); - 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(); - searchtest(); - } - - 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", - (const char *)array[j], j); - delpostest(j); - } - - printf("%d errors found\n", n_errors); - return (n_errors != 0); -} - -#endif diff --git a/unix/CMakeLists.txt b/unix/CMakeLists.txt index 2932d853..72bf507c 100644 --- a/unix/CMakeLists.txt +++ b/unix/CMakeLists.txt @@ -1,8 +1,29 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) add_platform_sources_to_library(utils - uxutils.c uxsignal.c uxpoll.c xkeysym.c uxmisc.c xpmpucfg.c - xpmputty.c xpmptcfg.c xpmpterm.c x11misc.c ../time.c) + utils/arm_arch_queries.c + utils/block_signal.c + utils/cloexec.c + utils/dputs.c + utils/filename.c + utils/fontspec.c + utils/getticks.c + utils/get_username.c + utils/keysym_to_unicode.c + utils/make_dir_and_check_ours.c + utils/make_dir_path.c + utils/nonblock.c + utils/open_for_write_would_lose_data.c + utils/pgp_fingerprints.c + utils/pollwrap.c + utils/signal.c + utils/x11_ignore_error.c + ../utils/smemclr.c + # Compiled icon pixmap files + xpmpucfg.c xpmputty.c xpmptcfg.c xpmpterm.c + # We want the ISO C implementation of ltime(), because we don't have + # a local better alternative + ../utils/ltime.c) add_platform_sources_to_library(eventloop uxcliloop.c uxsel.c) add_platform_sources_to_library(console diff --git a/unix/utils/arm_arch_queries.c b/unix/utils/arm_arch_queries.c new file mode 100644 index 00000000..7c0957fa --- /dev/null +++ b/unix/utils/arm_arch_queries.c @@ -0,0 +1,80 @@ +/* + * Unix implementation of the OS query functions that detect Arm + * architecture extensions. + */ + +#include "putty.h" +#include "ssh.h" + +#include "utils/arm_arch_queries.h" + +#if defined __arm__ || defined __aarch64__ + +bool platform_aes_hw_available(void) +{ +#if defined HWCAP_AES + return getauxval(AT_HWCAP) & HWCAP_AES; +#elif defined HWCAP2_AES + return getauxval(AT_HWCAP2) & HWCAP2_AES; +#elif defined __APPLE__ + /* M1 macOS defines no optional sysctl flag indicating presence of + * the AES extension, which I assume to be because it's always + * present */ + return true; +#else + return false; +#endif +} + +bool platform_sha256_hw_available(void) +{ +#if defined HWCAP_SHA2 + return getauxval(AT_HWCAP) & HWCAP_SHA2; +#elif defined HWCAP2_SHA2 + return getauxval(AT_HWCAP2) & HWCAP2_SHA2; +#elif defined __APPLE__ + /* Assume always present on M1 macOS, similarly to AES */ + return true; +#else + return false; +#endif +} + +bool platform_sha1_hw_available(void) +{ +#if defined HWCAP_SHA1 + return getauxval(AT_HWCAP) & HWCAP_SHA1; +#elif defined HWCAP2_SHA1 + return getauxval(AT_HWCAP2) & HWCAP2_SHA1; +#elif defined __APPLE__ + /* Assume always present on M1 macOS, similarly to AES */ + return true; +#else + return false; +#endif +} + +bool platform_sha512_hw_available(void) +{ +#if defined HWCAP_SHA512 + return getauxval(AT_HWCAP) & HWCAP_SHA512; +#elif defined HWCAP2_SHA512 + return getauxval(AT_HWCAP2) & HWCAP2_SHA512; +#elif defined __APPLE__ + return test_sysctl_flag("hw.optional.armv8_2_sha512"); +#else + return false; +#endif +} + +#else /* defined __arm__ || defined __aarch64__ */ + +/* + * Include _something_ in this file to prevent an annoying compiler + * warning, and to avoid having to condition out this file in + * CMakeLists. It's in a library, so this variable shouldn't end up in + * any actual program, because nothing will refer to it. + */ +const int arm_arch_queries_dummy_variable = 0; + +#endif /* defined __arm__ || defined __aarch64__ */ diff --git a/unix/utils/arm_arch_queries.h b/unix/utils/arm_arch_queries.h new file mode 100644 index 00000000..bd055687 --- /dev/null +++ b/unix/utils/arm_arch_queries.h @@ -0,0 +1,65 @@ +/* + * Header included only by arm_arch_queries.c. + * + * The only reason this is a header file instead of a source file is + * so that I can define 'static inline' functions which may or may not + * be used, without provoking a compiler warning when I turn out not + * to use them in the subsequent source file. + */ + +#ifndef PUTTY_ARM_ARCH_QUERIES_H +#define PUTTY_ARM_ARCH_QUERIES_H + +#if defined __APPLE__ +#if HAVE_SYS_SYSCTL_H +#include +#endif +#endif /* defined __APPLE__ */ + +#if defined __arm__ || defined __aarch64__ + +#if HAVE_SYS_TYPES_H +#include +#endif + +#if HAVE_SYS_AUXV_H +#include +#endif + +#if HAVE_ASM_HWCAP_H +#include +#endif + +#if HAVE_GETAUXVAL +/* No code needed: getauxval has just the API we want already */ +#elif HAVE_ELF_AUX_INFO +/* Implement the simple getauxval API in terms of FreeBSD elf_aux_info */ +static inline u_long getauxval(int which) +{ + u_long toret; + if (elf_aux_info(which, &toret, sizeof(toret)) != 0) + return 0; /* elf_aux_info didn't work */ + return toret; +} +#else +/* Implement a stub getauxval which returns no capabilities */ +static inline u_long getauxval(int which) { return 0; } +#endif + +#endif /* defined __arm__ || defined __aarch64__ */ + +#if defined __APPLE__ +static inline bool test_sysctl_flag(const char *flagname) +{ +#if HAVE_SYSCTLBYNAME + int value; + size_t size = sizeof(value); + return (sysctlbyname(flagname, &value, &size, NULL, 0) == 0 && + size == sizeof(value) && value != 0); +#else /* HAVE_SYSCTLBYNAME */ + return false; +#endif /* HAVE_SYSCTLBYNAME */ +} +#endif /* defined __APPLE__ */ + +#endif /* PUTTY_ARM_ARCH_QUERIES_H */ diff --git a/unix/utils/block_signal.c b/unix/utils/block_signal.c new file mode 100644 index 00000000..918e63a4 --- /dev/null +++ b/unix/utils/block_signal.c @@ -0,0 +1,21 @@ +/* + * Handy function to block or unblock a signal, which does all the + * messing about with sigset_t for you. + */ + +#include +#include + +#include "defs.h" + +void block_signal(int sig, bool 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); + } +} diff --git a/unix/utils/cloexec.c b/unix/utils/cloexec.c new file mode 100644 index 00000000..2fc22289 --- /dev/null +++ b/unix/utils/cloexec.c @@ -0,0 +1,49 @@ +/* + * Set and clear the FD_CLOEXEC fcntl option on a file descriptor. + * + * We don't realistically expect these operations to fail (the most + * plausible error condition is EBADF, but we always believe ourselves + * to be passing a valid fd so even that's an assertion-fail sort of + * response), so we don't make any effort to return sensible error + * codes to the caller - we just log to standard error and die + * unceremoniously. + */ + +#include +#include +#include +#include + +#include + +#include "putty.h" + +void cloexec(int fd) +{ + int fdflags; + + fdflags = fcntl(fd, F_GETFD); + if (fdflags < 0) { + fprintf(stderr, "%d: fcntl(F_GETFD): %s\n", fd, strerror(errno)); + exit(1); + } + if (fcntl(fd, F_SETFD, fdflags | FD_CLOEXEC) < 0) { + fprintf(stderr, "%d: fcntl(F_SETFD): %s\n", fd, strerror(errno)); + exit(1); + } +} + +void noncloexec(int fd) +{ + int fdflags; + + fdflags = fcntl(fd, F_GETFD); + if (fdflags < 0) { + fprintf(stderr, "%d: fcntl(F_GETFD): %s\n", fd, strerror(errno)); + exit(1); + } + if (fcntl(fd, F_SETFD, fdflags & ~FD_CLOEXEC) < 0) { + fprintf(stderr, "%d: fcntl(F_SETFD): %s\n", fd, strerror(errno)); + exit(1); + } +} diff --git a/unix/utils/dputs.c b/unix/utils/dputs.c new file mode 100644 index 00000000..97a275e0 --- /dev/null +++ b/unix/utils/dputs.c @@ -0,0 +1,24 @@ +/* + * Implementation of dputs() for Unix. + * + * The debug messages are written to standard output, and also into a + * file called debug.log. + */ + +#include + +#include "putty.h" + +static FILE *debug_fp = NULL; + +void dputs(const char *buf) +{ + if (!debug_fp) { + debug_fp = fopen("debug.log", "w"); + } + + if (write(1, buf, strlen(buf)) < 0) {} /* 'error check' to placate gcc */ + + fputs(buf, debug_fp); + fflush(debug_fp); +} diff --git a/unix/utils/filename.c b/unix/utils/filename.c new file mode 100644 index 00000000..208483d2 --- /dev/null +++ b/unix/utils/filename.c @@ -0,0 +1,72 @@ +/* + * Implementation of Filename for Unix, including f_open(). + */ + +#include +#include + +#include "putty.h" + +Filename *filename_from_str(const char *str) +{ + Filename *ret = snew(Filename); + ret->path = dupstr(str); + return ret; +} + +Filename *filename_copy(const Filename *fn) +{ + return filename_from_str(fn->path); +} + +const char *filename_to_str(const Filename *fn) +{ + return fn->path; +} + +bool filename_equal(const Filename *f1, const Filename *f2) +{ + return !strcmp(f1->path, f2->path); +} + +bool filename_is_null(const Filename *fn) +{ + return !fn->path[0]; +} + +void filename_free(Filename *fn) +{ + sfree(fn->path); + sfree(fn); +} + +void filename_serialise(BinarySink *bs, const Filename *f) +{ + put_asciz(bs, f->path); +} +Filename *filename_deserialise(BinarySource *src) +{ + return filename_from_str(get_asciz(src)); +} + +char filename_char_sanitise(char c) +{ + if (c == '/') + return '.'; + return c; +} + +FILE *f_open(const Filename *filename, char const *mode, bool 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, 0600); + if (fd < 0) + return NULL; + return fdopen(fd, mode); + } +} diff --git a/unix/utils/fontspec.c b/unix/utils/fontspec.c new file mode 100644 index 00000000..7c5a0a2f --- /dev/null +++ b/unix/utils/fontspec.c @@ -0,0 +1,35 @@ +/* + * Implementation of FontSpec for Unix. This is more or less + * degenerate - on this platform a font specification is just a + * string. + */ + +#include "putty.h" + +FontSpec *fontspec_new(const char *name) +{ + FontSpec *f = snew(FontSpec); + f->name = dupstr(name); + return f; +} + +FontSpec *fontspec_copy(const FontSpec *f) +{ + return fontspec_new(f->name); +} + +void fontspec_free(FontSpec *f) +{ + sfree(f->name); + sfree(f); +} + +void fontspec_serialise(BinarySink *bs, FontSpec *f) +{ + put_asciz(bs, f->name); +} + +FontSpec *fontspec_deserialise(BinarySource *src) +{ + return fontspec_new(get_asciz(src)); +} diff --git a/unix/utils/get_username.c b/unix/utils/get_username.c new file mode 100644 index 00000000..61e259c0 --- /dev/null +++ b/unix/utils/get_username.c @@ -0,0 +1,52 @@ +/* + * Implementation of get_username() for Unix. + */ + +#include +#include + +#include "putty.h" + +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(); +#if HAVE_SETPWENT + setpwent(); +#endif + 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; + } +#if HAVE_ENDPWENT + endpwent(); +#endif + + return dupstr(ret); +} diff --git a/unix/utils/getticks.c b/unix/utils/getticks.c new file mode 100644 index 00000000..9d169816 --- /dev/null +++ b/unix/utils/getticks.c @@ -0,0 +1,33 @@ +/* + * Implement getticks() for Unix. + */ + +#include +#include + +#include "putty.h" + +unsigned long getticks(void) +{ + /* + * We want to use milliseconds rather than the microseconds or + * nanoseconds given by the underlying clock functions, because we + * need a decent number of them to fit into a 32-bit word so it + * can be used for keepalives. + */ +#if HAVE_CLOCK_GETTIME && HAVE_CLOCK_MONOTONIC + { + /* Use CLOCK_MONOTONIC if available, so as to be unconfused if + * the system clock changes. */ + struct timespec ts; + if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) + return ts.tv_sec * TICKSPERSEC + + ts.tv_nsec / (1000000000 / TICKSPERSEC); + } +#endif + { + struct timeval tv; + gettimeofday(&tv, NULL); + return tv.tv_sec * TICKSPERSEC + tv.tv_usec / (1000000 / TICKSPERSEC); + } +} diff --git a/unix/utils/keysym_to_unicode.c b/unix/utils/keysym_to_unicode.c new file mode 100644 index 00000000..37e419fe --- /dev/null +++ b/unix/utils/keysym_to_unicode.c @@ -0,0 +1,1011 @@ +/* + * Map 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; +} diff --git a/unix/utils/make_dir_and_check_ours.c b/unix/utils/make_dir_and_check_ours.c new file mode 100644 index 00000000..cab4dc20 --- /dev/null +++ b/unix/utils/make_dir_and_check_ours.c @@ -0,0 +1,60 @@ +/* + * Create a directory accessible only to us, and then check afterwards + * that we really did end up with a directory with the right ownership + * and permissions. + * + * The idea is that this is a directory in which we're about to create + * something sensitive, like a listening Unix-domain socket for SSH + * connection sharing or an SSH agent. We want to be protected against + * somebody else previously having created the directory in a way + * that's writable to us, and thus manipulating us into creating the + * actual socket in a directory they can see so that they can connect + * to it and (say) use our authenticated SSH sessions. + * + * NOTE: The strategy used in this function is not safe if the enemy + * has unrestricted write access to the containing directory. In that + * case, they could move our directory out of the way and make a new + * one, after this function returns and before we create our socket + * (or whatever) inside it. + * + * But this should be OK for temp directories (which modify the + * default world-write behaviour by also setting the 't' bit, + * preventing people from renaming or deleting things in there that + * they don't own). And of course it's also safe if the directory is + * writable only by our _own_ uid. + */ + +#include +#include + +#include +#include +#include + +#include "putty.h" + +char *make_dir_and_check_ours(const char *dirname) +{ + struct stat st; + + /* + * Create the directory. We might have created it before, so + * EEXIST is an OK error; but anything else is doom. + */ + if (mkdir(dirname, 0700) < 0 && errno != EEXIST) + return dupprintf("%s: mkdir: %s", dirname, strerror(errno)); + + /* + * Stat the directory and check its ownership and permissions. + */ + if (stat(dirname, &st) < 0) + return dupprintf("%s: stat: %s", dirname, strerror(errno)); + if (st.st_uid != getuid()) + return dupprintf("%s: directory owned by uid %d, not by us", + dirname, st.st_uid); + if ((st.st_mode & 077) != 0) + return dupprintf("%s: directory has overgenerous permissions %03o" + " (expected 700)", dirname, st.st_mode & 0777); + + return NULL; +} diff --git a/unix/utils/make_dir_path.c b/unix/utils/make_dir_path.c new file mode 100644 index 00000000..4d212fe4 --- /dev/null +++ b/unix/utils/make_dir_path.c @@ -0,0 +1,39 @@ +/* + * Make a path of subdirectories, tolerating EEXIST at every step. + */ + +#include +#include + +#include +#include +#include + +#include "putty.h" + +char *make_dir_path(const char *path, mode_t mode) +{ + int pos = 0; + char *prefix; + + while (1) { + pos += strcspn(path + pos, "/"); + + if (pos > 0) { + prefix = dupprintf("%.*s", pos, path); + + if (mkdir(prefix, mode) < 0 && errno != EEXIST) { + char *ret = dupprintf("%s: mkdir: %s", + prefix, strerror(errno)); + sfree(prefix); + return ret; + } + + sfree(prefix); + } + + if (!path[pos]) + return NULL; + pos += strspn(path + pos, "/"); + } +} diff --git a/unix/utils/nonblock.c b/unix/utils/nonblock.c new file mode 100644 index 00000000..cece206c --- /dev/null +++ b/unix/utils/nonblock.c @@ -0,0 +1,55 @@ +/* + * Set and clear the O_NONBLOCK fcntl option on an open file. + * + * We don't realistically expect these operations to fail (the most + * plausible error condition is EBADF, but we always believe ourselves + * to be passing a valid fd so even that's an assertion-fail sort of + * response), so we don't make any effort to return sensible error + * codes to the caller - we just log to standard error and die + * unceremoniously. + * + * Returns the previous state of O_NONBLOCK. + */ + +#include +#include +#include +#include + +#include + +#include "putty.h" + +bool nonblock(int fd) +{ + int fdflags; + + fdflags = fcntl(fd, F_GETFL); + if (fdflags < 0) { + fprintf(stderr, "%d: fcntl(F_GETFL): %s\n", fd, strerror(errno)); + exit(1); + } + if (fcntl(fd, F_SETFL, fdflags | O_NONBLOCK) < 0) { + fprintf(stderr, "%d: fcntl(F_SETFL): %s\n", fd, strerror(errno)); + exit(1); + } + + return fdflags & O_NONBLOCK; +} + +bool no_nonblock(int fd) +{ + int fdflags; + + fdflags = fcntl(fd, F_GETFL); + if (fdflags < 0) { + fprintf(stderr, "%d: fcntl(F_GETFL): %s\n", fd, strerror(errno)); + exit(1); + } + if (fcntl(fd, F_SETFL, fdflags & ~O_NONBLOCK) < 0) { + fprintf(stderr, "%d: fcntl(F_SETFL): %s\n", fd, strerror(errno)); + exit(1); + } + + return fdflags & O_NONBLOCK; +} diff --git a/unix/utils/open_for_write_would_lose_data.c b/unix/utils/open_for_write_would_lose_data.c new file mode 100644 index 00000000..46e695fd --- /dev/null +++ b/unix/utils/open_for_write_would_lose_data.c @@ -0,0 +1,44 @@ +/* + * Unix implementation of open_for_write_would_lose_data(). + */ + +#include +#include + +#include "putty.h" + +bool open_for_write_would_lose_data(const Filename *fn) +{ + struct stat st; + + if (stat(fn->path, &st) < 0) { + /* + * If the file doesn't even exist, we obviously want to return + * false. If we failed to stat it for any other reason, + * ignoring the precise error code and returning false still + * doesn't seem too unreasonable, because then we'll try to + * open the file for writing and report _that_ error, which is + * likely to be more to the point. + */ + return false; + } + + /* + * OK, something exists at this pathname and we've found out + * something about it. But an open-for-write will only + * destructively truncate it if it's a regular file with nonzero + * size. If it's empty, or some other kind of special thing like a + * character device (e.g. /dev/tty) or a named pipe, then opening + * it for write is already non-destructive and it's pointless and + * annoying to warn about it just because the same file can be + * opened for reading. (Indeed, if it's a named pipe, opening it + * for reading actually _causes inconvenience_ in its own right, + * even before the question of whether it gives misleading + * information.) + */ + if (S_ISREG(st.st_mode) && st.st_size > 0) { + return true; + } + + return false; +} diff --git a/unix/utils/pgp_fingerprints.c b/unix/utils/pgp_fingerprints.c new file mode 100644 index 00000000..badedd71 --- /dev/null +++ b/unix/utils/pgp_fingerprints.c @@ -0,0 +1,23 @@ +/* + * Display the fingerprints of the PGP Master Keys to the user. + * + * (This is in its own file rather than in uxcons.c, because it's + * appropriate even for Unix GUI apps.) + */ + +#include "putty.h" + +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 as of " PGP_MASTER_KEY_YEAR + " (" PGP_MASTER_KEY_DETAILS "):\n" + " " PGP_MASTER_KEY_FP "\n\n" + "Previous Master Key (" PGP_PREV_MASTER_KEY_YEAR + ", " PGP_PREV_MASTER_KEY_DETAILS "):\n" + " " PGP_PREV_MASTER_KEY_FP "\n", stdout); +} diff --git a/unix/utils/pollwrap.c b/unix/utils/pollwrap.c new file mode 100644 index 00000000..51121e36 --- /dev/null +++ b/unix/utils/pollwrap.c @@ -0,0 +1,190 @@ +/* + * Wrapper system around poll() that lets me treat it more or less + * like select(), but avoiding the inherent limitation of select() + * that it can't handle the full range of fds that are capable of + * existing. + * + * The pollwrapper structure contains the 'struct pollfd' array passed + * to poll() itself, and also a tree234 that maps each fd to its + * location in the list, which makes it convenient to add or remove + * individual fds from the system or change what events you're + * watching for on them. So the API is _shaped_ basically like select, + * even if none of the details are identical: from outside this + * module, a pollwrapper can be used wherever you'd otherwise have had + * an fd_set. + * + * Also, this module translate between the simple select r/w/x + * classification and the richer poll flags. We have to stick to r/w/x + * in this code base, because it ports to other systems where that's + * all you get. + */ + +/* On some systems this is needed to get poll.h to define eg.. POLLRDNORM */ +#define _XOPEN_SOURCE + +#include + +#include "putty.h" +#include "tree234.h" + +struct pollwrapper { + struct pollfd *fds; + size_t nfd, fdsize; + tree234 *fdtopos; +}; + +typedef struct pollwrap_fdtopos pollwrap_fdtopos; +struct pollwrap_fdtopos { + int fd; + size_t pos; +}; + +static int pollwrap_fd_cmp(void *av, void *bv) +{ + pollwrap_fdtopos *a = (pollwrap_fdtopos *)av; + pollwrap_fdtopos *b = (pollwrap_fdtopos *)bv; + return a->fd < b->fd ? -1 : a->fd > b->fd ? +1 : 0; +} + +pollwrapper *pollwrap_new(void) +{ + pollwrapper *pw = snew(pollwrapper); + pw->fdsize = 16; + pw->nfd = 0; + pw->fds = snewn(pw->fdsize, struct pollfd); + pw->fdtopos = newtree234(pollwrap_fd_cmp); + return pw; +} + +void pollwrap_free(pollwrapper *pw) +{ + pollwrap_clear(pw); + freetree234(pw->fdtopos); + sfree(pw->fds); + sfree(pw); +} + +void pollwrap_clear(pollwrapper *pw) +{ + pw->nfd = 0; + for (pollwrap_fdtopos *f2p; + (f2p = delpos234(pw->fdtopos, 0)) != NULL ;) + sfree(f2p); +} + +void pollwrap_add_fd_events(pollwrapper *pw, int fd, int events) +{ + pollwrap_fdtopos *f2p, f2p_find; + + assert(fd >= 0); + + f2p_find.fd = fd; + f2p = find234(pw->fdtopos, &f2p_find, NULL); + if (!f2p) { + sgrowarray(pw->fds, pw->fdsize, pw->nfd); + size_t index = pw->nfd++; + pw->fds[index].fd = fd; + pw->fds[index].events = pw->fds[index].revents = 0; + + f2p = snew(pollwrap_fdtopos); + f2p->fd = fd; + f2p->pos = index; + pollwrap_fdtopos *added = add234(pw->fdtopos, f2p); + assert(added == f2p); + } + + pw->fds[f2p->pos].events |= events; +} + +/* Omit any of the POLL{RD,WR}{NORM,BAND} flag values that are still + * not defined by poll.h, just in case */ +#ifndef POLLRDNORM +#define POLLRDNORM 0 +#endif +#ifndef POLLRDBAND +#define POLLRDBAND 0 +#endif +#ifndef POLLWRNORM +#define POLLWRNORM 0 +#endif +#ifndef POLLWRBAND +#define POLLWRBAND 0 +#endif + +#define SELECT_R_IN (POLLIN | POLLRDNORM | POLLRDBAND) +#define SELECT_W_IN (POLLOUT | POLLWRNORM | POLLWRBAND) +#define SELECT_X_IN (POLLPRI) + +#define SELECT_R_OUT (SELECT_R_IN | POLLERR | POLLHUP) +#define SELECT_W_OUT (SELECT_W_IN | POLLERR) +#define SELECT_X_OUT (SELECT_X_IN) + +void pollwrap_add_fd_rwx(pollwrapper *pw, int fd, int rwx) +{ + int events = 0; + if (rwx & SELECT_R) + events |= SELECT_R_IN; + if (rwx & SELECT_W) + events |= SELECT_W_IN; + if (rwx & SELECT_X) + events |= SELECT_X_IN; + pollwrap_add_fd_events(pw, fd, events); +} + +int pollwrap_poll_instant(pollwrapper *pw) +{ + return poll(pw->fds, pw->nfd, 0); +} + +int pollwrap_poll_endless(pollwrapper *pw) +{ + return poll(pw->fds, pw->nfd, -1); +} + +int pollwrap_poll_timeout(pollwrapper *pw, int milliseconds) +{ + assert(milliseconds >= 0); + return poll(pw->fds, pw->nfd, milliseconds); +} + +static void pollwrap_get_fd_events_revents(pollwrapper *pw, int fd, + int *events_p, int *revents_p) +{ + pollwrap_fdtopos *f2p, f2p_find; + int events = 0, revents = 0; + + assert(fd >= 0); + + f2p_find.fd = fd; + f2p = find234(pw->fdtopos, &f2p_find, NULL); + if (f2p) { + events = pw->fds[f2p->pos].events; + revents = pw->fds[f2p->pos].revents; + } + + if (events_p) + *events_p = events; + if (revents_p) + *revents_p = revents; +} + +int pollwrap_get_fd_events(pollwrapper *pw, int fd) +{ + int revents; + pollwrap_get_fd_events_revents(pw, fd, NULL, &revents); + return revents; +} + +int pollwrap_get_fd_rwx(pollwrapper *pw, int fd) +{ + int events, revents; + pollwrap_get_fd_events_revents(pw, fd, &events, &revents); + int rwx = 0; + if ((events & POLLIN) && (revents & SELECT_R_OUT)) + rwx |= SELECT_R; + if ((events & POLLOUT) && (revents & SELECT_W_OUT)) + rwx |= SELECT_W; + if ((events & POLLPRI) && (revents & SELECT_X_OUT)) + rwx |= SELECT_X; + return rwx; +} diff --git a/unix/utils/signal.c b/unix/utils/signal.c new file mode 100644 index 00000000..a56fe6be --- /dev/null +++ b/unix/utils/signal.c @@ -0,0 +1,30 @@ +/* + * PuTTY's wrapper on signal(2). + * + * 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). + */ + +#include + +#include "defs.h" + +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; +} diff --git a/unix/utils/x11_ignore_error.c b/unix/utils/x11_ignore_error.c new file mode 100644 index 00000000..a4165ab5 --- /dev/null +++ b/unix/utils/x11_ignore_error.c @@ -0,0 +1,88 @@ +/* + * Error handling mechanism which permits us to ignore specific X11 + * errors from particular requests. We maintain a list of upcoming + * potential error events that we want to not treat as fatal errors. + */ + +#include +#include +#include +#include +#include + +#include "putty.h" + +#ifndef NOT_X_WINDOWS + +#include +#include +#include + +#include "x11misc.h" + +static int (*orig_x11_error_handler)(Display *thisdisp, XErrorEvent *err); + +struct x11_err_to_ignore { + Display *display; + unsigned char error_code; + unsigned long serial; +}; + +static struct x11_err_to_ignore *errs; +static size_t nerrs, errsize; + +static int x11_error_handler(Display *thisdisp, XErrorEvent *err) +{ + for (size_t i = 0; i < nerrs; i++) { + if (thisdisp == errs[i].display && + err->serial == errs[i].serial && + err->error_code == errs[i].error_code) { + /* Ok, this is an error we're happy to ignore */ + return 0; + } + } + + return (*orig_x11_error_handler)(thisdisp, err); +} + +void x11_ignore_error(Display *disp, unsigned char errcode) +{ + /* + * Install our error handler, if we haven't already. + */ + if (!orig_x11_error_handler) + orig_x11_error_handler = XSetErrorHandler(x11_error_handler); + + /* + * This is as good a moment as any to winnow the ignore list based + * on requests we know to have been processed. + */ + { + unsigned long last = LastKnownRequestProcessed(disp); + size_t i, j; + for (i = j = 0; i < nerrs; i++) { + if (errs[i].display == disp && errs[i].serial <= last) + continue; + errs[j++] = errs[i]; + } + nerrs = j; + } + + sgrowarray(errs, errsize, nerrs); + errs[nerrs].display = disp; + errs[nerrs].error_code = errcode; + errs[nerrs].serial = NextRequest(disp); + nerrs++; +} + +#else /* NOT_X_WINDOWS */ + +/* + * Include _something_ in this file to prevent an annoying compiler + * warning, and to avoid having to condition out this file in + * CMakeLists. It's in a library, so this variable shouldn't end up in + * any actual program, because nothing will refer to it. + */ +const int x11_ignore_error_dummy_variable = 0; + +#endif /* NOT_X_WINDOWS */ diff --git a/unix/uxmisc.c b/unix/uxmisc.c deleted file mode 100644 index 876725bd..00000000 --- a/unix/uxmisc.c +++ /dev/null @@ -1,371 +0,0 @@ -/* - * PuTTY miscellaneous Unix stuff - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "putty.h" - -unsigned long getticks(void) -{ - /* - * We want to use milliseconds rather than the microseconds or - * nanoseconds given by the underlying clock functions, because we - * need a decent number of them to fit into a 32-bit word so it - * can be used for keepalives. - */ -#if HAVE_CLOCK_GETTIME && HAVE_CLOCK_MONOTONIC - { - /* Use CLOCK_MONOTONIC if available, so as to be unconfused if - * the system clock changes. */ - struct timespec ts; - if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) - return ts.tv_sec * TICKSPERSEC + - ts.tv_nsec / (1000000000 / TICKSPERSEC); - } -#endif - { - struct timeval tv; - gettimeofday(&tv, NULL); - return tv.tv_sec * TICKSPERSEC + tv.tv_usec / (1000000 / TICKSPERSEC); - } -} - -Filename *filename_from_str(const char *str) -{ - Filename *ret = snew(Filename); - ret->path = dupstr(str); - return ret; -} - -Filename *filename_copy(const Filename *fn) -{ - return filename_from_str(fn->path); -} - -const char *filename_to_str(const Filename *fn) -{ - return fn->path; -} - -bool filename_equal(const Filename *f1, const Filename *f2) -{ - return !strcmp(f1->path, f2->path); -} - -bool filename_is_null(const Filename *fn) -{ - return !fn->path[0]; -} - -void filename_free(Filename *fn) -{ - sfree(fn->path); - sfree(fn); -} - -void filename_serialise(BinarySink *bs, const Filename *f) -{ - put_asciz(bs, f->path); -} -Filename *filename_deserialise(BinarySource *src) -{ - return filename_from_str(get_asciz(src)); -} - -char filename_char_sanitise(char c) -{ - if (c == '/') - return '.'; - return c; -} - -#ifdef DEBUG -static FILE *debug_fp = NULL; - -void dputs(const char *buf) -{ - if (!debug_fp) { - debug_fp = fopen("debug.log", "w"); - } - - if (write(1, buf, strlen(buf)) < 0) {} /* 'error check' to placate gcc */ - - 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(); -#if HAVE_SETPWENT - setpwent(); -#endif - 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; - } -#if HAVE_ENDPWENT - endpwent(); -#endif - - 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 as of " PGP_MASTER_KEY_YEAR - " (" PGP_MASTER_KEY_DETAILS "):\n" - " " PGP_MASTER_KEY_FP "\n\n" - "Previous Master Key (" PGP_PREV_MASTER_KEY_YEAR - ", " PGP_PREV_MASTER_KEY_DETAILS "):\n" - " " PGP_PREV_MASTER_KEY_FP "\n", stdout); -} - -/* - * Set and clear fcntl options on a file descriptor. We don't - * realistically expect any of these operations to fail (the most - * plausible error condition is EBADF, but we always believe ourselves - * to be passing a valid fd so even that's an assertion-fail sort of - * response), so we don't make any effort to return sensible error - * codes to the caller - we just log to standard error and die - * unceremoniously. However, nonblock and no_nonblock do return the - * previous state of O_NONBLOCK. - */ -void cloexec(int fd) { - int fdflags; - - fdflags = fcntl(fd, F_GETFD); - if (fdflags < 0) { - fprintf(stderr, "%d: fcntl(F_GETFD): %s\n", fd, strerror(errno)); - exit(1); - } - if (fcntl(fd, F_SETFD, fdflags | FD_CLOEXEC) < 0) { - fprintf(stderr, "%d: fcntl(F_SETFD): %s\n", fd, strerror(errno)); - exit(1); - } -} -void noncloexec(int fd) { - int fdflags; - - fdflags = fcntl(fd, F_GETFD); - if (fdflags < 0) { - fprintf(stderr, "%d: fcntl(F_GETFD): %s\n", fd, strerror(errno)); - exit(1); - } - if (fcntl(fd, F_SETFD, fdflags & ~FD_CLOEXEC) < 0) { - fprintf(stderr, "%d: fcntl(F_SETFD): %s\n", fd, strerror(errno)); - exit(1); - } -} -bool nonblock(int fd) { - int fdflags; - - fdflags = fcntl(fd, F_GETFL); - if (fdflags < 0) { - fprintf(stderr, "%d: fcntl(F_GETFL): %s\n", fd, strerror(errno)); - exit(1); - } - if (fcntl(fd, F_SETFL, fdflags | O_NONBLOCK) < 0) { - fprintf(stderr, "%d: fcntl(F_SETFL): %s\n", fd, strerror(errno)); - exit(1); - } - - return fdflags & O_NONBLOCK; -} -bool no_nonblock(int fd) { - int fdflags; - - fdflags = fcntl(fd, F_GETFL); - if (fdflags < 0) { - fprintf(stderr, "%d: fcntl(F_GETFL): %s\n", fd, strerror(errno)); - exit(1); - } - if (fcntl(fd, F_SETFL, fdflags & ~O_NONBLOCK) < 0) { - fprintf(stderr, "%d: fcntl(F_SETFL): %s\n", fd, strerror(errno)); - exit(1); - } - - return fdflags & O_NONBLOCK; -} - -FILE *f_open(const Filename *filename, char const *mode, bool 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, 0600); - if (fd < 0) - return NULL; - return fdopen(fd, mode); - } -} - -FontSpec *fontspec_new(const char *name) -{ - FontSpec *f = snew(FontSpec); - f->name = dupstr(name); - return f; -} -FontSpec *fontspec_copy(const FontSpec *f) -{ - return fontspec_new(f->name); -} -void fontspec_free(FontSpec *f) -{ - sfree(f->name); - sfree(f); -} -void fontspec_serialise(BinarySink *bs, FontSpec *f) -{ - put_asciz(bs, f->name); -} -FontSpec *fontspec_deserialise(BinarySource *src) -{ - return fontspec_new(get_asciz(src)); -} - -char *make_dir_and_check_ours(const char *dirname) -{ - struct stat st; - - /* - * Create the directory. We might have created it before, so - * EEXIST is an OK error; but anything else is doom. - */ - if (mkdir(dirname, 0700) < 0 && errno != EEXIST) - return dupprintf("%s: mkdir: %s", dirname, strerror(errno)); - - /* - * Now check that that directory is _owned by us_ and not writable - * by anybody else. This protects us against somebody else - * previously having created the directory in a way that's - * writable to us, and thus manipulating us into creating the - * actual socket in a directory they can see so that they can - * connect to it and use our authenticated SSH sessions. - */ - if (stat(dirname, &st) < 0) - return dupprintf("%s: stat: %s", dirname, strerror(errno)); - if (st.st_uid != getuid()) - return dupprintf("%s: directory owned by uid %d, not by us", - dirname, st.st_uid); - if ((st.st_mode & 077) != 0) - return dupprintf("%s: directory has overgenerous permissions %03o" - " (expected 700)", dirname, st.st_mode & 0777); - - return NULL; -} - -char *make_dir_path(const char *path, mode_t mode) -{ - int pos = 0; - char *prefix; - - while (1) { - pos += strcspn(path + pos, "/"); - - if (pos > 0) { - prefix = dupprintf("%.*s", pos, path); - - if (mkdir(prefix, mode) < 0 && errno != EEXIST) { - char *ret = dupprintf("%s: mkdir: %s", - prefix, strerror(errno)); - sfree(prefix); - return ret; - } - - sfree(prefix); - } - - if (!path[pos]) - return NULL; - pos += strspn(path + pos, "/"); - } -} - -bool open_for_write_would_lose_data(const Filename *fn) -{ - struct stat st; - - if (stat(fn->path, &st) < 0) { - /* - * If the file doesn't even exist, we obviously want to return - * false. If we failed to stat it for any other reason, - * ignoring the precise error code and returning false still - * doesn't seem too unreasonable, because then we'll try to - * open the file for writing and report _that_ error, which is - * likely to be more to the point. - */ - return false; - } - - /* - * OK, something exists at this pathname and we've found out - * something about it. But an open-for-write will only - * destructively truncate it if it's a regular file with nonzero - * size. If it's empty, or some other kind of special thing like a - * character device (e.g. /dev/tty) or a named pipe, then opening - * it for write is already non-destructive and it's pointless and - * annoying to warn about it just because the same file can be - * opened for reading. (Indeed, if it's a named pipe, opening it - * for reading actually _causes inconvenience_ in its own right, - * even before the question of whether it gives misleading - * information.) - */ - if (S_ISREG(st.st_mode) && st.st_size > 0) { - return true; - } - - return false; -} diff --git a/unix/uxpoll.c b/unix/uxpoll.c deleted file mode 100644 index 474926bb..00000000 --- a/unix/uxpoll.c +++ /dev/null @@ -1,169 +0,0 @@ -/* On some systems this is needed to get poll.h to define eg.. POLLRDNORM */ -#define _XOPEN_SOURCE - -#include - -#include "putty.h" -#include "tree234.h" - -struct pollwrapper { - struct pollfd *fds; - size_t nfd, fdsize; - tree234 *fdtopos; -}; - -typedef struct pollwrap_fdtopos pollwrap_fdtopos; -struct pollwrap_fdtopos { - int fd; - size_t pos; -}; - -static int pollwrap_fd_cmp(void *av, void *bv) -{ - pollwrap_fdtopos *a = (pollwrap_fdtopos *)av; - pollwrap_fdtopos *b = (pollwrap_fdtopos *)bv; - return a->fd < b->fd ? -1 : a->fd > b->fd ? +1 : 0; -} - -pollwrapper *pollwrap_new(void) -{ - pollwrapper *pw = snew(pollwrapper); - pw->fdsize = 16; - pw->nfd = 0; - pw->fds = snewn(pw->fdsize, struct pollfd); - pw->fdtopos = newtree234(pollwrap_fd_cmp); - return pw; -} - -void pollwrap_free(pollwrapper *pw) -{ - pollwrap_clear(pw); - freetree234(pw->fdtopos); - sfree(pw->fds); - sfree(pw); -} - -void pollwrap_clear(pollwrapper *pw) -{ - pw->nfd = 0; - for (pollwrap_fdtopos *f2p; - (f2p = delpos234(pw->fdtopos, 0)) != NULL ;) - sfree(f2p); -} - -void pollwrap_add_fd_events(pollwrapper *pw, int fd, int events) -{ - pollwrap_fdtopos *f2p, f2p_find; - - assert(fd >= 0); - - f2p_find.fd = fd; - f2p = find234(pw->fdtopos, &f2p_find, NULL); - if (!f2p) { - sgrowarray(pw->fds, pw->fdsize, pw->nfd); - size_t index = pw->nfd++; - pw->fds[index].fd = fd; - pw->fds[index].events = pw->fds[index].revents = 0; - - f2p = snew(pollwrap_fdtopos); - f2p->fd = fd; - f2p->pos = index; - pollwrap_fdtopos *added = add234(pw->fdtopos, f2p); - assert(added == f2p); - } - - pw->fds[f2p->pos].events |= events; -} - -/* Omit any of the POLL{RD,WR}{NORM,BAND} flag values that are still - * not defined by poll.h, just in case */ -#ifndef POLLRDNORM -#define POLLRDNORM 0 -#endif -#ifndef POLLRDBAND -#define POLLRDBAND 0 -#endif -#ifndef POLLWRNORM -#define POLLWRNORM 0 -#endif -#ifndef POLLWRBAND -#define POLLWRBAND 0 -#endif - -#define SELECT_R_IN (POLLIN | POLLRDNORM | POLLRDBAND) -#define SELECT_W_IN (POLLOUT | POLLWRNORM | POLLWRBAND) -#define SELECT_X_IN (POLLPRI) - -#define SELECT_R_OUT (SELECT_R_IN | POLLERR | POLLHUP) -#define SELECT_W_OUT (SELECT_W_IN | POLLERR) -#define SELECT_X_OUT (SELECT_X_IN) - -void pollwrap_add_fd_rwx(pollwrapper *pw, int fd, int rwx) -{ - int events = 0; - if (rwx & SELECT_R) - events |= SELECT_R_IN; - if (rwx & SELECT_W) - events |= SELECT_W_IN; - if (rwx & SELECT_X) - events |= SELECT_X_IN; - pollwrap_add_fd_events(pw, fd, events); -} - -int pollwrap_poll_instant(pollwrapper *pw) -{ - return poll(pw->fds, pw->nfd, 0); -} - -int pollwrap_poll_endless(pollwrapper *pw) -{ - return poll(pw->fds, pw->nfd, -1); -} - -int pollwrap_poll_timeout(pollwrapper *pw, int milliseconds) -{ - assert(milliseconds >= 0); - return poll(pw->fds, pw->nfd, milliseconds); -} - -static void pollwrap_get_fd_events_revents(pollwrapper *pw, int fd, - int *events_p, int *revents_p) -{ - pollwrap_fdtopos *f2p, f2p_find; - int events = 0, revents = 0; - - assert(fd >= 0); - - f2p_find.fd = fd; - f2p = find234(pw->fdtopos, &f2p_find, NULL); - if (f2p) { - events = pw->fds[f2p->pos].events; - revents = pw->fds[f2p->pos].revents; - } - - if (events_p) - *events_p = events; - if (revents_p) - *revents_p = revents; -} - -int pollwrap_get_fd_events(pollwrapper *pw, int fd) -{ - int revents; - pollwrap_get_fd_events_revents(pw, fd, NULL, &revents); - return revents; -} - -int pollwrap_get_fd_rwx(pollwrapper *pw, int fd) -{ - int events, revents; - pollwrap_get_fd_events_revents(pw, fd, &events, &revents); - int rwx = 0; - if ((events & POLLIN) && (revents & SELECT_R_OUT)) - rwx |= SELECT_R; - if ((events & POLLOUT) && (revents & SELECT_W_OUT)) - rwx |= SELECT_W; - if ((events & POLLPRI) && (revents & SELECT_X_OUT)) - rwx |= SELECT_X; - return rwx; -} diff --git a/unix/uxsignal.c b/unix/uxsignal.c deleted file mode 100644 index d75cce43..00000000 --- a/unix/uxsignal.c +++ /dev/null @@ -1,47 +0,0 @@ -#include -#include -#include - -#include "defs.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, bool 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: -*/ diff --git a/unix/uxutils.c b/unix/uxutils.c deleted file mode 100644 index 3a04c1be..00000000 --- a/unix/uxutils.c +++ /dev/null @@ -1,65 +0,0 @@ -#include "putty.h" -#include "ssh.h" - -#include "uxutils.h" - -#if defined __arm__ || defined __aarch64__ - -bool platform_aes_hw_available(void) -{ -#if defined HWCAP_AES - return getauxval(AT_HWCAP) & HWCAP_AES; -#elif defined HWCAP2_AES - return getauxval(AT_HWCAP2) & HWCAP2_AES; -#elif defined __APPLE__ - /* M1 macOS defines no optional sysctl flag indicating presence of - * the AES extension, which I assume to be because it's always - * present */ - return true; -#else - return false; -#endif -} - -bool platform_sha256_hw_available(void) -{ -#if defined HWCAP_SHA2 - return getauxval(AT_HWCAP) & HWCAP_SHA2; -#elif defined HWCAP2_SHA2 - return getauxval(AT_HWCAP2) & HWCAP2_SHA2; -#elif defined __APPLE__ - /* Assume always present on M1 macOS, similarly to AES */ - return true; -#else - return false; -#endif -} - -bool platform_sha1_hw_available(void) -{ -#if defined HWCAP_SHA1 - return getauxval(AT_HWCAP) & HWCAP_SHA1; -#elif defined HWCAP2_SHA1 - return getauxval(AT_HWCAP2) & HWCAP2_SHA1; -#elif defined __APPLE__ - /* Assume always present on M1 macOS, similarly to AES */ - return true; -#else - return false; -#endif -} - -bool platform_sha512_hw_available(void) -{ -#if defined HWCAP_SHA512 - return getauxval(AT_HWCAP) & HWCAP_SHA512; -#elif defined HWCAP2_SHA512 - return getauxval(AT_HWCAP2) & HWCAP2_SHA512; -#elif defined __APPLE__ - return test_sysctl_flag("hw.optional.armv8_2_sha512"); -#else - return false; -#endif -} - -#endif /* defined __arm__ || defined __aarch64__ */ diff --git a/unix/uxutils.h b/unix/uxutils.h deleted file mode 100644 index fcb7e9b7..00000000 --- a/unix/uxutils.h +++ /dev/null @@ -1,65 +0,0 @@ -/* - * uxutils.h: header included only by uxutils.c. - * - * The only reason this is a header file instead of a source file is - * so that I can define 'static inline' functions which may or may not - * be used, without provoking a compiler warning when I turn out not - * to use them in the subsequent source file. - */ - -#ifndef PUTTY_UXUTILS_H -#define PUTTY_UXUTILS_H - -#if defined __APPLE__ -#if HAVE_SYS_SYSCTL_H -#include -#endif -#endif /* defined __APPLE__ */ - -#if defined __arm__ || defined __aarch64__ - -#if HAVE_SYS_TYPES_H -#include -#endif - -#if HAVE_SYS_AUXV_H -#include -#endif - -#if HAVE_ASM_HWCAP_H -#include -#endif - -#if HAVE_GETAUXVAL -/* No code needed: getauxval has just the API we want already */ -#elif HAVE_ELF_AUX_INFO -/* Implement the simple getauxval API in terms of FreeBSD elf_aux_info */ -static inline u_long getauxval(int which) -{ - u_long toret; - if (elf_aux_info(which, &toret, sizeof(toret)) != 0) - return 0; /* elf_aux_info didn't work */ - return toret; -} -#else -/* Implement a stub getauxval which returns no capabilities */ -static inline u_long getauxval(int which) { return 0; } -#endif - -#endif /* defined __arm__ || defined __aarch64__ */ - -#if defined __APPLE__ -static inline bool test_sysctl_flag(const char *flagname) -{ -#if HAVE_SYSCTLBYNAME - int value; - size_t size = sizeof(value); - return (sysctlbyname(flagname, &value, &size, NULL, 0) == 0 && - size == sizeof(value) && value != 0); -#else /* HAVE_SYSCTLBYNAME */ - return false; -#endif /* HAVE_SYSCTLBYNAME */ -} -#endif /* defined __APPLE__ */ - -#endif /* PUTTY_UXUTILS_H */ diff --git a/unix/x11misc.c b/unix/x11misc.c deleted file mode 100644 index e1fd1906..00000000 --- a/unix/x11misc.c +++ /dev/null @@ -1,83 +0,0 @@ -/* - * x11misc.c: miscellaneous stuff for dealing directly with X servers. - */ - -#include -#include -#include -#include -#include - -#include "putty.h" - -#ifndef NOT_X_WINDOWS - -#include -#include -#include - -#include "x11misc.h" - -/* ---------------------------------------------------------------------- - * Error handling mechanism which permits us to ignore specific X11 - * errors from particular requests. We maintain a list of upcoming - * potential error events that we want to not treat as fatal errors. - */ - -static int (*orig_x11_error_handler)(Display *thisdisp, XErrorEvent *err); - -struct x11_err_to_ignore { - Display *display; - unsigned char error_code; - unsigned long serial; -}; - -static struct x11_err_to_ignore *errs; -static size_t nerrs, errsize; - -static int x11_error_handler(Display *thisdisp, XErrorEvent *err) -{ - for (size_t i = 0; i < nerrs; i++) { - if (thisdisp == errs[i].display && - err->serial == errs[i].serial && - err->error_code == errs[i].error_code) { - /* Ok, this is an error we're happy to ignore */ - return 0; - } - } - - return (*orig_x11_error_handler)(thisdisp, err); -} - -void x11_ignore_error(Display *disp, unsigned char errcode) -{ - /* - * Install our error handler, if we haven't already. - */ - if (!orig_x11_error_handler) - orig_x11_error_handler = XSetErrorHandler(x11_error_handler); - - /* - * This is as good a moment as any to winnow the ignore list based - * on requests we know to have been processed. - */ - { - unsigned long last = LastKnownRequestProcessed(disp); - size_t i, j; - for (i = j = 0; i < nerrs; i++) { - if (errs[i].display == disp && errs[i].serial <= last) - continue; - errs[j++] = errs[i]; - } - nerrs = j; - } - - sgrowarray(errs, errsize, nerrs); - errs[nerrs].display = disp; - errs[nerrs].error_code = errcode; - errs[nerrs].serial = NextRequest(disp); - nerrs++; -} - -#endif - diff --git a/unix/xkeysym.c b/unix/xkeysym.c deleted file mode 100644 index aa9f9539..00000000 --- a/unix/xkeysym.c +++ /dev/null @@ -1,1011 +0,0 @@ -/* - * 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; -} diff --git a/utils.c b/utils.c deleted file mode 100644 index 30b326ee..00000000 --- a/utils.c +++ /dev/null @@ -1,1122 +0,0 @@ -/* - * Platform-independent utility routines used throughout this code base. - * - * This file is linked into stand-alone test utilities which only want - * to include the things they really need, so functions in here should - * avoid depending on any functions outside it. Utility routines that - * are more tightly integrated into the main code should live in - * misc.c. - */ - -#include -#include -#include -#include -#include -#include - -#include "defs.h" -#include "misc.h" -#include "ssh.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 - * 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; -} - -/* - * Find a character in a string, unless it's a colon contained within - * square brackets. Used for untangling strings of the form - * 'host:port', where host can be an IPv6 literal. - * - * We provide several variants of this function, with semantics like - * various standard string.h functions. - */ -static const char *host_strchr_internal(const char *s, const char *set, - bool first) -{ - int brackets = 0; - const char *ret = NULL; - - while (1) { - if (!*s) - return ret; - - if (*s == '[') - brackets++; - else if (*s == ']' && brackets > 0) - brackets--; - else if (brackets && *s == ':') - /* never match */ ; - else if (strchr(set, *s)) { - ret = s; - if (first) - return ret; - } - - s++; - } -} -size_t host_strcspn(const char *s, const char *set) -{ - const char *answer = host_strchr_internal(s, set, true); - if (answer) - return answer - s; - else - return strlen(s); -} -char *host_strchr(const char *s, int c) -{ - char set[2]; - set[0] = c; - set[1] = '\0'; - return (char *) host_strchr_internal(s, set, true); -} -char *host_strrchr(const char *s, int c) -{ - char set[2]; - set[0] = c; - set[1] = '\0'; - return (char *) host_strchr_internal(s, set, false); -} - -#ifdef TEST_HOST_STRFOO -int main(void) -{ - int passes = 0, fails = 0; - -#define TEST1(func, string, arg2, suffix, result) do \ - { \ - const char *str = string; \ - unsigned ret = func(string, arg2) suffix; \ - if (ret == result) { \ - passes++; \ - } else { \ - printf("fail: %s(%s,%s)%s = %u, expected %u\n", \ - #func, #string, #arg2, #suffix, ret, \ - (unsigned)result); \ - fails++; \ - } \ -} while (0) - - TEST1(host_strchr, "[1:2:3]:4:5", ':', -str, 7); - TEST1(host_strrchr, "[1:2:3]:4:5", ':', -str, 9); - TEST1(host_strcspn, "[1:2:3]:4:5", "/:",, 7); - TEST1(host_strchr, "[1:2:3]", ':', == NULL, 1); - TEST1(host_strrchr, "[1:2:3]", ':', == NULL, 1); - TEST1(host_strcspn, "[1:2:3]", "/:",, 7); - TEST1(host_strcspn, "[1:2/3]", "/:",, 4); - TEST1(host_strcspn, "[1:2:3]/", "/:",, 7); - - printf("passed %d failed %d total %d\n", passes, fails, passes+fails); - return fails != 0 ? 1 : 0; -} - -/* Stubs to stop the rest of this module causing compile failures. */ -static NORETURN void fatal_error(const char *p, ...) -{ - va_list ap; - fprintf(stderr, "host_string_test: "); - va_start(ap, p); - vfprintf(stderr, p, ap); - va_end(ap); - fputc('\n', stderr); - exit(1); -} - -void out_of_memory(void) { fatal_error("out of memory"); } - -#endif /* TEST_HOST_STRFOO */ - -/* - * Trim square brackets off the outside of an IPv6 address literal. - * Leave all other strings unchanged. Returns a fresh dynamically - * allocated string. - */ -char *host_strduptrim(const char *s) -{ - if (s[0] == '[') { - const char *p = s+1; - int colons = 0; - while (*p && *p != ']') { - if (isxdigit((unsigned char)*p)) - /* OK */; - else if (*p == ':') - colons++; - else - break; - p++; - } - if (*p == '%') { - /* - * This delimiter character introduces an RFC 4007 scope - * id suffix (e.g. suffixing the address literal with - * %eth1 or %2 or some such). There's no syntax - * specification for the scope id, so just accept anything - * except the closing ]. - */ - p += strcspn(p, "]"); - } - if (*p == ']' && !p[1] && colons > 1) { - /* - * This looks like an IPv6 address literal (hex digits and - * at least two colons, plus optional scope id, contained - * in square brackets). Trim off the brackets. - */ - return dupprintf("%.*s", (int)(p - (s+1)), s+1); - } - } - - /* - * Any other shape of string is simply duplicated. - */ - return dupstr(s); -} - -/* ---------------------------------------------------------------------- - * 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_fn(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; -} - -void burnstr(char *string) /* sfree(str), only clear it first */ -{ - if (string) { - smemclr(string, strlen(string)); - sfree(string); - } -} - -int string_length_for_printf(size_t s) -{ - /* Truncate absurdly long strings (should one show up) to fit - * within a positive 'int', which is what the "%.*s" format will - * expect. */ - if (s > INT_MAX) - return INT_MAX; - return s; -} - -/* Work around lack of va_copy in old MSC */ -#if defined _MSC_VER && !defined va_copy -#define va_copy(a, b) TYPECHECK( \ - (va_list *)0 == &(a) && (va_list *)0 == &(b), \ - memcpy(&a, &b, sizeof(va_list))) -#endif - -/* Also lack of vsnprintf before VS2015 */ -#if defined _WINDOWS && \ - !defined __MINGW32__ && \ - !defined __WINE__ && \ - _MSC_VER < 1900 -#define vsnprintf _vsnprintf -#endif - -/* - * 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 , 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. - */ -static char *dupvprintf_inner(char *buf, size_t oldlen, size_t *sizeptr, - const char *fmt, va_list ap) -{ - size_t size = *sizeptr; - sgrowarrayn_nm(buf, size, oldlen, 512); - - while (1) { - va_list aq; - va_copy(aq, ap); - int len = vsnprintf(buf + oldlen, size - oldlen, fmt, aq); - va_end(aq); - - if (len >= 0 && len < size) { - /* This is the C99-specified criterion for snprintf to have - * been completely successful. */ - *sizeptr = size; - return buf; - } else if (len > 0) { - /* This is the C99 error condition: the returned length is - * the required buffer size not counting the NUL. */ - sgrowarrayn_nm(buf, size, oldlen + 1, len); - } 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. */ - sgrowarray_nm(buf, size, size); - } - } -} - -char *dupvprintf(const char *fmt, va_list ap) -{ - size_t size = 0; - return dupvprintf_inner(NULL, 0, &size, fmt, ap); -} -char *dupprintf(const char *fmt, ...) -{ - char *ret; - va_list ap; - va_start(ap, fmt); - ret = dupvprintf(fmt, ap); - va_end(ap); - return ret; -} - -struct strbuf_impl { - size_t size; - struct strbuf visible; - bool nm; /* true if we insist on non-moving buffer resizes */ -}; - -#define STRBUF_SET_UPTR(buf) \ - ((buf)->visible.u = (unsigned char *)(buf)->visible.s) -#define STRBUF_SET_PTR(buf, ptr) \ - ((buf)->visible.s = (ptr), STRBUF_SET_UPTR(buf)) - -void *strbuf_append(strbuf *buf_o, size_t len) -{ - struct strbuf_impl *buf = container_of(buf_o, struct strbuf_impl, visible); - char *toret; - sgrowarray_general( - buf->visible.s, buf->size, buf->visible.len + 1, len, buf->nm); - STRBUF_SET_UPTR(buf); - toret = buf->visible.s + buf->visible.len; - buf->visible.len += len; - buf->visible.s[buf->visible.len] = '\0'; - return toret; -} - -void strbuf_shrink_to(strbuf *buf, size_t new_len) -{ - assert(new_len <= buf->len); - buf->len = new_len; - buf->s[buf->len] = '\0'; -} - -void strbuf_shrink_by(strbuf *buf, size_t amount_to_remove) -{ - assert(amount_to_remove <= buf->len); - buf->len -= amount_to_remove; - buf->s[buf->len] = '\0'; -} - -bool strbuf_chomp(strbuf *buf, char char_to_remove) -{ - if (buf->len > 0 && buf->s[buf->len-1] == char_to_remove) { - strbuf_shrink_by(buf, 1); - return true; - } - return false; -} - -static void strbuf_BinarySink_write( - BinarySink *bs, const void *data, size_t len) -{ - strbuf *buf_o = BinarySink_DOWNCAST(bs, strbuf); - memcpy(strbuf_append(buf_o, len), data, len); -} - -static strbuf *strbuf_new_general(bool nm) -{ - struct strbuf_impl *buf = snew(struct strbuf_impl); - BinarySink_INIT(&buf->visible, strbuf_BinarySink_write); - buf->visible.len = 0; - buf->size = 512; - buf->nm = nm; - STRBUF_SET_PTR(buf, snewn(buf->size, char)); - *buf->visible.s = '\0'; - return &buf->visible; -} -strbuf *strbuf_new(void) { return strbuf_new_general(false); } -strbuf *strbuf_new_nm(void) { return strbuf_new_general(true); } -void strbuf_free(strbuf *buf_o) -{ - struct strbuf_impl *buf = container_of(buf_o, struct strbuf_impl, visible); - if (buf->visible.s) { - smemclr(buf->visible.s, buf->size); - sfree(buf->visible.s); - } - sfree(buf); -} -char *strbuf_to_str(strbuf *buf_o) -{ - struct strbuf_impl *buf = container_of(buf_o, struct strbuf_impl, visible); - char *ret = buf->visible.s; - sfree(buf); - return ret; -} -void strbuf_catfv(strbuf *buf_o, const char *fmt, va_list ap) -{ - struct strbuf_impl *buf = container_of(buf_o, struct strbuf_impl, visible); - STRBUF_SET_PTR(buf, dupvprintf_inner(buf->visible.s, buf->visible.len, - &buf->size, fmt, ap)); - buf->visible.len += strlen(buf->visible.s + buf->visible.len); -} -void strbuf_catf(strbuf *buf_o, const char *fmt, ...) -{ - va_list ap; - va_start(ap, fmt); - strbuf_catfv(buf_o, fmt, ap); - va_end(ap); -} - -strbuf *strbuf_new_for_agent_query(void) -{ - strbuf *buf = strbuf_new(); - strbuf_append(buf, 4); - return buf; -} -void strbuf_finalise_agent_query(strbuf *buf_o) -{ - struct strbuf_impl *buf = container_of(buf_o, struct strbuf_impl, visible); - assert(buf->visible.len >= 5); - PUT_32BIT_MSB_FIRST(buf->visible.u, buf->visible.len - 4); -} - -/* - * 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); - size_t size = 512, len = 0; - while (fgets(ret + len, size - len, fp)) { - len += strlen(ret + len); - if (len > 0 && ret[len-1] == '\n') - break; /* got a newline, we're done */ - sgrowarrayn_nm(ret, size, len, 512); - } - if (len == 0) { /* first fgets returned NULL */ - sfree(ret); - return NULL; - } - ret[len] = '\0'; - return ret; -} - -/* - * Read an entire file into a BinarySink. - */ -bool read_file_into(BinarySink *bs, FILE *fp) -{ - char buf[4096]; - while (1) { - size_t retd = fread(buf, 1, sizeof(buf), fp); - if (retd == 0) - return !ferror(fp); - put_data(bs, buf, retd); - } -} - -/* - * Perl-style 'chomp', for a line we just read with fgetline. Unlike - * Perl chomp, however, we're deliberately forgiving of strange - * line-ending conventions. Also we forgive NULL on input, so you can - * just write 'line = chomp(fgetline(fp));' and not bother checking - * for NULL until afterwards. - */ -char *chomp(char *str) -{ - if (str) { - int len = strlen(str); - while (len > 0 && (str[len-1] == '\r' || str[len-1] == '\n')) - len--; - str[len] = '\0'; - } - return str; -} - -/* ---------------------------------------------------------------------- - * Core base64 encoding and decoding routines. - */ - -void base64_encode_atom(const 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] = '='; -} - -int base64_decode_atom(const 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; -} - -/* ---------------------------------------------------------------------- - * 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_MIN_GRANULE 512 - -struct bufchain_granule { - struct bufchain_granule *next; - char *bufpos, *bufend, *bufmax; -}; - -static void uninitialised_queue_idempotent_callback(IdempotentCallback *ic) -{ - unreachable("bufchain callback used while uninitialised"); -} - -void bufchain_init(bufchain *ch) -{ - ch->head = ch->tail = NULL; - ch->buffersize = 0; - ch->ic = NULL; - ch->queue_idempotent_callback = uninitialised_queue_idempotent_callback; -} - -void bufchain_clear(bufchain *ch) -{ - struct bufchain_granule *b; - while (ch->head) { - b = ch->head; - ch->head = ch->head->next; - smemclr(b, sizeof(*b)); - sfree(b); - } - ch->tail = NULL; - ch->buffersize = 0; -} - -size_t bufchain_size(bufchain *ch) -{ - return ch->buffersize; -} - -void bufchain_set_callback_inner( - bufchain *ch, IdempotentCallback *ic, - void (*queue_idempotent_callback)(IdempotentCallback *ic)) -{ - ch->queue_idempotent_callback = queue_idempotent_callback; - ch->ic = ic; -} - -void bufchain_add(bufchain *ch, const void *data, size_t len) -{ - const char *buf = (const char *)data; - - if (len == 0) return; - - ch->buffersize += len; - - while (len > 0) { - if (ch->tail && ch->tail->bufend < ch->tail->bufmax) { - size_t copylen = min(len, ch->tail->bufmax - ch->tail->bufend); - memcpy(ch->tail->bufend, buf, copylen); - buf += copylen; - len -= copylen; - ch->tail->bufend += copylen; - } - if (len > 0) { - size_t grainlen = - max(sizeof(struct bufchain_granule) + len, BUFFER_MIN_GRANULE); - struct bufchain_granule *newbuf; - newbuf = smalloc(grainlen); - newbuf->bufpos = newbuf->bufend = - (char *)newbuf + sizeof(struct bufchain_granule); - newbuf->bufmax = (char *)newbuf + grainlen; - newbuf->next = NULL; - if (ch->tail) - ch->tail->next = newbuf; - else - ch->head = newbuf; - ch->tail = newbuf; - } - } - - if (ch->ic) - ch->queue_idempotent_callback(ch->ic); -} - -void bufchain_consume(bufchain *ch, size_t len) -{ - struct bufchain_granule *tmp; - - assert(ch->buffersize >= len); - while (len > 0) { - int remlen = len; - assert(ch->head != NULL); - if (remlen >= ch->head->bufend - ch->head->bufpos) { - remlen = ch->head->bufend - ch->head->bufpos; - tmp = ch->head; - ch->head = tmp->next; - if (!ch->head) - ch->tail = NULL; - smemclr(tmp, sizeof(*tmp)); - sfree(tmp); - } else - ch->head->bufpos += remlen; - ch->buffersize -= remlen; - len -= remlen; - } -} - -ptrlen bufchain_prefix(bufchain *ch) -{ - return make_ptrlen(ch->head->bufpos, ch->head->bufend - ch->head->bufpos); -} - -void bufchain_fetch(bufchain *ch, void *data, size_t 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->bufend - tmp->bufpos) - remlen = tmp->bufend - tmp->bufpos; - memcpy(data_c, tmp->bufpos, remlen); - - tmp = tmp->next; - len -= remlen; - data_c += remlen; - } -} - -void bufchain_fetch_consume(bufchain *ch, void *data, size_t len) -{ - bufchain_fetch(ch, data, len); - bufchain_consume(ch, len); -} - -bool bufchain_try_fetch_consume(bufchain *ch, void *data, size_t len) -{ - if (ch->buffersize >= len) { - bufchain_fetch_consume(ch, data, len); - return true; - } else { - return false; - } -} - -size_t bufchain_fetch_consume_up_to(bufchain *ch, void *data, size_t len) -{ - if (len > ch->buffersize) - len = ch->buffersize; - if (len) - bufchain_fetch_consume(ch, data, len); - return len; -} - -/* ---------------------------------------------------------------------- - * Debugging routines. - */ - -#ifdef DEBUG -extern void dputs(const char *); /* defined in per-platform *misc.c */ - -void debug_printf(const 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(const void *buf, int len, bool L) -{ - int i; - const unsigned char *p = buf; - char foo[17]; - if (L) { - int delta; - debug_printf("\t%d (0x%x) bytes:\n", len, len); - delta = 15 & (uintptr_t)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%2.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 */ - -#ifndef PLATFORM_HAS_SMEMCLR -/* - * Securely wipe memory. - * - * The actual wiping is no different from what memset would do: the - * point of 'securely' is to try to be sure over-clever compilers - * won't optimise away memsets on variables that are about to be freed - * or go out of scope. See - * https://buildsecurityin.us-cert.gov/bsi-rules/home/g1/771-BSI.html - * - * Some platforms (e.g. Windows) may provide their own version of this - * function. - */ -void smemclr(void *b, size_t n) { - volatile char *vp; - - if (b && n > 0) { - /* - * Zero out the memory. - */ - memset(b, 0, n); - - /* - * Perform a volatile access to the object, forcing the - * compiler to admit that the previous memset was important. - * - * This while loop should in practice run for zero iterations - * (since we know we just zeroed the object out), but in - * theory (as far as the compiler knows) it might range over - * the whole object. (If we had just written, say, '*vp = - * *vp;', a compiler could in principle have 'helpfully' - * optimised the memset into only zeroing out the first byte. - * This should be robust.) - */ - vp = b; - while (*vp) vp++; - } -} -#endif - -bool smemeq(const void *av, const void *bv, size_t len) -{ - const unsigned char *a = (const unsigned char *)av; - const unsigned char *b = (const unsigned char *)bv; - unsigned val = 0; - - while (len-- > 0) { - val |= *a++ ^ *b++; - } - /* Now val is 0 iff we want to return 1, and in the range - * 0x01..0xFF iff we want to return 0. So subtracting from 0x100 - * will clear bit 8 iff we want to return 0, and leave it set iff - * we want to return 1, so then we can just shift down. */ - return (0x100 - val) >> 8; -} - -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); -} - -bool ptrlen_eq_string(ptrlen pl, const char *str) -{ - size_t len = strlen(str); - return (pl.len == len && !memcmp(pl.ptr, str, len)); -} - -bool ptrlen_eq_ptrlen(ptrlen pl1, ptrlen pl2) -{ - return (pl1.len == pl2.len && !memcmp(pl1.ptr, pl2.ptr, pl1.len)); -} - -int ptrlen_strcmp(ptrlen pl1, ptrlen pl2) -{ - size_t minlen = pl1.len < pl2.len ? pl1.len : pl2.len; - if (minlen) { /* tolerate plX.ptr==NULL as long as plX.len==0 */ - int cmp = memcmp(pl1.ptr, pl2.ptr, minlen); - if (cmp) - return cmp; - } - return pl1.len < pl2.len ? -1 : pl1.len > pl2.len ? +1 : 0; -} - -bool ptrlen_startswith(ptrlen whole, ptrlen prefix, ptrlen *tail) -{ - if (whole.len >= prefix.len && - !memcmp(whole.ptr, prefix.ptr, prefix.len)) { - if (tail) { - tail->ptr = (const char *)whole.ptr + prefix.len; - tail->len = whole.len - prefix.len; - } - return true; - } - return false; -} - -bool ptrlen_endswith(ptrlen whole, ptrlen suffix, ptrlen *tail) -{ - if (whole.len >= suffix.len && - !memcmp((char *)whole.ptr + (whole.len - suffix.len), - suffix.ptr, suffix.len)) { - if (tail) { - tail->ptr = whole.ptr; - tail->len = whole.len - suffix.len; - } - return true; - } - return false; -} - -ptrlen ptrlen_get_word(ptrlen *input, const char *separators) -{ - const char *p = input->ptr, *end = p + input->len; - ptrlen toret; - - while (p < end && strchr(separators, *p)) - p++; - toret.ptr = p; - while (p < end && !strchr(separators, *p)) - p++; - toret.len = p - (const char *)toret.ptr; - - size_t to_consume = p - (const char *)input->ptr; - assert(to_consume <= input->len); - input->ptr = (const char *)input->ptr + to_consume; - input->len -= to_consume; - - return toret; -} - -char *mkstr(ptrlen pl) -{ - char *p = snewn(pl.len + 1, char); - memcpy(p, pl.ptr, pl.len); - p[pl.len] = '\0'; - return p; -} - -bool strstartswith(const char *s, const char *t) -{ - return !strncmp(s, t, strlen(t)); -} - -bool strendswith(const char *s, const char *t) -{ - size_t slen = strlen(s), tlen = strlen(t); - return slen >= tlen && !strcmp(s + (slen - tlen), t); -} - -size_t encode_utf8(void *output, unsigned long ch) -{ - unsigned char *start = (unsigned char *)output, *p = start; - - if (ch < 0x80) { - *p++ = ch; - } else if (ch < 0x800) { - *p++ = 0xC0 | (ch >> 6); - *p++ = 0x80 | (ch & 0x3F); - } else if (ch < 0x10000) { - *p++ = 0xE0 | (ch >> 12); - *p++ = 0x80 | ((ch >> 6) & 0x3F); - *p++ = 0x80 | (ch & 0x3F); - } else { - *p++ = 0xF0 | (ch >> 18); - *p++ = 0x80 | ((ch >> 12) & 0x3F); - *p++ = 0x80 | ((ch >> 6) & 0x3F); - *p++ = 0x80 | (ch & 0x3F); - } - return p - start; -} - -void write_c_string_literal(FILE *fp, ptrlen str) -{ - for (const char *p = str.ptr; p < (const char *)str.ptr + str.len; p++) { - char c = *p; - - if (c == '\n') - fputs("\\n", fp); - else if (c == '\r') - fputs("\\r", fp); - else if (c == '\t') - fputs("\\t", fp); - else if (c == '\b') - fputs("\\b", fp); - else if (c == '\\') - fputs("\\\\", fp); - else if (c == '"') - fputs("\\\"", fp); - else if (c >= 32 && c <= 126) - fputc(c, fp); - else - fprintf(fp, "\\%03o", (unsigned char)c); - } -} - -void memxor(uint8_t *out, const uint8_t *in1, const uint8_t *in2, size_t size) -{ - switch (size & 15) { - case 0: - while (size >= 16) { - size -= 16; - *out++ = *in1++ ^ *in2++; - case 15: *out++ = *in1++ ^ *in2++; - case 14: *out++ = *in1++ ^ *in2++; - case 13: *out++ = *in1++ ^ *in2++; - case 12: *out++ = *in1++ ^ *in2++; - case 11: *out++ = *in1++ ^ *in2++; - case 10: *out++ = *in1++ ^ *in2++; - case 9: *out++ = *in1++ ^ *in2++; - case 8: *out++ = *in1++ ^ *in2++; - case 7: *out++ = *in1++ ^ *in2++; - case 6: *out++ = *in1++ ^ *in2++; - case 5: *out++ = *in1++ ^ *in2++; - case 4: *out++ = *in1++ ^ *in2++; - case 3: *out++ = *in1++ ^ *in2++; - case 2: *out++ = *in1++ ^ *in2++; - case 1: *out++ = *in1++ ^ *in2++; - } - } -} - -FingerprintType ssh2_pick_fingerprint( - char **fingerprints, FingerprintType preferred_type) -{ - /* - * Keys are either SSH-2, in which case we have all fingerprint - * types, or SSH-1, in which case we have only MD5. So we return - * the default type if we can, or MD5 if that's all we have; no - * need for a fully general preference-list system. - */ - FingerprintType fptype = fingerprints[preferred_type] ? - preferred_type : SSH_FPTYPE_MD5; - assert(fingerprints[fptype]); - return fptype; -} - -FingerprintType ssh2_pick_default_fingerprint(char **fingerprints) -{ - return ssh2_pick_fingerprint(fingerprints, SSH_FPTYPE_DEFAULT); -} diff --git a/utils/base64_decode_atom.c b/utils/base64_decode_atom.c new file mode 100644 index 00000000..2a98150e --- /dev/null +++ b/utils/base64_decode_atom.c @@ -0,0 +1,54 @@ +/* + * Core routine to decode a single atomic base64 chunk. + */ + +#include "defs.h" +#include "misc.h" + +int base64_decode_atom(const 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; +} diff --git a/utils/base64_encode_atom.c b/utils/base64_encode_atom.c new file mode 100644 index 00000000..c33476f0 --- /dev/null +++ b/utils/base64_encode_atom.c @@ -0,0 +1,30 @@ +/* + * Core routine to encode a single atomic base64 chunk. + */ + +#include "defs.h" +#include "misc.h" + +void base64_encode_atom(const 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] = '='; +} diff --git a/utils/bufchain.c b/utils/bufchain.c new file mode 100644 index 00000000..60ebc2be --- /dev/null +++ b/utils/bufchain.c @@ -0,0 +1,173 @@ +/* + * 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 + */ + +#include "defs.h" +#include "misc.h" + +#define BUFFER_MIN_GRANULE 512 + +struct bufchain_granule { + struct bufchain_granule *next; + char *bufpos, *bufend, *bufmax; +}; + +static void uninitialised_queue_idempotent_callback(IdempotentCallback *ic) +{ + unreachable("bufchain callback used while uninitialised"); +} + +void bufchain_init(bufchain *ch) +{ + ch->head = ch->tail = NULL; + ch->buffersize = 0; + ch->ic = NULL; + ch->queue_idempotent_callback = uninitialised_queue_idempotent_callback; +} + +void bufchain_clear(bufchain *ch) +{ + struct bufchain_granule *b; + while (ch->head) { + b = ch->head; + ch->head = ch->head->next; + smemclr(b, sizeof(*b)); + sfree(b); + } + ch->tail = NULL; + ch->buffersize = 0; +} + +size_t bufchain_size(bufchain *ch) +{ + return ch->buffersize; +} + +void bufchain_set_callback_inner( + bufchain *ch, IdempotentCallback *ic, + void (*queue_idempotent_callback)(IdempotentCallback *ic)) +{ + ch->queue_idempotent_callback = queue_idempotent_callback; + ch->ic = ic; +} + +void bufchain_add(bufchain *ch, const void *data, size_t len) +{ + const char *buf = (const char *)data; + + if (len == 0) return; + + ch->buffersize += len; + + while (len > 0) { + if (ch->tail && ch->tail->bufend < ch->tail->bufmax) { + size_t copylen = min(len, ch->tail->bufmax - ch->tail->bufend); + memcpy(ch->tail->bufend, buf, copylen); + buf += copylen; + len -= copylen; + ch->tail->bufend += copylen; + } + if (len > 0) { + size_t grainlen = + max(sizeof(struct bufchain_granule) + len, BUFFER_MIN_GRANULE); + struct bufchain_granule *newbuf; + newbuf = smalloc(grainlen); + newbuf->bufpos = newbuf->bufend = + (char *)newbuf + sizeof(struct bufchain_granule); + newbuf->bufmax = (char *)newbuf + grainlen; + newbuf->next = NULL; + if (ch->tail) + ch->tail->next = newbuf; + else + ch->head = newbuf; + ch->tail = newbuf; + } + } + + if (ch->ic) + ch->queue_idempotent_callback(ch->ic); +} + +void bufchain_consume(bufchain *ch, size_t len) +{ + struct bufchain_granule *tmp; + + assert(ch->buffersize >= len); + while (len > 0) { + int remlen = len; + assert(ch->head != NULL); + if (remlen >= ch->head->bufend - ch->head->bufpos) { + remlen = ch->head->bufend - ch->head->bufpos; + tmp = ch->head; + ch->head = tmp->next; + if (!ch->head) + ch->tail = NULL; + smemclr(tmp, sizeof(*tmp)); + sfree(tmp); + } else + ch->head->bufpos += remlen; + ch->buffersize -= remlen; + len -= remlen; + } +} + +ptrlen bufchain_prefix(bufchain *ch) +{ + return make_ptrlen(ch->head->bufpos, ch->head->bufend - ch->head->bufpos); +} + +void bufchain_fetch(bufchain *ch, void *data, size_t 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->bufend - tmp->bufpos) + remlen = tmp->bufend - tmp->bufpos; + memcpy(data_c, tmp->bufpos, remlen); + + tmp = tmp->next; + len -= remlen; + data_c += remlen; + } +} + +void bufchain_fetch_consume(bufchain *ch, void *data, size_t len) +{ + bufchain_fetch(ch, data, len); + bufchain_consume(ch, len); +} + +bool bufchain_try_fetch_consume(bufchain *ch, void *data, size_t len) +{ + if (ch->buffersize >= len) { + bufchain_fetch_consume(ch, data, len); + return true; + } else { + return false; + } +} + +size_t bufchain_fetch_consume_up_to(bufchain *ch, void *data, size_t len) +{ + if (len > ch->buffersize) + len = ch->buffersize; + if (len) + bufchain_fetch_consume(ch, data, len); + return len; +} diff --git a/utils/buildinfo.c b/utils/buildinfo.c new file mode 100644 index 00000000..1db65a0d --- /dev/null +++ b/utils/buildinfo.c @@ -0,0 +1,158 @@ +/* + * Return a string describing everything we know about how this + * particular binary was built: from what source, for what target + * platform, using what tools, with what settings, etc. + */ + +#include "putty.h" + +char *buildinfo(const char *newline) +{ + strbuf *buf = strbuf_new(); + + strbuf_catf(buf, "Build platform: %d-bit %s", + (int)(CHAR_BIT * sizeof(void *)), + BUILDINFO_PLATFORM); + +#ifdef __clang_version__ +#define FOUND_COMPILER + strbuf_catf(buf, "%sCompiler: clang %s", newline, __clang_version__); +#elif defined __GNUC__ && defined __VERSION__ +#define FOUND_COMPILER + strbuf_catf(buf, "%sCompiler: gcc %s", newline, __VERSION__); +#endif + +#if defined _MSC_VER +#ifndef FOUND_COMPILER +#define FOUND_COMPILER + strbuf_catf(buf, "%sCompiler: ", newline); +#else + strbuf_catf(buf, ", emulating "); +#endif + strbuf_catf(buf, "Visual Studio"); + +#if 0 + /* + * List of _MSC_VER values and their translations taken from + * https://docs.microsoft.com/en-us/cpp/preprocessor/predefined-macros + * + * The pointless #if 0 branch containing this comment is there so + * that every real clause can start with #elif and there's no + * anomalous first clause. That way the patch looks nicer when you + * add extra ones. + */ +#elif _MSC_VER == 1928 && _MSC_FULL_VER >= 192829500 + /* + * 16.9 and 16.8 have the same _MSC_VER value, and have to be + * distinguished by _MSC_FULL_VER. As of 2021-03-04 that is not + * mentioned on the above page, but see e.g. + * https://developercommunity.visualstudio.com/t/the-169-cc-compiler-still-uses-the-same-version-nu/1335194#T-N1337120 + * which says that 16.9 builds will have versions starting at + * 19.28.29500.* and going up. Hence, 19 28 29500 is what we + * compare _MSC_FULL_VER against above. + */ + strbuf_catf(buf, " 2019 (16.9)"); +#elif _MSC_VER == 1928 + strbuf_catf(buf, " 2019 (16.8)"); +#elif _MSC_VER == 1927 + strbuf_catf(buf, " 2019 (16.7)"); +#elif _MSC_VER == 1926 + strbuf_catf(buf, " 2019 (16.6)"); +#elif _MSC_VER == 1925 + strbuf_catf(buf, " 2019 (16.5)"); +#elif _MSC_VER == 1924 + strbuf_catf(buf, " 2019 (16.4)"); +#elif _MSC_VER == 1923 + strbuf_catf(buf, " 2019 (16.3)"); +#elif _MSC_VER == 1922 + strbuf_catf(buf, " 2019 (16.2)"); +#elif _MSC_VER == 1921 + strbuf_catf(buf, " 2019 (16.1)"); +#elif _MSC_VER == 1920 + strbuf_catf(buf, " 2019 (16.0)"); +#elif _MSC_VER == 1916 + strbuf_catf(buf, " 2017 version 15.9"); +#elif _MSC_VER == 1915 + strbuf_catf(buf, " 2017 version 15.8"); +#elif _MSC_VER == 1914 + strbuf_catf(buf, " 2017 version 15.7"); +#elif _MSC_VER == 1913 + strbuf_catf(buf, " 2017 version 15.6"); +#elif _MSC_VER == 1912 + strbuf_catf(buf, " 2017 version 15.5"); +#elif _MSC_VER == 1911 + strbuf_catf(buf, " 2017 version 15.3"); +#elif _MSC_VER == 1910 + strbuf_catf(buf, " 2017 RTW (15.0)"); +#elif _MSC_VER == 1900 + strbuf_catf(buf, " 2015 (14.0)"); +#elif _MSC_VER == 1800 + strbuf_catf(buf, " 2013 (12.0)"); +#elif _MSC_VER == 1700 + strbuf_catf(buf, " 2012 (11.0)"); +#elif _MSC_VER == 1600 + strbuf_catf(buf, " 2010 (10.0)"); +#elif _MSC_VER == 1500 + strbuf_catf(buf, " 2008 (9.0)"); +#elif _MSC_VER == 1400 + strbuf_catf(buf, " 2005 (8.0)"); +#elif _MSC_VER == 1310 + strbuf_catf(buf, " .NET 2003 (7.1)"); +#elif _MSC_VER == 1300 + strbuf_catf(buf, " .NET 2002 (7.0)"); +#elif _MSC_VER == 1200 + strbuf_catf(buf, " 6.0"); +#else + strbuf_catf(buf, ", unrecognised version"); +#endif + strbuf_catf(buf, ", _MSC_VER=%d", (int)_MSC_VER); +#endif + +#ifdef BUILDINFO_GTK + { + char *gtk_buildinfo = buildinfo_gtk_version(); + if (gtk_buildinfo) { + strbuf_catf(buf, "%sCompiled against GTK version %s", + newline, gtk_buildinfo); + sfree(gtk_buildinfo); + } + } +#endif +#if defined _WINDOWS + { + int echm = has_embedded_chm(); + if (echm >= 0) + strbuf_catf(buf, "%sEmbedded HTML Help file: %s", newline, + echm ? "yes" : "no"); + } +#endif + +#if defined _WINDOWS && defined MINEFIELD + strbuf_catf(buf, "%sBuild option: MINEFIELD", newline); +#endif +#ifdef NO_SECUREZEROMEMORY + strbuf_catf(buf, "%sBuild option: NO_SECUREZEROMEMORY", newline); +#endif +#ifdef NO_IPV6 + strbuf_catf(buf, "%sBuild option: NO_IPV6", newline); +#endif +#ifdef NO_GSSAPI + strbuf_catf(buf, "%sBuild option: NO_GSSAPI", newline); +#endif +#ifdef STATIC_GSSAPI + strbuf_catf(buf, "%sBuild option: STATIC_GSSAPI", newline); +#endif +#ifdef UNPROTECT + strbuf_catf(buf, "%sBuild option: UNPROTECT", newline); +#endif +#ifdef FUZZING + strbuf_catf(buf, "%sBuild option: FUZZING", newline); +#endif +#ifdef DEBUG + strbuf_catf(buf, "%sBuild option: DEBUG", newline); +#endif + + strbuf_catf(buf, "%sSource commit: %s", newline, commitid); + + return strbuf_to_str(buf); +} diff --git a/utils/burnstr.c b/utils/burnstr.c new file mode 100644 index 00000000..33214d89 --- /dev/null +++ b/utils/burnstr.c @@ -0,0 +1,15 @@ +/* + * 'Burn' a dynamically allocated string, in the sense of destroying + * it beyond recovery: overwrite it with zeroes and then free it. + */ + +#include "defs.h" +#include "misc.h" + +void burnstr(char *string) +{ + if (string) { + smemclr(string, strlen(string)); + sfree(string); + } +} diff --git a/utils/chomp.c b/utils/chomp.c new file mode 100644 index 00000000..866fc652 --- /dev/null +++ b/utils/chomp.c @@ -0,0 +1,26 @@ +/* + * Perl-style 'chomp', for a line we just read with fgetline. + * + * Unlike Perl chomp, however, we're deliberately forgiving of strange + * line-ending conventions. + * + * Also we forgive NULL on input, so you can just write 'line = + * chomp(fgetline(fp));' and not bother checking for NULL until + * afterwards. + */ + +#include + +#include "defs.h" +#include "misc.h" + +char *chomp(char *str) +{ + if (str) { + int len = strlen(str); + while (len > 0 && (str[len-1] == '\r' || str[len-1] == '\n')) + len--; + str[len] = '\0'; + } + return str; +} diff --git a/utils/conf.c b/utils/conf.c new file mode 100644 index 00000000..ecd26cd0 --- /dev/null +++ b/utils/conf.c @@ -0,0 +1,593 @@ +/* + * conf.c: implementation of the internal storage format used for + * the configuration of a PuTTY session. + */ + +#include +#include +#include + +#include "tree234.h" +#include "putty.h" + +/* + * Enumeration of types used in keys and values. + */ +typedef enum { + TYPE_NONE, TYPE_BOOL, TYPE_INT, TYPE_STR, TYPE_FILENAME, TYPE_FONT +} Type; + +/* + * Arrays which allow us to look up the subkey and value types for a + * given primary key id. + */ +#define CONF_SUBKEYTYPE_DEF(valtype, keytype, keyword) TYPE_ ## keytype, +static int subkeytypes[] = { CONFIG_OPTIONS(CONF_SUBKEYTYPE_DEF) }; +#define CONF_VALUETYPE_DEF(valtype, keytype, keyword) TYPE_ ## valtype, +static int valuetypes[] = { CONFIG_OPTIONS(CONF_VALUETYPE_DEF) }; + +/* + * Configuration keys are primarily integers (big enum of all the + * different configurable options); some keys have string-designated + * subkeys, such as the list of environment variables (subkeys + * defined by the variable names); some have integer-designated + * subkeys (wordness, colours, preference lists). + */ +struct key { + int primary; + union { + int i; + char *s; + } secondary; +}; + +/* Variant form of struct key which doesn't contain dynamic data, used + * for lookups. */ +struct constkey { + int primary; + union { + int i; + const char *s; + } secondary; +}; + +struct value { + union { + bool boolval; + int intval; + char *stringval; + Filename *fileval; + FontSpec *fontval; + } u; +}; + +struct conf_entry { + struct key key; + struct value value; +}; + +struct conf_tag { + tree234 *tree; +}; + +/* + * Because 'struct key' is the first element in 'struct conf_entry', + * it's safe (guaranteed by the C standard) to cast arbitrarily back + * and forth between the two types. Therefore, we only need one + * comparison function, which can double as a main sort function for + * the tree (comparing two conf_entry structures with each other) + * and a search function (looking up an externally supplied key). + */ +static int conf_cmp(void *av, void *bv) +{ + struct key *a = (struct key *)av; + struct key *b = (struct key *)bv; + + if (a->primary < b->primary) + return -1; + else if (a->primary > b->primary) + return +1; + switch (subkeytypes[a->primary]) { + case TYPE_INT: + if (a->secondary.i < b->secondary.i) + return -1; + else if (a->secondary.i > b->secondary.i) + return +1; + return 0; + case TYPE_STR: + return strcmp(a->secondary.s, b->secondary.s); + default: + return 0; + } +} + +static int conf_cmp_constkey(void *av, void *bv) +{ + struct key *a = (struct key *)av; + struct constkey *b = (struct constkey *)bv; + + if (a->primary < b->primary) + return -1; + else if (a->primary > b->primary) + return +1; + switch (subkeytypes[a->primary]) { + case TYPE_INT: + if (a->secondary.i < b->secondary.i) + return -1; + else if (a->secondary.i > b->secondary.i) + return +1; + return 0; + case TYPE_STR: + return strcmp(a->secondary.s, b->secondary.s); + default: + return 0; + } +} + +/* + * Free any dynamic data items pointed to by a 'struct key'. We + * don't free the structure itself, since it's probably part of a + * larger allocated block. + */ +static void free_key(struct key *key) +{ + if (subkeytypes[key->primary] == TYPE_STR) + sfree(key->secondary.s); +} + +/* + * Copy a 'struct key' into another one, copying its dynamic data + * if necessary. + */ +static void copy_key(struct key *to, struct key *from) +{ + to->primary = from->primary; + switch (subkeytypes[to->primary]) { + case TYPE_INT: + to->secondary.i = from->secondary.i; + break; + case TYPE_STR: + to->secondary.s = dupstr(from->secondary.s); + break; + } +} + +/* + * Free any dynamic data items pointed to by a 'struct value'. We + * don't free the value itself, since it's probably part of a larger + * allocated block. + */ +static void free_value(struct value *val, int type) +{ + if (type == TYPE_STR) + sfree(val->u.stringval); + else if (type == TYPE_FILENAME) + filename_free(val->u.fileval); + else if (type == TYPE_FONT) + fontspec_free(val->u.fontval); +} + +/* + * Copy a 'struct value' into another one, copying its dynamic data + * if necessary. + */ +static void copy_value(struct value *to, struct value *from, int type) +{ + switch (type) { + case TYPE_BOOL: + to->u.boolval = from->u.boolval; + break; + case TYPE_INT: + to->u.intval = from->u.intval; + break; + case TYPE_STR: + to->u.stringval = dupstr(from->u.stringval); + break; + case TYPE_FILENAME: + to->u.fileval = filename_copy(from->u.fileval); + break; + case TYPE_FONT: + to->u.fontval = fontspec_copy(from->u.fontval); + break; + } +} + +/* + * Free an entire 'struct conf_entry' and its dynamic data. + */ +static void free_entry(struct conf_entry *entry) +{ + free_key(&entry->key); + free_value(&entry->value, valuetypes[entry->key.primary]); + sfree(entry); +} + +Conf *conf_new(void) +{ + Conf *conf = snew(struct conf_tag); + + conf->tree = newtree234(conf_cmp); + + return conf; +} + +static void conf_clear(Conf *conf) +{ + struct conf_entry *entry; + + while ((entry = delpos234(conf->tree, 0)) != NULL) + free_entry(entry); +} + +void conf_free(Conf *conf) +{ + conf_clear(conf); + freetree234(conf->tree); + sfree(conf); +} + +static void conf_insert(Conf *conf, struct conf_entry *entry) +{ + struct conf_entry *oldentry = add234(conf->tree, entry); + if (oldentry && oldentry != entry) { + del234(conf->tree, oldentry); + free_entry(oldentry); + oldentry = add234(conf->tree, entry); + assert(oldentry == entry); + } +} + +void conf_copy_into(Conf *newconf, Conf *oldconf) +{ + struct conf_entry *entry, *entry2; + int i; + + conf_clear(newconf); + + for (i = 0; (entry = index234(oldconf->tree, i)) != NULL; i++) { + entry2 = snew(struct conf_entry); + copy_key(&entry2->key, &entry->key); + copy_value(&entry2->value, &entry->value, + valuetypes[entry->key.primary]); + add234(newconf->tree, entry2); + } +} + +Conf *conf_copy(Conf *oldconf) +{ + Conf *newconf = conf_new(); + + conf_copy_into(newconf, oldconf); + + return newconf; +} + +bool conf_get_bool(Conf *conf, int primary) +{ + struct key key; + struct conf_entry *entry; + + assert(subkeytypes[primary] == TYPE_NONE); + assert(valuetypes[primary] == TYPE_BOOL); + key.primary = primary; + entry = find234(conf->tree, &key, NULL); + assert(entry); + return entry->value.u.boolval; +} + +int conf_get_int(Conf *conf, int primary) +{ + struct key key; + struct conf_entry *entry; + + assert(subkeytypes[primary] == TYPE_NONE); + assert(valuetypes[primary] == TYPE_INT); + key.primary = primary; + entry = find234(conf->tree, &key, NULL); + assert(entry); + return entry->value.u.intval; +} + +int conf_get_int_int(Conf *conf, int primary, int secondary) +{ + struct key key; + struct conf_entry *entry; + + assert(subkeytypes[primary] == TYPE_INT); + assert(valuetypes[primary] == TYPE_INT); + key.primary = primary; + key.secondary.i = secondary; + entry = find234(conf->tree, &key, NULL); + assert(entry); + return entry->value.u.intval; +} + +char *conf_get_str(Conf *conf, int primary) +{ + struct key key; + struct conf_entry *entry; + + assert(subkeytypes[primary] == TYPE_NONE); + assert(valuetypes[primary] == TYPE_STR); + key.primary = primary; + entry = find234(conf->tree, &key, NULL); + assert(entry); + return entry->value.u.stringval; +} + +char *conf_get_str_str_opt(Conf *conf, int primary, const char *secondary) +{ + struct key key; + struct conf_entry *entry; + + assert(subkeytypes[primary] == TYPE_STR); + assert(valuetypes[primary] == TYPE_STR); + key.primary = primary; + key.secondary.s = (char *)secondary; + entry = find234(conf->tree, &key, NULL); + return entry ? entry->value.u.stringval : NULL; +} + +char *conf_get_str_str(Conf *conf, int primary, const char *secondary) +{ + char *ret = conf_get_str_str_opt(conf, primary, secondary); + assert(ret); + return ret; +} + +char *conf_get_str_strs(Conf *conf, int primary, + char *subkeyin, char **subkeyout) +{ + struct constkey key; + struct conf_entry *entry; + + assert(subkeytypes[primary] == TYPE_STR); + assert(valuetypes[primary] == TYPE_STR); + key.primary = primary; + if (subkeyin) { + key.secondary.s = subkeyin; + entry = findrel234(conf->tree, &key, NULL, REL234_GT); + } else { + key.secondary.s = ""; + entry = findrel234(conf->tree, &key, conf_cmp_constkey, REL234_GE); + } + if (!entry || entry->key.primary != primary) + return NULL; + *subkeyout = entry->key.secondary.s; + return entry->value.u.stringval; +} + +char *conf_get_str_nthstrkey(Conf *conf, int primary, int n) +{ + struct constkey key; + struct conf_entry *entry; + int index; + + assert(subkeytypes[primary] == TYPE_STR); + assert(valuetypes[primary] == TYPE_STR); + key.primary = primary; + key.secondary.s = ""; + entry = findrelpos234(conf->tree, &key, conf_cmp_constkey, + REL234_GE, &index); + if (!entry || entry->key.primary != primary) + return NULL; + entry = index234(conf->tree, index + n); + if (!entry || entry->key.primary != primary) + return NULL; + return entry->key.secondary.s; +} + +Filename *conf_get_filename(Conf *conf, int primary) +{ + struct key key; + struct conf_entry *entry; + + assert(subkeytypes[primary] == TYPE_NONE); + assert(valuetypes[primary] == TYPE_FILENAME); + key.primary = primary; + entry = find234(conf->tree, &key, NULL); + assert(entry); + return entry->value.u.fileval; +} + +FontSpec *conf_get_fontspec(Conf *conf, int primary) +{ + struct key key; + struct conf_entry *entry; + + assert(subkeytypes[primary] == TYPE_NONE); + assert(valuetypes[primary] == TYPE_FONT); + key.primary = primary; + entry = find234(conf->tree, &key, NULL); + assert(entry); + return entry->value.u.fontval; +} + +void conf_set_bool(Conf *conf, int primary, bool value) +{ + struct conf_entry *entry = snew(struct conf_entry); + + assert(subkeytypes[primary] == TYPE_NONE); + assert(valuetypes[primary] == TYPE_BOOL); + entry->key.primary = primary; + entry->value.u.boolval = value; + conf_insert(conf, entry); +} + +void conf_set_int(Conf *conf, int primary, int value) +{ + struct conf_entry *entry = snew(struct conf_entry); + + assert(subkeytypes[primary] == TYPE_NONE); + assert(valuetypes[primary] == TYPE_INT); + entry->key.primary = primary; + entry->value.u.intval = value; + conf_insert(conf, entry); +} + +void conf_set_int_int(Conf *conf, int primary, + int secondary, int value) +{ + struct conf_entry *entry = snew(struct conf_entry); + + assert(subkeytypes[primary] == TYPE_INT); + assert(valuetypes[primary] == TYPE_INT); + entry->key.primary = primary; + entry->key.secondary.i = secondary; + entry->value.u.intval = value; + conf_insert(conf, entry); +} + +void conf_set_str(Conf *conf, int primary, const char *value) +{ + struct conf_entry *entry = snew(struct conf_entry); + + assert(subkeytypes[primary] == TYPE_NONE); + assert(valuetypes[primary] == TYPE_STR); + entry->key.primary = primary; + entry->value.u.stringval = dupstr(value); + conf_insert(conf, entry); +} + +void conf_set_str_str(Conf *conf, int primary, const char *secondary, + const char *value) +{ + struct conf_entry *entry = snew(struct conf_entry); + + assert(subkeytypes[primary] == TYPE_STR); + assert(valuetypes[primary] == TYPE_STR); + entry->key.primary = primary; + entry->key.secondary.s = dupstr(secondary); + entry->value.u.stringval = dupstr(value); + conf_insert(conf, entry); +} + +void conf_del_str_str(Conf *conf, int primary, const char *secondary) +{ + struct key key; + struct conf_entry *entry; + + assert(subkeytypes[primary] == TYPE_STR); + assert(valuetypes[primary] == TYPE_STR); + key.primary = primary; + key.secondary.s = (char *)secondary; + entry = find234(conf->tree, &key, NULL); + if (entry) { + del234(conf->tree, entry); + free_entry(entry); + } + } + +void conf_set_filename(Conf *conf, int primary, const Filename *value) +{ + struct conf_entry *entry = snew(struct conf_entry); + + assert(subkeytypes[primary] == TYPE_NONE); + assert(valuetypes[primary] == TYPE_FILENAME); + entry->key.primary = primary; + entry->value.u.fileval = filename_copy(value); + conf_insert(conf, entry); +} + +void conf_set_fontspec(Conf *conf, int primary, const FontSpec *value) +{ + struct conf_entry *entry = snew(struct conf_entry); + + assert(subkeytypes[primary] == TYPE_NONE); + assert(valuetypes[primary] == TYPE_FONT); + entry->key.primary = primary; + entry->value.u.fontval = fontspec_copy(value); + conf_insert(conf, entry); +} + +void conf_serialise(BinarySink *bs, Conf *conf) +{ + int i; + struct conf_entry *entry; + + for (i = 0; (entry = index234(conf->tree, i)) != NULL; i++) { + put_uint32(bs, entry->key.primary); + + switch (subkeytypes[entry->key.primary]) { + case TYPE_INT: + put_uint32(bs, entry->key.secondary.i); + break; + case TYPE_STR: + put_asciz(bs, entry->key.secondary.s); + break; + } + switch (valuetypes[entry->key.primary]) { + case TYPE_BOOL: + put_bool(bs, entry->value.u.boolval); + break; + case TYPE_INT: + put_uint32(bs, entry->value.u.intval); + break; + case TYPE_STR: + put_asciz(bs, entry->value.u.stringval); + break; + case TYPE_FILENAME: + filename_serialise(bs, entry->value.u.fileval); + break; + case TYPE_FONT: + fontspec_serialise(bs, entry->value.u.fontval); + break; + } + } + + put_uint32(bs, 0xFFFFFFFFU); +} + +bool conf_deserialise(Conf *conf, BinarySource *src) +{ + struct conf_entry *entry; + unsigned primary; + + while (1) { + primary = get_uint32(src); + + if (get_err(src)) + return false; + if (primary == 0xFFFFFFFFU) + return true; + if (primary >= N_CONFIG_OPTIONS) + return false; + + entry = snew(struct conf_entry); + entry->key.primary = primary; + + switch (subkeytypes[entry->key.primary]) { + case TYPE_INT: + entry->key.secondary.i = toint(get_uint32(src)); + break; + case TYPE_STR: + entry->key.secondary.s = dupstr(get_asciz(src)); + break; + } + + switch (valuetypes[entry->key.primary]) { + case TYPE_BOOL: + entry->value.u.boolval = get_bool(src); + break; + case TYPE_INT: + entry->value.u.intval = toint(get_uint32(src)); + break; + case TYPE_STR: + entry->value.u.stringval = dupstr(get_asciz(src)); + break; + case TYPE_FILENAME: + entry->value.u.fileval = filename_deserialise(src); + break; + case TYPE_FONT: + entry->value.u.fontval = fontspec_deserialise(src); + break; + } + + if (get_err(src)) { + free_entry(entry); + return false; + } + + conf_insert(conf, entry); + } +} diff --git a/utils/conf_dest.c b/utils/conf_dest.c new file mode 100644 index 00000000..13e4ce65 --- /dev/null +++ b/utils/conf_dest.c @@ -0,0 +1,15 @@ +/* + * Decide whether the 'host name' or 'serial line' field of a Conf is + * important, based on which protocol it has selected. + */ + +#include "putty.h" + +char const *conf_dest(Conf *conf) +{ + if (conf_get_int(conf, CONF_protocol) == PROT_SERIAL) + return conf_get_str(conf, CONF_serline); + else + return conf_get_str(conf, CONF_host); +} + diff --git a/utils/conf_launchable.c b/utils/conf_launchable.c new file mode 100644 index 00000000..904ade61 --- /dev/null +++ b/utils/conf_launchable.c @@ -0,0 +1,14 @@ +/* + * Determine whether or not a Conf represents a session which can + * sensibly be launched right now. + */ + +#include "putty.h" + +bool conf_launchable(Conf *conf) +{ + if (conf_get_int(conf, CONF_protocol) == PROT_SERIAL) + return conf_get_str(conf, CONF_serline)[0] != 0; + else + return conf_get_str(conf, CONF_host)[0] != 0; +} diff --git a/utils/ctrlparse.c b/utils/ctrlparse.c new file mode 100644 index 00000000..86f87902 --- /dev/null +++ b/utils/ctrlparse.c @@ -0,0 +1,49 @@ +/* + * 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 + * specified by number (decimal, 0octal, 0xHEX) + * ~ ^ escape + */ + +#include + +#include "defs.h" +#include "misc.h" + +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; +} diff --git a/utils/debug.c b/utils/debug.c new file mode 100644 index 00000000..806b250a --- /dev/null +++ b/utils/debug.c @@ -0,0 +1,56 @@ +/* + * Debugging routines used by the debug() macros, at least if you + * compiled with -DDEBUG (aka the PUTTY_DEBUG cmake option) so that + * those macros don't optimise down to nothing. + */ + +#include "defs.h" +#include "misc.h" +#include "utils/utils.h" + +void debug_printf(const 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(const void *buf, int len, bool L) +{ + int i; + const unsigned char *p = buf; + char foo[17]; + if (L) { + int delta; + debug_printf("\t%d (0x%x) bytes:\n", len, len); + delta = 15 & (uintptr_t)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%2.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); + } +} diff --git a/utils/dupcat.c b/utils/dupcat.c new file mode 100644 index 00000000..ddd6599e --- /dev/null +++ b/utils/dupcat.c @@ -0,0 +1,48 @@ +/* + * Implementation function behind dupcat() in misc.h. + * + * This function is called with an arbitrary number of 'const char *' + * parameters, of which the last one is a null pointer. The wrapper + * macro puts on the null pointer itself, so normally callers don't + * have to. + */ + +#include +#include + +#include "defs.h" +#include "misc.h" + +char *dupcat_fn(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; +} + diff --git a/utils/dupprintf.c b/utils/dupprintf.c new file mode 100644 index 00000000..aa9f330b --- /dev/null +++ b/utils/dupprintf.c @@ -0,0 +1,100 @@ +/* + * 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 , 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. + */ + +#include + +#include "defs.h" +#include "misc.h" +#include "utils/utils.h" + +/* Work around lack of va_copy in old MSC */ +#if defined _MSC_VER && !defined va_copy +#define va_copy(a, b) TYPECHECK( \ + (va_list *)0 == &(a) && (va_list *)0 == &(b), \ + memcpy(&a, &b, sizeof(va_list))) +#endif + +/* Also lack of vsnprintf before VS2015 */ +#if defined _WINDOWS && \ + !defined __MINGW32__ && \ + !defined __WINE__ && \ + _MSC_VER < 1900 +#define vsnprintf _vsnprintf +#endif + +char *dupvprintf_inner(char *buf, size_t oldlen, size_t *sizeptr, + const char *fmt, va_list ap) +{ + size_t size = *sizeptr; + sgrowarrayn_nm(buf, size, oldlen, 512); + + while (1) { + va_list aq; + va_copy(aq, ap); + int len = vsnprintf(buf + oldlen, size - oldlen, fmt, aq); + va_end(aq); + + if (len >= 0 && len < size) { + /* This is the C99-specified criterion for snprintf to have + * been completely successful. */ + *sizeptr = size; + return buf; + } else if (len > 0) { + /* This is the C99 error condition: the returned length is + * the required buffer size not counting the NUL. */ + sgrowarrayn_nm(buf, size, oldlen + 1, len); + } 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. */ + sgrowarray_nm(buf, size, size); + } + } +} + +char *dupvprintf(const char *fmt, va_list ap) +{ + size_t size = 0; + return dupvprintf_inner(NULL, 0, &size, fmt, ap); +} +char *dupprintf(const char *fmt, ...) +{ + char *ret; + va_list ap; + va_start(ap, fmt); + ret = dupvprintf(fmt, ap); + va_end(ap); + return ret; +} diff --git a/utils/dupstr.c b/utils/dupstr.c new file mode 100644 index 00000000..fd79583f --- /dev/null +++ b/utils/dupstr.c @@ -0,0 +1,19 @@ +/* + * Allocate a duplicate of an ordinary C NUL-terminated string. + */ + +#include + +#include "defs.h" +#include "misc.h" + +char *dupstr(const char *s) +{ + char *p = NULL; + if (s) { + int len = strlen(s); + p = snewn(len + 1, char); + strcpy(p, s); + } + return p; +} diff --git a/utils/encode_utf8.c b/utils/encode_utf8.c new file mode 100644 index 00000000..731ab925 --- /dev/null +++ b/utils/encode_utf8.c @@ -0,0 +1,29 @@ +/* + * Encode a single code point as UTF-8. + */ + +#include "defs.h" +#include "misc.h" + +size_t encode_utf8(void *output, unsigned long ch) +{ + unsigned char *start = (unsigned char *)output, *p = start; + + if (ch < 0x80) { + *p++ = ch; + } else if (ch < 0x800) { + *p++ = 0xC0 | (ch >> 6); + *p++ = 0x80 | (ch & 0x3F); + } else if (ch < 0x10000) { + *p++ = 0xE0 | (ch >> 12); + *p++ = 0x80 | ((ch >> 6) & 0x3F); + *p++ = 0x80 | (ch & 0x3F); + } else { + assert(ch <= 0x10FFFF); + *p++ = 0xF0 | (ch >> 18); + *p++ = 0x80 | ((ch >> 12) & 0x3F); + *p++ = 0x80 | ((ch >> 6) & 0x3F); + *p++ = 0x80 | (ch & 0x3F); + } + return p - start; +} diff --git a/utils/fgetline.c b/utils/fgetline.c new file mode 100644 index 00000000..2bb580f1 --- /dev/null +++ b/utils/fgetline.c @@ -0,0 +1,25 @@ +/* + * Read an entire line of text from a file. Return a buffer + * malloced to be as big as necessary (caller must free). + */ + +#include "defs.h" +#include "misc.h" + +char *fgetline(FILE *fp) +{ + char *ret = snewn(512, char); + size_t size = 512, len = 0; + while (fgets(ret + len, size - len, fp)) { + len += strlen(ret + len); + if (len > 0 && ret[len-1] == '\n') + break; /* got a newline, we're done */ + sgrowarrayn_nm(ret, size, len, 512); + } + if (len == 0) { /* first fgets returned NULL */ + sfree(ret); + return NULL; + } + ret[len] = '\0'; + return ret; +} diff --git a/utils/host_strchr.c b/utils/host_strchr.c new file mode 100644 index 00000000..363a915e --- /dev/null +++ b/utils/host_strchr.c @@ -0,0 +1,18 @@ +/* + * strchr-like wrapper around host_strchr_internal. + */ + +#include +#include + +#include "defs.h" +#include "misc.h" +#include "utils/utils.h" + +char *host_strchr(const char *s, int c) +{ + char set[2]; + set[0] = c; + set[1] = '\0'; + return (char *) host_strchr_internal(s, set, true); +} diff --git a/utils/host_strchr_internal.c b/utils/host_strchr_internal.c new file mode 100644 index 00000000..07aadd37 --- /dev/null +++ b/utils/host_strchr_internal.c @@ -0,0 +1,80 @@ +/* + * Find a character in a string, unless it's a colon contained within + * square brackets. Used for untangling strings of the form + * 'host:port', where host can be an IPv6 literal. + * + * This internal function provides an API that's a bit like strchr (in + * that it returns a pointer to the character it found, or NULL), and + * a bit like strcspn (in that you can give it a set of characters to + * look for, not just one). Also it has an option to return the first + * or last character it finds. Other functions in the utils directory + * provide wrappers on it with APIs more like familiar + * functions. + */ + +#include +#include + +#include "defs.h" +#include "misc.h" +#include "utils/utils.h" + +const char *host_strchr_internal(const char *s, const char *set, bool first) +{ + int brackets = 0; + const char *ret = NULL; + + while (1) { + if (!*s) + return ret; + + if (*s == '[') + brackets++; + else if (*s == ']' && brackets > 0) + brackets--; + else if (brackets && *s == ':') + /* never match */ ; + else if (strchr(set, *s)) { + ret = s; + if (first) + return ret; + } + + s++; + } +} + +#ifdef TEST + +int main(void) +{ + int passes = 0, fails = 0; + +#define TEST1(func, string, arg2, suffix, result) do \ + { \ + const char *str = string; \ + unsigned ret = func(string, arg2) suffix; \ + if (ret == result) { \ + passes++; \ + } else { \ + printf("fail: %s(%s,%s)%s = %u, expected %u\n", \ + #func, #string, #arg2, #suffix, ret, \ + (unsigned)result); \ + fails++; \ + } \ +} while (0) + + TEST1(host_strchr, "[1:2:3]:4:5", ':', -str, 7); + TEST1(host_strrchr, "[1:2:3]:4:5", ':', -str, 9); + TEST1(host_strcspn, "[1:2:3]:4:5", "/:",, 7); + TEST1(host_strchr, "[1:2:3]", ':', == NULL, 1); + TEST1(host_strrchr, "[1:2:3]", ':', == NULL, 1); + TEST1(host_strcspn, "[1:2:3]", "/:",, 7); + TEST1(host_strcspn, "[1:2/3]", "/:",, 4); + TEST1(host_strcspn, "[1:2:3]/", "/:",, 7); + + printf("passed %d failed %d total %d\n", passes, fails, passes+fails); + return fails != 0 ? 1 : 0; +} + +#endif /* TEST */ diff --git a/utils/host_strcspn.c b/utils/host_strcspn.c new file mode 100644 index 00000000..958f47f8 --- /dev/null +++ b/utils/host_strcspn.c @@ -0,0 +1,19 @@ +/* + * strcspn-like wrapper around host_strchr_internal. + */ + +#include +#include + +#include "defs.h" +#include "misc.h" +#include "utils/utils.h" + +size_t host_strcspn(const char *s, const char *set) +{ + const char *answer = host_strchr_internal(s, set, true); + if (answer) + return answer - s; + else + return strlen(s); +} diff --git a/utils/host_strduptrim.c b/utils/host_strduptrim.c new file mode 100644 index 00000000..94492e64 --- /dev/null +++ b/utils/host_strduptrim.c @@ -0,0 +1,51 @@ +/* + * Trim square brackets off the outside of an IPv6 address literal. + * Leave all other strings unchanged. Returns a fresh dynamically + * allocated string. + */ + +#include +#include + +#include "defs.h" +#include "misc.h" + +char *host_strduptrim(const char *s) +{ + if (s[0] == '[') { + const char *p = s+1; + int colons = 0; + while (*p && *p != ']') { + if (isxdigit((unsigned char)*p)) + /* OK */; + else if (*p == ':') + colons++; + else + break; + p++; + } + if (*p == '%') { + /* + * This delimiter character introduces an RFC 4007 scope + * id suffix (e.g. suffixing the address literal with + * %eth1 or %2 or some such). There's no syntax + * specification for the scope id, so just accept anything + * except the closing ]. + */ + p += strcspn(p, "]"); + } + if (*p == ']' && !p[1] && colons > 1) { + /* + * This looks like an IPv6 address literal (hex digits and + * at least two colons, plus optional scope id, contained + * in square brackets). Trim off the brackets. + */ + return dupprintf("%.*s", (int)(p - (s+1)), s+1); + } + } + + /* + * Any other shape of string is simply duplicated. + */ + return dupstr(s); +} diff --git a/utils/host_strrchr.c b/utils/host_strrchr.c new file mode 100644 index 00000000..a1dd4c94 --- /dev/null +++ b/utils/host_strrchr.c @@ -0,0 +1,18 @@ +/* + * strchr-like wrapper around host_strchr_internal. + */ + +#include +#include + +#include "defs.h" +#include "misc.h" +#include "utils/utils.h" + +char *host_strrchr(const char *s, int c) +{ + char set[2]; + set[0] = c; + set[1] = '\0'; + return (char *) host_strchr_internal(s, set, false); +} diff --git a/utils/ltime.c b/utils/ltime.c new file mode 100644 index 00000000..75b89535 --- /dev/null +++ b/utils/ltime.c @@ -0,0 +1,16 @@ +/* + * 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 +#include + +struct tm ltime(void) +{ + time_t t; + time(&t); + assert (t != ((time_t)-1)); + return *localtime(&t); +} diff --git a/utils/marshal.c b/utils/marshal.c new file mode 100644 index 00000000..ff9bb851 --- /dev/null +++ b/utils/marshal.c @@ -0,0 +1,318 @@ +#include +#include +#include + +#include "marshal.h" +#include "misc.h" + +void BinarySink_put_data(BinarySink *bs, const void *data, size_t len) +{ + bs->write(bs, data, len); +} + +void BinarySink_put_datapl(BinarySink *bs, ptrlen pl) +{ + BinarySink_put_data(bs, pl.ptr, pl.len); +} + +void BinarySink_put_padding(BinarySink *bs, size_t len, unsigned char padbyte) +{ + char buf[16]; + memset(buf, padbyte, sizeof(buf)); + while (len > 0) { + size_t thislen = len < sizeof(buf) ? len : sizeof(buf); + bs->write(bs, buf, thislen); + len -= thislen; + } +} + +void BinarySink_put_byte(BinarySink *bs, unsigned char val) +{ + bs->write(bs, &val, 1); +} + +void BinarySink_put_bool(BinarySink *bs, bool val) +{ + unsigned char cval = val ? 1 : 0; + bs->write(bs, &cval, 1); +} + +void BinarySink_put_uint16(BinarySink *bs, unsigned long val) +{ + unsigned char data[2]; + PUT_16BIT_MSB_FIRST(data, val); + bs->write(bs, data, sizeof(data)); +} + +void BinarySink_put_uint32(BinarySink *bs, unsigned long val) +{ + unsigned char data[4]; + PUT_32BIT_MSB_FIRST(data, val); + bs->write(bs, data, sizeof(data)); +} + +void BinarySink_put_uint64(BinarySink *bs, uint64_t val) +{ + unsigned char data[8]; + PUT_64BIT_MSB_FIRST(data, val); + bs->write(bs, data, sizeof(data)); +} + +void BinarySink_put_string(BinarySink *bs, const void *data, size_t len) +{ + /* Check that the string length fits in a uint32, without doing a + * potentially implementation-defined shift of more than 31 bits */ + assert((len >> 31) < 2); + + BinarySink_put_uint32(bs, len); + bs->write(bs, data, len); +} + +void BinarySink_put_stringpl(BinarySink *bs, ptrlen pl) +{ + BinarySink_put_string(bs, pl.ptr, pl.len); +} + +void BinarySink_put_stringz(BinarySink *bs, const char *str) +{ + BinarySink_put_string(bs, str, strlen(str)); +} + +void BinarySink_put_stringsb(BinarySink *bs, struct strbuf *buf) +{ + BinarySink_put_string(bs, buf->s, buf->len); + strbuf_free(buf); +} + +void BinarySink_put_asciz(BinarySink *bs, const char *str) +{ + bs->write(bs, str, strlen(str) + 1); +} + +bool BinarySink_put_pstring(BinarySink *bs, const char *str) +{ + size_t len = strlen(str); + if (len > 255) + return false; /* can't write a Pascal-style string this long */ + BinarySink_put_byte(bs, len); + bs->write(bs, str, len); + return true; +} + +/* ---------------------------------------------------------------------- */ + +static bool BinarySource_data_avail(BinarySource *src, size_t wanted) +{ + if (src->err) + return false; + + if (wanted <= src->len - src->pos) + return true; + + src->err = BSE_OUT_OF_DATA; + return false; +} + +#define avail(wanted) BinarySource_data_avail(src, wanted) +#define advance(dist) (src->pos += dist) +#define here ((const void *)((const unsigned char *)src->data + src->pos)) +#define consume(dist) \ + ((const void *)((const unsigned char *)src->data + \ + ((src->pos += dist) - dist))) + +ptrlen BinarySource_get_data(BinarySource *src, size_t wanted) +{ + if (!avail(wanted)) + return make_ptrlen("", 0); + + return make_ptrlen(consume(wanted), wanted); +} + +unsigned char BinarySource_get_byte(BinarySource *src) +{ + const unsigned char *ucp; + + if (!avail(1)) + return 0; + + ucp = consume(1); + return *ucp; +} + +bool BinarySource_get_bool(BinarySource *src) +{ + const unsigned char *ucp; + + if (!avail(1)) + return false; + + ucp = consume(1); + return *ucp != 0; +} + +unsigned BinarySource_get_uint16(BinarySource *src) +{ + const unsigned char *ucp; + + if (!avail(2)) + return 0; + + ucp = consume(2); + return GET_16BIT_MSB_FIRST(ucp); +} + +unsigned long BinarySource_get_uint32(BinarySource *src) +{ + const unsigned char *ucp; + + if (!avail(4)) + return 0; + + ucp = consume(4); + return GET_32BIT_MSB_FIRST(ucp); +} + +uint64_t BinarySource_get_uint64(BinarySource *src) +{ + const unsigned char *ucp; + + if (!avail(8)) + return 0; + + ucp = consume(8); + return GET_64BIT_MSB_FIRST(ucp); +} + +ptrlen BinarySource_get_string(BinarySource *src) +{ + const unsigned char *ucp; + size_t len; + + if (!avail(4)) + return make_ptrlen("", 0); + + ucp = consume(4); + len = GET_32BIT_MSB_FIRST(ucp); + + if (!avail(len)) + return make_ptrlen("", 0); + + return make_ptrlen(consume(len), len); +} + +const char *BinarySource_get_asciz(BinarySource *src) +{ + const char *start, *end; + + if (src->err) + return ""; + + start = here; + end = memchr(start, '\0', src->len - src->pos); + if (!end) { + src->err = BSE_OUT_OF_DATA; + return ""; + } + + advance(end + 1 - start); + return start; +} + +static ptrlen BinarySource_get_chars_internal( + BinarySource *src, const char *set, bool include) +{ + const char *start = here; + while (avail(1)) { + bool present = NULL != strchr(set, *(const char *)consume(0)); + if (present != include) + break; + (void) consume(1); + } + const char *end = here; + return make_ptrlen(start, end - start); +} + +ptrlen BinarySource_get_chars(BinarySource *src, const char *include_set) +{ + return BinarySource_get_chars_internal(src, include_set, true); +} + +ptrlen BinarySource_get_nonchars(BinarySource *src, const char *exclude_set) +{ + return BinarySource_get_chars_internal(src, exclude_set, false); +} + +ptrlen BinarySource_get_chomped_line(BinarySource *src) +{ + const char *start, *end; + + if (src->err) + return make_ptrlen(here, 0); + + start = here; + end = memchr(start, '\n', src->len - src->pos); + if (end) + advance(end + 1 - start); + else + advance(src->len - src->pos); + end = here; + + if (end > start && end[-1] == '\n') + end--; + if (end > start && end[-1] == '\r') + end--; + + return make_ptrlen(start, end - start); +} + +ptrlen BinarySource_get_pstring(BinarySource *src) +{ + const unsigned char *ucp; + size_t len; + + if (!avail(1)) + return make_ptrlen("", 0); + + ucp = consume(1); + len = *ucp; + + if (!avail(len)) + return make_ptrlen("", 0); + + return make_ptrlen(consume(len), len); +} + +void BinarySource_REWIND_TO__(BinarySource *src, size_t pos) +{ + if (pos <= src->len) { + src->pos = pos; + src->err = BSE_NO_ERROR; /* clear any existing error */ + } else { + src->pos = src->len; + src->err = BSE_OUT_OF_DATA; /* new error if we rewind out of range */ + } +} + +static void stdio_sink_write(BinarySink *bs, const void *data, size_t len) +{ + stdio_sink *sink = BinarySink_DOWNCAST(bs, stdio_sink); + fwrite(data, 1, len, sink->fp); +} + +void stdio_sink_init(stdio_sink *sink, FILE *fp) +{ + sink->fp = fp; + BinarySink_INIT(sink, stdio_sink_write); +} + +static void bufchain_sink_write(BinarySink *bs, const void *data, size_t len) +{ + bufchain_sink *sink = BinarySink_DOWNCAST(bs, bufchain_sink); + bufchain_add(sink->ch, data, len); +} + +void bufchain_sink_init(bufchain_sink *sink, bufchain *ch) +{ + sink->ch = ch; + BinarySink_INIT(sink, bufchain_sink_write); +} diff --git a/utils/memory.c b/utils/memory.c new file mode 100644 index 00000000..97ae9401 --- /dev/null +++ b/utils/memory.c @@ -0,0 +1,134 @@ +/* + * PuTTY's memory allocation wrappers. + */ + +#include +#include +#include + +#include "defs.h" +#include "puttymem.h" +#include "misc.h" + +void *safemalloc(size_t factor1, size_t factor2, size_t addend) +{ + if (factor1 > SIZE_MAX / factor2) + goto fail; + size_t product = factor1 * factor2; + + if (addend > SIZE_MAX) + goto fail; + if (product > SIZE_MAX - addend) + goto fail; + size_t size = product + addend; + + if (size == 0) + size = 1; + + void *p; +#ifdef MINEFIELD + p = minefield_c_malloc(size); +#else + p = malloc(size); +#endif + + if (!p) + goto fail; + + return p; + + fail: + out_of_memory(); +} + +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) + out_of_memory(); + + return p; +} + +void safefree(void *ptr) +{ + if (ptr) { +#ifdef MINEFIELD + minefield_c_free(ptr); +#else + free(ptr); +#endif + } +} + +void *safegrowarray(void *ptr, size_t *allocated, size_t eltsize, + size_t oldlen, size_t extralen, bool secret) +{ + /* The largest value we can safely multiply by eltsize */ + assert(eltsize > 0); + size_t maxsize = (~(size_t)0) / eltsize; + + size_t oldsize = *allocated; + + /* Range-check the input values */ + assert(oldsize <= maxsize); + assert(oldlen <= maxsize); + assert(extralen <= maxsize - oldlen); + + /* If the size is already enough, don't bother doing anything! */ + if (oldsize > oldlen + extralen) + return ptr; + + /* Find out how much we need to grow the array by. */ + size_t increment = (oldlen + extralen) - oldsize; + + /* Invent a new size. We want to grow the array by at least + * 'increment' elements; by at least a fixed number of bytes (to + * get things started when sizes are small); and by some constant + * factor of its old size (to avoid repeated calls to this + * function taking quadratic time overall). */ + if (increment < 256 / eltsize) + increment = 256 / eltsize; + if (increment < oldsize / 16) + increment = oldsize / 16; + + /* But we also can't grow beyond maxsize. */ + size_t maxincr = maxsize - oldsize; + if (increment > maxincr) + increment = maxincr; + + size_t newsize = oldsize + increment; + void *toret; + if (secret) { + toret = safemalloc(newsize, eltsize, 0); + if (oldsize) { + memcpy(toret, ptr, oldsize * eltsize); + smemclr(ptr, oldsize * eltsize); + sfree(ptr); + } + } else { + toret = saferealloc(ptr, newsize, eltsize); + } + *allocated = newsize; + return toret; +} diff --git a/utils/memxor.c b/utils/memxor.c new file mode 100644 index 00000000..4fd41f41 --- /dev/null +++ b/utils/memxor.c @@ -0,0 +1,34 @@ +/* + * XOR two pieces of memory, copying the result into a third, which + * may precisely alias one of the input pair (but no guarantees if it + * partially overlaps). + */ + +#include "defs.h" +#include "misc.h" + +void memxor(uint8_t *out, const uint8_t *in1, const uint8_t *in2, size_t size) +{ + switch (size & 15) { + case 0: + while (size >= 16) { + size -= 16; + *out++ = *in1++ ^ *in2++; + case 15: *out++ = *in1++ ^ *in2++; + case 14: *out++ = *in1++ ^ *in2++; + case 13: *out++ = *in1++ ^ *in2++; + case 12: *out++ = *in1++ ^ *in2++; + case 11: *out++ = *in1++ ^ *in2++; + case 10: *out++ = *in1++ ^ *in2++; + case 9: *out++ = *in1++ ^ *in2++; + case 8: *out++ = *in1++ ^ *in2++; + case 7: *out++ = *in1++ ^ *in2++; + case 6: *out++ = *in1++ ^ *in2++; + case 5: *out++ = *in1++ ^ *in2++; + case 4: *out++ = *in1++ ^ *in2++; + case 3: *out++ = *in1++ ^ *in2++; + case 2: *out++ = *in1++ ^ *in2++; + case 1: *out++ = *in1++ ^ *in2++; + } + } +} diff --git a/utils/miscucs.c b/utils/miscucs.c new file mode 100644 index 00000000..7785f9b6 --- /dev/null +++ b/utils/miscucs.c @@ -0,0 +1,28 @@ +/* + * Centralised Unicode-related helper functions, separate from misc.c + * so that they can be omitted from tools that aren't including + * Unicode handling. + */ + +#include "putty.h" +#include "misc.h" + +wchar_t *dup_mb_to_wc_c(int codepage, int flags, const char *string, int len) +{ + int mult; + for (mult = 1 ;; mult++) { + wchar_t *ret = snewn(mult*len + 2, wchar_t); + int outlen; + outlen = mb_to_wc(codepage, flags, string, len, ret, mult*len + 1); + if (outlen < mult*len+1) { + ret[outlen] = L'\0'; + return ret; + } + sfree(ret); + } +} + +wchar_t *dup_mb_to_wc(int codepage, int flags, const char *string) +{ + return dup_mb_to_wc_c(codepage, flags, string, strlen(string)); +} diff --git a/utils/null_lp.c b/utils/null_lp.c new file mode 100644 index 00000000..193c3392 --- /dev/null +++ b/utils/null_lp.c @@ -0,0 +1,8 @@ +/* + * Stub methods usable by LogPolicy implementations. + */ + +#include "putty.h" + +bool null_lp_verbose_no(LogPolicy *lp) { return false; } +bool null_lp_verbose_yes(LogPolicy *lp) { return true; } diff --git a/utils/nullseat.c b/utils/nullseat.c new file mode 100644 index 00000000..01aaabea --- /dev/null +++ b/utils/nullseat.c @@ -0,0 +1,42 @@ +/* + * Stub methods usable by Seat implementations. + */ + +#include "putty.h" + +size_t nullseat_output( + Seat *seat, bool is_stderr, const void *data, size_t len) { return 0; } +bool nullseat_eof(Seat *seat) { return true; } +int nullseat_get_userpass_input( + Seat *seat, prompts_t *p, bufchain *input) { return 0; } +void nullseat_notify_remote_exit(Seat *seat) {} +void nullseat_connection_fatal(Seat *seat, const char *message) {} +void nullseat_update_specials_menu(Seat *seat) {} +char *nullseat_get_ttymode(Seat *seat, const char *mode) { return NULL; } +void nullseat_set_busy_status(Seat *seat, BusyStatus status) {} +int nullseat_verify_ssh_host_key( + Seat *seat, const char *host, int port, const char *keytype, + char *keystr, const char *keydisp, char **key_fingerprints, + void (*callback)(void *ctx, int result), void *ctx) { return 0; } +int nullseat_confirm_weak_crypto_primitive( + Seat *seat, const char *algtype, const char *algname, + void (*callback)(void *ctx, int result), void *ctx) { return 0; } +int nullseat_confirm_weak_cached_hostkey( + Seat *seat, const char *algname, const char *betteralgs, + void (*callback)(void *ctx, int result), void *ctx) { return 0; } +bool nullseat_is_never_utf8(Seat *seat) { return false; } +bool nullseat_is_always_utf8(Seat *seat) { return true; } +void nullseat_echoedit_update(Seat *seat, bool echoing, bool editing) {} +const char *nullseat_get_x_display(Seat *seat) { return NULL; } +bool nullseat_get_windowid(Seat *seat, long *id_out) { return false; } +bool nullseat_get_window_pixel_size( + Seat *seat, int *width, int *height) { return false; } +StripCtrlChars *nullseat_stripctrl_new( + Seat *seat, BinarySink *bs_out, SeatInteractionContext sic) {return NULL;} +bool nullseat_set_trust_status(Seat *seat, bool tr) { return false; } +bool nullseat_set_trust_status_vacuously(Seat *seat, bool tr) { return true; } +bool nullseat_verbose_no(Seat *seat) { return false; } +bool nullseat_verbose_yes(Seat *seat) { return true; } +bool nullseat_interactive_no(Seat *seat) { return false; } +bool nullseat_interactive_yes(Seat *seat) { return true; } +bool nullseat_get_cursor_position(Seat *seat, int *x, int *y) { return false; } diff --git a/utils/nullstrcmp.c b/utils/nullstrcmp.c new file mode 100644 index 00000000..a9e50f40 --- /dev/null +++ b/utils/nullstrcmp.c @@ -0,0 +1,21 @@ +/* + * Compare two strings, just like strcmp, except that we tolerate null + * pointers as a legal input, and define them to compare before any + * non-null string (even the empty string). + */ + +#include + +#include "defs.h" +#include "misc.h" + +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); +} diff --git a/utils/out_of_memory.c b/utils/out_of_memory.c new file mode 100644 index 00000000..1c9cb259 --- /dev/null +++ b/utils/out_of_memory.c @@ -0,0 +1,11 @@ +/* + * Standard implementation of the out_of_memory function called by our + * malloc wrappers. + */ + +#include "putty.h" + +void out_of_memory(void) +{ + modalfatalbox("Out of memory"); +} diff --git a/utils/parse_blocksize.c b/utils/parse_blocksize.c new file mode 100644 index 00000000..3d559276 --- /dev/null +++ b/utils/parse_blocksize.c @@ -0,0 +1,40 @@ +/* + * 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. + */ + +#include +#include +#include + +#include "defs.h" +#include "misc.h" + +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; +} diff --git a/utils/prompts.c b/utils/prompts.c new file mode 100644 index 00000000..f37bde59 --- /dev/null +++ b/utils/prompts.c @@ -0,0 +1,59 @@ +/* + * Functions for making, destroying, and manipulating prompts_t + * structures. + */ + +#include "putty.h" + +prompts_t *new_prompts(void) +{ + prompts_t *p = snew(prompts_t); + p->prompts = NULL; + p->n_prompts = p->prompts_size = 0; + 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, bool echo) +{ + prompt_t *pr = snew(prompt_t); + pr->prompt = promptstr; + pr->echo = echo; + pr->result = strbuf_new_nm(); + sgrowarray(p->prompts, p->prompts_size, p->n_prompts); + p->prompts[p->n_prompts++] = pr; +} + +void prompt_set_result(prompt_t *pr, const char *newstr) +{ + strbuf_clear(pr->result); + put_datapl(pr->result, ptrlen_from_asciz(newstr)); +} + +const char *prompt_get_result_ref(prompt_t *pr) +{ + return pr->result->s; +} + +char *prompt_get_result(prompt_t *pr) +{ + return dupstr(pr->result->s); +} + +void free_prompts(prompts_t *p) +{ + size_t i; + for (i=0; i < p->n_prompts; i++) { + prompt_t *pr = p->prompts[i]; + strbuf_free(pr->result); + sfree(pr->prompt); + sfree(pr); + } + sfree(p->prompts); + sfree(p->name); + sfree(p->instruction); + sfree(p); +} diff --git a/utils/ptrlen.c b/utils/ptrlen.c new file mode 100644 index 00000000..f2297eb5 --- /dev/null +++ b/utils/ptrlen.c @@ -0,0 +1,95 @@ +/* + * Functions to deal with ptrlens. + */ + +#include "defs.h" +#include "misc.h" +#include "ssh.h" + +bool ptrlen_eq_string(ptrlen pl, const char *str) +{ + size_t len = strlen(str); + return (pl.len == len && !memcmp(pl.ptr, str, len)); +} + +bool ptrlen_eq_ptrlen(ptrlen pl1, ptrlen pl2) +{ + return (pl1.len == pl2.len && !memcmp(pl1.ptr, pl2.ptr, pl1.len)); +} + +int ptrlen_strcmp(ptrlen pl1, ptrlen pl2) +{ + size_t minlen = pl1.len < pl2.len ? pl1.len : pl2.len; + if (minlen) { /* tolerate plX.ptr==NULL as long as plX.len==0 */ + int cmp = memcmp(pl1.ptr, pl2.ptr, minlen); + if (cmp) + return cmp; + } + return pl1.len < pl2.len ? -1 : pl1.len > pl2.len ? +1 : 0; +} + +bool ptrlen_startswith(ptrlen whole, ptrlen prefix, ptrlen *tail) +{ + if (whole.len >= prefix.len && + !memcmp(whole.ptr, prefix.ptr, prefix.len)) { + if (tail) { + tail->ptr = (const char *)whole.ptr + prefix.len; + tail->len = whole.len - prefix.len; + } + return true; + } + return false; +} + +bool ptrlen_endswith(ptrlen whole, ptrlen suffix, ptrlen *tail) +{ + if (whole.len >= suffix.len && + !memcmp((char *)whole.ptr + (whole.len - suffix.len), + suffix.ptr, suffix.len)) { + if (tail) { + tail->ptr = whole.ptr; + tail->len = whole.len - suffix.len; + } + return true; + } + return false; +} + +ptrlen ptrlen_get_word(ptrlen *input, const char *separators) +{ + const char *p = input->ptr, *end = p + input->len; + ptrlen toret; + + while (p < end && strchr(separators, *p)) + p++; + toret.ptr = p; + while (p < end && !strchr(separators, *p)) + p++; + toret.len = p - (const char *)toret.ptr; + + size_t to_consume = p - (const char *)input->ptr; + assert(to_consume <= input->len); + input->ptr = (const char *)input->ptr + to_consume; + input->len -= to_consume; + + return toret; +} + +char *mkstr(ptrlen pl) +{ + char *p = snewn(pl.len + 1, char); + memcpy(p, pl.ptr, pl.len); + p[pl.len] = '\0'; + return p; +} + +bool strstartswith(const char *s, const char *t) +{ + return !strncmp(s, t, strlen(t)); +} + +bool strendswith(const char *s, const char *t) +{ + size_t slen = strlen(s), tlen = strlen(t); + return slen >= tlen && !strcmp(s + (slen - tlen), t); +} diff --git a/utils/read_file_into.c b/utils/read_file_into.c new file mode 100644 index 00000000..59569484 --- /dev/null +++ b/utils/read_file_into.c @@ -0,0 +1,19 @@ +/* + * Read an entire file into a BinarySink. + */ + +#include + +#include "defs.h" +#include "misc.h" + +bool read_file_into(BinarySink *bs, FILE *fp) +{ + char buf[4096]; + while (1) { + size_t retd = fread(buf, 1, sizeof(buf), fp); + if (retd == 0) + return !ferror(fp); + put_data(bs, buf, retd); + } +} diff --git a/utils/seat_connection_fatal.c b/utils/seat_connection_fatal.c new file mode 100644 index 00000000..9b0186b1 --- /dev/null +++ b/utils/seat_connection_fatal.c @@ -0,0 +1,20 @@ +/* + * Wrapper function for the connection_fatal() method of a Seat, + * providing printf-style formatting. + */ + +#include "putty.h" + +void seat_connection_fatal(Seat *seat, const char *fmt, ...) +{ + va_list ap; + char *msg; + + va_start(ap, fmt); + msg = dupvprintf(fmt, ap); + va_end(ap); + + seat->vt->connection_fatal(seat, msg); + sfree(msg); /* if we return */ +} + diff --git a/utils/sessprep.c b/utils/sessprep.c new file mode 100644 index 00000000..95e7b658 --- /dev/null +++ b/utils/sessprep.c @@ -0,0 +1,84 @@ +/* + * sessprep.c: centralise some preprocessing done on Conf objects + * before launching them. + */ + +#include "putty.h" + +void prepare_session(Conf *conf) +{ + char *hostbuf = dupstr(conf_get_str(conf, CONF_host)); + char *host = hostbuf; + char *p, *q; + + /* + * Trim leading whitespace from the hostname. + */ + host += strspn(host, " \t"); + + /* + * See if host is of the form user@host, and separate out the + * username if so. + */ + if (host[0] != '\0') { + /* + * Use strrchr, in case the _username_ in turn is of the form + * user@host, which has been known. + */ + char *atsign = strrchr(host, '@'); + if (atsign) { + *atsign = '\0'; + conf_set_str(conf, CONF_username, host); + host = atsign + 1; + } + } + + /* + * Trim a colon suffix off the hostname if it's there, and discard + * the text after it. + * + * The exact reason why we _ignore_ this text, rather than + * treating it as a port number, is unfortunately lost in the + * mists of history: the commit which originally introduced this + * change on 2001-05-06 was clear on _what_ it was doing but + * didn't bother to explain _why_. But I [SGT, 2017-12-03] suspect + * it has to do with priority order: what should a saved session + * do if its CONF_host contains 'server.example.com:123' and its + * CONF_port contains 456? If CONF_port contained the _default_ + * port number then it might be a good guess that the colon suffix + * on the host name was intended to override that, but you don't + * really want to get into making heuristic judgments on that + * basis. + * + * (Then again, you could just as easily make the same argument + * about whether a 'user@' prefix on the host name should override + * CONF_username, which this code _does_ do. I don't have a good + * answer, sadly. Both these pieces of behaviour have been around + * for years and it would probably cause subtle breakage in all + * sorts of long-forgotten scripting to go changing things around + * now.) + * + * In order to protect unbracketed IPv6 address literals against + * this treatment, we do not make this change at all if there's + * _more_ than one (un-IPv6-bracketed) colon. + */ + p = host_strchr(host, ':'); + if (p && p == host_strrchr(host, ':')) { + *p = '\0'; + } + + /* + * Remove any remaining whitespace. + */ + p = hostbuf; + q = host; + while (*q) { + if (*q != ' ' && *q != '\t') + *p++ = *q; + q++; + } + *p = '\0'; + + conf_set_str(conf, CONF_host, hostbuf); + sfree(hostbuf); +} diff --git a/utils/sk_free_peer_info.c b/utils/sk_free_peer_info.c new file mode 100644 index 00000000..a66e09d7 --- /dev/null +++ b/utils/sk_free_peer_info.c @@ -0,0 +1,14 @@ +/* + * Free a SocketPeerInfo, and everything that dangles off it. + */ + +#include "putty.h" + +void sk_free_peer_info(SocketPeerInfo *pi) +{ + if (pi) { + sfree((char *)pi->addr_text); + sfree((char *)pi->log_text); + sfree(pi); + } +} diff --git a/utils/smemclr.c b/utils/smemclr.c new file mode 100644 index 00000000..afe919d1 --- /dev/null +++ b/utils/smemclr.c @@ -0,0 +1,42 @@ +/* + * Securely wipe memory. + * + * The actual wiping is no different from what memset would do: the + * point of 'securely' is to try to be sure over-clever compilers + * won't optimise away memsets on variables that are about to be freed + * or go out of scope. See + * https://buildsecurityin.us-cert.gov/bsi-rules/home/g1/771-BSI.html + * + * Some platforms (e.g. Windows) may provide their own version of this + * function. + */ + +#include "defs.h" +#include "misc.h" + +void smemclr(void *b, size_t n) +{ + volatile char *vp; + + if (b && n > 0) { + /* + * Zero out the memory. + */ + memset(b, 0, n); + + /* + * Perform a volatile access to the object, forcing the + * compiler to admit that the previous memset was important. + * + * This while loop should in practice run for zero iterations + * (since we know we just zeroed the object out), but in + * theory (as far as the compiler knows) it might range over + * the whole object. (If we had just written, say, '*vp = + * *vp;', a compiler could in principle have 'helpfully' + * optimised the memset into only zeroing out the first byte. + * This should be robust.) + */ + vp = b; + while (*vp) vp++; + } +} diff --git a/utils/smemeq.c b/utils/smemeq.c new file mode 100644 index 00000000..2692d134 --- /dev/null +++ b/utils/smemeq.c @@ -0,0 +1,25 @@ +/* + * Compare two fixed-size regions of memory, in a crypto-safe way, + * i.e. without timing or cache side channels indicating anything + * about what the answer was or where the first difference (if any) + * might have been. + */ + +#include "defs.h" +#include "misc.h" + +bool smemeq(const void *av, const void *bv, size_t len) +{ + const unsigned char *a = (const unsigned char *)av; + const unsigned char *b = (const unsigned char *)bv; + unsigned val = 0; + + while (len-- > 0) { + val |= *a++ ^ *b++; + } + /* Now val is 0 iff we want to return 1, and in the range + * 0x01..0xFF iff we want to return 0. So subtracting from 0x100 + * will clear bit 8 iff we want to return 0, and leave it set iff + * we want to return 1, so then we can just shift down. */ + return (0x100 - val) >> 8; +} diff --git a/utils/ssh2_pick_fingerprint.c b/utils/ssh2_pick_fingerprint.c new file mode 100644 index 00000000..f81b2f1d --- /dev/null +++ b/utils/ssh2_pick_fingerprint.c @@ -0,0 +1,27 @@ +/* + * Choose an SSH-2 fingerprint type, out of an array of possible ones. + */ + +#include "defs.h" +#include "misc.h" +#include "ssh.h" + +FingerprintType ssh2_pick_fingerprint( + char **fingerprints, FingerprintType preferred_type) +{ + /* + * Keys are either SSH-2, in which case we have all fingerprint + * types, or SSH-1, in which case we have only MD5. So we return + * the default type if we can, or MD5 if that's all we have; no + * need for a fully general preference-list system. + */ + FingerprintType fptype = fingerprints[preferred_type] ? + preferred_type : SSH_FPTYPE_MD5; + assert(fingerprints[fptype]); + return fptype; +} + +FingerprintType ssh2_pick_default_fingerprint(char **fingerprints) +{ + return ssh2_pick_fingerprint(fingerprints, SSH_FPTYPE_DEFAULT); +} diff --git a/utils/sshutils.c b/utils/sshutils.c new file mode 100644 index 00000000..1ee3342b --- /dev/null +++ b/utils/sshutils.c @@ -0,0 +1,128 @@ +/* + * Supporting routines used in common by all the various components of + * the SSH system. + */ + +#include +#include + +#include "putty.h" +#include "ssh.h" +#include "sshchan.h" + +/* ---------------------------------------------------------------------- + * Centralised standard methods for other channel implementations to + * borrow. + */ + +void chan_remotely_opened_confirmation(Channel *chan) +{ + unreachable("this channel type should never receive OPEN_CONFIRMATION"); +} + +void chan_remotely_opened_failure(Channel *chan, const char *errtext) +{ + unreachable("this channel type should never receive OPEN_FAILURE"); +} + +bool chan_default_want_close( + Channel *chan, bool sent_local_eof, bool rcvd_remote_eof) +{ + /* + * Default close policy: we start initiating the CHANNEL_CLOSE + * procedure as soon as both sides of the channel have seen EOF. + */ + return sent_local_eof && rcvd_remote_eof; +} + +bool chan_no_exit_status(Channel *chan, int status) +{ + return false; +} + +bool chan_no_exit_signal( + Channel *chan, ptrlen signame, bool core_dumped, ptrlen msg) +{ + return false; +} + +bool chan_no_exit_signal_numeric( + Channel *chan, int signum, bool core_dumped, ptrlen msg) +{ + return false; +} + +bool chan_no_run_shell(Channel *chan) +{ + return false; +} + +bool chan_no_run_command(Channel *chan, ptrlen command) +{ + return false; +} + +bool chan_no_run_subsystem(Channel *chan, ptrlen subsys) +{ + return false; +} + +bool chan_no_enable_x11_forwarding( + Channel *chan, bool oneshot, ptrlen authproto, ptrlen authdata, + unsigned screen_number) +{ + return false; +} + +bool chan_no_enable_agent_forwarding(Channel *chan) +{ + return false; +} + +bool chan_no_allocate_pty( + Channel *chan, ptrlen termtype, unsigned width, unsigned height, + unsigned pixwidth, unsigned pixheight, struct ssh_ttymodes modes) +{ + return false; +} + +bool chan_no_set_env(Channel *chan, ptrlen var, ptrlen value) +{ + return false; +} + +bool chan_no_send_break(Channel *chan, unsigned length) +{ + return false; +} + +bool chan_no_send_signal(Channel *chan, ptrlen signame) +{ + return false; +} + +bool chan_no_change_window_size( + Channel *chan, unsigned width, unsigned height, + unsigned pixwidth, unsigned pixheight) +{ + return false; +} + +void chan_no_request_response(Channel *chan, bool success) +{ + unreachable("this channel type should never send a want-reply request"); +} + +/* ---------------------------------------------------------------------- + * Other miscellaneous utility functions. + */ + +void free_rportfwd(struct ssh_rportfwd *rpf) +{ + if (rpf) { + sfree(rpf->log_description); + sfree(rpf->shost); + sfree(rpf->dhost); + sfree(rpf); + } +} diff --git a/utils/strbuf.c b/utils/strbuf.c new file mode 100644 index 00000000..8358a413 --- /dev/null +++ b/utils/strbuf.c @@ -0,0 +1,118 @@ +/* + * Functions to work with strbufs. + */ + +#include "defs.h" +#include "misc.h" +#include "utils/utils.h" + +struct strbuf_impl { + size_t size; + struct strbuf visible; + bool nm; /* true if we insist on non-moving buffer resizes */ +}; + +#define STRBUF_SET_UPTR(buf) \ + ((buf)->visible.u = (unsigned char *)(buf)->visible.s) +#define STRBUF_SET_PTR(buf, ptr) \ + ((buf)->visible.s = (ptr), STRBUF_SET_UPTR(buf)) + +void *strbuf_append(strbuf *buf_o, size_t len) +{ + struct strbuf_impl *buf = container_of(buf_o, struct strbuf_impl, visible); + char *toret; + sgrowarray_general( + buf->visible.s, buf->size, buf->visible.len + 1, len, buf->nm); + STRBUF_SET_UPTR(buf); + toret = buf->visible.s + buf->visible.len; + buf->visible.len += len; + buf->visible.s[buf->visible.len] = '\0'; + return toret; +} + +void strbuf_shrink_to(strbuf *buf, size_t new_len) +{ + assert(new_len <= buf->len); + buf->len = new_len; + buf->s[buf->len] = '\0'; +} + +void strbuf_shrink_by(strbuf *buf, size_t amount_to_remove) +{ + assert(amount_to_remove <= buf->len); + buf->len -= amount_to_remove; + buf->s[buf->len] = '\0'; +} + +bool strbuf_chomp(strbuf *buf, char char_to_remove) +{ + if (buf->len > 0 && buf->s[buf->len-1] == char_to_remove) { + strbuf_shrink_by(buf, 1); + return true; + } + return false; +} + +static void strbuf_BinarySink_write( + BinarySink *bs, const void *data, size_t len) +{ + strbuf *buf_o = BinarySink_DOWNCAST(bs, strbuf); + memcpy(strbuf_append(buf_o, len), data, len); +} + +static strbuf *strbuf_new_general(bool nm) +{ + struct strbuf_impl *buf = snew(struct strbuf_impl); + BinarySink_INIT(&buf->visible, strbuf_BinarySink_write); + buf->visible.len = 0; + buf->size = 512; + buf->nm = nm; + STRBUF_SET_PTR(buf, snewn(buf->size, char)); + *buf->visible.s = '\0'; + return &buf->visible; +} +strbuf *strbuf_new(void) { return strbuf_new_general(false); } +strbuf *strbuf_new_nm(void) { return strbuf_new_general(true); } +void strbuf_free(strbuf *buf_o) +{ + struct strbuf_impl *buf = container_of(buf_o, struct strbuf_impl, visible); + if (buf->visible.s) { + smemclr(buf->visible.s, buf->size); + sfree(buf->visible.s); + } + sfree(buf); +} +char *strbuf_to_str(strbuf *buf_o) +{ + struct strbuf_impl *buf = container_of(buf_o, struct strbuf_impl, visible); + char *ret = buf->visible.s; + sfree(buf); + return ret; +} +void strbuf_catfv(strbuf *buf_o, const char *fmt, va_list ap) +{ + struct strbuf_impl *buf = container_of(buf_o, struct strbuf_impl, visible); + STRBUF_SET_PTR(buf, dupvprintf_inner(buf->visible.s, buf->visible.len, + &buf->size, fmt, ap)); + buf->visible.len += strlen(buf->visible.s + buf->visible.len); +} +void strbuf_catf(strbuf *buf_o, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + strbuf_catfv(buf_o, fmt, ap); + va_end(ap); +} + +strbuf *strbuf_new_for_agent_query(void) +{ + strbuf *buf = strbuf_new(); + strbuf_append(buf, 4); + return buf; +} +void strbuf_finalise_agent_query(strbuf *buf_o) +{ + struct strbuf_impl *buf = container_of(buf_o, struct strbuf_impl, visible); + assert(buf->visible.len >= 5); + PUT_32BIT_MSB_FIRST(buf->visible.u, buf->visible.len - 4); +} diff --git a/utils/string_length_for_printf.c b/utils/string_length_for_printf.c new file mode 100644 index 00000000..8b455316 --- /dev/null +++ b/utils/string_length_for_printf.c @@ -0,0 +1,21 @@ +/* + * Convert a size_t value to int, by saturating it at INT_MAX. Useful + * if you want to use the printf idiom "%.*s", where the '*' precision + * specifier expects an int in the variadic argument list, but what + * you have is not an int but a size_t. This method of converting to + * int will at least do something _safe_ with overlong values, even if + * (due to the limitation of printf itself) the whole string still + * won't be printed. + */ + +#include + +#include "defs.h" +#include "misc.h" + +int string_length_for_printf(size_t s) +{ + if (s > INT_MAX) + return INT_MAX; + return s; +} diff --git a/utils/stripctrl.c b/utils/stripctrl.c new file mode 100644 index 00000000..58289b10 --- /dev/null +++ b/utils/stripctrl.c @@ -0,0 +1,476 @@ +/* + * stripctrl.c: a facility for stripping control characters out of a + * data stream (defined as any multibyte character in the system + * locale which is neither printable nor \n), using the standard C + * library multibyte character facilities. + */ + +#include +#include +#include +#include +#include + +#include "putty.h" +#include "terminal.h" +#include "misc.h" +#include "marshal.h" + +#define SCC_BUFSIZE 64 +#define LINE_LIMIT 77 + +typedef struct StripCtrlCharsImpl StripCtrlCharsImpl; +struct StripCtrlCharsImpl { + mbstate_t mbs_in, mbs_out; + + bool permit_cr; + wchar_t substitution; + + char buf[SCC_BUFSIZE]; + size_t buflen; + + Terminal *term; + bool last_term_utf; + struct term_utf8_decode utf8; + unsigned long (*translate)(Terminal *, term_utf8_decode *, unsigned char); + + bool line_limit; + bool line_start; + size_t line_chars_remaining; + + BinarySink *bs_out; + + StripCtrlChars public; +}; + +static void stripctrl_locale_BinarySink_write( + BinarySink *bs, const void *vp, size_t len); +static void stripctrl_term_BinarySink_write( + BinarySink *bs, const void *vp, size_t len); + +static StripCtrlCharsImpl *stripctrl_new_common( + BinarySink *bs_out, bool permit_cr, wchar_t substitution) +{ + StripCtrlCharsImpl *scc = snew(StripCtrlCharsImpl); + memset(scc, 0, sizeof(StripCtrlCharsImpl)); /* zeroes mbstates */ + scc->bs_out = bs_out; + scc->permit_cr = permit_cr; + scc->substitution = substitution; + return scc; +} + +StripCtrlChars *stripctrl_new( + BinarySink *bs_out, bool permit_cr, wchar_t substitution) +{ + StripCtrlCharsImpl *scc = stripctrl_new_common( + bs_out, permit_cr, substitution); + BinarySink_INIT(&scc->public, stripctrl_locale_BinarySink_write); + return &scc->public; +} + +StripCtrlChars *stripctrl_new_term_fn( + BinarySink *bs_out, bool permit_cr, wchar_t substitution, + Terminal *term, unsigned long (*translate)( + Terminal *, term_utf8_decode *, unsigned char)) +{ + StripCtrlCharsImpl *scc = stripctrl_new_common( + bs_out, permit_cr, substitution); + scc->term = term; + scc->translate = translate; + BinarySink_INIT(&scc->public, stripctrl_term_BinarySink_write); + return &scc->public; +} + +void stripctrl_retarget(StripCtrlChars *sccpub, BinarySink *new_bs_out) +{ + StripCtrlCharsImpl *scc = + container_of(sccpub, StripCtrlCharsImpl, public); + scc->bs_out = new_bs_out; + stripctrl_reset(sccpub); +} + +void stripctrl_reset(StripCtrlChars *sccpub) +{ + StripCtrlCharsImpl *scc = + container_of(sccpub, StripCtrlCharsImpl, public); + + /* + * Clear all the fields that might have been in the middle of a + * multibyte character or non-default shift state, so that we can + * start converting a fresh piece of data to send to a channel + * that hasn't seen the previous output. + */ + memset(&scc->utf8, 0, sizeof(scc->utf8)); + memset(&scc->mbs_in, 0, sizeof(scc->mbs_in)); + memset(&scc->mbs_out, 0, sizeof(scc->mbs_out)); + + /* + * Also, reset the line-limiting system to its starting state. + */ + scc->line_start = true; +} + +void stripctrl_free(StripCtrlChars *sccpub) +{ + StripCtrlCharsImpl *scc = + container_of(sccpub, StripCtrlCharsImpl, public); + smemclr(scc, sizeof(StripCtrlCharsImpl)); + sfree(scc); +} + +void stripctrl_enable_line_limiting(StripCtrlChars *sccpub) +{ + StripCtrlCharsImpl *scc = + container_of(sccpub, StripCtrlCharsImpl, public); + scc->line_limit = true; + scc->line_start = true; +} + +static inline bool stripctrl_ctrlchar_ok(StripCtrlCharsImpl *scc, wchar_t wc) +{ + return wc == L'\n' || (wc == L'\r' && scc->permit_cr); +} + +static inline void stripctrl_check_line_limit( + StripCtrlCharsImpl *scc, wchar_t wc, size_t width) +{ + if (!scc->line_limit) + return; /* nothing to do */ + + if (scc->line_start) { + put_datapl(scc->bs_out, PTRLEN_LITERAL("| ")); + scc->line_start = false; + scc->line_chars_remaining = LINE_LIMIT; + } + + if (wc == '\n') { + scc->line_start = true; + return; + } + + if (scc->line_chars_remaining < width) { + put_datapl(scc->bs_out, PTRLEN_LITERAL("\r\n> ")); + scc->line_chars_remaining = LINE_LIMIT; + } + + assert(width <= scc->line_chars_remaining); + scc->line_chars_remaining -= width; +} + +static inline void stripctrl_locale_put_wc(StripCtrlCharsImpl *scc, wchar_t wc) +{ + int width = mk_wcwidth(wc); + if ((iswprint(wc) && width >= 0) || stripctrl_ctrlchar_ok(scc, wc)) { + /* Printable character, or one we're going to let through anyway. */ + if (width < 0) + width = 0; /* sanitise for stripctrl_check_line_limit */ + } else if (scc->substitution) { + wc = scc->substitution; + width = mk_wcwidth(wc); + assert(width >= 0); + } else { + /* No defined substitution, so don't write any output wchar_t. */ + return; + } + + stripctrl_check_line_limit(scc, wc, width); + + char outbuf[MB_LEN_MAX]; + size_t produced = wcrtomb(outbuf, wc, &scc->mbs_out); + if (produced > 0) + put_data(scc->bs_out, outbuf, produced); +} + +static inline void stripctrl_term_put_wc( + StripCtrlCharsImpl *scc, unsigned long wc) +{ + ptrlen prefix = PTRLEN_LITERAL(""); + int width = term_char_width(scc->term, wc); + + if (!(wc & ~0x9F) || width < 0) { + /* This is something the terminal interprets as a control + * character. */ + if (!stripctrl_ctrlchar_ok(scc, wc)) { + if (!scc->substitution) { + return; + } else { + wc = scc->substitution; + width = term_char_width(scc->term, wc); + assert(width >= 0); + } + } else { + if (width < 0) + width = 0; /* sanitise for stripctrl_check_line_limit */ + } + + if (wc == '\012') { + /* Precede \n with \r, because our terminal will not + * generally be in the ONLCR mode where it assumes that + * internally, and any \r on input has been stripped + * out. */ + prefix = PTRLEN_LITERAL("\r"); + } + } + + stripctrl_check_line_limit(scc, wc, width); + + if (prefix.len) + put_datapl(scc->bs_out, prefix); + + char outbuf[6]; + size_t produced; + + /* + * The Terminal implementation encodes 7-bit ASCII characters in + * UTF-8 mode, and all printing characters in non-UTF-8 (i.e. + * single-byte character set) mode, as values in the surrogate + * range (a conveniently unused piece of space in this context) + * whose low byte is the original 1-byte representation of the + * character. + */ + if ((wc - 0xD800) < (0xE000 - 0xD800)) + wc &= 0xFF; + + if (in_utf(scc->term)) { + produced = encode_utf8(outbuf, wc); + } else { + outbuf[0] = wc; + produced = 1; + } + + if (produced > 0) + put_data(scc->bs_out, outbuf, produced); +} + +static inline size_t stripctrl_locale_try_consume( + StripCtrlCharsImpl *scc, const char *p, size_t len) +{ + wchar_t wc; + mbstate_t mbs_orig = scc->mbs_in; + size_t consumed = mbrtowc(&wc, p, len, &scc->mbs_in); + + if (consumed == (size_t)-2) { + /* + * The buffer is too short to see the end of the multibyte + * character that it appears to be starting with. We return 0 + * for 'no data consumed', restore the conversion state from + * before consuming the partial character, and our caller will + * come back when it has more data available. + */ + scc->mbs_in = mbs_orig; + return 0; + } + + if (consumed == (size_t)-1) { + /* + * The buffer contains an illegal multibyte sequence. There's + * no really good way to recover from this, so we'll just + * reset our input state, consume a single byte without + * emitting anything, and hope we can resynchronise to + * _something_ sooner or later. + */ + memset(&scc->mbs_in, 0, sizeof(scc->mbs_in)); + return 1; + } + + if (consumed == 0) { + /* + * A zero wide character is encoded by the data, but mbrtowc + * hasn't told us how many input bytes it takes. There isn't + * really anything good we can do here, so we just advance by + * one byte in the hope that that was the NUL. + * + * (If it wasn't - that is, if we're in a multibyte encoding + * in which the terminator of a normal C string is encoded in + * some way other than a single zero byte - then probably lots + * of other things will have gone wrong before we get here!) + */ + stripctrl_locale_put_wc(scc, L'\0'); + return 1; + } + + /* + * Otherwise, this is the easy case: consumed > 0, and we've eaten + * a valid multibyte character. + */ + stripctrl_locale_put_wc(scc, wc); + return consumed; +} + +static void stripctrl_locale_BinarySink_write( + BinarySink *bs, const void *vp, size_t len) +{ + StripCtrlChars *sccpub = BinarySink_DOWNCAST(bs, StripCtrlChars); + StripCtrlCharsImpl *scc = + container_of(sccpub, StripCtrlCharsImpl, public); + const char *p = (const char *)vp; + + const char *previous_locale = setlocale(LC_CTYPE, NULL); + setlocale(LC_CTYPE, ""); + + /* + * Deal with any partial multibyte character buffered from last + * time. + */ + while (scc->buflen > 0) { + size_t to_copy = SCC_BUFSIZE - scc->buflen; + if (to_copy > len) + to_copy = len; + + memcpy(scc->buf + scc->buflen, p, to_copy); + size_t consumed = stripctrl_locale_try_consume( + scc, scc->buf, scc->buflen + to_copy); + + if (consumed >= scc->buflen) { + /* + * We've consumed a multibyte character that includes all + * the data buffered from last time. So we can clear our + * buffer and move on to processing the main input string + * in situ, having first discarded whatever initial + * segment of it completed our previous character. + */ + size_t consumed_from_main_string = consumed - scc->buflen; + assert(consumed_from_main_string <= len); + p += consumed_from_main_string; + len -= consumed_from_main_string; + scc->buflen = 0; + break; + } + + if (consumed == 0) { + /* + * If we didn't manage to consume anything, i.e. the whole + * buffer contains an incomplete sequence, it had better + * be because our entire input string _this_ time plus + * whatever leftover data we had from _last_ time still + * comes to less than SCC_BUFSIZE. In other words, we've + * already copied all the new data on to the end of our + * buffer, and it still hasn't helped. So increment buflen + * to reflect the new data, and return. + */ + assert(to_copy == len); + scc->buflen += to_copy; + goto out; + } + + /* + * Otherwise, we've somehow consumed _less_ data than we had + * buffered, and yet we weren't able to consume that data in + * the last call to this function. That sounds impossible, but + * I can think of one situation in which it could happen: if + * we had an incomplete MB sequence last time, and now more + * data has arrived, it turns out to be an _illegal_ one, so + * we consume one byte in the hope of resynchronising. + * + * Anyway, in this case we move the buffer up and go back + * round this initial loop. + */ + scc->buflen -= consumed; + memmove(scc->buf, scc->buf + consumed, scc->buflen); + } + + /* + * Now charge along the main string. + */ + while (len > 0) { + size_t consumed = stripctrl_locale_try_consume(scc, p, len); + if (consumed == 0) + break; + assert(consumed <= len); + p += consumed; + len -= consumed; + } + + /* + * Any data remaining should be copied into our buffer, to keep + * for next time. + */ + assert(len <= SCC_BUFSIZE); + memcpy(scc->buf, p, len); + scc->buflen = len; + + out: + setlocale(LC_CTYPE, previous_locale); +} + +static void stripctrl_term_BinarySink_write( + BinarySink *bs, const void *vp, size_t len) +{ + StripCtrlChars *sccpub = BinarySink_DOWNCAST(bs, StripCtrlChars); + StripCtrlCharsImpl *scc = + container_of(sccpub, StripCtrlCharsImpl, public); + + bool utf = in_utf(scc->term); + if (utf != scc->last_term_utf) { + scc->last_term_utf = utf; + scc->utf8.state = 0; + } + + for (const unsigned char *p = (const unsigned char *)vp; + len > 0; len--, p++) { + unsigned long t = scc->translate(scc->term, &scc->utf8, *p); + if (t == UCSTRUNCATED) { + stripctrl_term_put_wc(scc, 0xFFFD); + /* go round again */ + t = scc->translate(scc->term, &scc->utf8, *p); + } + if (t == UCSINCOMPLETE) + continue; + if (t == UCSINVALID) + t = 0xFFFD; + + stripctrl_term_put_wc(scc, t); + } +} + +char *stripctrl_string_ptrlen(StripCtrlChars *sccpub, ptrlen str) +{ + strbuf *out = strbuf_new(); + stripctrl_retarget(sccpub, BinarySink_UPCAST(out)); + put_datapl(sccpub, str); + stripctrl_retarget(sccpub, NULL); + return strbuf_to_str(out); +} + +#ifdef STRIPCTRL_TEST + +/* +gcc -std=c99 -DSTRIPCTRL_TEST -o scctest stripctrl.c marshal.c utils.c memory.c wcwidth.c -I . -I unix -I charset +*/ + +void out_of_memory(void) { fprintf(stderr, "out of memory\n"); abort(); } + +void stripctrl_write(BinarySink *bs, const void *vdata, size_t len) +{ + const uint8_t *p = vdata; + printf("["); + for (size_t i = 0; i < len; i++) + printf("%*s%02x", i?1:0, "", (unsigned)p[i]); + printf("]"); +} + +void stripctrl_test(StripCtrlChars *scc, ptrlen pl) +{ + stripctrl_write(NULL, pl.ptr, pl.len); + printf(" -> "); + put_datapl(scc, pl); + printf("\n"); +} + +int main(void) +{ + struct foo { BinarySink_IMPLEMENTATION; } foo; + BinarySink_INIT(&foo, stripctrl_write); + StripCtrlChars *scc = stripctrl_new(BinarySink_UPCAST(&foo), false, '?'); + stripctrl_test(scc, PTRLEN_LITERAL("a\033[1mb")); + stripctrl_test(scc, PTRLEN_LITERAL("a\xC2\x9B[1mb")); + stripctrl_test(scc, PTRLEN_LITERAL("a\xC2\xC2[1mb")); + stripctrl_test(scc, PTRLEN_LITERAL("\xC3")); + stripctrl_test(scc, PTRLEN_LITERAL("\xA9")); + stripctrl_test(scc, PTRLEN_LITERAL("\xE2\x80\x8F")); + stripctrl_test(scc, PTRLEN_LITERAL("a\0b")); + stripctrl_free(scc); + return 0; +} + +#endif /* STRIPCTRL_TEST */ diff --git a/utils/tree234.c b/utils/tree234.c new file mode 100644 index 00000000..a590382b --- /dev/null +++ b/utils/tree234.c @@ -0,0 +1,1611 @@ +/* + * 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 +#include +#include + +#include "defs.h" +#include "tree234.h" + +#ifdef TEST +#define LOG(x) (printf x) +#define snew(type) ((type *)malloc(sizeof(type))) +#define snewn(n, type) ((type *)malloc((n) * sizeof(type))) +#define sresize(ptr, n, type) \ + ((type *)realloc(sizeof((type *)0 == (ptr)) ? (ptr) : (ptr), \ + (n) * sizeof(type))) +#define sfree(ptr) free(ptr) +#else +#include "puttymem.h" +#define LOG(x) +#endif + +typedef struct node234_Tag node234; + +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; +} + +/* + * Internal function to return the number of elements in a node. + */ +static int elements234(node234 *n) +{ + int i; + for (i = 0; i < 3; i++) + if (!n->elems[i]) + break; + return i; +} + +/* + * 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, (int)(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) +{ + search234_state ss; + int reldir = (relation == REL234_LT || relation == REL234_LE ? -1 : + relation == REL234_GT || relation == REL234_GE ? +1 : 0); + bool equal_permitted = (relation != REL234_LT && relation != REL234_GT); + void *toret; + + /* Only LT / GT relations are permitted with a null query element. */ + assert(!(equal_permitted && !e)); + + if (cmp == NULL) + cmp = t->cmp; + + search234_start(&ss, t); + while (ss.element) { + int cmpret; + + if (e) { + cmpret = cmp(e, ss.element); + } else { + cmpret = -reldir; /* invent a fixed compare result */ + } + + if (cmpret == 0) { + /* + * We've found an element that compares exactly equal to + * the query element. + */ + if (equal_permitted) { + /* If our search relation permits equality, we've + * finished already. */ + if (index) + *index = ss.index; + return ss.element; + } else { + /* Otherwise, pretend this element was slightly too + * big/small, according to the direction of search. */ + cmpret = reldir; + } + } + + search234_step(&ss, cmpret); + } + + /* + * No element compares equal to the one we were after, but + * ss.index indicates the index that element would have if it were + * inserted. + * + * So if our search relation is EQ, we must simply return failure. + */ + if (relation == REL234_EQ) + return NULL; + + /* + * Otherwise, we must do an index lookup for the previous index + * (if we're going left - LE or LT) or this index (if we're going + * right - GE or GT). + */ + if (relation == REL234_LT || relation == REL234_LE) { + ss.index--; + } + + /* + * 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. + */ + toret = index234(t, ss.index); + if (toret && index) + *index = ss.index; + return toret; +} +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); +} + +void search234_start(search234_state *state, tree234 *t) +{ + state->_node = t->root; + state->_base = 0; /* index of first element in this node's subtree */ + state->_last = -1; /* indicate that this node is not previously visted */ + search234_step(state, 0); +} +void search234_step(search234_state *state, int direction) +{ + node234 *node = state->_node; + int i; + + if (!node) { + state->element = NULL; + state->index = 0; + return; + } + + if (state->_last != -1) { + /* + * We're already pointing at some element of a node, so we + * should restrict to the elements left or right of it, + * depending on the requested search direction. + */ + assert(direction); + assert(node); + + if (direction > 0) + state->_lo = state->_last + 1; + else + state->_hi = state->_last - 1; + + if (state->_lo > state->_hi) { + /* + * We've run out of elements in this node, i.e. we've + * narrowed to nothing but a child pointer. Descend to + * that child, and update _base to the leftmost index of + * its subtree. + */ + for (i = 0; i < state->_lo; i++) + state->_base += 1 + node->counts[i]; + state->_node = node = node->kids[state->_lo]; + state->_last = -1; + } + } + + if (state->_last == -1) { + /* + * We've just entered a new node - either because of the above + * code, or because we were called from search234_start - and + * anything in that node is a viable answer. + */ + state->_lo = 0; + state->_hi = node ? elements234(node)-1 : 0; + } + + /* + * Now we've got something we can return. + */ + if (!node) { + state->element = NULL; + state->index = state->_base; + } else { + state->_last = (state->_lo + state->_hi) / 2; + state->element = node->elems[state->_last]; + state->index = state->_base + state->_last; + for (i = 0; i <= state->_last; i++) + state->index += node->counts[i]; + } +} + +/* + * 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")); + 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 +#include + +int n_errors = 0; + +/* + * Error reporting function. + */ +PRINTF_LIKE(1, 2) void error(char *fmt, ...) +{ + va_list ap; + printf("ERROR: "); + va_start(ap, fmt); + vfprintf(stdout, fmt, ap); + va_end(ap); + printf("\n"); + n_errors++; +} + +/* 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[1]; + 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); + } +} + +void searchtest_recurse(search234_state ss, int lo, int hi, + char **expected, char *directionbuf, + char *directionptr) +{ + *directionptr = '\0'; + + if (!ss.element) { + if (lo != hi) { + error("search234(%s) gave NULL for non-empty interval [%d,%d)", + directionbuf, lo, hi); + } else if (ss.index != lo) { + error("search234(%s) gave index %d should be %d", + directionbuf, ss.index, lo); + } else { + printf("%*ssearch234(%s) gave NULL,%d\n", + (int)(directionptr-directionbuf) * 2, "", directionbuf, + ss.index); + } + } else if (lo == hi) { + error("search234(%s) gave %s for empty interval [%d,%d)", + directionbuf, (char *)ss.element, lo, hi); + } else if (ss.element != expected[ss.index]) { + error("search234(%s) gave element %s should be %s", + directionbuf, (char *)ss.element, expected[ss.index]); + } else if (ss.index < lo || ss.index >= hi) { + error("search234(%s) gave index %d should be in [%d,%d)", + directionbuf, ss.index, lo, hi); + return; + } else { + search234_state next; + + printf("%*ssearch234(%s) gave %s,%d\n", + (int)(directionptr-directionbuf) * 2, "", directionbuf, + (char *)ss.element, ss.index); + + next = ss; + search234_step(&next, -1); + *directionptr = '-'; + searchtest_recurse(next, lo, ss.index, + expected, directionbuf, directionptr+1); + + next = ss; + search234_step(&next, +1); + *directionptr = '+'; + searchtest_recurse(next, ss.index+1, hi, + expected, directionbuf, directionptr+1); + } +} + +void searchtest(void) +{ + char *expected[NSTR], *p; + char directionbuf[NSTR * 10]; + int n; + search234_state ss; + + printf("beginning searchtest:"); + for (n = 0; (p = index234(tree, n)) != NULL; n++) { + expected[n] = p; + printf(" %d=%s", n, p); + } + printf(" count=%d\n", n); + + search234_start(&ss, tree); + searchtest_recurse(ss, 0, n, expected, directionbuf, directionbuf); +} + +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(); + searchtest(); + 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(); + searchtest(); + } + + 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", + (const char *)array[j], j); + delpostest(j); + } + + printf("%d errors found\n", n_errors); + return (n_errors != 0); +} + +#endif diff --git a/utils/utils.h b/utils/utils.h new file mode 100644 index 00000000..ea4cda0c --- /dev/null +++ b/utils/utils.h @@ -0,0 +1,12 @@ +/* + * Internal header to the utils subdirectory, for definitions shared + * between the library implementations but not intended to be exposed + * further than that. + */ + +void dputs(const char *); + +char *dupvprintf_inner(char *buf, size_t oldlen, size_t *sizeptr, + const char *fmt, va_list ap); + +const char *host_strchr_internal(const char *s, const char *set, bool first); diff --git a/utils/validate_manual_hostkey.c b/utils/validate_manual_hostkey.c new file mode 100644 index 00000000..386e1039 --- /dev/null +++ b/utils/validate_manual_hostkey.c @@ -0,0 +1,116 @@ +/* + * Validate a manual host key specification (either entered in the + * GUI, or via -hostkey). If valid, we return true, and update 'key' + * to contain a canonicalised version of the key string in 'key' + * (which is guaranteed to take up at most as much space as the + * original version), suitable for putting into the Conf. If not + * valid, we return false. + */ + +#include +#include + +#include "putty.h" +#include "misc.h" + +#define BASE64_CHARS_NOEQ \ + "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+/" +#define BASE64_CHARS_ALL BASE64_CHARS_NOEQ "=" + +bool validate_manual_hostkey(char *key) +{ + char *p, *q, *r, *s; + + /* + * Step through the string word by word, looking for a word that's + * in one of the formats we like. + */ + p = key; + while ((p += strspn(p, " \t"))[0]) { + q = p; + p += strcspn(p, " \t"); + if (*p) *p++ = '\0'; + + /* + * Now q is our word. + */ + + if (strstartswith(q, "SHA256:")) { + /* Test for a valid SHA256 key fingerprint. */ + r = q + 7; + if (strlen(r) == 43 && r[strspn(r, BASE64_CHARS_NOEQ)] == 0) + return true; + } + + r = q; + if (strstartswith(r, "MD5:")) + r += 4; + if (strlen(r) == 16*3 - 1 && + r[strspn(r, "0123456789abcdefABCDEF:")] == 0) { + /* + * Test for a valid MD5 key fingerprint. Check the colons + * are in the right places, and if so, return the same + * fingerprint canonicalised into lowercase. + */ + int i; + for (i = 0; i < 16; i++) + if (r[3*i] == ':' || r[3*i+1] == ':') + goto not_fingerprint; /* sorry */ + for (i = 0; i < 15; i++) + if (r[3*i+2] != ':') + goto not_fingerprint; /* sorry */ + for (i = 0; i < 16*3 - 1; i++) + key[i] = tolower(r[i]); + key[16*3 - 1] = '\0'; + return true; + } + not_fingerprint:; + + /* + * Before we check for a public-key blob, trim newlines out of + * the middle of the word, in case someone's managed to paste + * in a public-key blob _with_ them. + */ + for (r = s = q; *r; r++) + if (*r != '\n' && *r != '\r') + *s++ = *r; + *s = '\0'; + + if (strlen(q) % 4 == 0 && strlen(q) > 2*4 && + q[strspn(q, BASE64_CHARS_ALL)] == 0) { + /* + * Might be a base64-encoded SSH-2 public key blob. Check + * that it starts with a sensible algorithm string. No + * canonicalisation is necessary for this string type. + * + * The algorithm string must be at most 64 characters long + * (RFC 4251 section 6). + */ + unsigned char decoded[6]; + unsigned alglen; + int minlen; + int len = 0; + + len += base64_decode_atom(q, decoded+len); + if (len < 3) + goto not_ssh2_blob; /* sorry */ + len += base64_decode_atom(q+4, decoded+len); + if (len < 4) + goto not_ssh2_blob; /* sorry */ + + alglen = GET_32BIT_MSB_FIRST(decoded); + if (alglen > 64) + goto not_ssh2_blob; /* sorry */ + + minlen = ((alglen + 4) + 2) / 3; + if (strlen(q) < minlen) + goto not_ssh2_blob; /* sorry */ + + strcpy(key, q); + return true; + } + not_ssh2_blob:; + } + + return false; +} diff --git a/utils/version.c b/utils/version.c new file mode 100644 index 00000000..edc4da99 --- /dev/null +++ b/utils/version.c @@ -0,0 +1,23 @@ +/* + * PuTTY version numbering + */ + +/* + * The difficult part of deciding what goes in these version strings + * is done in Buildscr, and then written into version.h. All we have + * to do here is to drop it into variables of the right names. + */ + +#include "putty.h" +#include "ssh.h" + +#include "version.h" + +const char ver[] = TEXTVER; +const char sshver[] = SSHVER; + +/* + * 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) }; diff --git a/utils/wcwidth.c b/utils/wcwidth.c new file mode 100644 index 00000000..6468fedd --- /dev/null +++ b/utils/wcwidth.c @@ -0,0 +1,558 @@ +/* + * 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 -- 2007-05-26 (Unicode 5.0) + * + * Permission to use, copy, modify, and distribute this software + * for any purpose and without fee is hereby granted. The author + * disclaims all warranties with regard to this software. + * + * Latest version: http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c + */ + +#include + +#include "putty.h" /* for prototypes */ + +struct interval { + unsigned int first; + unsigned int last; +}; + +/* auxiliary function for binary search in interval table */ +static bool bisearch(unsigned int ucs, const struct interval *table, int max) { + int min = 0; + int mid; + + if (ucs < table[0].first || ucs > table[max].last) + return false; + 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 true; + } + + return false; +} + + +/* 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(unsigned int ucs) +{ + /* sorted list of non-overlapping intervals of non-spacing characters */ + /* generated by "uniset +cat=Me +cat=Mn +cat=Cf -00AD +1160-11FF +200B c" */ + static const struct interval combining[] = { + { 0x0300, 0x036F }, { 0x0483, 0x0486 }, { 0x0488, 0x0489 }, + { 0x0591, 0x05BD }, { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 }, + { 0x05C4, 0x05C5 }, { 0x05C7, 0x05C7 }, { 0x0600, 0x0603 }, + { 0x0610, 0x0615 }, { 0x064B, 0x065E }, { 0x0670, 0x0670 }, + { 0x06D6, 0x06E4 }, { 0x06E7, 0x06E8 }, { 0x06EA, 0x06ED }, + { 0x070F, 0x070F }, { 0x0711, 0x0711 }, { 0x0730, 0x074A }, + { 0x07A6, 0x07B0 }, { 0x07EB, 0x07F3 }, { 0x0901, 0x0902 }, + { 0x093C, 0x093C }, { 0x0941, 0x0948 }, { 0x094D, 0x094D }, + { 0x0951, 0x0954 }, { 0x0962, 0x0963 }, { 0x0981, 0x0981 }, + { 0x09BC, 0x09BC }, { 0x09C1, 0x09C4 }, { 0x09CD, 0x09CD }, + { 0x09E2, 0x09E3 }, { 0x0A01, 0x0A02 }, { 0x0A3C, 0x0A3C }, + { 0x0A41, 0x0A42 }, { 0x0A47, 0x0A48 }, { 0x0A4B, 0x0A4D }, + { 0x0A70, 0x0A71 }, { 0x0A81, 0x0A82 }, { 0x0ABC, 0x0ABC }, + { 0x0AC1, 0x0AC5 }, { 0x0AC7, 0x0AC8 }, { 0x0ACD, 0x0ACD }, + { 0x0AE2, 0x0AE3 }, { 0x0B01, 0x0B01 }, { 0x0B3C, 0x0B3C }, + { 0x0B3F, 0x0B3F }, { 0x0B41, 0x0B43 }, { 0x0B4D, 0x0B4D }, + { 0x0B56, 0x0B56 }, { 0x0B82, 0x0B82 }, { 0x0BC0, 0x0BC0 }, + { 0x0BCD, 0x0BCD }, { 0x0C3E, 0x0C40 }, { 0x0C46, 0x0C48 }, + { 0x0C4A, 0x0C4D }, { 0x0C55, 0x0C56 }, { 0x0CBC, 0x0CBC }, + { 0x0CBF, 0x0CBF }, { 0x0CC6, 0x0CC6 }, { 0x0CCC, 0x0CCD }, + { 0x0CE2, 0x0CE3 }, { 0x0D41, 0x0D43 }, { 0x0D4D, 0x0D4D }, + { 0x0DCA, 0x0DCA }, { 0x0DD2, 0x0DD4 }, { 0x0DD6, 0x0DD6 }, + { 0x0E31, 0x0E31 }, { 0x0E34, 0x0E3A }, { 0x0E47, 0x0E4E }, + { 0x0EB1, 0x0EB1 }, { 0x0EB4, 0x0EB9 }, { 0x0EBB, 0x0EBC }, + { 0x0EC8, 0x0ECD }, { 0x0F18, 0x0F19 }, { 0x0F35, 0x0F35 }, + { 0x0F37, 0x0F37 }, { 0x0F39, 0x0F39 }, { 0x0F71, 0x0F7E }, + { 0x0F80, 0x0F84 }, { 0x0F86, 0x0F87 }, { 0x0F90, 0x0F97 }, + { 0x0F99, 0x0FBC }, { 0x0FC6, 0x0FC6 }, { 0x102D, 0x1030 }, + { 0x1032, 0x1032 }, { 0x1036, 0x1037 }, { 0x1039, 0x1039 }, + { 0x1058, 0x1059 }, { 0x1160, 0x11FF }, { 0x135F, 0x135F }, + { 0x1712, 0x1714 }, { 0x1732, 0x1734 }, { 0x1752, 0x1753 }, + { 0x1772, 0x1773 }, { 0x17B4, 0x17B5 }, { 0x17B7, 0x17BD }, + { 0x17C6, 0x17C6 }, { 0x17C9, 0x17D3 }, { 0x17DD, 0x17DD }, + { 0x180B, 0x180D }, { 0x18A9, 0x18A9 }, { 0x1920, 0x1922 }, + { 0x1927, 0x1928 }, { 0x1932, 0x1932 }, { 0x1939, 0x193B }, + { 0x1A17, 0x1A18 }, { 0x1B00, 0x1B03 }, { 0x1B34, 0x1B34 }, + { 0x1B36, 0x1B3A }, { 0x1B3C, 0x1B3C }, { 0x1B42, 0x1B42 }, + { 0x1B6B, 0x1B73 }, { 0x1DC0, 0x1DCA }, { 0x1DFE, 0x1DFF }, + { 0x200B, 0x200F }, { 0x202A, 0x202E }, { 0x2060, 0x2063 }, + { 0x206A, 0x206F }, { 0x20D0, 0x20EF }, { 0x302A, 0x302F }, + { 0x3099, 0x309A }, { 0xA806, 0xA806 }, { 0xA80B, 0xA80B }, + { 0xA825, 0xA826 }, { 0xFB1E, 0xFB1E }, { 0xFE00, 0xFE0F }, + { 0xFE20, 0xFE23 }, { 0xFEFF, 0xFEFF }, { 0xFFF9, 0xFFFB }, + { 0x10A01, 0x10A03 }, { 0x10A05, 0x10A06 }, { 0x10A0C, 0x10A0F }, + { 0x10A38, 0x10A3A }, { 0x10A3F, 0x10A3F }, { 0x1D167, 0x1D169 }, + { 0x1D173, 0x1D182 }, { 0x1D185, 0x1D18B }, { 0x1D1AA, 0x1D1AD }, + { 0x1D242, 0x1D244 }, { 0xE0001, 0xE0001 }, { 0xE0020, 0xE007F }, + { 0xE0100, 0xE01EF } + }; + + /* A sorted list of intervals of double-width characters generated by: + * https://raw.githubusercontent.com/GNOME/glib/37d4c2941bd0326b8b6e6bb22c81bd424fcc040b/glib/gen-unicode-tables.pl + * from the Unicode 9.0.0 data files available at: + * https://www.unicode.org/Public/13.0.0/ucd/ + */ + static const struct interval wide[] = { + {0x1100, 0x115F}, + {0x231A, 0x231B}, + {0x2329, 0x232A}, + {0x23E9, 0x23EC}, + {0x23F0, 0x23F0}, + {0x23F3, 0x23F3}, + {0x25FD, 0x25FE}, + {0x2614, 0x2615}, + {0x2648, 0x2653}, + {0x267F, 0x267F}, + {0x2693, 0x2693}, + {0x26A1, 0x26A1}, + {0x26AA, 0x26AB}, + {0x26BD, 0x26BE}, + {0x26C4, 0x26C5}, + {0x26CE, 0x26CE}, + {0x26D4, 0x26D4}, + {0x26EA, 0x26EA}, + {0x26F2, 0x26F3}, + {0x26F5, 0x26F5}, + {0x26FA, 0x26FA}, + {0x26FD, 0x26FD}, + {0x2705, 0x2705}, + {0x270A, 0x270B}, + {0x2728, 0x2728}, + {0x274C, 0x274C}, + {0x274E, 0x274E}, + {0x2753, 0x2755}, + {0x2757, 0x2757}, + {0x2795, 0x2797}, + {0x27B0, 0x27B0}, + {0x27BF, 0x27BF}, + {0x2B1B, 0x2B1C}, + {0x2B50, 0x2B50}, + {0x2B55, 0x2B55}, + {0x2E80, 0x2E99}, + {0x2E9B, 0x2EF3}, + {0x2F00, 0x2FD5}, + {0x2FF0, 0x2FFB}, + {0x3000, 0x303E}, + {0x3041, 0x3096}, + {0x3099, 0x30FF}, + {0x3105, 0x312F}, + {0x3131, 0x318E}, + {0x3190, 0x31E3}, + {0x31F0, 0x321E}, + {0x3220, 0x3247}, + {0x3250, 0x4DBF}, + {0x4E00, 0xA48C}, + {0xA490, 0xA4C6}, + {0xA960, 0xA97C}, + {0xAC00, 0xD7A3}, + {0xF900, 0xFAFF}, + {0xFE10, 0xFE19}, + {0xFE30, 0xFE52}, + {0xFE54, 0xFE66}, + {0xFE68, 0xFE6B}, + {0xFF01, 0xFF60}, + {0xFFE0, 0xFFE6}, + {0x16FE0, 0x16FE4}, + {0x16FF0, 0x16FF1}, + {0x17000, 0x187F7}, + {0x18800, 0x18CD5}, + {0x18D00, 0x18D08}, + {0x1B000, 0x1B11E}, + {0x1B150, 0x1B152}, + {0x1B164, 0x1B167}, + {0x1B170, 0x1B2FB}, + {0x1F004, 0x1F004}, + {0x1F0CF, 0x1F0CF}, + {0x1F18E, 0x1F18E}, + {0x1F191, 0x1F19A}, + {0x1F200, 0x1F202}, + {0x1F210, 0x1F23B}, + {0x1F240, 0x1F248}, + {0x1F250, 0x1F251}, + {0x1F260, 0x1F265}, + {0x1F300, 0x1F320}, + {0x1F32D, 0x1F335}, + {0x1F337, 0x1F37C}, + {0x1F37E, 0x1F393}, + {0x1F3A0, 0x1F3CA}, + {0x1F3CF, 0x1F3D3}, + {0x1F3E0, 0x1F3F0}, + {0x1F3F4, 0x1F3F4}, + {0x1F3F8, 0x1F43E}, + {0x1F440, 0x1F440}, + {0x1F442, 0x1F4FC}, + {0x1F4FF, 0x1F53D}, + {0x1F54B, 0x1F54E}, + {0x1F550, 0x1F567}, + {0x1F57A, 0x1F57A}, + {0x1F595, 0x1F596}, + {0x1F5A4, 0x1F5A4}, + {0x1F5FB, 0x1F64F}, + {0x1F680, 0x1F6C5}, + {0x1F6CC, 0x1F6CC}, + {0x1F6D0, 0x1F6D2}, + {0x1F6D5, 0x1F6D7}, + {0x1F6EB, 0x1F6EC}, + {0x1F6F4, 0x1F6FC}, + {0x1F7E0, 0x1F7EB}, + {0x1F90C, 0x1F93A}, + {0x1F93C, 0x1F945}, + {0x1F947, 0x1F978}, + {0x1F97A, 0x1F9CB}, + {0x1F9CD, 0x1F9FF}, + {0x1FA70, 0x1FA74}, + {0x1FA78, 0x1FA7A}, + {0x1FA80, 0x1FA86}, + {0x1FA90, 0x1FAA8}, + {0x1FAB0, 0x1FAB6}, + {0x1FAC0, 0x1FAC2}, + {0x1FAD0, 0x1FAD6}, + {0x20000, 0x2FFFD}, + {0x30000, 0x3FFFD}, + }; + + /* 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 */ + + /* binary search in table of double-width characters */ + if (bisearch(ucs, wide, + sizeof(wide) / sizeof(struct interval) - 1)) + return 2; + + /* normal width character */ + return 1; +} + + +int mk_wcswidth(const unsigned int *pwcs, size_t n) +{ + int w, width = 0; + + for (;*pwcs && n-- > 0; pwcs++) + if ((w = mk_wcwidth(*pwcs)) < 0) + return -1; + else + width += w; + + return width; +} + + +/* + * The following functions are the same as mk_wcwidth() and + * mk_wcswidth(), except that spacing characters in the East Asian + * Ambiguous (A) category as defined in Unicode Technical Report #11 + * have a column width of 2. This variant might be useful for users of + * CJK legacy encodings who want to migrate to UCS without changing + * the traditional terminal character-width behaviour. It is not + * otherwise recommended for general use. + */ +int mk_wcwidth_cjk(unsigned int ucs) +{ + /* A sorted list of intervals of ambiguous width characters generated by: + * https://raw.githubusercontent.com/GNOME/glib/37d4c2941bd0326b8b6e6bb22c81bd424fcc040b/glib/gen-unicode-tables.pl + * from the Unicode 9.0.0 data files available at: + * http://www.unicode.org/Public/9.0.0/ucd/ + */ + static const struct interval ambiguous[] = { + {0x00A1, 0x00A1}, + {0x00A4, 0x00A4}, + {0x00A7, 0x00A8}, + {0x00AA, 0x00AA}, + {0x00AD, 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}, + {0x0300, 0x036F}, + {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}, + {0x2189, 0x2189}, + {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}, + {0x261C, 0x261C}, + {0x261E, 0x261E}, + {0x2640, 0x2640}, + {0x2642, 0x2642}, + {0x2660, 0x2661}, + {0x2663, 0x2665}, + {0x2667, 0x266A}, + {0x266C, 0x266D}, + {0x266F, 0x266F}, + {0x269E, 0x269F}, + {0x26BF, 0x26BF}, + {0x26C6, 0x26CD}, + {0x26CF, 0x26D3}, + {0x26D5, 0x26E1}, + {0x26E3, 0x26E3}, + {0x26E8, 0x26E9}, + {0x26EB, 0x26F1}, + {0x26F4, 0x26F4}, + {0x26F6, 0x26F9}, + {0x26FB, 0x26FC}, + {0x26FE, 0x26FF}, + {0x273D, 0x273D}, + {0x2776, 0x277F}, + {0x2B56, 0x2B59}, + {0x3248, 0x324F}, + {0xE000, 0xF8FF}, + {0xFE00, 0xFE0F}, + {0xFFFD, 0xFFFD}, + {0x1F100, 0x1F10A}, + {0x1F110, 0x1F12D}, + {0x1F130, 0x1F169}, + {0x1F170, 0x1F18D}, + {0x1F18F, 0x1F190}, + {0x1F19B, 0x1F1AC}, + {0xE0100, 0xE01EF}, + {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 unsigned int *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; +} diff --git a/utils/wildcard.c b/utils/wildcard.c new file mode 100644 index 00000000..697feb9a --- /dev/null +++ b/utils/wildcard.c @@ -0,0 +1,486 @@ +/* + * 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 +#include +#include + +#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 *target_end) +{ + 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 < target_end) { + /* + * 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 == '[') { + bool invert = false; + bool matched = false; + /* + * Open bracket introduces a character class. + */ + f++; + if (*f == '^') { + invert = true; + 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 = true; + } 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. + */ +static int wc_match_inner( + const char *wildcard, const char *target, size_t target_len) +{ + const char *target_end = target + target_len; + 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, target_end); + 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, target_end); + + if (ret < 0) + return ret; /* syntax error */ + + if (ret > 0 && !*wildcard && target != target_end) { + /* + * 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 = target_end - (target - save_t); + wildcard = save_w; + return wc_match_fragment(&wildcard, &target, target_end); + } + + 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 == target_end; +} + +int wc_match(const char *wildcard, const char *target) +{ + return wc_match_inner(wildcard, target, strlen(target)); +} + +int wc_match_pl(const char *wildcard, ptrlen target) +{ + return wc_match_inner(wildcard, target.ptr, target.len); +} + +/* + * 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 true on success, or false 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! + */ +bool 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 false; /* it's a wildcard! */ + } else { + if (output) + *output++ = *wildcard; + wildcard++; + } + } + if (output) + *output = '\0'; + return true; /* 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, t + strlen(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 diff --git a/utils/write_c_string_literal.c b/utils/write_c_string_literal.c new file mode 100644 index 00000000..6415c287 --- /dev/null +++ b/utils/write_c_string_literal.c @@ -0,0 +1,31 @@ +/* + * Write data to a file in the form of a C string literal, with any + * non-printable-ASCII character escaped appropriately. + */ + +#include "defs.h" +#include "misc.h" + +void write_c_string_literal(FILE *fp, ptrlen str) +{ + for (const char *p = str.ptr; p < (const char *)str.ptr + str.len; p++) { + char c = *p; + + if (c == '\n') + fputs("\\n", fp); + else if (c == '\r') + fputs("\\r", fp); + else if (c == '\t') + fputs("\\t", fp); + else if (c == '\b') + fputs("\\b", fp); + else if (c == '\\') + fputs("\\\\", fp); + else if (c == '"') + fputs("\\\"", fp); + else if (c >= 32 && c <= 126) + fputc(c, fp); + else + fprintf(fp, "\\%03o", (unsigned char)c); + } +} diff --git a/version.c b/version.c deleted file mode 100644 index edc4da99..00000000 --- a/version.c +++ /dev/null @@ -1,23 +0,0 @@ -/* - * PuTTY version numbering - */ - -/* - * The difficult part of deciding what goes in these version strings - * is done in Buildscr, and then written into version.h. All we have - * to do here is to drop it into variables of the right names. - */ - -#include "putty.h" -#include "ssh.h" - -#include "version.h" - -const char ver[] = TEXTVER; -const char sshver[] = SSHVER; - -/* - * 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) }; diff --git a/wcwidth.c b/wcwidth.c deleted file mode 100644 index 6468fedd..00000000 --- a/wcwidth.c +++ /dev/null @@ -1,558 +0,0 @@ -/* - * 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 -- 2007-05-26 (Unicode 5.0) - * - * Permission to use, copy, modify, and distribute this software - * for any purpose and without fee is hereby granted. The author - * disclaims all warranties with regard to this software. - * - * Latest version: http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c - */ - -#include - -#include "putty.h" /* for prototypes */ - -struct interval { - unsigned int first; - unsigned int last; -}; - -/* auxiliary function for binary search in interval table */ -static bool bisearch(unsigned int ucs, const struct interval *table, int max) { - int min = 0; - int mid; - - if (ucs < table[0].first || ucs > table[max].last) - return false; - 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 true; - } - - return false; -} - - -/* 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(unsigned int ucs) -{ - /* sorted list of non-overlapping intervals of non-spacing characters */ - /* generated by "uniset +cat=Me +cat=Mn +cat=Cf -00AD +1160-11FF +200B c" */ - static const struct interval combining[] = { - { 0x0300, 0x036F }, { 0x0483, 0x0486 }, { 0x0488, 0x0489 }, - { 0x0591, 0x05BD }, { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 }, - { 0x05C4, 0x05C5 }, { 0x05C7, 0x05C7 }, { 0x0600, 0x0603 }, - { 0x0610, 0x0615 }, { 0x064B, 0x065E }, { 0x0670, 0x0670 }, - { 0x06D6, 0x06E4 }, { 0x06E7, 0x06E8 }, { 0x06EA, 0x06ED }, - { 0x070F, 0x070F }, { 0x0711, 0x0711 }, { 0x0730, 0x074A }, - { 0x07A6, 0x07B0 }, { 0x07EB, 0x07F3 }, { 0x0901, 0x0902 }, - { 0x093C, 0x093C }, { 0x0941, 0x0948 }, { 0x094D, 0x094D }, - { 0x0951, 0x0954 }, { 0x0962, 0x0963 }, { 0x0981, 0x0981 }, - { 0x09BC, 0x09BC }, { 0x09C1, 0x09C4 }, { 0x09CD, 0x09CD }, - { 0x09E2, 0x09E3 }, { 0x0A01, 0x0A02 }, { 0x0A3C, 0x0A3C }, - { 0x0A41, 0x0A42 }, { 0x0A47, 0x0A48 }, { 0x0A4B, 0x0A4D }, - { 0x0A70, 0x0A71 }, { 0x0A81, 0x0A82 }, { 0x0ABC, 0x0ABC }, - { 0x0AC1, 0x0AC5 }, { 0x0AC7, 0x0AC8 }, { 0x0ACD, 0x0ACD }, - { 0x0AE2, 0x0AE3 }, { 0x0B01, 0x0B01 }, { 0x0B3C, 0x0B3C }, - { 0x0B3F, 0x0B3F }, { 0x0B41, 0x0B43 }, { 0x0B4D, 0x0B4D }, - { 0x0B56, 0x0B56 }, { 0x0B82, 0x0B82 }, { 0x0BC0, 0x0BC0 }, - { 0x0BCD, 0x0BCD }, { 0x0C3E, 0x0C40 }, { 0x0C46, 0x0C48 }, - { 0x0C4A, 0x0C4D }, { 0x0C55, 0x0C56 }, { 0x0CBC, 0x0CBC }, - { 0x0CBF, 0x0CBF }, { 0x0CC6, 0x0CC6 }, { 0x0CCC, 0x0CCD }, - { 0x0CE2, 0x0CE3 }, { 0x0D41, 0x0D43 }, { 0x0D4D, 0x0D4D }, - { 0x0DCA, 0x0DCA }, { 0x0DD2, 0x0DD4 }, { 0x0DD6, 0x0DD6 }, - { 0x0E31, 0x0E31 }, { 0x0E34, 0x0E3A }, { 0x0E47, 0x0E4E }, - { 0x0EB1, 0x0EB1 }, { 0x0EB4, 0x0EB9 }, { 0x0EBB, 0x0EBC }, - { 0x0EC8, 0x0ECD }, { 0x0F18, 0x0F19 }, { 0x0F35, 0x0F35 }, - { 0x0F37, 0x0F37 }, { 0x0F39, 0x0F39 }, { 0x0F71, 0x0F7E }, - { 0x0F80, 0x0F84 }, { 0x0F86, 0x0F87 }, { 0x0F90, 0x0F97 }, - { 0x0F99, 0x0FBC }, { 0x0FC6, 0x0FC6 }, { 0x102D, 0x1030 }, - { 0x1032, 0x1032 }, { 0x1036, 0x1037 }, { 0x1039, 0x1039 }, - { 0x1058, 0x1059 }, { 0x1160, 0x11FF }, { 0x135F, 0x135F }, - { 0x1712, 0x1714 }, { 0x1732, 0x1734 }, { 0x1752, 0x1753 }, - { 0x1772, 0x1773 }, { 0x17B4, 0x17B5 }, { 0x17B7, 0x17BD }, - { 0x17C6, 0x17C6 }, { 0x17C9, 0x17D3 }, { 0x17DD, 0x17DD }, - { 0x180B, 0x180D }, { 0x18A9, 0x18A9 }, { 0x1920, 0x1922 }, - { 0x1927, 0x1928 }, { 0x1932, 0x1932 }, { 0x1939, 0x193B }, - { 0x1A17, 0x1A18 }, { 0x1B00, 0x1B03 }, { 0x1B34, 0x1B34 }, - { 0x1B36, 0x1B3A }, { 0x1B3C, 0x1B3C }, { 0x1B42, 0x1B42 }, - { 0x1B6B, 0x1B73 }, { 0x1DC0, 0x1DCA }, { 0x1DFE, 0x1DFF }, - { 0x200B, 0x200F }, { 0x202A, 0x202E }, { 0x2060, 0x2063 }, - { 0x206A, 0x206F }, { 0x20D0, 0x20EF }, { 0x302A, 0x302F }, - { 0x3099, 0x309A }, { 0xA806, 0xA806 }, { 0xA80B, 0xA80B }, - { 0xA825, 0xA826 }, { 0xFB1E, 0xFB1E }, { 0xFE00, 0xFE0F }, - { 0xFE20, 0xFE23 }, { 0xFEFF, 0xFEFF }, { 0xFFF9, 0xFFFB }, - { 0x10A01, 0x10A03 }, { 0x10A05, 0x10A06 }, { 0x10A0C, 0x10A0F }, - { 0x10A38, 0x10A3A }, { 0x10A3F, 0x10A3F }, { 0x1D167, 0x1D169 }, - { 0x1D173, 0x1D182 }, { 0x1D185, 0x1D18B }, { 0x1D1AA, 0x1D1AD }, - { 0x1D242, 0x1D244 }, { 0xE0001, 0xE0001 }, { 0xE0020, 0xE007F }, - { 0xE0100, 0xE01EF } - }; - - /* A sorted list of intervals of double-width characters generated by: - * https://raw.githubusercontent.com/GNOME/glib/37d4c2941bd0326b8b6e6bb22c81bd424fcc040b/glib/gen-unicode-tables.pl - * from the Unicode 9.0.0 data files available at: - * https://www.unicode.org/Public/13.0.0/ucd/ - */ - static const struct interval wide[] = { - {0x1100, 0x115F}, - {0x231A, 0x231B}, - {0x2329, 0x232A}, - {0x23E9, 0x23EC}, - {0x23F0, 0x23F0}, - {0x23F3, 0x23F3}, - {0x25FD, 0x25FE}, - {0x2614, 0x2615}, - {0x2648, 0x2653}, - {0x267F, 0x267F}, - {0x2693, 0x2693}, - {0x26A1, 0x26A1}, - {0x26AA, 0x26AB}, - {0x26BD, 0x26BE}, - {0x26C4, 0x26C5}, - {0x26CE, 0x26CE}, - {0x26D4, 0x26D4}, - {0x26EA, 0x26EA}, - {0x26F2, 0x26F3}, - {0x26F5, 0x26F5}, - {0x26FA, 0x26FA}, - {0x26FD, 0x26FD}, - {0x2705, 0x2705}, - {0x270A, 0x270B}, - {0x2728, 0x2728}, - {0x274C, 0x274C}, - {0x274E, 0x274E}, - {0x2753, 0x2755}, - {0x2757, 0x2757}, - {0x2795, 0x2797}, - {0x27B0, 0x27B0}, - {0x27BF, 0x27BF}, - {0x2B1B, 0x2B1C}, - {0x2B50, 0x2B50}, - {0x2B55, 0x2B55}, - {0x2E80, 0x2E99}, - {0x2E9B, 0x2EF3}, - {0x2F00, 0x2FD5}, - {0x2FF0, 0x2FFB}, - {0x3000, 0x303E}, - {0x3041, 0x3096}, - {0x3099, 0x30FF}, - {0x3105, 0x312F}, - {0x3131, 0x318E}, - {0x3190, 0x31E3}, - {0x31F0, 0x321E}, - {0x3220, 0x3247}, - {0x3250, 0x4DBF}, - {0x4E00, 0xA48C}, - {0xA490, 0xA4C6}, - {0xA960, 0xA97C}, - {0xAC00, 0xD7A3}, - {0xF900, 0xFAFF}, - {0xFE10, 0xFE19}, - {0xFE30, 0xFE52}, - {0xFE54, 0xFE66}, - {0xFE68, 0xFE6B}, - {0xFF01, 0xFF60}, - {0xFFE0, 0xFFE6}, - {0x16FE0, 0x16FE4}, - {0x16FF0, 0x16FF1}, - {0x17000, 0x187F7}, - {0x18800, 0x18CD5}, - {0x18D00, 0x18D08}, - {0x1B000, 0x1B11E}, - {0x1B150, 0x1B152}, - {0x1B164, 0x1B167}, - {0x1B170, 0x1B2FB}, - {0x1F004, 0x1F004}, - {0x1F0CF, 0x1F0CF}, - {0x1F18E, 0x1F18E}, - {0x1F191, 0x1F19A}, - {0x1F200, 0x1F202}, - {0x1F210, 0x1F23B}, - {0x1F240, 0x1F248}, - {0x1F250, 0x1F251}, - {0x1F260, 0x1F265}, - {0x1F300, 0x1F320}, - {0x1F32D, 0x1F335}, - {0x1F337, 0x1F37C}, - {0x1F37E, 0x1F393}, - {0x1F3A0, 0x1F3CA}, - {0x1F3CF, 0x1F3D3}, - {0x1F3E0, 0x1F3F0}, - {0x1F3F4, 0x1F3F4}, - {0x1F3F8, 0x1F43E}, - {0x1F440, 0x1F440}, - {0x1F442, 0x1F4FC}, - {0x1F4FF, 0x1F53D}, - {0x1F54B, 0x1F54E}, - {0x1F550, 0x1F567}, - {0x1F57A, 0x1F57A}, - {0x1F595, 0x1F596}, - {0x1F5A4, 0x1F5A4}, - {0x1F5FB, 0x1F64F}, - {0x1F680, 0x1F6C5}, - {0x1F6CC, 0x1F6CC}, - {0x1F6D0, 0x1F6D2}, - {0x1F6D5, 0x1F6D7}, - {0x1F6EB, 0x1F6EC}, - {0x1F6F4, 0x1F6FC}, - {0x1F7E0, 0x1F7EB}, - {0x1F90C, 0x1F93A}, - {0x1F93C, 0x1F945}, - {0x1F947, 0x1F978}, - {0x1F97A, 0x1F9CB}, - {0x1F9CD, 0x1F9FF}, - {0x1FA70, 0x1FA74}, - {0x1FA78, 0x1FA7A}, - {0x1FA80, 0x1FA86}, - {0x1FA90, 0x1FAA8}, - {0x1FAB0, 0x1FAB6}, - {0x1FAC0, 0x1FAC2}, - {0x1FAD0, 0x1FAD6}, - {0x20000, 0x2FFFD}, - {0x30000, 0x3FFFD}, - }; - - /* 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 */ - - /* binary search in table of double-width characters */ - if (bisearch(ucs, wide, - sizeof(wide) / sizeof(struct interval) - 1)) - return 2; - - /* normal width character */ - return 1; -} - - -int mk_wcswidth(const unsigned int *pwcs, size_t n) -{ - int w, width = 0; - - for (;*pwcs && n-- > 0; pwcs++) - if ((w = mk_wcwidth(*pwcs)) < 0) - return -1; - else - width += w; - - return width; -} - - -/* - * The following functions are the same as mk_wcwidth() and - * mk_wcswidth(), except that spacing characters in the East Asian - * Ambiguous (A) category as defined in Unicode Technical Report #11 - * have a column width of 2. This variant might be useful for users of - * CJK legacy encodings who want to migrate to UCS without changing - * the traditional terminal character-width behaviour. It is not - * otherwise recommended for general use. - */ -int mk_wcwidth_cjk(unsigned int ucs) -{ - /* A sorted list of intervals of ambiguous width characters generated by: - * https://raw.githubusercontent.com/GNOME/glib/37d4c2941bd0326b8b6e6bb22c81bd424fcc040b/glib/gen-unicode-tables.pl - * from the Unicode 9.0.0 data files available at: - * http://www.unicode.org/Public/9.0.0/ucd/ - */ - static const struct interval ambiguous[] = { - {0x00A1, 0x00A1}, - {0x00A4, 0x00A4}, - {0x00A7, 0x00A8}, - {0x00AA, 0x00AA}, - {0x00AD, 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}, - {0x0300, 0x036F}, - {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}, - {0x2189, 0x2189}, - {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}, - {0x261C, 0x261C}, - {0x261E, 0x261E}, - {0x2640, 0x2640}, - {0x2642, 0x2642}, - {0x2660, 0x2661}, - {0x2663, 0x2665}, - {0x2667, 0x266A}, - {0x266C, 0x266D}, - {0x266F, 0x266F}, - {0x269E, 0x269F}, - {0x26BF, 0x26BF}, - {0x26C6, 0x26CD}, - {0x26CF, 0x26D3}, - {0x26D5, 0x26E1}, - {0x26E3, 0x26E3}, - {0x26E8, 0x26E9}, - {0x26EB, 0x26F1}, - {0x26F4, 0x26F4}, - {0x26F6, 0x26F9}, - {0x26FB, 0x26FC}, - {0x26FE, 0x26FF}, - {0x273D, 0x273D}, - {0x2776, 0x277F}, - {0x2B56, 0x2B59}, - {0x3248, 0x324F}, - {0xE000, 0xF8FF}, - {0xFE00, 0xFE0F}, - {0xFFFD, 0xFFFD}, - {0x1F100, 0x1F10A}, - {0x1F110, 0x1F12D}, - {0x1F130, 0x1F169}, - {0x1F170, 0x1F18D}, - {0x1F18F, 0x1F190}, - {0x1F19B, 0x1F1AC}, - {0xE0100, 0xE01EF}, - {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 unsigned int *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; -} diff --git a/wildcard.c b/wildcard.c deleted file mode 100644 index 697feb9a..00000000 --- a/wildcard.c +++ /dev/null @@ -1,486 +0,0 @@ -/* - * 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 -#include -#include - -#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 *target_end) -{ - 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 < target_end) { - /* - * 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 == '[') { - bool invert = false; - bool matched = false; - /* - * Open bracket introduces a character class. - */ - f++; - if (*f == '^') { - invert = true; - 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 = true; - } 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. - */ -static int wc_match_inner( - const char *wildcard, const char *target, size_t target_len) -{ - const char *target_end = target + target_len; - 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, target_end); - 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, target_end); - - if (ret < 0) - return ret; /* syntax error */ - - if (ret > 0 && !*wildcard && target != target_end) { - /* - * 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 = target_end - (target - save_t); - wildcard = save_w; - return wc_match_fragment(&wildcard, &target, target_end); - } - - 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 == target_end; -} - -int wc_match(const char *wildcard, const char *target) -{ - return wc_match_inner(wildcard, target, strlen(target)); -} - -int wc_match_pl(const char *wildcard, ptrlen target) -{ - return wc_match_inner(wildcard, target.ptr, target.len); -} - -/* - * 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 true on success, or false 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! - */ -bool 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 false; /* it's a wildcard! */ - } else { - if (output) - *output++ = *wildcard; - wildcard++; - } - } - if (output) - *output = '\0'; - return true; /* 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, t + strlen(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 diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt index 4db250f8..ff6805f0 100644 --- a/windows/CMakeLists.txt +++ b/windows/CMakeLists.txt @@ -1,8 +1,38 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) add_platform_sources_to_library(utils - wincapi.c winutils.c winucs.c winmisc.c winmiscs.c wintime.c windefs.c - winsecur.c) + utils/arm_arch_queries.c + utils/capi.c + utils/defaults.c + utils/dll_hijacking_protection.c + utils/dputs.c + utils/escape_registry_key.c + utils/filename.c + utils/fontspec.c + utils/getdlgitemtext_alloc.c + utils/get_username.c + utils/is_console_handle.c + utils/load_system32_dll.c + utils/ltime.c + utils/makedlgitemborderless.c + utils/message_box.c + utils/minefield.c + utils/open_for_write_would_lose_data.c + utils/pgp_fingerprints_msgbox.c + utils/platform_get_x_display.c + utils/registry_get_string.c + utils/request_file.c + utils/security.c + utils/split_into_argv.c + utils/version.c + utils/win_strerror.c + winucs.c) +if(NOT HAVE_SECUREZEROMEMORY) + add_platform_sources_to_library(utils ../utils/smemclr.c) +endif() +if(NOT HAVE_STRTOUMAX) + add_platform_sources_to_library(utils utils/strtoumax.c) +endif() add_platform_sources_to_library(eventloop wincliloop.c winhandl.c) add_platform_sources_to_library(console diff --git a/windows/utils/arm_arch_queries.c b/windows/utils/arm_arch_queries.c new file mode 100644 index 00000000..05132b14 --- /dev/null +++ b/windows/utils/arm_arch_queries.c @@ -0,0 +1,39 @@ +/* + * Windows implementation of the OS query functions that detect Arm + * architecture extensions. + */ + +#include "putty.h" + +#if !(defined _M_ARM || defined _M_ARM64) +/* + * For non-Arm, stub out these functions. This module shouldn't be + * _called_ in that situation anyway, but it will still be compiled + * (because that's easier than getting CMake to identify the + * architecture in all cases). + */ +#define IsProcessorFeaturePresent(...) false +#endif + +bool platform_aes_hw_available(void) +{ + return IsProcessorFeaturePresent(PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE); +} + +bool platform_sha256_hw_available(void) +{ + return IsProcessorFeaturePresent(PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE); +} + +bool platform_sha1_hw_available(void) +{ + return IsProcessorFeaturePresent(PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE); +} + +bool platform_sha512_hw_available(void) +{ + /* As of 2020-12-24, as far as I can tell from docs.microsoft.com, + * Windows on Arm does not yet provide a PF_ARM_V8_* flag for the + * SHA-512 architecture extension. */ + return false; +} diff --git a/windows/utils/capi.c b/windows/utils/capi.c new file mode 100644 index 00000000..3d38ae04 --- /dev/null +++ b/windows/utils/capi.c @@ -0,0 +1,85 @@ +/* + * windows/utils/capi.c: implementation of wincapi.h. + */ + +#include "putty.h" + +#include "putty.h" +#include "ssh.h" + +#include "wincapi.h" + +DEF_WINDOWS_FUNCTION(CryptProtectMemory); + +bool got_crypt(void) +{ + static bool attempted = false; + static bool successful; + static HMODULE crypt; + + if (!attempted) { + attempted = true; + crypt = load_system32_dll("crypt32.dll"); + successful = crypt && + GET_WINDOWS_FUNCTION(crypt, CryptProtectMemory); + } + return successful; +} + +char *capi_obfuscate_string(const char *realname) +{ + char *cryptdata; + int cryptlen; + unsigned char digest[32]; + char retbuf[65]; + int i; + + cryptlen = strlen(realname) + 1; + cryptlen += CRYPTPROTECTMEMORY_BLOCK_SIZE - 1; + cryptlen /= CRYPTPROTECTMEMORY_BLOCK_SIZE; + cryptlen *= CRYPTPROTECTMEMORY_BLOCK_SIZE; + + cryptdata = snewn(cryptlen, char); + memset(cryptdata, 0, cryptlen); + strcpy(cryptdata, realname); + + /* + * CRYPTPROTECTMEMORY_CROSS_PROCESS causes CryptProtectMemory to + * use the same key in all processes with this user id, meaning + * that the next PuTTY process calling this function with the same + * input will get the same data. + * + * (Contrast with CryptProtectData, which invents a new session + * key every time since its API permits returning more data than + * was input, so calling _that_ and hashing the output would not + * be stable.) + * + * We don't worry too much if this doesn't work for some reason. + * Omitting this step still has _some_ privacy value (in that + * another user can test-hash things to confirm guesses as to + * where you might be connecting to, but cannot invert SHA-256 in + * the absence of any plausible guess). So we don't abort if we + * can't call CryptProtectMemory at all, or if it fails. + */ + if (got_crypt()) + p_CryptProtectMemory(cryptdata, cryptlen, + CRYPTPROTECTMEMORY_CROSS_PROCESS); + + /* + * We don't want to give away the length of the hostname either, + * so having got it back out of CryptProtectMemory we now hash it. + */ + hash_simple(&ssh_sha256, make_ptrlen(cryptdata, cryptlen), digest); + + sfree(cryptdata); + + /* + * Finally, make printable. + */ + for (i = 0; i < 32; i++) { + sprintf(retbuf + 2*i, "%02x", digest[i]); + /* the last of those will also write the trailing NUL */ + } + + return dupstr(retbuf); +} diff --git a/windows/utils/defaults.c b/windows/utils/defaults.c new file mode 100644 index 00000000..1a270009 --- /dev/null +++ b/windows/utils/defaults.c @@ -0,0 +1,40 @@ +/* + * windows/utils/defaults.c: default settings that are specific to Windows. + */ + +#include "putty.h" + +#include + +FontSpec *platform_default_fontspec(const char *name) +{ + if (!strcmp(name, "Font")) + return fontspec_new("Courier New", false, 10, ANSI_CHARSET); + else + return fontspec_new("", false, 0, 0); +} + +Filename *platform_default_filename(const char *name) +{ + if (!strcmp(name, "LogFileName")) + return filename_from_str("putty.log"); + else + return filename_from_str(""); +} + +char *platform_default_s(const char *name) +{ + if (!strcmp(name, "SerialLine")) + return dupstr("COM1"); + return NULL; +} + +bool platform_default_b(const char *name, bool def) +{ + return def; +} + +int platform_default_i(const char *name, int def) +{ + return def; +} diff --git a/windows/utils/dll_hijacking_protection.c b/windows/utils/dll_hijacking_protection.c new file mode 100644 index 00000000..fe9ae59c --- /dev/null +++ b/windows/utils/dll_hijacking_protection.c @@ -0,0 +1,43 @@ +/* + * If the OS provides it, call SetDefaultDllDirectories() to prevent + * DLLs from being loaded from the directory containing our own + * binary, and instead only load from system32. + * + * This is a protection against hijacking attacks, if someone runs + * PuTTY directly from their web browser's download directory having + * previously been enticed into clicking on an unwise link that + * downloaded a malicious DLL to the same directory under one of + * various magic names that seem to be things that standard Windows + * DLLs delegate to. + * + * It shouldn't break deliberate loading of user-provided DLLs such as + * GSSAPI providers, because those are specified by their full + * pathname by the user-provided configuration. + */ + +#include "putty.h" + +void dll_hijacking_protection(void) +{ + static HMODULE kernel32_module; + DECL_WINDOWS_FUNCTION(static, BOOL, SetDefaultDllDirectories, (DWORD)); + + if (!kernel32_module) { + kernel32_module = load_system32_dll("kernel32.dll"); +#if !HAVE_SETDEFAULTDLLDIRECTORIES + /* For older Visual Studio, this function isn't available in + * the header files to type-check */ + GET_WINDOWS_FUNCTION_NO_TYPECHECK( + kernel32_module, SetDefaultDllDirectories); +#else + GET_WINDOWS_FUNCTION(kernel32_module, SetDefaultDllDirectories); +#endif + } + + if (p_SetDefaultDllDirectories) { + /* LOAD_LIBRARY_SEARCH_SYSTEM32 and explicitly specified + * directories only */ + p_SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_SYSTEM32 | + LOAD_LIBRARY_SEARCH_USER_DIRS); + } +} diff --git a/windows/utils/dputs.c b/windows/utils/dputs.c new file mode 100644 index 00000000..15b0e4db --- /dev/null +++ b/windows/utils/dputs.c @@ -0,0 +1,37 @@ +/* + * Implementation of dputs() for Windows. + * + * The debug messages are written to STD_OUTPUT_HANDLE, except that + * first it has to make sure that handle _exists_, by calling + * AllocConsole first if necessary. + * + * They also go into a file called debug.log. + */ + +#include "putty.h" +#include "utils/utils.h" + +static FILE *debug_fp = NULL; +static HANDLE debug_hdl = INVALID_HANDLE_VALUE; +static int debug_got_console = 0; + +void dputs(const 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); +} diff --git a/windows/utils/escape_registry_key.c b/windows/utils/escape_registry_key.c new file mode 100644 index 00000000..f5c9c19e --- /dev/null +++ b/windows/utils/escape_registry_key.c @@ -0,0 +1,48 @@ +/* + * Escaping/unescaping functions to translate between a saved session + * name, and the key name used to represent it in the Registry area + * where we store saved sessions. + * + * The basic technique is to %-escape characters we can't use in + * Registry keys. + */ + +#include "putty.h" + +void escape_registry_key(const char *in, strbuf *out) +{ + bool candot = false; + static const char hex[16] = "0123456789ABCDEF"; + + while (*in) { + if (*in == ' ' || *in == '\\' || *in == '*' || *in == '?' || + *in == '%' || *in < ' ' || *in > '~' || (*in == '.' + && !candot)) { + put_byte(out, '%'); + put_byte(out, hex[((unsigned char) *in) >> 4]); + put_byte(out, hex[((unsigned char) *in) & 15]); + } else + put_byte(out, *in); + in++; + candot = true; + } +} + +void unescape_registry_key(const char *in, strbuf *out) +{ + 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); + + put_byte(out, (i << 4) + j); + in += 3; + } else { + put_byte(out, *in++); + } + } +} diff --git a/windows/utils/filename.c b/windows/utils/filename.c new file mode 100644 index 00000000..f4457ecb --- /dev/null +++ b/windows/utils/filename.c @@ -0,0 +1,54 @@ +/* + * Implementation of Filename for Windows. + */ + +#include "putty.h" + +Filename *filename_from_str(const char *str) +{ + Filename *ret = snew(Filename); + ret->path = dupstr(str); + return ret; +} + +Filename *filename_copy(const Filename *fn) +{ + return filename_from_str(fn->path); +} + +const char *filename_to_str(const Filename *fn) +{ + return fn->path; +} + +bool filename_equal(const Filename *f1, const Filename *f2) +{ + return !strcmp(f1->path, f2->path); +} + +bool filename_is_null(const Filename *fn) +{ + return !*fn->path; +} + +void filename_free(Filename *fn) +{ + sfree(fn->path); + sfree(fn); +} + +void filename_serialise(BinarySink *bs, const Filename *f) +{ + put_asciz(bs, f->path); +} +Filename *filename_deserialise(BinarySource *src) +{ + return filename_from_str(get_asciz(src)); +} + +char filename_char_sanitise(char c) +{ + if (strchr("<>:\"/\\|?*", c)) + return '.'; + return c; +} diff --git a/windows/utils/fontspec.c b/windows/utils/fontspec.c new file mode 100644 index 00000000..7e8d5175 --- /dev/null +++ b/windows/utils/fontspec.c @@ -0,0 +1,43 @@ +/* + * Implementation of FontSpec for Windows. + */ + +#include "putty.h" + +FontSpec *fontspec_new(const char *name, bool bold, int height, int charset) +{ + FontSpec *f = snew(FontSpec); + f->name = dupstr(name); + f->isbold = bold; + f->height = height; + f->charset = charset; + return f; +} + +FontSpec *fontspec_copy(const FontSpec *f) +{ + return fontspec_new(f->name, f->isbold, f->height, f->charset); +} + +void fontspec_free(FontSpec *f) +{ + sfree(f->name); + sfree(f); +} + +void fontspec_serialise(BinarySink *bs, FontSpec *f) +{ + put_asciz(bs, f->name); + put_uint32(bs, f->isbold); + put_uint32(bs, f->height); + put_uint32(bs, f->charset); +} + +FontSpec *fontspec_deserialise(BinarySource *src) +{ + const char *name = get_asciz(src); + unsigned isbold = get_uint32(src); + unsigned height = get_uint32(src); + unsigned charset = get_uint32(src); + return fontspec_new(name, isbold, height, charset); +} diff --git a/windows/utils/get_username.c b/windows/utils/get_username.c new file mode 100644 index 00000000..bc5780e4 --- /dev/null +++ b/windows/utils/get_username.c @@ -0,0 +1,77 @@ +/* + * Implementation of get_username() for Windows. + */ + +#include "putty.h" + +#ifndef SECURITY_WIN32 +#define SECURITY_WIN32 +#endif +#include + +char *get_username(void) +{ + DWORD namelen; + char *user; + bool got_username = false; + DECL_WINDOWS_FUNCTION(static, BOOLEAN, GetUserNameExA, + (EXTENDED_NAME_FORMAT, LPSTR, PULONG)); + + { + static bool tried_usernameex = false; + if (!tried_usernameex) { + /* Not available on Win9x, so load dynamically */ + HMODULE secur32 = load_system32_dll("secur32.dll"); + /* If MIT Kerberos is installed, the following call to + GET_WINDOWS_FUNCTION makes Windows implicitly load + sspicli.dll WITHOUT proper path sanitizing, so better + load it properly before */ + HMODULE sspicli = load_system32_dll("sspicli.dll"); + (void)sspicli; /* squash compiler warning about unused variable */ + 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)) { + /* + * 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; +} diff --git a/windows/utils/getdlgitemtext_alloc.c b/windows/utils/getdlgitemtext_alloc.c new file mode 100644 index 00000000..f0244d71 --- /dev/null +++ b/windows/utils/getdlgitemtext_alloc.c @@ -0,0 +1,20 @@ +/* + * Handy wrapper around GetDlgItemText which doesn't make you invent + * an arbitrary length limit on the output string. Returned string is + * dynamically allocated; caller must free. + */ + +#include "putty.h" + +char *GetDlgItemText_alloc(HWND hwnd, int id) +{ + char *ret = NULL; + size_t size = 0; + + do { + sgrowarray_nm(ret, size, size); + GetDlgItemText(hwnd, id, ret, size); + } while (!memchr(ret, '\0', size-1)); + + return ret; +} diff --git a/windows/utils/is_console_handle.c b/windows/utils/is_console_handle.c new file mode 100644 index 00000000..887069c9 --- /dev/null +++ b/windows/utils/is_console_handle.c @@ -0,0 +1,13 @@ +/* + * Determine whether a Windows HANDLE points at a console device. + */ + +#include "putty.h" + +bool is_console_handle(HANDLE handle) +{ + DWORD ignored_output; + if (GetConsoleMode(handle, &ignored_output)) + return true; + return false; +} diff --git a/windows/utils/load_system32_dll.c b/windows/utils/load_system32_dll.c new file mode 100644 index 00000000..e00d7c34 --- /dev/null +++ b/windows/utils/load_system32_dll.c @@ -0,0 +1,26 @@ +/* + * 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.) + */ + +#include "putty.h" + +HMODULE load_system32_dll(const char *libname) +{ + static char *sysdir = NULL; + static size_t sysdirsize = 0; + char *fullpath; + HMODULE ret; + + if (!sysdir) { + size_t len; + while ((len = GetSystemDirectory(sysdir, sysdirsize)) >= sysdirsize) + sgrowarray(sysdir, sysdirsize, len); + } + + fullpath = dupcat(sysdir, "\\", libname); + ret = LoadLibrary(fullpath); + sfree(fullpath); + return ret; +} diff --git a/windows/utils/ltime.c b/windows/utils/ltime.c new file mode 100644 index 00000000..d4364509 --- /dev/null +++ b/windows/utils/ltime.c @@ -0,0 +1,27 @@ +/* + * Implementation of ltime() that avoids trouble with time() returning + * (time_t)-1 on Windows. + */ + +#include "putty.h" +#include + +struct tm ltime(void) +{ + SYSTEMTIME st; + struct tm tm; + + memset(&tm, 0, sizeof(tm)); /* in case there are any other fields */ + + 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; +} diff --git a/windows/utils/makedlgitemborderless.c b/windows/utils/makedlgitemborderless.c new file mode 100644 index 00000000..53975d06 --- /dev/null +++ b/windows/utils/makedlgitemborderless.c @@ -0,0 +1,19 @@ +/* + * Helper function to remove the border around a dialog item such as + * a read-only edit control. + */ + +#include "putty.h" + +void MakeDlgItemBorderless(HWND parent, int id) +{ + HWND child = GetDlgItem(parent, id); + LONG_PTR style = GetWindowLongPtr(child, GWL_STYLE); + LONG_PTR exstyle = GetWindowLongPtr(child, GWL_EXSTYLE); + style &= ~WS_BORDER; + exstyle &= ~(WS_EX_CLIENTEDGE | WS_EX_STATICEDGE | WS_EX_WINDOWEDGE); + SetWindowLongPtr(child, GWL_STYLE, style); + SetWindowLongPtr(child, GWL_EXSTYLE, exstyle); + SetWindowPos(child, NULL, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED); +} diff --git a/windows/utils/message_box.c b/windows/utils/message_box.c new file mode 100644 index 00000000..ae78de4a --- /dev/null +++ b/windows/utils/message_box.c @@ -0,0 +1,49 @@ +/* + * Message box with optional context help. + */ + +#include "putty.h" + +static HWND message_box_owner; + +/* Callback function to launch context help. */ +static VOID CALLBACK message_box_help_callback(LPHELPINFO lpHelpInfo) +{ + const 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(message_box_owner, context); +} + +int message_box(HWND owner, 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 = message_box_owner = owner; + 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); +} diff --git a/windows/utils/minefield.c b/windows/utils/minefield.c new file mode 100644 index 00000000..de4d4836 --- /dev/null +++ b/windows/utils/minefield.c @@ -0,0 +1,227 @@ +/* + * 'Minefield' - a crude Windows memory debugger, similar in concept + * to the old Unix 'Electric Fence'. The main difference is that + * Electric Fence can be imposed on a program from outside, via + * LD_PRELOAD, whereas this has to be included in the program at + * compile time with its own cooperation. + * + * This module provides the Minefield allocator. Actually enabling it + * is done by a #define in force when the main utils/memory.c is + * compiled. + */ + +#include "putty.h" + +#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; +} diff --git a/windows/utils/open_for_write_would_lose_data.c b/windows/utils/open_for_write_would_lose_data.c new file mode 100644 index 00000000..3891f51e --- /dev/null +++ b/windows/utils/open_for_write_would_lose_data.c @@ -0,0 +1,38 @@ +/* + * Implementation of open_for_write_would_lose_data for Windows. + */ + +#include "putty.h" + +bool open_for_write_would_lose_data(const Filename *fn) +{ + WIN32_FILE_ATTRIBUTE_DATA attrs; + if (!GetFileAttributesEx(fn->path, GetFileExInfoStandard, &attrs)) { + /* + * Generally, if we don't identify a specific reason why we + * should return true from this function, we return false, and + * let the subsequent attempt to open the file for real give a + * more useful error message. + */ + return false; + } + if (attrs.dwFileAttributes & (FILE_ATTRIBUTE_DEVICE | + FILE_ATTRIBUTE_DIRECTORY)) { + /* + * File is something other than an ordinary disk file, so + * opening it for writing will not cause truncation. (It may + * not _succeed_ either, but that's not our problem here!) + */ + return false; + } + if (attrs.nFileSizeHigh == 0 && attrs.nFileSizeLow == 0) { + /* + * File is zero-length (or may be a named pipe, which + * dwFileAttributes can't tell apart from a regular file), so + * opening it for writing won't truncate any data away because + * there's nothing to truncate anyway. + */ + return false; + } + return true; +} diff --git a/windows/utils/pgp_fingerprints_msgbox.c b/windows/utils/pgp_fingerprints_msgbox.c new file mode 100644 index 00000000..6618de82 --- /dev/null +++ b/windows/utils/pgp_fingerprints_msgbox.c @@ -0,0 +1,25 @@ +/* + * Display the fingerprints of the PGP Master Keys to the user as a + * GUI message box. + */ + +#include "putty.h" + +void pgp_fingerprints_msgbox(HWND owner) +{ + message_box( + owner, + "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 as of " PGP_MASTER_KEY_YEAR + " (" PGP_MASTER_KEY_DETAILS "):\n" + " " PGP_MASTER_KEY_FP "\n\n" + "Previous Master Key (" PGP_PREV_MASTER_KEY_YEAR + ", " PGP_PREV_MASTER_KEY_DETAILS "):\n" + " " PGP_PREV_MASTER_KEY_FP, + "PGP fingerprints", MB_ICONINFORMATION | MB_OK, + HELPCTXID(pgp_fingerprints)); +} diff --git a/windows/utils/platform_get_x_display.c b/windows/utils/platform_get_x_display.c new file mode 100644 index 00000000..b3d11afc --- /dev/null +++ b/windows/utils/platform_get_x_display.c @@ -0,0 +1,12 @@ +/* + * Implementation of platform_get_x_display for Windows, common to all + * tools. + */ + +#include "putty.h" + +char *platform_get_x_display(void) +{ + /* We may as well check for DISPLAY in case it's useful. */ + return dupstr(getenv("DISPLAY")); +} diff --git a/windows/utils/registry_get_string.c b/windows/utils/registry_get_string.c new file mode 100644 index 00000000..c3745b92 --- /dev/null +++ b/windows/utils/registry_get_string.c @@ -0,0 +1,43 @@ +/* + * Self-contained function to try to fetch a single string value from + * the Registry, and return it as a dynamically allocated C string. + */ + +#include "putty.h" + +char *registry_get_string(HKEY root, const char *path, const char *leaf) +{ + HKEY key = root; + bool need_close_key = false; + char *toret = NULL, *str = NULL; + + if (path) { + if (RegCreateKey(key, path, &key) != ERROR_SUCCESS) + goto out; + need_close_key = true; + } + + DWORD type, size; + if (RegQueryValueEx(key, leaf, 0, &type, NULL, &size) != ERROR_SUCCESS) + goto out; + if (type != REG_SZ) + goto out; + + str = snewn(size + 1, char); + DWORD size_got = size; + if (RegQueryValueEx(key, leaf, 0, &type, (LPBYTE)str, + &size_got) != ERROR_SUCCESS) + goto out; + if (type != REG_SZ || size_got > size) + goto out; + str[size_got] = '\0'; + + toret = str; + str = NULL; + + out: + if (need_close_key) + RegCloseKey(key); + sfree(str); + return toret; +} diff --git a/windows/utils/request_file.c b/windows/utils/request_file.c new file mode 100644 index 00000000..dd2cab18 --- /dev/null +++ b/windows/utils/request_file.c @@ -0,0 +1,71 @@ +/* + * 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. + */ + +#include "putty.h" + +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, bool preserve, bool 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 = false; + } + + /* 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); +} diff --git a/windows/utils/security.c b/windows/utils/security.c new file mode 100644 index 00000000..69ac96f1 --- /dev/null +++ b/windows/utils/security.c @@ -0,0 +1,326 @@ +/* + * windows/utils/security.c: implementation of winsecur.h. + */ + +#include +#include + +#include "putty.h" + +#include "winsecur.h" + +/* Initialised once, then kept around to reuse forever */ +static PSID worldsid, networksid, usersid; + +DEF_WINDOWS_FUNCTION(OpenProcessToken); +DEF_WINDOWS_FUNCTION(GetTokenInformation); +DEF_WINDOWS_FUNCTION(InitializeSecurityDescriptor); +DEF_WINDOWS_FUNCTION(SetSecurityDescriptorOwner); +DEF_WINDOWS_FUNCTION(GetSecurityInfo); +DEF_WINDOWS_FUNCTION(SetSecurityInfo); +DEF_WINDOWS_FUNCTION(SetEntriesInAclA); + +bool got_advapi(void) +{ + static bool attempted = false; + static bool successful; + static HMODULE advapi; + + if (!attempted) { + attempted = true; + advapi = load_system32_dll("advapi32.dll"); + successful = advapi && + GET_WINDOWS_FUNCTION(advapi, GetSecurityInfo) && + GET_WINDOWS_FUNCTION(advapi, SetSecurityInfo) && + GET_WINDOWS_FUNCTION(advapi, OpenProcessToken) && + GET_WINDOWS_FUNCTION(advapi, GetTokenInformation) && + GET_WINDOWS_FUNCTION(advapi, InitializeSecurityDescriptor) && + GET_WINDOWS_FUNCTION(advapi, SetSecurityDescriptorOwner) && + GET_WINDOWS_FUNCTION(advapi, SetEntriesInAclA); + } + return successful; +} + +PSID get_user_sid(void) +{ + HANDLE proc = NULL, tok = NULL; + TOKEN_USER *user = NULL; + DWORD toklen, sidlen; + PSID sid = NULL, ret = NULL; + + if (usersid) + return usersid; + + if (!got_advapi()) + goto cleanup; + + if ((proc = OpenProcess(MAXIMUM_ALLOWED, false, + GetCurrentProcessId())) == NULL) + goto cleanup; + + if (!p_OpenProcessToken(proc, TOKEN_QUERY, &tok)) + goto cleanup; + + if (!p_GetTokenInformation(tok, TokenUser, NULL, 0, &toklen) && + GetLastError() != ERROR_INSUFFICIENT_BUFFER) + goto cleanup; + + if ((user = (TOKEN_USER *)LocalAlloc(LPTR, toklen)) == NULL) + goto cleanup; + + if (!p_GetTokenInformation(tok, TokenUser, user, toklen, &toklen)) + goto cleanup; + + sidlen = GetLengthSid(user->User.Sid); + + sid = (PSID)smalloc(sidlen); + + if (!CopySid(sidlen, sid, user->User.Sid)) + goto cleanup; + + /* Success. Move sid into the return value slot, and null it out + * to stop the cleanup code freeing it. */ + ret = usersid = 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; +} + +static bool getsids(char **error) +{ +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wmissing-braces" +#endif + SID_IDENTIFIER_AUTHORITY world_auth = SECURITY_WORLD_SID_AUTHORITY; + SID_IDENTIFIER_AUTHORITY nt_auth = SECURITY_NT_AUTHORITY; +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + + bool ret = false; + + *error = NULL; + + if (!usersid) { + if ((usersid = get_user_sid()) == NULL) { + *error = dupprintf("unable to construct SID for current user: %s", + win_strerror(GetLastError())); + goto cleanup; + } + } + + if (!worldsid) { + if (!AllocateAndInitializeSid(&world_auth, 1, SECURITY_WORLD_RID, + 0, 0, 0, 0, 0, 0, 0, &worldsid)) { + *error = dupprintf("unable to construct SID for world: %s", + win_strerror(GetLastError())); + goto cleanup; + } + } + + if (!networksid) { + if (!AllocateAndInitializeSid(&nt_auth, 1, SECURITY_NETWORK_RID, + 0, 0, 0, 0, 0, 0, 0, &networksid)) { + *error = dupprintf("unable to construct SID for " + "local same-user access only: %s", + win_strerror(GetLastError())); + goto cleanup; + } + } + + ret = true; + + cleanup: + return ret; +} + + +bool make_private_security_descriptor(DWORD permissions, + PSECURITY_DESCRIPTOR *psd, + PACL *acl, + char **error) +{ + EXPLICIT_ACCESS ea[3]; + int acl_err; + bool ret = false; + + + *psd = NULL; + *acl = NULL; + *error = NULL; + + if (!getsids(error)) + goto cleanup; + + memset(ea, 0, sizeof(ea)); + ea[0].grfAccessPermissions = permissions; + ea[0].grfAccessMode = REVOKE_ACCESS; + ea[0].grfInheritance = NO_INHERITANCE; + ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID; + ea[0].Trustee.ptstrName = (LPTSTR)worldsid; + ea[1].grfAccessPermissions = permissions; + ea[1].grfAccessMode = GRANT_ACCESS; + ea[1].grfInheritance = NO_INHERITANCE; + ea[1].Trustee.TrusteeForm = TRUSTEE_IS_SID; + ea[1].Trustee.ptstrName = (LPTSTR)usersid; + ea[2].grfAccessPermissions = permissions; + ea[2].grfAccessMode = REVOKE_ACCESS; + ea[2].grfInheritance = NO_INHERITANCE; + ea[2].Trustee.TrusteeForm = TRUSTEE_IS_SID; + ea[2].Trustee.ptstrName = (LPTSTR)networksid; + + acl_err = p_SetEntriesInAclA(3, ea, NULL, acl); + if (acl_err != ERROR_SUCCESS || *acl == NULL) { + *error = dupprintf("unable to construct ACL: %s", + win_strerror(acl_err)); + goto cleanup; + } + + *psd = (PSECURITY_DESCRIPTOR) + LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH); + if (!*psd) { + *error = dupprintf("unable to allocate security descriptor: %s", + win_strerror(GetLastError())); + goto cleanup; + } + + if (!InitializeSecurityDescriptor(*psd, SECURITY_DESCRIPTOR_REVISION)) { + *error = dupprintf("unable to initialise security descriptor: %s", + win_strerror(GetLastError())); + goto cleanup; + } + + if (!SetSecurityDescriptorOwner(*psd, usersid, false)) { + *error = dupprintf("unable to set owner in security descriptor: %s", + win_strerror(GetLastError())); + goto cleanup; + } + + if (!SetSecurityDescriptorDacl(*psd, true, *acl, false)) { + *error = dupprintf("unable to set DACL in security descriptor: %s", + win_strerror(GetLastError())); + goto cleanup; + } + + ret = true; + + cleanup: + if (!ret) { + if (*psd) { + LocalFree(*psd); + *psd = NULL; + } + if (*acl) { + LocalFree(*acl); + *acl = NULL; + } + } else { + sfree(*error); + *error = NULL; + } + return ret; +} + +static bool acl_restricted = false; +bool restricted_acl(void) { return acl_restricted; } + +static bool really_restrict_process_acl(char **error) +{ + EXPLICIT_ACCESS ea[2]; + int acl_err; + bool ret = false; + PACL acl = NULL; + + static const DWORD nastyace=WRITE_DAC | WRITE_OWNER | + PROCESS_CREATE_PROCESS | PROCESS_CREATE_THREAD | + PROCESS_DUP_HANDLE | + PROCESS_SET_QUOTA | PROCESS_SET_INFORMATION | + PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE | + PROCESS_SUSPEND_RESUME; + + if (!getsids(error)) + goto cleanup; + + memset(ea, 0, sizeof(ea)); + + /* Everyone: deny */ + ea[0].grfAccessPermissions = nastyace; + ea[0].grfAccessMode = DENY_ACCESS; + ea[0].grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT; + ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID; + ea[0].Trustee.ptstrName = (LPTSTR)worldsid; + + /* User: user ace */ + ea[1].grfAccessPermissions = ~nastyace & 0x1fff; + ea[1].grfAccessMode = GRANT_ACCESS; + ea[1].grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT; + ea[1].Trustee.TrusteeForm = TRUSTEE_IS_SID; + ea[1].Trustee.ptstrName = (LPTSTR)usersid; + + acl_err = p_SetEntriesInAclA(2, ea, NULL, &acl); + + if (acl_err != ERROR_SUCCESS || acl == NULL) { + *error = dupprintf("unable to construct ACL: %s", + win_strerror(acl_err)); + goto cleanup; + } + + if (ERROR_SUCCESS != p_SetSecurityInfo + (GetCurrentProcess(), SE_KERNEL_OBJECT, + OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION, + usersid, NULL, acl, NULL)) { + *error = dupprintf("Unable to set process ACL: %s", + win_strerror(GetLastError())); + goto cleanup; + } + + acl_restricted = true; + ret=true; + + cleanup: + if (!ret) { + if (acl) { + LocalFree(acl); + acl = NULL; + } + } + return ret; +} + +/* + * Lock down our process's ACL, to present an obstacle to malware + * trying to write into its memory. This can't be a full defence, + * because well timed malware could attack us before this code runs - + * even if it was unconditionally run at the very start of main(), + * which we wouldn't want to do anyway because it turns out in practie + * that interfering with other processes in this way has significant + * non-infringing uses on Windows (e.g. screen reader software). + * + * If we've been requested to do this and are unsuccessful, bomb out + * via modalfatalbox rather than continue in a less protected mode. + * + * This function is intentionally outside the #ifndef NO_SECURITY that + * covers the rest of this file, because when PuTTY is compiled + * without the ability to restrict its ACL, we don't want it to + * silently pretend to honour the instruction to do so. + */ +void restrict_process_acl(void) +{ + char *error = NULL; + bool ret; + + ret = really_restrict_process_acl(&error); + if (!ret) + modalfatalbox("Could not restrict process ACL: %s", error); +} diff --git a/windows/utils/split_into_argv.c b/windows/utils/split_into_argv.c new file mode 100644 index 00000000..d1957b48 --- /dev/null +++ b/windows/utils/split_into_argv.c @@ -0,0 +1,459 @@ +/* + * Split a complete command line into argc/argv, attempting to do it + * exactly the same way the Visual Studio C library would do it (so + * that our console utilities, which receive argc and argv already + * broken apart by the C library, will have their command lines + * processed in the same way as the GUI utilities which get a whole + * command line and must call this function). + * + * Does not modify the input command line. + * + * The final parameter (argstart) is used to return a second array + * of char * pointers, the same length as argv, each one pointing + * 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. + */ + +#include "putty.h" + +void split_into_argv(char *cmdline, int *argc, char ***argv, + char ***argstart) +{ + char *p; + char *outputline, *q; + char **outputargv, **outputargstart; + int outputargc; + + /* + * These argument-breaking rules apply to Visual Studio 7, which + * is currently the compiler expected to be used for PuTTY. Visual + * Studio 10 has different rules, lacking the curious mod 3 + * behaviour of consecutive quotes described below; I presume they + * fixed a bug. As and when we migrate to a newer compiler, we'll + * have to adjust this to match; however, for the moment we + * faithfully imitate in our GUI utilities what our CLI utilities + * can't be prevented from doing. + * + * When I investigated this, at first glance the rules appeared to + * be: + * + * - Single quotes are not special characters. + * + * - Double quotes are removed, but within them spaces cease + * to be special. + * + * - 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) { + bool 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 = false; + + /* 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--; + + /* 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 ', 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 ', 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 diff --git a/windows/utils/strtoumax.c b/windows/utils/strtoumax.c new file mode 100644 index 00000000..38d00014 --- /dev/null +++ b/windows/utils/strtoumax.c @@ -0,0 +1,12 @@ +/* + * Work around lack of strtoumax in older MSVC libraries. + */ + +#include + +#include "defs.h" + +uintmax_t strtoumax(const char *nptr, char **endptr, int base) +{ + return _strtoui64(nptr, endptr, base); +} diff --git a/windows/utils/version.c b/windows/utils/version.c new file mode 100644 index 00000000..a53c2ad3 --- /dev/null +++ b/windows/utils/version.c @@ -0,0 +1,40 @@ +#include "putty.h" + +DWORD osMajorVersion, osMinorVersion, osPlatformId; + +void init_winver(void) +{ + OSVERSIONINFO osVersion; + static HMODULE kernel32_module; + DECL_WINDOWS_FUNCTION(static, BOOL, GetVersionExA, (LPOSVERSIONINFO)); + + if (!kernel32_module) { + kernel32_module = load_system32_dll("kernel32.dll"); + /* Deliberately don't type-check this function, because that + * would involve using its declaration in a header file which + * triggers a deprecation warning. I know it's deprecated (see + * below) and don't need telling. */ + GET_WINDOWS_FUNCTION_NO_TYPECHECK(kernel32_module, GetVersionExA); + } + + ZeroMemory(&osVersion, sizeof(osVersion)); + osVersion.dwOSVersionInfoSize = sizeof (OSVERSIONINFO); + if (p_GetVersionExA && p_GetVersionExA(&osVersion)) { + osMajorVersion = osVersion.dwMajorVersion; + osMinorVersion = osVersion.dwMinorVersion; + osPlatformId = osVersion.dwPlatformId; + } else { + /* + * GetVersionEx is deprecated, so allow for it perhaps going + * away in future API versions. If it's not there, simply + * assume that's because Windows is too _new_, so fill in the + * variables we care about to a value that will always compare + * higher than any given test threshold. + * + * Normally we should be checking against the presence of a + * specific function if possible in any case. + */ + osMajorVersion = osMinorVersion = UINT_MAX; /* a very high number */ + osPlatformId = VER_PLATFORM_WIN32_NT; /* not Win32s or Win95-like */ + } +} diff --git a/windows/utils/win_strerror.c b/windows/utils/win_strerror.c new file mode 100644 index 00000000..5572bc8b --- /dev/null +++ b/windows/utils/win_strerror.c @@ -0,0 +1,72 @@ +/* + * Wrapper around the Windows FormatMessage system for retrieving the + * text of a system error code, with a simple API similar to strerror. + * + * Works by keeping a tree234 containing mappings from system error + * codes to strings. Entries allocated in this tree are simply never + * freed. + * + * Also, the returned string has its trailing newline removed (so it + * can go in places like the Event Log that never want a newline), and + * is prefixed with the error number (so that if a user sends an error + * report containing a translated error message we can't read, we can + * still find out what the error actually was). + */ + +#include "putty.h" + +struct errstring { + int error; + char *text; +}; + +static int errstring_find(void *av, void *bv) +{ + int *a = (int *)av; + struct errstring *b = (struct errstring *)bv; + if (*a < b->error) + return -1; + if (*a > b->error) + return +1; + return 0; +} +static int errstring_compare(void *av, void *bv) +{ + struct errstring *a = (struct errstring *)av; + return errstring_find(&a->error, bv); +} + +static tree234 *errstrings = NULL; + +const char *win_strerror(int error) +{ + struct errstring *es; + + if (!errstrings) + errstrings = newtree234(errstring_compare); + + es = find234(errstrings, &error, errstring_find); + + if (!es) { + char msgtext[65536]; /* maximum size for FormatMessage is 64K */ + + es = snew(struct errstring); + es->error = error; + if (!FormatMessage((FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS), NULL, error, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + msgtext, lenof(msgtext)-1, NULL)) { + sprintf(msgtext, + "(unable to format: FormatMessage returned %u)", + (unsigned int)GetLastError()); + } else { + int len = strlen(msgtext); + if (len > 0 && msgtext[len-1] == '\n') + msgtext[len-1] = '\0'; + } + es->text = dupprintf("Error %d: %s", error, msgtext); + add234(errstrings, es); + } + + return es->text; +} diff --git a/windows/wincapi.c b/windows/wincapi.c deleted file mode 100644 index d90a634f..00000000 --- a/windows/wincapi.c +++ /dev/null @@ -1,85 +0,0 @@ -/* - * wincapi.c: implementation of wincapi.h. - */ - -#include "putty.h" - -#include "putty.h" -#include "ssh.h" - -#include "wincapi.h" - -DEF_WINDOWS_FUNCTION(CryptProtectMemory); - -bool got_crypt(void) -{ - static bool attempted = false; - static bool successful; - static HMODULE crypt; - - if (!attempted) { - attempted = true; - crypt = load_system32_dll("crypt32.dll"); - successful = crypt && - GET_WINDOWS_FUNCTION(crypt, CryptProtectMemory); - } - return successful; -} - -char *capi_obfuscate_string(const char *realname) -{ - char *cryptdata; - int cryptlen; - unsigned char digest[32]; - char retbuf[65]; - int i; - - cryptlen = strlen(realname) + 1; - cryptlen += CRYPTPROTECTMEMORY_BLOCK_SIZE - 1; - cryptlen /= CRYPTPROTECTMEMORY_BLOCK_SIZE; - cryptlen *= CRYPTPROTECTMEMORY_BLOCK_SIZE; - - cryptdata = snewn(cryptlen, char); - memset(cryptdata, 0, cryptlen); - strcpy(cryptdata, realname); - - /* - * CRYPTPROTECTMEMORY_CROSS_PROCESS causes CryptProtectMemory to - * use the same key in all processes with this user id, meaning - * that the next PuTTY process calling this function with the same - * input will get the same data. - * - * (Contrast with CryptProtectData, which invents a new session - * key every time since its API permits returning more data than - * was input, so calling _that_ and hashing the output would not - * be stable.) - * - * We don't worry too much if this doesn't work for some reason. - * Omitting this step still has _some_ privacy value (in that - * another user can test-hash things to confirm guesses as to - * where you might be connecting to, but cannot invert SHA-256 in - * the absence of any plausible guess). So we don't abort if we - * can't call CryptProtectMemory at all, or if it fails. - */ - if (got_crypt()) - p_CryptProtectMemory(cryptdata, cryptlen, - CRYPTPROTECTMEMORY_CROSS_PROCESS); - - /* - * We don't want to give away the length of the hostname either, - * so having got it back out of CryptProtectMemory we now hash it. - */ - hash_simple(&ssh_sha256, make_ptrlen(cryptdata, cryptlen), digest); - - sfree(cryptdata); - - /* - * Finally, make printable. - */ - for (i = 0; i < 32; i++) { - sprintf(retbuf + 2*i, "%02x", digest[i]); - /* the last of those will also write the trailing NUL */ - } - - return dupstr(retbuf); -} diff --git a/windows/windefs.c b/windows/windefs.c deleted file mode 100644 index 006e8dc5..00000000 --- a/windows/windefs.c +++ /dev/null @@ -1,40 +0,0 @@ -/* - * windefs.c: default settings that are specific to Windows. - */ - -#include "putty.h" - -#include - -FontSpec *platform_default_fontspec(const char *name) -{ - if (!strcmp(name, "Font")) - return fontspec_new("Courier New", false, 10, ANSI_CHARSET); - else - return fontspec_new("", false, 0, 0); -} - -Filename *platform_default_filename(const char *name) -{ - if (!strcmp(name, "LogFileName")) - return filename_from_str("putty.log"); - else - return filename_from_str(""); -} - -char *platform_default_s(const char *name) -{ - if (!strcmp(name, "SerialLine")) - return dupstr("COM1"); - return NULL; -} - -bool platform_default_b(const char *name, bool def) -{ - return def; -} - -int platform_default_i(const char *name, int def) -{ - return def; -} diff --git a/windows/winmisc.c b/windows/winmisc.c deleted file mode 100644 index 4fa55398..00000000 --- a/windows/winmisc.c +++ /dev/null @@ -1,467 +0,0 @@ -/* - * winmisc.c: miscellaneous Windows-specific things - */ - -#include -#include -#include -#include "putty.h" -#ifndef SECURITY_WIN32 -#define SECURITY_WIN32 -#endif -#include - -DWORD osMajorVersion, osMinorVersion, osPlatformId; - -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 = snew(Filename); - ret->path = dupstr(str); - return ret; -} - -Filename *filename_copy(const Filename *fn) -{ - return filename_from_str(fn->path); -} - -const char *filename_to_str(const Filename *fn) -{ - return fn->path; -} - -bool filename_equal(const Filename *f1, const Filename *f2) -{ - return !strcmp(f1->path, f2->path); -} - -bool filename_is_null(const Filename *fn) -{ - return !*fn->path; -} - -void filename_free(Filename *fn) -{ - sfree(fn->path); - sfree(fn); -} - -void filename_serialise(BinarySink *bs, const Filename *f) -{ - put_asciz(bs, f->path); -} -Filename *filename_deserialise(BinarySource *src) -{ - return filename_from_str(get_asciz(src)); -} - -char filename_char_sanitise(char c) -{ - if (strchr("<>:\"/\\|?*", c)) - return '.'; - return c; -} - -char *get_username(void) -{ - DWORD namelen; - char *user; - bool got_username = false; - DECL_WINDOWS_FUNCTION(static, BOOLEAN, GetUserNameExA, - (EXTENDED_NAME_FORMAT, LPSTR, PULONG)); - - { - static bool tried_usernameex = false; - if (!tried_usernameex) { - /* Not available on Win9x, so load dynamically */ - HMODULE secur32 = load_system32_dll("secur32.dll"); - /* If MIT Kerberos is installed, the following call to - GET_WINDOWS_FUNCTION makes Windows implicitly load - sspicli.dll WITHOUT proper path sanitizing, so better - load it properly before */ - HMODULE sspicli = load_system32_dll("sspicli.dll"); - (void)sspicli; /* squash compiler warning about unused variable */ - 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)) { - /* - * 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; -} - -void dll_hijacking_protection(void) -{ - /* - * If the OS provides it, call SetDefaultDllDirectories() to - * prevent DLLs from being loaded from the directory containing - * our own binary, and instead only load from system32. - * - * This is a protection against hijacking attacks, if someone runs - * PuTTY directly from their web browser's download directory - * having previously been enticed into clicking on an unwise link - * that downloaded a malicious DLL to the same directory under one - * of various magic names that seem to be things that standard - * Windows DLLs delegate to. - * - * It shouldn't break deliberate loading of user-provided DLLs - * such as GSSAPI providers, because those are specified by their - * full pathname by the user-provided configuration. - */ - static HMODULE kernel32_module; - DECL_WINDOWS_FUNCTION(static, BOOL, SetDefaultDllDirectories, (DWORD)); - - if (!kernel32_module) { - kernel32_module = load_system32_dll("kernel32.dll"); -#if !HAVE_SETDEFAULTDLLDIRECTORIES - /* For older Visual Studio, this function isn't available in - * the header files to type-check */ - GET_WINDOWS_FUNCTION_NO_TYPECHECK( - kernel32_module, SetDefaultDllDirectories); -#else - GET_WINDOWS_FUNCTION(kernel32_module, SetDefaultDllDirectories); -#endif - } - - if (p_SetDefaultDllDirectories) { - /* LOAD_LIBRARY_SEARCH_SYSTEM32 and explicitly specified - * directories only */ - p_SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_SYSTEM32 | - LOAD_LIBRARY_SEARCH_USER_DIRS); - } -} - -void init_winver(void) -{ - OSVERSIONINFO osVersion; - static HMODULE kernel32_module; - DECL_WINDOWS_FUNCTION(static, BOOL, GetVersionExA, (LPOSVERSIONINFO)); - - if (!kernel32_module) { - kernel32_module = load_system32_dll("kernel32.dll"); - /* Deliberately don't type-check this function, because that - * would involve using its declaration in a header file which - * triggers a deprecation warning. I know it's deprecated (see - * below) and don't need telling. */ - GET_WINDOWS_FUNCTION_NO_TYPECHECK(kernel32_module, GetVersionExA); - } - - ZeroMemory(&osVersion, sizeof(osVersion)); - osVersion.dwOSVersionInfoSize = sizeof (OSVERSIONINFO); - if (p_GetVersionExA && p_GetVersionExA(&osVersion)) { - osMajorVersion = osVersion.dwMajorVersion; - osMinorVersion = osVersion.dwMinorVersion; - osPlatformId = osVersion.dwPlatformId; - } else { - /* - * GetVersionEx is deprecated, so allow for it perhaps going - * away in future API versions. If it's not there, simply - * assume that's because Windows is too _new_, so fill in the - * variables we care about to a value that will always compare - * higher than any given test threshold. - * - * Normally we should be checking against the presence of a - * specific function if possible in any case. - */ - osMajorVersion = osMinorVersion = UINT_MAX; /* a very high number */ - osPlatformId = VER_PLATFORM_WIN32_NT; /* not Win32s or Win95-like */ - } -} - -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; - static size_t sysdirsize = 0; - char *fullpath; - HMODULE ret; - - if (!sysdir) { - size_t len; - while ((len = GetSystemDirectory(sysdir, sysdirsize)) >= sysdirsize) - sgrowarray(sysdir, sysdirsize, len); - } - - fullpath = dupcat(sysdir, "\\", libname); - ret = LoadLibrary(fullpath); - sfree(fullpath); - return ret; -} - -/* - * A tree234 containing mappings from system error codes to strings. - */ - -struct errstring { - int error; - char *text; -}; - -static int errstring_find(void *av, void *bv) -{ - int *a = (int *)av; - struct errstring *b = (struct errstring *)bv; - if (*a < b->error) - return -1; - if (*a > b->error) - return +1; - return 0; -} -static int errstring_compare(void *av, void *bv) -{ - struct errstring *a = (struct errstring *)av; - return errstring_find(&a->error, bv); -} - -static tree234 *errstrings = NULL; - -const char *win_strerror(int error) -{ - struct errstring *es; - - if (!errstrings) - errstrings = newtree234(errstring_compare); - - es = find234(errstrings, &error, errstring_find); - - if (!es) { - char msgtext[65536]; /* maximum size for FormatMessage is 64K */ - - es = snew(struct errstring); - es->error = error; - if (!FormatMessage((FORMAT_MESSAGE_FROM_SYSTEM | - FORMAT_MESSAGE_IGNORE_INSERTS), NULL, error, - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - msgtext, lenof(msgtext)-1, NULL)) { - sprintf(msgtext, - "(unable to format: FormatMessage returned %u)", - (unsigned int)GetLastError()); - } else { - int len = strlen(msgtext); - if (len > 0 && msgtext[len-1] == '\n') - msgtext[len-1] = '\0'; - } - es->text = dupprintf("Error %d: %s", error, msgtext); - add234(errstrings, es); - } - - return es->text; -} - -FontSpec *fontspec_new(const char *name, bool bold, int height, int charset) -{ - FontSpec *f = snew(FontSpec); - f->name = dupstr(name); - f->isbold = bold; - f->height = height; - f->charset = charset; - return f; -} -FontSpec *fontspec_copy(const FontSpec *f) -{ - return fontspec_new(f->name, f->isbold, f->height, f->charset); -} -void fontspec_free(FontSpec *f) -{ - sfree(f->name); - sfree(f); -} -void fontspec_serialise(BinarySink *bs, FontSpec *f) -{ - put_asciz(bs, f->name); - put_uint32(bs, f->isbold); - put_uint32(bs, f->height); - put_uint32(bs, f->charset); -} -FontSpec *fontspec_deserialise(BinarySource *src) -{ - const char *name = get_asciz(src); - unsigned isbold = get_uint32(src); - unsigned height = get_uint32(src); - unsigned charset = get_uint32(src); - return fontspec_new(name, isbold, height, charset); -} - -bool open_for_write_would_lose_data(const Filename *fn) -{ - WIN32_FILE_ATTRIBUTE_DATA attrs; - if (!GetFileAttributesEx(fn->path, GetFileExInfoStandard, &attrs)) { - /* - * Generally, if we don't identify a specific reason why we - * should return true from this function, we return false, and - * let the subsequent attempt to open the file for real give a - * more useful error message. - */ - return false; - } - if (attrs.dwFileAttributes & (FILE_ATTRIBUTE_DEVICE | - FILE_ATTRIBUTE_DIRECTORY)) { - /* - * File is something other than an ordinary disk file, so - * opening it for writing will not cause truncation. (It may - * not _succeed_ either, but that's not our problem here!) - */ - return false; - } - if (attrs.nFileSizeHigh == 0 && attrs.nFileSizeLow == 0) { - /* - * File is zero-length (or may be a named pipe, which - * dwFileAttributes can't tell apart from a regular file), so - * opening it for writing won't truncate any data away because - * there's nothing to truncate anyway. - */ - return false; - } - return true; -} - -void escape_registry_key(const char *in, strbuf *out) -{ - bool candot = false; - static const char hex[16] = "0123456789ABCDEF"; - - while (*in) { - if (*in == ' ' || *in == '\\' || *in == '*' || *in == '?' || - *in == '%' || *in < ' ' || *in > '~' || (*in == '.' - && !candot)) { - put_byte(out, '%'); - put_byte(out, hex[((unsigned char) *in) >> 4]); - put_byte(out, hex[((unsigned char) *in) & 15]); - } else - put_byte(out, *in); - in++; - candot = true; - } -} - -void unescape_registry_key(const char *in, strbuf *out) -{ - 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); - - put_byte(out, (i << 4) + j); - in += 3; - } else { - put_byte(out, *in++); - } - } -} - -#ifdef DEBUG -static FILE *debug_fp = NULL; -static HANDLE debug_hdl = INVALID_HANDLE_VALUE; -static int debug_got_console = 0; - -void dputs(const 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 - -char *registry_get_string(HKEY root, const char *path, const char *leaf) -{ - HKEY key = root; - bool need_close_key = false; - char *toret = NULL, *str = NULL; - - if (path) { - if (RegCreateKey(key, path, &key) != ERROR_SUCCESS) - goto out; - need_close_key = true; - } - - DWORD type, size; - if (RegQueryValueEx(key, leaf, 0, &type, NULL, &size) != ERROR_SUCCESS) - goto out; - if (type != REG_SZ) - goto out; - - str = snewn(size + 1, char); - DWORD size_got = size; - if (RegQueryValueEx(key, leaf, 0, &type, (LPBYTE)str, - &size_got) != ERROR_SUCCESS) - goto out; - if (type != REG_SZ || size_got > size) - goto out; - str[size_got] = '\0'; - - toret = str; - str = NULL; - - out: - if (need_close_key) - RegCloseKey(key); - sfree(str); - return toret; -} diff --git a/windows/winmiscs.c b/windows/winmiscs.c deleted file mode 100644 index 335711b1..00000000 --- a/windows/winmiscs.c +++ /dev/null @@ -1,285 +0,0 @@ -/* - * winmiscs.c: Windows-specific standalone functions. Has the same - * relationship to winmisc.c that utils.c does to misc.c, but the - * corresponding name 'winutils.c' was already taken. - */ - -#include "putty.h" - -#ifndef NO_SECUREZEROMEMORY -/* - * Windows implementation of smemclr (see misc.c) using SecureZeroMemory. - */ -void smemclr(void *b, size_t n) { - if (b && n > 0) - SecureZeroMemory(b, n); -} -#endif - -#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 */ - -#if !HAVE_STRTOUMAX - -/* - * Work around lack of strtoumax in older MSVC libraries - */ -uintmax_t strtoumax(const char *nptr, char **endptr, int base) -{ - return _strtoui64(nptr, endptr, base); -} - -#endif - -#if defined _M_ARM || defined _M_ARM64 - -bool platform_aes_hw_available(void) -{ - return IsProcessorFeaturePresent(PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE); -} - -bool platform_sha256_hw_available(void) -{ - return IsProcessorFeaturePresent(PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE); -} - -bool platform_sha1_hw_available(void) -{ - return IsProcessorFeaturePresent(PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE); -} - -bool platform_sha512_hw_available(void) -{ - /* As of 2020-12-24, as far as I can tell from docs.microsoft.com, - * Windows on Arm does not yet provide a PF_ARM_V8_* flag for the - * SHA-512 architecture extension. */ - return false; -} - -#endif - -bool is_console_handle(HANDLE handle) -{ - DWORD ignored_output; - if (GetConsoleMode(handle, &ignored_output)) - return true; - return false; -} diff --git a/windows/winsecur.c b/windows/winsecur.c deleted file mode 100644 index c5251c31..00000000 --- a/windows/winsecur.c +++ /dev/null @@ -1,326 +0,0 @@ -/* - * winsecur.c: implementation of winsecur.h. - */ - -#include -#include - -#include "putty.h" - -#include "winsecur.h" - -/* Initialised once, then kept around to reuse forever */ -static PSID worldsid, networksid, usersid; - -DEF_WINDOWS_FUNCTION(OpenProcessToken); -DEF_WINDOWS_FUNCTION(GetTokenInformation); -DEF_WINDOWS_FUNCTION(InitializeSecurityDescriptor); -DEF_WINDOWS_FUNCTION(SetSecurityDescriptorOwner); -DEF_WINDOWS_FUNCTION(GetSecurityInfo); -DEF_WINDOWS_FUNCTION(SetSecurityInfo); -DEF_WINDOWS_FUNCTION(SetEntriesInAclA); - -bool got_advapi(void) -{ - static bool attempted = false; - static bool successful; - static HMODULE advapi; - - if (!attempted) { - attempted = true; - advapi = load_system32_dll("advapi32.dll"); - successful = advapi && - GET_WINDOWS_FUNCTION(advapi, GetSecurityInfo) && - GET_WINDOWS_FUNCTION(advapi, SetSecurityInfo) && - GET_WINDOWS_FUNCTION(advapi, OpenProcessToken) && - GET_WINDOWS_FUNCTION(advapi, GetTokenInformation) && - GET_WINDOWS_FUNCTION(advapi, InitializeSecurityDescriptor) && - GET_WINDOWS_FUNCTION(advapi, SetSecurityDescriptorOwner) && - GET_WINDOWS_FUNCTION(advapi, SetEntriesInAclA); - } - return successful; -} - -PSID get_user_sid(void) -{ - HANDLE proc = NULL, tok = NULL; - TOKEN_USER *user = NULL; - DWORD toklen, sidlen; - PSID sid = NULL, ret = NULL; - - if (usersid) - return usersid; - - if (!got_advapi()) - goto cleanup; - - if ((proc = OpenProcess(MAXIMUM_ALLOWED, false, - GetCurrentProcessId())) == NULL) - goto cleanup; - - if (!p_OpenProcessToken(proc, TOKEN_QUERY, &tok)) - goto cleanup; - - if (!p_GetTokenInformation(tok, TokenUser, NULL, 0, &toklen) && - GetLastError() != ERROR_INSUFFICIENT_BUFFER) - goto cleanup; - - if ((user = (TOKEN_USER *)LocalAlloc(LPTR, toklen)) == NULL) - goto cleanup; - - if (!p_GetTokenInformation(tok, TokenUser, user, toklen, &toklen)) - goto cleanup; - - sidlen = GetLengthSid(user->User.Sid); - - sid = (PSID)smalloc(sidlen); - - if (!CopySid(sidlen, sid, user->User.Sid)) - goto cleanup; - - /* Success. Move sid into the return value slot, and null it out - * to stop the cleanup code freeing it. */ - ret = usersid = 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; -} - -static bool getsids(char **error) -{ -#ifdef __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wmissing-braces" -#endif - SID_IDENTIFIER_AUTHORITY world_auth = SECURITY_WORLD_SID_AUTHORITY; - SID_IDENTIFIER_AUTHORITY nt_auth = SECURITY_NT_AUTHORITY; -#ifdef __clang__ -#pragma clang diagnostic pop -#endif - - bool ret = false; - - *error = NULL; - - if (!usersid) { - if ((usersid = get_user_sid()) == NULL) { - *error = dupprintf("unable to construct SID for current user: %s", - win_strerror(GetLastError())); - goto cleanup; - } - } - - if (!worldsid) { - if (!AllocateAndInitializeSid(&world_auth, 1, SECURITY_WORLD_RID, - 0, 0, 0, 0, 0, 0, 0, &worldsid)) { - *error = dupprintf("unable to construct SID for world: %s", - win_strerror(GetLastError())); - goto cleanup; - } - } - - if (!networksid) { - if (!AllocateAndInitializeSid(&nt_auth, 1, SECURITY_NETWORK_RID, - 0, 0, 0, 0, 0, 0, 0, &networksid)) { - *error = dupprintf("unable to construct SID for " - "local same-user access only: %s", - win_strerror(GetLastError())); - goto cleanup; - } - } - - ret = true; - - cleanup: - return ret; -} - - -bool make_private_security_descriptor(DWORD permissions, - PSECURITY_DESCRIPTOR *psd, - PACL *acl, - char **error) -{ - EXPLICIT_ACCESS ea[3]; - int acl_err; - bool ret = false; - - - *psd = NULL; - *acl = NULL; - *error = NULL; - - if (!getsids(error)) - goto cleanup; - - memset(ea, 0, sizeof(ea)); - ea[0].grfAccessPermissions = permissions; - ea[0].grfAccessMode = REVOKE_ACCESS; - ea[0].grfInheritance = NO_INHERITANCE; - ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID; - ea[0].Trustee.ptstrName = (LPTSTR)worldsid; - ea[1].grfAccessPermissions = permissions; - ea[1].grfAccessMode = GRANT_ACCESS; - ea[1].grfInheritance = NO_INHERITANCE; - ea[1].Trustee.TrusteeForm = TRUSTEE_IS_SID; - ea[1].Trustee.ptstrName = (LPTSTR)usersid; - ea[2].grfAccessPermissions = permissions; - ea[2].grfAccessMode = REVOKE_ACCESS; - ea[2].grfInheritance = NO_INHERITANCE; - ea[2].Trustee.TrusteeForm = TRUSTEE_IS_SID; - ea[2].Trustee.ptstrName = (LPTSTR)networksid; - - acl_err = p_SetEntriesInAclA(3, ea, NULL, acl); - if (acl_err != ERROR_SUCCESS || *acl == NULL) { - *error = dupprintf("unable to construct ACL: %s", - win_strerror(acl_err)); - goto cleanup; - } - - *psd = (PSECURITY_DESCRIPTOR) - LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH); - if (!*psd) { - *error = dupprintf("unable to allocate security descriptor: %s", - win_strerror(GetLastError())); - goto cleanup; - } - - if (!InitializeSecurityDescriptor(*psd, SECURITY_DESCRIPTOR_REVISION)) { - *error = dupprintf("unable to initialise security descriptor: %s", - win_strerror(GetLastError())); - goto cleanup; - } - - if (!SetSecurityDescriptorOwner(*psd, usersid, false)) { - *error = dupprintf("unable to set owner in security descriptor: %s", - win_strerror(GetLastError())); - goto cleanup; - } - - if (!SetSecurityDescriptorDacl(*psd, true, *acl, false)) { - *error = dupprintf("unable to set DACL in security descriptor: %s", - win_strerror(GetLastError())); - goto cleanup; - } - - ret = true; - - cleanup: - if (!ret) { - if (*psd) { - LocalFree(*psd); - *psd = NULL; - } - if (*acl) { - LocalFree(*acl); - *acl = NULL; - } - } else { - sfree(*error); - *error = NULL; - } - return ret; -} - -static bool acl_restricted = false; -bool restricted_acl(void) { return acl_restricted; } - -static bool really_restrict_process_acl(char **error) -{ - EXPLICIT_ACCESS ea[2]; - int acl_err; - bool ret = false; - PACL acl = NULL; - - static const DWORD nastyace=WRITE_DAC | WRITE_OWNER | - PROCESS_CREATE_PROCESS | PROCESS_CREATE_THREAD | - PROCESS_DUP_HANDLE | - PROCESS_SET_QUOTA | PROCESS_SET_INFORMATION | - PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE | - PROCESS_SUSPEND_RESUME; - - if (!getsids(error)) - goto cleanup; - - memset(ea, 0, sizeof(ea)); - - /* Everyone: deny */ - ea[0].grfAccessPermissions = nastyace; - ea[0].grfAccessMode = DENY_ACCESS; - ea[0].grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT; - ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID; - ea[0].Trustee.ptstrName = (LPTSTR)worldsid; - - /* User: user ace */ - ea[1].grfAccessPermissions = ~nastyace & 0x1fff; - ea[1].grfAccessMode = GRANT_ACCESS; - ea[1].grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT; - ea[1].Trustee.TrusteeForm = TRUSTEE_IS_SID; - ea[1].Trustee.ptstrName = (LPTSTR)usersid; - - acl_err = p_SetEntriesInAclA(2, ea, NULL, &acl); - - if (acl_err != ERROR_SUCCESS || acl == NULL) { - *error = dupprintf("unable to construct ACL: %s", - win_strerror(acl_err)); - goto cleanup; - } - - if (ERROR_SUCCESS != p_SetSecurityInfo - (GetCurrentProcess(), SE_KERNEL_OBJECT, - OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION, - usersid, NULL, acl, NULL)) { - *error = dupprintf("Unable to set process ACL: %s", - win_strerror(GetLastError())); - goto cleanup; - } - - acl_restricted = true; - ret=true; - - cleanup: - if (!ret) { - if (acl) { - LocalFree(acl); - acl = NULL; - } - } - return ret; -} - -/* - * Lock down our process's ACL, to present an obstacle to malware - * trying to write into its memory. This can't be a full defence, - * because well timed malware could attack us before this code runs - - * even if it was unconditionally run at the very start of main(), - * which we wouldn't want to do anyway because it turns out in practie - * that interfering with other processes in this way has significant - * non-infringing uses on Windows (e.g. screen reader software). - * - * If we've been requested to do this and are unsuccessful, bomb out - * via modalfatalbox rather than continue in a less protected mode. - * - * This function is intentionally outside the #ifndef NO_SECURITY that - * covers the rest of this file, because when PuTTY is compiled - * without the ability to restrict its ACL, we don't want it to - * silently pretend to honour the instruction to do so. - */ -void restrict_process_acl(void) -{ - char *error = NULL; - bool ret; - - ret = really_restrict_process_acl(&error); - if (!ret) - modalfatalbox("Could not restrict process ACL: %s", error); -} diff --git a/windows/winstuff.h b/windows/winstuff.h index ddd3277a..5f2557dd 100644 --- a/windows/winstuff.h +++ b/windows/winstuff.h @@ -103,6 +103,12 @@ struct FontSpec *fontspec_new( #define LONG_PTR LONG #endif +#if !HAVE_STRTOUMAX +/* Work around lack of strtoumax in older MSVC libraries */ +static inline uintmax_t strtoumax(const char *nptr, char **endptr, int base) +{ return _strtoui64(nptr, endptr, base); } +#endif + #define BOXFLAGS DLGWINDOWEXTRA #define BOXRESULT (DLGWINDOWEXTRA + sizeof(LONG_PTR)) #define DF_END 0x0001 diff --git a/windows/wintime.c b/windows/wintime.c deleted file mode 100644 index 5fa3b0de..00000000 --- a/windows/wintime.c +++ /dev/null @@ -1,26 +0,0 @@ -/* - * wintime.c - Avoid trouble with time() returning (time_t)-1 on Windows. - */ - -#include "putty.h" -#include - -struct tm ltime(void) -{ - SYSTEMTIME st; - struct tm tm; - - memset(&tm, 0, sizeof(tm)); /* in case there are any other fields */ - - 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; -} diff --git a/windows/winutils.c b/windows/winutils.c deleted file mode 100644 index 973c3be0..00000000 --- a/windows/winutils.c +++ /dev/null @@ -1,650 +0,0 @@ -/* - * winutils.c: miscellaneous Windows utilities for GUI apps - */ - -#include -#include -#include - -#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, bool preserve, bool 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 = false; - } - - /* 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. - */ - -static HWND message_box_owner; - -/* Callback function to launch context help. */ -static VOID CALLBACK message_box_help_callback(LPHELPINFO lpHelpInfo) -{ - const 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(message_box_owner, context); -} - -int message_box(HWND owner, 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 = message_box_owner = owner; - 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_msgbox(HWND owner) -{ - message_box( - owner, - "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 as of " PGP_MASTER_KEY_YEAR - " (" PGP_MASTER_KEY_DETAILS "):\n" - " " PGP_MASTER_KEY_FP "\n\n" - "Previous Master Key (" PGP_PREV_MASTER_KEY_YEAR - ", " PGP_PREV_MASTER_KEY_DETAILS "):\n" - " " PGP_PREV_MASTER_KEY_FP, - "PGP fingerprints", MB_ICONINFORMATION | MB_OK, - HELPCTXID(pgp_fingerprints)); -} - -/* - * Helper function to remove the border around a dialog item such as - * a read-only edit control. - */ -void MakeDlgItemBorderless(HWND parent, int id) -{ - HWND child = GetDlgItem(parent, id); - LONG_PTR style = GetWindowLongPtr(child, GWL_STYLE); - LONG_PTR exstyle = GetWindowLongPtr(child, GWL_EXSTYLE); - style &= ~WS_BORDER; - exstyle &= ~(WS_EX_CLIENTEDGE | WS_EX_STATICEDGE | WS_EX_WINDOWEDGE); - SetWindowLongPtr(child, GWL_STYLE, style); - SetWindowLongPtr(child, GWL_EXSTYLE, exstyle); - SetWindowPos(child, NULL, 0, 0, 0, 0, - SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED); -} - -/* - * Handy wrapper around GetDlgItemText which doesn't make you invent - * an arbitrary length limit on the output string. Returned string is - * dynamically allocated; caller must free. - */ -char *GetDlgItemText_alloc(HWND hwnd, int id) -{ - char *ret = NULL; - size_t size = 0; - - do { - sgrowarray_nm(ret, size, size); - GetDlgItemText(hwnd, id, ret, size); - } while (!memchr(ret, '\0', size-1)); - - return ret; -} - -/* - * Split a complete command line into argc/argv, attempting to do it - * exactly the same way the Visual Studio C library would do it (so - * that our console utilities, which receive argc and argv already - * broken apart by the C library, will have their command lines - * processed in the same way as the GUI utilities which get a whole - * command line and must call this function). - * - * Does not modify the input command line. - * - * The final parameter (argstart) is used to return a second array - * of char * pointers, the same length as argv, each one pointing - * 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; - - /* - * These argument-breaking rules apply to Visual Studio 7, which - * is currently the compiler expected to be used for PuTTY. Visual - * Studio 10 has different rules, lacking the curious mod 3 - * behaviour of consecutive quotes described below; I presume they - * fixed a bug. As and when we migrate to a newer compiler, we'll - * have to adjust this to match; however, for the moment we - * faithfully imitate in our GUI utilities what our CLI utilities - * can't be prevented from doing. - * - * When I investigated this, at first glance the rules appeared to - * be: - * - * - Single quotes are not special characters. - * - * - Double quotes are removed, but within them spaces cease - * to be special. - * - * - 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) { - bool 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 = false; - - /* 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--; - - /* 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 ', 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 ', 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 -- cgit v1.2.3 From cc3e4992d5e5bfb580cefcbc9a3ed85f1cd95eee Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 17 Apr 2021 17:01:08 +0100 Subject: Break up x11fwd.c. This is a module that I'd noticed in the past was too monolithic. There's a big pile of stub functions in uxpgnt.c that only have to be there because the implementation of true X11 _forwarding_ (i.e. actually managing a channel within an SSH connection), which Pageant doesn't need, was in the same module as more general X11-related utility functions which Pageant does need. So I've broken up this awkward monolith. Now x11fwd.c contains only the code that really does all go together for dealing with SSH X forwarding: the management of an X forwarding channel (including the vtables to make it behave as Channel at the SSH end and a Plug at the end that connects to the local X server), and the management of authorisation for those channels, including maintaining a tree234 of possible auth values and verifying the one we received. Most of the functions removed from this file have moved into the utils subdir, and also into the utils library (i.e. further down the link order), because they were basically just string and data processing. One exception is x11_setup_display, which parses a display string and returns a struct telling you everything about how to connect to it. That talks to the networking code (it does name lookups and makes a SockAddr), so it has to live in the network library rather than utils, and therefore it's not in the utils subdirectory either. The other exception is x11_get_screen_number, which it turned out nothing called at all! Apparently the job it used to do is now done as part of x11_setup_display. So I've just removed it completely. --- CMakeLists.txt | 8 +- misc.h | 16 ++ putty.h | 2 +- ssh.h | 1 + unix/CMakeLists.txt | 4 +- utils/x11_dehexify.c | 28 ++ utils/x11_identify_auth_proto.c | 16 ++ utils/x11_make_greeting.c | 67 +++++ utils/x11_parse_ip.c | 20 ++ utils/x11authfile.c | 244 +++++++++++++++++ utils/x11authnames.c | 9 + windows/CMakeLists.txt | 4 +- x11disp.c | 189 ++++++++++++++ x11fwd.c | 562 ---------------------------------------- 14 files changed, 602 insertions(+), 568 deletions(-) create mode 100644 utils/x11_dehexify.c create mode 100644 utils/x11_identify_auth_proto.c create mode 100644 utils/x11_make_greeting.c create mode 100644 utils/x11_parse_ip.c create mode 100644 utils/x11authfile.c create mode 100644 utils/x11authnames.c create mode 100644 x11disp.c diff --git a/CMakeLists.txt b/CMakeLists.txt index b1050f2d..1047773e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -52,6 +52,12 @@ add_library(utils STATIC utils/wcwidth.c utils/wildcard.c utils/write_c_string_literal.c + utils/x11authfile.c + utils/x11authnames.c + utils/x11_dehexify.c + utils/x11_identify_auth_proto.c + utils/x11_make_greeting.c + utils/x11_parse_ip.c ${GENERATED_COMMIT_C}) add_library(logging OBJECT @@ -78,7 +84,7 @@ add_library(crypto STATIC cproxy.c) add_library(network STATIC - be_misc.c nullplug.c errsock.c proxy.c logging.c) + be_misc.c nullplug.c errsock.c proxy.c logging.c x11disp.c) add_library(keygen STATIC millerrabin.c mpunsafe.c pockle.c primecandidate.c smallprimes.c diff --git a/misc.h b/misc.h index 7b4012b6..04fe3e68 100644 --- a/misc.h +++ b/misc.h @@ -374,6 +374,22 @@ static inline void PUT_16BIT_MSB_FIRST(void *vp, uint16_t value) p[0] = (uint8_t)(value >> 8); } +/* For use in X11-related applications, an endianness-variable form of + * {GET,PUT}_16BIT which expects 'endian' to be either 'B' or 'l' */ + +static inline uint16_t GET_16BIT_X11(char endian, const void *p) +{ + return endian == 'B' ? GET_16BIT_MSB_FIRST(p) : GET_16BIT_LSB_FIRST(p); +} + +static inline void PUT_16BIT_X11(char endian, void *p, uint16_t value) +{ + if (endian == 'B') + PUT_16BIT_MSB_FIRST(p, value); + else + PUT_16BIT_LSB_FIRST(p, value); +} + /* Replace NULL with the empty string, permitting an idiom in which we * get a string (pointer,length) pair that might be NULL,0 and can * then safely say things like printf("%.*s", length, NULLTOEMPTY(ptr)) */ diff --git a/putty.h b/putty.h index a73817af..a14da123 100644 --- a/putty.h +++ b/putty.h @@ -2205,7 +2205,7 @@ enum { X11_XDM, /* XDM-AUTHORIZATION-1 */ X11_NAUTHS }; -extern const char *const x11_authnames[]; /* declared in x11fwd.c */ +extern const char *const x11_authnames[X11_NAUTHS]; /* * An enum for the copy-paste UI action configuration. diff --git a/ssh.h b/ssh.h index 08c6797c..fbacbfa4 100644 --- a/ssh.h +++ b/ssh.h @@ -1191,6 +1191,7 @@ void x11_format_auth_for_authfile( ptrlen authproto, ptrlen authdata); int x11_identify_auth_proto(ptrlen protoname); void *x11_dehexify(ptrlen hex, int *outlen); +bool x11_parse_ip(const char *addr_string, unsigned long *ip); Channel *agentf_new(SshChannel *c); diff --git a/unix/CMakeLists.txt b/unix/CMakeLists.txt index 72bf507c..d77f7c9d 100644 --- a/unix/CMakeLists.txt +++ b/unix/CMakeLists.txt @@ -31,9 +31,9 @@ add_platform_sources_to_library(console add_platform_sources_to_library(settings uxstore.c) add_platform_sources_to_library(network - uxnet.c uxfdsock.c uxagentsock.c uxpeer.c uxproxy.c) + uxnet.c uxfdsock.c uxagentsock.c uxpeer.c uxproxy.c ux_x11.c) add_platform_sources_to_library(sshcommon - uxnoise.c ux_x11.c) + uxnoise.c) add_platform_sources_to_library(sshclient uxgss.c uxagentc.c uxshare.c) add_platform_sources_to_library(sshserver diff --git a/utils/x11_dehexify.c b/utils/x11_dehexify.c new file mode 100644 index 00000000..13ffb0d0 --- /dev/null +++ b/utils/x11_dehexify.c @@ -0,0 +1,28 @@ +/* + * Utility function to convert a textual representation of an X11 + * auth hex cookie into binary auth data. + */ + +#include "putty.h" + +void *x11_dehexify(ptrlen hexpl, int *outlen) +{ + int len, i; + unsigned char *ret; + + len = hexpl.len / 2; + ret = snewn(len, unsigned char); + + for (i = 0; i < len; i++) { + char bytestr[3]; + unsigned val = 0; + bytestr[0] = ((const char *)hexpl.ptr)[2*i]; + bytestr[1] = ((const char *)hexpl.ptr)[2*i+1]; + bytestr[2] = '\0'; + sscanf(bytestr, "%x", &val); + ret[i] = val; + } + + *outlen = len; + return ret; +} diff --git a/utils/x11_identify_auth_proto.c b/utils/x11_identify_auth_proto.c new file mode 100644 index 00000000..14075a3f --- /dev/null +++ b/utils/x11_identify_auth_proto.c @@ -0,0 +1,16 @@ +/* + * Utility function to convert a textual representation of an X11 + * auth protocol name into our integer protocol ids. + */ + +#include "putty.h" + +int x11_identify_auth_proto(ptrlen protoname) +{ + int protocol; + + for (protocol = 1; protocol < lenof(x11_authnames); protocol++) + if (ptrlen_eq_string(protoname, x11_authnames[protocol])) + return protocol; + return -1; +} diff --git a/utils/x11_make_greeting.c b/utils/x11_make_greeting.c new file mode 100644 index 00000000..4efd8e83 --- /dev/null +++ b/utils/x11_make_greeting.c @@ -0,0 +1,67 @@ +/* + * Construct an X11 greeting packet, including making up the right + * authorisation data. + */ + +#include "putty.h" +#include "ssh.h" + +void *x11_make_greeting(int endian, int protomajor, int protominor, + int auth_proto, const void *auth_data, int auth_len, + const char *peer_addr, int peer_port, + int *outlen) +{ + unsigned char *greeting; + unsigned char realauthdata[64]; + const char *authname; + const unsigned char *authdata; + int authnamelen, authnamelen_pad; + int authdatalen, authdatalen_pad; + int greeting_len; + + authname = x11_authnames[auth_proto]; + authnamelen = strlen(authname); + authnamelen_pad = (authnamelen + 3) & ~3; + + if (auth_proto == X11_MIT) { + authdata = auth_data; + authdatalen = auth_len; + } else if (auth_proto == X11_XDM && auth_len == 16) { + time_t t; + unsigned long peer_ip = 0; + + x11_parse_ip(peer_addr, &peer_ip); + + authdata = realauthdata; + authdatalen = 24; + memset(realauthdata, 0, authdatalen); + memcpy(realauthdata, auth_data, 8); + PUT_32BIT_MSB_FIRST(realauthdata+8, peer_ip); + PUT_16BIT_MSB_FIRST(realauthdata+12, peer_port); + t = time(NULL); + PUT_32BIT_MSB_FIRST(realauthdata+14, t); + + des_encrypt_xdmauth((char *)auth_data + 9, realauthdata, authdatalen); + } else { + authdata = realauthdata; + authdatalen = 0; + } + + authdatalen_pad = (authdatalen + 3) & ~3; + greeting_len = 12 + authnamelen_pad + authdatalen_pad; + + greeting = snewn(greeting_len, unsigned char); + memset(greeting, 0, greeting_len); + greeting[0] = endian; + PUT_16BIT_X11(endian, greeting+2, protomajor); + PUT_16BIT_X11(endian, greeting+4, protominor); + PUT_16BIT_X11(endian, greeting+6, authnamelen); + PUT_16BIT_X11(endian, greeting+8, authdatalen); + memcpy(greeting+12, authname, authnamelen); + memcpy(greeting+12+authnamelen_pad, authdata, authdatalen); + + smemclr(realauthdata, sizeof(realauthdata)); + + *outlen = greeting_len; + return greeting; +} diff --git a/utils/x11_parse_ip.c b/utils/x11_parse_ip.c new file mode 100644 index 00000000..12649b49 --- /dev/null +++ b/utils/x11_parse_ip.c @@ -0,0 +1,20 @@ +/* + * Try to make sense of a string as an IPv4 address, for + * XDM-AUTHORIZATION-1 purposes. + */ + +#include + +#include "putty.h" + +bool x11_parse_ip(const char *addr_string, unsigned long *ip) +{ + int i[4]; + if (addr_string && + 4 == sscanf(addr_string, "%d.%d.%d.%d", i+0, i+1, i+2, i+3)) { + *ip = (i[0] << 24) | (i[1] << 16) | (i[2] << 8) | i[3]; + return true; + } else { + return false; + } +} diff --git a/utils/x11authfile.c b/utils/x11authfile.c new file mode 100644 index 00000000..4fc84ab5 --- /dev/null +++ b/utils/x11authfile.c @@ -0,0 +1,244 @@ +/* + * Functions to handle .Xauthority files. + */ + +#include +#include +#include + +#include "putty.h" +#include "ssh.h" + +ptrlen BinarySource_get_string_xauth(BinarySource *src) +{ + size_t len = get_uint16(src); + return get_data(src, len); +} +#define get_string_xauth(src) \ + BinarySource_get_string_xauth(BinarySource_UPCAST(src)) + +void BinarySink_put_stringpl_xauth(BinarySink *bs, ptrlen pl) +{ + assert((pl.len >> 16) == 0); + put_uint16(bs, pl.len); + put_datapl(bs, pl); +} +#define put_stringpl_xauth(bs, ptrlen) \ + BinarySink_put_stringpl_xauth(BinarySink_UPCAST(bs),ptrlen) + +void x11_get_auth_from_authfile(struct X11Display *disp, + const char *authfilename) +{ + FILE *authfp; + char *buf; + int size; + BinarySource src[1]; + int family, protocol; + ptrlen addr, protoname, data; + char *displaynum_string; + int displaynum; + bool ideal_match = false; + char *ourhostname; + + /* A maximally sized (wildly implausible) .Xauthority record + * consists of a 16-bit integer to start with, then four strings, + * each of which has a 16-bit length field followed by that many + * bytes of data (i.e. up to 0xFFFF bytes). */ + const size_t MAX_RECORD_SIZE = 2 + 4 * (2+0xFFFF); + + /* We'll want a buffer of twice that size (see below). */ + const size_t BUF_SIZE = 2 * MAX_RECORD_SIZE; + + /* + * 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. + */ + bool localhost = !disp->unixdomain && sk_address_is_local(disp->addr); + + authfp = fopen(authfilename, "rb"); + if (!authfp) + return; + + ourhostname = get_hostname(); + + /* + * Allocate enough space to hold two maximally sized records, so + * that a full record can start anywhere in the first half. That + * way we avoid the accidentally-quadratic algorithm that would + * arise if we moved everything to the front of the buffer after + * consuming each record; instead, we only move everything to the + * front after our current position gets past the half-way mark. + * Before then, there's no need to move anyway; so this guarantees + * linear time, in that every byte written into this buffer moves + * at most once (because every move is from the second half of the + * buffer to the first half). + */ + buf = snewn(BUF_SIZE, char); + size = fread(buf, 1, BUF_SIZE, authfp); + BinarySource_BARE_INIT(src, buf, size); + + while (!ideal_match) { + bool match = false; + + if (src->pos >= MAX_RECORD_SIZE) { + size -= src->pos; + memcpy(buf, buf + src->pos, size); + size += fread(buf + size, 1, BUF_SIZE - size, authfp); + BinarySource_BARE_INIT(src, buf, size); + } + + family = get_uint16(src); + addr = get_string_xauth(src); + displaynum_string = mkstr(get_string_xauth(src)); + displaynum = displaynum_string[0] ? atoi(displaynum_string) : -1; + sfree(displaynum_string); + protoname = get_string_xauth(src); + data = get_string_xauth(src); + if (get_err(src)) + break; + + /* + * 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. + * + * - 'addr' 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). + * + * - 'displaynum' is the display number. An empty display + * number is a wildcard for any display number. + * + * - 'protoname' is the authorisation protocol, encoded as + * its canonical string name (i.e. "MIT-MAGIC-COOKIE-1", + * "XDM-AUTHORIZATION-1" or something we don't recognise). + * + * - 'data' is the actual authorisation data, stored in + * binary form. + */ + + if (disp->displaynum < 0 || + (displaynum >= 0 && disp->displaynum != displaynum)) + continue; /* not the one */ + + for (protocol = 1; protocol < lenof(x11_authnames); protocol++) + if (ptrlen_eq_string(protoname, 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 (addr.len == 4 && !memcmp(addr.ptr, 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 (addr.len == 16 && !memcmp(addr.ptr, buf, 16)) { + match = true; + ideal_match = !localhost; + } + } + break; + case 256: /* Unix-domain / localhost */ + if ((disp->unixdomain || localhost) + && ourhostname && ptrlen_eq_string(addr, ourhostname)) { + /* A matching Unix-domain socket is always the best + * match. */ + match = true; + 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(data.len, unsigned char); + memcpy(disp->localauthdata, data.ptr, data.len); + disp->localauthdatalen = data.len; + } + } + + fclose(authfp); + smemclr(buf, 2 * MAX_RECORD_SIZE); + sfree(buf); + sfree(ourhostname); +} + +void x11_format_auth_for_authfile( + BinarySink *bs, SockAddr *addr, int display_no, + ptrlen authproto, ptrlen authdata) +{ + if (sk_address_is_special_local(addr)) { + char *ourhostname = get_hostname(); + put_uint16(bs, 256); /* indicates Unix-domain socket */ + put_stringpl_xauth(bs, ptrlen_from_asciz(ourhostname)); + sfree(ourhostname); + } else if (sk_addrtype(addr) == ADDRTYPE_IPV4) { + char ipv4buf[4]; + sk_addrcopy(addr, ipv4buf); + put_uint16(bs, 0); /* indicates IPv4 */ + put_stringpl_xauth(bs, make_ptrlen(ipv4buf, 4)); + } else if (sk_addrtype(addr) == ADDRTYPE_IPV6) { + char ipv6buf[16]; + sk_addrcopy(addr, ipv6buf); + put_uint16(bs, 6); /* indicates IPv6 */ + put_stringpl_xauth(bs, make_ptrlen(ipv6buf, 16)); + } else { + unreachable("Bad address type in x11_format_auth_for_authfile"); + } + + { + char *numberbuf = dupprintf("%d", display_no); + put_stringpl_xauth(bs, ptrlen_from_asciz(numberbuf)); + sfree(numberbuf); + } + + put_stringpl_xauth(bs, authproto); + put_stringpl_xauth(bs, authdata); +} diff --git a/utils/x11authnames.c b/utils/x11authnames.c new file mode 100644 index 00000000..ef3c2673 --- /dev/null +++ b/utils/x11authnames.c @@ -0,0 +1,9 @@ +/* + * Definition of the array of X11 authorisation method names. + */ + +#include "putty.h" + +const char *const x11_authnames[X11_NAUTHS] = { + "", "MIT-MAGIC-COOKIE-1", "XDM-AUTHORIZATION-1" +}; diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt index ff6805f0..41b617ab 100644 --- a/windows/CMakeLists.txt +++ b/windows/CMakeLists.txt @@ -40,9 +40,9 @@ add_platform_sources_to_library(console add_platform_sources_to_library(settings winstore.c) add_platform_sources_to_library(network - winnet.c winhsock.c winnpc.c winnps.c winproxy.c) + winnet.c winhsock.c winnpc.c winnps.c winproxy.c winx11.c) add_platform_sources_to_library(sshcommon - winnoise.c winx11.c) + winnoise.c) add_platform_sources_to_library(sshclient winpgntc.c wingss.c winshare.c) add_platform_sources_to_library(sftpclient diff --git a/x11disp.c b/x11disp.c new file mode 100644 index 00000000..58ffc1e6 --- /dev/null +++ b/x11disp.c @@ -0,0 +1,189 @@ +/* + * Functions to manage an X11Display structure, by creating one from + * an ordinary display name string, and freeing one. + */ + +#include +#include +#include +#include + +#include "putty.h" +#include "ssh.h" +#include "sshchan.h" +#include "tree234.h" + +struct X11Display *x11_setup_display(const char *display, Conf *conf, + char **error_msg) +{ + struct X11Display *disp = snew(struct X11Display); + char *localcopy; + + *error_msg = NULL; + + 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 = host_strrchr(localcopy, ':'); + if (!colon) { + *error_msg = dupprintf("display name '%s' has no ':number'" + " suffix", localcopy); + + sfree(disp); + sfree(localcopy); + return NULL; + } + + *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, conf, ADDRTYPE_UNSPEC, + NULL, NULL); + + if ((err = sk_addr_error(disp->addr)) != NULL) { + *error_msg = dupprintf("unable to resolve host name '%s' in " + "display name", disp->hostname); + + sk_addr_free(disp->addr); + sfree(disp->hostname); + sfree(disp->unixsocketpath); + sfree(disp); + return NULL; + } + } + + /* + * 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 */ + Socket *s = sk_new(sk_addr_dup(ux), 0, false, false, + false, false, nullplug); + 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; + } + + /* + * Fetch the local authorisation details. + */ + disp->localauthproto = X11_NO_AUTH; + disp->localauthdata = NULL; + disp->localauthdatalen = 0; + platform_get_x11_auth(disp, conf); + + return disp; +} + +void x11_free_display(struct X11Display *disp) +{ + sfree(disp->hostname); + sfree(disp->unixsocketpath); + if (disp->localauthdata) + smemclr(disp->localauthdata, disp->localauthdatalen); + sfree(disp->localauthdata); + sk_addr_free(disp->addr); + sfree(disp); +} diff --git a/x11fwd.c b/x11fwd.c index 86f85831..a5066dd1 100644 --- a/x11fwd.c +++ b/x11fwd.c @@ -12,23 +12,6 @@ #include "sshchan.h" #include "tree234.h" -static inline uint16_t GET_16BIT_X11(char endian, const void *p) -{ - return endian == 'B' ? GET_16BIT_MSB_FIRST(p) : GET_16BIT_LSB_FIRST(p); -} - -static inline void PUT_16BIT_X11(char endian, void *p, uint16_t value) -{ - if (endian == 'B') - PUT_16BIT_MSB_FIRST(p, value); - else - PUT_16BIT_LSB_FIRST(p, value); -} - -const char *const x11_authnames[] = { - "", "MIT-MAGIC-COOKIE-1", "XDM-AUTHORIZATION-1" -}; - struct XDMSeen { unsigned int time; unsigned char clientid[6]; @@ -183,181 +166,6 @@ int x11_authcmp(void *av, void *bv) } } -struct X11Display *x11_setup_display(const char *display, Conf *conf, - char **error_msg) -{ - struct X11Display *disp = snew(struct X11Display); - char *localcopy; - - *error_msg = NULL; - - 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 = host_strrchr(localcopy, ':'); - if (!colon) { - *error_msg = dupprintf("display name '%s' has no ':number'" - " suffix", localcopy); - - sfree(disp); - sfree(localcopy); - return NULL; - } - - *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, conf, ADDRTYPE_UNSPEC, - NULL, NULL); - - if ((err = sk_addr_error(disp->addr)) != NULL) { - *error_msg = dupprintf("unable to resolve host name '%s' in " - "display name", disp->hostname); - - sk_addr_free(disp->addr); - sfree(disp->hostname); - sfree(disp->unixsocketpath); - sfree(disp); - return NULL; - } - } - - /* - * 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 */ - Socket *s = sk_new(sk_addr_dup(ux), 0, false, false, - false, false, nullplug); - 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; - } - - /* - * Fetch the local authorisation details. - */ - disp->localauthproto = X11_NO_AUTH; - disp->localauthdata = NULL; - disp->localauthdatalen = 0; - platform_get_x11_auth(disp, conf); - - return disp; -} - -void x11_free_display(struct X11Display *disp) -{ - sfree(disp->hostname); - sfree(disp->unixsocketpath); - if (disp->localauthdata) - smemclr(disp->localauthdata, disp->localauthdatalen); - sfree(disp->localauthdata); - sk_addr_free(disp->addr); - sfree(disp); -} - #define XDM_MAXSKEW 20*60 /* 20 minute clock skew should be OK */ static const char *x11_verify(unsigned long peer_ip, int peer_port, @@ -450,240 +258,6 @@ static const char *x11_verify(unsigned long peer_ip, int peer_port, return NULL; } -ptrlen BinarySource_get_string_xauth(BinarySource *src) -{ - size_t len = get_uint16(src); - return get_data(src, len); -} -#define get_string_xauth(src) \ - BinarySource_get_string_xauth(BinarySource_UPCAST(src)) - -void BinarySink_put_stringpl_xauth(BinarySink *bs, ptrlen pl) -{ - assert((pl.len >> 16) == 0); - put_uint16(bs, pl.len); - put_datapl(bs, pl); -} -#define put_stringpl_xauth(bs, ptrlen) \ - BinarySink_put_stringpl_xauth(BinarySink_UPCAST(bs),ptrlen) - -void x11_get_auth_from_authfile(struct X11Display *disp, - const char *authfilename) -{ - FILE *authfp; - char *buf; - int size; - BinarySource src[1]; - int family, protocol; - ptrlen addr, protoname, data; - char *displaynum_string; - int displaynum; - bool ideal_match = false; - char *ourhostname; - - /* A maximally sized (wildly implausible) .Xauthority record - * consists of a 16-bit integer to start with, then four strings, - * each of which has a 16-bit length field followed by that many - * bytes of data (i.e. up to 0xFFFF bytes). */ - const size_t MAX_RECORD_SIZE = 2 + 4 * (2+0xFFFF); - - /* We'll want a buffer of twice that size (see below). */ - const size_t BUF_SIZE = 2 * MAX_RECORD_SIZE; - - /* - * 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. - */ - bool localhost = !disp->unixdomain && sk_address_is_local(disp->addr); - - authfp = fopen(authfilename, "rb"); - if (!authfp) - return; - - ourhostname = get_hostname(); - - /* - * Allocate enough space to hold two maximally sized records, so - * that a full record can start anywhere in the first half. That - * way we avoid the accidentally-quadratic algorithm that would - * arise if we moved everything to the front of the buffer after - * consuming each record; instead, we only move everything to the - * front after our current position gets past the half-way mark. - * Before then, there's no need to move anyway; so this guarantees - * linear time, in that every byte written into this buffer moves - * at most once (because every move is from the second half of the - * buffer to the first half). - */ - buf = snewn(BUF_SIZE, char); - size = fread(buf, 1, BUF_SIZE, authfp); - BinarySource_BARE_INIT(src, buf, size); - - while (!ideal_match) { - bool match = false; - - if (src->pos >= MAX_RECORD_SIZE) { - size -= src->pos; - memcpy(buf, buf + src->pos, size); - size += fread(buf + size, 1, BUF_SIZE - size, authfp); - BinarySource_BARE_INIT(src, buf, size); - } - - family = get_uint16(src); - addr = get_string_xauth(src); - displaynum_string = mkstr(get_string_xauth(src)); - displaynum = displaynum_string[0] ? atoi(displaynum_string) : -1; - sfree(displaynum_string); - protoname = get_string_xauth(src); - data = get_string_xauth(src); - if (get_err(src)) - break; - - /* - * 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. - * - * - 'addr' 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). - * - * - 'displaynum' is the display number. An empty display - * number is a wildcard for any display number. - * - * - 'protoname' is the authorisation protocol, encoded as - * its canonical string name (i.e. "MIT-MAGIC-COOKIE-1", - * "XDM-AUTHORIZATION-1" or something we don't recognise). - * - * - 'data' is the actual authorisation data, stored in - * binary form. - */ - - if (disp->displaynum < 0 || - (displaynum >= 0 && disp->displaynum != displaynum)) - continue; /* not the one */ - - for (protocol = 1; protocol < lenof(x11_authnames); protocol++) - if (ptrlen_eq_string(protoname, 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 (addr.len == 4 && !memcmp(addr.ptr, 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 (addr.len == 16 && !memcmp(addr.ptr, buf, 16)) { - match = true; - ideal_match = !localhost; - } - } - break; - case 256: /* Unix-domain / localhost */ - if ((disp->unixdomain || localhost) - && ourhostname && ptrlen_eq_string(addr, ourhostname)) { - /* A matching Unix-domain socket is always the best - * match. */ - match = true; - 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(data.len, unsigned char); - memcpy(disp->localauthdata, data.ptr, data.len); - disp->localauthdatalen = data.len; - } - } - - fclose(authfp); - smemclr(buf, 2 * MAX_RECORD_SIZE); - sfree(buf); - sfree(ourhostname); -} - -void x11_format_auth_for_authfile( - BinarySink *bs, SockAddr *addr, int display_no, - ptrlen authproto, ptrlen authdata) -{ - if (sk_address_is_special_local(addr)) { - char *ourhostname = get_hostname(); - put_uint16(bs, 256); /* indicates Unix-domain socket */ - put_stringpl_xauth(bs, ptrlen_from_asciz(ourhostname)); - sfree(ourhostname); - } else if (sk_addrtype(addr) == ADDRTYPE_IPV4) { - char ipv4buf[4]; - sk_addrcopy(addr, ipv4buf); - put_uint16(bs, 0); /* indicates IPv4 */ - put_stringpl_xauth(bs, make_ptrlen(ipv4buf, 4)); - } else if (sk_addrtype(addr) == ADDRTYPE_IPV6) { - char ipv6buf[16]; - sk_addrcopy(addr, ipv6buf); - put_uint16(bs, 6); /* indicates IPv6 */ - put_stringpl_xauth(bs, make_ptrlen(ipv6buf, 16)); - } else { - unreachable("Bad address type in x11_format_auth_for_authfile"); - } - - { - char *numberbuf = dupprintf("%d", display_no); - put_stringpl_xauth(bs, ptrlen_from_asciz(numberbuf)); - sfree(numberbuf); - } - - put_stringpl_xauth(bs, authproto); - put_stringpl_xauth(bs, authdata); -} - static void x11_log(Plug *p, PlugLogType type, SockAddr *addr, int port, const char *error_msg, int error_code) { @@ -743,24 +317,6 @@ static void x11_sent(Plug *plug, size_t bufsize) sshfwd_unthrottle(xconn->c, bufsize); } -/* - * When setting up X forwarding, we should send the screen number - * from the specified local display. This function extracts it from - * the display string. - */ -int x11_get_screen_number(char *display) -{ - int n; - - n = host_strcspn(display, ":"); - if (!display[n]) - return 0; - n = strcspn(display, "."); - if (!display[n]) - return 0; - return atoi(display + n + 1); -} - static const PlugVtable X11Connection_plugvt = { .log = x11_log, .closing = x11_closing, @@ -898,23 +454,6 @@ static void x11_send_init_error(struct X11Connection *xconn, sfree(full_message); } -static bool x11_parse_ip(const char *addr_string, unsigned long *ip) -{ - - /* - * See if we can make sense of this string as an IPv4 address, for - * XDM-AUTHORIZATION-1 purposes. - */ - int i[4]; - if (addr_string && - 4 == sscanf(addr_string, "%d.%d.%d.%d", i+0, i+1, i+2, i+3)) { - *ip = (i[0] << 24) | (i[1] << 16) | (i[2] << 8) | i[3]; - return true; - } else { - return false; - } -} - /* * Called to send data down the raw connection. */ @@ -1098,104 +637,3 @@ static char *x11_log_close_msg(Channel *chan) { return dupstr("Forwarded X11 connection terminated"); } - -/* - * Utility functions used by connection sharing to convert textual - * representations of an X11 auth protocol name + hex cookie into our - * usual integer protocol id and binary auth data. - */ -int x11_identify_auth_proto(ptrlen protoname) -{ - int protocol; - - for (protocol = 1; protocol < lenof(x11_authnames); protocol++) - if (ptrlen_eq_string(protoname, x11_authnames[protocol])) - return protocol; - return -1; -} - -void *x11_dehexify(ptrlen hexpl, int *outlen) -{ - int len, i; - unsigned char *ret; - - len = hexpl.len / 2; - ret = snewn(len, unsigned char); - - for (i = 0; i < len; i++) { - char bytestr[3]; - unsigned val = 0; - bytestr[0] = ((const char *)hexpl.ptr)[2*i]; - bytestr[1] = ((const char *)hexpl.ptr)[2*i+1]; - bytestr[2] = '\0'; - sscanf(bytestr, "%x", &val); - ret[i] = val; - } - - *outlen = len; - return ret; -} - -/* - * Construct an X11 greeting packet, including making up the right - * authorisation data. - */ -void *x11_make_greeting(int endian, int protomajor, int protominor, - int auth_proto, const void *auth_data, int auth_len, - const char *peer_addr, int peer_port, - int *outlen) -{ - unsigned char *greeting; - unsigned char realauthdata[64]; - const char *authname; - const unsigned char *authdata; - int authnamelen, authnamelen_pad; - int authdatalen, authdatalen_pad; - int greeting_len; - - authname = x11_authnames[auth_proto]; - authnamelen = strlen(authname); - authnamelen_pad = (authnamelen + 3) & ~3; - - if (auth_proto == X11_MIT) { - authdata = auth_data; - authdatalen = auth_len; - } else if (auth_proto == X11_XDM && auth_len == 16) { - time_t t; - unsigned long peer_ip = 0; - - x11_parse_ip(peer_addr, &peer_ip); - - authdata = realauthdata; - authdatalen = 24; - memset(realauthdata, 0, authdatalen); - memcpy(realauthdata, auth_data, 8); - PUT_32BIT_MSB_FIRST(realauthdata+8, peer_ip); - PUT_16BIT_MSB_FIRST(realauthdata+12, peer_port); - t = time(NULL); - PUT_32BIT_MSB_FIRST(realauthdata+14, t); - - des_encrypt_xdmauth((char *)auth_data + 9, realauthdata, authdatalen); - } else { - authdata = realauthdata; - authdatalen = 0; - } - - authdatalen_pad = (authdatalen + 3) & ~3; - greeting_len = 12 + authnamelen_pad + authdatalen_pad; - - greeting = snewn(greeting_len, unsigned char); - memset(greeting, 0, greeting_len); - greeting[0] = endian; - PUT_16BIT_X11(endian, greeting+2, protomajor); - PUT_16BIT_X11(endian, greeting+4, protominor); - PUT_16BIT_X11(endian, greeting+6, authnamelen); - PUT_16BIT_X11(endian, greeting+8, authdatalen); - memcpy(greeting+12, authname, authnamelen); - memcpy(greeting+12+authnamelen_pad, authdata, authdatalen); - - smemclr(realauthdata, sizeof(realauthdata)); - - *outlen = greeting_len; - return greeting; -} -- cgit v1.2.3 From 5bb24a7eddfe129a3eb02257b0e63d9bbb2e4879 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 17 Apr 2021 17:11:19 +0100 Subject: Remove stub functions that are no longer needed. This is the start of the payoff for all that reorganisation (and perhaps also from having moved to a library-based build structure in the first place): a collection of pointless stub functions in outlying programs, which were only there to prevent link failures, now no longer need to be there even for that purpose. --- fuzzterm.c | 3 --- testcrypt.c | 7 ------- testsc.c | 7 ------- unix/uxpgnt.c | 31 ------------------------------- unix/uxsocks.c | 2 -- windows/winpgnt.c | 5 ----- 6 files changed, 55 deletions(-) diff --git a/fuzzterm.c b/fuzzterm.c index c5c29ef4..660a6be5 100644 --- a/fuzzterm.c +++ b/fuzzterm.c @@ -6,9 +6,6 @@ #include "dialog.h" #include "terminal.h" -/* For Unix in particular, but harmless if this main() is reused elsewhere */ -const bool buildinfo_gtk_relevant = false; - static const TermWinVtable fuzz_termwin_vt; int main(int argc, char **argv) diff --git a/testcrypt.c b/testcrypt.c index 67627752..752947cf 100644 --- a/testcrypt.c +++ b/testcrypt.c @@ -49,13 +49,6 @@ static NORETURN PRINTF_LIKE(1, 2) void fatal_error(const char *p, ...) void out_of_memory(void) { fatal_error("out of memory"); } -/* For platforms where getticks is defined within this code base */ -unsigned long (getticks)(void) -{ unreachable("this is a stub needed to link, and should never be called"); } - -FILE *f_open(const struct Filename *fn, char const *mode, bool private) -{ unreachable("f_open should never be called by this test program"); } - static bool old_keyfile_warning_given; void old_keyfile_warning(void) { old_keyfile_warning_given = true; } diff --git a/testsc.c b/testsc.c index f04f718c..93bf263a 100644 --- a/testsc.c +++ b/testsc.c @@ -93,13 +93,6 @@ static NORETURN PRINTF_LIKE(1, 2) void fatal_error(const char *p, ...) } void out_of_memory(void) { fatal_error("out of memory"); } -FILE *f_open(const Filename *filename, char const *mode, bool is_private) -{ unreachable("this is a stub needed to link, and should never be called"); } -void old_keyfile_warning(void) -{ unreachable("this is a stub needed to link, and should never be called"); } -/* For platforms where getticks is defined within this code base */ -unsigned long (getticks)(void) -{ unreachable("this is a stub needed to link, and should never be called"); } /* * A simple deterministic PRNG, without any of the Fortuna diff --git a/unix/uxpgnt.c b/unix/uxpgnt.c index 4adca625..0fc3dd11 100644 --- a/unix/uxpgnt.c +++ b/unix/uxpgnt.c @@ -238,37 +238,6 @@ const char *const appname = "Pageant"; static bool time_to_die = false; -/* Stub functions to permit linking against x11fwd.c. These never get - * used, because in LIFE_X11 mode we connect to the X server using a - * straightforward Socket and don't try to create an ersatz SSH - * forwarding too. */ -void chan_remotely_opened_confirmation(Channel *chan) { } -void chan_remotely_opened_failure(Channel *chan, const char *err) { } -bool chan_default_want_close(Channel *chan, bool s, bool r) { return false; } -bool chan_no_exit_status(Channel *ch, int s) { return false; } -bool chan_no_exit_signal(Channel *ch, ptrlen s, bool c, ptrlen m) -{ return false; } -bool chan_no_exit_signal_numeric(Channel *ch, int s, bool c, ptrlen m) -{ return false; } -bool chan_no_run_shell(Channel *chan) { return false; } -bool chan_no_run_command(Channel *chan, ptrlen command) { return false; } -bool chan_no_run_subsystem(Channel *chan, ptrlen subsys) { return false; } -bool chan_no_enable_x11_forwarding( - Channel *chan, bool oneshot, ptrlen authproto, ptrlen authdata, - unsigned screen_number) { return false; } -bool chan_no_enable_agent_forwarding(Channel *chan) { return false; } -bool chan_no_allocate_pty( - Channel *chan, ptrlen termtype, unsigned width, unsigned height, - unsigned pixwidth, unsigned pixheight, struct ssh_ttymodes modes) -{ return false; } -bool chan_no_set_env(Channel *chan, ptrlen var, ptrlen value) { return false; } -bool chan_no_send_break(Channel *chan, unsigned length) { return false; } -bool chan_no_send_signal(Channel *chan, ptrlen signame) { return false; } -bool chan_no_change_window_size( - Channel *chan, unsigned width, unsigned height, - unsigned pixwidth, unsigned pixheight) { return false; } -void chan_no_request_response(Channel *chan, bool success) {} - /* * These functions are part of the plug for our connection to the X * display, so they do get called. They needn't actually do anything, diff --git a/unix/uxsocks.c b/unix/uxsocks.c index 91613afd..748790b8 100644 --- a/unix/uxsocks.c +++ b/unix/uxsocks.c @@ -15,8 +15,6 @@ #include "ssh.h" #include "psocks.h" -const bool buildinfo_gtk_relevant = false; - typedef struct PsocksDataSinkPopen { stdio_sink sink[2]; PsocksDataSink pds; diff --git a/windows/winpgnt.c b/windows/winpgnt.c index e96a432e..1f3d7080 100644 --- a/windows/winpgnt.c +++ b/windows/winpgnt.c @@ -1331,11 +1331,6 @@ void spawn_cmd(const char *cmdline, const char *args, int show) } } -void logevent(LogContext *logctx, const char *event) -{ - unreachable("Pageant can't create a LogContext, so this can't be called"); -} - void noise_ultralight(NoiseSourceId id, unsigned long data) { /* Pageant doesn't use random numbers, so we ignore this */ -- cgit v1.2.3 From 395c228bee78515155d7e73fbbcd35fffb0d7fa7 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 17 Apr 2021 17:59:43 +0100 Subject: Adopt a new universal implementation of smemclr(). This new implementation uses the same optimisation-barrier technique that I used in various places in testsc: have a no-op function, and a volatile function pointer pointing at it, and then call through the function pointer, so that nothing actually happens (apart from the physical call and return) but the compiler has to assume that _anything_ might have happened. Doing this just after a memset enforces that the compiler can't have thrown away the memset, because the called function might (for example) check that all the memory really is zero and abort if not. I've been turning this over in my mind ever since coming up with the technique for testsc. I think it's far more robust than the previous smemclr technique: so much so that I'm switching to using it _everywhere_, and no longer using platform alternatives like Windows's SecureZeroMemory(). --- CMakeLists.txt | 1 + cmake/platforms/windows.cmake | 3 --- defs.h | 28 --------------------------- unix/CMakeLists.txt | 1 - utils/buildinfo.c | 3 --- utils/smemclr.c | 44 ++++++++++++++++++++++++++----------------- windows/CMakeLists.txt | 3 --- 7 files changed, 28 insertions(+), 55 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1047773e..178c1baf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,6 +40,7 @@ add_library(utils STATIC utils/seat_connection_fatal.c utils/sessprep.c utils/sk_free_peer_info.c + utils/smemclr.c utils/smemeq.c utils/ssh2_pick_fingerprint.c utils/sshutils.c diff --git a/cmake/platforms/windows.cmake b/cmake/platforms/windows.cmake index 2adda297..0760ec67 100644 --- a/cmake/platforms/windows.cmake +++ b/cmake/platforms/windows.cmake @@ -36,9 +36,6 @@ define_negation(NO_MULTIMON HAVE_MULTIMON_H) check_include_files("windows.h;htmlhelp.h" HAVE_HTMLHELP_H) define_negation(NO_HTMLHELP HAVE_HTMLHELP_H) -check_symbol_exists(SecureZeroMemory "windows.h" HAVE_SECUREZEROMEMORY) -define_negation(NO_SECUREZEROMEMORY HAVE_SECUREZEROMEMORY) - check_symbol_exists(strtoumax "inttypes.h" HAVE_STRTOUMAX) check_symbol_exists(AddDllDirectory "windows.h" HAVE_ADDDLLDIRECTORY) check_symbol_exists(SetDefaultDllDirectories "windows.h" diff --git a/defs.h b/defs.h index 86f8edde..f175d257 100644 --- a/defs.h +++ b/defs.h @@ -201,32 +201,4 @@ typedef struct PacketProtocolLayer PacketProtocolLayer; #define NORETURN #endif -/* ---------------------------------------------------------------------- - * Platform-specific definitions. - * - * Most of these live in the per-platform header files, of which - * puttyps.h selects the appropriate one. But some of the sources - * (particularly standalone test applications) would prefer not to - * have to include a per-platform header at all, because that makes it - * more portable to platforms not supported by the code base as a - * whole (for example, compiling purely computational parts of the - * code for specialist platforms for test and analysis purposes). So - * any definition that has to affect even _those_ modules will have to - * go here, with the key constraint being that this code has to come - * to _some_ decision even if the compilation platform is not a - * recognised one at all. - */ - -/* Purely computational code uses smemclr(), so we have to make the - * decision here about whether that's provided by utils.c or by a - * platform implementation. We define PLATFORM_HAS_SMEMCLR to suppress - * utils.c's definition. */ -#ifdef _WINDOWS -/* Windows provides the API function 'SecureZeroMemory', which we use - * unless the user has told us not to by defining NO_SECUREZEROMEMORY. */ -#ifndef NO_SECUREZEROMEMORY -#define PLATFORM_HAS_SMEMCLR -#endif -#endif - #endif /* PUTTY_DEFS_H */ diff --git a/unix/CMakeLists.txt b/unix/CMakeLists.txt index d77f7c9d..ebb77a55 100644 --- a/unix/CMakeLists.txt +++ b/unix/CMakeLists.txt @@ -18,7 +18,6 @@ add_platform_sources_to_library(utils utils/pollwrap.c utils/signal.c utils/x11_ignore_error.c - ../utils/smemclr.c # Compiled icon pixmap files xpmpucfg.c xpmputty.c xpmptcfg.c xpmpterm.c # We want the ISO C implementation of ltime(), because we don't have diff --git a/utils/buildinfo.c b/utils/buildinfo.c index 1db65a0d..2c40d6c2 100644 --- a/utils/buildinfo.c +++ b/utils/buildinfo.c @@ -130,9 +130,6 @@ char *buildinfo(const char *newline) #if defined _WINDOWS && defined MINEFIELD strbuf_catf(buf, "%sBuild option: MINEFIELD", newline); #endif -#ifdef NO_SECUREZEROMEMORY - strbuf_catf(buf, "%sBuild option: NO_SECUREZEROMEMORY", newline); -#endif #ifdef NO_IPV6 strbuf_catf(buf, "%sBuild option: NO_IPV6", newline); #endif diff --git a/utils/smemclr.c b/utils/smemclr.c index afe919d1..3cd7a761 100644 --- a/utils/smemclr.c +++ b/utils/smemclr.c @@ -6,18 +6,36 @@ * won't optimise away memsets on variables that are about to be freed * or go out of scope. See * https://buildsecurityin.us-cert.gov/bsi-rules/home/g1/771-BSI.html - * - * Some platforms (e.g. Windows) may provide their own version of this - * function. */ #include "defs.h" #include "misc.h" +/* + * Trivial function that is given a pointer to some memory and ignores + * it. + */ +static void no_op(void *ptr, size_t size) {} + +/* + * Function pointer that is given a pointer to some memory, and from + * the compiler's point of view, _might_ read it, or otherwise depend + * on its contents. + * + * In fact, this function pointer always points to no_op() above. But + * because the pointer itself is volatile-qualified, the compiler + * isn't allowed to optimise based on the assumption that that will + * always be the case. So it has to call through the function pointer + * anyway, on the basis that it _might_ have magically changed at run + * time into a pointer to some completely arbitrary function. And + * therefore it must also avoid optimising away any observable effect + * beforehand that a completely arbitrary function might depend on - + * such as the zeroing of our memory re3gion. + */ +static void (*const volatile maybe_read)(void *ptr, size_t size) = no_op; + void smemclr(void *b, size_t n) { - volatile char *vp; - if (b && n > 0) { /* * Zero out the memory. @@ -25,18 +43,10 @@ void smemclr(void *b, size_t n) memset(b, 0, n); /* - * Perform a volatile access to the object, forcing the - * compiler to admit that the previous memset was important. - * - * This while loop should in practice run for zero iterations - * (since we know we just zeroed the object out), but in - * theory (as far as the compiler knows) it might range over - * the whole object. (If we had just written, say, '*vp = - * *vp;', a compiler could in principle have 'helpfully' - * optimised the memset into only zeroing out the first byte. - * This should be robust.) + * Call the above function pointer, which (for all the + * compiler knows) might check that we've really zeroed the + * memory. */ - vp = b; - while (*vp) vp++; + maybe_read(b, n); } } diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt index 41b617ab..b512f459 100644 --- a/windows/CMakeLists.txt +++ b/windows/CMakeLists.txt @@ -27,9 +27,6 @@ add_platform_sources_to_library(utils utils/version.c utils/win_strerror.c winucs.c) -if(NOT HAVE_SECUREZEROMEMORY) - add_platform_sources_to_library(utils ../utils/smemclr.c) -endif() if(NOT HAVE_STRTOUMAX) add_platform_sources_to_library(utils utils/strtoumax.c) endif() -- cgit v1.2.3 From 1c61fdf800367ba8be30f335c340a4188820615c Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 17 Apr 2021 16:39:31 +0100 Subject: Build various unit-test main() programs in utils. I found these while going through the code, and decided if we're going to have them then we should compile them. They didn't all compile first time, proving my point :-) I've enhanced the tree234 test so that it has a verbose option, which by default is off. --- CMakeLists.txt | 15 ++++++ utils/host_strchr_internal.c | 2 +- utils/tree234.c | 105 +++++++++++++++++++++++++--------------- utils/wildcard.c | 4 +- windows/CMakeLists.txt | 5 ++ windows/utils/split_into_argv.c | 12 +++-- 6 files changed, 98 insertions(+), 45 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 178c1baf..a41f7bf3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -145,6 +145,21 @@ add_executable(testcrypt target_link_libraries(testcrypt keygen crypto utils ${platform_libraries}) +add_executable(test_host_strfoo + utils/host_strchr_internal.c) +target_compile_definitions(test_host_strfoo PRIVATE TEST) +target_link_libraries(test_host_strfoo utils ${platform_libraries}) + +add_executable(test_tree234 + utils/tree234.c) +target_compile_definitions(test_tree234 PRIVATE TEST) +target_link_libraries(test_tree234 utils ${platform_libraries}) + +add_executable(test_wildcard + utils/wildcard.c) +target_compile_definitions(test_wildcard PRIVATE TEST) +target_link_libraries(test_wildcard utils ${platform_libraries}) + add_compile_definitions(HAVE_CMAKE_H) foreach(subdir ${PLATFORM_SUBDIRS}) diff --git a/utils/host_strchr_internal.c b/utils/host_strchr_internal.c index 07aadd37..f995b53d 100644 --- a/utils/host_strchr_internal.c +++ b/utils/host_strchr_internal.c @@ -53,7 +53,7 @@ int main(void) #define TEST1(func, string, arg2, suffix, result) do \ { \ const char *str = string; \ - unsigned ret = func(string, arg2) suffix; \ + unsigned ret = func(str, arg2) suffix; \ if (ret == result) { \ passes++; \ } else { \ diff --git a/utils/tree234.c b/utils/tree234.c index a590382b..f9104158 100644 --- a/utils/tree234.c +++ b/utils/tree234.c @@ -31,17 +31,16 @@ #include "defs.h" #include "tree234.h" +#include "puttymem.h" #ifdef TEST -#define LOG(x) (printf x) -#define snew(type) ((type *)malloc(sizeof(type))) -#define snewn(n, type) ((type *)malloc((n) * sizeof(type))) -#define sresize(ptr, n, type) \ - ((type *)realloc(sizeof((type *)0 == (ptr)) ? (ptr) : (ptr), \ - (n) * sizeof(type))) -#define sfree(ptr) free(ptr) +static int verbose = 0; +#define LOG(x) do \ + { \ + if (verbose > 2) \ + printf x; \ + } while (0) #else -#include "puttymem.h" #define LOG(x) #endif @@ -1151,7 +1150,7 @@ int chknode(chkctx * ctx, int level, node234 * node, * nelems should be at least 1. */ if (nelems == 0) { - error("node %p: no elems", node, nkids); + error("node %p: no elems", node); } /* @@ -1173,7 +1172,7 @@ int chknode(chkctx * ctx, int level, node234 * node, (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); + node, i, (char *)lower, i + 1, (char *)higher); } } } @@ -1223,9 +1222,10 @@ void verify(void) 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); + chknode(ctx, 0, tree->root, NULL, NULL); } - printf("tree depth: %d\n", ctx->treedepth); + if (verbose) + printf("tree depth: %d\n", ctx->treedepth); /* * Enumerate the tree and ensure it matches up to the array. */ @@ -1234,7 +1234,7 @@ void verify(void) 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); + i, (char *)array[i], (char *)p); } if (ctx->elemcount != i) { error("tree really contains %d elements, enum gave %d", @@ -1379,7 +1379,7 @@ char *strings[] = { #define NSTR lenof(strings) -int findtest(void) +void findtest(void) { const static int rels[] = { REL234_EQ, REL234_GE, REL234_LE, REL234_LT, REL234_GT @@ -1444,17 +1444,16 @@ int findtest(void) p, relnames[j], realret, index, index, realret2); } } -#if 0 - printf("find(\"%s\",%s) gave %s(%d)\n", p, relnames[j], - realret, index); -#endif + if (verbose) + printf("find(\"%s\",%s) gave %s(%d)\n", p, relnames[j], + realret, index); } } 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]); + realret, index, (char *)array[0]); } else if (!arraylen && (realret != NULL)) { error("find(NULL,GT) gave %s(%d) should be NULL", realret, index); } @@ -1463,7 +1462,7 @@ int findtest(void) 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]); + (char *)array[arraylen - 1]); } else if (!arraylen && (realret != NULL)) { error("find(NULL,LT) gave %s(%d) should be NULL", realret, index); } @@ -1483,9 +1482,10 @@ void searchtest_recurse(search234_state ss, int lo, int hi, error("search234(%s) gave index %d should be %d", directionbuf, ss.index, lo); } else { - printf("%*ssearch234(%s) gave NULL,%d\n", - (int)(directionptr-directionbuf) * 2, "", directionbuf, - ss.index); + if (verbose) + printf("%*ssearch234(%s) gave NULL,%d\n", + (int)(directionptr-directionbuf) * 2, "", directionbuf, + ss.index); } } else if (lo == hi) { error("search234(%s) gave %s for empty interval [%d,%d)", @@ -1500,9 +1500,10 @@ void searchtest_recurse(search234_state ss, int lo, int hi, } else { search234_state next; - printf("%*ssearch234(%s) gave %s,%d\n", - (int)(directionptr-directionbuf) * 2, "", directionbuf, - (char *)ss.element, ss.index); + if (verbose) + printf("%*ssearch234(%s) gave %s,%d\n", + (int)(directionptr-directionbuf) * 2, "", directionbuf, + (char *)ss.element, ss.index); next = ss; search234_step(&next, -1); @@ -1525,23 +1526,42 @@ void searchtest(void) int n; search234_state ss; - printf("beginning searchtest:"); + if (verbose) + printf("beginning searchtest:"); for (n = 0; (p = index234(tree, n)) != NULL; n++) { expected[n] = p; - printf(" %d=%s", n, p); + if (verbose) + printf(" %d=%s", n, p); } - printf(" count=%d\n", n); + if (verbose) + printf(" count=%d\n", n); search234_start(&ss, tree); searchtest_recurse(ss, 0, n, expected, directionbuf, directionbuf); } -int main(void) +void out_of_memory(void) +{ + fprintf(stderr, "out of memory!\n"); + exit(2); +} + +int main(int argc, char **argv) { int in[NSTR]; int i, j, k; unsigned seed = 0; + for (i = 1; i < argc; i++) { + char *arg = argv[i]; + if (!strcmp(arg, "-v")) { + verbose++; + } else { + fprintf(stderr, "unrecognised option '%s'\n", arg); + return 1; + } + } + for (i = 0; i < NSTR; i++) in[i] = 0; array = NULL; @@ -1554,13 +1574,16 @@ int main(void) for (i = 0; i < 10000; i++) { j = randomnumber(&seed); j %= NSTR; - printf("trial: %d\n", i); + if (verbose) + printf("trial: %d\n", i); if (in[j]) { - printf("deleting %s (%d)\n", strings[j], j); + if (verbose) + printf("deleting %s (%d)\n", strings[j], j); deltest(strings[j]); in[j] = 0; } else { - printf("adding %s (%d)\n", strings[j], j); + if (verbose) + printf("adding %s (%d)\n", strings[j], j); addtest(strings[j]); in[j] = 1; } @@ -1587,20 +1610,24 @@ int main(void) cmp = NULL; verify(); for (i = 0; i < 1000; i++) { - printf("trial: %d\n", i); + if (verbose) + 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); + if (verbose) + 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)); + if (verbose) + printf("cleanup: tree size %d\n", count234(tree)); j = randomnumber(&seed); j %= count234(tree); - printf("deleting string %s from index %d\n", - (const char *)array[j], j); + if (verbose) + printf("deleting string %s from index %d\n", + (const char *)array[j], j); delpostest(j); } @@ -1608,4 +1635,4 @@ int main(void) return (n_errors != 0); } -#endif +#endif /* TEST */ diff --git a/utils/wildcard.c b/utils/wildcard.c index 697feb9a..255cc53f 100644 --- a/utils/wildcard.c +++ b/utils/wildcard.c @@ -344,7 +344,7 @@ bool wc_unescape(char *output, const char *wildcard) return true; /* it's clean */ } -#ifdef TESTMODE +#ifdef TEST struct test { const char *wildcard; @@ -483,4 +483,4 @@ int main(void) return 0; } -#endif +#endif /* TEST */ diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt index b512f459..e312f28f 100644 --- a/windows/CMakeLists.txt +++ b/windows/CMakeLists.txt @@ -167,3 +167,8 @@ set_target_properties(puttygen PROPERTIES WIN32_EXECUTABLE ON LINK_FLAGS "${LFLAG_MANIFEST_NO}") installed_program(puttygen) + +add_executable(test_split_into_argv + utils/split_into_argv.c) +target_compile_definitions(test_split_into_argv PRIVATE TEST) +target_link_libraries(test_split_into_argv utils ${platform_libraries}) diff --git a/windows/utils/split_into_argv.c b/windows/utils/split_into_argv.c index d1957b48..1f89f3b4 100644 --- a/windows/utils/split_into_argv.c +++ b/windows/utils/split_into_argv.c @@ -221,7 +221,7 @@ void split_into_argv(char *cmdline, int *argc, char ***argv, if (argstart) *argstart = outputargstart; else sfree(outputargstart); } -#ifdef TESTMODE +#ifdef TEST const struct argv_test { const char *cmdline; @@ -323,6 +323,12 @@ const struct argv_test { {"\"a\\\\\\\\\"\"\"\"\"\"\"\"b c\" d", {"a\\\\\"\"\"b", "c d", NULL}}, }; +void out_of_memory(void) +{ + fprintf(stderr, "out of memory!\n"); + exit(2); +} + int main(int argc, char **argv) { int i, j; @@ -429,7 +435,7 @@ int main(int argc, char **argv) int ac; char **av; - split_into_argv(argv_tests[i].cmdline, &ac, &av); + split_into_argv((char *)argv_tests[i].cmdline, &ac, &av, NULL); for (j = 0; j < ac && argv_tests[i].argv[j]; j++) { if (strcmp(av[j], argv_tests[i].argv[j])) { @@ -456,4 +462,4 @@ int main(int argc, char **argv) return 0; } -#endif +#endif /* TEST */ -- cgit v1.2.3 From b00e5fb12929da5015e7331fe9b88f245301ebf2 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 18 Apr 2021 07:58:27 +0100 Subject: Remove the switching system in puttyps.h. It was there because of a limitation of mkfiles.pl, which had a single list of include directories that it used on all platforms. CMake does not. So now there's an easier and more sensible way to have a different header file included on Windows and Unix: call it the same name in the two subdirectories, and rely on CMake having put the right one of those subdirs on the include path. --- putty.h | 2 +- puttyps.h | 18 -- unix/platform.h | 464 ++++++++++++++++++++++++++++++++++ unix/unix.h | 460 ---------------------------------- windows/platform.h | 711 +++++++++++++++++++++++++++++++++++++++++++++++++++++ windows/winstuff.h | 711 ----------------------------------------------------- 6 files changed, 1176 insertions(+), 1190 deletions(-) delete mode 100644 puttyps.h create mode 100644 unix/platform.h delete mode 100644 unix/unix.h create mode 100644 windows/platform.h delete mode 100644 windows/winstuff.h diff --git a/putty.h b/putty.h index a14da123..d09f89b4 100644 --- a/putty.h +++ b/putty.h @@ -5,7 +5,7 @@ #include /* for INT_MAX */ #include "defs.h" -#include "puttyps.h" +#include "platform.h" #include "network.h" #include "misc.h" #include "marshal.h" diff --git a/puttyps.h b/puttyps.h deleted file mode 100644 index 27916d27..00000000 --- a/puttyps.h +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Find the platform-specific header for this platform. - */ - -#ifndef PUTTY_PUTTYPS_H -#define PUTTY_PUTTYPS_H - -#ifdef _WINDOWS - -#include "winstuff.h" - -#else - -#include "unix.h" - -#endif - -#endif diff --git a/unix/platform.h b/unix/platform.h new file mode 100644 index 00000000..8f24b893 --- /dev/null +++ b/unix/platform.h @@ -0,0 +1,464 @@ +/* + * unix/platform.h: Unix-specific inter-module stuff. + */ + +#ifndef PUTTY_UNIX_PLATFORM_H +#define PUTTY_UNIX_PLATFORM_H + +#if HAVE_CMAKE_H +#include "cmake.h" +#endif + +#include /* for FILENAME_MAX */ +#include /* C99 int types */ +#ifndef NO_LIBDL +#include /* Dynamic library loading */ +#endif /* NO_LIBDL */ +#include "charset.h" +#include /* for mode_t */ + +#ifdef OSX_GTK +/* + * Assorted tweaks to various parts of the GTK front end which all + * need to be enabled when compiling on OS X. Because I might need the + * same tweaks on other systems in future, I don't want to + * conditionalise all of them on OSX_GTK directly, so instead, each + * one has its own name and we enable them all centrally here if + * OSX_GTK is defined at configure time. + */ +#define NOT_X_WINDOWS /* of course, all the X11 stuff should be disabled */ +#define NO_PTY_PRE_INIT /* OS X gets very huffy if we try to set[ug]id */ +#define SET_NONBLOCK_VIA_OPENPT /* work around missing fcntl functionality */ +#define OSX_META_KEY_CONFIG /* two possible Meta keys to choose from */ +/* this potential one of the Meta keys needs manual handling */ +#define META_MANUAL_MASK (GDK_MOD1_MASK) +#define JUST_USE_GTK_CLIPBOARD_UTF8 /* low-level gdk_selection_* fails */ + +#define BUILDINFO_PLATFORM_GTK "OS X (GTK)" +#define BUILDINFO_GTK + +#elif defined NOT_X_WINDOWS + +#define BUILDINFO_PLATFORM_GTK "Unix (pure GTK)" +#define BUILDINFO_GTK + +#else + +#define BUILDINFO_PLATFORM_GTK "Unix (GTK + X11)" +#define BUILDINFO_GTK + +#endif + +/* BUILDINFO_PLATFORM varies its expansion between the GTK and + * pure-CLI utilities, so that Unix Plink, PSFTP etc don't announce + * themselves incongruously as having something to do with GTK. */ +#define BUILDINFO_PLATFORM_CLI "Unix" +extern const bool buildinfo_gtk_relevant; +#define BUILDINFO_PLATFORM (buildinfo_gtk_relevant ? \ + BUILDINFO_PLATFORM_GTK : BUILDINFO_PLATFORM_CLI) + +char *buildinfo_gtk_version(void); + +struct Filename { + char *path; +}; +FILE *f_open(const struct Filename *, char const *, bool); + +struct FontSpec { + char *name; /* may be "" to indicate no selected font at all */ +}; +struct FontSpec *fontspec_new(const char *name); + +extern const struct BackendVtable pty_backend; + +#define BROKEN_PIPE_ERROR_CODE EPIPE /* used in sshshare.c */ + +/* + * 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); +#define GETTICKCOUNT getticks +#define TICKSPERSEC 1000 /* we choose to use milliseconds */ +#define CURSORBLINK 450 /* no standard way to set this */ + +#define WCHAR wchar_t +#define BYTE unsigned char + +#define PLATFORM_CLIPBOARDS(X) \ + X(CLIP_PRIMARY, "X11 primary selection") \ + X(CLIP_CLIPBOARD, "XDG clipboard") \ + X(CLIP_CUSTOM_1, "") \ + X(CLIP_CUSTOM_2, "") \ + X(CLIP_CUSTOM_3, "") \ + /* end of list */ + +#ifdef OSX_GTK +/* OS X has no PRIMARY selection */ +#define MOUSE_SELECT_CLIPBOARD CLIP_NULL +#define MOUSE_PASTE_CLIPBOARD CLIP_LOCAL +#define CLIPNAME_IMPLICIT "Last selected text" +#define CLIPNAME_EXPLICIT "System clipboard" +#define CLIPNAME_EXPLICIT_OBJECT "system clipboard" +/* These defaults are the ones that more or less comply with the OS X + * Human Interface Guidelines, i.e. copy/paste to the system clipboard + * is _not_ implicit but requires a specific UI action. This is at + * odds with all other PuTTY front ends' defaults, but on OS X there + * is no multi-decade precedent for PuTTY working the other way. */ +#define CLIPUI_DEFAULT_AUTOCOPY false +#define CLIPUI_DEFAULT_MOUSE CLIPUI_IMPLICIT +#define CLIPUI_DEFAULT_INS CLIPUI_EXPLICIT +#define MENU_CLIPBOARD CLIP_CLIPBOARD +#define COPYALL_CLIPBOARDS CLIP_CLIPBOARD +#else +#define MOUSE_SELECT_CLIPBOARD CLIP_PRIMARY +#define MOUSE_PASTE_CLIPBOARD CLIP_PRIMARY +#define CLIPNAME_IMPLICIT "PRIMARY" +#define CLIPNAME_EXPLICIT "CLIPBOARD" +#define CLIPNAME_EXPLICIT_OBJECT "CLIPBOARD" +/* These defaults are the ones Unix PuTTY has historically had since + * it was first thought of in 2002 */ +#define CLIPUI_DEFAULT_AUTOCOPY false +#define CLIPUI_DEFAULT_MOUSE CLIPUI_IMPLICIT +#define CLIPUI_DEFAULT_INS CLIPUI_IMPLICIT +#define MENU_CLIPBOARD CLIP_CLIPBOARD +#define COPYALL_CLIPBOARDS CLIP_PRIMARY, CLIP_CLIPBOARD +/* X11 supports arbitrary named clipboards */ +#define NAMED_CLIPBOARDS +#endif + +/* The per-session frontend structure managed by gtkwin.c */ +typedef struct GtkFrontend GtkFrontend; + +/* Callback when a dialog box finishes, and a no-op implementation of it */ +typedef void (*post_dialog_fn_t)(void *ctx, int result); +void trivial_post_dialog_fn(void *vctx, int result); + +/* Start up a session window, with or without a preliminary config box */ +void initial_config_box(Conf *conf, post_dialog_fn_t after, void *afterctx); +void new_session_window(Conf *conf, const char *geometry_string); + +/* Defined in gtkmain.c */ +void launch_duplicate_session(Conf *conf); +void launch_new_session(void); +void launch_saved_session(const char *str); +void session_window_closed(void); +void window_setup_error(const char *errmsg); +#ifdef MAY_REFER_TO_GTK_IN_HEADERS +GtkWidget *make_gtk_toplevel_window(GtkFrontend *frontend); +#endif + +const struct BackendVtable *select_backend(Conf *conf); + +/* Defined in gtkcomm.c */ +void gtkcomm_setup(void); + +/* Used to pass application-menu operations from gtkapp.c to gtkwin.c */ +enum MenuAction { + MA_COPY, MA_PASTE, MA_COPY_ALL, MA_DUPLICATE_SESSION, + MA_RESTART_SESSION, MA_CHANGE_SETTINGS, MA_CLEAR_SCROLLBACK, + MA_RESET_TERMINAL, MA_EVENT_LOG +}; +void app_menu_action(GtkFrontend *frontend, enum MenuAction); + +/* Arrays of pixmap data used for GTK window icons. (main_icon is for + * the process's main window; cfg_icon is the modified icon used for + * its config box.) */ +extern const char *const *const main_icon[]; +extern const char *const *const cfg_icon[]; +extern const int n_main_icon, n_cfg_icon; + +/* Things gtkdlg.c needs from gtkwin.c */ +#ifdef MAY_REFER_TO_GTK_IN_HEADERS +enum DialogSlot { + DIALOG_SLOT_RECONFIGURE, + DIALOG_SLOT_NETWORK_PROMPT, + DIALOG_SLOT_LOGFILE_PROMPT, + DIALOG_SLOT_WARN_ON_CLOSE, + DIALOG_SLOT_CONNECTION_FATAL, + DIALOG_SLOT_LIMIT /* must remain last */ +}; +GtkWidget *gtk_seat_get_window(Seat *seat); +void register_dialog(Seat *seat, enum DialogSlot slot, GtkWidget *dialog); +void unregister_dialog(Seat *seat, enum DialogSlot slot); +void set_window_icon(GtkWidget *window, const char *const *const *icon, + int n_icon); +extern GdkAtom compound_text_atom; +#endif + +/* Things gtkwin.c needs from gtkdlg.c */ +#ifdef MAY_REFER_TO_GTK_IN_HEADERS +GtkWidget *create_config_box(const char *title, Conf *conf, + bool midsession, int protcfginfo, + post_dialog_fn_t after, void *afterctx); +#endif +void nonfatal_message_box(void *window, const char *msg); +void about_box(void *window); +typedef struct eventlog_stuff eventlog_stuff; +eventlog_stuff *eventlogstuff_new(void); +void eventlogstuff_free(eventlog_stuff *); +void showeventlog(eventlog_stuff *estuff, void *parentwin); +void logevent_dlg(eventlog_stuff *estuff, const char *string); +int gtkdlg_askappend(Seat *seat, Filename *filename, + void (*callback)(void *ctx, int result), void *ctx); +int gtk_seat_verify_ssh_host_key( + Seat *seat, const char *host, int port, const char *keytype, + char *keystr, const char *keydisp, char **fingerprints, + void (*callback)(void *ctx, int result), void *ctx); +int gtk_seat_confirm_weak_crypto_primitive( + Seat *seat, const char *algtype, const char *algname, + void (*callback)(void *ctx, int result), void *ctx); +int gtk_seat_confirm_weak_cached_hostkey( + Seat *seat, const char *algname, const char *betteralgs, + void (*callback)(void *ctx, int result), void *ctx); +#ifdef MAY_REFER_TO_GTK_IN_HEADERS +struct message_box_button { + const char *title; + char shortcut; + int type; /* more negative means more appropriate to be the Esc action */ + int value; /* message box's return value if this is pressed */ +}; +struct message_box_buttons { + const struct message_box_button *buttons; + int nbuttons; +}; +extern const struct message_box_buttons buttons_yn, buttons_ok; +GtkWidget *create_message_box( + GtkWidget *parentwin, const char *title, const char *msg, int minwid, + bool selectable, const struct message_box_buttons *buttons, + post_dialog_fn_t after, void *afterctx); +#endif + +/* gtkwin.c needs this special function in xkeysym.c */ +int keysym_to_unicode(int keysym); + +/* Things uxstore.c needs from gtkwin.c */ +char *x_get_default(const char *key); + +/* Things uxstore.c provides to gtkwin.c */ +void provide_xrm_string(const char *string, const char *progname); + +/* Function that {gtkapp,gtkmain}.c needs from ux{pterm,putty}.c. Does + * early process setup that varies between applications (e.g. + * pty_pre_init or sk_init), and is passed a boolean by the caller + * indicating whether this is an OS X style multi-session monolithic + * process or an ordinary Unix one-shot. */ +void setup(bool single_session_in_this_process); + +/* + * Per-application constants that affect behaviour of shared modules. + */ +/* Do we need an Event Log menu item? (yes for PuTTY, no for pterm) */ +extern const bool use_event_log; +/* Do we need a New Session menu item? (yes for PuTTY, no for pterm) */ +extern const bool new_session; +/* Do we need a Saved Sessions menu item? (yes for PuTTY, no for pterm) */ +extern const bool saved_sessions; +/* When we Duplicate Session, do we need to double-check that the Conf + * is in a launchable state? (no for pterm, because conf_launchable + * returns an irrelevant answer, since we'll force use of the pty + * backend which ignores all the relevant settings) */ +extern const bool dup_check_launchable; +/* In the Duplicate Session serialised data, do we send/receive an + * argv array after the main Conf? (yes for pterm, no for PuTTY) */ +extern const bool use_pty_argv; + +/* + * OS X environment munging: this is the prefix we expect to find on + * environment variable names that were changed by osxlaunch. + * Extracted from the command line of the OS X pterm main binary, and + * used in uxpty.c to restore the original environment before + * launching its subprocess. + */ +extern char *pty_osx_envrestore_prefix; + +/* Things provided by uxcons.c */ +struct termios; +void stderr_tty_init(void); /* call at startup if stderr might be a tty */ +void premsg(struct termios *); +void postmsg(struct termios *); + +/* The interface used by uxsel.c */ +typedef struct uxsel_id uxsel_id; +void uxsel_init(void); +typedef void (*uxsel_callback_fn)(int fd, int event); +void uxsel_set(int fd, int rwx, uxsel_callback_fn callback); +void uxsel_del(int fd); +enum { SELECT_R = 1, SELECT_W = 2, SELECT_X = 4 }; +void 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 */ +uxsel_id *uxsel_input_add(int fd, int rwx); /* returns an id */ +void uxsel_input_remove(uxsel_id *id); + +/* uxcfg.c */ +struct controlbox; +void unix_setup_config_box( + struct controlbox *b, bool midsession, int protocol); + +/* gtkcfg.c */ +void gtk_setup_config_box( + struct controlbox *b, bool 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, bool block_it); + +/* uxmisc.c */ +void cloexec(int); +void noncloexec(int); +bool nonblock(int); +bool no_nonblock(int); +char *make_dir_and_check_ours(const char *dirname); +char *make_dir_path(const char *path, mode_t mode); + +/* + * Exports from unicode.c. + */ +struct unicode_data; +bool init_ucs(struct unicode_data *ucsdata, char *line_codepage, + bool utf8_override, int font_charset, int vtmode); + +/* + * Spare functions exported directly from uxnet.c. + */ +void *sk_getxdmdata(Socket *sock, int *lenp); +int sk_net_get_fd(Socket *sock); +SockAddr *unix_sock_addr(const char *path); +Socket *new_unix_listener(SockAddr *listenaddr, Plug *plug); + +/* + * 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 uxser.c. + */ +extern const struct BackendVtable serial_backend; + +/* + * uxpeer.c, wrapping getsockopt(SO_PEERCRED). + */ +bool so_peercred(int fd, int *pid, int *uid, int *gid); + +/* + * uxfdsock.c. + */ +Socket *make_fd_socket(int infd, int outfd, int inerrfd, Plug *plug); + +/* + * Default font setting, which can vary depending on NOT_X_WINDOWS. + */ +#ifdef NOT_X_WINDOWS +#define DEFAULT_GTK_FONT "client:Monospace 12" +#else +#define DEFAULT_GTK_FONT "server:fixed" +#endif + +/* + * uxpty.c. + */ +void pty_pre_init(void); /* pty+utmp setup before dropping privilege */ +/* Pass in the argv[] for an instance of the pty backend created by + * the standard vtable constructor. Only called from (non-OSX) pterm, + * which will construct exactly one such instance, and initialises + * this from the command line. */ +extern char **pty_argv; + +/* + * gtkask.c. + */ +char *gtk_askpass_main(const char *display, const char *wintitle, + const char *prompt, bool *success); + +/* + * procnet.c. + */ +bool socket_peer_is_same_user(int fd); +static inline bool sk_peer_trusted(Socket *sock) +{ + int fd = sk_net_get_fd(sock); + return fd >= 0 && socket_peer_is_same_user(fd); +} + +/* + * uxsftpserver.c. + */ +extern const SftpServerVtable unix_live_sftpserver_vt; + +/* + * uxpoll.c. + */ +typedef struct pollwrapper pollwrapper; +pollwrapper *pollwrap_new(void); +void pollwrap_free(pollwrapper *pw); +void pollwrap_clear(pollwrapper *pw); +void pollwrap_add_fd_events(pollwrapper *pw, int fd, int events); +void pollwrap_add_fd_rwx(pollwrapper *pw, int fd, int rwx); +int pollwrap_poll_instant(pollwrapper *pw); +int pollwrap_poll_endless(pollwrapper *pw); +int pollwrap_poll_timeout(pollwrapper *pw, int milliseconds); +int pollwrap_get_fd_events(pollwrapper *pw, int fd); +int pollwrap_get_fd_rwx(pollwrapper *pw, int fd); +static inline bool pollwrap_check_fd_rwx(pollwrapper *pw, int fd, int rwx) +{ + return (pollwrap_get_fd_rwx(pw, fd) & rwx) != 0; +} + +/* + * uxcliloop.c. + */ +typedef bool (*cliloop_pw_setup_t)(void *ctx, pollwrapper *pw); +typedef void (*cliloop_pw_check_t)(void *ctx, pollwrapper *pw); +typedef bool (*cliloop_continue_t)(void *ctx, bool found_any_fd, + bool ran_any_callback); + +void cli_main_loop(cliloop_pw_setup_t pw_setup, + cliloop_pw_check_t pw_check, + cliloop_continue_t cont, void *ctx); + +bool cliloop_no_pw_setup(void *ctx, pollwrapper *pw); +void cliloop_no_pw_check(void *ctx, pollwrapper *pw); +bool cliloop_always_continue(void *ctx, bool, bool); + +#endif /* PUTTY_UNIX_PLATFORM_H */ diff --git a/unix/unix.h b/unix/unix.h deleted file mode 100644 index d62c42f3..00000000 --- a/unix/unix.h +++ /dev/null @@ -1,460 +0,0 @@ -#ifndef PUTTY_UNIX_H -#define PUTTY_UNIX_H - -#if HAVE_CMAKE_H -#include "cmake.h" -#endif - -#include /* for FILENAME_MAX */ -#include /* C99 int types */ -#ifndef NO_LIBDL -#include /* Dynamic library loading */ -#endif /* NO_LIBDL */ -#include "charset.h" -#include /* for mode_t */ - -#ifdef OSX_GTK -/* - * Assorted tweaks to various parts of the GTK front end which all - * need to be enabled when compiling on OS X. Because I might need the - * same tweaks on other systems in future, I don't want to - * conditionalise all of them on OSX_GTK directly, so instead, each - * one has its own name and we enable them all centrally here if - * OSX_GTK is defined at configure time. - */ -#define NOT_X_WINDOWS /* of course, all the X11 stuff should be disabled */ -#define NO_PTY_PRE_INIT /* OS X gets very huffy if we try to set[ug]id */ -#define SET_NONBLOCK_VIA_OPENPT /* work around missing fcntl functionality */ -#define OSX_META_KEY_CONFIG /* two possible Meta keys to choose from */ -/* this potential one of the Meta keys needs manual handling */ -#define META_MANUAL_MASK (GDK_MOD1_MASK) -#define JUST_USE_GTK_CLIPBOARD_UTF8 /* low-level gdk_selection_* fails */ - -#define BUILDINFO_PLATFORM_GTK "OS X (GTK)" -#define BUILDINFO_GTK - -#elif defined NOT_X_WINDOWS - -#define BUILDINFO_PLATFORM_GTK "Unix (pure GTK)" -#define BUILDINFO_GTK - -#else - -#define BUILDINFO_PLATFORM_GTK "Unix (GTK + X11)" -#define BUILDINFO_GTK - -#endif - -/* BUILDINFO_PLATFORM varies its expansion between the GTK and - * pure-CLI utilities, so that Unix Plink, PSFTP etc don't announce - * themselves incongruously as having something to do with GTK. */ -#define BUILDINFO_PLATFORM_CLI "Unix" -extern const bool buildinfo_gtk_relevant; -#define BUILDINFO_PLATFORM (buildinfo_gtk_relevant ? \ - BUILDINFO_PLATFORM_GTK : BUILDINFO_PLATFORM_CLI) - -char *buildinfo_gtk_version(void); - -struct Filename { - char *path; -}; -FILE *f_open(const struct Filename *, char const *, bool); - -struct FontSpec { - char *name; /* may be "" to indicate no selected font at all */ -}; -struct FontSpec *fontspec_new(const char *name); - -extern const struct BackendVtable pty_backend; - -#define BROKEN_PIPE_ERROR_CODE EPIPE /* used in sshshare.c */ - -/* - * 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); -#define GETTICKCOUNT getticks -#define TICKSPERSEC 1000 /* we choose to use milliseconds */ -#define CURSORBLINK 450 /* no standard way to set this */ - -#define WCHAR wchar_t -#define BYTE unsigned char - -#define PLATFORM_CLIPBOARDS(X) \ - X(CLIP_PRIMARY, "X11 primary selection") \ - X(CLIP_CLIPBOARD, "XDG clipboard") \ - X(CLIP_CUSTOM_1, "") \ - X(CLIP_CUSTOM_2, "") \ - X(CLIP_CUSTOM_3, "") \ - /* end of list */ - -#ifdef OSX_GTK -/* OS X has no PRIMARY selection */ -#define MOUSE_SELECT_CLIPBOARD CLIP_NULL -#define MOUSE_PASTE_CLIPBOARD CLIP_LOCAL -#define CLIPNAME_IMPLICIT "Last selected text" -#define CLIPNAME_EXPLICIT "System clipboard" -#define CLIPNAME_EXPLICIT_OBJECT "system clipboard" -/* These defaults are the ones that more or less comply with the OS X - * Human Interface Guidelines, i.e. copy/paste to the system clipboard - * is _not_ implicit but requires a specific UI action. This is at - * odds with all other PuTTY front ends' defaults, but on OS X there - * is no multi-decade precedent for PuTTY working the other way. */ -#define CLIPUI_DEFAULT_AUTOCOPY false -#define CLIPUI_DEFAULT_MOUSE CLIPUI_IMPLICIT -#define CLIPUI_DEFAULT_INS CLIPUI_EXPLICIT -#define MENU_CLIPBOARD CLIP_CLIPBOARD -#define COPYALL_CLIPBOARDS CLIP_CLIPBOARD -#else -#define MOUSE_SELECT_CLIPBOARD CLIP_PRIMARY -#define MOUSE_PASTE_CLIPBOARD CLIP_PRIMARY -#define CLIPNAME_IMPLICIT "PRIMARY" -#define CLIPNAME_EXPLICIT "CLIPBOARD" -#define CLIPNAME_EXPLICIT_OBJECT "CLIPBOARD" -/* These defaults are the ones Unix PuTTY has historically had since - * it was first thought of in 2002 */ -#define CLIPUI_DEFAULT_AUTOCOPY false -#define CLIPUI_DEFAULT_MOUSE CLIPUI_IMPLICIT -#define CLIPUI_DEFAULT_INS CLIPUI_IMPLICIT -#define MENU_CLIPBOARD CLIP_CLIPBOARD -#define COPYALL_CLIPBOARDS CLIP_PRIMARY, CLIP_CLIPBOARD -/* X11 supports arbitrary named clipboards */ -#define NAMED_CLIPBOARDS -#endif - -/* The per-session frontend structure managed by gtkwin.c */ -typedef struct GtkFrontend GtkFrontend; - -/* Callback when a dialog box finishes, and a no-op implementation of it */ -typedef void (*post_dialog_fn_t)(void *ctx, int result); -void trivial_post_dialog_fn(void *vctx, int result); - -/* Start up a session window, with or without a preliminary config box */ -void initial_config_box(Conf *conf, post_dialog_fn_t after, void *afterctx); -void new_session_window(Conf *conf, const char *geometry_string); - -/* Defined in gtkmain.c */ -void launch_duplicate_session(Conf *conf); -void launch_new_session(void); -void launch_saved_session(const char *str); -void session_window_closed(void); -void window_setup_error(const char *errmsg); -#ifdef MAY_REFER_TO_GTK_IN_HEADERS -GtkWidget *make_gtk_toplevel_window(GtkFrontend *frontend); -#endif - -const struct BackendVtable *select_backend(Conf *conf); - -/* Defined in gtkcomm.c */ -void gtkcomm_setup(void); - -/* Used to pass application-menu operations from gtkapp.c to gtkwin.c */ -enum MenuAction { - MA_COPY, MA_PASTE, MA_COPY_ALL, MA_DUPLICATE_SESSION, - MA_RESTART_SESSION, MA_CHANGE_SETTINGS, MA_CLEAR_SCROLLBACK, - MA_RESET_TERMINAL, MA_EVENT_LOG -}; -void app_menu_action(GtkFrontend *frontend, enum MenuAction); - -/* Arrays of pixmap data used for GTK window icons. (main_icon is for - * the process's main window; cfg_icon is the modified icon used for - * its config box.) */ -extern const char *const *const main_icon[]; -extern const char *const *const cfg_icon[]; -extern const int n_main_icon, n_cfg_icon; - -/* Things gtkdlg.c needs from gtkwin.c */ -#ifdef MAY_REFER_TO_GTK_IN_HEADERS -enum DialogSlot { - DIALOG_SLOT_RECONFIGURE, - DIALOG_SLOT_NETWORK_PROMPT, - DIALOG_SLOT_LOGFILE_PROMPT, - DIALOG_SLOT_WARN_ON_CLOSE, - DIALOG_SLOT_CONNECTION_FATAL, - DIALOG_SLOT_LIMIT /* must remain last */ -}; -GtkWidget *gtk_seat_get_window(Seat *seat); -void register_dialog(Seat *seat, enum DialogSlot slot, GtkWidget *dialog); -void unregister_dialog(Seat *seat, enum DialogSlot slot); -void set_window_icon(GtkWidget *window, const char *const *const *icon, - int n_icon); -extern GdkAtom compound_text_atom; -#endif - -/* Things gtkwin.c needs from gtkdlg.c */ -#ifdef MAY_REFER_TO_GTK_IN_HEADERS -GtkWidget *create_config_box(const char *title, Conf *conf, - bool midsession, int protcfginfo, - post_dialog_fn_t after, void *afterctx); -#endif -void nonfatal_message_box(void *window, const char *msg); -void about_box(void *window); -typedef struct eventlog_stuff eventlog_stuff; -eventlog_stuff *eventlogstuff_new(void); -void eventlogstuff_free(eventlog_stuff *); -void showeventlog(eventlog_stuff *estuff, void *parentwin); -void logevent_dlg(eventlog_stuff *estuff, const char *string); -int gtkdlg_askappend(Seat *seat, Filename *filename, - void (*callback)(void *ctx, int result), void *ctx); -int gtk_seat_verify_ssh_host_key( - Seat *seat, const char *host, int port, const char *keytype, - char *keystr, const char *keydisp, char **fingerprints, - void (*callback)(void *ctx, int result), void *ctx); -int gtk_seat_confirm_weak_crypto_primitive( - Seat *seat, const char *algtype, const char *algname, - void (*callback)(void *ctx, int result), void *ctx); -int gtk_seat_confirm_weak_cached_hostkey( - Seat *seat, const char *algname, const char *betteralgs, - void (*callback)(void *ctx, int result), void *ctx); -#ifdef MAY_REFER_TO_GTK_IN_HEADERS -struct message_box_button { - const char *title; - char shortcut; - int type; /* more negative means more appropriate to be the Esc action */ - int value; /* message box's return value if this is pressed */ -}; -struct message_box_buttons { - const struct message_box_button *buttons; - int nbuttons; -}; -extern const struct message_box_buttons buttons_yn, buttons_ok; -GtkWidget *create_message_box( - GtkWidget *parentwin, const char *title, const char *msg, int minwid, - bool selectable, const struct message_box_buttons *buttons, - post_dialog_fn_t after, void *afterctx); -#endif - -/* gtkwin.c needs this special function in xkeysym.c */ -int keysym_to_unicode(int keysym); - -/* Things uxstore.c needs from gtkwin.c */ -char *x_get_default(const char *key); - -/* Things uxstore.c provides to gtkwin.c */ -void provide_xrm_string(const char *string, const char *progname); - -/* Function that {gtkapp,gtkmain}.c needs from ux{pterm,putty}.c. Does - * early process setup that varies between applications (e.g. - * pty_pre_init or sk_init), and is passed a boolean by the caller - * indicating whether this is an OS X style multi-session monolithic - * process or an ordinary Unix one-shot. */ -void setup(bool single_session_in_this_process); - -/* - * Per-application constants that affect behaviour of shared modules. - */ -/* Do we need an Event Log menu item? (yes for PuTTY, no for pterm) */ -extern const bool use_event_log; -/* Do we need a New Session menu item? (yes for PuTTY, no for pterm) */ -extern const bool new_session; -/* Do we need a Saved Sessions menu item? (yes for PuTTY, no for pterm) */ -extern const bool saved_sessions; -/* When we Duplicate Session, do we need to double-check that the Conf - * is in a launchable state? (no for pterm, because conf_launchable - * returns an irrelevant answer, since we'll force use of the pty - * backend which ignores all the relevant settings) */ -extern const bool dup_check_launchable; -/* In the Duplicate Session serialised data, do we send/receive an - * argv array after the main Conf? (yes for pterm, no for PuTTY) */ -extern const bool use_pty_argv; - -/* - * OS X environment munging: this is the prefix we expect to find on - * environment variable names that were changed by osxlaunch. - * Extracted from the command line of the OS X pterm main binary, and - * used in uxpty.c to restore the original environment before - * launching its subprocess. - */ -extern char *pty_osx_envrestore_prefix; - -/* Things provided by uxcons.c */ -struct termios; -void stderr_tty_init(void); /* call at startup if stderr might be a tty */ -void premsg(struct termios *); -void postmsg(struct termios *); - -/* The interface used by uxsel.c */ -typedef struct uxsel_id uxsel_id; -void uxsel_init(void); -typedef void (*uxsel_callback_fn)(int fd, int event); -void uxsel_set(int fd, int rwx, uxsel_callback_fn callback); -void uxsel_del(int fd); -enum { SELECT_R = 1, SELECT_W = 2, SELECT_X = 4 }; -void 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 */ -uxsel_id *uxsel_input_add(int fd, int rwx); /* returns an id */ -void uxsel_input_remove(uxsel_id *id); - -/* uxcfg.c */ -struct controlbox; -void unix_setup_config_box( - struct controlbox *b, bool midsession, int protocol); - -/* gtkcfg.c */ -void gtk_setup_config_box( - struct controlbox *b, bool 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, bool block_it); - -/* uxmisc.c */ -void cloexec(int); -void noncloexec(int); -bool nonblock(int); -bool no_nonblock(int); -char *make_dir_and_check_ours(const char *dirname); -char *make_dir_path(const char *path, mode_t mode); - -/* - * Exports from unicode.c. - */ -struct unicode_data; -bool init_ucs(struct unicode_data *ucsdata, char *line_codepage, - bool utf8_override, int font_charset, int vtmode); - -/* - * Spare functions exported directly from uxnet.c. - */ -void *sk_getxdmdata(Socket *sock, int *lenp); -int sk_net_get_fd(Socket *sock); -SockAddr *unix_sock_addr(const char *path); -Socket *new_unix_listener(SockAddr *listenaddr, Plug *plug); - -/* - * 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 uxser.c. - */ -extern const struct BackendVtable serial_backend; - -/* - * uxpeer.c, wrapping getsockopt(SO_PEERCRED). - */ -bool so_peercred(int fd, int *pid, int *uid, int *gid); - -/* - * uxfdsock.c. - */ -Socket *make_fd_socket(int infd, int outfd, int inerrfd, Plug *plug); - -/* - * Default font setting, which can vary depending on NOT_X_WINDOWS. - */ -#ifdef NOT_X_WINDOWS -#define DEFAULT_GTK_FONT "client:Monospace 12" -#else -#define DEFAULT_GTK_FONT "server:fixed" -#endif - -/* - * uxpty.c. - */ -void pty_pre_init(void); /* pty+utmp setup before dropping privilege */ -/* Pass in the argv[] for an instance of the pty backend created by - * the standard vtable constructor. Only called from (non-OSX) pterm, - * which will construct exactly one such instance, and initialises - * this from the command line. */ -extern char **pty_argv; - -/* - * gtkask.c. - */ -char *gtk_askpass_main(const char *display, const char *wintitle, - const char *prompt, bool *success); - -/* - * procnet.c. - */ -bool socket_peer_is_same_user(int fd); -static inline bool sk_peer_trusted(Socket *sock) -{ - int fd = sk_net_get_fd(sock); - return fd >= 0 && socket_peer_is_same_user(fd); -} - -/* - * uxsftpserver.c. - */ -extern const SftpServerVtable unix_live_sftpserver_vt; - -/* - * uxpoll.c. - */ -typedef struct pollwrapper pollwrapper; -pollwrapper *pollwrap_new(void); -void pollwrap_free(pollwrapper *pw); -void pollwrap_clear(pollwrapper *pw); -void pollwrap_add_fd_events(pollwrapper *pw, int fd, int events); -void pollwrap_add_fd_rwx(pollwrapper *pw, int fd, int rwx); -int pollwrap_poll_instant(pollwrapper *pw); -int pollwrap_poll_endless(pollwrapper *pw); -int pollwrap_poll_timeout(pollwrapper *pw, int milliseconds); -int pollwrap_get_fd_events(pollwrapper *pw, int fd); -int pollwrap_get_fd_rwx(pollwrapper *pw, int fd); -static inline bool pollwrap_check_fd_rwx(pollwrapper *pw, int fd, int rwx) -{ - return (pollwrap_get_fd_rwx(pw, fd) & rwx) != 0; -} - -/* - * uxcliloop.c. - */ -typedef bool (*cliloop_pw_setup_t)(void *ctx, pollwrapper *pw); -typedef void (*cliloop_pw_check_t)(void *ctx, pollwrapper *pw); -typedef bool (*cliloop_continue_t)(void *ctx, bool found_any_fd, - bool ran_any_callback); - -void cli_main_loop(cliloop_pw_setup_t pw_setup, - cliloop_pw_check_t pw_check, - cliloop_continue_t cont, void *ctx); - -bool cliloop_no_pw_setup(void *ctx, pollwrapper *pw); -void cliloop_no_pw_check(void *ctx, pollwrapper *pw); -bool cliloop_always_continue(void *ctx, bool, bool); - -#endif /* PUTTY_UNIX_H */ diff --git a/windows/platform.h b/windows/platform.h new file mode 100644 index 00000000..8fadf803 --- /dev/null +++ b/windows/platform.h @@ -0,0 +1,711 @@ +/* + * windows/platform.h: Windows-specific inter-module stuff. + */ + +#ifndef PUTTY_WINDOWS_PLATFORM_H +#define PUTTY_WINDOWS_PLATFORM_H + +#if HAVE_CMAKE_H +#include "cmake.h" +#endif + +#include +#include +#include /* for FILENAME_MAX */ + +/* We use uintptr_t for Win32/Win64 portability, so we should in + * principle include stdint.h, which defines it according to the C + * standard. But older versions of Visual Studio don't provide + * stdint.h at all, but do (non-standardly) define uintptr_t in + * stddef.h. So here we try to make sure _some_ standard header is + * included which defines uintptr_t. */ +#include +#if !HAVE_NO_STDINT_H +#include +#endif + +#include "defs.h" +#include "marshal.h" + +#include "tree234.h" + +#include "winhelp.h" + +#if defined _M_IX86 || defined _M_AMD64 +#define BUILDINFO_PLATFORM "x86 Windows" +#elif defined _M_ARM || defined _M_ARM64 +#define BUILDINFO_PLATFORM "Arm Windows" +#else +#define BUILDINFO_PLATFORM "Windows" +#endif + +struct Filename { + char *path; +}; +static inline FILE *f_open(const Filename *filename, const char *mode, + bool isprivate) +{ + return fopen(filename->path, mode); +} + +struct FontSpec { + char *name; + bool isbold; + int height; + int charset; +}; +struct FontSpec *fontspec_new( + const char *name, bool bold, 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 */ + +#define PLATFORM_CLIPBOARDS(X) \ + X(CLIP_SYSTEM, "system clipboard") \ + /* end of list */ + +/* + * 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 + +#if !HAVE_STRTOUMAX +/* Work around lack of strtoumax in older MSVC libraries */ +static inline uintmax_t strtoumax(const char *nptr, char **endptr, int base) +{ return _strtoui64(nptr, endptr, base); } +#endif + +#define BOXFLAGS DLGWINDOWEXTRA +#define BOXRESULT (DLGWINDOWEXTRA + sizeof(LONG_PTR)) +#define DF_END 0x0001 + +#ifndef __WINE__ +/* Up-to-date Windows headers warn that the unprefixed versions of + * these names are deprecated. */ +#define stricmp _stricmp +#define strnicmp _strnicmp +#else +/* Compiling with winegcc, _neither_ version of these functions + * exists. Use the POSIX names. */ +#define stricmp strcasecmp +#define strnicmp strncasecmp +#endif + +#define BROKEN_PIPE_ERROR_CODE ERROR_BROKEN_PIPE /* used in sshshare.c */ + +/* + * 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 +/* If you DECL_WINDOWS_FUNCTION as extern in a header file, use this to + * define the function pointer in a source file */ +#define DEF_WINDOWS_FUNCTION(name) t_##name p_##name +#define STR1(x) #x +#define STR(x) STR1(x) +#define GET_WINDOWS_FUNCTION_PP(module, name) \ + TYPECHECK((t_##name)NULL == name, \ + (p_##name = module ? \ + (t_##name) GetProcAddress(module, STR(name)) : NULL)) +#define GET_WINDOWS_FUNCTION(module, name) \ + TYPECHECK((t_##name)NULL == name, \ + (p_##name = module ? \ + (t_##name) GetProcAddress(module, #name) : NULL)) +#define GET_WINDOWS_FUNCTION_NO_TYPECHECK(module, name) \ + (p_##name = module ? \ + (t_##name) GetProcAddress(module, #name) : NULL) + +#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_CHM_FILE "putty.chm" + +#define GETTICKCOUNT GetTickCount +#define CURSORBLINK GetCaretBlinkTime() +#define TICKSPERSEC 1000 /* GetTickCount returns milliseconds */ + +#define DEFAULT_CODEPAGE CP_ACP +#define USES_VTLINE_HACK + +#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 + +/* + * The all-important instance handle, saved from WinMain in every GUI + * program and exported for other GUI code to pass back to the Windows + * API. + */ +extern HINSTANCE hinst; + +/* + * Help file stuff in winhelp.c. + */ +void init_help(void); +void shutdown_help(void); +bool has_help(void); +void launch_help(HWND hwnd, const char *topic); +void quit_help(HWND hwnd); +int has_embedded_chm(void); /* 1 = yes, 0 = no, -1 = N/A */ + +/* + * GUI seat methods in windlg.c, so that the vtable definition in + * window.c can refer to them. + */ +int win_seat_verify_ssh_host_key( + Seat *seat, const char *host, int port, const char *keytype, + char *keystr, const char *keydisp, char **key_fingerprints, + void (*callback)(void *ctx, int result), void *ctx); +int win_seat_confirm_weak_crypto_primitive( + Seat *seat, const char *algtype, const char *algname, + void (*callback)(void *ctx, int result), void *ctx); +int win_seat_confirm_weak_cached_hostkey( + Seat *seat, const char *algname, const char *betteralgs, + void (*callback)(void *ctx, int result), void *ctx); + +/* + * Windows-specific clipboard helper function shared with windlg.c, + * which takes the data string in the system code page instead of + * Unicode. + */ +void write_aclip(int clipboard, char *, int, bool); + +#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") + +/* + * Exports from winnet.c. + */ +/* Report an event notification from WSA*Select */ +void select_result(WPARAM, LPARAM); +/* Enumerate all currently live OS-level SOCKETs */ +SOCKET first_socket(int *); +SOCKET next_socket(int *); +/* Ask winnet.c whether we currently want to try to write to a SOCKET */ +bool socket_writable(SOCKET skt); +/* Force a refresh of the SOCKET list by re-calling do_select for each one */ +void socket_reselect_all(void); +/* Make a SockAddr which just holds a named pipe address. */ +SockAddr *sk_namedpipe_addr(const char *pipename); +/* Turn a WinSock error code into a string. */ +const char *winsock_error_string(int error); + +/* + * 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(extern, int, WSAAsyncSelect, + (SOCKET, HWND, u_int, long)); +DECL_WINDOWS_FUNCTION(extern, int, WSAEventSelect, + (SOCKET, WSAEVENT, long)); +DECL_WINDOWS_FUNCTION(extern, int, WSAGetLastError, (void)); +DECL_WINDOWS_FUNCTION(extern, int, WSAEnumNetworkEvents, + (SOCKET, WSAEVENT, LPWSANETWORKEVENTS)); +#ifdef NEED_DECLARATION_OF_SELECT +/* This declaration is protected by an ifdef for the sake of building + * against winelib, in which you have to include winsock2.h before + * stdlib.h so that the right fd_set type gets defined. It would be a + * pain to do that throughout this codebase, so instead I arrange that + * only a modules actually needing to use (or define, or initialise) + * this function pointer will see its declaration, and _those_ modules + * - which will be Windows-specific anyway - can take more care. */ +DECL_WINDOWS_FUNCTION(extern, int, select, + (int, fd_set FAR *, fd_set FAR *, + fd_set FAR *, const struct timeval FAR *)); +#endif + +/* + * Implemented differently depending on the client of winnet.c, and + * called by winnet.c to turn on or off WSA*Select for a given socket. + */ +const char *do_select(SOCKET skt, bool enable); + +/* + * Exports from winselgui.c and winselcli.c, each of which provides an + * implementation of do_select. + */ +void winselgui_set_hwnd(HWND hwnd); +void winselgui_clear_hwnd(void); + +void winselcli_setup(void); +SOCKET winselcli_unique_socket(void); +extern HANDLE winselcli_event; + +/* + * Network-subsystem-related functions provided in other Windows modules. + */ +Socket *make_handle_socket(HANDLE send_H, HANDLE recv_H, HANDLE stderr_H, + Plug *plug, bool overlapped); /* winhsock */ +Socket *new_named_pipe_client(const char *pipename, Plug *plug); /* winnpc */ +Socket *new_named_pipe_listener(const char *pipename, Plug *plug); /* winnps */ + +/* A lower-level function in winnpc.c, which does most of the work of + * new_named_pipe_client (including checking the ownership of what + * it's connected to), but returns a plain HANDLE instead of wrapping + * it into a Socket. */ +HANDLE connect_to_named_pipe(const char *pipename, char **err); + +/* + * Exports from winctrls.c. + */ + +struct ctlpos { + HWND hwnd; + WPARAM font; + int dlu4inpix; + int ypos, width; + int xoff; + int boxystart, boxid; + char *boxtext; +}; +void init_common_controls(void); /* also does some DLL-loading */ + +/* + * Exports from winutils.c. + */ +typedef struct filereq_tag filereq; /* cwd for file requester */ +bool request_file(filereq *state, OPENFILENAME *of, bool preserve, bool save); +filereq *filereq_new(void); +void filereq_free(filereq *state); +void pgp_fingerprints_msgbox(HWND owner); +int message_box(HWND owner, LPCTSTR text, LPCTSTR caption, + DWORD style, DWORD helpctxid); +void MakeDlgItemBorderless(HWND parent, int id); +char *GetDlgItemText_alloc(HWND hwnd, int id); +void split_into_argv(char *, int *, char ***, char ***); + +/* + * Private structure for prefslist state. Only in the header file + * so that we can delegate allocation to callers. + */ +struct prefslist { + int listid, upbid, dnbid; + int srcitem; + int dummyitem; + bool 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 */ + bool shortcuts[128]; /* track which shortcuts in use */ + bool coloursel_wanted; /* has an event handler asked for + * a colour selector? */ + struct { + unsigned char r, g, b; /* 0-255 */ + bool ok; + } coloursel_result; + tree234 *privdata; /* stores per-control private data */ + bool ended; /* has the dialog been ended? */ + int endresult; /* and if so, what was the result? */ + bool 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, bool 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, + bool 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(dlgparam *dlg); +bool dlg_get_fixed_pitch_flag(dlgparam *dlg); +void dlg_set_fixed_pitch_flag(dlgparam *dlg, bool 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; + /* + * For vertical alignment, the id of a particular representative + * control that has the y-extent of the sensible part of the + * control. + */ + int align_id; + /* + * 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); +bool winctrl_handle_command(struct dlgparam *dp, UINT msg, + WPARAM wParam, LPARAM lParam); +void winctrl_rem_shortcuts(struct dlgparam *dp, struct winctrl *c); +bool 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, bool has_help, + bool midsession, int protocol); + +/* + * Exports from windlg.c. + */ +void defuse_showwindow(void); +bool do_config(Conf *); +bool do_reconfig(HWND, Conf *, int); +void showeventlog(HWND); +void showabout(HWND); +void force_normal(HWND hwnd); +void modal_about_box(HWND hwnd); +void show_help(HWND hwnd); +HWND event_log_window(void); + +/* + * Exports from winmisc.c. + */ +extern DWORD osMajorVersion, osMinorVersion, osPlatformId; +void init_winver(void); +void dll_hijacking_protection(void); +HMODULE load_system32_dll(const char *libname); +const char *win_strerror(int error); +void restrict_process_acl(void); +bool restricted_acl(void); +void escape_registry_key(const char *in, strbuf *out); +void unescape_registry_key(const char *in, strbuf *out); + +bool is_console_handle(HANDLE); + +/* A few pieces of up-to-date Windows API definition needed for older + * compilers. */ +#ifndef LOAD_LIBRARY_SEARCH_SYSTEM32 +#define LOAD_LIBRARY_SEARCH_SYSTEM32 0x00000800 +#endif +#ifndef LOAD_LIBRARY_SEARCH_USER_DIRS +#define LOAD_LIBRARY_SEARCH_USER_DIRS 0x00000400 +#endif +#ifndef LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR +#define LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR 0x00000100 +#endif +#ifndef DLL_DIRECTORY_COOKIE +typedef PVOID DLL_DIRECTORY_COOKIE; +DECLSPEC_IMPORT DLL_DIRECTORY_COOKIE WINAPI AddDllDirectory (PCWSTR NewDirectory); +#endif + +/* + * Exports from sizetip.c. + */ +void UpdateSizeTip(HWND src, int cx, int cy); +void EnableSizeTip(bool bEnable); + +/* + * Exports from unicode.c. + */ +struct unicode_data; +void init_ucs(Conf *, 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 size_t (*handle_inputfn_t)( + struct handle *h, const void *data, size_t len, int err); +typedef void (*handle_outputfn_t)( + struct handle *h, size_t new_backlog, int err); +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); +size_t handle_write(struct handle *h, const void *data, size_t len); +void handle_write_eof(struct handle *h); +HANDLE *handle_get_events(int *nevents); +void handle_free(struct handle *h); +void handle_got_event(HANDLE event); +void handle_unthrottle(struct handle *h, size_t backlog); +size_t handle_backlog(struct handle *h); +void *handle_get_privdata(struct handle *h); +struct handle *handle_add_foreign_event(HANDLE event, + void (*callback)(void *), void *ctx); +/* Analogue of stdio_sink in marshal.h, for a Windows handle */ +struct handle_sink { + struct handle *h; + BinarySink_IMPLEMENTATION; +}; +void handle_sink_init(handle_sink *sink, struct handle *h); + +/* + * Exports from winpgntc.c. + */ +char *agent_named_pipe_name(void); + +/* + * Exports from winser.c. + */ +extern const struct BackendVtable 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); +bool set_explicit_app_user_model_id(void); + +/* + * Exports from winnoise.c. + */ +bool win_read_random(void *buf, unsigned wanted); /* returns true on success */ + +/* + * 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); + +/* + * Windows clipboard-UI wording. + */ +#define CLIPNAME_IMPLICIT "Last selected text" +#define CLIPNAME_EXPLICIT "System clipboard" +#define CLIPNAME_EXPLICIT_OBJECT "system clipboard" +/* These defaults are the ones PuTTY has historically had */ +#define CLIPUI_DEFAULT_AUTOCOPY true +#define CLIPUI_DEFAULT_MOUSE CLIPUI_EXPLICIT +#define CLIPUI_DEFAULT_INS CLIPUI_EXPLICIT + +/* In winmisc.c */ +char *registry_get_string(HKEY root, const char *path, const char *leaf); + +/* In wincliloop.c */ +typedef bool (*cliloop_pre_t)(void *vctx, const HANDLE **extra_handles, + size_t *n_extra_handles); +typedef bool (*cliloop_post_t)(void *vctx, size_t extra_handle_index); +void cli_main_loop(cliloop_pre_t pre, cliloop_post_t post, void *ctx); +bool cliloop_null_pre(void *vctx, const HANDLE **, size_t *); +bool cliloop_null_post(void *vctx, size_t); + +#endif /* PUTTY_WINDOWS_PLATFORM_H */ diff --git a/windows/winstuff.h b/windows/winstuff.h deleted file mode 100644 index 5f2557dd..00000000 --- a/windows/winstuff.h +++ /dev/null @@ -1,711 +0,0 @@ -/* - * winstuff.h: Windows-specific inter-module stuff. - */ - -#ifndef PUTTY_WINSTUFF_H -#define PUTTY_WINSTUFF_H - -#if HAVE_CMAKE_H -#include "cmake.h" -#endif - -#include -#include -#include /* for FILENAME_MAX */ - -/* We use uintptr_t for Win32/Win64 portability, so we should in - * principle include stdint.h, which defines it according to the C - * standard. But older versions of Visual Studio don't provide - * stdint.h at all, but do (non-standardly) define uintptr_t in - * stddef.h. So here we try to make sure _some_ standard header is - * included which defines uintptr_t. */ -#include -#if !HAVE_NO_STDINT_H -#include -#endif - -#include "defs.h" -#include "marshal.h" - -#include "tree234.h" - -#include "winhelp.h" - -#if defined _M_IX86 || defined _M_AMD64 -#define BUILDINFO_PLATFORM "x86 Windows" -#elif defined _M_ARM || defined _M_ARM64 -#define BUILDINFO_PLATFORM "Arm Windows" -#else -#define BUILDINFO_PLATFORM "Windows" -#endif - -struct Filename { - char *path; -}; -static inline FILE *f_open(const Filename *filename, const char *mode, - bool isprivate) -{ - return fopen(filename->path, mode); -} - -struct FontSpec { - char *name; - bool isbold; - int height; - int charset; -}; -struct FontSpec *fontspec_new( - const char *name, bool bold, 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 */ - -#define PLATFORM_CLIPBOARDS(X) \ - X(CLIP_SYSTEM, "system clipboard") \ - /* end of list */ - -/* - * 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 - -#if !HAVE_STRTOUMAX -/* Work around lack of strtoumax in older MSVC libraries */ -static inline uintmax_t strtoumax(const char *nptr, char **endptr, int base) -{ return _strtoui64(nptr, endptr, base); } -#endif - -#define BOXFLAGS DLGWINDOWEXTRA -#define BOXRESULT (DLGWINDOWEXTRA + sizeof(LONG_PTR)) -#define DF_END 0x0001 - -#ifndef __WINE__ -/* Up-to-date Windows headers warn that the unprefixed versions of - * these names are deprecated. */ -#define stricmp _stricmp -#define strnicmp _strnicmp -#else -/* Compiling with winegcc, _neither_ version of these functions - * exists. Use the POSIX names. */ -#define stricmp strcasecmp -#define strnicmp strncasecmp -#endif - -#define BROKEN_PIPE_ERROR_CODE ERROR_BROKEN_PIPE /* used in sshshare.c */ - -/* - * 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 -/* If you DECL_WINDOWS_FUNCTION as extern in a header file, use this to - * define the function pointer in a source file */ -#define DEF_WINDOWS_FUNCTION(name) t_##name p_##name -#define STR1(x) #x -#define STR(x) STR1(x) -#define GET_WINDOWS_FUNCTION_PP(module, name) \ - TYPECHECK((t_##name)NULL == name, \ - (p_##name = module ? \ - (t_##name) GetProcAddress(module, STR(name)) : NULL)) -#define GET_WINDOWS_FUNCTION(module, name) \ - TYPECHECK((t_##name)NULL == name, \ - (p_##name = module ? \ - (t_##name) GetProcAddress(module, #name) : NULL)) -#define GET_WINDOWS_FUNCTION_NO_TYPECHECK(module, name) \ - (p_##name = module ? \ - (t_##name) GetProcAddress(module, #name) : NULL) - -#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_CHM_FILE "putty.chm" - -#define GETTICKCOUNT GetTickCount -#define CURSORBLINK GetCaretBlinkTime() -#define TICKSPERSEC 1000 /* GetTickCount returns milliseconds */ - -#define DEFAULT_CODEPAGE CP_ACP -#define USES_VTLINE_HACK - -#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 - -/* - * The all-important instance handle, saved from WinMain in every GUI - * program and exported for other GUI code to pass back to the Windows - * API. - */ -extern HINSTANCE hinst; - -/* - * Help file stuff in winhelp.c. - */ -void init_help(void); -void shutdown_help(void); -bool has_help(void); -void launch_help(HWND hwnd, const char *topic); -void quit_help(HWND hwnd); -int has_embedded_chm(void); /* 1 = yes, 0 = no, -1 = N/A */ - -/* - * GUI seat methods in windlg.c, so that the vtable definition in - * window.c can refer to them. - */ -int win_seat_verify_ssh_host_key( - Seat *seat, const char *host, int port, const char *keytype, - char *keystr, const char *keydisp, char **key_fingerprints, - void (*callback)(void *ctx, int result), void *ctx); -int win_seat_confirm_weak_crypto_primitive( - Seat *seat, const char *algtype, const char *algname, - void (*callback)(void *ctx, int result), void *ctx); -int win_seat_confirm_weak_cached_hostkey( - Seat *seat, const char *algname, const char *betteralgs, - void (*callback)(void *ctx, int result), void *ctx); - -/* - * Windows-specific clipboard helper function shared with windlg.c, - * which takes the data string in the system code page instead of - * Unicode. - */ -void write_aclip(int clipboard, char *, int, bool); - -#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") - -/* - * Exports from winnet.c. - */ -/* Report an event notification from WSA*Select */ -void select_result(WPARAM, LPARAM); -/* Enumerate all currently live OS-level SOCKETs */ -SOCKET first_socket(int *); -SOCKET next_socket(int *); -/* Ask winnet.c whether we currently want to try to write to a SOCKET */ -bool socket_writable(SOCKET skt); -/* Force a refresh of the SOCKET list by re-calling do_select for each one */ -void socket_reselect_all(void); -/* Make a SockAddr which just holds a named pipe address. */ -SockAddr *sk_namedpipe_addr(const char *pipename); -/* Turn a WinSock error code into a string. */ -const char *winsock_error_string(int error); - -/* - * 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(extern, int, WSAAsyncSelect, - (SOCKET, HWND, u_int, long)); -DECL_WINDOWS_FUNCTION(extern, int, WSAEventSelect, - (SOCKET, WSAEVENT, long)); -DECL_WINDOWS_FUNCTION(extern, int, WSAGetLastError, (void)); -DECL_WINDOWS_FUNCTION(extern, int, WSAEnumNetworkEvents, - (SOCKET, WSAEVENT, LPWSANETWORKEVENTS)); -#ifdef NEED_DECLARATION_OF_SELECT -/* This declaration is protected by an ifdef for the sake of building - * against winelib, in which you have to include winsock2.h before - * stdlib.h so that the right fd_set type gets defined. It would be a - * pain to do that throughout this codebase, so instead I arrange that - * only a modules actually needing to use (or define, or initialise) - * this function pointer will see its declaration, and _those_ modules - * - which will be Windows-specific anyway - can take more care. */ -DECL_WINDOWS_FUNCTION(extern, int, select, - (int, fd_set FAR *, fd_set FAR *, - fd_set FAR *, const struct timeval FAR *)); -#endif - -/* - * Implemented differently depending on the client of winnet.c, and - * called by winnet.c to turn on or off WSA*Select for a given socket. - */ -const char *do_select(SOCKET skt, bool enable); - -/* - * Exports from winselgui.c and winselcli.c, each of which provides an - * implementation of do_select. - */ -void winselgui_set_hwnd(HWND hwnd); -void winselgui_clear_hwnd(void); - -void winselcli_setup(void); -SOCKET winselcli_unique_socket(void); -extern HANDLE winselcli_event; - -/* - * Network-subsystem-related functions provided in other Windows modules. - */ -Socket *make_handle_socket(HANDLE send_H, HANDLE recv_H, HANDLE stderr_H, - Plug *plug, bool overlapped); /* winhsock */ -Socket *new_named_pipe_client(const char *pipename, Plug *plug); /* winnpc */ -Socket *new_named_pipe_listener(const char *pipename, Plug *plug); /* winnps */ - -/* A lower-level function in winnpc.c, which does most of the work of - * new_named_pipe_client (including checking the ownership of what - * it's connected to), but returns a plain HANDLE instead of wrapping - * it into a Socket. */ -HANDLE connect_to_named_pipe(const char *pipename, char **err); - -/* - * Exports from winctrls.c. - */ - -struct ctlpos { - HWND hwnd; - WPARAM font; - int dlu4inpix; - int ypos, width; - int xoff; - int boxystart, boxid; - char *boxtext; -}; -void init_common_controls(void); /* also does some DLL-loading */ - -/* - * Exports from winutils.c. - */ -typedef struct filereq_tag filereq; /* cwd for file requester */ -bool request_file(filereq *state, OPENFILENAME *of, bool preserve, bool save); -filereq *filereq_new(void); -void filereq_free(filereq *state); -void pgp_fingerprints_msgbox(HWND owner); -int message_box(HWND owner, LPCTSTR text, LPCTSTR caption, - DWORD style, DWORD helpctxid); -void MakeDlgItemBorderless(HWND parent, int id); -char *GetDlgItemText_alloc(HWND hwnd, int id); -void split_into_argv(char *, int *, char ***, char ***); - -/* - * Private structure for prefslist state. Only in the header file - * so that we can delegate allocation to callers. - */ -struct prefslist { - int listid, upbid, dnbid; - int srcitem; - int dummyitem; - bool 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 */ - bool shortcuts[128]; /* track which shortcuts in use */ - bool coloursel_wanted; /* has an event handler asked for - * a colour selector? */ - struct { - unsigned char r, g, b; /* 0-255 */ - bool ok; - } coloursel_result; - tree234 *privdata; /* stores per-control private data */ - bool ended; /* has the dialog been ended? */ - int endresult; /* and if so, what was the result? */ - bool 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, bool 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, - bool 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(dlgparam *dlg); -bool dlg_get_fixed_pitch_flag(dlgparam *dlg); -void dlg_set_fixed_pitch_flag(dlgparam *dlg, bool 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; - /* - * For vertical alignment, the id of a particular representative - * control that has the y-extent of the sensible part of the - * control. - */ - int align_id; - /* - * 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); -bool winctrl_handle_command(struct dlgparam *dp, UINT msg, - WPARAM wParam, LPARAM lParam); -void winctrl_rem_shortcuts(struct dlgparam *dp, struct winctrl *c); -bool 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, bool has_help, - bool midsession, int protocol); - -/* - * Exports from windlg.c. - */ -void defuse_showwindow(void); -bool do_config(Conf *); -bool do_reconfig(HWND, Conf *, int); -void showeventlog(HWND); -void showabout(HWND); -void force_normal(HWND hwnd); -void modal_about_box(HWND hwnd); -void show_help(HWND hwnd); -HWND event_log_window(void); - -/* - * Exports from winmisc.c. - */ -extern DWORD osMajorVersion, osMinorVersion, osPlatformId; -void init_winver(void); -void dll_hijacking_protection(void); -HMODULE load_system32_dll(const char *libname); -const char *win_strerror(int error); -void restrict_process_acl(void); -bool restricted_acl(void); -void escape_registry_key(const char *in, strbuf *out); -void unescape_registry_key(const char *in, strbuf *out); - -bool is_console_handle(HANDLE); - -/* A few pieces of up-to-date Windows API definition needed for older - * compilers. */ -#ifndef LOAD_LIBRARY_SEARCH_SYSTEM32 -#define LOAD_LIBRARY_SEARCH_SYSTEM32 0x00000800 -#endif -#ifndef LOAD_LIBRARY_SEARCH_USER_DIRS -#define LOAD_LIBRARY_SEARCH_USER_DIRS 0x00000400 -#endif -#ifndef LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR -#define LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR 0x00000100 -#endif -#ifndef DLL_DIRECTORY_COOKIE -typedef PVOID DLL_DIRECTORY_COOKIE; -DECLSPEC_IMPORT DLL_DIRECTORY_COOKIE WINAPI AddDllDirectory (PCWSTR NewDirectory); -#endif - -/* - * Exports from sizetip.c. - */ -void UpdateSizeTip(HWND src, int cx, int cy); -void EnableSizeTip(bool bEnable); - -/* - * Exports from unicode.c. - */ -struct unicode_data; -void init_ucs(Conf *, 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 size_t (*handle_inputfn_t)( - struct handle *h, const void *data, size_t len, int err); -typedef void (*handle_outputfn_t)( - struct handle *h, size_t new_backlog, int err); -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); -size_t handle_write(struct handle *h, const void *data, size_t len); -void handle_write_eof(struct handle *h); -HANDLE *handle_get_events(int *nevents); -void handle_free(struct handle *h); -void handle_got_event(HANDLE event); -void handle_unthrottle(struct handle *h, size_t backlog); -size_t handle_backlog(struct handle *h); -void *handle_get_privdata(struct handle *h); -struct handle *handle_add_foreign_event(HANDLE event, - void (*callback)(void *), void *ctx); -/* Analogue of stdio_sink in marshal.h, for a Windows handle */ -struct handle_sink { - struct handle *h; - BinarySink_IMPLEMENTATION; -}; -void handle_sink_init(handle_sink *sink, struct handle *h); - -/* - * Exports from winpgntc.c. - */ -char *agent_named_pipe_name(void); - -/* - * Exports from winser.c. - */ -extern const struct BackendVtable 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); -bool set_explicit_app_user_model_id(void); - -/* - * Exports from winnoise.c. - */ -bool win_read_random(void *buf, unsigned wanted); /* returns true on success */ - -/* - * 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); - -/* - * Windows clipboard-UI wording. - */ -#define CLIPNAME_IMPLICIT "Last selected text" -#define CLIPNAME_EXPLICIT "System clipboard" -#define CLIPNAME_EXPLICIT_OBJECT "system clipboard" -/* These defaults are the ones PuTTY has historically had */ -#define CLIPUI_DEFAULT_AUTOCOPY true -#define CLIPUI_DEFAULT_MOUSE CLIPUI_EXPLICIT -#define CLIPUI_DEFAULT_INS CLIPUI_EXPLICIT - -/* In winmisc.c */ -char *registry_get_string(HKEY root, const char *path, const char *leaf); - -/* In wincliloop.c */ -typedef bool (*cliloop_pre_t)(void *vctx, const HANDLE **extra_handles, - size_t *n_extra_handles); -typedef bool (*cliloop_post_t)(void *vctx, size_t extra_handle_index); -void cli_main_loop(cliloop_pre_t pre, cliloop_post_t post, void *ctx); -bool cliloop_null_pre(void *vctx, const HANDLE **, size_t *); -bool cliloop_null_post(void *vctx, size_t); - -#endif -- cgit v1.2.3 From 3d55a2befb10e85bbd9bf6d1b67467400efa2a35 Mon Sep 17 00:00:00 2001 From: "Pavel I. Kryukov" Date: Sat, 17 Apr 2021 22:44:50 +0300 Subject: Add coverage flags --- cmake/setup.cmake | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cmake/setup.cmake b/cmake/setup.cmake index d55248d9..cc719cac 100644 --- a/cmake/setup.cmake +++ b/cmake/setup.cmake @@ -11,6 +11,8 @@ set(PUTTY_DEBUG OFF CACHE BOOL "Build PuTTY with debug() statements enabled") set(PUTTY_FUZZING OFF CACHE BOOL "Build PuTTY binaries suitable for fuzzing, NOT FOR REAL USE") +set(PUTTY_COVERAGE OFF + CACHE BOOL "Build PuTTY binaries suitable for code coverage analysis") set(STRICT OFF CACHE BOOL "Enable extra compiler warnings and make them errors") @@ -76,3 +78,6 @@ endif() if(PUTTY_FUZZING) add_compile_definitions(FUZZING) endif() +if(PUTTY_COVERAGE) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fprofile-arcs -ftest-coverage -g ") +endif() -- cgit v1.2.3 From c314f582541cfacec0790eebd3bdf6e130ffc29f Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 18 Apr 2021 08:22:43 +0100 Subject: Conditionalise a couple of CMake checks. On Windows, configure-style checks are a bit slow, so it's worth avoiding unnecessary ones if possible. I was testing for three different header file names that are alternatives to each other, so it makes sense to stop as soon as we find a usable one. --- cmake/platforms/windows.cmake | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/cmake/platforms/windows.cmake b/cmake/platforms/windows.cmake index 0760ec67..c3f2b97f 100644 --- a/cmake/platforms/windows.cmake +++ b/cmake/platforms/windows.cmake @@ -25,8 +25,15 @@ include(CheckCSourceCompiles) set(CMAKE_REQUIRED_DEFINITIONS -D_ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE) check_include_files("windows.h;winresrc.h" HAVE_WINRESRC_H) -check_include_files("windows.h;winres.h" HAVE_WINRES_H) -check_include_files("windows.h;win.h" HAVE_WIN_H) +if(NOT HAVE_WINRESRC_H) + # A couple of fallback names for the header file you can include in + # .rc files. We conditionalise even these checks, to save effort at + # cmake time. + check_include_files("windows.h;winres.h" HAVE_WINRES_H) + if(NOT HAVE_WINRES_H) + check_include_files("windows.h;win.h" HAVE_WIN_H) + endif() +endif() check_include_files("stdint.h" HAVE_STDINT_H) define_negation(HAVE_NO_STDINT_H HAVE_STDINT_H) -- cgit v1.2.3 From 3346b4b83fcd04e30c2a21eb6e456c889c64adce Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 18 Apr 2021 11:08:27 +0100 Subject: Fix one remaining MSVC warning for 32-bit targets. One bit shift inside mp_divmod_into gave me "warning C4334: '<<': result of 32-bit shift implicitly converted to 64 bits". In this case it was actually on purpose: I intentionally did a shift that I expected to always fit in a BignumInt, and then I passed the result to mp_add_integer_into_shifted_by_words() which is general enough that it wants to accept the biggest integer type it can think of. It's easy to squelch the warning by using a temporary variable of the right type. Now I get a warning-clean build on VS2017, for both 64- and 32-bit. --- mpint.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mpint.c b/mpint.c index 39f55063..d77726cc 100644 --- a/mpint.c +++ b/mpint.c @@ -2145,9 +2145,9 @@ void mp_divmod_into(mp_int *n, mp_int *d, mp_int *q_out, mp_int *r_out) * Make the constant 2*R, which we'll need in the iteration. */ mp_int *two_R = mp_make_sized(rw); + BignumInt top_word = (BignumInt)1 << ((log2_R+1) % BIGNUM_INT_BITS); mp_add_integer_into_shifted_by_words( - two_R, two_R, (BignumInt)1 << ((log2_R+1) % BIGNUM_INT_BITS), - (log2_R+1) / BIGNUM_INT_BITS); + two_R, two_R, top_word, (log2_R+1) / BIGNUM_INT_BITS); /* * Scratch space. -- cgit v1.2.3 From 397d75648d663cacfe21f0c070959b2dd2862259 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 18 Apr 2021 12:08:50 +0100 Subject: test_split_into_argv: fix the generation mode. Something weird was happening in the string handling which caused the output to be full of the kind of gibberish you expect to see from unterminated strings. Rather than debug it in detail, I've taken advantage of now having the utils library conveniently available, and simply used a strbuf, which I _know_ works sensibly. --- windows/utils/split_into_argv.c | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/windows/utils/split_into_argv.c b/windows/utils/split_into_argv.c index 1f89f3b4..d1d51ff4 100644 --- a/windows/utils/split_into_argv.c +++ b/windows/utils/split_into_argv.c @@ -369,27 +369,29 @@ int main(int argc, char **argv) } if (!strcmp(argv[1], "-split") && argc > 2) { - char *str = malloc(20 + strlen(argv[0]) + strlen(argv[2])); - char *p, *q; + strbuf *cmdline = strbuf_new(); + char *p; - q = str + sprintf(str, "%s -splat ", argv[0]); + strbuf_catf(cmdline, "%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; - } + size_t args_start = cmdline->len; + for (p = argv[2]; *p; p++) { + char c = (*p == '/' ? '\\' : + *p == '\'' ? '"' : + *p == '_' ? ' ' : + *p); + put_byte(cmdline, c); } - *p = '\0'; + write_c_string_literal(stdout, ptrlen_from_asciz( + cmdline->s + args_start)); printf("\", {"); fflush(stdout); - system(str); + system(cmdline->s); printf("}},\n"); + strbuf_free(cmdline); return 0; } -- cgit v1.2.3 From d028fd17791d78aec40c444d60b50700dff6c1c3 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 18 Apr 2021 12:10:04 +0100 Subject: test_split_into_argv: add a -tabulate mode. I've finally got round to updating this system for the fixed (post-VS7) command-line splitting. That means I need to regenerate the table in the big comment. So here's an automated method of doing it that doesn't require me to read off the output of -generate in an error-prone manual way. --- windows/utils/split_into_argv.c | 77 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/windows/utils/split_into_argv.c b/windows/utils/split_into_argv.c index d1d51ff4..97e85aeb 100644 --- a/windows/utils/split_into_argv.c +++ b/windows/utils/split_into_argv.c @@ -424,6 +424,83 @@ int main(int argc, char **argv) return 0; } + if (!strcmp(argv[1], "-tabulate")) { + char table[] = "\ + * backslashes \n\ + * \n\ + * 0 1 2 3 4 \n\ + * \n\ + * 0 | \n\ + * --------+----------------------------- \n\ + * 1 | \n\ + * q 2 | \n\ + * u 3 | \n\ + * o 4 | \n\ + * t 5 | \n\ + * e 6 | \n\ + * s 7 | \n\ + * 8 | \n\ +"; + char *linestarts[14]; + char *p = table; + for (i = 0; i < lenof(linestarts); i++) { + linestarts[i] = p; + p += strcspn(p, "\n"); + if (*p) p++; + } + + for (i = 0; i < lenof(argv_tests); i++) { + const struct argv_test *test = &argv_tests[i]; + const char *q = test->cmdline; + + /* Skip tests that aren't telling us something about + * the behaviour _inside_ a quoted string */ + if (*q != '"') + continue; + + q++; + + assert(*q == 'a'); + q++; + int backslashes_in = 0, quotes_in = 0; + while (*q == '\\') { + q++; + backslashes_in++; + } + while (*q == '"') { + q++; + quotes_in++; + } + + q = test->argv[0]; + assert(*q == 'a'); + q++; + int backslashes_out = 0, quotes_out = 0; + while (*q == '\\') { + q++; + backslashes_out++; + } + while (*q == '"') { + q++; + quotes_out++; + } + assert(*q == 'b'); + q++; + bool in_quoted_string = (*q == ' '); + + int x = (backslashes_in == 0 ? 15 : 18 + 7 * backslashes_in); + int y = (quotes_in == 0 ? 4 : 5 + quotes_in); + char *buf = dupprintf("%d,%d,%c", + backslashes_out, quotes_out, + in_quoted_string ? 'y' : 'n'); + memcpy(linestarts[y] + x, buf, strlen(buf)); + sfree(buf); + } + + fputs(table, stdout); + return 0; + } + fprintf(stderr, "unrecognised option: \"%s\"\n", argv[1]); return 1; } -- cgit v1.2.3 From 49b91bc128ab80349159c2595d29479b5d8aee82 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 18 Apr 2021 12:10:53 +0100 Subject: test_split_into_argv: update to post-VS7 behaviour. The old behaviour is still present under an ifdef based on _MSC_VER, so it should still appear in the w32old builds we're still making. --- windows/utils/split_into_argv.c | 343 ++++++++++++++++++++++++++++------------ 1 file changed, 240 insertions(+), 103 deletions(-) diff --git a/windows/utils/split_into_argv.c b/windows/utils/split_into_argv.c index 97e85aeb..c09f20da 100644 --- a/windows/utils/split_into_argv.c +++ b/windows/utils/split_into_argv.c @@ -19,6 +19,143 @@ #include "putty.h" +/* + * The precise argument-breaking rules vary with compiler version, or + * rather, with the crt0-type startup code that comes with each + * compiler's C library. We do our best to match the compiler version, + * so that we faithfully imitate in our GUI utilities what the + * corresponding set of CLI utilities can't be prevented from doing. + * + * The basic rules are: + * + * - 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. + * + * I investigated this in an ordinary CLI program, using the + * toolchain's crt0 to split a command line of the form + * + * "a\\\"""b c" d + * + * 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,y | 0,1,n 1,1,y 1,1,n 2,1,y + * u 3 0,1,n | 0,2,y 1,1,n 1,2,y 2,1,n + * o 4 0,2,y | 0,2,n 1,2,y 1,2,n 2,2,y + * t 5 0,2,n | 0,3,y 1,2,n 1,3,y 2,2,n + * e 6 0,3,y | 0,3,n 1,3,y 1,3,n 2,3,y + * s 7 0,3,n | 0,4,y 1,3,n 1,4,y 2,3,n + * 8 0,4,y | 0,4,n 1,4,y 1,4,n 2,4,y + * + * The row at the top of this table, with quotes=0, demonstrates what + * I claimed above, that when a sequence of backslashes are not + * followed by a double quote, they don't act specially at all. The + * rest of the table shows that the backslashes escape each other in + * pairs (so that with 2n or 2n+1 input backslashes you get n output + * ones); if there's an odd number of input backslashes then the last + * one escapes the first double quote (so you get a literal quote and + * enter a quoted string); thereafter, each input quote character + * either opens or closes a quoted string, and if it closes one, it + * generates a literal " as a side effect. + * + * But here's the corresponding table from the older Visual Studio 7: + * + * 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 + * + * 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. + */ + +/* + * We select between two behaviours depending on the version of Visual + * Studio (see large comment below). I don't know exactly when the bug + * fix happened, but I know that VS7 had the odd mod-3 behaviour. + */ +#if _MSC_VER < 1400 +#define MOD3 1 +#else +#define MOD3 0 +#endif + void split_into_argv(char *cmdline, int *argc, char ***argv, char ***argstart) { @@ -27,107 +164,6 @@ void split_into_argv(char *cmdline, int *argc, char ***argv, char **outputargv, **outputargstart; int outputargc; - /* - * These argument-breaking rules apply to Visual Studio 7, which - * is currently the compiler expected to be used for PuTTY. Visual - * Studio 10 has different rules, lacking the curious mod 3 - * behaviour of consecutive quotes described below; I presume they - * fixed a bug. As and when we migrate to a newer compiler, we'll - * have to adjust this to match; however, for the moment we - * faithfully imitate in our GUI utilities what our CLI utilities - * can't be prevented from doing. - * - * When I investigated this, at first glance the rules appeared to - * be: - * - * - Single quotes are not special characters. - * - * - Double quotes are removed, but within them spaces cease - * to be special. - * - * - 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. @@ -197,11 +233,17 @@ void split_into_argv(char *cmdline, int *argc, char ***argv, /* Outside a quote segment, a quote starts one. */ if (!quote) quotes--; - /* Now we produce (n+1)/3 literal quotes... */ +#if !MOD3 + /* New behaviour: produce n/2 literal quotes... */ + for (i = 2; i <= quotes; i += 2) *q++ = '"'; + /* ... and end in a quote segment iff 2 divides n. */ + quote = (quotes % 2 == 0); +#else + /* Old behaviour: 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); +#endif } } } else { @@ -231,6 +273,100 @@ const struct argv_test { * We generate this set of tests by invoking ourself with * `-generate'. */ +#if !MOD3 + /* Newer behaviour, with no weird mod-3 glitch. */ + {"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}}, +#else /* MOD3 */ + /* VS7 mod-3 behaviour. */ {"ab c\" d", {"ab", "c d", NULL}}, {"a\"b c\" d", {"ab c", "d", NULL}}, {"a\"\"b c\" d", {"ab", "c d", NULL}}, @@ -321,6 +457,7 @@ const struct argv_test { {"\"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}}, +#endif /* MOD3 */ }; void out_of_memory(void) -- cgit v1.2.3 From d01f682f32fb753df0652bb950fecaa47ee1b618 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 18 Apr 2021 12:13:39 +0100 Subject: test_split_into_argv: report test results sensibly. Now we say how many tests failed, and we also propagate the overall status into the exit code. --- windows/utils/split_into_argv.c | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/windows/utils/split_into_argv.c b/windows/utils/split_into_argv.c index c09f20da..b8ced4c4 100644 --- a/windows/utils/split_into_argv.c +++ b/windows/utils/split_into_argv.c @@ -646,10 +646,12 @@ int main(int argc, char **argv) * If we get here, we were invoked with no arguments, so just * run the tests. */ + int passes = 0, fails = 0; for (i = 0; i < lenof(argv_tests); i++) { int ac; char **av; + bool failed = false; split_into_argv((char *)argv_tests[i].cmdline, &ac, &av, NULL); @@ -658,6 +660,7 @@ int main(int argc, char **argv) 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]); + failed = true; } #ifdef VERBOSE else { @@ -667,15 +670,33 @@ int main(int argc, char **argv) } #endif } - if (j < ac) + 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]) + failed = true; + } + if (argv_tests[i].argv[j]) { printf("failed test %d (|%s|): %d args returned, should be more\n", i, argv_tests[i].cmdline, ac); + failed = true; + } + + if (failed) + fails++; + else + passes++; } - return 0; + printf("passed %d failed %d (%s mode)\n", + passes, fails, +#if MOD3 + "mod 3" +#else + "mod 2" +#endif + ); + + return fails != 0; } #endif /* TEST */ -- cgit v1.2.3 From fa353e9f4a173573c9bdc365145c5a50c51b3c5b Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 18 Apr 2021 17:01:50 +0100 Subject: Add missing dependencies on generated source files. The utils library shouldn't be built until cmake_commit.c exists, and similarly with the charset library and sbcsdat.c. --- CMakeLists.txt | 1 + charset/CMakeLists.txt | 1 + 2 files changed, 2 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index a41f7bf3..d617e387 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -60,6 +60,7 @@ add_library(utils STATIC utils/x11_make_greeting.c utils/x11_parse_ip.c ${GENERATED_COMMIT_C}) +add_dependencies(utils cmake_commit_c) add_library(logging OBJECT logging.c) diff --git a/charset/CMakeLists.txt b/charset/CMakeLists.txt index 22cc7a14..4ff5bb8a 100644 --- a/charset/CMakeLists.txt +++ b/charset/CMakeLists.txt @@ -27,3 +27,4 @@ add_library(charset STATIC toucs.c utf8.c xenc.c) +add_dependencies(charset generated_sbcsdat_c) -- cgit v1.2.3 From f6142ba29b9398d03ad56b5e2db03c5ec9fcba44 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 18 Apr 2021 17:03:01 +0100 Subject: Disavow putty.org in the FAQ. A user asked recently whether it was our website, and that seemed like a fair question. It isn't, and we should make that as clear as we can. --- doc/faq.but | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/doc/faq.but b/doc/faq.but index fcadaa0e..eaecbf58 100644 --- a/doc/faq.but +++ b/doc/faq.but @@ -1154,6 +1154,16 @@ inactive. And Pageant spends most of its time inactive. \H{faq-admin} Administrative questions +\S{faq-domain}{Question} Is \cw{putty.org} your website? + +No, it isn't. \cw{putty.org} is run by an opportunist who uses it to +advertise their own commercial SSH implementation to people looking +for our free one. We don't own that site, we can't control it, and we +don't advise anyone to use it in preference to our own site. + +The real PuTTY web site, run by the PuTTY team, has always been at +\W{https://www.chiark.greenend.org.uk/~sgtatham/putty/}\cw{https://www.chiark.greenend.org.uk/~sgtatham/putty/}. + \S{faq-domain}{Question} Would you like me to register you a nicer domain name? -- cgit v1.2.3 From 7c1bea59a3b49853808f96743a7464fb99ee1e2d Mon Sep 17 00:00:00 2001 From: Jacob Nevins Date: Mon, 19 Apr 2021 17:13:25 +0100 Subject: FAQ: fix duplicate keyword. --- doc/faq.but | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/faq.but b/doc/faq.but index eaecbf58..474b984a 100644 --- a/doc/faq.but +++ b/doc/faq.but @@ -1154,7 +1154,7 @@ inactive. And Pageant spends most of its time inactive. \H{faq-admin} Administrative questions -\S{faq-domain}{Question} Is \cw{putty.org} your website? +\S{faq-putty-org}{Question} Is \cw{putty.org} your website? No, it isn't. \cw{putty.org} is run by an opportunist who uses it to advertise their own commercial SSH implementation to people looking -- cgit v1.2.3 From 06c3344eaed62276c85862ff49226527a4eccb1f Mon Sep 17 00:00:00 2001 From: Jacob Nevins Date: Mon, 19 Apr 2021 17:14:01 +0100 Subject: Fix typo in comment. --- utils/smemclr.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/smemclr.c b/utils/smemclr.c index 3cd7a761..5503bdc4 100644 --- a/utils/smemclr.c +++ b/utils/smemclr.c @@ -30,7 +30,7 @@ static void no_op(void *ptr, size_t size) {} * time into a pointer to some completely arbitrary function. And * therefore it must also avoid optimising away any observable effect * beforehand that a completely arbitrary function might depend on - - * such as the zeroing of our memory re3gion. + * such as the zeroing of our memory region. */ static void (*const volatile maybe_read)(void *ptr, size_t size) = no_op; -- cgit v1.2.3 From 70f6ce5628d229a643ef33c89eca25ffd594c8b3 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 18 Apr 2021 13:37:18 +0100 Subject: Rename one of my cmake support functions. (NFC) add_platform_sources_to_library() is now called add_sources_from_current_dir(), so that it will make sense when I use it in subdirectories that aren't for a particular platform. --- cmake/setup.cmake | 2 +- unix/CMakeLists.txt | 26 +++++++++++++------------- windows/CMakeLists.txt | 24 ++++++++++++------------ 3 files changed, 26 insertions(+), 26 deletions(-) diff --git a/cmake/setup.cmake b/cmake/setup.cmake index cc719cac..4bd599a4 100644 --- a/cmake/setup.cmake +++ b/cmake/setup.cmake @@ -55,7 +55,7 @@ add_custom_target(cmake_commit_c DEPENDS check_git_commit ${INTERMEDIATE_COMMIT_C} COMMENT "Updating cmake_commit.c") -function(add_platform_sources_to_library target) +function(add_sources_from_current_dir target) set(sources ${ARGN}) list(TRANSFORM sources PREPEND ${CMAKE_CURRENT_SOURCE_DIR}/) target_sources(${target} PRIVATE ${sources}) diff --git a/unix/CMakeLists.txt b/unix/CMakeLists.txt index ebb77a55..9a79f145 100644 --- a/unix/CMakeLists.txt +++ b/unix/CMakeLists.txt @@ -1,6 +1,6 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) -add_platform_sources_to_library(utils +add_sources_from_current_dir(utils utils/arm_arch_queries.c utils/block_signal.c utils/cloexec.c @@ -23,25 +23,25 @@ add_platform_sources_to_library(utils # We want the ISO C implementation of ltime(), because we don't have # a local better alternative ../utils/ltime.c) -add_platform_sources_to_library(eventloop +add_sources_from_current_dir(eventloop uxcliloop.c uxsel.c) -add_platform_sources_to_library(console +add_sources_from_current_dir(console uxcons.c) -add_platform_sources_to_library(settings +add_sources_from_current_dir(settings uxstore.c) -add_platform_sources_to_library(network +add_sources_from_current_dir(network uxnet.c uxfdsock.c uxagentsock.c uxpeer.c uxproxy.c ux_x11.c) -add_platform_sources_to_library(sshcommon +add_sources_from_current_dir(sshcommon uxnoise.c) -add_platform_sources_to_library(sshclient +add_sources_from_current_dir(sshclient uxgss.c uxagentc.c uxshare.c) -add_platform_sources_to_library(sshserver +add_sources_from_current_dir(sshserver uxsftpserver.c procnet.c) -add_platform_sources_to_library(sftpclient +add_sources_from_current_dir(sftpclient uxsftp.c) -add_platform_sources_to_library(otherbackends +add_sources_from_current_dir(otherbackends uxser.c) -add_platform_sources_to_library(agent +add_sources_from_current_dir(agent uxagentc.c) add_executable(fuzzterm @@ -144,9 +144,9 @@ target_link_libraries(uppity eventloop sshserver keygen settings network crypto utils) if(GTK_FOUND) - add_platform_sources_to_library(utils + add_sources_from_current_dir(utils gtkcols.c) - add_platform_sources_to_library(guiterminal + add_sources_from_current_dir(guiterminal gtkwin.c gtkfont.c gtkdlg.c gtkcfg.c gtkcomm.c uxcfg.c uxucs.c uxprint.c) add_dependencies(guiterminal generated_licence_h) # gtkdlg.c uses licence.h diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt index e312f28f..d063b9d3 100644 --- a/windows/CMakeLists.txt +++ b/windows/CMakeLists.txt @@ -1,6 +1,6 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) -add_platform_sources_to_library(utils +add_sources_from_current_dir(utils utils/arm_arch_queries.c utils/capi.c utils/defaults.c @@ -28,27 +28,27 @@ add_platform_sources_to_library(utils utils/win_strerror.c winucs.c) if(NOT HAVE_STRTOUMAX) - add_platform_sources_to_library(utils utils/strtoumax.c) + add_sources_from_current_dir(utils utils/strtoumax.c) endif() -add_platform_sources_to_library(eventloop +add_sources_from_current_dir(eventloop wincliloop.c winhandl.c) -add_platform_sources_to_library(console +add_sources_from_current_dir(console winselcli.c winnohlp.c wincons.c) -add_platform_sources_to_library(settings +add_sources_from_current_dir(settings winstore.c) -add_platform_sources_to_library(network +add_sources_from_current_dir(network winnet.c winhsock.c winnpc.c winnps.c winproxy.c winx11.c) -add_platform_sources_to_library(sshcommon +add_sources_from_current_dir(sshcommon winnoise.c) -add_platform_sources_to_library(sshclient +add_sources_from_current_dir(sshclient winpgntc.c wingss.c winshare.c) -add_platform_sources_to_library(sftpclient +add_sources_from_current_dir(sftpclient winsftp.c) -add_platform_sources_to_library(otherbackends +add_sources_from_current_dir(otherbackends winser.c) -add_platform_sources_to_library(agent +add_sources_from_current_dir(agent winpgntc.c) -add_platform_sources_to_library(guiterminal +add_sources_from_current_dir(guiterminal windlg.c winctrls.c wincfg.c winprint.c winjump.c sizetip.c) add_dependencies(guiterminal generated_licence_h) # windlg.c uses licence.h -- cgit v1.2.3 From 9fe15509808b71f618e559dc04bce68ad21bdba0 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 19 Apr 2021 06:40:01 +0100 Subject: Make cmake.h available everywhere. The definition of HAVE_CMAKE_H is now at the very top of the main CMakeLists.txt, so that it applies to all objects. And the consequent include of cmake.h is at the very top of defs.h, so that it should be included first by everything. This way, I don't have to worry any more that the HAVE_FOO definitions in cmake.h might accidentally have failed to reach some part of the code. --- CMakeLists.txt | 4 ++-- defs.h | 4 ++++ unix/platform.h | 4 ---- windows/platform.h | 4 ---- 4 files changed, 6 insertions(+), 10 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d617e387..b4c457b4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,6 +3,8 @@ project(putty LANGUAGES C) include(cmake/setup.cmake) +add_compile_definitions(HAVE_CMAKE_H) + add_library(utils STATIC utils/base64_decode_atom.c utils/base64_encode_atom.c @@ -161,8 +163,6 @@ add_executable(test_wildcard target_compile_definitions(test_wildcard PRIVATE TEST) target_link_libraries(test_wildcard utils ${platform_libraries}) -add_compile_definitions(HAVE_CMAKE_H) - foreach(subdir ${PLATFORM_SUBDIRS}) add_subdirectory(${subdir}) endforeach() diff --git a/defs.h b/defs.h index f175d257..b8205c10 100644 --- a/defs.h +++ b/defs.h @@ -22,6 +22,10 @@ #error Do not compile this code base with NDEBUG defined! #endif +#if HAVE_CMAKE_H +#include "cmake.h" +#endif + #include #include #include /* for __MINGW_PRINTF_FORMAT */ diff --git a/unix/platform.h b/unix/platform.h index 8f24b893..62f86a74 100644 --- a/unix/platform.h +++ b/unix/platform.h @@ -5,10 +5,6 @@ #ifndef PUTTY_UNIX_PLATFORM_H #define PUTTY_UNIX_PLATFORM_H -#if HAVE_CMAKE_H -#include "cmake.h" -#endif - #include /* for FILENAME_MAX */ #include /* C99 int types */ #ifndef NO_LIBDL diff --git a/windows/platform.h b/windows/platform.h index 8fadf803..cd89d411 100644 --- a/windows/platform.h +++ b/windows/platform.h @@ -5,10 +5,6 @@ #ifndef PUTTY_WINDOWS_PLATFORM_H #define PUTTY_WINDOWS_PLATFORM_H -#if HAVE_CMAKE_H -#include "cmake.h" -#endif - #include #include #include /* for FILENAME_MAX */ -- cgit v1.2.3 From 68b9f0822f1d629ab8f3e366dc228a4e85518433 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 19 Apr 2021 18:07:28 +0100 Subject: Buildscr: set C flags explicitly. Somewhere in cmake, the settings I gave by hand weren't quite getting through to the Arm builds at least. --- Buildscr | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Buildscr b/Buildscr index 84465ac0..7fa35fba 100644 --- a/Buildscr +++ b/Buildscr @@ -165,15 +165,15 @@ mkdir putty/windows/abuild64 # # For the 32-bit ones, we set a subsystem version of 5.01, which # allows the resulting files to still run on Windows XP. -in putty/windows/build32 with cmake_at_least_3.20 do cmake ../.. -DCMAKE_TOOLCHAIN_FILE=$(cmake_toolchain_clangcl32) -DCMAKE_BUILD_TYPE=Release -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DPUTTY_LINK_MAPS=ON +in putty/windows/build32 with cmake_at_least_3.20 do cmake ../.. -DCMAKE_TOOLCHAIN_FILE=$(cmake_toolchain_clangcl32) -DCMAKE_BUILD_TYPE=Release -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DPUTTY_LINK_MAPS=ON -DCMAKE_C_FLAGS_RELEASE="/MT /O2" in putty/windows/build32 with cmake_at_least_3.20 do make -j$(nproc) VERBOSE=1 -in putty/windows/build64 with cmake_at_least_3.20 do cmake ../.. -DCMAKE_TOOLCHAIN_FILE=$(cmake_toolchain_clangcl64) -DCMAKE_BUILD_TYPE=Release -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DPUTTY_LINK_MAPS=ON +in putty/windows/build64 with cmake_at_least_3.20 do cmake ../.. -DCMAKE_TOOLCHAIN_FILE=$(cmake_toolchain_clangcl64) -DCMAKE_BUILD_TYPE=Release -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DPUTTY_LINK_MAPS=ON -DCMAKE_C_FLAGS_RELEASE="/MT /O2" in putty/windows/build64 with cmake_at_least_3.20 do make -j$(nproc) VERBOSE=1 # Build experimental Arm Windows binaries. -in putty/windows/abuild32 with cmake_at_least_3.20 do cmake ../.. -DCMAKE_TOOLCHAIN_FILE=$(cmake_toolchain_clangcl_a32) -DCMAKE_BUILD_TYPE=Release -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DPUTTY_LINK_MAPS=ON +in putty/windows/abuild32 with cmake_at_least_3.20 do cmake ../.. -DCMAKE_TOOLCHAIN_FILE=$(cmake_toolchain_clangcl_a32) -DCMAKE_BUILD_TYPE=Release -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DPUTTY_LINK_MAPS=ON -DCMAKE_C_FLAGS_RELEASE="/MT /O2" in putty/windows/abuild32 with cmake_at_least_3.20 do make -j$(nproc) VERBOSE=1 -in putty/windows/abuild64 with cmake_at_least_3.20 do cmake ../.. -DCMAKE_TOOLCHAIN_FILE=$(cmake_toolchain_clangcl_a64) -DCMAKE_BUILD_TYPE=Release -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DPUTTY_LINK_MAPS=ON +in putty/windows/abuild64 with cmake_at_least_3.20 do cmake ../.. -DCMAKE_TOOLCHAIN_FILE=$(cmake_toolchain_clangcl_a64) -DCMAKE_BUILD_TYPE=Release -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DPUTTY_LINK_MAPS=ON -DCMAKE_C_FLAGS_RELEASE="/MT /O2" in putty/windows/abuild64 with cmake_at_least_3.20 do make -j$(nproc) VERBOSE=1 # Make a list of the Windows binaries we're going to ship, so that we -- cgit v1.2.3 From e6c0fa6ba4fa13e87cfe1f7099a4929398dceeaf Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 18 Apr 2021 13:38:50 +0100 Subject: Move utils filename list into its own subdir. Now there's a utils/CMakeLists.txt, which contains the huge list of source files in that directory, so that the top-level file does a better job of showing the overview. --- CMakeLists.txt | 56 +--------------------------------------------------- utils/CMakeLists.txt | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 55 deletions(-) create mode 100644 utils/CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index b4c457b4..77c4fe23 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,63 +6,9 @@ include(cmake/setup.cmake) add_compile_definitions(HAVE_CMAKE_H) add_library(utils STATIC - utils/base64_decode_atom.c - utils/base64_encode_atom.c - utils/bufchain.c - utils/buildinfo.c - utils/burnstr.c - utils/chomp.c - utils/conf.c - utils/conf_dest.c - utils/conf_launchable.c - utils/ctrlparse.c - utils/debug.c - utils/dupcat.c - utils/dupprintf.c - utils/dupstr.c - utils/encode_utf8.c - utils/fgetline.c - utils/host_strchr.c - utils/host_strchr_internal.c - utils/host_strcspn.c - utils/host_strduptrim.c - utils/host_strrchr.c - utils/marshal.c - utils/memory.c - utils/memxor.c - utils/miscucs.c - utils/null_lp.c - utils/nullseat.c - utils/nullstrcmp.c - utils/out_of_memory.c - utils/parse_blocksize.c - utils/prompts.c - utils/ptrlen.c - utils/read_file_into.c - utils/seat_connection_fatal.c - utils/sessprep.c - utils/sk_free_peer_info.c - utils/smemclr.c - utils/smemeq.c - utils/ssh2_pick_fingerprint.c - utils/sshutils.c - utils/strbuf.c - utils/string_length_for_printf.c - utils/stripctrl.c - utils/tree234.c - utils/validate_manual_hostkey.c - utils/version.c - utils/wcwidth.c - utils/wildcard.c - utils/write_c_string_literal.c - utils/x11authfile.c - utils/x11authnames.c - utils/x11_dehexify.c - utils/x11_identify_auth_proto.c - utils/x11_make_greeting.c - utils/x11_parse_ip.c ${GENERATED_COMMIT_C}) add_dependencies(utils cmake_commit_c) +add_subdirectory(utils) add_library(logging OBJECT logging.c) diff --git a/utils/CMakeLists.txt b/utils/CMakeLists.txt new file mode 100644 index 00000000..260f9920 --- /dev/null +++ b/utils/CMakeLists.txt @@ -0,0 +1,56 @@ +add_sources_from_current_dir(utils + base64_decode_atom.c + base64_encode_atom.c + bufchain.c + buildinfo.c + burnstr.c + chomp.c + conf.c + conf_dest.c + conf_launchable.c + ctrlparse.c + debug.c + dupcat.c + dupprintf.c + dupstr.c + encode_utf8.c + fgetline.c + host_strchr.c + host_strchr_internal.c + host_strcspn.c + host_strduptrim.c + host_strrchr.c + marshal.c + memory.c + memxor.c + miscucs.c + null_lp.c + nullseat.c + nullstrcmp.c + out_of_memory.c + parse_blocksize.c + prompts.c + ptrlen.c + read_file_into.c + seat_connection_fatal.c + sessprep.c + sk_free_peer_info.c + smemclr.c + smemeq.c + ssh2_pick_fingerprint.c + sshutils.c + strbuf.c + string_length_for_printf.c + stripctrl.c + tree234.c + validate_manual_hostkey.c + version.c + wcwidth.c + wildcard.c + write_c_string_literal.c + x11authfile.c + x11authnames.c + x11_dehexify.c + x11_identify_auth_proto.c + x11_make_greeting.c + x11_parse_ip.c) -- cgit v1.2.3 From e273386272659afaa246499b5ece55f802c63c6e Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 19 Apr 2021 18:10:31 +0100 Subject: Add .gitignore rules for in-tree builds. This set of rules should cover make and ninja on Linux, and all of nmake, ninja and vcxproj on Windows, so that if someone follows the README build instructions (by doing 'cmake .' in-tree), it should generate no debris that .gitignore can't filter out. --- .gitignore | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 68729ab3..c663ae25 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,18 @@ *.o *.pyc +*.vcxproj +*.vcxproj.filters +.ninja_deps +.ninja_log +build.ninja +/CMakeCache.txt +CMakeFiles/ +Debug/ +Win32/ +cmake_install.cmake +/shipped.txt +*.dir/ +*.lib .dirstamp .deps .DS_Store @@ -16,6 +29,7 @@ /*.dsw /*.opt /*.dsp +/*.sln /*.tds /*.td2 /*.map @@ -46,6 +60,9 @@ /testzlib /cgtest /scctest +/test_host_strfoo +/test_tree234 +/test_wildcard /*.DSA /*.RSA /*.cnt @@ -54,9 +71,9 @@ /build.log /build.out /empty.h -/Makefile +Makefile /compile -/*.a +*.a /charset/sbcsdat.c /contrib/cygtermd/cygtermd.exe /doc/*.html -- cgit v1.2.3 From 5b30e6f7a6ff2ec5841b79bdc49671f6c3544f25 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 18 Apr 2021 13:16:59 +0100 Subject: Move crypto into its own subdirectory. Similarly to 'utils', I've moved all the stuff in the crypto build-time library into a source directory of its own, and while I'm at it, split up the monolithic sshauxcrypt.c into its various unrelated parts. This is also an opportunity to remove the annoying 'ssh' prefix from the front of the file names, and give several of them less cryptic names. --- CMakeLists.txt | 9 +- crypto/CMakeLists.txt | 30 + crypto/aes.c | 1912 ++++++++++++++++++++++++++++++++ crypto/arcfour.c | 141 +++ crypto/argon2.c | 565 ++++++++++ crypto/bcrypt.c | 119 ++ crypto/blake2.c | 223 ++++ crypto/blowfish.c | 699 ++++++++++++ crypto/chacha20-poly1305.c | 1062 ++++++++++++++++++ crypto/crc32.c | 113 ++ crypto/des.c | 1048 ++++++++++++++++++ crypto/diffie-hellman.c | 272 +++++ crypto/dsa.c | 503 +++++++++ crypto/ecc-arithmetic.c | 1171 ++++++++++++++++++++ crypto/ecc-ssh.c | 1698 ++++++++++++++++++++++++++++ crypto/hash_simple.c | 13 + crypto/hmac.c | 257 +++++ crypto/mac.c | 43 + crypto/mac_simple.c | 16 + crypto/md5.c | 245 ++++ crypto/mpint.c | 2650 ++++++++++++++++++++++++++++++++++++++++++++ crypto/prng.c | 287 +++++ crypto/pubkey-pem.c | 32 + crypto/pubkey-ppk.c | 29 + crypto/pubkey-ssh1.c | 38 + crypto/rsa.c | 1109 ++++++++++++++++++ crypto/sha1.c | 933 ++++++++++++++++ crypto/sha256.c | 939 ++++++++++++++++ crypto/sha3.c | 329 ++++++ crypto/sha512.c | 836 ++++++++++++++ crypto/xdmauth.c | 53 + ecc.c | 1167 ------------------- mpint.c | 2646 ------------------------------------------- sshaes.c | 1912 -------------------------------- ssharcf.c | 141 --- sshargon2.c | 565 ---------- sshauxcrypt.c | 166 --- sshbcrypt.c | 119 -- sshblake2.c | 223 ---- sshblowf.c | 699 ------------ sshccp.c | 1062 ------------------ sshcrc.c | 108 -- sshdes.c | 1048 ------------------ sshdh.c | 272 ----- sshdss.c | 503 --------- sshecc.c | 1706 ---------------------------- sshhmac.c | 257 ----- sshmac.c | 43 - sshmd5.c | 245 ---- sshprng.c | 287 ----- sshrsa.c | 1109 ------------------ sshsh256.c | 939 ---------------- sshsh512.c | 836 -------------- sshsha.c | 933 ---------------- sshsha3.c | 329 ------ 55 files changed, 17366 insertions(+), 17323 deletions(-) create mode 100644 crypto/CMakeLists.txt create mode 100644 crypto/aes.c create mode 100644 crypto/arcfour.c create mode 100644 crypto/argon2.c create mode 100644 crypto/bcrypt.c create mode 100644 crypto/blake2.c create mode 100644 crypto/blowfish.c create mode 100644 crypto/chacha20-poly1305.c create mode 100644 crypto/crc32.c create mode 100644 crypto/des.c create mode 100644 crypto/diffie-hellman.c create mode 100644 crypto/dsa.c create mode 100644 crypto/ecc-arithmetic.c create mode 100644 crypto/ecc-ssh.c create mode 100644 crypto/hash_simple.c create mode 100644 crypto/hmac.c create mode 100644 crypto/mac.c create mode 100644 crypto/mac_simple.c create mode 100644 crypto/md5.c create mode 100644 crypto/mpint.c create mode 100644 crypto/prng.c create mode 100644 crypto/pubkey-pem.c create mode 100644 crypto/pubkey-ppk.c create mode 100644 crypto/pubkey-ssh1.c create mode 100644 crypto/rsa.c create mode 100644 crypto/sha1.c create mode 100644 crypto/sha256.c create mode 100644 crypto/sha3.c create mode 100644 crypto/sha512.c create mode 100644 crypto/xdmauth.c delete mode 100644 ecc.c delete mode 100644 mpint.c delete mode 100644 sshaes.c delete mode 100644 ssharcf.c delete mode 100644 sshargon2.c delete mode 100644 sshauxcrypt.c delete mode 100644 sshbcrypt.c delete mode 100644 sshblake2.c delete mode 100644 sshblowf.c delete mode 100644 sshccp.c delete mode 100644 sshcrc.c delete mode 100644 sshdes.c delete mode 100644 sshdh.c delete mode 100644 sshdss.c delete mode 100644 sshecc.c delete mode 100644 sshhmac.c delete mode 100644 sshmac.c delete mode 100644 sshmd5.c delete mode 100644 sshprng.c delete mode 100644 sshrsa.c delete mode 100644 sshsh256.c delete mode 100644 sshsh512.c delete mode 100644 sshsha.c delete mode 100644 sshsha3.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 77c4fe23..d3be22b5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,15 +23,8 @@ add_library(settings STATIC cmdline.c settings.c) add_library(crypto STATIC - sshaes.c sshauxcrypt.c sshdes.c sshdss.c sshecc.c sshhmac.c sshmd5.c sshrsa.c - sshsh256.c sshsh512.c sshsha.c sshsha3.c - ecc.c mpint.c - sshprng.c - sshcrc.c - sshdh.c sshmac.c - ssharcf.c sshblowf.c sshccp.c - sshblake2.c sshargon2.c sshbcrypt.c cproxy.c) +add_subdirectory(crypto) add_library(network STATIC be_misc.c nullplug.c errsock.c proxy.c logging.c x11disp.c) diff --git a/crypto/CMakeLists.txt b/crypto/CMakeLists.txt new file mode 100644 index 00000000..74f86cd4 --- /dev/null +++ b/crypto/CMakeLists.txt @@ -0,0 +1,30 @@ +add_sources_from_current_dir(crypto + aes.c + arcfour.c + argon2.c + bcrypt.c + blake2.c + blowfish.c + chacha20-poly1305.c + crc32.c + des.c + diffie-hellman.c + dsa.c + ecc-arithmetic.c + ecc-ssh.c + hash_simple.c + hmac.c + mac.c + mac_simple.c + md5.c + mpint.c + prng.c + pubkey-pem.c + pubkey-ppk.c + pubkey-ssh1.c + rsa.c + sha256.c + sha512.c + sha3.c + sha1.c + xdmauth.c) diff --git a/crypto/aes.c b/crypto/aes.c new file mode 100644 index 00000000..a7ca1117 --- /dev/null +++ b/crypto/aes.c @@ -0,0 +1,1912 @@ +/* + * Implementation of AES. + */ + +#include +#include + +#include "ssh.h" +#include "mpint_i.h" /* we reuse the BignumInt system */ + +/* + * Start by deciding whether we can support hardware AES at all. + */ +#define HW_AES_NONE 0 +#define HW_AES_NI 1 +#define HW_AES_NEON 2 + +#ifdef _FORCE_AES_NI +# define HW_AES HW_AES_NI +#elif defined(__clang__) +# if __has_attribute(target) && __has_include() && \ + (defined(__x86_64__) || defined(__i386)) +# define HW_AES HW_AES_NI +# endif +#elif defined(__GNUC__) +# if (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 4)) && \ + (defined(__x86_64__) || defined(__i386)) +# define HW_AES HW_AES_NI +# endif +#elif defined (_MSC_VER) +# if (defined(_M_X64) || defined(_M_IX86)) && _MSC_FULL_VER >= 150030729 +# define HW_AES HW_AES_NI +# endif +#endif + +#ifdef _FORCE_AES_NEON +# define HW_AES HW_AES_NEON +#elif defined __BYTE_ORDER__ && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + /* Arm can potentially support both endiannesses, but this code + * hasn't been tested on anything but little. If anyone wants to + * run big-endian, they'll need to fix it first. */ +#elif defined __ARM_FEATURE_CRYPTO + /* If the Arm crypto extension is available already, we can + * support NEON AES without having to enable anything by hand */ +# define HW_AES HW_AES_NEON +#elif defined(__clang__) +# if __has_attribute(target) && __has_include() && \ + (defined(__aarch64__)) + /* clang can enable the crypto extension in AArch64 using + * __attribute__((target)) */ +# define HW_AES HW_AES_NEON +# define USE_CLANG_ATTR_TARGET_AARCH64 +# endif +#elif defined _MSC_VER +# if defined _M_ARM64 +# define HW_AES HW_AES_NEON + /* 64-bit Visual Studio uses the header in place + * of the standard */ +# define USE_ARM64_NEON_H +# elif defined _M_ARM +# define HW_AES HW_AES_NEON + /* 32-bit Visual Studio uses the right header name, but requires + * this #define to enable a set of intrinsic definitions that + * do not omit one of the parameters for vaes[ed]q_u8 */ +# define _ARM_USE_NEW_NEON_INTRINSICS +# endif +#endif + +#if defined _FORCE_SOFTWARE_AES || !defined HW_AES +# undef HW_AES +# define HW_AES HW_AES_NONE +#endif + +#if HW_AES == HW_AES_NI +#define HW_NAME_SUFFIX " (AES-NI accelerated)" +#elif HW_AES == HW_AES_NEON +#define HW_NAME_SUFFIX " (NEON accelerated)" +#else +#define HW_NAME_SUFFIX " (!NONEXISTENT ACCELERATED VERSION!)" +#endif + +/* + * Vtable collection for AES. For each SSH-level cipher id (i.e. + * combination of key length and cipher mode), we provide three + * vtables: one for the pure software implementation, one using + * hardware acceleration (if available), and a top-level one which is + * never actually instantiated, and only contains a new() method whose + * job is to decide which of the other two to return an actual + * instance of. + */ + +static ssh_cipher *aes_select(const ssh_cipheralg *alg); +static ssh_cipher *aes_sw_new(const ssh_cipheralg *alg); +static void aes_sw_free(ssh_cipher *); +static void aes_sw_setiv_cbc(ssh_cipher *, const void *iv); +static void aes_sw_setiv_sdctr(ssh_cipher *, const void *iv); +static void aes_sw_setkey(ssh_cipher *, const void *key); +static ssh_cipher *aes_hw_new(const ssh_cipheralg *alg); +static void aes_hw_free(ssh_cipher *); +static void aes_hw_setiv_cbc(ssh_cipher *, const void *iv); +static void aes_hw_setiv_sdctr(ssh_cipher *, const void *iv); +static void aes_hw_setkey(ssh_cipher *, const void *key); + +struct aes_extra { + const ssh_cipheralg *sw, *hw; +}; + +#define VTABLES_INNER(cid, pid, bits, name, encsuffix, \ + decsuffix, setivsuffix, flagsval) \ + static void cid##_sw##encsuffix(ssh_cipher *, void *blk, int len); \ + static void cid##_sw##decsuffix(ssh_cipher *, void *blk, int len); \ + const ssh_cipheralg ssh_##cid##_sw = { \ + .new = aes_sw_new, \ + .free = aes_sw_free, \ + .setiv = aes_sw_##setivsuffix, \ + .setkey = aes_sw_setkey, \ + .encrypt = cid##_sw##encsuffix, \ + .decrypt = cid##_sw##decsuffix, \ + .ssh2_id = pid, \ + .blksize = 16, \ + .real_keybits = bits, \ + .padded_keybytes = bits/8, \ + .flags = flagsval, \ + .text_name = name " (unaccelerated)", \ + }; \ + \ + static void cid##_hw##encsuffix(ssh_cipher *, void *blk, int len); \ + static void cid##_hw##decsuffix(ssh_cipher *, void *blk, int len); \ + const ssh_cipheralg ssh_##cid##_hw = { \ + .new = aes_hw_new, \ + .free = aes_hw_free, \ + .setiv = aes_hw_##setivsuffix, \ + .setkey = aes_hw_setkey, \ + .encrypt = cid##_hw##encsuffix, \ + .decrypt = cid##_hw##decsuffix, \ + .ssh2_id = pid, \ + .blksize = 16, \ + .real_keybits = bits, \ + .padded_keybytes = bits/8, \ + .flags = flagsval, \ + .text_name = name HW_NAME_SUFFIX, \ + }; \ + \ + static const struct aes_extra extra_##cid = { \ + &ssh_##cid##_sw, &ssh_##cid##_hw }; \ + \ + const ssh_cipheralg ssh_##cid = { \ + .new = aes_select, \ + .ssh2_id = pid, \ + .blksize = 16, \ + .real_keybits = bits, \ + .padded_keybytes = bits/8, \ + .flags = flagsval, \ + .text_name = name " (dummy selector vtable)", \ + .extra = &extra_##cid \ + }; \ + +#define VTABLES(keylen) \ + VTABLES_INNER(aes ## keylen ## _cbc, "aes" #keylen "-cbc", \ + keylen, "AES-" #keylen " CBC", _encrypt, _decrypt, \ + setiv_cbc, SSH_CIPHER_IS_CBC) \ + VTABLES_INNER(aes ## keylen ## _sdctr, "aes" #keylen "-ctr", \ + keylen, "AES-" #keylen " SDCTR",,, setiv_sdctr, 0) + +VTABLES(128) +VTABLES(192) +VTABLES(256) + +static const ssh_cipheralg ssh_rijndael_lysator = { + /* Same as aes256_cbc, but with a different protocol ID */ + .new = aes_select, + .ssh2_id = "rijndael-cbc@lysator.liu.se", + .blksize = 16, + .real_keybits = 256, + .padded_keybytes = 256/8, + .flags = 0, + .text_name = "AES-256 CBC (dummy selector vtable)", + .extra = &extra_aes256_cbc, +}; + +static const ssh_cipheralg *const aes_list[] = { + &ssh_aes256_sdctr, + &ssh_aes256_cbc, + &ssh_rijndael_lysator, + &ssh_aes192_sdctr, + &ssh_aes192_cbc, + &ssh_aes128_sdctr, + &ssh_aes128_cbc, +}; + +const ssh2_ciphers ssh2_aes = { lenof(aes_list), aes_list }; + +/* + * The actual query function that asks if hardware acceleration is + * available. + */ +static bool aes_hw_available(void); + +/* + * The top-level selection function, caching the results of + * aes_hw_available() so it only has to run once. + */ +static bool aes_hw_available_cached(void) +{ + static bool initialised = false; + static bool hw_available; + if (!initialised) { + hw_available = aes_hw_available(); + initialised = true; + } + return hw_available; +} + +static ssh_cipher *aes_select(const ssh_cipheralg *alg) +{ + const struct aes_extra *extra = (const struct aes_extra *)alg->extra; + const ssh_cipheralg *real_alg = + aes_hw_available_cached() ? extra->hw : extra->sw; + + return ssh_cipher_new(real_alg); +} + +/* ---------------------------------------------------------------------- + * Definitions likely to be helpful to multiple implementations. + */ + +#define REP2(x) x x +#define REP4(x) REP2(REP2(x)) +#define REP8(x) REP2(REP4(x)) +#define REP9(x) REP8(x) x +#define REP11(x) REP8(x) REP2(x) x +#define REP13(x) REP8(x) REP4(x) x + +static const uint8_t key_setup_round_constants[] = { + /* The first few powers of X in GF(2^8), used during key setup. + * This can safely be a lookup table without side channel risks, + * because key setup iterates through it once in a standard way + * regardless of the key. */ + 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, +}; + +#define MAXROUNDKEYS 15 + +/* ---------------------------------------------------------------------- + * Software implementation of AES. + * + * This implementation uses a bit-sliced representation. Instead of + * the obvious approach of storing the cipher state so that each byte + * (or field element, or entry in the cipher matrix) occupies 8 + * contiguous bits in a machine integer somewhere, we organise the + * cipher state as an array of 8 integers, in such a way that each + * logical byte of the cipher state occupies one bit in each integer, + * all at the same position. This allows us to do parallel logic on + * all bytes of the state by doing bitwise operations between the 8 + * integers; in particular, the S-box (SubBytes) lookup is done this + * way, which takes about 110 operations - but for those 110 bitwise + * ops you get 64 S-box lookups, not just one. + */ + +#define SLICE_PARALLELISM (BIGNUM_INT_BYTES / 2) + +#ifdef BITSLICED_DEBUG +/* Dump function that undoes the bitslicing transform, so you can see + * the logical data represented by a set of slice words. */ +static inline void dumpslices_uint16_t( + const char *prefix, const uint16_t slices[8]) +{ + printf("%-30s", prefix); + for (unsigned byte = 0; byte < 16; byte++) { + unsigned byteval = 0; + for (unsigned bit = 0; bit < 8; bit++) + byteval |= (1 & (slices[bit] >> byte)) << bit; + printf("%02x", byteval); + } + printf("\n"); +} + +static inline void dumpslices_BignumInt( + const char *prefix, const BignumInt slices[8]) +{ + printf("%-30s", prefix); + for (unsigned iter = 0; iter < SLICE_PARALLELISM; iter++) { + for (unsigned byte = 0; byte < 16; byte++) { + unsigned byteval = 0; + for (unsigned bit = 0; bit < 8; bit++) + byteval |= (1 & (slices[bit] >> (iter*16+byte))) << bit; + printf("%02x", byteval); + } + if (iter+1 < SLICE_PARALLELISM) + printf(" "); + } + printf("\n"); +} +#else +#define dumpslices_uintN_t(prefix, slices) ((void)0) +#define dumpslices_BignumInt(prefix, slices) ((void)0) +#endif + +/* ----- + * Bit-slicing transformation: convert between an array of 16 uint8_t + * and an array of 8 uint16_t, so as to interchange the bit index + * within each element and the element index within the array. (That + * is, bit j of input[i] == bit i of output[j]. + */ + +#define SWAPWORDS(shift) do \ + { \ + uint64_t mask = ~(uint64_t)0 / ((1ULL << shift) + 1); \ + uint64_t diff = ((i0 >> shift) ^ i1) & mask; \ + i0 ^= diff << shift; \ + i1 ^= diff; \ + } while (0) + +#define SWAPINWORD(i, bigshift, smallshift) do \ + { \ + uint64_t mask = ~(uint64_t)0; \ + mask /= ((1ULL << bigshift) + 1); \ + mask /= ((1ULL << smallshift) + 1); \ + mask <<= smallshift; \ + unsigned shift = bigshift - smallshift; \ + uint64_t diff = ((i >> shift) ^ i) & mask; \ + i ^= diff ^ (diff << shift); \ + } while (0) + +#define TO_BITSLICES(slices, bytes, uintN_t, assign_op, shift) do \ + { \ + uint64_t i0 = GET_64BIT_LSB_FIRST(bytes); \ + uint64_t i1 = GET_64BIT_LSB_FIRST(bytes + 8); \ + SWAPINWORD(i0, 8, 1); \ + SWAPINWORD(i1, 8, 1); \ + SWAPINWORD(i0, 16, 2); \ + SWAPINWORD(i1, 16, 2); \ + SWAPINWORD(i0, 32, 4); \ + SWAPINWORD(i1, 32, 4); \ + SWAPWORDS(8); \ + slices[0] assign_op (uintN_t)((i0 >> 0) & 0xFFFF) << (shift); \ + slices[2] assign_op (uintN_t)((i0 >> 16) & 0xFFFF) << (shift); \ + slices[4] assign_op (uintN_t)((i0 >> 32) & 0xFFFF) << (shift); \ + slices[6] assign_op (uintN_t)((i0 >> 48) & 0xFFFF) << (shift); \ + slices[1] assign_op (uintN_t)((i1 >> 0) & 0xFFFF) << (shift); \ + slices[3] assign_op (uintN_t)((i1 >> 16) & 0xFFFF) << (shift); \ + slices[5] assign_op (uintN_t)((i1 >> 32) & 0xFFFF) << (shift); \ + slices[7] assign_op (uintN_t)((i1 >> 48) & 0xFFFF) << (shift); \ + } while (0) + +#define FROM_BITSLICES(bytes, slices, shift) do \ + { \ + uint64_t i1 = ((slices[7] >> (shift)) & 0xFFFF); \ + i1 = (i1 << 16) | ((slices[5] >> (shift)) & 0xFFFF); \ + i1 = (i1 << 16) | ((slices[3] >> (shift)) & 0xFFFF); \ + i1 = (i1 << 16) | ((slices[1] >> (shift)) & 0xFFFF); \ + uint64_t i0 = ((slices[6] >> (shift)) & 0xFFFF); \ + i0 = (i0 << 16) | ((slices[4] >> (shift)) & 0xFFFF); \ + i0 = (i0 << 16) | ((slices[2] >> (shift)) & 0xFFFF); \ + i0 = (i0 << 16) | ((slices[0] >> (shift)) & 0xFFFF); \ + SWAPWORDS(8); \ + SWAPINWORD(i0, 32, 4); \ + SWAPINWORD(i1, 32, 4); \ + SWAPINWORD(i0, 16, 2); \ + SWAPINWORD(i1, 16, 2); \ + SWAPINWORD(i0, 8, 1); \ + SWAPINWORD(i1, 8, 1); \ + PUT_64BIT_LSB_FIRST(bytes, i0); \ + PUT_64BIT_LSB_FIRST((bytes) + 8, i1); \ + } while (0) + +/* ----- + * Some macros that will be useful repeatedly. + */ + +/* Iterate a unary transformation over all 8 slices. */ +#define ITERATE(MACRO, output, input, uintN_t) do \ + { \ + MACRO(output[0], input[0], uintN_t); \ + MACRO(output[1], input[1], uintN_t); \ + MACRO(output[2], input[2], uintN_t); \ + MACRO(output[3], input[3], uintN_t); \ + MACRO(output[4], input[4], uintN_t); \ + MACRO(output[5], input[5], uintN_t); \ + MACRO(output[6], input[6], uintN_t); \ + MACRO(output[7], input[7], uintN_t); \ + } while (0) + +/* Simply add (i.e. XOR) two whole sets of slices together. */ +#define BITSLICED_ADD(output, lhs, rhs) do \ + { \ + output[0] = lhs[0] ^ rhs[0]; \ + output[1] = lhs[1] ^ rhs[1]; \ + output[2] = lhs[2] ^ rhs[2]; \ + output[3] = lhs[3] ^ rhs[3]; \ + output[4] = lhs[4] ^ rhs[4]; \ + output[5] = lhs[5] ^ rhs[5]; \ + output[6] = lhs[6] ^ rhs[6]; \ + output[7] = lhs[7] ^ rhs[7]; \ + } while (0) + +/* ----- + * The AES S-box, in pure bitwise logic so that it can be run in + * parallel on whole words full of bit-sliced field elements. + * + * Source: 'A new combinational logic minimization technique with + * applications to cryptology', https://eprint.iacr.org/2009/191 + * + * As a minor speed optimisation, I use a modified version of the + * S-box which omits the additive constant 0x63, i.e. this S-box + * consists of only the field inversion and linear map components. + * Instead, the addition of the constant is deferred until after the + * subsequent ShiftRows and MixColumns stages, so that it happens at + * the same time as adding the next round key - and then we just make + * it _part_ of the round key, so it doesn't cost any extra + * instructions to add. + * + * (Obviously adding a constant to each byte commutes with ShiftRows, + * which only permutes the bytes. It also commutes with MixColumns: + * that's not quite so obvious, but since the effect of MixColumns is + * to multiply a constant polynomial M into each column, it is obvious + * that adding some polynomial K and then multiplying by M is + * equivalent to multiplying by M and then adding the product KM. And + * in fact, since the coefficients of M happen to sum to 1, it turns + * out that KM = K, so we don't even have to change the constant when + * we move it to the far side of MixColumns.) + * + * Of course, one knock-on effect of this is that the use of the S-box + * *during* key setup has to be corrected by manually adding on the + * constant afterwards! + */ + +/* Initial linear transformation for the forward S-box, from Fig 2 of + * the paper. */ +#define SBOX_FORWARD_TOP_TRANSFORM(input, uintN_t) \ + uintN_t y14 = input[4] ^ input[2]; \ + uintN_t y13 = input[7] ^ input[1]; \ + uintN_t y9 = input[7] ^ input[4]; \ + uintN_t y8 = input[7] ^ input[2]; \ + uintN_t t0 = input[6] ^ input[5]; \ + uintN_t y1 = t0 ^ input[0]; \ + uintN_t y4 = y1 ^ input[4]; \ + uintN_t y12 = y13 ^ y14; \ + uintN_t y2 = y1 ^ input[7]; \ + uintN_t y5 = y1 ^ input[1]; \ + uintN_t y3 = y5 ^ y8; \ + uintN_t t1 = input[3] ^ y12; \ + uintN_t y15 = t1 ^ input[2]; \ + uintN_t y20 = t1 ^ input[6]; \ + uintN_t y6 = y15 ^ input[0]; \ + uintN_t y10 = y15 ^ t0; \ + uintN_t y11 = y20 ^ y9; \ + uintN_t y7 = input[0] ^ y11; \ + uintN_t y17 = y10 ^ y11; \ + uintN_t y19 = y10 ^ y8; \ + uintN_t y16 = t0 ^ y11; \ + uintN_t y21 = y13 ^ y16; \ + uintN_t y18 = input[7] ^ y16; \ + /* Make a copy of input[0] under a new name, because the core + * will refer to it, and in the inverse version of the S-box + * the corresponding value will be one of the calculated ones + * and not in input[0] itself. */ \ + uintN_t i0 = input[0]; \ + /* end */ + +/* Core nonlinear component, from Fig 3 of the paper. */ +#define SBOX_CORE(uintN_t) \ + uintN_t t2 = y12 & y15; \ + uintN_t t3 = y3 & y6; \ + uintN_t t4 = t3 ^ t2; \ + uintN_t t5 = y4 & i0; \ + uintN_t t6 = t5 ^ t2; \ + uintN_t t7 = y13 & y16; \ + uintN_t t8 = y5 & y1; \ + uintN_t t9 = t8 ^ t7; \ + uintN_t t10 = y2 & y7; \ + uintN_t t11 = t10 ^ t7; \ + uintN_t t12 = y9 & y11; \ + uintN_t t13 = y14 & y17; \ + uintN_t t14 = t13 ^ t12; \ + uintN_t t15 = y8 & y10; \ + uintN_t t16 = t15 ^ t12; \ + uintN_t t17 = t4 ^ t14; \ + uintN_t t18 = t6 ^ t16; \ + uintN_t t19 = t9 ^ t14; \ + uintN_t t20 = t11 ^ t16; \ + uintN_t t21 = t17 ^ y20; \ + uintN_t t22 = t18 ^ y19; \ + uintN_t t23 = t19 ^ y21; \ + uintN_t t24 = t20 ^ y18; \ + uintN_t t25 = t21 ^ t22; \ + uintN_t t26 = t21 & t23; \ + uintN_t t27 = t24 ^ t26; \ + uintN_t t28 = t25 & t27; \ + uintN_t t29 = t28 ^ t22; \ + uintN_t t30 = t23 ^ t24; \ + uintN_t t31 = t22 ^ t26; \ + uintN_t t32 = t31 & t30; \ + uintN_t t33 = t32 ^ t24; \ + uintN_t t34 = t23 ^ t33; \ + uintN_t t35 = t27 ^ t33; \ + uintN_t t36 = t24 & t35; \ + uintN_t t37 = t36 ^ t34; \ + uintN_t t38 = t27 ^ t36; \ + uintN_t t39 = t29 & t38; \ + uintN_t t40 = t25 ^ t39; \ + uintN_t t41 = t40 ^ t37; \ + uintN_t t42 = t29 ^ t33; \ + uintN_t t43 = t29 ^ t40; \ + uintN_t t44 = t33 ^ t37; \ + uintN_t t45 = t42 ^ t41; \ + uintN_t z0 = t44 & y15; \ + uintN_t z1 = t37 & y6; \ + uintN_t z2 = t33 & i0; \ + uintN_t z3 = t43 & y16; \ + uintN_t z4 = t40 & y1; \ + uintN_t z5 = t29 & y7; \ + uintN_t z6 = t42 & y11; \ + uintN_t z7 = t45 & y17; \ + uintN_t z8 = t41 & y10; \ + uintN_t z9 = t44 & y12; \ + uintN_t z10 = t37 & y3; \ + uintN_t z11 = t33 & y4; \ + uintN_t z12 = t43 & y13; \ + uintN_t z13 = t40 & y5; \ + uintN_t z14 = t29 & y2; \ + uintN_t z15 = t42 & y9; \ + uintN_t z16 = t45 & y14; \ + uintN_t z17 = t41 & y8; \ + /* end */ + +/* Final linear transformation for the forward S-box, from Fig 4 of + * the paper. */ +#define SBOX_FORWARD_BOTTOM_TRANSFORM(output, uintN_t) \ + uintN_t t46 = z15 ^ z16; \ + uintN_t t47 = z10 ^ z11; \ + uintN_t t48 = z5 ^ z13; \ + uintN_t t49 = z9 ^ z10; \ + uintN_t t50 = z2 ^ z12; \ + uintN_t t51 = z2 ^ z5; \ + uintN_t t52 = z7 ^ z8; \ + uintN_t t53 = z0 ^ z3; \ + uintN_t t54 = z6 ^ z7; \ + uintN_t t55 = z16 ^ z17; \ + uintN_t t56 = z12 ^ t48; \ + uintN_t t57 = t50 ^ t53; \ + uintN_t t58 = z4 ^ t46; \ + uintN_t t59 = z3 ^ t54; \ + uintN_t t60 = t46 ^ t57; \ + uintN_t t61 = z14 ^ t57; \ + uintN_t t62 = t52 ^ t58; \ + uintN_t t63 = t49 ^ t58; \ + uintN_t t64 = z4 ^ t59; \ + uintN_t t65 = t61 ^ t62; \ + uintN_t t66 = z1 ^ t63; \ + output[7] = t59 ^ t63; \ + output[1] = t56 ^ t62; \ + output[0] = t48 ^ t60; \ + uintN_t t67 = t64 ^ t65; \ + output[4] = t53 ^ t66; \ + output[3] = t51 ^ t66; \ + output[2] = t47 ^ t65; \ + output[6] = t64 ^ output[4]; \ + output[5] = t55 ^ t67; \ + /* end */ + +#define BITSLICED_SUBBYTES(output, input, uintN_t) do { \ + SBOX_FORWARD_TOP_TRANSFORM(input, uintN_t); \ + SBOX_CORE(uintN_t); \ + SBOX_FORWARD_BOTTOM_TRANSFORM(output, uintN_t); \ + } while (0) + +/* + * Initial and final linear transformations for the backward S-box. I + * generated these myself, by implementing the linear-transform + * optimisation algorithm in the paper, and applying it to the + * matrices calculated by _their_ top and bottom transformations, pre- + * and post-multiplied as appropriate by the linear map in the inverse + * S_box. + */ +#define SBOX_BACKWARD_TOP_TRANSFORM(input, uintN_t) \ + uintN_t y5 = input[4] ^ input[6]; \ + uintN_t y19 = input[3] ^ input[0]; \ + uintN_t itmp8 = y5 ^ input[0]; \ + uintN_t y4 = itmp8 ^ input[1]; \ + uintN_t y9 = input[4] ^ input[3]; \ + uintN_t y2 = y9 ^ y4; \ + uintN_t itmp9 = y2 ^ input[7]; \ + uintN_t y1 = y9 ^ input[0]; \ + uintN_t y6 = y5 ^ input[7]; \ + uintN_t y18 = y9 ^ input[5]; \ + uintN_t y7 = y18 ^ y2; \ + uintN_t y16 = y7 ^ y1; \ + uintN_t y21 = y7 ^ input[1]; \ + uintN_t y3 = input[4] ^ input[7]; \ + uintN_t y13 = y16 ^ y21; \ + uintN_t y8 = input[4] ^ y6; \ + uintN_t y10 = y8 ^ y19; \ + uintN_t y14 = y8 ^ y9; \ + uintN_t y20 = itmp9 ^ input[2]; \ + uintN_t y11 = y9 ^ y20; \ + uintN_t i0 = y11 ^ y7; \ + uintN_t y15 = i0 ^ y6; \ + uintN_t y17 = y16 ^ y15; \ + uintN_t y12 = itmp9 ^ input[3]; \ + /* end */ +#define SBOX_BACKWARD_BOTTOM_TRANSFORM(output, uintN_t) \ + uintN_t otmp18 = z15 ^ z6; \ + uintN_t otmp19 = z13 ^ otmp18; \ + uintN_t otmp20 = z12 ^ otmp19; \ + uintN_t otmp21 = z16 ^ otmp20; \ + uintN_t otmp22 = z8 ^ otmp21; \ + uintN_t otmp23 = z0 ^ otmp22; \ + uintN_t otmp24 = otmp22 ^ z3; \ + uintN_t otmp25 = otmp24 ^ z4; \ + uintN_t otmp26 = otmp25 ^ z2; \ + uintN_t otmp27 = z1 ^ otmp26; \ + uintN_t otmp28 = z14 ^ otmp27; \ + uintN_t otmp29 = otmp28 ^ z10; \ + output[4] = z2 ^ otmp23; \ + output[7] = z5 ^ otmp24; \ + uintN_t otmp30 = z11 ^ otmp29; \ + output[5] = z13 ^ otmp30; \ + uintN_t otmp31 = otmp25 ^ z8; \ + output[1] = z7 ^ otmp31; \ + uintN_t otmp32 = z11 ^ z9; \ + uintN_t otmp33 = z17 ^ otmp32; \ + uintN_t otmp34 = otmp30 ^ otmp33; \ + output[0] = z15 ^ otmp33; \ + uintN_t otmp35 = z12 ^ otmp34; \ + output[6] = otmp35 ^ z16; \ + uintN_t otmp36 = z1 ^ otmp23; \ + uintN_t otmp37 = z5 ^ otmp36; \ + output[2] = z4 ^ otmp37; \ + uintN_t otmp38 = z11 ^ output[1]; \ + uintN_t otmp39 = z2 ^ otmp38; \ + uintN_t otmp40 = z17 ^ otmp39; \ + uintN_t otmp41 = z0 ^ otmp40; \ + uintN_t otmp42 = z5 ^ otmp41; \ + uintN_t otmp43 = otmp42 ^ z10; \ + uintN_t otmp44 = otmp43 ^ z3; \ + output[3] = otmp44 ^ z16; \ + /* end */ + +#define BITSLICED_INVSUBBYTES(output, input, uintN_t) do { \ + SBOX_BACKWARD_TOP_TRANSFORM(input, uintN_t); \ + SBOX_CORE(uintN_t); \ + SBOX_BACKWARD_BOTTOM_TRANSFORM(output, uintN_t); \ + } while (0) + + +/* ----- + * The ShiftRows transformation. This operates independently on each + * bit slice. + */ + +#define SINGLE_BITSLICE_SHIFTROWS(output, input, uintN_t) do \ + { \ + uintN_t mask, mask2, mask3, diff, x = (input); \ + /* Rotate rows 2 and 3 by 16 bits */ \ + mask = 0x00CC * (((uintN_t)~(uintN_t)0) / 0xFFFF); \ + diff = ((x >> 8) ^ x) & mask; \ + x ^= diff ^ (diff << 8); \ + /* Rotate rows 1 and 3 by 8 bits */ \ + mask = 0x0AAA * (((uintN_t)~(uintN_t)0) / 0xFFFF); \ + mask2 = 0xA000 * (((uintN_t)~(uintN_t)0) / 0xFFFF); \ + mask3 = 0x5555 * (((uintN_t)~(uintN_t)0) / 0xFFFF); \ + x = ((x >> 4) & mask) | ((x << 12) & mask2) | (x & mask3); \ + /* Write output */ \ + (output) = x; \ + } while (0) + +#define SINGLE_BITSLICE_INVSHIFTROWS(output, input, uintN_t) do \ + { \ + uintN_t mask, mask2, mask3, diff, x = (input); \ + /* Rotate rows 2 and 3 by 16 bits */ \ + mask = 0x00CC * (((uintN_t)~(uintN_t)0) / 0xFFFF); \ + diff = ((x >> 8) ^ x) & mask; \ + x ^= diff ^ (diff << 8); \ + /* Rotate rows 1 and 3 by 8 bits, the opposite way to ShiftRows */ \ + mask = 0x000A * (((uintN_t)~(uintN_t)0) / 0xFFFF); \ + mask2 = 0xAAA0 * (((uintN_t)~(uintN_t)0) / 0xFFFF); \ + mask3 = 0x5555 * (((uintN_t)~(uintN_t)0) / 0xFFFF); \ + x = ((x >> 12) & mask) | ((x << 4) & mask2) | (x & mask3); \ + /* Write output */ \ + (output) = x; \ + } while (0) + +#define BITSLICED_SHIFTROWS(output, input, uintN_t) do \ + { \ + ITERATE(SINGLE_BITSLICE_SHIFTROWS, output, input, uintN_t); \ + } while (0) + +#define BITSLICED_INVSHIFTROWS(output, input, uintN_t) do \ + { \ + ITERATE(SINGLE_BITSLICE_INVSHIFTROWS, output, input, uintN_t); \ + } while (0) + +/* ----- + * The MixColumns transformation. This has to operate on all eight bit + * slices at once, and also passes data back and forth between the + * bits in an adjacent group of 4 within each slice. + * + * Notation: let F = GF(2)[X]/ be the finite field + * used in AES, and let R = F[Y]/ be the ring whose elements + * represent the possible contents of a column of the matrix. I use X + * and Y below in those senses, i.e. X is the value in F that + * represents the byte 0x02, and Y is the value in R that cycles the + * four bytes around by one if you multiply by it. + */ + +/* Multiply every column by Y^3, i.e. cycle it round one place to the + * right. Operates on one bit slice at a time; you have to wrap it in + * ITERATE to affect all the data at once. */ +#define BITSLICED_MUL_BY_Y3(output, input, uintN_t) do \ + { \ + uintN_t mask, mask2, x; \ + mask = 0x8 * (((uintN_t)~(uintN_t)0) / 0xF); \ + mask2 = 0x7 * (((uintN_t)~(uintN_t)0) / 0xF); \ + x = input; \ + output = ((x << 3) & mask) ^ ((x >> 1) & mask2); \ + } while (0) + +/* Multiply every column by Y^2. */ +#define BITSLICED_MUL_BY_Y2(output, input, uintN_t) do \ + { \ + uintN_t mask, mask2, x; \ + mask = 0xC * (((uintN_t)~(uintN_t)0) / 0xF); \ + mask2 = 0x3 * (((uintN_t)~(uintN_t)0) / 0xF); \ + x = input; \ + output = ((x << 2) & mask) ^ ((x >> 2) & mask2); \ + } while (0) + +#define BITSLICED_MUL_BY_1_Y3(output, input, uintN_t) do \ + { \ + uintN_t tmp = input; \ + BITSLICED_MUL_BY_Y3(tmp, input, uintN_t); \ + output = input ^ tmp; \ + } while (0) + +/* Multiply every column by 1+Y^2. */ +#define BITSLICED_MUL_BY_1_Y2(output, input, uintN_t) do \ + { \ + uintN_t tmp = input; \ + BITSLICED_MUL_BY_Y2(tmp, input, uintN_t); \ + output = input ^ tmp; \ + } while (0) + +/* Multiply every field element by X. This has to feed data between + * slices, so it does the whole job in one go without needing ITERATE. */ +#define BITSLICED_MUL_BY_X(output, input, uintN_t) do \ + { \ + uintN_t bit7 = input[7]; \ + output[7] = input[6]; \ + output[6] = input[5]; \ + output[5] = input[4]; \ + output[4] = input[3] ^ bit7; \ + output[3] = input[2] ^ bit7; \ + output[2] = input[1]; \ + output[1] = input[0] ^ bit7; \ + output[0] = bit7; \ + } while (0) + +/* + * The MixColumns constant is + * M = X + Y + Y^2 + (X+1)Y^3 + * which we construct by rearranging it into + * M = 1 + (1+Y^3) [ X + (1+Y^2) ] + */ +#define BITSLICED_MIXCOLUMNS(output, input, uintN_t) do \ + { \ + uintN_t a[8], aX[8], b[8]; \ + /* a = input * (1+Y^3) */ \ + ITERATE(BITSLICED_MUL_BY_1_Y3, a, input, uintN_t); \ + /* aX = a * X */ \ + BITSLICED_MUL_BY_X(aX, a, uintN_t); \ + /* b = a * (1+Y^2) = input * (1+Y+Y^2+Y^3) */ \ + ITERATE(BITSLICED_MUL_BY_1_Y2, b, a, uintN_t); \ + /* output = input + aX + b (reusing a as a temp */ \ + BITSLICED_ADD(a, aX, b); \ + BITSLICED_ADD(output, input, a); \ + } while (0) + +/* + * The InvMixColumns constant, written out longhand, is + * I = (X^3+X^2+X) + (X^3+1)Y + (X^3+X^2+1)Y^2 + (X^3+X+1)Y^3 + * We represent this as + * I = (X^3+X^2+X+1)(Y^3+Y^2+Y+1) + 1 + X(Y+Y^2) + X^2(Y+Y^3) + */ +#define BITSLICED_INVMIXCOLUMNS(output, input, uintN_t) do \ + { \ + /* We need input * X^i for i=1,...,3 */ \ + uintN_t X[8], X2[8], X3[8]; \ + BITSLICED_MUL_BY_X(X, input, uintN_t); \ + BITSLICED_MUL_BY_X(X2, X, uintN_t); \ + BITSLICED_MUL_BY_X(X3, X2, uintN_t); \ + /* Sum them all and multiply by 1+Y+Y^2+Y^3. */ \ + uintN_t S[8]; \ + BITSLICED_ADD(S, input, X); \ + BITSLICED_ADD(S, S, X2); \ + BITSLICED_ADD(S, S, X3); \ + ITERATE(BITSLICED_MUL_BY_1_Y3, S, S, uintN_t); \ + ITERATE(BITSLICED_MUL_BY_1_Y2, S, S, uintN_t); \ + /* Compute the X(Y+Y^2) term. */ \ + uintN_t A[8]; \ + ITERATE(BITSLICED_MUL_BY_1_Y3, A, X, uintN_t); \ + ITERATE(BITSLICED_MUL_BY_Y2, A, A, uintN_t); \ + /* Compute the X^2(Y+Y^3) term. */ \ + uintN_t B[8]; \ + ITERATE(BITSLICED_MUL_BY_1_Y2, B, X2, uintN_t); \ + ITERATE(BITSLICED_MUL_BY_Y3, B, B, uintN_t); \ + /* And add all the pieces together. */ \ + BITSLICED_ADD(S, S, input); \ + BITSLICED_ADD(S, S, A); \ + BITSLICED_ADD(output, S, B); \ + } while (0) + +/* ----- + * Put it all together into a cipher round. + */ + +/* Dummy macro to get rid of the MixColumns in the final round. */ +#define NO_MIXCOLUMNS(out, in, uintN_t) do {} while (0) + +#define ENCRYPT_ROUND_FN(suffix, uintN_t, mixcol_macro) \ + static void aes_sliced_round_e_##suffix( \ + uintN_t output[8], const uintN_t input[8], const uintN_t roundkey[8]) \ + { \ + BITSLICED_SUBBYTES(output, input, uintN_t); \ + BITSLICED_SHIFTROWS(output, output, uintN_t); \ + mixcol_macro(output, output, uintN_t); \ + BITSLICED_ADD(output, output, roundkey); \ + } + +ENCRYPT_ROUND_FN(serial, uint16_t, BITSLICED_MIXCOLUMNS) +ENCRYPT_ROUND_FN(serial_last, uint16_t, NO_MIXCOLUMNS) +ENCRYPT_ROUND_FN(parallel, BignumInt, BITSLICED_MIXCOLUMNS) +ENCRYPT_ROUND_FN(parallel_last, BignumInt, NO_MIXCOLUMNS) + +#define DECRYPT_ROUND_FN(suffix, uintN_t, mixcol_macro) \ + static void aes_sliced_round_d_##suffix( \ + uintN_t output[8], const uintN_t input[8], const uintN_t roundkey[8]) \ + { \ + BITSLICED_ADD(output, input, roundkey); \ + mixcol_macro(output, output, uintN_t); \ + BITSLICED_INVSUBBYTES(output, output, uintN_t); \ + BITSLICED_INVSHIFTROWS(output, output, uintN_t); \ + } + +#if 0 /* no cipher mode we support requires serial decryption */ +DECRYPT_ROUND_FN(serial, uint16_t, BITSLICED_INVMIXCOLUMNS) +DECRYPT_ROUND_FN(serial_first, uint16_t, NO_MIXCOLUMNS) +#endif +DECRYPT_ROUND_FN(parallel, BignumInt, BITSLICED_INVMIXCOLUMNS) +DECRYPT_ROUND_FN(parallel_first, BignumInt, NO_MIXCOLUMNS) + +/* ----- + * Key setup function. + */ + +typedef struct aes_sliced_key aes_sliced_key; +struct aes_sliced_key { + BignumInt roundkeys_parallel[MAXROUNDKEYS * 8]; + uint16_t roundkeys_serial[MAXROUNDKEYS * 8]; + unsigned rounds; +}; + +static void aes_sliced_key_setup( + aes_sliced_key *sk, const void *vkey, size_t keybits) +{ + const unsigned char *key = (const unsigned char *)vkey; + + size_t key_words = keybits / 32; + sk->rounds = key_words + 6; + size_t sched_words = (sk->rounds + 1) * 4; + + unsigned rconpos = 0; + + uint16_t *outslices = sk->roundkeys_serial; + unsigned outshift = 0; + + memset(sk->roundkeys_serial, 0, sizeof(sk->roundkeys_serial)); + + uint8_t inblk[16]; + memset(inblk, 0, 16); + uint16_t slices[8]; + + for (size_t i = 0; i < sched_words; i++) { + /* + * Prepare a word of round key in the low 4 bits of each + * integer in slices[]. + */ + if (i < key_words) { + memcpy(inblk, key + 4*i, 4); + TO_BITSLICES(slices, inblk, uint16_t, =, 0); + } else { + unsigned wordindex, bitshift; + uint16_t *prevslices; + + /* Fetch the (i-1)th key word */ + wordindex = i-1; + bitshift = 4 * (wordindex & 3); + prevslices = sk->roundkeys_serial + 8 * (wordindex >> 2); + for (size_t i = 0; i < 8; i++) + slices[i] = prevslices[i] >> bitshift; + + /* Decide what we're doing in this expansion stage */ + bool rotate_and_round_constant = (i % key_words == 0); + bool sub = rotate_and_round_constant || + (key_words == 8 && i % 8 == 4); + + if (rotate_and_round_constant) { + for (size_t i = 0; i < 8; i++) + slices[i] = ((slices[i] << 3) | (slices[i] >> 1)) & 0xF; + } + + if (sub) { + /* Apply the SubBytes transform to the key word. But + * here we need to apply the _full_ SubBytes from the + * spec, including the constant which our S-box leaves + * out. */ + BITSLICED_SUBBYTES(slices, slices, uint16_t); + slices[0] ^= 0xFFFF; + slices[1] ^= 0xFFFF; + slices[5] ^= 0xFFFF; + slices[6] ^= 0xFFFF; + } + + if (rotate_and_round_constant) { + assert(rconpos < lenof(key_setup_round_constants)); + uint8_t rcon = key_setup_round_constants[rconpos++]; + for (size_t i = 0; i < 8; i++) + slices[i] ^= 1 & (rcon >> i); + } + + /* Combine with the (i-Nk)th key word */ + wordindex = i - key_words; + bitshift = 4 * (wordindex & 3); + prevslices = sk->roundkeys_serial + 8 * (wordindex >> 2); + for (size_t i = 0; i < 8; i++) + slices[i] ^= prevslices[i] >> bitshift; + } + + /* + * Now copy it into sk. + */ + for (unsigned b = 0; b < 8; b++) + outslices[b] |= (slices[b] & 0xF) << outshift; + outshift += 4; + if (outshift == 16) { + outshift = 0; + outslices += 8; + } + } + + smemclr(inblk, sizeof(inblk)); + smemclr(slices, sizeof(slices)); + + /* + * Add the S-box constant to every round key after the first one, + * compensating for it being left out in the main cipher. + */ + for (size_t i = 8; i < 8 * (sched_words/4); i += 8) { + sk->roundkeys_serial[i+0] ^= 0xFFFF; + sk->roundkeys_serial[i+1] ^= 0xFFFF; + sk->roundkeys_serial[i+5] ^= 0xFFFF; + sk->roundkeys_serial[i+6] ^= 0xFFFF; + } + + /* + * Replicate that set of round keys into larger integers for the + * parallel versions of the cipher. + */ + for (size_t i = 0; i < 8 * (sched_words / 4); i++) { + sk->roundkeys_parallel[i] = sk->roundkeys_serial[i] * + ((BignumInt)~(BignumInt)0 / 0xFFFF); + } +} + +/* ----- + * The full cipher primitive, including transforming the input and + * output to/from bit-sliced form. + */ + +#define ENCRYPT_FN(suffix, uintN_t, nblocks) \ + static void aes_sliced_e_##suffix( \ + uint8_t *output, const uint8_t *input, const aes_sliced_key *sk) \ + { \ + uintN_t state[8]; \ + TO_BITSLICES(state, input, uintN_t, =, 0); \ + for (unsigned i = 1; i < nblocks; i++) { \ + input += 16; \ + TO_BITSLICES(state, input, uintN_t, |=, i*16); \ + } \ + const uintN_t *keys = sk->roundkeys_##suffix; \ + BITSLICED_ADD(state, state, keys); \ + keys += 8; \ + for (unsigned i = 0; i < sk->rounds-1; i++) { \ + aes_sliced_round_e_##suffix(state, state, keys); \ + keys += 8; \ + } \ + aes_sliced_round_e_##suffix##_last(state, state, keys); \ + for (unsigned i = 0; i < nblocks; i++) { \ + FROM_BITSLICES(output, state, i*16); \ + output += 16; \ + } \ + } + +#define DECRYPT_FN(suffix, uintN_t, nblocks) \ + static void aes_sliced_d_##suffix( \ + uint8_t *output, const uint8_t *input, const aes_sliced_key *sk) \ + { \ + uintN_t state[8]; \ + TO_BITSLICES(state, input, uintN_t, =, 0); \ + for (unsigned i = 1; i < nblocks; i++) { \ + input += 16; \ + TO_BITSLICES(state, input, uintN_t, |=, i*16); \ + } \ + const uintN_t *keys = sk->roundkeys_##suffix + 8*sk->rounds; \ + aes_sliced_round_d_##suffix##_first(state, state, keys); \ + keys -= 8; \ + for (unsigned i = 0; i < sk->rounds-1; i++) { \ + aes_sliced_round_d_##suffix(state, state, keys); \ + keys -= 8; \ + } \ + BITSLICED_ADD(state, state, keys); \ + for (unsigned i = 0; i < nblocks; i++) { \ + FROM_BITSLICES(output, state, i*16); \ + output += 16; \ + } \ + } + +ENCRYPT_FN(serial, uint16_t, 1) +#if 0 /* no cipher mode we support requires serial decryption */ +DECRYPT_FN(serial, uint16_t, 1) +#endif +ENCRYPT_FN(parallel, BignumInt, SLICE_PARALLELISM) +DECRYPT_FN(parallel, BignumInt, SLICE_PARALLELISM) + +/* ----- + * The SSH interface and the cipher modes. + */ + +#define SDCTR_WORDS (16 / BIGNUM_INT_BYTES) + +typedef struct aes_sw_context aes_sw_context; +struct aes_sw_context { + aes_sliced_key sk; + union { + struct { + /* In CBC mode, the IV is just a copy of the last seen + * cipher block. */ + uint8_t prevblk[16]; + } cbc; + struct { + /* In SDCTR mode, we keep the counter itself in a form + * that's easy to increment. We also use the parallel + * version of the core AES function, so we'll encrypt + * multiple counter values in one go. That won't align + * nicely with the sizes of data we're asked to encrypt, + * so we must also store a cache of the last set of + * keystream blocks we generated, and our current position + * within that cache. */ + BignumInt counter[SDCTR_WORDS]; + uint8_t keystream[SLICE_PARALLELISM * 16]; + uint8_t *keystream_pos; + } sdctr; + } iv; + ssh_cipher ciph; +}; + +static ssh_cipher *aes_sw_new(const ssh_cipheralg *alg) +{ + aes_sw_context *ctx = snew(aes_sw_context); + ctx->ciph.vt = alg; + return &ctx->ciph; +} + +static void aes_sw_free(ssh_cipher *ciph) +{ + aes_sw_context *ctx = container_of(ciph, aes_sw_context, ciph); + smemclr(ctx, sizeof(*ctx)); + sfree(ctx); +} + +static void aes_sw_setkey(ssh_cipher *ciph, const void *vkey) +{ + aes_sw_context *ctx = container_of(ciph, aes_sw_context, ciph); + aes_sliced_key_setup(&ctx->sk, vkey, ctx->ciph.vt->real_keybits); +} + +static void aes_sw_setiv_cbc(ssh_cipher *ciph, const void *iv) +{ + aes_sw_context *ctx = container_of(ciph, aes_sw_context, ciph); + memcpy(ctx->iv.cbc.prevblk, iv, 16); +} + +static void aes_sw_setiv_sdctr(ssh_cipher *ciph, const void *viv) +{ + aes_sw_context *ctx = container_of(ciph, aes_sw_context, ciph); + const uint8_t *iv = (const uint8_t *)viv; + + /* Import the initial counter value into the internal representation */ + for (unsigned i = 0; i < SDCTR_WORDS; i++) + ctx->iv.sdctr.counter[i] = + GET_BIGNUMINT_MSB_FIRST( + iv + 16 - BIGNUM_INT_BYTES - i*BIGNUM_INT_BYTES); + + /* Set keystream_pos to indicate that the keystream cache is + * currently empty */ + ctx->iv.sdctr.keystream_pos = + ctx->iv.sdctr.keystream + sizeof(ctx->iv.sdctr.keystream); +} + +typedef void (*aes_sw_fn)(uint32_t v[4], const uint32_t *keysched); + +static inline void memxor16(void *vout, const void *vlhs, const void *vrhs) +{ + uint8_t *out = (uint8_t *)vout; + const uint8_t *lhs = (const uint8_t *)vlhs, *rhs = (const uint8_t *)vrhs; + uint64_t w; + + w = GET_64BIT_LSB_FIRST(lhs); + w ^= GET_64BIT_LSB_FIRST(rhs); + PUT_64BIT_LSB_FIRST(out, w); + w = GET_64BIT_LSB_FIRST(lhs + 8); + w ^= GET_64BIT_LSB_FIRST(rhs + 8); + PUT_64BIT_LSB_FIRST(out + 8, w); +} + +static inline void aes_cbc_sw_encrypt( + ssh_cipher *ciph, void *vblk, int blklen) +{ + aes_sw_context *ctx = container_of(ciph, aes_sw_context, ciph); + + /* + * CBC encryption has to be done serially, because the input to + * each run of the cipher includes the output from the previous + * run. + */ + + for (uint8_t *blk = (uint8_t *)vblk, *finish = blk + blklen; + blk < finish; blk += 16) { + /* + * We use the IV array itself as the location for the + * encryption, because there's no reason not to. + */ + + /* XOR the new plaintext block into the previous cipher block */ + memxor16(ctx->iv.cbc.prevblk, ctx->iv.cbc.prevblk, blk); + + /* Run the cipher over the result, which leaves it + * conveniently already stored in ctx->iv */ + aes_sliced_e_serial( + ctx->iv.cbc.prevblk, ctx->iv.cbc.prevblk, &ctx->sk); + + /* Copy it to the output location */ + memcpy(blk, ctx->iv.cbc.prevblk, 16); + } +} + +static inline void aes_cbc_sw_decrypt( + ssh_cipher *ciph, void *vblk, int blklen) +{ + aes_sw_context *ctx = container_of(ciph, aes_sw_context, ciph); + uint8_t *blk = (uint8_t *)vblk; + + /* + * CBC decryption can run in parallel, because all the + * _ciphertext_ blocks are already available. + */ + + size_t blocks_remaining = blklen / 16; + + uint8_t data[SLICE_PARALLELISM * 16]; + /* Zeroing the data array is probably overcautious, but it avoids + * technically undefined behaviour from leaving it uninitialised + * if our very first iteration doesn't include enough cipher + * blocks to populate it fully */ + memset(data, 0, sizeof(data)); + + while (blocks_remaining > 0) { + /* Number of blocks we'll handle in this iteration. If we're + * dealing with fewer than the maximum, it doesn't matter - + * it's harmless to run the full parallel cipher function + * anyway. */ + size_t blocks = (blocks_remaining < SLICE_PARALLELISM ? + blocks_remaining : SLICE_PARALLELISM); + + /* Parallel-decrypt the input, in a separate array so we still + * have the cipher stream available for XORing. */ + memcpy(data, blk, 16 * blocks); + aes_sliced_d_parallel(data, data, &ctx->sk); + + /* Write the output and update the IV */ + for (size_t i = 0; i < blocks; i++) { + uint8_t *decrypted = data + 16*i; + uint8_t *output = blk + 16*i; + + memxor16(decrypted, decrypted, ctx->iv.cbc.prevblk); + memcpy(ctx->iv.cbc.prevblk, output, 16); + memcpy(output, decrypted, 16); + } + + /* Advance the input pointer. */ + blk += 16 * blocks; + blocks_remaining -= blocks; + } + + smemclr(data, sizeof(data)); +} + +static inline void aes_sdctr_sw( + ssh_cipher *ciph, void *vblk, int blklen) +{ + aes_sw_context *ctx = container_of(ciph, aes_sw_context, ciph); + + /* + * SDCTR encrypt/decrypt loops round one block at a time XORing + * the keystream into the user's data, and periodically has to run + * a parallel encryption operation to get more keystream. + */ + + uint8_t *keystream_end = + ctx->iv.sdctr.keystream + sizeof(ctx->iv.sdctr.keystream); + + for (uint8_t *blk = (uint8_t *)vblk, *finish = blk + blklen; + blk < finish; blk += 16) { + + if (ctx->iv.sdctr.keystream_pos == keystream_end) { + /* + * Generate some keystream. + */ + for (uint8_t *block = ctx->iv.sdctr.keystream; + block < keystream_end; block += 16) { + /* Format the counter value into the buffer. */ + for (unsigned i = 0; i < SDCTR_WORDS; i++) + PUT_BIGNUMINT_MSB_FIRST( + block + 16 - BIGNUM_INT_BYTES - i*BIGNUM_INT_BYTES, + ctx->iv.sdctr.counter[i]); + + /* Increment the counter. */ + BignumCarry carry = 1; + for (unsigned i = 0; i < SDCTR_WORDS; i++) + BignumADC(ctx->iv.sdctr.counter[i], carry, + ctx->iv.sdctr.counter[i], 0, carry); + } + + /* Encrypt all those counter blocks. */ + aes_sliced_e_parallel(ctx->iv.sdctr.keystream, + ctx->iv.sdctr.keystream, &ctx->sk); + + /* Reset keystream_pos to the start of the buffer. */ + ctx->iv.sdctr.keystream_pos = ctx->iv.sdctr.keystream; + } + + memxor16(blk, blk, ctx->iv.sdctr.keystream_pos); + ctx->iv.sdctr.keystream_pos += 16; + } +} + +#define SW_ENC_DEC(len) \ + static void aes##len##_cbc_sw_encrypt( \ + ssh_cipher *ciph, void *vblk, int blklen) \ + { aes_cbc_sw_encrypt(ciph, vblk, blklen); } \ + static void aes##len##_cbc_sw_decrypt( \ + ssh_cipher *ciph, void *vblk, int blklen) \ + { aes_cbc_sw_decrypt(ciph, vblk, blklen); } \ + static void aes##len##_sdctr_sw( \ + ssh_cipher *ciph, void *vblk, int blklen) \ + { aes_sdctr_sw(ciph, vblk, blklen); } + +SW_ENC_DEC(128) +SW_ENC_DEC(192) +SW_ENC_DEC(256) + +/* ---------------------------------------------------------------------- + * Hardware-accelerated implementation of AES using x86 AES-NI. + */ + +#if HW_AES == HW_AES_NI + +/* + * Set target architecture for Clang and GCC + */ +#if !defined(__clang__) && defined(__GNUC__) +# pragma GCC target("aes") +# pragma GCC target("sse4.1") +#endif + +#if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8))) +# define FUNC_ISA __attribute__ ((target("sse4.1,aes"))) +#else +# define FUNC_ISA +#endif + +#include +#include + +#if defined(__clang__) || defined(__GNUC__) +#include +#define GET_CPU_ID(out) __cpuid(1, (out)[0], (out)[1], (out)[2], (out)[3]) +#else +#define GET_CPU_ID(out) __cpuid(out, 1) +#endif + +bool aes_hw_available(void) +{ + /* + * Determine if AES is available on this CPU, by checking that + * both AES itself and SSE4.1 are supported. + */ + unsigned int CPUInfo[4]; + GET_CPU_ID(CPUInfo); + return (CPUInfo[2] & (1 << 25)) && (CPUInfo[2] & (1 << 19)); +} + +/* + * Core AES-NI encrypt/decrypt functions, one per length and direction. + */ + +#define NI_CIPHER(len, dir, dirlong, repmacro) \ + static FUNC_ISA inline __m128i aes_ni_##len##_##dir( \ + __m128i v, const __m128i *keysched) \ + { \ + v = _mm_xor_si128(v, *keysched++); \ + repmacro(v = _mm_aes##dirlong##_si128(v, *keysched++);); \ + return _mm_aes##dirlong##last_si128(v, *keysched); \ + } + +NI_CIPHER(128, e, enc, REP9) +NI_CIPHER(128, d, dec, REP9) +NI_CIPHER(192, e, enc, REP11) +NI_CIPHER(192, d, dec, REP11) +NI_CIPHER(256, e, enc, REP13) +NI_CIPHER(256, d, dec, REP13) + +/* + * The main key expansion. + */ +static FUNC_ISA void aes_ni_key_expand( + const unsigned char *key, size_t key_words, + __m128i *keysched_e, __m128i *keysched_d) +{ + size_t rounds = key_words + 6; + size_t sched_words = (rounds + 1) * 4; + + /* + * Store the key schedule as 32-bit integers during expansion, so + * that it's easy to refer back to individual previous words. We + * collect them into the final __m128i form at the end. + */ + uint32_t sched[MAXROUNDKEYS * 4]; + + unsigned rconpos = 0; + + for (size_t i = 0; i < sched_words; i++) { + if (i < key_words) { + sched[i] = GET_32BIT_LSB_FIRST(key + 4 * i); + } else { + uint32_t temp = sched[i - 1]; + + bool rotate_and_round_constant = (i % key_words == 0); + bool only_sub = (key_words == 8 && i % 8 == 4); + + if (rotate_and_round_constant) { + __m128i v = _mm_setr_epi32(0,temp,0,0); + v = _mm_aeskeygenassist_si128(v, 0); + temp = _mm_extract_epi32(v, 1); + + assert(rconpos < lenof(key_setup_round_constants)); + temp ^= key_setup_round_constants[rconpos++]; + } else if (only_sub) { + __m128i v = _mm_setr_epi32(0,temp,0,0); + v = _mm_aeskeygenassist_si128(v, 0); + temp = _mm_extract_epi32(v, 0); + } + + sched[i] = sched[i - key_words] ^ temp; + } + } + + /* + * Combine the key schedule words into __m128i vectors and store + * them in the output context. + */ + for (size_t round = 0; round <= rounds; round++) + keysched_e[round] = _mm_setr_epi32( + sched[4*round ], sched[4*round+1], + sched[4*round+2], sched[4*round+3]); + + smemclr(sched, sizeof(sched)); + + /* + * Now prepare the modified keys for the inverse cipher. + */ + for (size_t eround = 0; eround <= rounds; eround++) { + size_t dround = rounds - eround; + __m128i rkey = keysched_e[eround]; + if (eround && dround) /* neither first nor last */ + rkey = _mm_aesimc_si128(rkey); + keysched_d[dround] = rkey; + } +} + +/* + * Auxiliary routine to increment the 128-bit counter used in SDCTR + * mode. + */ +static FUNC_ISA inline __m128i aes_ni_sdctr_increment(__m128i v) +{ + const __m128i ONE = _mm_setr_epi32(1,0,0,0); + const __m128i ZERO = _mm_setzero_si128(); + + /* Increment the low-order 64 bits of v */ + v = _mm_add_epi64(v, ONE); + /* Check if they've become zero */ + __m128i cmp = _mm_cmpeq_epi64(v, ZERO); + /* If so, the low half of cmp is all 1s. Pack that into the high + * half of addend with zero in the low half. */ + __m128i addend = _mm_unpacklo_epi64(ZERO, cmp); + /* And subtract that from v, which increments the high 64 bits iff + * the low 64 wrapped round. */ + v = _mm_sub_epi64(v, addend); + + return v; +} + +/* + * Auxiliary routine to reverse the byte order of a vector, so that + * the SDCTR IV can be made big-endian for feeding to the cipher. + */ +static FUNC_ISA inline __m128i aes_ni_sdctr_reverse(__m128i v) +{ + v = _mm_shuffle_epi8( + v, _mm_setr_epi8(15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0)); + return v; +} + +/* + * The SSH interface and the cipher modes. + */ + +typedef struct aes_ni_context aes_ni_context; +struct aes_ni_context { + __m128i keysched_e[MAXROUNDKEYS], keysched_d[MAXROUNDKEYS], iv; + + void *pointer_to_free; + ssh_cipher ciph; +}; + +static ssh_cipher *aes_hw_new(const ssh_cipheralg *alg) +{ + if (!aes_hw_available_cached()) + return NULL; + + /* + * The __m128i variables in the context structure need to be + * 16-byte aligned, but not all malloc implementations that this + * code has to work with will guarantee to return a 16-byte + * aligned pointer. So we over-allocate, manually realign the + * pointer ourselves, and store the original one inside the + * context so we know how to free it later. + */ + void *allocation = smalloc(sizeof(aes_ni_context) + 15); + uintptr_t alloc_address = (uintptr_t)allocation; + uintptr_t aligned_address = (alloc_address + 15) & ~15; + aes_ni_context *ctx = (aes_ni_context *)aligned_address; + + ctx->ciph.vt = alg; + ctx->pointer_to_free = allocation; + return &ctx->ciph; +} + +static void aes_hw_free(ssh_cipher *ciph) +{ + aes_ni_context *ctx = container_of(ciph, aes_ni_context, ciph); + void *allocation = ctx->pointer_to_free; + smemclr(ctx, sizeof(*ctx)); + sfree(allocation); +} + +static void aes_hw_setkey(ssh_cipher *ciph, const void *vkey) +{ + aes_ni_context *ctx = container_of(ciph, aes_ni_context, ciph); + const unsigned char *key = (const unsigned char *)vkey; + + aes_ni_key_expand(key, ctx->ciph.vt->real_keybits / 32, + ctx->keysched_e, ctx->keysched_d); +} + +static FUNC_ISA void aes_hw_setiv_cbc(ssh_cipher *ciph, const void *iv) +{ + aes_ni_context *ctx = container_of(ciph, aes_ni_context, ciph); + ctx->iv = _mm_loadu_si128(iv); +} + +static FUNC_ISA void aes_hw_setiv_sdctr(ssh_cipher *ciph, const void *iv) +{ + aes_ni_context *ctx = container_of(ciph, aes_ni_context, ciph); + __m128i counter = _mm_loadu_si128(iv); + ctx->iv = aes_ni_sdctr_reverse(counter); +} + +typedef __m128i (*aes_ni_fn)(__m128i v, const __m128i *keysched); + +static FUNC_ISA inline void aes_cbc_ni_encrypt( + ssh_cipher *ciph, void *vblk, int blklen, aes_ni_fn encrypt) +{ + aes_ni_context *ctx = container_of(ciph, aes_ni_context, ciph); + + for (uint8_t *blk = (uint8_t *)vblk, *finish = blk + blklen; + blk < finish; blk += 16) { + __m128i plaintext = _mm_loadu_si128((const __m128i *)blk); + __m128i cipher_input = _mm_xor_si128(plaintext, ctx->iv); + __m128i ciphertext = encrypt(cipher_input, ctx->keysched_e); + _mm_storeu_si128((__m128i *)blk, ciphertext); + ctx->iv = ciphertext; + } +} + +static FUNC_ISA inline void aes_cbc_ni_decrypt( + ssh_cipher *ciph, void *vblk, int blklen, aes_ni_fn decrypt) +{ + aes_ni_context *ctx = container_of(ciph, aes_ni_context, ciph); + + for (uint8_t *blk = (uint8_t *)vblk, *finish = blk + blklen; + blk < finish; blk += 16) { + __m128i ciphertext = _mm_loadu_si128((const __m128i *)blk); + __m128i decrypted = decrypt(ciphertext, ctx->keysched_d); + __m128i plaintext = _mm_xor_si128(decrypted, ctx->iv); + _mm_storeu_si128((__m128i *)blk, plaintext); + ctx->iv = ciphertext; + } +} + +static FUNC_ISA inline void aes_sdctr_ni( + ssh_cipher *ciph, void *vblk, int blklen, aes_ni_fn encrypt) +{ + aes_ni_context *ctx = container_of(ciph, aes_ni_context, ciph); + + for (uint8_t *blk = (uint8_t *)vblk, *finish = blk + blklen; + blk < finish; blk += 16) { + __m128i counter = aes_ni_sdctr_reverse(ctx->iv); + __m128i keystream = encrypt(counter, ctx->keysched_e); + __m128i input = _mm_loadu_si128((const __m128i *)blk); + __m128i output = _mm_xor_si128(input, keystream); + _mm_storeu_si128((__m128i *)blk, output); + ctx->iv = aes_ni_sdctr_increment(ctx->iv); + } +} + +#define NI_ENC_DEC(len) \ + static FUNC_ISA void aes##len##_cbc_hw_encrypt( \ + ssh_cipher *ciph, void *vblk, int blklen) \ + { aes_cbc_ni_encrypt(ciph, vblk, blklen, aes_ni_##len##_e); } \ + static FUNC_ISA void aes##len##_cbc_hw_decrypt( \ + ssh_cipher *ciph, void *vblk, int blklen) \ + { aes_cbc_ni_decrypt(ciph, vblk, blklen, aes_ni_##len##_d); } \ + static FUNC_ISA void aes##len##_sdctr_hw( \ + ssh_cipher *ciph, void *vblk, int blklen) \ + { aes_sdctr_ni(ciph, vblk, blklen, aes_ni_##len##_e); } \ + +NI_ENC_DEC(128) +NI_ENC_DEC(192) +NI_ENC_DEC(256) + +/* ---------------------------------------------------------------------- + * Hardware-accelerated implementation of AES using Arm NEON. + */ + +#elif HW_AES == HW_AES_NEON + +/* + * Manually set the target architecture, if we decided above that we + * need to. + */ +#ifdef USE_CLANG_ATTR_TARGET_AARCH64 +/* + * A spot of cheating: redefine some ACLE feature macros before + * including arm_neon.h. Otherwise we won't get the AES intrinsics + * defined by that header, because it will be looking at the settings + * for the whole translation unit rather than the ones we're going to + * put on some particular functions using __attribute__((target)). + */ +#define __ARM_NEON 1 +#define __ARM_FEATURE_CRYPTO 1 +#define FUNC_ISA __attribute__ ((target("neon,crypto"))) +#endif /* USE_CLANG_ATTR_TARGET_AARCH64 */ + +#ifndef FUNC_ISA +#define FUNC_ISA +#endif + +#ifdef USE_ARM64_NEON_H +#include +#else +#include +#endif + +static bool aes_hw_available(void) +{ + /* + * For Arm, we delegate to a per-platform AES detection function, + * because it has to be implemented by asking the operating system + * rather than directly querying the CPU. + * + * That's because Arm systems commonly have multiple cores that + * are not all alike, so any method of querying whether NEON + * crypto instructions work on the _current_ CPU - even one as + * crude as just trying one and catching the SIGILL - wouldn't + * give an answer that you could still rely on the first time the + * OS migrated your process to another CPU. + */ + return platform_aes_hw_available(); +} + +/* + * Core NEON encrypt/decrypt functions, one per length and direction. + */ + +#define NEON_CIPHER(len, repmacro) \ + static FUNC_ISA inline uint8x16_t aes_neon_##len##_e( \ + uint8x16_t v, const uint8x16_t *keysched) \ + { \ + repmacro(v = vaesmcq_u8(vaeseq_u8(v, *keysched++));); \ + v = vaeseq_u8(v, *keysched++); \ + return veorq_u8(v, *keysched); \ + } \ + static FUNC_ISA inline uint8x16_t aes_neon_##len##_d( \ + uint8x16_t v, const uint8x16_t *keysched) \ + { \ + repmacro(v = vaesimcq_u8(vaesdq_u8(v, *keysched++));); \ + v = vaesdq_u8(v, *keysched++); \ + return veorq_u8(v, *keysched); \ + } + +NEON_CIPHER(128, REP9) +NEON_CIPHER(192, REP11) +NEON_CIPHER(256, REP13) + +/* + * The main key expansion. + */ +static FUNC_ISA void aes_neon_key_expand( + const unsigned char *key, size_t key_words, + uint8x16_t *keysched_e, uint8x16_t *keysched_d) +{ + size_t rounds = key_words + 6; + size_t sched_words = (rounds + 1) * 4; + + /* + * Store the key schedule as 32-bit integers during expansion, so + * that it's easy to refer back to individual previous words. We + * collect them into the final uint8x16_t form at the end. + */ + uint32_t sched[MAXROUNDKEYS * 4]; + + unsigned rconpos = 0; + + for (size_t i = 0; i < sched_words; i++) { + if (i < key_words) { + sched[i] = GET_32BIT_LSB_FIRST(key + 4 * i); + } else { + uint32_t temp = sched[i - 1]; + + bool rotate_and_round_constant = (i % key_words == 0); + bool sub = rotate_and_round_constant || + (key_words == 8 && i % 8 == 4); + + if (rotate_and_round_constant) + temp = (temp << 24) | (temp >> 8); + + if (sub) { + uint32x4_t v32 = vdupq_n_u32(temp); + uint8x16_t v8 = vreinterpretq_u8_u32(v32); + v8 = vaeseq_u8(v8, vdupq_n_u8(0)); + v32 = vreinterpretq_u32_u8(v8); + temp = vget_lane_u32(vget_low_u32(v32), 0); + } + + if (rotate_and_round_constant) { + assert(rconpos < lenof(key_setup_round_constants)); + temp ^= key_setup_round_constants[rconpos++]; + } + + sched[i] = sched[i - key_words] ^ temp; + } + } + + /* + * Combine the key schedule words into uint8x16_t vectors and + * store them in the output context. + */ + for (size_t round = 0; round <= rounds; round++) + keysched_e[round] = vreinterpretq_u8_u32(vld1q_u32(sched + 4*round)); + + smemclr(sched, sizeof(sched)); + + /* + * Now prepare the modified keys for the inverse cipher. + */ + for (size_t eround = 0; eround <= rounds; eround++) { + size_t dround = rounds - eround; + uint8x16_t rkey = keysched_e[eround]; + if (eround && dround) /* neither first nor last */ + rkey = vaesimcq_u8(rkey); + keysched_d[dround] = rkey; + } +} + +/* + * Auxiliary routine to reverse the byte order of a vector, so that + * the SDCTR IV can be made big-endian for feeding to the cipher. + * + * In fact we don't need to reverse the vector _all_ the way; we leave + * the two lanes in MSW,LSW order, because that makes no difference to + * the efficiency of the increment. That way we only have to reverse + * bytes within each lane in this function. + */ +static FUNC_ISA inline uint8x16_t aes_neon_sdctr_reverse(uint8x16_t v) +{ + return vrev64q_u8(v); +} + +/* + * Auxiliary routine to increment the 128-bit counter used in SDCTR + * mode. There's no instruction to treat a 128-bit vector as a single + * long integer, so instead we have to increment the bottom half + * unconditionally, and the top half if the bottom half started off as + * all 1s (in which case there was about to be a carry). + */ +static FUNC_ISA inline uint8x16_t aes_neon_sdctr_increment(uint8x16_t in) +{ +#ifdef __aarch64__ + /* There will be a carry if the low 64 bits are all 1s. */ + uint64x1_t all1 = vcreate_u64(0xFFFFFFFFFFFFFFFF); + uint64x1_t carry = vceq_u64(vget_high_u64(vreinterpretq_u64_u8(in)), all1); + + /* Make a word whose bottom half is unconditionally all 1s, and + * the top half is 'carry', i.e. all 0s most of the time but all + * 1s if we need to increment the top half. Then that word is what + * we need to _subtract_ from the input counter. */ + uint64x2_t subtrahend = vcombine_u64(carry, all1); +#else + /* AArch32 doesn't have comparisons that operate on a 64-bit lane, + * so we start by comparing each 32-bit half of the low 64 bits + * _separately_ to all-1s. */ + uint32x2_t all1 = vdup_n_u32(0xFFFFFFFF); + uint32x2_t carry = vceq_u32( + vget_high_u32(vreinterpretq_u32_u8(in)), all1); + + /* Swap the 32-bit words of the compare output, and AND with the + * unswapped version. Now carry is all 1s iff the bottom half of + * the input counter was all 1s, and all 0s otherwise. */ + carry = vand_u32(carry, vrev64_u32(carry)); + + /* Now make the vector to subtract in the same way as above. */ + uint64x2_t subtrahend = vreinterpretq_u64_u32(vcombine_u32(carry, all1)); +#endif + + return vreinterpretq_u8_u64( + vsubq_u64(vreinterpretq_u64_u8(in), subtrahend)); +} + +/* + * The SSH interface and the cipher modes. + */ + +typedef struct aes_neon_context aes_neon_context; +struct aes_neon_context { + uint8x16_t keysched_e[MAXROUNDKEYS], keysched_d[MAXROUNDKEYS], iv; + + ssh_cipher ciph; +}; + +static ssh_cipher *aes_hw_new(const ssh_cipheralg *alg) +{ + if (!aes_hw_available_cached()) + return NULL; + + aes_neon_context *ctx = snew(aes_neon_context); + ctx->ciph.vt = alg; + return &ctx->ciph; +} + +static void aes_hw_free(ssh_cipher *ciph) +{ + aes_neon_context *ctx = container_of(ciph, aes_neon_context, ciph); + smemclr(ctx, sizeof(*ctx)); + sfree(ctx); +} + +static void aes_hw_setkey(ssh_cipher *ciph, const void *vkey) +{ + aes_neon_context *ctx = container_of(ciph, aes_neon_context, ciph); + const unsigned char *key = (const unsigned char *)vkey; + + aes_neon_key_expand(key, ctx->ciph.vt->real_keybits / 32, + ctx->keysched_e, ctx->keysched_d); +} + +static FUNC_ISA void aes_hw_setiv_cbc(ssh_cipher *ciph, const void *iv) +{ + aes_neon_context *ctx = container_of(ciph, aes_neon_context, ciph); + ctx->iv = vld1q_u8(iv); +} + +static FUNC_ISA void aes_hw_setiv_sdctr(ssh_cipher *ciph, const void *iv) +{ + aes_neon_context *ctx = container_of(ciph, aes_neon_context, ciph); + uint8x16_t counter = vld1q_u8(iv); + ctx->iv = aes_neon_sdctr_reverse(counter); +} + +typedef uint8x16_t (*aes_neon_fn)(uint8x16_t v, const uint8x16_t *keysched); + +static FUNC_ISA inline void aes_cbc_neon_encrypt( + ssh_cipher *ciph, void *vblk, int blklen, aes_neon_fn encrypt) +{ + aes_neon_context *ctx = container_of(ciph, aes_neon_context, ciph); + + for (uint8_t *blk = (uint8_t *)vblk, *finish = blk + blklen; + blk < finish; blk += 16) { + uint8x16_t plaintext = vld1q_u8(blk); + uint8x16_t cipher_input = veorq_u8(plaintext, ctx->iv); + uint8x16_t ciphertext = encrypt(cipher_input, ctx->keysched_e); + vst1q_u8(blk, ciphertext); + ctx->iv = ciphertext; + } +} + +static FUNC_ISA inline void aes_cbc_neon_decrypt( + ssh_cipher *ciph, void *vblk, int blklen, aes_neon_fn decrypt) +{ + aes_neon_context *ctx = container_of(ciph, aes_neon_context, ciph); + + for (uint8_t *blk = (uint8_t *)vblk, *finish = blk + blklen; + blk < finish; blk += 16) { + uint8x16_t ciphertext = vld1q_u8(blk); + uint8x16_t decrypted = decrypt(ciphertext, ctx->keysched_d); + uint8x16_t plaintext = veorq_u8(decrypted, ctx->iv); + vst1q_u8(blk, plaintext); + ctx->iv = ciphertext; + } +} + +static FUNC_ISA inline void aes_sdctr_neon( + ssh_cipher *ciph, void *vblk, int blklen, aes_neon_fn encrypt) +{ + aes_neon_context *ctx = container_of(ciph, aes_neon_context, ciph); + + for (uint8_t *blk = (uint8_t *)vblk, *finish = blk + blklen; + blk < finish; blk += 16) { + uint8x16_t counter = aes_neon_sdctr_reverse(ctx->iv); + uint8x16_t keystream = encrypt(counter, ctx->keysched_e); + uint8x16_t input = vld1q_u8(blk); + uint8x16_t output = veorq_u8(input, keystream); + vst1q_u8(blk, output); + ctx->iv = aes_neon_sdctr_increment(ctx->iv); + } +} + +#define NEON_ENC_DEC(len) \ + static FUNC_ISA void aes##len##_cbc_hw_encrypt( \ + ssh_cipher *ciph, void *vblk, int blklen) \ + { aes_cbc_neon_encrypt(ciph, vblk, blklen, aes_neon_##len##_e); } \ + static FUNC_ISA void aes##len##_cbc_hw_decrypt( \ + ssh_cipher *ciph, void *vblk, int blklen) \ + { aes_cbc_neon_decrypt(ciph, vblk, blklen, aes_neon_##len##_d); } \ + static FUNC_ISA void aes##len##_sdctr_hw( \ + ssh_cipher *ciph, void *vblk, int blklen) \ + { aes_sdctr_neon(ciph, vblk, blklen, aes_neon_##len##_e); } \ + +NEON_ENC_DEC(128) +NEON_ENC_DEC(192) +NEON_ENC_DEC(256) + +/* ---------------------------------------------------------------------- + * Stub functions if we have no hardware-accelerated AES. In this + * case, aes_hw_new returns NULL (though it should also never be + * selected by aes_select, so the only thing that should even be + * _able_ to call it is testcrypt). As a result, the remaining vtable + * functions should never be called at all. + */ + +#elif HW_AES == HW_AES_NONE + +bool aes_hw_available(void) +{ + return false; +} + +static ssh_cipher *aes_hw_new(const ssh_cipheralg *alg) +{ + return NULL; +} + +#define STUB_BODY { unreachable("Should never be called"); } + +static void aes_hw_free(ssh_cipher *ciph) STUB_BODY +static void aes_hw_setkey(ssh_cipher *ciph, const void *key) STUB_BODY +static void aes_hw_setiv_cbc(ssh_cipher *ciph, const void *iv) STUB_BODY +static void aes_hw_setiv_sdctr(ssh_cipher *ciph, const void *iv) STUB_BODY +#define STUB_ENC_DEC(len) \ + static void aes##len##_cbc_hw_encrypt( \ + ssh_cipher *ciph, void *vblk, int blklen) STUB_BODY \ + static void aes##len##_cbc_hw_decrypt( \ + ssh_cipher *ciph, void *vblk, int blklen) STUB_BODY \ + static void aes##len##_sdctr_hw( \ + ssh_cipher *ciph, void *vblk, int blklen) STUB_BODY + +STUB_ENC_DEC(128) +STUB_ENC_DEC(192) +STUB_ENC_DEC(256) + +#endif /* HW_AES */ diff --git a/crypto/arcfour.c b/crypto/arcfour.c new file mode 100644 index 00000000..53821473 --- /dev/null +++ b/crypto/arcfour.c @@ -0,0 +1,141 @@ +/* + * Arcfour (RC4) implementation for PuTTY. + * + * Coded from Schneier. + */ + +#include +#include "ssh.h" + +typedef struct { + unsigned char i, j, s[256]; + ssh_cipher ciph; +} ArcfourContext; + +static void arcfour_block(void *handle, void *vblk, int len) +{ + unsigned char *blk = (unsigned char *)vblk; + 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 ssh_cipher *arcfour_new(const ssh_cipheralg *alg) +{ + ArcfourContext *ctx = snew(ArcfourContext); + ctx->ciph.vt = alg; + return &ctx->ciph; +} + +static void arcfour_free(ssh_cipher *cipher) +{ + ArcfourContext *ctx = container_of(cipher, ArcfourContext, ciph); + smemclr(ctx, sizeof(*ctx)); + sfree(ctx); +} + +static void arcfour_stir(ArcfourContext *ctx) +{ + unsigned char *junk = snewn(1536, unsigned char); + memset(junk, 0, 1536); + arcfour_block(ctx, junk, 1536); + smemclr(junk, 1536); + sfree(junk); +} + +static void arcfour_ssh2_setiv(ssh_cipher *cipher, const void *key) +{ + /* As a pure stream cipher, Arcfour has no IV separate from the key */ +} + +static void arcfour_ssh2_setkey(ssh_cipher *cipher, const void *key) +{ + ArcfourContext *ctx = container_of(cipher, ArcfourContext, ciph); + arcfour_setkey(ctx, key, ctx->ciph.vt->padded_keybytes); + arcfour_stir(ctx); +} + +static void arcfour_ssh2_block(ssh_cipher *cipher, void *blk, int len) +{ + ArcfourContext *ctx = container_of(cipher, ArcfourContext, ciph); + arcfour_block(ctx, blk, len); +} + +const ssh_cipheralg ssh_arcfour128_ssh2 = { + .new = arcfour_new, + .free = arcfour_free, + .setiv = arcfour_ssh2_setiv, + .setkey = arcfour_ssh2_setkey, + .encrypt = arcfour_ssh2_block, + .decrypt = arcfour_ssh2_block, + .ssh2_id = "arcfour128", + .blksize = 1, + .real_keybits = 128, + .padded_keybytes = 16, + .flags = 0, + .text_name = "Arcfour-128", +}; + +const ssh_cipheralg ssh_arcfour256_ssh2 = { + .new = arcfour_new, + .free = arcfour_free, + .setiv = arcfour_ssh2_setiv, + .setkey = arcfour_ssh2_setkey, + .encrypt = arcfour_ssh2_block, + .decrypt = arcfour_ssh2_block, + .ssh2_id = "arcfour256", + .blksize = 1, + .real_keybits = 256, + .padded_keybytes = 32, + .flags = 0, + .text_name = "Arcfour-256", +}; + +static const ssh_cipheralg *const arcfour_list[] = { + &ssh_arcfour256_ssh2, + &ssh_arcfour128_ssh2, +}; + +const ssh2_ciphers ssh2_arcfour = { lenof(arcfour_list), arcfour_list }; diff --git a/crypto/argon2.c b/crypto/argon2.c new file mode 100644 index 00000000..25385d7e --- /dev/null +++ b/crypto/argon2.c @@ -0,0 +1,565 @@ +/* + * Implementation of the Argon2 password hash function. + * + * My sources for the algorithm description and test vectors (the latter in + * test/cryptsuite.py) were the reference implementation on Github, and also + * the Internet-Draft description: + * + * https://github.com/P-H-C/phc-winner-argon2 + * https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-argon2-13 + */ + +#include + +#include "putty.h" +#include "ssh.h" +#include "marshal.h" + +/* ---------------------------------------------------------------------- + * Argon2 uses data marshalling rules similar to SSH but with 32-bit integers + * stored little-endian. Start with some local BinarySink routines for storing + * a uint32 and a string in that fashion. + */ + +static void BinarySink_put_uint32_le(BinarySink *bs, unsigned long val) +{ + unsigned char data[4]; + PUT_32BIT_LSB_FIRST(data, val); + bs->write(bs, data, sizeof(data)); +} + +static void BinarySink_put_stringpl_le(BinarySink *bs, ptrlen pl) +{ + /* Check that the string length fits in a uint32, without doing a + * potentially implementation-defined shift of more than 31 bits */ + assert((pl.len >> 31) < 2); + + BinarySink_put_uint32_le(bs, pl.len); + bs->write(bs, pl.ptr, pl.len); +} + +#define put_uint32_le(bs, val) \ + BinarySink_put_uint32_le(BinarySink_UPCAST(bs), val) +#define put_stringpl_le(bs, val) \ + BinarySink_put_stringpl_le(BinarySink_UPCAST(bs), val) + +/* ---------------------------------------------------------------------- + * Argon2 defines a hash-function family that's an extension of BLAKE2b to + * generate longer output digests, by repeatedly outputting half of a BLAKE2 + * hash output and then re-hashing the whole thing until there are 64 or fewer + * bytes left to output. The spec calls this H' (a variant of the original + * hash it calls H, which is the unmodified BLAKE2b). + */ + +static ssh_hash *hprime_new(unsigned length) +{ + ssh_hash *h = blake2b_new_general(length > 64 ? 64 : length); + put_uint32_le(h, length); + return h; +} + +static void hprime_final(ssh_hash *h, unsigned length, void *vout) +{ + uint8_t *out = (uint8_t *)vout; + + while (length > 64) { + uint8_t hashbuf[64]; + ssh_hash_final(h, hashbuf); + + memcpy(out, hashbuf, 32); + out += 32; + length -= 32; + + h = blake2b_new_general(length > 64 ? 64 : length); + put_data(h, hashbuf, 64); + + smemclr(hashbuf, sizeof(hashbuf)); + } + + ssh_hash_final(h, out); +} + +/* Externally visible entry point for the long hash function. This is only + * used by testcrypt, so it would be overkill to set it up like a proper + * ssh_hash. */ +strbuf *argon2_long_hash(unsigned length, ptrlen data) +{ + ssh_hash *h = hprime_new(length); + put_datapl(h, data); + strbuf *out = strbuf_new(); + hprime_final(h, length, strbuf_append(out, length)); + return out; +} + +/* ---------------------------------------------------------------------- + * Argon2's own mixing function G, which operates on 1Kb blocks of data. + * + * The definition of G in the spec takes two 1Kb blocks as input and produces + * a 1Kb output block. The first thing that happens to the input blocks is + * that they get XORed together, and then only the XOR output is used, so you + * could perfectly well regard G as a 1Kb->1Kb function. + */ + +static inline uint64_t ror(uint64_t x, unsigned rotation) +{ + unsigned lshift = 63 & -rotation, rshift = 63 & rotation; + return (x << lshift) | (x >> rshift); +} + +static inline uint64_t trunc32(uint64_t x) +{ + return x & 0xFFFFFFFF; +} + +/* Internal function similar to the BLAKE2b round, which mixes up four 64-bit + * words */ +static inline void GB(uint64_t *a, uint64_t *b, uint64_t *c, uint64_t *d) +{ + *a += *b + 2 * trunc32(*a) * trunc32(*b); + *d = ror(*d ^ *a, 32); + *c += *d + 2 * trunc32(*c) * trunc32(*d); + *b = ror(*b ^ *c, 24); + *a += *b + 2 * trunc32(*a) * trunc32(*b); + *d = ror(*d ^ *a, 16); + *c += *d + 2 * trunc32(*c) * trunc32(*d); + *b = ror(*b ^ *c, 63); +} + +/* Higher-level internal function which mixes up sixteen 64-bit words. This is + * applied to different subsets of the 128 words in a kilobyte block, and the + * API here is designed to make it easy to apply in the circumstances the spec + * requires. In every call, the sixteen words form eight pairs adjacent in + * memory, whose addresses are in arithmetic progression. So the 16 input + * words are in[0], in[1], in[instep], in[instep+1], ..., in[7*instep], + * in[7*instep+1], and the 16 output words similarly. */ +static inline void P(uint64_t *out, unsigned outstep, + uint64_t *in, unsigned instep) +{ + for (unsigned i = 0; i < 8; i++) { + out[i*outstep] = in[i*instep]; + out[i*outstep+1] = in[i*instep+1]; + } + + GB(out+0*outstep+0, out+2*outstep+0, out+4*outstep+0, out+6*outstep+0); + GB(out+0*outstep+1, out+2*outstep+1, out+4*outstep+1, out+6*outstep+1); + GB(out+1*outstep+0, out+3*outstep+0, out+5*outstep+0, out+7*outstep+0); + GB(out+1*outstep+1, out+3*outstep+1, out+5*outstep+1, out+7*outstep+1); + + GB(out+0*outstep+0, out+2*outstep+1, out+5*outstep+0, out+7*outstep+1); + GB(out+0*outstep+1, out+3*outstep+0, out+5*outstep+1, out+6*outstep+0); + GB(out+1*outstep+0, out+3*outstep+1, out+4*outstep+0, out+6*outstep+1); + GB(out+1*outstep+1, out+2*outstep+0, out+4*outstep+1, out+7*outstep+0); +} + +/* The full G function, taking input blocks X and Y. The result of G is most + * often XORed into an existing output block, so this API is designed with + * that in mind: the mixing function's output is always XORed into whatever + * 1Kb of data is already at 'out'. */ +static void G_xor(uint8_t *out, const uint8_t *X, const uint8_t *Y) +{ + uint64_t R[128], Q[128], Z[128]; + + for (unsigned i = 0; i < 128; i++) + R[i] = GET_64BIT_LSB_FIRST(X + 8*i) ^ GET_64BIT_LSB_FIRST(Y + 8*i); + + for (unsigned i = 0; i < 8; i++) + P(Q+16*i, 2, R+16*i, 2); + + for (unsigned i = 0; i < 8; i++) + P(Z+2*i, 16, Q+2*i, 16); + + for (unsigned i = 0; i < 128; i++) + PUT_64BIT_LSB_FIRST(out + 8*i, + GET_64BIT_LSB_FIRST(out + 8*i) ^ R[i] ^ Z[i]); + + smemclr(R, sizeof(R)); + smemclr(Q, sizeof(Q)); + smemclr(Z, sizeof(Z)); +} + +/* ---------------------------------------------------------------------- + * The main Argon2 function. + */ + +static void argon2_internal(uint32_t p, uint32_t T, uint32_t m, uint32_t t, + uint32_t y, ptrlen P, ptrlen S, ptrlen K, ptrlen X, + uint8_t *out) +{ + /* + * Start by hashing all the input data together: the four string arguments + * (password P, salt S, optional secret key K, optional associated data + * X), plus all the parameters for the function's memory and time usage. + * + * The output of this hash is the sole input to the subsequent mixing + * step: Argon2 does not preserve any more entropy from the inputs, it + * just makes it extra painful to get the final answer. + */ + uint8_t h0[64]; + { + ssh_hash *h = blake2b_new_general(64); + put_uint32_le(h, p); + put_uint32_le(h, T); + put_uint32_le(h, m); + put_uint32_le(h, t); + put_uint32_le(h, 0x13); /* hash function version number */ + put_uint32_le(h, y); + put_stringpl_le(h, P); + put_stringpl_le(h, S); + put_stringpl_le(h, K); + put_stringpl_le(h, X); + ssh_hash_final(h, h0); + } + + struct blk { uint8_t data[1024]; }; + + /* + * Array of 1Kb blocks. The total size is (approximately) m, the + * caller-specified parameter for how much memory to use; the blocks are + * regarded as a rectangular array of p rows ('lanes') by q columns, where + * p is the 'parallelism' input parameter (the lanes can be processed + * concurrently up to a point) and q is whatever makes the product pq come + * to m. + * + * Additionally, each row is divided into four equal 'segments', which are + * important to the way the algorithm decides which blocks to use as input + * to each step of the function. + * + * The term 'slice' refers to a whole set of vertically aligned segments, + * i.e. slice 0 is the whole left quarter of the array, and slice 3 the + * whole right quarter. + */ + size_t SL = m / (4*p); /* segment length: # of 1Kb blocks in a segment */ + size_t q = 4 * SL; /* width of the array: 4 segments times SL */ + size_t mprime = q * p; /* total size of the array, approximately m */ + + /* Allocate the memory. */ + struct blk *B = snewn(mprime, struct blk); + memset(B, 0, mprime * sizeof(struct blk)); + + /* + * Initial setup: fill the first two full columns of the array with data + * expanded from the starting hash h0. Each block is the result of using + * the long-output hash function H' to hash h0 itself plus the block's + * coordinates in the array. + */ + for (size_t i = 0; i < p; i++) { + ssh_hash *h = hprime_new(1024); + put_data(h, h0, 64); + put_uint32_le(h, 0); + put_uint32_le(h, i); + hprime_final(h, 1024, B[i].data); + } + for (size_t i = 0; i < p; i++) { + ssh_hash *h = hprime_new(1024); + put_data(h, h0, 64); + put_uint32_le(h, 1); + put_uint32_le(h, i); + hprime_final(h, 1024, B[i+p].data); + } + + /* + * Declarations for the main loop. + * + * The basic structure of the main loop is going to involve processing the + * array one whole slice (vertically divided quarter) at a time. Usually + * we'll write a new value into every single block in the slice, except + * that in the initial slice on the first pass, we've already written + * values into the first two columns during the initial setup above. So + * 'jstart' indicates the starting index in each segment we process; it + * starts off as 2 so that we don't overwrite the inital setup, and then + * after the first slice is done, we set it to 0, and it stays there. + * + * d_mode indicates whether we're being data-dependent (true) or + * data-independent (false). In the hybrid Argon2id mode, we start off + * independent, and then once we've mixed things up enough, switch over to + * dependent mode to force long serial chains of computation. + */ + size_t jstart = 2; + bool d_mode = (y == 0); + struct blk out2i, tmp2i, in2i; + + /* Outermost loop: t whole passes from left to right over the array */ + for (size_t pass = 0; pass < t; pass++) { + + /* Within that, we process the array in its four main slices */ + for (unsigned slice = 0; slice < 4; slice++) { + + /* In Argon2id mode, if we're half way through the first pass, + * this is the moment to switch d_mode from false to true */ + if (pass == 0 && slice == 2 && y == 2) + d_mode = true; + + /* Loop over every segment in the slice (i.e. every row). So i is + * the y-coordinate of each block we process. */ + for (size_t i = 0; i < p; i++) { + + /* And within that segment, process the blocks from left to + * right, starting at 'jstart' (usually 0, but 2 in the first + * slice). */ + for (size_t jpre = jstart; jpre < SL; jpre++) { + + /* j is the x-coordinate of each block we process, made up + * of the slice number and the index 'jpre' within the + * segment. */ + size_t j = slice * SL + jpre; + + /* jm1 is j-1 (mod q) */ + uint32_t jm1 = (j == 0 ? q-1 : j-1); + + /* + * Construct two 32-bit pseudorandom integers J1 and J2. + * This is the part of the algorithm that varies between + * the data-dependent and independent modes. + */ + uint32_t J1, J2; + if (d_mode) { + /* + * Data-dependent: grab the first 64 bits of the block + * to the left of this one. + */ + J1 = GET_32BIT_LSB_FIRST(B[i + p * jm1].data); + J2 = GET_32BIT_LSB_FIRST(B[i + p * jm1].data + 4); + } else { + /* + * Data-independent: generate pseudorandom data by + * hashing a sequence of preimage blocks that include + * all our input parameters, plus the coordinates of + * this point in the algorithm (array position and + * pass number) to make all the hash outputs distinct. + * + * The hash we use is G itself, applied twice. So we + * generate 1Kb of data at a time, which is enough for + * 128 (J1,J2) pairs. Hence we only need to do the + * hashing if our index within the segment is a + * multiple of 128, or if we're at the very start of + * the algorithm (in which case we started at 2 rather + * than 0). After that we can just keep picking data + * out of our most recent hash output. + */ + if (jpre == jstart || jpre % 128 == 0) { + /* + * Hash preimage is mostly zeroes, with a + * collection of assorted integer values we had + * anyway. + */ + memset(in2i.data, 0, sizeof(in2i.data)); + PUT_64BIT_LSB_FIRST(in2i.data + 0, pass); + PUT_64BIT_LSB_FIRST(in2i.data + 8, i); + PUT_64BIT_LSB_FIRST(in2i.data + 16, slice); + PUT_64BIT_LSB_FIRST(in2i.data + 24, mprime); + PUT_64BIT_LSB_FIRST(in2i.data + 32, t); + PUT_64BIT_LSB_FIRST(in2i.data + 40, y); + PUT_64BIT_LSB_FIRST(in2i.data + 48, jpre / 128 + 1); + + /* + * Now apply G twice to generate the hash output + * in out2i. + */ + memset(tmp2i.data, 0, sizeof(tmp2i.data)); + G_xor(tmp2i.data, tmp2i.data, in2i.data); + memset(out2i.data, 0, sizeof(out2i.data)); + G_xor(out2i.data, out2i.data, tmp2i.data); + } + + /* + * Extract J1 and J2 from the most recent hash output + * (whether we've just computed it or not). + */ + J1 = GET_32BIT_LSB_FIRST( + out2i.data + 8 * (jpre % 128)); + J2 = GET_32BIT_LSB_FIRST( + out2i.data + 8 * (jpre % 128) + 4); + } + + /* + * Now convert J1 and J2 into the index of an existing + * block of the array to use as input to this step. This + * is fairly fiddly. + * + * The easy part: the y-coordinate of the input block is + * obtained by reducing J2 mod p, except that at the very + * start of the algorithm (processing the first slice on + * the first pass) we simply use the same y-coordinate as + * our output block. + * + * Note that it's safe to use the ordinary % operator + * here, without any concern for timing side channels: in + * data-independent mode J2 is not correlated to any + * secrets, and in data-dependent mode we're going to be + * giving away side-channel data _anyway_ when we use it + * as an array index (and by assumption we don't care, + * because it's already massively randomised from the real + * inputs). + */ + uint32_t index_l = (pass == 0 && slice == 0) ? i : J2 % p; + + /* + * The hard part: which block in this array row do we use? + * + * First, we decide what the possible candidates are. This + * requires some case analysis, and depends on whether the + * array row is the same one we're writing into or not. + * + * If it's not the same row: we can't use any block from + * the current slice (because the segments within a slice + * have to be processable in parallel, so in a concurrent + * implementation those blocks are potentially in the + * process of being overwritten by other threads). But the + * other three slices are fair game, except that in the + * first pass, slices to the right of us won't have had + * any values written into them yet at all. + * + * If it is the same row, we _are_ allowed to use blocks + * from the current slice, but only the ones before our + * current position. + * + * In both cases, we also exclude the individual _column_ + * just to the left of the current one. (The block + * immediately to our left is going to be the _other_ + * input to G, but the spec also says that we avoid that + * column even in a different row.) + * + * All of this means that we end up choosing from a + * cyclically contiguous interval of blocks within this + * lane, but the start and end points require some thought + * to get them right. + */ + + /* Start position is the beginning of the _next_ slice + * (containing data from the previous pass), unless we're + * on pass 0, where the start position has to be 0. */ + uint32_t Wstart = (pass == 0 ? 0 : (slice + 1) % 4 * SL); + + /* End position splits up by cases. */ + uint32_t Wend; + if (index_l == i) { + /* Same lane as output: we can use anything up to (but + * not including) the block immediately left of us. */ + Wend = jm1; + } else { + /* Different lane from output: we can use anything up + * to the previous slice boundary, or one less than + * that if we're at the very left edge of our slice + * right now. */ + Wend = SL * slice; + if (jpre == 0) + Wend = (Wend + q-1) % q; + } + + /* Total number of blocks available to choose from */ + uint32_t Wsize = (Wend + q - Wstart) % q; + + /* Fiddly computation from the spec that chooses from the + * available blocks, in a deliberately non-uniform + * fashion, using J1 as pseudorandom input data. Output is + * zz which is the index within our contiguous interval. */ + uint32_t x = ((uint64_t)J1 * J1) >> 32; + uint32_t y = ((uint64_t)Wsize * x) >> 32; + uint32_t zz = Wsize - 1 - y; + + /* And index_z is the actual x coordinate of the block we + * want. */ + uint32_t index_z = (Wstart + zz) % q; + + /* Phew! Combine that block with the one immediately to + * our left, and XOR over the top of whatever is already + * in our current output block. */ + G_xor(B[i + p * j].data, B[i + p * jm1].data, + B[index_l + p * index_z].data); + } + } + + /* We've finished processing a slice. Reset jstart to 0. It will + * onily _not_ have been 0 if this was pass 0 slice 0, in which + * case it still had its initial value of 2 to avoid the starting + * data. */ + jstart = 0; + } + } + + /* + * The main output is all done. Final output works by taking the XOR of + * all the blocks in the rightmost column of the array, and then using + * that as input to our long hash H'. The output of _that_ is what we + * deliver to the caller. + */ + + struct blk C = B[p * (q-1)]; + for (size_t i = 1; i < p; i++) + memxor(C.data, C.data, B[i + p * (q-1)].data, 1024); + + { + ssh_hash *h = hprime_new(T); + put_data(h, C.data, 1024); + hprime_final(h, T, out); + } + + /* + * Clean up. + */ + smemclr(out2i.data, sizeof(out2i.data)); + smemclr(tmp2i.data, sizeof(tmp2i.data)); + smemclr(in2i.data, sizeof(in2i.data)); + smemclr(C.data, sizeof(C.data)); + smemclr(B, mprime * sizeof(struct blk)); + sfree(B); +} + +/* + * Wrapper function that appends to a strbuf (which sshpubk.c will want). + */ +void argon2(Argon2Flavour flavour, uint32_t mem, uint32_t passes, + uint32_t parallel, uint32_t taglen, + ptrlen P, ptrlen S, ptrlen K, ptrlen X, strbuf *out) +{ + argon2_internal(parallel, taglen, mem, passes, flavour, + P, S, K, X, strbuf_append(out, taglen)); +} + +/* + * Wrapper function which dynamically chooses the number of passes to run in + * order to hit an approximate total amount of CPU time. Writes the result + * into 'passes'. + */ +void argon2_choose_passes( + Argon2Flavour flavour, uint32_t mem, + uint32_t milliseconds, uint32_t *passes, + uint32_t parallel, uint32_t taglen, + ptrlen P, ptrlen S, ptrlen K, ptrlen X, + strbuf *out) +{ + unsigned long desired_time = (TICKSPERSEC * milliseconds) / 1000; + + /* + * We only need the time taken to be approximately right, so we + * scale up the number of passes geometrically, which avoids + * taking O(t^2) time to find a pass count taking time t. + * + * Using the Fibonacci numbers is slightly nicer than the obvious + * approach of powers of 2, because it's still very easy to + * compute, and grows less fast (powers of 1.6 instead of 2), so + * you get just a touch more precision. + */ + uint32_t a = 1, b = 1; + + while (true) { + unsigned long start_time = GETTICKCOUNT(); + argon2(flavour, mem, b, parallel, taglen, P, S, K, X, out); + unsigned long ticks = GETTICKCOUNT() - start_time; + + /* But just in case computers get _too_ fast, we have to cap + * the growth before it gets past the uint32_t upper bound! So + * if computing a+b would overflow, stop here. */ + + if (ticks >= desired_time || a > (uint32_t)~b) { + *passes = b; + return; + } else { + strbuf_clear(out); + + /* Next Fibonacci number: replace (a, b) with (b, a+b) */ + b += a; + a = b - a; + } + } +} diff --git a/crypto/bcrypt.c b/crypto/bcrypt.c new file mode 100644 index 00000000..7cdd44ed --- /dev/null +++ b/crypto/bcrypt.c @@ -0,0 +1,119 @@ +/* + * 'bcrypt' password hash function, for PuTTY's import/export of + * OpenSSH encrypted private key files. + * + * This is not really the same as the original bcrypt; OpenSSH has + * modified it in various ways, and of course we have to do the same. + */ + +#include +#include +#include "ssh.h" +#include "sshblowf.h" + +BlowfishContext *bcrypt_setup(const unsigned char *key, int keybytes, + const unsigned char *salt, int saltbytes) +{ + int i; + BlowfishContext *ctx; + + ctx = blowfish_make_context(); + blowfish_initkey(ctx); + blowfish_expandkey(ctx, key, keybytes, salt, saltbytes); + + /* Original bcrypt replaces this fixed loop count with the + * variable cost. OpenSSH instead iterates the whole thing more + * than once if it wants extra rounds. */ + for (i = 0; i < 64; i++) { + blowfish_expandkey(ctx, salt, saltbytes, NULL, 0); + blowfish_expandkey(ctx, key, keybytes, NULL, 0); + } + + return ctx; +} + +void bcrypt_hash(const unsigned char *key, int keybytes, + const unsigned char *salt, int saltbytes, + unsigned char output[32]) +{ + BlowfishContext *ctx; + int i; + + ctx = bcrypt_setup(key, keybytes, salt, saltbytes); + /* This was quite a nice starting string until it ran into + * little-endian Blowfish :-/ */ + memcpy(output, "cyxOmorhcitawolBhsiftawSanyDetim", 32); + for (i = 0; i < 64; i++) { + blowfish_lsb_encrypt_ecb(output, 32, ctx); + } + blowfish_free_context(ctx); +} + +void bcrypt_genblock(int counter, + const unsigned char hashed_passphrase[64], + const unsigned char *salt, int saltbytes, + unsigned char output[32]) +{ + unsigned char hashed_salt[64]; + + /* Hash the input salt with the counter value optionally suffixed + * to get our real 32-byte salt */ + ssh_hash *h = ssh_hash_new(&ssh_sha512); + put_data(h, salt, saltbytes); + if (counter) + put_uint32(h, counter); + ssh_hash_final(h, hashed_salt); + + bcrypt_hash(hashed_passphrase, 64, hashed_salt, 64, output); + + smemclr(&hashed_salt, sizeof(hashed_salt)); +} + +void openssh_bcrypt(const char *passphrase, + const unsigned char *salt, int saltbytes, + int rounds, unsigned char *out, int outbytes) +{ + unsigned char hashed_passphrase[64]; + unsigned char block[32], outblock[32]; + const unsigned char *thissalt; + int thissaltbytes; + int modulus, residue, i, j, round; + + /* Hash the passphrase to get the bcrypt key material */ + hash_simple(&ssh_sha512, ptrlen_from_asciz(passphrase), hashed_passphrase); + + /* We output key bytes in a scattered fashion to meld all output + * key blocks into all parts of the output. To do this, we pick a + * modulus, and we output the key bytes to indices of out[] in the + * following order: first the indices that are multiples of the + * modulus, then the ones congruent to 1 mod modulus, etc. Each of + * those passes consumes exactly one block output from + * bcrypt_genblock, so we must pick a modulus large enough that at + * most 32 bytes are used in the pass. */ + modulus = (outbytes + 31) / 32; + + for (residue = 0; residue < modulus; residue++) { + /* Our output block of data is the XOR of all blocks generated + * by bcrypt in the following loop */ + memset(outblock, 0, sizeof(outblock)); + + thissalt = salt; + thissaltbytes = saltbytes; + for (round = 0; round < rounds; round++) { + bcrypt_genblock(round == 0 ? residue+1 : 0, + hashed_passphrase, + thissalt, thissaltbytes, block); + /* Each subsequent bcrypt call reuses the previous one's + * output as its salt */ + thissalt = block; + thissaltbytes = 32; + + for (i = 0; i < 32; i++) + outblock[i] ^= block[i]; + } + + for (i = residue, j = 0; i < outbytes; i += modulus, j++) + out[i] = outblock[j]; + } + smemclr(&hashed_passphrase, sizeof(hashed_passphrase)); +} diff --git a/crypto/blake2.c b/crypto/blake2.c new file mode 100644 index 00000000..a4d42f21 --- /dev/null +++ b/crypto/blake2.c @@ -0,0 +1,223 @@ +/* + * BLAKE2 (RFC 7693) implementation for PuTTY. + * + * The BLAKE2 hash family includes BLAKE2s, in which the hash state is + * operated on as a collection of 32-bit integers, and BLAKE2b, based + * on 64-bit integers. At present this code implements BLAKE2b only. + */ + +#include +#include "ssh.h" + +static inline uint64_t ror(uint64_t x, unsigned rotation) +{ + unsigned lshift = 63 & -rotation, rshift = 63 & rotation; + return (x << lshift) | (x >> rshift); +} + +/* RFC 7963 section 2.1 */ +enum { R1 = 32, R2 = 24, R3 = 16, R4 = 63 }; + +/* RFC 7693 section 2.6 */ +static const uint64_t iv[] = { + 0x6a09e667f3bcc908, /* floor(2^64 * frac(sqrt(2))) */ + 0xbb67ae8584caa73b, /* floor(2^64 * frac(sqrt(3))) */ + 0x3c6ef372fe94f82b, /* floor(2^64 * frac(sqrt(5))) */ + 0xa54ff53a5f1d36f1, /* floor(2^64 * frac(sqrt(7))) */ + 0x510e527fade682d1, /* floor(2^64 * frac(sqrt(11))) */ + 0x9b05688c2b3e6c1f, /* floor(2^64 * frac(sqrt(13))) */ + 0x1f83d9abfb41bd6b, /* floor(2^64 * frac(sqrt(17))) */ + 0x5be0cd19137e2179, /* floor(2^64 * frac(sqrt(19))) */ +}; + +/* RFC 7693 section 2.7 */ +static const unsigned char sigma[][16] = { + { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + {14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3}, + {11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4}, + { 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8}, + { 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13}, + { 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9}, + {12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11}, + {13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10}, + { 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5}, + {10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0}, + /* This array recycles if you have more than 10 rounds. BLAKE2b + * has 12, so we repeat the first two rows again. */ + { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + {14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3}, +}; + +static inline void g_half(uint64_t v[16], unsigned a, unsigned b, unsigned c, + unsigned d, uint64_t x, unsigned r1, unsigned r2) +{ + v[a] += v[b] + x; + v[d] ^= v[a]; + v[d] = ror(v[d], r1); + v[c] += v[d]; + v[b] ^= v[c]; + v[b] = ror(v[b], r2); +} + +static inline void g(uint64_t v[16], unsigned a, unsigned b, unsigned c, + unsigned d, uint64_t x, uint64_t y) +{ + g_half(v, a, b, c, d, x, R1, R2); + g_half(v, a, b, c, d, y, R3, R4); +} + +static inline void f(uint64_t h[8], uint64_t m[16], uint64_t offset_hi, + uint64_t offset_lo, unsigned final) +{ + uint64_t v[16]; + memcpy(v, h, 8 * sizeof(*v)); + memcpy(v + 8, iv, 8 * sizeof(*v)); + v[12] ^= offset_lo; + v[13] ^= offset_hi; + v[14] ^= -(uint64_t)final; + for (unsigned round = 0; round < 12; round++) { + const unsigned char *s = sigma[round]; + g(v, 0, 4, 8, 12, m[s[ 0]], m[s[ 1]]); + g(v, 1, 5, 9, 13, m[s[ 2]], m[s[ 3]]); + g(v, 2, 6, 10, 14, m[s[ 4]], m[s[ 5]]); + g(v, 3, 7, 11, 15, m[s[ 6]], m[s[ 7]]); + g(v, 0, 5, 10, 15, m[s[ 8]], m[s[ 9]]); + g(v, 1, 6, 11, 12, m[s[10]], m[s[11]]); + g(v, 2, 7, 8, 13, m[s[12]], m[s[13]]); + g(v, 3, 4, 9, 14, m[s[14]], m[s[15]]); + } + for (unsigned i = 0; i < 8; i++) + h[i] ^= v[i] ^ v[i+8]; + smemclr(v, sizeof(v)); +} + +static inline void f_outer(uint64_t h[8], uint8_t blk[128], uint64_t offset_hi, + uint64_t offset_lo, unsigned final) +{ + uint64_t m[16]; + for (unsigned i = 0; i < 16; i++) + m[i] = GET_64BIT_LSB_FIRST(blk + 8*i); + f(h, m, offset_hi, offset_lo, final); + smemclr(m, sizeof(m)); +} + +typedef struct blake2b { + uint64_t h[8]; + unsigned hashlen; + + uint8_t block[128]; + size_t used; + uint64_t lenhi, lenlo; + + BinarySink_IMPLEMENTATION; + ssh_hash hash; +} blake2b; + +static void blake2b_write(BinarySink *bs, const void *vp, size_t len); + +static ssh_hash *blake2b_new_inner(unsigned hashlen) +{ + assert(hashlen <= ssh_blake2b.hlen); + + blake2b *s = snew(blake2b); + s->hash.vt = &ssh_blake2b; + s->hashlen = hashlen; + BinarySink_INIT(s, blake2b_write); + BinarySink_DELEGATE_INIT(&s->hash, s); + return &s->hash; +} + +static ssh_hash *blake2b_new(const ssh_hashalg *alg) +{ + return blake2b_new_inner(alg->hlen); +} + +ssh_hash *blake2b_new_general(unsigned hashlen) +{ + ssh_hash *h = blake2b_new_inner(hashlen); + ssh_hash_reset(h); + return h; +} + +static void blake2b_reset(ssh_hash *hash) +{ + blake2b *s = container_of(hash, blake2b, hash); + + /* Initialise the hash to the standard IV */ + memcpy(s->h, iv, sizeof(s->h)); + + /* XOR in the parameters: secret key length (here always 0) in + * byte 1, and hash length in byte 0. */ + s->h[0] ^= 0x01010000 ^ s->hashlen; + + s->used = 0; + s->lenhi = s->lenlo = 0; +} + +static void blake2b_copyfrom(ssh_hash *hcopy, ssh_hash *horig) +{ + blake2b *copy = container_of(hcopy, blake2b, hash); + blake2b *orig = container_of(horig, blake2b, hash); + + memcpy(copy, orig, sizeof(*copy)); + BinarySink_COPIED(copy); + BinarySink_DELEGATE_INIT(©->hash, copy); +} + +static void blake2b_free(ssh_hash *hash) +{ + blake2b *s = container_of(hash, blake2b, hash); + + smemclr(s, sizeof(*s)); + sfree(s); +} + +static void blake2b_write(BinarySink *bs, const void *vp, size_t len) +{ + blake2b *s = BinarySink_DOWNCAST(bs, blake2b); + const uint8_t *p = vp; + + while (len > 0) { + if (s->used == sizeof(s->block)) { + f_outer(s->h, s->block, s->lenhi, s->lenlo, 0); + s->used = 0; + } + + size_t chunk = sizeof(s->block) - s->used; + if (chunk > len) + chunk = len; + + memcpy(s->block + s->used, p, chunk); + s->used += chunk; + p += chunk; + len -= chunk; + + s->lenlo += chunk; + s->lenhi += (s->lenlo < chunk); + } +} + +static void blake2b_digest(ssh_hash *hash, uint8_t *digest) +{ + blake2b *s = container_of(hash, blake2b, hash); + + memset(s->block + s->used, 0, sizeof(s->block) - s->used); + f_outer(s->h, s->block, s->lenhi, s->lenlo, 1); + + uint8_t hash_pre[128]; + for (unsigned i = 0; i < 8; i++) + PUT_64BIT_LSB_FIRST(hash_pre + 8*i, s->h[i]); + memcpy(digest, hash_pre, s->hashlen); + smemclr(hash_pre, sizeof(hash_pre)); +} + +const ssh_hashalg ssh_blake2b = { + .new = blake2b_new, + .reset = blake2b_reset, + .copyfrom = blake2b_copyfrom, + .digest = blake2b_digest, + .free = blake2b_free, + .hlen = 64, + .blocklen = 128, + HASHALG_NAMES_BARE("BLAKE2b-64"), +}; diff --git a/crypto/blowfish.c b/crypto/blowfish.c new file mode 100644 index 00000000..c74f06c0 --- /dev/null +++ b/crypto/blowfish.c @@ -0,0 +1,699 @@ +/* + * Blowfish implementation for PuTTY. + * + * Coded from scratch from the algorithm description. + */ + +#include +#include +#include "ssh.h" +#include "sshblowf.h" + +struct BlowfishContext { + uint32_t S0[256], S1[256], S2[256], S3[256], P[18]; + uint32_t iv0, iv1; /* for CBC mode */ +}; + +/* + * The Blowfish init data: hex digits of the fractional part of pi. + * (ie pi as a hex fraction is 3.243F6A8885A308D3...) + * + * If you have Simon Tatham's 'spigot' exact real calculator + * available, or any other method of generating 8336 fractional hex + * digits of pi on standard output, you can regenerate these tables + * exactly as below using the following Perl script (adjusting the + * first line or two if your pi-generator is not spigot). + +open my $spig, "spigot -n -B16 -d8336 pi |"; +read $spig, $ignore, 2; # throw away the leading "3." +for my $name ("parray", "sbox0".."sbox3") { + print "static const uint32_t ${name}[] = {\n"; + my $len = $name eq "parray" ? 18 : 256; + for my $i (1..$len) { + read $spig, $word, 8; + printf "%s0x%s,", ($i%6==1 ? " " : " "), uc $word; + print "\n" if ($i == $len || $i%6 == 0); + } + print "};\n\n"; +} +close $spig; + + */ +static const uint32_t parray[] = { + 0x243F6A88, 0x85A308D3, 0x13198A2E, 0x03707344, 0xA4093822, 0x299F31D0, + 0x082EFA98, 0xEC4E6C89, 0x452821E6, 0x38D01377, 0xBE5466CF, 0x34E90C6C, + 0xC0AC29B7, 0xC97C50DD, 0x3F84D5B5, 0xB5470917, 0x9216D5D9, 0x8979FB1B, +}; + +static const uint32_t 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 uint32_t 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 uint32_t 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 uint32_t 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(uint32_t xL, uint32_t xR, uint32_t *output, + BlowfishContext * ctx) +{ + uint32_t *S0 = ctx->S0; + uint32_t *S1 = ctx->S1; + uint32_t *S2 = ctx->S2; + uint32_t *S3 = ctx->S3; + uint32_t *P = ctx->P; + uint32_t 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(uint32_t xL, uint32_t xR, uint32_t *output, + BlowfishContext * ctx) +{ + uint32_t *S0 = ctx->S0; + uint32_t *S1 = ctx->S1; + uint32_t *S2 = ctx->S2; + uint32_t *S3 = ctx->S3; + uint32_t *P = ctx->P; + uint32_t 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) +{ + uint32_t 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; +} + +void blowfish_lsb_encrypt_ecb(void *vblk, int len, BlowfishContext * ctx) +{ + unsigned char *blk = (unsigned char *)vblk; + uint32_t xL, xR, out[2]; + + assert((len & 7) == 0); + + while (len > 0) { + xL = GET_32BIT_LSB_FIRST(blk); + xR = GET_32BIT_LSB_FIRST(blk + 4); + blowfish_encrypt(xL, xR, out, ctx); + PUT_32BIT_LSB_FIRST(blk, out[0]); + PUT_32BIT_LSB_FIRST(blk + 4, out[1]); + blk += 8; + len -= 8; + } +} + +static void blowfish_lsb_decrypt_cbc(unsigned char *blk, int len, + BlowfishContext * ctx) +{ + uint32_t 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) +{ + uint32_t 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) +{ + uint32_t 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) +{ + uint32_t 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; +} + +void blowfish_initkey(BlowfishContext *ctx) +{ + int i; + + for (i = 0; i < 18; i++) { + ctx->P[i] = parray[i]; + } + + for (i = 0; i < 256; i++) { + ctx->S0[i] = sbox0[i]; + ctx->S1[i] = sbox1[i]; + ctx->S2[i] = sbox2[i]; + ctx->S3[i] = sbox3[i]; + } +} + +void blowfish_expandkey(BlowfishContext * ctx, + const void *vkey, short keybytes, + const void *vsalt, short saltbytes) +{ + const unsigned char *key = (const unsigned char *)vkey; + const unsigned char *salt = (const unsigned char *)vsalt; + uint32_t *S0 = ctx->S0; + uint32_t *S1 = ctx->S1; + uint32_t *S2 = ctx->S2; + uint32_t *S3 = ctx->S3; + uint32_t *P = ctx->P; + uint32_t str[2]; + int i, j; + int saltpos; + unsigned char dummysalt[1]; + + saltpos = 0; + if (!salt) { + saltbytes = 1; + salt = dummysalt; + dummysalt[0] = 0; + } + + for (i = 0; i < 18; i++) { + P[i] ^= + ((uint32_t) (unsigned char) (key[(i * 4 + 0) % keybytes])) << 24; + P[i] ^= + ((uint32_t) (unsigned char) (key[(i * 4 + 1) % keybytes])) << 16; + P[i] ^= + ((uint32_t) (unsigned char) (key[(i * 4 + 2) % keybytes])) << 8; + P[i] ^= ((uint32_t) (unsigned char) (key[(i * 4 + 3) % keybytes])); + } + + str[0] = str[1] = 0; + + for (i = 0; i < 18; i += 2) { + for (j = 0; j < 8; j++) + str[j/4] ^= ((uint32_t)salt[saltpos++ % saltbytes]) << (24-8*(j%4)); + + blowfish_encrypt(str[0], str[1], str, ctx); + P[i] = str[0]; + P[i + 1] = str[1]; + } + + for (i = 0; i < 256; i += 2) { + for (j = 0; j < 8; j++) + str[j/4] ^= ((uint32_t)salt[saltpos++ % saltbytes]) << (24-8*(j%4)); + blowfish_encrypt(str[0], str[1], str, ctx); + S0[i] = str[0]; + S0[i + 1] = str[1]; + } + for (i = 0; i < 256; i += 2) { + for (j = 0; j < 8; j++) + str[j/4] ^= ((uint32_t)salt[saltpos++ % saltbytes]) << (24-8*(j%4)); + blowfish_encrypt(str[0], str[1], str, ctx); + S1[i] = str[0]; + S1[i + 1] = str[1]; + } + for (i = 0; i < 256; i += 2) { + for (j = 0; j < 8; j++) + str[j/4] ^= ((uint32_t)salt[saltpos++ % saltbytes]) << (24-8*(j%4)); + blowfish_encrypt(str[0], str[1], str, ctx); + S2[i] = str[0]; + S2[i + 1] = str[1]; + } + for (i = 0; i < 256; i += 2) { + for (j = 0; j < 8; j++) + str[j/4] ^= ((uint32_t)salt[saltpos++ % saltbytes]) << (24-8*(j%4)); + blowfish_encrypt(str[0], str[1], str, ctx); + S3[i] = str[0]; + S3[i + 1] = str[1]; + } +} + +static void blowfish_setkey(BlowfishContext *ctx, + const unsigned char *key, short keybytes) +{ + blowfish_initkey(ctx); + blowfish_expandkey(ctx, key, keybytes, NULL, 0); +} + +/* -- Interface with PuTTY -- */ + +#define SSH1_SESSION_KEY_LENGTH 32 + +BlowfishContext *blowfish_make_context(void) +{ + return snew(BlowfishContext); +} + +void blowfish_free_context(BlowfishContext *ctx) +{ + sfree(ctx); +} + +static void blowfish_iv_be(BlowfishContext *ctx, const void *viv) +{ + const unsigned char *iv = (const unsigned char *)viv; + ctx->iv0 = GET_32BIT_MSB_FIRST(iv); + ctx->iv1 = GET_32BIT_MSB_FIRST(iv + 4); +} + +static void blowfish_iv_le(BlowfishContext *ctx, const void *viv) +{ + const unsigned char *iv = (const unsigned char *)viv; + ctx->iv0 = GET_32BIT_LSB_FIRST(iv); + ctx->iv1 = GET_32BIT_LSB_FIRST(iv + 4); +} + +struct blowfish_ctx { + BlowfishContext context; + ssh_cipher ciph; +}; + +static ssh_cipher *blowfish_new(const ssh_cipheralg *alg) +{ + struct blowfish_ctx *ctx = snew(struct blowfish_ctx); + ctx->ciph.vt = alg; + return &ctx->ciph; +} + +static void blowfish_free(ssh_cipher *cipher) +{ + struct blowfish_ctx *ctx = container_of(cipher, struct blowfish_ctx, ciph); + smemclr(ctx, sizeof(*ctx)); + sfree(ctx); +} + +static void blowfish_ssh_setkey(ssh_cipher *cipher, const void *key) +{ + struct blowfish_ctx *ctx = container_of(cipher, struct blowfish_ctx, ciph); + blowfish_setkey(&ctx->context, key, ctx->ciph.vt->padded_keybytes); +} + +static void blowfish_ssh1_setiv(ssh_cipher *cipher, const void *iv) +{ + struct blowfish_ctx *ctx = container_of(cipher, struct blowfish_ctx, ciph); + blowfish_iv_le(&ctx->context, iv); +} + +static void blowfish_ssh2_setiv(ssh_cipher *cipher, const void *iv) +{ + struct blowfish_ctx *ctx = container_of(cipher, struct blowfish_ctx, ciph); + blowfish_iv_be(&ctx->context, iv); +} + +static void blowfish_ssh1_encrypt_blk(ssh_cipher *cipher, void *blk, int len) +{ + struct blowfish_ctx *ctx = container_of(cipher, struct blowfish_ctx, ciph); + blowfish_lsb_encrypt_cbc(blk, len, &ctx->context); +} + +static void blowfish_ssh1_decrypt_blk(ssh_cipher *cipher, void *blk, int len) +{ + struct blowfish_ctx *ctx = container_of(cipher, struct blowfish_ctx, ciph); + blowfish_lsb_decrypt_cbc(blk, len, &ctx->context); +} + +static void blowfish_ssh2_encrypt_blk(ssh_cipher *cipher, void *blk, int len) +{ + struct blowfish_ctx *ctx = container_of(cipher, struct blowfish_ctx, ciph); + blowfish_msb_encrypt_cbc(blk, len, &ctx->context); +} + +static void blowfish_ssh2_decrypt_blk(ssh_cipher *cipher, void *blk, int len) +{ + struct blowfish_ctx *ctx = container_of(cipher, struct blowfish_ctx, ciph); + blowfish_msb_decrypt_cbc(blk, len, &ctx->context); +} + +static void blowfish_ssh2_sdctr(ssh_cipher *cipher, void *blk, int len) +{ + struct blowfish_ctx *ctx = container_of(cipher, struct blowfish_ctx, ciph); + blowfish_msb_sdctr(blk, len, &ctx->context); +} + +const ssh_cipheralg ssh_blowfish_ssh1 = { + .new = blowfish_new, + .free = blowfish_free, + .setiv = blowfish_ssh1_setiv, + .setkey = blowfish_ssh_setkey, + .encrypt = blowfish_ssh1_encrypt_blk, + .decrypt = blowfish_ssh1_decrypt_blk, + .blksize = 8, + .real_keybits = 128, + .padded_keybytes = SSH1_SESSION_KEY_LENGTH, + .flags = SSH_CIPHER_IS_CBC, + .text_name = "Blowfish-256 CBC", +}; + +const ssh_cipheralg ssh_blowfish_ssh2 = { + .new = blowfish_new, + .free = blowfish_free, + .setiv = blowfish_ssh2_setiv, + .setkey = blowfish_ssh_setkey, + .encrypt = blowfish_ssh2_encrypt_blk, + .decrypt = blowfish_ssh2_decrypt_blk, + .ssh2_id = "blowfish-cbc", + .blksize = 8, + .real_keybits = 128, + .padded_keybytes = 16, + .flags = SSH_CIPHER_IS_CBC, + .text_name = "Blowfish-128 CBC", +}; + +const ssh_cipheralg ssh_blowfish_ssh2_ctr = { + .new = blowfish_new, + .free = blowfish_free, + .setiv = blowfish_ssh2_setiv, + .setkey = blowfish_ssh_setkey, + .encrypt = blowfish_ssh2_sdctr, + .decrypt = blowfish_ssh2_sdctr, + .ssh2_id = "blowfish-ctr", + .blksize = 8, + .real_keybits = 256, + .padded_keybytes = 32, + .flags = 0, + .text_name = "Blowfish-256 SDCTR", +}; + +static const ssh_cipheralg *const blowfish_list[] = { + &ssh_blowfish_ssh2_ctr, + &ssh_blowfish_ssh2 +}; + +const ssh2_ciphers ssh2_blowfish = { lenof(blowfish_list), blowfish_list }; diff --git a/crypto/chacha20-poly1305.c b/crypto/chacha20-poly1305.c new file mode 100644 index 00000000..dd25b997 --- /dev/null +++ b/crypto/chacha20-poly1305.c @@ -0,0 +1,1062 @@ +/* + * ChaCha20-Poly1305 Implementation for SSH-2 + * + * Protocol spec: + * http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.chacha20poly1305?rev=1.2&content-type=text/x-cvsweb-markup + * + * ChaCha20 spec: + * http://cr.yp.to/chacha/chacha-20080128.pdf + * + * Salsa20 spec: + * http://cr.yp.to/snuffle/spec.pdf + * + * Poly1305-AES spec: + * http://cr.yp.to/mac/poly1305-20050329.pdf + * + * The nonce for the Poly1305 is the second part of the key output + * from the first round of ChaCha20. This removes the AES requirement. + * This is undocumented! + * + * This has an intricate link between the cipher and the MAC. The + * keying of both is done in by the cipher and setting of the IV is + * done by the MAC. One cannot operate without the other. The + * configuration of the ssh_cipheralg structure ensures that the MAC is + * set (and others ignored) if this cipher is chosen. + * + * This cipher also encrypts the length using a different + * instantiation of the cipher using a different key and IV made from + * the sequence number which is passed in addition when calling + * encrypt/decrypt on it. + */ + +#include "ssh.h" +#include "mpint_i.h" + +#ifndef INLINE +#define INLINE +#endif + +/* ChaCha20 implementation, only supporting 256-bit keys */ + +/* State for each ChaCha20 instance */ +struct chacha20 { + /* Current context, usually with the count incremented + * 0-3 are the static constant + * 4-11 are the key + * 12-13 are the counter + * 14-15 are the IV */ + uint32_t state[16]; + /* The output of the state above ready to xor */ + unsigned char current[64]; + /* The index of the above currently used to allow a true streaming cipher */ + int currentIndex; +}; + +static INLINE void chacha20_round(struct chacha20 *ctx) +{ + int i; + uint32_t copy[16]; + + /* Take a copy */ + memcpy(copy, ctx->state, sizeof(copy)); + + /* A circular rotation for a 32bit number */ +#define rotl(x, shift) x = ((x << shift) | (x >> (32 - shift))) + + /* What to do for each quarter round operation */ +#define qrop(a, b, c, d) \ + copy[a] += copy[b]; \ + copy[c] ^= copy[a]; \ + rotl(copy[c], d) + + /* A quarter round */ +#define quarter(a, b, c, d) \ + qrop(a, b, d, 16); \ + qrop(c, d, b, 12); \ + qrop(a, b, d, 8); \ + qrop(c, d, b, 7) + + /* Do 20 rounds, in pairs because every other is different */ + for (i = 0; i < 20; i += 2) { + /* A round */ + quarter(0, 4, 8, 12); + quarter(1, 5, 9, 13); + quarter(2, 6, 10, 14); + quarter(3, 7, 11, 15); + /* Another slightly different round */ + quarter(0, 5, 10, 15); + quarter(1, 6, 11, 12); + quarter(2, 7, 8, 13); + quarter(3, 4, 9, 14); + } + + /* Dump the macros, don't need them littering */ +#undef rotl +#undef qrop +#undef quarter + + /* Add the initial state */ + for (i = 0; i < 16; ++i) { + copy[i] += ctx->state[i]; + } + + /* Update the content of the xor buffer */ + for (i = 0; i < 16; ++i) { + ctx->current[i * 4 + 0] = copy[i] >> 0; + ctx->current[i * 4 + 1] = copy[i] >> 8; + ctx->current[i * 4 + 2] = copy[i] >> 16; + ctx->current[i * 4 + 3] = copy[i] >> 24; + } + /* State full, reset pointer to beginning */ + ctx->currentIndex = 0; + smemclr(copy, sizeof(copy)); + + /* Increment round counter */ + ++ctx->state[12]; + /* Check for overflow, not done in one line so the 32 bits are chopped by the type */ + if (!(uint32_t)(ctx->state[12])) { + ++ctx->state[13]; + } +} + +/* Initialise context with 256bit key */ +static void chacha20_key(struct chacha20 *ctx, const unsigned char *key) +{ + static const char constant[16] = "expand 32-byte k"; + + /* Add the fixed string to the start of the state */ + ctx->state[0] = GET_32BIT_LSB_FIRST(constant + 0); + ctx->state[1] = GET_32BIT_LSB_FIRST(constant + 4); + ctx->state[2] = GET_32BIT_LSB_FIRST(constant + 8); + ctx->state[3] = GET_32BIT_LSB_FIRST(constant + 12); + + /* Add the key */ + ctx->state[4] = GET_32BIT_LSB_FIRST(key + 0); + ctx->state[5] = GET_32BIT_LSB_FIRST(key + 4); + ctx->state[6] = GET_32BIT_LSB_FIRST(key + 8); + ctx->state[7] = GET_32BIT_LSB_FIRST(key + 12); + ctx->state[8] = GET_32BIT_LSB_FIRST(key + 16); + ctx->state[9] = GET_32BIT_LSB_FIRST(key + 20); + ctx->state[10] = GET_32BIT_LSB_FIRST(key + 24); + ctx->state[11] = GET_32BIT_LSB_FIRST(key + 28); + + /* New key, dump context */ + ctx->currentIndex = 64; +} + +static void chacha20_iv(struct chacha20 *ctx, const unsigned char *iv) +{ + ctx->state[12] = 0; + ctx->state[13] = 0; + ctx->state[14] = GET_32BIT_MSB_FIRST(iv); + ctx->state[15] = GET_32BIT_MSB_FIRST(iv + 4); + + /* New IV, dump context */ + ctx->currentIndex = 64; +} + +static void chacha20_encrypt(struct chacha20 *ctx, unsigned char *blk, int len) +{ + while (len) { + /* If we don't have any state left, then cycle to the next */ + if (ctx->currentIndex >= 64) { + chacha20_round(ctx); + } + + /* Do the xor while there's some state left and some plaintext left */ + while (ctx->currentIndex < 64 && len) { + *blk++ ^= ctx->current[ctx->currentIndex++]; + --len; + } + } +} + +/* Decrypt is encrypt... It's xor against a PRNG... */ +static INLINE void chacha20_decrypt(struct chacha20 *ctx, + unsigned char *blk, int len) +{ + chacha20_encrypt(ctx, blk, len); +} + +/* Poly1305 implementation (no AES, nonce is not encrypted) */ + +#define NWORDS ((130 + BIGNUM_INT_BITS-1) / BIGNUM_INT_BITS) +typedef struct bigval { + BignumInt w[NWORDS]; +} bigval; + +static void bigval_clear(bigval *r) +{ + int i; + for (i = 0; i < NWORDS; i++) + r->w[i] = 0; +} + +static void bigval_import_le(bigval *r, const void *vdata, int len) +{ + const unsigned char *data = (const unsigned char *)vdata; + int i; + bigval_clear(r); + for (i = 0; i < len; i++) + r->w[i / BIGNUM_INT_BYTES] |= + (BignumInt)data[i] << (8 * (i % BIGNUM_INT_BYTES)); +} + +static void bigval_export_le(const bigval *r, void *vdata, int len) +{ + unsigned char *data = (unsigned char *)vdata; + int i; + for (i = 0; i < len; i++) + data[i] = r->w[i / BIGNUM_INT_BYTES] >> (8 * (i % BIGNUM_INT_BYTES)); +} + +/* + * Core functions to do arithmetic mod p = 2^130-5. The whole + * collection of these, up to and including the surrounding #if, are + * generated automatically for various sizes of BignumInt by + * contrib/make1305.py. + */ + +#if BIGNUM_INT_BITS == 16 + +static void bigval_add(bigval *r, const bigval *a, const bigval *b) +{ + BignumInt v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14; + BignumInt v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26; + BignumCarry carry; + + v0 = a->w[0]; + v1 = a->w[1]; + v2 = a->w[2]; + v3 = a->w[3]; + v4 = a->w[4]; + v5 = a->w[5]; + v6 = a->w[6]; + v7 = a->w[7]; + v8 = a->w[8]; + v9 = b->w[0]; + v10 = b->w[1]; + v11 = b->w[2]; + v12 = b->w[3]; + v13 = b->w[4]; + v14 = b->w[5]; + v15 = b->w[6]; + v16 = b->w[7]; + v17 = b->w[8]; + BignumADC(v18, carry, v0, v9, 0); + BignumADC(v19, carry, v1, v10, carry); + BignumADC(v20, carry, v2, v11, carry); + BignumADC(v21, carry, v3, v12, carry); + BignumADC(v22, carry, v4, v13, carry); + BignumADC(v23, carry, v5, v14, carry); + BignumADC(v24, carry, v6, v15, carry); + BignumADC(v25, carry, v7, v16, carry); + v26 = v8 + v17 + carry; + r->w[0] = v18; + r->w[1] = v19; + r->w[2] = v20; + r->w[3] = v21; + r->w[4] = v22; + r->w[5] = v23; + r->w[6] = v24; + r->w[7] = v25; + r->w[8] = v26; +} + +static void bigval_mul_mod_p(bigval *r, const bigval *a, const bigval *b) +{ + BignumInt v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14; + BignumInt v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27; + BignumInt v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40; + BignumInt v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53; + BignumInt v54, v55, v56, v57, v58, v59, v60, v61, v62, v63, v64, v65, v66; + BignumInt v67, v68, v69, v70, v71, v72, v73, v74, v75, v76, v77, v78, v79; + BignumInt v80, v81, v82, v83, v84, v85, v86, v87, v88, v89, v90, v91, v92; + BignumInt v93, v94, v95, v96, v97, v98, v99, v100, v101, v102, v103, v104; + BignumInt v105, v106, v107, v108, v109, v110, v111, v112, v113, v114; + BignumInt v115, v116, v117, v118, v119, v120, v121, v122, v123, v124; + BignumInt v125, v126, v127, v128, v129, v130, v131, v132, v133, v134; + BignumInt v135, v136, v137, v138, v139, v140, v141, v142, v143, v144; + BignumInt v145, v146, v147, v148, v149, v150, v151, v152, v153, v154; + BignumInt v155, v156, v157, v158, v159, v160, v161, v162, v163, v164; + BignumInt v165, v166, v167, v168, v169, v170, v171, v172, v173, v174; + BignumInt v175, v176, v177, v178, v180, v181, v182, v183, v184, v185; + BignumInt v186, v187, v188, v189, v190, v191, v192, v193, v194, v195; + BignumInt v196, v197, v198, v199, v200, v201, v202, v203, v204, v205; + BignumInt v206, v207, v208, v210, v212, v213, v214, v215, v216, v217; + BignumInt v218, v219, v220, v221, v222, v223, v224, v225, v226, v227; + BignumInt v228, v229; + BignumCarry carry; + + v0 = a->w[0]; + v1 = a->w[1]; + v2 = a->w[2]; + v3 = a->w[3]; + v4 = a->w[4]; + v5 = a->w[5]; + v6 = a->w[6]; + v7 = a->w[7]; + v8 = a->w[8]; + v9 = b->w[0]; + v10 = b->w[1]; + v11 = b->w[2]; + v12 = b->w[3]; + v13 = b->w[4]; + v14 = b->w[5]; + v15 = b->w[6]; + v16 = b->w[7]; + v17 = b->w[8]; + BignumMUL(v19, v18, v0, v9); + BignumMULADD(v21, v20, v0, v10, v19); + BignumMULADD(v23, v22, v0, v11, v21); + BignumMULADD(v25, v24, v0, v12, v23); + BignumMULADD(v27, v26, v0, v13, v25); + BignumMULADD(v29, v28, v0, v14, v27); + BignumMULADD(v31, v30, v0, v15, v29); + BignumMULADD(v33, v32, v0, v16, v31); + BignumMULADD(v35, v34, v0, v17, v33); + BignumMULADD(v37, v36, v1, v9, v20); + BignumMULADD2(v39, v38, v1, v10, v22, v37); + BignumMULADD2(v41, v40, v1, v11, v24, v39); + BignumMULADD2(v43, v42, v1, v12, v26, v41); + BignumMULADD2(v45, v44, v1, v13, v28, v43); + BignumMULADD2(v47, v46, v1, v14, v30, v45); + BignumMULADD2(v49, v48, v1, v15, v32, v47); + BignumMULADD2(v51, v50, v1, v16, v34, v49); + BignumMULADD2(v53, v52, v1, v17, v35, v51); + BignumMULADD(v55, v54, v2, v9, v38); + BignumMULADD2(v57, v56, v2, v10, v40, v55); + BignumMULADD2(v59, v58, v2, v11, v42, v57); + BignumMULADD2(v61, v60, v2, v12, v44, v59); + BignumMULADD2(v63, v62, v2, v13, v46, v61); + BignumMULADD2(v65, v64, v2, v14, v48, v63); + BignumMULADD2(v67, v66, v2, v15, v50, v65); + BignumMULADD2(v69, v68, v2, v16, v52, v67); + BignumMULADD2(v71, v70, v2, v17, v53, v69); + BignumMULADD(v73, v72, v3, v9, v56); + BignumMULADD2(v75, v74, v3, v10, v58, v73); + BignumMULADD2(v77, v76, v3, v11, v60, v75); + BignumMULADD2(v79, v78, v3, v12, v62, v77); + BignumMULADD2(v81, v80, v3, v13, v64, v79); + BignumMULADD2(v83, v82, v3, v14, v66, v81); + BignumMULADD2(v85, v84, v3, v15, v68, v83); + BignumMULADD2(v87, v86, v3, v16, v70, v85); + BignumMULADD2(v89, v88, v3, v17, v71, v87); + BignumMULADD(v91, v90, v4, v9, v74); + BignumMULADD2(v93, v92, v4, v10, v76, v91); + BignumMULADD2(v95, v94, v4, v11, v78, v93); + BignumMULADD2(v97, v96, v4, v12, v80, v95); + BignumMULADD2(v99, v98, v4, v13, v82, v97); + BignumMULADD2(v101, v100, v4, v14, v84, v99); + BignumMULADD2(v103, v102, v4, v15, v86, v101); + BignumMULADD2(v105, v104, v4, v16, v88, v103); + BignumMULADD2(v107, v106, v4, v17, v89, v105); + BignumMULADD(v109, v108, v5, v9, v92); + BignumMULADD2(v111, v110, v5, v10, v94, v109); + BignumMULADD2(v113, v112, v5, v11, v96, v111); + BignumMULADD2(v115, v114, v5, v12, v98, v113); + BignumMULADD2(v117, v116, v5, v13, v100, v115); + BignumMULADD2(v119, v118, v5, v14, v102, v117); + BignumMULADD2(v121, v120, v5, v15, v104, v119); + BignumMULADD2(v123, v122, v5, v16, v106, v121); + BignumMULADD2(v125, v124, v5, v17, v107, v123); + BignumMULADD(v127, v126, v6, v9, v110); + BignumMULADD2(v129, v128, v6, v10, v112, v127); + BignumMULADD2(v131, v130, v6, v11, v114, v129); + BignumMULADD2(v133, v132, v6, v12, v116, v131); + BignumMULADD2(v135, v134, v6, v13, v118, v133); + BignumMULADD2(v137, v136, v6, v14, v120, v135); + BignumMULADD2(v139, v138, v6, v15, v122, v137); + BignumMULADD2(v141, v140, v6, v16, v124, v139); + BignumMULADD2(v143, v142, v6, v17, v125, v141); + BignumMULADD(v145, v144, v7, v9, v128); + BignumMULADD2(v147, v146, v7, v10, v130, v145); + BignumMULADD2(v149, v148, v7, v11, v132, v147); + BignumMULADD2(v151, v150, v7, v12, v134, v149); + BignumMULADD2(v153, v152, v7, v13, v136, v151); + BignumMULADD2(v155, v154, v7, v14, v138, v153); + BignumMULADD2(v157, v156, v7, v15, v140, v155); + BignumMULADD2(v159, v158, v7, v16, v142, v157); + BignumMULADD2(v161, v160, v7, v17, v143, v159); + BignumMULADD(v163, v162, v8, v9, v146); + BignumMULADD2(v165, v164, v8, v10, v148, v163); + BignumMULADD2(v167, v166, v8, v11, v150, v165); + BignumMULADD2(v169, v168, v8, v12, v152, v167); + BignumMULADD2(v171, v170, v8, v13, v154, v169); + BignumMULADD2(v173, v172, v8, v14, v156, v171); + BignumMULADD2(v175, v174, v8, v15, v158, v173); + BignumMULADD2(v177, v176, v8, v16, v160, v175); + v178 = v8 * v17 + v161 + v177; + v180 = (v162) & ((((BignumInt)1) << 2)-1); + v181 = ((v162) >> 2) | ((v164) << 14); + v182 = ((v164) >> 2) | ((v166) << 14); + v183 = ((v166) >> 2) | ((v168) << 14); + v184 = ((v168) >> 2) | ((v170) << 14); + v185 = ((v170) >> 2) | ((v172) << 14); + v186 = ((v172) >> 2) | ((v174) << 14); + v187 = ((v174) >> 2) | ((v176) << 14); + v188 = ((v176) >> 2) | ((v178) << 14); + v189 = (v178) >> 2; + v190 = (v189) & ((((BignumInt)1) << 2)-1); + v191 = (v178) >> 4; + BignumMUL(v193, v192, 5, v181); + BignumMULADD(v195, v194, 5, v182, v193); + BignumMULADD(v197, v196, 5, v183, v195); + BignumMULADD(v199, v198, 5, v184, v197); + BignumMULADD(v201, v200, 5, v185, v199); + BignumMULADD(v203, v202, 5, v186, v201); + BignumMULADD(v205, v204, 5, v187, v203); + BignumMULADD(v207, v206, 5, v188, v205); + v208 = 5 * v190 + v207; + v210 = 25 * v191; + BignumADC(v212, carry, v18, v192, 0); + BignumADC(v213, carry, v36, v194, carry); + BignumADC(v214, carry, v54, v196, carry); + BignumADC(v215, carry, v72, v198, carry); + BignumADC(v216, carry, v90, v200, carry); + BignumADC(v217, carry, v108, v202, carry); + BignumADC(v218, carry, v126, v204, carry); + BignumADC(v219, carry, v144, v206, carry); + v220 = v180 + v208 + carry; + BignumADC(v221, carry, v212, v210, 0); + BignumADC(v222, carry, v213, 0, carry); + BignumADC(v223, carry, v214, 0, carry); + BignumADC(v224, carry, v215, 0, carry); + BignumADC(v225, carry, v216, 0, carry); + BignumADC(v226, carry, v217, 0, carry); + BignumADC(v227, carry, v218, 0, carry); + BignumADC(v228, carry, v219, 0, carry); + v229 = v220 + 0 + carry; + r->w[0] = v221; + r->w[1] = v222; + r->w[2] = v223; + r->w[3] = v224; + r->w[4] = v225; + r->w[5] = v226; + r->w[6] = v227; + r->w[7] = v228; + r->w[8] = v229; +} + +static void bigval_final_reduce(bigval *n) +{ + BignumInt v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v13, v14, v15; + BignumInt v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28; + BignumInt v29, v30, v31, v32, v34, v35, v36, v37, v38, v39, v40, v41, v42; + BignumInt v43; + BignumCarry carry; + + v0 = n->w[0]; + v1 = n->w[1]; + v2 = n->w[2]; + v3 = n->w[3]; + v4 = n->w[4]; + v5 = n->w[5]; + v6 = n->w[6]; + v7 = n->w[7]; + v8 = n->w[8]; + v9 = (v8) >> 2; + v10 = (v8) & ((((BignumInt)1) << 2)-1); + v11 = 5 * v9; + BignumADC(v13, carry, v0, v11, 0); + BignumADC(v14, carry, v1, 0, carry); + BignumADC(v15, carry, v2, 0, carry); + BignumADC(v16, carry, v3, 0, carry); + BignumADC(v17, carry, v4, 0, carry); + BignumADC(v18, carry, v5, 0, carry); + BignumADC(v19, carry, v6, 0, carry); + BignumADC(v20, carry, v7, 0, carry); + v21 = v10 + 0 + carry; + BignumADC(v22, carry, v13, 5, 0); + (void)v22; + BignumADC(v23, carry, v14, 0, carry); + (void)v23; + BignumADC(v24, carry, v15, 0, carry); + (void)v24; + BignumADC(v25, carry, v16, 0, carry); + (void)v25; + BignumADC(v26, carry, v17, 0, carry); + (void)v26; + BignumADC(v27, carry, v18, 0, carry); + (void)v27; + BignumADC(v28, carry, v19, 0, carry); + (void)v28; + BignumADC(v29, carry, v20, 0, carry); + (void)v29; + v30 = v21 + 0 + carry; + v31 = (v30) >> 2; + v32 = 5 * v31; + BignumADC(v34, carry, v13, v32, 0); + BignumADC(v35, carry, v14, 0, carry); + BignumADC(v36, carry, v15, 0, carry); + BignumADC(v37, carry, v16, 0, carry); + BignumADC(v38, carry, v17, 0, carry); + BignumADC(v39, carry, v18, 0, carry); + BignumADC(v40, carry, v19, 0, carry); + BignumADC(v41, carry, v20, 0, carry); + v42 = v21 + 0 + carry; + v43 = (v42) & ((((BignumInt)1) << 2)-1); + n->w[0] = v34; + n->w[1] = v35; + n->w[2] = v36; + n->w[3] = v37; + n->w[4] = v38; + n->w[5] = v39; + n->w[6] = v40; + n->w[7] = v41; + n->w[8] = v43; +} + +#elif BIGNUM_INT_BITS == 32 + +static void bigval_add(bigval *r, const bigval *a, const bigval *b) +{ + BignumInt v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14; + BignumCarry carry; + + v0 = a->w[0]; + v1 = a->w[1]; + v2 = a->w[2]; + v3 = a->w[3]; + v4 = a->w[4]; + v5 = b->w[0]; + v6 = b->w[1]; + v7 = b->w[2]; + v8 = b->w[3]; + v9 = b->w[4]; + BignumADC(v10, carry, v0, v5, 0); + BignumADC(v11, carry, v1, v6, carry); + BignumADC(v12, carry, v2, v7, carry); + BignumADC(v13, carry, v3, v8, carry); + v14 = v4 + v9 + carry; + r->w[0] = v10; + r->w[1] = v11; + r->w[2] = v12; + r->w[3] = v13; + r->w[4] = v14; +} + +static void bigval_mul_mod_p(bigval *r, const bigval *a, const bigval *b) +{ + BignumInt v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14; + BignumInt v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27; + BignumInt v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40; + BignumInt v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53; + BignumInt v54, v55, v56, v57, v58, v60, v61, v62, v63, v64, v65, v66, v67; + BignumInt v68, v69, v70, v71, v72, v73, v74, v75, v76, v78, v80, v81, v82; + BignumInt v83, v84, v85, v86, v87, v88, v89; + BignumCarry carry; + + v0 = a->w[0]; + v1 = a->w[1]; + v2 = a->w[2]; + v3 = a->w[3]; + v4 = a->w[4]; + v5 = b->w[0]; + v6 = b->w[1]; + v7 = b->w[2]; + v8 = b->w[3]; + v9 = b->w[4]; + BignumMUL(v11, v10, v0, v5); + BignumMULADD(v13, v12, v0, v6, v11); + BignumMULADD(v15, v14, v0, v7, v13); + BignumMULADD(v17, v16, v0, v8, v15); + BignumMULADD(v19, v18, v0, v9, v17); + BignumMULADD(v21, v20, v1, v5, v12); + BignumMULADD2(v23, v22, v1, v6, v14, v21); + BignumMULADD2(v25, v24, v1, v7, v16, v23); + BignumMULADD2(v27, v26, v1, v8, v18, v25); + BignumMULADD2(v29, v28, v1, v9, v19, v27); + BignumMULADD(v31, v30, v2, v5, v22); + BignumMULADD2(v33, v32, v2, v6, v24, v31); + BignumMULADD2(v35, v34, v2, v7, v26, v33); + BignumMULADD2(v37, v36, v2, v8, v28, v35); + BignumMULADD2(v39, v38, v2, v9, v29, v37); + BignumMULADD(v41, v40, v3, v5, v32); + BignumMULADD2(v43, v42, v3, v6, v34, v41); + BignumMULADD2(v45, v44, v3, v7, v36, v43); + BignumMULADD2(v47, v46, v3, v8, v38, v45); + BignumMULADD2(v49, v48, v3, v9, v39, v47); + BignumMULADD(v51, v50, v4, v5, v42); + BignumMULADD2(v53, v52, v4, v6, v44, v51); + BignumMULADD2(v55, v54, v4, v7, v46, v53); + BignumMULADD2(v57, v56, v4, v8, v48, v55); + v58 = v4 * v9 + v49 + v57; + v60 = (v50) & ((((BignumInt)1) << 2)-1); + v61 = ((v50) >> 2) | ((v52) << 30); + v62 = ((v52) >> 2) | ((v54) << 30); + v63 = ((v54) >> 2) | ((v56) << 30); + v64 = ((v56) >> 2) | ((v58) << 30); + v65 = (v58) >> 2; + v66 = (v65) & ((((BignumInt)1) << 2)-1); + v67 = (v58) >> 4; + BignumMUL(v69, v68, 5, v61); + BignumMULADD(v71, v70, 5, v62, v69); + BignumMULADD(v73, v72, 5, v63, v71); + BignumMULADD(v75, v74, 5, v64, v73); + v76 = 5 * v66 + v75; + v78 = 25 * v67; + BignumADC(v80, carry, v10, v68, 0); + BignumADC(v81, carry, v20, v70, carry); + BignumADC(v82, carry, v30, v72, carry); + BignumADC(v83, carry, v40, v74, carry); + v84 = v60 + v76 + carry; + BignumADC(v85, carry, v80, v78, 0); + BignumADC(v86, carry, v81, 0, carry); + BignumADC(v87, carry, v82, 0, carry); + BignumADC(v88, carry, v83, 0, carry); + v89 = v84 + 0 + carry; + r->w[0] = v85; + r->w[1] = v86; + r->w[2] = v87; + r->w[3] = v88; + r->w[4] = v89; +} + +static void bigval_final_reduce(bigval *n) +{ + BignumInt v0, v1, v2, v3, v4, v5, v6, v7, v9, v10, v11, v12, v13, v14; + BignumInt v15, v16, v17, v18, v19, v20, v22, v23, v24, v25, v26, v27; + BignumCarry carry; + + v0 = n->w[0]; + v1 = n->w[1]; + v2 = n->w[2]; + v3 = n->w[3]; + v4 = n->w[4]; + v5 = (v4) >> 2; + v6 = (v4) & ((((BignumInt)1) << 2)-1); + v7 = 5 * v5; + BignumADC(v9, carry, v0, v7, 0); + BignumADC(v10, carry, v1, 0, carry); + BignumADC(v11, carry, v2, 0, carry); + BignumADC(v12, carry, v3, 0, carry); + v13 = v6 + 0 + carry; + BignumADC(v14, carry, v9, 5, 0); + (void)v14; + BignumADC(v15, carry, v10, 0, carry); + (void)v15; + BignumADC(v16, carry, v11, 0, carry); + (void)v16; + BignumADC(v17, carry, v12, 0, carry); + (void)v17; + v18 = v13 + 0 + carry; + v19 = (v18) >> 2; + v20 = 5 * v19; + BignumADC(v22, carry, v9, v20, 0); + BignumADC(v23, carry, v10, 0, carry); + BignumADC(v24, carry, v11, 0, carry); + BignumADC(v25, carry, v12, 0, carry); + v26 = v13 + 0 + carry; + v27 = (v26) & ((((BignumInt)1) << 2)-1); + n->w[0] = v22; + n->w[1] = v23; + n->w[2] = v24; + n->w[3] = v25; + n->w[4] = v27; +} + +#elif BIGNUM_INT_BITS == 64 + +static void bigval_add(bigval *r, const bigval *a, const bigval *b) +{ + BignumInt v0, v1, v2, v3, v4, v5, v6, v7, v8; + BignumCarry carry; + + v0 = a->w[0]; + v1 = a->w[1]; + v2 = a->w[2]; + v3 = b->w[0]; + v4 = b->w[1]; + v5 = b->w[2]; + BignumADC(v6, carry, v0, v3, 0); + BignumADC(v7, carry, v1, v4, carry); + v8 = v2 + v5 + carry; + r->w[0] = v6; + r->w[1] = v7; + r->w[2] = v8; +} + +static void bigval_mul_mod_p(bigval *r, const bigval *a, const bigval *b) +{ + BignumInt v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14; + BignumInt v15, v16, v17, v18, v19, v20, v21, v22, v24, v25, v26, v27, v28; + BignumInt v29, v30, v31, v32, v33, v34, v36, v38, v39, v40, v41, v42, v43; + BignumCarry carry; + + v0 = a->w[0]; + v1 = a->w[1]; + v2 = a->w[2]; + v3 = b->w[0]; + v4 = b->w[1]; + v5 = b->w[2]; + BignumMUL(v7, v6, v0, v3); + BignumMULADD(v9, v8, v0, v4, v7); + BignumMULADD(v11, v10, v0, v5, v9); + BignumMULADD(v13, v12, v1, v3, v8); + BignumMULADD2(v15, v14, v1, v4, v10, v13); + BignumMULADD2(v17, v16, v1, v5, v11, v15); + BignumMULADD(v19, v18, v2, v3, v14); + BignumMULADD2(v21, v20, v2, v4, v16, v19); + v22 = v2 * v5 + v17 + v21; + v24 = (v18) & ((((BignumInt)1) << 2)-1); + v25 = ((v18) >> 2) | ((v20) << 62); + v26 = ((v20) >> 2) | ((v22) << 62); + v27 = (v22) >> 2; + v28 = (v27) & ((((BignumInt)1) << 2)-1); + v29 = (v22) >> 4; + BignumMUL(v31, v30, 5, v25); + BignumMULADD(v33, v32, 5, v26, v31); + v34 = 5 * v28 + v33; + v36 = 25 * v29; + BignumADC(v38, carry, v6, v30, 0); + BignumADC(v39, carry, v12, v32, carry); + v40 = v24 + v34 + carry; + BignumADC(v41, carry, v38, v36, 0); + BignumADC(v42, carry, v39, 0, carry); + v43 = v40 + 0 + carry; + r->w[0] = v41; + r->w[1] = v42; + r->w[2] = v43; +} + +static void bigval_final_reduce(bigval *n) +{ + BignumInt v0, v1, v2, v3, v4, v5, v7, v8, v9, v10, v11, v12, v13, v14; + BignumInt v16, v17, v18, v19; + BignumCarry carry; + + v0 = n->w[0]; + v1 = n->w[1]; + v2 = n->w[2]; + v3 = (v2) >> 2; + v4 = (v2) & ((((BignumInt)1) << 2)-1); + v5 = 5 * v3; + BignumADC(v7, carry, v0, v5, 0); + BignumADC(v8, carry, v1, 0, carry); + v9 = v4 + 0 + carry; + BignumADC(v10, carry, v7, 5, 0); + (void)v10; + BignumADC(v11, carry, v8, 0, carry); + (void)v11; + v12 = v9 + 0 + carry; + v13 = (v12) >> 2; + v14 = 5 * v13; + BignumADC(v16, carry, v7, v14, 0); + BignumADC(v17, carry, v8, 0, carry); + v18 = v9 + 0 + carry; + v19 = (v18) & ((((BignumInt)1) << 2)-1); + n->w[0] = v16; + n->w[1] = v17; + n->w[2] = v19; +} + +#else +#error Add another bit count to contrib/make1305.py and rerun it +#endif + +struct poly1305 { + unsigned char nonce[16]; + bigval r; + bigval h; + + /* Buffer in case we get less that a multiple of 16 bytes */ + unsigned char buffer[16]; + int bufferIndex; +}; + +static void poly1305_init(struct poly1305 *ctx) +{ + memset(ctx->nonce, 0, 16); + ctx->bufferIndex = 0; + bigval_clear(&ctx->h); +} + +static void poly1305_key(struct poly1305 *ctx, ptrlen key) +{ + assert(key.len == 32); /* Takes a 256 bit key */ + + unsigned char key_copy[16]; + memcpy(key_copy, key.ptr, 16); + + /* Key the MAC itself + * bytes 4, 8, 12 and 16 are required to have their top four bits clear */ + key_copy[3] &= 0x0f; + key_copy[7] &= 0x0f; + key_copy[11] &= 0x0f; + key_copy[15] &= 0x0f; + /* bytes 5, 9 and 13 are required to have their bottom two bits clear */ + key_copy[4] &= 0xfc; + key_copy[8] &= 0xfc; + key_copy[12] &= 0xfc; + bigval_import_le(&ctx->r, key_copy, 16); + smemclr(key_copy, sizeof(key_copy)); + + /* Use second 128 bits as the nonce */ + memcpy(ctx->nonce, (const char *)key.ptr + 16, 16); +} + +/* Feed up to 16 bytes (should only be less for the last chunk) */ +static void poly1305_feed_chunk(struct poly1305 *ctx, + const unsigned char *chunk, int len) +{ + bigval c; + bigval_import_le(&c, chunk, len); + c.w[len / BIGNUM_INT_BYTES] |= + (BignumInt)1 << (8 * (len % BIGNUM_INT_BYTES)); + bigval_add(&c, &c, &ctx->h); + bigval_mul_mod_p(&ctx->h, &c, &ctx->r); +} + +static void poly1305_feed(struct poly1305 *ctx, + const unsigned char *buf, int len) +{ + /* Check for stuff left in the buffer from last time */ + if (ctx->bufferIndex) { + /* Try to fill up to 16 */ + while (ctx->bufferIndex < 16 && len) { + ctx->buffer[ctx->bufferIndex++] = *buf++; + --len; + } + if (ctx->bufferIndex == 16) { + poly1305_feed_chunk(ctx, ctx->buffer, 16); + ctx->bufferIndex = 0; + } + } + + /* Process 16 byte whole chunks */ + while (len >= 16) { + poly1305_feed_chunk(ctx, buf, 16); + len -= 16; + buf += 16; + } + + /* Cache stuff that's left over */ + if (len) { + memcpy(ctx->buffer, buf, len); + ctx->bufferIndex = len; + } +} + +/* Finalise and populate buffer with 16 byte with MAC */ +static void poly1305_finalise(struct poly1305 *ctx, unsigned char *mac) +{ + bigval tmp; + + if (ctx->bufferIndex) { + poly1305_feed_chunk(ctx, ctx->buffer, ctx->bufferIndex); + } + + bigval_import_le(&tmp, ctx->nonce, 16); + bigval_final_reduce(&ctx->h); + bigval_add(&tmp, &tmp, &ctx->h); + bigval_export_le(&tmp, mac, 16); +} + +/* SSH-2 wrapper */ + +struct ccp_context { + struct chacha20 a_cipher; /* Used for length */ + struct chacha20 b_cipher; /* Used for content */ + + /* Cache of the first 4 bytes because they are the sequence number */ + /* Kept in 8 bytes with the top as zero to allow easy passing to setiv */ + int mac_initialised; /* Where we have got to in filling mac_iv */ + unsigned char mac_iv[8]; + + struct poly1305 mac; + + BinarySink_IMPLEMENTATION; + ssh_cipher ciph; + ssh2_mac mac_if; +}; + +static ssh2_mac *poly_ssh2_new( + const ssh2_macalg *alg, ssh_cipher *cipher) +{ + struct ccp_context *ctx = container_of(cipher, struct ccp_context, ciph); + ctx->mac_if.vt = alg; + BinarySink_DELEGATE_INIT(&ctx->mac_if, ctx); + return &ctx->mac_if; +} + +static void poly_ssh2_free(ssh2_mac *mac) +{ + /* Not allocated, just forwarded, no need to free */ +} + +static void poly_setkey(ssh2_mac *mac, ptrlen key) +{ + /* Uses the same context as ChaCha20, so ignore */ +} + +static void poly_start(ssh2_mac *mac) +{ + struct ccp_context *ctx = container_of(mac, struct ccp_context, mac_if); + + ctx->mac_initialised = 0; + memset(ctx->mac_iv, 0, 8); + poly1305_init(&ctx->mac); +} + +static void poly_BinarySink_write(BinarySink *bs, const void *blkv, size_t len) +{ + struct ccp_context *ctx = BinarySink_DOWNCAST(bs, struct ccp_context); + const unsigned char *blk = (const unsigned char *)blkv; + + /* First 4 bytes are the IV */ + while (ctx->mac_initialised < 4 && len) { + ctx->mac_iv[7 - ctx->mac_initialised] = *blk++; + ++ctx->mac_initialised; + --len; + } + + /* Initialise the IV if needed */ + if (ctx->mac_initialised == 4) { + chacha20_iv(&ctx->b_cipher, ctx->mac_iv); + ++ctx->mac_initialised; /* Don't do it again */ + + /* Do first rotation */ + chacha20_round(&ctx->b_cipher); + + /* Set the poly key */ + poly1305_key(&ctx->mac, make_ptrlen(ctx->b_cipher.current, 32)); + + /* Set the first round as used */ + ctx->b_cipher.currentIndex = 64; + } + + /* Update the MAC with anything left */ + if (len) { + poly1305_feed(&ctx->mac, blk, len); + } +} + +static void poly_genresult(ssh2_mac *mac, unsigned char *blk) +{ + struct ccp_context *ctx = container_of(mac, struct ccp_context, mac_if); + poly1305_finalise(&ctx->mac, blk); +} + +static const char *poly_text_name(ssh2_mac *mac) +{ + return "Poly1305"; +} + +const ssh2_macalg ssh2_poly1305 = { + .new = poly_ssh2_new, + .free = poly_ssh2_free, + .setkey = poly_setkey, + .start = poly_start, + .genresult = poly_genresult, + .text_name = poly_text_name, + .name = "", + .etm_name = "", /* Not selectable individually, just part of + * ChaCha20-Poly1305 */ + .len = 16, + .keylen = 0, +}; + +static ssh_cipher *ccp_new(const ssh_cipheralg *alg) +{ + struct ccp_context *ctx = snew(struct ccp_context); + BinarySink_INIT(ctx, poly_BinarySink_write); + poly1305_init(&ctx->mac); + ctx->ciph.vt = alg; + return &ctx->ciph; +} + +static void ccp_free(ssh_cipher *cipher) +{ + struct ccp_context *ctx = container_of(cipher, struct ccp_context, ciph); + smemclr(&ctx->a_cipher, sizeof(ctx->a_cipher)); + smemclr(&ctx->b_cipher, sizeof(ctx->b_cipher)); + smemclr(&ctx->mac, sizeof(ctx->mac)); + sfree(ctx); +} + +static void ccp_iv(ssh_cipher *cipher, const void *iv) +{ + /* struct ccp_context *ctx = + container_of(cipher, struct ccp_context, ciph); */ + /* IV is set based on the sequence number */ +} + +static void ccp_key(ssh_cipher *cipher, const void *vkey) +{ + const unsigned char *key = (const unsigned char *)vkey; + struct ccp_context *ctx = container_of(cipher, struct ccp_context, ciph); + /* Initialise the a_cipher (for decrypting lengths) with the first 256 bits */ + chacha20_key(&ctx->a_cipher, key + 32); + /* Initialise the b_cipher (for content and MAC) with the second 256 bits */ + chacha20_key(&ctx->b_cipher, key); +} + +static void ccp_encrypt(ssh_cipher *cipher, void *blk, int len) +{ + struct ccp_context *ctx = container_of(cipher, struct ccp_context, ciph); + chacha20_encrypt(&ctx->b_cipher, blk, len); +} + +static void ccp_decrypt(ssh_cipher *cipher, void *blk, int len) +{ + struct ccp_context *ctx = container_of(cipher, struct ccp_context, ciph); + chacha20_decrypt(&ctx->b_cipher, blk, len); +} + +static void ccp_length_op(struct ccp_context *ctx, void *blk, int len, + unsigned long seq) +{ + unsigned char iv[8]; + /* + * According to RFC 4253 (section 6.4), the packet sequence number wraps + * at 2^32, so its 32 high-order bits will always be zero. + */ + PUT_32BIT_LSB_FIRST(iv, 0); + PUT_32BIT_LSB_FIRST(iv + 4, seq); + chacha20_iv(&ctx->a_cipher, iv); + chacha20_iv(&ctx->b_cipher, iv); + /* Reset content block count to 1, as the first is the key for Poly1305 */ + ++ctx->b_cipher.state[12]; + smemclr(iv, sizeof(iv)); +} + +static void ccp_encrypt_length(ssh_cipher *cipher, void *blk, int len, + unsigned long seq) +{ + struct ccp_context *ctx = container_of(cipher, struct ccp_context, ciph); + ccp_length_op(ctx, blk, len, seq); + chacha20_encrypt(&ctx->a_cipher, blk, len); +} + +static void ccp_decrypt_length(ssh_cipher *cipher, void *blk, int len, + unsigned long seq) +{ + struct ccp_context *ctx = container_of(cipher, struct ccp_context, ciph); + ccp_length_op(ctx, blk, len, seq); + chacha20_decrypt(&ctx->a_cipher, blk, len); +} + +const ssh_cipheralg ssh2_chacha20_poly1305 = { + .new = ccp_new, + .free = ccp_free, + .setiv = ccp_iv, + .setkey = ccp_key, + .encrypt = ccp_encrypt, + .decrypt = ccp_decrypt, + .encrypt_length = ccp_encrypt_length, + .decrypt_length = ccp_decrypt_length, + .ssh2_id = "chacha20-poly1305@openssh.com", + .blksize = 1, + .real_keybits = 512, + .padded_keybytes = 64, + .flags = SSH_CIPHER_SEPARATE_LENGTH, + .text_name = "ChaCha20", + .required_mac = &ssh2_poly1305, +}; + +static const ssh_cipheralg *const ccp_list[] = { + &ssh2_chacha20_poly1305 +}; + +const ssh2_ciphers ssh2_ccp = { lenof(ccp_list), ccp_list }; diff --git a/crypto/crc32.c b/crypto/crc32.c new file mode 100644 index 00000000..bd874433 --- /dev/null +++ b/crypto/crc32.c @@ -0,0 +1,113 @@ +/* + * CRC32 implementation, as used in SSH-1. + * + * (This is not, of course, a cryptographic function! It lives in the + * 'crypto' directory because SSH-1 uses it _as if_ it was crypto: it + * handles sensitive data, and we implement it with care for side + * channels.) + * + * This particular form of the CRC uses the polynomial + * P(x) = 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+1 + * and represents polynomials in bit-reversed form, so that the x^0 + * coefficient (constant term) appears in the bit with place value + * 2^31, and the x^31 coefficient in the bit with place value 2^0. In + * this representation, (x^32 mod P) = 0xEDB88320, so multiplying the + * current state by x is done by shifting right by one bit, and XORing + * that constant into the result if the bit shifted out was 1. + * + * There's a bewildering array of subtly different variants of CRC out + * there, using different polynomials, both bit orders, and varying + * the start and end conditions. There are catalogue websites such as + * http://reveng.sourceforge.net/crc-catalogue/ , which generally seem + * to have the convention of indexing CRCs by their 'check value', + * defined as whatever you get if you hash the 9-byte test string + * "123456789". + * + * The crc32_rfc1662() function below, which starts off the CRC state + * at 0xFFFFFFFF and complements it after feeding all the data, gives + * the check value 0xCBF43926, and matches the hash function that the + * above catalogue refers to as "CRC-32/ISO-HDLC"; among other things, + * it's also the "FCS-32" checksum described in RFC 1662 section C.3 + * (hence the name I've given it here). + * + * The crc32_ssh1() function implements the variant form used by + * SSH-1, which uses the same update function, but starts the state at + * zero and doesn't complement it at the end of the computation. The + * check value for that version is 0x2DFD2D88, which that CRC + * catalogue doesn't list at all. + */ + +#include +#include + +#include "ssh.h" + +/* + * Multiply a CRC value by x^4. This implementation strategy avoids + * using a lookup table (which would be a side-channel hazard, since + * SSH-1 applies this CRC to decrypted session data). + * + * The basic idea is that you'd like to "multiply" the shifted-out 4 + * bits by the CRC polynomial value 0xEDB88320, or rather by that + * value shifted right 3 bits (since you want the _last_ bit shifted + * out, i.e. the one originally at the 2^3 position, to generate + * 0xEDB88320 itself). But the scare-quoted "multiply" would have to + * be a multiplication of polynomials over GF(2), which differs from + * integer multiplication in that you don't have any carries. In other + * words, you make a copy of one input shifted left by the index of + * each set bit in the other, so that adding them all together would + * give you the ordinary integer product, and then you XOR them + * together instead. + * + * With a 4-bit multiplier, the two kinds of multiplication coincide + * provided the multiplicand has no two set bits at positions + * differing by less than 4, because then no two copies of the + * multiplier can overlap to generate a carry. So I break up the + * intended multiplicand K = 0xEDB88320 >> 3 into three sub-constants + * a,b,c with that property, such that a^b^c = K. Then I can multiply + * m by each of them separately, and XOR together the results. + */ +static inline uint32_t crc32_shift_4(uint32_t v) +{ + const uint32_t a = 0x11111044, b = 0x08840020, c = 0x04220000; + uint32_t m = v & 0xF; + return (v >> 4) ^ (a*m) ^ (b*m) ^ (c*m); +} + +/* + * The 8-bit shift you need every time you absorb an input byte, + * implemented simply by iterating the 4-bit shift twice. + */ +static inline uint32_t crc32_shift_8(uint32_t v) +{ + return crc32_shift_4(crc32_shift_4(v)); +} + +/* + * Update an existing hash value with extra bytes of data. + */ +uint32_t crc32_update(uint32_t crc, ptrlen data) +{ + const uint8_t *p = (const uint8_t *)data.ptr; + for (size_t len = data.len; len-- > 0 ;) + crc = crc32_shift_8(crc ^ *p++); + return crc; +} + +/* + * The SSH-1 variant of CRC-32. + */ +uint32_t crc32_ssh1(ptrlen data) +{ + return crc32_update(0, data); +} + +/* + * The official version of CRC-32. Nothing in PuTTY proper uses this, + * but it's useful to expose it to testcrypt so that we can implement + * standard test vectors. + */ +uint32_t crc32_rfc1662(ptrlen data) +{ + return crc32_update(0xFFFFFFFF, data) ^ 0xFFFFFFFF; +} diff --git a/crypto/des.c b/crypto/des.c new file mode 100644 index 00000000..e8a26f0a --- /dev/null +++ b/crypto/des.c @@ -0,0 +1,1048 @@ +/* + * Implementation of DES. + */ + +/* + * Background + * ---------- + * + * The basic structure of DES is a Feistel network: the 64-bit cipher + * block is divided into two 32-bit halves L and R, and in each round, + * a mixing function is applied to one of them, the result is XORed + * into the other, and then the halves are swapped so that the other + * one will be the input to the mixing function next time. (This + * structure guarantees reversibility no matter whether the mixing + * function itself is bijective.) + * + * The mixing function for DES goes like this: + * + Extract eight contiguous 6-bit strings from the 32-bit word. + * They start at positions 4 bits apart, so each string overlaps + * the next one by one bit. At least one has to wrap cyclically + * round the end of the word. + * + XOR each of those strings with 6 bits of data from the key + * schedule (which consists of 8 x 6-bit strings per round). + * + Use the resulting 6-bit numbers as the indices into eight + * different lookup tables ('S-boxes'), each of which delivers a + * 4-bit output. + * + Concatenate those eight 4-bit values into a 32-bit word. + * + Finally, apply a fixed permutation P to that word. + * + * DES adds one more wrinkle on top of this structure, which is to + * conjugate it by a bitwise permutation of the cipher block. That is, + * before starting the main cipher rounds, the input bits are permuted + * according to a 64-bit permutation called IP, and after the rounds + * are finished, the output bits are permuted back again by applying + * the inverse of IP. + * + * This gives a lot of leeway to redefine the components of the cipher + * without actually changing the input and output. You could permute + * the bits in the output of any or all of the S-boxes, or reorder the + * S-boxes among themselves, and adjust the following permutation P to + * compensate. And you could adjust IP by post-composing a rotation of + * each 32-bit half, and adjust the starting offsets of the 6-bit + * S-box indices to compensate. + * + * test/desref.py demonstrates this by providing two equivalent forms + * of the cipher, called DES and SGTDES, which give the same output. + * DES is the form described in the original spec: if you make it + * print diagnostic output during the cipher and check it against the + * original, you should recognise the S-box outputs as matching the + * ones you expect. But SGTDES, which I egotistically name after + * myself, is much closer to the form implemented here: I've changed + * the permutation P to suit my implementation strategy and + * compensated by permuting the S-boxes, and also I've added a + * rotation right by 1 bit to IP so that only one S-box index has to + * wrap round the word and also so that the indices are nicely aligned + * for the constant-time selection system I'm using. + */ + +#include + +#include "ssh.h" +#include "mpint_i.h" /* we reuse the BignumInt system */ + +/* If you compile with -DDES_DIAGNOSTICS, intermediate results will be + * sent to debug() (so you also need to compile with -DDEBUG). + * Otherwise this ifdef will condition away all the debug() calls. */ +#ifndef DES_DIAGNOSTICS +#undef debug +#define debug(...) ((void)0) +#endif + +/* + * General utility functions. + */ +static inline uint32_t rol(uint32_t x, unsigned c) +{ + return (x << (31 & c)) | (x >> (31 & -c)); +} +static inline uint32_t ror(uint32_t x, unsigned c) +{ + return rol(x, -c); +} + +/* + * The hard part of doing DES in constant time is the S-box lookup. + * + * My strategy is to iterate over the whole lookup table! That's slow, + * but I don't see any way to avoid _something_ along those lines: in + * every round, every entry in every S-box is potentially needed, and + * if you can't change your memory access pattern based on the input + * data, it follows that you have to read a quantity of information + * equal to the size of all the S-boxes. (Unless they were to turn out + * to be significantly compressible, but I for one couldn't show them + * to be.) + * + * In more detail, I construct a sort of counter-based 'selection + * gadget', which is 15 bits wide and starts off with the top bit + * zero, the next eight bits all 1, and the bottom six set to the + * input S-box index: + * + * 011111111xxxxxx + * + * Now if you add 1 in the lowest bit position, then either it carries + * into the top section (resetting it to 100000000), or it doesn't do + * that yet. If you do that 64 times, then it will _guarantee_ to have + * ticked over into 100000000. In between those increments, the eight + * bits that started off as 11111111 will have stayed that way for + * some number of iterations and then become 00000000, and exactly how + * many iterations depends on the input index. + * + * The purpose of the 0 bit at the top is to absorb the carry when the + * switch happens, which means you can pack more than one gadget into + * the same machine word and have them all work in parallel without + * each one intefering with the next. + * + * The next step is to use each of those 8-bit segments as a bit mask: + * each one is ANDed with a lookup table entry, and all the results + * are XORed together. So you end up with the bitwise XOR of some + * initial segment of the table entries. And the stored S-box tables + * are transformed in such a way that the real S-box values are given + * not by the individual entries, but by the cumulative XORs + * constructed in this way. + * + * A refinement is that I increment each gadget by 2 rather than 1 + * each time, so I only iterate 32 times instead of 64. That's why + * there are 8 selection bits instead of 4: each gadget selects enough + * bits to reconstruct _two_ S-box entries, for a pair of indices + * (2n,2n+1), and then finally I use the low bit of the index to do a + * parallel selection between each of those pairs. + * + * The selection gadget is not quite 16 bits wide. So you can fit four + * of them across a 64-bit word at 16-bit intervals, which is also + * convenient because the place the S-box indices are coming from also + * has pairs of them separated by 16-bit distances, so it's easy to + * copy them into the gadgets in the first place. + */ + +/* + * The S-box data. Each pair of nonzero columns here describes one of + * the S-boxes, corresponding to the SGTDES tables in test/desref.py, + * under the following transformation. + * + * Take S-box #3 as an example. Its values in successive rows of this + * table are eb,e8,54,3d, ... So the cumulative XORs of initial + * sequences of those values are eb,(eb^e8),(eb^e8^54), ... which + * comes to eb,03,57,... Of _those_ values, the top nibble (e,0,5,...) + * gives the even-numbered entries in the S-box, in _reverse_ order + * (because a lower input index selects the XOR of a longer + * subsequence). The odd-numbered entries are given by XORing the two + * digits together: (e^b),(0^3),(5^7),... = 5,3,2,... And indeed, if + * you check SGTDES.sboxes[3] you find it ends ... 52 03 e5. + */ +#define SBOX_ITERATION(X) \ + /* 66 22 44 00 77 33 55 11 */ \ + X(0xf600970083008500, 0x0e00eb007b002e00) \ + X(0xda00e4009000e000, 0xad00e800a700b400) \ + X(0x1a009d003f003600, 0xf60054004300cd00) \ + X(0xaf00c500e900a900, 0x63003d00f2005900) \ + X(0xf300750079001400, 0x80005000a2008900) \ + X(0xa100d400d6007b00, 0xd3009000d300e100) \ + X(0x450087002600ac00, 0xae003c0031009c00) \ + X(0xd000b100b6003600, 0x3e006f0092005900) \ + X(0x4d008a0026001000, 0x89007a00b8004a00) \ + X(0xca00f5003f00ac00, 0x6f00f0003c009400) \ + X(0x92008d0090001000, 0x8c00c600ce004a00) \ + X(0xe2005900e9006d00, 0x790078007800fa00) \ + X(0x1300b10090008d00, 0xa300170027001800) \ + X(0xc70058005f006a00, 0x9c00c100e0006300) \ + X(0x9b002000f000f000, 0xf70057001600f900) \ + X(0xeb00b0009000af00, 0xa9006300b0005800) \ + X(0xa2001d00cf000000, 0x3800b00066000000) \ + X(0xf100da007900d000, 0xbc00790094007900) \ + X(0x570015001900ad00, 0x6f00ef005100cb00) \ + X(0xc3006100e9006d00, 0xc000b700f800f200) \ + X(0x1d005800b600d000, 0x67004d00cd002c00) \ + X(0xf400b800d600e000, 0x5e00a900b000e700) \ + X(0x5400d1003f009c00, 0xc90069002c005300) \ + X(0xe200e50060005900, 0x6a00b800c500f200) \ + X(0xdf0047007900d500, 0x7000ec004c00ea00) \ + X(0x7100d10060009c00, 0x3f00b10095005e00) \ + X(0x82008200f0002000, 0x87001d00cd008000) \ + X(0xd0007000af00c000, 0xe200be006100f200) \ + X(0x8000930060001000, 0x36006e0081001200) \ + X(0x6500a300d600ac00, 0xcf003d007d00c000) \ + X(0x9000700060009800, 0x62008100ad009200) \ + X(0xe000e4003f00f400, 0x5a00ed009000f200) \ + /* end of list */ + +/* + * The S-box mapping function. Expects two 32-bit input words: si6420 + * contains the table indices for S-boxes 0,2,4,6 with their low bits + * starting at position 2 (for S-box 0) and going up in steps of 8. + * si7531 has indices 1,3,5,7 in the same bit positions. + */ +static inline uint32_t des_S(uint32_t si6420, uint32_t si7531) +{ + debug("sindices: %02x %02x %02x %02x %02x %02x %02x %02x\n", + 0x3F & (si6420 >> 2), 0x3F & (si7531 >> 2), + 0x3F & (si6420 >> 10), 0x3F & (si7531 >> 10), + 0x3F & (si6420 >> 18), 0x3F & (si7531 >> 18), + 0x3F & (si6420 >> 26), 0x3F & (si7531 >> 26)); + +#ifdef SIXTY_FOUR_BIT + /* + * On 64-bit machines, we store the table in exactly the form + * shown above, and make two 64-bit words containing four + * selection gadgets each. + */ + + /* Set up the gadgets. The 'cNNNN' variables will be gradually + * incremented, and the bits in positions FF00FF00FF00FF00 will + * act as selectors for the words in the table. + * + * A side effect of moving the input indices further apart is that + * they change order, because it's easier to keep a pair that were + * originally 16 bits apart still 16 bits apart, which now makes + * them adjacent instead of separated by one. So the fact that + * si6420 turns into c6240 (with the 2,4 reversed) is not a typo! + * This will all be undone when we rebuild the output word later. + */ + uint64_t c6240 = ((si6420 | ((uint64_t)si6420 << 24)) + & 0x00FC00FC00FC00FC) | 0xFF00FF00FF00FF00; + uint64_t c7351 = ((si7531 | ((uint64_t)si7531 << 24)) + & 0x00FC00FC00FC00FC) | 0xFF00FF00FF00FF00; + debug("S in: c6240=%016"PRIx64" c7351=%016"PRIx64"\n", c6240, c7351); + + /* Iterate over the table. The 'sNNNN' variables accumulate the + * XOR of all the table entries not masked out. */ + static const struct tbl { uint64_t t6240, t7351; } tbl[32] = { +#define TABLE64(a, b) { a, b }, + SBOX_ITERATION(TABLE64) +#undef TABLE64 + }; + uint64_t s6240 = 0, s7351 = 0; + for (const struct tbl *t = tbl, *limit = tbl + 32; t < limit; t++) { + s6240 ^= c6240 & t->t6240; c6240 += 0x0008000800080008; + s7351 ^= c7351 & t->t7351; c7351 += 0x0008000800080008; + } + debug("S out: s6240=%016"PRIx64" s7351=%016"PRIx64"\n", s6240, s7351); + + /* Final selection between each even/odd pair: mask off the low + * bits of all the input indices (which haven't changed throughout + * the iteration), and multiply by a bit mask that will turn each + * set bit into a mask covering the upper nibble of the selected + * pair. Then use those masks to control which set of lower + * nibbles is XORed into the upper nibbles. */ + s6240 ^= (s6240 << 4) & ((0xf000/0x004) * (c6240 & 0x0004000400040004)); + s7351 ^= (s7351 << 4) & ((0xf000/0x004) * (c7351 & 0x0004000400040004)); + + /* Now the eight final S-box outputs are in the upper nibble of + * each selection position. Mask away the rest of the clutter. */ + s6240 &= 0xf000f000f000f000; + s7351 &= 0xf000f000f000f000; + debug("s0=%x s1=%x s2=%x s3=%x s4=%x s5=%x s6=%x s7=%x\n", + (unsigned)(0xF & (s6240 >> 12)), + (unsigned)(0xF & (s7351 >> 12)), + (unsigned)(0xF & (s6240 >> 44)), + (unsigned)(0xF & (s7351 >> 44)), + (unsigned)(0xF & (s6240 >> 28)), + (unsigned)(0xF & (s7351 >> 28)), + (unsigned)(0xF & (s6240 >> 60)), + (unsigned)(0xF & (s7351 >> 60))); + + /* Combine them all into a single 32-bit output word, which will + * come out in the order 76543210. */ + uint64_t combined = (s6240 >> 12) | (s7351 >> 8); + return combined | (combined >> 24); + +#else /* SIXTY_FOUR_BIT */ + /* + * For 32-bit platforms, we do the same thing but in four 32-bit + * words instead of two 64-bit ones, so the CPU doesn't have to + * waste time propagating carries or shifted bits between the two + * halves of a uint64 that weren't needed anyway. + */ + + /* Set up the gadgets */ + uint32_t c40 = ((si6420 ) & 0x00FC00FC) | 0xFF00FF00; + uint32_t c62 = ((si6420 >> 8) & 0x00FC00FC) | 0xFF00FF00; + uint32_t c51 = ((si7531 ) & 0x00FC00FC) | 0xFF00FF00; + uint32_t c73 = ((si7531 >> 8) & 0x00FC00FC) | 0xFF00FF00; + debug("S in: c40=%08"PRIx32" c62=%08"PRIx32 + " c51=%08"PRIx32" c73=%08"PRIx32"\n", c40, c62, c51, c73); + + /* Iterate over the table */ + static const struct tbl { uint32_t t40, t62, t51, t73; } tbl[32] = { +#define TABLE32(a, b) { ((uint32_t)a), (a>>32), ((uint32_t)b), (b>>32) }, + SBOX_ITERATION(TABLE32) +#undef TABLE32 + }; + uint32_t s40 = 0, s62 = 0, s51 = 0, s73 = 0; + for (const struct tbl *t = tbl, *limit = tbl + 32; t < limit; t++) { + s40 ^= c40 & t->t40; c40 += 0x00080008; + s62 ^= c62 & t->t62; c62 += 0x00080008; + s51 ^= c51 & t->t51; c51 += 0x00080008; + s73 ^= c73 & t->t73; c73 += 0x00080008; + } + debug("S out: s40=%08"PRIx32" s62=%08"PRIx32 + " s51=%08"PRIx32" s73=%08"PRIx32"\n", s40, s62, s51, s73); + + /* Final selection within each pair */ + s40 ^= (s40 << 4) & ((0xf000/0x004) * (c40 & 0x00040004)); + s62 ^= (s62 << 4) & ((0xf000/0x004) * (c62 & 0x00040004)); + s51 ^= (s51 << 4) & ((0xf000/0x004) * (c51 & 0x00040004)); + s73 ^= (s73 << 4) & ((0xf000/0x004) * (c73 & 0x00040004)); + + /* Clean up the clutter */ + s40 &= 0xf000f000; + s62 &= 0xf000f000; + s51 &= 0xf000f000; + s73 &= 0xf000f000; + debug("s0=%x s1=%x s2=%x s3=%x s4=%x s5=%x s6=%x s7=%x\n", + (unsigned)(0xF & (s40 >> 12)), + (unsigned)(0xF & (s51 >> 12)), + (unsigned)(0xF & (s62 >> 12)), + (unsigned)(0xF & (s73 >> 12)), + (unsigned)(0xF & (s40 >> 28)), + (unsigned)(0xF & (s51 >> 28)), + (unsigned)(0xF & (s62 >> 28)), + (unsigned)(0xF & (s73 >> 28))); + + /* Recombine and return */ + return (s40 >> 12) | (s62 >> 4) | (s51 >> 8) | (s73); + +#endif /* SIXTY_FOUR_BIT */ + +} + +/* + * Now for the permutation P. The basic strategy here is to use a + * Benes network: in each stage, the bit at position i is allowed to + * either stay where it is or swap with i ^ D, where D is a power of 2 + * that varies with each phase. (So when D=1, pairs of the form + * {2n,2n+1} can swap; when D=2, the pairs are {4n+j,4n+j+2} for + * j={0,1}, and so on.) + * + * You can recursively construct a Benes network for an arbitrary + * permutation, in which the values of D iterate across all the powers + * of 2 less than the permutation size and then go back again. For + * example, the typical presentation for 32 bits would have D iterate + * over 16,8,4,2,1,2,4,8,16, and there's an easy algorithm that can + * express any permutation in that form by deciding which pairs of + * bits to swap in the outer pair of stages and then recursing to do + * all the stages in between. + * + * Actually implementing the swaps is easy when they're all between + * bits at the same separation: make the value x ^ (x >> D), mask out + * just the bits in the low position of a pair that needs to swap, and + * then use the resulting value y to make x ^ y ^ (y << D) which is + * the swapped version. + * + * In this particular case, I processed the bit indices in the other + * order (going 1,2,4,8,16,8,4,2,1), which makes no significant + * difference to the construction algorithm (it's just a relabelling), + * but it now means that the first two steps only permute entries + * within the output of each S-box - and therefore we can leave them + * completely out, in favour of just defining the S-boxes so that + * those permutation steps are already applied. Furthermore, by + * exhaustive search over the rest of the possible bit-orders for each + * S-box, I was able to find a version of P which could be represented + * in such a way that two further phases had all their control bits + * zero and could be skipped. So the number of swap stages is reduced + * to 5 from the 9 that might have been needed. + */ + +static inline uint32_t des_benes_step(uint32_t v, unsigned D, uint32_t mask) +{ + uint32_t diff = (v ^ (v >> D)) & mask; + return v ^ diff ^ (diff << D); +} + +static inline uint32_t des_P(uint32_t v_orig) +{ + uint32_t v = v_orig; + + /* initial stages with distance 1,2 are part of the S-box data table */ + v = des_benes_step(v, 4, 0x07030702); + v = des_benes_step(v, 8, 0x004E009E); + v = des_benes_step(v, 16, 0x0000D9D3); +/* v = des_benes_step(v, 8, 0x00000000); no-op, so we can skip it */ + v = des_benes_step(v, 4, 0x05040004); +/* v = des_benes_step(v, 2, 0x00000000); no-op, so we can skip it */ + v = des_benes_step(v, 1, 0x04045015); + + debug("P(%08"PRIx32") = %08"PRIx32"\n", v_orig, v); + + return v; +} + +/* + * Putting the S and P functions together, and adding in the round key + * as well, gives us the full mixing function f. + */ + +static inline uint32_t des_f(uint32_t R, uint32_t K7531, uint32_t K6420) +{ + uint32_t s7531 = R ^ K7531, s6420 = rol(R, 4) ^ K6420; + return des_P(des_S(s6420, s7531)); +} + +/* + * The key schedule, and the function to set it up. + */ + +typedef struct des_keysched des_keysched; +struct des_keysched { + uint32_t k7531[16], k6420[16]; +}; + +/* + * Simplistic function to select an arbitrary sequence of bits from + * one value and glue them together into another value. bitnums[] + * gives the sequence of bit indices of the input, from the highest + * output bit downwards. An index of -1 means that output bit is left + * at zero. + * + * This function is only used during key setup, so it doesn't need to + * be highly optimised. + */ +static inline uint64_t bitsel( + uint64_t input, const int8_t *bitnums, size_t size) +{ + uint64_t ret = 0; + while (size-- > 0) { + int bitpos = *bitnums++; + ret <<= 1; + if (bitpos >= 0) + ret |= 1 & (input >> bitpos); + } + return ret; +} + +static void des_key_setup(uint64_t key, des_keysched *sched) +{ + static const int8_t PC1[] = { + 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, + -1, -1, -1, -1, + 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, + }; + static const int8_t PC2_7531[] = { + 46, 43, 49, 36, 59, 55, -1, -1, /* index into S-box 7 */ + 37, 41, 48, 56, 34, 52, -1, -1, /* index into S-box 5 */ + 15, 4, 25, 19, 9, 1, -1, -1, /* index into S-box 3 */ + 12, 7, 17, 0, 22, 3, -1, -1, /* index into S-box 1 */ + }; + static const int8_t PC2_6420[] = { + 57, 32, 45, 54, 39, 50, -1, -1, /* index into S-box 6 */ + 44, 53, 33, 40, 47, 58, -1, -1, /* index into S-box 4 */ + 26, 16, 5, 11, 23, 8, -1, -1, /* index into S-box 2 */ + 10, 14, 6, 20, 27, 24, -1, -1, /* index into S-box 0 */ + }; + static const int leftshifts[] = {1,1,2,2,2,2,2,2,1,2,2,2,2,2,2,1}; + + /* Select 56 bits from the 64-bit input key integer (the low bit + * of each input byte is unused), into a word consisting of two + * 28-bit integers starting at bits 0 and 32. */ + uint64_t CD = bitsel(key, PC1, lenof(PC1)); + + for (size_t i = 0; i < 16; i++) { + /* Rotate each 28-bit half of CD left by 1 or 2 bits (varying + * between rounds) */ + CD <<= leftshifts[i]; + CD = (CD & 0x0FFFFFFF0FFFFFFF) | ((CD & 0xF0000000F0000000) >> 28); + + /* Select key bits from the rotated word to use during the + * actual cipher */ + sched->k7531[i] = bitsel(CD, PC2_7531, lenof(PC2_7531)); + sched->k6420[i] = bitsel(CD, PC2_6420, lenof(PC2_6420)); + } +} + +/* + * Helper routines for dealing with 64-bit blocks in the form of an L + * and R word. + */ + +typedef struct LR LR; +struct LR { uint32_t L, R; }; + +static inline LR des_load_lr(const void *vp) +{ + const uint8_t *p = (const uint8_t *)vp; + LR out; + out.L = GET_32BIT_MSB_FIRST(p); + out.R = GET_32BIT_MSB_FIRST(p+4); + return out; +} + +static inline void des_store_lr(void *vp, LR lr) +{ + uint8_t *p = (uint8_t *)vp; + PUT_32BIT_MSB_FIRST(p, lr.L); + PUT_32BIT_MSB_FIRST(p+4, lr.R); +} + +static inline LR des_xor_lr(LR a, LR b) +{ + a.L ^= b.L; + a.R ^= b.R; + return a; +} + +static inline LR des_swap_lr(LR in) +{ + LR out; + out.L = in.R; + out.R = in.L; + return out; +} + +/* + * The initial and final permutations of official DES are in a + * restricted form, in which the 'before' and 'after' positions of a + * given data bit are derived from each other by permuting the bits of + * the _index_ and flipping some of them. This allows the permutation + * to be performed effectively by a method that looks rather like + * _half_ of a general Benes network, because the restricted form + * means only half of it is actually needed. + * + * _Our_ initial and final permutations include a rotation by 1 bit, + * but it's still easier to just suffix that to the standard IP/FP + * than to regenerate everything using a more general method. + * + * Because we're permuting 64 bits in this case, between two 32-bit + * words, there's a separate helper function for this code that + * doesn't look quite like des_benes_step() above. + */ + +static inline void des_bitswap_IP_FP(uint32_t *L, uint32_t *R, + unsigned D, uint32_t mask) +{ + uint32_t diff = mask & ((*R >> D) ^ *L); + *R ^= diff << D; + *L ^= diff; +} + +static inline LR des_IP(LR lr) +{ + des_bitswap_IP_FP(&lr.R, &lr.L, 4, 0x0F0F0F0F); + des_bitswap_IP_FP(&lr.R, &lr.L, 16, 0x0000FFFF); + des_bitswap_IP_FP(&lr.L, &lr.R, 2, 0x33333333); + des_bitswap_IP_FP(&lr.L, &lr.R, 8, 0x00FF00FF); + des_bitswap_IP_FP(&lr.R, &lr.L, 1, 0x55555555); + + lr.L = ror(lr.L, 1); + lr.R = ror(lr.R, 1); + + return lr; +} + +static inline LR des_FP(LR lr) +{ + lr.L = rol(lr.L, 1); + lr.R = rol(lr.R, 1); + + des_bitswap_IP_FP(&lr.R, &lr.L, 1, 0x55555555); + des_bitswap_IP_FP(&lr.L, &lr.R, 8, 0x00FF00FF); + des_bitswap_IP_FP(&lr.L, &lr.R, 2, 0x33333333); + des_bitswap_IP_FP(&lr.R, &lr.L, 16, 0x0000FFFF); + des_bitswap_IP_FP(&lr.R, &lr.L, 4, 0x0F0F0F0F); + + return lr; +} + +/* + * The main cipher functions, which are identical except that they use + * the key schedule in opposite orders. + * + * We provide a version without the initial and final permutations, + * for use in triple-DES mode (no sense undoing and redoing it in + * between the phases). + */ + +static inline LR des_round(LR in, const des_keysched *sched, size_t round) +{ + LR out; + out.L = in.R; + out.R = in.L ^ des_f(in.R, sched->k7531[round], sched->k6420[round]); + return out; +} + +static inline LR des_inner_cipher(LR lr, const des_keysched *sched, + size_t start, size_t step) +{ + lr = des_round(lr, sched, start+0x0*step); + lr = des_round(lr, sched, start+0x1*step); + lr = des_round(lr, sched, start+0x2*step); + lr = des_round(lr, sched, start+0x3*step); + lr = des_round(lr, sched, start+0x4*step); + lr = des_round(lr, sched, start+0x5*step); + lr = des_round(lr, sched, start+0x6*step); + lr = des_round(lr, sched, start+0x7*step); + lr = des_round(lr, sched, start+0x8*step); + lr = des_round(lr, sched, start+0x9*step); + lr = des_round(lr, sched, start+0xa*step); + lr = des_round(lr, sched, start+0xb*step); + lr = des_round(lr, sched, start+0xc*step); + lr = des_round(lr, sched, start+0xd*step); + lr = des_round(lr, sched, start+0xe*step); + lr = des_round(lr, sched, start+0xf*step); + return des_swap_lr(lr); +} + +static inline LR des_full_cipher(LR lr, const des_keysched *sched, + size_t start, size_t step) +{ + lr = des_IP(lr); + lr = des_inner_cipher(lr, sched, start, step); + lr = des_FP(lr); + return lr; +} + +/* + * Parameter pairs for the start,step arguments to the cipher routines + * above, causing them to use the same key schedule in opposite orders. + */ +#define ENCIPHER 0, 1 /* for encryption */ +#define DECIPHER 15, -1 /* for decryption */ + +/* ---------------------------------------------------------------------- + * Single-DES + */ + +struct des_cbc_ctx { + des_keysched sched; + LR iv; + ssh_cipher ciph; +}; + +static ssh_cipher *des_cbc_new(const ssh_cipheralg *alg) +{ + struct des_cbc_ctx *ctx = snew(struct des_cbc_ctx); + ctx->ciph.vt = alg; + return &ctx->ciph; +} + +static void des_cbc_free(ssh_cipher *ciph) +{ + struct des_cbc_ctx *ctx = container_of(ciph, struct des_cbc_ctx, ciph); + smemclr(ctx, sizeof(*ctx)); + sfree(ctx); +} + +static void des_cbc_setkey(ssh_cipher *ciph, const void *vkey) +{ + struct des_cbc_ctx *ctx = container_of(ciph, struct des_cbc_ctx, ciph); + const uint8_t *key = (const uint8_t *)vkey; + des_key_setup(GET_64BIT_MSB_FIRST(key), &ctx->sched); +} + +static void des_cbc_setiv(ssh_cipher *ciph, const void *iv) +{ + struct des_cbc_ctx *ctx = container_of(ciph, struct des_cbc_ctx, ciph); + ctx->iv = des_load_lr(iv); +} + +static void des_cbc_encrypt(ssh_cipher *ciph, void *vdata, int len) +{ + struct des_cbc_ctx *ctx = container_of(ciph, struct des_cbc_ctx, ciph); + uint8_t *data = (uint8_t *)vdata; + for (; len > 0; len -= 8, data += 8) { + LR plaintext = des_load_lr(data); + LR cipher_in = des_xor_lr(plaintext, ctx->iv); + LR ciphertext = des_full_cipher(cipher_in, &ctx->sched, ENCIPHER); + des_store_lr(data, ciphertext); + ctx->iv = ciphertext; + } +} + +static void des_cbc_decrypt(ssh_cipher *ciph, void *vdata, int len) +{ + struct des_cbc_ctx *ctx = container_of(ciph, struct des_cbc_ctx, ciph); + uint8_t *data = (uint8_t *)vdata; + for (; len > 0; len -= 8, data += 8) { + LR ciphertext = des_load_lr(data); + LR cipher_out = des_full_cipher(ciphertext, &ctx->sched, DECIPHER); + LR plaintext = des_xor_lr(cipher_out, ctx->iv); + des_store_lr(data, plaintext); + ctx->iv = ciphertext; + } +} + +const ssh_cipheralg ssh_des = { + .new = des_cbc_new, + .free = des_cbc_free, + .setiv = des_cbc_setiv, + .setkey = des_cbc_setkey, + .encrypt = des_cbc_encrypt, + .decrypt = des_cbc_decrypt, + .ssh2_id = "des-cbc", + .blksize = 8, + .real_keybits = 56, + .padded_keybytes = 8, + .flags = SSH_CIPHER_IS_CBC, + .text_name = "single-DES CBC", +}; + +const ssh_cipheralg ssh_des_sshcom_ssh2 = { + /* Same as ssh_des_cbc, but with a different SSH-2 ID */ + .new = des_cbc_new, + .free = des_cbc_free, + .setiv = des_cbc_setiv, + .setkey = des_cbc_setkey, + .encrypt = des_cbc_encrypt, + .decrypt = des_cbc_decrypt, + .ssh2_id = "des-cbc@ssh.com", + .blksize = 8, + .real_keybits = 56, + .padded_keybytes = 8, + .flags = SSH_CIPHER_IS_CBC, + .text_name = "single-DES CBC", +}; + +static const ssh_cipheralg *const des_list[] = { + &ssh_des, + &ssh_des_sshcom_ssh2 +}; + +const ssh2_ciphers ssh2_des = { lenof(des_list), des_list }; + +/* ---------------------------------------------------------------------- + * Triple-DES CBC, SSH-2 style. The CBC mode treats the three + * invocations of DES as a single unified cipher, and surrounds it + * with just one layer of CBC, so only one IV is needed. + */ + +struct des3_cbc1_ctx { + des_keysched sched[3]; + LR iv; + ssh_cipher ciph; +}; + +static ssh_cipher *des3_cbc1_new(const ssh_cipheralg *alg) +{ + struct des3_cbc1_ctx *ctx = snew(struct des3_cbc1_ctx); + ctx->ciph.vt = alg; + return &ctx->ciph; +} + +static void des3_cbc1_free(ssh_cipher *ciph) +{ + struct des3_cbc1_ctx *ctx = container_of(ciph, struct des3_cbc1_ctx, ciph); + smemclr(ctx, sizeof(*ctx)); + sfree(ctx); +} + +static void des3_cbc1_setkey(ssh_cipher *ciph, const void *vkey) +{ + struct des3_cbc1_ctx *ctx = container_of(ciph, struct des3_cbc1_ctx, ciph); + const uint8_t *key = (const uint8_t *)vkey; + for (size_t i = 0; i < 3; i++) + des_key_setup(GET_64BIT_MSB_FIRST(key + 8*i), &ctx->sched[i]); +} + +static void des3_cbc1_setiv(ssh_cipher *ciph, const void *iv) +{ + struct des3_cbc1_ctx *ctx = container_of(ciph, struct des3_cbc1_ctx, ciph); + ctx->iv = des_load_lr(iv); +} + +static void des3_cbc1_cbc_encrypt(ssh_cipher *ciph, void *vdata, int len) +{ + struct des3_cbc1_ctx *ctx = container_of(ciph, struct des3_cbc1_ctx, ciph); + uint8_t *data = (uint8_t *)vdata; + for (; len > 0; len -= 8, data += 8) { + LR plaintext = des_load_lr(data); + LR cipher_in = des_xor_lr(plaintext, ctx->iv); + + /* Run three copies of the cipher, without undoing and redoing + * IP/FP in between. */ + LR lr = des_IP(cipher_in); + lr = des_inner_cipher(lr, &ctx->sched[0], ENCIPHER); + lr = des_inner_cipher(lr, &ctx->sched[1], DECIPHER); + lr = des_inner_cipher(lr, &ctx->sched[2], ENCIPHER); + LR ciphertext = des_FP(lr); + + des_store_lr(data, ciphertext); + ctx->iv = ciphertext; + } +} + +static void des3_cbc1_cbc_decrypt(ssh_cipher *ciph, void *vdata, int len) +{ + struct des3_cbc1_ctx *ctx = container_of(ciph, struct des3_cbc1_ctx, ciph); + uint8_t *data = (uint8_t *)vdata; + for (; len > 0; len -= 8, data += 8) { + LR ciphertext = des_load_lr(data); + + /* Similarly to encryption, but with the order reversed. */ + LR lr = des_IP(ciphertext); + lr = des_inner_cipher(lr, &ctx->sched[2], DECIPHER); + lr = des_inner_cipher(lr, &ctx->sched[1], ENCIPHER); + lr = des_inner_cipher(lr, &ctx->sched[0], DECIPHER); + LR cipher_out = des_FP(lr); + + LR plaintext = des_xor_lr(cipher_out, ctx->iv); + des_store_lr(data, plaintext); + ctx->iv = ciphertext; + } +} + +const ssh_cipheralg ssh_3des_ssh2 = { + .new = des3_cbc1_new, + .free = des3_cbc1_free, + .setiv = des3_cbc1_setiv, + .setkey = des3_cbc1_setkey, + .encrypt = des3_cbc1_cbc_encrypt, + .decrypt = des3_cbc1_cbc_decrypt, + .ssh2_id = "3des-cbc", + .blksize = 8, + .real_keybits = 168, + .padded_keybytes = 24, + .flags = SSH_CIPHER_IS_CBC, + .text_name = "triple-DES CBC", +}; + +/* ---------------------------------------------------------------------- + * Triple-DES in SDCTR mode. Again, the three DES instances are + * treated as one big cipher, with a single counter encrypted through + * all three. + */ + +#define SDCTR_WORDS (8 / BIGNUM_INT_BYTES) + +struct des3_sdctr_ctx { + des_keysched sched[3]; + BignumInt counter[SDCTR_WORDS]; + ssh_cipher ciph; +}; + +static ssh_cipher *des3_sdctr_new(const ssh_cipheralg *alg) +{ + struct des3_sdctr_ctx *ctx = snew(struct des3_sdctr_ctx); + ctx->ciph.vt = alg; + return &ctx->ciph; +} + +static void des3_sdctr_free(ssh_cipher *ciph) +{ + struct des3_sdctr_ctx *ctx = container_of( + ciph, struct des3_sdctr_ctx, ciph); + smemclr(ctx, sizeof(*ctx)); + sfree(ctx); +} + +static void des3_sdctr_setkey(ssh_cipher *ciph, const void *vkey) +{ + struct des3_sdctr_ctx *ctx = container_of( + ciph, struct des3_sdctr_ctx, ciph); + const uint8_t *key = (const uint8_t *)vkey; + for (size_t i = 0; i < 3; i++) + des_key_setup(GET_64BIT_MSB_FIRST(key + 8*i), &ctx->sched[i]); +} + +static void des3_sdctr_setiv(ssh_cipher *ciph, const void *viv) +{ + struct des3_sdctr_ctx *ctx = container_of( + ciph, struct des3_sdctr_ctx, ciph); + const uint8_t *iv = (const uint8_t *)viv; + + /* Import the initial counter value into the internal representation */ + for (unsigned i = 0; i < SDCTR_WORDS; i++) + ctx->counter[i] = GET_BIGNUMINT_MSB_FIRST( + iv + 8 - BIGNUM_INT_BYTES - i*BIGNUM_INT_BYTES); +} + +static void des3_sdctr_encrypt_decrypt(ssh_cipher *ciph, void *vdata, int len) +{ + struct des3_sdctr_ctx *ctx = container_of( + ciph, struct des3_sdctr_ctx, ciph); + uint8_t *data = (uint8_t *)vdata; + uint8_t iv_buf[8]; + for (; len > 0; len -= 8, data += 8) { + /* Format the counter value into the buffer. */ + for (unsigned i = 0; i < SDCTR_WORDS; i++) + PUT_BIGNUMINT_MSB_FIRST( + iv_buf + 8 - BIGNUM_INT_BYTES - i*BIGNUM_INT_BYTES, + ctx->counter[i]); + + /* Increment the counter. */ + BignumCarry carry = 1; + for (unsigned i = 0; i < SDCTR_WORDS; i++) + BignumADC(ctx->counter[i], carry, ctx->counter[i], 0, carry); + + /* Triple-encrypt the counter value from the IV. */ + LR lr = des_IP(des_load_lr(iv_buf)); + lr = des_inner_cipher(lr, &ctx->sched[0], ENCIPHER); + lr = des_inner_cipher(lr, &ctx->sched[1], DECIPHER); + lr = des_inner_cipher(lr, &ctx->sched[2], ENCIPHER); + LR keystream = des_FP(lr); + + LR input = des_load_lr(data); + LR output = des_xor_lr(input, keystream); + des_store_lr(data, output); + } + smemclr(iv_buf, sizeof(iv_buf)); +} + +const ssh_cipheralg ssh_3des_ssh2_ctr = { + .new = des3_sdctr_new, + .free = des3_sdctr_free, + .setiv = des3_sdctr_setiv, + .setkey = des3_sdctr_setkey, + .encrypt = des3_sdctr_encrypt_decrypt, + .decrypt = des3_sdctr_encrypt_decrypt, + .ssh2_id = "3des-ctr", + .blksize = 8, + .real_keybits = 168, + .padded_keybytes = 24, + .flags = 0, + .text_name = "triple-DES SDCTR", +}; + +static const ssh_cipheralg *const des3_list[] = { + &ssh_3des_ssh2_ctr, + &ssh_3des_ssh2 +}; + +const ssh2_ciphers ssh2_3des = { lenof(des3_list), des3_list }; + +/* ---------------------------------------------------------------------- + * Triple-DES, SSH-1 style. SSH-1 replicated the whole CBC structure + * three times, so there have to be three separate IVs, one in each + * layer. + */ + +struct des3_cbc3_ctx { + des_keysched sched[3]; + LR iv[3]; + ssh_cipher ciph; +}; + +static ssh_cipher *des3_cbc3_new(const ssh_cipheralg *alg) +{ + struct des3_cbc3_ctx *ctx = snew(struct des3_cbc3_ctx); + ctx->ciph.vt = alg; + return &ctx->ciph; +} + +static void des3_cbc3_free(ssh_cipher *ciph) +{ + struct des3_cbc3_ctx *ctx = container_of(ciph, struct des3_cbc3_ctx, ciph); + smemclr(ctx, sizeof(*ctx)); + sfree(ctx); +} + +static void des3_cbc3_setkey(ssh_cipher *ciph, const void *vkey) +{ + struct des3_cbc3_ctx *ctx = container_of(ciph, struct des3_cbc3_ctx, ciph); + const uint8_t *key = (const uint8_t *)vkey; + for (size_t i = 0; i < 3; i++) + des_key_setup(GET_64BIT_MSB_FIRST(key + 8*i), &ctx->sched[i]); +} + +static void des3_cbc3_setiv(ssh_cipher *ciph, const void *viv) +{ + struct des3_cbc3_ctx *ctx = container_of(ciph, struct des3_cbc3_ctx, ciph); + + /* + * In principle, we ought to provide an interface for the user to + * input 24 instead of 8 bytes of IV. But that would make this an + * ugly exception to the otherwise universal rule that IV size = + * cipher block size, and there's really no need to violate that + * rule given that this is a historical one-off oddity and SSH-1 + * always initialises all three IVs to zero anyway. So we fudge it + * by just setting all the IVs to the same value. + */ + + LR iv = des_load_lr(viv); + + /* But we store the IVs in permuted form, so that we can handle + * all three CBC layers without having to do IP/FP in between. */ + iv = des_IP(iv); + for (size_t i = 0; i < 3; i++) + ctx->iv[i] = iv; +} + +static void des3_cbc3_cbc_encrypt(ssh_cipher *ciph, void *vdata, int len) +{ + struct des3_cbc3_ctx *ctx = container_of(ciph, struct des3_cbc3_ctx, ciph); + uint8_t *data = (uint8_t *)vdata; + for (; len > 0; len -= 8, data += 8) { + /* Load and IP the input. */ + LR plaintext = des_IP(des_load_lr(data)); + LR lr = plaintext; + + /* Do three passes of CBC, with the middle one inverted. */ + + lr = des_xor_lr(lr, ctx->iv[0]); + lr = des_inner_cipher(lr, &ctx->sched[0], ENCIPHER); + ctx->iv[0] = lr; + + LR ciphertext = lr; + lr = des_inner_cipher(ciphertext, &ctx->sched[1], DECIPHER); + lr = des_xor_lr(lr, ctx->iv[1]); + ctx->iv[1] = ciphertext; + + lr = des_xor_lr(lr, ctx->iv[2]); + lr = des_inner_cipher(lr, &ctx->sched[2], ENCIPHER); + ctx->iv[2] = lr; + + des_store_lr(data, des_FP(lr)); + } +} + +static void des3_cbc3_cbc_decrypt(ssh_cipher *ciph, void *vdata, int len) +{ + struct des3_cbc3_ctx *ctx = container_of(ciph, struct des3_cbc3_ctx, ciph); + uint8_t *data = (uint8_t *)vdata; + for (; len > 0; len -= 8, data += 8) { + /* Load and IP the input */ + LR lr = des_IP(des_load_lr(data)); + LR ciphertext; + + /* Do three passes of CBC, with the middle one inverted. */ + ciphertext = lr; + lr = des_inner_cipher(ciphertext, &ctx->sched[2], DECIPHER); + lr = des_xor_lr(lr, ctx->iv[2]); + ctx->iv[2] = ciphertext; + + lr = des_xor_lr(lr, ctx->iv[1]); + lr = des_inner_cipher(lr, &ctx->sched[1], ENCIPHER); + ctx->iv[1] = lr; + + ciphertext = lr; + lr = des_inner_cipher(ciphertext, &ctx->sched[0], DECIPHER); + lr = des_xor_lr(lr, ctx->iv[0]); + ctx->iv[0] = ciphertext; + + des_store_lr(data, des_FP(lr)); + } +} + +const ssh_cipheralg ssh_3des_ssh1 = { + .new = des3_cbc3_new, + .free = des3_cbc3_free, + .setiv = des3_cbc3_setiv, + .setkey = des3_cbc3_setkey, + .encrypt = des3_cbc3_cbc_encrypt, + .decrypt = des3_cbc3_cbc_decrypt, + .blksize = 8, + .real_keybits = 168, + .padded_keybytes = 24, + .flags = SSH_CIPHER_IS_CBC, + .text_name = "triple-DES inner-CBC", +}; diff --git a/crypto/diffie-hellman.c b/crypto/diffie-hellman.c new file mode 100644 index 00000000..b3756120 --- /dev/null +++ b/crypto/diffie-hellman.c @@ -0,0 +1,272 @@ +/* + * Diffie-Hellman implementation for PuTTY. + */ + +#include + +#include "ssh.h" +#include "misc.h" +#include "mpint.h" + +struct dh_ctx { + mp_int *x, *e, *p, *q, *g; +}; + +struct dh_extra { + bool gex; + void (*construct)(dh_ctx *ctx); +}; + +static void dh_group1_construct(dh_ctx *ctx) +{ + ctx->p = MP_LITERAL(0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF); + ctx->g = mp_from_integer(2); +} + +static void dh_group14_construct(dh_ctx *ctx) +{ + ctx->p = MP_LITERAL(0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF); + ctx->g = mp_from_integer(2); +} + +static const struct dh_extra extra_group1 = { + false, dh_group1_construct, +}; + +static const ssh_kex ssh_diffiehellman_group1_sha1 = { + "diffie-hellman-group1-sha1", "group1", + KEXTYPE_DH, &ssh_sha1, &extra_group1, +}; + +static const ssh_kex *const group1_list[] = { + &ssh_diffiehellman_group1_sha1 +}; + +const ssh_kexes ssh_diffiehellman_group1 = { lenof(group1_list), group1_list }; + +static const struct dh_extra extra_group14 = { + false, dh_group14_construct, +}; + +static const ssh_kex ssh_diffiehellman_group14_sha256 = { + "diffie-hellman-group14-sha256", "group14", + KEXTYPE_DH, &ssh_sha256, &extra_group14, +}; + +static const ssh_kex ssh_diffiehellman_group14_sha1 = { + "diffie-hellman-group14-sha1", "group14", + KEXTYPE_DH, &ssh_sha1, &extra_group14, +}; + +static const ssh_kex *const group14_list[] = { + &ssh_diffiehellman_group14_sha256, + &ssh_diffiehellman_group14_sha1 +}; + +const ssh_kexes ssh_diffiehellman_group14 = { + lenof(group14_list), group14_list +}; + +static const struct dh_extra extra_gex = { true }; + +static const ssh_kex ssh_diffiehellman_gex_sha256 = { + "diffie-hellman-group-exchange-sha256", NULL, + KEXTYPE_DH, &ssh_sha256, &extra_gex, +}; + +static const ssh_kex ssh_diffiehellman_gex_sha1 = { + "diffie-hellman-group-exchange-sha1", NULL, + KEXTYPE_DH, &ssh_sha1, &extra_gex, +}; + +static const ssh_kex *const gex_list[] = { + &ssh_diffiehellman_gex_sha256, + &ssh_diffiehellman_gex_sha1 +}; + +const ssh_kexes ssh_diffiehellman_gex = { lenof(gex_list), gex_list }; + +/* + * Suffix on GSSAPI SSH protocol identifiers that indicates Kerberos 5 + * as the mechanism. + * + * This suffix is the base64-encoded MD5 hash of the byte sequence + * 06 09 2A 86 48 86 F7 12 01 02 02, which in turn is the ASN.1 DER + * encoding of the object ID 1.2.840.113554.1.2.2 which designates + * Kerberos v5. + * + * (The same encoded OID, minus the two-byte DER header, is defined in + * pgssapi.c as GSS_MECH_KRB5.) + */ +#define GSS_KRB5_OID_HASH "toWM5Slw5Ew8Mqkay+al2g==" + +static const ssh_kex ssh_gssk5_diffiehellman_gex_sha1 = { + "gss-gex-sha1-" GSS_KRB5_OID_HASH, NULL, + KEXTYPE_GSS, &ssh_sha1, &extra_gex, +}; + +static const ssh_kex ssh_gssk5_diffiehellman_group14_sha1 = { + "gss-group14-sha1-" GSS_KRB5_OID_HASH, "group14", + KEXTYPE_GSS, &ssh_sha1, &extra_group14, +}; + +static const ssh_kex ssh_gssk5_diffiehellman_group1_sha1 = { + "gss-group1-sha1-" GSS_KRB5_OID_HASH, "group1", + KEXTYPE_GSS, &ssh_sha1, &extra_group1, +}; + +static const ssh_kex *const gssk5_sha1_kex_list[] = { + &ssh_gssk5_diffiehellman_gex_sha1, + &ssh_gssk5_diffiehellman_group14_sha1, + &ssh_gssk5_diffiehellman_group1_sha1 +}; + +const ssh_kexes ssh_gssk5_sha1_kex = { + lenof(gssk5_sha1_kex_list), gssk5_sha1_kex_list +}; + +/* + * Common DH initialisation. + */ +static void dh_init(dh_ctx *ctx) +{ + ctx->q = mp_rshift_fixed(ctx->p, 1); + ctx->x = ctx->e = NULL; +} + +bool dh_is_gex(const ssh_kex *kex) +{ + const struct dh_extra *extra = (const struct dh_extra *)kex->extra; + return extra->gex; +} + +/* + * Initialise DH for a standard group. + */ +dh_ctx *dh_setup_group(const ssh_kex *kex) +{ + const struct dh_extra *extra = (const struct dh_extra *)kex->extra; + assert(!extra->gex); + dh_ctx *ctx = snew(dh_ctx); + extra->construct(ctx); + dh_init(ctx); + return ctx; +} + +/* + * Initialise DH for a server-supplied group. + */ +dh_ctx *dh_setup_gex(mp_int *pval, mp_int *gval) +{ + dh_ctx *ctx = snew(dh_ctx); + ctx->p = mp_copy(pval); + ctx->g = mp_copy(gval); + dh_init(ctx); + return ctx; +} + +/* + * Return size of DH modulus p. + */ +int dh_modulus_bit_size(const dh_ctx *ctx) +{ + return mp_get_nbits(ctx->p); +} + +/* + * Clean up and free a context. + */ +void dh_cleanup(dh_ctx *ctx) +{ + if (ctx->x) + mp_free(ctx->x); + if (ctx->e) + mp_free(ctx->e); + if (ctx->p) + mp_free(ctx->p); + if (ctx->g) + mp_free(ctx->g); + if (ctx->q) + mp_free(ctx->q); + 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. + */ +mp_int *dh_create_e(dh_ctx *ctx, int nbits) +{ + /* + * Lower limit is just 2. + */ + mp_int *lo = mp_from_integer(2); + + /* + * Upper limit. + */ + mp_int *hi = mp_copy(ctx->q); + mp_sub_integer_into(hi, hi, 1); + if (nbits) { + mp_int *pow2 = mp_power_2(nbits+1); + mp_min_into(pow2, pow2, hi); + mp_free(hi); + hi = pow2; + } + + /* + * Make a random number in that range. + */ + ctx->x = mp_random_in_range(lo, hi); + mp_free(lo); + mp_free(hi); + + /* + * Now compute e = g^x mod p. + */ + ctx->e = mp_modpow(ctx->g, ctx->x, ctx->p); + + return ctx->e; +} + +/* + * DH stage 2-epsilon: given a number f, validate it to ensure it's in + * range. (RFC 4253 section 8: "Values of 'e' or 'f' that are not in + * the range [1, p-1] MUST NOT be sent or accepted by either side." + * Also, we rule out 1 and p-1 too, since that's easy to do and since + * they lead to obviously weak keys that even a passive eavesdropper + * can figure out.) + */ +const char *dh_validate_f(dh_ctx *ctx, mp_int *f) +{ + if (!mp_hs_integer(f, 2)) { + return "f value received is too small"; + } else { + mp_int *pm1 = mp_copy(ctx->p); + mp_sub_integer_into(pm1, pm1, 1); + unsigned cmp = mp_cmp_hs(f, pm1); + mp_free(pm1); + if (cmp) + return "f value received is too large"; + } + return NULL; +} + +/* + * DH stage 2: given a number f, compute K = f^x mod p. + */ +mp_int *dh_find_K(dh_ctx *ctx, mp_int *f) +{ + return mp_modpow(f, ctx->x, ctx->p); +} diff --git a/crypto/dsa.c b/crypto/dsa.c new file mode 100644 index 00000000..9976648e --- /dev/null +++ b/crypto/dsa.c @@ -0,0 +1,503 @@ +/* + * Digital Signature Algorithm implementation for PuTTY. + */ + +#include +#include +#include + +#include "ssh.h" +#include "mpint.h" +#include "misc.h" + +static void dss_freekey(ssh_key *key); /* forward reference */ + +static ssh_key *dss_new_pub(const ssh_keyalg *self, ptrlen data) +{ + BinarySource src[1]; + struct dss_key *dss; + + BinarySource_BARE_INIT_PL(src, data); + if (!ptrlen_eq_string(get_string(src), "ssh-dss")) + return NULL; + + dss = snew(struct dss_key); + dss->sshk.vt = &ssh_dss; + dss->p = get_mp_ssh2(src); + dss->q = get_mp_ssh2(src); + dss->g = get_mp_ssh2(src); + dss->y = get_mp_ssh2(src); + dss->x = NULL; + + if (get_err(src) || + mp_eq_integer(dss->p, 0) || mp_eq_integer(dss->q, 0)) { + /* Invalid key. */ + dss_freekey(&dss->sshk); + return NULL; + } + + return &dss->sshk; +} + +static void dss_freekey(ssh_key *key) +{ + struct dss_key *dss = container_of(key, struct dss_key, sshk); + if (dss->p) + mp_free(dss->p); + if (dss->q) + mp_free(dss->q); + if (dss->g) + mp_free(dss->g); + if (dss->y) + mp_free(dss->y); + if (dss->x) + mp_free(dss->x); + sfree(dss); +} + +static void append_hex_to_strbuf(strbuf *sb, mp_int *x) +{ + if (sb->len > 0) + put_byte(sb, ','); + put_data(sb, "0x", 2); + char *hex = mp_get_hex(x); + size_t hexlen = strlen(hex); + put_data(sb, hex, hexlen); + smemclr(hex, hexlen); + sfree(hex); +} + +static char *dss_cache_str(ssh_key *key) +{ + struct dss_key *dss = container_of(key, struct dss_key, sshk); + strbuf *sb = strbuf_new(); + + if (!dss->p) { + strbuf_free(sb); + return NULL; + } + + append_hex_to_strbuf(sb, dss->p); + append_hex_to_strbuf(sb, dss->q); + append_hex_to_strbuf(sb, dss->g); + append_hex_to_strbuf(sb, dss->y); + + return strbuf_to_str(sb); +} + +static key_components *dss_components(ssh_key *key) +{ + struct dss_key *dss = container_of(key, struct dss_key, sshk); + key_components *kc = key_components_new(); + + key_components_add_text(kc, "key_type", "DSA"); + assert(dss->p); + key_components_add_mp(kc, "p", dss->p); + key_components_add_mp(kc, "q", dss->q); + key_components_add_mp(kc, "g", dss->g); + key_components_add_mp(kc, "public_y", dss->y); + if (dss->x) + key_components_add_mp(kc, "private_x", dss->x); + + return kc; +} + +static char *dss_invalid(ssh_key *key, unsigned flags) +{ + /* No validity criterion will stop us from using a DSA key at all */ + return NULL; +} + +static bool dss_verify(ssh_key *key, ptrlen sig, ptrlen data) +{ + struct dss_key *dss = container_of(key, struct dss_key, sshk); + BinarySource src[1]; + unsigned char hash[20]; + bool toret; + + if (!dss->p) + return false; + + BinarySource_BARE_INIT_PL(src, sig); + + /* + * 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 (sig.len != 40) { /* bug not present; read admin fields */ + ptrlen type = get_string(src); + sig = get_string(src); + + if (get_err(src) || !ptrlen_eq_string(type, "ssh-dss") || + sig.len != 40) + return false; + } + + /* Now we're sitting on a 40-byte string for sure. */ + mp_int *r = mp_from_bytes_be(make_ptrlen(sig.ptr, 20)); + mp_int *s = mp_from_bytes_be(make_ptrlen((const char *)sig.ptr + 20, 20)); + if (!r || !s) { + if (r) + mp_free(r); + if (s) + mp_free(s); + return false; + } + + /* Basic sanity checks: 0 < r,s < q */ + unsigned invalid = 0; + invalid |= mp_eq_integer(r, 0); + invalid |= mp_eq_integer(s, 0); + invalid |= mp_cmp_hs(r, dss->q); + invalid |= mp_cmp_hs(s, dss->q); + if (invalid) { + mp_free(r); + mp_free(s); + return false; + } + + /* + * Step 1. w <- s^-1 mod q. + */ + mp_int *w = mp_invert(s, dss->q); + if (!w) { + mp_free(r); + mp_free(s); + return false; + } + + /* + * Step 2. u1 <- SHA(message) * w mod q. + */ + hash_simple(&ssh_sha1, data, hash); + mp_int *sha = mp_from_bytes_be(make_ptrlen(hash, 20)); + mp_int *u1 = mp_modmul(sha, w, dss->q); + + /* + * Step 3. u2 <- r * w mod q. + */ + mp_int *u2 = mp_modmul(r, w, dss->q); + + /* + * Step 4. v <- (g^u1 * y^u2 mod p) mod q. + */ + mp_int *gu1p = mp_modpow(dss->g, u1, dss->p); + mp_int *yu2p = mp_modpow(dss->y, u2, dss->p); + mp_int *gu1yu2p = mp_modmul(gu1p, yu2p, dss->p); + mp_int *v = mp_mod(gu1yu2p, dss->q); + + /* + * Step 5. v should now be equal to r. + */ + + toret = mp_cmp_eq(v, r); + + mp_free(w); + mp_free(sha); + mp_free(u1); + mp_free(u2); + mp_free(gu1p); + mp_free(yu2p); + mp_free(gu1yu2p); + mp_free(v); + mp_free(r); + mp_free(s); + + return toret; +} + +static void dss_public_blob(ssh_key *key, BinarySink *bs) +{ + struct dss_key *dss = container_of(key, struct dss_key, sshk); + + put_stringz(bs, "ssh-dss"); + put_mp_ssh2(bs, dss->p); + put_mp_ssh2(bs, dss->q); + put_mp_ssh2(bs, dss->g); + put_mp_ssh2(bs, dss->y); +} + +static void dss_private_blob(ssh_key *key, BinarySink *bs) +{ + struct dss_key *dss = container_of(key, struct dss_key, sshk); + + put_mp_ssh2(bs, dss->x); +} + +static ssh_key *dss_new_priv(const ssh_keyalg *self, ptrlen pub, ptrlen priv) +{ + BinarySource src[1]; + ssh_key *sshk; + struct dss_key *dss; + ptrlen hash; + unsigned char digest[20]; + mp_int *ytest; + + sshk = dss_new_pub(self, pub); + if (!sshk) + return NULL; + + dss = container_of(sshk, struct dss_key, sshk); + BinarySource_BARE_INIT_PL(src, priv); + dss->x = get_mp_ssh2(src); + if (get_err(src)) { + dss_freekey(&dss->sshk); + return NULL; + } + + /* + * Check the obsolete hash in the old DSS key format. + */ + hash = get_string(src); + if (hash.len == 20) { + ssh_hash *h = ssh_hash_new(&ssh_sha1); + put_mp_ssh2(h, dss->p); + put_mp_ssh2(h, dss->q); + put_mp_ssh2(h, dss->g); + ssh_hash_final(h, digest); + if (!smemeq(hash.ptr, digest, 20)) { + dss_freekey(&dss->sshk); + return NULL; + } + } + + /* + * Now ensure g^x mod p really is y. + */ + ytest = mp_modpow(dss->g, dss->x, dss->p); + if (!mp_cmp_eq(ytest, dss->y)) { + mp_free(ytest); + dss_freekey(&dss->sshk); + return NULL; + } + mp_free(ytest); + + return &dss->sshk; +} + +static ssh_key *dss_new_priv_openssh(const ssh_keyalg *self, + BinarySource *src) +{ + struct dss_key *dss; + + dss = snew(struct dss_key); + dss->sshk.vt = &ssh_dss; + + dss->p = get_mp_ssh2(src); + dss->q = get_mp_ssh2(src); + dss->g = get_mp_ssh2(src); + dss->y = get_mp_ssh2(src); + dss->x = get_mp_ssh2(src); + + if (get_err(src) || + mp_eq_integer(dss->q, 0) || mp_eq_integer(dss->p, 0)) { + /* Invalid key. */ + dss_freekey(&dss->sshk); + return NULL; + } + + return &dss->sshk; +} + +static void dss_openssh_blob(ssh_key *key, BinarySink *bs) +{ + struct dss_key *dss = container_of(key, struct dss_key, sshk); + + put_mp_ssh2(bs, dss->p); + put_mp_ssh2(bs, dss->q); + put_mp_ssh2(bs, dss->g); + put_mp_ssh2(bs, dss->y); + put_mp_ssh2(bs, dss->x); +} + +static int dss_pubkey_bits(const ssh_keyalg *self, ptrlen pub) +{ + ssh_key *sshk; + struct dss_key *dss; + int ret; + + sshk = dss_new_pub(self, pub); + if (!sshk) + return -1; + + dss = container_of(sshk, struct dss_key, sshk); + ret = mp_get_nbits(dss->p); + dss_freekey(&dss->sshk); + + return ret; +} + +mp_int *dss_gen_k(const char *id_string, mp_int *modulus, + mp_int *private_key, + unsigned char *digest, int digest_len) +{ + /* + * 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. + */ + ssh_hash *h; + unsigned char digest512[64]; + + /* + * Hash some identifying text plus x. + */ + h = ssh_hash_new(&ssh_sha512); + put_asciz(h, id_string); + put_mp_ssh2(h, private_key); + ssh_hash_digest(h, digest512); + + /* + * Now hash that digest plus the message hash. + */ + ssh_hash_reset(h); + put_data(h, digest512, sizeof(digest512)); + put_data(h, digest, digest_len); + ssh_hash_final(h, digest512); + + /* + * Now convert the result into a bignum, and coerce it to the + * range [2,q), which we do by reducing it mod q-2 and adding 2. + */ + mp_int *modminus2 = mp_copy(modulus); + mp_sub_integer_into(modminus2, modminus2, 2); + mp_int *proto_k = mp_from_bytes_be(make_ptrlen(digest512, 64)); + mp_int *k = mp_mod(proto_k, modminus2); + mp_free(proto_k); + mp_free(modminus2); + mp_add_integer_into(k, k, 2); + + smemclr(digest512, sizeof(digest512)); + + return k; +} + +static void dss_sign(ssh_key *key, ptrlen data, unsigned flags, BinarySink *bs) +{ + struct dss_key *dss = container_of(key, struct dss_key, sshk); + unsigned char digest[20]; + int i; + + hash_simple(&ssh_sha1, data, digest); + + mp_int *k = dss_gen_k("DSA deterministic k generator", dss->q, dss->x, + digest, sizeof(digest)); + mp_int *kinv = mp_invert(k, dss->q); /* k^-1 mod q */ + + /* + * Now we have k, so just go ahead and compute the signature. + */ + mp_int *gkp = mp_modpow(dss->g, k, dss->p); /* g^k mod p */ + mp_int *r = mp_mod(gkp, dss->q); /* r = (g^k mod p) mod q */ + mp_free(gkp); + + mp_int *hash = mp_from_bytes_be(make_ptrlen(digest, 20)); + mp_int *xr = mp_mul(dss->x, r); + mp_int *hxr = mp_add(xr, hash); /* hash + x*r */ + mp_int *s = mp_modmul(kinv, hxr, dss->q); /* s = k^-1 * (hash+x*r) mod q */ + mp_free(hxr); + mp_free(xr); + mp_free(kinv); + mp_free(k); + mp_free(hash); + + put_stringz(bs, "ssh-dss"); + put_uint32(bs, 40); + for (i = 0; i < 20; i++) + put_byte(bs, mp_get_byte(r, 19 - i)); + for (i = 0; i < 20; i++) + put_byte(bs, mp_get_byte(s, 19 - i)); + mp_free(r); + mp_free(s); +} + +const ssh_keyalg ssh_dss = { + .new_pub = dss_new_pub, + .new_priv = dss_new_priv, + .new_priv_openssh = dss_new_priv_openssh, + .freekey = dss_freekey, + .invalid = dss_invalid, + .sign = dss_sign, + .verify = dss_verify, + .public_blob = dss_public_blob, + .private_blob = dss_private_blob, + .openssh_blob = dss_openssh_blob, + .cache_str = dss_cache_str, + .components = dss_components, + .pubkey_bits = dss_pubkey_bits, + .ssh_id = "ssh-dss", + .cache_id = "dss", +}; diff --git a/crypto/ecc-arithmetic.c b/crypto/ecc-arithmetic.c new file mode 100644 index 00000000..6a896f92 --- /dev/null +++ b/crypto/ecc-arithmetic.c @@ -0,0 +1,1171 @@ +/* + * Basic arithmetic for elliptic curves, implementing ecc.h. + */ + +#include + +#include "ssh.h" +#include "mpint.h" +#include "ecc.h" + +/* ---------------------------------------------------------------------- + * Weierstrass curves. + */ + +struct WeierstrassPoint { + /* + * Internally, we represent a point using 'Jacobian coordinates', + * which are three values X,Y,Z whose relation to the affine + * coordinates x,y is that x = X/Z^2 and y = Y/Z^3. + * + * This allows us to do most of our calculations without having to + * take an inverse mod p: every time the obvious affine formulae + * would need you to divide by something, you instead multiply it + * into the 'denominator' coordinate Z. You only have to actually + * take the inverse of Z when you need to get the affine + * coordinates back out, which means you do it once after your + * entire computation instead of at every intermediate step. + * + * The point at infinity is represented by setting all three + * coordinates to zero. + * + * These values are also stored in the Montgomery-multiplication + * transformed representation. + */ + mp_int *X, *Y, *Z; + + WeierstrassCurve *wc; +}; + +struct WeierstrassCurve { + /* Prime modulus of the finite field. */ + mp_int *p; + + /* Persistent Montgomery context for doing arithmetic mod p. */ + MontyContext *mc; + + /* Modsqrt context for point decompression. NULL if this curve was + * constructed without providing nonsquare_mod_p. */ + ModsqrtContext *sc; + + /* Parameters of the curve, in Montgomery-multiplication + * transformed form. */ + mp_int *a, *b; +}; + +WeierstrassCurve *ecc_weierstrass_curve( + mp_int *p, mp_int *a, mp_int *b, mp_int *nonsquare_mod_p) +{ + WeierstrassCurve *wc = snew(WeierstrassCurve); + wc->p = mp_copy(p); + wc->mc = monty_new(p); + wc->a = monty_import(wc->mc, a); + wc->b = monty_import(wc->mc, b); + + if (nonsquare_mod_p) + wc->sc = modsqrt_new(p, nonsquare_mod_p); + else + wc->sc = NULL; + + return wc; +} + +void ecc_weierstrass_curve_free(WeierstrassCurve *wc) +{ + mp_free(wc->p); + mp_free(wc->a); + mp_free(wc->b); + monty_free(wc->mc); + if (wc->sc) + modsqrt_free(wc->sc); + sfree(wc); +} + +static WeierstrassPoint *ecc_weierstrass_point_new_empty(WeierstrassCurve *wc) +{ + WeierstrassPoint *wp = snew(WeierstrassPoint); + wp->wc = wc; + wp->X = wp->Y = wp->Z = NULL; + return wp; +} + +static WeierstrassPoint *ecc_weierstrass_point_new_imported( + WeierstrassCurve *wc, mp_int *monty_x, mp_int *monty_y) +{ + WeierstrassPoint *wp = ecc_weierstrass_point_new_empty(wc); + wp->X = monty_x; + wp->Y = monty_y; + wp->Z = mp_copy(monty_identity(wc->mc)); + return wp; +} + +WeierstrassPoint *ecc_weierstrass_point_new( + WeierstrassCurve *wc, mp_int *x, mp_int *y) +{ + return ecc_weierstrass_point_new_imported( + wc, monty_import(wc->mc, x), monty_import(wc->mc, y)); +} + +WeierstrassPoint *ecc_weierstrass_point_new_identity(WeierstrassCurve *wc) +{ + WeierstrassPoint *wp = ecc_weierstrass_point_new_empty(wc); + size_t bits = mp_max_bits(wc->p); + wp->X = mp_new(bits); + wp->Y = mp_new(bits); + wp->Z = mp_new(bits); + return wp; +} + +void ecc_weierstrass_point_copy_into( + WeierstrassPoint *dest, WeierstrassPoint *src) +{ + mp_copy_into(dest->X, src->X); + mp_copy_into(dest->Y, src->Y); + mp_copy_into(dest->Z, src->Z); +} + +WeierstrassPoint *ecc_weierstrass_point_copy(WeierstrassPoint *orig) +{ + WeierstrassPoint *wp = ecc_weierstrass_point_new_empty(orig->wc); + wp->X = mp_copy(orig->X); + wp->Y = mp_copy(orig->Y); + wp->Z = mp_copy(orig->Z); + return wp; +} + +void ecc_weierstrass_point_free(WeierstrassPoint *wp) +{ + mp_free(wp->X); + mp_free(wp->Y); + mp_free(wp->Z); + smemclr(wp, sizeof(*wp)); + sfree(wp); +} + +WeierstrassPoint *ecc_weierstrass_point_new_from_x( + WeierstrassCurve *wc, mp_int *xorig, unsigned desired_y_parity) +{ + assert(wc->sc); + + /* + * The curve equation is y^2 = x^3 + ax + b, which is already + * conveniently in a form where we can compute the RHS and take + * the square root of it to get y. + */ + unsigned success; + + mp_int *x = monty_import(wc->mc, xorig); + + /* + * Compute the RHS of the curve equation. We don't need to take + * account of z here, because we're constructing the point from + * scratch. So it really is just x^3 + ax + b. + */ + mp_int *x2 = monty_mul(wc->mc, x, x); + mp_int *x2_plus_a = monty_add(wc->mc, x2, wc->a); + mp_int *x3_plus_ax = monty_mul(wc->mc, x2_plus_a, x); + mp_int *rhs = monty_add(wc->mc, x3_plus_ax, wc->b); + mp_free(x2); + mp_free(x2_plus_a); + mp_free(x3_plus_ax); + + mp_int *y = monty_modsqrt(wc->sc, rhs, &success); + mp_free(rhs); + + if (!success) { + /* Failure! x^3+ax+b worked out to be a number that has no + * square root mod p. In this situation there's no point in + * trying to be time-constant, since the protocol sequence is + * going to diverge anyway when we complain to whoever gave us + * this bogus value. */ + mp_free(x); + mp_free(y); + return NULL; + } + + /* + * Choose whichever of y and p-y has the specified parity (of its + * lowest positive residue mod p). + */ + mp_int *tmp = monty_export(wc->mc, y); + unsigned flip = (mp_get_bit(tmp, 0) ^ desired_y_parity) & 1; + mp_sub_into(tmp, wc->p, y); + mp_select_into(y, y, tmp, flip); + mp_free(tmp); + + return ecc_weierstrass_point_new_imported(wc, x, y); +} + +static void ecc_weierstrass_cond_overwrite( + WeierstrassPoint *dest, WeierstrassPoint *src, unsigned overwrite) +{ + mp_select_into(dest->X, dest->X, src->X, overwrite); + mp_select_into(dest->Y, dest->Y, src->Y, overwrite); + mp_select_into(dest->Z, dest->Z, src->Z, overwrite); +} + +static void ecc_weierstrass_cond_swap( + WeierstrassPoint *P, WeierstrassPoint *Q, unsigned swap) +{ + mp_cond_swap(P->X, Q->X, swap); + mp_cond_swap(P->Y, Q->Y, swap); + mp_cond_swap(P->Z, Q->Z, swap); +} + +/* + * Shared code between all three of the basic arithmetic functions: + * once we've determined the slope of the line that we're intersecting + * the curve with, this takes care of finding the coordinates of the + * third intersection point (given the two input x-coordinates and one + * of the y-coords) and negating it to generate the output. + */ +static inline void ecc_weierstrass_epilogue( + mp_int *Px, mp_int *Qx, mp_int *Py, mp_int *common_Z, + mp_int *lambda_n, mp_int *lambda_d, WeierstrassPoint *out) +{ + WeierstrassCurve *wc = out->wc; + + /* Powers of the numerator and denominator of the slope lambda */ + mp_int *lambda_n2 = monty_mul(wc->mc, lambda_n, lambda_n); + mp_int *lambda_d2 = monty_mul(wc->mc, lambda_d, lambda_d); + mp_int *lambda_d3 = monty_mul(wc->mc, lambda_d, lambda_d2); + + /* Make the output x-coordinate */ + mp_int *xsum = monty_add(wc->mc, Px, Qx); + mp_int *lambda_d2_xsum = monty_mul(wc->mc, lambda_d2, xsum); + out->X = monty_sub(wc->mc, lambda_n2, lambda_d2_xsum); + + /* Make the output y-coordinate */ + mp_int *lambda_d2_Px = monty_mul(wc->mc, lambda_d2, Px); + mp_int *xdiff = monty_sub(wc->mc, lambda_d2_Px, out->X); + mp_int *lambda_n_xdiff = monty_mul(wc->mc, lambda_n, xdiff); + mp_int *lambda_d3_Py = monty_mul(wc->mc, lambda_d3, Py); + out->Y = monty_sub(wc->mc, lambda_n_xdiff, lambda_d3_Py); + + /* Make the output z-coordinate */ + out->Z = monty_mul(wc->mc, common_Z, lambda_d); + + mp_free(lambda_n2); + mp_free(lambda_d2); + mp_free(lambda_d3); + mp_free(xsum); + mp_free(xdiff); + mp_free(lambda_d2_xsum); + mp_free(lambda_n_xdiff); + mp_free(lambda_d2_Px); + mp_free(lambda_d3_Py); +} + +/* + * Shared code between add and add_general: put the two input points + * over a common denominator, and determine the slope lambda of the + * line through both of them. If the points have the same + * x-coordinate, then the slope will be returned with a zero + * denominator. + */ +static inline void ecc_weierstrass_add_prologue( + WeierstrassPoint *P, WeierstrassPoint *Q, + mp_int **Px, mp_int **Py, mp_int **Qx, mp_int **denom, + mp_int **lambda_n, mp_int **lambda_d) +{ + WeierstrassCurve *wc = P->wc; + + /* Powers of the points' denominators */ + mp_int *Pz2 = monty_mul(wc->mc, P->Z, P->Z); + mp_int *Pz3 = monty_mul(wc->mc, Pz2, P->Z); + mp_int *Qz2 = monty_mul(wc->mc, Q->Z, Q->Z); + mp_int *Qz3 = monty_mul(wc->mc, Qz2, Q->Z); + + /* Points' x,y coordinates scaled by the other one's denominator + * (raised to the appropriate power) */ + *Px = monty_mul(wc->mc, P->X, Qz2); + *Py = monty_mul(wc->mc, P->Y, Qz3); + *Qx = monty_mul(wc->mc, Q->X, Pz2); + mp_int *Qy = monty_mul(wc->mc, Q->Y, Pz3); + + /* Common denominator */ + *denom = monty_mul(wc->mc, P->Z, Q->Z); + + /* Slope of the line through the two points, if P != Q */ + *lambda_n = monty_sub(wc->mc, Qy, *Py); + *lambda_d = monty_sub(wc->mc, *Qx, *Px); + + mp_free(Pz2); + mp_free(Pz3); + mp_free(Qz2); + mp_free(Qz3); + mp_free(Qy); +} + +WeierstrassPoint *ecc_weierstrass_add(WeierstrassPoint *P, WeierstrassPoint *Q) +{ + WeierstrassCurve *wc = P->wc; + assert(Q->wc == wc); + + WeierstrassPoint *S = ecc_weierstrass_point_new_empty(wc); + + mp_int *Px, *Py, *Qx, *denom, *lambda_n, *lambda_d; + ecc_weierstrass_add_prologue( + P, Q, &Px, &Py, &Qx, &denom, &lambda_n, &lambda_d); + + /* Never expect to have received two mutually inverse inputs, or + * two identical ones (which would make this a doubling). In other + * words, the two input x-coordinates (after putting over a common + * denominator) should never have been equal. */ + assert(!mp_eq_integer(lambda_n, 0)); + + /* Now go to the common epilogue code. */ + ecc_weierstrass_epilogue(Px, Qx, Py, denom, lambda_n, lambda_d, S); + + mp_free(Px); + mp_free(Py); + mp_free(Qx); + mp_free(denom); + mp_free(lambda_n); + mp_free(lambda_d); + + return S; +} + +/* + * Code to determine the slope of the line you need to intersect with + * the curve in the case where you're adding a point to itself. In + * this situation you can't just say "the line through both input + * points" because that's under-determined; instead, you have to take + * the _tangent_ to the curve at the given point, by differentiating + * the curve equation y^2=x^3+ax+b to get 2y dy/dx = 3x^2+a. + */ +static inline void ecc_weierstrass_tangent_slope( + WeierstrassPoint *P, mp_int **lambda_n, mp_int **lambda_d) +{ + WeierstrassCurve *wc = P->wc; + + mp_int *X2 = monty_mul(wc->mc, P->X, P->X); + mp_int *twoX2 = monty_add(wc->mc, X2, X2); + mp_int *threeX2 = monty_add(wc->mc, twoX2, X2); + mp_int *Z2 = monty_mul(wc->mc, P->Z, P->Z); + mp_int *Z4 = monty_mul(wc->mc, Z2, Z2); + mp_int *aZ4 = monty_mul(wc->mc, wc->a, Z4); + + *lambda_n = monty_add(wc->mc, threeX2, aZ4); + *lambda_d = monty_add(wc->mc, P->Y, P->Y); + + mp_free(X2); + mp_free(twoX2); + mp_free(threeX2); + mp_free(Z2); + mp_free(Z4); + mp_free(aZ4); +} + +WeierstrassPoint *ecc_weierstrass_double(WeierstrassPoint *P) +{ + WeierstrassCurve *wc = P->wc; + WeierstrassPoint *D = ecc_weierstrass_point_new_empty(wc); + + mp_int *lambda_n, *lambda_d; + ecc_weierstrass_tangent_slope(P, &lambda_n, &lambda_d); + ecc_weierstrass_epilogue(P->X, P->X, P->Y, P->Z, lambda_n, lambda_d, D); + mp_free(lambda_n); + mp_free(lambda_d); + + return D; +} + +static inline void ecc_weierstrass_select_into( + WeierstrassPoint *dest, WeierstrassPoint *P, WeierstrassPoint *Q, + unsigned choose_Q) +{ + mp_select_into(dest->X, P->X, Q->X, choose_Q); + mp_select_into(dest->Y, P->Y, Q->Y, choose_Q); + mp_select_into(dest->Z, P->Z, Q->Z, choose_Q); +} + +WeierstrassPoint *ecc_weierstrass_add_general( + WeierstrassPoint *P, WeierstrassPoint *Q) +{ + WeierstrassCurve *wc = P->wc; + assert(Q->wc == wc); + + WeierstrassPoint *S = ecc_weierstrass_point_new_empty(wc); + + /* Parameters for the epilogue, and slope of the line if P != Q */ + mp_int *Px, *Py, *Qx, *denom, *lambda_n, *lambda_d; + ecc_weierstrass_add_prologue( + P, Q, &Px, &Py, &Qx, &denom, &lambda_n, &lambda_d); + + /* Slope if P == Q */ + mp_int *lambda_n_tangent, *lambda_d_tangent; + ecc_weierstrass_tangent_slope(P, &lambda_n_tangent, &lambda_d_tangent); + + /* Select between those slopes depending on whether P == Q */ + unsigned same_x_coord = mp_eq_integer(lambda_d, 0); + unsigned same_y_coord = mp_eq_integer(lambda_n, 0); + unsigned equality = same_x_coord & same_y_coord; + mp_select_into(lambda_n, lambda_n, lambda_n_tangent, equality); + mp_select_into(lambda_d, lambda_d, lambda_d_tangent, equality); + + /* Now go to the common code between addition and doubling */ + ecc_weierstrass_epilogue(Px, Qx, Py, denom, lambda_n, lambda_d, S); + + /* Check for the input identity cases, and overwrite the output if + * necessary. */ + ecc_weierstrass_select_into(S, S, Q, mp_eq_integer(P->Z, 0)); + ecc_weierstrass_select_into(S, S, P, mp_eq_integer(Q->Z, 0)); + + /* + * In the case where P == -Q and so the output is the identity, + * we'll have calculated lambda_d = 0 and so the output will have + * z==0 already. Detect that and use it to normalise the other two + * coordinates to zero. + */ + unsigned output_id = mp_eq_integer(S->Z, 0); + mp_cond_clear(S->X, output_id); + mp_cond_clear(S->Y, output_id); + + mp_free(Px); + mp_free(Py); + mp_free(Qx); + mp_free(denom); + mp_free(lambda_n); + mp_free(lambda_d); + mp_free(lambda_n_tangent); + mp_free(lambda_d_tangent); + + return S; +} + +WeierstrassPoint *ecc_weierstrass_multiply(WeierstrassPoint *B, mp_int *n) +{ + WeierstrassPoint *two_B = ecc_weierstrass_double(B); + WeierstrassPoint *k_B = ecc_weierstrass_point_copy(B); + WeierstrassPoint *kplus1_B = ecc_weierstrass_point_copy(two_B); + + /* + * This multiply routine more or less follows the shape of the + * 'Montgomery ladder' technique that you have to use under the + * extra constraint on addition in Montgomery curves, because it + * was fresh in my mind and easier to just do it the same way. See + * the comment in ecc_montgomery_multiply. + */ + + unsigned not_started_yet = 1; + for (size_t bitindex = mp_max_bits(n); bitindex-- > 0 ;) { + unsigned nbit = mp_get_bit(n, bitindex); + + WeierstrassPoint *sum = ecc_weierstrass_add(k_B, kplus1_B); + ecc_weierstrass_cond_swap(k_B, kplus1_B, nbit); + WeierstrassPoint *other = ecc_weierstrass_double(k_B); + ecc_weierstrass_point_free(k_B); + ecc_weierstrass_point_free(kplus1_B); + k_B = other; + kplus1_B = sum; + ecc_weierstrass_cond_swap(k_B, kplus1_B, nbit); + + ecc_weierstrass_cond_overwrite(k_B, B, not_started_yet); + ecc_weierstrass_cond_overwrite(kplus1_B, two_B, not_started_yet); + not_started_yet &= ~nbit; + } + + ecc_weierstrass_point_free(two_B); + ecc_weierstrass_point_free(kplus1_B); + return k_B; +} + +unsigned ecc_weierstrass_is_identity(WeierstrassPoint *wp) +{ + return mp_eq_integer(wp->Z, 0); +} + +/* + * Normalise a point by scaling its Jacobian coordinates so that Z=1. + * This doesn't change what point is represented by the triple, but it + * means the affine x,y can now be easily recovered from X and Y. + */ +static void ecc_weierstrass_normalise(WeierstrassPoint *wp) +{ + WeierstrassCurve *wc = wp->wc; + mp_int *zinv = monty_invert(wc->mc, wp->Z); + mp_int *zinv2 = monty_mul(wc->mc, zinv, zinv); + mp_int *zinv3 = monty_mul(wc->mc, zinv2, zinv); + monty_mul_into(wc->mc, wp->X, wp->X, zinv2); + monty_mul_into(wc->mc, wp->Y, wp->Y, zinv3); + monty_mul_into(wc->mc, wp->Z, wp->Z, zinv); + mp_free(zinv); + mp_free(zinv2); + mp_free(zinv3); +} + +void ecc_weierstrass_get_affine( + WeierstrassPoint *wp, mp_int **x, mp_int **y) +{ + WeierstrassCurve *wc = wp->wc; + + ecc_weierstrass_normalise(wp); + + if (x) + *x = monty_export(wc->mc, wp->X); + if (y) + *y = monty_export(wc->mc, wp->Y); +} + +unsigned ecc_weierstrass_point_valid(WeierstrassPoint *P) +{ + WeierstrassCurve *wc = P->wc; + + /* + * The projective version of the curve equation is + * Y^2 = X^3 + a X Z^4 + b Z^6 + */ + mp_int *lhs = monty_mul(P->wc->mc, P->Y, P->Y); + mp_int *x2 = monty_mul(wc->mc, P->X, P->X); + mp_int *x3 = monty_mul(wc->mc, x2, P->X); + mp_int *z2 = monty_mul(wc->mc, P->Z, P->Z); + mp_int *z4 = monty_mul(wc->mc, z2, z2); + mp_int *az4 = monty_mul(wc->mc, wc->a, z4); + mp_int *axz4 = monty_mul(wc->mc, az4, P->X); + mp_int *x3_plus_axz4 = monty_add(wc->mc, x3, axz4); + mp_int *z6 = monty_mul(wc->mc, z2, z4); + mp_int *bz6 = monty_mul(wc->mc, wc->b, z6); + mp_int *rhs = monty_add(wc->mc, x3_plus_axz4, bz6); + + unsigned valid = mp_cmp_eq(lhs, rhs); + + mp_free(lhs); + mp_free(x2); + mp_free(x3); + mp_free(z2); + mp_free(z4); + mp_free(az4); + mp_free(axz4); + mp_free(x3_plus_axz4); + mp_free(z6); + mp_free(bz6); + mp_free(rhs); + + return valid; +} + +/* ---------------------------------------------------------------------- + * Montgomery curves. + */ + +struct MontgomeryPoint { + /* XZ coordinates. These represent the affine x coordinate by the + * relationship x = X/Z. */ + mp_int *X, *Z; + + MontgomeryCurve *mc; +}; + +struct MontgomeryCurve { + /* Prime modulus of the finite field. */ + mp_int *p; + + /* Montgomery context for arithmetic mod p. */ + MontyContext *mc; + + /* Parameters of the curve, in Montgomery-multiplication + * transformed form. */ + mp_int *a, *b; + + /* (a+2)/4, also in Montgomery-multiplication form. */ + mp_int *aplus2over4; +}; + +MontgomeryCurve *ecc_montgomery_curve( + mp_int *p, mp_int *a, mp_int *b) +{ + MontgomeryCurve *mc = snew(MontgomeryCurve); + mc->p = mp_copy(p); + mc->mc = monty_new(p); + mc->a = monty_import(mc->mc, a); + mc->b = monty_import(mc->mc, b); + + mp_int *four = mp_from_integer(4); + mp_int *fourinverse = mp_invert(four, mc->p); + mp_int *aplus2 = mp_copy(a); + mp_add_integer_into(aplus2, aplus2, 2); + mp_int *aplus2over4 = mp_modmul(aplus2, fourinverse, mc->p); + mc->aplus2over4 = monty_import(mc->mc, aplus2over4); + mp_free(four); + mp_free(fourinverse); + mp_free(aplus2); + mp_free(aplus2over4); + + return mc; +} + +void ecc_montgomery_curve_free(MontgomeryCurve *mc) +{ + mp_free(mc->p); + mp_free(mc->a); + mp_free(mc->b); + mp_free(mc->aplus2over4); + monty_free(mc->mc); + sfree(mc); +} + +static MontgomeryPoint *ecc_montgomery_point_new_empty(MontgomeryCurve *mc) +{ + MontgomeryPoint *mp = snew(MontgomeryPoint); + mp->mc = mc; + mp->X = mp->Z = NULL; + return mp; +} + +MontgomeryPoint *ecc_montgomery_point_new(MontgomeryCurve *mc, mp_int *x) +{ + MontgomeryPoint *mp = ecc_montgomery_point_new_empty(mc); + mp->X = monty_import(mc->mc, x); + mp->Z = mp_copy(monty_identity(mc->mc)); + return mp; +} + +void ecc_montgomery_point_copy_into( + MontgomeryPoint *dest, MontgomeryPoint *src) +{ + mp_copy_into(dest->X, src->X); + mp_copy_into(dest->Z, src->Z); +} + +MontgomeryPoint *ecc_montgomery_point_copy(MontgomeryPoint *orig) +{ + MontgomeryPoint *mp = ecc_montgomery_point_new_empty(orig->mc); + mp->X = mp_copy(orig->X); + mp->Z = mp_copy(orig->Z); + return mp; +} + +void ecc_montgomery_point_free(MontgomeryPoint *mp) +{ + mp_free(mp->X); + mp_free(mp->Z); + smemclr(mp, sizeof(*mp)); + sfree(mp); +} + +static void ecc_montgomery_cond_overwrite( + MontgomeryPoint *dest, MontgomeryPoint *src, unsigned overwrite) +{ + mp_select_into(dest->X, dest->X, src->X, overwrite); + mp_select_into(dest->Z, dest->Z, src->Z, overwrite); +} + +static void ecc_montgomery_cond_swap( + MontgomeryPoint *P, MontgomeryPoint *Q, unsigned swap) +{ + mp_cond_swap(P->X, Q->X, swap); + mp_cond_swap(P->Z, Q->Z, swap); +} + +MontgomeryPoint *ecc_montgomery_diff_add( + MontgomeryPoint *P, MontgomeryPoint *Q, MontgomeryPoint *PminusQ) +{ + MontgomeryCurve *mc = P->mc; + assert(Q->mc == mc); + assert(PminusQ->mc == mc); + + /* + * Differential addition is achieved using the following formula + * that relates the affine x-coordinates of P, Q, P+Q and P-Q: + * + * x(P+Q) x(P-Q) (x(Q)-x(P))^2 = (x(P)x(Q) - 1)^2 + * + * As with the Weierstrass coordinates, the code below transforms + * that affine relation into a projective one to avoid having to + * do a division during the main arithmetic. + */ + + MontgomeryPoint *S = ecc_montgomery_point_new_empty(mc); + + mp_int *Px_m_Pz = monty_sub(mc->mc, P->X, P->Z); + mp_int *Px_p_Pz = monty_add(mc->mc, P->X, P->Z); + mp_int *Qx_m_Qz = monty_sub(mc->mc, Q->X, Q->Z); + mp_int *Qx_p_Qz = monty_add(mc->mc, Q->X, Q->Z); + mp_int *PmQp = monty_mul(mc->mc, Px_m_Pz, Qx_p_Qz); + mp_int *PpQm = monty_mul(mc->mc, Px_p_Pz, Qx_m_Qz); + mp_int *Xpre = monty_add(mc->mc, PmQp, PpQm); + mp_int *Zpre = monty_sub(mc->mc, PmQp, PpQm); + mp_int *Xpre2 = monty_mul(mc->mc, Xpre, Xpre); + mp_int *Zpre2 = monty_mul(mc->mc, Zpre, Zpre); + S->X = monty_mul(mc->mc, Xpre2, PminusQ->Z); + S->Z = monty_mul(mc->mc, Zpre2, PminusQ->X); + + mp_free(Px_m_Pz); + mp_free(Px_p_Pz); + mp_free(Qx_m_Qz); + mp_free(Qx_p_Qz); + mp_free(PmQp); + mp_free(PpQm); + mp_free(Xpre); + mp_free(Zpre); + mp_free(Xpre2); + mp_free(Zpre2); + + return S; +} + +MontgomeryPoint *ecc_montgomery_double(MontgomeryPoint *P) +{ + MontgomeryCurve *mc = P->mc; + MontgomeryPoint *D = ecc_montgomery_point_new_empty(mc); + + /* + * To double a point in affine coordinates, in principle you can + * use the same technique as for Weierstrass: differentiate the + * curve equation to get the tangent line at the input point, use + * that to get an expression for y which you substitute back into + * the curve equation, and subtract the known two roots (in this + * case both the same) from the x^2 coefficient of the resulting + * cubic. + * + * In this case, we don't have an input y-coordinate, so you have + * to do a bit of extra transformation to find a formula that can + * work without it. The tangent formula is (3x^2 + 2ax + 1)/(2y), + * and when that appears in the final formula it will be squared - + * so we can substitute the y^2 in the denominator for the RHS of + * the curve equation. Put together, that gives + * + * x_out = (x+1)^2 (x-1)^2 / 4(x^3+ax^2+x) + * + * and, as usual, the code below transforms that into projective + * form to avoid the division. + */ + + mp_int *Px_m_Pz = monty_sub(mc->mc, P->X, P->Z); + mp_int *Px_p_Pz = monty_add(mc->mc, P->X, P->Z); + mp_int *Px_m_Pz_2 = monty_mul(mc->mc, Px_m_Pz, Px_m_Pz); + mp_int *Px_p_Pz_2 = monty_mul(mc->mc, Px_p_Pz, Px_p_Pz); + D->X = monty_mul(mc->mc, Px_m_Pz_2, Px_p_Pz_2); + mp_int *XZ = monty_mul(mc->mc, P->X, P->Z); + mp_int *twoXZ = monty_add(mc->mc, XZ, XZ); + mp_int *fourXZ = monty_add(mc->mc, twoXZ, twoXZ); + mp_int *fourXZ_scaled = monty_mul(mc->mc, fourXZ, mc->aplus2over4); + mp_int *Zpre = monty_add(mc->mc, Px_m_Pz_2, fourXZ_scaled); + D->Z = monty_mul(mc->mc, fourXZ, Zpre); + + mp_free(Px_m_Pz); + mp_free(Px_p_Pz); + mp_free(Px_m_Pz_2); + mp_free(Px_p_Pz_2); + mp_free(XZ); + mp_free(twoXZ); + mp_free(fourXZ); + mp_free(fourXZ_scaled); + mp_free(Zpre); + + return D; +} + +static void ecc_montgomery_normalise(MontgomeryPoint *mp) +{ + MontgomeryCurve *mc = mp->mc; + mp_int *zinv = monty_invert(mc->mc, mp->Z); + monty_mul_into(mc->mc, mp->X, mp->X, zinv); + monty_mul_into(mc->mc, mp->Z, mp->Z, zinv); + mp_free(zinv); +} + +MontgomeryPoint *ecc_montgomery_multiply(MontgomeryPoint *B, mp_int *n) +{ + /* + * 'Montgomery ladder' technique, to compute an arbitrary integer + * multiple of B under the constraint that you can only add two + * unequal points if you also know their difference. + * + * The setup is that you maintain two curve points one of which is + * always the other one plus B. Call them kB and (k+1)B, where k + * is some integer that evolves as we go along. We begin by + * doubling the input B, to initialise those points to B and 2B, + * so that k=1. + * + * At each stage, we add kB and (k+1)B together - which we can do + * under the differential-addition constraint because we know + * their difference is always just B - to give us (2k+1)B. Then we + * double one of kB or (k+1)B, and depending on which one we + * choose, we end up with (2k)B or (2k+2)B. Either way, that + * differs by B from the other value we've just computed. So in + * each iteration, we do one diff-add and one doubling, plus a + * couple of conditional swaps to choose which value we double and + * which way round we put the output points, and the effect is to + * replace k with either 2k or 2k+1, which we choose based on the + * appropriate bit of the desired exponent. + * + * This routine doesn't assume we know the exact location of the + * topmost set bit of the exponent. So to maintain constant time + * it does an iteration for every _potential_ bit, starting from + * the top downwards; after each iteration in which we haven't + * seen a set exponent bit yet, we just overwrite the two points + * with B and 2B again, + */ + + MontgomeryPoint *two_B = ecc_montgomery_double(B); + MontgomeryPoint *k_B = ecc_montgomery_point_copy(B); + MontgomeryPoint *kplus1_B = ecc_montgomery_point_copy(two_B); + + unsigned not_started_yet = 1; + for (size_t bitindex = mp_max_bits(n); bitindex-- > 0 ;) { + unsigned nbit = mp_get_bit(n, bitindex); + + MontgomeryPoint *sum = ecc_montgomery_diff_add(k_B, kplus1_B, B); + ecc_montgomery_cond_swap(k_B, kplus1_B, nbit); + MontgomeryPoint *other = ecc_montgomery_double(k_B); + ecc_montgomery_point_free(k_B); + ecc_montgomery_point_free(kplus1_B); + k_B = other; + kplus1_B = sum; + ecc_montgomery_cond_swap(k_B, kplus1_B, nbit); + + ecc_montgomery_cond_overwrite(k_B, B, not_started_yet); + ecc_montgomery_cond_overwrite(kplus1_B, two_B, not_started_yet); + not_started_yet &= ~nbit; + } + + ecc_montgomery_point_free(two_B); + ecc_montgomery_point_free(kplus1_B); + return k_B; +} + +void ecc_montgomery_get_affine(MontgomeryPoint *mp, mp_int **x) +{ + MontgomeryCurve *mc = mp->mc; + + ecc_montgomery_normalise(mp); + + if (x) + *x = monty_export(mc->mc, mp->X); +} + +unsigned ecc_montgomery_is_identity(MontgomeryPoint *mp) +{ + return mp_eq_integer(mp->Z, 0); +} + +/* ---------------------------------------------------------------------- + * Twisted Edwards curves. + */ + +struct EdwardsPoint { + /* + * We represent an Edwards curve point in 'extended coordinates'. + * There's more than one coordinate system going by that name, + * unfortunately. These ones have the semantics that X,Y,Z are + * ordinary projective coordinates (so x=X/Z and y=Y/Z), but also, + * we store the extra value T = xyZ = XY/Z. + */ + mp_int *X, *Y, *Z, *T; + + EdwardsCurve *ec; +}; + +struct EdwardsCurve { + /* Prime modulus of the finite field. */ + mp_int *p; + + /* Montgomery context for arithmetic mod p. */ + MontyContext *mc; + + /* Modsqrt context for point decompression. */ + ModsqrtContext *sc; + + /* Parameters of the curve, in Montgomery-multiplication + * transformed form. */ + mp_int *d, *a; +}; + +EdwardsCurve *ecc_edwards_curve(mp_int *p, mp_int *d, mp_int *a, + mp_int *nonsquare_mod_p) +{ + EdwardsCurve *ec = snew(EdwardsCurve); + ec->p = mp_copy(p); + ec->mc = monty_new(p); + ec->d = monty_import(ec->mc, d); + ec->a = monty_import(ec->mc, a); + + if (nonsquare_mod_p) + ec->sc = modsqrt_new(p, nonsquare_mod_p); + else + ec->sc = NULL; + + return ec; +} + +void ecc_edwards_curve_free(EdwardsCurve *ec) +{ + mp_free(ec->p); + mp_free(ec->d); + mp_free(ec->a); + monty_free(ec->mc); + if (ec->sc) + modsqrt_free(ec->sc); + sfree(ec); +} + +static EdwardsPoint *ecc_edwards_point_new_empty(EdwardsCurve *ec) +{ + EdwardsPoint *ep = snew(EdwardsPoint); + ep->ec = ec; + ep->X = ep->Y = ep->Z = ep->T = NULL; + return ep; +} + +static EdwardsPoint *ecc_edwards_point_new_imported( + EdwardsCurve *ec, mp_int *monty_x, mp_int *monty_y) +{ + EdwardsPoint *ep = ecc_edwards_point_new_empty(ec); + ep->X = monty_x; + ep->Y = monty_y; + ep->T = monty_mul(ec->mc, ep->X, ep->Y); + ep->Z = mp_copy(monty_identity(ec->mc)); + return ep; +} + +EdwardsPoint *ecc_edwards_point_new( + EdwardsCurve *ec, mp_int *x, mp_int *y) +{ + return ecc_edwards_point_new_imported( + ec, monty_import(ec->mc, x), monty_import(ec->mc, y)); +} + +void ecc_edwards_point_copy_into(EdwardsPoint *dest, EdwardsPoint *src) +{ + mp_copy_into(dest->X, src->X); + mp_copy_into(dest->Y, src->Y); + mp_copy_into(dest->Z, src->Z); + mp_copy_into(dest->T, src->T); +} + +EdwardsPoint *ecc_edwards_point_copy(EdwardsPoint *orig) +{ + EdwardsPoint *ep = ecc_edwards_point_new_empty(orig->ec); + ep->X = mp_copy(orig->X); + ep->Y = mp_copy(orig->Y); + ep->Z = mp_copy(orig->Z); + ep->T = mp_copy(orig->T); + return ep; +} + +void ecc_edwards_point_free(EdwardsPoint *ep) +{ + mp_free(ep->X); + mp_free(ep->Y); + mp_free(ep->Z); + mp_free(ep->T); + smemclr(ep, sizeof(*ep)); + sfree(ep); +} + +EdwardsPoint *ecc_edwards_point_new_from_y( + EdwardsCurve *ec, mp_int *yorig, unsigned desired_x_parity) +{ + assert(ec->sc); + + /* + * The curve equation is ax^2 + y^2 = 1 + dx^2y^2, which + * rearranges to x^2(dy^2-a) = y^2-1. So we compute + * (y^2-1)/(dy^2-a) and take its square root. + */ + unsigned success; + + mp_int *y = monty_import(ec->mc, yorig); + mp_int *y2 = monty_mul(ec->mc, y, y); + mp_int *dy2 = monty_mul(ec->mc, ec->d, y2); + mp_int *dy2ma = monty_sub(ec->mc, dy2, ec->a); + mp_int *y2m1 = monty_sub(ec->mc, y2, monty_identity(ec->mc)); + mp_int *recip_denominator = monty_invert(ec->mc, dy2ma); + mp_int *radicand = monty_mul(ec->mc, y2m1, recip_denominator); + mp_int *x = monty_modsqrt(ec->sc, radicand, &success); + mp_free(y2); + mp_free(dy2); + mp_free(dy2ma); + mp_free(y2m1); + mp_free(recip_denominator); + mp_free(radicand); + + if (!success) { + /* Failure! x^2 worked out to be a number that has no square + * root mod p. In this situation there's no point in trying to + * be time-constant, since the protocol sequence is going to + * diverge anyway when we complain to whoever gave us this + * bogus value. */ + mp_free(x); + mp_free(y); + return NULL; + } + + /* + * Choose whichever of x and p-x has the specified parity (of its + * lowest positive residue mod p). + */ + mp_int *tmp = monty_export(ec->mc, x); + unsigned flip = (mp_get_bit(tmp, 0) ^ desired_x_parity) & 1; + mp_sub_into(tmp, ec->p, x); + mp_select_into(x, x, tmp, flip); + mp_free(tmp); + + return ecc_edwards_point_new_imported(ec, x, y); +} + +static void ecc_edwards_cond_overwrite( + EdwardsPoint *dest, EdwardsPoint *src, unsigned overwrite) +{ + mp_select_into(dest->X, dest->X, src->X, overwrite); + mp_select_into(dest->Y, dest->Y, src->Y, overwrite); + mp_select_into(dest->Z, dest->Z, src->Z, overwrite); + mp_select_into(dest->T, dest->T, src->T, overwrite); +} + +static void ecc_edwards_cond_swap( + EdwardsPoint *P, EdwardsPoint *Q, unsigned swap) +{ + mp_cond_swap(P->X, Q->X, swap); + mp_cond_swap(P->Y, Q->Y, swap); + mp_cond_swap(P->Z, Q->Z, swap); + mp_cond_swap(P->T, Q->T, swap); +} + +EdwardsPoint *ecc_edwards_add(EdwardsPoint *P, EdwardsPoint *Q) +{ + EdwardsCurve *ec = P->ec; + assert(Q->ec == ec); + + EdwardsPoint *S = ecc_edwards_point_new_empty(ec); + + /* + * The affine rule for Edwards addition of (x1,y1) and (x2,y2) is + * + * x_out = (x1 y2 + y1 x2) / (1 + d x1 x2 y1 y2) + * y_out = (y1 y2 - a x1 x2) / (1 - d x1 x2 y1 y2) + * + * The formulae below are listed as 'add-2008-hwcd' in + * https://hyperelliptic.org/EFD/g1p/auto-twisted-extended.html + * + * and if you undo the careful optimisation to find out what + * they're actually computing, it comes out to + * + * X_out = (X1 Y2 + Y1 X2) (Z1 Z2 - d T1 T2) + * Y_out = (Y1 Y2 - a X1 X2) (Z1 Z2 + d T1 T2) + * Z_out = (Z1 Z2 - d T1 T2) (Z1 Z2 + d T1 T2) + * T_out = (X1 Y2 + Y1 X2) (Y1 Y2 - a X1 X2) + */ + mp_int *PxQx = monty_mul(ec->mc, P->X, Q->X); + mp_int *PyQy = monty_mul(ec->mc, P->Y, Q->Y); + mp_int *PtQt = monty_mul(ec->mc, P->T, Q->T); + mp_int *PzQz = monty_mul(ec->mc, P->Z, Q->Z); + mp_int *Psum = monty_add(ec->mc, P->X, P->Y); + mp_int *Qsum = monty_add(ec->mc, Q->X, Q->Y); + mp_int *aPxQx = monty_mul(ec->mc, ec->a, PxQx); + mp_int *dPtQt = monty_mul(ec->mc, ec->d, PtQt); + mp_int *sumprod = monty_mul(ec->mc, Psum, Qsum); + mp_int *xx_p_yy = monty_add(ec->mc, PxQx, PyQy); + mp_int *E = monty_sub(ec->mc, sumprod, xx_p_yy); + mp_int *F = monty_sub(ec->mc, PzQz, dPtQt); + mp_int *G = monty_add(ec->mc, PzQz, dPtQt); + mp_int *H = monty_sub(ec->mc, PyQy, aPxQx); + S->X = monty_mul(ec->mc, E, F); + S->Z = monty_mul(ec->mc, F, G); + S->Y = monty_mul(ec->mc, G, H); + S->T = monty_mul(ec->mc, H, E); + + mp_free(PxQx); + mp_free(PyQy); + mp_free(PtQt); + mp_free(PzQz); + mp_free(Psum); + mp_free(Qsum); + mp_free(aPxQx); + mp_free(dPtQt); + mp_free(sumprod); + mp_free(xx_p_yy); + mp_free(E); + mp_free(F); + mp_free(G); + mp_free(H); + + return S; +} + +static void ecc_edwards_normalise(EdwardsPoint *ep) +{ + EdwardsCurve *ec = ep->ec; + mp_int *zinv = monty_invert(ec->mc, ep->Z); + monty_mul_into(ec->mc, ep->X, ep->X, zinv); + monty_mul_into(ec->mc, ep->Y, ep->Y, zinv); + monty_mul_into(ec->mc, ep->Z, ep->Z, zinv); + mp_free(zinv); + monty_mul_into(ec->mc, ep->T, ep->X, ep->Y); +} + +EdwardsPoint *ecc_edwards_multiply(EdwardsPoint *B, mp_int *n) +{ + EdwardsPoint *two_B = ecc_edwards_add(B, B); + EdwardsPoint *k_B = ecc_edwards_point_copy(B); + EdwardsPoint *kplus1_B = ecc_edwards_point_copy(two_B); + + /* + * Another copy of the same exponentiation routine following the + * pattern of the Montgomery ladder, because it works as well as + * any other technique and this way I didn't have to debug two of + * them. + */ + + unsigned not_started_yet = 1; + for (size_t bitindex = mp_max_bits(n); bitindex-- > 0 ;) { + unsigned nbit = mp_get_bit(n, bitindex); + + EdwardsPoint *sum = ecc_edwards_add(k_B, kplus1_B); + ecc_edwards_cond_swap(k_B, kplus1_B, nbit); + EdwardsPoint *other = ecc_edwards_add(k_B, k_B); + ecc_edwards_point_free(k_B); + ecc_edwards_point_free(kplus1_B); + k_B = other; + kplus1_B = sum; + ecc_edwards_cond_swap(k_B, kplus1_B, nbit); + + ecc_edwards_cond_overwrite(k_B, B, not_started_yet); + ecc_edwards_cond_overwrite(kplus1_B, two_B, not_started_yet); + not_started_yet &= ~nbit; + } + + ecc_edwards_point_free(two_B); + ecc_edwards_point_free(kplus1_B); + return k_B; +} + +/* + * Helper routine to determine whether two values each given as a pair + * of projective coordinates represent the same affine value. + */ +static inline unsigned projective_eq( + MontyContext *mc, mp_int *An, mp_int *Ad, + mp_int *Bn, mp_int *Bd) +{ + mp_int *AnBd = monty_mul(mc, An, Bd); + mp_int *BnAd = monty_mul(mc, Bn, Ad); + unsigned toret = mp_cmp_eq(AnBd, BnAd); + mp_free(AnBd); + mp_free(BnAd); + return toret; +} + +unsigned ecc_edwards_eq(EdwardsPoint *P, EdwardsPoint *Q) +{ + EdwardsCurve *ec = P->ec; + assert(Q->ec == ec); + + return (projective_eq(ec->mc, P->X, P->Z, Q->X, Q->Z) & + projective_eq(ec->mc, P->Y, P->Z, Q->Y, Q->Z)); +} + +void ecc_edwards_get_affine(EdwardsPoint *ep, mp_int **x, mp_int **y) +{ + EdwardsCurve *ec = ep->ec; + + ecc_edwards_normalise(ep); + + if (x) + *x = monty_export(ec->mc, ep->X); + if (y) + *y = monty_export(ec->mc, ep->Y); +} diff --git a/crypto/ecc-ssh.c b/crypto/ecc-ssh.c new file mode 100644 index 00000000..c227f30f --- /dev/null +++ b/crypto/ecc-ssh.c @@ -0,0 +1,1698 @@ +/* + * Elliptic-curve signing and key exchange for PuTTY's SSH layer. + */ + +/* + * References: + * + * Elliptic curves in SSH are specified in RFC 5656: + * http://tools.ietf.org/html/rfc5656 + * + * That specification delegates details of public key formatting and a + * lot of underlying mechanism to SEC 1: + * http://www.secg.org/sec1-v2.pdf + * + * Montgomery maths from: + * Handbook of elliptic and hyperelliptic curve cryptography, Chapter 13 + * http://cs.ucsb.edu/~koc/ccs130h/2013/EllipticHyperelliptic-CohenFrey.pdf + * + * Curve25519 spec from libssh (with reference to other things in the + * libssh code): + * https://git.libssh.org/users/aris/libssh.git/tree/doc/curve25519-sha256@libssh.org.txt + * + * Edwards DSA: + * http://ed25519.cr.yp.to/ed25519-20110926.pdf + */ + +#include +#include + +#include "ssh.h" +#include "mpint.h" +#include "ecc.h" + +/* ---------------------------------------------------------------------- + * Elliptic curve definitions + */ + +static void initialise_common( + struct ec_curve *curve, EllipticCurveType type, mp_int *p, + unsigned extrabits) +{ + curve->type = type; + curve->p = mp_copy(p); + curve->fieldBits = mp_get_nbits(p); + curve->fieldBytes = (curve->fieldBits + extrabits + 7) / 8; +} + +static void initialise_wcurve( + struct ec_curve *curve, mp_int *p, mp_int *a, mp_int *b, + mp_int *nonsquare, mp_int *G_x, mp_int *G_y, mp_int *G_order) +{ + initialise_common(curve, EC_WEIERSTRASS, p, 0); + + curve->w.wc = ecc_weierstrass_curve(p, a, b, nonsquare); + + curve->w.G = ecc_weierstrass_point_new(curve->w.wc, G_x, G_y); + curve->w.G_order = mp_copy(G_order); +} + +static void initialise_mcurve( + struct ec_curve *curve, mp_int *p, mp_int *a, mp_int *b, + mp_int *G_x, unsigned log2_cofactor) +{ + initialise_common(curve, EC_MONTGOMERY, p, 0); + + curve->m.mc = ecc_montgomery_curve(p, a, b); + curve->m.log2_cofactor = log2_cofactor; + + curve->m.G = ecc_montgomery_point_new(curve->m.mc, G_x); +} + +static void initialise_ecurve( + struct ec_curve *curve, mp_int *p, mp_int *d, mp_int *a, + mp_int *nonsquare, mp_int *G_x, mp_int *G_y, mp_int *G_order, + unsigned log2_cofactor) +{ + /* Ensure curve->fieldBytes is long enough to store an extra bit + * for a compressed point */ + initialise_common(curve, EC_EDWARDS, p, 1); + + curve->e.ec = ecc_edwards_curve(p, d, a, nonsquare); + curve->e.log2_cofactor = log2_cofactor; + + curve->e.G = ecc_edwards_point_new(curve->e.ec, G_x, G_y); + curve->e.G_order = mp_copy(G_order); +} + +static struct ec_curve *ec_p256(void) +{ + static struct ec_curve curve = { 0 }; + static bool initialised = false; + + if (!initialised) + { + mp_int *p = MP_LITERAL(0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff); + mp_int *a = MP_LITERAL(0xffffffff00000001000000000000000000000000fffffffffffffffffffffffc); + mp_int *b = MP_LITERAL(0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b); + mp_int *G_x = MP_LITERAL(0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296); + mp_int *G_y = MP_LITERAL(0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5); + mp_int *G_order = MP_LITERAL(0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551); + mp_int *nonsquare_mod_p = mp_from_integer(3); + initialise_wcurve(&curve, p, a, b, nonsquare_mod_p, G_x, G_y, G_order); + mp_free(p); + mp_free(a); + mp_free(b); + mp_free(G_x); + mp_free(G_y); + mp_free(G_order); + mp_free(nonsquare_mod_p); + + curve.textname = curve.name = "nistp256"; + + /* Now initialised, no need to do it again */ + initialised = true; + } + + return &curve; +} + +static struct ec_curve *ec_p384(void) +{ + static struct ec_curve curve = { 0 }; + static bool initialised = false; + + if (!initialised) + { + mp_int *p = MP_LITERAL(0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000ffffffff); + mp_int *a = MP_LITERAL(0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000fffffffc); + mp_int *b = MP_LITERAL(0xb3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875ac656398d8a2ed19d2a85c8edd3ec2aef); + mp_int *G_x = MP_LITERAL(0xaa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760ab7); + mp_int *G_y = MP_LITERAL(0x3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5f); + mp_int *G_order = MP_LITERAL(0xffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52973); + mp_int *nonsquare_mod_p = mp_from_integer(19); + initialise_wcurve(&curve, p, a, b, nonsquare_mod_p, G_x, G_y, G_order); + mp_free(p); + mp_free(a); + mp_free(b); + mp_free(G_x); + mp_free(G_y); + mp_free(G_order); + mp_free(nonsquare_mod_p); + + curve.textname = curve.name = "nistp384"; + + /* Now initialised, no need to do it again */ + initialised = true; + } + + return &curve; +} + +static struct ec_curve *ec_p521(void) +{ + static struct ec_curve curve = { 0 }; + static bool initialised = false; + + if (!initialised) + { + mp_int *p = MP_LITERAL(0x01ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff); + mp_int *a = MP_LITERAL(0x01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc); + mp_int *b = MP_LITERAL(0x0051953eb9618e1c9a1f929a21a0b68540eea2da725b99b315f3b8b489918ef109e156193951ec7e937b1652c0bd3bb1bf073573df883d2c34f1ef451fd46b503f00); + mp_int *G_x = MP_LITERAL(0x00c6858e06b70404e9cd9e3ecb662395b4429c648139053fb521f828af606b4d3dbaa14b5e77efe75928fe1dc127a2ffa8de3348b3c1856a429bf97e7e31c2e5bd66); + mp_int *G_y = MP_LITERAL(0x011839296a789a3bc0045c8a5fb42c7d1bd998f54449579b446817afbd17273e662c97ee72995ef42640c550b9013fad0761353c7086a272c24088be94769fd16650); + mp_int *G_order = MP_LITERAL(0x01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386409); + mp_int *nonsquare_mod_p = mp_from_integer(3); + initialise_wcurve(&curve, p, a, b, nonsquare_mod_p, G_x, G_y, G_order); + mp_free(p); + mp_free(a); + mp_free(b); + mp_free(G_x); + mp_free(G_y); + mp_free(G_order); + mp_free(nonsquare_mod_p); + + curve.textname = curve.name = "nistp521"; + + /* Now initialised, no need to do it again */ + initialised = true; + } + + return &curve; +} + +static struct ec_curve *ec_curve25519(void) +{ + static struct ec_curve curve = { 0 }; + static bool initialised = false; + + if (!initialised) + { + mp_int *p = MP_LITERAL(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed); + mp_int *a = MP_LITERAL(0x0000000000000000000000000000000000000000000000000000000000076d06); + mp_int *b = MP_LITERAL(0x0000000000000000000000000000000000000000000000000000000000000001); + mp_int *G_x = MP_LITERAL(0x0000000000000000000000000000000000000000000000000000000000000009); + initialise_mcurve(&curve, p, a, b, G_x, 3); + mp_free(p); + mp_free(a); + mp_free(b); + mp_free(G_x); + + /* This curve doesn't need a name, because it's never used in + * any format that embeds the curve name */ + curve.name = NULL; + curve.textname = "Curve25519"; + + /* Now initialised, no need to do it again */ + initialised = true; + } + + return &curve; +} + +static struct ec_curve *ec_curve448(void) +{ + static struct ec_curve curve = { 0 }; + static bool initialised = false; + + if (!initialised) + { + mp_int *p = MP_LITERAL(0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffffff); + mp_int *a = MP_LITERAL(0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000262a6); + mp_int *b = MP_LITERAL(0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001); + mp_int *G_x = MP_LITERAL(0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005); + initialise_mcurve(&curve, p, a, b, G_x, 2); + mp_free(p); + mp_free(a); + mp_free(b); + mp_free(G_x); + + /* This curve doesn't need a name, because it's never used in + * any format that embeds the curve name */ + curve.name = NULL; + curve.textname = "Curve448"; + + /* Now initialised, no need to do it again */ + initialised = true; + } + + return &curve; +} + +static struct ec_curve *ec_ed25519(void) +{ + static struct ec_curve curve = { 0 }; + static bool initialised = false; + + if (!initialised) + { + mp_int *p = MP_LITERAL(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed); + mp_int *d = MP_LITERAL(0x52036cee2b6ffe738cc740797779e89800700a4d4141d8ab75eb4dca135978a3); + mp_int *a = MP_LITERAL(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffec); /* == p-1 */ + mp_int *G_x = MP_LITERAL(0x216936d3cd6e53fec0a4e231fdd6dc5c692cc7609525a7b2c9562d608f25d51a); + mp_int *G_y = MP_LITERAL(0x6666666666666666666666666666666666666666666666666666666666666658); + mp_int *G_order = MP_LITERAL(0x1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3ed); + mp_int *nonsquare_mod_p = mp_from_integer(2); + initialise_ecurve(&curve, p, d, a, nonsquare_mod_p, + G_x, G_y, G_order, 3); + mp_free(p); + mp_free(d); + mp_free(a); + mp_free(G_x); + mp_free(G_y); + mp_free(G_order); + mp_free(nonsquare_mod_p); + + /* This curve doesn't need a name, because it's never used in + * any format that embeds the curve name */ + curve.name = NULL; + + curve.textname = "Ed25519"; + + /* Now initialised, no need to do it again */ + initialised = true; + } + + return &curve; +} + +static struct ec_curve *ec_ed448(void) +{ + static struct ec_curve curve = { 0 }; + static bool initialised = false; + + if (!initialised) + { + mp_int *p = MP_LITERAL(0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffffff); + mp_int *d = MP_LITERAL(0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffffffffffffffffffffffffffffffffffffffffffffffff6756); /* = p - 39081 */ + mp_int *a = MP_LITERAL(0x1); + mp_int *G_x = MP_LITERAL(0x4f1970c66bed0ded221d15a622bf36da9e146570470f1767ea6de324a3d3a46412ae1af72ab66511433b80e18b00938e2626a82bc70cc05e); + mp_int *G_y = MP_LITERAL(0x693f46716eb6bc248876203756c9c7624bea73736ca3984087789c1e05a0c2d73ad3ff1ce67c39c4fdbd132c4ed7c8ad9808795bf230fa14); + mp_int *G_order = MP_LITERAL(0x3fffffffffffffffffffffffffffffffffffffffffffffffffffffff7cca23e9c44edb49aed63690216cc2728dc58f552378c292ab5844f3); + mp_int *nonsquare_mod_p = mp_from_integer(7); + initialise_ecurve(&curve, p, d, a, nonsquare_mod_p, + G_x, G_y, G_order, 2); + mp_free(p); + mp_free(d); + mp_free(a); + mp_free(G_x); + mp_free(G_y); + mp_free(G_order); + mp_free(nonsquare_mod_p); + + /* This curve doesn't need a name, because it's never used in + * any format that embeds the curve name */ + curve.name = NULL; + + curve.textname = "Ed448"; + + /* Now initialised, no need to do it again */ + initialised = true; + } + + return &curve; +} + +/* ---------------------------------------------------------------------- + * Public point from private + */ + +struct ecsign_extra { + struct ec_curve *(*curve)(void); + const ssh_hashalg *hash; + + /* These fields are used by the OpenSSH PEM format importer/exporter */ + const unsigned char *oid; + int oidlen; + + /* Some EdDSA instances prefix a string to all hash preimages, to + * disambiguate which signature variant they're being used with */ + ptrlen hash_prefix; +}; + +WeierstrassPoint *ecdsa_public(mp_int *private_key, const ssh_keyalg *alg) +{ + const struct ecsign_extra *extra = + (const struct ecsign_extra *)alg->extra; + struct ec_curve *curve = extra->curve(); + assert(curve->type == EC_WEIERSTRASS); + + mp_int *priv_reduced = mp_mod(private_key, curve->p); + WeierstrassPoint *toret = ecc_weierstrass_multiply( + curve->w.G, priv_reduced); + mp_free(priv_reduced); + return toret; +} + +static mp_int *eddsa_exponent_from_hash( + ptrlen hash, const struct ec_curve *curve) +{ + /* + * Make an integer out of the hash data, little-endian. + */ + assert(hash.len >= curve->fieldBytes); + mp_int *e = mp_from_bytes_le(make_ptrlen(hash.ptr, curve->fieldBytes)); + + /* + * Set the highest bit that fits in the modulus, and clear any + * above that. + */ + mp_set_bit(e, curve->fieldBits - 1, 1); + mp_reduce_mod_2to(e, curve->fieldBits); + + /* + * Clear a curve-specific number of low bits. + */ + for (unsigned bit = 0; bit < curve->e.log2_cofactor; bit++) + mp_set_bit(e, bit, 0); + + return e; +} + +EdwardsPoint *eddsa_public(mp_int *private_key, const ssh_keyalg *alg) +{ + const struct ecsign_extra *extra = + (const struct ecsign_extra *)alg->extra; + struct ec_curve *curve = extra->curve(); + assert(curve->type == EC_EDWARDS); + + ssh_hash *h = ssh_hash_new(extra->hash); + for (size_t i = 0; i < curve->fieldBytes; ++i) + put_byte(h, mp_get_byte(private_key, i)); + + unsigned char hash[MAX_HASH_LEN]; + ssh_hash_final(h, hash); + + mp_int *exponent = eddsa_exponent_from_hash( + make_ptrlen(hash, extra->hash->hlen), curve); + + EdwardsPoint *toret = ecc_edwards_multiply(curve->e.G, exponent); + mp_free(exponent); + + return toret; +} + +/* ---------------------------------------------------------------------- + * Marshalling and unmarshalling functions + */ + +static mp_int *BinarySource_get_mp_le(BinarySource *src) +{ + return mp_from_bytes_le(get_string(src)); +} +#define get_mp_le(src) BinarySource_get_mp_le(BinarySource_UPCAST(src)) + +static void BinarySink_put_mp_le_fixedlen(BinarySink *bs, mp_int *x, + size_t bytes) +{ + put_uint32(bs, bytes); + for (size_t i = 0; i < bytes; ++i) + put_byte(bs, mp_get_byte(x, i)); +} +#define put_mp_le_fixedlen(bs, x, bytes) \ + BinarySink_put_mp_le_fixedlen(BinarySink_UPCAST(bs), x, bytes) + +static WeierstrassPoint *ecdsa_decode( + ptrlen encoded, const struct ec_curve *curve) +{ + assert(curve->type == EC_WEIERSTRASS); + BinarySource src[1]; + + BinarySource_BARE_INIT_PL(src, encoded); + unsigned char format_type = get_byte(src); + + WeierstrassPoint *P; + + size_t len = get_avail(src); + mp_int *x; + mp_int *y; + + switch (format_type) { + case 0: + /* The identity. */ + P = ecc_weierstrass_point_new_identity(curve->w.wc); + break; + case 2: + case 3: + /* A compressed point, in which the x-coordinate is stored in + * full, and y is deduced from that and a single bit + * indicating its parity (stored in the format type byte). */ + x = mp_from_bytes_be(get_data(src, len)); + P = ecc_weierstrass_point_new_from_x(curve->w.wc, x, format_type & 1); + mp_free(x); + if (!P) /* this can fail if the input is invalid */ + return NULL; + break; + case 4: + /* An uncompressed point: the x,y coordinates are stored in + * full. We expect the rest of the string to have even length, + * and be divided half and half between the two values. */ + if (len % 2 != 0) + return NULL; + len /= 2; + x = mp_from_bytes_be(get_data(src, len)); + y = mp_from_bytes_be(get_data(src, len)); + P = ecc_weierstrass_point_new(curve->w.wc, x, y); + mp_free(x); + mp_free(y); + break; + default: + /* An unrecognised type byte. */ + return NULL; + } + + /* Verify the point is on the curve */ + if (!ecc_weierstrass_point_valid(P)) { + ecc_weierstrass_point_free(P); + return NULL; + } + + return P; +} + +static WeierstrassPoint *BinarySource_get_wpoint( + BinarySource *src, const struct ec_curve *curve) +{ + ptrlen str = get_string(src); + if (get_err(src)) + return NULL; + return ecdsa_decode(str, curve); +} +#define get_wpoint(src, curve) \ + BinarySource_get_wpoint(BinarySource_UPCAST(src), curve) + +static void BinarySink_put_wpoint( + BinarySink *bs, WeierstrassPoint *point, const struct ec_curve *curve, + bool bare) +{ + strbuf *sb; + BinarySink *bs_inner; + + if (!bare) { + /* + * Encapsulate the raw data inside an outermost string layer. + */ + sb = strbuf_new(); + bs_inner = BinarySink_UPCAST(sb); + } else { + /* + * Just write the data directly to the output. + */ + bs_inner = bs; + } + + if (ecc_weierstrass_is_identity(point)) { + put_byte(bs_inner, 0); + } else { + mp_int *x, *y; + ecc_weierstrass_get_affine(point, &x, &y); + + /* + * For ECDSA, we only ever output uncompressed points. + */ + put_byte(bs_inner, 0x04); + for (size_t i = curve->fieldBytes; i--;) + put_byte(bs_inner, mp_get_byte(x, i)); + for (size_t i = curve->fieldBytes; i--;) + put_byte(bs_inner, mp_get_byte(y, i)); + + mp_free(x); + mp_free(y); + } + + if (!bare) + put_stringsb(bs, sb); +} +#define put_wpoint(bs, point, curve, bare) \ + BinarySink_put_wpoint(BinarySink_UPCAST(bs), point, curve, bare) + +static EdwardsPoint *eddsa_decode(ptrlen encoded, const struct ec_curve *curve) +{ + assert(curve->type == EC_EDWARDS); + + mp_int *y = mp_from_bytes_le(encoded); + + /* The topmost bit of the encoding isn't part of y, so it stores + * the bottom bit of x. Extract it, and zero that bit in y. */ + unsigned desired_x_parity = mp_get_bit(y, curve->fieldBytes * 8 - 1); + mp_set_bit(y, curve->fieldBytes * 8 - 1, 0); + + /* What's left should now be within the range of the curve's modulus */ + if (mp_cmp_hs(y, curve->p)) { + mp_free(y); + return NULL; + } + + EdwardsPoint *P = ecc_edwards_point_new_from_y( + curve->e.ec, y, desired_x_parity); + mp_free(y); + + /* A point constructed in this way will always satisfy the curve + * equation, unless ecc.c wasn't able to construct one at all, in + * which case P is now NULL. Either way, return it. */ + return P; +} + +static EdwardsPoint *BinarySource_get_epoint( + BinarySource *src, const struct ec_curve *curve) +{ + ptrlen str = get_string(src); + if (get_err(src)) + return NULL; + return eddsa_decode(str, curve); +} +#define get_epoint(src, curve) \ + BinarySource_get_epoint(BinarySource_UPCAST(src), curve) + +static void BinarySink_put_epoint( + BinarySink *bs, EdwardsPoint *point, const struct ec_curve *curve, + bool bare) +{ + mp_int *x, *y; + ecc_edwards_get_affine(point, &x, &y); + + assert(curve->fieldBytes >= 2); + + /* + * EdDSA requires point compression. We store a single integer, + * with bytes in little-endian order, which mostly contains y but + * in which the topmost bit is the low bit of x. + */ + if (!bare) + put_uint32(bs, curve->fieldBytes); /* string length field */ + for (size_t i = 0; i < curve->fieldBytes - 1; i++) + put_byte(bs, mp_get_byte(y, i)); + put_byte(bs, (mp_get_byte(y, curve->fieldBytes - 1) & 0x7F) | + (mp_get_bit(x, 0) << 7)); + + mp_free(x); + mp_free(y); +} +#define put_epoint(bs, point, curve, bare) \ + BinarySink_put_epoint(BinarySink_UPCAST(bs), point, curve, bare) + +/* ---------------------------------------------------------------------- + * Exposed ECDSA interface + */ + +static void ecdsa_freekey(ssh_key *key) +{ + struct ecdsa_key *ek = container_of(key, struct ecdsa_key, sshk); + + if (ek->publicKey) + ecc_weierstrass_point_free(ek->publicKey); + if (ek->privateKey) + mp_free(ek->privateKey); + sfree(ek); +} + +static void eddsa_freekey(ssh_key *key) +{ + struct eddsa_key *ek = container_of(key, struct eddsa_key, sshk); + + if (ek->publicKey) + ecc_edwards_point_free(ek->publicKey); + if (ek->privateKey) + mp_free(ek->privateKey); + sfree(ek); +} + +static char *ec_signkey_invalid(ssh_key *key, unsigned flags) +{ + /* All validity criteria for both ECDSA and EdDSA were checked + * when we loaded the key in the first place */ + return NULL; +} + +static ssh_key *ecdsa_new_pub(const ssh_keyalg *alg, ptrlen data) +{ + const struct ecsign_extra *extra = + (const struct ecsign_extra *)alg->extra; + struct ec_curve *curve = extra->curve(); + assert(curve->type == EC_WEIERSTRASS); + + BinarySource src[1]; + BinarySource_BARE_INIT_PL(src, data); + get_string(src); + + /* Curve name is duplicated for Weierstrass form */ + if (!ptrlen_eq_string(get_string(src), curve->name)) + return NULL; + + struct ecdsa_key *ek = snew(struct ecdsa_key); + ek->sshk.vt = alg; + ek->curve = curve; + ek->privateKey = NULL; + + ek->publicKey = get_wpoint(src, curve); + if (!ek->publicKey) { + ecdsa_freekey(&ek->sshk); + return NULL; + } + + return &ek->sshk; +} + +static ssh_key *eddsa_new_pub(const ssh_keyalg *alg, ptrlen data) +{ + const struct ecsign_extra *extra = + (const struct ecsign_extra *)alg->extra; + struct ec_curve *curve = extra->curve(); + assert(curve->type == EC_EDWARDS); + + BinarySource src[1]; + BinarySource_BARE_INIT_PL(src, data); + get_string(src); + + struct eddsa_key *ek = snew(struct eddsa_key); + ek->sshk.vt = alg; + ek->curve = curve; + ek->privateKey = NULL; + + ek->publicKey = get_epoint(src, curve); + if (!ek->publicKey) { + eddsa_freekey(&ek->sshk); + return NULL; + } + + return &ek->sshk; +} + +static char *ecc_cache_str_shared( + const char *curve_name, mp_int *x, mp_int *y) +{ + strbuf *sb = strbuf_new(); + + if (curve_name) + strbuf_catf(sb, "%s,", curve_name); + + char *hx = mp_get_hex(x); + char *hy = mp_get_hex(y); + strbuf_catf(sb, "0x%s,0x%s", hx, hy); + sfree(hx); + sfree(hy); + + return strbuf_to_str(sb); +} + +static char *ecdsa_cache_str(ssh_key *key) +{ + struct ecdsa_key *ek = container_of(key, struct ecdsa_key, sshk); + mp_int *x, *y; + + ecc_weierstrass_get_affine(ek->publicKey, &x, &y); + char *toret = ecc_cache_str_shared(ek->curve->name, x, y); + mp_free(x); + mp_free(y); + return toret; +} + +static key_components *ecdsa_components(ssh_key *key) +{ + struct ecdsa_key *ek = container_of(key, struct ecdsa_key, sshk); + key_components *kc = key_components_new(); + + key_components_add_text(kc, "key_type", "ECDSA"); + key_components_add_text(kc, "curve_name", ek->curve->textname); + + mp_int *x, *y; + ecc_weierstrass_get_affine(ek->publicKey, &x, &y); + key_components_add_mp(kc, "public_affine_x", x); + key_components_add_mp(kc, "public_affine_y", y); + mp_free(x); + mp_free(y); + + if (ek->privateKey) + key_components_add_mp(kc, "private_exponent", ek->privateKey); + + return kc; +} + +static char *eddsa_cache_str(ssh_key *key) +{ + struct eddsa_key *ek = container_of(key, struct eddsa_key, sshk); + mp_int *x, *y; + + ecc_edwards_get_affine(ek->publicKey, &x, &y); + char *toret = ecc_cache_str_shared(ek->curve->name, x, y); + mp_free(x); + mp_free(y); + return toret; +} + +static key_components *eddsa_components(ssh_key *key) +{ + struct eddsa_key *ek = container_of(key, struct eddsa_key, sshk); + key_components *kc = key_components_new(); + + key_components_add_text(kc, "key_type", "EdDSA"); + key_components_add_text(kc, "curve_name", ek->curve->textname); + + mp_int *x, *y; + ecc_edwards_get_affine(ek->publicKey, &x, &y); + key_components_add_mp(kc, "public_affine_x", x); + key_components_add_mp(kc, "public_affine_y", y); + mp_free(x); + mp_free(y); + + if (ek->privateKey) + key_components_add_mp(kc, "private_exponent", ek->privateKey); + + return kc; +} + +static void ecdsa_public_blob(ssh_key *key, BinarySink *bs) +{ + struct ecdsa_key *ek = container_of(key, struct ecdsa_key, sshk); + + put_stringz(bs, ek->sshk.vt->ssh_id); + put_stringz(bs, ek->curve->name); + put_wpoint(bs, ek->publicKey, ek->curve, false); +} + +static void eddsa_public_blob(ssh_key *key, BinarySink *bs) +{ + struct eddsa_key *ek = container_of(key, struct eddsa_key, sshk); + + put_stringz(bs, ek->sshk.vt->ssh_id); + put_epoint(bs, ek->publicKey, ek->curve, false); +} + +static void ecdsa_private_blob(ssh_key *key, BinarySink *bs) +{ + struct ecdsa_key *ek = container_of(key, struct ecdsa_key, sshk); + + /* ECDSA uses ordinary SSH-2 mpint format to store the private key */ + assert(ek->privateKey); + put_mp_ssh2(bs, ek->privateKey); +} + +static void eddsa_private_blob(ssh_key *key, BinarySink *bs) +{ + struct eddsa_key *ek = container_of(key, struct eddsa_key, sshk); + + /* EdDSA stores the private key integer little-endian and unsigned */ + assert(ek->privateKey); + put_mp_le_fixedlen(bs, ek->privateKey, ek->curve->fieldBytes); +} + +static ssh_key *ecdsa_new_priv(const ssh_keyalg *alg, ptrlen pub, ptrlen priv) +{ + ssh_key *sshk = ecdsa_new_pub(alg, pub); + if (!sshk) + return NULL; + struct ecdsa_key *ek = container_of(sshk, struct ecdsa_key, sshk); + + BinarySource src[1]; + BinarySource_BARE_INIT_PL(src, priv); + ek->privateKey = get_mp_ssh2(src); + + return &ek->sshk; +} + +static ssh_key *eddsa_new_priv(const ssh_keyalg *alg, ptrlen pub, ptrlen priv) +{ + ssh_key *sshk = eddsa_new_pub(alg, pub); + if (!sshk) + return NULL; + struct eddsa_key *ek = container_of(sshk, struct eddsa_key, sshk); + + BinarySource src[1]; + BinarySource_BARE_INIT_PL(src, priv); + ek->privateKey = get_mp_le(src); + + return &ek->sshk; +} + +static ssh_key *eddsa_new_priv_openssh( + const ssh_keyalg *alg, BinarySource *src) +{ + const struct ecsign_extra *extra = + (const struct ecsign_extra *)alg->extra; + struct ec_curve *curve = extra->curve(); + assert(curve->type == EC_EDWARDS); + + ptrlen pubkey_pl = get_string(src); + ptrlen privkey_extended_pl = get_string(src); + if (get_err(src) || pubkey_pl.len != curve->fieldBytes) + return NULL; + + /* + * The OpenSSH format for ed25519 private keys also for some + * reason encodes an extra copy of the public key in the second + * half of the secret-key string. Check that that's present and + * correct as well, otherwise the key we think we've imported + * won't behave identically to the way OpenSSH would have treated + * it. + * + * We assume that Ed448 will work the same way, as and when + * OpenSSH implements it, which at the time of writing this they + * had not. + */ + BinarySource subsrc[1]; + BinarySource_BARE_INIT_PL(subsrc, privkey_extended_pl); + ptrlen privkey_pl = get_data(subsrc, curve->fieldBytes); + ptrlen pubkey_copy_pl = get_data(subsrc, curve->fieldBytes); + if (get_err(subsrc) || get_avail(subsrc)) + return NULL; + if (!ptrlen_eq_ptrlen(pubkey_pl, pubkey_copy_pl)) + return NULL; + + struct eddsa_key *ek = snew(struct eddsa_key); + ek->sshk.vt = alg; + ek->curve = curve; + ek->privateKey = NULL; + + ek->publicKey = eddsa_decode(pubkey_pl, curve); + if (!ek->publicKey) { + eddsa_freekey(&ek->sshk); + return NULL; + } + + ek->privateKey = mp_from_bytes_le(privkey_pl); + + return &ek->sshk; +} + +static void eddsa_openssh_blob(ssh_key *key, BinarySink *bs) +{ + struct eddsa_key *ek = container_of(key, struct eddsa_key, sshk); + assert(ek->curve->type == EC_EDWARDS); + + /* Encode the public and private points as strings */ + strbuf *pub_sb = strbuf_new(); + put_epoint(pub_sb, ek->publicKey, ek->curve, false); + ptrlen pub = make_ptrlen(pub_sb->s + 4, pub_sb->len - 4); + + strbuf *priv_sb = strbuf_new_nm(); + put_mp_le_fixedlen(priv_sb, ek->privateKey, ek->curve->fieldBytes); + ptrlen priv = make_ptrlen(priv_sb->s + 4, priv_sb->len - 4); + + put_stringpl(bs, pub); + + /* Encode the private key as the concatenation of the + * little-endian key integer and the public key again */ + put_uint32(bs, priv.len + pub.len); + put_datapl(bs, priv); + put_datapl(bs, pub); + + strbuf_free(pub_sb); + strbuf_free(priv_sb); +} + +static ssh_key *ecdsa_new_priv_openssh( + const ssh_keyalg *alg, BinarySource *src) +{ + const struct ecsign_extra *extra = + (const struct ecsign_extra *)alg->extra; + struct ec_curve *curve = extra->curve(); + assert(curve->type == EC_WEIERSTRASS); + + get_string(src); + + struct ecdsa_key *ek = snew(struct ecdsa_key); + ek->sshk.vt = alg; + ek->curve = curve; + ek->privateKey = NULL; + + ek->publicKey = get_wpoint(src, curve); + if (!ek->publicKey) { + ecdsa_freekey(&ek->sshk); + return NULL; + } + + ek->privateKey = get_mp_ssh2(src); + + return &ek->sshk; +} + +static void ecdsa_openssh_blob(ssh_key *key, BinarySink *bs) +{ + struct ecdsa_key *ek = container_of(key, struct ecdsa_key, sshk); + put_stringz(bs, ek->curve->name); + put_wpoint(bs, ek->publicKey, ek->curve, false); + put_mp_ssh2(bs, ek->privateKey); +} + +static int ec_shared_pubkey_bits(const ssh_keyalg *alg, ptrlen blob) +{ + const struct ecsign_extra *extra = + (const struct ecsign_extra *)alg->extra; + struct ec_curve *curve = extra->curve(); + return curve->fieldBits; +} + +static mp_int *ecdsa_signing_exponent_from_data( + const struct ec_curve *curve, const struct ecsign_extra *extra, + ptrlen data) +{ + /* Hash the data being signed. */ + unsigned char hash[MAX_HASH_LEN]; + ssh_hash *h = ssh_hash_new(extra->hash); + put_datapl(h, data); + ssh_hash_final(h, hash); + + /* + * Take the leftmost b bits of the hash of the signed data (where + * b is the number of bits in order(G)), interpreted big-endian. + */ + mp_int *z = mp_from_bytes_be(make_ptrlen(hash, extra->hash->hlen)); + size_t zbits = mp_get_nbits(z); + size_t nbits = mp_get_nbits(curve->w.G_order); + size_t shift = zbits - nbits; + /* Bound the shift count below at 0, using bit twiddling to avoid + * a conditional branch */ + shift &= ~-(shift >> (CHAR_BIT * sizeof(size_t) - 1)); + mp_int *toret = mp_rshift_safe(z, shift); + mp_free(z); + + return toret; +} + +static bool ecdsa_verify(ssh_key *key, ptrlen sig, ptrlen data) +{ + struct ecdsa_key *ek = container_of(key, struct ecdsa_key, sshk); + const struct ecsign_extra *extra = + (const struct ecsign_extra *)ek->sshk.vt->extra; + + BinarySource src[1]; + BinarySource_BARE_INIT_PL(src, sig); + + /* Check the signature starts with the algorithm name */ + if (!ptrlen_eq_string(get_string(src), ek->sshk.vt->ssh_id)) + return false; + + /* Everything else is nested inside a sub-string. Descend into that. */ + ptrlen sigstr = get_string(src); + if (get_err(src)) + return false; + BinarySource_BARE_INIT_PL(src, sigstr); + + /* Extract the signature integers r,s */ + mp_int *r = get_mp_ssh2(src); + mp_int *s = get_mp_ssh2(src); + if (get_err(src)) { + mp_free(r); + mp_free(s); + return false; + } + + /* Basic sanity checks: 0 < r,s < order(G) */ + unsigned invalid = 0; + invalid |= mp_eq_integer(r, 0); + invalid |= mp_eq_integer(s, 0); + invalid |= mp_cmp_hs(r, ek->curve->w.G_order); + invalid |= mp_cmp_hs(s, ek->curve->w.G_order); + + /* Get the hash of the signed data, converted to an integer */ + mp_int *z = ecdsa_signing_exponent_from_data(ek->curve, extra, data); + + /* Verify the signature integers against the hash */ + mp_int *w = mp_invert(s, ek->curve->w.G_order); + mp_int *u1 = mp_modmul(z, w, ek->curve->w.G_order); + mp_free(z); + mp_int *u2 = mp_modmul(r, w, ek->curve->w.G_order); + mp_free(w); + WeierstrassPoint *u1G = ecc_weierstrass_multiply(ek->curve->w.G, u1); + mp_free(u1); + WeierstrassPoint *u2P = ecc_weierstrass_multiply(ek->publicKey, u2); + mp_free(u2); + WeierstrassPoint *sum = ecc_weierstrass_add_general(u1G, u2P); + ecc_weierstrass_point_free(u1G); + ecc_weierstrass_point_free(u2P); + + mp_int *x; + ecc_weierstrass_get_affine(sum, &x, NULL); + ecc_weierstrass_point_free(sum); + + mp_divmod_into(x, ek->curve->w.G_order, NULL, x); + invalid |= (1 ^ mp_cmp_eq(r, x)); + mp_free(x); + + mp_free(r); + mp_free(s); + + return !invalid; +} + +static mp_int *eddsa_signing_exponent_from_data( + struct eddsa_key *ek, const struct ecsign_extra *extra, + ptrlen r_encoded, ptrlen data) +{ + /* Hash (r || public key || message) */ + unsigned char hash[MAX_HASH_LEN]; + ssh_hash *h = ssh_hash_new(extra->hash); + put_datapl(h, extra->hash_prefix); + put_datapl(h, r_encoded); + put_epoint(h, ek->publicKey, ek->curve, true); /* omit string header */ + put_datapl(h, data); + ssh_hash_final(h, hash); + + /* Convert to an integer */ + mp_int *toret = mp_from_bytes_le(make_ptrlen(hash, extra->hash->hlen)); + + smemclr(hash, extra->hash->hlen); + return toret; +} + +static bool eddsa_verify(ssh_key *key, ptrlen sig, ptrlen data) +{ + struct eddsa_key *ek = container_of(key, struct eddsa_key, sshk); + const struct ecsign_extra *extra = + (const struct ecsign_extra *)ek->sshk.vt->extra; + + BinarySource src[1]; + BinarySource_BARE_INIT_PL(src, sig); + + /* Check the signature starts with the algorithm name */ + if (!ptrlen_eq_string(get_string(src), ek->sshk.vt->ssh_id)) + return false; + + /* Now expect a single string which is the concatenation of an + * encoded curve point r and an integer s. */ + ptrlen sigstr = get_string(src); + if (get_err(src)) + return false; + BinarySource_BARE_INIT_PL(src, sigstr); + ptrlen rstr = get_data(src, ek->curve->fieldBytes); + ptrlen sstr = get_data(src, ek->curve->fieldBytes); + if (get_err(src) || get_avail(src)) + return false; + + EdwardsPoint *r = eddsa_decode(rstr, ek->curve); + if (!r) + return false; + mp_int *s = mp_from_bytes_le(sstr); + + mp_int *H = eddsa_signing_exponent_from_data(ek, extra, rstr, data); + + /* Verify that s*G == r + H*publicKey */ + EdwardsPoint *lhs = ecc_edwards_multiply(ek->curve->e.G, s); + mp_free(s); + EdwardsPoint *hpk = ecc_edwards_multiply(ek->publicKey, H); + mp_free(H); + EdwardsPoint *rhs = ecc_edwards_add(r, hpk); + ecc_edwards_point_free(hpk); + unsigned valid = ecc_edwards_eq(lhs, rhs); + ecc_edwards_point_free(lhs); + ecc_edwards_point_free(rhs); + ecc_edwards_point_free(r); + + return valid; +} + +static void ecdsa_sign(ssh_key *key, ptrlen data, + unsigned flags, BinarySink *bs) +{ + struct ecdsa_key *ek = container_of(key, struct ecdsa_key, sshk); + const struct ecsign_extra *extra = + (const struct ecsign_extra *)ek->sshk.vt->extra; + assert(ek->privateKey); + + mp_int *z = ecdsa_signing_exponent_from_data(ek->curve, extra, data); + + /* Generate k between 1 and curve->n, using the same deterministic + * k generation system we use for conventional DSA. */ + mp_int *k; + { + unsigned char digest[20]; + hash_simple(&ssh_sha1, data, digest); + k = dss_gen_k( + "ECDSA deterministic k generator", ek->curve->w.G_order, + ek->privateKey, digest, sizeof(digest)); + } + + WeierstrassPoint *kG = ecc_weierstrass_multiply(ek->curve->w.G, k); + mp_int *x; + ecc_weierstrass_get_affine(kG, &x, NULL); + ecc_weierstrass_point_free(kG); + + /* r = kG.x mod order(G) */ + mp_int *r = mp_mod(x, ek->curve->w.G_order); + mp_free(x); + + /* s = (z + r * priv)/k mod n */ + mp_int *rPriv = mp_modmul(r, ek->privateKey, ek->curve->w.G_order); + mp_int *numerator = mp_modadd(z, rPriv, ek->curve->w.G_order); + mp_free(z); + mp_free(rPriv); + mp_int *kInv = mp_invert(k, ek->curve->w.G_order); + mp_free(k); + mp_int *s = mp_modmul(numerator, kInv, ek->curve->w.G_order); + mp_free(numerator); + mp_free(kInv); + + /* Format the output */ + put_stringz(bs, ek->sshk.vt->ssh_id); + + strbuf *substr = strbuf_new(); + put_mp_ssh2(substr, r); + put_mp_ssh2(substr, s); + put_stringsb(bs, substr); + + mp_free(r); + mp_free(s); +} + +static void eddsa_sign(ssh_key *key, ptrlen data, + unsigned flags, BinarySink *bs) +{ + struct eddsa_key *ek = container_of(key, struct eddsa_key, sshk); + const struct ecsign_extra *extra = + (const struct ecsign_extra *)ek->sshk.vt->extra; + assert(ek->privateKey); + + /* + * EdDSA prescribes a specific method of generating the random + * nonce integer for the signature. (A verifier can't tell + * whether you followed that method, but it's important to + * follow it anyway, because test vectors will want a specific + * signature for a given message, and because this preserves + * determinism of signatures even if the same signature were + * made twice by different software.) + */ + + /* + * First, we hash the private key integer (bare, little-endian) + * into a hash generating 2*fieldBytes of output. + */ + unsigned char hash[MAX_HASH_LEN]; + ssh_hash *h = ssh_hash_new(extra->hash); + for (size_t i = 0; i < ek->curve->fieldBytes; ++i) + put_byte(h, mp_get_byte(ek->privateKey, i)); + ssh_hash_final(h, hash); + + /* + * The first half of the output hash is converted into an + * integer a, by the standard EdDSA transformation. + */ + mp_int *a = eddsa_exponent_from_hash( + make_ptrlen(hash, ek->curve->fieldBytes), ek->curve); + + /* + * The second half of the hash of the private key is hashed again + * with the message to be signed, and used as an exponent to + * generate the signature point r. + */ + h = ssh_hash_new(extra->hash); + put_datapl(h, extra->hash_prefix); + put_data(h, hash + ek->curve->fieldBytes, + extra->hash->hlen - ek->curve->fieldBytes); + put_datapl(h, data); + ssh_hash_final(h, hash); + mp_int *log_r_unreduced = mp_from_bytes_le( + make_ptrlen(hash, extra->hash->hlen)); + mp_int *log_r = mp_mod(log_r_unreduced, ek->curve->e.G_order); + mp_free(log_r_unreduced); + EdwardsPoint *r = ecc_edwards_multiply(ek->curve->e.G, log_r); + + /* + * Encode r now, because we'll need its encoding for the next + * hashing step as well as to write into the actual signature. + */ + strbuf *r_enc = strbuf_new(); + put_epoint(r_enc, r, ek->curve, true); /* omit string header */ + ecc_edwards_point_free(r); + + /* + * Compute the hash of (r || public key || message) just as + * eddsa_verify does. + */ + mp_int *H = eddsa_signing_exponent_from_data( + ek, extra, ptrlen_from_strbuf(r_enc), data); + + /* And then s = (log(r) + H*a) mod order(G). */ + mp_int *Ha = mp_modmul(H, a, ek->curve->e.G_order); + mp_int *s = mp_modadd(log_r, Ha, ek->curve->e.G_order); + mp_free(H); + mp_free(a); + mp_free(Ha); + mp_free(log_r); + + /* Format the output */ + put_stringz(bs, ek->sshk.vt->ssh_id); + put_uint32(bs, r_enc->len + ek->curve->fieldBytes); + put_data(bs, r_enc->u, r_enc->len); + strbuf_free(r_enc); + for (size_t i = 0; i < ek->curve->fieldBytes; ++i) + put_byte(bs, mp_get_byte(s, i)); + mp_free(s); +} + +static const struct ecsign_extra sign_extra_ed25519 = { + ec_ed25519, &ssh_sha512, + NULL, 0, PTRLEN_DECL_LITERAL(""), +}; +const ssh_keyalg ssh_ecdsa_ed25519 = { + .new_pub = eddsa_new_pub, + .new_priv = eddsa_new_priv, + .new_priv_openssh = eddsa_new_priv_openssh, + .freekey = eddsa_freekey, + .invalid = ec_signkey_invalid, + .sign = eddsa_sign, + .verify = eddsa_verify, + .public_blob = eddsa_public_blob, + .private_blob = eddsa_private_blob, + .openssh_blob = eddsa_openssh_blob, + .cache_str = eddsa_cache_str, + .components = eddsa_components, + .pubkey_bits = ec_shared_pubkey_bits, + .ssh_id = "ssh-ed25519", + .cache_id = "ssh-ed25519", + .extra = &sign_extra_ed25519, +}; + +static const struct ecsign_extra sign_extra_ed448 = { + ec_ed448, &ssh_shake256_114bytes, + NULL, 0, PTRLEN_DECL_LITERAL("SigEd448\0\0"), +}; +const ssh_keyalg ssh_ecdsa_ed448 = { + .new_pub = eddsa_new_pub, + .new_priv = eddsa_new_priv, + .new_priv_openssh = eddsa_new_priv_openssh, + .freekey = eddsa_freekey, + .invalid = ec_signkey_invalid, + .sign = eddsa_sign, + .verify = eddsa_verify, + .public_blob = eddsa_public_blob, + .private_blob = eddsa_private_blob, + .openssh_blob = eddsa_openssh_blob, + .cache_str = eddsa_cache_str, + .components = eddsa_components, + .pubkey_bits = ec_shared_pubkey_bits, + .ssh_id = "ssh-ed448", + .cache_id = "ssh-ed448", + .extra = &sign_extra_ed448, +}; + +/* OID: 1.2.840.10045.3.1.7 (ansiX9p256r1) */ +static const unsigned char nistp256_oid[] = { + 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07 +}; +static const struct ecsign_extra sign_extra_nistp256 = { + ec_p256, &ssh_sha256, + nistp256_oid, lenof(nistp256_oid), +}; +const ssh_keyalg ssh_ecdsa_nistp256 = { + .new_pub = ecdsa_new_pub, + .new_priv = ecdsa_new_priv, + .new_priv_openssh = ecdsa_new_priv_openssh, + .freekey = ecdsa_freekey, + .invalid = ec_signkey_invalid, + .sign = ecdsa_sign, + .verify = ecdsa_verify, + .public_blob = ecdsa_public_blob, + .private_blob = ecdsa_private_blob, + .openssh_blob = ecdsa_openssh_blob, + .cache_str = ecdsa_cache_str, + .components = ecdsa_components, + .pubkey_bits = ec_shared_pubkey_bits, + .ssh_id = "ecdsa-sha2-nistp256", + .cache_id = "ecdsa-sha2-nistp256", + .extra = &sign_extra_nistp256, +}; + +/* OID: 1.3.132.0.34 (secp384r1) */ +static const unsigned char nistp384_oid[] = { + 0x2b, 0x81, 0x04, 0x00, 0x22 +}; +static const struct ecsign_extra sign_extra_nistp384 = { + ec_p384, &ssh_sha384, + nistp384_oid, lenof(nistp384_oid), +}; +const ssh_keyalg ssh_ecdsa_nistp384 = { + .new_pub = ecdsa_new_pub, + .new_priv = ecdsa_new_priv, + .new_priv_openssh = ecdsa_new_priv_openssh, + .freekey = ecdsa_freekey, + .invalid = ec_signkey_invalid, + .sign = ecdsa_sign, + .verify = ecdsa_verify, + .public_blob = ecdsa_public_blob, + .private_blob = ecdsa_private_blob, + .openssh_blob = ecdsa_openssh_blob, + .cache_str = ecdsa_cache_str, + .components = ecdsa_components, + .pubkey_bits = ec_shared_pubkey_bits, + .ssh_id = "ecdsa-sha2-nistp384", + .cache_id = "ecdsa-sha2-nistp384", + .extra = &sign_extra_nistp384, +}; + +/* OID: 1.3.132.0.35 (secp521r1) */ +static const unsigned char nistp521_oid[] = { + 0x2b, 0x81, 0x04, 0x00, 0x23 +}; +static const struct ecsign_extra sign_extra_nistp521 = { + ec_p521, &ssh_sha512, + nistp521_oid, lenof(nistp521_oid), +}; +const ssh_keyalg ssh_ecdsa_nistp521 = { + .new_pub = ecdsa_new_pub, + .new_priv = ecdsa_new_priv, + .new_priv_openssh = ecdsa_new_priv_openssh, + .freekey = ecdsa_freekey, + .invalid = ec_signkey_invalid, + .sign = ecdsa_sign, + .verify = ecdsa_verify, + .public_blob = ecdsa_public_blob, + .private_blob = ecdsa_private_blob, + .openssh_blob = ecdsa_openssh_blob, + .cache_str = ecdsa_cache_str, + .components = ecdsa_components, + .pubkey_bits = ec_shared_pubkey_bits, + .ssh_id = "ecdsa-sha2-nistp521", + .cache_id = "ecdsa-sha2-nistp521", + .extra = &sign_extra_nistp521, +}; + +/* ---------------------------------------------------------------------- + * Exposed ECDH interface + */ + +struct eckex_extra { + struct ec_curve *(*curve)(void); + void (*setup)(ecdh_key *dh); + void (*cleanup)(ecdh_key *dh); + void (*getpublic)(ecdh_key *dh, BinarySink *bs); + mp_int *(*getkey)(ecdh_key *dh, ptrlen remoteKey); +}; + +struct ecdh_key { + const struct eckex_extra *extra; + const struct ec_curve *curve; + mp_int *private; + union { + WeierstrassPoint *w_public; + MontgomeryPoint *m_public; + }; +}; + +const char *ssh_ecdhkex_curve_textname(const ssh_kex *kex) +{ + const struct eckex_extra *extra = (const struct eckex_extra *)kex->extra; + struct ec_curve *curve = extra->curve(); + return curve->textname; +} + +static void ssh_ecdhkex_w_setup(ecdh_key *dh) +{ + mp_int *one = mp_from_integer(1); + dh->private = mp_random_in_range(one, dh->curve->w.G_order); + mp_free(one); + + dh->w_public = ecc_weierstrass_multiply(dh->curve->w.G, dh->private); +} + +static void ssh_ecdhkex_m_setup(ecdh_key *dh) +{ + strbuf *bytes = strbuf_new_nm(); + random_read(strbuf_append(bytes, dh->curve->fieldBytes), + dh->curve->fieldBytes); + + dh->private = mp_from_bytes_le(ptrlen_from_strbuf(bytes)); + + /* Ensure the private key has the highest valid bit set, and no + * bits _above_ the highest valid one */ + mp_reduce_mod_2to(dh->private, dh->curve->fieldBits); + mp_set_bit(dh->private, dh->curve->fieldBits - 1, 1); + + /* Clear a curve-specific number of low bits */ + for (unsigned bit = 0; bit < dh->curve->m.log2_cofactor; bit++) + mp_set_bit(dh->private, bit, 0); + + strbuf_free(bytes); + + dh->m_public = ecc_montgomery_multiply(dh->curve->m.G, dh->private); +} + +ecdh_key *ssh_ecdhkex_newkey(const ssh_kex *kex) +{ + const struct eckex_extra *extra = (const struct eckex_extra *)kex->extra; + const struct ec_curve *curve = extra->curve(); + + ecdh_key *dh = snew(ecdh_key); + dh->extra = extra; + dh->curve = curve; + dh->extra->setup(dh); + return dh; +} + +static void ssh_ecdhkex_w_getpublic(ecdh_key *dh, BinarySink *bs) +{ + put_wpoint(bs, dh->w_public, dh->curve, true); +} + +static void ssh_ecdhkex_m_getpublic(ecdh_key *dh, BinarySink *bs) +{ + mp_int *x; + ecc_montgomery_get_affine(dh->m_public, &x); + for (size_t i = 0; i < dh->curve->fieldBytes; ++i) + put_byte(bs, mp_get_byte(x, i)); + mp_free(x); +} + +void ssh_ecdhkex_getpublic(ecdh_key *dh, BinarySink *bs) +{ + dh->extra->getpublic(dh, bs); +} + +static mp_int *ssh_ecdhkex_w_getkey(ecdh_key *dh, ptrlen remoteKey) +{ + WeierstrassPoint *remote_p = ecdsa_decode(remoteKey, dh->curve); + if (!remote_p) + return NULL; + + if (ecc_weierstrass_is_identity(remote_p)) { + /* Not a sensible Diffie-Hellman input value */ + ecc_weierstrass_point_free(remote_p); + return NULL; + } + + WeierstrassPoint *p = ecc_weierstrass_multiply(remote_p, dh->private); + + mp_int *x; + ecc_weierstrass_get_affine(p, &x, NULL); + + ecc_weierstrass_point_free(remote_p); + ecc_weierstrass_point_free(p); + + return x; +} + +static mp_int *ssh_ecdhkex_m_getkey(ecdh_key *dh, ptrlen remoteKey) +{ + mp_int *remote_x = mp_from_bytes_le(remoteKey); + + /* Per RFC 7748 section 5, discard any set bits of the other + * side's public value beyond the minimum number of bits required + * to represent all valid values. However, an overlarge value that + * still fits into the remaining number of bits is accepted, and + * will be reduced mod p. */ + mp_reduce_mod_2to(remote_x, dh->curve->fieldBits); + + MontgomeryPoint *remote_p = ecc_montgomery_point_new( + dh->curve->m.mc, remote_x); + mp_free(remote_x); + + MontgomeryPoint *p = ecc_montgomery_multiply(remote_p, dh->private); + + if (ecc_montgomery_is_identity(p)) { + ecc_montgomery_point_free(remote_p); + ecc_montgomery_point_free(p); + return NULL; + } + + mp_int *x; + ecc_montgomery_get_affine(p, &x); + + ecc_montgomery_point_free(remote_p); + ecc_montgomery_point_free(p); + + /* + * Endianness-swap. The Curve25519 algorithm definition assumes + * you were doing your computation in arrays of 32 little-endian + * bytes, and now specifies that you take your final one of those + * and convert it into a bignum in _network_ byte order, i.e. + * big-endian. + * + * In particular, the spec says, you convert the _whole_ 32 bytes + * into a bignum. That is, on the rare occasions that x has come + * out with the most significant 8 bits zero, we have to imagine + * that being represented by a 32-byte string with the last byte + * being zero, so that has to be converted into an SSH-2 bignum + * with the _low_ byte zero, i.e. a multiple of 256. + */ + strbuf *sb = strbuf_new(); + for (size_t i = 0; i < dh->curve->fieldBytes; ++i) + put_byte(sb, mp_get_byte(x, i)); + mp_free(x); + x = mp_from_bytes_be(ptrlen_from_strbuf(sb)); + strbuf_free(sb); + + return x; +} + +mp_int *ssh_ecdhkex_getkey(ecdh_key *dh, ptrlen remoteKey) +{ + return dh->extra->getkey(dh, remoteKey); +} + +static void ssh_ecdhkex_w_cleanup(ecdh_key *dh) +{ + ecc_weierstrass_point_free(dh->w_public); +} + +static void ssh_ecdhkex_m_cleanup(ecdh_key *dh) +{ + ecc_montgomery_point_free(dh->m_public); +} + +void ssh_ecdhkex_freekey(ecdh_key *dh) +{ + mp_free(dh->private); + dh->extra->cleanup(dh); + sfree(dh); +} + +static const struct eckex_extra kex_extra_curve25519 = { + ec_curve25519, + ssh_ecdhkex_m_setup, + ssh_ecdhkex_m_cleanup, + ssh_ecdhkex_m_getpublic, + ssh_ecdhkex_m_getkey, +}; +const ssh_kex ssh_ec_kex_curve25519 = { + "curve25519-sha256", NULL, KEXTYPE_ECDH, + &ssh_sha256, &kex_extra_curve25519, +}; +/* Pre-RFC alias */ +const ssh_kex ssh_ec_kex_curve25519_libssh = { + "curve25519-sha256@libssh.org", NULL, KEXTYPE_ECDH, + &ssh_sha256, &kex_extra_curve25519, +}; + +static const struct eckex_extra kex_extra_curve448 = { + ec_curve448, + ssh_ecdhkex_m_setup, + ssh_ecdhkex_m_cleanup, + ssh_ecdhkex_m_getpublic, + ssh_ecdhkex_m_getkey, +}; +const ssh_kex ssh_ec_kex_curve448 = { + "curve448-sha512", NULL, KEXTYPE_ECDH, + &ssh_sha512, &kex_extra_curve448, +}; + +static const struct eckex_extra kex_extra_nistp256 = { + ec_p256, + ssh_ecdhkex_w_setup, + ssh_ecdhkex_w_cleanup, + ssh_ecdhkex_w_getpublic, + ssh_ecdhkex_w_getkey, +}; +const ssh_kex ssh_ec_kex_nistp256 = { + "ecdh-sha2-nistp256", NULL, KEXTYPE_ECDH, + &ssh_sha256, &kex_extra_nistp256, +}; + +static const struct eckex_extra kex_extra_nistp384 = { + ec_p384, + ssh_ecdhkex_w_setup, + ssh_ecdhkex_w_cleanup, + ssh_ecdhkex_w_getpublic, + ssh_ecdhkex_w_getkey, +}; +const ssh_kex ssh_ec_kex_nistp384 = { + "ecdh-sha2-nistp384", NULL, KEXTYPE_ECDH, + &ssh_sha384, &kex_extra_nistp384, +}; + +static const struct eckex_extra kex_extra_nistp521 = { + ec_p521, + ssh_ecdhkex_w_setup, + ssh_ecdhkex_w_cleanup, + ssh_ecdhkex_w_getpublic, + ssh_ecdhkex_w_getkey, +}; +const ssh_kex ssh_ec_kex_nistp521 = { + "ecdh-sha2-nistp521", NULL, KEXTYPE_ECDH, + &ssh_sha512, &kex_extra_nistp521, +}; + +static const ssh_kex *const ec_kex_list[] = { + &ssh_ec_kex_curve448, + &ssh_ec_kex_curve25519, + &ssh_ec_kex_curve25519_libssh, + &ssh_ec_kex_nistp256, + &ssh_ec_kex_nistp384, + &ssh_ec_kex_nistp521, +}; + +const ssh_kexes ssh_ecdh_kex = { lenof(ec_kex_list), ec_kex_list }; + +/* ---------------------------------------------------------------------- + * Helper functions for finding key algorithms and returning auxiliary + * data. + */ + +const ssh_keyalg *ec_alg_by_oid(int len, const void *oid, + const struct ec_curve **curve) +{ + static const ssh_keyalg *algs_with_oid[] = { + &ssh_ecdsa_nistp256, + &ssh_ecdsa_nistp384, + &ssh_ecdsa_nistp521, + }; + int i; + + for (i = 0; i < lenof(algs_with_oid); i++) { + const ssh_keyalg *alg = algs_with_oid[i]; + const struct ecsign_extra *extra = + (const struct ecsign_extra *)alg->extra; + if (len == extra->oidlen && !memcmp(oid, extra->oid, len)) { + *curve = extra->curve(); + return alg; + } + } + return NULL; +} + +const unsigned char *ec_alg_oid(const ssh_keyalg *alg, + int *oidlen) +{ + const struct ecsign_extra *extra = (const struct ecsign_extra *)alg->extra; + *oidlen = extra->oidlen; + return extra->oid; +} + +const int ec_nist_curve_lengths[] = { 256, 384, 521 }; +const int n_ec_nist_curve_lengths = lenof(ec_nist_curve_lengths); + +const int ec_ed_curve_lengths[] = { 255, 448 }; +const int n_ec_ed_curve_lengths = lenof(ec_ed_curve_lengths); + +bool ec_nist_alg_and_curve_by_bits( + int bits, const struct ec_curve **curve, const ssh_keyalg **alg) +{ + switch (bits) { + case 256: *alg = &ssh_ecdsa_nistp256; break; + case 384: *alg = &ssh_ecdsa_nistp384; break; + case 521: *alg = &ssh_ecdsa_nistp521; break; + default: return false; + } + *curve = ((struct ecsign_extra *)(*alg)->extra)->curve(); + return true; +} + +bool ec_ed_alg_and_curve_by_bits( + int bits, const struct ec_curve **curve, const ssh_keyalg **alg) +{ + switch (bits) { + case 255: case 256: *alg = &ssh_ecdsa_ed25519; break; + case 448: *alg = &ssh_ecdsa_ed448; break; + default: return false; + } + *curve = ((struct ecsign_extra *)(*alg)->extra)->curve(); + return true; +} diff --git a/crypto/hash_simple.c b/crypto/hash_simple.c new file mode 100644 index 00000000..0115b920 --- /dev/null +++ b/crypto/hash_simple.c @@ -0,0 +1,13 @@ +/* + * Convenience function to hash a single piece of data, wrapping up + * the faff of making and freeing an ssh_hash. + */ + +#include "ssh.h" + +void hash_simple(const ssh_hashalg *alg, ptrlen data, void *output) +{ + ssh_hash *hash = ssh_hash_new(alg); + put_datapl(hash, data); + ssh_hash_final(hash, output); +} diff --git a/crypto/hmac.c b/crypto/hmac.c new file mode 100644 index 00000000..8f18eb28 --- /dev/null +++ b/crypto/hmac.c @@ -0,0 +1,257 @@ +/* + * Implementation of HMAC (RFC 2104) for PuTTY, in a general form that + * can wrap any underlying hash function. + */ + +#include "ssh.h" + +struct hmac { + const ssh_hashalg *hashalg; + ssh_hash *h_outer, *h_inner, *h_live; + uint8_t *digest; + strbuf *text_name; + ssh2_mac mac; +}; + +struct hmac_extra { + const ssh_hashalg *hashalg_base; + const char *suffix, *annotation; +}; + +static ssh2_mac *hmac_new(const ssh2_macalg *alg, ssh_cipher *cipher) +{ + struct hmac *ctx = snew(struct hmac); + const struct hmac_extra *extra = (const struct hmac_extra *)alg->extra; + + ctx->h_outer = ssh_hash_new(extra->hashalg_base); + /* In case that hashalg was a selector vtable, we'll now switch to + * using whatever real one it selected, for all future purposes. */ + ctx->hashalg = ssh_hash_alg(ctx->h_outer); + ctx->h_inner = ssh_hash_new(ctx->hashalg); + ctx->h_live = ssh_hash_new(ctx->hashalg); + + /* + * HMAC is not well defined as a wrapper on an absolutely general + * hash function; it expects that the function it's wrapping will + * consume data in fixed-size blocks, and it's partially defined + * in terms of that block size. So we insist that the hash we're + * given must have defined a meaningful block size. + */ + assert(ctx->hashalg->blocklen); + + ctx->digest = snewn(ctx->hashalg->hlen, uint8_t); + + ctx->text_name = strbuf_new(); + strbuf_catf(ctx->text_name, "HMAC-%s%s", + ctx->hashalg->text_basename, extra->suffix); + if (extra->annotation || ctx->hashalg->annotation) { + strbuf_catf(ctx->text_name, " ("); + const char *sep = ""; + if (extra->annotation) { + strbuf_catf(ctx->text_name, "%s%s", sep, extra->annotation); + sep = ", "; + } + if (ctx->hashalg->annotation) { + strbuf_catf(ctx->text_name, "%s%s", sep, ctx->hashalg->annotation); + sep = ", "; + } + strbuf_catf(ctx->text_name, ")"); + } + + ctx->mac.vt = alg; + BinarySink_DELEGATE_INIT(&ctx->mac, ctx->h_live); + + return &ctx->mac; +} + +static void hmac_free(ssh2_mac *mac) +{ + struct hmac *ctx = container_of(mac, struct hmac, mac); + + ssh_hash_free(ctx->h_outer); + ssh_hash_free(ctx->h_inner); + ssh_hash_free(ctx->h_live); + smemclr(ctx->digest, ctx->hashalg->hlen); + sfree(ctx->digest); + strbuf_free(ctx->text_name); + + smemclr(ctx, sizeof(*ctx)); + sfree(ctx); +} + +#define PAD_OUTER 0x5C +#define PAD_INNER 0x36 + +static void hmac_key(ssh2_mac *mac, ptrlen key) +{ + struct hmac *ctx = container_of(mac, struct hmac, mac); + + const uint8_t *kp; + size_t klen; + strbuf *sb = NULL; + + if (key.len > ctx->hashalg->blocklen) { + /* + * RFC 2104 section 2: if the key exceeds the block length of + * the underlying hash, then we start by hashing the key, and + * use that hash as the 'true' key for the HMAC construction. + */ + sb = strbuf_new_nm(); + strbuf_append(sb, ctx->hashalg->hlen); + hash_simple(ctx->hashalg, key, sb->u); + kp = sb->u; + klen = sb->len; + } else { + /* + * A short enough key is used as is. + */ + kp = (const uint8_t *)key.ptr; + klen = key.len; + } + + ssh_hash_reset(ctx->h_outer); + for (size_t i = 0; i < klen; i++) + put_byte(ctx->h_outer, PAD_OUTER ^ kp[i]); + for (size_t i = klen; i < ctx->hashalg->blocklen; i++) + put_byte(ctx->h_outer, PAD_OUTER); + + ssh_hash_reset(ctx->h_inner); + for (size_t i = 0; i < klen; i++) + put_byte(ctx->h_inner, PAD_INNER ^ kp[i]); + for (size_t i = klen; i < ctx->hashalg->blocklen; i++) + put_byte(ctx->h_inner, PAD_INNER); + + if (sb) + strbuf_free(sb); +} + +static void hmac_start(ssh2_mac *mac) +{ + struct hmac *ctx = container_of(mac, struct hmac, mac); + ssh_hash_copyfrom(ctx->h_live, ctx->h_inner); +} + +static void hmac_genresult(ssh2_mac *mac, unsigned char *output) +{ + struct hmac *ctx = container_of(mac, struct hmac, mac); + ssh_hash *htmp; + + /* Leave h_live and h_outer in place, so that the SSH-2 BPP can + * continue regenerating test results from different-length + * prefixes of the packet */ + ssh_hash_digest_nondestructive(ctx->h_live, ctx->digest); + + htmp = ssh_hash_copy(ctx->h_outer); + put_data(htmp, ctx->digest, ctx->hashalg->hlen); + ssh_hash_final(htmp, ctx->digest); + + /* + * Some instances of HMAC truncate the output hash, so instead of + * writing it directly to 'output' we wrote it to our own + * full-length buffer, and now we copy the required amount. + */ + memcpy(output, ctx->digest, mac->vt->len); + smemclr(ctx->digest, ctx->hashalg->hlen); +} + +static const char *hmac_text_name(ssh2_mac *mac) +{ + struct hmac *ctx = container_of(mac, struct hmac, mac); + return ctx->text_name->s; +} + +static const struct hmac_extra ssh_hmac_sha256_extra = { &ssh_sha256, "" }; +const ssh2_macalg ssh_hmac_sha256 = { + .new = hmac_new, + .free = hmac_free, + .setkey = hmac_key, + .start = hmac_start, + .genresult = hmac_genresult, + .text_name = hmac_text_name, + .name = "hmac-sha2-256", + .etm_name = "hmac-sha2-256-etm@openssh.com", + .len = 32, + .keylen = 32, + .extra = &ssh_hmac_sha256_extra, +}; + +static const struct hmac_extra ssh_hmac_md5_extra = { &ssh_md5, "" }; +const ssh2_macalg ssh_hmac_md5 = { + .new = hmac_new, + .free = hmac_free, + .setkey = hmac_key, + .start = hmac_start, + .genresult = hmac_genresult, + .text_name = hmac_text_name, + .name = "hmac-md5", + .etm_name = "hmac-md5-etm@openssh.com", + .len = 16, + .keylen = 16, + .extra = &ssh_hmac_md5_extra, +}; + +static const struct hmac_extra ssh_hmac_sha1_extra = { &ssh_sha1, "" }; + +const ssh2_macalg ssh_hmac_sha1 = { + .new = hmac_new, + .free = hmac_free, + .setkey = hmac_key, + .start = hmac_start, + .genresult = hmac_genresult, + .text_name = hmac_text_name, + .name = "hmac-sha1", + .etm_name = "hmac-sha1-etm@openssh.com", + .len = 20, + .keylen = 20, + .extra = &ssh_hmac_sha1_extra, +}; + +static const struct hmac_extra ssh_hmac_sha1_96_extra = { &ssh_sha1, "-96" }; + +const ssh2_macalg ssh_hmac_sha1_96 = { + .new = hmac_new, + .free = hmac_free, + .setkey = hmac_key, + .start = hmac_start, + .genresult = hmac_genresult, + .text_name = hmac_text_name, + .name = "hmac-sha1-96", + .etm_name = "hmac-sha1-96-etm@openssh.com", + .len = 12, + .keylen = 20, + .extra = &ssh_hmac_sha1_96_extra, +}; + +static const struct hmac_extra ssh_hmac_sha1_buggy_extra = { + &ssh_sha1, "", "bug-compatible" +}; + +const ssh2_macalg ssh_hmac_sha1_buggy = { + .new = hmac_new, + .free = hmac_free, + .setkey = hmac_key, + .start = hmac_start, + .genresult = hmac_genresult, + .text_name = hmac_text_name, + .name = "hmac-sha1", + .len = 20, + .keylen = 16, + .extra = &ssh_hmac_sha1_buggy_extra, +}; + +static const struct hmac_extra ssh_hmac_sha1_96_buggy_extra = { + &ssh_sha1, "-96", "bug-compatible" +}; + +const ssh2_macalg ssh_hmac_sha1_96_buggy = { + .new = hmac_new, + .free = hmac_free, + .setkey = hmac_key, + .start = hmac_start, + .genresult = hmac_genresult, + .text_name = hmac_text_name, + .name = "hmac-sha1-96", + .len = 12, + .keylen = 16, + .extra = &ssh_hmac_sha1_96_buggy_extra, +}; diff --git a/crypto/mac.c b/crypto/mac.c new file mode 100644 index 00000000..c117d90b --- /dev/null +++ b/crypto/mac.c @@ -0,0 +1,43 @@ +/* + * Centralised parts of the SSH-2 MAC API, which don't need to vary + * with the MAC implementation. + */ + +#include + +#include "ssh.h" + +bool ssh2_mac_verresult(ssh2_mac *mac, const void *candidate) +{ + unsigned char correct[64]; /* at least as big as all known MACs */ + bool toret; + + assert(mac->vt->len <= sizeof(correct)); + ssh2_mac_genresult(mac, correct); + toret = smemeq(correct, candidate, mac->vt->len); + + smemclr(correct, sizeof(correct)); + + return toret; +} + +static void ssh2_mac_prepare(ssh2_mac *mac, const void *blk, int len, + unsigned long seq) +{ + ssh2_mac_start(mac); + put_uint32(mac, seq); + put_data(mac, blk, len); +} + +void ssh2_mac_generate(ssh2_mac *mac, void *blk, int len, unsigned long seq) +{ + ssh2_mac_prepare(mac, blk, len, seq); + ssh2_mac_genresult(mac, (unsigned char *)blk + len); +} + +bool ssh2_mac_verify( + ssh2_mac *mac, const void *blk, int len, unsigned long seq) +{ + ssh2_mac_prepare(mac, blk, len, seq); + return ssh2_mac_verresult(mac, (const unsigned char *)blk + len); +} diff --git a/crypto/mac_simple.c b/crypto/mac_simple.c new file mode 100644 index 00000000..c705fd88 --- /dev/null +++ b/crypto/mac_simple.c @@ -0,0 +1,16 @@ +/* + * Convenience function to MAC a single piece of data, wrapping up + * the faff of making and freeing an ssh_mac. + */ + +#include "ssh.h" + +void mac_simple(const ssh2_macalg *alg, ptrlen key, ptrlen data, void *output) +{ + ssh2_mac *mac = ssh2_mac_new(alg, NULL); + ssh2_mac_setkey(mac, key); + ssh2_mac_start(mac); + put_datapl(mac, data); + ssh2_mac_genresult(mac, output); + ssh2_mac_free(mac); +} diff --git a/crypto/md5.c b/crypto/md5.c new file mode 100644 index 00000000..9155c99e --- /dev/null +++ b/crypto/md5.c @@ -0,0 +1,245 @@ +/* + * MD5 implementation for PuTTY. Written directly from the spec by + * Simon Tatham. + */ + +#include +#include "ssh.h" + +static const uint32_t md5_initial_state[] = { + 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, +}; + +static const struct md5_round_constant { + uint32_t addition, rotation, msg_index; +} md5_round_constants[] = { + { 0xd76aa478, 7, 0 }, { 0xe8c7b756, 12, 1 }, + { 0x242070db, 17, 2 }, { 0xc1bdceee, 22, 3 }, + { 0xf57c0faf, 7, 4 }, { 0x4787c62a, 12, 5 }, + { 0xa8304613, 17, 6 }, { 0xfd469501, 22, 7 }, + { 0x698098d8, 7, 8 }, { 0x8b44f7af, 12, 9 }, + { 0xffff5bb1, 17, 10 }, { 0x895cd7be, 22, 11 }, + { 0x6b901122, 7, 12 }, { 0xfd987193, 12, 13 }, + { 0xa679438e, 17, 14 }, { 0x49b40821, 22, 15 }, + { 0xf61e2562, 5, 1 }, { 0xc040b340, 9, 6 }, + { 0x265e5a51, 14, 11 }, { 0xe9b6c7aa, 20, 0 }, + { 0xd62f105d, 5, 5 }, { 0x02441453, 9, 10 }, + { 0xd8a1e681, 14, 15 }, { 0xe7d3fbc8, 20, 4 }, + { 0x21e1cde6, 5, 9 }, { 0xc33707d6, 9, 14 }, + { 0xf4d50d87, 14, 3 }, { 0x455a14ed, 20, 8 }, + { 0xa9e3e905, 5, 13 }, { 0xfcefa3f8, 9, 2 }, + { 0x676f02d9, 14, 7 }, { 0x8d2a4c8a, 20, 12 }, + { 0xfffa3942, 4, 5 }, { 0x8771f681, 11, 8 }, + { 0x6d9d6122, 16, 11 }, { 0xfde5380c, 23, 14 }, + { 0xa4beea44, 4, 1 }, { 0x4bdecfa9, 11, 4 }, + { 0xf6bb4b60, 16, 7 }, { 0xbebfbc70, 23, 10 }, + { 0x289b7ec6, 4, 13 }, { 0xeaa127fa, 11, 0 }, + { 0xd4ef3085, 16, 3 }, { 0x04881d05, 23, 6 }, + { 0xd9d4d039, 4, 9 }, { 0xe6db99e5, 11, 12 }, + { 0x1fa27cf8, 16, 15 }, { 0xc4ac5665, 23, 2 }, + { 0xf4292244, 6, 0 }, { 0x432aff97, 10, 7 }, + { 0xab9423a7, 15, 14 }, { 0xfc93a039, 21, 5 }, + { 0x655b59c3, 6, 12 }, { 0x8f0ccc92, 10, 3 }, + { 0xffeff47d, 15, 10 }, { 0x85845dd1, 21, 1 }, + { 0x6fa87e4f, 6, 8 }, { 0xfe2ce6e0, 10, 15 }, + { 0xa3014314, 15, 6 }, { 0x4e0811a1, 21, 13 }, + { 0xf7537e82, 6, 4 }, { 0xbd3af235, 10, 11 }, + { 0x2ad7d2bb, 15, 2 }, { 0xeb86d391, 21, 9 }, +}; + +typedef struct md5_block md5_block; +struct md5_block { + uint8_t block[64]; + size_t used; + uint64_t len; +}; + +static inline void md5_block_setup(md5_block *blk) +{ + blk->used = 0; + blk->len = 0; +} + +static inline bool md5_block_write( + md5_block *blk, const void **vdata, size_t *len) +{ + size_t blkleft = sizeof(blk->block) - blk->used; + size_t chunk = *len < blkleft ? *len : blkleft; + + const uint8_t *p = *vdata; + memcpy(blk->block + blk->used, p, chunk); + *vdata = p + chunk; + *len -= chunk; + blk->used += chunk; + blk->len += chunk; + + if (blk->used == sizeof(blk->block)) { + blk->used = 0; + return true; + } + + return false; +} + +static inline void md5_block_pad(md5_block *blk, BinarySink *bs) +{ + uint64_t final_len = blk->len << 3; + size_t pad = 63 & (55 - blk->used); + + put_byte(bs, 0x80); + put_padding(bs, pad, 0); + + unsigned char buf[8]; + PUT_64BIT_LSB_FIRST(buf, final_len); + put_data(bs, buf, 8); + smemclr(buf, 8); + + assert(blk->used == 0 && "Should have exactly hit a block boundary"); +} + +static inline uint32_t rol(uint32_t x, unsigned y) +{ + return (x << (31 & y)) | (x >> (31 & -y)); +} + +static inline uint32_t Ch(uint32_t ctrl, uint32_t if1, uint32_t if0) +{ + return if0 ^ (ctrl & (if1 ^ if0)); +} + +/* Parameter functions for the four MD5 round types */ +static inline uint32_t F(uint32_t x, uint32_t y, uint32_t z) +{ return Ch(x, y, z); } +static inline uint32_t G(uint32_t x, uint32_t y, uint32_t z) +{ return Ch(z, x, y); } +static inline uint32_t H(uint32_t x, uint32_t y, uint32_t z) +{ return x ^ y ^ z; } +static inline uint32_t I(uint32_t x, uint32_t y, uint32_t z) +{ return y ^ (x | ~z); } + +static inline void md5_round( + unsigned round_index, const uint32_t *message, + uint32_t *a, uint32_t *b, uint32_t *c, uint32_t *d, + uint32_t (*f)(uint32_t, uint32_t, uint32_t)) +{ + struct md5_round_constant rc = md5_round_constants[round_index]; + + *a = *b + rol(*a + f(*b, *c, *d) + message[rc.msg_index] + rc.addition, + rc.rotation); +} + +static void md5_do_block(uint32_t *core, const uint8_t *block) +{ + uint32_t message_words[16]; + for (size_t i = 0; i < 16; i++) + message_words[i] = GET_32BIT_LSB_FIRST(block + 4*i); + + uint32_t a = core[0], b = core[1], c = core[2], d = core[3]; + + size_t t = 0; + for (size_t u = 0; u < 4; u++) { + md5_round(t++, message_words, &a, &b, &c, &d, F); + md5_round(t++, message_words, &d, &a, &b, &c, F); + md5_round(t++, message_words, &c, &d, &a, &b, F); + md5_round(t++, message_words, &b, &c, &d, &a, F); + } + for (size_t u = 0; u < 4; u++) { + md5_round(t++, message_words, &a, &b, &c, &d, G); + md5_round(t++, message_words, &d, &a, &b, &c, G); + md5_round(t++, message_words, &c, &d, &a, &b, G); + md5_round(t++, message_words, &b, &c, &d, &a, G); + } + for (size_t u = 0; u < 4; u++) { + md5_round(t++, message_words, &a, &b, &c, &d, H); + md5_round(t++, message_words, &d, &a, &b, &c, H); + md5_round(t++, message_words, &c, &d, &a, &b, H); + md5_round(t++, message_words, &b, &c, &d, &a, H); + } + for (size_t u = 0; u < 4; u++) { + md5_round(t++, message_words, &a, &b, &c, &d, I); + md5_round(t++, message_words, &d, &a, &b, &c, I); + md5_round(t++, message_words, &c, &d, &a, &b, I); + md5_round(t++, message_words, &b, &c, &d, &a, I); + } + + core[0] += a; + core[1] += b; + core[2] += c; + core[3] += d; + + smemclr(message_words, sizeof(message_words)); +} + +typedef struct md5 { + uint32_t core[4]; + md5_block blk; + BinarySink_IMPLEMENTATION; + ssh_hash hash; +} md5; + +static void md5_write(BinarySink *bs, const void *vp, size_t len); + +static ssh_hash *md5_new(const ssh_hashalg *alg) +{ + md5 *s = snew(md5); + + s->hash.vt = alg; + BinarySink_INIT(s, md5_write); + BinarySink_DELEGATE_INIT(&s->hash, s); + return &s->hash; +} + +static void md5_reset(ssh_hash *hash) +{ + md5 *s = container_of(hash, md5, hash); + + memcpy(s->core, md5_initial_state, sizeof(s->core)); + md5_block_setup(&s->blk); +} + +static void md5_copyfrom(ssh_hash *hcopy, ssh_hash *horig) +{ + md5 *copy = container_of(hcopy, md5, hash); + md5 *orig = container_of(horig, md5, hash); + + memcpy(copy, orig, sizeof(*copy)); + BinarySink_COPIED(copy); + BinarySink_DELEGATE_INIT(©->hash, copy); +} + +static void md5_free(ssh_hash *hash) +{ + md5 *s = container_of(hash, md5, hash); + + smemclr(s, sizeof(*s)); + sfree(s); +} + +static void md5_write(BinarySink *bs, const void *vp, size_t len) +{ + md5 *s = BinarySink_DOWNCAST(bs, md5); + + while (len > 0) + if (md5_block_write(&s->blk, &vp, &len)) + md5_do_block(s->core, s->blk.block); +} + +static void md5_digest(ssh_hash *hash, uint8_t *digest) +{ + md5 *s = container_of(hash, md5, hash); + + md5_block_pad(&s->blk, BinarySink_UPCAST(s)); + for (size_t i = 0; i < 4; i++) + PUT_32BIT_LSB_FIRST(digest + 4*i, s->core[i]); +} + +const ssh_hashalg ssh_md5 = { + .new = md5_new, + .reset = md5_reset, + .copyfrom = md5_copyfrom, + .digest = md5_digest, + .free = md5_free, + .hlen = 16, + .blocklen = 64, + HASHALG_NAMES_BARE("MD5"), +}; diff --git a/crypto/mpint.c b/crypto/mpint.c new file mode 100644 index 00000000..fca7530f --- /dev/null +++ b/crypto/mpint.c @@ -0,0 +1,2650 @@ +/* + * Multiprecision integer arithmetic, implementing mpint.h. + */ + +#include +#include +#include + +#include "defs.h" +#include "misc.h" +#include "puttymem.h" + +#include "mpint.h" +#include "mpint_i.h" + +#define SIZE_T_BITS (CHAR_BIT * sizeof(size_t)) + +/* + * Inline helpers to take min and max of size_t values, used + * throughout this code. + */ +static inline size_t size_t_min(size_t a, size_t b) +{ + return a < b ? a : b; +} +static inline size_t size_t_max(size_t a, size_t b) +{ + return a > b ? a : b; +} + +/* + * Helper to fetch a word of data from x with array overflow checking. + * If x is too short to have that word, 0 is returned. + */ +static inline BignumInt mp_word(mp_int *x, size_t i) +{ + return i < x->nw ? x->w[i] : 0; +} + +/* + * Shift an ordinary C integer by BIGNUM_INT_BITS, in a way that + * avoids writing a shift operator whose RHS is greater or equal to + * the size of the type, because that's undefined behaviour in C. + * + * In fact we must avoid even writing it in a definitely-untaken + * branch of an if, because compilers will sometimes warn about + * that. So you can't just write 'shift too big ? 0 : n >> shift', + * because even if 'shift too big' is a constant-expression + * evaluating to false, you can still get complaints about the + * else clause of the ?:. + * + * So we have to re-check _inside_ that clause, so that the shift + * count is reset to something nonsensical but safe in the case + * where the clause wasn't going to be taken anyway. + */ +static uintmax_t shift_right_by_one_word(uintmax_t n) +{ + bool shift_too_big = BIGNUM_INT_BYTES >= sizeof(n); + return shift_too_big ? 0 : + n >> (shift_too_big ? 0 : BIGNUM_INT_BITS); +} +static uintmax_t shift_left_by_one_word(uintmax_t n) +{ + bool shift_too_big = BIGNUM_INT_BYTES >= sizeof(n); + return shift_too_big ? 0 : + n << (shift_too_big ? 0 : BIGNUM_INT_BITS); +} + +mp_int *mp_make_sized(size_t nw) +{ + mp_int *x = snew_plus(mp_int, nw * sizeof(BignumInt)); + assert(nw); /* we outlaw the zero-word mp_int */ + x->nw = nw; + x->w = snew_plus_get_aux(x); + mp_clear(x); + return x; +} + +mp_int *mp_new(size_t maxbits) +{ + size_t words = (maxbits + BIGNUM_INT_BITS - 1) / BIGNUM_INT_BITS; + return mp_make_sized(words); +} + +mp_int *mp_from_integer(uintmax_t n) +{ + mp_int *x = mp_make_sized( + (sizeof(n) + BIGNUM_INT_BYTES - 1) / BIGNUM_INT_BYTES); + for (size_t i = 0; i < x->nw; i++) + x->w[i] = n >> (i * BIGNUM_INT_BITS); + return x; +} + +size_t mp_max_bytes(mp_int *x) +{ + return x->nw * BIGNUM_INT_BYTES; +} + +size_t mp_max_bits(mp_int *x) +{ + return x->nw * BIGNUM_INT_BITS; +} + +void mp_free(mp_int *x) +{ + mp_clear(x); + smemclr(x, sizeof(*x)); + sfree(x); +} + +void mp_dump(FILE *fp, const char *prefix, mp_int *x, const char *suffix) +{ + fprintf(fp, "%s0x", prefix); + for (size_t i = mp_max_bytes(x); i-- > 0 ;) + fprintf(fp, "%02X", mp_get_byte(x, i)); + fputs(suffix, fp); +} + +void mp_copy_into(mp_int *dest, mp_int *src) +{ + size_t copy_nw = size_t_min(dest->nw, src->nw); + memmove(dest->w, src->w, copy_nw * sizeof(BignumInt)); + smemclr(dest->w + copy_nw, (dest->nw - copy_nw) * sizeof(BignumInt)); +} + +void mp_copy_integer_into(mp_int *r, uintmax_t n) +{ + for (size_t i = 0; i < r->nw; i++) { + r->w[i] = n; + n = shift_right_by_one_word(n); + } +} + +/* + * Conditional selection is done by negating 'which', to give a mask + * word which is all 1s if which==1 and all 0s if which==0. Then you + * can select between two inputs a,b without data-dependent control + * flow by XORing them to get their difference; ANDing with the mask + * word to replace that difference with 0 if which==0; and XORing that + * into a, which will either turn it into b or leave it alone. + * + * This trick will be used throughout this code and taken as read the + * rest of the time (or else I'd be here all week typing comments), + * but I felt I ought to explain it in words _once_. + */ +void mp_select_into(mp_int *dest, mp_int *src0, mp_int *src1, + unsigned which) +{ + BignumInt mask = -(BignumInt)(1 & which); + for (size_t i = 0; i < dest->nw; i++) { + BignumInt srcword0 = mp_word(src0, i), srcword1 = mp_word(src1, i); + dest->w[i] = srcword0 ^ ((srcword1 ^ srcword0) & mask); + } +} + +void mp_cond_swap(mp_int *x0, mp_int *x1, unsigned swap) +{ + assert(x0->nw == x1->nw); + volatile BignumInt mask = -(BignumInt)(1 & swap); + for (size_t i = 0; i < x0->nw; i++) { + BignumInt diff = (x0->w[i] ^ x1->w[i]) & mask; + x0->w[i] ^= diff; + x1->w[i] ^= diff; + } +} + +void mp_clear(mp_int *x) +{ + smemclr(x->w, x->nw * sizeof(BignumInt)); +} + +void mp_cond_clear(mp_int *x, unsigned clear) +{ + BignumInt mask = ~-(BignumInt)(1 & clear); + for (size_t i = 0; i < x->nw; i++) + x->w[i] &= mask; +} + +/* + * Common code between mp_from_bytes_{le,be} which reads bytes in an + * arbitrary arithmetic progression. + */ +static mp_int *mp_from_bytes_int(ptrlen bytes, size_t m, size_t c) +{ + size_t nw = (bytes.len + BIGNUM_INT_BYTES - 1) / BIGNUM_INT_BYTES; + nw = size_t_max(nw, 1); + mp_int *n = mp_make_sized(nw); + for (size_t i = 0; i < bytes.len; i++) + n->w[i / BIGNUM_INT_BYTES] |= + (BignumInt)(((const unsigned char *)bytes.ptr)[m*i+c]) << + (8 * (i % BIGNUM_INT_BYTES)); + return n; +} + +mp_int *mp_from_bytes_le(ptrlen bytes) +{ + return mp_from_bytes_int(bytes, 1, 0); +} + +mp_int *mp_from_bytes_be(ptrlen bytes) +{ + return mp_from_bytes_int(bytes, -1, bytes.len - 1); +} + +static mp_int *mp_from_words(size_t nw, const BignumInt *w) +{ + mp_int *x = mp_make_sized(nw); + memcpy(x->w, w, x->nw * sizeof(BignumInt)); + return x; +} + +/* + * Decimal-to-binary conversion: just go through the input string + * adding on the decimal value of each digit, and then multiplying the + * number so far by 10. + */ +mp_int *mp_from_decimal_pl(ptrlen decimal) +{ + /* 196/59 is an upper bound (and also a continued-fraction + * convergent) for log2(10), so this conservatively estimates the + * number of bits that will be needed to store any number that can + * be written in this many decimal digits. */ + assert(decimal.len < (~(size_t)0) / 196); + size_t bits = 196 * decimal.len / 59; + + /* Now round that up to words. */ + size_t words = bits / BIGNUM_INT_BITS + 1; + + mp_int *x = mp_make_sized(words); + for (size_t i = 0; i < decimal.len; i++) { + mp_add_integer_into(x, x, ((const char *)decimal.ptr)[i] - '0'); + + if (i+1 == decimal.len) + break; + + mp_mul_integer_into(x, x, 10); + } + return x; +} + +mp_int *mp_from_decimal(const char *decimal) +{ + return mp_from_decimal_pl(ptrlen_from_asciz(decimal)); +} + +/* + * Hex-to-binary conversion: _algorithmically_ simpler than decimal + * (none of those multiplications by 10), but there's some fiddly + * bit-twiddling needed to process each hex digit without diverging + * control flow depending on whether it's a letter or a number. + */ +mp_int *mp_from_hex_pl(ptrlen hex) +{ + assert(hex.len <= (~(size_t)0) / 4); + size_t bits = hex.len * 4; + size_t words = (bits + BIGNUM_INT_BITS - 1) / BIGNUM_INT_BITS; + words = size_t_max(words, 1); + mp_int *x = mp_make_sized(words); + for (size_t nibble = 0; nibble < hex.len; nibble++) { + BignumInt digit = ((const char *)hex.ptr)[hex.len-1 - nibble]; + + BignumInt lmask = ~-((BignumInt)((digit-'a')|('f'-digit)) + >> (BIGNUM_INT_BITS-1)); + BignumInt umask = ~-((BignumInt)((digit-'A')|('F'-digit)) + >> (BIGNUM_INT_BITS-1)); + + BignumInt digitval = digit - '0'; + digitval ^= (digitval ^ (digit - 'a' + 10)) & lmask; + digitval ^= (digitval ^ (digit - 'A' + 10)) & umask; + digitval &= 0xF; /* at least be _slightly_ nice about weird input */ + + size_t word_idx = nibble / (BIGNUM_INT_BYTES*2); + size_t nibble_within_word = nibble % (BIGNUM_INT_BYTES*2); + x->w[word_idx] |= digitval << (nibble_within_word * 4); + } + return x; +} + +mp_int *mp_from_hex(const char *hex) +{ + return mp_from_hex_pl(ptrlen_from_asciz(hex)); +} + +mp_int *mp_copy(mp_int *x) +{ + return mp_from_words(x->nw, x->w); +} + +uint8_t mp_get_byte(mp_int *x, size_t byte) +{ + return 0xFF & (mp_word(x, byte / BIGNUM_INT_BYTES) >> + (8 * (byte % BIGNUM_INT_BYTES))); +} + +unsigned mp_get_bit(mp_int *x, size_t bit) +{ + return 1 & (mp_word(x, bit / BIGNUM_INT_BITS) >> + (bit % BIGNUM_INT_BITS)); +} + +uintmax_t mp_get_integer(mp_int *x) +{ + uintmax_t toret = 0; + for (size_t i = x->nw; i-- > 0 ;) + toret = shift_left_by_one_word(toret) | x->w[i]; + return toret; +} + +void mp_set_bit(mp_int *x, size_t bit, unsigned val) +{ + size_t word = bit / BIGNUM_INT_BITS; + assert(word < x->nw); + + unsigned shift = (bit % BIGNUM_INT_BITS); + + x->w[word] &= ~((BignumInt)1 << shift); + x->w[word] |= (BignumInt)(val & 1) << shift; +} + +/* + * Helper function used here and there to normalise any nonzero input + * value to 1. + */ +static inline unsigned normalise_to_1(BignumInt n) +{ + n = (n >> 1) | (n & 1); /* ensure top bit is clear */ + n = (BignumInt)(-n) >> (BIGNUM_INT_BITS - 1); /* normalise to 0 or 1 */ + return n; +} +static inline unsigned normalise_to_1_u64(uint64_t n) +{ + n = (n >> 1) | (n & 1); /* ensure top bit is clear */ + n = (-n) >> 63; /* normalise to 0 or 1 */ + return n; +} + +/* + * Find the highest nonzero word in a number. Returns the index of the + * word in x->w, and also a pair of output uint64_t in which that word + * appears in the high one shifted left by 'shift_wanted' bits, the + * words immediately below it occupy the space to the right, and the + * words below _that_ fill up the low one. + * + * If there is no nonzero word at all, the passed-by-reference output + * variables retain their original values. + */ +static inline void mp_find_highest_nonzero_word_pair( + mp_int *x, size_t shift_wanted, size_t *index, + uint64_t *hi, uint64_t *lo) +{ + uint64_t curr_hi = 0, curr_lo = 0; + + for (size_t curr_index = 0; curr_index < x->nw; curr_index++) { + BignumInt curr_word = x->w[curr_index]; + unsigned indicator = normalise_to_1(curr_word); + + curr_lo = (BIGNUM_INT_BITS < 64 ? (curr_lo >> BIGNUM_INT_BITS) : 0) | + (curr_hi << (64 - BIGNUM_INT_BITS)); + curr_hi = (BIGNUM_INT_BITS < 64 ? (curr_hi >> BIGNUM_INT_BITS) : 0) | + ((uint64_t)curr_word << shift_wanted); + + if (hi) *hi ^= (curr_hi ^ *hi ) & -(uint64_t)indicator; + if (lo) *lo ^= (curr_lo ^ *lo ) & -(uint64_t)indicator; + if (index) *index ^= (curr_index ^ *index) & -(size_t) indicator; + } +} + +size_t mp_get_nbits(mp_int *x) +{ + /* Sentinel values in case there are no bits set at all: we + * imagine that there's a word at position -1 (i.e. the topmost + * fraction word) which is all 1s, because that way, we handle a + * zero input by considering its highest set bit to be the top one + * of that word, i.e. just below the units digit, i.e. at bit + * index -1, i.e. so we'll return 0 on output. */ + size_t hiword_index = -(size_t)1; + uint64_t hiword64 = ~(BignumInt)0; + + /* + * Find the highest nonzero word and its index. + */ + mp_find_highest_nonzero_word_pair(x, 0, &hiword_index, &hiword64, NULL); + BignumInt hiword = hiword64; /* in case BignumInt is a narrower type */ + + /* + * Find the index of the highest set bit within hiword. + */ + BignumInt hibit_index = 0; + for (size_t i = (1 << (BIGNUM_INT_BITS_BITS-1)); i != 0; i >>= 1) { + BignumInt shifted_word = hiword >> i; + BignumInt indicator = + (BignumInt)(-shifted_word) >> (BIGNUM_INT_BITS-1); + hiword ^= (shifted_word ^ hiword ) & -indicator; + hibit_index += i & -(size_t)indicator; + } + + /* + * Put together the result. + */ + return (hiword_index << BIGNUM_INT_BITS_BITS) + hibit_index + 1; +} + +/* + * Shared code between the hex and decimal output functions to get rid + * of leading zeroes on the output string. The idea is that we wrote + * out a fixed number of digits and a trailing \0 byte into 'buf', and + * now we want to shift it all left so that the first nonzero digit + * moves to buf[0] (or, if there are no nonzero digits at all, we move + * up by 'maxtrim', so that we return 0 as "0" instead of ""). + */ +static void trim_leading_zeroes(char *buf, size_t bufsize, size_t maxtrim) +{ + size_t trim = maxtrim; + + /* + * Look for the first character not equal to '0', to find the + * shift count. + */ + if (trim > 0) { + for (size_t pos = trim; pos-- > 0 ;) { + uint8_t diff = buf[pos] ^ '0'; + size_t mask = -((((size_t)diff) - 1) >> (SIZE_T_BITS - 1)); + trim ^= (trim ^ pos) & ~mask; + } + } + + /* + * Now do the shift, in log n passes each of which does a + * conditional shift by 2^i bytes if bit i is set in the shift + * count. + */ + uint8_t *ubuf = (uint8_t *)buf; + for (size_t logd = 0; bufsize >> logd; logd++) { + uint8_t mask = -(uint8_t)((trim >> logd) & 1); + size_t d = (size_t)1 << logd; + for (size_t i = 0; i+d < bufsize; i++) { + uint8_t diff = mask & (ubuf[i] ^ ubuf[i+d]); + ubuf[i] ^= diff; + ubuf[i+d] ^= diff; + } + } +} + +/* + * Binary to decimal conversion. Our strategy here is to extract each + * decimal digit by finding the input number's residue mod 10, then + * subtract that off to give an exact multiple of 10, which then means + * you can safely divide by 10 by means of shifting right one bit and + * then multiplying by the inverse of 5 mod 2^n. + */ +char *mp_get_decimal(mp_int *x_orig) +{ + mp_int *x = mp_copy(x_orig), *y = mp_make_sized(x->nw); + + /* + * The inverse of 5 mod 2^lots is 0xccccccccccccccccccccd, for an + * appropriate number of 'c's. Manually construct an integer the + * right size. + */ + mp_int *inv5 = mp_make_sized(x->nw); + assert(BIGNUM_INT_BITS % 8 == 0); + for (size_t i = 0; i < inv5->nw; i++) + inv5->w[i] = BIGNUM_INT_MASK / 5 * 4; + inv5->w[0]++; + + /* + * 146/485 is an upper bound (and also a continued-fraction + * convergent) of log10(2), so this is a conservative estimate of + * the number of decimal digits needed to store a value that fits + * in this many binary bits. + */ + assert(x->nw < (~(size_t)1) / (146 * BIGNUM_INT_BITS)); + size_t bufsize = size_t_max(x->nw * (146 * BIGNUM_INT_BITS) / 485, 1) + 2; + char *outbuf = snewn(bufsize, char); + outbuf[bufsize - 1] = '\0'; + + /* + * Loop over the number generating digits from the least + * significant upwards, so that we write to outbuf in reverse + * order. + */ + for (size_t pos = bufsize - 1; pos-- > 0 ;) { + /* + * Find the current residue mod 10. We do this by first + * summing the bytes of the number, with all but the lowest + * one multiplied by 6 (because 256^i == 6 mod 10 for all + * i>0). That gives us a single word congruent mod 10 to the + * input number, and then we reduce it further by manual + * multiplication and shifting, just in case the compiler + * target implements the C division operator in a way that has + * input-dependent timing. + */ + uint32_t low_digit = 0, maxval = 0, mult = 1; + for (size_t i = 0; i < x->nw; i++) { + for (unsigned j = 0; j < BIGNUM_INT_BYTES; j++) { + low_digit += mult * (0xFF & (x->w[i] >> (8*j))); + maxval += mult * 0xFF; + mult = 6; + } + /* + * For _really_ big numbers, prevent overflow of t by + * periodically folding the top half of the accumulator + * into the bottom half, using the same rule 'multiply by + * 6 when shifting down by one or more whole bytes'. + */ + if (maxval > UINT32_MAX - (6 * 0xFF * BIGNUM_INT_BYTES)) { + low_digit = (low_digit & 0xFFFF) + 6 * (low_digit >> 16); + maxval = (maxval & 0xFFFF) + 6 * (maxval >> 16); + } + } + + /* + * Final reduction of low_digit. We multiply by 2^32 / 10 + * (that's the constant 0x19999999) to get a 64-bit value + * whose top 32 bits are the approximate quotient + * low_digit/10; then we subtract off 10 times that; and + * finally we do one last trial subtraction of 10 by adding 6 + * (which sets bit 4 if the number was just over 10) and then + * testing bit 4. + */ + low_digit -= 10 * ((0x19999999ULL * low_digit) >> 32); + low_digit -= 10 * ((low_digit + 6) >> 4); + + assert(low_digit < 10); /* make sure we did reduce fully */ + outbuf[pos] = '0' + low_digit; + + /* + * Now subtract off that digit, divide by 2 (using a right + * shift) and by 5 (using the modular inverse), to get the + * next output digit into the units position. + */ + mp_sub_integer_into(x, x, low_digit); + mp_rshift_fixed_into(y, x, 1); + mp_mul_into(x, y, inv5); + } + + mp_free(x); + mp_free(y); + mp_free(inv5); + + trim_leading_zeroes(outbuf, bufsize, bufsize - 2); + return outbuf; +} + +/* + * Binary to hex conversion. Reasonably simple (only a spot of bit + * twiddling to choose whether to output a digit or a letter for each + * nibble). + */ +static char *mp_get_hex_internal(mp_int *x, uint8_t letter_offset) +{ + size_t nibbles = x->nw * BIGNUM_INT_BYTES * 2; + size_t bufsize = nibbles + 1; + char *outbuf = snewn(bufsize, char); + outbuf[nibbles] = '\0'; + + for (size_t nibble = 0; nibble < nibbles; nibble++) { + size_t word_idx = nibble / (BIGNUM_INT_BYTES*2); + size_t nibble_within_word = nibble % (BIGNUM_INT_BYTES*2); + uint8_t digitval = 0xF & (x->w[word_idx] >> (nibble_within_word * 4)); + + uint8_t mask = -((digitval + 6) >> 4); + char digit = digitval + '0' + (letter_offset & mask); + outbuf[nibbles-1 - nibble] = digit; + } + + trim_leading_zeroes(outbuf, bufsize, nibbles - 1); + return outbuf; +} + +char *mp_get_hex(mp_int *x) +{ + return mp_get_hex_internal(x, 'a' - ('0'+10)); +} + +char *mp_get_hex_uppercase(mp_int *x) +{ + return mp_get_hex_internal(x, 'A' - ('0'+10)); +} + +/* + * Routines for reading and writing the SSH-1 and SSH-2 wire formats + * for multiprecision integers, declared in marshal.h. + * + * These can't avoid having control flow dependent on the true bit + * size of the number, because the wire format requires the number of + * output bytes to depend on that. + */ +void BinarySink_put_mp_ssh1(BinarySink *bs, mp_int *x) +{ + size_t bits = mp_get_nbits(x); + size_t bytes = (bits + 7) / 8; + + assert(bits < 0x10000); + put_uint16(bs, bits); + for (size_t i = bytes; i-- > 0 ;) + put_byte(bs, mp_get_byte(x, i)); +} + +void BinarySink_put_mp_ssh2(BinarySink *bs, mp_int *x) +{ + size_t bytes = (mp_get_nbits(x) + 8) / 8; + + put_uint32(bs, bytes); + for (size_t i = bytes; i-- > 0 ;) + put_byte(bs, mp_get_byte(x, i)); +} + +mp_int *BinarySource_get_mp_ssh1(BinarySource *src) +{ + unsigned bitc = get_uint16(src); + ptrlen bytes = get_data(src, (bitc + 7) / 8); + if (get_err(src)) { + return mp_from_integer(0); + } else { + mp_int *toret = mp_from_bytes_be(bytes); + /* SSH-1.5 spec says that it's OK for the prefix uint16 to be + * _greater_ than the actual number of bits */ + if (mp_get_nbits(toret) > bitc) { + src->err = BSE_INVALID; + mp_free(toret); + toret = mp_from_integer(0); + } + return toret; + } +} + +mp_int *BinarySource_get_mp_ssh2(BinarySource *src) +{ + ptrlen bytes = get_string(src); + if (get_err(src)) { + return mp_from_integer(0); + } else { + const unsigned char *p = bytes.ptr; + if ((bytes.len > 0 && + ((p[0] & 0x80) || + (p[0] == 0 && (bytes.len <= 1 || !(p[1] & 0x80)))))) { + src->err = BSE_INVALID; + return mp_from_integer(0); + } + return mp_from_bytes_be(bytes); + } +} + +/* + * Make an mp_int structure whose words array aliases a subinterval of + * some other mp_int. This makes it easy to read or write just the low + * or high words of a number, e.g. to add a number starting from a + * high bit position, or to reduce mod 2^{n*BIGNUM_INT_BITS}. + * + * The convention throughout this code is that when we store an mp_int + * directly by value, we always expect it to be an alias of some kind, + * so its words array won't ever need freeing. Whereas an 'mp_int *' + * has an owner, who knows whether it needs freeing or whether it was + * created by address-taking an alias. + */ +static mp_int mp_make_alias(mp_int *in, size_t offset, size_t len) +{ + /* + * Bounds-check the offset and length so that we always return + * something valid, even if it's not necessarily the length the + * caller asked for. + */ + if (offset > in->nw) + offset = in->nw; + if (len > in->nw - offset) + len = in->nw - offset; + + mp_int toret; + toret.nw = len; + toret.w = in->w + offset; + return toret; +} + +/* + * A special case of mp_make_alias: in some cases we preallocate a + * large mp_int to use as scratch space (to avoid pointless + * malloc/free churn in recursive or iterative work). + * + * mp_alloc_from_scratch creates an alias of size 'len' to part of + * 'pool', and adjusts 'pool' itself so that further allocations won't + * overwrite that space. + * + * There's no free function to go with this. Typically you just copy + * the pool mp_int by value, allocate from the copy, and when you're + * done with those allocations, throw the copy away and go back to the + * original value of pool. (A mark/release system.) + */ +static mp_int mp_alloc_from_scratch(mp_int *pool, size_t len) +{ + assert(len <= pool->nw); + mp_int toret = mp_make_alias(pool, 0, len); + *pool = mp_make_alias(pool, len, pool->nw); + return toret; +} + +/* + * Internal component common to lots of assorted add/subtract code. + * Reads words from a,b; writes into w_out (which might be NULL if the + * output isn't even needed). Takes an input carry flag in 'carry', + * and returns the output carry. Each word read from b is ANDed with + * b_and and then XORed with b_xor. + * + * So you can implement addition by setting b_and to all 1s and b_xor + * to 0; you can subtract by making b_xor all 1s too (effectively + * bit-flipping b) and also passing 1 as the input carry (to turn + * one's complement into two's complement). And you can do conditional + * add/subtract by choosing b_and to be all 1s or all 0s based on a + * condition, because the value of b will be totally ignored if b_and + * == 0. + */ +static BignumCarry mp_add_masked_into( + BignumInt *w_out, size_t rw, mp_int *a, mp_int *b, + BignumInt b_and, BignumInt b_xor, BignumCarry carry) +{ + for (size_t i = 0; i < rw; i++) { + BignumInt aword = mp_word(a, i), bword = mp_word(b, i), out; + bword = (bword & b_and) ^ b_xor; + BignumADC(out, carry, aword, bword, carry); + if (w_out) + w_out[i] = out; + } + return carry; +} + +/* + * Like the public mp_add_into except that it returns the output carry. + */ +static inline BignumCarry mp_add_into_internal(mp_int *r, mp_int *a, mp_int *b) +{ + return mp_add_masked_into(r->w, r->nw, a, b, ~(BignumInt)0, 0, 0); +} + +void mp_add_into(mp_int *r, mp_int *a, mp_int *b) +{ + mp_add_into_internal(r, a, b); +} + +void mp_sub_into(mp_int *r, mp_int *a, mp_int *b) +{ + mp_add_masked_into(r->w, r->nw, a, b, ~(BignumInt)0, ~(BignumInt)0, 1); +} + +void mp_and_into(mp_int *r, mp_int *a, mp_int *b) +{ + for (size_t i = 0; i < r->nw; i++) { + BignumInt aword = mp_word(a, i), bword = mp_word(b, i); + r->w[i] = aword & bword; + } +} + +void mp_or_into(mp_int *r, mp_int *a, mp_int *b) +{ + for (size_t i = 0; i < r->nw; i++) { + BignumInt aword = mp_word(a, i), bword = mp_word(b, i); + r->w[i] = aword | bword; + } +} + +void mp_xor_into(mp_int *r, mp_int *a, mp_int *b) +{ + for (size_t i = 0; i < r->nw; i++) { + BignumInt aword = mp_word(a, i), bword = mp_word(b, i); + r->w[i] = aword ^ bword; + } +} + +void mp_bic_into(mp_int *r, mp_int *a, mp_int *b) +{ + for (size_t i = 0; i < r->nw; i++) { + BignumInt aword = mp_word(a, i), bword = mp_word(b, i); + r->w[i] = aword & ~bword; + } +} + +static void mp_cond_negate(mp_int *r, mp_int *x, unsigned yes) +{ + BignumCarry carry = yes; + BignumInt flip = -(BignumInt)yes; + for (size_t i = 0; i < r->nw; i++) { + BignumInt xword = mp_word(x, i); + xword ^= flip; + BignumADC(r->w[i], carry, 0, xword, carry); + } +} + +/* + * Similar to mp_add_masked_into, but takes a C integer instead of an + * mp_int as the masked operand. + */ +static BignumCarry mp_add_masked_integer_into( + BignumInt *w_out, size_t rw, mp_int *a, uintmax_t b, + BignumInt b_and, BignumInt b_xor, BignumCarry carry) +{ + for (size_t i = 0; i < rw; i++) { + BignumInt aword = mp_word(a, i); + BignumInt bword = b; + b = shift_right_by_one_word(b); + BignumInt out; + bword = (bword ^ b_xor) & b_and; + BignumADC(out, carry, aword, bword, carry); + if (w_out) + w_out[i] = out; + } + return carry; +} + +void mp_add_integer_into(mp_int *r, mp_int *a, uintmax_t n) +{ + mp_add_masked_integer_into(r->w, r->nw, a, n, ~(BignumInt)0, 0, 0); +} + +void mp_sub_integer_into(mp_int *r, mp_int *a, uintmax_t n) +{ + mp_add_masked_integer_into(r->w, r->nw, a, n, + ~(BignumInt)0, ~(BignumInt)0, 1); +} + +/* + * Sets r to a + n << (word_index * BIGNUM_INT_BITS), treating + * word_index as secret data. + */ +static void mp_add_integer_into_shifted_by_words( + mp_int *r, mp_int *a, uintmax_t n, size_t word_index) +{ + unsigned indicator = 0; + BignumCarry carry = 0; + + for (size_t i = 0; i < r->nw; i++) { + /* indicator becomes 1 when we reach the index that the least + * significant bits of n want to be placed at, and it stays 1 + * thereafter. */ + indicator |= 1 ^ normalise_to_1(i ^ word_index); + + /* If indicator is 1, we add the low bits of n into r, and + * shift n down. If it's 0, we add zero bits into r, and + * leave n alone. */ + BignumInt bword = n & -(BignumInt)indicator; + uintmax_t new_n = shift_right_by_one_word(n); + n ^= (n ^ new_n) & -(uintmax_t)indicator; + + BignumInt aword = mp_word(a, i); + BignumInt out; + BignumADC(out, carry, aword, bword, carry); + r->w[i] = out; + } +} + +void mp_mul_integer_into(mp_int *r, mp_int *a, uint16_t n) +{ + BignumInt carry = 0, mult = n; + for (size_t i = 0; i < r->nw; i++) { + BignumInt aword = mp_word(a, i); + BignumMULADD(carry, r->w[i], aword, mult, carry); + } + assert(!carry); +} + +void mp_cond_add_into(mp_int *r, mp_int *a, mp_int *b, unsigned yes) +{ + BignumInt mask = -(BignumInt)(yes & 1); + mp_add_masked_into(r->w, r->nw, a, b, mask, 0, 0); +} + +void mp_cond_sub_into(mp_int *r, mp_int *a, mp_int *b, unsigned yes) +{ + BignumInt mask = -(BignumInt)(yes & 1); + mp_add_masked_into(r->w, r->nw, a, b, mask, mask, 1 & mask); +} + +/* + * Ordered comparison between unsigned numbers is done by subtracting + * one from the other and looking at the output carry. + */ +unsigned mp_cmp_hs(mp_int *a, mp_int *b) +{ + size_t rw = size_t_max(a->nw, b->nw); + return mp_add_masked_into(NULL, rw, a, b, ~(BignumInt)0, ~(BignumInt)0, 1); +} + +unsigned mp_hs_integer(mp_int *x, uintmax_t n) +{ + BignumInt carry = 1; + size_t nwords = sizeof(n)/BIGNUM_INT_BYTES; + for (size_t i = 0, e = size_t_max(x->nw, nwords); i < e; i++) { + BignumInt nword = n; + n = shift_right_by_one_word(n); + BignumInt dummy_out; + BignumADC(dummy_out, carry, mp_word(x, i), ~nword, carry); + (void)dummy_out; + } + return carry; +} + +/* + * Equality comparison is done by bitwise XOR of the input numbers, + * ORing together all the output words, and normalising the result + * using our careful normalise_to_1 helper function. + */ +unsigned mp_cmp_eq(mp_int *a, mp_int *b) +{ + BignumInt diff = 0; + for (size_t i = 0, limit = size_t_max(a->nw, b->nw); i < limit; i++) + diff |= mp_word(a, i) ^ mp_word(b, i); + return 1 ^ normalise_to_1(diff); /* return 1 if diff _is_ zero */ +} + +unsigned mp_eq_integer(mp_int *x, uintmax_t n) +{ + BignumInt diff = 0; + size_t nwords = sizeof(n)/BIGNUM_INT_BYTES; + for (size_t i = 0, e = size_t_max(x->nw, nwords); i < e; i++) { + BignumInt nword = n; + n = shift_right_by_one_word(n); + diff |= mp_word(x, i) ^ nword; + } + return 1 ^ normalise_to_1(diff); /* return 1 if diff _is_ zero */ +} + +static void mp_neg_into(mp_int *r, mp_int *a) +{ + mp_int zero; + zero.nw = 0; + mp_sub_into(r, &zero, a); +} + +mp_int *mp_add(mp_int *x, mp_int *y) +{ + mp_int *r = mp_make_sized(size_t_max(x->nw, y->nw) + 1); + mp_add_into(r, x, y); + return r; +} + +mp_int *mp_sub(mp_int *x, mp_int *y) +{ + mp_int *r = mp_make_sized(size_t_max(x->nw, y->nw)); + mp_sub_into(r, x, y); + return r; +} + +/* + * Internal routine: multiply and accumulate in the trivial O(N^2) + * way. Sets r <- r + a*b. + */ +static void mp_mul_add_simple(mp_int *r, mp_int *a, mp_int *b) +{ + BignumInt *aend = a->w + a->nw, *bend = b->w + b->nw, *rend = r->w + r->nw; + + for (BignumInt *ap = a->w, *rp = r->w; + ap < aend && rp < rend; ap++, rp++) { + + BignumInt adata = *ap, carry = 0, *rq = rp; + + for (BignumInt *bp = b->w; bp < bend && rq < rend; bp++, rq++) { + BignumInt bdata = bp < bend ? *bp : 0; + BignumMULADD2(carry, *rq, adata, bdata, *rq, carry); + } + + for (; rq < rend; rq++) + BignumADC(*rq, carry, carry, *rq, 0); + } +} + +#ifndef KARATSUBA_THRESHOLD /* allow redefinition via -D for testing */ +#define KARATSUBA_THRESHOLD 24 +#endif + +static inline size_t mp_mul_scratchspace_unary(size_t n) +{ + /* + * Simplistic and overcautious bound on the amount of scratch + * space that the recursive multiply function will need. + * + * The rationale is: on the main Karatsuba branch of + * mp_mul_internal, which is the most space-intensive one, we + * allocate space for (a0+a1) and (b0+b1) (each just over half the + * input length n) and their product (the sum of those sizes, i.e. + * just over n itself). Then in order to actually compute the + * product, we do a recursive multiplication of size just over n. + * + * If all those 'just over' weren't there, and everything was + * _exactly_ half the length, you'd get the amount of space for a + * size-n multiply defined by the recurrence M(n) = 2n + M(n/2), + * which is satisfied by M(n) = 4n. But instead it's (2n plus a + * word or two) and M(n/2 plus a word or two). On the assumption + * that there's still some constant k such that M(n) <= kn, this + * gives us kn = 2n + w + k(n/2 + w), where w is a small constant + * (one or two words). That simplifies to kn/2 = 2n + (k+1)w, and + * since we don't even _start_ needing scratch space until n is at + * least 50, we can bound 2n + (k+1)w above by 3n, giving k=6. + * + * So I claim that 6n words of scratch space will suffice, and I + * check that by assertion at every stage of the recursion. + */ + return n * 6; +} + +static size_t mp_mul_scratchspace(size_t rw, size_t aw, size_t bw) +{ + size_t inlen = size_t_min(rw, size_t_max(aw, bw)); + return mp_mul_scratchspace_unary(inlen); +} + +static void mp_mul_internal(mp_int *r, mp_int *a, mp_int *b, mp_int scratch) +{ + size_t inlen = size_t_min(r->nw, size_t_max(a->nw, b->nw)); + assert(scratch.nw >= mp_mul_scratchspace_unary(inlen)); + + mp_clear(r); + + if (inlen < KARATSUBA_THRESHOLD || a->nw == 0 || b->nw == 0) { + /* + * The input numbers are too small to bother optimising. Go + * straight to the simple primitive approach. + */ + mp_mul_add_simple(r, a, b); + return; + } + + /* + * Karatsuba divide-and-conquer algorithm. We 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. + */ + + /* Break up the input as botlen + toplen, with botlen >= toplen. + * The 'base' D is equal to 2^{botlen * BIGNUM_INT_BITS}. */ + size_t toplen = inlen / 2; + size_t botlen = inlen - toplen; + + /* Alias bignums that address the two halves of a,b, and useful + * pieces of r. */ + mp_int a0 = mp_make_alias(a, 0, botlen); + mp_int b0 = mp_make_alias(b, 0, botlen); + mp_int a1 = mp_make_alias(a, botlen, toplen); + mp_int b1 = mp_make_alias(b, botlen, toplen); + mp_int r0 = mp_make_alias(r, 0, botlen*2); + mp_int r1 = mp_make_alias(r, botlen, r->nw); + mp_int r2 = mp_make_alias(r, botlen*2, r->nw); + + /* Recurse to compute a0*b0 and a1*b1, in their correct positions + * in the output bignum. They can't overlap. */ + mp_mul_internal(&r0, &a0, &b0, scratch); + mp_mul_internal(&r2, &a1, &b1, scratch); + + if (r->nw < inlen*2) { + /* + * The output buffer isn't large enough to require the whole + * product, so some of a1*b1 won't have been stored. In that + * case we won't try to do the full Karatsuba optimisation; + * we'll just recurse again to compute a0*b1 and a1*b0 - or at + * least as much of them as the output buffer size requires - + * and add each one in. + */ + mp_int s = mp_alloc_from_scratch( + &scratch, size_t_min(botlen+toplen, r1.nw)); + + mp_mul_internal(&s, &a0, &b1, scratch); + mp_add_into(&r1, &r1, &s); + mp_mul_internal(&s, &a1, &b0, scratch); + mp_add_into(&r1, &r1, &s); + return; + } + + /* a0+a1 and b0+b1 */ + mp_int asum = mp_alloc_from_scratch(&scratch, botlen+1); + mp_int bsum = mp_alloc_from_scratch(&scratch, botlen+1); + mp_add_into(&asum, &a0, &a1); + mp_add_into(&bsum, &b0, &b1); + + /* Their product */ + mp_int product = mp_alloc_from_scratch(&scratch, botlen*2+1); + mp_mul_internal(&product, &asum, &bsum, scratch); + + /* Subtract off the outer terms we already have */ + mp_sub_into(&product, &product, &r0); + mp_sub_into(&product, &product, &r2); + + /* And add it in with the right offset. */ + mp_add_into(&r1, &r1, &product); +} + +void mp_mul_into(mp_int *r, mp_int *a, mp_int *b) +{ + mp_int *scratch = mp_make_sized(mp_mul_scratchspace(r->nw, a->nw, b->nw)); + mp_mul_internal(r, a, b, *scratch); + mp_free(scratch); +} + +mp_int *mp_mul(mp_int *x, mp_int *y) +{ + mp_int *r = mp_make_sized(x->nw + y->nw); + mp_mul_into(r, x, y); + return r; +} + +void mp_lshift_fixed_into(mp_int *r, mp_int *a, size_t bits) +{ + size_t words = bits / BIGNUM_INT_BITS; + size_t bitoff = bits % BIGNUM_INT_BITS; + + for (size_t i = r->nw; i-- > 0 ;) { + if (i < words) { + r->w[i] = 0; + } else { + r->w[i] = mp_word(a, i - words); + if (bitoff != 0) { + r->w[i] <<= bitoff; + if (i > words) + r->w[i] |= mp_word(a, i - words - 1) >> + (BIGNUM_INT_BITS - bitoff); + } + } + } +} + +void mp_rshift_fixed_into(mp_int *r, mp_int *a, size_t bits) +{ + size_t words = bits / BIGNUM_INT_BITS; + size_t bitoff = bits % BIGNUM_INT_BITS; + + for (size_t i = 0; i < r->nw; i++) { + r->w[i] = mp_word(a, i + words); + if (bitoff != 0) { + r->w[i] >>= bitoff; + r->w[i] |= mp_word(a, i + words + 1) << (BIGNUM_INT_BITS - bitoff); + } + } +} + +mp_int *mp_lshift_fixed(mp_int *x, size_t bits) +{ + size_t words = (bits + BIGNUM_INT_BITS - 1) / BIGNUM_INT_BITS; + mp_int *r = mp_make_sized(x->nw + words); + mp_lshift_fixed_into(r, x, bits); + return r; +} + +mp_int *mp_rshift_fixed(mp_int *x, size_t bits) +{ + size_t words = bits / BIGNUM_INT_BITS; + size_t nw = x->nw - size_t_min(x->nw, words); + mp_int *r = mp_make_sized(size_t_max(nw, 1)); + mp_rshift_fixed_into(r, x, bits); + return r; +} + +/* + * Safe right shift is done using the same technique as + * trim_leading_zeroes above: you make an n-word left shift by + * composing an appropriate subset of power-of-2-sized shifts, so it + * takes log_2(n) loop iterations each of which does a different shift + * by a power of 2 words, using the usual bit twiddling to make the + * whole shift conditional on the appropriate bit of n. + */ +static void mp_rshift_safe_in_place(mp_int *r, size_t bits) +{ + size_t wordshift = bits / BIGNUM_INT_BITS; + size_t bitshift = bits % BIGNUM_INT_BITS; + + unsigned clear = (r->nw - wordshift) >> (CHAR_BIT * sizeof(size_t) - 1); + mp_cond_clear(r, clear); + + for (unsigned bit = 0; r->nw >> bit; bit++) { + size_t word_offset = (size_t)1 << bit; + BignumInt mask = -(BignumInt)((wordshift >> bit) & 1); + for (size_t i = 0; i < r->nw; i++) { + BignumInt w = mp_word(r, i + word_offset); + r->w[i] ^= (r->w[i] ^ w) & mask; + } + } + + /* + * That's done the shifting by words; now we do the shifting by + * bits. + */ + for (unsigned bit = 0; bit < BIGNUM_INT_BITS_BITS; bit++) { + unsigned shift = 1 << bit, upshift = BIGNUM_INT_BITS - shift; + BignumInt mask = -(BignumInt)((bitshift >> bit) & 1); + for (size_t i = 0; i < r->nw; i++) { + BignumInt w = ((r->w[i] >> shift) | (mp_word(r, i+1) << upshift)); + r->w[i] ^= (r->w[i] ^ w) & mask; + } + } +} + +mp_int *mp_rshift_safe(mp_int *x, size_t bits) +{ + mp_int *r = mp_copy(x); + mp_rshift_safe_in_place(r, bits); + return r; +} + +void mp_rshift_safe_into(mp_int *r, mp_int *x, size_t bits) +{ + mp_copy_into(r, x); + mp_rshift_safe_in_place(r, bits); +} + +static void mp_lshift_safe_in_place(mp_int *r, size_t bits) +{ + size_t wordshift = bits / BIGNUM_INT_BITS; + size_t bitshift = bits % BIGNUM_INT_BITS; + + /* + * Same strategy as mp_rshift_safe_in_place, but of course the + * other way up. + */ + + unsigned clear = (r->nw - wordshift) >> (CHAR_BIT * sizeof(size_t) - 1); + mp_cond_clear(r, clear); + + for (unsigned bit = 0; r->nw >> bit; bit++) { + size_t word_offset = (size_t)1 << bit; + BignumInt mask = -(BignumInt)((wordshift >> bit) & 1); + for (size_t i = r->nw; i-- > 0 ;) { + BignumInt w = mp_word(r, i - word_offset); + r->w[i] ^= (r->w[i] ^ w) & mask; + } + } + + size_t downshift = BIGNUM_INT_BITS - bitshift; + size_t no_shift = (downshift >> BIGNUM_INT_BITS_BITS); + downshift &= ~-(size_t)no_shift; + BignumInt downshifted_mask = ~-(BignumInt)no_shift; + + for (size_t i = r->nw; i-- > 0 ;) { + r->w[i] = (r->w[i] << bitshift) | + ((mp_word(r, i-1) >> downshift) & downshifted_mask); + } +} + +void mp_lshift_safe_into(mp_int *r, mp_int *x, size_t bits) +{ + mp_copy_into(r, x); + mp_lshift_safe_in_place(r, bits); +} + +void mp_reduce_mod_2to(mp_int *x, size_t p) +{ + size_t word = p / BIGNUM_INT_BITS; + size_t mask = ((size_t)1 << (p % BIGNUM_INT_BITS)) - 1; + for (; word < x->nw; word++) { + x->w[word] &= mask; + mask = 0; + } +} + +/* + * Inverse mod 2^n is computed by an iterative technique which doubles + * the number of bits at each step. + */ +mp_int *mp_invert_mod_2to(mp_int *x, size_t p) +{ + /* Input checks: x must be coprime to the modulus, i.e. odd, and p + * can't be zero */ + assert(x->nw > 0); + assert(x->w[0] & 1); + assert(p > 0); + + size_t rw = (p + BIGNUM_INT_BITS - 1) / BIGNUM_INT_BITS; + rw = size_t_max(rw, 1); + mp_int *r = mp_make_sized(rw); + + size_t mul_scratchsize = mp_mul_scratchspace(2*rw, rw, rw); + mp_int *scratch_orig = mp_make_sized(6 * rw + mul_scratchsize); + mp_int scratch_per_iter = *scratch_orig; + mp_int mul_scratch = mp_alloc_from_scratch( + &scratch_per_iter, mul_scratchsize); + + r->w[0] = 1; + + for (size_t b = 1; b < p; b <<= 1) { + /* + * In each step of this iteration, we have the inverse of x + * mod 2^b, and we want the inverse of x mod 2^{2b}. + * + * Write B = 2^b for convenience, so we want x^{-1} mod B^2. + * Let x = x_0 + B x_1 + k B^2, with 0 <= x_0,x_1 < B. + * + * We want to find r_0 and r_1 such that + * (r_1 B + r_0) (x_1 B + x_0) == 1 (mod B^2) + * + * To begin with, we know r_0 must be the inverse mod B of + * x_0, i.e. of x, i.e. it is the inverse we computed in the + * previous iteration. So now all we need is r_1. + * + * Multiplying out, neglecting multiples of B^2, and writing + * x_0 r_0 = K B + 1, we have + * + * r_1 x_0 B + r_0 x_1 B + K B == 0 (mod B^2) + * => r_1 x_0 B == - r_0 x_1 B - K B (mod B^2) + * => r_1 x_0 == - r_0 x_1 - K (mod B) + * => r_1 == r_0 (- r_0 x_1 - K) (mod B) + * + * (the last step because we multiply through by the inverse + * of x_0, which we already know is r_0). + */ + + mp_int scratch_this_iter = scratch_per_iter; + size_t Bw = (b + BIGNUM_INT_BITS - 1) / BIGNUM_INT_BITS; + size_t B2w = (2*b + BIGNUM_INT_BITS - 1) / BIGNUM_INT_BITS; + + /* Start by finding K: multiply x_0 by r_0, and shift down. */ + mp_int x0 = mp_alloc_from_scratch(&scratch_this_iter, Bw); + mp_copy_into(&x0, x); + mp_reduce_mod_2to(&x0, b); + mp_int r0 = mp_make_alias(r, 0, Bw); + mp_int Kshift = mp_alloc_from_scratch(&scratch_this_iter, B2w); + mp_mul_internal(&Kshift, &x0, &r0, mul_scratch); + mp_int K = mp_alloc_from_scratch(&scratch_this_iter, Bw); + mp_rshift_fixed_into(&K, &Kshift, b); + + /* Now compute the product r_0 x_1, reusing the space of Kshift. */ + mp_int x1 = mp_alloc_from_scratch(&scratch_this_iter, Bw); + mp_rshift_fixed_into(&x1, x, b); + mp_reduce_mod_2to(&x1, b); + mp_int r0x1 = mp_make_alias(&Kshift, 0, Bw); + mp_mul_internal(&r0x1, &r0, &x1, mul_scratch); + + /* Add K to that. */ + mp_add_into(&r0x1, &r0x1, &K); + + /* Negate it. */ + mp_neg_into(&r0x1, &r0x1); + + /* Multiply by r_0. */ + mp_int r1 = mp_alloc_from_scratch(&scratch_this_iter, Bw); + mp_mul_internal(&r1, &r0, &r0x1, mul_scratch); + mp_reduce_mod_2to(&r1, b); + + /* That's our r_1, so add it on to r_0 to get the full inverse + * output from this iteration. */ + mp_lshift_fixed_into(&K, &r1, (b % BIGNUM_INT_BITS)); + size_t Bpos = b / BIGNUM_INT_BITS; + mp_int r1_position = mp_make_alias(r, Bpos, B2w-Bpos); + mp_add_into(&r1_position, &r1_position, &K); + } + + /* Finally, reduce mod the precise desired number of bits. */ + mp_reduce_mod_2to(r, p); + + mp_free(scratch_orig); + return r; +} + +static size_t monty_scratch_size(MontyContext *mc) +{ + return 3*mc->rw + mc->pw + mp_mul_scratchspace(mc->pw, mc->rw, mc->rw); +} + +MontyContext *monty_new(mp_int *modulus) +{ + MontyContext *mc = snew(MontyContext); + + mc->rw = modulus->nw; + mc->rbits = mc->rw * BIGNUM_INT_BITS; + mc->pw = mc->rw * 2 + 1; + + mc->m = mp_copy(modulus); + + mc->minus_minv_mod_r = mp_invert_mod_2to(mc->m, mc->rbits); + mp_neg_into(mc->minus_minv_mod_r, mc->minus_minv_mod_r); + + mp_int *r = mp_make_sized(mc->rw + 1); + r->w[mc->rw] = 1; + mc->powers_of_r_mod_m[0] = mp_mod(r, mc->m); + mp_free(r); + + for (size_t j = 1; j < lenof(mc->powers_of_r_mod_m); j++) + mc->powers_of_r_mod_m[j] = mp_modmul( + mc->powers_of_r_mod_m[0], mc->powers_of_r_mod_m[j-1], mc->m); + + mc->scratch = mp_make_sized(monty_scratch_size(mc)); + + return mc; +} + +void monty_free(MontyContext *mc) +{ + mp_free(mc->m); + for (size_t j = 0; j < 3; j++) + mp_free(mc->powers_of_r_mod_m[j]); + mp_free(mc->minus_minv_mod_r); + mp_free(mc->scratch); + smemclr(mc, sizeof(*mc)); + sfree(mc); +} + +/* + * The main Montgomery reduction step. + */ +static mp_int monty_reduce_internal(MontyContext *mc, mp_int *x, mp_int scratch) +{ + /* + * The trick with Montgomery reduction is that on the one hand we + * want to reduce the size of the input by a factor of about r, + * and on the other hand, the two numbers we just multiplied were + * both stored with an extra factor of r multiplied in. So we + * computed ar*br = ab r^2, but we want to return abr, so we need + * to divide by r - and if we can do that by _actually dividing_ + * by r then this also reduces the size of the number. + * + * But we can only do that if the number we're dividing by r is a + * multiple of r. So first we must add an adjustment to it which + * clears its bottom 'rbits' bits. That adjustment must be a + * multiple of m in order to leave the residue mod n unchanged, so + * the question is, what multiple of m can we add to x to make it + * congruent to 0 mod r? And the answer is, x * (-m)^{-1} mod r. + */ + + /* x mod r */ + mp_int x_lo = mp_make_alias(x, 0, mc->rbits); + + /* x * (-m)^{-1}, i.e. the number we want to multiply by m */ + mp_int k = mp_alloc_from_scratch(&scratch, mc->rw); + mp_mul_internal(&k, &x_lo, mc->minus_minv_mod_r, scratch); + + /* m times that, i.e. the number we want to add to x */ + mp_int mk = mp_alloc_from_scratch(&scratch, mc->pw); + mp_mul_internal(&mk, mc->m, &k, scratch); + + /* Add it to x */ + mp_add_into(&mk, x, &mk); + + /* Reduce mod r, by simply making an alias to the upper words of x */ + mp_int toret = mp_make_alias(&mk, mc->rw, mk.nw - mc->rw); + + /* + * We'll generally be doing this after a multiplication of two + * fully reduced values. So our input could be anything up to m^2, + * and then we added up to rm to it. Hence, the maximum value is + * rm+m^2, and after dividing by r, that becomes r + m(m/r) < 2r. + * So a single trial-subtraction will finish reducing to the + * interval [0,m). + */ + mp_cond_sub_into(&toret, &toret, mc->m, mp_cmp_hs(&toret, mc->m)); + return toret; +} + +void monty_mul_into(MontyContext *mc, mp_int *r, mp_int *x, mp_int *y) +{ + assert(x->nw <= mc->rw); + assert(y->nw <= mc->rw); + + mp_int scratch = *mc->scratch; + mp_int tmp = mp_alloc_from_scratch(&scratch, 2*mc->rw); + mp_mul_into(&tmp, x, y); + mp_int reduced = monty_reduce_internal(mc, &tmp, scratch); + mp_copy_into(r, &reduced); + mp_clear(mc->scratch); +} + +mp_int *monty_mul(MontyContext *mc, mp_int *x, mp_int *y) +{ + mp_int *toret = mp_make_sized(mc->rw); + monty_mul_into(mc, toret, x, y); + return toret; +} + +mp_int *monty_modulus(MontyContext *mc) +{ + return mc->m; +} + +mp_int *monty_identity(MontyContext *mc) +{ + return mc->powers_of_r_mod_m[0]; +} + +mp_int *monty_invert(MontyContext *mc, mp_int *x) +{ + /* Given xr, we want to return x^{-1}r = (xr)^{-1} r^2 = + * monty_reduce((xr)^{-1} r^3) */ + mp_int *tmp = mp_invert(x, mc->m); + mp_int *toret = monty_mul(mc, tmp, mc->powers_of_r_mod_m[2]); + mp_free(tmp); + return toret; +} + +/* + * Importing a number into Montgomery representation involves + * multiplying it by r and reducing mod m. We use the general-purpose + * mp_modmul for this, in case the input number is out of range. + */ +mp_int *monty_import(MontyContext *mc, mp_int *x) +{ + return mp_modmul(x, mc->powers_of_r_mod_m[0], mc->m); +} + +void monty_import_into(MontyContext *mc, mp_int *r, mp_int *x) +{ + mp_int *imported = monty_import(mc, x); + mp_copy_into(r, imported); + mp_free(imported); +} + +/* + * Exporting a number means multiplying it by r^{-1}, which is exactly + * what monty_reduce does anyway, so we just do that. + */ +void monty_export_into(MontyContext *mc, mp_int *r, mp_int *x) +{ + assert(x->nw <= 2*mc->rw); + mp_int reduced = monty_reduce_internal(mc, x, *mc->scratch); + mp_copy_into(r, &reduced); + mp_clear(mc->scratch); +} + +mp_int *monty_export(MontyContext *mc, mp_int *x) +{ + mp_int *toret = mp_make_sized(mc->rw); + monty_export_into(mc, toret, x); + return toret; +} + +static void monty_reduce(MontyContext *mc, mp_int *x) +{ + mp_int reduced = monty_reduce_internal(mc, x, *mc->scratch); + mp_copy_into(x, &reduced); + mp_clear(mc->scratch); +} + +mp_int *monty_pow(MontyContext *mc, mp_int *base, mp_int *exponent) +{ + /* square builds up powers of the form base^{2^i}. */ + mp_int *square = mp_copy(base); + size_t i = 0; + + /* out accumulates the output value. Starts at 1 (in Montgomery + * representation) and we multiply in each base^{2^i}. */ + mp_int *out = mp_copy(mc->powers_of_r_mod_m[0]); + + /* tmp holds each product we compute and reduce. */ + mp_int *tmp = mp_make_sized(mc->rw * 2); + + while (true) { + mp_mul_into(tmp, out, square); + monty_reduce(mc, tmp); + mp_select_into(out, out, tmp, mp_get_bit(exponent, i)); + + if (++i >= exponent->nw * BIGNUM_INT_BITS) + break; + + mp_mul_into(tmp, square, square); + monty_reduce(mc, tmp); + mp_copy_into(square, tmp); + } + + mp_free(square); + mp_free(tmp); + mp_clear(mc->scratch); + return out; +} + +mp_int *mp_modpow(mp_int *base, mp_int *exponent, mp_int *modulus) +{ + assert(modulus->nw > 0); + assert(modulus->w[0] & 1); + + MontyContext *mc = monty_new(modulus); + mp_int *m_base = monty_import(mc, base); + mp_int *m_out = monty_pow(mc, m_base, exponent); + mp_int *out = monty_export(mc, m_out); + mp_free(m_base); + mp_free(m_out); + monty_free(mc); + return out; +} + +/* + * Given two input integers a,b which are not both even, computes d = + * gcd(a,b) and also two integers A,B such that A*a - B*b = d. A,B + * will be the minimal non-negative pair satisfying that criterion, + * which is equivalent to saying that 0 <= A < b/d and 0 <= B < a/d. + * + * This algorithm is an adapted form of Stein's algorithm, which + * computes gcd(a,b) using only addition and bit shifts (i.e. without + * needing general division), using the following rules: + * + * - if both of a,b are even, divide off a common factor of 2 + * - if one of a,b (WLOG a) is even, then gcd(a,b) = gcd(a/2,b), so + * just divide a by 2 + * - if both of a,b are odd, then WLOG a>b, and gcd(a,b) = + * gcd(b,(a-b)/2). + * + * Sometimes this function is used for modular inversion, in which + * case we already know we expect the two inputs to be coprime, so to + * save time the 'both even' initial case is assumed not to arise (or + * to have been handled already by the caller). So this function just + * performs a sequence of reductions in the following form: + * + * - if a,b are both odd, sort them so that a > b, and replace a with + * b-a; otherwise sort them so that a is the even one + * - either way, now a is even and b is odd, so divide a by 2. + * + * The big change to Stein's algorithm is that we need the Bezout + * coefficients as output, not just the gcd. So we need to know how to + * generate those in each case, based on the coefficients from the + * reduced pair of numbers: + * + * - If a is even, and u,v are such that u*(a/2) + v*b = d: + * + if u is also even, then this is just (u/2)*a + v*b = d + * + otherwise, (u+b)*(a/2) + (v-a/2)*b is also equal to d, and + * since u and b are both odd, (u+b)/2 is an integer, so we have + * ((u+b)/2)*a + (v-a/2)*b = d. + * + * - If a,b are both odd, and u,v are such that u*b + v*(a-b) = d, + * then v*a + (u-v)*b = d. + * + * In the case where we passed from (a,b) to (b,(a-b)/2), we regard it + * as having first subtracted b from a and then halved a, so both of + * these transformations must be done in sequence. + * + * The code below transforms this from a recursive to an iterative + * algorithm. We first reduce a,b to 0,1, recording at each stage + * whether we did the initial subtraction, and whether we had to swap + * the two values; then we iterate backwards over that record of what + * we did, applying the above rules for building up the Bezout + * coefficients as we go. Of course, all the case analysis is done by + * the usual bit-twiddling conditionalisation to avoid data-dependent + * control flow. + * + * Also, since these mp_ints are generally treated as unsigned, we + * store the coefficients by absolute value, with the semantics that + * they always have opposite sign, and in the unwinding loop we keep a + * bit indicating whether Aa-Bb is currently expected to be +d or -d, + * so that we can do one final conditional adjustment if it's -d. + * + * Once the reduction rules have managed to reduce the input numbers + * to (0,d), then they are stable (the next reduction will always + * divide the even one by 2, which maps 0 to 0). So it doesn't matter + * if we do more steps of the algorithm than necessary; hence, for + * constant time, we just need to find the maximum number we could + * _possibly_ require, and do that many. + * + * If a,b < 2^n, at most 2n iterations are required. Proof: consider + * the quantity Q = log_2(a) + log_2(b). Every step halves one of the + * numbers (and may also reduce one of them further by doing a + * subtraction beforehand, but in the worst case, not by much or not + * at all). So Q reduces by at least 1 per iteration, and it starts + * off with a value at most 2n. + * + * The worst case inputs (I think) are where x=2^{n-1} and y=2^n-1 + * (i.e. x is a power of 2 and y is all 1s). In that situation, the + * first n-1 steps repeatedly halve x until it's 1, and then there are + * n further steps each of which subtracts 1 from y and halves it. + */ +static void mp_bezout_into(mp_int *a_coeff_out, mp_int *b_coeff_out, + mp_int *gcd_out, mp_int *a_in, mp_int *b_in) +{ + size_t nw = size_t_max(1, size_t_max(a_in->nw, b_in->nw)); + + /* Make mutable copies of the input numbers */ + mp_int *a = mp_make_sized(nw), *b = mp_make_sized(nw); + mp_copy_into(a, a_in); + mp_copy_into(b, b_in); + + /* Space to build up the output coefficients, with an extra word + * so that intermediate values can overflow off the top and still + * right-shift back down to the correct value */ + mp_int *ac = mp_make_sized(nw + 1), *bc = mp_make_sized(nw + 1); + + /* And a general-purpose temp register */ + mp_int *tmp = mp_make_sized(nw); + + /* Space to record the sequence of reduction steps to unwind. We + * make it a BignumInt for no particular reason except that (a) + * mp_make_sized conveniently zeroes the allocation and mp_free + * wipes it, and (b) this way I can use mp_dump() if I have to + * debug this code. */ + size_t steps = 2 * nw * BIGNUM_INT_BITS; + mp_int *record = mp_make_sized( + (steps*2 + BIGNUM_INT_BITS - 1) / BIGNUM_INT_BITS); + + for (size_t step = 0; step < steps; step++) { + /* + * If a and b are both odd, we want to sort them so that a is + * larger. But if one is even, we want to sort them so that a + * is the even one. + */ + unsigned swap_if_both_odd = mp_cmp_hs(b, a); + unsigned swap_if_one_even = a->w[0] & 1; + unsigned both_odd = a->w[0] & b->w[0] & 1; + unsigned swap = swap_if_one_even ^ ( + (swap_if_both_odd ^ swap_if_one_even) & both_odd); + + mp_cond_swap(a, b, swap); + + /* + * If a,b are both odd, then a is the larger number, so + * subtract the smaller one from it. + */ + mp_cond_sub_into(a, a, b, both_odd); + + /* + * Now a is even, so divide it by two. + */ + mp_rshift_fixed_into(a, a, 1); + + /* + * Record the two 1-bit values both_odd and swap. + */ + mp_set_bit(record, step*2, both_odd); + mp_set_bit(record, step*2+1, swap); + } + + /* + * Now we expect to have reduced the two numbers to 0 and d, + * although we don't know which way round. (But we avoid checking + * this by assertion; sometimes we'll need to do this computation + * without giving away that we already know the inputs were bogus. + * So we'd prefer to just press on and return nonsense.) + */ + + if (gcd_out) { + /* + * At this point we can return the actual gcd. Since one of + * a,b is it and the other is zero, the easiest way to get it + * is to add them together. + */ + mp_add_into(gcd_out, a, b); + } + + /* + * If the caller _only_ wanted the gcd, and neither Bezout + * coefficient is even required, we can skip the entire unwind + * stage. + */ + if (a_coeff_out || b_coeff_out) { + + /* + * The Bezout coefficients of a,b at this point are simply 0 + * for whichever of a,b is zero, and 1 for whichever is + * nonzero. The nonzero number equals gcd(a,b), which by + * assumption is odd, so we can do this by just taking the low + * bit of each one. + */ + ac->w[0] = mp_get_bit(a, 0); + bc->w[0] = mp_get_bit(b, 0); + + /* + * Overwrite a,b themselves with those same numbers. This has + * the effect of dividing both of them by d, which will + * arrange that during the unwind stage we generate the + * minimal coefficients instead of a larger pair. + */ + mp_copy_into(a, ac); + mp_copy_into(b, bc); + + /* + * We'll maintain the invariant as we unwind that ac * a - bc + * * b is either +d or -d (or rather, +1/-1 after scaling by + * d), and we'll remember which. (We _could_ keep it at +d the + * whole time, but it would cost more work every time round + * the loop, so it's cheaper to fix that up once at the end.) + * + * Initially, the result is +d if a was the nonzero value after + * reduction, and -d if b was. + */ + unsigned minus_d = b->w[0]; + + for (size_t step = steps; step-- > 0 ;) { + /* + * Recover the data from the step we're unwinding. + */ + unsigned both_odd = mp_get_bit(record, step*2); + unsigned swap = mp_get_bit(record, step*2+1); + + /* + * Unwind the division: if our coefficient of a is odd, we + * adjust the coefficients by +b and +a respectively. + */ + unsigned adjust = ac->w[0] & 1; + mp_cond_add_into(ac, ac, b, adjust); + mp_cond_add_into(bc, bc, a, adjust); + + /* + * Now ac is definitely even, so we divide it by two. + */ + mp_rshift_fixed_into(ac, ac, 1); + + /* + * Now unwind the subtraction, if there was one, by adding + * ac to bc. + */ + mp_cond_add_into(bc, bc, ac, both_odd); + + /* + * Undo the transformation of the input numbers, by + * multiplying a by 2 and then adding b to a (the latter + * only if both_odd). + */ + mp_lshift_fixed_into(a, a, 1); + mp_cond_add_into(a, a, b, both_odd); + + /* + * Finally, undo the swap. If we do swap, this also + * reverses the sign of the current result ac*a+bc*b. + */ + mp_cond_swap(a, b, swap); + mp_cond_swap(ac, bc, swap); + minus_d ^= swap; + } + + /* + * Now we expect to have recovered the input a,b (or rather, + * the versions of them divided by d). But we might find that + * our current result is -d instead of +d, that is, we have + * A',B' such that A'a - B'b = -d. + * + * In that situation, we set A = b-A' and B = a-B', giving us + * Aa-Bb = ab - A'a - ab + B'b = +1. + */ + mp_sub_into(tmp, b, ac); + mp_select_into(ac, ac, tmp, minus_d); + mp_sub_into(tmp, a, bc); + mp_select_into(bc, bc, tmp, minus_d); + + /* + * Now we really are done. Return the outputs. + */ + if (a_coeff_out) + mp_copy_into(a_coeff_out, ac); + if (b_coeff_out) + mp_copy_into(b_coeff_out, bc); + + } + + mp_free(a); + mp_free(b); + mp_free(ac); + mp_free(bc); + mp_free(tmp); + mp_free(record); +} + +mp_int *mp_invert(mp_int *x, mp_int *m) +{ + mp_int *result = mp_make_sized(m->nw); + mp_bezout_into(result, NULL, NULL, x, m); + return result; +} + +void mp_gcd_into(mp_int *a, mp_int *b, mp_int *gcd, mp_int *A, mp_int *B) +{ + /* + * Identify shared factors of 2. To do this we OR the two numbers + * to get something whose lowest set bit is in the right place, + * remove all higher bits by ANDing it with its own negation, and + * use mp_get_nbits to find the location of the single remaining + * set bit. + */ + mp_int *tmp = mp_make_sized(size_t_max(a->nw, b->nw)); + for (size_t i = 0; i < tmp->nw; i++) + tmp->w[i] = mp_word(a, i) | mp_word(b, i); + BignumCarry carry = 1; + for (size_t i = 0; i < tmp->nw; i++) { + BignumInt negw; + BignumADC(negw, carry, 0, ~tmp->w[i], carry); + tmp->w[i] &= negw; + } + size_t shift = mp_get_nbits(tmp) - 1; + mp_free(tmp); + + /* + * Make copies of a,b with those shared factors of 2 divided off, + * so that at least one is odd (which is the precondition for + * mp_bezout_into). Compute the gcd of those. + */ + mp_int *as = mp_rshift_safe(a, shift); + mp_int *bs = mp_rshift_safe(b, shift); + mp_bezout_into(A, B, gcd, as, bs); + mp_free(as); + mp_free(bs); + + /* + * And finally shift the gcd back up (unless the caller didn't + * even ask for it), to put the shared factors of 2 back in. + */ + if (gcd) + mp_lshift_safe_in_place(gcd, shift); +} + +mp_int *mp_gcd(mp_int *a, mp_int *b) +{ + mp_int *gcd = mp_make_sized(size_t_min(a->nw, b->nw)); + mp_gcd_into(a, b, gcd, NULL, NULL); + return gcd; +} + +unsigned mp_coprime(mp_int *a, mp_int *b) +{ + mp_int *gcd = mp_gcd(a, b); + unsigned toret = mp_eq_integer(gcd, 1); + mp_free(gcd); + return toret; +} + +static uint32_t recip_approx_32(uint32_t x) +{ + /* + * Given an input x in [2^31,2^32), i.e. a uint32_t with its high + * bit set, this function returns an approximation to 2^63/x, + * computed using only multiplications and bit shifts just in case + * the C divide operator has non-constant time (either because the + * underlying machine instruction does, or because the operator + * expands to a library function on a CPU without hardware + * division). + * + * The coefficients are derived from those of the degree-9 + * polynomial which is the minimax-optimal approximation to that + * function on the given interval (generated using the Remez + * algorithm), converted into integer arithmetic with shifts used + * to maximise the number of significant bits at every state. (A + * sort of 'static floating point' - the exponent is statically + * known at every point in the code, so it never needs to be + * stored at run time or to influence runtime decisions.) + * + * Exhaustive iteration over the whole input space shows the + * largest possible error to be 1686.54. (The input value + * attaining that bound is 4226800006 == 0xfbefd986, whose true + * reciprocal is 2182116973.540... == 0x8210766d.8a6..., whereas + * this function returns 2182115287 == 0x82106fd7.) + */ + uint64_t r = 0x92db03d6ULL; + r = 0xf63e71eaULL - ((r*x) >> 34); + r = 0xb63721e8ULL - ((r*x) >> 34); + r = 0x9c2da00eULL - ((r*x) >> 33); + r = 0xaada0bb8ULL - ((r*x) >> 32); + r = 0xf75cd403ULL - ((r*x) >> 31); + r = 0xecf97a41ULL - ((r*x) >> 31); + r = 0x90d876cdULL - ((r*x) >> 31); + r = 0x6682799a0ULL - ((r*x) >> 26); + return r; +} + +void mp_divmod_into(mp_int *n, mp_int *d, mp_int *q_out, mp_int *r_out) +{ + assert(!mp_eq_integer(d, 0)); + + /* + * We do division by using Newton-Raphson iteration to converge to + * the reciprocal of d (or rather, R/d for R a sufficiently large + * power of 2); then we multiply that reciprocal by n; and we + * finish up with conditional subtraction. + * + * But we have to do it in a fixed number of N-R iterations, so we + * need some error analysis to know how many we might need. + * + * The iteration is derived by defining f(r) = d - R/r. + * Differentiating gives f'(r) = R/r^2, and the Newton-Raphson + * formula applied to those functions gives + * + * r_{i+1} = r_i - f(r_i) / f'(r_i) + * = r_i - (d - R/r_i) r_i^2 / R + * = r_i (2 R - d r_i) / R + * + * Now let e_i be the error in a given iteration, in the sense + * that + * + * d r_i = R + e_i + * i.e. e_i/R = (r_i - r_true) / r_true + * + * so e_i is the _relative_ error in r_i. + * + * We must also introduce a rounding-error term, because the + * division by R always gives an integer. This might make the + * output off by up to 1 (in the negative direction, because + * right-shifting gives floor of the true quotient). So when we + * divide by R, we must imagine adding some f in [0,1). Then we + * have + * + * d r_{i+1} = d r_i (2 R - d r_i) / R - d f + * = (R + e_i) (R - e_i) / R - d f + * = (R^2 - e_i^2) / R - d f + * = R - (e_i^2 / R + d f) + * => e_{i+1} = - (e_i^2 / R + d f) + * + * The sum of two positive quantities is bounded above by twice + * their max, and max |f| = 1, so we can bound this as follows: + * + * |e_{i+1}| <= 2 max (e_i^2/R, d) + * |e_{i+1}/R| <= 2 max ((e_i/R)^2, d/R) + * log2 |R/e_{i+1}| <= min (2 log2 |R/e_i|, log2 |R/d|) - 1 + * + * which tells us that the number of 'good' bits - i.e. + * log2(R/e_i) - very nearly doubles at every iteration (apart + * from that subtraction of 1), until it gets to the same size as + * log2(R/d). In other words, the size of R in bits has to be the + * size of denominator we're putting in, _plus_ the amount of + * precision we want to get back out. + * + * So when we multiply n (the input numerator) by our final + * reciprocal approximation r, but actually r differs from R/d by + * up to 2, then it follows that + * + * n/d - nr/R = n/d - [ n (R/d + e) ] / R + * = n/d - [ (n/d) R + n e ] / R + * = -ne/R + * => 0 <= n/d - nr/R < 2n/R + * + * so our computed quotient can differ from the true n/d by up to + * 2n/R. Hence, as long as we also choose R large enough that 2n/R + * is bounded above by a constant, we can guarantee a bounded + * number of final conditional-subtraction steps. + */ + + /* + * Get at least 32 of the most significant bits of the input + * number. + */ + size_t hiword_index = 0; + uint64_t hibits = 0, lobits = 0; + mp_find_highest_nonzero_word_pair(d, 64 - BIGNUM_INT_BITS, + &hiword_index, &hibits, &lobits); + + /* + * Make a shifted combination of those two words which puts the + * topmost bit of the number at bit 63. + */ + size_t shift_up = 0; + for (size_t i = BIGNUM_INT_BITS_BITS; i-- > 0;) { + size_t sl = (size_t)1 << i; /* left shift count */ + size_t sr = 64 - sl; /* complementary right-shift count */ + + /* Should we shift up? */ + unsigned indicator = 1 ^ normalise_to_1_u64(hibits >> sr); + + /* If we do, what will we get? */ + uint64_t new_hibits = (hibits << sl) | (lobits >> sr); + uint64_t new_lobits = lobits << sl; + size_t new_shift_up = shift_up + sl; + + /* Conditionally swap those values in. */ + hibits ^= (hibits ^ new_hibits ) & -(uint64_t)indicator; + lobits ^= (lobits ^ new_lobits ) & -(uint64_t)indicator; + shift_up ^= (shift_up ^ new_shift_up ) & -(size_t) indicator; + } + + /* + * So now we know the most significant 32 bits of d are at the top + * of hibits. Approximate the reciprocal of those bits. + */ + lobits = (uint64_t)recip_approx_32(hibits >> 32) << 32; + hibits = 0; + + /* + * And shift that up by as many bits as the input was shifted up + * just now, so that the product of this approximation and the + * actual input will be close to a fixed power of two regardless + * of where the MSB was. + * + * I do this in another log n individual passes, partly in case + * the CPU's register-controlled shift operation isn't + * time-constant, and also in case the compiler code-generates + * uint64_t shifts out of a variable number of smaller-word shift + * instructions, e.g. by splitting up into cases. + */ + for (size_t i = BIGNUM_INT_BITS_BITS; i-- > 0;) { + size_t sl = (size_t)1 << i; /* left shift count */ + size_t sr = 64 - sl; /* complementary right-shift count */ + + /* Should we shift up? */ + unsigned indicator = 1 & (shift_up >> i); + + /* If we do, what will we get? */ + uint64_t new_hibits = (hibits << sl) | (lobits >> sr); + uint64_t new_lobits = lobits << sl; + + /* Conditionally swap those values in. */ + hibits ^= (hibits ^ new_hibits ) & -(uint64_t)indicator; + lobits ^= (lobits ^ new_lobits ) & -(uint64_t)indicator; + } + + /* + * The product of the 128-bit value now in hibits:lobits with the + * 128-bit value we originally retrieved in the same variables + * will be in the vicinity of 2^191. So we'll take log2(R) to be + * 191, plus a multiple of BIGNUM_INT_BITS large enough to allow R + * to hold the combined sizes of n and d. + */ + size_t log2_R; + { + size_t max_log2_n = (n->nw + d->nw) * BIGNUM_INT_BITS; + log2_R = max_log2_n + 3; + log2_R -= size_t_min(191, log2_R); + log2_R = (log2_R + BIGNUM_INT_BITS - 1) & ~(BIGNUM_INT_BITS - 1); + log2_R += 191; + } + + /* Number of words in a bignum capable of holding numbers the size + * of twice R. */ + size_t rw = ((log2_R+2) + BIGNUM_INT_BITS - 1) / BIGNUM_INT_BITS; + + /* + * Now construct our full-sized starting reciprocal approximation. + */ + mp_int *r_approx = mp_make_sized(rw); + size_t output_bit_index; + { + /* Where in the input number did the input 128-bit value come from? */ + size_t input_bit_index = + (hiword_index * BIGNUM_INT_BITS) - (128 - BIGNUM_INT_BITS); + + /* So how far do we need to shift our 64-bit output, if the + * product of those two fixed-size values is 2^191 and we want + * to make it 2^log2_R instead? */ + output_bit_index = log2_R - 191 - input_bit_index; + + /* If we've done all that right, it should be a whole number + * of words. */ + assert(output_bit_index % BIGNUM_INT_BITS == 0); + size_t output_word_index = output_bit_index / BIGNUM_INT_BITS; + + mp_add_integer_into_shifted_by_words( + r_approx, r_approx, lobits, output_word_index); + mp_add_integer_into_shifted_by_words( + r_approx, r_approx, hibits, + output_word_index + 64 / BIGNUM_INT_BITS); + } + + /* + * Make the constant 2*R, which we'll need in the iteration. + */ + mp_int *two_R = mp_make_sized(rw); + BignumInt top_word = (BignumInt)1 << ((log2_R+1) % BIGNUM_INT_BITS); + mp_add_integer_into_shifted_by_words( + two_R, two_R, top_word, (log2_R+1) / BIGNUM_INT_BITS); + + /* + * Scratch space. + */ + mp_int *dr = mp_make_sized(rw + d->nw); + mp_int *diff = mp_make_sized(size_t_max(rw, dr->nw)); + mp_int *product = mp_make_sized(rw + diff->nw); + size_t scratchsize = size_t_max( + mp_mul_scratchspace(dr->nw, r_approx->nw, d->nw), + mp_mul_scratchspace(product->nw, r_approx->nw, diff->nw)); + mp_int *scratch = mp_make_sized(scratchsize); + mp_int product_shifted = mp_make_alias( + product, log2_R / BIGNUM_INT_BITS, product->nw); + + /* + * Initial error estimate: the 32-bit output of recip_approx_32 + * differs by less than 2048 (== 2^11) from the true top 32 bits + * of the reciprocal, so the relative error is at most 2^11 + * divided by the 32-bit reciprocal, which at worst is 2^11/2^31 = + * 2^-20. So even in the worst case, we have 20 good bits of + * reciprocal to start with. + */ + size_t good_bits = 31 - 11; + size_t good_bits_needed = BIGNUM_INT_BITS * n->nw + 4; /* add a few */ + + /* + * Now do Newton-Raphson iterations until we have reason to think + * they're not converging any more. + */ + while (good_bits < good_bits_needed) { + /* + * Compute the next iterate. + */ + mp_mul_internal(dr, r_approx, d, *scratch); + mp_sub_into(diff, two_R, dr); + mp_mul_internal(product, r_approx, diff, *scratch); + mp_rshift_fixed_into(r_approx, &product_shifted, + log2_R % BIGNUM_INT_BITS); + + /* + * Adjust the error estimate. + */ + good_bits = good_bits * 2 - 1; + } + + mp_free(dr); + mp_free(diff); + mp_free(product); + mp_free(scratch); + + /* + * Now we've got our reciprocal, we can compute the quotient, by + * multiplying in n and then shifting down by log2_R bits. + */ + mp_int *quotient_full = mp_mul(r_approx, n); + mp_int quotient_alias = mp_make_alias( + quotient_full, log2_R / BIGNUM_INT_BITS, quotient_full->nw); + mp_int *quotient = mp_make_sized(n->nw); + mp_rshift_fixed_into(quotient, "ient_alias, log2_R % BIGNUM_INT_BITS); + + /* + * Next, compute the remainder. + */ + mp_int *remainder = mp_make_sized(d->nw); + mp_mul_into(remainder, quotient, d); + mp_sub_into(remainder, n, remainder); + + /* + * Finally, two conditional subtractions to fix up any remaining + * rounding error. (I _think_ one should be enough, but this + * routine isn't time-critical enough to take chances.) + */ + unsigned q_correction = 0; + for (unsigned iter = 0; iter < 2; iter++) { + unsigned need_correction = mp_cmp_hs(remainder, d); + mp_cond_sub_into(remainder, remainder, d, need_correction); + q_correction += need_correction; + } + mp_add_integer_into(quotient, quotient, q_correction); + + /* + * Now we should have a perfect answer, i.e. 0 <= r < d. + */ + assert(!mp_cmp_hs(remainder, d)); + + if (q_out) + mp_copy_into(q_out, quotient); + if (r_out) + mp_copy_into(r_out, remainder); + + mp_free(r_approx); + mp_free(two_R); + mp_free(quotient_full); + mp_free(quotient); + mp_free(remainder); +} + +mp_int *mp_div(mp_int *n, mp_int *d) +{ + mp_int *q = mp_make_sized(n->nw); + mp_divmod_into(n, d, q, NULL); + return q; +} + +mp_int *mp_mod(mp_int *n, mp_int *d) +{ + mp_int *r = mp_make_sized(d->nw); + mp_divmod_into(n, d, NULL, r); + return r; +} + +mp_int *mp_nthroot(mp_int *y, unsigned n, mp_int *remainder_out) +{ + /* + * Allocate scratch space. + */ + mp_int **alloc, **powers, **newpowers, *scratch; + size_t nalloc = 2*(n+1)+1; + alloc = snewn(nalloc, mp_int *); + for (size_t i = 0; i < nalloc; i++) + alloc[i] = mp_make_sized(y->nw + 1); + powers = alloc; + newpowers = alloc + (n+1); + scratch = alloc[2*n+2]; + + /* + * We're computing the rounded-down nth root of y, i.e. the + * maximal x such that x^n <= y. We try to add 2^i to it for each + * possible value of i, starting from the largest one that might + * fit (i.e. such that 2^{n*i} fits in the size of y) downwards to + * i=0. + * + * We track all the smaller powers of x in the array 'powers'. In + * each iteration, if we update x, we update all of those values + * to match. + */ + mp_copy_integer_into(powers[0], 1); + for (size_t s = mp_max_bits(y) / n + 1; s-- > 0 ;) { + /* + * Let b = 2^s. We need to compute the powers (x+b)^i for each + * i, starting from our recorded values of x^i. + */ + for (size_t i = 0; i < n+1; i++) { + /* + * (x+b)^i = x^i + * + (i choose 1) x^{i-1} b + * + (i choose 2) x^{i-2} b^2 + * + ... + * + b^i + */ + uint16_t binom = 1; /* coefficient of b^i */ + mp_copy_into(newpowers[i], powers[i]); + for (size_t j = 0; j < i; j++) { + /* newpowers[i] += binom * powers[j] * 2^{(i-j)*s} */ + mp_mul_integer_into(scratch, powers[j], binom); + mp_lshift_fixed_into(scratch, scratch, (i-j) * s); + mp_add_into(newpowers[i], newpowers[i], scratch); + + uint32_t binom_mul = binom; + binom_mul *= (i-j); + binom_mul /= (j+1); + assert(binom_mul < 0x10000); + binom = binom_mul; + } + } + + /* + * Now, is the new value of x^n still <= y? If so, update. + */ + unsigned newbit = mp_cmp_hs(y, newpowers[n]); + for (size_t i = 0; i < n+1; i++) + mp_select_into(powers[i], powers[i], newpowers[i], newbit); + } + + if (remainder_out) + mp_sub_into(remainder_out, y, powers[n]); + + mp_int *root = mp_new(mp_max_bits(y) / n); + mp_copy_into(root, powers[1]); + + for (size_t i = 0; i < nalloc; i++) + mp_free(alloc[i]); + sfree(alloc); + + return root; +} + +mp_int *mp_modmul(mp_int *x, mp_int *y, mp_int *modulus) +{ + mp_int *product = mp_mul(x, y); + mp_int *reduced = mp_mod(product, modulus); + mp_free(product); + return reduced; +} + +mp_int *mp_modadd(mp_int *x, mp_int *y, mp_int *modulus) +{ + mp_int *sum = mp_add(x, y); + mp_int *reduced = mp_mod(sum, modulus); + mp_free(sum); + return reduced; +} + +mp_int *mp_modsub(mp_int *x, mp_int *y, mp_int *modulus) +{ + mp_int *diff = mp_make_sized(size_t_max(x->nw, y->nw)); + mp_sub_into(diff, x, y); + unsigned negate = mp_cmp_hs(y, x); + mp_cond_negate(diff, diff, negate); + mp_int *residue = mp_mod(diff, modulus); + mp_cond_negate(residue, residue, negate); + /* If we've just negated the residue, then it will be < 0 and need + * the modulus adding to it to make it positive - *except* if the + * residue was zero when we negated it. */ + unsigned make_positive = negate & ~mp_eq_integer(residue, 0); + mp_cond_add_into(residue, residue, modulus, make_positive); + mp_free(diff); + return residue; +} + +static mp_int *mp_modadd_in_range(mp_int *x, mp_int *y, mp_int *modulus) +{ + mp_int *sum = mp_make_sized(modulus->nw); + unsigned carry = mp_add_into_internal(sum, x, y); + mp_cond_sub_into(sum, sum, modulus, carry | mp_cmp_hs(sum, modulus)); + return sum; +} + +static mp_int *mp_modsub_in_range(mp_int *x, mp_int *y, mp_int *modulus) +{ + mp_int *diff = mp_make_sized(modulus->nw); + mp_sub_into(diff, x, y); + mp_cond_add_into(diff, diff, modulus, 1 ^ mp_cmp_hs(x, y)); + return diff; +} + +mp_int *monty_add(MontyContext *mc, mp_int *x, mp_int *y) +{ + return mp_modadd_in_range(x, y, mc->m); +} + +mp_int *monty_sub(MontyContext *mc, mp_int *x, mp_int *y) +{ + return mp_modsub_in_range(x, y, mc->m); +} + +void mp_min_into(mp_int *r, mp_int *x, mp_int *y) +{ + mp_select_into(r, x, y, mp_cmp_hs(x, y)); +} + +void mp_max_into(mp_int *r, mp_int *x, mp_int *y) +{ + mp_select_into(r, y, x, mp_cmp_hs(x, y)); +} + +mp_int *mp_min(mp_int *x, mp_int *y) +{ + mp_int *r = mp_make_sized(size_t_min(x->nw, y->nw)); + mp_min_into(r, x, y); + return r; +} + +mp_int *mp_max(mp_int *x, mp_int *y) +{ + mp_int *r = mp_make_sized(size_t_max(x->nw, y->nw)); + mp_max_into(r, x, y); + return r; +} + +mp_int *mp_power_2(size_t power) +{ + mp_int *x = mp_new(power + 1); + mp_set_bit(x, power, 1); + return x; +} + +struct ModsqrtContext { + mp_int *p; /* the prime */ + MontyContext *mc; /* for doing arithmetic mod p */ + + /* Decompose p-1 as 2^e k, for positive integer e and odd k */ + size_t e; + mp_int *k; + mp_int *km1o2; /* (k-1)/2 */ + + /* The user-provided value z which is not a quadratic residue mod + * p, and its kth power. Both in Montgomery form. */ + mp_int *z, *zk; +}; + +ModsqrtContext *modsqrt_new(mp_int *p, mp_int *any_nonsquare_mod_p) +{ + ModsqrtContext *sc = snew(ModsqrtContext); + memset(sc, 0, sizeof(ModsqrtContext)); + + sc->p = mp_copy(p); + sc->mc = monty_new(sc->p); + sc->z = monty_import(sc->mc, any_nonsquare_mod_p); + + /* Find the lowest set bit in p-1. Since this routine expects p to + * be non-secret (typically a well-known standard elliptic curve + * parameter), for once we don't need clever bit tricks. */ + for (sc->e = 1; sc->e < BIGNUM_INT_BITS * p->nw; sc->e++) + if (mp_get_bit(p, sc->e)) + break; + + sc->k = mp_rshift_fixed(p, sc->e); + sc->km1o2 = mp_rshift_fixed(sc->k, 1); + + /* Leave zk to be filled in lazily, since it's more expensive to + * compute. If this context turns out never to be needed, we can + * save the bulk of the setup time this way. */ + + return sc; +} + +static void modsqrt_lazy_setup(ModsqrtContext *sc) +{ + if (!sc->zk) + sc->zk = monty_pow(sc->mc, sc->z, sc->k); +} + +void modsqrt_free(ModsqrtContext *sc) +{ + monty_free(sc->mc); + mp_free(sc->p); + mp_free(sc->z); + mp_free(sc->k); + mp_free(sc->km1o2); + + if (sc->zk) + mp_free(sc->zk); + + sfree(sc); +} + +mp_int *mp_modsqrt(ModsqrtContext *sc, mp_int *x, unsigned *success) +{ + mp_int *mx = monty_import(sc->mc, x); + mp_int *mroot = monty_modsqrt(sc, mx, success); + mp_free(mx); + mp_int *root = monty_export(sc->mc, mroot); + mp_free(mroot); + return root; +} + +/* + * Modular square root, using an algorithm more or less similar to + * Tonelli-Shanks but adapted for constant time. + * + * The basic idea is to write p-1 = k 2^e, where k is odd and e > 0. + * Then the multiplicative group mod p (call it G) has a sequence of + * e+1 nested subgroups G = G_0 > G_1 > G_2 > ... > G_e, where each + * G_i is exactly half the size of G_{i-1} and consists of all the + * squares of elements in G_{i-1}. So the innermost group G_e has + * order k, which is odd, and hence within that group you can take a + * square root by raising to the power (k+1)/2. + * + * Our strategy is to iterate over these groups one by one and make + * sure the number x we're trying to take the square root of is inside + * each one, by adjusting it if it isn't. + * + * Suppose g is a primitive root of p, i.e. a generator of G_0. (We + * don't actually need to know what g _is_; we just imagine it for the + * sake of understanding.) Then G_i consists of precisely the (2^i)th + * powers of g, and hence, you can tell if a number is in G_i if + * raising it to the power k 2^{e-i} gives 1. So the conceptual + * algorithm goes: for each i, test whether x is in G_i by that + * method. If it isn't, then the previous iteration ensured it's in + * G_{i-1}, so it will be an odd power of g^{2^{i-1}}, and hence + * multiplying by any other odd power of g^{2^{i-1}} will give x' in + * G_i. And we have one of those, because our non-square z is an odd + * power of g, so z^{2^{i-1}} is an odd power of g^{2^{i-1}}. + * + * (There's a special case in the very first iteration, where we don't + * have a G_{i-1}. If it turns out that x is not even in G_1, that + * means it's not a square, so we set *success to 0. We still run the + * rest of the algorithm anyway, for the sake of constant time, but we + * don't give a hoot what it returns.) + * + * When we get to the end and have x in G_e, then we can take its + * square root by raising to (k+1)/2. But of course that's not the + * square root of the original input - it's only the square root of + * the adjusted version we produced during the algorithm. To get the + * true output answer we also have to multiply by a power of z, + * namely, z to the power of _half_ whatever we've been multiplying in + * as we go along. (The power of z we multiplied in must have been + * even, because the case in which we would have multiplied in an odd + * power of z is the i=0 case, in which we instead set the failure + * flag.) + * + * The code below is an optimised version of that basic idea, in which + * we _start_ by computing x^k so as to be able to test membership in + * G_i by only a few squarings rather than a full from-scratch modpow + * every time; we also start by computing our candidate output value + * x^{(k+1)/2}. So when the above description says 'adjust x by z^i' + * for some i, we have to adjust our running values of x^k and + * x^{(k+1)/2} by z^{ik} and z^{ik/2} respectively (the latter is safe + * because, as above, i is always even). And it turns out that we + * don't actually have to store the adjusted version of x itself at + * all - we _only_ keep those two powers of it. + */ +mp_int *monty_modsqrt(ModsqrtContext *sc, mp_int *x, unsigned *success) +{ + modsqrt_lazy_setup(sc); + + mp_int *scratch_to_free = mp_make_sized(3 * sc->mc->rw); + mp_int scratch = *scratch_to_free; + + /* + * Compute toret = x^{(k+1)/2}, our starting point for the output + * square root, and also xk = x^k which we'll use as we go along + * for knowing when to apply correction factors. We do this by + * first computing x^{(k-1)/2}, then multiplying it by x, then + * multiplying the two together. + */ + mp_int *toret = monty_pow(sc->mc, x, sc->km1o2); + mp_int xk = mp_alloc_from_scratch(&scratch, sc->mc->rw); + mp_copy_into(&xk, toret); + monty_mul_into(sc->mc, toret, toret, x); + monty_mul_into(sc->mc, &xk, toret, &xk); + + mp_int tmp = mp_alloc_from_scratch(&scratch, sc->mc->rw); + + mp_int power_of_zk = mp_alloc_from_scratch(&scratch, sc->mc->rw); + mp_copy_into(&power_of_zk, sc->zk); + + for (size_t i = 0; i < sc->e; i++) { + mp_copy_into(&tmp, &xk); + for (size_t j = i+1; j < sc->e; j++) + monty_mul_into(sc->mc, &tmp, &tmp, &tmp); + unsigned eq1 = mp_cmp_eq(&tmp, monty_identity(sc->mc)); + + if (i == 0) { + /* One special case: if x=0, then no power of x will ever + * equal 1, but we should still report success on the + * grounds that 0 does have a square root mod p. */ + *success = eq1 | mp_eq_integer(x, 0); + } else { + monty_mul_into(sc->mc, &tmp, toret, &power_of_zk); + mp_select_into(toret, &tmp, toret, eq1); + + monty_mul_into(sc->mc, &power_of_zk, + &power_of_zk, &power_of_zk); + + monty_mul_into(sc->mc, &tmp, &xk, &power_of_zk); + mp_select_into(&xk, &tmp, &xk, eq1); + } + } + + mp_free(scratch_to_free); + + return toret; +} + +mp_int *mp_random_bits_fn(size_t bits, random_read_fn_t random_read) +{ + size_t bytes = (bits + 7) / 8; + uint8_t *randbuf = snewn(bytes, uint8_t); + random_read(randbuf, bytes); + if (bytes) + randbuf[0] &= (2 << ((bits-1) & 7)) - 1; + mp_int *toret = mp_from_bytes_be(make_ptrlen(randbuf, bytes)); + smemclr(randbuf, bytes); + sfree(randbuf); + return toret; +} + +mp_int *mp_random_upto_fn(mp_int *limit, random_read_fn_t rf) +{ + /* + * It would be nice to generate our random numbers in such a way + * as to make every possible outcome literally equiprobable. But + * we can't do that in constant time, so we have to go for a very + * close approximation instead. I'm going to take the view that a + * factor of (1+2^-128) between the probabilities of two outcomes + * is acceptable on the grounds that you'd have to examine so many + * outputs to even detect it. + */ + mp_int *unreduced = mp_random_bits_fn(mp_max_bits(limit) + 128, rf); + mp_int *reduced = mp_mod(unreduced, limit); + mp_free(unreduced); + return reduced; +} + +mp_int *mp_random_in_range_fn(mp_int *lo, mp_int *hi, random_read_fn_t rf) +{ + mp_int *n_outcomes = mp_sub(hi, lo); + mp_int *addend = mp_random_upto_fn(n_outcomes, rf); + mp_int *result = mp_make_sized(hi->nw); + mp_add_into(result, addend, lo); + mp_free(addend); + mp_free(n_outcomes); + return result; +} diff --git a/crypto/prng.c b/crypto/prng.c new file mode 100644 index 00000000..247d1dcf --- /dev/null +++ b/crypto/prng.c @@ -0,0 +1,287 @@ +/* + * PuTTY's cryptographic pseudorandom number generator. + * + * This module just defines the PRNG object type and its methods. The + * usual global instance of it is managed by sshrand.c. + */ + +#include "putty.h" +#include "ssh.h" +#include "mpint_i.h" + +#ifdef PRNG_DIAGNOSTICS +#define prngdebug debug +#else +#define prngdebug(...) ((void)0) +#endif + +/* + * This random number generator is based on the 'Fortuna' design by + * Niels Ferguson and Bruce Schneier. The biggest difference is that I + * use SHA-256 in place of a block cipher: the generator side of the + * system works by computing HASH(key || counter) instead of + * ENCRYPT(counter, key). + * + * Rationale: the Fortuna description itself suggests that using + * SHA-256 would be nice but people wouldn't accept it because it's + * too slow - but PuTTY isn't a heavy enough user of random numbers to + * make that a serious worry. In fact even with SHA-256 this generator + * is faster than the one we previously used. Also the Fortuna + * description worries about periodic rekeying to avoid the barely + * detectable pattern of never repeating a cipher block - but with + * SHA-256, even that shouldn't be a worry, because the output + * 'blocks' are twice the size, and also SHA-256 has no guarantee of + * bijectivity, so it surely _could_ be possible to generate the same + * block from two counter values. Thirdly, Fortuna has to have a hash + * function anyway, for reseeding and entropy collection, so reusing + * the same one means it only depends on one underlying primitive and + * can be easily reinstantiated with a larger hash function if you + * decide you'd like to do that on a particular occasion. + */ + +#define NCOLLECTORS 32 +#define RESEED_DATA_SIZE 64 + +typedef struct prng_impl prng_impl; +struct prng_impl { + prng Prng; + + const ssh_hashalg *hashalg; + + /* + * Generation side: + * + * 'generator' is a hash object with the current key preloaded + * into it. The counter-mode generation is achieved by copying + * that hash object, appending the counter value to the copy, and + * calling ssh_hash_final. + */ + ssh_hash *generator; + BignumInt counter[128 / BIGNUM_INT_BITS]; + + /* + * When re-seeding the generator, you call prng_seed_begin(), + * which sets up a hash object in 'keymaker'. You write your new + * seed data into it (which you can do by calling put_data on the + * PRNG object itself) and then call prng_seed_finish(), which + * finalises this hash and uses the output to set up the new + * generator. + * + * The keymaker hash preimage includes the previous key, so if you + * just want to change keys for the sake of not keeping the same + * one for too long, you don't have to put any extra seed data in + * at all. + */ + ssh_hash *keymaker; + + /* + * Collection side: + * + * There are NCOLLECTORS hash objects collecting entropy. Each + * separately numbered entropy source puts its output into those + * hash objects in the order 0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,..., + * that is to say, each entropy source has a separate counter + * which is incremented every time that source generates an event, + * and the event data is added to the collector corresponding to + * the index of the lowest set bit in the current counter value. + * + * Whenever collector #0 has at least RESEED_DATA_SIZE bytes (and + * it's not at least 100ms since the last reseed), the PRNG is + * reseeded, with seed data on reseed #n taken from the first j + * collectors, where j is one more than the number of factors of 2 + * in n. That is, collector #0 is used in every reseed; #1 in + * every other one, #2 in every fourth, etc. + * + * 'until_reseed' counts the amount of data that still needs to be + * added to collector #0 before a reseed will be triggered. + */ + uint32_t source_counters[NOISE_MAX_SOURCES]; + ssh_hash *collectors[NCOLLECTORS]; + size_t until_reseed; + uint32_t reseeds; + uint64_t last_reseed_time; +}; + +static void prng_seed_BinarySink_write( + BinarySink *bs, const void *data, size_t len); + +prng *prng_new(const ssh_hashalg *hashalg) +{ + prng_impl *pi = snew(prng_impl); + + memset(pi, 0, sizeof(prng_impl)); + pi->hashalg = hashalg; + pi->keymaker = NULL; + pi->generator = NULL; + memset(pi->counter, 0, sizeof(pi->counter)); + for (size_t i = 0; i < NCOLLECTORS; i++) + pi->collectors[i] = ssh_hash_new(pi->hashalg); + pi->until_reseed = 0; + BinarySink_INIT(&pi->Prng, prng_seed_BinarySink_write); + + pi->Prng.savesize = pi->hashalg->hlen * 4; + + return &pi->Prng; +} + +void prng_free(prng *pr) +{ + prng_impl *pi = container_of(pr, prng_impl, Prng); + + smemclr(pi->counter, sizeof(pi->counter)); + for (size_t i = 0; i < NCOLLECTORS; i++) + ssh_hash_free(pi->collectors[i]); + if (pi->generator) + ssh_hash_free(pi->generator); + if (pi->keymaker) + ssh_hash_free(pi->keymaker); + smemclr(pi, sizeof(*pi)); + sfree(pi); +} + +void prng_seed_begin(prng *pr) +{ + prng_impl *pi = container_of(pr, prng_impl, Prng); + + assert(!pi->keymaker); + + prngdebug("prng: reseed begin\n"); + + /* + * Make a hash instance that will generate the key for the new one. + */ + if (pi->generator) { + pi->keymaker = pi->generator; + pi->generator = NULL; + } else { + pi->keymaker = ssh_hash_new(pi->hashalg); + } + + put_byte(pi->keymaker, 'R'); +} + +static void prng_seed_BinarySink_write( + BinarySink *bs, const void *data, size_t len) +{ + prng *pr = BinarySink_DOWNCAST(bs, prng); + prng_impl *pi = container_of(pr, prng_impl, Prng); + assert(pi->keymaker); + prngdebug("prng: got %"SIZEu" bytes of seed\n", len); + put_data(pi->keymaker, data, len); +} + +void prng_seed_finish(prng *pr) +{ + prng_impl *pi = container_of(pr, prng_impl, Prng); + unsigned char buf[MAX_HASH_LEN]; + + assert(pi->keymaker); + + prngdebug("prng: reseed finish\n"); + + /* + * Actually generate the key. + */ + ssh_hash_final(pi->keymaker, buf); + pi->keymaker = NULL; + + /* + * Load that key into a fresh hash instance, which will become the + * new generator. + */ + assert(!pi->generator); + pi->generator = ssh_hash_new(pi->hashalg); + put_data(pi->generator, buf, pi->hashalg->hlen); + + pi->until_reseed = RESEED_DATA_SIZE; + pi->last_reseed_time = prng_reseed_time_ms(); + + smemclr(buf, sizeof(buf)); +} + +static inline void prng_generate(prng_impl *pi, void *outbuf) +{ + ssh_hash *h = ssh_hash_copy(pi->generator); + + prngdebug("prng_generate\n"); + put_byte(h, 'G'); + for (unsigned i = 0; i < 128; i += 8) + put_byte(h, pi->counter[i/BIGNUM_INT_BITS] >> (i%BIGNUM_INT_BITS)); + BignumCarry c = 1; + for (unsigned i = 0; i < lenof(pi->counter); i++) + BignumADC(pi->counter[i], c, pi->counter[i], 0, c); + ssh_hash_final(h, outbuf); +} + +void prng_read(prng *pr, void *vout, size_t size) +{ + prng_impl *pi = container_of(pr, prng_impl, Prng); + unsigned char buf[MAX_HASH_LEN]; + + assert(!pi->keymaker); + + prngdebug("prng_read %"SIZEu"\n", size); + + uint8_t *out = (uint8_t *)vout; + while (size > 0) { + prng_generate(pi, buf); + size_t to_use = size > pi->hashalg->hlen ? pi->hashalg->hlen : size; + memcpy(out, buf, to_use); + out += to_use; + size -= to_use; + } + + smemclr(buf, sizeof(buf)); + + prng_seed_begin(&pi->Prng); + prng_seed_finish(&pi->Prng); +} + +void prng_add_entropy(prng *pr, unsigned source_id, ptrlen data) +{ + prng_impl *pi = container_of(pr, prng_impl, Prng); + + assert(source_id < NOISE_MAX_SOURCES); + uint32_t counter = ++pi->source_counters[source_id]; + + size_t index = 0; + while (index+1 < NCOLLECTORS && !(counter & 1)) { + counter >>= 1; + index++; + } + + prngdebug("prng_add_entropy source=%u size=%"SIZEu" -> collector %zi\n", + source_id, data.len, index); + + put_datapl(pi->collectors[index], data); + + if (index == 0) + pi->until_reseed = (pi->until_reseed < data.len ? 0 : + pi->until_reseed - data.len); + + if (pi->until_reseed == 0 && + prng_reseed_time_ms() - pi->last_reseed_time >= 100) { + prng_seed_begin(&pi->Prng); + + unsigned char buf[MAX_HASH_LEN]; + uint32_t reseed_index = ++pi->reseeds; + prngdebug("prng entropy reseed #%"PRIu32"\n", reseed_index); + for (size_t i = 0; i < NCOLLECTORS; i++) { + prngdebug("emptying collector %"SIZEu"\n", i); + ssh_hash_digest(pi->collectors[i], buf); + put_data(&pi->Prng, buf, pi->hashalg->hlen); + ssh_hash_reset(pi->collectors[i]); + if (reseed_index & 1) + break; + reseed_index >>= 1; + } + smemclr(buf, sizeof(buf)); + prng_seed_finish(&pi->Prng); + } +} + +size_t prng_seed_bits(prng *pr) +{ + prng_impl *pi = container_of(pr, prng_impl, Prng); + return pi->hashalg->hlen * 8; +} diff --git a/crypto/pubkey-pem.c b/crypto/pubkey-pem.c new file mode 100644 index 00000000..3eaa16aa --- /dev/null +++ b/crypto/pubkey-pem.c @@ -0,0 +1,32 @@ +/* + * Convenience functions to encrypt and decrypt OpenSSH PEM format for + * SSH-2 private key files. This uses triple-DES in SSH-2 style (one + * CBC layer), with three distinct keys, and an IV also generated from + * the passphrase. + */ + +#include "ssh.h" + +static ssh_cipher *des3_pubkey_ossh_cipher(const void *vkey, const void *viv) +{ + ssh_cipher *c = ssh_cipher_new(&ssh_3des_ssh2); + ssh_cipher_setkey(c, vkey); + ssh_cipher_setiv(c, viv); + return c; +} + +void des3_decrypt_pubkey_ossh(const void *vkey, const void *viv, + void *vblk, int len) +{ + ssh_cipher *c = des3_pubkey_ossh_cipher(vkey, viv); + ssh_cipher_decrypt(c, vblk, len); + ssh_cipher_free(c); +} + +void des3_encrypt_pubkey_ossh(const void *vkey, const void *viv, + void *vblk, int len) +{ + ssh_cipher *c = des3_pubkey_ossh_cipher(vkey, viv); + ssh_cipher_encrypt(c, vblk, len); + ssh_cipher_free(c); +} diff --git a/crypto/pubkey-ppk.c b/crypto/pubkey-ppk.c new file mode 100644 index 00000000..7ffb570a --- /dev/null +++ b/crypto/pubkey-ppk.c @@ -0,0 +1,29 @@ +/* + * Convenience functions to encrypt and decrypt PuTTY's own .PPK + * format for SSH-2 private key files, which uses 256-bit AES in CBC + * mode. + */ + +#include "ssh.h" + +static ssh_cipher *aes256_pubkey_cipher(const void *key, const void *iv) +{ + ssh_cipher *cipher = ssh_cipher_new(&ssh_aes256_cbc); + ssh_cipher_setkey(cipher, key); + ssh_cipher_setiv(cipher, iv); + return cipher; +} + +void aes256_encrypt_pubkey(const void *key, const void *iv, void *blk, int len) +{ + ssh_cipher *c = aes256_pubkey_cipher(key, iv); + ssh_cipher_encrypt(c, blk, len); + ssh_cipher_free(c); +} + +void aes256_decrypt_pubkey(const void *key, const void *iv, void *blk, int len) +{ + ssh_cipher *c = aes256_pubkey_cipher(key, iv); + ssh_cipher_decrypt(c, blk, len); + ssh_cipher_free(c); +} diff --git a/crypto/pubkey-ssh1.c b/crypto/pubkey-ssh1.c new file mode 100644 index 00000000..b3129e29 --- /dev/null +++ b/crypto/pubkey-ssh1.c @@ -0,0 +1,38 @@ +/* + * Convenience functions to encrypt and decrypt the standard format + * for SSH-1 private key files. This uses triple-DES in SSH-1 style + * (three separate CBC layers), but the same key is used for the first + * and third layers.CBC mode. + */ + +#include "ssh.h" + +static ssh_cipher *des3_pubkey_cipher(const void *vkey) +{ + ssh_cipher *c = ssh_cipher_new(&ssh_3des_ssh1); + uint8_t keys3[24], iv[8]; + + memcpy(keys3, vkey, 16); + memcpy(keys3 + 16, vkey, 8); + ssh_cipher_setkey(c, keys3); + smemclr(keys3, sizeof(keys3)); + + memset(iv, 0, 8); + ssh_cipher_setiv(c, iv); + + return c; +} + +void des3_decrypt_pubkey(const void *vkey, void *vblk, int len) +{ + ssh_cipher *c = des3_pubkey_cipher(vkey); + ssh_cipher_decrypt(c, vblk, len); + ssh_cipher_free(c); +} + +void des3_encrypt_pubkey(const void *vkey, void *vblk, int len) +{ + ssh_cipher *c = des3_pubkey_cipher(vkey); + ssh_cipher_encrypt(c, vblk, len); + ssh_cipher_free(c); +} diff --git a/crypto/rsa.c b/crypto/rsa.c new file mode 100644 index 00000000..e3dcbdda --- /dev/null +++ b/crypto/rsa.c @@ -0,0 +1,1109 @@ +/* + * RSA implementation for PuTTY. + */ + +#include +#include +#include +#include + +#include "ssh.h" +#include "mpint.h" +#include "misc.h" + +void BinarySource_get_rsa_ssh1_pub( + BinarySource *src, RSAKey *rsa, RsaSsh1Order order) +{ + unsigned bits; + mp_int *e, *m; + + bits = get_uint32(src); + if (order == RSA_SSH1_EXPONENT_FIRST) { + e = get_mp_ssh1(src); + m = get_mp_ssh1(src); + } else { + m = get_mp_ssh1(src); + e = get_mp_ssh1(src); + } + + if (rsa) { + rsa->bits = bits; + rsa->exponent = e; + rsa->modulus = m; + rsa->bytes = (mp_get_nbits(m) + 7) / 8; + } else { + mp_free(e); + mp_free(m); + } +} + +void BinarySource_get_rsa_ssh1_priv( + BinarySource *src, RSAKey *rsa) +{ + rsa->private_exponent = get_mp_ssh1(src); +} + +key_components *rsa_components(RSAKey *rsa) +{ + key_components *kc = key_components_new(); + key_components_add_text(kc, "key_type", "RSA"); + key_components_add_mp(kc, "public_modulus", rsa->modulus); + key_components_add_mp(kc, "public_exponent", rsa->exponent); + if (rsa->private_exponent) { + key_components_add_mp(kc, "private_exponent", rsa->private_exponent); + key_components_add_mp(kc, "private_p", rsa->p); + key_components_add_mp(kc, "private_q", rsa->q); + key_components_add_mp(kc, "private_inverse_q_mod_p", rsa->iqmp); + } + return kc; +} + +RSAKey *BinarySource_get_rsa_ssh1_priv_agent(BinarySource *src) +{ + RSAKey *rsa = snew(RSAKey); + memset(rsa, 0, sizeof(RSAKey)); + + get_rsa_ssh1_pub(src, rsa, RSA_SSH1_MODULUS_FIRST); + get_rsa_ssh1_priv(src, rsa); + + /* SSH-1 names p and q the other way round, i.e. we have the + * inverse of p mod q and not of q mod p. We swap the names, + * because our internal RSA wants iqmp. */ + rsa->iqmp = get_mp_ssh1(src); + rsa->q = get_mp_ssh1(src); + rsa->p = get_mp_ssh1(src); + + return rsa; +} + +bool rsa_ssh1_encrypt(unsigned char *data, int length, RSAKey *key) +{ + mp_int *b1, *b2; + int i; + unsigned char *p; + + if (key->bytes < length + 4) + return false; /* RSA key too short! */ + + memmove(data + key->bytes - length, data, length); + data[0] = 0; + data[1] = 2; + + size_t npad = key->bytes - length - 3; + /* + * Generate a sequence of nonzero padding bytes. We do this in a + * reasonably uniform way and without having to loop round + * retrying the random number generation, by first generating an + * integer in [0,2^n) for an appropriately large n; then we + * repeatedly multiply by 255 to give an integer in [0,255*2^n), + * extract the top 8 bits to give an integer in [0,255), and mask + * those bits off before multiplying up again for the next digit. + * This gives us a sequence of numbers in [0,255), and of course + * adding 1 to each of them gives numbers in [1,256) as we wanted. + * + * (You could imagine this being a sort of fixed-point operation: + * given a uniformly random binary _fraction_, multiplying it by k + * and subtracting off the integer part will yield you a sequence + * of integers each in [0,k). I'm just doing that scaled up by a + * power of 2 to avoid the fractions.) + */ + size_t random_bits = (npad + 16) * 8; + mp_int *randval = mp_new(random_bits + 8); + mp_int *tmp = mp_random_bits(random_bits); + mp_copy_into(randval, tmp); + mp_free(tmp); + for (i = 2; i < key->bytes - length - 1; i++) { + mp_mul_integer_into(randval, randval, 255); + uint8_t byte = mp_get_byte(randval, random_bits / 8); + assert(byte != 255); + data[i] = byte + 1; + mp_reduce_mod_2to(randval, random_bits); + } + mp_free(randval); + data[key->bytes - length - 1] = 0; + + b1 = mp_from_bytes_be(make_ptrlen(data, key->bytes)); + + b2 = mp_modpow(b1, key->exponent, key->modulus); + + p = data; + for (i = key->bytes; i--;) { + *p++ = mp_get_byte(b2, i); + } + + mp_free(b1); + mp_free(b2); + + return true; +} + +/* + * 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. + */ +static mp_int *crt_modpow(mp_int *base, mp_int *exp, mp_int *mod, + mp_int *p, mp_int *q, mp_int *iqmp) +{ + mp_int *pm1, *qm1, *pexp, *qexp, *presult, *qresult; + mp_int *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 = mp_copy(p); + mp_sub_integer_into(pm1, pm1, 1); + qm1 = mp_copy(q); + mp_sub_integer_into(qm1, qm1, 1); + pexp = mp_mod(exp, pm1); + qexp = mp_mod(exp, qm1); + + /* + * Do the two modpows. + */ + mp_int *base_mod_p = mp_mod(base, p); + presult = mp_modpow(base_mod_p, pexp, p); + mp_free(base_mod_p); + mp_int *base_mod_q = mp_mod(base, q); + qresult = mp_modpow(base_mod_q, qexp, q); + mp_free(base_mod_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 presult-qresult < 0, we add p to it to keep it positive.) + */ + unsigned presult_too_small = mp_cmp_hs(qresult, presult); + mp_cond_add_into(presult, presult, p, presult_too_small); + + diff = mp_sub(presult, qresult); + multiplier = mp_mul(iqmp, q); + ret0 = mp_mul(multiplier, diff); + mp_add_into(ret0, ret0, qresult); + + /* + * Finally, reduce the result mod n. + */ + ret = mp_mod(ret0, mod); + + /* + * Free all the intermediate results before returning. + */ + mp_free(pm1); + mp_free(qm1); + mp_free(pexp); + mp_free(qexp); + mp_free(presult); + mp_free(qresult); + mp_free(diff); + mp_free(multiplier); + mp_free(ret0); + + return ret; +} + +/* + * Wrapper on crt_modpow that looks up all the right values from an + * RSAKey. + */ +static mp_int *rsa_privkey_op(mp_int *input, RSAKey *key) +{ + return crt_modpow(input, key->private_exponent, + key->modulus, key->p, key->q, key->iqmp); +} + +mp_int *rsa_ssh1_decrypt(mp_int *input, RSAKey *key) +{ + return rsa_privkey_op(input, key); +} + +bool rsa_ssh1_decrypt_pkcs1(mp_int *input, RSAKey *key, + strbuf *outbuf) +{ + strbuf *data = strbuf_new_nm(); + bool success = false; + BinarySource src[1]; + + { + mp_int *b = rsa_ssh1_decrypt(input, key); + for (size_t i = (mp_get_nbits(key->modulus) + 7) / 8; i-- > 0 ;) { + put_byte(data, mp_get_byte(b, i)); + } + mp_free(b); + } + + BinarySource_BARE_INIT(src, data->u, data->len); + + /* Check PKCS#1 formatting prefix */ + if (get_byte(src) != 0) goto out; + if (get_byte(src) != 2) goto out; + while (1) { + unsigned char byte = get_byte(src); + if (get_err(src)) goto out; + if (byte == 0) + break; + } + + /* Everything else is the payload */ + success = true; + put_data(outbuf, get_ptr(src), get_avail(src)); + + out: + strbuf_free(data); + return success; +} + +static void append_hex_to_strbuf(strbuf *sb, mp_int *x) +{ + if (sb->len > 0) + put_byte(sb, ','); + put_data(sb, "0x", 2); + char *hex = mp_get_hex(x); + size_t hexlen = strlen(hex); + put_data(sb, hex, hexlen); + smemclr(hex, hexlen); + sfree(hex); +} + +char *rsastr_fmt(RSAKey *key) +{ + strbuf *sb = strbuf_new(); + + append_hex_to_strbuf(sb, key->exponent); + append_hex_to_strbuf(sb, key->modulus); + + return strbuf_to_str(sb); +} + +/* + * Generate a fingerprint string for the key. Compatible with the + * OpenSSH fingerprint code. + */ +char *rsa_ssh1_fingerprint(RSAKey *key) +{ + unsigned char digest[16]; + strbuf *out; + int i; + + /* + * The hash preimage for SSH-1 key fingerprinting consists of the + * modulus and exponent _without_ any preceding length field - + * just the minimum number of bytes to represent each integer, + * stored big-endian, concatenated with no marker at the division + * between them. + */ + + ssh_hash *hash = ssh_hash_new(&ssh_md5); + for (size_t i = (mp_get_nbits(key->modulus) + 7) / 8; i-- > 0 ;) + put_byte(hash, mp_get_byte(key->modulus, i)); + for (size_t i = (mp_get_nbits(key->exponent) + 7) / 8; i-- > 0 ;) + put_byte(hash, mp_get_byte(key->exponent, i)); + ssh_hash_final(hash, digest); + + out = strbuf_new(); + strbuf_catf(out, "%"SIZEu" ", mp_get_nbits(key->modulus)); + for (i = 0; i < 16; i++) + strbuf_catf(out, "%s%02x", i ? ":" : "", digest[i]); + if (key->comment) + strbuf_catf(out, " %s", key->comment); + return strbuf_to_str(out); +} + +/* + * Wrap the output of rsa_ssh1_fingerprint up into the same kind of + * structure that comes from ssh2_all_fingerprints. + */ +char **rsa_ssh1_fake_all_fingerprints(RSAKey *key) +{ + char **ret = snewn(SSH_N_FPTYPES, char *); + for (unsigned i = 0; i < SSH_N_FPTYPES; i++) + ret[i] = NULL; + ret[SSH_FPTYPE_MD5] = rsa_ssh1_fingerprint(key); + return ret; +} + +/* + * 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. + */ +bool rsa_verify(RSAKey *key) +{ + mp_int *n, *ed, *pm1, *qm1; + unsigned ok = 1; + + /* Preliminary checks: p,q can't be 0 or 1. (Of course no other + * very small value is any good either, but these are the values + * we _must_ check for to avoid assertion failures further down + * this function.) */ + if (!(mp_hs_integer(key->p, 2) & mp_hs_integer(key->q, 2))) + return false; + + /* n must equal pq. */ + n = mp_mul(key->p, key->q); + ok &= mp_cmp_eq(n, key->modulus); + mp_free(n); + + /* e * d must be congruent to 1, modulo (p-1) and modulo (q-1). */ + pm1 = mp_copy(key->p); + mp_sub_integer_into(pm1, pm1, 1); + ed = mp_modmul(key->exponent, key->private_exponent, pm1); + mp_free(pm1); + ok &= mp_eq_integer(ed, 1); + mp_free(ed); + + qm1 = mp_copy(key->q); + mp_sub_integer_into(qm1, qm1, 1); + ed = mp_modmul(key->exponent, key->private_exponent, qm1); + mp_free(qm1); + ok &= mp_eq_integer(ed, 1); + mp_free(ed); + + /* + * 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. + */ + mp_int *p_new = mp_max(key->p, key->q); + mp_int *q_new = mp_min(key->p, key->q); + mp_free(key->p); + mp_free(key->q); + mp_free(key->iqmp); + key->p = p_new; + key->q = q_new; + key->iqmp = mp_invert(key->q, key->p); + + return ok; +} + +void rsa_ssh1_public_blob(BinarySink *bs, RSAKey *key, + RsaSsh1Order order) +{ + put_uint32(bs, mp_get_nbits(key->modulus)); + if (order == RSA_SSH1_EXPONENT_FIRST) { + put_mp_ssh1(bs, key->exponent); + put_mp_ssh1(bs, key->modulus); + } else { + put_mp_ssh1(bs, key->modulus); + put_mp_ssh1(bs, key->exponent); + } +} + +void rsa_ssh1_private_blob_agent(BinarySink *bs, RSAKey *key) +{ + rsa_ssh1_public_blob(bs, key, RSA_SSH1_MODULUS_FIRST); + put_mp_ssh1(bs, key->private_exponent); + put_mp_ssh1(bs, key->iqmp); + put_mp_ssh1(bs, key->q); + put_mp_ssh1(bs, key->p); +} + +/* Given an SSH-1 public key blob, determine its length. */ +int rsa_ssh1_public_blob_len(ptrlen data) +{ + BinarySource src[1]; + + BinarySource_BARE_INIT_PL(src, data); + + /* Expect a length word, then exponent and modulus. (It doesn't + * even matter which order.) */ + get_uint32(src); + mp_free(get_mp_ssh1(src)); + mp_free(get_mp_ssh1(src)); + + if (get_err(src)) + return -1; + + /* Return the number of bytes consumed. */ + return src->pos; +} + +void freersapriv(RSAKey *key) +{ + if (key->private_exponent) { + mp_free(key->private_exponent); + key->private_exponent = NULL; + } + if (key->p) { + mp_free(key->p); + key->p = NULL; + } + if (key->q) { + mp_free(key->q); + key->q = NULL; + } + if (key->iqmp) { + mp_free(key->iqmp); + key->iqmp = NULL; + } +} + +void freersakey(RSAKey *key) +{ + freersapriv(key); + if (key->modulus) { + mp_free(key->modulus); + key->modulus = NULL; + } + if (key->exponent) { + mp_free(key->exponent); + key->exponent = NULL; + } + if (key->comment) { + sfree(key->comment); + key->comment = NULL; + } +} + +/* ---------------------------------------------------------------------- + * Implementation of the ssh-rsa signing key type family. + */ + +struct ssh2_rsa_extra { + unsigned signflags; +}; + +static void rsa2_freekey(ssh_key *key); /* forward reference */ + +static ssh_key *rsa2_new_pub(const ssh_keyalg *self, ptrlen data) +{ + BinarySource src[1]; + RSAKey *rsa; + + BinarySource_BARE_INIT_PL(src, data); + if (!ptrlen_eq_string(get_string(src), "ssh-rsa")) + return NULL; + + rsa = snew(RSAKey); + rsa->sshk.vt = self; + rsa->exponent = get_mp_ssh2(src); + rsa->modulus = get_mp_ssh2(src); + rsa->private_exponent = NULL; + rsa->p = rsa->q = rsa->iqmp = NULL; + rsa->comment = NULL; + + if (get_err(src)) { + rsa2_freekey(&rsa->sshk); + return NULL; + } + + return &rsa->sshk; +} + +static void rsa2_freekey(ssh_key *key) +{ + RSAKey *rsa = container_of(key, RSAKey, sshk); + freersakey(rsa); + sfree(rsa); +} + +static char *rsa2_cache_str(ssh_key *key) +{ + RSAKey *rsa = container_of(key, RSAKey, sshk); + return rsastr_fmt(rsa); +} + +static key_components *rsa2_components(ssh_key *key) +{ + RSAKey *rsa = container_of(key, RSAKey, sshk); + return rsa_components(rsa); +} + +static void rsa2_public_blob(ssh_key *key, BinarySink *bs) +{ + RSAKey *rsa = container_of(key, RSAKey, sshk); + + put_stringz(bs, "ssh-rsa"); + put_mp_ssh2(bs, rsa->exponent); + put_mp_ssh2(bs, rsa->modulus); +} + +static void rsa2_private_blob(ssh_key *key, BinarySink *bs) +{ + RSAKey *rsa = container_of(key, RSAKey, sshk); + + put_mp_ssh2(bs, rsa->private_exponent); + put_mp_ssh2(bs, rsa->p); + put_mp_ssh2(bs, rsa->q); + put_mp_ssh2(bs, rsa->iqmp); +} + +static ssh_key *rsa2_new_priv(const ssh_keyalg *self, + ptrlen pub, ptrlen priv) +{ + BinarySource src[1]; + ssh_key *sshk; + RSAKey *rsa; + + sshk = rsa2_new_pub(self, pub); + if (!sshk) + return NULL; + + rsa = container_of(sshk, RSAKey, sshk); + BinarySource_BARE_INIT_PL(src, priv); + rsa->private_exponent = get_mp_ssh2(src); + rsa->p = get_mp_ssh2(src); + rsa->q = get_mp_ssh2(src); + rsa->iqmp = get_mp_ssh2(src); + + if (get_err(src) || !rsa_verify(rsa)) { + rsa2_freekey(&rsa->sshk); + return NULL; + } + + return &rsa->sshk; +} + +static ssh_key *rsa2_new_priv_openssh(const ssh_keyalg *self, + BinarySource *src) +{ + RSAKey *rsa; + + rsa = snew(RSAKey); + rsa->sshk.vt = &ssh_rsa; + rsa->comment = NULL; + + rsa->modulus = get_mp_ssh2(src); + rsa->exponent = get_mp_ssh2(src); + rsa->private_exponent = get_mp_ssh2(src); + rsa->iqmp = get_mp_ssh2(src); + rsa->p = get_mp_ssh2(src); + rsa->q = get_mp_ssh2(src); + + if (get_err(src) || !rsa_verify(rsa)) { + rsa2_freekey(&rsa->sshk); + return NULL; + } + + return &rsa->sshk; +} + +static void rsa2_openssh_blob(ssh_key *key, BinarySink *bs) +{ + RSAKey *rsa = container_of(key, RSAKey, sshk); + + put_mp_ssh2(bs, rsa->modulus); + put_mp_ssh2(bs, rsa->exponent); + put_mp_ssh2(bs, rsa->private_exponent); + put_mp_ssh2(bs, rsa->iqmp); + put_mp_ssh2(bs, rsa->p); + put_mp_ssh2(bs, rsa->q); +} + +static int rsa2_pubkey_bits(const ssh_keyalg *self, ptrlen pub) +{ + ssh_key *sshk; + RSAKey *rsa; + int ret; + + sshk = rsa2_new_pub(self, pub); + if (!sshk) + return -1; + + rsa = container_of(sshk, RSAKey, sshk); + ret = mp_get_nbits(rsa->modulus); + rsa2_freekey(&rsa->sshk); + + return ret; +} + +static inline const ssh_hashalg *rsa2_hash_alg_for_flags( + unsigned flags, const char **protocol_id_out) +{ + const ssh_hashalg *halg; + const char *protocol_id; + + if (flags & SSH_AGENT_RSA_SHA2_256) { + halg = &ssh_sha256; + protocol_id = "rsa-sha2-256"; + } else if (flags & SSH_AGENT_RSA_SHA2_512) { + halg = &ssh_sha512; + protocol_id = "rsa-sha2-512"; + } else { + halg = &ssh_sha1; + protocol_id = "ssh-rsa"; + } + + if (protocol_id_out) + *protocol_id_out = protocol_id; + + return halg; +} + +static inline ptrlen rsa_pkcs1_prefix_for_hash(const ssh_hashalg *halg) +{ + if (halg == &ssh_sha1) { + /* + * This is the magic ASN.1/DER prefix that goes in the decoded + * signature, between the string of FFs and the actual SHA-1 + * 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 sha1_asn1_prefix[] = { + 0x00, 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2B, + 0x0E, 0x03, 0x02, 0x1A, 0x05, 0x00, 0x04, 0x14, + }; + return PTRLEN_FROM_CONST_BYTES(sha1_asn1_prefix); + } + + if (halg == &ssh_sha256) { + /* + * A similar piece of ASN.1 used for signatures using SHA-256, + * in the same format but differing only in various length + * fields and OID. + */ + static const unsigned char sha256_asn1_prefix[] = { + 0x00, 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, + 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, + 0x05, 0x00, 0x04, 0x20, + }; + return PTRLEN_FROM_CONST_BYTES(sha256_asn1_prefix); + } + + if (halg == &ssh_sha512) { + /* + * And one more for SHA-512. + */ + static const unsigned char sha512_asn1_prefix[] = { + 0x00, 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, + 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, + 0x05, 0x00, 0x04, 0x40, + }; + return PTRLEN_FROM_CONST_BYTES(sha512_asn1_prefix); + } + + unreachable("bad hash algorithm for RSA PKCS#1"); +} + +static inline size_t rsa_pkcs1_length_of_fixed_parts(const ssh_hashalg *halg) +{ + ptrlen asn1_prefix = rsa_pkcs1_prefix_for_hash(halg); + return halg->hlen + asn1_prefix.len + 2; +} + +static unsigned char *rsa_pkcs1_signature_string( + size_t nbytes, const ssh_hashalg *halg, ptrlen data) +{ + size_t fixed_parts = rsa_pkcs1_length_of_fixed_parts(halg); + assert(nbytes >= fixed_parts); + size_t padding = nbytes - fixed_parts; + + ptrlen asn1_prefix = rsa_pkcs1_prefix_for_hash(halg); + + unsigned char *bytes = snewn(nbytes, unsigned char); + + bytes[0] = 0; + bytes[1] = 1; + + memset(bytes + 2, 0xFF, padding); + + memcpy(bytes + 2 + padding, asn1_prefix.ptr, asn1_prefix.len); + + ssh_hash *h = ssh_hash_new(halg); + put_datapl(h, data); + ssh_hash_final(h, bytes + 2 + padding + asn1_prefix.len); + + return bytes; +} + +static bool rsa2_verify(ssh_key *key, ptrlen sig, ptrlen data) +{ + RSAKey *rsa = container_of(key, RSAKey, sshk); + BinarySource src[1]; + ptrlen type, in_pl; + mp_int *in, *out; + + const struct ssh2_rsa_extra *extra = + (const struct ssh2_rsa_extra *)key->vt->extra; + + const ssh_hashalg *halg = rsa2_hash_alg_for_flags(extra->signflags, NULL); + + /* Start by making sure the key is even long enough to encode a + * signature. If not, everything fails to verify. */ + size_t nbytes = (mp_get_nbits(rsa->modulus) + 7) / 8; + if (nbytes < rsa_pkcs1_length_of_fixed_parts(halg)) + return false; + + BinarySource_BARE_INIT_PL(src, sig); + type = get_string(src); + /* + * RFC 4253 section 6.6: the signature integer in an ssh-rsa + * signature is 'without lengths or padding'. That is, we _don't_ + * expect the usual leading zero byte if the topmost bit of the + * first byte is set. (However, because of the possibility of + * BUG_SSH2_RSA_PADDING at the other end, we tolerate it if it's + * there.) So we can't use get_mp_ssh2, which enforces that + * leading-byte scheme; instead we use get_string and + * mp_from_bytes_be, which will tolerate anything. + */ + in_pl = get_string(src); + if (get_err(src) || !ptrlen_eq_string(type, key->vt->ssh_id)) + return false; + + in = mp_from_bytes_be(in_pl); + out = mp_modpow(in, rsa->exponent, rsa->modulus); + mp_free(in); + + unsigned diff = 0; + + unsigned char *bytes = rsa_pkcs1_signature_string(nbytes, halg, data); + for (size_t i = 0; i < nbytes; i++) + diff |= bytes[nbytes-1 - i] ^ mp_get_byte(out, i); + smemclr(bytes, nbytes); + sfree(bytes); + mp_free(out); + + return diff == 0; +} + +static void rsa2_sign(ssh_key *key, ptrlen data, + unsigned flags, BinarySink *bs) +{ + RSAKey *rsa = container_of(key, RSAKey, sshk); + unsigned char *bytes; + size_t nbytes; + mp_int *in, *out; + const ssh_hashalg *halg; + const char *sign_alg_name; + + const struct ssh2_rsa_extra *extra = + (const struct ssh2_rsa_extra *)key->vt->extra; + flags |= extra->signflags; + + halg = rsa2_hash_alg_for_flags(flags, &sign_alg_name); + + nbytes = (mp_get_nbits(rsa->modulus) + 7) / 8; + + bytes = rsa_pkcs1_signature_string(nbytes, halg, data); + in = mp_from_bytes_be(make_ptrlen(bytes, nbytes)); + smemclr(bytes, nbytes); + sfree(bytes); + + out = rsa_privkey_op(in, rsa); + mp_free(in); + + put_stringz(bs, sign_alg_name); + nbytes = (mp_get_nbits(out) + 7) / 8; + put_uint32(bs, nbytes); + for (size_t i = 0; i < nbytes; i++) + put_byte(bs, mp_get_byte(out, nbytes - 1 - i)); + + mp_free(out); +} + +static char *rsa2_invalid(ssh_key *key, unsigned flags) +{ + RSAKey *rsa = container_of(key, RSAKey, sshk); + size_t bits = mp_get_nbits(rsa->modulus), nbytes = (bits + 7) / 8; + const char *sign_alg_name; + const ssh_hashalg *halg = rsa2_hash_alg_for_flags(flags, &sign_alg_name); + if (nbytes < rsa_pkcs1_length_of_fixed_parts(halg)) { + return dupprintf( + "%"SIZEu"-bit RSA key is too short to generate %s signatures", + bits, sign_alg_name); + } + + return NULL; +} + +static const struct ssh2_rsa_extra + rsa_extra = { 0 }, + rsa_sha256_extra = { SSH_AGENT_RSA_SHA2_256 }, + rsa_sha512_extra = { SSH_AGENT_RSA_SHA2_512 }; + +#define COMMON_KEYALG_FIELDS \ + .new_pub = rsa2_new_pub, \ + .new_priv = rsa2_new_priv, \ + .new_priv_openssh = rsa2_new_priv_openssh, \ + .freekey = rsa2_freekey, \ + .invalid = rsa2_invalid, \ + .sign = rsa2_sign, \ + .verify = rsa2_verify, \ + .public_blob = rsa2_public_blob, \ + .private_blob = rsa2_private_blob, \ + .openssh_blob = rsa2_openssh_blob, \ + .cache_str = rsa2_cache_str, \ + .components = rsa2_components, \ + .pubkey_bits = rsa2_pubkey_bits, \ + .cache_id = "rsa2" + +const ssh_keyalg ssh_rsa = { + COMMON_KEYALG_FIELDS, + .ssh_id = "ssh-rsa", + .supported_flags = SSH_AGENT_RSA_SHA2_256 | SSH_AGENT_RSA_SHA2_512, + .extra = &rsa_extra, +}; + +const ssh_keyalg ssh_rsa_sha256 = { + COMMON_KEYALG_FIELDS, + .ssh_id = "rsa-sha2-256", + .supported_flags = 0, + .extra = &rsa_sha256_extra, +}; + +const ssh_keyalg ssh_rsa_sha512 = { + COMMON_KEYALG_FIELDS, + .ssh_id = "rsa-sha2-512", + .supported_flags = 0, + .extra = &rsa_sha512_extra, +}; + +RSAKey *ssh_rsakex_newkey(ptrlen data) +{ + ssh_key *sshk = rsa2_new_pub(&ssh_rsa, data); + if (!sshk) + return NULL; + return container_of(sshk, RSAKey, sshk); +} + +void ssh_rsakex_freekey(RSAKey *key) +{ + rsa2_freekey(&key->sshk); +} + +int ssh_rsakex_klen(RSAKey *rsa) +{ + return mp_get_nbits(rsa->modulus); +} + +static void oaep_mask(const ssh_hashalg *h, void *seed, int seedlen, + void *vdata, int datalen) +{ + unsigned char *data = (unsigned char *)vdata; + unsigned count = 0; + + ssh_hash *s = ssh_hash_new(h); + + while (datalen > 0) { + int i, max = (datalen > h->hlen ? h->hlen : datalen); + unsigned char hash[MAX_HASH_LEN]; + + ssh_hash_reset(s); + assert(h->hlen <= MAX_HASH_LEN); + put_data(s, seed, seedlen); + put_uint32(s, count); + ssh_hash_digest(s, hash); + count++; + + for (i = 0; i < max; i++) + data[i] ^= hash[i]; + + data += max; + datalen -= max; + } + + ssh_hash_free(s); +} + +strbuf *ssh_rsakex_encrypt(RSAKey *rsa, const ssh_hashalg *h, ptrlen in) +{ + mp_int *b1, *b2; + 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 + mp_get_nbits(rsa->modulus)) / 8; + + /* The length of the input data must be at most k - 2hLen - 2. */ + assert(in.len > 0 && in.len <= k - 2*HLEN - 2); + + /* The length of the output data wants to be precisely k. */ + strbuf *toret = strbuf_new_nm(); + int outlen = k; + unsigned char *out = strbuf_append(toret, outlen); + + /* + * 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. */ + random_read(out + 1, HLEN); + /* At position 1+HLEN, the data block DB, consisting of: */ + /* The hash of the label (we only support an empty label here) */ + hash_simple(h, PTRLEN_LITERAL(""), 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 - in.len - 1] = 1; + memcpy(out + outlen - in.len, in.ptr, in.len); + + /* + * 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 = mp_from_bytes_be(make_ptrlen(out, outlen)); + b2 = mp_modpow(b1, rsa->exponent, rsa->modulus); + p = (char *)out; + for (i = outlen; i--;) { + *p++ = mp_get_byte(b2, i); + } + mp_free(b1); + mp_free(b2); + + /* + * And we're done. + */ + return toret; +} + +mp_int *ssh_rsakex_decrypt( + RSAKey *rsa, const ssh_hashalg *h, ptrlen ciphertext) +{ + mp_int *b1, *b2; + int outlen, i; + unsigned char *out; + unsigned char labelhash[64]; + BinarySource src[1]; + const int HLEN = h->hlen; + + /* + * Decryption side of the RSA key exchange operation. + */ + + /* The length of the encrypted data should be exactly the length + * in octets of the RSA modulus.. */ + outlen = (7 + mp_get_nbits(rsa->modulus)) / 8; + if (ciphertext.len != outlen) + return NULL; + + /* Do the RSA decryption, and extract the result into a byte array. */ + b1 = mp_from_bytes_be(ciphertext); + b2 = rsa_privkey_op(b1, rsa); + out = snewn(outlen, unsigned char); + for (i = 0; i < outlen; i++) + out[i] = mp_get_byte(b2, outlen-1-i); + mp_free(b1); + mp_free(b2); + + /* Do the OAEP masking operations, in the reverse order from encryption */ + oaep_mask(h, out+HLEN+1, outlen-HLEN-1, out+1, HLEN); + oaep_mask(h, out+1, HLEN, out+HLEN+1, outlen-HLEN-1); + + /* Check the leading byte is zero. */ + if (out[0] != 0) { + sfree(out); + return NULL; + } + /* Check the label hash at position 1+HLEN */ + assert(HLEN <= lenof(labelhash)); + hash_simple(h, PTRLEN_LITERAL(""), labelhash); + if (memcmp(out + HLEN + 1, labelhash, HLEN)) { + sfree(out); + return NULL; + } + /* Expect zero bytes followed by a 1 byte */ + for (i = 1 + 2 * HLEN; i < outlen; i++) { + if (out[i] == 1) { + i++; /* skip over the 1 byte */ + break; + } else if (out[i] != 0) { + sfree(out); + return NULL; + } + } + /* And what's left is the input message data, which should be + * encoded as an ordinary SSH-2 mpint. */ + BinarySource_BARE_INIT(src, out + i, outlen - i); + b1 = get_mp_ssh2(src); + sfree(out); + if (get_err(src) || get_avail(src) != 0) { + mp_free(b1); + return NULL; + } + + /* Success! */ + return b1; +} + +static const struct ssh_rsa_kex_extra ssh_rsa_kex_extra_sha1 = { 1024 }; +static const struct ssh_rsa_kex_extra ssh_rsa_kex_extra_sha256 = { 2048 }; + +static const ssh_kex ssh_rsa_kex_sha1 = { + "rsa1024-sha1", NULL, KEXTYPE_RSA, + &ssh_sha1, &ssh_rsa_kex_extra_sha1, +}; + +static const ssh_kex ssh_rsa_kex_sha256 = { + "rsa2048-sha256", NULL, KEXTYPE_RSA, + &ssh_sha256, &ssh_rsa_kex_extra_sha256, +}; + +static const ssh_kex *const rsa_kex_list[] = { + &ssh_rsa_kex_sha256, + &ssh_rsa_kex_sha1 +}; + +const ssh_kexes ssh_rsa_kex = { lenof(rsa_kex_list), rsa_kex_list }; diff --git a/crypto/sha1.c b/crypto/sha1.c new file mode 100644 index 00000000..536d474f --- /dev/null +++ b/crypto/sha1.c @@ -0,0 +1,933 @@ +/* + * SHA-1 algorithm as described at + * + * http://csrc.nist.gov/cryptval/shs.html + */ + +#include "ssh.h" +#include + +/* + * Start by deciding whether we can support hardware SHA at all. + */ +#define HW_SHA1_NONE 0 +#define HW_SHA1_NI 1 +#define HW_SHA1_NEON 2 + +#ifdef _FORCE_SHA_NI +# define HW_SHA1 HW_SHA1_NI +#elif defined(__clang__) +# if __has_attribute(target) && __has_include() && \ + (defined(__x86_64__) || defined(__i386)) +# define HW_SHA1 HW_SHA1_NI +# endif +#elif defined(__GNUC__) +# if (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 9)) && \ + (defined(__x86_64__) || defined(__i386)) +# define HW_SHA1 HW_SHA1_NI +# endif +#elif defined (_MSC_VER) +# if (defined(_M_X64) || defined(_M_IX86)) && _MSC_FULL_VER >= 150030729 +# define HW_SHA1 HW_SHA1_NI +# endif +#endif + +#ifdef _FORCE_SHA_NEON +# define HW_SHA1 HW_SHA1_NEON +#elif defined __BYTE_ORDER__ && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + /* Arm can potentially support both endiannesses, but this code + * hasn't been tested on anything but little. If anyone wants to + * run big-endian, they'll need to fix it first. */ +#elif defined __ARM_FEATURE_CRYPTO + /* If the Arm crypto extension is available already, we can + * support NEON SHA without having to enable anything by hand */ +# define HW_SHA1 HW_SHA1_NEON +#elif defined(__clang__) +# if __has_attribute(target) && __has_include() && \ + (defined(__aarch64__)) + /* clang can enable the crypto extension in AArch64 using + * __attribute__((target)) */ +# define HW_SHA1 HW_SHA1_NEON +# define USE_CLANG_ATTR_TARGET_AARCH64 +# endif +#elif defined _MSC_VER + /* Visual Studio supports the crypto extension when targeting + * AArch64, but as of VS2017, the AArch32 header doesn't quite + * manage it (declaring the shae/shad intrinsics without a round + * key operand). */ +# if defined _M_ARM64 +# define HW_SHA1 HW_SHA1_NEON +# if defined _M_ARM64 +# define USE_ARM64_NEON_H /* unusual header name in this case */ +# endif +# endif +#endif + +#if defined _FORCE_SOFTWARE_SHA || !defined HW_SHA1 +# undef HW_SHA1 +# define HW_SHA1 HW_SHA1_NONE +#endif + +/* + * The actual query function that asks if hardware acceleration is + * available. + */ +static bool sha1_hw_available(void); + +/* + * The top-level selection function, caching the results of + * sha1_hw_available() so it only has to run once. + */ +static bool sha1_hw_available_cached(void) +{ + static bool initialised = false; + static bool hw_available; + if (!initialised) { + hw_available = sha1_hw_available(); + initialised = true; + } + return hw_available; +} + +static ssh_hash *sha1_select(const ssh_hashalg *alg) +{ + const ssh_hashalg *real_alg = + sha1_hw_available_cached() ? &ssh_sha1_hw : &ssh_sha1_sw; + + return ssh_hash_new(real_alg); +} + +const ssh_hashalg ssh_sha1 = { + .new = sha1_select, + .hlen = 20, + .blocklen = 64, + HASHALG_NAMES_ANNOTATED("SHA-1", "dummy selector vtable"), +}; + +/* ---------------------------------------------------------------------- + * Definitions likely to be helpful to multiple implementations. + */ + +static const uint32_t sha1_initial_state[] = { + 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0, +}; + +#define SHA1_ROUNDS_PER_STAGE 20 +#define SHA1_STAGE0_CONSTANT 0x5a827999 +#define SHA1_STAGE1_CONSTANT 0x6ed9eba1 +#define SHA1_STAGE2_CONSTANT 0x8f1bbcdc +#define SHA1_STAGE3_CONSTANT 0xca62c1d6 +#define SHA1_ROUNDS (4 * SHA1_ROUNDS_PER_STAGE) + +typedef struct sha1_block sha1_block; +struct sha1_block { + uint8_t block[64]; + size_t used; + uint64_t len; +}; + +static inline void sha1_block_setup(sha1_block *blk) +{ + blk->used = 0; + blk->len = 0; +} + +static inline bool sha1_block_write( + sha1_block *blk, const void **vdata, size_t *len) +{ + size_t blkleft = sizeof(blk->block) - blk->used; + size_t chunk = *len < blkleft ? *len : blkleft; + + const uint8_t *p = *vdata; + memcpy(blk->block + blk->used, p, chunk); + *vdata = p + chunk; + *len -= chunk; + blk->used += chunk; + blk->len += chunk; + + if (blk->used == sizeof(blk->block)) { + blk->used = 0; + return true; + } + + return false; +} + +static inline void sha1_block_pad(sha1_block *blk, BinarySink *bs) +{ + uint64_t final_len = blk->len << 3; + size_t pad = 1 + (63 & (55 - blk->used)); + + put_byte(bs, 0x80); + for (size_t i = 1; i < pad; i++) + put_byte(bs, 0); + put_uint64(bs, final_len); + + assert(blk->used == 0 && "Should have exactly hit a block boundary"); +} + +/* ---------------------------------------------------------------------- + * Software implementation of SHA-1. + */ + +static inline uint32_t rol(uint32_t x, unsigned y) +{ + return (x << (31 & y)) | (x >> (31 & -y)); +} + +static inline uint32_t Ch(uint32_t ctrl, uint32_t if1, uint32_t if0) +{ + return if0 ^ (ctrl & (if1 ^ if0)); +} + +static inline uint32_t Maj(uint32_t x, uint32_t y, uint32_t z) +{ + return (x & y) | (z & (x | y)); +} + +static inline uint32_t Par(uint32_t x, uint32_t y, uint32_t z) +{ + return (x ^ y ^ z); +} + +static inline void sha1_sw_round( + unsigned round_index, const uint32_t *schedule, + uint32_t *a, uint32_t *b, uint32_t *c, uint32_t *d, uint32_t *e, + uint32_t f, uint32_t constant) +{ + *e = rol(*a, 5) + f + *e + schedule[round_index] + constant; + *b = rol(*b, 30); +} + +static void sha1_sw_block(uint32_t *core, const uint8_t *block) +{ + uint32_t w[SHA1_ROUNDS]; + uint32_t a,b,c,d,e; + + for (size_t t = 0; t < 16; t++) + w[t] = GET_32BIT_MSB_FIRST(block + 4*t); + + for (size_t t = 16; t < SHA1_ROUNDS; t++) + w[t] = rol(w[t - 3] ^ w[t - 8] ^ w[t - 14] ^ w[t - 16], 1); + + a = core[0]; b = core[1]; c = core[2]; d = core[3]; + e = core[4]; + + size_t t = 0; + for (size_t u = 0; u < SHA1_ROUNDS_PER_STAGE/5; u++) { + sha1_sw_round(t++,w, &a,&b,&c,&d,&e, Ch(b,c,d), SHA1_STAGE0_CONSTANT); + sha1_sw_round(t++,w, &e,&a,&b,&c,&d, Ch(a,b,c), SHA1_STAGE0_CONSTANT); + sha1_sw_round(t++,w, &d,&e,&a,&b,&c, Ch(e,a,b), SHA1_STAGE0_CONSTANT); + sha1_sw_round(t++,w, &c,&d,&e,&a,&b, Ch(d,e,a), SHA1_STAGE0_CONSTANT); + sha1_sw_round(t++,w, &b,&c,&d,&e,&a, Ch(c,d,e), SHA1_STAGE0_CONSTANT); + } + for (size_t u = 0; u < SHA1_ROUNDS_PER_STAGE/5; u++) { + sha1_sw_round(t++,w, &a,&b,&c,&d,&e, Par(b,c,d), SHA1_STAGE1_CONSTANT); + sha1_sw_round(t++,w, &e,&a,&b,&c,&d, Par(a,b,c), SHA1_STAGE1_CONSTANT); + sha1_sw_round(t++,w, &d,&e,&a,&b,&c, Par(e,a,b), SHA1_STAGE1_CONSTANT); + sha1_sw_round(t++,w, &c,&d,&e,&a,&b, Par(d,e,a), SHA1_STAGE1_CONSTANT); + sha1_sw_round(t++,w, &b,&c,&d,&e,&a, Par(c,d,e), SHA1_STAGE1_CONSTANT); + } + for (size_t u = 0; u < SHA1_ROUNDS_PER_STAGE/5; u++) { + sha1_sw_round(t++,w, &a,&b,&c,&d,&e, Maj(b,c,d), SHA1_STAGE2_CONSTANT); + sha1_sw_round(t++,w, &e,&a,&b,&c,&d, Maj(a,b,c), SHA1_STAGE2_CONSTANT); + sha1_sw_round(t++,w, &d,&e,&a,&b,&c, Maj(e,a,b), SHA1_STAGE2_CONSTANT); + sha1_sw_round(t++,w, &c,&d,&e,&a,&b, Maj(d,e,a), SHA1_STAGE2_CONSTANT); + sha1_sw_round(t++,w, &b,&c,&d,&e,&a, Maj(c,d,e), SHA1_STAGE2_CONSTANT); + } + for (size_t u = 0; u < SHA1_ROUNDS_PER_STAGE/5; u++) { + sha1_sw_round(t++,w, &a,&b,&c,&d,&e, Par(b,c,d), SHA1_STAGE3_CONSTANT); + sha1_sw_round(t++,w, &e,&a,&b,&c,&d, Par(a,b,c), SHA1_STAGE3_CONSTANT); + sha1_sw_round(t++,w, &d,&e,&a,&b,&c, Par(e,a,b), SHA1_STAGE3_CONSTANT); + sha1_sw_round(t++,w, &c,&d,&e,&a,&b, Par(d,e,a), SHA1_STAGE3_CONSTANT); + sha1_sw_round(t++,w, &b,&c,&d,&e,&a, Par(c,d,e), SHA1_STAGE3_CONSTANT); + } + + core[0] += a; core[1] += b; core[2] += c; core[3] += d; core[4] += e; + + smemclr(w, sizeof(w)); +} + +typedef struct sha1_sw { + uint32_t core[5]; + sha1_block blk; + BinarySink_IMPLEMENTATION; + ssh_hash hash; +} sha1_sw; + +static void sha1_sw_write(BinarySink *bs, const void *vp, size_t len); + +static ssh_hash *sha1_sw_new(const ssh_hashalg *alg) +{ + sha1_sw *s = snew(sha1_sw); + + s->hash.vt = alg; + BinarySink_INIT(s, sha1_sw_write); + BinarySink_DELEGATE_INIT(&s->hash, s); + return &s->hash; +} + +static void sha1_sw_reset(ssh_hash *hash) +{ + sha1_sw *s = container_of(hash, sha1_sw, hash); + + memcpy(s->core, sha1_initial_state, sizeof(s->core)); + sha1_block_setup(&s->blk); +} + +static void sha1_sw_copyfrom(ssh_hash *hcopy, ssh_hash *horig) +{ + sha1_sw *copy = container_of(hcopy, sha1_sw, hash); + sha1_sw *orig = container_of(horig, sha1_sw, hash); + + memcpy(copy, orig, sizeof(*copy)); + BinarySink_COPIED(copy); + BinarySink_DELEGATE_INIT(©->hash, copy); +} + +static void sha1_sw_free(ssh_hash *hash) +{ + sha1_sw *s = container_of(hash, sha1_sw, hash); + + smemclr(s, sizeof(*s)); + sfree(s); +} + +static void sha1_sw_write(BinarySink *bs, const void *vp, size_t len) +{ + sha1_sw *s = BinarySink_DOWNCAST(bs, sha1_sw); + + while (len > 0) + if (sha1_block_write(&s->blk, &vp, &len)) + sha1_sw_block(s->core, s->blk.block); +} + +static void sha1_sw_digest(ssh_hash *hash, uint8_t *digest) +{ + sha1_sw *s = container_of(hash, sha1_sw, hash); + + sha1_block_pad(&s->blk, BinarySink_UPCAST(s)); + for (size_t i = 0; i < 5; i++) + PUT_32BIT_MSB_FIRST(digest + 4*i, s->core[i]); +} + +const ssh_hashalg ssh_sha1_sw = { + .new = sha1_sw_new, + .reset = sha1_sw_reset, + .copyfrom = sha1_sw_copyfrom, + .digest = sha1_sw_digest, + .free = sha1_sw_free, + .hlen = 20, + .blocklen = 64, + HASHALG_NAMES_ANNOTATED("SHA-1", "unaccelerated"), +}; + +/* ---------------------------------------------------------------------- + * Hardware-accelerated implementation of SHA-1 using x86 SHA-NI. + */ + +#if HW_SHA1 == HW_SHA1_NI + +/* + * Set target architecture for Clang and GCC + */ + +#if defined(__clang__) || defined(__GNUC__) +# define FUNC_ISA __attribute__ ((target("sse4.1,sha"))) +#if !defined(__clang__) +# pragma GCC target("sha") +# pragma GCC target("sse4.1") +#endif +#else +# define FUNC_ISA +#endif + +#include +#include +#include +#if defined(__clang__) || defined(__GNUC__) +#include +#endif + +#if defined(__clang__) || defined(__GNUC__) +#include +#define GET_CPU_ID_0(out) \ + __cpuid(0, (out)[0], (out)[1], (out)[2], (out)[3]) +#define GET_CPU_ID_7(out) \ + __cpuid_count(7, 0, (out)[0], (out)[1], (out)[2], (out)[3]) +#else +#define GET_CPU_ID_0(out) __cpuid(out, 0) +#define GET_CPU_ID_7(out) __cpuidex(out, 7, 0) +#endif + +static bool sha1_hw_available(void) +{ + unsigned int CPUInfo[4]; + GET_CPU_ID_0(CPUInfo); + if (CPUInfo[0] < 7) + return false; + + GET_CPU_ID_7(CPUInfo); + return CPUInfo[1] & (1 << 29); /* Check SHA */ +} + +/* SHA1 implementation using new instructions + The code is based on Jeffrey Walton's SHA1 implementation: + https://github.com/noloader/SHA-Intrinsics +*/ +FUNC_ISA +static inline void sha1_ni_block(__m128i *core, const uint8_t *p) +{ + __m128i ABCD, E0, E1, MSG0, MSG1, MSG2, MSG3; + const __m128i MASK = _mm_set_epi64x( + 0x0001020304050607ULL, 0x08090a0b0c0d0e0fULL); + + const __m128i *block = (const __m128i *)p; + + /* Load initial values */ + ABCD = core[0]; + E0 = core[1]; + + /* Rounds 0-3 */ + MSG0 = _mm_loadu_si128(block); + MSG0 = _mm_shuffle_epi8(MSG0, MASK); + E0 = _mm_add_epi32(E0, MSG0); + E1 = ABCD; + ABCD = _mm_sha1rnds4_epu32(ABCD, E0, 0); + + /* Rounds 4-7 */ + MSG1 = _mm_loadu_si128(block + 1); + MSG1 = _mm_shuffle_epi8(MSG1, MASK); + E1 = _mm_sha1nexte_epu32(E1, MSG1); + E0 = ABCD; + ABCD = _mm_sha1rnds4_epu32(ABCD, E1, 0); + MSG0 = _mm_sha1msg1_epu32(MSG0, MSG1); + + /* Rounds 8-11 */ + MSG2 = _mm_loadu_si128(block + 2); + MSG2 = _mm_shuffle_epi8(MSG2, MASK); + E0 = _mm_sha1nexte_epu32(E0, MSG2); + E1 = ABCD; + ABCD = _mm_sha1rnds4_epu32(ABCD, E0, 0); + MSG1 = _mm_sha1msg1_epu32(MSG1, MSG2); + MSG0 = _mm_xor_si128(MSG0, MSG2); + + /* Rounds 12-15 */ + MSG3 = _mm_loadu_si128(block + 3); + MSG3 = _mm_shuffle_epi8(MSG3, MASK); + E1 = _mm_sha1nexte_epu32(E1, MSG3); + E0 = ABCD; + MSG0 = _mm_sha1msg2_epu32(MSG0, MSG3); + ABCD = _mm_sha1rnds4_epu32(ABCD, E1, 0); + MSG2 = _mm_sha1msg1_epu32(MSG2, MSG3); + MSG1 = _mm_xor_si128(MSG1, MSG3); + + /* Rounds 16-19 */ + E0 = _mm_sha1nexte_epu32(E0, MSG0); + E1 = ABCD; + MSG1 = _mm_sha1msg2_epu32(MSG1, MSG0); + ABCD = _mm_sha1rnds4_epu32(ABCD, E0, 0); + MSG3 = _mm_sha1msg1_epu32(MSG3, MSG0); + MSG2 = _mm_xor_si128(MSG2, MSG0); + + /* Rounds 20-23 */ + E1 = _mm_sha1nexte_epu32(E1, MSG1); + E0 = ABCD; + MSG2 = _mm_sha1msg2_epu32(MSG2, MSG1); + ABCD = _mm_sha1rnds4_epu32(ABCD, E1, 1); + MSG0 = _mm_sha1msg1_epu32(MSG0, MSG1); + MSG3 = _mm_xor_si128(MSG3, MSG1); + + /* Rounds 24-27 */ + E0 = _mm_sha1nexte_epu32(E0, MSG2); + E1 = ABCD; + MSG3 = _mm_sha1msg2_epu32(MSG3, MSG2); + ABCD = _mm_sha1rnds4_epu32(ABCD, E0, 1); + MSG1 = _mm_sha1msg1_epu32(MSG1, MSG2); + MSG0 = _mm_xor_si128(MSG0, MSG2); + + /* Rounds 28-31 */ + E1 = _mm_sha1nexte_epu32(E1, MSG3); + E0 = ABCD; + MSG0 = _mm_sha1msg2_epu32(MSG0, MSG3); + ABCD = _mm_sha1rnds4_epu32(ABCD, E1, 1); + MSG2 = _mm_sha1msg1_epu32(MSG2, MSG3); + MSG1 = _mm_xor_si128(MSG1, MSG3); + + /* Rounds 32-35 */ + E0 = _mm_sha1nexte_epu32(E0, MSG0); + E1 = ABCD; + MSG1 = _mm_sha1msg2_epu32(MSG1, MSG0); + ABCD = _mm_sha1rnds4_epu32(ABCD, E0, 1); + MSG3 = _mm_sha1msg1_epu32(MSG3, MSG0); + MSG2 = _mm_xor_si128(MSG2, MSG0); + + /* Rounds 36-39 */ + E1 = _mm_sha1nexte_epu32(E1, MSG1); + E0 = ABCD; + MSG2 = _mm_sha1msg2_epu32(MSG2, MSG1); + ABCD = _mm_sha1rnds4_epu32(ABCD, E1, 1); + MSG0 = _mm_sha1msg1_epu32(MSG0, MSG1); + MSG3 = _mm_xor_si128(MSG3, MSG1); + + /* Rounds 40-43 */ + E0 = _mm_sha1nexte_epu32(E0, MSG2); + E1 = ABCD; + MSG3 = _mm_sha1msg2_epu32(MSG3, MSG2); + ABCD = _mm_sha1rnds4_epu32(ABCD, E0, 2); + MSG1 = _mm_sha1msg1_epu32(MSG1, MSG2); + MSG0 = _mm_xor_si128(MSG0, MSG2); + + /* Rounds 44-47 */ + E1 = _mm_sha1nexte_epu32(E1, MSG3); + E0 = ABCD; + MSG0 = _mm_sha1msg2_epu32(MSG0, MSG3); + ABCD = _mm_sha1rnds4_epu32(ABCD, E1, 2); + MSG2 = _mm_sha1msg1_epu32(MSG2, MSG3); + MSG1 = _mm_xor_si128(MSG1, MSG3); + + /* Rounds 48-51 */ + E0 = _mm_sha1nexte_epu32(E0, MSG0); + E1 = ABCD; + MSG1 = _mm_sha1msg2_epu32(MSG1, MSG0); + ABCD = _mm_sha1rnds4_epu32(ABCD, E0, 2); + MSG3 = _mm_sha1msg1_epu32(MSG3, MSG0); + MSG2 = _mm_xor_si128(MSG2, MSG0); + + /* Rounds 52-55 */ + E1 = _mm_sha1nexte_epu32(E1, MSG1); + E0 = ABCD; + MSG2 = _mm_sha1msg2_epu32(MSG2, MSG1); + ABCD = _mm_sha1rnds4_epu32(ABCD, E1, 2); + MSG0 = _mm_sha1msg1_epu32(MSG0, MSG1); + MSG3 = _mm_xor_si128(MSG3, MSG1); + + /* Rounds 56-59 */ + E0 = _mm_sha1nexte_epu32(E0, MSG2); + E1 = ABCD; + MSG3 = _mm_sha1msg2_epu32(MSG3, MSG2); + ABCD = _mm_sha1rnds4_epu32(ABCD, E0, 2); + MSG1 = _mm_sha1msg1_epu32(MSG1, MSG2); + MSG0 = _mm_xor_si128(MSG0, MSG2); + + /* Rounds 60-63 */ + E1 = _mm_sha1nexte_epu32(E1, MSG3); + E0 = ABCD; + MSG0 = _mm_sha1msg2_epu32(MSG0, MSG3); + ABCD = _mm_sha1rnds4_epu32(ABCD, E1, 3); + MSG2 = _mm_sha1msg1_epu32(MSG2, MSG3); + MSG1 = _mm_xor_si128(MSG1, MSG3); + + /* Rounds 64-67 */ + E0 = _mm_sha1nexte_epu32(E0, MSG0); + E1 = ABCD; + MSG1 = _mm_sha1msg2_epu32(MSG1, MSG0); + ABCD = _mm_sha1rnds4_epu32(ABCD, E0, 3); + MSG3 = _mm_sha1msg1_epu32(MSG3, MSG0); + MSG2 = _mm_xor_si128(MSG2, MSG0); + + /* Rounds 68-71 */ + E1 = _mm_sha1nexte_epu32(E1, MSG1); + E0 = ABCD; + MSG2 = _mm_sha1msg2_epu32(MSG2, MSG1); + ABCD = _mm_sha1rnds4_epu32(ABCD, E1, 3); + MSG3 = _mm_xor_si128(MSG3, MSG1); + + /* Rounds 72-75 */ + E0 = _mm_sha1nexte_epu32(E0, MSG2); + E1 = ABCD; + MSG3 = _mm_sha1msg2_epu32(MSG3, MSG2); + ABCD = _mm_sha1rnds4_epu32(ABCD, E0, 3); + + /* Rounds 76-79 */ + E1 = _mm_sha1nexte_epu32(E1, MSG3); + E0 = ABCD; + ABCD = _mm_sha1rnds4_epu32(ABCD, E1, 3); + + /* Combine state */ + core[0] = _mm_add_epi32(ABCD, core[0]); + core[1] = _mm_sha1nexte_epu32(E0, core[1]); +} + +typedef struct sha1_ni { + /* + * core[0] stores the first four words of the SHA-1 state. core[1] + * stores just the fifth word, in the vector lane at the highest + * address. + */ + __m128i core[2]; + sha1_block blk; + void *pointer_to_free; + BinarySink_IMPLEMENTATION; + ssh_hash hash; +} sha1_ni; + +static void sha1_ni_write(BinarySink *bs, const void *vp, size_t len); + +static sha1_ni *sha1_ni_alloc(void) +{ + /* + * The __m128i variables in the context structure need to be + * 16-byte aligned, but not all malloc implementations that this + * code has to work with will guarantee to return a 16-byte + * aligned pointer. So we over-allocate, manually realign the + * pointer ourselves, and store the original one inside the + * context so we know how to free it later. + */ + void *allocation = smalloc(sizeof(sha1_ni) + 15); + uintptr_t alloc_address = (uintptr_t)allocation; + uintptr_t aligned_address = (alloc_address + 15) & ~15; + sha1_ni *s = (sha1_ni *)aligned_address; + s->pointer_to_free = allocation; + return s; +} + +static ssh_hash *sha1_ni_new(const ssh_hashalg *alg) +{ + if (!sha1_hw_available_cached()) + return NULL; + + sha1_ni *s = sha1_ni_alloc(); + + s->hash.vt = alg; + BinarySink_INIT(s, sha1_ni_write); + BinarySink_DELEGATE_INIT(&s->hash, s); + return &s->hash; +} + +FUNC_ISA static void sha1_ni_reset(ssh_hash *hash) +{ + sha1_ni *s = container_of(hash, sha1_ni, hash); + + /* Initialise the core vectors in their storage order */ + s->core[0] = _mm_set_epi64x( + 0x67452301efcdab89ULL, 0x98badcfe10325476ULL); + s->core[1] = _mm_set_epi32(0xc3d2e1f0, 0, 0, 0); + + sha1_block_setup(&s->blk); +} + +static void sha1_ni_copyfrom(ssh_hash *hcopy, ssh_hash *horig) +{ + sha1_ni *copy = container_of(hcopy, sha1_ni, hash); + sha1_ni *orig = container_of(horig, sha1_ni, hash); + + void *ptf_save = copy->pointer_to_free; + *copy = *orig; /* structure copy */ + copy->pointer_to_free = ptf_save; + + BinarySink_COPIED(copy); + BinarySink_DELEGATE_INIT(©->hash, copy); +} + +static void sha1_ni_free(ssh_hash *hash) +{ + sha1_ni *s = container_of(hash, sha1_ni, hash); + + void *ptf = s->pointer_to_free; + smemclr(s, sizeof(*s)); + sfree(ptf); +} + +static void sha1_ni_write(BinarySink *bs, const void *vp, size_t len) +{ + sha1_ni *s = BinarySink_DOWNCAST(bs, sha1_ni); + + while (len > 0) + if (sha1_block_write(&s->blk, &vp, &len)) + sha1_ni_block(s->core, s->blk.block); +} + +FUNC_ISA static void sha1_ni_digest(ssh_hash *hash, uint8_t *digest) +{ + sha1_ni *s = container_of(hash, sha1_ni, hash); + + sha1_block_pad(&s->blk, BinarySink_UPCAST(s)); + + /* Rearrange the first vector into its output order */ + __m128i abcd = _mm_shuffle_epi32(s->core[0], 0x1B); + + /* Byte-swap it into the output endianness */ + const __m128i mask = _mm_setr_epi8(3,2,1,0,7,6,5,4,11,10,9,8,15,14,13,12); + abcd = _mm_shuffle_epi8(abcd, mask); + + /* And store it */ + _mm_storeu_si128((__m128i *)digest, abcd); + + /* Finally, store the leftover word */ + uint32_t e = _mm_extract_epi32(s->core[1], 3); + PUT_32BIT_MSB_FIRST(digest + 16, e); +} + +const ssh_hashalg ssh_sha1_hw = { + .new = sha1_ni_new, + .reset = sha1_ni_reset, + .copyfrom = sha1_ni_copyfrom, + .digest = sha1_ni_digest, + .free = sha1_ni_free, + .hlen = 20, + .blocklen = 64, + HASHALG_NAMES_ANNOTATED("SHA-1", "SHA-NI accelerated"), +}; + +/* ---------------------------------------------------------------------- + * Hardware-accelerated implementation of SHA-1 using Arm NEON. + */ + +#elif HW_SHA1 == HW_SHA1_NEON + +/* + * Manually set the target architecture, if we decided above that we + * need to. + */ +#ifdef USE_CLANG_ATTR_TARGET_AARCH64 +/* + * A spot of cheating: redefine some ACLE feature macros before + * including arm_neon.h. Otherwise we won't get the SHA intrinsics + * defined by that header, because it will be looking at the settings + * for the whole translation unit rather than the ones we're going to + * put on some particular functions using __attribute__((target)). + */ +#define __ARM_NEON 1 +#define __ARM_FEATURE_CRYPTO 1 +#define FUNC_ISA __attribute__ ((target("neon,crypto"))) +#endif /* USE_CLANG_ATTR_TARGET_AARCH64 */ + +#ifndef FUNC_ISA +#define FUNC_ISA +#endif + +#ifdef USE_ARM64_NEON_H +#include +#else +#include +#endif + +static bool sha1_hw_available(void) +{ + /* + * For Arm, we delegate to a per-platform detection function (see + * explanation in sshaes.c). + */ + return platform_sha1_hw_available(); +} + +typedef struct sha1_neon_core sha1_neon_core; +struct sha1_neon_core { + uint32x4_t abcd; + uint32_t e; +}; + +FUNC_ISA +static inline uint32x4_t sha1_neon_load_input(const uint8_t *p) +{ + return vreinterpretq_u32_u8(vrev32q_u8(vld1q_u8(p))); +} + +FUNC_ISA +static inline uint32x4_t sha1_neon_schedule_update( + uint32x4_t m4, uint32x4_t m3, uint32x4_t m2, uint32x4_t m1) +{ + return vsha1su1q_u32(vsha1su0q_u32(m4, m3, m2), m1); +} + +/* + * SHA-1 has three different kinds of round, differing in whether they + * use the Ch, Maj or Par functions defined above. Each one uses a + * separate NEON instruction, so we define three inline functions for + * the different round types using this macro. + * + * The two batches of Par-type rounds also use a different constant, + * but that's passed in as an operand, so we don't need a fourth + * inline function just for that. + */ +#define SHA1_NEON_ROUND_FN(type) \ + FUNC_ISA static inline sha1_neon_core sha1_neon_round4_##type( \ + sha1_neon_core old, uint32x4_t sched, uint32x4_t constant) \ + { \ + sha1_neon_core new; \ + uint32x4_t round_input = vaddq_u32(sched, constant); \ + new.abcd = vsha1##type##q_u32(old.abcd, old.e, round_input); \ + new.e = vsha1h_u32(vget_lane_u32(vget_low_u32(old.abcd), 0)); \ + return new; \ + } +SHA1_NEON_ROUND_FN(c) +SHA1_NEON_ROUND_FN(p) +SHA1_NEON_ROUND_FN(m) + +FUNC_ISA +static inline void sha1_neon_block(sha1_neon_core *core, const uint8_t *p) +{ + uint32x4_t constant, s0, s1, s2, s3; + sha1_neon_core cr = *core; + + constant = vdupq_n_u32(SHA1_STAGE0_CONSTANT); + s0 = sha1_neon_load_input(p); + cr = sha1_neon_round4_c(cr, s0, constant); + s1 = sha1_neon_load_input(p + 16); + cr = sha1_neon_round4_c(cr, s1, constant); + s2 = sha1_neon_load_input(p + 32); + cr = sha1_neon_round4_c(cr, s2, constant); + s3 = sha1_neon_load_input(p + 48); + cr = sha1_neon_round4_c(cr, s3, constant); + s0 = sha1_neon_schedule_update(s0, s1, s2, s3); + cr = sha1_neon_round4_c(cr, s0, constant); + + constant = vdupq_n_u32(SHA1_STAGE1_CONSTANT); + s1 = sha1_neon_schedule_update(s1, s2, s3, s0); + cr = sha1_neon_round4_p(cr, s1, constant); + s2 = sha1_neon_schedule_update(s2, s3, s0, s1); + cr = sha1_neon_round4_p(cr, s2, constant); + s3 = sha1_neon_schedule_update(s3, s0, s1, s2); + cr = sha1_neon_round4_p(cr, s3, constant); + s0 = sha1_neon_schedule_update(s0, s1, s2, s3); + cr = sha1_neon_round4_p(cr, s0, constant); + s1 = sha1_neon_schedule_update(s1, s2, s3, s0); + cr = sha1_neon_round4_p(cr, s1, constant); + + constant = vdupq_n_u32(SHA1_STAGE2_CONSTANT); + s2 = sha1_neon_schedule_update(s2, s3, s0, s1); + cr = sha1_neon_round4_m(cr, s2, constant); + s3 = sha1_neon_schedule_update(s3, s0, s1, s2); + cr = sha1_neon_round4_m(cr, s3, constant); + s0 = sha1_neon_schedule_update(s0, s1, s2, s3); + cr = sha1_neon_round4_m(cr, s0, constant); + s1 = sha1_neon_schedule_update(s1, s2, s3, s0); + cr = sha1_neon_round4_m(cr, s1, constant); + s2 = sha1_neon_schedule_update(s2, s3, s0, s1); + cr = sha1_neon_round4_m(cr, s2, constant); + + constant = vdupq_n_u32(SHA1_STAGE3_CONSTANT); + s3 = sha1_neon_schedule_update(s3, s0, s1, s2); + cr = sha1_neon_round4_p(cr, s3, constant); + s0 = sha1_neon_schedule_update(s0, s1, s2, s3); + cr = sha1_neon_round4_p(cr, s0, constant); + s1 = sha1_neon_schedule_update(s1, s2, s3, s0); + cr = sha1_neon_round4_p(cr, s1, constant); + s2 = sha1_neon_schedule_update(s2, s3, s0, s1); + cr = sha1_neon_round4_p(cr, s2, constant); + s3 = sha1_neon_schedule_update(s3, s0, s1, s2); + cr = sha1_neon_round4_p(cr, s3, constant); + + core->abcd = vaddq_u32(core->abcd, cr.abcd); + core->e += cr.e; +} + +typedef struct sha1_neon { + sha1_neon_core core; + sha1_block blk; + BinarySink_IMPLEMENTATION; + ssh_hash hash; +} sha1_neon; + +static void sha1_neon_write(BinarySink *bs, const void *vp, size_t len); + +static ssh_hash *sha1_neon_new(const ssh_hashalg *alg) +{ + if (!sha1_hw_available_cached()) + return NULL; + + sha1_neon *s = snew(sha1_neon); + + s->hash.vt = alg; + BinarySink_INIT(s, sha1_neon_write); + BinarySink_DELEGATE_INIT(&s->hash, s); + return &s->hash; +} + +static void sha1_neon_reset(ssh_hash *hash) +{ + sha1_neon *s = container_of(hash, sha1_neon, hash); + + s->core.abcd = vld1q_u32(sha1_initial_state); + s->core.e = sha1_initial_state[4]; + + sha1_block_setup(&s->blk); +} + +static void sha1_neon_copyfrom(ssh_hash *hcopy, ssh_hash *horig) +{ + sha1_neon *copy = container_of(hcopy, sha1_neon, hash); + sha1_neon *orig = container_of(horig, sha1_neon, hash); + + *copy = *orig; /* structure copy */ + + BinarySink_COPIED(copy); + BinarySink_DELEGATE_INIT(©->hash, copy); +} + +static void sha1_neon_free(ssh_hash *hash) +{ + sha1_neon *s = container_of(hash, sha1_neon, hash); + smemclr(s, sizeof(*s)); + sfree(s); +} + +static void sha1_neon_write(BinarySink *bs, const void *vp, size_t len) +{ + sha1_neon *s = BinarySink_DOWNCAST(bs, sha1_neon); + + while (len > 0) + if (sha1_block_write(&s->blk, &vp, &len)) + sha1_neon_block(&s->core, s->blk.block); +} + +static void sha1_neon_digest(ssh_hash *hash, uint8_t *digest) +{ + sha1_neon *s = container_of(hash, sha1_neon, hash); + + sha1_block_pad(&s->blk, BinarySink_UPCAST(s)); + vst1q_u8(digest, vrev32q_u8(vreinterpretq_u8_u32(s->core.abcd))); + PUT_32BIT_MSB_FIRST(digest + 16, s->core.e); +} + +const ssh_hashalg ssh_sha1_hw = { + .new = sha1_neon_new, + .reset = sha1_neon_reset, + .copyfrom = sha1_neon_copyfrom, + .digest = sha1_neon_digest, + .free = sha1_neon_free, + .hlen = 20, + .blocklen = 64, + HASHALG_NAMES_ANNOTATED("SHA-1", "NEON accelerated"), +}; + +/* ---------------------------------------------------------------------- + * Stub functions if we have no hardware-accelerated SHA-1. In this + * case, sha1_hw_new returns NULL (though it should also never be + * selected by sha1_select, so the only thing that should even be + * _able_ to call it is testcrypt). As a result, the remaining vtable + * functions should never be called at all. + */ + +#elif HW_SHA1 == HW_SHA1_NONE + +static bool sha1_hw_available(void) +{ + return false; +} + +static ssh_hash *sha1_stub_new(const ssh_hashalg *alg) +{ + return NULL; +} + +#define STUB_BODY { unreachable("Should never be called"); } + +static void sha1_stub_reset(ssh_hash *hash) STUB_BODY +static void sha1_stub_copyfrom(ssh_hash *hash, ssh_hash *orig) STUB_BODY +static void sha1_stub_free(ssh_hash *hash) STUB_BODY +static void sha1_stub_digest(ssh_hash *hash, uint8_t *digest) STUB_BODY + +const ssh_hashalg ssh_sha1_hw = { + .new = sha1_stub_new, + .reset = sha1_stub_reset, + .copyfrom = sha1_stub_copyfrom, + .digest = sha1_stub_digest, + .free = sha1_stub_free, + .hlen = 20, + .blocklen = 64, + HASHALG_NAMES_ANNOTATED("SHA-1", "!NONEXISTENT ACCELERATED VERSION!"), +}; + +#endif /* HW_SHA1 */ diff --git a/crypto/sha256.c b/crypto/sha256.c new file mode 100644 index 00000000..206a976c --- /dev/null +++ b/crypto/sha256.c @@ -0,0 +1,939 @@ +/* + * SHA-256 algorithm as described at + * + * http://csrc.nist.gov/cryptval/shs.html + */ + +#include "ssh.h" +#include + +/* + * Start by deciding whether we can support hardware SHA at all. + */ +#define HW_SHA256_NONE 0 +#define HW_SHA256_NI 1 +#define HW_SHA256_NEON 2 + +#ifdef _FORCE_SHA_NI +# define HW_SHA256 HW_SHA256_NI +#elif defined(__clang__) +# if __has_attribute(target) && __has_include() && \ + (defined(__x86_64__) || defined(__i386)) +# define HW_SHA256 HW_SHA256_NI +# endif +#elif defined(__GNUC__) +# if (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 9)) && \ + (defined(__x86_64__) || defined(__i386)) +# define HW_SHA256 HW_SHA256_NI +# endif +#elif defined (_MSC_VER) +# if (defined(_M_X64) || defined(_M_IX86)) && _MSC_FULL_VER >= 150030729 +# define HW_SHA256 HW_SHA256_NI +# endif +#endif + +#ifdef _FORCE_SHA_NEON +# define HW_SHA256 HW_SHA256_NEON +#elif defined __BYTE_ORDER__ && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + /* Arm can potentially support both endiannesses, but this code + * hasn't been tested on anything but little. If anyone wants to + * run big-endian, they'll need to fix it first. */ +#elif defined __ARM_FEATURE_CRYPTO + /* If the Arm crypto extension is available already, we can + * support NEON SHA without having to enable anything by hand */ +# define HW_SHA256 HW_SHA256_NEON +#elif defined(__clang__) +# if __has_attribute(target) && __has_include() && \ + (defined(__aarch64__)) + /* clang can enable the crypto extension in AArch64 using + * __attribute__((target)) */ +# define HW_SHA256 HW_SHA256_NEON +# define USE_CLANG_ATTR_TARGET_AARCH64 +# endif +#elif defined _MSC_VER + /* Visual Studio supports the crypto extension when targeting + * AArch64, but as of VS2017, the AArch32 header doesn't quite + * manage it (declaring the shae/shad intrinsics without a round + * key operand). */ +# if defined _M_ARM64 +# define HW_SHA256 HW_SHA256_NEON +# if defined _M_ARM64 +# define USE_ARM64_NEON_H /* unusual header name in this case */ +# endif +# endif +#endif + +#if defined _FORCE_SOFTWARE_SHA || !defined HW_SHA256 +# undef HW_SHA256 +# define HW_SHA256 HW_SHA256_NONE +#endif + +/* + * The actual query function that asks if hardware acceleration is + * available. + */ +static bool sha256_hw_available(void); + +/* + * The top-level selection function, caching the results of + * sha256_hw_available() so it only has to run once. + */ +static bool sha256_hw_available_cached(void) +{ + static bool initialised = false; + static bool hw_available; + if (!initialised) { + hw_available = sha256_hw_available(); + initialised = true; + } + return hw_available; +} + +static ssh_hash *sha256_select(const ssh_hashalg *alg) +{ + const ssh_hashalg *real_alg = + sha256_hw_available_cached() ? &ssh_sha256_hw : &ssh_sha256_sw; + + return ssh_hash_new(real_alg); +} + +const ssh_hashalg ssh_sha256 = { + .new = sha256_select, + .hlen = 32, + .blocklen = 64, + HASHALG_NAMES_ANNOTATED("SHA-256", "dummy selector vtable"), +}; + +/* ---------------------------------------------------------------------- + * Definitions likely to be helpful to multiple implementations. + */ + +static const uint32_t sha256_initial_state[] = { + 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, + 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19, +}; + +static const uint32_t sha256_round_constants[] = { + 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, +}; + +#define SHA256_ROUNDS 64 + +typedef struct sha256_block sha256_block; +struct sha256_block { + uint8_t block[64]; + size_t used; + uint64_t len; +}; + +static inline void sha256_block_setup(sha256_block *blk) +{ + blk->used = 0; + blk->len = 0; +} + +static inline bool sha256_block_write( + sha256_block *blk, const void **vdata, size_t *len) +{ + size_t blkleft = sizeof(blk->block) - blk->used; + size_t chunk = *len < blkleft ? *len : blkleft; + + const uint8_t *p = *vdata; + memcpy(blk->block + blk->used, p, chunk); + *vdata = p + chunk; + *len -= chunk; + blk->used += chunk; + blk->len += chunk; + + if (blk->used == sizeof(blk->block)) { + blk->used = 0; + return true; + } + + return false; +} + +static inline void sha256_block_pad(sha256_block *blk, BinarySink *bs) +{ + uint64_t final_len = blk->len << 3; + size_t pad = 1 + (63 & (55 - blk->used)); + + put_byte(bs, 0x80); + for (size_t i = 1; i < pad; i++) + put_byte(bs, 0); + put_uint64(bs, final_len); + + assert(blk->used == 0 && "Should have exactly hit a block boundary"); +} + +/* ---------------------------------------------------------------------- + * Software implementation of SHA-256. + */ + +static inline uint32_t ror(uint32_t x, unsigned y) +{ + return (x << (31 & -y)) | (x >> (31 & y)); +} + +static inline uint32_t Ch(uint32_t ctrl, uint32_t if1, uint32_t if0) +{ + return if0 ^ (ctrl & (if1 ^ if0)); +} + +static inline uint32_t Maj(uint32_t x, uint32_t y, uint32_t z) +{ + return (x & y) | (z & (x | y)); +} + +static inline uint32_t Sigma_0(uint32_t x) +{ + return ror(x,2) ^ ror(x,13) ^ ror(x,22); +} + +static inline uint32_t Sigma_1(uint32_t x) +{ + return ror(x,6) ^ ror(x,11) ^ ror(x,25); +} + +static inline uint32_t sigma_0(uint32_t x) +{ + return ror(x,7) ^ ror(x,18) ^ (x >> 3); +} + +static inline uint32_t sigma_1(uint32_t x) +{ + return ror(x,17) ^ ror(x,19) ^ (x >> 10); +} + +static inline void sha256_sw_round( + unsigned round_index, const uint32_t *schedule, + uint32_t *a, uint32_t *b, uint32_t *c, uint32_t *d, + uint32_t *e, uint32_t *f, uint32_t *g, uint32_t *h) +{ + uint32_t t1 = *h + Sigma_1(*e) + Ch(*e,*f,*g) + + sha256_round_constants[round_index] + schedule[round_index]; + + uint32_t t2 = Sigma_0(*a) + Maj(*a,*b,*c); + + *d += t1; + *h = t1 + t2; +} + +static void sha256_sw_block(uint32_t *core, const uint8_t *block) +{ + uint32_t w[SHA256_ROUNDS]; + uint32_t a,b,c,d,e,f,g,h; + + for (size_t t = 0; t < 16; t++) + w[t] = GET_32BIT_MSB_FIRST(block + 4*t); + + for (size_t t = 16; t < SHA256_ROUNDS; t++) + w[t] = sigma_1(w[t-2]) + w[t-7] + sigma_0(w[t-15]) + w[t-16]; + + a = core[0]; b = core[1]; c = core[2]; d = core[3]; + e = core[4]; f = core[5]; g = core[6]; h = core[7]; + + for (size_t t = 0; t < SHA256_ROUNDS; t += 8) { + sha256_sw_round(t+0, w, &a,&b,&c,&d,&e,&f,&g,&h); + sha256_sw_round(t+1, w, &h,&a,&b,&c,&d,&e,&f,&g); + sha256_sw_round(t+2, w, &g,&h,&a,&b,&c,&d,&e,&f); + sha256_sw_round(t+3, w, &f,&g,&h,&a,&b,&c,&d,&e); + sha256_sw_round(t+4, w, &e,&f,&g,&h,&a,&b,&c,&d); + sha256_sw_round(t+5, w, &d,&e,&f,&g,&h,&a,&b,&c); + sha256_sw_round(t+6, w, &c,&d,&e,&f,&g,&h,&a,&b); + sha256_sw_round(t+7, w, &b,&c,&d,&e,&f,&g,&h,&a); + } + + core[0] += a; core[1] += b; core[2] += c; core[3] += d; + core[4] += e; core[5] += f; core[6] += g; core[7] += h; + + smemclr(w, sizeof(w)); +} + +typedef struct sha256_sw { + uint32_t core[8]; + sha256_block blk; + BinarySink_IMPLEMENTATION; + ssh_hash hash; +} sha256_sw; + +static void sha256_sw_write(BinarySink *bs, const void *vp, size_t len); + +static ssh_hash *sha256_sw_new(const ssh_hashalg *alg) +{ + sha256_sw *s = snew(sha256_sw); + + s->hash.vt = alg; + BinarySink_INIT(s, sha256_sw_write); + BinarySink_DELEGATE_INIT(&s->hash, s); + return &s->hash; +} + +static void sha256_sw_reset(ssh_hash *hash) +{ + sha256_sw *s = container_of(hash, sha256_sw, hash); + + memcpy(s->core, sha256_initial_state, sizeof(s->core)); + sha256_block_setup(&s->blk); +} + +static void sha256_sw_copyfrom(ssh_hash *hcopy, ssh_hash *horig) +{ + sha256_sw *copy = container_of(hcopy, sha256_sw, hash); + sha256_sw *orig = container_of(horig, sha256_sw, hash); + + memcpy(copy, orig, sizeof(*copy)); + BinarySink_COPIED(copy); + BinarySink_DELEGATE_INIT(©->hash, copy); +} + +static void sha256_sw_free(ssh_hash *hash) +{ + sha256_sw *s = container_of(hash, sha256_sw, hash); + + smemclr(s, sizeof(*s)); + sfree(s); +} + +static void sha256_sw_write(BinarySink *bs, const void *vp, size_t len) +{ + sha256_sw *s = BinarySink_DOWNCAST(bs, sha256_sw); + + while (len > 0) + if (sha256_block_write(&s->blk, &vp, &len)) + sha256_sw_block(s->core, s->blk.block); +} + +static void sha256_sw_digest(ssh_hash *hash, uint8_t *digest) +{ + sha256_sw *s = container_of(hash, sha256_sw, hash); + + sha256_block_pad(&s->blk, BinarySink_UPCAST(s)); + for (size_t i = 0; i < 8; i++) + PUT_32BIT_MSB_FIRST(digest + 4*i, s->core[i]); +} + +const ssh_hashalg ssh_sha256_sw = { + .new = sha256_sw_new, + .reset = sha256_sw_reset, + .copyfrom = sha256_sw_copyfrom, + .digest = sha256_sw_digest, + .free = sha256_sw_free, + .hlen = 32, + .blocklen = 64, + HASHALG_NAMES_ANNOTATED("SHA-256", "unaccelerated"), +}; + +/* ---------------------------------------------------------------------- + * Hardware-accelerated implementation of SHA-256 using x86 SHA-NI. + */ + +#if HW_SHA256 == HW_SHA256_NI + +/* + * Set target architecture for Clang and GCC + */ +#if defined(__clang__) || defined(__GNUC__) +# define FUNC_ISA __attribute__ ((target("sse4.1,sha"))) +#if !defined(__clang__) +# pragma GCC target("sha") +# pragma GCC target("sse4.1") +#endif +#else +# define FUNC_ISA +#endif + +#include +#include +#include +#if defined(__clang__) || defined(__GNUC__) +#include +#endif + +#if defined(__clang__) || defined(__GNUC__) +#include +#define GET_CPU_ID_0(out) \ + __cpuid(0, (out)[0], (out)[1], (out)[2], (out)[3]) +#define GET_CPU_ID_7(out) \ + __cpuid_count(7, 0, (out)[0], (out)[1], (out)[2], (out)[3]) +#else +#define GET_CPU_ID_0(out) __cpuid(out, 0) +#define GET_CPU_ID_7(out) __cpuidex(out, 7, 0) +#endif + +static bool sha256_hw_available(void) +{ + unsigned int CPUInfo[4]; + GET_CPU_ID_0(CPUInfo); + if (CPUInfo[0] < 7) + return false; + + GET_CPU_ID_7(CPUInfo); + return CPUInfo[1] & (1 << 29); /* Check SHA */ +} + +/* SHA256 implementation using new instructions + The code is based on Jeffrey Walton's SHA256 implementation: + https://github.com/noloader/SHA-Intrinsics +*/ +FUNC_ISA +static inline void sha256_ni_block(__m128i *core, const uint8_t *p) +{ + __m128i STATE0, STATE1; + __m128i MSG, TMP; + __m128i MSG0, MSG1, MSG2, MSG3; + const __m128i *block = (const __m128i *)p; + const __m128i MASK = _mm_set_epi64x( + 0x0c0d0e0f08090a0bULL, 0x0405060700010203ULL); + + /* Load initial values */ + STATE0 = core[0]; + STATE1 = core[1]; + + /* Rounds 0-3 */ + MSG = _mm_loadu_si128(block); + MSG0 = _mm_shuffle_epi8(MSG, MASK); + MSG = _mm_add_epi32(MSG0, _mm_set_epi64x( + 0xE9B5DBA5B5C0FBCFULL, 0x71374491428A2F98ULL)); + STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); + MSG = _mm_shuffle_epi32(MSG, 0x0E); + STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); + + /* Rounds 4-7 */ + MSG1 = _mm_loadu_si128(block + 1); + MSG1 = _mm_shuffle_epi8(MSG1, MASK); + MSG = _mm_add_epi32(MSG1, _mm_set_epi64x( + 0xAB1C5ED5923F82A4ULL, 0x59F111F13956C25BULL)); + STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); + MSG = _mm_shuffle_epi32(MSG, 0x0E); + STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); + MSG0 = _mm_sha256msg1_epu32(MSG0, MSG1); + + /* Rounds 8-11 */ + MSG2 = _mm_loadu_si128(block + 2); + MSG2 = _mm_shuffle_epi8(MSG2, MASK); + MSG = _mm_add_epi32(MSG2, _mm_set_epi64x( + 0x550C7DC3243185BEULL, 0x12835B01D807AA98ULL)); + STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); + MSG = _mm_shuffle_epi32(MSG, 0x0E); + STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); + MSG1 = _mm_sha256msg1_epu32(MSG1, MSG2); + + /* Rounds 12-15 */ + MSG3 = _mm_loadu_si128(block + 3); + MSG3 = _mm_shuffle_epi8(MSG3, MASK); + MSG = _mm_add_epi32(MSG3, _mm_set_epi64x( + 0xC19BF1749BDC06A7ULL, 0x80DEB1FE72BE5D74ULL)); + STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); + TMP = _mm_alignr_epi8(MSG3, MSG2, 4); + MSG0 = _mm_add_epi32(MSG0, TMP); + MSG0 = _mm_sha256msg2_epu32(MSG0, MSG3); + MSG = _mm_shuffle_epi32(MSG, 0x0E); + STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); + MSG2 = _mm_sha256msg1_epu32(MSG2, MSG3); + + /* Rounds 16-19 */ + MSG = _mm_add_epi32(MSG0, _mm_set_epi64x( + 0x240CA1CC0FC19DC6ULL, 0xEFBE4786E49B69C1ULL)); + STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); + TMP = _mm_alignr_epi8(MSG0, MSG3, 4); + MSG1 = _mm_add_epi32(MSG1, TMP); + MSG1 = _mm_sha256msg2_epu32(MSG1, MSG0); + MSG = _mm_shuffle_epi32(MSG, 0x0E); + STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); + MSG3 = _mm_sha256msg1_epu32(MSG3, MSG0); + + /* Rounds 20-23 */ + MSG = _mm_add_epi32(MSG1, _mm_set_epi64x( + 0x76F988DA5CB0A9DCULL, 0x4A7484AA2DE92C6FULL)); + STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); + TMP = _mm_alignr_epi8(MSG1, MSG0, 4); + MSG2 = _mm_add_epi32(MSG2, TMP); + MSG2 = _mm_sha256msg2_epu32(MSG2, MSG1); + MSG = _mm_shuffle_epi32(MSG, 0x0E); + STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); + MSG0 = _mm_sha256msg1_epu32(MSG0, MSG1); + + /* Rounds 24-27 */ + MSG = _mm_add_epi32(MSG2, _mm_set_epi64x( + 0xBF597FC7B00327C8ULL, 0xA831C66D983E5152ULL)); + STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); + TMP = _mm_alignr_epi8(MSG2, MSG1, 4); + MSG3 = _mm_add_epi32(MSG3, TMP); + MSG3 = _mm_sha256msg2_epu32(MSG3, MSG2); + MSG = _mm_shuffle_epi32(MSG, 0x0E); + STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); + MSG1 = _mm_sha256msg1_epu32(MSG1, MSG2); + + /* Rounds 28-31 */ + MSG = _mm_add_epi32(MSG3, _mm_set_epi64x( + 0x1429296706CA6351ULL, 0xD5A79147C6E00BF3ULL)); + STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); + TMP = _mm_alignr_epi8(MSG3, MSG2, 4); + MSG0 = _mm_add_epi32(MSG0, TMP); + MSG0 = _mm_sha256msg2_epu32(MSG0, MSG3); + MSG = _mm_shuffle_epi32(MSG, 0x0E); + STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); + MSG2 = _mm_sha256msg1_epu32(MSG2, MSG3); + + /* Rounds 32-35 */ + MSG = _mm_add_epi32(MSG0, _mm_set_epi64x( + 0x53380D134D2C6DFCULL, 0x2E1B213827B70A85ULL)); + STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); + TMP = _mm_alignr_epi8(MSG0, MSG3, 4); + MSG1 = _mm_add_epi32(MSG1, TMP); + MSG1 = _mm_sha256msg2_epu32(MSG1, MSG0); + MSG = _mm_shuffle_epi32(MSG, 0x0E); + STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); + MSG3 = _mm_sha256msg1_epu32(MSG3, MSG0); + + /* Rounds 36-39 */ + MSG = _mm_add_epi32(MSG1, _mm_set_epi64x( + 0x92722C8581C2C92EULL, 0x766A0ABB650A7354ULL)); + STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); + TMP = _mm_alignr_epi8(MSG1, MSG0, 4); + MSG2 = _mm_add_epi32(MSG2, TMP); + MSG2 = _mm_sha256msg2_epu32(MSG2, MSG1); + MSG = _mm_shuffle_epi32(MSG, 0x0E); + STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); + MSG0 = _mm_sha256msg1_epu32(MSG0, MSG1); + + /* Rounds 40-43 */ + MSG = _mm_add_epi32(MSG2, _mm_set_epi64x( + 0xC76C51A3C24B8B70ULL, 0xA81A664BA2BFE8A1ULL)); + STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); + TMP = _mm_alignr_epi8(MSG2, MSG1, 4); + MSG3 = _mm_add_epi32(MSG3, TMP); + MSG3 = _mm_sha256msg2_epu32(MSG3, MSG2); + MSG = _mm_shuffle_epi32(MSG, 0x0E); + STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); + MSG1 = _mm_sha256msg1_epu32(MSG1, MSG2); + + /* Rounds 44-47 */ + MSG = _mm_add_epi32(MSG3, _mm_set_epi64x( + 0x106AA070F40E3585ULL, 0xD6990624D192E819ULL)); + STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); + TMP = _mm_alignr_epi8(MSG3, MSG2, 4); + MSG0 = _mm_add_epi32(MSG0, TMP); + MSG0 = _mm_sha256msg2_epu32(MSG0, MSG3); + MSG = _mm_shuffle_epi32(MSG, 0x0E); + STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); + MSG2 = _mm_sha256msg1_epu32(MSG2, MSG3); + + /* Rounds 48-51 */ + MSG = _mm_add_epi32(MSG0, _mm_set_epi64x( + 0x34B0BCB52748774CULL, 0x1E376C0819A4C116ULL)); + STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); + TMP = _mm_alignr_epi8(MSG0, MSG3, 4); + MSG1 = _mm_add_epi32(MSG1, TMP); + MSG1 = _mm_sha256msg2_epu32(MSG1, MSG0); + MSG = _mm_shuffle_epi32(MSG, 0x0E); + STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); + MSG3 = _mm_sha256msg1_epu32(MSG3, MSG0); + + /* Rounds 52-55 */ + MSG = _mm_add_epi32(MSG1, _mm_set_epi64x( + 0x682E6FF35B9CCA4FULL, 0x4ED8AA4A391C0CB3ULL)); + STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); + TMP = _mm_alignr_epi8(MSG1, MSG0, 4); + MSG2 = _mm_add_epi32(MSG2, TMP); + MSG2 = _mm_sha256msg2_epu32(MSG2, MSG1); + MSG = _mm_shuffle_epi32(MSG, 0x0E); + STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); + + /* Rounds 56-59 */ + MSG = _mm_add_epi32(MSG2, _mm_set_epi64x( + 0x8CC7020884C87814ULL, 0x78A5636F748F82EEULL)); + STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); + TMP = _mm_alignr_epi8(MSG2, MSG1, 4); + MSG3 = _mm_add_epi32(MSG3, TMP); + MSG3 = _mm_sha256msg2_epu32(MSG3, MSG2); + MSG = _mm_shuffle_epi32(MSG, 0x0E); + STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); + + /* Rounds 60-63 */ + MSG = _mm_add_epi32(MSG3, _mm_set_epi64x( + 0xC67178F2BEF9A3F7ULL, 0xA4506CEB90BEFFFAULL)); + STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); + MSG = _mm_shuffle_epi32(MSG, 0x0E); + STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); + + /* Combine state */ + core[0] = _mm_add_epi32(STATE0, core[0]); + core[1] = _mm_add_epi32(STATE1, core[1]); +} + +typedef struct sha256_ni { + /* + * These two vectors store the 8 words of the SHA-256 state, but + * not in the same order they appear in the spec: the first word + * holds A,B,E,F and the second word C,D,G,H. + */ + __m128i core[2]; + sha256_block blk; + void *pointer_to_free; + BinarySink_IMPLEMENTATION; + ssh_hash hash; +} sha256_ni; + +static void sha256_ni_write(BinarySink *bs, const void *vp, size_t len); + +static sha256_ni *sha256_ni_alloc(void) +{ + /* + * The __m128i variables in the context structure need to be + * 16-byte aligned, but not all malloc implementations that this + * code has to work with will guarantee to return a 16-byte + * aligned pointer. So we over-allocate, manually realign the + * pointer ourselves, and store the original one inside the + * context so we know how to free it later. + */ + void *allocation = smalloc(sizeof(sha256_ni) + 15); + uintptr_t alloc_address = (uintptr_t)allocation; + uintptr_t aligned_address = (alloc_address + 15) & ~15; + sha256_ni *s = (sha256_ni *)aligned_address; + s->pointer_to_free = allocation; + return s; +} + +static ssh_hash *sha256_ni_new(const ssh_hashalg *alg) +{ + if (!sha256_hw_available_cached()) + return NULL; + + sha256_ni *s = sha256_ni_alloc(); + + s->hash.vt = alg; + BinarySink_INIT(s, sha256_ni_write); + BinarySink_DELEGATE_INIT(&s->hash, s); + + return &s->hash; +} + +FUNC_ISA static void sha256_ni_reset(ssh_hash *hash) +{ + sha256_ni *s = container_of(hash, sha256_ni, hash); + + /* Initialise the core vectors in their storage order */ + s->core[0] = _mm_set_epi64x( + 0x6a09e667bb67ae85ULL, 0x510e527f9b05688cULL); + s->core[1] = _mm_set_epi64x( + 0x3c6ef372a54ff53aULL, 0x1f83d9ab5be0cd19ULL); + + sha256_block_setup(&s->blk); +} + +static void sha256_ni_copyfrom(ssh_hash *hcopy, ssh_hash *horig) +{ + sha256_ni *copy = container_of(hcopy, sha256_ni, hash); + sha256_ni *orig = container_of(horig, sha256_ni, hash); + + void *ptf_save = copy->pointer_to_free; + *copy = *orig; /* structure copy */ + copy->pointer_to_free = ptf_save; + + BinarySink_COPIED(copy); + BinarySink_DELEGATE_INIT(©->hash, copy); +} + +static void sha256_ni_free(ssh_hash *hash) +{ + sha256_ni *s = container_of(hash, sha256_ni, hash); + + void *ptf = s->pointer_to_free; + smemclr(s, sizeof(*s)); + sfree(ptf); +} + +static void sha256_ni_write(BinarySink *bs, const void *vp, size_t len) +{ + sha256_ni *s = BinarySink_DOWNCAST(bs, sha256_ni); + + while (len > 0) + if (sha256_block_write(&s->blk, &vp, &len)) + sha256_ni_block(s->core, s->blk.block); +} + +FUNC_ISA static void sha256_ni_digest(ssh_hash *hash, uint8_t *digest) +{ + sha256_ni *s = container_of(hash, sha256_ni, hash); + + sha256_block_pad(&s->blk, BinarySink_UPCAST(s)); + + /* Rearrange the words into the output order */ + __m128i feba = _mm_shuffle_epi32(s->core[0], 0x1B); + __m128i dchg = _mm_shuffle_epi32(s->core[1], 0xB1); + __m128i dcba = _mm_blend_epi16(feba, dchg, 0xF0); + __m128i hgfe = _mm_alignr_epi8(dchg, feba, 8); + + /* Byte-swap them into the output endianness */ + const __m128i mask = _mm_setr_epi8(3,2,1,0,7,6,5,4,11,10,9,8,15,14,13,12); + dcba = _mm_shuffle_epi8(dcba, mask); + hgfe = _mm_shuffle_epi8(hgfe, mask); + + /* And store them */ + __m128i *output = (__m128i *)digest; + _mm_storeu_si128(output, dcba); + _mm_storeu_si128(output+1, hgfe); +} + +const ssh_hashalg ssh_sha256_hw = { + .new = sha256_ni_new, + .reset = sha256_ni_reset, + .copyfrom = sha256_ni_copyfrom, + .digest = sha256_ni_digest, + .free = sha256_ni_free, + .hlen = 32, + .blocklen = 64, + HASHALG_NAMES_ANNOTATED("SHA-256", "SHA-NI accelerated"), +}; + +/* ---------------------------------------------------------------------- + * Hardware-accelerated implementation of SHA-256 using Arm NEON. + */ + +#elif HW_SHA256 == HW_SHA256_NEON + +/* + * Manually set the target architecture, if we decided above that we + * need to. + */ +#ifdef USE_CLANG_ATTR_TARGET_AARCH64 +/* + * A spot of cheating: redefine some ACLE feature macros before + * including arm_neon.h. Otherwise we won't get the SHA intrinsics + * defined by that header, because it will be looking at the settings + * for the whole translation unit rather than the ones we're going to + * put on some particular functions using __attribute__((target)). + */ +#define __ARM_NEON 1 +#define __ARM_FEATURE_CRYPTO 1 +#define FUNC_ISA __attribute__ ((target("neon,crypto"))) +#endif /* USE_CLANG_ATTR_TARGET_AARCH64 */ + +#ifndef FUNC_ISA +#define FUNC_ISA +#endif + +#ifdef USE_ARM64_NEON_H +#include +#else +#include +#endif + +static bool sha256_hw_available(void) +{ + /* + * For Arm, we delegate to a per-platform detection function (see + * explanation in sshaes.c). + */ + return platform_sha256_hw_available(); +} + +typedef struct sha256_neon_core sha256_neon_core; +struct sha256_neon_core { + uint32x4_t abcd, efgh; +}; + +FUNC_ISA +static inline uint32x4_t sha256_neon_load_input(const uint8_t *p) +{ + return vreinterpretq_u32_u8(vrev32q_u8(vld1q_u8(p))); +} + +FUNC_ISA +static inline uint32x4_t sha256_neon_schedule_update( + uint32x4_t m4, uint32x4_t m3, uint32x4_t m2, uint32x4_t m1) +{ + return vsha256su1q_u32(vsha256su0q_u32(m4, m3), m2, m1); +} + +FUNC_ISA +static inline sha256_neon_core sha256_neon_round4( + sha256_neon_core old, uint32x4_t sched, unsigned round) +{ + sha256_neon_core new; + + uint32x4_t round_input = vaddq_u32( + sched, vld1q_u32(sha256_round_constants + round)); + new.abcd = vsha256hq_u32 (old.abcd, old.efgh, round_input); + new.efgh = vsha256h2q_u32(old.efgh, old.abcd, round_input); + return new; +} + +FUNC_ISA +static inline void sha256_neon_block(sha256_neon_core *core, const uint8_t *p) +{ + uint32x4_t s0, s1, s2, s3; + sha256_neon_core cr = *core; + + s0 = sha256_neon_load_input(p); + cr = sha256_neon_round4(cr, s0, 0); + s1 = sha256_neon_load_input(p+16); + cr = sha256_neon_round4(cr, s1, 4); + s2 = sha256_neon_load_input(p+32); + cr = sha256_neon_round4(cr, s2, 8); + s3 = sha256_neon_load_input(p+48); + cr = sha256_neon_round4(cr, s3, 12); + s0 = sha256_neon_schedule_update(s0, s1, s2, s3); + cr = sha256_neon_round4(cr, s0, 16); + s1 = sha256_neon_schedule_update(s1, s2, s3, s0); + cr = sha256_neon_round4(cr, s1, 20); + s2 = sha256_neon_schedule_update(s2, s3, s0, s1); + cr = sha256_neon_round4(cr, s2, 24); + s3 = sha256_neon_schedule_update(s3, s0, s1, s2); + cr = sha256_neon_round4(cr, s3, 28); + s0 = sha256_neon_schedule_update(s0, s1, s2, s3); + cr = sha256_neon_round4(cr, s0, 32); + s1 = sha256_neon_schedule_update(s1, s2, s3, s0); + cr = sha256_neon_round4(cr, s1, 36); + s2 = sha256_neon_schedule_update(s2, s3, s0, s1); + cr = sha256_neon_round4(cr, s2, 40); + s3 = sha256_neon_schedule_update(s3, s0, s1, s2); + cr = sha256_neon_round4(cr, s3, 44); + s0 = sha256_neon_schedule_update(s0, s1, s2, s3); + cr = sha256_neon_round4(cr, s0, 48); + s1 = sha256_neon_schedule_update(s1, s2, s3, s0); + cr = sha256_neon_round4(cr, s1, 52); + s2 = sha256_neon_schedule_update(s2, s3, s0, s1); + cr = sha256_neon_round4(cr, s2, 56); + s3 = sha256_neon_schedule_update(s3, s0, s1, s2); + cr = sha256_neon_round4(cr, s3, 60); + + core->abcd = vaddq_u32(core->abcd, cr.abcd); + core->efgh = vaddq_u32(core->efgh, cr.efgh); +} + +typedef struct sha256_neon { + sha256_neon_core core; + sha256_block blk; + BinarySink_IMPLEMENTATION; + ssh_hash hash; +} sha256_neon; + +static void sha256_neon_write(BinarySink *bs, const void *vp, size_t len); + +static ssh_hash *sha256_neon_new(const ssh_hashalg *alg) +{ + if (!sha256_hw_available_cached()) + return NULL; + + sha256_neon *s = snew(sha256_neon); + + s->hash.vt = alg; + BinarySink_INIT(s, sha256_neon_write); + BinarySink_DELEGATE_INIT(&s->hash, s); + return &s->hash; +} + +static void sha256_neon_reset(ssh_hash *hash) +{ + sha256_neon *s = container_of(hash, sha256_neon, hash); + + s->core.abcd = vld1q_u32(sha256_initial_state); + s->core.efgh = vld1q_u32(sha256_initial_state + 4); + + sha256_block_setup(&s->blk); +} + +static void sha256_neon_copyfrom(ssh_hash *hcopy, ssh_hash *horig) +{ + sha256_neon *copy = container_of(hcopy, sha256_neon, hash); + sha256_neon *orig = container_of(horig, sha256_neon, hash); + + *copy = *orig; /* structure copy */ + + BinarySink_COPIED(copy); + BinarySink_DELEGATE_INIT(©->hash, copy); +} + +static void sha256_neon_free(ssh_hash *hash) +{ + sha256_neon *s = container_of(hash, sha256_neon, hash); + smemclr(s, sizeof(*s)); + sfree(s); +} + +static void sha256_neon_write(BinarySink *bs, const void *vp, size_t len) +{ + sha256_neon *s = BinarySink_DOWNCAST(bs, sha256_neon); + + while (len > 0) + if (sha256_block_write(&s->blk, &vp, &len)) + sha256_neon_block(&s->core, s->blk.block); +} + +static void sha256_neon_digest(ssh_hash *hash, uint8_t *digest) +{ + sha256_neon *s = container_of(hash, sha256_neon, hash); + + sha256_block_pad(&s->blk, BinarySink_UPCAST(s)); + vst1q_u8(digest, vrev32q_u8(vreinterpretq_u8_u32(s->core.abcd))); + vst1q_u8(digest + 16, vrev32q_u8(vreinterpretq_u8_u32(s->core.efgh))); +} + +const ssh_hashalg ssh_sha256_hw = { + .new = sha256_neon_new, + .reset = sha256_neon_reset, + .copyfrom = sha256_neon_copyfrom, + .digest = sha256_neon_digest, + .free = sha256_neon_free, + .hlen = 32, + .blocklen = 64, + HASHALG_NAMES_ANNOTATED("SHA-256", "NEON accelerated"), +}; + +/* ---------------------------------------------------------------------- + * Stub functions if we have no hardware-accelerated SHA-256. In this + * case, sha256_hw_new returns NULL (though it should also never be + * selected by sha256_select, so the only thing that should even be + * _able_ to call it is testcrypt). As a result, the remaining vtable + * functions should never be called at all. + */ + +#elif HW_SHA256 == HW_SHA256_NONE + +static bool sha256_hw_available(void) +{ + return false; +} + +static ssh_hash *sha256_stub_new(const ssh_hashalg *alg) +{ + return NULL; +} + +#define STUB_BODY { unreachable("Should never be called"); } + +static void sha256_stub_reset(ssh_hash *hash) STUB_BODY +static void sha256_stub_copyfrom(ssh_hash *hash, ssh_hash *orig) STUB_BODY +static void sha256_stub_free(ssh_hash *hash) STUB_BODY +static void sha256_stub_digest(ssh_hash *hash, uint8_t *digest) STUB_BODY + +const ssh_hashalg ssh_sha256_hw = { + .new = sha256_stub_new, + .reset = sha256_stub_reset, + .copyfrom = sha256_stub_copyfrom, + .digest = sha256_stub_digest, + .free = sha256_stub_free, + .hlen = 32, + .blocklen = 64, + HASHALG_NAMES_ANNOTATED("SHA-256", "!NONEXISTENT ACCELERATED VERSION!"), +}; + +#endif /* HW_SHA256 */ diff --git a/crypto/sha3.c b/crypto/sha3.c new file mode 100644 index 00000000..83d136bf --- /dev/null +++ b/crypto/sha3.c @@ -0,0 +1,329 @@ +/* + * SHA-3, as defined in FIPS PUB 202. + */ + +#include +#include +#include "ssh.h" + +static inline uint64_t rol(uint64_t x, unsigned shift) +{ + unsigned L = (+shift) & 63; + unsigned R = (-shift) & 63; + return (x << L) | (x >> R); +} + +/* + * General Keccak is defined such that its state is a 5x5 array of + * words which can be any power-of-2 size from 1 up to 64. SHA-3 fixes + * on 64, and so do we. + * + * The number of rounds is defined as 12 + 2k if the word size is 2^k. + * Here we have 64-bit words only, so k=6, so 24 rounds always. + */ +typedef uint64_t keccak_core_state[5][5]; +#define NROUNDS 24 /* would differ for other word sizes */ +static const uint64_t round_constants[NROUNDS]; +static const unsigned rotation_counts[5][5]; + +/* + * Core Keccak transform: just squodge the state around internally, + * without adding or extracting any data from it. + */ +static void keccak_transform(keccak_core_state A) +{ + union { + uint64_t C[5]; + uint64_t B[5][5]; + } u; + + for (unsigned round = 0; round < NROUNDS; round++) { + /* theta step */ + for (unsigned x = 0; x < 5; x++) + u.C[x] = A[x][0] ^ A[x][1] ^ A[x][2] ^ A[x][3] ^ A[x][4]; + for (unsigned x = 0; x < 5; x++) { + uint64_t D = rol(u.C[(x+1) % 5], 1) ^ u.C[(x+4) % 5]; + for (unsigned y = 0; y < 5; y++) + A[x][y] ^= D; + } + + /* rho and pi steps */ + for (unsigned x = 0; x < 5; x++) + for (unsigned y = 0; y < 5; y++) + u.B[y][(2*x+3*y) % 5] = rol(A[x][y], rotation_counts[x][y]); + + /* chi step */ + for (unsigned x = 0; x < 5; x++) + for (unsigned y = 0; y < 5; y++) + A[x][y] = u.B[x][y] ^ (u.B[(x+2)%5][y] & ~u.B[(x+1)%5][y]); + + /* iota step */ + A[0][0] ^= round_constants[round]; + } + + smemclr(&u, sizeof(u)); +} + +typedef struct { + keccak_core_state A; + unsigned char bytes[25*8]; + unsigned char first_pad_byte; + size_t bytes_got, bytes_wanted, hash_bytes; +} keccak_state; + +/* + * Keccak accumulation function: given a piece of message, add it to + * the hash. + */ +static void keccak_accumulate(keccak_state *s, const void *vdata, size_t len) +{ + const unsigned char *data = (const unsigned char *)vdata; + + while (len >= s->bytes_wanted - s->bytes_got) { + size_t b = s->bytes_wanted - s->bytes_got; + memcpy(s->bytes + s->bytes_got, data, b); + len -= b; + data += b; + + size_t n = 0; + for (unsigned y = 0; y < 5; y++) { + for (unsigned x = 0; x < 5; x++) { + if (n >= s->bytes_wanted) + break; + + s->A[x][y] ^= GET_64BIT_LSB_FIRST(s->bytes + n); + n += 8; + } + } + keccak_transform(s->A); + + s->bytes_got = 0; + } + + memcpy(s->bytes + s->bytes_got, data, len); + s->bytes_got += len; +} + +/* + * Keccak output function. + */ +static void keccak_output(keccak_state *s, void *voutput) +{ + unsigned char *output = (unsigned char *)voutput; + + /* + * Add message padding. + */ + { + unsigned char padding[25*8]; + size_t len = s->bytes_wanted - s->bytes_got; + if (len == 0) + len = s->bytes_wanted; + memset(padding, 0, len); + padding[0] |= s->first_pad_byte; + padding[len-1] |= 0x80; + keccak_accumulate(s, padding, len); + } + + size_t n = 0; + for (unsigned y = 0; y < 5; y++) { + for (unsigned x = 0; x < 5; x++) { + size_t to_copy = s->hash_bytes - n; + if (to_copy == 0) + break; + if (to_copy > 8) + to_copy = 8; + unsigned char outbytes[8]; + PUT_64BIT_LSB_FIRST(outbytes, s->A[x][y]); + memcpy(output + n, outbytes, to_copy); + n += to_copy; + } + } +} + +static void keccak_init(keccak_state *s, unsigned hashbits, unsigned ratebits, + unsigned char first_pad_byte) +{ + int x, y; + + assert(hashbits % 8 == 0); + assert(ratebits % 8 == 0); + + s->hash_bytes = hashbits / 8; + s->bytes_wanted = (25 * 64 - ratebits) / 8; + s->bytes_got = 0; + s->first_pad_byte = first_pad_byte; + + assert(s->bytes_wanted % 8 == 0); + + for (y = 0; y < 5; y++) + for (x = 0; x < 5; x++) + s->A[x][y] = 0; +} + +static void keccak_sha3_init(keccak_state *s, int hashbits) +{ + keccak_init(s, hashbits, hashbits * 2, 0x06); +} + +static void keccak_shake_init(keccak_state *s, int parambits, int hashbits) +{ + keccak_init(s, hashbits, parambits * 2, 0x1f); +} + +/* + * Keccak round constants, generated via the LFSR specified in the + * Keccak reference by the following piece of Python: + +import textwrap +from functools import reduce + +rbytes = [1] +while len(rbytes) < 7*24: + k = rbytes[-1] * 2 + rbytes.append(k ^ (0x171 * (k >> 8))) + +rbits = [byte & 1 for byte in rbytes] + +rwords = [sum(rbits[i+j] << ((1 << j) - 1) for j in range(7)) + for i in range(0, len(rbits), 7)] + +print(textwrap.indent("\n".join(textwrap.wrap(", ".join( + map("0x{:016x}".format, rwords)))), " "*4)) + +*/ + +static const uint64_t round_constants[24] = { + 0x0000000000000001, 0x0000000000008082, 0x800000000000808a, + 0x8000000080008000, 0x000000000000808b, 0x0000000080000001, + 0x8000000080008081, 0x8000000000008009, 0x000000000000008a, + 0x0000000000000088, 0x0000000080008009, 0x000000008000000a, + 0x000000008000808b, 0x800000000000008b, 0x8000000000008089, + 0x8000000000008003, 0x8000000000008002, 0x8000000000000080, + 0x000000000000800a, 0x800000008000000a, 0x8000000080008081, + 0x8000000000008080, 0x0000000080000001, 0x8000000080008008 +}; + +/* + * Keccak per-element rotation counts, generated from the matrix + * formula in the Keccak reference by the following piece of Python: + +coords = [1, 0] +while len(coords) < 26: + coords.append((2*coords[-2] + 3*coords[-1]) % 5) + +matrix = { (coords[i], coords[i+1]) : i for i in range(24) } +matrix[0,0] = -1 + +f = lambda t: (t+1) * (t+2) // 2 % 64 + +for y in range(5): + print(" {{{}}},".format(", ".join("{:2d}".format(f(matrix[y,x])) + for x in range(5)))) + +*/ +static const unsigned rotation_counts[5][5] = { + { 0, 36, 3, 41, 18}, + { 1, 44, 10, 45, 2}, + {62, 6, 43, 15, 61}, + {28, 55, 25, 21, 56}, + {27, 20, 39, 8, 14}, +}; + +/* + * The PuTTY ssh_hashalg abstraction. + */ +struct keccak_hash { + keccak_state state; + ssh_hash hash; + BinarySink_IMPLEMENTATION; +}; + +static void keccak_BinarySink_write(BinarySink *bs, const void *p, size_t len) +{ + struct keccak_hash *kh = BinarySink_DOWNCAST(bs, struct keccak_hash); + keccak_accumulate(&kh->state, p, len); +} + +static ssh_hash *keccak_new(const ssh_hashalg *alg) +{ + struct keccak_hash *kh = snew(struct keccak_hash); + kh->hash.vt = alg; + BinarySink_INIT(kh, keccak_BinarySink_write); + BinarySink_DELEGATE_INIT(&kh->hash, kh); + return ssh_hash_reset(&kh->hash); +} + +static void keccak_free(ssh_hash *hash) +{ + struct keccak_hash *kh = container_of(hash, struct keccak_hash, hash); + smemclr(kh, sizeof(*kh)); + sfree(kh); +} + +static void keccak_copyfrom(ssh_hash *hnew, ssh_hash *hold) +{ + struct keccak_hash *khold = container_of(hold, struct keccak_hash, hash); + struct keccak_hash *khnew = container_of(hnew, struct keccak_hash, hash); + khnew->state = khold->state; +} + +static void keccak_digest(ssh_hash *hash, unsigned char *output) +{ + struct keccak_hash *kh = container_of(hash, struct keccak_hash, hash); + keccak_output(&kh->state, output); +} + +static void sha3_reset(ssh_hash *hash) +{ + struct keccak_hash *kh = container_of(hash, struct keccak_hash, hash); + keccak_sha3_init(&kh->state, hash->vt->hlen * 8); +} + +#define DEFINE_SHA3(bits) \ + const ssh_hashalg ssh_sha3_##bits = { \ + .new = keccak_new, \ + .reset = sha3_reset, \ + .copyfrom = keccak_copyfrom, \ + .digest = keccak_digest, \ + .free = keccak_free, \ + .hlen = bits/8, \ + .blocklen = 200 - 2*(bits/8), \ + HASHALG_NAMES_BARE("SHA3-" #bits), \ + } + +DEFINE_SHA3(224); +DEFINE_SHA3(256); +DEFINE_SHA3(384); +DEFINE_SHA3(512); + +static void shake256_reset(ssh_hash *hash) +{ + struct keccak_hash *kh = container_of(hash, struct keccak_hash, hash); + keccak_shake_init(&kh->state, 256, hash->vt->hlen * 8); +} + +/* + * There is some confusion over the output length parameter for the + * SHAKE functions. By my reading, FIPS PUB 202 defines SHAKE256(M,d) + * to generate d _bits_ of output. But RFC 8032 (defining Ed448) talks + * about "SHAKE256(x,114)" in a context where it definitely means + * generating 114 _bytes_ of output. + * + * Our internal ID therefore suffixes the output length with "bytes", + * to be clear which we're talking about + */ + +#define DEFINE_SHAKE(param, hashbytes) \ + const ssh_hashalg ssh_shake##param##_##hashbytes##bytes = { \ + .new = keccak_new, \ + .reset = shake##param##_reset, \ + .copyfrom = keccak_copyfrom, \ + .digest = keccak_digest, \ + .free = keccak_free, \ + .hlen = hashbytes, \ + .blocklen = 0, \ + HASHALG_NAMES_BARE("SHAKE" #param), \ + } + +DEFINE_SHAKE(256, 114); diff --git a/crypto/sha512.c b/crypto/sha512.c new file mode 100644 index 00000000..cba7f38d --- /dev/null +++ b/crypto/sha512.c @@ -0,0 +1,836 @@ +/* + * SHA-512 algorithm as described at + * + * http://csrc.nist.gov/cryptval/shs.html + * + * Modifications made for SHA-384 also + */ + +#include +#include "ssh.h" + +/* + * Start by deciding whether we can support hardware SHA at all. + */ +#define HW_SHA512_NONE 0 +#define HW_SHA512_NEON 1 + +#ifdef _FORCE_SHA512_NEON +# define HW_SHA512 HW_SHA512_NEON +#elif defined __BYTE_ORDER__ && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + /* Arm can potentially support both endiannesses, but this code + * hasn't been tested on anything but little. If anyone wants to + * run big-endian, they'll need to fix it first. */ +#elif defined __ARM_FEATURE_SHA512 + /* If the Arm SHA-512 extension is available already, we can + * support NEON SHA without having to enable anything by hand */ +# define HW_SHA512 HW_SHA512_NEON +#elif defined(__clang__) +# if __has_attribute(target) && __has_include() && \ + (defined(__aarch64__)) + /* clang can enable the crypto extension in AArch64 using + * __attribute__((target)) */ +# define HW_SHA512 HW_SHA512_NEON +# define USE_CLANG_ATTR_TARGET_AARCH64 +# endif +#endif + +#if defined _FORCE_SOFTWARE_SHA || !defined HW_SHA512 +# undef HW_SHA512 +# define HW_SHA512 HW_SHA512_NONE +#endif + +/* + * The actual query function that asks if hardware acceleration is + * available. + */ +static bool sha512_hw_available(void); + +/* + * The top-level selection function, caching the results of + * sha512_hw_available() so it only has to run once. + */ +static bool sha512_hw_available_cached(void) +{ + static bool initialised = false; + static bool hw_available; + if (!initialised) { + hw_available = sha512_hw_available(); + initialised = true; + } + return hw_available; +} + +struct sha512_select_options { + const ssh_hashalg *hw, *sw; +}; + +static ssh_hash *sha512_select(const ssh_hashalg *alg) +{ + const struct sha512_select_options *options = + (const struct sha512_select_options *)alg->extra; + + const ssh_hashalg *real_alg = + sha512_hw_available_cached() ? options->hw : options->sw; + + return ssh_hash_new(real_alg); +} + +const struct sha512_select_options ssh_sha512_select_options = { + &ssh_sha512_hw, &ssh_sha512_sw, +}; +const struct sha512_select_options ssh_sha384_select_options = { + &ssh_sha384_hw, &ssh_sha384_sw, +}; + +const ssh_hashalg ssh_sha512 = { + .new = sha512_select, + .hlen = 64, + .blocklen = 128, + HASHALG_NAMES_ANNOTATED("SHA-512", "dummy selector vtable"), + .extra = &ssh_sha512_select_options, +}; + +const ssh_hashalg ssh_sha384 = { + .new = sha512_select, + .hlen = 48, + .blocklen = 128, + HASHALG_NAMES_ANNOTATED("SHA-384", "dummy selector vtable"), + .extra = &ssh_sha384_select_options, +}; + +/* ---------------------------------------------------------------------- + * Definitions likely to be helpful to multiple implementations. + */ + +static const uint64_t sha512_initial_state[] = { + 0x6a09e667f3bcc908ULL, + 0xbb67ae8584caa73bULL, + 0x3c6ef372fe94f82bULL, + 0xa54ff53a5f1d36f1ULL, + 0x510e527fade682d1ULL, + 0x9b05688c2b3e6c1fULL, + 0x1f83d9abfb41bd6bULL, + 0x5be0cd19137e2179ULL, +}; + +static const uint64_t sha384_initial_state[] = { + 0xcbbb9d5dc1059ed8ULL, + 0x629a292a367cd507ULL, + 0x9159015a3070dd17ULL, + 0x152fecd8f70e5939ULL, + 0x67332667ffc00b31ULL, + 0x8eb44a8768581511ULL, + 0xdb0c2e0d64f98fa7ULL, + 0x47b5481dbefa4fa4ULL, +}; + +static const uint64_t sha512_round_constants[] = { + 0x428a2f98d728ae22ULL, 0x7137449123ef65cdULL, + 0xb5c0fbcfec4d3b2fULL, 0xe9b5dba58189dbbcULL, + 0x3956c25bf348b538ULL, 0x59f111f1b605d019ULL, + 0x923f82a4af194f9bULL, 0xab1c5ed5da6d8118ULL, + 0xd807aa98a3030242ULL, 0x12835b0145706fbeULL, + 0x243185be4ee4b28cULL, 0x550c7dc3d5ffb4e2ULL, + 0x72be5d74f27b896fULL, 0x80deb1fe3b1696b1ULL, + 0x9bdc06a725c71235ULL, 0xc19bf174cf692694ULL, + 0xe49b69c19ef14ad2ULL, 0xefbe4786384f25e3ULL, + 0x0fc19dc68b8cd5b5ULL, 0x240ca1cc77ac9c65ULL, + 0x2de92c6f592b0275ULL, 0x4a7484aa6ea6e483ULL, + 0x5cb0a9dcbd41fbd4ULL, 0x76f988da831153b5ULL, + 0x983e5152ee66dfabULL, 0xa831c66d2db43210ULL, + 0xb00327c898fb213fULL, 0xbf597fc7beef0ee4ULL, + 0xc6e00bf33da88fc2ULL, 0xd5a79147930aa725ULL, + 0x06ca6351e003826fULL, 0x142929670a0e6e70ULL, + 0x27b70a8546d22ffcULL, 0x2e1b21385c26c926ULL, + 0x4d2c6dfc5ac42aedULL, 0x53380d139d95b3dfULL, + 0x650a73548baf63deULL, 0x766a0abb3c77b2a8ULL, + 0x81c2c92e47edaee6ULL, 0x92722c851482353bULL, + 0xa2bfe8a14cf10364ULL, 0xa81a664bbc423001ULL, + 0xc24b8b70d0f89791ULL, 0xc76c51a30654be30ULL, + 0xd192e819d6ef5218ULL, 0xd69906245565a910ULL, + 0xf40e35855771202aULL, 0x106aa07032bbd1b8ULL, + 0x19a4c116b8d2d0c8ULL, 0x1e376c085141ab53ULL, + 0x2748774cdf8eeb99ULL, 0x34b0bcb5e19b48a8ULL, + 0x391c0cb3c5c95a63ULL, 0x4ed8aa4ae3418acbULL, + 0x5b9cca4f7763e373ULL, 0x682e6ff3d6b2b8a3ULL, + 0x748f82ee5defb2fcULL, 0x78a5636f43172f60ULL, + 0x84c87814a1f0ab72ULL, 0x8cc702081a6439ecULL, + 0x90befffa23631e28ULL, 0xa4506cebde82bde9ULL, + 0xbef9a3f7b2c67915ULL, 0xc67178f2e372532bULL, + 0xca273eceea26619cULL, 0xd186b8c721c0c207ULL, + 0xeada7dd6cde0eb1eULL, 0xf57d4f7fee6ed178ULL, + 0x06f067aa72176fbaULL, 0x0a637dc5a2c898a6ULL, + 0x113f9804bef90daeULL, 0x1b710b35131c471bULL, + 0x28db77f523047d84ULL, 0x32caab7b40c72493ULL, + 0x3c9ebe0a15c9bebcULL, 0x431d67c49c100d4cULL, + 0x4cc5d4becb3e42b6ULL, 0x597f299cfc657e2aULL, + 0x5fcb6fab3ad6faecULL, 0x6c44198c4a475817ULL, +}; + +#define SHA512_ROUNDS 80 + +typedef struct sha512_block sha512_block; +struct sha512_block { + uint8_t block[128]; + size_t used; + uint64_t lenhi, lenlo; +}; + +static inline void sha512_block_setup(sha512_block *blk) +{ + blk->used = 0; + blk->lenhi = blk->lenlo = 0; +} + +static inline bool sha512_block_write( + sha512_block *blk, const void **vdata, size_t *len) +{ + size_t blkleft = sizeof(blk->block) - blk->used; + size_t chunk = *len < blkleft ? *len : blkleft; + + const uint8_t *p = *vdata; + memcpy(blk->block + blk->used, p, chunk); + *vdata = p + chunk; + *len -= chunk; + blk->used += chunk; + + size_t chunkbits = chunk << 3; + + blk->lenlo += chunkbits; + blk->lenhi += (blk->lenlo < chunkbits); + + if (blk->used == sizeof(blk->block)) { + blk->used = 0; + return true; + } + + return false; +} + +static inline void sha512_block_pad(sha512_block *blk, BinarySink *bs) +{ + uint64_t final_lenhi = blk->lenhi; + uint64_t final_lenlo = blk->lenlo; + size_t pad = 127 & (111 - blk->used); + + put_byte(bs, 0x80); + put_padding(bs, pad, 0); + put_uint64(bs, final_lenhi); + put_uint64(bs, final_lenlo); + + assert(blk->used == 0 && "Should have exactly hit a block boundary"); +} + +/* ---------------------------------------------------------------------- + * Software implementation of SHA-512. + */ + +static inline uint64_t ror(uint64_t x, unsigned y) +{ + return (x << (63 & -y)) | (x >> (63 & y)); +} + +static inline uint64_t Ch(uint64_t ctrl, uint64_t if1, uint64_t if0) +{ + return if0 ^ (ctrl & (if1 ^ if0)); +} + +static inline uint64_t Maj(uint64_t x, uint64_t y, uint64_t z) +{ + return (x & y) | (z & (x | y)); +} + +static inline uint64_t Sigma_0(uint64_t x) +{ + return ror(x,28) ^ ror(x,34) ^ ror(x,39); +} + +static inline uint64_t Sigma_1(uint64_t x) +{ + return ror(x,14) ^ ror(x,18) ^ ror(x,41); +} + +static inline uint64_t sigma_0(uint64_t x) +{ + return ror(x,1) ^ ror(x,8) ^ (x >> 7); +} + +static inline uint64_t sigma_1(uint64_t x) +{ + return ror(x,19) ^ ror(x,61) ^ (x >> 6); +} + +static inline void sha512_sw_round( + unsigned round_index, const uint64_t *schedule, + uint64_t *a, uint64_t *b, uint64_t *c, uint64_t *d, + uint64_t *e, uint64_t *f, uint64_t *g, uint64_t *h) +{ + uint64_t t1 = *h + Sigma_1(*e) + Ch(*e,*f,*g) + + sha512_round_constants[round_index] + schedule[round_index]; + + uint64_t t2 = Sigma_0(*a) + Maj(*a,*b,*c); + + *d += t1; + *h = t1 + t2; +} + +static void sha512_sw_block(uint64_t *core, const uint8_t *block) +{ + uint64_t w[SHA512_ROUNDS]; + uint64_t a,b,c,d,e,f,g,h; + + int t; + + for (t = 0; t < 16; t++) + w[t] = GET_64BIT_MSB_FIRST(block + 8*t); + + for (t = 16; t < SHA512_ROUNDS; t++) + w[t] = w[t-16] + w[t-7] + sigma_0(w[t-15]) + sigma_1(w[t-2]); + + a = core[0]; b = core[1]; c = core[2]; d = core[3]; + e = core[4]; f = core[5]; g = core[6]; h = core[7]; + + for (t = 0; t < SHA512_ROUNDS; t+=8) { + sha512_sw_round(t+0, w, &a,&b,&c,&d,&e,&f,&g,&h); + sha512_sw_round(t+1, w, &h,&a,&b,&c,&d,&e,&f,&g); + sha512_sw_round(t+2, w, &g,&h,&a,&b,&c,&d,&e,&f); + sha512_sw_round(t+3, w, &f,&g,&h,&a,&b,&c,&d,&e); + sha512_sw_round(t+4, w, &e,&f,&g,&h,&a,&b,&c,&d); + sha512_sw_round(t+5, w, &d,&e,&f,&g,&h,&a,&b,&c); + sha512_sw_round(t+6, w, &c,&d,&e,&f,&g,&h,&a,&b); + sha512_sw_round(t+7, w, &b,&c,&d,&e,&f,&g,&h,&a); + } + + core[0] += a; core[1] += b; core[2] += c; core[3] += d; + core[4] += e; core[5] += f; core[6] += g; core[7] += h; + + smemclr(w, sizeof(w)); +} + +typedef struct sha512_sw { + uint64_t core[8]; + sha512_block blk; + BinarySink_IMPLEMENTATION; + ssh_hash hash; +} sha512_sw; + +static void sha512_sw_write(BinarySink *bs, const void *vp, size_t len); + +static ssh_hash *sha512_sw_new(const ssh_hashalg *alg) +{ + sha512_sw *s = snew(sha512_sw); + + s->hash.vt = alg; + BinarySink_INIT(s, sha512_sw_write); + BinarySink_DELEGATE_INIT(&s->hash, s); + return &s->hash; +} + +static void sha512_sw_reset(ssh_hash *hash) +{ + sha512_sw *s = container_of(hash, sha512_sw, hash); + + /* The 'extra' field in the ssh_hashalg indicates which + * initialisation vector we're using */ + memcpy(s->core, hash->vt->extra, sizeof(s->core)); + sha512_block_setup(&s->blk); +} + +static void sha512_sw_copyfrom(ssh_hash *hcopy, ssh_hash *horig) +{ + sha512_sw *copy = container_of(hcopy, sha512_sw, hash); + sha512_sw *orig = container_of(horig, sha512_sw, hash); + + memcpy(copy, orig, sizeof(*copy)); + BinarySink_COPIED(copy); + BinarySink_DELEGATE_INIT(©->hash, copy); +} + +static void sha512_sw_free(ssh_hash *hash) +{ + sha512_sw *s = container_of(hash, sha512_sw, hash); + + smemclr(s, sizeof(*s)); + sfree(s); +} + +static void sha512_sw_write(BinarySink *bs, const void *vp, size_t len) +{ + sha512_sw *s = BinarySink_DOWNCAST(bs, sha512_sw); + + while (len > 0) + if (sha512_block_write(&s->blk, &vp, &len)) + sha512_sw_block(s->core, s->blk.block); +} + +static void sha512_sw_digest(ssh_hash *hash, uint8_t *digest) +{ + sha512_sw *s = container_of(hash, sha512_sw, hash); + + sha512_block_pad(&s->blk, BinarySink_UPCAST(s)); + for (size_t i = 0; i < hash->vt->hlen / 8; i++) + PUT_64BIT_MSB_FIRST(digest + 8*i, s->core[i]); +} + +const ssh_hashalg ssh_sha512_sw = { + .new = sha512_sw_new, + .reset = sha512_sw_reset, + .copyfrom = sha512_sw_copyfrom, + .digest = sha512_sw_digest, + .free = sha512_sw_free, + .hlen = 64, + .blocklen = 128, + HASHALG_NAMES_ANNOTATED("SHA-512", "unaccelerated"), + .extra = sha512_initial_state, +}; + +const ssh_hashalg ssh_sha384_sw = { + .new = sha512_sw_new, + .reset = sha512_sw_reset, + .copyfrom = sha512_sw_copyfrom, + .digest = sha512_sw_digest, + .free = sha512_sw_free, + .hlen = 48, + .blocklen = 128, + HASHALG_NAMES_ANNOTATED("SHA-384", "unaccelerated"), + .extra = sha384_initial_state, +}; + +/* ---------------------------------------------------------------------- + * Hardware-accelerated implementation of SHA-512 using Arm NEON. + */ + +#if HW_SHA512 == HW_SHA512_NEON + +/* + * Manually set the target architecture, if we decided above that we + * need to. + */ +#ifdef USE_CLANG_ATTR_TARGET_AARCH64 +/* + * A spot of cheating: redefine some ACLE feature macros before + * including arm_neon.h. Otherwise we won't get the SHA intrinsics + * defined by that header, because it will be looking at the settings + * for the whole translation unit rather than the ones we're going to + * put on some particular functions using __attribute__((target)). + */ +#define __ARM_NEON 1 +#define __ARM_FEATURE_CRYPTO 1 +#define FUNC_ISA __attribute__ ((target("neon,sha3"))) +#endif /* USE_CLANG_ATTR_TARGET_AARCH64 */ + +#ifndef FUNC_ISA +#define FUNC_ISA +#endif + +#ifdef USE_ARM64_NEON_H +#include +#else +#include +#endif + +static bool sha512_hw_available(void) +{ + /* + * For Arm, we delegate to a per-platform detection function (see + * explanation in sshaes.c). + */ + return platform_sha512_hw_available(); +} + +#if defined __clang__ +/* + * As of 2020-12-24, I've found that clang doesn't provide the SHA-512 + * NEON intrinsics. So I define my own set using inline assembler, and + * use #define to effectively rename them over the top of the standard + * names. + * + * The aim of that #define technique is that it should avoid a build + * failure if these intrinsics _are_ defined in . + * Obviously it would be better in that situation to switch back to + * using the real intrinsics, but until I see a version of clang that + * supports them, I won't know what version number to test in the + * ifdef. + */ +static inline FUNC_ISA +uint64x2_t vsha512su0q_u64_asm(uint64x2_t x, uint64x2_t y) { + __asm__("sha512su0 %0.2D,%1.2D" : "+w" (x) : "w" (y)); + return x; +} +static inline FUNC_ISA +uint64x2_t vsha512su1q_u64_asm(uint64x2_t x, uint64x2_t y, uint64x2_t z) { + __asm__("sha512su1 %0.2D,%1.2D,%2.2D" : "+w" (x) : "w" (y), "w" (z)); + return x; +} +static inline FUNC_ISA +uint64x2_t vsha512hq_u64_asm(uint64x2_t x, uint64x2_t y, uint64x2_t z) { + __asm__("sha512h %0,%1,%2.2D" : "+w" (x) : "w" (y), "w" (z)); + return x; +} +static inline FUNC_ISA +uint64x2_t vsha512h2q_u64_asm(uint64x2_t x, uint64x2_t y, uint64x2_t z) { + __asm__("sha512h2 %0,%1,%2.2D" : "+w" (x) : "w" (y), "w" (z)); + return x; +} +#undef vsha512su0q_u64 +#define vsha512su0q_u64 vsha512su0q_u64_asm +#undef vsha512su1q_u64 +#define vsha512su1q_u64 vsha512su1q_u64_asm +#undef vsha512hq_u64 +#define vsha512hq_u64 vsha512hq_u64_asm +#undef vsha512h2q_u64 +#define vsha512h2q_u64 vsha512h2q_u64_asm +#endif /* defined __clang__ */ + +typedef struct sha512_neon_core sha512_neon_core; +struct sha512_neon_core { + uint64x2_t ab, cd, ef, gh; +}; + +FUNC_ISA +static inline uint64x2_t sha512_neon_load_input(const uint8_t *p) +{ + return vreinterpretq_u64_u8(vrev64q_u8(vld1q_u8(p))); +} + +FUNC_ISA +static inline uint64x2_t sha512_neon_schedule_update( + uint64x2_t m8, uint64x2_t m7, uint64x2_t m4, uint64x2_t m3, uint64x2_t m1) +{ + /* + * vsha512su0q_u64() takes words from a long way back in the + * schedule and performs the sigma_0 half of the computation of + * the next two 64-bit message-schedule words. + * + * vsha512su1q_u64() combines the result of that with the sigma_1 + * steps, to output the finished version of those two words. The + * total amount of input data it requires fits nicely into three + * 128-bit vector registers, but one of those registers is + * misaligned compared to the 128-bit chunks that the message + * schedule is stored in. So we use vextq_u64 to make one of its + * input words out of the second half of m4 and the first half of + * m3. + */ + return vsha512su1q_u64(vsha512su0q_u64(m8, m7), m1, vextq_u64(m4, m3, 1)); +} + +FUNC_ISA +static inline void sha512_neon_round2( + unsigned round_index, uint64x2_t schedule_words, + uint64x2_t *ab, uint64x2_t *cd, uint64x2_t *ef, uint64x2_t *gh) +{ + /* + * vsha512hq_u64 performs the Sigma_1 and Ch half of the + * computation of two rounds of SHA-512 (including feeding back + * one of the outputs from the first of those half-rounds into the + * second one). + * + * vsha512h2q_u64 combines the result of that with the Sigma_0 and + * Maj steps, and outputs one 128-bit vector that replaces the gh + * piece of the input hash state, and a second that updates cd by + * addition. + * + * Similarly to vsha512su1q_u64 above, some of the input registers + * expected by these instructions are misaligned by 64 bits + * relative to the chunks we've divided the hash state into, so we + * have to start by making 'de' and 'fg' words out of our input + * cd,ef,gh, using vextq_u64. + * + * Also, one of the inputs to vsha512hq_u64 is expected to contain + * the results of summing gh + two round constants + two words of + * message schedule, but the two words of the message schedule + * have to be the opposite way round in the vector register from + * the way that vsha512su1q_u64 output them. Hence, there's + * another vextq_u64 in here that swaps the two halves of the + * initial_sum vector register. + * + * (This also means that I don't have to prepare a specially + * reordered version of the sha512_round_constants[] array: as + * long as I'm unavoidably doing a swap at run time _anyway_, I + * can load from the normally ordered version of that array, and + * just take care to fold in that data _before_ the swap rather + * than after.) + */ + + /* Load two round constants, with the first one in the low half */ + uint64x2_t round_constants = vld1q_u64( + sha512_round_constants + round_index); + + /* Add schedule words to round constants */ + uint64x2_t initial_sum = vaddq_u64(schedule_words, round_constants); + + /* Swap that sum around so the word used in the first of the two + * rounds is in the _high_ half of the vector, matching where h + * lives in the gh vector */ + uint64x2_t swapped_initial_sum = vextq_u64(initial_sum, initial_sum, 1); + + /* Add gh to that, now that they're matching ways round */ + uint64x2_t sum = vaddq_u64(swapped_initial_sum, *gh); + + /* Make the misaligned de and fg words */ + uint64x2_t de = vextq_u64(*cd, *ef, 1); + uint64x2_t fg = vextq_u64(*ef, *gh, 1); + + /* Now we're ready to put all the pieces together. The output from + * vsha512h2q_u64 can be used directly as the new gh, and the + * output from vsha512hq_u64 is simultaneously the intermediate + * value passed to h2 and the thing you have to add on to cd. */ + uint64x2_t intermed = vsha512hq_u64(sum, fg, de); + *gh = vsha512h2q_u64(intermed, *cd, *ab); + *cd = vaddq_u64(*cd, intermed); +} + +FUNC_ISA +static inline void sha512_neon_block(sha512_neon_core *core, const uint8_t *p) +{ + uint64x2_t s0, s1, s2, s3, s4, s5, s6, s7; + + uint64x2_t ab = core->ab, cd = core->cd, ef = core->ef, gh = core->gh; + + s0 = sha512_neon_load_input(p + 16*0); + sha512_neon_round2(0, s0, &ab, &cd, &ef, &gh); + s1 = sha512_neon_load_input(p + 16*1); + sha512_neon_round2(2, s1, &gh, &ab, &cd, &ef); + s2 = sha512_neon_load_input(p + 16*2); + sha512_neon_round2(4, s2, &ef, &gh, &ab, &cd); + s3 = sha512_neon_load_input(p + 16*3); + sha512_neon_round2(6, s3, &cd, &ef, &gh, &ab); + s4 = sha512_neon_load_input(p + 16*4); + sha512_neon_round2(8, s4, &ab, &cd, &ef, &gh); + s5 = sha512_neon_load_input(p + 16*5); + sha512_neon_round2(10, s5, &gh, &ab, &cd, &ef); + s6 = sha512_neon_load_input(p + 16*6); + sha512_neon_round2(12, s6, &ef, &gh, &ab, &cd); + s7 = sha512_neon_load_input(p + 16*7); + sha512_neon_round2(14, s7, &cd, &ef, &gh, &ab); + s0 = sha512_neon_schedule_update(s0, s1, s4, s5, s7); + sha512_neon_round2(16, s0, &ab, &cd, &ef, &gh); + s1 = sha512_neon_schedule_update(s1, s2, s5, s6, s0); + sha512_neon_round2(18, s1, &gh, &ab, &cd, &ef); + s2 = sha512_neon_schedule_update(s2, s3, s6, s7, s1); + sha512_neon_round2(20, s2, &ef, &gh, &ab, &cd); + s3 = sha512_neon_schedule_update(s3, s4, s7, s0, s2); + sha512_neon_round2(22, s3, &cd, &ef, &gh, &ab); + s4 = sha512_neon_schedule_update(s4, s5, s0, s1, s3); + sha512_neon_round2(24, s4, &ab, &cd, &ef, &gh); + s5 = sha512_neon_schedule_update(s5, s6, s1, s2, s4); + sha512_neon_round2(26, s5, &gh, &ab, &cd, &ef); + s6 = sha512_neon_schedule_update(s6, s7, s2, s3, s5); + sha512_neon_round2(28, s6, &ef, &gh, &ab, &cd); + s7 = sha512_neon_schedule_update(s7, s0, s3, s4, s6); + sha512_neon_round2(30, s7, &cd, &ef, &gh, &ab); + s0 = sha512_neon_schedule_update(s0, s1, s4, s5, s7); + sha512_neon_round2(32, s0, &ab, &cd, &ef, &gh); + s1 = sha512_neon_schedule_update(s1, s2, s5, s6, s0); + sha512_neon_round2(34, s1, &gh, &ab, &cd, &ef); + s2 = sha512_neon_schedule_update(s2, s3, s6, s7, s1); + sha512_neon_round2(36, s2, &ef, &gh, &ab, &cd); + s3 = sha512_neon_schedule_update(s3, s4, s7, s0, s2); + sha512_neon_round2(38, s3, &cd, &ef, &gh, &ab); + s4 = sha512_neon_schedule_update(s4, s5, s0, s1, s3); + sha512_neon_round2(40, s4, &ab, &cd, &ef, &gh); + s5 = sha512_neon_schedule_update(s5, s6, s1, s2, s4); + sha512_neon_round2(42, s5, &gh, &ab, &cd, &ef); + s6 = sha512_neon_schedule_update(s6, s7, s2, s3, s5); + sha512_neon_round2(44, s6, &ef, &gh, &ab, &cd); + s7 = sha512_neon_schedule_update(s7, s0, s3, s4, s6); + sha512_neon_round2(46, s7, &cd, &ef, &gh, &ab); + s0 = sha512_neon_schedule_update(s0, s1, s4, s5, s7); + sha512_neon_round2(48, s0, &ab, &cd, &ef, &gh); + s1 = sha512_neon_schedule_update(s1, s2, s5, s6, s0); + sha512_neon_round2(50, s1, &gh, &ab, &cd, &ef); + s2 = sha512_neon_schedule_update(s2, s3, s6, s7, s1); + sha512_neon_round2(52, s2, &ef, &gh, &ab, &cd); + s3 = sha512_neon_schedule_update(s3, s4, s7, s0, s2); + sha512_neon_round2(54, s3, &cd, &ef, &gh, &ab); + s4 = sha512_neon_schedule_update(s4, s5, s0, s1, s3); + sha512_neon_round2(56, s4, &ab, &cd, &ef, &gh); + s5 = sha512_neon_schedule_update(s5, s6, s1, s2, s4); + sha512_neon_round2(58, s5, &gh, &ab, &cd, &ef); + s6 = sha512_neon_schedule_update(s6, s7, s2, s3, s5); + sha512_neon_round2(60, s6, &ef, &gh, &ab, &cd); + s7 = sha512_neon_schedule_update(s7, s0, s3, s4, s6); + sha512_neon_round2(62, s7, &cd, &ef, &gh, &ab); + s0 = sha512_neon_schedule_update(s0, s1, s4, s5, s7); + sha512_neon_round2(64, s0, &ab, &cd, &ef, &gh); + s1 = sha512_neon_schedule_update(s1, s2, s5, s6, s0); + sha512_neon_round2(66, s1, &gh, &ab, &cd, &ef); + s2 = sha512_neon_schedule_update(s2, s3, s6, s7, s1); + sha512_neon_round2(68, s2, &ef, &gh, &ab, &cd); + s3 = sha512_neon_schedule_update(s3, s4, s7, s0, s2); + sha512_neon_round2(70, s3, &cd, &ef, &gh, &ab); + s4 = sha512_neon_schedule_update(s4, s5, s0, s1, s3); + sha512_neon_round2(72, s4, &ab, &cd, &ef, &gh); + s5 = sha512_neon_schedule_update(s5, s6, s1, s2, s4); + sha512_neon_round2(74, s5, &gh, &ab, &cd, &ef); + s6 = sha512_neon_schedule_update(s6, s7, s2, s3, s5); + sha512_neon_round2(76, s6, &ef, &gh, &ab, &cd); + s7 = sha512_neon_schedule_update(s7, s0, s3, s4, s6); + sha512_neon_round2(78, s7, &cd, &ef, &gh, &ab); + + core->ab = vaddq_u64(core->ab, ab); + core->cd = vaddq_u64(core->cd, cd); + core->ef = vaddq_u64(core->ef, ef); + core->gh = vaddq_u64(core->gh, gh); +} + +typedef struct sha512_neon { + sha512_neon_core core; + sha512_block blk; + BinarySink_IMPLEMENTATION; + ssh_hash hash; +} sha512_neon; + +static void sha512_neon_write(BinarySink *bs, const void *vp, size_t len); + +static ssh_hash *sha512_neon_new(const ssh_hashalg *alg) +{ + if (!sha512_hw_available_cached()) + return NULL; + + sha512_neon *s = snew(sha512_neon); + + s->hash.vt = alg; + BinarySink_INIT(s, sha512_neon_write); + BinarySink_DELEGATE_INIT(&s->hash, s); + return &s->hash; +} + +static void sha512_neon_reset(ssh_hash *hash) +{ + sha512_neon *s = container_of(hash, sha512_neon, hash); + const uint64_t *iv = (const uint64_t *)hash->vt->extra; + + s->core.ab = vld1q_u64(iv); + s->core.cd = vld1q_u64(iv+2); + s->core.ef = vld1q_u64(iv+4); + s->core.gh = vld1q_u64(iv+6); + + sha512_block_setup(&s->blk); +} + +static void sha512_neon_copyfrom(ssh_hash *hcopy, ssh_hash *horig) +{ + sha512_neon *copy = container_of(hcopy, sha512_neon, hash); + sha512_neon *orig = container_of(horig, sha512_neon, hash); + + *copy = *orig; /* structure copy */ + + BinarySink_COPIED(copy); + BinarySink_DELEGATE_INIT(©->hash, copy); +} + +static void sha512_neon_free(ssh_hash *hash) +{ + sha512_neon *s = container_of(hash, sha512_neon, hash); + smemclr(s, sizeof(*s)); + sfree(s); +} + +static void sha512_neon_write(BinarySink *bs, const void *vp, size_t len) +{ + sha512_neon *s = BinarySink_DOWNCAST(bs, sha512_neon); + + while (len > 0) + if (sha512_block_write(&s->blk, &vp, &len)) + sha512_neon_block(&s->core, s->blk.block); +} + +static void sha512_neon_digest(ssh_hash *hash, uint8_t *digest) +{ + sha512_neon *s = container_of(hash, sha512_neon, hash); + + sha512_block_pad(&s->blk, BinarySink_UPCAST(s)); + + vst1q_u8(digest, vrev64q_u8(vreinterpretq_u8_u64(s->core.ab))); + vst1q_u8(digest+16, vrev64q_u8(vreinterpretq_u8_u64(s->core.cd))); + vst1q_u8(digest+32, vrev64q_u8(vreinterpretq_u8_u64(s->core.ef))); + vst1q_u8(digest+48, vrev64q_u8(vreinterpretq_u8_u64(s->core.gh))); +} + +static void sha384_neon_digest(ssh_hash *hash, uint8_t *digest) +{ + sha512_neon *s = container_of(hash, sha512_neon, hash); + + sha512_block_pad(&s->blk, BinarySink_UPCAST(s)); + + vst1q_u8(digest, vrev64q_u8(vreinterpretq_u8_u64(s->core.ab))); + vst1q_u8(digest+16, vrev64q_u8(vreinterpretq_u8_u64(s->core.cd))); + vst1q_u8(digest+32, vrev64q_u8(vreinterpretq_u8_u64(s->core.ef))); +} + +const ssh_hashalg ssh_sha512_hw = { + .new = sha512_neon_new, + .reset = sha512_neon_reset, + .copyfrom = sha512_neon_copyfrom, + .digest = sha512_neon_digest, + .free = sha512_neon_free, + .hlen = 64, + .blocklen = 128, + HASHALG_NAMES_ANNOTATED("SHA-512", "NEON accelerated"), + .extra = sha512_initial_state, +}; + +const ssh_hashalg ssh_sha384_hw = { + .new = sha512_neon_new, + .reset = sha512_neon_reset, + .copyfrom = sha512_neon_copyfrom, + .digest = sha384_neon_digest, + .free = sha512_neon_free, + .hlen = 48, + .blocklen = 128, + HASHALG_NAMES_ANNOTATED("SHA-384", "NEON accelerated"), + .extra = sha384_initial_state, +}; + +/* ---------------------------------------------------------------------- + * Stub functions if we have no hardware-accelerated SHA-512. In this + * case, sha512_hw_new returns NULL (though it should also never be + * selected by sha512_select, so the only thing that should even be + * _able_ to call it is testcrypt). As a result, the remaining vtable + * functions should never be called at all. + */ + +#elif HW_SHA512 == HW_SHA512_NONE + +static bool sha512_hw_available(void) +{ + return false; +} + +static ssh_hash *sha512_stub_new(const ssh_hashalg *alg) +{ + return NULL; +} + +#define STUB_BODY { unreachable("Should never be called"); } + +static void sha512_stub_reset(ssh_hash *hash) STUB_BODY +static void sha512_stub_copyfrom(ssh_hash *hash, ssh_hash *orig) STUB_BODY +static void sha512_stub_free(ssh_hash *hash) STUB_BODY +static void sha512_stub_digest(ssh_hash *hash, uint8_t *digest) STUB_BODY + +const ssh_hashalg ssh_sha512_hw = { + .new = sha512_stub_new, + .reset = sha512_stub_reset, + .copyfrom = sha512_stub_copyfrom, + .digest = sha512_stub_digest, + .free = sha512_stub_free, + .hlen = 64, + .blocklen = 128, + HASHALG_NAMES_ANNOTATED("SHA-512", "!NONEXISTENT ACCELERATED VERSION!"), +}; + +const ssh_hashalg ssh_sha384_hw = { + .new = sha512_stub_new, + .reset = sha512_stub_reset, + .copyfrom = sha512_stub_copyfrom, + .digest = sha512_stub_digest, + .free = sha512_stub_free, + .hlen = 48, + .blocklen = 128, + HASHALG_NAMES_ANNOTATED("SHA-384", "!NONEXISTENT ACCELERATED VERSION!"), +}; + +#endif /* HW_SHA512 */ diff --git a/crypto/xdmauth.c b/crypto/xdmauth.c new file mode 100644 index 00000000..86339b85 --- /dev/null +++ b/crypto/xdmauth.c @@ -0,0 +1,53 @@ +/* + * Convenience functions to encrypt and decrypt the cookies used in + * XDM-AUTHORIZATION-1. + */ + +#include "ssh.h" + +static ssh_cipher *des_xdmauth_cipher(const void *vkeydata) +{ + /* + * XDM-AUTHORIZATION-1 uses single-DES, but packs the key into 7 + * bytes, so here we have to repack it manually into the canonical + * form where it occupies 8 bytes each with the low bit unused. + */ + const unsigned char *keydata = (const unsigned char *)vkeydata; + 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; + } + + ssh_cipher *c = ssh_cipher_new(&ssh_des); + ssh_cipher_setkey(c, key); + smemclr(key, sizeof(key)); + ssh_cipher_setiv(c, key); + return c; +} + +void des_encrypt_xdmauth(const void *keydata, void *blk, int len) +{ + ssh_cipher *c = des_xdmauth_cipher(keydata); + ssh_cipher_encrypt(c, blk, len); + ssh_cipher_free(c); +} + +void des_decrypt_xdmauth(const void *keydata, void *blk, int len) +{ + ssh_cipher *c = des_xdmauth_cipher(keydata); + ssh_cipher_decrypt(c, blk, len); + ssh_cipher_free(c); +} diff --git a/ecc.c b/ecc.c deleted file mode 100644 index 72f9bfe5..00000000 --- a/ecc.c +++ /dev/null @@ -1,1167 +0,0 @@ -#include - -#include "ssh.h" -#include "mpint.h" -#include "ecc.h" - -/* ---------------------------------------------------------------------- - * Weierstrass curves. - */ - -struct WeierstrassPoint { - /* - * Internally, we represent a point using 'Jacobian coordinates', - * which are three values X,Y,Z whose relation to the affine - * coordinates x,y is that x = X/Z^2 and y = Y/Z^3. - * - * This allows us to do most of our calculations without having to - * take an inverse mod p: every time the obvious affine formulae - * would need you to divide by something, you instead multiply it - * into the 'denominator' coordinate Z. You only have to actually - * take the inverse of Z when you need to get the affine - * coordinates back out, which means you do it once after your - * entire computation instead of at every intermediate step. - * - * The point at infinity is represented by setting all three - * coordinates to zero. - * - * These values are also stored in the Montgomery-multiplication - * transformed representation. - */ - mp_int *X, *Y, *Z; - - WeierstrassCurve *wc; -}; - -struct WeierstrassCurve { - /* Prime modulus of the finite field. */ - mp_int *p; - - /* Persistent Montgomery context for doing arithmetic mod p. */ - MontyContext *mc; - - /* Modsqrt context for point decompression. NULL if this curve was - * constructed without providing nonsquare_mod_p. */ - ModsqrtContext *sc; - - /* Parameters of the curve, in Montgomery-multiplication - * transformed form. */ - mp_int *a, *b; -}; - -WeierstrassCurve *ecc_weierstrass_curve( - mp_int *p, mp_int *a, mp_int *b, mp_int *nonsquare_mod_p) -{ - WeierstrassCurve *wc = snew(WeierstrassCurve); - wc->p = mp_copy(p); - wc->mc = monty_new(p); - wc->a = monty_import(wc->mc, a); - wc->b = monty_import(wc->mc, b); - - if (nonsquare_mod_p) - wc->sc = modsqrt_new(p, nonsquare_mod_p); - else - wc->sc = NULL; - - return wc; -} - -void ecc_weierstrass_curve_free(WeierstrassCurve *wc) -{ - mp_free(wc->p); - mp_free(wc->a); - mp_free(wc->b); - monty_free(wc->mc); - if (wc->sc) - modsqrt_free(wc->sc); - sfree(wc); -} - -static WeierstrassPoint *ecc_weierstrass_point_new_empty(WeierstrassCurve *wc) -{ - WeierstrassPoint *wp = snew(WeierstrassPoint); - wp->wc = wc; - wp->X = wp->Y = wp->Z = NULL; - return wp; -} - -static WeierstrassPoint *ecc_weierstrass_point_new_imported( - WeierstrassCurve *wc, mp_int *monty_x, mp_int *monty_y) -{ - WeierstrassPoint *wp = ecc_weierstrass_point_new_empty(wc); - wp->X = monty_x; - wp->Y = monty_y; - wp->Z = mp_copy(monty_identity(wc->mc)); - return wp; -} - -WeierstrassPoint *ecc_weierstrass_point_new( - WeierstrassCurve *wc, mp_int *x, mp_int *y) -{ - return ecc_weierstrass_point_new_imported( - wc, monty_import(wc->mc, x), monty_import(wc->mc, y)); -} - -WeierstrassPoint *ecc_weierstrass_point_new_identity(WeierstrassCurve *wc) -{ - WeierstrassPoint *wp = ecc_weierstrass_point_new_empty(wc); - size_t bits = mp_max_bits(wc->p); - wp->X = mp_new(bits); - wp->Y = mp_new(bits); - wp->Z = mp_new(bits); - return wp; -} - -void ecc_weierstrass_point_copy_into( - WeierstrassPoint *dest, WeierstrassPoint *src) -{ - mp_copy_into(dest->X, src->X); - mp_copy_into(dest->Y, src->Y); - mp_copy_into(dest->Z, src->Z); -} - -WeierstrassPoint *ecc_weierstrass_point_copy(WeierstrassPoint *orig) -{ - WeierstrassPoint *wp = ecc_weierstrass_point_new_empty(orig->wc); - wp->X = mp_copy(orig->X); - wp->Y = mp_copy(orig->Y); - wp->Z = mp_copy(orig->Z); - return wp; -} - -void ecc_weierstrass_point_free(WeierstrassPoint *wp) -{ - mp_free(wp->X); - mp_free(wp->Y); - mp_free(wp->Z); - smemclr(wp, sizeof(*wp)); - sfree(wp); -} - -WeierstrassPoint *ecc_weierstrass_point_new_from_x( - WeierstrassCurve *wc, mp_int *xorig, unsigned desired_y_parity) -{ - assert(wc->sc); - - /* - * The curve equation is y^2 = x^3 + ax + b, which is already - * conveniently in a form where we can compute the RHS and take - * the square root of it to get y. - */ - unsigned success; - - mp_int *x = monty_import(wc->mc, xorig); - - /* - * Compute the RHS of the curve equation. We don't need to take - * account of z here, because we're constructing the point from - * scratch. So it really is just x^3 + ax + b. - */ - mp_int *x2 = monty_mul(wc->mc, x, x); - mp_int *x2_plus_a = monty_add(wc->mc, x2, wc->a); - mp_int *x3_plus_ax = monty_mul(wc->mc, x2_plus_a, x); - mp_int *rhs = monty_add(wc->mc, x3_plus_ax, wc->b); - mp_free(x2); - mp_free(x2_plus_a); - mp_free(x3_plus_ax); - - mp_int *y = monty_modsqrt(wc->sc, rhs, &success); - mp_free(rhs); - - if (!success) { - /* Failure! x^3+ax+b worked out to be a number that has no - * square root mod p. In this situation there's no point in - * trying to be time-constant, since the protocol sequence is - * going to diverge anyway when we complain to whoever gave us - * this bogus value. */ - mp_free(x); - mp_free(y); - return NULL; - } - - /* - * Choose whichever of y and p-y has the specified parity (of its - * lowest positive residue mod p). - */ - mp_int *tmp = monty_export(wc->mc, y); - unsigned flip = (mp_get_bit(tmp, 0) ^ desired_y_parity) & 1; - mp_sub_into(tmp, wc->p, y); - mp_select_into(y, y, tmp, flip); - mp_free(tmp); - - return ecc_weierstrass_point_new_imported(wc, x, y); -} - -static void ecc_weierstrass_cond_overwrite( - WeierstrassPoint *dest, WeierstrassPoint *src, unsigned overwrite) -{ - mp_select_into(dest->X, dest->X, src->X, overwrite); - mp_select_into(dest->Y, dest->Y, src->Y, overwrite); - mp_select_into(dest->Z, dest->Z, src->Z, overwrite); -} - -static void ecc_weierstrass_cond_swap( - WeierstrassPoint *P, WeierstrassPoint *Q, unsigned swap) -{ - mp_cond_swap(P->X, Q->X, swap); - mp_cond_swap(P->Y, Q->Y, swap); - mp_cond_swap(P->Z, Q->Z, swap); -} - -/* - * Shared code between all three of the basic arithmetic functions: - * once we've determined the slope of the line that we're intersecting - * the curve with, this takes care of finding the coordinates of the - * third intersection point (given the two input x-coordinates and one - * of the y-coords) and negating it to generate the output. - */ -static inline void ecc_weierstrass_epilogue( - mp_int *Px, mp_int *Qx, mp_int *Py, mp_int *common_Z, - mp_int *lambda_n, mp_int *lambda_d, WeierstrassPoint *out) -{ - WeierstrassCurve *wc = out->wc; - - /* Powers of the numerator and denominator of the slope lambda */ - mp_int *lambda_n2 = monty_mul(wc->mc, lambda_n, lambda_n); - mp_int *lambda_d2 = monty_mul(wc->mc, lambda_d, lambda_d); - mp_int *lambda_d3 = monty_mul(wc->mc, lambda_d, lambda_d2); - - /* Make the output x-coordinate */ - mp_int *xsum = monty_add(wc->mc, Px, Qx); - mp_int *lambda_d2_xsum = monty_mul(wc->mc, lambda_d2, xsum); - out->X = monty_sub(wc->mc, lambda_n2, lambda_d2_xsum); - - /* Make the output y-coordinate */ - mp_int *lambda_d2_Px = monty_mul(wc->mc, lambda_d2, Px); - mp_int *xdiff = monty_sub(wc->mc, lambda_d2_Px, out->X); - mp_int *lambda_n_xdiff = monty_mul(wc->mc, lambda_n, xdiff); - mp_int *lambda_d3_Py = monty_mul(wc->mc, lambda_d3, Py); - out->Y = monty_sub(wc->mc, lambda_n_xdiff, lambda_d3_Py); - - /* Make the output z-coordinate */ - out->Z = monty_mul(wc->mc, common_Z, lambda_d); - - mp_free(lambda_n2); - mp_free(lambda_d2); - mp_free(lambda_d3); - mp_free(xsum); - mp_free(xdiff); - mp_free(lambda_d2_xsum); - mp_free(lambda_n_xdiff); - mp_free(lambda_d2_Px); - mp_free(lambda_d3_Py); -} - -/* - * Shared code between add and add_general: put the two input points - * over a common denominator, and determine the slope lambda of the - * line through both of them. If the points have the same - * x-coordinate, then the slope will be returned with a zero - * denominator. - */ -static inline void ecc_weierstrass_add_prologue( - WeierstrassPoint *P, WeierstrassPoint *Q, - mp_int **Px, mp_int **Py, mp_int **Qx, mp_int **denom, - mp_int **lambda_n, mp_int **lambda_d) -{ - WeierstrassCurve *wc = P->wc; - - /* Powers of the points' denominators */ - mp_int *Pz2 = monty_mul(wc->mc, P->Z, P->Z); - mp_int *Pz3 = monty_mul(wc->mc, Pz2, P->Z); - mp_int *Qz2 = monty_mul(wc->mc, Q->Z, Q->Z); - mp_int *Qz3 = monty_mul(wc->mc, Qz2, Q->Z); - - /* Points' x,y coordinates scaled by the other one's denominator - * (raised to the appropriate power) */ - *Px = monty_mul(wc->mc, P->X, Qz2); - *Py = monty_mul(wc->mc, P->Y, Qz3); - *Qx = monty_mul(wc->mc, Q->X, Pz2); - mp_int *Qy = monty_mul(wc->mc, Q->Y, Pz3); - - /* Common denominator */ - *denom = monty_mul(wc->mc, P->Z, Q->Z); - - /* Slope of the line through the two points, if P != Q */ - *lambda_n = monty_sub(wc->mc, Qy, *Py); - *lambda_d = monty_sub(wc->mc, *Qx, *Px); - - mp_free(Pz2); - mp_free(Pz3); - mp_free(Qz2); - mp_free(Qz3); - mp_free(Qy); -} - -WeierstrassPoint *ecc_weierstrass_add(WeierstrassPoint *P, WeierstrassPoint *Q) -{ - WeierstrassCurve *wc = P->wc; - assert(Q->wc == wc); - - WeierstrassPoint *S = ecc_weierstrass_point_new_empty(wc); - - mp_int *Px, *Py, *Qx, *denom, *lambda_n, *lambda_d; - ecc_weierstrass_add_prologue( - P, Q, &Px, &Py, &Qx, &denom, &lambda_n, &lambda_d); - - /* Never expect to have received two mutually inverse inputs, or - * two identical ones (which would make this a doubling). In other - * words, the two input x-coordinates (after putting over a common - * denominator) should never have been equal. */ - assert(!mp_eq_integer(lambda_n, 0)); - - /* Now go to the common epilogue code. */ - ecc_weierstrass_epilogue(Px, Qx, Py, denom, lambda_n, lambda_d, S); - - mp_free(Px); - mp_free(Py); - mp_free(Qx); - mp_free(denom); - mp_free(lambda_n); - mp_free(lambda_d); - - return S; -} - -/* - * Code to determine the slope of the line you need to intersect with - * the curve in the case where you're adding a point to itself. In - * this situation you can't just say "the line through both input - * points" because that's under-determined; instead, you have to take - * the _tangent_ to the curve at the given point, by differentiating - * the curve equation y^2=x^3+ax+b to get 2y dy/dx = 3x^2+a. - */ -static inline void ecc_weierstrass_tangent_slope( - WeierstrassPoint *P, mp_int **lambda_n, mp_int **lambda_d) -{ - WeierstrassCurve *wc = P->wc; - - mp_int *X2 = monty_mul(wc->mc, P->X, P->X); - mp_int *twoX2 = monty_add(wc->mc, X2, X2); - mp_int *threeX2 = monty_add(wc->mc, twoX2, X2); - mp_int *Z2 = monty_mul(wc->mc, P->Z, P->Z); - mp_int *Z4 = monty_mul(wc->mc, Z2, Z2); - mp_int *aZ4 = monty_mul(wc->mc, wc->a, Z4); - - *lambda_n = monty_add(wc->mc, threeX2, aZ4); - *lambda_d = monty_add(wc->mc, P->Y, P->Y); - - mp_free(X2); - mp_free(twoX2); - mp_free(threeX2); - mp_free(Z2); - mp_free(Z4); - mp_free(aZ4); -} - -WeierstrassPoint *ecc_weierstrass_double(WeierstrassPoint *P) -{ - WeierstrassCurve *wc = P->wc; - WeierstrassPoint *D = ecc_weierstrass_point_new_empty(wc); - - mp_int *lambda_n, *lambda_d; - ecc_weierstrass_tangent_slope(P, &lambda_n, &lambda_d); - ecc_weierstrass_epilogue(P->X, P->X, P->Y, P->Z, lambda_n, lambda_d, D); - mp_free(lambda_n); - mp_free(lambda_d); - - return D; -} - -static inline void ecc_weierstrass_select_into( - WeierstrassPoint *dest, WeierstrassPoint *P, WeierstrassPoint *Q, - unsigned choose_Q) -{ - mp_select_into(dest->X, P->X, Q->X, choose_Q); - mp_select_into(dest->Y, P->Y, Q->Y, choose_Q); - mp_select_into(dest->Z, P->Z, Q->Z, choose_Q); -} - -WeierstrassPoint *ecc_weierstrass_add_general( - WeierstrassPoint *P, WeierstrassPoint *Q) -{ - WeierstrassCurve *wc = P->wc; - assert(Q->wc == wc); - - WeierstrassPoint *S = ecc_weierstrass_point_new_empty(wc); - - /* Parameters for the epilogue, and slope of the line if P != Q */ - mp_int *Px, *Py, *Qx, *denom, *lambda_n, *lambda_d; - ecc_weierstrass_add_prologue( - P, Q, &Px, &Py, &Qx, &denom, &lambda_n, &lambda_d); - - /* Slope if P == Q */ - mp_int *lambda_n_tangent, *lambda_d_tangent; - ecc_weierstrass_tangent_slope(P, &lambda_n_tangent, &lambda_d_tangent); - - /* Select between those slopes depending on whether P == Q */ - unsigned same_x_coord = mp_eq_integer(lambda_d, 0); - unsigned same_y_coord = mp_eq_integer(lambda_n, 0); - unsigned equality = same_x_coord & same_y_coord; - mp_select_into(lambda_n, lambda_n, lambda_n_tangent, equality); - mp_select_into(lambda_d, lambda_d, lambda_d_tangent, equality); - - /* Now go to the common code between addition and doubling */ - ecc_weierstrass_epilogue(Px, Qx, Py, denom, lambda_n, lambda_d, S); - - /* Check for the input identity cases, and overwrite the output if - * necessary. */ - ecc_weierstrass_select_into(S, S, Q, mp_eq_integer(P->Z, 0)); - ecc_weierstrass_select_into(S, S, P, mp_eq_integer(Q->Z, 0)); - - /* - * In the case where P == -Q and so the output is the identity, - * we'll have calculated lambda_d = 0 and so the output will have - * z==0 already. Detect that and use it to normalise the other two - * coordinates to zero. - */ - unsigned output_id = mp_eq_integer(S->Z, 0); - mp_cond_clear(S->X, output_id); - mp_cond_clear(S->Y, output_id); - - mp_free(Px); - mp_free(Py); - mp_free(Qx); - mp_free(denom); - mp_free(lambda_n); - mp_free(lambda_d); - mp_free(lambda_n_tangent); - mp_free(lambda_d_tangent); - - return S; -} - -WeierstrassPoint *ecc_weierstrass_multiply(WeierstrassPoint *B, mp_int *n) -{ - WeierstrassPoint *two_B = ecc_weierstrass_double(B); - WeierstrassPoint *k_B = ecc_weierstrass_point_copy(B); - WeierstrassPoint *kplus1_B = ecc_weierstrass_point_copy(two_B); - - /* - * This multiply routine more or less follows the shape of the - * 'Montgomery ladder' technique that you have to use under the - * extra constraint on addition in Montgomery curves, because it - * was fresh in my mind and easier to just do it the same way. See - * the comment in ecc_montgomery_multiply. - */ - - unsigned not_started_yet = 1; - for (size_t bitindex = mp_max_bits(n); bitindex-- > 0 ;) { - unsigned nbit = mp_get_bit(n, bitindex); - - WeierstrassPoint *sum = ecc_weierstrass_add(k_B, kplus1_B); - ecc_weierstrass_cond_swap(k_B, kplus1_B, nbit); - WeierstrassPoint *other = ecc_weierstrass_double(k_B); - ecc_weierstrass_point_free(k_B); - ecc_weierstrass_point_free(kplus1_B); - k_B = other; - kplus1_B = sum; - ecc_weierstrass_cond_swap(k_B, kplus1_B, nbit); - - ecc_weierstrass_cond_overwrite(k_B, B, not_started_yet); - ecc_weierstrass_cond_overwrite(kplus1_B, two_B, not_started_yet); - not_started_yet &= ~nbit; - } - - ecc_weierstrass_point_free(two_B); - ecc_weierstrass_point_free(kplus1_B); - return k_B; -} - -unsigned ecc_weierstrass_is_identity(WeierstrassPoint *wp) -{ - return mp_eq_integer(wp->Z, 0); -} - -/* - * Normalise a point by scaling its Jacobian coordinates so that Z=1. - * This doesn't change what point is represented by the triple, but it - * means the affine x,y can now be easily recovered from X and Y. - */ -static void ecc_weierstrass_normalise(WeierstrassPoint *wp) -{ - WeierstrassCurve *wc = wp->wc; - mp_int *zinv = monty_invert(wc->mc, wp->Z); - mp_int *zinv2 = monty_mul(wc->mc, zinv, zinv); - mp_int *zinv3 = monty_mul(wc->mc, zinv2, zinv); - monty_mul_into(wc->mc, wp->X, wp->X, zinv2); - monty_mul_into(wc->mc, wp->Y, wp->Y, zinv3); - monty_mul_into(wc->mc, wp->Z, wp->Z, zinv); - mp_free(zinv); - mp_free(zinv2); - mp_free(zinv3); -} - -void ecc_weierstrass_get_affine( - WeierstrassPoint *wp, mp_int **x, mp_int **y) -{ - WeierstrassCurve *wc = wp->wc; - - ecc_weierstrass_normalise(wp); - - if (x) - *x = monty_export(wc->mc, wp->X); - if (y) - *y = monty_export(wc->mc, wp->Y); -} - -unsigned ecc_weierstrass_point_valid(WeierstrassPoint *P) -{ - WeierstrassCurve *wc = P->wc; - - /* - * The projective version of the curve equation is - * Y^2 = X^3 + a X Z^4 + b Z^6 - */ - mp_int *lhs = monty_mul(P->wc->mc, P->Y, P->Y); - mp_int *x2 = monty_mul(wc->mc, P->X, P->X); - mp_int *x3 = monty_mul(wc->mc, x2, P->X); - mp_int *z2 = monty_mul(wc->mc, P->Z, P->Z); - mp_int *z4 = monty_mul(wc->mc, z2, z2); - mp_int *az4 = monty_mul(wc->mc, wc->a, z4); - mp_int *axz4 = monty_mul(wc->mc, az4, P->X); - mp_int *x3_plus_axz4 = monty_add(wc->mc, x3, axz4); - mp_int *z6 = monty_mul(wc->mc, z2, z4); - mp_int *bz6 = monty_mul(wc->mc, wc->b, z6); - mp_int *rhs = monty_add(wc->mc, x3_plus_axz4, bz6); - - unsigned valid = mp_cmp_eq(lhs, rhs); - - mp_free(lhs); - mp_free(x2); - mp_free(x3); - mp_free(z2); - mp_free(z4); - mp_free(az4); - mp_free(axz4); - mp_free(x3_plus_axz4); - mp_free(z6); - mp_free(bz6); - mp_free(rhs); - - return valid; -} - -/* ---------------------------------------------------------------------- - * Montgomery curves. - */ - -struct MontgomeryPoint { - /* XZ coordinates. These represent the affine x coordinate by the - * relationship x = X/Z. */ - mp_int *X, *Z; - - MontgomeryCurve *mc; -}; - -struct MontgomeryCurve { - /* Prime modulus of the finite field. */ - mp_int *p; - - /* Montgomery context for arithmetic mod p. */ - MontyContext *mc; - - /* Parameters of the curve, in Montgomery-multiplication - * transformed form. */ - mp_int *a, *b; - - /* (a+2)/4, also in Montgomery-multiplication form. */ - mp_int *aplus2over4; -}; - -MontgomeryCurve *ecc_montgomery_curve( - mp_int *p, mp_int *a, mp_int *b) -{ - MontgomeryCurve *mc = snew(MontgomeryCurve); - mc->p = mp_copy(p); - mc->mc = monty_new(p); - mc->a = monty_import(mc->mc, a); - mc->b = monty_import(mc->mc, b); - - mp_int *four = mp_from_integer(4); - mp_int *fourinverse = mp_invert(four, mc->p); - mp_int *aplus2 = mp_copy(a); - mp_add_integer_into(aplus2, aplus2, 2); - mp_int *aplus2over4 = mp_modmul(aplus2, fourinverse, mc->p); - mc->aplus2over4 = monty_import(mc->mc, aplus2over4); - mp_free(four); - mp_free(fourinverse); - mp_free(aplus2); - mp_free(aplus2over4); - - return mc; -} - -void ecc_montgomery_curve_free(MontgomeryCurve *mc) -{ - mp_free(mc->p); - mp_free(mc->a); - mp_free(mc->b); - mp_free(mc->aplus2over4); - monty_free(mc->mc); - sfree(mc); -} - -static MontgomeryPoint *ecc_montgomery_point_new_empty(MontgomeryCurve *mc) -{ - MontgomeryPoint *mp = snew(MontgomeryPoint); - mp->mc = mc; - mp->X = mp->Z = NULL; - return mp; -} - -MontgomeryPoint *ecc_montgomery_point_new(MontgomeryCurve *mc, mp_int *x) -{ - MontgomeryPoint *mp = ecc_montgomery_point_new_empty(mc); - mp->X = monty_import(mc->mc, x); - mp->Z = mp_copy(monty_identity(mc->mc)); - return mp; -} - -void ecc_montgomery_point_copy_into( - MontgomeryPoint *dest, MontgomeryPoint *src) -{ - mp_copy_into(dest->X, src->X); - mp_copy_into(dest->Z, src->Z); -} - -MontgomeryPoint *ecc_montgomery_point_copy(MontgomeryPoint *orig) -{ - MontgomeryPoint *mp = ecc_montgomery_point_new_empty(orig->mc); - mp->X = mp_copy(orig->X); - mp->Z = mp_copy(orig->Z); - return mp; -} - -void ecc_montgomery_point_free(MontgomeryPoint *mp) -{ - mp_free(mp->X); - mp_free(mp->Z); - smemclr(mp, sizeof(*mp)); - sfree(mp); -} - -static void ecc_montgomery_cond_overwrite( - MontgomeryPoint *dest, MontgomeryPoint *src, unsigned overwrite) -{ - mp_select_into(dest->X, dest->X, src->X, overwrite); - mp_select_into(dest->Z, dest->Z, src->Z, overwrite); -} - -static void ecc_montgomery_cond_swap( - MontgomeryPoint *P, MontgomeryPoint *Q, unsigned swap) -{ - mp_cond_swap(P->X, Q->X, swap); - mp_cond_swap(P->Z, Q->Z, swap); -} - -MontgomeryPoint *ecc_montgomery_diff_add( - MontgomeryPoint *P, MontgomeryPoint *Q, MontgomeryPoint *PminusQ) -{ - MontgomeryCurve *mc = P->mc; - assert(Q->mc == mc); - assert(PminusQ->mc == mc); - - /* - * Differential addition is achieved using the following formula - * that relates the affine x-coordinates of P, Q, P+Q and P-Q: - * - * x(P+Q) x(P-Q) (x(Q)-x(P))^2 = (x(P)x(Q) - 1)^2 - * - * As with the Weierstrass coordinates, the code below transforms - * that affine relation into a projective one to avoid having to - * do a division during the main arithmetic. - */ - - MontgomeryPoint *S = ecc_montgomery_point_new_empty(mc); - - mp_int *Px_m_Pz = monty_sub(mc->mc, P->X, P->Z); - mp_int *Px_p_Pz = monty_add(mc->mc, P->X, P->Z); - mp_int *Qx_m_Qz = monty_sub(mc->mc, Q->X, Q->Z); - mp_int *Qx_p_Qz = monty_add(mc->mc, Q->X, Q->Z); - mp_int *PmQp = monty_mul(mc->mc, Px_m_Pz, Qx_p_Qz); - mp_int *PpQm = monty_mul(mc->mc, Px_p_Pz, Qx_m_Qz); - mp_int *Xpre = monty_add(mc->mc, PmQp, PpQm); - mp_int *Zpre = monty_sub(mc->mc, PmQp, PpQm); - mp_int *Xpre2 = monty_mul(mc->mc, Xpre, Xpre); - mp_int *Zpre2 = monty_mul(mc->mc, Zpre, Zpre); - S->X = monty_mul(mc->mc, Xpre2, PminusQ->Z); - S->Z = monty_mul(mc->mc, Zpre2, PminusQ->X); - - mp_free(Px_m_Pz); - mp_free(Px_p_Pz); - mp_free(Qx_m_Qz); - mp_free(Qx_p_Qz); - mp_free(PmQp); - mp_free(PpQm); - mp_free(Xpre); - mp_free(Zpre); - mp_free(Xpre2); - mp_free(Zpre2); - - return S; -} - -MontgomeryPoint *ecc_montgomery_double(MontgomeryPoint *P) -{ - MontgomeryCurve *mc = P->mc; - MontgomeryPoint *D = ecc_montgomery_point_new_empty(mc); - - /* - * To double a point in affine coordinates, in principle you can - * use the same technique as for Weierstrass: differentiate the - * curve equation to get the tangent line at the input point, use - * that to get an expression for y which you substitute back into - * the curve equation, and subtract the known two roots (in this - * case both the same) from the x^2 coefficient of the resulting - * cubic. - * - * In this case, we don't have an input y-coordinate, so you have - * to do a bit of extra transformation to find a formula that can - * work without it. The tangent formula is (3x^2 + 2ax + 1)/(2y), - * and when that appears in the final formula it will be squared - - * so we can substitute the y^2 in the denominator for the RHS of - * the curve equation. Put together, that gives - * - * x_out = (x+1)^2 (x-1)^2 / 4(x^3+ax^2+x) - * - * and, as usual, the code below transforms that into projective - * form to avoid the division. - */ - - mp_int *Px_m_Pz = monty_sub(mc->mc, P->X, P->Z); - mp_int *Px_p_Pz = monty_add(mc->mc, P->X, P->Z); - mp_int *Px_m_Pz_2 = monty_mul(mc->mc, Px_m_Pz, Px_m_Pz); - mp_int *Px_p_Pz_2 = monty_mul(mc->mc, Px_p_Pz, Px_p_Pz); - D->X = monty_mul(mc->mc, Px_m_Pz_2, Px_p_Pz_2); - mp_int *XZ = monty_mul(mc->mc, P->X, P->Z); - mp_int *twoXZ = monty_add(mc->mc, XZ, XZ); - mp_int *fourXZ = monty_add(mc->mc, twoXZ, twoXZ); - mp_int *fourXZ_scaled = monty_mul(mc->mc, fourXZ, mc->aplus2over4); - mp_int *Zpre = monty_add(mc->mc, Px_m_Pz_2, fourXZ_scaled); - D->Z = monty_mul(mc->mc, fourXZ, Zpre); - - mp_free(Px_m_Pz); - mp_free(Px_p_Pz); - mp_free(Px_m_Pz_2); - mp_free(Px_p_Pz_2); - mp_free(XZ); - mp_free(twoXZ); - mp_free(fourXZ); - mp_free(fourXZ_scaled); - mp_free(Zpre); - - return D; -} - -static void ecc_montgomery_normalise(MontgomeryPoint *mp) -{ - MontgomeryCurve *mc = mp->mc; - mp_int *zinv = monty_invert(mc->mc, mp->Z); - monty_mul_into(mc->mc, mp->X, mp->X, zinv); - monty_mul_into(mc->mc, mp->Z, mp->Z, zinv); - mp_free(zinv); -} - -MontgomeryPoint *ecc_montgomery_multiply(MontgomeryPoint *B, mp_int *n) -{ - /* - * 'Montgomery ladder' technique, to compute an arbitrary integer - * multiple of B under the constraint that you can only add two - * unequal points if you also know their difference. - * - * The setup is that you maintain two curve points one of which is - * always the other one plus B. Call them kB and (k+1)B, where k - * is some integer that evolves as we go along. We begin by - * doubling the input B, to initialise those points to B and 2B, - * so that k=1. - * - * At each stage, we add kB and (k+1)B together - which we can do - * under the differential-addition constraint because we know - * their difference is always just B - to give us (2k+1)B. Then we - * double one of kB or (k+1)B, and depending on which one we - * choose, we end up with (2k)B or (2k+2)B. Either way, that - * differs by B from the other value we've just computed. So in - * each iteration, we do one diff-add and one doubling, plus a - * couple of conditional swaps to choose which value we double and - * which way round we put the output points, and the effect is to - * replace k with either 2k or 2k+1, which we choose based on the - * appropriate bit of the desired exponent. - * - * This routine doesn't assume we know the exact location of the - * topmost set bit of the exponent. So to maintain constant time - * it does an iteration for every _potential_ bit, starting from - * the top downwards; after each iteration in which we haven't - * seen a set exponent bit yet, we just overwrite the two points - * with B and 2B again, - */ - - MontgomeryPoint *two_B = ecc_montgomery_double(B); - MontgomeryPoint *k_B = ecc_montgomery_point_copy(B); - MontgomeryPoint *kplus1_B = ecc_montgomery_point_copy(two_B); - - unsigned not_started_yet = 1; - for (size_t bitindex = mp_max_bits(n); bitindex-- > 0 ;) { - unsigned nbit = mp_get_bit(n, bitindex); - - MontgomeryPoint *sum = ecc_montgomery_diff_add(k_B, kplus1_B, B); - ecc_montgomery_cond_swap(k_B, kplus1_B, nbit); - MontgomeryPoint *other = ecc_montgomery_double(k_B); - ecc_montgomery_point_free(k_B); - ecc_montgomery_point_free(kplus1_B); - k_B = other; - kplus1_B = sum; - ecc_montgomery_cond_swap(k_B, kplus1_B, nbit); - - ecc_montgomery_cond_overwrite(k_B, B, not_started_yet); - ecc_montgomery_cond_overwrite(kplus1_B, two_B, not_started_yet); - not_started_yet &= ~nbit; - } - - ecc_montgomery_point_free(two_B); - ecc_montgomery_point_free(kplus1_B); - return k_B; -} - -void ecc_montgomery_get_affine(MontgomeryPoint *mp, mp_int **x) -{ - MontgomeryCurve *mc = mp->mc; - - ecc_montgomery_normalise(mp); - - if (x) - *x = monty_export(mc->mc, mp->X); -} - -unsigned ecc_montgomery_is_identity(MontgomeryPoint *mp) -{ - return mp_eq_integer(mp->Z, 0); -} - -/* ---------------------------------------------------------------------- - * Twisted Edwards curves. - */ - -struct EdwardsPoint { - /* - * We represent an Edwards curve point in 'extended coordinates'. - * There's more than one coordinate system going by that name, - * unfortunately. These ones have the semantics that X,Y,Z are - * ordinary projective coordinates (so x=X/Z and y=Y/Z), but also, - * we store the extra value T = xyZ = XY/Z. - */ - mp_int *X, *Y, *Z, *T; - - EdwardsCurve *ec; -}; - -struct EdwardsCurve { - /* Prime modulus of the finite field. */ - mp_int *p; - - /* Montgomery context for arithmetic mod p. */ - MontyContext *mc; - - /* Modsqrt context for point decompression. */ - ModsqrtContext *sc; - - /* Parameters of the curve, in Montgomery-multiplication - * transformed form. */ - mp_int *d, *a; -}; - -EdwardsCurve *ecc_edwards_curve(mp_int *p, mp_int *d, mp_int *a, - mp_int *nonsquare_mod_p) -{ - EdwardsCurve *ec = snew(EdwardsCurve); - ec->p = mp_copy(p); - ec->mc = monty_new(p); - ec->d = monty_import(ec->mc, d); - ec->a = monty_import(ec->mc, a); - - if (nonsquare_mod_p) - ec->sc = modsqrt_new(p, nonsquare_mod_p); - else - ec->sc = NULL; - - return ec; -} - -void ecc_edwards_curve_free(EdwardsCurve *ec) -{ - mp_free(ec->p); - mp_free(ec->d); - mp_free(ec->a); - monty_free(ec->mc); - if (ec->sc) - modsqrt_free(ec->sc); - sfree(ec); -} - -static EdwardsPoint *ecc_edwards_point_new_empty(EdwardsCurve *ec) -{ - EdwardsPoint *ep = snew(EdwardsPoint); - ep->ec = ec; - ep->X = ep->Y = ep->Z = ep->T = NULL; - return ep; -} - -static EdwardsPoint *ecc_edwards_point_new_imported( - EdwardsCurve *ec, mp_int *monty_x, mp_int *monty_y) -{ - EdwardsPoint *ep = ecc_edwards_point_new_empty(ec); - ep->X = monty_x; - ep->Y = monty_y; - ep->T = monty_mul(ec->mc, ep->X, ep->Y); - ep->Z = mp_copy(monty_identity(ec->mc)); - return ep; -} - -EdwardsPoint *ecc_edwards_point_new( - EdwardsCurve *ec, mp_int *x, mp_int *y) -{ - return ecc_edwards_point_new_imported( - ec, monty_import(ec->mc, x), monty_import(ec->mc, y)); -} - -void ecc_edwards_point_copy_into(EdwardsPoint *dest, EdwardsPoint *src) -{ - mp_copy_into(dest->X, src->X); - mp_copy_into(dest->Y, src->Y); - mp_copy_into(dest->Z, src->Z); - mp_copy_into(dest->T, src->T); -} - -EdwardsPoint *ecc_edwards_point_copy(EdwardsPoint *orig) -{ - EdwardsPoint *ep = ecc_edwards_point_new_empty(orig->ec); - ep->X = mp_copy(orig->X); - ep->Y = mp_copy(orig->Y); - ep->Z = mp_copy(orig->Z); - ep->T = mp_copy(orig->T); - return ep; -} - -void ecc_edwards_point_free(EdwardsPoint *ep) -{ - mp_free(ep->X); - mp_free(ep->Y); - mp_free(ep->Z); - mp_free(ep->T); - smemclr(ep, sizeof(*ep)); - sfree(ep); -} - -EdwardsPoint *ecc_edwards_point_new_from_y( - EdwardsCurve *ec, mp_int *yorig, unsigned desired_x_parity) -{ - assert(ec->sc); - - /* - * The curve equation is ax^2 + y^2 = 1 + dx^2y^2, which - * rearranges to x^2(dy^2-a) = y^2-1. So we compute - * (y^2-1)/(dy^2-a) and take its square root. - */ - unsigned success; - - mp_int *y = monty_import(ec->mc, yorig); - mp_int *y2 = monty_mul(ec->mc, y, y); - mp_int *dy2 = monty_mul(ec->mc, ec->d, y2); - mp_int *dy2ma = monty_sub(ec->mc, dy2, ec->a); - mp_int *y2m1 = monty_sub(ec->mc, y2, monty_identity(ec->mc)); - mp_int *recip_denominator = monty_invert(ec->mc, dy2ma); - mp_int *radicand = monty_mul(ec->mc, y2m1, recip_denominator); - mp_int *x = monty_modsqrt(ec->sc, radicand, &success); - mp_free(y2); - mp_free(dy2); - mp_free(dy2ma); - mp_free(y2m1); - mp_free(recip_denominator); - mp_free(radicand); - - if (!success) { - /* Failure! x^2 worked out to be a number that has no square - * root mod p. In this situation there's no point in trying to - * be time-constant, since the protocol sequence is going to - * diverge anyway when we complain to whoever gave us this - * bogus value. */ - mp_free(x); - mp_free(y); - return NULL; - } - - /* - * Choose whichever of x and p-x has the specified parity (of its - * lowest positive residue mod p). - */ - mp_int *tmp = monty_export(ec->mc, x); - unsigned flip = (mp_get_bit(tmp, 0) ^ desired_x_parity) & 1; - mp_sub_into(tmp, ec->p, x); - mp_select_into(x, x, tmp, flip); - mp_free(tmp); - - return ecc_edwards_point_new_imported(ec, x, y); -} - -static void ecc_edwards_cond_overwrite( - EdwardsPoint *dest, EdwardsPoint *src, unsigned overwrite) -{ - mp_select_into(dest->X, dest->X, src->X, overwrite); - mp_select_into(dest->Y, dest->Y, src->Y, overwrite); - mp_select_into(dest->Z, dest->Z, src->Z, overwrite); - mp_select_into(dest->T, dest->T, src->T, overwrite); -} - -static void ecc_edwards_cond_swap( - EdwardsPoint *P, EdwardsPoint *Q, unsigned swap) -{ - mp_cond_swap(P->X, Q->X, swap); - mp_cond_swap(P->Y, Q->Y, swap); - mp_cond_swap(P->Z, Q->Z, swap); - mp_cond_swap(P->T, Q->T, swap); -} - -EdwardsPoint *ecc_edwards_add(EdwardsPoint *P, EdwardsPoint *Q) -{ - EdwardsCurve *ec = P->ec; - assert(Q->ec == ec); - - EdwardsPoint *S = ecc_edwards_point_new_empty(ec); - - /* - * The affine rule for Edwards addition of (x1,y1) and (x2,y2) is - * - * x_out = (x1 y2 + y1 x2) / (1 + d x1 x2 y1 y2) - * y_out = (y1 y2 - a x1 x2) / (1 - d x1 x2 y1 y2) - * - * The formulae below are listed as 'add-2008-hwcd' in - * https://hyperelliptic.org/EFD/g1p/auto-twisted-extended.html - * - * and if you undo the careful optimisation to find out what - * they're actually computing, it comes out to - * - * X_out = (X1 Y2 + Y1 X2) (Z1 Z2 - d T1 T2) - * Y_out = (Y1 Y2 - a X1 X2) (Z1 Z2 + d T1 T2) - * Z_out = (Z1 Z2 - d T1 T2) (Z1 Z2 + d T1 T2) - * T_out = (X1 Y2 + Y1 X2) (Y1 Y2 - a X1 X2) - */ - mp_int *PxQx = monty_mul(ec->mc, P->X, Q->X); - mp_int *PyQy = monty_mul(ec->mc, P->Y, Q->Y); - mp_int *PtQt = monty_mul(ec->mc, P->T, Q->T); - mp_int *PzQz = monty_mul(ec->mc, P->Z, Q->Z); - mp_int *Psum = monty_add(ec->mc, P->X, P->Y); - mp_int *Qsum = monty_add(ec->mc, Q->X, Q->Y); - mp_int *aPxQx = monty_mul(ec->mc, ec->a, PxQx); - mp_int *dPtQt = monty_mul(ec->mc, ec->d, PtQt); - mp_int *sumprod = monty_mul(ec->mc, Psum, Qsum); - mp_int *xx_p_yy = monty_add(ec->mc, PxQx, PyQy); - mp_int *E = monty_sub(ec->mc, sumprod, xx_p_yy); - mp_int *F = monty_sub(ec->mc, PzQz, dPtQt); - mp_int *G = monty_add(ec->mc, PzQz, dPtQt); - mp_int *H = monty_sub(ec->mc, PyQy, aPxQx); - S->X = monty_mul(ec->mc, E, F); - S->Z = monty_mul(ec->mc, F, G); - S->Y = monty_mul(ec->mc, G, H); - S->T = monty_mul(ec->mc, H, E); - - mp_free(PxQx); - mp_free(PyQy); - mp_free(PtQt); - mp_free(PzQz); - mp_free(Psum); - mp_free(Qsum); - mp_free(aPxQx); - mp_free(dPtQt); - mp_free(sumprod); - mp_free(xx_p_yy); - mp_free(E); - mp_free(F); - mp_free(G); - mp_free(H); - - return S; -} - -static void ecc_edwards_normalise(EdwardsPoint *ep) -{ - EdwardsCurve *ec = ep->ec; - mp_int *zinv = monty_invert(ec->mc, ep->Z); - monty_mul_into(ec->mc, ep->X, ep->X, zinv); - monty_mul_into(ec->mc, ep->Y, ep->Y, zinv); - monty_mul_into(ec->mc, ep->Z, ep->Z, zinv); - mp_free(zinv); - monty_mul_into(ec->mc, ep->T, ep->X, ep->Y); -} - -EdwardsPoint *ecc_edwards_multiply(EdwardsPoint *B, mp_int *n) -{ - EdwardsPoint *two_B = ecc_edwards_add(B, B); - EdwardsPoint *k_B = ecc_edwards_point_copy(B); - EdwardsPoint *kplus1_B = ecc_edwards_point_copy(two_B); - - /* - * Another copy of the same exponentiation routine following the - * pattern of the Montgomery ladder, because it works as well as - * any other technique and this way I didn't have to debug two of - * them. - */ - - unsigned not_started_yet = 1; - for (size_t bitindex = mp_max_bits(n); bitindex-- > 0 ;) { - unsigned nbit = mp_get_bit(n, bitindex); - - EdwardsPoint *sum = ecc_edwards_add(k_B, kplus1_B); - ecc_edwards_cond_swap(k_B, kplus1_B, nbit); - EdwardsPoint *other = ecc_edwards_add(k_B, k_B); - ecc_edwards_point_free(k_B); - ecc_edwards_point_free(kplus1_B); - k_B = other; - kplus1_B = sum; - ecc_edwards_cond_swap(k_B, kplus1_B, nbit); - - ecc_edwards_cond_overwrite(k_B, B, not_started_yet); - ecc_edwards_cond_overwrite(kplus1_B, two_B, not_started_yet); - not_started_yet &= ~nbit; - } - - ecc_edwards_point_free(two_B); - ecc_edwards_point_free(kplus1_B); - return k_B; -} - -/* - * Helper routine to determine whether two values each given as a pair - * of projective coordinates represent the same affine value. - */ -static inline unsigned projective_eq( - MontyContext *mc, mp_int *An, mp_int *Ad, - mp_int *Bn, mp_int *Bd) -{ - mp_int *AnBd = monty_mul(mc, An, Bd); - mp_int *BnAd = monty_mul(mc, Bn, Ad); - unsigned toret = mp_cmp_eq(AnBd, BnAd); - mp_free(AnBd); - mp_free(BnAd); - return toret; -} - -unsigned ecc_edwards_eq(EdwardsPoint *P, EdwardsPoint *Q) -{ - EdwardsCurve *ec = P->ec; - assert(Q->ec == ec); - - return (projective_eq(ec->mc, P->X, P->Z, Q->X, Q->Z) & - projective_eq(ec->mc, P->Y, P->Z, Q->Y, Q->Z)); -} - -void ecc_edwards_get_affine(EdwardsPoint *ep, mp_int **x, mp_int **y) -{ - EdwardsCurve *ec = ep->ec; - - ecc_edwards_normalise(ep); - - if (x) - *x = monty_export(ec->mc, ep->X); - if (y) - *y = monty_export(ec->mc, ep->Y); -} diff --git a/mpint.c b/mpint.c deleted file mode 100644 index d77726cc..00000000 --- a/mpint.c +++ /dev/null @@ -1,2646 +0,0 @@ -#include -#include -#include - -#include "defs.h" -#include "misc.h" -#include "puttymem.h" - -#include "mpint.h" -#include "mpint_i.h" - -#define SIZE_T_BITS (CHAR_BIT * sizeof(size_t)) - -/* - * Inline helpers to take min and max of size_t values, used - * throughout this code. - */ -static inline size_t size_t_min(size_t a, size_t b) -{ - return a < b ? a : b; -} -static inline size_t size_t_max(size_t a, size_t b) -{ - return a > b ? a : b; -} - -/* - * Helper to fetch a word of data from x with array overflow checking. - * If x is too short to have that word, 0 is returned. - */ -static inline BignumInt mp_word(mp_int *x, size_t i) -{ - return i < x->nw ? x->w[i] : 0; -} - -/* - * Shift an ordinary C integer by BIGNUM_INT_BITS, in a way that - * avoids writing a shift operator whose RHS is greater or equal to - * the size of the type, because that's undefined behaviour in C. - * - * In fact we must avoid even writing it in a definitely-untaken - * branch of an if, because compilers will sometimes warn about - * that. So you can't just write 'shift too big ? 0 : n >> shift', - * because even if 'shift too big' is a constant-expression - * evaluating to false, you can still get complaints about the - * else clause of the ?:. - * - * So we have to re-check _inside_ that clause, so that the shift - * count is reset to something nonsensical but safe in the case - * where the clause wasn't going to be taken anyway. - */ -static uintmax_t shift_right_by_one_word(uintmax_t n) -{ - bool shift_too_big = BIGNUM_INT_BYTES >= sizeof(n); - return shift_too_big ? 0 : - n >> (shift_too_big ? 0 : BIGNUM_INT_BITS); -} -static uintmax_t shift_left_by_one_word(uintmax_t n) -{ - bool shift_too_big = BIGNUM_INT_BYTES >= sizeof(n); - return shift_too_big ? 0 : - n << (shift_too_big ? 0 : BIGNUM_INT_BITS); -} - -mp_int *mp_make_sized(size_t nw) -{ - mp_int *x = snew_plus(mp_int, nw * sizeof(BignumInt)); - assert(nw); /* we outlaw the zero-word mp_int */ - x->nw = nw; - x->w = snew_plus_get_aux(x); - mp_clear(x); - return x; -} - -mp_int *mp_new(size_t maxbits) -{ - size_t words = (maxbits + BIGNUM_INT_BITS - 1) / BIGNUM_INT_BITS; - return mp_make_sized(words); -} - -mp_int *mp_from_integer(uintmax_t n) -{ - mp_int *x = mp_make_sized( - (sizeof(n) + BIGNUM_INT_BYTES - 1) / BIGNUM_INT_BYTES); - for (size_t i = 0; i < x->nw; i++) - x->w[i] = n >> (i * BIGNUM_INT_BITS); - return x; -} - -size_t mp_max_bytes(mp_int *x) -{ - return x->nw * BIGNUM_INT_BYTES; -} - -size_t mp_max_bits(mp_int *x) -{ - return x->nw * BIGNUM_INT_BITS; -} - -void mp_free(mp_int *x) -{ - mp_clear(x); - smemclr(x, sizeof(*x)); - sfree(x); -} - -void mp_dump(FILE *fp, const char *prefix, mp_int *x, const char *suffix) -{ - fprintf(fp, "%s0x", prefix); - for (size_t i = mp_max_bytes(x); i-- > 0 ;) - fprintf(fp, "%02X", mp_get_byte(x, i)); - fputs(suffix, fp); -} - -void mp_copy_into(mp_int *dest, mp_int *src) -{ - size_t copy_nw = size_t_min(dest->nw, src->nw); - memmove(dest->w, src->w, copy_nw * sizeof(BignumInt)); - smemclr(dest->w + copy_nw, (dest->nw - copy_nw) * sizeof(BignumInt)); -} - -void mp_copy_integer_into(mp_int *r, uintmax_t n) -{ - for (size_t i = 0; i < r->nw; i++) { - r->w[i] = n; - n = shift_right_by_one_word(n); - } -} - -/* - * Conditional selection is done by negating 'which', to give a mask - * word which is all 1s if which==1 and all 0s if which==0. Then you - * can select between two inputs a,b without data-dependent control - * flow by XORing them to get their difference; ANDing with the mask - * word to replace that difference with 0 if which==0; and XORing that - * into a, which will either turn it into b or leave it alone. - * - * This trick will be used throughout this code and taken as read the - * rest of the time (or else I'd be here all week typing comments), - * but I felt I ought to explain it in words _once_. - */ -void mp_select_into(mp_int *dest, mp_int *src0, mp_int *src1, - unsigned which) -{ - BignumInt mask = -(BignumInt)(1 & which); - for (size_t i = 0; i < dest->nw; i++) { - BignumInt srcword0 = mp_word(src0, i), srcword1 = mp_word(src1, i); - dest->w[i] = srcword0 ^ ((srcword1 ^ srcword0) & mask); - } -} - -void mp_cond_swap(mp_int *x0, mp_int *x1, unsigned swap) -{ - assert(x0->nw == x1->nw); - volatile BignumInt mask = -(BignumInt)(1 & swap); - for (size_t i = 0; i < x0->nw; i++) { - BignumInt diff = (x0->w[i] ^ x1->w[i]) & mask; - x0->w[i] ^= diff; - x1->w[i] ^= diff; - } -} - -void mp_clear(mp_int *x) -{ - smemclr(x->w, x->nw * sizeof(BignumInt)); -} - -void mp_cond_clear(mp_int *x, unsigned clear) -{ - BignumInt mask = ~-(BignumInt)(1 & clear); - for (size_t i = 0; i < x->nw; i++) - x->w[i] &= mask; -} - -/* - * Common code between mp_from_bytes_{le,be} which reads bytes in an - * arbitrary arithmetic progression. - */ -static mp_int *mp_from_bytes_int(ptrlen bytes, size_t m, size_t c) -{ - size_t nw = (bytes.len + BIGNUM_INT_BYTES - 1) / BIGNUM_INT_BYTES; - nw = size_t_max(nw, 1); - mp_int *n = mp_make_sized(nw); - for (size_t i = 0; i < bytes.len; i++) - n->w[i / BIGNUM_INT_BYTES] |= - (BignumInt)(((const unsigned char *)bytes.ptr)[m*i+c]) << - (8 * (i % BIGNUM_INT_BYTES)); - return n; -} - -mp_int *mp_from_bytes_le(ptrlen bytes) -{ - return mp_from_bytes_int(bytes, 1, 0); -} - -mp_int *mp_from_bytes_be(ptrlen bytes) -{ - return mp_from_bytes_int(bytes, -1, bytes.len - 1); -} - -static mp_int *mp_from_words(size_t nw, const BignumInt *w) -{ - mp_int *x = mp_make_sized(nw); - memcpy(x->w, w, x->nw * sizeof(BignumInt)); - return x; -} - -/* - * Decimal-to-binary conversion: just go through the input string - * adding on the decimal value of each digit, and then multiplying the - * number so far by 10. - */ -mp_int *mp_from_decimal_pl(ptrlen decimal) -{ - /* 196/59 is an upper bound (and also a continued-fraction - * convergent) for log2(10), so this conservatively estimates the - * number of bits that will be needed to store any number that can - * be written in this many decimal digits. */ - assert(decimal.len < (~(size_t)0) / 196); - size_t bits = 196 * decimal.len / 59; - - /* Now round that up to words. */ - size_t words = bits / BIGNUM_INT_BITS + 1; - - mp_int *x = mp_make_sized(words); - for (size_t i = 0; i < decimal.len; i++) { - mp_add_integer_into(x, x, ((const char *)decimal.ptr)[i] - '0'); - - if (i+1 == decimal.len) - break; - - mp_mul_integer_into(x, x, 10); - } - return x; -} - -mp_int *mp_from_decimal(const char *decimal) -{ - return mp_from_decimal_pl(ptrlen_from_asciz(decimal)); -} - -/* - * Hex-to-binary conversion: _algorithmically_ simpler than decimal - * (none of those multiplications by 10), but there's some fiddly - * bit-twiddling needed to process each hex digit without diverging - * control flow depending on whether it's a letter or a number. - */ -mp_int *mp_from_hex_pl(ptrlen hex) -{ - assert(hex.len <= (~(size_t)0) / 4); - size_t bits = hex.len * 4; - size_t words = (bits + BIGNUM_INT_BITS - 1) / BIGNUM_INT_BITS; - words = size_t_max(words, 1); - mp_int *x = mp_make_sized(words); - for (size_t nibble = 0; nibble < hex.len; nibble++) { - BignumInt digit = ((const char *)hex.ptr)[hex.len-1 - nibble]; - - BignumInt lmask = ~-((BignumInt)((digit-'a')|('f'-digit)) - >> (BIGNUM_INT_BITS-1)); - BignumInt umask = ~-((BignumInt)((digit-'A')|('F'-digit)) - >> (BIGNUM_INT_BITS-1)); - - BignumInt digitval = digit - '0'; - digitval ^= (digitval ^ (digit - 'a' + 10)) & lmask; - digitval ^= (digitval ^ (digit - 'A' + 10)) & umask; - digitval &= 0xF; /* at least be _slightly_ nice about weird input */ - - size_t word_idx = nibble / (BIGNUM_INT_BYTES*2); - size_t nibble_within_word = nibble % (BIGNUM_INT_BYTES*2); - x->w[word_idx] |= digitval << (nibble_within_word * 4); - } - return x; -} - -mp_int *mp_from_hex(const char *hex) -{ - return mp_from_hex_pl(ptrlen_from_asciz(hex)); -} - -mp_int *mp_copy(mp_int *x) -{ - return mp_from_words(x->nw, x->w); -} - -uint8_t mp_get_byte(mp_int *x, size_t byte) -{ - return 0xFF & (mp_word(x, byte / BIGNUM_INT_BYTES) >> - (8 * (byte % BIGNUM_INT_BYTES))); -} - -unsigned mp_get_bit(mp_int *x, size_t bit) -{ - return 1 & (mp_word(x, bit / BIGNUM_INT_BITS) >> - (bit % BIGNUM_INT_BITS)); -} - -uintmax_t mp_get_integer(mp_int *x) -{ - uintmax_t toret = 0; - for (size_t i = x->nw; i-- > 0 ;) - toret = shift_left_by_one_word(toret) | x->w[i]; - return toret; -} - -void mp_set_bit(mp_int *x, size_t bit, unsigned val) -{ - size_t word = bit / BIGNUM_INT_BITS; - assert(word < x->nw); - - unsigned shift = (bit % BIGNUM_INT_BITS); - - x->w[word] &= ~((BignumInt)1 << shift); - x->w[word] |= (BignumInt)(val & 1) << shift; -} - -/* - * Helper function used here and there to normalise any nonzero input - * value to 1. - */ -static inline unsigned normalise_to_1(BignumInt n) -{ - n = (n >> 1) | (n & 1); /* ensure top bit is clear */ - n = (BignumInt)(-n) >> (BIGNUM_INT_BITS - 1); /* normalise to 0 or 1 */ - return n; -} -static inline unsigned normalise_to_1_u64(uint64_t n) -{ - n = (n >> 1) | (n & 1); /* ensure top bit is clear */ - n = (-n) >> 63; /* normalise to 0 or 1 */ - return n; -} - -/* - * Find the highest nonzero word in a number. Returns the index of the - * word in x->w, and also a pair of output uint64_t in which that word - * appears in the high one shifted left by 'shift_wanted' bits, the - * words immediately below it occupy the space to the right, and the - * words below _that_ fill up the low one. - * - * If there is no nonzero word at all, the passed-by-reference output - * variables retain their original values. - */ -static inline void mp_find_highest_nonzero_word_pair( - mp_int *x, size_t shift_wanted, size_t *index, - uint64_t *hi, uint64_t *lo) -{ - uint64_t curr_hi = 0, curr_lo = 0; - - for (size_t curr_index = 0; curr_index < x->nw; curr_index++) { - BignumInt curr_word = x->w[curr_index]; - unsigned indicator = normalise_to_1(curr_word); - - curr_lo = (BIGNUM_INT_BITS < 64 ? (curr_lo >> BIGNUM_INT_BITS) : 0) | - (curr_hi << (64 - BIGNUM_INT_BITS)); - curr_hi = (BIGNUM_INT_BITS < 64 ? (curr_hi >> BIGNUM_INT_BITS) : 0) | - ((uint64_t)curr_word << shift_wanted); - - if (hi) *hi ^= (curr_hi ^ *hi ) & -(uint64_t)indicator; - if (lo) *lo ^= (curr_lo ^ *lo ) & -(uint64_t)indicator; - if (index) *index ^= (curr_index ^ *index) & -(size_t) indicator; - } -} - -size_t mp_get_nbits(mp_int *x) -{ - /* Sentinel values in case there are no bits set at all: we - * imagine that there's a word at position -1 (i.e. the topmost - * fraction word) which is all 1s, because that way, we handle a - * zero input by considering its highest set bit to be the top one - * of that word, i.e. just below the units digit, i.e. at bit - * index -1, i.e. so we'll return 0 on output. */ - size_t hiword_index = -(size_t)1; - uint64_t hiword64 = ~(BignumInt)0; - - /* - * Find the highest nonzero word and its index. - */ - mp_find_highest_nonzero_word_pair(x, 0, &hiword_index, &hiword64, NULL); - BignumInt hiword = hiword64; /* in case BignumInt is a narrower type */ - - /* - * Find the index of the highest set bit within hiword. - */ - BignumInt hibit_index = 0; - for (size_t i = (1 << (BIGNUM_INT_BITS_BITS-1)); i != 0; i >>= 1) { - BignumInt shifted_word = hiword >> i; - BignumInt indicator = - (BignumInt)(-shifted_word) >> (BIGNUM_INT_BITS-1); - hiword ^= (shifted_word ^ hiword ) & -indicator; - hibit_index += i & -(size_t)indicator; - } - - /* - * Put together the result. - */ - return (hiword_index << BIGNUM_INT_BITS_BITS) + hibit_index + 1; -} - -/* - * Shared code between the hex and decimal output functions to get rid - * of leading zeroes on the output string. The idea is that we wrote - * out a fixed number of digits and a trailing \0 byte into 'buf', and - * now we want to shift it all left so that the first nonzero digit - * moves to buf[0] (or, if there are no nonzero digits at all, we move - * up by 'maxtrim', so that we return 0 as "0" instead of ""). - */ -static void trim_leading_zeroes(char *buf, size_t bufsize, size_t maxtrim) -{ - size_t trim = maxtrim; - - /* - * Look for the first character not equal to '0', to find the - * shift count. - */ - if (trim > 0) { - for (size_t pos = trim; pos-- > 0 ;) { - uint8_t diff = buf[pos] ^ '0'; - size_t mask = -((((size_t)diff) - 1) >> (SIZE_T_BITS - 1)); - trim ^= (trim ^ pos) & ~mask; - } - } - - /* - * Now do the shift, in log n passes each of which does a - * conditional shift by 2^i bytes if bit i is set in the shift - * count. - */ - uint8_t *ubuf = (uint8_t *)buf; - for (size_t logd = 0; bufsize >> logd; logd++) { - uint8_t mask = -(uint8_t)((trim >> logd) & 1); - size_t d = (size_t)1 << logd; - for (size_t i = 0; i+d < bufsize; i++) { - uint8_t diff = mask & (ubuf[i] ^ ubuf[i+d]); - ubuf[i] ^= diff; - ubuf[i+d] ^= diff; - } - } -} - -/* - * Binary to decimal conversion. Our strategy here is to extract each - * decimal digit by finding the input number's residue mod 10, then - * subtract that off to give an exact multiple of 10, which then means - * you can safely divide by 10 by means of shifting right one bit and - * then multiplying by the inverse of 5 mod 2^n. - */ -char *mp_get_decimal(mp_int *x_orig) -{ - mp_int *x = mp_copy(x_orig), *y = mp_make_sized(x->nw); - - /* - * The inverse of 5 mod 2^lots is 0xccccccccccccccccccccd, for an - * appropriate number of 'c's. Manually construct an integer the - * right size. - */ - mp_int *inv5 = mp_make_sized(x->nw); - assert(BIGNUM_INT_BITS % 8 == 0); - for (size_t i = 0; i < inv5->nw; i++) - inv5->w[i] = BIGNUM_INT_MASK / 5 * 4; - inv5->w[0]++; - - /* - * 146/485 is an upper bound (and also a continued-fraction - * convergent) of log10(2), so this is a conservative estimate of - * the number of decimal digits needed to store a value that fits - * in this many binary bits. - */ - assert(x->nw < (~(size_t)1) / (146 * BIGNUM_INT_BITS)); - size_t bufsize = size_t_max(x->nw * (146 * BIGNUM_INT_BITS) / 485, 1) + 2; - char *outbuf = snewn(bufsize, char); - outbuf[bufsize - 1] = '\0'; - - /* - * Loop over the number generating digits from the least - * significant upwards, so that we write to outbuf in reverse - * order. - */ - for (size_t pos = bufsize - 1; pos-- > 0 ;) { - /* - * Find the current residue mod 10. We do this by first - * summing the bytes of the number, with all but the lowest - * one multiplied by 6 (because 256^i == 6 mod 10 for all - * i>0). That gives us a single word congruent mod 10 to the - * input number, and then we reduce it further by manual - * multiplication and shifting, just in case the compiler - * target implements the C division operator in a way that has - * input-dependent timing. - */ - uint32_t low_digit = 0, maxval = 0, mult = 1; - for (size_t i = 0; i < x->nw; i++) { - for (unsigned j = 0; j < BIGNUM_INT_BYTES; j++) { - low_digit += mult * (0xFF & (x->w[i] >> (8*j))); - maxval += mult * 0xFF; - mult = 6; - } - /* - * For _really_ big numbers, prevent overflow of t by - * periodically folding the top half of the accumulator - * into the bottom half, using the same rule 'multiply by - * 6 when shifting down by one or more whole bytes'. - */ - if (maxval > UINT32_MAX - (6 * 0xFF * BIGNUM_INT_BYTES)) { - low_digit = (low_digit & 0xFFFF) + 6 * (low_digit >> 16); - maxval = (maxval & 0xFFFF) + 6 * (maxval >> 16); - } - } - - /* - * Final reduction of low_digit. We multiply by 2^32 / 10 - * (that's the constant 0x19999999) to get a 64-bit value - * whose top 32 bits are the approximate quotient - * low_digit/10; then we subtract off 10 times that; and - * finally we do one last trial subtraction of 10 by adding 6 - * (which sets bit 4 if the number was just over 10) and then - * testing bit 4. - */ - low_digit -= 10 * ((0x19999999ULL * low_digit) >> 32); - low_digit -= 10 * ((low_digit + 6) >> 4); - - assert(low_digit < 10); /* make sure we did reduce fully */ - outbuf[pos] = '0' + low_digit; - - /* - * Now subtract off that digit, divide by 2 (using a right - * shift) and by 5 (using the modular inverse), to get the - * next output digit into the units position. - */ - mp_sub_integer_into(x, x, low_digit); - mp_rshift_fixed_into(y, x, 1); - mp_mul_into(x, y, inv5); - } - - mp_free(x); - mp_free(y); - mp_free(inv5); - - trim_leading_zeroes(outbuf, bufsize, bufsize - 2); - return outbuf; -} - -/* - * Binary to hex conversion. Reasonably simple (only a spot of bit - * twiddling to choose whether to output a digit or a letter for each - * nibble). - */ -static char *mp_get_hex_internal(mp_int *x, uint8_t letter_offset) -{ - size_t nibbles = x->nw * BIGNUM_INT_BYTES * 2; - size_t bufsize = nibbles + 1; - char *outbuf = snewn(bufsize, char); - outbuf[nibbles] = '\0'; - - for (size_t nibble = 0; nibble < nibbles; nibble++) { - size_t word_idx = nibble / (BIGNUM_INT_BYTES*2); - size_t nibble_within_word = nibble % (BIGNUM_INT_BYTES*2); - uint8_t digitval = 0xF & (x->w[word_idx] >> (nibble_within_word * 4)); - - uint8_t mask = -((digitval + 6) >> 4); - char digit = digitval + '0' + (letter_offset & mask); - outbuf[nibbles-1 - nibble] = digit; - } - - trim_leading_zeroes(outbuf, bufsize, nibbles - 1); - return outbuf; -} - -char *mp_get_hex(mp_int *x) -{ - return mp_get_hex_internal(x, 'a' - ('0'+10)); -} - -char *mp_get_hex_uppercase(mp_int *x) -{ - return mp_get_hex_internal(x, 'A' - ('0'+10)); -} - -/* - * Routines for reading and writing the SSH-1 and SSH-2 wire formats - * for multiprecision integers, declared in marshal.h. - * - * These can't avoid having control flow dependent on the true bit - * size of the number, because the wire format requires the number of - * output bytes to depend on that. - */ -void BinarySink_put_mp_ssh1(BinarySink *bs, mp_int *x) -{ - size_t bits = mp_get_nbits(x); - size_t bytes = (bits + 7) / 8; - - assert(bits < 0x10000); - put_uint16(bs, bits); - for (size_t i = bytes; i-- > 0 ;) - put_byte(bs, mp_get_byte(x, i)); -} - -void BinarySink_put_mp_ssh2(BinarySink *bs, mp_int *x) -{ - size_t bytes = (mp_get_nbits(x) + 8) / 8; - - put_uint32(bs, bytes); - for (size_t i = bytes; i-- > 0 ;) - put_byte(bs, mp_get_byte(x, i)); -} - -mp_int *BinarySource_get_mp_ssh1(BinarySource *src) -{ - unsigned bitc = get_uint16(src); - ptrlen bytes = get_data(src, (bitc + 7) / 8); - if (get_err(src)) { - return mp_from_integer(0); - } else { - mp_int *toret = mp_from_bytes_be(bytes); - /* SSH-1.5 spec says that it's OK for the prefix uint16 to be - * _greater_ than the actual number of bits */ - if (mp_get_nbits(toret) > bitc) { - src->err = BSE_INVALID; - mp_free(toret); - toret = mp_from_integer(0); - } - return toret; - } -} - -mp_int *BinarySource_get_mp_ssh2(BinarySource *src) -{ - ptrlen bytes = get_string(src); - if (get_err(src)) { - return mp_from_integer(0); - } else { - const unsigned char *p = bytes.ptr; - if ((bytes.len > 0 && - ((p[0] & 0x80) || - (p[0] == 0 && (bytes.len <= 1 || !(p[1] & 0x80)))))) { - src->err = BSE_INVALID; - return mp_from_integer(0); - } - return mp_from_bytes_be(bytes); - } -} - -/* - * Make an mp_int structure whose words array aliases a subinterval of - * some other mp_int. This makes it easy to read or write just the low - * or high words of a number, e.g. to add a number starting from a - * high bit position, or to reduce mod 2^{n*BIGNUM_INT_BITS}. - * - * The convention throughout this code is that when we store an mp_int - * directly by value, we always expect it to be an alias of some kind, - * so its words array won't ever need freeing. Whereas an 'mp_int *' - * has an owner, who knows whether it needs freeing or whether it was - * created by address-taking an alias. - */ -static mp_int mp_make_alias(mp_int *in, size_t offset, size_t len) -{ - /* - * Bounds-check the offset and length so that we always return - * something valid, even if it's not necessarily the length the - * caller asked for. - */ - if (offset > in->nw) - offset = in->nw; - if (len > in->nw - offset) - len = in->nw - offset; - - mp_int toret; - toret.nw = len; - toret.w = in->w + offset; - return toret; -} - -/* - * A special case of mp_make_alias: in some cases we preallocate a - * large mp_int to use as scratch space (to avoid pointless - * malloc/free churn in recursive or iterative work). - * - * mp_alloc_from_scratch creates an alias of size 'len' to part of - * 'pool', and adjusts 'pool' itself so that further allocations won't - * overwrite that space. - * - * There's no free function to go with this. Typically you just copy - * the pool mp_int by value, allocate from the copy, and when you're - * done with those allocations, throw the copy away and go back to the - * original value of pool. (A mark/release system.) - */ -static mp_int mp_alloc_from_scratch(mp_int *pool, size_t len) -{ - assert(len <= pool->nw); - mp_int toret = mp_make_alias(pool, 0, len); - *pool = mp_make_alias(pool, len, pool->nw); - return toret; -} - -/* - * Internal component common to lots of assorted add/subtract code. - * Reads words from a,b; writes into w_out (which might be NULL if the - * output isn't even needed). Takes an input carry flag in 'carry', - * and returns the output carry. Each word read from b is ANDed with - * b_and and then XORed with b_xor. - * - * So you can implement addition by setting b_and to all 1s and b_xor - * to 0; you can subtract by making b_xor all 1s too (effectively - * bit-flipping b) and also passing 1 as the input carry (to turn - * one's complement into two's complement). And you can do conditional - * add/subtract by choosing b_and to be all 1s or all 0s based on a - * condition, because the value of b will be totally ignored if b_and - * == 0. - */ -static BignumCarry mp_add_masked_into( - BignumInt *w_out, size_t rw, mp_int *a, mp_int *b, - BignumInt b_and, BignumInt b_xor, BignumCarry carry) -{ - for (size_t i = 0; i < rw; i++) { - BignumInt aword = mp_word(a, i), bword = mp_word(b, i), out; - bword = (bword & b_and) ^ b_xor; - BignumADC(out, carry, aword, bword, carry); - if (w_out) - w_out[i] = out; - } - return carry; -} - -/* - * Like the public mp_add_into except that it returns the output carry. - */ -static inline BignumCarry mp_add_into_internal(mp_int *r, mp_int *a, mp_int *b) -{ - return mp_add_masked_into(r->w, r->nw, a, b, ~(BignumInt)0, 0, 0); -} - -void mp_add_into(mp_int *r, mp_int *a, mp_int *b) -{ - mp_add_into_internal(r, a, b); -} - -void mp_sub_into(mp_int *r, mp_int *a, mp_int *b) -{ - mp_add_masked_into(r->w, r->nw, a, b, ~(BignumInt)0, ~(BignumInt)0, 1); -} - -void mp_and_into(mp_int *r, mp_int *a, mp_int *b) -{ - for (size_t i = 0; i < r->nw; i++) { - BignumInt aword = mp_word(a, i), bword = mp_word(b, i); - r->w[i] = aword & bword; - } -} - -void mp_or_into(mp_int *r, mp_int *a, mp_int *b) -{ - for (size_t i = 0; i < r->nw; i++) { - BignumInt aword = mp_word(a, i), bword = mp_word(b, i); - r->w[i] = aword | bword; - } -} - -void mp_xor_into(mp_int *r, mp_int *a, mp_int *b) -{ - for (size_t i = 0; i < r->nw; i++) { - BignumInt aword = mp_word(a, i), bword = mp_word(b, i); - r->w[i] = aword ^ bword; - } -} - -void mp_bic_into(mp_int *r, mp_int *a, mp_int *b) -{ - for (size_t i = 0; i < r->nw; i++) { - BignumInt aword = mp_word(a, i), bword = mp_word(b, i); - r->w[i] = aword & ~bword; - } -} - -static void mp_cond_negate(mp_int *r, mp_int *x, unsigned yes) -{ - BignumCarry carry = yes; - BignumInt flip = -(BignumInt)yes; - for (size_t i = 0; i < r->nw; i++) { - BignumInt xword = mp_word(x, i); - xword ^= flip; - BignumADC(r->w[i], carry, 0, xword, carry); - } -} - -/* - * Similar to mp_add_masked_into, but takes a C integer instead of an - * mp_int as the masked operand. - */ -static BignumCarry mp_add_masked_integer_into( - BignumInt *w_out, size_t rw, mp_int *a, uintmax_t b, - BignumInt b_and, BignumInt b_xor, BignumCarry carry) -{ - for (size_t i = 0; i < rw; i++) { - BignumInt aword = mp_word(a, i); - BignumInt bword = b; - b = shift_right_by_one_word(b); - BignumInt out; - bword = (bword ^ b_xor) & b_and; - BignumADC(out, carry, aword, bword, carry); - if (w_out) - w_out[i] = out; - } - return carry; -} - -void mp_add_integer_into(mp_int *r, mp_int *a, uintmax_t n) -{ - mp_add_masked_integer_into(r->w, r->nw, a, n, ~(BignumInt)0, 0, 0); -} - -void mp_sub_integer_into(mp_int *r, mp_int *a, uintmax_t n) -{ - mp_add_masked_integer_into(r->w, r->nw, a, n, - ~(BignumInt)0, ~(BignumInt)0, 1); -} - -/* - * Sets r to a + n << (word_index * BIGNUM_INT_BITS), treating - * word_index as secret data. - */ -static void mp_add_integer_into_shifted_by_words( - mp_int *r, mp_int *a, uintmax_t n, size_t word_index) -{ - unsigned indicator = 0; - BignumCarry carry = 0; - - for (size_t i = 0; i < r->nw; i++) { - /* indicator becomes 1 when we reach the index that the least - * significant bits of n want to be placed at, and it stays 1 - * thereafter. */ - indicator |= 1 ^ normalise_to_1(i ^ word_index); - - /* If indicator is 1, we add the low bits of n into r, and - * shift n down. If it's 0, we add zero bits into r, and - * leave n alone. */ - BignumInt bword = n & -(BignumInt)indicator; - uintmax_t new_n = shift_right_by_one_word(n); - n ^= (n ^ new_n) & -(uintmax_t)indicator; - - BignumInt aword = mp_word(a, i); - BignumInt out; - BignumADC(out, carry, aword, bword, carry); - r->w[i] = out; - } -} - -void mp_mul_integer_into(mp_int *r, mp_int *a, uint16_t n) -{ - BignumInt carry = 0, mult = n; - for (size_t i = 0; i < r->nw; i++) { - BignumInt aword = mp_word(a, i); - BignumMULADD(carry, r->w[i], aword, mult, carry); - } - assert(!carry); -} - -void mp_cond_add_into(mp_int *r, mp_int *a, mp_int *b, unsigned yes) -{ - BignumInt mask = -(BignumInt)(yes & 1); - mp_add_masked_into(r->w, r->nw, a, b, mask, 0, 0); -} - -void mp_cond_sub_into(mp_int *r, mp_int *a, mp_int *b, unsigned yes) -{ - BignumInt mask = -(BignumInt)(yes & 1); - mp_add_masked_into(r->w, r->nw, a, b, mask, mask, 1 & mask); -} - -/* - * Ordered comparison between unsigned numbers is done by subtracting - * one from the other and looking at the output carry. - */ -unsigned mp_cmp_hs(mp_int *a, mp_int *b) -{ - size_t rw = size_t_max(a->nw, b->nw); - return mp_add_masked_into(NULL, rw, a, b, ~(BignumInt)0, ~(BignumInt)0, 1); -} - -unsigned mp_hs_integer(mp_int *x, uintmax_t n) -{ - BignumInt carry = 1; - size_t nwords = sizeof(n)/BIGNUM_INT_BYTES; - for (size_t i = 0, e = size_t_max(x->nw, nwords); i < e; i++) { - BignumInt nword = n; - n = shift_right_by_one_word(n); - BignumInt dummy_out; - BignumADC(dummy_out, carry, mp_word(x, i), ~nword, carry); - (void)dummy_out; - } - return carry; -} - -/* - * Equality comparison is done by bitwise XOR of the input numbers, - * ORing together all the output words, and normalising the result - * using our careful normalise_to_1 helper function. - */ -unsigned mp_cmp_eq(mp_int *a, mp_int *b) -{ - BignumInt diff = 0; - for (size_t i = 0, limit = size_t_max(a->nw, b->nw); i < limit; i++) - diff |= mp_word(a, i) ^ mp_word(b, i); - return 1 ^ normalise_to_1(diff); /* return 1 if diff _is_ zero */ -} - -unsigned mp_eq_integer(mp_int *x, uintmax_t n) -{ - BignumInt diff = 0; - size_t nwords = sizeof(n)/BIGNUM_INT_BYTES; - for (size_t i = 0, e = size_t_max(x->nw, nwords); i < e; i++) { - BignumInt nword = n; - n = shift_right_by_one_word(n); - diff |= mp_word(x, i) ^ nword; - } - return 1 ^ normalise_to_1(diff); /* return 1 if diff _is_ zero */ -} - -static void mp_neg_into(mp_int *r, mp_int *a) -{ - mp_int zero; - zero.nw = 0; - mp_sub_into(r, &zero, a); -} - -mp_int *mp_add(mp_int *x, mp_int *y) -{ - mp_int *r = mp_make_sized(size_t_max(x->nw, y->nw) + 1); - mp_add_into(r, x, y); - return r; -} - -mp_int *mp_sub(mp_int *x, mp_int *y) -{ - mp_int *r = mp_make_sized(size_t_max(x->nw, y->nw)); - mp_sub_into(r, x, y); - return r; -} - -/* - * Internal routine: multiply and accumulate in the trivial O(N^2) - * way. Sets r <- r + a*b. - */ -static void mp_mul_add_simple(mp_int *r, mp_int *a, mp_int *b) -{ - BignumInt *aend = a->w + a->nw, *bend = b->w + b->nw, *rend = r->w + r->nw; - - for (BignumInt *ap = a->w, *rp = r->w; - ap < aend && rp < rend; ap++, rp++) { - - BignumInt adata = *ap, carry = 0, *rq = rp; - - for (BignumInt *bp = b->w; bp < bend && rq < rend; bp++, rq++) { - BignumInt bdata = bp < bend ? *bp : 0; - BignumMULADD2(carry, *rq, adata, bdata, *rq, carry); - } - - for (; rq < rend; rq++) - BignumADC(*rq, carry, carry, *rq, 0); - } -} - -#ifndef KARATSUBA_THRESHOLD /* allow redefinition via -D for testing */ -#define KARATSUBA_THRESHOLD 24 -#endif - -static inline size_t mp_mul_scratchspace_unary(size_t n) -{ - /* - * Simplistic and overcautious bound on the amount of scratch - * space that the recursive multiply function will need. - * - * The rationale is: on the main Karatsuba branch of - * mp_mul_internal, which is the most space-intensive one, we - * allocate space for (a0+a1) and (b0+b1) (each just over half the - * input length n) and their product (the sum of those sizes, i.e. - * just over n itself). Then in order to actually compute the - * product, we do a recursive multiplication of size just over n. - * - * If all those 'just over' weren't there, and everything was - * _exactly_ half the length, you'd get the amount of space for a - * size-n multiply defined by the recurrence M(n) = 2n + M(n/2), - * which is satisfied by M(n) = 4n. But instead it's (2n plus a - * word or two) and M(n/2 plus a word or two). On the assumption - * that there's still some constant k such that M(n) <= kn, this - * gives us kn = 2n + w + k(n/2 + w), where w is a small constant - * (one or two words). That simplifies to kn/2 = 2n + (k+1)w, and - * since we don't even _start_ needing scratch space until n is at - * least 50, we can bound 2n + (k+1)w above by 3n, giving k=6. - * - * So I claim that 6n words of scratch space will suffice, and I - * check that by assertion at every stage of the recursion. - */ - return n * 6; -} - -static size_t mp_mul_scratchspace(size_t rw, size_t aw, size_t bw) -{ - size_t inlen = size_t_min(rw, size_t_max(aw, bw)); - return mp_mul_scratchspace_unary(inlen); -} - -static void mp_mul_internal(mp_int *r, mp_int *a, mp_int *b, mp_int scratch) -{ - size_t inlen = size_t_min(r->nw, size_t_max(a->nw, b->nw)); - assert(scratch.nw >= mp_mul_scratchspace_unary(inlen)); - - mp_clear(r); - - if (inlen < KARATSUBA_THRESHOLD || a->nw == 0 || b->nw == 0) { - /* - * The input numbers are too small to bother optimising. Go - * straight to the simple primitive approach. - */ - mp_mul_add_simple(r, a, b); - return; - } - - /* - * Karatsuba divide-and-conquer algorithm. We 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. - */ - - /* Break up the input as botlen + toplen, with botlen >= toplen. - * The 'base' D is equal to 2^{botlen * BIGNUM_INT_BITS}. */ - size_t toplen = inlen / 2; - size_t botlen = inlen - toplen; - - /* Alias bignums that address the two halves of a,b, and useful - * pieces of r. */ - mp_int a0 = mp_make_alias(a, 0, botlen); - mp_int b0 = mp_make_alias(b, 0, botlen); - mp_int a1 = mp_make_alias(a, botlen, toplen); - mp_int b1 = mp_make_alias(b, botlen, toplen); - mp_int r0 = mp_make_alias(r, 0, botlen*2); - mp_int r1 = mp_make_alias(r, botlen, r->nw); - mp_int r2 = mp_make_alias(r, botlen*2, r->nw); - - /* Recurse to compute a0*b0 and a1*b1, in their correct positions - * in the output bignum. They can't overlap. */ - mp_mul_internal(&r0, &a0, &b0, scratch); - mp_mul_internal(&r2, &a1, &b1, scratch); - - if (r->nw < inlen*2) { - /* - * The output buffer isn't large enough to require the whole - * product, so some of a1*b1 won't have been stored. In that - * case we won't try to do the full Karatsuba optimisation; - * we'll just recurse again to compute a0*b1 and a1*b0 - or at - * least as much of them as the output buffer size requires - - * and add each one in. - */ - mp_int s = mp_alloc_from_scratch( - &scratch, size_t_min(botlen+toplen, r1.nw)); - - mp_mul_internal(&s, &a0, &b1, scratch); - mp_add_into(&r1, &r1, &s); - mp_mul_internal(&s, &a1, &b0, scratch); - mp_add_into(&r1, &r1, &s); - return; - } - - /* a0+a1 and b0+b1 */ - mp_int asum = mp_alloc_from_scratch(&scratch, botlen+1); - mp_int bsum = mp_alloc_from_scratch(&scratch, botlen+1); - mp_add_into(&asum, &a0, &a1); - mp_add_into(&bsum, &b0, &b1); - - /* Their product */ - mp_int product = mp_alloc_from_scratch(&scratch, botlen*2+1); - mp_mul_internal(&product, &asum, &bsum, scratch); - - /* Subtract off the outer terms we already have */ - mp_sub_into(&product, &product, &r0); - mp_sub_into(&product, &product, &r2); - - /* And add it in with the right offset. */ - mp_add_into(&r1, &r1, &product); -} - -void mp_mul_into(mp_int *r, mp_int *a, mp_int *b) -{ - mp_int *scratch = mp_make_sized(mp_mul_scratchspace(r->nw, a->nw, b->nw)); - mp_mul_internal(r, a, b, *scratch); - mp_free(scratch); -} - -mp_int *mp_mul(mp_int *x, mp_int *y) -{ - mp_int *r = mp_make_sized(x->nw + y->nw); - mp_mul_into(r, x, y); - return r; -} - -void mp_lshift_fixed_into(mp_int *r, mp_int *a, size_t bits) -{ - size_t words = bits / BIGNUM_INT_BITS; - size_t bitoff = bits % BIGNUM_INT_BITS; - - for (size_t i = r->nw; i-- > 0 ;) { - if (i < words) { - r->w[i] = 0; - } else { - r->w[i] = mp_word(a, i - words); - if (bitoff != 0) { - r->w[i] <<= bitoff; - if (i > words) - r->w[i] |= mp_word(a, i - words - 1) >> - (BIGNUM_INT_BITS - bitoff); - } - } - } -} - -void mp_rshift_fixed_into(mp_int *r, mp_int *a, size_t bits) -{ - size_t words = bits / BIGNUM_INT_BITS; - size_t bitoff = bits % BIGNUM_INT_BITS; - - for (size_t i = 0; i < r->nw; i++) { - r->w[i] = mp_word(a, i + words); - if (bitoff != 0) { - r->w[i] >>= bitoff; - r->w[i] |= mp_word(a, i + words + 1) << (BIGNUM_INT_BITS - bitoff); - } - } -} - -mp_int *mp_lshift_fixed(mp_int *x, size_t bits) -{ - size_t words = (bits + BIGNUM_INT_BITS - 1) / BIGNUM_INT_BITS; - mp_int *r = mp_make_sized(x->nw + words); - mp_lshift_fixed_into(r, x, bits); - return r; -} - -mp_int *mp_rshift_fixed(mp_int *x, size_t bits) -{ - size_t words = bits / BIGNUM_INT_BITS; - size_t nw = x->nw - size_t_min(x->nw, words); - mp_int *r = mp_make_sized(size_t_max(nw, 1)); - mp_rshift_fixed_into(r, x, bits); - return r; -} - -/* - * Safe right shift is done using the same technique as - * trim_leading_zeroes above: you make an n-word left shift by - * composing an appropriate subset of power-of-2-sized shifts, so it - * takes log_2(n) loop iterations each of which does a different shift - * by a power of 2 words, using the usual bit twiddling to make the - * whole shift conditional on the appropriate bit of n. - */ -static void mp_rshift_safe_in_place(mp_int *r, size_t bits) -{ - size_t wordshift = bits / BIGNUM_INT_BITS; - size_t bitshift = bits % BIGNUM_INT_BITS; - - unsigned clear = (r->nw - wordshift) >> (CHAR_BIT * sizeof(size_t) - 1); - mp_cond_clear(r, clear); - - for (unsigned bit = 0; r->nw >> bit; bit++) { - size_t word_offset = (size_t)1 << bit; - BignumInt mask = -(BignumInt)((wordshift >> bit) & 1); - for (size_t i = 0; i < r->nw; i++) { - BignumInt w = mp_word(r, i + word_offset); - r->w[i] ^= (r->w[i] ^ w) & mask; - } - } - - /* - * That's done the shifting by words; now we do the shifting by - * bits. - */ - for (unsigned bit = 0; bit < BIGNUM_INT_BITS_BITS; bit++) { - unsigned shift = 1 << bit, upshift = BIGNUM_INT_BITS - shift; - BignumInt mask = -(BignumInt)((bitshift >> bit) & 1); - for (size_t i = 0; i < r->nw; i++) { - BignumInt w = ((r->w[i] >> shift) | (mp_word(r, i+1) << upshift)); - r->w[i] ^= (r->w[i] ^ w) & mask; - } - } -} - -mp_int *mp_rshift_safe(mp_int *x, size_t bits) -{ - mp_int *r = mp_copy(x); - mp_rshift_safe_in_place(r, bits); - return r; -} - -void mp_rshift_safe_into(mp_int *r, mp_int *x, size_t bits) -{ - mp_copy_into(r, x); - mp_rshift_safe_in_place(r, bits); -} - -static void mp_lshift_safe_in_place(mp_int *r, size_t bits) -{ - size_t wordshift = bits / BIGNUM_INT_BITS; - size_t bitshift = bits % BIGNUM_INT_BITS; - - /* - * Same strategy as mp_rshift_safe_in_place, but of course the - * other way up. - */ - - unsigned clear = (r->nw - wordshift) >> (CHAR_BIT * sizeof(size_t) - 1); - mp_cond_clear(r, clear); - - for (unsigned bit = 0; r->nw >> bit; bit++) { - size_t word_offset = (size_t)1 << bit; - BignumInt mask = -(BignumInt)((wordshift >> bit) & 1); - for (size_t i = r->nw; i-- > 0 ;) { - BignumInt w = mp_word(r, i - word_offset); - r->w[i] ^= (r->w[i] ^ w) & mask; - } - } - - size_t downshift = BIGNUM_INT_BITS - bitshift; - size_t no_shift = (downshift >> BIGNUM_INT_BITS_BITS); - downshift &= ~-(size_t)no_shift; - BignumInt downshifted_mask = ~-(BignumInt)no_shift; - - for (size_t i = r->nw; i-- > 0 ;) { - r->w[i] = (r->w[i] << bitshift) | - ((mp_word(r, i-1) >> downshift) & downshifted_mask); - } -} - -void mp_lshift_safe_into(mp_int *r, mp_int *x, size_t bits) -{ - mp_copy_into(r, x); - mp_lshift_safe_in_place(r, bits); -} - -void mp_reduce_mod_2to(mp_int *x, size_t p) -{ - size_t word = p / BIGNUM_INT_BITS; - size_t mask = ((size_t)1 << (p % BIGNUM_INT_BITS)) - 1; - for (; word < x->nw; word++) { - x->w[word] &= mask; - mask = 0; - } -} - -/* - * Inverse mod 2^n is computed by an iterative technique which doubles - * the number of bits at each step. - */ -mp_int *mp_invert_mod_2to(mp_int *x, size_t p) -{ - /* Input checks: x must be coprime to the modulus, i.e. odd, and p - * can't be zero */ - assert(x->nw > 0); - assert(x->w[0] & 1); - assert(p > 0); - - size_t rw = (p + BIGNUM_INT_BITS - 1) / BIGNUM_INT_BITS; - rw = size_t_max(rw, 1); - mp_int *r = mp_make_sized(rw); - - size_t mul_scratchsize = mp_mul_scratchspace(2*rw, rw, rw); - mp_int *scratch_orig = mp_make_sized(6 * rw + mul_scratchsize); - mp_int scratch_per_iter = *scratch_orig; - mp_int mul_scratch = mp_alloc_from_scratch( - &scratch_per_iter, mul_scratchsize); - - r->w[0] = 1; - - for (size_t b = 1; b < p; b <<= 1) { - /* - * In each step of this iteration, we have the inverse of x - * mod 2^b, and we want the inverse of x mod 2^{2b}. - * - * Write B = 2^b for convenience, so we want x^{-1} mod B^2. - * Let x = x_0 + B x_1 + k B^2, with 0 <= x_0,x_1 < B. - * - * We want to find r_0 and r_1 such that - * (r_1 B + r_0) (x_1 B + x_0) == 1 (mod B^2) - * - * To begin with, we know r_0 must be the inverse mod B of - * x_0, i.e. of x, i.e. it is the inverse we computed in the - * previous iteration. So now all we need is r_1. - * - * Multiplying out, neglecting multiples of B^2, and writing - * x_0 r_0 = K B + 1, we have - * - * r_1 x_0 B + r_0 x_1 B + K B == 0 (mod B^2) - * => r_1 x_0 B == - r_0 x_1 B - K B (mod B^2) - * => r_1 x_0 == - r_0 x_1 - K (mod B) - * => r_1 == r_0 (- r_0 x_1 - K) (mod B) - * - * (the last step because we multiply through by the inverse - * of x_0, which we already know is r_0). - */ - - mp_int scratch_this_iter = scratch_per_iter; - size_t Bw = (b + BIGNUM_INT_BITS - 1) / BIGNUM_INT_BITS; - size_t B2w = (2*b + BIGNUM_INT_BITS - 1) / BIGNUM_INT_BITS; - - /* Start by finding K: multiply x_0 by r_0, and shift down. */ - mp_int x0 = mp_alloc_from_scratch(&scratch_this_iter, Bw); - mp_copy_into(&x0, x); - mp_reduce_mod_2to(&x0, b); - mp_int r0 = mp_make_alias(r, 0, Bw); - mp_int Kshift = mp_alloc_from_scratch(&scratch_this_iter, B2w); - mp_mul_internal(&Kshift, &x0, &r0, mul_scratch); - mp_int K = mp_alloc_from_scratch(&scratch_this_iter, Bw); - mp_rshift_fixed_into(&K, &Kshift, b); - - /* Now compute the product r_0 x_1, reusing the space of Kshift. */ - mp_int x1 = mp_alloc_from_scratch(&scratch_this_iter, Bw); - mp_rshift_fixed_into(&x1, x, b); - mp_reduce_mod_2to(&x1, b); - mp_int r0x1 = mp_make_alias(&Kshift, 0, Bw); - mp_mul_internal(&r0x1, &r0, &x1, mul_scratch); - - /* Add K to that. */ - mp_add_into(&r0x1, &r0x1, &K); - - /* Negate it. */ - mp_neg_into(&r0x1, &r0x1); - - /* Multiply by r_0. */ - mp_int r1 = mp_alloc_from_scratch(&scratch_this_iter, Bw); - mp_mul_internal(&r1, &r0, &r0x1, mul_scratch); - mp_reduce_mod_2to(&r1, b); - - /* That's our r_1, so add it on to r_0 to get the full inverse - * output from this iteration. */ - mp_lshift_fixed_into(&K, &r1, (b % BIGNUM_INT_BITS)); - size_t Bpos = b / BIGNUM_INT_BITS; - mp_int r1_position = mp_make_alias(r, Bpos, B2w-Bpos); - mp_add_into(&r1_position, &r1_position, &K); - } - - /* Finally, reduce mod the precise desired number of bits. */ - mp_reduce_mod_2to(r, p); - - mp_free(scratch_orig); - return r; -} - -static size_t monty_scratch_size(MontyContext *mc) -{ - return 3*mc->rw + mc->pw + mp_mul_scratchspace(mc->pw, mc->rw, mc->rw); -} - -MontyContext *monty_new(mp_int *modulus) -{ - MontyContext *mc = snew(MontyContext); - - mc->rw = modulus->nw; - mc->rbits = mc->rw * BIGNUM_INT_BITS; - mc->pw = mc->rw * 2 + 1; - - mc->m = mp_copy(modulus); - - mc->minus_minv_mod_r = mp_invert_mod_2to(mc->m, mc->rbits); - mp_neg_into(mc->minus_minv_mod_r, mc->minus_minv_mod_r); - - mp_int *r = mp_make_sized(mc->rw + 1); - r->w[mc->rw] = 1; - mc->powers_of_r_mod_m[0] = mp_mod(r, mc->m); - mp_free(r); - - for (size_t j = 1; j < lenof(mc->powers_of_r_mod_m); j++) - mc->powers_of_r_mod_m[j] = mp_modmul( - mc->powers_of_r_mod_m[0], mc->powers_of_r_mod_m[j-1], mc->m); - - mc->scratch = mp_make_sized(monty_scratch_size(mc)); - - return mc; -} - -void monty_free(MontyContext *mc) -{ - mp_free(mc->m); - for (size_t j = 0; j < 3; j++) - mp_free(mc->powers_of_r_mod_m[j]); - mp_free(mc->minus_minv_mod_r); - mp_free(mc->scratch); - smemclr(mc, sizeof(*mc)); - sfree(mc); -} - -/* - * The main Montgomery reduction step. - */ -static mp_int monty_reduce_internal(MontyContext *mc, mp_int *x, mp_int scratch) -{ - /* - * The trick with Montgomery reduction is that on the one hand we - * want to reduce the size of the input by a factor of about r, - * and on the other hand, the two numbers we just multiplied were - * both stored with an extra factor of r multiplied in. So we - * computed ar*br = ab r^2, but we want to return abr, so we need - * to divide by r - and if we can do that by _actually dividing_ - * by r then this also reduces the size of the number. - * - * But we can only do that if the number we're dividing by r is a - * multiple of r. So first we must add an adjustment to it which - * clears its bottom 'rbits' bits. That adjustment must be a - * multiple of m in order to leave the residue mod n unchanged, so - * the question is, what multiple of m can we add to x to make it - * congruent to 0 mod r? And the answer is, x * (-m)^{-1} mod r. - */ - - /* x mod r */ - mp_int x_lo = mp_make_alias(x, 0, mc->rbits); - - /* x * (-m)^{-1}, i.e. the number we want to multiply by m */ - mp_int k = mp_alloc_from_scratch(&scratch, mc->rw); - mp_mul_internal(&k, &x_lo, mc->minus_minv_mod_r, scratch); - - /* m times that, i.e. the number we want to add to x */ - mp_int mk = mp_alloc_from_scratch(&scratch, mc->pw); - mp_mul_internal(&mk, mc->m, &k, scratch); - - /* Add it to x */ - mp_add_into(&mk, x, &mk); - - /* Reduce mod r, by simply making an alias to the upper words of x */ - mp_int toret = mp_make_alias(&mk, mc->rw, mk.nw - mc->rw); - - /* - * We'll generally be doing this after a multiplication of two - * fully reduced values. So our input could be anything up to m^2, - * and then we added up to rm to it. Hence, the maximum value is - * rm+m^2, and after dividing by r, that becomes r + m(m/r) < 2r. - * So a single trial-subtraction will finish reducing to the - * interval [0,m). - */ - mp_cond_sub_into(&toret, &toret, mc->m, mp_cmp_hs(&toret, mc->m)); - return toret; -} - -void monty_mul_into(MontyContext *mc, mp_int *r, mp_int *x, mp_int *y) -{ - assert(x->nw <= mc->rw); - assert(y->nw <= mc->rw); - - mp_int scratch = *mc->scratch; - mp_int tmp = mp_alloc_from_scratch(&scratch, 2*mc->rw); - mp_mul_into(&tmp, x, y); - mp_int reduced = monty_reduce_internal(mc, &tmp, scratch); - mp_copy_into(r, &reduced); - mp_clear(mc->scratch); -} - -mp_int *monty_mul(MontyContext *mc, mp_int *x, mp_int *y) -{ - mp_int *toret = mp_make_sized(mc->rw); - monty_mul_into(mc, toret, x, y); - return toret; -} - -mp_int *monty_modulus(MontyContext *mc) -{ - return mc->m; -} - -mp_int *monty_identity(MontyContext *mc) -{ - return mc->powers_of_r_mod_m[0]; -} - -mp_int *monty_invert(MontyContext *mc, mp_int *x) -{ - /* Given xr, we want to return x^{-1}r = (xr)^{-1} r^2 = - * monty_reduce((xr)^{-1} r^3) */ - mp_int *tmp = mp_invert(x, mc->m); - mp_int *toret = monty_mul(mc, tmp, mc->powers_of_r_mod_m[2]); - mp_free(tmp); - return toret; -} - -/* - * Importing a number into Montgomery representation involves - * multiplying it by r and reducing mod m. We use the general-purpose - * mp_modmul for this, in case the input number is out of range. - */ -mp_int *monty_import(MontyContext *mc, mp_int *x) -{ - return mp_modmul(x, mc->powers_of_r_mod_m[0], mc->m); -} - -void monty_import_into(MontyContext *mc, mp_int *r, mp_int *x) -{ - mp_int *imported = monty_import(mc, x); - mp_copy_into(r, imported); - mp_free(imported); -} - -/* - * Exporting a number means multiplying it by r^{-1}, which is exactly - * what monty_reduce does anyway, so we just do that. - */ -void monty_export_into(MontyContext *mc, mp_int *r, mp_int *x) -{ - assert(x->nw <= 2*mc->rw); - mp_int reduced = monty_reduce_internal(mc, x, *mc->scratch); - mp_copy_into(r, &reduced); - mp_clear(mc->scratch); -} - -mp_int *monty_export(MontyContext *mc, mp_int *x) -{ - mp_int *toret = mp_make_sized(mc->rw); - monty_export_into(mc, toret, x); - return toret; -} - -static void monty_reduce(MontyContext *mc, mp_int *x) -{ - mp_int reduced = monty_reduce_internal(mc, x, *mc->scratch); - mp_copy_into(x, &reduced); - mp_clear(mc->scratch); -} - -mp_int *monty_pow(MontyContext *mc, mp_int *base, mp_int *exponent) -{ - /* square builds up powers of the form base^{2^i}. */ - mp_int *square = mp_copy(base); - size_t i = 0; - - /* out accumulates the output value. Starts at 1 (in Montgomery - * representation) and we multiply in each base^{2^i}. */ - mp_int *out = mp_copy(mc->powers_of_r_mod_m[0]); - - /* tmp holds each product we compute and reduce. */ - mp_int *tmp = mp_make_sized(mc->rw * 2); - - while (true) { - mp_mul_into(tmp, out, square); - monty_reduce(mc, tmp); - mp_select_into(out, out, tmp, mp_get_bit(exponent, i)); - - if (++i >= exponent->nw * BIGNUM_INT_BITS) - break; - - mp_mul_into(tmp, square, square); - monty_reduce(mc, tmp); - mp_copy_into(square, tmp); - } - - mp_free(square); - mp_free(tmp); - mp_clear(mc->scratch); - return out; -} - -mp_int *mp_modpow(mp_int *base, mp_int *exponent, mp_int *modulus) -{ - assert(modulus->nw > 0); - assert(modulus->w[0] & 1); - - MontyContext *mc = monty_new(modulus); - mp_int *m_base = monty_import(mc, base); - mp_int *m_out = monty_pow(mc, m_base, exponent); - mp_int *out = monty_export(mc, m_out); - mp_free(m_base); - mp_free(m_out); - monty_free(mc); - return out; -} - -/* - * Given two input integers a,b which are not both even, computes d = - * gcd(a,b) and also two integers A,B such that A*a - B*b = d. A,B - * will be the minimal non-negative pair satisfying that criterion, - * which is equivalent to saying that 0 <= A < b/d and 0 <= B < a/d. - * - * This algorithm is an adapted form of Stein's algorithm, which - * computes gcd(a,b) using only addition and bit shifts (i.e. without - * needing general division), using the following rules: - * - * - if both of a,b are even, divide off a common factor of 2 - * - if one of a,b (WLOG a) is even, then gcd(a,b) = gcd(a/2,b), so - * just divide a by 2 - * - if both of a,b are odd, then WLOG a>b, and gcd(a,b) = - * gcd(b,(a-b)/2). - * - * Sometimes this function is used for modular inversion, in which - * case we already know we expect the two inputs to be coprime, so to - * save time the 'both even' initial case is assumed not to arise (or - * to have been handled already by the caller). So this function just - * performs a sequence of reductions in the following form: - * - * - if a,b are both odd, sort them so that a > b, and replace a with - * b-a; otherwise sort them so that a is the even one - * - either way, now a is even and b is odd, so divide a by 2. - * - * The big change to Stein's algorithm is that we need the Bezout - * coefficients as output, not just the gcd. So we need to know how to - * generate those in each case, based on the coefficients from the - * reduced pair of numbers: - * - * - If a is even, and u,v are such that u*(a/2) + v*b = d: - * + if u is also even, then this is just (u/2)*a + v*b = d - * + otherwise, (u+b)*(a/2) + (v-a/2)*b is also equal to d, and - * since u and b are both odd, (u+b)/2 is an integer, so we have - * ((u+b)/2)*a + (v-a/2)*b = d. - * - * - If a,b are both odd, and u,v are such that u*b + v*(a-b) = d, - * then v*a + (u-v)*b = d. - * - * In the case where we passed from (a,b) to (b,(a-b)/2), we regard it - * as having first subtracted b from a and then halved a, so both of - * these transformations must be done in sequence. - * - * The code below transforms this from a recursive to an iterative - * algorithm. We first reduce a,b to 0,1, recording at each stage - * whether we did the initial subtraction, and whether we had to swap - * the two values; then we iterate backwards over that record of what - * we did, applying the above rules for building up the Bezout - * coefficients as we go. Of course, all the case analysis is done by - * the usual bit-twiddling conditionalisation to avoid data-dependent - * control flow. - * - * Also, since these mp_ints are generally treated as unsigned, we - * store the coefficients by absolute value, with the semantics that - * they always have opposite sign, and in the unwinding loop we keep a - * bit indicating whether Aa-Bb is currently expected to be +d or -d, - * so that we can do one final conditional adjustment if it's -d. - * - * Once the reduction rules have managed to reduce the input numbers - * to (0,d), then they are stable (the next reduction will always - * divide the even one by 2, which maps 0 to 0). So it doesn't matter - * if we do more steps of the algorithm than necessary; hence, for - * constant time, we just need to find the maximum number we could - * _possibly_ require, and do that many. - * - * If a,b < 2^n, at most 2n iterations are required. Proof: consider - * the quantity Q = log_2(a) + log_2(b). Every step halves one of the - * numbers (and may also reduce one of them further by doing a - * subtraction beforehand, but in the worst case, not by much or not - * at all). So Q reduces by at least 1 per iteration, and it starts - * off with a value at most 2n. - * - * The worst case inputs (I think) are where x=2^{n-1} and y=2^n-1 - * (i.e. x is a power of 2 and y is all 1s). In that situation, the - * first n-1 steps repeatedly halve x until it's 1, and then there are - * n further steps each of which subtracts 1 from y and halves it. - */ -static void mp_bezout_into(mp_int *a_coeff_out, mp_int *b_coeff_out, - mp_int *gcd_out, mp_int *a_in, mp_int *b_in) -{ - size_t nw = size_t_max(1, size_t_max(a_in->nw, b_in->nw)); - - /* Make mutable copies of the input numbers */ - mp_int *a = mp_make_sized(nw), *b = mp_make_sized(nw); - mp_copy_into(a, a_in); - mp_copy_into(b, b_in); - - /* Space to build up the output coefficients, with an extra word - * so that intermediate values can overflow off the top and still - * right-shift back down to the correct value */ - mp_int *ac = mp_make_sized(nw + 1), *bc = mp_make_sized(nw + 1); - - /* And a general-purpose temp register */ - mp_int *tmp = mp_make_sized(nw); - - /* Space to record the sequence of reduction steps to unwind. We - * make it a BignumInt for no particular reason except that (a) - * mp_make_sized conveniently zeroes the allocation and mp_free - * wipes it, and (b) this way I can use mp_dump() if I have to - * debug this code. */ - size_t steps = 2 * nw * BIGNUM_INT_BITS; - mp_int *record = mp_make_sized( - (steps*2 + BIGNUM_INT_BITS - 1) / BIGNUM_INT_BITS); - - for (size_t step = 0; step < steps; step++) { - /* - * If a and b are both odd, we want to sort them so that a is - * larger. But if one is even, we want to sort them so that a - * is the even one. - */ - unsigned swap_if_both_odd = mp_cmp_hs(b, a); - unsigned swap_if_one_even = a->w[0] & 1; - unsigned both_odd = a->w[0] & b->w[0] & 1; - unsigned swap = swap_if_one_even ^ ( - (swap_if_both_odd ^ swap_if_one_even) & both_odd); - - mp_cond_swap(a, b, swap); - - /* - * If a,b are both odd, then a is the larger number, so - * subtract the smaller one from it. - */ - mp_cond_sub_into(a, a, b, both_odd); - - /* - * Now a is even, so divide it by two. - */ - mp_rshift_fixed_into(a, a, 1); - - /* - * Record the two 1-bit values both_odd and swap. - */ - mp_set_bit(record, step*2, both_odd); - mp_set_bit(record, step*2+1, swap); - } - - /* - * Now we expect to have reduced the two numbers to 0 and d, - * although we don't know which way round. (But we avoid checking - * this by assertion; sometimes we'll need to do this computation - * without giving away that we already know the inputs were bogus. - * So we'd prefer to just press on and return nonsense.) - */ - - if (gcd_out) { - /* - * At this point we can return the actual gcd. Since one of - * a,b is it and the other is zero, the easiest way to get it - * is to add them together. - */ - mp_add_into(gcd_out, a, b); - } - - /* - * If the caller _only_ wanted the gcd, and neither Bezout - * coefficient is even required, we can skip the entire unwind - * stage. - */ - if (a_coeff_out || b_coeff_out) { - - /* - * The Bezout coefficients of a,b at this point are simply 0 - * for whichever of a,b is zero, and 1 for whichever is - * nonzero. The nonzero number equals gcd(a,b), which by - * assumption is odd, so we can do this by just taking the low - * bit of each one. - */ - ac->w[0] = mp_get_bit(a, 0); - bc->w[0] = mp_get_bit(b, 0); - - /* - * Overwrite a,b themselves with those same numbers. This has - * the effect of dividing both of them by d, which will - * arrange that during the unwind stage we generate the - * minimal coefficients instead of a larger pair. - */ - mp_copy_into(a, ac); - mp_copy_into(b, bc); - - /* - * We'll maintain the invariant as we unwind that ac * a - bc - * * b is either +d or -d (or rather, +1/-1 after scaling by - * d), and we'll remember which. (We _could_ keep it at +d the - * whole time, but it would cost more work every time round - * the loop, so it's cheaper to fix that up once at the end.) - * - * Initially, the result is +d if a was the nonzero value after - * reduction, and -d if b was. - */ - unsigned minus_d = b->w[0]; - - for (size_t step = steps; step-- > 0 ;) { - /* - * Recover the data from the step we're unwinding. - */ - unsigned both_odd = mp_get_bit(record, step*2); - unsigned swap = mp_get_bit(record, step*2+1); - - /* - * Unwind the division: if our coefficient of a is odd, we - * adjust the coefficients by +b and +a respectively. - */ - unsigned adjust = ac->w[0] & 1; - mp_cond_add_into(ac, ac, b, adjust); - mp_cond_add_into(bc, bc, a, adjust); - - /* - * Now ac is definitely even, so we divide it by two. - */ - mp_rshift_fixed_into(ac, ac, 1); - - /* - * Now unwind the subtraction, if there was one, by adding - * ac to bc. - */ - mp_cond_add_into(bc, bc, ac, both_odd); - - /* - * Undo the transformation of the input numbers, by - * multiplying a by 2 and then adding b to a (the latter - * only if both_odd). - */ - mp_lshift_fixed_into(a, a, 1); - mp_cond_add_into(a, a, b, both_odd); - - /* - * Finally, undo the swap. If we do swap, this also - * reverses the sign of the current result ac*a+bc*b. - */ - mp_cond_swap(a, b, swap); - mp_cond_swap(ac, bc, swap); - minus_d ^= swap; - } - - /* - * Now we expect to have recovered the input a,b (or rather, - * the versions of them divided by d). But we might find that - * our current result is -d instead of +d, that is, we have - * A',B' such that A'a - B'b = -d. - * - * In that situation, we set A = b-A' and B = a-B', giving us - * Aa-Bb = ab - A'a - ab + B'b = +1. - */ - mp_sub_into(tmp, b, ac); - mp_select_into(ac, ac, tmp, minus_d); - mp_sub_into(tmp, a, bc); - mp_select_into(bc, bc, tmp, minus_d); - - /* - * Now we really are done. Return the outputs. - */ - if (a_coeff_out) - mp_copy_into(a_coeff_out, ac); - if (b_coeff_out) - mp_copy_into(b_coeff_out, bc); - - } - - mp_free(a); - mp_free(b); - mp_free(ac); - mp_free(bc); - mp_free(tmp); - mp_free(record); -} - -mp_int *mp_invert(mp_int *x, mp_int *m) -{ - mp_int *result = mp_make_sized(m->nw); - mp_bezout_into(result, NULL, NULL, x, m); - return result; -} - -void mp_gcd_into(mp_int *a, mp_int *b, mp_int *gcd, mp_int *A, mp_int *B) -{ - /* - * Identify shared factors of 2. To do this we OR the two numbers - * to get something whose lowest set bit is in the right place, - * remove all higher bits by ANDing it with its own negation, and - * use mp_get_nbits to find the location of the single remaining - * set bit. - */ - mp_int *tmp = mp_make_sized(size_t_max(a->nw, b->nw)); - for (size_t i = 0; i < tmp->nw; i++) - tmp->w[i] = mp_word(a, i) | mp_word(b, i); - BignumCarry carry = 1; - for (size_t i = 0; i < tmp->nw; i++) { - BignumInt negw; - BignumADC(negw, carry, 0, ~tmp->w[i], carry); - tmp->w[i] &= negw; - } - size_t shift = mp_get_nbits(tmp) - 1; - mp_free(tmp); - - /* - * Make copies of a,b with those shared factors of 2 divided off, - * so that at least one is odd (which is the precondition for - * mp_bezout_into). Compute the gcd of those. - */ - mp_int *as = mp_rshift_safe(a, shift); - mp_int *bs = mp_rshift_safe(b, shift); - mp_bezout_into(A, B, gcd, as, bs); - mp_free(as); - mp_free(bs); - - /* - * And finally shift the gcd back up (unless the caller didn't - * even ask for it), to put the shared factors of 2 back in. - */ - if (gcd) - mp_lshift_safe_in_place(gcd, shift); -} - -mp_int *mp_gcd(mp_int *a, mp_int *b) -{ - mp_int *gcd = mp_make_sized(size_t_min(a->nw, b->nw)); - mp_gcd_into(a, b, gcd, NULL, NULL); - return gcd; -} - -unsigned mp_coprime(mp_int *a, mp_int *b) -{ - mp_int *gcd = mp_gcd(a, b); - unsigned toret = mp_eq_integer(gcd, 1); - mp_free(gcd); - return toret; -} - -static uint32_t recip_approx_32(uint32_t x) -{ - /* - * Given an input x in [2^31,2^32), i.e. a uint32_t with its high - * bit set, this function returns an approximation to 2^63/x, - * computed using only multiplications and bit shifts just in case - * the C divide operator has non-constant time (either because the - * underlying machine instruction does, or because the operator - * expands to a library function on a CPU without hardware - * division). - * - * The coefficients are derived from those of the degree-9 - * polynomial which is the minimax-optimal approximation to that - * function on the given interval (generated using the Remez - * algorithm), converted into integer arithmetic with shifts used - * to maximise the number of significant bits at every state. (A - * sort of 'static floating point' - the exponent is statically - * known at every point in the code, so it never needs to be - * stored at run time or to influence runtime decisions.) - * - * Exhaustive iteration over the whole input space shows the - * largest possible error to be 1686.54. (The input value - * attaining that bound is 4226800006 == 0xfbefd986, whose true - * reciprocal is 2182116973.540... == 0x8210766d.8a6..., whereas - * this function returns 2182115287 == 0x82106fd7.) - */ - uint64_t r = 0x92db03d6ULL; - r = 0xf63e71eaULL - ((r*x) >> 34); - r = 0xb63721e8ULL - ((r*x) >> 34); - r = 0x9c2da00eULL - ((r*x) >> 33); - r = 0xaada0bb8ULL - ((r*x) >> 32); - r = 0xf75cd403ULL - ((r*x) >> 31); - r = 0xecf97a41ULL - ((r*x) >> 31); - r = 0x90d876cdULL - ((r*x) >> 31); - r = 0x6682799a0ULL - ((r*x) >> 26); - return r; -} - -void mp_divmod_into(mp_int *n, mp_int *d, mp_int *q_out, mp_int *r_out) -{ - assert(!mp_eq_integer(d, 0)); - - /* - * We do division by using Newton-Raphson iteration to converge to - * the reciprocal of d (or rather, R/d for R a sufficiently large - * power of 2); then we multiply that reciprocal by n; and we - * finish up with conditional subtraction. - * - * But we have to do it in a fixed number of N-R iterations, so we - * need some error analysis to know how many we might need. - * - * The iteration is derived by defining f(r) = d - R/r. - * Differentiating gives f'(r) = R/r^2, and the Newton-Raphson - * formula applied to those functions gives - * - * r_{i+1} = r_i - f(r_i) / f'(r_i) - * = r_i - (d - R/r_i) r_i^2 / R - * = r_i (2 R - d r_i) / R - * - * Now let e_i be the error in a given iteration, in the sense - * that - * - * d r_i = R + e_i - * i.e. e_i/R = (r_i - r_true) / r_true - * - * so e_i is the _relative_ error in r_i. - * - * We must also introduce a rounding-error term, because the - * division by R always gives an integer. This might make the - * output off by up to 1 (in the negative direction, because - * right-shifting gives floor of the true quotient). So when we - * divide by R, we must imagine adding some f in [0,1). Then we - * have - * - * d r_{i+1} = d r_i (2 R - d r_i) / R - d f - * = (R + e_i) (R - e_i) / R - d f - * = (R^2 - e_i^2) / R - d f - * = R - (e_i^2 / R + d f) - * => e_{i+1} = - (e_i^2 / R + d f) - * - * The sum of two positive quantities is bounded above by twice - * their max, and max |f| = 1, so we can bound this as follows: - * - * |e_{i+1}| <= 2 max (e_i^2/R, d) - * |e_{i+1}/R| <= 2 max ((e_i/R)^2, d/R) - * log2 |R/e_{i+1}| <= min (2 log2 |R/e_i|, log2 |R/d|) - 1 - * - * which tells us that the number of 'good' bits - i.e. - * log2(R/e_i) - very nearly doubles at every iteration (apart - * from that subtraction of 1), until it gets to the same size as - * log2(R/d). In other words, the size of R in bits has to be the - * size of denominator we're putting in, _plus_ the amount of - * precision we want to get back out. - * - * So when we multiply n (the input numerator) by our final - * reciprocal approximation r, but actually r differs from R/d by - * up to 2, then it follows that - * - * n/d - nr/R = n/d - [ n (R/d + e) ] / R - * = n/d - [ (n/d) R + n e ] / R - * = -ne/R - * => 0 <= n/d - nr/R < 2n/R - * - * so our computed quotient can differ from the true n/d by up to - * 2n/R. Hence, as long as we also choose R large enough that 2n/R - * is bounded above by a constant, we can guarantee a bounded - * number of final conditional-subtraction steps. - */ - - /* - * Get at least 32 of the most significant bits of the input - * number. - */ - size_t hiword_index = 0; - uint64_t hibits = 0, lobits = 0; - mp_find_highest_nonzero_word_pair(d, 64 - BIGNUM_INT_BITS, - &hiword_index, &hibits, &lobits); - - /* - * Make a shifted combination of those two words which puts the - * topmost bit of the number at bit 63. - */ - size_t shift_up = 0; - for (size_t i = BIGNUM_INT_BITS_BITS; i-- > 0;) { - size_t sl = (size_t)1 << i; /* left shift count */ - size_t sr = 64 - sl; /* complementary right-shift count */ - - /* Should we shift up? */ - unsigned indicator = 1 ^ normalise_to_1_u64(hibits >> sr); - - /* If we do, what will we get? */ - uint64_t new_hibits = (hibits << sl) | (lobits >> sr); - uint64_t new_lobits = lobits << sl; - size_t new_shift_up = shift_up + sl; - - /* Conditionally swap those values in. */ - hibits ^= (hibits ^ new_hibits ) & -(uint64_t)indicator; - lobits ^= (lobits ^ new_lobits ) & -(uint64_t)indicator; - shift_up ^= (shift_up ^ new_shift_up ) & -(size_t) indicator; - } - - /* - * So now we know the most significant 32 bits of d are at the top - * of hibits. Approximate the reciprocal of those bits. - */ - lobits = (uint64_t)recip_approx_32(hibits >> 32) << 32; - hibits = 0; - - /* - * And shift that up by as many bits as the input was shifted up - * just now, so that the product of this approximation and the - * actual input will be close to a fixed power of two regardless - * of where the MSB was. - * - * I do this in another log n individual passes, partly in case - * the CPU's register-controlled shift operation isn't - * time-constant, and also in case the compiler code-generates - * uint64_t shifts out of a variable number of smaller-word shift - * instructions, e.g. by splitting up into cases. - */ - for (size_t i = BIGNUM_INT_BITS_BITS; i-- > 0;) { - size_t sl = (size_t)1 << i; /* left shift count */ - size_t sr = 64 - sl; /* complementary right-shift count */ - - /* Should we shift up? */ - unsigned indicator = 1 & (shift_up >> i); - - /* If we do, what will we get? */ - uint64_t new_hibits = (hibits << sl) | (lobits >> sr); - uint64_t new_lobits = lobits << sl; - - /* Conditionally swap those values in. */ - hibits ^= (hibits ^ new_hibits ) & -(uint64_t)indicator; - lobits ^= (lobits ^ new_lobits ) & -(uint64_t)indicator; - } - - /* - * The product of the 128-bit value now in hibits:lobits with the - * 128-bit value we originally retrieved in the same variables - * will be in the vicinity of 2^191. So we'll take log2(R) to be - * 191, plus a multiple of BIGNUM_INT_BITS large enough to allow R - * to hold the combined sizes of n and d. - */ - size_t log2_R; - { - size_t max_log2_n = (n->nw + d->nw) * BIGNUM_INT_BITS; - log2_R = max_log2_n + 3; - log2_R -= size_t_min(191, log2_R); - log2_R = (log2_R + BIGNUM_INT_BITS - 1) & ~(BIGNUM_INT_BITS - 1); - log2_R += 191; - } - - /* Number of words in a bignum capable of holding numbers the size - * of twice R. */ - size_t rw = ((log2_R+2) + BIGNUM_INT_BITS - 1) / BIGNUM_INT_BITS; - - /* - * Now construct our full-sized starting reciprocal approximation. - */ - mp_int *r_approx = mp_make_sized(rw); - size_t output_bit_index; - { - /* Where in the input number did the input 128-bit value come from? */ - size_t input_bit_index = - (hiword_index * BIGNUM_INT_BITS) - (128 - BIGNUM_INT_BITS); - - /* So how far do we need to shift our 64-bit output, if the - * product of those two fixed-size values is 2^191 and we want - * to make it 2^log2_R instead? */ - output_bit_index = log2_R - 191 - input_bit_index; - - /* If we've done all that right, it should be a whole number - * of words. */ - assert(output_bit_index % BIGNUM_INT_BITS == 0); - size_t output_word_index = output_bit_index / BIGNUM_INT_BITS; - - mp_add_integer_into_shifted_by_words( - r_approx, r_approx, lobits, output_word_index); - mp_add_integer_into_shifted_by_words( - r_approx, r_approx, hibits, - output_word_index + 64 / BIGNUM_INT_BITS); - } - - /* - * Make the constant 2*R, which we'll need in the iteration. - */ - mp_int *two_R = mp_make_sized(rw); - BignumInt top_word = (BignumInt)1 << ((log2_R+1) % BIGNUM_INT_BITS); - mp_add_integer_into_shifted_by_words( - two_R, two_R, top_word, (log2_R+1) / BIGNUM_INT_BITS); - - /* - * Scratch space. - */ - mp_int *dr = mp_make_sized(rw + d->nw); - mp_int *diff = mp_make_sized(size_t_max(rw, dr->nw)); - mp_int *product = mp_make_sized(rw + diff->nw); - size_t scratchsize = size_t_max( - mp_mul_scratchspace(dr->nw, r_approx->nw, d->nw), - mp_mul_scratchspace(product->nw, r_approx->nw, diff->nw)); - mp_int *scratch = mp_make_sized(scratchsize); - mp_int product_shifted = mp_make_alias( - product, log2_R / BIGNUM_INT_BITS, product->nw); - - /* - * Initial error estimate: the 32-bit output of recip_approx_32 - * differs by less than 2048 (== 2^11) from the true top 32 bits - * of the reciprocal, so the relative error is at most 2^11 - * divided by the 32-bit reciprocal, which at worst is 2^11/2^31 = - * 2^-20. So even in the worst case, we have 20 good bits of - * reciprocal to start with. - */ - size_t good_bits = 31 - 11; - size_t good_bits_needed = BIGNUM_INT_BITS * n->nw + 4; /* add a few */ - - /* - * Now do Newton-Raphson iterations until we have reason to think - * they're not converging any more. - */ - while (good_bits < good_bits_needed) { - /* - * Compute the next iterate. - */ - mp_mul_internal(dr, r_approx, d, *scratch); - mp_sub_into(diff, two_R, dr); - mp_mul_internal(product, r_approx, diff, *scratch); - mp_rshift_fixed_into(r_approx, &product_shifted, - log2_R % BIGNUM_INT_BITS); - - /* - * Adjust the error estimate. - */ - good_bits = good_bits * 2 - 1; - } - - mp_free(dr); - mp_free(diff); - mp_free(product); - mp_free(scratch); - - /* - * Now we've got our reciprocal, we can compute the quotient, by - * multiplying in n and then shifting down by log2_R bits. - */ - mp_int *quotient_full = mp_mul(r_approx, n); - mp_int quotient_alias = mp_make_alias( - quotient_full, log2_R / BIGNUM_INT_BITS, quotient_full->nw); - mp_int *quotient = mp_make_sized(n->nw); - mp_rshift_fixed_into(quotient, "ient_alias, log2_R % BIGNUM_INT_BITS); - - /* - * Next, compute the remainder. - */ - mp_int *remainder = mp_make_sized(d->nw); - mp_mul_into(remainder, quotient, d); - mp_sub_into(remainder, n, remainder); - - /* - * Finally, two conditional subtractions to fix up any remaining - * rounding error. (I _think_ one should be enough, but this - * routine isn't time-critical enough to take chances.) - */ - unsigned q_correction = 0; - for (unsigned iter = 0; iter < 2; iter++) { - unsigned need_correction = mp_cmp_hs(remainder, d); - mp_cond_sub_into(remainder, remainder, d, need_correction); - q_correction += need_correction; - } - mp_add_integer_into(quotient, quotient, q_correction); - - /* - * Now we should have a perfect answer, i.e. 0 <= r < d. - */ - assert(!mp_cmp_hs(remainder, d)); - - if (q_out) - mp_copy_into(q_out, quotient); - if (r_out) - mp_copy_into(r_out, remainder); - - mp_free(r_approx); - mp_free(two_R); - mp_free(quotient_full); - mp_free(quotient); - mp_free(remainder); -} - -mp_int *mp_div(mp_int *n, mp_int *d) -{ - mp_int *q = mp_make_sized(n->nw); - mp_divmod_into(n, d, q, NULL); - return q; -} - -mp_int *mp_mod(mp_int *n, mp_int *d) -{ - mp_int *r = mp_make_sized(d->nw); - mp_divmod_into(n, d, NULL, r); - return r; -} - -mp_int *mp_nthroot(mp_int *y, unsigned n, mp_int *remainder_out) -{ - /* - * Allocate scratch space. - */ - mp_int **alloc, **powers, **newpowers, *scratch; - size_t nalloc = 2*(n+1)+1; - alloc = snewn(nalloc, mp_int *); - for (size_t i = 0; i < nalloc; i++) - alloc[i] = mp_make_sized(y->nw + 1); - powers = alloc; - newpowers = alloc + (n+1); - scratch = alloc[2*n+2]; - - /* - * We're computing the rounded-down nth root of y, i.e. the - * maximal x such that x^n <= y. We try to add 2^i to it for each - * possible value of i, starting from the largest one that might - * fit (i.e. such that 2^{n*i} fits in the size of y) downwards to - * i=0. - * - * We track all the smaller powers of x in the array 'powers'. In - * each iteration, if we update x, we update all of those values - * to match. - */ - mp_copy_integer_into(powers[0], 1); - for (size_t s = mp_max_bits(y) / n + 1; s-- > 0 ;) { - /* - * Let b = 2^s. We need to compute the powers (x+b)^i for each - * i, starting from our recorded values of x^i. - */ - for (size_t i = 0; i < n+1; i++) { - /* - * (x+b)^i = x^i - * + (i choose 1) x^{i-1} b - * + (i choose 2) x^{i-2} b^2 - * + ... - * + b^i - */ - uint16_t binom = 1; /* coefficient of b^i */ - mp_copy_into(newpowers[i], powers[i]); - for (size_t j = 0; j < i; j++) { - /* newpowers[i] += binom * powers[j] * 2^{(i-j)*s} */ - mp_mul_integer_into(scratch, powers[j], binom); - mp_lshift_fixed_into(scratch, scratch, (i-j) * s); - mp_add_into(newpowers[i], newpowers[i], scratch); - - uint32_t binom_mul = binom; - binom_mul *= (i-j); - binom_mul /= (j+1); - assert(binom_mul < 0x10000); - binom = binom_mul; - } - } - - /* - * Now, is the new value of x^n still <= y? If so, update. - */ - unsigned newbit = mp_cmp_hs(y, newpowers[n]); - for (size_t i = 0; i < n+1; i++) - mp_select_into(powers[i], powers[i], newpowers[i], newbit); - } - - if (remainder_out) - mp_sub_into(remainder_out, y, powers[n]); - - mp_int *root = mp_new(mp_max_bits(y) / n); - mp_copy_into(root, powers[1]); - - for (size_t i = 0; i < nalloc; i++) - mp_free(alloc[i]); - sfree(alloc); - - return root; -} - -mp_int *mp_modmul(mp_int *x, mp_int *y, mp_int *modulus) -{ - mp_int *product = mp_mul(x, y); - mp_int *reduced = mp_mod(product, modulus); - mp_free(product); - return reduced; -} - -mp_int *mp_modadd(mp_int *x, mp_int *y, mp_int *modulus) -{ - mp_int *sum = mp_add(x, y); - mp_int *reduced = mp_mod(sum, modulus); - mp_free(sum); - return reduced; -} - -mp_int *mp_modsub(mp_int *x, mp_int *y, mp_int *modulus) -{ - mp_int *diff = mp_make_sized(size_t_max(x->nw, y->nw)); - mp_sub_into(diff, x, y); - unsigned negate = mp_cmp_hs(y, x); - mp_cond_negate(diff, diff, negate); - mp_int *residue = mp_mod(diff, modulus); - mp_cond_negate(residue, residue, negate); - /* If we've just negated the residue, then it will be < 0 and need - * the modulus adding to it to make it positive - *except* if the - * residue was zero when we negated it. */ - unsigned make_positive = negate & ~mp_eq_integer(residue, 0); - mp_cond_add_into(residue, residue, modulus, make_positive); - mp_free(diff); - return residue; -} - -static mp_int *mp_modadd_in_range(mp_int *x, mp_int *y, mp_int *modulus) -{ - mp_int *sum = mp_make_sized(modulus->nw); - unsigned carry = mp_add_into_internal(sum, x, y); - mp_cond_sub_into(sum, sum, modulus, carry | mp_cmp_hs(sum, modulus)); - return sum; -} - -static mp_int *mp_modsub_in_range(mp_int *x, mp_int *y, mp_int *modulus) -{ - mp_int *diff = mp_make_sized(modulus->nw); - mp_sub_into(diff, x, y); - mp_cond_add_into(diff, diff, modulus, 1 ^ mp_cmp_hs(x, y)); - return diff; -} - -mp_int *monty_add(MontyContext *mc, mp_int *x, mp_int *y) -{ - return mp_modadd_in_range(x, y, mc->m); -} - -mp_int *monty_sub(MontyContext *mc, mp_int *x, mp_int *y) -{ - return mp_modsub_in_range(x, y, mc->m); -} - -void mp_min_into(mp_int *r, mp_int *x, mp_int *y) -{ - mp_select_into(r, x, y, mp_cmp_hs(x, y)); -} - -void mp_max_into(mp_int *r, mp_int *x, mp_int *y) -{ - mp_select_into(r, y, x, mp_cmp_hs(x, y)); -} - -mp_int *mp_min(mp_int *x, mp_int *y) -{ - mp_int *r = mp_make_sized(size_t_min(x->nw, y->nw)); - mp_min_into(r, x, y); - return r; -} - -mp_int *mp_max(mp_int *x, mp_int *y) -{ - mp_int *r = mp_make_sized(size_t_max(x->nw, y->nw)); - mp_max_into(r, x, y); - return r; -} - -mp_int *mp_power_2(size_t power) -{ - mp_int *x = mp_new(power + 1); - mp_set_bit(x, power, 1); - return x; -} - -struct ModsqrtContext { - mp_int *p; /* the prime */ - MontyContext *mc; /* for doing arithmetic mod p */ - - /* Decompose p-1 as 2^e k, for positive integer e and odd k */ - size_t e; - mp_int *k; - mp_int *km1o2; /* (k-1)/2 */ - - /* The user-provided value z which is not a quadratic residue mod - * p, and its kth power. Both in Montgomery form. */ - mp_int *z, *zk; -}; - -ModsqrtContext *modsqrt_new(mp_int *p, mp_int *any_nonsquare_mod_p) -{ - ModsqrtContext *sc = snew(ModsqrtContext); - memset(sc, 0, sizeof(ModsqrtContext)); - - sc->p = mp_copy(p); - sc->mc = monty_new(sc->p); - sc->z = monty_import(sc->mc, any_nonsquare_mod_p); - - /* Find the lowest set bit in p-1. Since this routine expects p to - * be non-secret (typically a well-known standard elliptic curve - * parameter), for once we don't need clever bit tricks. */ - for (sc->e = 1; sc->e < BIGNUM_INT_BITS * p->nw; sc->e++) - if (mp_get_bit(p, sc->e)) - break; - - sc->k = mp_rshift_fixed(p, sc->e); - sc->km1o2 = mp_rshift_fixed(sc->k, 1); - - /* Leave zk to be filled in lazily, since it's more expensive to - * compute. If this context turns out never to be needed, we can - * save the bulk of the setup time this way. */ - - return sc; -} - -static void modsqrt_lazy_setup(ModsqrtContext *sc) -{ - if (!sc->zk) - sc->zk = monty_pow(sc->mc, sc->z, sc->k); -} - -void modsqrt_free(ModsqrtContext *sc) -{ - monty_free(sc->mc); - mp_free(sc->p); - mp_free(sc->z); - mp_free(sc->k); - mp_free(sc->km1o2); - - if (sc->zk) - mp_free(sc->zk); - - sfree(sc); -} - -mp_int *mp_modsqrt(ModsqrtContext *sc, mp_int *x, unsigned *success) -{ - mp_int *mx = monty_import(sc->mc, x); - mp_int *mroot = monty_modsqrt(sc, mx, success); - mp_free(mx); - mp_int *root = monty_export(sc->mc, mroot); - mp_free(mroot); - return root; -} - -/* - * Modular square root, using an algorithm more or less similar to - * Tonelli-Shanks but adapted for constant time. - * - * The basic idea is to write p-1 = k 2^e, where k is odd and e > 0. - * Then the multiplicative group mod p (call it G) has a sequence of - * e+1 nested subgroups G = G_0 > G_1 > G_2 > ... > G_e, where each - * G_i is exactly half the size of G_{i-1} and consists of all the - * squares of elements in G_{i-1}. So the innermost group G_e has - * order k, which is odd, and hence within that group you can take a - * square root by raising to the power (k+1)/2. - * - * Our strategy is to iterate over these groups one by one and make - * sure the number x we're trying to take the square root of is inside - * each one, by adjusting it if it isn't. - * - * Suppose g is a primitive root of p, i.e. a generator of G_0. (We - * don't actually need to know what g _is_; we just imagine it for the - * sake of understanding.) Then G_i consists of precisely the (2^i)th - * powers of g, and hence, you can tell if a number is in G_i if - * raising it to the power k 2^{e-i} gives 1. So the conceptual - * algorithm goes: for each i, test whether x is in G_i by that - * method. If it isn't, then the previous iteration ensured it's in - * G_{i-1}, so it will be an odd power of g^{2^{i-1}}, and hence - * multiplying by any other odd power of g^{2^{i-1}} will give x' in - * G_i. And we have one of those, because our non-square z is an odd - * power of g, so z^{2^{i-1}} is an odd power of g^{2^{i-1}}. - * - * (There's a special case in the very first iteration, where we don't - * have a G_{i-1}. If it turns out that x is not even in G_1, that - * means it's not a square, so we set *success to 0. We still run the - * rest of the algorithm anyway, for the sake of constant time, but we - * don't give a hoot what it returns.) - * - * When we get to the end and have x in G_e, then we can take its - * square root by raising to (k+1)/2. But of course that's not the - * square root of the original input - it's only the square root of - * the adjusted version we produced during the algorithm. To get the - * true output answer we also have to multiply by a power of z, - * namely, z to the power of _half_ whatever we've been multiplying in - * as we go along. (The power of z we multiplied in must have been - * even, because the case in which we would have multiplied in an odd - * power of z is the i=0 case, in which we instead set the failure - * flag.) - * - * The code below is an optimised version of that basic idea, in which - * we _start_ by computing x^k so as to be able to test membership in - * G_i by only a few squarings rather than a full from-scratch modpow - * every time; we also start by computing our candidate output value - * x^{(k+1)/2}. So when the above description says 'adjust x by z^i' - * for some i, we have to adjust our running values of x^k and - * x^{(k+1)/2} by z^{ik} and z^{ik/2} respectively (the latter is safe - * because, as above, i is always even). And it turns out that we - * don't actually have to store the adjusted version of x itself at - * all - we _only_ keep those two powers of it. - */ -mp_int *monty_modsqrt(ModsqrtContext *sc, mp_int *x, unsigned *success) -{ - modsqrt_lazy_setup(sc); - - mp_int *scratch_to_free = mp_make_sized(3 * sc->mc->rw); - mp_int scratch = *scratch_to_free; - - /* - * Compute toret = x^{(k+1)/2}, our starting point for the output - * square root, and also xk = x^k which we'll use as we go along - * for knowing when to apply correction factors. We do this by - * first computing x^{(k-1)/2}, then multiplying it by x, then - * multiplying the two together. - */ - mp_int *toret = monty_pow(sc->mc, x, sc->km1o2); - mp_int xk = mp_alloc_from_scratch(&scratch, sc->mc->rw); - mp_copy_into(&xk, toret); - monty_mul_into(sc->mc, toret, toret, x); - monty_mul_into(sc->mc, &xk, toret, &xk); - - mp_int tmp = mp_alloc_from_scratch(&scratch, sc->mc->rw); - - mp_int power_of_zk = mp_alloc_from_scratch(&scratch, sc->mc->rw); - mp_copy_into(&power_of_zk, sc->zk); - - for (size_t i = 0; i < sc->e; i++) { - mp_copy_into(&tmp, &xk); - for (size_t j = i+1; j < sc->e; j++) - monty_mul_into(sc->mc, &tmp, &tmp, &tmp); - unsigned eq1 = mp_cmp_eq(&tmp, monty_identity(sc->mc)); - - if (i == 0) { - /* One special case: if x=0, then no power of x will ever - * equal 1, but we should still report success on the - * grounds that 0 does have a square root mod p. */ - *success = eq1 | mp_eq_integer(x, 0); - } else { - monty_mul_into(sc->mc, &tmp, toret, &power_of_zk); - mp_select_into(toret, &tmp, toret, eq1); - - monty_mul_into(sc->mc, &power_of_zk, - &power_of_zk, &power_of_zk); - - monty_mul_into(sc->mc, &tmp, &xk, &power_of_zk); - mp_select_into(&xk, &tmp, &xk, eq1); - } - } - - mp_free(scratch_to_free); - - return toret; -} - -mp_int *mp_random_bits_fn(size_t bits, random_read_fn_t random_read) -{ - size_t bytes = (bits + 7) / 8; - uint8_t *randbuf = snewn(bytes, uint8_t); - random_read(randbuf, bytes); - if (bytes) - randbuf[0] &= (2 << ((bits-1) & 7)) - 1; - mp_int *toret = mp_from_bytes_be(make_ptrlen(randbuf, bytes)); - smemclr(randbuf, bytes); - sfree(randbuf); - return toret; -} - -mp_int *mp_random_upto_fn(mp_int *limit, random_read_fn_t rf) -{ - /* - * It would be nice to generate our random numbers in such a way - * as to make every possible outcome literally equiprobable. But - * we can't do that in constant time, so we have to go for a very - * close approximation instead. I'm going to take the view that a - * factor of (1+2^-128) between the probabilities of two outcomes - * is acceptable on the grounds that you'd have to examine so many - * outputs to even detect it. - */ - mp_int *unreduced = mp_random_bits_fn(mp_max_bits(limit) + 128, rf); - mp_int *reduced = mp_mod(unreduced, limit); - mp_free(unreduced); - return reduced; -} - -mp_int *mp_random_in_range_fn(mp_int *lo, mp_int *hi, random_read_fn_t rf) -{ - mp_int *n_outcomes = mp_sub(hi, lo); - mp_int *addend = mp_random_upto_fn(n_outcomes, rf); - mp_int *result = mp_make_sized(hi->nw); - mp_add_into(result, addend, lo); - mp_free(addend); - mp_free(n_outcomes); - return result; -} diff --git a/sshaes.c b/sshaes.c deleted file mode 100644 index 97ea9f31..00000000 --- a/sshaes.c +++ /dev/null @@ -1,1912 +0,0 @@ -/* - * sshaes.c - implementation of AES - */ - -#include -#include - -#include "ssh.h" -#include "mpint_i.h" /* we reuse the BignumInt system */ - -/* - * Start by deciding whether we can support hardware AES at all. - */ -#define HW_AES_NONE 0 -#define HW_AES_NI 1 -#define HW_AES_NEON 2 - -#ifdef _FORCE_AES_NI -# define HW_AES HW_AES_NI -#elif defined(__clang__) -# if __has_attribute(target) && __has_include() && \ - (defined(__x86_64__) || defined(__i386)) -# define HW_AES HW_AES_NI -# endif -#elif defined(__GNUC__) -# if (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 4)) && \ - (defined(__x86_64__) || defined(__i386)) -# define HW_AES HW_AES_NI -# endif -#elif defined (_MSC_VER) -# if (defined(_M_X64) || defined(_M_IX86)) && _MSC_FULL_VER >= 150030729 -# define HW_AES HW_AES_NI -# endif -#endif - -#ifdef _FORCE_AES_NEON -# define HW_AES HW_AES_NEON -#elif defined __BYTE_ORDER__ && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ - /* Arm can potentially support both endiannesses, but this code - * hasn't been tested on anything but little. If anyone wants to - * run big-endian, they'll need to fix it first. */ -#elif defined __ARM_FEATURE_CRYPTO - /* If the Arm crypto extension is available already, we can - * support NEON AES without having to enable anything by hand */ -# define HW_AES HW_AES_NEON -#elif defined(__clang__) -# if __has_attribute(target) && __has_include() && \ - (defined(__aarch64__)) - /* clang can enable the crypto extension in AArch64 using - * __attribute__((target)) */ -# define HW_AES HW_AES_NEON -# define USE_CLANG_ATTR_TARGET_AARCH64 -# endif -#elif defined _MSC_VER -# if defined _M_ARM64 -# define HW_AES HW_AES_NEON - /* 64-bit Visual Studio uses the header in place - * of the standard */ -# define USE_ARM64_NEON_H -# elif defined _M_ARM -# define HW_AES HW_AES_NEON - /* 32-bit Visual Studio uses the right header name, but requires - * this #define to enable a set of intrinsic definitions that - * do not omit one of the parameters for vaes[ed]q_u8 */ -# define _ARM_USE_NEW_NEON_INTRINSICS -# endif -#endif - -#if defined _FORCE_SOFTWARE_AES || !defined HW_AES -# undef HW_AES -# define HW_AES HW_AES_NONE -#endif - -#if HW_AES == HW_AES_NI -#define HW_NAME_SUFFIX " (AES-NI accelerated)" -#elif HW_AES == HW_AES_NEON -#define HW_NAME_SUFFIX " (NEON accelerated)" -#else -#define HW_NAME_SUFFIX " (!NONEXISTENT ACCELERATED VERSION!)" -#endif - -/* - * Vtable collection for AES. For each SSH-level cipher id (i.e. - * combination of key length and cipher mode), we provide three - * vtables: one for the pure software implementation, one using - * hardware acceleration (if available), and a top-level one which is - * never actually instantiated, and only contains a new() method whose - * job is to decide which of the other two to return an actual - * instance of. - */ - -static ssh_cipher *aes_select(const ssh_cipheralg *alg); -static ssh_cipher *aes_sw_new(const ssh_cipheralg *alg); -static void aes_sw_free(ssh_cipher *); -static void aes_sw_setiv_cbc(ssh_cipher *, const void *iv); -static void aes_sw_setiv_sdctr(ssh_cipher *, const void *iv); -static void aes_sw_setkey(ssh_cipher *, const void *key); -static ssh_cipher *aes_hw_new(const ssh_cipheralg *alg); -static void aes_hw_free(ssh_cipher *); -static void aes_hw_setiv_cbc(ssh_cipher *, const void *iv); -static void aes_hw_setiv_sdctr(ssh_cipher *, const void *iv); -static void aes_hw_setkey(ssh_cipher *, const void *key); - -struct aes_extra { - const ssh_cipheralg *sw, *hw; -}; - -#define VTABLES_INNER(cid, pid, bits, name, encsuffix, \ - decsuffix, setivsuffix, flagsval) \ - static void cid##_sw##encsuffix(ssh_cipher *, void *blk, int len); \ - static void cid##_sw##decsuffix(ssh_cipher *, void *blk, int len); \ - const ssh_cipheralg ssh_##cid##_sw = { \ - .new = aes_sw_new, \ - .free = aes_sw_free, \ - .setiv = aes_sw_##setivsuffix, \ - .setkey = aes_sw_setkey, \ - .encrypt = cid##_sw##encsuffix, \ - .decrypt = cid##_sw##decsuffix, \ - .ssh2_id = pid, \ - .blksize = 16, \ - .real_keybits = bits, \ - .padded_keybytes = bits/8, \ - .flags = flagsval, \ - .text_name = name " (unaccelerated)", \ - }; \ - \ - static void cid##_hw##encsuffix(ssh_cipher *, void *blk, int len); \ - static void cid##_hw##decsuffix(ssh_cipher *, void *blk, int len); \ - const ssh_cipheralg ssh_##cid##_hw = { \ - .new = aes_hw_new, \ - .free = aes_hw_free, \ - .setiv = aes_hw_##setivsuffix, \ - .setkey = aes_hw_setkey, \ - .encrypt = cid##_hw##encsuffix, \ - .decrypt = cid##_hw##decsuffix, \ - .ssh2_id = pid, \ - .blksize = 16, \ - .real_keybits = bits, \ - .padded_keybytes = bits/8, \ - .flags = flagsval, \ - .text_name = name HW_NAME_SUFFIX, \ - }; \ - \ - static const struct aes_extra extra_##cid = { \ - &ssh_##cid##_sw, &ssh_##cid##_hw }; \ - \ - const ssh_cipheralg ssh_##cid = { \ - .new = aes_select, \ - .ssh2_id = pid, \ - .blksize = 16, \ - .real_keybits = bits, \ - .padded_keybytes = bits/8, \ - .flags = flagsval, \ - .text_name = name " (dummy selector vtable)", \ - .extra = &extra_##cid \ - }; \ - -#define VTABLES(keylen) \ - VTABLES_INNER(aes ## keylen ## _cbc, "aes" #keylen "-cbc", \ - keylen, "AES-" #keylen " CBC", _encrypt, _decrypt, \ - setiv_cbc, SSH_CIPHER_IS_CBC) \ - VTABLES_INNER(aes ## keylen ## _sdctr, "aes" #keylen "-ctr", \ - keylen, "AES-" #keylen " SDCTR",,, setiv_sdctr, 0) - -VTABLES(128) -VTABLES(192) -VTABLES(256) - -static const ssh_cipheralg ssh_rijndael_lysator = { - /* Same as aes256_cbc, but with a different protocol ID */ - .new = aes_select, - .ssh2_id = "rijndael-cbc@lysator.liu.se", - .blksize = 16, - .real_keybits = 256, - .padded_keybytes = 256/8, - .flags = 0, - .text_name = "AES-256 CBC (dummy selector vtable)", - .extra = &extra_aes256_cbc, -}; - -static const ssh_cipheralg *const aes_list[] = { - &ssh_aes256_sdctr, - &ssh_aes256_cbc, - &ssh_rijndael_lysator, - &ssh_aes192_sdctr, - &ssh_aes192_cbc, - &ssh_aes128_sdctr, - &ssh_aes128_cbc, -}; - -const ssh2_ciphers ssh2_aes = { lenof(aes_list), aes_list }; - -/* - * The actual query function that asks if hardware acceleration is - * available. - */ -static bool aes_hw_available(void); - -/* - * The top-level selection function, caching the results of - * aes_hw_available() so it only has to run once. - */ -static bool aes_hw_available_cached(void) -{ - static bool initialised = false; - static bool hw_available; - if (!initialised) { - hw_available = aes_hw_available(); - initialised = true; - } - return hw_available; -} - -static ssh_cipher *aes_select(const ssh_cipheralg *alg) -{ - const struct aes_extra *extra = (const struct aes_extra *)alg->extra; - const ssh_cipheralg *real_alg = - aes_hw_available_cached() ? extra->hw : extra->sw; - - return ssh_cipher_new(real_alg); -} - -/* ---------------------------------------------------------------------- - * Definitions likely to be helpful to multiple implementations. - */ - -#define REP2(x) x x -#define REP4(x) REP2(REP2(x)) -#define REP8(x) REP2(REP4(x)) -#define REP9(x) REP8(x) x -#define REP11(x) REP8(x) REP2(x) x -#define REP13(x) REP8(x) REP4(x) x - -static const uint8_t key_setup_round_constants[] = { - /* The first few powers of X in GF(2^8), used during key setup. - * This can safely be a lookup table without side channel risks, - * because key setup iterates through it once in a standard way - * regardless of the key. */ - 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, -}; - -#define MAXROUNDKEYS 15 - -/* ---------------------------------------------------------------------- - * Software implementation of AES. - * - * This implementation uses a bit-sliced representation. Instead of - * the obvious approach of storing the cipher state so that each byte - * (or field element, or entry in the cipher matrix) occupies 8 - * contiguous bits in a machine integer somewhere, we organise the - * cipher state as an array of 8 integers, in such a way that each - * logical byte of the cipher state occupies one bit in each integer, - * all at the same position. This allows us to do parallel logic on - * all bytes of the state by doing bitwise operations between the 8 - * integers; in particular, the S-box (SubBytes) lookup is done this - * way, which takes about 110 operations - but for those 110 bitwise - * ops you get 64 S-box lookups, not just one. - */ - -#define SLICE_PARALLELISM (BIGNUM_INT_BYTES / 2) - -#ifdef BITSLICED_DEBUG -/* Dump function that undoes the bitslicing transform, so you can see - * the logical data represented by a set of slice words. */ -static inline void dumpslices_uint16_t( - const char *prefix, const uint16_t slices[8]) -{ - printf("%-30s", prefix); - for (unsigned byte = 0; byte < 16; byte++) { - unsigned byteval = 0; - for (unsigned bit = 0; bit < 8; bit++) - byteval |= (1 & (slices[bit] >> byte)) << bit; - printf("%02x", byteval); - } - printf("\n"); -} - -static inline void dumpslices_BignumInt( - const char *prefix, const BignumInt slices[8]) -{ - printf("%-30s", prefix); - for (unsigned iter = 0; iter < SLICE_PARALLELISM; iter++) { - for (unsigned byte = 0; byte < 16; byte++) { - unsigned byteval = 0; - for (unsigned bit = 0; bit < 8; bit++) - byteval |= (1 & (slices[bit] >> (iter*16+byte))) << bit; - printf("%02x", byteval); - } - if (iter+1 < SLICE_PARALLELISM) - printf(" "); - } - printf("\n"); -} -#else -#define dumpslices_uintN_t(prefix, slices) ((void)0) -#define dumpslices_BignumInt(prefix, slices) ((void)0) -#endif - -/* ----- - * Bit-slicing transformation: convert between an array of 16 uint8_t - * and an array of 8 uint16_t, so as to interchange the bit index - * within each element and the element index within the array. (That - * is, bit j of input[i] == bit i of output[j]. - */ - -#define SWAPWORDS(shift) do \ - { \ - uint64_t mask = ~(uint64_t)0 / ((1ULL << shift) + 1); \ - uint64_t diff = ((i0 >> shift) ^ i1) & mask; \ - i0 ^= diff << shift; \ - i1 ^= diff; \ - } while (0) - -#define SWAPINWORD(i, bigshift, smallshift) do \ - { \ - uint64_t mask = ~(uint64_t)0; \ - mask /= ((1ULL << bigshift) + 1); \ - mask /= ((1ULL << smallshift) + 1); \ - mask <<= smallshift; \ - unsigned shift = bigshift - smallshift; \ - uint64_t diff = ((i >> shift) ^ i) & mask; \ - i ^= diff ^ (diff << shift); \ - } while (0) - -#define TO_BITSLICES(slices, bytes, uintN_t, assign_op, shift) do \ - { \ - uint64_t i0 = GET_64BIT_LSB_FIRST(bytes); \ - uint64_t i1 = GET_64BIT_LSB_FIRST(bytes + 8); \ - SWAPINWORD(i0, 8, 1); \ - SWAPINWORD(i1, 8, 1); \ - SWAPINWORD(i0, 16, 2); \ - SWAPINWORD(i1, 16, 2); \ - SWAPINWORD(i0, 32, 4); \ - SWAPINWORD(i1, 32, 4); \ - SWAPWORDS(8); \ - slices[0] assign_op (uintN_t)((i0 >> 0) & 0xFFFF) << (shift); \ - slices[2] assign_op (uintN_t)((i0 >> 16) & 0xFFFF) << (shift); \ - slices[4] assign_op (uintN_t)((i0 >> 32) & 0xFFFF) << (shift); \ - slices[6] assign_op (uintN_t)((i0 >> 48) & 0xFFFF) << (shift); \ - slices[1] assign_op (uintN_t)((i1 >> 0) & 0xFFFF) << (shift); \ - slices[3] assign_op (uintN_t)((i1 >> 16) & 0xFFFF) << (shift); \ - slices[5] assign_op (uintN_t)((i1 >> 32) & 0xFFFF) << (shift); \ - slices[7] assign_op (uintN_t)((i1 >> 48) & 0xFFFF) << (shift); \ - } while (0) - -#define FROM_BITSLICES(bytes, slices, shift) do \ - { \ - uint64_t i1 = ((slices[7] >> (shift)) & 0xFFFF); \ - i1 = (i1 << 16) | ((slices[5] >> (shift)) & 0xFFFF); \ - i1 = (i1 << 16) | ((slices[3] >> (shift)) & 0xFFFF); \ - i1 = (i1 << 16) | ((slices[1] >> (shift)) & 0xFFFF); \ - uint64_t i0 = ((slices[6] >> (shift)) & 0xFFFF); \ - i0 = (i0 << 16) | ((slices[4] >> (shift)) & 0xFFFF); \ - i0 = (i0 << 16) | ((slices[2] >> (shift)) & 0xFFFF); \ - i0 = (i0 << 16) | ((slices[0] >> (shift)) & 0xFFFF); \ - SWAPWORDS(8); \ - SWAPINWORD(i0, 32, 4); \ - SWAPINWORD(i1, 32, 4); \ - SWAPINWORD(i0, 16, 2); \ - SWAPINWORD(i1, 16, 2); \ - SWAPINWORD(i0, 8, 1); \ - SWAPINWORD(i1, 8, 1); \ - PUT_64BIT_LSB_FIRST(bytes, i0); \ - PUT_64BIT_LSB_FIRST((bytes) + 8, i1); \ - } while (0) - -/* ----- - * Some macros that will be useful repeatedly. - */ - -/* Iterate a unary transformation over all 8 slices. */ -#define ITERATE(MACRO, output, input, uintN_t) do \ - { \ - MACRO(output[0], input[0], uintN_t); \ - MACRO(output[1], input[1], uintN_t); \ - MACRO(output[2], input[2], uintN_t); \ - MACRO(output[3], input[3], uintN_t); \ - MACRO(output[4], input[4], uintN_t); \ - MACRO(output[5], input[5], uintN_t); \ - MACRO(output[6], input[6], uintN_t); \ - MACRO(output[7], input[7], uintN_t); \ - } while (0) - -/* Simply add (i.e. XOR) two whole sets of slices together. */ -#define BITSLICED_ADD(output, lhs, rhs) do \ - { \ - output[0] = lhs[0] ^ rhs[0]; \ - output[1] = lhs[1] ^ rhs[1]; \ - output[2] = lhs[2] ^ rhs[2]; \ - output[3] = lhs[3] ^ rhs[3]; \ - output[4] = lhs[4] ^ rhs[4]; \ - output[5] = lhs[5] ^ rhs[5]; \ - output[6] = lhs[6] ^ rhs[6]; \ - output[7] = lhs[7] ^ rhs[7]; \ - } while (0) - -/* ----- - * The AES S-box, in pure bitwise logic so that it can be run in - * parallel on whole words full of bit-sliced field elements. - * - * Source: 'A new combinational logic minimization technique with - * applications to cryptology', https://eprint.iacr.org/2009/191 - * - * As a minor speed optimisation, I use a modified version of the - * S-box which omits the additive constant 0x63, i.e. this S-box - * consists of only the field inversion and linear map components. - * Instead, the addition of the constant is deferred until after the - * subsequent ShiftRows and MixColumns stages, so that it happens at - * the same time as adding the next round key - and then we just make - * it _part_ of the round key, so it doesn't cost any extra - * instructions to add. - * - * (Obviously adding a constant to each byte commutes with ShiftRows, - * which only permutes the bytes. It also commutes with MixColumns: - * that's not quite so obvious, but since the effect of MixColumns is - * to multiply a constant polynomial M into each column, it is obvious - * that adding some polynomial K and then multiplying by M is - * equivalent to multiplying by M and then adding the product KM. And - * in fact, since the coefficients of M happen to sum to 1, it turns - * out that KM = K, so we don't even have to change the constant when - * we move it to the far side of MixColumns.) - * - * Of course, one knock-on effect of this is that the use of the S-box - * *during* key setup has to be corrected by manually adding on the - * constant afterwards! - */ - -/* Initial linear transformation for the forward S-box, from Fig 2 of - * the paper. */ -#define SBOX_FORWARD_TOP_TRANSFORM(input, uintN_t) \ - uintN_t y14 = input[4] ^ input[2]; \ - uintN_t y13 = input[7] ^ input[1]; \ - uintN_t y9 = input[7] ^ input[4]; \ - uintN_t y8 = input[7] ^ input[2]; \ - uintN_t t0 = input[6] ^ input[5]; \ - uintN_t y1 = t0 ^ input[0]; \ - uintN_t y4 = y1 ^ input[4]; \ - uintN_t y12 = y13 ^ y14; \ - uintN_t y2 = y1 ^ input[7]; \ - uintN_t y5 = y1 ^ input[1]; \ - uintN_t y3 = y5 ^ y8; \ - uintN_t t1 = input[3] ^ y12; \ - uintN_t y15 = t1 ^ input[2]; \ - uintN_t y20 = t1 ^ input[6]; \ - uintN_t y6 = y15 ^ input[0]; \ - uintN_t y10 = y15 ^ t0; \ - uintN_t y11 = y20 ^ y9; \ - uintN_t y7 = input[0] ^ y11; \ - uintN_t y17 = y10 ^ y11; \ - uintN_t y19 = y10 ^ y8; \ - uintN_t y16 = t0 ^ y11; \ - uintN_t y21 = y13 ^ y16; \ - uintN_t y18 = input[7] ^ y16; \ - /* Make a copy of input[0] under a new name, because the core - * will refer to it, and in the inverse version of the S-box - * the corresponding value will be one of the calculated ones - * and not in input[0] itself. */ \ - uintN_t i0 = input[0]; \ - /* end */ - -/* Core nonlinear component, from Fig 3 of the paper. */ -#define SBOX_CORE(uintN_t) \ - uintN_t t2 = y12 & y15; \ - uintN_t t3 = y3 & y6; \ - uintN_t t4 = t3 ^ t2; \ - uintN_t t5 = y4 & i0; \ - uintN_t t6 = t5 ^ t2; \ - uintN_t t7 = y13 & y16; \ - uintN_t t8 = y5 & y1; \ - uintN_t t9 = t8 ^ t7; \ - uintN_t t10 = y2 & y7; \ - uintN_t t11 = t10 ^ t7; \ - uintN_t t12 = y9 & y11; \ - uintN_t t13 = y14 & y17; \ - uintN_t t14 = t13 ^ t12; \ - uintN_t t15 = y8 & y10; \ - uintN_t t16 = t15 ^ t12; \ - uintN_t t17 = t4 ^ t14; \ - uintN_t t18 = t6 ^ t16; \ - uintN_t t19 = t9 ^ t14; \ - uintN_t t20 = t11 ^ t16; \ - uintN_t t21 = t17 ^ y20; \ - uintN_t t22 = t18 ^ y19; \ - uintN_t t23 = t19 ^ y21; \ - uintN_t t24 = t20 ^ y18; \ - uintN_t t25 = t21 ^ t22; \ - uintN_t t26 = t21 & t23; \ - uintN_t t27 = t24 ^ t26; \ - uintN_t t28 = t25 & t27; \ - uintN_t t29 = t28 ^ t22; \ - uintN_t t30 = t23 ^ t24; \ - uintN_t t31 = t22 ^ t26; \ - uintN_t t32 = t31 & t30; \ - uintN_t t33 = t32 ^ t24; \ - uintN_t t34 = t23 ^ t33; \ - uintN_t t35 = t27 ^ t33; \ - uintN_t t36 = t24 & t35; \ - uintN_t t37 = t36 ^ t34; \ - uintN_t t38 = t27 ^ t36; \ - uintN_t t39 = t29 & t38; \ - uintN_t t40 = t25 ^ t39; \ - uintN_t t41 = t40 ^ t37; \ - uintN_t t42 = t29 ^ t33; \ - uintN_t t43 = t29 ^ t40; \ - uintN_t t44 = t33 ^ t37; \ - uintN_t t45 = t42 ^ t41; \ - uintN_t z0 = t44 & y15; \ - uintN_t z1 = t37 & y6; \ - uintN_t z2 = t33 & i0; \ - uintN_t z3 = t43 & y16; \ - uintN_t z4 = t40 & y1; \ - uintN_t z5 = t29 & y7; \ - uintN_t z6 = t42 & y11; \ - uintN_t z7 = t45 & y17; \ - uintN_t z8 = t41 & y10; \ - uintN_t z9 = t44 & y12; \ - uintN_t z10 = t37 & y3; \ - uintN_t z11 = t33 & y4; \ - uintN_t z12 = t43 & y13; \ - uintN_t z13 = t40 & y5; \ - uintN_t z14 = t29 & y2; \ - uintN_t z15 = t42 & y9; \ - uintN_t z16 = t45 & y14; \ - uintN_t z17 = t41 & y8; \ - /* end */ - -/* Final linear transformation for the forward S-box, from Fig 4 of - * the paper. */ -#define SBOX_FORWARD_BOTTOM_TRANSFORM(output, uintN_t) \ - uintN_t t46 = z15 ^ z16; \ - uintN_t t47 = z10 ^ z11; \ - uintN_t t48 = z5 ^ z13; \ - uintN_t t49 = z9 ^ z10; \ - uintN_t t50 = z2 ^ z12; \ - uintN_t t51 = z2 ^ z5; \ - uintN_t t52 = z7 ^ z8; \ - uintN_t t53 = z0 ^ z3; \ - uintN_t t54 = z6 ^ z7; \ - uintN_t t55 = z16 ^ z17; \ - uintN_t t56 = z12 ^ t48; \ - uintN_t t57 = t50 ^ t53; \ - uintN_t t58 = z4 ^ t46; \ - uintN_t t59 = z3 ^ t54; \ - uintN_t t60 = t46 ^ t57; \ - uintN_t t61 = z14 ^ t57; \ - uintN_t t62 = t52 ^ t58; \ - uintN_t t63 = t49 ^ t58; \ - uintN_t t64 = z4 ^ t59; \ - uintN_t t65 = t61 ^ t62; \ - uintN_t t66 = z1 ^ t63; \ - output[7] = t59 ^ t63; \ - output[1] = t56 ^ t62; \ - output[0] = t48 ^ t60; \ - uintN_t t67 = t64 ^ t65; \ - output[4] = t53 ^ t66; \ - output[3] = t51 ^ t66; \ - output[2] = t47 ^ t65; \ - output[6] = t64 ^ output[4]; \ - output[5] = t55 ^ t67; \ - /* end */ - -#define BITSLICED_SUBBYTES(output, input, uintN_t) do { \ - SBOX_FORWARD_TOP_TRANSFORM(input, uintN_t); \ - SBOX_CORE(uintN_t); \ - SBOX_FORWARD_BOTTOM_TRANSFORM(output, uintN_t); \ - } while (0) - -/* - * Initial and final linear transformations for the backward S-box. I - * generated these myself, by implementing the linear-transform - * optimisation algorithm in the paper, and applying it to the - * matrices calculated by _their_ top and bottom transformations, pre- - * and post-multiplied as appropriate by the linear map in the inverse - * S_box. - */ -#define SBOX_BACKWARD_TOP_TRANSFORM(input, uintN_t) \ - uintN_t y5 = input[4] ^ input[6]; \ - uintN_t y19 = input[3] ^ input[0]; \ - uintN_t itmp8 = y5 ^ input[0]; \ - uintN_t y4 = itmp8 ^ input[1]; \ - uintN_t y9 = input[4] ^ input[3]; \ - uintN_t y2 = y9 ^ y4; \ - uintN_t itmp9 = y2 ^ input[7]; \ - uintN_t y1 = y9 ^ input[0]; \ - uintN_t y6 = y5 ^ input[7]; \ - uintN_t y18 = y9 ^ input[5]; \ - uintN_t y7 = y18 ^ y2; \ - uintN_t y16 = y7 ^ y1; \ - uintN_t y21 = y7 ^ input[1]; \ - uintN_t y3 = input[4] ^ input[7]; \ - uintN_t y13 = y16 ^ y21; \ - uintN_t y8 = input[4] ^ y6; \ - uintN_t y10 = y8 ^ y19; \ - uintN_t y14 = y8 ^ y9; \ - uintN_t y20 = itmp9 ^ input[2]; \ - uintN_t y11 = y9 ^ y20; \ - uintN_t i0 = y11 ^ y7; \ - uintN_t y15 = i0 ^ y6; \ - uintN_t y17 = y16 ^ y15; \ - uintN_t y12 = itmp9 ^ input[3]; \ - /* end */ -#define SBOX_BACKWARD_BOTTOM_TRANSFORM(output, uintN_t) \ - uintN_t otmp18 = z15 ^ z6; \ - uintN_t otmp19 = z13 ^ otmp18; \ - uintN_t otmp20 = z12 ^ otmp19; \ - uintN_t otmp21 = z16 ^ otmp20; \ - uintN_t otmp22 = z8 ^ otmp21; \ - uintN_t otmp23 = z0 ^ otmp22; \ - uintN_t otmp24 = otmp22 ^ z3; \ - uintN_t otmp25 = otmp24 ^ z4; \ - uintN_t otmp26 = otmp25 ^ z2; \ - uintN_t otmp27 = z1 ^ otmp26; \ - uintN_t otmp28 = z14 ^ otmp27; \ - uintN_t otmp29 = otmp28 ^ z10; \ - output[4] = z2 ^ otmp23; \ - output[7] = z5 ^ otmp24; \ - uintN_t otmp30 = z11 ^ otmp29; \ - output[5] = z13 ^ otmp30; \ - uintN_t otmp31 = otmp25 ^ z8; \ - output[1] = z7 ^ otmp31; \ - uintN_t otmp32 = z11 ^ z9; \ - uintN_t otmp33 = z17 ^ otmp32; \ - uintN_t otmp34 = otmp30 ^ otmp33; \ - output[0] = z15 ^ otmp33; \ - uintN_t otmp35 = z12 ^ otmp34; \ - output[6] = otmp35 ^ z16; \ - uintN_t otmp36 = z1 ^ otmp23; \ - uintN_t otmp37 = z5 ^ otmp36; \ - output[2] = z4 ^ otmp37; \ - uintN_t otmp38 = z11 ^ output[1]; \ - uintN_t otmp39 = z2 ^ otmp38; \ - uintN_t otmp40 = z17 ^ otmp39; \ - uintN_t otmp41 = z0 ^ otmp40; \ - uintN_t otmp42 = z5 ^ otmp41; \ - uintN_t otmp43 = otmp42 ^ z10; \ - uintN_t otmp44 = otmp43 ^ z3; \ - output[3] = otmp44 ^ z16; \ - /* end */ - -#define BITSLICED_INVSUBBYTES(output, input, uintN_t) do { \ - SBOX_BACKWARD_TOP_TRANSFORM(input, uintN_t); \ - SBOX_CORE(uintN_t); \ - SBOX_BACKWARD_BOTTOM_TRANSFORM(output, uintN_t); \ - } while (0) - - -/* ----- - * The ShiftRows transformation. This operates independently on each - * bit slice. - */ - -#define SINGLE_BITSLICE_SHIFTROWS(output, input, uintN_t) do \ - { \ - uintN_t mask, mask2, mask3, diff, x = (input); \ - /* Rotate rows 2 and 3 by 16 bits */ \ - mask = 0x00CC * (((uintN_t)~(uintN_t)0) / 0xFFFF); \ - diff = ((x >> 8) ^ x) & mask; \ - x ^= diff ^ (diff << 8); \ - /* Rotate rows 1 and 3 by 8 bits */ \ - mask = 0x0AAA * (((uintN_t)~(uintN_t)0) / 0xFFFF); \ - mask2 = 0xA000 * (((uintN_t)~(uintN_t)0) / 0xFFFF); \ - mask3 = 0x5555 * (((uintN_t)~(uintN_t)0) / 0xFFFF); \ - x = ((x >> 4) & mask) | ((x << 12) & mask2) | (x & mask3); \ - /* Write output */ \ - (output) = x; \ - } while (0) - -#define SINGLE_BITSLICE_INVSHIFTROWS(output, input, uintN_t) do \ - { \ - uintN_t mask, mask2, mask3, diff, x = (input); \ - /* Rotate rows 2 and 3 by 16 bits */ \ - mask = 0x00CC * (((uintN_t)~(uintN_t)0) / 0xFFFF); \ - diff = ((x >> 8) ^ x) & mask; \ - x ^= diff ^ (diff << 8); \ - /* Rotate rows 1 and 3 by 8 bits, the opposite way to ShiftRows */ \ - mask = 0x000A * (((uintN_t)~(uintN_t)0) / 0xFFFF); \ - mask2 = 0xAAA0 * (((uintN_t)~(uintN_t)0) / 0xFFFF); \ - mask3 = 0x5555 * (((uintN_t)~(uintN_t)0) / 0xFFFF); \ - x = ((x >> 12) & mask) | ((x << 4) & mask2) | (x & mask3); \ - /* Write output */ \ - (output) = x; \ - } while (0) - -#define BITSLICED_SHIFTROWS(output, input, uintN_t) do \ - { \ - ITERATE(SINGLE_BITSLICE_SHIFTROWS, output, input, uintN_t); \ - } while (0) - -#define BITSLICED_INVSHIFTROWS(output, input, uintN_t) do \ - { \ - ITERATE(SINGLE_BITSLICE_INVSHIFTROWS, output, input, uintN_t); \ - } while (0) - -/* ----- - * The MixColumns transformation. This has to operate on all eight bit - * slices at once, and also passes data back and forth between the - * bits in an adjacent group of 4 within each slice. - * - * Notation: let F = GF(2)[X]/ be the finite field - * used in AES, and let R = F[Y]/ be the ring whose elements - * represent the possible contents of a column of the matrix. I use X - * and Y below in those senses, i.e. X is the value in F that - * represents the byte 0x02, and Y is the value in R that cycles the - * four bytes around by one if you multiply by it. - */ - -/* Multiply every column by Y^3, i.e. cycle it round one place to the - * right. Operates on one bit slice at a time; you have to wrap it in - * ITERATE to affect all the data at once. */ -#define BITSLICED_MUL_BY_Y3(output, input, uintN_t) do \ - { \ - uintN_t mask, mask2, x; \ - mask = 0x8 * (((uintN_t)~(uintN_t)0) / 0xF); \ - mask2 = 0x7 * (((uintN_t)~(uintN_t)0) / 0xF); \ - x = input; \ - output = ((x << 3) & mask) ^ ((x >> 1) & mask2); \ - } while (0) - -/* Multiply every column by Y^2. */ -#define BITSLICED_MUL_BY_Y2(output, input, uintN_t) do \ - { \ - uintN_t mask, mask2, x; \ - mask = 0xC * (((uintN_t)~(uintN_t)0) / 0xF); \ - mask2 = 0x3 * (((uintN_t)~(uintN_t)0) / 0xF); \ - x = input; \ - output = ((x << 2) & mask) ^ ((x >> 2) & mask2); \ - } while (0) - -#define BITSLICED_MUL_BY_1_Y3(output, input, uintN_t) do \ - { \ - uintN_t tmp = input; \ - BITSLICED_MUL_BY_Y3(tmp, input, uintN_t); \ - output = input ^ tmp; \ - } while (0) - -/* Multiply every column by 1+Y^2. */ -#define BITSLICED_MUL_BY_1_Y2(output, input, uintN_t) do \ - { \ - uintN_t tmp = input; \ - BITSLICED_MUL_BY_Y2(tmp, input, uintN_t); \ - output = input ^ tmp; \ - } while (0) - -/* Multiply every field element by X. This has to feed data between - * slices, so it does the whole job in one go without needing ITERATE. */ -#define BITSLICED_MUL_BY_X(output, input, uintN_t) do \ - { \ - uintN_t bit7 = input[7]; \ - output[7] = input[6]; \ - output[6] = input[5]; \ - output[5] = input[4]; \ - output[4] = input[3] ^ bit7; \ - output[3] = input[2] ^ bit7; \ - output[2] = input[1]; \ - output[1] = input[0] ^ bit7; \ - output[0] = bit7; \ - } while (0) - -/* - * The MixColumns constant is - * M = X + Y + Y^2 + (X+1)Y^3 - * which we construct by rearranging it into - * M = 1 + (1+Y^3) [ X + (1+Y^2) ] - */ -#define BITSLICED_MIXCOLUMNS(output, input, uintN_t) do \ - { \ - uintN_t a[8], aX[8], b[8]; \ - /* a = input * (1+Y^3) */ \ - ITERATE(BITSLICED_MUL_BY_1_Y3, a, input, uintN_t); \ - /* aX = a * X */ \ - BITSLICED_MUL_BY_X(aX, a, uintN_t); \ - /* b = a * (1+Y^2) = input * (1+Y+Y^2+Y^3) */ \ - ITERATE(BITSLICED_MUL_BY_1_Y2, b, a, uintN_t); \ - /* output = input + aX + b (reusing a as a temp */ \ - BITSLICED_ADD(a, aX, b); \ - BITSLICED_ADD(output, input, a); \ - } while (0) - -/* - * The InvMixColumns constant, written out longhand, is - * I = (X^3+X^2+X) + (X^3+1)Y + (X^3+X^2+1)Y^2 + (X^3+X+1)Y^3 - * We represent this as - * I = (X^3+X^2+X+1)(Y^3+Y^2+Y+1) + 1 + X(Y+Y^2) + X^2(Y+Y^3) - */ -#define BITSLICED_INVMIXCOLUMNS(output, input, uintN_t) do \ - { \ - /* We need input * X^i for i=1,...,3 */ \ - uintN_t X[8], X2[8], X3[8]; \ - BITSLICED_MUL_BY_X(X, input, uintN_t); \ - BITSLICED_MUL_BY_X(X2, X, uintN_t); \ - BITSLICED_MUL_BY_X(X3, X2, uintN_t); \ - /* Sum them all and multiply by 1+Y+Y^2+Y^3. */ \ - uintN_t S[8]; \ - BITSLICED_ADD(S, input, X); \ - BITSLICED_ADD(S, S, X2); \ - BITSLICED_ADD(S, S, X3); \ - ITERATE(BITSLICED_MUL_BY_1_Y3, S, S, uintN_t); \ - ITERATE(BITSLICED_MUL_BY_1_Y2, S, S, uintN_t); \ - /* Compute the X(Y+Y^2) term. */ \ - uintN_t A[8]; \ - ITERATE(BITSLICED_MUL_BY_1_Y3, A, X, uintN_t); \ - ITERATE(BITSLICED_MUL_BY_Y2, A, A, uintN_t); \ - /* Compute the X^2(Y+Y^3) term. */ \ - uintN_t B[8]; \ - ITERATE(BITSLICED_MUL_BY_1_Y2, B, X2, uintN_t); \ - ITERATE(BITSLICED_MUL_BY_Y3, B, B, uintN_t); \ - /* And add all the pieces together. */ \ - BITSLICED_ADD(S, S, input); \ - BITSLICED_ADD(S, S, A); \ - BITSLICED_ADD(output, S, B); \ - } while (0) - -/* ----- - * Put it all together into a cipher round. - */ - -/* Dummy macro to get rid of the MixColumns in the final round. */ -#define NO_MIXCOLUMNS(out, in, uintN_t) do {} while (0) - -#define ENCRYPT_ROUND_FN(suffix, uintN_t, mixcol_macro) \ - static void aes_sliced_round_e_##suffix( \ - uintN_t output[8], const uintN_t input[8], const uintN_t roundkey[8]) \ - { \ - BITSLICED_SUBBYTES(output, input, uintN_t); \ - BITSLICED_SHIFTROWS(output, output, uintN_t); \ - mixcol_macro(output, output, uintN_t); \ - BITSLICED_ADD(output, output, roundkey); \ - } - -ENCRYPT_ROUND_FN(serial, uint16_t, BITSLICED_MIXCOLUMNS) -ENCRYPT_ROUND_FN(serial_last, uint16_t, NO_MIXCOLUMNS) -ENCRYPT_ROUND_FN(parallel, BignumInt, BITSLICED_MIXCOLUMNS) -ENCRYPT_ROUND_FN(parallel_last, BignumInt, NO_MIXCOLUMNS) - -#define DECRYPT_ROUND_FN(suffix, uintN_t, mixcol_macro) \ - static void aes_sliced_round_d_##suffix( \ - uintN_t output[8], const uintN_t input[8], const uintN_t roundkey[8]) \ - { \ - BITSLICED_ADD(output, input, roundkey); \ - mixcol_macro(output, output, uintN_t); \ - BITSLICED_INVSUBBYTES(output, output, uintN_t); \ - BITSLICED_INVSHIFTROWS(output, output, uintN_t); \ - } - -#if 0 /* no cipher mode we support requires serial decryption */ -DECRYPT_ROUND_FN(serial, uint16_t, BITSLICED_INVMIXCOLUMNS) -DECRYPT_ROUND_FN(serial_first, uint16_t, NO_MIXCOLUMNS) -#endif -DECRYPT_ROUND_FN(parallel, BignumInt, BITSLICED_INVMIXCOLUMNS) -DECRYPT_ROUND_FN(parallel_first, BignumInt, NO_MIXCOLUMNS) - -/* ----- - * Key setup function. - */ - -typedef struct aes_sliced_key aes_sliced_key; -struct aes_sliced_key { - BignumInt roundkeys_parallel[MAXROUNDKEYS * 8]; - uint16_t roundkeys_serial[MAXROUNDKEYS * 8]; - unsigned rounds; -}; - -static void aes_sliced_key_setup( - aes_sliced_key *sk, const void *vkey, size_t keybits) -{ - const unsigned char *key = (const unsigned char *)vkey; - - size_t key_words = keybits / 32; - sk->rounds = key_words + 6; - size_t sched_words = (sk->rounds + 1) * 4; - - unsigned rconpos = 0; - - uint16_t *outslices = sk->roundkeys_serial; - unsigned outshift = 0; - - memset(sk->roundkeys_serial, 0, sizeof(sk->roundkeys_serial)); - - uint8_t inblk[16]; - memset(inblk, 0, 16); - uint16_t slices[8]; - - for (size_t i = 0; i < sched_words; i++) { - /* - * Prepare a word of round key in the low 4 bits of each - * integer in slices[]. - */ - if (i < key_words) { - memcpy(inblk, key + 4*i, 4); - TO_BITSLICES(slices, inblk, uint16_t, =, 0); - } else { - unsigned wordindex, bitshift; - uint16_t *prevslices; - - /* Fetch the (i-1)th key word */ - wordindex = i-1; - bitshift = 4 * (wordindex & 3); - prevslices = sk->roundkeys_serial + 8 * (wordindex >> 2); - for (size_t i = 0; i < 8; i++) - slices[i] = prevslices[i] >> bitshift; - - /* Decide what we're doing in this expansion stage */ - bool rotate_and_round_constant = (i % key_words == 0); - bool sub = rotate_and_round_constant || - (key_words == 8 && i % 8 == 4); - - if (rotate_and_round_constant) { - for (size_t i = 0; i < 8; i++) - slices[i] = ((slices[i] << 3) | (slices[i] >> 1)) & 0xF; - } - - if (sub) { - /* Apply the SubBytes transform to the key word. But - * here we need to apply the _full_ SubBytes from the - * spec, including the constant which our S-box leaves - * out. */ - BITSLICED_SUBBYTES(slices, slices, uint16_t); - slices[0] ^= 0xFFFF; - slices[1] ^= 0xFFFF; - slices[5] ^= 0xFFFF; - slices[6] ^= 0xFFFF; - } - - if (rotate_and_round_constant) { - assert(rconpos < lenof(key_setup_round_constants)); - uint8_t rcon = key_setup_round_constants[rconpos++]; - for (size_t i = 0; i < 8; i++) - slices[i] ^= 1 & (rcon >> i); - } - - /* Combine with the (i-Nk)th key word */ - wordindex = i - key_words; - bitshift = 4 * (wordindex & 3); - prevslices = sk->roundkeys_serial + 8 * (wordindex >> 2); - for (size_t i = 0; i < 8; i++) - slices[i] ^= prevslices[i] >> bitshift; - } - - /* - * Now copy it into sk. - */ - for (unsigned b = 0; b < 8; b++) - outslices[b] |= (slices[b] & 0xF) << outshift; - outshift += 4; - if (outshift == 16) { - outshift = 0; - outslices += 8; - } - } - - smemclr(inblk, sizeof(inblk)); - smemclr(slices, sizeof(slices)); - - /* - * Add the S-box constant to every round key after the first one, - * compensating for it being left out in the main cipher. - */ - for (size_t i = 8; i < 8 * (sched_words/4); i += 8) { - sk->roundkeys_serial[i+0] ^= 0xFFFF; - sk->roundkeys_serial[i+1] ^= 0xFFFF; - sk->roundkeys_serial[i+5] ^= 0xFFFF; - sk->roundkeys_serial[i+6] ^= 0xFFFF; - } - - /* - * Replicate that set of round keys into larger integers for the - * parallel versions of the cipher. - */ - for (size_t i = 0; i < 8 * (sched_words / 4); i++) { - sk->roundkeys_parallel[i] = sk->roundkeys_serial[i] * - ((BignumInt)~(BignumInt)0 / 0xFFFF); - } -} - -/* ----- - * The full cipher primitive, including transforming the input and - * output to/from bit-sliced form. - */ - -#define ENCRYPT_FN(suffix, uintN_t, nblocks) \ - static void aes_sliced_e_##suffix( \ - uint8_t *output, const uint8_t *input, const aes_sliced_key *sk) \ - { \ - uintN_t state[8]; \ - TO_BITSLICES(state, input, uintN_t, =, 0); \ - for (unsigned i = 1; i < nblocks; i++) { \ - input += 16; \ - TO_BITSLICES(state, input, uintN_t, |=, i*16); \ - } \ - const uintN_t *keys = sk->roundkeys_##suffix; \ - BITSLICED_ADD(state, state, keys); \ - keys += 8; \ - for (unsigned i = 0; i < sk->rounds-1; i++) { \ - aes_sliced_round_e_##suffix(state, state, keys); \ - keys += 8; \ - } \ - aes_sliced_round_e_##suffix##_last(state, state, keys); \ - for (unsigned i = 0; i < nblocks; i++) { \ - FROM_BITSLICES(output, state, i*16); \ - output += 16; \ - } \ - } - -#define DECRYPT_FN(suffix, uintN_t, nblocks) \ - static void aes_sliced_d_##suffix( \ - uint8_t *output, const uint8_t *input, const aes_sliced_key *sk) \ - { \ - uintN_t state[8]; \ - TO_BITSLICES(state, input, uintN_t, =, 0); \ - for (unsigned i = 1; i < nblocks; i++) { \ - input += 16; \ - TO_BITSLICES(state, input, uintN_t, |=, i*16); \ - } \ - const uintN_t *keys = sk->roundkeys_##suffix + 8*sk->rounds; \ - aes_sliced_round_d_##suffix##_first(state, state, keys); \ - keys -= 8; \ - for (unsigned i = 0; i < sk->rounds-1; i++) { \ - aes_sliced_round_d_##suffix(state, state, keys); \ - keys -= 8; \ - } \ - BITSLICED_ADD(state, state, keys); \ - for (unsigned i = 0; i < nblocks; i++) { \ - FROM_BITSLICES(output, state, i*16); \ - output += 16; \ - } \ - } - -ENCRYPT_FN(serial, uint16_t, 1) -#if 0 /* no cipher mode we support requires serial decryption */ -DECRYPT_FN(serial, uint16_t, 1) -#endif -ENCRYPT_FN(parallel, BignumInt, SLICE_PARALLELISM) -DECRYPT_FN(parallel, BignumInt, SLICE_PARALLELISM) - -/* ----- - * The SSH interface and the cipher modes. - */ - -#define SDCTR_WORDS (16 / BIGNUM_INT_BYTES) - -typedef struct aes_sw_context aes_sw_context; -struct aes_sw_context { - aes_sliced_key sk; - union { - struct { - /* In CBC mode, the IV is just a copy of the last seen - * cipher block. */ - uint8_t prevblk[16]; - } cbc; - struct { - /* In SDCTR mode, we keep the counter itself in a form - * that's easy to increment. We also use the parallel - * version of the core AES function, so we'll encrypt - * multiple counter values in one go. That won't align - * nicely with the sizes of data we're asked to encrypt, - * so we must also store a cache of the last set of - * keystream blocks we generated, and our current position - * within that cache. */ - BignumInt counter[SDCTR_WORDS]; - uint8_t keystream[SLICE_PARALLELISM * 16]; - uint8_t *keystream_pos; - } sdctr; - } iv; - ssh_cipher ciph; -}; - -static ssh_cipher *aes_sw_new(const ssh_cipheralg *alg) -{ - aes_sw_context *ctx = snew(aes_sw_context); - ctx->ciph.vt = alg; - return &ctx->ciph; -} - -static void aes_sw_free(ssh_cipher *ciph) -{ - aes_sw_context *ctx = container_of(ciph, aes_sw_context, ciph); - smemclr(ctx, sizeof(*ctx)); - sfree(ctx); -} - -static void aes_sw_setkey(ssh_cipher *ciph, const void *vkey) -{ - aes_sw_context *ctx = container_of(ciph, aes_sw_context, ciph); - aes_sliced_key_setup(&ctx->sk, vkey, ctx->ciph.vt->real_keybits); -} - -static void aes_sw_setiv_cbc(ssh_cipher *ciph, const void *iv) -{ - aes_sw_context *ctx = container_of(ciph, aes_sw_context, ciph); - memcpy(ctx->iv.cbc.prevblk, iv, 16); -} - -static void aes_sw_setiv_sdctr(ssh_cipher *ciph, const void *viv) -{ - aes_sw_context *ctx = container_of(ciph, aes_sw_context, ciph); - const uint8_t *iv = (const uint8_t *)viv; - - /* Import the initial counter value into the internal representation */ - for (unsigned i = 0; i < SDCTR_WORDS; i++) - ctx->iv.sdctr.counter[i] = - GET_BIGNUMINT_MSB_FIRST( - iv + 16 - BIGNUM_INT_BYTES - i*BIGNUM_INT_BYTES); - - /* Set keystream_pos to indicate that the keystream cache is - * currently empty */ - ctx->iv.sdctr.keystream_pos = - ctx->iv.sdctr.keystream + sizeof(ctx->iv.sdctr.keystream); -} - -typedef void (*aes_sw_fn)(uint32_t v[4], const uint32_t *keysched); - -static inline void memxor16(void *vout, const void *vlhs, const void *vrhs) -{ - uint8_t *out = (uint8_t *)vout; - const uint8_t *lhs = (const uint8_t *)vlhs, *rhs = (const uint8_t *)vrhs; - uint64_t w; - - w = GET_64BIT_LSB_FIRST(lhs); - w ^= GET_64BIT_LSB_FIRST(rhs); - PUT_64BIT_LSB_FIRST(out, w); - w = GET_64BIT_LSB_FIRST(lhs + 8); - w ^= GET_64BIT_LSB_FIRST(rhs + 8); - PUT_64BIT_LSB_FIRST(out + 8, w); -} - -static inline void aes_cbc_sw_encrypt( - ssh_cipher *ciph, void *vblk, int blklen) -{ - aes_sw_context *ctx = container_of(ciph, aes_sw_context, ciph); - - /* - * CBC encryption has to be done serially, because the input to - * each run of the cipher includes the output from the previous - * run. - */ - - for (uint8_t *blk = (uint8_t *)vblk, *finish = blk + blklen; - blk < finish; blk += 16) { - /* - * We use the IV array itself as the location for the - * encryption, because there's no reason not to. - */ - - /* XOR the new plaintext block into the previous cipher block */ - memxor16(ctx->iv.cbc.prevblk, ctx->iv.cbc.prevblk, blk); - - /* Run the cipher over the result, which leaves it - * conveniently already stored in ctx->iv */ - aes_sliced_e_serial( - ctx->iv.cbc.prevblk, ctx->iv.cbc.prevblk, &ctx->sk); - - /* Copy it to the output location */ - memcpy(blk, ctx->iv.cbc.prevblk, 16); - } -} - -static inline void aes_cbc_sw_decrypt( - ssh_cipher *ciph, void *vblk, int blklen) -{ - aes_sw_context *ctx = container_of(ciph, aes_sw_context, ciph); - uint8_t *blk = (uint8_t *)vblk; - - /* - * CBC decryption can run in parallel, because all the - * _ciphertext_ blocks are already available. - */ - - size_t blocks_remaining = blklen / 16; - - uint8_t data[SLICE_PARALLELISM * 16]; - /* Zeroing the data array is probably overcautious, but it avoids - * technically undefined behaviour from leaving it uninitialised - * if our very first iteration doesn't include enough cipher - * blocks to populate it fully */ - memset(data, 0, sizeof(data)); - - while (blocks_remaining > 0) { - /* Number of blocks we'll handle in this iteration. If we're - * dealing with fewer than the maximum, it doesn't matter - - * it's harmless to run the full parallel cipher function - * anyway. */ - size_t blocks = (blocks_remaining < SLICE_PARALLELISM ? - blocks_remaining : SLICE_PARALLELISM); - - /* Parallel-decrypt the input, in a separate array so we still - * have the cipher stream available for XORing. */ - memcpy(data, blk, 16 * blocks); - aes_sliced_d_parallel(data, data, &ctx->sk); - - /* Write the output and update the IV */ - for (size_t i = 0; i < blocks; i++) { - uint8_t *decrypted = data + 16*i; - uint8_t *output = blk + 16*i; - - memxor16(decrypted, decrypted, ctx->iv.cbc.prevblk); - memcpy(ctx->iv.cbc.prevblk, output, 16); - memcpy(output, decrypted, 16); - } - - /* Advance the input pointer. */ - blk += 16 * blocks; - blocks_remaining -= blocks; - } - - smemclr(data, sizeof(data)); -} - -static inline void aes_sdctr_sw( - ssh_cipher *ciph, void *vblk, int blklen) -{ - aes_sw_context *ctx = container_of(ciph, aes_sw_context, ciph); - - /* - * SDCTR encrypt/decrypt loops round one block at a time XORing - * the keystream into the user's data, and periodically has to run - * a parallel encryption operation to get more keystream. - */ - - uint8_t *keystream_end = - ctx->iv.sdctr.keystream + sizeof(ctx->iv.sdctr.keystream); - - for (uint8_t *blk = (uint8_t *)vblk, *finish = blk + blklen; - blk < finish; blk += 16) { - - if (ctx->iv.sdctr.keystream_pos == keystream_end) { - /* - * Generate some keystream. - */ - for (uint8_t *block = ctx->iv.sdctr.keystream; - block < keystream_end; block += 16) { - /* Format the counter value into the buffer. */ - for (unsigned i = 0; i < SDCTR_WORDS; i++) - PUT_BIGNUMINT_MSB_FIRST( - block + 16 - BIGNUM_INT_BYTES - i*BIGNUM_INT_BYTES, - ctx->iv.sdctr.counter[i]); - - /* Increment the counter. */ - BignumCarry carry = 1; - for (unsigned i = 0; i < SDCTR_WORDS; i++) - BignumADC(ctx->iv.sdctr.counter[i], carry, - ctx->iv.sdctr.counter[i], 0, carry); - } - - /* Encrypt all those counter blocks. */ - aes_sliced_e_parallel(ctx->iv.sdctr.keystream, - ctx->iv.sdctr.keystream, &ctx->sk); - - /* Reset keystream_pos to the start of the buffer. */ - ctx->iv.sdctr.keystream_pos = ctx->iv.sdctr.keystream; - } - - memxor16(blk, blk, ctx->iv.sdctr.keystream_pos); - ctx->iv.sdctr.keystream_pos += 16; - } -} - -#define SW_ENC_DEC(len) \ - static void aes##len##_cbc_sw_encrypt( \ - ssh_cipher *ciph, void *vblk, int blklen) \ - { aes_cbc_sw_encrypt(ciph, vblk, blklen); } \ - static void aes##len##_cbc_sw_decrypt( \ - ssh_cipher *ciph, void *vblk, int blklen) \ - { aes_cbc_sw_decrypt(ciph, vblk, blklen); } \ - static void aes##len##_sdctr_sw( \ - ssh_cipher *ciph, void *vblk, int blklen) \ - { aes_sdctr_sw(ciph, vblk, blklen); } - -SW_ENC_DEC(128) -SW_ENC_DEC(192) -SW_ENC_DEC(256) - -/* ---------------------------------------------------------------------- - * Hardware-accelerated implementation of AES using x86 AES-NI. - */ - -#if HW_AES == HW_AES_NI - -/* - * Set target architecture for Clang and GCC - */ -#if !defined(__clang__) && defined(__GNUC__) -# pragma GCC target("aes") -# pragma GCC target("sse4.1") -#endif - -#if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8))) -# define FUNC_ISA __attribute__ ((target("sse4.1,aes"))) -#else -# define FUNC_ISA -#endif - -#include -#include - -#if defined(__clang__) || defined(__GNUC__) -#include -#define GET_CPU_ID(out) __cpuid(1, (out)[0], (out)[1], (out)[2], (out)[3]) -#else -#define GET_CPU_ID(out) __cpuid(out, 1) -#endif - -bool aes_hw_available(void) -{ - /* - * Determine if AES is available on this CPU, by checking that - * both AES itself and SSE4.1 are supported. - */ - unsigned int CPUInfo[4]; - GET_CPU_ID(CPUInfo); - return (CPUInfo[2] & (1 << 25)) && (CPUInfo[2] & (1 << 19)); -} - -/* - * Core AES-NI encrypt/decrypt functions, one per length and direction. - */ - -#define NI_CIPHER(len, dir, dirlong, repmacro) \ - static FUNC_ISA inline __m128i aes_ni_##len##_##dir( \ - __m128i v, const __m128i *keysched) \ - { \ - v = _mm_xor_si128(v, *keysched++); \ - repmacro(v = _mm_aes##dirlong##_si128(v, *keysched++);); \ - return _mm_aes##dirlong##last_si128(v, *keysched); \ - } - -NI_CIPHER(128, e, enc, REP9) -NI_CIPHER(128, d, dec, REP9) -NI_CIPHER(192, e, enc, REP11) -NI_CIPHER(192, d, dec, REP11) -NI_CIPHER(256, e, enc, REP13) -NI_CIPHER(256, d, dec, REP13) - -/* - * The main key expansion. - */ -static FUNC_ISA void aes_ni_key_expand( - const unsigned char *key, size_t key_words, - __m128i *keysched_e, __m128i *keysched_d) -{ - size_t rounds = key_words + 6; - size_t sched_words = (rounds + 1) * 4; - - /* - * Store the key schedule as 32-bit integers during expansion, so - * that it's easy to refer back to individual previous words. We - * collect them into the final __m128i form at the end. - */ - uint32_t sched[MAXROUNDKEYS * 4]; - - unsigned rconpos = 0; - - for (size_t i = 0; i < sched_words; i++) { - if (i < key_words) { - sched[i] = GET_32BIT_LSB_FIRST(key + 4 * i); - } else { - uint32_t temp = sched[i - 1]; - - bool rotate_and_round_constant = (i % key_words == 0); - bool only_sub = (key_words == 8 && i % 8 == 4); - - if (rotate_and_round_constant) { - __m128i v = _mm_setr_epi32(0,temp,0,0); - v = _mm_aeskeygenassist_si128(v, 0); - temp = _mm_extract_epi32(v, 1); - - assert(rconpos < lenof(key_setup_round_constants)); - temp ^= key_setup_round_constants[rconpos++]; - } else if (only_sub) { - __m128i v = _mm_setr_epi32(0,temp,0,0); - v = _mm_aeskeygenassist_si128(v, 0); - temp = _mm_extract_epi32(v, 0); - } - - sched[i] = sched[i - key_words] ^ temp; - } - } - - /* - * Combine the key schedule words into __m128i vectors and store - * them in the output context. - */ - for (size_t round = 0; round <= rounds; round++) - keysched_e[round] = _mm_setr_epi32( - sched[4*round ], sched[4*round+1], - sched[4*round+2], sched[4*round+3]); - - smemclr(sched, sizeof(sched)); - - /* - * Now prepare the modified keys for the inverse cipher. - */ - for (size_t eround = 0; eround <= rounds; eround++) { - size_t dround = rounds - eround; - __m128i rkey = keysched_e[eround]; - if (eround && dround) /* neither first nor last */ - rkey = _mm_aesimc_si128(rkey); - keysched_d[dround] = rkey; - } -} - -/* - * Auxiliary routine to increment the 128-bit counter used in SDCTR - * mode. - */ -static FUNC_ISA inline __m128i aes_ni_sdctr_increment(__m128i v) -{ - const __m128i ONE = _mm_setr_epi32(1,0,0,0); - const __m128i ZERO = _mm_setzero_si128(); - - /* Increment the low-order 64 bits of v */ - v = _mm_add_epi64(v, ONE); - /* Check if they've become zero */ - __m128i cmp = _mm_cmpeq_epi64(v, ZERO); - /* If so, the low half of cmp is all 1s. Pack that into the high - * half of addend with zero in the low half. */ - __m128i addend = _mm_unpacklo_epi64(ZERO, cmp); - /* And subtract that from v, which increments the high 64 bits iff - * the low 64 wrapped round. */ - v = _mm_sub_epi64(v, addend); - - return v; -} - -/* - * Auxiliary routine to reverse the byte order of a vector, so that - * the SDCTR IV can be made big-endian for feeding to the cipher. - */ -static FUNC_ISA inline __m128i aes_ni_sdctr_reverse(__m128i v) -{ - v = _mm_shuffle_epi8( - v, _mm_setr_epi8(15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0)); - return v; -} - -/* - * The SSH interface and the cipher modes. - */ - -typedef struct aes_ni_context aes_ni_context; -struct aes_ni_context { - __m128i keysched_e[MAXROUNDKEYS], keysched_d[MAXROUNDKEYS], iv; - - void *pointer_to_free; - ssh_cipher ciph; -}; - -static ssh_cipher *aes_hw_new(const ssh_cipheralg *alg) -{ - if (!aes_hw_available_cached()) - return NULL; - - /* - * The __m128i variables in the context structure need to be - * 16-byte aligned, but not all malloc implementations that this - * code has to work with will guarantee to return a 16-byte - * aligned pointer. So we over-allocate, manually realign the - * pointer ourselves, and store the original one inside the - * context so we know how to free it later. - */ - void *allocation = smalloc(sizeof(aes_ni_context) + 15); - uintptr_t alloc_address = (uintptr_t)allocation; - uintptr_t aligned_address = (alloc_address + 15) & ~15; - aes_ni_context *ctx = (aes_ni_context *)aligned_address; - - ctx->ciph.vt = alg; - ctx->pointer_to_free = allocation; - return &ctx->ciph; -} - -static void aes_hw_free(ssh_cipher *ciph) -{ - aes_ni_context *ctx = container_of(ciph, aes_ni_context, ciph); - void *allocation = ctx->pointer_to_free; - smemclr(ctx, sizeof(*ctx)); - sfree(allocation); -} - -static void aes_hw_setkey(ssh_cipher *ciph, const void *vkey) -{ - aes_ni_context *ctx = container_of(ciph, aes_ni_context, ciph); - const unsigned char *key = (const unsigned char *)vkey; - - aes_ni_key_expand(key, ctx->ciph.vt->real_keybits / 32, - ctx->keysched_e, ctx->keysched_d); -} - -static FUNC_ISA void aes_hw_setiv_cbc(ssh_cipher *ciph, const void *iv) -{ - aes_ni_context *ctx = container_of(ciph, aes_ni_context, ciph); - ctx->iv = _mm_loadu_si128(iv); -} - -static FUNC_ISA void aes_hw_setiv_sdctr(ssh_cipher *ciph, const void *iv) -{ - aes_ni_context *ctx = container_of(ciph, aes_ni_context, ciph); - __m128i counter = _mm_loadu_si128(iv); - ctx->iv = aes_ni_sdctr_reverse(counter); -} - -typedef __m128i (*aes_ni_fn)(__m128i v, const __m128i *keysched); - -static FUNC_ISA inline void aes_cbc_ni_encrypt( - ssh_cipher *ciph, void *vblk, int blklen, aes_ni_fn encrypt) -{ - aes_ni_context *ctx = container_of(ciph, aes_ni_context, ciph); - - for (uint8_t *blk = (uint8_t *)vblk, *finish = blk + blklen; - blk < finish; blk += 16) { - __m128i plaintext = _mm_loadu_si128((const __m128i *)blk); - __m128i cipher_input = _mm_xor_si128(plaintext, ctx->iv); - __m128i ciphertext = encrypt(cipher_input, ctx->keysched_e); - _mm_storeu_si128((__m128i *)blk, ciphertext); - ctx->iv = ciphertext; - } -} - -static FUNC_ISA inline void aes_cbc_ni_decrypt( - ssh_cipher *ciph, void *vblk, int blklen, aes_ni_fn decrypt) -{ - aes_ni_context *ctx = container_of(ciph, aes_ni_context, ciph); - - for (uint8_t *blk = (uint8_t *)vblk, *finish = blk + blklen; - blk < finish; blk += 16) { - __m128i ciphertext = _mm_loadu_si128((const __m128i *)blk); - __m128i decrypted = decrypt(ciphertext, ctx->keysched_d); - __m128i plaintext = _mm_xor_si128(decrypted, ctx->iv); - _mm_storeu_si128((__m128i *)blk, plaintext); - ctx->iv = ciphertext; - } -} - -static FUNC_ISA inline void aes_sdctr_ni( - ssh_cipher *ciph, void *vblk, int blklen, aes_ni_fn encrypt) -{ - aes_ni_context *ctx = container_of(ciph, aes_ni_context, ciph); - - for (uint8_t *blk = (uint8_t *)vblk, *finish = blk + blklen; - blk < finish; blk += 16) { - __m128i counter = aes_ni_sdctr_reverse(ctx->iv); - __m128i keystream = encrypt(counter, ctx->keysched_e); - __m128i input = _mm_loadu_si128((const __m128i *)blk); - __m128i output = _mm_xor_si128(input, keystream); - _mm_storeu_si128((__m128i *)blk, output); - ctx->iv = aes_ni_sdctr_increment(ctx->iv); - } -} - -#define NI_ENC_DEC(len) \ - static FUNC_ISA void aes##len##_cbc_hw_encrypt( \ - ssh_cipher *ciph, void *vblk, int blklen) \ - { aes_cbc_ni_encrypt(ciph, vblk, blklen, aes_ni_##len##_e); } \ - static FUNC_ISA void aes##len##_cbc_hw_decrypt( \ - ssh_cipher *ciph, void *vblk, int blklen) \ - { aes_cbc_ni_decrypt(ciph, vblk, blklen, aes_ni_##len##_d); } \ - static FUNC_ISA void aes##len##_sdctr_hw( \ - ssh_cipher *ciph, void *vblk, int blklen) \ - { aes_sdctr_ni(ciph, vblk, blklen, aes_ni_##len##_e); } \ - -NI_ENC_DEC(128) -NI_ENC_DEC(192) -NI_ENC_DEC(256) - -/* ---------------------------------------------------------------------- - * Hardware-accelerated implementation of AES using Arm NEON. - */ - -#elif HW_AES == HW_AES_NEON - -/* - * Manually set the target architecture, if we decided above that we - * need to. - */ -#ifdef USE_CLANG_ATTR_TARGET_AARCH64 -/* - * A spot of cheating: redefine some ACLE feature macros before - * including arm_neon.h. Otherwise we won't get the AES intrinsics - * defined by that header, because it will be looking at the settings - * for the whole translation unit rather than the ones we're going to - * put on some particular functions using __attribute__((target)). - */ -#define __ARM_NEON 1 -#define __ARM_FEATURE_CRYPTO 1 -#define FUNC_ISA __attribute__ ((target("neon,crypto"))) -#endif /* USE_CLANG_ATTR_TARGET_AARCH64 */ - -#ifndef FUNC_ISA -#define FUNC_ISA -#endif - -#ifdef USE_ARM64_NEON_H -#include -#else -#include -#endif - -static bool aes_hw_available(void) -{ - /* - * For Arm, we delegate to a per-platform AES detection function, - * because it has to be implemented by asking the operating system - * rather than directly querying the CPU. - * - * That's because Arm systems commonly have multiple cores that - * are not all alike, so any method of querying whether NEON - * crypto instructions work on the _current_ CPU - even one as - * crude as just trying one and catching the SIGILL - wouldn't - * give an answer that you could still rely on the first time the - * OS migrated your process to another CPU. - */ - return platform_aes_hw_available(); -} - -/* - * Core NEON encrypt/decrypt functions, one per length and direction. - */ - -#define NEON_CIPHER(len, repmacro) \ - static FUNC_ISA inline uint8x16_t aes_neon_##len##_e( \ - uint8x16_t v, const uint8x16_t *keysched) \ - { \ - repmacro(v = vaesmcq_u8(vaeseq_u8(v, *keysched++));); \ - v = vaeseq_u8(v, *keysched++); \ - return veorq_u8(v, *keysched); \ - } \ - static FUNC_ISA inline uint8x16_t aes_neon_##len##_d( \ - uint8x16_t v, const uint8x16_t *keysched) \ - { \ - repmacro(v = vaesimcq_u8(vaesdq_u8(v, *keysched++));); \ - v = vaesdq_u8(v, *keysched++); \ - return veorq_u8(v, *keysched); \ - } - -NEON_CIPHER(128, REP9) -NEON_CIPHER(192, REP11) -NEON_CIPHER(256, REP13) - -/* - * The main key expansion. - */ -static FUNC_ISA void aes_neon_key_expand( - const unsigned char *key, size_t key_words, - uint8x16_t *keysched_e, uint8x16_t *keysched_d) -{ - size_t rounds = key_words + 6; - size_t sched_words = (rounds + 1) * 4; - - /* - * Store the key schedule as 32-bit integers during expansion, so - * that it's easy to refer back to individual previous words. We - * collect them into the final uint8x16_t form at the end. - */ - uint32_t sched[MAXROUNDKEYS * 4]; - - unsigned rconpos = 0; - - for (size_t i = 0; i < sched_words; i++) { - if (i < key_words) { - sched[i] = GET_32BIT_LSB_FIRST(key + 4 * i); - } else { - uint32_t temp = sched[i - 1]; - - bool rotate_and_round_constant = (i % key_words == 0); - bool sub = rotate_and_round_constant || - (key_words == 8 && i % 8 == 4); - - if (rotate_and_round_constant) - temp = (temp << 24) | (temp >> 8); - - if (sub) { - uint32x4_t v32 = vdupq_n_u32(temp); - uint8x16_t v8 = vreinterpretq_u8_u32(v32); - v8 = vaeseq_u8(v8, vdupq_n_u8(0)); - v32 = vreinterpretq_u32_u8(v8); - temp = vget_lane_u32(vget_low_u32(v32), 0); - } - - if (rotate_and_round_constant) { - assert(rconpos < lenof(key_setup_round_constants)); - temp ^= key_setup_round_constants[rconpos++]; - } - - sched[i] = sched[i - key_words] ^ temp; - } - } - - /* - * Combine the key schedule words into uint8x16_t vectors and - * store them in the output context. - */ - for (size_t round = 0; round <= rounds; round++) - keysched_e[round] = vreinterpretq_u8_u32(vld1q_u32(sched + 4*round)); - - smemclr(sched, sizeof(sched)); - - /* - * Now prepare the modified keys for the inverse cipher. - */ - for (size_t eround = 0; eround <= rounds; eround++) { - size_t dround = rounds - eround; - uint8x16_t rkey = keysched_e[eround]; - if (eround && dround) /* neither first nor last */ - rkey = vaesimcq_u8(rkey); - keysched_d[dround] = rkey; - } -} - -/* - * Auxiliary routine to reverse the byte order of a vector, so that - * the SDCTR IV can be made big-endian for feeding to the cipher. - * - * In fact we don't need to reverse the vector _all_ the way; we leave - * the two lanes in MSW,LSW order, because that makes no difference to - * the efficiency of the increment. That way we only have to reverse - * bytes within each lane in this function. - */ -static FUNC_ISA inline uint8x16_t aes_neon_sdctr_reverse(uint8x16_t v) -{ - return vrev64q_u8(v); -} - -/* - * Auxiliary routine to increment the 128-bit counter used in SDCTR - * mode. There's no instruction to treat a 128-bit vector as a single - * long integer, so instead we have to increment the bottom half - * unconditionally, and the top half if the bottom half started off as - * all 1s (in which case there was about to be a carry). - */ -static FUNC_ISA inline uint8x16_t aes_neon_sdctr_increment(uint8x16_t in) -{ -#ifdef __aarch64__ - /* There will be a carry if the low 64 bits are all 1s. */ - uint64x1_t all1 = vcreate_u64(0xFFFFFFFFFFFFFFFF); - uint64x1_t carry = vceq_u64(vget_high_u64(vreinterpretq_u64_u8(in)), all1); - - /* Make a word whose bottom half is unconditionally all 1s, and - * the top half is 'carry', i.e. all 0s most of the time but all - * 1s if we need to increment the top half. Then that word is what - * we need to _subtract_ from the input counter. */ - uint64x2_t subtrahend = vcombine_u64(carry, all1); -#else - /* AArch32 doesn't have comparisons that operate on a 64-bit lane, - * so we start by comparing each 32-bit half of the low 64 bits - * _separately_ to all-1s. */ - uint32x2_t all1 = vdup_n_u32(0xFFFFFFFF); - uint32x2_t carry = vceq_u32( - vget_high_u32(vreinterpretq_u32_u8(in)), all1); - - /* Swap the 32-bit words of the compare output, and AND with the - * unswapped version. Now carry is all 1s iff the bottom half of - * the input counter was all 1s, and all 0s otherwise. */ - carry = vand_u32(carry, vrev64_u32(carry)); - - /* Now make the vector to subtract in the same way as above. */ - uint64x2_t subtrahend = vreinterpretq_u64_u32(vcombine_u32(carry, all1)); -#endif - - return vreinterpretq_u8_u64( - vsubq_u64(vreinterpretq_u64_u8(in), subtrahend)); -} - -/* - * The SSH interface and the cipher modes. - */ - -typedef struct aes_neon_context aes_neon_context; -struct aes_neon_context { - uint8x16_t keysched_e[MAXROUNDKEYS], keysched_d[MAXROUNDKEYS], iv; - - ssh_cipher ciph; -}; - -static ssh_cipher *aes_hw_new(const ssh_cipheralg *alg) -{ - if (!aes_hw_available_cached()) - return NULL; - - aes_neon_context *ctx = snew(aes_neon_context); - ctx->ciph.vt = alg; - return &ctx->ciph; -} - -static void aes_hw_free(ssh_cipher *ciph) -{ - aes_neon_context *ctx = container_of(ciph, aes_neon_context, ciph); - smemclr(ctx, sizeof(*ctx)); - sfree(ctx); -} - -static void aes_hw_setkey(ssh_cipher *ciph, const void *vkey) -{ - aes_neon_context *ctx = container_of(ciph, aes_neon_context, ciph); - const unsigned char *key = (const unsigned char *)vkey; - - aes_neon_key_expand(key, ctx->ciph.vt->real_keybits / 32, - ctx->keysched_e, ctx->keysched_d); -} - -static FUNC_ISA void aes_hw_setiv_cbc(ssh_cipher *ciph, const void *iv) -{ - aes_neon_context *ctx = container_of(ciph, aes_neon_context, ciph); - ctx->iv = vld1q_u8(iv); -} - -static FUNC_ISA void aes_hw_setiv_sdctr(ssh_cipher *ciph, const void *iv) -{ - aes_neon_context *ctx = container_of(ciph, aes_neon_context, ciph); - uint8x16_t counter = vld1q_u8(iv); - ctx->iv = aes_neon_sdctr_reverse(counter); -} - -typedef uint8x16_t (*aes_neon_fn)(uint8x16_t v, const uint8x16_t *keysched); - -static FUNC_ISA inline void aes_cbc_neon_encrypt( - ssh_cipher *ciph, void *vblk, int blklen, aes_neon_fn encrypt) -{ - aes_neon_context *ctx = container_of(ciph, aes_neon_context, ciph); - - for (uint8_t *blk = (uint8_t *)vblk, *finish = blk + blklen; - blk < finish; blk += 16) { - uint8x16_t plaintext = vld1q_u8(blk); - uint8x16_t cipher_input = veorq_u8(plaintext, ctx->iv); - uint8x16_t ciphertext = encrypt(cipher_input, ctx->keysched_e); - vst1q_u8(blk, ciphertext); - ctx->iv = ciphertext; - } -} - -static FUNC_ISA inline void aes_cbc_neon_decrypt( - ssh_cipher *ciph, void *vblk, int blklen, aes_neon_fn decrypt) -{ - aes_neon_context *ctx = container_of(ciph, aes_neon_context, ciph); - - for (uint8_t *blk = (uint8_t *)vblk, *finish = blk + blklen; - blk < finish; blk += 16) { - uint8x16_t ciphertext = vld1q_u8(blk); - uint8x16_t decrypted = decrypt(ciphertext, ctx->keysched_d); - uint8x16_t plaintext = veorq_u8(decrypted, ctx->iv); - vst1q_u8(blk, plaintext); - ctx->iv = ciphertext; - } -} - -static FUNC_ISA inline void aes_sdctr_neon( - ssh_cipher *ciph, void *vblk, int blklen, aes_neon_fn encrypt) -{ - aes_neon_context *ctx = container_of(ciph, aes_neon_context, ciph); - - for (uint8_t *blk = (uint8_t *)vblk, *finish = blk + blklen; - blk < finish; blk += 16) { - uint8x16_t counter = aes_neon_sdctr_reverse(ctx->iv); - uint8x16_t keystream = encrypt(counter, ctx->keysched_e); - uint8x16_t input = vld1q_u8(blk); - uint8x16_t output = veorq_u8(input, keystream); - vst1q_u8(blk, output); - ctx->iv = aes_neon_sdctr_increment(ctx->iv); - } -} - -#define NEON_ENC_DEC(len) \ - static FUNC_ISA void aes##len##_cbc_hw_encrypt( \ - ssh_cipher *ciph, void *vblk, int blklen) \ - { aes_cbc_neon_encrypt(ciph, vblk, blklen, aes_neon_##len##_e); } \ - static FUNC_ISA void aes##len##_cbc_hw_decrypt( \ - ssh_cipher *ciph, void *vblk, int blklen) \ - { aes_cbc_neon_decrypt(ciph, vblk, blklen, aes_neon_##len##_d); } \ - static FUNC_ISA void aes##len##_sdctr_hw( \ - ssh_cipher *ciph, void *vblk, int blklen) \ - { aes_sdctr_neon(ciph, vblk, blklen, aes_neon_##len##_e); } \ - -NEON_ENC_DEC(128) -NEON_ENC_DEC(192) -NEON_ENC_DEC(256) - -/* ---------------------------------------------------------------------- - * Stub functions if we have no hardware-accelerated AES. In this - * case, aes_hw_new returns NULL (though it should also never be - * selected by aes_select, so the only thing that should even be - * _able_ to call it is testcrypt). As a result, the remaining vtable - * functions should never be called at all. - */ - -#elif HW_AES == HW_AES_NONE - -bool aes_hw_available(void) -{ - return false; -} - -static ssh_cipher *aes_hw_new(const ssh_cipheralg *alg) -{ - return NULL; -} - -#define STUB_BODY { unreachable("Should never be called"); } - -static void aes_hw_free(ssh_cipher *ciph) STUB_BODY -static void aes_hw_setkey(ssh_cipher *ciph, const void *key) STUB_BODY -static void aes_hw_setiv_cbc(ssh_cipher *ciph, const void *iv) STUB_BODY -static void aes_hw_setiv_sdctr(ssh_cipher *ciph, const void *iv) STUB_BODY -#define STUB_ENC_DEC(len) \ - static void aes##len##_cbc_hw_encrypt( \ - ssh_cipher *ciph, void *vblk, int blklen) STUB_BODY \ - static void aes##len##_cbc_hw_decrypt( \ - ssh_cipher *ciph, void *vblk, int blklen) STUB_BODY \ - static void aes##len##_sdctr_hw( \ - ssh_cipher *ciph, void *vblk, int blklen) STUB_BODY - -STUB_ENC_DEC(128) -STUB_ENC_DEC(192) -STUB_ENC_DEC(256) - -#endif /* HW_AES */ diff --git a/ssharcf.c b/ssharcf.c deleted file mode 100644 index 53821473..00000000 --- a/ssharcf.c +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Arcfour (RC4) implementation for PuTTY. - * - * Coded from Schneier. - */ - -#include -#include "ssh.h" - -typedef struct { - unsigned char i, j, s[256]; - ssh_cipher ciph; -} ArcfourContext; - -static void arcfour_block(void *handle, void *vblk, int len) -{ - unsigned char *blk = (unsigned char *)vblk; - 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 ssh_cipher *arcfour_new(const ssh_cipheralg *alg) -{ - ArcfourContext *ctx = snew(ArcfourContext); - ctx->ciph.vt = alg; - return &ctx->ciph; -} - -static void arcfour_free(ssh_cipher *cipher) -{ - ArcfourContext *ctx = container_of(cipher, ArcfourContext, ciph); - smemclr(ctx, sizeof(*ctx)); - sfree(ctx); -} - -static void arcfour_stir(ArcfourContext *ctx) -{ - unsigned char *junk = snewn(1536, unsigned char); - memset(junk, 0, 1536); - arcfour_block(ctx, junk, 1536); - smemclr(junk, 1536); - sfree(junk); -} - -static void arcfour_ssh2_setiv(ssh_cipher *cipher, const void *key) -{ - /* As a pure stream cipher, Arcfour has no IV separate from the key */ -} - -static void arcfour_ssh2_setkey(ssh_cipher *cipher, const void *key) -{ - ArcfourContext *ctx = container_of(cipher, ArcfourContext, ciph); - arcfour_setkey(ctx, key, ctx->ciph.vt->padded_keybytes); - arcfour_stir(ctx); -} - -static void arcfour_ssh2_block(ssh_cipher *cipher, void *blk, int len) -{ - ArcfourContext *ctx = container_of(cipher, ArcfourContext, ciph); - arcfour_block(ctx, blk, len); -} - -const ssh_cipheralg ssh_arcfour128_ssh2 = { - .new = arcfour_new, - .free = arcfour_free, - .setiv = arcfour_ssh2_setiv, - .setkey = arcfour_ssh2_setkey, - .encrypt = arcfour_ssh2_block, - .decrypt = arcfour_ssh2_block, - .ssh2_id = "arcfour128", - .blksize = 1, - .real_keybits = 128, - .padded_keybytes = 16, - .flags = 0, - .text_name = "Arcfour-128", -}; - -const ssh_cipheralg ssh_arcfour256_ssh2 = { - .new = arcfour_new, - .free = arcfour_free, - .setiv = arcfour_ssh2_setiv, - .setkey = arcfour_ssh2_setkey, - .encrypt = arcfour_ssh2_block, - .decrypt = arcfour_ssh2_block, - .ssh2_id = "arcfour256", - .blksize = 1, - .real_keybits = 256, - .padded_keybytes = 32, - .flags = 0, - .text_name = "Arcfour-256", -}; - -static const ssh_cipheralg *const arcfour_list[] = { - &ssh_arcfour256_ssh2, - &ssh_arcfour128_ssh2, -}; - -const ssh2_ciphers ssh2_arcfour = { lenof(arcfour_list), arcfour_list }; diff --git a/sshargon2.c b/sshargon2.c deleted file mode 100644 index 25385d7e..00000000 --- a/sshargon2.c +++ /dev/null @@ -1,565 +0,0 @@ -/* - * Implementation of the Argon2 password hash function. - * - * My sources for the algorithm description and test vectors (the latter in - * test/cryptsuite.py) were the reference implementation on Github, and also - * the Internet-Draft description: - * - * https://github.com/P-H-C/phc-winner-argon2 - * https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-argon2-13 - */ - -#include - -#include "putty.h" -#include "ssh.h" -#include "marshal.h" - -/* ---------------------------------------------------------------------- - * Argon2 uses data marshalling rules similar to SSH but with 32-bit integers - * stored little-endian. Start with some local BinarySink routines for storing - * a uint32 and a string in that fashion. - */ - -static void BinarySink_put_uint32_le(BinarySink *bs, unsigned long val) -{ - unsigned char data[4]; - PUT_32BIT_LSB_FIRST(data, val); - bs->write(bs, data, sizeof(data)); -} - -static void BinarySink_put_stringpl_le(BinarySink *bs, ptrlen pl) -{ - /* Check that the string length fits in a uint32, without doing a - * potentially implementation-defined shift of more than 31 bits */ - assert((pl.len >> 31) < 2); - - BinarySink_put_uint32_le(bs, pl.len); - bs->write(bs, pl.ptr, pl.len); -} - -#define put_uint32_le(bs, val) \ - BinarySink_put_uint32_le(BinarySink_UPCAST(bs), val) -#define put_stringpl_le(bs, val) \ - BinarySink_put_stringpl_le(BinarySink_UPCAST(bs), val) - -/* ---------------------------------------------------------------------- - * Argon2 defines a hash-function family that's an extension of BLAKE2b to - * generate longer output digests, by repeatedly outputting half of a BLAKE2 - * hash output and then re-hashing the whole thing until there are 64 or fewer - * bytes left to output. The spec calls this H' (a variant of the original - * hash it calls H, which is the unmodified BLAKE2b). - */ - -static ssh_hash *hprime_new(unsigned length) -{ - ssh_hash *h = blake2b_new_general(length > 64 ? 64 : length); - put_uint32_le(h, length); - return h; -} - -static void hprime_final(ssh_hash *h, unsigned length, void *vout) -{ - uint8_t *out = (uint8_t *)vout; - - while (length > 64) { - uint8_t hashbuf[64]; - ssh_hash_final(h, hashbuf); - - memcpy(out, hashbuf, 32); - out += 32; - length -= 32; - - h = blake2b_new_general(length > 64 ? 64 : length); - put_data(h, hashbuf, 64); - - smemclr(hashbuf, sizeof(hashbuf)); - } - - ssh_hash_final(h, out); -} - -/* Externally visible entry point for the long hash function. This is only - * used by testcrypt, so it would be overkill to set it up like a proper - * ssh_hash. */ -strbuf *argon2_long_hash(unsigned length, ptrlen data) -{ - ssh_hash *h = hprime_new(length); - put_datapl(h, data); - strbuf *out = strbuf_new(); - hprime_final(h, length, strbuf_append(out, length)); - return out; -} - -/* ---------------------------------------------------------------------- - * Argon2's own mixing function G, which operates on 1Kb blocks of data. - * - * The definition of G in the spec takes two 1Kb blocks as input and produces - * a 1Kb output block. The first thing that happens to the input blocks is - * that they get XORed together, and then only the XOR output is used, so you - * could perfectly well regard G as a 1Kb->1Kb function. - */ - -static inline uint64_t ror(uint64_t x, unsigned rotation) -{ - unsigned lshift = 63 & -rotation, rshift = 63 & rotation; - return (x << lshift) | (x >> rshift); -} - -static inline uint64_t trunc32(uint64_t x) -{ - return x & 0xFFFFFFFF; -} - -/* Internal function similar to the BLAKE2b round, which mixes up four 64-bit - * words */ -static inline void GB(uint64_t *a, uint64_t *b, uint64_t *c, uint64_t *d) -{ - *a += *b + 2 * trunc32(*a) * trunc32(*b); - *d = ror(*d ^ *a, 32); - *c += *d + 2 * trunc32(*c) * trunc32(*d); - *b = ror(*b ^ *c, 24); - *a += *b + 2 * trunc32(*a) * trunc32(*b); - *d = ror(*d ^ *a, 16); - *c += *d + 2 * trunc32(*c) * trunc32(*d); - *b = ror(*b ^ *c, 63); -} - -/* Higher-level internal function which mixes up sixteen 64-bit words. This is - * applied to different subsets of the 128 words in a kilobyte block, and the - * API here is designed to make it easy to apply in the circumstances the spec - * requires. In every call, the sixteen words form eight pairs adjacent in - * memory, whose addresses are in arithmetic progression. So the 16 input - * words are in[0], in[1], in[instep], in[instep+1], ..., in[7*instep], - * in[7*instep+1], and the 16 output words similarly. */ -static inline void P(uint64_t *out, unsigned outstep, - uint64_t *in, unsigned instep) -{ - for (unsigned i = 0; i < 8; i++) { - out[i*outstep] = in[i*instep]; - out[i*outstep+1] = in[i*instep+1]; - } - - GB(out+0*outstep+0, out+2*outstep+0, out+4*outstep+0, out+6*outstep+0); - GB(out+0*outstep+1, out+2*outstep+1, out+4*outstep+1, out+6*outstep+1); - GB(out+1*outstep+0, out+3*outstep+0, out+5*outstep+0, out+7*outstep+0); - GB(out+1*outstep+1, out+3*outstep+1, out+5*outstep+1, out+7*outstep+1); - - GB(out+0*outstep+0, out+2*outstep+1, out+5*outstep+0, out+7*outstep+1); - GB(out+0*outstep+1, out+3*outstep+0, out+5*outstep+1, out+6*outstep+0); - GB(out+1*outstep+0, out+3*outstep+1, out+4*outstep+0, out+6*outstep+1); - GB(out+1*outstep+1, out+2*outstep+0, out+4*outstep+1, out+7*outstep+0); -} - -/* The full G function, taking input blocks X and Y. The result of G is most - * often XORed into an existing output block, so this API is designed with - * that in mind: the mixing function's output is always XORed into whatever - * 1Kb of data is already at 'out'. */ -static void G_xor(uint8_t *out, const uint8_t *X, const uint8_t *Y) -{ - uint64_t R[128], Q[128], Z[128]; - - for (unsigned i = 0; i < 128; i++) - R[i] = GET_64BIT_LSB_FIRST(X + 8*i) ^ GET_64BIT_LSB_FIRST(Y + 8*i); - - for (unsigned i = 0; i < 8; i++) - P(Q+16*i, 2, R+16*i, 2); - - for (unsigned i = 0; i < 8; i++) - P(Z+2*i, 16, Q+2*i, 16); - - for (unsigned i = 0; i < 128; i++) - PUT_64BIT_LSB_FIRST(out + 8*i, - GET_64BIT_LSB_FIRST(out + 8*i) ^ R[i] ^ Z[i]); - - smemclr(R, sizeof(R)); - smemclr(Q, sizeof(Q)); - smemclr(Z, sizeof(Z)); -} - -/* ---------------------------------------------------------------------- - * The main Argon2 function. - */ - -static void argon2_internal(uint32_t p, uint32_t T, uint32_t m, uint32_t t, - uint32_t y, ptrlen P, ptrlen S, ptrlen K, ptrlen X, - uint8_t *out) -{ - /* - * Start by hashing all the input data together: the four string arguments - * (password P, salt S, optional secret key K, optional associated data - * X), plus all the parameters for the function's memory and time usage. - * - * The output of this hash is the sole input to the subsequent mixing - * step: Argon2 does not preserve any more entropy from the inputs, it - * just makes it extra painful to get the final answer. - */ - uint8_t h0[64]; - { - ssh_hash *h = blake2b_new_general(64); - put_uint32_le(h, p); - put_uint32_le(h, T); - put_uint32_le(h, m); - put_uint32_le(h, t); - put_uint32_le(h, 0x13); /* hash function version number */ - put_uint32_le(h, y); - put_stringpl_le(h, P); - put_stringpl_le(h, S); - put_stringpl_le(h, K); - put_stringpl_le(h, X); - ssh_hash_final(h, h0); - } - - struct blk { uint8_t data[1024]; }; - - /* - * Array of 1Kb blocks. The total size is (approximately) m, the - * caller-specified parameter for how much memory to use; the blocks are - * regarded as a rectangular array of p rows ('lanes') by q columns, where - * p is the 'parallelism' input parameter (the lanes can be processed - * concurrently up to a point) and q is whatever makes the product pq come - * to m. - * - * Additionally, each row is divided into four equal 'segments', which are - * important to the way the algorithm decides which blocks to use as input - * to each step of the function. - * - * The term 'slice' refers to a whole set of vertically aligned segments, - * i.e. slice 0 is the whole left quarter of the array, and slice 3 the - * whole right quarter. - */ - size_t SL = m / (4*p); /* segment length: # of 1Kb blocks in a segment */ - size_t q = 4 * SL; /* width of the array: 4 segments times SL */ - size_t mprime = q * p; /* total size of the array, approximately m */ - - /* Allocate the memory. */ - struct blk *B = snewn(mprime, struct blk); - memset(B, 0, mprime * sizeof(struct blk)); - - /* - * Initial setup: fill the first two full columns of the array with data - * expanded from the starting hash h0. Each block is the result of using - * the long-output hash function H' to hash h0 itself plus the block's - * coordinates in the array. - */ - for (size_t i = 0; i < p; i++) { - ssh_hash *h = hprime_new(1024); - put_data(h, h0, 64); - put_uint32_le(h, 0); - put_uint32_le(h, i); - hprime_final(h, 1024, B[i].data); - } - for (size_t i = 0; i < p; i++) { - ssh_hash *h = hprime_new(1024); - put_data(h, h0, 64); - put_uint32_le(h, 1); - put_uint32_le(h, i); - hprime_final(h, 1024, B[i+p].data); - } - - /* - * Declarations for the main loop. - * - * The basic structure of the main loop is going to involve processing the - * array one whole slice (vertically divided quarter) at a time. Usually - * we'll write a new value into every single block in the slice, except - * that in the initial slice on the first pass, we've already written - * values into the first two columns during the initial setup above. So - * 'jstart' indicates the starting index in each segment we process; it - * starts off as 2 so that we don't overwrite the inital setup, and then - * after the first slice is done, we set it to 0, and it stays there. - * - * d_mode indicates whether we're being data-dependent (true) or - * data-independent (false). In the hybrid Argon2id mode, we start off - * independent, and then once we've mixed things up enough, switch over to - * dependent mode to force long serial chains of computation. - */ - size_t jstart = 2; - bool d_mode = (y == 0); - struct blk out2i, tmp2i, in2i; - - /* Outermost loop: t whole passes from left to right over the array */ - for (size_t pass = 0; pass < t; pass++) { - - /* Within that, we process the array in its four main slices */ - for (unsigned slice = 0; slice < 4; slice++) { - - /* In Argon2id mode, if we're half way through the first pass, - * this is the moment to switch d_mode from false to true */ - if (pass == 0 && slice == 2 && y == 2) - d_mode = true; - - /* Loop over every segment in the slice (i.e. every row). So i is - * the y-coordinate of each block we process. */ - for (size_t i = 0; i < p; i++) { - - /* And within that segment, process the blocks from left to - * right, starting at 'jstart' (usually 0, but 2 in the first - * slice). */ - for (size_t jpre = jstart; jpre < SL; jpre++) { - - /* j is the x-coordinate of each block we process, made up - * of the slice number and the index 'jpre' within the - * segment. */ - size_t j = slice * SL + jpre; - - /* jm1 is j-1 (mod q) */ - uint32_t jm1 = (j == 0 ? q-1 : j-1); - - /* - * Construct two 32-bit pseudorandom integers J1 and J2. - * This is the part of the algorithm that varies between - * the data-dependent and independent modes. - */ - uint32_t J1, J2; - if (d_mode) { - /* - * Data-dependent: grab the first 64 bits of the block - * to the left of this one. - */ - J1 = GET_32BIT_LSB_FIRST(B[i + p * jm1].data); - J2 = GET_32BIT_LSB_FIRST(B[i + p * jm1].data + 4); - } else { - /* - * Data-independent: generate pseudorandom data by - * hashing a sequence of preimage blocks that include - * all our input parameters, plus the coordinates of - * this point in the algorithm (array position and - * pass number) to make all the hash outputs distinct. - * - * The hash we use is G itself, applied twice. So we - * generate 1Kb of data at a time, which is enough for - * 128 (J1,J2) pairs. Hence we only need to do the - * hashing if our index within the segment is a - * multiple of 128, or if we're at the very start of - * the algorithm (in which case we started at 2 rather - * than 0). After that we can just keep picking data - * out of our most recent hash output. - */ - if (jpre == jstart || jpre % 128 == 0) { - /* - * Hash preimage is mostly zeroes, with a - * collection of assorted integer values we had - * anyway. - */ - memset(in2i.data, 0, sizeof(in2i.data)); - PUT_64BIT_LSB_FIRST(in2i.data + 0, pass); - PUT_64BIT_LSB_FIRST(in2i.data + 8, i); - PUT_64BIT_LSB_FIRST(in2i.data + 16, slice); - PUT_64BIT_LSB_FIRST(in2i.data + 24, mprime); - PUT_64BIT_LSB_FIRST(in2i.data + 32, t); - PUT_64BIT_LSB_FIRST(in2i.data + 40, y); - PUT_64BIT_LSB_FIRST(in2i.data + 48, jpre / 128 + 1); - - /* - * Now apply G twice to generate the hash output - * in out2i. - */ - memset(tmp2i.data, 0, sizeof(tmp2i.data)); - G_xor(tmp2i.data, tmp2i.data, in2i.data); - memset(out2i.data, 0, sizeof(out2i.data)); - G_xor(out2i.data, out2i.data, tmp2i.data); - } - - /* - * Extract J1 and J2 from the most recent hash output - * (whether we've just computed it or not). - */ - J1 = GET_32BIT_LSB_FIRST( - out2i.data + 8 * (jpre % 128)); - J2 = GET_32BIT_LSB_FIRST( - out2i.data + 8 * (jpre % 128) + 4); - } - - /* - * Now convert J1 and J2 into the index of an existing - * block of the array to use as input to this step. This - * is fairly fiddly. - * - * The easy part: the y-coordinate of the input block is - * obtained by reducing J2 mod p, except that at the very - * start of the algorithm (processing the first slice on - * the first pass) we simply use the same y-coordinate as - * our output block. - * - * Note that it's safe to use the ordinary % operator - * here, without any concern for timing side channels: in - * data-independent mode J2 is not correlated to any - * secrets, and in data-dependent mode we're going to be - * giving away side-channel data _anyway_ when we use it - * as an array index (and by assumption we don't care, - * because it's already massively randomised from the real - * inputs). - */ - uint32_t index_l = (pass == 0 && slice == 0) ? i : J2 % p; - - /* - * The hard part: which block in this array row do we use? - * - * First, we decide what the possible candidates are. This - * requires some case analysis, and depends on whether the - * array row is the same one we're writing into or not. - * - * If it's not the same row: we can't use any block from - * the current slice (because the segments within a slice - * have to be processable in parallel, so in a concurrent - * implementation those blocks are potentially in the - * process of being overwritten by other threads). But the - * other three slices are fair game, except that in the - * first pass, slices to the right of us won't have had - * any values written into them yet at all. - * - * If it is the same row, we _are_ allowed to use blocks - * from the current slice, but only the ones before our - * current position. - * - * In both cases, we also exclude the individual _column_ - * just to the left of the current one. (The block - * immediately to our left is going to be the _other_ - * input to G, but the spec also says that we avoid that - * column even in a different row.) - * - * All of this means that we end up choosing from a - * cyclically contiguous interval of blocks within this - * lane, but the start and end points require some thought - * to get them right. - */ - - /* Start position is the beginning of the _next_ slice - * (containing data from the previous pass), unless we're - * on pass 0, where the start position has to be 0. */ - uint32_t Wstart = (pass == 0 ? 0 : (slice + 1) % 4 * SL); - - /* End position splits up by cases. */ - uint32_t Wend; - if (index_l == i) { - /* Same lane as output: we can use anything up to (but - * not including) the block immediately left of us. */ - Wend = jm1; - } else { - /* Different lane from output: we can use anything up - * to the previous slice boundary, or one less than - * that if we're at the very left edge of our slice - * right now. */ - Wend = SL * slice; - if (jpre == 0) - Wend = (Wend + q-1) % q; - } - - /* Total number of blocks available to choose from */ - uint32_t Wsize = (Wend + q - Wstart) % q; - - /* Fiddly computation from the spec that chooses from the - * available blocks, in a deliberately non-uniform - * fashion, using J1 as pseudorandom input data. Output is - * zz which is the index within our contiguous interval. */ - uint32_t x = ((uint64_t)J1 * J1) >> 32; - uint32_t y = ((uint64_t)Wsize * x) >> 32; - uint32_t zz = Wsize - 1 - y; - - /* And index_z is the actual x coordinate of the block we - * want. */ - uint32_t index_z = (Wstart + zz) % q; - - /* Phew! Combine that block with the one immediately to - * our left, and XOR over the top of whatever is already - * in our current output block. */ - G_xor(B[i + p * j].data, B[i + p * jm1].data, - B[index_l + p * index_z].data); - } - } - - /* We've finished processing a slice. Reset jstart to 0. It will - * onily _not_ have been 0 if this was pass 0 slice 0, in which - * case it still had its initial value of 2 to avoid the starting - * data. */ - jstart = 0; - } - } - - /* - * The main output is all done. Final output works by taking the XOR of - * all the blocks in the rightmost column of the array, and then using - * that as input to our long hash H'. The output of _that_ is what we - * deliver to the caller. - */ - - struct blk C = B[p * (q-1)]; - for (size_t i = 1; i < p; i++) - memxor(C.data, C.data, B[i + p * (q-1)].data, 1024); - - { - ssh_hash *h = hprime_new(T); - put_data(h, C.data, 1024); - hprime_final(h, T, out); - } - - /* - * Clean up. - */ - smemclr(out2i.data, sizeof(out2i.data)); - smemclr(tmp2i.data, sizeof(tmp2i.data)); - smemclr(in2i.data, sizeof(in2i.data)); - smemclr(C.data, sizeof(C.data)); - smemclr(B, mprime * sizeof(struct blk)); - sfree(B); -} - -/* - * Wrapper function that appends to a strbuf (which sshpubk.c will want). - */ -void argon2(Argon2Flavour flavour, uint32_t mem, uint32_t passes, - uint32_t parallel, uint32_t taglen, - ptrlen P, ptrlen S, ptrlen K, ptrlen X, strbuf *out) -{ - argon2_internal(parallel, taglen, mem, passes, flavour, - P, S, K, X, strbuf_append(out, taglen)); -} - -/* - * Wrapper function which dynamically chooses the number of passes to run in - * order to hit an approximate total amount of CPU time. Writes the result - * into 'passes'. - */ -void argon2_choose_passes( - Argon2Flavour flavour, uint32_t mem, - uint32_t milliseconds, uint32_t *passes, - uint32_t parallel, uint32_t taglen, - ptrlen P, ptrlen S, ptrlen K, ptrlen X, - strbuf *out) -{ - unsigned long desired_time = (TICKSPERSEC * milliseconds) / 1000; - - /* - * We only need the time taken to be approximately right, so we - * scale up the number of passes geometrically, which avoids - * taking O(t^2) time to find a pass count taking time t. - * - * Using the Fibonacci numbers is slightly nicer than the obvious - * approach of powers of 2, because it's still very easy to - * compute, and grows less fast (powers of 1.6 instead of 2), so - * you get just a touch more precision. - */ - uint32_t a = 1, b = 1; - - while (true) { - unsigned long start_time = GETTICKCOUNT(); - argon2(flavour, mem, b, parallel, taglen, P, S, K, X, out); - unsigned long ticks = GETTICKCOUNT() - start_time; - - /* But just in case computers get _too_ fast, we have to cap - * the growth before it gets past the uint32_t upper bound! So - * if computing a+b would overflow, stop here. */ - - if (ticks >= desired_time || a > (uint32_t)~b) { - *passes = b; - return; - } else { - strbuf_clear(out); - - /* Next Fibonacci number: replace (a, b) with (b, a+b) */ - b += a; - a = b - a; - } - } -} diff --git a/sshauxcrypt.c b/sshauxcrypt.c deleted file mode 100644 index 7f64b27a..00000000 --- a/sshauxcrypt.c +++ /dev/null @@ -1,166 +0,0 @@ -/* - * sshauxcrypt.c: wrapper functions on crypto primitives for use in - * other contexts than the main SSH packet protocol, such as - * encrypting private key files and performing XDM-AUTHORIZATION-1. - * - * These all work through the standard cipher/hash/MAC APIs, so they - * don't need to live in the same actual source files as the ciphers - * they wrap, and I think it keeps things tidier to have them out of - * the way here instead. - */ - -#include "ssh.h" - -static ssh_cipher *aes256_pubkey_cipher(const void *key, const void *iv) -{ - /* - * PuTTY's own .PPK format for SSH-2 private key files is - * encrypted with 256-bit AES in CBC mode. - */ - ssh_cipher *cipher = ssh_cipher_new(&ssh_aes256_cbc); - ssh_cipher_setkey(cipher, key); - ssh_cipher_setiv(cipher, iv); - return cipher; -} - -void aes256_encrypt_pubkey(const void *key, const void *iv, void *blk, int len) -{ - ssh_cipher *c = aes256_pubkey_cipher(key, iv); - ssh_cipher_encrypt(c, blk, len); - ssh_cipher_free(c); -} - -void aes256_decrypt_pubkey(const void *key, const void *iv, void *blk, int len) -{ - ssh_cipher *c = aes256_pubkey_cipher(key, iv); - ssh_cipher_decrypt(c, blk, len); - ssh_cipher_free(c); -} - -static ssh_cipher *des3_pubkey_cipher(const void *vkey) -{ - /* - * SSH-1 private key files are encrypted with triple-DES in SSH-1 - * style (three separate CBC layers), but the same key is used for - * the first and third layers. - */ - ssh_cipher *c = ssh_cipher_new(&ssh_3des_ssh1); - uint8_t keys3[24], iv[8]; - - memcpy(keys3, vkey, 16); - memcpy(keys3 + 16, vkey, 8); - ssh_cipher_setkey(c, keys3); - smemclr(keys3, sizeof(keys3)); - - memset(iv, 0, 8); - ssh_cipher_setiv(c, iv); - - return c; -} - -void des3_decrypt_pubkey(const void *vkey, void *vblk, int len) -{ - ssh_cipher *c = des3_pubkey_cipher(vkey); - ssh_cipher_decrypt(c, vblk, len); - ssh_cipher_free(c); -} - -void des3_encrypt_pubkey(const void *vkey, void *vblk, int len) -{ - ssh_cipher *c = des3_pubkey_cipher(vkey); - ssh_cipher_encrypt(c, vblk, len); - ssh_cipher_free(c); -} - -static ssh_cipher *des3_pubkey_ossh_cipher(const void *vkey, const void *viv) -{ - /* - * OpenSSH PEM private key files are encrypted with triple-DES in - * SSH-2 style (one CBC layer), with three distinct keys, and an - * IV also generated from the passphrase. - */ - ssh_cipher *c = ssh_cipher_new(&ssh_3des_ssh2); - ssh_cipher_setkey(c, vkey); - ssh_cipher_setiv(c, viv); - return c; -} - -void des3_decrypt_pubkey_ossh(const void *vkey, const void *viv, - void *vblk, int len) -{ - ssh_cipher *c = des3_pubkey_ossh_cipher(vkey, viv); - ssh_cipher_decrypt(c, vblk, len); - ssh_cipher_free(c); -} - -void des3_encrypt_pubkey_ossh(const void *vkey, const void *viv, - void *vblk, int len) -{ - ssh_cipher *c = des3_pubkey_ossh_cipher(vkey, viv); - ssh_cipher_encrypt(c, vblk, len); - ssh_cipher_free(c); -} - -static ssh_cipher *des_xdmauth_cipher(const void *vkeydata) -{ - /* - * XDM-AUTHORIZATION-1 uses single-DES, but packs the key into 7 - * bytes, so here we have to repack it manually into the canonical - * form where it occupies 8 bytes each with the low bit unused. - */ - const unsigned char *keydata = (const unsigned char *)vkeydata; - 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; - } - - ssh_cipher *c = ssh_cipher_new(&ssh_des); - ssh_cipher_setkey(c, key); - smemclr(key, sizeof(key)); - ssh_cipher_setiv(c, key); - return c; -} - -void des_encrypt_xdmauth(const void *keydata, void *blk, int len) -{ - ssh_cipher *c = des_xdmauth_cipher(keydata); - ssh_cipher_encrypt(c, blk, len); - ssh_cipher_free(c); -} - -void des_decrypt_xdmauth(const void *keydata, void *blk, int len) -{ - ssh_cipher *c = des_xdmauth_cipher(keydata); - ssh_cipher_decrypt(c, blk, len); - ssh_cipher_free(c); -} - -void hash_simple(const ssh_hashalg *alg, ptrlen data, void *output) -{ - ssh_hash *hash = ssh_hash_new(alg); - put_datapl(hash, data); - ssh_hash_final(hash, output); -} - -void mac_simple(const ssh2_macalg *alg, ptrlen key, ptrlen data, void *output) -{ - ssh2_mac *mac = ssh2_mac_new(alg, NULL); - ssh2_mac_setkey(mac, key); - ssh2_mac_start(mac); - put_datapl(mac, data); - ssh2_mac_genresult(mac, output); - ssh2_mac_free(mac); -} diff --git a/sshbcrypt.c b/sshbcrypt.c deleted file mode 100644 index 7cdd44ed..00000000 --- a/sshbcrypt.c +++ /dev/null @@ -1,119 +0,0 @@ -/* - * 'bcrypt' password hash function, for PuTTY's import/export of - * OpenSSH encrypted private key files. - * - * This is not really the same as the original bcrypt; OpenSSH has - * modified it in various ways, and of course we have to do the same. - */ - -#include -#include -#include "ssh.h" -#include "sshblowf.h" - -BlowfishContext *bcrypt_setup(const unsigned char *key, int keybytes, - const unsigned char *salt, int saltbytes) -{ - int i; - BlowfishContext *ctx; - - ctx = blowfish_make_context(); - blowfish_initkey(ctx); - blowfish_expandkey(ctx, key, keybytes, salt, saltbytes); - - /* Original bcrypt replaces this fixed loop count with the - * variable cost. OpenSSH instead iterates the whole thing more - * than once if it wants extra rounds. */ - for (i = 0; i < 64; i++) { - blowfish_expandkey(ctx, salt, saltbytes, NULL, 0); - blowfish_expandkey(ctx, key, keybytes, NULL, 0); - } - - return ctx; -} - -void bcrypt_hash(const unsigned char *key, int keybytes, - const unsigned char *salt, int saltbytes, - unsigned char output[32]) -{ - BlowfishContext *ctx; - int i; - - ctx = bcrypt_setup(key, keybytes, salt, saltbytes); - /* This was quite a nice starting string until it ran into - * little-endian Blowfish :-/ */ - memcpy(output, "cyxOmorhcitawolBhsiftawSanyDetim", 32); - for (i = 0; i < 64; i++) { - blowfish_lsb_encrypt_ecb(output, 32, ctx); - } - blowfish_free_context(ctx); -} - -void bcrypt_genblock(int counter, - const unsigned char hashed_passphrase[64], - const unsigned char *salt, int saltbytes, - unsigned char output[32]) -{ - unsigned char hashed_salt[64]; - - /* Hash the input salt with the counter value optionally suffixed - * to get our real 32-byte salt */ - ssh_hash *h = ssh_hash_new(&ssh_sha512); - put_data(h, salt, saltbytes); - if (counter) - put_uint32(h, counter); - ssh_hash_final(h, hashed_salt); - - bcrypt_hash(hashed_passphrase, 64, hashed_salt, 64, output); - - smemclr(&hashed_salt, sizeof(hashed_salt)); -} - -void openssh_bcrypt(const char *passphrase, - const unsigned char *salt, int saltbytes, - int rounds, unsigned char *out, int outbytes) -{ - unsigned char hashed_passphrase[64]; - unsigned char block[32], outblock[32]; - const unsigned char *thissalt; - int thissaltbytes; - int modulus, residue, i, j, round; - - /* Hash the passphrase to get the bcrypt key material */ - hash_simple(&ssh_sha512, ptrlen_from_asciz(passphrase), hashed_passphrase); - - /* We output key bytes in a scattered fashion to meld all output - * key blocks into all parts of the output. To do this, we pick a - * modulus, and we output the key bytes to indices of out[] in the - * following order: first the indices that are multiples of the - * modulus, then the ones congruent to 1 mod modulus, etc. Each of - * those passes consumes exactly one block output from - * bcrypt_genblock, so we must pick a modulus large enough that at - * most 32 bytes are used in the pass. */ - modulus = (outbytes + 31) / 32; - - for (residue = 0; residue < modulus; residue++) { - /* Our output block of data is the XOR of all blocks generated - * by bcrypt in the following loop */ - memset(outblock, 0, sizeof(outblock)); - - thissalt = salt; - thissaltbytes = saltbytes; - for (round = 0; round < rounds; round++) { - bcrypt_genblock(round == 0 ? residue+1 : 0, - hashed_passphrase, - thissalt, thissaltbytes, block); - /* Each subsequent bcrypt call reuses the previous one's - * output as its salt */ - thissalt = block; - thissaltbytes = 32; - - for (i = 0; i < 32; i++) - outblock[i] ^= block[i]; - } - - for (i = residue, j = 0; i < outbytes; i += modulus, j++) - out[i] = outblock[j]; - } - smemclr(&hashed_passphrase, sizeof(hashed_passphrase)); -} diff --git a/sshblake2.c b/sshblake2.c deleted file mode 100644 index a4d42f21..00000000 --- a/sshblake2.c +++ /dev/null @@ -1,223 +0,0 @@ -/* - * BLAKE2 (RFC 7693) implementation for PuTTY. - * - * The BLAKE2 hash family includes BLAKE2s, in which the hash state is - * operated on as a collection of 32-bit integers, and BLAKE2b, based - * on 64-bit integers. At present this code implements BLAKE2b only. - */ - -#include -#include "ssh.h" - -static inline uint64_t ror(uint64_t x, unsigned rotation) -{ - unsigned lshift = 63 & -rotation, rshift = 63 & rotation; - return (x << lshift) | (x >> rshift); -} - -/* RFC 7963 section 2.1 */ -enum { R1 = 32, R2 = 24, R3 = 16, R4 = 63 }; - -/* RFC 7693 section 2.6 */ -static const uint64_t iv[] = { - 0x6a09e667f3bcc908, /* floor(2^64 * frac(sqrt(2))) */ - 0xbb67ae8584caa73b, /* floor(2^64 * frac(sqrt(3))) */ - 0x3c6ef372fe94f82b, /* floor(2^64 * frac(sqrt(5))) */ - 0xa54ff53a5f1d36f1, /* floor(2^64 * frac(sqrt(7))) */ - 0x510e527fade682d1, /* floor(2^64 * frac(sqrt(11))) */ - 0x9b05688c2b3e6c1f, /* floor(2^64 * frac(sqrt(13))) */ - 0x1f83d9abfb41bd6b, /* floor(2^64 * frac(sqrt(17))) */ - 0x5be0cd19137e2179, /* floor(2^64 * frac(sqrt(19))) */ -}; - -/* RFC 7693 section 2.7 */ -static const unsigned char sigma[][16] = { - { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, - {14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3}, - {11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4}, - { 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8}, - { 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13}, - { 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9}, - {12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11}, - {13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10}, - { 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5}, - {10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0}, - /* This array recycles if you have more than 10 rounds. BLAKE2b - * has 12, so we repeat the first two rows again. */ - { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, - {14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3}, -}; - -static inline void g_half(uint64_t v[16], unsigned a, unsigned b, unsigned c, - unsigned d, uint64_t x, unsigned r1, unsigned r2) -{ - v[a] += v[b] + x; - v[d] ^= v[a]; - v[d] = ror(v[d], r1); - v[c] += v[d]; - v[b] ^= v[c]; - v[b] = ror(v[b], r2); -} - -static inline void g(uint64_t v[16], unsigned a, unsigned b, unsigned c, - unsigned d, uint64_t x, uint64_t y) -{ - g_half(v, a, b, c, d, x, R1, R2); - g_half(v, a, b, c, d, y, R3, R4); -} - -static inline void f(uint64_t h[8], uint64_t m[16], uint64_t offset_hi, - uint64_t offset_lo, unsigned final) -{ - uint64_t v[16]; - memcpy(v, h, 8 * sizeof(*v)); - memcpy(v + 8, iv, 8 * sizeof(*v)); - v[12] ^= offset_lo; - v[13] ^= offset_hi; - v[14] ^= -(uint64_t)final; - for (unsigned round = 0; round < 12; round++) { - const unsigned char *s = sigma[round]; - g(v, 0, 4, 8, 12, m[s[ 0]], m[s[ 1]]); - g(v, 1, 5, 9, 13, m[s[ 2]], m[s[ 3]]); - g(v, 2, 6, 10, 14, m[s[ 4]], m[s[ 5]]); - g(v, 3, 7, 11, 15, m[s[ 6]], m[s[ 7]]); - g(v, 0, 5, 10, 15, m[s[ 8]], m[s[ 9]]); - g(v, 1, 6, 11, 12, m[s[10]], m[s[11]]); - g(v, 2, 7, 8, 13, m[s[12]], m[s[13]]); - g(v, 3, 4, 9, 14, m[s[14]], m[s[15]]); - } - for (unsigned i = 0; i < 8; i++) - h[i] ^= v[i] ^ v[i+8]; - smemclr(v, sizeof(v)); -} - -static inline void f_outer(uint64_t h[8], uint8_t blk[128], uint64_t offset_hi, - uint64_t offset_lo, unsigned final) -{ - uint64_t m[16]; - for (unsigned i = 0; i < 16; i++) - m[i] = GET_64BIT_LSB_FIRST(blk + 8*i); - f(h, m, offset_hi, offset_lo, final); - smemclr(m, sizeof(m)); -} - -typedef struct blake2b { - uint64_t h[8]; - unsigned hashlen; - - uint8_t block[128]; - size_t used; - uint64_t lenhi, lenlo; - - BinarySink_IMPLEMENTATION; - ssh_hash hash; -} blake2b; - -static void blake2b_write(BinarySink *bs, const void *vp, size_t len); - -static ssh_hash *blake2b_new_inner(unsigned hashlen) -{ - assert(hashlen <= ssh_blake2b.hlen); - - blake2b *s = snew(blake2b); - s->hash.vt = &ssh_blake2b; - s->hashlen = hashlen; - BinarySink_INIT(s, blake2b_write); - BinarySink_DELEGATE_INIT(&s->hash, s); - return &s->hash; -} - -static ssh_hash *blake2b_new(const ssh_hashalg *alg) -{ - return blake2b_new_inner(alg->hlen); -} - -ssh_hash *blake2b_new_general(unsigned hashlen) -{ - ssh_hash *h = blake2b_new_inner(hashlen); - ssh_hash_reset(h); - return h; -} - -static void blake2b_reset(ssh_hash *hash) -{ - blake2b *s = container_of(hash, blake2b, hash); - - /* Initialise the hash to the standard IV */ - memcpy(s->h, iv, sizeof(s->h)); - - /* XOR in the parameters: secret key length (here always 0) in - * byte 1, and hash length in byte 0. */ - s->h[0] ^= 0x01010000 ^ s->hashlen; - - s->used = 0; - s->lenhi = s->lenlo = 0; -} - -static void blake2b_copyfrom(ssh_hash *hcopy, ssh_hash *horig) -{ - blake2b *copy = container_of(hcopy, blake2b, hash); - blake2b *orig = container_of(horig, blake2b, hash); - - memcpy(copy, orig, sizeof(*copy)); - BinarySink_COPIED(copy); - BinarySink_DELEGATE_INIT(©->hash, copy); -} - -static void blake2b_free(ssh_hash *hash) -{ - blake2b *s = container_of(hash, blake2b, hash); - - smemclr(s, sizeof(*s)); - sfree(s); -} - -static void blake2b_write(BinarySink *bs, const void *vp, size_t len) -{ - blake2b *s = BinarySink_DOWNCAST(bs, blake2b); - const uint8_t *p = vp; - - while (len > 0) { - if (s->used == sizeof(s->block)) { - f_outer(s->h, s->block, s->lenhi, s->lenlo, 0); - s->used = 0; - } - - size_t chunk = sizeof(s->block) - s->used; - if (chunk > len) - chunk = len; - - memcpy(s->block + s->used, p, chunk); - s->used += chunk; - p += chunk; - len -= chunk; - - s->lenlo += chunk; - s->lenhi += (s->lenlo < chunk); - } -} - -static void blake2b_digest(ssh_hash *hash, uint8_t *digest) -{ - blake2b *s = container_of(hash, blake2b, hash); - - memset(s->block + s->used, 0, sizeof(s->block) - s->used); - f_outer(s->h, s->block, s->lenhi, s->lenlo, 1); - - uint8_t hash_pre[128]; - for (unsigned i = 0; i < 8; i++) - PUT_64BIT_LSB_FIRST(hash_pre + 8*i, s->h[i]); - memcpy(digest, hash_pre, s->hashlen); - smemclr(hash_pre, sizeof(hash_pre)); -} - -const ssh_hashalg ssh_blake2b = { - .new = blake2b_new, - .reset = blake2b_reset, - .copyfrom = blake2b_copyfrom, - .digest = blake2b_digest, - .free = blake2b_free, - .hlen = 64, - .blocklen = 128, - HASHALG_NAMES_BARE("BLAKE2b-64"), -}; diff --git a/sshblowf.c b/sshblowf.c deleted file mode 100644 index c74f06c0..00000000 --- a/sshblowf.c +++ /dev/null @@ -1,699 +0,0 @@ -/* - * Blowfish implementation for PuTTY. - * - * Coded from scratch from the algorithm description. - */ - -#include -#include -#include "ssh.h" -#include "sshblowf.h" - -struct BlowfishContext { - uint32_t S0[256], S1[256], S2[256], S3[256], P[18]; - uint32_t iv0, iv1; /* for CBC mode */ -}; - -/* - * The Blowfish init data: hex digits of the fractional part of pi. - * (ie pi as a hex fraction is 3.243F6A8885A308D3...) - * - * If you have Simon Tatham's 'spigot' exact real calculator - * available, or any other method of generating 8336 fractional hex - * digits of pi on standard output, you can regenerate these tables - * exactly as below using the following Perl script (adjusting the - * first line or two if your pi-generator is not spigot). - -open my $spig, "spigot -n -B16 -d8336 pi |"; -read $spig, $ignore, 2; # throw away the leading "3." -for my $name ("parray", "sbox0".."sbox3") { - print "static const uint32_t ${name}[] = {\n"; - my $len = $name eq "parray" ? 18 : 256; - for my $i (1..$len) { - read $spig, $word, 8; - printf "%s0x%s,", ($i%6==1 ? " " : " "), uc $word; - print "\n" if ($i == $len || $i%6 == 0); - } - print "};\n\n"; -} -close $spig; - - */ -static const uint32_t parray[] = { - 0x243F6A88, 0x85A308D3, 0x13198A2E, 0x03707344, 0xA4093822, 0x299F31D0, - 0x082EFA98, 0xEC4E6C89, 0x452821E6, 0x38D01377, 0xBE5466CF, 0x34E90C6C, - 0xC0AC29B7, 0xC97C50DD, 0x3F84D5B5, 0xB5470917, 0x9216D5D9, 0x8979FB1B, -}; - -static const uint32_t 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 uint32_t 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 uint32_t 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 uint32_t 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(uint32_t xL, uint32_t xR, uint32_t *output, - BlowfishContext * ctx) -{ - uint32_t *S0 = ctx->S0; - uint32_t *S1 = ctx->S1; - uint32_t *S2 = ctx->S2; - uint32_t *S3 = ctx->S3; - uint32_t *P = ctx->P; - uint32_t 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(uint32_t xL, uint32_t xR, uint32_t *output, - BlowfishContext * ctx) -{ - uint32_t *S0 = ctx->S0; - uint32_t *S1 = ctx->S1; - uint32_t *S2 = ctx->S2; - uint32_t *S3 = ctx->S3; - uint32_t *P = ctx->P; - uint32_t 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) -{ - uint32_t 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; -} - -void blowfish_lsb_encrypt_ecb(void *vblk, int len, BlowfishContext * ctx) -{ - unsigned char *blk = (unsigned char *)vblk; - uint32_t xL, xR, out[2]; - - assert((len & 7) == 0); - - while (len > 0) { - xL = GET_32BIT_LSB_FIRST(blk); - xR = GET_32BIT_LSB_FIRST(blk + 4); - blowfish_encrypt(xL, xR, out, ctx); - PUT_32BIT_LSB_FIRST(blk, out[0]); - PUT_32BIT_LSB_FIRST(blk + 4, out[1]); - blk += 8; - len -= 8; - } -} - -static void blowfish_lsb_decrypt_cbc(unsigned char *blk, int len, - BlowfishContext * ctx) -{ - uint32_t 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) -{ - uint32_t 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) -{ - uint32_t 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) -{ - uint32_t 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; -} - -void blowfish_initkey(BlowfishContext *ctx) -{ - int i; - - for (i = 0; i < 18; i++) { - ctx->P[i] = parray[i]; - } - - for (i = 0; i < 256; i++) { - ctx->S0[i] = sbox0[i]; - ctx->S1[i] = sbox1[i]; - ctx->S2[i] = sbox2[i]; - ctx->S3[i] = sbox3[i]; - } -} - -void blowfish_expandkey(BlowfishContext * ctx, - const void *vkey, short keybytes, - const void *vsalt, short saltbytes) -{ - const unsigned char *key = (const unsigned char *)vkey; - const unsigned char *salt = (const unsigned char *)vsalt; - uint32_t *S0 = ctx->S0; - uint32_t *S1 = ctx->S1; - uint32_t *S2 = ctx->S2; - uint32_t *S3 = ctx->S3; - uint32_t *P = ctx->P; - uint32_t str[2]; - int i, j; - int saltpos; - unsigned char dummysalt[1]; - - saltpos = 0; - if (!salt) { - saltbytes = 1; - salt = dummysalt; - dummysalt[0] = 0; - } - - for (i = 0; i < 18; i++) { - P[i] ^= - ((uint32_t) (unsigned char) (key[(i * 4 + 0) % keybytes])) << 24; - P[i] ^= - ((uint32_t) (unsigned char) (key[(i * 4 + 1) % keybytes])) << 16; - P[i] ^= - ((uint32_t) (unsigned char) (key[(i * 4 + 2) % keybytes])) << 8; - P[i] ^= ((uint32_t) (unsigned char) (key[(i * 4 + 3) % keybytes])); - } - - str[0] = str[1] = 0; - - for (i = 0; i < 18; i += 2) { - for (j = 0; j < 8; j++) - str[j/4] ^= ((uint32_t)salt[saltpos++ % saltbytes]) << (24-8*(j%4)); - - blowfish_encrypt(str[0], str[1], str, ctx); - P[i] = str[0]; - P[i + 1] = str[1]; - } - - for (i = 0; i < 256; i += 2) { - for (j = 0; j < 8; j++) - str[j/4] ^= ((uint32_t)salt[saltpos++ % saltbytes]) << (24-8*(j%4)); - blowfish_encrypt(str[0], str[1], str, ctx); - S0[i] = str[0]; - S0[i + 1] = str[1]; - } - for (i = 0; i < 256; i += 2) { - for (j = 0; j < 8; j++) - str[j/4] ^= ((uint32_t)salt[saltpos++ % saltbytes]) << (24-8*(j%4)); - blowfish_encrypt(str[0], str[1], str, ctx); - S1[i] = str[0]; - S1[i + 1] = str[1]; - } - for (i = 0; i < 256; i += 2) { - for (j = 0; j < 8; j++) - str[j/4] ^= ((uint32_t)salt[saltpos++ % saltbytes]) << (24-8*(j%4)); - blowfish_encrypt(str[0], str[1], str, ctx); - S2[i] = str[0]; - S2[i + 1] = str[1]; - } - for (i = 0; i < 256; i += 2) { - for (j = 0; j < 8; j++) - str[j/4] ^= ((uint32_t)salt[saltpos++ % saltbytes]) << (24-8*(j%4)); - blowfish_encrypt(str[0], str[1], str, ctx); - S3[i] = str[0]; - S3[i + 1] = str[1]; - } -} - -static void blowfish_setkey(BlowfishContext *ctx, - const unsigned char *key, short keybytes) -{ - blowfish_initkey(ctx); - blowfish_expandkey(ctx, key, keybytes, NULL, 0); -} - -/* -- Interface with PuTTY -- */ - -#define SSH1_SESSION_KEY_LENGTH 32 - -BlowfishContext *blowfish_make_context(void) -{ - return snew(BlowfishContext); -} - -void blowfish_free_context(BlowfishContext *ctx) -{ - sfree(ctx); -} - -static void blowfish_iv_be(BlowfishContext *ctx, const void *viv) -{ - const unsigned char *iv = (const unsigned char *)viv; - ctx->iv0 = GET_32BIT_MSB_FIRST(iv); - ctx->iv1 = GET_32BIT_MSB_FIRST(iv + 4); -} - -static void blowfish_iv_le(BlowfishContext *ctx, const void *viv) -{ - const unsigned char *iv = (const unsigned char *)viv; - ctx->iv0 = GET_32BIT_LSB_FIRST(iv); - ctx->iv1 = GET_32BIT_LSB_FIRST(iv + 4); -} - -struct blowfish_ctx { - BlowfishContext context; - ssh_cipher ciph; -}; - -static ssh_cipher *blowfish_new(const ssh_cipheralg *alg) -{ - struct blowfish_ctx *ctx = snew(struct blowfish_ctx); - ctx->ciph.vt = alg; - return &ctx->ciph; -} - -static void blowfish_free(ssh_cipher *cipher) -{ - struct blowfish_ctx *ctx = container_of(cipher, struct blowfish_ctx, ciph); - smemclr(ctx, sizeof(*ctx)); - sfree(ctx); -} - -static void blowfish_ssh_setkey(ssh_cipher *cipher, const void *key) -{ - struct blowfish_ctx *ctx = container_of(cipher, struct blowfish_ctx, ciph); - blowfish_setkey(&ctx->context, key, ctx->ciph.vt->padded_keybytes); -} - -static void blowfish_ssh1_setiv(ssh_cipher *cipher, const void *iv) -{ - struct blowfish_ctx *ctx = container_of(cipher, struct blowfish_ctx, ciph); - blowfish_iv_le(&ctx->context, iv); -} - -static void blowfish_ssh2_setiv(ssh_cipher *cipher, const void *iv) -{ - struct blowfish_ctx *ctx = container_of(cipher, struct blowfish_ctx, ciph); - blowfish_iv_be(&ctx->context, iv); -} - -static void blowfish_ssh1_encrypt_blk(ssh_cipher *cipher, void *blk, int len) -{ - struct blowfish_ctx *ctx = container_of(cipher, struct blowfish_ctx, ciph); - blowfish_lsb_encrypt_cbc(blk, len, &ctx->context); -} - -static void blowfish_ssh1_decrypt_blk(ssh_cipher *cipher, void *blk, int len) -{ - struct blowfish_ctx *ctx = container_of(cipher, struct blowfish_ctx, ciph); - blowfish_lsb_decrypt_cbc(blk, len, &ctx->context); -} - -static void blowfish_ssh2_encrypt_blk(ssh_cipher *cipher, void *blk, int len) -{ - struct blowfish_ctx *ctx = container_of(cipher, struct blowfish_ctx, ciph); - blowfish_msb_encrypt_cbc(blk, len, &ctx->context); -} - -static void blowfish_ssh2_decrypt_blk(ssh_cipher *cipher, void *blk, int len) -{ - struct blowfish_ctx *ctx = container_of(cipher, struct blowfish_ctx, ciph); - blowfish_msb_decrypt_cbc(blk, len, &ctx->context); -} - -static void blowfish_ssh2_sdctr(ssh_cipher *cipher, void *blk, int len) -{ - struct blowfish_ctx *ctx = container_of(cipher, struct blowfish_ctx, ciph); - blowfish_msb_sdctr(blk, len, &ctx->context); -} - -const ssh_cipheralg ssh_blowfish_ssh1 = { - .new = blowfish_new, - .free = blowfish_free, - .setiv = blowfish_ssh1_setiv, - .setkey = blowfish_ssh_setkey, - .encrypt = blowfish_ssh1_encrypt_blk, - .decrypt = blowfish_ssh1_decrypt_blk, - .blksize = 8, - .real_keybits = 128, - .padded_keybytes = SSH1_SESSION_KEY_LENGTH, - .flags = SSH_CIPHER_IS_CBC, - .text_name = "Blowfish-256 CBC", -}; - -const ssh_cipheralg ssh_blowfish_ssh2 = { - .new = blowfish_new, - .free = blowfish_free, - .setiv = blowfish_ssh2_setiv, - .setkey = blowfish_ssh_setkey, - .encrypt = blowfish_ssh2_encrypt_blk, - .decrypt = blowfish_ssh2_decrypt_blk, - .ssh2_id = "blowfish-cbc", - .blksize = 8, - .real_keybits = 128, - .padded_keybytes = 16, - .flags = SSH_CIPHER_IS_CBC, - .text_name = "Blowfish-128 CBC", -}; - -const ssh_cipheralg ssh_blowfish_ssh2_ctr = { - .new = blowfish_new, - .free = blowfish_free, - .setiv = blowfish_ssh2_setiv, - .setkey = blowfish_ssh_setkey, - .encrypt = blowfish_ssh2_sdctr, - .decrypt = blowfish_ssh2_sdctr, - .ssh2_id = "blowfish-ctr", - .blksize = 8, - .real_keybits = 256, - .padded_keybytes = 32, - .flags = 0, - .text_name = "Blowfish-256 SDCTR", -}; - -static const ssh_cipheralg *const blowfish_list[] = { - &ssh_blowfish_ssh2_ctr, - &ssh_blowfish_ssh2 -}; - -const ssh2_ciphers ssh2_blowfish = { lenof(blowfish_list), blowfish_list }; diff --git a/sshccp.c b/sshccp.c deleted file mode 100644 index dd25b997..00000000 --- a/sshccp.c +++ /dev/null @@ -1,1062 +0,0 @@ -/* - * ChaCha20-Poly1305 Implementation for SSH-2 - * - * Protocol spec: - * http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.chacha20poly1305?rev=1.2&content-type=text/x-cvsweb-markup - * - * ChaCha20 spec: - * http://cr.yp.to/chacha/chacha-20080128.pdf - * - * Salsa20 spec: - * http://cr.yp.to/snuffle/spec.pdf - * - * Poly1305-AES spec: - * http://cr.yp.to/mac/poly1305-20050329.pdf - * - * The nonce for the Poly1305 is the second part of the key output - * from the first round of ChaCha20. This removes the AES requirement. - * This is undocumented! - * - * This has an intricate link between the cipher and the MAC. The - * keying of both is done in by the cipher and setting of the IV is - * done by the MAC. One cannot operate without the other. The - * configuration of the ssh_cipheralg structure ensures that the MAC is - * set (and others ignored) if this cipher is chosen. - * - * This cipher also encrypts the length using a different - * instantiation of the cipher using a different key and IV made from - * the sequence number which is passed in addition when calling - * encrypt/decrypt on it. - */ - -#include "ssh.h" -#include "mpint_i.h" - -#ifndef INLINE -#define INLINE -#endif - -/* ChaCha20 implementation, only supporting 256-bit keys */ - -/* State for each ChaCha20 instance */ -struct chacha20 { - /* Current context, usually with the count incremented - * 0-3 are the static constant - * 4-11 are the key - * 12-13 are the counter - * 14-15 are the IV */ - uint32_t state[16]; - /* The output of the state above ready to xor */ - unsigned char current[64]; - /* The index of the above currently used to allow a true streaming cipher */ - int currentIndex; -}; - -static INLINE void chacha20_round(struct chacha20 *ctx) -{ - int i; - uint32_t copy[16]; - - /* Take a copy */ - memcpy(copy, ctx->state, sizeof(copy)); - - /* A circular rotation for a 32bit number */ -#define rotl(x, shift) x = ((x << shift) | (x >> (32 - shift))) - - /* What to do for each quarter round operation */ -#define qrop(a, b, c, d) \ - copy[a] += copy[b]; \ - copy[c] ^= copy[a]; \ - rotl(copy[c], d) - - /* A quarter round */ -#define quarter(a, b, c, d) \ - qrop(a, b, d, 16); \ - qrop(c, d, b, 12); \ - qrop(a, b, d, 8); \ - qrop(c, d, b, 7) - - /* Do 20 rounds, in pairs because every other is different */ - for (i = 0; i < 20; i += 2) { - /* A round */ - quarter(0, 4, 8, 12); - quarter(1, 5, 9, 13); - quarter(2, 6, 10, 14); - quarter(3, 7, 11, 15); - /* Another slightly different round */ - quarter(0, 5, 10, 15); - quarter(1, 6, 11, 12); - quarter(2, 7, 8, 13); - quarter(3, 4, 9, 14); - } - - /* Dump the macros, don't need them littering */ -#undef rotl -#undef qrop -#undef quarter - - /* Add the initial state */ - for (i = 0; i < 16; ++i) { - copy[i] += ctx->state[i]; - } - - /* Update the content of the xor buffer */ - for (i = 0; i < 16; ++i) { - ctx->current[i * 4 + 0] = copy[i] >> 0; - ctx->current[i * 4 + 1] = copy[i] >> 8; - ctx->current[i * 4 + 2] = copy[i] >> 16; - ctx->current[i * 4 + 3] = copy[i] >> 24; - } - /* State full, reset pointer to beginning */ - ctx->currentIndex = 0; - smemclr(copy, sizeof(copy)); - - /* Increment round counter */ - ++ctx->state[12]; - /* Check for overflow, not done in one line so the 32 bits are chopped by the type */ - if (!(uint32_t)(ctx->state[12])) { - ++ctx->state[13]; - } -} - -/* Initialise context with 256bit key */ -static void chacha20_key(struct chacha20 *ctx, const unsigned char *key) -{ - static const char constant[16] = "expand 32-byte k"; - - /* Add the fixed string to the start of the state */ - ctx->state[0] = GET_32BIT_LSB_FIRST(constant + 0); - ctx->state[1] = GET_32BIT_LSB_FIRST(constant + 4); - ctx->state[2] = GET_32BIT_LSB_FIRST(constant + 8); - ctx->state[3] = GET_32BIT_LSB_FIRST(constant + 12); - - /* Add the key */ - ctx->state[4] = GET_32BIT_LSB_FIRST(key + 0); - ctx->state[5] = GET_32BIT_LSB_FIRST(key + 4); - ctx->state[6] = GET_32BIT_LSB_FIRST(key + 8); - ctx->state[7] = GET_32BIT_LSB_FIRST(key + 12); - ctx->state[8] = GET_32BIT_LSB_FIRST(key + 16); - ctx->state[9] = GET_32BIT_LSB_FIRST(key + 20); - ctx->state[10] = GET_32BIT_LSB_FIRST(key + 24); - ctx->state[11] = GET_32BIT_LSB_FIRST(key + 28); - - /* New key, dump context */ - ctx->currentIndex = 64; -} - -static void chacha20_iv(struct chacha20 *ctx, const unsigned char *iv) -{ - ctx->state[12] = 0; - ctx->state[13] = 0; - ctx->state[14] = GET_32BIT_MSB_FIRST(iv); - ctx->state[15] = GET_32BIT_MSB_FIRST(iv + 4); - - /* New IV, dump context */ - ctx->currentIndex = 64; -} - -static void chacha20_encrypt(struct chacha20 *ctx, unsigned char *blk, int len) -{ - while (len) { - /* If we don't have any state left, then cycle to the next */ - if (ctx->currentIndex >= 64) { - chacha20_round(ctx); - } - - /* Do the xor while there's some state left and some plaintext left */ - while (ctx->currentIndex < 64 && len) { - *blk++ ^= ctx->current[ctx->currentIndex++]; - --len; - } - } -} - -/* Decrypt is encrypt... It's xor against a PRNG... */ -static INLINE void chacha20_decrypt(struct chacha20 *ctx, - unsigned char *blk, int len) -{ - chacha20_encrypt(ctx, blk, len); -} - -/* Poly1305 implementation (no AES, nonce is not encrypted) */ - -#define NWORDS ((130 + BIGNUM_INT_BITS-1) / BIGNUM_INT_BITS) -typedef struct bigval { - BignumInt w[NWORDS]; -} bigval; - -static void bigval_clear(bigval *r) -{ - int i; - for (i = 0; i < NWORDS; i++) - r->w[i] = 0; -} - -static void bigval_import_le(bigval *r, const void *vdata, int len) -{ - const unsigned char *data = (const unsigned char *)vdata; - int i; - bigval_clear(r); - for (i = 0; i < len; i++) - r->w[i / BIGNUM_INT_BYTES] |= - (BignumInt)data[i] << (8 * (i % BIGNUM_INT_BYTES)); -} - -static void bigval_export_le(const bigval *r, void *vdata, int len) -{ - unsigned char *data = (unsigned char *)vdata; - int i; - for (i = 0; i < len; i++) - data[i] = r->w[i / BIGNUM_INT_BYTES] >> (8 * (i % BIGNUM_INT_BYTES)); -} - -/* - * Core functions to do arithmetic mod p = 2^130-5. The whole - * collection of these, up to and including the surrounding #if, are - * generated automatically for various sizes of BignumInt by - * contrib/make1305.py. - */ - -#if BIGNUM_INT_BITS == 16 - -static void bigval_add(bigval *r, const bigval *a, const bigval *b) -{ - BignumInt v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14; - BignumInt v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26; - BignumCarry carry; - - v0 = a->w[0]; - v1 = a->w[1]; - v2 = a->w[2]; - v3 = a->w[3]; - v4 = a->w[4]; - v5 = a->w[5]; - v6 = a->w[6]; - v7 = a->w[7]; - v8 = a->w[8]; - v9 = b->w[0]; - v10 = b->w[1]; - v11 = b->w[2]; - v12 = b->w[3]; - v13 = b->w[4]; - v14 = b->w[5]; - v15 = b->w[6]; - v16 = b->w[7]; - v17 = b->w[8]; - BignumADC(v18, carry, v0, v9, 0); - BignumADC(v19, carry, v1, v10, carry); - BignumADC(v20, carry, v2, v11, carry); - BignumADC(v21, carry, v3, v12, carry); - BignumADC(v22, carry, v4, v13, carry); - BignumADC(v23, carry, v5, v14, carry); - BignumADC(v24, carry, v6, v15, carry); - BignumADC(v25, carry, v7, v16, carry); - v26 = v8 + v17 + carry; - r->w[0] = v18; - r->w[1] = v19; - r->w[2] = v20; - r->w[3] = v21; - r->w[4] = v22; - r->w[5] = v23; - r->w[6] = v24; - r->w[7] = v25; - r->w[8] = v26; -} - -static void bigval_mul_mod_p(bigval *r, const bigval *a, const bigval *b) -{ - BignumInt v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14; - BignumInt v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27; - BignumInt v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40; - BignumInt v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53; - BignumInt v54, v55, v56, v57, v58, v59, v60, v61, v62, v63, v64, v65, v66; - BignumInt v67, v68, v69, v70, v71, v72, v73, v74, v75, v76, v77, v78, v79; - BignumInt v80, v81, v82, v83, v84, v85, v86, v87, v88, v89, v90, v91, v92; - BignumInt v93, v94, v95, v96, v97, v98, v99, v100, v101, v102, v103, v104; - BignumInt v105, v106, v107, v108, v109, v110, v111, v112, v113, v114; - BignumInt v115, v116, v117, v118, v119, v120, v121, v122, v123, v124; - BignumInt v125, v126, v127, v128, v129, v130, v131, v132, v133, v134; - BignumInt v135, v136, v137, v138, v139, v140, v141, v142, v143, v144; - BignumInt v145, v146, v147, v148, v149, v150, v151, v152, v153, v154; - BignumInt v155, v156, v157, v158, v159, v160, v161, v162, v163, v164; - BignumInt v165, v166, v167, v168, v169, v170, v171, v172, v173, v174; - BignumInt v175, v176, v177, v178, v180, v181, v182, v183, v184, v185; - BignumInt v186, v187, v188, v189, v190, v191, v192, v193, v194, v195; - BignumInt v196, v197, v198, v199, v200, v201, v202, v203, v204, v205; - BignumInt v206, v207, v208, v210, v212, v213, v214, v215, v216, v217; - BignumInt v218, v219, v220, v221, v222, v223, v224, v225, v226, v227; - BignumInt v228, v229; - BignumCarry carry; - - v0 = a->w[0]; - v1 = a->w[1]; - v2 = a->w[2]; - v3 = a->w[3]; - v4 = a->w[4]; - v5 = a->w[5]; - v6 = a->w[6]; - v7 = a->w[7]; - v8 = a->w[8]; - v9 = b->w[0]; - v10 = b->w[1]; - v11 = b->w[2]; - v12 = b->w[3]; - v13 = b->w[4]; - v14 = b->w[5]; - v15 = b->w[6]; - v16 = b->w[7]; - v17 = b->w[8]; - BignumMUL(v19, v18, v0, v9); - BignumMULADD(v21, v20, v0, v10, v19); - BignumMULADD(v23, v22, v0, v11, v21); - BignumMULADD(v25, v24, v0, v12, v23); - BignumMULADD(v27, v26, v0, v13, v25); - BignumMULADD(v29, v28, v0, v14, v27); - BignumMULADD(v31, v30, v0, v15, v29); - BignumMULADD(v33, v32, v0, v16, v31); - BignumMULADD(v35, v34, v0, v17, v33); - BignumMULADD(v37, v36, v1, v9, v20); - BignumMULADD2(v39, v38, v1, v10, v22, v37); - BignumMULADD2(v41, v40, v1, v11, v24, v39); - BignumMULADD2(v43, v42, v1, v12, v26, v41); - BignumMULADD2(v45, v44, v1, v13, v28, v43); - BignumMULADD2(v47, v46, v1, v14, v30, v45); - BignumMULADD2(v49, v48, v1, v15, v32, v47); - BignumMULADD2(v51, v50, v1, v16, v34, v49); - BignumMULADD2(v53, v52, v1, v17, v35, v51); - BignumMULADD(v55, v54, v2, v9, v38); - BignumMULADD2(v57, v56, v2, v10, v40, v55); - BignumMULADD2(v59, v58, v2, v11, v42, v57); - BignumMULADD2(v61, v60, v2, v12, v44, v59); - BignumMULADD2(v63, v62, v2, v13, v46, v61); - BignumMULADD2(v65, v64, v2, v14, v48, v63); - BignumMULADD2(v67, v66, v2, v15, v50, v65); - BignumMULADD2(v69, v68, v2, v16, v52, v67); - BignumMULADD2(v71, v70, v2, v17, v53, v69); - BignumMULADD(v73, v72, v3, v9, v56); - BignumMULADD2(v75, v74, v3, v10, v58, v73); - BignumMULADD2(v77, v76, v3, v11, v60, v75); - BignumMULADD2(v79, v78, v3, v12, v62, v77); - BignumMULADD2(v81, v80, v3, v13, v64, v79); - BignumMULADD2(v83, v82, v3, v14, v66, v81); - BignumMULADD2(v85, v84, v3, v15, v68, v83); - BignumMULADD2(v87, v86, v3, v16, v70, v85); - BignumMULADD2(v89, v88, v3, v17, v71, v87); - BignumMULADD(v91, v90, v4, v9, v74); - BignumMULADD2(v93, v92, v4, v10, v76, v91); - BignumMULADD2(v95, v94, v4, v11, v78, v93); - BignumMULADD2(v97, v96, v4, v12, v80, v95); - BignumMULADD2(v99, v98, v4, v13, v82, v97); - BignumMULADD2(v101, v100, v4, v14, v84, v99); - BignumMULADD2(v103, v102, v4, v15, v86, v101); - BignumMULADD2(v105, v104, v4, v16, v88, v103); - BignumMULADD2(v107, v106, v4, v17, v89, v105); - BignumMULADD(v109, v108, v5, v9, v92); - BignumMULADD2(v111, v110, v5, v10, v94, v109); - BignumMULADD2(v113, v112, v5, v11, v96, v111); - BignumMULADD2(v115, v114, v5, v12, v98, v113); - BignumMULADD2(v117, v116, v5, v13, v100, v115); - BignumMULADD2(v119, v118, v5, v14, v102, v117); - BignumMULADD2(v121, v120, v5, v15, v104, v119); - BignumMULADD2(v123, v122, v5, v16, v106, v121); - BignumMULADD2(v125, v124, v5, v17, v107, v123); - BignumMULADD(v127, v126, v6, v9, v110); - BignumMULADD2(v129, v128, v6, v10, v112, v127); - BignumMULADD2(v131, v130, v6, v11, v114, v129); - BignumMULADD2(v133, v132, v6, v12, v116, v131); - BignumMULADD2(v135, v134, v6, v13, v118, v133); - BignumMULADD2(v137, v136, v6, v14, v120, v135); - BignumMULADD2(v139, v138, v6, v15, v122, v137); - BignumMULADD2(v141, v140, v6, v16, v124, v139); - BignumMULADD2(v143, v142, v6, v17, v125, v141); - BignumMULADD(v145, v144, v7, v9, v128); - BignumMULADD2(v147, v146, v7, v10, v130, v145); - BignumMULADD2(v149, v148, v7, v11, v132, v147); - BignumMULADD2(v151, v150, v7, v12, v134, v149); - BignumMULADD2(v153, v152, v7, v13, v136, v151); - BignumMULADD2(v155, v154, v7, v14, v138, v153); - BignumMULADD2(v157, v156, v7, v15, v140, v155); - BignumMULADD2(v159, v158, v7, v16, v142, v157); - BignumMULADD2(v161, v160, v7, v17, v143, v159); - BignumMULADD(v163, v162, v8, v9, v146); - BignumMULADD2(v165, v164, v8, v10, v148, v163); - BignumMULADD2(v167, v166, v8, v11, v150, v165); - BignumMULADD2(v169, v168, v8, v12, v152, v167); - BignumMULADD2(v171, v170, v8, v13, v154, v169); - BignumMULADD2(v173, v172, v8, v14, v156, v171); - BignumMULADD2(v175, v174, v8, v15, v158, v173); - BignumMULADD2(v177, v176, v8, v16, v160, v175); - v178 = v8 * v17 + v161 + v177; - v180 = (v162) & ((((BignumInt)1) << 2)-1); - v181 = ((v162) >> 2) | ((v164) << 14); - v182 = ((v164) >> 2) | ((v166) << 14); - v183 = ((v166) >> 2) | ((v168) << 14); - v184 = ((v168) >> 2) | ((v170) << 14); - v185 = ((v170) >> 2) | ((v172) << 14); - v186 = ((v172) >> 2) | ((v174) << 14); - v187 = ((v174) >> 2) | ((v176) << 14); - v188 = ((v176) >> 2) | ((v178) << 14); - v189 = (v178) >> 2; - v190 = (v189) & ((((BignumInt)1) << 2)-1); - v191 = (v178) >> 4; - BignumMUL(v193, v192, 5, v181); - BignumMULADD(v195, v194, 5, v182, v193); - BignumMULADD(v197, v196, 5, v183, v195); - BignumMULADD(v199, v198, 5, v184, v197); - BignumMULADD(v201, v200, 5, v185, v199); - BignumMULADD(v203, v202, 5, v186, v201); - BignumMULADD(v205, v204, 5, v187, v203); - BignumMULADD(v207, v206, 5, v188, v205); - v208 = 5 * v190 + v207; - v210 = 25 * v191; - BignumADC(v212, carry, v18, v192, 0); - BignumADC(v213, carry, v36, v194, carry); - BignumADC(v214, carry, v54, v196, carry); - BignumADC(v215, carry, v72, v198, carry); - BignumADC(v216, carry, v90, v200, carry); - BignumADC(v217, carry, v108, v202, carry); - BignumADC(v218, carry, v126, v204, carry); - BignumADC(v219, carry, v144, v206, carry); - v220 = v180 + v208 + carry; - BignumADC(v221, carry, v212, v210, 0); - BignumADC(v222, carry, v213, 0, carry); - BignumADC(v223, carry, v214, 0, carry); - BignumADC(v224, carry, v215, 0, carry); - BignumADC(v225, carry, v216, 0, carry); - BignumADC(v226, carry, v217, 0, carry); - BignumADC(v227, carry, v218, 0, carry); - BignumADC(v228, carry, v219, 0, carry); - v229 = v220 + 0 + carry; - r->w[0] = v221; - r->w[1] = v222; - r->w[2] = v223; - r->w[3] = v224; - r->w[4] = v225; - r->w[5] = v226; - r->w[6] = v227; - r->w[7] = v228; - r->w[8] = v229; -} - -static void bigval_final_reduce(bigval *n) -{ - BignumInt v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v13, v14, v15; - BignumInt v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28; - BignumInt v29, v30, v31, v32, v34, v35, v36, v37, v38, v39, v40, v41, v42; - BignumInt v43; - BignumCarry carry; - - v0 = n->w[0]; - v1 = n->w[1]; - v2 = n->w[2]; - v3 = n->w[3]; - v4 = n->w[4]; - v5 = n->w[5]; - v6 = n->w[6]; - v7 = n->w[7]; - v8 = n->w[8]; - v9 = (v8) >> 2; - v10 = (v8) & ((((BignumInt)1) << 2)-1); - v11 = 5 * v9; - BignumADC(v13, carry, v0, v11, 0); - BignumADC(v14, carry, v1, 0, carry); - BignumADC(v15, carry, v2, 0, carry); - BignumADC(v16, carry, v3, 0, carry); - BignumADC(v17, carry, v4, 0, carry); - BignumADC(v18, carry, v5, 0, carry); - BignumADC(v19, carry, v6, 0, carry); - BignumADC(v20, carry, v7, 0, carry); - v21 = v10 + 0 + carry; - BignumADC(v22, carry, v13, 5, 0); - (void)v22; - BignumADC(v23, carry, v14, 0, carry); - (void)v23; - BignumADC(v24, carry, v15, 0, carry); - (void)v24; - BignumADC(v25, carry, v16, 0, carry); - (void)v25; - BignumADC(v26, carry, v17, 0, carry); - (void)v26; - BignumADC(v27, carry, v18, 0, carry); - (void)v27; - BignumADC(v28, carry, v19, 0, carry); - (void)v28; - BignumADC(v29, carry, v20, 0, carry); - (void)v29; - v30 = v21 + 0 + carry; - v31 = (v30) >> 2; - v32 = 5 * v31; - BignumADC(v34, carry, v13, v32, 0); - BignumADC(v35, carry, v14, 0, carry); - BignumADC(v36, carry, v15, 0, carry); - BignumADC(v37, carry, v16, 0, carry); - BignumADC(v38, carry, v17, 0, carry); - BignumADC(v39, carry, v18, 0, carry); - BignumADC(v40, carry, v19, 0, carry); - BignumADC(v41, carry, v20, 0, carry); - v42 = v21 + 0 + carry; - v43 = (v42) & ((((BignumInt)1) << 2)-1); - n->w[0] = v34; - n->w[1] = v35; - n->w[2] = v36; - n->w[3] = v37; - n->w[4] = v38; - n->w[5] = v39; - n->w[6] = v40; - n->w[7] = v41; - n->w[8] = v43; -} - -#elif BIGNUM_INT_BITS == 32 - -static void bigval_add(bigval *r, const bigval *a, const bigval *b) -{ - BignumInt v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14; - BignumCarry carry; - - v0 = a->w[0]; - v1 = a->w[1]; - v2 = a->w[2]; - v3 = a->w[3]; - v4 = a->w[4]; - v5 = b->w[0]; - v6 = b->w[1]; - v7 = b->w[2]; - v8 = b->w[3]; - v9 = b->w[4]; - BignumADC(v10, carry, v0, v5, 0); - BignumADC(v11, carry, v1, v6, carry); - BignumADC(v12, carry, v2, v7, carry); - BignumADC(v13, carry, v3, v8, carry); - v14 = v4 + v9 + carry; - r->w[0] = v10; - r->w[1] = v11; - r->w[2] = v12; - r->w[3] = v13; - r->w[4] = v14; -} - -static void bigval_mul_mod_p(bigval *r, const bigval *a, const bigval *b) -{ - BignumInt v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14; - BignumInt v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27; - BignumInt v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40; - BignumInt v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53; - BignumInt v54, v55, v56, v57, v58, v60, v61, v62, v63, v64, v65, v66, v67; - BignumInt v68, v69, v70, v71, v72, v73, v74, v75, v76, v78, v80, v81, v82; - BignumInt v83, v84, v85, v86, v87, v88, v89; - BignumCarry carry; - - v0 = a->w[0]; - v1 = a->w[1]; - v2 = a->w[2]; - v3 = a->w[3]; - v4 = a->w[4]; - v5 = b->w[0]; - v6 = b->w[1]; - v7 = b->w[2]; - v8 = b->w[3]; - v9 = b->w[4]; - BignumMUL(v11, v10, v0, v5); - BignumMULADD(v13, v12, v0, v6, v11); - BignumMULADD(v15, v14, v0, v7, v13); - BignumMULADD(v17, v16, v0, v8, v15); - BignumMULADD(v19, v18, v0, v9, v17); - BignumMULADD(v21, v20, v1, v5, v12); - BignumMULADD2(v23, v22, v1, v6, v14, v21); - BignumMULADD2(v25, v24, v1, v7, v16, v23); - BignumMULADD2(v27, v26, v1, v8, v18, v25); - BignumMULADD2(v29, v28, v1, v9, v19, v27); - BignumMULADD(v31, v30, v2, v5, v22); - BignumMULADD2(v33, v32, v2, v6, v24, v31); - BignumMULADD2(v35, v34, v2, v7, v26, v33); - BignumMULADD2(v37, v36, v2, v8, v28, v35); - BignumMULADD2(v39, v38, v2, v9, v29, v37); - BignumMULADD(v41, v40, v3, v5, v32); - BignumMULADD2(v43, v42, v3, v6, v34, v41); - BignumMULADD2(v45, v44, v3, v7, v36, v43); - BignumMULADD2(v47, v46, v3, v8, v38, v45); - BignumMULADD2(v49, v48, v3, v9, v39, v47); - BignumMULADD(v51, v50, v4, v5, v42); - BignumMULADD2(v53, v52, v4, v6, v44, v51); - BignumMULADD2(v55, v54, v4, v7, v46, v53); - BignumMULADD2(v57, v56, v4, v8, v48, v55); - v58 = v4 * v9 + v49 + v57; - v60 = (v50) & ((((BignumInt)1) << 2)-1); - v61 = ((v50) >> 2) | ((v52) << 30); - v62 = ((v52) >> 2) | ((v54) << 30); - v63 = ((v54) >> 2) | ((v56) << 30); - v64 = ((v56) >> 2) | ((v58) << 30); - v65 = (v58) >> 2; - v66 = (v65) & ((((BignumInt)1) << 2)-1); - v67 = (v58) >> 4; - BignumMUL(v69, v68, 5, v61); - BignumMULADD(v71, v70, 5, v62, v69); - BignumMULADD(v73, v72, 5, v63, v71); - BignumMULADD(v75, v74, 5, v64, v73); - v76 = 5 * v66 + v75; - v78 = 25 * v67; - BignumADC(v80, carry, v10, v68, 0); - BignumADC(v81, carry, v20, v70, carry); - BignumADC(v82, carry, v30, v72, carry); - BignumADC(v83, carry, v40, v74, carry); - v84 = v60 + v76 + carry; - BignumADC(v85, carry, v80, v78, 0); - BignumADC(v86, carry, v81, 0, carry); - BignumADC(v87, carry, v82, 0, carry); - BignumADC(v88, carry, v83, 0, carry); - v89 = v84 + 0 + carry; - r->w[0] = v85; - r->w[1] = v86; - r->w[2] = v87; - r->w[3] = v88; - r->w[4] = v89; -} - -static void bigval_final_reduce(bigval *n) -{ - BignumInt v0, v1, v2, v3, v4, v5, v6, v7, v9, v10, v11, v12, v13, v14; - BignumInt v15, v16, v17, v18, v19, v20, v22, v23, v24, v25, v26, v27; - BignumCarry carry; - - v0 = n->w[0]; - v1 = n->w[1]; - v2 = n->w[2]; - v3 = n->w[3]; - v4 = n->w[4]; - v5 = (v4) >> 2; - v6 = (v4) & ((((BignumInt)1) << 2)-1); - v7 = 5 * v5; - BignumADC(v9, carry, v0, v7, 0); - BignumADC(v10, carry, v1, 0, carry); - BignumADC(v11, carry, v2, 0, carry); - BignumADC(v12, carry, v3, 0, carry); - v13 = v6 + 0 + carry; - BignumADC(v14, carry, v9, 5, 0); - (void)v14; - BignumADC(v15, carry, v10, 0, carry); - (void)v15; - BignumADC(v16, carry, v11, 0, carry); - (void)v16; - BignumADC(v17, carry, v12, 0, carry); - (void)v17; - v18 = v13 + 0 + carry; - v19 = (v18) >> 2; - v20 = 5 * v19; - BignumADC(v22, carry, v9, v20, 0); - BignumADC(v23, carry, v10, 0, carry); - BignumADC(v24, carry, v11, 0, carry); - BignumADC(v25, carry, v12, 0, carry); - v26 = v13 + 0 + carry; - v27 = (v26) & ((((BignumInt)1) << 2)-1); - n->w[0] = v22; - n->w[1] = v23; - n->w[2] = v24; - n->w[3] = v25; - n->w[4] = v27; -} - -#elif BIGNUM_INT_BITS == 64 - -static void bigval_add(bigval *r, const bigval *a, const bigval *b) -{ - BignumInt v0, v1, v2, v3, v4, v5, v6, v7, v8; - BignumCarry carry; - - v0 = a->w[0]; - v1 = a->w[1]; - v2 = a->w[2]; - v3 = b->w[0]; - v4 = b->w[1]; - v5 = b->w[2]; - BignumADC(v6, carry, v0, v3, 0); - BignumADC(v7, carry, v1, v4, carry); - v8 = v2 + v5 + carry; - r->w[0] = v6; - r->w[1] = v7; - r->w[2] = v8; -} - -static void bigval_mul_mod_p(bigval *r, const bigval *a, const bigval *b) -{ - BignumInt v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14; - BignumInt v15, v16, v17, v18, v19, v20, v21, v22, v24, v25, v26, v27, v28; - BignumInt v29, v30, v31, v32, v33, v34, v36, v38, v39, v40, v41, v42, v43; - BignumCarry carry; - - v0 = a->w[0]; - v1 = a->w[1]; - v2 = a->w[2]; - v3 = b->w[0]; - v4 = b->w[1]; - v5 = b->w[2]; - BignumMUL(v7, v6, v0, v3); - BignumMULADD(v9, v8, v0, v4, v7); - BignumMULADD(v11, v10, v0, v5, v9); - BignumMULADD(v13, v12, v1, v3, v8); - BignumMULADD2(v15, v14, v1, v4, v10, v13); - BignumMULADD2(v17, v16, v1, v5, v11, v15); - BignumMULADD(v19, v18, v2, v3, v14); - BignumMULADD2(v21, v20, v2, v4, v16, v19); - v22 = v2 * v5 + v17 + v21; - v24 = (v18) & ((((BignumInt)1) << 2)-1); - v25 = ((v18) >> 2) | ((v20) << 62); - v26 = ((v20) >> 2) | ((v22) << 62); - v27 = (v22) >> 2; - v28 = (v27) & ((((BignumInt)1) << 2)-1); - v29 = (v22) >> 4; - BignumMUL(v31, v30, 5, v25); - BignumMULADD(v33, v32, 5, v26, v31); - v34 = 5 * v28 + v33; - v36 = 25 * v29; - BignumADC(v38, carry, v6, v30, 0); - BignumADC(v39, carry, v12, v32, carry); - v40 = v24 + v34 + carry; - BignumADC(v41, carry, v38, v36, 0); - BignumADC(v42, carry, v39, 0, carry); - v43 = v40 + 0 + carry; - r->w[0] = v41; - r->w[1] = v42; - r->w[2] = v43; -} - -static void bigval_final_reduce(bigval *n) -{ - BignumInt v0, v1, v2, v3, v4, v5, v7, v8, v9, v10, v11, v12, v13, v14; - BignumInt v16, v17, v18, v19; - BignumCarry carry; - - v0 = n->w[0]; - v1 = n->w[1]; - v2 = n->w[2]; - v3 = (v2) >> 2; - v4 = (v2) & ((((BignumInt)1) << 2)-1); - v5 = 5 * v3; - BignumADC(v7, carry, v0, v5, 0); - BignumADC(v8, carry, v1, 0, carry); - v9 = v4 + 0 + carry; - BignumADC(v10, carry, v7, 5, 0); - (void)v10; - BignumADC(v11, carry, v8, 0, carry); - (void)v11; - v12 = v9 + 0 + carry; - v13 = (v12) >> 2; - v14 = 5 * v13; - BignumADC(v16, carry, v7, v14, 0); - BignumADC(v17, carry, v8, 0, carry); - v18 = v9 + 0 + carry; - v19 = (v18) & ((((BignumInt)1) << 2)-1); - n->w[0] = v16; - n->w[1] = v17; - n->w[2] = v19; -} - -#else -#error Add another bit count to contrib/make1305.py and rerun it -#endif - -struct poly1305 { - unsigned char nonce[16]; - bigval r; - bigval h; - - /* Buffer in case we get less that a multiple of 16 bytes */ - unsigned char buffer[16]; - int bufferIndex; -}; - -static void poly1305_init(struct poly1305 *ctx) -{ - memset(ctx->nonce, 0, 16); - ctx->bufferIndex = 0; - bigval_clear(&ctx->h); -} - -static void poly1305_key(struct poly1305 *ctx, ptrlen key) -{ - assert(key.len == 32); /* Takes a 256 bit key */ - - unsigned char key_copy[16]; - memcpy(key_copy, key.ptr, 16); - - /* Key the MAC itself - * bytes 4, 8, 12 and 16 are required to have their top four bits clear */ - key_copy[3] &= 0x0f; - key_copy[7] &= 0x0f; - key_copy[11] &= 0x0f; - key_copy[15] &= 0x0f; - /* bytes 5, 9 and 13 are required to have their bottom two bits clear */ - key_copy[4] &= 0xfc; - key_copy[8] &= 0xfc; - key_copy[12] &= 0xfc; - bigval_import_le(&ctx->r, key_copy, 16); - smemclr(key_copy, sizeof(key_copy)); - - /* Use second 128 bits as the nonce */ - memcpy(ctx->nonce, (const char *)key.ptr + 16, 16); -} - -/* Feed up to 16 bytes (should only be less for the last chunk) */ -static void poly1305_feed_chunk(struct poly1305 *ctx, - const unsigned char *chunk, int len) -{ - bigval c; - bigval_import_le(&c, chunk, len); - c.w[len / BIGNUM_INT_BYTES] |= - (BignumInt)1 << (8 * (len % BIGNUM_INT_BYTES)); - bigval_add(&c, &c, &ctx->h); - bigval_mul_mod_p(&ctx->h, &c, &ctx->r); -} - -static void poly1305_feed(struct poly1305 *ctx, - const unsigned char *buf, int len) -{ - /* Check for stuff left in the buffer from last time */ - if (ctx->bufferIndex) { - /* Try to fill up to 16 */ - while (ctx->bufferIndex < 16 && len) { - ctx->buffer[ctx->bufferIndex++] = *buf++; - --len; - } - if (ctx->bufferIndex == 16) { - poly1305_feed_chunk(ctx, ctx->buffer, 16); - ctx->bufferIndex = 0; - } - } - - /* Process 16 byte whole chunks */ - while (len >= 16) { - poly1305_feed_chunk(ctx, buf, 16); - len -= 16; - buf += 16; - } - - /* Cache stuff that's left over */ - if (len) { - memcpy(ctx->buffer, buf, len); - ctx->bufferIndex = len; - } -} - -/* Finalise and populate buffer with 16 byte with MAC */ -static void poly1305_finalise(struct poly1305 *ctx, unsigned char *mac) -{ - bigval tmp; - - if (ctx->bufferIndex) { - poly1305_feed_chunk(ctx, ctx->buffer, ctx->bufferIndex); - } - - bigval_import_le(&tmp, ctx->nonce, 16); - bigval_final_reduce(&ctx->h); - bigval_add(&tmp, &tmp, &ctx->h); - bigval_export_le(&tmp, mac, 16); -} - -/* SSH-2 wrapper */ - -struct ccp_context { - struct chacha20 a_cipher; /* Used for length */ - struct chacha20 b_cipher; /* Used for content */ - - /* Cache of the first 4 bytes because they are the sequence number */ - /* Kept in 8 bytes with the top as zero to allow easy passing to setiv */ - int mac_initialised; /* Where we have got to in filling mac_iv */ - unsigned char mac_iv[8]; - - struct poly1305 mac; - - BinarySink_IMPLEMENTATION; - ssh_cipher ciph; - ssh2_mac mac_if; -}; - -static ssh2_mac *poly_ssh2_new( - const ssh2_macalg *alg, ssh_cipher *cipher) -{ - struct ccp_context *ctx = container_of(cipher, struct ccp_context, ciph); - ctx->mac_if.vt = alg; - BinarySink_DELEGATE_INIT(&ctx->mac_if, ctx); - return &ctx->mac_if; -} - -static void poly_ssh2_free(ssh2_mac *mac) -{ - /* Not allocated, just forwarded, no need to free */ -} - -static void poly_setkey(ssh2_mac *mac, ptrlen key) -{ - /* Uses the same context as ChaCha20, so ignore */ -} - -static void poly_start(ssh2_mac *mac) -{ - struct ccp_context *ctx = container_of(mac, struct ccp_context, mac_if); - - ctx->mac_initialised = 0; - memset(ctx->mac_iv, 0, 8); - poly1305_init(&ctx->mac); -} - -static void poly_BinarySink_write(BinarySink *bs, const void *blkv, size_t len) -{ - struct ccp_context *ctx = BinarySink_DOWNCAST(bs, struct ccp_context); - const unsigned char *blk = (const unsigned char *)blkv; - - /* First 4 bytes are the IV */ - while (ctx->mac_initialised < 4 && len) { - ctx->mac_iv[7 - ctx->mac_initialised] = *blk++; - ++ctx->mac_initialised; - --len; - } - - /* Initialise the IV if needed */ - if (ctx->mac_initialised == 4) { - chacha20_iv(&ctx->b_cipher, ctx->mac_iv); - ++ctx->mac_initialised; /* Don't do it again */ - - /* Do first rotation */ - chacha20_round(&ctx->b_cipher); - - /* Set the poly key */ - poly1305_key(&ctx->mac, make_ptrlen(ctx->b_cipher.current, 32)); - - /* Set the first round as used */ - ctx->b_cipher.currentIndex = 64; - } - - /* Update the MAC with anything left */ - if (len) { - poly1305_feed(&ctx->mac, blk, len); - } -} - -static void poly_genresult(ssh2_mac *mac, unsigned char *blk) -{ - struct ccp_context *ctx = container_of(mac, struct ccp_context, mac_if); - poly1305_finalise(&ctx->mac, blk); -} - -static const char *poly_text_name(ssh2_mac *mac) -{ - return "Poly1305"; -} - -const ssh2_macalg ssh2_poly1305 = { - .new = poly_ssh2_new, - .free = poly_ssh2_free, - .setkey = poly_setkey, - .start = poly_start, - .genresult = poly_genresult, - .text_name = poly_text_name, - .name = "", - .etm_name = "", /* Not selectable individually, just part of - * ChaCha20-Poly1305 */ - .len = 16, - .keylen = 0, -}; - -static ssh_cipher *ccp_new(const ssh_cipheralg *alg) -{ - struct ccp_context *ctx = snew(struct ccp_context); - BinarySink_INIT(ctx, poly_BinarySink_write); - poly1305_init(&ctx->mac); - ctx->ciph.vt = alg; - return &ctx->ciph; -} - -static void ccp_free(ssh_cipher *cipher) -{ - struct ccp_context *ctx = container_of(cipher, struct ccp_context, ciph); - smemclr(&ctx->a_cipher, sizeof(ctx->a_cipher)); - smemclr(&ctx->b_cipher, sizeof(ctx->b_cipher)); - smemclr(&ctx->mac, sizeof(ctx->mac)); - sfree(ctx); -} - -static void ccp_iv(ssh_cipher *cipher, const void *iv) -{ - /* struct ccp_context *ctx = - container_of(cipher, struct ccp_context, ciph); */ - /* IV is set based on the sequence number */ -} - -static void ccp_key(ssh_cipher *cipher, const void *vkey) -{ - const unsigned char *key = (const unsigned char *)vkey; - struct ccp_context *ctx = container_of(cipher, struct ccp_context, ciph); - /* Initialise the a_cipher (for decrypting lengths) with the first 256 bits */ - chacha20_key(&ctx->a_cipher, key + 32); - /* Initialise the b_cipher (for content and MAC) with the second 256 bits */ - chacha20_key(&ctx->b_cipher, key); -} - -static void ccp_encrypt(ssh_cipher *cipher, void *blk, int len) -{ - struct ccp_context *ctx = container_of(cipher, struct ccp_context, ciph); - chacha20_encrypt(&ctx->b_cipher, blk, len); -} - -static void ccp_decrypt(ssh_cipher *cipher, void *blk, int len) -{ - struct ccp_context *ctx = container_of(cipher, struct ccp_context, ciph); - chacha20_decrypt(&ctx->b_cipher, blk, len); -} - -static void ccp_length_op(struct ccp_context *ctx, void *blk, int len, - unsigned long seq) -{ - unsigned char iv[8]; - /* - * According to RFC 4253 (section 6.4), the packet sequence number wraps - * at 2^32, so its 32 high-order bits will always be zero. - */ - PUT_32BIT_LSB_FIRST(iv, 0); - PUT_32BIT_LSB_FIRST(iv + 4, seq); - chacha20_iv(&ctx->a_cipher, iv); - chacha20_iv(&ctx->b_cipher, iv); - /* Reset content block count to 1, as the first is the key for Poly1305 */ - ++ctx->b_cipher.state[12]; - smemclr(iv, sizeof(iv)); -} - -static void ccp_encrypt_length(ssh_cipher *cipher, void *blk, int len, - unsigned long seq) -{ - struct ccp_context *ctx = container_of(cipher, struct ccp_context, ciph); - ccp_length_op(ctx, blk, len, seq); - chacha20_encrypt(&ctx->a_cipher, blk, len); -} - -static void ccp_decrypt_length(ssh_cipher *cipher, void *blk, int len, - unsigned long seq) -{ - struct ccp_context *ctx = container_of(cipher, struct ccp_context, ciph); - ccp_length_op(ctx, blk, len, seq); - chacha20_decrypt(&ctx->a_cipher, blk, len); -} - -const ssh_cipheralg ssh2_chacha20_poly1305 = { - .new = ccp_new, - .free = ccp_free, - .setiv = ccp_iv, - .setkey = ccp_key, - .encrypt = ccp_encrypt, - .decrypt = ccp_decrypt, - .encrypt_length = ccp_encrypt_length, - .decrypt_length = ccp_decrypt_length, - .ssh2_id = "chacha20-poly1305@openssh.com", - .blksize = 1, - .real_keybits = 512, - .padded_keybytes = 64, - .flags = SSH_CIPHER_SEPARATE_LENGTH, - .text_name = "ChaCha20", - .required_mac = &ssh2_poly1305, -}; - -static const ssh_cipheralg *const ccp_list[] = { - &ssh2_chacha20_poly1305 -}; - -const ssh2_ciphers ssh2_ccp = { lenof(ccp_list), ccp_list }; diff --git a/sshcrc.c b/sshcrc.c deleted file mode 100644 index b1dc8333..00000000 --- a/sshcrc.c +++ /dev/null @@ -1,108 +0,0 @@ -/* - * CRC32 implementation, as used in SSH-1. - * - * This particular form of the CRC uses the polynomial - * P(x) = 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+1 - * and represents polynomials in bit-reversed form, so that the x^0 - * coefficient (constant term) appears in the bit with place value - * 2^31, and the x^31 coefficient in the bit with place value 2^0. In - * this representation, (x^32 mod P) = 0xEDB88320, so multiplying the - * current state by x is done by shifting right by one bit, and XORing - * that constant into the result if the bit shifted out was 1. - * - * There's a bewildering array of subtly different variants of CRC out - * there, using different polynomials, both bit orders, and varying - * the start and end conditions. There are catalogue websites such as - * http://reveng.sourceforge.net/crc-catalogue/ , which generally seem - * to have the convention of indexing CRCs by their 'check value', - * defined as whatever you get if you hash the 9-byte test string - * "123456789". - * - * The crc32_rfc1662() function below, which starts off the CRC state - * at 0xFFFFFFFF and complements it after feeding all the data, gives - * the check value 0xCBF43926, and matches the hash function that the - * above catalogue refers to as "CRC-32/ISO-HDLC"; among other things, - * it's also the "FCS-32" checksum described in RFC 1662 section C.3 - * (hence the name I've given it here). - * - * The crc32_ssh1() function implements the variant form used by - * SSH-1, which uses the same update function, but starts the state at - * zero and doesn't complement it at the end of the computation. The - * check value for that version is 0x2DFD2D88, which that CRC - * catalogue doesn't list at all. - */ - -#include -#include - -#include "ssh.h" - -/* - * Multiply a CRC value by x^4. This implementation strategy avoids - * using a lookup table (which would be a side-channel hazard, since - * SSH-1 applies this CRC to decrypted session data). - * - * The basic idea is that you'd like to "multiply" the shifted-out 4 - * bits by the CRC polynomial value 0xEDB88320, or rather by that - * value shifted right 3 bits (since you want the _last_ bit shifted - * out, i.e. the one originally at the 2^3 position, to generate - * 0xEDB88320 itself). But the scare-quoted "multiply" would have to - * be a multiplication of polynomials over GF(2), which differs from - * integer multiplication in that you don't have any carries. In other - * words, you make a copy of one input shifted left by the index of - * each set bit in the other, so that adding them all together would - * give you the ordinary integer product, and then you XOR them - * together instead. - * - * With a 4-bit multiplier, the two kinds of multiplication coincide - * provided the multiplicand has no two set bits at positions - * differing by less than 4, because then no two copies of the - * multiplier can overlap to generate a carry. So I break up the - * intended multiplicand K = 0xEDB88320 >> 3 into three sub-constants - * a,b,c with that property, such that a^b^c = K. Then I can multiply - * m by each of them separately, and XOR together the results. - */ -static inline uint32_t crc32_shift_4(uint32_t v) -{ - const uint32_t a = 0x11111044, b = 0x08840020, c = 0x04220000; - uint32_t m = v & 0xF; - return (v >> 4) ^ (a*m) ^ (b*m) ^ (c*m); -} - -/* - * The 8-bit shift you need every time you absorb an input byte, - * implemented simply by iterating the 4-bit shift twice. - */ -static inline uint32_t crc32_shift_8(uint32_t v) -{ - return crc32_shift_4(crc32_shift_4(v)); -} - -/* - * Update an existing hash value with extra bytes of data. - */ -uint32_t crc32_update(uint32_t crc, ptrlen data) -{ - const uint8_t *p = (const uint8_t *)data.ptr; - for (size_t len = data.len; len-- > 0 ;) - crc = crc32_shift_8(crc ^ *p++); - return crc; -} - -/* - * The SSH-1 variant of CRC-32. - */ -uint32_t crc32_ssh1(ptrlen data) -{ - return crc32_update(0, data); -} - -/* - * The official version of CRC-32. Nothing in PuTTY proper uses this, - * but it's useful to expose it to testcrypt so that we can implement - * standard test vectors. - */ -uint32_t crc32_rfc1662(ptrlen data) -{ - return crc32_update(0xFFFFFFFF, data) ^ 0xFFFFFFFF; -} diff --git a/sshdes.c b/sshdes.c deleted file mode 100644 index d2ba9825..00000000 --- a/sshdes.c +++ /dev/null @@ -1,1048 +0,0 @@ -/* - * sshdes.c: implementation of DES. - */ - -/* - * Background - * ---------- - * - * The basic structure of DES is a Feistel network: the 64-bit cipher - * block is divided into two 32-bit halves L and R, and in each round, - * a mixing function is applied to one of them, the result is XORed - * into the other, and then the halves are swapped so that the other - * one will be the input to the mixing function next time. (This - * structure guarantees reversibility no matter whether the mixing - * function itself is bijective.) - * - * The mixing function for DES goes like this: - * + Extract eight contiguous 6-bit strings from the 32-bit word. - * They start at positions 4 bits apart, so each string overlaps - * the next one by one bit. At least one has to wrap cyclically - * round the end of the word. - * + XOR each of those strings with 6 bits of data from the key - * schedule (which consists of 8 x 6-bit strings per round). - * + Use the resulting 6-bit numbers as the indices into eight - * different lookup tables ('S-boxes'), each of which delivers a - * 4-bit output. - * + Concatenate those eight 4-bit values into a 32-bit word. - * + Finally, apply a fixed permutation P to that word. - * - * DES adds one more wrinkle on top of this structure, which is to - * conjugate it by a bitwise permutation of the cipher block. That is, - * before starting the main cipher rounds, the input bits are permuted - * according to a 64-bit permutation called IP, and after the rounds - * are finished, the output bits are permuted back again by applying - * the inverse of IP. - * - * This gives a lot of leeway to redefine the components of the cipher - * without actually changing the input and output. You could permute - * the bits in the output of any or all of the S-boxes, or reorder the - * S-boxes among themselves, and adjust the following permutation P to - * compensate. And you could adjust IP by post-composing a rotation of - * each 32-bit half, and adjust the starting offsets of the 6-bit - * S-box indices to compensate. - * - * test/desref.py demonstrates this by providing two equivalent forms - * of the cipher, called DES and SGTDES, which give the same output. - * DES is the form described in the original spec: if you make it - * print diagnostic output during the cipher and check it against the - * original, you should recognise the S-box outputs as matching the - * ones you expect. But SGTDES, which I egotistically name after - * myself, is much closer to the form implemented here: I've changed - * the permutation P to suit my implementation strategy and - * compensated by permuting the S-boxes, and also I've added a - * rotation right by 1 bit to IP so that only one S-box index has to - * wrap round the word and also so that the indices are nicely aligned - * for the constant-time selection system I'm using. - */ - -#include - -#include "ssh.h" -#include "mpint_i.h" /* we reuse the BignumInt system */ - -/* If you compile with -DDES_DIAGNOSTICS, intermediate results will be - * sent to debug() (so you also need to compile with -DDEBUG). - * Otherwise this ifdef will condition away all the debug() calls. */ -#ifndef DES_DIAGNOSTICS -#undef debug -#define debug(...) ((void)0) -#endif - -/* - * General utility functions. - */ -static inline uint32_t rol(uint32_t x, unsigned c) -{ - return (x << (31 & c)) | (x >> (31 & -c)); -} -static inline uint32_t ror(uint32_t x, unsigned c) -{ - return rol(x, -c); -} - -/* - * The hard part of doing DES in constant time is the S-box lookup. - * - * My strategy is to iterate over the whole lookup table! That's slow, - * but I don't see any way to avoid _something_ along those lines: in - * every round, every entry in every S-box is potentially needed, and - * if you can't change your memory access pattern based on the input - * data, it follows that you have to read a quantity of information - * equal to the size of all the S-boxes. (Unless they were to turn out - * to be significantly compressible, but I for one couldn't show them - * to be.) - * - * In more detail, I construct a sort of counter-based 'selection - * gadget', which is 15 bits wide and starts off with the top bit - * zero, the next eight bits all 1, and the bottom six set to the - * input S-box index: - * - * 011111111xxxxxx - * - * Now if you add 1 in the lowest bit position, then either it carries - * into the top section (resetting it to 100000000), or it doesn't do - * that yet. If you do that 64 times, then it will _guarantee_ to have - * ticked over into 100000000. In between those increments, the eight - * bits that started off as 11111111 will have stayed that way for - * some number of iterations and then become 00000000, and exactly how - * many iterations depends on the input index. - * - * The purpose of the 0 bit at the top is to absorb the carry when the - * switch happens, which means you can pack more than one gadget into - * the same machine word and have them all work in parallel without - * each one intefering with the next. - * - * The next step is to use each of those 8-bit segments as a bit mask: - * each one is ANDed with a lookup table entry, and all the results - * are XORed together. So you end up with the bitwise XOR of some - * initial segment of the table entries. And the stored S-box tables - * are transformed in such a way that the real S-box values are given - * not by the individual entries, but by the cumulative XORs - * constructed in this way. - * - * A refinement is that I increment each gadget by 2 rather than 1 - * each time, so I only iterate 32 times instead of 64. That's why - * there are 8 selection bits instead of 4: each gadget selects enough - * bits to reconstruct _two_ S-box entries, for a pair of indices - * (2n,2n+1), and then finally I use the low bit of the index to do a - * parallel selection between each of those pairs. - * - * The selection gadget is not quite 16 bits wide. So you can fit four - * of them across a 64-bit word at 16-bit intervals, which is also - * convenient because the place the S-box indices are coming from also - * has pairs of them separated by 16-bit distances, so it's easy to - * copy them into the gadgets in the first place. - */ - -/* - * The S-box data. Each pair of nonzero columns here describes one of - * the S-boxes, corresponding to the SGTDES tables in test/desref.py, - * under the following transformation. - * - * Take S-box #3 as an example. Its values in successive rows of this - * table are eb,e8,54,3d, ... So the cumulative XORs of initial - * sequences of those values are eb,(eb^e8),(eb^e8^54), ... which - * comes to eb,03,57,... Of _those_ values, the top nibble (e,0,5,...) - * gives the even-numbered entries in the S-box, in _reverse_ order - * (because a lower input index selects the XOR of a longer - * subsequence). The odd-numbered entries are given by XORing the two - * digits together: (e^b),(0^3),(5^7),... = 5,3,2,... And indeed, if - * you check SGTDES.sboxes[3] you find it ends ... 52 03 e5. - */ -#define SBOX_ITERATION(X) \ - /* 66 22 44 00 77 33 55 11 */ \ - X(0xf600970083008500, 0x0e00eb007b002e00) \ - X(0xda00e4009000e000, 0xad00e800a700b400) \ - X(0x1a009d003f003600, 0xf60054004300cd00) \ - X(0xaf00c500e900a900, 0x63003d00f2005900) \ - X(0xf300750079001400, 0x80005000a2008900) \ - X(0xa100d400d6007b00, 0xd3009000d300e100) \ - X(0x450087002600ac00, 0xae003c0031009c00) \ - X(0xd000b100b6003600, 0x3e006f0092005900) \ - X(0x4d008a0026001000, 0x89007a00b8004a00) \ - X(0xca00f5003f00ac00, 0x6f00f0003c009400) \ - X(0x92008d0090001000, 0x8c00c600ce004a00) \ - X(0xe2005900e9006d00, 0x790078007800fa00) \ - X(0x1300b10090008d00, 0xa300170027001800) \ - X(0xc70058005f006a00, 0x9c00c100e0006300) \ - X(0x9b002000f000f000, 0xf70057001600f900) \ - X(0xeb00b0009000af00, 0xa9006300b0005800) \ - X(0xa2001d00cf000000, 0x3800b00066000000) \ - X(0xf100da007900d000, 0xbc00790094007900) \ - X(0x570015001900ad00, 0x6f00ef005100cb00) \ - X(0xc3006100e9006d00, 0xc000b700f800f200) \ - X(0x1d005800b600d000, 0x67004d00cd002c00) \ - X(0xf400b800d600e000, 0x5e00a900b000e700) \ - X(0x5400d1003f009c00, 0xc90069002c005300) \ - X(0xe200e50060005900, 0x6a00b800c500f200) \ - X(0xdf0047007900d500, 0x7000ec004c00ea00) \ - X(0x7100d10060009c00, 0x3f00b10095005e00) \ - X(0x82008200f0002000, 0x87001d00cd008000) \ - X(0xd0007000af00c000, 0xe200be006100f200) \ - X(0x8000930060001000, 0x36006e0081001200) \ - X(0x6500a300d600ac00, 0xcf003d007d00c000) \ - X(0x9000700060009800, 0x62008100ad009200) \ - X(0xe000e4003f00f400, 0x5a00ed009000f200) \ - /* end of list */ - -/* - * The S-box mapping function. Expects two 32-bit input words: si6420 - * contains the table indices for S-boxes 0,2,4,6 with their low bits - * starting at position 2 (for S-box 0) and going up in steps of 8. - * si7531 has indices 1,3,5,7 in the same bit positions. - */ -static inline uint32_t des_S(uint32_t si6420, uint32_t si7531) -{ - debug("sindices: %02x %02x %02x %02x %02x %02x %02x %02x\n", - 0x3F & (si6420 >> 2), 0x3F & (si7531 >> 2), - 0x3F & (si6420 >> 10), 0x3F & (si7531 >> 10), - 0x3F & (si6420 >> 18), 0x3F & (si7531 >> 18), - 0x3F & (si6420 >> 26), 0x3F & (si7531 >> 26)); - -#ifdef SIXTY_FOUR_BIT - /* - * On 64-bit machines, we store the table in exactly the form - * shown above, and make two 64-bit words containing four - * selection gadgets each. - */ - - /* Set up the gadgets. The 'cNNNN' variables will be gradually - * incremented, and the bits in positions FF00FF00FF00FF00 will - * act as selectors for the words in the table. - * - * A side effect of moving the input indices further apart is that - * they change order, because it's easier to keep a pair that were - * originally 16 bits apart still 16 bits apart, which now makes - * them adjacent instead of separated by one. So the fact that - * si6420 turns into c6240 (with the 2,4 reversed) is not a typo! - * This will all be undone when we rebuild the output word later. - */ - uint64_t c6240 = ((si6420 | ((uint64_t)si6420 << 24)) - & 0x00FC00FC00FC00FC) | 0xFF00FF00FF00FF00; - uint64_t c7351 = ((si7531 | ((uint64_t)si7531 << 24)) - & 0x00FC00FC00FC00FC) | 0xFF00FF00FF00FF00; - debug("S in: c6240=%016"PRIx64" c7351=%016"PRIx64"\n", c6240, c7351); - - /* Iterate over the table. The 'sNNNN' variables accumulate the - * XOR of all the table entries not masked out. */ - static const struct tbl { uint64_t t6240, t7351; } tbl[32] = { -#define TABLE64(a, b) { a, b }, - SBOX_ITERATION(TABLE64) -#undef TABLE64 - }; - uint64_t s6240 = 0, s7351 = 0; - for (const struct tbl *t = tbl, *limit = tbl + 32; t < limit; t++) { - s6240 ^= c6240 & t->t6240; c6240 += 0x0008000800080008; - s7351 ^= c7351 & t->t7351; c7351 += 0x0008000800080008; - } - debug("S out: s6240=%016"PRIx64" s7351=%016"PRIx64"\n", s6240, s7351); - - /* Final selection between each even/odd pair: mask off the low - * bits of all the input indices (which haven't changed throughout - * the iteration), and multiply by a bit mask that will turn each - * set bit into a mask covering the upper nibble of the selected - * pair. Then use those masks to control which set of lower - * nibbles is XORed into the upper nibbles. */ - s6240 ^= (s6240 << 4) & ((0xf000/0x004) * (c6240 & 0x0004000400040004)); - s7351 ^= (s7351 << 4) & ((0xf000/0x004) * (c7351 & 0x0004000400040004)); - - /* Now the eight final S-box outputs are in the upper nibble of - * each selection position. Mask away the rest of the clutter. */ - s6240 &= 0xf000f000f000f000; - s7351 &= 0xf000f000f000f000; - debug("s0=%x s1=%x s2=%x s3=%x s4=%x s5=%x s6=%x s7=%x\n", - (unsigned)(0xF & (s6240 >> 12)), - (unsigned)(0xF & (s7351 >> 12)), - (unsigned)(0xF & (s6240 >> 44)), - (unsigned)(0xF & (s7351 >> 44)), - (unsigned)(0xF & (s6240 >> 28)), - (unsigned)(0xF & (s7351 >> 28)), - (unsigned)(0xF & (s6240 >> 60)), - (unsigned)(0xF & (s7351 >> 60))); - - /* Combine them all into a single 32-bit output word, which will - * come out in the order 76543210. */ - uint64_t combined = (s6240 >> 12) | (s7351 >> 8); - return combined | (combined >> 24); - -#else /* SIXTY_FOUR_BIT */ - /* - * For 32-bit platforms, we do the same thing but in four 32-bit - * words instead of two 64-bit ones, so the CPU doesn't have to - * waste time propagating carries or shifted bits between the two - * halves of a uint64 that weren't needed anyway. - */ - - /* Set up the gadgets */ - uint32_t c40 = ((si6420 ) & 0x00FC00FC) | 0xFF00FF00; - uint32_t c62 = ((si6420 >> 8) & 0x00FC00FC) | 0xFF00FF00; - uint32_t c51 = ((si7531 ) & 0x00FC00FC) | 0xFF00FF00; - uint32_t c73 = ((si7531 >> 8) & 0x00FC00FC) | 0xFF00FF00; - debug("S in: c40=%08"PRIx32" c62=%08"PRIx32 - " c51=%08"PRIx32" c73=%08"PRIx32"\n", c40, c62, c51, c73); - - /* Iterate over the table */ - static const struct tbl { uint32_t t40, t62, t51, t73; } tbl[32] = { -#define TABLE32(a, b) { ((uint32_t)a), (a>>32), ((uint32_t)b), (b>>32) }, - SBOX_ITERATION(TABLE32) -#undef TABLE32 - }; - uint32_t s40 = 0, s62 = 0, s51 = 0, s73 = 0; - for (const struct tbl *t = tbl, *limit = tbl + 32; t < limit; t++) { - s40 ^= c40 & t->t40; c40 += 0x00080008; - s62 ^= c62 & t->t62; c62 += 0x00080008; - s51 ^= c51 & t->t51; c51 += 0x00080008; - s73 ^= c73 & t->t73; c73 += 0x00080008; - } - debug("S out: s40=%08"PRIx32" s62=%08"PRIx32 - " s51=%08"PRIx32" s73=%08"PRIx32"\n", s40, s62, s51, s73); - - /* Final selection within each pair */ - s40 ^= (s40 << 4) & ((0xf000/0x004) * (c40 & 0x00040004)); - s62 ^= (s62 << 4) & ((0xf000/0x004) * (c62 & 0x00040004)); - s51 ^= (s51 << 4) & ((0xf000/0x004) * (c51 & 0x00040004)); - s73 ^= (s73 << 4) & ((0xf000/0x004) * (c73 & 0x00040004)); - - /* Clean up the clutter */ - s40 &= 0xf000f000; - s62 &= 0xf000f000; - s51 &= 0xf000f000; - s73 &= 0xf000f000; - debug("s0=%x s1=%x s2=%x s3=%x s4=%x s5=%x s6=%x s7=%x\n", - (unsigned)(0xF & (s40 >> 12)), - (unsigned)(0xF & (s51 >> 12)), - (unsigned)(0xF & (s62 >> 12)), - (unsigned)(0xF & (s73 >> 12)), - (unsigned)(0xF & (s40 >> 28)), - (unsigned)(0xF & (s51 >> 28)), - (unsigned)(0xF & (s62 >> 28)), - (unsigned)(0xF & (s73 >> 28))); - - /* Recombine and return */ - return (s40 >> 12) | (s62 >> 4) | (s51 >> 8) | (s73); - -#endif /* SIXTY_FOUR_BIT */ - -} - -/* - * Now for the permutation P. The basic strategy here is to use a - * Benes network: in each stage, the bit at position i is allowed to - * either stay where it is or swap with i ^ D, where D is a power of 2 - * that varies with each phase. (So when D=1, pairs of the form - * {2n,2n+1} can swap; when D=2, the pairs are {4n+j,4n+j+2} for - * j={0,1}, and so on.) - * - * You can recursively construct a Benes network for an arbitrary - * permutation, in which the values of D iterate across all the powers - * of 2 less than the permutation size and then go back again. For - * example, the typical presentation for 32 bits would have D iterate - * over 16,8,4,2,1,2,4,8,16, and there's an easy algorithm that can - * express any permutation in that form by deciding which pairs of - * bits to swap in the outer pair of stages and then recursing to do - * all the stages in between. - * - * Actually implementing the swaps is easy when they're all between - * bits at the same separation: make the value x ^ (x >> D), mask out - * just the bits in the low position of a pair that needs to swap, and - * then use the resulting value y to make x ^ y ^ (y << D) which is - * the swapped version. - * - * In this particular case, I processed the bit indices in the other - * order (going 1,2,4,8,16,8,4,2,1), which makes no significant - * difference to the construction algorithm (it's just a relabelling), - * but it now means that the first two steps only permute entries - * within the output of each S-box - and therefore we can leave them - * completely out, in favour of just defining the S-boxes so that - * those permutation steps are already applied. Furthermore, by - * exhaustive search over the rest of the possible bit-orders for each - * S-box, I was able to find a version of P which could be represented - * in such a way that two further phases had all their control bits - * zero and could be skipped. So the number of swap stages is reduced - * to 5 from the 9 that might have been needed. - */ - -static inline uint32_t des_benes_step(uint32_t v, unsigned D, uint32_t mask) -{ - uint32_t diff = (v ^ (v >> D)) & mask; - return v ^ diff ^ (diff << D); -} - -static inline uint32_t des_P(uint32_t v_orig) -{ - uint32_t v = v_orig; - - /* initial stages with distance 1,2 are part of the S-box data table */ - v = des_benes_step(v, 4, 0x07030702); - v = des_benes_step(v, 8, 0x004E009E); - v = des_benes_step(v, 16, 0x0000D9D3); -/* v = des_benes_step(v, 8, 0x00000000); no-op, so we can skip it */ - v = des_benes_step(v, 4, 0x05040004); -/* v = des_benes_step(v, 2, 0x00000000); no-op, so we can skip it */ - v = des_benes_step(v, 1, 0x04045015); - - debug("P(%08"PRIx32") = %08"PRIx32"\n", v_orig, v); - - return v; -} - -/* - * Putting the S and P functions together, and adding in the round key - * as well, gives us the full mixing function f. - */ - -static inline uint32_t des_f(uint32_t R, uint32_t K7531, uint32_t K6420) -{ - uint32_t s7531 = R ^ K7531, s6420 = rol(R, 4) ^ K6420; - return des_P(des_S(s6420, s7531)); -} - -/* - * The key schedule, and the function to set it up. - */ - -typedef struct des_keysched des_keysched; -struct des_keysched { - uint32_t k7531[16], k6420[16]; -}; - -/* - * Simplistic function to select an arbitrary sequence of bits from - * one value and glue them together into another value. bitnums[] - * gives the sequence of bit indices of the input, from the highest - * output bit downwards. An index of -1 means that output bit is left - * at zero. - * - * This function is only used during key setup, so it doesn't need to - * be highly optimised. - */ -static inline uint64_t bitsel( - uint64_t input, const int8_t *bitnums, size_t size) -{ - uint64_t ret = 0; - while (size-- > 0) { - int bitpos = *bitnums++; - ret <<= 1; - if (bitpos >= 0) - ret |= 1 & (input >> bitpos); - } - return ret; -} - -static void des_key_setup(uint64_t key, des_keysched *sched) -{ - static const int8_t PC1[] = { - 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, - -1, -1, -1, -1, - 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, - }; - static const int8_t PC2_7531[] = { - 46, 43, 49, 36, 59, 55, -1, -1, /* index into S-box 7 */ - 37, 41, 48, 56, 34, 52, -1, -1, /* index into S-box 5 */ - 15, 4, 25, 19, 9, 1, -1, -1, /* index into S-box 3 */ - 12, 7, 17, 0, 22, 3, -1, -1, /* index into S-box 1 */ - }; - static const int8_t PC2_6420[] = { - 57, 32, 45, 54, 39, 50, -1, -1, /* index into S-box 6 */ - 44, 53, 33, 40, 47, 58, -1, -1, /* index into S-box 4 */ - 26, 16, 5, 11, 23, 8, -1, -1, /* index into S-box 2 */ - 10, 14, 6, 20, 27, 24, -1, -1, /* index into S-box 0 */ - }; - static const int leftshifts[] = {1,1,2,2,2,2,2,2,1,2,2,2,2,2,2,1}; - - /* Select 56 bits from the 64-bit input key integer (the low bit - * of each input byte is unused), into a word consisting of two - * 28-bit integers starting at bits 0 and 32. */ - uint64_t CD = bitsel(key, PC1, lenof(PC1)); - - for (size_t i = 0; i < 16; i++) { - /* Rotate each 28-bit half of CD left by 1 or 2 bits (varying - * between rounds) */ - CD <<= leftshifts[i]; - CD = (CD & 0x0FFFFFFF0FFFFFFF) | ((CD & 0xF0000000F0000000) >> 28); - - /* Select key bits from the rotated word to use during the - * actual cipher */ - sched->k7531[i] = bitsel(CD, PC2_7531, lenof(PC2_7531)); - sched->k6420[i] = bitsel(CD, PC2_6420, lenof(PC2_6420)); - } -} - -/* - * Helper routines for dealing with 64-bit blocks in the form of an L - * and R word. - */ - -typedef struct LR LR; -struct LR { uint32_t L, R; }; - -static inline LR des_load_lr(const void *vp) -{ - const uint8_t *p = (const uint8_t *)vp; - LR out; - out.L = GET_32BIT_MSB_FIRST(p); - out.R = GET_32BIT_MSB_FIRST(p+4); - return out; -} - -static inline void des_store_lr(void *vp, LR lr) -{ - uint8_t *p = (uint8_t *)vp; - PUT_32BIT_MSB_FIRST(p, lr.L); - PUT_32BIT_MSB_FIRST(p+4, lr.R); -} - -static inline LR des_xor_lr(LR a, LR b) -{ - a.L ^= b.L; - a.R ^= b.R; - return a; -} - -static inline LR des_swap_lr(LR in) -{ - LR out; - out.L = in.R; - out.R = in.L; - return out; -} - -/* - * The initial and final permutations of official DES are in a - * restricted form, in which the 'before' and 'after' positions of a - * given data bit are derived from each other by permuting the bits of - * the _index_ and flipping some of them. This allows the permutation - * to be performed effectively by a method that looks rather like - * _half_ of a general Benes network, because the restricted form - * means only half of it is actually needed. - * - * _Our_ initial and final permutations include a rotation by 1 bit, - * but it's still easier to just suffix that to the standard IP/FP - * than to regenerate everything using a more general method. - * - * Because we're permuting 64 bits in this case, between two 32-bit - * words, there's a separate helper function for this code that - * doesn't look quite like des_benes_step() above. - */ - -static inline void des_bitswap_IP_FP(uint32_t *L, uint32_t *R, - unsigned D, uint32_t mask) -{ - uint32_t diff = mask & ((*R >> D) ^ *L); - *R ^= diff << D; - *L ^= diff; -} - -static inline LR des_IP(LR lr) -{ - des_bitswap_IP_FP(&lr.R, &lr.L, 4, 0x0F0F0F0F); - des_bitswap_IP_FP(&lr.R, &lr.L, 16, 0x0000FFFF); - des_bitswap_IP_FP(&lr.L, &lr.R, 2, 0x33333333); - des_bitswap_IP_FP(&lr.L, &lr.R, 8, 0x00FF00FF); - des_bitswap_IP_FP(&lr.R, &lr.L, 1, 0x55555555); - - lr.L = ror(lr.L, 1); - lr.R = ror(lr.R, 1); - - return lr; -} - -static inline LR des_FP(LR lr) -{ - lr.L = rol(lr.L, 1); - lr.R = rol(lr.R, 1); - - des_bitswap_IP_FP(&lr.R, &lr.L, 1, 0x55555555); - des_bitswap_IP_FP(&lr.L, &lr.R, 8, 0x00FF00FF); - des_bitswap_IP_FP(&lr.L, &lr.R, 2, 0x33333333); - des_bitswap_IP_FP(&lr.R, &lr.L, 16, 0x0000FFFF); - des_bitswap_IP_FP(&lr.R, &lr.L, 4, 0x0F0F0F0F); - - return lr; -} - -/* - * The main cipher functions, which are identical except that they use - * the key schedule in opposite orders. - * - * We provide a version without the initial and final permutations, - * for use in triple-DES mode (no sense undoing and redoing it in - * between the phases). - */ - -static inline LR des_round(LR in, const des_keysched *sched, size_t round) -{ - LR out; - out.L = in.R; - out.R = in.L ^ des_f(in.R, sched->k7531[round], sched->k6420[round]); - return out; -} - -static inline LR des_inner_cipher(LR lr, const des_keysched *sched, - size_t start, size_t step) -{ - lr = des_round(lr, sched, start+0x0*step); - lr = des_round(lr, sched, start+0x1*step); - lr = des_round(lr, sched, start+0x2*step); - lr = des_round(lr, sched, start+0x3*step); - lr = des_round(lr, sched, start+0x4*step); - lr = des_round(lr, sched, start+0x5*step); - lr = des_round(lr, sched, start+0x6*step); - lr = des_round(lr, sched, start+0x7*step); - lr = des_round(lr, sched, start+0x8*step); - lr = des_round(lr, sched, start+0x9*step); - lr = des_round(lr, sched, start+0xa*step); - lr = des_round(lr, sched, start+0xb*step); - lr = des_round(lr, sched, start+0xc*step); - lr = des_round(lr, sched, start+0xd*step); - lr = des_round(lr, sched, start+0xe*step); - lr = des_round(lr, sched, start+0xf*step); - return des_swap_lr(lr); -} - -static inline LR des_full_cipher(LR lr, const des_keysched *sched, - size_t start, size_t step) -{ - lr = des_IP(lr); - lr = des_inner_cipher(lr, sched, start, step); - lr = des_FP(lr); - return lr; -} - -/* - * Parameter pairs for the start,step arguments to the cipher routines - * above, causing them to use the same key schedule in opposite orders. - */ -#define ENCIPHER 0, 1 /* for encryption */ -#define DECIPHER 15, -1 /* for decryption */ - -/* ---------------------------------------------------------------------- - * Single-DES - */ - -struct des_cbc_ctx { - des_keysched sched; - LR iv; - ssh_cipher ciph; -}; - -static ssh_cipher *des_cbc_new(const ssh_cipheralg *alg) -{ - struct des_cbc_ctx *ctx = snew(struct des_cbc_ctx); - ctx->ciph.vt = alg; - return &ctx->ciph; -} - -static void des_cbc_free(ssh_cipher *ciph) -{ - struct des_cbc_ctx *ctx = container_of(ciph, struct des_cbc_ctx, ciph); - smemclr(ctx, sizeof(*ctx)); - sfree(ctx); -} - -static void des_cbc_setkey(ssh_cipher *ciph, const void *vkey) -{ - struct des_cbc_ctx *ctx = container_of(ciph, struct des_cbc_ctx, ciph); - const uint8_t *key = (const uint8_t *)vkey; - des_key_setup(GET_64BIT_MSB_FIRST(key), &ctx->sched); -} - -static void des_cbc_setiv(ssh_cipher *ciph, const void *iv) -{ - struct des_cbc_ctx *ctx = container_of(ciph, struct des_cbc_ctx, ciph); - ctx->iv = des_load_lr(iv); -} - -static void des_cbc_encrypt(ssh_cipher *ciph, void *vdata, int len) -{ - struct des_cbc_ctx *ctx = container_of(ciph, struct des_cbc_ctx, ciph); - uint8_t *data = (uint8_t *)vdata; - for (; len > 0; len -= 8, data += 8) { - LR plaintext = des_load_lr(data); - LR cipher_in = des_xor_lr(plaintext, ctx->iv); - LR ciphertext = des_full_cipher(cipher_in, &ctx->sched, ENCIPHER); - des_store_lr(data, ciphertext); - ctx->iv = ciphertext; - } -} - -static void des_cbc_decrypt(ssh_cipher *ciph, void *vdata, int len) -{ - struct des_cbc_ctx *ctx = container_of(ciph, struct des_cbc_ctx, ciph); - uint8_t *data = (uint8_t *)vdata; - for (; len > 0; len -= 8, data += 8) { - LR ciphertext = des_load_lr(data); - LR cipher_out = des_full_cipher(ciphertext, &ctx->sched, DECIPHER); - LR plaintext = des_xor_lr(cipher_out, ctx->iv); - des_store_lr(data, plaintext); - ctx->iv = ciphertext; - } -} - -const ssh_cipheralg ssh_des = { - .new = des_cbc_new, - .free = des_cbc_free, - .setiv = des_cbc_setiv, - .setkey = des_cbc_setkey, - .encrypt = des_cbc_encrypt, - .decrypt = des_cbc_decrypt, - .ssh2_id = "des-cbc", - .blksize = 8, - .real_keybits = 56, - .padded_keybytes = 8, - .flags = SSH_CIPHER_IS_CBC, - .text_name = "single-DES CBC", -}; - -const ssh_cipheralg ssh_des_sshcom_ssh2 = { - /* Same as ssh_des_cbc, but with a different SSH-2 ID */ - .new = des_cbc_new, - .free = des_cbc_free, - .setiv = des_cbc_setiv, - .setkey = des_cbc_setkey, - .encrypt = des_cbc_encrypt, - .decrypt = des_cbc_decrypt, - .ssh2_id = "des-cbc@ssh.com", - .blksize = 8, - .real_keybits = 56, - .padded_keybytes = 8, - .flags = SSH_CIPHER_IS_CBC, - .text_name = "single-DES CBC", -}; - -static const ssh_cipheralg *const des_list[] = { - &ssh_des, - &ssh_des_sshcom_ssh2 -}; - -const ssh2_ciphers ssh2_des = { lenof(des_list), des_list }; - -/* ---------------------------------------------------------------------- - * Triple-DES CBC, SSH-2 style. The CBC mode treats the three - * invocations of DES as a single unified cipher, and surrounds it - * with just one layer of CBC, so only one IV is needed. - */ - -struct des3_cbc1_ctx { - des_keysched sched[3]; - LR iv; - ssh_cipher ciph; -}; - -static ssh_cipher *des3_cbc1_new(const ssh_cipheralg *alg) -{ - struct des3_cbc1_ctx *ctx = snew(struct des3_cbc1_ctx); - ctx->ciph.vt = alg; - return &ctx->ciph; -} - -static void des3_cbc1_free(ssh_cipher *ciph) -{ - struct des3_cbc1_ctx *ctx = container_of(ciph, struct des3_cbc1_ctx, ciph); - smemclr(ctx, sizeof(*ctx)); - sfree(ctx); -} - -static void des3_cbc1_setkey(ssh_cipher *ciph, const void *vkey) -{ - struct des3_cbc1_ctx *ctx = container_of(ciph, struct des3_cbc1_ctx, ciph); - const uint8_t *key = (const uint8_t *)vkey; - for (size_t i = 0; i < 3; i++) - des_key_setup(GET_64BIT_MSB_FIRST(key + 8*i), &ctx->sched[i]); -} - -static void des3_cbc1_setiv(ssh_cipher *ciph, const void *iv) -{ - struct des3_cbc1_ctx *ctx = container_of(ciph, struct des3_cbc1_ctx, ciph); - ctx->iv = des_load_lr(iv); -} - -static void des3_cbc1_cbc_encrypt(ssh_cipher *ciph, void *vdata, int len) -{ - struct des3_cbc1_ctx *ctx = container_of(ciph, struct des3_cbc1_ctx, ciph); - uint8_t *data = (uint8_t *)vdata; - for (; len > 0; len -= 8, data += 8) { - LR plaintext = des_load_lr(data); - LR cipher_in = des_xor_lr(plaintext, ctx->iv); - - /* Run three copies of the cipher, without undoing and redoing - * IP/FP in between. */ - LR lr = des_IP(cipher_in); - lr = des_inner_cipher(lr, &ctx->sched[0], ENCIPHER); - lr = des_inner_cipher(lr, &ctx->sched[1], DECIPHER); - lr = des_inner_cipher(lr, &ctx->sched[2], ENCIPHER); - LR ciphertext = des_FP(lr); - - des_store_lr(data, ciphertext); - ctx->iv = ciphertext; - } -} - -static void des3_cbc1_cbc_decrypt(ssh_cipher *ciph, void *vdata, int len) -{ - struct des3_cbc1_ctx *ctx = container_of(ciph, struct des3_cbc1_ctx, ciph); - uint8_t *data = (uint8_t *)vdata; - for (; len > 0; len -= 8, data += 8) { - LR ciphertext = des_load_lr(data); - - /* Similarly to encryption, but with the order reversed. */ - LR lr = des_IP(ciphertext); - lr = des_inner_cipher(lr, &ctx->sched[2], DECIPHER); - lr = des_inner_cipher(lr, &ctx->sched[1], ENCIPHER); - lr = des_inner_cipher(lr, &ctx->sched[0], DECIPHER); - LR cipher_out = des_FP(lr); - - LR plaintext = des_xor_lr(cipher_out, ctx->iv); - des_store_lr(data, plaintext); - ctx->iv = ciphertext; - } -} - -const ssh_cipheralg ssh_3des_ssh2 = { - .new = des3_cbc1_new, - .free = des3_cbc1_free, - .setiv = des3_cbc1_setiv, - .setkey = des3_cbc1_setkey, - .encrypt = des3_cbc1_cbc_encrypt, - .decrypt = des3_cbc1_cbc_decrypt, - .ssh2_id = "3des-cbc", - .blksize = 8, - .real_keybits = 168, - .padded_keybytes = 24, - .flags = SSH_CIPHER_IS_CBC, - .text_name = "triple-DES CBC", -}; - -/* ---------------------------------------------------------------------- - * Triple-DES in SDCTR mode. Again, the three DES instances are - * treated as one big cipher, with a single counter encrypted through - * all three. - */ - -#define SDCTR_WORDS (8 / BIGNUM_INT_BYTES) - -struct des3_sdctr_ctx { - des_keysched sched[3]; - BignumInt counter[SDCTR_WORDS]; - ssh_cipher ciph; -}; - -static ssh_cipher *des3_sdctr_new(const ssh_cipheralg *alg) -{ - struct des3_sdctr_ctx *ctx = snew(struct des3_sdctr_ctx); - ctx->ciph.vt = alg; - return &ctx->ciph; -} - -static void des3_sdctr_free(ssh_cipher *ciph) -{ - struct des3_sdctr_ctx *ctx = container_of( - ciph, struct des3_sdctr_ctx, ciph); - smemclr(ctx, sizeof(*ctx)); - sfree(ctx); -} - -static void des3_sdctr_setkey(ssh_cipher *ciph, const void *vkey) -{ - struct des3_sdctr_ctx *ctx = container_of( - ciph, struct des3_sdctr_ctx, ciph); - const uint8_t *key = (const uint8_t *)vkey; - for (size_t i = 0; i < 3; i++) - des_key_setup(GET_64BIT_MSB_FIRST(key + 8*i), &ctx->sched[i]); -} - -static void des3_sdctr_setiv(ssh_cipher *ciph, const void *viv) -{ - struct des3_sdctr_ctx *ctx = container_of( - ciph, struct des3_sdctr_ctx, ciph); - const uint8_t *iv = (const uint8_t *)viv; - - /* Import the initial counter value into the internal representation */ - for (unsigned i = 0; i < SDCTR_WORDS; i++) - ctx->counter[i] = GET_BIGNUMINT_MSB_FIRST( - iv + 8 - BIGNUM_INT_BYTES - i*BIGNUM_INT_BYTES); -} - -static void des3_sdctr_encrypt_decrypt(ssh_cipher *ciph, void *vdata, int len) -{ - struct des3_sdctr_ctx *ctx = container_of( - ciph, struct des3_sdctr_ctx, ciph); - uint8_t *data = (uint8_t *)vdata; - uint8_t iv_buf[8]; - for (; len > 0; len -= 8, data += 8) { - /* Format the counter value into the buffer. */ - for (unsigned i = 0; i < SDCTR_WORDS; i++) - PUT_BIGNUMINT_MSB_FIRST( - iv_buf + 8 - BIGNUM_INT_BYTES - i*BIGNUM_INT_BYTES, - ctx->counter[i]); - - /* Increment the counter. */ - BignumCarry carry = 1; - for (unsigned i = 0; i < SDCTR_WORDS; i++) - BignumADC(ctx->counter[i], carry, ctx->counter[i], 0, carry); - - /* Triple-encrypt the counter value from the IV. */ - LR lr = des_IP(des_load_lr(iv_buf)); - lr = des_inner_cipher(lr, &ctx->sched[0], ENCIPHER); - lr = des_inner_cipher(lr, &ctx->sched[1], DECIPHER); - lr = des_inner_cipher(lr, &ctx->sched[2], ENCIPHER); - LR keystream = des_FP(lr); - - LR input = des_load_lr(data); - LR output = des_xor_lr(input, keystream); - des_store_lr(data, output); - } - smemclr(iv_buf, sizeof(iv_buf)); -} - -const ssh_cipheralg ssh_3des_ssh2_ctr = { - .new = des3_sdctr_new, - .free = des3_sdctr_free, - .setiv = des3_sdctr_setiv, - .setkey = des3_sdctr_setkey, - .encrypt = des3_sdctr_encrypt_decrypt, - .decrypt = des3_sdctr_encrypt_decrypt, - .ssh2_id = "3des-ctr", - .blksize = 8, - .real_keybits = 168, - .padded_keybytes = 24, - .flags = 0, - .text_name = "triple-DES SDCTR", -}; - -static const ssh_cipheralg *const des3_list[] = { - &ssh_3des_ssh2_ctr, - &ssh_3des_ssh2 -}; - -const ssh2_ciphers ssh2_3des = { lenof(des3_list), des3_list }; - -/* ---------------------------------------------------------------------- - * Triple-DES, SSH-1 style. SSH-1 replicated the whole CBC structure - * three times, so there have to be three separate IVs, one in each - * layer. - */ - -struct des3_cbc3_ctx { - des_keysched sched[3]; - LR iv[3]; - ssh_cipher ciph; -}; - -static ssh_cipher *des3_cbc3_new(const ssh_cipheralg *alg) -{ - struct des3_cbc3_ctx *ctx = snew(struct des3_cbc3_ctx); - ctx->ciph.vt = alg; - return &ctx->ciph; -} - -static void des3_cbc3_free(ssh_cipher *ciph) -{ - struct des3_cbc3_ctx *ctx = container_of(ciph, struct des3_cbc3_ctx, ciph); - smemclr(ctx, sizeof(*ctx)); - sfree(ctx); -} - -static void des3_cbc3_setkey(ssh_cipher *ciph, const void *vkey) -{ - struct des3_cbc3_ctx *ctx = container_of(ciph, struct des3_cbc3_ctx, ciph); - const uint8_t *key = (const uint8_t *)vkey; - for (size_t i = 0; i < 3; i++) - des_key_setup(GET_64BIT_MSB_FIRST(key + 8*i), &ctx->sched[i]); -} - -static void des3_cbc3_setiv(ssh_cipher *ciph, const void *viv) -{ - struct des3_cbc3_ctx *ctx = container_of(ciph, struct des3_cbc3_ctx, ciph); - - /* - * In principle, we ought to provide an interface for the user to - * input 24 instead of 8 bytes of IV. But that would make this an - * ugly exception to the otherwise universal rule that IV size = - * cipher block size, and there's really no need to violate that - * rule given that this is a historical one-off oddity and SSH-1 - * always initialises all three IVs to zero anyway. So we fudge it - * by just setting all the IVs to the same value. - */ - - LR iv = des_load_lr(viv); - - /* But we store the IVs in permuted form, so that we can handle - * all three CBC layers without having to do IP/FP in between. */ - iv = des_IP(iv); - for (size_t i = 0; i < 3; i++) - ctx->iv[i] = iv; -} - -static void des3_cbc3_cbc_encrypt(ssh_cipher *ciph, void *vdata, int len) -{ - struct des3_cbc3_ctx *ctx = container_of(ciph, struct des3_cbc3_ctx, ciph); - uint8_t *data = (uint8_t *)vdata; - for (; len > 0; len -= 8, data += 8) { - /* Load and IP the input. */ - LR plaintext = des_IP(des_load_lr(data)); - LR lr = plaintext; - - /* Do three passes of CBC, with the middle one inverted. */ - - lr = des_xor_lr(lr, ctx->iv[0]); - lr = des_inner_cipher(lr, &ctx->sched[0], ENCIPHER); - ctx->iv[0] = lr; - - LR ciphertext = lr; - lr = des_inner_cipher(ciphertext, &ctx->sched[1], DECIPHER); - lr = des_xor_lr(lr, ctx->iv[1]); - ctx->iv[1] = ciphertext; - - lr = des_xor_lr(lr, ctx->iv[2]); - lr = des_inner_cipher(lr, &ctx->sched[2], ENCIPHER); - ctx->iv[2] = lr; - - des_store_lr(data, des_FP(lr)); - } -} - -static void des3_cbc3_cbc_decrypt(ssh_cipher *ciph, void *vdata, int len) -{ - struct des3_cbc3_ctx *ctx = container_of(ciph, struct des3_cbc3_ctx, ciph); - uint8_t *data = (uint8_t *)vdata; - for (; len > 0; len -= 8, data += 8) { - /* Load and IP the input */ - LR lr = des_IP(des_load_lr(data)); - LR ciphertext; - - /* Do three passes of CBC, with the middle one inverted. */ - ciphertext = lr; - lr = des_inner_cipher(ciphertext, &ctx->sched[2], DECIPHER); - lr = des_xor_lr(lr, ctx->iv[2]); - ctx->iv[2] = ciphertext; - - lr = des_xor_lr(lr, ctx->iv[1]); - lr = des_inner_cipher(lr, &ctx->sched[1], ENCIPHER); - ctx->iv[1] = lr; - - ciphertext = lr; - lr = des_inner_cipher(ciphertext, &ctx->sched[0], DECIPHER); - lr = des_xor_lr(lr, ctx->iv[0]); - ctx->iv[0] = ciphertext; - - des_store_lr(data, des_FP(lr)); - } -} - -const ssh_cipheralg ssh_3des_ssh1 = { - .new = des3_cbc3_new, - .free = des3_cbc3_free, - .setiv = des3_cbc3_setiv, - .setkey = des3_cbc3_setkey, - .encrypt = des3_cbc3_cbc_encrypt, - .decrypt = des3_cbc3_cbc_decrypt, - .blksize = 8, - .real_keybits = 168, - .padded_keybytes = 24, - .flags = SSH_CIPHER_IS_CBC, - .text_name = "triple-DES inner-CBC", -}; diff --git a/sshdh.c b/sshdh.c deleted file mode 100644 index b3756120..00000000 --- a/sshdh.c +++ /dev/null @@ -1,272 +0,0 @@ -/* - * Diffie-Hellman implementation for PuTTY. - */ - -#include - -#include "ssh.h" -#include "misc.h" -#include "mpint.h" - -struct dh_ctx { - mp_int *x, *e, *p, *q, *g; -}; - -struct dh_extra { - bool gex; - void (*construct)(dh_ctx *ctx); -}; - -static void dh_group1_construct(dh_ctx *ctx) -{ - ctx->p = MP_LITERAL(0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF); - ctx->g = mp_from_integer(2); -} - -static void dh_group14_construct(dh_ctx *ctx) -{ - ctx->p = MP_LITERAL(0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF); - ctx->g = mp_from_integer(2); -} - -static const struct dh_extra extra_group1 = { - false, dh_group1_construct, -}; - -static const ssh_kex ssh_diffiehellman_group1_sha1 = { - "diffie-hellman-group1-sha1", "group1", - KEXTYPE_DH, &ssh_sha1, &extra_group1, -}; - -static const ssh_kex *const group1_list[] = { - &ssh_diffiehellman_group1_sha1 -}; - -const ssh_kexes ssh_diffiehellman_group1 = { lenof(group1_list), group1_list }; - -static const struct dh_extra extra_group14 = { - false, dh_group14_construct, -}; - -static const ssh_kex ssh_diffiehellman_group14_sha256 = { - "diffie-hellman-group14-sha256", "group14", - KEXTYPE_DH, &ssh_sha256, &extra_group14, -}; - -static const ssh_kex ssh_diffiehellman_group14_sha1 = { - "diffie-hellman-group14-sha1", "group14", - KEXTYPE_DH, &ssh_sha1, &extra_group14, -}; - -static const ssh_kex *const group14_list[] = { - &ssh_diffiehellman_group14_sha256, - &ssh_diffiehellman_group14_sha1 -}; - -const ssh_kexes ssh_diffiehellman_group14 = { - lenof(group14_list), group14_list -}; - -static const struct dh_extra extra_gex = { true }; - -static const ssh_kex ssh_diffiehellman_gex_sha256 = { - "diffie-hellman-group-exchange-sha256", NULL, - KEXTYPE_DH, &ssh_sha256, &extra_gex, -}; - -static const ssh_kex ssh_diffiehellman_gex_sha1 = { - "diffie-hellman-group-exchange-sha1", NULL, - KEXTYPE_DH, &ssh_sha1, &extra_gex, -}; - -static const ssh_kex *const gex_list[] = { - &ssh_diffiehellman_gex_sha256, - &ssh_diffiehellman_gex_sha1 -}; - -const ssh_kexes ssh_diffiehellman_gex = { lenof(gex_list), gex_list }; - -/* - * Suffix on GSSAPI SSH protocol identifiers that indicates Kerberos 5 - * as the mechanism. - * - * This suffix is the base64-encoded MD5 hash of the byte sequence - * 06 09 2A 86 48 86 F7 12 01 02 02, which in turn is the ASN.1 DER - * encoding of the object ID 1.2.840.113554.1.2.2 which designates - * Kerberos v5. - * - * (The same encoded OID, minus the two-byte DER header, is defined in - * pgssapi.c as GSS_MECH_KRB5.) - */ -#define GSS_KRB5_OID_HASH "toWM5Slw5Ew8Mqkay+al2g==" - -static const ssh_kex ssh_gssk5_diffiehellman_gex_sha1 = { - "gss-gex-sha1-" GSS_KRB5_OID_HASH, NULL, - KEXTYPE_GSS, &ssh_sha1, &extra_gex, -}; - -static const ssh_kex ssh_gssk5_diffiehellman_group14_sha1 = { - "gss-group14-sha1-" GSS_KRB5_OID_HASH, "group14", - KEXTYPE_GSS, &ssh_sha1, &extra_group14, -}; - -static const ssh_kex ssh_gssk5_diffiehellman_group1_sha1 = { - "gss-group1-sha1-" GSS_KRB5_OID_HASH, "group1", - KEXTYPE_GSS, &ssh_sha1, &extra_group1, -}; - -static const ssh_kex *const gssk5_sha1_kex_list[] = { - &ssh_gssk5_diffiehellman_gex_sha1, - &ssh_gssk5_diffiehellman_group14_sha1, - &ssh_gssk5_diffiehellman_group1_sha1 -}; - -const ssh_kexes ssh_gssk5_sha1_kex = { - lenof(gssk5_sha1_kex_list), gssk5_sha1_kex_list -}; - -/* - * Common DH initialisation. - */ -static void dh_init(dh_ctx *ctx) -{ - ctx->q = mp_rshift_fixed(ctx->p, 1); - ctx->x = ctx->e = NULL; -} - -bool dh_is_gex(const ssh_kex *kex) -{ - const struct dh_extra *extra = (const struct dh_extra *)kex->extra; - return extra->gex; -} - -/* - * Initialise DH for a standard group. - */ -dh_ctx *dh_setup_group(const ssh_kex *kex) -{ - const struct dh_extra *extra = (const struct dh_extra *)kex->extra; - assert(!extra->gex); - dh_ctx *ctx = snew(dh_ctx); - extra->construct(ctx); - dh_init(ctx); - return ctx; -} - -/* - * Initialise DH for a server-supplied group. - */ -dh_ctx *dh_setup_gex(mp_int *pval, mp_int *gval) -{ - dh_ctx *ctx = snew(dh_ctx); - ctx->p = mp_copy(pval); - ctx->g = mp_copy(gval); - dh_init(ctx); - return ctx; -} - -/* - * Return size of DH modulus p. - */ -int dh_modulus_bit_size(const dh_ctx *ctx) -{ - return mp_get_nbits(ctx->p); -} - -/* - * Clean up and free a context. - */ -void dh_cleanup(dh_ctx *ctx) -{ - if (ctx->x) - mp_free(ctx->x); - if (ctx->e) - mp_free(ctx->e); - if (ctx->p) - mp_free(ctx->p); - if (ctx->g) - mp_free(ctx->g); - if (ctx->q) - mp_free(ctx->q); - 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. - */ -mp_int *dh_create_e(dh_ctx *ctx, int nbits) -{ - /* - * Lower limit is just 2. - */ - mp_int *lo = mp_from_integer(2); - - /* - * Upper limit. - */ - mp_int *hi = mp_copy(ctx->q); - mp_sub_integer_into(hi, hi, 1); - if (nbits) { - mp_int *pow2 = mp_power_2(nbits+1); - mp_min_into(pow2, pow2, hi); - mp_free(hi); - hi = pow2; - } - - /* - * Make a random number in that range. - */ - ctx->x = mp_random_in_range(lo, hi); - mp_free(lo); - mp_free(hi); - - /* - * Now compute e = g^x mod p. - */ - ctx->e = mp_modpow(ctx->g, ctx->x, ctx->p); - - return ctx->e; -} - -/* - * DH stage 2-epsilon: given a number f, validate it to ensure it's in - * range. (RFC 4253 section 8: "Values of 'e' or 'f' that are not in - * the range [1, p-1] MUST NOT be sent or accepted by either side." - * Also, we rule out 1 and p-1 too, since that's easy to do and since - * they lead to obviously weak keys that even a passive eavesdropper - * can figure out.) - */ -const char *dh_validate_f(dh_ctx *ctx, mp_int *f) -{ - if (!mp_hs_integer(f, 2)) { - return "f value received is too small"; - } else { - mp_int *pm1 = mp_copy(ctx->p); - mp_sub_integer_into(pm1, pm1, 1); - unsigned cmp = mp_cmp_hs(f, pm1); - mp_free(pm1); - if (cmp) - return "f value received is too large"; - } - return NULL; -} - -/* - * DH stage 2: given a number f, compute K = f^x mod p. - */ -mp_int *dh_find_K(dh_ctx *ctx, mp_int *f) -{ - return mp_modpow(f, ctx->x, ctx->p); -} diff --git a/sshdss.c b/sshdss.c deleted file mode 100644 index 3e0c7618..00000000 --- a/sshdss.c +++ /dev/null @@ -1,503 +0,0 @@ -/* - * Digital Signature Standard implementation for PuTTY. - */ - -#include -#include -#include - -#include "ssh.h" -#include "mpint.h" -#include "misc.h" - -static void dss_freekey(ssh_key *key); /* forward reference */ - -static ssh_key *dss_new_pub(const ssh_keyalg *self, ptrlen data) -{ - BinarySource src[1]; - struct dss_key *dss; - - BinarySource_BARE_INIT_PL(src, data); - if (!ptrlen_eq_string(get_string(src), "ssh-dss")) - return NULL; - - dss = snew(struct dss_key); - dss->sshk.vt = &ssh_dss; - dss->p = get_mp_ssh2(src); - dss->q = get_mp_ssh2(src); - dss->g = get_mp_ssh2(src); - dss->y = get_mp_ssh2(src); - dss->x = NULL; - - if (get_err(src) || - mp_eq_integer(dss->p, 0) || mp_eq_integer(dss->q, 0)) { - /* Invalid key. */ - dss_freekey(&dss->sshk); - return NULL; - } - - return &dss->sshk; -} - -static void dss_freekey(ssh_key *key) -{ - struct dss_key *dss = container_of(key, struct dss_key, sshk); - if (dss->p) - mp_free(dss->p); - if (dss->q) - mp_free(dss->q); - if (dss->g) - mp_free(dss->g); - if (dss->y) - mp_free(dss->y); - if (dss->x) - mp_free(dss->x); - sfree(dss); -} - -static void append_hex_to_strbuf(strbuf *sb, mp_int *x) -{ - if (sb->len > 0) - put_byte(sb, ','); - put_data(sb, "0x", 2); - char *hex = mp_get_hex(x); - size_t hexlen = strlen(hex); - put_data(sb, hex, hexlen); - smemclr(hex, hexlen); - sfree(hex); -} - -static char *dss_cache_str(ssh_key *key) -{ - struct dss_key *dss = container_of(key, struct dss_key, sshk); - strbuf *sb = strbuf_new(); - - if (!dss->p) { - strbuf_free(sb); - return NULL; - } - - append_hex_to_strbuf(sb, dss->p); - append_hex_to_strbuf(sb, dss->q); - append_hex_to_strbuf(sb, dss->g); - append_hex_to_strbuf(sb, dss->y); - - return strbuf_to_str(sb); -} - -static key_components *dss_components(ssh_key *key) -{ - struct dss_key *dss = container_of(key, struct dss_key, sshk); - key_components *kc = key_components_new(); - - key_components_add_text(kc, "key_type", "DSA"); - assert(dss->p); - key_components_add_mp(kc, "p", dss->p); - key_components_add_mp(kc, "q", dss->q); - key_components_add_mp(kc, "g", dss->g); - key_components_add_mp(kc, "public_y", dss->y); - if (dss->x) - key_components_add_mp(kc, "private_x", dss->x); - - return kc; -} - -static char *dss_invalid(ssh_key *key, unsigned flags) -{ - /* No validity criterion will stop us from using a DSA key at all */ - return NULL; -} - -static bool dss_verify(ssh_key *key, ptrlen sig, ptrlen data) -{ - struct dss_key *dss = container_of(key, struct dss_key, sshk); - BinarySource src[1]; - unsigned char hash[20]; - bool toret; - - if (!dss->p) - return false; - - BinarySource_BARE_INIT_PL(src, sig); - - /* - * 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 (sig.len != 40) { /* bug not present; read admin fields */ - ptrlen type = get_string(src); - sig = get_string(src); - - if (get_err(src) || !ptrlen_eq_string(type, "ssh-dss") || - sig.len != 40) - return false; - } - - /* Now we're sitting on a 40-byte string for sure. */ - mp_int *r = mp_from_bytes_be(make_ptrlen(sig.ptr, 20)); - mp_int *s = mp_from_bytes_be(make_ptrlen((const char *)sig.ptr + 20, 20)); - if (!r || !s) { - if (r) - mp_free(r); - if (s) - mp_free(s); - return false; - } - - /* Basic sanity checks: 0 < r,s < q */ - unsigned invalid = 0; - invalid |= mp_eq_integer(r, 0); - invalid |= mp_eq_integer(s, 0); - invalid |= mp_cmp_hs(r, dss->q); - invalid |= mp_cmp_hs(s, dss->q); - if (invalid) { - mp_free(r); - mp_free(s); - return false; - } - - /* - * Step 1. w <- s^-1 mod q. - */ - mp_int *w = mp_invert(s, dss->q); - if (!w) { - mp_free(r); - mp_free(s); - return false; - } - - /* - * Step 2. u1 <- SHA(message) * w mod q. - */ - hash_simple(&ssh_sha1, data, hash); - mp_int *sha = mp_from_bytes_be(make_ptrlen(hash, 20)); - mp_int *u1 = mp_modmul(sha, w, dss->q); - - /* - * Step 3. u2 <- r * w mod q. - */ - mp_int *u2 = mp_modmul(r, w, dss->q); - - /* - * Step 4. v <- (g^u1 * y^u2 mod p) mod q. - */ - mp_int *gu1p = mp_modpow(dss->g, u1, dss->p); - mp_int *yu2p = mp_modpow(dss->y, u2, dss->p); - mp_int *gu1yu2p = mp_modmul(gu1p, yu2p, dss->p); - mp_int *v = mp_mod(gu1yu2p, dss->q); - - /* - * Step 5. v should now be equal to r. - */ - - toret = mp_cmp_eq(v, r); - - mp_free(w); - mp_free(sha); - mp_free(u1); - mp_free(u2); - mp_free(gu1p); - mp_free(yu2p); - mp_free(gu1yu2p); - mp_free(v); - mp_free(r); - mp_free(s); - - return toret; -} - -static void dss_public_blob(ssh_key *key, BinarySink *bs) -{ - struct dss_key *dss = container_of(key, struct dss_key, sshk); - - put_stringz(bs, "ssh-dss"); - put_mp_ssh2(bs, dss->p); - put_mp_ssh2(bs, dss->q); - put_mp_ssh2(bs, dss->g); - put_mp_ssh2(bs, dss->y); -} - -static void dss_private_blob(ssh_key *key, BinarySink *bs) -{ - struct dss_key *dss = container_of(key, struct dss_key, sshk); - - put_mp_ssh2(bs, dss->x); -} - -static ssh_key *dss_new_priv(const ssh_keyalg *self, ptrlen pub, ptrlen priv) -{ - BinarySource src[1]; - ssh_key *sshk; - struct dss_key *dss; - ptrlen hash; - unsigned char digest[20]; - mp_int *ytest; - - sshk = dss_new_pub(self, pub); - if (!sshk) - return NULL; - - dss = container_of(sshk, struct dss_key, sshk); - BinarySource_BARE_INIT_PL(src, priv); - dss->x = get_mp_ssh2(src); - if (get_err(src)) { - dss_freekey(&dss->sshk); - return NULL; - } - - /* - * Check the obsolete hash in the old DSS key format. - */ - hash = get_string(src); - if (hash.len == 20) { - ssh_hash *h = ssh_hash_new(&ssh_sha1); - put_mp_ssh2(h, dss->p); - put_mp_ssh2(h, dss->q); - put_mp_ssh2(h, dss->g); - ssh_hash_final(h, digest); - if (!smemeq(hash.ptr, digest, 20)) { - dss_freekey(&dss->sshk); - return NULL; - } - } - - /* - * Now ensure g^x mod p really is y. - */ - ytest = mp_modpow(dss->g, dss->x, dss->p); - if (!mp_cmp_eq(ytest, dss->y)) { - mp_free(ytest); - dss_freekey(&dss->sshk); - return NULL; - } - mp_free(ytest); - - return &dss->sshk; -} - -static ssh_key *dss_new_priv_openssh(const ssh_keyalg *self, - BinarySource *src) -{ - struct dss_key *dss; - - dss = snew(struct dss_key); - dss->sshk.vt = &ssh_dss; - - dss->p = get_mp_ssh2(src); - dss->q = get_mp_ssh2(src); - dss->g = get_mp_ssh2(src); - dss->y = get_mp_ssh2(src); - dss->x = get_mp_ssh2(src); - - if (get_err(src) || - mp_eq_integer(dss->q, 0) || mp_eq_integer(dss->p, 0)) { - /* Invalid key. */ - dss_freekey(&dss->sshk); - return NULL; - } - - return &dss->sshk; -} - -static void dss_openssh_blob(ssh_key *key, BinarySink *bs) -{ - struct dss_key *dss = container_of(key, struct dss_key, sshk); - - put_mp_ssh2(bs, dss->p); - put_mp_ssh2(bs, dss->q); - put_mp_ssh2(bs, dss->g); - put_mp_ssh2(bs, dss->y); - put_mp_ssh2(bs, dss->x); -} - -static int dss_pubkey_bits(const ssh_keyalg *self, ptrlen pub) -{ - ssh_key *sshk; - struct dss_key *dss; - int ret; - - sshk = dss_new_pub(self, pub); - if (!sshk) - return -1; - - dss = container_of(sshk, struct dss_key, sshk); - ret = mp_get_nbits(dss->p); - dss_freekey(&dss->sshk); - - return ret; -} - -mp_int *dss_gen_k(const char *id_string, mp_int *modulus, - mp_int *private_key, - unsigned char *digest, int digest_len) -{ - /* - * 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. - */ - ssh_hash *h; - unsigned char digest512[64]; - - /* - * Hash some identifying text plus x. - */ - h = ssh_hash_new(&ssh_sha512); - put_asciz(h, id_string); - put_mp_ssh2(h, private_key); - ssh_hash_digest(h, digest512); - - /* - * Now hash that digest plus the message hash. - */ - ssh_hash_reset(h); - put_data(h, digest512, sizeof(digest512)); - put_data(h, digest, digest_len); - ssh_hash_final(h, digest512); - - /* - * Now convert the result into a bignum, and coerce it to the - * range [2,q), which we do by reducing it mod q-2 and adding 2. - */ - mp_int *modminus2 = mp_copy(modulus); - mp_sub_integer_into(modminus2, modminus2, 2); - mp_int *proto_k = mp_from_bytes_be(make_ptrlen(digest512, 64)); - mp_int *k = mp_mod(proto_k, modminus2); - mp_free(proto_k); - mp_free(modminus2); - mp_add_integer_into(k, k, 2); - - smemclr(digest512, sizeof(digest512)); - - return k; -} - -static void dss_sign(ssh_key *key, ptrlen data, unsigned flags, BinarySink *bs) -{ - struct dss_key *dss = container_of(key, struct dss_key, sshk); - unsigned char digest[20]; - int i; - - hash_simple(&ssh_sha1, data, digest); - - mp_int *k = dss_gen_k("DSA deterministic k generator", dss->q, dss->x, - digest, sizeof(digest)); - mp_int *kinv = mp_invert(k, dss->q); /* k^-1 mod q */ - - /* - * Now we have k, so just go ahead and compute the signature. - */ - mp_int *gkp = mp_modpow(dss->g, k, dss->p); /* g^k mod p */ - mp_int *r = mp_mod(gkp, dss->q); /* r = (g^k mod p) mod q */ - mp_free(gkp); - - mp_int *hash = mp_from_bytes_be(make_ptrlen(digest, 20)); - mp_int *xr = mp_mul(dss->x, r); - mp_int *hxr = mp_add(xr, hash); /* hash + x*r */ - mp_int *s = mp_modmul(kinv, hxr, dss->q); /* s = k^-1 * (hash+x*r) mod q */ - mp_free(hxr); - mp_free(xr); - mp_free(kinv); - mp_free(k); - mp_free(hash); - - put_stringz(bs, "ssh-dss"); - put_uint32(bs, 40); - for (i = 0; i < 20; i++) - put_byte(bs, mp_get_byte(r, 19 - i)); - for (i = 0; i < 20; i++) - put_byte(bs, mp_get_byte(s, 19 - i)); - mp_free(r); - mp_free(s); -} - -const ssh_keyalg ssh_dss = { - .new_pub = dss_new_pub, - .new_priv = dss_new_priv, - .new_priv_openssh = dss_new_priv_openssh, - .freekey = dss_freekey, - .invalid = dss_invalid, - .sign = dss_sign, - .verify = dss_verify, - .public_blob = dss_public_blob, - .private_blob = dss_private_blob, - .openssh_blob = dss_openssh_blob, - .cache_str = dss_cache_str, - .components = dss_components, - .pubkey_bits = dss_pubkey_bits, - .ssh_id = "ssh-dss", - .cache_id = "dss", -}; diff --git a/sshecc.c b/sshecc.c deleted file mode 100644 index ba580dbf..00000000 --- a/sshecc.c +++ /dev/null @@ -1,1706 +0,0 @@ -/* - * Elliptic-curve crypto module for PuTTY - * Implements the three required curves, no optional curves - * - * NOTE: Only curves on prime field are handled by the maths functions - * in Weierstrass form using Jacobian co-ordinates. - * - * Montgomery form curves are supported for DH. (Curve25519) - * - * Edwards form curves are supported for DSA. (Ed25519, Ed448) - */ - -/* - * References: - * - * Elliptic curves in SSH are specified in RFC 5656: - * http://tools.ietf.org/html/rfc5656 - * - * That specification delegates details of public key formatting and a - * lot of underlying mechanism to SEC 1: - * http://www.secg.org/sec1-v2.pdf - * - * Montgomery maths from: - * Handbook of elliptic and hyperelliptic curve cryptography, Chapter 13 - * http://cs.ucsb.edu/~koc/ccs130h/2013/EllipticHyperelliptic-CohenFrey.pdf - * - * Curve25519 spec from libssh (with reference to other things in the - * libssh code): - * https://git.libssh.org/users/aris/libssh.git/tree/doc/curve25519-sha256@libssh.org.txt - * - * Edwards DSA: - * http://ed25519.cr.yp.to/ed25519-20110926.pdf - */ - -#include -#include - -#include "ssh.h" -#include "mpint.h" -#include "ecc.h" - -/* ---------------------------------------------------------------------- - * Elliptic curve definitions - */ - -static void initialise_common( - struct ec_curve *curve, EllipticCurveType type, mp_int *p, - unsigned extrabits) -{ - curve->type = type; - curve->p = mp_copy(p); - curve->fieldBits = mp_get_nbits(p); - curve->fieldBytes = (curve->fieldBits + extrabits + 7) / 8; -} - -static void initialise_wcurve( - struct ec_curve *curve, mp_int *p, mp_int *a, mp_int *b, - mp_int *nonsquare, mp_int *G_x, mp_int *G_y, mp_int *G_order) -{ - initialise_common(curve, EC_WEIERSTRASS, p, 0); - - curve->w.wc = ecc_weierstrass_curve(p, a, b, nonsquare); - - curve->w.G = ecc_weierstrass_point_new(curve->w.wc, G_x, G_y); - curve->w.G_order = mp_copy(G_order); -} - -static void initialise_mcurve( - struct ec_curve *curve, mp_int *p, mp_int *a, mp_int *b, - mp_int *G_x, unsigned log2_cofactor) -{ - initialise_common(curve, EC_MONTGOMERY, p, 0); - - curve->m.mc = ecc_montgomery_curve(p, a, b); - curve->m.log2_cofactor = log2_cofactor; - - curve->m.G = ecc_montgomery_point_new(curve->m.mc, G_x); -} - -static void initialise_ecurve( - struct ec_curve *curve, mp_int *p, mp_int *d, mp_int *a, - mp_int *nonsquare, mp_int *G_x, mp_int *G_y, mp_int *G_order, - unsigned log2_cofactor) -{ - /* Ensure curve->fieldBytes is long enough to store an extra bit - * for a compressed point */ - initialise_common(curve, EC_EDWARDS, p, 1); - - curve->e.ec = ecc_edwards_curve(p, d, a, nonsquare); - curve->e.log2_cofactor = log2_cofactor; - - curve->e.G = ecc_edwards_point_new(curve->e.ec, G_x, G_y); - curve->e.G_order = mp_copy(G_order); -} - -static struct ec_curve *ec_p256(void) -{ - static struct ec_curve curve = { 0 }; - static bool initialised = false; - - if (!initialised) - { - mp_int *p = MP_LITERAL(0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff); - mp_int *a = MP_LITERAL(0xffffffff00000001000000000000000000000000fffffffffffffffffffffffc); - mp_int *b = MP_LITERAL(0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b); - mp_int *G_x = MP_LITERAL(0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296); - mp_int *G_y = MP_LITERAL(0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5); - mp_int *G_order = MP_LITERAL(0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551); - mp_int *nonsquare_mod_p = mp_from_integer(3); - initialise_wcurve(&curve, p, a, b, nonsquare_mod_p, G_x, G_y, G_order); - mp_free(p); - mp_free(a); - mp_free(b); - mp_free(G_x); - mp_free(G_y); - mp_free(G_order); - mp_free(nonsquare_mod_p); - - curve.textname = curve.name = "nistp256"; - - /* Now initialised, no need to do it again */ - initialised = true; - } - - return &curve; -} - -static struct ec_curve *ec_p384(void) -{ - static struct ec_curve curve = { 0 }; - static bool initialised = false; - - if (!initialised) - { - mp_int *p = MP_LITERAL(0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000ffffffff); - mp_int *a = MP_LITERAL(0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000fffffffc); - mp_int *b = MP_LITERAL(0xb3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875ac656398d8a2ed19d2a85c8edd3ec2aef); - mp_int *G_x = MP_LITERAL(0xaa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760ab7); - mp_int *G_y = MP_LITERAL(0x3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5f); - mp_int *G_order = MP_LITERAL(0xffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52973); - mp_int *nonsquare_mod_p = mp_from_integer(19); - initialise_wcurve(&curve, p, a, b, nonsquare_mod_p, G_x, G_y, G_order); - mp_free(p); - mp_free(a); - mp_free(b); - mp_free(G_x); - mp_free(G_y); - mp_free(G_order); - mp_free(nonsquare_mod_p); - - curve.textname = curve.name = "nistp384"; - - /* Now initialised, no need to do it again */ - initialised = true; - } - - return &curve; -} - -static struct ec_curve *ec_p521(void) -{ - static struct ec_curve curve = { 0 }; - static bool initialised = false; - - if (!initialised) - { - mp_int *p = MP_LITERAL(0x01ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff); - mp_int *a = MP_LITERAL(0x01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc); - mp_int *b = MP_LITERAL(0x0051953eb9618e1c9a1f929a21a0b68540eea2da725b99b315f3b8b489918ef109e156193951ec7e937b1652c0bd3bb1bf073573df883d2c34f1ef451fd46b503f00); - mp_int *G_x = MP_LITERAL(0x00c6858e06b70404e9cd9e3ecb662395b4429c648139053fb521f828af606b4d3dbaa14b5e77efe75928fe1dc127a2ffa8de3348b3c1856a429bf97e7e31c2e5bd66); - mp_int *G_y = MP_LITERAL(0x011839296a789a3bc0045c8a5fb42c7d1bd998f54449579b446817afbd17273e662c97ee72995ef42640c550b9013fad0761353c7086a272c24088be94769fd16650); - mp_int *G_order = MP_LITERAL(0x01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386409); - mp_int *nonsquare_mod_p = mp_from_integer(3); - initialise_wcurve(&curve, p, a, b, nonsquare_mod_p, G_x, G_y, G_order); - mp_free(p); - mp_free(a); - mp_free(b); - mp_free(G_x); - mp_free(G_y); - mp_free(G_order); - mp_free(nonsquare_mod_p); - - curve.textname = curve.name = "nistp521"; - - /* Now initialised, no need to do it again */ - initialised = true; - } - - return &curve; -} - -static struct ec_curve *ec_curve25519(void) -{ - static struct ec_curve curve = { 0 }; - static bool initialised = false; - - if (!initialised) - { - mp_int *p = MP_LITERAL(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed); - mp_int *a = MP_LITERAL(0x0000000000000000000000000000000000000000000000000000000000076d06); - mp_int *b = MP_LITERAL(0x0000000000000000000000000000000000000000000000000000000000000001); - mp_int *G_x = MP_LITERAL(0x0000000000000000000000000000000000000000000000000000000000000009); - initialise_mcurve(&curve, p, a, b, G_x, 3); - mp_free(p); - mp_free(a); - mp_free(b); - mp_free(G_x); - - /* This curve doesn't need a name, because it's never used in - * any format that embeds the curve name */ - curve.name = NULL; - curve.textname = "Curve25519"; - - /* Now initialised, no need to do it again */ - initialised = true; - } - - return &curve; -} - -static struct ec_curve *ec_curve448(void) -{ - static struct ec_curve curve = { 0 }; - static bool initialised = false; - - if (!initialised) - { - mp_int *p = MP_LITERAL(0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffffff); - mp_int *a = MP_LITERAL(0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000262a6); - mp_int *b = MP_LITERAL(0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001); - mp_int *G_x = MP_LITERAL(0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005); - initialise_mcurve(&curve, p, a, b, G_x, 2); - mp_free(p); - mp_free(a); - mp_free(b); - mp_free(G_x); - - /* This curve doesn't need a name, because it's never used in - * any format that embeds the curve name */ - curve.name = NULL; - curve.textname = "Curve448"; - - /* Now initialised, no need to do it again */ - initialised = true; - } - - return &curve; -} - -static struct ec_curve *ec_ed25519(void) -{ - static struct ec_curve curve = { 0 }; - static bool initialised = false; - - if (!initialised) - { - mp_int *p = MP_LITERAL(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed); - mp_int *d = MP_LITERAL(0x52036cee2b6ffe738cc740797779e89800700a4d4141d8ab75eb4dca135978a3); - mp_int *a = MP_LITERAL(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffec); /* == p-1 */ - mp_int *G_x = MP_LITERAL(0x216936d3cd6e53fec0a4e231fdd6dc5c692cc7609525a7b2c9562d608f25d51a); - mp_int *G_y = MP_LITERAL(0x6666666666666666666666666666666666666666666666666666666666666658); - mp_int *G_order = MP_LITERAL(0x1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3ed); - mp_int *nonsquare_mod_p = mp_from_integer(2); - initialise_ecurve(&curve, p, d, a, nonsquare_mod_p, - G_x, G_y, G_order, 3); - mp_free(p); - mp_free(d); - mp_free(a); - mp_free(G_x); - mp_free(G_y); - mp_free(G_order); - mp_free(nonsquare_mod_p); - - /* This curve doesn't need a name, because it's never used in - * any format that embeds the curve name */ - curve.name = NULL; - - curve.textname = "Ed25519"; - - /* Now initialised, no need to do it again */ - initialised = true; - } - - return &curve; -} - -static struct ec_curve *ec_ed448(void) -{ - static struct ec_curve curve = { 0 }; - static bool initialised = false; - - if (!initialised) - { - mp_int *p = MP_LITERAL(0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffffff); - mp_int *d = MP_LITERAL(0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffffffffffffffffffffffffffffffffffffffffffffffff6756); /* = p - 39081 */ - mp_int *a = MP_LITERAL(0x1); - mp_int *G_x = MP_LITERAL(0x4f1970c66bed0ded221d15a622bf36da9e146570470f1767ea6de324a3d3a46412ae1af72ab66511433b80e18b00938e2626a82bc70cc05e); - mp_int *G_y = MP_LITERAL(0x693f46716eb6bc248876203756c9c7624bea73736ca3984087789c1e05a0c2d73ad3ff1ce67c39c4fdbd132c4ed7c8ad9808795bf230fa14); - mp_int *G_order = MP_LITERAL(0x3fffffffffffffffffffffffffffffffffffffffffffffffffffffff7cca23e9c44edb49aed63690216cc2728dc58f552378c292ab5844f3); - mp_int *nonsquare_mod_p = mp_from_integer(7); - initialise_ecurve(&curve, p, d, a, nonsquare_mod_p, - G_x, G_y, G_order, 2); - mp_free(p); - mp_free(d); - mp_free(a); - mp_free(G_x); - mp_free(G_y); - mp_free(G_order); - mp_free(nonsquare_mod_p); - - /* This curve doesn't need a name, because it's never used in - * any format that embeds the curve name */ - curve.name = NULL; - - curve.textname = "Ed448"; - - /* Now initialised, no need to do it again */ - initialised = true; - } - - return &curve; -} - -/* ---------------------------------------------------------------------- - * Public point from private - */ - -struct ecsign_extra { - struct ec_curve *(*curve)(void); - const ssh_hashalg *hash; - - /* These fields are used by the OpenSSH PEM format importer/exporter */ - const unsigned char *oid; - int oidlen; - - /* Some EdDSA instances prefix a string to all hash preimages, to - * disambiguate which signature variant they're being used with */ - ptrlen hash_prefix; -}; - -WeierstrassPoint *ecdsa_public(mp_int *private_key, const ssh_keyalg *alg) -{ - const struct ecsign_extra *extra = - (const struct ecsign_extra *)alg->extra; - struct ec_curve *curve = extra->curve(); - assert(curve->type == EC_WEIERSTRASS); - - mp_int *priv_reduced = mp_mod(private_key, curve->p); - WeierstrassPoint *toret = ecc_weierstrass_multiply( - curve->w.G, priv_reduced); - mp_free(priv_reduced); - return toret; -} - -static mp_int *eddsa_exponent_from_hash( - ptrlen hash, const struct ec_curve *curve) -{ - /* - * Make an integer out of the hash data, little-endian. - */ - assert(hash.len >= curve->fieldBytes); - mp_int *e = mp_from_bytes_le(make_ptrlen(hash.ptr, curve->fieldBytes)); - - /* - * Set the highest bit that fits in the modulus, and clear any - * above that. - */ - mp_set_bit(e, curve->fieldBits - 1, 1); - mp_reduce_mod_2to(e, curve->fieldBits); - - /* - * Clear a curve-specific number of low bits. - */ - for (unsigned bit = 0; bit < curve->e.log2_cofactor; bit++) - mp_set_bit(e, bit, 0); - - return e; -} - -EdwardsPoint *eddsa_public(mp_int *private_key, const ssh_keyalg *alg) -{ - const struct ecsign_extra *extra = - (const struct ecsign_extra *)alg->extra; - struct ec_curve *curve = extra->curve(); - assert(curve->type == EC_EDWARDS); - - ssh_hash *h = ssh_hash_new(extra->hash); - for (size_t i = 0; i < curve->fieldBytes; ++i) - put_byte(h, mp_get_byte(private_key, i)); - - unsigned char hash[MAX_HASH_LEN]; - ssh_hash_final(h, hash); - - mp_int *exponent = eddsa_exponent_from_hash( - make_ptrlen(hash, extra->hash->hlen), curve); - - EdwardsPoint *toret = ecc_edwards_multiply(curve->e.G, exponent); - mp_free(exponent); - - return toret; -} - -/* ---------------------------------------------------------------------- - * Marshalling and unmarshalling functions - */ - -static mp_int *BinarySource_get_mp_le(BinarySource *src) -{ - return mp_from_bytes_le(get_string(src)); -} -#define get_mp_le(src) BinarySource_get_mp_le(BinarySource_UPCAST(src)) - -static void BinarySink_put_mp_le_fixedlen(BinarySink *bs, mp_int *x, - size_t bytes) -{ - put_uint32(bs, bytes); - for (size_t i = 0; i < bytes; ++i) - put_byte(bs, mp_get_byte(x, i)); -} -#define put_mp_le_fixedlen(bs, x, bytes) \ - BinarySink_put_mp_le_fixedlen(BinarySink_UPCAST(bs), x, bytes) - -static WeierstrassPoint *ecdsa_decode( - ptrlen encoded, const struct ec_curve *curve) -{ - assert(curve->type == EC_WEIERSTRASS); - BinarySource src[1]; - - BinarySource_BARE_INIT_PL(src, encoded); - unsigned char format_type = get_byte(src); - - WeierstrassPoint *P; - - size_t len = get_avail(src); - mp_int *x; - mp_int *y; - - switch (format_type) { - case 0: - /* The identity. */ - P = ecc_weierstrass_point_new_identity(curve->w.wc); - break; - case 2: - case 3: - /* A compressed point, in which the x-coordinate is stored in - * full, and y is deduced from that and a single bit - * indicating its parity (stored in the format type byte). */ - x = mp_from_bytes_be(get_data(src, len)); - P = ecc_weierstrass_point_new_from_x(curve->w.wc, x, format_type & 1); - mp_free(x); - if (!P) /* this can fail if the input is invalid */ - return NULL; - break; - case 4: - /* An uncompressed point: the x,y coordinates are stored in - * full. We expect the rest of the string to have even length, - * and be divided half and half between the two values. */ - if (len % 2 != 0) - return NULL; - len /= 2; - x = mp_from_bytes_be(get_data(src, len)); - y = mp_from_bytes_be(get_data(src, len)); - P = ecc_weierstrass_point_new(curve->w.wc, x, y); - mp_free(x); - mp_free(y); - break; - default: - /* An unrecognised type byte. */ - return NULL; - } - - /* Verify the point is on the curve */ - if (!ecc_weierstrass_point_valid(P)) { - ecc_weierstrass_point_free(P); - return NULL; - } - - return P; -} - -static WeierstrassPoint *BinarySource_get_wpoint( - BinarySource *src, const struct ec_curve *curve) -{ - ptrlen str = get_string(src); - if (get_err(src)) - return NULL; - return ecdsa_decode(str, curve); -} -#define get_wpoint(src, curve) \ - BinarySource_get_wpoint(BinarySource_UPCAST(src), curve) - -static void BinarySink_put_wpoint( - BinarySink *bs, WeierstrassPoint *point, const struct ec_curve *curve, - bool bare) -{ - strbuf *sb; - BinarySink *bs_inner; - - if (!bare) { - /* - * Encapsulate the raw data inside an outermost string layer. - */ - sb = strbuf_new(); - bs_inner = BinarySink_UPCAST(sb); - } else { - /* - * Just write the data directly to the output. - */ - bs_inner = bs; - } - - if (ecc_weierstrass_is_identity(point)) { - put_byte(bs_inner, 0); - } else { - mp_int *x, *y; - ecc_weierstrass_get_affine(point, &x, &y); - - /* - * For ECDSA, we only ever output uncompressed points. - */ - put_byte(bs_inner, 0x04); - for (size_t i = curve->fieldBytes; i--;) - put_byte(bs_inner, mp_get_byte(x, i)); - for (size_t i = curve->fieldBytes; i--;) - put_byte(bs_inner, mp_get_byte(y, i)); - - mp_free(x); - mp_free(y); - } - - if (!bare) - put_stringsb(bs, sb); -} -#define put_wpoint(bs, point, curve, bare) \ - BinarySink_put_wpoint(BinarySink_UPCAST(bs), point, curve, bare) - -static EdwardsPoint *eddsa_decode(ptrlen encoded, const struct ec_curve *curve) -{ - assert(curve->type == EC_EDWARDS); - - mp_int *y = mp_from_bytes_le(encoded); - - /* The topmost bit of the encoding isn't part of y, so it stores - * the bottom bit of x. Extract it, and zero that bit in y. */ - unsigned desired_x_parity = mp_get_bit(y, curve->fieldBytes * 8 - 1); - mp_set_bit(y, curve->fieldBytes * 8 - 1, 0); - - /* What's left should now be within the range of the curve's modulus */ - if (mp_cmp_hs(y, curve->p)) { - mp_free(y); - return NULL; - } - - EdwardsPoint *P = ecc_edwards_point_new_from_y( - curve->e.ec, y, desired_x_parity); - mp_free(y); - - /* A point constructed in this way will always satisfy the curve - * equation, unless ecc.c wasn't able to construct one at all, in - * which case P is now NULL. Either way, return it. */ - return P; -} - -static EdwardsPoint *BinarySource_get_epoint( - BinarySource *src, const struct ec_curve *curve) -{ - ptrlen str = get_string(src); - if (get_err(src)) - return NULL; - return eddsa_decode(str, curve); -} -#define get_epoint(src, curve) \ - BinarySource_get_epoint(BinarySource_UPCAST(src), curve) - -static void BinarySink_put_epoint( - BinarySink *bs, EdwardsPoint *point, const struct ec_curve *curve, - bool bare) -{ - mp_int *x, *y; - ecc_edwards_get_affine(point, &x, &y); - - assert(curve->fieldBytes >= 2); - - /* - * EdDSA requires point compression. We store a single integer, - * with bytes in little-endian order, which mostly contains y but - * in which the topmost bit is the low bit of x. - */ - if (!bare) - put_uint32(bs, curve->fieldBytes); /* string length field */ - for (size_t i = 0; i < curve->fieldBytes - 1; i++) - put_byte(bs, mp_get_byte(y, i)); - put_byte(bs, (mp_get_byte(y, curve->fieldBytes - 1) & 0x7F) | - (mp_get_bit(x, 0) << 7)); - - mp_free(x); - mp_free(y); -} -#define put_epoint(bs, point, curve, bare) \ - BinarySink_put_epoint(BinarySink_UPCAST(bs), point, curve, bare) - -/* ---------------------------------------------------------------------- - * Exposed ECDSA interface - */ - -static void ecdsa_freekey(ssh_key *key) -{ - struct ecdsa_key *ek = container_of(key, struct ecdsa_key, sshk); - - if (ek->publicKey) - ecc_weierstrass_point_free(ek->publicKey); - if (ek->privateKey) - mp_free(ek->privateKey); - sfree(ek); -} - -static void eddsa_freekey(ssh_key *key) -{ - struct eddsa_key *ek = container_of(key, struct eddsa_key, sshk); - - if (ek->publicKey) - ecc_edwards_point_free(ek->publicKey); - if (ek->privateKey) - mp_free(ek->privateKey); - sfree(ek); -} - -static char *ec_signkey_invalid(ssh_key *key, unsigned flags) -{ - /* All validity criteria for both ECDSA and EdDSA were checked - * when we loaded the key in the first place */ - return NULL; -} - -static ssh_key *ecdsa_new_pub(const ssh_keyalg *alg, ptrlen data) -{ - const struct ecsign_extra *extra = - (const struct ecsign_extra *)alg->extra; - struct ec_curve *curve = extra->curve(); - assert(curve->type == EC_WEIERSTRASS); - - BinarySource src[1]; - BinarySource_BARE_INIT_PL(src, data); - get_string(src); - - /* Curve name is duplicated for Weierstrass form */ - if (!ptrlen_eq_string(get_string(src), curve->name)) - return NULL; - - struct ecdsa_key *ek = snew(struct ecdsa_key); - ek->sshk.vt = alg; - ek->curve = curve; - ek->privateKey = NULL; - - ek->publicKey = get_wpoint(src, curve); - if (!ek->publicKey) { - ecdsa_freekey(&ek->sshk); - return NULL; - } - - return &ek->sshk; -} - -static ssh_key *eddsa_new_pub(const ssh_keyalg *alg, ptrlen data) -{ - const struct ecsign_extra *extra = - (const struct ecsign_extra *)alg->extra; - struct ec_curve *curve = extra->curve(); - assert(curve->type == EC_EDWARDS); - - BinarySource src[1]; - BinarySource_BARE_INIT_PL(src, data); - get_string(src); - - struct eddsa_key *ek = snew(struct eddsa_key); - ek->sshk.vt = alg; - ek->curve = curve; - ek->privateKey = NULL; - - ek->publicKey = get_epoint(src, curve); - if (!ek->publicKey) { - eddsa_freekey(&ek->sshk); - return NULL; - } - - return &ek->sshk; -} - -static char *ecc_cache_str_shared( - const char *curve_name, mp_int *x, mp_int *y) -{ - strbuf *sb = strbuf_new(); - - if (curve_name) - strbuf_catf(sb, "%s,", curve_name); - - char *hx = mp_get_hex(x); - char *hy = mp_get_hex(y); - strbuf_catf(sb, "0x%s,0x%s", hx, hy); - sfree(hx); - sfree(hy); - - return strbuf_to_str(sb); -} - -static char *ecdsa_cache_str(ssh_key *key) -{ - struct ecdsa_key *ek = container_of(key, struct ecdsa_key, sshk); - mp_int *x, *y; - - ecc_weierstrass_get_affine(ek->publicKey, &x, &y); - char *toret = ecc_cache_str_shared(ek->curve->name, x, y); - mp_free(x); - mp_free(y); - return toret; -} - -static key_components *ecdsa_components(ssh_key *key) -{ - struct ecdsa_key *ek = container_of(key, struct ecdsa_key, sshk); - key_components *kc = key_components_new(); - - key_components_add_text(kc, "key_type", "ECDSA"); - key_components_add_text(kc, "curve_name", ek->curve->textname); - - mp_int *x, *y; - ecc_weierstrass_get_affine(ek->publicKey, &x, &y); - key_components_add_mp(kc, "public_affine_x", x); - key_components_add_mp(kc, "public_affine_y", y); - mp_free(x); - mp_free(y); - - if (ek->privateKey) - key_components_add_mp(kc, "private_exponent", ek->privateKey); - - return kc; -} - -static char *eddsa_cache_str(ssh_key *key) -{ - struct eddsa_key *ek = container_of(key, struct eddsa_key, sshk); - mp_int *x, *y; - - ecc_edwards_get_affine(ek->publicKey, &x, &y); - char *toret = ecc_cache_str_shared(ek->curve->name, x, y); - mp_free(x); - mp_free(y); - return toret; -} - -static key_components *eddsa_components(ssh_key *key) -{ - struct eddsa_key *ek = container_of(key, struct eddsa_key, sshk); - key_components *kc = key_components_new(); - - key_components_add_text(kc, "key_type", "EdDSA"); - key_components_add_text(kc, "curve_name", ek->curve->textname); - - mp_int *x, *y; - ecc_edwards_get_affine(ek->publicKey, &x, &y); - key_components_add_mp(kc, "public_affine_x", x); - key_components_add_mp(kc, "public_affine_y", y); - mp_free(x); - mp_free(y); - - if (ek->privateKey) - key_components_add_mp(kc, "private_exponent", ek->privateKey); - - return kc; -} - -static void ecdsa_public_blob(ssh_key *key, BinarySink *bs) -{ - struct ecdsa_key *ek = container_of(key, struct ecdsa_key, sshk); - - put_stringz(bs, ek->sshk.vt->ssh_id); - put_stringz(bs, ek->curve->name); - put_wpoint(bs, ek->publicKey, ek->curve, false); -} - -static void eddsa_public_blob(ssh_key *key, BinarySink *bs) -{ - struct eddsa_key *ek = container_of(key, struct eddsa_key, sshk); - - put_stringz(bs, ek->sshk.vt->ssh_id); - put_epoint(bs, ek->publicKey, ek->curve, false); -} - -static void ecdsa_private_blob(ssh_key *key, BinarySink *bs) -{ - struct ecdsa_key *ek = container_of(key, struct ecdsa_key, sshk); - - /* ECDSA uses ordinary SSH-2 mpint format to store the private key */ - assert(ek->privateKey); - put_mp_ssh2(bs, ek->privateKey); -} - -static void eddsa_private_blob(ssh_key *key, BinarySink *bs) -{ - struct eddsa_key *ek = container_of(key, struct eddsa_key, sshk); - - /* EdDSA stores the private key integer little-endian and unsigned */ - assert(ek->privateKey); - put_mp_le_fixedlen(bs, ek->privateKey, ek->curve->fieldBytes); -} - -static ssh_key *ecdsa_new_priv(const ssh_keyalg *alg, ptrlen pub, ptrlen priv) -{ - ssh_key *sshk = ecdsa_new_pub(alg, pub); - if (!sshk) - return NULL; - struct ecdsa_key *ek = container_of(sshk, struct ecdsa_key, sshk); - - BinarySource src[1]; - BinarySource_BARE_INIT_PL(src, priv); - ek->privateKey = get_mp_ssh2(src); - - return &ek->sshk; -} - -static ssh_key *eddsa_new_priv(const ssh_keyalg *alg, ptrlen pub, ptrlen priv) -{ - ssh_key *sshk = eddsa_new_pub(alg, pub); - if (!sshk) - return NULL; - struct eddsa_key *ek = container_of(sshk, struct eddsa_key, sshk); - - BinarySource src[1]; - BinarySource_BARE_INIT_PL(src, priv); - ek->privateKey = get_mp_le(src); - - return &ek->sshk; -} - -static ssh_key *eddsa_new_priv_openssh( - const ssh_keyalg *alg, BinarySource *src) -{ - const struct ecsign_extra *extra = - (const struct ecsign_extra *)alg->extra; - struct ec_curve *curve = extra->curve(); - assert(curve->type == EC_EDWARDS); - - ptrlen pubkey_pl = get_string(src); - ptrlen privkey_extended_pl = get_string(src); - if (get_err(src) || pubkey_pl.len != curve->fieldBytes) - return NULL; - - /* - * The OpenSSH format for ed25519 private keys also for some - * reason encodes an extra copy of the public key in the second - * half of the secret-key string. Check that that's present and - * correct as well, otherwise the key we think we've imported - * won't behave identically to the way OpenSSH would have treated - * it. - * - * We assume that Ed448 will work the same way, as and when - * OpenSSH implements it, which at the time of writing this they - * had not. - */ - BinarySource subsrc[1]; - BinarySource_BARE_INIT_PL(subsrc, privkey_extended_pl); - ptrlen privkey_pl = get_data(subsrc, curve->fieldBytes); - ptrlen pubkey_copy_pl = get_data(subsrc, curve->fieldBytes); - if (get_err(subsrc) || get_avail(subsrc)) - return NULL; - if (!ptrlen_eq_ptrlen(pubkey_pl, pubkey_copy_pl)) - return NULL; - - struct eddsa_key *ek = snew(struct eddsa_key); - ek->sshk.vt = alg; - ek->curve = curve; - ek->privateKey = NULL; - - ek->publicKey = eddsa_decode(pubkey_pl, curve); - if (!ek->publicKey) { - eddsa_freekey(&ek->sshk); - return NULL; - } - - ek->privateKey = mp_from_bytes_le(privkey_pl); - - return &ek->sshk; -} - -static void eddsa_openssh_blob(ssh_key *key, BinarySink *bs) -{ - struct eddsa_key *ek = container_of(key, struct eddsa_key, sshk); - assert(ek->curve->type == EC_EDWARDS); - - /* Encode the public and private points as strings */ - strbuf *pub_sb = strbuf_new(); - put_epoint(pub_sb, ek->publicKey, ek->curve, false); - ptrlen pub = make_ptrlen(pub_sb->s + 4, pub_sb->len - 4); - - strbuf *priv_sb = strbuf_new_nm(); - put_mp_le_fixedlen(priv_sb, ek->privateKey, ek->curve->fieldBytes); - ptrlen priv = make_ptrlen(priv_sb->s + 4, priv_sb->len - 4); - - put_stringpl(bs, pub); - - /* Encode the private key as the concatenation of the - * little-endian key integer and the public key again */ - put_uint32(bs, priv.len + pub.len); - put_datapl(bs, priv); - put_datapl(bs, pub); - - strbuf_free(pub_sb); - strbuf_free(priv_sb); -} - -static ssh_key *ecdsa_new_priv_openssh( - const ssh_keyalg *alg, BinarySource *src) -{ - const struct ecsign_extra *extra = - (const struct ecsign_extra *)alg->extra; - struct ec_curve *curve = extra->curve(); - assert(curve->type == EC_WEIERSTRASS); - - get_string(src); - - struct ecdsa_key *ek = snew(struct ecdsa_key); - ek->sshk.vt = alg; - ek->curve = curve; - ek->privateKey = NULL; - - ek->publicKey = get_wpoint(src, curve); - if (!ek->publicKey) { - ecdsa_freekey(&ek->sshk); - return NULL; - } - - ek->privateKey = get_mp_ssh2(src); - - return &ek->sshk; -} - -static void ecdsa_openssh_blob(ssh_key *key, BinarySink *bs) -{ - struct ecdsa_key *ek = container_of(key, struct ecdsa_key, sshk); - put_stringz(bs, ek->curve->name); - put_wpoint(bs, ek->publicKey, ek->curve, false); - put_mp_ssh2(bs, ek->privateKey); -} - -static int ec_shared_pubkey_bits(const ssh_keyalg *alg, ptrlen blob) -{ - const struct ecsign_extra *extra = - (const struct ecsign_extra *)alg->extra; - struct ec_curve *curve = extra->curve(); - return curve->fieldBits; -} - -static mp_int *ecdsa_signing_exponent_from_data( - const struct ec_curve *curve, const struct ecsign_extra *extra, - ptrlen data) -{ - /* Hash the data being signed. */ - unsigned char hash[MAX_HASH_LEN]; - ssh_hash *h = ssh_hash_new(extra->hash); - put_datapl(h, data); - ssh_hash_final(h, hash); - - /* - * Take the leftmost b bits of the hash of the signed data (where - * b is the number of bits in order(G)), interpreted big-endian. - */ - mp_int *z = mp_from_bytes_be(make_ptrlen(hash, extra->hash->hlen)); - size_t zbits = mp_get_nbits(z); - size_t nbits = mp_get_nbits(curve->w.G_order); - size_t shift = zbits - nbits; - /* Bound the shift count below at 0, using bit twiddling to avoid - * a conditional branch */ - shift &= ~-(shift >> (CHAR_BIT * sizeof(size_t) - 1)); - mp_int *toret = mp_rshift_safe(z, shift); - mp_free(z); - - return toret; -} - -static bool ecdsa_verify(ssh_key *key, ptrlen sig, ptrlen data) -{ - struct ecdsa_key *ek = container_of(key, struct ecdsa_key, sshk); - const struct ecsign_extra *extra = - (const struct ecsign_extra *)ek->sshk.vt->extra; - - BinarySource src[1]; - BinarySource_BARE_INIT_PL(src, sig); - - /* Check the signature starts with the algorithm name */ - if (!ptrlen_eq_string(get_string(src), ek->sshk.vt->ssh_id)) - return false; - - /* Everything else is nested inside a sub-string. Descend into that. */ - ptrlen sigstr = get_string(src); - if (get_err(src)) - return false; - BinarySource_BARE_INIT_PL(src, sigstr); - - /* Extract the signature integers r,s */ - mp_int *r = get_mp_ssh2(src); - mp_int *s = get_mp_ssh2(src); - if (get_err(src)) { - mp_free(r); - mp_free(s); - return false; - } - - /* Basic sanity checks: 0 < r,s < order(G) */ - unsigned invalid = 0; - invalid |= mp_eq_integer(r, 0); - invalid |= mp_eq_integer(s, 0); - invalid |= mp_cmp_hs(r, ek->curve->w.G_order); - invalid |= mp_cmp_hs(s, ek->curve->w.G_order); - - /* Get the hash of the signed data, converted to an integer */ - mp_int *z = ecdsa_signing_exponent_from_data(ek->curve, extra, data); - - /* Verify the signature integers against the hash */ - mp_int *w = mp_invert(s, ek->curve->w.G_order); - mp_int *u1 = mp_modmul(z, w, ek->curve->w.G_order); - mp_free(z); - mp_int *u2 = mp_modmul(r, w, ek->curve->w.G_order); - mp_free(w); - WeierstrassPoint *u1G = ecc_weierstrass_multiply(ek->curve->w.G, u1); - mp_free(u1); - WeierstrassPoint *u2P = ecc_weierstrass_multiply(ek->publicKey, u2); - mp_free(u2); - WeierstrassPoint *sum = ecc_weierstrass_add_general(u1G, u2P); - ecc_weierstrass_point_free(u1G); - ecc_weierstrass_point_free(u2P); - - mp_int *x; - ecc_weierstrass_get_affine(sum, &x, NULL); - ecc_weierstrass_point_free(sum); - - mp_divmod_into(x, ek->curve->w.G_order, NULL, x); - invalid |= (1 ^ mp_cmp_eq(r, x)); - mp_free(x); - - mp_free(r); - mp_free(s); - - return !invalid; -} - -static mp_int *eddsa_signing_exponent_from_data( - struct eddsa_key *ek, const struct ecsign_extra *extra, - ptrlen r_encoded, ptrlen data) -{ - /* Hash (r || public key || message) */ - unsigned char hash[MAX_HASH_LEN]; - ssh_hash *h = ssh_hash_new(extra->hash); - put_datapl(h, extra->hash_prefix); - put_datapl(h, r_encoded); - put_epoint(h, ek->publicKey, ek->curve, true); /* omit string header */ - put_datapl(h, data); - ssh_hash_final(h, hash); - - /* Convert to an integer */ - mp_int *toret = mp_from_bytes_le(make_ptrlen(hash, extra->hash->hlen)); - - smemclr(hash, extra->hash->hlen); - return toret; -} - -static bool eddsa_verify(ssh_key *key, ptrlen sig, ptrlen data) -{ - struct eddsa_key *ek = container_of(key, struct eddsa_key, sshk); - const struct ecsign_extra *extra = - (const struct ecsign_extra *)ek->sshk.vt->extra; - - BinarySource src[1]; - BinarySource_BARE_INIT_PL(src, sig); - - /* Check the signature starts with the algorithm name */ - if (!ptrlen_eq_string(get_string(src), ek->sshk.vt->ssh_id)) - return false; - - /* Now expect a single string which is the concatenation of an - * encoded curve point r and an integer s. */ - ptrlen sigstr = get_string(src); - if (get_err(src)) - return false; - BinarySource_BARE_INIT_PL(src, sigstr); - ptrlen rstr = get_data(src, ek->curve->fieldBytes); - ptrlen sstr = get_data(src, ek->curve->fieldBytes); - if (get_err(src) || get_avail(src)) - return false; - - EdwardsPoint *r = eddsa_decode(rstr, ek->curve); - if (!r) - return false; - mp_int *s = mp_from_bytes_le(sstr); - - mp_int *H = eddsa_signing_exponent_from_data(ek, extra, rstr, data); - - /* Verify that s*G == r + H*publicKey */ - EdwardsPoint *lhs = ecc_edwards_multiply(ek->curve->e.G, s); - mp_free(s); - EdwardsPoint *hpk = ecc_edwards_multiply(ek->publicKey, H); - mp_free(H); - EdwardsPoint *rhs = ecc_edwards_add(r, hpk); - ecc_edwards_point_free(hpk); - unsigned valid = ecc_edwards_eq(lhs, rhs); - ecc_edwards_point_free(lhs); - ecc_edwards_point_free(rhs); - ecc_edwards_point_free(r); - - return valid; -} - -static void ecdsa_sign(ssh_key *key, ptrlen data, - unsigned flags, BinarySink *bs) -{ - struct ecdsa_key *ek = container_of(key, struct ecdsa_key, sshk); - const struct ecsign_extra *extra = - (const struct ecsign_extra *)ek->sshk.vt->extra; - assert(ek->privateKey); - - mp_int *z = ecdsa_signing_exponent_from_data(ek->curve, extra, data); - - /* Generate k between 1 and curve->n, using the same deterministic - * k generation system we use for conventional DSA. */ - mp_int *k; - { - unsigned char digest[20]; - hash_simple(&ssh_sha1, data, digest); - k = dss_gen_k( - "ECDSA deterministic k generator", ek->curve->w.G_order, - ek->privateKey, digest, sizeof(digest)); - } - - WeierstrassPoint *kG = ecc_weierstrass_multiply(ek->curve->w.G, k); - mp_int *x; - ecc_weierstrass_get_affine(kG, &x, NULL); - ecc_weierstrass_point_free(kG); - - /* r = kG.x mod order(G) */ - mp_int *r = mp_mod(x, ek->curve->w.G_order); - mp_free(x); - - /* s = (z + r * priv)/k mod n */ - mp_int *rPriv = mp_modmul(r, ek->privateKey, ek->curve->w.G_order); - mp_int *numerator = mp_modadd(z, rPriv, ek->curve->w.G_order); - mp_free(z); - mp_free(rPriv); - mp_int *kInv = mp_invert(k, ek->curve->w.G_order); - mp_free(k); - mp_int *s = mp_modmul(numerator, kInv, ek->curve->w.G_order); - mp_free(numerator); - mp_free(kInv); - - /* Format the output */ - put_stringz(bs, ek->sshk.vt->ssh_id); - - strbuf *substr = strbuf_new(); - put_mp_ssh2(substr, r); - put_mp_ssh2(substr, s); - put_stringsb(bs, substr); - - mp_free(r); - mp_free(s); -} - -static void eddsa_sign(ssh_key *key, ptrlen data, - unsigned flags, BinarySink *bs) -{ - struct eddsa_key *ek = container_of(key, struct eddsa_key, sshk); - const struct ecsign_extra *extra = - (const struct ecsign_extra *)ek->sshk.vt->extra; - assert(ek->privateKey); - - /* - * EdDSA prescribes a specific method of generating the random - * nonce integer for the signature. (A verifier can't tell - * whether you followed that method, but it's important to - * follow it anyway, because test vectors will want a specific - * signature for a given message, and because this preserves - * determinism of signatures even if the same signature were - * made twice by different software.) - */ - - /* - * First, we hash the private key integer (bare, little-endian) - * into a hash generating 2*fieldBytes of output. - */ - unsigned char hash[MAX_HASH_LEN]; - ssh_hash *h = ssh_hash_new(extra->hash); - for (size_t i = 0; i < ek->curve->fieldBytes; ++i) - put_byte(h, mp_get_byte(ek->privateKey, i)); - ssh_hash_final(h, hash); - - /* - * The first half of the output hash is converted into an - * integer a, by the standard EdDSA transformation. - */ - mp_int *a = eddsa_exponent_from_hash( - make_ptrlen(hash, ek->curve->fieldBytes), ek->curve); - - /* - * The second half of the hash of the private key is hashed again - * with the message to be signed, and used as an exponent to - * generate the signature point r. - */ - h = ssh_hash_new(extra->hash); - put_datapl(h, extra->hash_prefix); - put_data(h, hash + ek->curve->fieldBytes, - extra->hash->hlen - ek->curve->fieldBytes); - put_datapl(h, data); - ssh_hash_final(h, hash); - mp_int *log_r_unreduced = mp_from_bytes_le( - make_ptrlen(hash, extra->hash->hlen)); - mp_int *log_r = mp_mod(log_r_unreduced, ek->curve->e.G_order); - mp_free(log_r_unreduced); - EdwardsPoint *r = ecc_edwards_multiply(ek->curve->e.G, log_r); - - /* - * Encode r now, because we'll need its encoding for the next - * hashing step as well as to write into the actual signature. - */ - strbuf *r_enc = strbuf_new(); - put_epoint(r_enc, r, ek->curve, true); /* omit string header */ - ecc_edwards_point_free(r); - - /* - * Compute the hash of (r || public key || message) just as - * eddsa_verify does. - */ - mp_int *H = eddsa_signing_exponent_from_data( - ek, extra, ptrlen_from_strbuf(r_enc), data); - - /* And then s = (log(r) + H*a) mod order(G). */ - mp_int *Ha = mp_modmul(H, a, ek->curve->e.G_order); - mp_int *s = mp_modadd(log_r, Ha, ek->curve->e.G_order); - mp_free(H); - mp_free(a); - mp_free(Ha); - mp_free(log_r); - - /* Format the output */ - put_stringz(bs, ek->sshk.vt->ssh_id); - put_uint32(bs, r_enc->len + ek->curve->fieldBytes); - put_data(bs, r_enc->u, r_enc->len); - strbuf_free(r_enc); - for (size_t i = 0; i < ek->curve->fieldBytes; ++i) - put_byte(bs, mp_get_byte(s, i)); - mp_free(s); -} - -static const struct ecsign_extra sign_extra_ed25519 = { - ec_ed25519, &ssh_sha512, - NULL, 0, PTRLEN_DECL_LITERAL(""), -}; -const ssh_keyalg ssh_ecdsa_ed25519 = { - .new_pub = eddsa_new_pub, - .new_priv = eddsa_new_priv, - .new_priv_openssh = eddsa_new_priv_openssh, - .freekey = eddsa_freekey, - .invalid = ec_signkey_invalid, - .sign = eddsa_sign, - .verify = eddsa_verify, - .public_blob = eddsa_public_blob, - .private_blob = eddsa_private_blob, - .openssh_blob = eddsa_openssh_blob, - .cache_str = eddsa_cache_str, - .components = eddsa_components, - .pubkey_bits = ec_shared_pubkey_bits, - .ssh_id = "ssh-ed25519", - .cache_id = "ssh-ed25519", - .extra = &sign_extra_ed25519, -}; - -static const struct ecsign_extra sign_extra_ed448 = { - ec_ed448, &ssh_shake256_114bytes, - NULL, 0, PTRLEN_DECL_LITERAL("SigEd448\0\0"), -}; -const ssh_keyalg ssh_ecdsa_ed448 = { - .new_pub = eddsa_new_pub, - .new_priv = eddsa_new_priv, - .new_priv_openssh = eddsa_new_priv_openssh, - .freekey = eddsa_freekey, - .invalid = ec_signkey_invalid, - .sign = eddsa_sign, - .verify = eddsa_verify, - .public_blob = eddsa_public_blob, - .private_blob = eddsa_private_blob, - .openssh_blob = eddsa_openssh_blob, - .cache_str = eddsa_cache_str, - .components = eddsa_components, - .pubkey_bits = ec_shared_pubkey_bits, - .ssh_id = "ssh-ed448", - .cache_id = "ssh-ed448", - .extra = &sign_extra_ed448, -}; - -/* OID: 1.2.840.10045.3.1.7 (ansiX9p256r1) */ -static const unsigned char nistp256_oid[] = { - 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07 -}; -static const struct ecsign_extra sign_extra_nistp256 = { - ec_p256, &ssh_sha256, - nistp256_oid, lenof(nistp256_oid), -}; -const ssh_keyalg ssh_ecdsa_nistp256 = { - .new_pub = ecdsa_new_pub, - .new_priv = ecdsa_new_priv, - .new_priv_openssh = ecdsa_new_priv_openssh, - .freekey = ecdsa_freekey, - .invalid = ec_signkey_invalid, - .sign = ecdsa_sign, - .verify = ecdsa_verify, - .public_blob = ecdsa_public_blob, - .private_blob = ecdsa_private_blob, - .openssh_blob = ecdsa_openssh_blob, - .cache_str = ecdsa_cache_str, - .components = ecdsa_components, - .pubkey_bits = ec_shared_pubkey_bits, - .ssh_id = "ecdsa-sha2-nistp256", - .cache_id = "ecdsa-sha2-nistp256", - .extra = &sign_extra_nistp256, -}; - -/* OID: 1.3.132.0.34 (secp384r1) */ -static const unsigned char nistp384_oid[] = { - 0x2b, 0x81, 0x04, 0x00, 0x22 -}; -static const struct ecsign_extra sign_extra_nistp384 = { - ec_p384, &ssh_sha384, - nistp384_oid, lenof(nistp384_oid), -}; -const ssh_keyalg ssh_ecdsa_nistp384 = { - .new_pub = ecdsa_new_pub, - .new_priv = ecdsa_new_priv, - .new_priv_openssh = ecdsa_new_priv_openssh, - .freekey = ecdsa_freekey, - .invalid = ec_signkey_invalid, - .sign = ecdsa_sign, - .verify = ecdsa_verify, - .public_blob = ecdsa_public_blob, - .private_blob = ecdsa_private_blob, - .openssh_blob = ecdsa_openssh_blob, - .cache_str = ecdsa_cache_str, - .components = ecdsa_components, - .pubkey_bits = ec_shared_pubkey_bits, - .ssh_id = "ecdsa-sha2-nistp384", - .cache_id = "ecdsa-sha2-nistp384", - .extra = &sign_extra_nistp384, -}; - -/* OID: 1.3.132.0.35 (secp521r1) */ -static const unsigned char nistp521_oid[] = { - 0x2b, 0x81, 0x04, 0x00, 0x23 -}; -static const struct ecsign_extra sign_extra_nistp521 = { - ec_p521, &ssh_sha512, - nistp521_oid, lenof(nistp521_oid), -}; -const ssh_keyalg ssh_ecdsa_nistp521 = { - .new_pub = ecdsa_new_pub, - .new_priv = ecdsa_new_priv, - .new_priv_openssh = ecdsa_new_priv_openssh, - .freekey = ecdsa_freekey, - .invalid = ec_signkey_invalid, - .sign = ecdsa_sign, - .verify = ecdsa_verify, - .public_blob = ecdsa_public_blob, - .private_blob = ecdsa_private_blob, - .openssh_blob = ecdsa_openssh_blob, - .cache_str = ecdsa_cache_str, - .components = ecdsa_components, - .pubkey_bits = ec_shared_pubkey_bits, - .ssh_id = "ecdsa-sha2-nistp521", - .cache_id = "ecdsa-sha2-nistp521", - .extra = &sign_extra_nistp521, -}; - -/* ---------------------------------------------------------------------- - * Exposed ECDH interface - */ - -struct eckex_extra { - struct ec_curve *(*curve)(void); - void (*setup)(ecdh_key *dh); - void (*cleanup)(ecdh_key *dh); - void (*getpublic)(ecdh_key *dh, BinarySink *bs); - mp_int *(*getkey)(ecdh_key *dh, ptrlen remoteKey); -}; - -struct ecdh_key { - const struct eckex_extra *extra; - const struct ec_curve *curve; - mp_int *private; - union { - WeierstrassPoint *w_public; - MontgomeryPoint *m_public; - }; -}; - -const char *ssh_ecdhkex_curve_textname(const ssh_kex *kex) -{ - const struct eckex_extra *extra = (const struct eckex_extra *)kex->extra; - struct ec_curve *curve = extra->curve(); - return curve->textname; -} - -static void ssh_ecdhkex_w_setup(ecdh_key *dh) -{ - mp_int *one = mp_from_integer(1); - dh->private = mp_random_in_range(one, dh->curve->w.G_order); - mp_free(one); - - dh->w_public = ecc_weierstrass_multiply(dh->curve->w.G, dh->private); -} - -static void ssh_ecdhkex_m_setup(ecdh_key *dh) -{ - strbuf *bytes = strbuf_new_nm(); - random_read(strbuf_append(bytes, dh->curve->fieldBytes), - dh->curve->fieldBytes); - - dh->private = mp_from_bytes_le(ptrlen_from_strbuf(bytes)); - - /* Ensure the private key has the highest valid bit set, and no - * bits _above_ the highest valid one */ - mp_reduce_mod_2to(dh->private, dh->curve->fieldBits); - mp_set_bit(dh->private, dh->curve->fieldBits - 1, 1); - - /* Clear a curve-specific number of low bits */ - for (unsigned bit = 0; bit < dh->curve->m.log2_cofactor; bit++) - mp_set_bit(dh->private, bit, 0); - - strbuf_free(bytes); - - dh->m_public = ecc_montgomery_multiply(dh->curve->m.G, dh->private); -} - -ecdh_key *ssh_ecdhkex_newkey(const ssh_kex *kex) -{ - const struct eckex_extra *extra = (const struct eckex_extra *)kex->extra; - const struct ec_curve *curve = extra->curve(); - - ecdh_key *dh = snew(ecdh_key); - dh->extra = extra; - dh->curve = curve; - dh->extra->setup(dh); - return dh; -} - -static void ssh_ecdhkex_w_getpublic(ecdh_key *dh, BinarySink *bs) -{ - put_wpoint(bs, dh->w_public, dh->curve, true); -} - -static void ssh_ecdhkex_m_getpublic(ecdh_key *dh, BinarySink *bs) -{ - mp_int *x; - ecc_montgomery_get_affine(dh->m_public, &x); - for (size_t i = 0; i < dh->curve->fieldBytes; ++i) - put_byte(bs, mp_get_byte(x, i)); - mp_free(x); -} - -void ssh_ecdhkex_getpublic(ecdh_key *dh, BinarySink *bs) -{ - dh->extra->getpublic(dh, bs); -} - -static mp_int *ssh_ecdhkex_w_getkey(ecdh_key *dh, ptrlen remoteKey) -{ - WeierstrassPoint *remote_p = ecdsa_decode(remoteKey, dh->curve); - if (!remote_p) - return NULL; - - if (ecc_weierstrass_is_identity(remote_p)) { - /* Not a sensible Diffie-Hellman input value */ - ecc_weierstrass_point_free(remote_p); - return NULL; - } - - WeierstrassPoint *p = ecc_weierstrass_multiply(remote_p, dh->private); - - mp_int *x; - ecc_weierstrass_get_affine(p, &x, NULL); - - ecc_weierstrass_point_free(remote_p); - ecc_weierstrass_point_free(p); - - return x; -} - -static mp_int *ssh_ecdhkex_m_getkey(ecdh_key *dh, ptrlen remoteKey) -{ - mp_int *remote_x = mp_from_bytes_le(remoteKey); - - /* Per RFC 7748 section 5, discard any set bits of the other - * side's public value beyond the minimum number of bits required - * to represent all valid values. However, an overlarge value that - * still fits into the remaining number of bits is accepted, and - * will be reduced mod p. */ - mp_reduce_mod_2to(remote_x, dh->curve->fieldBits); - - MontgomeryPoint *remote_p = ecc_montgomery_point_new( - dh->curve->m.mc, remote_x); - mp_free(remote_x); - - MontgomeryPoint *p = ecc_montgomery_multiply(remote_p, dh->private); - - if (ecc_montgomery_is_identity(p)) { - ecc_montgomery_point_free(remote_p); - ecc_montgomery_point_free(p); - return NULL; - } - - mp_int *x; - ecc_montgomery_get_affine(p, &x); - - ecc_montgomery_point_free(remote_p); - ecc_montgomery_point_free(p); - - /* - * Endianness-swap. The Curve25519 algorithm definition assumes - * you were doing your computation in arrays of 32 little-endian - * bytes, and now specifies that you take your final one of those - * and convert it into a bignum in _network_ byte order, i.e. - * big-endian. - * - * In particular, the spec says, you convert the _whole_ 32 bytes - * into a bignum. That is, on the rare occasions that x has come - * out with the most significant 8 bits zero, we have to imagine - * that being represented by a 32-byte string with the last byte - * being zero, so that has to be converted into an SSH-2 bignum - * with the _low_ byte zero, i.e. a multiple of 256. - */ - strbuf *sb = strbuf_new(); - for (size_t i = 0; i < dh->curve->fieldBytes; ++i) - put_byte(sb, mp_get_byte(x, i)); - mp_free(x); - x = mp_from_bytes_be(ptrlen_from_strbuf(sb)); - strbuf_free(sb); - - return x; -} - -mp_int *ssh_ecdhkex_getkey(ecdh_key *dh, ptrlen remoteKey) -{ - return dh->extra->getkey(dh, remoteKey); -} - -static void ssh_ecdhkex_w_cleanup(ecdh_key *dh) -{ - ecc_weierstrass_point_free(dh->w_public); -} - -static void ssh_ecdhkex_m_cleanup(ecdh_key *dh) -{ - ecc_montgomery_point_free(dh->m_public); -} - -void ssh_ecdhkex_freekey(ecdh_key *dh) -{ - mp_free(dh->private); - dh->extra->cleanup(dh); - sfree(dh); -} - -static const struct eckex_extra kex_extra_curve25519 = { - ec_curve25519, - ssh_ecdhkex_m_setup, - ssh_ecdhkex_m_cleanup, - ssh_ecdhkex_m_getpublic, - ssh_ecdhkex_m_getkey, -}; -const ssh_kex ssh_ec_kex_curve25519 = { - "curve25519-sha256", NULL, KEXTYPE_ECDH, - &ssh_sha256, &kex_extra_curve25519, -}; -/* Pre-RFC alias */ -const ssh_kex ssh_ec_kex_curve25519_libssh = { - "curve25519-sha256@libssh.org", NULL, KEXTYPE_ECDH, - &ssh_sha256, &kex_extra_curve25519, -}; - -static const struct eckex_extra kex_extra_curve448 = { - ec_curve448, - ssh_ecdhkex_m_setup, - ssh_ecdhkex_m_cleanup, - ssh_ecdhkex_m_getpublic, - ssh_ecdhkex_m_getkey, -}; -const ssh_kex ssh_ec_kex_curve448 = { - "curve448-sha512", NULL, KEXTYPE_ECDH, - &ssh_sha512, &kex_extra_curve448, -}; - -static const struct eckex_extra kex_extra_nistp256 = { - ec_p256, - ssh_ecdhkex_w_setup, - ssh_ecdhkex_w_cleanup, - ssh_ecdhkex_w_getpublic, - ssh_ecdhkex_w_getkey, -}; -const ssh_kex ssh_ec_kex_nistp256 = { - "ecdh-sha2-nistp256", NULL, KEXTYPE_ECDH, - &ssh_sha256, &kex_extra_nistp256, -}; - -static const struct eckex_extra kex_extra_nistp384 = { - ec_p384, - ssh_ecdhkex_w_setup, - ssh_ecdhkex_w_cleanup, - ssh_ecdhkex_w_getpublic, - ssh_ecdhkex_w_getkey, -}; -const ssh_kex ssh_ec_kex_nistp384 = { - "ecdh-sha2-nistp384", NULL, KEXTYPE_ECDH, - &ssh_sha384, &kex_extra_nistp384, -}; - -static const struct eckex_extra kex_extra_nistp521 = { - ec_p521, - ssh_ecdhkex_w_setup, - ssh_ecdhkex_w_cleanup, - ssh_ecdhkex_w_getpublic, - ssh_ecdhkex_w_getkey, -}; -const ssh_kex ssh_ec_kex_nistp521 = { - "ecdh-sha2-nistp521", NULL, KEXTYPE_ECDH, - &ssh_sha512, &kex_extra_nistp521, -}; - -static const ssh_kex *const ec_kex_list[] = { - &ssh_ec_kex_curve448, - &ssh_ec_kex_curve25519, - &ssh_ec_kex_curve25519_libssh, - &ssh_ec_kex_nistp256, - &ssh_ec_kex_nistp384, - &ssh_ec_kex_nistp521, -}; - -const ssh_kexes ssh_ecdh_kex = { lenof(ec_kex_list), ec_kex_list }; - -/* ---------------------------------------------------------------------- - * Helper functions for finding key algorithms and returning auxiliary - * data. - */ - -const ssh_keyalg *ec_alg_by_oid(int len, const void *oid, - const struct ec_curve **curve) -{ - static const ssh_keyalg *algs_with_oid[] = { - &ssh_ecdsa_nistp256, - &ssh_ecdsa_nistp384, - &ssh_ecdsa_nistp521, - }; - int i; - - for (i = 0; i < lenof(algs_with_oid); i++) { - const ssh_keyalg *alg = algs_with_oid[i]; - const struct ecsign_extra *extra = - (const struct ecsign_extra *)alg->extra; - if (len == extra->oidlen && !memcmp(oid, extra->oid, len)) { - *curve = extra->curve(); - return alg; - } - } - return NULL; -} - -const unsigned char *ec_alg_oid(const ssh_keyalg *alg, - int *oidlen) -{ - const struct ecsign_extra *extra = (const struct ecsign_extra *)alg->extra; - *oidlen = extra->oidlen; - return extra->oid; -} - -const int ec_nist_curve_lengths[] = { 256, 384, 521 }; -const int n_ec_nist_curve_lengths = lenof(ec_nist_curve_lengths); - -const int ec_ed_curve_lengths[] = { 255, 448 }; -const int n_ec_ed_curve_lengths = lenof(ec_ed_curve_lengths); - -bool ec_nist_alg_and_curve_by_bits( - int bits, const struct ec_curve **curve, const ssh_keyalg **alg) -{ - switch (bits) { - case 256: *alg = &ssh_ecdsa_nistp256; break; - case 384: *alg = &ssh_ecdsa_nistp384; break; - case 521: *alg = &ssh_ecdsa_nistp521; break; - default: return false; - } - *curve = ((struct ecsign_extra *)(*alg)->extra)->curve(); - return true; -} - -bool ec_ed_alg_and_curve_by_bits( - int bits, const struct ec_curve **curve, const ssh_keyalg **alg) -{ - switch (bits) { - case 255: case 256: *alg = &ssh_ecdsa_ed25519; break; - case 448: *alg = &ssh_ecdsa_ed448; break; - default: return false; - } - *curve = ((struct ecsign_extra *)(*alg)->extra)->curve(); - return true; -} diff --git a/sshhmac.c b/sshhmac.c deleted file mode 100644 index 8f18eb28..00000000 --- a/sshhmac.c +++ /dev/null @@ -1,257 +0,0 @@ -/* - * Implementation of HMAC (RFC 2104) for PuTTY, in a general form that - * can wrap any underlying hash function. - */ - -#include "ssh.h" - -struct hmac { - const ssh_hashalg *hashalg; - ssh_hash *h_outer, *h_inner, *h_live; - uint8_t *digest; - strbuf *text_name; - ssh2_mac mac; -}; - -struct hmac_extra { - const ssh_hashalg *hashalg_base; - const char *suffix, *annotation; -}; - -static ssh2_mac *hmac_new(const ssh2_macalg *alg, ssh_cipher *cipher) -{ - struct hmac *ctx = snew(struct hmac); - const struct hmac_extra *extra = (const struct hmac_extra *)alg->extra; - - ctx->h_outer = ssh_hash_new(extra->hashalg_base); - /* In case that hashalg was a selector vtable, we'll now switch to - * using whatever real one it selected, for all future purposes. */ - ctx->hashalg = ssh_hash_alg(ctx->h_outer); - ctx->h_inner = ssh_hash_new(ctx->hashalg); - ctx->h_live = ssh_hash_new(ctx->hashalg); - - /* - * HMAC is not well defined as a wrapper on an absolutely general - * hash function; it expects that the function it's wrapping will - * consume data in fixed-size blocks, and it's partially defined - * in terms of that block size. So we insist that the hash we're - * given must have defined a meaningful block size. - */ - assert(ctx->hashalg->blocklen); - - ctx->digest = snewn(ctx->hashalg->hlen, uint8_t); - - ctx->text_name = strbuf_new(); - strbuf_catf(ctx->text_name, "HMAC-%s%s", - ctx->hashalg->text_basename, extra->suffix); - if (extra->annotation || ctx->hashalg->annotation) { - strbuf_catf(ctx->text_name, " ("); - const char *sep = ""; - if (extra->annotation) { - strbuf_catf(ctx->text_name, "%s%s", sep, extra->annotation); - sep = ", "; - } - if (ctx->hashalg->annotation) { - strbuf_catf(ctx->text_name, "%s%s", sep, ctx->hashalg->annotation); - sep = ", "; - } - strbuf_catf(ctx->text_name, ")"); - } - - ctx->mac.vt = alg; - BinarySink_DELEGATE_INIT(&ctx->mac, ctx->h_live); - - return &ctx->mac; -} - -static void hmac_free(ssh2_mac *mac) -{ - struct hmac *ctx = container_of(mac, struct hmac, mac); - - ssh_hash_free(ctx->h_outer); - ssh_hash_free(ctx->h_inner); - ssh_hash_free(ctx->h_live); - smemclr(ctx->digest, ctx->hashalg->hlen); - sfree(ctx->digest); - strbuf_free(ctx->text_name); - - smemclr(ctx, sizeof(*ctx)); - sfree(ctx); -} - -#define PAD_OUTER 0x5C -#define PAD_INNER 0x36 - -static void hmac_key(ssh2_mac *mac, ptrlen key) -{ - struct hmac *ctx = container_of(mac, struct hmac, mac); - - const uint8_t *kp; - size_t klen; - strbuf *sb = NULL; - - if (key.len > ctx->hashalg->blocklen) { - /* - * RFC 2104 section 2: if the key exceeds the block length of - * the underlying hash, then we start by hashing the key, and - * use that hash as the 'true' key for the HMAC construction. - */ - sb = strbuf_new_nm(); - strbuf_append(sb, ctx->hashalg->hlen); - hash_simple(ctx->hashalg, key, sb->u); - kp = sb->u; - klen = sb->len; - } else { - /* - * A short enough key is used as is. - */ - kp = (const uint8_t *)key.ptr; - klen = key.len; - } - - ssh_hash_reset(ctx->h_outer); - for (size_t i = 0; i < klen; i++) - put_byte(ctx->h_outer, PAD_OUTER ^ kp[i]); - for (size_t i = klen; i < ctx->hashalg->blocklen; i++) - put_byte(ctx->h_outer, PAD_OUTER); - - ssh_hash_reset(ctx->h_inner); - for (size_t i = 0; i < klen; i++) - put_byte(ctx->h_inner, PAD_INNER ^ kp[i]); - for (size_t i = klen; i < ctx->hashalg->blocklen; i++) - put_byte(ctx->h_inner, PAD_INNER); - - if (sb) - strbuf_free(sb); -} - -static void hmac_start(ssh2_mac *mac) -{ - struct hmac *ctx = container_of(mac, struct hmac, mac); - ssh_hash_copyfrom(ctx->h_live, ctx->h_inner); -} - -static void hmac_genresult(ssh2_mac *mac, unsigned char *output) -{ - struct hmac *ctx = container_of(mac, struct hmac, mac); - ssh_hash *htmp; - - /* Leave h_live and h_outer in place, so that the SSH-2 BPP can - * continue regenerating test results from different-length - * prefixes of the packet */ - ssh_hash_digest_nondestructive(ctx->h_live, ctx->digest); - - htmp = ssh_hash_copy(ctx->h_outer); - put_data(htmp, ctx->digest, ctx->hashalg->hlen); - ssh_hash_final(htmp, ctx->digest); - - /* - * Some instances of HMAC truncate the output hash, so instead of - * writing it directly to 'output' we wrote it to our own - * full-length buffer, and now we copy the required amount. - */ - memcpy(output, ctx->digest, mac->vt->len); - smemclr(ctx->digest, ctx->hashalg->hlen); -} - -static const char *hmac_text_name(ssh2_mac *mac) -{ - struct hmac *ctx = container_of(mac, struct hmac, mac); - return ctx->text_name->s; -} - -static const struct hmac_extra ssh_hmac_sha256_extra = { &ssh_sha256, "" }; -const ssh2_macalg ssh_hmac_sha256 = { - .new = hmac_new, - .free = hmac_free, - .setkey = hmac_key, - .start = hmac_start, - .genresult = hmac_genresult, - .text_name = hmac_text_name, - .name = "hmac-sha2-256", - .etm_name = "hmac-sha2-256-etm@openssh.com", - .len = 32, - .keylen = 32, - .extra = &ssh_hmac_sha256_extra, -}; - -static const struct hmac_extra ssh_hmac_md5_extra = { &ssh_md5, "" }; -const ssh2_macalg ssh_hmac_md5 = { - .new = hmac_new, - .free = hmac_free, - .setkey = hmac_key, - .start = hmac_start, - .genresult = hmac_genresult, - .text_name = hmac_text_name, - .name = "hmac-md5", - .etm_name = "hmac-md5-etm@openssh.com", - .len = 16, - .keylen = 16, - .extra = &ssh_hmac_md5_extra, -}; - -static const struct hmac_extra ssh_hmac_sha1_extra = { &ssh_sha1, "" }; - -const ssh2_macalg ssh_hmac_sha1 = { - .new = hmac_new, - .free = hmac_free, - .setkey = hmac_key, - .start = hmac_start, - .genresult = hmac_genresult, - .text_name = hmac_text_name, - .name = "hmac-sha1", - .etm_name = "hmac-sha1-etm@openssh.com", - .len = 20, - .keylen = 20, - .extra = &ssh_hmac_sha1_extra, -}; - -static const struct hmac_extra ssh_hmac_sha1_96_extra = { &ssh_sha1, "-96" }; - -const ssh2_macalg ssh_hmac_sha1_96 = { - .new = hmac_new, - .free = hmac_free, - .setkey = hmac_key, - .start = hmac_start, - .genresult = hmac_genresult, - .text_name = hmac_text_name, - .name = "hmac-sha1-96", - .etm_name = "hmac-sha1-96-etm@openssh.com", - .len = 12, - .keylen = 20, - .extra = &ssh_hmac_sha1_96_extra, -}; - -static const struct hmac_extra ssh_hmac_sha1_buggy_extra = { - &ssh_sha1, "", "bug-compatible" -}; - -const ssh2_macalg ssh_hmac_sha1_buggy = { - .new = hmac_new, - .free = hmac_free, - .setkey = hmac_key, - .start = hmac_start, - .genresult = hmac_genresult, - .text_name = hmac_text_name, - .name = "hmac-sha1", - .len = 20, - .keylen = 16, - .extra = &ssh_hmac_sha1_buggy_extra, -}; - -static const struct hmac_extra ssh_hmac_sha1_96_buggy_extra = { - &ssh_sha1, "-96", "bug-compatible" -}; - -const ssh2_macalg ssh_hmac_sha1_96_buggy = { - .new = hmac_new, - .free = hmac_free, - .setkey = hmac_key, - .start = hmac_start, - .genresult = hmac_genresult, - .text_name = hmac_text_name, - .name = "hmac-sha1-96", - .len = 12, - .keylen = 16, - .extra = &ssh_hmac_sha1_96_buggy_extra, -}; diff --git a/sshmac.c b/sshmac.c deleted file mode 100644 index c117d90b..00000000 --- a/sshmac.c +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Centralised parts of the SSH-2 MAC API, which don't need to vary - * with the MAC implementation. - */ - -#include - -#include "ssh.h" - -bool ssh2_mac_verresult(ssh2_mac *mac, const void *candidate) -{ - unsigned char correct[64]; /* at least as big as all known MACs */ - bool toret; - - assert(mac->vt->len <= sizeof(correct)); - ssh2_mac_genresult(mac, correct); - toret = smemeq(correct, candidate, mac->vt->len); - - smemclr(correct, sizeof(correct)); - - return toret; -} - -static void ssh2_mac_prepare(ssh2_mac *mac, const void *blk, int len, - unsigned long seq) -{ - ssh2_mac_start(mac); - put_uint32(mac, seq); - put_data(mac, blk, len); -} - -void ssh2_mac_generate(ssh2_mac *mac, void *blk, int len, unsigned long seq) -{ - ssh2_mac_prepare(mac, blk, len, seq); - ssh2_mac_genresult(mac, (unsigned char *)blk + len); -} - -bool ssh2_mac_verify( - ssh2_mac *mac, const void *blk, int len, unsigned long seq) -{ - ssh2_mac_prepare(mac, blk, len, seq); - return ssh2_mac_verresult(mac, (const unsigned char *)blk + len); -} diff --git a/sshmd5.c b/sshmd5.c deleted file mode 100644 index 9155c99e..00000000 --- a/sshmd5.c +++ /dev/null @@ -1,245 +0,0 @@ -/* - * MD5 implementation for PuTTY. Written directly from the spec by - * Simon Tatham. - */ - -#include -#include "ssh.h" - -static const uint32_t md5_initial_state[] = { - 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, -}; - -static const struct md5_round_constant { - uint32_t addition, rotation, msg_index; -} md5_round_constants[] = { - { 0xd76aa478, 7, 0 }, { 0xe8c7b756, 12, 1 }, - { 0x242070db, 17, 2 }, { 0xc1bdceee, 22, 3 }, - { 0xf57c0faf, 7, 4 }, { 0x4787c62a, 12, 5 }, - { 0xa8304613, 17, 6 }, { 0xfd469501, 22, 7 }, - { 0x698098d8, 7, 8 }, { 0x8b44f7af, 12, 9 }, - { 0xffff5bb1, 17, 10 }, { 0x895cd7be, 22, 11 }, - { 0x6b901122, 7, 12 }, { 0xfd987193, 12, 13 }, - { 0xa679438e, 17, 14 }, { 0x49b40821, 22, 15 }, - { 0xf61e2562, 5, 1 }, { 0xc040b340, 9, 6 }, - { 0x265e5a51, 14, 11 }, { 0xe9b6c7aa, 20, 0 }, - { 0xd62f105d, 5, 5 }, { 0x02441453, 9, 10 }, - { 0xd8a1e681, 14, 15 }, { 0xe7d3fbc8, 20, 4 }, - { 0x21e1cde6, 5, 9 }, { 0xc33707d6, 9, 14 }, - { 0xf4d50d87, 14, 3 }, { 0x455a14ed, 20, 8 }, - { 0xa9e3e905, 5, 13 }, { 0xfcefa3f8, 9, 2 }, - { 0x676f02d9, 14, 7 }, { 0x8d2a4c8a, 20, 12 }, - { 0xfffa3942, 4, 5 }, { 0x8771f681, 11, 8 }, - { 0x6d9d6122, 16, 11 }, { 0xfde5380c, 23, 14 }, - { 0xa4beea44, 4, 1 }, { 0x4bdecfa9, 11, 4 }, - { 0xf6bb4b60, 16, 7 }, { 0xbebfbc70, 23, 10 }, - { 0x289b7ec6, 4, 13 }, { 0xeaa127fa, 11, 0 }, - { 0xd4ef3085, 16, 3 }, { 0x04881d05, 23, 6 }, - { 0xd9d4d039, 4, 9 }, { 0xe6db99e5, 11, 12 }, - { 0x1fa27cf8, 16, 15 }, { 0xc4ac5665, 23, 2 }, - { 0xf4292244, 6, 0 }, { 0x432aff97, 10, 7 }, - { 0xab9423a7, 15, 14 }, { 0xfc93a039, 21, 5 }, - { 0x655b59c3, 6, 12 }, { 0x8f0ccc92, 10, 3 }, - { 0xffeff47d, 15, 10 }, { 0x85845dd1, 21, 1 }, - { 0x6fa87e4f, 6, 8 }, { 0xfe2ce6e0, 10, 15 }, - { 0xa3014314, 15, 6 }, { 0x4e0811a1, 21, 13 }, - { 0xf7537e82, 6, 4 }, { 0xbd3af235, 10, 11 }, - { 0x2ad7d2bb, 15, 2 }, { 0xeb86d391, 21, 9 }, -}; - -typedef struct md5_block md5_block; -struct md5_block { - uint8_t block[64]; - size_t used; - uint64_t len; -}; - -static inline void md5_block_setup(md5_block *blk) -{ - blk->used = 0; - blk->len = 0; -} - -static inline bool md5_block_write( - md5_block *blk, const void **vdata, size_t *len) -{ - size_t blkleft = sizeof(blk->block) - blk->used; - size_t chunk = *len < blkleft ? *len : blkleft; - - const uint8_t *p = *vdata; - memcpy(blk->block + blk->used, p, chunk); - *vdata = p + chunk; - *len -= chunk; - blk->used += chunk; - blk->len += chunk; - - if (blk->used == sizeof(blk->block)) { - blk->used = 0; - return true; - } - - return false; -} - -static inline void md5_block_pad(md5_block *blk, BinarySink *bs) -{ - uint64_t final_len = blk->len << 3; - size_t pad = 63 & (55 - blk->used); - - put_byte(bs, 0x80); - put_padding(bs, pad, 0); - - unsigned char buf[8]; - PUT_64BIT_LSB_FIRST(buf, final_len); - put_data(bs, buf, 8); - smemclr(buf, 8); - - assert(blk->used == 0 && "Should have exactly hit a block boundary"); -} - -static inline uint32_t rol(uint32_t x, unsigned y) -{ - return (x << (31 & y)) | (x >> (31 & -y)); -} - -static inline uint32_t Ch(uint32_t ctrl, uint32_t if1, uint32_t if0) -{ - return if0 ^ (ctrl & (if1 ^ if0)); -} - -/* Parameter functions for the four MD5 round types */ -static inline uint32_t F(uint32_t x, uint32_t y, uint32_t z) -{ return Ch(x, y, z); } -static inline uint32_t G(uint32_t x, uint32_t y, uint32_t z) -{ return Ch(z, x, y); } -static inline uint32_t H(uint32_t x, uint32_t y, uint32_t z) -{ return x ^ y ^ z; } -static inline uint32_t I(uint32_t x, uint32_t y, uint32_t z) -{ return y ^ (x | ~z); } - -static inline void md5_round( - unsigned round_index, const uint32_t *message, - uint32_t *a, uint32_t *b, uint32_t *c, uint32_t *d, - uint32_t (*f)(uint32_t, uint32_t, uint32_t)) -{ - struct md5_round_constant rc = md5_round_constants[round_index]; - - *a = *b + rol(*a + f(*b, *c, *d) + message[rc.msg_index] + rc.addition, - rc.rotation); -} - -static void md5_do_block(uint32_t *core, const uint8_t *block) -{ - uint32_t message_words[16]; - for (size_t i = 0; i < 16; i++) - message_words[i] = GET_32BIT_LSB_FIRST(block + 4*i); - - uint32_t a = core[0], b = core[1], c = core[2], d = core[3]; - - size_t t = 0; - for (size_t u = 0; u < 4; u++) { - md5_round(t++, message_words, &a, &b, &c, &d, F); - md5_round(t++, message_words, &d, &a, &b, &c, F); - md5_round(t++, message_words, &c, &d, &a, &b, F); - md5_round(t++, message_words, &b, &c, &d, &a, F); - } - for (size_t u = 0; u < 4; u++) { - md5_round(t++, message_words, &a, &b, &c, &d, G); - md5_round(t++, message_words, &d, &a, &b, &c, G); - md5_round(t++, message_words, &c, &d, &a, &b, G); - md5_round(t++, message_words, &b, &c, &d, &a, G); - } - for (size_t u = 0; u < 4; u++) { - md5_round(t++, message_words, &a, &b, &c, &d, H); - md5_round(t++, message_words, &d, &a, &b, &c, H); - md5_round(t++, message_words, &c, &d, &a, &b, H); - md5_round(t++, message_words, &b, &c, &d, &a, H); - } - for (size_t u = 0; u < 4; u++) { - md5_round(t++, message_words, &a, &b, &c, &d, I); - md5_round(t++, message_words, &d, &a, &b, &c, I); - md5_round(t++, message_words, &c, &d, &a, &b, I); - md5_round(t++, message_words, &b, &c, &d, &a, I); - } - - core[0] += a; - core[1] += b; - core[2] += c; - core[3] += d; - - smemclr(message_words, sizeof(message_words)); -} - -typedef struct md5 { - uint32_t core[4]; - md5_block blk; - BinarySink_IMPLEMENTATION; - ssh_hash hash; -} md5; - -static void md5_write(BinarySink *bs, const void *vp, size_t len); - -static ssh_hash *md5_new(const ssh_hashalg *alg) -{ - md5 *s = snew(md5); - - s->hash.vt = alg; - BinarySink_INIT(s, md5_write); - BinarySink_DELEGATE_INIT(&s->hash, s); - return &s->hash; -} - -static void md5_reset(ssh_hash *hash) -{ - md5 *s = container_of(hash, md5, hash); - - memcpy(s->core, md5_initial_state, sizeof(s->core)); - md5_block_setup(&s->blk); -} - -static void md5_copyfrom(ssh_hash *hcopy, ssh_hash *horig) -{ - md5 *copy = container_of(hcopy, md5, hash); - md5 *orig = container_of(horig, md5, hash); - - memcpy(copy, orig, sizeof(*copy)); - BinarySink_COPIED(copy); - BinarySink_DELEGATE_INIT(©->hash, copy); -} - -static void md5_free(ssh_hash *hash) -{ - md5 *s = container_of(hash, md5, hash); - - smemclr(s, sizeof(*s)); - sfree(s); -} - -static void md5_write(BinarySink *bs, const void *vp, size_t len) -{ - md5 *s = BinarySink_DOWNCAST(bs, md5); - - while (len > 0) - if (md5_block_write(&s->blk, &vp, &len)) - md5_do_block(s->core, s->blk.block); -} - -static void md5_digest(ssh_hash *hash, uint8_t *digest) -{ - md5 *s = container_of(hash, md5, hash); - - md5_block_pad(&s->blk, BinarySink_UPCAST(s)); - for (size_t i = 0; i < 4; i++) - PUT_32BIT_LSB_FIRST(digest + 4*i, s->core[i]); -} - -const ssh_hashalg ssh_md5 = { - .new = md5_new, - .reset = md5_reset, - .copyfrom = md5_copyfrom, - .digest = md5_digest, - .free = md5_free, - .hlen = 16, - .blocklen = 64, - HASHALG_NAMES_BARE("MD5"), -}; diff --git a/sshprng.c b/sshprng.c deleted file mode 100644 index 58df9945..00000000 --- a/sshprng.c +++ /dev/null @@ -1,287 +0,0 @@ -/* - * sshprng.c: PuTTY's cryptographic pseudorandom number generator. - * - * This module just defines the PRNG object type and its methods. The - * usual global instance of it is managed by sshrand.c. - */ - -#include "putty.h" -#include "ssh.h" -#include "mpint_i.h" - -#ifdef PRNG_DIAGNOSTICS -#define prngdebug debug -#else -#define prngdebug(...) ((void)0) -#endif - -/* - * This random number generator is based on the 'Fortuna' design by - * Niels Ferguson and Bruce Schneier. The biggest difference is that I - * use SHA-256 in place of a block cipher: the generator side of the - * system works by computing HASH(key || counter) instead of - * ENCRYPT(counter, key). - * - * Rationale: the Fortuna description itself suggests that using - * SHA-256 would be nice but people wouldn't accept it because it's - * too slow - but PuTTY isn't a heavy enough user of random numbers to - * make that a serious worry. In fact even with SHA-256 this generator - * is faster than the one we previously used. Also the Fortuna - * description worries about periodic rekeying to avoid the barely - * detectable pattern of never repeating a cipher block - but with - * SHA-256, even that shouldn't be a worry, because the output - * 'blocks' are twice the size, and also SHA-256 has no guarantee of - * bijectivity, so it surely _could_ be possible to generate the same - * block from two counter values. Thirdly, Fortuna has to have a hash - * function anyway, for reseeding and entropy collection, so reusing - * the same one means it only depends on one underlying primitive and - * can be easily reinstantiated with a larger hash function if you - * decide you'd like to do that on a particular occasion. - */ - -#define NCOLLECTORS 32 -#define RESEED_DATA_SIZE 64 - -typedef struct prng_impl prng_impl; -struct prng_impl { - prng Prng; - - const ssh_hashalg *hashalg; - - /* - * Generation side: - * - * 'generator' is a hash object with the current key preloaded - * into it. The counter-mode generation is achieved by copying - * that hash object, appending the counter value to the copy, and - * calling ssh_hash_final. - */ - ssh_hash *generator; - BignumInt counter[128 / BIGNUM_INT_BITS]; - - /* - * When re-seeding the generator, you call prng_seed_begin(), - * which sets up a hash object in 'keymaker'. You write your new - * seed data into it (which you can do by calling put_data on the - * PRNG object itself) and then call prng_seed_finish(), which - * finalises this hash and uses the output to set up the new - * generator. - * - * The keymaker hash preimage includes the previous key, so if you - * just want to change keys for the sake of not keeping the same - * one for too long, you don't have to put any extra seed data in - * at all. - */ - ssh_hash *keymaker; - - /* - * Collection side: - * - * There are NCOLLECTORS hash objects collecting entropy. Each - * separately numbered entropy source puts its output into those - * hash objects in the order 0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,..., - * that is to say, each entropy source has a separate counter - * which is incremented every time that source generates an event, - * and the event data is added to the collector corresponding to - * the index of the lowest set bit in the current counter value. - * - * Whenever collector #0 has at least RESEED_DATA_SIZE bytes (and - * it's not at least 100ms since the last reseed), the PRNG is - * reseeded, with seed data on reseed #n taken from the first j - * collectors, where j is one more than the number of factors of 2 - * in n. That is, collector #0 is used in every reseed; #1 in - * every other one, #2 in every fourth, etc. - * - * 'until_reseed' counts the amount of data that still needs to be - * added to collector #0 before a reseed will be triggered. - */ - uint32_t source_counters[NOISE_MAX_SOURCES]; - ssh_hash *collectors[NCOLLECTORS]; - size_t until_reseed; - uint32_t reseeds; - uint64_t last_reseed_time; -}; - -static void prng_seed_BinarySink_write( - BinarySink *bs, const void *data, size_t len); - -prng *prng_new(const ssh_hashalg *hashalg) -{ - prng_impl *pi = snew(prng_impl); - - memset(pi, 0, sizeof(prng_impl)); - pi->hashalg = hashalg; - pi->keymaker = NULL; - pi->generator = NULL; - memset(pi->counter, 0, sizeof(pi->counter)); - for (size_t i = 0; i < NCOLLECTORS; i++) - pi->collectors[i] = ssh_hash_new(pi->hashalg); - pi->until_reseed = 0; - BinarySink_INIT(&pi->Prng, prng_seed_BinarySink_write); - - pi->Prng.savesize = pi->hashalg->hlen * 4; - - return &pi->Prng; -} - -void prng_free(prng *pr) -{ - prng_impl *pi = container_of(pr, prng_impl, Prng); - - smemclr(pi->counter, sizeof(pi->counter)); - for (size_t i = 0; i < NCOLLECTORS; i++) - ssh_hash_free(pi->collectors[i]); - if (pi->generator) - ssh_hash_free(pi->generator); - if (pi->keymaker) - ssh_hash_free(pi->keymaker); - smemclr(pi, sizeof(*pi)); - sfree(pi); -} - -void prng_seed_begin(prng *pr) -{ - prng_impl *pi = container_of(pr, prng_impl, Prng); - - assert(!pi->keymaker); - - prngdebug("prng: reseed begin\n"); - - /* - * Make a hash instance that will generate the key for the new one. - */ - if (pi->generator) { - pi->keymaker = pi->generator; - pi->generator = NULL; - } else { - pi->keymaker = ssh_hash_new(pi->hashalg); - } - - put_byte(pi->keymaker, 'R'); -} - -static void prng_seed_BinarySink_write( - BinarySink *bs, const void *data, size_t len) -{ - prng *pr = BinarySink_DOWNCAST(bs, prng); - prng_impl *pi = container_of(pr, prng_impl, Prng); - assert(pi->keymaker); - prngdebug("prng: got %"SIZEu" bytes of seed\n", len); - put_data(pi->keymaker, data, len); -} - -void prng_seed_finish(prng *pr) -{ - prng_impl *pi = container_of(pr, prng_impl, Prng); - unsigned char buf[MAX_HASH_LEN]; - - assert(pi->keymaker); - - prngdebug("prng: reseed finish\n"); - - /* - * Actually generate the key. - */ - ssh_hash_final(pi->keymaker, buf); - pi->keymaker = NULL; - - /* - * Load that key into a fresh hash instance, which will become the - * new generator. - */ - assert(!pi->generator); - pi->generator = ssh_hash_new(pi->hashalg); - put_data(pi->generator, buf, pi->hashalg->hlen); - - pi->until_reseed = RESEED_DATA_SIZE; - pi->last_reseed_time = prng_reseed_time_ms(); - - smemclr(buf, sizeof(buf)); -} - -static inline void prng_generate(prng_impl *pi, void *outbuf) -{ - ssh_hash *h = ssh_hash_copy(pi->generator); - - prngdebug("prng_generate\n"); - put_byte(h, 'G'); - for (unsigned i = 0; i < 128; i += 8) - put_byte(h, pi->counter[i/BIGNUM_INT_BITS] >> (i%BIGNUM_INT_BITS)); - BignumCarry c = 1; - for (unsigned i = 0; i < lenof(pi->counter); i++) - BignumADC(pi->counter[i], c, pi->counter[i], 0, c); - ssh_hash_final(h, outbuf); -} - -void prng_read(prng *pr, void *vout, size_t size) -{ - prng_impl *pi = container_of(pr, prng_impl, Prng); - unsigned char buf[MAX_HASH_LEN]; - - assert(!pi->keymaker); - - prngdebug("prng_read %"SIZEu"\n", size); - - uint8_t *out = (uint8_t *)vout; - while (size > 0) { - prng_generate(pi, buf); - size_t to_use = size > pi->hashalg->hlen ? pi->hashalg->hlen : size; - memcpy(out, buf, to_use); - out += to_use; - size -= to_use; - } - - smemclr(buf, sizeof(buf)); - - prng_seed_begin(&pi->Prng); - prng_seed_finish(&pi->Prng); -} - -void prng_add_entropy(prng *pr, unsigned source_id, ptrlen data) -{ - prng_impl *pi = container_of(pr, prng_impl, Prng); - - assert(source_id < NOISE_MAX_SOURCES); - uint32_t counter = ++pi->source_counters[source_id]; - - size_t index = 0; - while (index+1 < NCOLLECTORS && !(counter & 1)) { - counter >>= 1; - index++; - } - - prngdebug("prng_add_entropy source=%u size=%"SIZEu" -> collector %zi\n", - source_id, data.len, index); - - put_datapl(pi->collectors[index], data); - - if (index == 0) - pi->until_reseed = (pi->until_reseed < data.len ? 0 : - pi->until_reseed - data.len); - - if (pi->until_reseed == 0 && - prng_reseed_time_ms() - pi->last_reseed_time >= 100) { - prng_seed_begin(&pi->Prng); - - unsigned char buf[MAX_HASH_LEN]; - uint32_t reseed_index = ++pi->reseeds; - prngdebug("prng entropy reseed #%"PRIu32"\n", reseed_index); - for (size_t i = 0; i < NCOLLECTORS; i++) { - prngdebug("emptying collector %"SIZEu"\n", i); - ssh_hash_digest(pi->collectors[i], buf); - put_data(&pi->Prng, buf, pi->hashalg->hlen); - ssh_hash_reset(pi->collectors[i]); - if (reseed_index & 1) - break; - reseed_index >>= 1; - } - smemclr(buf, sizeof(buf)); - prng_seed_finish(&pi->Prng); - } -} - -size_t prng_seed_bits(prng *pr) -{ - prng_impl *pi = container_of(pr, prng_impl, Prng); - return pi->hashalg->hlen * 8; -} diff --git a/sshrsa.c b/sshrsa.c deleted file mode 100644 index e3dcbdda..00000000 --- a/sshrsa.c +++ /dev/null @@ -1,1109 +0,0 @@ -/* - * RSA implementation for PuTTY. - */ - -#include -#include -#include -#include - -#include "ssh.h" -#include "mpint.h" -#include "misc.h" - -void BinarySource_get_rsa_ssh1_pub( - BinarySource *src, RSAKey *rsa, RsaSsh1Order order) -{ - unsigned bits; - mp_int *e, *m; - - bits = get_uint32(src); - if (order == RSA_SSH1_EXPONENT_FIRST) { - e = get_mp_ssh1(src); - m = get_mp_ssh1(src); - } else { - m = get_mp_ssh1(src); - e = get_mp_ssh1(src); - } - - if (rsa) { - rsa->bits = bits; - rsa->exponent = e; - rsa->modulus = m; - rsa->bytes = (mp_get_nbits(m) + 7) / 8; - } else { - mp_free(e); - mp_free(m); - } -} - -void BinarySource_get_rsa_ssh1_priv( - BinarySource *src, RSAKey *rsa) -{ - rsa->private_exponent = get_mp_ssh1(src); -} - -key_components *rsa_components(RSAKey *rsa) -{ - key_components *kc = key_components_new(); - key_components_add_text(kc, "key_type", "RSA"); - key_components_add_mp(kc, "public_modulus", rsa->modulus); - key_components_add_mp(kc, "public_exponent", rsa->exponent); - if (rsa->private_exponent) { - key_components_add_mp(kc, "private_exponent", rsa->private_exponent); - key_components_add_mp(kc, "private_p", rsa->p); - key_components_add_mp(kc, "private_q", rsa->q); - key_components_add_mp(kc, "private_inverse_q_mod_p", rsa->iqmp); - } - return kc; -} - -RSAKey *BinarySource_get_rsa_ssh1_priv_agent(BinarySource *src) -{ - RSAKey *rsa = snew(RSAKey); - memset(rsa, 0, sizeof(RSAKey)); - - get_rsa_ssh1_pub(src, rsa, RSA_SSH1_MODULUS_FIRST); - get_rsa_ssh1_priv(src, rsa); - - /* SSH-1 names p and q the other way round, i.e. we have the - * inverse of p mod q and not of q mod p. We swap the names, - * because our internal RSA wants iqmp. */ - rsa->iqmp = get_mp_ssh1(src); - rsa->q = get_mp_ssh1(src); - rsa->p = get_mp_ssh1(src); - - return rsa; -} - -bool rsa_ssh1_encrypt(unsigned char *data, int length, RSAKey *key) -{ - mp_int *b1, *b2; - int i; - unsigned char *p; - - if (key->bytes < length + 4) - return false; /* RSA key too short! */ - - memmove(data + key->bytes - length, data, length); - data[0] = 0; - data[1] = 2; - - size_t npad = key->bytes - length - 3; - /* - * Generate a sequence of nonzero padding bytes. We do this in a - * reasonably uniform way and without having to loop round - * retrying the random number generation, by first generating an - * integer in [0,2^n) for an appropriately large n; then we - * repeatedly multiply by 255 to give an integer in [0,255*2^n), - * extract the top 8 bits to give an integer in [0,255), and mask - * those bits off before multiplying up again for the next digit. - * This gives us a sequence of numbers in [0,255), and of course - * adding 1 to each of them gives numbers in [1,256) as we wanted. - * - * (You could imagine this being a sort of fixed-point operation: - * given a uniformly random binary _fraction_, multiplying it by k - * and subtracting off the integer part will yield you a sequence - * of integers each in [0,k). I'm just doing that scaled up by a - * power of 2 to avoid the fractions.) - */ - size_t random_bits = (npad + 16) * 8; - mp_int *randval = mp_new(random_bits + 8); - mp_int *tmp = mp_random_bits(random_bits); - mp_copy_into(randval, tmp); - mp_free(tmp); - for (i = 2; i < key->bytes - length - 1; i++) { - mp_mul_integer_into(randval, randval, 255); - uint8_t byte = mp_get_byte(randval, random_bits / 8); - assert(byte != 255); - data[i] = byte + 1; - mp_reduce_mod_2to(randval, random_bits); - } - mp_free(randval); - data[key->bytes - length - 1] = 0; - - b1 = mp_from_bytes_be(make_ptrlen(data, key->bytes)); - - b2 = mp_modpow(b1, key->exponent, key->modulus); - - p = data; - for (i = key->bytes; i--;) { - *p++ = mp_get_byte(b2, i); - } - - mp_free(b1); - mp_free(b2); - - return true; -} - -/* - * 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. - */ -static mp_int *crt_modpow(mp_int *base, mp_int *exp, mp_int *mod, - mp_int *p, mp_int *q, mp_int *iqmp) -{ - mp_int *pm1, *qm1, *pexp, *qexp, *presult, *qresult; - mp_int *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 = mp_copy(p); - mp_sub_integer_into(pm1, pm1, 1); - qm1 = mp_copy(q); - mp_sub_integer_into(qm1, qm1, 1); - pexp = mp_mod(exp, pm1); - qexp = mp_mod(exp, qm1); - - /* - * Do the two modpows. - */ - mp_int *base_mod_p = mp_mod(base, p); - presult = mp_modpow(base_mod_p, pexp, p); - mp_free(base_mod_p); - mp_int *base_mod_q = mp_mod(base, q); - qresult = mp_modpow(base_mod_q, qexp, q); - mp_free(base_mod_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 presult-qresult < 0, we add p to it to keep it positive.) - */ - unsigned presult_too_small = mp_cmp_hs(qresult, presult); - mp_cond_add_into(presult, presult, p, presult_too_small); - - diff = mp_sub(presult, qresult); - multiplier = mp_mul(iqmp, q); - ret0 = mp_mul(multiplier, diff); - mp_add_into(ret0, ret0, qresult); - - /* - * Finally, reduce the result mod n. - */ - ret = mp_mod(ret0, mod); - - /* - * Free all the intermediate results before returning. - */ - mp_free(pm1); - mp_free(qm1); - mp_free(pexp); - mp_free(qexp); - mp_free(presult); - mp_free(qresult); - mp_free(diff); - mp_free(multiplier); - mp_free(ret0); - - return ret; -} - -/* - * Wrapper on crt_modpow that looks up all the right values from an - * RSAKey. - */ -static mp_int *rsa_privkey_op(mp_int *input, RSAKey *key) -{ - return crt_modpow(input, key->private_exponent, - key->modulus, key->p, key->q, key->iqmp); -} - -mp_int *rsa_ssh1_decrypt(mp_int *input, RSAKey *key) -{ - return rsa_privkey_op(input, key); -} - -bool rsa_ssh1_decrypt_pkcs1(mp_int *input, RSAKey *key, - strbuf *outbuf) -{ - strbuf *data = strbuf_new_nm(); - bool success = false; - BinarySource src[1]; - - { - mp_int *b = rsa_ssh1_decrypt(input, key); - for (size_t i = (mp_get_nbits(key->modulus) + 7) / 8; i-- > 0 ;) { - put_byte(data, mp_get_byte(b, i)); - } - mp_free(b); - } - - BinarySource_BARE_INIT(src, data->u, data->len); - - /* Check PKCS#1 formatting prefix */ - if (get_byte(src) != 0) goto out; - if (get_byte(src) != 2) goto out; - while (1) { - unsigned char byte = get_byte(src); - if (get_err(src)) goto out; - if (byte == 0) - break; - } - - /* Everything else is the payload */ - success = true; - put_data(outbuf, get_ptr(src), get_avail(src)); - - out: - strbuf_free(data); - return success; -} - -static void append_hex_to_strbuf(strbuf *sb, mp_int *x) -{ - if (sb->len > 0) - put_byte(sb, ','); - put_data(sb, "0x", 2); - char *hex = mp_get_hex(x); - size_t hexlen = strlen(hex); - put_data(sb, hex, hexlen); - smemclr(hex, hexlen); - sfree(hex); -} - -char *rsastr_fmt(RSAKey *key) -{ - strbuf *sb = strbuf_new(); - - append_hex_to_strbuf(sb, key->exponent); - append_hex_to_strbuf(sb, key->modulus); - - return strbuf_to_str(sb); -} - -/* - * Generate a fingerprint string for the key. Compatible with the - * OpenSSH fingerprint code. - */ -char *rsa_ssh1_fingerprint(RSAKey *key) -{ - unsigned char digest[16]; - strbuf *out; - int i; - - /* - * The hash preimage for SSH-1 key fingerprinting consists of the - * modulus and exponent _without_ any preceding length field - - * just the minimum number of bytes to represent each integer, - * stored big-endian, concatenated with no marker at the division - * between them. - */ - - ssh_hash *hash = ssh_hash_new(&ssh_md5); - for (size_t i = (mp_get_nbits(key->modulus) + 7) / 8; i-- > 0 ;) - put_byte(hash, mp_get_byte(key->modulus, i)); - for (size_t i = (mp_get_nbits(key->exponent) + 7) / 8; i-- > 0 ;) - put_byte(hash, mp_get_byte(key->exponent, i)); - ssh_hash_final(hash, digest); - - out = strbuf_new(); - strbuf_catf(out, "%"SIZEu" ", mp_get_nbits(key->modulus)); - for (i = 0; i < 16; i++) - strbuf_catf(out, "%s%02x", i ? ":" : "", digest[i]); - if (key->comment) - strbuf_catf(out, " %s", key->comment); - return strbuf_to_str(out); -} - -/* - * Wrap the output of rsa_ssh1_fingerprint up into the same kind of - * structure that comes from ssh2_all_fingerprints. - */ -char **rsa_ssh1_fake_all_fingerprints(RSAKey *key) -{ - char **ret = snewn(SSH_N_FPTYPES, char *); - for (unsigned i = 0; i < SSH_N_FPTYPES; i++) - ret[i] = NULL; - ret[SSH_FPTYPE_MD5] = rsa_ssh1_fingerprint(key); - return ret; -} - -/* - * 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. - */ -bool rsa_verify(RSAKey *key) -{ - mp_int *n, *ed, *pm1, *qm1; - unsigned ok = 1; - - /* Preliminary checks: p,q can't be 0 or 1. (Of course no other - * very small value is any good either, but these are the values - * we _must_ check for to avoid assertion failures further down - * this function.) */ - if (!(mp_hs_integer(key->p, 2) & mp_hs_integer(key->q, 2))) - return false; - - /* n must equal pq. */ - n = mp_mul(key->p, key->q); - ok &= mp_cmp_eq(n, key->modulus); - mp_free(n); - - /* e * d must be congruent to 1, modulo (p-1) and modulo (q-1). */ - pm1 = mp_copy(key->p); - mp_sub_integer_into(pm1, pm1, 1); - ed = mp_modmul(key->exponent, key->private_exponent, pm1); - mp_free(pm1); - ok &= mp_eq_integer(ed, 1); - mp_free(ed); - - qm1 = mp_copy(key->q); - mp_sub_integer_into(qm1, qm1, 1); - ed = mp_modmul(key->exponent, key->private_exponent, qm1); - mp_free(qm1); - ok &= mp_eq_integer(ed, 1); - mp_free(ed); - - /* - * 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. - */ - mp_int *p_new = mp_max(key->p, key->q); - mp_int *q_new = mp_min(key->p, key->q); - mp_free(key->p); - mp_free(key->q); - mp_free(key->iqmp); - key->p = p_new; - key->q = q_new; - key->iqmp = mp_invert(key->q, key->p); - - return ok; -} - -void rsa_ssh1_public_blob(BinarySink *bs, RSAKey *key, - RsaSsh1Order order) -{ - put_uint32(bs, mp_get_nbits(key->modulus)); - if (order == RSA_SSH1_EXPONENT_FIRST) { - put_mp_ssh1(bs, key->exponent); - put_mp_ssh1(bs, key->modulus); - } else { - put_mp_ssh1(bs, key->modulus); - put_mp_ssh1(bs, key->exponent); - } -} - -void rsa_ssh1_private_blob_agent(BinarySink *bs, RSAKey *key) -{ - rsa_ssh1_public_blob(bs, key, RSA_SSH1_MODULUS_FIRST); - put_mp_ssh1(bs, key->private_exponent); - put_mp_ssh1(bs, key->iqmp); - put_mp_ssh1(bs, key->q); - put_mp_ssh1(bs, key->p); -} - -/* Given an SSH-1 public key blob, determine its length. */ -int rsa_ssh1_public_blob_len(ptrlen data) -{ - BinarySource src[1]; - - BinarySource_BARE_INIT_PL(src, data); - - /* Expect a length word, then exponent and modulus. (It doesn't - * even matter which order.) */ - get_uint32(src); - mp_free(get_mp_ssh1(src)); - mp_free(get_mp_ssh1(src)); - - if (get_err(src)) - return -1; - - /* Return the number of bytes consumed. */ - return src->pos; -} - -void freersapriv(RSAKey *key) -{ - if (key->private_exponent) { - mp_free(key->private_exponent); - key->private_exponent = NULL; - } - if (key->p) { - mp_free(key->p); - key->p = NULL; - } - if (key->q) { - mp_free(key->q); - key->q = NULL; - } - if (key->iqmp) { - mp_free(key->iqmp); - key->iqmp = NULL; - } -} - -void freersakey(RSAKey *key) -{ - freersapriv(key); - if (key->modulus) { - mp_free(key->modulus); - key->modulus = NULL; - } - if (key->exponent) { - mp_free(key->exponent); - key->exponent = NULL; - } - if (key->comment) { - sfree(key->comment); - key->comment = NULL; - } -} - -/* ---------------------------------------------------------------------- - * Implementation of the ssh-rsa signing key type family. - */ - -struct ssh2_rsa_extra { - unsigned signflags; -}; - -static void rsa2_freekey(ssh_key *key); /* forward reference */ - -static ssh_key *rsa2_new_pub(const ssh_keyalg *self, ptrlen data) -{ - BinarySource src[1]; - RSAKey *rsa; - - BinarySource_BARE_INIT_PL(src, data); - if (!ptrlen_eq_string(get_string(src), "ssh-rsa")) - return NULL; - - rsa = snew(RSAKey); - rsa->sshk.vt = self; - rsa->exponent = get_mp_ssh2(src); - rsa->modulus = get_mp_ssh2(src); - rsa->private_exponent = NULL; - rsa->p = rsa->q = rsa->iqmp = NULL; - rsa->comment = NULL; - - if (get_err(src)) { - rsa2_freekey(&rsa->sshk); - return NULL; - } - - return &rsa->sshk; -} - -static void rsa2_freekey(ssh_key *key) -{ - RSAKey *rsa = container_of(key, RSAKey, sshk); - freersakey(rsa); - sfree(rsa); -} - -static char *rsa2_cache_str(ssh_key *key) -{ - RSAKey *rsa = container_of(key, RSAKey, sshk); - return rsastr_fmt(rsa); -} - -static key_components *rsa2_components(ssh_key *key) -{ - RSAKey *rsa = container_of(key, RSAKey, sshk); - return rsa_components(rsa); -} - -static void rsa2_public_blob(ssh_key *key, BinarySink *bs) -{ - RSAKey *rsa = container_of(key, RSAKey, sshk); - - put_stringz(bs, "ssh-rsa"); - put_mp_ssh2(bs, rsa->exponent); - put_mp_ssh2(bs, rsa->modulus); -} - -static void rsa2_private_blob(ssh_key *key, BinarySink *bs) -{ - RSAKey *rsa = container_of(key, RSAKey, sshk); - - put_mp_ssh2(bs, rsa->private_exponent); - put_mp_ssh2(bs, rsa->p); - put_mp_ssh2(bs, rsa->q); - put_mp_ssh2(bs, rsa->iqmp); -} - -static ssh_key *rsa2_new_priv(const ssh_keyalg *self, - ptrlen pub, ptrlen priv) -{ - BinarySource src[1]; - ssh_key *sshk; - RSAKey *rsa; - - sshk = rsa2_new_pub(self, pub); - if (!sshk) - return NULL; - - rsa = container_of(sshk, RSAKey, sshk); - BinarySource_BARE_INIT_PL(src, priv); - rsa->private_exponent = get_mp_ssh2(src); - rsa->p = get_mp_ssh2(src); - rsa->q = get_mp_ssh2(src); - rsa->iqmp = get_mp_ssh2(src); - - if (get_err(src) || !rsa_verify(rsa)) { - rsa2_freekey(&rsa->sshk); - return NULL; - } - - return &rsa->sshk; -} - -static ssh_key *rsa2_new_priv_openssh(const ssh_keyalg *self, - BinarySource *src) -{ - RSAKey *rsa; - - rsa = snew(RSAKey); - rsa->sshk.vt = &ssh_rsa; - rsa->comment = NULL; - - rsa->modulus = get_mp_ssh2(src); - rsa->exponent = get_mp_ssh2(src); - rsa->private_exponent = get_mp_ssh2(src); - rsa->iqmp = get_mp_ssh2(src); - rsa->p = get_mp_ssh2(src); - rsa->q = get_mp_ssh2(src); - - if (get_err(src) || !rsa_verify(rsa)) { - rsa2_freekey(&rsa->sshk); - return NULL; - } - - return &rsa->sshk; -} - -static void rsa2_openssh_blob(ssh_key *key, BinarySink *bs) -{ - RSAKey *rsa = container_of(key, RSAKey, sshk); - - put_mp_ssh2(bs, rsa->modulus); - put_mp_ssh2(bs, rsa->exponent); - put_mp_ssh2(bs, rsa->private_exponent); - put_mp_ssh2(bs, rsa->iqmp); - put_mp_ssh2(bs, rsa->p); - put_mp_ssh2(bs, rsa->q); -} - -static int rsa2_pubkey_bits(const ssh_keyalg *self, ptrlen pub) -{ - ssh_key *sshk; - RSAKey *rsa; - int ret; - - sshk = rsa2_new_pub(self, pub); - if (!sshk) - return -1; - - rsa = container_of(sshk, RSAKey, sshk); - ret = mp_get_nbits(rsa->modulus); - rsa2_freekey(&rsa->sshk); - - return ret; -} - -static inline const ssh_hashalg *rsa2_hash_alg_for_flags( - unsigned flags, const char **protocol_id_out) -{ - const ssh_hashalg *halg; - const char *protocol_id; - - if (flags & SSH_AGENT_RSA_SHA2_256) { - halg = &ssh_sha256; - protocol_id = "rsa-sha2-256"; - } else if (flags & SSH_AGENT_RSA_SHA2_512) { - halg = &ssh_sha512; - protocol_id = "rsa-sha2-512"; - } else { - halg = &ssh_sha1; - protocol_id = "ssh-rsa"; - } - - if (protocol_id_out) - *protocol_id_out = protocol_id; - - return halg; -} - -static inline ptrlen rsa_pkcs1_prefix_for_hash(const ssh_hashalg *halg) -{ - if (halg == &ssh_sha1) { - /* - * This is the magic ASN.1/DER prefix that goes in the decoded - * signature, between the string of FFs and the actual SHA-1 - * 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 sha1_asn1_prefix[] = { - 0x00, 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2B, - 0x0E, 0x03, 0x02, 0x1A, 0x05, 0x00, 0x04, 0x14, - }; - return PTRLEN_FROM_CONST_BYTES(sha1_asn1_prefix); - } - - if (halg == &ssh_sha256) { - /* - * A similar piece of ASN.1 used for signatures using SHA-256, - * in the same format but differing only in various length - * fields and OID. - */ - static const unsigned char sha256_asn1_prefix[] = { - 0x00, 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, - 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, - 0x05, 0x00, 0x04, 0x20, - }; - return PTRLEN_FROM_CONST_BYTES(sha256_asn1_prefix); - } - - if (halg == &ssh_sha512) { - /* - * And one more for SHA-512. - */ - static const unsigned char sha512_asn1_prefix[] = { - 0x00, 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, - 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, - 0x05, 0x00, 0x04, 0x40, - }; - return PTRLEN_FROM_CONST_BYTES(sha512_asn1_prefix); - } - - unreachable("bad hash algorithm for RSA PKCS#1"); -} - -static inline size_t rsa_pkcs1_length_of_fixed_parts(const ssh_hashalg *halg) -{ - ptrlen asn1_prefix = rsa_pkcs1_prefix_for_hash(halg); - return halg->hlen + asn1_prefix.len + 2; -} - -static unsigned char *rsa_pkcs1_signature_string( - size_t nbytes, const ssh_hashalg *halg, ptrlen data) -{ - size_t fixed_parts = rsa_pkcs1_length_of_fixed_parts(halg); - assert(nbytes >= fixed_parts); - size_t padding = nbytes - fixed_parts; - - ptrlen asn1_prefix = rsa_pkcs1_prefix_for_hash(halg); - - unsigned char *bytes = snewn(nbytes, unsigned char); - - bytes[0] = 0; - bytes[1] = 1; - - memset(bytes + 2, 0xFF, padding); - - memcpy(bytes + 2 + padding, asn1_prefix.ptr, asn1_prefix.len); - - ssh_hash *h = ssh_hash_new(halg); - put_datapl(h, data); - ssh_hash_final(h, bytes + 2 + padding + asn1_prefix.len); - - return bytes; -} - -static bool rsa2_verify(ssh_key *key, ptrlen sig, ptrlen data) -{ - RSAKey *rsa = container_of(key, RSAKey, sshk); - BinarySource src[1]; - ptrlen type, in_pl; - mp_int *in, *out; - - const struct ssh2_rsa_extra *extra = - (const struct ssh2_rsa_extra *)key->vt->extra; - - const ssh_hashalg *halg = rsa2_hash_alg_for_flags(extra->signflags, NULL); - - /* Start by making sure the key is even long enough to encode a - * signature. If not, everything fails to verify. */ - size_t nbytes = (mp_get_nbits(rsa->modulus) + 7) / 8; - if (nbytes < rsa_pkcs1_length_of_fixed_parts(halg)) - return false; - - BinarySource_BARE_INIT_PL(src, sig); - type = get_string(src); - /* - * RFC 4253 section 6.6: the signature integer in an ssh-rsa - * signature is 'without lengths or padding'. That is, we _don't_ - * expect the usual leading zero byte if the topmost bit of the - * first byte is set. (However, because of the possibility of - * BUG_SSH2_RSA_PADDING at the other end, we tolerate it if it's - * there.) So we can't use get_mp_ssh2, which enforces that - * leading-byte scheme; instead we use get_string and - * mp_from_bytes_be, which will tolerate anything. - */ - in_pl = get_string(src); - if (get_err(src) || !ptrlen_eq_string(type, key->vt->ssh_id)) - return false; - - in = mp_from_bytes_be(in_pl); - out = mp_modpow(in, rsa->exponent, rsa->modulus); - mp_free(in); - - unsigned diff = 0; - - unsigned char *bytes = rsa_pkcs1_signature_string(nbytes, halg, data); - for (size_t i = 0; i < nbytes; i++) - diff |= bytes[nbytes-1 - i] ^ mp_get_byte(out, i); - smemclr(bytes, nbytes); - sfree(bytes); - mp_free(out); - - return diff == 0; -} - -static void rsa2_sign(ssh_key *key, ptrlen data, - unsigned flags, BinarySink *bs) -{ - RSAKey *rsa = container_of(key, RSAKey, sshk); - unsigned char *bytes; - size_t nbytes; - mp_int *in, *out; - const ssh_hashalg *halg; - const char *sign_alg_name; - - const struct ssh2_rsa_extra *extra = - (const struct ssh2_rsa_extra *)key->vt->extra; - flags |= extra->signflags; - - halg = rsa2_hash_alg_for_flags(flags, &sign_alg_name); - - nbytes = (mp_get_nbits(rsa->modulus) + 7) / 8; - - bytes = rsa_pkcs1_signature_string(nbytes, halg, data); - in = mp_from_bytes_be(make_ptrlen(bytes, nbytes)); - smemclr(bytes, nbytes); - sfree(bytes); - - out = rsa_privkey_op(in, rsa); - mp_free(in); - - put_stringz(bs, sign_alg_name); - nbytes = (mp_get_nbits(out) + 7) / 8; - put_uint32(bs, nbytes); - for (size_t i = 0; i < nbytes; i++) - put_byte(bs, mp_get_byte(out, nbytes - 1 - i)); - - mp_free(out); -} - -static char *rsa2_invalid(ssh_key *key, unsigned flags) -{ - RSAKey *rsa = container_of(key, RSAKey, sshk); - size_t bits = mp_get_nbits(rsa->modulus), nbytes = (bits + 7) / 8; - const char *sign_alg_name; - const ssh_hashalg *halg = rsa2_hash_alg_for_flags(flags, &sign_alg_name); - if (nbytes < rsa_pkcs1_length_of_fixed_parts(halg)) { - return dupprintf( - "%"SIZEu"-bit RSA key is too short to generate %s signatures", - bits, sign_alg_name); - } - - return NULL; -} - -static const struct ssh2_rsa_extra - rsa_extra = { 0 }, - rsa_sha256_extra = { SSH_AGENT_RSA_SHA2_256 }, - rsa_sha512_extra = { SSH_AGENT_RSA_SHA2_512 }; - -#define COMMON_KEYALG_FIELDS \ - .new_pub = rsa2_new_pub, \ - .new_priv = rsa2_new_priv, \ - .new_priv_openssh = rsa2_new_priv_openssh, \ - .freekey = rsa2_freekey, \ - .invalid = rsa2_invalid, \ - .sign = rsa2_sign, \ - .verify = rsa2_verify, \ - .public_blob = rsa2_public_blob, \ - .private_blob = rsa2_private_blob, \ - .openssh_blob = rsa2_openssh_blob, \ - .cache_str = rsa2_cache_str, \ - .components = rsa2_components, \ - .pubkey_bits = rsa2_pubkey_bits, \ - .cache_id = "rsa2" - -const ssh_keyalg ssh_rsa = { - COMMON_KEYALG_FIELDS, - .ssh_id = "ssh-rsa", - .supported_flags = SSH_AGENT_RSA_SHA2_256 | SSH_AGENT_RSA_SHA2_512, - .extra = &rsa_extra, -}; - -const ssh_keyalg ssh_rsa_sha256 = { - COMMON_KEYALG_FIELDS, - .ssh_id = "rsa-sha2-256", - .supported_flags = 0, - .extra = &rsa_sha256_extra, -}; - -const ssh_keyalg ssh_rsa_sha512 = { - COMMON_KEYALG_FIELDS, - .ssh_id = "rsa-sha2-512", - .supported_flags = 0, - .extra = &rsa_sha512_extra, -}; - -RSAKey *ssh_rsakex_newkey(ptrlen data) -{ - ssh_key *sshk = rsa2_new_pub(&ssh_rsa, data); - if (!sshk) - return NULL; - return container_of(sshk, RSAKey, sshk); -} - -void ssh_rsakex_freekey(RSAKey *key) -{ - rsa2_freekey(&key->sshk); -} - -int ssh_rsakex_klen(RSAKey *rsa) -{ - return mp_get_nbits(rsa->modulus); -} - -static void oaep_mask(const ssh_hashalg *h, void *seed, int seedlen, - void *vdata, int datalen) -{ - unsigned char *data = (unsigned char *)vdata; - unsigned count = 0; - - ssh_hash *s = ssh_hash_new(h); - - while (datalen > 0) { - int i, max = (datalen > h->hlen ? h->hlen : datalen); - unsigned char hash[MAX_HASH_LEN]; - - ssh_hash_reset(s); - assert(h->hlen <= MAX_HASH_LEN); - put_data(s, seed, seedlen); - put_uint32(s, count); - ssh_hash_digest(s, hash); - count++; - - for (i = 0; i < max; i++) - data[i] ^= hash[i]; - - data += max; - datalen -= max; - } - - ssh_hash_free(s); -} - -strbuf *ssh_rsakex_encrypt(RSAKey *rsa, const ssh_hashalg *h, ptrlen in) -{ - mp_int *b1, *b2; - 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 + mp_get_nbits(rsa->modulus)) / 8; - - /* The length of the input data must be at most k - 2hLen - 2. */ - assert(in.len > 0 && in.len <= k - 2*HLEN - 2); - - /* The length of the output data wants to be precisely k. */ - strbuf *toret = strbuf_new_nm(); - int outlen = k; - unsigned char *out = strbuf_append(toret, outlen); - - /* - * 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. */ - random_read(out + 1, HLEN); - /* At position 1+HLEN, the data block DB, consisting of: */ - /* The hash of the label (we only support an empty label here) */ - hash_simple(h, PTRLEN_LITERAL(""), 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 - in.len - 1] = 1; - memcpy(out + outlen - in.len, in.ptr, in.len); - - /* - * 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 = mp_from_bytes_be(make_ptrlen(out, outlen)); - b2 = mp_modpow(b1, rsa->exponent, rsa->modulus); - p = (char *)out; - for (i = outlen; i--;) { - *p++ = mp_get_byte(b2, i); - } - mp_free(b1); - mp_free(b2); - - /* - * And we're done. - */ - return toret; -} - -mp_int *ssh_rsakex_decrypt( - RSAKey *rsa, const ssh_hashalg *h, ptrlen ciphertext) -{ - mp_int *b1, *b2; - int outlen, i; - unsigned char *out; - unsigned char labelhash[64]; - BinarySource src[1]; - const int HLEN = h->hlen; - - /* - * Decryption side of the RSA key exchange operation. - */ - - /* The length of the encrypted data should be exactly the length - * in octets of the RSA modulus.. */ - outlen = (7 + mp_get_nbits(rsa->modulus)) / 8; - if (ciphertext.len != outlen) - return NULL; - - /* Do the RSA decryption, and extract the result into a byte array. */ - b1 = mp_from_bytes_be(ciphertext); - b2 = rsa_privkey_op(b1, rsa); - out = snewn(outlen, unsigned char); - for (i = 0; i < outlen; i++) - out[i] = mp_get_byte(b2, outlen-1-i); - mp_free(b1); - mp_free(b2); - - /* Do the OAEP masking operations, in the reverse order from encryption */ - oaep_mask(h, out+HLEN+1, outlen-HLEN-1, out+1, HLEN); - oaep_mask(h, out+1, HLEN, out+HLEN+1, outlen-HLEN-1); - - /* Check the leading byte is zero. */ - if (out[0] != 0) { - sfree(out); - return NULL; - } - /* Check the label hash at position 1+HLEN */ - assert(HLEN <= lenof(labelhash)); - hash_simple(h, PTRLEN_LITERAL(""), labelhash); - if (memcmp(out + HLEN + 1, labelhash, HLEN)) { - sfree(out); - return NULL; - } - /* Expect zero bytes followed by a 1 byte */ - for (i = 1 + 2 * HLEN; i < outlen; i++) { - if (out[i] == 1) { - i++; /* skip over the 1 byte */ - break; - } else if (out[i] != 0) { - sfree(out); - return NULL; - } - } - /* And what's left is the input message data, which should be - * encoded as an ordinary SSH-2 mpint. */ - BinarySource_BARE_INIT(src, out + i, outlen - i); - b1 = get_mp_ssh2(src); - sfree(out); - if (get_err(src) || get_avail(src) != 0) { - mp_free(b1); - return NULL; - } - - /* Success! */ - return b1; -} - -static const struct ssh_rsa_kex_extra ssh_rsa_kex_extra_sha1 = { 1024 }; -static const struct ssh_rsa_kex_extra ssh_rsa_kex_extra_sha256 = { 2048 }; - -static const ssh_kex ssh_rsa_kex_sha1 = { - "rsa1024-sha1", NULL, KEXTYPE_RSA, - &ssh_sha1, &ssh_rsa_kex_extra_sha1, -}; - -static const ssh_kex ssh_rsa_kex_sha256 = { - "rsa2048-sha256", NULL, KEXTYPE_RSA, - &ssh_sha256, &ssh_rsa_kex_extra_sha256, -}; - -static const ssh_kex *const rsa_kex_list[] = { - &ssh_rsa_kex_sha256, - &ssh_rsa_kex_sha1 -}; - -const ssh_kexes ssh_rsa_kex = { lenof(rsa_kex_list), rsa_kex_list }; diff --git a/sshsh256.c b/sshsh256.c deleted file mode 100644 index 206a976c..00000000 --- a/sshsh256.c +++ /dev/null @@ -1,939 +0,0 @@ -/* - * SHA-256 algorithm as described at - * - * http://csrc.nist.gov/cryptval/shs.html - */ - -#include "ssh.h" -#include - -/* - * Start by deciding whether we can support hardware SHA at all. - */ -#define HW_SHA256_NONE 0 -#define HW_SHA256_NI 1 -#define HW_SHA256_NEON 2 - -#ifdef _FORCE_SHA_NI -# define HW_SHA256 HW_SHA256_NI -#elif defined(__clang__) -# if __has_attribute(target) && __has_include() && \ - (defined(__x86_64__) || defined(__i386)) -# define HW_SHA256 HW_SHA256_NI -# endif -#elif defined(__GNUC__) -# if (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 9)) && \ - (defined(__x86_64__) || defined(__i386)) -# define HW_SHA256 HW_SHA256_NI -# endif -#elif defined (_MSC_VER) -# if (defined(_M_X64) || defined(_M_IX86)) && _MSC_FULL_VER >= 150030729 -# define HW_SHA256 HW_SHA256_NI -# endif -#endif - -#ifdef _FORCE_SHA_NEON -# define HW_SHA256 HW_SHA256_NEON -#elif defined __BYTE_ORDER__ && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ - /* Arm can potentially support both endiannesses, but this code - * hasn't been tested on anything but little. If anyone wants to - * run big-endian, they'll need to fix it first. */ -#elif defined __ARM_FEATURE_CRYPTO - /* If the Arm crypto extension is available already, we can - * support NEON SHA without having to enable anything by hand */ -# define HW_SHA256 HW_SHA256_NEON -#elif defined(__clang__) -# if __has_attribute(target) && __has_include() && \ - (defined(__aarch64__)) - /* clang can enable the crypto extension in AArch64 using - * __attribute__((target)) */ -# define HW_SHA256 HW_SHA256_NEON -# define USE_CLANG_ATTR_TARGET_AARCH64 -# endif -#elif defined _MSC_VER - /* Visual Studio supports the crypto extension when targeting - * AArch64, but as of VS2017, the AArch32 header doesn't quite - * manage it (declaring the shae/shad intrinsics without a round - * key operand). */ -# if defined _M_ARM64 -# define HW_SHA256 HW_SHA256_NEON -# if defined _M_ARM64 -# define USE_ARM64_NEON_H /* unusual header name in this case */ -# endif -# endif -#endif - -#if defined _FORCE_SOFTWARE_SHA || !defined HW_SHA256 -# undef HW_SHA256 -# define HW_SHA256 HW_SHA256_NONE -#endif - -/* - * The actual query function that asks if hardware acceleration is - * available. - */ -static bool sha256_hw_available(void); - -/* - * The top-level selection function, caching the results of - * sha256_hw_available() so it only has to run once. - */ -static bool sha256_hw_available_cached(void) -{ - static bool initialised = false; - static bool hw_available; - if (!initialised) { - hw_available = sha256_hw_available(); - initialised = true; - } - return hw_available; -} - -static ssh_hash *sha256_select(const ssh_hashalg *alg) -{ - const ssh_hashalg *real_alg = - sha256_hw_available_cached() ? &ssh_sha256_hw : &ssh_sha256_sw; - - return ssh_hash_new(real_alg); -} - -const ssh_hashalg ssh_sha256 = { - .new = sha256_select, - .hlen = 32, - .blocklen = 64, - HASHALG_NAMES_ANNOTATED("SHA-256", "dummy selector vtable"), -}; - -/* ---------------------------------------------------------------------- - * Definitions likely to be helpful to multiple implementations. - */ - -static const uint32_t sha256_initial_state[] = { - 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, - 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19, -}; - -static const uint32_t sha256_round_constants[] = { - 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, -}; - -#define SHA256_ROUNDS 64 - -typedef struct sha256_block sha256_block; -struct sha256_block { - uint8_t block[64]; - size_t used; - uint64_t len; -}; - -static inline void sha256_block_setup(sha256_block *blk) -{ - blk->used = 0; - blk->len = 0; -} - -static inline bool sha256_block_write( - sha256_block *blk, const void **vdata, size_t *len) -{ - size_t blkleft = sizeof(blk->block) - blk->used; - size_t chunk = *len < blkleft ? *len : blkleft; - - const uint8_t *p = *vdata; - memcpy(blk->block + blk->used, p, chunk); - *vdata = p + chunk; - *len -= chunk; - blk->used += chunk; - blk->len += chunk; - - if (blk->used == sizeof(blk->block)) { - blk->used = 0; - return true; - } - - return false; -} - -static inline void sha256_block_pad(sha256_block *blk, BinarySink *bs) -{ - uint64_t final_len = blk->len << 3; - size_t pad = 1 + (63 & (55 - blk->used)); - - put_byte(bs, 0x80); - for (size_t i = 1; i < pad; i++) - put_byte(bs, 0); - put_uint64(bs, final_len); - - assert(blk->used == 0 && "Should have exactly hit a block boundary"); -} - -/* ---------------------------------------------------------------------- - * Software implementation of SHA-256. - */ - -static inline uint32_t ror(uint32_t x, unsigned y) -{ - return (x << (31 & -y)) | (x >> (31 & y)); -} - -static inline uint32_t Ch(uint32_t ctrl, uint32_t if1, uint32_t if0) -{ - return if0 ^ (ctrl & (if1 ^ if0)); -} - -static inline uint32_t Maj(uint32_t x, uint32_t y, uint32_t z) -{ - return (x & y) | (z & (x | y)); -} - -static inline uint32_t Sigma_0(uint32_t x) -{ - return ror(x,2) ^ ror(x,13) ^ ror(x,22); -} - -static inline uint32_t Sigma_1(uint32_t x) -{ - return ror(x,6) ^ ror(x,11) ^ ror(x,25); -} - -static inline uint32_t sigma_0(uint32_t x) -{ - return ror(x,7) ^ ror(x,18) ^ (x >> 3); -} - -static inline uint32_t sigma_1(uint32_t x) -{ - return ror(x,17) ^ ror(x,19) ^ (x >> 10); -} - -static inline void sha256_sw_round( - unsigned round_index, const uint32_t *schedule, - uint32_t *a, uint32_t *b, uint32_t *c, uint32_t *d, - uint32_t *e, uint32_t *f, uint32_t *g, uint32_t *h) -{ - uint32_t t1 = *h + Sigma_1(*e) + Ch(*e,*f,*g) + - sha256_round_constants[round_index] + schedule[round_index]; - - uint32_t t2 = Sigma_0(*a) + Maj(*a,*b,*c); - - *d += t1; - *h = t1 + t2; -} - -static void sha256_sw_block(uint32_t *core, const uint8_t *block) -{ - uint32_t w[SHA256_ROUNDS]; - uint32_t a,b,c,d,e,f,g,h; - - for (size_t t = 0; t < 16; t++) - w[t] = GET_32BIT_MSB_FIRST(block + 4*t); - - for (size_t t = 16; t < SHA256_ROUNDS; t++) - w[t] = sigma_1(w[t-2]) + w[t-7] + sigma_0(w[t-15]) + w[t-16]; - - a = core[0]; b = core[1]; c = core[2]; d = core[3]; - e = core[4]; f = core[5]; g = core[6]; h = core[7]; - - for (size_t t = 0; t < SHA256_ROUNDS; t += 8) { - sha256_sw_round(t+0, w, &a,&b,&c,&d,&e,&f,&g,&h); - sha256_sw_round(t+1, w, &h,&a,&b,&c,&d,&e,&f,&g); - sha256_sw_round(t+2, w, &g,&h,&a,&b,&c,&d,&e,&f); - sha256_sw_round(t+3, w, &f,&g,&h,&a,&b,&c,&d,&e); - sha256_sw_round(t+4, w, &e,&f,&g,&h,&a,&b,&c,&d); - sha256_sw_round(t+5, w, &d,&e,&f,&g,&h,&a,&b,&c); - sha256_sw_round(t+6, w, &c,&d,&e,&f,&g,&h,&a,&b); - sha256_sw_round(t+7, w, &b,&c,&d,&e,&f,&g,&h,&a); - } - - core[0] += a; core[1] += b; core[2] += c; core[3] += d; - core[4] += e; core[5] += f; core[6] += g; core[7] += h; - - smemclr(w, sizeof(w)); -} - -typedef struct sha256_sw { - uint32_t core[8]; - sha256_block blk; - BinarySink_IMPLEMENTATION; - ssh_hash hash; -} sha256_sw; - -static void sha256_sw_write(BinarySink *bs, const void *vp, size_t len); - -static ssh_hash *sha256_sw_new(const ssh_hashalg *alg) -{ - sha256_sw *s = snew(sha256_sw); - - s->hash.vt = alg; - BinarySink_INIT(s, sha256_sw_write); - BinarySink_DELEGATE_INIT(&s->hash, s); - return &s->hash; -} - -static void sha256_sw_reset(ssh_hash *hash) -{ - sha256_sw *s = container_of(hash, sha256_sw, hash); - - memcpy(s->core, sha256_initial_state, sizeof(s->core)); - sha256_block_setup(&s->blk); -} - -static void sha256_sw_copyfrom(ssh_hash *hcopy, ssh_hash *horig) -{ - sha256_sw *copy = container_of(hcopy, sha256_sw, hash); - sha256_sw *orig = container_of(horig, sha256_sw, hash); - - memcpy(copy, orig, sizeof(*copy)); - BinarySink_COPIED(copy); - BinarySink_DELEGATE_INIT(©->hash, copy); -} - -static void sha256_sw_free(ssh_hash *hash) -{ - sha256_sw *s = container_of(hash, sha256_sw, hash); - - smemclr(s, sizeof(*s)); - sfree(s); -} - -static void sha256_sw_write(BinarySink *bs, const void *vp, size_t len) -{ - sha256_sw *s = BinarySink_DOWNCAST(bs, sha256_sw); - - while (len > 0) - if (sha256_block_write(&s->blk, &vp, &len)) - sha256_sw_block(s->core, s->blk.block); -} - -static void sha256_sw_digest(ssh_hash *hash, uint8_t *digest) -{ - sha256_sw *s = container_of(hash, sha256_sw, hash); - - sha256_block_pad(&s->blk, BinarySink_UPCAST(s)); - for (size_t i = 0; i < 8; i++) - PUT_32BIT_MSB_FIRST(digest + 4*i, s->core[i]); -} - -const ssh_hashalg ssh_sha256_sw = { - .new = sha256_sw_new, - .reset = sha256_sw_reset, - .copyfrom = sha256_sw_copyfrom, - .digest = sha256_sw_digest, - .free = sha256_sw_free, - .hlen = 32, - .blocklen = 64, - HASHALG_NAMES_ANNOTATED("SHA-256", "unaccelerated"), -}; - -/* ---------------------------------------------------------------------- - * Hardware-accelerated implementation of SHA-256 using x86 SHA-NI. - */ - -#if HW_SHA256 == HW_SHA256_NI - -/* - * Set target architecture for Clang and GCC - */ -#if defined(__clang__) || defined(__GNUC__) -# define FUNC_ISA __attribute__ ((target("sse4.1,sha"))) -#if !defined(__clang__) -# pragma GCC target("sha") -# pragma GCC target("sse4.1") -#endif -#else -# define FUNC_ISA -#endif - -#include -#include -#include -#if defined(__clang__) || defined(__GNUC__) -#include -#endif - -#if defined(__clang__) || defined(__GNUC__) -#include -#define GET_CPU_ID_0(out) \ - __cpuid(0, (out)[0], (out)[1], (out)[2], (out)[3]) -#define GET_CPU_ID_7(out) \ - __cpuid_count(7, 0, (out)[0], (out)[1], (out)[2], (out)[3]) -#else -#define GET_CPU_ID_0(out) __cpuid(out, 0) -#define GET_CPU_ID_7(out) __cpuidex(out, 7, 0) -#endif - -static bool sha256_hw_available(void) -{ - unsigned int CPUInfo[4]; - GET_CPU_ID_0(CPUInfo); - if (CPUInfo[0] < 7) - return false; - - GET_CPU_ID_7(CPUInfo); - return CPUInfo[1] & (1 << 29); /* Check SHA */ -} - -/* SHA256 implementation using new instructions - The code is based on Jeffrey Walton's SHA256 implementation: - https://github.com/noloader/SHA-Intrinsics -*/ -FUNC_ISA -static inline void sha256_ni_block(__m128i *core, const uint8_t *p) -{ - __m128i STATE0, STATE1; - __m128i MSG, TMP; - __m128i MSG0, MSG1, MSG2, MSG3; - const __m128i *block = (const __m128i *)p; - const __m128i MASK = _mm_set_epi64x( - 0x0c0d0e0f08090a0bULL, 0x0405060700010203ULL); - - /* Load initial values */ - STATE0 = core[0]; - STATE1 = core[1]; - - /* Rounds 0-3 */ - MSG = _mm_loadu_si128(block); - MSG0 = _mm_shuffle_epi8(MSG, MASK); - MSG = _mm_add_epi32(MSG0, _mm_set_epi64x( - 0xE9B5DBA5B5C0FBCFULL, 0x71374491428A2F98ULL)); - STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); - MSG = _mm_shuffle_epi32(MSG, 0x0E); - STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); - - /* Rounds 4-7 */ - MSG1 = _mm_loadu_si128(block + 1); - MSG1 = _mm_shuffle_epi8(MSG1, MASK); - MSG = _mm_add_epi32(MSG1, _mm_set_epi64x( - 0xAB1C5ED5923F82A4ULL, 0x59F111F13956C25BULL)); - STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); - MSG = _mm_shuffle_epi32(MSG, 0x0E); - STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); - MSG0 = _mm_sha256msg1_epu32(MSG0, MSG1); - - /* Rounds 8-11 */ - MSG2 = _mm_loadu_si128(block + 2); - MSG2 = _mm_shuffle_epi8(MSG2, MASK); - MSG = _mm_add_epi32(MSG2, _mm_set_epi64x( - 0x550C7DC3243185BEULL, 0x12835B01D807AA98ULL)); - STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); - MSG = _mm_shuffle_epi32(MSG, 0x0E); - STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); - MSG1 = _mm_sha256msg1_epu32(MSG1, MSG2); - - /* Rounds 12-15 */ - MSG3 = _mm_loadu_si128(block + 3); - MSG3 = _mm_shuffle_epi8(MSG3, MASK); - MSG = _mm_add_epi32(MSG3, _mm_set_epi64x( - 0xC19BF1749BDC06A7ULL, 0x80DEB1FE72BE5D74ULL)); - STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); - TMP = _mm_alignr_epi8(MSG3, MSG2, 4); - MSG0 = _mm_add_epi32(MSG0, TMP); - MSG0 = _mm_sha256msg2_epu32(MSG0, MSG3); - MSG = _mm_shuffle_epi32(MSG, 0x0E); - STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); - MSG2 = _mm_sha256msg1_epu32(MSG2, MSG3); - - /* Rounds 16-19 */ - MSG = _mm_add_epi32(MSG0, _mm_set_epi64x( - 0x240CA1CC0FC19DC6ULL, 0xEFBE4786E49B69C1ULL)); - STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); - TMP = _mm_alignr_epi8(MSG0, MSG3, 4); - MSG1 = _mm_add_epi32(MSG1, TMP); - MSG1 = _mm_sha256msg2_epu32(MSG1, MSG0); - MSG = _mm_shuffle_epi32(MSG, 0x0E); - STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); - MSG3 = _mm_sha256msg1_epu32(MSG3, MSG0); - - /* Rounds 20-23 */ - MSG = _mm_add_epi32(MSG1, _mm_set_epi64x( - 0x76F988DA5CB0A9DCULL, 0x4A7484AA2DE92C6FULL)); - STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); - TMP = _mm_alignr_epi8(MSG1, MSG0, 4); - MSG2 = _mm_add_epi32(MSG2, TMP); - MSG2 = _mm_sha256msg2_epu32(MSG2, MSG1); - MSG = _mm_shuffle_epi32(MSG, 0x0E); - STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); - MSG0 = _mm_sha256msg1_epu32(MSG0, MSG1); - - /* Rounds 24-27 */ - MSG = _mm_add_epi32(MSG2, _mm_set_epi64x( - 0xBF597FC7B00327C8ULL, 0xA831C66D983E5152ULL)); - STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); - TMP = _mm_alignr_epi8(MSG2, MSG1, 4); - MSG3 = _mm_add_epi32(MSG3, TMP); - MSG3 = _mm_sha256msg2_epu32(MSG3, MSG2); - MSG = _mm_shuffle_epi32(MSG, 0x0E); - STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); - MSG1 = _mm_sha256msg1_epu32(MSG1, MSG2); - - /* Rounds 28-31 */ - MSG = _mm_add_epi32(MSG3, _mm_set_epi64x( - 0x1429296706CA6351ULL, 0xD5A79147C6E00BF3ULL)); - STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); - TMP = _mm_alignr_epi8(MSG3, MSG2, 4); - MSG0 = _mm_add_epi32(MSG0, TMP); - MSG0 = _mm_sha256msg2_epu32(MSG0, MSG3); - MSG = _mm_shuffle_epi32(MSG, 0x0E); - STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); - MSG2 = _mm_sha256msg1_epu32(MSG2, MSG3); - - /* Rounds 32-35 */ - MSG = _mm_add_epi32(MSG0, _mm_set_epi64x( - 0x53380D134D2C6DFCULL, 0x2E1B213827B70A85ULL)); - STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); - TMP = _mm_alignr_epi8(MSG0, MSG3, 4); - MSG1 = _mm_add_epi32(MSG1, TMP); - MSG1 = _mm_sha256msg2_epu32(MSG1, MSG0); - MSG = _mm_shuffle_epi32(MSG, 0x0E); - STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); - MSG3 = _mm_sha256msg1_epu32(MSG3, MSG0); - - /* Rounds 36-39 */ - MSG = _mm_add_epi32(MSG1, _mm_set_epi64x( - 0x92722C8581C2C92EULL, 0x766A0ABB650A7354ULL)); - STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); - TMP = _mm_alignr_epi8(MSG1, MSG0, 4); - MSG2 = _mm_add_epi32(MSG2, TMP); - MSG2 = _mm_sha256msg2_epu32(MSG2, MSG1); - MSG = _mm_shuffle_epi32(MSG, 0x0E); - STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); - MSG0 = _mm_sha256msg1_epu32(MSG0, MSG1); - - /* Rounds 40-43 */ - MSG = _mm_add_epi32(MSG2, _mm_set_epi64x( - 0xC76C51A3C24B8B70ULL, 0xA81A664BA2BFE8A1ULL)); - STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); - TMP = _mm_alignr_epi8(MSG2, MSG1, 4); - MSG3 = _mm_add_epi32(MSG3, TMP); - MSG3 = _mm_sha256msg2_epu32(MSG3, MSG2); - MSG = _mm_shuffle_epi32(MSG, 0x0E); - STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); - MSG1 = _mm_sha256msg1_epu32(MSG1, MSG2); - - /* Rounds 44-47 */ - MSG = _mm_add_epi32(MSG3, _mm_set_epi64x( - 0x106AA070F40E3585ULL, 0xD6990624D192E819ULL)); - STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); - TMP = _mm_alignr_epi8(MSG3, MSG2, 4); - MSG0 = _mm_add_epi32(MSG0, TMP); - MSG0 = _mm_sha256msg2_epu32(MSG0, MSG3); - MSG = _mm_shuffle_epi32(MSG, 0x0E); - STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); - MSG2 = _mm_sha256msg1_epu32(MSG2, MSG3); - - /* Rounds 48-51 */ - MSG = _mm_add_epi32(MSG0, _mm_set_epi64x( - 0x34B0BCB52748774CULL, 0x1E376C0819A4C116ULL)); - STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); - TMP = _mm_alignr_epi8(MSG0, MSG3, 4); - MSG1 = _mm_add_epi32(MSG1, TMP); - MSG1 = _mm_sha256msg2_epu32(MSG1, MSG0); - MSG = _mm_shuffle_epi32(MSG, 0x0E); - STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); - MSG3 = _mm_sha256msg1_epu32(MSG3, MSG0); - - /* Rounds 52-55 */ - MSG = _mm_add_epi32(MSG1, _mm_set_epi64x( - 0x682E6FF35B9CCA4FULL, 0x4ED8AA4A391C0CB3ULL)); - STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); - TMP = _mm_alignr_epi8(MSG1, MSG0, 4); - MSG2 = _mm_add_epi32(MSG2, TMP); - MSG2 = _mm_sha256msg2_epu32(MSG2, MSG1); - MSG = _mm_shuffle_epi32(MSG, 0x0E); - STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); - - /* Rounds 56-59 */ - MSG = _mm_add_epi32(MSG2, _mm_set_epi64x( - 0x8CC7020884C87814ULL, 0x78A5636F748F82EEULL)); - STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); - TMP = _mm_alignr_epi8(MSG2, MSG1, 4); - MSG3 = _mm_add_epi32(MSG3, TMP); - MSG3 = _mm_sha256msg2_epu32(MSG3, MSG2); - MSG = _mm_shuffle_epi32(MSG, 0x0E); - STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); - - /* Rounds 60-63 */ - MSG = _mm_add_epi32(MSG3, _mm_set_epi64x( - 0xC67178F2BEF9A3F7ULL, 0xA4506CEB90BEFFFAULL)); - STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); - MSG = _mm_shuffle_epi32(MSG, 0x0E); - STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); - - /* Combine state */ - core[0] = _mm_add_epi32(STATE0, core[0]); - core[1] = _mm_add_epi32(STATE1, core[1]); -} - -typedef struct sha256_ni { - /* - * These two vectors store the 8 words of the SHA-256 state, but - * not in the same order they appear in the spec: the first word - * holds A,B,E,F and the second word C,D,G,H. - */ - __m128i core[2]; - sha256_block blk; - void *pointer_to_free; - BinarySink_IMPLEMENTATION; - ssh_hash hash; -} sha256_ni; - -static void sha256_ni_write(BinarySink *bs, const void *vp, size_t len); - -static sha256_ni *sha256_ni_alloc(void) -{ - /* - * The __m128i variables in the context structure need to be - * 16-byte aligned, but not all malloc implementations that this - * code has to work with will guarantee to return a 16-byte - * aligned pointer. So we over-allocate, manually realign the - * pointer ourselves, and store the original one inside the - * context so we know how to free it later. - */ - void *allocation = smalloc(sizeof(sha256_ni) + 15); - uintptr_t alloc_address = (uintptr_t)allocation; - uintptr_t aligned_address = (alloc_address + 15) & ~15; - sha256_ni *s = (sha256_ni *)aligned_address; - s->pointer_to_free = allocation; - return s; -} - -static ssh_hash *sha256_ni_new(const ssh_hashalg *alg) -{ - if (!sha256_hw_available_cached()) - return NULL; - - sha256_ni *s = sha256_ni_alloc(); - - s->hash.vt = alg; - BinarySink_INIT(s, sha256_ni_write); - BinarySink_DELEGATE_INIT(&s->hash, s); - - return &s->hash; -} - -FUNC_ISA static void sha256_ni_reset(ssh_hash *hash) -{ - sha256_ni *s = container_of(hash, sha256_ni, hash); - - /* Initialise the core vectors in their storage order */ - s->core[0] = _mm_set_epi64x( - 0x6a09e667bb67ae85ULL, 0x510e527f9b05688cULL); - s->core[1] = _mm_set_epi64x( - 0x3c6ef372a54ff53aULL, 0x1f83d9ab5be0cd19ULL); - - sha256_block_setup(&s->blk); -} - -static void sha256_ni_copyfrom(ssh_hash *hcopy, ssh_hash *horig) -{ - sha256_ni *copy = container_of(hcopy, sha256_ni, hash); - sha256_ni *orig = container_of(horig, sha256_ni, hash); - - void *ptf_save = copy->pointer_to_free; - *copy = *orig; /* structure copy */ - copy->pointer_to_free = ptf_save; - - BinarySink_COPIED(copy); - BinarySink_DELEGATE_INIT(©->hash, copy); -} - -static void sha256_ni_free(ssh_hash *hash) -{ - sha256_ni *s = container_of(hash, sha256_ni, hash); - - void *ptf = s->pointer_to_free; - smemclr(s, sizeof(*s)); - sfree(ptf); -} - -static void sha256_ni_write(BinarySink *bs, const void *vp, size_t len) -{ - sha256_ni *s = BinarySink_DOWNCAST(bs, sha256_ni); - - while (len > 0) - if (sha256_block_write(&s->blk, &vp, &len)) - sha256_ni_block(s->core, s->blk.block); -} - -FUNC_ISA static void sha256_ni_digest(ssh_hash *hash, uint8_t *digest) -{ - sha256_ni *s = container_of(hash, sha256_ni, hash); - - sha256_block_pad(&s->blk, BinarySink_UPCAST(s)); - - /* Rearrange the words into the output order */ - __m128i feba = _mm_shuffle_epi32(s->core[0], 0x1B); - __m128i dchg = _mm_shuffle_epi32(s->core[1], 0xB1); - __m128i dcba = _mm_blend_epi16(feba, dchg, 0xF0); - __m128i hgfe = _mm_alignr_epi8(dchg, feba, 8); - - /* Byte-swap them into the output endianness */ - const __m128i mask = _mm_setr_epi8(3,2,1,0,7,6,5,4,11,10,9,8,15,14,13,12); - dcba = _mm_shuffle_epi8(dcba, mask); - hgfe = _mm_shuffle_epi8(hgfe, mask); - - /* And store them */ - __m128i *output = (__m128i *)digest; - _mm_storeu_si128(output, dcba); - _mm_storeu_si128(output+1, hgfe); -} - -const ssh_hashalg ssh_sha256_hw = { - .new = sha256_ni_new, - .reset = sha256_ni_reset, - .copyfrom = sha256_ni_copyfrom, - .digest = sha256_ni_digest, - .free = sha256_ni_free, - .hlen = 32, - .blocklen = 64, - HASHALG_NAMES_ANNOTATED("SHA-256", "SHA-NI accelerated"), -}; - -/* ---------------------------------------------------------------------- - * Hardware-accelerated implementation of SHA-256 using Arm NEON. - */ - -#elif HW_SHA256 == HW_SHA256_NEON - -/* - * Manually set the target architecture, if we decided above that we - * need to. - */ -#ifdef USE_CLANG_ATTR_TARGET_AARCH64 -/* - * A spot of cheating: redefine some ACLE feature macros before - * including arm_neon.h. Otherwise we won't get the SHA intrinsics - * defined by that header, because it will be looking at the settings - * for the whole translation unit rather than the ones we're going to - * put on some particular functions using __attribute__((target)). - */ -#define __ARM_NEON 1 -#define __ARM_FEATURE_CRYPTO 1 -#define FUNC_ISA __attribute__ ((target("neon,crypto"))) -#endif /* USE_CLANG_ATTR_TARGET_AARCH64 */ - -#ifndef FUNC_ISA -#define FUNC_ISA -#endif - -#ifdef USE_ARM64_NEON_H -#include -#else -#include -#endif - -static bool sha256_hw_available(void) -{ - /* - * For Arm, we delegate to a per-platform detection function (see - * explanation in sshaes.c). - */ - return platform_sha256_hw_available(); -} - -typedef struct sha256_neon_core sha256_neon_core; -struct sha256_neon_core { - uint32x4_t abcd, efgh; -}; - -FUNC_ISA -static inline uint32x4_t sha256_neon_load_input(const uint8_t *p) -{ - return vreinterpretq_u32_u8(vrev32q_u8(vld1q_u8(p))); -} - -FUNC_ISA -static inline uint32x4_t sha256_neon_schedule_update( - uint32x4_t m4, uint32x4_t m3, uint32x4_t m2, uint32x4_t m1) -{ - return vsha256su1q_u32(vsha256su0q_u32(m4, m3), m2, m1); -} - -FUNC_ISA -static inline sha256_neon_core sha256_neon_round4( - sha256_neon_core old, uint32x4_t sched, unsigned round) -{ - sha256_neon_core new; - - uint32x4_t round_input = vaddq_u32( - sched, vld1q_u32(sha256_round_constants + round)); - new.abcd = vsha256hq_u32 (old.abcd, old.efgh, round_input); - new.efgh = vsha256h2q_u32(old.efgh, old.abcd, round_input); - return new; -} - -FUNC_ISA -static inline void sha256_neon_block(sha256_neon_core *core, const uint8_t *p) -{ - uint32x4_t s0, s1, s2, s3; - sha256_neon_core cr = *core; - - s0 = sha256_neon_load_input(p); - cr = sha256_neon_round4(cr, s0, 0); - s1 = sha256_neon_load_input(p+16); - cr = sha256_neon_round4(cr, s1, 4); - s2 = sha256_neon_load_input(p+32); - cr = sha256_neon_round4(cr, s2, 8); - s3 = sha256_neon_load_input(p+48); - cr = sha256_neon_round4(cr, s3, 12); - s0 = sha256_neon_schedule_update(s0, s1, s2, s3); - cr = sha256_neon_round4(cr, s0, 16); - s1 = sha256_neon_schedule_update(s1, s2, s3, s0); - cr = sha256_neon_round4(cr, s1, 20); - s2 = sha256_neon_schedule_update(s2, s3, s0, s1); - cr = sha256_neon_round4(cr, s2, 24); - s3 = sha256_neon_schedule_update(s3, s0, s1, s2); - cr = sha256_neon_round4(cr, s3, 28); - s0 = sha256_neon_schedule_update(s0, s1, s2, s3); - cr = sha256_neon_round4(cr, s0, 32); - s1 = sha256_neon_schedule_update(s1, s2, s3, s0); - cr = sha256_neon_round4(cr, s1, 36); - s2 = sha256_neon_schedule_update(s2, s3, s0, s1); - cr = sha256_neon_round4(cr, s2, 40); - s3 = sha256_neon_schedule_update(s3, s0, s1, s2); - cr = sha256_neon_round4(cr, s3, 44); - s0 = sha256_neon_schedule_update(s0, s1, s2, s3); - cr = sha256_neon_round4(cr, s0, 48); - s1 = sha256_neon_schedule_update(s1, s2, s3, s0); - cr = sha256_neon_round4(cr, s1, 52); - s2 = sha256_neon_schedule_update(s2, s3, s0, s1); - cr = sha256_neon_round4(cr, s2, 56); - s3 = sha256_neon_schedule_update(s3, s0, s1, s2); - cr = sha256_neon_round4(cr, s3, 60); - - core->abcd = vaddq_u32(core->abcd, cr.abcd); - core->efgh = vaddq_u32(core->efgh, cr.efgh); -} - -typedef struct sha256_neon { - sha256_neon_core core; - sha256_block blk; - BinarySink_IMPLEMENTATION; - ssh_hash hash; -} sha256_neon; - -static void sha256_neon_write(BinarySink *bs, const void *vp, size_t len); - -static ssh_hash *sha256_neon_new(const ssh_hashalg *alg) -{ - if (!sha256_hw_available_cached()) - return NULL; - - sha256_neon *s = snew(sha256_neon); - - s->hash.vt = alg; - BinarySink_INIT(s, sha256_neon_write); - BinarySink_DELEGATE_INIT(&s->hash, s); - return &s->hash; -} - -static void sha256_neon_reset(ssh_hash *hash) -{ - sha256_neon *s = container_of(hash, sha256_neon, hash); - - s->core.abcd = vld1q_u32(sha256_initial_state); - s->core.efgh = vld1q_u32(sha256_initial_state + 4); - - sha256_block_setup(&s->blk); -} - -static void sha256_neon_copyfrom(ssh_hash *hcopy, ssh_hash *horig) -{ - sha256_neon *copy = container_of(hcopy, sha256_neon, hash); - sha256_neon *orig = container_of(horig, sha256_neon, hash); - - *copy = *orig; /* structure copy */ - - BinarySink_COPIED(copy); - BinarySink_DELEGATE_INIT(©->hash, copy); -} - -static void sha256_neon_free(ssh_hash *hash) -{ - sha256_neon *s = container_of(hash, sha256_neon, hash); - smemclr(s, sizeof(*s)); - sfree(s); -} - -static void sha256_neon_write(BinarySink *bs, const void *vp, size_t len) -{ - sha256_neon *s = BinarySink_DOWNCAST(bs, sha256_neon); - - while (len > 0) - if (sha256_block_write(&s->blk, &vp, &len)) - sha256_neon_block(&s->core, s->blk.block); -} - -static void sha256_neon_digest(ssh_hash *hash, uint8_t *digest) -{ - sha256_neon *s = container_of(hash, sha256_neon, hash); - - sha256_block_pad(&s->blk, BinarySink_UPCAST(s)); - vst1q_u8(digest, vrev32q_u8(vreinterpretq_u8_u32(s->core.abcd))); - vst1q_u8(digest + 16, vrev32q_u8(vreinterpretq_u8_u32(s->core.efgh))); -} - -const ssh_hashalg ssh_sha256_hw = { - .new = sha256_neon_new, - .reset = sha256_neon_reset, - .copyfrom = sha256_neon_copyfrom, - .digest = sha256_neon_digest, - .free = sha256_neon_free, - .hlen = 32, - .blocklen = 64, - HASHALG_NAMES_ANNOTATED("SHA-256", "NEON accelerated"), -}; - -/* ---------------------------------------------------------------------- - * Stub functions if we have no hardware-accelerated SHA-256. In this - * case, sha256_hw_new returns NULL (though it should also never be - * selected by sha256_select, so the only thing that should even be - * _able_ to call it is testcrypt). As a result, the remaining vtable - * functions should never be called at all. - */ - -#elif HW_SHA256 == HW_SHA256_NONE - -static bool sha256_hw_available(void) -{ - return false; -} - -static ssh_hash *sha256_stub_new(const ssh_hashalg *alg) -{ - return NULL; -} - -#define STUB_BODY { unreachable("Should never be called"); } - -static void sha256_stub_reset(ssh_hash *hash) STUB_BODY -static void sha256_stub_copyfrom(ssh_hash *hash, ssh_hash *orig) STUB_BODY -static void sha256_stub_free(ssh_hash *hash) STUB_BODY -static void sha256_stub_digest(ssh_hash *hash, uint8_t *digest) STUB_BODY - -const ssh_hashalg ssh_sha256_hw = { - .new = sha256_stub_new, - .reset = sha256_stub_reset, - .copyfrom = sha256_stub_copyfrom, - .digest = sha256_stub_digest, - .free = sha256_stub_free, - .hlen = 32, - .blocklen = 64, - HASHALG_NAMES_ANNOTATED("SHA-256", "!NONEXISTENT ACCELERATED VERSION!"), -}; - -#endif /* HW_SHA256 */ diff --git a/sshsh512.c b/sshsh512.c deleted file mode 100644 index cba7f38d..00000000 --- a/sshsh512.c +++ /dev/null @@ -1,836 +0,0 @@ -/* - * SHA-512 algorithm as described at - * - * http://csrc.nist.gov/cryptval/shs.html - * - * Modifications made for SHA-384 also - */ - -#include -#include "ssh.h" - -/* - * Start by deciding whether we can support hardware SHA at all. - */ -#define HW_SHA512_NONE 0 -#define HW_SHA512_NEON 1 - -#ifdef _FORCE_SHA512_NEON -# define HW_SHA512 HW_SHA512_NEON -#elif defined __BYTE_ORDER__ && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ - /* Arm can potentially support both endiannesses, but this code - * hasn't been tested on anything but little. If anyone wants to - * run big-endian, they'll need to fix it first. */ -#elif defined __ARM_FEATURE_SHA512 - /* If the Arm SHA-512 extension is available already, we can - * support NEON SHA without having to enable anything by hand */ -# define HW_SHA512 HW_SHA512_NEON -#elif defined(__clang__) -# if __has_attribute(target) && __has_include() && \ - (defined(__aarch64__)) - /* clang can enable the crypto extension in AArch64 using - * __attribute__((target)) */ -# define HW_SHA512 HW_SHA512_NEON -# define USE_CLANG_ATTR_TARGET_AARCH64 -# endif -#endif - -#if defined _FORCE_SOFTWARE_SHA || !defined HW_SHA512 -# undef HW_SHA512 -# define HW_SHA512 HW_SHA512_NONE -#endif - -/* - * The actual query function that asks if hardware acceleration is - * available. - */ -static bool sha512_hw_available(void); - -/* - * The top-level selection function, caching the results of - * sha512_hw_available() so it only has to run once. - */ -static bool sha512_hw_available_cached(void) -{ - static bool initialised = false; - static bool hw_available; - if (!initialised) { - hw_available = sha512_hw_available(); - initialised = true; - } - return hw_available; -} - -struct sha512_select_options { - const ssh_hashalg *hw, *sw; -}; - -static ssh_hash *sha512_select(const ssh_hashalg *alg) -{ - const struct sha512_select_options *options = - (const struct sha512_select_options *)alg->extra; - - const ssh_hashalg *real_alg = - sha512_hw_available_cached() ? options->hw : options->sw; - - return ssh_hash_new(real_alg); -} - -const struct sha512_select_options ssh_sha512_select_options = { - &ssh_sha512_hw, &ssh_sha512_sw, -}; -const struct sha512_select_options ssh_sha384_select_options = { - &ssh_sha384_hw, &ssh_sha384_sw, -}; - -const ssh_hashalg ssh_sha512 = { - .new = sha512_select, - .hlen = 64, - .blocklen = 128, - HASHALG_NAMES_ANNOTATED("SHA-512", "dummy selector vtable"), - .extra = &ssh_sha512_select_options, -}; - -const ssh_hashalg ssh_sha384 = { - .new = sha512_select, - .hlen = 48, - .blocklen = 128, - HASHALG_NAMES_ANNOTATED("SHA-384", "dummy selector vtable"), - .extra = &ssh_sha384_select_options, -}; - -/* ---------------------------------------------------------------------- - * Definitions likely to be helpful to multiple implementations. - */ - -static const uint64_t sha512_initial_state[] = { - 0x6a09e667f3bcc908ULL, - 0xbb67ae8584caa73bULL, - 0x3c6ef372fe94f82bULL, - 0xa54ff53a5f1d36f1ULL, - 0x510e527fade682d1ULL, - 0x9b05688c2b3e6c1fULL, - 0x1f83d9abfb41bd6bULL, - 0x5be0cd19137e2179ULL, -}; - -static const uint64_t sha384_initial_state[] = { - 0xcbbb9d5dc1059ed8ULL, - 0x629a292a367cd507ULL, - 0x9159015a3070dd17ULL, - 0x152fecd8f70e5939ULL, - 0x67332667ffc00b31ULL, - 0x8eb44a8768581511ULL, - 0xdb0c2e0d64f98fa7ULL, - 0x47b5481dbefa4fa4ULL, -}; - -static const uint64_t sha512_round_constants[] = { - 0x428a2f98d728ae22ULL, 0x7137449123ef65cdULL, - 0xb5c0fbcfec4d3b2fULL, 0xe9b5dba58189dbbcULL, - 0x3956c25bf348b538ULL, 0x59f111f1b605d019ULL, - 0x923f82a4af194f9bULL, 0xab1c5ed5da6d8118ULL, - 0xd807aa98a3030242ULL, 0x12835b0145706fbeULL, - 0x243185be4ee4b28cULL, 0x550c7dc3d5ffb4e2ULL, - 0x72be5d74f27b896fULL, 0x80deb1fe3b1696b1ULL, - 0x9bdc06a725c71235ULL, 0xc19bf174cf692694ULL, - 0xe49b69c19ef14ad2ULL, 0xefbe4786384f25e3ULL, - 0x0fc19dc68b8cd5b5ULL, 0x240ca1cc77ac9c65ULL, - 0x2de92c6f592b0275ULL, 0x4a7484aa6ea6e483ULL, - 0x5cb0a9dcbd41fbd4ULL, 0x76f988da831153b5ULL, - 0x983e5152ee66dfabULL, 0xa831c66d2db43210ULL, - 0xb00327c898fb213fULL, 0xbf597fc7beef0ee4ULL, - 0xc6e00bf33da88fc2ULL, 0xd5a79147930aa725ULL, - 0x06ca6351e003826fULL, 0x142929670a0e6e70ULL, - 0x27b70a8546d22ffcULL, 0x2e1b21385c26c926ULL, - 0x4d2c6dfc5ac42aedULL, 0x53380d139d95b3dfULL, - 0x650a73548baf63deULL, 0x766a0abb3c77b2a8ULL, - 0x81c2c92e47edaee6ULL, 0x92722c851482353bULL, - 0xa2bfe8a14cf10364ULL, 0xa81a664bbc423001ULL, - 0xc24b8b70d0f89791ULL, 0xc76c51a30654be30ULL, - 0xd192e819d6ef5218ULL, 0xd69906245565a910ULL, - 0xf40e35855771202aULL, 0x106aa07032bbd1b8ULL, - 0x19a4c116b8d2d0c8ULL, 0x1e376c085141ab53ULL, - 0x2748774cdf8eeb99ULL, 0x34b0bcb5e19b48a8ULL, - 0x391c0cb3c5c95a63ULL, 0x4ed8aa4ae3418acbULL, - 0x5b9cca4f7763e373ULL, 0x682e6ff3d6b2b8a3ULL, - 0x748f82ee5defb2fcULL, 0x78a5636f43172f60ULL, - 0x84c87814a1f0ab72ULL, 0x8cc702081a6439ecULL, - 0x90befffa23631e28ULL, 0xa4506cebde82bde9ULL, - 0xbef9a3f7b2c67915ULL, 0xc67178f2e372532bULL, - 0xca273eceea26619cULL, 0xd186b8c721c0c207ULL, - 0xeada7dd6cde0eb1eULL, 0xf57d4f7fee6ed178ULL, - 0x06f067aa72176fbaULL, 0x0a637dc5a2c898a6ULL, - 0x113f9804bef90daeULL, 0x1b710b35131c471bULL, - 0x28db77f523047d84ULL, 0x32caab7b40c72493ULL, - 0x3c9ebe0a15c9bebcULL, 0x431d67c49c100d4cULL, - 0x4cc5d4becb3e42b6ULL, 0x597f299cfc657e2aULL, - 0x5fcb6fab3ad6faecULL, 0x6c44198c4a475817ULL, -}; - -#define SHA512_ROUNDS 80 - -typedef struct sha512_block sha512_block; -struct sha512_block { - uint8_t block[128]; - size_t used; - uint64_t lenhi, lenlo; -}; - -static inline void sha512_block_setup(sha512_block *blk) -{ - blk->used = 0; - blk->lenhi = blk->lenlo = 0; -} - -static inline bool sha512_block_write( - sha512_block *blk, const void **vdata, size_t *len) -{ - size_t blkleft = sizeof(blk->block) - blk->used; - size_t chunk = *len < blkleft ? *len : blkleft; - - const uint8_t *p = *vdata; - memcpy(blk->block + blk->used, p, chunk); - *vdata = p + chunk; - *len -= chunk; - blk->used += chunk; - - size_t chunkbits = chunk << 3; - - blk->lenlo += chunkbits; - blk->lenhi += (blk->lenlo < chunkbits); - - if (blk->used == sizeof(blk->block)) { - blk->used = 0; - return true; - } - - return false; -} - -static inline void sha512_block_pad(sha512_block *blk, BinarySink *bs) -{ - uint64_t final_lenhi = blk->lenhi; - uint64_t final_lenlo = blk->lenlo; - size_t pad = 127 & (111 - blk->used); - - put_byte(bs, 0x80); - put_padding(bs, pad, 0); - put_uint64(bs, final_lenhi); - put_uint64(bs, final_lenlo); - - assert(blk->used == 0 && "Should have exactly hit a block boundary"); -} - -/* ---------------------------------------------------------------------- - * Software implementation of SHA-512. - */ - -static inline uint64_t ror(uint64_t x, unsigned y) -{ - return (x << (63 & -y)) | (x >> (63 & y)); -} - -static inline uint64_t Ch(uint64_t ctrl, uint64_t if1, uint64_t if0) -{ - return if0 ^ (ctrl & (if1 ^ if0)); -} - -static inline uint64_t Maj(uint64_t x, uint64_t y, uint64_t z) -{ - return (x & y) | (z & (x | y)); -} - -static inline uint64_t Sigma_0(uint64_t x) -{ - return ror(x,28) ^ ror(x,34) ^ ror(x,39); -} - -static inline uint64_t Sigma_1(uint64_t x) -{ - return ror(x,14) ^ ror(x,18) ^ ror(x,41); -} - -static inline uint64_t sigma_0(uint64_t x) -{ - return ror(x,1) ^ ror(x,8) ^ (x >> 7); -} - -static inline uint64_t sigma_1(uint64_t x) -{ - return ror(x,19) ^ ror(x,61) ^ (x >> 6); -} - -static inline void sha512_sw_round( - unsigned round_index, const uint64_t *schedule, - uint64_t *a, uint64_t *b, uint64_t *c, uint64_t *d, - uint64_t *e, uint64_t *f, uint64_t *g, uint64_t *h) -{ - uint64_t t1 = *h + Sigma_1(*e) + Ch(*e,*f,*g) + - sha512_round_constants[round_index] + schedule[round_index]; - - uint64_t t2 = Sigma_0(*a) + Maj(*a,*b,*c); - - *d += t1; - *h = t1 + t2; -} - -static void sha512_sw_block(uint64_t *core, const uint8_t *block) -{ - uint64_t w[SHA512_ROUNDS]; - uint64_t a,b,c,d,e,f,g,h; - - int t; - - for (t = 0; t < 16; t++) - w[t] = GET_64BIT_MSB_FIRST(block + 8*t); - - for (t = 16; t < SHA512_ROUNDS; t++) - w[t] = w[t-16] + w[t-7] + sigma_0(w[t-15]) + sigma_1(w[t-2]); - - a = core[0]; b = core[1]; c = core[2]; d = core[3]; - e = core[4]; f = core[5]; g = core[6]; h = core[7]; - - for (t = 0; t < SHA512_ROUNDS; t+=8) { - sha512_sw_round(t+0, w, &a,&b,&c,&d,&e,&f,&g,&h); - sha512_sw_round(t+1, w, &h,&a,&b,&c,&d,&e,&f,&g); - sha512_sw_round(t+2, w, &g,&h,&a,&b,&c,&d,&e,&f); - sha512_sw_round(t+3, w, &f,&g,&h,&a,&b,&c,&d,&e); - sha512_sw_round(t+4, w, &e,&f,&g,&h,&a,&b,&c,&d); - sha512_sw_round(t+5, w, &d,&e,&f,&g,&h,&a,&b,&c); - sha512_sw_round(t+6, w, &c,&d,&e,&f,&g,&h,&a,&b); - sha512_sw_round(t+7, w, &b,&c,&d,&e,&f,&g,&h,&a); - } - - core[0] += a; core[1] += b; core[2] += c; core[3] += d; - core[4] += e; core[5] += f; core[6] += g; core[7] += h; - - smemclr(w, sizeof(w)); -} - -typedef struct sha512_sw { - uint64_t core[8]; - sha512_block blk; - BinarySink_IMPLEMENTATION; - ssh_hash hash; -} sha512_sw; - -static void sha512_sw_write(BinarySink *bs, const void *vp, size_t len); - -static ssh_hash *sha512_sw_new(const ssh_hashalg *alg) -{ - sha512_sw *s = snew(sha512_sw); - - s->hash.vt = alg; - BinarySink_INIT(s, sha512_sw_write); - BinarySink_DELEGATE_INIT(&s->hash, s); - return &s->hash; -} - -static void sha512_sw_reset(ssh_hash *hash) -{ - sha512_sw *s = container_of(hash, sha512_sw, hash); - - /* The 'extra' field in the ssh_hashalg indicates which - * initialisation vector we're using */ - memcpy(s->core, hash->vt->extra, sizeof(s->core)); - sha512_block_setup(&s->blk); -} - -static void sha512_sw_copyfrom(ssh_hash *hcopy, ssh_hash *horig) -{ - sha512_sw *copy = container_of(hcopy, sha512_sw, hash); - sha512_sw *orig = container_of(horig, sha512_sw, hash); - - memcpy(copy, orig, sizeof(*copy)); - BinarySink_COPIED(copy); - BinarySink_DELEGATE_INIT(©->hash, copy); -} - -static void sha512_sw_free(ssh_hash *hash) -{ - sha512_sw *s = container_of(hash, sha512_sw, hash); - - smemclr(s, sizeof(*s)); - sfree(s); -} - -static void sha512_sw_write(BinarySink *bs, const void *vp, size_t len) -{ - sha512_sw *s = BinarySink_DOWNCAST(bs, sha512_sw); - - while (len > 0) - if (sha512_block_write(&s->blk, &vp, &len)) - sha512_sw_block(s->core, s->blk.block); -} - -static void sha512_sw_digest(ssh_hash *hash, uint8_t *digest) -{ - sha512_sw *s = container_of(hash, sha512_sw, hash); - - sha512_block_pad(&s->blk, BinarySink_UPCAST(s)); - for (size_t i = 0; i < hash->vt->hlen / 8; i++) - PUT_64BIT_MSB_FIRST(digest + 8*i, s->core[i]); -} - -const ssh_hashalg ssh_sha512_sw = { - .new = sha512_sw_new, - .reset = sha512_sw_reset, - .copyfrom = sha512_sw_copyfrom, - .digest = sha512_sw_digest, - .free = sha512_sw_free, - .hlen = 64, - .blocklen = 128, - HASHALG_NAMES_ANNOTATED("SHA-512", "unaccelerated"), - .extra = sha512_initial_state, -}; - -const ssh_hashalg ssh_sha384_sw = { - .new = sha512_sw_new, - .reset = sha512_sw_reset, - .copyfrom = sha512_sw_copyfrom, - .digest = sha512_sw_digest, - .free = sha512_sw_free, - .hlen = 48, - .blocklen = 128, - HASHALG_NAMES_ANNOTATED("SHA-384", "unaccelerated"), - .extra = sha384_initial_state, -}; - -/* ---------------------------------------------------------------------- - * Hardware-accelerated implementation of SHA-512 using Arm NEON. - */ - -#if HW_SHA512 == HW_SHA512_NEON - -/* - * Manually set the target architecture, if we decided above that we - * need to. - */ -#ifdef USE_CLANG_ATTR_TARGET_AARCH64 -/* - * A spot of cheating: redefine some ACLE feature macros before - * including arm_neon.h. Otherwise we won't get the SHA intrinsics - * defined by that header, because it will be looking at the settings - * for the whole translation unit rather than the ones we're going to - * put on some particular functions using __attribute__((target)). - */ -#define __ARM_NEON 1 -#define __ARM_FEATURE_CRYPTO 1 -#define FUNC_ISA __attribute__ ((target("neon,sha3"))) -#endif /* USE_CLANG_ATTR_TARGET_AARCH64 */ - -#ifndef FUNC_ISA -#define FUNC_ISA -#endif - -#ifdef USE_ARM64_NEON_H -#include -#else -#include -#endif - -static bool sha512_hw_available(void) -{ - /* - * For Arm, we delegate to a per-platform detection function (see - * explanation in sshaes.c). - */ - return platform_sha512_hw_available(); -} - -#if defined __clang__ -/* - * As of 2020-12-24, I've found that clang doesn't provide the SHA-512 - * NEON intrinsics. So I define my own set using inline assembler, and - * use #define to effectively rename them over the top of the standard - * names. - * - * The aim of that #define technique is that it should avoid a build - * failure if these intrinsics _are_ defined in . - * Obviously it would be better in that situation to switch back to - * using the real intrinsics, but until I see a version of clang that - * supports them, I won't know what version number to test in the - * ifdef. - */ -static inline FUNC_ISA -uint64x2_t vsha512su0q_u64_asm(uint64x2_t x, uint64x2_t y) { - __asm__("sha512su0 %0.2D,%1.2D" : "+w" (x) : "w" (y)); - return x; -} -static inline FUNC_ISA -uint64x2_t vsha512su1q_u64_asm(uint64x2_t x, uint64x2_t y, uint64x2_t z) { - __asm__("sha512su1 %0.2D,%1.2D,%2.2D" : "+w" (x) : "w" (y), "w" (z)); - return x; -} -static inline FUNC_ISA -uint64x2_t vsha512hq_u64_asm(uint64x2_t x, uint64x2_t y, uint64x2_t z) { - __asm__("sha512h %0,%1,%2.2D" : "+w" (x) : "w" (y), "w" (z)); - return x; -} -static inline FUNC_ISA -uint64x2_t vsha512h2q_u64_asm(uint64x2_t x, uint64x2_t y, uint64x2_t z) { - __asm__("sha512h2 %0,%1,%2.2D" : "+w" (x) : "w" (y), "w" (z)); - return x; -} -#undef vsha512su0q_u64 -#define vsha512su0q_u64 vsha512su0q_u64_asm -#undef vsha512su1q_u64 -#define vsha512su1q_u64 vsha512su1q_u64_asm -#undef vsha512hq_u64 -#define vsha512hq_u64 vsha512hq_u64_asm -#undef vsha512h2q_u64 -#define vsha512h2q_u64 vsha512h2q_u64_asm -#endif /* defined __clang__ */ - -typedef struct sha512_neon_core sha512_neon_core; -struct sha512_neon_core { - uint64x2_t ab, cd, ef, gh; -}; - -FUNC_ISA -static inline uint64x2_t sha512_neon_load_input(const uint8_t *p) -{ - return vreinterpretq_u64_u8(vrev64q_u8(vld1q_u8(p))); -} - -FUNC_ISA -static inline uint64x2_t sha512_neon_schedule_update( - uint64x2_t m8, uint64x2_t m7, uint64x2_t m4, uint64x2_t m3, uint64x2_t m1) -{ - /* - * vsha512su0q_u64() takes words from a long way back in the - * schedule and performs the sigma_0 half of the computation of - * the next two 64-bit message-schedule words. - * - * vsha512su1q_u64() combines the result of that with the sigma_1 - * steps, to output the finished version of those two words. The - * total amount of input data it requires fits nicely into three - * 128-bit vector registers, but one of those registers is - * misaligned compared to the 128-bit chunks that the message - * schedule is stored in. So we use vextq_u64 to make one of its - * input words out of the second half of m4 and the first half of - * m3. - */ - return vsha512su1q_u64(vsha512su0q_u64(m8, m7), m1, vextq_u64(m4, m3, 1)); -} - -FUNC_ISA -static inline void sha512_neon_round2( - unsigned round_index, uint64x2_t schedule_words, - uint64x2_t *ab, uint64x2_t *cd, uint64x2_t *ef, uint64x2_t *gh) -{ - /* - * vsha512hq_u64 performs the Sigma_1 and Ch half of the - * computation of two rounds of SHA-512 (including feeding back - * one of the outputs from the first of those half-rounds into the - * second one). - * - * vsha512h2q_u64 combines the result of that with the Sigma_0 and - * Maj steps, and outputs one 128-bit vector that replaces the gh - * piece of the input hash state, and a second that updates cd by - * addition. - * - * Similarly to vsha512su1q_u64 above, some of the input registers - * expected by these instructions are misaligned by 64 bits - * relative to the chunks we've divided the hash state into, so we - * have to start by making 'de' and 'fg' words out of our input - * cd,ef,gh, using vextq_u64. - * - * Also, one of the inputs to vsha512hq_u64 is expected to contain - * the results of summing gh + two round constants + two words of - * message schedule, but the two words of the message schedule - * have to be the opposite way round in the vector register from - * the way that vsha512su1q_u64 output them. Hence, there's - * another vextq_u64 in here that swaps the two halves of the - * initial_sum vector register. - * - * (This also means that I don't have to prepare a specially - * reordered version of the sha512_round_constants[] array: as - * long as I'm unavoidably doing a swap at run time _anyway_, I - * can load from the normally ordered version of that array, and - * just take care to fold in that data _before_ the swap rather - * than after.) - */ - - /* Load two round constants, with the first one in the low half */ - uint64x2_t round_constants = vld1q_u64( - sha512_round_constants + round_index); - - /* Add schedule words to round constants */ - uint64x2_t initial_sum = vaddq_u64(schedule_words, round_constants); - - /* Swap that sum around so the word used in the first of the two - * rounds is in the _high_ half of the vector, matching where h - * lives in the gh vector */ - uint64x2_t swapped_initial_sum = vextq_u64(initial_sum, initial_sum, 1); - - /* Add gh to that, now that they're matching ways round */ - uint64x2_t sum = vaddq_u64(swapped_initial_sum, *gh); - - /* Make the misaligned de and fg words */ - uint64x2_t de = vextq_u64(*cd, *ef, 1); - uint64x2_t fg = vextq_u64(*ef, *gh, 1); - - /* Now we're ready to put all the pieces together. The output from - * vsha512h2q_u64 can be used directly as the new gh, and the - * output from vsha512hq_u64 is simultaneously the intermediate - * value passed to h2 and the thing you have to add on to cd. */ - uint64x2_t intermed = vsha512hq_u64(sum, fg, de); - *gh = vsha512h2q_u64(intermed, *cd, *ab); - *cd = vaddq_u64(*cd, intermed); -} - -FUNC_ISA -static inline void sha512_neon_block(sha512_neon_core *core, const uint8_t *p) -{ - uint64x2_t s0, s1, s2, s3, s4, s5, s6, s7; - - uint64x2_t ab = core->ab, cd = core->cd, ef = core->ef, gh = core->gh; - - s0 = sha512_neon_load_input(p + 16*0); - sha512_neon_round2(0, s0, &ab, &cd, &ef, &gh); - s1 = sha512_neon_load_input(p + 16*1); - sha512_neon_round2(2, s1, &gh, &ab, &cd, &ef); - s2 = sha512_neon_load_input(p + 16*2); - sha512_neon_round2(4, s2, &ef, &gh, &ab, &cd); - s3 = sha512_neon_load_input(p + 16*3); - sha512_neon_round2(6, s3, &cd, &ef, &gh, &ab); - s4 = sha512_neon_load_input(p + 16*4); - sha512_neon_round2(8, s4, &ab, &cd, &ef, &gh); - s5 = sha512_neon_load_input(p + 16*5); - sha512_neon_round2(10, s5, &gh, &ab, &cd, &ef); - s6 = sha512_neon_load_input(p + 16*6); - sha512_neon_round2(12, s6, &ef, &gh, &ab, &cd); - s7 = sha512_neon_load_input(p + 16*7); - sha512_neon_round2(14, s7, &cd, &ef, &gh, &ab); - s0 = sha512_neon_schedule_update(s0, s1, s4, s5, s7); - sha512_neon_round2(16, s0, &ab, &cd, &ef, &gh); - s1 = sha512_neon_schedule_update(s1, s2, s5, s6, s0); - sha512_neon_round2(18, s1, &gh, &ab, &cd, &ef); - s2 = sha512_neon_schedule_update(s2, s3, s6, s7, s1); - sha512_neon_round2(20, s2, &ef, &gh, &ab, &cd); - s3 = sha512_neon_schedule_update(s3, s4, s7, s0, s2); - sha512_neon_round2(22, s3, &cd, &ef, &gh, &ab); - s4 = sha512_neon_schedule_update(s4, s5, s0, s1, s3); - sha512_neon_round2(24, s4, &ab, &cd, &ef, &gh); - s5 = sha512_neon_schedule_update(s5, s6, s1, s2, s4); - sha512_neon_round2(26, s5, &gh, &ab, &cd, &ef); - s6 = sha512_neon_schedule_update(s6, s7, s2, s3, s5); - sha512_neon_round2(28, s6, &ef, &gh, &ab, &cd); - s7 = sha512_neon_schedule_update(s7, s0, s3, s4, s6); - sha512_neon_round2(30, s7, &cd, &ef, &gh, &ab); - s0 = sha512_neon_schedule_update(s0, s1, s4, s5, s7); - sha512_neon_round2(32, s0, &ab, &cd, &ef, &gh); - s1 = sha512_neon_schedule_update(s1, s2, s5, s6, s0); - sha512_neon_round2(34, s1, &gh, &ab, &cd, &ef); - s2 = sha512_neon_schedule_update(s2, s3, s6, s7, s1); - sha512_neon_round2(36, s2, &ef, &gh, &ab, &cd); - s3 = sha512_neon_schedule_update(s3, s4, s7, s0, s2); - sha512_neon_round2(38, s3, &cd, &ef, &gh, &ab); - s4 = sha512_neon_schedule_update(s4, s5, s0, s1, s3); - sha512_neon_round2(40, s4, &ab, &cd, &ef, &gh); - s5 = sha512_neon_schedule_update(s5, s6, s1, s2, s4); - sha512_neon_round2(42, s5, &gh, &ab, &cd, &ef); - s6 = sha512_neon_schedule_update(s6, s7, s2, s3, s5); - sha512_neon_round2(44, s6, &ef, &gh, &ab, &cd); - s7 = sha512_neon_schedule_update(s7, s0, s3, s4, s6); - sha512_neon_round2(46, s7, &cd, &ef, &gh, &ab); - s0 = sha512_neon_schedule_update(s0, s1, s4, s5, s7); - sha512_neon_round2(48, s0, &ab, &cd, &ef, &gh); - s1 = sha512_neon_schedule_update(s1, s2, s5, s6, s0); - sha512_neon_round2(50, s1, &gh, &ab, &cd, &ef); - s2 = sha512_neon_schedule_update(s2, s3, s6, s7, s1); - sha512_neon_round2(52, s2, &ef, &gh, &ab, &cd); - s3 = sha512_neon_schedule_update(s3, s4, s7, s0, s2); - sha512_neon_round2(54, s3, &cd, &ef, &gh, &ab); - s4 = sha512_neon_schedule_update(s4, s5, s0, s1, s3); - sha512_neon_round2(56, s4, &ab, &cd, &ef, &gh); - s5 = sha512_neon_schedule_update(s5, s6, s1, s2, s4); - sha512_neon_round2(58, s5, &gh, &ab, &cd, &ef); - s6 = sha512_neon_schedule_update(s6, s7, s2, s3, s5); - sha512_neon_round2(60, s6, &ef, &gh, &ab, &cd); - s7 = sha512_neon_schedule_update(s7, s0, s3, s4, s6); - sha512_neon_round2(62, s7, &cd, &ef, &gh, &ab); - s0 = sha512_neon_schedule_update(s0, s1, s4, s5, s7); - sha512_neon_round2(64, s0, &ab, &cd, &ef, &gh); - s1 = sha512_neon_schedule_update(s1, s2, s5, s6, s0); - sha512_neon_round2(66, s1, &gh, &ab, &cd, &ef); - s2 = sha512_neon_schedule_update(s2, s3, s6, s7, s1); - sha512_neon_round2(68, s2, &ef, &gh, &ab, &cd); - s3 = sha512_neon_schedule_update(s3, s4, s7, s0, s2); - sha512_neon_round2(70, s3, &cd, &ef, &gh, &ab); - s4 = sha512_neon_schedule_update(s4, s5, s0, s1, s3); - sha512_neon_round2(72, s4, &ab, &cd, &ef, &gh); - s5 = sha512_neon_schedule_update(s5, s6, s1, s2, s4); - sha512_neon_round2(74, s5, &gh, &ab, &cd, &ef); - s6 = sha512_neon_schedule_update(s6, s7, s2, s3, s5); - sha512_neon_round2(76, s6, &ef, &gh, &ab, &cd); - s7 = sha512_neon_schedule_update(s7, s0, s3, s4, s6); - sha512_neon_round2(78, s7, &cd, &ef, &gh, &ab); - - core->ab = vaddq_u64(core->ab, ab); - core->cd = vaddq_u64(core->cd, cd); - core->ef = vaddq_u64(core->ef, ef); - core->gh = vaddq_u64(core->gh, gh); -} - -typedef struct sha512_neon { - sha512_neon_core core; - sha512_block blk; - BinarySink_IMPLEMENTATION; - ssh_hash hash; -} sha512_neon; - -static void sha512_neon_write(BinarySink *bs, const void *vp, size_t len); - -static ssh_hash *sha512_neon_new(const ssh_hashalg *alg) -{ - if (!sha512_hw_available_cached()) - return NULL; - - sha512_neon *s = snew(sha512_neon); - - s->hash.vt = alg; - BinarySink_INIT(s, sha512_neon_write); - BinarySink_DELEGATE_INIT(&s->hash, s); - return &s->hash; -} - -static void sha512_neon_reset(ssh_hash *hash) -{ - sha512_neon *s = container_of(hash, sha512_neon, hash); - const uint64_t *iv = (const uint64_t *)hash->vt->extra; - - s->core.ab = vld1q_u64(iv); - s->core.cd = vld1q_u64(iv+2); - s->core.ef = vld1q_u64(iv+4); - s->core.gh = vld1q_u64(iv+6); - - sha512_block_setup(&s->blk); -} - -static void sha512_neon_copyfrom(ssh_hash *hcopy, ssh_hash *horig) -{ - sha512_neon *copy = container_of(hcopy, sha512_neon, hash); - sha512_neon *orig = container_of(horig, sha512_neon, hash); - - *copy = *orig; /* structure copy */ - - BinarySink_COPIED(copy); - BinarySink_DELEGATE_INIT(©->hash, copy); -} - -static void sha512_neon_free(ssh_hash *hash) -{ - sha512_neon *s = container_of(hash, sha512_neon, hash); - smemclr(s, sizeof(*s)); - sfree(s); -} - -static void sha512_neon_write(BinarySink *bs, const void *vp, size_t len) -{ - sha512_neon *s = BinarySink_DOWNCAST(bs, sha512_neon); - - while (len > 0) - if (sha512_block_write(&s->blk, &vp, &len)) - sha512_neon_block(&s->core, s->blk.block); -} - -static void sha512_neon_digest(ssh_hash *hash, uint8_t *digest) -{ - sha512_neon *s = container_of(hash, sha512_neon, hash); - - sha512_block_pad(&s->blk, BinarySink_UPCAST(s)); - - vst1q_u8(digest, vrev64q_u8(vreinterpretq_u8_u64(s->core.ab))); - vst1q_u8(digest+16, vrev64q_u8(vreinterpretq_u8_u64(s->core.cd))); - vst1q_u8(digest+32, vrev64q_u8(vreinterpretq_u8_u64(s->core.ef))); - vst1q_u8(digest+48, vrev64q_u8(vreinterpretq_u8_u64(s->core.gh))); -} - -static void sha384_neon_digest(ssh_hash *hash, uint8_t *digest) -{ - sha512_neon *s = container_of(hash, sha512_neon, hash); - - sha512_block_pad(&s->blk, BinarySink_UPCAST(s)); - - vst1q_u8(digest, vrev64q_u8(vreinterpretq_u8_u64(s->core.ab))); - vst1q_u8(digest+16, vrev64q_u8(vreinterpretq_u8_u64(s->core.cd))); - vst1q_u8(digest+32, vrev64q_u8(vreinterpretq_u8_u64(s->core.ef))); -} - -const ssh_hashalg ssh_sha512_hw = { - .new = sha512_neon_new, - .reset = sha512_neon_reset, - .copyfrom = sha512_neon_copyfrom, - .digest = sha512_neon_digest, - .free = sha512_neon_free, - .hlen = 64, - .blocklen = 128, - HASHALG_NAMES_ANNOTATED("SHA-512", "NEON accelerated"), - .extra = sha512_initial_state, -}; - -const ssh_hashalg ssh_sha384_hw = { - .new = sha512_neon_new, - .reset = sha512_neon_reset, - .copyfrom = sha512_neon_copyfrom, - .digest = sha384_neon_digest, - .free = sha512_neon_free, - .hlen = 48, - .blocklen = 128, - HASHALG_NAMES_ANNOTATED("SHA-384", "NEON accelerated"), - .extra = sha384_initial_state, -}; - -/* ---------------------------------------------------------------------- - * Stub functions if we have no hardware-accelerated SHA-512. In this - * case, sha512_hw_new returns NULL (though it should also never be - * selected by sha512_select, so the only thing that should even be - * _able_ to call it is testcrypt). As a result, the remaining vtable - * functions should never be called at all. - */ - -#elif HW_SHA512 == HW_SHA512_NONE - -static bool sha512_hw_available(void) -{ - return false; -} - -static ssh_hash *sha512_stub_new(const ssh_hashalg *alg) -{ - return NULL; -} - -#define STUB_BODY { unreachable("Should never be called"); } - -static void sha512_stub_reset(ssh_hash *hash) STUB_BODY -static void sha512_stub_copyfrom(ssh_hash *hash, ssh_hash *orig) STUB_BODY -static void sha512_stub_free(ssh_hash *hash) STUB_BODY -static void sha512_stub_digest(ssh_hash *hash, uint8_t *digest) STUB_BODY - -const ssh_hashalg ssh_sha512_hw = { - .new = sha512_stub_new, - .reset = sha512_stub_reset, - .copyfrom = sha512_stub_copyfrom, - .digest = sha512_stub_digest, - .free = sha512_stub_free, - .hlen = 64, - .blocklen = 128, - HASHALG_NAMES_ANNOTATED("SHA-512", "!NONEXISTENT ACCELERATED VERSION!"), -}; - -const ssh_hashalg ssh_sha384_hw = { - .new = sha512_stub_new, - .reset = sha512_stub_reset, - .copyfrom = sha512_stub_copyfrom, - .digest = sha512_stub_digest, - .free = sha512_stub_free, - .hlen = 48, - .blocklen = 128, - HASHALG_NAMES_ANNOTATED("SHA-384", "!NONEXISTENT ACCELERATED VERSION!"), -}; - -#endif /* HW_SHA512 */ diff --git a/sshsha.c b/sshsha.c deleted file mode 100644 index 536d474f..00000000 --- a/sshsha.c +++ /dev/null @@ -1,933 +0,0 @@ -/* - * SHA-1 algorithm as described at - * - * http://csrc.nist.gov/cryptval/shs.html - */ - -#include "ssh.h" -#include - -/* - * Start by deciding whether we can support hardware SHA at all. - */ -#define HW_SHA1_NONE 0 -#define HW_SHA1_NI 1 -#define HW_SHA1_NEON 2 - -#ifdef _FORCE_SHA_NI -# define HW_SHA1 HW_SHA1_NI -#elif defined(__clang__) -# if __has_attribute(target) && __has_include() && \ - (defined(__x86_64__) || defined(__i386)) -# define HW_SHA1 HW_SHA1_NI -# endif -#elif defined(__GNUC__) -# if (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 9)) && \ - (defined(__x86_64__) || defined(__i386)) -# define HW_SHA1 HW_SHA1_NI -# endif -#elif defined (_MSC_VER) -# if (defined(_M_X64) || defined(_M_IX86)) && _MSC_FULL_VER >= 150030729 -# define HW_SHA1 HW_SHA1_NI -# endif -#endif - -#ifdef _FORCE_SHA_NEON -# define HW_SHA1 HW_SHA1_NEON -#elif defined __BYTE_ORDER__ && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ - /* Arm can potentially support both endiannesses, but this code - * hasn't been tested on anything but little. If anyone wants to - * run big-endian, they'll need to fix it first. */ -#elif defined __ARM_FEATURE_CRYPTO - /* If the Arm crypto extension is available already, we can - * support NEON SHA without having to enable anything by hand */ -# define HW_SHA1 HW_SHA1_NEON -#elif defined(__clang__) -# if __has_attribute(target) && __has_include() && \ - (defined(__aarch64__)) - /* clang can enable the crypto extension in AArch64 using - * __attribute__((target)) */ -# define HW_SHA1 HW_SHA1_NEON -# define USE_CLANG_ATTR_TARGET_AARCH64 -# endif -#elif defined _MSC_VER - /* Visual Studio supports the crypto extension when targeting - * AArch64, but as of VS2017, the AArch32 header doesn't quite - * manage it (declaring the shae/shad intrinsics without a round - * key operand). */ -# if defined _M_ARM64 -# define HW_SHA1 HW_SHA1_NEON -# if defined _M_ARM64 -# define USE_ARM64_NEON_H /* unusual header name in this case */ -# endif -# endif -#endif - -#if defined _FORCE_SOFTWARE_SHA || !defined HW_SHA1 -# undef HW_SHA1 -# define HW_SHA1 HW_SHA1_NONE -#endif - -/* - * The actual query function that asks if hardware acceleration is - * available. - */ -static bool sha1_hw_available(void); - -/* - * The top-level selection function, caching the results of - * sha1_hw_available() so it only has to run once. - */ -static bool sha1_hw_available_cached(void) -{ - static bool initialised = false; - static bool hw_available; - if (!initialised) { - hw_available = sha1_hw_available(); - initialised = true; - } - return hw_available; -} - -static ssh_hash *sha1_select(const ssh_hashalg *alg) -{ - const ssh_hashalg *real_alg = - sha1_hw_available_cached() ? &ssh_sha1_hw : &ssh_sha1_sw; - - return ssh_hash_new(real_alg); -} - -const ssh_hashalg ssh_sha1 = { - .new = sha1_select, - .hlen = 20, - .blocklen = 64, - HASHALG_NAMES_ANNOTATED("SHA-1", "dummy selector vtable"), -}; - -/* ---------------------------------------------------------------------- - * Definitions likely to be helpful to multiple implementations. - */ - -static const uint32_t sha1_initial_state[] = { - 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0, -}; - -#define SHA1_ROUNDS_PER_STAGE 20 -#define SHA1_STAGE0_CONSTANT 0x5a827999 -#define SHA1_STAGE1_CONSTANT 0x6ed9eba1 -#define SHA1_STAGE2_CONSTANT 0x8f1bbcdc -#define SHA1_STAGE3_CONSTANT 0xca62c1d6 -#define SHA1_ROUNDS (4 * SHA1_ROUNDS_PER_STAGE) - -typedef struct sha1_block sha1_block; -struct sha1_block { - uint8_t block[64]; - size_t used; - uint64_t len; -}; - -static inline void sha1_block_setup(sha1_block *blk) -{ - blk->used = 0; - blk->len = 0; -} - -static inline bool sha1_block_write( - sha1_block *blk, const void **vdata, size_t *len) -{ - size_t blkleft = sizeof(blk->block) - blk->used; - size_t chunk = *len < blkleft ? *len : blkleft; - - const uint8_t *p = *vdata; - memcpy(blk->block + blk->used, p, chunk); - *vdata = p + chunk; - *len -= chunk; - blk->used += chunk; - blk->len += chunk; - - if (blk->used == sizeof(blk->block)) { - blk->used = 0; - return true; - } - - return false; -} - -static inline void sha1_block_pad(sha1_block *blk, BinarySink *bs) -{ - uint64_t final_len = blk->len << 3; - size_t pad = 1 + (63 & (55 - blk->used)); - - put_byte(bs, 0x80); - for (size_t i = 1; i < pad; i++) - put_byte(bs, 0); - put_uint64(bs, final_len); - - assert(blk->used == 0 && "Should have exactly hit a block boundary"); -} - -/* ---------------------------------------------------------------------- - * Software implementation of SHA-1. - */ - -static inline uint32_t rol(uint32_t x, unsigned y) -{ - return (x << (31 & y)) | (x >> (31 & -y)); -} - -static inline uint32_t Ch(uint32_t ctrl, uint32_t if1, uint32_t if0) -{ - return if0 ^ (ctrl & (if1 ^ if0)); -} - -static inline uint32_t Maj(uint32_t x, uint32_t y, uint32_t z) -{ - return (x & y) | (z & (x | y)); -} - -static inline uint32_t Par(uint32_t x, uint32_t y, uint32_t z) -{ - return (x ^ y ^ z); -} - -static inline void sha1_sw_round( - unsigned round_index, const uint32_t *schedule, - uint32_t *a, uint32_t *b, uint32_t *c, uint32_t *d, uint32_t *e, - uint32_t f, uint32_t constant) -{ - *e = rol(*a, 5) + f + *e + schedule[round_index] + constant; - *b = rol(*b, 30); -} - -static void sha1_sw_block(uint32_t *core, const uint8_t *block) -{ - uint32_t w[SHA1_ROUNDS]; - uint32_t a,b,c,d,e; - - for (size_t t = 0; t < 16; t++) - w[t] = GET_32BIT_MSB_FIRST(block + 4*t); - - for (size_t t = 16; t < SHA1_ROUNDS; t++) - w[t] = rol(w[t - 3] ^ w[t - 8] ^ w[t - 14] ^ w[t - 16], 1); - - a = core[0]; b = core[1]; c = core[2]; d = core[3]; - e = core[4]; - - size_t t = 0; - for (size_t u = 0; u < SHA1_ROUNDS_PER_STAGE/5; u++) { - sha1_sw_round(t++,w, &a,&b,&c,&d,&e, Ch(b,c,d), SHA1_STAGE0_CONSTANT); - sha1_sw_round(t++,w, &e,&a,&b,&c,&d, Ch(a,b,c), SHA1_STAGE0_CONSTANT); - sha1_sw_round(t++,w, &d,&e,&a,&b,&c, Ch(e,a,b), SHA1_STAGE0_CONSTANT); - sha1_sw_round(t++,w, &c,&d,&e,&a,&b, Ch(d,e,a), SHA1_STAGE0_CONSTANT); - sha1_sw_round(t++,w, &b,&c,&d,&e,&a, Ch(c,d,e), SHA1_STAGE0_CONSTANT); - } - for (size_t u = 0; u < SHA1_ROUNDS_PER_STAGE/5; u++) { - sha1_sw_round(t++,w, &a,&b,&c,&d,&e, Par(b,c,d), SHA1_STAGE1_CONSTANT); - sha1_sw_round(t++,w, &e,&a,&b,&c,&d, Par(a,b,c), SHA1_STAGE1_CONSTANT); - sha1_sw_round(t++,w, &d,&e,&a,&b,&c, Par(e,a,b), SHA1_STAGE1_CONSTANT); - sha1_sw_round(t++,w, &c,&d,&e,&a,&b, Par(d,e,a), SHA1_STAGE1_CONSTANT); - sha1_sw_round(t++,w, &b,&c,&d,&e,&a, Par(c,d,e), SHA1_STAGE1_CONSTANT); - } - for (size_t u = 0; u < SHA1_ROUNDS_PER_STAGE/5; u++) { - sha1_sw_round(t++,w, &a,&b,&c,&d,&e, Maj(b,c,d), SHA1_STAGE2_CONSTANT); - sha1_sw_round(t++,w, &e,&a,&b,&c,&d, Maj(a,b,c), SHA1_STAGE2_CONSTANT); - sha1_sw_round(t++,w, &d,&e,&a,&b,&c, Maj(e,a,b), SHA1_STAGE2_CONSTANT); - sha1_sw_round(t++,w, &c,&d,&e,&a,&b, Maj(d,e,a), SHA1_STAGE2_CONSTANT); - sha1_sw_round(t++,w, &b,&c,&d,&e,&a, Maj(c,d,e), SHA1_STAGE2_CONSTANT); - } - for (size_t u = 0; u < SHA1_ROUNDS_PER_STAGE/5; u++) { - sha1_sw_round(t++,w, &a,&b,&c,&d,&e, Par(b,c,d), SHA1_STAGE3_CONSTANT); - sha1_sw_round(t++,w, &e,&a,&b,&c,&d, Par(a,b,c), SHA1_STAGE3_CONSTANT); - sha1_sw_round(t++,w, &d,&e,&a,&b,&c, Par(e,a,b), SHA1_STAGE3_CONSTANT); - sha1_sw_round(t++,w, &c,&d,&e,&a,&b, Par(d,e,a), SHA1_STAGE3_CONSTANT); - sha1_sw_round(t++,w, &b,&c,&d,&e,&a, Par(c,d,e), SHA1_STAGE3_CONSTANT); - } - - core[0] += a; core[1] += b; core[2] += c; core[3] += d; core[4] += e; - - smemclr(w, sizeof(w)); -} - -typedef struct sha1_sw { - uint32_t core[5]; - sha1_block blk; - BinarySink_IMPLEMENTATION; - ssh_hash hash; -} sha1_sw; - -static void sha1_sw_write(BinarySink *bs, const void *vp, size_t len); - -static ssh_hash *sha1_sw_new(const ssh_hashalg *alg) -{ - sha1_sw *s = snew(sha1_sw); - - s->hash.vt = alg; - BinarySink_INIT(s, sha1_sw_write); - BinarySink_DELEGATE_INIT(&s->hash, s); - return &s->hash; -} - -static void sha1_sw_reset(ssh_hash *hash) -{ - sha1_sw *s = container_of(hash, sha1_sw, hash); - - memcpy(s->core, sha1_initial_state, sizeof(s->core)); - sha1_block_setup(&s->blk); -} - -static void sha1_sw_copyfrom(ssh_hash *hcopy, ssh_hash *horig) -{ - sha1_sw *copy = container_of(hcopy, sha1_sw, hash); - sha1_sw *orig = container_of(horig, sha1_sw, hash); - - memcpy(copy, orig, sizeof(*copy)); - BinarySink_COPIED(copy); - BinarySink_DELEGATE_INIT(©->hash, copy); -} - -static void sha1_sw_free(ssh_hash *hash) -{ - sha1_sw *s = container_of(hash, sha1_sw, hash); - - smemclr(s, sizeof(*s)); - sfree(s); -} - -static void sha1_sw_write(BinarySink *bs, const void *vp, size_t len) -{ - sha1_sw *s = BinarySink_DOWNCAST(bs, sha1_sw); - - while (len > 0) - if (sha1_block_write(&s->blk, &vp, &len)) - sha1_sw_block(s->core, s->blk.block); -} - -static void sha1_sw_digest(ssh_hash *hash, uint8_t *digest) -{ - sha1_sw *s = container_of(hash, sha1_sw, hash); - - sha1_block_pad(&s->blk, BinarySink_UPCAST(s)); - for (size_t i = 0; i < 5; i++) - PUT_32BIT_MSB_FIRST(digest + 4*i, s->core[i]); -} - -const ssh_hashalg ssh_sha1_sw = { - .new = sha1_sw_new, - .reset = sha1_sw_reset, - .copyfrom = sha1_sw_copyfrom, - .digest = sha1_sw_digest, - .free = sha1_sw_free, - .hlen = 20, - .blocklen = 64, - HASHALG_NAMES_ANNOTATED("SHA-1", "unaccelerated"), -}; - -/* ---------------------------------------------------------------------- - * Hardware-accelerated implementation of SHA-1 using x86 SHA-NI. - */ - -#if HW_SHA1 == HW_SHA1_NI - -/* - * Set target architecture for Clang and GCC - */ - -#if defined(__clang__) || defined(__GNUC__) -# define FUNC_ISA __attribute__ ((target("sse4.1,sha"))) -#if !defined(__clang__) -# pragma GCC target("sha") -# pragma GCC target("sse4.1") -#endif -#else -# define FUNC_ISA -#endif - -#include -#include -#include -#if defined(__clang__) || defined(__GNUC__) -#include -#endif - -#if defined(__clang__) || defined(__GNUC__) -#include -#define GET_CPU_ID_0(out) \ - __cpuid(0, (out)[0], (out)[1], (out)[2], (out)[3]) -#define GET_CPU_ID_7(out) \ - __cpuid_count(7, 0, (out)[0], (out)[1], (out)[2], (out)[3]) -#else -#define GET_CPU_ID_0(out) __cpuid(out, 0) -#define GET_CPU_ID_7(out) __cpuidex(out, 7, 0) -#endif - -static bool sha1_hw_available(void) -{ - unsigned int CPUInfo[4]; - GET_CPU_ID_0(CPUInfo); - if (CPUInfo[0] < 7) - return false; - - GET_CPU_ID_7(CPUInfo); - return CPUInfo[1] & (1 << 29); /* Check SHA */ -} - -/* SHA1 implementation using new instructions - The code is based on Jeffrey Walton's SHA1 implementation: - https://github.com/noloader/SHA-Intrinsics -*/ -FUNC_ISA -static inline void sha1_ni_block(__m128i *core, const uint8_t *p) -{ - __m128i ABCD, E0, E1, MSG0, MSG1, MSG2, MSG3; - const __m128i MASK = _mm_set_epi64x( - 0x0001020304050607ULL, 0x08090a0b0c0d0e0fULL); - - const __m128i *block = (const __m128i *)p; - - /* Load initial values */ - ABCD = core[0]; - E0 = core[1]; - - /* Rounds 0-3 */ - MSG0 = _mm_loadu_si128(block); - MSG0 = _mm_shuffle_epi8(MSG0, MASK); - E0 = _mm_add_epi32(E0, MSG0); - E1 = ABCD; - ABCD = _mm_sha1rnds4_epu32(ABCD, E0, 0); - - /* Rounds 4-7 */ - MSG1 = _mm_loadu_si128(block + 1); - MSG1 = _mm_shuffle_epi8(MSG1, MASK); - E1 = _mm_sha1nexte_epu32(E1, MSG1); - E0 = ABCD; - ABCD = _mm_sha1rnds4_epu32(ABCD, E1, 0); - MSG0 = _mm_sha1msg1_epu32(MSG0, MSG1); - - /* Rounds 8-11 */ - MSG2 = _mm_loadu_si128(block + 2); - MSG2 = _mm_shuffle_epi8(MSG2, MASK); - E0 = _mm_sha1nexte_epu32(E0, MSG2); - E1 = ABCD; - ABCD = _mm_sha1rnds4_epu32(ABCD, E0, 0); - MSG1 = _mm_sha1msg1_epu32(MSG1, MSG2); - MSG0 = _mm_xor_si128(MSG0, MSG2); - - /* Rounds 12-15 */ - MSG3 = _mm_loadu_si128(block + 3); - MSG3 = _mm_shuffle_epi8(MSG3, MASK); - E1 = _mm_sha1nexte_epu32(E1, MSG3); - E0 = ABCD; - MSG0 = _mm_sha1msg2_epu32(MSG0, MSG3); - ABCD = _mm_sha1rnds4_epu32(ABCD, E1, 0); - MSG2 = _mm_sha1msg1_epu32(MSG2, MSG3); - MSG1 = _mm_xor_si128(MSG1, MSG3); - - /* Rounds 16-19 */ - E0 = _mm_sha1nexte_epu32(E0, MSG0); - E1 = ABCD; - MSG1 = _mm_sha1msg2_epu32(MSG1, MSG0); - ABCD = _mm_sha1rnds4_epu32(ABCD, E0, 0); - MSG3 = _mm_sha1msg1_epu32(MSG3, MSG0); - MSG2 = _mm_xor_si128(MSG2, MSG0); - - /* Rounds 20-23 */ - E1 = _mm_sha1nexte_epu32(E1, MSG1); - E0 = ABCD; - MSG2 = _mm_sha1msg2_epu32(MSG2, MSG1); - ABCD = _mm_sha1rnds4_epu32(ABCD, E1, 1); - MSG0 = _mm_sha1msg1_epu32(MSG0, MSG1); - MSG3 = _mm_xor_si128(MSG3, MSG1); - - /* Rounds 24-27 */ - E0 = _mm_sha1nexte_epu32(E0, MSG2); - E1 = ABCD; - MSG3 = _mm_sha1msg2_epu32(MSG3, MSG2); - ABCD = _mm_sha1rnds4_epu32(ABCD, E0, 1); - MSG1 = _mm_sha1msg1_epu32(MSG1, MSG2); - MSG0 = _mm_xor_si128(MSG0, MSG2); - - /* Rounds 28-31 */ - E1 = _mm_sha1nexte_epu32(E1, MSG3); - E0 = ABCD; - MSG0 = _mm_sha1msg2_epu32(MSG0, MSG3); - ABCD = _mm_sha1rnds4_epu32(ABCD, E1, 1); - MSG2 = _mm_sha1msg1_epu32(MSG2, MSG3); - MSG1 = _mm_xor_si128(MSG1, MSG3); - - /* Rounds 32-35 */ - E0 = _mm_sha1nexte_epu32(E0, MSG0); - E1 = ABCD; - MSG1 = _mm_sha1msg2_epu32(MSG1, MSG0); - ABCD = _mm_sha1rnds4_epu32(ABCD, E0, 1); - MSG3 = _mm_sha1msg1_epu32(MSG3, MSG0); - MSG2 = _mm_xor_si128(MSG2, MSG0); - - /* Rounds 36-39 */ - E1 = _mm_sha1nexte_epu32(E1, MSG1); - E0 = ABCD; - MSG2 = _mm_sha1msg2_epu32(MSG2, MSG1); - ABCD = _mm_sha1rnds4_epu32(ABCD, E1, 1); - MSG0 = _mm_sha1msg1_epu32(MSG0, MSG1); - MSG3 = _mm_xor_si128(MSG3, MSG1); - - /* Rounds 40-43 */ - E0 = _mm_sha1nexte_epu32(E0, MSG2); - E1 = ABCD; - MSG3 = _mm_sha1msg2_epu32(MSG3, MSG2); - ABCD = _mm_sha1rnds4_epu32(ABCD, E0, 2); - MSG1 = _mm_sha1msg1_epu32(MSG1, MSG2); - MSG0 = _mm_xor_si128(MSG0, MSG2); - - /* Rounds 44-47 */ - E1 = _mm_sha1nexte_epu32(E1, MSG3); - E0 = ABCD; - MSG0 = _mm_sha1msg2_epu32(MSG0, MSG3); - ABCD = _mm_sha1rnds4_epu32(ABCD, E1, 2); - MSG2 = _mm_sha1msg1_epu32(MSG2, MSG3); - MSG1 = _mm_xor_si128(MSG1, MSG3); - - /* Rounds 48-51 */ - E0 = _mm_sha1nexte_epu32(E0, MSG0); - E1 = ABCD; - MSG1 = _mm_sha1msg2_epu32(MSG1, MSG0); - ABCD = _mm_sha1rnds4_epu32(ABCD, E0, 2); - MSG3 = _mm_sha1msg1_epu32(MSG3, MSG0); - MSG2 = _mm_xor_si128(MSG2, MSG0); - - /* Rounds 52-55 */ - E1 = _mm_sha1nexte_epu32(E1, MSG1); - E0 = ABCD; - MSG2 = _mm_sha1msg2_epu32(MSG2, MSG1); - ABCD = _mm_sha1rnds4_epu32(ABCD, E1, 2); - MSG0 = _mm_sha1msg1_epu32(MSG0, MSG1); - MSG3 = _mm_xor_si128(MSG3, MSG1); - - /* Rounds 56-59 */ - E0 = _mm_sha1nexte_epu32(E0, MSG2); - E1 = ABCD; - MSG3 = _mm_sha1msg2_epu32(MSG3, MSG2); - ABCD = _mm_sha1rnds4_epu32(ABCD, E0, 2); - MSG1 = _mm_sha1msg1_epu32(MSG1, MSG2); - MSG0 = _mm_xor_si128(MSG0, MSG2); - - /* Rounds 60-63 */ - E1 = _mm_sha1nexte_epu32(E1, MSG3); - E0 = ABCD; - MSG0 = _mm_sha1msg2_epu32(MSG0, MSG3); - ABCD = _mm_sha1rnds4_epu32(ABCD, E1, 3); - MSG2 = _mm_sha1msg1_epu32(MSG2, MSG3); - MSG1 = _mm_xor_si128(MSG1, MSG3); - - /* Rounds 64-67 */ - E0 = _mm_sha1nexte_epu32(E0, MSG0); - E1 = ABCD; - MSG1 = _mm_sha1msg2_epu32(MSG1, MSG0); - ABCD = _mm_sha1rnds4_epu32(ABCD, E0, 3); - MSG3 = _mm_sha1msg1_epu32(MSG3, MSG0); - MSG2 = _mm_xor_si128(MSG2, MSG0); - - /* Rounds 68-71 */ - E1 = _mm_sha1nexte_epu32(E1, MSG1); - E0 = ABCD; - MSG2 = _mm_sha1msg2_epu32(MSG2, MSG1); - ABCD = _mm_sha1rnds4_epu32(ABCD, E1, 3); - MSG3 = _mm_xor_si128(MSG3, MSG1); - - /* Rounds 72-75 */ - E0 = _mm_sha1nexte_epu32(E0, MSG2); - E1 = ABCD; - MSG3 = _mm_sha1msg2_epu32(MSG3, MSG2); - ABCD = _mm_sha1rnds4_epu32(ABCD, E0, 3); - - /* Rounds 76-79 */ - E1 = _mm_sha1nexte_epu32(E1, MSG3); - E0 = ABCD; - ABCD = _mm_sha1rnds4_epu32(ABCD, E1, 3); - - /* Combine state */ - core[0] = _mm_add_epi32(ABCD, core[0]); - core[1] = _mm_sha1nexte_epu32(E0, core[1]); -} - -typedef struct sha1_ni { - /* - * core[0] stores the first four words of the SHA-1 state. core[1] - * stores just the fifth word, in the vector lane at the highest - * address. - */ - __m128i core[2]; - sha1_block blk; - void *pointer_to_free; - BinarySink_IMPLEMENTATION; - ssh_hash hash; -} sha1_ni; - -static void sha1_ni_write(BinarySink *bs, const void *vp, size_t len); - -static sha1_ni *sha1_ni_alloc(void) -{ - /* - * The __m128i variables in the context structure need to be - * 16-byte aligned, but not all malloc implementations that this - * code has to work with will guarantee to return a 16-byte - * aligned pointer. So we over-allocate, manually realign the - * pointer ourselves, and store the original one inside the - * context so we know how to free it later. - */ - void *allocation = smalloc(sizeof(sha1_ni) + 15); - uintptr_t alloc_address = (uintptr_t)allocation; - uintptr_t aligned_address = (alloc_address + 15) & ~15; - sha1_ni *s = (sha1_ni *)aligned_address; - s->pointer_to_free = allocation; - return s; -} - -static ssh_hash *sha1_ni_new(const ssh_hashalg *alg) -{ - if (!sha1_hw_available_cached()) - return NULL; - - sha1_ni *s = sha1_ni_alloc(); - - s->hash.vt = alg; - BinarySink_INIT(s, sha1_ni_write); - BinarySink_DELEGATE_INIT(&s->hash, s); - return &s->hash; -} - -FUNC_ISA static void sha1_ni_reset(ssh_hash *hash) -{ - sha1_ni *s = container_of(hash, sha1_ni, hash); - - /* Initialise the core vectors in their storage order */ - s->core[0] = _mm_set_epi64x( - 0x67452301efcdab89ULL, 0x98badcfe10325476ULL); - s->core[1] = _mm_set_epi32(0xc3d2e1f0, 0, 0, 0); - - sha1_block_setup(&s->blk); -} - -static void sha1_ni_copyfrom(ssh_hash *hcopy, ssh_hash *horig) -{ - sha1_ni *copy = container_of(hcopy, sha1_ni, hash); - sha1_ni *orig = container_of(horig, sha1_ni, hash); - - void *ptf_save = copy->pointer_to_free; - *copy = *orig; /* structure copy */ - copy->pointer_to_free = ptf_save; - - BinarySink_COPIED(copy); - BinarySink_DELEGATE_INIT(©->hash, copy); -} - -static void sha1_ni_free(ssh_hash *hash) -{ - sha1_ni *s = container_of(hash, sha1_ni, hash); - - void *ptf = s->pointer_to_free; - smemclr(s, sizeof(*s)); - sfree(ptf); -} - -static void sha1_ni_write(BinarySink *bs, const void *vp, size_t len) -{ - sha1_ni *s = BinarySink_DOWNCAST(bs, sha1_ni); - - while (len > 0) - if (sha1_block_write(&s->blk, &vp, &len)) - sha1_ni_block(s->core, s->blk.block); -} - -FUNC_ISA static void sha1_ni_digest(ssh_hash *hash, uint8_t *digest) -{ - sha1_ni *s = container_of(hash, sha1_ni, hash); - - sha1_block_pad(&s->blk, BinarySink_UPCAST(s)); - - /* Rearrange the first vector into its output order */ - __m128i abcd = _mm_shuffle_epi32(s->core[0], 0x1B); - - /* Byte-swap it into the output endianness */ - const __m128i mask = _mm_setr_epi8(3,2,1,0,7,6,5,4,11,10,9,8,15,14,13,12); - abcd = _mm_shuffle_epi8(abcd, mask); - - /* And store it */ - _mm_storeu_si128((__m128i *)digest, abcd); - - /* Finally, store the leftover word */ - uint32_t e = _mm_extract_epi32(s->core[1], 3); - PUT_32BIT_MSB_FIRST(digest + 16, e); -} - -const ssh_hashalg ssh_sha1_hw = { - .new = sha1_ni_new, - .reset = sha1_ni_reset, - .copyfrom = sha1_ni_copyfrom, - .digest = sha1_ni_digest, - .free = sha1_ni_free, - .hlen = 20, - .blocklen = 64, - HASHALG_NAMES_ANNOTATED("SHA-1", "SHA-NI accelerated"), -}; - -/* ---------------------------------------------------------------------- - * Hardware-accelerated implementation of SHA-1 using Arm NEON. - */ - -#elif HW_SHA1 == HW_SHA1_NEON - -/* - * Manually set the target architecture, if we decided above that we - * need to. - */ -#ifdef USE_CLANG_ATTR_TARGET_AARCH64 -/* - * A spot of cheating: redefine some ACLE feature macros before - * including arm_neon.h. Otherwise we won't get the SHA intrinsics - * defined by that header, because it will be looking at the settings - * for the whole translation unit rather than the ones we're going to - * put on some particular functions using __attribute__((target)). - */ -#define __ARM_NEON 1 -#define __ARM_FEATURE_CRYPTO 1 -#define FUNC_ISA __attribute__ ((target("neon,crypto"))) -#endif /* USE_CLANG_ATTR_TARGET_AARCH64 */ - -#ifndef FUNC_ISA -#define FUNC_ISA -#endif - -#ifdef USE_ARM64_NEON_H -#include -#else -#include -#endif - -static bool sha1_hw_available(void) -{ - /* - * For Arm, we delegate to a per-platform detection function (see - * explanation in sshaes.c). - */ - return platform_sha1_hw_available(); -} - -typedef struct sha1_neon_core sha1_neon_core; -struct sha1_neon_core { - uint32x4_t abcd; - uint32_t e; -}; - -FUNC_ISA -static inline uint32x4_t sha1_neon_load_input(const uint8_t *p) -{ - return vreinterpretq_u32_u8(vrev32q_u8(vld1q_u8(p))); -} - -FUNC_ISA -static inline uint32x4_t sha1_neon_schedule_update( - uint32x4_t m4, uint32x4_t m3, uint32x4_t m2, uint32x4_t m1) -{ - return vsha1su1q_u32(vsha1su0q_u32(m4, m3, m2), m1); -} - -/* - * SHA-1 has three different kinds of round, differing in whether they - * use the Ch, Maj or Par functions defined above. Each one uses a - * separate NEON instruction, so we define three inline functions for - * the different round types using this macro. - * - * The two batches of Par-type rounds also use a different constant, - * but that's passed in as an operand, so we don't need a fourth - * inline function just for that. - */ -#define SHA1_NEON_ROUND_FN(type) \ - FUNC_ISA static inline sha1_neon_core sha1_neon_round4_##type( \ - sha1_neon_core old, uint32x4_t sched, uint32x4_t constant) \ - { \ - sha1_neon_core new; \ - uint32x4_t round_input = vaddq_u32(sched, constant); \ - new.abcd = vsha1##type##q_u32(old.abcd, old.e, round_input); \ - new.e = vsha1h_u32(vget_lane_u32(vget_low_u32(old.abcd), 0)); \ - return new; \ - } -SHA1_NEON_ROUND_FN(c) -SHA1_NEON_ROUND_FN(p) -SHA1_NEON_ROUND_FN(m) - -FUNC_ISA -static inline void sha1_neon_block(sha1_neon_core *core, const uint8_t *p) -{ - uint32x4_t constant, s0, s1, s2, s3; - sha1_neon_core cr = *core; - - constant = vdupq_n_u32(SHA1_STAGE0_CONSTANT); - s0 = sha1_neon_load_input(p); - cr = sha1_neon_round4_c(cr, s0, constant); - s1 = sha1_neon_load_input(p + 16); - cr = sha1_neon_round4_c(cr, s1, constant); - s2 = sha1_neon_load_input(p + 32); - cr = sha1_neon_round4_c(cr, s2, constant); - s3 = sha1_neon_load_input(p + 48); - cr = sha1_neon_round4_c(cr, s3, constant); - s0 = sha1_neon_schedule_update(s0, s1, s2, s3); - cr = sha1_neon_round4_c(cr, s0, constant); - - constant = vdupq_n_u32(SHA1_STAGE1_CONSTANT); - s1 = sha1_neon_schedule_update(s1, s2, s3, s0); - cr = sha1_neon_round4_p(cr, s1, constant); - s2 = sha1_neon_schedule_update(s2, s3, s0, s1); - cr = sha1_neon_round4_p(cr, s2, constant); - s3 = sha1_neon_schedule_update(s3, s0, s1, s2); - cr = sha1_neon_round4_p(cr, s3, constant); - s0 = sha1_neon_schedule_update(s0, s1, s2, s3); - cr = sha1_neon_round4_p(cr, s0, constant); - s1 = sha1_neon_schedule_update(s1, s2, s3, s0); - cr = sha1_neon_round4_p(cr, s1, constant); - - constant = vdupq_n_u32(SHA1_STAGE2_CONSTANT); - s2 = sha1_neon_schedule_update(s2, s3, s0, s1); - cr = sha1_neon_round4_m(cr, s2, constant); - s3 = sha1_neon_schedule_update(s3, s0, s1, s2); - cr = sha1_neon_round4_m(cr, s3, constant); - s0 = sha1_neon_schedule_update(s0, s1, s2, s3); - cr = sha1_neon_round4_m(cr, s0, constant); - s1 = sha1_neon_schedule_update(s1, s2, s3, s0); - cr = sha1_neon_round4_m(cr, s1, constant); - s2 = sha1_neon_schedule_update(s2, s3, s0, s1); - cr = sha1_neon_round4_m(cr, s2, constant); - - constant = vdupq_n_u32(SHA1_STAGE3_CONSTANT); - s3 = sha1_neon_schedule_update(s3, s0, s1, s2); - cr = sha1_neon_round4_p(cr, s3, constant); - s0 = sha1_neon_schedule_update(s0, s1, s2, s3); - cr = sha1_neon_round4_p(cr, s0, constant); - s1 = sha1_neon_schedule_update(s1, s2, s3, s0); - cr = sha1_neon_round4_p(cr, s1, constant); - s2 = sha1_neon_schedule_update(s2, s3, s0, s1); - cr = sha1_neon_round4_p(cr, s2, constant); - s3 = sha1_neon_schedule_update(s3, s0, s1, s2); - cr = sha1_neon_round4_p(cr, s3, constant); - - core->abcd = vaddq_u32(core->abcd, cr.abcd); - core->e += cr.e; -} - -typedef struct sha1_neon { - sha1_neon_core core; - sha1_block blk; - BinarySink_IMPLEMENTATION; - ssh_hash hash; -} sha1_neon; - -static void sha1_neon_write(BinarySink *bs, const void *vp, size_t len); - -static ssh_hash *sha1_neon_new(const ssh_hashalg *alg) -{ - if (!sha1_hw_available_cached()) - return NULL; - - sha1_neon *s = snew(sha1_neon); - - s->hash.vt = alg; - BinarySink_INIT(s, sha1_neon_write); - BinarySink_DELEGATE_INIT(&s->hash, s); - return &s->hash; -} - -static void sha1_neon_reset(ssh_hash *hash) -{ - sha1_neon *s = container_of(hash, sha1_neon, hash); - - s->core.abcd = vld1q_u32(sha1_initial_state); - s->core.e = sha1_initial_state[4]; - - sha1_block_setup(&s->blk); -} - -static void sha1_neon_copyfrom(ssh_hash *hcopy, ssh_hash *horig) -{ - sha1_neon *copy = container_of(hcopy, sha1_neon, hash); - sha1_neon *orig = container_of(horig, sha1_neon, hash); - - *copy = *orig; /* structure copy */ - - BinarySink_COPIED(copy); - BinarySink_DELEGATE_INIT(©->hash, copy); -} - -static void sha1_neon_free(ssh_hash *hash) -{ - sha1_neon *s = container_of(hash, sha1_neon, hash); - smemclr(s, sizeof(*s)); - sfree(s); -} - -static void sha1_neon_write(BinarySink *bs, const void *vp, size_t len) -{ - sha1_neon *s = BinarySink_DOWNCAST(bs, sha1_neon); - - while (len > 0) - if (sha1_block_write(&s->blk, &vp, &len)) - sha1_neon_block(&s->core, s->blk.block); -} - -static void sha1_neon_digest(ssh_hash *hash, uint8_t *digest) -{ - sha1_neon *s = container_of(hash, sha1_neon, hash); - - sha1_block_pad(&s->blk, BinarySink_UPCAST(s)); - vst1q_u8(digest, vrev32q_u8(vreinterpretq_u8_u32(s->core.abcd))); - PUT_32BIT_MSB_FIRST(digest + 16, s->core.e); -} - -const ssh_hashalg ssh_sha1_hw = { - .new = sha1_neon_new, - .reset = sha1_neon_reset, - .copyfrom = sha1_neon_copyfrom, - .digest = sha1_neon_digest, - .free = sha1_neon_free, - .hlen = 20, - .blocklen = 64, - HASHALG_NAMES_ANNOTATED("SHA-1", "NEON accelerated"), -}; - -/* ---------------------------------------------------------------------- - * Stub functions if we have no hardware-accelerated SHA-1. In this - * case, sha1_hw_new returns NULL (though it should also never be - * selected by sha1_select, so the only thing that should even be - * _able_ to call it is testcrypt). As a result, the remaining vtable - * functions should never be called at all. - */ - -#elif HW_SHA1 == HW_SHA1_NONE - -static bool sha1_hw_available(void) -{ - return false; -} - -static ssh_hash *sha1_stub_new(const ssh_hashalg *alg) -{ - return NULL; -} - -#define STUB_BODY { unreachable("Should never be called"); } - -static void sha1_stub_reset(ssh_hash *hash) STUB_BODY -static void sha1_stub_copyfrom(ssh_hash *hash, ssh_hash *orig) STUB_BODY -static void sha1_stub_free(ssh_hash *hash) STUB_BODY -static void sha1_stub_digest(ssh_hash *hash, uint8_t *digest) STUB_BODY - -const ssh_hashalg ssh_sha1_hw = { - .new = sha1_stub_new, - .reset = sha1_stub_reset, - .copyfrom = sha1_stub_copyfrom, - .digest = sha1_stub_digest, - .free = sha1_stub_free, - .hlen = 20, - .blocklen = 64, - HASHALG_NAMES_ANNOTATED("SHA-1", "!NONEXISTENT ACCELERATED VERSION!"), -}; - -#endif /* HW_SHA1 */ diff --git a/sshsha3.c b/sshsha3.c deleted file mode 100644 index 83d136bf..00000000 --- a/sshsha3.c +++ /dev/null @@ -1,329 +0,0 @@ -/* - * SHA-3, as defined in FIPS PUB 202. - */ - -#include -#include -#include "ssh.h" - -static inline uint64_t rol(uint64_t x, unsigned shift) -{ - unsigned L = (+shift) & 63; - unsigned R = (-shift) & 63; - return (x << L) | (x >> R); -} - -/* - * General Keccak is defined such that its state is a 5x5 array of - * words which can be any power-of-2 size from 1 up to 64. SHA-3 fixes - * on 64, and so do we. - * - * The number of rounds is defined as 12 + 2k if the word size is 2^k. - * Here we have 64-bit words only, so k=6, so 24 rounds always. - */ -typedef uint64_t keccak_core_state[5][5]; -#define NROUNDS 24 /* would differ for other word sizes */ -static const uint64_t round_constants[NROUNDS]; -static const unsigned rotation_counts[5][5]; - -/* - * Core Keccak transform: just squodge the state around internally, - * without adding or extracting any data from it. - */ -static void keccak_transform(keccak_core_state A) -{ - union { - uint64_t C[5]; - uint64_t B[5][5]; - } u; - - for (unsigned round = 0; round < NROUNDS; round++) { - /* theta step */ - for (unsigned x = 0; x < 5; x++) - u.C[x] = A[x][0] ^ A[x][1] ^ A[x][2] ^ A[x][3] ^ A[x][4]; - for (unsigned x = 0; x < 5; x++) { - uint64_t D = rol(u.C[(x+1) % 5], 1) ^ u.C[(x+4) % 5]; - for (unsigned y = 0; y < 5; y++) - A[x][y] ^= D; - } - - /* rho and pi steps */ - for (unsigned x = 0; x < 5; x++) - for (unsigned y = 0; y < 5; y++) - u.B[y][(2*x+3*y) % 5] = rol(A[x][y], rotation_counts[x][y]); - - /* chi step */ - for (unsigned x = 0; x < 5; x++) - for (unsigned y = 0; y < 5; y++) - A[x][y] = u.B[x][y] ^ (u.B[(x+2)%5][y] & ~u.B[(x+1)%5][y]); - - /* iota step */ - A[0][0] ^= round_constants[round]; - } - - smemclr(&u, sizeof(u)); -} - -typedef struct { - keccak_core_state A; - unsigned char bytes[25*8]; - unsigned char first_pad_byte; - size_t bytes_got, bytes_wanted, hash_bytes; -} keccak_state; - -/* - * Keccak accumulation function: given a piece of message, add it to - * the hash. - */ -static void keccak_accumulate(keccak_state *s, const void *vdata, size_t len) -{ - const unsigned char *data = (const unsigned char *)vdata; - - while (len >= s->bytes_wanted - s->bytes_got) { - size_t b = s->bytes_wanted - s->bytes_got; - memcpy(s->bytes + s->bytes_got, data, b); - len -= b; - data += b; - - size_t n = 0; - for (unsigned y = 0; y < 5; y++) { - for (unsigned x = 0; x < 5; x++) { - if (n >= s->bytes_wanted) - break; - - s->A[x][y] ^= GET_64BIT_LSB_FIRST(s->bytes + n); - n += 8; - } - } - keccak_transform(s->A); - - s->bytes_got = 0; - } - - memcpy(s->bytes + s->bytes_got, data, len); - s->bytes_got += len; -} - -/* - * Keccak output function. - */ -static void keccak_output(keccak_state *s, void *voutput) -{ - unsigned char *output = (unsigned char *)voutput; - - /* - * Add message padding. - */ - { - unsigned char padding[25*8]; - size_t len = s->bytes_wanted - s->bytes_got; - if (len == 0) - len = s->bytes_wanted; - memset(padding, 0, len); - padding[0] |= s->first_pad_byte; - padding[len-1] |= 0x80; - keccak_accumulate(s, padding, len); - } - - size_t n = 0; - for (unsigned y = 0; y < 5; y++) { - for (unsigned x = 0; x < 5; x++) { - size_t to_copy = s->hash_bytes - n; - if (to_copy == 0) - break; - if (to_copy > 8) - to_copy = 8; - unsigned char outbytes[8]; - PUT_64BIT_LSB_FIRST(outbytes, s->A[x][y]); - memcpy(output + n, outbytes, to_copy); - n += to_copy; - } - } -} - -static void keccak_init(keccak_state *s, unsigned hashbits, unsigned ratebits, - unsigned char first_pad_byte) -{ - int x, y; - - assert(hashbits % 8 == 0); - assert(ratebits % 8 == 0); - - s->hash_bytes = hashbits / 8; - s->bytes_wanted = (25 * 64 - ratebits) / 8; - s->bytes_got = 0; - s->first_pad_byte = first_pad_byte; - - assert(s->bytes_wanted % 8 == 0); - - for (y = 0; y < 5; y++) - for (x = 0; x < 5; x++) - s->A[x][y] = 0; -} - -static void keccak_sha3_init(keccak_state *s, int hashbits) -{ - keccak_init(s, hashbits, hashbits * 2, 0x06); -} - -static void keccak_shake_init(keccak_state *s, int parambits, int hashbits) -{ - keccak_init(s, hashbits, parambits * 2, 0x1f); -} - -/* - * Keccak round constants, generated via the LFSR specified in the - * Keccak reference by the following piece of Python: - -import textwrap -from functools import reduce - -rbytes = [1] -while len(rbytes) < 7*24: - k = rbytes[-1] * 2 - rbytes.append(k ^ (0x171 * (k >> 8))) - -rbits = [byte & 1 for byte in rbytes] - -rwords = [sum(rbits[i+j] << ((1 << j) - 1) for j in range(7)) - for i in range(0, len(rbits), 7)] - -print(textwrap.indent("\n".join(textwrap.wrap(", ".join( - map("0x{:016x}".format, rwords)))), " "*4)) - -*/ - -static const uint64_t round_constants[24] = { - 0x0000000000000001, 0x0000000000008082, 0x800000000000808a, - 0x8000000080008000, 0x000000000000808b, 0x0000000080000001, - 0x8000000080008081, 0x8000000000008009, 0x000000000000008a, - 0x0000000000000088, 0x0000000080008009, 0x000000008000000a, - 0x000000008000808b, 0x800000000000008b, 0x8000000000008089, - 0x8000000000008003, 0x8000000000008002, 0x8000000000000080, - 0x000000000000800a, 0x800000008000000a, 0x8000000080008081, - 0x8000000000008080, 0x0000000080000001, 0x8000000080008008 -}; - -/* - * Keccak per-element rotation counts, generated from the matrix - * formula in the Keccak reference by the following piece of Python: - -coords = [1, 0] -while len(coords) < 26: - coords.append((2*coords[-2] + 3*coords[-1]) % 5) - -matrix = { (coords[i], coords[i+1]) : i for i in range(24) } -matrix[0,0] = -1 - -f = lambda t: (t+1) * (t+2) // 2 % 64 - -for y in range(5): - print(" {{{}}},".format(", ".join("{:2d}".format(f(matrix[y,x])) - for x in range(5)))) - -*/ -static const unsigned rotation_counts[5][5] = { - { 0, 36, 3, 41, 18}, - { 1, 44, 10, 45, 2}, - {62, 6, 43, 15, 61}, - {28, 55, 25, 21, 56}, - {27, 20, 39, 8, 14}, -}; - -/* - * The PuTTY ssh_hashalg abstraction. - */ -struct keccak_hash { - keccak_state state; - ssh_hash hash; - BinarySink_IMPLEMENTATION; -}; - -static void keccak_BinarySink_write(BinarySink *bs, const void *p, size_t len) -{ - struct keccak_hash *kh = BinarySink_DOWNCAST(bs, struct keccak_hash); - keccak_accumulate(&kh->state, p, len); -} - -static ssh_hash *keccak_new(const ssh_hashalg *alg) -{ - struct keccak_hash *kh = snew(struct keccak_hash); - kh->hash.vt = alg; - BinarySink_INIT(kh, keccak_BinarySink_write); - BinarySink_DELEGATE_INIT(&kh->hash, kh); - return ssh_hash_reset(&kh->hash); -} - -static void keccak_free(ssh_hash *hash) -{ - struct keccak_hash *kh = container_of(hash, struct keccak_hash, hash); - smemclr(kh, sizeof(*kh)); - sfree(kh); -} - -static void keccak_copyfrom(ssh_hash *hnew, ssh_hash *hold) -{ - struct keccak_hash *khold = container_of(hold, struct keccak_hash, hash); - struct keccak_hash *khnew = container_of(hnew, struct keccak_hash, hash); - khnew->state = khold->state; -} - -static void keccak_digest(ssh_hash *hash, unsigned char *output) -{ - struct keccak_hash *kh = container_of(hash, struct keccak_hash, hash); - keccak_output(&kh->state, output); -} - -static void sha3_reset(ssh_hash *hash) -{ - struct keccak_hash *kh = container_of(hash, struct keccak_hash, hash); - keccak_sha3_init(&kh->state, hash->vt->hlen * 8); -} - -#define DEFINE_SHA3(bits) \ - const ssh_hashalg ssh_sha3_##bits = { \ - .new = keccak_new, \ - .reset = sha3_reset, \ - .copyfrom = keccak_copyfrom, \ - .digest = keccak_digest, \ - .free = keccak_free, \ - .hlen = bits/8, \ - .blocklen = 200 - 2*(bits/8), \ - HASHALG_NAMES_BARE("SHA3-" #bits), \ - } - -DEFINE_SHA3(224); -DEFINE_SHA3(256); -DEFINE_SHA3(384); -DEFINE_SHA3(512); - -static void shake256_reset(ssh_hash *hash) -{ - struct keccak_hash *kh = container_of(hash, struct keccak_hash, hash); - keccak_shake_init(&kh->state, 256, hash->vt->hlen * 8); -} - -/* - * There is some confusion over the output length parameter for the - * SHAKE functions. By my reading, FIPS PUB 202 defines SHAKE256(M,d) - * to generate d _bits_ of output. But RFC 8032 (defining Ed448) talks - * about "SHAKE256(x,114)" in a context where it definitely means - * generating 114 _bytes_ of output. - * - * Our internal ID therefore suffixes the output length with "bytes", - * to be clear which we're talking about - */ - -#define DEFINE_SHAKE(param, hashbytes) \ - const ssh_hashalg ssh_shake##param##_##hashbytes##bytes = { \ - .new = keccak_new, \ - .reset = shake##param##_reset, \ - .copyfrom = keccak_copyfrom, \ - .digest = keccak_digest, \ - .free = keccak_free, \ - .hlen = hashbytes, \ - .blocklen = 0, \ - HASHALG_NAMES_BARE("SHAKE" #param), \ - } - -DEFINE_SHAKE(256, 114); -- cgit v1.2.3 From fca13a17b160da3b5069df3ceab19d4448c4f389 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 19 Apr 2021 06:42:12 +0100 Subject: Break up crypto modules containing HW acceleration. This applies to all of AES, SHA-1, SHA-256 and SHA-512. All those source files previously contained multiple implementations of the algorithm, enabled or disabled by ifdefs detecting whether they would work on a given compiler. And in order to get advanced machine instructions like AES-NI or NEON crypto into the output file when the compile flags hadn't enabled them, we had to do nasty stuff with compiler-specific pragmas or attributes. Now we can do the detection at cmake time, and enable advanced instructions in the more sensible way, by compile-time flags. So I've broken up each of these modules into lots of sub-pieces: a file called (e.g.) 'foo-common.c' containing common definitions across all implementations (such as round constants), one called 'foo-select.c' containing the top-level vtable(s), and a separate file for each implementation exporting just the vtable(s) for that implementation. One advantage of this is that it depends a lot less on compiler- specific bodgery. My particular least favourite part of the previous setup was the part where I had to _manually_ define some Arm ACLE feature macros before including , so that it would define the intrinsics I wanted. Now I'm enabling interesting architecture features in the normal way, on the compiler command line, there's no need for that kind of trick: the right feature macros are already defined and does the right thing. Another change in this reorganisation is that I've stopped assuming there's just one hardware implementation per platform. Previously, the accelerated vtables were called things like sha256_hw, and varied between FOO-NI and NEON depending on platform; and the selection code would simply ask 'is hw available? if so, use hw, else sw'. Now, each HW acceleration strategy names its vtable its own way, and the selection vtable has a whole list of possibilities to iterate over looking for a supported one. So if someone feels like writing a second accelerated implementation of something for a given platform - for example, I've heard you can use plain NEON to speed up AES somewhat even without the crypto extension - then it will now have somewhere to drop in alongside the existing ones. --- cmake/cmake.h.in | 8 + crypto/CMakeLists.txt | 183 +++- crypto/aes-common.c | 14 + crypto/aes-neon.c | 294 ++++++ crypto/aes-ni.c | 281 ++++++ crypto/aes-select.c | 89 ++ crypto/aes-sw.c | 1040 +++++++++++++++++++++ crypto/aes.c | 1912 -------------------------------------- crypto/aes.h | 109 +++ crypto/sha1-common.c | 10 + crypto/sha1-neon.c | 190 ++++ crypto/sha1-ni.c | 325 +++++++ crypto/sha1-select.c | 44 + crypto/sha1-sw.c | 155 +++ crypto/sha1.c | 933 ------------------- crypto/sha1.h | 109 +++ crypto/sha256-common.c | 30 + crypto/sha256-neon.c | 162 ++++ crypto/sha256-ni.c | 342 +++++++ crypto/sha256-select.c | 44 + crypto/sha256-sw.c | 157 ++++ crypto/sha256.c | 939 ------------------- crypto/sha256.h | 105 +++ crypto/sha512-common.c | 71 ++ crypto/sha512-neon.c | 329 +++++++ crypto/sha512-select.c | 61 ++ crypto/sha512-sw.c | 168 ++++ crypto/sha512.c | 836 ----------------- crypto/sha512.h | 131 +++ ssh.h | 36 +- test/cryptsuite.py | 95 +- testcrypt.c | 84 +- testcrypt.h | 1 + testsc.c | 66 +- unix/utils/arm_arch_queries.c | 8 +- windows/utils/arm_arch_queries.c | 8 +- 36 files changed, 4644 insertions(+), 4725 deletions(-) create mode 100644 crypto/aes-common.c create mode 100644 crypto/aes-neon.c create mode 100644 crypto/aes-ni.c create mode 100644 crypto/aes-select.c create mode 100644 crypto/aes-sw.c delete mode 100644 crypto/aes.c create mode 100644 crypto/aes.h create mode 100644 crypto/sha1-common.c create mode 100644 crypto/sha1-neon.c create mode 100644 crypto/sha1-ni.c create mode 100644 crypto/sha1-select.c create mode 100644 crypto/sha1-sw.c delete mode 100644 crypto/sha1.c create mode 100644 crypto/sha1.h create mode 100644 crypto/sha256-common.c create mode 100644 crypto/sha256-neon.c create mode 100644 crypto/sha256-ni.c create mode 100644 crypto/sha256-select.c create mode 100644 crypto/sha256-sw.c delete mode 100644 crypto/sha256.c create mode 100644 crypto/sha256.h create mode 100644 crypto/sha512-common.c create mode 100644 crypto/sha512-neon.c create mode 100644 crypto/sha512-select.c create mode 100644 crypto/sha512-sw.c delete mode 100644 crypto/sha512.c create mode 100644 crypto/sha512.h diff --git a/cmake/cmake.h.in b/cmake/cmake.h.in index f051c759..b6c07d2c 100644 --- a/cmake/cmake.h.in +++ b/cmake/cmake.h.in @@ -40,3 +40,11 @@ #cmakedefine01 HAVE_SO_PEERCRED #cmakedefine01 HAVE_PANGO_FONT_FAMILY_IS_MONOSPACE #cmakedefine01 HAVE_PANGO_FONT_MAP_LIST_FAMILIES + +#cmakedefine01 HAVE_AES_NI +#cmakedefine01 HAVE_SHA_NI +#cmakedefine01 HAVE_SHAINTRIN_H +#cmakedefine01 HAVE_NEON_CRYPTO +#cmakedefine01 HAVE_NEON_SHA512 +#cmakedefine01 HAVE_NEON_SHA512_INTRINSICS +#cmakedefine01 USE_ARM64_NEON_H diff --git a/crypto/CMakeLists.txt b/crypto/CMakeLists.txt index 74f86cd4..917614be 100644 --- a/crypto/CMakeLists.txt +++ b/crypto/CMakeLists.txt @@ -1,5 +1,7 @@ add_sources_from_current_dir(crypto - aes.c + aes-common.c + aes-select.c + aes-sw.c arcfour.c argon2.c bcrypt.c @@ -23,8 +25,181 @@ add_sources_from_current_dir(crypto pubkey-ppk.c pubkey-ssh1.c rsa.c - sha256.c - sha512.c + sha256-common.c + sha256-select.c + sha256-sw.c + sha512-common.c + sha512-select.c + sha512-sw.c sha3.c - sha1.c + sha1-common.c + sha1-select.c + sha1-sw.c xdmauth.c) + +include(CheckCSourceCompiles) + +function(test_compile_with_flags outvar) + cmake_parse_arguments(OPT "" "" + "GNU_FLAGS;MSVC_FLAGS;ADD_SOURCES_IF_SUCCESSFUL;TEST_SOURCE" "${ARGN}") + + # Figure out what flags are applicable to this compiler. + set(flags) + if(CMAKE_C_COMPILER_ID MATCHES "GNU" OR + CMAKE_C_COMPILER_ID MATCHES "Clang") + set(flags ${OPT_GNU_FLAGS}) + endif() + if(CMAKE_C_COMPILER_ID MATCHES "MSVC") + set(flags ${OPT_MSVC_FLAGS}) + endif() + + # See if we can compile the provided test program. + string(JOIN " " CMAKE_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS} ${flags}) + check_c_source_compiles("${OPT_TEST_SOURCE}" "${outvar}") + + if(${outvar} AND OPT_ADD_SOURCES_IF_SUCCESSFUL) + # Make an object library that compiles the implementation with the + # necessary flags, and add the resulting objects to the crypto + # library. + set(libname object_lib_${outvar}) + add_library(${libname} OBJECT ${OPT_ADD_SOURCES_IF_SUCCESSFUL}) + target_compile_options(${libname} PRIVATE ${flags}) + target_sources(crypto PRIVATE $) + endif() + + # Export the output to the caller's scope, so that further tests can + # be based on it. + set(${outvar} ${${outvar}} PARENT_SCOPE) +endfunction() + +# ---------------------------------------------------------------------- +# Try to enable x86 intrinsics-based crypto implementations. + +test_compile_with_flags(HAVE_WMMINTRIN_H + GNU_FLAGS -msse4.1 + TEST_SOURCE " + #include + #include + volatile __m128i r, a, b; + int main(void) { r = _mm_xor_si128(a, b); }") +if(HAVE_WMMINTRIN_H) + test_compile_with_flags(HAVE_AES_NI + GNU_FLAGS -msse4.1 -maes + TEST_SOURCE " + #include + #include + volatile __m128i r, a, b; + int main(void) { r = _mm_aesenc_si128(a, b); }" + ADD_SOURCES_IF_SUCCESSFUL aes-ni aes-ni.c) + + # shaintrin.h doesn't exist on all compilers; sometimes it's folded + # into the other headers + test_compile_with_flags(HAVE_SHAINTRIN_H + GNU_FLAGS -msse4.1 -msha + TEST_SOURCE " + #include + #include + #include + #include + volatile __m128i r, a, b; + int main(void) { r = _mm_xor_si128(a, b); }") + if(HAVE_SHAINTRIN_H) + set(include_shaintrin "#include ") + else() + set(include_shaintrin "") + endif() + + test_compile_with_flags(HAVE_SHA_NI + GNU_FLAGS -msse4.1 -msha + TEST_SOURCE " + #include + #include + #include + ${include_shaintrin} + volatile __m128i r, a, b, c; + int main(void) { r = _mm_sha256rnds2_epu32(a, b, c); } + " sha-ni sha256-ni.c sha1-ni.c) +endif() + +# ---------------------------------------------------------------------- +# Try to enable Arm Neon intrinsics-based crypto implementations. + +# Start by checking which header file we need. ACLE specifies that it +# ought to be , on both 32- and 64-bit Arm, but Visual +# Studio for some reason renamed the header to in +# 64-bit, and gives an error if you use the standard name. (However, +# clang-cl does let you use the standard name.) +test_compile_with_flags(HAVE_ARM_NEON_H + MSVC_FLAGS -D_ARM_USE_NEW_NEON_INTRINSICS + TEST_SOURCE " + #include + volatile uint8x16_t r, a, b; + int main(void) { r = veorq_u8(a, b); }") +if(HAVE_ARM_NEON_H) + set(neon ON) + set(neon_header "arm_neon.h") +else() + test_compile_with_flags(HAVE_ARM64_NEON_H TEST_SOURCE " + #include + volatile uint8x16_t r, a, b; + int main(void) { r = veorq_u8(a, b); }") + if(HAVE_ARM64_NEON_H) + set(neon ON) + set(neon_header "arm64_neon.h") + set(USE_ARM64_NEON_H ON) + endif() +endif() + +if(neon) + # If we have _some_ NEON header, look for the individual things we + # can enable with it. + + # The 'crypto' architecture extension includes support for AES, + # SHA-1, and SHA-256. + test_compile_with_flags(HAVE_NEON_CRYPTO + GNU_FLAGS -march=armv8-a+crypto + MSVC_FLAGS -D_ARM_USE_NEW_NEON_INTRINSICS + TEST_SOURCE " + #include <${neon_header}> + volatile uint8x16_t r, a, b; + volatile uint32x4_t s, x, y, z; + int main(void) { r = vaeseq_u8(a, b); s = vsha256hq_u32(x, y, z); }" + ADD_SOURCES_IF_SUCCESSFUL aes-neon.c sha256-neon.c sha1-neon.c) + + # The 'sha3' architecture extension, despite the name, includes + # support for SHA-512 (from the SHA-2 standard) as well as SHA-3 + # proper. + # + # Versions of clang up to and including clang 12 support this + # extension in assembly language, but not the ACLE intrinsics for + # it. So we check both. + test_compile_with_flags(HAVE_NEON_SHA512_INTRINSICS + GNU_FLAGS -march=armv8.2-a+crypto+sha3 + TEST_SOURCE " + #include <${neon_header}> + volatile uint64x2_t r, a, b; + int main(void) { r = vsha512su0q_u64(a, b); }" + ADD_SOURCES_IF_SUCCESSFUL sha512-neon.c) + if(HAVE_NEON_SHA512_INTRINSICS) + set(HAVE_NEON_SHA512 ON) + else() + test_compile_with_flags(HAVE_NEON_SHA512_ASM + GNU_FLAGS -march=armv8.2-a+crypto+sha3 + TEST_SOURCE " + #include <${neon_header}> + volatile uint64x2_t r, a; + int main(void) { __asm__(\"sha512su0 %0.2D,%1.2D\" : \"+w\" (r) : \"w\" (a)); }" + ADD_SOURCES_IF_SUCCESSFUL sha512-neon.c) + if(HAVE_NEON_SHA512_ASM) + set(HAVE_NEON_SHA512 ON) + endif() + endif() +endif() + +set(HAVE_AES_NI ${HAVE_AES_NI} PARENT_SCOPE) +set(HAVE_SHA_NI ${HAVE_SHA_NI} PARENT_SCOPE) +set(HAVE_SHAINTRIN_H ${HAVE_SHAINTRIN_H} PARENT_SCOPE) +set(HAVE_NEON_CRYPTO ${HAVE_NEON_CRYPTO} PARENT_SCOPE) +set(HAVE_NEON_SHA512 ${HAVE_NEON_SHA512} PARENT_SCOPE) +set(HAVE_NEON_SHA512_INTRINSICS ${HAVE_NEON_SHA512_INTRINSICS} PARENT_SCOPE) +set(USE_ARM64_NEON_H ${USE_ARM64_NEON_H} PARENT_SCOPE) diff --git a/crypto/aes-common.c b/crypto/aes-common.c new file mode 100644 index 00000000..e1c41ddf --- /dev/null +++ b/crypto/aes-common.c @@ -0,0 +1,14 @@ +/* + * Common variable definitions across all the AES implementations. + */ + +#include "ssh.h" +#include "aes.h" + +const uint8_t aes_key_setup_round_constants[10] = { + /* The first few powers of X in GF(2^8), used during key setup. + * This can safely be a lookup table without side channel risks, + * because key setup iterates through it once in a standard way + * regardless of the key. */ + 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, +}; diff --git a/crypto/aes-neon.c b/crypto/aes-neon.c new file mode 100644 index 00000000..d47d2fd3 --- /dev/null +++ b/crypto/aes-neon.c @@ -0,0 +1,294 @@ +/* ---------------------------------------------------------------------- + * Hardware-accelerated implementation of AES using Arm NEON. + */ + +#include "ssh.h" +#include "aes.h" + +#if USE_ARM64_NEON_H +#include +#else +#include +#endif + +static bool aes_neon_available(void) +{ + /* + * For Arm, we delegate to a per-platform AES detection function, + * because it has to be implemented by asking the operating system + * rather than directly querying the CPU. + * + * That's because Arm systems commonly have multiple cores that + * are not all alike, so any method of querying whether NEON + * crypto instructions work on the _current_ CPU - even one as + * crude as just trying one and catching the SIGILL - wouldn't + * give an answer that you could still rely on the first time the + * OS migrated your process to another CPU. + */ + return platform_aes_neon_available(); +} + +/* + * Core NEON encrypt/decrypt functions, one per length and direction. + */ + +#define NEON_CIPHER(len, repmacro) \ + static inline uint8x16_t aes_neon_##len##_e( \ + uint8x16_t v, const uint8x16_t *keysched) \ + { \ + repmacro(v = vaesmcq_u8(vaeseq_u8(v, *keysched++));); \ + v = vaeseq_u8(v, *keysched++); \ + return veorq_u8(v, *keysched); \ + } \ + static inline uint8x16_t aes_neon_##len##_d( \ + uint8x16_t v, const uint8x16_t *keysched) \ + { \ + repmacro(v = vaesimcq_u8(vaesdq_u8(v, *keysched++));); \ + v = vaesdq_u8(v, *keysched++); \ + return veorq_u8(v, *keysched); \ + } + +NEON_CIPHER(128, REP9) +NEON_CIPHER(192, REP11) +NEON_CIPHER(256, REP13) + +/* + * The main key expansion. + */ +static void aes_neon_key_expand( + const unsigned char *key, size_t key_words, + uint8x16_t *keysched_e, uint8x16_t *keysched_d) +{ + size_t rounds = key_words + 6; + size_t sched_words = (rounds + 1) * 4; + + /* + * Store the key schedule as 32-bit integers during expansion, so + * that it's easy to refer back to individual previous words. We + * collect them into the final uint8x16_t form at the end. + */ + uint32_t sched[MAXROUNDKEYS * 4]; + + unsigned rconpos = 0; + + for (size_t i = 0; i < sched_words; i++) { + if (i < key_words) { + sched[i] = GET_32BIT_LSB_FIRST(key + 4 * i); + } else { + uint32_t temp = sched[i - 1]; + + bool rotate_and_round_constant = (i % key_words == 0); + bool sub = rotate_and_round_constant || + (key_words == 8 && i % 8 == 4); + + if (rotate_and_round_constant) + temp = (temp << 24) | (temp >> 8); + + if (sub) { + uint32x4_t v32 = vdupq_n_u32(temp); + uint8x16_t v8 = vreinterpretq_u8_u32(v32); + v8 = vaeseq_u8(v8, vdupq_n_u8(0)); + v32 = vreinterpretq_u32_u8(v8); + temp = vget_lane_u32(vget_low_u32(v32), 0); + } + + if (rotate_and_round_constant) { + assert(rconpos < lenof(aes_key_setup_round_constants)); + temp ^= aes_key_setup_round_constants[rconpos++]; + } + + sched[i] = sched[i - key_words] ^ temp; + } + } + + /* + * Combine the key schedule words into uint8x16_t vectors and + * store them in the output context. + */ + for (size_t round = 0; round <= rounds; round++) + keysched_e[round] = vreinterpretq_u8_u32(vld1q_u32(sched + 4*round)); + + smemclr(sched, sizeof(sched)); + + /* + * Now prepare the modified keys for the inverse cipher. + */ + for (size_t eround = 0; eround <= rounds; eround++) { + size_t dround = rounds - eround; + uint8x16_t rkey = keysched_e[eround]; + if (eround && dround) /* neither first nor last */ + rkey = vaesimcq_u8(rkey); + keysched_d[dround] = rkey; + } +} + +/* + * Auxiliary routine to reverse the byte order of a vector, so that + * the SDCTR IV can be made big-endian for feeding to the cipher. + * + * In fact we don't need to reverse the vector _all_ the way; we leave + * the two lanes in MSW,LSW order, because that makes no difference to + * the efficiency of the increment. That way we only have to reverse + * bytes within each lane in this function. + */ +static inline uint8x16_t aes_neon_sdctr_reverse(uint8x16_t v) +{ + return vrev64q_u8(v); +} + +/* + * Auxiliary routine to increment the 128-bit counter used in SDCTR + * mode. There's no instruction to treat a 128-bit vector as a single + * long integer, so instead we have to increment the bottom half + * unconditionally, and the top half if the bottom half started off as + * all 1s (in which case there was about to be a carry). + */ +static inline uint8x16_t aes_neon_sdctr_increment(uint8x16_t in) +{ +#ifdef __aarch64__ + /* There will be a carry if the low 64 bits are all 1s. */ + uint64x1_t all1 = vcreate_u64(0xFFFFFFFFFFFFFFFF); + uint64x1_t carry = vceq_u64(vget_high_u64(vreinterpretq_u64_u8(in)), all1); + + /* Make a word whose bottom half is unconditionally all 1s, and + * the top half is 'carry', i.e. all 0s most of the time but all + * 1s if we need to increment the top half. Then that word is what + * we need to _subtract_ from the input counter. */ + uint64x2_t subtrahend = vcombine_u64(carry, all1); +#else + /* AArch32 doesn't have comparisons that operate on a 64-bit lane, + * so we start by comparing each 32-bit half of the low 64 bits + * _separately_ to all-1s. */ + uint32x2_t all1 = vdup_n_u32(0xFFFFFFFF); + uint32x2_t carry = vceq_u32( + vget_high_u32(vreinterpretq_u32_u8(in)), all1); + + /* Swap the 32-bit words of the compare output, and AND with the + * unswapped version. Now carry is all 1s iff the bottom half of + * the input counter was all 1s, and all 0s otherwise. */ + carry = vand_u32(carry, vrev64_u32(carry)); + + /* Now make the vector to subtract in the same way as above. */ + uint64x2_t subtrahend = vreinterpretq_u64_u32(vcombine_u32(carry, all1)); +#endif + + return vreinterpretq_u8_u64( + vsubq_u64(vreinterpretq_u64_u8(in), subtrahend)); +} + +/* + * The SSH interface and the cipher modes. + */ + +typedef struct aes_neon_context aes_neon_context; +struct aes_neon_context { + uint8x16_t keysched_e[MAXROUNDKEYS], keysched_d[MAXROUNDKEYS], iv; + + ssh_cipher ciph; +}; + +static ssh_cipher *aes_neon_new(const ssh_cipheralg *alg) +{ + const struct aes_extra *extra = (const struct aes_extra *)alg->extra; + if (!check_availability(extra)) + return NULL; + + aes_neon_context *ctx = snew(aes_neon_context); + ctx->ciph.vt = alg; + return &ctx->ciph; +} + +static void aes_neon_free(ssh_cipher *ciph) +{ + aes_neon_context *ctx = container_of(ciph, aes_neon_context, ciph); + smemclr(ctx, sizeof(*ctx)); + sfree(ctx); +} + +static void aes_neon_setkey(ssh_cipher *ciph, const void *vkey) +{ + aes_neon_context *ctx = container_of(ciph, aes_neon_context, ciph); + const unsigned char *key = (const unsigned char *)vkey; + + aes_neon_key_expand(key, ctx->ciph.vt->real_keybits / 32, + ctx->keysched_e, ctx->keysched_d); +} + +static void aes_neon_setiv_cbc(ssh_cipher *ciph, const void *iv) +{ + aes_neon_context *ctx = container_of(ciph, aes_neon_context, ciph); + ctx->iv = vld1q_u8(iv); +} + +static void aes_neon_setiv_sdctr(ssh_cipher *ciph, const void *iv) +{ + aes_neon_context *ctx = container_of(ciph, aes_neon_context, ciph); + uint8x16_t counter = vld1q_u8(iv); + ctx->iv = aes_neon_sdctr_reverse(counter); +} + +typedef uint8x16_t (*aes_neon_fn)(uint8x16_t v, const uint8x16_t *keysched); + +static inline void aes_cbc_neon_encrypt( + ssh_cipher *ciph, void *vblk, int blklen, aes_neon_fn encrypt) +{ + aes_neon_context *ctx = container_of(ciph, aes_neon_context, ciph); + + for (uint8_t *blk = (uint8_t *)vblk, *finish = blk + blklen; + blk < finish; blk += 16) { + uint8x16_t plaintext = vld1q_u8(blk); + uint8x16_t cipher_input = veorq_u8(plaintext, ctx->iv); + uint8x16_t ciphertext = encrypt(cipher_input, ctx->keysched_e); + vst1q_u8(blk, ciphertext); + ctx->iv = ciphertext; + } +} + +static inline void aes_cbc_neon_decrypt( + ssh_cipher *ciph, void *vblk, int blklen, aes_neon_fn decrypt) +{ + aes_neon_context *ctx = container_of(ciph, aes_neon_context, ciph); + + for (uint8_t *blk = (uint8_t *)vblk, *finish = blk + blklen; + blk < finish; blk += 16) { + uint8x16_t ciphertext = vld1q_u8(blk); + uint8x16_t decrypted = decrypt(ciphertext, ctx->keysched_d); + uint8x16_t plaintext = veorq_u8(decrypted, ctx->iv); + vst1q_u8(blk, plaintext); + ctx->iv = ciphertext; + } +} + +static inline void aes_sdctr_neon( + ssh_cipher *ciph, void *vblk, int blklen, aes_neon_fn encrypt) +{ + aes_neon_context *ctx = container_of(ciph, aes_neon_context, ciph); + + for (uint8_t *blk = (uint8_t *)vblk, *finish = blk + blklen; + blk < finish; blk += 16) { + uint8x16_t counter = aes_neon_sdctr_reverse(ctx->iv); + uint8x16_t keystream = encrypt(counter, ctx->keysched_e); + uint8x16_t input = vld1q_u8(blk); + uint8x16_t output = veorq_u8(input, keystream); + vst1q_u8(blk, output); + ctx->iv = aes_neon_sdctr_increment(ctx->iv); + } +} + +#define NEON_ENC_DEC(len) \ + static void aes##len##_neon_cbc_encrypt( \ + ssh_cipher *ciph, void *vblk, int blklen) \ + { aes_cbc_neon_encrypt(ciph, vblk, blklen, aes_neon_##len##_e); } \ + static void aes##len##_neon_cbc_decrypt( \ + ssh_cipher *ciph, void *vblk, int blklen) \ + { aes_cbc_neon_decrypt(ciph, vblk, blklen, aes_neon_##len##_d); } \ + static void aes##len##_neon_sdctr( \ + ssh_cipher *ciph, void *vblk, int blklen) \ + { aes_sdctr_neon(ciph, vblk, blklen, aes_neon_##len##_e); } \ + +NEON_ENC_DEC(128) +NEON_ENC_DEC(192) +NEON_ENC_DEC(256) + +AES_EXTRA(_neon); +AES_ALL_VTABLES(_neon, "NEON accelerated"); diff --git a/crypto/aes-ni.c b/crypto/aes-ni.c new file mode 100644 index 00000000..22348de4 --- /dev/null +++ b/crypto/aes-ni.c @@ -0,0 +1,281 @@ +/* + * Hardware-accelerated implementation of AES using x86 AES-NI. + */ + +#include "ssh.h" +#include "aes.h" + +#include +#include + +#if defined(__clang__) || defined(__GNUC__) +#include +#define GET_CPU_ID(out) __cpuid(1, (out)[0], (out)[1], (out)[2], (out)[3]) +#else +#define GET_CPU_ID(out) __cpuid(out, 1) +#endif + +static bool aes_ni_available(void) +{ + /* + * Determine if AES is available on this CPU, by checking that + * both AES itself and SSE4.1 are supported. + */ + unsigned int CPUInfo[4]; + GET_CPU_ID(CPUInfo); + return (CPUInfo[2] & (1 << 25)) && (CPUInfo[2] & (1 << 19)); +} + +/* + * Core AES-NI encrypt/decrypt functions, one per length and direction. + */ + +#define NI_CIPHER(len, dir, dirlong, repmacro) \ + static inline __m128i aes_ni_##len##_##dir( \ + __m128i v, const __m128i *keysched) \ + { \ + v = _mm_xor_si128(v, *keysched++); \ + repmacro(v = _mm_aes##dirlong##_si128(v, *keysched++);); \ + return _mm_aes##dirlong##last_si128(v, *keysched); \ + } + +NI_CIPHER(128, e, enc, REP9) +NI_CIPHER(128, d, dec, REP9) +NI_CIPHER(192, e, enc, REP11) +NI_CIPHER(192, d, dec, REP11) +NI_CIPHER(256, e, enc, REP13) +NI_CIPHER(256, d, dec, REP13) + +/* + * The main key expansion. + */ +static void aes_ni_key_expand( + const unsigned char *key, size_t key_words, + __m128i *keysched_e, __m128i *keysched_d) +{ + size_t rounds = key_words + 6; + size_t sched_words = (rounds + 1) * 4; + + /* + * Store the key schedule as 32-bit integers during expansion, so + * that it's easy to refer back to individual previous words. We + * collect them into the final __m128i form at the end. + */ + uint32_t sched[MAXROUNDKEYS * 4]; + + unsigned rconpos = 0; + + for (size_t i = 0; i < sched_words; i++) { + if (i < key_words) { + sched[i] = GET_32BIT_LSB_FIRST(key + 4 * i); + } else { + uint32_t temp = sched[i - 1]; + + bool rotate_and_round_constant = (i % key_words == 0); + bool only_sub = (key_words == 8 && i % 8 == 4); + + if (rotate_and_round_constant) { + __m128i v = _mm_setr_epi32(0,temp,0,0); + v = _mm_aeskeygenassist_si128(v, 0); + temp = _mm_extract_epi32(v, 1); + + assert(rconpos < lenof(aes_key_setup_round_constants)); + temp ^= aes_key_setup_round_constants[rconpos++]; + } else if (only_sub) { + __m128i v = _mm_setr_epi32(0,temp,0,0); + v = _mm_aeskeygenassist_si128(v, 0); + temp = _mm_extract_epi32(v, 0); + } + + sched[i] = sched[i - key_words] ^ temp; + } + } + + /* + * Combine the key schedule words into __m128i vectors and store + * them in the output context. + */ + for (size_t round = 0; round <= rounds; round++) + keysched_e[round] = _mm_setr_epi32( + sched[4*round ], sched[4*round+1], + sched[4*round+2], sched[4*round+3]); + + smemclr(sched, sizeof(sched)); + + /* + * Now prepare the modified keys for the inverse cipher. + */ + for (size_t eround = 0; eround <= rounds; eround++) { + size_t dround = rounds - eround; + __m128i rkey = keysched_e[eround]; + if (eround && dround) /* neither first nor last */ + rkey = _mm_aesimc_si128(rkey); + keysched_d[dround] = rkey; + } +} + +/* + * Auxiliary routine to increment the 128-bit counter used in SDCTR + * mode. + */ +static inline __m128i aes_ni_sdctr_increment(__m128i v) +{ + const __m128i ONE = _mm_setr_epi32(1,0,0,0); + const __m128i ZERO = _mm_setzero_si128(); + + /* Increment the low-order 64 bits of v */ + v = _mm_add_epi64(v, ONE); + /* Check if they've become zero */ + __m128i cmp = _mm_cmpeq_epi64(v, ZERO); + /* If so, the low half of cmp is all 1s. Pack that into the high + * half of addend with zero in the low half. */ + __m128i addend = _mm_unpacklo_epi64(ZERO, cmp); + /* And subtract that from v, which increments the high 64 bits iff + * the low 64 wrapped round. */ + v = _mm_sub_epi64(v, addend); + + return v; +} + +/* + * Auxiliary routine to reverse the byte order of a vector, so that + * the SDCTR IV can be made big-endian for feeding to the cipher. + */ +static inline __m128i aes_ni_sdctr_reverse(__m128i v) +{ + v = _mm_shuffle_epi8( + v, _mm_setr_epi8(15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0)); + return v; +} + +/* + * The SSH interface and the cipher modes. + */ + +typedef struct aes_ni_context aes_ni_context; +struct aes_ni_context { + __m128i keysched_e[MAXROUNDKEYS], keysched_d[MAXROUNDKEYS], iv; + + void *pointer_to_free; + ssh_cipher ciph; +}; + +static ssh_cipher *aes_ni_new(const ssh_cipheralg *alg) +{ + const struct aes_extra *extra = (const struct aes_extra *)alg->extra; + if (!check_availability(extra)) + return NULL; + + /* + * The __m128i variables in the context structure need to be + * 16-byte aligned, but not all malloc implementations that this + * code has to work with will guarantee to return a 16-byte + * aligned pointer. So we over-allocate, manually realign the + * pointer ourselves, and store the original one inside the + * context so we know how to free it later. + */ + void *allocation = smalloc(sizeof(aes_ni_context) + 15); + uintptr_t alloc_address = (uintptr_t)allocation; + uintptr_t aligned_address = (alloc_address + 15) & ~15; + aes_ni_context *ctx = (aes_ni_context *)aligned_address; + + ctx->ciph.vt = alg; + ctx->pointer_to_free = allocation; + return &ctx->ciph; +} + +static void aes_ni_free(ssh_cipher *ciph) +{ + aes_ni_context *ctx = container_of(ciph, aes_ni_context, ciph); + void *allocation = ctx->pointer_to_free; + smemclr(ctx, sizeof(*ctx)); + sfree(allocation); +} + +static void aes_ni_setkey(ssh_cipher *ciph, const void *vkey) +{ + aes_ni_context *ctx = container_of(ciph, aes_ni_context, ciph); + const unsigned char *key = (const unsigned char *)vkey; + + aes_ni_key_expand(key, ctx->ciph.vt->real_keybits / 32, + ctx->keysched_e, ctx->keysched_d); +} + +static void aes_ni_setiv_cbc(ssh_cipher *ciph, const void *iv) +{ + aes_ni_context *ctx = container_of(ciph, aes_ni_context, ciph); + ctx->iv = _mm_loadu_si128(iv); +} + +static void aes_ni_setiv_sdctr(ssh_cipher *ciph, const void *iv) +{ + aes_ni_context *ctx = container_of(ciph, aes_ni_context, ciph); + __m128i counter = _mm_loadu_si128(iv); + ctx->iv = aes_ni_sdctr_reverse(counter); +} + +typedef __m128i (*aes_ni_fn)(__m128i v, const __m128i *keysched); + +static inline void aes_cbc_ni_encrypt( + ssh_cipher *ciph, void *vblk, int blklen, aes_ni_fn encrypt) +{ + aes_ni_context *ctx = container_of(ciph, aes_ni_context, ciph); + + for (uint8_t *blk = (uint8_t *)vblk, *finish = blk + blklen; + blk < finish; blk += 16) { + __m128i plaintext = _mm_loadu_si128((const __m128i *)blk); + __m128i cipher_input = _mm_xor_si128(plaintext, ctx->iv); + __m128i ciphertext = encrypt(cipher_input, ctx->keysched_e); + _mm_storeu_si128((__m128i *)blk, ciphertext); + ctx->iv = ciphertext; + } +} + +static inline void aes_cbc_ni_decrypt( + ssh_cipher *ciph, void *vblk, int blklen, aes_ni_fn decrypt) +{ + aes_ni_context *ctx = container_of(ciph, aes_ni_context, ciph); + + for (uint8_t *blk = (uint8_t *)vblk, *finish = blk + blklen; + blk < finish; blk += 16) { + __m128i ciphertext = _mm_loadu_si128((const __m128i *)blk); + __m128i decrypted = decrypt(ciphertext, ctx->keysched_d); + __m128i plaintext = _mm_xor_si128(decrypted, ctx->iv); + _mm_storeu_si128((__m128i *)blk, plaintext); + ctx->iv = ciphertext; + } +} + +static inline void aes_sdctr_ni( + ssh_cipher *ciph, void *vblk, int blklen, aes_ni_fn encrypt) +{ + aes_ni_context *ctx = container_of(ciph, aes_ni_context, ciph); + + for (uint8_t *blk = (uint8_t *)vblk, *finish = blk + blklen; + blk < finish; blk += 16) { + __m128i counter = aes_ni_sdctr_reverse(ctx->iv); + __m128i keystream = encrypt(counter, ctx->keysched_e); + __m128i input = _mm_loadu_si128((const __m128i *)blk); + __m128i output = _mm_xor_si128(input, keystream); + _mm_storeu_si128((__m128i *)blk, output); + ctx->iv = aes_ni_sdctr_increment(ctx->iv); + } +} + +#define NI_ENC_DEC(len) \ + static void aes##len##_ni_cbc_encrypt( \ + ssh_cipher *ciph, void *vblk, int blklen) \ + { aes_cbc_ni_encrypt(ciph, vblk, blklen, aes_ni_##len##_e); } \ + static void aes##len##_ni_cbc_decrypt( \ + ssh_cipher *ciph, void *vblk, int blklen) \ + { aes_cbc_ni_decrypt(ciph, vblk, blklen, aes_ni_##len##_d); } \ + static void aes##len##_ni_sdctr( \ + ssh_cipher *ciph, void *vblk, int blklen) \ + { aes_sdctr_ni(ciph, vblk, blklen, aes_ni_##len##_e); } \ + +NI_ENC_DEC(128) +NI_ENC_DEC(192) +NI_ENC_DEC(256) + +AES_EXTRA(_ni); +AES_ALL_VTABLES(_ni, "AES-NI accelerated"); diff --git a/crypto/aes-select.c b/crypto/aes-select.c new file mode 100644 index 00000000..f0c5031f --- /dev/null +++ b/crypto/aes-select.c @@ -0,0 +1,89 @@ +/* + * Top-level vtables to select an AES implementation. + */ + +#include +#include + +#include "putty.h" +#include "ssh.h" +#include "aes.h" + +static ssh_cipher *aes_select(const ssh_cipheralg *alg) +{ + const ssh_cipheralg *const *real_algs = (const ssh_cipheralg **)alg->extra; + + for (size_t i = 0; real_algs[i]; i++) { + const ssh_cipheralg *alg = real_algs[i]; + const struct aes_extra *alg_extra = + (const struct aes_extra *)alg->extra; + if (check_availability(alg_extra)) + return ssh_cipher_new(alg); + } + + /* We should never reach the NULL at the end of the list, because + * the last non-NULL entry should be software-only AES, which is + * always available. */ + unreachable("aes_select ran off the end of its list"); +} + +#if HAVE_AES_NI +#define IF_NI(...) __VA_ARGS__ +#else +#define IF_NI(...) +#endif + +#if HAVE_NEON_CRYPTO +#define IF_NEON(...) __VA_ARGS__ +#else +#define IF_NEON(...) +#endif + +#define AES_SELECTOR_VTABLE(mode_c, mode_protocol, mode_display, bits) \ + static const ssh_cipheralg * \ + ssh_aes ## bits ## _ ## mode_c ## _impls[] = { \ + IF_NI(&ssh_aes ## bits ## _ ## mode_c ## _ni,) \ + IF_NEON(&ssh_aes ## bits ## _ ## mode_c ## _neon,) \ + &ssh_aes ## bits ## _ ## mode_c ## _sw, \ + NULL, \ + }; \ + const ssh_cipheralg ssh_aes ## bits ## _ ## mode_c = { \ + .new = aes_select, \ + .ssh2_id = "aes" #bits "-" mode_protocol, \ + .blksize = 16, \ + .real_keybits = bits, \ + .padded_keybytes = bits/8, \ + .text_name = "AES-" #bits " " mode_display \ + " (dummy selector vtable)", \ + .extra = ssh_aes ## bits ## _ ## mode_c ## _impls, \ + } + +AES_SELECTOR_VTABLE(cbc, "cbc", "CBC", 128); +AES_SELECTOR_VTABLE(cbc, "cbc", "CBC", 192); +AES_SELECTOR_VTABLE(cbc, "cbc", "CBC", 256); +AES_SELECTOR_VTABLE(sdctr, "ctr", "SDCTR", 128); +AES_SELECTOR_VTABLE(sdctr, "ctr", "SDCTR", 192); +AES_SELECTOR_VTABLE(sdctr, "ctr", "SDCTR", 256); + +static const ssh_cipheralg ssh_rijndael_lysator = { + /* Same as aes256_cbc, but with a different protocol ID */ + .new = aes_select, + .ssh2_id = "rijndael-cbc@lysator.liu.se", + .blksize = 16, + .real_keybits = 256, + .padded_keybytes = 256/8, + .text_name = "AES-256 CBC (dummy selector vtable)", + .extra = ssh_aes256_cbc_impls, +}; + +static const ssh_cipheralg *const aes_list[] = { + &ssh_aes256_sdctr, + &ssh_aes256_cbc, + &ssh_rijndael_lysator, + &ssh_aes192_sdctr, + &ssh_aes192_cbc, + &ssh_aes128_sdctr, + &ssh_aes128_cbc, +}; + +const ssh2_ciphers ssh2_aes = { lenof(aes_list), aes_list }; diff --git a/crypto/aes-sw.c b/crypto/aes-sw.c new file mode 100644 index 00000000..f8512388 --- /dev/null +++ b/crypto/aes-sw.c @@ -0,0 +1,1040 @@ +/* + * Software implementation of AES. + * + * This implementation uses a bit-sliced representation. Instead of + * the obvious approach of storing the cipher state so that each byte + * (or field element, or entry in the cipher matrix) occupies 8 + * contiguous bits in a machine integer somewhere, we organise the + * cipher state as an array of 8 integers, in such a way that each + * logical byte of the cipher state occupies one bit in each integer, + * all at the same position. This allows us to do parallel logic on + * all bytes of the state by doing bitwise operations between the 8 + * integers; in particular, the S-box (SubBytes) lookup is done this + * way, which takes about 110 operations - but for those 110 bitwise + * ops you get 64 S-box lookups, not just one. + */ + +#include "ssh.h" +#include "aes.h" +#include "mpint_i.h" /* we reuse the BignumInt system */ + +static bool aes_sw_available(void) +{ + /* Software AES is always available */ + return true; +} + +#define SLICE_PARALLELISM (BIGNUM_INT_BYTES / 2) + +#ifdef BITSLICED_DEBUG +/* Dump function that undoes the bitslicing transform, so you can see + * the logical data represented by a set of slice words. */ +static inline void dumpslices_uint16_t( + const char *prefix, const uint16_t slices[8]) +{ + printf("%-30s", prefix); + for (unsigned byte = 0; byte < 16; byte++) { + unsigned byteval = 0; + for (unsigned bit = 0; bit < 8; bit++) + byteval |= (1 & (slices[bit] >> byte)) << bit; + printf("%02x", byteval); + } + printf("\n"); +} + +static inline void dumpslices_BignumInt( + const char *prefix, const BignumInt slices[8]) +{ + printf("%-30s", prefix); + for (unsigned iter = 0; iter < SLICE_PARALLELISM; iter++) { + for (unsigned byte = 0; byte < 16; byte++) { + unsigned byteval = 0; + for (unsigned bit = 0; bit < 8; bit++) + byteval |= (1 & (slices[bit] >> (iter*16+byte))) << bit; + printf("%02x", byteval); + } + if (iter+1 < SLICE_PARALLELISM) + printf(" "); + } + printf("\n"); +} +#else +#define dumpslices_uintN_t(prefix, slices) ((void)0) +#define dumpslices_BignumInt(prefix, slices) ((void)0) +#endif + +/* ----- + * Bit-slicing transformation: convert between an array of 16 uint8_t + * and an array of 8 uint16_t, so as to interchange the bit index + * within each element and the element index within the array. (That + * is, bit j of input[i] == bit i of output[j]. + */ + +#define SWAPWORDS(shift) do \ + { \ + uint64_t mask = ~(uint64_t)0 / ((1ULL << shift) + 1); \ + uint64_t diff = ((i0 >> shift) ^ i1) & mask; \ + i0 ^= diff << shift; \ + i1 ^= diff; \ + } while (0) + +#define SWAPINWORD(i, bigshift, smallshift) do \ + { \ + uint64_t mask = ~(uint64_t)0; \ + mask /= ((1ULL << bigshift) + 1); \ + mask /= ((1ULL << smallshift) + 1); \ + mask <<= smallshift; \ + unsigned shift = bigshift - smallshift; \ + uint64_t diff = ((i >> shift) ^ i) & mask; \ + i ^= diff ^ (diff << shift); \ + } while (0) + +#define TO_BITSLICES(slices, bytes, uintN_t, assign_op, shift) do \ + { \ + uint64_t i0 = GET_64BIT_LSB_FIRST(bytes); \ + uint64_t i1 = GET_64BIT_LSB_FIRST(bytes + 8); \ + SWAPINWORD(i0, 8, 1); \ + SWAPINWORD(i1, 8, 1); \ + SWAPINWORD(i0, 16, 2); \ + SWAPINWORD(i1, 16, 2); \ + SWAPINWORD(i0, 32, 4); \ + SWAPINWORD(i1, 32, 4); \ + SWAPWORDS(8); \ + slices[0] assign_op (uintN_t)((i0 >> 0) & 0xFFFF) << (shift); \ + slices[2] assign_op (uintN_t)((i0 >> 16) & 0xFFFF) << (shift); \ + slices[4] assign_op (uintN_t)((i0 >> 32) & 0xFFFF) << (shift); \ + slices[6] assign_op (uintN_t)((i0 >> 48) & 0xFFFF) << (shift); \ + slices[1] assign_op (uintN_t)((i1 >> 0) & 0xFFFF) << (shift); \ + slices[3] assign_op (uintN_t)((i1 >> 16) & 0xFFFF) << (shift); \ + slices[5] assign_op (uintN_t)((i1 >> 32) & 0xFFFF) << (shift); \ + slices[7] assign_op (uintN_t)((i1 >> 48) & 0xFFFF) << (shift); \ + } while (0) + +#define FROM_BITSLICES(bytes, slices, shift) do \ + { \ + uint64_t i1 = ((slices[7] >> (shift)) & 0xFFFF); \ + i1 = (i1 << 16) | ((slices[5] >> (shift)) & 0xFFFF); \ + i1 = (i1 << 16) | ((slices[3] >> (shift)) & 0xFFFF); \ + i1 = (i1 << 16) | ((slices[1] >> (shift)) & 0xFFFF); \ + uint64_t i0 = ((slices[6] >> (shift)) & 0xFFFF); \ + i0 = (i0 << 16) | ((slices[4] >> (shift)) & 0xFFFF); \ + i0 = (i0 << 16) | ((slices[2] >> (shift)) & 0xFFFF); \ + i0 = (i0 << 16) | ((slices[0] >> (shift)) & 0xFFFF); \ + SWAPWORDS(8); \ + SWAPINWORD(i0, 32, 4); \ + SWAPINWORD(i1, 32, 4); \ + SWAPINWORD(i0, 16, 2); \ + SWAPINWORD(i1, 16, 2); \ + SWAPINWORD(i0, 8, 1); \ + SWAPINWORD(i1, 8, 1); \ + PUT_64BIT_LSB_FIRST(bytes, i0); \ + PUT_64BIT_LSB_FIRST((bytes) + 8, i1); \ + } while (0) + +/* ----- + * Some macros that will be useful repeatedly. + */ + +/* Iterate a unary transformation over all 8 slices. */ +#define ITERATE(MACRO, output, input, uintN_t) do \ + { \ + MACRO(output[0], input[0], uintN_t); \ + MACRO(output[1], input[1], uintN_t); \ + MACRO(output[2], input[2], uintN_t); \ + MACRO(output[3], input[3], uintN_t); \ + MACRO(output[4], input[4], uintN_t); \ + MACRO(output[5], input[5], uintN_t); \ + MACRO(output[6], input[6], uintN_t); \ + MACRO(output[7], input[7], uintN_t); \ + } while (0) + +/* Simply add (i.e. XOR) two whole sets of slices together. */ +#define BITSLICED_ADD(output, lhs, rhs) do \ + { \ + output[0] = lhs[0] ^ rhs[0]; \ + output[1] = lhs[1] ^ rhs[1]; \ + output[2] = lhs[2] ^ rhs[2]; \ + output[3] = lhs[3] ^ rhs[3]; \ + output[4] = lhs[4] ^ rhs[4]; \ + output[5] = lhs[5] ^ rhs[5]; \ + output[6] = lhs[6] ^ rhs[6]; \ + output[7] = lhs[7] ^ rhs[7]; \ + } while (0) + +/* ----- + * The AES S-box, in pure bitwise logic so that it can be run in + * parallel on whole words full of bit-sliced field elements. + * + * Source: 'A new combinational logic minimization technique with + * applications to cryptology', https://eprint.iacr.org/2009/191 + * + * As a minor speed optimisation, I use a modified version of the + * S-box which omits the additive constant 0x63, i.e. this S-box + * consists of only the field inversion and linear map components. + * Instead, the addition of the constant is deferred until after the + * subsequent ShiftRows and MixColumns stages, so that it happens at + * the same time as adding the next round key - and then we just make + * it _part_ of the round key, so it doesn't cost any extra + * instructions to add. + * + * (Obviously adding a constant to each byte commutes with ShiftRows, + * which only permutes the bytes. It also commutes with MixColumns: + * that's not quite so obvious, but since the effect of MixColumns is + * to multiply a constant polynomial M into each column, it is obvious + * that adding some polynomial K and then multiplying by M is + * equivalent to multiplying by M and then adding the product KM. And + * in fact, since the coefficients of M happen to sum to 1, it turns + * out that KM = K, so we don't even have to change the constant when + * we move it to the far side of MixColumns.) + * + * Of course, one knock-on effect of this is that the use of the S-box + * *during* key setup has to be corrected by manually adding on the + * constant afterwards! + */ + +/* Initial linear transformation for the forward S-box, from Fig 2 of + * the paper. */ +#define SBOX_FORWARD_TOP_TRANSFORM(input, uintN_t) \ + uintN_t y14 = input[4] ^ input[2]; \ + uintN_t y13 = input[7] ^ input[1]; \ + uintN_t y9 = input[7] ^ input[4]; \ + uintN_t y8 = input[7] ^ input[2]; \ + uintN_t t0 = input[6] ^ input[5]; \ + uintN_t y1 = t0 ^ input[0]; \ + uintN_t y4 = y1 ^ input[4]; \ + uintN_t y12 = y13 ^ y14; \ + uintN_t y2 = y1 ^ input[7]; \ + uintN_t y5 = y1 ^ input[1]; \ + uintN_t y3 = y5 ^ y8; \ + uintN_t t1 = input[3] ^ y12; \ + uintN_t y15 = t1 ^ input[2]; \ + uintN_t y20 = t1 ^ input[6]; \ + uintN_t y6 = y15 ^ input[0]; \ + uintN_t y10 = y15 ^ t0; \ + uintN_t y11 = y20 ^ y9; \ + uintN_t y7 = input[0] ^ y11; \ + uintN_t y17 = y10 ^ y11; \ + uintN_t y19 = y10 ^ y8; \ + uintN_t y16 = t0 ^ y11; \ + uintN_t y21 = y13 ^ y16; \ + uintN_t y18 = input[7] ^ y16; \ + /* Make a copy of input[0] under a new name, because the core + * will refer to it, and in the inverse version of the S-box + * the corresponding value will be one of the calculated ones + * and not in input[0] itself. */ \ + uintN_t i0 = input[0]; \ + /* end */ + +/* Core nonlinear component, from Fig 3 of the paper. */ +#define SBOX_CORE(uintN_t) \ + uintN_t t2 = y12 & y15; \ + uintN_t t3 = y3 & y6; \ + uintN_t t4 = t3 ^ t2; \ + uintN_t t5 = y4 & i0; \ + uintN_t t6 = t5 ^ t2; \ + uintN_t t7 = y13 & y16; \ + uintN_t t8 = y5 & y1; \ + uintN_t t9 = t8 ^ t7; \ + uintN_t t10 = y2 & y7; \ + uintN_t t11 = t10 ^ t7; \ + uintN_t t12 = y9 & y11; \ + uintN_t t13 = y14 & y17; \ + uintN_t t14 = t13 ^ t12; \ + uintN_t t15 = y8 & y10; \ + uintN_t t16 = t15 ^ t12; \ + uintN_t t17 = t4 ^ t14; \ + uintN_t t18 = t6 ^ t16; \ + uintN_t t19 = t9 ^ t14; \ + uintN_t t20 = t11 ^ t16; \ + uintN_t t21 = t17 ^ y20; \ + uintN_t t22 = t18 ^ y19; \ + uintN_t t23 = t19 ^ y21; \ + uintN_t t24 = t20 ^ y18; \ + uintN_t t25 = t21 ^ t22; \ + uintN_t t26 = t21 & t23; \ + uintN_t t27 = t24 ^ t26; \ + uintN_t t28 = t25 & t27; \ + uintN_t t29 = t28 ^ t22; \ + uintN_t t30 = t23 ^ t24; \ + uintN_t t31 = t22 ^ t26; \ + uintN_t t32 = t31 & t30; \ + uintN_t t33 = t32 ^ t24; \ + uintN_t t34 = t23 ^ t33; \ + uintN_t t35 = t27 ^ t33; \ + uintN_t t36 = t24 & t35; \ + uintN_t t37 = t36 ^ t34; \ + uintN_t t38 = t27 ^ t36; \ + uintN_t t39 = t29 & t38; \ + uintN_t t40 = t25 ^ t39; \ + uintN_t t41 = t40 ^ t37; \ + uintN_t t42 = t29 ^ t33; \ + uintN_t t43 = t29 ^ t40; \ + uintN_t t44 = t33 ^ t37; \ + uintN_t t45 = t42 ^ t41; \ + uintN_t z0 = t44 & y15; \ + uintN_t z1 = t37 & y6; \ + uintN_t z2 = t33 & i0; \ + uintN_t z3 = t43 & y16; \ + uintN_t z4 = t40 & y1; \ + uintN_t z5 = t29 & y7; \ + uintN_t z6 = t42 & y11; \ + uintN_t z7 = t45 & y17; \ + uintN_t z8 = t41 & y10; \ + uintN_t z9 = t44 & y12; \ + uintN_t z10 = t37 & y3; \ + uintN_t z11 = t33 & y4; \ + uintN_t z12 = t43 & y13; \ + uintN_t z13 = t40 & y5; \ + uintN_t z14 = t29 & y2; \ + uintN_t z15 = t42 & y9; \ + uintN_t z16 = t45 & y14; \ + uintN_t z17 = t41 & y8; \ + /* end */ + +/* Final linear transformation for the forward S-box, from Fig 4 of + * the paper. */ +#define SBOX_FORWARD_BOTTOM_TRANSFORM(output, uintN_t) \ + uintN_t t46 = z15 ^ z16; \ + uintN_t t47 = z10 ^ z11; \ + uintN_t t48 = z5 ^ z13; \ + uintN_t t49 = z9 ^ z10; \ + uintN_t t50 = z2 ^ z12; \ + uintN_t t51 = z2 ^ z5; \ + uintN_t t52 = z7 ^ z8; \ + uintN_t t53 = z0 ^ z3; \ + uintN_t t54 = z6 ^ z7; \ + uintN_t t55 = z16 ^ z17; \ + uintN_t t56 = z12 ^ t48; \ + uintN_t t57 = t50 ^ t53; \ + uintN_t t58 = z4 ^ t46; \ + uintN_t t59 = z3 ^ t54; \ + uintN_t t60 = t46 ^ t57; \ + uintN_t t61 = z14 ^ t57; \ + uintN_t t62 = t52 ^ t58; \ + uintN_t t63 = t49 ^ t58; \ + uintN_t t64 = z4 ^ t59; \ + uintN_t t65 = t61 ^ t62; \ + uintN_t t66 = z1 ^ t63; \ + output[7] = t59 ^ t63; \ + output[1] = t56 ^ t62; \ + output[0] = t48 ^ t60; \ + uintN_t t67 = t64 ^ t65; \ + output[4] = t53 ^ t66; \ + output[3] = t51 ^ t66; \ + output[2] = t47 ^ t65; \ + output[6] = t64 ^ output[4]; \ + output[5] = t55 ^ t67; \ + /* end */ + +#define BITSLICED_SUBBYTES(output, input, uintN_t) do { \ + SBOX_FORWARD_TOP_TRANSFORM(input, uintN_t); \ + SBOX_CORE(uintN_t); \ + SBOX_FORWARD_BOTTOM_TRANSFORM(output, uintN_t); \ + } while (0) + +/* + * Initial and final linear transformations for the backward S-box. I + * generated these myself, by implementing the linear-transform + * optimisation algorithm in the paper, and applying it to the + * matrices calculated by _their_ top and bottom transformations, pre- + * and post-multiplied as appropriate by the linear map in the inverse + * S_box. + */ +#define SBOX_BACKWARD_TOP_TRANSFORM(input, uintN_t) \ + uintN_t y5 = input[4] ^ input[6]; \ + uintN_t y19 = input[3] ^ input[0]; \ + uintN_t itmp8 = y5 ^ input[0]; \ + uintN_t y4 = itmp8 ^ input[1]; \ + uintN_t y9 = input[4] ^ input[3]; \ + uintN_t y2 = y9 ^ y4; \ + uintN_t itmp9 = y2 ^ input[7]; \ + uintN_t y1 = y9 ^ input[0]; \ + uintN_t y6 = y5 ^ input[7]; \ + uintN_t y18 = y9 ^ input[5]; \ + uintN_t y7 = y18 ^ y2; \ + uintN_t y16 = y7 ^ y1; \ + uintN_t y21 = y7 ^ input[1]; \ + uintN_t y3 = input[4] ^ input[7]; \ + uintN_t y13 = y16 ^ y21; \ + uintN_t y8 = input[4] ^ y6; \ + uintN_t y10 = y8 ^ y19; \ + uintN_t y14 = y8 ^ y9; \ + uintN_t y20 = itmp9 ^ input[2]; \ + uintN_t y11 = y9 ^ y20; \ + uintN_t i0 = y11 ^ y7; \ + uintN_t y15 = i0 ^ y6; \ + uintN_t y17 = y16 ^ y15; \ + uintN_t y12 = itmp9 ^ input[3]; \ + /* end */ +#define SBOX_BACKWARD_BOTTOM_TRANSFORM(output, uintN_t) \ + uintN_t otmp18 = z15 ^ z6; \ + uintN_t otmp19 = z13 ^ otmp18; \ + uintN_t otmp20 = z12 ^ otmp19; \ + uintN_t otmp21 = z16 ^ otmp20; \ + uintN_t otmp22 = z8 ^ otmp21; \ + uintN_t otmp23 = z0 ^ otmp22; \ + uintN_t otmp24 = otmp22 ^ z3; \ + uintN_t otmp25 = otmp24 ^ z4; \ + uintN_t otmp26 = otmp25 ^ z2; \ + uintN_t otmp27 = z1 ^ otmp26; \ + uintN_t otmp28 = z14 ^ otmp27; \ + uintN_t otmp29 = otmp28 ^ z10; \ + output[4] = z2 ^ otmp23; \ + output[7] = z5 ^ otmp24; \ + uintN_t otmp30 = z11 ^ otmp29; \ + output[5] = z13 ^ otmp30; \ + uintN_t otmp31 = otmp25 ^ z8; \ + output[1] = z7 ^ otmp31; \ + uintN_t otmp32 = z11 ^ z9; \ + uintN_t otmp33 = z17 ^ otmp32; \ + uintN_t otmp34 = otmp30 ^ otmp33; \ + output[0] = z15 ^ otmp33; \ + uintN_t otmp35 = z12 ^ otmp34; \ + output[6] = otmp35 ^ z16; \ + uintN_t otmp36 = z1 ^ otmp23; \ + uintN_t otmp37 = z5 ^ otmp36; \ + output[2] = z4 ^ otmp37; \ + uintN_t otmp38 = z11 ^ output[1]; \ + uintN_t otmp39 = z2 ^ otmp38; \ + uintN_t otmp40 = z17 ^ otmp39; \ + uintN_t otmp41 = z0 ^ otmp40; \ + uintN_t otmp42 = z5 ^ otmp41; \ + uintN_t otmp43 = otmp42 ^ z10; \ + uintN_t otmp44 = otmp43 ^ z3; \ + output[3] = otmp44 ^ z16; \ + /* end */ + +#define BITSLICED_INVSUBBYTES(output, input, uintN_t) do { \ + SBOX_BACKWARD_TOP_TRANSFORM(input, uintN_t); \ + SBOX_CORE(uintN_t); \ + SBOX_BACKWARD_BOTTOM_TRANSFORM(output, uintN_t); \ + } while (0) + + +/* ----- + * The ShiftRows transformation. This operates independently on each + * bit slice. + */ + +#define SINGLE_BITSLICE_SHIFTROWS(output, input, uintN_t) do \ + { \ + uintN_t mask, mask2, mask3, diff, x = (input); \ + /* Rotate rows 2 and 3 by 16 bits */ \ + mask = 0x00CC * (((uintN_t)~(uintN_t)0) / 0xFFFF); \ + diff = ((x >> 8) ^ x) & mask; \ + x ^= diff ^ (diff << 8); \ + /* Rotate rows 1 and 3 by 8 bits */ \ + mask = 0x0AAA * (((uintN_t)~(uintN_t)0) / 0xFFFF); \ + mask2 = 0xA000 * (((uintN_t)~(uintN_t)0) / 0xFFFF); \ + mask3 = 0x5555 * (((uintN_t)~(uintN_t)0) / 0xFFFF); \ + x = ((x >> 4) & mask) | ((x << 12) & mask2) | (x & mask3); \ + /* Write output */ \ + (output) = x; \ + } while (0) + +#define SINGLE_BITSLICE_INVSHIFTROWS(output, input, uintN_t) do \ + { \ + uintN_t mask, mask2, mask3, diff, x = (input); \ + /* Rotate rows 2 and 3 by 16 bits */ \ + mask = 0x00CC * (((uintN_t)~(uintN_t)0) / 0xFFFF); \ + diff = ((x >> 8) ^ x) & mask; \ + x ^= diff ^ (diff << 8); \ + /* Rotate rows 1 and 3 by 8 bits, the opposite way to ShiftRows */ \ + mask = 0x000A * (((uintN_t)~(uintN_t)0) / 0xFFFF); \ + mask2 = 0xAAA0 * (((uintN_t)~(uintN_t)0) / 0xFFFF); \ + mask3 = 0x5555 * (((uintN_t)~(uintN_t)0) / 0xFFFF); \ + x = ((x >> 12) & mask) | ((x << 4) & mask2) | (x & mask3); \ + /* Write output */ \ + (output) = x; \ + } while (0) + +#define BITSLICED_SHIFTROWS(output, input, uintN_t) do \ + { \ + ITERATE(SINGLE_BITSLICE_SHIFTROWS, output, input, uintN_t); \ + } while (0) + +#define BITSLICED_INVSHIFTROWS(output, input, uintN_t) do \ + { \ + ITERATE(SINGLE_BITSLICE_INVSHIFTROWS, output, input, uintN_t); \ + } while (0) + +/* ----- + * The MixColumns transformation. This has to operate on all eight bit + * slices at once, and also passes data back and forth between the + * bits in an adjacent group of 4 within each slice. + * + * Notation: let F = GF(2)[X]/ be the finite field + * used in AES, and let R = F[Y]/ be the ring whose elements + * represent the possible contents of a column of the matrix. I use X + * and Y below in those senses, i.e. X is the value in F that + * represents the byte 0x02, and Y is the value in R that cycles the + * four bytes around by one if you multiply by it. + */ + +/* Multiply every column by Y^3, i.e. cycle it round one place to the + * right. Operates on one bit slice at a time; you have to wrap it in + * ITERATE to affect all the data at once. */ +#define BITSLICED_MUL_BY_Y3(output, input, uintN_t) do \ + { \ + uintN_t mask, mask2, x; \ + mask = 0x8 * (((uintN_t)~(uintN_t)0) / 0xF); \ + mask2 = 0x7 * (((uintN_t)~(uintN_t)0) / 0xF); \ + x = input; \ + output = ((x << 3) & mask) ^ ((x >> 1) & mask2); \ + } while (0) + +/* Multiply every column by Y^2. */ +#define BITSLICED_MUL_BY_Y2(output, input, uintN_t) do \ + { \ + uintN_t mask, mask2, x; \ + mask = 0xC * (((uintN_t)~(uintN_t)0) / 0xF); \ + mask2 = 0x3 * (((uintN_t)~(uintN_t)0) / 0xF); \ + x = input; \ + output = ((x << 2) & mask) ^ ((x >> 2) & mask2); \ + } while (0) + +#define BITSLICED_MUL_BY_1_Y3(output, input, uintN_t) do \ + { \ + uintN_t tmp = input; \ + BITSLICED_MUL_BY_Y3(tmp, input, uintN_t); \ + output = input ^ tmp; \ + } while (0) + +/* Multiply every column by 1+Y^2. */ +#define BITSLICED_MUL_BY_1_Y2(output, input, uintN_t) do \ + { \ + uintN_t tmp = input; \ + BITSLICED_MUL_BY_Y2(tmp, input, uintN_t); \ + output = input ^ tmp; \ + } while (0) + +/* Multiply every field element by X. This has to feed data between + * slices, so it does the whole job in one go without needing ITERATE. */ +#define BITSLICED_MUL_BY_X(output, input, uintN_t) do \ + { \ + uintN_t bit7 = input[7]; \ + output[7] = input[6]; \ + output[6] = input[5]; \ + output[5] = input[4]; \ + output[4] = input[3] ^ bit7; \ + output[3] = input[2] ^ bit7; \ + output[2] = input[1]; \ + output[1] = input[0] ^ bit7; \ + output[0] = bit7; \ + } while (0) + +/* + * The MixColumns constant is + * M = X + Y + Y^2 + (X+1)Y^3 + * which we construct by rearranging it into + * M = 1 + (1+Y^3) [ X + (1+Y^2) ] + */ +#define BITSLICED_MIXCOLUMNS(output, input, uintN_t) do \ + { \ + uintN_t a[8], aX[8], b[8]; \ + /* a = input * (1+Y^3) */ \ + ITERATE(BITSLICED_MUL_BY_1_Y3, a, input, uintN_t); \ + /* aX = a * X */ \ + BITSLICED_MUL_BY_X(aX, a, uintN_t); \ + /* b = a * (1+Y^2) = input * (1+Y+Y^2+Y^3) */ \ + ITERATE(BITSLICED_MUL_BY_1_Y2, b, a, uintN_t); \ + /* output = input + aX + b (reusing a as a temp */ \ + BITSLICED_ADD(a, aX, b); \ + BITSLICED_ADD(output, input, a); \ + } while (0) + +/* + * The InvMixColumns constant, written out longhand, is + * I = (X^3+X^2+X) + (X^3+1)Y + (X^3+X^2+1)Y^2 + (X^3+X+1)Y^3 + * We represent this as + * I = (X^3+X^2+X+1)(Y^3+Y^2+Y+1) + 1 + X(Y+Y^2) + X^2(Y+Y^3) + */ +#define BITSLICED_INVMIXCOLUMNS(output, input, uintN_t) do \ + { \ + /* We need input * X^i for i=1,...,3 */ \ + uintN_t X[8], X2[8], X3[8]; \ + BITSLICED_MUL_BY_X(X, input, uintN_t); \ + BITSLICED_MUL_BY_X(X2, X, uintN_t); \ + BITSLICED_MUL_BY_X(X3, X2, uintN_t); \ + /* Sum them all and multiply by 1+Y+Y^2+Y^3. */ \ + uintN_t S[8]; \ + BITSLICED_ADD(S, input, X); \ + BITSLICED_ADD(S, S, X2); \ + BITSLICED_ADD(S, S, X3); \ + ITERATE(BITSLICED_MUL_BY_1_Y3, S, S, uintN_t); \ + ITERATE(BITSLICED_MUL_BY_1_Y2, S, S, uintN_t); \ + /* Compute the X(Y+Y^2) term. */ \ + uintN_t A[8]; \ + ITERATE(BITSLICED_MUL_BY_1_Y3, A, X, uintN_t); \ + ITERATE(BITSLICED_MUL_BY_Y2, A, A, uintN_t); \ + /* Compute the X^2(Y+Y^3) term. */ \ + uintN_t B[8]; \ + ITERATE(BITSLICED_MUL_BY_1_Y2, B, X2, uintN_t); \ + ITERATE(BITSLICED_MUL_BY_Y3, B, B, uintN_t); \ + /* And add all the pieces together. */ \ + BITSLICED_ADD(S, S, input); \ + BITSLICED_ADD(S, S, A); \ + BITSLICED_ADD(output, S, B); \ + } while (0) + +/* ----- + * Put it all together into a cipher round. + */ + +/* Dummy macro to get rid of the MixColumns in the final round. */ +#define NO_MIXCOLUMNS(out, in, uintN_t) do {} while (0) + +#define ENCRYPT_ROUND_FN(suffix, uintN_t, mixcol_macro) \ + static void aes_sliced_round_e_##suffix( \ + uintN_t output[8], const uintN_t input[8], const uintN_t roundkey[8]) \ + { \ + BITSLICED_SUBBYTES(output, input, uintN_t); \ + BITSLICED_SHIFTROWS(output, output, uintN_t); \ + mixcol_macro(output, output, uintN_t); \ + BITSLICED_ADD(output, output, roundkey); \ + } + +ENCRYPT_ROUND_FN(serial, uint16_t, BITSLICED_MIXCOLUMNS) +ENCRYPT_ROUND_FN(serial_last, uint16_t, NO_MIXCOLUMNS) +ENCRYPT_ROUND_FN(parallel, BignumInt, BITSLICED_MIXCOLUMNS) +ENCRYPT_ROUND_FN(parallel_last, BignumInt, NO_MIXCOLUMNS) + +#define DECRYPT_ROUND_FN(suffix, uintN_t, mixcol_macro) \ + static void aes_sliced_round_d_##suffix( \ + uintN_t output[8], const uintN_t input[8], const uintN_t roundkey[8]) \ + { \ + BITSLICED_ADD(output, input, roundkey); \ + mixcol_macro(output, output, uintN_t); \ + BITSLICED_INVSUBBYTES(output, output, uintN_t); \ + BITSLICED_INVSHIFTROWS(output, output, uintN_t); \ + } + +#if 0 /* no cipher mode we support requires serial decryption */ +DECRYPT_ROUND_FN(serial, uint16_t, BITSLICED_INVMIXCOLUMNS) +DECRYPT_ROUND_FN(serial_first, uint16_t, NO_MIXCOLUMNS) +#endif +DECRYPT_ROUND_FN(parallel, BignumInt, BITSLICED_INVMIXCOLUMNS) +DECRYPT_ROUND_FN(parallel_first, BignumInt, NO_MIXCOLUMNS) + +/* ----- + * Key setup function. + */ + +typedef struct aes_sliced_key aes_sliced_key; +struct aes_sliced_key { + BignumInt roundkeys_parallel[MAXROUNDKEYS * 8]; + uint16_t roundkeys_serial[MAXROUNDKEYS * 8]; + unsigned rounds; +}; + +static void aes_sliced_key_setup( + aes_sliced_key *sk, const void *vkey, size_t keybits) +{ + const unsigned char *key = (const unsigned char *)vkey; + + size_t key_words = keybits / 32; + sk->rounds = key_words + 6; + size_t sched_words = (sk->rounds + 1) * 4; + + unsigned rconpos = 0; + + uint16_t *outslices = sk->roundkeys_serial; + unsigned outshift = 0; + + memset(sk->roundkeys_serial, 0, sizeof(sk->roundkeys_serial)); + + uint8_t inblk[16]; + memset(inblk, 0, 16); + uint16_t slices[8]; + + for (size_t i = 0; i < sched_words; i++) { + /* + * Prepare a word of round key in the low 4 bits of each + * integer in slices[]. + */ + if (i < key_words) { + memcpy(inblk, key + 4*i, 4); + TO_BITSLICES(slices, inblk, uint16_t, =, 0); + } else { + unsigned wordindex, bitshift; + uint16_t *prevslices; + + /* Fetch the (i-1)th key word */ + wordindex = i-1; + bitshift = 4 * (wordindex & 3); + prevslices = sk->roundkeys_serial + 8 * (wordindex >> 2); + for (size_t i = 0; i < 8; i++) + slices[i] = prevslices[i] >> bitshift; + + /* Decide what we're doing in this expansion stage */ + bool rotate_and_round_constant = (i % key_words == 0); + bool sub = rotate_and_round_constant || + (key_words == 8 && i % 8 == 4); + + if (rotate_and_round_constant) { + for (size_t i = 0; i < 8; i++) + slices[i] = ((slices[i] << 3) | (slices[i] >> 1)) & 0xF; + } + + if (sub) { + /* Apply the SubBytes transform to the key word. But + * here we need to apply the _full_ SubBytes from the + * spec, including the constant which our S-box leaves + * out. */ + BITSLICED_SUBBYTES(slices, slices, uint16_t); + slices[0] ^= 0xFFFF; + slices[1] ^= 0xFFFF; + slices[5] ^= 0xFFFF; + slices[6] ^= 0xFFFF; + } + + if (rotate_and_round_constant) { + assert(rconpos < lenof(aes_key_setup_round_constants)); + uint8_t rcon = aes_key_setup_round_constants[rconpos++]; + for (size_t i = 0; i < 8; i++) + slices[i] ^= 1 & (rcon >> i); + } + + /* Combine with the (i-Nk)th key word */ + wordindex = i - key_words; + bitshift = 4 * (wordindex & 3); + prevslices = sk->roundkeys_serial + 8 * (wordindex >> 2); + for (size_t i = 0; i < 8; i++) + slices[i] ^= prevslices[i] >> bitshift; + } + + /* + * Now copy it into sk. + */ + for (unsigned b = 0; b < 8; b++) + outslices[b] |= (slices[b] & 0xF) << outshift; + outshift += 4; + if (outshift == 16) { + outshift = 0; + outslices += 8; + } + } + + smemclr(inblk, sizeof(inblk)); + smemclr(slices, sizeof(slices)); + + /* + * Add the S-box constant to every round key after the first one, + * compensating for it being left out in the main cipher. + */ + for (size_t i = 8; i < 8 * (sched_words/4); i += 8) { + sk->roundkeys_serial[i+0] ^= 0xFFFF; + sk->roundkeys_serial[i+1] ^= 0xFFFF; + sk->roundkeys_serial[i+5] ^= 0xFFFF; + sk->roundkeys_serial[i+6] ^= 0xFFFF; + } + + /* + * Replicate that set of round keys into larger integers for the + * parallel versions of the cipher. + */ + for (size_t i = 0; i < 8 * (sched_words / 4); i++) { + sk->roundkeys_parallel[i] = sk->roundkeys_serial[i] * + ((BignumInt)~(BignumInt)0 / 0xFFFF); + } +} + +/* ----- + * The full cipher primitive, including transforming the input and + * output to/from bit-sliced form. + */ + +#define ENCRYPT_FN(suffix, uintN_t, nblocks) \ + static void aes_sliced_e_##suffix( \ + uint8_t *output, const uint8_t *input, const aes_sliced_key *sk) \ + { \ + uintN_t state[8]; \ + TO_BITSLICES(state, input, uintN_t, =, 0); \ + for (unsigned i = 1; i < nblocks; i++) { \ + input += 16; \ + TO_BITSLICES(state, input, uintN_t, |=, i*16); \ + } \ + const uintN_t *keys = sk->roundkeys_##suffix; \ + BITSLICED_ADD(state, state, keys); \ + keys += 8; \ + for (unsigned i = 0; i < sk->rounds-1; i++) { \ + aes_sliced_round_e_##suffix(state, state, keys); \ + keys += 8; \ + } \ + aes_sliced_round_e_##suffix##_last(state, state, keys); \ + for (unsigned i = 0; i < nblocks; i++) { \ + FROM_BITSLICES(output, state, i*16); \ + output += 16; \ + } \ + } + +#define DECRYPT_FN(suffix, uintN_t, nblocks) \ + static void aes_sliced_d_##suffix( \ + uint8_t *output, const uint8_t *input, const aes_sliced_key *sk) \ + { \ + uintN_t state[8]; \ + TO_BITSLICES(state, input, uintN_t, =, 0); \ + for (unsigned i = 1; i < nblocks; i++) { \ + input += 16; \ + TO_BITSLICES(state, input, uintN_t, |=, i*16); \ + } \ + const uintN_t *keys = sk->roundkeys_##suffix + 8*sk->rounds; \ + aes_sliced_round_d_##suffix##_first(state, state, keys); \ + keys -= 8; \ + for (unsigned i = 0; i < sk->rounds-1; i++) { \ + aes_sliced_round_d_##suffix(state, state, keys); \ + keys -= 8; \ + } \ + BITSLICED_ADD(state, state, keys); \ + for (unsigned i = 0; i < nblocks; i++) { \ + FROM_BITSLICES(output, state, i*16); \ + output += 16; \ + } \ + } + +ENCRYPT_FN(serial, uint16_t, 1) +#if 0 /* no cipher mode we support requires serial decryption */ +DECRYPT_FN(serial, uint16_t, 1) +#endif +ENCRYPT_FN(parallel, BignumInt, SLICE_PARALLELISM) +DECRYPT_FN(parallel, BignumInt, SLICE_PARALLELISM) + +/* ----- + * The SSH interface and the cipher modes. + */ + +#define SDCTR_WORDS (16 / BIGNUM_INT_BYTES) + +typedef struct aes_sw_context aes_sw_context; +struct aes_sw_context { + aes_sliced_key sk; + union { + struct { + /* In CBC mode, the IV is just a copy of the last seen + * cipher block. */ + uint8_t prevblk[16]; + } cbc; + struct { + /* In SDCTR mode, we keep the counter itself in a form + * that's easy to increment. We also use the parallel + * version of the core AES function, so we'll encrypt + * multiple counter values in one go. That won't align + * nicely with the sizes of data we're asked to encrypt, + * so we must also store a cache of the last set of + * keystream blocks we generated, and our current position + * within that cache. */ + BignumInt counter[SDCTR_WORDS]; + uint8_t keystream[SLICE_PARALLELISM * 16]; + uint8_t *keystream_pos; + } sdctr; + } iv; + ssh_cipher ciph; +}; + +static ssh_cipher *aes_sw_new(const ssh_cipheralg *alg) +{ + aes_sw_context *ctx = snew(aes_sw_context); + ctx->ciph.vt = alg; + return &ctx->ciph; +} + +static void aes_sw_free(ssh_cipher *ciph) +{ + aes_sw_context *ctx = container_of(ciph, aes_sw_context, ciph); + smemclr(ctx, sizeof(*ctx)); + sfree(ctx); +} + +static void aes_sw_setkey(ssh_cipher *ciph, const void *vkey) +{ + aes_sw_context *ctx = container_of(ciph, aes_sw_context, ciph); + aes_sliced_key_setup(&ctx->sk, vkey, ctx->ciph.vt->real_keybits); +} + +static void aes_sw_setiv_cbc(ssh_cipher *ciph, const void *iv) +{ + aes_sw_context *ctx = container_of(ciph, aes_sw_context, ciph); + memcpy(ctx->iv.cbc.prevblk, iv, 16); +} + +static void aes_sw_setiv_sdctr(ssh_cipher *ciph, const void *viv) +{ + aes_sw_context *ctx = container_of(ciph, aes_sw_context, ciph); + const uint8_t *iv = (const uint8_t *)viv; + + /* Import the initial counter value into the internal representation */ + for (unsigned i = 0; i < SDCTR_WORDS; i++) + ctx->iv.sdctr.counter[i] = + GET_BIGNUMINT_MSB_FIRST( + iv + 16 - BIGNUM_INT_BYTES - i*BIGNUM_INT_BYTES); + + /* Set keystream_pos to indicate that the keystream cache is + * currently empty */ + ctx->iv.sdctr.keystream_pos = + ctx->iv.sdctr.keystream + sizeof(ctx->iv.sdctr.keystream); +} + +typedef void (*aes_sw_fn)(uint32_t v[4], const uint32_t *keysched); + +static inline void memxor16(void *vout, const void *vlhs, const void *vrhs) +{ + uint8_t *out = (uint8_t *)vout; + const uint8_t *lhs = (const uint8_t *)vlhs, *rhs = (const uint8_t *)vrhs; + uint64_t w; + + w = GET_64BIT_LSB_FIRST(lhs); + w ^= GET_64BIT_LSB_FIRST(rhs); + PUT_64BIT_LSB_FIRST(out, w); + w = GET_64BIT_LSB_FIRST(lhs + 8); + w ^= GET_64BIT_LSB_FIRST(rhs + 8); + PUT_64BIT_LSB_FIRST(out + 8, w); +} + +static inline void aes_cbc_sw_encrypt( + ssh_cipher *ciph, void *vblk, int blklen) +{ + aes_sw_context *ctx = container_of(ciph, aes_sw_context, ciph); + + /* + * CBC encryption has to be done serially, because the input to + * each run of the cipher includes the output from the previous + * run. + */ + + for (uint8_t *blk = (uint8_t *)vblk, *finish = blk + blklen; + blk < finish; blk += 16) { + /* + * We use the IV array itself as the location for the + * encryption, because there's no reason not to. + */ + + /* XOR the new plaintext block into the previous cipher block */ + memxor16(ctx->iv.cbc.prevblk, ctx->iv.cbc.prevblk, blk); + + /* Run the cipher over the result, which leaves it + * conveniently already stored in ctx->iv */ + aes_sliced_e_serial( + ctx->iv.cbc.prevblk, ctx->iv.cbc.prevblk, &ctx->sk); + + /* Copy it to the output location */ + memcpy(blk, ctx->iv.cbc.prevblk, 16); + } +} + +static inline void aes_cbc_sw_decrypt( + ssh_cipher *ciph, void *vblk, int blklen) +{ + aes_sw_context *ctx = container_of(ciph, aes_sw_context, ciph); + uint8_t *blk = (uint8_t *)vblk; + + /* + * CBC decryption can run in parallel, because all the + * _ciphertext_ blocks are already available. + */ + + size_t blocks_remaining = blklen / 16; + + uint8_t data[SLICE_PARALLELISM * 16]; + /* Zeroing the data array is probably overcautious, but it avoids + * technically undefined behaviour from leaving it uninitialised + * if our very first iteration doesn't include enough cipher + * blocks to populate it fully */ + memset(data, 0, sizeof(data)); + + while (blocks_remaining > 0) { + /* Number of blocks we'll handle in this iteration. If we're + * dealing with fewer than the maximum, it doesn't matter - + * it's harmless to run the full parallel cipher function + * anyway. */ + size_t blocks = (blocks_remaining < SLICE_PARALLELISM ? + blocks_remaining : SLICE_PARALLELISM); + + /* Parallel-decrypt the input, in a separate array so we still + * have the cipher stream available for XORing. */ + memcpy(data, blk, 16 * blocks); + aes_sliced_d_parallel(data, data, &ctx->sk); + + /* Write the output and update the IV */ + for (size_t i = 0; i < blocks; i++) { + uint8_t *decrypted = data + 16*i; + uint8_t *output = blk + 16*i; + + memxor16(decrypted, decrypted, ctx->iv.cbc.prevblk); + memcpy(ctx->iv.cbc.prevblk, output, 16); + memcpy(output, decrypted, 16); + } + + /* Advance the input pointer. */ + blk += 16 * blocks; + blocks_remaining -= blocks; + } + + smemclr(data, sizeof(data)); +} + +static inline void aes_sdctr_sw( + ssh_cipher *ciph, void *vblk, int blklen) +{ + aes_sw_context *ctx = container_of(ciph, aes_sw_context, ciph); + + /* + * SDCTR encrypt/decrypt loops round one block at a time XORing + * the keystream into the user's data, and periodically has to run + * a parallel encryption operation to get more keystream. + */ + + uint8_t *keystream_end = + ctx->iv.sdctr.keystream + sizeof(ctx->iv.sdctr.keystream); + + for (uint8_t *blk = (uint8_t *)vblk, *finish = blk + blklen; + blk < finish; blk += 16) { + + if (ctx->iv.sdctr.keystream_pos == keystream_end) { + /* + * Generate some keystream. + */ + for (uint8_t *block = ctx->iv.sdctr.keystream; + block < keystream_end; block += 16) { + /* Format the counter value into the buffer. */ + for (unsigned i = 0; i < SDCTR_WORDS; i++) + PUT_BIGNUMINT_MSB_FIRST( + block + 16 - BIGNUM_INT_BYTES - i*BIGNUM_INT_BYTES, + ctx->iv.sdctr.counter[i]); + + /* Increment the counter. */ + BignumCarry carry = 1; + for (unsigned i = 0; i < SDCTR_WORDS; i++) + BignumADC(ctx->iv.sdctr.counter[i], carry, + ctx->iv.sdctr.counter[i], 0, carry); + } + + /* Encrypt all those counter blocks. */ + aes_sliced_e_parallel(ctx->iv.sdctr.keystream, + ctx->iv.sdctr.keystream, &ctx->sk); + + /* Reset keystream_pos to the start of the buffer. */ + ctx->iv.sdctr.keystream_pos = ctx->iv.sdctr.keystream; + } + + memxor16(blk, blk, ctx->iv.sdctr.keystream_pos); + ctx->iv.sdctr.keystream_pos += 16; + } +} + +#define SW_ENC_DEC(len) \ + static void aes##len##_sw_cbc_encrypt( \ + ssh_cipher *ciph, void *vblk, int blklen) \ + { aes_cbc_sw_encrypt(ciph, vblk, blklen); } \ + static void aes##len##_sw_cbc_decrypt( \ + ssh_cipher *ciph, void *vblk, int blklen) \ + { aes_cbc_sw_decrypt(ciph, vblk, blklen); } \ + static void aes##len##_sw_sdctr( \ + ssh_cipher *ciph, void *vblk, int blklen) \ + { aes_sdctr_sw(ciph, vblk, blklen); } + +SW_ENC_DEC(128) +SW_ENC_DEC(192) +SW_ENC_DEC(256) + +AES_EXTRA(_sw); +AES_ALL_VTABLES(_sw, "unaccelerated"); diff --git a/crypto/aes.c b/crypto/aes.c deleted file mode 100644 index a7ca1117..00000000 --- a/crypto/aes.c +++ /dev/null @@ -1,1912 +0,0 @@ -/* - * Implementation of AES. - */ - -#include -#include - -#include "ssh.h" -#include "mpint_i.h" /* we reuse the BignumInt system */ - -/* - * Start by deciding whether we can support hardware AES at all. - */ -#define HW_AES_NONE 0 -#define HW_AES_NI 1 -#define HW_AES_NEON 2 - -#ifdef _FORCE_AES_NI -# define HW_AES HW_AES_NI -#elif defined(__clang__) -# if __has_attribute(target) && __has_include() && \ - (defined(__x86_64__) || defined(__i386)) -# define HW_AES HW_AES_NI -# endif -#elif defined(__GNUC__) -# if (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 4)) && \ - (defined(__x86_64__) || defined(__i386)) -# define HW_AES HW_AES_NI -# endif -#elif defined (_MSC_VER) -# if (defined(_M_X64) || defined(_M_IX86)) && _MSC_FULL_VER >= 150030729 -# define HW_AES HW_AES_NI -# endif -#endif - -#ifdef _FORCE_AES_NEON -# define HW_AES HW_AES_NEON -#elif defined __BYTE_ORDER__ && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ - /* Arm can potentially support both endiannesses, but this code - * hasn't been tested on anything but little. If anyone wants to - * run big-endian, they'll need to fix it first. */ -#elif defined __ARM_FEATURE_CRYPTO - /* If the Arm crypto extension is available already, we can - * support NEON AES without having to enable anything by hand */ -# define HW_AES HW_AES_NEON -#elif defined(__clang__) -# if __has_attribute(target) && __has_include() && \ - (defined(__aarch64__)) - /* clang can enable the crypto extension in AArch64 using - * __attribute__((target)) */ -# define HW_AES HW_AES_NEON -# define USE_CLANG_ATTR_TARGET_AARCH64 -# endif -#elif defined _MSC_VER -# if defined _M_ARM64 -# define HW_AES HW_AES_NEON - /* 64-bit Visual Studio uses the header in place - * of the standard */ -# define USE_ARM64_NEON_H -# elif defined _M_ARM -# define HW_AES HW_AES_NEON - /* 32-bit Visual Studio uses the right header name, but requires - * this #define to enable a set of intrinsic definitions that - * do not omit one of the parameters for vaes[ed]q_u8 */ -# define _ARM_USE_NEW_NEON_INTRINSICS -# endif -#endif - -#if defined _FORCE_SOFTWARE_AES || !defined HW_AES -# undef HW_AES -# define HW_AES HW_AES_NONE -#endif - -#if HW_AES == HW_AES_NI -#define HW_NAME_SUFFIX " (AES-NI accelerated)" -#elif HW_AES == HW_AES_NEON -#define HW_NAME_SUFFIX " (NEON accelerated)" -#else -#define HW_NAME_SUFFIX " (!NONEXISTENT ACCELERATED VERSION!)" -#endif - -/* - * Vtable collection for AES. For each SSH-level cipher id (i.e. - * combination of key length and cipher mode), we provide three - * vtables: one for the pure software implementation, one using - * hardware acceleration (if available), and a top-level one which is - * never actually instantiated, and only contains a new() method whose - * job is to decide which of the other two to return an actual - * instance of. - */ - -static ssh_cipher *aes_select(const ssh_cipheralg *alg); -static ssh_cipher *aes_sw_new(const ssh_cipheralg *alg); -static void aes_sw_free(ssh_cipher *); -static void aes_sw_setiv_cbc(ssh_cipher *, const void *iv); -static void aes_sw_setiv_sdctr(ssh_cipher *, const void *iv); -static void aes_sw_setkey(ssh_cipher *, const void *key); -static ssh_cipher *aes_hw_new(const ssh_cipheralg *alg); -static void aes_hw_free(ssh_cipher *); -static void aes_hw_setiv_cbc(ssh_cipher *, const void *iv); -static void aes_hw_setiv_sdctr(ssh_cipher *, const void *iv); -static void aes_hw_setkey(ssh_cipher *, const void *key); - -struct aes_extra { - const ssh_cipheralg *sw, *hw; -}; - -#define VTABLES_INNER(cid, pid, bits, name, encsuffix, \ - decsuffix, setivsuffix, flagsval) \ - static void cid##_sw##encsuffix(ssh_cipher *, void *blk, int len); \ - static void cid##_sw##decsuffix(ssh_cipher *, void *blk, int len); \ - const ssh_cipheralg ssh_##cid##_sw = { \ - .new = aes_sw_new, \ - .free = aes_sw_free, \ - .setiv = aes_sw_##setivsuffix, \ - .setkey = aes_sw_setkey, \ - .encrypt = cid##_sw##encsuffix, \ - .decrypt = cid##_sw##decsuffix, \ - .ssh2_id = pid, \ - .blksize = 16, \ - .real_keybits = bits, \ - .padded_keybytes = bits/8, \ - .flags = flagsval, \ - .text_name = name " (unaccelerated)", \ - }; \ - \ - static void cid##_hw##encsuffix(ssh_cipher *, void *blk, int len); \ - static void cid##_hw##decsuffix(ssh_cipher *, void *blk, int len); \ - const ssh_cipheralg ssh_##cid##_hw = { \ - .new = aes_hw_new, \ - .free = aes_hw_free, \ - .setiv = aes_hw_##setivsuffix, \ - .setkey = aes_hw_setkey, \ - .encrypt = cid##_hw##encsuffix, \ - .decrypt = cid##_hw##decsuffix, \ - .ssh2_id = pid, \ - .blksize = 16, \ - .real_keybits = bits, \ - .padded_keybytes = bits/8, \ - .flags = flagsval, \ - .text_name = name HW_NAME_SUFFIX, \ - }; \ - \ - static const struct aes_extra extra_##cid = { \ - &ssh_##cid##_sw, &ssh_##cid##_hw }; \ - \ - const ssh_cipheralg ssh_##cid = { \ - .new = aes_select, \ - .ssh2_id = pid, \ - .blksize = 16, \ - .real_keybits = bits, \ - .padded_keybytes = bits/8, \ - .flags = flagsval, \ - .text_name = name " (dummy selector vtable)", \ - .extra = &extra_##cid \ - }; \ - -#define VTABLES(keylen) \ - VTABLES_INNER(aes ## keylen ## _cbc, "aes" #keylen "-cbc", \ - keylen, "AES-" #keylen " CBC", _encrypt, _decrypt, \ - setiv_cbc, SSH_CIPHER_IS_CBC) \ - VTABLES_INNER(aes ## keylen ## _sdctr, "aes" #keylen "-ctr", \ - keylen, "AES-" #keylen " SDCTR",,, setiv_sdctr, 0) - -VTABLES(128) -VTABLES(192) -VTABLES(256) - -static const ssh_cipheralg ssh_rijndael_lysator = { - /* Same as aes256_cbc, but with a different protocol ID */ - .new = aes_select, - .ssh2_id = "rijndael-cbc@lysator.liu.se", - .blksize = 16, - .real_keybits = 256, - .padded_keybytes = 256/8, - .flags = 0, - .text_name = "AES-256 CBC (dummy selector vtable)", - .extra = &extra_aes256_cbc, -}; - -static const ssh_cipheralg *const aes_list[] = { - &ssh_aes256_sdctr, - &ssh_aes256_cbc, - &ssh_rijndael_lysator, - &ssh_aes192_sdctr, - &ssh_aes192_cbc, - &ssh_aes128_sdctr, - &ssh_aes128_cbc, -}; - -const ssh2_ciphers ssh2_aes = { lenof(aes_list), aes_list }; - -/* - * The actual query function that asks if hardware acceleration is - * available. - */ -static bool aes_hw_available(void); - -/* - * The top-level selection function, caching the results of - * aes_hw_available() so it only has to run once. - */ -static bool aes_hw_available_cached(void) -{ - static bool initialised = false; - static bool hw_available; - if (!initialised) { - hw_available = aes_hw_available(); - initialised = true; - } - return hw_available; -} - -static ssh_cipher *aes_select(const ssh_cipheralg *alg) -{ - const struct aes_extra *extra = (const struct aes_extra *)alg->extra; - const ssh_cipheralg *real_alg = - aes_hw_available_cached() ? extra->hw : extra->sw; - - return ssh_cipher_new(real_alg); -} - -/* ---------------------------------------------------------------------- - * Definitions likely to be helpful to multiple implementations. - */ - -#define REP2(x) x x -#define REP4(x) REP2(REP2(x)) -#define REP8(x) REP2(REP4(x)) -#define REP9(x) REP8(x) x -#define REP11(x) REP8(x) REP2(x) x -#define REP13(x) REP8(x) REP4(x) x - -static const uint8_t key_setup_round_constants[] = { - /* The first few powers of X in GF(2^8), used during key setup. - * This can safely be a lookup table without side channel risks, - * because key setup iterates through it once in a standard way - * regardless of the key. */ - 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, -}; - -#define MAXROUNDKEYS 15 - -/* ---------------------------------------------------------------------- - * Software implementation of AES. - * - * This implementation uses a bit-sliced representation. Instead of - * the obvious approach of storing the cipher state so that each byte - * (or field element, or entry in the cipher matrix) occupies 8 - * contiguous bits in a machine integer somewhere, we organise the - * cipher state as an array of 8 integers, in such a way that each - * logical byte of the cipher state occupies one bit in each integer, - * all at the same position. This allows us to do parallel logic on - * all bytes of the state by doing bitwise operations between the 8 - * integers; in particular, the S-box (SubBytes) lookup is done this - * way, which takes about 110 operations - but for those 110 bitwise - * ops you get 64 S-box lookups, not just one. - */ - -#define SLICE_PARALLELISM (BIGNUM_INT_BYTES / 2) - -#ifdef BITSLICED_DEBUG -/* Dump function that undoes the bitslicing transform, so you can see - * the logical data represented by a set of slice words. */ -static inline void dumpslices_uint16_t( - const char *prefix, const uint16_t slices[8]) -{ - printf("%-30s", prefix); - for (unsigned byte = 0; byte < 16; byte++) { - unsigned byteval = 0; - for (unsigned bit = 0; bit < 8; bit++) - byteval |= (1 & (slices[bit] >> byte)) << bit; - printf("%02x", byteval); - } - printf("\n"); -} - -static inline void dumpslices_BignumInt( - const char *prefix, const BignumInt slices[8]) -{ - printf("%-30s", prefix); - for (unsigned iter = 0; iter < SLICE_PARALLELISM; iter++) { - for (unsigned byte = 0; byte < 16; byte++) { - unsigned byteval = 0; - for (unsigned bit = 0; bit < 8; bit++) - byteval |= (1 & (slices[bit] >> (iter*16+byte))) << bit; - printf("%02x", byteval); - } - if (iter+1 < SLICE_PARALLELISM) - printf(" "); - } - printf("\n"); -} -#else -#define dumpslices_uintN_t(prefix, slices) ((void)0) -#define dumpslices_BignumInt(prefix, slices) ((void)0) -#endif - -/* ----- - * Bit-slicing transformation: convert between an array of 16 uint8_t - * and an array of 8 uint16_t, so as to interchange the bit index - * within each element and the element index within the array. (That - * is, bit j of input[i] == bit i of output[j]. - */ - -#define SWAPWORDS(shift) do \ - { \ - uint64_t mask = ~(uint64_t)0 / ((1ULL << shift) + 1); \ - uint64_t diff = ((i0 >> shift) ^ i1) & mask; \ - i0 ^= diff << shift; \ - i1 ^= diff; \ - } while (0) - -#define SWAPINWORD(i, bigshift, smallshift) do \ - { \ - uint64_t mask = ~(uint64_t)0; \ - mask /= ((1ULL << bigshift) + 1); \ - mask /= ((1ULL << smallshift) + 1); \ - mask <<= smallshift; \ - unsigned shift = bigshift - smallshift; \ - uint64_t diff = ((i >> shift) ^ i) & mask; \ - i ^= diff ^ (diff << shift); \ - } while (0) - -#define TO_BITSLICES(slices, bytes, uintN_t, assign_op, shift) do \ - { \ - uint64_t i0 = GET_64BIT_LSB_FIRST(bytes); \ - uint64_t i1 = GET_64BIT_LSB_FIRST(bytes + 8); \ - SWAPINWORD(i0, 8, 1); \ - SWAPINWORD(i1, 8, 1); \ - SWAPINWORD(i0, 16, 2); \ - SWAPINWORD(i1, 16, 2); \ - SWAPINWORD(i0, 32, 4); \ - SWAPINWORD(i1, 32, 4); \ - SWAPWORDS(8); \ - slices[0] assign_op (uintN_t)((i0 >> 0) & 0xFFFF) << (shift); \ - slices[2] assign_op (uintN_t)((i0 >> 16) & 0xFFFF) << (shift); \ - slices[4] assign_op (uintN_t)((i0 >> 32) & 0xFFFF) << (shift); \ - slices[6] assign_op (uintN_t)((i0 >> 48) & 0xFFFF) << (shift); \ - slices[1] assign_op (uintN_t)((i1 >> 0) & 0xFFFF) << (shift); \ - slices[3] assign_op (uintN_t)((i1 >> 16) & 0xFFFF) << (shift); \ - slices[5] assign_op (uintN_t)((i1 >> 32) & 0xFFFF) << (shift); \ - slices[7] assign_op (uintN_t)((i1 >> 48) & 0xFFFF) << (shift); \ - } while (0) - -#define FROM_BITSLICES(bytes, slices, shift) do \ - { \ - uint64_t i1 = ((slices[7] >> (shift)) & 0xFFFF); \ - i1 = (i1 << 16) | ((slices[5] >> (shift)) & 0xFFFF); \ - i1 = (i1 << 16) | ((slices[3] >> (shift)) & 0xFFFF); \ - i1 = (i1 << 16) | ((slices[1] >> (shift)) & 0xFFFF); \ - uint64_t i0 = ((slices[6] >> (shift)) & 0xFFFF); \ - i0 = (i0 << 16) | ((slices[4] >> (shift)) & 0xFFFF); \ - i0 = (i0 << 16) | ((slices[2] >> (shift)) & 0xFFFF); \ - i0 = (i0 << 16) | ((slices[0] >> (shift)) & 0xFFFF); \ - SWAPWORDS(8); \ - SWAPINWORD(i0, 32, 4); \ - SWAPINWORD(i1, 32, 4); \ - SWAPINWORD(i0, 16, 2); \ - SWAPINWORD(i1, 16, 2); \ - SWAPINWORD(i0, 8, 1); \ - SWAPINWORD(i1, 8, 1); \ - PUT_64BIT_LSB_FIRST(bytes, i0); \ - PUT_64BIT_LSB_FIRST((bytes) + 8, i1); \ - } while (0) - -/* ----- - * Some macros that will be useful repeatedly. - */ - -/* Iterate a unary transformation over all 8 slices. */ -#define ITERATE(MACRO, output, input, uintN_t) do \ - { \ - MACRO(output[0], input[0], uintN_t); \ - MACRO(output[1], input[1], uintN_t); \ - MACRO(output[2], input[2], uintN_t); \ - MACRO(output[3], input[3], uintN_t); \ - MACRO(output[4], input[4], uintN_t); \ - MACRO(output[5], input[5], uintN_t); \ - MACRO(output[6], input[6], uintN_t); \ - MACRO(output[7], input[7], uintN_t); \ - } while (0) - -/* Simply add (i.e. XOR) two whole sets of slices together. */ -#define BITSLICED_ADD(output, lhs, rhs) do \ - { \ - output[0] = lhs[0] ^ rhs[0]; \ - output[1] = lhs[1] ^ rhs[1]; \ - output[2] = lhs[2] ^ rhs[2]; \ - output[3] = lhs[3] ^ rhs[3]; \ - output[4] = lhs[4] ^ rhs[4]; \ - output[5] = lhs[5] ^ rhs[5]; \ - output[6] = lhs[6] ^ rhs[6]; \ - output[7] = lhs[7] ^ rhs[7]; \ - } while (0) - -/* ----- - * The AES S-box, in pure bitwise logic so that it can be run in - * parallel on whole words full of bit-sliced field elements. - * - * Source: 'A new combinational logic minimization technique with - * applications to cryptology', https://eprint.iacr.org/2009/191 - * - * As a minor speed optimisation, I use a modified version of the - * S-box which omits the additive constant 0x63, i.e. this S-box - * consists of only the field inversion and linear map components. - * Instead, the addition of the constant is deferred until after the - * subsequent ShiftRows and MixColumns stages, so that it happens at - * the same time as adding the next round key - and then we just make - * it _part_ of the round key, so it doesn't cost any extra - * instructions to add. - * - * (Obviously adding a constant to each byte commutes with ShiftRows, - * which only permutes the bytes. It also commutes with MixColumns: - * that's not quite so obvious, but since the effect of MixColumns is - * to multiply a constant polynomial M into each column, it is obvious - * that adding some polynomial K and then multiplying by M is - * equivalent to multiplying by M and then adding the product KM. And - * in fact, since the coefficients of M happen to sum to 1, it turns - * out that KM = K, so we don't even have to change the constant when - * we move it to the far side of MixColumns.) - * - * Of course, one knock-on effect of this is that the use of the S-box - * *during* key setup has to be corrected by manually adding on the - * constant afterwards! - */ - -/* Initial linear transformation for the forward S-box, from Fig 2 of - * the paper. */ -#define SBOX_FORWARD_TOP_TRANSFORM(input, uintN_t) \ - uintN_t y14 = input[4] ^ input[2]; \ - uintN_t y13 = input[7] ^ input[1]; \ - uintN_t y9 = input[7] ^ input[4]; \ - uintN_t y8 = input[7] ^ input[2]; \ - uintN_t t0 = input[6] ^ input[5]; \ - uintN_t y1 = t0 ^ input[0]; \ - uintN_t y4 = y1 ^ input[4]; \ - uintN_t y12 = y13 ^ y14; \ - uintN_t y2 = y1 ^ input[7]; \ - uintN_t y5 = y1 ^ input[1]; \ - uintN_t y3 = y5 ^ y8; \ - uintN_t t1 = input[3] ^ y12; \ - uintN_t y15 = t1 ^ input[2]; \ - uintN_t y20 = t1 ^ input[6]; \ - uintN_t y6 = y15 ^ input[0]; \ - uintN_t y10 = y15 ^ t0; \ - uintN_t y11 = y20 ^ y9; \ - uintN_t y7 = input[0] ^ y11; \ - uintN_t y17 = y10 ^ y11; \ - uintN_t y19 = y10 ^ y8; \ - uintN_t y16 = t0 ^ y11; \ - uintN_t y21 = y13 ^ y16; \ - uintN_t y18 = input[7] ^ y16; \ - /* Make a copy of input[0] under a new name, because the core - * will refer to it, and in the inverse version of the S-box - * the corresponding value will be one of the calculated ones - * and not in input[0] itself. */ \ - uintN_t i0 = input[0]; \ - /* end */ - -/* Core nonlinear component, from Fig 3 of the paper. */ -#define SBOX_CORE(uintN_t) \ - uintN_t t2 = y12 & y15; \ - uintN_t t3 = y3 & y6; \ - uintN_t t4 = t3 ^ t2; \ - uintN_t t5 = y4 & i0; \ - uintN_t t6 = t5 ^ t2; \ - uintN_t t7 = y13 & y16; \ - uintN_t t8 = y5 & y1; \ - uintN_t t9 = t8 ^ t7; \ - uintN_t t10 = y2 & y7; \ - uintN_t t11 = t10 ^ t7; \ - uintN_t t12 = y9 & y11; \ - uintN_t t13 = y14 & y17; \ - uintN_t t14 = t13 ^ t12; \ - uintN_t t15 = y8 & y10; \ - uintN_t t16 = t15 ^ t12; \ - uintN_t t17 = t4 ^ t14; \ - uintN_t t18 = t6 ^ t16; \ - uintN_t t19 = t9 ^ t14; \ - uintN_t t20 = t11 ^ t16; \ - uintN_t t21 = t17 ^ y20; \ - uintN_t t22 = t18 ^ y19; \ - uintN_t t23 = t19 ^ y21; \ - uintN_t t24 = t20 ^ y18; \ - uintN_t t25 = t21 ^ t22; \ - uintN_t t26 = t21 & t23; \ - uintN_t t27 = t24 ^ t26; \ - uintN_t t28 = t25 & t27; \ - uintN_t t29 = t28 ^ t22; \ - uintN_t t30 = t23 ^ t24; \ - uintN_t t31 = t22 ^ t26; \ - uintN_t t32 = t31 & t30; \ - uintN_t t33 = t32 ^ t24; \ - uintN_t t34 = t23 ^ t33; \ - uintN_t t35 = t27 ^ t33; \ - uintN_t t36 = t24 & t35; \ - uintN_t t37 = t36 ^ t34; \ - uintN_t t38 = t27 ^ t36; \ - uintN_t t39 = t29 & t38; \ - uintN_t t40 = t25 ^ t39; \ - uintN_t t41 = t40 ^ t37; \ - uintN_t t42 = t29 ^ t33; \ - uintN_t t43 = t29 ^ t40; \ - uintN_t t44 = t33 ^ t37; \ - uintN_t t45 = t42 ^ t41; \ - uintN_t z0 = t44 & y15; \ - uintN_t z1 = t37 & y6; \ - uintN_t z2 = t33 & i0; \ - uintN_t z3 = t43 & y16; \ - uintN_t z4 = t40 & y1; \ - uintN_t z5 = t29 & y7; \ - uintN_t z6 = t42 & y11; \ - uintN_t z7 = t45 & y17; \ - uintN_t z8 = t41 & y10; \ - uintN_t z9 = t44 & y12; \ - uintN_t z10 = t37 & y3; \ - uintN_t z11 = t33 & y4; \ - uintN_t z12 = t43 & y13; \ - uintN_t z13 = t40 & y5; \ - uintN_t z14 = t29 & y2; \ - uintN_t z15 = t42 & y9; \ - uintN_t z16 = t45 & y14; \ - uintN_t z17 = t41 & y8; \ - /* end */ - -/* Final linear transformation for the forward S-box, from Fig 4 of - * the paper. */ -#define SBOX_FORWARD_BOTTOM_TRANSFORM(output, uintN_t) \ - uintN_t t46 = z15 ^ z16; \ - uintN_t t47 = z10 ^ z11; \ - uintN_t t48 = z5 ^ z13; \ - uintN_t t49 = z9 ^ z10; \ - uintN_t t50 = z2 ^ z12; \ - uintN_t t51 = z2 ^ z5; \ - uintN_t t52 = z7 ^ z8; \ - uintN_t t53 = z0 ^ z3; \ - uintN_t t54 = z6 ^ z7; \ - uintN_t t55 = z16 ^ z17; \ - uintN_t t56 = z12 ^ t48; \ - uintN_t t57 = t50 ^ t53; \ - uintN_t t58 = z4 ^ t46; \ - uintN_t t59 = z3 ^ t54; \ - uintN_t t60 = t46 ^ t57; \ - uintN_t t61 = z14 ^ t57; \ - uintN_t t62 = t52 ^ t58; \ - uintN_t t63 = t49 ^ t58; \ - uintN_t t64 = z4 ^ t59; \ - uintN_t t65 = t61 ^ t62; \ - uintN_t t66 = z1 ^ t63; \ - output[7] = t59 ^ t63; \ - output[1] = t56 ^ t62; \ - output[0] = t48 ^ t60; \ - uintN_t t67 = t64 ^ t65; \ - output[4] = t53 ^ t66; \ - output[3] = t51 ^ t66; \ - output[2] = t47 ^ t65; \ - output[6] = t64 ^ output[4]; \ - output[5] = t55 ^ t67; \ - /* end */ - -#define BITSLICED_SUBBYTES(output, input, uintN_t) do { \ - SBOX_FORWARD_TOP_TRANSFORM(input, uintN_t); \ - SBOX_CORE(uintN_t); \ - SBOX_FORWARD_BOTTOM_TRANSFORM(output, uintN_t); \ - } while (0) - -/* - * Initial and final linear transformations for the backward S-box. I - * generated these myself, by implementing the linear-transform - * optimisation algorithm in the paper, and applying it to the - * matrices calculated by _their_ top and bottom transformations, pre- - * and post-multiplied as appropriate by the linear map in the inverse - * S_box. - */ -#define SBOX_BACKWARD_TOP_TRANSFORM(input, uintN_t) \ - uintN_t y5 = input[4] ^ input[6]; \ - uintN_t y19 = input[3] ^ input[0]; \ - uintN_t itmp8 = y5 ^ input[0]; \ - uintN_t y4 = itmp8 ^ input[1]; \ - uintN_t y9 = input[4] ^ input[3]; \ - uintN_t y2 = y9 ^ y4; \ - uintN_t itmp9 = y2 ^ input[7]; \ - uintN_t y1 = y9 ^ input[0]; \ - uintN_t y6 = y5 ^ input[7]; \ - uintN_t y18 = y9 ^ input[5]; \ - uintN_t y7 = y18 ^ y2; \ - uintN_t y16 = y7 ^ y1; \ - uintN_t y21 = y7 ^ input[1]; \ - uintN_t y3 = input[4] ^ input[7]; \ - uintN_t y13 = y16 ^ y21; \ - uintN_t y8 = input[4] ^ y6; \ - uintN_t y10 = y8 ^ y19; \ - uintN_t y14 = y8 ^ y9; \ - uintN_t y20 = itmp9 ^ input[2]; \ - uintN_t y11 = y9 ^ y20; \ - uintN_t i0 = y11 ^ y7; \ - uintN_t y15 = i0 ^ y6; \ - uintN_t y17 = y16 ^ y15; \ - uintN_t y12 = itmp9 ^ input[3]; \ - /* end */ -#define SBOX_BACKWARD_BOTTOM_TRANSFORM(output, uintN_t) \ - uintN_t otmp18 = z15 ^ z6; \ - uintN_t otmp19 = z13 ^ otmp18; \ - uintN_t otmp20 = z12 ^ otmp19; \ - uintN_t otmp21 = z16 ^ otmp20; \ - uintN_t otmp22 = z8 ^ otmp21; \ - uintN_t otmp23 = z0 ^ otmp22; \ - uintN_t otmp24 = otmp22 ^ z3; \ - uintN_t otmp25 = otmp24 ^ z4; \ - uintN_t otmp26 = otmp25 ^ z2; \ - uintN_t otmp27 = z1 ^ otmp26; \ - uintN_t otmp28 = z14 ^ otmp27; \ - uintN_t otmp29 = otmp28 ^ z10; \ - output[4] = z2 ^ otmp23; \ - output[7] = z5 ^ otmp24; \ - uintN_t otmp30 = z11 ^ otmp29; \ - output[5] = z13 ^ otmp30; \ - uintN_t otmp31 = otmp25 ^ z8; \ - output[1] = z7 ^ otmp31; \ - uintN_t otmp32 = z11 ^ z9; \ - uintN_t otmp33 = z17 ^ otmp32; \ - uintN_t otmp34 = otmp30 ^ otmp33; \ - output[0] = z15 ^ otmp33; \ - uintN_t otmp35 = z12 ^ otmp34; \ - output[6] = otmp35 ^ z16; \ - uintN_t otmp36 = z1 ^ otmp23; \ - uintN_t otmp37 = z5 ^ otmp36; \ - output[2] = z4 ^ otmp37; \ - uintN_t otmp38 = z11 ^ output[1]; \ - uintN_t otmp39 = z2 ^ otmp38; \ - uintN_t otmp40 = z17 ^ otmp39; \ - uintN_t otmp41 = z0 ^ otmp40; \ - uintN_t otmp42 = z5 ^ otmp41; \ - uintN_t otmp43 = otmp42 ^ z10; \ - uintN_t otmp44 = otmp43 ^ z3; \ - output[3] = otmp44 ^ z16; \ - /* end */ - -#define BITSLICED_INVSUBBYTES(output, input, uintN_t) do { \ - SBOX_BACKWARD_TOP_TRANSFORM(input, uintN_t); \ - SBOX_CORE(uintN_t); \ - SBOX_BACKWARD_BOTTOM_TRANSFORM(output, uintN_t); \ - } while (0) - - -/* ----- - * The ShiftRows transformation. This operates independently on each - * bit slice. - */ - -#define SINGLE_BITSLICE_SHIFTROWS(output, input, uintN_t) do \ - { \ - uintN_t mask, mask2, mask3, diff, x = (input); \ - /* Rotate rows 2 and 3 by 16 bits */ \ - mask = 0x00CC * (((uintN_t)~(uintN_t)0) / 0xFFFF); \ - diff = ((x >> 8) ^ x) & mask; \ - x ^= diff ^ (diff << 8); \ - /* Rotate rows 1 and 3 by 8 bits */ \ - mask = 0x0AAA * (((uintN_t)~(uintN_t)0) / 0xFFFF); \ - mask2 = 0xA000 * (((uintN_t)~(uintN_t)0) / 0xFFFF); \ - mask3 = 0x5555 * (((uintN_t)~(uintN_t)0) / 0xFFFF); \ - x = ((x >> 4) & mask) | ((x << 12) & mask2) | (x & mask3); \ - /* Write output */ \ - (output) = x; \ - } while (0) - -#define SINGLE_BITSLICE_INVSHIFTROWS(output, input, uintN_t) do \ - { \ - uintN_t mask, mask2, mask3, diff, x = (input); \ - /* Rotate rows 2 and 3 by 16 bits */ \ - mask = 0x00CC * (((uintN_t)~(uintN_t)0) / 0xFFFF); \ - diff = ((x >> 8) ^ x) & mask; \ - x ^= diff ^ (diff << 8); \ - /* Rotate rows 1 and 3 by 8 bits, the opposite way to ShiftRows */ \ - mask = 0x000A * (((uintN_t)~(uintN_t)0) / 0xFFFF); \ - mask2 = 0xAAA0 * (((uintN_t)~(uintN_t)0) / 0xFFFF); \ - mask3 = 0x5555 * (((uintN_t)~(uintN_t)0) / 0xFFFF); \ - x = ((x >> 12) & mask) | ((x << 4) & mask2) | (x & mask3); \ - /* Write output */ \ - (output) = x; \ - } while (0) - -#define BITSLICED_SHIFTROWS(output, input, uintN_t) do \ - { \ - ITERATE(SINGLE_BITSLICE_SHIFTROWS, output, input, uintN_t); \ - } while (0) - -#define BITSLICED_INVSHIFTROWS(output, input, uintN_t) do \ - { \ - ITERATE(SINGLE_BITSLICE_INVSHIFTROWS, output, input, uintN_t); \ - } while (0) - -/* ----- - * The MixColumns transformation. This has to operate on all eight bit - * slices at once, and also passes data back and forth between the - * bits in an adjacent group of 4 within each slice. - * - * Notation: let F = GF(2)[X]/ be the finite field - * used in AES, and let R = F[Y]/ be the ring whose elements - * represent the possible contents of a column of the matrix. I use X - * and Y below in those senses, i.e. X is the value in F that - * represents the byte 0x02, and Y is the value in R that cycles the - * four bytes around by one if you multiply by it. - */ - -/* Multiply every column by Y^3, i.e. cycle it round one place to the - * right. Operates on one bit slice at a time; you have to wrap it in - * ITERATE to affect all the data at once. */ -#define BITSLICED_MUL_BY_Y3(output, input, uintN_t) do \ - { \ - uintN_t mask, mask2, x; \ - mask = 0x8 * (((uintN_t)~(uintN_t)0) / 0xF); \ - mask2 = 0x7 * (((uintN_t)~(uintN_t)0) / 0xF); \ - x = input; \ - output = ((x << 3) & mask) ^ ((x >> 1) & mask2); \ - } while (0) - -/* Multiply every column by Y^2. */ -#define BITSLICED_MUL_BY_Y2(output, input, uintN_t) do \ - { \ - uintN_t mask, mask2, x; \ - mask = 0xC * (((uintN_t)~(uintN_t)0) / 0xF); \ - mask2 = 0x3 * (((uintN_t)~(uintN_t)0) / 0xF); \ - x = input; \ - output = ((x << 2) & mask) ^ ((x >> 2) & mask2); \ - } while (0) - -#define BITSLICED_MUL_BY_1_Y3(output, input, uintN_t) do \ - { \ - uintN_t tmp = input; \ - BITSLICED_MUL_BY_Y3(tmp, input, uintN_t); \ - output = input ^ tmp; \ - } while (0) - -/* Multiply every column by 1+Y^2. */ -#define BITSLICED_MUL_BY_1_Y2(output, input, uintN_t) do \ - { \ - uintN_t tmp = input; \ - BITSLICED_MUL_BY_Y2(tmp, input, uintN_t); \ - output = input ^ tmp; \ - } while (0) - -/* Multiply every field element by X. This has to feed data between - * slices, so it does the whole job in one go without needing ITERATE. */ -#define BITSLICED_MUL_BY_X(output, input, uintN_t) do \ - { \ - uintN_t bit7 = input[7]; \ - output[7] = input[6]; \ - output[6] = input[5]; \ - output[5] = input[4]; \ - output[4] = input[3] ^ bit7; \ - output[3] = input[2] ^ bit7; \ - output[2] = input[1]; \ - output[1] = input[0] ^ bit7; \ - output[0] = bit7; \ - } while (0) - -/* - * The MixColumns constant is - * M = X + Y + Y^2 + (X+1)Y^3 - * which we construct by rearranging it into - * M = 1 + (1+Y^3) [ X + (1+Y^2) ] - */ -#define BITSLICED_MIXCOLUMNS(output, input, uintN_t) do \ - { \ - uintN_t a[8], aX[8], b[8]; \ - /* a = input * (1+Y^3) */ \ - ITERATE(BITSLICED_MUL_BY_1_Y3, a, input, uintN_t); \ - /* aX = a * X */ \ - BITSLICED_MUL_BY_X(aX, a, uintN_t); \ - /* b = a * (1+Y^2) = input * (1+Y+Y^2+Y^3) */ \ - ITERATE(BITSLICED_MUL_BY_1_Y2, b, a, uintN_t); \ - /* output = input + aX + b (reusing a as a temp */ \ - BITSLICED_ADD(a, aX, b); \ - BITSLICED_ADD(output, input, a); \ - } while (0) - -/* - * The InvMixColumns constant, written out longhand, is - * I = (X^3+X^2+X) + (X^3+1)Y + (X^3+X^2+1)Y^2 + (X^3+X+1)Y^3 - * We represent this as - * I = (X^3+X^2+X+1)(Y^3+Y^2+Y+1) + 1 + X(Y+Y^2) + X^2(Y+Y^3) - */ -#define BITSLICED_INVMIXCOLUMNS(output, input, uintN_t) do \ - { \ - /* We need input * X^i for i=1,...,3 */ \ - uintN_t X[8], X2[8], X3[8]; \ - BITSLICED_MUL_BY_X(X, input, uintN_t); \ - BITSLICED_MUL_BY_X(X2, X, uintN_t); \ - BITSLICED_MUL_BY_X(X3, X2, uintN_t); \ - /* Sum them all and multiply by 1+Y+Y^2+Y^3. */ \ - uintN_t S[8]; \ - BITSLICED_ADD(S, input, X); \ - BITSLICED_ADD(S, S, X2); \ - BITSLICED_ADD(S, S, X3); \ - ITERATE(BITSLICED_MUL_BY_1_Y3, S, S, uintN_t); \ - ITERATE(BITSLICED_MUL_BY_1_Y2, S, S, uintN_t); \ - /* Compute the X(Y+Y^2) term. */ \ - uintN_t A[8]; \ - ITERATE(BITSLICED_MUL_BY_1_Y3, A, X, uintN_t); \ - ITERATE(BITSLICED_MUL_BY_Y2, A, A, uintN_t); \ - /* Compute the X^2(Y+Y^3) term. */ \ - uintN_t B[8]; \ - ITERATE(BITSLICED_MUL_BY_1_Y2, B, X2, uintN_t); \ - ITERATE(BITSLICED_MUL_BY_Y3, B, B, uintN_t); \ - /* And add all the pieces together. */ \ - BITSLICED_ADD(S, S, input); \ - BITSLICED_ADD(S, S, A); \ - BITSLICED_ADD(output, S, B); \ - } while (0) - -/* ----- - * Put it all together into a cipher round. - */ - -/* Dummy macro to get rid of the MixColumns in the final round. */ -#define NO_MIXCOLUMNS(out, in, uintN_t) do {} while (0) - -#define ENCRYPT_ROUND_FN(suffix, uintN_t, mixcol_macro) \ - static void aes_sliced_round_e_##suffix( \ - uintN_t output[8], const uintN_t input[8], const uintN_t roundkey[8]) \ - { \ - BITSLICED_SUBBYTES(output, input, uintN_t); \ - BITSLICED_SHIFTROWS(output, output, uintN_t); \ - mixcol_macro(output, output, uintN_t); \ - BITSLICED_ADD(output, output, roundkey); \ - } - -ENCRYPT_ROUND_FN(serial, uint16_t, BITSLICED_MIXCOLUMNS) -ENCRYPT_ROUND_FN(serial_last, uint16_t, NO_MIXCOLUMNS) -ENCRYPT_ROUND_FN(parallel, BignumInt, BITSLICED_MIXCOLUMNS) -ENCRYPT_ROUND_FN(parallel_last, BignumInt, NO_MIXCOLUMNS) - -#define DECRYPT_ROUND_FN(suffix, uintN_t, mixcol_macro) \ - static void aes_sliced_round_d_##suffix( \ - uintN_t output[8], const uintN_t input[8], const uintN_t roundkey[8]) \ - { \ - BITSLICED_ADD(output, input, roundkey); \ - mixcol_macro(output, output, uintN_t); \ - BITSLICED_INVSUBBYTES(output, output, uintN_t); \ - BITSLICED_INVSHIFTROWS(output, output, uintN_t); \ - } - -#if 0 /* no cipher mode we support requires serial decryption */ -DECRYPT_ROUND_FN(serial, uint16_t, BITSLICED_INVMIXCOLUMNS) -DECRYPT_ROUND_FN(serial_first, uint16_t, NO_MIXCOLUMNS) -#endif -DECRYPT_ROUND_FN(parallel, BignumInt, BITSLICED_INVMIXCOLUMNS) -DECRYPT_ROUND_FN(parallel_first, BignumInt, NO_MIXCOLUMNS) - -/* ----- - * Key setup function. - */ - -typedef struct aes_sliced_key aes_sliced_key; -struct aes_sliced_key { - BignumInt roundkeys_parallel[MAXROUNDKEYS * 8]; - uint16_t roundkeys_serial[MAXROUNDKEYS * 8]; - unsigned rounds; -}; - -static void aes_sliced_key_setup( - aes_sliced_key *sk, const void *vkey, size_t keybits) -{ - const unsigned char *key = (const unsigned char *)vkey; - - size_t key_words = keybits / 32; - sk->rounds = key_words + 6; - size_t sched_words = (sk->rounds + 1) * 4; - - unsigned rconpos = 0; - - uint16_t *outslices = sk->roundkeys_serial; - unsigned outshift = 0; - - memset(sk->roundkeys_serial, 0, sizeof(sk->roundkeys_serial)); - - uint8_t inblk[16]; - memset(inblk, 0, 16); - uint16_t slices[8]; - - for (size_t i = 0; i < sched_words; i++) { - /* - * Prepare a word of round key in the low 4 bits of each - * integer in slices[]. - */ - if (i < key_words) { - memcpy(inblk, key + 4*i, 4); - TO_BITSLICES(slices, inblk, uint16_t, =, 0); - } else { - unsigned wordindex, bitshift; - uint16_t *prevslices; - - /* Fetch the (i-1)th key word */ - wordindex = i-1; - bitshift = 4 * (wordindex & 3); - prevslices = sk->roundkeys_serial + 8 * (wordindex >> 2); - for (size_t i = 0; i < 8; i++) - slices[i] = prevslices[i] >> bitshift; - - /* Decide what we're doing in this expansion stage */ - bool rotate_and_round_constant = (i % key_words == 0); - bool sub = rotate_and_round_constant || - (key_words == 8 && i % 8 == 4); - - if (rotate_and_round_constant) { - for (size_t i = 0; i < 8; i++) - slices[i] = ((slices[i] << 3) | (slices[i] >> 1)) & 0xF; - } - - if (sub) { - /* Apply the SubBytes transform to the key word. But - * here we need to apply the _full_ SubBytes from the - * spec, including the constant which our S-box leaves - * out. */ - BITSLICED_SUBBYTES(slices, slices, uint16_t); - slices[0] ^= 0xFFFF; - slices[1] ^= 0xFFFF; - slices[5] ^= 0xFFFF; - slices[6] ^= 0xFFFF; - } - - if (rotate_and_round_constant) { - assert(rconpos < lenof(key_setup_round_constants)); - uint8_t rcon = key_setup_round_constants[rconpos++]; - for (size_t i = 0; i < 8; i++) - slices[i] ^= 1 & (rcon >> i); - } - - /* Combine with the (i-Nk)th key word */ - wordindex = i - key_words; - bitshift = 4 * (wordindex & 3); - prevslices = sk->roundkeys_serial + 8 * (wordindex >> 2); - for (size_t i = 0; i < 8; i++) - slices[i] ^= prevslices[i] >> bitshift; - } - - /* - * Now copy it into sk. - */ - for (unsigned b = 0; b < 8; b++) - outslices[b] |= (slices[b] & 0xF) << outshift; - outshift += 4; - if (outshift == 16) { - outshift = 0; - outslices += 8; - } - } - - smemclr(inblk, sizeof(inblk)); - smemclr(slices, sizeof(slices)); - - /* - * Add the S-box constant to every round key after the first one, - * compensating for it being left out in the main cipher. - */ - for (size_t i = 8; i < 8 * (sched_words/4); i += 8) { - sk->roundkeys_serial[i+0] ^= 0xFFFF; - sk->roundkeys_serial[i+1] ^= 0xFFFF; - sk->roundkeys_serial[i+5] ^= 0xFFFF; - sk->roundkeys_serial[i+6] ^= 0xFFFF; - } - - /* - * Replicate that set of round keys into larger integers for the - * parallel versions of the cipher. - */ - for (size_t i = 0; i < 8 * (sched_words / 4); i++) { - sk->roundkeys_parallel[i] = sk->roundkeys_serial[i] * - ((BignumInt)~(BignumInt)0 / 0xFFFF); - } -} - -/* ----- - * The full cipher primitive, including transforming the input and - * output to/from bit-sliced form. - */ - -#define ENCRYPT_FN(suffix, uintN_t, nblocks) \ - static void aes_sliced_e_##suffix( \ - uint8_t *output, const uint8_t *input, const aes_sliced_key *sk) \ - { \ - uintN_t state[8]; \ - TO_BITSLICES(state, input, uintN_t, =, 0); \ - for (unsigned i = 1; i < nblocks; i++) { \ - input += 16; \ - TO_BITSLICES(state, input, uintN_t, |=, i*16); \ - } \ - const uintN_t *keys = sk->roundkeys_##suffix; \ - BITSLICED_ADD(state, state, keys); \ - keys += 8; \ - for (unsigned i = 0; i < sk->rounds-1; i++) { \ - aes_sliced_round_e_##suffix(state, state, keys); \ - keys += 8; \ - } \ - aes_sliced_round_e_##suffix##_last(state, state, keys); \ - for (unsigned i = 0; i < nblocks; i++) { \ - FROM_BITSLICES(output, state, i*16); \ - output += 16; \ - } \ - } - -#define DECRYPT_FN(suffix, uintN_t, nblocks) \ - static void aes_sliced_d_##suffix( \ - uint8_t *output, const uint8_t *input, const aes_sliced_key *sk) \ - { \ - uintN_t state[8]; \ - TO_BITSLICES(state, input, uintN_t, =, 0); \ - for (unsigned i = 1; i < nblocks; i++) { \ - input += 16; \ - TO_BITSLICES(state, input, uintN_t, |=, i*16); \ - } \ - const uintN_t *keys = sk->roundkeys_##suffix + 8*sk->rounds; \ - aes_sliced_round_d_##suffix##_first(state, state, keys); \ - keys -= 8; \ - for (unsigned i = 0; i < sk->rounds-1; i++) { \ - aes_sliced_round_d_##suffix(state, state, keys); \ - keys -= 8; \ - } \ - BITSLICED_ADD(state, state, keys); \ - for (unsigned i = 0; i < nblocks; i++) { \ - FROM_BITSLICES(output, state, i*16); \ - output += 16; \ - } \ - } - -ENCRYPT_FN(serial, uint16_t, 1) -#if 0 /* no cipher mode we support requires serial decryption */ -DECRYPT_FN(serial, uint16_t, 1) -#endif -ENCRYPT_FN(parallel, BignumInt, SLICE_PARALLELISM) -DECRYPT_FN(parallel, BignumInt, SLICE_PARALLELISM) - -/* ----- - * The SSH interface and the cipher modes. - */ - -#define SDCTR_WORDS (16 / BIGNUM_INT_BYTES) - -typedef struct aes_sw_context aes_sw_context; -struct aes_sw_context { - aes_sliced_key sk; - union { - struct { - /* In CBC mode, the IV is just a copy of the last seen - * cipher block. */ - uint8_t prevblk[16]; - } cbc; - struct { - /* In SDCTR mode, we keep the counter itself in a form - * that's easy to increment. We also use the parallel - * version of the core AES function, so we'll encrypt - * multiple counter values in one go. That won't align - * nicely with the sizes of data we're asked to encrypt, - * so we must also store a cache of the last set of - * keystream blocks we generated, and our current position - * within that cache. */ - BignumInt counter[SDCTR_WORDS]; - uint8_t keystream[SLICE_PARALLELISM * 16]; - uint8_t *keystream_pos; - } sdctr; - } iv; - ssh_cipher ciph; -}; - -static ssh_cipher *aes_sw_new(const ssh_cipheralg *alg) -{ - aes_sw_context *ctx = snew(aes_sw_context); - ctx->ciph.vt = alg; - return &ctx->ciph; -} - -static void aes_sw_free(ssh_cipher *ciph) -{ - aes_sw_context *ctx = container_of(ciph, aes_sw_context, ciph); - smemclr(ctx, sizeof(*ctx)); - sfree(ctx); -} - -static void aes_sw_setkey(ssh_cipher *ciph, const void *vkey) -{ - aes_sw_context *ctx = container_of(ciph, aes_sw_context, ciph); - aes_sliced_key_setup(&ctx->sk, vkey, ctx->ciph.vt->real_keybits); -} - -static void aes_sw_setiv_cbc(ssh_cipher *ciph, const void *iv) -{ - aes_sw_context *ctx = container_of(ciph, aes_sw_context, ciph); - memcpy(ctx->iv.cbc.prevblk, iv, 16); -} - -static void aes_sw_setiv_sdctr(ssh_cipher *ciph, const void *viv) -{ - aes_sw_context *ctx = container_of(ciph, aes_sw_context, ciph); - const uint8_t *iv = (const uint8_t *)viv; - - /* Import the initial counter value into the internal representation */ - for (unsigned i = 0; i < SDCTR_WORDS; i++) - ctx->iv.sdctr.counter[i] = - GET_BIGNUMINT_MSB_FIRST( - iv + 16 - BIGNUM_INT_BYTES - i*BIGNUM_INT_BYTES); - - /* Set keystream_pos to indicate that the keystream cache is - * currently empty */ - ctx->iv.sdctr.keystream_pos = - ctx->iv.sdctr.keystream + sizeof(ctx->iv.sdctr.keystream); -} - -typedef void (*aes_sw_fn)(uint32_t v[4], const uint32_t *keysched); - -static inline void memxor16(void *vout, const void *vlhs, const void *vrhs) -{ - uint8_t *out = (uint8_t *)vout; - const uint8_t *lhs = (const uint8_t *)vlhs, *rhs = (const uint8_t *)vrhs; - uint64_t w; - - w = GET_64BIT_LSB_FIRST(lhs); - w ^= GET_64BIT_LSB_FIRST(rhs); - PUT_64BIT_LSB_FIRST(out, w); - w = GET_64BIT_LSB_FIRST(lhs + 8); - w ^= GET_64BIT_LSB_FIRST(rhs + 8); - PUT_64BIT_LSB_FIRST(out + 8, w); -} - -static inline void aes_cbc_sw_encrypt( - ssh_cipher *ciph, void *vblk, int blklen) -{ - aes_sw_context *ctx = container_of(ciph, aes_sw_context, ciph); - - /* - * CBC encryption has to be done serially, because the input to - * each run of the cipher includes the output from the previous - * run. - */ - - for (uint8_t *blk = (uint8_t *)vblk, *finish = blk + blklen; - blk < finish; blk += 16) { - /* - * We use the IV array itself as the location for the - * encryption, because there's no reason not to. - */ - - /* XOR the new plaintext block into the previous cipher block */ - memxor16(ctx->iv.cbc.prevblk, ctx->iv.cbc.prevblk, blk); - - /* Run the cipher over the result, which leaves it - * conveniently already stored in ctx->iv */ - aes_sliced_e_serial( - ctx->iv.cbc.prevblk, ctx->iv.cbc.prevblk, &ctx->sk); - - /* Copy it to the output location */ - memcpy(blk, ctx->iv.cbc.prevblk, 16); - } -} - -static inline void aes_cbc_sw_decrypt( - ssh_cipher *ciph, void *vblk, int blklen) -{ - aes_sw_context *ctx = container_of(ciph, aes_sw_context, ciph); - uint8_t *blk = (uint8_t *)vblk; - - /* - * CBC decryption can run in parallel, because all the - * _ciphertext_ blocks are already available. - */ - - size_t blocks_remaining = blklen / 16; - - uint8_t data[SLICE_PARALLELISM * 16]; - /* Zeroing the data array is probably overcautious, but it avoids - * technically undefined behaviour from leaving it uninitialised - * if our very first iteration doesn't include enough cipher - * blocks to populate it fully */ - memset(data, 0, sizeof(data)); - - while (blocks_remaining > 0) { - /* Number of blocks we'll handle in this iteration. If we're - * dealing with fewer than the maximum, it doesn't matter - - * it's harmless to run the full parallel cipher function - * anyway. */ - size_t blocks = (blocks_remaining < SLICE_PARALLELISM ? - blocks_remaining : SLICE_PARALLELISM); - - /* Parallel-decrypt the input, in a separate array so we still - * have the cipher stream available for XORing. */ - memcpy(data, blk, 16 * blocks); - aes_sliced_d_parallel(data, data, &ctx->sk); - - /* Write the output and update the IV */ - for (size_t i = 0; i < blocks; i++) { - uint8_t *decrypted = data + 16*i; - uint8_t *output = blk + 16*i; - - memxor16(decrypted, decrypted, ctx->iv.cbc.prevblk); - memcpy(ctx->iv.cbc.prevblk, output, 16); - memcpy(output, decrypted, 16); - } - - /* Advance the input pointer. */ - blk += 16 * blocks; - blocks_remaining -= blocks; - } - - smemclr(data, sizeof(data)); -} - -static inline void aes_sdctr_sw( - ssh_cipher *ciph, void *vblk, int blklen) -{ - aes_sw_context *ctx = container_of(ciph, aes_sw_context, ciph); - - /* - * SDCTR encrypt/decrypt loops round one block at a time XORing - * the keystream into the user's data, and periodically has to run - * a parallel encryption operation to get more keystream. - */ - - uint8_t *keystream_end = - ctx->iv.sdctr.keystream + sizeof(ctx->iv.sdctr.keystream); - - for (uint8_t *blk = (uint8_t *)vblk, *finish = blk + blklen; - blk < finish; blk += 16) { - - if (ctx->iv.sdctr.keystream_pos == keystream_end) { - /* - * Generate some keystream. - */ - for (uint8_t *block = ctx->iv.sdctr.keystream; - block < keystream_end; block += 16) { - /* Format the counter value into the buffer. */ - for (unsigned i = 0; i < SDCTR_WORDS; i++) - PUT_BIGNUMINT_MSB_FIRST( - block + 16 - BIGNUM_INT_BYTES - i*BIGNUM_INT_BYTES, - ctx->iv.sdctr.counter[i]); - - /* Increment the counter. */ - BignumCarry carry = 1; - for (unsigned i = 0; i < SDCTR_WORDS; i++) - BignumADC(ctx->iv.sdctr.counter[i], carry, - ctx->iv.sdctr.counter[i], 0, carry); - } - - /* Encrypt all those counter blocks. */ - aes_sliced_e_parallel(ctx->iv.sdctr.keystream, - ctx->iv.sdctr.keystream, &ctx->sk); - - /* Reset keystream_pos to the start of the buffer. */ - ctx->iv.sdctr.keystream_pos = ctx->iv.sdctr.keystream; - } - - memxor16(blk, blk, ctx->iv.sdctr.keystream_pos); - ctx->iv.sdctr.keystream_pos += 16; - } -} - -#define SW_ENC_DEC(len) \ - static void aes##len##_cbc_sw_encrypt( \ - ssh_cipher *ciph, void *vblk, int blklen) \ - { aes_cbc_sw_encrypt(ciph, vblk, blklen); } \ - static void aes##len##_cbc_sw_decrypt( \ - ssh_cipher *ciph, void *vblk, int blklen) \ - { aes_cbc_sw_decrypt(ciph, vblk, blklen); } \ - static void aes##len##_sdctr_sw( \ - ssh_cipher *ciph, void *vblk, int blklen) \ - { aes_sdctr_sw(ciph, vblk, blklen); } - -SW_ENC_DEC(128) -SW_ENC_DEC(192) -SW_ENC_DEC(256) - -/* ---------------------------------------------------------------------- - * Hardware-accelerated implementation of AES using x86 AES-NI. - */ - -#if HW_AES == HW_AES_NI - -/* - * Set target architecture for Clang and GCC - */ -#if !defined(__clang__) && defined(__GNUC__) -# pragma GCC target("aes") -# pragma GCC target("sse4.1") -#endif - -#if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8))) -# define FUNC_ISA __attribute__ ((target("sse4.1,aes"))) -#else -# define FUNC_ISA -#endif - -#include -#include - -#if defined(__clang__) || defined(__GNUC__) -#include -#define GET_CPU_ID(out) __cpuid(1, (out)[0], (out)[1], (out)[2], (out)[3]) -#else -#define GET_CPU_ID(out) __cpuid(out, 1) -#endif - -bool aes_hw_available(void) -{ - /* - * Determine if AES is available on this CPU, by checking that - * both AES itself and SSE4.1 are supported. - */ - unsigned int CPUInfo[4]; - GET_CPU_ID(CPUInfo); - return (CPUInfo[2] & (1 << 25)) && (CPUInfo[2] & (1 << 19)); -} - -/* - * Core AES-NI encrypt/decrypt functions, one per length and direction. - */ - -#define NI_CIPHER(len, dir, dirlong, repmacro) \ - static FUNC_ISA inline __m128i aes_ni_##len##_##dir( \ - __m128i v, const __m128i *keysched) \ - { \ - v = _mm_xor_si128(v, *keysched++); \ - repmacro(v = _mm_aes##dirlong##_si128(v, *keysched++);); \ - return _mm_aes##dirlong##last_si128(v, *keysched); \ - } - -NI_CIPHER(128, e, enc, REP9) -NI_CIPHER(128, d, dec, REP9) -NI_CIPHER(192, e, enc, REP11) -NI_CIPHER(192, d, dec, REP11) -NI_CIPHER(256, e, enc, REP13) -NI_CIPHER(256, d, dec, REP13) - -/* - * The main key expansion. - */ -static FUNC_ISA void aes_ni_key_expand( - const unsigned char *key, size_t key_words, - __m128i *keysched_e, __m128i *keysched_d) -{ - size_t rounds = key_words + 6; - size_t sched_words = (rounds + 1) * 4; - - /* - * Store the key schedule as 32-bit integers during expansion, so - * that it's easy to refer back to individual previous words. We - * collect them into the final __m128i form at the end. - */ - uint32_t sched[MAXROUNDKEYS * 4]; - - unsigned rconpos = 0; - - for (size_t i = 0; i < sched_words; i++) { - if (i < key_words) { - sched[i] = GET_32BIT_LSB_FIRST(key + 4 * i); - } else { - uint32_t temp = sched[i - 1]; - - bool rotate_and_round_constant = (i % key_words == 0); - bool only_sub = (key_words == 8 && i % 8 == 4); - - if (rotate_and_round_constant) { - __m128i v = _mm_setr_epi32(0,temp,0,0); - v = _mm_aeskeygenassist_si128(v, 0); - temp = _mm_extract_epi32(v, 1); - - assert(rconpos < lenof(key_setup_round_constants)); - temp ^= key_setup_round_constants[rconpos++]; - } else if (only_sub) { - __m128i v = _mm_setr_epi32(0,temp,0,0); - v = _mm_aeskeygenassist_si128(v, 0); - temp = _mm_extract_epi32(v, 0); - } - - sched[i] = sched[i - key_words] ^ temp; - } - } - - /* - * Combine the key schedule words into __m128i vectors and store - * them in the output context. - */ - for (size_t round = 0; round <= rounds; round++) - keysched_e[round] = _mm_setr_epi32( - sched[4*round ], sched[4*round+1], - sched[4*round+2], sched[4*round+3]); - - smemclr(sched, sizeof(sched)); - - /* - * Now prepare the modified keys for the inverse cipher. - */ - for (size_t eround = 0; eround <= rounds; eround++) { - size_t dround = rounds - eround; - __m128i rkey = keysched_e[eround]; - if (eround && dround) /* neither first nor last */ - rkey = _mm_aesimc_si128(rkey); - keysched_d[dround] = rkey; - } -} - -/* - * Auxiliary routine to increment the 128-bit counter used in SDCTR - * mode. - */ -static FUNC_ISA inline __m128i aes_ni_sdctr_increment(__m128i v) -{ - const __m128i ONE = _mm_setr_epi32(1,0,0,0); - const __m128i ZERO = _mm_setzero_si128(); - - /* Increment the low-order 64 bits of v */ - v = _mm_add_epi64(v, ONE); - /* Check if they've become zero */ - __m128i cmp = _mm_cmpeq_epi64(v, ZERO); - /* If so, the low half of cmp is all 1s. Pack that into the high - * half of addend with zero in the low half. */ - __m128i addend = _mm_unpacklo_epi64(ZERO, cmp); - /* And subtract that from v, which increments the high 64 bits iff - * the low 64 wrapped round. */ - v = _mm_sub_epi64(v, addend); - - return v; -} - -/* - * Auxiliary routine to reverse the byte order of a vector, so that - * the SDCTR IV can be made big-endian for feeding to the cipher. - */ -static FUNC_ISA inline __m128i aes_ni_sdctr_reverse(__m128i v) -{ - v = _mm_shuffle_epi8( - v, _mm_setr_epi8(15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0)); - return v; -} - -/* - * The SSH interface and the cipher modes. - */ - -typedef struct aes_ni_context aes_ni_context; -struct aes_ni_context { - __m128i keysched_e[MAXROUNDKEYS], keysched_d[MAXROUNDKEYS], iv; - - void *pointer_to_free; - ssh_cipher ciph; -}; - -static ssh_cipher *aes_hw_new(const ssh_cipheralg *alg) -{ - if (!aes_hw_available_cached()) - return NULL; - - /* - * The __m128i variables in the context structure need to be - * 16-byte aligned, but not all malloc implementations that this - * code has to work with will guarantee to return a 16-byte - * aligned pointer. So we over-allocate, manually realign the - * pointer ourselves, and store the original one inside the - * context so we know how to free it later. - */ - void *allocation = smalloc(sizeof(aes_ni_context) + 15); - uintptr_t alloc_address = (uintptr_t)allocation; - uintptr_t aligned_address = (alloc_address + 15) & ~15; - aes_ni_context *ctx = (aes_ni_context *)aligned_address; - - ctx->ciph.vt = alg; - ctx->pointer_to_free = allocation; - return &ctx->ciph; -} - -static void aes_hw_free(ssh_cipher *ciph) -{ - aes_ni_context *ctx = container_of(ciph, aes_ni_context, ciph); - void *allocation = ctx->pointer_to_free; - smemclr(ctx, sizeof(*ctx)); - sfree(allocation); -} - -static void aes_hw_setkey(ssh_cipher *ciph, const void *vkey) -{ - aes_ni_context *ctx = container_of(ciph, aes_ni_context, ciph); - const unsigned char *key = (const unsigned char *)vkey; - - aes_ni_key_expand(key, ctx->ciph.vt->real_keybits / 32, - ctx->keysched_e, ctx->keysched_d); -} - -static FUNC_ISA void aes_hw_setiv_cbc(ssh_cipher *ciph, const void *iv) -{ - aes_ni_context *ctx = container_of(ciph, aes_ni_context, ciph); - ctx->iv = _mm_loadu_si128(iv); -} - -static FUNC_ISA void aes_hw_setiv_sdctr(ssh_cipher *ciph, const void *iv) -{ - aes_ni_context *ctx = container_of(ciph, aes_ni_context, ciph); - __m128i counter = _mm_loadu_si128(iv); - ctx->iv = aes_ni_sdctr_reverse(counter); -} - -typedef __m128i (*aes_ni_fn)(__m128i v, const __m128i *keysched); - -static FUNC_ISA inline void aes_cbc_ni_encrypt( - ssh_cipher *ciph, void *vblk, int blklen, aes_ni_fn encrypt) -{ - aes_ni_context *ctx = container_of(ciph, aes_ni_context, ciph); - - for (uint8_t *blk = (uint8_t *)vblk, *finish = blk + blklen; - blk < finish; blk += 16) { - __m128i plaintext = _mm_loadu_si128((const __m128i *)blk); - __m128i cipher_input = _mm_xor_si128(plaintext, ctx->iv); - __m128i ciphertext = encrypt(cipher_input, ctx->keysched_e); - _mm_storeu_si128((__m128i *)blk, ciphertext); - ctx->iv = ciphertext; - } -} - -static FUNC_ISA inline void aes_cbc_ni_decrypt( - ssh_cipher *ciph, void *vblk, int blklen, aes_ni_fn decrypt) -{ - aes_ni_context *ctx = container_of(ciph, aes_ni_context, ciph); - - for (uint8_t *blk = (uint8_t *)vblk, *finish = blk + blklen; - blk < finish; blk += 16) { - __m128i ciphertext = _mm_loadu_si128((const __m128i *)blk); - __m128i decrypted = decrypt(ciphertext, ctx->keysched_d); - __m128i plaintext = _mm_xor_si128(decrypted, ctx->iv); - _mm_storeu_si128((__m128i *)blk, plaintext); - ctx->iv = ciphertext; - } -} - -static FUNC_ISA inline void aes_sdctr_ni( - ssh_cipher *ciph, void *vblk, int blklen, aes_ni_fn encrypt) -{ - aes_ni_context *ctx = container_of(ciph, aes_ni_context, ciph); - - for (uint8_t *blk = (uint8_t *)vblk, *finish = blk + blklen; - blk < finish; blk += 16) { - __m128i counter = aes_ni_sdctr_reverse(ctx->iv); - __m128i keystream = encrypt(counter, ctx->keysched_e); - __m128i input = _mm_loadu_si128((const __m128i *)blk); - __m128i output = _mm_xor_si128(input, keystream); - _mm_storeu_si128((__m128i *)blk, output); - ctx->iv = aes_ni_sdctr_increment(ctx->iv); - } -} - -#define NI_ENC_DEC(len) \ - static FUNC_ISA void aes##len##_cbc_hw_encrypt( \ - ssh_cipher *ciph, void *vblk, int blklen) \ - { aes_cbc_ni_encrypt(ciph, vblk, blklen, aes_ni_##len##_e); } \ - static FUNC_ISA void aes##len##_cbc_hw_decrypt( \ - ssh_cipher *ciph, void *vblk, int blklen) \ - { aes_cbc_ni_decrypt(ciph, vblk, blklen, aes_ni_##len##_d); } \ - static FUNC_ISA void aes##len##_sdctr_hw( \ - ssh_cipher *ciph, void *vblk, int blklen) \ - { aes_sdctr_ni(ciph, vblk, blklen, aes_ni_##len##_e); } \ - -NI_ENC_DEC(128) -NI_ENC_DEC(192) -NI_ENC_DEC(256) - -/* ---------------------------------------------------------------------- - * Hardware-accelerated implementation of AES using Arm NEON. - */ - -#elif HW_AES == HW_AES_NEON - -/* - * Manually set the target architecture, if we decided above that we - * need to. - */ -#ifdef USE_CLANG_ATTR_TARGET_AARCH64 -/* - * A spot of cheating: redefine some ACLE feature macros before - * including arm_neon.h. Otherwise we won't get the AES intrinsics - * defined by that header, because it will be looking at the settings - * for the whole translation unit rather than the ones we're going to - * put on some particular functions using __attribute__((target)). - */ -#define __ARM_NEON 1 -#define __ARM_FEATURE_CRYPTO 1 -#define FUNC_ISA __attribute__ ((target("neon,crypto"))) -#endif /* USE_CLANG_ATTR_TARGET_AARCH64 */ - -#ifndef FUNC_ISA -#define FUNC_ISA -#endif - -#ifdef USE_ARM64_NEON_H -#include -#else -#include -#endif - -static bool aes_hw_available(void) -{ - /* - * For Arm, we delegate to a per-platform AES detection function, - * because it has to be implemented by asking the operating system - * rather than directly querying the CPU. - * - * That's because Arm systems commonly have multiple cores that - * are not all alike, so any method of querying whether NEON - * crypto instructions work on the _current_ CPU - even one as - * crude as just trying one and catching the SIGILL - wouldn't - * give an answer that you could still rely on the first time the - * OS migrated your process to another CPU. - */ - return platform_aes_hw_available(); -} - -/* - * Core NEON encrypt/decrypt functions, one per length and direction. - */ - -#define NEON_CIPHER(len, repmacro) \ - static FUNC_ISA inline uint8x16_t aes_neon_##len##_e( \ - uint8x16_t v, const uint8x16_t *keysched) \ - { \ - repmacro(v = vaesmcq_u8(vaeseq_u8(v, *keysched++));); \ - v = vaeseq_u8(v, *keysched++); \ - return veorq_u8(v, *keysched); \ - } \ - static FUNC_ISA inline uint8x16_t aes_neon_##len##_d( \ - uint8x16_t v, const uint8x16_t *keysched) \ - { \ - repmacro(v = vaesimcq_u8(vaesdq_u8(v, *keysched++));); \ - v = vaesdq_u8(v, *keysched++); \ - return veorq_u8(v, *keysched); \ - } - -NEON_CIPHER(128, REP9) -NEON_CIPHER(192, REP11) -NEON_CIPHER(256, REP13) - -/* - * The main key expansion. - */ -static FUNC_ISA void aes_neon_key_expand( - const unsigned char *key, size_t key_words, - uint8x16_t *keysched_e, uint8x16_t *keysched_d) -{ - size_t rounds = key_words + 6; - size_t sched_words = (rounds + 1) * 4; - - /* - * Store the key schedule as 32-bit integers during expansion, so - * that it's easy to refer back to individual previous words. We - * collect them into the final uint8x16_t form at the end. - */ - uint32_t sched[MAXROUNDKEYS * 4]; - - unsigned rconpos = 0; - - for (size_t i = 0; i < sched_words; i++) { - if (i < key_words) { - sched[i] = GET_32BIT_LSB_FIRST(key + 4 * i); - } else { - uint32_t temp = sched[i - 1]; - - bool rotate_and_round_constant = (i % key_words == 0); - bool sub = rotate_and_round_constant || - (key_words == 8 && i % 8 == 4); - - if (rotate_and_round_constant) - temp = (temp << 24) | (temp >> 8); - - if (sub) { - uint32x4_t v32 = vdupq_n_u32(temp); - uint8x16_t v8 = vreinterpretq_u8_u32(v32); - v8 = vaeseq_u8(v8, vdupq_n_u8(0)); - v32 = vreinterpretq_u32_u8(v8); - temp = vget_lane_u32(vget_low_u32(v32), 0); - } - - if (rotate_and_round_constant) { - assert(rconpos < lenof(key_setup_round_constants)); - temp ^= key_setup_round_constants[rconpos++]; - } - - sched[i] = sched[i - key_words] ^ temp; - } - } - - /* - * Combine the key schedule words into uint8x16_t vectors and - * store them in the output context. - */ - for (size_t round = 0; round <= rounds; round++) - keysched_e[round] = vreinterpretq_u8_u32(vld1q_u32(sched + 4*round)); - - smemclr(sched, sizeof(sched)); - - /* - * Now prepare the modified keys for the inverse cipher. - */ - for (size_t eround = 0; eround <= rounds; eround++) { - size_t dround = rounds - eround; - uint8x16_t rkey = keysched_e[eround]; - if (eround && dround) /* neither first nor last */ - rkey = vaesimcq_u8(rkey); - keysched_d[dround] = rkey; - } -} - -/* - * Auxiliary routine to reverse the byte order of a vector, so that - * the SDCTR IV can be made big-endian for feeding to the cipher. - * - * In fact we don't need to reverse the vector _all_ the way; we leave - * the two lanes in MSW,LSW order, because that makes no difference to - * the efficiency of the increment. That way we only have to reverse - * bytes within each lane in this function. - */ -static FUNC_ISA inline uint8x16_t aes_neon_sdctr_reverse(uint8x16_t v) -{ - return vrev64q_u8(v); -} - -/* - * Auxiliary routine to increment the 128-bit counter used in SDCTR - * mode. There's no instruction to treat a 128-bit vector as a single - * long integer, so instead we have to increment the bottom half - * unconditionally, and the top half if the bottom half started off as - * all 1s (in which case there was about to be a carry). - */ -static FUNC_ISA inline uint8x16_t aes_neon_sdctr_increment(uint8x16_t in) -{ -#ifdef __aarch64__ - /* There will be a carry if the low 64 bits are all 1s. */ - uint64x1_t all1 = vcreate_u64(0xFFFFFFFFFFFFFFFF); - uint64x1_t carry = vceq_u64(vget_high_u64(vreinterpretq_u64_u8(in)), all1); - - /* Make a word whose bottom half is unconditionally all 1s, and - * the top half is 'carry', i.e. all 0s most of the time but all - * 1s if we need to increment the top half. Then that word is what - * we need to _subtract_ from the input counter. */ - uint64x2_t subtrahend = vcombine_u64(carry, all1); -#else - /* AArch32 doesn't have comparisons that operate on a 64-bit lane, - * so we start by comparing each 32-bit half of the low 64 bits - * _separately_ to all-1s. */ - uint32x2_t all1 = vdup_n_u32(0xFFFFFFFF); - uint32x2_t carry = vceq_u32( - vget_high_u32(vreinterpretq_u32_u8(in)), all1); - - /* Swap the 32-bit words of the compare output, and AND with the - * unswapped version. Now carry is all 1s iff the bottom half of - * the input counter was all 1s, and all 0s otherwise. */ - carry = vand_u32(carry, vrev64_u32(carry)); - - /* Now make the vector to subtract in the same way as above. */ - uint64x2_t subtrahend = vreinterpretq_u64_u32(vcombine_u32(carry, all1)); -#endif - - return vreinterpretq_u8_u64( - vsubq_u64(vreinterpretq_u64_u8(in), subtrahend)); -} - -/* - * The SSH interface and the cipher modes. - */ - -typedef struct aes_neon_context aes_neon_context; -struct aes_neon_context { - uint8x16_t keysched_e[MAXROUNDKEYS], keysched_d[MAXROUNDKEYS], iv; - - ssh_cipher ciph; -}; - -static ssh_cipher *aes_hw_new(const ssh_cipheralg *alg) -{ - if (!aes_hw_available_cached()) - return NULL; - - aes_neon_context *ctx = snew(aes_neon_context); - ctx->ciph.vt = alg; - return &ctx->ciph; -} - -static void aes_hw_free(ssh_cipher *ciph) -{ - aes_neon_context *ctx = container_of(ciph, aes_neon_context, ciph); - smemclr(ctx, sizeof(*ctx)); - sfree(ctx); -} - -static void aes_hw_setkey(ssh_cipher *ciph, const void *vkey) -{ - aes_neon_context *ctx = container_of(ciph, aes_neon_context, ciph); - const unsigned char *key = (const unsigned char *)vkey; - - aes_neon_key_expand(key, ctx->ciph.vt->real_keybits / 32, - ctx->keysched_e, ctx->keysched_d); -} - -static FUNC_ISA void aes_hw_setiv_cbc(ssh_cipher *ciph, const void *iv) -{ - aes_neon_context *ctx = container_of(ciph, aes_neon_context, ciph); - ctx->iv = vld1q_u8(iv); -} - -static FUNC_ISA void aes_hw_setiv_sdctr(ssh_cipher *ciph, const void *iv) -{ - aes_neon_context *ctx = container_of(ciph, aes_neon_context, ciph); - uint8x16_t counter = vld1q_u8(iv); - ctx->iv = aes_neon_sdctr_reverse(counter); -} - -typedef uint8x16_t (*aes_neon_fn)(uint8x16_t v, const uint8x16_t *keysched); - -static FUNC_ISA inline void aes_cbc_neon_encrypt( - ssh_cipher *ciph, void *vblk, int blklen, aes_neon_fn encrypt) -{ - aes_neon_context *ctx = container_of(ciph, aes_neon_context, ciph); - - for (uint8_t *blk = (uint8_t *)vblk, *finish = blk + blklen; - blk < finish; blk += 16) { - uint8x16_t plaintext = vld1q_u8(blk); - uint8x16_t cipher_input = veorq_u8(plaintext, ctx->iv); - uint8x16_t ciphertext = encrypt(cipher_input, ctx->keysched_e); - vst1q_u8(blk, ciphertext); - ctx->iv = ciphertext; - } -} - -static FUNC_ISA inline void aes_cbc_neon_decrypt( - ssh_cipher *ciph, void *vblk, int blklen, aes_neon_fn decrypt) -{ - aes_neon_context *ctx = container_of(ciph, aes_neon_context, ciph); - - for (uint8_t *blk = (uint8_t *)vblk, *finish = blk + blklen; - blk < finish; blk += 16) { - uint8x16_t ciphertext = vld1q_u8(blk); - uint8x16_t decrypted = decrypt(ciphertext, ctx->keysched_d); - uint8x16_t plaintext = veorq_u8(decrypted, ctx->iv); - vst1q_u8(blk, plaintext); - ctx->iv = ciphertext; - } -} - -static FUNC_ISA inline void aes_sdctr_neon( - ssh_cipher *ciph, void *vblk, int blklen, aes_neon_fn encrypt) -{ - aes_neon_context *ctx = container_of(ciph, aes_neon_context, ciph); - - for (uint8_t *blk = (uint8_t *)vblk, *finish = blk + blklen; - blk < finish; blk += 16) { - uint8x16_t counter = aes_neon_sdctr_reverse(ctx->iv); - uint8x16_t keystream = encrypt(counter, ctx->keysched_e); - uint8x16_t input = vld1q_u8(blk); - uint8x16_t output = veorq_u8(input, keystream); - vst1q_u8(blk, output); - ctx->iv = aes_neon_sdctr_increment(ctx->iv); - } -} - -#define NEON_ENC_DEC(len) \ - static FUNC_ISA void aes##len##_cbc_hw_encrypt( \ - ssh_cipher *ciph, void *vblk, int blklen) \ - { aes_cbc_neon_encrypt(ciph, vblk, blklen, aes_neon_##len##_e); } \ - static FUNC_ISA void aes##len##_cbc_hw_decrypt( \ - ssh_cipher *ciph, void *vblk, int blklen) \ - { aes_cbc_neon_decrypt(ciph, vblk, blklen, aes_neon_##len##_d); } \ - static FUNC_ISA void aes##len##_sdctr_hw( \ - ssh_cipher *ciph, void *vblk, int blklen) \ - { aes_sdctr_neon(ciph, vblk, blklen, aes_neon_##len##_e); } \ - -NEON_ENC_DEC(128) -NEON_ENC_DEC(192) -NEON_ENC_DEC(256) - -/* ---------------------------------------------------------------------- - * Stub functions if we have no hardware-accelerated AES. In this - * case, aes_hw_new returns NULL (though it should also never be - * selected by aes_select, so the only thing that should even be - * _able_ to call it is testcrypt). As a result, the remaining vtable - * functions should never be called at all. - */ - -#elif HW_AES == HW_AES_NONE - -bool aes_hw_available(void) -{ - return false; -} - -static ssh_cipher *aes_hw_new(const ssh_cipheralg *alg) -{ - return NULL; -} - -#define STUB_BODY { unreachable("Should never be called"); } - -static void aes_hw_free(ssh_cipher *ciph) STUB_BODY -static void aes_hw_setkey(ssh_cipher *ciph, const void *key) STUB_BODY -static void aes_hw_setiv_cbc(ssh_cipher *ciph, const void *iv) STUB_BODY -static void aes_hw_setiv_sdctr(ssh_cipher *ciph, const void *iv) STUB_BODY -#define STUB_ENC_DEC(len) \ - static void aes##len##_cbc_hw_encrypt( \ - ssh_cipher *ciph, void *vblk, int blklen) STUB_BODY \ - static void aes##len##_cbc_hw_decrypt( \ - ssh_cipher *ciph, void *vblk, int blklen) STUB_BODY \ - static void aes##len##_sdctr_hw( \ - ssh_cipher *ciph, void *vblk, int blklen) STUB_BODY - -STUB_ENC_DEC(128) -STUB_ENC_DEC(192) -STUB_ENC_DEC(256) - -#endif /* HW_AES */ diff --git a/crypto/aes.h b/crypto/aes.h new file mode 100644 index 00000000..1960713a --- /dev/null +++ b/crypto/aes.h @@ -0,0 +1,109 @@ +/* + * Definitions likely to be helpful to multiple AES implementations. + */ + +/* + * The 'extra' structure used by AES implementations is used to + * include information about how to check if a given implementation is + * available at run time, and whether we've already checked. + */ +struct aes_extra_mutable; +struct aes_extra { + /* Function to check availability. Might be expensive, so we don't + * want to call it more than once. */ + bool (*check_available)(void); + + /* Point to a writable substructure. */ + struct aes_extra_mutable *mut; +}; +struct aes_extra_mutable { + bool checked_availability; + bool is_available; +}; +static inline bool check_availability(const struct aes_extra *extra) +{ + if (!extra->mut->checked_availability) { + extra->mut->is_available = extra->check_available(); + extra->mut->checked_availability = true; + } + + return extra->mut->is_available; +} + +/* + * Macros to define vtables for AES variants. There are a lot of + * these, because of the cross product between cipher modes, key + * sizes, and assorted HW/SW implementations, so it's worth spending + * some effort here to reduce the boilerplate in the sub-files. + */ + +#define AES_EXTRA(impl_c) \ + static struct aes_extra_mutable aes ## impl_c ## _extra_mut; \ + static const struct aes_extra aes ## impl_c ## _extra = { \ + .check_available = aes ## impl_c ## _available, \ + .mut = &aes ## impl_c ## _extra_mut, \ + } + +#define AES_CBC_VTABLE(impl_c, impl_display, bits) \ + const ssh_cipheralg ssh_aes ## bits ## _cbc ## impl_c = { \ + .new = aes ## impl_c ## _new, \ + .free = aes ## impl_c ## _free, \ + .setiv = aes ## impl_c ## _setiv_cbc, \ + .setkey = aes ## impl_c ## _setkey, \ + .encrypt = aes ## bits ## impl_c ## _cbc_encrypt, \ + .decrypt = aes ## bits ## impl_c ## _cbc_decrypt, \ + .ssh2_id = "aes" #bits "-cbc", \ + .blksize = 16, \ + .real_keybits = bits, \ + .padded_keybytes = bits/8, \ + .flags = SSH_CIPHER_IS_CBC, \ + .text_name = "AES-" #bits " CBC (" impl_display ")", \ + .extra = &aes ## impl_c ## _extra, \ + } + +#define AES_SDCTR_VTABLE(impl_c, impl_display, bits) \ + const ssh_cipheralg ssh_aes ## bits ## _sdctr ## impl_c = { \ + .new = aes ## impl_c ## _new, \ + .free = aes ## impl_c ## _free, \ + .setiv = aes ## impl_c ## _setiv_sdctr, \ + .setkey = aes ## impl_c ## _setkey, \ + .encrypt = aes ## bits ## impl_c ## _sdctr, \ + .decrypt = aes ## bits ## impl_c ## _sdctr, \ + .ssh2_id = "aes" #bits "-ctr", \ + .blksize = 16, \ + .real_keybits = bits, \ + .padded_keybytes = bits/8, \ + .flags = 0, \ + .text_name = "AES-" #bits " SDCTR (" impl_display ")", \ + .extra = &aes ## impl_c ## _extra, \ + } + +#define AES_ALL_VTABLES(impl_c, impl_display) \ + AES_CBC_VTABLE(impl_c, impl_display, 128); \ + AES_CBC_VTABLE(impl_c, impl_display, 192); \ + AES_CBC_VTABLE(impl_c, impl_display, 256); \ + AES_SDCTR_VTABLE(impl_c, impl_display, 128); \ + AES_SDCTR_VTABLE(impl_c, impl_display, 192); \ + AES_SDCTR_VTABLE(impl_c, impl_display, 256) + +/* + * Macros to repeat a piece of code particular numbers of times that + * correspond to 1 fewer than the number of AES rounds. (Because the + * last round is different.) + */ +#define REP2(x) x x +#define REP4(x) REP2(REP2(x)) +#define REP8(x) REP2(REP4(x)) +#define REP9(x) REP8(x) x +#define REP11(x) REP8(x) REP2(x) x +#define REP13(x) REP8(x) REP4(x) x + +/* + * The round constants used in key schedule expansion. + */ +extern const uint8_t aes_key_setup_round_constants[10]; + +/* + * The largest number of round keys ever needed. + */ +#define MAXROUNDKEYS 15 diff --git a/crypto/sha1-common.c b/crypto/sha1-common.c new file mode 100644 index 00000000..bf1db67a --- /dev/null +++ b/crypto/sha1-common.c @@ -0,0 +1,10 @@ +/* + * Common variable definitions across all the SHA-1 implementations. + */ + +#include "ssh.h" +#include "sha1.h" + +const uint32_t sha1_initial_state[5] = { + 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0, +}; diff --git a/crypto/sha1-neon.c b/crypto/sha1-neon.c new file mode 100644 index 00000000..99045714 --- /dev/null +++ b/crypto/sha1-neon.c @@ -0,0 +1,190 @@ +/* + * Hardware-accelerated implementation of SHA-1 using Arm NEON. + */ + +#include "ssh.h" +#include "sha1.h" + +#if USE_ARM64_NEON_H +#include +#else +#include +#endif + +static bool sha1_neon_available(void) +{ + /* + * For Arm, we delegate to a per-platform detection function (see + * explanation in aes-neon.c). + */ + return platform_sha1_neon_available(); +} + +typedef struct sha1_neon_core sha1_neon_core; +struct sha1_neon_core { + uint32x4_t abcd; + uint32_t e; +}; + +static inline uint32x4_t sha1_neon_load_input(const uint8_t *p) +{ + return vreinterpretq_u32_u8(vrev32q_u8(vld1q_u8(p))); +} + +static inline uint32x4_t sha1_neon_schedule_update( + uint32x4_t m4, uint32x4_t m3, uint32x4_t m2, uint32x4_t m1) +{ + return vsha1su1q_u32(vsha1su0q_u32(m4, m3, m2), m1); +} + +/* + * SHA-1 has three different kinds of round, differing in whether they + * use the Ch, Maj or Par functions defined above. Each one uses a + * separate NEON instruction, so we define three inline functions for + * the different round types using this macro. + * + * The two batches of Par-type rounds also use a different constant, + * but that's passed in as an operand, so we don't need a fourth + * inline function just for that. + */ +#define SHA1_NEON_ROUND_FN(type) \ + static inline sha1_neon_core sha1_neon_round4_##type( \ + sha1_neon_core old, uint32x4_t sched, uint32x4_t constant) \ + { \ + sha1_neon_core new; \ + uint32x4_t round_input = vaddq_u32(sched, constant); \ + new.abcd = vsha1##type##q_u32(old.abcd, old.e, round_input); \ + new.e = vsha1h_u32(vget_lane_u32(vget_low_u32(old.abcd), 0)); \ + return new; \ + } +SHA1_NEON_ROUND_FN(c) +SHA1_NEON_ROUND_FN(p) +SHA1_NEON_ROUND_FN(m) + +static inline void sha1_neon_block(sha1_neon_core *core, const uint8_t *p) +{ + uint32x4_t constant, s0, s1, s2, s3; + sha1_neon_core cr = *core; + + constant = vdupq_n_u32(SHA1_STAGE0_CONSTANT); + s0 = sha1_neon_load_input(p); + cr = sha1_neon_round4_c(cr, s0, constant); + s1 = sha1_neon_load_input(p + 16); + cr = sha1_neon_round4_c(cr, s1, constant); + s2 = sha1_neon_load_input(p + 32); + cr = sha1_neon_round4_c(cr, s2, constant); + s3 = sha1_neon_load_input(p + 48); + cr = sha1_neon_round4_c(cr, s3, constant); + s0 = sha1_neon_schedule_update(s0, s1, s2, s3); + cr = sha1_neon_round4_c(cr, s0, constant); + + constant = vdupq_n_u32(SHA1_STAGE1_CONSTANT); + s1 = sha1_neon_schedule_update(s1, s2, s3, s0); + cr = sha1_neon_round4_p(cr, s1, constant); + s2 = sha1_neon_schedule_update(s2, s3, s0, s1); + cr = sha1_neon_round4_p(cr, s2, constant); + s3 = sha1_neon_schedule_update(s3, s0, s1, s2); + cr = sha1_neon_round4_p(cr, s3, constant); + s0 = sha1_neon_schedule_update(s0, s1, s2, s3); + cr = sha1_neon_round4_p(cr, s0, constant); + s1 = sha1_neon_schedule_update(s1, s2, s3, s0); + cr = sha1_neon_round4_p(cr, s1, constant); + + constant = vdupq_n_u32(SHA1_STAGE2_CONSTANT); + s2 = sha1_neon_schedule_update(s2, s3, s0, s1); + cr = sha1_neon_round4_m(cr, s2, constant); + s3 = sha1_neon_schedule_update(s3, s0, s1, s2); + cr = sha1_neon_round4_m(cr, s3, constant); + s0 = sha1_neon_schedule_update(s0, s1, s2, s3); + cr = sha1_neon_round4_m(cr, s0, constant); + s1 = sha1_neon_schedule_update(s1, s2, s3, s0); + cr = sha1_neon_round4_m(cr, s1, constant); + s2 = sha1_neon_schedule_update(s2, s3, s0, s1); + cr = sha1_neon_round4_m(cr, s2, constant); + + constant = vdupq_n_u32(SHA1_STAGE3_CONSTANT); + s3 = sha1_neon_schedule_update(s3, s0, s1, s2); + cr = sha1_neon_round4_p(cr, s3, constant); + s0 = sha1_neon_schedule_update(s0, s1, s2, s3); + cr = sha1_neon_round4_p(cr, s0, constant); + s1 = sha1_neon_schedule_update(s1, s2, s3, s0); + cr = sha1_neon_round4_p(cr, s1, constant); + s2 = sha1_neon_schedule_update(s2, s3, s0, s1); + cr = sha1_neon_round4_p(cr, s2, constant); + s3 = sha1_neon_schedule_update(s3, s0, s1, s2); + cr = sha1_neon_round4_p(cr, s3, constant); + + core->abcd = vaddq_u32(core->abcd, cr.abcd); + core->e += cr.e; +} + +typedef struct sha1_neon { + sha1_neon_core core; + sha1_block blk; + BinarySink_IMPLEMENTATION; + ssh_hash hash; +} sha1_neon; + +static void sha1_neon_write(BinarySink *bs, const void *vp, size_t len); + +static ssh_hash *sha1_neon_new(const ssh_hashalg *alg) +{ + const struct sha1_extra *extra = (const struct sha1_extra *)alg->extra; + if (!check_availability(extra)) + return NULL; + + sha1_neon *s = snew(sha1_neon); + + s->hash.vt = alg; + BinarySink_INIT(s, sha1_neon_write); + BinarySink_DELEGATE_INIT(&s->hash, s); + return &s->hash; +} + +static void sha1_neon_reset(ssh_hash *hash) +{ + sha1_neon *s = container_of(hash, sha1_neon, hash); + + s->core.abcd = vld1q_u32(sha1_initial_state); + s->core.e = sha1_initial_state[4]; + + sha1_block_setup(&s->blk); +} + +static void sha1_neon_copyfrom(ssh_hash *hcopy, ssh_hash *horig) +{ + sha1_neon *copy = container_of(hcopy, sha1_neon, hash); + sha1_neon *orig = container_of(horig, sha1_neon, hash); + + *copy = *orig; /* structure copy */ + + BinarySink_COPIED(copy); + BinarySink_DELEGATE_INIT(©->hash, copy); +} + +static void sha1_neon_free(ssh_hash *hash) +{ + sha1_neon *s = container_of(hash, sha1_neon, hash); + smemclr(s, sizeof(*s)); + sfree(s); +} + +static void sha1_neon_write(BinarySink *bs, const void *vp, size_t len) +{ + sha1_neon *s = BinarySink_DOWNCAST(bs, sha1_neon); + + while (len > 0) + if (sha1_block_write(&s->blk, &vp, &len)) + sha1_neon_block(&s->core, s->blk.block); +} + +static void sha1_neon_digest(ssh_hash *hash, uint8_t *digest) +{ + sha1_neon *s = container_of(hash, sha1_neon, hash); + + sha1_block_pad(&s->blk, BinarySink_UPCAST(s)); + vst1q_u8(digest, vrev32q_u8(vreinterpretq_u8_u32(s->core.abcd))); + PUT_32BIT_MSB_FIRST(digest + 16, s->core.e); +} + +SHA1_VTABLE(neon, "NEON accelerated"); diff --git a/crypto/sha1-ni.c b/crypto/sha1-ni.c new file mode 100644 index 00000000..04e6386b --- /dev/null +++ b/crypto/sha1-ni.c @@ -0,0 +1,325 @@ +/* + * Hardware-accelerated implementation of SHA-1 using x86 SHA-NI. + */ + +#include "ssh.h" +#include "sha1.h" + +#include +#include +#include +#if HAVE_SHAINTRIN_H +#include +#endif + +#if defined(__clang__) || defined(__GNUC__) +#include +#define GET_CPU_ID_0(out) \ + __cpuid(0, (out)[0], (out)[1], (out)[2], (out)[3]) +#define GET_CPU_ID_7(out) \ + __cpuid_count(7, 0, (out)[0], (out)[1], (out)[2], (out)[3]) +#else +#define GET_CPU_ID_0(out) __cpuid(out, 0) +#define GET_CPU_ID_7(out) __cpuidex(out, 7, 0) +#endif + +static bool sha1_ni_available(void) +{ + unsigned int CPUInfo[4]; + GET_CPU_ID_0(CPUInfo); + if (CPUInfo[0] < 7) + return false; + + GET_CPU_ID_7(CPUInfo); + return CPUInfo[1] & (1 << 29); /* Check SHA */ +} + +/* SHA1 implementation using new instructions + The code is based on Jeffrey Walton's SHA1 implementation: + https://github.com/noloader/SHA-Intrinsics +*/ +static inline void sha1_ni_block(__m128i *core, const uint8_t *p) +{ + __m128i ABCD, E0, E1, MSG0, MSG1, MSG2, MSG3; + const __m128i MASK = _mm_set_epi64x( + 0x0001020304050607ULL, 0x08090a0b0c0d0e0fULL); + + const __m128i *block = (const __m128i *)p; + + /* Load initial values */ + ABCD = core[0]; + E0 = core[1]; + + /* Rounds 0-3 */ + MSG0 = _mm_loadu_si128(block); + MSG0 = _mm_shuffle_epi8(MSG0, MASK); + E0 = _mm_add_epi32(E0, MSG0); + E1 = ABCD; + ABCD = _mm_sha1rnds4_epu32(ABCD, E0, 0); + + /* Rounds 4-7 */ + MSG1 = _mm_loadu_si128(block + 1); + MSG1 = _mm_shuffle_epi8(MSG1, MASK); + E1 = _mm_sha1nexte_epu32(E1, MSG1); + E0 = ABCD; + ABCD = _mm_sha1rnds4_epu32(ABCD, E1, 0); + MSG0 = _mm_sha1msg1_epu32(MSG0, MSG1); + + /* Rounds 8-11 */ + MSG2 = _mm_loadu_si128(block + 2); + MSG2 = _mm_shuffle_epi8(MSG2, MASK); + E0 = _mm_sha1nexte_epu32(E0, MSG2); + E1 = ABCD; + ABCD = _mm_sha1rnds4_epu32(ABCD, E0, 0); + MSG1 = _mm_sha1msg1_epu32(MSG1, MSG2); + MSG0 = _mm_xor_si128(MSG0, MSG2); + + /* Rounds 12-15 */ + MSG3 = _mm_loadu_si128(block + 3); + MSG3 = _mm_shuffle_epi8(MSG3, MASK); + E1 = _mm_sha1nexte_epu32(E1, MSG3); + E0 = ABCD; + MSG0 = _mm_sha1msg2_epu32(MSG0, MSG3); + ABCD = _mm_sha1rnds4_epu32(ABCD, E1, 0); + MSG2 = _mm_sha1msg1_epu32(MSG2, MSG3); + MSG1 = _mm_xor_si128(MSG1, MSG3); + + /* Rounds 16-19 */ + E0 = _mm_sha1nexte_epu32(E0, MSG0); + E1 = ABCD; + MSG1 = _mm_sha1msg2_epu32(MSG1, MSG0); + ABCD = _mm_sha1rnds4_epu32(ABCD, E0, 0); + MSG3 = _mm_sha1msg1_epu32(MSG3, MSG0); + MSG2 = _mm_xor_si128(MSG2, MSG0); + + /* Rounds 20-23 */ + E1 = _mm_sha1nexte_epu32(E1, MSG1); + E0 = ABCD; + MSG2 = _mm_sha1msg2_epu32(MSG2, MSG1); + ABCD = _mm_sha1rnds4_epu32(ABCD, E1, 1); + MSG0 = _mm_sha1msg1_epu32(MSG0, MSG1); + MSG3 = _mm_xor_si128(MSG3, MSG1); + + /* Rounds 24-27 */ + E0 = _mm_sha1nexte_epu32(E0, MSG2); + E1 = ABCD; + MSG3 = _mm_sha1msg2_epu32(MSG3, MSG2); + ABCD = _mm_sha1rnds4_epu32(ABCD, E0, 1); + MSG1 = _mm_sha1msg1_epu32(MSG1, MSG2); + MSG0 = _mm_xor_si128(MSG0, MSG2); + + /* Rounds 28-31 */ + E1 = _mm_sha1nexte_epu32(E1, MSG3); + E0 = ABCD; + MSG0 = _mm_sha1msg2_epu32(MSG0, MSG3); + ABCD = _mm_sha1rnds4_epu32(ABCD, E1, 1); + MSG2 = _mm_sha1msg1_epu32(MSG2, MSG3); + MSG1 = _mm_xor_si128(MSG1, MSG3); + + /* Rounds 32-35 */ + E0 = _mm_sha1nexte_epu32(E0, MSG0); + E1 = ABCD; + MSG1 = _mm_sha1msg2_epu32(MSG1, MSG0); + ABCD = _mm_sha1rnds4_epu32(ABCD, E0, 1); + MSG3 = _mm_sha1msg1_epu32(MSG3, MSG0); + MSG2 = _mm_xor_si128(MSG2, MSG0); + + /* Rounds 36-39 */ + E1 = _mm_sha1nexte_epu32(E1, MSG1); + E0 = ABCD; + MSG2 = _mm_sha1msg2_epu32(MSG2, MSG1); + ABCD = _mm_sha1rnds4_epu32(ABCD, E1, 1); + MSG0 = _mm_sha1msg1_epu32(MSG0, MSG1); + MSG3 = _mm_xor_si128(MSG3, MSG1); + + /* Rounds 40-43 */ + E0 = _mm_sha1nexte_epu32(E0, MSG2); + E1 = ABCD; + MSG3 = _mm_sha1msg2_epu32(MSG3, MSG2); + ABCD = _mm_sha1rnds4_epu32(ABCD, E0, 2); + MSG1 = _mm_sha1msg1_epu32(MSG1, MSG2); + MSG0 = _mm_xor_si128(MSG0, MSG2); + + /* Rounds 44-47 */ + E1 = _mm_sha1nexte_epu32(E1, MSG3); + E0 = ABCD; + MSG0 = _mm_sha1msg2_epu32(MSG0, MSG3); + ABCD = _mm_sha1rnds4_epu32(ABCD, E1, 2); + MSG2 = _mm_sha1msg1_epu32(MSG2, MSG3); + MSG1 = _mm_xor_si128(MSG1, MSG3); + + /* Rounds 48-51 */ + E0 = _mm_sha1nexte_epu32(E0, MSG0); + E1 = ABCD; + MSG1 = _mm_sha1msg2_epu32(MSG1, MSG0); + ABCD = _mm_sha1rnds4_epu32(ABCD, E0, 2); + MSG3 = _mm_sha1msg1_epu32(MSG3, MSG0); + MSG2 = _mm_xor_si128(MSG2, MSG0); + + /* Rounds 52-55 */ + E1 = _mm_sha1nexte_epu32(E1, MSG1); + E0 = ABCD; + MSG2 = _mm_sha1msg2_epu32(MSG2, MSG1); + ABCD = _mm_sha1rnds4_epu32(ABCD, E1, 2); + MSG0 = _mm_sha1msg1_epu32(MSG0, MSG1); + MSG3 = _mm_xor_si128(MSG3, MSG1); + + /* Rounds 56-59 */ + E0 = _mm_sha1nexte_epu32(E0, MSG2); + E1 = ABCD; + MSG3 = _mm_sha1msg2_epu32(MSG3, MSG2); + ABCD = _mm_sha1rnds4_epu32(ABCD, E0, 2); + MSG1 = _mm_sha1msg1_epu32(MSG1, MSG2); + MSG0 = _mm_xor_si128(MSG0, MSG2); + + /* Rounds 60-63 */ + E1 = _mm_sha1nexte_epu32(E1, MSG3); + E0 = ABCD; + MSG0 = _mm_sha1msg2_epu32(MSG0, MSG3); + ABCD = _mm_sha1rnds4_epu32(ABCD, E1, 3); + MSG2 = _mm_sha1msg1_epu32(MSG2, MSG3); + MSG1 = _mm_xor_si128(MSG1, MSG3); + + /* Rounds 64-67 */ + E0 = _mm_sha1nexte_epu32(E0, MSG0); + E1 = ABCD; + MSG1 = _mm_sha1msg2_epu32(MSG1, MSG0); + ABCD = _mm_sha1rnds4_epu32(ABCD, E0, 3); + MSG3 = _mm_sha1msg1_epu32(MSG3, MSG0); + MSG2 = _mm_xor_si128(MSG2, MSG0); + + /* Rounds 68-71 */ + E1 = _mm_sha1nexte_epu32(E1, MSG1); + E0 = ABCD; + MSG2 = _mm_sha1msg2_epu32(MSG2, MSG1); + ABCD = _mm_sha1rnds4_epu32(ABCD, E1, 3); + MSG3 = _mm_xor_si128(MSG3, MSG1); + + /* Rounds 72-75 */ + E0 = _mm_sha1nexte_epu32(E0, MSG2); + E1 = ABCD; + MSG3 = _mm_sha1msg2_epu32(MSG3, MSG2); + ABCD = _mm_sha1rnds4_epu32(ABCD, E0, 3); + + /* Rounds 76-79 */ + E1 = _mm_sha1nexte_epu32(E1, MSG3); + E0 = ABCD; + ABCD = _mm_sha1rnds4_epu32(ABCD, E1, 3); + + /* Combine state */ + core[0] = _mm_add_epi32(ABCD, core[0]); + core[1] = _mm_sha1nexte_epu32(E0, core[1]); +} + +typedef struct sha1_ni { + /* + * core[0] stores the first four words of the SHA-1 state. core[1] + * stores just the fifth word, in the vector lane at the highest + * address. + */ + __m128i core[2]; + sha1_block blk; + void *pointer_to_free; + BinarySink_IMPLEMENTATION; + ssh_hash hash; +} sha1_ni; + +static void sha1_ni_write(BinarySink *bs, const void *vp, size_t len); + +static sha1_ni *sha1_ni_alloc(void) +{ + /* + * The __m128i variables in the context structure need to be + * 16-byte aligned, but not all malloc implementations that this + * code has to work with will guarantee to return a 16-byte + * aligned pointer. So we over-allocate, manually realign the + * pointer ourselves, and store the original one inside the + * context so we know how to free it later. + */ + void *allocation = smalloc(sizeof(sha1_ni) + 15); + uintptr_t alloc_address = (uintptr_t)allocation; + uintptr_t aligned_address = (alloc_address + 15) & ~15; + sha1_ni *s = (sha1_ni *)aligned_address; + s->pointer_to_free = allocation; + return s; +} + +static ssh_hash *sha1_ni_new(const ssh_hashalg *alg) +{ + const struct sha1_extra *extra = (const struct sha1_extra *)alg->extra; + if (!check_availability(extra)) + return NULL; + + sha1_ni *s = sha1_ni_alloc(); + + s->hash.vt = alg; + BinarySink_INIT(s, sha1_ni_write); + BinarySink_DELEGATE_INIT(&s->hash, s); + return &s->hash; +} + +static void sha1_ni_reset(ssh_hash *hash) +{ + sha1_ni *s = container_of(hash, sha1_ni, hash); + + /* Initialise the core vectors in their storage order */ + s->core[0] = _mm_set_epi64x( + 0x67452301efcdab89ULL, 0x98badcfe10325476ULL); + s->core[1] = _mm_set_epi32(0xc3d2e1f0, 0, 0, 0); + + sha1_block_setup(&s->blk); +} + +static void sha1_ni_copyfrom(ssh_hash *hcopy, ssh_hash *horig) +{ + sha1_ni *copy = container_of(hcopy, sha1_ni, hash); + sha1_ni *orig = container_of(horig, sha1_ni, hash); + + void *ptf_save = copy->pointer_to_free; + *copy = *orig; /* structure copy */ + copy->pointer_to_free = ptf_save; + + BinarySink_COPIED(copy); + BinarySink_DELEGATE_INIT(©->hash, copy); +} + +static void sha1_ni_free(ssh_hash *hash) +{ + sha1_ni *s = container_of(hash, sha1_ni, hash); + + void *ptf = s->pointer_to_free; + smemclr(s, sizeof(*s)); + sfree(ptf); +} + +static void sha1_ni_write(BinarySink *bs, const void *vp, size_t len) +{ + sha1_ni *s = BinarySink_DOWNCAST(bs, sha1_ni); + + while (len > 0) + if (sha1_block_write(&s->blk, &vp, &len)) + sha1_ni_block(s->core, s->blk.block); +} + +static void sha1_ni_digest(ssh_hash *hash, uint8_t *digest) +{ + sha1_ni *s = container_of(hash, sha1_ni, hash); + + sha1_block_pad(&s->blk, BinarySink_UPCAST(s)); + + /* Rearrange the first vector into its output order */ + __m128i abcd = _mm_shuffle_epi32(s->core[0], 0x1B); + + /* Byte-swap it into the output endianness */ + const __m128i mask = _mm_setr_epi8(3,2,1,0,7,6,5,4,11,10,9,8,15,14,13,12); + abcd = _mm_shuffle_epi8(abcd, mask); + + /* And store it */ + _mm_storeu_si128((__m128i *)digest, abcd); + + /* Finally, store the leftover word */ + uint32_t e = _mm_extract_epi32(s->core[1], 3); + PUT_32BIT_MSB_FIRST(digest + 16, e); +} + +SHA1_VTABLE(ni, "SHA-NI accelerated"); diff --git a/crypto/sha1-select.c b/crypto/sha1-select.c new file mode 100644 index 00000000..1e8a6ce9 --- /dev/null +++ b/crypto/sha1-select.c @@ -0,0 +1,44 @@ +/* + * Top-level vtables to select a SHA-1 implementation. + */ + +#include +#include + +#include "putty.h" +#include "ssh.h" +#include "sha1.h" + +static ssh_hash *sha1_select(const ssh_hashalg *alg) +{ + static const ssh_hashalg *const real_algs[] = { +#if HAVE_SHA_NI + &ssh_sha1_ni, +#endif +#if HAVE_NEON_CRYPTO + &ssh_sha1_neon, +#endif + &ssh_sha1_sw, + NULL, + }; + + for (size_t i = 0; real_algs[i]; i++) { + const ssh_hashalg *alg = real_algs[i]; + const struct sha1_extra *alg_extra = + (const struct sha1_extra *)alg->extra; + if (check_availability(alg_extra)) + return ssh_hash_new(alg); + } + + /* We should never reach the NULL at the end of the list, because + * the last non-NULL entry should be software-only SHA-1, which + * is always available. */ + unreachable("sha1_select ran off the end of its list"); +} + +const ssh_hashalg ssh_sha1 = { + .new = sha1_select, + .hlen = 20, + .blocklen = 64, + HASHALG_NAMES_ANNOTATED("SHA-1", "dummy selector vtable"), +}; diff --git a/crypto/sha1-sw.c b/crypto/sha1-sw.c new file mode 100644 index 00000000..905d97f3 --- /dev/null +++ b/crypto/sha1-sw.c @@ -0,0 +1,155 @@ +/* + * Software implementation of SHA-1. + */ + +#include "ssh.h" +#include "sha1.h" + +static bool sha1_sw_available(void) +{ + /* Software SHA-1 is always available */ + return true; +} + +static inline uint32_t rol(uint32_t x, unsigned y) +{ + return (x << (31 & y)) | (x >> (31 & -y)); +} + +static inline uint32_t Ch(uint32_t ctrl, uint32_t if1, uint32_t if0) +{ + return if0 ^ (ctrl & (if1 ^ if0)); +} + +static inline uint32_t Maj(uint32_t x, uint32_t y, uint32_t z) +{ + return (x & y) | (z & (x | y)); +} + +static inline uint32_t Par(uint32_t x, uint32_t y, uint32_t z) +{ + return (x ^ y ^ z); +} + +static inline void sha1_sw_round( + unsigned round_index, const uint32_t *schedule, + uint32_t *a, uint32_t *b, uint32_t *c, uint32_t *d, uint32_t *e, + uint32_t f, uint32_t constant) +{ + *e = rol(*a, 5) + f + *e + schedule[round_index] + constant; + *b = rol(*b, 30); +} + +static void sha1_sw_block(uint32_t *core, const uint8_t *block) +{ + uint32_t w[SHA1_ROUNDS]; + uint32_t a,b,c,d,e; + + for (size_t t = 0; t < 16; t++) + w[t] = GET_32BIT_MSB_FIRST(block + 4*t); + + for (size_t t = 16; t < SHA1_ROUNDS; t++) + w[t] = rol(w[t - 3] ^ w[t - 8] ^ w[t - 14] ^ w[t - 16], 1); + + a = core[0]; b = core[1]; c = core[2]; d = core[3]; + e = core[4]; + + size_t t = 0; + for (size_t u = 0; u < SHA1_ROUNDS_PER_STAGE/5; u++) { + sha1_sw_round(t++,w, &a,&b,&c,&d,&e, Ch(b,c,d), SHA1_STAGE0_CONSTANT); + sha1_sw_round(t++,w, &e,&a,&b,&c,&d, Ch(a,b,c), SHA1_STAGE0_CONSTANT); + sha1_sw_round(t++,w, &d,&e,&a,&b,&c, Ch(e,a,b), SHA1_STAGE0_CONSTANT); + sha1_sw_round(t++,w, &c,&d,&e,&a,&b, Ch(d,e,a), SHA1_STAGE0_CONSTANT); + sha1_sw_round(t++,w, &b,&c,&d,&e,&a, Ch(c,d,e), SHA1_STAGE0_CONSTANT); + } + for (size_t u = 0; u < SHA1_ROUNDS_PER_STAGE/5; u++) { + sha1_sw_round(t++,w, &a,&b,&c,&d,&e, Par(b,c,d), SHA1_STAGE1_CONSTANT); + sha1_sw_round(t++,w, &e,&a,&b,&c,&d, Par(a,b,c), SHA1_STAGE1_CONSTANT); + sha1_sw_round(t++,w, &d,&e,&a,&b,&c, Par(e,a,b), SHA1_STAGE1_CONSTANT); + sha1_sw_round(t++,w, &c,&d,&e,&a,&b, Par(d,e,a), SHA1_STAGE1_CONSTANT); + sha1_sw_round(t++,w, &b,&c,&d,&e,&a, Par(c,d,e), SHA1_STAGE1_CONSTANT); + } + for (size_t u = 0; u < SHA1_ROUNDS_PER_STAGE/5; u++) { + sha1_sw_round(t++,w, &a,&b,&c,&d,&e, Maj(b,c,d), SHA1_STAGE2_CONSTANT); + sha1_sw_round(t++,w, &e,&a,&b,&c,&d, Maj(a,b,c), SHA1_STAGE2_CONSTANT); + sha1_sw_round(t++,w, &d,&e,&a,&b,&c, Maj(e,a,b), SHA1_STAGE2_CONSTANT); + sha1_sw_round(t++,w, &c,&d,&e,&a,&b, Maj(d,e,a), SHA1_STAGE2_CONSTANT); + sha1_sw_round(t++,w, &b,&c,&d,&e,&a, Maj(c,d,e), SHA1_STAGE2_CONSTANT); + } + for (size_t u = 0; u < SHA1_ROUNDS_PER_STAGE/5; u++) { + sha1_sw_round(t++,w, &a,&b,&c,&d,&e, Par(b,c,d), SHA1_STAGE3_CONSTANT); + sha1_sw_round(t++,w, &e,&a,&b,&c,&d, Par(a,b,c), SHA1_STAGE3_CONSTANT); + sha1_sw_round(t++,w, &d,&e,&a,&b,&c, Par(e,a,b), SHA1_STAGE3_CONSTANT); + sha1_sw_round(t++,w, &c,&d,&e,&a,&b, Par(d,e,a), SHA1_STAGE3_CONSTANT); + sha1_sw_round(t++,w, &b,&c,&d,&e,&a, Par(c,d,e), SHA1_STAGE3_CONSTANT); + } + + core[0] += a; core[1] += b; core[2] += c; core[3] += d; core[4] += e; + + smemclr(w, sizeof(w)); +} + +typedef struct sha1_sw { + uint32_t core[5]; + sha1_block blk; + BinarySink_IMPLEMENTATION; + ssh_hash hash; +} sha1_sw; + +static void sha1_sw_write(BinarySink *bs, const void *vp, size_t len); + +static ssh_hash *sha1_sw_new(const ssh_hashalg *alg) +{ + sha1_sw *s = snew(sha1_sw); + + s->hash.vt = alg; + BinarySink_INIT(s, sha1_sw_write); + BinarySink_DELEGATE_INIT(&s->hash, s); + return &s->hash; +} + +static void sha1_sw_reset(ssh_hash *hash) +{ + sha1_sw *s = container_of(hash, sha1_sw, hash); + + memcpy(s->core, sha1_initial_state, sizeof(s->core)); + sha1_block_setup(&s->blk); +} + +static void sha1_sw_copyfrom(ssh_hash *hcopy, ssh_hash *horig) +{ + sha1_sw *copy = container_of(hcopy, sha1_sw, hash); + sha1_sw *orig = container_of(horig, sha1_sw, hash); + + memcpy(copy, orig, sizeof(*copy)); + BinarySink_COPIED(copy); + BinarySink_DELEGATE_INIT(©->hash, copy); +} + +static void sha1_sw_free(ssh_hash *hash) +{ + sha1_sw *s = container_of(hash, sha1_sw, hash); + + smemclr(s, sizeof(*s)); + sfree(s); +} + +static void sha1_sw_write(BinarySink *bs, const void *vp, size_t len) +{ + sha1_sw *s = BinarySink_DOWNCAST(bs, sha1_sw); + + while (len > 0) + if (sha1_block_write(&s->blk, &vp, &len)) + sha1_sw_block(s->core, s->blk.block); +} + +static void sha1_sw_digest(ssh_hash *hash, uint8_t *digest) +{ + sha1_sw *s = container_of(hash, sha1_sw, hash); + + sha1_block_pad(&s->blk, BinarySink_UPCAST(s)); + for (size_t i = 0; i < 5; i++) + PUT_32BIT_MSB_FIRST(digest + 4*i, s->core[i]); +} + +SHA1_VTABLE(sw, "unaccelerated"); diff --git a/crypto/sha1.c b/crypto/sha1.c deleted file mode 100644 index 536d474f..00000000 --- a/crypto/sha1.c +++ /dev/null @@ -1,933 +0,0 @@ -/* - * SHA-1 algorithm as described at - * - * http://csrc.nist.gov/cryptval/shs.html - */ - -#include "ssh.h" -#include - -/* - * Start by deciding whether we can support hardware SHA at all. - */ -#define HW_SHA1_NONE 0 -#define HW_SHA1_NI 1 -#define HW_SHA1_NEON 2 - -#ifdef _FORCE_SHA_NI -# define HW_SHA1 HW_SHA1_NI -#elif defined(__clang__) -# if __has_attribute(target) && __has_include() && \ - (defined(__x86_64__) || defined(__i386)) -# define HW_SHA1 HW_SHA1_NI -# endif -#elif defined(__GNUC__) -# if (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 9)) && \ - (defined(__x86_64__) || defined(__i386)) -# define HW_SHA1 HW_SHA1_NI -# endif -#elif defined (_MSC_VER) -# if (defined(_M_X64) || defined(_M_IX86)) && _MSC_FULL_VER >= 150030729 -# define HW_SHA1 HW_SHA1_NI -# endif -#endif - -#ifdef _FORCE_SHA_NEON -# define HW_SHA1 HW_SHA1_NEON -#elif defined __BYTE_ORDER__ && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ - /* Arm can potentially support both endiannesses, but this code - * hasn't been tested on anything but little. If anyone wants to - * run big-endian, they'll need to fix it first. */ -#elif defined __ARM_FEATURE_CRYPTO - /* If the Arm crypto extension is available already, we can - * support NEON SHA without having to enable anything by hand */ -# define HW_SHA1 HW_SHA1_NEON -#elif defined(__clang__) -# if __has_attribute(target) && __has_include() && \ - (defined(__aarch64__)) - /* clang can enable the crypto extension in AArch64 using - * __attribute__((target)) */ -# define HW_SHA1 HW_SHA1_NEON -# define USE_CLANG_ATTR_TARGET_AARCH64 -# endif -#elif defined _MSC_VER - /* Visual Studio supports the crypto extension when targeting - * AArch64, but as of VS2017, the AArch32 header doesn't quite - * manage it (declaring the shae/shad intrinsics without a round - * key operand). */ -# if defined _M_ARM64 -# define HW_SHA1 HW_SHA1_NEON -# if defined _M_ARM64 -# define USE_ARM64_NEON_H /* unusual header name in this case */ -# endif -# endif -#endif - -#if defined _FORCE_SOFTWARE_SHA || !defined HW_SHA1 -# undef HW_SHA1 -# define HW_SHA1 HW_SHA1_NONE -#endif - -/* - * The actual query function that asks if hardware acceleration is - * available. - */ -static bool sha1_hw_available(void); - -/* - * The top-level selection function, caching the results of - * sha1_hw_available() so it only has to run once. - */ -static bool sha1_hw_available_cached(void) -{ - static bool initialised = false; - static bool hw_available; - if (!initialised) { - hw_available = sha1_hw_available(); - initialised = true; - } - return hw_available; -} - -static ssh_hash *sha1_select(const ssh_hashalg *alg) -{ - const ssh_hashalg *real_alg = - sha1_hw_available_cached() ? &ssh_sha1_hw : &ssh_sha1_sw; - - return ssh_hash_new(real_alg); -} - -const ssh_hashalg ssh_sha1 = { - .new = sha1_select, - .hlen = 20, - .blocklen = 64, - HASHALG_NAMES_ANNOTATED("SHA-1", "dummy selector vtable"), -}; - -/* ---------------------------------------------------------------------- - * Definitions likely to be helpful to multiple implementations. - */ - -static const uint32_t sha1_initial_state[] = { - 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0, -}; - -#define SHA1_ROUNDS_PER_STAGE 20 -#define SHA1_STAGE0_CONSTANT 0x5a827999 -#define SHA1_STAGE1_CONSTANT 0x6ed9eba1 -#define SHA1_STAGE2_CONSTANT 0x8f1bbcdc -#define SHA1_STAGE3_CONSTANT 0xca62c1d6 -#define SHA1_ROUNDS (4 * SHA1_ROUNDS_PER_STAGE) - -typedef struct sha1_block sha1_block; -struct sha1_block { - uint8_t block[64]; - size_t used; - uint64_t len; -}; - -static inline void sha1_block_setup(sha1_block *blk) -{ - blk->used = 0; - blk->len = 0; -} - -static inline bool sha1_block_write( - sha1_block *blk, const void **vdata, size_t *len) -{ - size_t blkleft = sizeof(blk->block) - blk->used; - size_t chunk = *len < blkleft ? *len : blkleft; - - const uint8_t *p = *vdata; - memcpy(blk->block + blk->used, p, chunk); - *vdata = p + chunk; - *len -= chunk; - blk->used += chunk; - blk->len += chunk; - - if (blk->used == sizeof(blk->block)) { - blk->used = 0; - return true; - } - - return false; -} - -static inline void sha1_block_pad(sha1_block *blk, BinarySink *bs) -{ - uint64_t final_len = blk->len << 3; - size_t pad = 1 + (63 & (55 - blk->used)); - - put_byte(bs, 0x80); - for (size_t i = 1; i < pad; i++) - put_byte(bs, 0); - put_uint64(bs, final_len); - - assert(blk->used == 0 && "Should have exactly hit a block boundary"); -} - -/* ---------------------------------------------------------------------- - * Software implementation of SHA-1. - */ - -static inline uint32_t rol(uint32_t x, unsigned y) -{ - return (x << (31 & y)) | (x >> (31 & -y)); -} - -static inline uint32_t Ch(uint32_t ctrl, uint32_t if1, uint32_t if0) -{ - return if0 ^ (ctrl & (if1 ^ if0)); -} - -static inline uint32_t Maj(uint32_t x, uint32_t y, uint32_t z) -{ - return (x & y) | (z & (x | y)); -} - -static inline uint32_t Par(uint32_t x, uint32_t y, uint32_t z) -{ - return (x ^ y ^ z); -} - -static inline void sha1_sw_round( - unsigned round_index, const uint32_t *schedule, - uint32_t *a, uint32_t *b, uint32_t *c, uint32_t *d, uint32_t *e, - uint32_t f, uint32_t constant) -{ - *e = rol(*a, 5) + f + *e + schedule[round_index] + constant; - *b = rol(*b, 30); -} - -static void sha1_sw_block(uint32_t *core, const uint8_t *block) -{ - uint32_t w[SHA1_ROUNDS]; - uint32_t a,b,c,d,e; - - for (size_t t = 0; t < 16; t++) - w[t] = GET_32BIT_MSB_FIRST(block + 4*t); - - for (size_t t = 16; t < SHA1_ROUNDS; t++) - w[t] = rol(w[t - 3] ^ w[t - 8] ^ w[t - 14] ^ w[t - 16], 1); - - a = core[0]; b = core[1]; c = core[2]; d = core[3]; - e = core[4]; - - size_t t = 0; - for (size_t u = 0; u < SHA1_ROUNDS_PER_STAGE/5; u++) { - sha1_sw_round(t++,w, &a,&b,&c,&d,&e, Ch(b,c,d), SHA1_STAGE0_CONSTANT); - sha1_sw_round(t++,w, &e,&a,&b,&c,&d, Ch(a,b,c), SHA1_STAGE0_CONSTANT); - sha1_sw_round(t++,w, &d,&e,&a,&b,&c, Ch(e,a,b), SHA1_STAGE0_CONSTANT); - sha1_sw_round(t++,w, &c,&d,&e,&a,&b, Ch(d,e,a), SHA1_STAGE0_CONSTANT); - sha1_sw_round(t++,w, &b,&c,&d,&e,&a, Ch(c,d,e), SHA1_STAGE0_CONSTANT); - } - for (size_t u = 0; u < SHA1_ROUNDS_PER_STAGE/5; u++) { - sha1_sw_round(t++,w, &a,&b,&c,&d,&e, Par(b,c,d), SHA1_STAGE1_CONSTANT); - sha1_sw_round(t++,w, &e,&a,&b,&c,&d, Par(a,b,c), SHA1_STAGE1_CONSTANT); - sha1_sw_round(t++,w, &d,&e,&a,&b,&c, Par(e,a,b), SHA1_STAGE1_CONSTANT); - sha1_sw_round(t++,w, &c,&d,&e,&a,&b, Par(d,e,a), SHA1_STAGE1_CONSTANT); - sha1_sw_round(t++,w, &b,&c,&d,&e,&a, Par(c,d,e), SHA1_STAGE1_CONSTANT); - } - for (size_t u = 0; u < SHA1_ROUNDS_PER_STAGE/5; u++) { - sha1_sw_round(t++,w, &a,&b,&c,&d,&e, Maj(b,c,d), SHA1_STAGE2_CONSTANT); - sha1_sw_round(t++,w, &e,&a,&b,&c,&d, Maj(a,b,c), SHA1_STAGE2_CONSTANT); - sha1_sw_round(t++,w, &d,&e,&a,&b,&c, Maj(e,a,b), SHA1_STAGE2_CONSTANT); - sha1_sw_round(t++,w, &c,&d,&e,&a,&b, Maj(d,e,a), SHA1_STAGE2_CONSTANT); - sha1_sw_round(t++,w, &b,&c,&d,&e,&a, Maj(c,d,e), SHA1_STAGE2_CONSTANT); - } - for (size_t u = 0; u < SHA1_ROUNDS_PER_STAGE/5; u++) { - sha1_sw_round(t++,w, &a,&b,&c,&d,&e, Par(b,c,d), SHA1_STAGE3_CONSTANT); - sha1_sw_round(t++,w, &e,&a,&b,&c,&d, Par(a,b,c), SHA1_STAGE3_CONSTANT); - sha1_sw_round(t++,w, &d,&e,&a,&b,&c, Par(e,a,b), SHA1_STAGE3_CONSTANT); - sha1_sw_round(t++,w, &c,&d,&e,&a,&b, Par(d,e,a), SHA1_STAGE3_CONSTANT); - sha1_sw_round(t++,w, &b,&c,&d,&e,&a, Par(c,d,e), SHA1_STAGE3_CONSTANT); - } - - core[0] += a; core[1] += b; core[2] += c; core[3] += d; core[4] += e; - - smemclr(w, sizeof(w)); -} - -typedef struct sha1_sw { - uint32_t core[5]; - sha1_block blk; - BinarySink_IMPLEMENTATION; - ssh_hash hash; -} sha1_sw; - -static void sha1_sw_write(BinarySink *bs, const void *vp, size_t len); - -static ssh_hash *sha1_sw_new(const ssh_hashalg *alg) -{ - sha1_sw *s = snew(sha1_sw); - - s->hash.vt = alg; - BinarySink_INIT(s, sha1_sw_write); - BinarySink_DELEGATE_INIT(&s->hash, s); - return &s->hash; -} - -static void sha1_sw_reset(ssh_hash *hash) -{ - sha1_sw *s = container_of(hash, sha1_sw, hash); - - memcpy(s->core, sha1_initial_state, sizeof(s->core)); - sha1_block_setup(&s->blk); -} - -static void sha1_sw_copyfrom(ssh_hash *hcopy, ssh_hash *horig) -{ - sha1_sw *copy = container_of(hcopy, sha1_sw, hash); - sha1_sw *orig = container_of(horig, sha1_sw, hash); - - memcpy(copy, orig, sizeof(*copy)); - BinarySink_COPIED(copy); - BinarySink_DELEGATE_INIT(©->hash, copy); -} - -static void sha1_sw_free(ssh_hash *hash) -{ - sha1_sw *s = container_of(hash, sha1_sw, hash); - - smemclr(s, sizeof(*s)); - sfree(s); -} - -static void sha1_sw_write(BinarySink *bs, const void *vp, size_t len) -{ - sha1_sw *s = BinarySink_DOWNCAST(bs, sha1_sw); - - while (len > 0) - if (sha1_block_write(&s->blk, &vp, &len)) - sha1_sw_block(s->core, s->blk.block); -} - -static void sha1_sw_digest(ssh_hash *hash, uint8_t *digest) -{ - sha1_sw *s = container_of(hash, sha1_sw, hash); - - sha1_block_pad(&s->blk, BinarySink_UPCAST(s)); - for (size_t i = 0; i < 5; i++) - PUT_32BIT_MSB_FIRST(digest + 4*i, s->core[i]); -} - -const ssh_hashalg ssh_sha1_sw = { - .new = sha1_sw_new, - .reset = sha1_sw_reset, - .copyfrom = sha1_sw_copyfrom, - .digest = sha1_sw_digest, - .free = sha1_sw_free, - .hlen = 20, - .blocklen = 64, - HASHALG_NAMES_ANNOTATED("SHA-1", "unaccelerated"), -}; - -/* ---------------------------------------------------------------------- - * Hardware-accelerated implementation of SHA-1 using x86 SHA-NI. - */ - -#if HW_SHA1 == HW_SHA1_NI - -/* - * Set target architecture for Clang and GCC - */ - -#if defined(__clang__) || defined(__GNUC__) -# define FUNC_ISA __attribute__ ((target("sse4.1,sha"))) -#if !defined(__clang__) -# pragma GCC target("sha") -# pragma GCC target("sse4.1") -#endif -#else -# define FUNC_ISA -#endif - -#include -#include -#include -#if defined(__clang__) || defined(__GNUC__) -#include -#endif - -#if defined(__clang__) || defined(__GNUC__) -#include -#define GET_CPU_ID_0(out) \ - __cpuid(0, (out)[0], (out)[1], (out)[2], (out)[3]) -#define GET_CPU_ID_7(out) \ - __cpuid_count(7, 0, (out)[0], (out)[1], (out)[2], (out)[3]) -#else -#define GET_CPU_ID_0(out) __cpuid(out, 0) -#define GET_CPU_ID_7(out) __cpuidex(out, 7, 0) -#endif - -static bool sha1_hw_available(void) -{ - unsigned int CPUInfo[4]; - GET_CPU_ID_0(CPUInfo); - if (CPUInfo[0] < 7) - return false; - - GET_CPU_ID_7(CPUInfo); - return CPUInfo[1] & (1 << 29); /* Check SHA */ -} - -/* SHA1 implementation using new instructions - The code is based on Jeffrey Walton's SHA1 implementation: - https://github.com/noloader/SHA-Intrinsics -*/ -FUNC_ISA -static inline void sha1_ni_block(__m128i *core, const uint8_t *p) -{ - __m128i ABCD, E0, E1, MSG0, MSG1, MSG2, MSG3; - const __m128i MASK = _mm_set_epi64x( - 0x0001020304050607ULL, 0x08090a0b0c0d0e0fULL); - - const __m128i *block = (const __m128i *)p; - - /* Load initial values */ - ABCD = core[0]; - E0 = core[1]; - - /* Rounds 0-3 */ - MSG0 = _mm_loadu_si128(block); - MSG0 = _mm_shuffle_epi8(MSG0, MASK); - E0 = _mm_add_epi32(E0, MSG0); - E1 = ABCD; - ABCD = _mm_sha1rnds4_epu32(ABCD, E0, 0); - - /* Rounds 4-7 */ - MSG1 = _mm_loadu_si128(block + 1); - MSG1 = _mm_shuffle_epi8(MSG1, MASK); - E1 = _mm_sha1nexte_epu32(E1, MSG1); - E0 = ABCD; - ABCD = _mm_sha1rnds4_epu32(ABCD, E1, 0); - MSG0 = _mm_sha1msg1_epu32(MSG0, MSG1); - - /* Rounds 8-11 */ - MSG2 = _mm_loadu_si128(block + 2); - MSG2 = _mm_shuffle_epi8(MSG2, MASK); - E0 = _mm_sha1nexte_epu32(E0, MSG2); - E1 = ABCD; - ABCD = _mm_sha1rnds4_epu32(ABCD, E0, 0); - MSG1 = _mm_sha1msg1_epu32(MSG1, MSG2); - MSG0 = _mm_xor_si128(MSG0, MSG2); - - /* Rounds 12-15 */ - MSG3 = _mm_loadu_si128(block + 3); - MSG3 = _mm_shuffle_epi8(MSG3, MASK); - E1 = _mm_sha1nexte_epu32(E1, MSG3); - E0 = ABCD; - MSG0 = _mm_sha1msg2_epu32(MSG0, MSG3); - ABCD = _mm_sha1rnds4_epu32(ABCD, E1, 0); - MSG2 = _mm_sha1msg1_epu32(MSG2, MSG3); - MSG1 = _mm_xor_si128(MSG1, MSG3); - - /* Rounds 16-19 */ - E0 = _mm_sha1nexte_epu32(E0, MSG0); - E1 = ABCD; - MSG1 = _mm_sha1msg2_epu32(MSG1, MSG0); - ABCD = _mm_sha1rnds4_epu32(ABCD, E0, 0); - MSG3 = _mm_sha1msg1_epu32(MSG3, MSG0); - MSG2 = _mm_xor_si128(MSG2, MSG0); - - /* Rounds 20-23 */ - E1 = _mm_sha1nexte_epu32(E1, MSG1); - E0 = ABCD; - MSG2 = _mm_sha1msg2_epu32(MSG2, MSG1); - ABCD = _mm_sha1rnds4_epu32(ABCD, E1, 1); - MSG0 = _mm_sha1msg1_epu32(MSG0, MSG1); - MSG3 = _mm_xor_si128(MSG3, MSG1); - - /* Rounds 24-27 */ - E0 = _mm_sha1nexte_epu32(E0, MSG2); - E1 = ABCD; - MSG3 = _mm_sha1msg2_epu32(MSG3, MSG2); - ABCD = _mm_sha1rnds4_epu32(ABCD, E0, 1); - MSG1 = _mm_sha1msg1_epu32(MSG1, MSG2); - MSG0 = _mm_xor_si128(MSG0, MSG2); - - /* Rounds 28-31 */ - E1 = _mm_sha1nexte_epu32(E1, MSG3); - E0 = ABCD; - MSG0 = _mm_sha1msg2_epu32(MSG0, MSG3); - ABCD = _mm_sha1rnds4_epu32(ABCD, E1, 1); - MSG2 = _mm_sha1msg1_epu32(MSG2, MSG3); - MSG1 = _mm_xor_si128(MSG1, MSG3); - - /* Rounds 32-35 */ - E0 = _mm_sha1nexte_epu32(E0, MSG0); - E1 = ABCD; - MSG1 = _mm_sha1msg2_epu32(MSG1, MSG0); - ABCD = _mm_sha1rnds4_epu32(ABCD, E0, 1); - MSG3 = _mm_sha1msg1_epu32(MSG3, MSG0); - MSG2 = _mm_xor_si128(MSG2, MSG0); - - /* Rounds 36-39 */ - E1 = _mm_sha1nexte_epu32(E1, MSG1); - E0 = ABCD; - MSG2 = _mm_sha1msg2_epu32(MSG2, MSG1); - ABCD = _mm_sha1rnds4_epu32(ABCD, E1, 1); - MSG0 = _mm_sha1msg1_epu32(MSG0, MSG1); - MSG3 = _mm_xor_si128(MSG3, MSG1); - - /* Rounds 40-43 */ - E0 = _mm_sha1nexte_epu32(E0, MSG2); - E1 = ABCD; - MSG3 = _mm_sha1msg2_epu32(MSG3, MSG2); - ABCD = _mm_sha1rnds4_epu32(ABCD, E0, 2); - MSG1 = _mm_sha1msg1_epu32(MSG1, MSG2); - MSG0 = _mm_xor_si128(MSG0, MSG2); - - /* Rounds 44-47 */ - E1 = _mm_sha1nexte_epu32(E1, MSG3); - E0 = ABCD; - MSG0 = _mm_sha1msg2_epu32(MSG0, MSG3); - ABCD = _mm_sha1rnds4_epu32(ABCD, E1, 2); - MSG2 = _mm_sha1msg1_epu32(MSG2, MSG3); - MSG1 = _mm_xor_si128(MSG1, MSG3); - - /* Rounds 48-51 */ - E0 = _mm_sha1nexte_epu32(E0, MSG0); - E1 = ABCD; - MSG1 = _mm_sha1msg2_epu32(MSG1, MSG0); - ABCD = _mm_sha1rnds4_epu32(ABCD, E0, 2); - MSG3 = _mm_sha1msg1_epu32(MSG3, MSG0); - MSG2 = _mm_xor_si128(MSG2, MSG0); - - /* Rounds 52-55 */ - E1 = _mm_sha1nexte_epu32(E1, MSG1); - E0 = ABCD; - MSG2 = _mm_sha1msg2_epu32(MSG2, MSG1); - ABCD = _mm_sha1rnds4_epu32(ABCD, E1, 2); - MSG0 = _mm_sha1msg1_epu32(MSG0, MSG1); - MSG3 = _mm_xor_si128(MSG3, MSG1); - - /* Rounds 56-59 */ - E0 = _mm_sha1nexte_epu32(E0, MSG2); - E1 = ABCD; - MSG3 = _mm_sha1msg2_epu32(MSG3, MSG2); - ABCD = _mm_sha1rnds4_epu32(ABCD, E0, 2); - MSG1 = _mm_sha1msg1_epu32(MSG1, MSG2); - MSG0 = _mm_xor_si128(MSG0, MSG2); - - /* Rounds 60-63 */ - E1 = _mm_sha1nexte_epu32(E1, MSG3); - E0 = ABCD; - MSG0 = _mm_sha1msg2_epu32(MSG0, MSG3); - ABCD = _mm_sha1rnds4_epu32(ABCD, E1, 3); - MSG2 = _mm_sha1msg1_epu32(MSG2, MSG3); - MSG1 = _mm_xor_si128(MSG1, MSG3); - - /* Rounds 64-67 */ - E0 = _mm_sha1nexte_epu32(E0, MSG0); - E1 = ABCD; - MSG1 = _mm_sha1msg2_epu32(MSG1, MSG0); - ABCD = _mm_sha1rnds4_epu32(ABCD, E0, 3); - MSG3 = _mm_sha1msg1_epu32(MSG3, MSG0); - MSG2 = _mm_xor_si128(MSG2, MSG0); - - /* Rounds 68-71 */ - E1 = _mm_sha1nexte_epu32(E1, MSG1); - E0 = ABCD; - MSG2 = _mm_sha1msg2_epu32(MSG2, MSG1); - ABCD = _mm_sha1rnds4_epu32(ABCD, E1, 3); - MSG3 = _mm_xor_si128(MSG3, MSG1); - - /* Rounds 72-75 */ - E0 = _mm_sha1nexte_epu32(E0, MSG2); - E1 = ABCD; - MSG3 = _mm_sha1msg2_epu32(MSG3, MSG2); - ABCD = _mm_sha1rnds4_epu32(ABCD, E0, 3); - - /* Rounds 76-79 */ - E1 = _mm_sha1nexte_epu32(E1, MSG3); - E0 = ABCD; - ABCD = _mm_sha1rnds4_epu32(ABCD, E1, 3); - - /* Combine state */ - core[0] = _mm_add_epi32(ABCD, core[0]); - core[1] = _mm_sha1nexte_epu32(E0, core[1]); -} - -typedef struct sha1_ni { - /* - * core[0] stores the first four words of the SHA-1 state. core[1] - * stores just the fifth word, in the vector lane at the highest - * address. - */ - __m128i core[2]; - sha1_block blk; - void *pointer_to_free; - BinarySink_IMPLEMENTATION; - ssh_hash hash; -} sha1_ni; - -static void sha1_ni_write(BinarySink *bs, const void *vp, size_t len); - -static sha1_ni *sha1_ni_alloc(void) -{ - /* - * The __m128i variables in the context structure need to be - * 16-byte aligned, but not all malloc implementations that this - * code has to work with will guarantee to return a 16-byte - * aligned pointer. So we over-allocate, manually realign the - * pointer ourselves, and store the original one inside the - * context so we know how to free it later. - */ - void *allocation = smalloc(sizeof(sha1_ni) + 15); - uintptr_t alloc_address = (uintptr_t)allocation; - uintptr_t aligned_address = (alloc_address + 15) & ~15; - sha1_ni *s = (sha1_ni *)aligned_address; - s->pointer_to_free = allocation; - return s; -} - -static ssh_hash *sha1_ni_new(const ssh_hashalg *alg) -{ - if (!sha1_hw_available_cached()) - return NULL; - - sha1_ni *s = sha1_ni_alloc(); - - s->hash.vt = alg; - BinarySink_INIT(s, sha1_ni_write); - BinarySink_DELEGATE_INIT(&s->hash, s); - return &s->hash; -} - -FUNC_ISA static void sha1_ni_reset(ssh_hash *hash) -{ - sha1_ni *s = container_of(hash, sha1_ni, hash); - - /* Initialise the core vectors in their storage order */ - s->core[0] = _mm_set_epi64x( - 0x67452301efcdab89ULL, 0x98badcfe10325476ULL); - s->core[1] = _mm_set_epi32(0xc3d2e1f0, 0, 0, 0); - - sha1_block_setup(&s->blk); -} - -static void sha1_ni_copyfrom(ssh_hash *hcopy, ssh_hash *horig) -{ - sha1_ni *copy = container_of(hcopy, sha1_ni, hash); - sha1_ni *orig = container_of(horig, sha1_ni, hash); - - void *ptf_save = copy->pointer_to_free; - *copy = *orig; /* structure copy */ - copy->pointer_to_free = ptf_save; - - BinarySink_COPIED(copy); - BinarySink_DELEGATE_INIT(©->hash, copy); -} - -static void sha1_ni_free(ssh_hash *hash) -{ - sha1_ni *s = container_of(hash, sha1_ni, hash); - - void *ptf = s->pointer_to_free; - smemclr(s, sizeof(*s)); - sfree(ptf); -} - -static void sha1_ni_write(BinarySink *bs, const void *vp, size_t len) -{ - sha1_ni *s = BinarySink_DOWNCAST(bs, sha1_ni); - - while (len > 0) - if (sha1_block_write(&s->blk, &vp, &len)) - sha1_ni_block(s->core, s->blk.block); -} - -FUNC_ISA static void sha1_ni_digest(ssh_hash *hash, uint8_t *digest) -{ - sha1_ni *s = container_of(hash, sha1_ni, hash); - - sha1_block_pad(&s->blk, BinarySink_UPCAST(s)); - - /* Rearrange the first vector into its output order */ - __m128i abcd = _mm_shuffle_epi32(s->core[0], 0x1B); - - /* Byte-swap it into the output endianness */ - const __m128i mask = _mm_setr_epi8(3,2,1,0,7,6,5,4,11,10,9,8,15,14,13,12); - abcd = _mm_shuffle_epi8(abcd, mask); - - /* And store it */ - _mm_storeu_si128((__m128i *)digest, abcd); - - /* Finally, store the leftover word */ - uint32_t e = _mm_extract_epi32(s->core[1], 3); - PUT_32BIT_MSB_FIRST(digest + 16, e); -} - -const ssh_hashalg ssh_sha1_hw = { - .new = sha1_ni_new, - .reset = sha1_ni_reset, - .copyfrom = sha1_ni_copyfrom, - .digest = sha1_ni_digest, - .free = sha1_ni_free, - .hlen = 20, - .blocklen = 64, - HASHALG_NAMES_ANNOTATED("SHA-1", "SHA-NI accelerated"), -}; - -/* ---------------------------------------------------------------------- - * Hardware-accelerated implementation of SHA-1 using Arm NEON. - */ - -#elif HW_SHA1 == HW_SHA1_NEON - -/* - * Manually set the target architecture, if we decided above that we - * need to. - */ -#ifdef USE_CLANG_ATTR_TARGET_AARCH64 -/* - * A spot of cheating: redefine some ACLE feature macros before - * including arm_neon.h. Otherwise we won't get the SHA intrinsics - * defined by that header, because it will be looking at the settings - * for the whole translation unit rather than the ones we're going to - * put on some particular functions using __attribute__((target)). - */ -#define __ARM_NEON 1 -#define __ARM_FEATURE_CRYPTO 1 -#define FUNC_ISA __attribute__ ((target("neon,crypto"))) -#endif /* USE_CLANG_ATTR_TARGET_AARCH64 */ - -#ifndef FUNC_ISA -#define FUNC_ISA -#endif - -#ifdef USE_ARM64_NEON_H -#include -#else -#include -#endif - -static bool sha1_hw_available(void) -{ - /* - * For Arm, we delegate to a per-platform detection function (see - * explanation in sshaes.c). - */ - return platform_sha1_hw_available(); -} - -typedef struct sha1_neon_core sha1_neon_core; -struct sha1_neon_core { - uint32x4_t abcd; - uint32_t e; -}; - -FUNC_ISA -static inline uint32x4_t sha1_neon_load_input(const uint8_t *p) -{ - return vreinterpretq_u32_u8(vrev32q_u8(vld1q_u8(p))); -} - -FUNC_ISA -static inline uint32x4_t sha1_neon_schedule_update( - uint32x4_t m4, uint32x4_t m3, uint32x4_t m2, uint32x4_t m1) -{ - return vsha1su1q_u32(vsha1su0q_u32(m4, m3, m2), m1); -} - -/* - * SHA-1 has three different kinds of round, differing in whether they - * use the Ch, Maj or Par functions defined above. Each one uses a - * separate NEON instruction, so we define three inline functions for - * the different round types using this macro. - * - * The two batches of Par-type rounds also use a different constant, - * but that's passed in as an operand, so we don't need a fourth - * inline function just for that. - */ -#define SHA1_NEON_ROUND_FN(type) \ - FUNC_ISA static inline sha1_neon_core sha1_neon_round4_##type( \ - sha1_neon_core old, uint32x4_t sched, uint32x4_t constant) \ - { \ - sha1_neon_core new; \ - uint32x4_t round_input = vaddq_u32(sched, constant); \ - new.abcd = vsha1##type##q_u32(old.abcd, old.e, round_input); \ - new.e = vsha1h_u32(vget_lane_u32(vget_low_u32(old.abcd), 0)); \ - return new; \ - } -SHA1_NEON_ROUND_FN(c) -SHA1_NEON_ROUND_FN(p) -SHA1_NEON_ROUND_FN(m) - -FUNC_ISA -static inline void sha1_neon_block(sha1_neon_core *core, const uint8_t *p) -{ - uint32x4_t constant, s0, s1, s2, s3; - sha1_neon_core cr = *core; - - constant = vdupq_n_u32(SHA1_STAGE0_CONSTANT); - s0 = sha1_neon_load_input(p); - cr = sha1_neon_round4_c(cr, s0, constant); - s1 = sha1_neon_load_input(p + 16); - cr = sha1_neon_round4_c(cr, s1, constant); - s2 = sha1_neon_load_input(p + 32); - cr = sha1_neon_round4_c(cr, s2, constant); - s3 = sha1_neon_load_input(p + 48); - cr = sha1_neon_round4_c(cr, s3, constant); - s0 = sha1_neon_schedule_update(s0, s1, s2, s3); - cr = sha1_neon_round4_c(cr, s0, constant); - - constant = vdupq_n_u32(SHA1_STAGE1_CONSTANT); - s1 = sha1_neon_schedule_update(s1, s2, s3, s0); - cr = sha1_neon_round4_p(cr, s1, constant); - s2 = sha1_neon_schedule_update(s2, s3, s0, s1); - cr = sha1_neon_round4_p(cr, s2, constant); - s3 = sha1_neon_schedule_update(s3, s0, s1, s2); - cr = sha1_neon_round4_p(cr, s3, constant); - s0 = sha1_neon_schedule_update(s0, s1, s2, s3); - cr = sha1_neon_round4_p(cr, s0, constant); - s1 = sha1_neon_schedule_update(s1, s2, s3, s0); - cr = sha1_neon_round4_p(cr, s1, constant); - - constant = vdupq_n_u32(SHA1_STAGE2_CONSTANT); - s2 = sha1_neon_schedule_update(s2, s3, s0, s1); - cr = sha1_neon_round4_m(cr, s2, constant); - s3 = sha1_neon_schedule_update(s3, s0, s1, s2); - cr = sha1_neon_round4_m(cr, s3, constant); - s0 = sha1_neon_schedule_update(s0, s1, s2, s3); - cr = sha1_neon_round4_m(cr, s0, constant); - s1 = sha1_neon_schedule_update(s1, s2, s3, s0); - cr = sha1_neon_round4_m(cr, s1, constant); - s2 = sha1_neon_schedule_update(s2, s3, s0, s1); - cr = sha1_neon_round4_m(cr, s2, constant); - - constant = vdupq_n_u32(SHA1_STAGE3_CONSTANT); - s3 = sha1_neon_schedule_update(s3, s0, s1, s2); - cr = sha1_neon_round4_p(cr, s3, constant); - s0 = sha1_neon_schedule_update(s0, s1, s2, s3); - cr = sha1_neon_round4_p(cr, s0, constant); - s1 = sha1_neon_schedule_update(s1, s2, s3, s0); - cr = sha1_neon_round4_p(cr, s1, constant); - s2 = sha1_neon_schedule_update(s2, s3, s0, s1); - cr = sha1_neon_round4_p(cr, s2, constant); - s3 = sha1_neon_schedule_update(s3, s0, s1, s2); - cr = sha1_neon_round4_p(cr, s3, constant); - - core->abcd = vaddq_u32(core->abcd, cr.abcd); - core->e += cr.e; -} - -typedef struct sha1_neon { - sha1_neon_core core; - sha1_block blk; - BinarySink_IMPLEMENTATION; - ssh_hash hash; -} sha1_neon; - -static void sha1_neon_write(BinarySink *bs, const void *vp, size_t len); - -static ssh_hash *sha1_neon_new(const ssh_hashalg *alg) -{ - if (!sha1_hw_available_cached()) - return NULL; - - sha1_neon *s = snew(sha1_neon); - - s->hash.vt = alg; - BinarySink_INIT(s, sha1_neon_write); - BinarySink_DELEGATE_INIT(&s->hash, s); - return &s->hash; -} - -static void sha1_neon_reset(ssh_hash *hash) -{ - sha1_neon *s = container_of(hash, sha1_neon, hash); - - s->core.abcd = vld1q_u32(sha1_initial_state); - s->core.e = sha1_initial_state[4]; - - sha1_block_setup(&s->blk); -} - -static void sha1_neon_copyfrom(ssh_hash *hcopy, ssh_hash *horig) -{ - sha1_neon *copy = container_of(hcopy, sha1_neon, hash); - sha1_neon *orig = container_of(horig, sha1_neon, hash); - - *copy = *orig; /* structure copy */ - - BinarySink_COPIED(copy); - BinarySink_DELEGATE_INIT(©->hash, copy); -} - -static void sha1_neon_free(ssh_hash *hash) -{ - sha1_neon *s = container_of(hash, sha1_neon, hash); - smemclr(s, sizeof(*s)); - sfree(s); -} - -static void sha1_neon_write(BinarySink *bs, const void *vp, size_t len) -{ - sha1_neon *s = BinarySink_DOWNCAST(bs, sha1_neon); - - while (len > 0) - if (sha1_block_write(&s->blk, &vp, &len)) - sha1_neon_block(&s->core, s->blk.block); -} - -static void sha1_neon_digest(ssh_hash *hash, uint8_t *digest) -{ - sha1_neon *s = container_of(hash, sha1_neon, hash); - - sha1_block_pad(&s->blk, BinarySink_UPCAST(s)); - vst1q_u8(digest, vrev32q_u8(vreinterpretq_u8_u32(s->core.abcd))); - PUT_32BIT_MSB_FIRST(digest + 16, s->core.e); -} - -const ssh_hashalg ssh_sha1_hw = { - .new = sha1_neon_new, - .reset = sha1_neon_reset, - .copyfrom = sha1_neon_copyfrom, - .digest = sha1_neon_digest, - .free = sha1_neon_free, - .hlen = 20, - .blocklen = 64, - HASHALG_NAMES_ANNOTATED("SHA-1", "NEON accelerated"), -}; - -/* ---------------------------------------------------------------------- - * Stub functions if we have no hardware-accelerated SHA-1. In this - * case, sha1_hw_new returns NULL (though it should also never be - * selected by sha1_select, so the only thing that should even be - * _able_ to call it is testcrypt). As a result, the remaining vtable - * functions should never be called at all. - */ - -#elif HW_SHA1 == HW_SHA1_NONE - -static bool sha1_hw_available(void) -{ - return false; -} - -static ssh_hash *sha1_stub_new(const ssh_hashalg *alg) -{ - return NULL; -} - -#define STUB_BODY { unreachable("Should never be called"); } - -static void sha1_stub_reset(ssh_hash *hash) STUB_BODY -static void sha1_stub_copyfrom(ssh_hash *hash, ssh_hash *orig) STUB_BODY -static void sha1_stub_free(ssh_hash *hash) STUB_BODY -static void sha1_stub_digest(ssh_hash *hash, uint8_t *digest) STUB_BODY - -const ssh_hashalg ssh_sha1_hw = { - .new = sha1_stub_new, - .reset = sha1_stub_reset, - .copyfrom = sha1_stub_copyfrom, - .digest = sha1_stub_digest, - .free = sha1_stub_free, - .hlen = 20, - .blocklen = 64, - HASHALG_NAMES_ANNOTATED("SHA-1", "!NONEXISTENT ACCELERATED VERSION!"), -}; - -#endif /* HW_SHA1 */ diff --git a/crypto/sha1.h b/crypto/sha1.h new file mode 100644 index 00000000..2cdba0d4 --- /dev/null +++ b/crypto/sha1.h @@ -0,0 +1,109 @@ +/* + * Definitions likely to be helpful to multiple SHA-1 implementations. + */ + +/* + * The 'extra' structure used by SHA-1 implementations is used to + * include information about how to check if a given implementation is + * available at run time, and whether we've already checked. + */ +struct sha1_extra_mutable; +struct sha1_extra { + /* Function to check availability. Might be expensive, so we don't + * want to call it more than once. */ + bool (*check_available)(void); + + /* Point to a writable substructure. */ + struct sha1_extra_mutable *mut; +}; +struct sha1_extra_mutable { + bool checked_availability; + bool is_available; +}; +static inline bool check_availability(const struct sha1_extra *extra) +{ + if (!extra->mut->checked_availability) { + extra->mut->is_available = extra->check_available(); + extra->mut->checked_availability = true; + } + + return extra->mut->is_available; +} + +/* + * Macro to define a SHA-1 vtable together with its 'extra' + * structure. + */ +#define SHA1_VTABLE(impl_c, impl_display) \ + static struct sha1_extra_mutable sha1_ ## impl_c ## _extra_mut; \ + static const struct sha1_extra sha1_ ## impl_c ## _extra = { \ + .check_available = sha1_ ## impl_c ## _available, \ + .mut = &sha1_ ## impl_c ## _extra_mut, \ + }; \ + const ssh_hashalg ssh_sha1_ ## impl_c = { \ + .new = sha1_ ## impl_c ## _new, \ + .reset = sha1_ ## impl_c ## _reset, \ + .copyfrom = sha1_ ## impl_c ## _copyfrom, \ + .digest = sha1_ ## impl_c ## _digest, \ + .free = sha1_ ## impl_c ## _free, \ + .hlen = 20, \ + .blocklen = 64, \ + HASHALG_NAMES_ANNOTATED("SHA-1", impl_display), \ + .extra = &sha1_ ## impl_c ## _extra, \ + } + +extern const uint32_t sha1_initial_state[5]; + +#define SHA1_ROUNDS_PER_STAGE 20 +#define SHA1_STAGE0_CONSTANT 0x5a827999 +#define SHA1_STAGE1_CONSTANT 0x6ed9eba1 +#define SHA1_STAGE2_CONSTANT 0x8f1bbcdc +#define SHA1_STAGE3_CONSTANT 0xca62c1d6 +#define SHA1_ROUNDS (4 * SHA1_ROUNDS_PER_STAGE) + +typedef struct sha1_block sha1_block; +struct sha1_block { + uint8_t block[64]; + size_t used; + uint64_t len; +}; + +static inline void sha1_block_setup(sha1_block *blk) +{ + blk->used = 0; + blk->len = 0; +} + +static inline bool sha1_block_write( + sha1_block *blk, const void **vdata, size_t *len) +{ + size_t blkleft = sizeof(blk->block) - blk->used; + size_t chunk = *len < blkleft ? *len : blkleft; + + const uint8_t *p = *vdata; + memcpy(blk->block + blk->used, p, chunk); + *vdata = p + chunk; + *len -= chunk; + blk->used += chunk; + blk->len += chunk; + + if (blk->used == sizeof(blk->block)) { + blk->used = 0; + return true; + } + + return false; +} + +static inline void sha1_block_pad(sha1_block *blk, BinarySink *bs) +{ + uint64_t final_len = blk->len << 3; + size_t pad = 1 + (63 & (55 - blk->used)); + + put_byte(bs, 0x80); + for (size_t i = 1; i < pad; i++) + put_byte(bs, 0); + put_uint64(bs, final_len); + + assert(blk->used == 0 && "Should have exactly hit a block boundary"); +} diff --git a/crypto/sha256-common.c b/crypto/sha256-common.c new file mode 100644 index 00000000..52904c08 --- /dev/null +++ b/crypto/sha256-common.c @@ -0,0 +1,30 @@ +/* + * Common variable definitions across all the SHA-256 implementations. + */ + +#include "ssh.h" +#include "sha256.h" + +const uint32_t sha256_initial_state[8] = { + 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, + 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19, +}; + +const uint32_t sha256_round_constants[64] = { + 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, +}; diff --git a/crypto/sha256-neon.c b/crypto/sha256-neon.c new file mode 100644 index 00000000..87d24d0c --- /dev/null +++ b/crypto/sha256-neon.c @@ -0,0 +1,162 @@ +/* + * Hardware-accelerated implementation of SHA-256 using Arm NEON. + */ + +#include "ssh.h" +#include "sha256.h" + +#if USE_ARM64_NEON_H +#include +#else +#include +#endif + +static bool sha256_neon_available(void) +{ + /* + * For Arm, we delegate to a per-platform detection function (see + * explanation in aes-neon.c). + */ + return platform_sha256_neon_available(); +} + +typedef struct sha256_neon_core sha256_neon_core; +struct sha256_neon_core { + uint32x4_t abcd, efgh; +}; + +static inline uint32x4_t sha256_neon_load_input(const uint8_t *p) +{ + return vreinterpretq_u32_u8(vrev32q_u8(vld1q_u8(p))); +} + +static inline uint32x4_t sha256_neon_schedule_update( + uint32x4_t m4, uint32x4_t m3, uint32x4_t m2, uint32x4_t m1) +{ + return vsha256su1q_u32(vsha256su0q_u32(m4, m3), m2, m1); +} + +static inline sha256_neon_core sha256_neon_round4( + sha256_neon_core old, uint32x4_t sched, unsigned round) +{ + sha256_neon_core new; + + uint32x4_t round_input = vaddq_u32( + sched, vld1q_u32(sha256_round_constants + round)); + new.abcd = vsha256hq_u32 (old.abcd, old.efgh, round_input); + new.efgh = vsha256h2q_u32(old.efgh, old.abcd, round_input); + return new; +} + +static inline void sha256_neon_block(sha256_neon_core *core, const uint8_t *p) +{ + uint32x4_t s0, s1, s2, s3; + sha256_neon_core cr = *core; + + s0 = sha256_neon_load_input(p); + cr = sha256_neon_round4(cr, s0, 0); + s1 = sha256_neon_load_input(p+16); + cr = sha256_neon_round4(cr, s1, 4); + s2 = sha256_neon_load_input(p+32); + cr = sha256_neon_round4(cr, s2, 8); + s3 = sha256_neon_load_input(p+48); + cr = sha256_neon_round4(cr, s3, 12); + s0 = sha256_neon_schedule_update(s0, s1, s2, s3); + cr = sha256_neon_round4(cr, s0, 16); + s1 = sha256_neon_schedule_update(s1, s2, s3, s0); + cr = sha256_neon_round4(cr, s1, 20); + s2 = sha256_neon_schedule_update(s2, s3, s0, s1); + cr = sha256_neon_round4(cr, s2, 24); + s3 = sha256_neon_schedule_update(s3, s0, s1, s2); + cr = sha256_neon_round4(cr, s3, 28); + s0 = sha256_neon_schedule_update(s0, s1, s2, s3); + cr = sha256_neon_round4(cr, s0, 32); + s1 = sha256_neon_schedule_update(s1, s2, s3, s0); + cr = sha256_neon_round4(cr, s1, 36); + s2 = sha256_neon_schedule_update(s2, s3, s0, s1); + cr = sha256_neon_round4(cr, s2, 40); + s3 = sha256_neon_schedule_update(s3, s0, s1, s2); + cr = sha256_neon_round4(cr, s3, 44); + s0 = sha256_neon_schedule_update(s0, s1, s2, s3); + cr = sha256_neon_round4(cr, s0, 48); + s1 = sha256_neon_schedule_update(s1, s2, s3, s0); + cr = sha256_neon_round4(cr, s1, 52); + s2 = sha256_neon_schedule_update(s2, s3, s0, s1); + cr = sha256_neon_round4(cr, s2, 56); + s3 = sha256_neon_schedule_update(s3, s0, s1, s2); + cr = sha256_neon_round4(cr, s3, 60); + + core->abcd = vaddq_u32(core->abcd, cr.abcd); + core->efgh = vaddq_u32(core->efgh, cr.efgh); +} + +typedef struct sha256_neon { + sha256_neon_core core; + sha256_block blk; + BinarySink_IMPLEMENTATION; + ssh_hash hash; +} sha256_neon; + +static void sha256_neon_write(BinarySink *bs, const void *vp, size_t len); + +static ssh_hash *sha256_neon_new(const ssh_hashalg *alg) +{ + const struct sha256_extra *extra = (const struct sha256_extra *)alg->extra; + if (!check_availability(extra)) + return NULL; + + sha256_neon *s = snew(sha256_neon); + + s->hash.vt = alg; + BinarySink_INIT(s, sha256_neon_write); + BinarySink_DELEGATE_INIT(&s->hash, s); + return &s->hash; +} + +static void sha256_neon_reset(ssh_hash *hash) +{ + sha256_neon *s = container_of(hash, sha256_neon, hash); + + s->core.abcd = vld1q_u32(sha256_initial_state); + s->core.efgh = vld1q_u32(sha256_initial_state + 4); + + sha256_block_setup(&s->blk); +} + +static void sha256_neon_copyfrom(ssh_hash *hcopy, ssh_hash *horig) +{ + sha256_neon *copy = container_of(hcopy, sha256_neon, hash); + sha256_neon *orig = container_of(horig, sha256_neon, hash); + + *copy = *orig; /* structure copy */ + + BinarySink_COPIED(copy); + BinarySink_DELEGATE_INIT(©->hash, copy); +} + +static void sha256_neon_free(ssh_hash *hash) +{ + sha256_neon *s = container_of(hash, sha256_neon, hash); + smemclr(s, sizeof(*s)); + sfree(s); +} + +static void sha256_neon_write(BinarySink *bs, const void *vp, size_t len) +{ + sha256_neon *s = BinarySink_DOWNCAST(bs, sha256_neon); + + while (len > 0) + if (sha256_block_write(&s->blk, &vp, &len)) + sha256_neon_block(&s->core, s->blk.block); +} + +static void sha256_neon_digest(ssh_hash *hash, uint8_t *digest) +{ + sha256_neon *s = container_of(hash, sha256_neon, hash); + + sha256_block_pad(&s->blk, BinarySink_UPCAST(s)); + vst1q_u8(digest, vrev32q_u8(vreinterpretq_u8_u32(s->core.abcd))); + vst1q_u8(digest + 16, vrev32q_u8(vreinterpretq_u8_u32(s->core.efgh))); +} + +SHA256_VTABLE(neon, "NEON accelerated"); diff --git a/crypto/sha256-ni.c b/crypto/sha256-ni.c new file mode 100644 index 00000000..530fa433 --- /dev/null +++ b/crypto/sha256-ni.c @@ -0,0 +1,342 @@ +/* + * Hardware-accelerated implementation of SHA-256 using x86 SHA-NI. + */ + +#include "ssh.h" +#include "sha256.h" + +#include +#include +#include +#if HAVE_SHAINTRIN_H +#include +#endif + +#if defined(__clang__) || defined(__GNUC__) +#include +#define GET_CPU_ID_0(out) \ + __cpuid(0, (out)[0], (out)[1], (out)[2], (out)[3]) +#define GET_CPU_ID_7(out) \ + __cpuid_count(7, 0, (out)[0], (out)[1], (out)[2], (out)[3]) +#else +#define GET_CPU_ID_0(out) __cpuid(out, 0) +#define GET_CPU_ID_7(out) __cpuidex(out, 7, 0) +#endif + +static bool sha256_ni_available(void) +{ + unsigned int CPUInfo[4]; + GET_CPU_ID_0(CPUInfo); + if (CPUInfo[0] < 7) + return false; + + GET_CPU_ID_7(CPUInfo); + return CPUInfo[1] & (1 << 29); /* Check SHA */ +} + +/* SHA256 implementation using new instructions + The code is based on Jeffrey Walton's SHA256 implementation: + https://github.com/noloader/SHA-Intrinsics +*/ +static inline void sha256_ni_block(__m128i *core, const uint8_t *p) +{ + __m128i STATE0, STATE1; + __m128i MSG, TMP; + __m128i MSG0, MSG1, MSG2, MSG3; + const __m128i *block = (const __m128i *)p; + const __m128i MASK = _mm_set_epi64x( + 0x0c0d0e0f08090a0bULL, 0x0405060700010203ULL); + + /* Load initial values */ + STATE0 = core[0]; + STATE1 = core[1]; + + /* Rounds 0-3 */ + MSG = _mm_loadu_si128(block); + MSG0 = _mm_shuffle_epi8(MSG, MASK); + MSG = _mm_add_epi32(MSG0, _mm_set_epi64x( + 0xE9B5DBA5B5C0FBCFULL, 0x71374491428A2F98ULL)); + STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); + MSG = _mm_shuffle_epi32(MSG, 0x0E); + STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); + + /* Rounds 4-7 */ + MSG1 = _mm_loadu_si128(block + 1); + MSG1 = _mm_shuffle_epi8(MSG1, MASK); + MSG = _mm_add_epi32(MSG1, _mm_set_epi64x( + 0xAB1C5ED5923F82A4ULL, 0x59F111F13956C25BULL)); + STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); + MSG = _mm_shuffle_epi32(MSG, 0x0E); + STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); + MSG0 = _mm_sha256msg1_epu32(MSG0, MSG1); + + /* Rounds 8-11 */ + MSG2 = _mm_loadu_si128(block + 2); + MSG2 = _mm_shuffle_epi8(MSG2, MASK); + MSG = _mm_add_epi32(MSG2, _mm_set_epi64x( + 0x550C7DC3243185BEULL, 0x12835B01D807AA98ULL)); + STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); + MSG = _mm_shuffle_epi32(MSG, 0x0E); + STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); + MSG1 = _mm_sha256msg1_epu32(MSG1, MSG2); + + /* Rounds 12-15 */ + MSG3 = _mm_loadu_si128(block + 3); + MSG3 = _mm_shuffle_epi8(MSG3, MASK); + MSG = _mm_add_epi32(MSG3, _mm_set_epi64x( + 0xC19BF1749BDC06A7ULL, 0x80DEB1FE72BE5D74ULL)); + STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); + TMP = _mm_alignr_epi8(MSG3, MSG2, 4); + MSG0 = _mm_add_epi32(MSG0, TMP); + MSG0 = _mm_sha256msg2_epu32(MSG0, MSG3); + MSG = _mm_shuffle_epi32(MSG, 0x0E); + STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); + MSG2 = _mm_sha256msg1_epu32(MSG2, MSG3); + + /* Rounds 16-19 */ + MSG = _mm_add_epi32(MSG0, _mm_set_epi64x( + 0x240CA1CC0FC19DC6ULL, 0xEFBE4786E49B69C1ULL)); + STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); + TMP = _mm_alignr_epi8(MSG0, MSG3, 4); + MSG1 = _mm_add_epi32(MSG1, TMP); + MSG1 = _mm_sha256msg2_epu32(MSG1, MSG0); + MSG = _mm_shuffle_epi32(MSG, 0x0E); + STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); + MSG3 = _mm_sha256msg1_epu32(MSG3, MSG0); + + /* Rounds 20-23 */ + MSG = _mm_add_epi32(MSG1, _mm_set_epi64x( + 0x76F988DA5CB0A9DCULL, 0x4A7484AA2DE92C6FULL)); + STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); + TMP = _mm_alignr_epi8(MSG1, MSG0, 4); + MSG2 = _mm_add_epi32(MSG2, TMP); + MSG2 = _mm_sha256msg2_epu32(MSG2, MSG1); + MSG = _mm_shuffle_epi32(MSG, 0x0E); + STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); + MSG0 = _mm_sha256msg1_epu32(MSG0, MSG1); + + /* Rounds 24-27 */ + MSG = _mm_add_epi32(MSG2, _mm_set_epi64x( + 0xBF597FC7B00327C8ULL, 0xA831C66D983E5152ULL)); + STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); + TMP = _mm_alignr_epi8(MSG2, MSG1, 4); + MSG3 = _mm_add_epi32(MSG3, TMP); + MSG3 = _mm_sha256msg2_epu32(MSG3, MSG2); + MSG = _mm_shuffle_epi32(MSG, 0x0E); + STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); + MSG1 = _mm_sha256msg1_epu32(MSG1, MSG2); + + /* Rounds 28-31 */ + MSG = _mm_add_epi32(MSG3, _mm_set_epi64x( + 0x1429296706CA6351ULL, 0xD5A79147C6E00BF3ULL)); + STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); + TMP = _mm_alignr_epi8(MSG3, MSG2, 4); + MSG0 = _mm_add_epi32(MSG0, TMP); + MSG0 = _mm_sha256msg2_epu32(MSG0, MSG3); + MSG = _mm_shuffle_epi32(MSG, 0x0E); + STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); + MSG2 = _mm_sha256msg1_epu32(MSG2, MSG3); + + /* Rounds 32-35 */ + MSG = _mm_add_epi32(MSG0, _mm_set_epi64x( + 0x53380D134D2C6DFCULL, 0x2E1B213827B70A85ULL)); + STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); + TMP = _mm_alignr_epi8(MSG0, MSG3, 4); + MSG1 = _mm_add_epi32(MSG1, TMP); + MSG1 = _mm_sha256msg2_epu32(MSG1, MSG0); + MSG = _mm_shuffle_epi32(MSG, 0x0E); + STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); + MSG3 = _mm_sha256msg1_epu32(MSG3, MSG0); + + /* Rounds 36-39 */ + MSG = _mm_add_epi32(MSG1, _mm_set_epi64x( + 0x92722C8581C2C92EULL, 0x766A0ABB650A7354ULL)); + STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); + TMP = _mm_alignr_epi8(MSG1, MSG0, 4); + MSG2 = _mm_add_epi32(MSG2, TMP); + MSG2 = _mm_sha256msg2_epu32(MSG2, MSG1); + MSG = _mm_shuffle_epi32(MSG, 0x0E); + STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); + MSG0 = _mm_sha256msg1_epu32(MSG0, MSG1); + + /* Rounds 40-43 */ + MSG = _mm_add_epi32(MSG2, _mm_set_epi64x( + 0xC76C51A3C24B8B70ULL, 0xA81A664BA2BFE8A1ULL)); + STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); + TMP = _mm_alignr_epi8(MSG2, MSG1, 4); + MSG3 = _mm_add_epi32(MSG3, TMP); + MSG3 = _mm_sha256msg2_epu32(MSG3, MSG2); + MSG = _mm_shuffle_epi32(MSG, 0x0E); + STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); + MSG1 = _mm_sha256msg1_epu32(MSG1, MSG2); + + /* Rounds 44-47 */ + MSG = _mm_add_epi32(MSG3, _mm_set_epi64x( + 0x106AA070F40E3585ULL, 0xD6990624D192E819ULL)); + STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); + TMP = _mm_alignr_epi8(MSG3, MSG2, 4); + MSG0 = _mm_add_epi32(MSG0, TMP); + MSG0 = _mm_sha256msg2_epu32(MSG0, MSG3); + MSG = _mm_shuffle_epi32(MSG, 0x0E); + STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); + MSG2 = _mm_sha256msg1_epu32(MSG2, MSG3); + + /* Rounds 48-51 */ + MSG = _mm_add_epi32(MSG0, _mm_set_epi64x( + 0x34B0BCB52748774CULL, 0x1E376C0819A4C116ULL)); + STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); + TMP = _mm_alignr_epi8(MSG0, MSG3, 4); + MSG1 = _mm_add_epi32(MSG1, TMP); + MSG1 = _mm_sha256msg2_epu32(MSG1, MSG0); + MSG = _mm_shuffle_epi32(MSG, 0x0E); + STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); + MSG3 = _mm_sha256msg1_epu32(MSG3, MSG0); + + /* Rounds 52-55 */ + MSG = _mm_add_epi32(MSG1, _mm_set_epi64x( + 0x682E6FF35B9CCA4FULL, 0x4ED8AA4A391C0CB3ULL)); + STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); + TMP = _mm_alignr_epi8(MSG1, MSG0, 4); + MSG2 = _mm_add_epi32(MSG2, TMP); + MSG2 = _mm_sha256msg2_epu32(MSG2, MSG1); + MSG = _mm_shuffle_epi32(MSG, 0x0E); + STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); + + /* Rounds 56-59 */ + MSG = _mm_add_epi32(MSG2, _mm_set_epi64x( + 0x8CC7020884C87814ULL, 0x78A5636F748F82EEULL)); + STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); + TMP = _mm_alignr_epi8(MSG2, MSG1, 4); + MSG3 = _mm_add_epi32(MSG3, TMP); + MSG3 = _mm_sha256msg2_epu32(MSG3, MSG2); + MSG = _mm_shuffle_epi32(MSG, 0x0E); + STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); + + /* Rounds 60-63 */ + MSG = _mm_add_epi32(MSG3, _mm_set_epi64x( + 0xC67178F2BEF9A3F7ULL, 0xA4506CEB90BEFFFAULL)); + STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); + MSG = _mm_shuffle_epi32(MSG, 0x0E); + STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); + + /* Combine state */ + core[0] = _mm_add_epi32(STATE0, core[0]); + core[1] = _mm_add_epi32(STATE1, core[1]); +} + +typedef struct sha256_ni { + /* + * These two vectors store the 8 words of the SHA-256 state, but + * not in the same order they appear in the spec: the first word + * holds A,B,E,F and the second word C,D,G,H. + */ + __m128i core[2]; + sha256_block blk; + void *pointer_to_free; + BinarySink_IMPLEMENTATION; + ssh_hash hash; +} sha256_ni; + +static void sha256_ni_write(BinarySink *bs, const void *vp, size_t len); + +static sha256_ni *sha256_ni_alloc(void) +{ + /* + * The __m128i variables in the context structure need to be + * 16-byte aligned, but not all malloc implementations that this + * code has to work with will guarantee to return a 16-byte + * aligned pointer. So we over-allocate, manually realign the + * pointer ourselves, and store the original one inside the + * context so we know how to free it later. + */ + void *allocation = smalloc(sizeof(sha256_ni) + 15); + uintptr_t alloc_address = (uintptr_t)allocation; + uintptr_t aligned_address = (alloc_address + 15) & ~15; + sha256_ni *s = (sha256_ni *)aligned_address; + s->pointer_to_free = allocation; + return s; +} + +static ssh_hash *sha256_ni_new(const ssh_hashalg *alg) +{ + const struct sha256_extra *extra = (const struct sha256_extra *)alg->extra; + if (!check_availability(extra)) + return NULL; + + sha256_ni *s = sha256_ni_alloc(); + + s->hash.vt = alg; + BinarySink_INIT(s, sha256_ni_write); + BinarySink_DELEGATE_INIT(&s->hash, s); + + return &s->hash; +} + +static void sha256_ni_reset(ssh_hash *hash) +{ + sha256_ni *s = container_of(hash, sha256_ni, hash); + + /* Initialise the core vectors in their storage order */ + s->core[0] = _mm_set_epi64x( + 0x6a09e667bb67ae85ULL, 0x510e527f9b05688cULL); + s->core[1] = _mm_set_epi64x( + 0x3c6ef372a54ff53aULL, 0x1f83d9ab5be0cd19ULL); + + sha256_block_setup(&s->blk); +} + +static void sha256_ni_copyfrom(ssh_hash *hcopy, ssh_hash *horig) +{ + sha256_ni *copy = container_of(hcopy, sha256_ni, hash); + sha256_ni *orig = container_of(horig, sha256_ni, hash); + + void *ptf_save = copy->pointer_to_free; + *copy = *orig; /* structure copy */ + copy->pointer_to_free = ptf_save; + + BinarySink_COPIED(copy); + BinarySink_DELEGATE_INIT(©->hash, copy); +} + +static void sha256_ni_free(ssh_hash *hash) +{ + sha256_ni *s = container_of(hash, sha256_ni, hash); + + void *ptf = s->pointer_to_free; + smemclr(s, sizeof(*s)); + sfree(ptf); +} + +static void sha256_ni_write(BinarySink *bs, const void *vp, size_t len) +{ + sha256_ni *s = BinarySink_DOWNCAST(bs, sha256_ni); + + while (len > 0) + if (sha256_block_write(&s->blk, &vp, &len)) + sha256_ni_block(s->core, s->blk.block); +} + +static void sha256_ni_digest(ssh_hash *hash, uint8_t *digest) +{ + sha256_ni *s = container_of(hash, sha256_ni, hash); + + sha256_block_pad(&s->blk, BinarySink_UPCAST(s)); + + /* Rearrange the words into the output order */ + __m128i feba = _mm_shuffle_epi32(s->core[0], 0x1B); + __m128i dchg = _mm_shuffle_epi32(s->core[1], 0xB1); + __m128i dcba = _mm_blend_epi16(feba, dchg, 0xF0); + __m128i hgfe = _mm_alignr_epi8(dchg, feba, 8); + + /* Byte-swap them into the output endianness */ + const __m128i mask = _mm_setr_epi8(3,2,1,0,7,6,5,4,11,10,9,8,15,14,13,12); + dcba = _mm_shuffle_epi8(dcba, mask); + hgfe = _mm_shuffle_epi8(hgfe, mask); + + /* And store them */ + __m128i *output = (__m128i *)digest; + _mm_storeu_si128(output, dcba); + _mm_storeu_si128(output+1, hgfe); +} + +SHA256_VTABLE(ni, "SHA-NI accelerated"); diff --git a/crypto/sha256-select.c b/crypto/sha256-select.c new file mode 100644 index 00000000..78e5b7e4 --- /dev/null +++ b/crypto/sha256-select.c @@ -0,0 +1,44 @@ +/* + * Top-level vtables to select a SHA-256 implementation. + */ + +#include +#include + +#include "putty.h" +#include "ssh.h" +#include "sha256.h" + +static ssh_hash *sha256_select(const ssh_hashalg *alg) +{ + static const ssh_hashalg *const real_algs[] = { +#if HAVE_SHA_NI + &ssh_sha256_ni, +#endif +#if HAVE_NEON_CRYPTO + &ssh_sha256_neon, +#endif + &ssh_sha256_sw, + NULL, + }; + + for (size_t i = 0; real_algs[i]; i++) { + const ssh_hashalg *alg = real_algs[i]; + const struct sha256_extra *alg_extra = + (const struct sha256_extra *)alg->extra; + if (check_availability(alg_extra)) + return ssh_hash_new(alg); + } + + /* We should never reach the NULL at the end of the list, because + * the last non-NULL entry should be software-only SHA-256, which + * is always available. */ + unreachable("sha256_select ran off the end of its list"); +} + +const ssh_hashalg ssh_sha256 = { + .new = sha256_select, + .hlen = 32, + .blocklen = 64, + HASHALG_NAMES_ANNOTATED("SHA-256", "dummy selector vtable"), +}; diff --git a/crypto/sha256-sw.c b/crypto/sha256-sw.c new file mode 100644 index 00000000..82a116c6 --- /dev/null +++ b/crypto/sha256-sw.c @@ -0,0 +1,157 @@ +/* + * Software implementation of SHA-256. + */ + +#include "ssh.h" +#include "sha256.h" + +static bool sha256_sw_available(void) +{ + /* Software SHA-256 is always available */ + return true; +} + +static inline uint32_t ror(uint32_t x, unsigned y) +{ + return (x << (31 & -y)) | (x >> (31 & y)); +} + +static inline uint32_t Ch(uint32_t ctrl, uint32_t if1, uint32_t if0) +{ + return if0 ^ (ctrl & (if1 ^ if0)); +} + +static inline uint32_t Maj(uint32_t x, uint32_t y, uint32_t z) +{ + return (x & y) | (z & (x | y)); +} + +static inline uint32_t Sigma_0(uint32_t x) +{ + return ror(x,2) ^ ror(x,13) ^ ror(x,22); +} + +static inline uint32_t Sigma_1(uint32_t x) +{ + return ror(x,6) ^ ror(x,11) ^ ror(x,25); +} + +static inline uint32_t sigma_0(uint32_t x) +{ + return ror(x,7) ^ ror(x,18) ^ (x >> 3); +} + +static inline uint32_t sigma_1(uint32_t x) +{ + return ror(x,17) ^ ror(x,19) ^ (x >> 10); +} + +static inline void sha256_sw_round( + unsigned round_index, const uint32_t *schedule, + uint32_t *a, uint32_t *b, uint32_t *c, uint32_t *d, + uint32_t *e, uint32_t *f, uint32_t *g, uint32_t *h) +{ + uint32_t t1 = *h + Sigma_1(*e) + Ch(*e,*f,*g) + + sha256_round_constants[round_index] + schedule[round_index]; + + uint32_t t2 = Sigma_0(*a) + Maj(*a,*b,*c); + + *d += t1; + *h = t1 + t2; +} + +static void sha256_sw_block(uint32_t *core, const uint8_t *block) +{ + uint32_t w[SHA256_ROUNDS]; + uint32_t a,b,c,d,e,f,g,h; + + for (size_t t = 0; t < 16; t++) + w[t] = GET_32BIT_MSB_FIRST(block + 4*t); + + for (size_t t = 16; t < SHA256_ROUNDS; t++) + w[t] = sigma_1(w[t-2]) + w[t-7] + sigma_0(w[t-15]) + w[t-16]; + + a = core[0]; b = core[1]; c = core[2]; d = core[3]; + e = core[4]; f = core[5]; g = core[6]; h = core[7]; + + for (size_t t = 0; t < SHA256_ROUNDS; t += 8) { + sha256_sw_round(t+0, w, &a,&b,&c,&d,&e,&f,&g,&h); + sha256_sw_round(t+1, w, &h,&a,&b,&c,&d,&e,&f,&g); + sha256_sw_round(t+2, w, &g,&h,&a,&b,&c,&d,&e,&f); + sha256_sw_round(t+3, w, &f,&g,&h,&a,&b,&c,&d,&e); + sha256_sw_round(t+4, w, &e,&f,&g,&h,&a,&b,&c,&d); + sha256_sw_round(t+5, w, &d,&e,&f,&g,&h,&a,&b,&c); + sha256_sw_round(t+6, w, &c,&d,&e,&f,&g,&h,&a,&b); + sha256_sw_round(t+7, w, &b,&c,&d,&e,&f,&g,&h,&a); + } + + core[0] += a; core[1] += b; core[2] += c; core[3] += d; + core[4] += e; core[5] += f; core[6] += g; core[7] += h; + + smemclr(w, sizeof(w)); +} + +typedef struct sha256_sw { + uint32_t core[8]; + sha256_block blk; + BinarySink_IMPLEMENTATION; + ssh_hash hash; +} sha256_sw; + +static void sha256_sw_write(BinarySink *bs, const void *vp, size_t len); + +static ssh_hash *sha256_sw_new(const ssh_hashalg *alg) +{ + sha256_sw *s = snew(sha256_sw); + + s->hash.vt = alg; + BinarySink_INIT(s, sha256_sw_write); + BinarySink_DELEGATE_INIT(&s->hash, s); + return &s->hash; +} + +static void sha256_sw_reset(ssh_hash *hash) +{ + sha256_sw *s = container_of(hash, sha256_sw, hash); + + memcpy(s->core, sha256_initial_state, sizeof(s->core)); + sha256_block_setup(&s->blk); +} + +static void sha256_sw_copyfrom(ssh_hash *hcopy, ssh_hash *horig) +{ + sha256_sw *copy = container_of(hcopy, sha256_sw, hash); + sha256_sw *orig = container_of(horig, sha256_sw, hash); + + memcpy(copy, orig, sizeof(*copy)); + BinarySink_COPIED(copy); + BinarySink_DELEGATE_INIT(©->hash, copy); +} + +static void sha256_sw_free(ssh_hash *hash) +{ + sha256_sw *s = container_of(hash, sha256_sw, hash); + + smemclr(s, sizeof(*s)); + sfree(s); +} + +static void sha256_sw_write(BinarySink *bs, const void *vp, size_t len) +{ + sha256_sw *s = BinarySink_DOWNCAST(bs, sha256_sw); + + while (len > 0) + if (sha256_block_write(&s->blk, &vp, &len)) + sha256_sw_block(s->core, s->blk.block); +} + +static void sha256_sw_digest(ssh_hash *hash, uint8_t *digest) +{ + sha256_sw *s = container_of(hash, sha256_sw, hash); + + sha256_block_pad(&s->blk, BinarySink_UPCAST(s)); + for (size_t i = 0; i < 8; i++) + PUT_32BIT_MSB_FIRST(digest + 4*i, s->core[i]); +} + +SHA256_VTABLE(sw, "unaccelerated"); diff --git a/crypto/sha256.c b/crypto/sha256.c deleted file mode 100644 index 206a976c..00000000 --- a/crypto/sha256.c +++ /dev/null @@ -1,939 +0,0 @@ -/* - * SHA-256 algorithm as described at - * - * http://csrc.nist.gov/cryptval/shs.html - */ - -#include "ssh.h" -#include - -/* - * Start by deciding whether we can support hardware SHA at all. - */ -#define HW_SHA256_NONE 0 -#define HW_SHA256_NI 1 -#define HW_SHA256_NEON 2 - -#ifdef _FORCE_SHA_NI -# define HW_SHA256 HW_SHA256_NI -#elif defined(__clang__) -# if __has_attribute(target) && __has_include() && \ - (defined(__x86_64__) || defined(__i386)) -# define HW_SHA256 HW_SHA256_NI -# endif -#elif defined(__GNUC__) -# if (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 9)) && \ - (defined(__x86_64__) || defined(__i386)) -# define HW_SHA256 HW_SHA256_NI -# endif -#elif defined (_MSC_VER) -# if (defined(_M_X64) || defined(_M_IX86)) && _MSC_FULL_VER >= 150030729 -# define HW_SHA256 HW_SHA256_NI -# endif -#endif - -#ifdef _FORCE_SHA_NEON -# define HW_SHA256 HW_SHA256_NEON -#elif defined __BYTE_ORDER__ && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ - /* Arm can potentially support both endiannesses, but this code - * hasn't been tested on anything but little. If anyone wants to - * run big-endian, they'll need to fix it first. */ -#elif defined __ARM_FEATURE_CRYPTO - /* If the Arm crypto extension is available already, we can - * support NEON SHA without having to enable anything by hand */ -# define HW_SHA256 HW_SHA256_NEON -#elif defined(__clang__) -# if __has_attribute(target) && __has_include() && \ - (defined(__aarch64__)) - /* clang can enable the crypto extension in AArch64 using - * __attribute__((target)) */ -# define HW_SHA256 HW_SHA256_NEON -# define USE_CLANG_ATTR_TARGET_AARCH64 -# endif -#elif defined _MSC_VER - /* Visual Studio supports the crypto extension when targeting - * AArch64, but as of VS2017, the AArch32 header doesn't quite - * manage it (declaring the shae/shad intrinsics without a round - * key operand). */ -# if defined _M_ARM64 -# define HW_SHA256 HW_SHA256_NEON -# if defined _M_ARM64 -# define USE_ARM64_NEON_H /* unusual header name in this case */ -# endif -# endif -#endif - -#if defined _FORCE_SOFTWARE_SHA || !defined HW_SHA256 -# undef HW_SHA256 -# define HW_SHA256 HW_SHA256_NONE -#endif - -/* - * The actual query function that asks if hardware acceleration is - * available. - */ -static bool sha256_hw_available(void); - -/* - * The top-level selection function, caching the results of - * sha256_hw_available() so it only has to run once. - */ -static bool sha256_hw_available_cached(void) -{ - static bool initialised = false; - static bool hw_available; - if (!initialised) { - hw_available = sha256_hw_available(); - initialised = true; - } - return hw_available; -} - -static ssh_hash *sha256_select(const ssh_hashalg *alg) -{ - const ssh_hashalg *real_alg = - sha256_hw_available_cached() ? &ssh_sha256_hw : &ssh_sha256_sw; - - return ssh_hash_new(real_alg); -} - -const ssh_hashalg ssh_sha256 = { - .new = sha256_select, - .hlen = 32, - .blocklen = 64, - HASHALG_NAMES_ANNOTATED("SHA-256", "dummy selector vtable"), -}; - -/* ---------------------------------------------------------------------- - * Definitions likely to be helpful to multiple implementations. - */ - -static const uint32_t sha256_initial_state[] = { - 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, - 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19, -}; - -static const uint32_t sha256_round_constants[] = { - 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, -}; - -#define SHA256_ROUNDS 64 - -typedef struct sha256_block sha256_block; -struct sha256_block { - uint8_t block[64]; - size_t used; - uint64_t len; -}; - -static inline void sha256_block_setup(sha256_block *blk) -{ - blk->used = 0; - blk->len = 0; -} - -static inline bool sha256_block_write( - sha256_block *blk, const void **vdata, size_t *len) -{ - size_t blkleft = sizeof(blk->block) - blk->used; - size_t chunk = *len < blkleft ? *len : blkleft; - - const uint8_t *p = *vdata; - memcpy(blk->block + blk->used, p, chunk); - *vdata = p + chunk; - *len -= chunk; - blk->used += chunk; - blk->len += chunk; - - if (blk->used == sizeof(blk->block)) { - blk->used = 0; - return true; - } - - return false; -} - -static inline void sha256_block_pad(sha256_block *blk, BinarySink *bs) -{ - uint64_t final_len = blk->len << 3; - size_t pad = 1 + (63 & (55 - blk->used)); - - put_byte(bs, 0x80); - for (size_t i = 1; i < pad; i++) - put_byte(bs, 0); - put_uint64(bs, final_len); - - assert(blk->used == 0 && "Should have exactly hit a block boundary"); -} - -/* ---------------------------------------------------------------------- - * Software implementation of SHA-256. - */ - -static inline uint32_t ror(uint32_t x, unsigned y) -{ - return (x << (31 & -y)) | (x >> (31 & y)); -} - -static inline uint32_t Ch(uint32_t ctrl, uint32_t if1, uint32_t if0) -{ - return if0 ^ (ctrl & (if1 ^ if0)); -} - -static inline uint32_t Maj(uint32_t x, uint32_t y, uint32_t z) -{ - return (x & y) | (z & (x | y)); -} - -static inline uint32_t Sigma_0(uint32_t x) -{ - return ror(x,2) ^ ror(x,13) ^ ror(x,22); -} - -static inline uint32_t Sigma_1(uint32_t x) -{ - return ror(x,6) ^ ror(x,11) ^ ror(x,25); -} - -static inline uint32_t sigma_0(uint32_t x) -{ - return ror(x,7) ^ ror(x,18) ^ (x >> 3); -} - -static inline uint32_t sigma_1(uint32_t x) -{ - return ror(x,17) ^ ror(x,19) ^ (x >> 10); -} - -static inline void sha256_sw_round( - unsigned round_index, const uint32_t *schedule, - uint32_t *a, uint32_t *b, uint32_t *c, uint32_t *d, - uint32_t *e, uint32_t *f, uint32_t *g, uint32_t *h) -{ - uint32_t t1 = *h + Sigma_1(*e) + Ch(*e,*f,*g) + - sha256_round_constants[round_index] + schedule[round_index]; - - uint32_t t2 = Sigma_0(*a) + Maj(*a,*b,*c); - - *d += t1; - *h = t1 + t2; -} - -static void sha256_sw_block(uint32_t *core, const uint8_t *block) -{ - uint32_t w[SHA256_ROUNDS]; - uint32_t a,b,c,d,e,f,g,h; - - for (size_t t = 0; t < 16; t++) - w[t] = GET_32BIT_MSB_FIRST(block + 4*t); - - for (size_t t = 16; t < SHA256_ROUNDS; t++) - w[t] = sigma_1(w[t-2]) + w[t-7] + sigma_0(w[t-15]) + w[t-16]; - - a = core[0]; b = core[1]; c = core[2]; d = core[3]; - e = core[4]; f = core[5]; g = core[6]; h = core[7]; - - for (size_t t = 0; t < SHA256_ROUNDS; t += 8) { - sha256_sw_round(t+0, w, &a,&b,&c,&d,&e,&f,&g,&h); - sha256_sw_round(t+1, w, &h,&a,&b,&c,&d,&e,&f,&g); - sha256_sw_round(t+2, w, &g,&h,&a,&b,&c,&d,&e,&f); - sha256_sw_round(t+3, w, &f,&g,&h,&a,&b,&c,&d,&e); - sha256_sw_round(t+4, w, &e,&f,&g,&h,&a,&b,&c,&d); - sha256_sw_round(t+5, w, &d,&e,&f,&g,&h,&a,&b,&c); - sha256_sw_round(t+6, w, &c,&d,&e,&f,&g,&h,&a,&b); - sha256_sw_round(t+7, w, &b,&c,&d,&e,&f,&g,&h,&a); - } - - core[0] += a; core[1] += b; core[2] += c; core[3] += d; - core[4] += e; core[5] += f; core[6] += g; core[7] += h; - - smemclr(w, sizeof(w)); -} - -typedef struct sha256_sw { - uint32_t core[8]; - sha256_block blk; - BinarySink_IMPLEMENTATION; - ssh_hash hash; -} sha256_sw; - -static void sha256_sw_write(BinarySink *bs, const void *vp, size_t len); - -static ssh_hash *sha256_sw_new(const ssh_hashalg *alg) -{ - sha256_sw *s = snew(sha256_sw); - - s->hash.vt = alg; - BinarySink_INIT(s, sha256_sw_write); - BinarySink_DELEGATE_INIT(&s->hash, s); - return &s->hash; -} - -static void sha256_sw_reset(ssh_hash *hash) -{ - sha256_sw *s = container_of(hash, sha256_sw, hash); - - memcpy(s->core, sha256_initial_state, sizeof(s->core)); - sha256_block_setup(&s->blk); -} - -static void sha256_sw_copyfrom(ssh_hash *hcopy, ssh_hash *horig) -{ - sha256_sw *copy = container_of(hcopy, sha256_sw, hash); - sha256_sw *orig = container_of(horig, sha256_sw, hash); - - memcpy(copy, orig, sizeof(*copy)); - BinarySink_COPIED(copy); - BinarySink_DELEGATE_INIT(©->hash, copy); -} - -static void sha256_sw_free(ssh_hash *hash) -{ - sha256_sw *s = container_of(hash, sha256_sw, hash); - - smemclr(s, sizeof(*s)); - sfree(s); -} - -static void sha256_sw_write(BinarySink *bs, const void *vp, size_t len) -{ - sha256_sw *s = BinarySink_DOWNCAST(bs, sha256_sw); - - while (len > 0) - if (sha256_block_write(&s->blk, &vp, &len)) - sha256_sw_block(s->core, s->blk.block); -} - -static void sha256_sw_digest(ssh_hash *hash, uint8_t *digest) -{ - sha256_sw *s = container_of(hash, sha256_sw, hash); - - sha256_block_pad(&s->blk, BinarySink_UPCAST(s)); - for (size_t i = 0; i < 8; i++) - PUT_32BIT_MSB_FIRST(digest + 4*i, s->core[i]); -} - -const ssh_hashalg ssh_sha256_sw = { - .new = sha256_sw_new, - .reset = sha256_sw_reset, - .copyfrom = sha256_sw_copyfrom, - .digest = sha256_sw_digest, - .free = sha256_sw_free, - .hlen = 32, - .blocklen = 64, - HASHALG_NAMES_ANNOTATED("SHA-256", "unaccelerated"), -}; - -/* ---------------------------------------------------------------------- - * Hardware-accelerated implementation of SHA-256 using x86 SHA-NI. - */ - -#if HW_SHA256 == HW_SHA256_NI - -/* - * Set target architecture for Clang and GCC - */ -#if defined(__clang__) || defined(__GNUC__) -# define FUNC_ISA __attribute__ ((target("sse4.1,sha"))) -#if !defined(__clang__) -# pragma GCC target("sha") -# pragma GCC target("sse4.1") -#endif -#else -# define FUNC_ISA -#endif - -#include -#include -#include -#if defined(__clang__) || defined(__GNUC__) -#include -#endif - -#if defined(__clang__) || defined(__GNUC__) -#include -#define GET_CPU_ID_0(out) \ - __cpuid(0, (out)[0], (out)[1], (out)[2], (out)[3]) -#define GET_CPU_ID_7(out) \ - __cpuid_count(7, 0, (out)[0], (out)[1], (out)[2], (out)[3]) -#else -#define GET_CPU_ID_0(out) __cpuid(out, 0) -#define GET_CPU_ID_7(out) __cpuidex(out, 7, 0) -#endif - -static bool sha256_hw_available(void) -{ - unsigned int CPUInfo[4]; - GET_CPU_ID_0(CPUInfo); - if (CPUInfo[0] < 7) - return false; - - GET_CPU_ID_7(CPUInfo); - return CPUInfo[1] & (1 << 29); /* Check SHA */ -} - -/* SHA256 implementation using new instructions - The code is based on Jeffrey Walton's SHA256 implementation: - https://github.com/noloader/SHA-Intrinsics -*/ -FUNC_ISA -static inline void sha256_ni_block(__m128i *core, const uint8_t *p) -{ - __m128i STATE0, STATE1; - __m128i MSG, TMP; - __m128i MSG0, MSG1, MSG2, MSG3; - const __m128i *block = (const __m128i *)p; - const __m128i MASK = _mm_set_epi64x( - 0x0c0d0e0f08090a0bULL, 0x0405060700010203ULL); - - /* Load initial values */ - STATE0 = core[0]; - STATE1 = core[1]; - - /* Rounds 0-3 */ - MSG = _mm_loadu_si128(block); - MSG0 = _mm_shuffle_epi8(MSG, MASK); - MSG = _mm_add_epi32(MSG0, _mm_set_epi64x( - 0xE9B5DBA5B5C0FBCFULL, 0x71374491428A2F98ULL)); - STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); - MSG = _mm_shuffle_epi32(MSG, 0x0E); - STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); - - /* Rounds 4-7 */ - MSG1 = _mm_loadu_si128(block + 1); - MSG1 = _mm_shuffle_epi8(MSG1, MASK); - MSG = _mm_add_epi32(MSG1, _mm_set_epi64x( - 0xAB1C5ED5923F82A4ULL, 0x59F111F13956C25BULL)); - STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); - MSG = _mm_shuffle_epi32(MSG, 0x0E); - STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); - MSG0 = _mm_sha256msg1_epu32(MSG0, MSG1); - - /* Rounds 8-11 */ - MSG2 = _mm_loadu_si128(block + 2); - MSG2 = _mm_shuffle_epi8(MSG2, MASK); - MSG = _mm_add_epi32(MSG2, _mm_set_epi64x( - 0x550C7DC3243185BEULL, 0x12835B01D807AA98ULL)); - STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); - MSG = _mm_shuffle_epi32(MSG, 0x0E); - STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); - MSG1 = _mm_sha256msg1_epu32(MSG1, MSG2); - - /* Rounds 12-15 */ - MSG3 = _mm_loadu_si128(block + 3); - MSG3 = _mm_shuffle_epi8(MSG3, MASK); - MSG = _mm_add_epi32(MSG3, _mm_set_epi64x( - 0xC19BF1749BDC06A7ULL, 0x80DEB1FE72BE5D74ULL)); - STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); - TMP = _mm_alignr_epi8(MSG3, MSG2, 4); - MSG0 = _mm_add_epi32(MSG0, TMP); - MSG0 = _mm_sha256msg2_epu32(MSG0, MSG3); - MSG = _mm_shuffle_epi32(MSG, 0x0E); - STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); - MSG2 = _mm_sha256msg1_epu32(MSG2, MSG3); - - /* Rounds 16-19 */ - MSG = _mm_add_epi32(MSG0, _mm_set_epi64x( - 0x240CA1CC0FC19DC6ULL, 0xEFBE4786E49B69C1ULL)); - STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); - TMP = _mm_alignr_epi8(MSG0, MSG3, 4); - MSG1 = _mm_add_epi32(MSG1, TMP); - MSG1 = _mm_sha256msg2_epu32(MSG1, MSG0); - MSG = _mm_shuffle_epi32(MSG, 0x0E); - STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); - MSG3 = _mm_sha256msg1_epu32(MSG3, MSG0); - - /* Rounds 20-23 */ - MSG = _mm_add_epi32(MSG1, _mm_set_epi64x( - 0x76F988DA5CB0A9DCULL, 0x4A7484AA2DE92C6FULL)); - STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); - TMP = _mm_alignr_epi8(MSG1, MSG0, 4); - MSG2 = _mm_add_epi32(MSG2, TMP); - MSG2 = _mm_sha256msg2_epu32(MSG2, MSG1); - MSG = _mm_shuffle_epi32(MSG, 0x0E); - STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); - MSG0 = _mm_sha256msg1_epu32(MSG0, MSG1); - - /* Rounds 24-27 */ - MSG = _mm_add_epi32(MSG2, _mm_set_epi64x( - 0xBF597FC7B00327C8ULL, 0xA831C66D983E5152ULL)); - STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); - TMP = _mm_alignr_epi8(MSG2, MSG1, 4); - MSG3 = _mm_add_epi32(MSG3, TMP); - MSG3 = _mm_sha256msg2_epu32(MSG3, MSG2); - MSG = _mm_shuffle_epi32(MSG, 0x0E); - STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); - MSG1 = _mm_sha256msg1_epu32(MSG1, MSG2); - - /* Rounds 28-31 */ - MSG = _mm_add_epi32(MSG3, _mm_set_epi64x( - 0x1429296706CA6351ULL, 0xD5A79147C6E00BF3ULL)); - STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); - TMP = _mm_alignr_epi8(MSG3, MSG2, 4); - MSG0 = _mm_add_epi32(MSG0, TMP); - MSG0 = _mm_sha256msg2_epu32(MSG0, MSG3); - MSG = _mm_shuffle_epi32(MSG, 0x0E); - STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); - MSG2 = _mm_sha256msg1_epu32(MSG2, MSG3); - - /* Rounds 32-35 */ - MSG = _mm_add_epi32(MSG0, _mm_set_epi64x( - 0x53380D134D2C6DFCULL, 0x2E1B213827B70A85ULL)); - STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); - TMP = _mm_alignr_epi8(MSG0, MSG3, 4); - MSG1 = _mm_add_epi32(MSG1, TMP); - MSG1 = _mm_sha256msg2_epu32(MSG1, MSG0); - MSG = _mm_shuffle_epi32(MSG, 0x0E); - STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); - MSG3 = _mm_sha256msg1_epu32(MSG3, MSG0); - - /* Rounds 36-39 */ - MSG = _mm_add_epi32(MSG1, _mm_set_epi64x( - 0x92722C8581C2C92EULL, 0x766A0ABB650A7354ULL)); - STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); - TMP = _mm_alignr_epi8(MSG1, MSG0, 4); - MSG2 = _mm_add_epi32(MSG2, TMP); - MSG2 = _mm_sha256msg2_epu32(MSG2, MSG1); - MSG = _mm_shuffle_epi32(MSG, 0x0E); - STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); - MSG0 = _mm_sha256msg1_epu32(MSG0, MSG1); - - /* Rounds 40-43 */ - MSG = _mm_add_epi32(MSG2, _mm_set_epi64x( - 0xC76C51A3C24B8B70ULL, 0xA81A664BA2BFE8A1ULL)); - STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); - TMP = _mm_alignr_epi8(MSG2, MSG1, 4); - MSG3 = _mm_add_epi32(MSG3, TMP); - MSG3 = _mm_sha256msg2_epu32(MSG3, MSG2); - MSG = _mm_shuffle_epi32(MSG, 0x0E); - STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); - MSG1 = _mm_sha256msg1_epu32(MSG1, MSG2); - - /* Rounds 44-47 */ - MSG = _mm_add_epi32(MSG3, _mm_set_epi64x( - 0x106AA070F40E3585ULL, 0xD6990624D192E819ULL)); - STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); - TMP = _mm_alignr_epi8(MSG3, MSG2, 4); - MSG0 = _mm_add_epi32(MSG0, TMP); - MSG0 = _mm_sha256msg2_epu32(MSG0, MSG3); - MSG = _mm_shuffle_epi32(MSG, 0x0E); - STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); - MSG2 = _mm_sha256msg1_epu32(MSG2, MSG3); - - /* Rounds 48-51 */ - MSG = _mm_add_epi32(MSG0, _mm_set_epi64x( - 0x34B0BCB52748774CULL, 0x1E376C0819A4C116ULL)); - STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); - TMP = _mm_alignr_epi8(MSG0, MSG3, 4); - MSG1 = _mm_add_epi32(MSG1, TMP); - MSG1 = _mm_sha256msg2_epu32(MSG1, MSG0); - MSG = _mm_shuffle_epi32(MSG, 0x0E); - STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); - MSG3 = _mm_sha256msg1_epu32(MSG3, MSG0); - - /* Rounds 52-55 */ - MSG = _mm_add_epi32(MSG1, _mm_set_epi64x( - 0x682E6FF35B9CCA4FULL, 0x4ED8AA4A391C0CB3ULL)); - STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); - TMP = _mm_alignr_epi8(MSG1, MSG0, 4); - MSG2 = _mm_add_epi32(MSG2, TMP); - MSG2 = _mm_sha256msg2_epu32(MSG2, MSG1); - MSG = _mm_shuffle_epi32(MSG, 0x0E); - STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); - - /* Rounds 56-59 */ - MSG = _mm_add_epi32(MSG2, _mm_set_epi64x( - 0x8CC7020884C87814ULL, 0x78A5636F748F82EEULL)); - STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); - TMP = _mm_alignr_epi8(MSG2, MSG1, 4); - MSG3 = _mm_add_epi32(MSG3, TMP); - MSG3 = _mm_sha256msg2_epu32(MSG3, MSG2); - MSG = _mm_shuffle_epi32(MSG, 0x0E); - STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); - - /* Rounds 60-63 */ - MSG = _mm_add_epi32(MSG3, _mm_set_epi64x( - 0xC67178F2BEF9A3F7ULL, 0xA4506CEB90BEFFFAULL)); - STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); - MSG = _mm_shuffle_epi32(MSG, 0x0E); - STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); - - /* Combine state */ - core[0] = _mm_add_epi32(STATE0, core[0]); - core[1] = _mm_add_epi32(STATE1, core[1]); -} - -typedef struct sha256_ni { - /* - * These two vectors store the 8 words of the SHA-256 state, but - * not in the same order they appear in the spec: the first word - * holds A,B,E,F and the second word C,D,G,H. - */ - __m128i core[2]; - sha256_block blk; - void *pointer_to_free; - BinarySink_IMPLEMENTATION; - ssh_hash hash; -} sha256_ni; - -static void sha256_ni_write(BinarySink *bs, const void *vp, size_t len); - -static sha256_ni *sha256_ni_alloc(void) -{ - /* - * The __m128i variables in the context structure need to be - * 16-byte aligned, but not all malloc implementations that this - * code has to work with will guarantee to return a 16-byte - * aligned pointer. So we over-allocate, manually realign the - * pointer ourselves, and store the original one inside the - * context so we know how to free it later. - */ - void *allocation = smalloc(sizeof(sha256_ni) + 15); - uintptr_t alloc_address = (uintptr_t)allocation; - uintptr_t aligned_address = (alloc_address + 15) & ~15; - sha256_ni *s = (sha256_ni *)aligned_address; - s->pointer_to_free = allocation; - return s; -} - -static ssh_hash *sha256_ni_new(const ssh_hashalg *alg) -{ - if (!sha256_hw_available_cached()) - return NULL; - - sha256_ni *s = sha256_ni_alloc(); - - s->hash.vt = alg; - BinarySink_INIT(s, sha256_ni_write); - BinarySink_DELEGATE_INIT(&s->hash, s); - - return &s->hash; -} - -FUNC_ISA static void sha256_ni_reset(ssh_hash *hash) -{ - sha256_ni *s = container_of(hash, sha256_ni, hash); - - /* Initialise the core vectors in their storage order */ - s->core[0] = _mm_set_epi64x( - 0x6a09e667bb67ae85ULL, 0x510e527f9b05688cULL); - s->core[1] = _mm_set_epi64x( - 0x3c6ef372a54ff53aULL, 0x1f83d9ab5be0cd19ULL); - - sha256_block_setup(&s->blk); -} - -static void sha256_ni_copyfrom(ssh_hash *hcopy, ssh_hash *horig) -{ - sha256_ni *copy = container_of(hcopy, sha256_ni, hash); - sha256_ni *orig = container_of(horig, sha256_ni, hash); - - void *ptf_save = copy->pointer_to_free; - *copy = *orig; /* structure copy */ - copy->pointer_to_free = ptf_save; - - BinarySink_COPIED(copy); - BinarySink_DELEGATE_INIT(©->hash, copy); -} - -static void sha256_ni_free(ssh_hash *hash) -{ - sha256_ni *s = container_of(hash, sha256_ni, hash); - - void *ptf = s->pointer_to_free; - smemclr(s, sizeof(*s)); - sfree(ptf); -} - -static void sha256_ni_write(BinarySink *bs, const void *vp, size_t len) -{ - sha256_ni *s = BinarySink_DOWNCAST(bs, sha256_ni); - - while (len > 0) - if (sha256_block_write(&s->blk, &vp, &len)) - sha256_ni_block(s->core, s->blk.block); -} - -FUNC_ISA static void sha256_ni_digest(ssh_hash *hash, uint8_t *digest) -{ - sha256_ni *s = container_of(hash, sha256_ni, hash); - - sha256_block_pad(&s->blk, BinarySink_UPCAST(s)); - - /* Rearrange the words into the output order */ - __m128i feba = _mm_shuffle_epi32(s->core[0], 0x1B); - __m128i dchg = _mm_shuffle_epi32(s->core[1], 0xB1); - __m128i dcba = _mm_blend_epi16(feba, dchg, 0xF0); - __m128i hgfe = _mm_alignr_epi8(dchg, feba, 8); - - /* Byte-swap them into the output endianness */ - const __m128i mask = _mm_setr_epi8(3,2,1,0,7,6,5,4,11,10,9,8,15,14,13,12); - dcba = _mm_shuffle_epi8(dcba, mask); - hgfe = _mm_shuffle_epi8(hgfe, mask); - - /* And store them */ - __m128i *output = (__m128i *)digest; - _mm_storeu_si128(output, dcba); - _mm_storeu_si128(output+1, hgfe); -} - -const ssh_hashalg ssh_sha256_hw = { - .new = sha256_ni_new, - .reset = sha256_ni_reset, - .copyfrom = sha256_ni_copyfrom, - .digest = sha256_ni_digest, - .free = sha256_ni_free, - .hlen = 32, - .blocklen = 64, - HASHALG_NAMES_ANNOTATED("SHA-256", "SHA-NI accelerated"), -}; - -/* ---------------------------------------------------------------------- - * Hardware-accelerated implementation of SHA-256 using Arm NEON. - */ - -#elif HW_SHA256 == HW_SHA256_NEON - -/* - * Manually set the target architecture, if we decided above that we - * need to. - */ -#ifdef USE_CLANG_ATTR_TARGET_AARCH64 -/* - * A spot of cheating: redefine some ACLE feature macros before - * including arm_neon.h. Otherwise we won't get the SHA intrinsics - * defined by that header, because it will be looking at the settings - * for the whole translation unit rather than the ones we're going to - * put on some particular functions using __attribute__((target)). - */ -#define __ARM_NEON 1 -#define __ARM_FEATURE_CRYPTO 1 -#define FUNC_ISA __attribute__ ((target("neon,crypto"))) -#endif /* USE_CLANG_ATTR_TARGET_AARCH64 */ - -#ifndef FUNC_ISA -#define FUNC_ISA -#endif - -#ifdef USE_ARM64_NEON_H -#include -#else -#include -#endif - -static bool sha256_hw_available(void) -{ - /* - * For Arm, we delegate to a per-platform detection function (see - * explanation in sshaes.c). - */ - return platform_sha256_hw_available(); -} - -typedef struct sha256_neon_core sha256_neon_core; -struct sha256_neon_core { - uint32x4_t abcd, efgh; -}; - -FUNC_ISA -static inline uint32x4_t sha256_neon_load_input(const uint8_t *p) -{ - return vreinterpretq_u32_u8(vrev32q_u8(vld1q_u8(p))); -} - -FUNC_ISA -static inline uint32x4_t sha256_neon_schedule_update( - uint32x4_t m4, uint32x4_t m3, uint32x4_t m2, uint32x4_t m1) -{ - return vsha256su1q_u32(vsha256su0q_u32(m4, m3), m2, m1); -} - -FUNC_ISA -static inline sha256_neon_core sha256_neon_round4( - sha256_neon_core old, uint32x4_t sched, unsigned round) -{ - sha256_neon_core new; - - uint32x4_t round_input = vaddq_u32( - sched, vld1q_u32(sha256_round_constants + round)); - new.abcd = vsha256hq_u32 (old.abcd, old.efgh, round_input); - new.efgh = vsha256h2q_u32(old.efgh, old.abcd, round_input); - return new; -} - -FUNC_ISA -static inline void sha256_neon_block(sha256_neon_core *core, const uint8_t *p) -{ - uint32x4_t s0, s1, s2, s3; - sha256_neon_core cr = *core; - - s0 = sha256_neon_load_input(p); - cr = sha256_neon_round4(cr, s0, 0); - s1 = sha256_neon_load_input(p+16); - cr = sha256_neon_round4(cr, s1, 4); - s2 = sha256_neon_load_input(p+32); - cr = sha256_neon_round4(cr, s2, 8); - s3 = sha256_neon_load_input(p+48); - cr = sha256_neon_round4(cr, s3, 12); - s0 = sha256_neon_schedule_update(s0, s1, s2, s3); - cr = sha256_neon_round4(cr, s0, 16); - s1 = sha256_neon_schedule_update(s1, s2, s3, s0); - cr = sha256_neon_round4(cr, s1, 20); - s2 = sha256_neon_schedule_update(s2, s3, s0, s1); - cr = sha256_neon_round4(cr, s2, 24); - s3 = sha256_neon_schedule_update(s3, s0, s1, s2); - cr = sha256_neon_round4(cr, s3, 28); - s0 = sha256_neon_schedule_update(s0, s1, s2, s3); - cr = sha256_neon_round4(cr, s0, 32); - s1 = sha256_neon_schedule_update(s1, s2, s3, s0); - cr = sha256_neon_round4(cr, s1, 36); - s2 = sha256_neon_schedule_update(s2, s3, s0, s1); - cr = sha256_neon_round4(cr, s2, 40); - s3 = sha256_neon_schedule_update(s3, s0, s1, s2); - cr = sha256_neon_round4(cr, s3, 44); - s0 = sha256_neon_schedule_update(s0, s1, s2, s3); - cr = sha256_neon_round4(cr, s0, 48); - s1 = sha256_neon_schedule_update(s1, s2, s3, s0); - cr = sha256_neon_round4(cr, s1, 52); - s2 = sha256_neon_schedule_update(s2, s3, s0, s1); - cr = sha256_neon_round4(cr, s2, 56); - s3 = sha256_neon_schedule_update(s3, s0, s1, s2); - cr = sha256_neon_round4(cr, s3, 60); - - core->abcd = vaddq_u32(core->abcd, cr.abcd); - core->efgh = vaddq_u32(core->efgh, cr.efgh); -} - -typedef struct sha256_neon { - sha256_neon_core core; - sha256_block blk; - BinarySink_IMPLEMENTATION; - ssh_hash hash; -} sha256_neon; - -static void sha256_neon_write(BinarySink *bs, const void *vp, size_t len); - -static ssh_hash *sha256_neon_new(const ssh_hashalg *alg) -{ - if (!sha256_hw_available_cached()) - return NULL; - - sha256_neon *s = snew(sha256_neon); - - s->hash.vt = alg; - BinarySink_INIT(s, sha256_neon_write); - BinarySink_DELEGATE_INIT(&s->hash, s); - return &s->hash; -} - -static void sha256_neon_reset(ssh_hash *hash) -{ - sha256_neon *s = container_of(hash, sha256_neon, hash); - - s->core.abcd = vld1q_u32(sha256_initial_state); - s->core.efgh = vld1q_u32(sha256_initial_state + 4); - - sha256_block_setup(&s->blk); -} - -static void sha256_neon_copyfrom(ssh_hash *hcopy, ssh_hash *horig) -{ - sha256_neon *copy = container_of(hcopy, sha256_neon, hash); - sha256_neon *orig = container_of(horig, sha256_neon, hash); - - *copy = *orig; /* structure copy */ - - BinarySink_COPIED(copy); - BinarySink_DELEGATE_INIT(©->hash, copy); -} - -static void sha256_neon_free(ssh_hash *hash) -{ - sha256_neon *s = container_of(hash, sha256_neon, hash); - smemclr(s, sizeof(*s)); - sfree(s); -} - -static void sha256_neon_write(BinarySink *bs, const void *vp, size_t len) -{ - sha256_neon *s = BinarySink_DOWNCAST(bs, sha256_neon); - - while (len > 0) - if (sha256_block_write(&s->blk, &vp, &len)) - sha256_neon_block(&s->core, s->blk.block); -} - -static void sha256_neon_digest(ssh_hash *hash, uint8_t *digest) -{ - sha256_neon *s = container_of(hash, sha256_neon, hash); - - sha256_block_pad(&s->blk, BinarySink_UPCAST(s)); - vst1q_u8(digest, vrev32q_u8(vreinterpretq_u8_u32(s->core.abcd))); - vst1q_u8(digest + 16, vrev32q_u8(vreinterpretq_u8_u32(s->core.efgh))); -} - -const ssh_hashalg ssh_sha256_hw = { - .new = sha256_neon_new, - .reset = sha256_neon_reset, - .copyfrom = sha256_neon_copyfrom, - .digest = sha256_neon_digest, - .free = sha256_neon_free, - .hlen = 32, - .blocklen = 64, - HASHALG_NAMES_ANNOTATED("SHA-256", "NEON accelerated"), -}; - -/* ---------------------------------------------------------------------- - * Stub functions if we have no hardware-accelerated SHA-256. In this - * case, sha256_hw_new returns NULL (though it should also never be - * selected by sha256_select, so the only thing that should even be - * _able_ to call it is testcrypt). As a result, the remaining vtable - * functions should never be called at all. - */ - -#elif HW_SHA256 == HW_SHA256_NONE - -static bool sha256_hw_available(void) -{ - return false; -} - -static ssh_hash *sha256_stub_new(const ssh_hashalg *alg) -{ - return NULL; -} - -#define STUB_BODY { unreachable("Should never be called"); } - -static void sha256_stub_reset(ssh_hash *hash) STUB_BODY -static void sha256_stub_copyfrom(ssh_hash *hash, ssh_hash *orig) STUB_BODY -static void sha256_stub_free(ssh_hash *hash) STUB_BODY -static void sha256_stub_digest(ssh_hash *hash, uint8_t *digest) STUB_BODY - -const ssh_hashalg ssh_sha256_hw = { - .new = sha256_stub_new, - .reset = sha256_stub_reset, - .copyfrom = sha256_stub_copyfrom, - .digest = sha256_stub_digest, - .free = sha256_stub_free, - .hlen = 32, - .blocklen = 64, - HASHALG_NAMES_ANNOTATED("SHA-256", "!NONEXISTENT ACCELERATED VERSION!"), -}; - -#endif /* HW_SHA256 */ diff --git a/crypto/sha256.h b/crypto/sha256.h new file mode 100644 index 00000000..e6ca7564 --- /dev/null +++ b/crypto/sha256.h @@ -0,0 +1,105 @@ +/* + * Definitions likely to be helpful to multiple SHA-256 implementations. + */ + +/* + * The 'extra' structure used by SHA-256 implementations is used to + * include information about how to check if a given implementation is + * available at run time, and whether we've already checked. + */ +struct sha256_extra_mutable; +struct sha256_extra { + /* Function to check availability. Might be expensive, so we don't + * want to call it more than once. */ + bool (*check_available)(void); + + /* Point to a writable substructure. */ + struct sha256_extra_mutable *mut; +}; +struct sha256_extra_mutable { + bool checked_availability; + bool is_available; +}; +static inline bool check_availability(const struct sha256_extra *extra) +{ + if (!extra->mut->checked_availability) { + extra->mut->is_available = extra->check_available(); + extra->mut->checked_availability = true; + } + + return extra->mut->is_available; +} + +/* + * Macro to define a SHA-256 vtable together with its 'extra' + * structure. + */ +#define SHA256_VTABLE(impl_c, impl_display) \ + static struct sha256_extra_mutable sha256_ ## impl_c ## _extra_mut; \ + static const struct sha256_extra sha256_ ## impl_c ## _extra = { \ + .check_available = sha256_ ## impl_c ## _available, \ + .mut = &sha256_ ## impl_c ## _extra_mut, \ + }; \ + const ssh_hashalg ssh_sha256_ ## impl_c = { \ + .new = sha256_ ## impl_c ## _new, \ + .reset = sha256_ ## impl_c ## _reset, \ + .copyfrom = sha256_ ## impl_c ## _copyfrom, \ + .digest = sha256_ ## impl_c ## _digest, \ + .free = sha256_ ## impl_c ## _free, \ + .hlen = 32, \ + .blocklen = 64, \ + HASHALG_NAMES_ANNOTATED("SHA-256", impl_display), \ + .extra = &sha256_ ## impl_c ## _extra, \ + } + +extern const uint32_t sha256_initial_state[8]; +extern const uint32_t sha256_round_constants[64]; + +#define SHA256_ROUNDS 64 + +typedef struct sha256_block sha256_block; +struct sha256_block { + uint8_t block[64]; + size_t used; + uint64_t len; +}; + +static inline void sha256_block_setup(sha256_block *blk) +{ + blk->used = 0; + blk->len = 0; +} + +static inline bool sha256_block_write( + sha256_block *blk, const void **vdata, size_t *len) +{ + size_t blkleft = sizeof(blk->block) - blk->used; + size_t chunk = *len < blkleft ? *len : blkleft; + + const uint8_t *p = *vdata; + memcpy(blk->block + blk->used, p, chunk); + *vdata = p + chunk; + *len -= chunk; + blk->used += chunk; + blk->len += chunk; + + if (blk->used == sizeof(blk->block)) { + blk->used = 0; + return true; + } + + return false; +} + +static inline void sha256_block_pad(sha256_block *blk, BinarySink *bs) +{ + uint64_t final_len = blk->len << 3; + size_t pad = 1 + (63 & (55 - blk->used)); + + put_byte(bs, 0x80); + for (size_t i = 1; i < pad; i++) + put_byte(bs, 0); + put_uint64(bs, final_len); + + assert(blk->used == 0 && "Should have exactly hit a block boundary"); +} diff --git a/crypto/sha512-common.c b/crypto/sha512-common.c new file mode 100644 index 00000000..89ac136c --- /dev/null +++ b/crypto/sha512-common.c @@ -0,0 +1,71 @@ +/* + * Common variable definitions across all the SHA-512 implementations. + */ + +#include "ssh.h" +#include "sha512.h" + +const uint64_t sha512_initial_state[8] = { + 0x6a09e667f3bcc908ULL, + 0xbb67ae8584caa73bULL, + 0x3c6ef372fe94f82bULL, + 0xa54ff53a5f1d36f1ULL, + 0x510e527fade682d1ULL, + 0x9b05688c2b3e6c1fULL, + 0x1f83d9abfb41bd6bULL, + 0x5be0cd19137e2179ULL, +}; + +const uint64_t sha384_initial_state[8] = { + 0xcbbb9d5dc1059ed8ULL, + 0x629a292a367cd507ULL, + 0x9159015a3070dd17ULL, + 0x152fecd8f70e5939ULL, + 0x67332667ffc00b31ULL, + 0x8eb44a8768581511ULL, + 0xdb0c2e0d64f98fa7ULL, + 0x47b5481dbefa4fa4ULL, +}; + +const uint64_t sha512_round_constants[80] = { + 0x428a2f98d728ae22ULL, 0x7137449123ef65cdULL, + 0xb5c0fbcfec4d3b2fULL, 0xe9b5dba58189dbbcULL, + 0x3956c25bf348b538ULL, 0x59f111f1b605d019ULL, + 0x923f82a4af194f9bULL, 0xab1c5ed5da6d8118ULL, + 0xd807aa98a3030242ULL, 0x12835b0145706fbeULL, + 0x243185be4ee4b28cULL, 0x550c7dc3d5ffb4e2ULL, + 0x72be5d74f27b896fULL, 0x80deb1fe3b1696b1ULL, + 0x9bdc06a725c71235ULL, 0xc19bf174cf692694ULL, + 0xe49b69c19ef14ad2ULL, 0xefbe4786384f25e3ULL, + 0x0fc19dc68b8cd5b5ULL, 0x240ca1cc77ac9c65ULL, + 0x2de92c6f592b0275ULL, 0x4a7484aa6ea6e483ULL, + 0x5cb0a9dcbd41fbd4ULL, 0x76f988da831153b5ULL, + 0x983e5152ee66dfabULL, 0xa831c66d2db43210ULL, + 0xb00327c898fb213fULL, 0xbf597fc7beef0ee4ULL, + 0xc6e00bf33da88fc2ULL, 0xd5a79147930aa725ULL, + 0x06ca6351e003826fULL, 0x142929670a0e6e70ULL, + 0x27b70a8546d22ffcULL, 0x2e1b21385c26c926ULL, + 0x4d2c6dfc5ac42aedULL, 0x53380d139d95b3dfULL, + 0x650a73548baf63deULL, 0x766a0abb3c77b2a8ULL, + 0x81c2c92e47edaee6ULL, 0x92722c851482353bULL, + 0xa2bfe8a14cf10364ULL, 0xa81a664bbc423001ULL, + 0xc24b8b70d0f89791ULL, 0xc76c51a30654be30ULL, + 0xd192e819d6ef5218ULL, 0xd69906245565a910ULL, + 0xf40e35855771202aULL, 0x106aa07032bbd1b8ULL, + 0x19a4c116b8d2d0c8ULL, 0x1e376c085141ab53ULL, + 0x2748774cdf8eeb99ULL, 0x34b0bcb5e19b48a8ULL, + 0x391c0cb3c5c95a63ULL, 0x4ed8aa4ae3418acbULL, + 0x5b9cca4f7763e373ULL, 0x682e6ff3d6b2b8a3ULL, + 0x748f82ee5defb2fcULL, 0x78a5636f43172f60ULL, + 0x84c87814a1f0ab72ULL, 0x8cc702081a6439ecULL, + 0x90befffa23631e28ULL, 0xa4506cebde82bde9ULL, + 0xbef9a3f7b2c67915ULL, 0xc67178f2e372532bULL, + 0xca273eceea26619cULL, 0xd186b8c721c0c207ULL, + 0xeada7dd6cde0eb1eULL, 0xf57d4f7fee6ed178ULL, + 0x06f067aa72176fbaULL, 0x0a637dc5a2c898a6ULL, + 0x113f9804bef90daeULL, 0x1b710b35131c471bULL, + 0x28db77f523047d84ULL, 0x32caab7b40c72493ULL, + 0x3c9ebe0a15c9bebcULL, 0x431d67c49c100d4cULL, + 0x4cc5d4becb3e42b6ULL, 0x597f299cfc657e2aULL, + 0x5fcb6fab3ad6faecULL, 0x6c44198c4a475817ULL, +}; diff --git a/crypto/sha512-neon.c b/crypto/sha512-neon.c new file mode 100644 index 00000000..849a79d7 --- /dev/null +++ b/crypto/sha512-neon.c @@ -0,0 +1,329 @@ +/* + * Hardware-accelerated implementation of SHA-512 using Arm NEON. + */ + +#include "ssh.h" +#include "sha512.h" + +#if USE_ARM64_NEON_H +#include +#else +#include +#endif + +static bool sha512_neon_available(void) +{ + /* + * For Arm, we delegate to a per-platform detection function (see + * explanation in aes-neon.c). + */ + return platform_sha512_neon_available(); +} + +#if !HAVE_NEON_SHA512_INTRINSICS +/* + * clang 12 and before do not provide the SHA-512 NEON intrinsics, but + * do provide assembler support for the underlying instructions. So I + * define the intrinsic functions myself, using inline assembler. + */ +static inline uint64x2_t vsha512su0q_u64(uint64x2_t x, uint64x2_t y) +{ + __asm__("sha512su0 %0.2D,%1.2D" : "+w" (x) : "w" (y)); + return x; +} +static inline uint64x2_t vsha512su1q_u64(uint64x2_t x, uint64x2_t y, + uint64x2_t z) +{ + __asm__("sha512su1 %0.2D,%1.2D,%2.2D" : "+w" (x) : "w" (y), "w" (z)); + return x; +} +static inline uint64x2_t vsha512hq_u64(uint64x2_t x, uint64x2_t y, + uint64x2_t z) +{ + __asm__("sha512h %0,%1,%2.2D" : "+w" (x) : "w" (y), "w" (z)); + return x; +} +static inline uint64x2_t vsha512h2q_u64(uint64x2_t x, uint64x2_t y, + uint64x2_t z) +{ + __asm__("sha512h2 %0,%1,%2.2D" : "+w" (x) : "w" (y), "w" (z)); + return x; +} +#endif /* HAVE_NEON_SHA512_INTRINSICS */ + +typedef struct sha512_neon_core sha512_neon_core; +struct sha512_neon_core { + uint64x2_t ab, cd, ef, gh; +}; + +static inline uint64x2_t sha512_neon_load_input(const uint8_t *p) +{ + return vreinterpretq_u64_u8(vrev64q_u8(vld1q_u8(p))); +} + +static inline uint64x2_t sha512_neon_schedule_update( + uint64x2_t m8, uint64x2_t m7, uint64x2_t m4, uint64x2_t m3, uint64x2_t m1) +{ + /* + * vsha512su0q_u64() takes words from a long way back in the + * schedule and performs the sigma_0 half of the computation of + * the next two 64-bit message-schedule words. + * + * vsha512su1q_u64() combines the result of that with the sigma_1 + * steps, to output the finished version of those two words. The + * total amount of input data it requires fits nicely into three + * 128-bit vector registers, but one of those registers is + * misaligned compared to the 128-bit chunks that the message + * schedule is stored in. So we use vextq_u64 to make one of its + * input words out of the second half of m4 and the first half of + * m3. + */ + return vsha512su1q_u64(vsha512su0q_u64(m8, m7), m1, vextq_u64(m4, m3, 1)); +} + +static inline void sha512_neon_round2( + unsigned round_index, uint64x2_t schedule_words, + uint64x2_t *ab, uint64x2_t *cd, uint64x2_t *ef, uint64x2_t *gh) +{ + /* + * vsha512hq_u64 performs the Sigma_1 and Ch half of the + * computation of two rounds of SHA-512 (including feeding back + * one of the outputs from the first of those half-rounds into the + * second one). + * + * vsha512h2q_u64 combines the result of that with the Sigma_0 and + * Maj steps, and outputs one 128-bit vector that replaces the gh + * piece of the input hash state, and a second that updates cd by + * addition. + * + * Similarly to vsha512su1q_u64 above, some of the input registers + * expected by these instructions are misaligned by 64 bits + * relative to the chunks we've divided the hash state into, so we + * have to start by making 'de' and 'fg' words out of our input + * cd,ef,gh, using vextq_u64. + * + * Also, one of the inputs to vsha512hq_u64 is expected to contain + * the results of summing gh + two round constants + two words of + * message schedule, but the two words of the message schedule + * have to be the opposite way round in the vector register from + * the way that vsha512su1q_u64 output them. Hence, there's + * another vextq_u64 in here that swaps the two halves of the + * initial_sum vector register. + * + * (This also means that I don't have to prepare a specially + * reordered version of the sha512_round_constants[] array: as + * long as I'm unavoidably doing a swap at run time _anyway_, I + * can load from the normally ordered version of that array, and + * just take care to fold in that data _before_ the swap rather + * than after.) + */ + + /* Load two round constants, with the first one in the low half */ + uint64x2_t round_constants = vld1q_u64( + sha512_round_constants + round_index); + + /* Add schedule words to round constants */ + uint64x2_t initial_sum = vaddq_u64(schedule_words, round_constants); + + /* Swap that sum around so the word used in the first of the two + * rounds is in the _high_ half of the vector, matching where h + * lives in the gh vector */ + uint64x2_t swapped_initial_sum = vextq_u64(initial_sum, initial_sum, 1); + + /* Add gh to that, now that they're matching ways round */ + uint64x2_t sum = vaddq_u64(swapped_initial_sum, *gh); + + /* Make the misaligned de and fg words */ + uint64x2_t de = vextq_u64(*cd, *ef, 1); + uint64x2_t fg = vextq_u64(*ef, *gh, 1); + + /* Now we're ready to put all the pieces together. The output from + * vsha512h2q_u64 can be used directly as the new gh, and the + * output from vsha512hq_u64 is simultaneously the intermediate + * value passed to h2 and the thing you have to add on to cd. */ + uint64x2_t intermed = vsha512hq_u64(sum, fg, de); + *gh = vsha512h2q_u64(intermed, *cd, *ab); + *cd = vaddq_u64(*cd, intermed); +} + +static inline void sha512_neon_block(sha512_neon_core *core, const uint8_t *p) +{ + uint64x2_t s0, s1, s2, s3, s4, s5, s6, s7; + + uint64x2_t ab = core->ab, cd = core->cd, ef = core->ef, gh = core->gh; + + s0 = sha512_neon_load_input(p + 16*0); + sha512_neon_round2(0, s0, &ab, &cd, &ef, &gh); + s1 = sha512_neon_load_input(p + 16*1); + sha512_neon_round2(2, s1, &gh, &ab, &cd, &ef); + s2 = sha512_neon_load_input(p + 16*2); + sha512_neon_round2(4, s2, &ef, &gh, &ab, &cd); + s3 = sha512_neon_load_input(p + 16*3); + sha512_neon_round2(6, s3, &cd, &ef, &gh, &ab); + s4 = sha512_neon_load_input(p + 16*4); + sha512_neon_round2(8, s4, &ab, &cd, &ef, &gh); + s5 = sha512_neon_load_input(p + 16*5); + sha512_neon_round2(10, s5, &gh, &ab, &cd, &ef); + s6 = sha512_neon_load_input(p + 16*6); + sha512_neon_round2(12, s6, &ef, &gh, &ab, &cd); + s7 = sha512_neon_load_input(p + 16*7); + sha512_neon_round2(14, s7, &cd, &ef, &gh, &ab); + s0 = sha512_neon_schedule_update(s0, s1, s4, s5, s7); + sha512_neon_round2(16, s0, &ab, &cd, &ef, &gh); + s1 = sha512_neon_schedule_update(s1, s2, s5, s6, s0); + sha512_neon_round2(18, s1, &gh, &ab, &cd, &ef); + s2 = sha512_neon_schedule_update(s2, s3, s6, s7, s1); + sha512_neon_round2(20, s2, &ef, &gh, &ab, &cd); + s3 = sha512_neon_schedule_update(s3, s4, s7, s0, s2); + sha512_neon_round2(22, s3, &cd, &ef, &gh, &ab); + s4 = sha512_neon_schedule_update(s4, s5, s0, s1, s3); + sha512_neon_round2(24, s4, &ab, &cd, &ef, &gh); + s5 = sha512_neon_schedule_update(s5, s6, s1, s2, s4); + sha512_neon_round2(26, s5, &gh, &ab, &cd, &ef); + s6 = sha512_neon_schedule_update(s6, s7, s2, s3, s5); + sha512_neon_round2(28, s6, &ef, &gh, &ab, &cd); + s7 = sha512_neon_schedule_update(s7, s0, s3, s4, s6); + sha512_neon_round2(30, s7, &cd, &ef, &gh, &ab); + s0 = sha512_neon_schedule_update(s0, s1, s4, s5, s7); + sha512_neon_round2(32, s0, &ab, &cd, &ef, &gh); + s1 = sha512_neon_schedule_update(s1, s2, s5, s6, s0); + sha512_neon_round2(34, s1, &gh, &ab, &cd, &ef); + s2 = sha512_neon_schedule_update(s2, s3, s6, s7, s1); + sha512_neon_round2(36, s2, &ef, &gh, &ab, &cd); + s3 = sha512_neon_schedule_update(s3, s4, s7, s0, s2); + sha512_neon_round2(38, s3, &cd, &ef, &gh, &ab); + s4 = sha512_neon_schedule_update(s4, s5, s0, s1, s3); + sha512_neon_round2(40, s4, &ab, &cd, &ef, &gh); + s5 = sha512_neon_schedule_update(s5, s6, s1, s2, s4); + sha512_neon_round2(42, s5, &gh, &ab, &cd, &ef); + s6 = sha512_neon_schedule_update(s6, s7, s2, s3, s5); + sha512_neon_round2(44, s6, &ef, &gh, &ab, &cd); + s7 = sha512_neon_schedule_update(s7, s0, s3, s4, s6); + sha512_neon_round2(46, s7, &cd, &ef, &gh, &ab); + s0 = sha512_neon_schedule_update(s0, s1, s4, s5, s7); + sha512_neon_round2(48, s0, &ab, &cd, &ef, &gh); + s1 = sha512_neon_schedule_update(s1, s2, s5, s6, s0); + sha512_neon_round2(50, s1, &gh, &ab, &cd, &ef); + s2 = sha512_neon_schedule_update(s2, s3, s6, s7, s1); + sha512_neon_round2(52, s2, &ef, &gh, &ab, &cd); + s3 = sha512_neon_schedule_update(s3, s4, s7, s0, s2); + sha512_neon_round2(54, s3, &cd, &ef, &gh, &ab); + s4 = sha512_neon_schedule_update(s4, s5, s0, s1, s3); + sha512_neon_round2(56, s4, &ab, &cd, &ef, &gh); + s5 = sha512_neon_schedule_update(s5, s6, s1, s2, s4); + sha512_neon_round2(58, s5, &gh, &ab, &cd, &ef); + s6 = sha512_neon_schedule_update(s6, s7, s2, s3, s5); + sha512_neon_round2(60, s6, &ef, &gh, &ab, &cd); + s7 = sha512_neon_schedule_update(s7, s0, s3, s4, s6); + sha512_neon_round2(62, s7, &cd, &ef, &gh, &ab); + s0 = sha512_neon_schedule_update(s0, s1, s4, s5, s7); + sha512_neon_round2(64, s0, &ab, &cd, &ef, &gh); + s1 = sha512_neon_schedule_update(s1, s2, s5, s6, s0); + sha512_neon_round2(66, s1, &gh, &ab, &cd, &ef); + s2 = sha512_neon_schedule_update(s2, s3, s6, s7, s1); + sha512_neon_round2(68, s2, &ef, &gh, &ab, &cd); + s3 = sha512_neon_schedule_update(s3, s4, s7, s0, s2); + sha512_neon_round2(70, s3, &cd, &ef, &gh, &ab); + s4 = sha512_neon_schedule_update(s4, s5, s0, s1, s3); + sha512_neon_round2(72, s4, &ab, &cd, &ef, &gh); + s5 = sha512_neon_schedule_update(s5, s6, s1, s2, s4); + sha512_neon_round2(74, s5, &gh, &ab, &cd, &ef); + s6 = sha512_neon_schedule_update(s6, s7, s2, s3, s5); + sha512_neon_round2(76, s6, &ef, &gh, &ab, &cd); + s7 = sha512_neon_schedule_update(s7, s0, s3, s4, s6); + sha512_neon_round2(78, s7, &cd, &ef, &gh, &ab); + + core->ab = vaddq_u64(core->ab, ab); + core->cd = vaddq_u64(core->cd, cd); + core->ef = vaddq_u64(core->ef, ef); + core->gh = vaddq_u64(core->gh, gh); +} + +typedef struct sha512_neon { + sha512_neon_core core; + sha512_block blk; + BinarySink_IMPLEMENTATION; + ssh_hash hash; +} sha512_neon; + +static void sha512_neon_write(BinarySink *bs, const void *vp, size_t len); + +static ssh_hash *sha512_neon_new(const ssh_hashalg *alg) +{ + const struct sha512_extra *extra = (const struct sha512_extra *)alg->extra; + if (!check_availability(extra)) + return NULL; + + sha512_neon *s = snew(sha512_neon); + + s->hash.vt = alg; + BinarySink_INIT(s, sha512_neon_write); + BinarySink_DELEGATE_INIT(&s->hash, s); + return &s->hash; +} + +static void sha512_neon_reset(ssh_hash *hash) +{ + sha512_neon *s = container_of(hash, sha512_neon, hash); + const struct sha512_extra *extra = + (const struct sha512_extra *)hash->vt->extra; + + s->core.ab = vld1q_u64(extra->initial_state); + s->core.cd = vld1q_u64(extra->initial_state+2); + s->core.ef = vld1q_u64(extra->initial_state+4); + s->core.gh = vld1q_u64(extra->initial_state+6); + + sha512_block_setup(&s->blk); +} + +static void sha512_neon_copyfrom(ssh_hash *hcopy, ssh_hash *horig) +{ + sha512_neon *copy = container_of(hcopy, sha512_neon, hash); + sha512_neon *orig = container_of(horig, sha512_neon, hash); + + *copy = *orig; /* structure copy */ + + BinarySink_COPIED(copy); + BinarySink_DELEGATE_INIT(©->hash, copy); +} + +static void sha512_neon_free(ssh_hash *hash) +{ + sha512_neon *s = container_of(hash, sha512_neon, hash); + smemclr(s, sizeof(*s)); + sfree(s); +} + +static void sha512_neon_write(BinarySink *bs, const void *vp, size_t len) +{ + sha512_neon *s = BinarySink_DOWNCAST(bs, sha512_neon); + + while (len > 0) + if (sha512_block_write(&s->blk, &vp, &len)) + sha512_neon_block(&s->core, s->blk.block); +} + +static void sha512_neon_digest(ssh_hash *hash, uint8_t *digest) +{ + sha512_neon *s = container_of(hash, sha512_neon, hash); + + sha512_block_pad(&s->blk, BinarySink_UPCAST(s)); + + vst1q_u8(digest, vrev64q_u8(vreinterpretq_u8_u64(s->core.ab))); + vst1q_u8(digest+16, vrev64q_u8(vreinterpretq_u8_u64(s->core.cd))); + vst1q_u8(digest+32, vrev64q_u8(vreinterpretq_u8_u64(s->core.ef))); + vst1q_u8(digest+48, vrev64q_u8(vreinterpretq_u8_u64(s->core.gh))); +} + +static void sha384_neon_digest(ssh_hash *hash, uint8_t *digest) +{ + sha512_neon *s = container_of(hash, sha512_neon, hash); + + sha512_block_pad(&s->blk, BinarySink_UPCAST(s)); + + vst1q_u8(digest, vrev64q_u8(vreinterpretq_u8_u64(s->core.ab))); + vst1q_u8(digest+16, vrev64q_u8(vreinterpretq_u8_u64(s->core.cd))); + vst1q_u8(digest+32, vrev64q_u8(vreinterpretq_u8_u64(s->core.ef))); +} + +SHA512_VTABLES(neon, "NEON accelerated"); diff --git a/crypto/sha512-select.c b/crypto/sha512-select.c new file mode 100644 index 00000000..ecd567bd --- /dev/null +++ b/crypto/sha512-select.c @@ -0,0 +1,61 @@ +/* + * Top-level vtables to select a SHA-512 implementation. + */ + +#include +#include + +#include "putty.h" +#include "ssh.h" +#include "sha512.h" + +static const ssh_hashalg *const real_sha512_algs[] = { +#if HAVE_NEON_SHA512 + &ssh_sha512_neon, +#endif + &ssh_sha512_sw, + NULL, +}; + +static const ssh_hashalg *const real_sha384_algs[] = { +#if HAVE_NEON_SHA512 + &ssh_sha384_neon, +#endif + &ssh_sha384_sw, + NULL, +}; + +static ssh_hash *sha512_select(const ssh_hashalg *alg) +{ + const ssh_hashalg *const *real_algs = + (const ssh_hashalg *const *)alg->extra; + + for (size_t i = 0; real_algs[i]; i++) { + const ssh_hashalg *alg = real_algs[i]; + const struct sha512_extra *alg_extra = + (const struct sha512_extra *)alg->extra; + if (check_availability(alg_extra)) + return ssh_hash_new(alg); + } + + /* We should never reach the NULL at the end of the list, because + * the last non-NULL entry should be software-only SHA-512, which + * is always available. */ + unreachable("sha512_select ran off the end of its list"); +} + +const ssh_hashalg ssh_sha512 = { + .new = sha512_select, + .hlen = 64, + .blocklen = 128, + HASHALG_NAMES_ANNOTATED("SHA-512", "dummy selector vtable"), + .extra = real_sha512_algs, +}; + +const ssh_hashalg ssh_sha384 = { + .new = sha512_select, + .hlen = 48, + .blocklen = 128, + HASHALG_NAMES_ANNOTATED("SHA-384", "dummy selector vtable"), + .extra = real_sha384_algs, +}; diff --git a/crypto/sha512-sw.c b/crypto/sha512-sw.c new file mode 100644 index 00000000..9e47bbb9 --- /dev/null +++ b/crypto/sha512-sw.c @@ -0,0 +1,168 @@ +/* + * Software implementation of SHA-512. + */ + +#include "ssh.h" +#include "sha512.h" + +static bool sha512_sw_available(void) +{ + /* Software SHA-512 is always available */ + return true; +} + +static inline uint64_t ror(uint64_t x, unsigned y) +{ + return (x << (63 & -y)) | (x >> (63 & y)); +} + +static inline uint64_t Ch(uint64_t ctrl, uint64_t if1, uint64_t if0) +{ + return if0 ^ (ctrl & (if1 ^ if0)); +} + +static inline uint64_t Maj(uint64_t x, uint64_t y, uint64_t z) +{ + return (x & y) | (z & (x | y)); +} + +static inline uint64_t Sigma_0(uint64_t x) +{ + return ror(x,28) ^ ror(x,34) ^ ror(x,39); +} + +static inline uint64_t Sigma_1(uint64_t x) +{ + return ror(x,14) ^ ror(x,18) ^ ror(x,41); +} + +static inline uint64_t sigma_0(uint64_t x) +{ + return ror(x,1) ^ ror(x,8) ^ (x >> 7); +} + +static inline uint64_t sigma_1(uint64_t x) +{ + return ror(x,19) ^ ror(x,61) ^ (x >> 6); +} + +static inline void sha512_sw_round( + unsigned round_index, const uint64_t *schedule, + uint64_t *a, uint64_t *b, uint64_t *c, uint64_t *d, + uint64_t *e, uint64_t *f, uint64_t *g, uint64_t *h) +{ + uint64_t t1 = *h + Sigma_1(*e) + Ch(*e,*f,*g) + + sha512_round_constants[round_index] + schedule[round_index]; + + uint64_t t2 = Sigma_0(*a) + Maj(*a,*b,*c); + + *d += t1; + *h = t1 + t2; +} + +static void sha512_sw_block(uint64_t *core, const uint8_t *block) +{ + uint64_t w[SHA512_ROUNDS]; + uint64_t a,b,c,d,e,f,g,h; + + int t; + + for (t = 0; t < 16; t++) + w[t] = GET_64BIT_MSB_FIRST(block + 8*t); + + for (t = 16; t < SHA512_ROUNDS; t++) + w[t] = w[t-16] + w[t-7] + sigma_0(w[t-15]) + sigma_1(w[t-2]); + + a = core[0]; b = core[1]; c = core[2]; d = core[3]; + e = core[4]; f = core[5]; g = core[6]; h = core[7]; + + for (t = 0; t < SHA512_ROUNDS; t+=8) { + sha512_sw_round(t+0, w, &a,&b,&c,&d,&e,&f,&g,&h); + sha512_sw_round(t+1, w, &h,&a,&b,&c,&d,&e,&f,&g); + sha512_sw_round(t+2, w, &g,&h,&a,&b,&c,&d,&e,&f); + sha512_sw_round(t+3, w, &f,&g,&h,&a,&b,&c,&d,&e); + sha512_sw_round(t+4, w, &e,&f,&g,&h,&a,&b,&c,&d); + sha512_sw_round(t+5, w, &d,&e,&f,&g,&h,&a,&b,&c); + sha512_sw_round(t+6, w, &c,&d,&e,&f,&g,&h,&a,&b); + sha512_sw_round(t+7, w, &b,&c,&d,&e,&f,&g,&h,&a); + } + + core[0] += a; core[1] += b; core[2] += c; core[3] += d; + core[4] += e; core[5] += f; core[6] += g; core[7] += h; + + smemclr(w, sizeof(w)); +} + +typedef struct sha512_sw { + uint64_t core[8]; + sha512_block blk; + BinarySink_IMPLEMENTATION; + ssh_hash hash; +} sha512_sw; + +static void sha512_sw_write(BinarySink *bs, const void *vp, size_t len); + +static ssh_hash *sha512_sw_new(const ssh_hashalg *alg) +{ + sha512_sw *s = snew(sha512_sw); + + s->hash.vt = alg; + BinarySink_INIT(s, sha512_sw_write); + BinarySink_DELEGATE_INIT(&s->hash, s); + return &s->hash; +} + +static void sha512_sw_reset(ssh_hash *hash) +{ + sha512_sw *s = container_of(hash, sha512_sw, hash); + const struct sha512_extra *extra = + (const struct sha512_extra *)hash->vt->extra; + + memcpy(s->core, extra->initial_state, sizeof(s->core)); + sha512_block_setup(&s->blk); +} + +static void sha512_sw_copyfrom(ssh_hash *hcopy, ssh_hash *horig) +{ + sha512_sw *copy = container_of(hcopy, sha512_sw, hash); + sha512_sw *orig = container_of(horig, sha512_sw, hash); + + memcpy(copy, orig, sizeof(*copy)); + BinarySink_COPIED(copy); + BinarySink_DELEGATE_INIT(©->hash, copy); +} + +static void sha512_sw_free(ssh_hash *hash) +{ + sha512_sw *s = container_of(hash, sha512_sw, hash); + + smemclr(s, sizeof(*s)); + sfree(s); +} + +static void sha512_sw_write(BinarySink *bs, const void *vp, size_t len) +{ + sha512_sw *s = BinarySink_DOWNCAST(bs, sha512_sw); + + while (len > 0) + if (sha512_block_write(&s->blk, &vp, &len)) + sha512_sw_block(s->core, s->blk.block); +} + +static void sha512_sw_digest(ssh_hash *hash, uint8_t *digest) +{ + sha512_sw *s = container_of(hash, sha512_sw, hash); + + sha512_block_pad(&s->blk, BinarySink_UPCAST(s)); + for (size_t i = 0; i < hash->vt->hlen / 8; i++) + PUT_64BIT_MSB_FIRST(digest + 8*i, s->core[i]); +} + +/* + * This implementation doesn't need separate digest methods for + * SHA-384 and SHA-512, because the above implementation reads the + * hash length out of the vtable. + */ +#define sha384_sw_digest sha512_sw_digest + +SHA512_VTABLES(sw, "unaccelerated"); diff --git a/crypto/sha512.c b/crypto/sha512.c deleted file mode 100644 index cba7f38d..00000000 --- a/crypto/sha512.c +++ /dev/null @@ -1,836 +0,0 @@ -/* - * SHA-512 algorithm as described at - * - * http://csrc.nist.gov/cryptval/shs.html - * - * Modifications made for SHA-384 also - */ - -#include -#include "ssh.h" - -/* - * Start by deciding whether we can support hardware SHA at all. - */ -#define HW_SHA512_NONE 0 -#define HW_SHA512_NEON 1 - -#ifdef _FORCE_SHA512_NEON -# define HW_SHA512 HW_SHA512_NEON -#elif defined __BYTE_ORDER__ && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ - /* Arm can potentially support both endiannesses, but this code - * hasn't been tested on anything but little. If anyone wants to - * run big-endian, they'll need to fix it first. */ -#elif defined __ARM_FEATURE_SHA512 - /* If the Arm SHA-512 extension is available already, we can - * support NEON SHA without having to enable anything by hand */ -# define HW_SHA512 HW_SHA512_NEON -#elif defined(__clang__) -# if __has_attribute(target) && __has_include() && \ - (defined(__aarch64__)) - /* clang can enable the crypto extension in AArch64 using - * __attribute__((target)) */ -# define HW_SHA512 HW_SHA512_NEON -# define USE_CLANG_ATTR_TARGET_AARCH64 -# endif -#endif - -#if defined _FORCE_SOFTWARE_SHA || !defined HW_SHA512 -# undef HW_SHA512 -# define HW_SHA512 HW_SHA512_NONE -#endif - -/* - * The actual query function that asks if hardware acceleration is - * available. - */ -static bool sha512_hw_available(void); - -/* - * The top-level selection function, caching the results of - * sha512_hw_available() so it only has to run once. - */ -static bool sha512_hw_available_cached(void) -{ - static bool initialised = false; - static bool hw_available; - if (!initialised) { - hw_available = sha512_hw_available(); - initialised = true; - } - return hw_available; -} - -struct sha512_select_options { - const ssh_hashalg *hw, *sw; -}; - -static ssh_hash *sha512_select(const ssh_hashalg *alg) -{ - const struct sha512_select_options *options = - (const struct sha512_select_options *)alg->extra; - - const ssh_hashalg *real_alg = - sha512_hw_available_cached() ? options->hw : options->sw; - - return ssh_hash_new(real_alg); -} - -const struct sha512_select_options ssh_sha512_select_options = { - &ssh_sha512_hw, &ssh_sha512_sw, -}; -const struct sha512_select_options ssh_sha384_select_options = { - &ssh_sha384_hw, &ssh_sha384_sw, -}; - -const ssh_hashalg ssh_sha512 = { - .new = sha512_select, - .hlen = 64, - .blocklen = 128, - HASHALG_NAMES_ANNOTATED("SHA-512", "dummy selector vtable"), - .extra = &ssh_sha512_select_options, -}; - -const ssh_hashalg ssh_sha384 = { - .new = sha512_select, - .hlen = 48, - .blocklen = 128, - HASHALG_NAMES_ANNOTATED("SHA-384", "dummy selector vtable"), - .extra = &ssh_sha384_select_options, -}; - -/* ---------------------------------------------------------------------- - * Definitions likely to be helpful to multiple implementations. - */ - -static const uint64_t sha512_initial_state[] = { - 0x6a09e667f3bcc908ULL, - 0xbb67ae8584caa73bULL, - 0x3c6ef372fe94f82bULL, - 0xa54ff53a5f1d36f1ULL, - 0x510e527fade682d1ULL, - 0x9b05688c2b3e6c1fULL, - 0x1f83d9abfb41bd6bULL, - 0x5be0cd19137e2179ULL, -}; - -static const uint64_t sha384_initial_state[] = { - 0xcbbb9d5dc1059ed8ULL, - 0x629a292a367cd507ULL, - 0x9159015a3070dd17ULL, - 0x152fecd8f70e5939ULL, - 0x67332667ffc00b31ULL, - 0x8eb44a8768581511ULL, - 0xdb0c2e0d64f98fa7ULL, - 0x47b5481dbefa4fa4ULL, -}; - -static const uint64_t sha512_round_constants[] = { - 0x428a2f98d728ae22ULL, 0x7137449123ef65cdULL, - 0xb5c0fbcfec4d3b2fULL, 0xe9b5dba58189dbbcULL, - 0x3956c25bf348b538ULL, 0x59f111f1b605d019ULL, - 0x923f82a4af194f9bULL, 0xab1c5ed5da6d8118ULL, - 0xd807aa98a3030242ULL, 0x12835b0145706fbeULL, - 0x243185be4ee4b28cULL, 0x550c7dc3d5ffb4e2ULL, - 0x72be5d74f27b896fULL, 0x80deb1fe3b1696b1ULL, - 0x9bdc06a725c71235ULL, 0xc19bf174cf692694ULL, - 0xe49b69c19ef14ad2ULL, 0xefbe4786384f25e3ULL, - 0x0fc19dc68b8cd5b5ULL, 0x240ca1cc77ac9c65ULL, - 0x2de92c6f592b0275ULL, 0x4a7484aa6ea6e483ULL, - 0x5cb0a9dcbd41fbd4ULL, 0x76f988da831153b5ULL, - 0x983e5152ee66dfabULL, 0xa831c66d2db43210ULL, - 0xb00327c898fb213fULL, 0xbf597fc7beef0ee4ULL, - 0xc6e00bf33da88fc2ULL, 0xd5a79147930aa725ULL, - 0x06ca6351e003826fULL, 0x142929670a0e6e70ULL, - 0x27b70a8546d22ffcULL, 0x2e1b21385c26c926ULL, - 0x4d2c6dfc5ac42aedULL, 0x53380d139d95b3dfULL, - 0x650a73548baf63deULL, 0x766a0abb3c77b2a8ULL, - 0x81c2c92e47edaee6ULL, 0x92722c851482353bULL, - 0xa2bfe8a14cf10364ULL, 0xa81a664bbc423001ULL, - 0xc24b8b70d0f89791ULL, 0xc76c51a30654be30ULL, - 0xd192e819d6ef5218ULL, 0xd69906245565a910ULL, - 0xf40e35855771202aULL, 0x106aa07032bbd1b8ULL, - 0x19a4c116b8d2d0c8ULL, 0x1e376c085141ab53ULL, - 0x2748774cdf8eeb99ULL, 0x34b0bcb5e19b48a8ULL, - 0x391c0cb3c5c95a63ULL, 0x4ed8aa4ae3418acbULL, - 0x5b9cca4f7763e373ULL, 0x682e6ff3d6b2b8a3ULL, - 0x748f82ee5defb2fcULL, 0x78a5636f43172f60ULL, - 0x84c87814a1f0ab72ULL, 0x8cc702081a6439ecULL, - 0x90befffa23631e28ULL, 0xa4506cebde82bde9ULL, - 0xbef9a3f7b2c67915ULL, 0xc67178f2e372532bULL, - 0xca273eceea26619cULL, 0xd186b8c721c0c207ULL, - 0xeada7dd6cde0eb1eULL, 0xf57d4f7fee6ed178ULL, - 0x06f067aa72176fbaULL, 0x0a637dc5a2c898a6ULL, - 0x113f9804bef90daeULL, 0x1b710b35131c471bULL, - 0x28db77f523047d84ULL, 0x32caab7b40c72493ULL, - 0x3c9ebe0a15c9bebcULL, 0x431d67c49c100d4cULL, - 0x4cc5d4becb3e42b6ULL, 0x597f299cfc657e2aULL, - 0x5fcb6fab3ad6faecULL, 0x6c44198c4a475817ULL, -}; - -#define SHA512_ROUNDS 80 - -typedef struct sha512_block sha512_block; -struct sha512_block { - uint8_t block[128]; - size_t used; - uint64_t lenhi, lenlo; -}; - -static inline void sha512_block_setup(sha512_block *blk) -{ - blk->used = 0; - blk->lenhi = blk->lenlo = 0; -} - -static inline bool sha512_block_write( - sha512_block *blk, const void **vdata, size_t *len) -{ - size_t blkleft = sizeof(blk->block) - blk->used; - size_t chunk = *len < blkleft ? *len : blkleft; - - const uint8_t *p = *vdata; - memcpy(blk->block + blk->used, p, chunk); - *vdata = p + chunk; - *len -= chunk; - blk->used += chunk; - - size_t chunkbits = chunk << 3; - - blk->lenlo += chunkbits; - blk->lenhi += (blk->lenlo < chunkbits); - - if (blk->used == sizeof(blk->block)) { - blk->used = 0; - return true; - } - - return false; -} - -static inline void sha512_block_pad(sha512_block *blk, BinarySink *bs) -{ - uint64_t final_lenhi = blk->lenhi; - uint64_t final_lenlo = blk->lenlo; - size_t pad = 127 & (111 - blk->used); - - put_byte(bs, 0x80); - put_padding(bs, pad, 0); - put_uint64(bs, final_lenhi); - put_uint64(bs, final_lenlo); - - assert(blk->used == 0 && "Should have exactly hit a block boundary"); -} - -/* ---------------------------------------------------------------------- - * Software implementation of SHA-512. - */ - -static inline uint64_t ror(uint64_t x, unsigned y) -{ - return (x << (63 & -y)) | (x >> (63 & y)); -} - -static inline uint64_t Ch(uint64_t ctrl, uint64_t if1, uint64_t if0) -{ - return if0 ^ (ctrl & (if1 ^ if0)); -} - -static inline uint64_t Maj(uint64_t x, uint64_t y, uint64_t z) -{ - return (x & y) | (z & (x | y)); -} - -static inline uint64_t Sigma_0(uint64_t x) -{ - return ror(x,28) ^ ror(x,34) ^ ror(x,39); -} - -static inline uint64_t Sigma_1(uint64_t x) -{ - return ror(x,14) ^ ror(x,18) ^ ror(x,41); -} - -static inline uint64_t sigma_0(uint64_t x) -{ - return ror(x,1) ^ ror(x,8) ^ (x >> 7); -} - -static inline uint64_t sigma_1(uint64_t x) -{ - return ror(x,19) ^ ror(x,61) ^ (x >> 6); -} - -static inline void sha512_sw_round( - unsigned round_index, const uint64_t *schedule, - uint64_t *a, uint64_t *b, uint64_t *c, uint64_t *d, - uint64_t *e, uint64_t *f, uint64_t *g, uint64_t *h) -{ - uint64_t t1 = *h + Sigma_1(*e) + Ch(*e,*f,*g) + - sha512_round_constants[round_index] + schedule[round_index]; - - uint64_t t2 = Sigma_0(*a) + Maj(*a,*b,*c); - - *d += t1; - *h = t1 + t2; -} - -static void sha512_sw_block(uint64_t *core, const uint8_t *block) -{ - uint64_t w[SHA512_ROUNDS]; - uint64_t a,b,c,d,e,f,g,h; - - int t; - - for (t = 0; t < 16; t++) - w[t] = GET_64BIT_MSB_FIRST(block + 8*t); - - for (t = 16; t < SHA512_ROUNDS; t++) - w[t] = w[t-16] + w[t-7] + sigma_0(w[t-15]) + sigma_1(w[t-2]); - - a = core[0]; b = core[1]; c = core[2]; d = core[3]; - e = core[4]; f = core[5]; g = core[6]; h = core[7]; - - for (t = 0; t < SHA512_ROUNDS; t+=8) { - sha512_sw_round(t+0, w, &a,&b,&c,&d,&e,&f,&g,&h); - sha512_sw_round(t+1, w, &h,&a,&b,&c,&d,&e,&f,&g); - sha512_sw_round(t+2, w, &g,&h,&a,&b,&c,&d,&e,&f); - sha512_sw_round(t+3, w, &f,&g,&h,&a,&b,&c,&d,&e); - sha512_sw_round(t+4, w, &e,&f,&g,&h,&a,&b,&c,&d); - sha512_sw_round(t+5, w, &d,&e,&f,&g,&h,&a,&b,&c); - sha512_sw_round(t+6, w, &c,&d,&e,&f,&g,&h,&a,&b); - sha512_sw_round(t+7, w, &b,&c,&d,&e,&f,&g,&h,&a); - } - - core[0] += a; core[1] += b; core[2] += c; core[3] += d; - core[4] += e; core[5] += f; core[6] += g; core[7] += h; - - smemclr(w, sizeof(w)); -} - -typedef struct sha512_sw { - uint64_t core[8]; - sha512_block blk; - BinarySink_IMPLEMENTATION; - ssh_hash hash; -} sha512_sw; - -static void sha512_sw_write(BinarySink *bs, const void *vp, size_t len); - -static ssh_hash *sha512_sw_new(const ssh_hashalg *alg) -{ - sha512_sw *s = snew(sha512_sw); - - s->hash.vt = alg; - BinarySink_INIT(s, sha512_sw_write); - BinarySink_DELEGATE_INIT(&s->hash, s); - return &s->hash; -} - -static void sha512_sw_reset(ssh_hash *hash) -{ - sha512_sw *s = container_of(hash, sha512_sw, hash); - - /* The 'extra' field in the ssh_hashalg indicates which - * initialisation vector we're using */ - memcpy(s->core, hash->vt->extra, sizeof(s->core)); - sha512_block_setup(&s->blk); -} - -static void sha512_sw_copyfrom(ssh_hash *hcopy, ssh_hash *horig) -{ - sha512_sw *copy = container_of(hcopy, sha512_sw, hash); - sha512_sw *orig = container_of(horig, sha512_sw, hash); - - memcpy(copy, orig, sizeof(*copy)); - BinarySink_COPIED(copy); - BinarySink_DELEGATE_INIT(©->hash, copy); -} - -static void sha512_sw_free(ssh_hash *hash) -{ - sha512_sw *s = container_of(hash, sha512_sw, hash); - - smemclr(s, sizeof(*s)); - sfree(s); -} - -static void sha512_sw_write(BinarySink *bs, const void *vp, size_t len) -{ - sha512_sw *s = BinarySink_DOWNCAST(bs, sha512_sw); - - while (len > 0) - if (sha512_block_write(&s->blk, &vp, &len)) - sha512_sw_block(s->core, s->blk.block); -} - -static void sha512_sw_digest(ssh_hash *hash, uint8_t *digest) -{ - sha512_sw *s = container_of(hash, sha512_sw, hash); - - sha512_block_pad(&s->blk, BinarySink_UPCAST(s)); - for (size_t i = 0; i < hash->vt->hlen / 8; i++) - PUT_64BIT_MSB_FIRST(digest + 8*i, s->core[i]); -} - -const ssh_hashalg ssh_sha512_sw = { - .new = sha512_sw_new, - .reset = sha512_sw_reset, - .copyfrom = sha512_sw_copyfrom, - .digest = sha512_sw_digest, - .free = sha512_sw_free, - .hlen = 64, - .blocklen = 128, - HASHALG_NAMES_ANNOTATED("SHA-512", "unaccelerated"), - .extra = sha512_initial_state, -}; - -const ssh_hashalg ssh_sha384_sw = { - .new = sha512_sw_new, - .reset = sha512_sw_reset, - .copyfrom = sha512_sw_copyfrom, - .digest = sha512_sw_digest, - .free = sha512_sw_free, - .hlen = 48, - .blocklen = 128, - HASHALG_NAMES_ANNOTATED("SHA-384", "unaccelerated"), - .extra = sha384_initial_state, -}; - -/* ---------------------------------------------------------------------- - * Hardware-accelerated implementation of SHA-512 using Arm NEON. - */ - -#if HW_SHA512 == HW_SHA512_NEON - -/* - * Manually set the target architecture, if we decided above that we - * need to. - */ -#ifdef USE_CLANG_ATTR_TARGET_AARCH64 -/* - * A spot of cheating: redefine some ACLE feature macros before - * including arm_neon.h. Otherwise we won't get the SHA intrinsics - * defined by that header, because it will be looking at the settings - * for the whole translation unit rather than the ones we're going to - * put on some particular functions using __attribute__((target)). - */ -#define __ARM_NEON 1 -#define __ARM_FEATURE_CRYPTO 1 -#define FUNC_ISA __attribute__ ((target("neon,sha3"))) -#endif /* USE_CLANG_ATTR_TARGET_AARCH64 */ - -#ifndef FUNC_ISA -#define FUNC_ISA -#endif - -#ifdef USE_ARM64_NEON_H -#include -#else -#include -#endif - -static bool sha512_hw_available(void) -{ - /* - * For Arm, we delegate to a per-platform detection function (see - * explanation in sshaes.c). - */ - return platform_sha512_hw_available(); -} - -#if defined __clang__ -/* - * As of 2020-12-24, I've found that clang doesn't provide the SHA-512 - * NEON intrinsics. So I define my own set using inline assembler, and - * use #define to effectively rename them over the top of the standard - * names. - * - * The aim of that #define technique is that it should avoid a build - * failure if these intrinsics _are_ defined in . - * Obviously it would be better in that situation to switch back to - * using the real intrinsics, but until I see a version of clang that - * supports them, I won't know what version number to test in the - * ifdef. - */ -static inline FUNC_ISA -uint64x2_t vsha512su0q_u64_asm(uint64x2_t x, uint64x2_t y) { - __asm__("sha512su0 %0.2D,%1.2D" : "+w" (x) : "w" (y)); - return x; -} -static inline FUNC_ISA -uint64x2_t vsha512su1q_u64_asm(uint64x2_t x, uint64x2_t y, uint64x2_t z) { - __asm__("sha512su1 %0.2D,%1.2D,%2.2D" : "+w" (x) : "w" (y), "w" (z)); - return x; -} -static inline FUNC_ISA -uint64x2_t vsha512hq_u64_asm(uint64x2_t x, uint64x2_t y, uint64x2_t z) { - __asm__("sha512h %0,%1,%2.2D" : "+w" (x) : "w" (y), "w" (z)); - return x; -} -static inline FUNC_ISA -uint64x2_t vsha512h2q_u64_asm(uint64x2_t x, uint64x2_t y, uint64x2_t z) { - __asm__("sha512h2 %0,%1,%2.2D" : "+w" (x) : "w" (y), "w" (z)); - return x; -} -#undef vsha512su0q_u64 -#define vsha512su0q_u64 vsha512su0q_u64_asm -#undef vsha512su1q_u64 -#define vsha512su1q_u64 vsha512su1q_u64_asm -#undef vsha512hq_u64 -#define vsha512hq_u64 vsha512hq_u64_asm -#undef vsha512h2q_u64 -#define vsha512h2q_u64 vsha512h2q_u64_asm -#endif /* defined __clang__ */ - -typedef struct sha512_neon_core sha512_neon_core; -struct sha512_neon_core { - uint64x2_t ab, cd, ef, gh; -}; - -FUNC_ISA -static inline uint64x2_t sha512_neon_load_input(const uint8_t *p) -{ - return vreinterpretq_u64_u8(vrev64q_u8(vld1q_u8(p))); -} - -FUNC_ISA -static inline uint64x2_t sha512_neon_schedule_update( - uint64x2_t m8, uint64x2_t m7, uint64x2_t m4, uint64x2_t m3, uint64x2_t m1) -{ - /* - * vsha512su0q_u64() takes words from a long way back in the - * schedule and performs the sigma_0 half of the computation of - * the next two 64-bit message-schedule words. - * - * vsha512su1q_u64() combines the result of that with the sigma_1 - * steps, to output the finished version of those two words. The - * total amount of input data it requires fits nicely into three - * 128-bit vector registers, but one of those registers is - * misaligned compared to the 128-bit chunks that the message - * schedule is stored in. So we use vextq_u64 to make one of its - * input words out of the second half of m4 and the first half of - * m3. - */ - return vsha512su1q_u64(vsha512su0q_u64(m8, m7), m1, vextq_u64(m4, m3, 1)); -} - -FUNC_ISA -static inline void sha512_neon_round2( - unsigned round_index, uint64x2_t schedule_words, - uint64x2_t *ab, uint64x2_t *cd, uint64x2_t *ef, uint64x2_t *gh) -{ - /* - * vsha512hq_u64 performs the Sigma_1 and Ch half of the - * computation of two rounds of SHA-512 (including feeding back - * one of the outputs from the first of those half-rounds into the - * second one). - * - * vsha512h2q_u64 combines the result of that with the Sigma_0 and - * Maj steps, and outputs one 128-bit vector that replaces the gh - * piece of the input hash state, and a second that updates cd by - * addition. - * - * Similarly to vsha512su1q_u64 above, some of the input registers - * expected by these instructions are misaligned by 64 bits - * relative to the chunks we've divided the hash state into, so we - * have to start by making 'de' and 'fg' words out of our input - * cd,ef,gh, using vextq_u64. - * - * Also, one of the inputs to vsha512hq_u64 is expected to contain - * the results of summing gh + two round constants + two words of - * message schedule, but the two words of the message schedule - * have to be the opposite way round in the vector register from - * the way that vsha512su1q_u64 output them. Hence, there's - * another vextq_u64 in here that swaps the two halves of the - * initial_sum vector register. - * - * (This also means that I don't have to prepare a specially - * reordered version of the sha512_round_constants[] array: as - * long as I'm unavoidably doing a swap at run time _anyway_, I - * can load from the normally ordered version of that array, and - * just take care to fold in that data _before_ the swap rather - * than after.) - */ - - /* Load two round constants, with the first one in the low half */ - uint64x2_t round_constants = vld1q_u64( - sha512_round_constants + round_index); - - /* Add schedule words to round constants */ - uint64x2_t initial_sum = vaddq_u64(schedule_words, round_constants); - - /* Swap that sum around so the word used in the first of the two - * rounds is in the _high_ half of the vector, matching where h - * lives in the gh vector */ - uint64x2_t swapped_initial_sum = vextq_u64(initial_sum, initial_sum, 1); - - /* Add gh to that, now that they're matching ways round */ - uint64x2_t sum = vaddq_u64(swapped_initial_sum, *gh); - - /* Make the misaligned de and fg words */ - uint64x2_t de = vextq_u64(*cd, *ef, 1); - uint64x2_t fg = vextq_u64(*ef, *gh, 1); - - /* Now we're ready to put all the pieces together. The output from - * vsha512h2q_u64 can be used directly as the new gh, and the - * output from vsha512hq_u64 is simultaneously the intermediate - * value passed to h2 and the thing you have to add on to cd. */ - uint64x2_t intermed = vsha512hq_u64(sum, fg, de); - *gh = vsha512h2q_u64(intermed, *cd, *ab); - *cd = vaddq_u64(*cd, intermed); -} - -FUNC_ISA -static inline void sha512_neon_block(sha512_neon_core *core, const uint8_t *p) -{ - uint64x2_t s0, s1, s2, s3, s4, s5, s6, s7; - - uint64x2_t ab = core->ab, cd = core->cd, ef = core->ef, gh = core->gh; - - s0 = sha512_neon_load_input(p + 16*0); - sha512_neon_round2(0, s0, &ab, &cd, &ef, &gh); - s1 = sha512_neon_load_input(p + 16*1); - sha512_neon_round2(2, s1, &gh, &ab, &cd, &ef); - s2 = sha512_neon_load_input(p + 16*2); - sha512_neon_round2(4, s2, &ef, &gh, &ab, &cd); - s3 = sha512_neon_load_input(p + 16*3); - sha512_neon_round2(6, s3, &cd, &ef, &gh, &ab); - s4 = sha512_neon_load_input(p + 16*4); - sha512_neon_round2(8, s4, &ab, &cd, &ef, &gh); - s5 = sha512_neon_load_input(p + 16*5); - sha512_neon_round2(10, s5, &gh, &ab, &cd, &ef); - s6 = sha512_neon_load_input(p + 16*6); - sha512_neon_round2(12, s6, &ef, &gh, &ab, &cd); - s7 = sha512_neon_load_input(p + 16*7); - sha512_neon_round2(14, s7, &cd, &ef, &gh, &ab); - s0 = sha512_neon_schedule_update(s0, s1, s4, s5, s7); - sha512_neon_round2(16, s0, &ab, &cd, &ef, &gh); - s1 = sha512_neon_schedule_update(s1, s2, s5, s6, s0); - sha512_neon_round2(18, s1, &gh, &ab, &cd, &ef); - s2 = sha512_neon_schedule_update(s2, s3, s6, s7, s1); - sha512_neon_round2(20, s2, &ef, &gh, &ab, &cd); - s3 = sha512_neon_schedule_update(s3, s4, s7, s0, s2); - sha512_neon_round2(22, s3, &cd, &ef, &gh, &ab); - s4 = sha512_neon_schedule_update(s4, s5, s0, s1, s3); - sha512_neon_round2(24, s4, &ab, &cd, &ef, &gh); - s5 = sha512_neon_schedule_update(s5, s6, s1, s2, s4); - sha512_neon_round2(26, s5, &gh, &ab, &cd, &ef); - s6 = sha512_neon_schedule_update(s6, s7, s2, s3, s5); - sha512_neon_round2(28, s6, &ef, &gh, &ab, &cd); - s7 = sha512_neon_schedule_update(s7, s0, s3, s4, s6); - sha512_neon_round2(30, s7, &cd, &ef, &gh, &ab); - s0 = sha512_neon_schedule_update(s0, s1, s4, s5, s7); - sha512_neon_round2(32, s0, &ab, &cd, &ef, &gh); - s1 = sha512_neon_schedule_update(s1, s2, s5, s6, s0); - sha512_neon_round2(34, s1, &gh, &ab, &cd, &ef); - s2 = sha512_neon_schedule_update(s2, s3, s6, s7, s1); - sha512_neon_round2(36, s2, &ef, &gh, &ab, &cd); - s3 = sha512_neon_schedule_update(s3, s4, s7, s0, s2); - sha512_neon_round2(38, s3, &cd, &ef, &gh, &ab); - s4 = sha512_neon_schedule_update(s4, s5, s0, s1, s3); - sha512_neon_round2(40, s4, &ab, &cd, &ef, &gh); - s5 = sha512_neon_schedule_update(s5, s6, s1, s2, s4); - sha512_neon_round2(42, s5, &gh, &ab, &cd, &ef); - s6 = sha512_neon_schedule_update(s6, s7, s2, s3, s5); - sha512_neon_round2(44, s6, &ef, &gh, &ab, &cd); - s7 = sha512_neon_schedule_update(s7, s0, s3, s4, s6); - sha512_neon_round2(46, s7, &cd, &ef, &gh, &ab); - s0 = sha512_neon_schedule_update(s0, s1, s4, s5, s7); - sha512_neon_round2(48, s0, &ab, &cd, &ef, &gh); - s1 = sha512_neon_schedule_update(s1, s2, s5, s6, s0); - sha512_neon_round2(50, s1, &gh, &ab, &cd, &ef); - s2 = sha512_neon_schedule_update(s2, s3, s6, s7, s1); - sha512_neon_round2(52, s2, &ef, &gh, &ab, &cd); - s3 = sha512_neon_schedule_update(s3, s4, s7, s0, s2); - sha512_neon_round2(54, s3, &cd, &ef, &gh, &ab); - s4 = sha512_neon_schedule_update(s4, s5, s0, s1, s3); - sha512_neon_round2(56, s4, &ab, &cd, &ef, &gh); - s5 = sha512_neon_schedule_update(s5, s6, s1, s2, s4); - sha512_neon_round2(58, s5, &gh, &ab, &cd, &ef); - s6 = sha512_neon_schedule_update(s6, s7, s2, s3, s5); - sha512_neon_round2(60, s6, &ef, &gh, &ab, &cd); - s7 = sha512_neon_schedule_update(s7, s0, s3, s4, s6); - sha512_neon_round2(62, s7, &cd, &ef, &gh, &ab); - s0 = sha512_neon_schedule_update(s0, s1, s4, s5, s7); - sha512_neon_round2(64, s0, &ab, &cd, &ef, &gh); - s1 = sha512_neon_schedule_update(s1, s2, s5, s6, s0); - sha512_neon_round2(66, s1, &gh, &ab, &cd, &ef); - s2 = sha512_neon_schedule_update(s2, s3, s6, s7, s1); - sha512_neon_round2(68, s2, &ef, &gh, &ab, &cd); - s3 = sha512_neon_schedule_update(s3, s4, s7, s0, s2); - sha512_neon_round2(70, s3, &cd, &ef, &gh, &ab); - s4 = sha512_neon_schedule_update(s4, s5, s0, s1, s3); - sha512_neon_round2(72, s4, &ab, &cd, &ef, &gh); - s5 = sha512_neon_schedule_update(s5, s6, s1, s2, s4); - sha512_neon_round2(74, s5, &gh, &ab, &cd, &ef); - s6 = sha512_neon_schedule_update(s6, s7, s2, s3, s5); - sha512_neon_round2(76, s6, &ef, &gh, &ab, &cd); - s7 = sha512_neon_schedule_update(s7, s0, s3, s4, s6); - sha512_neon_round2(78, s7, &cd, &ef, &gh, &ab); - - core->ab = vaddq_u64(core->ab, ab); - core->cd = vaddq_u64(core->cd, cd); - core->ef = vaddq_u64(core->ef, ef); - core->gh = vaddq_u64(core->gh, gh); -} - -typedef struct sha512_neon { - sha512_neon_core core; - sha512_block blk; - BinarySink_IMPLEMENTATION; - ssh_hash hash; -} sha512_neon; - -static void sha512_neon_write(BinarySink *bs, const void *vp, size_t len); - -static ssh_hash *sha512_neon_new(const ssh_hashalg *alg) -{ - if (!sha512_hw_available_cached()) - return NULL; - - sha512_neon *s = snew(sha512_neon); - - s->hash.vt = alg; - BinarySink_INIT(s, sha512_neon_write); - BinarySink_DELEGATE_INIT(&s->hash, s); - return &s->hash; -} - -static void sha512_neon_reset(ssh_hash *hash) -{ - sha512_neon *s = container_of(hash, sha512_neon, hash); - const uint64_t *iv = (const uint64_t *)hash->vt->extra; - - s->core.ab = vld1q_u64(iv); - s->core.cd = vld1q_u64(iv+2); - s->core.ef = vld1q_u64(iv+4); - s->core.gh = vld1q_u64(iv+6); - - sha512_block_setup(&s->blk); -} - -static void sha512_neon_copyfrom(ssh_hash *hcopy, ssh_hash *horig) -{ - sha512_neon *copy = container_of(hcopy, sha512_neon, hash); - sha512_neon *orig = container_of(horig, sha512_neon, hash); - - *copy = *orig; /* structure copy */ - - BinarySink_COPIED(copy); - BinarySink_DELEGATE_INIT(©->hash, copy); -} - -static void sha512_neon_free(ssh_hash *hash) -{ - sha512_neon *s = container_of(hash, sha512_neon, hash); - smemclr(s, sizeof(*s)); - sfree(s); -} - -static void sha512_neon_write(BinarySink *bs, const void *vp, size_t len) -{ - sha512_neon *s = BinarySink_DOWNCAST(bs, sha512_neon); - - while (len > 0) - if (sha512_block_write(&s->blk, &vp, &len)) - sha512_neon_block(&s->core, s->blk.block); -} - -static void sha512_neon_digest(ssh_hash *hash, uint8_t *digest) -{ - sha512_neon *s = container_of(hash, sha512_neon, hash); - - sha512_block_pad(&s->blk, BinarySink_UPCAST(s)); - - vst1q_u8(digest, vrev64q_u8(vreinterpretq_u8_u64(s->core.ab))); - vst1q_u8(digest+16, vrev64q_u8(vreinterpretq_u8_u64(s->core.cd))); - vst1q_u8(digest+32, vrev64q_u8(vreinterpretq_u8_u64(s->core.ef))); - vst1q_u8(digest+48, vrev64q_u8(vreinterpretq_u8_u64(s->core.gh))); -} - -static void sha384_neon_digest(ssh_hash *hash, uint8_t *digest) -{ - sha512_neon *s = container_of(hash, sha512_neon, hash); - - sha512_block_pad(&s->blk, BinarySink_UPCAST(s)); - - vst1q_u8(digest, vrev64q_u8(vreinterpretq_u8_u64(s->core.ab))); - vst1q_u8(digest+16, vrev64q_u8(vreinterpretq_u8_u64(s->core.cd))); - vst1q_u8(digest+32, vrev64q_u8(vreinterpretq_u8_u64(s->core.ef))); -} - -const ssh_hashalg ssh_sha512_hw = { - .new = sha512_neon_new, - .reset = sha512_neon_reset, - .copyfrom = sha512_neon_copyfrom, - .digest = sha512_neon_digest, - .free = sha512_neon_free, - .hlen = 64, - .blocklen = 128, - HASHALG_NAMES_ANNOTATED("SHA-512", "NEON accelerated"), - .extra = sha512_initial_state, -}; - -const ssh_hashalg ssh_sha384_hw = { - .new = sha512_neon_new, - .reset = sha512_neon_reset, - .copyfrom = sha512_neon_copyfrom, - .digest = sha384_neon_digest, - .free = sha512_neon_free, - .hlen = 48, - .blocklen = 128, - HASHALG_NAMES_ANNOTATED("SHA-384", "NEON accelerated"), - .extra = sha384_initial_state, -}; - -/* ---------------------------------------------------------------------- - * Stub functions if we have no hardware-accelerated SHA-512. In this - * case, sha512_hw_new returns NULL (though it should also never be - * selected by sha512_select, so the only thing that should even be - * _able_ to call it is testcrypt). As a result, the remaining vtable - * functions should never be called at all. - */ - -#elif HW_SHA512 == HW_SHA512_NONE - -static bool sha512_hw_available(void) -{ - return false; -} - -static ssh_hash *sha512_stub_new(const ssh_hashalg *alg) -{ - return NULL; -} - -#define STUB_BODY { unreachable("Should never be called"); } - -static void sha512_stub_reset(ssh_hash *hash) STUB_BODY -static void sha512_stub_copyfrom(ssh_hash *hash, ssh_hash *orig) STUB_BODY -static void sha512_stub_free(ssh_hash *hash) STUB_BODY -static void sha512_stub_digest(ssh_hash *hash, uint8_t *digest) STUB_BODY - -const ssh_hashalg ssh_sha512_hw = { - .new = sha512_stub_new, - .reset = sha512_stub_reset, - .copyfrom = sha512_stub_copyfrom, - .digest = sha512_stub_digest, - .free = sha512_stub_free, - .hlen = 64, - .blocklen = 128, - HASHALG_NAMES_ANNOTATED("SHA-512", "!NONEXISTENT ACCELERATED VERSION!"), -}; - -const ssh_hashalg ssh_sha384_hw = { - .new = sha512_stub_new, - .reset = sha512_stub_reset, - .copyfrom = sha512_stub_copyfrom, - .digest = sha512_stub_digest, - .free = sha512_stub_free, - .hlen = 48, - .blocklen = 128, - HASHALG_NAMES_ANNOTATED("SHA-384", "!NONEXISTENT ACCELERATED VERSION!"), -}; - -#endif /* HW_SHA512 */ diff --git a/crypto/sha512.h b/crypto/sha512.h new file mode 100644 index 00000000..98145558 --- /dev/null +++ b/crypto/sha512.h @@ -0,0 +1,131 @@ +/* + * Definitions likely to be helpful to multiple SHA-512 implementations. + */ + +/* + * The 'extra' structure used by SHA-512 implementations is used to + * include information about how to check if a given implementation is + * available at run time, and whether we've already checked. + */ +struct sha512_extra_mutable; +struct sha512_extra { + /* Pointer to the initial state (distinguishes SHA-384 from -512) */ + const uint64_t *initial_state; + + /* Function to check availability. Might be expensive, so we don't + * want to call it more than once. */ + bool (*check_available)(void); + + /* Point to a writable substructure. */ + struct sha512_extra_mutable *mut; +}; +struct sha512_extra_mutable { + bool checked_availability; + bool is_available; +}; +static inline bool check_availability(const struct sha512_extra *extra) +{ + if (!extra->mut->checked_availability) { + extra->mut->is_available = extra->check_available(); + extra->mut->checked_availability = true; + } + + return extra->mut->is_available; +} + +/* + * Macro to define a pair of SHA-{384,512} vtables together with their + * 'extra' structure. + */ +#define SHA512_VTABLES(impl_c, impl_display) \ + static struct sha512_extra_mutable sha512_ ## impl_c ## _extra_mut; \ + static const struct sha512_extra sha384_ ## impl_c ## _extra = { \ + .initial_state = sha384_initial_state, \ + .check_available = sha512_ ## impl_c ## _available, \ + .mut = &sha512_ ## impl_c ## _extra_mut, \ + }; \ + static const struct sha512_extra sha512_ ## impl_c ## _extra = { \ + .initial_state = sha512_initial_state, \ + .check_available = sha512_ ## impl_c ## _available, \ + .mut = &sha512_ ## impl_c ## _extra_mut, \ + }; \ + const ssh_hashalg ssh_sha384_ ## impl_c = { \ + .new = sha512_ ## impl_c ## _new, \ + .reset = sha512_ ## impl_c ## _reset, \ + .copyfrom = sha512_ ## impl_c ## _copyfrom, \ + .digest = sha384_ ## impl_c ## _digest, \ + .free = sha512_ ## impl_c ## _free, \ + .hlen = 48, \ + .blocklen = 128, \ + HASHALG_NAMES_ANNOTATED("SHA-384", impl_display), \ + .extra = &sha384_ ## impl_c ## _extra, \ + }; \ + const ssh_hashalg ssh_sha512_ ## impl_c = { \ + .new = sha512_ ## impl_c ## _new, \ + .reset = sha512_ ## impl_c ## _reset, \ + .copyfrom = sha512_ ## impl_c ## _copyfrom, \ + .digest = sha512_ ## impl_c ## _digest, \ + .free = sha512_ ## impl_c ## _free, \ + .hlen = 64, \ + .blocklen = 128, \ + HASHALG_NAMES_ANNOTATED("SHA-512", impl_display), \ + .extra = &sha512_ ## impl_c ## _extra, \ + } + +extern const uint64_t sha512_initial_state[8]; +extern const uint64_t sha384_initial_state[8]; +extern const uint64_t sha512_round_constants[80]; + +#define SHA512_ROUNDS 80 + +typedef struct sha512_block sha512_block; +struct sha512_block { + uint8_t block[128]; + size_t used; + uint64_t lenhi, lenlo; +}; + +static inline void sha512_block_setup(sha512_block *blk) +{ + blk->used = 0; + blk->lenhi = blk->lenlo = 0; +} + +static inline bool sha512_block_write( + sha512_block *blk, const void **vdata, size_t *len) +{ + size_t blkleft = sizeof(blk->block) - blk->used; + size_t chunk = *len < blkleft ? *len : blkleft; + + const uint8_t *p = *vdata; + memcpy(blk->block + blk->used, p, chunk); + *vdata = p + chunk; + *len -= chunk; + blk->used += chunk; + + size_t chunkbits = chunk << 3; + + blk->lenlo += chunkbits; + blk->lenhi += (blk->lenlo < chunkbits); + + if (blk->used == sizeof(blk->block)) { + blk->used = 0; + return true; + } + + return false; +} + +static inline void sha512_block_pad(sha512_block *blk, BinarySink *bs) +{ + uint64_t final_lenhi = blk->lenhi; + uint64_t final_lenlo = blk->lenlo; + size_t pad = 127 & (111 - blk->used); + + put_byte(bs, 0x80); + put_padding(bs, pad, 0); + put_uint64(bs, final_lenhi); + put_uint64(bs, final_lenlo); + + assert(blk->used == 0 && "Should have exactly hit a block boundary"); +} diff --git a/ssh.h b/ssh.h index 4162fc1e..24d7f1a5 100644 --- a/ssh.h +++ b/ssh.h @@ -953,22 +953,28 @@ extern const ssh_cipheralg ssh_3des_ssh2; extern const ssh_cipheralg ssh_des; extern const ssh_cipheralg ssh_des_sshcom_ssh2; extern const ssh_cipheralg ssh_aes256_sdctr; -extern const ssh_cipheralg ssh_aes256_sdctr_hw; +extern const ssh_cipheralg ssh_aes256_sdctr_ni; +extern const ssh_cipheralg ssh_aes256_sdctr_neon; extern const ssh_cipheralg ssh_aes256_sdctr_sw; extern const ssh_cipheralg ssh_aes256_cbc; -extern const ssh_cipheralg ssh_aes256_cbc_hw; +extern const ssh_cipheralg ssh_aes256_cbc_ni; +extern const ssh_cipheralg ssh_aes256_cbc_neon; extern const ssh_cipheralg ssh_aes256_cbc_sw; extern const ssh_cipheralg ssh_aes192_sdctr; -extern const ssh_cipheralg ssh_aes192_sdctr_hw; +extern const ssh_cipheralg ssh_aes192_sdctr_ni; +extern const ssh_cipheralg ssh_aes192_sdctr_neon; extern const ssh_cipheralg ssh_aes192_sdctr_sw; extern const ssh_cipheralg ssh_aes192_cbc; -extern const ssh_cipheralg ssh_aes192_cbc_hw; +extern const ssh_cipheralg ssh_aes192_cbc_ni; +extern const ssh_cipheralg ssh_aes192_cbc_neon; extern const ssh_cipheralg ssh_aes192_cbc_sw; extern const ssh_cipheralg ssh_aes128_sdctr; -extern const ssh_cipheralg ssh_aes128_sdctr_hw; +extern const ssh_cipheralg ssh_aes128_sdctr_ni; +extern const ssh_cipheralg ssh_aes128_sdctr_neon; extern const ssh_cipheralg ssh_aes128_sdctr_sw; extern const ssh_cipheralg ssh_aes128_cbc; -extern const ssh_cipheralg ssh_aes128_cbc_hw; +extern const ssh_cipheralg ssh_aes128_cbc_ni; +extern const ssh_cipheralg ssh_aes128_cbc_neon; extern const ssh_cipheralg ssh_aes128_cbc_sw; extern const ssh_cipheralg ssh_blowfish_ssh2_ctr; extern const ssh_cipheralg ssh_blowfish_ssh2; @@ -983,16 +989,18 @@ extern const ssh2_ciphers ssh2_arcfour; extern const ssh2_ciphers ssh2_ccp; extern const ssh_hashalg ssh_md5; extern const ssh_hashalg ssh_sha1; -extern const ssh_hashalg ssh_sha1_hw; +extern const ssh_hashalg ssh_sha1_ni; +extern const ssh_hashalg ssh_sha1_neon; extern const ssh_hashalg ssh_sha1_sw; extern const ssh_hashalg ssh_sha256; -extern const ssh_hashalg ssh_sha256_hw; +extern const ssh_hashalg ssh_sha256_ni; +extern const ssh_hashalg ssh_sha256_neon; extern const ssh_hashalg ssh_sha256_sw; extern const ssh_hashalg ssh_sha384; -extern const ssh_hashalg ssh_sha384_hw; +extern const ssh_hashalg ssh_sha384_neon; extern const ssh_hashalg ssh_sha384_sw; extern const ssh_hashalg ssh_sha512; -extern const ssh_hashalg ssh_sha512_hw; +extern const ssh_hashalg ssh_sha512_neon; extern const ssh_hashalg ssh_sha512_sw; extern const ssh_hashalg ssh_sha3_224; extern const ssh_hashalg ssh_sha3_256; @@ -1039,10 +1047,10 @@ ssh_hash *blake2b_new_general(unsigned hashlen); * itself. If so, then this function should be implemented in each * platform subdirectory. */ -bool platform_aes_hw_available(void); -bool platform_sha256_hw_available(void); -bool platform_sha1_hw_available(void); -bool platform_sha512_hw_available(void); +bool platform_aes_neon_available(void); +bool platform_sha256_neon_available(void); +bool platform_sha1_neon_available(void); +bool platform_sha512_neon_available(void); /* * PuTTY version number formatted as an SSH version string. diff --git a/test/cryptsuite.py b/test/cryptsuite.py index 757de673..9ed0c3f5 100755 --- a/test/cryptsuite.py +++ b/test/cryptsuite.py @@ -141,6 +141,14 @@ def mac_str(alg, key, message, cipher=None): def lcm(a, b): return a * b // gcd(a, b) +def get_implementations(alg): + return get_implementations_commasep(alg).decode("ASCII").split(",") + +def get_aes_impls(): + return [impl.rsplit("_", 1)[-1] + for impl in get_implementations("aes128_cbc") + if impl.startswith("aes128_cbc_")] + class MyTestBase(unittest.TestCase): "Intermediate class that adds useful helper methods." def assertEqualBin(self, x, y): @@ -1181,9 +1189,9 @@ class crypt(MyTestBase): # reference implementation of AES in Python. ('Mostly' # independent in that it was written by me.) - def vector(cipher, key, iv, plaintext, ciphertext): - for suffix in "hw", "sw": - c = ssh_cipher_new("{}_{}".format(cipher, suffix)) + def vector(cipherbase, key, iv, plaintext, ciphertext): + for cipher in get_implementations(cipherbase): + c = ssh_cipher_new(cipher) if c is None: return # skip test if HW AES not available ssh_cipher_setkey(c, key) ssh_cipher_setiv(c, iv) @@ -1302,7 +1310,7 @@ class crypt(MyTestBase): # We also test this at all three AES key lengths, in case the # core cipher routines are written separately for each one. - for suffix in "hw", "sw": + for suffix in get_aes_impls(): for keylen in [128, 192, 256]: hexTestValues = ["00000000", "00000001", "ffffffff"] for ivHexBytes in itertools.product(*([hexTestValues] * 4)): @@ -1325,7 +1333,7 @@ class crypt(MyTestBase): for keylen in [128, 192, 256]: decryptions = [] - for suffix in "hw", "sw": + for suffix in get_aes_impls(): c = ssh_cipher_new("aes{:d}_cbc_{}".format(keylen, suffix)) if c is None: continue ssh_cipher_setkey(c, test_key[:keylen//8]) @@ -1493,23 +1501,11 @@ class crypt(MyTestBase): ("3des_ssh1", 24, 8, False, unhex('d5f1cc25b8fbc62de63590b9b92344adf6dd72753273ff0fb32d4dbc6af858529129f34242f3d557eed3a5c84204eb4f868474294964cf70df5d8f45dfccfc45')), ("des_cbc", 8, 8, True, unhex('051524e77fb40e109d9fffeceacf0f28c940e2f8415ddccc117020bdd2612af5036490b12085d0e46129919b8e499f51cb82a4b341d7a1a1ea3e65201ef248f6')), ("aes256_ctr", 32, 16, False, unhex('b87b35e819f60f0f398a37b05d7bcf0b04ad4ebe570bd08e8bfa8606bafb0db2cfcd82baf2ccceae5de1a3c1ae08a8b8fdd884fdc5092031ea8ce53333e62976')), - ("aes256_ctr_hw", 32, 16, False, unhex('b87b35e819f60f0f398a37b05d7bcf0b04ad4ebe570bd08e8bfa8606bafb0db2cfcd82baf2ccceae5de1a3c1ae08a8b8fdd884fdc5092031ea8ce53333e62976')), - ("aes256_ctr_sw", 32, 16, False, unhex('b87b35e819f60f0f398a37b05d7bcf0b04ad4ebe570bd08e8bfa8606bafb0db2cfcd82baf2ccceae5de1a3c1ae08a8b8fdd884fdc5092031ea8ce53333e62976')), ("aes256_cbc", 32, 16, True, unhex('381cbb2fbcc48118d0094540242bd990dd6af5b9a9890edd013d5cad2d904f34b9261c623a452f32ea60e5402919a77165df12862742f1059f8c4a862f0827c5')), - ("aes256_cbc_hw", 32, 16, True, unhex('381cbb2fbcc48118d0094540242bd990dd6af5b9a9890edd013d5cad2d904f34b9261c623a452f32ea60e5402919a77165df12862742f1059f8c4a862f0827c5')), - ("aes256_cbc_sw", 32, 16, True, unhex('381cbb2fbcc48118d0094540242bd990dd6af5b9a9890edd013d5cad2d904f34b9261c623a452f32ea60e5402919a77165df12862742f1059f8c4a862f0827c5')), ("aes192_ctr", 24, 16, False, unhex('06bcfa7ccf075d723e12b724695a571a0fad67c56287ea609c410ac12749c51bb96e27fa7e1c7ea3b14792bbbb8856efb0617ebec24a8e4a87340d820cf347b8')), - ("aes192_ctr_hw", 24, 16, False, unhex('06bcfa7ccf075d723e12b724695a571a0fad67c56287ea609c410ac12749c51bb96e27fa7e1c7ea3b14792bbbb8856efb0617ebec24a8e4a87340d820cf347b8')), - ("aes192_ctr_sw", 24, 16, False, unhex('06bcfa7ccf075d723e12b724695a571a0fad67c56287ea609c410ac12749c51bb96e27fa7e1c7ea3b14792bbbb8856efb0617ebec24a8e4a87340d820cf347b8')), ("aes192_cbc", 24, 16, True, unhex('ac97f8698170f9c05341214bd7624d5d2efef8311596163dc597d9fe6c868971bd7557389974612cbf49ea4e7cc6cc302d4cc90519478dd88a4f09b530c141f3')), - ("aes192_cbc_hw", 24, 16, True, unhex('ac97f8698170f9c05341214bd7624d5d2efef8311596163dc597d9fe6c868971bd7557389974612cbf49ea4e7cc6cc302d4cc90519478dd88a4f09b530c141f3')), - ("aes192_cbc_sw", 24, 16, True, unhex('ac97f8698170f9c05341214bd7624d5d2efef8311596163dc597d9fe6c868971bd7557389974612cbf49ea4e7cc6cc302d4cc90519478dd88a4f09b530c141f3')), ("aes128_ctr", 16, 16, False, unhex('0ad4ddfd2360ec59d77dcb9a981f92109437c68c5e7f02f92017d9f424f89ab7850473ac0e19274125e740f252c84ad1f6ad138b6020a03bdaba2f3a7378ce1e')), - ("aes128_ctr_hw", 16, 16, False, unhex('0ad4ddfd2360ec59d77dcb9a981f92109437c68c5e7f02f92017d9f424f89ab7850473ac0e19274125e740f252c84ad1f6ad138b6020a03bdaba2f3a7378ce1e')), - ("aes128_ctr_sw", 16, 16, False, unhex('0ad4ddfd2360ec59d77dcb9a981f92109437c68c5e7f02f92017d9f424f89ab7850473ac0e19274125e740f252c84ad1f6ad138b6020a03bdaba2f3a7378ce1e')), ("aes128_cbc", 16, 16, True, unhex('36de36917fb7955a711c8b0bf149b29120a77524f393ae3490f4ce5b1d5ca2a0d7064ce3c38e267807438d12c0e40cd0d84134647f9f4a5b11804a0cc5070e62')), - ("aes128_cbc_hw", 16, 16, True, unhex('36de36917fb7955a711c8b0bf149b29120a77524f393ae3490f4ce5b1d5ca2a0d7064ce3c38e267807438d12c0e40cd0d84134647f9f4a5b11804a0cc5070e62')), - ("aes128_cbc_sw", 16, 16, True, unhex('36de36917fb7955a711c8b0bf149b29120a77524f393ae3490f4ce5b1d5ca2a0d7064ce3c38e267807438d12c0e40cd0d84134647f9f4a5b11804a0cc5070e62')), ("blowfish_ctr", 32, 8, False, unhex('079daf0f859363ccf72e975764d709232ec48adc74f88ccd1f342683f0bfa89ca0e8dbfccc8d4d99005d6b61e9cc4e6eaa2fd2a8163271b94bf08ef212129f01')), ("blowfish_ssh2", 16, 8, True, unhex('e986b7b01f17dfe80ee34cac81fa029b771ec0f859ae21ae3ec3df1674bc4ceb54a184c6c56c17dd2863c3e9c068e76fd9aef5673465995f0d648b0bb848017f')), ("blowfish_ssh1", 32, 8, True, unhex('d44092a9035d895acf564ba0365d19570fbb4f125d5a4fd2a1812ee6c8a1911a51bb181fbf7d1a261253cab71ee19346eb477b3e7ecf1d95dd941e635c1a4fbf')), @@ -1517,36 +1513,37 @@ class crypt(MyTestBase): ("arcfour128", 16, None, False, unhex('fd4af54c5642cb29629e50a15d22e4944e21ffba77d0543b27590eafffe3886686d1aefae0484afc9e67edc0e67eb176bbb5340af1919ea39adfe866d066dd05')), ] - for alg, keylen, ivlen, simple_cbc, c in ciphers: - cipher = ssh_cipher_new(alg) - if cipher is None: - continue # hardware-accelerated cipher not available - - ssh_cipher_setkey(cipher, k[:keylen]) - if ivlen is not None: - ssh_cipher_setiv(cipher, iv[:ivlen]) - self.assertEqualBin(ssh_cipher_encrypt(cipher, p), c) - - ssh_cipher_setkey(cipher, k[:keylen]) - if ivlen is not None: - ssh_cipher_setiv(cipher, iv[:ivlen]) - self.assertEqualBin(ssh_cipher_decrypt(cipher, c), p) - - if simple_cbc: - # CBC ciphers (other than the three-layered CBC used - # by SSH-1 3DES) have more specific semantics for - # their IV than 'some kind of starting state for the - # cipher mode': the IV is specifically supposed to - # represent the previous block of ciphertext. So we - # can check that, by supplying the IV _as_ a - # ciphertext block via a call to decrypt(), and seeing - # if that causes our test ciphertext to decrypt the - # same way as when we provided the same IV via - # setiv(). + for algbase, keylen, ivlen, simple_cbc, c in ciphers: + for alg in get_implementations(algbase): + cipher = ssh_cipher_new(alg) + if cipher is None: + continue # hardware-accelerated cipher not available + ssh_cipher_setkey(cipher, k[:keylen]) - ssh_cipher_decrypt(cipher, iv[:ivlen]) + if ivlen is not None: + ssh_cipher_setiv(cipher, iv[:ivlen]) + self.assertEqualBin(ssh_cipher_encrypt(cipher, p), c) + + ssh_cipher_setkey(cipher, k[:keylen]) + if ivlen is not None: + ssh_cipher_setiv(cipher, iv[:ivlen]) self.assertEqualBin(ssh_cipher_decrypt(cipher, c), p) + if simple_cbc: + # CBC ciphers (other than the three-layered CBC used + # by SSH-1 3DES) have more specific semantics for + # their IV than 'some kind of starting state for the + # cipher mode': the IV is specifically supposed to + # represent the previous block of ciphertext. So we + # can check that, by supplying the IV _as_ a + # ciphertext block via a call to decrypt(), and seeing + # if that causes our test ciphertext to decrypt the + # same way as when we provided the same IV via + # setiv(). + ssh_cipher_setkey(cipher, k[:keylen]) + ssh_cipher_decrypt(cipher, iv[:ivlen]) + self.assertEqualBin(ssh_cipher_decrypt(cipher, c), p) + def testRSAKex(self): # Round-trip test of the RSA key exchange functions, plus a # hardcoded plain/ciphertext pair to guard against the @@ -2324,7 +2321,7 @@ Private-MAC: 5b1f6f4cc43eb0060d2c3e181bc0129343adba2b class standard_test_vectors(MyTestBase): def testAES(self): def vector(cipher, key, plaintext, ciphertext): - for suffix in "hw", "sw": + for suffix in get_aes_impls(): c = ssh_cipher_new("{}_{}".format(cipher, suffix)) if c is None: return # skip test if HW AES not available ssh_cipher_setkey(c, key) @@ -2540,7 +2537,7 @@ class standard_test_vectors(MyTestBase): unhex('56be34521d144c88dbb8c733f0e8b3f6')) def testSHA1(self): - for hashname in ['sha1_sw', 'sha1_hw']: + for hashname in get_implementations("sha1"): if ssh_hash_new(hashname) is None: continue # skip testing of unavailable HW implementation @@ -2577,7 +2574,7 @@ class standard_test_vectors(MyTestBase): "cb0082c8f197d260991ba6a460e76e202bad27b3")) def testSHA256(self): - for hashname in ['sha256_sw', 'sha256_hw']: + for hashname in get_implementations("sha256"): if ssh_hash_new(hashname) is None: continue # skip testing of unavailable HW implementation @@ -2621,7 +2618,7 @@ class standard_test_vectors(MyTestBase): "8ad3361763f7e9b2d95f4f0da6e1ccbc")) def testSHA384(self): - for hashname in ['sha384_sw', 'sha384_hw']: + for hashname in get_implementations("sha384"): if ssh_hash_new(hashname) is None: continue # skip testing of unavailable HW implementation @@ -2663,7 +2660,7 @@ class standard_test_vectors(MyTestBase): '38e42b5c4de660f5de8fb2a5b2fbd2a3cbffd20cff1288c0')) def testSHA512(self): - for hashname in ['sha512_sw', 'sha512_hw']: + for hashname in get_implementations("sha512"): if ssh_hash_new(hashname) is None: continue # skip testing of unavailable HW implementation diff --git a/testcrypt.c b/testcrypt.c index 752947cf..1948da08 100644 --- a/testcrypt.c +++ b/testcrypt.c @@ -207,16 +207,24 @@ static const ssh_hashalg *get_hashalg(BinarySource *in) {"md5", &ssh_md5}, {"sha1", &ssh_sha1}, {"sha1_sw", &ssh_sha1_sw}, - {"sha1_hw", &ssh_sha1_hw}, {"sha256", &ssh_sha256}, - {"sha256_sw", &ssh_sha256_sw}, - {"sha256_hw", &ssh_sha256_hw}, {"sha384", &ssh_sha384}, - {"sha384_sw", &ssh_sha384_sw}, - {"sha384_hw", &ssh_sha384_hw}, {"sha512", &ssh_sha512}, + {"sha256_sw", &ssh_sha256_sw}, + {"sha384_sw", &ssh_sha384_sw}, {"sha512_sw", &ssh_sha512_sw}, - {"sha512_hw", &ssh_sha512_hw}, +#if HAVE_SHA_NI + {"sha1_ni", &ssh_sha1_ni}, + {"sha256_ni", &ssh_sha256_ni}, +#endif +#if HAVE_NEON_CRYPTO + {"sha1_neon", &ssh_sha1_neon}, + {"sha256_neon", &ssh_sha256_neon}, +#endif +#if HAVE_NEON_SHA512 + {"sha384_neon", &ssh_sha384_neon}, + {"sha512_neon", &ssh_sha512_neon}, +#endif {"sha3_224", &ssh_sha3_224}, {"sha3_256", &ssh_sha3_256}, {"sha3_384", &ssh_sha3_384}, @@ -290,23 +298,33 @@ static const ssh_cipheralg *get_cipheralg(BinarySource *in) {"3des_ssh1", &ssh_3des_ssh1}, {"des_cbc", &ssh_des}, {"aes256_ctr", &ssh_aes256_sdctr}, - {"aes256_ctr_hw", &ssh_aes256_sdctr_hw}, - {"aes256_ctr_sw", &ssh_aes256_sdctr_sw}, {"aes256_cbc", &ssh_aes256_cbc}, - {"aes256_cbc_hw", &ssh_aes256_cbc_hw}, - {"aes256_cbc_sw", &ssh_aes256_cbc_sw}, {"aes192_ctr", &ssh_aes192_sdctr}, - {"aes192_ctr_hw", &ssh_aes192_sdctr_hw}, - {"aes192_ctr_sw", &ssh_aes192_sdctr_sw}, {"aes192_cbc", &ssh_aes192_cbc}, - {"aes192_cbc_hw", &ssh_aes192_cbc_hw}, - {"aes192_cbc_sw", &ssh_aes192_cbc_sw}, {"aes128_ctr", &ssh_aes128_sdctr}, - {"aes128_ctr_hw", &ssh_aes128_sdctr_hw}, - {"aes128_ctr_sw", &ssh_aes128_sdctr_sw}, {"aes128_cbc", &ssh_aes128_cbc}, - {"aes128_cbc_hw", &ssh_aes128_cbc_hw}, + {"aes256_ctr_sw", &ssh_aes256_sdctr_sw}, + {"aes256_cbc_sw", &ssh_aes256_cbc_sw}, + {"aes192_ctr_sw", &ssh_aes192_sdctr_sw}, + {"aes192_cbc_sw", &ssh_aes192_cbc_sw}, + {"aes128_ctr_sw", &ssh_aes128_sdctr_sw}, {"aes128_cbc_sw", &ssh_aes128_cbc_sw}, +#if HAVE_AES_NI + {"aes256_ctr_ni", &ssh_aes256_sdctr_ni}, + {"aes256_cbc_ni", &ssh_aes256_cbc_ni}, + {"aes192_ctr_ni", &ssh_aes192_sdctr_ni}, + {"aes192_cbc_ni", &ssh_aes192_cbc_ni}, + {"aes128_ctr_ni", &ssh_aes128_sdctr_ni}, + {"aes128_cbc_ni", &ssh_aes128_cbc_ni}, +#endif +#if HAVE_NEON_CRYPTO + {"aes256_ctr_neon", &ssh_aes256_sdctr_neon}, + {"aes256_cbc_neon", &ssh_aes256_cbc_neon}, + {"aes192_ctr_neon", &ssh_aes192_sdctr_neon}, + {"aes192_cbc_neon", &ssh_aes192_cbc_neon}, + {"aes128_ctr_neon", &ssh_aes128_sdctr_neon}, + {"aes128_cbc_neon", &ssh_aes128_cbc_neon}, +#endif {"blowfish_ctr", &ssh_blowfish_ssh2_ctr}, {"blowfish_ssh2", &ssh_blowfish_ssh2}, {"blowfish_ssh1", &ssh_blowfish_ssh1}, @@ -1285,6 +1303,38 @@ strbuf *argon2_wrapper(Argon2Flavour flavour, uint32_t mem, uint32_t passes, } #define argon2 argon2_wrapper +strbuf *get_implementations_commasep(ptrlen alg) +{ + strbuf *out = strbuf_new(); + put_datapl(out, alg); + + if (ptrlen_startswith(alg, PTRLEN_LITERAL("aes"), NULL)) { + strbuf_catf(out, ",%.*s_sw", PTRLEN_PRINTF(alg)); +#if HAVE_AES_NI + strbuf_catf(out, ",%.*s_ni", PTRLEN_PRINTF(alg)); +#endif +#if HAVE_NEON_CRYPTO + strbuf_catf(out, ",%.*s_neon", PTRLEN_PRINTF(alg)); +#endif + } else if (ptrlen_startswith(alg, PTRLEN_LITERAL("sha256"), NULL) || + ptrlen_startswith(alg, PTRLEN_LITERAL("sha1"), NULL)) { + strbuf_catf(out, ",%.*s_sw", PTRLEN_PRINTF(alg)); +#if HAVE_SHA_NI + strbuf_catf(out, ",%.*s_ni", PTRLEN_PRINTF(alg)); +#endif +#if HAVE_NEON_CRYPTO + strbuf_catf(out, ",%.*s_neon", PTRLEN_PRINTF(alg)); +#endif + } else if (ptrlen_startswith(alg, PTRLEN_LITERAL("sha512"), NULL)) { + strbuf_catf(out, ",%.*s_sw", PTRLEN_PRINTF(alg)); +#if HAVE_NEON_SHA512 + strbuf_catf(out, ",%.*s_neon", PTRLEN_PRINTF(alg)); +#endif + } + + return out; +} + #define OPTIONAL_PTR_FUNC(type) \ typedef TD_val_##type TD_opt_val_##type; \ static TD_opt_val_##type get_opt_val_##type(BinarySource *in) { \ diff --git a/testcrypt.h b/testcrypt.h index 298abc0f..2e6e993b 100644 --- a/testcrypt.h +++ b/testcrypt.h @@ -315,6 +315,7 @@ FUNC1(uint, crc32_rfc1662, val_string_ptrlen) FUNC1(uint, crc32_ssh1, val_string_ptrlen) FUNC2(uint, crc32_update, uint, val_string_ptrlen) FUNC2(boolean, crcda_detect, val_string_ptrlen, val_string_ptrlen) +FUNC1(val_string, get_implementations_commasep, val_string_ptrlen) /* * These functions aren't part of PuTTY's own API, but are additions diff --git a/testsc.c b/testsc.c index 93bf263a..b182d382 100644 --- a/testsc.c +++ b/testsc.c @@ -216,6 +216,31 @@ VOLATILE_WRAPPED_DEFN(static, size_t, looplimit, (size_t x)) return x; } +#if HAVE_AES_NI +#define CIPHERS_AES_NI(X, Y) \ + X(Y, ssh_aes256_sdctr_ni) \ + X(Y, ssh_aes256_cbc_ni) \ + X(Y, ssh_aes192_sdctr_ni) \ + X(Y, ssh_aes192_cbc_ni) \ + X(Y, ssh_aes128_sdctr_ni) \ + X(Y, ssh_aes128_cbc_ni) \ + /* end of list */ +#else +#define CIPHERS_AES_NI(X, Y) +#endif +#if HAVE_NEON_CRYPTO +#define CIPHERS_AES_NEON(X, Y) \ + X(Y, ssh_aes256_sdctr_neon) \ + X(Y, ssh_aes256_cbc_neon) \ + X(Y, ssh_aes192_sdctr_neon) \ + X(Y, ssh_aes192_cbc_neon) \ + X(Y, ssh_aes128_sdctr_neon) \ + X(Y, ssh_aes128_cbc_neon) \ + /* end of list */ +#else +#define CIPHERS_AES_NEON(X, Y) +#endif + /* Ciphers that we expect to pass this test. Blowfish and Arcfour are * intentionally omitted, because we already know they don't. */ #define CIPHERS(X, Y) \ @@ -225,23 +250,19 @@ VOLATILE_WRAPPED_DEFN(static, size_t, looplimit, (size_t x)) X(Y, ssh_des) \ X(Y, ssh_des_sshcom_ssh2) \ X(Y, ssh_aes256_sdctr) \ - X(Y, ssh_aes256_sdctr_hw) \ - X(Y, ssh_aes256_sdctr_sw) \ X(Y, ssh_aes256_cbc) \ - X(Y, ssh_aes256_cbc_hw) \ - X(Y, ssh_aes256_cbc_sw) \ X(Y, ssh_aes192_sdctr) \ - X(Y, ssh_aes192_sdctr_hw) \ - X(Y, ssh_aes192_sdctr_sw) \ X(Y, ssh_aes192_cbc) \ - X(Y, ssh_aes192_cbc_hw) \ - X(Y, ssh_aes192_cbc_sw) \ X(Y, ssh_aes128_sdctr) \ - X(Y, ssh_aes128_sdctr_hw) \ - X(Y, ssh_aes128_sdctr_sw) \ X(Y, ssh_aes128_cbc) \ - X(Y, ssh_aes128_cbc_hw) \ + X(Y, ssh_aes256_sdctr_sw) \ + X(Y, ssh_aes256_cbc_sw) \ + X(Y, ssh_aes192_sdctr_sw) \ + X(Y, ssh_aes192_cbc_sw) \ + X(Y, ssh_aes128_sdctr_sw) \ X(Y, ssh_aes128_cbc_sw) \ + CIPHERS_AES_NI(X, Y) \ + CIPHERS_AES_NEON(X, Y) \ X(Y, ssh2_chacha20_poly1305) \ /* end of list */ @@ -258,16 +279,35 @@ VOLATILE_WRAPPED_DEFN(static, size_t, looplimit, (size_t x)) #define MAC_TESTLIST(X, name) X(mac_ ## name) +#if HAVE_SHA_NI +#define HASH_SHA_NI(X, Y) X(Y, ssh_sha256_ni) X(Y, ssh_sha1_ni) +#else +#define HASH_SHA_NI(X, Y) +#endif +#if HAVE_NEON_CRYPTO +#define HASH_SHA_NEON(X, Y) X(Y, ssh_sha256_neon) X(Y, ssh_sha1_neon) +#else +#define HASH_SHA_NEON(X, Y) +#endif +#if HAVE_NEON_SHA512 +#define HASH_SHA512_NEON(X, Y) X(Y, ssh_sha384_neon) X(Y, ssh_sha512_neon) +#else +#define HASH_SHA512_NEON(X, Y) +#endif + #define HASHES(X, Y) \ X(Y, ssh_md5) \ X(Y, ssh_sha1) \ - X(Y, ssh_sha1_hw) \ X(Y, ssh_sha1_sw) \ X(Y, ssh_sha256) \ - X(Y, ssh_sha256_hw) \ X(Y, ssh_sha256_sw) \ X(Y, ssh_sha384) \ X(Y, ssh_sha512) \ + X(Y, ssh_sha384_sw) \ + X(Y, ssh_sha512_sw) \ + HASH_SHA_NI(X, Y) \ + HASH_SHA_NEON(X, Y) \ + HASH_SHA512_NEON(X, Y) \ X(Y, ssh_sha3_224) \ X(Y, ssh_sha3_256) \ X(Y, ssh_sha3_384) \ diff --git a/unix/utils/arm_arch_queries.c b/unix/utils/arm_arch_queries.c index 7c0957fa..cc3e4125 100644 --- a/unix/utils/arm_arch_queries.c +++ b/unix/utils/arm_arch_queries.c @@ -10,7 +10,7 @@ #if defined __arm__ || defined __aarch64__ -bool platform_aes_hw_available(void) +bool platform_aes_neon_available(void) { #if defined HWCAP_AES return getauxval(AT_HWCAP) & HWCAP_AES; @@ -26,7 +26,7 @@ bool platform_aes_hw_available(void) #endif } -bool platform_sha256_hw_available(void) +bool platform_sha256_neon_available(void) { #if defined HWCAP_SHA2 return getauxval(AT_HWCAP) & HWCAP_SHA2; @@ -40,7 +40,7 @@ bool platform_sha256_hw_available(void) #endif } -bool platform_sha1_hw_available(void) +bool platform_sha1_neon_available(void) { #if defined HWCAP_SHA1 return getauxval(AT_HWCAP) & HWCAP_SHA1; @@ -54,7 +54,7 @@ bool platform_sha1_hw_available(void) #endif } -bool platform_sha512_hw_available(void) +bool platform_sha512_neon_available(void) { #if defined HWCAP_SHA512 return getauxval(AT_HWCAP) & HWCAP_SHA512; diff --git a/windows/utils/arm_arch_queries.c b/windows/utils/arm_arch_queries.c index 05132b14..439a59fb 100644 --- a/windows/utils/arm_arch_queries.c +++ b/windows/utils/arm_arch_queries.c @@ -15,22 +15,22 @@ #define IsProcessorFeaturePresent(...) false #endif -bool platform_aes_hw_available(void) +bool platform_aes_neon_available(void) { return IsProcessorFeaturePresent(PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE); } -bool platform_sha256_hw_available(void) +bool platform_sha256_neon_available(void) { return IsProcessorFeaturePresent(PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE); } -bool platform_sha1_hw_available(void) +bool platform_sha1_neon_available(void) { return IsProcessorFeaturePresent(PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE); } -bool platform_sha512_hw_available(void) +bool platform_sha512_neon_available(void) { /* As of 2020-12-24, as far as I can tell from docs.microsoft.com, * Windows on Arm does not yet provide a PF_ARM_V8_* flag for the -- cgit v1.2.3 From e06cf1ec40c30cded8d277e43fd4f1f3079a0e5f Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Wed, 21 Apr 2021 21:25:32 +0100 Subject: Remove the 'compile-once' design principle. It's no longer a hard requirement, because now we're on cmake rather than mkfiles.pl, we _can_ compile the same source file multiple times with different ifdefs. I still think it's a better idea not to: I'd prefer that most of this code base remained in the form of libraries reused between applications, with parametrisation done by choice of what other objects to link them to rather than by recompiling the library modules themselves with different settings. But the latter is now a possibility at need. --- doc/udp.but | 40 ---------------------------------------- 1 file changed, 40 deletions(-) diff --git a/doc/udp.but b/doc/udp.but index bc23e226..22a8ceec 100644 --- a/doc/udp.but +++ b/doc/udp.but @@ -752,46 +752,6 @@ other two full implementation vtables. } -\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} -- cgit v1.2.3 From 970f374ea689c33321760ba62cfcad401d32521d Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 22 Apr 2021 12:42:32 +0100 Subject: Fix the SHA-NI cmake-time check. When preparing commit fca13a17b160da3, I redesigned the cmake test function at the last minute, and apparently didn't quite get all the call sites correctly rewritten. This one still omitted some of the argument-type keywords, and had an obsolete parameter giving an explicit name for a sub-library, which I later decided wasn't needed. --- crypto/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crypto/CMakeLists.txt b/crypto/CMakeLists.txt index 917614be..7d11f444 100644 --- a/crypto/CMakeLists.txt +++ b/crypto/CMakeLists.txt @@ -117,8 +117,8 @@ if(HAVE_WMMINTRIN_H) #include ${include_shaintrin} volatile __m128i r, a, b, c; - int main(void) { r = _mm_sha256rnds2_epu32(a, b, c); } - " sha-ni sha256-ni.c sha1-ni.c) + int main(void) { r = _mm_sha256rnds2_epu32(a, b, c); }" + ADD_SOURCES_IF_SUCCESSFUL sha256-ni.c sha1-ni.c) endif() # ---------------------------------------------------------------------- -- cgit v1.2.3 From b1d2f96823e92128a70f425a467b0b4d2655caa7 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 22 Apr 2021 17:43:38 +0100 Subject: sesschan.c: use dupprintf in place of snprintf. I hadn't actually realised until now that the SSH server code is now being compiled on Windows! It happened because I've been using static libraries internally to the build organisation: of course, CMake has no way of knowing that those libraries are only needed _within_ the build, and for all it knows they might be end products shipped to users to link their own applications with. So all the objects in the 'sshserver' library will now be compiled, even on Windows, where no applications actually link with it. And in that context, the use of snprintf caused a compiler warning from the w32old build, because there, snprintf doesn't exist in the older version of the C library. Of course, it's currently benign, because no application in the w32old build (or any other Windows build) is actually linking again the sshserver library. But I don't want to rule it out in future, or at least not for a trivial reason like this. So I've fixed the warning in the simplest way, by switching to our own dupprintf, which is available everywhere. --- sesschan.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sesschan.c b/sesschan.c index 0cf85b3b..4ab709da 100644 --- a/sesschan.c +++ b/sesschan.c @@ -400,13 +400,10 @@ bool sesschan_enable_x11_forwarding( sesschan *sess = container_of(chan, sesschan, chan); strbuf *authdata_bin; size_t i; - char screensuffix[32]; if (oneshot) return false; /* not supported */ - snprintf(screensuffix, sizeof(screensuffix), ".%u", screen_number); - /* * Decode the authorisation data from ASCII hex into binary. */ @@ -430,11 +427,14 @@ bool sesschan_enable_x11_forwarding( sess->xfwd_plug.vt = &xfwd_plugvt; + char *screensuffix = dupprintf(".%u", screen_number); + sess->n_x11_sockets = platform_make_x11_server( &sess->xfwd_plug, appname, 10, screensuffix, authproto, ptrlen_from_strbuf(authdata_bin), sess->x11_sockets, sess->conf); + sfree(screensuffix); strbuf_free(authdata_bin); return sess->n_x11_sockets != 0; } -- cgit v1.2.3 From 66e62915d2565013458b83218e489a1f2aa6ab82 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 22 Apr 2021 17:57:56 +0100 Subject: Move a few stray header files into the crypto subdir. sshblowf.h (as was) is 100% internal to that directory. And mpint_i.h and ecc.h are specialist enough that it's reasonable to ask clients outside the crypto directory to include them with a subdirectory path, to hint that it's an unusual thing to be doing. --- crypto/bcrypt.c | 2 +- crypto/blowfish.c | 2 +- crypto/blowfish.h | 14 +++ crypto/ecc.h | 243 ++++++++++++++++++++++++++++++++++++++++ crypto/mpint_i.h | 324 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ ecc.h | 243 ---------------------------------------- mpint_i.h | 324 ------------------------------------------------------ mpunsafe.c | 2 +- sshblowf.h | 14 --- testcrypt.c | 2 +- testsc.c | 2 +- 11 files changed, 586 insertions(+), 586 deletions(-) create mode 100644 crypto/blowfish.h create mode 100644 crypto/ecc.h create mode 100644 crypto/mpint_i.h delete mode 100644 ecc.h delete mode 100644 mpint_i.h delete mode 100644 sshblowf.h diff --git a/crypto/bcrypt.c b/crypto/bcrypt.c index 7cdd44ed..2b29b037 100644 --- a/crypto/bcrypt.c +++ b/crypto/bcrypt.c @@ -9,7 +9,7 @@ #include #include #include "ssh.h" -#include "sshblowf.h" +#include "blowfish.h" BlowfishContext *bcrypt_setup(const unsigned char *key, int keybytes, const unsigned char *salt, int saltbytes) diff --git a/crypto/blowfish.c b/crypto/blowfish.c index c74f06c0..e8886898 100644 --- a/crypto/blowfish.c +++ b/crypto/blowfish.c @@ -7,7 +7,7 @@ #include #include #include "ssh.h" -#include "sshblowf.h" +#include "blowfish.h" struct BlowfishContext { uint32_t S0[256], S1[256], S2[256], S3[256], P[18]; diff --git a/crypto/blowfish.h b/crypto/blowfish.h new file mode 100644 index 00000000..a9efe3da --- /dev/null +++ b/crypto/blowfish.h @@ -0,0 +1,14 @@ +/* + * Header file shared between sshblowf.c and sshbcrypt.c. Exposes the + * internal Blowfish routines needed by bcrypt. + */ + +typedef struct BlowfishContext BlowfishContext; + +BlowfishContext *blowfish_make_context(void); +void blowfish_free_context(BlowfishContext *ctx); +void blowfish_initkey(BlowfishContext *ctx); +void blowfish_expandkey(BlowfishContext *ctx, + const void *key, short keybytes, + const void *salt, short saltbytes); +void blowfish_lsb_encrypt_ecb(void *blk, int len, BlowfishContext *ctx); diff --git a/crypto/ecc.h b/crypto/ecc.h new file mode 100644 index 00000000..96eebdf0 --- /dev/null +++ b/crypto/ecc.h @@ -0,0 +1,243 @@ +#ifndef PUTTY_ECC_H +#define PUTTY_ECC_H + +/* + * Arithmetic functions for the various kinds of elliptic curves used + * by PuTTY's public-key cryptography. + * + * All of these elliptic curves are over the finite field whose order + * is a large prime p. (Elliptic curves over a field of order 2^n are + * also known, but PuTTY currently has no need of them.) + */ + +/* ---------------------------------------------------------------------- + * Weierstrass curves (or rather, 'short form' Weierstrass curves). + * + * A curve in this form is defined by two parameters a,b, and the + * non-identity points on the curve are represented by (x,y) (the + * 'affine coordinates') such that y^2 = x^3 + ax + b. + * + * The identity element of the curve's group is an additional 'point + * at infinity', which is considered to be the third point on the + * intersection of the curve with any vertical line. Hence, the + * inverse of the point (x,y) is (x,-y). + */ + +/* + * Create and destroy Weierstrass curve data structures. The mandatory + * parameters to the constructor are the prime modulus p, and the + * curve parameters a,b. + * + * 'nonsquare_mod_p' is an optional extra parameter, only needed by + * ecc_edwards_point_new_from_y which has to take a modular square + * root. You can pass it as NULL if you don't need that function. + */ +WeierstrassCurve *ecc_weierstrass_curve( + mp_int *p, mp_int *a, mp_int *b, mp_int *nonsquare_mod_p); +void ecc_weierstrass_curve_free(WeierstrassCurve *); + +/* + * Create points on a Weierstrass curve, given the curve. + * + * point_new_identity returns the special identity point. + * point_new(x,y) returns the non-identity point with the given affine + * coordinates. + * + * point_new_from_x constructs a non-identity point given only the + * x-coordinate, by using the curve equation to work out what y has to + * be. Of course the equation only tells you y^2, so it only + * determines y up to sign; the parameter desired_y_parity controls + * which of the two values of y you get, by saying whether you'd like + * its minimal non-negative residue mod p to be even or odd. (Of + * course, since p itself is odd, exactly one of y and p-y is odd.) + * This function has to take a modular square root, so it will only + * work if you passed in a non-square mod p when constructing the + * curve. + */ +WeierstrassPoint *ecc_weierstrass_point_new_identity(WeierstrassCurve *curve); +WeierstrassPoint *ecc_weierstrass_point_new( + WeierstrassCurve *curve, mp_int *x, mp_int *y); +WeierstrassPoint *ecc_weierstrass_point_new_from_x( + WeierstrassCurve *curve, mp_int *x, unsigned desired_y_parity); + +/* Memory management: copy and free points. */ +void ecc_weierstrass_point_copy_into( + WeierstrassPoint *dest, WeierstrassPoint *src); +WeierstrassPoint *ecc_weierstrass_point_copy(WeierstrassPoint *wc); +void ecc_weierstrass_point_free(WeierstrassPoint *point); + +/* Check whether a point is actually on the curve. */ +unsigned ecc_weierstrass_point_valid(WeierstrassPoint *); + +/* + * Add two points and return their sum. This function is fully + * general: it should do the right thing if the two inputs are the + * same, or if either (or both) of the input points is the identity, + * or if the two input points are inverses so the output is the + * identity. However, it pays for that generality by being slower than + * the special-purpose functions below.. + */ +WeierstrassPoint *ecc_weierstrass_add_general( + WeierstrassPoint *, WeierstrassPoint *); + +/* + * Fast but less general arithmetic functions: add two points on the + * condition that they are not equal and neither is the identity, and + * add a point to itself. + */ +WeierstrassPoint *ecc_weierstrass_add(WeierstrassPoint *, WeierstrassPoint *); +WeierstrassPoint *ecc_weierstrass_double(WeierstrassPoint *); + +/* + * Compute an integer multiple of a point. Not guaranteed to work + * unless the integer argument is less than the order of the point in + * the group (because it won't cope if an identity element shows up in + * any intermediate product). + */ +WeierstrassPoint *ecc_weierstrass_multiply(WeierstrassPoint *, mp_int *); + +/* + * Query functions to get the value of a point back out. is_identity + * tells you whether the point is the identity; if it isn't, then + * get_affine will retrieve one or both of its affine coordinates. + * (You can pass NULL as either output pointer, if you don't need that + * coordinate as output.) + */ +unsigned ecc_weierstrass_is_identity(WeierstrassPoint *wp); +void ecc_weierstrass_get_affine(WeierstrassPoint *wp, mp_int **x, mp_int **y); + +/* ---------------------------------------------------------------------- + * Montgomery curves. + * + * A curve in this form is defined by two parameters a,b, and the + * curve equation is by^2 = x^3 + ax^2 + x. + * + * As with Weierstrass curves, there's an additional point at infinity + * that is the identity element, and the inverse of (x,y) is (x,-y). + * + * However, we don't actually work with full (x,y) pairs. We just + * store the x-coordinate (so what we're really representing is not a + * specific point on the curve but a two-point set {P,-P}). This means + * you can't quite do point addition, because if you're given {P,-P} + * and {Q,-Q} as input, you can work out a pair of x-coordinates that + * are those of P-Q and P+Q, but you don't know which is which. + * + * Instead, the basic operation is 'differential addition', in which + * you are given three parameters P, Q and P-Q and you return P+Q. (As + * well as disambiguating which of the possible answers you want, that + * extra input also enables a fast formulae for computing it. This + * fast formula is more or less why Montgomery curves are useful in + * the first place.) + * + * Doubling a point is still possible to do unambiguously, so you can + * still compute an integer multiple of P if you start by making 2P + * and then doing a series of differential additions. + */ + +/* + * Create and destroy Montgomery curve data structures. + */ +MontgomeryCurve *ecc_montgomery_curve(mp_int *p, mp_int *a, mp_int *b); +void ecc_montgomery_curve_free(MontgomeryCurve *); + +/* + * Create, copy and free points on the curve. We don't need to + * explicitly represent the identity for this application. + */ +MontgomeryPoint *ecc_montgomery_point_new(MontgomeryCurve *mc, mp_int *x); +void ecc_montgomery_point_copy_into( + MontgomeryPoint *dest, MontgomeryPoint *src); +MontgomeryPoint *ecc_montgomery_point_copy(MontgomeryPoint *orig); +void ecc_montgomery_point_free(MontgomeryPoint *mp); + +/* + * Basic arithmetic routines: differential addition and point- + * doubling. Each of these assumes that no special cases come up - no + * input or output point should be the identity, and in diff_add, P + * and Q shouldn't be the same. + */ +MontgomeryPoint *ecc_montgomery_diff_add( + MontgomeryPoint *P, MontgomeryPoint *Q, MontgomeryPoint *PminusQ); +MontgomeryPoint *ecc_montgomery_double(MontgomeryPoint *P); + +/* + * Compute an integer multiple of a point. + */ +MontgomeryPoint *ecc_montgomery_multiply(MontgomeryPoint *, mp_int *); + +/* + * Return the affine x-coordinate of a point. + */ +void ecc_montgomery_get_affine(MontgomeryPoint *mp, mp_int **x); + +/* + * Test whether a point is the curve identity. + */ +unsigned ecc_montgomery_is_identity(MontgomeryPoint *mp); + +/* ---------------------------------------------------------------------- + * Twisted Edwards curves. + * + * A curve in this form is defined by two parameters d,a, and the + * curve equation is a x^2 + y^2 = 1 + d x^2 y^2. + * + * Apparently if you ask a proper algebraic geometer they'll tell you + * that this is technically not an actual elliptic curve. Certainly it + * doesn't work quite the same way as the other kinds: in this form, + * there is no need for a point at infinity, because the identity + * element is represented by the affine coordinates (0,1). And you + * invert a point by negating its x rather than y coordinate: the + * inverse of (x,y) is (-x,y). + * + * The usefulness of this representation is that the addition formula + * is 'strongly unified', meaning that the same formula works for any + * input and output points, without needing special cases for the + * identity or for doubling. + */ + +/* + * Create and destroy Edwards curve data structures. + * + * Similarly to ecc_weierstrass_curve, you don't have to provide + * nonsquare_mod_p if you don't need ecc_edwards_point_new_from_y. + */ +EdwardsCurve *ecc_edwards_curve( + mp_int *p, mp_int *d, mp_int *a, mp_int *nonsquare_mod_p); +void ecc_edwards_curve_free(EdwardsCurve *); + +/* + * Create points. + * + * There's no need to have a separate function to create the identity + * point, because you can just pass x=0 and y=1 to the usual function. + * + * Similarly to the Weierstrass curve, ecc_edwards_point_new_from_y + * creates a point given only its y-coordinate and the desired parity + * of its x-coordinate, and you can only call it if you provided the + * optional nonsquare_mod_p argument when creating the curve. + */ +EdwardsPoint *ecc_edwards_point_new( + EdwardsCurve *curve, mp_int *x, mp_int *y); +EdwardsPoint *ecc_edwards_point_new_from_y( + EdwardsCurve *curve, mp_int *y, unsigned desired_x_parity); + +/* Copy and free points. */ +void ecc_edwards_point_copy_into(EdwardsPoint *dest, EdwardsPoint *src); +EdwardsPoint *ecc_edwards_point_copy(EdwardsPoint *ec); +void ecc_edwards_point_free(EdwardsPoint *point); + +/* + * Arithmetic: add two points, and calculate an integer multiple of a + * point. + */ +EdwardsPoint *ecc_edwards_add(EdwardsPoint *, EdwardsPoint *); +EdwardsPoint *ecc_edwards_multiply(EdwardsPoint *, mp_int *); + +/* + * Query functions: compare two points for equality, and return the + * affine coordinates of a point. + */ +unsigned ecc_edwards_eq(EdwardsPoint *, EdwardsPoint *); +void ecc_edwards_get_affine(EdwardsPoint *wp, mp_int **x, mp_int **y); + +#endif /* PUTTY_ECC_H */ diff --git a/crypto/mpint_i.h b/crypto/mpint_i.h new file mode 100644 index 00000000..d37e75f0 --- /dev/null +++ b/crypto/mpint_i.h @@ -0,0 +1,324 @@ +/* + * mpint_i.h: definitions used internally by the bignum code, and + * also a few other vaguely-bignum-like places. + */ + +/* ---------------------------------------------------------------------- + * The assorted conditional definitions of BignumInt and multiply + * macros used throughout the bignum code to treat numbers as arrays + * of the most conveniently sized word for the target machine. + * Exported so that other code (e.g. poly1305) can use it too. + * + * This code must export, in whatever ifdef branch it ends up in: + * + * - two types: 'BignumInt' and 'BignumCarry'. BignumInt is an + * unsigned integer type which will be used as the base word size + * for all bignum operations. BignumCarry is an unsigned integer + * type used to hold the carry flag taken as input and output by + * the BignumADC macro (see below). + * + * - five constant macros: + * + BIGNUM_INT_BITS, the number of bits in BignumInt, + * + BIGNUM_INT_BYTES, the number of bytes that works out to + * + BIGNUM_TOP_BIT, the BignumInt value consisting of only the top bit + * + BIGNUM_INT_MASK, the BignumInt value with all bits set + * + BIGNUM_INT_BITS_BITS, log to the base 2 of BIGNUM_INT_BITS. + * + * - four statement macros: BignumADC, BignumMUL, BignumMULADD, + * BignumMULADD2. These do various kinds of multi-word arithmetic, + * and all produce two output values. + * * BignumADC(ret,retc,a,b,c) takes input BignumInt values a,b + * and a BignumCarry c, and outputs a BignumInt ret = a+b+c and + * a BignumCarry retc which is the carry off the top of that + * addition. + * * BignumMUL(rh,rl,a,b) returns the two halves of the + * double-width product a*b. + * * BignumMULADD(rh,rl,a,b,addend) returns the two halves of the + * double-width value a*b + addend. + * * BignumMULADD2(rh,rl,a,b,addend1,addend2) returns the two + * halves of the double-width value a*b + addend1 + addend2. + * + * Every branch of the main ifdef below defines the type BignumInt and + * the value BIGNUM_INT_BITS_BITS. The other constant macros are + * filled in by common code further down. + * + * Most branches also define a macro DEFINE_BIGNUMDBLINT containing a + * typedef statement which declares a type _twice_ the length of a + * BignumInt. This causes the common code further down to produce a + * default implementation of the four statement macros in terms of + * that double-width type, and also to defined BignumCarry to be + * BignumInt. + * + * However, if a particular compile target does not have a type twice + * the length of the BignumInt you want to use but it does provide + * some alternative means of doing add-with-carry and double-word + * multiply, then the ifdef branch in question can just define + * BignumCarry and the four statement macros itself, and that's fine + * too. + */ + +/* You can lower the BignumInt size by defining BIGNUM_OVERRIDE on the + * command line to be your chosen max value of BIGNUM_INT_BITS_BITS */ +#if defined BIGNUM_OVERRIDE +#define BB_OK(b) ((b) <= BIGNUM_OVERRIDE) +#else +#define BB_OK(b) (1) +#endif + +#if defined __SIZEOF_INT128__ && BB_OK(6) + + /* + * 64-bit BignumInt using gcc/clang style 128-bit BignumDblInt. + * + * gcc and clang both provide a __uint128_t type on 64-bit targets + * (and, when they do, indicate its presence by the above macro), + * using the same 'two machine registers' kind of code generation + * that 32-bit targets use for 64-bit ints. + */ + + typedef unsigned long long BignumInt; + #define BIGNUM_INT_BITS_BITS 6 + #define DEFINE_BIGNUMDBLINT typedef __uint128_t BignumDblInt + +#elif defined _MSC_VER && defined _M_AMD64 && BB_OK(6) + + /* + * 64-bit BignumInt, using Visual Studio x86-64 compiler intrinsics. + * + * 64-bit Visual Studio doesn't provide very much in the way of help + * here: there's no int128 type, and also no inline assembler giving + * us direct access to the x86-64 MUL or ADC instructions. However, + * there are compiler intrinsics giving us that access, so we can + * use those - though it turns out we have to be a little careful, + * since they seem to generate wrong code if their pointer-typed + * output parameters alias their inputs. Hence all the internal temp + * variables inside the macros. + */ + + #include + typedef unsigned char BignumCarry; /* the type _addcarry_u64 likes to use */ + typedef unsigned __int64 BignumInt; + #define BIGNUM_INT_BITS_BITS 6 + #define BignumADC(ret, retc, a, b, c) do \ + { \ + BignumInt ADC_tmp; \ + (retc) = _addcarry_u64(c, a, b, &ADC_tmp); \ + (ret) = ADC_tmp; \ + } while (0) + #define BignumMUL(rh, rl, a, b) do \ + { \ + BignumInt MULADD_hi; \ + (rl) = _umul128(a, b, &MULADD_hi); \ + (rh) = MULADD_hi; \ + } while (0) + #define BignumMULADD(rh, rl, a, b, addend) do \ + { \ + BignumInt MULADD_lo, MULADD_hi; \ + MULADD_lo = _umul128(a, b, &MULADD_hi); \ + MULADD_hi += _addcarry_u64(0, MULADD_lo, (addend), &(rl)); \ + (rh) = MULADD_hi; \ + } while (0) + #define BignumMULADD2(rh, rl, a, b, addend1, addend2) do \ + { \ + BignumInt MULADD_lo1, MULADD_lo2, MULADD_hi; \ + MULADD_lo1 = _umul128(a, b, &MULADD_hi); \ + MULADD_hi += _addcarry_u64(0, MULADD_lo1, (addend1), &MULADD_lo2); \ + MULADD_hi += _addcarry_u64(0, MULADD_lo2, (addend2), &(rl)); \ + (rh) = MULADD_hi; \ + } while (0) + +#elif (defined __GNUC__ || defined _LLP64 || __STDC__ >= 199901L) && BB_OK(5) + + /* 32-bit BignumInt, using C99 unsigned long long as BignumDblInt */ + + typedef unsigned int BignumInt; + #define BIGNUM_INT_BITS_BITS 5 + #define DEFINE_BIGNUMDBLINT typedef unsigned long long BignumDblInt + +#elif defined _MSC_VER && BB_OK(5) + + /* 32-bit BignumInt, using Visual Studio __int64 as BignumDblInt */ + + typedef unsigned int BignumInt; + #define BIGNUM_INT_BITS_BITS 5 + #define DEFINE_BIGNUMDBLINT typedef unsigned __int64 BignumDblInt + +#elif defined _LP64 && BB_OK(5) + + /* + * 32-bit BignumInt, using unsigned long itself as BignumDblInt. + * + * Only for platforms where long is 64 bits, of course. + */ + + typedef unsigned int BignumInt; + #define BIGNUM_INT_BITS_BITS 5 + #define DEFINE_BIGNUMDBLINT typedef unsigned long BignumDblInt + +#elif BB_OK(4) + + /* + * 16-bit BignumInt, using unsigned long as BignumDblInt. + * + * This is the final fallback for real emergencies: C89 guarantees + * unsigned short/long to be at least the required sizes, so this + * should work on any C implementation at all. But it'll be + * noticeably slow, so if you find yourself in this case you + * probably want to move heaven and earth to find an alternative! + */ + + typedef unsigned short BignumInt; + #define BIGNUM_INT_BITS_BITS 4 + #define DEFINE_BIGNUMDBLINT typedef unsigned long BignumDblInt + +#else + + /* Should only get here if BB_OK(4) evaluated false, i.e. the + * command line defined BIGNUM_OVERRIDE to an absurdly small + * value. */ + #error Must define BIGNUM_OVERRIDE to at least 4 + +#endif + +#undef BB_OK + +/* + * Common code across all branches of that ifdef: define all the + * easy constant macros in terms of BIGNUM_INT_BITS_BITS. + */ +#define BIGNUM_INT_BITS (1 << BIGNUM_INT_BITS_BITS) +#define BIGNUM_INT_BYTES (BIGNUM_INT_BITS / 8) +#define BIGNUM_TOP_BIT (((BignumInt)1) << (BIGNUM_INT_BITS-1)) +#define BIGNUM_INT_MASK (BIGNUM_TOP_BIT | (BIGNUM_TOP_BIT-1)) + +/* + * Just occasionally, we might need a GET_nnBIT_xSB_FIRST macro to + * operate on whatever BignumInt is. + */ +#if BIGNUM_INT_BITS_BITS == 4 +#define GET_BIGNUMINT_MSB_FIRST GET_16BIT_MSB_FIRST +#define GET_BIGNUMINT_LSB_FIRST GET_16BIT_LSB_FIRST +#define PUT_BIGNUMINT_MSB_FIRST PUT_16BIT_MSB_FIRST +#define PUT_BIGNUMINT_LSB_FIRST PUT_16BIT_LSB_FIRST +#elif BIGNUM_INT_BITS_BITS == 5 +#define GET_BIGNUMINT_MSB_FIRST GET_32BIT_MSB_FIRST +#define GET_BIGNUMINT_LSB_FIRST GET_32BIT_LSB_FIRST +#define PUT_BIGNUMINT_MSB_FIRST PUT_32BIT_MSB_FIRST +#define PUT_BIGNUMINT_LSB_FIRST PUT_32BIT_LSB_FIRST +#elif BIGNUM_INT_BITS_BITS == 6 +#define GET_BIGNUMINT_MSB_FIRST GET_64BIT_MSB_FIRST +#define GET_BIGNUMINT_LSB_FIRST GET_64BIT_LSB_FIRST +#define PUT_BIGNUMINT_MSB_FIRST PUT_64BIT_MSB_FIRST +#define PUT_BIGNUMINT_LSB_FIRST PUT_64BIT_LSB_FIRST +#else + #error Ran out of options for GET_BIGNUMINT_xSB_FIRST +#endif + +/* + * Common code across _most_ branches of the ifdef: define a set of + * statement macros in terms of the BignumDblInt type provided. In + * this case, we also define BignumCarry to be the same thing as + * BignumInt, for simplicity. + */ +#ifdef DEFINE_BIGNUMDBLINT + + typedef BignumInt BignumCarry; + #define BignumADC(ret, retc, a, b, c) do \ + { \ + DEFINE_BIGNUMDBLINT; \ + BignumDblInt ADC_temp = (BignumInt)(a); \ + ADC_temp += (BignumInt)(b); \ + ADC_temp += (c); \ + (ret) = (BignumInt)ADC_temp; \ + (retc) = (BignumCarry)(ADC_temp >> BIGNUM_INT_BITS); \ + } while (0) + + #define BignumMUL(rh, rl, a, b) do \ + { \ + DEFINE_BIGNUMDBLINT; \ + BignumDblInt MUL_temp = (BignumInt)(a); \ + MUL_temp *= (BignumInt)(b); \ + (rh) = (BignumInt)(MUL_temp >> BIGNUM_INT_BITS); \ + (rl) = (BignumInt)(MUL_temp); \ + } while (0) + + #define BignumMULADD(rh, rl, a, b, addend) do \ + { \ + DEFINE_BIGNUMDBLINT; \ + BignumDblInt MUL_temp = (BignumInt)(a); \ + MUL_temp *= (BignumInt)(b); \ + MUL_temp += (BignumInt)(addend); \ + (rh) = (BignumInt)(MUL_temp >> BIGNUM_INT_BITS); \ + (rl) = (BignumInt)(MUL_temp); \ + } while (0) + + #define BignumMULADD2(rh, rl, a, b, addend1, addend2) do \ + { \ + DEFINE_BIGNUMDBLINT; \ + BignumDblInt MUL_temp = (BignumInt)(a); \ + MUL_temp *= (BignumInt)(b); \ + MUL_temp += (BignumInt)(addend1); \ + MUL_temp += (BignumInt)(addend2); \ + (rh) = (BignumInt)(MUL_temp >> BIGNUM_INT_BITS); \ + (rl) = (BignumInt)(MUL_temp); \ + } while (0) + +#endif /* DEFINE_BIGNUMDBLINT */ + +/* ---------------------------------------------------------------------- + * Data structures used inside bignum.c. + */ + +struct mp_int { + size_t nw; + BignumInt *w; +}; + +struct MontyContext { + /* + * The actual modulus. + */ + mp_int *m; + + /* + * Montgomery multiplication works by selecting a value r > m, + * coprime to m, which is really easy to divide by. In binary + * arithmetic, that means making it a power of 2; in fact we make + * it a whole number of BignumInt. + * + * We don't store r directly as an mp_int (there's no need). But + * its value is 2^rbits; we also store rw = rbits/BIGNUM_INT_BITS + * (the corresponding word offset within an mp_int). + * + * pw is the number of words needed to store an mp_int you're + * doing reduction on: it has to be big enough to hold the sum of + * an input value up to m^2 plus an extra addend up to m*r. + */ + size_t rbits, rw, pw; + + /* + * The key step in Montgomery reduction requires the inverse of -m + * mod r. + */ + mp_int *minus_minv_mod_r; + + /* + * r^1, r^2 and r^3 mod m, which are used for various purposes. + * + * (Annoyingly, this is one of the rare cases where it would have + * been nicer to have a Pascal-style 1-indexed array. I couldn't + * _quite_ bring myself to put a gratuitous zero element in here. + * So you just have to live with getting r^k by taking the [k-1]th + * element of this array.) + */ + mp_int *powers_of_r_mod_m[3]; + + /* + * Persistent scratch space from which monty_* functions can + * allocate storage for intermediate values. + */ + mp_int *scratch; +}; + +/* Functions shared between mpint.c and mpunsafe.c */ +mp_int *mp_make_sized(size_t nw); diff --git a/ecc.h b/ecc.h deleted file mode 100644 index 96eebdf0..00000000 --- a/ecc.h +++ /dev/null @@ -1,243 +0,0 @@ -#ifndef PUTTY_ECC_H -#define PUTTY_ECC_H - -/* - * Arithmetic functions for the various kinds of elliptic curves used - * by PuTTY's public-key cryptography. - * - * All of these elliptic curves are over the finite field whose order - * is a large prime p. (Elliptic curves over a field of order 2^n are - * also known, but PuTTY currently has no need of them.) - */ - -/* ---------------------------------------------------------------------- - * Weierstrass curves (or rather, 'short form' Weierstrass curves). - * - * A curve in this form is defined by two parameters a,b, and the - * non-identity points on the curve are represented by (x,y) (the - * 'affine coordinates') such that y^2 = x^3 + ax + b. - * - * The identity element of the curve's group is an additional 'point - * at infinity', which is considered to be the third point on the - * intersection of the curve with any vertical line. Hence, the - * inverse of the point (x,y) is (x,-y). - */ - -/* - * Create and destroy Weierstrass curve data structures. The mandatory - * parameters to the constructor are the prime modulus p, and the - * curve parameters a,b. - * - * 'nonsquare_mod_p' is an optional extra parameter, only needed by - * ecc_edwards_point_new_from_y which has to take a modular square - * root. You can pass it as NULL if you don't need that function. - */ -WeierstrassCurve *ecc_weierstrass_curve( - mp_int *p, mp_int *a, mp_int *b, mp_int *nonsquare_mod_p); -void ecc_weierstrass_curve_free(WeierstrassCurve *); - -/* - * Create points on a Weierstrass curve, given the curve. - * - * point_new_identity returns the special identity point. - * point_new(x,y) returns the non-identity point with the given affine - * coordinates. - * - * point_new_from_x constructs a non-identity point given only the - * x-coordinate, by using the curve equation to work out what y has to - * be. Of course the equation only tells you y^2, so it only - * determines y up to sign; the parameter desired_y_parity controls - * which of the two values of y you get, by saying whether you'd like - * its minimal non-negative residue mod p to be even or odd. (Of - * course, since p itself is odd, exactly one of y and p-y is odd.) - * This function has to take a modular square root, so it will only - * work if you passed in a non-square mod p when constructing the - * curve. - */ -WeierstrassPoint *ecc_weierstrass_point_new_identity(WeierstrassCurve *curve); -WeierstrassPoint *ecc_weierstrass_point_new( - WeierstrassCurve *curve, mp_int *x, mp_int *y); -WeierstrassPoint *ecc_weierstrass_point_new_from_x( - WeierstrassCurve *curve, mp_int *x, unsigned desired_y_parity); - -/* Memory management: copy and free points. */ -void ecc_weierstrass_point_copy_into( - WeierstrassPoint *dest, WeierstrassPoint *src); -WeierstrassPoint *ecc_weierstrass_point_copy(WeierstrassPoint *wc); -void ecc_weierstrass_point_free(WeierstrassPoint *point); - -/* Check whether a point is actually on the curve. */ -unsigned ecc_weierstrass_point_valid(WeierstrassPoint *); - -/* - * Add two points and return their sum. This function is fully - * general: it should do the right thing if the two inputs are the - * same, or if either (or both) of the input points is the identity, - * or if the two input points are inverses so the output is the - * identity. However, it pays for that generality by being slower than - * the special-purpose functions below.. - */ -WeierstrassPoint *ecc_weierstrass_add_general( - WeierstrassPoint *, WeierstrassPoint *); - -/* - * Fast but less general arithmetic functions: add two points on the - * condition that they are not equal and neither is the identity, and - * add a point to itself. - */ -WeierstrassPoint *ecc_weierstrass_add(WeierstrassPoint *, WeierstrassPoint *); -WeierstrassPoint *ecc_weierstrass_double(WeierstrassPoint *); - -/* - * Compute an integer multiple of a point. Not guaranteed to work - * unless the integer argument is less than the order of the point in - * the group (because it won't cope if an identity element shows up in - * any intermediate product). - */ -WeierstrassPoint *ecc_weierstrass_multiply(WeierstrassPoint *, mp_int *); - -/* - * Query functions to get the value of a point back out. is_identity - * tells you whether the point is the identity; if it isn't, then - * get_affine will retrieve one or both of its affine coordinates. - * (You can pass NULL as either output pointer, if you don't need that - * coordinate as output.) - */ -unsigned ecc_weierstrass_is_identity(WeierstrassPoint *wp); -void ecc_weierstrass_get_affine(WeierstrassPoint *wp, mp_int **x, mp_int **y); - -/* ---------------------------------------------------------------------- - * Montgomery curves. - * - * A curve in this form is defined by two parameters a,b, and the - * curve equation is by^2 = x^3 + ax^2 + x. - * - * As with Weierstrass curves, there's an additional point at infinity - * that is the identity element, and the inverse of (x,y) is (x,-y). - * - * However, we don't actually work with full (x,y) pairs. We just - * store the x-coordinate (so what we're really representing is not a - * specific point on the curve but a two-point set {P,-P}). This means - * you can't quite do point addition, because if you're given {P,-P} - * and {Q,-Q} as input, you can work out a pair of x-coordinates that - * are those of P-Q and P+Q, but you don't know which is which. - * - * Instead, the basic operation is 'differential addition', in which - * you are given three parameters P, Q and P-Q and you return P+Q. (As - * well as disambiguating which of the possible answers you want, that - * extra input also enables a fast formulae for computing it. This - * fast formula is more or less why Montgomery curves are useful in - * the first place.) - * - * Doubling a point is still possible to do unambiguously, so you can - * still compute an integer multiple of P if you start by making 2P - * and then doing a series of differential additions. - */ - -/* - * Create and destroy Montgomery curve data structures. - */ -MontgomeryCurve *ecc_montgomery_curve(mp_int *p, mp_int *a, mp_int *b); -void ecc_montgomery_curve_free(MontgomeryCurve *); - -/* - * Create, copy and free points on the curve. We don't need to - * explicitly represent the identity for this application. - */ -MontgomeryPoint *ecc_montgomery_point_new(MontgomeryCurve *mc, mp_int *x); -void ecc_montgomery_point_copy_into( - MontgomeryPoint *dest, MontgomeryPoint *src); -MontgomeryPoint *ecc_montgomery_point_copy(MontgomeryPoint *orig); -void ecc_montgomery_point_free(MontgomeryPoint *mp); - -/* - * Basic arithmetic routines: differential addition and point- - * doubling. Each of these assumes that no special cases come up - no - * input or output point should be the identity, and in diff_add, P - * and Q shouldn't be the same. - */ -MontgomeryPoint *ecc_montgomery_diff_add( - MontgomeryPoint *P, MontgomeryPoint *Q, MontgomeryPoint *PminusQ); -MontgomeryPoint *ecc_montgomery_double(MontgomeryPoint *P); - -/* - * Compute an integer multiple of a point. - */ -MontgomeryPoint *ecc_montgomery_multiply(MontgomeryPoint *, mp_int *); - -/* - * Return the affine x-coordinate of a point. - */ -void ecc_montgomery_get_affine(MontgomeryPoint *mp, mp_int **x); - -/* - * Test whether a point is the curve identity. - */ -unsigned ecc_montgomery_is_identity(MontgomeryPoint *mp); - -/* ---------------------------------------------------------------------- - * Twisted Edwards curves. - * - * A curve in this form is defined by two parameters d,a, and the - * curve equation is a x^2 + y^2 = 1 + d x^2 y^2. - * - * Apparently if you ask a proper algebraic geometer they'll tell you - * that this is technically not an actual elliptic curve. Certainly it - * doesn't work quite the same way as the other kinds: in this form, - * there is no need for a point at infinity, because the identity - * element is represented by the affine coordinates (0,1). And you - * invert a point by negating its x rather than y coordinate: the - * inverse of (x,y) is (-x,y). - * - * The usefulness of this representation is that the addition formula - * is 'strongly unified', meaning that the same formula works for any - * input and output points, without needing special cases for the - * identity or for doubling. - */ - -/* - * Create and destroy Edwards curve data structures. - * - * Similarly to ecc_weierstrass_curve, you don't have to provide - * nonsquare_mod_p if you don't need ecc_edwards_point_new_from_y. - */ -EdwardsCurve *ecc_edwards_curve( - mp_int *p, mp_int *d, mp_int *a, mp_int *nonsquare_mod_p); -void ecc_edwards_curve_free(EdwardsCurve *); - -/* - * Create points. - * - * There's no need to have a separate function to create the identity - * point, because you can just pass x=0 and y=1 to the usual function. - * - * Similarly to the Weierstrass curve, ecc_edwards_point_new_from_y - * creates a point given only its y-coordinate and the desired parity - * of its x-coordinate, and you can only call it if you provided the - * optional nonsquare_mod_p argument when creating the curve. - */ -EdwardsPoint *ecc_edwards_point_new( - EdwardsCurve *curve, mp_int *x, mp_int *y); -EdwardsPoint *ecc_edwards_point_new_from_y( - EdwardsCurve *curve, mp_int *y, unsigned desired_x_parity); - -/* Copy and free points. */ -void ecc_edwards_point_copy_into(EdwardsPoint *dest, EdwardsPoint *src); -EdwardsPoint *ecc_edwards_point_copy(EdwardsPoint *ec); -void ecc_edwards_point_free(EdwardsPoint *point); - -/* - * Arithmetic: add two points, and calculate an integer multiple of a - * point. - */ -EdwardsPoint *ecc_edwards_add(EdwardsPoint *, EdwardsPoint *); -EdwardsPoint *ecc_edwards_multiply(EdwardsPoint *, mp_int *); - -/* - * Query functions: compare two points for equality, and return the - * affine coordinates of a point. - */ -unsigned ecc_edwards_eq(EdwardsPoint *, EdwardsPoint *); -void ecc_edwards_get_affine(EdwardsPoint *wp, mp_int **x, mp_int **y); - -#endif /* PUTTY_ECC_H */ diff --git a/mpint_i.h b/mpint_i.h deleted file mode 100644 index d37e75f0..00000000 --- a/mpint_i.h +++ /dev/null @@ -1,324 +0,0 @@ -/* - * mpint_i.h: definitions used internally by the bignum code, and - * also a few other vaguely-bignum-like places. - */ - -/* ---------------------------------------------------------------------- - * The assorted conditional definitions of BignumInt and multiply - * macros used throughout the bignum code to treat numbers as arrays - * of the most conveniently sized word for the target machine. - * Exported so that other code (e.g. poly1305) can use it too. - * - * This code must export, in whatever ifdef branch it ends up in: - * - * - two types: 'BignumInt' and 'BignumCarry'. BignumInt is an - * unsigned integer type which will be used as the base word size - * for all bignum operations. BignumCarry is an unsigned integer - * type used to hold the carry flag taken as input and output by - * the BignumADC macro (see below). - * - * - five constant macros: - * + BIGNUM_INT_BITS, the number of bits in BignumInt, - * + BIGNUM_INT_BYTES, the number of bytes that works out to - * + BIGNUM_TOP_BIT, the BignumInt value consisting of only the top bit - * + BIGNUM_INT_MASK, the BignumInt value with all bits set - * + BIGNUM_INT_BITS_BITS, log to the base 2 of BIGNUM_INT_BITS. - * - * - four statement macros: BignumADC, BignumMUL, BignumMULADD, - * BignumMULADD2. These do various kinds of multi-word arithmetic, - * and all produce two output values. - * * BignumADC(ret,retc,a,b,c) takes input BignumInt values a,b - * and a BignumCarry c, and outputs a BignumInt ret = a+b+c and - * a BignumCarry retc which is the carry off the top of that - * addition. - * * BignumMUL(rh,rl,a,b) returns the two halves of the - * double-width product a*b. - * * BignumMULADD(rh,rl,a,b,addend) returns the two halves of the - * double-width value a*b + addend. - * * BignumMULADD2(rh,rl,a,b,addend1,addend2) returns the two - * halves of the double-width value a*b + addend1 + addend2. - * - * Every branch of the main ifdef below defines the type BignumInt and - * the value BIGNUM_INT_BITS_BITS. The other constant macros are - * filled in by common code further down. - * - * Most branches also define a macro DEFINE_BIGNUMDBLINT containing a - * typedef statement which declares a type _twice_ the length of a - * BignumInt. This causes the common code further down to produce a - * default implementation of the four statement macros in terms of - * that double-width type, and also to defined BignumCarry to be - * BignumInt. - * - * However, if a particular compile target does not have a type twice - * the length of the BignumInt you want to use but it does provide - * some alternative means of doing add-with-carry and double-word - * multiply, then the ifdef branch in question can just define - * BignumCarry and the four statement macros itself, and that's fine - * too. - */ - -/* You can lower the BignumInt size by defining BIGNUM_OVERRIDE on the - * command line to be your chosen max value of BIGNUM_INT_BITS_BITS */ -#if defined BIGNUM_OVERRIDE -#define BB_OK(b) ((b) <= BIGNUM_OVERRIDE) -#else -#define BB_OK(b) (1) -#endif - -#if defined __SIZEOF_INT128__ && BB_OK(6) - - /* - * 64-bit BignumInt using gcc/clang style 128-bit BignumDblInt. - * - * gcc and clang both provide a __uint128_t type on 64-bit targets - * (and, when they do, indicate its presence by the above macro), - * using the same 'two machine registers' kind of code generation - * that 32-bit targets use for 64-bit ints. - */ - - typedef unsigned long long BignumInt; - #define BIGNUM_INT_BITS_BITS 6 - #define DEFINE_BIGNUMDBLINT typedef __uint128_t BignumDblInt - -#elif defined _MSC_VER && defined _M_AMD64 && BB_OK(6) - - /* - * 64-bit BignumInt, using Visual Studio x86-64 compiler intrinsics. - * - * 64-bit Visual Studio doesn't provide very much in the way of help - * here: there's no int128 type, and also no inline assembler giving - * us direct access to the x86-64 MUL or ADC instructions. However, - * there are compiler intrinsics giving us that access, so we can - * use those - though it turns out we have to be a little careful, - * since they seem to generate wrong code if their pointer-typed - * output parameters alias their inputs. Hence all the internal temp - * variables inside the macros. - */ - - #include - typedef unsigned char BignumCarry; /* the type _addcarry_u64 likes to use */ - typedef unsigned __int64 BignumInt; - #define BIGNUM_INT_BITS_BITS 6 - #define BignumADC(ret, retc, a, b, c) do \ - { \ - BignumInt ADC_tmp; \ - (retc) = _addcarry_u64(c, a, b, &ADC_tmp); \ - (ret) = ADC_tmp; \ - } while (0) - #define BignumMUL(rh, rl, a, b) do \ - { \ - BignumInt MULADD_hi; \ - (rl) = _umul128(a, b, &MULADD_hi); \ - (rh) = MULADD_hi; \ - } while (0) - #define BignumMULADD(rh, rl, a, b, addend) do \ - { \ - BignumInt MULADD_lo, MULADD_hi; \ - MULADD_lo = _umul128(a, b, &MULADD_hi); \ - MULADD_hi += _addcarry_u64(0, MULADD_lo, (addend), &(rl)); \ - (rh) = MULADD_hi; \ - } while (0) - #define BignumMULADD2(rh, rl, a, b, addend1, addend2) do \ - { \ - BignumInt MULADD_lo1, MULADD_lo2, MULADD_hi; \ - MULADD_lo1 = _umul128(a, b, &MULADD_hi); \ - MULADD_hi += _addcarry_u64(0, MULADD_lo1, (addend1), &MULADD_lo2); \ - MULADD_hi += _addcarry_u64(0, MULADD_lo2, (addend2), &(rl)); \ - (rh) = MULADD_hi; \ - } while (0) - -#elif (defined __GNUC__ || defined _LLP64 || __STDC__ >= 199901L) && BB_OK(5) - - /* 32-bit BignumInt, using C99 unsigned long long as BignumDblInt */ - - typedef unsigned int BignumInt; - #define BIGNUM_INT_BITS_BITS 5 - #define DEFINE_BIGNUMDBLINT typedef unsigned long long BignumDblInt - -#elif defined _MSC_VER && BB_OK(5) - - /* 32-bit BignumInt, using Visual Studio __int64 as BignumDblInt */ - - typedef unsigned int BignumInt; - #define BIGNUM_INT_BITS_BITS 5 - #define DEFINE_BIGNUMDBLINT typedef unsigned __int64 BignumDblInt - -#elif defined _LP64 && BB_OK(5) - - /* - * 32-bit BignumInt, using unsigned long itself as BignumDblInt. - * - * Only for platforms where long is 64 bits, of course. - */ - - typedef unsigned int BignumInt; - #define BIGNUM_INT_BITS_BITS 5 - #define DEFINE_BIGNUMDBLINT typedef unsigned long BignumDblInt - -#elif BB_OK(4) - - /* - * 16-bit BignumInt, using unsigned long as BignumDblInt. - * - * This is the final fallback for real emergencies: C89 guarantees - * unsigned short/long to be at least the required sizes, so this - * should work on any C implementation at all. But it'll be - * noticeably slow, so if you find yourself in this case you - * probably want to move heaven and earth to find an alternative! - */ - - typedef unsigned short BignumInt; - #define BIGNUM_INT_BITS_BITS 4 - #define DEFINE_BIGNUMDBLINT typedef unsigned long BignumDblInt - -#else - - /* Should only get here if BB_OK(4) evaluated false, i.e. the - * command line defined BIGNUM_OVERRIDE to an absurdly small - * value. */ - #error Must define BIGNUM_OVERRIDE to at least 4 - -#endif - -#undef BB_OK - -/* - * Common code across all branches of that ifdef: define all the - * easy constant macros in terms of BIGNUM_INT_BITS_BITS. - */ -#define BIGNUM_INT_BITS (1 << BIGNUM_INT_BITS_BITS) -#define BIGNUM_INT_BYTES (BIGNUM_INT_BITS / 8) -#define BIGNUM_TOP_BIT (((BignumInt)1) << (BIGNUM_INT_BITS-1)) -#define BIGNUM_INT_MASK (BIGNUM_TOP_BIT | (BIGNUM_TOP_BIT-1)) - -/* - * Just occasionally, we might need a GET_nnBIT_xSB_FIRST macro to - * operate on whatever BignumInt is. - */ -#if BIGNUM_INT_BITS_BITS == 4 -#define GET_BIGNUMINT_MSB_FIRST GET_16BIT_MSB_FIRST -#define GET_BIGNUMINT_LSB_FIRST GET_16BIT_LSB_FIRST -#define PUT_BIGNUMINT_MSB_FIRST PUT_16BIT_MSB_FIRST -#define PUT_BIGNUMINT_LSB_FIRST PUT_16BIT_LSB_FIRST -#elif BIGNUM_INT_BITS_BITS == 5 -#define GET_BIGNUMINT_MSB_FIRST GET_32BIT_MSB_FIRST -#define GET_BIGNUMINT_LSB_FIRST GET_32BIT_LSB_FIRST -#define PUT_BIGNUMINT_MSB_FIRST PUT_32BIT_MSB_FIRST -#define PUT_BIGNUMINT_LSB_FIRST PUT_32BIT_LSB_FIRST -#elif BIGNUM_INT_BITS_BITS == 6 -#define GET_BIGNUMINT_MSB_FIRST GET_64BIT_MSB_FIRST -#define GET_BIGNUMINT_LSB_FIRST GET_64BIT_LSB_FIRST -#define PUT_BIGNUMINT_MSB_FIRST PUT_64BIT_MSB_FIRST -#define PUT_BIGNUMINT_LSB_FIRST PUT_64BIT_LSB_FIRST -#else - #error Ran out of options for GET_BIGNUMINT_xSB_FIRST -#endif - -/* - * Common code across _most_ branches of the ifdef: define a set of - * statement macros in terms of the BignumDblInt type provided. In - * this case, we also define BignumCarry to be the same thing as - * BignumInt, for simplicity. - */ -#ifdef DEFINE_BIGNUMDBLINT - - typedef BignumInt BignumCarry; - #define BignumADC(ret, retc, a, b, c) do \ - { \ - DEFINE_BIGNUMDBLINT; \ - BignumDblInt ADC_temp = (BignumInt)(a); \ - ADC_temp += (BignumInt)(b); \ - ADC_temp += (c); \ - (ret) = (BignumInt)ADC_temp; \ - (retc) = (BignumCarry)(ADC_temp >> BIGNUM_INT_BITS); \ - } while (0) - - #define BignumMUL(rh, rl, a, b) do \ - { \ - DEFINE_BIGNUMDBLINT; \ - BignumDblInt MUL_temp = (BignumInt)(a); \ - MUL_temp *= (BignumInt)(b); \ - (rh) = (BignumInt)(MUL_temp >> BIGNUM_INT_BITS); \ - (rl) = (BignumInt)(MUL_temp); \ - } while (0) - - #define BignumMULADD(rh, rl, a, b, addend) do \ - { \ - DEFINE_BIGNUMDBLINT; \ - BignumDblInt MUL_temp = (BignumInt)(a); \ - MUL_temp *= (BignumInt)(b); \ - MUL_temp += (BignumInt)(addend); \ - (rh) = (BignumInt)(MUL_temp >> BIGNUM_INT_BITS); \ - (rl) = (BignumInt)(MUL_temp); \ - } while (0) - - #define BignumMULADD2(rh, rl, a, b, addend1, addend2) do \ - { \ - DEFINE_BIGNUMDBLINT; \ - BignumDblInt MUL_temp = (BignumInt)(a); \ - MUL_temp *= (BignumInt)(b); \ - MUL_temp += (BignumInt)(addend1); \ - MUL_temp += (BignumInt)(addend2); \ - (rh) = (BignumInt)(MUL_temp >> BIGNUM_INT_BITS); \ - (rl) = (BignumInt)(MUL_temp); \ - } while (0) - -#endif /* DEFINE_BIGNUMDBLINT */ - -/* ---------------------------------------------------------------------- - * Data structures used inside bignum.c. - */ - -struct mp_int { - size_t nw; - BignumInt *w; -}; - -struct MontyContext { - /* - * The actual modulus. - */ - mp_int *m; - - /* - * Montgomery multiplication works by selecting a value r > m, - * coprime to m, which is really easy to divide by. In binary - * arithmetic, that means making it a power of 2; in fact we make - * it a whole number of BignumInt. - * - * We don't store r directly as an mp_int (there's no need). But - * its value is 2^rbits; we also store rw = rbits/BIGNUM_INT_BITS - * (the corresponding word offset within an mp_int). - * - * pw is the number of words needed to store an mp_int you're - * doing reduction on: it has to be big enough to hold the sum of - * an input value up to m^2 plus an extra addend up to m*r. - */ - size_t rbits, rw, pw; - - /* - * The key step in Montgomery reduction requires the inverse of -m - * mod r. - */ - mp_int *minus_minv_mod_r; - - /* - * r^1, r^2 and r^3 mod m, which are used for various purposes. - * - * (Annoyingly, this is one of the rare cases where it would have - * been nicer to have a Pascal-style 1-indexed array. I couldn't - * _quite_ bring myself to put a gratuitous zero element in here. - * So you just have to live with getting r^k by taking the [k-1]th - * element of this array.) - */ - mp_int *powers_of_r_mod_m[3]; - - /* - * Persistent scratch space from which monty_* functions can - * allocate storage for intermediate values. - */ - mp_int *scratch; -}; - -/* Functions shared between mpint.c and mpunsafe.c */ -mp_int *mp_make_sized(size_t nw); diff --git a/mpunsafe.c b/mpunsafe.c index beec13fa..a6130018 100644 --- a/mpunsafe.c +++ b/mpunsafe.c @@ -7,7 +7,7 @@ #include "puttymem.h" #include "mpint.h" -#include "mpint_i.h" +#include "crypto/mpint_i.h" /* * This global symbol is also defined in ssh2kex-client.c, to ensure diff --git a/sshblowf.h b/sshblowf.h deleted file mode 100644 index a9efe3da..00000000 --- a/sshblowf.h +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Header file shared between sshblowf.c and sshbcrypt.c. Exposes the - * internal Blowfish routines needed by bcrypt. - */ - -typedef struct BlowfishContext BlowfishContext; - -BlowfishContext *blowfish_make_context(void); -void blowfish_free_context(BlowfishContext *ctx); -void blowfish_initkey(BlowfishContext *ctx); -void blowfish_expandkey(BlowfishContext *ctx, - const void *key, short keybytes, - const void *salt, short saltbytes); -void blowfish_lsb_encrypt_ecb(void *blk, int len, BlowfishContext *ctx); diff --git a/testcrypt.c b/testcrypt.c index 1948da08..a370a667 100644 --- a/testcrypt.c +++ b/testcrypt.c @@ -34,7 +34,7 @@ #include "sshkeygen.h" #include "misc.h" #include "mpint.h" -#include "ecc.h" +#include "crypto/ecc.h" static NORETURN PRINTF_LIKE(1, 2) void fatal_error(const char *p, ...) { diff --git a/testsc.c b/testsc.c index b182d382..a3e8b5d0 100644 --- a/testsc.c +++ b/testsc.c @@ -79,7 +79,7 @@ #include "ssh.h" #include "misc.h" #include "mpint.h" -#include "ecc.h" +#include "crypto/ecc.h" static NORETURN PRINTF_LIKE(1, 2) void fatal_error(const char *p, ...) { -- cgit v1.2.3 From 83fa43497fb6885152ed303b77a904ae5ca9c43e Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 22 Apr 2021 17:58:40 +0100 Subject: Move the SSH implementation into its own subdirectory. This clears up another large pile of clutter at the top level, and in the process, allows me to rename source files to things that don't all have that annoying 'ssh' prefix at the top. --- CMakeLists.txt | 32 +- agentf.c | 249 ----- crypto/diffie-hellman.c | 2 +- mainchan.c | 538 ---------- mpunsafe.c | 2 +- noshare.c | 25 - pgssapi.c | 105 -- pgssapi.h | 333 ------- portfwd.c | 1174 ---------------------- pscp.c | 2 +- psftp.c | 2 +- psftpcommon.c | 2 +- psocks.c | 2 +- putty.h | 8 +- scpserver.c | 1399 -------------------------- sesschan.c | 787 --------------- settings.c | 4 +- sftp.c | 1205 ----------------------- sftp.h | 544 ---------- sftpcommon.c | 139 --- sftpserver.c | 279 ------ ssh.c | 1248 ----------------------- ssh.h | 2 +- ssh/CMakeLists.txt | 51 + ssh/agentf.c | 249 +++++ ssh/bpp-bare.c | 204 ++++ ssh/bpp.h | 179 ++++ ssh/bpp1.c | 387 ++++++++ ssh/bpp2.c | 982 +++++++++++++++++++ ssh/censor1.c | 76 ++ ssh/censor2.c | 107 ++ ssh/channel.h | 316 ++++++ ssh/common.c | 946 ++++++++++++++++++ ssh/connection1-client.c | 547 +++++++++++ ssh/connection1-server.c | 365 +++++++ ssh/connection1.c | 815 +++++++++++++++ ssh/connection1.h | 123 +++ ssh/connection2-client.c | 505 ++++++++++ ssh/connection2-server.c | 306 ++++++ ssh/connection2.c | 1745 +++++++++++++++++++++++++++++++++ ssh/connection2.h | 235 +++++ ssh/crc-attack-detector.c | 171 ++++ ssh/gss.h | 217 ++++ ssh/gssc.c | 288 ++++++ ssh/gssc.h | 24 + ssh/kex2-client.c | 930 ++++++++++++++++++ ssh/kex2-server.c | 330 +++++++ ssh/login1-server.c | 447 +++++++++ ssh/login1.c | 1242 +++++++++++++++++++++++ ssh/mainchan.c | 538 ++++++++++ ssh/nogss.c | 19 + ssh/nosharing.c | 25 + ssh/pgssapi.c | 105 ++ ssh/pgssapi.h | 333 +++++++ ssh/portfwd.c | 1174 ++++++++++++++++++++++ ssh/ppl.h | 174 ++++ ssh/scpserver.c | 1399 ++++++++++++++++++++++++++ ssh/server.c | 591 +++++++++++ ssh/server.h | 143 +++ ssh/sesschan.c | 787 +++++++++++++++ ssh/sftp.c | 1205 +++++++++++++++++++++++ ssh/sftp.h | 544 ++++++++++ ssh/sftpcommon.c | 139 +++ ssh/sftpserver.c | 279 ++++++ ssh/sharing.c | 2180 ++++++++++++++++++++++++++++++++++++++++ ssh/signal-list.h | 53 + ssh/ssh.c | 1248 +++++++++++++++++++++++ ssh/transient-hostkey-cache.c | 126 +++ ssh/transport2.c | 2181 +++++++++++++++++++++++++++++++++++++++++ ssh/transport2.h | 245 +++++ ssh/ttymode-list.h | 179 ++++ ssh/userauth2-client.c | 1961 ++++++++++++++++++++++++++++++++++++ ssh/userauth2-server.c | 377 +++++++ ssh/verstring.c | 628 ++++++++++++ ssh/x11fwd.c | 639 ++++++++++++ ssh/zlib.c | 1253 +++++++++++++++++++++++ ssh1bpp.c | 387 -------- ssh1censor.c | 76 -- ssh1connection-client.c | 547 ----------- ssh1connection-server.c | 365 ------- ssh1connection.c | 815 --------------- ssh1connection.h | 123 --- ssh1login-server.c | 447 --------- ssh1login.c | 1242 ----------------------- ssh2bpp-bare.c | 204 ---- ssh2bpp.c | 982 ------------------- ssh2censor.c | 107 -- ssh2connection-client.c | 505 ---------- ssh2connection-server.c | 306 ------ ssh2connection.c | 1745 --------------------------------- ssh2connection.h | 235 ----- ssh2kex-client.c | 930 ------------------ ssh2kex-server.c | 330 ------- ssh2transhk.c | 126 --- ssh2transport.c | 2181 ----------------------------------------- ssh2transport.h | 245 ----- ssh2userauth-server.c | 377 ------- ssh2userauth.c | 1961 ------------------------------------ sshbpp.h | 179 ---- sshchan.h | 316 ------ sshcommon.c | 946 ------------------ sshcrcda.c | 171 ---- sshgss.h | 217 ---- sshgssc.c | 288 ------ sshgssc.h | 24 - sshnogss.c | 19 - sshppl.h | 174 ---- sshserver.c | 591 ----------- sshserver.h | 143 --- sshshare.c | 2180 ---------------------------------------- sshsignals.h | 53 - sshttymodes.h | 179 ---- sshverstring.c | 628 ------------ sshzlib.c | 1253 ----------------------- testzlib.c | 2 +- unix/CMakeLists.txt | 10 +- unix/platform.h | 2 +- unix/uxgss.c | 6 +- unix/uxpsusan.c | 2 +- unix/uxpty.c | 8 +- unix/uxserver.c | 2 +- unix/uxsftpserver.c | 4 +- utils/sshutils.c | 2 +- windows/CMakeLists.txt | 2 +- windows/platform.h | 4 +- windows/wingss.c | 6 +- x11disp.c | 2 +- x11fwd.c | 639 ------------ 128 files changed, 30354 insertions(+), 30329 deletions(-) delete mode 100644 agentf.c delete mode 100644 mainchan.c delete mode 100644 noshare.c delete mode 100644 pgssapi.c delete mode 100644 pgssapi.h delete mode 100644 portfwd.c delete mode 100644 scpserver.c delete mode 100644 sesschan.c delete mode 100644 sftp.c delete mode 100644 sftp.h delete mode 100644 sftpcommon.c delete mode 100644 sftpserver.c delete mode 100644 ssh.c create mode 100644 ssh/CMakeLists.txt create mode 100644 ssh/agentf.c create mode 100644 ssh/bpp-bare.c create mode 100644 ssh/bpp.h create mode 100644 ssh/bpp1.c create mode 100644 ssh/bpp2.c create mode 100644 ssh/censor1.c create mode 100644 ssh/censor2.c create mode 100644 ssh/channel.h create mode 100644 ssh/common.c create mode 100644 ssh/connection1-client.c create mode 100644 ssh/connection1-server.c create mode 100644 ssh/connection1.c create mode 100644 ssh/connection1.h create mode 100644 ssh/connection2-client.c create mode 100644 ssh/connection2-server.c create mode 100644 ssh/connection2.c create mode 100644 ssh/connection2.h create mode 100644 ssh/crc-attack-detector.c create mode 100644 ssh/gss.h create mode 100644 ssh/gssc.c create mode 100644 ssh/gssc.h create mode 100644 ssh/kex2-client.c create mode 100644 ssh/kex2-server.c create mode 100644 ssh/login1-server.c create mode 100644 ssh/login1.c create mode 100644 ssh/mainchan.c create mode 100644 ssh/nogss.c create mode 100644 ssh/nosharing.c create mode 100644 ssh/pgssapi.c create mode 100644 ssh/pgssapi.h create mode 100644 ssh/portfwd.c create mode 100644 ssh/ppl.h create mode 100644 ssh/scpserver.c create mode 100644 ssh/server.c create mode 100644 ssh/server.h create mode 100644 ssh/sesschan.c create mode 100644 ssh/sftp.c create mode 100644 ssh/sftp.h create mode 100644 ssh/sftpcommon.c create mode 100644 ssh/sftpserver.c create mode 100644 ssh/sharing.c create mode 100644 ssh/signal-list.h create mode 100644 ssh/ssh.c create mode 100644 ssh/transient-hostkey-cache.c create mode 100644 ssh/transport2.c create mode 100644 ssh/transport2.h create mode 100644 ssh/ttymode-list.h create mode 100644 ssh/userauth2-client.c create mode 100644 ssh/userauth2-server.c create mode 100644 ssh/verstring.c create mode 100644 ssh/x11fwd.c create mode 100644 ssh/zlib.c delete mode 100644 ssh1bpp.c delete mode 100644 ssh1censor.c delete mode 100644 ssh1connection-client.c delete mode 100644 ssh1connection-server.c delete mode 100644 ssh1connection.c delete mode 100644 ssh1connection.h delete mode 100644 ssh1login-server.c delete mode 100644 ssh1login.c delete mode 100644 ssh2bpp-bare.c delete mode 100644 ssh2bpp.c delete mode 100644 ssh2censor.c delete mode 100644 ssh2connection-client.c delete mode 100644 ssh2connection-server.c delete mode 100644 ssh2connection.c delete mode 100644 ssh2connection.h delete mode 100644 ssh2kex-client.c delete mode 100644 ssh2kex-server.c delete mode 100644 ssh2transhk.c delete mode 100644 ssh2transport.c delete mode 100644 ssh2transport.h delete mode 100644 ssh2userauth-server.c delete mode 100644 ssh2userauth.c delete mode 100644 sshbpp.h delete mode 100644 sshchan.h delete mode 100644 sshcommon.c delete mode 100644 sshcrcda.c delete mode 100644 sshgss.h delete mode 100644 sshgssc.c delete mode 100644 sshgssc.h delete mode 100644 sshnogss.c delete mode 100644 sshppl.h delete mode 100644 sshserver.c delete mode 100644 sshserver.h delete mode 100644 sshshare.c delete mode 100644 sshsignals.h delete mode 100644 sshttymodes.h delete mode 100644 sshverstring.c delete mode 100644 sshzlib.c delete mode 100644 x11fwd.c diff --git a/CMakeLists.txt b/CMakeLists.txt index d3be22b5..3b28c065 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,38 +44,12 @@ add_library(guiterminal STATIC add_library(noterminal STATIC noterm.c ldisc.c) -add_library(sshcommon OBJECT - ssh1bpp.c ssh1censor.c - ssh1connection.c ssh1login.c ssh2bpp-bare.c ssh2bpp.c ssh2censor.c - ssh2connection.c ssh2transhk.c ssh2transport.c ssh2userauth.c - sshcommon.c sshcrcda.c sshgssc.c sshpubk.c sshrand.c - sshverstring.c sshzlib.c - pgssapi.c portfwd.c x11fwd.c) - -add_library(sftpcommon OBJECT - sftpcommon.c) - add_library(all-backends OBJECT pinger.c) -add_library(sshclient STATIC - ssh1connection-client.c ssh2connection-client.c ssh2kex-client.c - sshshare.c ssh.c - mainchan.c agentf.c - $ - $ - $) - -add_library(sshserver STATIC - ssh1connection-server.c ssh1login-server.c ssh2connection-server.c - ssh2kex-server.c ssh2userauth-server.c sshserver.c - sesschan.c - sftpserver.c - $ - $) - add_library(sftpclient STATIC - psftpcommon.c sftp.c $) + psftpcommon.c) +add_subdirectory(ssh) add_library(otherbackends STATIC telnet.c rlogin.c raw.c supdup.c @@ -83,7 +57,7 @@ add_library(otherbackends STATIC $) add_executable(testcrypt - testcrypt.c sshpubk.c sshcrcda.c) + testcrypt.c sshpubk.c ssh/crc-attack-detector.c) target_link_libraries(testcrypt keygen crypto utils ${platform_libraries}) diff --git a/agentf.c b/agentf.c deleted file mode 100644 index dc5bec01..00000000 --- a/agentf.c +++ /dev/null @@ -1,249 +0,0 @@ -/* - * SSH agent forwarding. - */ - -#include -#include -#include - -#include "putty.h" -#include "ssh.h" -#include "pageant.h" -#include "sshchan.h" - -typedef struct agentf { - SshChannel *c; - bufchain inbuffer; - agent_pending_query *pending; - bool input_wanted; - bool rcvd_eof; - - Channel chan; -} agentf; - -static void agentf_got_response(agentf *af, void *reply, int replylen) -{ - af->pending = NULL; - - if (!reply) { - /* The real agent didn't send any kind of reply at all for - * some reason, so fake an SSH_AGENT_FAILURE. */ - reply = "\0\0\0\1\5"; - replylen = 5; - } - - sshfwd_write(af->c, reply, replylen); -} - -static void agentf_callback(void *vctx, void *reply, int replylen); - -static void agentf_try_forward(agentf *af) -{ - size_t datalen, length; - strbuf *message; - unsigned char msglen[4]; - void *reply; - int replylen; - - /* - * Don't try to parallelise agent requests. Wait for each one to - * return before attempting the next. - */ - if (af->pending) - return; - - /* - * If the outgoing side of the channel connection is currently - * throttled, don't submit any new forwarded requests to the real - * agent. This causes the input side of the agent forwarding not - * to be emptied, exerting the required back-pressure on the - * remote client, and encouraging it to read our responses before - * sending too many more requests. - */ - if (!af->input_wanted) - return; - - while (1) { - /* - * Try to extract a complete message from the input buffer. - */ - datalen = bufchain_size(&af->inbuffer); - if (datalen < 4) - break; /* not even a length field available yet */ - - bufchain_fetch(&af->inbuffer, msglen, 4); - length = GET_32BIT_MSB_FIRST(msglen); - - if (length > AGENT_MAX_MSGLEN-4) { - /* - * If the remote has sent a message that's just _too_ - * long, we should reject it in advance of seeing the rest - * of the incoming message, and also close the connection - * for good measure (which avoids us having to faff about - * with carefully ignoring just the right number of bytes - * from the overlong message). - */ - agentf_got_response(af, NULL, 0); - sshfwd_write_eof(af->c); - return; - } - - if (length > datalen - 4) - break; /* a whole message is not yet available */ - - bufchain_consume(&af->inbuffer, 4); - - message = strbuf_new_for_agent_query(); - bufchain_fetch_consume( - &af->inbuffer, strbuf_append(message, length), length); - af->pending = agent_query( - message, &reply, &replylen, agentf_callback, af); - strbuf_free(message); - - if (af->pending) - return; /* agent_query promised to reply in due course */ - - /* - * If the agent gave us an answer immediately, pass it - * straight on and go round this loop again. - */ - agentf_got_response(af, reply, replylen); - sfree(reply); - } - - /* - * If we get here (i.e. we left the above while loop via 'break' - * rather than 'return'), that means we've determined that the - * input buffer for the agent forwarding connection doesn't - * contain a complete request. - * - * So if there's potentially more data to come, we can return now, - * and wait for the remote client to send it. But if the remote - * has sent EOF, it would be a mistake to do that, because we'd be - * waiting a long time. So this is the moment to check for EOF, - * and respond appropriately. - */ - if (af->rcvd_eof) - sshfwd_write_eof(af->c); -} - -static void agentf_callback(void *vctx, void *reply, int replylen) -{ - agentf *af = (agentf *)vctx; - - agentf_got_response(af, reply, replylen); - sfree(reply); - - /* - * Now try to extract and send further messages from the channel's - * input-side buffer. - */ - agentf_try_forward(af); -} - -static void agentf_free(Channel *chan); -static size_t agentf_send(Channel *chan, bool is_stderr, const void *, size_t); -static void agentf_send_eof(Channel *chan); -static char *agentf_log_close_msg(Channel *chan); -static void agentf_set_input_wanted(Channel *chan, bool wanted); - -static const ChannelVtable agentf_channelvt = { - .free = agentf_free, - .open_confirmation = chan_remotely_opened_confirmation, - .open_failed = chan_remotely_opened_failure, - .send = agentf_send, - .send_eof = agentf_send_eof, - .set_input_wanted = agentf_set_input_wanted, - .log_close_msg = agentf_log_close_msg, - .want_close = chan_default_want_close, - .rcvd_exit_status = chan_no_exit_status, - .rcvd_exit_signal = chan_no_exit_signal, - .rcvd_exit_signal_numeric = chan_no_exit_signal_numeric, - .run_shell = chan_no_run_shell, - .run_command = chan_no_run_command, - .run_subsystem = chan_no_run_subsystem, - .enable_x11_forwarding = chan_no_enable_x11_forwarding, - .enable_agent_forwarding = chan_no_enable_agent_forwarding, - .allocate_pty = chan_no_allocate_pty, - .set_env = chan_no_set_env, - .send_break = chan_no_send_break, - .send_signal = chan_no_send_signal, - .change_window_size = chan_no_change_window_size, - .request_response = chan_no_request_response, -}; - -Channel *agentf_new(SshChannel *c) -{ - agentf *af = snew(agentf); - af->c = c; - af->chan.vt = &agentf_channelvt; - af->chan.initial_fixed_window_size = 0; - af->rcvd_eof = false; - bufchain_init(&af->inbuffer); - af->pending = NULL; - af->input_wanted = true; - return &af->chan; -} - -static void agentf_free(Channel *chan) -{ - assert(chan->vt == &agentf_channelvt); - agentf *af = container_of(chan, agentf, chan); - - if (af->pending) - agent_cancel_query(af->pending); - bufchain_clear(&af->inbuffer); - sfree(af); -} - -static size_t agentf_send(Channel *chan, bool is_stderr, - const void *data, size_t length) -{ - assert(chan->vt == &agentf_channelvt); - agentf *af = container_of(chan, agentf, chan); - bufchain_add(&af->inbuffer, data, length); - agentf_try_forward(af); - - /* - * We exert back-pressure on an agent forwarding client if and - * only if we're waiting for the response to an asynchronous agent - * request. This prevents the client running out of window while - * receiving the _first_ message, but means that if any message - * takes time to process, the client will be discouraged from - * sending an endless stream of further ones after it. - */ - return (af->pending ? bufchain_size(&af->inbuffer) : 0); -} - -static void agentf_send_eof(Channel *chan) -{ - assert(chan->vt == &agentf_channelvt); - agentf *af = container_of(chan, agentf, chan); - - af->rcvd_eof = true; - - /* Call try_forward, which will respond to the EOF now if - * appropriate, or wait until the queue of outstanding requests is - * dealt with if not. */ - agentf_try_forward(af); -} - -static char *agentf_log_close_msg(Channel *chan) -{ - return dupstr("Agent-forwarding connection closed"); -} - -static void agentf_set_input_wanted(Channel *chan, bool wanted) -{ - assert(chan->vt == &agentf_channelvt); - agentf *af = container_of(chan, agentf, chan); - - af->input_wanted = wanted; - - /* Agent forwarding channels are buffer-managed by not asking the - * agent questions if the SSH channel isn't accepting input. So if - * it's started again, we should ask a question if we have one - * pending.. */ - if (wanted) - agentf_try_forward(af); -} diff --git a/crypto/diffie-hellman.c b/crypto/diffie-hellman.c index b3756120..634ef297 100644 --- a/crypto/diffie-hellman.c +++ b/crypto/diffie-hellman.c @@ -96,7 +96,7 @@ const ssh_kexes ssh_diffiehellman_gex = { lenof(gex_list), gex_list }; * Kerberos v5. * * (The same encoded OID, minus the two-byte DER header, is defined in - * pgssapi.c as GSS_MECH_KRB5.) + * ssh/pgssapi.c as GSS_MECH_KRB5.) */ #define GSS_KRB5_OID_HASH "toWM5Slw5Ew8Mqkay+al2g==" diff --git a/mainchan.c b/mainchan.c deleted file mode 100644 index 8653ad02..00000000 --- a/mainchan.c +++ /dev/null @@ -1,538 +0,0 @@ -/* - * SSH main session channel handling. - */ - -#include -#include -#include - -#include "putty.h" -#include "ssh.h" -#include "sshppl.h" -#include "sshchan.h" - -static void mainchan_free(Channel *chan); -static void mainchan_open_confirmation(Channel *chan); -static void mainchan_open_failure(Channel *chan, const char *errtext); -static size_t mainchan_send( - Channel *chan, bool is_stderr, const void *, size_t); -static void mainchan_send_eof(Channel *chan); -static void mainchan_set_input_wanted(Channel *chan, bool wanted); -static char *mainchan_log_close_msg(Channel *chan); -static bool mainchan_rcvd_exit_status(Channel *chan, int status); -static bool mainchan_rcvd_exit_signal( - Channel *chan, ptrlen signame, bool core_dumped, ptrlen msg); -static bool mainchan_rcvd_exit_signal_numeric( - Channel *chan, int signum, bool core_dumped, ptrlen msg); -static void mainchan_request_response(Channel *chan, bool success); - -static const ChannelVtable mainchan_channelvt = { - .free = mainchan_free, - .open_confirmation = mainchan_open_confirmation, - .open_failed = mainchan_open_failure, - .send = mainchan_send, - .send_eof = mainchan_send_eof, - .set_input_wanted = mainchan_set_input_wanted, - .log_close_msg = mainchan_log_close_msg, - .want_close = chan_default_want_close, - .rcvd_exit_status = mainchan_rcvd_exit_status, - .rcvd_exit_signal = mainchan_rcvd_exit_signal, - .rcvd_exit_signal_numeric = mainchan_rcvd_exit_signal_numeric, - .run_shell = chan_no_run_shell, - .run_command = chan_no_run_command, - .run_subsystem = chan_no_run_subsystem, - .enable_x11_forwarding = chan_no_enable_x11_forwarding, - .enable_agent_forwarding = chan_no_enable_agent_forwarding, - .allocate_pty = chan_no_allocate_pty, - .set_env = chan_no_set_env, - .send_break = chan_no_send_break, - .send_signal = chan_no_send_signal, - .change_window_size = chan_no_change_window_size, - .request_response = mainchan_request_response, -}; - -typedef enum MainChanType { - MAINCHAN_SESSION, MAINCHAN_DIRECT_TCPIP -} MainChanType; - -struct mainchan { - SshChannel *sc; - Conf *conf; - PacketProtocolLayer *ppl; - ConnectionLayer *cl; - - MainChanType type; - bool is_simple; - - bool req_x11, req_agent, req_pty, req_cmd_primary, req_cmd_fallback; - int n_req_env, n_env_replies, n_env_fails; - bool eof_pending, eof_sent, got_pty, ready; - - int term_width, term_height; - - Channel chan; -}; - -mainchan *mainchan_new( - PacketProtocolLayer *ppl, ConnectionLayer *cl, Conf *conf, - int term_width, int term_height, bool is_simple, SshChannel **sc_out) -{ - mainchan *mc; - - if (conf_get_bool(conf, CONF_ssh_no_shell)) - return NULL; /* no main channel at all */ - - mc = snew(mainchan); - memset(mc, 0, sizeof(mainchan)); - mc->ppl = ppl; - mc->cl = cl; - mc->conf = conf_copy(conf); - mc->term_width = term_width; - mc->term_height = term_height; - mc->is_simple = is_simple; - - mc->sc = NULL; - mc->chan.vt = &mainchan_channelvt; - mc->chan.initial_fixed_window_size = 0; - - if (*conf_get_str(mc->conf, CONF_ssh_nc_host)) { - const char *host = conf_get_str(mc->conf, CONF_ssh_nc_host); - int port = conf_get_int(mc->conf, CONF_ssh_nc_port); - - mc->sc = ssh_lportfwd_open(cl, host, port, "main channel", - NULL, &mc->chan); - mc->type = MAINCHAN_DIRECT_TCPIP; - } else { - mc->sc = ssh_session_open(cl, &mc->chan); - mc->type = MAINCHAN_SESSION; - } - - if (sc_out) *sc_out = mc->sc; - return mc; -} - -static void mainchan_free(Channel *chan) -{ - assert(chan->vt == &mainchan_channelvt); - mainchan *mc = container_of(chan, mainchan, chan); - conf_free(mc->conf); - sfree(mc); -} - -static void mainchan_try_fallback_command(mainchan *mc); -static void mainchan_ready(mainchan *mc); - -static void mainchan_open_confirmation(Channel *chan) -{ - mainchan *mc = container_of(chan, mainchan, chan); - PacketProtocolLayer *ppl = mc->ppl; /* for ppl_logevent */ - - seat_update_specials_menu(mc->ppl->seat); - ppl_logevent("Opened main channel"); - - if (mc->is_simple) - sshfwd_hint_channel_is_simple(mc->sc); - - if (mc->type == MAINCHAN_SESSION) { - /* - * Send the CHANNEL_REQUESTS for the main session channel. - */ - char *key, *val, *cmd; - struct X11Display *x11disp; - struct X11FakeAuth *x11auth; - bool retry_cmd_now = false; - - if (conf_get_bool(mc->conf, CONF_x11_forward)) { - char *x11_setup_err; - if ((x11disp = x11_setup_display( - conf_get_str(mc->conf, CONF_x11_display), - mc->conf, &x11_setup_err)) == NULL) { - ppl_logevent("X11 forwarding not enabled: unable to" - " initialise X display: %s", x11_setup_err); - sfree(x11_setup_err); - } else { - x11auth = ssh_add_x11_display( - mc->cl, conf_get_int(mc->conf, CONF_x11_auth), x11disp); - - sshfwd_request_x11_forwarding( - mc->sc, true, x11auth->protoname, x11auth->datastring, - x11disp->screennum, false); - mc->req_x11 = true; - } - } - - if (ssh_agent_forwarding_permitted(mc->cl)) { - sshfwd_request_agent_forwarding(mc->sc, true); - mc->req_agent = true; - } - - if (!conf_get_bool(mc->conf, CONF_nopty)) { - sshfwd_request_pty( - mc->sc, true, mc->conf, mc->term_width, mc->term_height); - mc->req_pty = true; - } - - for (val = conf_get_str_strs(mc->conf, CONF_environmt, NULL, &key); - val != NULL; - val = conf_get_str_strs(mc->conf, CONF_environmt, key, &key)) { - sshfwd_send_env_var(mc->sc, true, key, val); - mc->n_req_env++; - } - if (mc->n_req_env) - ppl_logevent("Sent %d environment variables", mc->n_req_env); - - cmd = conf_get_str(mc->conf, CONF_remote_cmd); - if (conf_get_bool(mc->conf, CONF_ssh_subsys)) { - retry_cmd_now = !sshfwd_start_subsystem(mc->sc, true, cmd); - } else if (*cmd) { - sshfwd_start_command(mc->sc, true, cmd); - } else { - sshfwd_start_shell(mc->sc, true); - } - - if (retry_cmd_now) - mainchan_try_fallback_command(mc); - else - mc->req_cmd_primary = true; - - } else { - ssh_set_ldisc_option(mc->cl, LD_ECHO, true); - ssh_set_ldisc_option(mc->cl, LD_EDIT, true); - mainchan_ready(mc); - } -} - -static void mainchan_try_fallback_command(mainchan *mc) -{ - const char *cmd = conf_get_str(mc->conf, CONF_remote_cmd2); - if (conf_get_bool(mc->conf, CONF_ssh_subsys2)) { - sshfwd_start_subsystem(mc->sc, true, cmd); - } else { - sshfwd_start_command(mc->sc, true, cmd); - } - mc->req_cmd_fallback = true; -} - -static void mainchan_request_response(Channel *chan, bool success) -{ - assert(chan->vt == &mainchan_channelvt); - mainchan *mc = container_of(chan, mainchan, chan); - PacketProtocolLayer *ppl = mc->ppl; /* for ppl_logevent */ - - if (mc->req_x11) { - mc->req_x11 = false; - - if (success) { - ppl_logevent("X11 forwarding enabled"); - ssh_enable_x_fwd(mc->cl); - } else { - ppl_logevent("X11 forwarding refused"); - } - return; - } - - if (mc->req_agent) { - mc->req_agent = false; - - if (success) { - ppl_logevent("Agent forwarding enabled"); - } else { - ppl_logevent("Agent forwarding refused"); - } - return; - } - - if (mc->req_pty) { - mc->req_pty = false; - - if (success) { - ppl_logevent("Allocated pty"); - mc->got_pty = true; - } else { - ppl_logevent("Server refused to allocate pty"); - ppl_printf("Server refused to allocate pty\r\n"); - ssh_set_ldisc_option(mc->cl, LD_ECHO, true); - ssh_set_ldisc_option(mc->cl, LD_EDIT, true); - } - return; - } - - if (mc->n_env_replies < mc->n_req_env) { - int j = mc->n_env_replies++; - if (!success) { - ppl_logevent("Server refused to set environment variable %s", - conf_get_str_nthstrkey(mc->conf, - CONF_environmt, j)); - mc->n_env_fails++; - } - - if (mc->n_env_replies == mc->n_req_env) { - if (mc->n_env_fails == 0) { - ppl_logevent("All environment variables successfully set"); - } else if (mc->n_env_fails == mc->n_req_env) { - ppl_logevent("All environment variables refused"); - ppl_printf("Server refused to set environment " - "variables\r\n"); - } else { - ppl_printf("Server refused to set all environment " - "variables\r\n"); - } - } - return; - } - - if (mc->req_cmd_primary) { - mc->req_cmd_primary = false; - - if (success) { - ppl_logevent("Started a shell/command"); - mainchan_ready(mc); - } else if (*conf_get_str(mc->conf, CONF_remote_cmd2)) { - ppl_logevent("Primary command failed; attempting fallback"); - mainchan_try_fallback_command(mc); - } else { - /* - * If there's no remote_cmd2 configured, then we have no - * fallback command, so we've run out of options. - */ - ssh_sw_abort(mc->ppl->ssh, - "Server refused to start a shell/command"); - } - return; - } - - if (mc->req_cmd_fallback) { - mc->req_cmd_fallback = false; - - if (success) { - ppl_logevent("Started a shell/command"); - ssh_got_fallback_cmd(mc->ppl->ssh); - mainchan_ready(mc); - } else { - ssh_sw_abort(mc->ppl->ssh, - "Server refused to start a shell/command"); - } - return; - } -} - -static void mainchan_ready(mainchan *mc) -{ - mc->ready = true; - - ssh_set_wants_user_input(mc->cl, true); - ssh_ppl_got_user_input(mc->ppl); /* in case any is already queued */ - - /* If an EOF arrived before we were ready, handle it now. */ - if (mc->eof_pending) { - mc->eof_pending = false; - mainchan_special_cmd(mc, SS_EOF, 0); - } - - ssh_ldisc_update(mc->ppl->ssh); - queue_idempotent_callback(&mc->ppl->ic_process_queue); -} - -static void mainchan_open_failure(Channel *chan, const char *errtext) -{ - assert(chan->vt == &mainchan_channelvt); - mainchan *mc = container_of(chan, mainchan, chan); - - ssh_sw_abort_deferred(mc->ppl->ssh, - "Server refused to open main channel: %s", errtext); -} - -static size_t mainchan_send(Channel *chan, bool is_stderr, - const void *data, size_t length) -{ - assert(chan->vt == &mainchan_channelvt); - mainchan *mc = container_of(chan, mainchan, chan); - return seat_output(mc->ppl->seat, is_stderr, data, length); -} - -static void mainchan_send_eof(Channel *chan) -{ - assert(chan->vt == &mainchan_channelvt); - mainchan *mc = container_of(chan, mainchan, chan); - PacketProtocolLayer *ppl = mc->ppl; /* for ppl_logevent */ - - if (!mc->eof_sent && (seat_eof(mc->ppl->seat) || mc->got_pty)) { - /* - * Either seat_eof told us that the front end wants us to - * close the outgoing side of the connection as soon as we see - * EOF from the far end, or else we've unilaterally decided to - * do that because we've allocated a remote pty and hence EOF - * isn't a particularly meaningful concept. - */ - sshfwd_write_eof(mc->sc); - ppl_logevent("Sent EOF message"); - mc->eof_sent = true; - ssh_set_wants_user_input(mc->cl, false); /* stop reading from stdin */ - } -} - -static void mainchan_set_input_wanted(Channel *chan, bool wanted) -{ - assert(chan->vt == &mainchan_channelvt); - mainchan *mc = container_of(chan, mainchan, chan); - - /* - * This is the main channel of the SSH session, i.e. the one tied - * to the standard input (or GUI) of the primary SSH client user - * interface. So ssh->send_ok is how we control whether we're - * reading from that input. - */ - ssh_set_wants_user_input(mc->cl, wanted); -} - -static char *mainchan_log_close_msg(Channel *chan) -{ - return dupstr("Main session channel closed"); -} - -static bool mainchan_rcvd_exit_status(Channel *chan, int status) -{ - assert(chan->vt == &mainchan_channelvt); - mainchan *mc = container_of(chan, mainchan, chan); - PacketProtocolLayer *ppl = mc->ppl; /* for ppl_logevent */ - - ssh_got_exitcode(mc->ppl->ssh, status); - ppl_logevent("Session sent command exit status %d", status); - return true; -} - -static void mainchan_log_exit_signal_common( - mainchan *mc, const char *sigdesc, - bool core_dumped, ptrlen msg) -{ - PacketProtocolLayer *ppl = mc->ppl; /* for ppl_logevent */ - - const char *core_msg = core_dumped ? " (core dumped)" : ""; - const char *msg_pre = (msg.len ? " (" : ""); - const char *msg_post = (msg.len ? ")" : ""); - ppl_logevent("Session exited on %s%s%s%.*s%s", - sigdesc, core_msg, msg_pre, PTRLEN_PRINTF(msg), msg_post); -} - -static bool mainchan_rcvd_exit_signal( - Channel *chan, ptrlen signame, bool core_dumped, ptrlen msg) -{ - assert(chan->vt == &mainchan_channelvt); - mainchan *mc = container_of(chan, mainchan, chan); - int exitcode; - char *signame_str; - - /* - * Translate the signal description back into a locally meaningful - * number, or 128 if the string didn't match any we recognise. - */ - exitcode = 128; - - #define SIGNAL_SUB(s) \ - if (ptrlen_eq_string(signame, #s)) \ - exitcode = 128 + SIG ## s; - #define SIGNAL_MAIN(s, text) SIGNAL_SUB(s) - #define SIGNALS_LOCAL_ONLY - #include "sshsignals.h" - #undef SIGNAL_SUB - #undef SIGNAL_MAIN - #undef SIGNALS_LOCAL_ONLY - - ssh_got_exitcode(mc->ppl->ssh, exitcode); - if (exitcode == 128) - signame_str = dupprintf("unrecognised signal \"%.*s\"", - PTRLEN_PRINTF(signame)); - else - signame_str = dupprintf("signal SIG%.*s", PTRLEN_PRINTF(signame)); - mainchan_log_exit_signal_common(mc, signame_str, core_dumped, msg); - sfree(signame_str); - return true; -} - -static bool mainchan_rcvd_exit_signal_numeric( - Channel *chan, int signum, bool core_dumped, ptrlen msg) -{ - assert(chan->vt == &mainchan_channelvt); - mainchan *mc = container_of(chan, mainchan, chan); - char *signum_str; - - ssh_got_exitcode(mc->ppl->ssh, 128 + signum); - signum_str = dupprintf("signal %d", signum); - mainchan_log_exit_signal_common(mc, signum_str, core_dumped, msg); - sfree(signum_str); - return true; -} - -void mainchan_get_specials( - mainchan *mc, add_special_fn_t add_special, void *ctx) -{ - /* FIXME: this _does_ depend on whether these services are supported */ - - add_special(ctx, "Break", SS_BRK, 0); - - #define SIGNAL_MAIN(name, desc) \ - add_special(ctx, "SIG" #name " (" desc ")", SS_SIG ## name, 0); - #define SIGNAL_SUB(name) - #include "sshsignals.h" - #undef SIGNAL_MAIN - #undef SIGNAL_SUB - - add_special(ctx, "More signals", SS_SUBMENU, 0); - - #define SIGNAL_MAIN(name, desc) - #define SIGNAL_SUB(name) \ - add_special(ctx, "SIG" #name, SS_SIG ## name, 0); - #include "sshsignals.h" - #undef SIGNAL_MAIN - #undef SIGNAL_SUB - - add_special(ctx, NULL, SS_EXITMENU, 0); -} - -static const char *ssh_signal_lookup(SessionSpecialCode code) -{ - #define SIGNAL_SUB(name) \ - if (code == SS_SIG ## name) return #name; - #define SIGNAL_MAIN(name, desc) SIGNAL_SUB(name) - #include "sshsignals.h" - #undef SIGNAL_MAIN - #undef SIGNAL_SUB - - /* If none of those clauses matched, fail lookup. */ - return NULL; -} - -void mainchan_special_cmd(mainchan *mc, SessionSpecialCode code, int arg) -{ - PacketProtocolLayer *ppl = mc->ppl; /* for ppl_logevent */ - const char *signame; - - if (code == SS_EOF) { - if (!mc->ready) { - /* - * Buffer the EOF to send as soon as the main channel is - * fully set up. - */ - mc->eof_pending = true; - } else if (!mc->eof_sent) { - sshfwd_write_eof(mc->sc); - mc->eof_sent = true; - } - } else if (code == SS_BRK) { - sshfwd_send_serial_break( - mc->sc, false, 0 /* default break length */); - } else if ((signame = ssh_signal_lookup(code)) != NULL) { - /* It's a signal. */ - sshfwd_send_signal(mc->sc, false, signame); - ppl_logevent("Sent signal SIG%s", signame); - } -} - -void mainchan_terminal_size(mainchan *mc, int width, int height) -{ - mc->term_width = width; - mc->term_height = height; - - if (mc->req_pty || mc->got_pty) - sshfwd_send_terminal_size_change(mc->sc, width, height); -} diff --git a/mpunsafe.c b/mpunsafe.c index a6130018..f33532c4 100644 --- a/mpunsafe.c +++ b/mpunsafe.c @@ -10,7 +10,7 @@ #include "crypto/mpint_i.h" /* - * This global symbol is also defined in ssh2kex-client.c, to ensure + * This global symbol is also defined in ssh/kex2-client.c, to ensure * that these unsafe non-constant-time mp_int functions can't end up * accidentally linked in to any PuTTY tool that actually makes an SSH * client connection. diff --git a/noshare.c b/noshare.c deleted file mode 100644 index c45634c5..00000000 --- a/noshare.c +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Stub implementation of SSH connection-sharing IPC, for any - * platform which can't support it at all. - */ - -#include -#include -#include - -#include "tree234.h" -#include "putty.h" -#include "ssh.h" -#include "network.h" - -int platform_ssh_share(const char *name, Conf *conf, - Plug *downplug, Plug *upplug, Socket **sock, - char **logtext, char **ds_err, char **us_err, - bool can_upstream, bool can_downstream) -{ - return SHARE_NONE; -} - -void platform_ssh_share_cleanup(const char *name) -{ -} diff --git a/pgssapi.c b/pgssapi.c deleted file mode 100644 index 9d33220f..00000000 --- a/pgssapi.c +++ /dev/null @@ -1,105 +0,0 @@ -/* 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 */ diff --git a/pgssapi.h b/pgssapi.h deleted file mode 100644 index 53d8cb61..00000000 --- a/pgssapi.h +++ /dev/null @@ -1,333 +0,0 @@ -#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 -typedef gss_OID const_gss_OID; /* for our prototypes below */ -#else /* STATIC_GSSAPI */ - -/******************************************************************************* - * GSSAPI Definitions, taken from RFC 2744 - ******************************************************************************/ - -/* GSSAPI Type Definitions */ -typedef uint32_t 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; -typedef int gss_cred_usage_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 - -/*- - * RFC 2744 Page 86 - * Expiration time of 2^32-1 seconds means infinite lifetime for a - * credential or security context - */ -#define GSS_C_INDEFINITE 0xfffffffful - -/* 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_verify_mic) - (OM_uint32 * /*minor_status*/, - const gss_ctx_id_t /*context_handle*/, - const gss_buffer_t /*message_buffer*/, - const gss_buffer_t /*msg_token*/, - gss_qop_t * /*qop_state*/); - -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*/); - -typedef OM_uint32 (GSS_CC *t_gss_acquire_cred) - (OM_uint32 * /*minor_status*/, - const gss_name_t /*desired_name*/, - OM_uint32 /*time_req*/, - const gss_OID_set /*desired_mechs*/, - gss_cred_usage_t /*cred_usage*/, - gss_cred_id_t * /*output_cred_handle*/, - gss_OID_set * /*actual_mechs*/, - OM_uint32 * /*time_rec*/); - -typedef OM_uint32 (GSS_CC *t_gss_inquire_cred_by_mech) - (OM_uint32 * /*minor_status*/, - const gss_cred_id_t /*cred_handle*/, - const gss_OID /*mech_type*/, - gss_name_t * /*name*/, - OM_uint32 * /*initiator_lifetime*/, - OM_uint32 * /*acceptor_lifetime*/, - gss_cred_usage_t * /*cred_usage*/); - -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_verify_mic verify_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; - t_gss_acquire_cred acquire_cred; - t_gss_inquire_cred_by_mech inquire_cred_by_mech; -}; - -#endif /* NO_GSSAPI */ - -#endif /* PUTTY_PGSSAPI_H */ diff --git a/portfwd.c b/portfwd.c deleted file mode 100644 index f3349b80..00000000 --- a/portfwd.c +++ /dev/null @@ -1,1174 +0,0 @@ -/* - * SSH port forwarding. - */ - -#include -#include -#include - -#include "putty.h" -#include "ssh.h" -#include "sshchan.h" - -/* - * Enumeration of values that live in the 'socks_state' field of - * struct PortForwarding. - */ -typedef enum { - SOCKS_NONE, /* direct connection (no SOCKS, or SOCKS already done) */ - SOCKS_INITIAL, /* don't know if we're SOCKS 4 or 5 yet */ - SOCKS_4, /* expect a SOCKS 4 (or 4A) connection message */ - SOCKS_5_INITIAL, /* expect a SOCKS 5 preliminary message */ - SOCKS_5_CONNECT /* expect a SOCKS 5 connection message */ -} SocksState; - -typedef struct PortForwarding { - SshChannel *c; /* channel structure held by SSH connection layer */ - ConnectionLayer *cl; /* the connection layer itself */ - /* Note that ssh need not be filled in if c is non-NULL */ - Socket *s; - bool input_wanted; - bool ready; - SocksState socks_state; - /* - * `hostname' and `port' are the real hostname and port, once - * we know what we're connecting to. - */ - char *hostname; - int port; - /* - * `socksbuf' is the buffer we use to accumulate the initial SOCKS - * segment of the incoming data, plus anything after that that we - * receive before we're ready to send data to the SSH server. - */ - strbuf *socksbuf; - size_t socksbuf_consumed; - - Plug plug; - Channel chan; -} PortForwarding; - -struct PortListener { - ConnectionLayer *cl; - Socket *s; - bool is_dynamic; - /* - * `hostname' and `port' are the real hostname and port, for - * ordinary forwardings. - */ - char *hostname; - int port; - - Plug plug; -}; - -static struct PortForwarding *new_portfwd_state(void) -{ - struct PortForwarding *pf = snew(struct PortForwarding); - pf->hostname = NULL; - pf->socksbuf = NULL; - return pf; -} - -static void free_portfwd_state(struct PortForwarding *pf) -{ - if (!pf) - return; - sfree(pf->hostname); - if (pf->socksbuf) - strbuf_free(pf->socksbuf); - sfree(pf); -} - -static struct PortListener *new_portlistener_state(void) -{ - struct PortListener *pl = snew(struct PortListener); - pl->hostname = NULL; - return pl; -} - -static void free_portlistener_state(struct PortListener *pl) -{ - if (!pl) - return; - sfree(pl->hostname); - sfree(pl); -} - -static void pfd_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, - const char *error_msg, int error_code) -{ - /* we have to dump these since we have no interface to logging.c */ -} - -static void pfl_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, - const char *error_msg, int error_code) -{ - /* we have to dump these since we have no interface to logging.c */ -} - -static void pfd_close(struct PortForwarding *pf); - -static void pfd_closing(Plug *plug, const char *error_msg, int error_code, - bool calling_back) -{ - struct PortForwarding *pf = - container_of(plug, struct PortForwarding, plug); - - if (error_msg) { - /* - * Socket error. Slam the connection instantly shut. - */ - if (pf->c) { - sshfwd_initiate_close(pf->c, error_msg); - } else { - /* - * We might not have an SSH channel, if a socket error - * occurred during SOCKS negotiation. If not, we must - * clean ourself up without sshfwd_initiate_close's call - * back to pfd_close. - */ - pfd_close(pf); - } - } else { - /* - * Ordinary EOF received on socket. Send an EOF on the SSH - * channel. - */ - if (pf->c) - sshfwd_write_eof(pf->c); - } -} - -static void pfl_terminate(struct PortListener *pl); - -static void pfl_closing(Plug *plug, const char *error_msg, int error_code, - bool calling_back) -{ - struct PortListener *pl = (struct PortListener *) plug; - pfl_terminate(pl); -} - -static SshChannel *wrap_lportfwd_open( - ConnectionLayer *cl, const char *hostname, int port, - Socket *s, Channel *chan) -{ - SocketPeerInfo *pi; - char *description; - SshChannel *toret; - - pi = sk_peer_info(s); - if (pi && pi->log_text) { - description = dupprintf("forwarding from %s", pi->log_text); - } else { - description = dupstr("forwarding"); - } - toret = ssh_lportfwd_open(cl, hostname, port, description, pi, chan); - sk_free_peer_info(pi); - - sfree(description); - return toret; -} - -static char *ipv4_to_string(unsigned ipv4) -{ - return dupprintf("%u.%u.%u.%u", - (ipv4 >> 24) & 0xFF, (ipv4 >> 16) & 0xFF, - (ipv4 >> 8) & 0xFF, (ipv4 ) & 0xFF); -} - -static char *ipv6_to_string(ptrlen ipv6) -{ - const unsigned char *addr = ipv6.ptr; - assert(ipv6.len == 16); - return dupprintf("%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x", - (unsigned)GET_16BIT_MSB_FIRST(addr + 0), - (unsigned)GET_16BIT_MSB_FIRST(addr + 2), - (unsigned)GET_16BIT_MSB_FIRST(addr + 4), - (unsigned)GET_16BIT_MSB_FIRST(addr + 6), - (unsigned)GET_16BIT_MSB_FIRST(addr + 8), - (unsigned)GET_16BIT_MSB_FIRST(addr + 10), - (unsigned)GET_16BIT_MSB_FIRST(addr + 12), - (unsigned)GET_16BIT_MSB_FIRST(addr + 14)); -} - -static void pfd_receive(Plug *plug, int urgent, const char *data, size_t len) -{ - struct PortForwarding *pf = - container_of(plug, struct PortForwarding, plug); - - if (len == 0) - return; - - if (pf->socks_state != SOCKS_NONE) { - BinarySource src[1]; - - /* - * Store all the data we've got in socksbuf. - */ - put_data(pf->socksbuf, data, len); - - /* - * Check the start of socksbuf to see if it's a valid and - * complete message in the SOCKS exchange. - */ - - if (pf->socks_state == SOCKS_INITIAL) { - /* Preliminary: check the first byte of the data (which we - * _must_ have by now) to find out which SOCKS major - * version we're speaking. */ - switch (pf->socksbuf->u[0]) { - case 4: - pf->socks_state = SOCKS_4; - break; - case 5: - pf->socks_state = SOCKS_5_INITIAL; - break; - default: - pfd_close(pf); /* unrecognised version */ - return; - } - } - - BinarySource_BARE_INIT(src, pf->socksbuf->u, pf->socksbuf->len); - get_data(src, pf->socksbuf_consumed); - - while (pf->socks_state != SOCKS_NONE) { - unsigned socks_version, message_type, reserved_byte; - unsigned reply_code, port, ipv4, method; - ptrlen methods; - const char *socks4_hostname; - strbuf *output; - - switch (pf->socks_state) { - case SOCKS_INITIAL: - case SOCKS_NONE: - unreachable("These case values cannot appear"); - - case SOCKS_4: - /* SOCKS 4/4A connect message */ - socks_version = get_byte(src); - message_type = get_byte(src); - - if (get_err(src) == BSE_OUT_OF_DATA) - return; - if (socks_version == 4 && message_type == 1) { - /* CONNECT message */ - bool name_based = false; - - port = get_uint16(src); - ipv4 = get_uint32(src); - if (ipv4 > 0x00000000 && ipv4 < 0x00000100) { - /* - * Addresses in this range indicate the SOCKS 4A - * extension to specify a hostname, which comes - * after the username. - */ - name_based = true; - } - get_asciz(src); /* skip username */ - socks4_hostname = name_based ? get_asciz(src) : NULL; - - if (get_err(src) == BSE_OUT_OF_DATA) - return; - if (get_err(src)) - goto socks4_reject; - - pf->port = port; - if (name_based) { - pf->hostname = dupstr(socks4_hostname); - } else { - pf->hostname = ipv4_to_string(ipv4); - } - - output = strbuf_new(); - put_byte(output, 0); /* reply version */ - put_byte(output, 90); /* SOCKS 4 'request granted' */ - put_uint16(output, 0); /* null port field */ - put_uint32(output, 0); /* null address field */ - sk_write(pf->s, output->u, output->len); - strbuf_free(output); - - pf->socks_state = SOCKS_NONE; - pf->socksbuf_consumed = src->pos; - break; - } - - socks4_reject: - output = strbuf_new(); - put_byte(output, 0); /* reply version */ - put_byte(output, 91); /* SOCKS 4 'request rejected' */ - put_uint16(output, 0); /* null port field */ - put_uint32(output, 0); /* null address field */ - sk_write(pf->s, output->u, output->len); - strbuf_free(output); - pfd_close(pf); - return; - - case SOCKS_5_INITIAL: - /* SOCKS 5 initial method list */ - socks_version = get_byte(src); - methods = get_pstring(src); - - method = 0xFF; /* means 'no usable method found' */ - { - int i; - for (i = 0; i < methods.len; i++) { - if (((const unsigned char *)methods.ptr)[i] == 0 ) { - method = 0; /* no auth */ - break; - } - } - } - - if (get_err(src) == BSE_OUT_OF_DATA) - return; - if (get_err(src)) - method = 0xFF; - - output = strbuf_new(); - put_byte(output, 5); /* SOCKS version */ - put_byte(output, method); /* selected auth method */ - sk_write(pf->s, output->u, output->len); - strbuf_free(output); - - if (method == 0xFF) { - pfd_close(pf); - return; - } - - pf->socks_state = SOCKS_5_CONNECT; - pf->socksbuf_consumed = src->pos; - break; - - case SOCKS_5_CONNECT: - /* SOCKS 5 connect message */ - socks_version = get_byte(src); - message_type = get_byte(src); - reserved_byte = get_byte(src); - - if (socks_version == 5 && message_type == 1 && - reserved_byte == 0) { - - reply_code = 0; /* success */ - - switch (get_byte(src)) { - case 1: /* IPv4 */ - pf->hostname = ipv4_to_string(get_uint32(src)); - break; - case 4: /* IPv6 */ - pf->hostname = ipv6_to_string(get_data(src, 16)); - break; - case 3: /* unresolved domain name */ - pf->hostname = mkstr(get_pstring(src)); - break; - default: - pf->hostname = NULL; - reply_code = 8; /* address type not supported */ - break; - } - - pf->port = get_uint16(src); - } else { - reply_code = 7; /* command not supported */ - } - - if (get_err(src) == BSE_OUT_OF_DATA) - return; - if (get_err(src)) - reply_code = 1; /* general server failure */ - - output = strbuf_new(); - put_byte(output, 5); /* SOCKS version */ - put_byte(output, reply_code); - put_byte(output, 0); /* reserved */ - put_byte(output, 1); /* IPv4 address follows */ - put_uint32(output, 0); /* bound IPv4 address (unused) */ - put_uint16(output, 0); /* bound port number (unused) */ - sk_write(pf->s, output->u, output->len); - strbuf_free(output); - - if (reply_code != 0) { - pfd_close(pf); - return; - } - - pf->socks_state = SOCKS_NONE; - pf->socksbuf_consumed = src->pos; - break; - } - } - - /* - * We come here when we're ready to make an actual - * connection. - */ - - /* - * Freeze the socket until the SSH server confirms the - * connection. - */ - sk_set_frozen(pf->s, true); - - pf->c = wrap_lportfwd_open(pf->cl, pf->hostname, pf->port, pf->s, - &pf->chan); - } - if (pf->ready) - sshfwd_write(pf->c, data, len); -} - -static void pfd_sent(Plug *plug, size_t bufsize) -{ - struct PortForwarding *pf = - container_of(plug, struct PortForwarding, plug); - - if (pf->c) - sshfwd_unthrottle(pf->c, bufsize); -} - -static const PlugVtable PortForwarding_plugvt = { - .log = pfd_log, - .closing = pfd_closing, - .receive = pfd_receive, - .sent = pfd_sent, -}; - -static void pfd_chan_free(Channel *chan); -static void pfd_open_confirmation(Channel *chan); -static void pfd_open_failure(Channel *chan, const char *errtext); -static size_t pfd_send( - Channel *chan, bool is_stderr, const void *data, size_t len); -static void pfd_send_eof(Channel *chan); -static void pfd_set_input_wanted(Channel *chan, bool wanted); -static char *pfd_log_close_msg(Channel *chan); - -static const ChannelVtable PortForwarding_channelvt = { - .free = pfd_chan_free, - .open_confirmation = pfd_open_confirmation, - .open_failed = pfd_open_failure, - .send = pfd_send, - .send_eof = pfd_send_eof, - .set_input_wanted = pfd_set_input_wanted, - .log_close_msg = pfd_log_close_msg, - .want_close = chan_default_want_close, - .rcvd_exit_status = chan_no_exit_status, - .rcvd_exit_signal = chan_no_exit_signal, - .rcvd_exit_signal_numeric = chan_no_exit_signal_numeric, - .run_shell = chan_no_run_shell, - .run_command = chan_no_run_command, - .run_subsystem = chan_no_run_subsystem, - .enable_x11_forwarding = chan_no_enable_x11_forwarding, - .enable_agent_forwarding = chan_no_enable_agent_forwarding, - .allocate_pty = chan_no_allocate_pty, - .set_env = chan_no_set_env, - .send_break = chan_no_send_break, - .send_signal = chan_no_send_signal, - .change_window_size = chan_no_change_window_size, - .request_response = chan_no_request_response, -}; - -Channel *portfwd_raw_new(ConnectionLayer *cl, Plug **plug, bool start_ready) -{ - struct PortForwarding *pf; - - pf = new_portfwd_state(); - pf->plug.vt = &PortForwarding_plugvt; - pf->chan.initial_fixed_window_size = 0; - pf->chan.vt = &PortForwarding_channelvt; - pf->input_wanted = true; - - pf->c = NULL; - - pf->cl = cl; - pf->input_wanted = true; - pf->ready = start_ready; - - pf->socks_state = SOCKS_NONE; - pf->hostname = NULL; - pf->port = 0; - - *plug = &pf->plug; - return &pf->chan; -} - -void portfwd_raw_free(Channel *pfchan) -{ - struct PortForwarding *pf; - assert(pfchan->vt == &PortForwarding_channelvt); - pf = container_of(pfchan, struct PortForwarding, chan); - free_portfwd_state(pf); -} - -void portfwd_raw_setup(Channel *pfchan, Socket *s, SshChannel *sc) -{ - struct PortForwarding *pf; - assert(pfchan->vt == &PortForwarding_channelvt); - pf = container_of(pfchan, struct PortForwarding, chan); - - pf->s = s; - pf->c = sc; -} - -/* - called when someone connects to the local port - */ - -static int pfl_accepting(Plug *p, accept_fn_t constructor, accept_ctx_t ctx) -{ - struct PortListener *pl = container_of(p, struct PortListener, plug); - struct PortForwarding *pf; - Channel *chan; - Plug *plug; - Socket *s; - const char *err; - - chan = portfwd_raw_new(pl->cl, &plug, false); - s = constructor(ctx, plug); - if ((err = sk_socket_error(s)) != NULL) { - portfwd_raw_free(chan); - return 1; - } - - pf = container_of(chan, struct PortForwarding, chan); - - if (pl->is_dynamic) { - pf->s = s; - pf->socks_state = SOCKS_INITIAL; - pf->socksbuf = strbuf_new(); - pf->socksbuf_consumed = 0; - pf->port = 0; /* "hostname" buffer is so far empty */ - sk_set_frozen(s, false); /* we want to receive SOCKS _now_! */ - } else { - pf->hostname = dupstr(pl->hostname); - pf->port = pl->port; - portfwd_raw_setup( - chan, s, - wrap_lportfwd_open(pl->cl, pf->hostname, pf->port, s, &pf->chan)); - } - - return 0; -} - -static const PlugVtable PortListener_plugvt = { - .log = pfl_log, - .closing = pfl_closing, - .accepting = pfl_accepting, -}; - -/* - * Add a new port-forwarding listener from srcaddr:port -> desthost:destport. - * - * desthost == NULL indicates dynamic SOCKS port forwarding. - * - * On success, returns NULL and fills in *pl_ret. On error, returns a - * dynamically allocated error message string. - */ -static char *pfl_listen(const char *desthost, int destport, - const char *srcaddr, int port, - ConnectionLayer *cl, Conf *conf, - struct PortListener **pl_ret, int address_family) -{ - const char *err; - struct PortListener *pl; - - /* - * Open socket. - */ - pl = *pl_ret = new_portlistener_state(); - pl->plug.vt = &PortListener_plugvt; - if (desthost) { - pl->hostname = dupstr(desthost); - pl->port = destport; - pl->is_dynamic = false; - } else - pl->is_dynamic = true; - pl->cl = cl; - - pl->s = new_listener(srcaddr, port, &pl->plug, - !conf_get_bool(conf, CONF_lport_acceptall), - conf, address_family); - if ((err = sk_socket_error(pl->s)) != NULL) { - char *err_ret = dupstr(err); - sk_close(pl->s); - free_portlistener_state(pl); - *pl_ret = NULL; - return err_ret; - } - - return NULL; -} - -static char *pfd_log_close_msg(Channel *chan) -{ - return dupstr("Forwarded port closed"); -} - -static void pfd_close(struct PortForwarding *pf) -{ - if (!pf) - return; - - sk_close(pf->s); - free_portfwd_state(pf); -} - -/* - * Terminate a listener. - */ -static void pfl_terminate(struct PortListener *pl) -{ - if (!pl) - return; - - sk_close(pl->s); - free_portlistener_state(pl); -} - -static void pfd_set_input_wanted(Channel *chan, bool wanted) -{ - assert(chan->vt == &PortForwarding_channelvt); - PortForwarding *pf = container_of(chan, PortForwarding, chan); - pf->input_wanted = wanted; - sk_set_frozen(pf->s, !pf->input_wanted); -} - -static void pfd_chan_free(Channel *chan) -{ - assert(chan->vt == &PortForwarding_channelvt); - PortForwarding *pf = container_of(chan, PortForwarding, chan); - pfd_close(pf); -} - -/* - * Called to send data down the raw connection. - */ -static size_t pfd_send( - Channel *chan, bool is_stderr, const void *data, size_t len) -{ - assert(chan->vt == &PortForwarding_channelvt); - PortForwarding *pf = container_of(chan, PortForwarding, chan); - return sk_write(pf->s, data, len); -} - -static void pfd_send_eof(Channel *chan) -{ - assert(chan->vt == &PortForwarding_channelvt); - PortForwarding *pf = container_of(chan, PortForwarding, chan); - sk_write_eof(pf->s); -} - -static void pfd_open_confirmation(Channel *chan) -{ - assert(chan->vt == &PortForwarding_channelvt); - PortForwarding *pf = container_of(chan, PortForwarding, chan); - - pf->ready = true; - sk_set_frozen(pf->s, false); - sk_write(pf->s, NULL, 0); - if (pf->socksbuf) { - sshfwd_write(pf->c, pf->socksbuf->u + pf->socksbuf_consumed, - pf->socksbuf->len - pf->socksbuf_consumed); - strbuf_free(pf->socksbuf); - pf->socksbuf = NULL; - } -} - -static void pfd_open_failure(Channel *chan, const char *errtext) -{ - assert(chan->vt == &PortForwarding_channelvt); - PortForwarding *pf = container_of(chan, PortForwarding, chan); - - logeventf(pf->cl->logctx, - "Forwarded connection refused by remote%s%s", - errtext ? ": " : "", errtext ? errtext : ""); -} - -/* ---------------------------------------------------------------------- - * Code to manage the complete set of currently active port - * forwardings, and update it from Conf. - */ - -struct PortFwdRecord { - enum { DESTROY, KEEP, CREATE } status; - int type; - unsigned sport, dport; - char *saddr, *daddr; - char *sserv, *dserv; - struct ssh_rportfwd *remote; - int addressfamily; - struct PortListener *local; -}; - -static int pfr_cmp(void *av, void *bv) -{ - PortFwdRecord *a = (PortFwdRecord *) av; - PortFwdRecord *b = (PortFwdRecord *) 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 void pfr_free(PortFwdRecord *pfr) -{ - /* Dispose of any listening socket. */ - if (pfr->local) - pfl_terminate(pfr->local); - - sfree(pfr->saddr); - sfree(pfr->daddr); - sfree(pfr->sserv); - sfree(pfr->dserv); - sfree(pfr); -} - -struct PortFwdManager { - ConnectionLayer *cl; - Conf *conf; - tree234 *forwardings; -}; - -PortFwdManager *portfwdmgr_new(ConnectionLayer *cl) -{ - PortFwdManager *mgr = snew(PortFwdManager); - - mgr->cl = cl; - mgr->conf = NULL; - mgr->forwardings = newtree234(pfr_cmp); - - return mgr; -} - -void portfwdmgr_close(PortFwdManager *mgr, PortFwdRecord *pfr) -{ - PortFwdRecord *realpfr = del234(mgr->forwardings, pfr); - if (realpfr == pfr) - pfr_free(pfr); -} - -void portfwdmgr_close_all(PortFwdManager *mgr) -{ - PortFwdRecord *pfr; - - while ((pfr = delpos234(mgr->forwardings, 0)) != NULL) - pfr_free(pfr); -} - -void portfwdmgr_free(PortFwdManager *mgr) -{ - portfwdmgr_close_all(mgr); - freetree234(mgr->forwardings); - if (mgr->conf) - conf_free(mgr->conf); - sfree(mgr); -} - -void portfwdmgr_config(PortFwdManager *mgr, Conf *conf) -{ - PortFwdRecord *pfr; - int i; - char *key, *val; - - if (mgr->conf) - conf_free(mgr->conf); - mgr->conf = conf_copy(conf); - - /* - * 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. - */ - for (i = 0; (pfr = index234(mgr->forwardings, i)) != NULL; i++) - pfr->status = DESTROY; - - for (val = conf_get_str_strs(conf, CONF_portfwd, NULL, &key); - val != NULL; - val = conf_get_str_strs(conf, CONF_portfwd, key, &key)) { - char *kp, *kp2, *vp, *vp2; - char address_family, type; - int sport, dport, sserv, dserv; - char *sports, *dports, *saddr, *host; - - kp = key; - - address_family = 'A'; - type = 'L'; - if (*kp == 'A' || *kp == '4' || *kp == '6') - address_family = *kp++; - if (*kp == 'L' || *kp == 'R') - type = *kp++; - - if ((kp2 = host_strchr(kp, ':')) != NULL) { - /* - * There's a colon in the middle of the source port - * string, which means that the part before it is - * actually a source address. - */ - char *saddr_tmp = dupprintf("%.*s", (int)(kp2 - kp), kp); - saddr = host_strduptrim(saddr_tmp); - sfree(saddr_tmp); - sports = kp2+1; - } else { - saddr = NULL; - sports = kp; - } - sport = atoi(sports); - sserv = 0; - if (sport == 0) { - sserv = 1; - sport = net_service_lookup(sports); - if (!sport) { - logeventf(mgr->cl->logctx, "Service lookup failed for source" - " port \"%s\"", sports); - } - } - - if (type == 'L' && !strcmp(val, "D")) { - /* dynamic forwarding */ - host = NULL; - dports = NULL; - dport = -1; - dserv = 0; - type = 'D'; - } else { - /* ordinary forwarding */ - vp = val; - vp2 = vp + host_strcspn(vp, ":"); - host = dupprintf("%.*s", (int)(vp2 - vp), vp); - if (*vp2) - vp2++; - dports = vp2; - dport = atoi(dports); - dserv = 0; - if (dport == 0) { - dserv = 1; - dport = net_service_lookup(dports); - if (!dport) { - logeventf(mgr->cl->logctx, - "Service lookup failed for destination" - " port \"%s\"", dports); - } - } - } - - if (sport && dport) { - /* Set up a description of the source port. */ - pfr = snew(PortFwdRecord); - pfr->type = type; - pfr->saddr = saddr; - pfr->sserv = sserv ? dupstr(sports) : NULL; - pfr->sport = sport; - pfr->daddr = host; - pfr->dserv = dserv ? dupstr(dports) : NULL; - pfr->dport = dport; - pfr->local = NULL; - pfr->remote = NULL; - pfr->addressfamily = (address_family == '4' ? ADDRTYPE_IPV4 : - address_family == '6' ? ADDRTYPE_IPV6 : - ADDRTYPE_UNSPEC); - - PortFwdRecord *existing = add234(mgr->forwardings, pfr); - if (existing != pfr) { - if (existing->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. - */ - existing->status = KEEP; - } - /* - * Anything else indicates that there was a duplicate - * in our input, which we'll silently ignore. - */ - pfr_free(pfr); - } else { - pfr->status = CREATE; - } - } else { - sfree(saddr); - sfree(host); - } - } - - /* - * Now go through and destroy any port forwardings which were - * not re-enabled. - */ - for (i = 0; (pfr = index234(mgr->forwardings, i)) != NULL; i++) { - if (pfr->status == DESTROY) { - char *message; - - message = dupprintf("%s port forwarding from %s%s%d", - pfr->type == 'L' ? "local" : - pfr->type == 'R' ? "remote" : "dynamic", - pfr->saddr ? pfr->saddr : "", - pfr->saddr ? ":" : "", - pfr->sport); - - if (pfr->type != 'D') { - char *msg2 = dupprintf("%s to %s:%d", message, - pfr->daddr, pfr->dport); - sfree(message); - message = msg2; - } - - logeventf(mgr->cl->logctx, "Cancelling %s", message); - sfree(message); - - /* pfr->remote or pfr->local may be NULL if setting up a - * forwarding failed. */ - if (pfr->remote) { - /* - * Cancel the port forwarding at the server - * end. - * - * Actually closing the listening port on the server - * side may fail - because in SSH-1 there's no message - * in the protocol to request it! - * - * Instead, we simply remove the record of the - * forwarding from our local end, so that any - * connections the server tries to make on it are - * rejected. - */ - ssh_rportfwd_remove(mgr->cl, pfr->remote); - pfr->remote = NULL; - } else if (pfr->local) { - pfl_terminate(pfr->local); - pfr->local = NULL; - } - - delpos234(mgr->forwardings, i); - pfr_free(pfr); - i--; /* so we don't skip one in the list */ - } - } - - /* - * And finally, set up any new port forwardings (status==CREATE). - */ - for (i = 0; (pfr = index234(mgr->forwardings, i)) != NULL; i++) { - if (pfr->status == CREATE) { - char *sportdesc, *dportdesc; - sportdesc = dupprintf("%s%s%s%s%d%s", - pfr->saddr ? pfr->saddr : "", - pfr->saddr ? ":" : "", - pfr->sserv ? pfr->sserv : "", - pfr->sserv ? "(" : "", - pfr->sport, - pfr->sserv ? ")" : ""); - if (pfr->type == 'D') { - dportdesc = NULL; - } else { - dportdesc = dupprintf("%s:%s%s%d%s", - pfr->daddr, - pfr->dserv ? pfr->dserv : "", - pfr->dserv ? "(" : "", - pfr->dport, - pfr->dserv ? ")" : ""); - } - - if (pfr->type == 'L') { - char *err = pfl_listen(pfr->daddr, pfr->dport, - pfr->saddr, pfr->sport, - mgr->cl, conf, &pfr->local, - pfr->addressfamily); - - logeventf(mgr->cl->logctx, - "Local %sport %s forwarding to %s%s%s", - pfr->addressfamily == ADDRTYPE_IPV4 ? "IPv4 " : - pfr->addressfamily == ADDRTYPE_IPV6 ? "IPv6 " : "", - sportdesc, dportdesc, - err ? " failed: " : "", err ? err : ""); - if (err) - sfree(err); - } else if (pfr->type == 'D') { - char *err = pfl_listen(NULL, -1, pfr->saddr, pfr->sport, - mgr->cl, conf, &pfr->local, - pfr->addressfamily); - - logeventf(mgr->cl->logctx, - "Local %sport %s SOCKS dynamic forwarding%s%s", - pfr->addressfamily == ADDRTYPE_IPV4 ? "IPv4 " : - pfr->addressfamily == ADDRTYPE_IPV6 ? "IPv6 " : "", - sportdesc, - err ? " failed: " : "", err ? err : ""); - - if (err) - sfree(err); - } else { - const char *shost; - - if (pfr->saddr) { - shost = pfr->saddr; - } else if (conf_get_bool(conf, CONF_rport_acceptall)) { - shost = ""; - } else { - shost = "localhost"; - } - - pfr->remote = ssh_rportfwd_alloc( - mgr->cl, shost, pfr->sport, pfr->daddr, pfr->dport, - pfr->addressfamily, sportdesc, pfr, NULL); - - if (!pfr->remote) { - logeventf(mgr->cl->logctx, - "Duplicate remote port forwarding to %s:%d", - pfr->daddr, pfr->dport); - pfr_free(pfr); - } else { - logeventf(mgr->cl->logctx, "Requesting remote port %s" - " forward to %s", sportdesc, dportdesc); - } - } - sfree(sportdesc); - sfree(dportdesc); - } - } -} - -bool portfwdmgr_listen(PortFwdManager *mgr, const char *host, int port, - const char *keyhost, int keyport, Conf *conf) -{ - PortFwdRecord *pfr; - - pfr = snew(PortFwdRecord); - pfr->type = 'L'; - pfr->saddr = host ? dupstr(host) : NULL; - pfr->daddr = keyhost ? dupstr(keyhost) : NULL; - pfr->sserv = pfr->dserv = NULL; - pfr->sport = port; - pfr->dport = keyport; - pfr->local = NULL; - pfr->remote = NULL; - pfr->addressfamily = ADDRTYPE_UNSPEC; - - PortFwdRecord *existing = add234(mgr->forwardings, pfr); - if (existing != pfr) { - /* - * We had this record already. Return failure. - */ - pfr_free(pfr); - return false; - } - - char *err = pfl_listen(keyhost, keyport, host, port, - mgr->cl, conf, &pfr->local, pfr->addressfamily); - logeventf(mgr->cl->logctx, - "%s on port %s:%d to forward to client%s%s", - err ? "Failed to listen" : "Listening", host, port, - err ? ": " : "", err ? err : ""); - if (err) { - sfree(err); - del234(mgr->forwardings, pfr); - pfr_free(pfr); - return false; - } - - return true; -} - -bool portfwdmgr_unlisten(PortFwdManager *mgr, const char *host, int port) -{ - PortFwdRecord pfr_key; - - pfr_key.type = 'L'; - /* Safe to cast the const away here, because it will only be used - * by pfr_cmp, which won't write to the string */ - pfr_key.saddr = pfr_key.daddr = (char *)host; - pfr_key.sserv = pfr_key.dserv = NULL; - pfr_key.sport = pfr_key.dport = port; - pfr_key.local = NULL; - pfr_key.remote = NULL; - pfr_key.addressfamily = ADDRTYPE_UNSPEC; - - PortFwdRecord *pfr = del234(mgr->forwardings, &pfr_key); - - if (!pfr) - return false; - - logeventf(mgr->cl->logctx, "Closing listening port %s:%d", host, port); - - pfr_free(pfr); - return true; -} - -/* - * Called when receiving a PORT OPEN from the server to make a - * connection to a destination host. - * - * On success, returns NULL and fills in *pf_ret. On error, returns a - * dynamically allocated error message string. - */ -char *portfwdmgr_connect(PortFwdManager *mgr, Channel **chan_ret, - char *hostname, int port, SshChannel *c, - int addressfamily) -{ - SockAddr *addr; - const char *err; - char *dummy_realhost = NULL; - struct PortForwarding *pf; - - /* - * Try to find host. - */ - addr = name_lookup(hostname, port, &dummy_realhost, mgr->conf, - addressfamily, NULL, NULL); - if ((err = sk_addr_error(addr)) != NULL) { - char *err_ret = dupstr(err); - sk_addr_free(addr); - sfree(dummy_realhost); - return err_ret; - } - - /* - * Open socket. - */ - pf = new_portfwd_state(); - *chan_ret = &pf->chan; - pf->plug.vt = &PortForwarding_plugvt; - pf->chan.initial_fixed_window_size = 0; - pf->chan.vt = &PortForwarding_channelvt; - pf->input_wanted = true; - pf->ready = true; - pf->c = c; - pf->cl = mgr->cl; - pf->socks_state = SOCKS_NONE; - - pf->s = new_connection(addr, dummy_realhost, port, - false, true, false, false, &pf->plug, mgr->conf); - sfree(dummy_realhost); - if ((err = sk_socket_error(pf->s)) != NULL) { - char *err_ret = dupstr(err); - sk_close(pf->s); - free_portfwd_state(pf); - *chan_ret = NULL; - return err_ret; - } - - return NULL; -} diff --git a/pscp.c b/pscp.c index 0bfda92d..19bc3807 100644 --- a/pscp.c +++ b/pscp.c @@ -22,7 +22,7 @@ #include "putty.h" #include "psftp.h" #include "ssh.h" -#include "sftp.h" +#include "ssh/sftp.h" #include "storage.h" static bool list = false; diff --git a/psftp.c b/psftp.c index 40cb73f5..c2791e3b 100644 --- a/psftp.c +++ b/psftp.c @@ -12,7 +12,7 @@ #include "psftp.h" #include "storage.h" #include "ssh.h" -#include "sftp.h" +#include "ssh/sftp.h" const char *const appname = "PSFTP"; diff --git a/psftpcommon.c b/psftpcommon.c index 21f3737e..e6c8e2d8 100644 --- a/psftpcommon.c +++ b/psftpcommon.c @@ -8,7 +8,7 @@ #include #include "putty.h" -#include "sftp.h" +#include "ssh/sftp.h" #include "psftp.h" #define MAX_NAMES_MEMORY ((size_t)8 << 20) diff --git a/psocks.c b/psocks.c index f29eeaa9..75dc85d9 100644 --- a/psocks.c +++ b/psocks.c @@ -9,7 +9,7 @@ #include "putty.h" #include "misc.h" #include "ssh.h" -#include "sshchan.h" +#include "ssh/channel.h" #include "psocks.h" /* diff --git a/putty.h b/putty.h index d09f89b4..1b3c7c67 100644 --- a/putty.h +++ b/putty.h @@ -326,13 +326,13 @@ typedef enum { /* * Send a POSIX-style signal. (Useful in SSH and also pterm.) * - * We use the master list in sshsignals.h to define these enum + * We use the master list in ssh/signal-list.h to define these enum * values, which will come out looking like names of the form * SS_SIGABRT, SS_SIGINT etc. */ #define SIGNAL_MAIN(name, text) SS_SIG ## name, #define SIGNAL_SUB(name) SS_SIG ## name, - #include "sshsignals.h" + #include "ssh/signal-list.h" #undef SIGNAL_MAIN #undef SIGNAL_SUB @@ -356,7 +356,7 @@ struct SessionSpecial { int arg; }; -/* Needed by both sshchan.h and sshppl.h */ +/* Needed by both ssh/channel.h and ssh/ppl.h */ typedef void (*add_special_fn_t)( void *ctx, const char *text, SessionSpecialCode code, int arg); @@ -1925,7 +1925,7 @@ extern const struct BackendVtable rlogin_backend; extern const struct BackendVtable telnet_backend; /* - * Exports from ssh.c. + * Exports from ssh/ssh.c. */ extern const struct BackendVtable ssh_backend; extern const struct BackendVtable sshconn_backend; diff --git a/scpserver.c b/scpserver.c deleted file mode 100644 index 3c6e4559..00000000 --- a/scpserver.c +++ /dev/null @@ -1,1399 +0,0 @@ -/* - * Server side of the old-school SCP protocol. - */ - -#include -#include -#include - -#include "putty.h" -#include "ssh.h" -#include "sshcr.h" -#include "sshchan.h" -#include "sftp.h" - -/* - * I think it's worth actually documenting my understanding of what - * this protocol _is_, since I don't know of any other documentation - * of it anywhere. - * - * Format of data stream - * --------------------- - * - * The sending side of an SCP connection - the client, if you're - * uploading files, or the server if you're downloading - sends a data - * stream consisting of a sequence of 'commands', or header records, - * or whatever you want to call them, interleaved with file data. - * - * Each command starts with a letter indicating what type it is, and - * ends with a \n. - * - * The 'C' command introduces an actual file. It is followed by an - * octal file-permissions mask, then a space, then a decimal file - * size, then a space, then the file name up to the termating newline. - * For example, "C0644 12345 filename.txt\n" would be a plausible C - * command. - * - * After the 'C' command, the sending side will transmit exactly as - * many bytes of file data as specified by the size field in the - * header line, followed by a single zero byte. - * - * The 'D' command introduces a subdirectory. Its format is identical - * to 'C', including the size field, but the size field is sent as - * zero. - * - * After the 'D' command, all subsequent C and D commands are taken to - * indicate files that should be placed inside that subdirectory, - * until a terminating 'E' command. - * - * The 'E' command indicates the end of a subdirectory. It has no - * arguments at all (its format is always just "E\n"). After the E - * command, the receiver should revert to placing further downloaded - * files in whatever directory it was placing them before the - * subdirectory opened by the just-closed D. - * - * D and E commands match like parentheses: if you send, say, - * - * C0644 123 foo.txt ( followed by data ) - * D0755 0 subdir - * C0644 123 bar.txt ( followed by data ) - * D0755 0 subsubdir - * C0644 123 baz.txt ( followed by data ) - * E - * C0644 123 quux.txt ( followed by data ) - * E - * C0644 123 wibble.txt ( followed by data ) - * - * then foo.txt, subdir and wibble.txt go in the top-level destination - * directory; bar.txt, subsubdir and quux.txt go in 'subdir'; and - * baz.txt goes in 'subdir/subsubdir'. - * - * The sender terminates the data stream with EOF when it has no more - * files to send. I believe it is not _required_ for all D to be - * closed by an E before this happens - you can elide a trailing - * sequence of E commands without provoking an error message from the - * receiver. - * - * Finally, the 'T' command is sent immediately before a C or D. It is - * followed by four space-separated decimal integers giving an mtime - * and atime to be applied to the file or directory created by the - * following C or D command. The first two integers give the mtime, - * encoded as seconds and microseconds (respectively) since the Unix - * epoch; the next two give the atime, encoded similarly. So - * "T1540373455 0 1540373457 0\n" is an example of a valid T command. - * - * Acknowledgments - * --------------- - * - * The sending side waits for an ack from the receiving side before - * sending each command; before beginning to send the file data - * following a C command; and before sending the final EOF. - * - * (In particular, the receiving side is expected to send an initial - * ack before _anything_ is sent.) - * - * Normally an ack consists of a single zero byte. It's also allowable - * to send a byte with value 1 or 2 followed by a \n-terminated error - * message (where 1 means a non-fatal error and 2 means a fatal one). - * I have to suppose that sending an error message from client to - * server is of limited use, but apparently it's allowed. - * - * Initiation - * ---------- - * - * The protocol is begun by the client sending a command string to the - * server via the SSH-2 "exec" request (or the analogous - * SSH1_CMSG_EXEC_CMD), which indicates that this is an scp session - * rather than any other thing; specifies the direction of transfer; - * says what file(s) are to be sent by the server, or where the server - * should put files that the client is about to send; and a couple of - * other options. - * - * The command string takes the following form: - * - * Start with prefix "scp ", indicating that this is an SCP command at - * all. Otherwise it's a request to run some completely different - * command in the SSH session. - * - * Next the command can contain zero or more of the following options, - * each followed by a space: - * - * "-v" turns on verbose server diagnostics. Of course a server is not - * required to actually produce any, but this is an invitation for it - * to send any it might have available. Diagnostics are free-form, and - * sent as SSH standard-error extended data, so that they are separate - * from the actual data stream as described above. - * - * (Servers can send standard-error output anyway if they like, and in - * case of an actual error, they probably will with or without -v.) - * - * "-r" indicates recursive file transfer, i.e. potentially including - * subdirectories. For a download, this indicates that the client is - * willing to receive subdirectories (a D/E command pair bracketing - * further files and subdirs); without it, the server should only send - * C commands for individual files, followed by EOF. - * - * This flag must also be specified for a recursive upload, because I - * believe at least one server will reject D/E pairs sent by the - * client if the command didn't have -r in it. (Probably a consequence - * of sharing code between download and upload.) - * - * "-p" means preserve file times. In a download, this requests the - * server to send a T command before each C or D. I don't know whether - * any server will insist on having seen this option from the client - * before accepting T commands in an upload, but it is probably - * sensible to send it anyway. - * - * "-d", in an upload, means that the destination pathname (see below) - * is expected to be a directory, and that uploaded files (and - * subdirs) should be put inside it. Without -d, the semantics are - * that _if_ the destination exists and is a directory, then files - * will be put in it, whereas if it is not, then just a single file - * (or subdir) upload is expected, which will be placed at that exact - * pathname. - * - * In a download, I observe that clients tend to send -d if they are - * requesting multiple files or a wildcard, but as far as I know, - * servers ignore it. - * - * After all those optional options, there is a single mandatory - * option indicating the direction of transfer, which is either "-f" - * or "-t". "-f" indicates a download; "-t" indicates an upload. - * - * After that mandatory option, there is a single space, followed by - * the name(s) of files to transfer. - * - * This file name field is transmitted with NO QUOTING, in spite of - * the fact that a server will typically interpret it as a shell - * command. You'd think this couldn't possibly work, in the face of - * almost any filename with an interesting character in it - and you'd - * be right. Or rather (you might argue), it works 'as designed', but - * it's designed in a weird way, in that it's the user's - * responsibility to apply quoting on the client command line to get - * the filename through the shell that will decode things on the - * server side. - * - * But one effect of this is that if you issue a download command - * including a wildcard, say "scp -f somedir/foo*.txt", then the shell - * will expand the wildcard, and actually run the server-side scp - * program with multiple arguments, say "somedir/foo.txt - * somedir/quux.txt", leading to the download sending multiple C - * commands. This clearly _is_ intended: it's how a command such as - * 'scp server:somedir/foo*.txt destdir' can work at all. - * - * (You would think, given that, that it might also be legal to send - * multiple space-separated filenames in order to trigger a download - * of exactly those files. Given how scp is invoked in practice on a - * typical server, this would surely actually work, but my observation - * is that scp clients don't in fact try this - if you run OpenSSH's - * scp by saying 'scp server:foo server:bar destdir' then it will make - * two separate connections to the server for the two files, rather - * than sending a single space-separated remote command. PSCP won't - * even do that, and will make you do it in two separate runs.) - * - * So, some examples: - * - * - "scp -f filename.txt" - * - * Server should send a single C command (plus data) for that file. - * Client ought to ignore the filename in the C command, in favour - * of saving the file under the name implied by the user's command - * line. - * - * - "scp -f file*.txt" - * - * Server sends zero or more C commands, then EOF. Client will have - * been given a target directory to put them all in, and will name - * each one according to the name in the C command. - * - * (You'd like the client to validate the filenames against the - * wildcard it sent, to ensure a malicious server didn't try to - * overwrite some path like ".bashrc" when you thought you were - * downloading only normal text files. But wildcard semantics are - * chosen by the server, so this is essentially hopeless to do - * rigorously.) - * - * - "scp -f -r somedir" - * - * Assuming somedir is actually a directory, server sends a D/E - * pair, in between which are the contents of the directory - * (perhaps including further nested D/E pairs). Client probably - * ignores the name field of the outermost D - * - * - "scp -f -r some*wild*card*" - * - * Server sends multiple C or D-stuff-E, one for each top-level - * thing matching the wildcard, whether it's a file or a directory. - * - * - "scp -t -d some_dir" - * - * Client sends stuff, and server deposits each file at - * some_dir/. - * - * - "scp -t some_path_name" - * - * Client sends one C command, and server deposits it at - * some_path_name itself, or in some_path_name/, depending whether some_path_name was already a - * directory or not. - */ - -/* - * Here's a useful debugging aid: run over a binary file containing - * the complete contents of the sender's data stream (e.g. extracted - * by contrib/logparse.pl -d), it removes the file contents, leaving - * only the list of commands, so you can see what the server sent. - * - * perl -pe 'read ARGV,$x,1+$1 if/^C\S+ (\d+)/' - */ - -/* ---------------------------------------------------------------------- - * Shared system for receiving replies from the SftpServer, and - * putting them into a set of ordinary variables rather than - * marshalling them into actual SFTP reply packets that we'd only have - * to unmarshal again. - */ - -typedef struct ScpReplyReceiver ScpReplyReceiver; -struct ScpReplyReceiver { - bool err; - unsigned code; - char *errmsg; - struct fxp_attrs attrs; - ptrlen name, handle, data; - - SftpReplyBuilder srb; -}; - -static void scp_reply_ok(SftpReplyBuilder *srb) -{ - ScpReplyReceiver *reply = container_of(srb, ScpReplyReceiver, srb); - reply->err = false; -} - -static void scp_reply_error( - SftpReplyBuilder *srb, unsigned code, const char *msg) -{ - ScpReplyReceiver *reply = container_of(srb, ScpReplyReceiver, srb); - reply->err = true; - reply->code = code; - sfree(reply->errmsg); - reply->errmsg = dupstr(msg); -} - -static void scp_reply_name_count(SftpReplyBuilder *srb, unsigned count) -{ - ScpReplyReceiver *reply = container_of(srb, ScpReplyReceiver, srb); - reply->err = false; -} - -static void scp_reply_full_name( - SftpReplyBuilder *srb, ptrlen name, - ptrlen longname, struct fxp_attrs attrs) -{ - ScpReplyReceiver *reply = container_of(srb, ScpReplyReceiver, srb); - char *p; - reply->err = false; - sfree((void *)reply->name.ptr); - reply->name.ptr = p = mkstr(name); - reply->name.len = name.len; - reply->attrs = attrs; -} - -static void scp_reply_simple_name(SftpReplyBuilder *srb, ptrlen name) -{ - ScpReplyReceiver *reply = container_of(srb, ScpReplyReceiver, srb); - reply->err = false; -} - -static void scp_reply_handle(SftpReplyBuilder *srb, ptrlen handle) -{ - ScpReplyReceiver *reply = container_of(srb, ScpReplyReceiver, srb); - char *p; - reply->err = false; - sfree((void *)reply->handle.ptr); - reply->handle.ptr = p = mkstr(handle); - reply->handle.len = handle.len; -} - -static void scp_reply_data(SftpReplyBuilder *srb, ptrlen data) -{ - ScpReplyReceiver *reply = container_of(srb, ScpReplyReceiver, srb); - char *p; - reply->err = false; - sfree((void *)reply->data.ptr); - reply->data.ptr = p = mkstr(data); - reply->data.len = data.len; -} - -static void scp_reply_attrs( - SftpReplyBuilder *srb, struct fxp_attrs attrs) -{ - ScpReplyReceiver *reply = container_of(srb, ScpReplyReceiver, srb); - reply->err = false; - reply->attrs = attrs; -} - -static const SftpReplyBuilderVtable ScpReplyReceiver_vt = { - .reply_ok = scp_reply_ok, - .reply_error = scp_reply_error, - .reply_simple_name = scp_reply_simple_name, - .reply_name_count = scp_reply_name_count, - .reply_full_name = scp_reply_full_name, - .reply_handle = scp_reply_handle, - .reply_data = scp_reply_data, - .reply_attrs = scp_reply_attrs, -}; - -static void scp_reply_setup(ScpReplyReceiver *reply) -{ - memset(reply, 0, sizeof(*reply)); - reply->srb.vt = &ScpReplyReceiver_vt; -} - -static void scp_reply_cleanup(ScpReplyReceiver *reply) -{ - sfree(reply->errmsg); - sfree((void *)reply->name.ptr); - sfree((void *)reply->handle.ptr); - sfree((void *)reply->data.ptr); -} - -/* ---------------------------------------------------------------------- - * Source end of the SCP protocol. - */ - -#define SCP_MAX_BACKLOG 65536 - -typedef struct ScpSource ScpSource; -typedef struct ScpSourceStackEntry ScpSourceStackEntry; - -struct ScpSource { - SftpServer *sf; - - int acks; - bool expect_newline, eof, throttled, finished; - - SshChannel *sc; - ScpSourceStackEntry *head; - bool recursive; - bool send_file_times; - - strbuf *pending_commands[3]; - int n_pending_commands; - - uint64_t file_offset, file_size; - - ScpReplyReceiver reply; - - ScpServer scpserver; -}; - -typedef enum ScpSourceNodeType ScpSourceNodeType; -enum ScpSourceNodeType { SCP_ROOTPATH, SCP_NAME, SCP_READDIR, SCP_READFILE }; - -struct ScpSourceStackEntry { - ScpSourceStackEntry *next; - ScpSourceNodeType type; - ptrlen pathname, handle; - const char *wildcard; - struct fxp_attrs attrs; -}; - -static void scp_source_push(ScpSource *scp, ScpSourceNodeType type, - ptrlen pathname, ptrlen handle, - const struct fxp_attrs *attrs, const char *wc) -{ - size_t wc_len = wc ? strlen(wc)+1 : 0; - ScpSourceStackEntry *node = snew_plus( - ScpSourceStackEntry, pathname.len + handle.len + wc_len); - char *namebuf = snew_plus_get_aux(node); - memcpy(namebuf, pathname.ptr, pathname.len); - node->pathname = make_ptrlen(namebuf, pathname.len); - memcpy(namebuf + pathname.len, handle.ptr, handle.len); - node->handle = make_ptrlen(namebuf + pathname.len, handle.len); - if (wc) { - strcpy(namebuf + pathname.len + handle.len, wc); - node->wildcard = namebuf + pathname.len + handle.len; - } else { - node->wildcard = NULL; - } - node->attrs = attrs ? *attrs : no_attrs; - node->type = type; - node->next = scp->head; - scp->head = node; -} - -static char *scp_source_err_base(ScpSource *scp, const char *fmt, va_list ap) -{ - char *msg = dupvprintf(fmt, ap); - sshfwd_write_ext(scp->sc, true, msg, strlen(msg)); - sshfwd_write_ext(scp->sc, true, "\012", 1); - return msg; -} -static PRINTF_LIKE(2, 3) void scp_source_err( - ScpSource *scp, const char *fmt, ...) -{ - va_list ap; - - va_start(ap, fmt); - sfree(scp_source_err_base(scp, fmt, ap)); - va_end(ap); -} -static PRINTF_LIKE(2, 3) void scp_source_abort( - ScpSource *scp, const char *fmt, ...) -{ - va_list ap; - char *msg; - - va_start(ap, fmt); - msg = scp_source_err_base(scp, fmt, ap); - va_end(ap); - - sshfwd_send_exit_status(scp->sc, 1); - sshfwd_write_eof(scp->sc); - sshfwd_initiate_close(scp->sc, msg); - - scp->finished = true; -} - -static void scp_source_push_name( - ScpSource *scp, ptrlen pathname, struct fxp_attrs attrs, const char *wc) -{ - if (!(attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS)) { - scp_source_err(scp, "unable to read file permissions for %.*s", - PTRLEN_PRINTF(pathname)); - return; - } - if (attrs.permissions & PERMS_DIRECTORY) { - if (!scp->recursive && !wc) { - scp_source_err(scp, "%.*s: is a directory", - PTRLEN_PRINTF(pathname)); - return; - } - } else { - if (!(attrs.flags & SSH_FILEXFER_ATTR_SIZE)) { - scp_source_err(scp, "unable to read file size for %.*s", - PTRLEN_PRINTF(pathname)); - return; - } - } - - scp_source_push(scp, SCP_NAME, pathname, PTRLEN_LITERAL(""), &attrs, wc); -} - -static void scp_source_free(ScpServer *s); -static size_t scp_source_send(ScpServer *s, const void *data, size_t length); -static void scp_source_eof(ScpServer *s); -static void scp_source_throttle(ScpServer *s, bool throttled); - -static const ScpServerVtable ScpSource_ScpServer_vt = { - .free = scp_source_free, - .send = scp_source_send, - .throttle = scp_source_throttle, - .eof = scp_source_eof, -}; - -static ScpSource *scp_source_new( - SshChannel *sc, const SftpServerVtable *sftpserver_vt, ptrlen pathname) -{ - ScpSource *scp = snew(ScpSource); - memset(scp, 0, sizeof(*scp)); - - scp->scpserver.vt = &ScpSource_ScpServer_vt; - scp_reply_setup(&scp->reply); - scp->sc = sc; - scp->sf = sftpsrv_new(sftpserver_vt); - scp->n_pending_commands = 0; - - scp_source_push(scp, SCP_ROOTPATH, pathname, PTRLEN_LITERAL(""), - NULL, NULL); - - return scp; -} - -static void scp_source_free(ScpServer *s) -{ - ScpSource *scp = container_of(s, ScpSource, scpserver); - scp_reply_cleanup(&scp->reply); - while (scp->n_pending_commands > 0) - strbuf_free(scp->pending_commands[--scp->n_pending_commands]); - while (scp->head) { - ScpSourceStackEntry *node = scp->head; - scp->head = node->next; - sfree(node); - } - - delete_callbacks_for_context(scp); - - sfree(scp); -} - -static void scp_source_send_E(ScpSource *scp) -{ - strbuf *cmd; - - assert(scp->n_pending_commands == 0); - - scp->pending_commands[scp->n_pending_commands++] = cmd = strbuf_new(); - strbuf_catf(cmd, "E\012"); -} - -static void scp_source_send_CD( - ScpSource *scp, char cmdchar, - struct fxp_attrs attrs, uint64_t size, ptrlen name) -{ - strbuf *cmd; - - assert(scp->n_pending_commands == 0); - - if (scp->send_file_times && (attrs.flags & SSH_FILEXFER_ATTR_ACMODTIME)) { - scp->pending_commands[scp->n_pending_commands++] = cmd = strbuf_new(); - /* Our SFTP-based filesystem API doesn't support microsecond times */ - strbuf_catf(cmd, "T%lu 0 %lu 0\012", attrs.mtime, attrs.atime); - } - - const char *slash; - while ((slash = memchr(name.ptr, '/', name.len)) != NULL) - name = make_ptrlen( - slash+1, name.len - (slash+1 - (const char *)name.ptr)); - - scp->pending_commands[scp->n_pending_commands++] = cmd = strbuf_new(); - strbuf_catf(cmd, "%c%04o %"PRIu64" %.*s\012", cmdchar, - (unsigned)(attrs.permissions & 07777), - size, PTRLEN_PRINTF(name)); - - if (cmdchar == 'C') { - /* We'll also wait for an ack before sending the file data, - * which we record by saving a zero-length 'command' to be - * sent after the C. */ - scp->pending_commands[scp->n_pending_commands++] = cmd = strbuf_new(); - } -} - -static void scp_source_process_stack(ScpSource *scp); -static void scp_source_process_stack_cb(void *vscp) -{ - ScpSource *scp = (ScpSource *)vscp; - if (scp->finished) - return; /* this callback is out of date */ - scp_source_process_stack(scp); -} -static void scp_requeue(ScpSource *scp) -{ - queue_toplevel_callback(scp_source_process_stack_cb, scp); -} - -static void scp_source_process_stack(ScpSource *scp) -{ - if (scp->throttled) - return; - - while (scp->n_pending_commands > 0) { - /* Expect an ack, and consume it */ - if (scp->eof) { - scp_source_abort( - scp, "scp: received client EOF, abandoning transfer"); - return; - } - if (scp->acks == 0) - return; - scp->acks--; - - /* - * Now send the actual command (unless it was the phony - * zero-length one that indicates our need for an ack before - * beginning to send file data). - */ - - if (scp->pending_commands[0]->len) - sshfwd_write(scp->sc, scp->pending_commands[0]->s, - scp->pending_commands[0]->len); - - strbuf_free(scp->pending_commands[0]); - scp->n_pending_commands--; - if (scp->n_pending_commands > 0) { - /* - * We still have at least one pending command to send, so - * move up the queue. - * - * (We do that with a bodgy memmove, because there are at - * most a bounded number of commands ever pending at once, - * so no need to worry about quadratic time.) - */ - memmove(scp->pending_commands, scp->pending_commands+1, - scp->n_pending_commands * sizeof(*scp->pending_commands)); - } - } - - /* - * Mostly, we start by waiting for an ack byte from the receiver. - */ - if (scp->head && scp->head->type == SCP_READFILE && scp->file_offset) { - /* - * Exception: if we're already in the middle of transferring a - * file, we'll be called back here because the channel backlog - * has cleared; we don't need to wait for an ack. - */ - } else if (scp->head && scp->head->type == SCP_ROOTPATH) { - /* - * Another exception: the initial action node that makes us - * stat the root path. We'll translate it into an SCP_NAME, - * and _that_ will require an ack. - */ - ScpSourceStackEntry *node = scp->head; - scp->head = node->next; - - /* - * Start by checking if there's a wildcard involved in the - * root path. - */ - char *rootpath_str = mkstr(node->pathname); - char *rootpath_unesc = snewn(1+node->pathname.len, char); - ptrlen pathname; - const char *wildcard; - - if (wc_unescape(rootpath_unesc, rootpath_str)) { - /* - * We successfully removed instances of the escape - * character used in our wildcard syntax, without - * encountering any actual wildcard chars - i.e. this is - * not a wildcard, just a single file. The simple case. - */ - pathname = ptrlen_from_asciz(rootpath_str); - wildcard = NULL; - } else { - /* - * This is a wildcard. Separate it into a directory name - * (which we enforce mustn't contain wc characters, for - * simplicity) and a wildcard to match leaf names. - */ - char *last_slash = strrchr(rootpath_str, '/'); - - if (last_slash) { - wildcard = last_slash + 1; - *last_slash = '\0'; - if (!wc_unescape(rootpath_unesc, rootpath_str)) { - scp_source_abort(scp, "scp: wildcards in path components " - "before the file name not supported"); - sfree(rootpath_str); - sfree(rootpath_unesc); - return; - } - - pathname = ptrlen_from_asciz(rootpath_unesc); - } else { - pathname = PTRLEN_LITERAL("."); - wildcard = rootpath_str; - } - } - - /* - * Now we know what directory we're scanning, and what - * wildcard (if any) we're using to match the filenames we get - * back. - */ - sftpsrv_stat(scp->sf, &scp->reply.srb, pathname, true); - if (scp->reply.err) { - scp_source_abort( - scp, "%.*s: unable to access: %s", - PTRLEN_PRINTF(pathname), scp->reply.errmsg); - sfree(rootpath_str); - sfree(rootpath_unesc); - sfree(node); - return; - } - - scp_source_push_name(scp, pathname, scp->reply.attrs, wildcard); - - sfree(rootpath_str); - sfree(rootpath_unesc); - sfree(node); - scp_requeue(scp); - return; - } else { - } - - if (scp->head && scp->head->type == SCP_READFILE) { - /* - * Transfer file data if our backlog hasn't filled up. - */ - int backlog; - uint64_t limit = scp->file_size - scp->file_offset; - if (limit > 4096) - limit = 4096; - if (limit > 0) { - sftpsrv_read(scp->sf, &scp->reply.srb, scp->head->handle, - scp->file_offset, limit); - if (scp->reply.err) { - scp_source_abort( - scp, "%.*s: unable to read: %s", - PTRLEN_PRINTF(scp->head->pathname), scp->reply.errmsg); - return; - } - - backlog = sshfwd_write( - scp->sc, scp->reply.data.ptr, scp->reply.data.len); - scp->file_offset += scp->reply.data.len; - - if (backlog < SCP_MAX_BACKLOG) - scp_requeue(scp); - return; - } - - /* - * If we're done, send a terminating zero byte, close our file - * handle, and pop the stack. - */ - sshfwd_write(scp->sc, "\0", 1); - sftpsrv_close(scp->sf, &scp->reply.srb, scp->head->handle); - ScpSourceStackEntry *node = scp->head; - scp->head = node->next; - sfree(node); - scp_requeue(scp); - return; - } - - /* - * If our queue is actually empty, send outgoing EOF. - */ - if (!scp->head) { - sshfwd_send_exit_status(scp->sc, 0); - sshfwd_write_eof(scp->sc); - sshfwd_initiate_close(scp->sc, NULL); - scp->finished = true; - return; - } - - /* - * Otherwise, handle a command. - */ - ScpSourceStackEntry *node = scp->head; - scp->head = node->next; - - if (node->type == SCP_READDIR) { - sftpsrv_readdir(scp->sf, &scp->reply.srb, node->handle, 1, true); - if (scp->reply.err) { - if (scp->reply.code != SSH_FX_EOF) - scp_source_err(scp, "%.*s: unable to list directory: %s", - PTRLEN_PRINTF(node->pathname), - scp->reply.errmsg); - sftpsrv_close(scp->sf, &scp->reply.srb, node->handle); - - if (!node->wildcard) { - /* - * Send 'pop stack' or 'end of directory' command, - * unless this was the topmost READDIR in a - * wildcard-based retrieval (in which case we didn't - * send a D command to start, so an E now would have - * no stack entry to pop). - */ - scp_source_send_E(scp); - } - } else if (ptrlen_eq_string(scp->reply.name, ".") || - ptrlen_eq_string(scp->reply.name, "..") || - (node->wildcard && - !wc_match_pl(node->wildcard, scp->reply.name))) { - /* Skip special directory names . and .., and anything - * that doesn't match our wildcard (if we have one). */ - scp->head = node; /* put back the unfinished READDIR */ - node = NULL; /* and prevent it being freed */ - } else { - ptrlen subpath; - subpath.len = node->pathname.len + 1 + scp->reply.name.len; - char *subpath_space = snewn(subpath.len, char); - subpath.ptr = subpath_space; - memcpy(subpath_space, node->pathname.ptr, node->pathname.len); - subpath_space[node->pathname.len] = '/'; - memcpy(subpath_space + node->pathname.len + 1, - scp->reply.name.ptr, scp->reply.name.len); - - scp->head = node; /* put back the unfinished READDIR */ - node = NULL; /* and prevent it being freed */ - scp_source_push_name(scp, subpath, scp->reply.attrs, NULL); - - sfree(subpath_space); - } - } else if (node->attrs.permissions & PERMS_DIRECTORY) { - assert(scp->recursive || node->wildcard); - - if (!node->wildcard) - scp_source_send_CD(scp, 'D', node->attrs, 0, node->pathname); - sftpsrv_opendir(scp->sf, &scp->reply.srb, node->pathname); - if (scp->reply.err) { - scp_source_err( - scp, "%.*s: unable to access: %s", - PTRLEN_PRINTF(node->pathname), scp->reply.errmsg); - - if (!node->wildcard) { - /* Send 'pop stack' or 'end of directory' command. */ - scp_source_send_E(scp); - } - } else { - scp_source_push( - scp, SCP_READDIR, node->pathname, - scp->reply.handle, NULL, node->wildcard); - } - } else { - sftpsrv_open(scp->sf, &scp->reply.srb, - node->pathname, SSH_FXF_READ, no_attrs); - if (scp->reply.err) { - scp_source_err( - scp, "%.*s: unable to open: %s", - PTRLEN_PRINTF(node->pathname), scp->reply.errmsg); - scp_requeue(scp); - return; - } - sftpsrv_fstat(scp->sf, &scp->reply.srb, scp->reply.handle); - if (scp->reply.err) { - scp_source_err( - scp, "%.*s: unable to stat: %s", - PTRLEN_PRINTF(node->pathname), scp->reply.errmsg); - sftpsrv_close(scp->sf, &scp->reply.srb, scp->reply.handle); - scp_requeue(scp); - return; - } - scp->file_offset = 0; - scp->file_size = scp->reply.attrs.size; - scp_source_send_CD(scp, 'C', node->attrs, - scp->file_size, node->pathname); - scp_source_push( - scp, SCP_READFILE, node->pathname, scp->reply.handle, NULL, NULL); - } - sfree(node); - scp_requeue(scp); -} - -static size_t scp_source_send(ScpServer *s, const void *vdata, size_t length) -{ - ScpSource *scp = container_of(s, ScpSource, scpserver); - const char *data = (const char *)vdata; - size_t i; - - if (scp->finished) - return 0; - - for (i = 0; i < length; i++) { - if (scp->expect_newline) { - if (data[i] == '\012') { - /* End of an error message following a 1 byte */ - scp->expect_newline = false; - scp->acks++; - } - } else { - switch (data[i]) { - case 0: /* ordinary ack */ - scp->acks++; - break; - case 1: /* non-fatal error; consume it */ - scp->expect_newline = true; - break; - case 2: - scp_source_abort( - scp, "terminating on fatal error from client"); - return 0; - default: - scp_source_abort( - scp, "unrecognised response code from client"); - return 0; - } - } - } - - scp_source_process_stack(scp); - - return 0; -} - -static void scp_source_throttle(ScpServer *s, bool throttled) -{ - ScpSource *scp = container_of(s, ScpSource, scpserver); - - if (scp->finished) - return; - - scp->throttled = throttled; - if (!throttled) - scp_source_process_stack(scp); -} - -static void scp_source_eof(ScpServer *s) -{ - ScpSource *scp = container_of(s, ScpSource, scpserver); - - if (scp->finished) - return; - - scp->eof = true; - scp_source_process_stack(scp); -} - -/* ---------------------------------------------------------------------- - * Sink end of the SCP protocol. - */ - -typedef struct ScpSink ScpSink; -typedef struct ScpSinkStackEntry ScpSinkStackEntry; - -struct ScpSink { - SftpServer *sf; - - SshChannel *sc; - ScpSinkStackEntry *head; - - uint64_t file_offset, file_size; - unsigned long atime, mtime; - bool got_file_times; - - bufchain data; - bool input_eof; - strbuf *command; - char command_chr; - - strbuf *filename_sb; - ptrlen filename; - struct fxp_attrs attrs; - - char *errmsg; - - int crState; - - ScpReplyReceiver reply; - - ScpServer scpserver; -}; - -struct ScpSinkStackEntry { - ScpSinkStackEntry *next; - ptrlen destpath; - - /* - * If isdir is true, then destpath identifies a directory that the - * files we receive should be created inside. If it's false, then - * it identifies the exact pathname the next file we receive - * should be created _as_ - regardless of the filename in the 'C' - * command. - */ - bool isdir; -}; - -static void scp_sink_push(ScpSink *scp, ptrlen pathname, bool isdir) -{ - ScpSinkStackEntry *node = snew_plus(ScpSinkStackEntry, pathname.len); - char *p = snew_plus_get_aux(node); - - node->destpath.ptr = p; - node->destpath.len = pathname.len; - memcpy(p, pathname.ptr, pathname.len); - node->isdir = isdir; - - node->next = scp->head; - scp->head = node; -} - -static void scp_sink_pop(ScpSink *scp) -{ - ScpSinkStackEntry *node = scp->head; - scp->head = node->next; - sfree(node); -} - -static void scp_sink_free(ScpServer *s); -static size_t scp_sink_send(ScpServer *s, const void *data, size_t length); -static void scp_sink_eof(ScpServer *s); -static void scp_sink_throttle(ScpServer *s, bool throttled) {} - -static const ScpServerVtable ScpSink_ScpServer_vt = { - .free = scp_sink_free, - .send = scp_sink_send, - .throttle = scp_sink_throttle, - .eof = scp_sink_eof, -}; - -static void scp_sink_coroutine(ScpSink *scp); -static void scp_sink_start_callback(void *vscp) -{ - scp_sink_coroutine((ScpSink *)vscp); -} - -static ScpSink *scp_sink_new( - SshChannel *sc, const SftpServerVtable *sftpserver_vt, ptrlen pathname, - bool pathname_is_definitely_dir) -{ - ScpSink *scp = snew(ScpSink); - memset(scp, 0, sizeof(*scp)); - - scp->scpserver.vt = &ScpSink_ScpServer_vt; - scp_reply_setup(&scp->reply); - scp->sc = sc; - scp->sf = sftpsrv_new(sftpserver_vt); - bufchain_init(&scp->data); - scp->command = strbuf_new(); - scp->filename_sb = strbuf_new(); - - if (!pathname_is_definitely_dir) { - /* - * If our root pathname is not already expected to be a - * directory because of the -d option in the command line, - * test it ourself to see whether it is or not. - */ - sftpsrv_stat(scp->sf, &scp->reply.srb, pathname, true); - if (!scp->reply.err && - (scp->reply.attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) && - (scp->reply.attrs.permissions & PERMS_DIRECTORY)) - pathname_is_definitely_dir = true; - } - scp_sink_push(scp, pathname, pathname_is_definitely_dir); - - queue_toplevel_callback(scp_sink_start_callback, scp); - - return scp; -} - -static void scp_sink_free(ScpServer *s) -{ - ScpSink *scp = container_of(s, ScpSink, scpserver); - - scp_reply_cleanup(&scp->reply); - bufchain_clear(&scp->data); - strbuf_free(scp->command); - strbuf_free(scp->filename_sb); - while (scp->head) - scp_sink_pop(scp); - sfree(scp->errmsg); - - delete_callbacks_for_context(scp); - - sfree(scp); -} - -static void scp_sink_coroutine(ScpSink *scp) -{ - crBegin(scp->crState); - - while (1) { - /* - * Send an ack, and read a command. - */ - sshfwd_write(scp->sc, "\0", 1); - strbuf_clear(scp->command); - while (1) { - crMaybeWaitUntilV(scp->input_eof || bufchain_size(&scp->data) > 0); - if (scp->input_eof) - goto done; - - ptrlen data = bufchain_prefix(&scp->data); - const char *cdata = data.ptr; - const char *newline = memchr(cdata, '\012', data.len); - if (newline) - data.len = (int)(newline+1 - cdata); - put_data(scp->command, cdata, data.len); - bufchain_consume(&scp->data, data.len); - - if (newline) - break; - } - - /* - * Parse the command. - */ - strbuf_chomp(scp->command, '\n'); - scp->command_chr = scp->command->len > 0 ? scp->command->s[0] : '\0'; - if (scp->command_chr == 'T') { - unsigned long dummy1, dummy2; - if (sscanf(scp->command->s, "T%lu %lu %lu %lu", - &scp->mtime, &dummy1, &scp->atime, &dummy2) != 4) - goto parse_error; - scp->got_file_times = true; - } else if (scp->command_chr == 'C' || scp->command_chr == 'D') { - /* - * Common handling of the start of this case, because the - * messages are parsed similarly. We diverge later. - */ - const char *q, *p = scp->command->s + 1; /* skip the 'C' */ - - scp->attrs.flags = SSH_FILEXFER_ATTR_PERMISSIONS; - scp->attrs.permissions = 0; - while (*p >= '0' && *p <= '7') { - scp->attrs.permissions = - scp->attrs.permissions * 8 + (*p - '0'); - p++; - } - if (*p != ' ') - goto parse_error; - p++; - - q = p; - while (*p >= '0' && *p <= '9') - p++; - if (*p != ' ') - goto parse_error; - p++; - scp->file_size = strtoull(q, NULL, 10); - - ptrlen leafname = make_ptrlen( - p, scp->command->len - (p - scp->command->s)); - strbuf_clear(scp->filename_sb); - put_datapl(scp->filename_sb, scp->head->destpath); - if (scp->head->isdir) { - if (scp->filename_sb->len > 0 && - scp->filename_sb->s[scp->filename_sb->len-1] - != '/') - put_byte(scp->filename_sb, '/'); - put_datapl(scp->filename_sb, leafname); - } - scp->filename = ptrlen_from_strbuf(scp->filename_sb); - - if (scp->got_file_times) { - scp->attrs.mtime = scp->mtime; - scp->attrs.atime = scp->atime; - scp->attrs.flags |= SSH_FILEXFER_ATTR_ACMODTIME; - } - scp->got_file_times = false; - - if (scp->command_chr == 'D') { - sftpsrv_mkdir(scp->sf, &scp->reply.srb, - scp->filename, scp->attrs); - - if (scp->reply.err) { - scp->errmsg = dupprintf( - "'%.*s': unable to create directory: %s", - PTRLEN_PRINTF(scp->filename), scp->reply.errmsg); - goto done; - } - - scp_sink_push(scp, scp->filename, true); - } else { - sftpsrv_open(scp->sf, &scp->reply.srb, scp->filename, - SSH_FXF_WRITE | SSH_FXF_CREAT | SSH_FXF_TRUNC, - scp->attrs); - if (scp->reply.err) { - scp->errmsg = dupprintf( - "'%.*s': unable to open file: %s", - PTRLEN_PRINTF(scp->filename), scp->reply.errmsg); - goto done; - } - - /* - * Now send an ack, and read the file data. - */ - sshfwd_write(scp->sc, "\0", 1); - scp->file_offset = 0; - while (scp->file_offset < scp->file_size) { - ptrlen data; - uint64_t this_len, remaining; - - crMaybeWaitUntilV( - scp->input_eof || bufchain_size(&scp->data) > 0); - if (scp->input_eof) { - sftpsrv_close(scp->sf, &scp->reply.srb, - scp->reply.handle); - goto done; - } - - data = bufchain_prefix(&scp->data); - this_len = data.len; - remaining = scp->file_size - scp->file_offset; - if (this_len > remaining) - this_len = remaining; - sftpsrv_write(scp->sf, &scp->reply.srb, - scp->reply.handle, scp->file_offset, - make_ptrlen(data.ptr, this_len)); - if (scp->reply.err) { - scp->errmsg = dupprintf( - "'%.*s': unable to write to file: %s", - PTRLEN_PRINTF(scp->filename), scp->reply.errmsg); - goto done; - } - bufchain_consume(&scp->data, this_len); - scp->file_offset += this_len; - } - - /* - * Wait for the trailing NUL byte. - */ - crMaybeWaitUntilV( - scp->input_eof || bufchain_size(&scp->data) > 0); - if (scp->input_eof) { - sftpsrv_close(scp->sf, &scp->reply.srb, - scp->reply.handle); - goto done; - } - bufchain_consume(&scp->data, 1); - } - } else if (scp->command_chr == 'E') { - if (!scp->head) { - scp->errmsg = dupstr("received E command without matching D"); - goto done; - } - scp_sink_pop(scp); - scp->got_file_times = false; - } else { - ptrlen cmd_pl; - - /* - * Also come here if any of the above cases run into - * parsing difficulties. - */ - parse_error: - cmd_pl = ptrlen_from_strbuf(scp->command); - scp->errmsg = dupprintf("unrecognised scp command '%.*s'", - PTRLEN_PRINTF(cmd_pl)); - goto done; - } - } - - done: - if (scp->errmsg) { - sshfwd_write_ext(scp->sc, true, scp->errmsg, strlen(scp->errmsg)); - sshfwd_write_ext(scp->sc, true, "\012", 1); - sshfwd_send_exit_status(scp->sc, 1); - } else { - sshfwd_send_exit_status(scp->sc, 0); - } - sshfwd_write_eof(scp->sc); - sshfwd_initiate_close(scp->sc, scp->errmsg); - while (1) crReturnV; - - crFinishV; -} - -static size_t scp_sink_send(ScpServer *s, const void *data, size_t length) -{ - ScpSink *scp = container_of(s, ScpSink, scpserver); - - if (!scp->input_eof) { - bufchain_add(&scp->data, data, length); - scp_sink_coroutine(scp); - } - return 0; -} - -static void scp_sink_eof(ScpServer *s) -{ - ScpSink *scp = container_of(s, ScpSink, scpserver); - - scp->input_eof = true; - scp_sink_coroutine(scp); -} - -/* ---------------------------------------------------------------------- - * Top-level error handler, instantiated in the case where the user - * sent a command starting with "scp " that we couldn't make sense of. - */ - -typedef struct ScpError ScpError; - -struct ScpError { - SshChannel *sc; - char *message; - ScpServer scpserver; -}; - -static void scp_error_free(ScpServer *s); - -static size_t scp_error_send(ScpServer *s, const void *data, size_t length) -{ return 0; } -static void scp_error_eof(ScpServer *s) {} -static void scp_error_throttle(ScpServer *s, bool throttled) {} - -static const ScpServerVtable ScpError_ScpServer_vt = { - .free = scp_error_free, - .send = scp_error_send, - .throttle = scp_error_throttle, - .eof = scp_error_eof, -}; - -static void scp_error_send_message_cb(void *vscp) -{ - ScpError *scp = (ScpError *)vscp; - sshfwd_write_ext(scp->sc, true, scp->message, strlen(scp->message)); - sshfwd_write_ext(scp->sc, true, "\n", 1); - sshfwd_send_exit_status(scp->sc, 1); - sshfwd_write_eof(scp->sc); - sshfwd_initiate_close(scp->sc, scp->message); -} - -static PRINTF_LIKE(2, 3) ScpError *scp_error_new( - SshChannel *sc, const char *fmt, ...) -{ - va_list ap; - ScpError *scp = snew(ScpError); - - memset(scp, 0, sizeof(*scp)); - - scp->scpserver.vt = &ScpError_ScpServer_vt; - scp->sc = sc; - - va_start(ap, fmt); - scp->message = dupvprintf(fmt, ap); - va_end(ap); - - queue_toplevel_callback(scp_error_send_message_cb, scp); - - return scp; -} - -static void scp_error_free(ScpServer *s) -{ - ScpError *scp = container_of(s, ScpError, scpserver); - - sfree(scp->message); - - delete_callbacks_for_context(scp); - - sfree(scp); -} - -/* ---------------------------------------------------------------------- - * Top-level entry point, which parses a command sent from the SSH - * client, and if it recognises it as an scp command, instantiates an - * appropriate ScpServer implementation and returns it. - */ - -ScpServer *scp_recognise_exec( - SshChannel *sc, const SftpServerVtable *sftpserver_vt, ptrlen command) -{ - bool recursive = false, preserve = false; - bool targetshouldbedirectory = false; - ptrlen command_orig = command; - - if (!ptrlen_startswith(command, PTRLEN_LITERAL("scp "), &command)) - return NULL; - - while (1) { - if (ptrlen_startswith(command, PTRLEN_LITERAL("-v "), &command)) { - /* Enable verbose mode in the server, which we ignore */ - continue; - } - if (ptrlen_startswith(command, PTRLEN_LITERAL("-r "), &command)) { - recursive = true; - continue; - } - if (ptrlen_startswith(command, PTRLEN_LITERAL("-p "), &command)) { - preserve = true; - continue; - } - if (ptrlen_startswith(command, PTRLEN_LITERAL("-d "), &command)) { - targetshouldbedirectory = true; - continue; - } - break; - } - - if (ptrlen_startswith(command, PTRLEN_LITERAL("-t "), &command)) { - ScpSink *scp = scp_sink_new(sc, sftpserver_vt, command, - targetshouldbedirectory); - return &scp->scpserver; - } else if (ptrlen_startswith(command, PTRLEN_LITERAL("-f "), &command)) { - ScpSource *scp = scp_source_new(sc, sftpserver_vt, command); - scp->recursive = recursive; - scp->send_file_times = preserve; - return &scp->scpserver; - } else { - ScpError *scp = scp_error_new( - sc, "Unable to parse scp command: '%.*s'", - PTRLEN_PRINTF(command_orig)); - return &scp->scpserver; - } -} diff --git a/sesschan.c b/sesschan.c deleted file mode 100644 index 4ab709da..00000000 --- a/sesschan.c +++ /dev/null @@ -1,787 +0,0 @@ -/* - * Implement the "session" channel type for the SSH server. - */ - -#include -#include -#include -#include - -#include "putty.h" -#include "ssh.h" -#include "sshchan.h" -#include "sshserver.h" -#include "sftp.h" - -struct agentfwd { - ConnectionLayer *cl; - Socket *socket; - Plug plug; -}; - -typedef struct sesschan { - SshChannel *c; - - LogContext *parent_logctx, *child_logctx; - Conf *conf; - const SftpServerVtable *sftpserver_vt; - - LogPolicy logpolicy; - Seat seat; - - bool want_pty; - struct ssh_ttymodes ttymodes; - int wc, hc, wp, hp; - strbuf *termtype; - - bool ignoring_input; - bool seen_eof, seen_exit; - - Plug xfwd_plug; - int n_x11_sockets; - Socket *x11_sockets[MAX_X11_SOCKETS]; - - agentfwd *agent; - - Backend *backend; - - bufchain subsys_input; - SftpServer *sftpsrv; - ScpServer *scpsrv; - const SshServerConfig *ssc; - - Channel chan; -} sesschan; - -static void sesschan_free(Channel *chan); -static size_t sesschan_send( - Channel *chan, bool is_stderr, const void *, size_t); -static void sesschan_send_eof(Channel *chan); -static char *sesschan_log_close_msg(Channel *chan); -static bool sesschan_want_close(Channel *, bool, bool); -static void sesschan_set_input_wanted(Channel *chan, bool wanted); -static bool sesschan_run_shell(Channel *chan); -static bool sesschan_run_command(Channel *chan, ptrlen command); -static bool sesschan_run_subsystem(Channel *chan, ptrlen subsys); -static bool sesschan_enable_x11_forwarding( - Channel *chan, bool oneshot, ptrlen authproto, ptrlen authdata, - unsigned screen_number); -static bool sesschan_enable_agent_forwarding(Channel *chan); -static bool sesschan_allocate_pty( - Channel *chan, ptrlen termtype, unsigned width, unsigned height, - unsigned pixwidth, unsigned pixheight, struct ssh_ttymodes modes); -static bool sesschan_set_env(Channel *chan, ptrlen var, ptrlen value); -static bool sesschan_send_break(Channel *chan, unsigned length); -static bool sesschan_send_signal(Channel *chan, ptrlen signame); -static bool sesschan_change_window_size( - Channel *chan, unsigned width, unsigned height, - unsigned pixwidth, unsigned pixheight); - -static const ChannelVtable sesschan_channelvt = { - .free = sesschan_free, - .open_confirmation = chan_remotely_opened_confirmation, - .open_failed = chan_remotely_opened_failure, - .send = sesschan_send, - .send_eof = sesschan_send_eof, - .set_input_wanted = sesschan_set_input_wanted, - .log_close_msg = sesschan_log_close_msg, - .want_close = sesschan_want_close, - .rcvd_exit_status = chan_no_exit_status, - .rcvd_exit_signal = chan_no_exit_signal, - .rcvd_exit_signal_numeric = chan_no_exit_signal_numeric, - .run_shell = sesschan_run_shell, - .run_command = sesschan_run_command, - .run_subsystem = sesschan_run_subsystem, - .enable_x11_forwarding = sesschan_enable_x11_forwarding, - .enable_agent_forwarding = sesschan_enable_agent_forwarding, - .allocate_pty = sesschan_allocate_pty, - .set_env = sesschan_set_env, - .send_break = sesschan_send_break, - .send_signal = sesschan_send_signal, - .change_window_size = sesschan_change_window_size, - .request_response = chan_no_request_response, -}; - -static size_t sftp_chan_send( - Channel *chan, bool is_stderr, const void *, size_t); -static void sftp_chan_send_eof(Channel *chan); -static char *sftp_log_close_msg(Channel *chan); - -static const ChannelVtable sftp_channelvt = { - .free = sesschan_free, - .open_confirmation = chan_remotely_opened_confirmation, - .open_failed = chan_remotely_opened_failure, - .send = sftp_chan_send, - .send_eof = sftp_chan_send_eof, - .set_input_wanted = sesschan_set_input_wanted, - .log_close_msg = sftp_log_close_msg, - .want_close = chan_default_want_close, - .rcvd_exit_status = chan_no_exit_status, - .rcvd_exit_signal = chan_no_exit_signal, - .rcvd_exit_signal_numeric = chan_no_exit_signal_numeric, - .run_shell = chan_no_run_shell, - .run_command = chan_no_run_command, - .run_subsystem = chan_no_run_subsystem, - .enable_x11_forwarding = chan_no_enable_x11_forwarding, - .enable_agent_forwarding = chan_no_enable_agent_forwarding, - .allocate_pty = chan_no_allocate_pty, - .set_env = chan_no_set_env, - .send_break = chan_no_send_break, - .send_signal = chan_no_send_signal, - .change_window_size = chan_no_change_window_size, - .request_response = chan_no_request_response, -}; - -static size_t scp_chan_send( - Channel *chan, bool is_stderr, const void *, size_t); -static void scp_chan_send_eof(Channel *chan); -static void scp_set_input_wanted(Channel *chan, bool wanted); -static char *scp_log_close_msg(Channel *chan); - -static const ChannelVtable scp_channelvt = { - .free = sesschan_free, - .open_confirmation = chan_remotely_opened_confirmation, - .open_failed = chan_remotely_opened_failure, - .send = scp_chan_send, - .send_eof = scp_chan_send_eof, - .set_input_wanted = scp_set_input_wanted, - .log_close_msg = scp_log_close_msg, - .want_close = chan_default_want_close, - .rcvd_exit_status = chan_no_exit_status, - .rcvd_exit_signal = chan_no_exit_signal, - .rcvd_exit_signal_numeric = chan_no_exit_signal_numeric, - .run_shell = chan_no_run_shell, - .run_command = chan_no_run_command, - .run_subsystem = chan_no_run_subsystem, - .enable_x11_forwarding = chan_no_enable_x11_forwarding, - .enable_agent_forwarding = chan_no_enable_agent_forwarding, - .allocate_pty = chan_no_allocate_pty, - .set_env = chan_no_set_env, - .send_break = chan_no_send_break, - .send_signal = chan_no_send_signal, - .change_window_size = chan_no_change_window_size, - .request_response = chan_no_request_response, -}; - -static void sesschan_eventlog(LogPolicy *lp, const char *event) {} -static void sesschan_logging_error(LogPolicy *lp, const char *event) {} -static int sesschan_askappend( - LogPolicy *lp, Filename *filename, - void (*callback)(void *ctx, int result), void *ctx) { return 2; } - -static const LogPolicyVtable sesschan_logpolicy_vt = { - .eventlog = sesschan_eventlog, - .askappend = sesschan_askappend, - .logging_error = sesschan_logging_error, - .verbose = null_lp_verbose_no, -}; - -static size_t sesschan_seat_output( - Seat *, bool is_stderr, const void *, size_t); -static bool sesschan_seat_eof(Seat *); -static void sesschan_notify_remote_exit(Seat *seat); -static void sesschan_connection_fatal(Seat *seat, const char *message); -static bool sesschan_get_window_pixel_size(Seat *seat, int *w, int *h); - -static const SeatVtable sesschan_seat_vt = { - .output = sesschan_seat_output, - .eof = sesschan_seat_eof, - .get_userpass_input = nullseat_get_userpass_input, - .notify_remote_exit = sesschan_notify_remote_exit, - .connection_fatal = sesschan_connection_fatal, - .update_specials_menu = nullseat_update_specials_menu, - .get_ttymode = nullseat_get_ttymode, - .set_busy_status = nullseat_set_busy_status, - .verify_ssh_host_key = nullseat_verify_ssh_host_key, - .confirm_weak_crypto_primitive = nullseat_confirm_weak_crypto_primitive, - .confirm_weak_cached_hostkey = nullseat_confirm_weak_cached_hostkey, - .is_utf8 = nullseat_is_never_utf8, - .echoedit_update = nullseat_echoedit_update, - .get_x_display = nullseat_get_x_display, - .get_windowid = nullseat_get_windowid, - .get_window_pixel_size = sesschan_get_window_pixel_size, - .stripctrl_new = nullseat_stripctrl_new, - .set_trust_status = nullseat_set_trust_status, - .verbose = nullseat_verbose_no, - .interactive = nullseat_interactive_no, - .get_cursor_position = nullseat_get_cursor_position, -}; - -Channel *sesschan_new(SshChannel *c, LogContext *logctx, - const SftpServerVtable *sftpserver_vt, - const SshServerConfig *ssc) -{ - sesschan *sess = snew(sesschan); - memset(sess, 0, sizeof(sesschan)); - - sess->c = c; - sess->chan.vt = &sesschan_channelvt; - sess->chan.initial_fixed_window_size = 0; - sess->parent_logctx = logctx; - sess->ssc = ssc; - - /* Start with a completely default Conf */ - sess->conf = conf_new(); - load_open_settings(NULL, sess->conf); - - /* Set close-on-exit = true to suppress uxpty.c's "[pterm: process - * terminated with status x]" message */ - conf_set_int(sess->conf, CONF_close_on_exit, FORCE_ON); - - sess->seat.vt = &sesschan_seat_vt; - sess->logpolicy.vt = &sesschan_logpolicy_vt; - sess->child_logctx = log_init(&sess->logpolicy, sess->conf); - - sess->sftpserver_vt = sftpserver_vt; - - bufchain_init(&sess->subsys_input); - - return &sess->chan; -} - -static void sesschan_free(Channel *chan) -{ - sesschan *sess = container_of(chan, sesschan, chan); - int i; - - delete_callbacks_for_context(sess); - conf_free(sess->conf); - if (sess->backend) - backend_free(sess->backend); - bufchain_clear(&sess->subsys_input); - if (sess->sftpsrv) - sftpsrv_free(sess->sftpsrv); - for (i = 0; i < sess->n_x11_sockets; i++) - sk_close(sess->x11_sockets[i]); - if (sess->agent) - agentfwd_free(sess->agent); - - sfree(sess); -} - -static size_t sesschan_send(Channel *chan, bool is_stderr, - const void *data, size_t length) -{ - sesschan *sess = container_of(chan, sesschan, chan); - - if (!sess->backend || sess->ignoring_input) - return 0; - - return backend_send(sess->backend, data, length); -} - -static void sesschan_send_eof(Channel *chan) -{ - sesschan *sess = container_of(chan, sesschan, chan); - if (sess->backend) - backend_special(sess->backend, SS_EOF, 0); -} - -static char *sesschan_log_close_msg(Channel *chan) -{ - return dupstr("Session channel closed"); -} - -static void sesschan_set_input_wanted(Channel *chan, bool wanted) -{ - /* I don't think we need to do anything here */ -} - -static void sesschan_start_backend(sesschan *sess, const char *cmd) -{ - /* - * List of environment variables that we should not pass through - * from the login session Uppity was run in (which, it being a - * test server, there will usually be one of). These variables - * will be set as part of X or agent forwarding, and shouldn't be - * confusingly set in the absence of that. - * - * (DISPLAY must also be cleared, but uxpty.c will do that anyway - * when our get_x_display method returns NULL.) - */ - static const char *const env_to_unset[] = { - "XAUTHORITY", "SSH_AUTH_SOCK", "SSH_AGENT_PID", - NULL /* terminator */ - }; - - sess->backend = pty_backend_create( - &sess->seat, sess->child_logctx, sess->conf, NULL, cmd, - sess->ttymodes, !sess->want_pty, sess->ssc->session_starting_dir, - env_to_unset); - backend_size(sess->backend, sess->wc, sess->hc); -} - -bool sesschan_run_shell(Channel *chan) -{ - sesschan *sess = container_of(chan, sesschan, chan); - - if (sess->backend) - return false; - - sesschan_start_backend(sess, NULL); - return true; -} - -bool sesschan_run_command(Channel *chan, ptrlen command) -{ - sesschan *sess = container_of(chan, sesschan, chan); - - if (sess->backend) - return false; - - /* FIXME: make this possible to configure off */ - if ((sess->scpsrv = scp_recognise_exec(sess->c, sess->sftpserver_vt, - command)) != NULL) { - sess->chan.vt = &scp_channelvt; - logevent(sess->parent_logctx, "Starting built-in SCP server"); - return true; - } - - char *command_str = mkstr(command); - sesschan_start_backend(sess, command_str); - sfree(command_str); - - return true; -} - -bool sesschan_run_subsystem(Channel *chan, ptrlen subsys) -{ - sesschan *sess = container_of(chan, sesschan, chan); - - if (ptrlen_eq_string(subsys, "sftp") && sess->sftpserver_vt) { - sess->sftpsrv = sftpsrv_new(sess->sftpserver_vt); - sess->chan.vt = &sftp_channelvt; - logevent(sess->parent_logctx, "Starting built-in SFTP subsystem"); - return true; - } - - return false; -} - -static void fwd_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, - const char *error_msg, int error_code) -{ /* don't expect any weirdnesses from a listening socket */ } -static void fwd_closing(Plug *plug, const char *error_msg, int error_code, - bool calling_back) -{ /* not here, either */ } - -static int xfwd_accepting(Plug *p, accept_fn_t constructor, accept_ctx_t ctx) -{ - sesschan *sess = container_of(p, sesschan, xfwd_plug); - Plug *plug; - Channel *chan; - Socket *s; - SocketPeerInfo *pi; - const char *err; - - chan = portfwd_raw_new(sess->c->cl, &plug, false); - s = constructor(ctx, plug); - if ((err = sk_socket_error(s)) != NULL) { - portfwd_raw_free(chan); - return 1; - } - pi = sk_peer_info(s); - portfwd_raw_setup(chan, s, ssh_serverside_x11_open(sess->c->cl, chan, pi)); - sk_free_peer_info(pi); - - return 0; -} - -static const PlugVtable xfwd_plugvt = { - .log = fwd_log, - .closing = fwd_closing, - .accepting = xfwd_accepting, -}; - -bool sesschan_enable_x11_forwarding( - Channel *chan, bool oneshot, ptrlen authproto, ptrlen authdata_hex, - unsigned screen_number) -{ - sesschan *sess = container_of(chan, sesschan, chan); - strbuf *authdata_bin; - size_t i; - - if (oneshot) - return false; /* not supported */ - - /* - * Decode the authorisation data from ASCII hex into binary. - */ - if (authdata_hex.len % 2) - return false; /* expected an even number of digits */ - authdata_bin = strbuf_new_nm(); - for (i = 0; i < authdata_hex.len; i += 2) { - const unsigned char *hex = authdata_hex.ptr; - char hexbuf[3]; - - if (!isxdigit(hex[i]) || !isxdigit(hex[i+1])) { - strbuf_free(authdata_bin); - return false; /* not hex */ - } - - hexbuf[0] = hex[i]; - hexbuf[1] = hex[i+1]; - hexbuf[2] = '\0'; - put_byte(authdata_bin, strtoul(hexbuf, NULL, 16)); - } - - sess->xfwd_plug.vt = &xfwd_plugvt; - - char *screensuffix = dupprintf(".%u", screen_number); - - sess->n_x11_sockets = platform_make_x11_server( - &sess->xfwd_plug, appname, 10, screensuffix, - authproto, ptrlen_from_strbuf(authdata_bin), - sess->x11_sockets, sess->conf); - - sfree(screensuffix); - strbuf_free(authdata_bin); - return sess->n_x11_sockets != 0; -} - -static int agentfwd_accepting( - Plug *p, accept_fn_t constructor, accept_ctx_t ctx) -{ - agentfwd *agent = container_of(p, agentfwd, plug); - Plug *plug; - Channel *chan; - Socket *s; - const char *err; - - chan = portfwd_raw_new(agent->cl, &plug, false); - s = constructor(ctx, plug); - if ((err = sk_socket_error(s)) != NULL) { - portfwd_raw_free(chan); - return 1; - } - portfwd_raw_setup(chan, s, ssh_serverside_agent_open(agent->cl, chan)); - - return 0; -} - -static const PlugVtable agentfwd_plugvt = { - .log = fwd_log, - .closing = fwd_closing, - .accepting = agentfwd_accepting, -}; - -agentfwd *agentfwd_new(ConnectionLayer *cl, char **socketname_out) -{ - agentfwd *agent = snew(agentfwd); - agent->cl = cl; - agent->plug.vt = &agentfwd_plugvt; - - char *dir_prefix = dupprintf("/tmp/%s-agentfwd", appname); - char *error = NULL, *socketname = NULL; - agent->socket = platform_make_agent_socket( - &agent->plug, dir_prefix, &error, &socketname); - sfree(dir_prefix); - sfree(error); - - if (!agent->socket) { - sfree(agent); - sfree(socketname); - return NULL; - } - - *socketname_out = socketname; - return agent; -} - -void agentfwd_free(agentfwd *agent) -{ - if (agent->socket) - sk_close(agent->socket); - sfree(agent); -} - -bool sesschan_enable_agent_forwarding(Channel *chan) -{ - sesschan *sess = container_of(chan, sesschan, chan); - char *socketname; - - assert(!sess->agent); - - sess->agent = agentfwd_new(sess->c->cl, &socketname); - - if (!sess->agent) - return false; - - conf_set_str_str(sess->conf, CONF_environmt, "SSH_AUTH_SOCK", socketname); - sfree(socketname); - return true; -} - -bool sesschan_allocate_pty( - Channel *chan, ptrlen termtype, unsigned width, unsigned height, - unsigned pixwidth, unsigned pixheight, struct ssh_ttymodes modes) -{ - sesschan *sess = container_of(chan, sesschan, chan); - char *s; - - if (sess->want_pty) - return false; - - s = mkstr(termtype); - conf_set_str(sess->conf, CONF_termtype, s); - sfree(s); - - sess->want_pty = true; - sess->ttymodes = modes; - sess->wc = width; - sess->hc = height; - sess->wp = pixwidth; - sess->hp = pixheight; - - return true; -} - -bool sesschan_set_env(Channel *chan, ptrlen var, ptrlen value) -{ - sesschan *sess = container_of(chan, sesschan, chan); - - char *svar = mkstr(var), *svalue = mkstr(value); - conf_set_str_str(sess->conf, CONF_environmt, svar, svalue); - sfree(svar); - sfree(svalue); - - return true; -} - -bool sesschan_send_break(Channel *chan, unsigned length) -{ - sesschan *sess = container_of(chan, sesschan, chan); - - if (sess->backend) { - /* We ignore the break length. We could pass it through as the - * 'arg' parameter, and have uxpty.c collect it and pass it on - * to tcsendbreak, but since tcsendbreak in turn assigns - * implementation-defined semantics to _its_ duration - * parameter, this all just sounds too difficult. */ - backend_special(sess->backend, SS_BRK, 0); - return true; - } - return false; -} - -bool sesschan_send_signal(Channel *chan, ptrlen signame) -{ - sesschan *sess = container_of(chan, sesschan, chan); - - /* Start with a code that definitely isn't a signal (or indeed a - * special command at all), to indicate 'nothing matched'. */ - SessionSpecialCode code = SS_EXITMENU; - - #define SIGNAL_SUB(name) \ - if (ptrlen_eq_string(signame, #name)) code = SS_SIG ## name; - #define SIGNAL_MAIN(name, text) SIGNAL_SUB(name) - #include "sshsignals.h" - #undef SIGNAL_MAIN - #undef SIGNAL_SUB - - if (code == SS_EXITMENU) - return false; - - backend_special(sess->backend, code, 0); - return true; -} - -bool sesschan_change_window_size( - Channel *chan, unsigned width, unsigned height, - unsigned pixwidth, unsigned pixheight) -{ - sesschan *sess = container_of(chan, sesschan, chan); - - if (!sess->want_pty) - return false; - - sess->wc = width; - sess->hc = height; - sess->wp = pixwidth; - sess->hp = pixheight; - - if (sess->backend) - backend_size(sess->backend, sess->wc, sess->hc); - - return true; -} - -static size_t sesschan_seat_output( - Seat *seat, bool is_stderr, const void *data, size_t len) -{ - sesschan *sess = container_of(seat, sesschan, seat); - return sshfwd_write_ext(sess->c, is_stderr, data, len); -} - -static void sesschan_check_close_callback(void *vctx) -{ - sesschan *sess = (sesschan *)vctx; - - /* - * Once we've seen incoming EOF from the backend (aka EIO from the - * pty master) and also passed on the process's exit status, we - * should proactively initiate closure of the session channel. - */ - if (sess->seen_eof && sess->seen_exit) - sshfwd_initiate_close(sess->c, NULL); -} - -static bool sesschan_want_close(Channel *chan, bool seen_eof, bool rcvd_eof) -{ - sesschan *sess = container_of(chan, sesschan, chan); - - /* - * Similarly to above, we don't want to initiate channel closure - * until we've sent the process's exit status, _even_ if EOF of - * the actual data stream has happened in both directions. - */ - return (sess->seen_eof && sess->seen_exit); -} - -static bool sesschan_seat_eof(Seat *seat) -{ - sesschan *sess = container_of(seat, sesschan, seat); - - sshfwd_write_eof(sess->c); - sess->seen_eof = true; - - queue_toplevel_callback(sesschan_check_close_callback, sess); - return true; -} - -static void sesschan_notify_remote_exit(Seat *seat) -{ - sesschan *sess = container_of(seat, sesschan, seat); - - if (!sess->backend) - return; - - bool got_signal = false; - if (!sess->ssc->exit_signal_numeric) { - char *sigmsg; - ptrlen signame = pty_backend_exit_signame(sess->backend, &sigmsg); - - if (signame.len) { - if (!sigmsg) - sigmsg = dupstr(""); - - sshfwd_send_exit_signal( - sess->c, signame, false, ptrlen_from_asciz(sigmsg)); - - got_signal = true; - } - - sfree(sigmsg); - } else { - int signum = pty_backend_exit_signum(sess->backend); - - if (signum >= 0) { - sshfwd_send_exit_signal_numeric(sess->c, signum, false, - PTRLEN_LITERAL("")); - got_signal = true; - } - } - - if (!got_signal) - sshfwd_send_exit_status(sess->c, backend_exitcode(sess->backend)); - - sess->seen_exit = true; - queue_toplevel_callback(sesschan_check_close_callback, sess); -} - -static void sesschan_connection_fatal(Seat *seat, const char *message) -{ - sesschan *sess = container_of(seat, sesschan, seat); - - /* Closest translation I can think of */ - sshfwd_send_exit_signal( - sess->c, PTRLEN_LITERAL("HUP"), false, ptrlen_from_asciz(message)); - - sess->ignoring_input = true; -} - -static bool sesschan_get_window_pixel_size(Seat *seat, int *width, int *height) -{ - sesschan *sess = container_of(seat, sesschan, seat); - - *width = sess->wp; - *height = sess->hp; - - return true; -} - -/* ---------------------------------------------------------------------- - * Built-in SFTP subsystem. - */ - -static size_t sftp_chan_send(Channel *chan, bool is_stderr, - const void *data, size_t length) -{ - sesschan *sess = container_of(chan, sesschan, chan); - - bufchain_add(&sess->subsys_input, data, length); - - while (bufchain_size(&sess->subsys_input) >= 4) { - char lenbuf[4]; - unsigned pktlen; - struct sftp_packet *pkt, *reply; - - bufchain_fetch(&sess->subsys_input, lenbuf, 4); - pktlen = GET_32BIT_MSB_FIRST(lenbuf); - - if (bufchain_size(&sess->subsys_input) - 4 < pktlen) - break; /* wait for more data */ - - bufchain_consume(&sess->subsys_input, 4); - pkt = sftp_recv_prepare(pktlen); - bufchain_fetch_consume(&sess->subsys_input, pkt->data, pktlen); - sftp_recv_finish(pkt); - reply = sftp_handle_request(sess->sftpsrv, pkt); - sftp_pkt_free(pkt); - - sftp_send_prepare(reply); - sshfwd_write(sess->c, reply->data, reply->length); - sftp_pkt_free(reply); - } - - return 0; -} - -static void sftp_chan_send_eof(Channel *chan) -{ - sesschan *sess = container_of(chan, sesschan, chan); - sshfwd_write_eof(sess->c); -} - -static char *sftp_log_close_msg(Channel *chan) -{ - return dupstr("Session channel (SFTP) closed"); -} - -/* ---------------------------------------------------------------------- - * Built-in SCP subsystem. - */ - -static size_t scp_chan_send(Channel *chan, bool is_stderr, - const void *data, size_t length) -{ - sesschan *sess = container_of(chan, sesschan, chan); - return scp_send(sess->scpsrv, data, length); -} - -static void scp_chan_send_eof(Channel *chan) -{ - sesschan *sess = container_of(chan, sesschan, chan); - scp_eof(sess->scpsrv); -} - -static char *scp_log_close_msg(Channel *chan) -{ - return dupstr("Session channel (SCP) closed"); -} - -static void scp_set_input_wanted(Channel *chan, bool wanted) -{ - sesschan *sess = container_of(chan, sesschan, chan); - scp_throttle(sess->scpsrv, !wanted); -} diff --git a/settings.c b/settings.c index 547af0dc..3f8f2e92 100644 --- a/settings.c +++ b/settings.c @@ -8,8 +8,8 @@ #include "putty.h" #include "storage.h" #ifndef NO_GSSAPI -#include "sshgssc.h" -#include "sshgss.h" +#include "ssh/gssc.h" +#include "ssh/gss.h" #endif diff --git a/sftp.c b/sftp.c deleted file mode 100644 index a76702f8..00000000 --- a/sftp.c +++ /dev/null @@ -1,1205 +0,0 @@ -/* - * sftp.c: SFTP generic client code. - */ - -#include -#include -#include -#include -#include - -#include "misc.h" -#include "tree234.h" -#include "sftp.h" - -static const char *fxp_error_message; -static int fxp_errtype; - -static void fxp_internal_error(const char *msg); - -/* ---------------------------------------------------------------------- - * Client-specific parts of the send- and receive-packet system. - */ - -bool sftp_send(struct sftp_packet *pkt) -{ - bool ret; - sftp_send_prepare(pkt); - ret = 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]; - - if (!sftp_recvdata(x, 4)) - return NULL; - - /* Impose _some_ upper bound on packet size. We never expect to - * receive more than 32K of data in response to an FXP_READ, - * because we decide how much data to ask for. FXP_READDIR and - * pathname-returning things like FXP_REALPATH don't have an - * explicit bound, so I suppose we just have to trust the server - * to be sensible. */ - unsigned pktlen = GET_32BIT_MSB_FIRST(x); - if (pktlen > (1<<20)) - return NULL; - - pkt = sftp_recv_prepare(pktlen); - - if (!sftp_recvdata(pkt->data, pkt->length)) { - sftp_pkt_free(pkt); - return NULL; - } - - if (!sftp_recv_finish(pkt)) { - sftp_pkt_free(pkt); - return NULL; - } - - return pkt; -} - -/* ---------------------------------------------------------------------- - * Request ID allocation and temporary dispatch routines. - */ - -#define REQUEST_ID_OFFSET 256 - -struct sftp_request { - unsigned id; - bool 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 = false; - 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 = true; -} - -struct sftp_request *sftp_find_request(struct sftp_packet *pktin) -{ - unsigned id; - struct sftp_request *req; - - if (!pktin) { - fxp_internal_error("did not receive a valid SFTP packet\n"); - return NULL; - } - - id = get_uint32(pktin); - if (get_err(pktin)) { - 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"); - return NULL; - } - - del234(sftp_requests, req); - - return req; -} - -/* ---------------------------------------------------------------------- - * 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 { - fxp_errtype = get_uint32(pktin); - if (get_err(pktin)) { - fxp_error_message = "malformed FXP_STATUS packet"; - fxp_errtype = -1; - } else { - if (fxp_errtype < 0 || fxp_errtype >= lenof(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(const 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. - */ -bool fxp_init(void) -{ - struct sftp_packet *pktout, *pktin; - unsigned long remotever; - - pktout = sftp_pkt_init(SSH_FXP_INIT); - put_uint32(pktout, SFTP_PROTO_VERSION); - sftp_send(pktout); - - pktin = sftp_recv(); - if (!pktin) { - fxp_internal_error("could not connect"); - return false; - } - if (pktin->type != SSH_FXP_VERSION) { - fxp_internal_error("did not receive FXP_VERSION"); - sftp_pkt_free(pktin); - return false; - } - remotever = get_uint32(pktin); - if (get_err(pktin)) { - fxp_internal_error("malformed FXP_VERSION packet"); - sftp_pkt_free(pktin); - return false; - } - if (remotever > SFTP_PROTO_VERSION) { - fxp_internal_error - ("remote protocol is more advanced than we support"); - sftp_pkt_free(pktin); - return false; - } - /* - * 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 true; -} - -/* - * Canonify a pathname. - */ -struct sftp_request *fxp_realpath_send(const char *path) -{ - struct sftp_request *req = sftp_alloc_request(); - struct sftp_packet *pktout; - - pktout = sftp_pkt_init(SSH_FXP_REALPATH); - put_uint32(pktout, req->id); - put_stringz(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; - ptrlen name; - - count = get_uint32(pktin); - if (get_err(pktin) || count != 1) { - fxp_internal_error("REALPATH did not return name count of 1\n"); - sftp_pkt_free(pktin); - return NULL; - } - name = get_string(pktin); - if (get_err(pktin)) { - fxp_internal_error("REALPATH returned malformed FXP_NAME\n"); - sftp_pkt_free(pktin); - return NULL; - } - path = mkstr(name); - 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(const char *path, int type, - const struct fxp_attrs *attrs) -{ - struct sftp_request *req = sftp_alloc_request(); - struct sftp_packet *pktout; - - pktout = sftp_pkt_init(SSH_FXP_OPEN); - put_uint32(pktout, req->id); - put_stringz(pktout, path); - put_uint32(pktout, type); - put_fxp_attrs(pktout, attrs ? *attrs : no_attrs); - sftp_send(pktout); - - return req; -} - -static struct fxp_handle *fxp_got_handle(struct sftp_packet *pktin) -{ - ptrlen id; - struct fxp_handle *handle; - - id = get_string(pktin); - if (get_err(pktin)) { - fxp_internal_error("received malformed FXP_HANDLE"); - sftp_pkt_free(pktin); - return NULL; - } - handle = snew(struct fxp_handle); - handle->hstring = mkstr(id); - handle->hlen = id.len; - sftp_pkt_free(pktin); - return handle; -} - -struct fxp_handle *fxp_open_recv(struct sftp_packet *pktin, - struct sftp_request *req) -{ - sfree(req); - - if (pktin->type == SSH_FXP_HANDLE) { - return fxp_got_handle(pktin); - } else { - fxp_got_status(pktin); - sftp_pkt_free(pktin); - return NULL; - } -} - -/* - * Open a directory. - */ -struct sftp_request *fxp_opendir_send(const char *path) -{ - struct sftp_request *req = sftp_alloc_request(); - struct sftp_packet *pktout; - - pktout = sftp_pkt_init(SSH_FXP_OPENDIR); - put_uint32(pktout, req->id); - put_stringz(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) { - return fxp_got_handle(pktin); - } 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); - put_uint32(pktout, req->id); - put_string(pktout, handle->hstring, handle->hlen); - sftp_send(pktout); - - sfree(handle->hstring); - sfree(handle); - - return req; -} - -bool fxp_close_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; -} - -struct sftp_request *fxp_mkdir_send(const char *path, - const struct fxp_attrs *attrs) -{ - struct sftp_request *req = sftp_alloc_request(); - struct sftp_packet *pktout; - - pktout = sftp_pkt_init(SSH_FXP_MKDIR); - put_uint32(pktout, req->id); - put_stringz(pktout, path); - put_fxp_attrs(pktout, attrs ? *attrs : no_attrs); - sftp_send(pktout); - - return req; -} - -bool fxp_mkdir_recv(struct sftp_packet *pktin, struct sftp_request *req) -{ - int id; - sfree(req); - id = fxp_got_status(pktin); - sftp_pkt_free(pktin); - return id == 1; -} - -struct sftp_request *fxp_rmdir_send(const char *path) -{ - struct sftp_request *req = sftp_alloc_request(); - struct sftp_packet *pktout; - - pktout = sftp_pkt_init(SSH_FXP_RMDIR); - put_uint32(pktout, req->id); - put_stringz(pktout, path); - sftp_send(pktout); - - return req; -} - -bool fxp_rmdir_recv(struct sftp_packet *pktin, struct sftp_request *req) -{ - int id; - sfree(req); - id = fxp_got_status(pktin); - sftp_pkt_free(pktin); - return id == 1; -} - -struct sftp_request *fxp_remove_send(const char *fname) -{ - struct sftp_request *req = sftp_alloc_request(); - struct sftp_packet *pktout; - - pktout = sftp_pkt_init(SSH_FXP_REMOVE); - put_uint32(pktout, req->id); - put_stringz(pktout, fname); - sftp_send(pktout); - - return req; -} - -bool fxp_remove_recv(struct sftp_packet *pktin, struct sftp_request *req) -{ - int id; - sfree(req); - id = fxp_got_status(pktin); - sftp_pkt_free(pktin); - return id == 1; -} - -struct sftp_request *fxp_rename_send(const char *srcfname, - const char *dstfname) -{ - struct sftp_request *req = sftp_alloc_request(); - struct sftp_packet *pktout; - - pktout = sftp_pkt_init(SSH_FXP_RENAME); - put_uint32(pktout, req->id); - put_stringz(pktout, srcfname); - put_stringz(pktout, dstfname); - sftp_send(pktout); - - return req; -} - -bool fxp_rename_recv(struct sftp_packet *pktin, struct sftp_request *req) -{ - int id; - sfree(req); - id = fxp_got_status(pktin); - sftp_pkt_free(pktin); - return id == 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(const char *fname) -{ - struct sftp_request *req = sftp_alloc_request(); - struct sftp_packet *pktout; - - pktout = sftp_pkt_init(SSH_FXP_STAT); - put_uint32(pktout, req->id); - put_stringz(pktout, fname); - sftp_send(pktout); - - return req; -} - -static bool fxp_got_attrs(struct sftp_packet *pktin, struct fxp_attrs *attrs) -{ - get_fxp_attrs(pktin, attrs); - if (get_err(pktin)) { - fxp_internal_error("malformed SSH_FXP_ATTRS packet"); - sftp_pkt_free(pktin); - return false; - } - sftp_pkt_free(pktin); - return true; -} - -bool fxp_stat_recv(struct sftp_packet *pktin, struct sftp_request *req, - struct fxp_attrs *attrs) -{ - sfree(req); - if (pktin->type == SSH_FXP_ATTRS) { - return fxp_got_attrs(pktin, attrs); - } else { - fxp_got_status(pktin); - sftp_pkt_free(pktin); - return false; - } -} - -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); - put_uint32(pktout, req->id); - put_string(pktout, handle->hstring, handle->hlen); - sftp_send(pktout); - - return req; -} - -bool fxp_fstat_recv(struct sftp_packet *pktin, struct sftp_request *req, - struct fxp_attrs *attrs) -{ - sfree(req); - if (pktin->type == SSH_FXP_ATTRS) { - return fxp_got_attrs(pktin, attrs); - } else { - fxp_got_status(pktin); - sftp_pkt_free(pktin); - return false; - } -} - -/* - * Set the attributes of a file. - */ -struct sftp_request *fxp_setstat_send(const char *fname, - struct fxp_attrs attrs) -{ - struct sftp_request *req = sftp_alloc_request(); - struct sftp_packet *pktout; - - pktout = sftp_pkt_init(SSH_FXP_SETSTAT); - put_uint32(pktout, req->id); - put_stringz(pktout, fname); - put_fxp_attrs(pktout, attrs); - sftp_send(pktout); - - return req; -} - -bool fxp_setstat_recv(struct sftp_packet *pktin, struct sftp_request *req) -{ - int id; - sfree(req); - id = fxp_got_status(pktin); - sftp_pkt_free(pktin); - return id == 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); - put_uint32(pktout, req->id); - put_string(pktout, handle->hstring, handle->hlen); - put_fxp_attrs(pktout, attrs); - sftp_send(pktout); - - return req; -} - -bool fxp_fsetstat_recv(struct sftp_packet *pktin, struct sftp_request *req) -{ - int id; - sfree(req); - id = fxp_got_status(pktin); - sftp_pkt_free(pktin); - return id == 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_t offset, int len) -{ - struct sftp_request *req = sftp_alloc_request(); - struct sftp_packet *pktout; - - pktout = sftp_pkt_init(SSH_FXP_READ); - put_uint32(pktout, req->id); - put_string(pktout, handle->hstring, handle->hlen); - put_uint64(pktout, offset); - put_uint32(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) { - ptrlen data; - - data = get_string(pktin); - if (get_err(pktin)) { - fxp_internal_error("READ returned malformed SSH_FXP_DATA packet"); - sftp_pkt_free(pktin); - return -1; - } - - if (data.len > len) { - fxp_internal_error("READ returned more bytes than requested"); - sftp_pkt_free(pktin); - return -1; - } - - memcpy(buffer, data.ptr, data.len); - sftp_pkt_free(pktin); - return data.len; - } 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); - put_uint32(pktout, req->id); - put_string(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; - - i = get_uint32(pktin); - - /* - * 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 (get_err(pktin) || i > get_avail(pktin) / 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++) { - ret->names[i].filename = mkstr(get_string(pktin)); - ret->names[i].longname = mkstr(get_string(pktin)); - get_fxp_attrs(pktin, &ret->names[i].attrs); - } - - if (get_err(pktin)) { - fxp_internal_error("malformed FXP_NAME packet"); - for (i = 0; i < (unsigned long)ret->nnames; i++) { - sfree(ret->names[i].filename); - sfree(ret->names[i].longname); - } - sfree(ret->names); - sfree(ret); - sfree(pktin); - return NULL; - } - 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, - void *buffer, uint64_t offset, int len) -{ - struct sftp_request *req = sftp_alloc_request(); - struct sftp_packet *pktout; - - pktout = sftp_pkt_init(SSH_FXP_WRITE); - put_uint32(pktout, req->id); - put_string(pktout, handle->hstring, handle->hlen); - put_uint64(pktout, offset); - put_string(pktout, buffer, len); - sftp_send(pktout); - - return req; -} - -bool 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_t offset; - struct req *next, *prev; -}; - -struct fxp_xfer { - uint64_t offset, furthestdata, filesize; - int req_totalsize, req_maxsize; - bool eof, err; - struct fxp_handle *fh; - struct req *head, *tail; -}; - -static struct fxp_xfer *xfer_init(struct fxp_handle *fh, uint64_t 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 = false; - xfer->filesize = UINT64_MAX; - xfer->furthestdata = 0; - - return xfer; -} - -bool 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 += rr->len; - xfer->req_totalsize += rr->len; - -#ifdef DEBUG_DOWNLOAD - printf("queueing read request %p at %"PRIu64"\n", rr, rr->offset); -#endif - } -} - -struct fxp_xfer *xfer_download_init(struct fxp_handle *fh, uint64_t offset) -{ - struct fxp_xfer *xfer = xfer_init(fh, offset); - - xfer->eof = false; - xfer_download_queue(xfer); - - return xfer; -} - -/* - * Returns INT_MIN to indicate that it didn't even get as far as - * fxp_read_recv and hence has not freed pktin. - */ -int xfer_download_gotpkt(struct fxp_xfer *xfer, struct sftp_packet *pktin) -{ - struct sftp_request *rreq; - struct req *rr; - - rreq = sftp_find_request(pktin); - if (!rreq) - return INT_MIN; /* this packet doesn't even make sense */ - rr = (struct req *)fxp_get_userdata(rreq); - if (!rr) { - fxp_internal_error("request ID is not part of the current download"); - return INT_MIN; /* this packet isn't ours */ - } - rr->retlen = fxp_read_recv(pktin, rreq, rr->buffer, rr->len); -#ifdef DEBUG_DOWNLOAD - printf("read request %p has returned [%d]\n", rr, rr->retlen); -#endif - - if ((rr->retlen < 0 && fxp_error_type()==SSH_FX_EOF) || rr->retlen == 0) { - xfer->eof = true; - rr->retlen = 0; - 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 && xfer->furthestdata < rr->offset) { - xfer->furthestdata = rr->offset; -#ifdef DEBUG_DOWNLOAD - printf("setting furthestdata = %"PRIu64"\n", xfer->furthestdata); -#endif - } - - if (rr->retlen < rr->len) { - uint64_t filesize = rr->offset + (rr->retlen < 0 ? 0 : rr->retlen); -#ifdef DEBUG_DOWNLOAD - printf("short block! trying filesize = %"PRIu64"\n", filesize); -#endif - if (xfer->filesize > filesize) { - xfer->filesize = filesize; -#ifdef DEBUG_DOWNLOAD - printf("actually changing filesize\n"); -#endif - } - } - - if (xfer->furthestdata > xfer->filesize) { - 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 = true; -} - -bool 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 true; - } else - return false; -} - -struct fxp_xfer *xfer_upload_init(struct fxp_handle *fh, uint64_t 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 = true; - - return xfer; -} - -bool xfer_upload_ready(struct fxp_xfer *xfer) -{ - return sftp_sendbuffer() == 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 += rr->len; - xfer->req_totalsize += rr->len; - -#ifdef DEBUG_UPLOAD - printf("queueing write request %p at %"PRIu64" [len %d]\n", - rr, rr->offset, len); -#endif -} - -/* - * Returns INT_MIN to indicate that it didn't even get as far as - * fxp_write_recv and hence has not freed pktin. - */ -int xfer_upload_gotpkt(struct fxp_xfer *xfer, struct sftp_packet *pktin) -{ - struct sftp_request *rreq; - struct req *rr, *prev, *next; - bool ret; - - rreq = sftp_find_request(pktin); - if (!rreq) - return INT_MIN; /* this packet doesn't even make sense */ - rr = (struct req *)fxp_get_userdata(rreq); - if (!rr) { - fxp_internal_error("request ID is not part of the current upload"); - return INT_MIN; /* this packet isn't ours */ - } - ret = fxp_write_recv(pktin, rreq); -#ifdef DEBUG_UPLOAD - printf("write request %p has returned [%d]\n", rr, ret ? 1 : 0); -#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); -} diff --git a/sftp.h b/sftp.h deleted file mode 100644 index 5835c16b..00000000 --- a/sftp.h +++ /dev/null @@ -1,544 +0,0 @@ -/* - * sftp.h: definitions for SFTP and the sftp.c routines. - */ - -#include "defs.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 - -#define PERMS_DIRECTORY 040000 - -/* - * 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 and then returns true, or it returns false - * for failure. - * - * sftp_senddata returns true on success, false on failure. - * - * sftp_sendbuffer returns the size of the backlog of data in the - * transmit queue. - */ -bool sftp_senddata(const char *data, size_t len); -size_t sftp_sendbuffer(void); -bool sftp_recvdata(char *data, size_t len); - -/* - * Free sftp_requests - */ -void sftp_cleanup_request(void); - -struct fxp_attrs { - unsigned long flags; - uint64_t size; - unsigned long uid; - unsigned long gid; - unsigned long permissions; - unsigned long atime; - unsigned long mtime; -}; -extern const struct fxp_attrs no_attrs; - -/* - * Copy between the possibly-unused permissions field in an fxp_attrs - * and a possibly-negative integer containing the same permissions. - */ -#define PUT_PERMISSIONS(attrs, perms) \ - ((perms) >= 0 ? \ - ((attrs).flags |= SSH_FILEXFER_ATTR_PERMISSIONS, \ - (attrs).permissions = (perms)) : \ - ((attrs).flags &= ~SSH_FILEXFER_ATTR_PERMISSIONS)) -#define GET_PERMISSIONS(attrs, defaultperms) \ - ((attrs).flags & SSH_FILEXFER_ATTR_PERMISSIONS ? \ - (attrs).permissions : defaultperms) - -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; - -/* - * Packet-manipulation functions. - */ - -struct sftp_packet { - char *data; - size_t length, maxlen, savedpos; - int type; - BinarySink_IMPLEMENTATION; - BinarySource_IMPLEMENTATION; -}; - -/* When sending a packet, create it with sftp_pkt_init, then add - * things to it by treating it as a BinarySink. When it's done, call - * sftp_send_prepare, and then pkt->data and pkt->length describe its - * wire format. */ -struct sftp_packet *sftp_pkt_init(int pkt_type); -void sftp_send_prepare(struct sftp_packet *pkt); - -/* When receiving a packet, create it with sftp_recv_prepare once you - * decode its length from the first 4 bytes of wire data. Then write - * that many bytes into pkt->data, and call sftp_recv_finish to set up - * the type code and BinarySource. */ -struct sftp_packet *sftp_recv_prepare(unsigned length); -bool sftp_recv_finish(struct sftp_packet *pkt); - -/* Either kind of packet can be freed afterwards with sftp_pkt_free. */ -void sftp_pkt_free(struct sftp_packet *pkt); - -void BinarySink_put_fxp_attrs(BinarySink *bs, struct fxp_attrs attrs); -bool BinarySource_get_fxp_attrs(BinarySource *src, struct fxp_attrs *attrs); -#define put_fxp_attrs(bs, attrs) \ - BinarySink_put_fxp_attrs(BinarySink_UPCAST(bs), attrs) -#define get_fxp_attrs(bs, attrs) \ - BinarySource_get_fxp_attrs(BinarySource_UPCAST(bs), attrs) - -/* - * Error handling. - */ - -const char *fxp_error(void); -int fxp_error_type(void); - -/* - * Perform exchange of init/version packets. Return false on failure. - */ -bool 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(const char *path); -char *fxp_realpath_recv(struct sftp_packet *pktin, struct sftp_request *req); - -/* - * Open a file. 'attrs' contains attributes to be applied to the file - * if it's being created. - */ -struct sftp_request *fxp_open_send(const char *path, int type, - const struct fxp_attrs *attrs); -struct fxp_handle *fxp_open_recv(struct sftp_packet *pktin, - struct sftp_request *req); - -/* - * Open a directory. - */ -struct sftp_request *fxp_opendir_send(const char *path); -struct fxp_handle *fxp_opendir_recv(struct sftp_packet *pktin, - struct sftp_request *req); - -/* - * Close a file/dir. Returns true on success, false on error. - */ -struct sftp_request *fxp_close_send(struct fxp_handle *handle); -bool fxp_close_recv(struct sftp_packet *pktin, struct sftp_request *req); - -/* - * Make a directory. - */ -struct sftp_request *fxp_mkdir_send(const char *path, - const struct fxp_attrs *attrs); -bool fxp_mkdir_recv(struct sftp_packet *pktin, struct sftp_request *req); - -/* - * Remove a directory. - */ -struct sftp_request *fxp_rmdir_send(const char *path); -bool fxp_rmdir_recv(struct sftp_packet *pktin, struct sftp_request *req); - -/* - * Remove a file. - */ -struct sftp_request *fxp_remove_send(const char *fname); -bool fxp_remove_recv(struct sftp_packet *pktin, struct sftp_request *req); - -/* - * Rename a file. - */ -struct sftp_request *fxp_rename_send(const char *srcfname, - const char *dstfname); -bool fxp_rename_recv(struct sftp_packet *pktin, struct sftp_request *req); - -/* - * Return file attributes. - */ -struct sftp_request *fxp_stat_send(const char *fname); -bool 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); -bool fxp_fstat_recv(struct sftp_packet *pktin, struct sftp_request *req, - struct fxp_attrs *attrs); - -/* - * Set file attributes. - */ -struct sftp_request *fxp_setstat_send(const char *fname, - struct fxp_attrs attrs); -bool 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); -bool 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_t offset, int len); -int fxp_read_recv(struct sftp_packet *pktin, struct sftp_request *req, - char *buffer, int len); - -/* - * Write to a file. - */ -struct sftp_request *fxp_write_send(struct fxp_handle *handle, - void *buffer, uint64_t offset, int len); -bool 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_t offset); -void xfer_download_queue(struct fxp_xfer *xfer); -int xfer_download_gotpkt(struct fxp_xfer *xfer, struct sftp_packet *pktin); -bool xfer_download_data(struct fxp_xfer *xfer, void **buf, int *len); - -struct fxp_xfer *xfer_upload_init(struct fxp_handle *fh, uint64_t offset); -bool 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); - -bool xfer_done(struct fxp_xfer *xfer); -void xfer_set_error(struct fxp_xfer *xfer); -void xfer_cleanup(struct fxp_xfer *xfer); - -/* - * Vtable for the platform-specific filesystem implementation that - * answers requests in an SFTP server. - */ -typedef struct SftpReplyBuilder SftpReplyBuilder; -struct SftpServer { - const SftpServerVtable *vt; -}; -struct SftpServerVtable { - SftpServer *(*new)(const SftpServerVtable *vt); - void (*free)(SftpServer *srv); - - /* - * Handle actual filesystem requests. - * - * Each of these functions replies by calling an appropiate - * sftp_reply_foo() function on the given reply packet. - */ - - /* Should call fxp_reply_error or fxp_reply_simple_name */ - void (*realpath)(SftpServer *srv, SftpReplyBuilder *reply, - ptrlen path); - - /* Should call fxp_reply_error or fxp_reply_handle */ - void (*open)(SftpServer *srv, SftpReplyBuilder *reply, - ptrlen path, unsigned flags, struct fxp_attrs attrs); - - /* Should call fxp_reply_error or fxp_reply_handle */ - void (*opendir)(SftpServer *srv, SftpReplyBuilder *reply, - ptrlen path); - - /* Should call fxp_reply_error or fxp_reply_ok */ - void (*close)(SftpServer *srv, SftpReplyBuilder *reply, ptrlen handle); - - /* Should call fxp_reply_error or fxp_reply_ok */ - void (*mkdir)(SftpServer *srv, SftpReplyBuilder *reply, - ptrlen path, struct fxp_attrs attrs); - - /* Should call fxp_reply_error or fxp_reply_ok */ - void (*rmdir)(SftpServer *srv, SftpReplyBuilder *reply, ptrlen path); - - /* Should call fxp_reply_error or fxp_reply_ok */ - void (*remove)(SftpServer *srv, SftpReplyBuilder *reply, ptrlen path); - - /* Should call fxp_reply_error or fxp_reply_ok */ - void (*rename)(SftpServer *srv, SftpReplyBuilder *reply, - ptrlen srcpath, ptrlen dstpath); - - /* Should call fxp_reply_error or fxp_reply_attrs */ - void (*stat)(SftpServer *srv, SftpReplyBuilder *reply, ptrlen path, - bool follow_symlinks); - - /* Should call fxp_reply_error or fxp_reply_attrs */ - void (*fstat)(SftpServer *srv, SftpReplyBuilder *reply, ptrlen handle); - - /* Should call fxp_reply_error or fxp_reply_ok */ - void (*setstat)(SftpServer *srv, SftpReplyBuilder *reply, - ptrlen path, struct fxp_attrs attrs); - - /* Should call fxp_reply_error or fxp_reply_ok */ - void (*fsetstat)(SftpServer *srv, SftpReplyBuilder *reply, - ptrlen handle, struct fxp_attrs attrs); - - /* Should call fxp_reply_error or fxp_reply_data */ - void (*read)(SftpServer *srv, SftpReplyBuilder *reply, - ptrlen handle, uint64_t offset, unsigned length); - - /* Should call fxp_reply_error or fxp_reply_ok */ - void (*write)(SftpServer *srv, SftpReplyBuilder *reply, - ptrlen handle, uint64_t offset, ptrlen data); - - /* Should call fxp_reply_error, or fxp_reply_name_count once and - * then fxp_reply_full_name that many times */ - void (*readdir)(SftpServer *srv, SftpReplyBuilder *reply, ptrlen handle, - int max_entries, bool omit_longname); -}; - -static inline SftpServer *sftpsrv_new(const SftpServerVtable *vt) -{ return vt->new(vt); } -static inline void sftpsrv_free(SftpServer *srv) -{ srv->vt->free(srv); } -static inline void sftpsrv_realpath(SftpServer *srv, SftpReplyBuilder *reply, - ptrlen path) -{ srv->vt->realpath(srv, reply, path); } -static inline void sftpsrv_open( - SftpServer *srv, SftpReplyBuilder *reply, - ptrlen path, unsigned flags, struct fxp_attrs attrs) -{ srv->vt->open(srv, reply, path, flags, attrs); } -static inline void sftpsrv_opendir( - SftpServer *srv, SftpReplyBuilder *reply, ptrlen path) -{ srv->vt->opendir(srv, reply, path); } -static inline void sftpsrv_close( - SftpServer *srv, SftpReplyBuilder *reply, ptrlen handle) -{ srv->vt->close(srv, reply, handle); } -static inline void sftpsrv_mkdir(SftpServer *srv, SftpReplyBuilder *reply, - ptrlen path, struct fxp_attrs attrs) -{ srv->vt->mkdir(srv, reply, path, attrs); } -static inline void sftpsrv_rmdir( - SftpServer *srv, SftpReplyBuilder *reply, ptrlen path) -{ srv->vt->rmdir(srv, reply, path); } -static inline void sftpsrv_remove( - SftpServer *srv, SftpReplyBuilder *reply, ptrlen path) -{ srv->vt->remove(srv, reply, path); } -static inline void sftpsrv_rename(SftpServer *srv, SftpReplyBuilder *reply, - ptrlen srcpath, ptrlen dstpath) -{ srv->vt->rename(srv, reply, srcpath, dstpath); } -static inline void sftpsrv_stat( - SftpServer *srv, SftpReplyBuilder *reply, ptrlen path, bool follow) -{ srv->vt->stat(srv, reply, path, follow); } -static inline void sftpsrv_fstat( - SftpServer *srv, SftpReplyBuilder *reply, ptrlen handle) -{ srv->vt->fstat(srv, reply, handle); } -static inline void sftpsrv_setstat(SftpServer *srv, SftpReplyBuilder *reply, - ptrlen path, struct fxp_attrs attrs) -{ srv->vt->setstat(srv, reply, path, attrs); } -static inline void sftpsrv_fsetstat(SftpServer *srv, SftpReplyBuilder *reply, - ptrlen handle, struct fxp_attrs attrs) -{ srv->vt->fsetstat(srv, reply, handle, attrs); } -static inline void sftpsrv_read( - SftpServer *srv, SftpReplyBuilder *reply, - ptrlen handle, uint64_t offset, unsigned length) -{ srv->vt->read(srv, reply, handle, offset, length); } -static inline void sftpsrv_write(SftpServer *srv, SftpReplyBuilder *reply, - ptrlen handle, uint64_t offset, ptrlen data) -{ srv->vt->write(srv, reply, handle, offset, data); } -static inline void sftpsrv_readdir( - SftpServer *srv, SftpReplyBuilder *reply, ptrlen handle, - int max_entries, bool omit_longname) -{ srv->vt->readdir(srv, reply, handle, max_entries, omit_longname); } - -typedef struct SftpReplyBuilderVtable SftpReplyBuilderVtable; -struct SftpReplyBuilder { - const SftpReplyBuilderVtable *vt; -}; -struct SftpReplyBuilderVtable { - void (*reply_ok)(SftpReplyBuilder *reply); - void (*reply_error)(SftpReplyBuilder *reply, unsigned code, - const char *msg); - void (*reply_simple_name)(SftpReplyBuilder *reply, ptrlen name); - void (*reply_name_count)(SftpReplyBuilder *reply, unsigned count); - void (*reply_full_name)(SftpReplyBuilder *reply, ptrlen name, - ptrlen longname, struct fxp_attrs attrs); - void (*reply_handle)(SftpReplyBuilder *reply, ptrlen handle); - void (*reply_data)(SftpReplyBuilder *reply, ptrlen data); - void (*reply_attrs)(SftpReplyBuilder *reply, struct fxp_attrs attrs); -}; - -static inline void fxp_reply_ok(SftpReplyBuilder *reply) -{ reply->vt->reply_ok(reply); } -static inline void fxp_reply_error(SftpReplyBuilder *reply, unsigned code, - const char *msg) -{ reply->vt->reply_error(reply, code, msg); } -static inline void fxp_reply_simple_name(SftpReplyBuilder *reply, ptrlen name) -{ reply->vt->reply_simple_name(reply, name); } -static inline void fxp_reply_name_count( - SftpReplyBuilder *reply, unsigned count) -{ reply->vt->reply_name_count(reply, count); } -static inline void fxp_reply_full_name(SftpReplyBuilder *reply, ptrlen name, - ptrlen longname, struct fxp_attrs attrs) -{ reply->vt->reply_full_name(reply, name, longname, attrs); } -static inline void fxp_reply_handle(SftpReplyBuilder *reply, ptrlen handle) -{ reply->vt->reply_handle(reply, handle); } -static inline void fxp_reply_data(SftpReplyBuilder *reply, ptrlen data) -{ reply->vt->reply_data(reply, data); } -static inline void fxp_reply_attrs( - SftpReplyBuilder *reply, struct fxp_attrs attrs) -{ reply->vt->reply_attrs(reply, attrs); } - -/* - * The usual implementation of an SftpReplyBuilder, containing a - * 'struct sftp_packet' which is assumed to be already initialised - * before one of the above request methods is called. - */ -extern const struct SftpReplyBuilderVtable DefaultSftpReplyBuilder_vt; -typedef struct DefaultSftpReplyBuilder DefaultSftpReplyBuilder; -struct DefaultSftpReplyBuilder { - SftpReplyBuilder rb; - struct sftp_packet *pkt; -}; - -/* - * The top-level function that handles an SFTP request, given an - * implementation of the above SftpServer abstraction to do the actual - * filesystem work. It handles all the marshalling and unmarshalling - * of packets, and the copying of request ids into the responses. - */ -struct sftp_packet *sftp_handle_request( - SftpServer *srv, struct sftp_packet *request); - -/* ---------------------------------------------------------------------- - * Not exactly SFTP-related, but here's a system that implements an - * old-fashioned SCP server module, given an SftpServer vtable to use - * as its underlying filesystem access. - */ - -typedef struct ScpServer ScpServer; -typedef struct ScpServerVtable ScpServerVtable; -struct ScpServer { - const struct ScpServerVtable *vt; -}; -struct ScpServerVtable { - void (*free)(ScpServer *s); - - size_t (*send)(ScpServer *s, const void *data, size_t length); - void (*throttle)(ScpServer *s, bool throttled); - void (*eof)(ScpServer *s); -}; - -static inline void scp_free(ScpServer *s) -{ s->vt->free(s); } -static inline size_t scp_send(ScpServer *s, const void *data, size_t length) -{ return s->vt->send(s, data, length); } -static inline void scp_throttle(ScpServer *s, bool throttled) -{ s->vt->throttle(s, throttled); } -static inline void scp_eof(ScpServer *s) -{ s->vt->eof(s); } - -/* - * Create an ScpServer by calling this function, giving it the command - * you received from the SSH client to execute. If that command is - * recognised as an scp command, it will construct an ScpServer object - * and return it; otherwise, it will return NULL, and you should - * execute the command in whatever way you normally would. - * - * The ScpServer will generate output for the client by writing it to - * the provided SshChannel using sshfwd_write; you pass it input using - * the send method in its own vtable. - */ -ScpServer *scp_recognise_exec( - SshChannel *sc, const SftpServerVtable *sftpserver_vt, ptrlen command); diff --git a/sftpcommon.c b/sftpcommon.c deleted file mode 100644 index f2f9a3bc..00000000 --- a/sftpcommon.c +++ /dev/null @@ -1,139 +0,0 @@ -/* - * sftpcommon.c: SFTP code shared between client and server. - */ - -#include -#include -#include -#include -#include - -#include "misc.h" -#include "sftp.h" - -static void sftp_pkt_BinarySink_write( - BinarySink *bs, const void *data, size_t length) -{ - struct sftp_packet *pkt = BinarySink_DOWNCAST(bs, struct sftp_packet); - - assert(length <= 0xFFFFFFFFU - pkt->length); - - sgrowarrayn_nm(pkt->data, pkt->maxlen, pkt->length, length); - memcpy(pkt->data + pkt->length, data, length); - pkt->length += length; -} - -struct sftp_packet *sftp_pkt_init(int type) -{ - struct sftp_packet *pkt; - pkt = snew(struct sftp_packet); - pkt->data = NULL; - pkt->savedpos = -1; - pkt->length = 0; - pkt->maxlen = 0; - pkt->type = type; - BinarySink_INIT(pkt, sftp_pkt_BinarySink_write); - put_uint32(pkt, 0); /* length field will be filled in later */ - put_byte(pkt, 0); /* so will the type field */ - return pkt; -} - -void BinarySink_put_fxp_attrs(BinarySink *bs, struct fxp_attrs attrs) -{ - put_uint32(bs, attrs.flags); - if (attrs.flags & SSH_FILEXFER_ATTR_SIZE) - put_uint64(bs, attrs.size); - if (attrs.flags & SSH_FILEXFER_ATTR_UIDGID) { - put_uint32(bs, attrs.uid); - put_uint32(bs, attrs.gid); - } - if (attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) { - put_uint32(bs, attrs.permissions); - } - if (attrs.flags & SSH_FILEXFER_ATTR_ACMODTIME) { - put_uint32(bs, attrs.atime); - put_uint32(bs, attrs.mtime); - } - if (attrs.flags & SSH_FILEXFER_ATTR_EXTENDED) { - /* - * We currently don't support sending any extended - * attributes. - */ - } -} - -const struct fxp_attrs no_attrs = { 0 }; - -#define put_fxp_attrs(bs, attrs) \ - BinarySink_put_fxp_attrs(BinarySink_UPCAST(bs), attrs) - -bool BinarySource_get_fxp_attrs(BinarySource *src, struct fxp_attrs *attrs) -{ - attrs->flags = get_uint32(src); - if (attrs->flags & SSH_FILEXFER_ATTR_SIZE) - attrs->size = get_uint64(src); - if (attrs->flags & SSH_FILEXFER_ATTR_UIDGID) { - attrs->uid = get_uint32(src); - attrs->gid = get_uint32(src); - } - if (attrs->flags & SSH_FILEXFER_ATTR_PERMISSIONS) - attrs->permissions = get_uint32(src); - if (attrs->flags & SSH_FILEXFER_ATTR_ACMODTIME) { - attrs->atime = get_uint32(src); - attrs->mtime = get_uint32(src); - } - if (attrs->flags & SSH_FILEXFER_ATTR_EXTENDED) { - unsigned long count = get_uint32(src); - while (count--) { - if (get_err(src)) { - /* Truncated packet. Don't waste time looking for - * attributes that aren't there. Caller should spot - * the truncation. */ - break; - } - /* - * We should try to analyse these, if we ever find one - * we recognise. - */ - get_string(src); - get_string(src); - } - } - return true; -} - -void sftp_pkt_free(struct sftp_packet *pkt) -{ - if (pkt->data) - sfree(pkt->data); - sfree(pkt); -} - -void sftp_send_prepare(struct sftp_packet *pkt) -{ - PUT_32BIT_MSB_FIRST(pkt->data, pkt->length - 4); - if (pkt->length >= 5) { - /* Rewrite the type code, in case the caller changed its mind - * about pkt->type since calling sftp_pkt_init */ - pkt->data[4] = pkt->type; - } -} - -struct sftp_packet *sftp_recv_prepare(unsigned length) -{ - struct sftp_packet *pkt; - - pkt = snew(struct sftp_packet); - pkt->savedpos = 0; - pkt->length = pkt->maxlen = length; - pkt->data = snewn(pkt->length, char); - - return pkt; -} - -bool sftp_recv_finish(struct sftp_packet *pkt) -{ - BinarySource_INIT(pkt, pkt->data, pkt->length); - pkt->type = get_byte(pkt); - return !get_err(pkt); -} diff --git a/sftpserver.c b/sftpserver.c deleted file mode 100644 index ba216872..00000000 --- a/sftpserver.c +++ /dev/null @@ -1,279 +0,0 @@ -/* - * Implement the centralised parts of the server side of SFTP. - */ - -#include -#include -#include - -#include "putty.h" -#include "ssh.h" -#include "sftp.h" - -struct sftp_packet *sftp_handle_request( - SftpServer *srv, struct sftp_packet *req) -{ - struct sftp_packet *reply; - unsigned id; - uint32_t flags; - ptrlen path, dstpath, handle, data; - uint64_t offset; - unsigned length; - struct fxp_attrs attrs; - DefaultSftpReplyBuilder dsrb; - SftpReplyBuilder *rb; - - if (req->type == SSH_FXP_INIT) { - /* - * Special case which doesn't have a request id at the start. - */ - reply = sftp_pkt_init(SSH_FXP_VERSION); - /* - * Since we support only the lowest protocol version, we don't - * need to take the min of this and the client's version, or - * even to bother reading the client version number out of the - * input packet. - */ - put_uint32(reply, SFTP_PROTO_VERSION); - return reply; - } - - /* - * Centralise the request id handling. We'll overwrite the type - * code of the output packet later. - */ - id = get_uint32(req); - reply = sftp_pkt_init(0); - put_uint32(reply, id); - - dsrb.rb.vt = &DefaultSftpReplyBuilder_vt; - dsrb.pkt = reply; - rb = &dsrb.rb; - - switch (req->type) { - case SSH_FXP_REALPATH: - path = get_string(req); - if (get_err(req)) - goto decode_error; - sftpsrv_realpath(srv, rb, path); - break; - - case SSH_FXP_OPEN: - path = get_string(req); - flags = get_uint32(req); - get_fxp_attrs(req, &attrs); - if (get_err(req)) - goto decode_error; - if ((flags & (SSH_FXF_READ|SSH_FXF_WRITE)) == 0) { - fxp_reply_error(rb, SSH_FX_BAD_MESSAGE, - "open without READ or WRITE flag"); - } else if ((flags & (SSH_FXF_CREAT|SSH_FXF_TRUNC)) == SSH_FXF_TRUNC) { - fxp_reply_error(rb, SSH_FX_BAD_MESSAGE, - "open with TRUNC but not CREAT"); - } else if ((flags & (SSH_FXF_CREAT|SSH_FXF_EXCL)) == SSH_FXF_EXCL) { - fxp_reply_error(rb, SSH_FX_BAD_MESSAGE, - "open with EXCL but not CREAT"); - } else { - sftpsrv_open(srv, rb, path, flags, attrs); - } - break; - - case SSH_FXP_OPENDIR: - path = get_string(req); - if (get_err(req)) - goto decode_error; - sftpsrv_opendir(srv, rb, path); - break; - - case SSH_FXP_CLOSE: - handle = get_string(req); - if (get_err(req)) - goto decode_error; - sftpsrv_close(srv, rb, handle); - break; - - case SSH_FXP_MKDIR: - path = get_string(req); - get_fxp_attrs(req, &attrs); - if (get_err(req)) - goto decode_error; - sftpsrv_mkdir(srv, rb, path, attrs); - break; - - case SSH_FXP_RMDIR: - path = get_string(req); - if (get_err(req)) - goto decode_error; - sftpsrv_rmdir(srv, rb, path); - break; - - case SSH_FXP_REMOVE: - path = get_string(req); - if (get_err(req)) - goto decode_error; - sftpsrv_remove(srv, rb, path); - break; - - case SSH_FXP_RENAME: - path = get_string(req); - dstpath = get_string(req); - if (get_err(req)) - goto decode_error; - sftpsrv_rename(srv, rb, path, dstpath); - break; - - case SSH_FXP_STAT: - path = get_string(req); - if (get_err(req)) - goto decode_error; - sftpsrv_stat(srv, rb, path, true); - break; - - case SSH_FXP_LSTAT: - path = get_string(req); - if (get_err(req)) - goto decode_error; - sftpsrv_stat(srv, rb, path, false); - break; - - case SSH_FXP_FSTAT: - handle = get_string(req); - if (get_err(req)) - goto decode_error; - sftpsrv_fstat(srv, rb, handle); - break; - - case SSH_FXP_SETSTAT: - path = get_string(req); - get_fxp_attrs(req, &attrs); - if (get_err(req)) - goto decode_error; - sftpsrv_setstat(srv, rb, path, attrs); - break; - - case SSH_FXP_FSETSTAT: - handle = get_string(req); - get_fxp_attrs(req, &attrs); - if (get_err(req)) - goto decode_error; - sftpsrv_fsetstat(srv, rb, handle, attrs); - break; - - case SSH_FXP_READ: - handle = get_string(req); - offset = get_uint64(req); - length = get_uint32(req); - if (get_err(req)) - goto decode_error; - sftpsrv_read(srv, rb, handle, offset, length); - break; - - case SSH_FXP_READDIR: - handle = get_string(req); - if (get_err(req)) - goto decode_error; - sftpsrv_readdir(srv, rb, handle, INT_MAX, false); - break; - - case SSH_FXP_WRITE: - handle = get_string(req); - offset = get_uint64(req); - data = get_string(req); - if (get_err(req)) - goto decode_error; - sftpsrv_write(srv, rb, handle, offset, data); - break; - - default: - if (get_err(req)) - goto decode_error; - fxp_reply_error(rb, SSH_FX_OP_UNSUPPORTED, - "Unrecognised request type"); - break; - - decode_error: - fxp_reply_error(rb, SSH_FX_BAD_MESSAGE, "Unable to decode request"); - } - - return reply; -} - -static void default_reply_ok(SftpReplyBuilder *reply) -{ - DefaultSftpReplyBuilder *d = - container_of(reply, DefaultSftpReplyBuilder, rb); - d->pkt->type = SSH_FXP_STATUS; - put_uint32(d->pkt, SSH_FX_OK); - put_stringz(d->pkt, ""); -} - -static void default_reply_error( - SftpReplyBuilder *reply, unsigned code, const char *msg) -{ - DefaultSftpReplyBuilder *d = - container_of(reply, DefaultSftpReplyBuilder, rb); - d->pkt->type = SSH_FXP_STATUS; - put_uint32(d->pkt, code); - put_stringz(d->pkt, msg); -} - -static void default_reply_name_count(SftpReplyBuilder *reply, unsigned count) -{ - DefaultSftpReplyBuilder *d = - container_of(reply, DefaultSftpReplyBuilder, rb); - d->pkt->type = SSH_FXP_NAME; - put_uint32(d->pkt, count); -} - -static void default_reply_full_name(SftpReplyBuilder *reply, ptrlen name, - ptrlen longname, struct fxp_attrs attrs) -{ - DefaultSftpReplyBuilder *d = - container_of(reply, DefaultSftpReplyBuilder, rb); - d->pkt->type = SSH_FXP_NAME; - put_stringpl(d->pkt, name); - put_stringpl(d->pkt, longname); - put_fxp_attrs(d->pkt, attrs); -} - -static void default_reply_simple_name(SftpReplyBuilder *reply, ptrlen name) -{ - fxp_reply_name_count(reply, 1); - fxp_reply_full_name(reply, name, PTRLEN_LITERAL(""), no_attrs); -} - -static void default_reply_handle(SftpReplyBuilder *reply, ptrlen handle) -{ - DefaultSftpReplyBuilder *d = - container_of(reply, DefaultSftpReplyBuilder, rb); - d->pkt->type = SSH_FXP_HANDLE; - put_stringpl(d->pkt, handle); -} - -static void default_reply_data(SftpReplyBuilder *reply, ptrlen data) -{ - DefaultSftpReplyBuilder *d = - container_of(reply, DefaultSftpReplyBuilder, rb); - d->pkt->type = SSH_FXP_DATA; - put_stringpl(d->pkt, data); -} - -static void default_reply_attrs( - SftpReplyBuilder *reply, struct fxp_attrs attrs) -{ - DefaultSftpReplyBuilder *d = - container_of(reply, DefaultSftpReplyBuilder, rb); - d->pkt->type = SSH_FXP_ATTRS; - put_fxp_attrs(d->pkt, attrs); -} - -const SftpReplyBuilderVtable DefaultSftpReplyBuilder_vt = { - .reply_ok = default_reply_ok, - .reply_error = default_reply_error, - .reply_simple_name = default_reply_simple_name, - .reply_name_count = default_reply_name_count, - .reply_full_name = default_reply_full_name, - .reply_handle = default_reply_handle, - .reply_data = default_reply_data, - .reply_attrs = default_reply_attrs, -}; diff --git a/ssh.c b/ssh.c deleted file mode 100644 index 00a516ea..00000000 --- a/ssh.c +++ /dev/null @@ -1,1248 +0,0 @@ -/* - * SSH backend. - */ - -#include -#include -#include -#include -#include -#include - -#include "putty.h" -#include "pageant.h" /* for AGENT_MAX_MSGLEN */ -#include "tree234.h" -#include "storage.h" -#include "marshal.h" -#include "ssh.h" -#include "sshcr.h" -#include "sshbpp.h" -#include "sshppl.h" -#include "sshchan.h" -#ifndef NO_GSSAPI -#include "sshgssc.h" -#include "sshgss.h" -#define MIN_CTXT_LIFETIME 5 /* Avoid rekey with short lifetime (seconds) */ -#define GSS_KEX_CAPABLE (1<<0) /* Can do GSS KEX */ -#define GSS_CRED_UPDATED (1<<1) /* Cred updated since previous delegation */ -#define GSS_CTXT_EXPIRES (1<<2) /* Context expires before next timer */ -#define GSS_CTXT_MAYFAIL (1<<3) /* Context may expire during handshake */ -#endif - -struct Ssh { - Socket *s; - Seat *seat; - Conf *conf; - - struct ssh_version_receiver version_receiver; - int remote_bugs; - - Plug plug; - Backend backend; - - Ldisc *ldisc; - LogContext *logctx; - - /* The last list returned from get_specials. */ - SessionSpecial *specials; - - bool bare_connection; - ssh_sharing_state *connshare; - bool attempting_connshare; - -#ifndef NO_GSSAPI - struct ssh_connection_shared_gss_state gss_state; -#endif - - char *savedhost; - int savedport; - char *fullhostname; - - bool fallback_cmd; - int exitcode; - - int version; - int conn_throttle_count; - size_t overall_bufsize; - bool throttled_all; - - /* - * logically_frozen is true if we're not currently _processing_ - * data from the SSH socket (e.g. because a higher layer has asked - * us not to due to ssh_throttle_conn). socket_frozen is true if - * we're not even _reading_ data from the socket (i.e. it should - * always match the value we last passed to sk_set_frozen). - * - * The two differ in that socket_frozen can also become - * temporarily true because of a large backlog in the in_raw - * bufchain, to force no further plug_receive events until the BPP - * input function has had a chance to run. (Some front ends, like - * GTK, can persistently call the network and never get round to - * the toplevel callbacks.) If we've stopped reading from the - * socket for that reason, we absolutely _do_ want to carry on - * processing our input bufchain, because that's the only way - * it'll ever get cleared! - * - * ssh_check_frozen() resets socket_frozen, and should be called - * whenever either of logically_frozen and the bufchain size - * changes. - */ - bool logically_frozen, socket_frozen; - - /* in case we find these out before we have a ConnectionLayer to tell */ - int term_width, term_height; - - bufchain in_raw, out_raw, user_input; - bool pending_close; - IdempotentCallback ic_out_raw; - - PacketLogSettings pls; - struct DataTransferStats stats; - - BinaryPacketProtocol *bpp; - - /* - * base_layer identifies the bottommost packet protocol layer, the - * one connected directly to the BPP's packet queues. Any - * operation that needs to talk to all layers (e.g. free, or - * get_specials) will do it by talking to this, which will - * recursively propagate it if necessary. - */ - PacketProtocolLayer *base_layer; - - /* - * The ConnectionLayer vtable from our connection layer. - */ - ConnectionLayer *cl; - - /* - * A dummy ConnectionLayer that can be used for logging sharing - * downstreams that connect before the real one is ready. - */ - ConnectionLayer cl_dummy; - - /* - * session_started is false until we initialise the main protocol - * layers. So it distinguishes between base_layer==NULL meaning - * that the SSH protocol hasn't been set up _yet_, and - * base_layer==NULL meaning the SSH protocol has run and finished. - * It's also used to mark the point where we stop counting proxy - * command diagnostics as pre-session-startup. - */ - bool session_started; - - Pinger *pinger; - - char *deferred_abort_message; - - bool need_random_unref; -}; - - -#define ssh_logevent(params) ( \ - logevent_and_free((ssh)->logctx, dupprintf params)) - -static void ssh_shutdown(Ssh *ssh); -static void ssh_throttle_all(Ssh *ssh, bool enable, size_t bufsize); -static void ssh_bpp_output_raw_data_callback(void *vctx); - -LogContext *ssh_get_logctx(Ssh *ssh) -{ - return ssh->logctx; -} - -static void ssh_connect_bpp(Ssh *ssh) -{ - ssh->bpp->ssh = ssh; - ssh->bpp->in_raw = &ssh->in_raw; - ssh->bpp->out_raw = &ssh->out_raw; - bufchain_set_callback(ssh->bpp->out_raw, &ssh->ic_out_raw); - ssh->bpp->pls = &ssh->pls; - ssh->bpp->logctx = ssh->logctx; - ssh->bpp->remote_bugs = ssh->remote_bugs; -} - -static void ssh_connect_ppl(Ssh *ssh, PacketProtocolLayer *ppl) -{ - ppl->bpp = ssh->bpp; - ppl->user_input = &ssh->user_input; - ppl->seat = ssh->seat; - ppl->ssh = ssh; - ppl->logctx = ssh->logctx; - ppl->remote_bugs = ssh->remote_bugs; -} - -static void ssh_got_ssh_version(struct ssh_version_receiver *rcv, - int major_version) -{ - Ssh *ssh = container_of(rcv, Ssh, version_receiver); - BinaryPacketProtocol *old_bpp; - PacketProtocolLayer *connection_layer; - - ssh->session_started = true; - - /* - * We don't support choosing a major protocol version dynamically, - * so this should always be the same value we set up in - * connect_to_host(). - */ - assert(ssh->version == major_version); - - old_bpp = ssh->bpp; - ssh->remote_bugs = ssh_verstring_get_bugs(old_bpp); - - if (!ssh->bare_connection) { - if (ssh->version == 2) { - PacketProtocolLayer *userauth_layer, *transport_child_layer; - - /* - * We use the 'simple' variant of the SSH protocol if - * we're asked to, except not if we're also doing - * connection-sharing (either tunnelling our packets over - * an upstream or expecting to be tunnelled over - * ourselves), since then the assumption that we have only - * one channel to worry about is not true after all. - */ - bool is_simple = - (conf_get_bool(ssh->conf, CONF_ssh_simple) && !ssh->connshare); - - ssh->bpp = ssh2_bpp_new(ssh->logctx, &ssh->stats, false); - ssh_connect_bpp(ssh); - -#ifndef NO_GSSAPI - /* Load and pick the highest GSS library on the preference - * list. */ - if (!ssh->gss_state.libs) - ssh->gss_state.libs = ssh_gss_setup(ssh->conf); - ssh->gss_state.lib = NULL; - if (ssh->gss_state.libs->nlibraries > 0) { - int i, j; - for (i = 0; i < ngsslibs; i++) { - int want_id = conf_get_int_int(ssh->conf, - CONF_ssh_gsslist, i); - for (j = 0; j < ssh->gss_state.libs->nlibraries; j++) - if (ssh->gss_state.libs->libraries[j].id == want_id) { - ssh->gss_state.lib = - &ssh->gss_state.libs->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(ssh->gss_state.lib); - } -#endif - - connection_layer = ssh2_connection_new( - ssh, ssh->connshare, is_simple, ssh->conf, - ssh_verstring_get_remote(old_bpp), &ssh->cl); - ssh_connect_ppl(ssh, connection_layer); - - if (conf_get_bool(ssh->conf, CONF_ssh_no_userauth)) { - userauth_layer = NULL; - transport_child_layer = connection_layer; - } else { - char *username = get_remote_username(ssh->conf); - - userauth_layer = ssh2_userauth_new( - connection_layer, ssh->savedhost, ssh->fullhostname, - conf_get_filename(ssh->conf, CONF_keyfile), - conf_get_bool(ssh->conf, CONF_ssh_show_banner), - conf_get_bool(ssh->conf, CONF_tryagent), username, - conf_get_bool(ssh->conf, CONF_change_username), - conf_get_bool(ssh->conf, CONF_try_ki_auth), -#ifndef NO_GSSAPI - conf_get_bool(ssh->conf, CONF_try_gssapi_auth), - conf_get_bool(ssh->conf, CONF_try_gssapi_kex), - conf_get_bool(ssh->conf, CONF_gssapifwd), - &ssh->gss_state -#else - false, - false, - false, - NULL -#endif - ); - ssh_connect_ppl(ssh, userauth_layer); - transport_child_layer = userauth_layer; - - sfree(username); - } - - ssh->base_layer = ssh2_transport_new( - ssh->conf, ssh->savedhost, ssh->savedport, - ssh->fullhostname, - ssh_verstring_get_local(old_bpp), - ssh_verstring_get_remote(old_bpp), -#ifndef NO_GSSAPI - &ssh->gss_state, -#else - NULL, -#endif - &ssh->stats, transport_child_layer, NULL); - ssh_connect_ppl(ssh, ssh->base_layer); - - if (userauth_layer) - ssh2_userauth_set_transport_layer(userauth_layer, - ssh->base_layer); - - } else { - - ssh->bpp = ssh1_bpp_new(ssh->logctx); - ssh_connect_bpp(ssh); - - connection_layer = ssh1_connection_new(ssh, ssh->conf, &ssh->cl); - ssh_connect_ppl(ssh, connection_layer); - - ssh->base_layer = ssh1_login_new( - ssh->conf, ssh->savedhost, ssh->savedport, connection_layer); - ssh_connect_ppl(ssh, ssh->base_layer); - - } - - } else { - ssh->bpp = ssh2_bare_bpp_new(ssh->logctx); - ssh_connect_bpp(ssh); - - connection_layer = ssh2_connection_new( - ssh, ssh->connshare, false, ssh->conf, - ssh_verstring_get_remote(old_bpp), &ssh->cl); - ssh_connect_ppl(ssh, connection_layer); - ssh->base_layer = connection_layer; - } - - /* Connect the base layer - whichever it is - to the BPP, and set - * up its selfptr. */ - ssh->base_layer->selfptr = &ssh->base_layer; - ssh_ppl_setup_queues(ssh->base_layer, &ssh->bpp->in_pq, &ssh->bpp->out_pq); - - seat_update_specials_menu(ssh->seat); - ssh->pinger = pinger_new(ssh->conf, &ssh->backend); - - queue_idempotent_callback(&ssh->bpp->ic_in_raw); - ssh_ppl_process_queue(ssh->base_layer); - - /* Pass in the initial terminal size, if we knew it already. */ - ssh_terminal_size(ssh->cl, ssh->term_width, ssh->term_height); - - ssh_bpp_free(old_bpp); -} - -void ssh_check_frozen(Ssh *ssh) -{ - if (!ssh->s) - return; - - bool prev_frozen = ssh->socket_frozen; - ssh->socket_frozen = (ssh->logically_frozen || - bufchain_size(&ssh->in_raw) > SSH_MAX_BACKLOG); - sk_set_frozen(ssh->s, ssh->socket_frozen); - if (prev_frozen && !ssh->socket_frozen && ssh->bpp) { - /* - * If we've just unfrozen, process any SSH connection data - * that was stashed in our queue while we were frozen. - */ - queue_idempotent_callback(&ssh->bpp->ic_in_raw); - } -} - -void ssh_conn_processed_data(Ssh *ssh) -{ - ssh_check_frozen(ssh); -} - -static void ssh_bpp_output_raw_data_callback(void *vctx) -{ - Ssh *ssh = (Ssh *)vctx; - - if (!ssh->s) - return; - - while (bufchain_size(&ssh->out_raw) > 0) { - size_t backlog; - - ptrlen data = bufchain_prefix(&ssh->out_raw); - - if (ssh->logctx) - log_packet(ssh->logctx, PKT_OUTGOING, -1, NULL, data.ptr, data.len, - 0, NULL, NULL, 0, NULL); - backlog = sk_write(ssh->s, data.ptr, data.len); - - bufchain_consume(&ssh->out_raw, data.len); - - if (backlog > SSH_MAX_BACKLOG) { - ssh_throttle_all(ssh, true, backlog); - return; - } - } - - ssh_check_frozen(ssh); - - if (ssh->pending_close) { - sk_close(ssh->s); - ssh->s = NULL; - } -} - -static void ssh_shutdown_internal(Ssh *ssh) -{ - expire_timer_context(ssh); - - if (ssh->connshare) { - sharestate_free(ssh->connshare); - ssh->connshare = NULL; - } - - if (ssh->pinger) { - pinger_free(ssh->pinger); - ssh->pinger = NULL; - } - - /* - * We only need to free the base PPL, which will free the others - * (if any) transitively. - */ - if (ssh->base_layer) { - ssh_ppl_free(ssh->base_layer); - ssh->base_layer = NULL; - } - - ssh->cl = NULL; -} - -static void ssh_shutdown(Ssh *ssh) -{ - ssh_shutdown_internal(ssh); - - if (ssh->bpp) { - ssh_bpp_free(ssh->bpp); - ssh->bpp = NULL; - } - - if (ssh->s) { - sk_close(ssh->s); - ssh->s = NULL; - } - - bufchain_clear(&ssh->in_raw); - bufchain_clear(&ssh->out_raw); - bufchain_clear(&ssh->user_input); -} - -static void ssh_initiate_connection_close(Ssh *ssh) -{ - /* Wind up everything above the BPP. */ - ssh_shutdown_internal(ssh); - - /* Force any remaining queued SSH packets through the BPP, and - * schedule closing the network socket after they go out. */ - ssh_bpp_handle_output(ssh->bpp); - ssh->pending_close = true; - queue_idempotent_callback(&ssh->ic_out_raw); - - /* Now we expect the other end to close the connection too in - * response, so arrange that we'll receive notification of that - * via ssh_remote_eof. */ - ssh->bpp->expect_close = true; -} - -#define GET_FORMATTED_MSG \ - char *msg; \ - va_list ap; \ - va_start(ap, fmt); \ - msg = dupvprintf(fmt, ap); \ - va_end(ap); \ - ((void)0) /* eat trailing semicolon */ - -void ssh_remote_error(Ssh *ssh, const char *fmt, ...) -{ - if (ssh->base_layer || !ssh->session_started) { - GET_FORMATTED_MSG; - - /* Error messages sent by the remote don't count as clean exits */ - ssh->exitcode = 128; - - /* Close the socket immediately, since the server has already - * closed its end (or is about to). */ - ssh_shutdown(ssh); - - logevent(ssh->logctx, msg); - seat_connection_fatal(ssh->seat, "%s", msg); - sfree(msg); - } -} - -void ssh_remote_eof(Ssh *ssh, const char *fmt, ...) -{ - if (ssh->base_layer || !ssh->session_started) { - GET_FORMATTED_MSG; - - /* EOF from the remote, if we were expecting it, does count as - * a clean exit */ - ssh->exitcode = 0; - - /* Close the socket immediately, since the server has already - * closed its end. */ - ssh_shutdown(ssh); - - logevent(ssh->logctx, msg); - sfree(msg); - seat_notify_remote_exit(ssh->seat); - } else { - /* This is responding to EOF after we've already seen some - * other reason for terminating the session. */ - ssh_shutdown(ssh); - } -} - -void ssh_proto_error(Ssh *ssh, const char *fmt, ...) -{ - if (ssh->base_layer || !ssh->session_started) { - GET_FORMATTED_MSG; - - ssh->exitcode = 128; - - ssh_bpp_queue_disconnect(ssh->bpp, msg, - SSH2_DISCONNECT_PROTOCOL_ERROR); - ssh_initiate_connection_close(ssh); - - logevent(ssh->logctx, msg); - seat_connection_fatal(ssh->seat, "%s", msg); - sfree(msg); - } -} - -void ssh_sw_abort(Ssh *ssh, const char *fmt, ...) -{ - if (ssh->base_layer || !ssh->session_started) { - GET_FORMATTED_MSG; - - ssh->exitcode = 128; - - ssh_initiate_connection_close(ssh); - - logevent(ssh->logctx, msg); - seat_connection_fatal(ssh->seat, "%s", msg); - sfree(msg); - - seat_notify_remote_exit(ssh->seat); - } -} - -void ssh_user_close(Ssh *ssh, const char *fmt, ...) -{ - if (ssh->base_layer || !ssh->session_started) { - GET_FORMATTED_MSG; - - /* Closing the connection due to user action, even if the - * action is the user aborting during authentication prompts, - * does count as a clean exit - except that this is also how - * we signal ordinary session termination, in which case we - * should use the exit status already sent from the main - * session (if any). */ - if (ssh->exitcode < 0) - ssh->exitcode = 0; - - ssh_initiate_connection_close(ssh); - - logevent(ssh->logctx, msg); - sfree(msg); - - seat_notify_remote_exit(ssh->seat); - } -} - -static void ssh_deferred_abort_callback(void *vctx) -{ - Ssh *ssh = (Ssh *)vctx; - char *msg = ssh->deferred_abort_message; - ssh->deferred_abort_message = NULL; - ssh_sw_abort(ssh, "%s", msg); - sfree(msg); -} - -void ssh_sw_abort_deferred(Ssh *ssh, const char *fmt, ...) -{ - if (!ssh->deferred_abort_message) { - GET_FORMATTED_MSG; - ssh->deferred_abort_message = msg; - queue_toplevel_callback(ssh_deferred_abort_callback, ssh); - } -} - -static void ssh_socket_log(Plug *plug, PlugLogType type, SockAddr *addr, - int port, const char *error_msg, int error_code) -{ - Ssh *ssh = container_of(plug, Ssh, plug); - - /* - * While we're attempting connection sharing, don't loudly log - * everything that happens. Real TCP connections need to be logged - * when we _start_ trying to connect, because it might be ages - * before they respond if something goes wrong; but connection - * sharing is local and quick to respond, and it's sufficient to - * simply wait and see whether it worked afterwards. - */ - - if (!ssh->attempting_connshare) - backend_socket_log(ssh->seat, ssh->logctx, type, addr, port, - error_msg, error_code, ssh->conf, - ssh->session_started); -} - -static void ssh_closing(Plug *plug, const char *error_msg, int error_code, - bool calling_back) -{ - Ssh *ssh = container_of(plug, Ssh, plug); - if (error_msg) { - ssh_remote_error(ssh, "%s", error_msg); - } else if (ssh->bpp) { - ssh->bpp->input_eof = true; - queue_idempotent_callback(&ssh->bpp->ic_in_raw); - } -} - -static void ssh_receive(Plug *plug, int urgent, const char *data, size_t len) -{ - Ssh *ssh = container_of(plug, Ssh, plug); - - /* Log raw data, if we're in that mode. */ - if (ssh->logctx) - log_packet(ssh->logctx, PKT_INCOMING, -1, NULL, data, len, - 0, NULL, NULL, 0, NULL); - - bufchain_add(&ssh->in_raw, data, len); - if (!ssh->logically_frozen && ssh->bpp) - queue_idempotent_callback(&ssh->bpp->ic_in_raw); - - ssh_check_frozen(ssh); -} - -static void ssh_sent(Plug *plug, size_t bufsize) -{ - Ssh *ssh = container_of(plug, Ssh, plug); - /* - * If the send backlog on the SSH socket itself clears, we should - * unthrottle the whole world if it was throttled. Also trigger an - * extra call to the consumer of the BPP's output, to try to send - * some more data off its bufchain. - */ - if (bufsize < SSH_MAX_BACKLOG) { - ssh_throttle_all(ssh, false, bufsize); - queue_idempotent_callback(&ssh->ic_out_raw); - } -} - -static void ssh_hostport_setup(const char *host, int port, Conf *conf, - char **savedhost, int *savedport, - char **loghost_ret) -{ - char *loghost = conf_get_str(conf, CONF_loghost); - if (loghost_ret) - *loghost_ret = loghost; - - if (*loghost) { - char *tmphost; - char *colon; - - tmphost = dupstr(loghost); - *savedport = 22; /* default ssh port */ - - /* - * A colon suffix on the hostname string also lets us affect - * savedport. (Unless there are multiple colons, in which case - * we assume this is an unbracketed IPv6 literal.) - */ - colon = host_strrchr(tmphost, ':'); - if (colon && colon == host_strchr(tmphost, ':')) { - *colon++ = '\0'; - if (*colon) - *savedport = atoi(colon); - } - - *savedhost = host_strduptrim(tmphost); - sfree(tmphost); - } else { - *savedhost = host_strduptrim(host); - if (port < 0) - port = 22; /* default ssh port */ - *savedport = port; - } -} - -static bool ssh_test_for_upstream(const char *host, int port, Conf *conf) -{ - char *savedhost; - int savedport; - bool ret; - - random_ref(); /* platform may need this to determine share socket name */ - ssh_hostport_setup(host, port, conf, &savedhost, &savedport, NULL); - ret = ssh_share_test_for_upstream(savedhost, savedport, conf); - sfree(savedhost); - random_unref(); - - return ret; -} - -static char *ssh_close_warn_text(Backend *be) -{ - Ssh *ssh = container_of(be, Ssh, backend); - if (!ssh->connshare) - return NULL; - int ndowns = share_ndownstreams(ssh->connshare); - if (ndowns == 0) - return NULL; - char *msg = dupprintf("This will also close %d downstream connection%s.", - ndowns, ndowns==1 ? "" : "s"); - return msg; -} - -static const PlugVtable Ssh_plugvt = { - .log = ssh_socket_log, - .closing = ssh_closing, - .receive = ssh_receive, - .sent = ssh_sent, -}; - -/* - * 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 char *connect_to_host( - Ssh *ssh, const char *host, int port, char **realhost, - bool nodelay, bool keepalive) -{ - SockAddr *addr; - const char *err; - char *loghost; - int addressfamily, sshprot; - - ssh_hostport_setup(host, port, ssh->conf, - &ssh->savedhost, &ssh->savedport, &loghost); - - ssh->plug.vt = &Ssh_plugvt; - - /* - * Try connection-sharing, in case that means we don't open a - * socket after all. ssh_connection_sharing_init will connect to a - * previously established upstream if it can, and failing that, - * establish a listening socket for _us_ to be the upstream. In - * the latter case it will return NULL just as if it had done - * nothing, because here we only need to care if we're a - * downstream and need to do our connection setup differently. - */ - ssh->connshare = NULL; - ssh->attempting_connshare = true; /* affects socket logging behaviour */ - ssh->s = ssh_connection_sharing_init( - ssh->savedhost, ssh->savedport, ssh->conf, ssh->logctx, - &ssh->plug, &ssh->connshare); - if (ssh->connshare) - ssh_connshare_provide_connlayer(ssh->connshare, &ssh->cl_dummy); - ssh->attempting_connshare = false; - if (ssh->s != NULL) { - /* - * We are a downstream. - */ - ssh->bare_connection = true; - ssh->fullhostname = NULL; - *realhost = dupstr(host); /* best we can do */ - - if (seat_verbose(ssh->seat) || seat_interactive(ssh->seat)) { - /* In an interactive session, or in verbose mode, announce - * in the console window that we're a sharing downstream, - * to avoid confusing users as to why this session doesn't - * behave in quite the usual way. */ - const char *msg = - "Reusing a shared connection to this server.\r\n"; - seat_stderr_pl(ssh->seat, ptrlen_from_asciz(msg)); - } - } else { - /* - * We're not a downstream, so open a normal socket. - */ - - /* - * Try to find host. - */ - addressfamily = conf_get_int(ssh->conf, CONF_addressfamily); - addr = name_lookup(host, port, realhost, ssh->conf, addressfamily, - ssh->logctx, "SSH connection"); - if ((err = sk_addr_error(addr)) != NULL) { - sk_addr_free(addr); - return dupstr(err); - } - ssh->fullhostname = dupstr(*realhost); /* save in case of GSSAPI */ - - ssh->s = new_connection(addr, *realhost, port, - false, true, nodelay, keepalive, - &ssh->plug, ssh->conf); - if ((err = sk_socket_error(ssh->s)) != NULL) { - ssh->s = NULL; - seat_notify_remote_exit(ssh->seat); - return dupstr(err); - } - } - - /* - * The SSH version number is always fixed (since we no longer support - * fallback between versions), so set it now. - */ - sshprot = conf_get_int(ssh->conf, CONF_sshprot); - assert(sshprot == 0 || sshprot == 3); - if (sshprot == 0) - /* SSH-1 only */ - ssh->version = 1; - if (sshprot == 3 || ssh->bare_connection) { - /* SSH-2 only */ - ssh->version = 2; - } - - /* - * Set up the initial BPP that will do the version string - * exchange, and get it started so that it can send the outgoing - * version string early if it wants to. - */ - ssh->version_receiver.got_ssh_version = ssh_got_ssh_version; - ssh->bpp = ssh_verstring_new( - ssh->conf, ssh->logctx, ssh->bare_connection, - ssh->version == 1 ? "1.5" : "2.0", &ssh->version_receiver, - false, "PuTTY"); - ssh_connect_bpp(ssh); - queue_idempotent_callback(&ssh->bpp->ic_in_raw); - - /* - * loghost, if configured, overrides realhost. - */ - if (*loghost) { - sfree(*realhost); - *realhost = dupstr(loghost); - } - - return NULL; -} - -/* - * Throttle or unthrottle the SSH connection. - */ -void ssh_throttle_conn(Ssh *ssh, int adjust) -{ - int old_count = ssh->conn_throttle_count; - bool frozen; - - ssh->conn_throttle_count += adjust; - assert(ssh->conn_throttle_count >= 0); - - if (ssh->conn_throttle_count && !old_count) { - frozen = true; - } else if (!ssh->conn_throttle_count && old_count) { - frozen = false; - } else { - return; /* don't change current frozen state */ - } - - ssh->logically_frozen = frozen; - ssh_check_frozen(ssh); -} - -/* - * Throttle or unthrottle _all_ local data streams (for when sends - * on the SSH connection itself back up). - */ -static void ssh_throttle_all(Ssh *ssh, bool enable, size_t bufsize) -{ - if (enable == ssh->throttled_all) - return; - ssh->throttled_all = enable; - ssh->overall_bufsize = bufsize; - - ssh_throttle_all_channels(ssh->cl, enable); -} - -static void ssh_cache_conf_values(Ssh *ssh) -{ - ssh->pls.omit_passwords = conf_get_bool(ssh->conf, CONF_logomitpass); - ssh->pls.omit_data = conf_get_bool(ssh->conf, CONF_logomitdata); -} - -bool ssh_is_bare(Ssh *ssh) -{ - return ssh->backend.vt->protocol == PROT_SSHCONN; -} - -/* Dummy connlayer must provide ssh_sharing_no_more_downstreams, - * because it might be called early due to plink -shareexists */ -static void dummy_sharing_no_more_downstreams(ConnectionLayer *cl) {} -static const ConnectionLayerVtable dummy_connlayer_vtable = { - .sharing_no_more_downstreams = dummy_sharing_no_more_downstreams, -}; - -/* - * Called to set up the connection. - * - * Returns an error message, or NULL on success. - */ -static char *ssh_init(const BackendVtable *vt, Seat *seat, - Backend **backend_handle, LogContext *logctx, - Conf *conf, const char *host, int port, - char **realhost, bool nodelay, bool keepalive) -{ - Ssh *ssh; - - ssh = snew(Ssh); - memset(ssh, 0, sizeof(Ssh)); - - ssh->conf = conf_copy(conf); - ssh_cache_conf_values(ssh); - ssh->exitcode = -1; - ssh->pls.kctx = SSH2_PKTCTX_NOKEX; - ssh->pls.actx = SSH2_PKTCTX_NOAUTH; - bufchain_init(&ssh->in_raw); - bufchain_init(&ssh->out_raw); - bufchain_init(&ssh->user_input); - ssh->ic_out_raw.fn = ssh_bpp_output_raw_data_callback; - ssh->ic_out_raw.ctx = ssh; - - ssh->term_width = conf_get_int(ssh->conf, CONF_width); - ssh->term_height = conf_get_int(ssh->conf, CONF_height); - - ssh->backend.vt = vt; - *backend_handle = &ssh->backend; - - ssh->bare_connection = (vt->protocol == PROT_SSHCONN); - - ssh->seat = seat; - ssh->cl_dummy.vt = &dummy_connlayer_vtable; - ssh->cl_dummy.logctx = ssh->logctx = logctx; - - random_ref(); /* do this now - may be needed by sharing setup code */ - ssh->need_random_unref = true; - - char *conn_err = connect_to_host( - ssh, host, port, realhost, nodelay, keepalive); - if (conn_err) { - /* Call random_unref now instead of waiting until the caller - * frees this useless Ssh object, in case the caller is - * impatient and just exits without bothering, in which case - * the random seed won't be re-saved. */ - ssh->need_random_unref = false; - random_unref(); - return conn_err; - } - - return NULL; -} - -static void ssh_free(Backend *be) -{ - Ssh *ssh = container_of(be, Ssh, backend); - bool need_random_unref; - - ssh_shutdown(ssh); - - conf_free(ssh->conf); - if (ssh->connshare) - sharestate_free(ssh->connshare); - sfree(ssh->savedhost); - sfree(ssh->fullhostname); - sfree(ssh->specials); - -#ifndef NO_GSSAPI - if (ssh->gss_state.srv_name) - ssh->gss_state.lib->release_name( - ssh->gss_state.lib, &ssh->gss_state.srv_name); - if (ssh->gss_state.ctx != NULL) - ssh->gss_state.lib->release_cred( - ssh->gss_state.lib, &ssh->gss_state.ctx); - if (ssh->gss_state.libs) - ssh_gss_cleanup(ssh->gss_state.libs); -#endif - - sfree(ssh->deferred_abort_message); - - delete_callbacks_for_context(ssh); /* likely to catch ic_out_raw */ - - need_random_unref = ssh->need_random_unref; - sfree(ssh); - - if (need_random_unref) - random_unref(); -} - -/* - * Reconfigure the SSH backend. - */ -static void ssh_reconfig(Backend *be, Conf *conf) -{ - Ssh *ssh = container_of(be, Ssh, backend); - - if (ssh->pinger) - pinger_reconfig(ssh->pinger, ssh->conf, conf); - - ssh_ppl_reconfigure(ssh->base_layer, conf); - - conf_free(ssh->conf); - ssh->conf = conf_copy(conf); - ssh_cache_conf_values(ssh); -} - -/* - * Called to send data down the SSH connection. - */ -static size_t ssh_send(Backend *be, const char *buf, size_t len) -{ - Ssh *ssh = container_of(be, Ssh, backend); - - if (ssh == NULL || ssh->s == NULL) - return 0; - - bufchain_add(&ssh->user_input, buf, len); - if (ssh->base_layer) - ssh_ppl_got_user_input(ssh->base_layer); - - return backend_sendbuffer(&ssh->backend); -} - -/* - * Called to query the current amount of buffered stdin data. - */ -static size_t ssh_sendbuffer(Backend *be) -{ - Ssh *ssh = container_of(be, Ssh, backend); - size_t backlog; - - if (!ssh || !ssh->s || !ssh->cl) - return 0; - - backlog = ssh_stdin_backlog(ssh->cl); - - if (ssh->base_layer) - backlog += ssh_ppl_queued_data_size(ssh->base_layer); - - /* - * If the SSH socket itself has backed up, add the total backup - * size on that to any individual buffer on the stdin channel. - */ - if (ssh->throttled_all) - backlog += ssh->overall_bufsize; - - return backlog; -} - -/* - * Called to set the size of the window from SSH's POV. - */ -static void ssh_size(Backend *be, int width, int height) -{ - Ssh *ssh = container_of(be, Ssh, backend); - - ssh->term_width = width; - ssh->term_height = height; - if (ssh->cl) - ssh_terminal_size(ssh->cl, ssh->term_width, ssh->term_height); -} - -struct ssh_add_special_ctx { - SessionSpecial *specials; - size_t nspecials, specials_size; -}; - -static void ssh_add_special(void *vctx, const char *text, - SessionSpecialCode code, int arg) -{ - struct ssh_add_special_ctx *ctx = (struct ssh_add_special_ctx *)vctx; - SessionSpecial *spec; - - sgrowarray(ctx->specials, ctx->specials_size, ctx->nspecials); - spec = &ctx->specials[ctx->nspecials++]; - spec->name = text; - spec->code = code; - spec->arg = arg; -} - -/* - * Return a list of the special codes that make sense in this - * protocol. - */ -static const SessionSpecial *ssh_get_specials(Backend *be) -{ - Ssh *ssh = container_of(be, Ssh, backend); - - /* - * Ask all our active protocol layers what specials they've got, - * and amalgamate the list into one combined one. - */ - - struct ssh_add_special_ctx ctx[1]; - - ctx->specials = NULL; - ctx->nspecials = ctx->specials_size = 0; - - if (ssh->base_layer) - ssh_ppl_get_specials(ssh->base_layer, ssh_add_special, ctx); - - if (ctx->specials) { - /* If the list is non-empty, terminate it with a SS_EXITMENU. */ - ssh_add_special(ctx, NULL, SS_EXITMENU, 0); - } - - sfree(ssh->specials); - ssh->specials = ctx->specials; - return ssh->specials; -} - -/* - * Send special codes. - */ -static void ssh_special(Backend *be, SessionSpecialCode code, int arg) -{ - Ssh *ssh = container_of(be, Ssh, backend); - - if (ssh->base_layer) - ssh_ppl_special_cmd(ssh->base_layer, code, arg); -} - -/* - * This is called when the seat's output channel manages to clear some - * backlog. - */ -static void ssh_unthrottle(Backend *be, size_t bufsize) -{ - Ssh *ssh = container_of(be, Ssh, backend); - - if (ssh->cl) - ssh_stdout_unthrottle(ssh->cl, bufsize); -} - -static bool ssh_connected(Backend *be) -{ - Ssh *ssh = container_of(be, Ssh, backend); - return ssh->s != NULL; -} - -static bool ssh_sendok(Backend *be) -{ - Ssh *ssh = container_of(be, Ssh, backend); - return ssh->base_layer && ssh_ppl_want_user_input(ssh->base_layer); -} - -void ssh_ldisc_update(Ssh *ssh) -{ - /* Called when the connection layer wants to propagate an update - * to the line discipline options */ - if (ssh->ldisc) - ldisc_echoedit_update(ssh->ldisc); -} - -static bool ssh_ldisc(Backend *be, int option) -{ - Ssh *ssh = container_of(be, Ssh, backend); - return ssh->cl ? ssh_ldisc_option(ssh->cl, option) : false; -} - -static void ssh_provide_ldisc(Backend *be, Ldisc *ldisc) -{ - Ssh *ssh = container_of(be, Ssh, backend); - ssh->ldisc = ldisc; -} - -void ssh_got_exitcode(Ssh *ssh, int exitcode) -{ - ssh->exitcode = exitcode; -} - -static int ssh_return_exitcode(Backend *be) -{ - Ssh *ssh = container_of(be, Ssh, backend); - if (ssh->s && (!ssh->session_started || ssh->base_layer)) - return -1; - else - return (ssh->exitcode >= 0 ? ssh->exitcode : INT_MAX); -} - -/* - * cfg_info for SSH is the protocol running in this session. - * (1 or 2 for the full SSH-1 or SSH-2 protocol; -1 for the bare - * SSH-2 connection protocol, i.e. a downstream; 0 for not-decided-yet.) - */ -static int ssh_cfg_info(Backend *be) -{ - Ssh *ssh = container_of(be, Ssh, backend); - if (ssh->version == 0) - return 0; /* don't know yet */ - else if (ssh->bare_connection) - return -1; - else - 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 bool ssh_fallback_cmd(Backend *be) -{ - Ssh *ssh = container_of(be, Ssh, backend); - return ssh->fallback_cmd; -} - -void ssh_got_fallback_cmd(Ssh *ssh) -{ - ssh->fallback_cmd = true; -} - -const BackendVtable ssh_backend = { - .init = ssh_init, - .free = ssh_free, - .reconfig = ssh_reconfig, - .send = ssh_send, - .sendbuffer = ssh_sendbuffer, - .size = ssh_size, - .special = ssh_special, - .get_specials = ssh_get_specials, - .connected = ssh_connected, - .exitcode = ssh_return_exitcode, - .sendok = ssh_sendok, - .ldisc_option_state = ssh_ldisc, - .provide_ldisc = ssh_provide_ldisc, - .unthrottle = ssh_unthrottle, - .cfg_info = ssh_cfg_info, - .test_for_upstream = ssh_test_for_upstream, - .close_warn_text = ssh_close_warn_text, - .id = "ssh", - .displayname = "SSH", - .protocol = PROT_SSH, - .default_port = 22, -}; - -const BackendVtable sshconn_backend = { - .init = ssh_init, - .free = ssh_free, - .reconfig = ssh_reconfig, - .send = ssh_send, - .sendbuffer = ssh_sendbuffer, - .size = ssh_size, - .special = ssh_special, - .get_specials = ssh_get_specials, - .connected = ssh_connected, - .exitcode = ssh_return_exitcode, - .sendok = ssh_sendok, - .ldisc_option_state = ssh_ldisc, - .provide_ldisc = ssh_provide_ldisc, - .unthrottle = ssh_unthrottle, - .cfg_info = ssh_cfg_info, - .test_for_upstream = ssh_test_for_upstream, - .close_warn_text = ssh_close_warn_text, - .id = "ssh-connection", - .displayname = "Bare ssh-connection", - .protocol = PROT_SSHCONN, -}; diff --git a/ssh.h b/ssh.h index 24d7f1a5..61a95c8e 100644 --- a/ssh.h +++ b/ssh.h @@ -1612,7 +1612,7 @@ enum { /* TTY modes with opcodes defined consistently in the SSH specs. */ #define TTYMODE_CHAR(name, val, index) SSH_TTYMODE_##name = val, #define TTYMODE_FLAG(name, val, field, mask) SSH_TTYMODE_##name = val, - #include "sshttymodes.h" + #include "ssh/ttymode-list.h" #undef TTYMODE_CHAR #undef TTYMODE_FLAG diff --git a/ssh/CMakeLists.txt b/ssh/CMakeLists.txt new file mode 100644 index 00000000..4b0e03fe --- /dev/null +++ b/ssh/CMakeLists.txt @@ -0,0 +1,51 @@ +add_library(sshcommon OBJECT + bpp1.c + bpp2.c + bpp-bare.c + censor1.c + censor2.c + common.c + connection1.c + connection2.c + crc-attack-detector.c + gssc.c + login1.c + pgssapi.c + portfwd.c + ../sshpubk.c + ../sshrand.c + transient-hostkey-cache.c + transport2.c + verstring.c + x11fwd.c + zlib.c) + +add_library(sftpcommon OBJECT sftpcommon.c) + +add_library(sshclient STATIC + agentf.c + connection1-client.c + connection2-client.c + kex2-client.c + mainchan.c + sharing.c + ssh.c + userauth2-client.c + $ + $ + $) + +add_library(sshserver STATIC + connection1-server.c + connection2-server.c + kex2-server.c + login1-server.c + server.c + sesschan.c + sftpserver.c + userauth2-server.c + $ + $) + +add_sources_from_current_dir(sftpclient sftp.c) +target_sources(sftpclient PRIVATE $) diff --git a/ssh/agentf.c b/ssh/agentf.c new file mode 100644 index 00000000..6a5ecee5 --- /dev/null +++ b/ssh/agentf.c @@ -0,0 +1,249 @@ +/* + * SSH agent forwarding. + */ + +#include +#include +#include + +#include "putty.h" +#include "ssh.h" +#include "pageant.h" +#include "channel.h" + +typedef struct agentf { + SshChannel *c; + bufchain inbuffer; + agent_pending_query *pending; + bool input_wanted; + bool rcvd_eof; + + Channel chan; +} agentf; + +static void agentf_got_response(agentf *af, void *reply, int replylen) +{ + af->pending = NULL; + + if (!reply) { + /* The real agent didn't send any kind of reply at all for + * some reason, so fake an SSH_AGENT_FAILURE. */ + reply = "\0\0\0\1\5"; + replylen = 5; + } + + sshfwd_write(af->c, reply, replylen); +} + +static void agentf_callback(void *vctx, void *reply, int replylen); + +static void agentf_try_forward(agentf *af) +{ + size_t datalen, length; + strbuf *message; + unsigned char msglen[4]; + void *reply; + int replylen; + + /* + * Don't try to parallelise agent requests. Wait for each one to + * return before attempting the next. + */ + if (af->pending) + return; + + /* + * If the outgoing side of the channel connection is currently + * throttled, don't submit any new forwarded requests to the real + * agent. This causes the input side of the agent forwarding not + * to be emptied, exerting the required back-pressure on the + * remote client, and encouraging it to read our responses before + * sending too many more requests. + */ + if (!af->input_wanted) + return; + + while (1) { + /* + * Try to extract a complete message from the input buffer. + */ + datalen = bufchain_size(&af->inbuffer); + if (datalen < 4) + break; /* not even a length field available yet */ + + bufchain_fetch(&af->inbuffer, msglen, 4); + length = GET_32BIT_MSB_FIRST(msglen); + + if (length > AGENT_MAX_MSGLEN-4) { + /* + * If the remote has sent a message that's just _too_ + * long, we should reject it in advance of seeing the rest + * of the incoming message, and also close the connection + * for good measure (which avoids us having to faff about + * with carefully ignoring just the right number of bytes + * from the overlong message). + */ + agentf_got_response(af, NULL, 0); + sshfwd_write_eof(af->c); + return; + } + + if (length > datalen - 4) + break; /* a whole message is not yet available */ + + bufchain_consume(&af->inbuffer, 4); + + message = strbuf_new_for_agent_query(); + bufchain_fetch_consume( + &af->inbuffer, strbuf_append(message, length), length); + af->pending = agent_query( + message, &reply, &replylen, agentf_callback, af); + strbuf_free(message); + + if (af->pending) + return; /* agent_query promised to reply in due course */ + + /* + * If the agent gave us an answer immediately, pass it + * straight on and go round this loop again. + */ + agentf_got_response(af, reply, replylen); + sfree(reply); + } + + /* + * If we get here (i.e. we left the above while loop via 'break' + * rather than 'return'), that means we've determined that the + * input buffer for the agent forwarding connection doesn't + * contain a complete request. + * + * So if there's potentially more data to come, we can return now, + * and wait for the remote client to send it. But if the remote + * has sent EOF, it would be a mistake to do that, because we'd be + * waiting a long time. So this is the moment to check for EOF, + * and respond appropriately. + */ + if (af->rcvd_eof) + sshfwd_write_eof(af->c); +} + +static void agentf_callback(void *vctx, void *reply, int replylen) +{ + agentf *af = (agentf *)vctx; + + agentf_got_response(af, reply, replylen); + sfree(reply); + + /* + * Now try to extract and send further messages from the channel's + * input-side buffer. + */ + agentf_try_forward(af); +} + +static void agentf_free(Channel *chan); +static size_t agentf_send(Channel *chan, bool is_stderr, const void *, size_t); +static void agentf_send_eof(Channel *chan); +static char *agentf_log_close_msg(Channel *chan); +static void agentf_set_input_wanted(Channel *chan, bool wanted); + +static const ChannelVtable agentf_channelvt = { + .free = agentf_free, + .open_confirmation = chan_remotely_opened_confirmation, + .open_failed = chan_remotely_opened_failure, + .send = agentf_send, + .send_eof = agentf_send_eof, + .set_input_wanted = agentf_set_input_wanted, + .log_close_msg = agentf_log_close_msg, + .want_close = chan_default_want_close, + .rcvd_exit_status = chan_no_exit_status, + .rcvd_exit_signal = chan_no_exit_signal, + .rcvd_exit_signal_numeric = chan_no_exit_signal_numeric, + .run_shell = chan_no_run_shell, + .run_command = chan_no_run_command, + .run_subsystem = chan_no_run_subsystem, + .enable_x11_forwarding = chan_no_enable_x11_forwarding, + .enable_agent_forwarding = chan_no_enable_agent_forwarding, + .allocate_pty = chan_no_allocate_pty, + .set_env = chan_no_set_env, + .send_break = chan_no_send_break, + .send_signal = chan_no_send_signal, + .change_window_size = chan_no_change_window_size, + .request_response = chan_no_request_response, +}; + +Channel *agentf_new(SshChannel *c) +{ + agentf *af = snew(agentf); + af->c = c; + af->chan.vt = &agentf_channelvt; + af->chan.initial_fixed_window_size = 0; + af->rcvd_eof = false; + bufchain_init(&af->inbuffer); + af->pending = NULL; + af->input_wanted = true; + return &af->chan; +} + +static void agentf_free(Channel *chan) +{ + assert(chan->vt == &agentf_channelvt); + agentf *af = container_of(chan, agentf, chan); + + if (af->pending) + agent_cancel_query(af->pending); + bufchain_clear(&af->inbuffer); + sfree(af); +} + +static size_t agentf_send(Channel *chan, bool is_stderr, + const void *data, size_t length) +{ + assert(chan->vt == &agentf_channelvt); + agentf *af = container_of(chan, agentf, chan); + bufchain_add(&af->inbuffer, data, length); + agentf_try_forward(af); + + /* + * We exert back-pressure on an agent forwarding client if and + * only if we're waiting for the response to an asynchronous agent + * request. This prevents the client running out of window while + * receiving the _first_ message, but means that if any message + * takes time to process, the client will be discouraged from + * sending an endless stream of further ones after it. + */ + return (af->pending ? bufchain_size(&af->inbuffer) : 0); +} + +static void agentf_send_eof(Channel *chan) +{ + assert(chan->vt == &agentf_channelvt); + agentf *af = container_of(chan, agentf, chan); + + af->rcvd_eof = true; + + /* Call try_forward, which will respond to the EOF now if + * appropriate, or wait until the queue of outstanding requests is + * dealt with if not. */ + agentf_try_forward(af); +} + +static char *agentf_log_close_msg(Channel *chan) +{ + return dupstr("Agent-forwarding connection closed"); +} + +static void agentf_set_input_wanted(Channel *chan, bool wanted) +{ + assert(chan->vt == &agentf_channelvt); + agentf *af = container_of(chan, agentf, chan); + + af->input_wanted = wanted; + + /* Agent forwarding channels are buffer-managed by not asking the + * agent questions if the SSH channel isn't accepting input. So if + * it's started again, we should ask a question if we have one + * pending.. */ + if (wanted) + agentf_try_forward(af); +} diff --git a/ssh/bpp-bare.c b/ssh/bpp-bare.c new file mode 100644 index 00000000..3546d160 --- /dev/null +++ b/ssh/bpp-bare.c @@ -0,0 +1,204 @@ +/* + * Trivial binary packet protocol for the 'bare' ssh-connection + * protocol used in PuTTY's SSH-2 connection sharing system. + */ + +#include + +#include "putty.h" +#include "ssh.h" +#include "bpp.h" +#include "sshcr.h" + +struct ssh2_bare_bpp_state { + int crState; + long packetlen, maxlen; + unsigned char *data; + unsigned long incoming_sequence, outgoing_sequence; + PktIn *pktin; + + BinaryPacketProtocol bpp; +}; + +static void ssh2_bare_bpp_free(BinaryPacketProtocol *bpp); +static void ssh2_bare_bpp_handle_input(BinaryPacketProtocol *bpp); +static void ssh2_bare_bpp_handle_output(BinaryPacketProtocol *bpp); +static PktOut *ssh2_bare_bpp_new_pktout(int type); + +static const BinaryPacketProtocolVtable ssh2_bare_bpp_vtable = { + .free = ssh2_bare_bpp_free, + .handle_input = ssh2_bare_bpp_handle_input, + .handle_output = ssh2_bare_bpp_handle_output, + .new_pktout = ssh2_bare_bpp_new_pktout, + .queue_disconnect = ssh2_bpp_queue_disconnect, /* in common.c */ + + /* packet size limit, per protocol spec in sharing.c comment */ + .packet_size_limit = 0x4000, +}; + +BinaryPacketProtocol *ssh2_bare_bpp_new(LogContext *logctx) +{ + struct ssh2_bare_bpp_state *s = snew(struct ssh2_bare_bpp_state); + memset(s, 0, sizeof(*s)); + s->bpp.vt = &ssh2_bare_bpp_vtable; + s->bpp.logctx = logctx; + ssh_bpp_common_setup(&s->bpp); + return &s->bpp; +} + +static void ssh2_bare_bpp_free(BinaryPacketProtocol *bpp) +{ + struct ssh2_bare_bpp_state *s = + container_of(bpp, struct ssh2_bare_bpp_state, bpp); + sfree(s->pktin); + sfree(s); +} + +#define BPP_READ(ptr, len) do \ + { \ + bool success; \ + crMaybeWaitUntilV((success = bufchain_try_fetch_consume( \ + s->bpp.in_raw, ptr, len)) || \ + s->bpp.input_eof); \ + if (!success) \ + goto eof; \ + ssh_check_frozen(s->bpp.ssh); \ + } while (0) + +static void ssh2_bare_bpp_handle_input(BinaryPacketProtocol *bpp) +{ + struct ssh2_bare_bpp_state *s = + container_of(bpp, struct ssh2_bare_bpp_state, bpp); + + crBegin(s->crState); + + while (1) { + /* Read the length field. */ + { + unsigned char lenbuf[4]; + BPP_READ(lenbuf, 4); + s->packetlen = toint(GET_32BIT_MSB_FIRST(lenbuf)); + } + + if (s->packetlen <= 0 || s->packetlen >= (long)OUR_V2_PACKETLIMIT) { + ssh_sw_abort(s->bpp.ssh, "Invalid packet length received"); + crStopV; + } + + /* + * Allocate the packet to return, now we know its length. + */ + s->pktin = snew_plus(PktIn, s->packetlen); + s->pktin->qnode.prev = s->pktin->qnode.next = NULL; + s->pktin->qnode.on_free_queue = false; + s->maxlen = 0; + s->data = snew_plus_get_aux(s->pktin); + + s->pktin->sequence = s->incoming_sequence++; + + /* + * Read the remainder of the packet. + */ + BPP_READ(s->data, s->packetlen); + + /* + * The data we just read is precisely the initial type byte + * followed by the packet payload. + */ + s->pktin->type = s->data[0]; + s->data++; + s->packetlen--; + BinarySource_INIT(s->pktin, s->data, s->packetlen); + + if (s->pktin->type == SSH2_MSG_EXT_INFO) { + /* + * Mild layer violation: EXT_INFO is not permitted in the + * bare ssh-connection protocol. Faulting it here means + * that ssh2_common_filter_queue doesn't receive it in the + * first place unless it's legal to have sent it. + */ + ssh_proto_error(s->bpp.ssh, "Remote side sent SSH2_MSG_EXT_INFO " + "in bare connection protocol"); + return; + } + + /* + * Log incoming packet, possibly omitting sensitive fields. + */ + if (s->bpp.logctx) { + logblank_t blanks[MAX_BLANKS]; + int nblanks = ssh2_censor_packet( + s->bpp.pls, s->pktin->type, false, + make_ptrlen(s->data, s->packetlen), blanks); + log_packet(s->bpp.logctx, PKT_INCOMING, s->pktin->type, + ssh2_pkt_type(s->bpp.pls->kctx, s->bpp.pls->actx, + s->pktin->type), + get_ptr(s->pktin), get_avail(s->pktin), nblanks, blanks, + &s->pktin->sequence, 0, NULL); + } + + if (ssh2_bpp_check_unimplemented(&s->bpp, s->pktin)) { + sfree(s->pktin); + s->pktin = NULL; + continue; + } + + s->pktin->qnode.formal_size = get_avail(s->pktin); + pq_push(&s->bpp.in_pq, s->pktin); + s->pktin = NULL; + } + + eof: + if (!s->bpp.expect_close) { + ssh_remote_error(s->bpp.ssh, + "Remote side unexpectedly closed network connection"); + } else { + ssh_remote_eof(s->bpp.ssh, "Remote side closed network connection"); + } + return; /* avoid touching s now it's been freed */ + + crFinishV; +} + +static PktOut *ssh2_bare_bpp_new_pktout(int pkt_type) +{ + PktOut *pkt = ssh_new_packet(); + pkt->length = 4; /* space for packet length */ + pkt->type = pkt_type; + put_byte(pkt, pkt_type); + return pkt; +} + +static void ssh2_bare_bpp_format_packet(struct ssh2_bare_bpp_state *s, + PktOut *pkt) +{ + if (s->bpp.logctx) { + ptrlen pktdata = make_ptrlen(pkt->data + 5, pkt->length - 5); + logblank_t blanks[MAX_BLANKS]; + int nblanks = ssh2_censor_packet( + s->bpp.pls, pkt->type, true, pktdata, blanks); + log_packet(s->bpp.logctx, PKT_OUTGOING, pkt->type, + ssh2_pkt_type(s->bpp.pls->kctx, s->bpp.pls->actx, + pkt->type), + pktdata.ptr, pktdata.len, nblanks, blanks, + &s->outgoing_sequence, + pkt->downstream_id, pkt->additional_log_text); + } + + s->outgoing_sequence++; /* only for diagnostics, really */ + + PUT_32BIT_MSB_FIRST(pkt->data, pkt->length - 4); + bufchain_add(s->bpp.out_raw, pkt->data, pkt->length); +} + +static void ssh2_bare_bpp_handle_output(BinaryPacketProtocol *bpp) +{ + struct ssh2_bare_bpp_state *s = + container_of(bpp, struct ssh2_bare_bpp_state, bpp); + PktOut *pkt; + + while ((pkt = pq_pop(&s->bpp.out_pq)) != NULL) { + ssh2_bare_bpp_format_packet(s, pkt); + ssh_free_pktout(pkt); + } +} diff --git a/ssh/bpp.h b/ssh/bpp.h new file mode 100644 index 00000000..87e7d7e7 --- /dev/null +++ b/ssh/bpp.h @@ -0,0 +1,179 @@ +/* + * Abstraction of the binary packet protocols used in SSH. + */ + +#ifndef PUTTY_SSHBPP_H +#define PUTTY_SSHBPP_H + +typedef struct BinaryPacketProtocolVtable BinaryPacketProtocolVtable; + +struct BinaryPacketProtocolVtable { + void (*free)(BinaryPacketProtocol *); + void (*handle_input)(BinaryPacketProtocol *); + void (*handle_output)(BinaryPacketProtocol *); + PktOut *(*new_pktout)(int type); + void (*queue_disconnect)(BinaryPacketProtocol *, + const char *msg, int category); + uint32_t packet_size_limit; +}; + +struct BinaryPacketProtocol { + const struct BinaryPacketProtocolVtable *vt; + bufchain *in_raw, *out_raw; + bool input_eof; /* set this if in_raw will never be added to again */ + PktInQueue in_pq; + PktOutQueue out_pq; + PacketLogSettings *pls; + LogContext *logctx; + Ssh *ssh; + + /* ic_in_raw is filled in by the BPP (probably by calling + * ssh_bpp_common_setup). The BPP's owner triggers it when data is + * added to in_raw, and also when the BPP is newly created. */ + IdempotentCallback ic_in_raw; + + /* ic_out_pq is entirely internal to the BPP itself; it's used as + * the callback on out_pq. */ + IdempotentCallback ic_out_pq; + + /* Information that all packet layers sharing this BPP will + * potentially be interested in. */ + int remote_bugs; + bool ext_info_rsa_sha256_ok, ext_info_rsa_sha512_ok; + + /* Set this if remote connection closure should not generate an + * error message (either because it's not to be treated as an + * error at all, or because some other error message has already + * been emitted). */ + bool expect_close; +}; + +static inline void ssh_bpp_handle_input(BinaryPacketProtocol *bpp) +{ bpp->vt->handle_input(bpp); } +static inline void ssh_bpp_handle_output(BinaryPacketProtocol *bpp) +{ bpp->vt->handle_output(bpp); } +static inline PktOut *ssh_bpp_new_pktout(BinaryPacketProtocol *bpp, int type) +{ return bpp->vt->new_pktout(type); } +static inline void ssh_bpp_queue_disconnect(BinaryPacketProtocol *bpp, + const char *msg, int category) +{ bpp->vt->queue_disconnect(bpp, msg, category); } + +/* ssh_bpp_free is more than just a macro wrapper on the vtable; it + * does centralised parts of the freeing too. */ +void ssh_bpp_free(BinaryPacketProtocol *bpp); + +BinaryPacketProtocol *ssh1_bpp_new(LogContext *logctx); +void ssh1_bpp_new_cipher(BinaryPacketProtocol *bpp, + const ssh_cipheralg *cipher, + const void *session_key); +/* This is only called from outside the BPP in server mode; in client + * mode the BPP detects compression start time automatically by + * snooping message types */ +void ssh1_bpp_start_compression(BinaryPacketProtocol *bpp); + +/* Helper routine which does common BPP initialisation, e.g. setting + * up in_pq and out_pq, and initialising input_consumer. */ +void ssh_bpp_common_setup(BinaryPacketProtocol *); + +/* Common helper functions between the SSH-2 full and bare BPPs */ +void ssh2_bpp_queue_disconnect(BinaryPacketProtocol *bpp, + const char *msg, int category); +bool ssh2_bpp_check_unimplemented(BinaryPacketProtocol *bpp, PktIn *pktin); + +/* Convenience macro for BPPs to send formatted strings to the Event + * Log. Assumes a function parameter called 'bpp' is in scope. */ +#define bpp_logevent(...) ( \ + logevent_and_free((bpp)->logctx, dupprintf(__VA_ARGS__))) + +/* + * Structure that tracks how much data is sent and received, for + * purposes of triggering an SSH-2 rekey when either one gets over a + * configured limit. In each direction, the flag 'running' indicates + * that we haven't hit the limit yet, and 'remaining' tracks how much + * longer until we do. The function dts_consume() subtracts a given + * amount from the counter in a particular direction, and sets + * 'expired' if the limit has been hit. + * + * The limit is sticky: once 'running' has flipped to false, + * 'remaining' is no longer decremented, so it shouldn't dangerously + * wrap round. + */ +struct DataTransferStatsDirection { + bool running, expired; + unsigned long remaining; +}; +struct DataTransferStats { + struct DataTransferStatsDirection in, out; +}; +static inline void dts_consume(struct DataTransferStatsDirection *s, + unsigned long size_consumed) +{ + if (s->running) { + if (s->remaining <= size_consumed) { + s->running = false; + s->expired = true; + } else { + s->remaining -= size_consumed; + } + } +} +static inline void dts_reset(struct DataTransferStatsDirection *s, + unsigned long starting_size) +{ + s->expired = false; + s->remaining = starting_size; + /* + * The semantics of setting CONF_ssh_rekey_data to zero are to + * disable data-volume based rekeying completely. So if the + * starting size is actually zero, we don't set 'running' to true + * in the first place, which means we won't ever set the expired + * flag. + */ + s->running = (starting_size != 0); +} + +BinaryPacketProtocol *ssh2_bpp_new( + LogContext *logctx, struct DataTransferStats *stats, bool is_server); +void ssh2_bpp_new_outgoing_crypto( + BinaryPacketProtocol *bpp, + const ssh_cipheralg *cipher, const void *ckey, const void *iv, + const ssh2_macalg *mac, bool etm_mode, const void *mac_key, + const ssh_compression_alg *compression, bool delayed_compression); +void ssh2_bpp_new_incoming_crypto( + BinaryPacketProtocol *bpp, + const ssh_cipheralg *cipher, const void *ckey, const void *iv, + const ssh2_macalg *mac, bool etm_mode, const void *mac_key, + const ssh_compression_alg *compression, bool delayed_compression); + +/* + * A query method specific to the interface between ssh2transport and + * ssh2bpp. If true, it indicates that we're potentially in the + * race-condition-prone part of delayed compression setup and so + * asynchronous outgoing transport-layer packets are currently not + * being sent, which means in particular that it would be a bad idea + * to start a rekey because then we'd stop responding to anything + * _other_ than transport-layer packets and deadlock the protocol. + */ +bool ssh2_bpp_rekey_inadvisable(BinaryPacketProtocol *bpp); + +BinaryPacketProtocol *ssh2_bare_bpp_new(LogContext *logctx); + +/* + * The initial code to handle the SSH version exchange is also + * structured as an implementation of BinaryPacketProtocol, because + * that makes it easy to switch from that to the next BPP once it + * tells us which one we're using. + */ +struct ssh_version_receiver { + void (*got_ssh_version)(struct ssh_version_receiver *rcv, + int major_version); +}; +BinaryPacketProtocol *ssh_verstring_new( + Conf *conf, LogContext *logctx, bool bare_connection_mode, + const char *protoversion, struct ssh_version_receiver *rcv, + bool server_mode, const char *impl_name); +const char *ssh_verstring_get_remote(BinaryPacketProtocol *); +const char *ssh_verstring_get_local(BinaryPacketProtocol *); +int ssh_verstring_get_bugs(BinaryPacketProtocol *); + +#endif /* PUTTY_SSHBPP_H */ diff --git a/ssh/bpp1.c b/ssh/bpp1.c new file mode 100644 index 00000000..f84d787b --- /dev/null +++ b/ssh/bpp1.c @@ -0,0 +1,387 @@ +/* + * Binary packet protocol for SSH-1. + */ + +#include + +#include "putty.h" +#include "ssh.h" +#include "bpp.h" +#include "sshcr.h" + +struct ssh1_bpp_state { + int crState; + long len, pad, biglen, length, maxlen; + unsigned char *data; + uint32_t realcrc, gotcrc; + int chunk; + PktIn *pktin; + + ssh_cipher *cipher_in, *cipher_out; + + struct crcda_ctx *crcda_ctx; + uint8_t iv[8]; /* for crcda */ + + bool pending_compression_request; + ssh_compressor *compctx; + ssh_decompressor *decompctx; + + BinaryPacketProtocol bpp; +}; + +static void ssh1_bpp_free(BinaryPacketProtocol *bpp); +static void ssh1_bpp_handle_input(BinaryPacketProtocol *bpp); +static void ssh1_bpp_handle_output(BinaryPacketProtocol *bpp); +static void ssh1_bpp_queue_disconnect(BinaryPacketProtocol *bpp, + const char *msg, int category); +static PktOut *ssh1_bpp_new_pktout(int type); + +static const BinaryPacketProtocolVtable ssh1_bpp_vtable = { + .free = ssh1_bpp_free, + .handle_input = ssh1_bpp_handle_input, + .handle_output = ssh1_bpp_handle_output, + .new_pktout = ssh1_bpp_new_pktout, + .queue_disconnect = ssh1_bpp_queue_disconnect, + .packet_size_limit = 0xFFFFFFFF, /* no special limit for this bpp */ +}; + +BinaryPacketProtocol *ssh1_bpp_new(LogContext *logctx) +{ + struct ssh1_bpp_state *s = snew(struct ssh1_bpp_state); + memset(s, 0, sizeof(*s)); + s->bpp.vt = &ssh1_bpp_vtable; + s->bpp.logctx = logctx; + ssh_bpp_common_setup(&s->bpp); + return &s->bpp; +} + +static void ssh1_bpp_free(BinaryPacketProtocol *bpp) +{ + struct ssh1_bpp_state *s = container_of(bpp, struct ssh1_bpp_state, bpp); + if (s->cipher_in) + ssh_cipher_free(s->cipher_in); + if (s->cipher_out) + ssh_cipher_free(s->cipher_out); + if (s->compctx) + ssh_compressor_free(s->compctx); + if (s->decompctx) + ssh_decompressor_free(s->decompctx); + if (s->crcda_ctx) + crcda_free_context(s->crcda_ctx); + sfree(s->pktin); + sfree(s); +} + +void ssh1_bpp_new_cipher(BinaryPacketProtocol *bpp, + const ssh_cipheralg *cipher, + const void *session_key) +{ + struct ssh1_bpp_state *s; + assert(bpp->vt == &ssh1_bpp_vtable); + s = container_of(bpp, struct ssh1_bpp_state, bpp); + + assert(!s->cipher_in); + assert(!s->cipher_out); + + if (cipher) { + s->cipher_in = ssh_cipher_new(cipher); + s->cipher_out = ssh_cipher_new(cipher); + ssh_cipher_setkey(s->cipher_in, session_key); + ssh_cipher_setkey(s->cipher_out, session_key); + + assert(!s->crcda_ctx); + s->crcda_ctx = crcda_make_context(); + + bpp_logevent("Initialised %s encryption", cipher->text_name); + + memset(s->iv, 0, sizeof(s->iv)); + + assert(cipher->blksize <= sizeof(s->iv)); + ssh_cipher_setiv(s->cipher_in, s->iv); + ssh_cipher_setiv(s->cipher_out, s->iv); + } +} + +void ssh1_bpp_start_compression(BinaryPacketProtocol *bpp) +{ + struct ssh1_bpp_state *s; + assert(bpp->vt == &ssh1_bpp_vtable); + s = container_of(bpp, struct ssh1_bpp_state, bpp); + + assert(!s->compctx); + assert(!s->decompctx); + + s->compctx = ssh_compressor_new(&ssh_zlib); + s->decompctx = ssh_decompressor_new(&ssh_zlib); + + bpp_logevent("Started zlib (RFC1950) compression"); +} + +#define BPP_READ(ptr, len) do \ + { \ + bool success; \ + crMaybeWaitUntilV((success = bufchain_try_fetch_consume( \ + s->bpp.in_raw, ptr, len)) || \ + s->bpp.input_eof); \ + if (!success) \ + goto eof; \ + ssh_check_frozen(s->bpp.ssh); \ + } while (0) + +static void ssh1_bpp_handle_input(BinaryPacketProtocol *bpp) +{ + struct ssh1_bpp_state *s = container_of(bpp, struct ssh1_bpp_state, bpp); + + crBegin(s->crState); + + while (1) { + s->maxlen = 0; + s->length = 0; + + { + unsigned char lenbuf[4]; + BPP_READ(lenbuf, 4); + s->len = toint(GET_32BIT_MSB_FIRST(lenbuf)); + } + + if (s->len < 5 || s->len > 262144) { /* SSH1.5-mandated max size */ + ssh_sw_abort(s->bpp.ssh, + "Out-of-range packet length from remote suggests" + " data stream corruption"); + crStopV; + } + + s->pad = 8 - (s->len % 8); + s->biglen = s->len + s->pad; + s->length = s->len - 5; + + /* + * Allocate the packet to return, now we know its length. + */ + s->pktin = snew_plus(PktIn, s->biglen); + s->pktin->qnode.prev = s->pktin->qnode.next = NULL; + s->pktin->qnode.on_free_queue = false; + s->pktin->type = 0; + + s->maxlen = s->biglen; + s->data = snew_plus_get_aux(s->pktin); + + BPP_READ(s->data, s->biglen); + + if (s->cipher_in && detect_attack(s->crcda_ctx, + s->data, s->biglen, s->iv)) { + ssh_sw_abort(s->bpp.ssh, + "Network attack (CRC compensation) detected!"); + crStopV; + } + /* Save the last cipher block, to be passed to the next call + * to detect_attack */ + assert(s->biglen >= 8); + memcpy(s->iv, s->data + s->biglen - 8, sizeof(s->iv)); + + if (s->cipher_in) + ssh_cipher_decrypt(s->cipher_in, s->data, s->biglen); + + s->realcrc = crc32_ssh1(make_ptrlen(s->data, s->biglen - 4)); + s->gotcrc = GET_32BIT_MSB_FIRST(s->data + s->biglen - 4); + if (s->gotcrc != s->realcrc) { + ssh_sw_abort(s->bpp.ssh, "Incorrect CRC received on packet"); + crStopV; + } + + if (s->decompctx) { + unsigned char *decompblk; + int decomplen; + if (!ssh_decompressor_decompress( + s->decompctx, s->data + s->pad, s->length + 1, + &decompblk, &decomplen)) { + ssh_sw_abort(s->bpp.ssh, + "Zlib decompression encountered invalid data"); + crStopV; + } + + if (s->maxlen < s->pad + decomplen) { + PktIn *old_pktin = s->pktin; + + s->maxlen = s->pad + decomplen; + s->pktin = snew_plus(PktIn, s->maxlen); + *s->pktin = *old_pktin; /* structure copy */ + s->data = snew_plus_get_aux(s->pktin); + + smemclr(old_pktin, s->biglen); + sfree(old_pktin); + } + + memcpy(s->data + s->pad, decompblk, decomplen); + sfree(decompblk); + s->length = decomplen - 1; + } + + /* + * Now we can find the bounds of the semantic content of the + * packet, and the initial type byte. + */ + s->data += s->pad; + s->pktin->type = *s->data++; + BinarySource_INIT(s->pktin, s->data, s->length); + + if (s->bpp.logctx) { + logblank_t blanks[MAX_BLANKS]; + int nblanks = ssh1_censor_packet( + s->bpp.pls, s->pktin->type, false, + make_ptrlen(s->data, s->length), blanks); + log_packet(s->bpp.logctx, PKT_INCOMING, s->pktin->type, + ssh1_pkt_type(s->pktin->type), + get_ptr(s->pktin), get_avail(s->pktin), nblanks, blanks, + NULL, 0, NULL); + } + + s->pktin->qnode.formal_size = get_avail(s->pktin); + pq_push(&s->bpp.in_pq, s->pktin); + + { + int type = s->pktin->type; + s->pktin = NULL; + + switch (type) { + case SSH1_SMSG_SUCCESS: + case SSH1_SMSG_FAILURE: + if (s->pending_compression_request) { + /* + * This is the response to + * SSH1_CMSG_REQUEST_COMPRESSION. + */ + if (type == SSH1_SMSG_SUCCESS) { + /* + * If the response was positive, start + * compression. + */ + ssh1_bpp_start_compression(&s->bpp); + } + + /* + * Either way, cancel the pending flag, and + * schedule a run of our output side in case we + * had any packets queued up in the meantime. + */ + s->pending_compression_request = false; + queue_idempotent_callback(&s->bpp.ic_out_pq); + } + break; + } + } + } + + eof: + if (!s->bpp.expect_close) { + ssh_remote_error(s->bpp.ssh, + "Remote side unexpectedly closed network connection"); + } else { + ssh_remote_eof(s->bpp.ssh, "Remote side closed network connection"); + } + return; /* avoid touching s now it's been freed */ + + crFinishV; +} + +static PktOut *ssh1_bpp_new_pktout(int pkt_type) +{ + PktOut *pkt = ssh_new_packet(); + pkt->length = 4 + 8; /* space for length + max padding */ + put_byte(pkt, pkt_type); + pkt->prefix = pkt->length; + pkt->type = pkt_type; + pkt->downstream_id = 0; + pkt->additional_log_text = NULL; + return pkt; +} + +static void ssh1_bpp_format_packet(struct ssh1_bpp_state *s, PktOut *pkt) +{ + int pad, biglen, pktoffs; + uint32_t crc; + int len; + + if (s->bpp.logctx) { + ptrlen pktdata = make_ptrlen(pkt->data + pkt->prefix, + pkt->length - pkt->prefix); + logblank_t blanks[MAX_BLANKS]; + int nblanks = ssh1_censor_packet( + s->bpp.pls, pkt->type, true, pktdata, blanks); + log_packet(s->bpp.logctx, PKT_OUTGOING, pkt->type, + ssh1_pkt_type(pkt->type), + pktdata.ptr, pktdata.len, nblanks, blanks, + NULL, 0, NULL); + } + + if (s->compctx) { + unsigned char *compblk; + int complen; + ssh_compressor_compress(s->compctx, pkt->data + 12, pkt->length - 12, + &compblk, &complen, 0); + /* Replace the uncompressed packet data with the compressed + * version. */ + pkt->length = 12; + put_data(pkt, compblk, complen); + sfree(compblk); + } + + put_uint32(pkt, 0); /* space for CRC */ + len = pkt->length - 4 - 8; /* len(type+data+CRC) */ + pad = 8 - (len % 8); + pktoffs = 8 - pad; + biglen = len + pad; /* len(padding+type+data+CRC) */ + + random_read(pkt->data + pktoffs, 4+8 - pktoffs); + crc = crc32_ssh1( + make_ptrlen(pkt->data + pktoffs + 4, biglen - 4)); /* all ex len */ + PUT_32BIT_MSB_FIRST(pkt->data + pktoffs + 4 + biglen - 4, crc); + PUT_32BIT_MSB_FIRST(pkt->data + pktoffs, len); + + if (s->cipher_out) + ssh_cipher_encrypt(s->cipher_out, pkt->data + pktoffs + 4, biglen); + + bufchain_add(s->bpp.out_raw, pkt->data + pktoffs, + biglen + 4); /* len(length+padding+type+data+CRC) */ +} + +static void ssh1_bpp_handle_output(BinaryPacketProtocol *bpp) +{ + struct ssh1_bpp_state *s = container_of(bpp, struct ssh1_bpp_state, bpp); + PktOut *pkt; + + if (s->pending_compression_request) { + /* + * Don't send any output packets while we're awaiting a + * response to SSH1_CMSG_REQUEST_COMPRESSION, because if they + * cross over in transit with the responding SSH1_CMSG_SUCCESS + * then the other end could decode them with the wrong + * compression settings. + */ + return; + } + + while ((pkt = pq_pop(&s->bpp.out_pq)) != NULL) { + int type = pkt->type; + ssh1_bpp_format_packet(s, pkt); + ssh_free_pktout(pkt); + + if (type == SSH1_CMSG_REQUEST_COMPRESSION) { + /* + * When we see the actual compression request go past, set + * the pending flag, and stop processing packets this + * time. + */ + s->pending_compression_request = true; + break; + } + } +} + +static void ssh1_bpp_queue_disconnect(BinaryPacketProtocol *bpp, + const char *msg, int category) +{ + PktOut *pkt = ssh_bpp_new_pktout(bpp, SSH1_MSG_DISCONNECT); + put_stringz(pkt, msg); + pq_push(&bpp->out_pq, pkt); +} diff --git a/ssh/bpp2.c b/ssh/bpp2.c new file mode 100644 index 00000000..f29b962f --- /dev/null +++ b/ssh/bpp2.c @@ -0,0 +1,982 @@ +/* + * Binary packet protocol for SSH-2. + */ + +#include + +#include "putty.h" +#include "ssh.h" +#include "bpp.h" +#include "sshcr.h" + +struct ssh2_bpp_direction { + unsigned long sequence; + ssh_cipher *cipher; + ssh2_mac *mac; + bool etm_mode; + const ssh_compression_alg *pending_compression; +}; + +struct ssh2_bpp_state { + int crState; + long len, pad, payload, packetlen, maclen, length, maxlen; + unsigned char *buf; + size_t bufsize; + unsigned char *data; + unsigned cipherblk; + PktIn *pktin; + struct DataTransferStats *stats; + bool cbc_ignore_workaround; + + struct ssh2_bpp_direction in, out; + /* comp and decomp logically belong in the per-direction + * substructure, except that they have different types */ + ssh_decompressor *in_decomp; + ssh_compressor *out_comp; + + bool is_server; + bool pending_newkeys; + bool pending_compression, seen_userauth_success; + bool enforce_next_packet_is_userauth_success; + unsigned nnewkeys; + int prev_type; + + BinaryPacketProtocol bpp; +}; + +static void ssh2_bpp_free(BinaryPacketProtocol *bpp); +static void ssh2_bpp_handle_input(BinaryPacketProtocol *bpp); +static void ssh2_bpp_handle_output(BinaryPacketProtocol *bpp); +static PktOut *ssh2_bpp_new_pktout(int type); + +static const BinaryPacketProtocolVtable ssh2_bpp_vtable = { + .free = ssh2_bpp_free, + .handle_input = ssh2_bpp_handle_input, + .handle_output = ssh2_bpp_handle_output, + .new_pktout = ssh2_bpp_new_pktout, + .queue_disconnect = ssh2_bpp_queue_disconnect, /* in common.c */ + .packet_size_limit = 0xFFFFFFFF, /* no special limit for this bpp */ +}; + +BinaryPacketProtocol *ssh2_bpp_new( + LogContext *logctx, struct DataTransferStats *stats, bool is_server) +{ + struct ssh2_bpp_state *s = snew(struct ssh2_bpp_state); + memset(s, 0, sizeof(*s)); + s->bpp.vt = &ssh2_bpp_vtable; + s->bpp.logctx = logctx; + s->stats = stats; + s->is_server = is_server; + ssh_bpp_common_setup(&s->bpp); + return &s->bpp; +} + +static void ssh2_bpp_free_outgoing_crypto(struct ssh2_bpp_state *s) +{ + /* + * We must free the MAC before the cipher, because sometimes the + * MAC is not actually separately allocated but just a different + * facet of the same object as the cipher, in which case + * ssh2_mac_free does nothing and ssh_cipher_free does the actual + * freeing. So if we freed the cipher first and then tried to + * dereference the MAC's vtable pointer to find out how to free + * that too, we'd be accessing freed memory. + */ + if (s->out.mac) + ssh2_mac_free(s->out.mac); + if (s->out.cipher) + ssh_cipher_free(s->out.cipher); + if (s->out_comp) + ssh_compressor_free(s->out_comp); +} + +static void ssh2_bpp_free_incoming_crypto(struct ssh2_bpp_state *s) +{ + /* As above, take care to free in.mac before in.cipher */ + if (s->in.mac) + ssh2_mac_free(s->in.mac); + if (s->in.cipher) + ssh_cipher_free(s->in.cipher); + if (s->in_decomp) + ssh_decompressor_free(s->in_decomp); +} + +static void ssh2_bpp_free(BinaryPacketProtocol *bpp) +{ + struct ssh2_bpp_state *s = container_of(bpp, struct ssh2_bpp_state, bpp); + sfree(s->buf); + ssh2_bpp_free_outgoing_crypto(s); + ssh2_bpp_free_incoming_crypto(s); + sfree(s->pktin); + sfree(s); +} + +void ssh2_bpp_new_outgoing_crypto( + BinaryPacketProtocol *bpp, + const ssh_cipheralg *cipher, const void *ckey, const void *iv, + const ssh2_macalg *mac, bool etm_mode, const void *mac_key, + const ssh_compression_alg *compression, bool delayed_compression) +{ + struct ssh2_bpp_state *s; + assert(bpp->vt == &ssh2_bpp_vtable); + s = container_of(bpp, struct ssh2_bpp_state, bpp); + + ssh2_bpp_free_outgoing_crypto(s); + + if (cipher) { + s->out.cipher = ssh_cipher_new(cipher); + ssh_cipher_setkey(s->out.cipher, ckey); + ssh_cipher_setiv(s->out.cipher, iv); + + s->cbc_ignore_workaround = ( + (ssh_cipher_alg(s->out.cipher)->flags & SSH_CIPHER_IS_CBC) && + !(s->bpp.remote_bugs & BUG_CHOKES_ON_SSH2_IGNORE)); + + bpp_logevent("Initialised %s outbound encryption", + ssh_cipher_alg(s->out.cipher)->text_name); + } else { + s->out.cipher = NULL; + s->cbc_ignore_workaround = false; + } + s->out.etm_mode = etm_mode; + if (mac) { + s->out.mac = ssh2_mac_new(mac, s->out.cipher); + ssh2_mac_setkey(s->out.mac, make_ptrlen(mac_key, mac->keylen)); + + bpp_logevent("Initialised %s outbound MAC algorithm%s%s", + ssh2_mac_text_name(s->out.mac), + etm_mode ? " (in ETM mode)" : "", + (s->out.cipher && + ssh_cipher_alg(s->out.cipher)->required_mac ? + " (required by cipher)" : "")); + } else { + s->out.mac = NULL; + } + + if (delayed_compression && !s->seen_userauth_success) { + s->out.pending_compression = compression; + s->out_comp = NULL; + + bpp_logevent("Will enable %s compression after user authentication", + s->out.pending_compression->text_name); + } else { + s->out.pending_compression = NULL; + + /* 'compression' is always non-NULL, because no compression is + * indicated by ssh_comp_none. But this setup call may return a + * null out_comp. */ + s->out_comp = ssh_compressor_new(compression); + + if (s->out_comp) + bpp_logevent("Initialised %s compression", + ssh_compressor_alg(s->out_comp)->text_name); + } +} + +void ssh2_bpp_new_incoming_crypto( + BinaryPacketProtocol *bpp, + const ssh_cipheralg *cipher, const void *ckey, const void *iv, + const ssh2_macalg *mac, bool etm_mode, const void *mac_key, + const ssh_compression_alg *compression, bool delayed_compression) +{ + struct ssh2_bpp_state *s; + assert(bpp->vt == &ssh2_bpp_vtable); + s = container_of(bpp, struct ssh2_bpp_state, bpp); + + ssh2_bpp_free_incoming_crypto(s); + + if (cipher) { + s->in.cipher = ssh_cipher_new(cipher); + ssh_cipher_setkey(s->in.cipher, ckey); + ssh_cipher_setiv(s->in.cipher, iv); + + bpp_logevent("Initialised %s inbound encryption", + ssh_cipher_alg(s->in.cipher)->text_name); + } else { + s->in.cipher = NULL; + } + s->in.etm_mode = etm_mode; + if (mac) { + s->in.mac = ssh2_mac_new(mac, s->in.cipher); + ssh2_mac_setkey(s->in.mac, make_ptrlen(mac_key, mac->keylen)); + + bpp_logevent("Initialised %s inbound MAC algorithm%s%s", + ssh2_mac_text_name(s->in.mac), + etm_mode ? " (in ETM mode)" : "", + (s->in.cipher && + ssh_cipher_alg(s->in.cipher)->required_mac ? + " (required by cipher)" : "")); + } else { + s->in.mac = NULL; + } + + if (delayed_compression && !s->seen_userauth_success) { + s->in.pending_compression = compression; + s->in_decomp = NULL; + + bpp_logevent("Will enable %s decompression after user authentication", + s->in.pending_compression->text_name); + } else { + s->in.pending_compression = NULL; + + /* 'compression' is always non-NULL, because no compression is + * indicated by ssh_comp_none. But this setup call may return a + * null in_decomp. */ + s->in_decomp = ssh_decompressor_new(compression); + + if (s->in_decomp) + bpp_logevent("Initialised %s decompression", + ssh_decompressor_alg(s->in_decomp)->text_name); + } + + /* Clear the pending_newkeys flag, so that handle_input below will + * start consuming the input data again. */ + s->pending_newkeys = false; + + /* And schedule a run of handle_input, in case there's already + * input data in the queue. */ + queue_idempotent_callback(&s->bpp.ic_in_raw); +} + +bool ssh2_bpp_rekey_inadvisable(BinaryPacketProtocol *bpp) +{ + struct ssh2_bpp_state *s; + assert(bpp->vt == &ssh2_bpp_vtable); + s = container_of(bpp, struct ssh2_bpp_state, bpp); + + return s->pending_compression; +} + +static void ssh2_bpp_enable_pending_compression(struct ssh2_bpp_state *s) +{ + BinaryPacketProtocol *bpp = &s->bpp; /* for bpp_logevent */ + + if (s->in.pending_compression) { + s->in_decomp = ssh_decompressor_new(s->in.pending_compression); + bpp_logevent("Initialised delayed %s decompression", + ssh_decompressor_alg(s->in_decomp)->text_name); + s->in.pending_compression = NULL; + } + if (s->out.pending_compression) { + s->out_comp = ssh_compressor_new(s->out.pending_compression); + bpp_logevent("Initialised delayed %s compression", + ssh_compressor_alg(s->out_comp)->text_name); + s->out.pending_compression = NULL; + } +} + +#define BPP_READ(ptr, len) do \ + { \ + bool success; \ + crMaybeWaitUntilV((success = bufchain_try_fetch_consume( \ + s->bpp.in_raw, ptr, len)) || \ + s->bpp.input_eof); \ + if (!success) \ + goto eof; \ + ssh_check_frozen(s->bpp.ssh); \ + } while (0) + +#define userauth_range(pkttype) ((unsigned)((pkttype) - 50) < 20) + +static void ssh2_bpp_handle_input(BinaryPacketProtocol *bpp) +{ + struct ssh2_bpp_state *s = container_of(bpp, struct ssh2_bpp_state, bpp); + + crBegin(s->crState); + + while (1) { + s->maxlen = 0; + s->length = 0; + if (s->in.cipher) + s->cipherblk = ssh_cipher_alg(s->in.cipher)->blksize; + else + s->cipherblk = 8; + if (s->cipherblk < 8) + s->cipherblk = 8; + s->maclen = s->in.mac ? ssh2_mac_alg(s->in.mac)->len : 0; + + if (s->in.cipher && + (ssh_cipher_alg(s->in.cipher)->flags & SSH_CIPHER_IS_CBC) && + s->in.mac && !s->in.etm_mode) { + /* + * 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. + * + * This defence is unnecessary in OpenSSH ETM mode, because + * the whole point of ETM mode is that the attacker can't + * tweak the ciphertext stream at all without the MAC + * detecting it before we decrypt anything. + */ + + /* + * Make sure we have buffer space for a maximum-size packet. + */ + unsigned buflimit = OUR_V2_PACKETLIMIT + s->maclen; + if (s->bufsize < buflimit) { + s->bufsize = buflimit; + s->buf = sresize(s->buf, s->bufsize, unsigned char); + } + + /* Read an amount corresponding to the MAC. */ + BPP_READ(s->buf, s->maclen); + + s->packetlen = 0; + ssh2_mac_start(s->in.mac); + put_uint32(s->in.mac, s->in.sequence); + + for (;;) { /* Once around this loop per cipher block. */ + /* Read another cipher-block's worth, and tack it on to + * the end. */ + BPP_READ(s->buf + (s->packetlen + s->maclen), s->cipherblk); + /* Decrypt one more block (a little further back in + * the stream). */ + ssh_cipher_decrypt(s->in.cipher, + s->buf + s->packetlen, s->cipherblk); + + /* Feed that block to the MAC. */ + put_data(s->in.mac, + s->buf + s->packetlen, s->cipherblk); + s->packetlen += s->cipherblk; + + /* See if that gives us a valid packet. */ + if (ssh2_mac_verresult(s->in.mac, s->buf + s->packetlen) && + ((s->len = toint(GET_32BIT_MSB_FIRST(s->buf))) == + s->packetlen-4)) + break; + if (s->packetlen >= (long)OUR_V2_PACKETLIMIT) { + ssh_sw_abort(s->bpp.ssh, + "No valid incoming packet found"); + crStopV; + } + } + s->maxlen = s->packetlen + s->maclen; + + /* + * Now transfer the data into an output packet. + */ + s->pktin = snew_plus(PktIn, s->maxlen); + s->pktin->qnode.prev = s->pktin->qnode.next = NULL; + s->pktin->type = 0; + s->pktin->qnode.on_free_queue = false; + s->data = snew_plus_get_aux(s->pktin); + memcpy(s->data, s->buf, s->maxlen); + } else if (s->in.mac && s->in.etm_mode) { + if (s->bufsize < 4) { + s->bufsize = 4; + s->buf = sresize(s->buf, s->bufsize, unsigned char); + } + + /* + * OpenSSH encrypt-then-MAC mode: the packet length is + * unencrypted, unless the cipher supports length encryption. + */ + BPP_READ(s->buf, 4); + + /* Cipher supports length decryption, so do it */ + if (s->in.cipher && (ssh_cipher_alg(s->in.cipher)->flags & + SSH_CIPHER_SEPARATE_LENGTH)) { + /* Keep the packet the same though, so the MAC passes */ + unsigned char len[4]; + memcpy(len, s->buf, 4); + ssh_cipher_decrypt_length( + s->in.cipher, len, 4, s->in.sequence); + s->len = toint(GET_32BIT_MSB_FIRST(len)); + } else { + s->len = toint(GET_32BIT_MSB_FIRST(s->buf)); + } + + /* + * _Completely_ silly lengths should be stomped on before they + * do us any more damage. + */ + if (s->len < 0 || s->len > (long)OUR_V2_PACKETLIMIT || + s->len % s->cipherblk != 0) { + ssh_sw_abort(s->bpp.ssh, + "Incoming packet length field was garbled"); + crStopV; + } + + /* + * So now we can work out the total packet length. + */ + s->packetlen = s->len + 4; + + /* + * Allocate the packet to return, now we know its length. + */ + s->pktin = snew_plus(PktIn, OUR_V2_PACKETLIMIT + s->maclen); + s->pktin->qnode.prev = s->pktin->qnode.next = NULL; + s->pktin->type = 0; + s->pktin->qnode.on_free_queue = false; + s->data = snew_plus_get_aux(s->pktin); + memcpy(s->data, s->buf, 4); + + /* + * Read the remainder of the packet. + */ + BPP_READ(s->data + 4, s->packetlen + s->maclen - 4); + + /* + * Check the MAC. + */ + if (s->in.mac && !ssh2_mac_verify( + s->in.mac, s->data, s->len + 4, s->in.sequence)) { + ssh_sw_abort(s->bpp.ssh, "Incorrect MAC received on packet"); + crStopV; + } + + /* Decrypt everything between the length field and the MAC. */ + if (s->in.cipher) + ssh_cipher_decrypt( + s->in.cipher, s->data + 4, s->packetlen - 4); + } else { + if (s->bufsize < s->cipherblk) { + s->bufsize = s->cipherblk; + s->buf = sresize(s->buf, s->bufsize, unsigned char); + } + + /* + * Acquire and decrypt the first block of the packet. This will + * contain the length and padding details. + */ + BPP_READ(s->buf, s->cipherblk); + + if (s->in.cipher) + ssh_cipher_decrypt(s->in.cipher, s->buf, s->cipherblk); + + /* + * Now get the length figure. + */ + s->len = toint(GET_32BIT_MSB_FIRST(s->buf)); + + /* + * _Completely_ silly lengths should be stomped on before they + * do us any more damage. + */ + if (s->len < 0 || s->len > (long)OUR_V2_PACKETLIMIT || + (s->len + 4) % s->cipherblk != 0) { + ssh_sw_abort(s->bpp.ssh, + "Incoming packet was garbled on decryption"); + crStopV; + } + + /* + * So now we can work out the total packet length. + */ + s->packetlen = s->len + 4; + + /* + * Allocate the packet to return, now we know its length. + */ + s->maxlen = s->packetlen + s->maclen; + s->pktin = snew_plus(PktIn, s->maxlen); + s->pktin->qnode.prev = s->pktin->qnode.next = NULL; + s->pktin->type = 0; + s->pktin->qnode.on_free_queue = false; + s->data = snew_plus_get_aux(s->pktin); + memcpy(s->data, s->buf, s->cipherblk); + + /* + * Read and decrypt the remainder of the packet. + */ + BPP_READ(s->data + s->cipherblk, + s->packetlen + s->maclen - s->cipherblk); + + /* Decrypt everything _except_ the MAC. */ + if (s->in.cipher) + ssh_cipher_decrypt( + s->in.cipher, + s->data + s->cipherblk, s->packetlen - s->cipherblk); + + /* + * Check the MAC. + */ + if (s->in.mac && !ssh2_mac_verify( + s->in.mac, s->data, s->len + 4, s->in.sequence)) { + ssh_sw_abort(s->bpp.ssh, "Incorrect MAC received on packet"); + crStopV; + } + } + /* Get and sanity-check the amount of random padding. */ + s->pad = s->data[4]; + if (s->pad < 4 || s->len - s->pad < 1) { + ssh_sw_abort(s->bpp.ssh, + "Invalid padding length on received packet"); + crStopV; + } + /* + * This enables us to deduce the payload length. + */ + s->payload = s->len - s->pad - 1; + + s->length = s->payload + 5; + + dts_consume(&s->stats->in, s->packetlen); + + s->pktin->sequence = s->in.sequence++; + + s->length = s->packetlen - s->pad; + assert(s->length >= 0); + + /* + * Decompress packet payload. + */ + { + unsigned char *newpayload; + int newlen; + if (s->in_decomp && ssh_decompressor_decompress( + s->in_decomp, s->data + 5, s->length - 5, + &newpayload, &newlen)) { + if (s->maxlen < newlen + 5) { + PktIn *old_pktin = s->pktin; + + s->maxlen = newlen + 5; + s->pktin = snew_plus(PktIn, s->maxlen); + *s->pktin = *old_pktin; /* structure copy */ + s->data = snew_plus_get_aux(s->pktin); + + smemclr(old_pktin, s->packetlen + s->maclen); + sfree(old_pktin); + } + s->length = 5 + newlen; + memcpy(s->data + 5, newpayload, newlen); + sfree(newpayload); + } + } + + /* + * Now we can identify the semantic content of the packet, + * and also the initial type byte. + */ + if (s->length <= 5) { /* == 5 we hope, but robustness */ + /* + * RFC 4253 doesn't explicitly say that completely empty + * packets with no type byte are forbidden. We handle them + * here by giving them a type code larger than 0xFF, which + * will be picked up at the next layer and trigger + * SSH_MSG_UNIMPLEMENTED. + */ + s->pktin->type = SSH_MSG_NO_TYPE_CODE; + s->data += 5; + s->length = 0; + } else { + s->pktin->type = s->data[5]; + s->data += 6; + s->length -= 6; + } + BinarySource_INIT(s->pktin, s->data, s->length); + + if (s->bpp.logctx) { + logblank_t blanks[MAX_BLANKS]; + int nblanks = ssh2_censor_packet( + s->bpp.pls, s->pktin->type, false, + make_ptrlen(s->data, s->length), blanks); + log_packet(s->bpp.logctx, PKT_INCOMING, s->pktin->type, + ssh2_pkt_type(s->bpp.pls->kctx, s->bpp.pls->actx, + s->pktin->type), + s->data, s->length, nblanks, blanks, + &s->pktin->sequence, 0, NULL); + } + + if (ssh2_bpp_check_unimplemented(&s->bpp, s->pktin)) { + sfree(s->pktin); + s->pktin = NULL; + continue; + } + + s->pktin->qnode.formal_size = get_avail(s->pktin); + pq_push(&s->bpp.in_pq, s->pktin); + + { + int type = s->pktin->type; + int prev_type = s->prev_type; + s->prev_type = type; + s->pktin = NULL; + + if (s->enforce_next_packet_is_userauth_success) { + /* See EXT_INFO handler below */ + if (type != SSH2_MSG_USERAUTH_SUCCESS) { + ssh_proto_error(s->bpp.ssh, + "Remote side sent SSH2_MSG_EXT_INFO " + "not either preceded by NEWKEYS or " + "followed by USERAUTH_SUCCESS"); + return; + } + s->enforce_next_packet_is_userauth_success = false; + } + + if (type == SSH2_MSG_NEWKEYS) { + if (s->nnewkeys < 2) + s->nnewkeys++; + /* + * Mild layer violation: in this situation we must + * suspend processing of the input byte stream until + * the transport layer has initialised the new keys by + * calling ssh2_bpp_new_incoming_crypto above. + */ + s->pending_newkeys = true; + crWaitUntilV(!s->pending_newkeys); + continue; + } + + if (type == SSH2_MSG_USERAUTH_SUCCESS && !s->is_server) { + /* + * Another one: if we were configured with OpenSSH's + * deferred compression which is triggered on receipt + * of USERAUTH_SUCCESS, then this is the moment to + * turn on compression. + */ + ssh2_bpp_enable_pending_compression(s); + + /* + * Whether or not we were doing delayed compression in + * _this_ set of crypto parameters, we should set a + * flag indicating that we're now authenticated, so + * that a delayed compression method enabled in any + * future rekey will be treated as un-delayed. + */ + s->seen_userauth_success = true; + } + + if (type == SSH2_MSG_EXT_INFO) { + /* + * And another: enforce that an incoming EXT_INFO is + * either the message immediately after the initial + * NEWKEYS, or (if we're the client) the one + * immediately before USERAUTH_SUCCESS. + */ + if (prev_type == SSH2_MSG_NEWKEYS && s->nnewkeys == 1) { + /* OK - this is right after the first NEWKEYS. */ + } else if (s->is_server) { + /* We're the server, so they're the client. + * Clients may not send EXT_INFO at _any_ other + * time. */ + ssh_proto_error(s->bpp.ssh, + "Remote side sent SSH2_MSG_EXT_INFO " + "that was not immediately after the " + "initial NEWKEYS"); + return; + } else if (s->nnewkeys > 0 && s->seen_userauth_success) { + /* We're the client, so they're the server. In + * that case they may also send EXT_INFO + * immediately before USERAUTH_SUCCESS. Error out + * immediately if this can't _possibly_ be that + * moment (because we haven't even seen NEWKEYS + * yet, or because we've already seen + * USERAUTH_SUCCESS). */ + ssh_proto_error(s->bpp.ssh, + "Remote side sent SSH2_MSG_EXT_INFO " + "after USERAUTH_SUCCESS"); + return; + } else { + /* This _could_ be OK, provided the next packet is + * USERAUTH_SUCCESS. Set a flag to remember to + * fault it if not. */ + s->enforce_next_packet_is_userauth_success = true; + } + } + + if (s->pending_compression && userauth_range(type)) { + /* + * Receiving any userauth message at all indicates + * that we're not about to turn on delayed compression + * - either because we just _have_ done, or because + * this message is a USERAUTH_FAILURE or some kind of + * intermediate 'please send more data' continuation + * message. Either way, we turn off the outgoing + * packet blockage for now, and release any queued + * output packets, so that we can make another attempt + * to authenticate. The next userauth packet we send + * will re-block the output direction. + */ + s->pending_compression = false; + queue_idempotent_callback(&s->bpp.ic_out_pq); + } + } + } + + eof: + /* + * We've seen EOF. But we might have pushed stuff on the outgoing + * packet queue first, and that stuff _might_ include a DISCONNECT + * message, in which case we'd like to use that as the diagnostic. + * So first wait for the queue to have been processed. + */ + crMaybeWaitUntilV(!pq_peek(&s->bpp.in_pq)); + if (!s->bpp.expect_close) { + ssh_remote_error(s->bpp.ssh, + "Remote side unexpectedly closed network connection"); + } else { + ssh_remote_eof(s->bpp.ssh, "Remote side closed network connection"); + } + return; /* avoid touching s now it's been freed */ + + crFinishV; +} + +static PktOut *ssh2_bpp_new_pktout(int pkt_type) +{ + PktOut *pkt = ssh_new_packet(); + pkt->length = 5; /* space for packet length + padding length */ + pkt->minlen = 0; + pkt->type = pkt_type; + put_byte(pkt, pkt_type); + pkt->prefix = pkt->length; + return pkt; +} + +static void ssh2_bpp_format_packet_inner(struct ssh2_bpp_state *s, PktOut *pkt) +{ + int origlen, cipherblk, maclen, padding, unencrypted_prefix, i; + + if (s->bpp.logctx) { + ptrlen pktdata = make_ptrlen(pkt->data + pkt->prefix, + pkt->length - pkt->prefix); + logblank_t blanks[MAX_BLANKS]; + int nblanks = ssh2_censor_packet( + s->bpp.pls, pkt->type, true, pktdata, blanks); + log_packet(s->bpp.logctx, PKT_OUTGOING, pkt->type, + ssh2_pkt_type(s->bpp.pls->kctx, s->bpp.pls->actx, + pkt->type), + pktdata.ptr, pktdata.len, nblanks, blanks, &s->out.sequence, + pkt->downstream_id, pkt->additional_log_text); + } + + cipherblk = s->out.cipher ? ssh_cipher_alg(s->out.cipher)->blksize : 8; + cipherblk = cipherblk < 8 ? 8 : cipherblk; /* or 8 if blksize < 8 */ + + if (s->out_comp) { + unsigned char *newpayload; + int minlen, newlen; + + /* + * Compress packet payload. + */ + minlen = pkt->minlen; + if (minlen) { + /* + * Work out how much compressed data we need (at least) to + * make the overall packet length come to pkt->minlen. + */ + if (s->out.mac) + minlen -= ssh2_mac_alg(s->out.mac)->len; + minlen -= 8; /* length field + min padding */ + } + + ssh_compressor_compress(s->out_comp, pkt->data + 5, pkt->length - 5, + &newpayload, &newlen, minlen); + pkt->length = 5; + put_data(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. + */ + padding = 4; + unencrypted_prefix = (s->out.mac && s->out.etm_mode) ? 4 : 0; + padding += + (cipherblk - (pkt->length - unencrypted_prefix + padding) % cipherblk) + % cipherblk; + assert(padding <= 255); + maclen = s->out.mac ? ssh2_mac_alg(s->out.mac)->len : 0; + origlen = pkt->length; + for (i = 0; i < padding; i++) + put_byte(pkt, 0); /* make space for random padding */ + random_read(pkt->data + origlen, padding); + pkt->data[4] = padding; + PUT_32BIT_MSB_FIRST(pkt->data, origlen + padding - 4); + + /* Encrypt length if the scheme requires it */ + if (s->out.cipher && + (ssh_cipher_alg(s->out.cipher)->flags & SSH_CIPHER_SEPARATE_LENGTH)) { + ssh_cipher_encrypt_length(s->out.cipher, pkt->data, 4, + s->out.sequence); + } + + put_padding(pkt, maclen, 0); + + if (s->out.mac && s->out.etm_mode) { + /* + * OpenSSH-defined encrypt-then-MAC protocol. + */ + if (s->out.cipher) + ssh_cipher_encrypt(s->out.cipher, + pkt->data + 4, origlen + padding - 4); + ssh2_mac_generate(s->out.mac, pkt->data, origlen + padding, + s->out.sequence); + } else { + /* + * SSH-2 standard protocol. + */ + if (s->out.mac) + ssh2_mac_generate(s->out.mac, pkt->data, origlen + padding, + s->out.sequence); + if (s->out.cipher) + ssh_cipher_encrypt(s->out.cipher, pkt->data, origlen + padding); + } + + s->out.sequence++; /* whether or not we MACed */ + + dts_consume(&s->stats->out, origlen + padding); +} + +static void ssh2_bpp_format_packet(struct ssh2_bpp_state *s, PktOut *pkt) +{ + if (pkt->minlen > 0 && !s->out_comp) { + /* + * If we've been told to pad the packet out to a given minimum + * length, but we're not compressing (and hence can't get the + * compression to do the padding by pointlessly opening and + * closing zlib blocks), then our other strategy is to precede + * this message with an SSH_MSG_IGNORE that makes it up to the + * right length. + * + * A third option in principle, and the most obviously + * sensible, would be to set the explicit padding field in the + * packet to more than its minimum value. Sadly, that turns + * out to break some servers (our institutional memory thinks + * Cisco in particular) and so we abandoned that idea shortly + * after trying it. + */ + + /* + * Calculate the length we expect the real packet to have. + */ + int block, length; + PktOut *ignore_pkt; + + block = s->out.cipher ? ssh_cipher_alg(s->out.cipher)->blksize : 0; + if (block < 8) + block = 8; + length = pkt->length; + length += 4; /* minimum 4 byte padding */ + length += block-1; + length -= (length % block); + if (s->out.mac) + length += ssh2_mac_alg(s->out.mac)->len; + + if (length < pkt->minlen) { + /* + * We need an ignore message. Calculate its length. + */ + length = pkt->minlen - length; + + /* + * And work backwards from that to the length of the + * contained string. + */ + if (s->out.mac) + length -= ssh2_mac_alg(s->out.mac)->len; + length -= 8; /* length field + min padding */ + length -= 5; /* type code + string length prefix */ + + if (length < 0) + length = 0; + + ignore_pkt = ssh2_bpp_new_pktout(SSH2_MSG_IGNORE); + put_uint32(ignore_pkt, length); + size_t origlen = ignore_pkt->length; + for (size_t i = 0; i < length; i++) + put_byte(ignore_pkt, 0); /* make space for random padding */ + random_read(ignore_pkt->data + origlen, length); + ssh2_bpp_format_packet_inner(s, ignore_pkt); + bufchain_add(s->bpp.out_raw, ignore_pkt->data, ignore_pkt->length); + ssh_free_pktout(ignore_pkt); + } + } + + ssh2_bpp_format_packet_inner(s, pkt); + bufchain_add(s->bpp.out_raw, pkt->data, pkt->length); +} + +static void ssh2_bpp_handle_output(BinaryPacketProtocol *bpp) +{ + struct ssh2_bpp_state *s = container_of(bpp, struct ssh2_bpp_state, bpp); + PktOut *pkt; + int n_userauth; + + /* + * Count the userauth packets in the queue. + */ + n_userauth = 0; + for (pkt = pq_first(&s->bpp.out_pq); pkt != NULL; + pkt = pq_next(&s->bpp.out_pq, pkt)) + if (userauth_range(pkt->type)) + n_userauth++; + + if (s->pending_compression && !n_userauth) { + /* + * We're currently blocked from sending any outgoing packets + * until the other end tells us whether we're going to have to + * enable compression or not. + * + * If our end has pushed a userauth packet on the queue, that + * must mean it knows that a USERAUTH_SUCCESS is not + * immediately forthcoming, so we unblock ourselves and send + * up to and including that packet. But in this if statement, + * there aren't any, so we're still blocked. + */ + return; + } + + if (s->cbc_ignore_workaround) { + /* + * When using a CBC-mode cipher in SSH-2, 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 inserting an + * SSH_MSG_IGNORE if the last cipher block of the previous + * packet has already been sent to the network (which we + * approximate conservatively by checking if it's vanished + * from out_raw). + */ + if (bufchain_size(s->bpp.out_raw) < + (ssh_cipher_alg(s->out.cipher)->blksize + + ssh2_mac_alg(s->out.mac)->len)) { + /* + * There's less data in out_raw than the MAC size plus the + * cipher block size, which means at least one byte of + * that cipher block must already have left. Add an + * IGNORE. + */ + pkt = ssh_bpp_new_pktout(&s->bpp, SSH2_MSG_IGNORE); + put_stringz(pkt, ""); + ssh2_bpp_format_packet(s, pkt); + } + } + + while ((pkt = pq_pop(&s->bpp.out_pq)) != NULL) { + int type = pkt->type; + + if (userauth_range(type)) + n_userauth--; + + ssh2_bpp_format_packet(s, pkt); + ssh_free_pktout(pkt); + + if (n_userauth == 0 && s->out.pending_compression && !s->is_server) { + /* + * This is the last userauth packet in the queue, so + * unless our side decides to send another one in future, + * we have to assume will potentially provoke + * USERAUTH_SUCCESS. Block (non-userauth) outgoing packets + * until we see the reply. + */ + s->pending_compression = true; + return; + } else if (type == SSH2_MSG_USERAUTH_SUCCESS && s->is_server) { + ssh2_bpp_enable_pending_compression(s); + } + } +} diff --git a/ssh/censor1.c b/ssh/censor1.c new file mode 100644 index 00000000..8dacd3a0 --- /dev/null +++ b/ssh/censor1.c @@ -0,0 +1,76 @@ +/* + * Packet-censoring code for SSH-1, used to identify sensitive fields + * like passwords so that the logging system can avoid writing them + * into log files. + */ + +#include + +#include "putty.h" +#include "ssh.h" + +int ssh1_censor_packet( + const PacketLogSettings *pls, int type, bool sender_is_client, + ptrlen pkt, logblank_t *blanks) +{ + int nblanks = 0; + ptrlen str; + BinarySource src[1]; + + BinarySource_BARE_INIT_PL(src, pkt); + + if (pls->omit_data && + (type == SSH1_SMSG_STDOUT_DATA || + type == SSH1_SMSG_STDERR_DATA || + type == SSH1_CMSG_STDIN_DATA || + type == SSH1_MSG_CHANNEL_DATA)) { + /* "Session data" packets - omit the data string. */ + if (type == SSH1_MSG_CHANNEL_DATA) + get_uint32(src); /* skip channel id */ + str = get_string(src); + if (!get_err(src)) { + assert(nblanks < MAX_BLANKS); + blanks[nblanks].offset = src->pos - str.len; + blanks[nblanks].type = PKTLOG_OMIT; + blanks[nblanks].len = str.len; + nblanks++; + } + } + + if (sender_is_client && pls->omit_passwords) { + if (type == SSH1_CMSG_AUTH_PASSWORD || + type == SSH1_CMSG_AUTH_TIS_RESPONSE || + type == SSH1_CMSG_AUTH_CCARD_RESPONSE) { + /* If this is a password or similar packet, blank the + * password(s). */ + assert(nblanks < MAX_BLANKS); + blanks[nblanks].offset = 0; + blanks[nblanks].len = pkt.len; + blanks[nblanks].type = PKTLOG_BLANK; + nblanks++; + } else if (type == SSH1_CMSG_X11_REQUEST_FORWARDING) { + /* + * If this is an X forwarding request packet, blank the + * fake auth data. + * + * Note that while we blank the X authentication data + * here, we don't take any special action to blank the + * start of an X11 channel, so using MIT-MAGIC-COOKIE-1 + * and actually opening an X connection without having + * session blanking enabled is likely to leak your cookie + * into the log. + */ + get_string(src); /* skip protocol name */ + str = get_string(src); + if (!get_err(src)) { + assert(nblanks < MAX_BLANKS); + blanks[nblanks].offset = src->pos - str.len; + blanks[nblanks].type = PKTLOG_BLANK; + blanks[nblanks].len = str.len; + nblanks++; + } + } + } + + return nblanks; +} diff --git a/ssh/censor2.c b/ssh/censor2.c new file mode 100644 index 00000000..31ad8149 --- /dev/null +++ b/ssh/censor2.c @@ -0,0 +1,107 @@ +/* + * Packet-censoring code for SSH-2, used to identify sensitive fields + * like passwords so that the logging system can avoid writing them + * into log files. + */ + +#include + +#include "putty.h" +#include "ssh.h" + +int ssh2_censor_packet( + const PacketLogSettings *pls, int type, bool sender_is_client, + ptrlen pkt, logblank_t *blanks) +{ + int nblanks = 0; + ptrlen str; + BinarySource src[1]; + + BinarySource_BARE_INIT_PL(src, pkt); + + if (pls->omit_data && + (type == SSH2_MSG_CHANNEL_DATA || + type == SSH2_MSG_CHANNEL_EXTENDED_DATA)) { + /* "Session data" packets - omit the data string. */ + get_uint32(src); /* skip channel id */ + if (type == SSH2_MSG_CHANNEL_EXTENDED_DATA) + get_uint32(src); /* skip extended data type */ + str = get_string(src); + if (!get_err(src)) { + assert(nblanks < MAX_BLANKS); + blanks[nblanks].offset = src->pos - str.len; + blanks[nblanks].type = PKTLOG_OMIT; + blanks[nblanks].len = str.len; + nblanks++; + } + } + + if (sender_is_client && pls->omit_passwords) { + if (type == SSH2_MSG_USERAUTH_REQUEST) { + /* If this is a password packet, blank the password(s). */ + get_string(src); /* username */ + get_string(src); /* service name */ + str = get_string(src); /* auth method */ + if (ptrlen_eq_string(str, "password")) { + get_bool(src); + /* Blank the password field. */ + str = get_string(src); + if (!get_err(src)) { + assert(nblanks < MAX_BLANKS); + blanks[nblanks].offset = src->pos - str.len; + blanks[nblanks].type = PKTLOG_BLANK; + blanks[nblanks].len = str.len; + nblanks++; + /* If there's another password field beyond it + * (change of password), blank that too. */ + str = get_string(src); + if (!get_err(src)) + blanks[nblanks-1].len = + src->pos - blanks[nblanks].offset; + } + } + } else if (pls->actx == SSH2_PKTCTX_KBDINTER && + type == SSH2_MSG_USERAUTH_INFO_RESPONSE) { + /* If this is a keyboard-interactive response packet, + * blank the responses. */ + get_uint32(src); + assert(nblanks < MAX_BLANKS); + blanks[nblanks].offset = src->pos; + blanks[nblanks].type = PKTLOG_BLANK; + do { + str = get_string(src); + } while (!get_err(src)); + blanks[nblanks].len = src->pos - blanks[nblanks].offset; + nblanks++; + } else if (type == SSH2_MSG_CHANNEL_REQUEST) { + /* + * If this is an X forwarding request packet, blank the + * fake auth data. + * + * Note that while we blank the X authentication data + * here, we don't take any special action to blank the + * start of an X11 channel, so using MIT-MAGIC-COOKIE-1 + * and actually opening an X connection without having + * session blanking enabled is likely to leak your cookie + * into the log. + */ + get_uint32(src); + str = get_string(src); + if (ptrlen_eq_string(str, "x11-req")) { + get_bool(src); + get_bool(src); + get_string(src); + str = get_string(src); + if (!get_err(src)) { + assert(nblanks < MAX_BLANKS); + blanks[nblanks].offset = src->pos - str.len; + blanks[nblanks].type = PKTLOG_BLANK; + blanks[nblanks].len = str.len; + nblanks++; + } + } + } + } + + return nblanks; +} diff --git a/ssh/channel.h b/ssh/channel.h new file mode 100644 index 00000000..14dfccb5 --- /dev/null +++ b/ssh/channel.h @@ -0,0 +1,316 @@ +/* + * Abstraction of the various ways to handle the local end of an SSH + * connection-layer channel. + */ + +#ifndef PUTTY_SSHCHAN_H +#define PUTTY_SSHCHAN_H + +typedef struct ChannelVtable ChannelVtable; + +struct ChannelVtable { + void (*free)(Channel *); + + /* Called for channel types that were created at the same time as + * we sent an outgoing CHANNEL_OPEN, when the confirmation comes + * back from the server indicating that the channel has been + * opened, or the failure message indicating that it hasn't, + * respectively. In the latter case, this must _not_ free the + * Channel structure - the client will call the free method + * separately. But it might do logging or other local cleanup. */ + void (*open_confirmation)(Channel *); + void (*open_failed)(Channel *, const char *error_text); + + size_t (*send)(Channel *, bool is_stderr, const void *buf, size_t len); + void (*send_eof)(Channel *); + void (*set_input_wanted)(Channel *, bool wanted); + + char *(*log_close_msg)(Channel *); + + bool (*want_close)(Channel *, bool sent_local_eof, bool rcvd_remote_eof); + + /* A method for every channel request we know of. All of these + * return true for success or false for failure. */ + bool (*rcvd_exit_status)(Channel *, int status); + bool (*rcvd_exit_signal)( + Channel *chan, ptrlen signame, bool core_dumped, ptrlen msg); + bool (*rcvd_exit_signal_numeric)( + Channel *chan, int signum, bool core_dumped, ptrlen msg); + bool (*run_shell)(Channel *chan); + bool (*run_command)(Channel *chan, ptrlen command); + bool (*run_subsystem)(Channel *chan, ptrlen subsys); + bool (*enable_x11_forwarding)( + Channel *chan, bool oneshot, ptrlen authproto, ptrlen authdata, + unsigned screen_number); + bool (*enable_agent_forwarding)(Channel *chan); + bool (*allocate_pty)( + Channel *chan, ptrlen termtype, unsigned width, unsigned height, + unsigned pixwidth, unsigned pixheight, struct ssh_ttymodes modes); + bool (*set_env)(Channel *chan, ptrlen var, ptrlen value); + bool (*send_break)(Channel *chan, unsigned length); + bool (*send_signal)(Channel *chan, ptrlen signame); + bool (*change_window_size)( + Channel *chan, unsigned width, unsigned height, + unsigned pixwidth, unsigned pixheight); + + /* A method for signalling success/failure responses to channel + * requests initiated from the SshChannel vtable with want_reply + * true. */ + void (*request_response)(Channel *, bool success); +}; + +struct Channel { + const struct ChannelVtable *vt; + unsigned initial_fixed_window_size; +}; + +static inline void chan_free(Channel *ch) +{ ch->vt->free(ch); } +static inline void chan_open_confirmation(Channel *ch) +{ ch->vt->open_confirmation(ch); } +static inline void chan_open_failed(Channel *ch, const char *err) +{ ch->vt->open_failed(ch, err); } +static inline size_t chan_send( + Channel *ch, bool err, const void *buf, size_t len) +{ return ch->vt->send(ch, err, buf, len); } +static inline void chan_send_eof(Channel *ch) +{ ch->vt->send_eof(ch); } +static inline void chan_set_input_wanted(Channel *ch, bool wanted) +{ ch->vt->set_input_wanted(ch, wanted); } +static inline char *chan_log_close_msg(Channel *ch) +{ return ch->vt->log_close_msg(ch); } +static inline bool chan_want_close(Channel *ch, bool leof, bool reof) +{ return ch->vt->want_close(ch, leof, reof); } +static inline bool chan_rcvd_exit_status(Channel *ch, int status) +{ return ch->vt->rcvd_exit_status(ch, status); } +static inline bool chan_rcvd_exit_signal( + Channel *ch, ptrlen sig, bool core, ptrlen msg) +{ return ch->vt->rcvd_exit_signal(ch, sig, core, msg); } +static inline bool chan_rcvd_exit_signal_numeric( + Channel *ch, int sig, bool core, ptrlen msg) +{ return ch->vt->rcvd_exit_signal_numeric(ch, sig, core, msg); } +static inline bool chan_run_shell(Channel *ch) +{ return ch->vt->run_shell(ch); } +static inline bool chan_run_command(Channel *ch, ptrlen cmd) +{ return ch->vt->run_command(ch, cmd); } +static inline bool chan_run_subsystem(Channel *ch, ptrlen subsys) +{ return ch->vt->run_subsystem(ch, subsys); } +static inline bool chan_enable_x11_forwarding( + Channel *ch, bool oneshot, ptrlen ap, ptrlen ad, unsigned scr) +{ return ch->vt->enable_x11_forwarding(ch, oneshot, ap, ad, scr); } +static inline bool chan_enable_agent_forwarding(Channel *ch) +{ return ch->vt->enable_agent_forwarding(ch); } +static inline bool chan_allocate_pty( + Channel *ch, ptrlen termtype, unsigned w, unsigned h, + unsigned pw, unsigned ph, struct ssh_ttymodes modes) +{ return ch->vt->allocate_pty(ch, termtype, w, h, pw, ph, modes); } +static inline bool chan_set_env(Channel *ch, ptrlen var, ptrlen value) +{ return ch->vt->set_env(ch, var, value); } +static inline bool chan_send_break(Channel *ch, unsigned length) +{ return ch->vt->send_break(ch, length); } +static inline bool chan_send_signal(Channel *ch, ptrlen signame) +{ return ch->vt->send_signal(ch, signame); } +static inline bool chan_change_window_size( + Channel *ch, unsigned w, unsigned h, unsigned pw, unsigned ph) +{ return ch->vt->change_window_size(ch, w, h, pw, ph); } +static inline void chan_request_response(Channel *ch, bool success) +{ ch->vt->request_response(ch, success); } + +/* + * Reusable methods you can put in vtables to give default handling of + * some of those functions. + */ + +/* open_confirmation / open_failed for any channel it doesn't apply to */ +void chan_remotely_opened_confirmation(Channel *chan); +void chan_remotely_opened_failure(Channel *chan, const char *errtext); + +/* want_close for any channel that wants the default behaviour of not + * closing until both directions have had an EOF */ +bool chan_default_want_close(Channel *, bool, bool); + +/* default implementations that refuse all the channel requests */ +bool chan_no_exit_status(Channel *, int); +bool chan_no_exit_signal(Channel *, ptrlen, bool, ptrlen); +bool chan_no_exit_signal_numeric(Channel *, int, bool, ptrlen); +bool chan_no_run_shell(Channel *chan); +bool chan_no_run_command(Channel *chan, ptrlen command); +bool chan_no_run_subsystem(Channel *chan, ptrlen subsys); +bool chan_no_enable_x11_forwarding( + Channel *chan, bool oneshot, ptrlen authproto, ptrlen authdata, + unsigned screen_number); +bool chan_no_enable_agent_forwarding(Channel *chan); +bool chan_no_allocate_pty( + Channel *chan, ptrlen termtype, unsigned width, unsigned height, + unsigned pixwidth, unsigned pixheight, struct ssh_ttymodes modes); +bool chan_no_set_env(Channel *chan, ptrlen var, ptrlen value); +bool chan_no_send_break(Channel *chan, unsigned length); +bool chan_no_send_signal(Channel *chan, ptrlen signame); +bool chan_no_change_window_size( + Channel *chan, unsigned width, unsigned height, + unsigned pixwidth, unsigned pixheight); + +/* default implementation that never expects to receive a response */ +void chan_no_request_response(Channel *, bool); + +/* + * Constructor for a trivial do-nothing implementation of + * ChannelVtable. Used for 'zombie' channels, i.e. channels whose + * proper local source of data has been shut down or otherwise stopped + * existing, but the SSH side is still there and needs some kind of a + * Channel implementation to talk to. In particular, the want_close + * method for this channel always returns 'yes, please close this + * channel asap', regardless of whether local and/or remote EOF have + * been sent - indeed, even if _neither_ has. + */ +Channel *zombiechan_new(void); + +/* ---------------------------------------------------------------------- + * This structure is owned by an SSH connection layer, and identifies + * the connection layer's end of the channel, for the Channel + * implementation to talk back to. + */ + +typedef struct SshChannelVtable SshChannelVtable; + +struct SshChannelVtable { + size_t (*write)(SshChannel *c, bool is_stderr, const void *, size_t); + void (*write_eof)(SshChannel *c); + void (*initiate_close)(SshChannel *c, const char *err); + void (*unthrottle)(SshChannel *c, size_t bufsize); + Conf *(*get_conf)(SshChannel *c); + void (*window_override_removed)(SshChannel *c); + void (*x11_sharing_handover)(SshChannel *c, + ssh_sharing_connstate *share_cs, + share_channel *share_chan, + const char *peer_addr, int peer_port, + int endian, int protomajor, int protominor, + const void *initial_data, int initial_len); + + /* + * All the outgoing channel requests we support. Each one has a + * want_reply flag, which will cause a callback to + * chan_request_response when the result is available. + * + * The ones that return 'bool' use it to indicate that the SSH + * protocol in use doesn't support this request at all. + * + * (It's also intentional that not all of them have a want_reply + * flag: the ones that don't are because SSH-1 has no method for + * signalling success or failure of that request, or because we + * wouldn't do anything usefully different with the reply in any + * case.) + */ + void (*send_exit_status)(SshChannel *c, int status); + void (*send_exit_signal)( + SshChannel *c, ptrlen signame, bool core_dumped, ptrlen msg); + void (*send_exit_signal_numeric)( + SshChannel *c, int signum, bool core_dumped, ptrlen msg); + void (*request_x11_forwarding)( + SshChannel *c, bool want_reply, const char *authproto, + const char *authdata, int screen_number, bool oneshot); + void (*request_agent_forwarding)( + SshChannel *c, bool want_reply); + void (*request_pty)( + SshChannel *c, bool want_reply, Conf *conf, int w, int h); + bool (*send_env_var)( + SshChannel *c, bool want_reply, const char *var, const char *value); + void (*start_shell)( + SshChannel *c, bool want_reply); + void (*start_command)( + SshChannel *c, bool want_reply, const char *command); + bool (*start_subsystem)( + SshChannel *c, bool want_reply, const char *subsystem); + bool (*send_serial_break)( + SshChannel *c, bool want_reply, int length); /* length=0 for default */ + bool (*send_signal)( + SshChannel *c, bool want_reply, const char *signame); + void (*send_terminal_size_change)( + SshChannel *c, int w, int h); + void (*hint_channel_is_simple)(SshChannel *c); +}; + +struct SshChannel { + const struct SshChannelVtable *vt; + ConnectionLayer *cl; +}; + +static inline size_t sshfwd_write_ext( + SshChannel *c, bool is_stderr, const void *data, size_t len) +{ return c->vt->write(c, is_stderr, data, len); } +static inline size_t sshfwd_write(SshChannel *c, const void *data, size_t len) +{ return sshfwd_write_ext(c, false, data, len); } +static inline void sshfwd_write_eof(SshChannel *c) +{ c->vt->write_eof(c); } +static inline void sshfwd_initiate_close(SshChannel *c, const char *err) +{ c->vt->initiate_close(c, err); } +static inline void sshfwd_unthrottle(SshChannel *c, size_t bufsize) +{ c->vt->unthrottle(c, bufsize); } +static inline Conf *sshfwd_get_conf(SshChannel *c) +{ return c->vt->get_conf(c); } +static inline void sshfwd_window_override_removed(SshChannel *c) +{ c->vt->window_override_removed(c); } +static inline void sshfwd_x11_sharing_handover( + SshChannel *c, ssh_sharing_connstate *cs, share_channel *sch, + const char *addr, int port, int endian, int maj, int min, + const void *idata, int ilen) +{ c->vt->x11_sharing_handover(c, cs, sch, addr, port, endian, + maj, min, idata, ilen); } +static inline void sshfwd_send_exit_status(SshChannel *c, int status) +{ c->vt->send_exit_status(c, status); } +static inline void sshfwd_send_exit_signal( + SshChannel *c, ptrlen signame, bool core_dumped, ptrlen msg) +{ c->vt->send_exit_signal(c, signame, core_dumped, msg); } +static inline void sshfwd_send_exit_signal_numeric( + SshChannel *c, int signum, bool core_dumped, ptrlen msg) +{ c->vt->send_exit_signal_numeric(c, signum, core_dumped, msg); } +static inline void sshfwd_request_x11_forwarding( + SshChannel *c, bool want_reply, const char *proto, + const char *data, int scr, bool once) +{ c->vt->request_x11_forwarding(c, want_reply, proto, data, scr, once); } +static inline void sshfwd_request_agent_forwarding( + SshChannel *c, bool want_reply) +{ c->vt->request_agent_forwarding(c, want_reply); } +static inline void sshfwd_request_pty( + SshChannel *c, bool want_reply, Conf *conf, int w, int h) +{ c->vt->request_pty(c, want_reply, conf, w, h); } +static inline bool sshfwd_send_env_var( + SshChannel *c, bool want_reply, const char *var, const char *value) +{ return c->vt->send_env_var(c, want_reply, var, value); } +static inline void sshfwd_start_shell( + SshChannel *c, bool want_reply) +{ c->vt->start_shell(c, want_reply); } +static inline void sshfwd_start_command( + SshChannel *c, bool want_reply, const char *command) +{ c->vt->start_command(c, want_reply, command); } +static inline bool sshfwd_start_subsystem( + SshChannel *c, bool want_reply, const char *subsystem) +{ return c->vt->start_subsystem(c, want_reply, subsystem); } +static inline bool sshfwd_send_serial_break( + SshChannel *c, bool want_reply, int length) +{ return c->vt->send_serial_break(c, want_reply, length); } +static inline bool sshfwd_send_signal( + SshChannel *c, bool want_reply, const char *signame) +{ return c->vt->send_signal(c, want_reply, signame); } +static inline void sshfwd_send_terminal_size_change( + SshChannel *c, int w, int h) +{ c->vt->send_terminal_size_change(c, w, h); } +static inline void sshfwd_hint_channel_is_simple(SshChannel *c) +{ c->vt->hint_channel_is_simple(c); } + +/* ---------------------------------------------------------------------- + * The 'main' or primary channel of the SSH connection is special, + * because it's the one that's connected directly to parts of the + * frontend such as the terminal and the specials menu. So it exposes + * a richer API. + */ + +mainchan *mainchan_new( + PacketProtocolLayer *ppl, ConnectionLayer *cl, Conf *conf, + int term_width, int term_height, bool is_simple, SshChannel **sc_out); +void mainchan_get_specials( + mainchan *mc, add_special_fn_t add_special, void *ctx); +void mainchan_special_cmd(mainchan *mc, SessionSpecialCode code, int arg); +void mainchan_terminal_size(mainchan *mc, int width, int height); + +#endif /* PUTTY_SSHCHAN_H */ diff --git a/ssh/common.c b/ssh/common.c new file mode 100644 index 00000000..96e07cf1 --- /dev/null +++ b/ssh/common.c @@ -0,0 +1,946 @@ +/* + * Supporting routines used in common by all the various components of + * the SSH system. + */ + +#include +#include + +#include "putty.h" +#include "mpint.h" +#include "ssh.h" +#include "bpp.h" +#include "ppl.h" +#include "channel.h" + +/* ---------------------------------------------------------------------- + * Implementation of PacketQueue. + */ + +static void pq_ensure_unlinked(PacketQueueNode *node) +{ + if (node->on_free_queue) { + node->next->prev = node->prev; + node->prev->next = node->next; + } else { + assert(!node->next); + assert(!node->prev); + } +} + +void pq_base_push(PacketQueueBase *pqb, PacketQueueNode *node) +{ + pq_ensure_unlinked(node); + node->next = &pqb->end; + node->prev = pqb->end.prev; + node->next->prev = node; + node->prev->next = node; + pqb->total_size += node->formal_size; + + if (pqb->ic) + queue_idempotent_callback(pqb->ic); +} + +void pq_base_push_front(PacketQueueBase *pqb, PacketQueueNode *node) +{ + pq_ensure_unlinked(node); + node->prev = &pqb->end; + node->next = pqb->end.next; + node->next->prev = node; + node->prev->next = node; + pqb->total_size += node->formal_size; + + if (pqb->ic) + queue_idempotent_callback(pqb->ic); +} + +static PacketQueueNode pktin_freeq_head = { + &pktin_freeq_head, &pktin_freeq_head, true +}; + +static void pktin_free_queue_callback(void *vctx) +{ + while (pktin_freeq_head.next != &pktin_freeq_head) { + PacketQueueNode *node = pktin_freeq_head.next; + PktIn *pktin = container_of(node, PktIn, qnode); + pktin_freeq_head.next = node->next; + sfree(pktin); + } + + pktin_freeq_head.prev = &pktin_freeq_head; +} + +static IdempotentCallback ic_pktin_free = { + pktin_free_queue_callback, NULL, false +}; + +static inline void pq_unlink_common(PacketQueueBase *pqb, + PacketQueueNode *node) +{ + node->next->prev = node->prev; + node->prev->next = node->next; + + /* Check total_size doesn't drift out of sync downwards, by + * ensuring it doesn't underflow when we do this subtraction */ + assert(pqb->total_size >= node->formal_size); + pqb->total_size -= node->formal_size; + + /* Check total_size doesn't drift out of sync upwards, by checking + * that it's returned to exactly zero whenever a queue is + * emptied */ + assert(pqb->end.next != &pqb->end || pqb->total_size == 0); +} + +static PktIn *pq_in_after(PacketQueueBase *pqb, + PacketQueueNode *prev, bool pop) +{ + PacketQueueNode *node = prev->next; + if (node == &pqb->end) + return NULL; + + if (pop) { + pq_unlink_common(pqb, node); + + node->prev = pktin_freeq_head.prev; + node->next = &pktin_freeq_head; + node->next->prev = node; + node->prev->next = node; + node->on_free_queue = true; + + queue_idempotent_callback(&ic_pktin_free); + } + + return container_of(node, PktIn, qnode); +} + +static PktOut *pq_out_after(PacketQueueBase *pqb, + PacketQueueNode *prev, bool pop) +{ + PacketQueueNode *node = prev->next; + if (node == &pqb->end) + return NULL; + + if (pop) { + pq_unlink_common(pqb, node); + + node->prev = node->next = NULL; + } + + return container_of(node, PktOut, qnode); +} + +void pq_in_init(PktInQueue *pq) +{ + pq->pqb.ic = NULL; + pq->pqb.end.next = pq->pqb.end.prev = &pq->pqb.end; + pq->after = pq_in_after; + pq->pqb.total_size = 0; +} + +void pq_out_init(PktOutQueue *pq) +{ + pq->pqb.ic = NULL; + pq->pqb.end.next = pq->pqb.end.prev = &pq->pqb.end; + pq->after = pq_out_after; + pq->pqb.total_size = 0; +} + +void pq_in_clear(PktInQueue *pq) +{ + PktIn *pkt; + pq->pqb.ic = NULL; + while ((pkt = pq_pop(pq)) != NULL) { + /* No need to actually free these packets: pq_pop on a + * PktInQueue will automatically move them to the free + * queue. */ + } +} + +void pq_out_clear(PktOutQueue *pq) +{ + PktOut *pkt; + pq->pqb.ic = NULL; + while ((pkt = pq_pop(pq)) != NULL) + ssh_free_pktout(pkt); +} + +/* + * Concatenate the contents of the two queues q1 and q2, and leave the + * result in qdest. qdest must be either empty, or one of the input + * queues. + */ +void pq_base_concatenate(PacketQueueBase *qdest, + PacketQueueBase *q1, PacketQueueBase *q2) +{ + struct PacketQueueNode *head1, *tail1, *head2, *tail2; + + size_t total_size = q1->total_size + q2->total_size; + + /* + * Extract the contents from both input queues, and empty them. + */ + + head1 = (q1->end.next == &q1->end ? NULL : q1->end.next); + tail1 = (q1->end.prev == &q1->end ? NULL : q1->end.prev); + head2 = (q2->end.next == &q2->end ? NULL : q2->end.next); + tail2 = (q2->end.prev == &q2->end ? NULL : q2->end.prev); + + q1->end.next = q1->end.prev = &q1->end; + q2->end.next = q2->end.prev = &q2->end; + q1->total_size = q2->total_size = 0; + + /* + * Link the two lists together, handling the case where one or + * both is empty. + */ + + if (tail1) + tail1->next = head2; + else + head1 = head2; + + if (head2) + head2->prev = tail1; + else + tail2 = tail1; + + /* + * Check the destination queue is currently empty. (If it was one + * of the input queues, then it will be, because we emptied both + * of those just a moment ago.) + */ + + assert(qdest->end.next == &qdest->end); + assert(qdest->end.prev == &qdest->end); + + /* + * If our concatenated list has anything in it, then put it in + * dest. + */ + + if (!head1) { + assert(!tail2); + } else { + assert(tail2); + qdest->end.next = head1; + qdest->end.prev = tail2; + head1->prev = &qdest->end; + tail2->next = &qdest->end; + + if (qdest->ic) + queue_idempotent_callback(qdest->ic); + } + + qdest->total_size = total_size; +} + +/* ---------------------------------------------------------------------- + * Low-level functions for the packet structures themselves. + */ + +static void ssh_pkt_BinarySink_write(BinarySink *bs, + const void *data, size_t len); +PktOut *ssh_new_packet(void) +{ + PktOut *pkt = snew(PktOut); + + BinarySink_INIT(pkt, ssh_pkt_BinarySink_write); + pkt->data = NULL; + pkt->length = 0; + pkt->maxlen = 0; + pkt->downstream_id = 0; + pkt->additional_log_text = NULL; + pkt->qnode.next = pkt->qnode.prev = NULL; + pkt->qnode.on_free_queue = false; + + return pkt; +} + +static void ssh_pkt_adddata(PktOut *pkt, const void *data, int len) +{ + sgrowarrayn_nm(pkt->data, pkt->maxlen, pkt->length, len); + memcpy(pkt->data + pkt->length, data, len); + pkt->length += len; + pkt->qnode.formal_size = pkt->length; +} + +static void ssh_pkt_BinarySink_write(BinarySink *bs, + const void *data, size_t len) +{ + PktOut *pkt = BinarySink_DOWNCAST(bs, PktOut); + ssh_pkt_adddata(pkt, data, len); +} + +void ssh_free_pktout(PktOut *pkt) +{ + sfree(pkt->data); + sfree(pkt); +} + +/* ---------------------------------------------------------------------- + * Implement zombiechan_new() and its trivial vtable. + */ + +static void zombiechan_free(Channel *chan); +static size_t zombiechan_send( + Channel *chan, bool is_stderr, const void *, size_t); +static void zombiechan_set_input_wanted(Channel *chan, bool wanted); +static void zombiechan_do_nothing(Channel *chan); +static void zombiechan_open_failure(Channel *chan, const char *); +static bool zombiechan_want_close(Channel *chan, bool sent_eof, bool rcvd_eof); +static char *zombiechan_log_close_msg(Channel *chan) { return NULL; } + +static const ChannelVtable zombiechan_channelvt = { + .free = zombiechan_free, + .open_confirmation = zombiechan_do_nothing, + .open_failed = zombiechan_open_failure, + .send = zombiechan_send, + .send_eof = zombiechan_do_nothing, + .set_input_wanted = zombiechan_set_input_wanted, + .log_close_msg = zombiechan_log_close_msg, + .want_close = zombiechan_want_close, + .rcvd_exit_status = chan_no_exit_status, + .rcvd_exit_signal = chan_no_exit_signal, + .rcvd_exit_signal_numeric = chan_no_exit_signal_numeric, + .run_shell = chan_no_run_shell, + .run_command = chan_no_run_command, + .run_subsystem = chan_no_run_subsystem, + .enable_x11_forwarding = chan_no_enable_x11_forwarding, + .enable_agent_forwarding = chan_no_enable_agent_forwarding, + .allocate_pty = chan_no_allocate_pty, + .set_env = chan_no_set_env, + .send_break = chan_no_send_break, + .send_signal = chan_no_send_signal, + .change_window_size = chan_no_change_window_size, + .request_response = chan_no_request_response, +}; + +Channel *zombiechan_new(void) +{ + Channel *chan = snew(Channel); + chan->vt = &zombiechan_channelvt; + chan->initial_fixed_window_size = 0; + return chan; +} + +static void zombiechan_free(Channel *chan) +{ + assert(chan->vt == &zombiechan_channelvt); + sfree(chan); +} + +static void zombiechan_do_nothing(Channel *chan) +{ + assert(chan->vt == &zombiechan_channelvt); +} + +static void zombiechan_open_failure(Channel *chan, const char *errtext) +{ + assert(chan->vt == &zombiechan_channelvt); +} + +static size_t zombiechan_send(Channel *chan, bool is_stderr, + const void *data, size_t length) +{ + assert(chan->vt == &zombiechan_channelvt); + return 0; +} + +static void zombiechan_set_input_wanted(Channel *chan, bool enable) +{ + assert(chan->vt == &zombiechan_channelvt); +} + +static bool zombiechan_want_close(Channel *chan, bool sent_eof, bool rcvd_eof) +{ + return true; +} + +/* ---------------------------------------------------------------------- + * Common routines for handling SSH tty modes. + */ + +static unsigned real_ttymode_opcode(unsigned our_opcode, int ssh_version) +{ + switch (our_opcode) { + case TTYMODE_ISPEED: + return ssh_version == 1 ? TTYMODE_ISPEED_SSH1 : TTYMODE_ISPEED_SSH2; + case TTYMODE_OSPEED: + return ssh_version == 1 ? TTYMODE_OSPEED_SSH1 : TTYMODE_OSPEED_SSH2; + default: + return our_opcode; + } +} + +static unsigned our_ttymode_opcode(unsigned real_opcode, int ssh_version) +{ + if (ssh_version == 1) { + switch (real_opcode) { + case TTYMODE_ISPEED_SSH1: + return TTYMODE_ISPEED; + case TTYMODE_OSPEED_SSH1: + return TTYMODE_OSPEED; + default: + return real_opcode; + } + } else { + switch (real_opcode) { + case TTYMODE_ISPEED_SSH2: + return TTYMODE_ISPEED; + case TTYMODE_OSPEED_SSH2: + return TTYMODE_OSPEED; + default: + return real_opcode; + } + } +} + +struct ssh_ttymodes get_ttymodes_from_conf(Seat *seat, Conf *conf) +{ + struct ssh_ttymodes modes; + size_t i; + + static const struct mode_name_type { + const char *mode; + int opcode; + enum { TYPE_CHAR, TYPE_BOOL } type; + } modes_names_types[] = { + #define TTYMODE_CHAR(name, val, index) { #name, val, TYPE_CHAR }, + #define TTYMODE_FLAG(name, val, field, mask) { #name, val, TYPE_BOOL }, + #include "ttymode-list.h" + #undef TTYMODE_CHAR + #undef TTYMODE_FLAG + }; + + memset(&modes, 0, sizeof(modes)); + + for (i = 0; i < lenof(modes_names_types); i++) { + const struct mode_name_type *mode = &modes_names_types[i]; + const char *sval = conf_get_str_str(conf, CONF_ttymodes, mode->mode); + char *to_free = NULL; + + if (!sval) + sval = "N"; /* just in case */ + + /* + * sval[0] can be + * - 'V', indicating that an explicit value follows it; + * - 'A', indicating that we should pass the value through from + * the local environment via get_ttymode; or + * - 'N', indicating that we should explicitly not send this + * mode. + */ + if (sval[0] == 'A') { + sval = to_free = seat_get_ttymode(seat, mode->mode); + } else if (sval[0] == 'V') { + sval++; /* skip the 'V' */ + } else { + /* else 'N', or something from the future we don't understand */ + continue; + } + + if (sval) { + /* + * Parse the string representation of the tty mode + * into the integer value it will take on the wire. + */ + unsigned ival = 0; + + switch (mode->type) { + case TYPE_CHAR: + if (*sval) { + char *next = NULL; + /* We know ctrlparse won't write to the string, so + * casting away const is ugly but allowable. */ + ival = ctrlparse((char *)sval, &next); + if (!next) + ival = sval[0]; + } else { + ival = 255; /* special value meaning "don't set" */ + } + break; + case TYPE_BOOL: + if (stricmp(sval, "yes") == 0 || + stricmp(sval, "on") == 0 || + stricmp(sval, "true") == 0 || + stricmp(sval, "+") == 0) + ival = 1; /* true */ + else if (stricmp(sval, "no") == 0 || + stricmp(sval, "off") == 0 || + stricmp(sval, "false") == 0 || + stricmp(sval, "-") == 0) + ival = 0; /* false */ + else + ival = (atoi(sval) != 0); + break; + default: + unreachable("Bad mode->type"); + } + + modes.have_mode[mode->opcode] = true; + modes.mode_val[mode->opcode] = ival; + } + + sfree(to_free); + } + + { + unsigned ospeed, ispeed; + + /* Unpick the terminal-speed config string. */ + ospeed = ispeed = 38400; /* last-resort defaults */ + sscanf(conf_get_str(conf, CONF_termspeed), "%u,%u", &ospeed, &ispeed); + /* Currently we unconditionally set these */ + modes.have_mode[TTYMODE_ISPEED] = true; + modes.mode_val[TTYMODE_ISPEED] = ispeed; + modes.have_mode[TTYMODE_OSPEED] = true; + modes.mode_val[TTYMODE_OSPEED] = ospeed; + } + + return modes; +} + +struct ssh_ttymodes read_ttymodes_from_packet( + BinarySource *bs, int ssh_version) +{ + struct ssh_ttymodes modes; + memset(&modes, 0, sizeof(modes)); + + while (1) { + unsigned real_opcode, our_opcode; + + real_opcode = get_byte(bs); + if (real_opcode == TTYMODE_END_OF_LIST) + break; + if (real_opcode >= 160) { + /* + * RFC 4254 (and the SSH 1.5 spec): "Opcodes 160 to 255 + * are not yet defined, and cause parsing to stop (they + * should only be used after any other data)." + * + * My interpretation of this is that if one of these + * opcodes appears, it's not a parse _error_, but it is + * something that we don't know how to parse even well + * enough to step over it to find the next opcode, so we + * stop parsing now and assume that the rest of the string + * is composed entirely of things we don't understand and + * (as usual for unsupported terminal modes) silently + * ignore. + */ + return modes; + } + + our_opcode = our_ttymode_opcode(real_opcode, ssh_version); + assert(our_opcode < TTYMODE_LIMIT); + modes.have_mode[our_opcode] = true; + + if (ssh_version == 1 && real_opcode >= 1 && real_opcode <= 127) + modes.mode_val[our_opcode] = get_byte(bs); + else + modes.mode_val[our_opcode] = get_uint32(bs); + } + + return modes; +} + +void write_ttymodes_to_packet(BinarySink *bs, int ssh_version, + struct ssh_ttymodes modes) +{ + unsigned i; + + for (i = 0; i < TTYMODE_LIMIT; i++) { + if (modes.have_mode[i]) { + unsigned val = modes.mode_val[i]; + unsigned opcode = real_ttymode_opcode(i, ssh_version); + + put_byte(bs, opcode); + if (ssh_version == 1 && opcode >= 1 && opcode <= 127) + put_byte(bs, val); + else + put_uint32(bs, val); + } + } + + put_byte(bs, TTYMODE_END_OF_LIST); +} + +/* ---------------------------------------------------------------------- + * Routine for allocating a new channel ID, given a means of finding + * the index field in a given channel structure. + */ + +unsigned alloc_channel_id_general(tree234 *channels, size_t localid_offset) +{ + const unsigned CHANNEL_NUMBER_OFFSET = 256; + search234_state ss; + + /* + * First-fit allocation of channel numbers: we always pick the + * lowest unused one. + * + * Every channel before that, and no channel after it, has an ID + * exactly equal to its tree index plus CHANNEL_NUMBER_OFFSET. So + * we can use the search234 system to identify the length of that + * initial sequence, in a single log-time pass down the channels + * tree. + */ + search234_start(&ss, channels); + while (ss.element) { + unsigned localid = *(unsigned *)((char *)ss.element + localid_offset); + if (localid == ss.index + CHANNEL_NUMBER_OFFSET) + search234_step(&ss, +1); + else + search234_step(&ss, -1); + } + + /* + * Now ss.index gives exactly the number of channels in that + * initial sequence. So adding CHANNEL_NUMBER_OFFSET to it must + * give precisely the lowest unused channel number. + */ + return ss.index + CHANNEL_NUMBER_OFFSET; +} + +/* ---------------------------------------------------------------------- + * Functions for handling the comma-separated strings used to store + * lists of protocol identifiers in SSH-2. + */ + +void add_to_commasep(strbuf *buf, const char *data) +{ + if (buf->len > 0) + put_byte(buf, ','); + put_data(buf, data, strlen(data)); +} + +bool get_commasep_word(ptrlen *list, ptrlen *word) +{ + const char *comma; + + /* + * Discard empty list elements, should there be any, because we + * never want to return one as if it was a real string. (This + * introduces a mild tolerance of badly formatted data in lists we + * receive, but I think that's acceptable.) + */ + while (list->len > 0 && *(const char *)list->ptr == ',') { + list->ptr = (const char *)list->ptr + 1; + list->len--; + } + + if (!list->len) + return false; + + comma = memchr(list->ptr, ',', list->len); + if (!comma) { + *word = *list; + list->len = 0; + } else { + size_t wordlen = comma - (const char *)list->ptr; + word->ptr = list->ptr; + word->len = wordlen; + list->ptr = (const char *)list->ptr + wordlen + 1; + list->len -= wordlen + 1; + } + return true; +} + +/* ---------------------------------------------------------------------- + * Functions for translating SSH packet type codes into their symbolic + * string names. + */ + +#define TRANSLATE_UNIVERSAL(y, name, value) \ + if (type == value) return #name; +#define TRANSLATE_KEX(y, name, value, ctx) \ + if (type == value && pkt_kctx == ctx) return #name; +#define TRANSLATE_AUTH(y, name, value, ctx) \ + if (type == value && pkt_actx == ctx) return #name; + +const char *ssh1_pkt_type(int type) +{ + SSH1_MESSAGE_TYPES(TRANSLATE_UNIVERSAL, y); + return "unknown"; +} +const char *ssh2_pkt_type(Pkt_KCtx pkt_kctx, Pkt_ACtx pkt_actx, int type) +{ + SSH2_MESSAGE_TYPES(TRANSLATE_UNIVERSAL, TRANSLATE_KEX, TRANSLATE_AUTH, y); + return "unknown"; +} + +#undef TRANSLATE_UNIVERSAL +#undef TRANSLATE_KEX +#undef TRANSLATE_AUTH + +/* ---------------------------------------------------------------------- + * Common helper function for clients and implementations of + * PacketProtocolLayer. + */ + +void ssh_ppl_replace(PacketProtocolLayer *old, PacketProtocolLayer *new) +{ + new->bpp = old->bpp; + ssh_ppl_setup_queues(new, old->in_pq, old->out_pq); + new->selfptr = old->selfptr; + new->user_input = old->user_input; + new->seat = old->seat; + new->ssh = old->ssh; + + *new->selfptr = new; + ssh_ppl_free(old); + + /* The new layer might need to be the first one that sends a + * packet, so trigger a call to its main coroutine immediately. If + * it doesn't need to go first, the worst that will do is return + * straight away. */ + queue_idempotent_callback(&new->ic_process_queue); +} + +void ssh_ppl_free(PacketProtocolLayer *ppl) +{ + delete_callbacks_for_context(ppl); + ppl->vt->free(ppl); +} + +static void ssh_ppl_ic_process_queue_callback(void *context) +{ + PacketProtocolLayer *ppl = (PacketProtocolLayer *)context; + ssh_ppl_process_queue(ppl); +} + +void ssh_ppl_setup_queues(PacketProtocolLayer *ppl, + PktInQueue *inq, PktOutQueue *outq) +{ + ppl->in_pq = inq; + ppl->out_pq = outq; + ppl->in_pq->pqb.ic = &ppl->ic_process_queue; + ppl->ic_process_queue.fn = ssh_ppl_ic_process_queue_callback; + ppl->ic_process_queue.ctx = ppl; + + /* If there's already something on the input queue, it will want + * handling immediately. */ + if (pq_peek(ppl->in_pq)) + queue_idempotent_callback(&ppl->ic_process_queue); +} + +void ssh_ppl_user_output_string_and_free(PacketProtocolLayer *ppl, char *text) +{ + /* Messages sent via this function are from the SSH layer, not + * from the server-side process, so they always have the stderr + * flag set. */ + seat_stderr_pl(ppl->seat, ptrlen_from_asciz(text)); + sfree(text); +} + +size_t ssh_ppl_default_queued_data_size(PacketProtocolLayer *ppl) +{ + return ppl->out_pq->pqb.total_size; +} + +/* ---------------------------------------------------------------------- + * Common helper functions for clients and implementations of + * BinaryPacketProtocol. + */ + +static void ssh_bpp_input_raw_data_callback(void *context) +{ + BinaryPacketProtocol *bpp = (BinaryPacketProtocol *)context; + Ssh *ssh = bpp->ssh; /* in case bpp is about to get freed */ + ssh_bpp_handle_input(bpp); + /* If we've now cleared enough backlog on the input connection, we + * may need to unfreeze it. */ + ssh_conn_processed_data(ssh); +} + +static void ssh_bpp_output_packet_callback(void *context) +{ + BinaryPacketProtocol *bpp = (BinaryPacketProtocol *)context; + ssh_bpp_handle_output(bpp); +} + +void ssh_bpp_common_setup(BinaryPacketProtocol *bpp) +{ + pq_in_init(&bpp->in_pq); + pq_out_init(&bpp->out_pq); + bpp->input_eof = false; + bpp->ic_in_raw.fn = ssh_bpp_input_raw_data_callback; + bpp->ic_in_raw.ctx = bpp; + bpp->ic_out_pq.fn = ssh_bpp_output_packet_callback; + bpp->ic_out_pq.ctx = bpp; + bpp->out_pq.pqb.ic = &bpp->ic_out_pq; +} + +void ssh_bpp_free(BinaryPacketProtocol *bpp) +{ + delete_callbacks_for_context(bpp); + bpp->vt->free(bpp); +} + +void ssh2_bpp_queue_disconnect(BinaryPacketProtocol *bpp, + const char *msg, int category) +{ + PktOut *pkt = ssh_bpp_new_pktout(bpp, SSH2_MSG_DISCONNECT); + put_uint32(pkt, category); + put_stringz(pkt, msg); + put_stringz(pkt, "en"); /* language tag */ + pq_push(&bpp->out_pq, pkt); +} + +#define BITMAP_UNIVERSAL(y, name, value) \ + | (value >= y && value < y+32 ? 1UL << (value-y) : 0) +#define BITMAP_CONDITIONAL(y, name, value, ctx) \ + BITMAP_UNIVERSAL(y, name, value) +#define SSH2_BITMAP_WORD(y) \ + (0 SSH2_MESSAGE_TYPES(BITMAP_UNIVERSAL, BITMAP_CONDITIONAL, \ + BITMAP_CONDITIONAL, (32*y))) + +bool ssh2_bpp_check_unimplemented(BinaryPacketProtocol *bpp, PktIn *pktin) +{ + static const unsigned valid_bitmap[] = { + SSH2_BITMAP_WORD(0), + SSH2_BITMAP_WORD(1), + SSH2_BITMAP_WORD(2), + SSH2_BITMAP_WORD(3), + SSH2_BITMAP_WORD(4), + SSH2_BITMAP_WORD(5), + SSH2_BITMAP_WORD(6), + SSH2_BITMAP_WORD(7), + }; + + if (pktin->type < 0x100 && + !((valid_bitmap[pktin->type >> 5] >> (pktin->type & 0x1F)) & 1)) { + PktOut *pkt = ssh_bpp_new_pktout(bpp, SSH2_MSG_UNIMPLEMENTED); + put_uint32(pkt, pktin->sequence); + pq_push(&bpp->out_pq, pkt); + return true; + } + + return false; +} + +#undef BITMAP_UNIVERSAL +#undef BITMAP_CONDITIONAL +#undef SSH1_BITMAP_WORD + +/* ---------------------------------------------------------------------- + * Function to check a host key against any manually configured in Conf. + */ + +int verify_ssh_manual_host_key(Conf *conf, char **fingerprints, ssh_key *key) +{ + if (!conf_get_str_nthstrkey(conf, CONF_ssh_manual_hostkeys, 0)) + return -1; /* no manual keys configured */ + + if (fingerprints) { + for (size_t i = 0; i < SSH_N_FPTYPES; i++) { + /* + * Each fingerprint string we've been given will have + * things like 'ssh-rsa 2048' at the front of it. Strip + * those off and narrow down to just the hash at the end + * of the string. + */ + const char *fingerprint = fingerprints[i]; + if (!fingerprint) + continue; + const char *p = strrchr(fingerprint, ' '); + fingerprint = p ? p+1 : fingerprint; + if (conf_get_str_str_opt(conf, CONF_ssh_manual_hostkeys, + fingerprint)) + return 1; /* success */ + } + } + + if (key) { + /* + * Construct the base64-encoded public key blob and see if + * that's listed. + */ + strbuf *binblob; + char *base64blob; + int atoms, i; + binblob = strbuf_new(); + ssh_key_public_blob(key, BinarySink_UPCAST(binblob)); + atoms = (binblob->len + 2) / 3; + base64blob = snewn(atoms * 4 + 1, char); + for (i = 0; i < atoms; i++) + base64_encode_atom(binblob->u + 3*i, + binblob->len - 3*i, base64blob + 4*i); + base64blob[atoms * 4] = '\0'; + strbuf_free(binblob); + if (conf_get_str_str_opt(conf, CONF_ssh_manual_hostkeys, base64blob)) { + sfree(base64blob); + return 1; /* success */ + } + sfree(base64blob); + } + + return 0; +} + +/* ---------------------------------------------------------------------- + * Common functions shared between SSH-1 layers. + */ + +bool ssh1_common_get_specials( + PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx) +{ + /* + * 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 (!(ppl->remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE)) { + add_special(ctx, "IGNORE message", SS_NOP, 0); + return true; + } + + return false; +} + +bool ssh1_common_filter_queue(PacketProtocolLayer *ppl) +{ + PktIn *pktin; + ptrlen msg; + + while ((pktin = pq_peek(ppl->in_pq)) != NULL) { + switch (pktin->type) { + case SSH1_MSG_DISCONNECT: + msg = get_string(pktin); + ssh_remote_error(ppl->ssh, + "Remote side sent disconnect message:\n\"%.*s\"", + PTRLEN_PRINTF(msg)); + /* don't try to pop the queue, because we've been freed! */ + return true; /* indicate that we've been freed */ + + case SSH1_MSG_DEBUG: + msg = get_string(pktin); + ppl_logevent("Remote debug message: %.*s", PTRLEN_PRINTF(msg)); + pq_pop(ppl->in_pq); + break; + + case SSH1_MSG_IGNORE: + /* Do nothing, because we're ignoring it! Duhh. */ + pq_pop(ppl->in_pq); + break; + + default: + return false; + } + } + + return false; +} + +void ssh1_compute_session_id( + unsigned char *session_id, const unsigned char *cookie, + RSAKey *hostkey, RSAKey *servkey) +{ + ssh_hash *hash = ssh_hash_new(&ssh_md5); + + for (size_t i = (mp_get_nbits(hostkey->modulus) + 7) / 8; i-- ;) + put_byte(hash, mp_get_byte(hostkey->modulus, i)); + for (size_t i = (mp_get_nbits(servkey->modulus) + 7) / 8; i-- ;) + put_byte(hash, mp_get_byte(servkey->modulus, i)); + put_data(hash, cookie, 8); + ssh_hash_final(hash, session_id); +} diff --git a/ssh/connection1-client.c b/ssh/connection1-client.c new file mode 100644 index 00000000..f32fefcd --- /dev/null +++ b/ssh/connection1-client.c @@ -0,0 +1,547 @@ +/* + * Client-specific parts of the SSH-1 connection layer. + */ + +#include + +#include "putty.h" +#include "ssh.h" +#include "bpp.h" +#include "ppl.h" +#include "channel.h" +#include "sshcr.h" +#include "connection1.h" + +void ssh1_connection_direction_specific_setup( + struct ssh1_connection_state *s) +{ + if (!s->mainchan) { + /* + * Start up the main session, by telling mainchan.c to do it + * all just as it would in SSH-2, and translating those + * concepts to SSH-1's non-channel-shaped idea of the main + * session. + */ + s->mainchan = mainchan_new( + &s->ppl, &s->cl, s->conf, s->term_width, s->term_height, + false /* is_simple */, NULL); + } +} + +typedef void (*sf_handler_fn_t)(struct ssh1_connection_state *s, + bool success, void *ctx); + +struct outstanding_succfail { + sf_handler_fn_t handler; + void *ctx; + struct outstanding_succfail *next; + + /* + * The 'trivial' flag is set if this handler is in response to a + * request for which the SSH-1 protocol doesn't actually specify a + * response packet. The client of this system (mainchan.c) will + * expect to get an acknowledgment regardless, so we arrange to + * send that ack immediately after the rest of the queue empties. + */ + bool trivial; +}; + +static void ssh1_connection_process_trivial_succfails(void *vs); + +static void ssh1_queue_succfail_handler( + struct ssh1_connection_state *s, sf_handler_fn_t handler, void *ctx, + bool trivial) +{ + struct outstanding_succfail *osf = snew(struct outstanding_succfail); + osf->handler = handler; + osf->ctx = ctx; + osf->trivial = trivial; + osf->next = NULL; + if (s->succfail_tail) + s->succfail_tail->next = osf; + else + s->succfail_head = osf; + s->succfail_tail = osf; + + /* In case this one was trivial and the queue was already empty, + * we should make sure we run the handler promptly, and the + * easiest way is to queue it anyway and then run a trivials pass + * by callback. */ + queue_toplevel_callback(ssh1_connection_process_trivial_succfails, s); +} + +static void ssh1_connection_process_succfail( + struct ssh1_connection_state *s, bool success) +{ + struct outstanding_succfail *prevhead = s->succfail_head; + s->succfail_head = s->succfail_head->next; + if (!s->succfail_head) + s->succfail_tail = NULL; + prevhead->handler(s, success, prevhead->ctx); + sfree(prevhead); +} + +static void ssh1_connection_process_trivial_succfails(void *vs) +{ + struct ssh1_connection_state *s = (struct ssh1_connection_state *)vs; + while (s->succfail_head && s->succfail_head->trivial) + ssh1_connection_process_succfail(s, true); +} + +bool ssh1_handle_direction_specific_packet( + struct ssh1_connection_state *s, PktIn *pktin) +{ + PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ + + PktOut *pktout; + struct ssh1_channel *c; + unsigned remid; + struct ssh_rportfwd pf, *pfp; + ptrlen host, data; + int port; + + switch (pktin->type) { + case SSH1_SMSG_SUCCESS: + case SSH1_SMSG_FAILURE: + if (!s->succfail_head) { + ssh_remote_error(s->ppl.ssh, + "Received %s with no outstanding request", + ssh1_pkt_type(pktin->type)); + return true; + } + + ssh1_connection_process_succfail( + s, pktin->type == SSH1_SMSG_SUCCESS); + queue_toplevel_callback( + ssh1_connection_process_trivial_succfails, s); + + return true; + + case SSH1_SMSG_X11_OPEN: + remid = get_uint32(pktin); + + /* Refuse if X11 forwarding is disabled. */ + if (!s->X11_fwd_enabled) { + pktout = ssh_bpp_new_pktout( + s->ppl.bpp, SSH1_MSG_CHANNEL_OPEN_FAILURE); + put_uint32(pktout, remid); + pq_push(s->ppl.out_pq, pktout); + ppl_logevent("Rejected X11 connect request"); + } else { + c = snew(struct ssh1_channel); + c->connlayer = s; + ssh1_channel_init(c); + c->remoteid = remid; + c->chan = x11_new_channel(s->x11authtree, &c->sc, + NULL, -1, false); + c->remoteid = remid; + c->halfopen = false; + + pktout = ssh_bpp_new_pktout( + s->ppl.bpp, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION); + put_uint32(pktout, c->remoteid); + put_uint32(pktout, c->localid); + pq_push(s->ppl.out_pq, pktout); + ppl_logevent("Opened X11 forward channel"); + } + + return true; + + case SSH1_SMSG_AGENT_OPEN: + remid = get_uint32(pktin); + + /* Refuse if agent forwarding is disabled. */ + if (!ssh_agent_forwarding_permitted(&s->cl)) { + pktout = ssh_bpp_new_pktout( + s->ppl.bpp, SSH1_MSG_CHANNEL_OPEN_FAILURE); + put_uint32(pktout, remid); + pq_push(s->ppl.out_pq, pktout); + } else { + c = snew(struct ssh1_channel); + c->connlayer = s; + ssh1_channel_init(c); + c->remoteid = remid; + c->halfopen = false; + + /* + * If possible, make a stream-oriented connection to the + * agent and set up an ordinary port-forwarding type + * channel over it. + */ + Plug *plug; + Channel *ch = portfwd_raw_new(&s->cl, &plug, true); + Socket *skt = agent_connect(plug); + if (!sk_socket_error(skt)) { + portfwd_raw_setup(ch, skt, &c->sc); + c->chan = ch; + } else { + portfwd_raw_free(ch); + + /* + * Otherwise, fall back to the old-fashioned system of + * parsing the forwarded data stream ourselves for + * message boundaries, and passing each individual + * message to the one-off agent_query(). + */ + c->chan = agentf_new(&c->sc); + } + + pktout = ssh_bpp_new_pktout( + s->ppl.bpp, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION); + put_uint32(pktout, c->remoteid); + put_uint32(pktout, c->localid); + pq_push(s->ppl.out_pq, pktout); + } + + return true; + + case SSH1_MSG_PORT_OPEN: + remid = get_uint32(pktin); + host = get_string(pktin); + port = toint(get_uint32(pktin)); + + pf.dhost = mkstr(host); + pf.dport = port; + pfp = find234(s->rportfwds, &pf, NULL); + + if (!pfp) { + ppl_logevent("Rejected remote port open request for %s:%d", + pf.dhost, port); + pktout = ssh_bpp_new_pktout( + s->ppl.bpp, SSH1_MSG_CHANNEL_OPEN_FAILURE); + put_uint32(pktout, remid); + pq_push(s->ppl.out_pq, pktout); + } else { + char *err; + + c = snew(struct ssh1_channel); + c->connlayer = s; + ppl_logevent("Received remote port open request for %s:%d", + pf.dhost, port); + err = portfwdmgr_connect( + s->portfwdmgr, &c->chan, pf.dhost, port, + &c->sc, pfp->addressfamily); + + if (err) { + ppl_logevent("Port open failed: %s", err); + sfree(err); + ssh1_channel_free(c); + pktout = ssh_bpp_new_pktout( + s->ppl.bpp, SSH1_MSG_CHANNEL_OPEN_FAILURE); + put_uint32(pktout, remid); + pq_push(s->ppl.out_pq, pktout); + } else { + ssh1_channel_init(c); + c->remoteid = remid; + c->halfopen = false; + pktout = ssh_bpp_new_pktout( + s->ppl.bpp, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION); + put_uint32(pktout, c->remoteid); + put_uint32(pktout, c->localid); + pq_push(s->ppl.out_pq, pktout); + ppl_logevent("Forwarded port opened successfully"); + } + } + + sfree(pf.dhost); + + return true; + + case SSH1_SMSG_STDOUT_DATA: + case SSH1_SMSG_STDERR_DATA: + data = get_string(pktin); + if (!get_err(pktin)) { + int bufsize = seat_output( + s->ppl.seat, pktin->type == SSH1_SMSG_STDERR_DATA, + data.ptr, data.len); + if (!s->stdout_throttling && bufsize > SSH1_BUFFER_LIMIT) { + s->stdout_throttling = true; + ssh_throttle_conn(s->ppl.ssh, +1); + } + } + + return true; + + case SSH1_SMSG_EXIT_STATUS: { + int exitcode = get_uint32(pktin); + ppl_logevent("Server sent command exit status %d", exitcode); + ssh_got_exitcode(s->ppl.ssh, exitcode); + + s->session_terminated = true; + return true; + } + + default: + return false; + } +} + +static void ssh1mainchan_succfail_wantreply(struct ssh1_connection_state *s, + bool success, void *ctx) +{ + chan_request_response(s->mainchan_chan, success); +} + +static void ssh1mainchan_succfail_nowantreply(struct ssh1_connection_state *s, + bool success, void *ctx) +{ +} + +static void ssh1mainchan_queue_response(struct ssh1_connection_state *s, + bool want_reply, bool trivial) +{ + sf_handler_fn_t handler = (want_reply ? ssh1mainchan_succfail_wantreply : + ssh1mainchan_succfail_nowantreply); + ssh1_queue_succfail_handler(s, handler, NULL, trivial); +} + +static void ssh1mainchan_request_x11_forwarding( + SshChannel *sc, bool want_reply, const char *authproto, + const char *authdata, int screen_number, bool oneshot) +{ + struct ssh1_connection_state *s = + container_of(sc, struct ssh1_connection_state, mainchan_sc); + PktOut *pktout; + + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_X11_REQUEST_FORWARDING); + put_stringz(pktout, authproto); + put_stringz(pktout, authdata); + if (s->local_protoflags & SSH1_PROTOFLAG_SCREEN_NUMBER) + put_uint32(pktout, screen_number); + pq_push(s->ppl.out_pq, pktout); + + ssh1mainchan_queue_response(s, want_reply, false); +} + +static void ssh1mainchan_request_agent_forwarding( + SshChannel *sc, bool want_reply) +{ + struct ssh1_connection_state *s = + container_of(sc, struct ssh1_connection_state, mainchan_sc); + PktOut *pktout; + + pktout = ssh_bpp_new_pktout( + s->ppl.bpp, SSH1_CMSG_AGENT_REQUEST_FORWARDING); + pq_push(s->ppl.out_pq, pktout); + + ssh1mainchan_queue_response(s, want_reply, false); +} + +static void ssh1mainchan_request_pty( + SshChannel *sc, bool want_reply, Conf *conf, int w, int h) +{ + struct ssh1_connection_state *s = + container_of(sc, struct ssh1_connection_state, mainchan_sc); + PktOut *pktout; + + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_REQUEST_PTY); + put_stringz(pktout, conf_get_str(s->conf, CONF_termtype)); + put_uint32(pktout, h); + put_uint32(pktout, w); + put_uint32(pktout, 0); /* width in pixels */ + put_uint32(pktout, 0); /* height in pixels */ + write_ttymodes_to_packet( + BinarySink_UPCAST(pktout), 1, + get_ttymodes_from_conf(s->ppl.seat, conf)); + pq_push(s->ppl.out_pq, pktout); + + ssh1mainchan_queue_response(s, want_reply, false); +} + +static bool ssh1mainchan_send_env_var( + SshChannel *sc, bool want_reply, const char *var, const char *value) +{ + return false; /* SSH-1 doesn't support this at all */ +} + +static void ssh1mainchan_start_shell(SshChannel *sc, bool want_reply) +{ + struct ssh1_connection_state *s = + container_of(sc, struct ssh1_connection_state, mainchan_sc); + PktOut *pktout; + + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_EXEC_SHELL); + pq_push(s->ppl.out_pq, pktout); + + ssh1mainchan_queue_response(s, want_reply, true); +} + +static void ssh1mainchan_start_command( + SshChannel *sc, bool want_reply, const char *command) +{ + struct ssh1_connection_state *s = + container_of(sc, struct ssh1_connection_state, mainchan_sc); + PktOut *pktout; + + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_EXEC_CMD); + put_stringz(pktout, command); + pq_push(s->ppl.out_pq, pktout); + + ssh1mainchan_queue_response(s, want_reply, true); +} + +static bool ssh1mainchan_start_subsystem( + SshChannel *sc, bool want_reply, const char *subsystem) +{ + return false; /* SSH-1 doesn't support this at all */ +} + +static bool ssh1mainchan_send_serial_break( + SshChannel *sc, bool want_reply, int length) +{ + return false; /* SSH-1 doesn't support this at all */ +} + +static bool ssh1mainchan_send_signal( + SshChannel *sc, bool want_reply, const char *signame) +{ + return false; /* SSH-1 doesn't support this at all */ +} + +static void ssh1mainchan_send_terminal_size_change( + SshChannel *sc, int w, int h) +{ + struct ssh1_connection_state *s = + container_of(sc, struct ssh1_connection_state, mainchan_sc); + PktOut *pktout; + + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_WINDOW_SIZE); + put_uint32(pktout, h); + put_uint32(pktout, w); + put_uint32(pktout, 0); /* width in pixels */ + put_uint32(pktout, 0); /* height in pixels */ + pq_push(s->ppl.out_pq, pktout); +} + +static void ssh1mainchan_hint_channel_is_simple(SshChannel *sc) +{ +} + +static size_t ssh1mainchan_write( + SshChannel *sc, bool is_stderr, const void *data, size_t len) +{ + struct ssh1_connection_state *s = + container_of(sc, struct ssh1_connection_state, mainchan_sc); + PktOut *pktout; + + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_STDIN_DATA); + put_string(pktout, data, len); + pq_push(s->ppl.out_pq, pktout); + + return 0; +} + +static void ssh1mainchan_write_eof(SshChannel *sc) +{ + struct ssh1_connection_state *s = + container_of(sc, struct ssh1_connection_state, mainchan_sc); + PktOut *pktout; + + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_EOF); + pq_push(s->ppl.out_pq, pktout); +} + +static const SshChannelVtable ssh1mainchan_vtable = { + .write = ssh1mainchan_write, + .write_eof = ssh1mainchan_write_eof, + .request_x11_forwarding = ssh1mainchan_request_x11_forwarding, + .request_agent_forwarding = ssh1mainchan_request_agent_forwarding, + .request_pty = ssh1mainchan_request_pty, + .send_env_var = ssh1mainchan_send_env_var, + .start_shell = ssh1mainchan_start_shell, + .start_command = ssh1mainchan_start_command, + .start_subsystem = ssh1mainchan_start_subsystem, + .send_serial_break = ssh1mainchan_send_serial_break, + .send_signal = ssh1mainchan_send_signal, + .send_terminal_size_change = ssh1mainchan_send_terminal_size_change, + .hint_channel_is_simple = ssh1mainchan_hint_channel_is_simple, + /* other methods are NULL */ +}; + +static void ssh1_session_confirm_callback(void *vctx) +{ + struct ssh1_connection_state *s = (struct ssh1_connection_state *)vctx; + chan_open_confirmation(s->mainchan_chan); +} + +SshChannel *ssh1_session_open(ConnectionLayer *cl, Channel *chan) +{ + struct ssh1_connection_state *s = + container_of(cl, struct ssh1_connection_state, cl); + s->mainchan_sc.vt = &ssh1mainchan_vtable; + s->mainchan_sc.cl = &s->cl; + s->mainchan_chan = chan; + queue_toplevel_callback(ssh1_session_confirm_callback, s); + return &s->mainchan_sc; +} + +static void ssh1_rportfwd_response(struct ssh1_connection_state *s, + bool success, void *ctx) +{ + PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ + struct ssh_rportfwd *rpf = (struct ssh_rportfwd *)ctx; + + if (success) { + ppl_logevent("Remote port forwarding from %s enabled", + rpf->log_description); + } else { + ppl_logevent("Remote port forwarding from %s refused", + rpf->log_description); + + struct ssh_rportfwd *realpf = del234(s->rportfwds, rpf); + assert(realpf == rpf); + portfwdmgr_close(s->portfwdmgr, rpf->pfr); + free_rportfwd(rpf); + } +} + +struct ssh_rportfwd *ssh1_rportfwd_alloc( + ConnectionLayer *cl, + const char *shost, int sport, const char *dhost, int dport, + int addressfamily, const char *log_description, PortFwdRecord *pfr, + ssh_sharing_connstate *share_ctx) +{ + struct ssh1_connection_state *s = + container_of(cl, struct ssh1_connection_state, cl); + struct ssh_rportfwd *rpf = snew(struct ssh_rportfwd); + + rpf->shost = dupstr(shost); + rpf->sport = sport; + rpf->dhost = dupstr(dhost); + rpf->dport = dport; + rpf->addressfamily = addressfamily; + rpf->log_description = dupstr(log_description); + rpf->pfr = pfr; + + if (add234(s->rportfwds, rpf) != rpf) { + free_rportfwd(rpf); + return NULL; + } + + PktOut *pktout = ssh_bpp_new_pktout( + s->ppl.bpp, SSH1_CMSG_PORT_FORWARD_REQUEST); + put_uint32(pktout, rpf->sport); + put_stringz(pktout, rpf->dhost); + put_uint32(pktout, rpf->dport); + pq_push(s->ppl.out_pq, pktout); + + ssh1_queue_succfail_handler(s, ssh1_rportfwd_response, rpf, false); + + return rpf; +} + +SshChannel *ssh1_serverside_x11_open( + ConnectionLayer *cl, Channel *chan, const SocketPeerInfo *pi) +{ + unreachable("Should never be called in the client"); +} + +SshChannel *ssh1_serverside_agent_open(ConnectionLayer *cl, Channel *chan) +{ + unreachable("Should never be called in the client"); +} + +bool ssh1_connection_need_antispoof_prompt(struct ssh1_connection_state *s) +{ + return !seat_set_trust_status(s->ppl.seat, false); +} diff --git a/ssh/connection1-server.c b/ssh/connection1-server.c new file mode 100644 index 00000000..1123327c --- /dev/null +++ b/ssh/connection1-server.c @@ -0,0 +1,365 @@ +/* + * Server-specific parts of the SSH-1 connection layer. + */ + +#include + +#include "putty.h" +#include "ssh.h" +#include "bpp.h" +#include "ppl.h" +#include "channel.h" +#include "sshcr.h" +#include "connection1.h" +#include "server.h" + +static size_t ssh1sesschan_write(SshChannel *c, bool is_stderr, + const void *, size_t); +static void ssh1sesschan_write_eof(SshChannel *c); +static void ssh1sesschan_initiate_close(SshChannel *c, const char *err); +static void ssh1sesschan_send_exit_status(SshChannel *c, int status); +static void ssh1sesschan_send_exit_signal( + SshChannel *c, ptrlen signame, bool core_dumped, ptrlen msg); + +static const SshChannelVtable ssh1sesschan_vtable = { + .write = ssh1sesschan_write, + .write_eof = ssh1sesschan_write_eof, + .initiate_close = ssh1sesschan_initiate_close, + .send_exit_status = ssh1sesschan_send_exit_status, + .send_exit_signal = ssh1sesschan_send_exit_signal, + /* everything else is NULL */ +}; + +void ssh1connection_server_configure( + PacketProtocolLayer *ppl, const SshServerConfig *ssc) +{ + struct ssh1_connection_state *s = + container_of(ppl, struct ssh1_connection_state, ppl); + s->ssc = ssc; +} + +void ssh1_connection_direction_specific_setup( + struct ssh1_connection_state *s) +{ + if (!s->mainchan_chan) { + s->mainchan_sc.vt = &ssh1sesschan_vtable; + s->mainchan_sc.cl = &s->cl; + s->mainchan_chan = sesschan_new( + &s->mainchan_sc, s->ppl.logctx, NULL, s->ssc); + } +} + +bool ssh1_handle_direction_specific_packet( + struct ssh1_connection_state *s, PktIn *pktin) +{ + PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ + PktOut *pktout; + struct ssh1_channel *c; + unsigned remid; + ptrlen host, cmd, data; + char *host_str, *err; + int port, listenport; + bool success; + + switch (pktin->type) { + case SSH1_CMSG_EXEC_SHELL: + if (s->finished_setup) + goto unexpected_setup_packet; + + ppl_logevent("Client requested a shell"); + chan_run_shell(s->mainchan_chan); + s->finished_setup = true; + return true; + + case SSH1_CMSG_EXEC_CMD: + if (s->finished_setup) + goto unexpected_setup_packet; + + cmd = get_string(pktin); + ppl_logevent("Client sent command '%.*s'", PTRLEN_PRINTF(cmd)); + chan_run_command(s->mainchan_chan, cmd); + s->finished_setup = true; + return true; + + case SSH1_CMSG_REQUEST_COMPRESSION: + if (s->compressing || !s->ssc->ssh1_allow_compression) { + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_SMSG_FAILURE); + pq_push(s->ppl.out_pq, pktout); + } else { + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_SMSG_SUCCESS); + pq_push(s->ppl.out_pq, pktout); + /* Synchronous run of output formatting, to ensure that + * success packet is converted into wire format before we + * start compressing */ + ssh_bpp_handle_output(s->ppl.bpp); + /* And now ensure that the _next_ packet will be the first + * compressed one. */ + ssh1_bpp_start_compression(s->ppl.bpp); + s->compressing = true; + } + + return true; + + case SSH1_CMSG_REQUEST_PTY: { + if (s->finished_setup) + goto unexpected_setup_packet; + + ptrlen termtype = get_string(pktin); + unsigned height = get_uint32(pktin); + unsigned width = get_uint32(pktin); + unsigned pixwidth = get_uint32(pktin); + unsigned pixheight = get_uint32(pktin); + struct ssh_ttymodes modes = read_ttymodes_from_packet( + BinarySource_UPCAST(pktin), 1); + + if (get_err(pktin)) { + ppl_logevent("Unable to decode pty request packet"); + success = false; + } else if (!chan_allocate_pty( + s->mainchan_chan, termtype, width, height, + pixwidth, pixheight, modes)) { + ppl_logevent("Unable to allocate a pty"); + success = false; + } else { + success = true; + } + + pktout = ssh_bpp_new_pktout( + s->ppl.bpp, (success ? SSH1_SMSG_SUCCESS : SSH1_SMSG_FAILURE)); + pq_push(s->ppl.out_pq, pktout); + return true; + } + + case SSH1_CMSG_PORT_FORWARD_REQUEST: + if (s->finished_setup) + goto unexpected_setup_packet; + + listenport = toint(get_uint32(pktin)); + host = get_string(pktin); + port = toint(get_uint32(pktin)); + + ppl_logevent("Client requested port %d forward to %.*s:%d", + listenport, PTRLEN_PRINTF(host), port); + + host_str = mkstr(host); + success = portfwdmgr_listen( + s->portfwdmgr, NULL, listenport, host_str, port, s->conf); + sfree(host_str); + + pktout = ssh_bpp_new_pktout( + s->ppl.bpp, (success ? SSH1_SMSG_SUCCESS : SSH1_SMSG_FAILURE)); + pq_push(s->ppl.out_pq, pktout); + return true; + + case SSH1_CMSG_X11_REQUEST_FORWARDING: { + if (s->finished_setup) + goto unexpected_setup_packet; + + ptrlen authproto = get_string(pktin); + ptrlen authdata = get_string(pktin); + unsigned screen_number = 0; + if (s->remote_protoflags & SSH1_PROTOFLAG_SCREEN_NUMBER) + screen_number = get_uint32(pktin); + + success = chan_enable_x11_forwarding( + s->mainchan_chan, false, authproto, authdata, screen_number); + + pktout = ssh_bpp_new_pktout( + s->ppl.bpp, (success ? SSH1_SMSG_SUCCESS : SSH1_SMSG_FAILURE)); + pq_push(s->ppl.out_pq, pktout); + return true; + } + + case SSH1_CMSG_AGENT_REQUEST_FORWARDING: + if (s->finished_setup) + goto unexpected_setup_packet; + + success = chan_enable_agent_forwarding(s->mainchan_chan); + + pktout = ssh_bpp_new_pktout( + s->ppl.bpp, (success ? SSH1_SMSG_SUCCESS : SSH1_SMSG_FAILURE)); + pq_push(s->ppl.out_pq, pktout); + return true; + + case SSH1_CMSG_STDIN_DATA: + data = get_string(pktin); + chan_send(s->mainchan_chan, false, data.ptr, data.len); + return true; + + case SSH1_CMSG_EOF: + chan_send_eof(s->mainchan_chan); + return true; + + case SSH1_CMSG_WINDOW_SIZE: + return true; + + case SSH1_MSG_PORT_OPEN: + remid = get_uint32(pktin); + host = get_string(pktin); + port = toint(get_uint32(pktin)); + + host_str = mkstr(host); + + ppl_logevent("Received request to connect to port %s:%d", + host_str, port); + c = snew(struct ssh1_channel); + c->connlayer = s; + err = portfwdmgr_connect( + s->portfwdmgr, &c->chan, host_str, port, + &c->sc, ADDRTYPE_UNSPEC); + + sfree(host_str); + + if (err) { + ppl_logevent("Port open failed: %s", err); + sfree(err); + ssh1_channel_free(c); + pktout = ssh_bpp_new_pktout( + s->ppl.bpp, SSH1_MSG_CHANNEL_OPEN_FAILURE); + put_uint32(pktout, remid); + pq_push(s->ppl.out_pq, pktout); + } else { + ssh1_channel_init(c); + c->remoteid = remid; + c->halfopen = false; + pktout = ssh_bpp_new_pktout( + s->ppl.bpp, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION); + put_uint32(pktout, c->remoteid); + put_uint32(pktout, c->localid); + pq_push(s->ppl.out_pq, pktout); + ppl_logevent("Forwarded port opened successfully"); + } + + return true; + + case SSH1_CMSG_EXIT_CONFIRMATION: + if (!s->sent_exit_status) { + ssh_proto_error(s->ppl.ssh, "Received SSH1_CMSG_EXIT_CONFIRMATION" + " without having sent SSH1_SMSG_EXIT_STATUS"); + return true; + } + ppl_logevent("Client sent exit confirmation"); + return true; + + default: + return false; + } + + unexpected_setup_packet: + ssh_proto_error(s->ppl.ssh, "Received unexpected setup packet after the " + "setup phase, type %d (%s)", pktin->type, + ssh1_pkt_type(pktin->type)); + /* FIXME: ensure caller copes with us just having freed the whole layer */ + return true; +} + +SshChannel *ssh1_session_open(ConnectionLayer *cl, Channel *chan) +{ + unreachable("Should never be called in the server"); +} + +struct ssh_rportfwd *ssh1_rportfwd_alloc( + ConnectionLayer *cl, + const char *shost, int sport, const char *dhost, int dport, + int addressfamily, const char *log_description, PortFwdRecord *pfr, + ssh_sharing_connstate *share_ctx) +{ + unreachable("Should never be called in the server"); +} + +static size_t ssh1sesschan_write(SshChannel *sc, bool is_stderr, + const void *data, size_t len) +{ + struct ssh1_connection_state *s = + container_of(sc, struct ssh1_connection_state, mainchan_sc); + PktOut *pktout; + + pktout = ssh_bpp_new_pktout( + s->ppl.bpp, + (is_stderr ? SSH1_SMSG_STDERR_DATA : SSH1_SMSG_STDOUT_DATA)); + put_string(pktout, data, len); + pq_push(s->ppl.out_pq, pktout); + + return 0; +} + +static void ssh1sesschan_write_eof(SshChannel *sc) +{ + /* SSH-1 can't represent server-side EOF */ + /* FIXME: some kind of check-termination system, whereby once this has been called _and_ we've had an exit status _and_ we've got no other channels open, we send the actual EXIT_STATUS message */ +} + +static void ssh1sesschan_initiate_close(SshChannel *sc, const char *err) +{ + /* SSH-1 relies on the client to close the connection in the end */ +} + +static void ssh1sesschan_send_exit_status(SshChannel *sc, int status) +{ + struct ssh1_connection_state *s = + container_of(sc, struct ssh1_connection_state, mainchan_sc); + PktOut *pktout; + + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_SMSG_EXIT_STATUS); + put_uint32(pktout, status); + pq_push(s->ppl.out_pq, pktout); + + s->sent_exit_status = true; +} + +static void ssh1sesschan_send_exit_signal( + SshChannel *sc, ptrlen signame, bool core_dumped, ptrlen msg) +{ + /* SSH-1 has no separate representation for signals */ + ssh1sesschan_send_exit_status(sc, 128); +} + +SshChannel *ssh1_serverside_x11_open( + ConnectionLayer *cl, Channel *chan, const SocketPeerInfo *pi) +{ + struct ssh1_connection_state *s = + container_of(cl, struct ssh1_connection_state, cl); + PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ + struct ssh1_channel *c = snew(struct ssh1_channel); + PktOut *pktout; + + c->connlayer = s; + ssh1_channel_init(c); + c->halfopen = true; + c->chan = chan; + + ppl_logevent("Forwarding X11 connection to client"); + + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_SMSG_X11_OPEN); + put_uint32(pktout, c->localid); + pq_push(s->ppl.out_pq, pktout); + + return &c->sc; +} + +SshChannel *ssh1_serverside_agent_open(ConnectionLayer *cl, Channel *chan) +{ + struct ssh1_connection_state *s = + container_of(cl, struct ssh1_connection_state, cl); + PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ + struct ssh1_channel *c = snew(struct ssh1_channel); + PktOut *pktout; + + c->connlayer = s; + ssh1_channel_init(c); + c->halfopen = true; + c->chan = chan; + + ppl_logevent("Forwarding agent connection to client"); + + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_SMSG_AGENT_OPEN); + put_uint32(pktout, c->localid); + pq_push(s->ppl.out_pq, pktout); + + return &c->sc; +} + +bool ssh1_connection_need_antispoof_prompt(struct ssh1_connection_state *s) +{ + return false; +} diff --git a/ssh/connection1.c b/ssh/connection1.c new file mode 100644 index 00000000..7b1765f6 --- /dev/null +++ b/ssh/connection1.c @@ -0,0 +1,815 @@ +/* + * Packet protocol layer for the SSH-1 'connection protocol', i.e. + * everything after authentication finishes. + */ + +#include + +#include "putty.h" +#include "ssh.h" +#include "bpp.h" +#include "ppl.h" +#include "channel.h" +#include "sshcr.h" +#include "connection1.h" + +static int ssh1_rportfwd_cmp(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 void ssh1_connection_free(PacketProtocolLayer *); +static void ssh1_connection_process_queue(PacketProtocolLayer *); +static void ssh1_connection_special_cmd(PacketProtocolLayer *ppl, + SessionSpecialCode code, int arg); +static bool ssh1_connection_want_user_input(PacketProtocolLayer *ppl); +static void ssh1_connection_got_user_input(PacketProtocolLayer *ppl); +static void ssh1_connection_reconfigure(PacketProtocolLayer *ppl, Conf *conf); + +static const PacketProtocolLayerVtable ssh1_connection_vtable = { + .free = ssh1_connection_free, + .process_queue = ssh1_connection_process_queue, + .get_specials = ssh1_common_get_specials, + .special_cmd = ssh1_connection_special_cmd, + .want_user_input = ssh1_connection_want_user_input, + .got_user_input = ssh1_connection_got_user_input, + .reconfigure = ssh1_connection_reconfigure, + .queued_data_size = ssh_ppl_default_queued_data_size, + .name = NULL, /* no layer names in SSH-1 */ +}; + +static void ssh1_rportfwd_remove( + ConnectionLayer *cl, struct ssh_rportfwd *rpf); +static SshChannel *ssh1_lportfwd_open( + ConnectionLayer *cl, const char *hostname, int port, + const char *description, const SocketPeerInfo *pi, Channel *chan); +static struct X11FakeAuth *ssh1_add_x11_display( + ConnectionLayer *cl, int authtype, struct X11Display *disp); +static bool ssh1_agent_forwarding_permitted(ConnectionLayer *cl); +static void ssh1_terminal_size(ConnectionLayer *cl, int width, int height); +static void ssh1_stdout_unthrottle(ConnectionLayer *cl, size_t bufsize); +static size_t ssh1_stdin_backlog(ConnectionLayer *cl); +static void ssh1_throttle_all_channels(ConnectionLayer *cl, bool throttled); +static bool ssh1_ldisc_option(ConnectionLayer *cl, int option); +static void ssh1_set_ldisc_option(ConnectionLayer *cl, int option, bool value); +static void ssh1_enable_x_fwd(ConnectionLayer *cl); +static void ssh1_set_wants_user_input(ConnectionLayer *cl, bool wanted); + +static const ConnectionLayerVtable ssh1_connlayer_vtable = { + .rportfwd_alloc = ssh1_rportfwd_alloc, + .rportfwd_remove = ssh1_rportfwd_remove, + .lportfwd_open = ssh1_lportfwd_open, + .session_open = ssh1_session_open, + .serverside_x11_open = ssh1_serverside_x11_open, + .serverside_agent_open = ssh1_serverside_agent_open, + .add_x11_display = ssh1_add_x11_display, + .agent_forwarding_permitted = ssh1_agent_forwarding_permitted, + .terminal_size = ssh1_terminal_size, + .stdout_unthrottle = ssh1_stdout_unthrottle, + .stdin_backlog = ssh1_stdin_backlog, + .throttle_all_channels = ssh1_throttle_all_channels, + .ldisc_option = ssh1_ldisc_option, + .set_ldisc_option = ssh1_set_ldisc_option, + .enable_x_fwd = ssh1_enable_x_fwd, + .set_wants_user_input = ssh1_set_wants_user_input, + /* other methods are NULL */ +}; + +static size_t ssh1channel_write( + SshChannel *c, bool is_stderr, const void *buf, size_t len); +static void ssh1channel_write_eof(SshChannel *c); +static void ssh1channel_initiate_close(SshChannel *c, const char *err); +static void ssh1channel_unthrottle(SshChannel *c, size_t bufsize); +static Conf *ssh1channel_get_conf(SshChannel *c); +static void ssh1channel_window_override_removed(SshChannel *c) { /* ignore */ } + +static const SshChannelVtable ssh1channel_vtable = { + .write = ssh1channel_write, + .write_eof = ssh1channel_write_eof, + .initiate_close = ssh1channel_initiate_close, + .unthrottle = ssh1channel_unthrottle, + .get_conf = ssh1channel_get_conf, + .window_override_removed = ssh1channel_window_override_removed, + /* everything else is NULL */ +}; + +static void ssh1_channel_try_eof(struct ssh1_channel *c); +static void ssh1_channel_close_local(struct ssh1_channel *c, + const char *reason); +static void ssh1_channel_destroy(struct ssh1_channel *c); +static void ssh1_channel_check_close(struct ssh1_channel *c); + +static int ssh1_channelcmp(void *av, void *bv) +{ + const struct ssh1_channel *a = (const struct ssh1_channel *) av; + const struct ssh1_channel *b = (const struct ssh1_channel *) bv; + if (a->localid < b->localid) + return -1; + if (a->localid > b->localid) + return +1; + return 0; +} + +static int ssh1_channelfind(void *av, void *bv) +{ + const unsigned *a = (const unsigned *) av; + const struct ssh1_channel *b = (const struct ssh1_channel *) bv; + if (*a < b->localid) + return -1; + if (*a > b->localid) + return +1; + return 0; +} + +void ssh1_channel_free(struct ssh1_channel *c) +{ + if (c->chan) + chan_free(c->chan); + sfree(c); +} + +PacketProtocolLayer *ssh1_connection_new( + Ssh *ssh, Conf *conf, ConnectionLayer **cl_out) +{ + struct ssh1_connection_state *s = snew(struct ssh1_connection_state); + memset(s, 0, sizeof(*s)); + s->ppl.vt = &ssh1_connection_vtable; + + s->conf = conf_copy(conf); + + s->channels = newtree234(ssh1_channelcmp); + + s->x11authtree = newtree234(x11_authcmp); + + /* Need to get the log context for s->cl now, because we won't be + * helpfully notified when a copy is written into s->ppl by our + * owner. */ + s->cl.vt = &ssh1_connlayer_vtable; + s->cl.logctx = ssh_get_logctx(ssh); + + s->portfwdmgr = portfwdmgr_new(&s->cl); + s->rportfwds = newtree234(ssh1_rportfwd_cmp); + + *cl_out = &s->cl; + return &s->ppl; +} + +static void ssh1_connection_free(PacketProtocolLayer *ppl) +{ + struct ssh1_connection_state *s = + container_of(ppl, struct ssh1_connection_state, ppl); + struct X11FakeAuth *auth; + struct ssh1_channel *c; + struct ssh_rportfwd *rpf; + + conf_free(s->conf); + + while ((c = delpos234(s->channels, 0)) != NULL) + ssh1_channel_free(c); + freetree234(s->channels); + if (s->mainchan_chan) + chan_free(s->mainchan_chan); + + if (s->x11disp) + x11_free_display(s->x11disp); + while ((auth = delpos234(s->x11authtree, 0)) != NULL) + x11_free_fake_auth(auth); + freetree234(s->x11authtree); + + while ((rpf = delpos234(s->rportfwds, 0)) != NULL) + free_rportfwd(rpf); + freetree234(s->rportfwds); + portfwdmgr_free(s->portfwdmgr); + + if (s->antispoof_prompt) + free_prompts(s->antispoof_prompt); + + delete_callbacks_for_context(s); + + sfree(s); +} + +void ssh1_connection_set_protoflags(PacketProtocolLayer *ppl, + int local, int remote) +{ + assert(ppl->vt == &ssh1_connection_vtable); + struct ssh1_connection_state *s = + container_of(ppl, struct ssh1_connection_state, ppl); + s->local_protoflags = local; + s->remote_protoflags = remote; +} + +static bool ssh1_connection_filter_queue(struct ssh1_connection_state *s) +{ + PktIn *pktin; + ptrlen data; + struct ssh1_channel *c; + unsigned localid; + bool expect_halfopen; + + while (1) { + if (ssh1_common_filter_queue(&s->ppl)) + return true; + if ((pktin = pq_peek(s->ppl.in_pq)) == NULL) + return false; + + switch (pktin->type) { + case SSH1_MSG_CHANNEL_DATA: + case SSH1_MSG_CHANNEL_OPEN_CONFIRMATION: + case SSH1_MSG_CHANNEL_OPEN_FAILURE: + case SSH1_MSG_CHANNEL_CLOSE: + case SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION: + /* + * Common preliminary code for all the messages from the + * server that cite one of our channel ids: look up that + * channel id, check it exists, and if it's for a sharing + * downstream, pass it on. + */ + localid = get_uint32(pktin); + c = find234(s->channels, &localid, ssh1_channelfind); + + expect_halfopen = ( + pktin->type == SSH1_MSG_CHANNEL_OPEN_CONFIRMATION || + pktin->type == SSH1_MSG_CHANNEL_OPEN_FAILURE); + + if (!c || c->halfopen != expect_halfopen) { + ssh_remote_error( + s->ppl.ssh, "Received %s for %s channel %u", + ssh1_pkt_type(pktin->type), + !c ? "nonexistent" : c->halfopen ? "half-open" : "open", + localid); + return true; + } + + switch (pktin->type) { + case SSH1_MSG_CHANNEL_OPEN_CONFIRMATION: + assert(c->halfopen); + c->remoteid = get_uint32(pktin); + c->halfopen = false; + c->throttling_conn = false; + + chan_open_confirmation(c->chan); + + /* + * Now that the channel is fully open, it's possible + * in principle to immediately close it. Check whether + * it wants us to! + * + * This can occur if a local socket error occurred + * between us sending out CHANNEL_OPEN and receiving + * OPEN_CONFIRMATION. If that happens, all we can do + * is immediately initiate close proceedings now that + * we know the server's id to put in the close + * message. We'll have handled that in this code by + * having already turned c->chan into a zombie, so its + * want_close method (which ssh1_channel_check_close + * will consult) will already be returning true. + */ + ssh1_channel_check_close(c); + + if (c->pending_eof) + ssh1_channel_try_eof(c); /* in case we had a pending EOF */ + break; + + case SSH1_MSG_CHANNEL_OPEN_FAILURE: + assert(c->halfopen); + + chan_open_failed(c->chan, NULL); + chan_free(c->chan); + + del234(s->channels, c); + ssh1_channel_free(c); + break; + + case SSH1_MSG_CHANNEL_DATA: + data = get_string(pktin); + if (!get_err(pktin)) { + int bufsize = chan_send( + c->chan, false, data.ptr, data.len); + + if (!c->throttling_conn && bufsize > SSH1_BUFFER_LIMIT) { + c->throttling_conn = true; + ssh_throttle_conn(s->ppl.ssh, +1); + } + } + break; + + case SSH1_MSG_CHANNEL_CLOSE: + if (!(c->closes & CLOSES_RCVD_CLOSE)) { + c->closes |= CLOSES_RCVD_CLOSE; + chan_send_eof(c->chan); + ssh1_channel_check_close(c); + } + break; + + case SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION: + if (!(c->closes & CLOSES_RCVD_CLOSECONF)) { + if (!(c->closes & CLOSES_SENT_CLOSE)) { + ssh_remote_error( + s->ppl.ssh, + "Received CHANNEL_CLOSE_CONFIRMATION for channel" + " %u for which we never sent CHANNEL_CLOSE\n", + c->localid); + return true; + } + + c->closes |= CLOSES_RCVD_CLOSECONF; + ssh1_channel_check_close(c); + } + break; + } + + pq_pop(s->ppl.in_pq); + break; + + default: + if (ssh1_handle_direction_specific_packet(s, pktin)) { + pq_pop(s->ppl.in_pq); + if (ssh1_check_termination(s)) + return true; + } else { + return false; + } + } + } +} + +static PktIn *ssh1_connection_pop(struct ssh1_connection_state *s) +{ + ssh1_connection_filter_queue(s); + return pq_pop(s->ppl.in_pq); +} + +static void ssh1_connection_process_queue(PacketProtocolLayer *ppl) +{ + struct ssh1_connection_state *s = + container_of(ppl, struct ssh1_connection_state, ppl); + PktIn *pktin; + + if (ssh1_connection_filter_queue(s)) /* no matter why we were called */ + return; + + crBegin(s->crState); + + /* + * Signal the seat that authentication is done, so that it can + * deploy spoofing defences. If it doesn't have any, deploy our + * own fallback one. + * + * We do this here rather than at the end of userauth, because we + * might not have gone through userauth at all (if we're a + * connection-sharing downstream). + */ + if (ssh1_connection_need_antispoof_prompt(s)) { + s->antispoof_prompt = new_prompts(); + s->antispoof_prompt->to_server = true; + s->antispoof_prompt->from_server = false; + s->antispoof_prompt->name = dupstr("Authentication successful"); + add_prompt( + s->antispoof_prompt, + dupstr("Access granted. Press Return to begin session. "), false); + s->antispoof_ret = seat_get_userpass_input( + s->ppl.seat, s->antispoof_prompt, NULL); + while (1) { + while (s->antispoof_ret < 0 && + bufchain_size(s->ppl.user_input) > 0) + s->antispoof_ret = seat_get_userpass_input( + s->ppl.seat, s->antispoof_prompt, s->ppl.user_input); + + if (s->antispoof_ret >= 0) + break; + + s->want_user_input = true; + crReturnV; + s->want_user_input = false; + } + free_prompts(s->antispoof_prompt); + s->antispoof_prompt = NULL; + } + + portfwdmgr_config(s->portfwdmgr, s->conf); + s->portfwdmgr_configured = true; + + while (!s->finished_setup) { + ssh1_connection_direction_specific_setup(s); + crReturnV; + } + + while (1) { + + /* + * By this point, most incoming packets are already being + * handled by filter_queue, and we need only pay attention to + * the unusual ones. + */ + + if ((pktin = ssh1_connection_pop(s)) != NULL) { + ssh_proto_error(s->ppl.ssh, "Unexpected packet received, " + "type %d (%s)", pktin->type, + ssh1_pkt_type(pktin->type)); + return; + } + crReturnV; + } + + crFinishV; +} + +static void ssh1_channel_check_close(struct ssh1_channel *c) +{ + struct ssh1_connection_state *s = c->connlayer; + PktOut *pktout; + + if (c->halfopen) { + /* + * If we've sent out our own CHANNEL_OPEN but not yet seen + * either OPEN_CONFIRMATION or OPEN_FAILURE in response, then + * it's too early to be sending close messages of any kind. + */ + return; + } + + if ((!((CLOSES_SENT_CLOSE | CLOSES_RCVD_CLOSE) & ~c->closes) || + chan_want_close(c->chan, (c->closes & CLOSES_SENT_CLOSE), + (c->closes & CLOSES_RCVD_CLOSE))) && + !(c->closes & CLOSES_SENT_CLOSECONF)) { + /* + * We have both sent and received CLOSE (or the channel type + * doesn't need us to), which means the channel is in final + * wind-up. Send CLOSE and/or CLOSE_CONFIRMATION, whichever we + * haven't sent yet. + */ + if (!(c->closes & CLOSES_SENT_CLOSE)) { + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_MSG_CHANNEL_CLOSE); + put_uint32(pktout, c->remoteid); + pq_push(s->ppl.out_pq, pktout); + c->closes |= CLOSES_SENT_CLOSE; + } + if (c->closes & CLOSES_RCVD_CLOSE) { + pktout = ssh_bpp_new_pktout( + s->ppl.bpp, SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION); + put_uint32(pktout, c->remoteid); + pq_push(s->ppl.out_pq, pktout); + c->closes |= CLOSES_SENT_CLOSECONF; + } + } + + if (!((CLOSES_SENT_CLOSECONF | CLOSES_RCVD_CLOSECONF) & ~c->closes)) { + /* + * We have both sent and received CLOSE_CONFIRMATION, which + * means we're completely done with the channel. + */ + ssh1_channel_destroy(c); + } +} + +static void ssh1_channel_try_eof(struct ssh1_channel *c) +{ + struct ssh1_connection_state *s = c->connlayer; + PktOut *pktout; + assert(c->pending_eof); /* precondition for calling us */ + if (c->halfopen) + return; /* can't close: not even opened yet */ + + c->pending_eof = false; /* we're about to send it */ + + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_MSG_CHANNEL_CLOSE); + put_uint32(pktout, c->remoteid); + pq_push(s->ppl.out_pq, pktout); + c->closes |= CLOSES_SENT_CLOSE; + + ssh1_channel_check_close(c); +} + +/* + * Close any local socket and free any local resources associated with + * a channel. This converts the channel into a zombie. + */ +static void ssh1_channel_close_local(struct ssh1_channel *c, + const char *reason) +{ + struct ssh1_connection_state *s = c->connlayer; + PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ + char *msg = chan_log_close_msg(c->chan); + + if (msg != NULL) { + ppl_logevent("%s%s%s", msg, reason ? " " : "", reason ? reason : ""); + sfree(msg); + } + + chan_free(c->chan); + c->chan = zombiechan_new(); +} + +static void ssh1_check_termination_callback(void *vctx) +{ + struct ssh1_connection_state *s = (struct ssh1_connection_state *)vctx; + ssh1_check_termination(s); +} + +static void ssh1_channel_destroy(struct ssh1_channel *c) +{ + struct ssh1_connection_state *s = c->connlayer; + + ssh1_channel_close_local(c, NULL); + del234(s->channels, c); + ssh1_channel_free(c); + + /* + * If that was the last channel left open, we might need to + * terminate. But we'll be a bit cautious, by doing that in a + * toplevel callback, just in case anything on the current call + * stack objects to this entire PPL being freed. + */ + queue_toplevel_callback(ssh1_check_termination_callback, s); +} + +bool ssh1_check_termination(struct ssh1_connection_state *s) +{ + /* + * Decide whether we should terminate the SSH connection now. + * Called after a channel goes away, or when the main session + * returns SSH1_SMSG_EXIT_STATUS; we terminate when none of either + * is left. + */ + if (s->session_terminated && count234(s->channels) == 0) { + PktOut *pktout = ssh_bpp_new_pktout( + s->ppl.bpp, SSH1_CMSG_EXIT_CONFIRMATION); + pq_push(s->ppl.out_pq, pktout); + + ssh_user_close(s->ppl.ssh, "Session finished"); + return true; + } + + return false; +} + +/* + * Set up most of a new ssh1_channel. Leaves chan untouched (since it + * will sometimes have been filled in before calling this). + */ +void ssh1_channel_init(struct ssh1_channel *c) +{ + struct ssh1_connection_state *s = c->connlayer; + c->closes = 0; + c->pending_eof = false; + c->throttling_conn = false; + c->sc.vt = &ssh1channel_vtable; + c->sc.cl = &s->cl; + c->localid = alloc_channel_id(s->channels, struct ssh1_channel); + add234(s->channels, c); +} + +static Conf *ssh1channel_get_conf(SshChannel *sc) +{ + struct ssh1_channel *c = container_of(sc, struct ssh1_channel, sc); + struct ssh1_connection_state *s = c->connlayer; + return s->conf; +} + +static void ssh1channel_write_eof(SshChannel *sc) +{ + struct ssh1_channel *c = container_of(sc, struct ssh1_channel, sc); + + if (c->closes & CLOSES_SENT_CLOSE) + return; + + c->pending_eof = true; + ssh1_channel_try_eof(c); +} + +static void ssh1channel_initiate_close(SshChannel *sc, const char *err) +{ + struct ssh1_channel *c = container_of(sc, struct ssh1_channel, sc); + char *reason; + + reason = err ? dupprintf("due to local error: %s", err) : NULL; + ssh1_channel_close_local(c, reason); + sfree(reason); + c->pending_eof = false; /* this will confuse a zombie channel */ + + ssh1_channel_check_close(c); +} + +static void ssh1channel_unthrottle(SshChannel *sc, size_t bufsize) +{ + struct ssh1_channel *c = container_of(sc, struct ssh1_channel, sc); + struct ssh1_connection_state *s = c->connlayer; + + if (c->throttling_conn && bufsize <= SSH1_BUFFER_LIMIT) { + c->throttling_conn = false; + ssh_throttle_conn(s->ppl.ssh, -1); + } +} + +static size_t ssh1channel_write( + SshChannel *sc, bool is_stderr, const void *buf, size_t len) +{ + struct ssh1_channel *c = container_of(sc, struct ssh1_channel, sc); + struct ssh1_connection_state *s = c->connlayer; + + assert(!(c->closes & CLOSES_SENT_CLOSE)); + + PktOut *pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_MSG_CHANNEL_DATA); + put_uint32(pktout, c->remoteid); + put_string(pktout, buf, len); + pq_push(s->ppl.out_pq, pktout); + + /* + * In SSH-1 we can return 0 here - implying that channels 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; +} + +static struct X11FakeAuth *ssh1_add_x11_display( + ConnectionLayer *cl, int authtype, struct X11Display *disp) +{ + struct ssh1_connection_state *s = + container_of(cl, struct ssh1_connection_state, cl); + struct X11FakeAuth *auth = x11_invent_fake_auth(s->x11authtree, authtype); + auth->disp = disp; + return auth; +} + +static SshChannel *ssh1_lportfwd_open( + ConnectionLayer *cl, const char *hostname, int port, + const char *description, const SocketPeerInfo *pi, Channel *chan) +{ + struct ssh1_connection_state *s = + container_of(cl, struct ssh1_connection_state, cl); + PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ + struct ssh1_channel *c = snew(struct ssh1_channel); + PktOut *pktout; + + c->connlayer = s; + ssh1_channel_init(c); + c->halfopen = true; + c->chan = chan; + + ppl_logevent("Opening connection to %s:%d for %s", + hostname, port, description); + + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_MSG_PORT_OPEN); + put_uint32(pktout, c->localid); + put_stringz(pktout, hostname); + put_uint32(pktout, port); + /* originator string would go here, but we didn't specify + * SSH_PROTOFLAG_HOST_IN_FWD_OPEN */ + pq_push(s->ppl.out_pq, pktout); + + return &c->sc; +} + +static void ssh1_rportfwd_remove(ConnectionLayer *cl, struct ssh_rportfwd *rpf) +{ + /* + * We cannot cancel listening ports on the server side in SSH-1! + * There's no message to support it. + */ +} + +static bool ssh1_agent_forwarding_permitted(ConnectionLayer *cl) +{ + struct ssh1_connection_state *s = + container_of(cl, struct ssh1_connection_state, cl); + return conf_get_bool(s->conf, CONF_agentfwd) && agent_exists(); +} + +static void ssh1_connection_special_cmd(PacketProtocolLayer *ppl, + SessionSpecialCode code, int arg) +{ + struct ssh1_connection_state *s = + container_of(ppl, struct ssh1_connection_state, ppl); + PktOut *pktout; + + if (code == SS_PING || code == SS_NOP) { + if (!(s->ppl.remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE)) { + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_MSG_IGNORE); + put_stringz(pktout, ""); + pq_push(s->ppl.out_pq, pktout); + } + } else if (s->mainchan) { + mainchan_special_cmd(s->mainchan, code, arg); + } +} + +static void ssh1_terminal_size(ConnectionLayer *cl, int width, int height) +{ + struct ssh1_connection_state *s = + container_of(cl, struct ssh1_connection_state, cl); + + s->term_width = width; + s->term_height = height; + if (s->mainchan) + mainchan_terminal_size(s->mainchan, width, height); +} + +static void ssh1_stdout_unthrottle(ConnectionLayer *cl, size_t bufsize) +{ + struct ssh1_connection_state *s = + container_of(cl, struct ssh1_connection_state, cl); + + if (s->stdout_throttling && bufsize < SSH1_BUFFER_LIMIT) { + s->stdout_throttling = false; + ssh_throttle_conn(s->ppl.ssh, -1); + } +} + +static size_t ssh1_stdin_backlog(ConnectionLayer *cl) +{ + return 0; +} + +static void ssh1_throttle_all_channels(ConnectionLayer *cl, bool throttled) +{ + struct ssh1_connection_state *s = + container_of(cl, struct ssh1_connection_state, cl); + struct ssh1_channel *c; + int i; + + for (i = 0; NULL != (c = index234(s->channels, i)); i++) + chan_set_input_wanted(c->chan, !throttled); +} + +static bool ssh1_ldisc_option(ConnectionLayer *cl, int option) +{ + struct ssh1_connection_state *s = + container_of(cl, struct ssh1_connection_state, cl); + + return s->ldisc_opts[option]; +} + +static void ssh1_set_ldisc_option(ConnectionLayer *cl, int option, bool value) +{ + struct ssh1_connection_state *s = + container_of(cl, struct ssh1_connection_state, cl); + + s->ldisc_opts[option] = value; +} + +static void ssh1_enable_x_fwd(ConnectionLayer *cl) +{ + struct ssh1_connection_state *s = + container_of(cl, struct ssh1_connection_state, cl); + + s->X11_fwd_enabled = true; +} + +static void ssh1_set_wants_user_input(ConnectionLayer *cl, bool wanted) +{ + struct ssh1_connection_state *s = + container_of(cl, struct ssh1_connection_state, cl); + + s->want_user_input = wanted; + s->finished_setup = true; +} + +static bool ssh1_connection_want_user_input(PacketProtocolLayer *ppl) +{ + struct ssh1_connection_state *s = + container_of(ppl, struct ssh1_connection_state, ppl); + + return s->want_user_input; +} + +static void ssh1_connection_got_user_input(PacketProtocolLayer *ppl) +{ + struct ssh1_connection_state *s = + container_of(ppl, struct ssh1_connection_state, ppl); + + while (s->mainchan && bufchain_size(s->ppl.user_input) > 0) { + /* + * Add user input to the main channel's buffer. + */ + ptrlen data = bufchain_prefix(s->ppl.user_input); + if (data.len > 512) + data.len = 512; + sshfwd_write(&s->mainchan_sc, data.ptr, data.len); + bufchain_consume(s->ppl.user_input, data.len); + } +} + +static void ssh1_connection_reconfigure(PacketProtocolLayer *ppl, Conf *conf) +{ + struct ssh1_connection_state *s = + container_of(ppl, struct ssh1_connection_state, ppl); + + conf_free(s->conf); + s->conf = conf_copy(conf); + + if (s->portfwdmgr_configured) + portfwdmgr_config(s->portfwdmgr, s->conf); +} diff --git a/ssh/connection1.h b/ssh/connection1.h new file mode 100644 index 00000000..44370787 --- /dev/null +++ b/ssh/connection1.h @@ -0,0 +1,123 @@ +struct ssh1_channel; + +struct outstanding_succfail; + +struct ssh1_connection_state { + int crState; + + Conf *conf; + int local_protoflags, remote_protoflags; + + tree234 *channels; /* indexed by local id */ + + /* In SSH-1, the main session doesn't take the form of a 'channel' + * according to the wire protocol. But we want to use the same API + * for it, so we define an SshChannel here - but one that uses a + * separate vtable from the usual one, so it doesn't map to a + * struct ssh1_channel as all the others do. */ + SshChannel mainchan_sc; + Channel *mainchan_chan; /* the other end of mainchan_sc */ + mainchan *mainchan; /* and its subtype */ + + bool got_pty; + bool ldisc_opts[LD_N_OPTIONS]; + bool stdout_throttling; + bool want_user_input; + bool session_terminated; + int term_width, term_height, term_width_orig, term_height_orig; + + bool X11_fwd_enabled; + struct X11Display *x11disp; + struct X11FakeAuth *x11auth; + tree234 *x11authtree; + + tree234 *rportfwds; + PortFwdManager *portfwdmgr; + bool portfwdmgr_configured; + + bool finished_setup; + + /* + * These store the list of requests that we're waiting for + * SSH_SMSG_{SUCCESS,FAILURE} replies to. (Those messages don't + * come with any indication of what they're in response to, so we + * have to keep track of the queue ourselves.) + */ + struct outstanding_succfail *succfail_head, *succfail_tail; + + bool compressing; /* used in server mode only */ + bool sent_exit_status; /* also for server mode */ + + prompts_t *antispoof_prompt; + int antispoof_ret; + + const SshServerConfig *ssc; + + ConnectionLayer cl; + PacketProtocolLayer ppl; +}; + +struct ssh1_channel { + struct ssh1_connection_state *connlayer; + + unsigned remoteid, localid; + int type; + /* True if we opened this channel but server hasn't confirmed. */ + bool halfopen; + + /* Bitmap of whether we've sent/received CHANNEL_CLOSE and + * CHANNEL_CLOSE_CONFIRMATION. */ +#define CLOSES_SENT_CLOSE 1 +#define CLOSES_SENT_CLOSECONF 2 +#define CLOSES_RCVD_CLOSE 4 +#define CLOSES_RCVD_CLOSECONF 8 + int closes; + + /* + * This flag indicates that an EOF is pending on the outgoing side + * of the channel: that is, wherever we're getting the data for + * this channel has sent us some data followed by EOF. We can't + * actually send the EOF until we've finished sending the data, so + * we set this flag instead to remind us to do so once our buffer + * is clear. + */ + bool pending_eof; + + /* + * True if this channel is causing the underlying connection to be + * throttled. + */ + bool throttling_conn; + + /* + * True if we currently have backed-up data on the direction of + * this channel pointing out of the SSH connection, and therefore + * would prefer the 'Channel' implementation not to read further + * local input if possible. + */ + bool throttled_by_backlog; + + Channel *chan; /* handle the client side of this channel, if not */ + SshChannel sc; /* entry point for chan to talk back to */ +}; + +SshChannel *ssh1_session_open(ConnectionLayer *cl, Channel *chan); +void ssh1_channel_init(struct ssh1_channel *c); +void ssh1_channel_free(struct ssh1_channel *c); +struct ssh_rportfwd *ssh1_rportfwd_alloc( + ConnectionLayer *cl, + const char *shost, int sport, const char *dhost, int dport, + int addressfamily, const char *log_description, PortFwdRecord *pfr, + ssh_sharing_connstate *share_ctx); +SshChannel *ssh1_serverside_x11_open( + ConnectionLayer *cl, Channel *chan, const SocketPeerInfo *pi); +SshChannel *ssh1_serverside_agent_open(ConnectionLayer *cl, Channel *chan); + +void ssh1_connection_direction_specific_setup( + struct ssh1_connection_state *s); +bool ssh1_handle_direction_specific_packet( + struct ssh1_connection_state *s, PktIn *pktin); + +bool ssh1_check_termination(struct ssh1_connection_state *s); + +bool ssh1_connection_need_antispoof_prompt(struct ssh1_connection_state *s); diff --git a/ssh/connection2-client.c b/ssh/connection2-client.c new file mode 100644 index 00000000..b07e1eb2 --- /dev/null +++ b/ssh/connection2-client.c @@ -0,0 +1,505 @@ +/* + * Client-specific parts of the SSH-2 connection layer. + */ + +#include + +#include "putty.h" +#include "ssh.h" +#include "bpp.h" +#include "ppl.h" +#include "channel.h" +#include "sshcr.h" +#include "connection2.h" + +static ChanopenResult chan_open_x11( + struct ssh2_connection_state *s, SshChannel *sc, + ptrlen peeraddr, int peerport) +{ + PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ + char *peeraddr_str; + Channel *ch; + + ppl_logevent("Received X11 connect request from %.*s:%d", + PTRLEN_PRINTF(peeraddr), peerport); + + if (!s->X11_fwd_enabled && !s->connshare) { + CHANOPEN_RETURN_FAILURE( + SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED, + ("X11 forwarding is not enabled")); + } + + peeraddr_str = peeraddr.ptr ? mkstr(peeraddr) : NULL; + ch = x11_new_channel( + s->x11authtree, sc, peeraddr_str, peerport, s->connshare != NULL); + sfree(peeraddr_str); + ppl_logevent("Opened X11 forward channel"); + CHANOPEN_RETURN_SUCCESS(ch); +} + +static ChanopenResult chan_open_forwarded_tcpip( + struct ssh2_connection_state *s, SshChannel *sc, + ptrlen fwdaddr, int fwdport, ptrlen peeraddr, int peerport) +{ + PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ + struct ssh_rportfwd pf, *realpf; + Channel *ch; + char *err; + + ppl_logevent("Received remote port %.*s:%d open request from %.*s:%d", + PTRLEN_PRINTF(fwdaddr), fwdport, + PTRLEN_PRINTF(peeraddr), peerport); + + pf.shost = mkstr(fwdaddr); + pf.sport = fwdport; + realpf = find234(s->rportfwds, &pf, NULL); + sfree(pf.shost); + + if (realpf == NULL) { + CHANOPEN_RETURN_FAILURE( + SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED, + ("Remote port is not recognised")); + } + + if (realpf->share_ctx) { + /* + * This port forwarding is on behalf of a connection-sharing + * downstream. + */ + CHANOPEN_RETURN_DOWNSTREAM(realpf->share_ctx); + } + + err = portfwdmgr_connect( + s->portfwdmgr, &ch, realpf->dhost, realpf->dport, + sc, realpf->addressfamily); + ppl_logevent("Attempting to forward remote port to %s:%d", + realpf->dhost, realpf->dport); + if (err != NULL) { + ppl_logevent("Port open failed: %s", err); + sfree(err); + CHANOPEN_RETURN_FAILURE( + SSH2_OPEN_CONNECT_FAILED, + ("Port open failed")); + } + + ppl_logevent("Forwarded port opened successfully"); + CHANOPEN_RETURN_SUCCESS(ch); +} + +static ChanopenResult chan_open_auth_agent( + struct ssh2_connection_state *s, SshChannel *sc) +{ + if (!ssh_agent_forwarding_permitted(&s->cl)) { + CHANOPEN_RETURN_FAILURE( + SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED, + ("Agent forwarding is not enabled")); + } + + /* + * If possible, make a stream-oriented connection to the agent and + * set up an ordinary port-forwarding type channel over it. + */ + Plug *plug; + Channel *ch = portfwd_raw_new(&s->cl, &plug, true); + Socket *skt = agent_connect(plug); + + if (!sk_socket_error(skt)) { + portfwd_raw_setup(ch, skt, sc); + CHANOPEN_RETURN_SUCCESS(ch); + } else { + portfwd_raw_free(ch); + /* + * Otherwise, fall back to the old-fashioned system of parsing the + * forwarded data stream ourselves for message boundaries, and + * passing each individual message to the one-off agent_query(). + */ + CHANOPEN_RETURN_SUCCESS(agentf_new(sc)); + } +} + +ChanopenResult ssh2_connection_parse_channel_open( + struct ssh2_connection_state *s, ptrlen type, + PktIn *pktin, SshChannel *sc) +{ + if (ptrlen_eq_string(type, "x11")) { + ptrlen peeraddr = get_string(pktin); + int peerport = get_uint32(pktin); + + return chan_open_x11(s, sc, peeraddr, peerport); + } else if (ptrlen_eq_string(type, "forwarded-tcpip")) { + ptrlen fwdaddr = get_string(pktin); + int fwdport = toint(get_uint32(pktin)); + ptrlen peeraddr = get_string(pktin); + int peerport = toint(get_uint32(pktin)); + + return chan_open_forwarded_tcpip( + s, sc, fwdaddr, fwdport, peeraddr, peerport); + } else if (ptrlen_eq_string(type, "auth-agent@openssh.com")) { + return chan_open_auth_agent(s, sc); + } else { + CHANOPEN_RETURN_FAILURE( + SSH2_OPEN_UNKNOWN_CHANNEL_TYPE, + ("Unsupported channel type requested")); + } +} + +bool ssh2_connection_parse_global_request( + struct ssh2_connection_state *s, ptrlen type, PktIn *pktin) +{ + /* + * We don't know of any global requests that an SSH client needs + * to honour. + */ + return false; +} + +PktOut *ssh2_portfwd_chanopen( + struct ssh2_connection_state *s, struct ssh2_channel *c, + const char *hostname, int port, + const char *description, const SocketPeerInfo *peerinfo) +{ + PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ + PktOut *pktout; + + /* + * In client mode, this function is called by portfwdmgr in + * response to PortListeners that were set up in + * portfwdmgr_config, which means that the hostname and port + * parameters will indicate the host we want to tell the server to + * connect _to_. + */ + + ppl_logevent("Opening connection to %s:%d for %s", + hostname, port, description); + + pktout = ssh2_chanopen_init(c, "direct-tcpip"); + { + char *trimmed_host = host_strduptrim(hostname); + put_stringz(pktout, trimmed_host); + sfree(trimmed_host); + } + put_uint32(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. + */ + put_stringz(pktout, "0.0.0.0"); + put_uint32(pktout, 0); + + return pktout; +} + +static int ssh2_rportfwd_cmp(void *av, void *bv) +{ + struct ssh_rportfwd *a = (struct ssh_rportfwd *) av; + struct ssh_rportfwd *b = (struct ssh_rportfwd *) bv; + int i; + if ( (i = strcmp(a->shost, b->shost)) != 0) + return i < 0 ? -1 : +1; + if (a->sport > b->sport) + return +1; + if (a->sport < b->sport) + return -1; + return 0; +} + +static void ssh2_rportfwd_globreq_response(struct ssh2_connection_state *s, + PktIn *pktin, void *ctx) +{ + PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ + struct ssh_rportfwd *rpf = (struct ssh_rportfwd *)ctx; + + if (pktin->type == SSH2_MSG_REQUEST_SUCCESS) { + ppl_logevent("Remote port forwarding from %s enabled", + rpf->log_description); + } else { + ppl_logevent("Remote port forwarding from %s refused", + rpf->log_description); + + struct ssh_rportfwd *realpf = del234(s->rportfwds, rpf); + assert(realpf == rpf); + portfwdmgr_close(s->portfwdmgr, rpf->pfr); + free_rportfwd(rpf); + } +} + +struct ssh_rportfwd *ssh2_rportfwd_alloc( + ConnectionLayer *cl, + const char *shost, int sport, const char *dhost, int dport, + int addressfamily, const char *log_description, PortFwdRecord *pfr, + ssh_sharing_connstate *share_ctx) +{ + struct ssh2_connection_state *s = + container_of(cl, struct ssh2_connection_state, cl); + struct ssh_rportfwd *rpf = snew(struct ssh_rportfwd); + + if (!s->rportfwds) + s->rportfwds = newtree234(ssh2_rportfwd_cmp); + + rpf->shost = dupstr(shost); + rpf->sport = sport; + rpf->dhost = dupstr(dhost); + rpf->dport = dport; + rpf->addressfamily = addressfamily; + rpf->log_description = dupstr(log_description); + rpf->pfr = pfr; + rpf->share_ctx = share_ctx; + + if (add234(s->rportfwds, rpf) != rpf) { + free_rportfwd(rpf); + return NULL; + } + + if (!rpf->share_ctx) { + PktOut *pktout = ssh_bpp_new_pktout( + s->ppl.bpp, SSH2_MSG_GLOBAL_REQUEST); + put_stringz(pktout, "tcpip-forward"); + put_bool(pktout, true); /* want reply */ + put_stringz(pktout, rpf->shost); + put_uint32(pktout, rpf->sport); + pq_push(s->ppl.out_pq, pktout); + + ssh2_queue_global_request_handler( + s, ssh2_rportfwd_globreq_response, rpf); + } + + return rpf; +} + +void ssh2_rportfwd_remove(ConnectionLayer *cl, struct ssh_rportfwd *rpf) +{ + struct ssh2_connection_state *s = + container_of(cl, struct ssh2_connection_state, cl); + + if (rpf->share_ctx) { + /* + * We don't manufacture a cancel-tcpip-forward message for + * remote port forwardings being removed on behalf of a + * downstream; we just pass through the one the downstream + * sent to us. + */ + } else { + PktOut *pktout = ssh_bpp_new_pktout( + s->ppl.bpp, SSH2_MSG_GLOBAL_REQUEST); + put_stringz(pktout, "cancel-tcpip-forward"); + put_bool(pktout, false); /* _don't_ want reply */ + put_stringz(pktout, rpf->shost); + put_uint32(pktout, rpf->sport); + pq_push(s->ppl.out_pq, pktout); + } + + assert(s->rportfwds); + struct ssh_rportfwd *realpf = del234(s->rportfwds, rpf); + assert(realpf == rpf); + free_rportfwd(rpf); +} + +SshChannel *ssh2_session_open(ConnectionLayer *cl, Channel *chan) +{ + struct ssh2_connection_state *s = + container_of(cl, struct ssh2_connection_state, cl); + PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ + struct ssh2_channel *c = snew(struct ssh2_channel); + PktOut *pktout; + + c->connlayer = s; + ssh2_channel_init(c); + c->halfopen = true; + c->chan = chan; + + ppl_logevent("Opening main session channel"); + + pktout = ssh2_chanopen_init(c, "session"); + pq_push(s->ppl.out_pq, pktout); + + return &c->sc; +} + +SshChannel *ssh2_serverside_x11_open( + ConnectionLayer *cl, Channel *chan, const SocketPeerInfo *pi) +{ + unreachable("Should never be called in the client"); +} + +SshChannel *ssh2_serverside_agent_open(ConnectionLayer *cl, Channel *chan) +{ + unreachable("Should never be called in the client"); +} + +static void ssh2_channel_response( + struct ssh2_channel *c, PktIn *pkt, void *ctx) +{ + /* If pkt==NULL (because this handler has been called in response + * to CHANNEL_CLOSE arriving while the request was still + * outstanding), we treat that the same as CHANNEL_FAILURE. */ + chan_request_response(c->chan, + pkt && pkt->type == SSH2_MSG_CHANNEL_SUCCESS); +} + +void ssh2channel_start_shell(SshChannel *sc, bool want_reply) +{ + struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc); + struct ssh2_connection_state *s = c->connlayer; + + PktOut *pktout = ssh2_chanreq_init( + c, "shell", want_reply ? ssh2_channel_response : NULL, NULL); + pq_push(s->ppl.out_pq, pktout); +} + +void ssh2channel_start_command( + SshChannel *sc, bool want_reply, const char *command) +{ + struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc); + struct ssh2_connection_state *s = c->connlayer; + + PktOut *pktout = ssh2_chanreq_init( + c, "exec", want_reply ? ssh2_channel_response : NULL, NULL); + put_stringz(pktout, command); + pq_push(s->ppl.out_pq, pktout); +} + +bool ssh2channel_start_subsystem( + SshChannel *sc, bool want_reply, const char *subsystem) +{ + struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc); + struct ssh2_connection_state *s = c->connlayer; + + PktOut *pktout = ssh2_chanreq_init( + c, "subsystem", want_reply ? ssh2_channel_response : NULL, NULL); + put_stringz(pktout, subsystem); + pq_push(s->ppl.out_pq, pktout); + + return true; +} + +void ssh2channel_send_exit_status(SshChannel *sc, int status) +{ + unreachable("Should never be called in the client"); +} + +void ssh2channel_send_exit_signal( + SshChannel *sc, ptrlen signame, bool core_dumped, ptrlen msg) +{ + unreachable("Should never be called in the client"); +} + +void ssh2channel_send_exit_signal_numeric( + SshChannel *sc, int signum, bool core_dumped, ptrlen msg) +{ + unreachable("Should never be called in the client"); +} + +void ssh2channel_request_x11_forwarding( + SshChannel *sc, bool want_reply, const char *authproto, + const char *authdata, int screen_number, bool oneshot) +{ + struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc); + struct ssh2_connection_state *s = c->connlayer; + + PktOut *pktout = ssh2_chanreq_init( + c, "x11-req", want_reply ? ssh2_channel_response : NULL, NULL); + put_bool(pktout, oneshot); + put_stringz(pktout, authproto); + put_stringz(pktout, authdata); + put_uint32(pktout, screen_number); + pq_push(s->ppl.out_pq, pktout); +} + +void ssh2channel_request_agent_forwarding(SshChannel *sc, bool want_reply) +{ + struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc); + struct ssh2_connection_state *s = c->connlayer; + + PktOut *pktout = ssh2_chanreq_init( + c, "auth-agent-req@openssh.com", + want_reply ? ssh2_channel_response : NULL, NULL); + pq_push(s->ppl.out_pq, pktout); +} + +void ssh2channel_request_pty( + SshChannel *sc, bool want_reply, Conf *conf, int w, int h) +{ + struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc); + struct ssh2_connection_state *s = c->connlayer; + strbuf *modebuf; + + PktOut *pktout = ssh2_chanreq_init( + c, "pty-req", want_reply ? ssh2_channel_response : NULL, NULL); + put_stringz(pktout, conf_get_str(conf, CONF_termtype)); + put_uint32(pktout, w); + put_uint32(pktout, h); + put_uint32(pktout, 0); /* pixel width */ + put_uint32(pktout, 0); /* pixel height */ + modebuf = strbuf_new(); + write_ttymodes_to_packet( + BinarySink_UPCAST(modebuf), 2, + get_ttymodes_from_conf(s->ppl.seat, conf)); + put_stringsb(pktout, modebuf); + pq_push(s->ppl.out_pq, pktout); +} + +bool ssh2channel_send_env_var( + SshChannel *sc, bool want_reply, const char *var, const char *value) +{ + struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc); + struct ssh2_connection_state *s = c->connlayer; + + PktOut *pktout = ssh2_chanreq_init( + c, "env", want_reply ? ssh2_channel_response : NULL, NULL); + put_stringz(pktout, var); + put_stringz(pktout, value); + pq_push(s->ppl.out_pq, pktout); + + return true; +} + +bool ssh2channel_send_serial_break(SshChannel *sc, bool want_reply, int length) +{ + struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc); + struct ssh2_connection_state *s = c->connlayer; + + PktOut *pktout = ssh2_chanreq_init( + c, "break", want_reply ? ssh2_channel_response : NULL, NULL); + put_uint32(pktout, length); + pq_push(s->ppl.out_pq, pktout); + + return true; +} + +bool ssh2channel_send_signal( + SshChannel *sc, bool want_reply, const char *signame) +{ + struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc); + struct ssh2_connection_state *s = c->connlayer; + + PktOut *pktout = ssh2_chanreq_init( + c, "signal", want_reply ? ssh2_channel_response : NULL, NULL); + put_stringz(pktout, signame); + pq_push(s->ppl.out_pq, pktout); + + return true; +} + +void ssh2channel_send_terminal_size_change(SshChannel *sc, int w, int h) +{ + struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc); + struct ssh2_connection_state *s = c->connlayer; + + PktOut *pktout = ssh2_chanreq_init(c, "window-change", NULL, NULL); + put_uint32(pktout, w); + put_uint32(pktout, h); + put_uint32(pktout, 0); /* pixel width */ + put_uint32(pktout, 0); /* pixel height */ + pq_push(s->ppl.out_pq, pktout); +} + +bool ssh2_connection_need_antispoof_prompt(struct ssh2_connection_state *s) +{ + bool success = seat_set_trust_status(s->ppl.seat, false); + return (!success && !ssh_is_bare(s->ppl.ssh)); +} diff --git a/ssh/connection2-server.c b/ssh/connection2-server.c new file mode 100644 index 00000000..c871b4b3 --- /dev/null +++ b/ssh/connection2-server.c @@ -0,0 +1,306 @@ +/* + * Server-specific parts of the SSH-2 connection layer. + */ + +#include + +#include "putty.h" +#include "ssh.h" +#include "bpp.h" +#include "ppl.h" +#include "channel.h" +#include "sshcr.h" +#include "connection2.h" +#include "server.h" + +void ssh2connection_server_configure( + PacketProtocolLayer *ppl, const SftpServerVtable *sftpserver_vt, + const SshServerConfig *ssc) +{ + struct ssh2_connection_state *s = + container_of(ppl, struct ssh2_connection_state, ppl); + s->sftpserver_vt = sftpserver_vt; + s->ssc = ssc; +} + +static ChanopenResult chan_open_session( + struct ssh2_connection_state *s, SshChannel *sc) +{ + PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ + + ppl_logevent("Opened session channel"); + CHANOPEN_RETURN_SUCCESS(sesschan_new(sc, s->ppl.logctx, + s->sftpserver_vt, s->ssc)); +} + +static ChanopenResult chan_open_direct_tcpip( + struct ssh2_connection_state *s, SshChannel *sc, + ptrlen dstaddr, int dstport, ptrlen peeraddr, int peerport) +{ + PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ + Channel *ch; + char *dstaddr_str, *err; + + dstaddr_str = mkstr(dstaddr); + + ppl_logevent("Received request to connect to port %s:%d (from %.*s:%d)", + dstaddr_str, dstport, PTRLEN_PRINTF(peeraddr), peerport); + err = portfwdmgr_connect( + s->portfwdmgr, &ch, dstaddr_str, dstport, sc, ADDRTYPE_UNSPEC); + + sfree(dstaddr_str); + + if (err != NULL) { + ppl_logevent("Port open failed: %s", err); + sfree(err); + CHANOPEN_RETURN_FAILURE( + SSH2_OPEN_CONNECT_FAILED, ("Connection failed")); + } + + ppl_logevent("Port opened successfully"); + CHANOPEN_RETURN_SUCCESS(ch); +} + +ChanopenResult ssh2_connection_parse_channel_open( + struct ssh2_connection_state *s, ptrlen type, + PktIn *pktin, SshChannel *sc) +{ + if (ptrlen_eq_string(type, "session")) { + return chan_open_session(s, sc); + } else if (ptrlen_eq_string(type, "direct-tcpip")) { + ptrlen dstaddr = get_string(pktin); + int dstport = toint(get_uint32(pktin)); + ptrlen peeraddr = get_string(pktin); + int peerport = toint(get_uint32(pktin)); + return chan_open_direct_tcpip( + s, sc, dstaddr, dstport, peeraddr, peerport); + } else { + CHANOPEN_RETURN_FAILURE( + SSH2_OPEN_UNKNOWN_CHANNEL_TYPE, + ("Unsupported channel type requested")); + } +} + +bool ssh2_connection_parse_global_request( + struct ssh2_connection_state *s, ptrlen type, PktIn *pktin) +{ + if (ptrlen_eq_string(type, "tcpip-forward")) { + char *host = mkstr(get_string(pktin)); + unsigned port = get_uint32(pktin); + /* In SSH-2, the host/port we listen on are the same host/port + * we want reported back to us when a connection comes in, + * because that's what we tell the client */ + bool toret = portfwdmgr_listen( + s->portfwdmgr, host, port, host, port, s->conf); + sfree(host); + return toret; + } else if (ptrlen_eq_string(type, "cancel-tcpip-forward")) { + char *host = mkstr(get_string(pktin)); + unsigned port = get_uint32(pktin); + bool toret = portfwdmgr_unlisten(s->portfwdmgr, host, port); + sfree(host); + return toret; + } else { + /* Unrecognised request. */ + return false; + } +} + +PktOut *ssh2_portfwd_chanopen( + struct ssh2_connection_state *s, struct ssh2_channel *c, + const char *hostname, int port, + const char *description, const SocketPeerInfo *pi) +{ + PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ + PktOut *pktout; + + /* + * In server mode, this function is called by portfwdmgr in + * response to PortListeners that were set up by calling + * portfwdmgr_listen, which means that the hostname and port + * parameters will identify the listening socket on which a + * connection just came in. + */ + + if (pi && pi->log_text) + ppl_logevent("Forwarding connection to listening port %s:%d from %s", + hostname, port, pi->log_text); + else + ppl_logevent("Forwarding connection to listening port %s:%d", + hostname, port); + + pktout = ssh2_chanopen_init(c, "forwarded-tcpip"); + put_stringz(pktout, hostname); + put_uint32(pktout, port); + put_stringz(pktout, (pi && pi->addr_text ? pi->addr_text : "0.0.0.0")); + put_uint32(pktout, (pi && pi->port >= 0 ? pi->port : 0)); + + return pktout; +} + +struct ssh_rportfwd *ssh2_rportfwd_alloc( + ConnectionLayer *cl, + const char *shost, int sport, const char *dhost, int dport, + int addressfamily, const char *log_description, PortFwdRecord *pfr, + ssh_sharing_connstate *share_ctx) +{ + unreachable("Should never be called in the server"); +} + +void ssh2_rportfwd_remove(ConnectionLayer *cl, struct ssh_rportfwd *rpf) +{ + unreachable("Should never be called in the server"); +} + +SshChannel *ssh2_session_open(ConnectionLayer *cl, Channel *chan) +{ + unreachable("Should never be called in the server"); +} + +SshChannel *ssh2_serverside_x11_open( + ConnectionLayer *cl, Channel *chan, const SocketPeerInfo *pi) +{ + struct ssh2_connection_state *s = + container_of(cl, struct ssh2_connection_state, cl); + PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ + struct ssh2_channel *c = snew(struct ssh2_channel); + PktOut *pktout; + + c->connlayer = s; + ssh2_channel_init(c); + c->halfopen = true; + c->chan = chan; + + ppl_logevent("Forwarding X11 channel to client"); + + pktout = ssh2_chanopen_init(c, "x11"); + put_stringz(pktout, (pi && pi->addr_text ? pi->addr_text : "0.0.0.0")); + put_uint32(pktout, (pi && pi->port >= 0 ? pi->port : 0)); + pq_push(s->ppl.out_pq, pktout); + + return &c->sc; +} + +SshChannel *ssh2_serverside_agent_open(ConnectionLayer *cl, Channel *chan) +{ + struct ssh2_connection_state *s = + container_of(cl, struct ssh2_connection_state, cl); + PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ + struct ssh2_channel *c = snew(struct ssh2_channel); + PktOut *pktout; + + c->connlayer = s; + ssh2_channel_init(c); + c->halfopen = true; + c->chan = chan; + + ppl_logevent("Forwarding SSH agent to client"); + + pktout = ssh2_chanopen_init(c, "auth-agent@openssh.com"); + pq_push(s->ppl.out_pq, pktout); + + return &c->sc; +} + +void ssh2channel_start_shell(SshChannel *sc, bool want_reply) +{ + unreachable("Should never be called in the server"); +} + +void ssh2channel_start_command( + SshChannel *sc, bool want_reply, const char *command) +{ + unreachable("Should never be called in the server"); +} + +bool ssh2channel_start_subsystem( + SshChannel *sc, bool want_reply, const char *subsystem) +{ + unreachable("Should never be called in the server"); +} + +void ssh2channel_send_exit_status(SshChannel *sc, int status) +{ + struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc); + struct ssh2_connection_state *s = c->connlayer; + + PktOut *pktout = ssh2_chanreq_init(c, "exit-status", NULL, NULL); + put_uint32(pktout, status); + + pq_push(s->ppl.out_pq, pktout); +} + +void ssh2channel_send_exit_signal( + SshChannel *sc, ptrlen signame, bool core_dumped, ptrlen msg) +{ + struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc); + struct ssh2_connection_state *s = c->connlayer; + + PktOut *pktout = ssh2_chanreq_init(c, "exit-signal", NULL, NULL); + put_stringpl(pktout, signame); + put_bool(pktout, core_dumped); + put_stringpl(pktout, msg); + put_stringz(pktout, ""); /* language tag */ + + pq_push(s->ppl.out_pq, pktout); +} + +void ssh2channel_send_exit_signal_numeric( + SshChannel *sc, int signum, bool core_dumped, ptrlen msg) +{ + struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc); + struct ssh2_connection_state *s = c->connlayer; + + PktOut *pktout = ssh2_chanreq_init(c, "exit-signal", NULL, NULL); + put_uint32(pktout, signum); + put_bool(pktout, core_dumped); + put_stringpl(pktout, msg); + put_stringz(pktout, ""); /* language tag */ + + pq_push(s->ppl.out_pq, pktout); +} + +void ssh2channel_request_x11_forwarding( + SshChannel *sc, bool want_reply, const char *authproto, + const char *authdata, int screen_number, bool oneshot) +{ + unreachable("Should never be called in the server"); +} + +void ssh2channel_request_agent_forwarding(SshChannel *sc, bool want_reply) +{ + unreachable("Should never be called in the server"); +} + +void ssh2channel_request_pty( + SshChannel *sc, bool want_reply, Conf *conf, int w, int h) +{ + unreachable("Should never be called in the server"); +} + +bool ssh2channel_send_env_var( + SshChannel *sc, bool want_reply, const char *var, const char *value) +{ + unreachable("Should never be called in the server"); +} + +bool ssh2channel_send_serial_break(SshChannel *sc, bool want_reply, int length) +{ + unreachable("Should never be called in the server"); +} + +bool ssh2channel_send_signal( + SshChannel *sc, bool want_reply, const char *signame) +{ + unreachable("Should never be called in the server"); +} + +void ssh2channel_send_terminal_size_change(SshChannel *sc, int w, int h) +{ + unreachable("Should never be called in the server"); +} + +bool ssh2_connection_need_antispoof_prompt(struct ssh2_connection_state *s) +{ + return false; +} diff --git a/ssh/connection2.c b/ssh/connection2.c new file mode 100644 index 00000000..48436848 --- /dev/null +++ b/ssh/connection2.c @@ -0,0 +1,1745 @@ +/* + * Packet protocol layer for the SSH-2 connection protocol (RFC 4254). + */ + +#include + +#include "putty.h" +#include "ssh.h" +#include "bpp.h" +#include "ppl.h" +#include "channel.h" +#include "sshcr.h" +#include "connection2.h" + +static void ssh2_connection_free(PacketProtocolLayer *); +static void ssh2_connection_process_queue(PacketProtocolLayer *); +static bool ssh2_connection_get_specials( + PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx); +static void ssh2_connection_special_cmd(PacketProtocolLayer *ppl, + SessionSpecialCode code, int arg); +static bool ssh2_connection_want_user_input(PacketProtocolLayer *ppl); +static void ssh2_connection_got_user_input(PacketProtocolLayer *ppl); +static void ssh2_connection_reconfigure(PacketProtocolLayer *ppl, Conf *conf); + +static const PacketProtocolLayerVtable ssh2_connection_vtable = { + .free = ssh2_connection_free, + .process_queue = ssh2_connection_process_queue, + .get_specials = ssh2_connection_get_specials, + .special_cmd = ssh2_connection_special_cmd, + .want_user_input = ssh2_connection_want_user_input, + .got_user_input = ssh2_connection_got_user_input, + .reconfigure = ssh2_connection_reconfigure, + .queued_data_size = ssh_ppl_default_queued_data_size, + .name = "ssh-connection", +}; + +static SshChannel *ssh2_lportfwd_open( + ConnectionLayer *cl, const char *hostname, int port, + const char *description, const SocketPeerInfo *pi, Channel *chan); +static struct X11FakeAuth *ssh2_add_x11_display( + ConnectionLayer *cl, int authtype, struct X11Display *x11disp); +static struct X11FakeAuth *ssh2_add_sharing_x11_display( + ConnectionLayer *cl, int authtype, ssh_sharing_connstate *share_cs, + share_channel *share_chan); +static void ssh2_remove_sharing_x11_display(ConnectionLayer *cl, + struct X11FakeAuth *auth); +static void ssh2_send_packet_from_downstream( + ConnectionLayer *cl, unsigned id, int type, + const void *pkt, int pktlen, const char *additional_log_text); +static unsigned ssh2_alloc_sharing_channel( + ConnectionLayer *cl, ssh_sharing_connstate *connstate); +static void ssh2_delete_sharing_channel( + ConnectionLayer *cl, unsigned localid); +static void ssh2_sharing_queue_global_request( + ConnectionLayer *cl, ssh_sharing_connstate *share_ctx); +static void ssh2_sharing_no_more_downstreams(ConnectionLayer *cl); +static bool ssh2_agent_forwarding_permitted(ConnectionLayer *cl); +static void ssh2_terminal_size(ConnectionLayer *cl, int width, int height); +static void ssh2_stdout_unthrottle(ConnectionLayer *cl, size_t bufsize); +static size_t ssh2_stdin_backlog(ConnectionLayer *cl); +static void ssh2_throttle_all_channels(ConnectionLayer *cl, bool throttled); +static bool ssh2_ldisc_option(ConnectionLayer *cl, int option); +static void ssh2_set_ldisc_option(ConnectionLayer *cl, int option, bool value); +static void ssh2_enable_x_fwd(ConnectionLayer *cl); +static void ssh2_set_wants_user_input(ConnectionLayer *cl, bool wanted); + +static const ConnectionLayerVtable ssh2_connlayer_vtable = { + .rportfwd_alloc = ssh2_rportfwd_alloc, + .rportfwd_remove = ssh2_rportfwd_remove, + .lportfwd_open = ssh2_lportfwd_open, + .session_open = ssh2_session_open, + .serverside_x11_open = ssh2_serverside_x11_open, + .serverside_agent_open = ssh2_serverside_agent_open, + .add_x11_display = ssh2_add_x11_display, + .add_sharing_x11_display = ssh2_add_sharing_x11_display, + .remove_sharing_x11_display = ssh2_remove_sharing_x11_display, + .send_packet_from_downstream = ssh2_send_packet_from_downstream, + .alloc_sharing_channel = ssh2_alloc_sharing_channel, + .delete_sharing_channel = ssh2_delete_sharing_channel, + .sharing_queue_global_request = ssh2_sharing_queue_global_request, + .sharing_no_more_downstreams = ssh2_sharing_no_more_downstreams, + .agent_forwarding_permitted = ssh2_agent_forwarding_permitted, + .terminal_size = ssh2_terminal_size, + .stdout_unthrottle = ssh2_stdout_unthrottle, + .stdin_backlog = ssh2_stdin_backlog, + .throttle_all_channels = ssh2_throttle_all_channels, + .ldisc_option = ssh2_ldisc_option, + .set_ldisc_option = ssh2_set_ldisc_option, + .enable_x_fwd = ssh2_enable_x_fwd, + .set_wants_user_input = ssh2_set_wants_user_input, +}; + +static char *ssh2_channel_open_failure_error_text(PktIn *pktin) +{ + static const char *const reasons[] = { + NULL, + "Administratively prohibited", + "Connect failed", + "Unknown channel type", + "Resource shortage", + }; + unsigned reason_code; + const char *reason_code_string; + char reason_code_buf[256]; + ptrlen reason; + + reason_code = get_uint32(pktin); + if (reason_code < lenof(reasons) && reasons[reason_code]) { + reason_code_string = reasons[reason_code]; + } else { + reason_code_string = reason_code_buf; + sprintf(reason_code_buf, "unknown reason code %#x", reason_code); + } + + reason = get_string(pktin); + + return dupprintf("%s [%.*s]", reason_code_string, PTRLEN_PRINTF(reason)); +} + +static size_t ssh2channel_write( + SshChannel *c, bool is_stderr, const void *buf, size_t len); +static void ssh2channel_write_eof(SshChannel *c); +static void ssh2channel_initiate_close(SshChannel *c, const char *err); +static void ssh2channel_unthrottle(SshChannel *c, size_t bufsize); +static Conf *ssh2channel_get_conf(SshChannel *c); +static void ssh2channel_window_override_removed(SshChannel *c); +static void ssh2channel_x11_sharing_handover( + SshChannel *c, ssh_sharing_connstate *share_cs, share_channel *share_chan, + const char *peer_addr, int peer_port, int endian, + int protomajor, int protominor, const void *initial_data, int initial_len); +static void ssh2channel_hint_channel_is_simple(SshChannel *c); + +static const SshChannelVtable ssh2channel_vtable = { + .write = ssh2channel_write, + .write_eof = ssh2channel_write_eof, + .initiate_close = ssh2channel_initiate_close, + .unthrottle = ssh2channel_unthrottle, + .get_conf = ssh2channel_get_conf, + .window_override_removed = ssh2channel_window_override_removed, + .x11_sharing_handover = ssh2channel_x11_sharing_handover, + .send_exit_status = ssh2channel_send_exit_status, + .send_exit_signal = ssh2channel_send_exit_signal, + .send_exit_signal_numeric = ssh2channel_send_exit_signal_numeric, + .request_x11_forwarding = ssh2channel_request_x11_forwarding, + .request_agent_forwarding = ssh2channel_request_agent_forwarding, + .request_pty = ssh2channel_request_pty, + .send_env_var = ssh2channel_send_env_var, + .start_shell = ssh2channel_start_shell, + .start_command = ssh2channel_start_command, + .start_subsystem = ssh2channel_start_subsystem, + .send_serial_break = ssh2channel_send_serial_break, + .send_signal = ssh2channel_send_signal, + .send_terminal_size_change = ssh2channel_send_terminal_size_change, + .hint_channel_is_simple = ssh2channel_hint_channel_is_simple, +}; + +static void ssh2_channel_check_close(struct ssh2_channel *c); +static void ssh2_channel_try_eof(struct ssh2_channel *c); +static void ssh2_set_window(struct ssh2_channel *c, int newwin); +static size_t ssh2_try_send(struct ssh2_channel *c); +static void ssh2_try_send_and_unthrottle(struct ssh2_channel *c); +static void ssh2_channel_check_throttle(struct ssh2_channel *c); +static void ssh2_channel_close_local(struct ssh2_channel *c, + const char *reason); +static void ssh2_channel_destroy(struct ssh2_channel *c); + +static void ssh2_check_termination(struct ssh2_connection_state *s); + +struct outstanding_global_request { + gr_handler_fn_t handler; + void *ctx; + struct outstanding_global_request *next; +}; +void ssh2_queue_global_request_handler( + struct ssh2_connection_state *s, gr_handler_fn_t handler, void *ctx) +{ + struct outstanding_global_request *ogr = + snew(struct outstanding_global_request); + ogr->handler = handler; + ogr->ctx = ctx; + if (s->globreq_tail) + s->globreq_tail->next = ogr; + else + s->globreq_head = ogr; + s->globreq_tail = ogr; +} + +static int ssh2_channelcmp(void *av, void *bv) +{ + const struct ssh2_channel *a = (const struct ssh2_channel *) av; + const struct ssh2_channel *b = (const struct ssh2_channel *) bv; + if (a->localid < b->localid) + return -1; + if (a->localid > b->localid) + return +1; + return 0; +} + +static int ssh2_channelfind(void *av, void *bv) +{ + const unsigned *a = (const unsigned *) av; + const struct ssh2_channel *b = (const struct ssh2_channel *) bv; + if (*a < b->localid) + return -1; + if (*a > b->localid) + return +1; + return 0; +} + +/* + * Each channel has a queue of outstanding CHANNEL_REQUESTS and their + * handlers. + */ +struct outstanding_channel_request { + cr_handler_fn_t handler; + void *ctx; + struct outstanding_channel_request *next; +}; + +static void ssh2_channel_free(struct ssh2_channel *c) +{ + bufchain_clear(&c->outbuffer); + bufchain_clear(&c->errbuffer); + while (c->chanreq_head) { + struct outstanding_channel_request *chanreq = c->chanreq_head; + c->chanreq_head = c->chanreq_head->next; + sfree(chanreq); + } + if (c->chan) { + struct ssh2_connection_state *s = c->connlayer; + if (s->mainchan_sc == &c->sc) { + s->mainchan = NULL; + s->mainchan_sc = NULL; + } + chan_free(c->chan); + } + sfree(c); +} + +PacketProtocolLayer *ssh2_connection_new( + Ssh *ssh, ssh_sharing_state *connshare, bool is_simple, + Conf *conf, const char *peer_verstring, ConnectionLayer **cl_out) +{ + struct ssh2_connection_state *s = snew(struct ssh2_connection_state); + memset(s, 0, sizeof(*s)); + s->ppl.vt = &ssh2_connection_vtable; + + s->conf = conf_copy(conf); + + s->ssh_is_simple = is_simple; + + /* + * If the ssh_no_shell option is enabled, we disable the usual + * termination check, so that we persist even in the absence of + * any at all channels (because our purpose is probably to be a + * background port forwarder). + */ + s->persistent = conf_get_bool(s->conf, CONF_ssh_no_shell); + + s->connshare = connshare; + s->peer_verstring = dupstr(peer_verstring); + + s->channels = newtree234(ssh2_channelcmp); + + s->x11authtree = newtree234(x11_authcmp); + + /* Need to get the log context for s->cl now, because we won't be + * helpfully notified when a copy is written into s->ppl by our + * owner. */ + s->cl.vt = &ssh2_connlayer_vtable; + s->cl.logctx = ssh_get_logctx(ssh); + + s->portfwdmgr = portfwdmgr_new(&s->cl); + + *cl_out = &s->cl; + if (s->connshare) + ssh_connshare_provide_connlayer(s->connshare, &s->cl); + + return &s->ppl; +} + +static void ssh2_connection_free(PacketProtocolLayer *ppl) +{ + struct ssh2_connection_state *s = + container_of(ppl, struct ssh2_connection_state, ppl); + struct X11FakeAuth *auth; + struct ssh2_channel *c; + struct ssh_rportfwd *rpf; + + sfree(s->peer_verstring); + + conf_free(s->conf); + + while ((c = delpos234(s->channels, 0)) != NULL) + ssh2_channel_free(c); + freetree234(s->channels); + + while ((auth = delpos234(s->x11authtree, 0)) != NULL) { + if (auth->disp) + x11_free_display(auth->disp); + x11_free_fake_auth(auth); + } + freetree234(s->x11authtree); + + if (s->rportfwds) { + while ((rpf = delpos234(s->rportfwds, 0)) != NULL) + free_rportfwd(rpf); + freetree234(s->rportfwds); + } + portfwdmgr_free(s->portfwdmgr); + + if (s->antispoof_prompt) + free_prompts(s->antispoof_prompt); + + delete_callbacks_for_context(s); + + sfree(s); +} + +static bool ssh2_connection_filter_queue(struct ssh2_connection_state *s) +{ + PktIn *pktin; + PktOut *pktout; + ptrlen type, data; + struct ssh2_channel *c; + struct outstanding_channel_request *ocr; + unsigned localid, remid, winsize, pktsize, ext_type; + bool want_reply, reply_success, expect_halfopen; + ChanopenResult chanopen_result; + PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ + + while (1) { + if (ssh2_common_filter_queue(&s->ppl)) + return true; + if ((pktin = pq_peek(s->ppl.in_pq)) == NULL) + return false; + + switch (pktin->type) { + case SSH2_MSG_GLOBAL_REQUEST: + type = get_string(pktin); + want_reply = get_bool(pktin); + + reply_success = ssh2_connection_parse_global_request( + s, type, pktin); + + if (want_reply) { + int type = (reply_success ? SSH2_MSG_REQUEST_SUCCESS : + SSH2_MSG_REQUEST_FAILURE); + pktout = ssh_bpp_new_pktout(s->ppl.bpp, type); + pq_push(s->ppl.out_pq, pktout); + } + pq_pop(s->ppl.in_pq); + break; + + case SSH2_MSG_REQUEST_SUCCESS: + case SSH2_MSG_REQUEST_FAILURE: + if (!s->globreq_head) { + ssh_proto_error( + s->ppl.ssh, + "Received %s with no outstanding global request", + ssh2_pkt_type(s->ppl.bpp->pls->kctx, s->ppl.bpp->pls->actx, + pktin->type)); + return true; + } + + s->globreq_head->handler(s, pktin, s->globreq_head->ctx); + { + struct outstanding_global_request *tmp = s->globreq_head; + s->globreq_head = s->globreq_head->next; + sfree(tmp); + } + + pq_pop(s->ppl.in_pq); + break; + + case SSH2_MSG_CHANNEL_OPEN: + type = get_string(pktin); + c = snew(struct ssh2_channel); + c->connlayer = s; + c->chan = NULL; + + remid = get_uint32(pktin); + winsize = get_uint32(pktin); + pktsize = get_uint32(pktin); + + chanopen_result = ssh2_connection_parse_channel_open( + s, type, pktin, &c->sc); + + if (chanopen_result.outcome == CHANOPEN_RESULT_DOWNSTREAM) { + /* + * This channel-open request needs to go to a + * connection-sharing downstream, so abandon our own + * channel-open procedure and just pass the message on + * to sharing.c. + */ + share_got_pkt_from_server( + chanopen_result.u.downstream.share_ctx, pktin->type, + BinarySource_UPCAST(pktin)->data, + BinarySource_UPCAST(pktin)->len); + sfree(c); + break; + } + + c->remoteid = remid; + c->halfopen = false; + if (chanopen_result.outcome == CHANOPEN_RESULT_FAILURE) { + pktout = ssh_bpp_new_pktout( + s->ppl.bpp, SSH2_MSG_CHANNEL_OPEN_FAILURE); + put_uint32(pktout, c->remoteid); + put_uint32(pktout, chanopen_result.u.failure.reason_code); + put_stringz(pktout, chanopen_result.u.failure.wire_message); + put_stringz(pktout, "en"); /* language tag */ + pq_push(s->ppl.out_pq, pktout); + ppl_logevent("Rejected channel open: %s", + chanopen_result.u.failure.wire_message); + sfree(chanopen_result.u.failure.wire_message); + sfree(c); + } else { + c->chan = chanopen_result.u.success.channel; + ssh2_channel_init(c); + c->remwindow = winsize; + c->remmaxpkt = pktsize; + if (c->remmaxpkt > s->ppl.bpp->vt->packet_size_limit) + c->remmaxpkt = s->ppl.bpp->vt->packet_size_limit; + if (c->chan->initial_fixed_window_size) { + c->locwindow = c->locmaxwin = c->remlocwin = + c->chan->initial_fixed_window_size; + } + pktout = ssh_bpp_new_pktout( + s->ppl.bpp, SSH2_MSG_CHANNEL_OPEN_CONFIRMATION); + put_uint32(pktout, c->remoteid); + put_uint32(pktout, c->localid); + put_uint32(pktout, c->locwindow); + put_uint32(pktout, OUR_V2_MAXPKT); /* our max pkt size */ + pq_push(s->ppl.out_pq, pktout); + } + + pq_pop(s->ppl.in_pq); + break; + + case SSH2_MSG_CHANNEL_DATA: + case SSH2_MSG_CHANNEL_EXTENDED_DATA: + case SSH2_MSG_CHANNEL_WINDOW_ADJUST: + case SSH2_MSG_CHANNEL_REQUEST: + case SSH2_MSG_CHANNEL_EOF: + case SSH2_MSG_CHANNEL_CLOSE: + case SSH2_MSG_CHANNEL_OPEN_CONFIRMATION: + case SSH2_MSG_CHANNEL_OPEN_FAILURE: + case SSH2_MSG_CHANNEL_SUCCESS: + case SSH2_MSG_CHANNEL_FAILURE: + /* + * Common preliminary code for all the messages from the + * server that cite one of our channel ids: look up that + * channel id, check it exists, and if it's for a sharing + * downstream, pass it on. + */ + localid = get_uint32(pktin); + c = find234(s->channels, &localid, ssh2_channelfind); + + if (c && c->sharectx) { + share_got_pkt_from_server(c->sharectx, pktin->type, + BinarySource_UPCAST(pktin)->data, + BinarySource_UPCAST(pktin)->len); + pq_pop(s->ppl.in_pq); + break; + } + + expect_halfopen = ( + pktin->type == SSH2_MSG_CHANNEL_OPEN_CONFIRMATION || + pktin->type == SSH2_MSG_CHANNEL_OPEN_FAILURE); + + if (!c || c->halfopen != expect_halfopen) { + ssh_proto_error(s->ppl.ssh, + "Received %s for %s channel %u", + ssh2_pkt_type(s->ppl.bpp->pls->kctx, + s->ppl.bpp->pls->actx, + pktin->type), + (!c ? "nonexistent" : + c->halfopen ? "half-open" : "open"), + localid); + return true; + } + + switch (pktin->type) { + case SSH2_MSG_CHANNEL_OPEN_CONFIRMATION: + assert(c->halfopen); + c->remoteid = get_uint32(pktin); + c->halfopen = false; + c->remwindow = get_uint32(pktin); + c->remmaxpkt = get_uint32(pktin); + if (c->remmaxpkt > s->ppl.bpp->vt->packet_size_limit) + c->remmaxpkt = s->ppl.bpp->vt->packet_size_limit; + + chan_open_confirmation(c->chan); + + /* + * Now that the channel is fully open, it's possible + * in principle to immediately close it. Check whether + * it wants us to! + * + * This can occur if a local socket error occurred + * between us sending out CHANNEL_OPEN and receiving + * OPEN_CONFIRMATION. If that happens, all we can do + * is immediately initiate close proceedings now that + * we know the server's id to put in the close + * message. We'll have handled that in this code by + * having already turned c->chan into a zombie, so its + * want_close method (which ssh2_channel_check_close + * will consult) will already be returning true. + */ + ssh2_channel_check_close(c); + + if (c->pending_eof) + ssh2_channel_try_eof(c); /* in case we had a pending EOF */ + break; + + case SSH2_MSG_CHANNEL_OPEN_FAILURE: { + assert(c->halfopen); + + char *err = ssh2_channel_open_failure_error_text(pktin); + chan_open_failed(c->chan, err); + sfree(err); + + del234(s->channels, c); + ssh2_channel_free(c); + + break; + } + + case SSH2_MSG_CHANNEL_DATA: + case SSH2_MSG_CHANNEL_EXTENDED_DATA: + ext_type = (pktin->type == SSH2_MSG_CHANNEL_DATA ? 0 : + get_uint32(pktin)); + data = get_string(pktin); + if (!get_err(pktin)) { + int bufsize; + c->locwindow -= data.len; + c->remlocwin -= data.len; + if (ext_type != 0 && ext_type != SSH2_EXTENDED_DATA_STDERR) + data.len = 0; /* ignore unknown extended data */ + bufsize = chan_send( + c->chan, ext_type == SSH2_EXTENDED_DATA_STDERR, + data.ptr, data.len); + + /* + * The channel may have turned into a connection- + * shared one as a result of that chan_send, e.g. + * if the data we just provided completed the X11 + * auth phase and caused a callback to + * x11_sharing_handover. If so, do nothing + * further. + */ + if (c->sharectx) + 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->remlocwin <= 0 && + c->throttle_state == UNTHROTTLED && + c->locmaxwin < 0x40000000) + c->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. + */ + if (bufsize < c->locmaxwin) + ssh2_set_window(c, c->locmaxwin - bufsize); + + /* + * 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->locmaxwin || + (s->ssh_is_simple && bufsize>0)) && + !c->throttling_conn) { + c->throttling_conn = true; + ssh_throttle_conn(s->ppl.ssh, +1); + } + } + break; + + case SSH2_MSG_CHANNEL_WINDOW_ADJUST: + if (!(c->closes & CLOSES_SENT_EOF)) { + c->remwindow += get_uint32(pktin); + ssh2_try_send_and_unthrottle(c); + } + break; + + case SSH2_MSG_CHANNEL_REQUEST: + type = get_string(pktin); + want_reply = get_bool(pktin); + + reply_success = false; + + if (c->closes & CLOSES_SENT_CLOSE) { + /* + * We don't reply to channel requests after we've + * sent CHANNEL_CLOSE for the channel, because our + * reply might cross in the network with the other + * side's CHANNEL_CLOSE and arrive after they have + * wound the channel up completely. + */ + want_reply = false; + } + + /* + * Try every channel request name we recognise, no + * matter what the channel, and see if the Channel + * instance will accept it. + */ + if (ptrlen_eq_string(type, "exit-status")) { + int exitcode = toint(get_uint32(pktin)); + reply_success = chan_rcvd_exit_status(c->chan, exitcode); + } else if (ptrlen_eq_string(type, "exit-signal")) { + ptrlen signame; + int signum; + bool core = false; + ptrlen errmsg; + int format; + + /* + * 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. + */ + + size_t startpos = BinarySource_UPCAST(pktin)->pos; + + for (format = 0; format < 2; format++) { + BinarySource_UPCAST(pktin)->pos = startpos; + BinarySource_UPCAST(pktin)->err = BSE_NO_ERROR; + + /* placate compiler warnings about unin */ + signame = make_ptrlen(NULL, 0); + signum = 0; + + if (format == 0) /* standard string-based format */ + signame = get_string(pktin); + else /* nonstandard integer format */ + signum = toint(get_uint32(pktin)); + + core = get_bool(pktin); + errmsg = get_string(pktin); /* error message */ + get_string(pktin); /* language tag */ + + if (!get_err(pktin) && get_avail(pktin) == 0) + break; /* successful parse */ + } + + switch (format) { + case 0: + reply_success = chan_rcvd_exit_signal( + c->chan, signame, core, errmsg); + break; + case 1: + reply_success = chan_rcvd_exit_signal_numeric( + c->chan, signum, core, errmsg); + break; + default: + /* Couldn't parse this message in either format */ + reply_success = false; + break; + } + } else if (ptrlen_eq_string(type, "shell")) { + reply_success = chan_run_shell(c->chan); + } else if (ptrlen_eq_string(type, "exec")) { + ptrlen command = get_string(pktin); + reply_success = chan_run_command(c->chan, command); + } else if (ptrlen_eq_string(type, "subsystem")) { + ptrlen subsys = get_string(pktin); + reply_success = chan_run_subsystem(c->chan, subsys); + } else if (ptrlen_eq_string(type, "x11-req")) { + bool oneshot = get_bool(pktin); + ptrlen authproto = get_string(pktin); + ptrlen authdata = get_string(pktin); + unsigned screen_number = get_uint32(pktin); + reply_success = chan_enable_x11_forwarding( + c->chan, oneshot, authproto, authdata, screen_number); + } else if (ptrlen_eq_string(type, + "auth-agent-req@openssh.com")) { + reply_success = chan_enable_agent_forwarding(c->chan); + } else if (ptrlen_eq_string(type, "pty-req")) { + ptrlen termtype = get_string(pktin); + unsigned width = get_uint32(pktin); + unsigned height = get_uint32(pktin); + unsigned pixwidth = get_uint32(pktin); + unsigned pixheight = get_uint32(pktin); + ptrlen encoded_modes = get_string(pktin); + BinarySource bs_modes[1]; + struct ssh_ttymodes modes; + + BinarySource_BARE_INIT_PL(bs_modes, encoded_modes); + modes = read_ttymodes_from_packet(bs_modes, 2); + if (get_err(bs_modes) || get_avail(bs_modes) > 0) { + ppl_logevent("Unable to decode terminal mode string"); + reply_success = false; + } else { + reply_success = chan_allocate_pty( + c->chan, termtype, width, height, + pixwidth, pixheight, modes); + } + } else if (ptrlen_eq_string(type, "env")) { + ptrlen var = get_string(pktin); + ptrlen value = get_string(pktin); + + reply_success = chan_set_env(c->chan, var, value); + } else if (ptrlen_eq_string(type, "break")) { + unsigned length = get_uint32(pktin); + + reply_success = chan_send_break(c->chan, length); + } else if (ptrlen_eq_string(type, "signal")) { + ptrlen signame = get_string(pktin); + + reply_success = chan_send_signal(c->chan, signame); + } else if (ptrlen_eq_string(type, "window-change")) { + unsigned width = get_uint32(pktin); + unsigned height = get_uint32(pktin); + unsigned pixwidth = get_uint32(pktin); + unsigned pixheight = get_uint32(pktin); + reply_success = chan_change_window_size( + c->chan, width, height, pixwidth, pixheight); + } + if (want_reply) { + int type = (reply_success ? SSH2_MSG_CHANNEL_SUCCESS : + SSH2_MSG_CHANNEL_FAILURE); + pktout = ssh_bpp_new_pktout(s->ppl.bpp, type); + put_uint32(pktout, c->remoteid); + pq_push(s->ppl.out_pq, pktout); + } + break; + + case SSH2_MSG_CHANNEL_SUCCESS: + case SSH2_MSG_CHANNEL_FAILURE: + ocr = c->chanreq_head; + if (!ocr) { + ssh_proto_error( + s->ppl.ssh, + "Received %s for channel %d with no outstanding " + "channel request", + ssh2_pkt_type(s->ppl.bpp->pls->kctx, + s->ppl.bpp->pls->actx, pktin->type), + c->localid); + return true; + } + ocr->handler(c, pktin, ocr->ctx); + c->chanreq_head = ocr->next; + sfree(ocr); + /* + * We may now initiate channel-closing procedures, if + * that CHANNEL_REQUEST was the last thing outstanding + * before we send CHANNEL_CLOSE. + */ + ssh2_channel_check_close(c); + break; + + case SSH2_MSG_CHANNEL_EOF: + if (!(c->closes & CLOSES_RCVD_EOF)) { + c->closes |= CLOSES_RCVD_EOF; + chan_send_eof(c->chan); + ssh2_channel_check_close(c); + } + break; + + case SSH2_MSG_CHANNEL_CLOSE: + /* + * When we receive CLOSE on a channel, we assume it + * comes with an implied EOF if we haven't seen EOF + * yet. + */ + if (!(c->closes & CLOSES_RCVD_EOF)) { + c->closes |= CLOSES_RCVD_EOF; + chan_send_eof(c->chan); + } + + if (!(s->ppl.remote_bugs & BUG_SENDS_LATE_REQUEST_REPLY)) { + /* + * It also means we stop expecting to see replies + * to any outstanding channel requests, so clean + * those up too. (ssh_chanreq_init will enforce by + * assertion that we don't subsequently put + * anything back on this list.) + */ + while (c->chanreq_head) { + struct outstanding_channel_request *ocr = + c->chanreq_head; + ocr->handler(c, NULL, ocr->ctx); + c->chanreq_head = ocr->next; + sfree(ocr); + } + } + + /* + * And we also send an outgoing EOF, if we haven't + * already, on the assumption that CLOSE is a pretty + * forceful announcement that the remote side is doing + * away with the entire channel. (If it had wanted to + * send us EOF and continue receiving data from us, it + * would have just sent CHANNEL_EOF.) + */ + if (!(c->closes & CLOSES_SENT_EOF)) { + /* + * Abandon any buffered data we still wanted to + * send to this channel. Receiving a CHANNEL_CLOSE + * is an indication that the server really wants + * to get on and _destroy_ this channel, and it + * isn't going to send us any further + * WINDOW_ADJUSTs to permit us to send pending + * stuff. + */ + bufchain_clear(&c->outbuffer); + bufchain_clear(&c->errbuffer); + + /* + * Send outgoing EOF. + */ + sshfwd_write_eof(&c->sc); + + /* + * Make sure we don't read any more from whatever + * our local data source is for this channel. + * (This will pick up on the changes made by + * sshfwd_write_eof.) + */ + ssh2_channel_check_throttle(c); + } + + /* + * Now process the actual close. + */ + if (!(c->closes & CLOSES_RCVD_CLOSE)) { + c->closes |= CLOSES_RCVD_CLOSE; + ssh2_channel_check_close(c); + } + + break; + } + + pq_pop(s->ppl.in_pq); + break; + + default: + return false; + } + } +} + +static void ssh2_handle_winadj_response(struct ssh2_channel *c, + PktIn *pktin, void *ctx) +{ + unsigned *sizep = ctx; + + /* + * Winadj responses should always be failures. However, at least + * one server ("boks_sshd") is known to return SUCCESS for channel + * requests it's never heard of, such as "winadj@putty". Raised + * with foxt.com as bug 090916-090424, but for the sake of a quiet + * life, we don't worry about what kind of response we got. + */ + + c->remlocwin += *sizep; + sfree(sizep); + /* + * winadj messages are only sent when the window is fully open, so + * if we get an ack of one, we know any pending unthrottle is + * complete. + */ + if (c->throttle_state == UNTHROTTLING) + c->throttle_state = UNTHROTTLED; +} + +static void ssh2_set_window(struct ssh2_channel *c, int newwin) +{ + struct ssh2_connection_state *s = c->connlayer; + + /* + * Never send WINDOW_ADJUST for a channel that the remote side has + * already sent EOF on; there's no point, since it won't be + * sending any more data anyway. Ditto if _we've_ already sent + * CLOSE. + */ + if (c->closes & (CLOSES_RCVD_EOF | CLOSES_SENT_CLOSE)) + return; + + /* + * If the client-side Channel is in an initial setup phase with a + * fixed window size, e.g. for an X11 channel when we're still + * waiting to see its initial auth and may yet hand it off to a + * downstream, don't send any WINDOW_ADJUST either. + */ + if (c->chan->initial_fixed_window_size) + 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 ((s->ppl.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->locwindow) { + PktOut *pktout; + unsigned *up; + + /* + * In order to keep track of how much window the client + * actually has available, we'd like it to acknowledge each + * WINDOW_ADJUST. We can't do that directly, so we accompany + * 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. + */ + if (newwin == c->locmaxwin && + !(s->ppl.remote_bugs & BUG_CHOKES_ON_WINADJ)) { + up = snew(unsigned); + *up = newwin - c->locwindow; + pktout = ssh2_chanreq_init(c, "winadj@putty.projects.tartarus.org", + ssh2_handle_winadj_response, up); + pq_push(s->ppl.out_pq, pktout); + + if (c->throttle_state != UNTHROTTLED) + c->throttle_state = UNTHROTTLING; + } else { + /* Pretend the WINDOW_ADJUST was acked immediately. */ + c->remlocwin = newwin; + c->throttle_state = THROTTLED; + } + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_CHANNEL_WINDOW_ADJUST); + put_uint32(pktout, c->remoteid); + put_uint32(pktout, newwin - c->locwindow); + pq_push(s->ppl.out_pq, pktout); + c->locwindow = newwin; + } +} + +static PktIn *ssh2_connection_pop(struct ssh2_connection_state *s) +{ + ssh2_connection_filter_queue(s); + return pq_pop(s->ppl.in_pq); +} + +static void ssh2_connection_process_queue(PacketProtocolLayer *ppl) +{ + struct ssh2_connection_state *s = + container_of(ppl, struct ssh2_connection_state, ppl); + PktIn *pktin; + + if (ssh2_connection_filter_queue(s)) /* no matter why we were called */ + return; + + crBegin(s->crState); + + if (s->connshare) + share_activate(s->connshare, s->peer_verstring); + + /* + * Signal the seat that authentication is done, so that it can + * deploy spoofing defences. If it doesn't have any, deploy our + * own fallback one. + * + * We do this here rather than at the end of userauth, because we + * might not have gone through userauth at all (if we're a + * connection-sharing downstream). + */ + if (ssh2_connection_need_antispoof_prompt(s)) { + s->antispoof_prompt = new_prompts(); + s->antispoof_prompt->to_server = true; + s->antispoof_prompt->from_server = false; + s->antispoof_prompt->name = dupstr("Authentication successful"); + add_prompt( + s->antispoof_prompt, + dupstr("Access granted. Press Return to begin session. "), false); + s->antispoof_ret = seat_get_userpass_input( + s->ppl.seat, s->antispoof_prompt, NULL); + while (1) { + while (s->antispoof_ret < 0 && + bufchain_size(s->ppl.user_input) > 0) + s->antispoof_ret = seat_get_userpass_input( + s->ppl.seat, s->antispoof_prompt, s->ppl.user_input); + + if (s->antispoof_ret >= 0) + break; + + s->want_user_input = true; + crReturnV; + s->want_user_input = false; + } + free_prompts(s->antispoof_prompt); + s->antispoof_prompt = NULL; + } + + /* + * Enable port forwardings. + */ + portfwdmgr_config(s->portfwdmgr, s->conf); + s->portfwdmgr_configured = true; + + /* + * Create the main session channel, if any. + */ + s->mainchan = mainchan_new( + &s->ppl, &s->cl, s->conf, s->term_width, s->term_height, + s->ssh_is_simple, &s->mainchan_sc); + s->started = true; + + /* + * Transfer data! + */ + + while (1) { + if ((pktin = ssh2_connection_pop(s)) != NULL) { + + /* + * _All_ the connection-layer packets we expect to + * receive are now handled by the dispatch table. + * Anything that reaches here must be bogus. + */ + + ssh_proto_error(s->ppl.ssh, "Received unexpected connection-layer " + "packet, type %d (%s)", pktin->type, + ssh2_pkt_type(s->ppl.bpp->pls->kctx, + s->ppl.bpp->pls->actx, + pktin->type)); + return; + } + crReturnV; + } + + crFinishV; +} + +static void ssh2_channel_check_close(struct ssh2_channel *c) +{ + struct ssh2_connection_state *s = c->connlayer; + PktOut *pktout; + + if (c->halfopen) { + /* + * If we've sent out our own CHANNEL_OPEN but not yet seen + * either OPEN_CONFIRMATION or OPEN_FAILURE in response, then + * it's too early to be sending close messages of any kind. + */ + return; + } + + if (chan_want_close(c->chan, (c->closes & CLOSES_SENT_EOF), + (c->closes & CLOSES_RCVD_EOF)) && + !c->chanreq_head && + !(c->closes & CLOSES_SENT_CLOSE)) { + /* + * We have both sent and received EOF (or the channel is a + * zombie), and we have no outstanding channel requests, which + * means the channel is in final wind-up. But we haven't sent + * CLOSE, so let's do so now. + */ + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_CHANNEL_CLOSE); + put_uint32(pktout, c->remoteid); + pq_push(s->ppl.out_pq, pktout); + c->closes |= CLOSES_SENT_EOF | CLOSES_SENT_CLOSE; + } + + if (!((CLOSES_SENT_CLOSE | CLOSES_RCVD_CLOSE) & ~c->closes)) { + assert(c->chanreq_head == NULL); + /* + * We have both sent and received CLOSE, which means we're + * completely done with the channel. + */ + ssh2_channel_destroy(c); + } +} + +static void ssh2_channel_try_eof(struct ssh2_channel *c) +{ + struct ssh2_connection_state *s = c->connlayer; + PktOut *pktout; + assert(c->pending_eof); /* precondition for calling us */ + if (c->halfopen) + return; /* can't close: not even opened yet */ + if (bufchain_size(&c->outbuffer) > 0 || bufchain_size(&c->errbuffer) > 0) + return; /* can't send EOF: pending outgoing data */ + + c->pending_eof = false; /* we're about to send it */ + + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_CHANNEL_EOF); + put_uint32(pktout, c->remoteid); + pq_push(s->ppl.out_pq, pktout); + c->closes |= CLOSES_SENT_EOF; + ssh2_channel_check_close(c); +} + +/* + * Attempt to send data on an SSH-2 channel. + */ +static size_t ssh2_try_send(struct ssh2_channel *c) +{ + struct ssh2_connection_state *s = c->connlayer; + PktOut *pktout; + size_t bufsize; + + if (!c->halfopen) { + while (c->remwindow > 0 && + (bufchain_size(&c->outbuffer) > 0 || + bufchain_size(&c->errbuffer) > 0)) { + bufchain *buf = (bufchain_size(&c->errbuffer) > 0 ? + &c->errbuffer : &c->outbuffer); + + ptrlen data = bufchain_prefix(buf); + if (data.len > c->remwindow) + data.len = c->remwindow; + if (data.len > c->remmaxpkt) + data.len = c->remmaxpkt; + if (buf == &c->errbuffer) { + pktout = ssh_bpp_new_pktout( + s->ppl.bpp, SSH2_MSG_CHANNEL_EXTENDED_DATA); + put_uint32(pktout, c->remoteid); + put_uint32(pktout, SSH2_EXTENDED_DATA_STDERR); + } else { + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_CHANNEL_DATA); + put_uint32(pktout, c->remoteid); + } + put_stringpl(pktout, data); + pq_push(s->ppl.out_pq, pktout); + bufchain_consume(buf, data.len); + c->remwindow -= data.len; + } + } + + /* + * After having sent as much data as we can, return the amount + * still buffered. + */ + bufsize = bufchain_size(&c->outbuffer) + bufchain_size(&c->errbuffer); + + /* + * And if there's no data pending but we need to send an EOF, send + * it. + */ + if (!bufsize && c->pending_eof) + ssh2_channel_try_eof(c); + + return bufsize; +} + +static void ssh2_try_send_and_unthrottle(struct ssh2_channel *c) +{ + int bufsize; + if (c->closes & CLOSES_SENT_EOF) + return; /* don't send on channels we've EOFed */ + bufsize = ssh2_try_send(c); + if (bufsize == 0) { + c->throttled_by_backlog = false; + ssh2_channel_check_throttle(c); + } +} + +static void ssh2_channel_check_throttle(struct ssh2_channel *c) +{ + /* + * We don't want this channel to read further input if this + * particular channel has a backed-up SSH window, or if the + * outgoing side of the whole SSH connection is currently + * throttled, or if this channel already has an outgoing EOF + * either sent or pending. + */ + chan_set_input_wanted(c->chan, + !c->throttled_by_backlog && + !c->connlayer->all_channels_throttled && + !c->pending_eof && + !(c->closes & CLOSES_SENT_EOF)); +} + +/* + * Close any local socket and free any local resources associated with + * a channel. This converts the channel into a zombie. + */ +static void ssh2_channel_close_local(struct ssh2_channel *c, + const char *reason) +{ + struct ssh2_connection_state *s = c->connlayer; + PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ + char *msg = NULL; + + if (c->sharectx) + return; + + msg = chan_log_close_msg(c->chan); + + if (msg) + ppl_logevent("%s%s%s", msg, reason ? " " : "", reason ? reason : ""); + + sfree(msg); + + chan_free(c->chan); + c->chan = zombiechan_new(); +} + +static void ssh2_check_termination_callback(void *vctx) +{ + struct ssh2_connection_state *s = (struct ssh2_connection_state *)vctx; + ssh2_check_termination(s); +} + +static void ssh2_channel_destroy(struct ssh2_channel *c) +{ + struct ssh2_connection_state *s = c->connlayer; + + assert(c->chanreq_head == NULL); + + ssh2_channel_close_local(c, NULL); + del234(s->channels, c); + ssh2_channel_free(c); + + /* + * If that was the last channel left open, we might need to + * terminate. But we'll be a bit cautious, by doing that in a + * toplevel callback, just in case anything on the current call + * stack objects to this entire PPL being freed. + */ + queue_toplevel_callback(ssh2_check_termination_callback, s); +} + +static void ssh2_check_termination(struct ssh2_connection_state *s) +{ + /* + * Decide whether we should terminate the SSH connection now. + * Called after a channel or a downstream goes away. The general + * policy is that we terminate when none of either is left. + */ + + if (s->persistent) + return; /* persistent mode: never proactively terminate */ + + if (!s->started) { + /* At startup, we don't have any channels open because we + * haven't got round to opening the main one yet. In that + * situation, we don't want to terminate, even if a sharing + * connection opens and closes and causes a call to this + * function. */ + return; + } + + if (count234(s->channels) == 0 && + !(s->connshare && share_ndownstreams(s->connshare) > 0)) { + /* + * We used to send SSH_MSG_DISCONNECT here, because I'd + * believed that _every_ conforming SSH-2 connection had to + * end with a disconnect being sent by at least one side; + * apparently I was wrong and it's perfectly OK to + * unceremoniously slam the connection shut when you're done, + * and indeed OpenSSH feels this is more polite than sending a + * DISCONNECT. So now we don't. + */ + ssh_user_close(s->ppl.ssh, "All channels closed"); + return; + } +} + +/* + * Set up most of a new ssh2_channel. Nulls out sharectx, but leaves + * chan untouched (since it will sometimes have been filled in before + * calling this). + */ +void ssh2_channel_init(struct ssh2_channel *c) +{ + struct ssh2_connection_state *s = c->connlayer; + c->closes = 0; + c->pending_eof = false; + c->throttling_conn = false; + c->throttled_by_backlog = false; + c->sharectx = NULL; + c->locwindow = c->locmaxwin = c->remlocwin = + s->ssh_is_simple ? OUR_V2_BIGWIN : OUR_V2_WINSIZE; + c->chanreq_head = NULL; + c->throttle_state = UNTHROTTLED; + bufchain_init(&c->outbuffer); + bufchain_init(&c->errbuffer); + c->sc.vt = &ssh2channel_vtable; + c->sc.cl = &s->cl; + c->localid = alloc_channel_id(s->channels, struct ssh2_channel); + add234(s->channels, c); +} + +/* + * Construct the common parts of a CHANNEL_OPEN. + */ +PktOut *ssh2_chanopen_init(struct ssh2_channel *c, const char *type) +{ + struct ssh2_connection_state *s = c->connlayer; + PktOut *pktout; + + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_CHANNEL_OPEN); + put_stringz(pktout, type); + put_uint32(pktout, c->localid); + put_uint32(pktout, c->locwindow); /* our window size */ + put_uint32(pktout, OUR_V2_MAXPKT); /* our max pkt size */ + return pktout; +} + +/* + * Construct the common parts of a CHANNEL_REQUEST. If handler is not + * NULL then a reply will be requested and the handler will be called + * when it arrives. The returned packet is ready to have any + * request-specific data added and be sent. Note that if a handler is + * provided, it's essential that the request actually be sent. + * + * The handler will usually be passed the response packet in pktin. If + * pktin is NULL, this means that no reply will ever be forthcoming + * (e.g. because the entire connection is being destroyed, or because + * the server initiated channel closure before we saw the response) + * and the handler should free any storage it's holding. + */ +PktOut *ssh2_chanreq_init(struct ssh2_channel *c, const char *type, + cr_handler_fn_t handler, void *ctx) +{ + struct ssh2_connection_state *s = c->connlayer; + PktOut *pktout; + + assert(!(c->closes & (CLOSES_SENT_CLOSE | CLOSES_RCVD_CLOSE))); + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_CHANNEL_REQUEST); + put_uint32(pktout, c->remoteid); + put_stringz(pktout, type); + put_bool(pktout, handler != NULL); + if (handler != NULL) { + struct outstanding_channel_request *ocr = + snew(struct outstanding_channel_request); + + ocr->handler = handler; + ocr->ctx = ctx; + ocr->next = NULL; + if (!c->chanreq_head) + c->chanreq_head = ocr; + else + c->chanreq_tail->next = ocr; + c->chanreq_tail = ocr; + } + return pktout; +} + +static Conf *ssh2channel_get_conf(SshChannel *sc) +{ + struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc); + struct ssh2_connection_state *s = c->connlayer; + return s->conf; +} + +static void ssh2channel_write_eof(SshChannel *sc) +{ + struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc); + + if (c->closes & CLOSES_SENT_EOF) + return; + + c->pending_eof = true; + ssh2_channel_try_eof(c); +} + +static void ssh2channel_initiate_close(SshChannel *sc, const char *err) +{ + struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc); + char *reason; + + reason = err ? dupprintf("due to local error: %s", err) : NULL; + ssh2_channel_close_local(c, reason); + sfree(reason); + c->pending_eof = false; /* this will confuse a zombie channel */ + + ssh2_channel_check_close(c); +} + +static void ssh2channel_unthrottle(SshChannel *sc, size_t bufsize) +{ + struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc); + struct ssh2_connection_state *s = c->connlayer; + size_t buflimit; + + buflimit = s->ssh_is_simple ? 0 : c->locmaxwin; + if (bufsize < buflimit) + ssh2_set_window(c, buflimit - bufsize); + + if (c->throttling_conn && bufsize <= buflimit) { + c->throttling_conn = false; + ssh_throttle_conn(s->ppl.ssh, -1); + } +} + +static size_t ssh2channel_write( + SshChannel *sc, bool is_stderr, const void *buf, size_t len) +{ + struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc); + assert(!(c->closes & CLOSES_SENT_EOF)); + bufchain_add(is_stderr ? &c->errbuffer : &c->outbuffer, buf, len); + return ssh2_try_send(c); +} + +static void ssh2channel_x11_sharing_handover( + SshChannel *sc, ssh_sharing_connstate *share_cs, share_channel *share_chan, + const char *peer_addr, int peer_port, int endian, + int protomajor, int protominor, const void *initial_data, int initial_len) +{ + struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc); + /* + * This function is called when we've just discovered that an X + * forwarding channel on which we'd been handling the initial auth + * ourselves turns out to be destined for a connection-sharing + * downstream. So we turn the channel into a sharing one, meaning + * that we completely stop tracking windows and buffering data and + * just pass more or less unmodified SSH messages back and forth. + */ + c->sharectx = share_cs; + share_setup_x11_channel(share_cs, share_chan, + c->localid, c->remoteid, c->remwindow, + c->remmaxpkt, c->locwindow, + peer_addr, peer_port, endian, + protomajor, protominor, + initial_data, initial_len); + chan_free(c->chan); + c->chan = NULL; +} + +static void ssh2channel_window_override_removed(SshChannel *sc) +{ + struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc); + struct ssh2_connection_state *s = c->connlayer; + + /* + * This function is called when a client-side Channel has just + * stopped requiring an initial fixed-size window. + */ + assert(!c->chan->initial_fixed_window_size); + ssh2_set_window(c, s->ssh_is_simple ? OUR_V2_BIGWIN : OUR_V2_WINSIZE); +} + +static void ssh2channel_hint_channel_is_simple(SshChannel *sc) +{ + struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc); + struct ssh2_connection_state *s = c->connlayer; + + PktOut *pktout = ssh2_chanreq_init( + c, "simple@putty.projects.tartarus.org", NULL, NULL); + pq_push(s->ppl.out_pq, pktout); +} + +static SshChannel *ssh2_lportfwd_open( + ConnectionLayer *cl, const char *hostname, int port, + const char *description, const SocketPeerInfo *pi, Channel *chan) +{ + struct ssh2_connection_state *s = + container_of(cl, struct ssh2_connection_state, cl); + struct ssh2_channel *c = snew(struct ssh2_channel); + PktOut *pktout; + + c->connlayer = s; + ssh2_channel_init(c); + c->halfopen = true; + c->chan = chan; + + pktout = ssh2_portfwd_chanopen(s, c, hostname, port, description, pi); + pq_push(s->ppl.out_pq, pktout); + + return &c->sc; +} + +static void ssh2_sharing_globreq_response( + struct ssh2_connection_state *s, PktIn *pktin, void *ctx) +{ + ssh_sharing_connstate *cs = (ssh_sharing_connstate *)ctx; + share_got_pkt_from_server(cs, pktin->type, + BinarySource_UPCAST(pktin)->data, + BinarySource_UPCAST(pktin)->len); +} + +static void ssh2_sharing_queue_global_request( + ConnectionLayer *cl, ssh_sharing_connstate *cs) +{ + struct ssh2_connection_state *s = + container_of(cl, struct ssh2_connection_state, cl); + ssh2_queue_global_request_handler(s, ssh2_sharing_globreq_response, cs); +} + +static void ssh2_sharing_no_more_downstreams(ConnectionLayer *cl) +{ + struct ssh2_connection_state *s = + container_of(cl, struct ssh2_connection_state, cl); + queue_toplevel_callback(ssh2_check_termination_callback, s); +} + +static struct X11FakeAuth *ssh2_add_x11_display( + ConnectionLayer *cl, int authtype, struct X11Display *disp) +{ + struct ssh2_connection_state *s = + container_of(cl, struct ssh2_connection_state, cl); + struct X11FakeAuth *auth = x11_invent_fake_auth(s->x11authtree, authtype); + auth->disp = disp; + return auth; +} + +static struct X11FakeAuth *ssh2_add_sharing_x11_display( + ConnectionLayer *cl, int authtype, ssh_sharing_connstate *share_cs, + share_channel *share_chan) +{ + struct ssh2_connection_state *s = + container_of(cl, struct ssh2_connection_state, cl); + struct X11FakeAuth *auth; + + /* + * Make up a new set of fake X11 auth data, and add it to the tree + * of currently valid ones with an indication of the sharing + * context that it's relevant to. + */ + auth = x11_invent_fake_auth(s->x11authtree, authtype); + auth->share_cs = share_cs; + auth->share_chan = share_chan; + + return auth; +} + +static void ssh2_remove_sharing_x11_display( + ConnectionLayer *cl, struct X11FakeAuth *auth) +{ + struct ssh2_connection_state *s = + container_of(cl, struct ssh2_connection_state, cl); + del234(s->x11authtree, auth); + x11_free_fake_auth(auth); +} + +static unsigned ssh2_alloc_sharing_channel( + ConnectionLayer *cl, ssh_sharing_connstate *connstate) +{ + struct ssh2_connection_state *s = + container_of(cl, struct ssh2_connection_state, cl); + struct ssh2_channel *c = snew(struct ssh2_channel); + + c->connlayer = s; + ssh2_channel_init(c); + c->chan = NULL; + c->sharectx = connstate; + return c->localid; +} + +static void ssh2_delete_sharing_channel(ConnectionLayer *cl, unsigned localid) +{ + struct ssh2_connection_state *s = + container_of(cl, struct ssh2_connection_state, cl); + struct ssh2_channel *c = find234(s->channels, &localid, ssh2_channelfind); + if (c) + ssh2_channel_destroy(c); +} + +static void ssh2_send_packet_from_downstream( + ConnectionLayer *cl, unsigned id, int type, + const void *data, int datalen, const char *additional_log_text) +{ + struct ssh2_connection_state *s = + container_of(cl, struct ssh2_connection_state, cl); + PktOut *pkt = ssh_bpp_new_pktout(s->ppl.bpp, type); + pkt->downstream_id = id; + pkt->additional_log_text = additional_log_text; + put_data(pkt, data, datalen); + pq_push(s->ppl.out_pq, pkt); +} + +static bool ssh2_agent_forwarding_permitted(ConnectionLayer *cl) +{ + struct ssh2_connection_state *s = + container_of(cl, struct ssh2_connection_state, cl); + return conf_get_bool(s->conf, CONF_agentfwd) && agent_exists(); +} + +static bool ssh2_connection_get_specials( + PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx) +{ + struct ssh2_connection_state *s = + container_of(ppl, struct ssh2_connection_state, ppl); + bool toret = false; + + if (s->mainchan) { + mainchan_get_specials(s->mainchan, add_special, ctx); + toret = true; + } + + /* + * 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 (!(s->ppl.remote_bugs & BUG_CHOKES_ON_SSH2_IGNORE)) { + if (toret) + add_special(ctx, NULL, SS_SEP, 0); + + add_special(ctx, "IGNORE message", SS_NOP, 0); + toret = true; + } + + return toret; +} + +static void ssh2_connection_special_cmd(PacketProtocolLayer *ppl, + SessionSpecialCode code, int arg) +{ + struct ssh2_connection_state *s = + container_of(ppl, struct ssh2_connection_state, ppl); + PktOut *pktout; + + if (code == SS_PING || code == SS_NOP) { + if (!(s->ppl.remote_bugs & BUG_CHOKES_ON_SSH2_IGNORE)) { + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_IGNORE); + put_stringz(pktout, ""); + pq_push(s->ppl.out_pq, pktout); + } + } else if (s->mainchan) { + mainchan_special_cmd(s->mainchan, code, arg); + } +} + +static void ssh2_terminal_size(ConnectionLayer *cl, int width, int height) +{ + struct ssh2_connection_state *s = + container_of(cl, struct ssh2_connection_state, cl); + + s->term_width = width; + s->term_height = height; + if (s->mainchan) + mainchan_terminal_size(s->mainchan, width, height); +} + +static void ssh2_stdout_unthrottle(ConnectionLayer *cl, size_t bufsize) +{ + struct ssh2_connection_state *s = + container_of(cl, struct ssh2_connection_state, cl); + + if (s->mainchan) + sshfwd_unthrottle(s->mainchan_sc, bufsize); +} + +static size_t ssh2_stdin_backlog(ConnectionLayer *cl) +{ + struct ssh2_connection_state *s = + container_of(cl, struct ssh2_connection_state, cl); + struct ssh2_channel *c; + + if (!s->mainchan) + return 0; + c = container_of(s->mainchan_sc, struct ssh2_channel, sc); + return s->mainchan ? + bufchain_size(&c->outbuffer) + bufchain_size(&c->errbuffer) : 0; +} + +static void ssh2_throttle_all_channels(ConnectionLayer *cl, bool throttled) +{ + struct ssh2_connection_state *s = + container_of(cl, struct ssh2_connection_state, cl); + struct ssh2_channel *c; + int i; + + s->all_channels_throttled = throttled; + + for (i = 0; NULL != (c = index234(s->channels, i)); i++) + if (!c->sharectx) + ssh2_channel_check_throttle(c); +} + +static bool ssh2_ldisc_option(ConnectionLayer *cl, int option) +{ + struct ssh2_connection_state *s = + container_of(cl, struct ssh2_connection_state, cl); + + return s->ldisc_opts[option]; +} + +static void ssh2_set_ldisc_option(ConnectionLayer *cl, int option, bool value) +{ + struct ssh2_connection_state *s = + container_of(cl, struct ssh2_connection_state, cl); + + s->ldisc_opts[option] = value; +} + +static void ssh2_enable_x_fwd(ConnectionLayer *cl) +{ + struct ssh2_connection_state *s = + container_of(cl, struct ssh2_connection_state, cl); + + s->X11_fwd_enabled = true; +} + +static void ssh2_set_wants_user_input(ConnectionLayer *cl, bool wanted) +{ + struct ssh2_connection_state *s = + container_of(cl, struct ssh2_connection_state, cl); + + s->want_user_input = wanted; +} + +static bool ssh2_connection_want_user_input(PacketProtocolLayer *ppl) +{ + struct ssh2_connection_state *s = + container_of(ppl, struct ssh2_connection_state, ppl); + return s->want_user_input; +} + +static void ssh2_connection_got_user_input(PacketProtocolLayer *ppl) +{ + struct ssh2_connection_state *s = + container_of(ppl, struct ssh2_connection_state, ppl); + + while (s->mainchan && bufchain_size(s->ppl.user_input) > 0) { + /* + * Add user input to the main channel's buffer. + */ + ptrlen data = bufchain_prefix(s->ppl.user_input); + sshfwd_write(s->mainchan_sc, data.ptr, data.len); + bufchain_consume(s->ppl.user_input, data.len); + } +} + +static void ssh2_connection_reconfigure(PacketProtocolLayer *ppl, Conf *conf) +{ + struct ssh2_connection_state *s = + container_of(ppl, struct ssh2_connection_state, ppl); + + conf_free(s->conf); + s->conf = conf_copy(conf); + + if (s->portfwdmgr_configured) + portfwdmgr_config(s->portfwdmgr, s->conf); +} diff --git a/ssh/connection2.h b/ssh/connection2.h new file mode 100644 index 00000000..d3bb240a --- /dev/null +++ b/ssh/connection2.h @@ -0,0 +1,235 @@ +#ifndef PUTTY_SSH2CONNECTION_H +#define PUTTY_SSH2CONNECTION_H + +struct outstanding_channel_request; +struct outstanding_global_request; + +struct ssh2_connection_state { + int crState; + + ssh_sharing_state *connshare; + char *peer_verstring; + + mainchan *mainchan; + SshChannel *mainchan_sc; + bool ldisc_opts[LD_N_OPTIONS]; + int session_attempt, session_status; + int term_width, term_height; + bool want_user_input; + + bool ssh_is_simple; + bool persistent; + bool started; + + Conf *conf; + + tree234 *channels; /* indexed by local id */ + bool all_channels_throttled; + + bool X11_fwd_enabled; + tree234 *x11authtree; + + bool got_pty; + + tree234 *rportfwds; + PortFwdManager *portfwdmgr; + bool portfwdmgr_configured; + + prompts_t *antispoof_prompt; + int antispoof_ret; + + const SftpServerVtable *sftpserver_vt; + const SshServerConfig *ssc; + + /* + * These store the list of global requests that we're waiting for + * replies to. (REQUEST_FAILURE doesn't come with any indication + * of what message caused it, so we have to keep track of the + * queue ourselves.) + */ + struct outstanding_global_request *globreq_head, *globreq_tail; + + ConnectionLayer cl; + PacketProtocolLayer ppl; +}; + +typedef void (*gr_handler_fn_t)(struct ssh2_connection_state *s, + PktIn *pktin, void *ctx); +void ssh2_queue_global_request_handler( + struct ssh2_connection_state *s, gr_handler_fn_t handler, void *ctx); + +struct ssh2_channel { + struct ssh2_connection_state *connlayer; + + unsigned remoteid, localid; + int type; + /* True if we opened this channel but server hasn't confirmed. */ + bool halfopen; + + /* Bitmap of whether we've sent/received CHANNEL_EOF and + * CHANNEL_CLOSE. */ +#define CLOSES_SENT_EOF 1 +#define CLOSES_SENT_CLOSE 2 +#define CLOSES_RCVD_EOF 4 +#define CLOSES_RCVD_CLOSE 8 + int closes; + + /* + * This flag indicates that an EOF is pending on the outgoing side + * of the channel: that is, wherever we're getting the data for + * this channel has sent us some data followed by EOF. We can't + * actually send the EOF until we've finished sending the data, so + * we set this flag instead to remind us to do so once our buffer + * is clear. + */ + bool pending_eof; + + /* + * True if this channel is causing the underlying connection to be + * throttled. + */ + bool throttling_conn; + + /* + * True if we currently have backed-up data on the direction of + * this channel pointing out of the SSH connection, and therefore + * would prefer the 'Channel' implementation not to read further + * local input if possible. + */ + bool throttled_by_backlog; + + bufchain outbuffer, errbuffer; + 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 channel requests that we're waiting for + * replies to. (CHANNEL_FAILURE doesn't come with any indication + * of what message caused it, so we have to keep track of the + * queue ourselves.) + */ + struct outstanding_channel_request *chanreq_head, *chanreq_tail; + + enum { THROTTLED, UNTHROTTLING, UNTHROTTLED } throttle_state; + + ssh_sharing_connstate *sharectx; /* sharing context, if this is a + * downstream channel */ + Channel *chan; /* handle the client side of this channel, if not */ + SshChannel sc; /* entry point for chan to talk back to */ +}; + +typedef void (*cr_handler_fn_t)(struct ssh2_channel *, PktIn *, void *); + +void ssh2_channel_init(struct ssh2_channel *c); +PktOut *ssh2_chanreq_init(struct ssh2_channel *c, const char *type, + cr_handler_fn_t handler, void *ctx); + +typedef enum ChanopenOutcome { + CHANOPEN_RESULT_FAILURE, + CHANOPEN_RESULT_SUCCESS, + CHANOPEN_RESULT_DOWNSTREAM, +} ChanopenOutcome; + +typedef struct ChanopenResult { + ChanopenOutcome outcome; + union { + struct { + char *wire_message; /* must be freed by recipient */ + unsigned reason_code; + } failure; + struct { + Channel *channel; + } success; + struct { + ssh_sharing_connstate *share_ctx; + } downstream; + } u; +} ChanopenResult; + +PktOut *ssh2_chanopen_init(struct ssh2_channel *c, const char *type); + +PktOut *ssh2_portfwd_chanopen( + struct ssh2_connection_state *s, struct ssh2_channel *c, + const char *hostname, int port, + const char *description, const SocketPeerInfo *peerinfo); + +struct ssh_rportfwd *ssh2_rportfwd_alloc( + ConnectionLayer *cl, + const char *shost, int sport, const char *dhost, int dport, + int addressfamily, const char *log_description, PortFwdRecord *pfr, + ssh_sharing_connstate *share_ctx); +void ssh2_rportfwd_remove( + ConnectionLayer *cl, struct ssh_rportfwd *rpf); +SshChannel *ssh2_session_open(ConnectionLayer *cl, Channel *chan); +SshChannel *ssh2_serverside_x11_open( + ConnectionLayer *cl, Channel *chan, const SocketPeerInfo *pi); +SshChannel *ssh2_serverside_agent_open(ConnectionLayer *cl, Channel *chan); + +void ssh2channel_send_exit_status(SshChannel *c, int status); +void ssh2channel_send_exit_signal( + SshChannel *c, ptrlen signame, bool core_dumped, ptrlen msg); +void ssh2channel_send_exit_signal_numeric( + SshChannel *c, int signum, bool core_dumped, ptrlen msg); +void ssh2channel_request_x11_forwarding( + SshChannel *c, bool want_reply, const char *authproto, + const char *authdata, int screen_number, bool oneshot); +void ssh2channel_request_agent_forwarding(SshChannel *c, bool want_reply); +void ssh2channel_request_pty( + SshChannel *c, bool want_reply, Conf *conf, int w, int h); +bool ssh2channel_send_env_var( + SshChannel *c, bool want_reply, const char *var, const char *value); +void ssh2channel_start_shell(SshChannel *c, bool want_reply); +void ssh2channel_start_command( + SshChannel *c, bool want_reply, const char *command); +bool ssh2channel_start_subsystem( + SshChannel *c, bool want_reply, const char *subsystem); +bool ssh2channel_send_env_var( + SshChannel *c, bool want_reply, const char *var, const char *value); +bool ssh2channel_send_serial_break( + SshChannel *c, bool want_reply, int length); +bool ssh2channel_send_signal( + SshChannel *c, bool want_reply, const char *signame); +void ssh2channel_send_terminal_size_change(SshChannel *c, int w, int h); + +#define CHANOPEN_RETURN_FAILURE(code, msgparams) do \ + { \ + ChanopenResult toret; \ + toret.outcome = CHANOPEN_RESULT_FAILURE; \ + toret.u.failure.reason_code = code; \ + toret.u.failure.wire_message = dupprintf msgparams; \ + return toret; \ + } while (0) + +#define CHANOPEN_RETURN_SUCCESS(chan) do \ + { \ + ChanopenResult toret; \ + toret.outcome = CHANOPEN_RESULT_SUCCESS; \ + toret.u.success.channel = chan; \ + return toret; \ + } while (0) + +#define CHANOPEN_RETURN_DOWNSTREAM(shctx) do \ + { \ + ChanopenResult toret; \ + toret.outcome = CHANOPEN_RESULT_DOWNSTREAM; \ + toret.u.downstream.share_ctx = shctx; \ + return toret; \ + } while (0) + +ChanopenResult ssh2_connection_parse_channel_open( + struct ssh2_connection_state *s, ptrlen type, + PktIn *pktin, SshChannel *sc); + +bool ssh2_connection_parse_global_request( + struct ssh2_connection_state *s, ptrlen type, PktIn *pktin); + +bool ssh2_connection_need_antispoof_prompt(struct ssh2_connection_state *s); + +#endif /* PUTTY_SSH2CONNECTION_H */ diff --git a/ssh/crc-attack-detector.c b/ssh/crc-attack-detector.c new file mode 100644 index 00000000..18044754 --- /dev/null +++ b/ssh/crc-attack-detector.c @@ -0,0 +1,171 @@ +/* $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 + * + * + * Modified for use in PuTTY by Simon Tatham + */ + +#include +#include "misc.h" +#include "ssh.h" + +/* SSH Constants */ +#define SSH_MAXBLOCKS (32 * 1024) +#define SSH_BLOCKSIZE (8) + +/* Hashing constants */ +#define HASH_MINSIZE (8 * 1024) +#define HASH_ENTRYSIZE (sizeof(uint16_t)) +#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)) + +static const uint8_t ONE[4] = { 1, 0, 0, 0 }; +static const uint8_t ZERO[4] = { 0, 0, 0, 0 }; + +struct crcda_ctx { + uint16_t *h; + uint32_t n; +}; + +struct crcda_ctx *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(struct crcda_ctx *ctx) +{ + if (ctx) { + sfree(ctx->h); + ctx->h = NULL; + sfree(ctx); + } +} + +static void crc_update(uint32_t *a, const void *b) +{ + *a = crc32_update(*a, make_ptrlen(b, 4)); +} + +/* detect if a block is used in a particular pattern */ +static bool check_crc(const uint8_t *S, const uint8_t *buf, + uint32_t len, const uint8_t *IV) +{ + uint32_t crc; + const uint8_t *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 */ +bool detect_attack(struct crcda_ctx *ctx, + const unsigned char *buf, uint32_t len, + const unsigned char *IV) +{ + register uint32_t i, j; + uint32_t l; + register const uint8_t *c; + const uint8_t *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_t); + } else { + if (l > ctx->n) { + ctx->n = l; + ctx->h = sresize(ctx->h, ctx->n, uint16_t); + } + } + + 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 true; /* attack detected */ + else + break; + } + for (d = buf; d < c; d += SSH_BLOCKSIZE) { + if (!CMP(c, d)) { + if ((check_crc(c, buf, len, IV))) + return true; /* attack detected */ + else + break; + } + } + } + return false; /* 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) { + assert(IV); /* or we wouldn't have stored HASH_IV above */ + if (!CMP(c, IV)) { + if (check_crc(c, buf, len, IV)) + return true; /* attack detected */ + else + break; + } + } else if (!CMP(c, buf + ctx->h[i] * SSH_BLOCKSIZE)) { + if (check_crc(c, buf, len, IV)) + return true; /* attack detected */ + else + break; + } + } + ctx->h[i] = j; + } + return false; /* ok */ +} diff --git a/ssh/gss.h b/ssh/gss.h new file mode 100644 index 00000000..eaef5dd9 --- /dev/null +++ b/ssh/gss.h @@ -0,0 +1,217 @@ +#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_BAD_MIC, + SSH_GSS_NO_CREDS, + 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; + +#define GSS_NO_EXPIRATION ((time_t)-1) + +#define GSS_DEF_REKEY_MINS 2 /* Default minutes between GSS cache checks */ + +/* Functions, provided by either wingss.c or gssc.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(Conf *conf); +void ssh_gss_cleanup(struct ssh_gss_liblist *list); + +/* + * Fills in buf with a string describing the GSSAPI mechanism in + * use. buf->data is not dynamically allocated. + */ +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, time_t *expiry, + unsigned long *lifetime); + +/* + * 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 *, + time_t *expiry); + +/* + * 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); + +/* + * Validates an input MIC for some input data. + */ +typedef Ssh_gss_stat (*t_ssh_gss_verify_mic)(struct ssh_gss_library *lib, + Ssh_gss_ctx ctx, + Ssh_gss_buf *in_data, + Ssh_gss_buf *in_mic); + +/* + * 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_verify_mic verify_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; +}; + +/* + * State that has to be shared between all GSSAPI-using parts of the + * same SSH connection, in particular between GSS key exchange and the + * subsequent trivial userauth method that reuses its output. + */ +struct ssh_connection_shared_gss_state { + struct ssh_gss_liblist *libs; + struct ssh_gss_library *lib; + Ssh_gss_name srv_name; + Ssh_gss_ctx ctx; +}; + +#endif /* NO_GSSAPI */ + +#endif /*PUTTY_SSHGSS_H*/ diff --git a/ssh/gssc.c b/ssh/gssc.c new file mode 100644 index 00000000..0224afe2 --- /dev/null +++ b/ssh/gssc.c @@ -0,0 +1,288 @@ +#include "putty.h" + +#include +#include +#include "gssc.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); + + 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, + time_t *expiry) +{ + struct gssapi_functions *gss = &lib->u.gssapi; + gss_OID_set_desc k5only = { 1, GSS_MECH_KRB5 }; + gss_cred_id_t cred; + OM_uint32 dummy; + OM_uint32 time_rec; + gssapi_ssh_gss_ctx *gssctx = snew(gssapi_ssh_gss_ctx); + + gssctx->ctx = GSS_C_NO_CONTEXT; + gssctx->expiry = 0; + + gssctx->maj_stat = + gss->acquire_cred(&gssctx->min_stat, GSS_C_NO_NAME, GSS_C_INDEFINITE, + &k5only, GSS_C_INITIATE, &cred, + (gss_OID_set *)0, &time_rec); + + if (gssctx->maj_stat != GSS_S_COMPLETE) { + sfree(gssctx); + return SSH_GSS_FAILURE; + } + + /* + * When the credential lifetime is not yet available due to deferred + * processing, gss_acquire_cred should return a 0 lifetime which is + * distinct from GSS_C_INDEFINITE which signals a crential that never + * expires. However, not all implementations get this right, and with + * Kerberos, initiator credentials always expire at some point. So when + * lifetime is 0 or GSS_C_INDEFINITE we call gss_inquire_cred_by_mech() to + * complete deferred processing. + */ + if (time_rec == GSS_C_INDEFINITE || time_rec == 0) { + gssctx->maj_stat = + gss->inquire_cred_by_mech(&gssctx->min_stat, cred, + (gss_OID) GSS_MECH_KRB5, + GSS_C_NO_NAME, + &time_rec, + NULL, + NULL); + } + (void) gss->release_cred(&dummy, &cred); + + if (gssctx->maj_stat != GSS_S_COMPLETE) { + sfree(gssctx); + return SSH_GSS_FAILURE; + } + + if (time_rec != GSS_C_INDEFINITE) + gssctx->expiry = time(NULL) + time_rec; + else + gssctx->expiry = GSS_NO_EXPIRATION; + + if (expiry) { + *expiry = gssctx->expiry; + } + + *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, + time_t *expiry, + unsigned long *lifetime) +{ + struct gssapi_functions *gss = &lib->u.gssapi; + gssapi_ssh_gss_ctx *gssctx = (gssapi_ssh_gss_ctx*) *ctx; + OM_uint32 ret_flags; + OM_uint32 lifetime_rec; + + 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, + &lifetime_rec); + + if (lifetime) { + if (lifetime_rec == GSS_C_INDEFINITE) + *lifetime = ULONG_MAX; + else + *lifetime = lifetime_rec; + } + if (expiry) { + if (lifetime_rec == GSS_C_INDEFINITE) + *expiry = GSS_NO_EXPIRATION; + else + *expiry = time(NULL) + lifetime_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); + *ctx = NULL; + + 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_verify_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->verify_mic(&(gssctx->min_stat), gssctx->ctx, buf, hash, NULL); +} + +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->verify_mic = ssh_gssapi_verify_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 diff --git a/ssh/gssc.h b/ssh/gssc.h new file mode 100644 index 00000000..d1d99eb1 --- /dev/null +++ b/ssh/gssc.h @@ -0,0 +1,24 @@ +#ifndef PUTTY_SSHGSSC_H +#define PUTTY_SSHGSSC_H +#include "putty.h" +#ifndef NO_GSSAPI + +#include "pgssapi.h" +#include "gss.h" + +typedef struct gssapi_ssh_gss_ctx { + OM_uint32 maj_stat; + OM_uint32 min_stat; + gss_ctx_id_t ctx; + time_t expiry; +} 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*/ diff --git a/ssh/kex2-client.c b/ssh/kex2-client.c new file mode 100644 index 00000000..4bbd8765 --- /dev/null +++ b/ssh/kex2-client.c @@ -0,0 +1,930 @@ +/* + * Client side of key exchange for the SSH-2 transport protocol (RFC 4253). + */ + +#include + +#include "putty.h" +#include "ssh.h" +#include "bpp.h" +#include "ppl.h" +#include "sshcr.h" +#include "storage.h" +#include "transport2.h" +#include "mpint.h" + +/* + * Another copy of the symbol defined in mpunsafe.c. See the comment + * there. + */ +const int deliberate_symbol_clash = 12345; + +void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) +{ + PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ + PktIn *pktin; + PktOut *pktout; + + crBegin(s->crStateKex); + + if (s->kex_alg->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->out.cipher ? s->out.cipher->real_keybits : 0; + scbits = s->in.cipher ? s->in.cipher->real_keybits : 0; + 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 > s->kex_alg->hash->hlen * 8) + s->nbits = s->kex_alg->hash->hlen * 8; + + /* + * If we're doing Diffie-Hellman group exchange, start by + * requesting a group. + */ + if (dh_is_gex(s->kex_alg)) { + ppl_logevent("Doing Diffie-Hellman group exchange"); + s->ppl.bpp->pls->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); + if (s->pbits < DH_MIN_SIZE) + s->pbits = DH_MIN_SIZE; + if (s->pbits > DH_MAX_SIZE) + s->pbits = DH_MAX_SIZE; + if ((s->ppl.remote_bugs & BUG_SSH2_OLDGEX)) { + pktout = ssh_bpp_new_pktout( + s->ppl.bpp, SSH2_MSG_KEX_DH_GEX_REQUEST_OLD); + put_uint32(pktout, s->pbits); + } else { + pktout = ssh_bpp_new_pktout( + s->ppl.bpp, SSH2_MSG_KEX_DH_GEX_REQUEST); + put_uint32(pktout, DH_MIN_SIZE); + put_uint32(pktout, s->pbits); + put_uint32(pktout, DH_MAX_SIZE); + } + pq_push(s->ppl.out_pq, pktout); + + crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL); + if (pktin->type != SSH2_MSG_KEX_DH_GEX_GROUP) { + ssh_proto_error(s->ppl.ssh, "Received unexpected packet when " + "expecting Diffie-Hellman group, type %d (%s)", + pktin->type, + ssh2_pkt_type(s->ppl.bpp->pls->kctx, + s->ppl.bpp->pls->actx, + pktin->type)); + *aborted = true; + return; + } + s->p = get_mp_ssh2(pktin); + s->g = get_mp_ssh2(pktin); + if (get_err(pktin)) { + ssh_proto_error(s->ppl.ssh, + "Unable to parse Diffie-Hellman group packet"); + *aborted = true; + return; + } + s->dh_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; + + ppl_logevent("Doing Diffie-Hellman key exchange using %d-bit " + "modulus and hash %s with a server-supplied group", + dh_modulus_bit_size(s->dh_ctx), + ssh_hash_alg(s->exhash)->text_name); + } else { + s->ppl.bpp->pls->kctx = SSH2_PKTCTX_DHGROUP; + s->dh_ctx = dh_setup_group(s->kex_alg); + s->kex_init_value = SSH2_MSG_KEXDH_INIT; + s->kex_reply_value = SSH2_MSG_KEXDH_REPLY; + + ppl_logevent("Doing Diffie-Hellman key exchange using %d-bit " + "modulus and hash %s with standard group \"%s\"", + dh_modulus_bit_size(s->dh_ctx), + ssh_hash_alg(s->exhash)->text_name, + s->kex_alg->groupname); + } + + /* + * Now generate and send e for Diffie-Hellman. + */ + seat_set_busy_status(s->ppl.seat, BUSY_CPU); + s->e = dh_create_e(s->dh_ctx, s->nbits * 2); + pktout = ssh_bpp_new_pktout(s->ppl.bpp, s->kex_init_value); + put_mp_ssh2(pktout, s->e); + pq_push(s->ppl.out_pq, pktout); + + seat_set_busy_status(s->ppl.seat, BUSY_WAITING); + crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL); + if (pktin->type != s->kex_reply_value) { + ssh_proto_error(s->ppl.ssh, "Received unexpected packet when " + "expecting Diffie-Hellman reply, type %d (%s)", + pktin->type, + ssh2_pkt_type(s->ppl.bpp->pls->kctx, + s->ppl.bpp->pls->actx, + pktin->type)); + *aborted = true; + return; + } + seat_set_busy_status(s->ppl.seat, BUSY_CPU); + s->hostkeydata = get_string(pktin); + s->hkey = ssh_key_new_pub(s->hostkey_alg, s->hostkeydata); + s->f = get_mp_ssh2(pktin); + s->sigdata = get_string(pktin); + if (get_err(pktin)) { + ssh_proto_error(s->ppl.ssh, + "Unable to parse Diffie-Hellman reply packet"); + *aborted = true; + return; + } + + { + const char *err = dh_validate_f(s->dh_ctx, s->f); + if (err) { + ssh_proto_error(s->ppl.ssh, "Diffie-Hellman reply failed " + "validation: %s", err); + *aborted = true; + return; + } + } + s->K = dh_find_K(s->dh_ctx, s->f); + + /* We assume everything from now on will be quick, and it might + * involve user interaction. */ + seat_set_busy_status(s->ppl.seat, BUSY_NOT); + + put_stringpl(s->exhash, s->hostkeydata); + if (dh_is_gex(s->kex_alg)) { + if (!(s->ppl.remote_bugs & BUG_SSH2_OLDGEX)) + put_uint32(s->exhash, DH_MIN_SIZE); + put_uint32(s->exhash, s->pbits); + if (!(s->ppl.remote_bugs & BUG_SSH2_OLDGEX)) + put_uint32(s->exhash, DH_MAX_SIZE); + put_mp_ssh2(s->exhash, s->p); + put_mp_ssh2(s->exhash, s->g); + } + put_mp_ssh2(s->exhash, s->e); + put_mp_ssh2(s->exhash, s->f); + + dh_cleanup(s->dh_ctx); + s->dh_ctx = NULL; + mp_free(s->f); s->f = NULL; + if (dh_is_gex(s->kex_alg)) { + mp_free(s->g); s->g = NULL; + mp_free(s->p); s->p = NULL; + } + } else if (s->kex_alg->main_type == KEXTYPE_ECDH) { + + ppl_logevent("Doing ECDH key exchange with curve %s and hash %s", + ssh_ecdhkex_curve_textname(s->kex_alg), + ssh_hash_alg(s->exhash)->text_name); + s->ppl.bpp->pls->kctx = SSH2_PKTCTX_ECDHKEX; + + s->ecdh_key = ssh_ecdhkex_newkey(s->kex_alg); + if (!s->ecdh_key) { + ssh_sw_abort(s->ppl.ssh, "Unable to generate key for ECDH"); + *aborted = true; + return; + } + + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEX_ECDH_INIT); + { + strbuf *pubpoint = strbuf_new(); + ssh_ecdhkex_getpublic(s->ecdh_key, BinarySink_UPCAST(pubpoint)); + put_stringsb(pktout, pubpoint); + } + + pq_push(s->ppl.out_pq, pktout); + + crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL); + if (pktin->type != SSH2_MSG_KEX_ECDH_REPLY) { + ssh_proto_error(s->ppl.ssh, "Received unexpected packet when " + "expecting ECDH reply, type %d (%s)", pktin->type, + ssh2_pkt_type(s->ppl.bpp->pls->kctx, + s->ppl.bpp->pls->actx, + pktin->type)); + *aborted = true; + return; + } + + s->hostkeydata = get_string(pktin); + put_stringpl(s->exhash, s->hostkeydata); + s->hkey = ssh_key_new_pub(s->hostkey_alg, s->hostkeydata); + + { + strbuf *pubpoint = strbuf_new(); + ssh_ecdhkex_getpublic(s->ecdh_key, BinarySink_UPCAST(pubpoint)); + put_string(s->exhash, pubpoint->u, pubpoint->len); + strbuf_free(pubpoint); + } + + { + ptrlen keydata = get_string(pktin); + put_stringpl(s->exhash, keydata); + s->K = ssh_ecdhkex_getkey(s->ecdh_key, keydata); + if (!get_err(pktin) && !s->K) { + ssh_proto_error(s->ppl.ssh, "Received invalid elliptic curve " + "point in ECDH reply"); + *aborted = true; + return; + } + } + + s->sigdata = get_string(pktin); + if (get_err(pktin)) { + ssh_proto_error(s->ppl.ssh, "Unable to parse ECDH reply packet"); + *aborted = true; + return; + } + + ssh_ecdhkex_freekey(s->ecdh_key); + s->ecdh_key = NULL; +#ifndef NO_GSSAPI + } else if (s->kex_alg->main_type == KEXTYPE_GSS) { + ptrlen data; + + s->ppl.bpp->pls->kctx = SSH2_PKTCTX_GSSKEX; + s->init_token_sent = false; + s->complete_rcvd = false; + s->hkey = NULL; + s->keystr = NULL; + + /* + * 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... + * + * This is rote from the KEXTYPE_DH section above. + */ + { + int csbits, scbits; + + csbits = s->out.cipher->real_keybits; + scbits = s->in.cipher->real_keybits; + 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 > s->kex_alg->hash->hlen * 8) + s->nbits = s->kex_alg->hash->hlen * 8; + + if (dh_is_gex(s->kex_alg)) { + /* + * Work out how big a DH group we will need to allow that + * much data. + */ + s->pbits = 512 << ((s->nbits - 1) / 64); + ppl_logevent("Doing GSSAPI (with Kerberos V5) Diffie-Hellman " + "group exchange, with minimum %d bits", s->pbits); + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEXGSS_GROUPREQ); + put_uint32(pktout, s->pbits); /* min */ + put_uint32(pktout, s->pbits); /* preferred */ + put_uint32(pktout, s->pbits * 2); /* max */ + pq_push(s->ppl.out_pq, pktout); + + crMaybeWaitUntilV( + (pktin = ssh2_transport_pop(s)) != NULL); + if (pktin->type != SSH2_MSG_KEXGSS_GROUP) { + ssh_proto_error(s->ppl.ssh, "Received unexpected packet when " + "expecting Diffie-Hellman group, type %d (%s)", + pktin->type, + ssh2_pkt_type(s->ppl.bpp->pls->kctx, + s->ppl.bpp->pls->actx, + pktin->type)); + *aborted = true; + return; + } + s->p = get_mp_ssh2(pktin); + s->g = get_mp_ssh2(pktin); + if (get_err(pktin)) { + ssh_proto_error(s->ppl.ssh, + "Unable to parse Diffie-Hellman group packet"); + *aborted = true; + return; + } + s->dh_ctx = dh_setup_gex(s->p, s->g); + } else { + s->dh_ctx = dh_setup_group(s->kex_alg); + ppl_logevent("Using GSSAPI (with Kerberos V5) Diffie-Hellman with" + " standard group \"%s\"", s->kex_alg->groupname); + } + + ppl_logevent("Doing GSSAPI (with Kerberos V5) Diffie-Hellman key " + "exchange with hash %s", ssh_hash_alg(s->exhash)->text_name); + /* Now generate e for Diffie-Hellman. */ + seat_set_busy_status(s->ppl.seat, BUSY_CPU); + s->e = dh_create_e(s->dh_ctx, s->nbits * 2); + + if (s->shgss->lib->gsslogmsg) + ppl_logevent("%s", s->shgss->lib->gsslogmsg); + + /* initial tokens are empty */ + SSH_GSS_CLEAR_BUF(&s->gss_rcvtok); + SSH_GSS_CLEAR_BUF(&s->gss_sndtok); + SSH_GSS_CLEAR_BUF(&s->mic); + s->gss_stat = s->shgss->lib->acquire_cred( + s->shgss->lib, &s->shgss->ctx, &s->gss_cred_expiry); + if (s->gss_stat != SSH_GSS_OK) { + ssh_sw_abort(s->ppl.ssh, + "GSSAPI key exchange failed to initialise"); + *aborted = true; + return; + } + + /* now enter the loop */ + assert(s->shgss->srv_name); + do { + /* + * When acquire_cred yields no useful expiration, go with the + * service ticket expiration. + */ + s->gss_stat = s->shgss->lib->init_sec_context( + s->shgss->lib, &s->shgss->ctx, s->shgss->srv_name, + s->gss_delegate, &s->gss_rcvtok, &s->gss_sndtok, + (s->gss_cred_expiry == GSS_NO_EXPIRATION ? + &s->gss_cred_expiry : NULL), NULL); + SSH_GSS_CLEAR_BUF(&s->gss_rcvtok); + + if (s->gss_stat == SSH_GSS_S_COMPLETE && s->complete_rcvd) + break; /* MIC is verified after the loop */ + + if (s->gss_stat != SSH_GSS_S_COMPLETE && + s->gss_stat != SSH_GSS_S_CONTINUE_NEEDED) { + if (s->shgss->lib->display_status( + s->shgss->lib, s->shgss->ctx, + &s->gss_buf) == SSH_GSS_OK) { + char *err = s->gss_buf.value; + ssh_sw_abort(s->ppl.ssh, + "GSSAPI key exchange failed to initialise " + "context: %s", err); + sfree(err); + *aborted = true; + return; + } + } + assert(s->gss_stat == SSH_GSS_S_COMPLETE || + s->gss_stat == SSH_GSS_S_CONTINUE_NEEDED); + + if (!s->init_token_sent) { + s->init_token_sent = true; + pktout = ssh_bpp_new_pktout(s->ppl.bpp, + SSH2_MSG_KEXGSS_INIT); + if (s->gss_sndtok.length == 0) { + ssh_sw_abort(s->ppl.ssh, "GSSAPI key exchange failed: " + "no initial context token"); + *aborted = true; + return; + } + put_string(pktout, + s->gss_sndtok.value, s->gss_sndtok.length); + put_mp_ssh2(pktout, s->e); + pq_push(s->ppl.out_pq, pktout); + s->shgss->lib->free_tok(s->shgss->lib, &s->gss_sndtok); + ppl_logevent("GSSAPI key exchange initialised"); + } else if (s->gss_sndtok.length != 0) { + pktout = ssh_bpp_new_pktout( + s->ppl.bpp, SSH2_MSG_KEXGSS_CONTINUE); + put_string(pktout, + s->gss_sndtok.value, s->gss_sndtok.length); + pq_push(s->ppl.out_pq, pktout); + s->shgss->lib->free_tok(s->shgss->lib, &s->gss_sndtok); + } + + if (s->gss_stat == SSH_GSS_S_COMPLETE && s->complete_rcvd) + break; + + wait_for_gss_token: + crMaybeWaitUntilV( + (pktin = ssh2_transport_pop(s)) != NULL); + switch (pktin->type) { + case SSH2_MSG_KEXGSS_CONTINUE: + data = get_string(pktin); + s->gss_rcvtok.value = (char *)data.ptr; + s->gss_rcvtok.length = data.len; + continue; + case SSH2_MSG_KEXGSS_COMPLETE: + s->complete_rcvd = true; + s->f = get_mp_ssh2(pktin); + data = get_string(pktin); + s->mic.value = (char *)data.ptr; + s->mic.length = data.len; + /* If there's a final token we loop to consume it */ + if (get_bool(pktin)) { + data = get_string(pktin); + s->gss_rcvtok.value = (char *)data.ptr; + s->gss_rcvtok.length = data.len; + continue; + } + break; + case SSH2_MSG_KEXGSS_HOSTKEY: + s->hostkeydata = get_string(pktin); + if (s->hostkey_alg) { + s->hkey = ssh_key_new_pub(s->hostkey_alg, + s->hostkeydata); + put_stringpl(s->exhash, s->hostkeydata); + } + /* + * Can't loop as we have no token to pass to + * init_sec_context. + */ + goto wait_for_gss_token; + case SSH2_MSG_KEXGSS_ERROR: + /* + * We have no use for the server's major and minor + * status. The minor status is really only + * meaningful to the server, and with luck the major + * status means something to us (but not really all + * that much). The string is more meaningful, and + * hopefully the server sends any error tokens, as + * that will produce the most useful information for + * us. + */ + get_uint32(pktin); /* server's major status */ + get_uint32(pktin); /* server's minor status */ + data = get_string(pktin); + ppl_logevent("GSSAPI key exchange failed; " + "server's message: %.*s", PTRLEN_PRINTF(data)); + /* Language tag, but we have no use for it */ + get_string(pktin); + /* + * Wait for an error token, if there is one, or the + * server's disconnect. The error token, if there + * is one, must follow the SSH2_MSG_KEXGSS_ERROR + * message, per the RFC. + */ + goto wait_for_gss_token; + default: + ssh_proto_error(s->ppl.ssh, "Received unexpected packet " + "during GSSAPI key exchange, type %d (%s)", + pktin->type, + ssh2_pkt_type(s->ppl.bpp->pls->kctx, + s->ppl.bpp->pls->actx, + pktin->type)); + *aborted = true; + return; + } + } while (s->gss_rcvtok.length || + s->gss_stat == SSH_GSS_S_CONTINUE_NEEDED || + !s->complete_rcvd); + + { + const char *err = dh_validate_f(s->dh_ctx, s->f); + if (err) { + ssh_proto_error(s->ppl.ssh, "GSSAPI reply failed " + "validation: %s", err); + *aborted = true; + return; + } + } + s->K = dh_find_K(s->dh_ctx, s->f); + + /* We assume everything from now on will be quick, and it might + * involve user interaction. */ + seat_set_busy_status(s->ppl.seat, BUSY_NOT); + + if (!s->hkey) + put_stringz(s->exhash, ""); + if (dh_is_gex(s->kex_alg)) { + /* min, preferred, max */ + put_uint32(s->exhash, s->pbits); + put_uint32(s->exhash, s->pbits); + put_uint32(s->exhash, s->pbits * 2); + + put_mp_ssh2(s->exhash, s->p); + put_mp_ssh2(s->exhash, s->g); + } + put_mp_ssh2(s->exhash, s->e); + put_mp_ssh2(s->exhash, s->f); + + /* + * MIC verification is done below, after we compute the hash + * used as the MIC input. + */ + + dh_cleanup(s->dh_ctx); + s->dh_ctx = NULL; + mp_free(s->f); s->f = NULL; + if (dh_is_gex(s->kex_alg)) { + mp_free(s->g); s->g = NULL; + mp_free(s->p); s->p = NULL; + } +#endif + } else { + ptrlen rsakeydata; + + assert(s->kex_alg->main_type == KEXTYPE_RSA); + ppl_logevent("Doing RSA key exchange with hash %s", + ssh_hash_alg(s->exhash)->text_name); + s->ppl.bpp->pls->kctx = SSH2_PKTCTX_RSAKEX; + /* + * RSA key exchange. First expect a KEXRSA_PUBKEY packet + * from the server. + */ + crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL); + if (pktin->type != SSH2_MSG_KEXRSA_PUBKEY) { + ssh_proto_error(s->ppl.ssh, "Received unexpected packet when " + "expecting RSA public key, type %d (%s)", + pktin->type, + ssh2_pkt_type(s->ppl.bpp->pls->kctx, + s->ppl.bpp->pls->actx, + pktin->type)); + *aborted = true; + return; + } + + s->hostkeydata = get_string(pktin); + put_stringpl(s->exhash, s->hostkeydata); + s->hkey = ssh_key_new_pub(s->hostkey_alg, s->hostkeydata); + + rsakeydata = get_string(pktin); + + s->rsa_kex_key = ssh_rsakex_newkey(rsakeydata); + if (!s->rsa_kex_key) { + ssh_proto_error(s->ppl.ssh, + "Unable to parse RSA public key packet"); + *aborted = true; + return; + } + s->rsa_kex_key_needs_freeing = true; + + put_stringpl(s->exhash, rsakeydata); + + /* + * 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->rsa_kex_key); + + const struct ssh_rsa_kex_extra *extra = + (const struct ssh_rsa_kex_extra *)s->kex_alg->extra; + if (klen < extra->minklen) { + ssh_proto_error(s->ppl.ssh, "Server sent %d-bit RSA key, " + "less than the minimum size %d for %s " + "key exchange", klen, extra->minklen, + s->kex_alg->name); + *aborted = true; + return; + } + + int nbits = klen - (2*s->kex_alg->hash->hlen*8 + 49); + assert(nbits > 0); + + strbuf *buf, *outstr; + + mp_int *tmp = mp_random_bits(nbits - 1); + s->K = mp_power_2(nbits - 1); + mp_add_into(s->K, s->K, tmp); + mp_free(tmp); + + /* + * Encode this as an mpint. + */ + buf = strbuf_new_nm(); + put_mp_ssh2(buf, s->K); + + /* + * Encrypt it with the given RSA key. + */ + outstr = ssh_rsakex_encrypt(s->rsa_kex_key, s->kex_alg->hash, + ptrlen_from_strbuf(buf)); + + /* + * And send it off in a return packet. + */ + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEXRSA_SECRET); + put_stringpl(pktout, ptrlen_from_strbuf(outstr)); + pq_push(s->ppl.out_pq, pktout); + + put_stringsb(s->exhash, outstr); /* frees outstr */ + + strbuf_free(buf); + } + + ssh_rsakex_freekey(s->rsa_kex_key); + s->rsa_kex_key = NULL; + s->rsa_kex_key_needs_freeing = false; + + crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL); + if (pktin->type != SSH2_MSG_KEXRSA_DONE) { + ssh_proto_error(s->ppl.ssh, "Received unexpected packet when " + "expecting RSA kex signature, type %d (%s)", + pktin->type, + ssh2_pkt_type(s->ppl.bpp->pls->kctx, + s->ppl.bpp->pls->actx, + pktin->type)); + *aborted = true; + return; + } + + s->sigdata = get_string(pktin); + if (get_err(pktin)) { + ssh_proto_error(s->ppl.ssh, "Unable to parse RSA kex signature"); + *aborted = true; + return; + } + } + + ssh2transport_finalise_exhash(s); + +#ifndef NO_GSSAPI + if (s->kex_alg->main_type == KEXTYPE_GSS) { + Ssh_gss_buf gss_buf; + SSH_GSS_CLEAR_BUF(&s->gss_buf); + + gss_buf.value = s->exchange_hash; + gss_buf.length = s->kex_alg->hash->hlen; + s->gss_stat = s->shgss->lib->verify_mic( + s->shgss->lib, s->shgss->ctx, &gss_buf, &s->mic); + if (s->gss_stat != SSH_GSS_OK) { + if (s->shgss->lib->display_status( + s->shgss->lib, s->shgss->ctx, &s->gss_buf) == SSH_GSS_OK) { + char *err = s->gss_buf.value; + ssh_sw_abort(s->ppl.ssh, "GSSAPI key exchange MIC was " + "not valid: %s", err); + sfree(err); + } else { + ssh_sw_abort(s->ppl.ssh, "GSSAPI key exchange MIC was " + "not valid"); + } + *aborted = true; + return; + } + + s->gss_kex_used = true; + + /*- + * If this the first KEX, save the GSS context for "gssapi-keyex" + * authentication. + * + * http://tools.ietf.org/html/rfc4462#section-4 + * + * This method may be used only if the initial key exchange was + * performed using a GSS-API-based key exchange method defined in + * accordance with Section 2. The GSS-API context used with this + * method is always that established during an initial GSS-API-based + * key exchange. Any context established during key exchange for the + * purpose of rekeying MUST NOT be used with this method. + */ + if (s->got_session_id) { + s->shgss->lib->release_cred(s->shgss->lib, &s->shgss->ctx); + } + ppl_logevent("GSSAPI Key Exchange complete!"); + } +#endif + + s->dh_ctx = NULL; + + /* In GSS keyex there's no hostkey signature to verify */ + if (s->kex_alg->main_type != KEXTYPE_GSS) { + if (!s->hkey) { + ssh_proto_error(s->ppl.ssh, "Server's host key is invalid"); + *aborted = true; + return; + } + + if (!ssh_key_verify( + s->hkey, s->sigdata, + make_ptrlen(s->exchange_hash, s->kex_alg->hash->hlen))) { +#ifndef FUZZING + ssh_proto_error(s->ppl.ssh, "Signature from server's host key " + "is invalid"); + *aborted = true; + return; +#endif + } + } + + s->keystr = (s->hkey ? ssh_key_cache_str(s->hkey) : NULL); +#ifndef NO_GSSAPI + if (s->gss_kex_used) { + /* + * In a GSS-based session, check the host key (if any) against + * the transient host key cache. + */ + if (s->kex_alg->main_type == KEXTYPE_GSS) { + + /* + * We've just done a GSS key exchange. If it gave us a + * host key, store it. + */ + if (s->hkey) { + char *fingerprint = ssh2_fingerprint( + s->hkey, SSH_FPTYPE_DEFAULT); + ppl_logevent("GSS kex provided fallback host key:"); + ppl_logevent("%s", fingerprint); + sfree(fingerprint); + + ssh_transient_hostkey_cache_add(s->thc, s->hkey); + } else if (!ssh_transient_hostkey_cache_non_empty(s->thc)) { + /* + * But if it didn't, then we currently have no + * fallback host key to use in subsequent non-GSS + * rekeys. So we should immediately trigger a non-GSS + * rekey of our own, to set one up, before the session + * keys have been used for anything else. + * + * This is similar to the cross-certification done at + * user request in the permanent host key cache, but + * here we do it automatically, once, at session + * startup, and only add the key to the transient + * cache. + */ + if (s->hostkey_alg) { + s->need_gss_transient_hostkey = true; + } else { + /* + * If we negotiated the "null" host key algorithm + * in the key exchange, that's an indication that + * no host key at all is available from the server + * (both because we listed "null" last, and + * because RFC 4462 section 5 says that a server + * MUST NOT offer "null" as a host key algorithm + * unless that is the only algorithm it provides + * at all). + * + * In that case we actually _can't_ perform a + * non-GSSAPI key exchange, so it's pointless to + * attempt one proactively. This is also likely to + * cause trouble later if a rekey is required at a + * moment whne GSS credentials are not available, + * but someone setting up a server in this + * configuration presumably accepts that as a + * consequence. + */ + if (!s->warned_about_no_gss_transient_hostkey) { + ppl_logevent("No fallback host key available"); + s->warned_about_no_gss_transient_hostkey = true; + } + } + } + } else { + /* + * We've just done a fallback key exchange, so make + * sure the host key it used is in the cache of keys + * we previously received in GSS kexes. + * + * An exception is if this was the non-GSS key exchange we + * triggered on purpose to populate the transient cache. + */ + assert(s->hkey); /* only KEXTYPE_GSS lets this be null */ + char *fingerprint = ssh2_fingerprint(s->hkey, SSH_FPTYPE_DEFAULT); + + if (s->need_gss_transient_hostkey) { + ppl_logevent("Post-GSS rekey provided fallback host key:"); + ppl_logevent("%s", fingerprint); + ssh_transient_hostkey_cache_add(s->thc, s->hkey); + s->need_gss_transient_hostkey = false; + } else if (!ssh_transient_hostkey_cache_verify(s->thc, s->hkey)) { + ppl_logevent("Non-GSS rekey after initial GSS kex " + "used host key:"); + ppl_logevent("%s", fingerprint); + sfree(fingerprint); + ssh_sw_abort(s->ppl.ssh, "Server's host key did not match any " + "used in previous GSS kex"); + *aborted = true; + return; + } + + sfree(fingerprint); + } + } else +#endif /* NO_GSSAPI */ + if (!s->got_session_id) { + /* + * Make a note of any other host key formats that are available. + */ + { + int i, j, nkeys = 0; + char *list = NULL; + for (i = 0; i < lenof(ssh2_hostkey_algs); i++) { + if (ssh2_hostkey_algs[i].alg == s->hostkey_alg) + continue; + + for (j = 0; j < s->n_uncert_hostkeys; j++) + if (s->uncert_hostkeys[j] == i) + break; + + if (j < s->n_uncert_hostkeys) { + char *newlist; + if (list) + newlist = dupprintf( + "%s/%s", list, + ssh2_hostkey_algs[i].alg->ssh_id); + else + newlist = dupprintf( + "%s", ssh2_hostkey_algs[i].alg->ssh_id); + sfree(list); + list = newlist; + nkeys++; + } + } + if (list) { + ppl_logevent("Server also has %s host key%s, but we " + "don't know %s", list, + nkeys > 1 ? "s" : "", + nkeys > 1 ? "any of them" : "it"); + sfree(list); + } + } + + /* + * Authenticate remote host: verify host key. (We've already + * checked the signature of the exchange hash.) + */ + char **fingerprints = ssh2_all_fingerprints(s->hkey); + FingerprintType fptype_default = + ssh2_pick_default_fingerprint(fingerprints); + ppl_logevent("Host key fingerprint is:"); + ppl_logevent("%s", fingerprints[fptype_default]); + /* First check against manually configured host keys. */ + s->dlgret = verify_ssh_manual_host_key( + s->conf, fingerprints, s->hkey); + if (s->dlgret == 0) { /* did not match */ + ssh2_free_all_fingerprints(fingerprints); + ssh_sw_abort(s->ppl.ssh, "Host key did not appear in manually " + "configured list"); + *aborted = true; + return; + } else if (s->dlgret < 0) { /* none configured; use standard handling */ + ssh2_userkey uk = { .key = s->hkey, .comment = NULL }; + char *keydisp = ssh2_pubkey_openssh_str(&uk); + s->dlgret = seat_verify_ssh_host_key( + s->ppl.seat, s->savedhost, s->savedport, + ssh_key_cache_id(s->hkey), s->keystr, keydisp, + fingerprints, ssh2_transport_dialog_callback, s); + sfree(keydisp); + ssh2_free_all_fingerprints(fingerprints); +#ifdef FUZZING + s->dlgret = 1; +#endif + crMaybeWaitUntilV(s->dlgret >= 0); + if (s->dlgret == 0) { + ssh_user_close(s->ppl.ssh, + "User aborted at host key verification"); + *aborted = true; + return; + } + } + + /* + * Save this host key, to check against the one presented in + * subsequent rekeys. + */ + s->hostkey_str = s->keystr; + s->keystr = NULL; + } else if (s->cross_certifying) { + assert(s->hkey); + assert(ssh_key_alg(s->hkey) == s->cross_certifying); + + char *fingerprint = ssh2_fingerprint(s->hkey, SSH_FPTYPE_DEFAULT); + ppl_logevent("Storing additional host key for this host:"); + ppl_logevent("%s", fingerprint); + sfree(fingerprint); + + store_host_key(s->savedhost, s->savedport, + ssh_key_cache_id(s->hkey), s->keystr); + /* + * Don't forget to store the new key as the one we'll be + * re-checking in future normal rekeys. + */ + s->hostkey_str = s->keystr; + s->keystr = NULL; + } else { + /* + * In a rekey, we never present an interactive host key + * verification request to the user. Instead, we simply + * enforce that the key we're seeing this time is identical to + * the one we saw before. + */ + assert(s->keystr); /* filled in by prior key exchange */ + if (strcmp(s->hostkey_str, s->keystr)) { +#ifndef FUZZING + ssh_sw_abort(s->ppl.ssh, + "Host key was different in repeat key exchange"); + *aborted = true; + return; +#endif + } + } + + sfree(s->keystr); + s->keystr = NULL; + if (s->hkey) { + ssh_key_free(s->hkey); + s->hkey = NULL; + } + + crFinishV; +} diff --git a/ssh/kex2-server.c b/ssh/kex2-server.c new file mode 100644 index 00000000..56bdc3c5 --- /dev/null +++ b/ssh/kex2-server.c @@ -0,0 +1,330 @@ +/* + * Server side of key exchange for the SSH-2 transport protocol (RFC 4253). + */ + +#include + +#include "putty.h" +#include "ssh.h" +#include "bpp.h" +#include "ppl.h" +#include "sshcr.h" +#include "server.h" +#include "sshkeygen.h" +#include "storage.h" +#include "transport2.h" +#include "mpint.h" + +void ssh2_transport_provide_hostkeys(PacketProtocolLayer *ppl, + ssh_key *const *hostkeys, int nhostkeys) +{ + struct ssh2_transport_state *s = + container_of(ppl, struct ssh2_transport_state, ppl); + + s->hostkeys = hostkeys; + s->nhostkeys = nhostkeys; +} + +static strbuf *finalise_and_sign_exhash(struct ssh2_transport_state *s) +{ + strbuf *sb; + ssh2transport_finalise_exhash(s); + sb = strbuf_new(); + ssh_key_sign( + s->hkey, make_ptrlen(s->exchange_hash, s->kex_alg->hash->hlen), + s->hkflags, BinarySink_UPCAST(sb)); + return sb; +} + +void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) +{ + PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ + PktIn *pktin; + PktOut *pktout; + + crBegin(s->crStateKex); + + { + int i; + for (i = 0; i < s->nhostkeys; i++) + if (ssh_key_alg(s->hostkeys[i]) == s->hostkey_alg) { + s->hkey = s->hostkeys[i]; + break; + } + assert(s->hkey); + } + + strbuf_clear(s->hostkeyblob); + ssh_key_public_blob(s->hkey, BinarySink_UPCAST(s->hostkeyblob)); + s->hostkeydata = ptrlen_from_strbuf(s->hostkeyblob); + + put_stringpl(s->exhash, s->hostkeydata); + + if (s->kex_alg->main_type == KEXTYPE_DH) { + /* + * If we're doing Diffie-Hellman group exchange, start by + * waiting for the group request. + */ + if (dh_is_gex(s->kex_alg)) { + ppl_logevent("Doing Diffie-Hellman group exchange"); + s->ppl.bpp->pls->kctx = SSH2_PKTCTX_DHGEX; + + crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL); + if (pktin->type != SSH2_MSG_KEX_DH_GEX_REQUEST && + pktin->type != SSH2_MSG_KEX_DH_GEX_REQUEST_OLD) { + ssh_proto_error(s->ppl.ssh, "Received unexpected packet when " + "expecting Diffie-Hellman group exchange " + "request, type %d (%s)", pktin->type, + ssh2_pkt_type(s->ppl.bpp->pls->kctx, + s->ppl.bpp->pls->actx, + pktin->type)); + *aborted = true; + return; + } + + if (pktin->type != SSH2_MSG_KEX_DH_GEX_REQUEST_OLD) { + s->dh_got_size_bounds = true; + s->dh_min_size = get_uint32(pktin); + s->pbits = get_uint32(pktin); + s->dh_max_size = get_uint32(pktin); + } else { + s->dh_got_size_bounds = false; + s->pbits = get_uint32(pktin); + } + + /* + * This is a hopeless strategy for making a secure DH + * group! It's good enough for testing a client against, + * but not for serious use. + */ + PrimeGenerationContext *pgc = primegen_new_context( + &primegen_probabilistic); + ProgressReceiver null_progress; + null_progress.vt = &null_progress_vt; + s->p = primegen_generate(pgc, pcs_new(s->pbits), &null_progress); + primegen_free_context(pgc); + + s->g = mp_from_integer(2); + s->dh_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; + + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEX_DH_GEX_GROUP); + put_mp_ssh2(pktout, s->p); + put_mp_ssh2(pktout, s->g); + pq_push(s->ppl.out_pq, pktout); + } else { + s->ppl.bpp->pls->kctx = SSH2_PKTCTX_DHGROUP; + s->dh_ctx = dh_setup_group(s->kex_alg); + s->kex_init_value = SSH2_MSG_KEXDH_INIT; + s->kex_reply_value = SSH2_MSG_KEXDH_REPLY; + ppl_logevent("Using Diffie-Hellman with standard group \"%s\"", + s->kex_alg->groupname); + } + + ppl_logevent("Doing Diffie-Hellman key exchange with hash %s", + ssh_hash_alg(s->exhash)->text_name); + + /* + * Generate e for Diffie-Hellman. + */ + s->e = dh_create_e(s->dh_ctx, s->nbits * 2); + + /* + * Wait to receive f. + */ + crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL); + if (pktin->type != s->kex_init_value) { + ssh_proto_error(s->ppl.ssh, "Received unexpected packet when " + "expecting Diffie-Hellman initial packet, " + "type %d (%s)", pktin->type, + ssh2_pkt_type(s->ppl.bpp->pls->kctx, + s->ppl.bpp->pls->actx, + pktin->type)); + *aborted = true; + return; + } + s->f = get_mp_ssh2(pktin); + if (get_err(pktin)) { + ssh_proto_error(s->ppl.ssh, + "Unable to parse Diffie-Hellman initial packet"); + *aborted = true; + return; + } + + { + const char *err = dh_validate_f(s->dh_ctx, s->f); + if (err) { + ssh_proto_error(s->ppl.ssh, "Diffie-Hellman initial packet " + "failed validation: %s", err); + *aborted = true; + return; + } + } + s->K = dh_find_K(s->dh_ctx, s->f); + + if (dh_is_gex(s->kex_alg)) { + if (s->dh_got_size_bounds) + put_uint32(s->exhash, s->dh_min_size); + put_uint32(s->exhash, s->pbits); + if (s->dh_got_size_bounds) + put_uint32(s->exhash, s->dh_max_size); + put_mp_ssh2(s->exhash, s->p); + put_mp_ssh2(s->exhash, s->g); + } + put_mp_ssh2(s->exhash, s->f); + put_mp_ssh2(s->exhash, s->e); + + pktout = ssh_bpp_new_pktout(s->ppl.bpp, s->kex_reply_value); + put_stringpl(pktout, s->hostkeydata); + put_mp_ssh2(pktout, s->e); + put_stringsb(pktout, finalise_and_sign_exhash(s)); + pq_push(s->ppl.out_pq, pktout); + + dh_cleanup(s->dh_ctx); + s->dh_ctx = NULL; + mp_free(s->f); s->f = NULL; + if (dh_is_gex(s->kex_alg)) { + mp_free(s->g); s->g = NULL; + mp_free(s->p); s->p = NULL; + } + } else if (s->kex_alg->main_type == KEXTYPE_ECDH) { + ppl_logevent("Doing ECDH key exchange with curve %s and hash %s", + ssh_ecdhkex_curve_textname(s->kex_alg), + ssh_hash_alg(s->exhash)->text_name); + s->ppl.bpp->pls->kctx = SSH2_PKTCTX_ECDHKEX; + + s->ecdh_key = ssh_ecdhkex_newkey(s->kex_alg); + if (!s->ecdh_key) { + ssh_sw_abort(s->ppl.ssh, "Unable to generate key for ECDH"); + *aborted = true; + return; + } + + crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL); + if (pktin->type != SSH2_MSG_KEX_ECDH_INIT) { + ssh_proto_error(s->ppl.ssh, "Received unexpected packet when " + "expecting ECDH initial packet, type %d (%s)", + pktin->type, + ssh2_pkt_type(s->ppl.bpp->pls->kctx, + s->ppl.bpp->pls->actx, + pktin->type)); + *aborted = true; + return; + } + + { + ptrlen keydata = get_string(pktin); + put_stringpl(s->exhash, keydata); + + s->K = ssh_ecdhkex_getkey(s->ecdh_key, keydata); + if (!get_err(pktin) && !s->K) { + ssh_proto_error(s->ppl.ssh, "Received invalid elliptic curve " + "point in ECDH initial packet"); + *aborted = true; + return; + } + } + + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEX_ECDH_REPLY); + put_stringpl(pktout, s->hostkeydata); + { + strbuf *pubpoint = strbuf_new(); + ssh_ecdhkex_getpublic(s->ecdh_key, BinarySink_UPCAST(pubpoint)); + put_string(s->exhash, pubpoint->u, pubpoint->len); + put_stringsb(pktout, pubpoint); + } + put_stringsb(pktout, finalise_and_sign_exhash(s)); + pq_push(s->ppl.out_pq, pktout); + + ssh_ecdhkex_freekey(s->ecdh_key); + s->ecdh_key = NULL; + } else if (s->kex_alg->main_type == KEXTYPE_GSS) { + ssh_sw_abort(s->ppl.ssh, "GSS key exchange not supported in server"); + } else { + assert(s->kex_alg->main_type == KEXTYPE_RSA); + ppl_logevent("Doing RSA key exchange with hash %s", + ssh_hash_alg(s->exhash)->text_name); + s->ppl.bpp->pls->kctx = SSH2_PKTCTX_RSAKEX; + + const struct ssh_rsa_kex_extra *extra = + (const struct ssh_rsa_kex_extra *)s->kex_alg->extra; + + if (s->ssc && s->ssc->rsa_kex_key) { + int klen = ssh_rsakex_klen(s->ssc->rsa_kex_key); + if (klen >= extra->minklen) { + ppl_logevent("Using configured %d-bit RSA key", klen); + s->rsa_kex_key = s->ssc->rsa_kex_key; + } else { + ppl_logevent("Configured %d-bit RSA key is too short (min %d)", + klen, extra->minklen); + } + } + + if (!s->rsa_kex_key) { + ppl_logevent("Generating a %d-bit RSA key", extra->minklen); + + s->rsa_kex_key = snew(RSAKey); + + PrimeGenerationContext *pgc = primegen_new_context( + &primegen_probabilistic); + ProgressReceiver null_progress; + null_progress.vt = &null_progress_vt; + rsa_generate(s->rsa_kex_key, extra->minklen, false, + pgc, &null_progress); + primegen_free_context(pgc); + + s->rsa_kex_key->comment = NULL; + s->rsa_kex_key_needs_freeing = true; + } + + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEXRSA_PUBKEY); + put_stringpl(pktout, s->hostkeydata); + { + strbuf *pubblob = strbuf_new(); + ssh_key_public_blob(&s->rsa_kex_key->sshk, + BinarySink_UPCAST(pubblob)); + put_string(s->exhash, pubblob->u, pubblob->len); + put_stringsb(pktout, pubblob); + } + pq_push(s->ppl.out_pq, pktout); + + crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL); + if (pktin->type != SSH2_MSG_KEXRSA_SECRET) { + ssh_proto_error(s->ppl.ssh, "Received unexpected packet when " + "expecting RSA kex secret, type %d (%s)", + pktin->type, + ssh2_pkt_type(s->ppl.bpp->pls->kctx, + s->ppl.bpp->pls->actx, + pktin->type)); + *aborted = true; + return; + } + + { + ptrlen encrypted_secret = get_string(pktin); + put_stringpl(s->exhash, encrypted_secret); + s->K = ssh_rsakex_decrypt( + s->rsa_kex_key, s->kex_alg->hash, encrypted_secret); + } + + if (!s->K) { + ssh_proto_error(s->ppl.ssh, "Unable to decrypt RSA kex secret"); + *aborted = true; + return; + } + + if (s->rsa_kex_key_needs_freeing) { + ssh_rsakex_freekey(s->rsa_kex_key); + sfree(s->rsa_kex_key); + } + s->rsa_kex_key = NULL; + s->rsa_kex_key_needs_freeing = false; + + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEXRSA_DONE); + put_stringsb(pktout, finalise_and_sign_exhash(s)); + pq_push(s->ppl.out_pq, pktout); + } + + crFinishV; +} diff --git a/ssh/login1-server.c b/ssh/login1-server.c new file mode 100644 index 00000000..040342da --- /dev/null +++ b/ssh/login1-server.c @@ -0,0 +1,447 @@ +/* + * Packet protocol layer for the SSH-1 login phase, from the server side. + */ + +#include + +#include "putty.h" +#include "mpint.h" +#include "ssh.h" +#include "bpp.h" +#include "ppl.h" +#include "sshcr.h" +#include "server.h" +#include "sshkeygen.h" + +struct ssh1_login_server_state { + int crState; + + PacketProtocolLayer *successor_layer; + + const SshServerConfig *ssc; + + int remote_protoflags; + int local_protoflags; + unsigned long supported_ciphers_mask, supported_auths_mask; + unsigned cipher_type; + + unsigned char cookie[8]; + unsigned char session_key[32]; + unsigned char session_id[16]; + char *username_str; + ptrlen username; + + RSAKey *servkey, *hostkey; + bool servkey_generated_here; + mp_int *sesskey; + + AuthPolicy *authpolicy; + unsigned ap_methods, current_method; + unsigned char auth_rsa_expected_response[16]; + RSAKey *authkey; + bool auth_successful; + + PacketProtocolLayer ppl; +}; + +static void ssh1_login_server_free(PacketProtocolLayer *); +static void ssh1_login_server_process_queue(PacketProtocolLayer *); + +static bool ssh1_login_server_get_specials( + PacketProtocolLayer *ppl, add_special_fn_t add_special, + void *ctx) { return false; } +static void ssh1_login_server_special_cmd(PacketProtocolLayer *ppl, + SessionSpecialCode code, int arg) {} +static bool ssh1_login_server_want_user_input( + PacketProtocolLayer *ppl) { return false; } +static void ssh1_login_server_got_user_input(PacketProtocolLayer *ppl) {} +static void ssh1_login_server_reconfigure( + PacketProtocolLayer *ppl, Conf *conf) {} + +static const PacketProtocolLayerVtable ssh1_login_server_vtable = { + .free = ssh1_login_server_free, + .process_queue = ssh1_login_server_process_queue, + .get_specials = ssh1_login_server_get_specials, + .special_cmd = ssh1_login_server_special_cmd, + .want_user_input = ssh1_login_server_want_user_input, + .got_user_input = ssh1_login_server_got_user_input, + .reconfigure = ssh1_login_server_reconfigure, + .queued_data_size = ssh_ppl_default_queued_data_size, + .name = NULL, /* no layer names in SSH-1 */ +}; + +PacketProtocolLayer *ssh1_login_server_new( + PacketProtocolLayer *successor_layer, RSAKey *hostkey, + AuthPolicy *authpolicy, const SshServerConfig *ssc) +{ + struct ssh1_login_server_state *s = snew(struct ssh1_login_server_state); + memset(s, 0, sizeof(*s)); + s->ppl.vt = &ssh1_login_server_vtable; + + s->ssc = ssc; + s->hostkey = hostkey; + s->authpolicy = authpolicy; + + s->successor_layer = successor_layer; + return &s->ppl; +} + +static void ssh1_login_server_free(PacketProtocolLayer *ppl) +{ + struct ssh1_login_server_state *s = + container_of(ppl, struct ssh1_login_server_state, ppl); + + if (s->successor_layer) + ssh_ppl_free(s->successor_layer); + + if (s->servkey_generated_here && s->servkey) { + freersakey(s->servkey); + sfree(s->servkey); + } + + smemclr(s->session_key, sizeof(s->session_key)); + sfree(s->username_str); + + sfree(s); +} + +static bool ssh1_login_server_filter_queue(struct ssh1_login_server_state *s) +{ + return ssh1_common_filter_queue(&s->ppl); +} + +static PktIn *ssh1_login_server_pop(struct ssh1_login_server_state *s) +{ + if (ssh1_login_server_filter_queue(s)) + return NULL; + return pq_pop(s->ppl.in_pq); +} + +static void ssh1_login_server_process_queue(PacketProtocolLayer *ppl) +{ + struct ssh1_login_server_state *s = + container_of(ppl, struct ssh1_login_server_state, ppl); + PktIn *pktin; + PktOut *pktout; + int i; + + /* Filter centrally handled messages off the front of the queue on + * every entry to this coroutine, no matter where we're resuming + * from, even if we're _not_ looping on pq_pop. That way we can + * still proactively handle those messages even if we're waiting + * for a user response. */ + if (ssh1_login_server_filter_queue(s)) + return; + + crBegin(s->crState); + + if (!s->servkey) { + int server_key_bits = s->hostkey->bytes - 256; + if (server_key_bits < 512) + server_key_bits = s->hostkey->bytes + 256; + s->servkey = snew(RSAKey); + + PrimeGenerationContext *pgc = primegen_new_context( + &primegen_probabilistic); + ProgressReceiver null_progress; + null_progress.vt = &null_progress_vt; + rsa_generate(s->servkey, server_key_bits, false, pgc, &null_progress); + primegen_free_context(pgc); + + s->servkey->comment = NULL; + s->servkey_generated_here = true; + } + + s->local_protoflags = SSH1_PROTOFLAGS_SUPPORTED; + s->supported_ciphers_mask = s->ssc->ssh1_cipher_mask; + s->supported_auths_mask = 0; + s->ap_methods = auth_methods(s->authpolicy); + if (s->ap_methods & AUTHMETHOD_PASSWORD) + s->supported_auths_mask |= (1U << SSH1_AUTH_PASSWORD); + if (s->ap_methods & AUTHMETHOD_PUBLICKEY) + s->supported_auths_mask |= (1U << SSH1_AUTH_RSA); + if (s->ap_methods & AUTHMETHOD_TIS) + s->supported_auths_mask |= (1U << SSH1_AUTH_TIS); + if (s->ap_methods & AUTHMETHOD_CRYPTOCARD) + s->supported_auths_mask |= (1U << SSH1_AUTH_CCARD); + + random_read(s->cookie, 8); + + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_SMSG_PUBLIC_KEY); + put_data(pktout, s->cookie, 8); + rsa_ssh1_public_blob(BinarySink_UPCAST(pktout), + s->servkey, RSA_SSH1_EXPONENT_FIRST); + rsa_ssh1_public_blob(BinarySink_UPCAST(pktout), + s->hostkey, RSA_SSH1_EXPONENT_FIRST); + put_uint32(pktout, s->local_protoflags); + put_uint32(pktout, s->supported_ciphers_mask); + put_uint32(pktout, s->supported_auths_mask); + pq_push(s->ppl.out_pq, pktout); + + crMaybeWaitUntilV((pktin = ssh1_login_server_pop(s)) != NULL); + if (pktin->type != SSH1_CMSG_SESSION_KEY) { + ssh_proto_error(s->ppl.ssh, "Received unexpected packet in response" + " to initial public key packet, type %d (%s)", + pktin->type, ssh1_pkt_type(pktin->type)); + return; + } + + { + ptrlen client_cookie; + s->cipher_type = get_byte(pktin); + client_cookie = get_data(pktin, 8); + s->sesskey = get_mp_ssh1(pktin); + s->remote_protoflags = get_uint32(pktin); + + if (get_err(pktin)) { + ssh_proto_error(s->ppl.ssh, "Unable to parse session key packet"); + return; + } + if (!ptrlen_eq_ptrlen(client_cookie, make_ptrlen(s->cookie, 8))) { + ssh_proto_error(s->ppl.ssh, + "Client sent incorrect anti-spoofing cookie"); + return; + } + } + if (s->cipher_type >= 32 || + !((s->supported_ciphers_mask >> s->cipher_type) & 1)) { + ssh_proto_error(s->ppl.ssh, "Client selected an unsupported cipher"); + return; + } + + { + RSAKey *smaller, *larger; + strbuf *data = strbuf_new_nm(); + + if (mp_get_nbits(s->hostkey->modulus) > + mp_get_nbits(s->servkey->modulus)) { + larger = s->hostkey; + smaller = s->servkey; + } else { + smaller = s->hostkey; + larger = s->servkey; + } + + if (rsa_ssh1_decrypt_pkcs1(s->sesskey, larger, data)) { + mp_free(s->sesskey); + s->sesskey = mp_from_bytes_be(ptrlen_from_strbuf(data)); + strbuf_clear(data); + if (rsa_ssh1_decrypt_pkcs1(s->sesskey, smaller, data) && + data->len == sizeof(s->session_key)) { + memcpy(s->session_key, data->u, sizeof(s->session_key)); + mp_free(s->sesskey); + s->sesskey = NULL; /* indicates success */ + } + } + + strbuf_free(data); + } + if (s->sesskey) { + ssh_proto_error(s->ppl.ssh, "Failed to decrypt session key"); + return; + } + + ssh1_compute_session_id(s->session_id, s->cookie, s->hostkey, s->servkey); + + for (i = 0; i < 16; i++) + s->session_key[i] ^= s->session_id[i]; + + { + const ssh_cipheralg *cipher = + (s->cipher_type == SSH1_CIPHER_BLOWFISH ? &ssh_blowfish_ssh1 : + s->cipher_type == SSH1_CIPHER_DES ? &ssh_des : &ssh_3des_ssh1); + ssh1_bpp_new_cipher(s->ppl.bpp, cipher, s->session_key); + } + + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_SMSG_SUCCESS); + pq_push(s->ppl.out_pq, pktout); + + crMaybeWaitUntilV((pktin = ssh1_login_server_pop(s)) != NULL); + if (pktin->type != SSH1_CMSG_USER) { + ssh_proto_error(s->ppl.ssh, "Received unexpected packet while " + "expecting username, type %d (%s)", + pktin->type, ssh1_pkt_type(pktin->type)); + return; + } + s->username = get_string(pktin); + s->username.ptr = s->username_str = mkstr(s->username); + ppl_logevent("Received username '%.*s'", PTRLEN_PRINTF(s->username)); + + s->auth_successful = auth_none(s->authpolicy, s->username); + while (1) { + /* Signal failed authentication */ + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_SMSG_FAILURE); + pq_push(s->ppl.out_pq, pktout); + + crMaybeWaitUntilV((pktin = ssh1_login_server_pop(s)) != NULL); + if (pktin->type == SSH1_CMSG_AUTH_PASSWORD) { + s->current_method = AUTHMETHOD_PASSWORD; + if (!(s->ap_methods & s->current_method)) + continue; + + ptrlen password = get_string(pktin); + + /* Tolerate historic traffic-analysis defence of NUL + + * garbage on the end of the binary password string */ + char *nul = memchr(password.ptr, '\0', password.len); + if (nul) + password.len = (const char *)nul - (const char *)password.ptr; + + if (auth_password(s->authpolicy, s->username, password, NULL)) + goto auth_success; + } else if (pktin->type == SSH1_CMSG_AUTH_RSA) { + s->current_method = AUTHMETHOD_PUBLICKEY; + if (!(s->ap_methods & s->current_method)) + continue; + + { + mp_int *modulus = get_mp_ssh1(pktin); + s->authkey = auth_publickey_ssh1( + s->authpolicy, s->username, modulus); + + if (!s->authkey && + s->ssc->stunt_pretend_to_accept_any_pubkey) { + mp_int *zero = mp_from_integer(0); + mp_int *fake_challenge = mp_random_in_range(zero, modulus); + + pktout = ssh_bpp_new_pktout( + s->ppl.bpp, SSH1_SMSG_AUTH_RSA_CHALLENGE); + put_mp_ssh1(pktout, fake_challenge); + pq_push(s->ppl.out_pq, pktout); + + mp_free(zero); + mp_free(fake_challenge); + } + + mp_free(modulus); + } + + if (!s->authkey && + !s->ssc->stunt_pretend_to_accept_any_pubkey) + continue; + + if (s->authkey && s->authkey->bytes < 32) { + ppl_logevent("Auth key far too small"); + continue; + } + + if (s->authkey) { + unsigned char *rsabuf = + snewn(s->authkey->bytes, unsigned char); + + random_read(rsabuf, 32); + + { + ssh_hash *h = ssh_hash_new(&ssh_md5); + put_data(h, rsabuf, 32); + put_data(h, s->session_id, 16); + ssh_hash_final(h, s->auth_rsa_expected_response); + } + + if (!rsa_ssh1_encrypt(rsabuf, 32, s->authkey)) { + sfree(rsabuf); + ppl_logevent("Failed to encrypt auth challenge"); + continue; + } + + mp_int *bn = mp_from_bytes_be( + make_ptrlen(rsabuf, s->authkey->bytes)); + smemclr(rsabuf, s->authkey->bytes); + sfree(rsabuf); + + pktout = ssh_bpp_new_pktout( + s->ppl.bpp, SSH1_SMSG_AUTH_RSA_CHALLENGE); + put_mp_ssh1(pktout, bn); + pq_push(s->ppl.out_pq, pktout); + + mp_free(bn); + } + + crMaybeWaitUntilV((pktin = ssh1_login_server_pop(s)) != NULL); + if (pktin->type != SSH1_CMSG_AUTH_RSA_RESPONSE) { + ssh_proto_error(s->ppl.ssh, "Received unexpected packet in " + "response to RSA auth challenge, type %d (%s)", + pktin->type, ssh1_pkt_type(pktin->type)); + return; + } + + if (!s->authkey) + continue; + + { + ptrlen response = get_data(pktin, 16); + ptrlen expected = make_ptrlen( + s->auth_rsa_expected_response, 16); + if (!ptrlen_eq_ptrlen(response, expected)) { + ppl_logevent("Wrong response to auth challenge"); + continue; + } + } + + goto auth_success; + } else if (pktin->type == SSH1_CMSG_AUTH_TIS || + pktin->type == SSH1_CMSG_AUTH_CCARD) { + char *challenge; + unsigned response_type; + ptrlen response; + + s->current_method = (pktin->type == SSH1_CMSG_AUTH_TIS ? + AUTHMETHOD_TIS : AUTHMETHOD_CRYPTOCARD); + if (!(s->ap_methods & s->current_method)) + continue; + + challenge = auth_ssh1int_challenge( + s->authpolicy, s->current_method, s->username); + if (!challenge) + continue; + pktout = ssh_bpp_new_pktout( + s->ppl.bpp, + (s->current_method == AUTHMETHOD_TIS ? + SSH1_SMSG_AUTH_TIS_CHALLENGE : + SSH1_SMSG_AUTH_CCARD_CHALLENGE)); + put_stringz(pktout, challenge); + pq_push(s->ppl.out_pq, pktout); + sfree(challenge); + + crMaybeWaitUntilV((pktin = ssh1_login_server_pop(s)) != NULL); + response_type = (s->current_method == AUTHMETHOD_TIS ? + SSH1_CMSG_AUTH_TIS_RESPONSE : + SSH1_CMSG_AUTH_CCARD_RESPONSE); + if (pktin->type != response_type) { + ssh_proto_error(s->ppl.ssh, "Received unexpected packet in " + "response to %s challenge, type %d (%s)", + (s->current_method == AUTHMETHOD_TIS ? + "TIS" : "CryptoCard"), + pktin->type, ssh1_pkt_type(pktin->type)); + return; + } + + response = get_string(pktin); + + if (auth_ssh1int_response(s->authpolicy, response)) + goto auth_success; + } + } + + auth_success: + if (!auth_successful(s->authpolicy, s->username, s->current_method)) { + ssh_sw_abort(s->ppl.ssh, "Multiple authentications required but SSH-1" + " cannot perform them"); + return; + } + + /* Signal successful authentication */ + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_SMSG_SUCCESS); + pq_push(s->ppl.out_pq, pktout); + + ssh1_connection_set_protoflags( + s->successor_layer, s->local_protoflags, s->remote_protoflags); + { + PacketProtocolLayer *successor = s->successor_layer; + s->successor_layer = NULL; /* avoid freeing it ourself */ + ssh_ppl_replace(&s->ppl, successor); + return; /* we've just freed s, so avoid even touching s->crState */ + } + + crFinishV; +} diff --git a/ssh/login1.c b/ssh/login1.c new file mode 100644 index 00000000..716e248d --- /dev/null +++ b/ssh/login1.c @@ -0,0 +1,1242 @@ +/* + * Packet protocol layer for the SSH-1 login phase (combining what + * SSH-2 would think of as key exchange and user authentication). + */ + +#include + +#include "putty.h" +#include "ssh.h" +#include "mpint.h" +#include "bpp.h" +#include "ppl.h" +#include "sshcr.h" + +typedef struct agent_key { + RSAKey key; + strbuf *comment; + ptrlen blob; /* only used during initial parsing of agent response */ +} agent_key; + +struct ssh1_login_state { + int crState; + + PacketProtocolLayer *successor_layer; + + Conf *conf; + + char *savedhost; + int savedport; + bool try_agent_auth; + + int remote_protoflags; + int local_protoflags; + unsigned char session_key[32]; + char *username; + agent_pending_query *auth_agent_query; + + int len; + unsigned char *rsabuf; + unsigned long supported_ciphers_mask, supported_auths_mask; + bool tried_publickey, tried_agent; + bool tis_auth_refused, ccard_auth_refused; + unsigned char cookie[8]; + unsigned char session_id[16]; + int cipher_type; + strbuf *publickey_blob; + char *publickey_comment; + bool privatekey_available, privatekey_encrypted; + prompts_t *cur_prompt; + int userpass_ret; + char c; + int pwpkt_type; + void *agent_response_to_free; + ptrlen agent_response; + BinarySource asrc[1]; /* response from SSH agent */ + size_t agent_keys_len; + agent_key *agent_keys; + size_t agent_key_index, agent_key_limit; + bool authed; + RSAKey key; + int dlgret; + Filename *keyfile; + RSAKey servkey, hostkey; + bool want_user_input; + + StripCtrlChars *tis_scc; + bool tis_scc_initialised; + + PacketProtocolLayer ppl; +}; + +static void ssh1_login_free(PacketProtocolLayer *); +static void ssh1_login_process_queue(PacketProtocolLayer *); +static void ssh1_login_dialog_callback(void *, int); +static void ssh1_login_special_cmd(PacketProtocolLayer *ppl, + SessionSpecialCode code, int arg); +static bool ssh1_login_want_user_input(PacketProtocolLayer *ppl); +static void ssh1_login_got_user_input(PacketProtocolLayer *ppl); +static void ssh1_login_reconfigure(PacketProtocolLayer *ppl, Conf *conf); + +static const PacketProtocolLayerVtable ssh1_login_vtable = { + .free = ssh1_login_free, + .process_queue = ssh1_login_process_queue, + .get_specials = ssh1_common_get_specials, + .special_cmd = ssh1_login_special_cmd, + .want_user_input = ssh1_login_want_user_input, + .got_user_input = ssh1_login_got_user_input, + .reconfigure = ssh1_login_reconfigure, + .queued_data_size = ssh_ppl_default_queued_data_size, + .name = NULL, /* no layer names in SSH-1 */ +}; + +static void ssh1_login_agent_query(struct ssh1_login_state *s, strbuf *req); +static void ssh1_login_agent_callback(void *loginv, void *reply, int replylen); + +PacketProtocolLayer *ssh1_login_new( + Conf *conf, const char *host, int port, + PacketProtocolLayer *successor_layer) +{ + struct ssh1_login_state *s = snew(struct ssh1_login_state); + memset(s, 0, sizeof(*s)); + s->ppl.vt = &ssh1_login_vtable; + + s->conf = conf_copy(conf); + s->savedhost = dupstr(host); + s->savedport = port; + s->successor_layer = successor_layer; + return &s->ppl; +} + +static void ssh1_login_free(PacketProtocolLayer *ppl) +{ + struct ssh1_login_state *s = + container_of(ppl, struct ssh1_login_state, ppl); + + if (s->successor_layer) + ssh_ppl_free(s->successor_layer); + + conf_free(s->conf); + sfree(s->savedhost); + sfree(s->rsabuf); + sfree(s->username); + if (s->publickey_blob) + strbuf_free(s->publickey_blob); + sfree(s->publickey_comment); + if (s->cur_prompt) + free_prompts(s->cur_prompt); + if (s->agent_keys) { + for (size_t i = 0; i < s->agent_keys_len; i++) { + freersakey(&s->agent_keys[i].key); + strbuf_free(s->agent_keys[i].comment); + } + sfree(s->agent_keys); + } + sfree(s->agent_response_to_free); + if (s->auth_agent_query) + agent_cancel_query(s->auth_agent_query); + sfree(s); +} + +static bool ssh1_login_filter_queue(struct ssh1_login_state *s) +{ + return ssh1_common_filter_queue(&s->ppl); +} + +static PktIn *ssh1_login_pop(struct ssh1_login_state *s) +{ + if (ssh1_login_filter_queue(s)) + return NULL; + return pq_pop(s->ppl.in_pq); +} + +static void ssh1_login_setup_tis_scc(struct ssh1_login_state *s); + +static void ssh1_login_process_queue(PacketProtocolLayer *ppl) +{ + struct ssh1_login_state *s = + container_of(ppl, struct ssh1_login_state, ppl); + PktIn *pktin; + PktOut *pkt; + int i; + + /* Filter centrally handled messages off the front of the queue on + * every entry to this coroutine, no matter where we're resuming + * from, even if we're _not_ looping on pq_pop. That way we can + * still proactively handle those messages even if we're waiting + * for a user response. */ + if (ssh1_login_filter_queue(s)) + return; + + crBegin(s->crState); + + crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL); + + if (pktin->type != SSH1_SMSG_PUBLIC_KEY) { + ssh_proto_error(s->ppl.ssh, "Public key packet not received"); + return; + } + + ppl_logevent("Received public keys"); + + { + ptrlen pl = get_data(pktin, 8); + memcpy(s->cookie, pl.ptr, pl.len); + } + + get_rsa_ssh1_pub(pktin, &s->servkey, RSA_SSH1_EXPONENT_FIRST); + get_rsa_ssh1_pub(pktin, &s->hostkey, RSA_SSH1_EXPONENT_FIRST); + + s->hostkey.comment = NULL; /* avoid confusing rsa_ssh1_fingerprint */ + + /* + * Log the host key fingerprint. + */ + if (!get_err(pktin)) { + char *fingerprint = rsa_ssh1_fingerprint(&s->hostkey); + ppl_logevent("Host key fingerprint is:"); + ppl_logevent(" %s", fingerprint); + sfree(fingerprint); + } + + s->remote_protoflags = get_uint32(pktin); + s->supported_ciphers_mask = get_uint32(pktin); + s->supported_auths_mask = get_uint32(pktin); + + if (get_err(pktin)) { + ssh_proto_error(s->ppl.ssh, "Bad SSH-1 public key packet"); + return; + } + + if ((s->ppl.remote_bugs & BUG_CHOKES_ON_RSA)) + s->supported_auths_mask &= ~(1 << SSH1_AUTH_RSA); + + s->local_protoflags = + s->remote_protoflags & SSH1_PROTOFLAGS_SUPPORTED; + s->local_protoflags |= SSH1_PROTOFLAG_SCREEN_NUMBER; + + ssh1_compute_session_id(s->session_id, s->cookie, + &s->hostkey, &s->servkey); + + random_read(s->session_key, 32); + + /* + * Verify that the `bits' and `bytes' parameters match. + */ + if (s->hostkey.bits > s->hostkey.bytes * 8 || + s->servkey.bits > s->servkey.bytes * 8) { + ssh_proto_error(s->ppl.ssh, "SSH-1 public keys were badly formatted"); + return; + } + + s->len = 32; + if (s->len < s->hostkey.bytes) + s->len = s->hostkey.bytes; + if (s->len < s->servkey.bytes) + s->len = s->servkey.bytes; + + s->rsabuf = snewn(s->len, unsigned char); + + /* + * Verify the host key. + */ + { + /* + * First format the key into a string. + */ + char *keystr = rsastr_fmt(&s->hostkey); + char **fingerprints = rsa_ssh1_fake_all_fingerprints(&s->hostkey); + + /* First check against manually configured host keys. */ + s->dlgret = verify_ssh_manual_host_key(s->conf, fingerprints, NULL); + if (s->dlgret == 0) { /* did not match */ + ssh2_free_all_fingerprints(fingerprints); + sfree(keystr); + ssh_proto_error(s->ppl.ssh, "Host key did not appear in manually " + "configured list"); + return; + } else if (s->dlgret < 0) { /* none configured; use standard handling */ + char *keydisp = ssh1_pubkey_str(&s->hostkey); + s->dlgret = seat_verify_ssh_host_key( + s->ppl.seat, s->savedhost, s->savedport, "rsa", keystr, + keydisp, fingerprints, ssh1_login_dialog_callback, s); + sfree(keydisp); + ssh2_free_all_fingerprints(fingerprints); + sfree(keystr); +#ifdef FUZZING + s->dlgret = 1; +#endif + crMaybeWaitUntilV(s->dlgret >= 0); + + if (s->dlgret == 0) { + ssh_user_close(s->ppl.ssh, + "User aborted at host key verification"); + return; + } + } else { + ssh2_free_all_fingerprints(fingerprints); + sfree(keystr); + } + } + + for (i = 0; i < 32; i++) { + s->rsabuf[i] = s->session_key[i]; + if (i < 16) + s->rsabuf[i] ^= s->session_id[i]; + } + + { + RSAKey *smaller = (s->hostkey.bytes > s->servkey.bytes ? + &s->servkey : &s->hostkey); + RSAKey *larger = (s->hostkey.bytes > s->servkey.bytes ? + &s->hostkey : &s->servkey); + + if (!rsa_ssh1_encrypt(s->rsabuf, 32, smaller) || + !rsa_ssh1_encrypt(s->rsabuf, smaller->bytes, larger)) { + ssh_proto_error(s->ppl.ssh, "SSH-1 public key encryptions failed " + "due to bad formatting"); + return; + } + } + + ppl_logevent("Encrypted session key"); + + { + bool cipher_chosen = false, warn = false; + const char *cipher_string = NULL; + int i; + for (i = 0; !cipher_chosen && i < CIPHER_MAX; i++) { + int next_cipher = conf_get_int_int( + s->conf, CONF_ssh_cipherlist, i); + if (next_cipher == CIPHER_WARN) { + /* If/when we choose a cipher, warn about it */ + warn = true; + } else if (next_cipher == CIPHER_AES) { + /* XXX Probably don't need to mention this. */ + ppl_logevent("AES not supported in SSH-1, skipping"); + } else { + switch (next_cipher) { + case CIPHER_3DES: s->cipher_type = SSH1_CIPHER_3DES; + cipher_string = "3DES"; break; + case CIPHER_BLOWFISH: s->cipher_type = SSH1_CIPHER_BLOWFISH; + cipher_string = "Blowfish"; break; + case CIPHER_DES: s->cipher_type = SSH1_CIPHER_DES; + cipher_string = "single-DES"; break; + } + if (s->supported_ciphers_mask & (1 << s->cipher_type)) + cipher_chosen = true; + } + } + if (!cipher_chosen) { + if ((s->supported_ciphers_mask & (1 << SSH1_CIPHER_3DES)) == 0) { + ssh_proto_error(s->ppl.ssh, "Server violates SSH-1 protocol " + "by not supporting 3DES encryption"); + } else { + /* shouldn't happen */ + ssh_sw_abort(s->ppl.ssh, "No supported ciphers found"); + } + return; + } + + /* Warn about chosen cipher if necessary. */ + if (warn) { + s->dlgret = seat_confirm_weak_crypto_primitive( + s->ppl.seat, "cipher", cipher_string, + ssh1_login_dialog_callback, s); + crMaybeWaitUntilV(s->dlgret >= 0); + if (s->dlgret == 0) { + ssh_user_close(s->ppl.ssh, "User aborted at cipher warning"); + return; + } + } + } + + switch (s->cipher_type) { + case SSH1_CIPHER_3DES: + ppl_logevent("Using 3DES encryption"); + break; + case SSH1_CIPHER_DES: + ppl_logevent("Using single-DES encryption"); + break; + case SSH1_CIPHER_BLOWFISH: + ppl_logevent("Using Blowfish encryption"); + break; + } + + pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_SESSION_KEY); + put_byte(pkt, s->cipher_type); + put_data(pkt, s->cookie, 8); + put_uint16(pkt, s->len * 8); + put_data(pkt, s->rsabuf, s->len); + put_uint32(pkt, s->local_protoflags); + pq_push(s->ppl.out_pq, pkt); + + ppl_logevent("Trying to enable encryption..."); + + sfree(s->rsabuf); + s->rsabuf = NULL; + + /* + * Force the BPP to synchronously marshal all packets up to and + * including the SESSION_KEY into wire format, before we turn on + * crypto. + */ + ssh_bpp_handle_output(s->ppl.bpp); + + { + const ssh_cipheralg *cipher = + (s->cipher_type == SSH1_CIPHER_BLOWFISH ? &ssh_blowfish_ssh1 : + s->cipher_type == SSH1_CIPHER_DES ? &ssh_des : &ssh_3des_ssh1); + ssh1_bpp_new_cipher(s->ppl.bpp, cipher, s->session_key); + } + + freersakey(&s->servkey); + freersakey(&s->hostkey); + crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL); + + if (pktin->type != SSH1_SMSG_SUCCESS) { + ssh_proto_error(s->ppl.ssh, "Encryption not successfully enabled"); + return; + } + + ppl_logevent("Successfully started encryption"); + + if ((s->username = get_remote_username(s->conf)) == NULL) { + s->cur_prompt = new_prompts(); + s->cur_prompt->to_server = true; + s->cur_prompt->from_server = false; + s->cur_prompt->name = dupstr("SSH login name"); + add_prompt(s->cur_prompt, dupstr("login as: "), true); + s->userpass_ret = seat_get_userpass_input( + s->ppl.seat, s->cur_prompt, NULL); + while (1) { + while (s->userpass_ret < 0 && + bufchain_size(s->ppl.user_input) > 0) + s->userpass_ret = seat_get_userpass_input( + s->ppl.seat, s->cur_prompt, s->ppl.user_input); + + if (s->userpass_ret >= 0) + break; + + s->want_user_input = true; + crReturnV; + s->want_user_input = false; + } + if (!s->userpass_ret) { + /* + * Failed to get a username. Terminate. + */ + ssh_user_close(s->ppl.ssh, "No username provided"); + return; + } + s->username = prompt_get_result(s->cur_prompt->prompts[0]); + free_prompts(s->cur_prompt); + s->cur_prompt = NULL; + } + + pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_USER); + put_stringz(pkt, s->username); + pq_push(s->ppl.out_pq, pkt); + + ppl_logevent("Sent username \"%s\"", s->username); + if (seat_verbose(s->ppl.seat) || seat_interactive(s->ppl.seat)) + ppl_printf("Sent username \"%s\"\r\n", s->username); + + crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL); + + if (!(s->supported_auths_mask & (1 << SSH1_AUTH_RSA))) { + /* We must not attempt PK auth. Pretend we've already tried it. */ + s->tried_publickey = s->tried_agent = true; + } else { + s->tried_publickey = s->tried_agent = false; + } + s->tis_auth_refused = s->ccard_auth_refused = false; + + /* + * Load the public half of any configured keyfile for later use. + */ + s->keyfile = conf_get_filename(s->conf, CONF_keyfile); + if (!filename_is_null(s->keyfile)) { + int keytype; + ppl_logevent("Reading key file \"%s\"", filename_to_str(s->keyfile)); + keytype = key_type(s->keyfile); + if (keytype == SSH_KEYTYPE_SSH1 || + keytype == SSH_KEYTYPE_SSH1_PUBLIC) { + const char *error; + s->publickey_blob = strbuf_new(); + if (rsa1_loadpub_f(s->keyfile, + BinarySink_UPCAST(s->publickey_blob), + &s->publickey_comment, &error)) { + s->privatekey_available = (keytype == SSH_KEYTYPE_SSH1); + if (!s->privatekey_available) + ppl_logevent("Key file contains public key only"); + s->privatekey_encrypted = rsa1_encrypted_f(s->keyfile, NULL); + } else { + ppl_logevent("Unable to load key (%s)", error); + ppl_printf("Unable to load key file \"%s\" (%s)\r\n", + filename_to_str(s->keyfile), error); + + strbuf_free(s->publickey_blob); + s->publickey_blob = NULL; + } + } else { + ppl_logevent("Unable to use this key file (%s)", + key_type_to_str(keytype)); + ppl_printf("Unable to use key file \"%s\" (%s)\r\n", + filename_to_str(s->keyfile), + key_type_to_str(keytype)); + } + } + + /* Check whether we're configured to try Pageant, and also whether + * it's available. */ + s->try_agent_auth = (conf_get_bool(s->conf, CONF_tryagent) && + agent_exists()); + + while (pktin->type == SSH1_SMSG_FAILURE) { + s->pwpkt_type = SSH1_CMSG_AUTH_PASSWORD; + + if (s->try_agent_auth && !s->tried_agent) { + /* + * Attempt RSA authentication using Pageant. + */ + s->authed = false; + s->tried_agent = true; + ppl_logevent("Pageant is running. Requesting keys."); + + /* Request the keys held by the agent. */ + { + strbuf *request = strbuf_new_for_agent_query(); + put_byte(request, SSH1_AGENTC_REQUEST_RSA_IDENTITIES); + ssh1_login_agent_query(s, request); + strbuf_free(request); + crMaybeWaitUntilV(!s->auth_agent_query); + } + BinarySource_BARE_INIT_PL(s->asrc, s->agent_response); + + get_uint32(s->asrc); /* skip length field */ + if (get_byte(s->asrc) == SSH1_AGENT_RSA_IDENTITIES_ANSWER) { + size_t nkeys = get_uint32(s->asrc); + size_t origpos = s->asrc->pos; + + /* + * Check that the agent response is well formed. + */ + for (size_t i = 0; i < nkeys; i++) { + get_rsa_ssh1_pub(s->asrc, NULL, RSA_SSH1_EXPONENT_FIRST); + get_string(s->asrc); /* comment */ + if (get_err(s->asrc)) { + ppl_logevent("Pageant's response was truncated"); + goto parsed_agent_query; + } + } + + /* + * Copy the list of public-key blobs out of the Pageant + * response. + */ + BinarySource_REWIND_TO(s->asrc, origpos); + s->agent_keys_len = nkeys; + s->agent_keys = snewn(s->agent_keys_len, agent_key); + for (size_t i = 0; i < nkeys; i++) { + memset(&s->agent_keys[i].key, 0, + sizeof(s->agent_keys[i].key)); + + const char *blobstart = get_ptr(s->asrc); + get_rsa_ssh1_pub(s->asrc, &s->agent_keys[i].key, + RSA_SSH1_EXPONENT_FIRST); + const char *blobend = get_ptr(s->asrc); + + s->agent_keys[i].comment = strbuf_new(); + put_datapl(s->agent_keys[i].comment, get_string(s->asrc)); + + s->agent_keys[i].blob = make_ptrlen( + blobstart, blobend - blobstart); + } + + ppl_logevent("Pageant has %"SIZEu" SSH-1 keys", nkeys); + + if (s->publickey_blob) { + /* + * If we've been given a specific public key blob, + * filter the list of keys to try from the agent + * down to only that one, or none if it's not + * there. + */ + ptrlen our_blob = ptrlen_from_strbuf(s->publickey_blob); + size_t i; + + for (i = 0; i < nkeys; i++) { + if (ptrlen_eq_ptrlen(our_blob, s->agent_keys[i].blob)) + break; + } + + if (i < nkeys) { + ppl_logevent("Pageant key #%"SIZEu" matches " + "configured key file", i); + s->agent_key_index = i; + s->agent_key_limit = i+1; + } else { + ppl_logevent("Configured key file not in Pageant"); + s->agent_key_index = 0; + s->agent_key_limit = 0; + } + } else { + /* + * Otherwise, try them all. + */ + s->agent_key_index = 0; + s->agent_key_limit = nkeys; + } + } else { + ppl_logevent("Failed to get reply from Pageant"); + } + parsed_agent_query:; + + for (; s->agent_key_index < s->agent_key_limit; + s->agent_key_index++) { + ppl_logevent("Trying Pageant key #%"SIZEu, s->agent_key_index); + pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_AUTH_RSA); + put_mp_ssh1(pkt, + s->agent_keys[s->agent_key_index].key.modulus); + pq_push(s->ppl.out_pq, pkt); + crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) + != NULL); + if (pktin->type != SSH1_SMSG_AUTH_RSA_CHALLENGE) { + ppl_logevent("Key refused"); + continue; + } + ppl_logevent("Received RSA challenge"); + + { + mp_int *challenge = get_mp_ssh1(pktin); + if (get_err(pktin)) { + mp_free(challenge); + ssh_proto_error(s->ppl.ssh, "Server's RSA challenge " + "was badly formatted"); + return; + } + + strbuf *agentreq = strbuf_new_for_agent_query(); + put_byte(agentreq, SSH1_AGENTC_RSA_CHALLENGE); + + rsa_ssh1_public_blob( + BinarySink_UPCAST(agentreq), + &s->agent_keys[s->agent_key_index].key, + RSA_SSH1_EXPONENT_FIRST); + + put_mp_ssh1(agentreq, challenge); + mp_free(challenge); + + put_data(agentreq, s->session_id, 16); + put_uint32(agentreq, 1); /* response format */ + ssh1_login_agent_query(s, agentreq); + strbuf_free(agentreq); + crMaybeWaitUntilV(!s->auth_agent_query); + } + + { + const unsigned char *ret = s->agent_response.ptr; + if (ret) { + if (s->agent_response.len >= 5+16 && + ret[4] == SSH1_AGENT_RSA_RESPONSE) { + ppl_logevent("Sending Pageant's response"); + pkt = ssh_bpp_new_pktout( + s->ppl.bpp, SSH1_CMSG_AUTH_RSA_RESPONSE); + put_data(pkt, ret + 5, 16); + pq_push(s->ppl.out_pq, pkt); + crMaybeWaitUntilV( + (pktin = ssh1_login_pop(s)) + != NULL); + if (pktin->type == SSH1_SMSG_SUCCESS) { + ppl_logevent("Pageant's response " + "accepted"); + if (seat_verbose(s->ppl.seat)) { + ptrlen comment = ptrlen_from_strbuf( + s->agent_keys[s->agent_key_index]. + comment); + ppl_printf("Authenticated using RSA " + "key \"%.*s\" from " + "agent\r\n", + PTRLEN_PRINTF(comment)); + } + s->authed = true; + } else + ppl_logevent("Pageant's response not " + "accepted"); + } else { + ppl_logevent("Pageant failed to answer " + "challenge"); + sfree((char *)ret); + } + } else { + ppl_logevent("No reply received from Pageant"); + } + } + if (s->authed) + break; + } + if (s->authed) + break; + } + if (s->publickey_blob && s->privatekey_available && + !s->tried_publickey) { + /* + * Try public key authentication with the specified + * key file. + */ + bool got_passphrase; /* need not be kept over crReturn */ + if (seat_verbose(s->ppl.seat)) + ppl_printf("Trying public key authentication.\r\n"); + ppl_logevent("Trying public key \"%s\"", + filename_to_str(s->keyfile)); + s->tried_publickey = true; + got_passphrase = false; + while (!got_passphrase) { + /* + * Get a passphrase, if necessary. + */ + int retd; + char *passphrase = NULL; /* only written after crReturn */ + const char *error; + if (!s->privatekey_encrypted) { + if (seat_verbose(s->ppl.seat)) + ppl_printf("No passphrase required.\r\n"); + passphrase = NULL; + } else { + s->cur_prompt = new_prompts(); + s->cur_prompt->to_server = false; + s->cur_prompt->from_server = false; + s->cur_prompt->name = dupstr("SSH key passphrase"); + add_prompt(s->cur_prompt, + dupprintf("Passphrase for key \"%s\": ", + s->publickey_comment), false); + s->userpass_ret = seat_get_userpass_input( + s->ppl.seat, s->cur_prompt, NULL); + while (1) { + while (s->userpass_ret < 0 && + bufchain_size(s->ppl.user_input) > 0) + s->userpass_ret = seat_get_userpass_input( + s->ppl.seat, s->cur_prompt, s->ppl.user_input); + + if (s->userpass_ret >= 0) + break; + + s->want_user_input = true; + crReturnV; + s->want_user_input = false; + } + if (!s->userpass_ret) { + /* Failed to get a passphrase. Terminate. */ + ssh_user_close(s->ppl.ssh, + "User aborted at passphrase prompt"); + return; + } + passphrase = prompt_get_result(s->cur_prompt->prompts[0]); + free_prompts(s->cur_prompt); + s->cur_prompt = NULL; + } + /* + * Try decrypting key with passphrase. + */ + retd = rsa1_load_f(s->keyfile, &s->key, passphrase, &error); + if (passphrase) { + smemclr(passphrase, strlen(passphrase)); + sfree(passphrase); + } + if (retd == 1) { + /* Correct passphrase. */ + got_passphrase = true; + } else if (retd == 0) { + ppl_printf("Couldn't load private key from %s (%s).\r\n", + filename_to_str(s->keyfile), error); + got_passphrase = false; + break; /* go and try something else */ + } else if (retd == -1) { + ppl_printf("Wrong passphrase.\r\n"); + got_passphrase = false; + /* and try again */ + } else { + unreachable("unexpected return from rsa1_load_f()"); + } + } + + if (got_passphrase) { + + /* + * Send a public key attempt. + */ + pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_AUTH_RSA); + put_mp_ssh1(pkt, s->key.modulus); + pq_push(s->ppl.out_pq, pkt); + + crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) + != NULL); + if (pktin->type == SSH1_SMSG_FAILURE) { + ppl_printf("Server refused our public key.\r\n"); + continue; /* go and try something else */ + } + if (pktin->type != SSH1_SMSG_AUTH_RSA_CHALLENGE) { + ssh_proto_error(s->ppl.ssh, "Received unexpected packet" + " in response to offer of public key, " + "type %d (%s)", pktin->type, + ssh1_pkt_type(pktin->type)); + return; + } + + { + int i; + unsigned char buffer[32]; + mp_int *challenge, *response; + + challenge = get_mp_ssh1(pktin); + if (get_err(pktin)) { + mp_free(challenge); + ssh_proto_error(s->ppl.ssh, "Server's RSA challenge " + "was badly formatted"); + return; + } + response = rsa_ssh1_decrypt(challenge, &s->key); + freersapriv(&s->key); /* burn the evidence */ + + for (i = 0; i < 32; i++) { + buffer[i] = mp_get_byte(response, 31 - i); + } + + { + ssh_hash *h = ssh_hash_new(&ssh_md5); + put_data(h, buffer, 32); + put_data(h, s->session_id, 16); + ssh_hash_final(h, buffer); + } + + pkt = ssh_bpp_new_pktout( + s->ppl.bpp, SSH1_CMSG_AUTH_RSA_RESPONSE); + put_data(pkt, buffer, 16); + pq_push(s->ppl.out_pq, pkt); + + mp_free(challenge); + mp_free(response); + } + + crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) + != NULL); + if (pktin->type == SSH1_SMSG_FAILURE) { + if (seat_verbose(s->ppl.seat)) + ppl_printf("Failed to authenticate with" + " our public key.\r\n"); + continue; /* go and try something else */ + } else if (pktin->type != SSH1_SMSG_SUCCESS) { + ssh_proto_error(s->ppl.ssh, "Received unexpected packet" + " in response to RSA authentication, " + "type %d (%s)", pktin->type, + ssh1_pkt_type(pktin->type)); + return; + } + + break; /* we're through! */ + } + + } + + /* + * Otherwise, try various forms of password-like authentication. + */ + s->cur_prompt = new_prompts(); + + if (conf_get_bool(s->conf, CONF_try_tis_auth) && + (s->supported_auths_mask & (1 << SSH1_AUTH_TIS)) && + !s->tis_auth_refused) { + ssh1_login_setup_tis_scc(s); + s->pwpkt_type = SSH1_CMSG_AUTH_TIS_RESPONSE; + ppl_logevent("Requested TIS authentication"); + pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_AUTH_TIS); + pq_push(s->ppl.out_pq, pkt); + crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL); + if (pktin->type == SSH1_SMSG_FAILURE) { + ppl_logevent("TIS authentication declined"); + if (seat_interactive(s->ppl.seat)) + ppl_printf("TIS authentication refused.\r\n"); + s->tis_auth_refused = true; + continue; + } else if (pktin->type == SSH1_SMSG_AUTH_TIS_CHALLENGE) { + ptrlen challenge = get_string(pktin); + if (get_err(pktin)) { + ssh_proto_error(s->ppl.ssh, "TIS challenge packet was " + "badly formed"); + return; + } + ppl_logevent("Received TIS challenge"); + s->cur_prompt->to_server = true; + s->cur_prompt->from_server = true; + s->cur_prompt->name = dupstr("SSH TIS authentication"); + + strbuf *sb = strbuf_new(); + put_datapl(sb, PTRLEN_LITERAL("\ +-- TIS authentication challenge from server: ---------------------------------\ +\r\n")); + if (s->tis_scc) { + stripctrl_retarget(s->tis_scc, BinarySink_UPCAST(sb)); + put_datapl(s->tis_scc, challenge); + stripctrl_retarget(s->tis_scc, NULL); + } else { + put_datapl(sb, challenge); + } + if (!ptrlen_endswith(challenge, PTRLEN_LITERAL("\n"), NULL)) + put_datapl(sb, PTRLEN_LITERAL("\r\n")); + put_datapl(sb, PTRLEN_LITERAL("\ +-- End of TIS authentication challenge from server: --------------------------\ +\r\n")); + + s->cur_prompt->instruction = strbuf_to_str(sb); + s->cur_prompt->instr_reqd = true; + add_prompt(s->cur_prompt, dupstr( + "TIS authentication response: "), false); + } else { + ssh_proto_error(s->ppl.ssh, "Received unexpected packet" + " in response to TIS authentication, " + "type %d (%s)", pktin->type, + ssh1_pkt_type(pktin->type)); + return; + } + } else if (conf_get_bool(s->conf, CONF_try_tis_auth) && + (s->supported_auths_mask & (1 << SSH1_AUTH_CCARD)) && + !s->ccard_auth_refused) { + ssh1_login_setup_tis_scc(s); + s->pwpkt_type = SSH1_CMSG_AUTH_CCARD_RESPONSE; + ppl_logevent("Requested CryptoCard authentication"); + pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_AUTH_CCARD); + pq_push(s->ppl.out_pq, pkt); + crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL); + if (pktin->type == SSH1_SMSG_FAILURE) { + ppl_logevent("CryptoCard authentication declined"); + ppl_printf("CryptoCard authentication refused.\r\n"); + s->ccard_auth_refused = true; + continue; + } else if (pktin->type == SSH1_SMSG_AUTH_CCARD_CHALLENGE) { + ptrlen challenge = get_string(pktin); + if (get_err(pktin)) { + ssh_proto_error(s->ppl.ssh, "CryptoCard challenge packet " + "was badly formed"); + return; + } + ppl_logevent("Received CryptoCard challenge"); + s->cur_prompt->to_server = true; + s->cur_prompt->from_server = true; + s->cur_prompt->name = dupstr("SSH CryptoCard authentication"); + + strbuf *sb = strbuf_new(); + put_datapl(sb, PTRLEN_LITERAL("\ +-- CryptoCard authentication challenge from server: --------------------------\ +\r\n")); + if (s->tis_scc) { + stripctrl_retarget(s->tis_scc, BinarySink_UPCAST(sb)); + put_datapl(s->tis_scc, challenge); + stripctrl_retarget(s->tis_scc, NULL); + } else { + put_datapl(sb, challenge); + } + if (!ptrlen_endswith(challenge, PTRLEN_LITERAL("\n"), NULL)) + put_datapl(sb, PTRLEN_LITERAL("\r\n")); + put_datapl(sb, PTRLEN_LITERAL("\ +-- End of CryptoCard authentication challenge from server: -------------------\ +\r\n")); + + s->cur_prompt->instruction = strbuf_to_str(sb); + s->cur_prompt->instr_reqd = true; + add_prompt(s->cur_prompt, dupstr( + "CryptoCard authentication response: "), false); + } else { + ssh_proto_error(s->ppl.ssh, "Received unexpected packet" + " in response to TIS authentication, " + "type %d (%s)", pktin->type, + ssh1_pkt_type(pktin->type)); + return; + } + } + if (s->pwpkt_type == SSH1_CMSG_AUTH_PASSWORD) { + if ((s->supported_auths_mask & (1 << SSH1_AUTH_PASSWORD)) == 0) { + ssh_sw_abort(s->ppl.ssh, "No supported authentication methods " + "available"); + return; + } + s->cur_prompt->to_server = true; + s->cur_prompt->from_server = false; + s->cur_prompt->name = dupstr("SSH password"); + add_prompt(s->cur_prompt, dupprintf("%s@%s's password: ", + s->username, s->savedhost), + false); + } + + /* + * Show password prompt, having first obtained it via a TIS + * or CryptoCard exchange if we're doing TIS or CryptoCard + * authentication. + */ + s->userpass_ret = seat_get_userpass_input( + s->ppl.seat, s->cur_prompt, NULL); + while (1) { + while (s->userpass_ret < 0 && + bufchain_size(s->ppl.user_input) > 0) + s->userpass_ret = seat_get_userpass_input( + s->ppl.seat, s->cur_prompt, s->ppl.user_input); + + if (s->userpass_ret >= 0) + break; + + s->want_user_input = true; + crReturnV; + s->want_user_input = false; + } + if (!s->userpass_ret) { + /* + * Failed to get a password (for example + * because one was supplied on the command line + * which has already failed to work). Terminate. + */ + ssh_user_close(s->ppl.ssh, "User aborted at password prompt"); + return; + } + + 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 (!(s->ppl.remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE) && + !(s->ppl.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; + const char *pw = prompt_get_result_ref( + s->cur_prompt->prompts[0]); + + pwlen = strlen(pw); + if (pwlen < 16) { + bottom = 0; /* zero length passwords are OK! :-) */ + top = 15; + } else { + bottom = pwlen & ~7; + top = bottom + 7; + } + + assert(pwlen >= bottom && pwlen <= top); + + for (i = bottom; i <= top; i++) { + if (i == pwlen) { + pkt = ssh_bpp_new_pktout(s->ppl.bpp, s->pwpkt_type); + put_stringz(pkt, pw); + pq_push(s->ppl.out_pq, pkt); + } else { + strbuf *random_data = strbuf_new_nm(); + random_read(strbuf_append(random_data, i), i); + + pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_MSG_IGNORE); + put_stringsb(pkt, random_data); + pq_push(s->ppl.out_pq, pkt); + } + } + ppl_logevent("Sending password with camouflage packets"); + } + else if (!(s->ppl.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. + */ + strbuf *padded_pw = strbuf_new_nm(); + + ppl_logevent("Sending length-padded password"); + pkt = ssh_bpp_new_pktout(s->ppl.bpp, s->pwpkt_type); + put_asciz(padded_pw, prompt_get_result_ref( + s->cur_prompt->prompts[0])); + size_t pad = 63 & -padded_pw->len; + random_read(strbuf_append(padded_pw, pad), pad); + put_stringsb(pkt, padded_pw); + pq_push(s->ppl.out_pq, pkt); + } else { + /* + * The server is believed unable to cope with + * any of our password camouflage methods. + */ + ppl_logevent("Sending unpadded password"); + pkt = ssh_bpp_new_pktout(s->ppl.bpp, s->pwpkt_type); + put_stringz(pkt, prompt_get_result_ref( + s->cur_prompt->prompts[0])); + pq_push(s->ppl.out_pq, pkt); + } + } else { + pkt = ssh_bpp_new_pktout(s->ppl.bpp, s->pwpkt_type); + put_stringz(pkt, prompt_get_result_ref(s->cur_prompt->prompts[0])); + pq_push(s->ppl.out_pq, pkt); + } + ppl_logevent("Sent password"); + free_prompts(s->cur_prompt); + s->cur_prompt = NULL; + crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL); + if (pktin->type == SSH1_SMSG_FAILURE) { + if (seat_verbose(s->ppl.seat)) + ppl_printf("Access denied\r\n"); + ppl_logevent("Authentication refused"); + } else if (pktin->type != SSH1_SMSG_SUCCESS) { + ssh_proto_error(s->ppl.ssh, "Received unexpected packet" + " in response to password authentication, type %d " + "(%s)", pktin->type, ssh1_pkt_type(pktin->type)); + return; + } + } + + ppl_logevent("Authentication successful"); + + if (conf_get_bool(s->conf, CONF_compression)) { + ppl_logevent("Requesting compression"); + pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_REQUEST_COMPRESSION); + put_uint32(pkt, 6); /* gzip compression level */ + pq_push(s->ppl.out_pq, pkt); + crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL); + if (pktin->type == SSH1_SMSG_SUCCESS) { + /* + * We don't have to actually do anything here: the SSH-1 + * BPP will take care of automatically starting the + * compression, by recognising our outgoing request packet + * and the success response. (Horrible, but it's the + * easiest way to avoid race conditions if other packets + * cross in transit.) + */ + } else if (pktin->type == SSH1_SMSG_FAILURE) { + ppl_logevent("Server refused to enable compression"); + ppl_printf("Server refused to compress\r\n"); + } else { + ssh_proto_error(s->ppl.ssh, "Received unexpected packet" + " in response to compression request, type %d " + "(%s)", pktin->type, ssh1_pkt_type(pktin->type)); + return; + } + } + + ssh1_connection_set_protoflags( + s->successor_layer, s->local_protoflags, s->remote_protoflags); + { + PacketProtocolLayer *successor = s->successor_layer; + s->successor_layer = NULL; /* avoid freeing it ourself */ + ssh_ppl_replace(&s->ppl, successor); + return; /* we've just freed s, so avoid even touching s->crState */ + } + + crFinishV; +} + +static void ssh1_login_setup_tis_scc(struct ssh1_login_state *s) +{ + if (s->tis_scc_initialised) + return; + s->tis_scc = seat_stripctrl_new(s->ppl.seat, NULL, SIC_KI_PROMPTS); + if (s->tis_scc) + stripctrl_enable_line_limiting(s->tis_scc); + s->tis_scc_initialised = true; +} + +static void ssh1_login_dialog_callback(void *loginv, int ret) +{ + struct ssh1_login_state *s = (struct ssh1_login_state *)loginv; + s->dlgret = ret; + ssh_ppl_process_queue(&s->ppl); +} + +static void ssh1_login_agent_query(struct ssh1_login_state *s, strbuf *req) +{ + void *response; + int response_len; + + sfree(s->agent_response_to_free); + s->agent_response_to_free = NULL; + + s->auth_agent_query = agent_query(req, &response, &response_len, + ssh1_login_agent_callback, s); + if (!s->auth_agent_query) + ssh1_login_agent_callback(s, response, response_len); +} + +static void ssh1_login_agent_callback(void *loginv, void *reply, int replylen) +{ + struct ssh1_login_state *s = (struct ssh1_login_state *)loginv; + + s->auth_agent_query = NULL; + s->agent_response_to_free = reply; + s->agent_response = make_ptrlen(reply, replylen); + + queue_idempotent_callback(&s->ppl.ic_process_queue); +} + +static void ssh1_login_special_cmd(PacketProtocolLayer *ppl, + SessionSpecialCode code, int arg) +{ + struct ssh1_login_state *s = + container_of(ppl, struct ssh1_login_state, ppl); + PktOut *pktout; + + if (code == SS_PING || code == SS_NOP) { + if (!(s->ppl.remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE)) { + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_MSG_IGNORE); + put_stringz(pktout, ""); + pq_push(s->ppl.out_pq, pktout); + } + } +} + +static bool ssh1_login_want_user_input(PacketProtocolLayer *ppl) +{ + struct ssh1_login_state *s = + container_of(ppl, struct ssh1_login_state, ppl); + return s->want_user_input; +} + +static void ssh1_login_got_user_input(PacketProtocolLayer *ppl) +{ + struct ssh1_login_state *s = + container_of(ppl, struct ssh1_login_state, ppl); + if (s->want_user_input) + queue_idempotent_callback(&s->ppl.ic_process_queue); +} + +static void ssh1_login_reconfigure(PacketProtocolLayer *ppl, Conf *conf) +{ + struct ssh1_login_state *s = + container_of(ppl, struct ssh1_login_state, ppl); + ssh_ppl_reconfigure(s->successor_layer, conf); +} diff --git a/ssh/mainchan.c b/ssh/mainchan.c new file mode 100644 index 00000000..70033a7a --- /dev/null +++ b/ssh/mainchan.c @@ -0,0 +1,538 @@ +/* + * SSH main session channel handling. + */ + +#include +#include +#include + +#include "putty.h" +#include "ssh.h" +#include "ppl.h" +#include "channel.h" + +static void mainchan_free(Channel *chan); +static void mainchan_open_confirmation(Channel *chan); +static void mainchan_open_failure(Channel *chan, const char *errtext); +static size_t mainchan_send( + Channel *chan, bool is_stderr, const void *, size_t); +static void mainchan_send_eof(Channel *chan); +static void mainchan_set_input_wanted(Channel *chan, bool wanted); +static char *mainchan_log_close_msg(Channel *chan); +static bool mainchan_rcvd_exit_status(Channel *chan, int status); +static bool mainchan_rcvd_exit_signal( + Channel *chan, ptrlen signame, bool core_dumped, ptrlen msg); +static bool mainchan_rcvd_exit_signal_numeric( + Channel *chan, int signum, bool core_dumped, ptrlen msg); +static void mainchan_request_response(Channel *chan, bool success); + +static const ChannelVtable mainchan_channelvt = { + .free = mainchan_free, + .open_confirmation = mainchan_open_confirmation, + .open_failed = mainchan_open_failure, + .send = mainchan_send, + .send_eof = mainchan_send_eof, + .set_input_wanted = mainchan_set_input_wanted, + .log_close_msg = mainchan_log_close_msg, + .want_close = chan_default_want_close, + .rcvd_exit_status = mainchan_rcvd_exit_status, + .rcvd_exit_signal = mainchan_rcvd_exit_signal, + .rcvd_exit_signal_numeric = mainchan_rcvd_exit_signal_numeric, + .run_shell = chan_no_run_shell, + .run_command = chan_no_run_command, + .run_subsystem = chan_no_run_subsystem, + .enable_x11_forwarding = chan_no_enable_x11_forwarding, + .enable_agent_forwarding = chan_no_enable_agent_forwarding, + .allocate_pty = chan_no_allocate_pty, + .set_env = chan_no_set_env, + .send_break = chan_no_send_break, + .send_signal = chan_no_send_signal, + .change_window_size = chan_no_change_window_size, + .request_response = mainchan_request_response, +}; + +typedef enum MainChanType { + MAINCHAN_SESSION, MAINCHAN_DIRECT_TCPIP +} MainChanType; + +struct mainchan { + SshChannel *sc; + Conf *conf; + PacketProtocolLayer *ppl; + ConnectionLayer *cl; + + MainChanType type; + bool is_simple; + + bool req_x11, req_agent, req_pty, req_cmd_primary, req_cmd_fallback; + int n_req_env, n_env_replies, n_env_fails; + bool eof_pending, eof_sent, got_pty, ready; + + int term_width, term_height; + + Channel chan; +}; + +mainchan *mainchan_new( + PacketProtocolLayer *ppl, ConnectionLayer *cl, Conf *conf, + int term_width, int term_height, bool is_simple, SshChannel **sc_out) +{ + mainchan *mc; + + if (conf_get_bool(conf, CONF_ssh_no_shell)) + return NULL; /* no main channel at all */ + + mc = snew(mainchan); + memset(mc, 0, sizeof(mainchan)); + mc->ppl = ppl; + mc->cl = cl; + mc->conf = conf_copy(conf); + mc->term_width = term_width; + mc->term_height = term_height; + mc->is_simple = is_simple; + + mc->sc = NULL; + mc->chan.vt = &mainchan_channelvt; + mc->chan.initial_fixed_window_size = 0; + + if (*conf_get_str(mc->conf, CONF_ssh_nc_host)) { + const char *host = conf_get_str(mc->conf, CONF_ssh_nc_host); + int port = conf_get_int(mc->conf, CONF_ssh_nc_port); + + mc->sc = ssh_lportfwd_open(cl, host, port, "main channel", + NULL, &mc->chan); + mc->type = MAINCHAN_DIRECT_TCPIP; + } else { + mc->sc = ssh_session_open(cl, &mc->chan); + mc->type = MAINCHAN_SESSION; + } + + if (sc_out) *sc_out = mc->sc; + return mc; +} + +static void mainchan_free(Channel *chan) +{ + assert(chan->vt == &mainchan_channelvt); + mainchan *mc = container_of(chan, mainchan, chan); + conf_free(mc->conf); + sfree(mc); +} + +static void mainchan_try_fallback_command(mainchan *mc); +static void mainchan_ready(mainchan *mc); + +static void mainchan_open_confirmation(Channel *chan) +{ + mainchan *mc = container_of(chan, mainchan, chan); + PacketProtocolLayer *ppl = mc->ppl; /* for ppl_logevent */ + + seat_update_specials_menu(mc->ppl->seat); + ppl_logevent("Opened main channel"); + + if (mc->is_simple) + sshfwd_hint_channel_is_simple(mc->sc); + + if (mc->type == MAINCHAN_SESSION) { + /* + * Send the CHANNEL_REQUESTS for the main session channel. + */ + char *key, *val, *cmd; + struct X11Display *x11disp; + struct X11FakeAuth *x11auth; + bool retry_cmd_now = false; + + if (conf_get_bool(mc->conf, CONF_x11_forward)) { + char *x11_setup_err; + if ((x11disp = x11_setup_display( + conf_get_str(mc->conf, CONF_x11_display), + mc->conf, &x11_setup_err)) == NULL) { + ppl_logevent("X11 forwarding not enabled: unable to" + " initialise X display: %s", x11_setup_err); + sfree(x11_setup_err); + } else { + x11auth = ssh_add_x11_display( + mc->cl, conf_get_int(mc->conf, CONF_x11_auth), x11disp); + + sshfwd_request_x11_forwarding( + mc->sc, true, x11auth->protoname, x11auth->datastring, + x11disp->screennum, false); + mc->req_x11 = true; + } + } + + if (ssh_agent_forwarding_permitted(mc->cl)) { + sshfwd_request_agent_forwarding(mc->sc, true); + mc->req_agent = true; + } + + if (!conf_get_bool(mc->conf, CONF_nopty)) { + sshfwd_request_pty( + mc->sc, true, mc->conf, mc->term_width, mc->term_height); + mc->req_pty = true; + } + + for (val = conf_get_str_strs(mc->conf, CONF_environmt, NULL, &key); + val != NULL; + val = conf_get_str_strs(mc->conf, CONF_environmt, key, &key)) { + sshfwd_send_env_var(mc->sc, true, key, val); + mc->n_req_env++; + } + if (mc->n_req_env) + ppl_logevent("Sent %d environment variables", mc->n_req_env); + + cmd = conf_get_str(mc->conf, CONF_remote_cmd); + if (conf_get_bool(mc->conf, CONF_ssh_subsys)) { + retry_cmd_now = !sshfwd_start_subsystem(mc->sc, true, cmd); + } else if (*cmd) { + sshfwd_start_command(mc->sc, true, cmd); + } else { + sshfwd_start_shell(mc->sc, true); + } + + if (retry_cmd_now) + mainchan_try_fallback_command(mc); + else + mc->req_cmd_primary = true; + + } else { + ssh_set_ldisc_option(mc->cl, LD_ECHO, true); + ssh_set_ldisc_option(mc->cl, LD_EDIT, true); + mainchan_ready(mc); + } +} + +static void mainchan_try_fallback_command(mainchan *mc) +{ + const char *cmd = conf_get_str(mc->conf, CONF_remote_cmd2); + if (conf_get_bool(mc->conf, CONF_ssh_subsys2)) { + sshfwd_start_subsystem(mc->sc, true, cmd); + } else { + sshfwd_start_command(mc->sc, true, cmd); + } + mc->req_cmd_fallback = true; +} + +static void mainchan_request_response(Channel *chan, bool success) +{ + assert(chan->vt == &mainchan_channelvt); + mainchan *mc = container_of(chan, mainchan, chan); + PacketProtocolLayer *ppl = mc->ppl; /* for ppl_logevent */ + + if (mc->req_x11) { + mc->req_x11 = false; + + if (success) { + ppl_logevent("X11 forwarding enabled"); + ssh_enable_x_fwd(mc->cl); + } else { + ppl_logevent("X11 forwarding refused"); + } + return; + } + + if (mc->req_agent) { + mc->req_agent = false; + + if (success) { + ppl_logevent("Agent forwarding enabled"); + } else { + ppl_logevent("Agent forwarding refused"); + } + return; + } + + if (mc->req_pty) { + mc->req_pty = false; + + if (success) { + ppl_logevent("Allocated pty"); + mc->got_pty = true; + } else { + ppl_logevent("Server refused to allocate pty"); + ppl_printf("Server refused to allocate pty\r\n"); + ssh_set_ldisc_option(mc->cl, LD_ECHO, true); + ssh_set_ldisc_option(mc->cl, LD_EDIT, true); + } + return; + } + + if (mc->n_env_replies < mc->n_req_env) { + int j = mc->n_env_replies++; + if (!success) { + ppl_logevent("Server refused to set environment variable %s", + conf_get_str_nthstrkey(mc->conf, + CONF_environmt, j)); + mc->n_env_fails++; + } + + if (mc->n_env_replies == mc->n_req_env) { + if (mc->n_env_fails == 0) { + ppl_logevent("All environment variables successfully set"); + } else if (mc->n_env_fails == mc->n_req_env) { + ppl_logevent("All environment variables refused"); + ppl_printf("Server refused to set environment " + "variables\r\n"); + } else { + ppl_printf("Server refused to set all environment " + "variables\r\n"); + } + } + return; + } + + if (mc->req_cmd_primary) { + mc->req_cmd_primary = false; + + if (success) { + ppl_logevent("Started a shell/command"); + mainchan_ready(mc); + } else if (*conf_get_str(mc->conf, CONF_remote_cmd2)) { + ppl_logevent("Primary command failed; attempting fallback"); + mainchan_try_fallback_command(mc); + } else { + /* + * If there's no remote_cmd2 configured, then we have no + * fallback command, so we've run out of options. + */ + ssh_sw_abort(mc->ppl->ssh, + "Server refused to start a shell/command"); + } + return; + } + + if (mc->req_cmd_fallback) { + mc->req_cmd_fallback = false; + + if (success) { + ppl_logevent("Started a shell/command"); + ssh_got_fallback_cmd(mc->ppl->ssh); + mainchan_ready(mc); + } else { + ssh_sw_abort(mc->ppl->ssh, + "Server refused to start a shell/command"); + } + return; + } +} + +static void mainchan_ready(mainchan *mc) +{ + mc->ready = true; + + ssh_set_wants_user_input(mc->cl, true); + ssh_ppl_got_user_input(mc->ppl); /* in case any is already queued */ + + /* If an EOF arrived before we were ready, handle it now. */ + if (mc->eof_pending) { + mc->eof_pending = false; + mainchan_special_cmd(mc, SS_EOF, 0); + } + + ssh_ldisc_update(mc->ppl->ssh); + queue_idempotent_callback(&mc->ppl->ic_process_queue); +} + +static void mainchan_open_failure(Channel *chan, const char *errtext) +{ + assert(chan->vt == &mainchan_channelvt); + mainchan *mc = container_of(chan, mainchan, chan); + + ssh_sw_abort_deferred(mc->ppl->ssh, + "Server refused to open main channel: %s", errtext); +} + +static size_t mainchan_send(Channel *chan, bool is_stderr, + const void *data, size_t length) +{ + assert(chan->vt == &mainchan_channelvt); + mainchan *mc = container_of(chan, mainchan, chan); + return seat_output(mc->ppl->seat, is_stderr, data, length); +} + +static void mainchan_send_eof(Channel *chan) +{ + assert(chan->vt == &mainchan_channelvt); + mainchan *mc = container_of(chan, mainchan, chan); + PacketProtocolLayer *ppl = mc->ppl; /* for ppl_logevent */ + + if (!mc->eof_sent && (seat_eof(mc->ppl->seat) || mc->got_pty)) { + /* + * Either seat_eof told us that the front end wants us to + * close the outgoing side of the connection as soon as we see + * EOF from the far end, or else we've unilaterally decided to + * do that because we've allocated a remote pty and hence EOF + * isn't a particularly meaningful concept. + */ + sshfwd_write_eof(mc->sc); + ppl_logevent("Sent EOF message"); + mc->eof_sent = true; + ssh_set_wants_user_input(mc->cl, false); /* stop reading from stdin */ + } +} + +static void mainchan_set_input_wanted(Channel *chan, bool wanted) +{ + assert(chan->vt == &mainchan_channelvt); + mainchan *mc = container_of(chan, mainchan, chan); + + /* + * This is the main channel of the SSH session, i.e. the one tied + * to the standard input (or GUI) of the primary SSH client user + * interface. So ssh->send_ok is how we control whether we're + * reading from that input. + */ + ssh_set_wants_user_input(mc->cl, wanted); +} + +static char *mainchan_log_close_msg(Channel *chan) +{ + return dupstr("Main session channel closed"); +} + +static bool mainchan_rcvd_exit_status(Channel *chan, int status) +{ + assert(chan->vt == &mainchan_channelvt); + mainchan *mc = container_of(chan, mainchan, chan); + PacketProtocolLayer *ppl = mc->ppl; /* for ppl_logevent */ + + ssh_got_exitcode(mc->ppl->ssh, status); + ppl_logevent("Session sent command exit status %d", status); + return true; +} + +static void mainchan_log_exit_signal_common( + mainchan *mc, const char *sigdesc, + bool core_dumped, ptrlen msg) +{ + PacketProtocolLayer *ppl = mc->ppl; /* for ppl_logevent */ + + const char *core_msg = core_dumped ? " (core dumped)" : ""; + const char *msg_pre = (msg.len ? " (" : ""); + const char *msg_post = (msg.len ? ")" : ""); + ppl_logevent("Session exited on %s%s%s%.*s%s", + sigdesc, core_msg, msg_pre, PTRLEN_PRINTF(msg), msg_post); +} + +static bool mainchan_rcvd_exit_signal( + Channel *chan, ptrlen signame, bool core_dumped, ptrlen msg) +{ + assert(chan->vt == &mainchan_channelvt); + mainchan *mc = container_of(chan, mainchan, chan); + int exitcode; + char *signame_str; + + /* + * Translate the signal description back into a locally meaningful + * number, or 128 if the string didn't match any we recognise. + */ + exitcode = 128; + + #define SIGNAL_SUB(s) \ + if (ptrlen_eq_string(signame, #s)) \ + exitcode = 128 + SIG ## s; + #define SIGNAL_MAIN(s, text) SIGNAL_SUB(s) + #define SIGNALS_LOCAL_ONLY + #include "signal-list.h" + #undef SIGNAL_SUB + #undef SIGNAL_MAIN + #undef SIGNALS_LOCAL_ONLY + + ssh_got_exitcode(mc->ppl->ssh, exitcode); + if (exitcode == 128) + signame_str = dupprintf("unrecognised signal \"%.*s\"", + PTRLEN_PRINTF(signame)); + else + signame_str = dupprintf("signal SIG%.*s", PTRLEN_PRINTF(signame)); + mainchan_log_exit_signal_common(mc, signame_str, core_dumped, msg); + sfree(signame_str); + return true; +} + +static bool mainchan_rcvd_exit_signal_numeric( + Channel *chan, int signum, bool core_dumped, ptrlen msg) +{ + assert(chan->vt == &mainchan_channelvt); + mainchan *mc = container_of(chan, mainchan, chan); + char *signum_str; + + ssh_got_exitcode(mc->ppl->ssh, 128 + signum); + signum_str = dupprintf("signal %d", signum); + mainchan_log_exit_signal_common(mc, signum_str, core_dumped, msg); + sfree(signum_str); + return true; +} + +void mainchan_get_specials( + mainchan *mc, add_special_fn_t add_special, void *ctx) +{ + /* FIXME: this _does_ depend on whether these services are supported */ + + add_special(ctx, "Break", SS_BRK, 0); + + #define SIGNAL_MAIN(name, desc) \ + add_special(ctx, "SIG" #name " (" desc ")", SS_SIG ## name, 0); + #define SIGNAL_SUB(name) + #include "signal-list.h" + #undef SIGNAL_MAIN + #undef SIGNAL_SUB + + add_special(ctx, "More signals", SS_SUBMENU, 0); + + #define SIGNAL_MAIN(name, desc) + #define SIGNAL_SUB(name) \ + add_special(ctx, "SIG" #name, SS_SIG ## name, 0); + #include "signal-list.h" + #undef SIGNAL_MAIN + #undef SIGNAL_SUB + + add_special(ctx, NULL, SS_EXITMENU, 0); +} + +static const char *ssh_signal_lookup(SessionSpecialCode code) +{ + #define SIGNAL_SUB(name) \ + if (code == SS_SIG ## name) return #name; + #define SIGNAL_MAIN(name, desc) SIGNAL_SUB(name) + #include "signal-list.h" + #undef SIGNAL_MAIN + #undef SIGNAL_SUB + + /* If none of those clauses matched, fail lookup. */ + return NULL; +} + +void mainchan_special_cmd(mainchan *mc, SessionSpecialCode code, int arg) +{ + PacketProtocolLayer *ppl = mc->ppl; /* for ppl_logevent */ + const char *signame; + + if (code == SS_EOF) { + if (!mc->ready) { + /* + * Buffer the EOF to send as soon as the main channel is + * fully set up. + */ + mc->eof_pending = true; + } else if (!mc->eof_sent) { + sshfwd_write_eof(mc->sc); + mc->eof_sent = true; + } + } else if (code == SS_BRK) { + sshfwd_send_serial_break( + mc->sc, false, 0 /* default break length */); + } else if ((signame = ssh_signal_lookup(code)) != NULL) { + /* It's a signal. */ + sshfwd_send_signal(mc->sc, false, signame); + ppl_logevent("Sent signal SIG%s", signame); + } +} + +void mainchan_terminal_size(mainchan *mc, int width, int height) +{ + mc->term_width = width; + mc->term_height = height; + + if (mc->req_pty || mc->got_pty) + sshfwd_send_terminal_size_change(mc->sc, width, height); +} diff --git a/ssh/nogss.c b/ssh/nogss.c new file mode 100644 index 00000000..fa4ac65f --- /dev/null +++ b/ssh/nogss.c @@ -0,0 +1,19 @@ +#include "putty.h" +#ifndef NO_GSSAPI + +/* For platforms not supporting GSSAPI */ + +struct ssh_gss_liblist *ssh_gss_setup(Conf *conf) +{ + struct ssh_gss_liblist *list = snew(struct ssh_gss_liblist *); + list->libraries = NULL; + list->nlibraries = 0; + return list; +} + +void ssh_gss_cleanup(struct ssh_gss_liblist *list) +{ + sfree(list); +} + +#endif /* NO_GSSAPI */ diff --git a/ssh/nosharing.c b/ssh/nosharing.c new file mode 100644 index 00000000..c45634c5 --- /dev/null +++ b/ssh/nosharing.c @@ -0,0 +1,25 @@ +/* + * Stub implementation of SSH connection-sharing IPC, for any + * platform which can't support it at all. + */ + +#include +#include +#include + +#include "tree234.h" +#include "putty.h" +#include "ssh.h" +#include "network.h" + +int platform_ssh_share(const char *name, Conf *conf, + Plug *downplug, Plug *upplug, Socket **sock, + char **logtext, char **ds_err, char **us_err, + bool can_upstream, bool can_downstream) +{ + return SHARE_NONE; +} + +void platform_ssh_share_cleanup(const char *name) +{ +} diff --git a/ssh/pgssapi.c b/ssh/pgssapi.c new file mode 100644 index 00000000..9d33220f --- /dev/null +++ b/ssh/pgssapi.c @@ -0,0 +1,105 @@ +/* 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 */ diff --git a/ssh/pgssapi.h b/ssh/pgssapi.h new file mode 100644 index 00000000..53d8cb61 --- /dev/null +++ b/ssh/pgssapi.h @@ -0,0 +1,333 @@ +#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 +typedef gss_OID const_gss_OID; /* for our prototypes below */ +#else /* STATIC_GSSAPI */ + +/******************************************************************************* + * GSSAPI Definitions, taken from RFC 2744 + ******************************************************************************/ + +/* GSSAPI Type Definitions */ +typedef uint32_t 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; +typedef int gss_cred_usage_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 + +/*- + * RFC 2744 Page 86 + * Expiration time of 2^32-1 seconds means infinite lifetime for a + * credential or security context + */ +#define GSS_C_INDEFINITE 0xfffffffful + +/* 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_verify_mic) + (OM_uint32 * /*minor_status*/, + const gss_ctx_id_t /*context_handle*/, + const gss_buffer_t /*message_buffer*/, + const gss_buffer_t /*msg_token*/, + gss_qop_t * /*qop_state*/); + +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*/); + +typedef OM_uint32 (GSS_CC *t_gss_acquire_cred) + (OM_uint32 * /*minor_status*/, + const gss_name_t /*desired_name*/, + OM_uint32 /*time_req*/, + const gss_OID_set /*desired_mechs*/, + gss_cred_usage_t /*cred_usage*/, + gss_cred_id_t * /*output_cred_handle*/, + gss_OID_set * /*actual_mechs*/, + OM_uint32 * /*time_rec*/); + +typedef OM_uint32 (GSS_CC *t_gss_inquire_cred_by_mech) + (OM_uint32 * /*minor_status*/, + const gss_cred_id_t /*cred_handle*/, + const gss_OID /*mech_type*/, + gss_name_t * /*name*/, + OM_uint32 * /*initiator_lifetime*/, + OM_uint32 * /*acceptor_lifetime*/, + gss_cred_usage_t * /*cred_usage*/); + +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_verify_mic verify_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; + t_gss_acquire_cred acquire_cred; + t_gss_inquire_cred_by_mech inquire_cred_by_mech; +}; + +#endif /* NO_GSSAPI */ + +#endif /* PUTTY_PGSSAPI_H */ diff --git a/ssh/portfwd.c b/ssh/portfwd.c new file mode 100644 index 00000000..f96fe31a --- /dev/null +++ b/ssh/portfwd.c @@ -0,0 +1,1174 @@ +/* + * SSH port forwarding. + */ + +#include +#include +#include + +#include "putty.h" +#include "ssh.h" +#include "channel.h" + +/* + * Enumeration of values that live in the 'socks_state' field of + * struct PortForwarding. + */ +typedef enum { + SOCKS_NONE, /* direct connection (no SOCKS, or SOCKS already done) */ + SOCKS_INITIAL, /* don't know if we're SOCKS 4 or 5 yet */ + SOCKS_4, /* expect a SOCKS 4 (or 4A) connection message */ + SOCKS_5_INITIAL, /* expect a SOCKS 5 preliminary message */ + SOCKS_5_CONNECT /* expect a SOCKS 5 connection message */ +} SocksState; + +typedef struct PortForwarding { + SshChannel *c; /* channel structure held by SSH connection layer */ + ConnectionLayer *cl; /* the connection layer itself */ + /* Note that ssh need not be filled in if c is non-NULL */ + Socket *s; + bool input_wanted; + bool ready; + SocksState socks_state; + /* + * `hostname' and `port' are the real hostname and port, once + * we know what we're connecting to. + */ + char *hostname; + int port; + /* + * `socksbuf' is the buffer we use to accumulate the initial SOCKS + * segment of the incoming data, plus anything after that that we + * receive before we're ready to send data to the SSH server. + */ + strbuf *socksbuf; + size_t socksbuf_consumed; + + Plug plug; + Channel chan; +} PortForwarding; + +struct PortListener { + ConnectionLayer *cl; + Socket *s; + bool is_dynamic; + /* + * `hostname' and `port' are the real hostname and port, for + * ordinary forwardings. + */ + char *hostname; + int port; + + Plug plug; +}; + +static struct PortForwarding *new_portfwd_state(void) +{ + struct PortForwarding *pf = snew(struct PortForwarding); + pf->hostname = NULL; + pf->socksbuf = NULL; + return pf; +} + +static void free_portfwd_state(struct PortForwarding *pf) +{ + if (!pf) + return; + sfree(pf->hostname); + if (pf->socksbuf) + strbuf_free(pf->socksbuf); + sfree(pf); +} + +static struct PortListener *new_portlistener_state(void) +{ + struct PortListener *pl = snew(struct PortListener); + pl->hostname = NULL; + return pl; +} + +static void free_portlistener_state(struct PortListener *pl) +{ + if (!pl) + return; + sfree(pl->hostname); + sfree(pl); +} + +static void pfd_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, + const char *error_msg, int error_code) +{ + /* we have to dump these since we have no interface to logging.c */ +} + +static void pfl_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, + const char *error_msg, int error_code) +{ + /* we have to dump these since we have no interface to logging.c */ +} + +static void pfd_close(struct PortForwarding *pf); + +static void pfd_closing(Plug *plug, const char *error_msg, int error_code, + bool calling_back) +{ + struct PortForwarding *pf = + container_of(plug, struct PortForwarding, plug); + + if (error_msg) { + /* + * Socket error. Slam the connection instantly shut. + */ + if (pf->c) { + sshfwd_initiate_close(pf->c, error_msg); + } else { + /* + * We might not have an SSH channel, if a socket error + * occurred during SOCKS negotiation. If not, we must + * clean ourself up without sshfwd_initiate_close's call + * back to pfd_close. + */ + pfd_close(pf); + } + } else { + /* + * Ordinary EOF received on socket. Send an EOF on the SSH + * channel. + */ + if (pf->c) + sshfwd_write_eof(pf->c); + } +} + +static void pfl_terminate(struct PortListener *pl); + +static void pfl_closing(Plug *plug, const char *error_msg, int error_code, + bool calling_back) +{ + struct PortListener *pl = (struct PortListener *) plug; + pfl_terminate(pl); +} + +static SshChannel *wrap_lportfwd_open( + ConnectionLayer *cl, const char *hostname, int port, + Socket *s, Channel *chan) +{ + SocketPeerInfo *pi; + char *description; + SshChannel *toret; + + pi = sk_peer_info(s); + if (pi && pi->log_text) { + description = dupprintf("forwarding from %s", pi->log_text); + } else { + description = dupstr("forwarding"); + } + toret = ssh_lportfwd_open(cl, hostname, port, description, pi, chan); + sk_free_peer_info(pi); + + sfree(description); + return toret; +} + +static char *ipv4_to_string(unsigned ipv4) +{ + return dupprintf("%u.%u.%u.%u", + (ipv4 >> 24) & 0xFF, (ipv4 >> 16) & 0xFF, + (ipv4 >> 8) & 0xFF, (ipv4 ) & 0xFF); +} + +static char *ipv6_to_string(ptrlen ipv6) +{ + const unsigned char *addr = ipv6.ptr; + assert(ipv6.len == 16); + return dupprintf("%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x", + (unsigned)GET_16BIT_MSB_FIRST(addr + 0), + (unsigned)GET_16BIT_MSB_FIRST(addr + 2), + (unsigned)GET_16BIT_MSB_FIRST(addr + 4), + (unsigned)GET_16BIT_MSB_FIRST(addr + 6), + (unsigned)GET_16BIT_MSB_FIRST(addr + 8), + (unsigned)GET_16BIT_MSB_FIRST(addr + 10), + (unsigned)GET_16BIT_MSB_FIRST(addr + 12), + (unsigned)GET_16BIT_MSB_FIRST(addr + 14)); +} + +static void pfd_receive(Plug *plug, int urgent, const char *data, size_t len) +{ + struct PortForwarding *pf = + container_of(plug, struct PortForwarding, plug); + + if (len == 0) + return; + + if (pf->socks_state != SOCKS_NONE) { + BinarySource src[1]; + + /* + * Store all the data we've got in socksbuf. + */ + put_data(pf->socksbuf, data, len); + + /* + * Check the start of socksbuf to see if it's a valid and + * complete message in the SOCKS exchange. + */ + + if (pf->socks_state == SOCKS_INITIAL) { + /* Preliminary: check the first byte of the data (which we + * _must_ have by now) to find out which SOCKS major + * version we're speaking. */ + switch (pf->socksbuf->u[0]) { + case 4: + pf->socks_state = SOCKS_4; + break; + case 5: + pf->socks_state = SOCKS_5_INITIAL; + break; + default: + pfd_close(pf); /* unrecognised version */ + return; + } + } + + BinarySource_BARE_INIT(src, pf->socksbuf->u, pf->socksbuf->len); + get_data(src, pf->socksbuf_consumed); + + while (pf->socks_state != SOCKS_NONE) { + unsigned socks_version, message_type, reserved_byte; + unsigned reply_code, port, ipv4, method; + ptrlen methods; + const char *socks4_hostname; + strbuf *output; + + switch (pf->socks_state) { + case SOCKS_INITIAL: + case SOCKS_NONE: + unreachable("These case values cannot appear"); + + case SOCKS_4: + /* SOCKS 4/4A connect message */ + socks_version = get_byte(src); + message_type = get_byte(src); + + if (get_err(src) == BSE_OUT_OF_DATA) + return; + if (socks_version == 4 && message_type == 1) { + /* CONNECT message */ + bool name_based = false; + + port = get_uint16(src); + ipv4 = get_uint32(src); + if (ipv4 > 0x00000000 && ipv4 < 0x00000100) { + /* + * Addresses in this range indicate the SOCKS 4A + * extension to specify a hostname, which comes + * after the username. + */ + name_based = true; + } + get_asciz(src); /* skip username */ + socks4_hostname = name_based ? get_asciz(src) : NULL; + + if (get_err(src) == BSE_OUT_OF_DATA) + return; + if (get_err(src)) + goto socks4_reject; + + pf->port = port; + if (name_based) { + pf->hostname = dupstr(socks4_hostname); + } else { + pf->hostname = ipv4_to_string(ipv4); + } + + output = strbuf_new(); + put_byte(output, 0); /* reply version */ + put_byte(output, 90); /* SOCKS 4 'request granted' */ + put_uint16(output, 0); /* null port field */ + put_uint32(output, 0); /* null address field */ + sk_write(pf->s, output->u, output->len); + strbuf_free(output); + + pf->socks_state = SOCKS_NONE; + pf->socksbuf_consumed = src->pos; + break; + } + + socks4_reject: + output = strbuf_new(); + put_byte(output, 0); /* reply version */ + put_byte(output, 91); /* SOCKS 4 'request rejected' */ + put_uint16(output, 0); /* null port field */ + put_uint32(output, 0); /* null address field */ + sk_write(pf->s, output->u, output->len); + strbuf_free(output); + pfd_close(pf); + return; + + case SOCKS_5_INITIAL: + /* SOCKS 5 initial method list */ + socks_version = get_byte(src); + methods = get_pstring(src); + + method = 0xFF; /* means 'no usable method found' */ + { + int i; + for (i = 0; i < methods.len; i++) { + if (((const unsigned char *)methods.ptr)[i] == 0 ) { + method = 0; /* no auth */ + break; + } + } + } + + if (get_err(src) == BSE_OUT_OF_DATA) + return; + if (get_err(src)) + method = 0xFF; + + output = strbuf_new(); + put_byte(output, 5); /* SOCKS version */ + put_byte(output, method); /* selected auth method */ + sk_write(pf->s, output->u, output->len); + strbuf_free(output); + + if (method == 0xFF) { + pfd_close(pf); + return; + } + + pf->socks_state = SOCKS_5_CONNECT; + pf->socksbuf_consumed = src->pos; + break; + + case SOCKS_5_CONNECT: + /* SOCKS 5 connect message */ + socks_version = get_byte(src); + message_type = get_byte(src); + reserved_byte = get_byte(src); + + if (socks_version == 5 && message_type == 1 && + reserved_byte == 0) { + + reply_code = 0; /* success */ + + switch (get_byte(src)) { + case 1: /* IPv4 */ + pf->hostname = ipv4_to_string(get_uint32(src)); + break; + case 4: /* IPv6 */ + pf->hostname = ipv6_to_string(get_data(src, 16)); + break; + case 3: /* unresolved domain name */ + pf->hostname = mkstr(get_pstring(src)); + break; + default: + pf->hostname = NULL; + reply_code = 8; /* address type not supported */ + break; + } + + pf->port = get_uint16(src); + } else { + reply_code = 7; /* command not supported */ + } + + if (get_err(src) == BSE_OUT_OF_DATA) + return; + if (get_err(src)) + reply_code = 1; /* general server failure */ + + output = strbuf_new(); + put_byte(output, 5); /* SOCKS version */ + put_byte(output, reply_code); + put_byte(output, 0); /* reserved */ + put_byte(output, 1); /* IPv4 address follows */ + put_uint32(output, 0); /* bound IPv4 address (unused) */ + put_uint16(output, 0); /* bound port number (unused) */ + sk_write(pf->s, output->u, output->len); + strbuf_free(output); + + if (reply_code != 0) { + pfd_close(pf); + return; + } + + pf->socks_state = SOCKS_NONE; + pf->socksbuf_consumed = src->pos; + break; + } + } + + /* + * We come here when we're ready to make an actual + * connection. + */ + + /* + * Freeze the socket until the SSH server confirms the + * connection. + */ + sk_set_frozen(pf->s, true); + + pf->c = wrap_lportfwd_open(pf->cl, pf->hostname, pf->port, pf->s, + &pf->chan); + } + if (pf->ready) + sshfwd_write(pf->c, data, len); +} + +static void pfd_sent(Plug *plug, size_t bufsize) +{ + struct PortForwarding *pf = + container_of(plug, struct PortForwarding, plug); + + if (pf->c) + sshfwd_unthrottle(pf->c, bufsize); +} + +static const PlugVtable PortForwarding_plugvt = { + .log = pfd_log, + .closing = pfd_closing, + .receive = pfd_receive, + .sent = pfd_sent, +}; + +static void pfd_chan_free(Channel *chan); +static void pfd_open_confirmation(Channel *chan); +static void pfd_open_failure(Channel *chan, const char *errtext); +static size_t pfd_send( + Channel *chan, bool is_stderr, const void *data, size_t len); +static void pfd_send_eof(Channel *chan); +static void pfd_set_input_wanted(Channel *chan, bool wanted); +static char *pfd_log_close_msg(Channel *chan); + +static const ChannelVtable PortForwarding_channelvt = { + .free = pfd_chan_free, + .open_confirmation = pfd_open_confirmation, + .open_failed = pfd_open_failure, + .send = pfd_send, + .send_eof = pfd_send_eof, + .set_input_wanted = pfd_set_input_wanted, + .log_close_msg = pfd_log_close_msg, + .want_close = chan_default_want_close, + .rcvd_exit_status = chan_no_exit_status, + .rcvd_exit_signal = chan_no_exit_signal, + .rcvd_exit_signal_numeric = chan_no_exit_signal_numeric, + .run_shell = chan_no_run_shell, + .run_command = chan_no_run_command, + .run_subsystem = chan_no_run_subsystem, + .enable_x11_forwarding = chan_no_enable_x11_forwarding, + .enable_agent_forwarding = chan_no_enable_agent_forwarding, + .allocate_pty = chan_no_allocate_pty, + .set_env = chan_no_set_env, + .send_break = chan_no_send_break, + .send_signal = chan_no_send_signal, + .change_window_size = chan_no_change_window_size, + .request_response = chan_no_request_response, +}; + +Channel *portfwd_raw_new(ConnectionLayer *cl, Plug **plug, bool start_ready) +{ + struct PortForwarding *pf; + + pf = new_portfwd_state(); + pf->plug.vt = &PortForwarding_plugvt; + pf->chan.initial_fixed_window_size = 0; + pf->chan.vt = &PortForwarding_channelvt; + pf->input_wanted = true; + + pf->c = NULL; + + pf->cl = cl; + pf->input_wanted = true; + pf->ready = start_ready; + + pf->socks_state = SOCKS_NONE; + pf->hostname = NULL; + pf->port = 0; + + *plug = &pf->plug; + return &pf->chan; +} + +void portfwd_raw_free(Channel *pfchan) +{ + struct PortForwarding *pf; + assert(pfchan->vt == &PortForwarding_channelvt); + pf = container_of(pfchan, struct PortForwarding, chan); + free_portfwd_state(pf); +} + +void portfwd_raw_setup(Channel *pfchan, Socket *s, SshChannel *sc) +{ + struct PortForwarding *pf; + assert(pfchan->vt == &PortForwarding_channelvt); + pf = container_of(pfchan, struct PortForwarding, chan); + + pf->s = s; + pf->c = sc; +} + +/* + called when someone connects to the local port + */ + +static int pfl_accepting(Plug *p, accept_fn_t constructor, accept_ctx_t ctx) +{ + struct PortListener *pl = container_of(p, struct PortListener, plug); + struct PortForwarding *pf; + Channel *chan; + Plug *plug; + Socket *s; + const char *err; + + chan = portfwd_raw_new(pl->cl, &plug, false); + s = constructor(ctx, plug); + if ((err = sk_socket_error(s)) != NULL) { + portfwd_raw_free(chan); + return 1; + } + + pf = container_of(chan, struct PortForwarding, chan); + + if (pl->is_dynamic) { + pf->s = s; + pf->socks_state = SOCKS_INITIAL; + pf->socksbuf = strbuf_new(); + pf->socksbuf_consumed = 0; + pf->port = 0; /* "hostname" buffer is so far empty */ + sk_set_frozen(s, false); /* we want to receive SOCKS _now_! */ + } else { + pf->hostname = dupstr(pl->hostname); + pf->port = pl->port; + portfwd_raw_setup( + chan, s, + wrap_lportfwd_open(pl->cl, pf->hostname, pf->port, s, &pf->chan)); + } + + return 0; +} + +static const PlugVtable PortListener_plugvt = { + .log = pfl_log, + .closing = pfl_closing, + .accepting = pfl_accepting, +}; + +/* + * Add a new port-forwarding listener from srcaddr:port -> desthost:destport. + * + * desthost == NULL indicates dynamic SOCKS port forwarding. + * + * On success, returns NULL and fills in *pl_ret. On error, returns a + * dynamically allocated error message string. + */ +static char *pfl_listen(const char *desthost, int destport, + const char *srcaddr, int port, + ConnectionLayer *cl, Conf *conf, + struct PortListener **pl_ret, int address_family) +{ + const char *err; + struct PortListener *pl; + + /* + * Open socket. + */ + pl = *pl_ret = new_portlistener_state(); + pl->plug.vt = &PortListener_plugvt; + if (desthost) { + pl->hostname = dupstr(desthost); + pl->port = destport; + pl->is_dynamic = false; + } else + pl->is_dynamic = true; + pl->cl = cl; + + pl->s = new_listener(srcaddr, port, &pl->plug, + !conf_get_bool(conf, CONF_lport_acceptall), + conf, address_family); + if ((err = sk_socket_error(pl->s)) != NULL) { + char *err_ret = dupstr(err); + sk_close(pl->s); + free_portlistener_state(pl); + *pl_ret = NULL; + return err_ret; + } + + return NULL; +} + +static char *pfd_log_close_msg(Channel *chan) +{ + return dupstr("Forwarded port closed"); +} + +static void pfd_close(struct PortForwarding *pf) +{ + if (!pf) + return; + + sk_close(pf->s); + free_portfwd_state(pf); +} + +/* + * Terminate a listener. + */ +static void pfl_terminate(struct PortListener *pl) +{ + if (!pl) + return; + + sk_close(pl->s); + free_portlistener_state(pl); +} + +static void pfd_set_input_wanted(Channel *chan, bool wanted) +{ + assert(chan->vt == &PortForwarding_channelvt); + PortForwarding *pf = container_of(chan, PortForwarding, chan); + pf->input_wanted = wanted; + sk_set_frozen(pf->s, !pf->input_wanted); +} + +static void pfd_chan_free(Channel *chan) +{ + assert(chan->vt == &PortForwarding_channelvt); + PortForwarding *pf = container_of(chan, PortForwarding, chan); + pfd_close(pf); +} + +/* + * Called to send data down the raw connection. + */ +static size_t pfd_send( + Channel *chan, bool is_stderr, const void *data, size_t len) +{ + assert(chan->vt == &PortForwarding_channelvt); + PortForwarding *pf = container_of(chan, PortForwarding, chan); + return sk_write(pf->s, data, len); +} + +static void pfd_send_eof(Channel *chan) +{ + assert(chan->vt == &PortForwarding_channelvt); + PortForwarding *pf = container_of(chan, PortForwarding, chan); + sk_write_eof(pf->s); +} + +static void pfd_open_confirmation(Channel *chan) +{ + assert(chan->vt == &PortForwarding_channelvt); + PortForwarding *pf = container_of(chan, PortForwarding, chan); + + pf->ready = true; + sk_set_frozen(pf->s, false); + sk_write(pf->s, NULL, 0); + if (pf->socksbuf) { + sshfwd_write(pf->c, pf->socksbuf->u + pf->socksbuf_consumed, + pf->socksbuf->len - pf->socksbuf_consumed); + strbuf_free(pf->socksbuf); + pf->socksbuf = NULL; + } +} + +static void pfd_open_failure(Channel *chan, const char *errtext) +{ + assert(chan->vt == &PortForwarding_channelvt); + PortForwarding *pf = container_of(chan, PortForwarding, chan); + + logeventf(pf->cl->logctx, + "Forwarded connection refused by remote%s%s", + errtext ? ": " : "", errtext ? errtext : ""); +} + +/* ---------------------------------------------------------------------- + * Code to manage the complete set of currently active port + * forwardings, and update it from Conf. + */ + +struct PortFwdRecord { + enum { DESTROY, KEEP, CREATE } status; + int type; + unsigned sport, dport; + char *saddr, *daddr; + char *sserv, *dserv; + struct ssh_rportfwd *remote; + int addressfamily; + struct PortListener *local; +}; + +static int pfr_cmp(void *av, void *bv) +{ + PortFwdRecord *a = (PortFwdRecord *) av; + PortFwdRecord *b = (PortFwdRecord *) 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 void pfr_free(PortFwdRecord *pfr) +{ + /* Dispose of any listening socket. */ + if (pfr->local) + pfl_terminate(pfr->local); + + sfree(pfr->saddr); + sfree(pfr->daddr); + sfree(pfr->sserv); + sfree(pfr->dserv); + sfree(pfr); +} + +struct PortFwdManager { + ConnectionLayer *cl; + Conf *conf; + tree234 *forwardings; +}; + +PortFwdManager *portfwdmgr_new(ConnectionLayer *cl) +{ + PortFwdManager *mgr = snew(PortFwdManager); + + mgr->cl = cl; + mgr->conf = NULL; + mgr->forwardings = newtree234(pfr_cmp); + + return mgr; +} + +void portfwdmgr_close(PortFwdManager *mgr, PortFwdRecord *pfr) +{ + PortFwdRecord *realpfr = del234(mgr->forwardings, pfr); + if (realpfr == pfr) + pfr_free(pfr); +} + +void portfwdmgr_close_all(PortFwdManager *mgr) +{ + PortFwdRecord *pfr; + + while ((pfr = delpos234(mgr->forwardings, 0)) != NULL) + pfr_free(pfr); +} + +void portfwdmgr_free(PortFwdManager *mgr) +{ + portfwdmgr_close_all(mgr); + freetree234(mgr->forwardings); + if (mgr->conf) + conf_free(mgr->conf); + sfree(mgr); +} + +void portfwdmgr_config(PortFwdManager *mgr, Conf *conf) +{ + PortFwdRecord *pfr; + int i; + char *key, *val; + + if (mgr->conf) + conf_free(mgr->conf); + mgr->conf = conf_copy(conf); + + /* + * 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. + */ + for (i = 0; (pfr = index234(mgr->forwardings, i)) != NULL; i++) + pfr->status = DESTROY; + + for (val = conf_get_str_strs(conf, CONF_portfwd, NULL, &key); + val != NULL; + val = conf_get_str_strs(conf, CONF_portfwd, key, &key)) { + char *kp, *kp2, *vp, *vp2; + char address_family, type; + int sport, dport, sserv, dserv; + char *sports, *dports, *saddr, *host; + + kp = key; + + address_family = 'A'; + type = 'L'; + if (*kp == 'A' || *kp == '4' || *kp == '6') + address_family = *kp++; + if (*kp == 'L' || *kp == 'R') + type = *kp++; + + if ((kp2 = host_strchr(kp, ':')) != NULL) { + /* + * There's a colon in the middle of the source port + * string, which means that the part before it is + * actually a source address. + */ + char *saddr_tmp = dupprintf("%.*s", (int)(kp2 - kp), kp); + saddr = host_strduptrim(saddr_tmp); + sfree(saddr_tmp); + sports = kp2+1; + } else { + saddr = NULL; + sports = kp; + } + sport = atoi(sports); + sserv = 0; + if (sport == 0) { + sserv = 1; + sport = net_service_lookup(sports); + if (!sport) { + logeventf(mgr->cl->logctx, "Service lookup failed for source" + " port \"%s\"", sports); + } + } + + if (type == 'L' && !strcmp(val, "D")) { + /* dynamic forwarding */ + host = NULL; + dports = NULL; + dport = -1; + dserv = 0; + type = 'D'; + } else { + /* ordinary forwarding */ + vp = val; + vp2 = vp + host_strcspn(vp, ":"); + host = dupprintf("%.*s", (int)(vp2 - vp), vp); + if (*vp2) + vp2++; + dports = vp2; + dport = atoi(dports); + dserv = 0; + if (dport == 0) { + dserv = 1; + dport = net_service_lookup(dports); + if (!dport) { + logeventf(mgr->cl->logctx, + "Service lookup failed for destination" + " port \"%s\"", dports); + } + } + } + + if (sport && dport) { + /* Set up a description of the source port. */ + pfr = snew(PortFwdRecord); + pfr->type = type; + pfr->saddr = saddr; + pfr->sserv = sserv ? dupstr(sports) : NULL; + pfr->sport = sport; + pfr->daddr = host; + pfr->dserv = dserv ? dupstr(dports) : NULL; + pfr->dport = dport; + pfr->local = NULL; + pfr->remote = NULL; + pfr->addressfamily = (address_family == '4' ? ADDRTYPE_IPV4 : + address_family == '6' ? ADDRTYPE_IPV6 : + ADDRTYPE_UNSPEC); + + PortFwdRecord *existing = add234(mgr->forwardings, pfr); + if (existing != pfr) { + if (existing->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. + */ + existing->status = KEEP; + } + /* + * Anything else indicates that there was a duplicate + * in our input, which we'll silently ignore. + */ + pfr_free(pfr); + } else { + pfr->status = CREATE; + } + } else { + sfree(saddr); + sfree(host); + } + } + + /* + * Now go through and destroy any port forwardings which were + * not re-enabled. + */ + for (i = 0; (pfr = index234(mgr->forwardings, i)) != NULL; i++) { + if (pfr->status == DESTROY) { + char *message; + + message = dupprintf("%s port forwarding from %s%s%d", + pfr->type == 'L' ? "local" : + pfr->type == 'R' ? "remote" : "dynamic", + pfr->saddr ? pfr->saddr : "", + pfr->saddr ? ":" : "", + pfr->sport); + + if (pfr->type != 'D') { + char *msg2 = dupprintf("%s to %s:%d", message, + pfr->daddr, pfr->dport); + sfree(message); + message = msg2; + } + + logeventf(mgr->cl->logctx, "Cancelling %s", message); + sfree(message); + + /* pfr->remote or pfr->local may be NULL if setting up a + * forwarding failed. */ + if (pfr->remote) { + /* + * Cancel the port forwarding at the server + * end. + * + * Actually closing the listening port on the server + * side may fail - because in SSH-1 there's no message + * in the protocol to request it! + * + * Instead, we simply remove the record of the + * forwarding from our local end, so that any + * connections the server tries to make on it are + * rejected. + */ + ssh_rportfwd_remove(mgr->cl, pfr->remote); + pfr->remote = NULL; + } else if (pfr->local) { + pfl_terminate(pfr->local); + pfr->local = NULL; + } + + delpos234(mgr->forwardings, i); + pfr_free(pfr); + i--; /* so we don't skip one in the list */ + } + } + + /* + * And finally, set up any new port forwardings (status==CREATE). + */ + for (i = 0; (pfr = index234(mgr->forwardings, i)) != NULL; i++) { + if (pfr->status == CREATE) { + char *sportdesc, *dportdesc; + sportdesc = dupprintf("%s%s%s%s%d%s", + pfr->saddr ? pfr->saddr : "", + pfr->saddr ? ":" : "", + pfr->sserv ? pfr->sserv : "", + pfr->sserv ? "(" : "", + pfr->sport, + pfr->sserv ? ")" : ""); + if (pfr->type == 'D') { + dportdesc = NULL; + } else { + dportdesc = dupprintf("%s:%s%s%d%s", + pfr->daddr, + pfr->dserv ? pfr->dserv : "", + pfr->dserv ? "(" : "", + pfr->dport, + pfr->dserv ? ")" : ""); + } + + if (pfr->type == 'L') { + char *err = pfl_listen(pfr->daddr, pfr->dport, + pfr->saddr, pfr->sport, + mgr->cl, conf, &pfr->local, + pfr->addressfamily); + + logeventf(mgr->cl->logctx, + "Local %sport %s forwarding to %s%s%s", + pfr->addressfamily == ADDRTYPE_IPV4 ? "IPv4 " : + pfr->addressfamily == ADDRTYPE_IPV6 ? "IPv6 " : "", + sportdesc, dportdesc, + err ? " failed: " : "", err ? err : ""); + if (err) + sfree(err); + } else if (pfr->type == 'D') { + char *err = pfl_listen(NULL, -1, pfr->saddr, pfr->sport, + mgr->cl, conf, &pfr->local, + pfr->addressfamily); + + logeventf(mgr->cl->logctx, + "Local %sport %s SOCKS dynamic forwarding%s%s", + pfr->addressfamily == ADDRTYPE_IPV4 ? "IPv4 " : + pfr->addressfamily == ADDRTYPE_IPV6 ? "IPv6 " : "", + sportdesc, + err ? " failed: " : "", err ? err : ""); + + if (err) + sfree(err); + } else { + const char *shost; + + if (pfr->saddr) { + shost = pfr->saddr; + } else if (conf_get_bool(conf, CONF_rport_acceptall)) { + shost = ""; + } else { + shost = "localhost"; + } + + pfr->remote = ssh_rportfwd_alloc( + mgr->cl, shost, pfr->sport, pfr->daddr, pfr->dport, + pfr->addressfamily, sportdesc, pfr, NULL); + + if (!pfr->remote) { + logeventf(mgr->cl->logctx, + "Duplicate remote port forwarding to %s:%d", + pfr->daddr, pfr->dport); + pfr_free(pfr); + } else { + logeventf(mgr->cl->logctx, "Requesting remote port %s" + " forward to %s", sportdesc, dportdesc); + } + } + sfree(sportdesc); + sfree(dportdesc); + } + } +} + +bool portfwdmgr_listen(PortFwdManager *mgr, const char *host, int port, + const char *keyhost, int keyport, Conf *conf) +{ + PortFwdRecord *pfr; + + pfr = snew(PortFwdRecord); + pfr->type = 'L'; + pfr->saddr = host ? dupstr(host) : NULL; + pfr->daddr = keyhost ? dupstr(keyhost) : NULL; + pfr->sserv = pfr->dserv = NULL; + pfr->sport = port; + pfr->dport = keyport; + pfr->local = NULL; + pfr->remote = NULL; + pfr->addressfamily = ADDRTYPE_UNSPEC; + + PortFwdRecord *existing = add234(mgr->forwardings, pfr); + if (existing != pfr) { + /* + * We had this record already. Return failure. + */ + pfr_free(pfr); + return false; + } + + char *err = pfl_listen(keyhost, keyport, host, port, + mgr->cl, conf, &pfr->local, pfr->addressfamily); + logeventf(mgr->cl->logctx, + "%s on port %s:%d to forward to client%s%s", + err ? "Failed to listen" : "Listening", host, port, + err ? ": " : "", err ? err : ""); + if (err) { + sfree(err); + del234(mgr->forwardings, pfr); + pfr_free(pfr); + return false; + } + + return true; +} + +bool portfwdmgr_unlisten(PortFwdManager *mgr, const char *host, int port) +{ + PortFwdRecord pfr_key; + + pfr_key.type = 'L'; + /* Safe to cast the const away here, because it will only be used + * by pfr_cmp, which won't write to the string */ + pfr_key.saddr = pfr_key.daddr = (char *)host; + pfr_key.sserv = pfr_key.dserv = NULL; + pfr_key.sport = pfr_key.dport = port; + pfr_key.local = NULL; + pfr_key.remote = NULL; + pfr_key.addressfamily = ADDRTYPE_UNSPEC; + + PortFwdRecord *pfr = del234(mgr->forwardings, &pfr_key); + + if (!pfr) + return false; + + logeventf(mgr->cl->logctx, "Closing listening port %s:%d", host, port); + + pfr_free(pfr); + return true; +} + +/* + * Called when receiving a PORT OPEN from the server to make a + * connection to a destination host. + * + * On success, returns NULL and fills in *pf_ret. On error, returns a + * dynamically allocated error message string. + */ +char *portfwdmgr_connect(PortFwdManager *mgr, Channel **chan_ret, + char *hostname, int port, SshChannel *c, + int addressfamily) +{ + SockAddr *addr; + const char *err; + char *dummy_realhost = NULL; + struct PortForwarding *pf; + + /* + * Try to find host. + */ + addr = name_lookup(hostname, port, &dummy_realhost, mgr->conf, + addressfamily, NULL, NULL); + if ((err = sk_addr_error(addr)) != NULL) { + char *err_ret = dupstr(err); + sk_addr_free(addr); + sfree(dummy_realhost); + return err_ret; + } + + /* + * Open socket. + */ + pf = new_portfwd_state(); + *chan_ret = &pf->chan; + pf->plug.vt = &PortForwarding_plugvt; + pf->chan.initial_fixed_window_size = 0; + pf->chan.vt = &PortForwarding_channelvt; + pf->input_wanted = true; + pf->ready = true; + pf->c = c; + pf->cl = mgr->cl; + pf->socks_state = SOCKS_NONE; + + pf->s = new_connection(addr, dummy_realhost, port, + false, true, false, false, &pf->plug, mgr->conf); + sfree(dummy_realhost); + if ((err = sk_socket_error(pf->s)) != NULL) { + char *err_ret = dupstr(err); + sk_close(pf->s); + free_portfwd_state(pf); + *chan_ret = NULL; + return err_ret; + } + + return NULL; +} diff --git a/ssh/ppl.h b/ssh/ppl.h new file mode 100644 index 00000000..363b0e6d --- /dev/null +++ b/ssh/ppl.h @@ -0,0 +1,174 @@ +/* + * Abstraction of the various layers of SSH packet-level protocol, + * general enough to take in all three of the main SSH-2 layers and + * both of the SSH-1 phases. + */ + +#ifndef PUTTY_SSHPPL_H +#define PUTTY_SSHPPL_H + +typedef void (*packet_handler_fn_t)(PacketProtocolLayer *ppl, PktIn *pktin); +typedef struct PacketProtocolLayerVtable PacketProtocolLayerVtable; + +struct PacketProtocolLayerVtable { + void (*free)(PacketProtocolLayer *); + void (*process_queue)(PacketProtocolLayer *ppl); + bool (*get_specials)( + PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx); + void (*special_cmd)( + PacketProtocolLayer *ppl, SessionSpecialCode code, int arg); + bool (*want_user_input)(PacketProtocolLayer *ppl); + void (*got_user_input)(PacketProtocolLayer *ppl); + void (*reconfigure)(PacketProtocolLayer *ppl, Conf *conf); + size_t (*queued_data_size)(PacketProtocolLayer *ppl); + + /* Protocol-level name of this layer. */ + const char *name; +}; + +struct PacketProtocolLayer { + const struct PacketProtocolLayerVtable *vt; + + /* Link to the underlying SSH BPP. */ + BinaryPacketProtocol *bpp; + + /* Queue from which the layer receives its input packets, and one + * to put its output packets on. */ + PktInQueue *in_pq; + PktOutQueue *out_pq; + + /* Idempotent callback that in_pq will be linked to, causing a + * call to the process_queue method. in_pq points to this, so it + * will be automatically triggered by pushing things on the + * layer's input queue, but it can also be triggered on purpose. */ + IdempotentCallback ic_process_queue; + + /* Owner's pointer to this layer. Permits a layer to unilaterally + * abdicate in favour of a replacement, by overwriting this + * pointer and then freeing itself. */ + PacketProtocolLayer **selfptr; + + /* Bufchain of keyboard input from the user, for login prompts and + * similar. */ + bufchain *user_input; + + /* Logging and error-reporting facilities. */ + LogContext *logctx; + Seat *seat; /* for dialog boxes, session output etc */ + Ssh *ssh; /* for session termination + assorted connection-layer ops */ + + /* Known bugs in the remote implementation. */ + unsigned remote_bugs; +}; + +static inline void ssh_ppl_process_queue(PacketProtocolLayer *ppl) +{ ppl->vt->process_queue(ppl); } +static inline bool ssh_ppl_get_specials( + PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx) +{ return ppl->vt->get_specials(ppl, add_special, ctx); } +static inline void ssh_ppl_special_cmd( + PacketProtocolLayer *ppl, SessionSpecialCode code, int arg) +{ ppl->vt->special_cmd(ppl, code, arg); } +static inline bool ssh_ppl_want_user_input(PacketProtocolLayer *ppl) +{ return ppl->vt->want_user_input(ppl); } +static inline void ssh_ppl_got_user_input(PacketProtocolLayer *ppl) +{ ppl->vt->got_user_input(ppl); } +static inline void ssh_ppl_reconfigure(PacketProtocolLayer *ppl, Conf *conf) +{ ppl->vt->reconfigure(ppl, conf); } +static inline size_t ssh_ppl_queued_data_size(PacketProtocolLayer *ppl) +{ return ppl->vt->queued_data_size(ppl); } + +/* ssh_ppl_free is more than just a macro wrapper on the vtable; it + * does centralised parts of the freeing too. */ +void ssh_ppl_free(PacketProtocolLayer *ppl); + +/* Helper routine to point a PPL at its input and output queues. Also + * sets up the IdempotentCallback on the input queue to trigger a call + * to process_queue whenever packets are added to it. */ +void ssh_ppl_setup_queues(PacketProtocolLayer *ppl, + PktInQueue *inq, PktOutQueue *outq); + +/* Routine a PPL can call to abdicate in favour of a replacement, by + * overwriting ppl->selfptr. Has the side effect of freeing 'old', so + * if 'old' actually called this (which is likely) then it should + * avoid dereferencing itself on return from this function! */ +void ssh_ppl_replace(PacketProtocolLayer *old, PacketProtocolLayer *new); + +/* Default implementation of queued_data_size, which just adds up the + * sizes of all the packets in pq_out. A layer can override this if it + * has other things to take into account as well. */ +size_t ssh_ppl_default_queued_data_size(PacketProtocolLayer *ppl); + +PacketProtocolLayer *ssh1_login_new( + Conf *conf, const char *host, int port, + PacketProtocolLayer *successor_layer); +PacketProtocolLayer *ssh1_connection_new( + Ssh *ssh, Conf *conf, ConnectionLayer **cl_out); + +struct DataTransferStats; +struct ssh_connection_shared_gss_state; +PacketProtocolLayer *ssh2_transport_new( + Conf *conf, const char *host, int port, const char *fullhostname, + const char *client_greeting, const char *server_greeting, + struct ssh_connection_shared_gss_state *shgss, + struct DataTransferStats *stats, PacketProtocolLayer *higher_layer, + const SshServerConfig *ssc); +PacketProtocolLayer *ssh2_userauth_new( + PacketProtocolLayer *successor_layer, + const char *hostname, const char *fullhostname, + Filename *keyfile, bool show_banner, bool tryagent, + const char *default_username, bool change_username, + bool try_ki_auth, + bool try_gssapi_auth, bool try_gssapi_kex_auth, + bool gssapi_fwd, struct ssh_connection_shared_gss_state *shgss); +PacketProtocolLayer *ssh2_connection_new( + Ssh *ssh, ssh_sharing_state *connshare, bool is_simple, + Conf *conf, const char *peer_verstring, ConnectionLayer **cl_out); + +/* Can't put this in the userauth constructor without having a + * dependency loop at setup time (transport and userauth can't _both_ + * be constructed second and given a pointer to the other). */ +void ssh2_userauth_set_transport_layer(PacketProtocolLayer *userauth, + PacketProtocolLayer *transport); + +/* Convenience macro for protocol layers to send formatted strings to + * the Event Log. Assumes a function parameter called 'ppl' is in + * scope. */ +#define ppl_logevent(...) ( \ + logevent_and_free((ppl)->logctx, dupprintf(__VA_ARGS__))) + +/* Convenience macro for protocol layers to send formatted strings to + * the terminal. Also expects 'ppl' to be in scope. */ +#define ppl_printf(...) \ + ssh_ppl_user_output_string_and_free(ppl, dupprintf(__VA_ARGS__)) +void ssh_ppl_user_output_string_and_free(PacketProtocolLayer *ppl, char *text); + +/* Methods for userauth to communicate back to the transport layer */ +ptrlen ssh2_transport_get_session_id(PacketProtocolLayer *ssh2_transport_ptr); +void ssh2_transport_notify_auth_done(PacketProtocolLayer *ssh2_transport_ptr); + +/* Shared method between ssh2 layers (defined in transport2.c) to + * handle the common packets between login and connection: DISCONNECT, + * DEBUG and IGNORE. Those messages are handled by the ssh2transport + * layer if we have one, but in bare ssh2-connection mode they have to + * be handled by ssh2connection. */ +bool ssh2_common_filter_queue(PacketProtocolLayer *ppl); + +/* Methods for ssh1login to pass protocol flags to ssh1connection */ +void ssh1_connection_set_protoflags( + PacketProtocolLayer *ppl, int local, int remote); + +/* Shared get_specials method between the two ssh1 layers */ +bool ssh1_common_get_specials(PacketProtocolLayer *, add_special_fn_t, void *); + +/* Other shared functions between ssh1 layers */ +bool ssh1_common_filter_queue(PacketProtocolLayer *ppl); +void ssh1_compute_session_id( + unsigned char *session_id, const unsigned char *cookie, + RSAKey *hostkey, RSAKey *servkey); + +/* Method used by the SSH server */ +void ssh2_transport_provide_hostkeys(PacketProtocolLayer *ssh2_transport_ptr, + ssh_key *const *hostkeys, int nhostkeys); + +#endif /* PUTTY_SSHPPL_H */ diff --git a/ssh/scpserver.c b/ssh/scpserver.c new file mode 100644 index 00000000..97b20814 --- /dev/null +++ b/ssh/scpserver.c @@ -0,0 +1,1399 @@ +/* + * Server side of the old-school SCP protocol. + */ + +#include +#include +#include + +#include "putty.h" +#include "ssh.h" +#include "sshcr.h" +#include "channel.h" +#include "sftp.h" + +/* + * I think it's worth actually documenting my understanding of what + * this protocol _is_, since I don't know of any other documentation + * of it anywhere. + * + * Format of data stream + * --------------------- + * + * The sending side of an SCP connection - the client, if you're + * uploading files, or the server if you're downloading - sends a data + * stream consisting of a sequence of 'commands', or header records, + * or whatever you want to call them, interleaved with file data. + * + * Each command starts with a letter indicating what type it is, and + * ends with a \n. + * + * The 'C' command introduces an actual file. It is followed by an + * octal file-permissions mask, then a space, then a decimal file + * size, then a space, then the file name up to the termating newline. + * For example, "C0644 12345 filename.txt\n" would be a plausible C + * command. + * + * After the 'C' command, the sending side will transmit exactly as + * many bytes of file data as specified by the size field in the + * header line, followed by a single zero byte. + * + * The 'D' command introduces a subdirectory. Its format is identical + * to 'C', including the size field, but the size field is sent as + * zero. + * + * After the 'D' command, all subsequent C and D commands are taken to + * indicate files that should be placed inside that subdirectory, + * until a terminating 'E' command. + * + * The 'E' command indicates the end of a subdirectory. It has no + * arguments at all (its format is always just "E\n"). After the E + * command, the receiver should revert to placing further downloaded + * files in whatever directory it was placing them before the + * subdirectory opened by the just-closed D. + * + * D and E commands match like parentheses: if you send, say, + * + * C0644 123 foo.txt ( followed by data ) + * D0755 0 subdir + * C0644 123 bar.txt ( followed by data ) + * D0755 0 subsubdir + * C0644 123 baz.txt ( followed by data ) + * E + * C0644 123 quux.txt ( followed by data ) + * E + * C0644 123 wibble.txt ( followed by data ) + * + * then foo.txt, subdir and wibble.txt go in the top-level destination + * directory; bar.txt, subsubdir and quux.txt go in 'subdir'; and + * baz.txt goes in 'subdir/subsubdir'. + * + * The sender terminates the data stream with EOF when it has no more + * files to send. I believe it is not _required_ for all D to be + * closed by an E before this happens - you can elide a trailing + * sequence of E commands without provoking an error message from the + * receiver. + * + * Finally, the 'T' command is sent immediately before a C or D. It is + * followed by four space-separated decimal integers giving an mtime + * and atime to be applied to the file or directory created by the + * following C or D command. The first two integers give the mtime, + * encoded as seconds and microseconds (respectively) since the Unix + * epoch; the next two give the atime, encoded similarly. So + * "T1540373455 0 1540373457 0\n" is an example of a valid T command. + * + * Acknowledgments + * --------------- + * + * The sending side waits for an ack from the receiving side before + * sending each command; before beginning to send the file data + * following a C command; and before sending the final EOF. + * + * (In particular, the receiving side is expected to send an initial + * ack before _anything_ is sent.) + * + * Normally an ack consists of a single zero byte. It's also allowable + * to send a byte with value 1 or 2 followed by a \n-terminated error + * message (where 1 means a non-fatal error and 2 means a fatal one). + * I have to suppose that sending an error message from client to + * server is of limited use, but apparently it's allowed. + * + * Initiation + * ---------- + * + * The protocol is begun by the client sending a command string to the + * server via the SSH-2 "exec" request (or the analogous + * SSH1_CMSG_EXEC_CMD), which indicates that this is an scp session + * rather than any other thing; specifies the direction of transfer; + * says what file(s) are to be sent by the server, or where the server + * should put files that the client is about to send; and a couple of + * other options. + * + * The command string takes the following form: + * + * Start with prefix "scp ", indicating that this is an SCP command at + * all. Otherwise it's a request to run some completely different + * command in the SSH session. + * + * Next the command can contain zero or more of the following options, + * each followed by a space: + * + * "-v" turns on verbose server diagnostics. Of course a server is not + * required to actually produce any, but this is an invitation for it + * to send any it might have available. Diagnostics are free-form, and + * sent as SSH standard-error extended data, so that they are separate + * from the actual data stream as described above. + * + * (Servers can send standard-error output anyway if they like, and in + * case of an actual error, they probably will with or without -v.) + * + * "-r" indicates recursive file transfer, i.e. potentially including + * subdirectories. For a download, this indicates that the client is + * willing to receive subdirectories (a D/E command pair bracketing + * further files and subdirs); without it, the server should only send + * C commands for individual files, followed by EOF. + * + * This flag must also be specified for a recursive upload, because I + * believe at least one server will reject D/E pairs sent by the + * client if the command didn't have -r in it. (Probably a consequence + * of sharing code between download and upload.) + * + * "-p" means preserve file times. In a download, this requests the + * server to send a T command before each C or D. I don't know whether + * any server will insist on having seen this option from the client + * before accepting T commands in an upload, but it is probably + * sensible to send it anyway. + * + * "-d", in an upload, means that the destination pathname (see below) + * is expected to be a directory, and that uploaded files (and + * subdirs) should be put inside it. Without -d, the semantics are + * that _if_ the destination exists and is a directory, then files + * will be put in it, whereas if it is not, then just a single file + * (or subdir) upload is expected, which will be placed at that exact + * pathname. + * + * In a download, I observe that clients tend to send -d if they are + * requesting multiple files or a wildcard, but as far as I know, + * servers ignore it. + * + * After all those optional options, there is a single mandatory + * option indicating the direction of transfer, which is either "-f" + * or "-t". "-f" indicates a download; "-t" indicates an upload. + * + * After that mandatory option, there is a single space, followed by + * the name(s) of files to transfer. + * + * This file name field is transmitted with NO QUOTING, in spite of + * the fact that a server will typically interpret it as a shell + * command. You'd think this couldn't possibly work, in the face of + * almost any filename with an interesting character in it - and you'd + * be right. Or rather (you might argue), it works 'as designed', but + * it's designed in a weird way, in that it's the user's + * responsibility to apply quoting on the client command line to get + * the filename through the shell that will decode things on the + * server side. + * + * But one effect of this is that if you issue a download command + * including a wildcard, say "scp -f somedir/foo*.txt", then the shell + * will expand the wildcard, and actually run the server-side scp + * program with multiple arguments, say "somedir/foo.txt + * somedir/quux.txt", leading to the download sending multiple C + * commands. This clearly _is_ intended: it's how a command such as + * 'scp server:somedir/foo*.txt destdir' can work at all. + * + * (You would think, given that, that it might also be legal to send + * multiple space-separated filenames in order to trigger a download + * of exactly those files. Given how scp is invoked in practice on a + * typical server, this would surely actually work, but my observation + * is that scp clients don't in fact try this - if you run OpenSSH's + * scp by saying 'scp server:foo server:bar destdir' then it will make + * two separate connections to the server for the two files, rather + * than sending a single space-separated remote command. PSCP won't + * even do that, and will make you do it in two separate runs.) + * + * So, some examples: + * + * - "scp -f filename.txt" + * + * Server should send a single C command (plus data) for that file. + * Client ought to ignore the filename in the C command, in favour + * of saving the file under the name implied by the user's command + * line. + * + * - "scp -f file*.txt" + * + * Server sends zero or more C commands, then EOF. Client will have + * been given a target directory to put them all in, and will name + * each one according to the name in the C command. + * + * (You'd like the client to validate the filenames against the + * wildcard it sent, to ensure a malicious server didn't try to + * overwrite some path like ".bashrc" when you thought you were + * downloading only normal text files. But wildcard semantics are + * chosen by the server, so this is essentially hopeless to do + * rigorously.) + * + * - "scp -f -r somedir" + * + * Assuming somedir is actually a directory, server sends a D/E + * pair, in between which are the contents of the directory + * (perhaps including further nested D/E pairs). Client probably + * ignores the name field of the outermost D + * + * - "scp -f -r some*wild*card*" + * + * Server sends multiple C or D-stuff-E, one for each top-level + * thing matching the wildcard, whether it's a file or a directory. + * + * - "scp -t -d some_dir" + * + * Client sends stuff, and server deposits each file at + * some_dir/. + * + * - "scp -t some_path_name" + * + * Client sends one C command, and server deposits it at + * some_path_name itself, or in some_path_name/, depending whether some_path_name was already a + * directory or not. + */ + +/* + * Here's a useful debugging aid: run over a binary file containing + * the complete contents of the sender's data stream (e.g. extracted + * by contrib/logparse.pl -d), it removes the file contents, leaving + * only the list of commands, so you can see what the server sent. + * + * perl -pe 'read ARGV,$x,1+$1 if/^C\S+ (\d+)/' + */ + +/* ---------------------------------------------------------------------- + * Shared system for receiving replies from the SftpServer, and + * putting them into a set of ordinary variables rather than + * marshalling them into actual SFTP reply packets that we'd only have + * to unmarshal again. + */ + +typedef struct ScpReplyReceiver ScpReplyReceiver; +struct ScpReplyReceiver { + bool err; + unsigned code; + char *errmsg; + struct fxp_attrs attrs; + ptrlen name, handle, data; + + SftpReplyBuilder srb; +}; + +static void scp_reply_ok(SftpReplyBuilder *srb) +{ + ScpReplyReceiver *reply = container_of(srb, ScpReplyReceiver, srb); + reply->err = false; +} + +static void scp_reply_error( + SftpReplyBuilder *srb, unsigned code, const char *msg) +{ + ScpReplyReceiver *reply = container_of(srb, ScpReplyReceiver, srb); + reply->err = true; + reply->code = code; + sfree(reply->errmsg); + reply->errmsg = dupstr(msg); +} + +static void scp_reply_name_count(SftpReplyBuilder *srb, unsigned count) +{ + ScpReplyReceiver *reply = container_of(srb, ScpReplyReceiver, srb); + reply->err = false; +} + +static void scp_reply_full_name( + SftpReplyBuilder *srb, ptrlen name, + ptrlen longname, struct fxp_attrs attrs) +{ + ScpReplyReceiver *reply = container_of(srb, ScpReplyReceiver, srb); + char *p; + reply->err = false; + sfree((void *)reply->name.ptr); + reply->name.ptr = p = mkstr(name); + reply->name.len = name.len; + reply->attrs = attrs; +} + +static void scp_reply_simple_name(SftpReplyBuilder *srb, ptrlen name) +{ + ScpReplyReceiver *reply = container_of(srb, ScpReplyReceiver, srb); + reply->err = false; +} + +static void scp_reply_handle(SftpReplyBuilder *srb, ptrlen handle) +{ + ScpReplyReceiver *reply = container_of(srb, ScpReplyReceiver, srb); + char *p; + reply->err = false; + sfree((void *)reply->handle.ptr); + reply->handle.ptr = p = mkstr(handle); + reply->handle.len = handle.len; +} + +static void scp_reply_data(SftpReplyBuilder *srb, ptrlen data) +{ + ScpReplyReceiver *reply = container_of(srb, ScpReplyReceiver, srb); + char *p; + reply->err = false; + sfree((void *)reply->data.ptr); + reply->data.ptr = p = mkstr(data); + reply->data.len = data.len; +} + +static void scp_reply_attrs( + SftpReplyBuilder *srb, struct fxp_attrs attrs) +{ + ScpReplyReceiver *reply = container_of(srb, ScpReplyReceiver, srb); + reply->err = false; + reply->attrs = attrs; +} + +static const SftpReplyBuilderVtable ScpReplyReceiver_vt = { + .reply_ok = scp_reply_ok, + .reply_error = scp_reply_error, + .reply_simple_name = scp_reply_simple_name, + .reply_name_count = scp_reply_name_count, + .reply_full_name = scp_reply_full_name, + .reply_handle = scp_reply_handle, + .reply_data = scp_reply_data, + .reply_attrs = scp_reply_attrs, +}; + +static void scp_reply_setup(ScpReplyReceiver *reply) +{ + memset(reply, 0, sizeof(*reply)); + reply->srb.vt = &ScpReplyReceiver_vt; +} + +static void scp_reply_cleanup(ScpReplyReceiver *reply) +{ + sfree(reply->errmsg); + sfree((void *)reply->name.ptr); + sfree((void *)reply->handle.ptr); + sfree((void *)reply->data.ptr); +} + +/* ---------------------------------------------------------------------- + * Source end of the SCP protocol. + */ + +#define SCP_MAX_BACKLOG 65536 + +typedef struct ScpSource ScpSource; +typedef struct ScpSourceStackEntry ScpSourceStackEntry; + +struct ScpSource { + SftpServer *sf; + + int acks; + bool expect_newline, eof, throttled, finished; + + SshChannel *sc; + ScpSourceStackEntry *head; + bool recursive; + bool send_file_times; + + strbuf *pending_commands[3]; + int n_pending_commands; + + uint64_t file_offset, file_size; + + ScpReplyReceiver reply; + + ScpServer scpserver; +}; + +typedef enum ScpSourceNodeType ScpSourceNodeType; +enum ScpSourceNodeType { SCP_ROOTPATH, SCP_NAME, SCP_READDIR, SCP_READFILE }; + +struct ScpSourceStackEntry { + ScpSourceStackEntry *next; + ScpSourceNodeType type; + ptrlen pathname, handle; + const char *wildcard; + struct fxp_attrs attrs; +}; + +static void scp_source_push(ScpSource *scp, ScpSourceNodeType type, + ptrlen pathname, ptrlen handle, + const struct fxp_attrs *attrs, const char *wc) +{ + size_t wc_len = wc ? strlen(wc)+1 : 0; + ScpSourceStackEntry *node = snew_plus( + ScpSourceStackEntry, pathname.len + handle.len + wc_len); + char *namebuf = snew_plus_get_aux(node); + memcpy(namebuf, pathname.ptr, pathname.len); + node->pathname = make_ptrlen(namebuf, pathname.len); + memcpy(namebuf + pathname.len, handle.ptr, handle.len); + node->handle = make_ptrlen(namebuf + pathname.len, handle.len); + if (wc) { + strcpy(namebuf + pathname.len + handle.len, wc); + node->wildcard = namebuf + pathname.len + handle.len; + } else { + node->wildcard = NULL; + } + node->attrs = attrs ? *attrs : no_attrs; + node->type = type; + node->next = scp->head; + scp->head = node; +} + +static char *scp_source_err_base(ScpSource *scp, const char *fmt, va_list ap) +{ + char *msg = dupvprintf(fmt, ap); + sshfwd_write_ext(scp->sc, true, msg, strlen(msg)); + sshfwd_write_ext(scp->sc, true, "\012", 1); + return msg; +} +static PRINTF_LIKE(2, 3) void scp_source_err( + ScpSource *scp, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + sfree(scp_source_err_base(scp, fmt, ap)); + va_end(ap); +} +static PRINTF_LIKE(2, 3) void scp_source_abort( + ScpSource *scp, const char *fmt, ...) +{ + va_list ap; + char *msg; + + va_start(ap, fmt); + msg = scp_source_err_base(scp, fmt, ap); + va_end(ap); + + sshfwd_send_exit_status(scp->sc, 1); + sshfwd_write_eof(scp->sc); + sshfwd_initiate_close(scp->sc, msg); + + scp->finished = true; +} + +static void scp_source_push_name( + ScpSource *scp, ptrlen pathname, struct fxp_attrs attrs, const char *wc) +{ + if (!(attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS)) { + scp_source_err(scp, "unable to read file permissions for %.*s", + PTRLEN_PRINTF(pathname)); + return; + } + if (attrs.permissions & PERMS_DIRECTORY) { + if (!scp->recursive && !wc) { + scp_source_err(scp, "%.*s: is a directory", + PTRLEN_PRINTF(pathname)); + return; + } + } else { + if (!(attrs.flags & SSH_FILEXFER_ATTR_SIZE)) { + scp_source_err(scp, "unable to read file size for %.*s", + PTRLEN_PRINTF(pathname)); + return; + } + } + + scp_source_push(scp, SCP_NAME, pathname, PTRLEN_LITERAL(""), &attrs, wc); +} + +static void scp_source_free(ScpServer *s); +static size_t scp_source_send(ScpServer *s, const void *data, size_t length); +static void scp_source_eof(ScpServer *s); +static void scp_source_throttle(ScpServer *s, bool throttled); + +static const ScpServerVtable ScpSource_ScpServer_vt = { + .free = scp_source_free, + .send = scp_source_send, + .throttle = scp_source_throttle, + .eof = scp_source_eof, +}; + +static ScpSource *scp_source_new( + SshChannel *sc, const SftpServerVtable *sftpserver_vt, ptrlen pathname) +{ + ScpSource *scp = snew(ScpSource); + memset(scp, 0, sizeof(*scp)); + + scp->scpserver.vt = &ScpSource_ScpServer_vt; + scp_reply_setup(&scp->reply); + scp->sc = sc; + scp->sf = sftpsrv_new(sftpserver_vt); + scp->n_pending_commands = 0; + + scp_source_push(scp, SCP_ROOTPATH, pathname, PTRLEN_LITERAL(""), + NULL, NULL); + + return scp; +} + +static void scp_source_free(ScpServer *s) +{ + ScpSource *scp = container_of(s, ScpSource, scpserver); + scp_reply_cleanup(&scp->reply); + while (scp->n_pending_commands > 0) + strbuf_free(scp->pending_commands[--scp->n_pending_commands]); + while (scp->head) { + ScpSourceStackEntry *node = scp->head; + scp->head = node->next; + sfree(node); + } + + delete_callbacks_for_context(scp); + + sfree(scp); +} + +static void scp_source_send_E(ScpSource *scp) +{ + strbuf *cmd; + + assert(scp->n_pending_commands == 0); + + scp->pending_commands[scp->n_pending_commands++] = cmd = strbuf_new(); + strbuf_catf(cmd, "E\012"); +} + +static void scp_source_send_CD( + ScpSource *scp, char cmdchar, + struct fxp_attrs attrs, uint64_t size, ptrlen name) +{ + strbuf *cmd; + + assert(scp->n_pending_commands == 0); + + if (scp->send_file_times && (attrs.flags & SSH_FILEXFER_ATTR_ACMODTIME)) { + scp->pending_commands[scp->n_pending_commands++] = cmd = strbuf_new(); + /* Our SFTP-based filesystem API doesn't support microsecond times */ + strbuf_catf(cmd, "T%lu 0 %lu 0\012", attrs.mtime, attrs.atime); + } + + const char *slash; + while ((slash = memchr(name.ptr, '/', name.len)) != NULL) + name = make_ptrlen( + slash+1, name.len - (slash+1 - (const char *)name.ptr)); + + scp->pending_commands[scp->n_pending_commands++] = cmd = strbuf_new(); + strbuf_catf(cmd, "%c%04o %"PRIu64" %.*s\012", cmdchar, + (unsigned)(attrs.permissions & 07777), + size, PTRLEN_PRINTF(name)); + + if (cmdchar == 'C') { + /* We'll also wait for an ack before sending the file data, + * which we record by saving a zero-length 'command' to be + * sent after the C. */ + scp->pending_commands[scp->n_pending_commands++] = cmd = strbuf_new(); + } +} + +static void scp_source_process_stack(ScpSource *scp); +static void scp_source_process_stack_cb(void *vscp) +{ + ScpSource *scp = (ScpSource *)vscp; + if (scp->finished) + return; /* this callback is out of date */ + scp_source_process_stack(scp); +} +static void scp_requeue(ScpSource *scp) +{ + queue_toplevel_callback(scp_source_process_stack_cb, scp); +} + +static void scp_source_process_stack(ScpSource *scp) +{ + if (scp->throttled) + return; + + while (scp->n_pending_commands > 0) { + /* Expect an ack, and consume it */ + if (scp->eof) { + scp_source_abort( + scp, "scp: received client EOF, abandoning transfer"); + return; + } + if (scp->acks == 0) + return; + scp->acks--; + + /* + * Now send the actual command (unless it was the phony + * zero-length one that indicates our need for an ack before + * beginning to send file data). + */ + + if (scp->pending_commands[0]->len) + sshfwd_write(scp->sc, scp->pending_commands[0]->s, + scp->pending_commands[0]->len); + + strbuf_free(scp->pending_commands[0]); + scp->n_pending_commands--; + if (scp->n_pending_commands > 0) { + /* + * We still have at least one pending command to send, so + * move up the queue. + * + * (We do that with a bodgy memmove, because there are at + * most a bounded number of commands ever pending at once, + * so no need to worry about quadratic time.) + */ + memmove(scp->pending_commands, scp->pending_commands+1, + scp->n_pending_commands * sizeof(*scp->pending_commands)); + } + } + + /* + * Mostly, we start by waiting for an ack byte from the receiver. + */ + if (scp->head && scp->head->type == SCP_READFILE && scp->file_offset) { + /* + * Exception: if we're already in the middle of transferring a + * file, we'll be called back here because the channel backlog + * has cleared; we don't need to wait for an ack. + */ + } else if (scp->head && scp->head->type == SCP_ROOTPATH) { + /* + * Another exception: the initial action node that makes us + * stat the root path. We'll translate it into an SCP_NAME, + * and _that_ will require an ack. + */ + ScpSourceStackEntry *node = scp->head; + scp->head = node->next; + + /* + * Start by checking if there's a wildcard involved in the + * root path. + */ + char *rootpath_str = mkstr(node->pathname); + char *rootpath_unesc = snewn(1+node->pathname.len, char); + ptrlen pathname; + const char *wildcard; + + if (wc_unescape(rootpath_unesc, rootpath_str)) { + /* + * We successfully removed instances of the escape + * character used in our wildcard syntax, without + * encountering any actual wildcard chars - i.e. this is + * not a wildcard, just a single file. The simple case. + */ + pathname = ptrlen_from_asciz(rootpath_str); + wildcard = NULL; + } else { + /* + * This is a wildcard. Separate it into a directory name + * (which we enforce mustn't contain wc characters, for + * simplicity) and a wildcard to match leaf names. + */ + char *last_slash = strrchr(rootpath_str, '/'); + + if (last_slash) { + wildcard = last_slash + 1; + *last_slash = '\0'; + if (!wc_unescape(rootpath_unesc, rootpath_str)) { + scp_source_abort(scp, "scp: wildcards in path components " + "before the file name not supported"); + sfree(rootpath_str); + sfree(rootpath_unesc); + return; + } + + pathname = ptrlen_from_asciz(rootpath_unesc); + } else { + pathname = PTRLEN_LITERAL("."); + wildcard = rootpath_str; + } + } + + /* + * Now we know what directory we're scanning, and what + * wildcard (if any) we're using to match the filenames we get + * back. + */ + sftpsrv_stat(scp->sf, &scp->reply.srb, pathname, true); + if (scp->reply.err) { + scp_source_abort( + scp, "%.*s: unable to access: %s", + PTRLEN_PRINTF(pathname), scp->reply.errmsg); + sfree(rootpath_str); + sfree(rootpath_unesc); + sfree(node); + return; + } + + scp_source_push_name(scp, pathname, scp->reply.attrs, wildcard); + + sfree(rootpath_str); + sfree(rootpath_unesc); + sfree(node); + scp_requeue(scp); + return; + } else { + } + + if (scp->head && scp->head->type == SCP_READFILE) { + /* + * Transfer file data if our backlog hasn't filled up. + */ + int backlog; + uint64_t limit = scp->file_size - scp->file_offset; + if (limit > 4096) + limit = 4096; + if (limit > 0) { + sftpsrv_read(scp->sf, &scp->reply.srb, scp->head->handle, + scp->file_offset, limit); + if (scp->reply.err) { + scp_source_abort( + scp, "%.*s: unable to read: %s", + PTRLEN_PRINTF(scp->head->pathname), scp->reply.errmsg); + return; + } + + backlog = sshfwd_write( + scp->sc, scp->reply.data.ptr, scp->reply.data.len); + scp->file_offset += scp->reply.data.len; + + if (backlog < SCP_MAX_BACKLOG) + scp_requeue(scp); + return; + } + + /* + * If we're done, send a terminating zero byte, close our file + * handle, and pop the stack. + */ + sshfwd_write(scp->sc, "\0", 1); + sftpsrv_close(scp->sf, &scp->reply.srb, scp->head->handle); + ScpSourceStackEntry *node = scp->head; + scp->head = node->next; + sfree(node); + scp_requeue(scp); + return; + } + + /* + * If our queue is actually empty, send outgoing EOF. + */ + if (!scp->head) { + sshfwd_send_exit_status(scp->sc, 0); + sshfwd_write_eof(scp->sc); + sshfwd_initiate_close(scp->sc, NULL); + scp->finished = true; + return; + } + + /* + * Otherwise, handle a command. + */ + ScpSourceStackEntry *node = scp->head; + scp->head = node->next; + + if (node->type == SCP_READDIR) { + sftpsrv_readdir(scp->sf, &scp->reply.srb, node->handle, 1, true); + if (scp->reply.err) { + if (scp->reply.code != SSH_FX_EOF) + scp_source_err(scp, "%.*s: unable to list directory: %s", + PTRLEN_PRINTF(node->pathname), + scp->reply.errmsg); + sftpsrv_close(scp->sf, &scp->reply.srb, node->handle); + + if (!node->wildcard) { + /* + * Send 'pop stack' or 'end of directory' command, + * unless this was the topmost READDIR in a + * wildcard-based retrieval (in which case we didn't + * send a D command to start, so an E now would have + * no stack entry to pop). + */ + scp_source_send_E(scp); + } + } else if (ptrlen_eq_string(scp->reply.name, ".") || + ptrlen_eq_string(scp->reply.name, "..") || + (node->wildcard && + !wc_match_pl(node->wildcard, scp->reply.name))) { + /* Skip special directory names . and .., and anything + * that doesn't match our wildcard (if we have one). */ + scp->head = node; /* put back the unfinished READDIR */ + node = NULL; /* and prevent it being freed */ + } else { + ptrlen subpath; + subpath.len = node->pathname.len + 1 + scp->reply.name.len; + char *subpath_space = snewn(subpath.len, char); + subpath.ptr = subpath_space; + memcpy(subpath_space, node->pathname.ptr, node->pathname.len); + subpath_space[node->pathname.len] = '/'; + memcpy(subpath_space + node->pathname.len + 1, + scp->reply.name.ptr, scp->reply.name.len); + + scp->head = node; /* put back the unfinished READDIR */ + node = NULL; /* and prevent it being freed */ + scp_source_push_name(scp, subpath, scp->reply.attrs, NULL); + + sfree(subpath_space); + } + } else if (node->attrs.permissions & PERMS_DIRECTORY) { + assert(scp->recursive || node->wildcard); + + if (!node->wildcard) + scp_source_send_CD(scp, 'D', node->attrs, 0, node->pathname); + sftpsrv_opendir(scp->sf, &scp->reply.srb, node->pathname); + if (scp->reply.err) { + scp_source_err( + scp, "%.*s: unable to access: %s", + PTRLEN_PRINTF(node->pathname), scp->reply.errmsg); + + if (!node->wildcard) { + /* Send 'pop stack' or 'end of directory' command. */ + scp_source_send_E(scp); + } + } else { + scp_source_push( + scp, SCP_READDIR, node->pathname, + scp->reply.handle, NULL, node->wildcard); + } + } else { + sftpsrv_open(scp->sf, &scp->reply.srb, + node->pathname, SSH_FXF_READ, no_attrs); + if (scp->reply.err) { + scp_source_err( + scp, "%.*s: unable to open: %s", + PTRLEN_PRINTF(node->pathname), scp->reply.errmsg); + scp_requeue(scp); + return; + } + sftpsrv_fstat(scp->sf, &scp->reply.srb, scp->reply.handle); + if (scp->reply.err) { + scp_source_err( + scp, "%.*s: unable to stat: %s", + PTRLEN_PRINTF(node->pathname), scp->reply.errmsg); + sftpsrv_close(scp->sf, &scp->reply.srb, scp->reply.handle); + scp_requeue(scp); + return; + } + scp->file_offset = 0; + scp->file_size = scp->reply.attrs.size; + scp_source_send_CD(scp, 'C', node->attrs, + scp->file_size, node->pathname); + scp_source_push( + scp, SCP_READFILE, node->pathname, scp->reply.handle, NULL, NULL); + } + sfree(node); + scp_requeue(scp); +} + +static size_t scp_source_send(ScpServer *s, const void *vdata, size_t length) +{ + ScpSource *scp = container_of(s, ScpSource, scpserver); + const char *data = (const char *)vdata; + size_t i; + + if (scp->finished) + return 0; + + for (i = 0; i < length; i++) { + if (scp->expect_newline) { + if (data[i] == '\012') { + /* End of an error message following a 1 byte */ + scp->expect_newline = false; + scp->acks++; + } + } else { + switch (data[i]) { + case 0: /* ordinary ack */ + scp->acks++; + break; + case 1: /* non-fatal error; consume it */ + scp->expect_newline = true; + break; + case 2: + scp_source_abort( + scp, "terminating on fatal error from client"); + return 0; + default: + scp_source_abort( + scp, "unrecognised response code from client"); + return 0; + } + } + } + + scp_source_process_stack(scp); + + return 0; +} + +static void scp_source_throttle(ScpServer *s, bool throttled) +{ + ScpSource *scp = container_of(s, ScpSource, scpserver); + + if (scp->finished) + return; + + scp->throttled = throttled; + if (!throttled) + scp_source_process_stack(scp); +} + +static void scp_source_eof(ScpServer *s) +{ + ScpSource *scp = container_of(s, ScpSource, scpserver); + + if (scp->finished) + return; + + scp->eof = true; + scp_source_process_stack(scp); +} + +/* ---------------------------------------------------------------------- + * Sink end of the SCP protocol. + */ + +typedef struct ScpSink ScpSink; +typedef struct ScpSinkStackEntry ScpSinkStackEntry; + +struct ScpSink { + SftpServer *sf; + + SshChannel *sc; + ScpSinkStackEntry *head; + + uint64_t file_offset, file_size; + unsigned long atime, mtime; + bool got_file_times; + + bufchain data; + bool input_eof; + strbuf *command; + char command_chr; + + strbuf *filename_sb; + ptrlen filename; + struct fxp_attrs attrs; + + char *errmsg; + + int crState; + + ScpReplyReceiver reply; + + ScpServer scpserver; +}; + +struct ScpSinkStackEntry { + ScpSinkStackEntry *next; + ptrlen destpath; + + /* + * If isdir is true, then destpath identifies a directory that the + * files we receive should be created inside. If it's false, then + * it identifies the exact pathname the next file we receive + * should be created _as_ - regardless of the filename in the 'C' + * command. + */ + bool isdir; +}; + +static void scp_sink_push(ScpSink *scp, ptrlen pathname, bool isdir) +{ + ScpSinkStackEntry *node = snew_plus(ScpSinkStackEntry, pathname.len); + char *p = snew_plus_get_aux(node); + + node->destpath.ptr = p; + node->destpath.len = pathname.len; + memcpy(p, pathname.ptr, pathname.len); + node->isdir = isdir; + + node->next = scp->head; + scp->head = node; +} + +static void scp_sink_pop(ScpSink *scp) +{ + ScpSinkStackEntry *node = scp->head; + scp->head = node->next; + sfree(node); +} + +static void scp_sink_free(ScpServer *s); +static size_t scp_sink_send(ScpServer *s, const void *data, size_t length); +static void scp_sink_eof(ScpServer *s); +static void scp_sink_throttle(ScpServer *s, bool throttled) {} + +static const ScpServerVtable ScpSink_ScpServer_vt = { + .free = scp_sink_free, + .send = scp_sink_send, + .throttle = scp_sink_throttle, + .eof = scp_sink_eof, +}; + +static void scp_sink_coroutine(ScpSink *scp); +static void scp_sink_start_callback(void *vscp) +{ + scp_sink_coroutine((ScpSink *)vscp); +} + +static ScpSink *scp_sink_new( + SshChannel *sc, const SftpServerVtable *sftpserver_vt, ptrlen pathname, + bool pathname_is_definitely_dir) +{ + ScpSink *scp = snew(ScpSink); + memset(scp, 0, sizeof(*scp)); + + scp->scpserver.vt = &ScpSink_ScpServer_vt; + scp_reply_setup(&scp->reply); + scp->sc = sc; + scp->sf = sftpsrv_new(sftpserver_vt); + bufchain_init(&scp->data); + scp->command = strbuf_new(); + scp->filename_sb = strbuf_new(); + + if (!pathname_is_definitely_dir) { + /* + * If our root pathname is not already expected to be a + * directory because of the -d option in the command line, + * test it ourself to see whether it is or not. + */ + sftpsrv_stat(scp->sf, &scp->reply.srb, pathname, true); + if (!scp->reply.err && + (scp->reply.attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) && + (scp->reply.attrs.permissions & PERMS_DIRECTORY)) + pathname_is_definitely_dir = true; + } + scp_sink_push(scp, pathname, pathname_is_definitely_dir); + + queue_toplevel_callback(scp_sink_start_callback, scp); + + return scp; +} + +static void scp_sink_free(ScpServer *s) +{ + ScpSink *scp = container_of(s, ScpSink, scpserver); + + scp_reply_cleanup(&scp->reply); + bufchain_clear(&scp->data); + strbuf_free(scp->command); + strbuf_free(scp->filename_sb); + while (scp->head) + scp_sink_pop(scp); + sfree(scp->errmsg); + + delete_callbacks_for_context(scp); + + sfree(scp); +} + +static void scp_sink_coroutine(ScpSink *scp) +{ + crBegin(scp->crState); + + while (1) { + /* + * Send an ack, and read a command. + */ + sshfwd_write(scp->sc, "\0", 1); + strbuf_clear(scp->command); + while (1) { + crMaybeWaitUntilV(scp->input_eof || bufchain_size(&scp->data) > 0); + if (scp->input_eof) + goto done; + + ptrlen data = bufchain_prefix(&scp->data); + const char *cdata = data.ptr; + const char *newline = memchr(cdata, '\012', data.len); + if (newline) + data.len = (int)(newline+1 - cdata); + put_data(scp->command, cdata, data.len); + bufchain_consume(&scp->data, data.len); + + if (newline) + break; + } + + /* + * Parse the command. + */ + strbuf_chomp(scp->command, '\n'); + scp->command_chr = scp->command->len > 0 ? scp->command->s[0] : '\0'; + if (scp->command_chr == 'T') { + unsigned long dummy1, dummy2; + if (sscanf(scp->command->s, "T%lu %lu %lu %lu", + &scp->mtime, &dummy1, &scp->atime, &dummy2) != 4) + goto parse_error; + scp->got_file_times = true; + } else if (scp->command_chr == 'C' || scp->command_chr == 'D') { + /* + * Common handling of the start of this case, because the + * messages are parsed similarly. We diverge later. + */ + const char *q, *p = scp->command->s + 1; /* skip the 'C' */ + + scp->attrs.flags = SSH_FILEXFER_ATTR_PERMISSIONS; + scp->attrs.permissions = 0; + while (*p >= '0' && *p <= '7') { + scp->attrs.permissions = + scp->attrs.permissions * 8 + (*p - '0'); + p++; + } + if (*p != ' ') + goto parse_error; + p++; + + q = p; + while (*p >= '0' && *p <= '9') + p++; + if (*p != ' ') + goto parse_error; + p++; + scp->file_size = strtoull(q, NULL, 10); + + ptrlen leafname = make_ptrlen( + p, scp->command->len - (p - scp->command->s)); + strbuf_clear(scp->filename_sb); + put_datapl(scp->filename_sb, scp->head->destpath); + if (scp->head->isdir) { + if (scp->filename_sb->len > 0 && + scp->filename_sb->s[scp->filename_sb->len-1] + != '/') + put_byte(scp->filename_sb, '/'); + put_datapl(scp->filename_sb, leafname); + } + scp->filename = ptrlen_from_strbuf(scp->filename_sb); + + if (scp->got_file_times) { + scp->attrs.mtime = scp->mtime; + scp->attrs.atime = scp->atime; + scp->attrs.flags |= SSH_FILEXFER_ATTR_ACMODTIME; + } + scp->got_file_times = false; + + if (scp->command_chr == 'D') { + sftpsrv_mkdir(scp->sf, &scp->reply.srb, + scp->filename, scp->attrs); + + if (scp->reply.err) { + scp->errmsg = dupprintf( + "'%.*s': unable to create directory: %s", + PTRLEN_PRINTF(scp->filename), scp->reply.errmsg); + goto done; + } + + scp_sink_push(scp, scp->filename, true); + } else { + sftpsrv_open(scp->sf, &scp->reply.srb, scp->filename, + SSH_FXF_WRITE | SSH_FXF_CREAT | SSH_FXF_TRUNC, + scp->attrs); + if (scp->reply.err) { + scp->errmsg = dupprintf( + "'%.*s': unable to open file: %s", + PTRLEN_PRINTF(scp->filename), scp->reply.errmsg); + goto done; + } + + /* + * Now send an ack, and read the file data. + */ + sshfwd_write(scp->sc, "\0", 1); + scp->file_offset = 0; + while (scp->file_offset < scp->file_size) { + ptrlen data; + uint64_t this_len, remaining; + + crMaybeWaitUntilV( + scp->input_eof || bufchain_size(&scp->data) > 0); + if (scp->input_eof) { + sftpsrv_close(scp->sf, &scp->reply.srb, + scp->reply.handle); + goto done; + } + + data = bufchain_prefix(&scp->data); + this_len = data.len; + remaining = scp->file_size - scp->file_offset; + if (this_len > remaining) + this_len = remaining; + sftpsrv_write(scp->sf, &scp->reply.srb, + scp->reply.handle, scp->file_offset, + make_ptrlen(data.ptr, this_len)); + if (scp->reply.err) { + scp->errmsg = dupprintf( + "'%.*s': unable to write to file: %s", + PTRLEN_PRINTF(scp->filename), scp->reply.errmsg); + goto done; + } + bufchain_consume(&scp->data, this_len); + scp->file_offset += this_len; + } + + /* + * Wait for the trailing NUL byte. + */ + crMaybeWaitUntilV( + scp->input_eof || bufchain_size(&scp->data) > 0); + if (scp->input_eof) { + sftpsrv_close(scp->sf, &scp->reply.srb, + scp->reply.handle); + goto done; + } + bufchain_consume(&scp->data, 1); + } + } else if (scp->command_chr == 'E') { + if (!scp->head) { + scp->errmsg = dupstr("received E command without matching D"); + goto done; + } + scp_sink_pop(scp); + scp->got_file_times = false; + } else { + ptrlen cmd_pl; + + /* + * Also come here if any of the above cases run into + * parsing difficulties. + */ + parse_error: + cmd_pl = ptrlen_from_strbuf(scp->command); + scp->errmsg = dupprintf("unrecognised scp command '%.*s'", + PTRLEN_PRINTF(cmd_pl)); + goto done; + } + } + + done: + if (scp->errmsg) { + sshfwd_write_ext(scp->sc, true, scp->errmsg, strlen(scp->errmsg)); + sshfwd_write_ext(scp->sc, true, "\012", 1); + sshfwd_send_exit_status(scp->sc, 1); + } else { + sshfwd_send_exit_status(scp->sc, 0); + } + sshfwd_write_eof(scp->sc); + sshfwd_initiate_close(scp->sc, scp->errmsg); + while (1) crReturnV; + + crFinishV; +} + +static size_t scp_sink_send(ScpServer *s, const void *data, size_t length) +{ + ScpSink *scp = container_of(s, ScpSink, scpserver); + + if (!scp->input_eof) { + bufchain_add(&scp->data, data, length); + scp_sink_coroutine(scp); + } + return 0; +} + +static void scp_sink_eof(ScpServer *s) +{ + ScpSink *scp = container_of(s, ScpSink, scpserver); + + scp->input_eof = true; + scp_sink_coroutine(scp); +} + +/* ---------------------------------------------------------------------- + * Top-level error handler, instantiated in the case where the user + * sent a command starting with "scp " that we couldn't make sense of. + */ + +typedef struct ScpError ScpError; + +struct ScpError { + SshChannel *sc; + char *message; + ScpServer scpserver; +}; + +static void scp_error_free(ScpServer *s); + +static size_t scp_error_send(ScpServer *s, const void *data, size_t length) +{ return 0; } +static void scp_error_eof(ScpServer *s) {} +static void scp_error_throttle(ScpServer *s, bool throttled) {} + +static const ScpServerVtable ScpError_ScpServer_vt = { + .free = scp_error_free, + .send = scp_error_send, + .throttle = scp_error_throttle, + .eof = scp_error_eof, +}; + +static void scp_error_send_message_cb(void *vscp) +{ + ScpError *scp = (ScpError *)vscp; + sshfwd_write_ext(scp->sc, true, scp->message, strlen(scp->message)); + sshfwd_write_ext(scp->sc, true, "\n", 1); + sshfwd_send_exit_status(scp->sc, 1); + sshfwd_write_eof(scp->sc); + sshfwd_initiate_close(scp->sc, scp->message); +} + +static PRINTF_LIKE(2, 3) ScpError *scp_error_new( + SshChannel *sc, const char *fmt, ...) +{ + va_list ap; + ScpError *scp = snew(ScpError); + + memset(scp, 0, sizeof(*scp)); + + scp->scpserver.vt = &ScpError_ScpServer_vt; + scp->sc = sc; + + va_start(ap, fmt); + scp->message = dupvprintf(fmt, ap); + va_end(ap); + + queue_toplevel_callback(scp_error_send_message_cb, scp); + + return scp; +} + +static void scp_error_free(ScpServer *s) +{ + ScpError *scp = container_of(s, ScpError, scpserver); + + sfree(scp->message); + + delete_callbacks_for_context(scp); + + sfree(scp); +} + +/* ---------------------------------------------------------------------- + * Top-level entry point, which parses a command sent from the SSH + * client, and if it recognises it as an scp command, instantiates an + * appropriate ScpServer implementation and returns it. + */ + +ScpServer *scp_recognise_exec( + SshChannel *sc, const SftpServerVtable *sftpserver_vt, ptrlen command) +{ + bool recursive = false, preserve = false; + bool targetshouldbedirectory = false; + ptrlen command_orig = command; + + if (!ptrlen_startswith(command, PTRLEN_LITERAL("scp "), &command)) + return NULL; + + while (1) { + if (ptrlen_startswith(command, PTRLEN_LITERAL("-v "), &command)) { + /* Enable verbose mode in the server, which we ignore */ + continue; + } + if (ptrlen_startswith(command, PTRLEN_LITERAL("-r "), &command)) { + recursive = true; + continue; + } + if (ptrlen_startswith(command, PTRLEN_LITERAL("-p "), &command)) { + preserve = true; + continue; + } + if (ptrlen_startswith(command, PTRLEN_LITERAL("-d "), &command)) { + targetshouldbedirectory = true; + continue; + } + break; + } + + if (ptrlen_startswith(command, PTRLEN_LITERAL("-t "), &command)) { + ScpSink *scp = scp_sink_new(sc, sftpserver_vt, command, + targetshouldbedirectory); + return &scp->scpserver; + } else if (ptrlen_startswith(command, PTRLEN_LITERAL("-f "), &command)) { + ScpSource *scp = scp_source_new(sc, sftpserver_vt, command); + scp->recursive = recursive; + scp->send_file_times = preserve; + return &scp->scpserver; + } else { + ScpError *scp = scp_error_new( + sc, "Unable to parse scp command: '%.*s'", + PTRLEN_PRINTF(command_orig)); + return &scp->scpserver; + } +} diff --git a/ssh/server.c b/ssh/server.c new file mode 100644 index 00000000..e3fc86fe --- /dev/null +++ b/ssh/server.c @@ -0,0 +1,591 @@ +/* + * Top-level code for SSH server implementation. + */ + +#include +#include + +#include "putty.h" +#include "ssh.h" +#include "bpp.h" +#include "ppl.h" +#include "channel.h" +#include "server.h" +#ifndef NO_GSSAPI +#include "gssc.h" +#include "gss.h" +#endif + +struct Ssh { int dummy; }; + +typedef struct server server; +struct server { + bufchain in_raw, out_raw; + IdempotentCallback ic_out_raw; + bool pending_close; + + bufchain dummy_user_input; /* we never put anything on this */ + + PacketLogSettings pls; + LogContext *logctx; + struct DataTransferStats stats; + + int remote_bugs; + + Socket *socket; + Plug plug; + int conn_throttle_count; + bool frozen; + + Conf *conf; + const SshServerConfig *ssc; + ssh_key *const *hostkeys; + int nhostkeys; + RSAKey *hostkey1; + AuthPolicy *authpolicy; + LogPolicy *logpolicy; + const SftpServerVtable *sftpserver_vt; + + agentfwd *stunt_agentfwd; + + Seat seat; + Ssh ssh; + struct ssh_version_receiver version_receiver; + + BinaryPacketProtocol *bpp; + PacketProtocolLayer *base_layer; + ConnectionLayer *cl; + +#ifndef NO_GSSAPI + struct ssh_connection_shared_gss_state gss_state; +#endif +}; + +static void ssh_server_free_callback(void *vsrv); +static void server_got_ssh_version(struct ssh_version_receiver *rcv, + int major_version); +static void server_connect_bpp(server *srv); +static void server_bpp_output_raw_data_callback(void *vctx); + +void share_activate(ssh_sharing_state *sharestate, + const char *server_verstring) {} +void ssh_connshare_provide_connlayer(ssh_sharing_state *sharestate, + ConnectionLayer *cl) {} +int share_ndownstreams(ssh_sharing_state *sharestate) { return 0; } +void share_got_pkt_from_server(ssh_sharing_connstate *cs, int type, + const void *vpkt, int pktlen) {} +void share_setup_x11_channel(ssh_sharing_connstate *cs, share_channel *chan, + unsigned upstream_id, unsigned server_id, + unsigned server_currwin, unsigned server_maxpkt, + unsigned client_adjusted_window, + const char *peer_addr, int peer_port, int endian, + int protomajor, int protominor, + const void *initial_data, int initial_len) {} +Channel *agentf_new(SshChannel *c) { return NULL; } +bool agent_exists(void) { return false; } +void ssh_got_exitcode(Ssh *ssh, int exitcode) {} +void ssh_check_frozen(Ssh *ssh) {} + +mainchan *mainchan_new( + PacketProtocolLayer *ppl, ConnectionLayer *cl, Conf *conf, + int term_width, int term_height, bool is_simple, SshChannel **sc_out) +{ return NULL; } +void mainchan_get_specials( + mainchan *mc, add_special_fn_t add_special, void *ctx) {} +void mainchan_special_cmd(mainchan *mc, SessionSpecialCode code, int arg) {} +void mainchan_terminal_size(mainchan *mc, int width, int height) {} + +/* Seat functions to ensure we don't get choosy about crypto - as the + * server, it's not up to us to give user warnings */ +static int server_confirm_weak_crypto_primitive( + Seat *seat, const char *algtype, const char *algname, + void (*callback)(void *ctx, int result), void *ctx) { return 1; } +static int server_confirm_weak_cached_hostkey( + Seat *seat, const char *algname, const char *betteralgs, + void (*callback)(void *ctx, int result), void *ctx) { return 1; } + +static const SeatVtable server_seat_vt = { + .output = nullseat_output, + .eof = nullseat_eof, + .get_userpass_input = nullseat_get_userpass_input, + .notify_remote_exit = nullseat_notify_remote_exit, + .connection_fatal = nullseat_connection_fatal, + .update_specials_menu = nullseat_update_specials_menu, + .get_ttymode = nullseat_get_ttymode, + .set_busy_status = nullseat_set_busy_status, + .verify_ssh_host_key = nullseat_verify_ssh_host_key, + .confirm_weak_crypto_primitive = server_confirm_weak_crypto_primitive, + .confirm_weak_cached_hostkey = server_confirm_weak_cached_hostkey, + .is_utf8 = nullseat_is_never_utf8, + .echoedit_update = nullseat_echoedit_update, + .get_x_display = nullseat_get_x_display, + .get_windowid = nullseat_get_windowid, + .get_window_pixel_size = nullseat_get_window_pixel_size, + .stripctrl_new = nullseat_stripctrl_new, + .set_trust_status = nullseat_set_trust_status, + .verbose = nullseat_verbose_no, + .interactive = nullseat_interactive_no, + .get_cursor_position = nullseat_get_cursor_position, +}; + +static void server_socket_log(Plug *plug, PlugLogType type, SockAddr *addr, + int port, const char *error_msg, int error_code) +{ + /* server *srv = container_of(plug, server, plug); */ + /* FIXME */ +} + +static void server_closing(Plug *plug, const char *error_msg, int error_code, + bool calling_back) +{ + server *srv = container_of(plug, server, plug); + if (error_msg) { + ssh_remote_error(&srv->ssh, "%s", error_msg); + } else if (srv->bpp) { + srv->bpp->input_eof = true; + queue_idempotent_callback(&srv->bpp->ic_in_raw); + } +} + +static void server_receive( + Plug *plug, int urgent, const char *data, size_t len) +{ + server *srv = container_of(plug, server, plug); + + /* Log raw data, if we're in that mode. */ + if (srv->logctx) + log_packet(srv->logctx, PKT_INCOMING, -1, NULL, data, len, + 0, NULL, NULL, 0, NULL); + + bufchain_add(&srv->in_raw, data, len); + if (!srv->frozen && srv->bpp) + queue_idempotent_callback(&srv->bpp->ic_in_raw); +} + +static void server_sent(Plug *plug, size_t bufsize) +{ +#ifdef FIXME + server *srv = container_of(plug, server, plug); + + /* + * If the send backlog on the SSH socket itself clears, we should + * unthrottle the whole world if it was throttled. Also trigger an + * extra call to the consumer of the BPP's output, to try to send + * some more data off its bufchain. + */ + if (bufsize < SSH_MAX_BACKLOG) { + srv_throttle_all(srv, 0, bufsize); + queue_idempotent_callback(&srv->ic_out_raw); + } +#endif +} + +LogContext *ssh_get_logctx(Ssh *ssh) +{ + server *srv = container_of(ssh, server, ssh); + return srv->logctx; +} + +void ssh_throttle_conn(Ssh *ssh, int adjust) +{ + server *srv = container_of(ssh, server, ssh); + int old_count = srv->conn_throttle_count; + bool frozen; + + srv->conn_throttle_count += adjust; + assert(srv->conn_throttle_count >= 0); + + if (srv->conn_throttle_count && !old_count) { + frozen = true; + } else if (!srv->conn_throttle_count && old_count) { + frozen = false; + } else { + return; /* don't change current frozen state */ + } + + srv->frozen = frozen; + + if (srv->socket) { + sk_set_frozen(srv->socket, frozen); + + /* + * Now process any SSH connection data that was stashed in our + * queue while we were frozen. + */ + queue_idempotent_callback(&srv->bpp->ic_in_raw); + } +} + +void ssh_conn_processed_data(Ssh *ssh) +{ + /* FIXME: we could add the same check_frozen_state system as we + * have in ssh.c, but because that was originally added to work + * around a peculiarity of the GUI event loop, I haven't yet. */ +} + +Conf *make_ssh_server_conf(void) +{ + Conf *conf = conf_new(); + load_open_settings(NULL, conf); + /* In Uppity, we support even the legacy des-cbc cipher by + * default, so that it will be available if the user forces it by + * overriding the KEXINIT strings. If the user wants it _not_ + * supported, of course, they can override KEXINIT in the other + * direction. */ + conf_set_bool(conf, CONF_ssh2_des_cbc, true); + return conf; +} + +static const PlugVtable ssh_server_plugvt = { + .log = server_socket_log, + .closing = server_closing, + .receive = server_receive, + .sent = server_sent, +}; + +Plug *ssh_server_plug( + Conf *conf, const SshServerConfig *ssc, + ssh_key *const *hostkeys, int nhostkeys, + RSAKey *hostkey1, AuthPolicy *authpolicy, LogPolicy *logpolicy, + const SftpServerVtable *sftpserver_vt) +{ + server *srv = snew(server); + + memset(srv, 0, sizeof(server)); + + srv->plug.vt = &ssh_server_plugvt; + srv->conf = conf_copy(conf); + srv->ssc = ssc; + srv->logctx = log_init(logpolicy, conf); + conf_set_bool(srv->conf, CONF_ssh_no_shell, true); + srv->nhostkeys = nhostkeys; + srv->hostkeys = hostkeys; + srv->hostkey1 = hostkey1; + srv->authpolicy = authpolicy; + srv->logpolicy = logpolicy; + srv->sftpserver_vt = sftpserver_vt; + + srv->seat.vt = &server_seat_vt; + + bufchain_init(&srv->in_raw); + bufchain_init(&srv->out_raw); + bufchain_init(&srv->dummy_user_input); + +#ifndef NO_GSSAPI + /* FIXME: replace with sensible */ + srv->gss_state.libs = snew(struct ssh_gss_liblist); + srv->gss_state.libs->nlibraries = 0; +#endif + + return &srv->plug; +} + +void ssh_server_start(Plug *plug, Socket *socket) +{ + server *srv = container_of(plug, server, plug); + const char *our_protoversion; + + if (srv->ssc->bare_connection) { + our_protoversion = "2.0"; /* SSH-2 only */ + } else if (srv->hostkey1 && srv->nhostkeys) { + our_protoversion = "1.99"; /* offer both SSH-1 and SSH-2 */ + } else if (srv->hostkey1) { + our_protoversion = "1.5"; /* SSH-1 only */ + } else { + assert(srv->nhostkeys); + our_protoversion = "2.0"; /* SSH-2 only */ + } + + srv->socket = socket; + + srv->ic_out_raw.fn = server_bpp_output_raw_data_callback; + srv->ic_out_raw.ctx = srv; + srv->version_receiver.got_ssh_version = server_got_ssh_version; + srv->bpp = ssh_verstring_new( + srv->conf, srv->logctx, srv->ssc->bare_connection, + our_protoversion, &srv->version_receiver, + true, srv->ssc->application_name); + server_connect_bpp(srv); + queue_idempotent_callback(&srv->bpp->ic_in_raw); +} + +static void ssh_server_free_callback(void *vsrv) +{ + server *srv = (server *)vsrv; + + logeventf(srv->logctx, "freeing server instance"); + + bufchain_clear(&srv->in_raw); + bufchain_clear(&srv->out_raw); + bufchain_clear(&srv->dummy_user_input); + + if (srv->socket) + sk_close(srv->socket); + + if (srv->stunt_agentfwd) + agentfwd_free(srv->stunt_agentfwd); + + if (srv->base_layer) + ssh_ppl_free(srv->base_layer); + if (srv->bpp) + ssh_bpp_free(srv->bpp); + + delete_callbacks_for_context(srv); + + conf_free(srv->conf); + log_free(srv->logctx); + +#ifndef NO_GSSAPI + sfree(srv->gss_state.libs); /* FIXME: replace with sensible */ +#endif + + LogPolicy *lp = srv->logpolicy; + sfree(srv); + + server_instance_terminated(lp); +} + +static void server_connect_bpp(server *srv) +{ + srv->bpp->ssh = &srv->ssh; + srv->bpp->in_raw = &srv->in_raw; + srv->bpp->out_raw = &srv->out_raw; + bufchain_set_callback(srv->bpp->out_raw, &srv->ic_out_raw); + srv->bpp->pls = &srv->pls; + srv->bpp->logctx = srv->logctx; + srv->bpp->remote_bugs = srv->remote_bugs; + /* Servers don't really have a notion of 'unexpected' connection + * closure. The client is free to close if it likes. */ + srv->bpp->expect_close = true; +} + +static void server_connect_ppl(server *srv, PacketProtocolLayer *ppl) +{ + ppl->bpp = srv->bpp; + ppl->user_input = &srv->dummy_user_input; + ppl->logctx = srv->logctx; + ppl->ssh = &srv->ssh; + ppl->seat = &srv->seat; + ppl->remote_bugs = srv->remote_bugs; +} + +static void server_bpp_output_raw_data_callback(void *vctx) +{ + server *srv = (server *)vctx; + + if (!srv->socket) + return; + + while (bufchain_size(&srv->out_raw) > 0) { + size_t backlog; + + ptrlen data = bufchain_prefix(&srv->out_raw); + + if (srv->logctx) + log_packet(srv->logctx, PKT_OUTGOING, -1, NULL, data.ptr, data.len, + 0, NULL, NULL, 0, NULL); + backlog = sk_write(srv->socket, data.ptr, data.len); + + bufchain_consume(&srv->out_raw, data.len); + + if (backlog > SSH_MAX_BACKLOG) { +#ifdef FIXME + ssh_throttle_all(ssh, 1, backlog); +#endif + return; + } + } + + if (srv->pending_close) { + sk_close(srv->socket); + srv->socket = NULL; + queue_toplevel_callback(ssh_server_free_callback, srv); + } +} + +static void server_shutdown_internal(server *srv) +{ + /* + * We only need to free the base PPL, which will free the others + * (if any) transitively. + */ + if (srv->base_layer) { + ssh_ppl_free(srv->base_layer); + srv->base_layer = NULL; + } + + srv->cl = NULL; +} + +static void server_initiate_connection_close(server *srv) +{ + /* Wind up everything above the BPP. */ + server_shutdown_internal(srv); + + /* Force any remaining queued SSH packets through the BPP, and + * schedule closing the network socket after they go out. */ + ssh_bpp_handle_output(srv->bpp); + srv->pending_close = true; + queue_idempotent_callback(&srv->ic_out_raw); + + /* Now we expect the other end to close the connection too in + * response, so arrange that we'll receive notification of that + * via ssh_remote_eof. */ + srv->bpp->expect_close = true; +} + +#define GET_FORMATTED_MSG(fmt) \ + char *msg; \ + va_list ap; \ + va_start(ap, fmt); \ + msg = dupvprintf(fmt, ap); \ + va_end(ap); + +#define LOG_FORMATTED_MSG(logctx, fmt) do \ + { \ + va_list ap; \ + va_start(ap, fmt); \ + logeventvf(logctx, fmt, ap); \ + va_end(ap); \ + } while (0) + +void ssh_remote_error(Ssh *ssh, const char *fmt, ...) +{ + server *srv = container_of(ssh, server, ssh); + LOG_FORMATTED_MSG(srv->logctx, fmt); + queue_toplevel_callback(ssh_server_free_callback, srv); +} + +void ssh_remote_eof(Ssh *ssh, const char *fmt, ...) +{ + server *srv = container_of(ssh, server, ssh); + LOG_FORMATTED_MSG(srv->logctx, fmt); + queue_toplevel_callback(ssh_server_free_callback, srv); +} + +void ssh_proto_error(Ssh *ssh, const char *fmt, ...) +{ + server *srv = container_of(ssh, server, ssh); + if (srv->base_layer) { + GET_FORMATTED_MSG(fmt); + ssh_bpp_queue_disconnect(srv->bpp, msg, + SSH2_DISCONNECT_PROTOCOL_ERROR); + server_initiate_connection_close(srv); + logeventf(srv->logctx, "Protocol error: %s", msg); + sfree(msg); + } +} + +void ssh_sw_abort(Ssh *ssh, const char *fmt, ...) +{ + server *srv = container_of(ssh, server, ssh); + LOG_FORMATTED_MSG(srv->logctx, fmt); + queue_toplevel_callback(ssh_server_free_callback, srv); +} + +void ssh_user_close(Ssh *ssh, const char *fmt, ...) +{ + server *srv = container_of(ssh, server, ssh); + LOG_FORMATTED_MSG(srv->logctx, fmt); + queue_toplevel_callback(ssh_server_free_callback, srv); +} + +static void server_got_ssh_version(struct ssh_version_receiver *rcv, + int major_version) +{ + server *srv = container_of(rcv, server, version_receiver); + BinaryPacketProtocol *old_bpp; + PacketProtocolLayer *connection_layer; + + old_bpp = srv->bpp; + srv->remote_bugs = ssh_verstring_get_bugs(old_bpp); + + if (srv->ssc->bare_connection) { + srv->bpp = ssh2_bare_bpp_new(srv->logctx); + server_connect_bpp(srv); + + connection_layer = ssh2_connection_new( + &srv->ssh, NULL, false, srv->conf, + ssh_verstring_get_local(old_bpp), &srv->cl); + ssh2connection_server_configure(connection_layer, + srv->sftpserver_vt, srv->ssc); + server_connect_ppl(srv, connection_layer); + + srv->base_layer = connection_layer; + } else if (major_version == 2) { + PacketProtocolLayer *userauth_layer, *transport_child_layer; + + srv->bpp = ssh2_bpp_new(srv->logctx, &srv->stats, true); + server_connect_bpp(srv); + + connection_layer = ssh2_connection_new( + &srv->ssh, NULL, false, srv->conf, + ssh_verstring_get_local(old_bpp), &srv->cl); + ssh2connection_server_configure(connection_layer, + srv->sftpserver_vt, srv->ssc); + server_connect_ppl(srv, connection_layer); + + if (conf_get_bool(srv->conf, CONF_ssh_no_userauth)) { + userauth_layer = NULL; + transport_child_layer = connection_layer; + } else { + userauth_layer = ssh2_userauth_server_new( + connection_layer, srv->authpolicy, srv->ssc); + server_connect_ppl(srv, userauth_layer); + transport_child_layer = userauth_layer; + } + + srv->base_layer = ssh2_transport_new( + srv->conf, NULL, 0, NULL, + ssh_verstring_get_remote(old_bpp), + ssh_verstring_get_local(old_bpp), +#ifndef NO_GSSAPI + &srv->gss_state, +#else + NULL, +#endif + &srv->stats, transport_child_layer, srv->ssc); + ssh2_transport_provide_hostkeys( + srv->base_layer, srv->hostkeys, srv->nhostkeys); + if (userauth_layer) + ssh2_userauth_server_set_transport_layer( + userauth_layer, srv->base_layer); + server_connect_ppl(srv, srv->base_layer); + + } else { + srv->bpp = ssh1_bpp_new(srv->logctx); + server_connect_bpp(srv); + + connection_layer = ssh1_connection_new(&srv->ssh, srv->conf, &srv->cl); + ssh1connection_server_configure(connection_layer, srv->ssc); + server_connect_ppl(srv, connection_layer); + + srv->base_layer = ssh1_login_server_new( + connection_layer, srv->hostkey1, srv->authpolicy, srv->ssc); + server_connect_ppl(srv, srv->base_layer); + } + + /* Connect the base layer - whichever it is - to the BPP, and set + * up its selfptr. */ + srv->base_layer->selfptr = &srv->base_layer; + ssh_ppl_setup_queues(srv->base_layer, &srv->bpp->in_pq, &srv->bpp->out_pq); + +#ifdef FIXME // we probably will want one of these, in the end + srv->pinger = pinger_new(srv->conf, &srv->backend); +#endif + + if (srv->ssc->stunt_open_unconditional_agent_socket) { + char *socketname; + srv->stunt_agentfwd = agentfwd_new(srv->cl, &socketname); + if (srv->stunt_agentfwd) { + logeventf(srv->logctx, "opened unconditional agent socket at %s\n", + socketname); + sfree(socketname); + } + } + + queue_idempotent_callback(&srv->bpp->ic_in_raw); + ssh_ppl_process_queue(srv->base_layer); + + ssh_bpp_free(old_bpp); +} diff --git a/ssh/server.h b/ssh/server.h new file mode 100644 index 00000000..5cc393df --- /dev/null +++ b/ssh/server.h @@ -0,0 +1,143 @@ +typedef struct AuthPolicy AuthPolicy; + +struct SshServerConfig { + const char *application_name; + const char *session_starting_dir; + + RSAKey *rsa_kex_key; + + /* + * In all of these ptrlens, setting the 'ptr' member to NULL means + * that we're not overriding the default configuration. + */ + ptrlen banner; /* default here is 'no banner' */ + ptrlen kex_override[NKEXLIST]; + + bool exit_signal_numeric; /* mimic an old server bug */ + + unsigned long ssh1_cipher_mask; + bool ssh1_allow_compression; + bool bare_connection; + + bool stunt_pretend_to_accept_any_pubkey; + bool stunt_open_unconditional_agent_socket; +}; + +Plug *ssh_server_plug( + Conf *conf, const SshServerConfig *ssc, + ssh_key *const *hostkeys, int nhostkeys, + RSAKey *hostkey1, AuthPolicy *authpolicy, LogPolicy *logpolicy, + const SftpServerVtable *sftpserver_vt); +void ssh_server_start(Plug *plug, Socket *socket); + +void server_instance_terminated(LogPolicy *logpolicy); +void platform_logevent(const char *msg); + +#define AUTHMETHODS(X) \ + X(NONE) \ + X(PASSWORD) \ + X(PUBLICKEY) \ + X(KBDINT) \ + X(TIS) \ + X(CRYPTOCARD) \ + /* end of list */ + +#define AUTHMETHOD_BIT_INDEX(name) AUTHMETHOD_BIT_INDEX_##name, +enum { AUTHMETHODS(AUTHMETHOD_BIT_INDEX) AUTHMETHOD_BIT_INDEX_dummy }; +#define AUTHMETHOD_BIT_VALUE(name) \ + AUTHMETHOD_##name = 1 << AUTHMETHOD_BIT_INDEX_##name, +enum { AUTHMETHODS(AUTHMETHOD_BIT_VALUE) AUTHMETHOD_BIT_VALUE_dummy }; + +typedef struct AuthKbdInt AuthKbdInt; +typedef struct AuthKbdIntPrompt AuthKbdIntPrompt; +struct AuthKbdInt { + char *title, *instruction; /* both need freeing */ + int nprompts; + AuthKbdIntPrompt *prompts; /* the array itself needs freeing */ +}; +struct AuthKbdIntPrompt { + char *prompt; /* needs freeing */ + bool echo; +}; + +unsigned auth_methods(AuthPolicy *); +bool auth_none(AuthPolicy *, ptrlen username); + +int auth_password(AuthPolicy *, ptrlen username, ptrlen password, + ptrlen *opt_new_password); +/* auth_password returns 1 for 'accepted', 0 for 'rejected', and 2 for + * 'ok but now you need to change your password' */ + +bool auth_publickey(AuthPolicy *, ptrlen username, ptrlen public_blob); +/* auth_publickey_ssh1 must return the whole public key given the modulus, + * because the SSH-1 client never transmits the exponent over the wire. + * The key remains owned by the AuthPolicy. */ + +AuthKbdInt *auth_kbdint_prompts(AuthPolicy *, ptrlen username); +/* auth_kbdint_prompts returns NULL to trigger auth failure */ +int auth_kbdint_responses(AuthPolicy *, const ptrlen *responses); +/* auth_kbdint_responses returns >0 for success, <0 for failure, and 0 + * to indicate that we haven't decided yet and further prompts are + * coming */ + +/* The very similar SSH-1 TIS and CryptoCard methods are combined into + * a single API for AuthPolicy, which takes a method argument */ +char *auth_ssh1int_challenge(AuthPolicy *, unsigned method, ptrlen username); +bool auth_ssh1int_response(AuthPolicy *, ptrlen response); + +RSAKey *auth_publickey_ssh1( + AuthPolicy *ap, ptrlen username, mp_int *rsa_modulus); +/* auth_successful returns false if further authentication is needed */ +bool auth_successful(AuthPolicy *, ptrlen username, unsigned method); + +PacketProtocolLayer *ssh2_userauth_server_new( + PacketProtocolLayer *successor_layer, AuthPolicy *authpolicy, + const SshServerConfig *ssc); +void ssh2_userauth_server_set_transport_layer( + PacketProtocolLayer *userauth, PacketProtocolLayer *transport); + +void ssh2connection_server_configure( + PacketProtocolLayer *ppl, const SftpServerVtable *sftpserver_vt, + const SshServerConfig *ssc); +void ssh1connection_server_configure( + PacketProtocolLayer *ppl, const SshServerConfig *ssc); + +PacketProtocolLayer *ssh1_login_server_new( + PacketProtocolLayer *successor_layer, RSAKey *hostkey, + AuthPolicy *authpolicy, const SshServerConfig *ssc); + +Channel *sesschan_new(SshChannel *c, LogContext *logctx, + const SftpServerVtable *sftpserver_vt, + const SshServerConfig *ssc); + +Backend *pty_backend_create( + Seat *seat, LogContext *logctx, Conf *conf, char **argv, const char *cmd, + struct ssh_ttymodes ttymodes, bool pipes_instead_of_pty, const char *dir, + const char *const *env_vars_to_unset); +int pty_backend_exit_signum(Backend *be); +ptrlen pty_backend_exit_signame(Backend *be, char **aux_msg); + +/* + * Establish a listening X server. Return value is the _number_ of + * Sockets that it established pointing at the given Plug. (0 + * indicates complete failure.) The socket pointers themselves are + * written into sockets[], up to a possible total of MAX_X11_SOCKETS. + * + * The supplied Conf has necessary environment variables written into + * it. (And is also used to open the port listeners, though that + * shouldn't affect anything.) + */ +#define MAX_X11_SOCKETS 2 +int platform_make_x11_server(Plug *plug, const char *progname, int mindisp, + const char *screen_number_suffix, + ptrlen authproto, ptrlen authdata, + Socket **sockets, Conf *conf); + +Conf *make_ssh_server_conf(void); + +/* Provided by Unix front end programs to uxsftpserver.c */ +void make_unix_sftp_filehandle_key(void *data, size_t size); + +typedef struct agentfwd agentfwd; +agentfwd *agentfwd_new(ConnectionLayer *cl, char **socketname_out); +void agentfwd_free(agentfwd *agent); diff --git a/ssh/sesschan.c b/ssh/sesschan.c new file mode 100644 index 00000000..4b204b29 --- /dev/null +++ b/ssh/sesschan.c @@ -0,0 +1,787 @@ +/* + * Implement the "session" channel type for the SSH server. + */ + +#include +#include +#include +#include + +#include "putty.h" +#include "ssh.h" +#include "channel.h" +#include "server.h" +#include "sftp.h" + +struct agentfwd { + ConnectionLayer *cl; + Socket *socket; + Plug plug; +}; + +typedef struct sesschan { + SshChannel *c; + + LogContext *parent_logctx, *child_logctx; + Conf *conf; + const SftpServerVtable *sftpserver_vt; + + LogPolicy logpolicy; + Seat seat; + + bool want_pty; + struct ssh_ttymodes ttymodes; + int wc, hc, wp, hp; + strbuf *termtype; + + bool ignoring_input; + bool seen_eof, seen_exit; + + Plug xfwd_plug; + int n_x11_sockets; + Socket *x11_sockets[MAX_X11_SOCKETS]; + + agentfwd *agent; + + Backend *backend; + + bufchain subsys_input; + SftpServer *sftpsrv; + ScpServer *scpsrv; + const SshServerConfig *ssc; + + Channel chan; +} sesschan; + +static void sesschan_free(Channel *chan); +static size_t sesschan_send( + Channel *chan, bool is_stderr, const void *, size_t); +static void sesschan_send_eof(Channel *chan); +static char *sesschan_log_close_msg(Channel *chan); +static bool sesschan_want_close(Channel *, bool, bool); +static void sesschan_set_input_wanted(Channel *chan, bool wanted); +static bool sesschan_run_shell(Channel *chan); +static bool sesschan_run_command(Channel *chan, ptrlen command); +static bool sesschan_run_subsystem(Channel *chan, ptrlen subsys); +static bool sesschan_enable_x11_forwarding( + Channel *chan, bool oneshot, ptrlen authproto, ptrlen authdata, + unsigned screen_number); +static bool sesschan_enable_agent_forwarding(Channel *chan); +static bool sesschan_allocate_pty( + Channel *chan, ptrlen termtype, unsigned width, unsigned height, + unsigned pixwidth, unsigned pixheight, struct ssh_ttymodes modes); +static bool sesschan_set_env(Channel *chan, ptrlen var, ptrlen value); +static bool sesschan_send_break(Channel *chan, unsigned length); +static bool sesschan_send_signal(Channel *chan, ptrlen signame); +static bool sesschan_change_window_size( + Channel *chan, unsigned width, unsigned height, + unsigned pixwidth, unsigned pixheight); + +static const ChannelVtable sesschan_channelvt = { + .free = sesschan_free, + .open_confirmation = chan_remotely_opened_confirmation, + .open_failed = chan_remotely_opened_failure, + .send = sesschan_send, + .send_eof = sesschan_send_eof, + .set_input_wanted = sesschan_set_input_wanted, + .log_close_msg = sesschan_log_close_msg, + .want_close = sesschan_want_close, + .rcvd_exit_status = chan_no_exit_status, + .rcvd_exit_signal = chan_no_exit_signal, + .rcvd_exit_signal_numeric = chan_no_exit_signal_numeric, + .run_shell = sesschan_run_shell, + .run_command = sesschan_run_command, + .run_subsystem = sesschan_run_subsystem, + .enable_x11_forwarding = sesschan_enable_x11_forwarding, + .enable_agent_forwarding = sesschan_enable_agent_forwarding, + .allocate_pty = sesschan_allocate_pty, + .set_env = sesschan_set_env, + .send_break = sesschan_send_break, + .send_signal = sesschan_send_signal, + .change_window_size = sesschan_change_window_size, + .request_response = chan_no_request_response, +}; + +static size_t sftp_chan_send( + Channel *chan, bool is_stderr, const void *, size_t); +static void sftp_chan_send_eof(Channel *chan); +static char *sftp_log_close_msg(Channel *chan); + +static const ChannelVtable sftp_channelvt = { + .free = sesschan_free, + .open_confirmation = chan_remotely_opened_confirmation, + .open_failed = chan_remotely_opened_failure, + .send = sftp_chan_send, + .send_eof = sftp_chan_send_eof, + .set_input_wanted = sesschan_set_input_wanted, + .log_close_msg = sftp_log_close_msg, + .want_close = chan_default_want_close, + .rcvd_exit_status = chan_no_exit_status, + .rcvd_exit_signal = chan_no_exit_signal, + .rcvd_exit_signal_numeric = chan_no_exit_signal_numeric, + .run_shell = chan_no_run_shell, + .run_command = chan_no_run_command, + .run_subsystem = chan_no_run_subsystem, + .enable_x11_forwarding = chan_no_enable_x11_forwarding, + .enable_agent_forwarding = chan_no_enable_agent_forwarding, + .allocate_pty = chan_no_allocate_pty, + .set_env = chan_no_set_env, + .send_break = chan_no_send_break, + .send_signal = chan_no_send_signal, + .change_window_size = chan_no_change_window_size, + .request_response = chan_no_request_response, +}; + +static size_t scp_chan_send( + Channel *chan, bool is_stderr, const void *, size_t); +static void scp_chan_send_eof(Channel *chan); +static void scp_set_input_wanted(Channel *chan, bool wanted); +static char *scp_log_close_msg(Channel *chan); + +static const ChannelVtable scp_channelvt = { + .free = sesschan_free, + .open_confirmation = chan_remotely_opened_confirmation, + .open_failed = chan_remotely_opened_failure, + .send = scp_chan_send, + .send_eof = scp_chan_send_eof, + .set_input_wanted = scp_set_input_wanted, + .log_close_msg = scp_log_close_msg, + .want_close = chan_default_want_close, + .rcvd_exit_status = chan_no_exit_status, + .rcvd_exit_signal = chan_no_exit_signal, + .rcvd_exit_signal_numeric = chan_no_exit_signal_numeric, + .run_shell = chan_no_run_shell, + .run_command = chan_no_run_command, + .run_subsystem = chan_no_run_subsystem, + .enable_x11_forwarding = chan_no_enable_x11_forwarding, + .enable_agent_forwarding = chan_no_enable_agent_forwarding, + .allocate_pty = chan_no_allocate_pty, + .set_env = chan_no_set_env, + .send_break = chan_no_send_break, + .send_signal = chan_no_send_signal, + .change_window_size = chan_no_change_window_size, + .request_response = chan_no_request_response, +}; + +static void sesschan_eventlog(LogPolicy *lp, const char *event) {} +static void sesschan_logging_error(LogPolicy *lp, const char *event) {} +static int sesschan_askappend( + LogPolicy *lp, Filename *filename, + void (*callback)(void *ctx, int result), void *ctx) { return 2; } + +static const LogPolicyVtable sesschan_logpolicy_vt = { + .eventlog = sesschan_eventlog, + .askappend = sesschan_askappend, + .logging_error = sesschan_logging_error, + .verbose = null_lp_verbose_no, +}; + +static size_t sesschan_seat_output( + Seat *, bool is_stderr, const void *, size_t); +static bool sesschan_seat_eof(Seat *); +static void sesschan_notify_remote_exit(Seat *seat); +static void sesschan_connection_fatal(Seat *seat, const char *message); +static bool sesschan_get_window_pixel_size(Seat *seat, int *w, int *h); + +static const SeatVtable sesschan_seat_vt = { + .output = sesschan_seat_output, + .eof = sesschan_seat_eof, + .get_userpass_input = nullseat_get_userpass_input, + .notify_remote_exit = sesschan_notify_remote_exit, + .connection_fatal = sesschan_connection_fatal, + .update_specials_menu = nullseat_update_specials_menu, + .get_ttymode = nullseat_get_ttymode, + .set_busy_status = nullseat_set_busy_status, + .verify_ssh_host_key = nullseat_verify_ssh_host_key, + .confirm_weak_crypto_primitive = nullseat_confirm_weak_crypto_primitive, + .confirm_weak_cached_hostkey = nullseat_confirm_weak_cached_hostkey, + .is_utf8 = nullseat_is_never_utf8, + .echoedit_update = nullseat_echoedit_update, + .get_x_display = nullseat_get_x_display, + .get_windowid = nullseat_get_windowid, + .get_window_pixel_size = sesschan_get_window_pixel_size, + .stripctrl_new = nullseat_stripctrl_new, + .set_trust_status = nullseat_set_trust_status, + .verbose = nullseat_verbose_no, + .interactive = nullseat_interactive_no, + .get_cursor_position = nullseat_get_cursor_position, +}; + +Channel *sesschan_new(SshChannel *c, LogContext *logctx, + const SftpServerVtable *sftpserver_vt, + const SshServerConfig *ssc) +{ + sesschan *sess = snew(sesschan); + memset(sess, 0, sizeof(sesschan)); + + sess->c = c; + sess->chan.vt = &sesschan_channelvt; + sess->chan.initial_fixed_window_size = 0; + sess->parent_logctx = logctx; + sess->ssc = ssc; + + /* Start with a completely default Conf */ + sess->conf = conf_new(); + load_open_settings(NULL, sess->conf); + + /* Set close-on-exit = true to suppress uxpty.c's "[pterm: process + * terminated with status x]" message */ + conf_set_int(sess->conf, CONF_close_on_exit, FORCE_ON); + + sess->seat.vt = &sesschan_seat_vt; + sess->logpolicy.vt = &sesschan_logpolicy_vt; + sess->child_logctx = log_init(&sess->logpolicy, sess->conf); + + sess->sftpserver_vt = sftpserver_vt; + + bufchain_init(&sess->subsys_input); + + return &sess->chan; +} + +static void sesschan_free(Channel *chan) +{ + sesschan *sess = container_of(chan, sesschan, chan); + int i; + + delete_callbacks_for_context(sess); + conf_free(sess->conf); + if (sess->backend) + backend_free(sess->backend); + bufchain_clear(&sess->subsys_input); + if (sess->sftpsrv) + sftpsrv_free(sess->sftpsrv); + for (i = 0; i < sess->n_x11_sockets; i++) + sk_close(sess->x11_sockets[i]); + if (sess->agent) + agentfwd_free(sess->agent); + + sfree(sess); +} + +static size_t sesschan_send(Channel *chan, bool is_stderr, + const void *data, size_t length) +{ + sesschan *sess = container_of(chan, sesschan, chan); + + if (!sess->backend || sess->ignoring_input) + return 0; + + return backend_send(sess->backend, data, length); +} + +static void sesschan_send_eof(Channel *chan) +{ + sesschan *sess = container_of(chan, sesschan, chan); + if (sess->backend) + backend_special(sess->backend, SS_EOF, 0); +} + +static char *sesschan_log_close_msg(Channel *chan) +{ + return dupstr("Session channel closed"); +} + +static void sesschan_set_input_wanted(Channel *chan, bool wanted) +{ + /* I don't think we need to do anything here */ +} + +static void sesschan_start_backend(sesschan *sess, const char *cmd) +{ + /* + * List of environment variables that we should not pass through + * from the login session Uppity was run in (which, it being a + * test server, there will usually be one of). These variables + * will be set as part of X or agent forwarding, and shouldn't be + * confusingly set in the absence of that. + * + * (DISPLAY must also be cleared, but uxpty.c will do that anyway + * when our get_x_display method returns NULL.) + */ + static const char *const env_to_unset[] = { + "XAUTHORITY", "SSH_AUTH_SOCK", "SSH_AGENT_PID", + NULL /* terminator */ + }; + + sess->backend = pty_backend_create( + &sess->seat, sess->child_logctx, sess->conf, NULL, cmd, + sess->ttymodes, !sess->want_pty, sess->ssc->session_starting_dir, + env_to_unset); + backend_size(sess->backend, sess->wc, sess->hc); +} + +bool sesschan_run_shell(Channel *chan) +{ + sesschan *sess = container_of(chan, sesschan, chan); + + if (sess->backend) + return false; + + sesschan_start_backend(sess, NULL); + return true; +} + +bool sesschan_run_command(Channel *chan, ptrlen command) +{ + sesschan *sess = container_of(chan, sesschan, chan); + + if (sess->backend) + return false; + + /* FIXME: make this possible to configure off */ + if ((sess->scpsrv = scp_recognise_exec(sess->c, sess->sftpserver_vt, + command)) != NULL) { + sess->chan.vt = &scp_channelvt; + logevent(sess->parent_logctx, "Starting built-in SCP server"); + return true; + } + + char *command_str = mkstr(command); + sesschan_start_backend(sess, command_str); + sfree(command_str); + + return true; +} + +bool sesschan_run_subsystem(Channel *chan, ptrlen subsys) +{ + sesschan *sess = container_of(chan, sesschan, chan); + + if (ptrlen_eq_string(subsys, "sftp") && sess->sftpserver_vt) { + sess->sftpsrv = sftpsrv_new(sess->sftpserver_vt); + sess->chan.vt = &sftp_channelvt; + logevent(sess->parent_logctx, "Starting built-in SFTP subsystem"); + return true; + } + + return false; +} + +static void fwd_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, + const char *error_msg, int error_code) +{ /* don't expect any weirdnesses from a listening socket */ } +static void fwd_closing(Plug *plug, const char *error_msg, int error_code, + bool calling_back) +{ /* not here, either */ } + +static int xfwd_accepting(Plug *p, accept_fn_t constructor, accept_ctx_t ctx) +{ + sesschan *sess = container_of(p, sesschan, xfwd_plug); + Plug *plug; + Channel *chan; + Socket *s; + SocketPeerInfo *pi; + const char *err; + + chan = portfwd_raw_new(sess->c->cl, &plug, false); + s = constructor(ctx, plug); + if ((err = sk_socket_error(s)) != NULL) { + portfwd_raw_free(chan); + return 1; + } + pi = sk_peer_info(s); + portfwd_raw_setup(chan, s, ssh_serverside_x11_open(sess->c->cl, chan, pi)); + sk_free_peer_info(pi); + + return 0; +} + +static const PlugVtable xfwd_plugvt = { + .log = fwd_log, + .closing = fwd_closing, + .accepting = xfwd_accepting, +}; + +bool sesschan_enable_x11_forwarding( + Channel *chan, bool oneshot, ptrlen authproto, ptrlen authdata_hex, + unsigned screen_number) +{ + sesschan *sess = container_of(chan, sesschan, chan); + strbuf *authdata_bin; + size_t i; + + if (oneshot) + return false; /* not supported */ + + /* + * Decode the authorisation data from ASCII hex into binary. + */ + if (authdata_hex.len % 2) + return false; /* expected an even number of digits */ + authdata_bin = strbuf_new_nm(); + for (i = 0; i < authdata_hex.len; i += 2) { + const unsigned char *hex = authdata_hex.ptr; + char hexbuf[3]; + + if (!isxdigit(hex[i]) || !isxdigit(hex[i+1])) { + strbuf_free(authdata_bin); + return false; /* not hex */ + } + + hexbuf[0] = hex[i]; + hexbuf[1] = hex[i+1]; + hexbuf[2] = '\0'; + put_byte(authdata_bin, strtoul(hexbuf, NULL, 16)); + } + + sess->xfwd_plug.vt = &xfwd_plugvt; + + char *screensuffix = dupprintf(".%u", screen_number); + + sess->n_x11_sockets = platform_make_x11_server( + &sess->xfwd_plug, appname, 10, screensuffix, + authproto, ptrlen_from_strbuf(authdata_bin), + sess->x11_sockets, sess->conf); + + sfree(screensuffix); + strbuf_free(authdata_bin); + return sess->n_x11_sockets != 0; +} + +static int agentfwd_accepting( + Plug *p, accept_fn_t constructor, accept_ctx_t ctx) +{ + agentfwd *agent = container_of(p, agentfwd, plug); + Plug *plug; + Channel *chan; + Socket *s; + const char *err; + + chan = portfwd_raw_new(agent->cl, &plug, false); + s = constructor(ctx, plug); + if ((err = sk_socket_error(s)) != NULL) { + portfwd_raw_free(chan); + return 1; + } + portfwd_raw_setup(chan, s, ssh_serverside_agent_open(agent->cl, chan)); + + return 0; +} + +static const PlugVtable agentfwd_plugvt = { + .log = fwd_log, + .closing = fwd_closing, + .accepting = agentfwd_accepting, +}; + +agentfwd *agentfwd_new(ConnectionLayer *cl, char **socketname_out) +{ + agentfwd *agent = snew(agentfwd); + agent->cl = cl; + agent->plug.vt = &agentfwd_plugvt; + + char *dir_prefix = dupprintf("/tmp/%s-agentfwd", appname); + char *error = NULL, *socketname = NULL; + agent->socket = platform_make_agent_socket( + &agent->plug, dir_prefix, &error, &socketname); + sfree(dir_prefix); + sfree(error); + + if (!agent->socket) { + sfree(agent); + sfree(socketname); + return NULL; + } + + *socketname_out = socketname; + return agent; +} + +void agentfwd_free(agentfwd *agent) +{ + if (agent->socket) + sk_close(agent->socket); + sfree(agent); +} + +bool sesschan_enable_agent_forwarding(Channel *chan) +{ + sesschan *sess = container_of(chan, sesschan, chan); + char *socketname; + + assert(!sess->agent); + + sess->agent = agentfwd_new(sess->c->cl, &socketname); + + if (!sess->agent) + return false; + + conf_set_str_str(sess->conf, CONF_environmt, "SSH_AUTH_SOCK", socketname); + sfree(socketname); + return true; +} + +bool sesschan_allocate_pty( + Channel *chan, ptrlen termtype, unsigned width, unsigned height, + unsigned pixwidth, unsigned pixheight, struct ssh_ttymodes modes) +{ + sesschan *sess = container_of(chan, sesschan, chan); + char *s; + + if (sess->want_pty) + return false; + + s = mkstr(termtype); + conf_set_str(sess->conf, CONF_termtype, s); + sfree(s); + + sess->want_pty = true; + sess->ttymodes = modes; + sess->wc = width; + sess->hc = height; + sess->wp = pixwidth; + sess->hp = pixheight; + + return true; +} + +bool sesschan_set_env(Channel *chan, ptrlen var, ptrlen value) +{ + sesschan *sess = container_of(chan, sesschan, chan); + + char *svar = mkstr(var), *svalue = mkstr(value); + conf_set_str_str(sess->conf, CONF_environmt, svar, svalue); + sfree(svar); + sfree(svalue); + + return true; +} + +bool sesschan_send_break(Channel *chan, unsigned length) +{ + sesschan *sess = container_of(chan, sesschan, chan); + + if (sess->backend) { + /* We ignore the break length. We could pass it through as the + * 'arg' parameter, and have uxpty.c collect it and pass it on + * to tcsendbreak, but since tcsendbreak in turn assigns + * implementation-defined semantics to _its_ duration + * parameter, this all just sounds too difficult. */ + backend_special(sess->backend, SS_BRK, 0); + return true; + } + return false; +} + +bool sesschan_send_signal(Channel *chan, ptrlen signame) +{ + sesschan *sess = container_of(chan, sesschan, chan); + + /* Start with a code that definitely isn't a signal (or indeed a + * special command at all), to indicate 'nothing matched'. */ + SessionSpecialCode code = SS_EXITMENU; + + #define SIGNAL_SUB(name) \ + if (ptrlen_eq_string(signame, #name)) code = SS_SIG ## name; + #define SIGNAL_MAIN(name, text) SIGNAL_SUB(name) + #include "signal-list.h" + #undef SIGNAL_MAIN + #undef SIGNAL_SUB + + if (code == SS_EXITMENU) + return false; + + backend_special(sess->backend, code, 0); + return true; +} + +bool sesschan_change_window_size( + Channel *chan, unsigned width, unsigned height, + unsigned pixwidth, unsigned pixheight) +{ + sesschan *sess = container_of(chan, sesschan, chan); + + if (!sess->want_pty) + return false; + + sess->wc = width; + sess->hc = height; + sess->wp = pixwidth; + sess->hp = pixheight; + + if (sess->backend) + backend_size(sess->backend, sess->wc, sess->hc); + + return true; +} + +static size_t sesschan_seat_output( + Seat *seat, bool is_stderr, const void *data, size_t len) +{ + sesschan *sess = container_of(seat, sesschan, seat); + return sshfwd_write_ext(sess->c, is_stderr, data, len); +} + +static void sesschan_check_close_callback(void *vctx) +{ + sesschan *sess = (sesschan *)vctx; + + /* + * Once we've seen incoming EOF from the backend (aka EIO from the + * pty master) and also passed on the process's exit status, we + * should proactively initiate closure of the session channel. + */ + if (sess->seen_eof && sess->seen_exit) + sshfwd_initiate_close(sess->c, NULL); +} + +static bool sesschan_want_close(Channel *chan, bool seen_eof, bool rcvd_eof) +{ + sesschan *sess = container_of(chan, sesschan, chan); + + /* + * Similarly to above, we don't want to initiate channel closure + * until we've sent the process's exit status, _even_ if EOF of + * the actual data stream has happened in both directions. + */ + return (sess->seen_eof && sess->seen_exit); +} + +static bool sesschan_seat_eof(Seat *seat) +{ + sesschan *sess = container_of(seat, sesschan, seat); + + sshfwd_write_eof(sess->c); + sess->seen_eof = true; + + queue_toplevel_callback(sesschan_check_close_callback, sess); + return true; +} + +static void sesschan_notify_remote_exit(Seat *seat) +{ + sesschan *sess = container_of(seat, sesschan, seat); + + if (!sess->backend) + return; + + bool got_signal = false; + if (!sess->ssc->exit_signal_numeric) { + char *sigmsg; + ptrlen signame = pty_backend_exit_signame(sess->backend, &sigmsg); + + if (signame.len) { + if (!sigmsg) + sigmsg = dupstr(""); + + sshfwd_send_exit_signal( + sess->c, signame, false, ptrlen_from_asciz(sigmsg)); + + got_signal = true; + } + + sfree(sigmsg); + } else { + int signum = pty_backend_exit_signum(sess->backend); + + if (signum >= 0) { + sshfwd_send_exit_signal_numeric(sess->c, signum, false, + PTRLEN_LITERAL("")); + got_signal = true; + } + } + + if (!got_signal) + sshfwd_send_exit_status(sess->c, backend_exitcode(sess->backend)); + + sess->seen_exit = true; + queue_toplevel_callback(sesschan_check_close_callback, sess); +} + +static void sesschan_connection_fatal(Seat *seat, const char *message) +{ + sesschan *sess = container_of(seat, sesschan, seat); + + /* Closest translation I can think of */ + sshfwd_send_exit_signal( + sess->c, PTRLEN_LITERAL("HUP"), false, ptrlen_from_asciz(message)); + + sess->ignoring_input = true; +} + +static bool sesschan_get_window_pixel_size(Seat *seat, int *width, int *height) +{ + sesschan *sess = container_of(seat, sesschan, seat); + + *width = sess->wp; + *height = sess->hp; + + return true; +} + +/* ---------------------------------------------------------------------- + * Built-in SFTP subsystem. + */ + +static size_t sftp_chan_send(Channel *chan, bool is_stderr, + const void *data, size_t length) +{ + sesschan *sess = container_of(chan, sesschan, chan); + + bufchain_add(&sess->subsys_input, data, length); + + while (bufchain_size(&sess->subsys_input) >= 4) { + char lenbuf[4]; + unsigned pktlen; + struct sftp_packet *pkt, *reply; + + bufchain_fetch(&sess->subsys_input, lenbuf, 4); + pktlen = GET_32BIT_MSB_FIRST(lenbuf); + + if (bufchain_size(&sess->subsys_input) - 4 < pktlen) + break; /* wait for more data */ + + bufchain_consume(&sess->subsys_input, 4); + pkt = sftp_recv_prepare(pktlen); + bufchain_fetch_consume(&sess->subsys_input, pkt->data, pktlen); + sftp_recv_finish(pkt); + reply = sftp_handle_request(sess->sftpsrv, pkt); + sftp_pkt_free(pkt); + + sftp_send_prepare(reply); + sshfwd_write(sess->c, reply->data, reply->length); + sftp_pkt_free(reply); + } + + return 0; +} + +static void sftp_chan_send_eof(Channel *chan) +{ + sesschan *sess = container_of(chan, sesschan, chan); + sshfwd_write_eof(sess->c); +} + +static char *sftp_log_close_msg(Channel *chan) +{ + return dupstr("Session channel (SFTP) closed"); +} + +/* ---------------------------------------------------------------------- + * Built-in SCP subsystem. + */ + +static size_t scp_chan_send(Channel *chan, bool is_stderr, + const void *data, size_t length) +{ + sesschan *sess = container_of(chan, sesschan, chan); + return scp_send(sess->scpsrv, data, length); +} + +static void scp_chan_send_eof(Channel *chan) +{ + sesschan *sess = container_of(chan, sesschan, chan); + scp_eof(sess->scpsrv); +} + +static char *scp_log_close_msg(Channel *chan) +{ + return dupstr("Session channel (SCP) closed"); +} + +static void scp_set_input_wanted(Channel *chan, bool wanted) +{ + sesschan *sess = container_of(chan, sesschan, chan); + scp_throttle(sess->scpsrv, !wanted); +} diff --git a/ssh/sftp.c b/ssh/sftp.c new file mode 100644 index 00000000..a76702f8 --- /dev/null +++ b/ssh/sftp.c @@ -0,0 +1,1205 @@ +/* + * sftp.c: SFTP generic client code. + */ + +#include +#include +#include +#include +#include + +#include "misc.h" +#include "tree234.h" +#include "sftp.h" + +static const char *fxp_error_message; +static int fxp_errtype; + +static void fxp_internal_error(const char *msg); + +/* ---------------------------------------------------------------------- + * Client-specific parts of the send- and receive-packet system. + */ + +bool sftp_send(struct sftp_packet *pkt) +{ + bool ret; + sftp_send_prepare(pkt); + ret = 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]; + + if (!sftp_recvdata(x, 4)) + return NULL; + + /* Impose _some_ upper bound on packet size. We never expect to + * receive more than 32K of data in response to an FXP_READ, + * because we decide how much data to ask for. FXP_READDIR and + * pathname-returning things like FXP_REALPATH don't have an + * explicit bound, so I suppose we just have to trust the server + * to be sensible. */ + unsigned pktlen = GET_32BIT_MSB_FIRST(x); + if (pktlen > (1<<20)) + return NULL; + + pkt = sftp_recv_prepare(pktlen); + + if (!sftp_recvdata(pkt->data, pkt->length)) { + sftp_pkt_free(pkt); + return NULL; + } + + if (!sftp_recv_finish(pkt)) { + sftp_pkt_free(pkt); + return NULL; + } + + return pkt; +} + +/* ---------------------------------------------------------------------- + * Request ID allocation and temporary dispatch routines. + */ + +#define REQUEST_ID_OFFSET 256 + +struct sftp_request { + unsigned id; + bool 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 = false; + 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 = true; +} + +struct sftp_request *sftp_find_request(struct sftp_packet *pktin) +{ + unsigned id; + struct sftp_request *req; + + if (!pktin) { + fxp_internal_error("did not receive a valid SFTP packet\n"); + return NULL; + } + + id = get_uint32(pktin); + if (get_err(pktin)) { + 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"); + return NULL; + } + + del234(sftp_requests, req); + + return req; +} + +/* ---------------------------------------------------------------------- + * 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 { + fxp_errtype = get_uint32(pktin); + if (get_err(pktin)) { + fxp_error_message = "malformed FXP_STATUS packet"; + fxp_errtype = -1; + } else { + if (fxp_errtype < 0 || fxp_errtype >= lenof(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(const 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. + */ +bool fxp_init(void) +{ + struct sftp_packet *pktout, *pktin; + unsigned long remotever; + + pktout = sftp_pkt_init(SSH_FXP_INIT); + put_uint32(pktout, SFTP_PROTO_VERSION); + sftp_send(pktout); + + pktin = sftp_recv(); + if (!pktin) { + fxp_internal_error("could not connect"); + return false; + } + if (pktin->type != SSH_FXP_VERSION) { + fxp_internal_error("did not receive FXP_VERSION"); + sftp_pkt_free(pktin); + return false; + } + remotever = get_uint32(pktin); + if (get_err(pktin)) { + fxp_internal_error("malformed FXP_VERSION packet"); + sftp_pkt_free(pktin); + return false; + } + if (remotever > SFTP_PROTO_VERSION) { + fxp_internal_error + ("remote protocol is more advanced than we support"); + sftp_pkt_free(pktin); + return false; + } + /* + * 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 true; +} + +/* + * Canonify a pathname. + */ +struct sftp_request *fxp_realpath_send(const char *path) +{ + struct sftp_request *req = sftp_alloc_request(); + struct sftp_packet *pktout; + + pktout = sftp_pkt_init(SSH_FXP_REALPATH); + put_uint32(pktout, req->id); + put_stringz(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; + ptrlen name; + + count = get_uint32(pktin); + if (get_err(pktin) || count != 1) { + fxp_internal_error("REALPATH did not return name count of 1\n"); + sftp_pkt_free(pktin); + return NULL; + } + name = get_string(pktin); + if (get_err(pktin)) { + fxp_internal_error("REALPATH returned malformed FXP_NAME\n"); + sftp_pkt_free(pktin); + return NULL; + } + path = mkstr(name); + 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(const char *path, int type, + const struct fxp_attrs *attrs) +{ + struct sftp_request *req = sftp_alloc_request(); + struct sftp_packet *pktout; + + pktout = sftp_pkt_init(SSH_FXP_OPEN); + put_uint32(pktout, req->id); + put_stringz(pktout, path); + put_uint32(pktout, type); + put_fxp_attrs(pktout, attrs ? *attrs : no_attrs); + sftp_send(pktout); + + return req; +} + +static struct fxp_handle *fxp_got_handle(struct sftp_packet *pktin) +{ + ptrlen id; + struct fxp_handle *handle; + + id = get_string(pktin); + if (get_err(pktin)) { + fxp_internal_error("received malformed FXP_HANDLE"); + sftp_pkt_free(pktin); + return NULL; + } + handle = snew(struct fxp_handle); + handle->hstring = mkstr(id); + handle->hlen = id.len; + sftp_pkt_free(pktin); + return handle; +} + +struct fxp_handle *fxp_open_recv(struct sftp_packet *pktin, + struct sftp_request *req) +{ + sfree(req); + + if (pktin->type == SSH_FXP_HANDLE) { + return fxp_got_handle(pktin); + } else { + fxp_got_status(pktin); + sftp_pkt_free(pktin); + return NULL; + } +} + +/* + * Open a directory. + */ +struct sftp_request *fxp_opendir_send(const char *path) +{ + struct sftp_request *req = sftp_alloc_request(); + struct sftp_packet *pktout; + + pktout = sftp_pkt_init(SSH_FXP_OPENDIR); + put_uint32(pktout, req->id); + put_stringz(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) { + return fxp_got_handle(pktin); + } 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); + put_uint32(pktout, req->id); + put_string(pktout, handle->hstring, handle->hlen); + sftp_send(pktout); + + sfree(handle->hstring); + sfree(handle); + + return req; +} + +bool fxp_close_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; +} + +struct sftp_request *fxp_mkdir_send(const char *path, + const struct fxp_attrs *attrs) +{ + struct sftp_request *req = sftp_alloc_request(); + struct sftp_packet *pktout; + + pktout = sftp_pkt_init(SSH_FXP_MKDIR); + put_uint32(pktout, req->id); + put_stringz(pktout, path); + put_fxp_attrs(pktout, attrs ? *attrs : no_attrs); + sftp_send(pktout); + + return req; +} + +bool fxp_mkdir_recv(struct sftp_packet *pktin, struct sftp_request *req) +{ + int id; + sfree(req); + id = fxp_got_status(pktin); + sftp_pkt_free(pktin); + return id == 1; +} + +struct sftp_request *fxp_rmdir_send(const char *path) +{ + struct sftp_request *req = sftp_alloc_request(); + struct sftp_packet *pktout; + + pktout = sftp_pkt_init(SSH_FXP_RMDIR); + put_uint32(pktout, req->id); + put_stringz(pktout, path); + sftp_send(pktout); + + return req; +} + +bool fxp_rmdir_recv(struct sftp_packet *pktin, struct sftp_request *req) +{ + int id; + sfree(req); + id = fxp_got_status(pktin); + sftp_pkt_free(pktin); + return id == 1; +} + +struct sftp_request *fxp_remove_send(const char *fname) +{ + struct sftp_request *req = sftp_alloc_request(); + struct sftp_packet *pktout; + + pktout = sftp_pkt_init(SSH_FXP_REMOVE); + put_uint32(pktout, req->id); + put_stringz(pktout, fname); + sftp_send(pktout); + + return req; +} + +bool fxp_remove_recv(struct sftp_packet *pktin, struct sftp_request *req) +{ + int id; + sfree(req); + id = fxp_got_status(pktin); + sftp_pkt_free(pktin); + return id == 1; +} + +struct sftp_request *fxp_rename_send(const char *srcfname, + const char *dstfname) +{ + struct sftp_request *req = sftp_alloc_request(); + struct sftp_packet *pktout; + + pktout = sftp_pkt_init(SSH_FXP_RENAME); + put_uint32(pktout, req->id); + put_stringz(pktout, srcfname); + put_stringz(pktout, dstfname); + sftp_send(pktout); + + return req; +} + +bool fxp_rename_recv(struct sftp_packet *pktin, struct sftp_request *req) +{ + int id; + sfree(req); + id = fxp_got_status(pktin); + sftp_pkt_free(pktin); + return id == 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(const char *fname) +{ + struct sftp_request *req = sftp_alloc_request(); + struct sftp_packet *pktout; + + pktout = sftp_pkt_init(SSH_FXP_STAT); + put_uint32(pktout, req->id); + put_stringz(pktout, fname); + sftp_send(pktout); + + return req; +} + +static bool fxp_got_attrs(struct sftp_packet *pktin, struct fxp_attrs *attrs) +{ + get_fxp_attrs(pktin, attrs); + if (get_err(pktin)) { + fxp_internal_error("malformed SSH_FXP_ATTRS packet"); + sftp_pkt_free(pktin); + return false; + } + sftp_pkt_free(pktin); + return true; +} + +bool fxp_stat_recv(struct sftp_packet *pktin, struct sftp_request *req, + struct fxp_attrs *attrs) +{ + sfree(req); + if (pktin->type == SSH_FXP_ATTRS) { + return fxp_got_attrs(pktin, attrs); + } else { + fxp_got_status(pktin); + sftp_pkt_free(pktin); + return false; + } +} + +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); + put_uint32(pktout, req->id); + put_string(pktout, handle->hstring, handle->hlen); + sftp_send(pktout); + + return req; +} + +bool fxp_fstat_recv(struct sftp_packet *pktin, struct sftp_request *req, + struct fxp_attrs *attrs) +{ + sfree(req); + if (pktin->type == SSH_FXP_ATTRS) { + return fxp_got_attrs(pktin, attrs); + } else { + fxp_got_status(pktin); + sftp_pkt_free(pktin); + return false; + } +} + +/* + * Set the attributes of a file. + */ +struct sftp_request *fxp_setstat_send(const char *fname, + struct fxp_attrs attrs) +{ + struct sftp_request *req = sftp_alloc_request(); + struct sftp_packet *pktout; + + pktout = sftp_pkt_init(SSH_FXP_SETSTAT); + put_uint32(pktout, req->id); + put_stringz(pktout, fname); + put_fxp_attrs(pktout, attrs); + sftp_send(pktout); + + return req; +} + +bool fxp_setstat_recv(struct sftp_packet *pktin, struct sftp_request *req) +{ + int id; + sfree(req); + id = fxp_got_status(pktin); + sftp_pkt_free(pktin); + return id == 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); + put_uint32(pktout, req->id); + put_string(pktout, handle->hstring, handle->hlen); + put_fxp_attrs(pktout, attrs); + sftp_send(pktout); + + return req; +} + +bool fxp_fsetstat_recv(struct sftp_packet *pktin, struct sftp_request *req) +{ + int id; + sfree(req); + id = fxp_got_status(pktin); + sftp_pkt_free(pktin); + return id == 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_t offset, int len) +{ + struct sftp_request *req = sftp_alloc_request(); + struct sftp_packet *pktout; + + pktout = sftp_pkt_init(SSH_FXP_READ); + put_uint32(pktout, req->id); + put_string(pktout, handle->hstring, handle->hlen); + put_uint64(pktout, offset); + put_uint32(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) { + ptrlen data; + + data = get_string(pktin); + if (get_err(pktin)) { + fxp_internal_error("READ returned malformed SSH_FXP_DATA packet"); + sftp_pkt_free(pktin); + return -1; + } + + if (data.len > len) { + fxp_internal_error("READ returned more bytes than requested"); + sftp_pkt_free(pktin); + return -1; + } + + memcpy(buffer, data.ptr, data.len); + sftp_pkt_free(pktin); + return data.len; + } 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); + put_uint32(pktout, req->id); + put_string(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; + + i = get_uint32(pktin); + + /* + * 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 (get_err(pktin) || i > get_avail(pktin) / 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++) { + ret->names[i].filename = mkstr(get_string(pktin)); + ret->names[i].longname = mkstr(get_string(pktin)); + get_fxp_attrs(pktin, &ret->names[i].attrs); + } + + if (get_err(pktin)) { + fxp_internal_error("malformed FXP_NAME packet"); + for (i = 0; i < (unsigned long)ret->nnames; i++) { + sfree(ret->names[i].filename); + sfree(ret->names[i].longname); + } + sfree(ret->names); + sfree(ret); + sfree(pktin); + return NULL; + } + 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, + void *buffer, uint64_t offset, int len) +{ + struct sftp_request *req = sftp_alloc_request(); + struct sftp_packet *pktout; + + pktout = sftp_pkt_init(SSH_FXP_WRITE); + put_uint32(pktout, req->id); + put_string(pktout, handle->hstring, handle->hlen); + put_uint64(pktout, offset); + put_string(pktout, buffer, len); + sftp_send(pktout); + + return req; +} + +bool 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_t offset; + struct req *next, *prev; +}; + +struct fxp_xfer { + uint64_t offset, furthestdata, filesize; + int req_totalsize, req_maxsize; + bool eof, err; + struct fxp_handle *fh; + struct req *head, *tail; +}; + +static struct fxp_xfer *xfer_init(struct fxp_handle *fh, uint64_t 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 = false; + xfer->filesize = UINT64_MAX; + xfer->furthestdata = 0; + + return xfer; +} + +bool 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 += rr->len; + xfer->req_totalsize += rr->len; + +#ifdef DEBUG_DOWNLOAD + printf("queueing read request %p at %"PRIu64"\n", rr, rr->offset); +#endif + } +} + +struct fxp_xfer *xfer_download_init(struct fxp_handle *fh, uint64_t offset) +{ + struct fxp_xfer *xfer = xfer_init(fh, offset); + + xfer->eof = false; + xfer_download_queue(xfer); + + return xfer; +} + +/* + * Returns INT_MIN to indicate that it didn't even get as far as + * fxp_read_recv and hence has not freed pktin. + */ +int xfer_download_gotpkt(struct fxp_xfer *xfer, struct sftp_packet *pktin) +{ + struct sftp_request *rreq; + struct req *rr; + + rreq = sftp_find_request(pktin); + if (!rreq) + return INT_MIN; /* this packet doesn't even make sense */ + rr = (struct req *)fxp_get_userdata(rreq); + if (!rr) { + fxp_internal_error("request ID is not part of the current download"); + return INT_MIN; /* this packet isn't ours */ + } + rr->retlen = fxp_read_recv(pktin, rreq, rr->buffer, rr->len); +#ifdef DEBUG_DOWNLOAD + printf("read request %p has returned [%d]\n", rr, rr->retlen); +#endif + + if ((rr->retlen < 0 && fxp_error_type()==SSH_FX_EOF) || rr->retlen == 0) { + xfer->eof = true; + rr->retlen = 0; + 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 && xfer->furthestdata < rr->offset) { + xfer->furthestdata = rr->offset; +#ifdef DEBUG_DOWNLOAD + printf("setting furthestdata = %"PRIu64"\n", xfer->furthestdata); +#endif + } + + if (rr->retlen < rr->len) { + uint64_t filesize = rr->offset + (rr->retlen < 0 ? 0 : rr->retlen); +#ifdef DEBUG_DOWNLOAD + printf("short block! trying filesize = %"PRIu64"\n", filesize); +#endif + if (xfer->filesize > filesize) { + xfer->filesize = filesize; +#ifdef DEBUG_DOWNLOAD + printf("actually changing filesize\n"); +#endif + } + } + + if (xfer->furthestdata > xfer->filesize) { + 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 = true; +} + +bool 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 true; + } else + return false; +} + +struct fxp_xfer *xfer_upload_init(struct fxp_handle *fh, uint64_t 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 = true; + + return xfer; +} + +bool xfer_upload_ready(struct fxp_xfer *xfer) +{ + return sftp_sendbuffer() == 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 += rr->len; + xfer->req_totalsize += rr->len; + +#ifdef DEBUG_UPLOAD + printf("queueing write request %p at %"PRIu64" [len %d]\n", + rr, rr->offset, len); +#endif +} + +/* + * Returns INT_MIN to indicate that it didn't even get as far as + * fxp_write_recv and hence has not freed pktin. + */ +int xfer_upload_gotpkt(struct fxp_xfer *xfer, struct sftp_packet *pktin) +{ + struct sftp_request *rreq; + struct req *rr, *prev, *next; + bool ret; + + rreq = sftp_find_request(pktin); + if (!rreq) + return INT_MIN; /* this packet doesn't even make sense */ + rr = (struct req *)fxp_get_userdata(rreq); + if (!rr) { + fxp_internal_error("request ID is not part of the current upload"); + return INT_MIN; /* this packet isn't ours */ + } + ret = fxp_write_recv(pktin, rreq); +#ifdef DEBUG_UPLOAD + printf("write request %p has returned [%d]\n", rr, ret ? 1 : 0); +#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); +} diff --git a/ssh/sftp.h b/ssh/sftp.h new file mode 100644 index 00000000..5835c16b --- /dev/null +++ b/ssh/sftp.h @@ -0,0 +1,544 @@ +/* + * sftp.h: definitions for SFTP and the sftp.c routines. + */ + +#include "defs.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 + +#define PERMS_DIRECTORY 040000 + +/* + * 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 and then returns true, or it returns false + * for failure. + * + * sftp_senddata returns true on success, false on failure. + * + * sftp_sendbuffer returns the size of the backlog of data in the + * transmit queue. + */ +bool sftp_senddata(const char *data, size_t len); +size_t sftp_sendbuffer(void); +bool sftp_recvdata(char *data, size_t len); + +/* + * Free sftp_requests + */ +void sftp_cleanup_request(void); + +struct fxp_attrs { + unsigned long flags; + uint64_t size; + unsigned long uid; + unsigned long gid; + unsigned long permissions; + unsigned long atime; + unsigned long mtime; +}; +extern const struct fxp_attrs no_attrs; + +/* + * Copy between the possibly-unused permissions field in an fxp_attrs + * and a possibly-negative integer containing the same permissions. + */ +#define PUT_PERMISSIONS(attrs, perms) \ + ((perms) >= 0 ? \ + ((attrs).flags |= SSH_FILEXFER_ATTR_PERMISSIONS, \ + (attrs).permissions = (perms)) : \ + ((attrs).flags &= ~SSH_FILEXFER_ATTR_PERMISSIONS)) +#define GET_PERMISSIONS(attrs, defaultperms) \ + ((attrs).flags & SSH_FILEXFER_ATTR_PERMISSIONS ? \ + (attrs).permissions : defaultperms) + +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; + +/* + * Packet-manipulation functions. + */ + +struct sftp_packet { + char *data; + size_t length, maxlen, savedpos; + int type; + BinarySink_IMPLEMENTATION; + BinarySource_IMPLEMENTATION; +}; + +/* When sending a packet, create it with sftp_pkt_init, then add + * things to it by treating it as a BinarySink. When it's done, call + * sftp_send_prepare, and then pkt->data and pkt->length describe its + * wire format. */ +struct sftp_packet *sftp_pkt_init(int pkt_type); +void sftp_send_prepare(struct sftp_packet *pkt); + +/* When receiving a packet, create it with sftp_recv_prepare once you + * decode its length from the first 4 bytes of wire data. Then write + * that many bytes into pkt->data, and call sftp_recv_finish to set up + * the type code and BinarySource. */ +struct sftp_packet *sftp_recv_prepare(unsigned length); +bool sftp_recv_finish(struct sftp_packet *pkt); + +/* Either kind of packet can be freed afterwards with sftp_pkt_free. */ +void sftp_pkt_free(struct sftp_packet *pkt); + +void BinarySink_put_fxp_attrs(BinarySink *bs, struct fxp_attrs attrs); +bool BinarySource_get_fxp_attrs(BinarySource *src, struct fxp_attrs *attrs); +#define put_fxp_attrs(bs, attrs) \ + BinarySink_put_fxp_attrs(BinarySink_UPCAST(bs), attrs) +#define get_fxp_attrs(bs, attrs) \ + BinarySource_get_fxp_attrs(BinarySource_UPCAST(bs), attrs) + +/* + * Error handling. + */ + +const char *fxp_error(void); +int fxp_error_type(void); + +/* + * Perform exchange of init/version packets. Return false on failure. + */ +bool 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(const char *path); +char *fxp_realpath_recv(struct sftp_packet *pktin, struct sftp_request *req); + +/* + * Open a file. 'attrs' contains attributes to be applied to the file + * if it's being created. + */ +struct sftp_request *fxp_open_send(const char *path, int type, + const struct fxp_attrs *attrs); +struct fxp_handle *fxp_open_recv(struct sftp_packet *pktin, + struct sftp_request *req); + +/* + * Open a directory. + */ +struct sftp_request *fxp_opendir_send(const char *path); +struct fxp_handle *fxp_opendir_recv(struct sftp_packet *pktin, + struct sftp_request *req); + +/* + * Close a file/dir. Returns true on success, false on error. + */ +struct sftp_request *fxp_close_send(struct fxp_handle *handle); +bool fxp_close_recv(struct sftp_packet *pktin, struct sftp_request *req); + +/* + * Make a directory. + */ +struct sftp_request *fxp_mkdir_send(const char *path, + const struct fxp_attrs *attrs); +bool fxp_mkdir_recv(struct sftp_packet *pktin, struct sftp_request *req); + +/* + * Remove a directory. + */ +struct sftp_request *fxp_rmdir_send(const char *path); +bool fxp_rmdir_recv(struct sftp_packet *pktin, struct sftp_request *req); + +/* + * Remove a file. + */ +struct sftp_request *fxp_remove_send(const char *fname); +bool fxp_remove_recv(struct sftp_packet *pktin, struct sftp_request *req); + +/* + * Rename a file. + */ +struct sftp_request *fxp_rename_send(const char *srcfname, + const char *dstfname); +bool fxp_rename_recv(struct sftp_packet *pktin, struct sftp_request *req); + +/* + * Return file attributes. + */ +struct sftp_request *fxp_stat_send(const char *fname); +bool 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); +bool fxp_fstat_recv(struct sftp_packet *pktin, struct sftp_request *req, + struct fxp_attrs *attrs); + +/* + * Set file attributes. + */ +struct sftp_request *fxp_setstat_send(const char *fname, + struct fxp_attrs attrs); +bool 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); +bool 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_t offset, int len); +int fxp_read_recv(struct sftp_packet *pktin, struct sftp_request *req, + char *buffer, int len); + +/* + * Write to a file. + */ +struct sftp_request *fxp_write_send(struct fxp_handle *handle, + void *buffer, uint64_t offset, int len); +bool 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_t offset); +void xfer_download_queue(struct fxp_xfer *xfer); +int xfer_download_gotpkt(struct fxp_xfer *xfer, struct sftp_packet *pktin); +bool xfer_download_data(struct fxp_xfer *xfer, void **buf, int *len); + +struct fxp_xfer *xfer_upload_init(struct fxp_handle *fh, uint64_t offset); +bool 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); + +bool xfer_done(struct fxp_xfer *xfer); +void xfer_set_error(struct fxp_xfer *xfer); +void xfer_cleanup(struct fxp_xfer *xfer); + +/* + * Vtable for the platform-specific filesystem implementation that + * answers requests in an SFTP server. + */ +typedef struct SftpReplyBuilder SftpReplyBuilder; +struct SftpServer { + const SftpServerVtable *vt; +}; +struct SftpServerVtable { + SftpServer *(*new)(const SftpServerVtable *vt); + void (*free)(SftpServer *srv); + + /* + * Handle actual filesystem requests. + * + * Each of these functions replies by calling an appropiate + * sftp_reply_foo() function on the given reply packet. + */ + + /* Should call fxp_reply_error or fxp_reply_simple_name */ + void (*realpath)(SftpServer *srv, SftpReplyBuilder *reply, + ptrlen path); + + /* Should call fxp_reply_error or fxp_reply_handle */ + void (*open)(SftpServer *srv, SftpReplyBuilder *reply, + ptrlen path, unsigned flags, struct fxp_attrs attrs); + + /* Should call fxp_reply_error or fxp_reply_handle */ + void (*opendir)(SftpServer *srv, SftpReplyBuilder *reply, + ptrlen path); + + /* Should call fxp_reply_error or fxp_reply_ok */ + void (*close)(SftpServer *srv, SftpReplyBuilder *reply, ptrlen handle); + + /* Should call fxp_reply_error or fxp_reply_ok */ + void (*mkdir)(SftpServer *srv, SftpReplyBuilder *reply, + ptrlen path, struct fxp_attrs attrs); + + /* Should call fxp_reply_error or fxp_reply_ok */ + void (*rmdir)(SftpServer *srv, SftpReplyBuilder *reply, ptrlen path); + + /* Should call fxp_reply_error or fxp_reply_ok */ + void (*remove)(SftpServer *srv, SftpReplyBuilder *reply, ptrlen path); + + /* Should call fxp_reply_error or fxp_reply_ok */ + void (*rename)(SftpServer *srv, SftpReplyBuilder *reply, + ptrlen srcpath, ptrlen dstpath); + + /* Should call fxp_reply_error or fxp_reply_attrs */ + void (*stat)(SftpServer *srv, SftpReplyBuilder *reply, ptrlen path, + bool follow_symlinks); + + /* Should call fxp_reply_error or fxp_reply_attrs */ + void (*fstat)(SftpServer *srv, SftpReplyBuilder *reply, ptrlen handle); + + /* Should call fxp_reply_error or fxp_reply_ok */ + void (*setstat)(SftpServer *srv, SftpReplyBuilder *reply, + ptrlen path, struct fxp_attrs attrs); + + /* Should call fxp_reply_error or fxp_reply_ok */ + void (*fsetstat)(SftpServer *srv, SftpReplyBuilder *reply, + ptrlen handle, struct fxp_attrs attrs); + + /* Should call fxp_reply_error or fxp_reply_data */ + void (*read)(SftpServer *srv, SftpReplyBuilder *reply, + ptrlen handle, uint64_t offset, unsigned length); + + /* Should call fxp_reply_error or fxp_reply_ok */ + void (*write)(SftpServer *srv, SftpReplyBuilder *reply, + ptrlen handle, uint64_t offset, ptrlen data); + + /* Should call fxp_reply_error, or fxp_reply_name_count once and + * then fxp_reply_full_name that many times */ + void (*readdir)(SftpServer *srv, SftpReplyBuilder *reply, ptrlen handle, + int max_entries, bool omit_longname); +}; + +static inline SftpServer *sftpsrv_new(const SftpServerVtable *vt) +{ return vt->new(vt); } +static inline void sftpsrv_free(SftpServer *srv) +{ srv->vt->free(srv); } +static inline void sftpsrv_realpath(SftpServer *srv, SftpReplyBuilder *reply, + ptrlen path) +{ srv->vt->realpath(srv, reply, path); } +static inline void sftpsrv_open( + SftpServer *srv, SftpReplyBuilder *reply, + ptrlen path, unsigned flags, struct fxp_attrs attrs) +{ srv->vt->open(srv, reply, path, flags, attrs); } +static inline void sftpsrv_opendir( + SftpServer *srv, SftpReplyBuilder *reply, ptrlen path) +{ srv->vt->opendir(srv, reply, path); } +static inline void sftpsrv_close( + SftpServer *srv, SftpReplyBuilder *reply, ptrlen handle) +{ srv->vt->close(srv, reply, handle); } +static inline void sftpsrv_mkdir(SftpServer *srv, SftpReplyBuilder *reply, + ptrlen path, struct fxp_attrs attrs) +{ srv->vt->mkdir(srv, reply, path, attrs); } +static inline void sftpsrv_rmdir( + SftpServer *srv, SftpReplyBuilder *reply, ptrlen path) +{ srv->vt->rmdir(srv, reply, path); } +static inline void sftpsrv_remove( + SftpServer *srv, SftpReplyBuilder *reply, ptrlen path) +{ srv->vt->remove(srv, reply, path); } +static inline void sftpsrv_rename(SftpServer *srv, SftpReplyBuilder *reply, + ptrlen srcpath, ptrlen dstpath) +{ srv->vt->rename(srv, reply, srcpath, dstpath); } +static inline void sftpsrv_stat( + SftpServer *srv, SftpReplyBuilder *reply, ptrlen path, bool follow) +{ srv->vt->stat(srv, reply, path, follow); } +static inline void sftpsrv_fstat( + SftpServer *srv, SftpReplyBuilder *reply, ptrlen handle) +{ srv->vt->fstat(srv, reply, handle); } +static inline void sftpsrv_setstat(SftpServer *srv, SftpReplyBuilder *reply, + ptrlen path, struct fxp_attrs attrs) +{ srv->vt->setstat(srv, reply, path, attrs); } +static inline void sftpsrv_fsetstat(SftpServer *srv, SftpReplyBuilder *reply, + ptrlen handle, struct fxp_attrs attrs) +{ srv->vt->fsetstat(srv, reply, handle, attrs); } +static inline void sftpsrv_read( + SftpServer *srv, SftpReplyBuilder *reply, + ptrlen handle, uint64_t offset, unsigned length) +{ srv->vt->read(srv, reply, handle, offset, length); } +static inline void sftpsrv_write(SftpServer *srv, SftpReplyBuilder *reply, + ptrlen handle, uint64_t offset, ptrlen data) +{ srv->vt->write(srv, reply, handle, offset, data); } +static inline void sftpsrv_readdir( + SftpServer *srv, SftpReplyBuilder *reply, ptrlen handle, + int max_entries, bool omit_longname) +{ srv->vt->readdir(srv, reply, handle, max_entries, omit_longname); } + +typedef struct SftpReplyBuilderVtable SftpReplyBuilderVtable; +struct SftpReplyBuilder { + const SftpReplyBuilderVtable *vt; +}; +struct SftpReplyBuilderVtable { + void (*reply_ok)(SftpReplyBuilder *reply); + void (*reply_error)(SftpReplyBuilder *reply, unsigned code, + const char *msg); + void (*reply_simple_name)(SftpReplyBuilder *reply, ptrlen name); + void (*reply_name_count)(SftpReplyBuilder *reply, unsigned count); + void (*reply_full_name)(SftpReplyBuilder *reply, ptrlen name, + ptrlen longname, struct fxp_attrs attrs); + void (*reply_handle)(SftpReplyBuilder *reply, ptrlen handle); + void (*reply_data)(SftpReplyBuilder *reply, ptrlen data); + void (*reply_attrs)(SftpReplyBuilder *reply, struct fxp_attrs attrs); +}; + +static inline void fxp_reply_ok(SftpReplyBuilder *reply) +{ reply->vt->reply_ok(reply); } +static inline void fxp_reply_error(SftpReplyBuilder *reply, unsigned code, + const char *msg) +{ reply->vt->reply_error(reply, code, msg); } +static inline void fxp_reply_simple_name(SftpReplyBuilder *reply, ptrlen name) +{ reply->vt->reply_simple_name(reply, name); } +static inline void fxp_reply_name_count( + SftpReplyBuilder *reply, unsigned count) +{ reply->vt->reply_name_count(reply, count); } +static inline void fxp_reply_full_name(SftpReplyBuilder *reply, ptrlen name, + ptrlen longname, struct fxp_attrs attrs) +{ reply->vt->reply_full_name(reply, name, longname, attrs); } +static inline void fxp_reply_handle(SftpReplyBuilder *reply, ptrlen handle) +{ reply->vt->reply_handle(reply, handle); } +static inline void fxp_reply_data(SftpReplyBuilder *reply, ptrlen data) +{ reply->vt->reply_data(reply, data); } +static inline void fxp_reply_attrs( + SftpReplyBuilder *reply, struct fxp_attrs attrs) +{ reply->vt->reply_attrs(reply, attrs); } + +/* + * The usual implementation of an SftpReplyBuilder, containing a + * 'struct sftp_packet' which is assumed to be already initialised + * before one of the above request methods is called. + */ +extern const struct SftpReplyBuilderVtable DefaultSftpReplyBuilder_vt; +typedef struct DefaultSftpReplyBuilder DefaultSftpReplyBuilder; +struct DefaultSftpReplyBuilder { + SftpReplyBuilder rb; + struct sftp_packet *pkt; +}; + +/* + * The top-level function that handles an SFTP request, given an + * implementation of the above SftpServer abstraction to do the actual + * filesystem work. It handles all the marshalling and unmarshalling + * of packets, and the copying of request ids into the responses. + */ +struct sftp_packet *sftp_handle_request( + SftpServer *srv, struct sftp_packet *request); + +/* ---------------------------------------------------------------------- + * Not exactly SFTP-related, but here's a system that implements an + * old-fashioned SCP server module, given an SftpServer vtable to use + * as its underlying filesystem access. + */ + +typedef struct ScpServer ScpServer; +typedef struct ScpServerVtable ScpServerVtable; +struct ScpServer { + const struct ScpServerVtable *vt; +}; +struct ScpServerVtable { + void (*free)(ScpServer *s); + + size_t (*send)(ScpServer *s, const void *data, size_t length); + void (*throttle)(ScpServer *s, bool throttled); + void (*eof)(ScpServer *s); +}; + +static inline void scp_free(ScpServer *s) +{ s->vt->free(s); } +static inline size_t scp_send(ScpServer *s, const void *data, size_t length) +{ return s->vt->send(s, data, length); } +static inline void scp_throttle(ScpServer *s, bool throttled) +{ s->vt->throttle(s, throttled); } +static inline void scp_eof(ScpServer *s) +{ s->vt->eof(s); } + +/* + * Create an ScpServer by calling this function, giving it the command + * you received from the SSH client to execute. If that command is + * recognised as an scp command, it will construct an ScpServer object + * and return it; otherwise, it will return NULL, and you should + * execute the command in whatever way you normally would. + * + * The ScpServer will generate output for the client by writing it to + * the provided SshChannel using sshfwd_write; you pass it input using + * the send method in its own vtable. + */ +ScpServer *scp_recognise_exec( + SshChannel *sc, const SftpServerVtable *sftpserver_vt, ptrlen command); diff --git a/ssh/sftpcommon.c b/ssh/sftpcommon.c new file mode 100644 index 00000000..f2f9a3bc --- /dev/null +++ b/ssh/sftpcommon.c @@ -0,0 +1,139 @@ +/* + * sftpcommon.c: SFTP code shared between client and server. + */ + +#include +#include +#include +#include +#include + +#include "misc.h" +#include "sftp.h" + +static void sftp_pkt_BinarySink_write( + BinarySink *bs, const void *data, size_t length) +{ + struct sftp_packet *pkt = BinarySink_DOWNCAST(bs, struct sftp_packet); + + assert(length <= 0xFFFFFFFFU - pkt->length); + + sgrowarrayn_nm(pkt->data, pkt->maxlen, pkt->length, length); + memcpy(pkt->data + pkt->length, data, length); + pkt->length += length; +} + +struct sftp_packet *sftp_pkt_init(int type) +{ + struct sftp_packet *pkt; + pkt = snew(struct sftp_packet); + pkt->data = NULL; + pkt->savedpos = -1; + pkt->length = 0; + pkt->maxlen = 0; + pkt->type = type; + BinarySink_INIT(pkt, sftp_pkt_BinarySink_write); + put_uint32(pkt, 0); /* length field will be filled in later */ + put_byte(pkt, 0); /* so will the type field */ + return pkt; +} + +void BinarySink_put_fxp_attrs(BinarySink *bs, struct fxp_attrs attrs) +{ + put_uint32(bs, attrs.flags); + if (attrs.flags & SSH_FILEXFER_ATTR_SIZE) + put_uint64(bs, attrs.size); + if (attrs.flags & SSH_FILEXFER_ATTR_UIDGID) { + put_uint32(bs, attrs.uid); + put_uint32(bs, attrs.gid); + } + if (attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) { + put_uint32(bs, attrs.permissions); + } + if (attrs.flags & SSH_FILEXFER_ATTR_ACMODTIME) { + put_uint32(bs, attrs.atime); + put_uint32(bs, attrs.mtime); + } + if (attrs.flags & SSH_FILEXFER_ATTR_EXTENDED) { + /* + * We currently don't support sending any extended + * attributes. + */ + } +} + +const struct fxp_attrs no_attrs = { 0 }; + +#define put_fxp_attrs(bs, attrs) \ + BinarySink_put_fxp_attrs(BinarySink_UPCAST(bs), attrs) + +bool BinarySource_get_fxp_attrs(BinarySource *src, struct fxp_attrs *attrs) +{ + attrs->flags = get_uint32(src); + if (attrs->flags & SSH_FILEXFER_ATTR_SIZE) + attrs->size = get_uint64(src); + if (attrs->flags & SSH_FILEXFER_ATTR_UIDGID) { + attrs->uid = get_uint32(src); + attrs->gid = get_uint32(src); + } + if (attrs->flags & SSH_FILEXFER_ATTR_PERMISSIONS) + attrs->permissions = get_uint32(src); + if (attrs->flags & SSH_FILEXFER_ATTR_ACMODTIME) { + attrs->atime = get_uint32(src); + attrs->mtime = get_uint32(src); + } + if (attrs->flags & SSH_FILEXFER_ATTR_EXTENDED) { + unsigned long count = get_uint32(src); + while (count--) { + if (get_err(src)) { + /* Truncated packet. Don't waste time looking for + * attributes that aren't there. Caller should spot + * the truncation. */ + break; + } + /* + * We should try to analyse these, if we ever find one + * we recognise. + */ + get_string(src); + get_string(src); + } + } + return true; +} + +void sftp_pkt_free(struct sftp_packet *pkt) +{ + if (pkt->data) + sfree(pkt->data); + sfree(pkt); +} + +void sftp_send_prepare(struct sftp_packet *pkt) +{ + PUT_32BIT_MSB_FIRST(pkt->data, pkt->length - 4); + if (pkt->length >= 5) { + /* Rewrite the type code, in case the caller changed its mind + * about pkt->type since calling sftp_pkt_init */ + pkt->data[4] = pkt->type; + } +} + +struct sftp_packet *sftp_recv_prepare(unsigned length) +{ + struct sftp_packet *pkt; + + pkt = snew(struct sftp_packet); + pkt->savedpos = 0; + pkt->length = pkt->maxlen = length; + pkt->data = snewn(pkt->length, char); + + return pkt; +} + +bool sftp_recv_finish(struct sftp_packet *pkt) +{ + BinarySource_INIT(pkt, pkt->data, pkt->length); + pkt->type = get_byte(pkt); + return !get_err(pkt); +} diff --git a/ssh/sftpserver.c b/ssh/sftpserver.c new file mode 100644 index 00000000..ba216872 --- /dev/null +++ b/ssh/sftpserver.c @@ -0,0 +1,279 @@ +/* + * Implement the centralised parts of the server side of SFTP. + */ + +#include +#include +#include + +#include "putty.h" +#include "ssh.h" +#include "sftp.h" + +struct sftp_packet *sftp_handle_request( + SftpServer *srv, struct sftp_packet *req) +{ + struct sftp_packet *reply; + unsigned id; + uint32_t flags; + ptrlen path, dstpath, handle, data; + uint64_t offset; + unsigned length; + struct fxp_attrs attrs; + DefaultSftpReplyBuilder dsrb; + SftpReplyBuilder *rb; + + if (req->type == SSH_FXP_INIT) { + /* + * Special case which doesn't have a request id at the start. + */ + reply = sftp_pkt_init(SSH_FXP_VERSION); + /* + * Since we support only the lowest protocol version, we don't + * need to take the min of this and the client's version, or + * even to bother reading the client version number out of the + * input packet. + */ + put_uint32(reply, SFTP_PROTO_VERSION); + return reply; + } + + /* + * Centralise the request id handling. We'll overwrite the type + * code of the output packet later. + */ + id = get_uint32(req); + reply = sftp_pkt_init(0); + put_uint32(reply, id); + + dsrb.rb.vt = &DefaultSftpReplyBuilder_vt; + dsrb.pkt = reply; + rb = &dsrb.rb; + + switch (req->type) { + case SSH_FXP_REALPATH: + path = get_string(req); + if (get_err(req)) + goto decode_error; + sftpsrv_realpath(srv, rb, path); + break; + + case SSH_FXP_OPEN: + path = get_string(req); + flags = get_uint32(req); + get_fxp_attrs(req, &attrs); + if (get_err(req)) + goto decode_error; + if ((flags & (SSH_FXF_READ|SSH_FXF_WRITE)) == 0) { + fxp_reply_error(rb, SSH_FX_BAD_MESSAGE, + "open without READ or WRITE flag"); + } else if ((flags & (SSH_FXF_CREAT|SSH_FXF_TRUNC)) == SSH_FXF_TRUNC) { + fxp_reply_error(rb, SSH_FX_BAD_MESSAGE, + "open with TRUNC but not CREAT"); + } else if ((flags & (SSH_FXF_CREAT|SSH_FXF_EXCL)) == SSH_FXF_EXCL) { + fxp_reply_error(rb, SSH_FX_BAD_MESSAGE, + "open with EXCL but not CREAT"); + } else { + sftpsrv_open(srv, rb, path, flags, attrs); + } + break; + + case SSH_FXP_OPENDIR: + path = get_string(req); + if (get_err(req)) + goto decode_error; + sftpsrv_opendir(srv, rb, path); + break; + + case SSH_FXP_CLOSE: + handle = get_string(req); + if (get_err(req)) + goto decode_error; + sftpsrv_close(srv, rb, handle); + break; + + case SSH_FXP_MKDIR: + path = get_string(req); + get_fxp_attrs(req, &attrs); + if (get_err(req)) + goto decode_error; + sftpsrv_mkdir(srv, rb, path, attrs); + break; + + case SSH_FXP_RMDIR: + path = get_string(req); + if (get_err(req)) + goto decode_error; + sftpsrv_rmdir(srv, rb, path); + break; + + case SSH_FXP_REMOVE: + path = get_string(req); + if (get_err(req)) + goto decode_error; + sftpsrv_remove(srv, rb, path); + break; + + case SSH_FXP_RENAME: + path = get_string(req); + dstpath = get_string(req); + if (get_err(req)) + goto decode_error; + sftpsrv_rename(srv, rb, path, dstpath); + break; + + case SSH_FXP_STAT: + path = get_string(req); + if (get_err(req)) + goto decode_error; + sftpsrv_stat(srv, rb, path, true); + break; + + case SSH_FXP_LSTAT: + path = get_string(req); + if (get_err(req)) + goto decode_error; + sftpsrv_stat(srv, rb, path, false); + break; + + case SSH_FXP_FSTAT: + handle = get_string(req); + if (get_err(req)) + goto decode_error; + sftpsrv_fstat(srv, rb, handle); + break; + + case SSH_FXP_SETSTAT: + path = get_string(req); + get_fxp_attrs(req, &attrs); + if (get_err(req)) + goto decode_error; + sftpsrv_setstat(srv, rb, path, attrs); + break; + + case SSH_FXP_FSETSTAT: + handle = get_string(req); + get_fxp_attrs(req, &attrs); + if (get_err(req)) + goto decode_error; + sftpsrv_fsetstat(srv, rb, handle, attrs); + break; + + case SSH_FXP_READ: + handle = get_string(req); + offset = get_uint64(req); + length = get_uint32(req); + if (get_err(req)) + goto decode_error; + sftpsrv_read(srv, rb, handle, offset, length); + break; + + case SSH_FXP_READDIR: + handle = get_string(req); + if (get_err(req)) + goto decode_error; + sftpsrv_readdir(srv, rb, handle, INT_MAX, false); + break; + + case SSH_FXP_WRITE: + handle = get_string(req); + offset = get_uint64(req); + data = get_string(req); + if (get_err(req)) + goto decode_error; + sftpsrv_write(srv, rb, handle, offset, data); + break; + + default: + if (get_err(req)) + goto decode_error; + fxp_reply_error(rb, SSH_FX_OP_UNSUPPORTED, + "Unrecognised request type"); + break; + + decode_error: + fxp_reply_error(rb, SSH_FX_BAD_MESSAGE, "Unable to decode request"); + } + + return reply; +} + +static void default_reply_ok(SftpReplyBuilder *reply) +{ + DefaultSftpReplyBuilder *d = + container_of(reply, DefaultSftpReplyBuilder, rb); + d->pkt->type = SSH_FXP_STATUS; + put_uint32(d->pkt, SSH_FX_OK); + put_stringz(d->pkt, ""); +} + +static void default_reply_error( + SftpReplyBuilder *reply, unsigned code, const char *msg) +{ + DefaultSftpReplyBuilder *d = + container_of(reply, DefaultSftpReplyBuilder, rb); + d->pkt->type = SSH_FXP_STATUS; + put_uint32(d->pkt, code); + put_stringz(d->pkt, msg); +} + +static void default_reply_name_count(SftpReplyBuilder *reply, unsigned count) +{ + DefaultSftpReplyBuilder *d = + container_of(reply, DefaultSftpReplyBuilder, rb); + d->pkt->type = SSH_FXP_NAME; + put_uint32(d->pkt, count); +} + +static void default_reply_full_name(SftpReplyBuilder *reply, ptrlen name, + ptrlen longname, struct fxp_attrs attrs) +{ + DefaultSftpReplyBuilder *d = + container_of(reply, DefaultSftpReplyBuilder, rb); + d->pkt->type = SSH_FXP_NAME; + put_stringpl(d->pkt, name); + put_stringpl(d->pkt, longname); + put_fxp_attrs(d->pkt, attrs); +} + +static void default_reply_simple_name(SftpReplyBuilder *reply, ptrlen name) +{ + fxp_reply_name_count(reply, 1); + fxp_reply_full_name(reply, name, PTRLEN_LITERAL(""), no_attrs); +} + +static void default_reply_handle(SftpReplyBuilder *reply, ptrlen handle) +{ + DefaultSftpReplyBuilder *d = + container_of(reply, DefaultSftpReplyBuilder, rb); + d->pkt->type = SSH_FXP_HANDLE; + put_stringpl(d->pkt, handle); +} + +static void default_reply_data(SftpReplyBuilder *reply, ptrlen data) +{ + DefaultSftpReplyBuilder *d = + container_of(reply, DefaultSftpReplyBuilder, rb); + d->pkt->type = SSH_FXP_DATA; + put_stringpl(d->pkt, data); +} + +static void default_reply_attrs( + SftpReplyBuilder *reply, struct fxp_attrs attrs) +{ + DefaultSftpReplyBuilder *d = + container_of(reply, DefaultSftpReplyBuilder, rb); + d->pkt->type = SSH_FXP_ATTRS; + put_fxp_attrs(d->pkt, attrs); +} + +const SftpReplyBuilderVtable DefaultSftpReplyBuilder_vt = { + .reply_ok = default_reply_ok, + .reply_error = default_reply_error, + .reply_simple_name = default_reply_simple_name, + .reply_name_count = default_reply_name_count, + .reply_full_name = default_reply_full_name, + .reply_handle = default_reply_handle, + .reply_data = default_reply_data, + .reply_attrs = default_reply_attrs, +}; diff --git a/ssh/sharing.c b/ssh/sharing.c new file mode 100644 index 00000000..b5da8657 --- /dev/null +++ b/ssh/sharing.c @@ -0,0 +1,2180 @@ +/* + * Support for SSH connection sharing, i.e. permitting one PuTTY to + * open its own channels over the SSH session being run by another. + */ + +/* + * Discussion and technical documentation + * ====================================== + * + * The basic strategy for PuTTY's implementation of SSH connection + * sharing is to have a single 'upstream' PuTTY process, which manages + * the real SSH connection and all the cryptography, and then zero or + * more 'downstream' PuTTYs, which never talk to the real host but + * only talk to the upstream through local IPC (Unix-domain sockets or + * Windows named pipes). + * + * The downstreams communicate with the upstream using a protocol + * derived from SSH itself, which I'll document in detail below. In + * brief, though: the downstream->upstream protocol uses a trivial + * binary packet protocol (just length/type/data) to encapsulate + * unencrypted SSH messages, and downstreams talk to the upstream more + * or less as if it was an SSH server itself. (So downstreams can + * themselves open multiple SSH channels, for example, by sending + * multiple SSH2_MSG_CHANNEL_OPENs; they can send CHANNEL_REQUESTs of + * their choice within each channel, and they handle their own + * WINDOW_ADJUST messages.) + * + * The upstream would ideally handle these downstreams by just putting + * their messages into the queue for proper SSH-2 encapsulation and + * encryption and sending them straight on to the server. However, + * that's not quite feasible as written, because client-side channel + * IDs could easily conflict (between multiple downstreams, or between + * a downstream and the upstream). To protect against that, the + * upstream rewrites the client-side channel IDs in messages it passes + * on to the server, so that it's performing what you might describe + * as 'channel-number NAT'. Then the upstream remembers which of its + * own channel IDs are channels it's managing itself, and which are + * placeholders associated with a particular downstream, so that when + * replies come in from the server they can be sent on to the relevant + * downstream (after un-NATting the channel number, of course). + * + * Global requests from downstreams are only accepted if the upstream + * knows what to do about them; currently the only such requests are + * the ones having to do with remote-to-local port forwarding (in + * which, again, the upstream remembers that some of the forwardings + * it's asked the server to set up were on behalf of particular + * downstreams, and sends the incoming CHANNEL_OPENs to those + * downstreams when connections come in). + * + * Other fiddly pieces of this mechanism are X forwarding and + * (OpenSSH-style) agent forwarding. Both of these have a fundamental + * problem arising from the protocol design: that the CHANNEL_OPEN + * from the server introducing a forwarded connection does not carry + * any indication of which session channel gave rise to it; so if + * session channels from multiple downstreams enable those forwarding + * methods, it's hard for the upstream to know which downstream to + * send the resulting connections back to. + * + * For X forwarding, we can work around this in a really painful way + * by using the fake X11 authorisation data sent to the server as part + * of the forwarding setup: upstream ensures that every X forwarding + * request carries distinguishable fake auth data, and then when X + * connections come in it waits to see the auth data in the X11 setup + * message before it decides which downstream to pass the connection + * on to. + * + * For agent forwarding, that workaround is unavailable. As a result, + * this system (and, as far as I can think of, any other system too) + * has the fundamental constraint that it can only forward one SSH + * agent - it can't forward two agents to different session channels. + * So downstreams can request agent forwarding if they like, but if + * they do, they'll get whatever SSH agent is known to the upstream + * (if any) forwarded to their sessions. + * + * Downstream-to-upstream protocol + * ------------------------------- + * + * Here I document in detail the protocol spoken between PuTTY + * downstreams and upstreams over local IPC. The IPC mechanism can + * vary between host platforms, but the protocol is the same. + * + * The protocol commences with a version exchange which is exactly + * like the SSH-2 one, in that each side sends a single line of text + * of the form + * + * -- [comments] \r\n + * + * The only difference is that in real SSH-2, is the string + * "SSH", whereas in this protocol the string is + * "SSHCONNECTION@putty.projects.tartarus.org". + * + * (The SSH RFCs allow many protocol-level identifier namespaces to be + * extended by implementors without central standardisation as long as + * they suffix "@" and a domain name they control to their new ids. + * RFC 4253 does not define this particular name to be changeable at + * all, but I like to think this is obviously how it would have done + * so if the working group had foreseen the need :-) + * + * Thereafter, all data exchanged consists of a sequence of binary + * packets concatenated end-to-end, each of which is of the form + * + * uint32 length of packet, N + * byte[N] N bytes of packet data + * + * and, since these are SSH-2 messages, the first data byte is taken + * to be the packet type code. + * + * These messages are interpreted as those of an SSH connection, after + * userauth completes, and without any repeat key exchange. + * Specifically, any message from the SSH Connection Protocol is + * permitted, and also SSH_MSG_IGNORE, SSH_MSG_DEBUG, + * SSH_MSG_DISCONNECT and SSH_MSG_UNIMPLEMENTED from the SSH Transport + * Protocol. + * + * This protocol imposes a few additional requirements, over and above + * those of the standard SSH Connection Protocol: + * + * Message sizes are not permitted to exceed 0x4010 (16400) bytes, + * including their length header. + * + * When the server (i.e. really the PuTTY upstream) sends + * SSH_MSG_CHANNEL_OPEN with channel type "x11", and the client + * (downstream) responds with SSH_MSG_CHANNEL_OPEN_CONFIRMATION, that + * confirmation message MUST include an initial window size of at + * least 256. (Rationale: this is a bit of a fudge which makes it + * easier, by eliminating the possibility of nasty edge cases, for an + * upstream to arrange not to pass the CHANNEL_OPEN on to downstream + * until after it's seen the X11 auth data to decide which downstream + * it needs to go to.) + */ + +#include +#include +#include +#include +#include + +#include "putty.h" +#include "tree234.h" +#include "ssh.h" +#include "sshcr.h" + +struct ssh_sharing_state { + char *sockname; /* the socket name, kept for cleanup */ + Socket *listensock; /* the master listening Socket */ + tree234 *connections; /* holds ssh_sharing_connstates */ + unsigned nextid; /* preferred id for next connstate */ + ConnectionLayer *cl; /* instance of the ssh connection layer */ + char *server_verstring; /* server version string after "SSH-" */ + + Plug plug; +}; + +struct share_globreq; + +struct ssh_sharing_connstate { + unsigned id; /* used to identify this downstream in log messages */ + + Socket *sock; /* the Socket for this connection */ + struct ssh_sharing_state *parent; + + int crLine; /* coroutine state for share_receive */ + + bool sent_verstring, got_verstring; + int curr_packetlen; + + unsigned char recvbuf[0x4010]; + size_t recvlen; + + /* + * Assorted state we have to remember about this downstream, so + * that we can clean it up appropriately when the downstream goes + * away. + */ + + /* Channels which don't have a downstream id, i.e. we've passed a + * CHANNEL_OPEN down from the server but not had an + * OPEN_CONFIRMATION or OPEN_FAILURE back. If downstream goes + * away, we respond to all of these with OPEN_FAILURE. */ + tree234 *halfchannels; /* stores 'struct share_halfchannel' */ + + /* Channels which do have a downstream id. We need to index these + * by both server id and upstream id, so we can find a channel + * when handling either an upward or a downward message referring + * to it. */ + tree234 *channels_by_us; /* stores 'struct share_channel' */ + tree234 *channels_by_server; /* stores 'struct share_channel' */ + + /* Another class of channel which doesn't have a downstream id. + * The difference between these and halfchannels is that xchannels + * do have an *upstream* id, because upstream has already accepted + * the channel request from the server. This arises in the case of + * X forwarding, where we have to accept the request and read the + * X authorisation data before we know whether the channel needs + * to be forwarded to a downstream. */ + tree234 *xchannels_by_us; /* stores 'struct share_xchannel' */ + tree234 *xchannels_by_server; /* stores 'struct share_xchannel' */ + + /* Remote port forwarding requests in force. */ + tree234 *forwardings; /* stores 'struct share_forwarding' */ + + /* Global requests we've sent on to the server, pending replies. */ + struct share_globreq *globreq_head, *globreq_tail; + + Plug plug; +}; + +struct share_halfchannel { + unsigned server_id; +}; + +/* States of a share_channel. */ +enum { + OPEN, + SENT_CLOSE, + RCVD_CLOSE, + /* Downstream has sent CHANNEL_OPEN but server hasn't replied yet. + * If downstream goes away when a channel is in this state, we + * must wait for the server's response before starting to send + * CLOSE. Channels in this state are also not held in + * channels_by_server, because their server_id field is + * meaningless. */ + UNACKNOWLEDGED +}; + +struct share_channel { + unsigned downstream_id, upstream_id, server_id; + int downstream_maxpkt; + int state; + /* + * Some channels (specifically, channels on which downstream has + * sent "x11-req") have the additional function of storing a set + * of downstream X authorisation data and a handle to an upstream + * fake set. + */ + struct X11FakeAuth *x11_auth_upstream; + int x11_auth_proto; + char *x11_auth_data; + int x11_auth_datalen; + bool x11_one_shot; +}; + +struct share_forwarding { + char *host; + int port; + bool active; /* has the server sent REQUEST_SUCCESS? */ + struct ssh_rportfwd *rpf; +}; + +struct share_xchannel_message { + struct share_xchannel_message *next; + int type; + unsigned char *data; + int datalen; +}; + +struct share_xchannel { + unsigned upstream_id, server_id; + + /* + * xchannels come in two flavours: live and dead. Live ones are + * waiting for an OPEN_CONFIRMATION or OPEN_FAILURE from + * downstream; dead ones have had an OPEN_FAILURE, so they only + * exist as a means of letting us conveniently respond to further + * channel messages from the server until such time as the server + * sends us CHANNEL_CLOSE. + */ + bool live; + + /* + * When we receive OPEN_CONFIRMATION, we will need to send a + * WINDOW_ADJUST to the server to synchronise the windows. For + * this purpose we need to know what window we have so far offered + * the server. We record this as exactly the value in the + * OPEN_CONFIRMATION that upstream sent us, adjusted by the amount + * by which the two X greetings differed in length. + */ + int window; + + /* + * Linked list of SSH messages from the server relating to this + * channel, which we queue up until downstream sends us an + * OPEN_CONFIRMATION and we can belatedly send them all on. + */ + struct share_xchannel_message *msghead, *msgtail; +}; + +enum { + GLOBREQ_TCPIP_FORWARD, + GLOBREQ_CANCEL_TCPIP_FORWARD +}; + +struct share_globreq { + struct share_globreq *next; + int type; + bool want_reply; + struct share_forwarding *fwd; +}; + +static int share_connstate_cmp(void *av, void *bv) +{ + const struct ssh_sharing_connstate *a = + (const struct ssh_sharing_connstate *)av; + const struct ssh_sharing_connstate *b = + (const struct ssh_sharing_connstate *)bv; + + if (a->id < b->id) + return -1; + else if (a->id > b->id) + return +1; + else + return 0; +} + +static unsigned share_find_unused_id +(struct ssh_sharing_state *sharestate, unsigned first) +{ + int low_orig, low, mid, high, high_orig; + struct ssh_sharing_connstate *cs; + unsigned ret; + + /* + * Find the lowest unused downstream ID greater or equal to + * 'first'. + * + * Begin by seeing if 'first' itself is available. If it is, we'll + * just return it; if it's already in the tree, we'll find the + * tree index where it appears and use that for the next stage. + */ + { + struct ssh_sharing_connstate dummy; + dummy.id = first; + cs = findrelpos234(sharestate->connections, &dummy, NULL, + REL234_GE, &low_orig); + if (!cs) + return first; + } + + /* + * Now binary-search using the counted B-tree, to find the largest + * ID which is in a contiguous sequence from the beginning of that + * range. + */ + low = low_orig; + high = high_orig = count234(sharestate->connections); + while (high - low > 1) { + mid = (high + low) / 2; + cs = index234(sharestate->connections, mid); + if (cs->id == first + (mid - low_orig)) + low = mid; /* this one is still in the sequence */ + else + high = mid; /* this one is past the end */ + } + + /* + * Now low is the tree index of the largest ID in the initial + * sequence. So the return value is one more than low's id, and we + * know low's id is given by the formula in the binary search loop + * above. + * + * (If an SSH connection went on for _enormously_ long, we might + * reach a point where all ids from 'first' to UINT_MAX were in + * use. In that situation the formula below would wrap round by + * one and return zero, which is conveniently the right way to + * signal 'no id available' from this function.) + */ + ret = first + (low - low_orig) + 1; + { + struct ssh_sharing_connstate dummy; + dummy.id = ret; + assert(NULL == find234(sharestate->connections, &dummy, NULL)); + } + return ret; +} + +static int share_halfchannel_cmp(void *av, void *bv) +{ + const struct share_halfchannel *a = (const struct share_halfchannel *)av; + const struct share_halfchannel *b = (const struct share_halfchannel *)bv; + + if (a->server_id < b->server_id) + return -1; + else if (a->server_id > b->server_id) + return +1; + else + return 0; +} + +static int share_channel_us_cmp(void *av, void *bv) +{ + const struct share_channel *a = (const struct share_channel *)av; + const struct share_channel *b = (const struct share_channel *)bv; + + if (a->upstream_id < b->upstream_id) + return -1; + else if (a->upstream_id > b->upstream_id) + return +1; + else + return 0; +} + +static int share_channel_server_cmp(void *av, void *bv) +{ + const struct share_channel *a = (const struct share_channel *)av; + const struct share_channel *b = (const struct share_channel *)bv; + + if (a->server_id < b->server_id) + return -1; + else if (a->server_id > b->server_id) + return +1; + else + return 0; +} + +static int share_xchannel_us_cmp(void *av, void *bv) +{ + const struct share_xchannel *a = (const struct share_xchannel *)av; + const struct share_xchannel *b = (const struct share_xchannel *)bv; + + if (a->upstream_id < b->upstream_id) + return -1; + else if (a->upstream_id > b->upstream_id) + return +1; + else + return 0; +} + +static int share_xchannel_server_cmp(void *av, void *bv) +{ + const struct share_xchannel *a = (const struct share_xchannel *)av; + const struct share_xchannel *b = (const struct share_xchannel *)bv; + + if (a->server_id < b->server_id) + return -1; + else if (a->server_id > b->server_id) + return +1; + else + return 0; +} + +static int share_forwarding_cmp(void *av, void *bv) +{ + const struct share_forwarding *a = (const struct share_forwarding *)av; + const struct share_forwarding *b = (const struct share_forwarding *)bv; + int i; + + if ((i = strcmp(a->host, b->host)) != 0) + return i; + else if (a->port < b->port) + return -1; + else if (a->port > b->port) + return +1; + else + return 0; +} + +static void share_xchannel_free(struct share_xchannel *xc) +{ + while (xc->msghead) { + struct share_xchannel_message *tmp = xc->msghead; + xc->msghead = tmp->next; + sfree(tmp); + } + sfree(xc); +} + +static void share_connstate_free(struct ssh_sharing_connstate *cs) +{ + struct share_halfchannel *hc; + struct share_xchannel *xc; + struct share_channel *chan; + struct share_forwarding *fwd; + + while ((hc = (struct share_halfchannel *) + delpos234(cs->halfchannels, 0)) != NULL) + sfree(hc); + freetree234(cs->halfchannels); + + /* All channels live in 'channels_by_us' but only some in + * 'channels_by_server', so we use the former to find the list of + * ones to free */ + freetree234(cs->channels_by_server); + while ((chan = (struct share_channel *) + delpos234(cs->channels_by_us, 0)) != NULL) + sfree(chan); + freetree234(cs->channels_by_us); + + /* But every xchannel is in both trees, so it doesn't matter which + * we use to free them. */ + while ((xc = (struct share_xchannel *) + delpos234(cs->xchannels_by_us, 0)) != NULL) + share_xchannel_free(xc); + freetree234(cs->xchannels_by_us); + freetree234(cs->xchannels_by_server); + + while ((fwd = (struct share_forwarding *) + delpos234(cs->forwardings, 0)) != NULL) + sfree(fwd); + freetree234(cs->forwardings); + + while (cs->globreq_head) { + struct share_globreq *globreq = cs->globreq_head; + cs->globreq_head = cs->globreq_head->next; + sfree(globreq); + } + + if (cs->sock) + sk_close(cs->sock); + + sfree(cs); +} + +void sharestate_free(ssh_sharing_state *sharestate) +{ + struct ssh_sharing_connstate *cs; + + platform_ssh_share_cleanup(sharestate->sockname); + + while ((cs = (struct ssh_sharing_connstate *) + delpos234(sharestate->connections, 0)) != NULL) { + share_connstate_free(cs); + } + freetree234(sharestate->connections); + if (sharestate->listensock) { + sk_close(sharestate->listensock); + sharestate->listensock = NULL; + } + sfree(sharestate->server_verstring); + sfree(sharestate->sockname); + sfree(sharestate); +} + +static struct share_halfchannel *share_add_halfchannel + (struct ssh_sharing_connstate *cs, unsigned server_id) +{ + struct share_halfchannel *hc = snew(struct share_halfchannel); + hc->server_id = server_id; + if (add234(cs->halfchannels, hc) != hc) { + /* Duplicate?! */ + sfree(hc); + return NULL; + } else { + return hc; + } +} + +static struct share_halfchannel *share_find_halfchannel + (struct ssh_sharing_connstate *cs, unsigned server_id) +{ + struct share_halfchannel dummyhc; + dummyhc.server_id = server_id; + return find234(cs->halfchannels, &dummyhc, NULL); +} + +static void share_remove_halfchannel(struct ssh_sharing_connstate *cs, + struct share_halfchannel *hc) +{ + del234(cs->halfchannels, hc); + sfree(hc); +} + +static struct share_channel *share_add_channel + (struct ssh_sharing_connstate *cs, unsigned downstream_id, + unsigned upstream_id, unsigned server_id, int state, int maxpkt) +{ + struct share_channel *chan = snew(struct share_channel); + chan->downstream_id = downstream_id; + chan->upstream_id = upstream_id; + chan->server_id = server_id; + chan->state = state; + chan->downstream_maxpkt = maxpkt; + chan->x11_auth_upstream = NULL; + chan->x11_auth_data = NULL; + chan->x11_auth_proto = -1; + chan->x11_auth_datalen = 0; + chan->x11_one_shot = false; + if (add234(cs->channels_by_us, chan) != chan) { + sfree(chan); + return NULL; + } + if (chan->state != UNACKNOWLEDGED) { + if (add234(cs->channels_by_server, chan) != chan) { + del234(cs->channels_by_us, chan); + sfree(chan); + return NULL; + } + } + return chan; +} + +static void share_channel_set_server_id(struct ssh_sharing_connstate *cs, + struct share_channel *chan, + unsigned server_id, int newstate) +{ + chan->server_id = server_id; + chan->state = newstate; + assert(newstate != UNACKNOWLEDGED); + add234(cs->channels_by_server, chan); +} + +static struct share_channel *share_find_channel_by_upstream + (struct ssh_sharing_connstate *cs, unsigned upstream_id) +{ + struct share_channel dummychan; + dummychan.upstream_id = upstream_id; + return find234(cs->channels_by_us, &dummychan, NULL); +} + +static struct share_channel *share_find_channel_by_server + (struct ssh_sharing_connstate *cs, unsigned server_id) +{ + struct share_channel dummychan; + dummychan.server_id = server_id; + return find234(cs->channels_by_server, &dummychan, NULL); +} + +static void share_remove_channel(struct ssh_sharing_connstate *cs, + struct share_channel *chan) +{ + del234(cs->channels_by_us, chan); + del234(cs->channels_by_server, chan); + if (chan->x11_auth_upstream) + ssh_remove_sharing_x11_display(cs->parent->cl, + chan->x11_auth_upstream); + sfree(chan->x11_auth_data); + sfree(chan); +} + +static struct share_xchannel *share_add_xchannel + (struct ssh_sharing_connstate *cs, + unsigned upstream_id, unsigned server_id) +{ + struct share_xchannel *xc = snew(struct share_xchannel); + xc->upstream_id = upstream_id; + xc->server_id = server_id; + xc->live = true; + xc->msghead = xc->msgtail = NULL; + if (add234(cs->xchannels_by_us, xc) != xc) { + sfree(xc); + return NULL; + } + if (add234(cs->xchannels_by_server, xc) != xc) { + del234(cs->xchannels_by_us, xc); + sfree(xc); + return NULL; + } + return xc; +} + +static struct share_xchannel *share_find_xchannel_by_upstream + (struct ssh_sharing_connstate *cs, unsigned upstream_id) +{ + struct share_xchannel dummyxc; + dummyxc.upstream_id = upstream_id; + return find234(cs->xchannels_by_us, &dummyxc, NULL); +} + +static struct share_xchannel *share_find_xchannel_by_server + (struct ssh_sharing_connstate *cs, unsigned server_id) +{ + struct share_xchannel dummyxc; + dummyxc.server_id = server_id; + return find234(cs->xchannels_by_server, &dummyxc, NULL); +} + +static void share_remove_xchannel(struct ssh_sharing_connstate *cs, + struct share_xchannel *xc) +{ + del234(cs->xchannels_by_us, xc); + del234(cs->xchannels_by_server, xc); + share_xchannel_free(xc); +} + +static struct share_forwarding *share_add_forwarding + (struct ssh_sharing_connstate *cs, + const char *host, int port) +{ + struct share_forwarding *fwd = snew(struct share_forwarding); + fwd->host = dupstr(host); + fwd->port = port; + fwd->active = false; + if (add234(cs->forwardings, fwd) != fwd) { + /* Duplicate?! */ + sfree(fwd); + return NULL; + } + return fwd; +} + +static struct share_forwarding *share_find_forwarding + (struct ssh_sharing_connstate *cs, const char *host, int port) +{ + struct share_forwarding dummyfwd, *ret; + dummyfwd.host = dupstr(host); + dummyfwd.port = port; + ret = find234(cs->forwardings, &dummyfwd, NULL); + sfree(dummyfwd.host); + return ret; +} + +static void share_remove_forwarding(struct ssh_sharing_connstate *cs, + struct share_forwarding *fwd) +{ + del234(cs->forwardings, fwd); + sfree(fwd); +} + +static PRINTF_LIKE(2, 3) void log_downstream(struct ssh_sharing_connstate *cs, + const char *logfmt, ...) +{ + va_list ap; + char *buf; + + va_start(ap, logfmt); + buf = dupvprintf(logfmt, ap); + va_end(ap); + logeventf(cs->parent->cl->logctx, + "Connection sharing downstream #%u: %s", cs->id, buf); + sfree(buf); +} + +static PRINTF_LIKE(2, 3) void log_general(struct ssh_sharing_state *sharestate, + const char *logfmt, ...) +{ + va_list ap; + char *buf; + + va_start(ap, logfmt); + buf = dupvprintf(logfmt, ap); + va_end(ap); + logeventf(sharestate->cl->logctx, "Connection sharing: %s", buf); + sfree(buf); +} + +static void send_packet_to_downstream(struct ssh_sharing_connstate *cs, + int type, const void *pkt, int pktlen, + struct share_channel *chan) +{ + strbuf *packet; + + if (!cs->sock) /* throw away all packets destined for a dead downstream */ + return; + + if (type == SSH2_MSG_CHANNEL_DATA) { + /* + * Special case which we take care of at a low level, so as to + * be sure to apply it in all cases. On rare occasions we + * might find that we have a channel for which the + * downstream's maximum packet size exceeds the max packet + * size we presented to the server on its behalf. (This can + * occur in X11 forwarding, where we have to send _our_ + * CHANNEL_OPEN_CONFIRMATION before we discover which if any + * downstream the channel is destined for, so if that + * downstream turns out to present a smaller max packet size + * then we're in this situation.) + * + * If that happens, we just chop up the packet into pieces and + * send them as separate CHANNEL_DATA packets. + */ + BinarySource src[1]; + unsigned channel; + ptrlen data; + + BinarySource_BARE_INIT(src, pkt, pktlen); + channel = get_uint32(src); + data = get_string(src); + + do { + int this_len = (data.len > chan->downstream_maxpkt ? + chan->downstream_maxpkt : data.len); + + packet = strbuf_new_nm(); + put_uint32(packet, 0); /* placeholder for length field */ + put_byte(packet, type); + put_uint32(packet, channel); + put_uint32(packet, this_len); + put_data(packet, data.ptr, this_len); + data.ptr = (const char *)data.ptr + this_len; + data.len -= this_len; + PUT_32BIT_MSB_FIRST(packet->s, packet->len-4); + sk_write(cs->sock, packet->s, packet->len); + strbuf_free(packet); + } while (data.len > 0); + } else { + /* + * Just do the obvious thing. + */ + packet = strbuf_new_nm(); + put_uint32(packet, 0); /* placeholder for length field */ + put_byte(packet, type); + put_data(packet, pkt, pktlen); + PUT_32BIT_MSB_FIRST(packet->s, packet->len-4); + sk_write(cs->sock, packet->s, packet->len); + strbuf_free(packet); + } +} + +static void share_try_cleanup(struct ssh_sharing_connstate *cs) +{ + int i; + struct share_halfchannel *hc; + struct share_channel *chan; + struct share_forwarding *fwd; + + /* + * Any half-open channels, i.e. those for which we'd received + * CHANNEL_OPEN from the server but not passed back a response + * from downstream, should be responded to with OPEN_FAILURE. + */ + while ((hc = (struct share_halfchannel *) + index234(cs->halfchannels, 0)) != NULL) { + static const char reason[] = "PuTTY downstream no longer available"; + static const char lang[] = "en"; + strbuf *packet; + + packet = strbuf_new(); + put_uint32(packet, hc->server_id); + put_uint32(packet, SSH2_OPEN_CONNECT_FAILED); + put_stringz(packet, reason); + put_stringz(packet, lang); + ssh_send_packet_from_downstream( + cs->parent->cl, cs->id, SSH2_MSG_CHANNEL_OPEN_FAILURE, + packet->s, packet->len, + "cleanup after downstream went away"); + strbuf_free(packet); + + share_remove_halfchannel(cs, hc); + } + + /* + * Any actually open channels should have a CHANNEL_CLOSE sent for + * them, unless we've already done so. We won't be able to + * actually clean them up until CHANNEL_CLOSE comes back from the + * server, though (unless the server happens to have sent a CLOSE + * already). + * + * Another annoying exception is UNACKNOWLEDGED channels, i.e. + * we've _sent_ a CHANNEL_OPEN to the server but not received an + * OPEN_CONFIRMATION or OPEN_FAILURE. We must wait for a reply + * before closing the channel, because until we see that reply we + * won't have the server's channel id to put in the close message. + */ + for (i = 0; (chan = (struct share_channel *) + index234(cs->channels_by_us, i)) != NULL; i++) { + strbuf *packet; + + if (chan->state != SENT_CLOSE && chan->state != UNACKNOWLEDGED) { + packet = strbuf_new(); + put_uint32(packet, chan->server_id); + ssh_send_packet_from_downstream( + cs->parent->cl, cs->id, SSH2_MSG_CHANNEL_CLOSE, + packet->s, packet->len, + "cleanup after downstream went away"); + strbuf_free(packet); + + if (chan->state != RCVD_CLOSE) { + chan->state = SENT_CLOSE; + } else { + /* In this case, we _can_ clear up the channel now. */ + ssh_delete_sharing_channel(cs->parent->cl, chan->upstream_id); + share_remove_channel(cs, chan); + i--; /* don't accidentally skip one as a result */ + } + } + } + + /* + * Any remote port forwardings we're managing on behalf of this + * downstream should be cancelled. Again, we must defer those for + * which we haven't yet seen REQUEST_SUCCESS/FAILURE. + * + * We take a fire-and-forget approach during cleanup, not + * bothering to set want_reply. + */ + for (i = 0; (fwd = (struct share_forwarding *) + index234(cs->forwardings, i)) != NULL; i++) { + if (fwd->active) { + strbuf *packet = strbuf_new(); + put_stringz(packet, "cancel-tcpip-forward"); + put_bool(packet, false); /* !want_reply */ + put_stringz(packet, fwd->host); + put_uint32(packet, fwd->port); + ssh_send_packet_from_downstream( + cs->parent->cl, cs->id, SSH2_MSG_GLOBAL_REQUEST, + packet->s, packet->len, + "cleanup after downstream went away"); + strbuf_free(packet); + + ssh_rportfwd_remove(cs->parent->cl, fwd->rpf); + share_remove_forwarding(cs, fwd); + i--; /* don't accidentally skip one as a result */ + } + } + + if (count234(cs->halfchannels) == 0 && + count234(cs->channels_by_us) == 0 && + count234(cs->forwardings) == 0) { + struct ssh_sharing_state *sharestate = cs->parent; + + /* + * Now we're _really_ done, so we can get rid of cs completely. + */ + del234(sharestate->connections, cs); + log_downstream(cs, "disconnected"); + share_connstate_free(cs); + + /* + * And if this was the last downstream, notify the connection + * layer, because it might now be time to wind up the whole + * SSH connection. + */ + if (count234(sharestate->connections) == 0 && sharestate->cl) + ssh_sharing_no_more_downstreams(sharestate->cl); + } +} + +static void share_begin_cleanup(struct ssh_sharing_connstate *cs) +{ + + sk_close(cs->sock); + cs->sock = NULL; + + share_try_cleanup(cs); +} + +static void share_disconnect(struct ssh_sharing_connstate *cs, + const char *message) +{ + strbuf *packet = strbuf_new(); + put_uint32(packet, SSH2_DISCONNECT_PROTOCOL_ERROR); + put_stringz(packet, message); + put_stringz(packet, "en"); /* language */ + send_packet_to_downstream(cs, SSH2_MSG_DISCONNECT, + packet->s, packet->len, NULL); + strbuf_free(packet); + + share_begin_cleanup(cs); +} + +static void share_closing(Plug *plug, const char *error_msg, int error_code, + bool calling_back) +{ + struct ssh_sharing_connstate *cs = container_of( + plug, struct ssh_sharing_connstate, plug); + + if (error_msg) { +#ifdef BROKEN_PIPE_ERROR_CODE + /* + * Most of the time, we log what went wrong when a downstream + * disappears with a socket error. One exception, though, is + * receiving EPIPE when we haven't received a protocol version + * string from the downstream, because that can happen as a result + * of plink -shareexists (opening the connection and instantly + * closing it again without bothering to read our version string). + * So that one case is not treated as a log-worthy error. + */ + if (error_code == BROKEN_PIPE_ERROR_CODE && !cs->got_verstring) + /* do nothing */; + else +#endif + log_downstream(cs, "Socket error: %s", error_msg); + } + share_begin_cleanup(cs); +} + +/* + * Append a message to the end of an xchannel's queue. + */ +static void share_xchannel_add_message( + struct share_xchannel *xc, int type, const void *data, int len) +{ + struct share_xchannel_message *msg; + + /* + * Allocate the 'struct share_xchannel_message' and the actual + * data in one unit. + */ + msg = snew_plus(struct share_xchannel_message, len); + msg->data = snew_plus_get_aux(msg); + msg->datalen = len; + msg->type = type; + memcpy(msg->data, data, len); + + /* + * Queue it in the xchannel. + */ + if (xc->msgtail) + xc->msgtail->next = msg; + else + xc->msghead = msg; + msg->next = NULL; + xc->msgtail = msg; +} + +void share_dead_xchannel_respond(struct ssh_sharing_connstate *cs, + struct share_xchannel *xc) +{ + /* + * Handle queued incoming messages from the server destined for an + * xchannel which is dead (i.e. downstream sent OPEN_FAILURE). + */ + bool delete = false; + while (xc->msghead) { + struct share_xchannel_message *msg = xc->msghead; + xc->msghead = msg->next; + + if (msg->type == SSH2_MSG_CHANNEL_REQUEST && msg->datalen > 4) { + /* + * A CHANNEL_REQUEST is responded to by sending + * CHANNEL_FAILURE, if it has want_reply set. + */ + BinarySource src[1]; + BinarySource_BARE_INIT(src, msg->data, msg->datalen); + get_uint32(src); /* skip channel id */ + get_string(src); /* skip request type */ + if (get_bool(src)) { + strbuf *packet = strbuf_new(); + put_uint32(packet, xc->server_id); + ssh_send_packet_from_downstream + (cs->parent->cl, cs->id, SSH2_MSG_CHANNEL_FAILURE, + packet->s, packet->len, + "downstream refused X channel open"); + strbuf_free(packet); + } + } else if (msg->type == SSH2_MSG_CHANNEL_CLOSE) { + /* + * On CHANNEL_CLOSE we can discard the channel completely. + */ + delete = true; + } + + sfree(msg); + } + xc->msgtail = NULL; + if (delete) { + ssh_delete_sharing_channel(cs->parent->cl, xc->upstream_id); + share_remove_xchannel(cs, xc); + } +} + +void share_xchannel_confirmation(struct ssh_sharing_connstate *cs, + struct share_xchannel *xc, + struct share_channel *chan, + unsigned downstream_window) +{ + strbuf *packet; + + /* + * Send all the queued messages downstream. + */ + while (xc->msghead) { + struct share_xchannel_message *msg = xc->msghead; + xc->msghead = msg->next; + + if (msg->datalen >= 4) + PUT_32BIT_MSB_FIRST(msg->data, chan->downstream_id); + send_packet_to_downstream(cs, msg->type, + msg->data, msg->datalen, chan); + + sfree(msg); + } + + /* + * Send a WINDOW_ADJUST back upstream, to synchronise the window + * size downstream thinks it's presented with the one we've + * actually presented. + */ + packet = strbuf_new(); + put_uint32(packet, xc->server_id); + put_uint32(packet, downstream_window - xc->window); + ssh_send_packet_from_downstream( + cs->parent->cl, cs->id, SSH2_MSG_CHANNEL_WINDOW_ADJUST, + packet->s, packet->len, + "window adjustment after downstream accepted X channel"); + strbuf_free(packet); +} + +void share_xchannel_failure(struct ssh_sharing_connstate *cs, + struct share_xchannel *xc) +{ + /* + * If downstream refuses to open our X channel at all for some + * reason, we must respond by sending an emergency CLOSE upstream. + */ + strbuf *packet = strbuf_new(); + put_uint32(packet, xc->server_id); + ssh_send_packet_from_downstream( + cs->parent->cl, cs->id, SSH2_MSG_CHANNEL_CLOSE, + packet->s, packet->len, + "downstream refused X channel open"); + strbuf_free(packet); + + /* + * Now mark the xchannel as dead, and respond to anything sent on + * it until we see CLOSE for it in turn. + */ + xc->live = false; + share_dead_xchannel_respond(cs, xc); +} + +void share_setup_x11_channel(ssh_sharing_connstate *cs, share_channel *chan, + unsigned upstream_id, unsigned server_id, + unsigned server_currwin, unsigned server_maxpkt, + unsigned client_adjusted_window, + const char *peer_addr, int peer_port, int endian, + int protomajor, int protominor, + const void *initial_data, int initial_len) +{ + struct share_xchannel *xc; + void *greeting; + int greeting_len; + strbuf *packet; + + /* + * Create an xchannel containing data we've already received from + * the X client, and preload it with a CHANNEL_DATA message + * containing our own made-up authorisation greeting and any + * additional data sent from the server so far. + */ + xc = share_add_xchannel(cs, upstream_id, server_id); + greeting = x11_make_greeting(endian, protomajor, protominor, + chan->x11_auth_proto, + chan->x11_auth_data, chan->x11_auth_datalen, + peer_addr, peer_port, &greeting_len); + packet = strbuf_new_nm(); + put_uint32(packet, 0); /* leave the channel id field unfilled - we + * don't know the downstream id yet */ + put_uint32(packet, greeting_len + initial_len); + put_data(packet, greeting, greeting_len); + put_data(packet, initial_data, initial_len); + sfree(greeting); + share_xchannel_add_message(xc, SSH2_MSG_CHANNEL_DATA, + packet->s, packet->len); + strbuf_free(packet); + + xc->window = client_adjusted_window + greeting_len; + + /* + * Send on a CHANNEL_OPEN to downstream. + */ + packet = strbuf_new(); + put_stringz(packet, "x11"); + put_uint32(packet, server_id); + put_uint32(packet, server_currwin); + put_uint32(packet, server_maxpkt); + put_stringz(packet, peer_addr); + put_uint32(packet, peer_port); + send_packet_to_downstream(cs, SSH2_MSG_CHANNEL_OPEN, + packet->s, packet->len, NULL); + strbuf_free(packet); + + /* + * If this was a once-only X forwarding, clean it up now. + */ + if (chan->x11_one_shot) { + ssh_remove_sharing_x11_display(cs->parent->cl, + chan->x11_auth_upstream); + chan->x11_auth_upstream = NULL; + sfree(chan->x11_auth_data); + chan->x11_auth_proto = -1; + chan->x11_auth_datalen = 0; + chan->x11_one_shot = false; + } +} + +void share_got_pkt_from_server(ssh_sharing_connstate *cs, int type, + const void *vpkt, int pktlen) +{ + const unsigned char *pkt = (const unsigned char *)vpkt; + struct share_globreq *globreq; + size_t id_pos; + unsigned upstream_id, server_id; + struct share_channel *chan; + struct share_xchannel *xc; + BinarySource src[1]; + + BinarySource_BARE_INIT(src, pkt, pktlen); + + switch (type) { + case SSH2_MSG_REQUEST_SUCCESS: + case SSH2_MSG_REQUEST_FAILURE: + globreq = cs->globreq_head; + assert(globreq); /* should match the queue in ssh.c */ + if (globreq->type == GLOBREQ_TCPIP_FORWARD) { + if (type == SSH2_MSG_REQUEST_FAILURE) { + share_remove_forwarding(cs, globreq->fwd); + } else { + globreq->fwd->active = true; + } + } else if (globreq->type == GLOBREQ_CANCEL_TCPIP_FORWARD) { + if (type == SSH2_MSG_REQUEST_SUCCESS) { + share_remove_forwarding(cs, globreq->fwd); + } + } + if (globreq->want_reply) { + send_packet_to_downstream(cs, type, pkt, pktlen, NULL); + } + cs->globreq_head = globreq->next; + sfree(globreq); + if (cs->globreq_head == NULL) + cs->globreq_tail = NULL; + + if (!cs->sock) { + /* Retry cleaning up this connection, in case that reply + * was the last thing we were waiting for. */ + share_try_cleanup(cs); + } + + break; + + case SSH2_MSG_CHANNEL_OPEN: + get_string(src); + server_id = get_uint32(src); + assert(!get_err(src)); + share_add_halfchannel(cs, server_id); + + send_packet_to_downstream(cs, type, pkt, pktlen, NULL); + break; + + case SSH2_MSG_CHANNEL_OPEN_CONFIRMATION: + case SSH2_MSG_CHANNEL_OPEN_FAILURE: + case SSH2_MSG_CHANNEL_CLOSE: + case SSH2_MSG_CHANNEL_WINDOW_ADJUST: + case SSH2_MSG_CHANNEL_DATA: + case SSH2_MSG_CHANNEL_EXTENDED_DATA: + case SSH2_MSG_CHANNEL_EOF: + case SSH2_MSG_CHANNEL_REQUEST: + case SSH2_MSG_CHANNEL_SUCCESS: + case SSH2_MSG_CHANNEL_FAILURE: + /* + * All these messages have the recipient channel id as the + * first uint32 field in the packet. Substitute the downstream + * channel id for our one and pass the packet downstream. + */ + id_pos = src->pos; + upstream_id = get_uint32(src); + if ((chan = share_find_channel_by_upstream(cs, upstream_id)) != NULL) { + /* + * The normal case: this id refers to an open channel. + */ + unsigned char *rewritten = snewn(pktlen, unsigned char); + memcpy(rewritten, pkt, pktlen); + PUT_32BIT_MSB_FIRST(rewritten + id_pos, chan->downstream_id); + send_packet_to_downstream(cs, type, rewritten, pktlen, chan); + sfree(rewritten); + + /* + * Update the channel state, for messages that need it. + */ + if (type == SSH2_MSG_CHANNEL_OPEN_CONFIRMATION) { + if (chan->state == UNACKNOWLEDGED && pktlen >= 8) { + share_channel_set_server_id( + cs, chan, GET_32BIT_MSB_FIRST(pkt+4), OPEN); + if (!cs->sock) { + /* Retry cleaning up this connection, so that we + * can send an immediate CLOSE on this channel for + * which we now know the server id. */ + share_try_cleanup(cs); + } + } + } else if (type == SSH2_MSG_CHANNEL_OPEN_FAILURE) { + ssh_delete_sharing_channel(cs->parent->cl, chan->upstream_id); + share_remove_channel(cs, chan); + } else if (type == SSH2_MSG_CHANNEL_CLOSE) { + if (chan->state == SENT_CLOSE) { + ssh_delete_sharing_channel(cs->parent->cl, + chan->upstream_id); + share_remove_channel(cs, chan); + if (!cs->sock) { + /* Retry cleaning up this connection, in case this + * channel closure was the last thing we were + * waiting for. */ + share_try_cleanup(cs); + } + } else { + chan->state = RCVD_CLOSE; + } + } + } else if ((xc = share_find_xchannel_by_upstream(cs, upstream_id)) + != NULL) { + /* + * The unusual case: this id refers to an xchannel. Add it + * to the xchannel's queue. + */ + share_xchannel_add_message(xc, type, pkt, pktlen); + + /* If the xchannel is dead, then also respond to it (which + * may involve deleting the channel). */ + if (!xc->live) + share_dead_xchannel_respond(cs, xc); + } + break; + + default: + unreachable("This packet type should never have come from ssh.c"); + } +} + +static void share_got_pkt_from_downstream(struct ssh_sharing_connstate *cs, + int type, + unsigned char *pkt, int pktlen) +{ + ptrlen request_name; + struct share_forwarding *fwd; + size_t id_pos; + unsigned maxpkt; + unsigned old_id, new_id, server_id; + struct share_globreq *globreq; + struct share_channel *chan; + struct share_halfchannel *hc; + struct share_xchannel *xc; + strbuf *packet; + char *err = NULL; + BinarySource src[1]; + size_t wantreplypos; + bool orig_wantreply; + + BinarySource_BARE_INIT(src, pkt, pktlen); + + switch (type) { + case SSH2_MSG_DISCONNECT: + /* + * This message stops here: if downstream is disconnecting + * from us, that doesn't mean we want to disconnect from the + * SSH server. Close the downstream connection and start + * cleanup. + */ + share_begin_cleanup(cs); + break; + + case SSH2_MSG_GLOBAL_REQUEST: + /* + * The only global requests we understand are "tcpip-forward" + * and "cancel-tcpip-forward". Since those require us to + * maintain state, we must assume that other global requests + * will probably require that too, and so we don't forward on + * any request we don't understand. + */ + request_name = get_string(src); + wantreplypos = src->pos; + orig_wantreply = get_bool(src); + + if (ptrlen_eq_string(request_name, "tcpip-forward")) { + ptrlen hostpl; + char *host; + int port; + struct ssh_rportfwd *rpf; + + /* + * Pick the packet apart to find the want_reply field and + * the host/port we're going to ask to listen on. + */ + hostpl = get_string(src); + port = toint(get_uint32(src)); + if (get_err(src)) { + err = dupprintf("Truncated GLOBAL_REQUEST packet"); + goto confused; + } + host = mkstr(hostpl); + + /* + * See if we can allocate space in ssh.c's tree of remote + * port forwardings. If we can't, it's because another + * client sharing this connection has already allocated + * the identical port forwarding, so we take it on + * ourselves to manufacture a failure packet and send it + * back to downstream. + */ + rpf = ssh_rportfwd_alloc( + cs->parent->cl, host, port, NULL, 0, 0, NULL, NULL, cs); + if (!rpf) { + if (orig_wantreply) { + send_packet_to_downstream(cs, SSH2_MSG_REQUEST_FAILURE, + "", 0, NULL); + } + } else { + /* + * We've managed to make space for this forwarding + * locally. Pass the request on to the SSH server, but + * set want_reply even if it wasn't originally set, so + * that we know whether this forwarding needs to be + * cleaned up if downstream goes away. + */ + pkt[wantreplypos] = 1; + ssh_send_packet_from_downstream + (cs->parent->cl, cs->id, type, pkt, pktlen, + orig_wantreply ? NULL : "upstream added want_reply flag"); + fwd = share_add_forwarding(cs, host, port); + ssh_sharing_queue_global_request(cs->parent->cl, cs); + + if (fwd) { + globreq = snew(struct share_globreq); + globreq->next = NULL; + if (cs->globreq_tail) + cs->globreq_tail->next = globreq; + else + cs->globreq_head = globreq; + globreq->fwd = fwd; + globreq->want_reply = orig_wantreply; + globreq->type = GLOBREQ_TCPIP_FORWARD; + + fwd->rpf = rpf; + } + } + + sfree(host); + } else if (ptrlen_eq_string(request_name, "cancel-tcpip-forward")) { + ptrlen hostpl; + char *host; + int port; + struct share_forwarding *fwd; + + /* + * Pick the packet apart to find the want_reply field and + * the host/port we're going to ask to listen on. + */ + hostpl = get_string(src); + port = toint(get_uint32(src)); + if (get_err(src)) { + err = dupprintf("Truncated GLOBAL_REQUEST packet"); + goto confused; + } + host = mkstr(hostpl); + + /* + * Look up the existing forwarding with these details. + */ + fwd = share_find_forwarding(cs, host, port); + if (!fwd) { + if (orig_wantreply) { + send_packet_to_downstream(cs, SSH2_MSG_REQUEST_FAILURE, + "", 0, NULL); + } + } else { + /* + * Tell ssh.c to stop sending us channel-opens for + * this forwarding. + */ + ssh_rportfwd_remove(cs->parent->cl, fwd->rpf); + + /* + * Pass the cancel request on to the SSH server, but + * set want_reply even if it wasn't originally set, so + * that _we_ know whether the forwarding has been + * deleted even if downstream doesn't want to know. + */ + pkt[wantreplypos] = 1; + ssh_send_packet_from_downstream + (cs->parent->cl, cs->id, type, pkt, pktlen, + orig_wantreply ? NULL : "upstream added want_reply flag"); + ssh_sharing_queue_global_request(cs->parent->cl, cs); + + /* + * And queue a globreq so that when the reply comes + * back we know to cancel it. + */ + globreq = snew(struct share_globreq); + globreq->next = NULL; + if (cs->globreq_tail) + cs->globreq_tail->next = globreq; + else + cs->globreq_head = globreq; + globreq->fwd = fwd; + globreq->want_reply = orig_wantreply; + globreq->type = GLOBREQ_CANCEL_TCPIP_FORWARD; + } + + sfree(host); + } else { + /* + * Request we don't understand. Manufacture a failure + * message if an answer was required. + */ + if (orig_wantreply) + send_packet_to_downstream(cs, SSH2_MSG_REQUEST_FAILURE, + "", 0, NULL); + } + break; + + case SSH2_MSG_CHANNEL_OPEN: + /* Sender channel id comes after the channel type string */ + get_string(src); + id_pos = src->pos; + old_id = get_uint32(src); + new_id = ssh_alloc_sharing_channel(cs->parent->cl, cs); + get_uint32(src); /* skip initial window size */ + maxpkt = get_uint32(src); + if (get_err(src)) { + err = dupprintf("Truncated CHANNEL_OPEN packet"); + goto confused; + } + share_add_channel(cs, old_id, new_id, 0, UNACKNOWLEDGED, maxpkt); + PUT_32BIT_MSB_FIRST(pkt + id_pos, new_id); + ssh_send_packet_from_downstream(cs->parent->cl, cs->id, + type, pkt, pktlen, NULL); + break; + + case SSH2_MSG_CHANNEL_OPEN_CONFIRMATION: + if (pktlen < 16) { + err = dupprintf("Truncated CHANNEL_OPEN_CONFIRMATION packet"); + goto confused; + } + + server_id = get_uint32(src); + id_pos = src->pos; + old_id = get_uint32(src); + get_uint32(src); /* skip initial window size */ + maxpkt = get_uint32(src); + if (get_err(src)) { + err = dupprintf("Truncated CHANNEL_OPEN_CONFIRMATION packet"); + goto confused; + } + + /* This server id may refer to either a halfchannel or an xchannel. */ + hc = NULL, xc = NULL; /* placate optimiser */ + if ((hc = share_find_halfchannel(cs, server_id)) != NULL) { + new_id = ssh_alloc_sharing_channel(cs->parent->cl, cs); + } else if ((xc = share_find_xchannel_by_server(cs, server_id)) + != NULL) { + new_id = xc->upstream_id; + } else { + err = dupprintf("CHANNEL_OPEN_CONFIRMATION packet cited unknown channel %u", (unsigned)server_id); + goto confused; + } + + PUT_32BIT_MSB_FIRST(pkt + id_pos, new_id); + + chan = share_add_channel(cs, old_id, new_id, server_id, OPEN, maxpkt); + + if (hc) { + ssh_send_packet_from_downstream(cs->parent->cl, cs->id, + type, pkt, pktlen, NULL); + share_remove_halfchannel(cs, hc); + } else if (xc) { + unsigned downstream_window = GET_32BIT_MSB_FIRST(pkt + 8); + if (downstream_window < 256) { + err = dupprintf("Initial window size for x11 channel must be at least 256 (got %u)", downstream_window); + goto confused; + } + share_xchannel_confirmation(cs, xc, chan, downstream_window); + share_remove_xchannel(cs, xc); + } + + break; + + case SSH2_MSG_CHANNEL_OPEN_FAILURE: + server_id = get_uint32(src); + if (get_err(src)) { + err = dupprintf("Truncated CHANNEL_OPEN_FAILURE packet"); + goto confused; + } + + /* This server id may refer to either a halfchannel or an xchannel. */ + if ((hc = share_find_halfchannel(cs, server_id)) != NULL) { + ssh_send_packet_from_downstream(cs->parent->cl, cs->id, + type, pkt, pktlen, NULL); + share_remove_halfchannel(cs, hc); + } else if ((xc = share_find_xchannel_by_server(cs, server_id)) + != NULL) { + share_xchannel_failure(cs, xc); + } else { + err = dupprintf("CHANNEL_OPEN_FAILURE packet cited unknown channel %u", (unsigned)server_id); + goto confused; + } + + break; + + case SSH2_MSG_CHANNEL_WINDOW_ADJUST: + case SSH2_MSG_CHANNEL_DATA: + case SSH2_MSG_CHANNEL_EXTENDED_DATA: + case SSH2_MSG_CHANNEL_EOF: + case SSH2_MSG_CHANNEL_CLOSE: + case SSH2_MSG_CHANNEL_REQUEST: + case SSH2_MSG_CHANNEL_SUCCESS: + case SSH2_MSG_CHANNEL_FAILURE: + case SSH2_MSG_IGNORE: + case SSH2_MSG_DEBUG: + server_id = get_uint32(src); + + if (type == SSH2_MSG_CHANNEL_REQUEST) { + request_name = get_string(src); + + /* + * Agent forwarding requests from downstream are treated + * specially. Because OpenSSHD doesn't let us enable agent + * forwarding independently per session channel, and in + * particular because the OpenSSH-defined agent forwarding + * protocol does not mark agent-channel requests with the + * id of the session channel they originate from, the only + * way we can implement agent forwarding in a + * connection-shared PuTTY is to forward the _upstream_ + * agent. Hence, we unilaterally deny agent forwarding + * requests from downstreams if we aren't prepared to + * forward an agent ourselves. + * + * (If we are, then we dutifully pass agent forwarding + * requests upstream. OpenSSHD has the curious behaviour + * that all but the first such request will be rejected, + * but all session channels opened after the first request + * get agent forwarding enabled whether they ask for it or + * not; but that's not our concern, since other SSH + * servers supporting the same piece of protocol might in + * principle at least manage to enable agent forwarding on + * precisely the channels that requested it, even if the + * subsequent CHANNEL_OPENs still can't be associated with + * a parent session channel.) + */ + if (ptrlen_eq_string(request_name, "auth-agent-req@openssh.com") && + !ssh_agent_forwarding_permitted(cs->parent->cl)) { + + chan = share_find_channel_by_server(cs, server_id); + if (chan) { + packet = strbuf_new(); + put_uint32(packet, chan->downstream_id); + send_packet_to_downstream( + cs, SSH2_MSG_CHANNEL_FAILURE, + packet->s, packet->len, NULL); + strbuf_free(packet); + } else { + char *buf = dupprintf("Agent forwarding request for " + "unrecognised channel %u", server_id); + share_disconnect(cs, buf); + sfree(buf); + return; + } + break; + } + + /* + * Another thing we treat specially is X11 forwarding + * requests. For these, we have to make up another set of + * X11 auth data, and enter it into our SSH connection's + * list of possible X11 authorisation credentials so that + * when we see an X11 channel open request we can know + * whether it's one to handle locally or one to pass on to + * a downstream, and if the latter, which one. + */ + if (ptrlen_eq_string(request_name, "x11-req")) { + bool want_reply, single_connection; + int screen; + ptrlen auth_data; + int auth_proto; + + chan = share_find_channel_by_server(cs, server_id); + if (!chan) { + char *buf = dupprintf("X11 forwarding request for " + "unrecognised channel %u", server_id); + share_disconnect(cs, buf); + sfree(buf); + return; + } + + /* + * Pick apart the whole message to find the downstream + * auth details. + */ + want_reply = get_bool(src); + single_connection = get_bool(src); + auth_proto = x11_identify_auth_proto(get_string(src)); + auth_data = get_string(src); + screen = toint(get_uint32(src)); + if (get_err(src)) { + err = dupprintf("Truncated CHANNEL_REQUEST(\"x11-req\")" + " packet"); + goto confused; + } + + if (auth_proto < 0) { + /* Reject due to not understanding downstream's + * requested authorisation method. */ + packet = strbuf_new(); + put_uint32(packet, chan->downstream_id); + send_packet_to_downstream( + cs, SSH2_MSG_CHANNEL_FAILURE, + packet->s, packet->len, NULL); + strbuf_free(packet); + break; + } + + chan->x11_auth_proto = auth_proto; + chan->x11_auth_data = x11_dehexify(auth_data, + &chan->x11_auth_datalen); + chan->x11_auth_upstream = + ssh_add_sharing_x11_display(cs->parent->cl, auth_proto, + cs, chan); + chan->x11_one_shot = single_connection; + + /* + * Now construct a replacement X forwarding request, + * containing our own auth data, and send that to the + * server. + */ + packet = strbuf_new_nm(); + put_uint32(packet, server_id); + put_stringz(packet, "x11-req"); + put_bool(packet, want_reply); + put_bool(packet, single_connection); + put_stringz(packet, chan->x11_auth_upstream->protoname); + put_stringz(packet, chan->x11_auth_upstream->datastring); + put_uint32(packet, screen); + ssh_send_packet_from_downstream( + cs->parent->cl, cs->id, SSH2_MSG_CHANNEL_REQUEST, + packet->s, packet->len, NULL); + strbuf_free(packet); + + break; + } + } + + ssh_send_packet_from_downstream(cs->parent->cl, cs->id, + type, pkt, pktlen, NULL); + if (type == SSH2_MSG_CHANNEL_CLOSE && pktlen >= 4) { + chan = share_find_channel_by_server(cs, server_id); + if (chan) { + if (chan->state == RCVD_CLOSE) { + ssh_delete_sharing_channel(cs->parent->cl, + chan->upstream_id); + share_remove_channel(cs, chan); + } else { + chan->state = SENT_CLOSE; + } + } + } + break; + + default: + err = dupprintf("Unexpected packet type %d\n", type); + goto confused; + + /* + * Any other packet type is unexpected. In particular, we + * never pass GLOBAL_REQUESTs downstream, so we never expect + * to see SSH2_MSG_REQUEST_{SUCCESS,FAILURE}. + */ + confused: + assert(err != NULL); + share_disconnect(cs, err); + sfree(err); + break; + } +} + +/* + * An extra coroutine macro, specific to this code which is consuming + * 'const char *data'. + */ +#define crGetChar(c) do \ + { \ + while (len == 0) { \ + *crLine =__LINE__; return; case __LINE__:; \ + } \ + len--; \ + (c) = (unsigned char)*data++; \ + } while (0) + +static void share_receive(Plug *plug, int urgent, const char *data, size_t len) +{ + ssh_sharing_connstate *cs = container_of( + plug, ssh_sharing_connstate, plug); + static const char expected_verstring_prefix[] = + "SSHCONNECTION@putty.projects.tartarus.org-2.0-"; + unsigned char c; + + crBegin(cs->crLine); + + /* + * First read the version string from downstream. + */ + cs->recvlen = 0; + while (1) { + crGetChar(c); + if (c == '\012') + break; + if (cs->recvlen >= sizeof(cs->recvbuf)) { + char *buf = dupprintf("Version string far too long\n"); + share_disconnect(cs, buf); + sfree(buf); + goto dead; + } + cs->recvbuf[cs->recvlen++] = c; + } + + /* + * Now parse the version string to make sure it's at least vaguely + * sensible, and log it. + */ + if (cs->recvlen < sizeof(expected_verstring_prefix)-1 || + memcmp(cs->recvbuf, expected_verstring_prefix, + sizeof(expected_verstring_prefix) - 1)) { + char *buf = dupprintf("Version string did not have expected prefix\n"); + share_disconnect(cs, buf); + sfree(buf); + goto dead; + } + if (cs->recvlen > 0 && cs->recvbuf[cs->recvlen-1] == '\015') + cs->recvlen--; /* trim off \r before \n */ + ptrlen verstring = make_ptrlen(cs->recvbuf, cs->recvlen); + log_downstream(cs, "Downstream version string: %.*s", + PTRLEN_PRINTF(verstring)); + cs->got_verstring = true; + + /* + * Loop round reading packets. + */ + while (1) { + cs->recvlen = 0; + while (cs->recvlen < 4) { + crGetChar(c); + cs->recvbuf[cs->recvlen++] = c; + } + cs->curr_packetlen = toint(GET_32BIT_MSB_FIRST(cs->recvbuf) + 4); + if (cs->curr_packetlen < 5 || + cs->curr_packetlen > sizeof(cs->recvbuf)) { + char *buf = dupprintf("Bad packet length %u\n", + (unsigned)cs->curr_packetlen); + share_disconnect(cs, buf); + sfree(buf); + goto dead; + } + while (cs->recvlen < cs->curr_packetlen) { + crGetChar(c); + cs->recvbuf[cs->recvlen++] = c; + } + + share_got_pkt_from_downstream(cs, cs->recvbuf[4], + cs->recvbuf + 5, cs->recvlen - 5); + } + + dead:; + crFinishV; +} + +static void share_sent(Plug *plug, size_t bufsize) +{ + /* ssh_sharing_connstate *cs = container_of( + plug, ssh_sharing_connstate, plug); */ + + /* + * We do nothing here, because we expect that there won't be a + * need to throttle and unthrottle the connection to a downstream. + * It should automatically throttle itself: if the SSH server + * sends huge amounts of data on all channels then it'll run out + * of window until our downstream sends it back some + * WINDOW_ADJUSTs. + */ +} + +static void share_listen_closing(Plug *plug, const char *error_msg, + int error_code, bool calling_back) +{ + ssh_sharing_state *sharestate = + container_of(plug, ssh_sharing_state, plug); + if (error_msg) + log_general(sharestate, "listening socket: %s", error_msg); + sk_close(sharestate->listensock); + sharestate->listensock = NULL; +} + +static void share_send_verstring(ssh_sharing_connstate *cs) +{ + char *fullstring = dupcat("SSHCONNECTION@putty.projects.tartarus.org-2.0-", + cs->parent->server_verstring, "\015\012"); + sk_write(cs->sock, fullstring, strlen(fullstring)); + sfree(fullstring); + + cs->sent_verstring = true; +} + +int share_ndownstreams(ssh_sharing_state *sharestate) +{ + return count234(sharestate->connections); +} + +void share_activate(ssh_sharing_state *sharestate, + const char *server_verstring) +{ + /* + * Indication from ssh.c that we are now ready to begin serving + * any downstreams that have already connected to us. + */ + struct ssh_sharing_connstate *cs; + int i; + + /* + * Trim the server's version string down to just the software + * version component, removing "SSH-2.0-" or whatever at the + * front. + */ + for (i = 0; i < 2; i++) { + server_verstring += strcspn(server_verstring, "-"); + if (*server_verstring) + server_verstring++; + } + + sharestate->server_verstring = dupstr(server_verstring); + + for (i = 0; (cs = (struct ssh_sharing_connstate *) + index234(sharestate->connections, i)) != NULL; i++) { + assert(!cs->sent_verstring); + share_send_verstring(cs); + } +} + +static const PlugVtable ssh_sharing_conn_plugvt = { + .closing = share_closing, + .receive = share_receive, + .sent = share_sent, +}; + +static int share_listen_accepting(Plug *plug, + accept_fn_t constructor, accept_ctx_t ctx) +{ + struct ssh_sharing_state *sharestate = container_of( + plug, struct ssh_sharing_state, plug); + struct ssh_sharing_connstate *cs; + const char *err; + SocketPeerInfo *peerinfo; + + /* + * A new downstream has connected to us. + */ + cs = snew(struct ssh_sharing_connstate); + cs->plug.vt = &ssh_sharing_conn_plugvt; + cs->parent = sharestate; + + if ((cs->id = share_find_unused_id(sharestate, sharestate->nextid)) == 0 && + (cs->id = share_find_unused_id(sharestate, 1)) == 0) { + sfree(cs); + return 1; + } + sharestate->nextid = cs->id + 1; + if (sharestate->nextid == 0) + sharestate->nextid++; /* only happens in VERY long-running upstreams */ + + cs->sock = constructor(ctx, &cs->plug); + if ((err = sk_socket_error(cs->sock)) != NULL) { + sfree(cs); + return err != NULL; + } + + sk_set_frozen(cs->sock, false); + + add234(cs->parent->connections, cs); + + cs->sent_verstring = false; + if (sharestate->server_verstring) + share_send_verstring(cs); + + cs->got_verstring = false; + cs->recvlen = 0; + cs->crLine = 0; + cs->halfchannels = newtree234(share_halfchannel_cmp); + cs->channels_by_us = newtree234(share_channel_us_cmp); + cs->channels_by_server = newtree234(share_channel_server_cmp); + cs->xchannels_by_us = newtree234(share_xchannel_us_cmp); + cs->xchannels_by_server = newtree234(share_xchannel_server_cmp); + cs->forwardings = newtree234(share_forwarding_cmp); + cs->globreq_head = cs->globreq_tail = NULL; + + peerinfo = sk_peer_info(cs->sock); + log_downstream(cs, "connected%s%s", + (peerinfo && peerinfo->log_text ? " from " : ""), + (peerinfo && peerinfo->log_text ? peerinfo->log_text : "")); + sk_free_peer_info(peerinfo); + + return 0; +} + +/* + * Decide on the string used to identify the connection point between + * upstream and downstream (be it a Windows named pipe or a + * Unix-domain socket or whatever else). + * + * I wondered about making this a SHA hash of all sorts of pieces of + * the PuTTY configuration - essentially everything PuTTY uses to know + * where and how to make a connection, including all the proxy details + * (or rather, all the _relevant_ ones - only including settings that + * other settings didn't prevent from having any effect), plus the + * username. However, I think it's better to keep it really simple: + * the connection point identifier is derived from the hostname and + * port used to index the host-key cache (not necessarily where we + * _physically_ connected to, in cases involving proxies or + * CONF_loghost), plus the username if one is specified. + * + * The per-platform code will quite likely hash or obfuscate this name + * in turn, for privacy from other users; failing that, it might + * transform it to avoid dangerous filename characters and so on. But + * that doesn't matter to us: for us, the point is that two session + * configurations which return the same string from this function will + * be treated as potentially shareable with each other. + */ +char *ssh_share_sockname(const char *host, int port, Conf *conf) +{ + char *username = NULL; + char *sockname; + + /* Include the username we're logging in as in the hash, unless + * we're using a protocol for which it's completely irrelevant. */ + if (conf_get_int(conf, CONF_protocol) != PROT_SSHCONN) + username = get_remote_username(conf); + + if (port == 22) { + if (username) + sockname = dupprintf("%s@%s", username, host); + else + sockname = dupprintf("%s", host); + } else { + if (username) + sockname = dupprintf("%s@%s:%d", username, host, port); + else + sockname = dupprintf("%s:%d", host, port); + } + + sfree(username); + return sockname; +} + +bool ssh_share_test_for_upstream(const char *host, int port, Conf *conf) +{ + char *sockname, *logtext, *ds_err, *us_err; + int result; + Socket *sock; + + sockname = ssh_share_sockname(host, port, conf); + + sock = NULL; + logtext = ds_err = us_err = NULL; + result = platform_ssh_share(sockname, conf, nullplug, (Plug *)NULL, &sock, + &logtext, &ds_err, &us_err, false, true); + + sfree(logtext); + sfree(ds_err); + sfree(us_err); + sfree(sockname); + + if (result == SHARE_NONE) { + assert(sock == NULL); + return false; + } else { + assert(result == SHARE_DOWNSTREAM); + sk_close(sock); + return true; + } +} + +static const PlugVtable ssh_sharing_listen_plugvt = { + .closing = share_listen_closing, + .accepting = share_listen_accepting, +}; + +void ssh_connshare_provide_connlayer(ssh_sharing_state *sharestate, + ConnectionLayer *cl) +{ + sharestate->cl = cl; +} + +/* + * Init function for connection sharing. We either open a listening + * socket and become an upstream, or connect to an existing one and + * become a downstream, or do neither. We are responsible for deciding + * which of these to do (including checking the Conf to see if + * connection sharing is even enabled in the first place). If we + * become a downstream, we return the Socket with which we connected + * to the upstream; otherwise (whether or not we have established an + * upstream) we return NULL. + */ +Socket *ssh_connection_sharing_init( + const char *host, int port, Conf *conf, LogContext *logctx, + Plug *sshplug, ssh_sharing_state **state) +{ + int result; + bool can_upstream, can_downstream; + char *logtext, *ds_err, *us_err; + char *sockname; + Socket *sock, *toret = NULL; + struct ssh_sharing_state *sharestate; + + if (!conf_get_bool(conf, CONF_ssh_connection_sharing)) + return NULL; /* do not share anything */ + can_upstream = share_can_be_upstream && + conf_get_bool(conf, CONF_ssh_connection_sharing_upstream); + can_downstream = share_can_be_downstream && + conf_get_bool(conf, CONF_ssh_connection_sharing_downstream); + if (!can_upstream && !can_downstream) + return NULL; + + sockname = ssh_share_sockname(host, port, conf); + + /* + * Create a data structure for the listening plug if we turn out + * to be an upstream. + */ + sharestate = snew(struct ssh_sharing_state); + sharestate->plug.vt = &ssh_sharing_listen_plugvt; + sharestate->listensock = NULL; + sharestate->cl = NULL; + + /* + * Now hand off to a per-platform routine that either connects to + * an existing upstream (using 'ssh' as the plug), establishes our + * own upstream (using 'sharestate' as the plug), or forks off a + * separate upstream and then connects to that. It will return a + * code telling us which kind of socket it put in 'sock'. + */ + sock = NULL; + logtext = ds_err = us_err = NULL; + result = platform_ssh_share( + sockname, conf, sshplug, &sharestate->plug, &sock, &logtext, + &ds_err, &us_err, can_upstream, can_downstream); + switch (result) { + case SHARE_NONE: + /* + * We aren't sharing our connection at all (e.g. something + * went wrong setting the socket up). Free the upstream + * structure and return NULL. + */ + + if (logtext) { + /* For this result, if 'logtext' is not NULL then it is an + * error message indicating a reason why connection sharing + * couldn't be set up _at all_ */ + logeventf(logctx, + "Could not set up connection sharing: %s", logtext); + } else { + /* Failing that, ds_err and us_err indicate why we + * couldn't be a downstream and an upstream respectively */ + if (ds_err) + logeventf(logctx, "Could not set up connection sharing" + " as downstream: %s", ds_err); + if (us_err) + logeventf(logctx, "Could not set up connection sharing" + " as upstream: %s", us_err); + } + + assert(sock == NULL); + *state = NULL; + sfree(sharestate); + sfree(sockname); + break; + + case SHARE_DOWNSTREAM: + /* + * We are downstream, so free sharestate which it turns out we + * don't need after all, and return the downstream socket as a + * replacement for an ordinary SSH connection. + */ + + /* 'logtext' is a local endpoint address */ + logeventf(logctx, "Using existing shared connection at %s", logtext); + + *state = NULL; + sfree(sharestate); + sfree(sockname); + toret = sock; + break; + + case SHARE_UPSTREAM: + /* + * We are upstream. Set up sharestate properly and pass a copy + * to the caller; return NULL, to tell ssh.c that it has to + * make an ordinary connection after all. + */ + + /* 'logtext' is a local endpoint address */ + logeventf(logctx, "Sharing this connection at %s", logtext); + + *state = sharestate; + sharestate->listensock = sock; + sharestate->connections = newtree234(share_connstate_cmp); + sharestate->server_verstring = NULL; + sharestate->sockname = sockname; + sharestate->nextid = 1; + break; + } + + sfree(logtext); + sfree(ds_err); + sfree(us_err); + return toret; +} diff --git a/ssh/signal-list.h b/ssh/signal-list.h new file mode 100644 index 00000000..b213c34f --- /dev/null +++ b/ssh/signal-list.h @@ -0,0 +1,53 @@ +/* + * List of signal names known to SSH, indicating whether PuTTY's UI + * for special session commands likes to put them in the main specials + * menu or in a submenu (and if the former, what title they have). + * + * This is a separate header file rather than my usual style of a + * parametric list macro, because in this case I need to be able to + * #ifdef out each mode in case it's not defined on a particular + * target system. + * + * If you want only the locally defined signals, #define + * SIGNALS_LOCAL_ONLY before including this header. + */ + +#if !defined SIGNALS_LOCAL_ONLY || defined SIGINT +SIGNAL_MAIN(INT, "Interrupt") +#endif +#if !defined SIGNALS_LOCAL_ONLY || defined SIGTERM +SIGNAL_MAIN(TERM, "Terminate") +#endif +#if !defined SIGNALS_LOCAL_ONLY || defined SIGKILL +SIGNAL_MAIN(KILL, "Kill") +#endif +#if !defined SIGNALS_LOCAL_ONLY || defined SIGQUIT +SIGNAL_MAIN(QUIT, "Quit") +#endif +#if !defined SIGNALS_LOCAL_ONLY || defined SIGHUP +SIGNAL_MAIN(HUP, "Hangup") +#endif +#if !defined SIGNALS_LOCAL_ONLY || defined SIGABRT +SIGNAL_SUB(ABRT) +#endif +#if !defined SIGNALS_LOCAL_ONLY || defined SIGALRM +SIGNAL_SUB(ALRM) +#endif +#if !defined SIGNALS_LOCAL_ONLY || defined SIGFPE +SIGNAL_SUB(FPE) +#endif +#if !defined SIGNALS_LOCAL_ONLY || defined SIGILL +SIGNAL_SUB(ILL) +#endif +#if !defined SIGNALS_LOCAL_ONLY || defined SIGPIPE +SIGNAL_SUB(PIPE) +#endif +#if !defined SIGNALS_LOCAL_ONLY || defined SIGSEGV +SIGNAL_SUB(SEGV) +#endif +#if !defined SIGNALS_LOCAL_ONLY || defined SIGUSR1 +SIGNAL_SUB(USR1) +#endif +#if !defined SIGNALS_LOCAL_ONLY || defined SIGUSR2 +SIGNAL_SUB(USR2) +#endif diff --git a/ssh/ssh.c b/ssh/ssh.c new file mode 100644 index 00000000..b1499a54 --- /dev/null +++ b/ssh/ssh.c @@ -0,0 +1,1248 @@ +/* + * SSH backend. + */ + +#include +#include +#include +#include +#include +#include + +#include "putty.h" +#include "pageant.h" /* for AGENT_MAX_MSGLEN */ +#include "tree234.h" +#include "storage.h" +#include "marshal.h" +#include "ssh.h" +#include "sshcr.h" +#include "bpp.h" +#include "ppl.h" +#include "channel.h" +#ifndef NO_GSSAPI +#include "gssc.h" +#include "gss.h" +#define MIN_CTXT_LIFETIME 5 /* Avoid rekey with short lifetime (seconds) */ +#define GSS_KEX_CAPABLE (1<<0) /* Can do GSS KEX */ +#define GSS_CRED_UPDATED (1<<1) /* Cred updated since previous delegation */ +#define GSS_CTXT_EXPIRES (1<<2) /* Context expires before next timer */ +#define GSS_CTXT_MAYFAIL (1<<3) /* Context may expire during handshake */ +#endif + +struct Ssh { + Socket *s; + Seat *seat; + Conf *conf; + + struct ssh_version_receiver version_receiver; + int remote_bugs; + + Plug plug; + Backend backend; + + Ldisc *ldisc; + LogContext *logctx; + + /* The last list returned from get_specials. */ + SessionSpecial *specials; + + bool bare_connection; + ssh_sharing_state *connshare; + bool attempting_connshare; + +#ifndef NO_GSSAPI + struct ssh_connection_shared_gss_state gss_state; +#endif + + char *savedhost; + int savedport; + char *fullhostname; + + bool fallback_cmd; + int exitcode; + + int version; + int conn_throttle_count; + size_t overall_bufsize; + bool throttled_all; + + /* + * logically_frozen is true if we're not currently _processing_ + * data from the SSH socket (e.g. because a higher layer has asked + * us not to due to ssh_throttle_conn). socket_frozen is true if + * we're not even _reading_ data from the socket (i.e. it should + * always match the value we last passed to sk_set_frozen). + * + * The two differ in that socket_frozen can also become + * temporarily true because of a large backlog in the in_raw + * bufchain, to force no further plug_receive events until the BPP + * input function has had a chance to run. (Some front ends, like + * GTK, can persistently call the network and never get round to + * the toplevel callbacks.) If we've stopped reading from the + * socket for that reason, we absolutely _do_ want to carry on + * processing our input bufchain, because that's the only way + * it'll ever get cleared! + * + * ssh_check_frozen() resets socket_frozen, and should be called + * whenever either of logically_frozen and the bufchain size + * changes. + */ + bool logically_frozen, socket_frozen; + + /* in case we find these out before we have a ConnectionLayer to tell */ + int term_width, term_height; + + bufchain in_raw, out_raw, user_input; + bool pending_close; + IdempotentCallback ic_out_raw; + + PacketLogSettings pls; + struct DataTransferStats stats; + + BinaryPacketProtocol *bpp; + + /* + * base_layer identifies the bottommost packet protocol layer, the + * one connected directly to the BPP's packet queues. Any + * operation that needs to talk to all layers (e.g. free, or + * get_specials) will do it by talking to this, which will + * recursively propagate it if necessary. + */ + PacketProtocolLayer *base_layer; + + /* + * The ConnectionLayer vtable from our connection layer. + */ + ConnectionLayer *cl; + + /* + * A dummy ConnectionLayer that can be used for logging sharing + * downstreams that connect before the real one is ready. + */ + ConnectionLayer cl_dummy; + + /* + * session_started is false until we initialise the main protocol + * layers. So it distinguishes between base_layer==NULL meaning + * that the SSH protocol hasn't been set up _yet_, and + * base_layer==NULL meaning the SSH protocol has run and finished. + * It's also used to mark the point where we stop counting proxy + * command diagnostics as pre-session-startup. + */ + bool session_started; + + Pinger *pinger; + + char *deferred_abort_message; + + bool need_random_unref; +}; + + +#define ssh_logevent(params) ( \ + logevent_and_free((ssh)->logctx, dupprintf params)) + +static void ssh_shutdown(Ssh *ssh); +static void ssh_throttle_all(Ssh *ssh, bool enable, size_t bufsize); +static void ssh_bpp_output_raw_data_callback(void *vctx); + +LogContext *ssh_get_logctx(Ssh *ssh) +{ + return ssh->logctx; +} + +static void ssh_connect_bpp(Ssh *ssh) +{ + ssh->bpp->ssh = ssh; + ssh->bpp->in_raw = &ssh->in_raw; + ssh->bpp->out_raw = &ssh->out_raw; + bufchain_set_callback(ssh->bpp->out_raw, &ssh->ic_out_raw); + ssh->bpp->pls = &ssh->pls; + ssh->bpp->logctx = ssh->logctx; + ssh->bpp->remote_bugs = ssh->remote_bugs; +} + +static void ssh_connect_ppl(Ssh *ssh, PacketProtocolLayer *ppl) +{ + ppl->bpp = ssh->bpp; + ppl->user_input = &ssh->user_input; + ppl->seat = ssh->seat; + ppl->ssh = ssh; + ppl->logctx = ssh->logctx; + ppl->remote_bugs = ssh->remote_bugs; +} + +static void ssh_got_ssh_version(struct ssh_version_receiver *rcv, + int major_version) +{ + Ssh *ssh = container_of(rcv, Ssh, version_receiver); + BinaryPacketProtocol *old_bpp; + PacketProtocolLayer *connection_layer; + + ssh->session_started = true; + + /* + * We don't support choosing a major protocol version dynamically, + * so this should always be the same value we set up in + * connect_to_host(). + */ + assert(ssh->version == major_version); + + old_bpp = ssh->bpp; + ssh->remote_bugs = ssh_verstring_get_bugs(old_bpp); + + if (!ssh->bare_connection) { + if (ssh->version == 2) { + PacketProtocolLayer *userauth_layer, *transport_child_layer; + + /* + * We use the 'simple' variant of the SSH protocol if + * we're asked to, except not if we're also doing + * connection-sharing (either tunnelling our packets over + * an upstream or expecting to be tunnelled over + * ourselves), since then the assumption that we have only + * one channel to worry about is not true after all. + */ + bool is_simple = + (conf_get_bool(ssh->conf, CONF_ssh_simple) && !ssh->connshare); + + ssh->bpp = ssh2_bpp_new(ssh->logctx, &ssh->stats, false); + ssh_connect_bpp(ssh); + +#ifndef NO_GSSAPI + /* Load and pick the highest GSS library on the preference + * list. */ + if (!ssh->gss_state.libs) + ssh->gss_state.libs = ssh_gss_setup(ssh->conf); + ssh->gss_state.lib = NULL; + if (ssh->gss_state.libs->nlibraries > 0) { + int i, j; + for (i = 0; i < ngsslibs; i++) { + int want_id = conf_get_int_int(ssh->conf, + CONF_ssh_gsslist, i); + for (j = 0; j < ssh->gss_state.libs->nlibraries; j++) + if (ssh->gss_state.libs->libraries[j].id == want_id) { + ssh->gss_state.lib = + &ssh->gss_state.libs->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(ssh->gss_state.lib); + } +#endif + + connection_layer = ssh2_connection_new( + ssh, ssh->connshare, is_simple, ssh->conf, + ssh_verstring_get_remote(old_bpp), &ssh->cl); + ssh_connect_ppl(ssh, connection_layer); + + if (conf_get_bool(ssh->conf, CONF_ssh_no_userauth)) { + userauth_layer = NULL; + transport_child_layer = connection_layer; + } else { + char *username = get_remote_username(ssh->conf); + + userauth_layer = ssh2_userauth_new( + connection_layer, ssh->savedhost, ssh->fullhostname, + conf_get_filename(ssh->conf, CONF_keyfile), + conf_get_bool(ssh->conf, CONF_ssh_show_banner), + conf_get_bool(ssh->conf, CONF_tryagent), username, + conf_get_bool(ssh->conf, CONF_change_username), + conf_get_bool(ssh->conf, CONF_try_ki_auth), +#ifndef NO_GSSAPI + conf_get_bool(ssh->conf, CONF_try_gssapi_auth), + conf_get_bool(ssh->conf, CONF_try_gssapi_kex), + conf_get_bool(ssh->conf, CONF_gssapifwd), + &ssh->gss_state +#else + false, + false, + false, + NULL +#endif + ); + ssh_connect_ppl(ssh, userauth_layer); + transport_child_layer = userauth_layer; + + sfree(username); + } + + ssh->base_layer = ssh2_transport_new( + ssh->conf, ssh->savedhost, ssh->savedport, + ssh->fullhostname, + ssh_verstring_get_local(old_bpp), + ssh_verstring_get_remote(old_bpp), +#ifndef NO_GSSAPI + &ssh->gss_state, +#else + NULL, +#endif + &ssh->stats, transport_child_layer, NULL); + ssh_connect_ppl(ssh, ssh->base_layer); + + if (userauth_layer) + ssh2_userauth_set_transport_layer(userauth_layer, + ssh->base_layer); + + } else { + + ssh->bpp = ssh1_bpp_new(ssh->logctx); + ssh_connect_bpp(ssh); + + connection_layer = ssh1_connection_new(ssh, ssh->conf, &ssh->cl); + ssh_connect_ppl(ssh, connection_layer); + + ssh->base_layer = ssh1_login_new( + ssh->conf, ssh->savedhost, ssh->savedport, connection_layer); + ssh_connect_ppl(ssh, ssh->base_layer); + + } + + } else { + ssh->bpp = ssh2_bare_bpp_new(ssh->logctx); + ssh_connect_bpp(ssh); + + connection_layer = ssh2_connection_new( + ssh, ssh->connshare, false, ssh->conf, + ssh_verstring_get_remote(old_bpp), &ssh->cl); + ssh_connect_ppl(ssh, connection_layer); + ssh->base_layer = connection_layer; + } + + /* Connect the base layer - whichever it is - to the BPP, and set + * up its selfptr. */ + ssh->base_layer->selfptr = &ssh->base_layer; + ssh_ppl_setup_queues(ssh->base_layer, &ssh->bpp->in_pq, &ssh->bpp->out_pq); + + seat_update_specials_menu(ssh->seat); + ssh->pinger = pinger_new(ssh->conf, &ssh->backend); + + queue_idempotent_callback(&ssh->bpp->ic_in_raw); + ssh_ppl_process_queue(ssh->base_layer); + + /* Pass in the initial terminal size, if we knew it already. */ + ssh_terminal_size(ssh->cl, ssh->term_width, ssh->term_height); + + ssh_bpp_free(old_bpp); +} + +void ssh_check_frozen(Ssh *ssh) +{ + if (!ssh->s) + return; + + bool prev_frozen = ssh->socket_frozen; + ssh->socket_frozen = (ssh->logically_frozen || + bufchain_size(&ssh->in_raw) > SSH_MAX_BACKLOG); + sk_set_frozen(ssh->s, ssh->socket_frozen); + if (prev_frozen && !ssh->socket_frozen && ssh->bpp) { + /* + * If we've just unfrozen, process any SSH connection data + * that was stashed in our queue while we were frozen. + */ + queue_idempotent_callback(&ssh->bpp->ic_in_raw); + } +} + +void ssh_conn_processed_data(Ssh *ssh) +{ + ssh_check_frozen(ssh); +} + +static void ssh_bpp_output_raw_data_callback(void *vctx) +{ + Ssh *ssh = (Ssh *)vctx; + + if (!ssh->s) + return; + + while (bufchain_size(&ssh->out_raw) > 0) { + size_t backlog; + + ptrlen data = bufchain_prefix(&ssh->out_raw); + + if (ssh->logctx) + log_packet(ssh->logctx, PKT_OUTGOING, -1, NULL, data.ptr, data.len, + 0, NULL, NULL, 0, NULL); + backlog = sk_write(ssh->s, data.ptr, data.len); + + bufchain_consume(&ssh->out_raw, data.len); + + if (backlog > SSH_MAX_BACKLOG) { + ssh_throttle_all(ssh, true, backlog); + return; + } + } + + ssh_check_frozen(ssh); + + if (ssh->pending_close) { + sk_close(ssh->s); + ssh->s = NULL; + } +} + +static void ssh_shutdown_internal(Ssh *ssh) +{ + expire_timer_context(ssh); + + if (ssh->connshare) { + sharestate_free(ssh->connshare); + ssh->connshare = NULL; + } + + if (ssh->pinger) { + pinger_free(ssh->pinger); + ssh->pinger = NULL; + } + + /* + * We only need to free the base PPL, which will free the others + * (if any) transitively. + */ + if (ssh->base_layer) { + ssh_ppl_free(ssh->base_layer); + ssh->base_layer = NULL; + } + + ssh->cl = NULL; +} + +static void ssh_shutdown(Ssh *ssh) +{ + ssh_shutdown_internal(ssh); + + if (ssh->bpp) { + ssh_bpp_free(ssh->bpp); + ssh->bpp = NULL; + } + + if (ssh->s) { + sk_close(ssh->s); + ssh->s = NULL; + } + + bufchain_clear(&ssh->in_raw); + bufchain_clear(&ssh->out_raw); + bufchain_clear(&ssh->user_input); +} + +static void ssh_initiate_connection_close(Ssh *ssh) +{ + /* Wind up everything above the BPP. */ + ssh_shutdown_internal(ssh); + + /* Force any remaining queued SSH packets through the BPP, and + * schedule closing the network socket after they go out. */ + ssh_bpp_handle_output(ssh->bpp); + ssh->pending_close = true; + queue_idempotent_callback(&ssh->ic_out_raw); + + /* Now we expect the other end to close the connection too in + * response, so arrange that we'll receive notification of that + * via ssh_remote_eof. */ + ssh->bpp->expect_close = true; +} + +#define GET_FORMATTED_MSG \ + char *msg; \ + va_list ap; \ + va_start(ap, fmt); \ + msg = dupvprintf(fmt, ap); \ + va_end(ap); \ + ((void)0) /* eat trailing semicolon */ + +void ssh_remote_error(Ssh *ssh, const char *fmt, ...) +{ + if (ssh->base_layer || !ssh->session_started) { + GET_FORMATTED_MSG; + + /* Error messages sent by the remote don't count as clean exits */ + ssh->exitcode = 128; + + /* Close the socket immediately, since the server has already + * closed its end (or is about to). */ + ssh_shutdown(ssh); + + logevent(ssh->logctx, msg); + seat_connection_fatal(ssh->seat, "%s", msg); + sfree(msg); + } +} + +void ssh_remote_eof(Ssh *ssh, const char *fmt, ...) +{ + if (ssh->base_layer || !ssh->session_started) { + GET_FORMATTED_MSG; + + /* EOF from the remote, if we were expecting it, does count as + * a clean exit */ + ssh->exitcode = 0; + + /* Close the socket immediately, since the server has already + * closed its end. */ + ssh_shutdown(ssh); + + logevent(ssh->logctx, msg); + sfree(msg); + seat_notify_remote_exit(ssh->seat); + } else { + /* This is responding to EOF after we've already seen some + * other reason for terminating the session. */ + ssh_shutdown(ssh); + } +} + +void ssh_proto_error(Ssh *ssh, const char *fmt, ...) +{ + if (ssh->base_layer || !ssh->session_started) { + GET_FORMATTED_MSG; + + ssh->exitcode = 128; + + ssh_bpp_queue_disconnect(ssh->bpp, msg, + SSH2_DISCONNECT_PROTOCOL_ERROR); + ssh_initiate_connection_close(ssh); + + logevent(ssh->logctx, msg); + seat_connection_fatal(ssh->seat, "%s", msg); + sfree(msg); + } +} + +void ssh_sw_abort(Ssh *ssh, const char *fmt, ...) +{ + if (ssh->base_layer || !ssh->session_started) { + GET_FORMATTED_MSG; + + ssh->exitcode = 128; + + ssh_initiate_connection_close(ssh); + + logevent(ssh->logctx, msg); + seat_connection_fatal(ssh->seat, "%s", msg); + sfree(msg); + + seat_notify_remote_exit(ssh->seat); + } +} + +void ssh_user_close(Ssh *ssh, const char *fmt, ...) +{ + if (ssh->base_layer || !ssh->session_started) { + GET_FORMATTED_MSG; + + /* Closing the connection due to user action, even if the + * action is the user aborting during authentication prompts, + * does count as a clean exit - except that this is also how + * we signal ordinary session termination, in which case we + * should use the exit status already sent from the main + * session (if any). */ + if (ssh->exitcode < 0) + ssh->exitcode = 0; + + ssh_initiate_connection_close(ssh); + + logevent(ssh->logctx, msg); + sfree(msg); + + seat_notify_remote_exit(ssh->seat); + } +} + +static void ssh_deferred_abort_callback(void *vctx) +{ + Ssh *ssh = (Ssh *)vctx; + char *msg = ssh->deferred_abort_message; + ssh->deferred_abort_message = NULL; + ssh_sw_abort(ssh, "%s", msg); + sfree(msg); +} + +void ssh_sw_abort_deferred(Ssh *ssh, const char *fmt, ...) +{ + if (!ssh->deferred_abort_message) { + GET_FORMATTED_MSG; + ssh->deferred_abort_message = msg; + queue_toplevel_callback(ssh_deferred_abort_callback, ssh); + } +} + +static void ssh_socket_log(Plug *plug, PlugLogType type, SockAddr *addr, + int port, const char *error_msg, int error_code) +{ + Ssh *ssh = container_of(plug, Ssh, plug); + + /* + * While we're attempting connection sharing, don't loudly log + * everything that happens. Real TCP connections need to be logged + * when we _start_ trying to connect, because it might be ages + * before they respond if something goes wrong; but connection + * sharing is local and quick to respond, and it's sufficient to + * simply wait and see whether it worked afterwards. + */ + + if (!ssh->attempting_connshare) + backend_socket_log(ssh->seat, ssh->logctx, type, addr, port, + error_msg, error_code, ssh->conf, + ssh->session_started); +} + +static void ssh_closing(Plug *plug, const char *error_msg, int error_code, + bool calling_back) +{ + Ssh *ssh = container_of(plug, Ssh, plug); + if (error_msg) { + ssh_remote_error(ssh, "%s", error_msg); + } else if (ssh->bpp) { + ssh->bpp->input_eof = true; + queue_idempotent_callback(&ssh->bpp->ic_in_raw); + } +} + +static void ssh_receive(Plug *plug, int urgent, const char *data, size_t len) +{ + Ssh *ssh = container_of(plug, Ssh, plug); + + /* Log raw data, if we're in that mode. */ + if (ssh->logctx) + log_packet(ssh->logctx, PKT_INCOMING, -1, NULL, data, len, + 0, NULL, NULL, 0, NULL); + + bufchain_add(&ssh->in_raw, data, len); + if (!ssh->logically_frozen && ssh->bpp) + queue_idempotent_callback(&ssh->bpp->ic_in_raw); + + ssh_check_frozen(ssh); +} + +static void ssh_sent(Plug *plug, size_t bufsize) +{ + Ssh *ssh = container_of(plug, Ssh, plug); + /* + * If the send backlog on the SSH socket itself clears, we should + * unthrottle the whole world if it was throttled. Also trigger an + * extra call to the consumer of the BPP's output, to try to send + * some more data off its bufchain. + */ + if (bufsize < SSH_MAX_BACKLOG) { + ssh_throttle_all(ssh, false, bufsize); + queue_idempotent_callback(&ssh->ic_out_raw); + } +} + +static void ssh_hostport_setup(const char *host, int port, Conf *conf, + char **savedhost, int *savedport, + char **loghost_ret) +{ + char *loghost = conf_get_str(conf, CONF_loghost); + if (loghost_ret) + *loghost_ret = loghost; + + if (*loghost) { + char *tmphost; + char *colon; + + tmphost = dupstr(loghost); + *savedport = 22; /* default ssh port */ + + /* + * A colon suffix on the hostname string also lets us affect + * savedport. (Unless there are multiple colons, in which case + * we assume this is an unbracketed IPv6 literal.) + */ + colon = host_strrchr(tmphost, ':'); + if (colon && colon == host_strchr(tmphost, ':')) { + *colon++ = '\0'; + if (*colon) + *savedport = atoi(colon); + } + + *savedhost = host_strduptrim(tmphost); + sfree(tmphost); + } else { + *savedhost = host_strduptrim(host); + if (port < 0) + port = 22; /* default ssh port */ + *savedport = port; + } +} + +static bool ssh_test_for_upstream(const char *host, int port, Conf *conf) +{ + char *savedhost; + int savedport; + bool ret; + + random_ref(); /* platform may need this to determine share socket name */ + ssh_hostport_setup(host, port, conf, &savedhost, &savedport, NULL); + ret = ssh_share_test_for_upstream(savedhost, savedport, conf); + sfree(savedhost); + random_unref(); + + return ret; +} + +static char *ssh_close_warn_text(Backend *be) +{ + Ssh *ssh = container_of(be, Ssh, backend); + if (!ssh->connshare) + return NULL; + int ndowns = share_ndownstreams(ssh->connshare); + if (ndowns == 0) + return NULL; + char *msg = dupprintf("This will also close %d downstream connection%s.", + ndowns, ndowns==1 ? "" : "s"); + return msg; +} + +static const PlugVtable Ssh_plugvt = { + .log = ssh_socket_log, + .closing = ssh_closing, + .receive = ssh_receive, + .sent = ssh_sent, +}; + +/* + * 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 char *connect_to_host( + Ssh *ssh, const char *host, int port, char **realhost, + bool nodelay, bool keepalive) +{ + SockAddr *addr; + const char *err; + char *loghost; + int addressfamily, sshprot; + + ssh_hostport_setup(host, port, ssh->conf, + &ssh->savedhost, &ssh->savedport, &loghost); + + ssh->plug.vt = &Ssh_plugvt; + + /* + * Try connection-sharing, in case that means we don't open a + * socket after all. ssh_connection_sharing_init will connect to a + * previously established upstream if it can, and failing that, + * establish a listening socket for _us_ to be the upstream. In + * the latter case it will return NULL just as if it had done + * nothing, because here we only need to care if we're a + * downstream and need to do our connection setup differently. + */ + ssh->connshare = NULL; + ssh->attempting_connshare = true; /* affects socket logging behaviour */ + ssh->s = ssh_connection_sharing_init( + ssh->savedhost, ssh->savedport, ssh->conf, ssh->logctx, + &ssh->plug, &ssh->connshare); + if (ssh->connshare) + ssh_connshare_provide_connlayer(ssh->connshare, &ssh->cl_dummy); + ssh->attempting_connshare = false; + if (ssh->s != NULL) { + /* + * We are a downstream. + */ + ssh->bare_connection = true; + ssh->fullhostname = NULL; + *realhost = dupstr(host); /* best we can do */ + + if (seat_verbose(ssh->seat) || seat_interactive(ssh->seat)) { + /* In an interactive session, or in verbose mode, announce + * in the console window that we're a sharing downstream, + * to avoid confusing users as to why this session doesn't + * behave in quite the usual way. */ + const char *msg = + "Reusing a shared connection to this server.\r\n"; + seat_stderr_pl(ssh->seat, ptrlen_from_asciz(msg)); + } + } else { + /* + * We're not a downstream, so open a normal socket. + */ + + /* + * Try to find host. + */ + addressfamily = conf_get_int(ssh->conf, CONF_addressfamily); + addr = name_lookup(host, port, realhost, ssh->conf, addressfamily, + ssh->logctx, "SSH connection"); + if ((err = sk_addr_error(addr)) != NULL) { + sk_addr_free(addr); + return dupstr(err); + } + ssh->fullhostname = dupstr(*realhost); /* save in case of GSSAPI */ + + ssh->s = new_connection(addr, *realhost, port, + false, true, nodelay, keepalive, + &ssh->plug, ssh->conf); + if ((err = sk_socket_error(ssh->s)) != NULL) { + ssh->s = NULL; + seat_notify_remote_exit(ssh->seat); + return dupstr(err); + } + } + + /* + * The SSH version number is always fixed (since we no longer support + * fallback between versions), so set it now. + */ + sshprot = conf_get_int(ssh->conf, CONF_sshprot); + assert(sshprot == 0 || sshprot == 3); + if (sshprot == 0) + /* SSH-1 only */ + ssh->version = 1; + if (sshprot == 3 || ssh->bare_connection) { + /* SSH-2 only */ + ssh->version = 2; + } + + /* + * Set up the initial BPP that will do the version string + * exchange, and get it started so that it can send the outgoing + * version string early if it wants to. + */ + ssh->version_receiver.got_ssh_version = ssh_got_ssh_version; + ssh->bpp = ssh_verstring_new( + ssh->conf, ssh->logctx, ssh->bare_connection, + ssh->version == 1 ? "1.5" : "2.0", &ssh->version_receiver, + false, "PuTTY"); + ssh_connect_bpp(ssh); + queue_idempotent_callback(&ssh->bpp->ic_in_raw); + + /* + * loghost, if configured, overrides realhost. + */ + if (*loghost) { + sfree(*realhost); + *realhost = dupstr(loghost); + } + + return NULL; +} + +/* + * Throttle or unthrottle the SSH connection. + */ +void ssh_throttle_conn(Ssh *ssh, int adjust) +{ + int old_count = ssh->conn_throttle_count; + bool frozen; + + ssh->conn_throttle_count += adjust; + assert(ssh->conn_throttle_count >= 0); + + if (ssh->conn_throttle_count && !old_count) { + frozen = true; + } else if (!ssh->conn_throttle_count && old_count) { + frozen = false; + } else { + return; /* don't change current frozen state */ + } + + ssh->logically_frozen = frozen; + ssh_check_frozen(ssh); +} + +/* + * Throttle or unthrottle _all_ local data streams (for when sends + * on the SSH connection itself back up). + */ +static void ssh_throttle_all(Ssh *ssh, bool enable, size_t bufsize) +{ + if (enable == ssh->throttled_all) + return; + ssh->throttled_all = enable; + ssh->overall_bufsize = bufsize; + + ssh_throttle_all_channels(ssh->cl, enable); +} + +static void ssh_cache_conf_values(Ssh *ssh) +{ + ssh->pls.omit_passwords = conf_get_bool(ssh->conf, CONF_logomitpass); + ssh->pls.omit_data = conf_get_bool(ssh->conf, CONF_logomitdata); +} + +bool ssh_is_bare(Ssh *ssh) +{ + return ssh->backend.vt->protocol == PROT_SSHCONN; +} + +/* Dummy connlayer must provide ssh_sharing_no_more_downstreams, + * because it might be called early due to plink -shareexists */ +static void dummy_sharing_no_more_downstreams(ConnectionLayer *cl) {} +static const ConnectionLayerVtable dummy_connlayer_vtable = { + .sharing_no_more_downstreams = dummy_sharing_no_more_downstreams, +}; + +/* + * Called to set up the connection. + * + * Returns an error message, or NULL on success. + */ +static char *ssh_init(const BackendVtable *vt, Seat *seat, + Backend **backend_handle, LogContext *logctx, + Conf *conf, const char *host, int port, + char **realhost, bool nodelay, bool keepalive) +{ + Ssh *ssh; + + ssh = snew(Ssh); + memset(ssh, 0, sizeof(Ssh)); + + ssh->conf = conf_copy(conf); + ssh_cache_conf_values(ssh); + ssh->exitcode = -1; + ssh->pls.kctx = SSH2_PKTCTX_NOKEX; + ssh->pls.actx = SSH2_PKTCTX_NOAUTH; + bufchain_init(&ssh->in_raw); + bufchain_init(&ssh->out_raw); + bufchain_init(&ssh->user_input); + ssh->ic_out_raw.fn = ssh_bpp_output_raw_data_callback; + ssh->ic_out_raw.ctx = ssh; + + ssh->term_width = conf_get_int(ssh->conf, CONF_width); + ssh->term_height = conf_get_int(ssh->conf, CONF_height); + + ssh->backend.vt = vt; + *backend_handle = &ssh->backend; + + ssh->bare_connection = (vt->protocol == PROT_SSHCONN); + + ssh->seat = seat; + ssh->cl_dummy.vt = &dummy_connlayer_vtable; + ssh->cl_dummy.logctx = ssh->logctx = logctx; + + random_ref(); /* do this now - may be needed by sharing setup code */ + ssh->need_random_unref = true; + + char *conn_err = connect_to_host( + ssh, host, port, realhost, nodelay, keepalive); + if (conn_err) { + /* Call random_unref now instead of waiting until the caller + * frees this useless Ssh object, in case the caller is + * impatient and just exits without bothering, in which case + * the random seed won't be re-saved. */ + ssh->need_random_unref = false; + random_unref(); + return conn_err; + } + + return NULL; +} + +static void ssh_free(Backend *be) +{ + Ssh *ssh = container_of(be, Ssh, backend); + bool need_random_unref; + + ssh_shutdown(ssh); + + conf_free(ssh->conf); + if (ssh->connshare) + sharestate_free(ssh->connshare); + sfree(ssh->savedhost); + sfree(ssh->fullhostname); + sfree(ssh->specials); + +#ifndef NO_GSSAPI + if (ssh->gss_state.srv_name) + ssh->gss_state.lib->release_name( + ssh->gss_state.lib, &ssh->gss_state.srv_name); + if (ssh->gss_state.ctx != NULL) + ssh->gss_state.lib->release_cred( + ssh->gss_state.lib, &ssh->gss_state.ctx); + if (ssh->gss_state.libs) + ssh_gss_cleanup(ssh->gss_state.libs); +#endif + + sfree(ssh->deferred_abort_message); + + delete_callbacks_for_context(ssh); /* likely to catch ic_out_raw */ + + need_random_unref = ssh->need_random_unref; + sfree(ssh); + + if (need_random_unref) + random_unref(); +} + +/* + * Reconfigure the SSH backend. + */ +static void ssh_reconfig(Backend *be, Conf *conf) +{ + Ssh *ssh = container_of(be, Ssh, backend); + + if (ssh->pinger) + pinger_reconfig(ssh->pinger, ssh->conf, conf); + + ssh_ppl_reconfigure(ssh->base_layer, conf); + + conf_free(ssh->conf); + ssh->conf = conf_copy(conf); + ssh_cache_conf_values(ssh); +} + +/* + * Called to send data down the SSH connection. + */ +static size_t ssh_send(Backend *be, const char *buf, size_t len) +{ + Ssh *ssh = container_of(be, Ssh, backend); + + if (ssh == NULL || ssh->s == NULL) + return 0; + + bufchain_add(&ssh->user_input, buf, len); + if (ssh->base_layer) + ssh_ppl_got_user_input(ssh->base_layer); + + return backend_sendbuffer(&ssh->backend); +} + +/* + * Called to query the current amount of buffered stdin data. + */ +static size_t ssh_sendbuffer(Backend *be) +{ + Ssh *ssh = container_of(be, Ssh, backend); + size_t backlog; + + if (!ssh || !ssh->s || !ssh->cl) + return 0; + + backlog = ssh_stdin_backlog(ssh->cl); + + if (ssh->base_layer) + backlog += ssh_ppl_queued_data_size(ssh->base_layer); + + /* + * If the SSH socket itself has backed up, add the total backup + * size on that to any individual buffer on the stdin channel. + */ + if (ssh->throttled_all) + backlog += ssh->overall_bufsize; + + return backlog; +} + +/* + * Called to set the size of the window from SSH's POV. + */ +static void ssh_size(Backend *be, int width, int height) +{ + Ssh *ssh = container_of(be, Ssh, backend); + + ssh->term_width = width; + ssh->term_height = height; + if (ssh->cl) + ssh_terminal_size(ssh->cl, ssh->term_width, ssh->term_height); +} + +struct ssh_add_special_ctx { + SessionSpecial *specials; + size_t nspecials, specials_size; +}; + +static void ssh_add_special(void *vctx, const char *text, + SessionSpecialCode code, int arg) +{ + struct ssh_add_special_ctx *ctx = (struct ssh_add_special_ctx *)vctx; + SessionSpecial *spec; + + sgrowarray(ctx->specials, ctx->specials_size, ctx->nspecials); + spec = &ctx->specials[ctx->nspecials++]; + spec->name = text; + spec->code = code; + spec->arg = arg; +} + +/* + * Return a list of the special codes that make sense in this + * protocol. + */ +static const SessionSpecial *ssh_get_specials(Backend *be) +{ + Ssh *ssh = container_of(be, Ssh, backend); + + /* + * Ask all our active protocol layers what specials they've got, + * and amalgamate the list into one combined one. + */ + + struct ssh_add_special_ctx ctx[1]; + + ctx->specials = NULL; + ctx->nspecials = ctx->specials_size = 0; + + if (ssh->base_layer) + ssh_ppl_get_specials(ssh->base_layer, ssh_add_special, ctx); + + if (ctx->specials) { + /* If the list is non-empty, terminate it with a SS_EXITMENU. */ + ssh_add_special(ctx, NULL, SS_EXITMENU, 0); + } + + sfree(ssh->specials); + ssh->specials = ctx->specials; + return ssh->specials; +} + +/* + * Send special codes. + */ +static void ssh_special(Backend *be, SessionSpecialCode code, int arg) +{ + Ssh *ssh = container_of(be, Ssh, backend); + + if (ssh->base_layer) + ssh_ppl_special_cmd(ssh->base_layer, code, arg); +} + +/* + * This is called when the seat's output channel manages to clear some + * backlog. + */ +static void ssh_unthrottle(Backend *be, size_t bufsize) +{ + Ssh *ssh = container_of(be, Ssh, backend); + + if (ssh->cl) + ssh_stdout_unthrottle(ssh->cl, bufsize); +} + +static bool ssh_connected(Backend *be) +{ + Ssh *ssh = container_of(be, Ssh, backend); + return ssh->s != NULL; +} + +static bool ssh_sendok(Backend *be) +{ + Ssh *ssh = container_of(be, Ssh, backend); + return ssh->base_layer && ssh_ppl_want_user_input(ssh->base_layer); +} + +void ssh_ldisc_update(Ssh *ssh) +{ + /* Called when the connection layer wants to propagate an update + * to the line discipline options */ + if (ssh->ldisc) + ldisc_echoedit_update(ssh->ldisc); +} + +static bool ssh_ldisc(Backend *be, int option) +{ + Ssh *ssh = container_of(be, Ssh, backend); + return ssh->cl ? ssh_ldisc_option(ssh->cl, option) : false; +} + +static void ssh_provide_ldisc(Backend *be, Ldisc *ldisc) +{ + Ssh *ssh = container_of(be, Ssh, backend); + ssh->ldisc = ldisc; +} + +void ssh_got_exitcode(Ssh *ssh, int exitcode) +{ + ssh->exitcode = exitcode; +} + +static int ssh_return_exitcode(Backend *be) +{ + Ssh *ssh = container_of(be, Ssh, backend); + if (ssh->s && (!ssh->session_started || ssh->base_layer)) + return -1; + else + return (ssh->exitcode >= 0 ? ssh->exitcode : INT_MAX); +} + +/* + * cfg_info for SSH is the protocol running in this session. + * (1 or 2 for the full SSH-1 or SSH-2 protocol; -1 for the bare + * SSH-2 connection protocol, i.e. a downstream; 0 for not-decided-yet.) + */ +static int ssh_cfg_info(Backend *be) +{ + Ssh *ssh = container_of(be, Ssh, backend); + if (ssh->version == 0) + return 0; /* don't know yet */ + else if (ssh->bare_connection) + return -1; + else + 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 bool ssh_fallback_cmd(Backend *be) +{ + Ssh *ssh = container_of(be, Ssh, backend); + return ssh->fallback_cmd; +} + +void ssh_got_fallback_cmd(Ssh *ssh) +{ + ssh->fallback_cmd = true; +} + +const BackendVtable ssh_backend = { + .init = ssh_init, + .free = ssh_free, + .reconfig = ssh_reconfig, + .send = ssh_send, + .sendbuffer = ssh_sendbuffer, + .size = ssh_size, + .special = ssh_special, + .get_specials = ssh_get_specials, + .connected = ssh_connected, + .exitcode = ssh_return_exitcode, + .sendok = ssh_sendok, + .ldisc_option_state = ssh_ldisc, + .provide_ldisc = ssh_provide_ldisc, + .unthrottle = ssh_unthrottle, + .cfg_info = ssh_cfg_info, + .test_for_upstream = ssh_test_for_upstream, + .close_warn_text = ssh_close_warn_text, + .id = "ssh", + .displayname = "SSH", + .protocol = PROT_SSH, + .default_port = 22, +}; + +const BackendVtable sshconn_backend = { + .init = ssh_init, + .free = ssh_free, + .reconfig = ssh_reconfig, + .send = ssh_send, + .sendbuffer = ssh_sendbuffer, + .size = ssh_size, + .special = ssh_special, + .get_specials = ssh_get_specials, + .connected = ssh_connected, + .exitcode = ssh_return_exitcode, + .sendok = ssh_sendok, + .ldisc_option_state = ssh_ldisc, + .provide_ldisc = ssh_provide_ldisc, + .unthrottle = ssh_unthrottle, + .cfg_info = ssh_cfg_info, + .test_for_upstream = ssh_test_for_upstream, + .close_warn_text = ssh_close_warn_text, + .id = "ssh-connection", + .displayname = "Bare ssh-connection", + .protocol = PROT_SSHCONN, +}; diff --git a/ssh/transient-hostkey-cache.c b/ssh/transient-hostkey-cache.c new file mode 100644 index 00000000..2e77fdf9 --- /dev/null +++ b/ssh/transient-hostkey-cache.c @@ -0,0 +1,126 @@ +/* + * Data structure managing host keys in sessions based on GSSAPI KEX. + * + * In a session we started with a GSSAPI key exchange, the concept of + * 'host key' has completely different lifetime and security semantics + * from the usual ones. Per RFC 4462 section 2.1, we assume that any + * host key delivered to us in the course of a GSSAPI key exchange is + * _solely_ there to use as a transient fallback within the same + * session, if at the time of a subsequent rekey the GSS credentials + * are temporarily invalid and so a non-GSS KEX method has to be used. + * + * In particular, in a GSS-based SSH deployment, host keys may not + * even _be_ persistent identities for the server; it would be + * legitimate for a server to generate a fresh one routinely if it + * wanted to, like SSH-1 server keys. + * + * So, in this mode, we never touch the persistent host key cache at + * all, either to check keys against it _or_ to store keys in it. + * Instead, we maintain an in-memory cache of host keys that have been + * mentioned in GSS key exchanges within this particular session, and + * we permit precisely those host keys in non-GSS rekeys. + */ + +#include + +#include "putty.h" +#include "ssh.h" + +struct ssh_transient_hostkey_cache { + tree234 *cache; +}; + +struct ssh_transient_hostkey_cache_entry { + const ssh_keyalg *alg; + strbuf *pub_blob; +}; + +static int ssh_transient_hostkey_cache_cmp(void *av, void *bv) +{ + const struct ssh_transient_hostkey_cache_entry + *a = (const struct ssh_transient_hostkey_cache_entry *)av, + *b = (const struct ssh_transient_hostkey_cache_entry *)bv; + return strcmp(a->alg->ssh_id, b->alg->ssh_id); +} + +static int ssh_transient_hostkey_cache_find(void *av, void *bv) +{ + const ssh_keyalg *aalg = (const ssh_keyalg *)av; + const struct ssh_transient_hostkey_cache_entry + *b = (const struct ssh_transient_hostkey_cache_entry *)bv; + return strcmp(aalg->ssh_id, b->alg->ssh_id); +} + +ssh_transient_hostkey_cache *ssh_transient_hostkey_cache_new(void) +{ + ssh_transient_hostkey_cache *thc = snew(ssh_transient_hostkey_cache); + thc->cache = newtree234(ssh_transient_hostkey_cache_cmp); + return thc; +} + +void ssh_transient_hostkey_cache_free(ssh_transient_hostkey_cache *thc) +{ + struct ssh_transient_hostkey_cache_entry *ent; + while ((ent = delpos234(thc->cache, 0)) != NULL) { + strbuf_free(ent->pub_blob); + sfree(ent); + } + freetree234(thc->cache); + sfree(thc); +} + +void ssh_transient_hostkey_cache_add( + ssh_transient_hostkey_cache *thc, ssh_key *key) +{ + struct ssh_transient_hostkey_cache_entry *ent, *retd; + + if ((ent = find234(thc->cache, (void *)ssh_key_alg(key), + ssh_transient_hostkey_cache_find)) != NULL) { + del234(thc->cache, ent); + strbuf_free(ent->pub_blob); + sfree(ent); + } + + ent = snew(struct ssh_transient_hostkey_cache_entry); + ent->alg = ssh_key_alg(key); + ent->pub_blob = strbuf_new(); + ssh_key_public_blob(key, BinarySink_UPCAST(ent->pub_blob)); + retd = add234(thc->cache, ent); + assert(retd == ent); +} + +bool ssh_transient_hostkey_cache_verify( + ssh_transient_hostkey_cache *thc, ssh_key *key) +{ + struct ssh_transient_hostkey_cache_entry *ent; + bool toret = false; + + if ((ent = find234(thc->cache, (void *)ssh_key_alg(key), + ssh_transient_hostkey_cache_find)) != NULL) { + strbuf *this_blob = strbuf_new(); + ssh_key_public_blob(key, BinarySink_UPCAST(this_blob)); + + if (this_blob->len == ent->pub_blob->len && + !memcmp(this_blob->s, ent->pub_blob->s, + this_blob->len)) + toret = true; + + strbuf_free(this_blob); + } + + return toret; +} + +bool ssh_transient_hostkey_cache_has( + ssh_transient_hostkey_cache *thc, const ssh_keyalg *alg) +{ + struct ssh_transient_hostkey_cache_entry *ent = + find234(thc->cache, (void *)alg, + ssh_transient_hostkey_cache_find); + return ent != NULL; +} + +bool ssh_transient_hostkey_cache_non_empty(ssh_transient_hostkey_cache *thc) +{ + return count234(thc->cache) > 0; +} diff --git a/ssh/transport2.c b/ssh/transport2.c new file mode 100644 index 00000000..fcd2667a --- /dev/null +++ b/ssh/transport2.c @@ -0,0 +1,2181 @@ +/* + * Packet protocol layer for the SSH-2 transport protocol (RFC 4253). + */ + +#include + +#include "putty.h" +#include "ssh.h" +#include "bpp.h" +#include "ppl.h" +#include "sshcr.h" +#include "server.h" +#include "storage.h" +#include "transport2.h" +#include "mpint.h" + +const struct ssh_signkey_with_user_pref_id ssh2_hostkey_algs[] = { + #define ARRAYENT_HOSTKEY_ALGORITHM(type, alg) { &alg, type }, + HOSTKEY_ALGORITHMS(ARRAYENT_HOSTKEY_ALGORITHM) +}; + +const static ssh2_macalg *const macs[] = { + &ssh_hmac_sha256, &ssh_hmac_sha1, &ssh_hmac_sha1_96, &ssh_hmac_md5 +}; +const static ssh2_macalg *const buggymacs[] = { + &ssh_hmac_sha1_buggy, &ssh_hmac_sha1_96_buggy, &ssh_hmac_md5 +}; + +static ssh_compressor *ssh_comp_none_init(void) +{ + return NULL; +} +static void ssh_comp_none_cleanup(ssh_compressor *handle) +{ +} +static ssh_decompressor *ssh_decomp_none_init(void) +{ + return NULL; +} +static void ssh_decomp_none_cleanup(ssh_decompressor *handle) +{ +} +static void ssh_comp_none_block(ssh_compressor *handle, + const unsigned char *block, int len, + unsigned char **outblock, int *outlen, + int minlen) +{ +} +static bool ssh_decomp_none_block(ssh_decompressor *handle, + const unsigned char *block, int len, + unsigned char **outblock, int *outlen) +{ + return false; +} +static const ssh_compression_alg ssh_comp_none = { + .name = "none", + .delayed_name = NULL, + .compress_new = ssh_comp_none_init, + .compress_free = ssh_comp_none_cleanup, + .compress = ssh_comp_none_block, + .decompress_new = ssh_decomp_none_init, + .decompress_free = ssh_decomp_none_cleanup, + .decompress = ssh_decomp_none_block, + .text_name = NULL, +}; +const static ssh_compression_alg *const compressions[] = { + &ssh_zlib, &ssh_comp_none +}; + +static void ssh2_transport_free(PacketProtocolLayer *); +static void ssh2_transport_process_queue(PacketProtocolLayer *); +static bool ssh2_transport_get_specials( + PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx); +static void ssh2_transport_special_cmd(PacketProtocolLayer *ppl, + SessionSpecialCode code, int arg); +static bool ssh2_transport_want_user_input(PacketProtocolLayer *ppl); +static void ssh2_transport_got_user_input(PacketProtocolLayer *ppl); +static void ssh2_transport_reconfigure(PacketProtocolLayer *ppl, Conf *conf); +static size_t ssh2_transport_queued_data_size(PacketProtocolLayer *ppl); + +static void ssh2_transport_set_max_data_size(struct ssh2_transport_state *s); +static unsigned long sanitise_rekey_time(int rekey_time, unsigned long def); +static void ssh2_transport_higher_layer_packet_callback(void *context); + +static const PacketProtocolLayerVtable ssh2_transport_vtable = { + .free = ssh2_transport_free, + .process_queue = ssh2_transport_process_queue, + .get_specials = ssh2_transport_get_specials, + .special_cmd = ssh2_transport_special_cmd, + .want_user_input = ssh2_transport_want_user_input, + .got_user_input = ssh2_transport_got_user_input, + .reconfigure = ssh2_transport_reconfigure, + .queued_data_size = ssh2_transport_queued_data_size, + .name = NULL, /* no protocol name for this layer */ +}; + +#ifndef NO_GSSAPI +static void ssh2_transport_gss_update(struct ssh2_transport_state *s, + bool definitely_rekeying); +#endif + +static bool ssh2_transport_timer_update(struct ssh2_transport_state *s, + unsigned long rekey_time); +static int ssh2_transport_confirm_weak_crypto_primitive( + struct ssh2_transport_state *s, const char *type, const char *name, + const void *alg); + +static const char *const kexlist_descr[NKEXLIST] = { + "key exchange algorithm", + "host key algorithm", + "client-to-server cipher", + "server-to-client cipher", + "client-to-server MAC", + "server-to-client MAC", + "client-to-server compression method", + "server-to-client compression method" +}; + +static int weak_algorithm_compare(void *av, void *bv); + +PacketProtocolLayer *ssh2_transport_new( + Conf *conf, const char *host, int port, const char *fullhostname, + const char *client_greeting, const char *server_greeting, + struct ssh_connection_shared_gss_state *shgss, + struct DataTransferStats *stats, PacketProtocolLayer *higher_layer, + const SshServerConfig *ssc) +{ + struct ssh2_transport_state *s = snew(struct ssh2_transport_state); + memset(s, 0, sizeof(*s)); + s->ppl.vt = &ssh2_transport_vtable; + + s->conf = conf_copy(conf); + s->savedhost = dupstr(host); + s->savedport = port; + s->fullhostname = dupstr(fullhostname); + s->shgss = shgss; + s->client_greeting = dupstr(client_greeting); + s->server_greeting = dupstr(server_greeting); + s->stats = stats; + s->hostkeyblob = strbuf_new(); + + pq_in_init(&s->pq_in_higher); + pq_out_init(&s->pq_out_higher); + s->pq_out_higher.pqb.ic = &s->ic_pq_out_higher; + s->ic_pq_out_higher.fn = ssh2_transport_higher_layer_packet_callback; + s->ic_pq_out_higher.ctx = &s->ppl; + + s->higher_layer = higher_layer; + s->higher_layer->selfptr = &s->higher_layer; + ssh_ppl_setup_queues(s->higher_layer, &s->pq_in_higher, &s->pq_out_higher); + +#ifndef NO_GSSAPI + s->gss_cred_expiry = GSS_NO_EXPIRATION; + s->shgss->srv_name = GSS_C_NO_NAME; + s->shgss->ctx = NULL; +#endif + s->thc = ssh_transient_hostkey_cache_new(); + s->gss_kex_used = false; + + s->outgoing_kexinit = strbuf_new(); + s->incoming_kexinit = strbuf_new(); + if (ssc) { + s->ssc = ssc; + s->client_kexinit = s->incoming_kexinit; + s->server_kexinit = s->outgoing_kexinit; + s->cstrans = &s->in; + s->sctrans = &s->out; + s->out.mkkey_adjust = 1; + } else { + s->client_kexinit = s->outgoing_kexinit; + s->server_kexinit = s->incoming_kexinit; + s->cstrans = &s->out; + s->sctrans = &s->in; + s->in.mkkey_adjust = 1; + } + + s->weak_algorithms_consented_to = newtree234(weak_algorithm_compare); + + ssh2_transport_set_max_data_size(s); + + return &s->ppl; +} + +static void ssh2_transport_free(PacketProtocolLayer *ppl) +{ + struct ssh2_transport_state *s = + container_of(ppl, struct ssh2_transport_state, ppl); + + /* + * As our last act before being freed, move any outgoing packets + * off our higher layer's output queue on to our own output queue. + * We might be being freed while the SSH connection is still alive + * (because we're initiating shutdown from our end), in which case + * we don't want those last few packets to get lost. + * + * (If our owner were to have already destroyed our output pq + * before wanting to free us, then it would have to reset our + * publicly visible out_pq field to NULL to inhibit this attempt. + * But that's not how I expect the shutdown sequence to go in + * practice.) + */ + if (s->ppl.out_pq) + pq_concatenate(s->ppl.out_pq, s->ppl.out_pq, &s->pq_out_higher); + + conf_free(s->conf); + + ssh_ppl_free(s->higher_layer); + + pq_in_clear(&s->pq_in_higher); + pq_out_clear(&s->pq_out_higher); + + sfree(s->savedhost); + sfree(s->fullhostname); + sfree(s->client_greeting); + sfree(s->server_greeting); + sfree(s->keystr); + sfree(s->hostkey_str); + strbuf_free(s->hostkeyblob); + if (s->hkey && !s->hostkeys) { + ssh_key_free(s->hkey); + s->hkey = NULL; + } + if (s->f) mp_free(s->f); + if (s->p) mp_free(s->p); + if (s->g) mp_free(s->g); + if (s->K) mp_free(s->K); + if (s->dh_ctx) + dh_cleanup(s->dh_ctx); + if (s->rsa_kex_key_needs_freeing) { + ssh_rsakex_freekey(s->rsa_kex_key); + sfree(s->rsa_kex_key); + } + if (s->ecdh_key) + ssh_ecdhkex_freekey(s->ecdh_key); + if (s->exhash) + ssh_hash_free(s->exhash); + strbuf_free(s->outgoing_kexinit); + strbuf_free(s->incoming_kexinit); + ssh_transient_hostkey_cache_free(s->thc); + + freetree234(s->weak_algorithms_consented_to); + + expire_timer_context(s); + sfree(s); +} + +/* + * SSH-2 key derivation (RFC 4253 section 7.2). + */ +static void ssh2_mkkey( + struct ssh2_transport_state *s, strbuf *out, + mp_int *K, unsigned char *H, char chr, int keylen) +{ + int hlen = s->kex_alg->hash->hlen; + int keylen_padded; + unsigned char *key; + ssh_hash *h; + + if (keylen == 0) + return; + + /* + * Round the requested amount of key material up to a multiple of + * the length of the hash we're using to make it. This makes life + * simpler because then we can just write each hash output block + * straight into the output buffer without fiddling about + * truncating the last one. Since it's going into a strbuf, and + * strbufs are always smemclr()ed on free, there's no need to + * worry about leaving extra potentially-sensitive data in memory + * that the caller didn't ask for. + */ + keylen_padded = ((keylen + hlen - 1) / hlen) * hlen; + + strbuf_clear(out); + key = strbuf_append(out, keylen_padded); + + /* First hlen bytes. */ + h = ssh_hash_new(s->kex_alg->hash); + if (!(s->ppl.remote_bugs & BUG_SSH2_DERIVEKEY)) + put_mp_ssh2(h, K); + put_data(h, H, hlen); + put_byte(h, chr); + put_data(h, s->session_id, s->session_id_len); + ssh_hash_digest(h, key); + + /* Subsequent blocks of hlen bytes. */ + if (keylen_padded > hlen) { + int offset; + + ssh_hash_reset(h); + if (!(s->ppl.remote_bugs & BUG_SSH2_DERIVEKEY)) + put_mp_ssh2(h, K); + put_data(h, H, hlen); + + for (offset = hlen; offset < keylen_padded; offset += hlen) { + put_data(h, key + offset - hlen, hlen); + ssh_hash_digest_nondestructive(h, key + offset); + } + + } + + ssh_hash_free(h); +} + +/* + * Find a slot in a KEXINIT algorithm list to use for a new algorithm. + * If the algorithm is already in the list, return a pointer to its + * entry, otherwise return an entry from the end of the list. + * This assumes that every time a particular name is passed in, it + * comes from the same string constant. If this isn't true, this + * function may need to be rewritten to use strcmp() instead. + */ +static struct kexinit_algorithm *ssh2_kexinit_addalg(struct kexinit_algorithm + *list, const char *name) +{ + int i; + + for (i = 0; i < MAXKEXLIST; i++) + if (list[i].name == NULL || list[i].name == name) { + list[i].name = name; + return &list[i]; + } + + unreachable("Should never run out of space in KEXINIT list"); +} + +bool ssh2_common_filter_queue(PacketProtocolLayer *ppl) +{ + 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", + }; + + PktIn *pktin; + ptrlen msg; + int reason; + + while ((pktin = pq_peek(ppl->in_pq)) != NULL) { + switch (pktin->type) { + case SSH2_MSG_DISCONNECT: + reason = get_uint32(pktin); + msg = get_string(pktin); + + ssh_remote_error( + ppl->ssh, "Remote side sent disconnect message\n" + "type %d (%s):\n\"%.*s\"", reason, + ((reason > 0 && reason < lenof(ssh2_disconnect_reasons)) ? + ssh2_disconnect_reasons[reason] : "unknown"), + PTRLEN_PRINTF(msg)); + /* don't try to pop the queue, because we've been freed! */ + return true; /* indicate that we've been freed */ + + case SSH2_MSG_DEBUG: + /* XXX maybe we should actually take notice of the return value */ + get_bool(pktin); + msg = get_string(pktin); + ppl_logevent("Remote debug message: %.*s", PTRLEN_PRINTF(msg)); + pq_pop(ppl->in_pq); + break; + + case SSH2_MSG_IGNORE: + /* Do nothing, because we're ignoring it! Duhh. */ + pq_pop(ppl->in_pq); + break; + + case SSH2_MSG_EXT_INFO: { + /* + * The BPP enforces that these turn up only at legal + * points in the protocol. In particular, it will not pass + * an EXT_INFO on to us if it arrives before encryption is + * enabled (which is when a MITM could inject one + * maliciously). + * + * However, one of the criteria for legality is that a + * server is permitted to send this message immediately + * _before_ USERAUTH_SUCCESS. So we may receive this + * message not yet knowing whether it's legal to have sent + * it - we won't know until the BPP processes the next + * packet. + * + * But that should be OK, because firstly, an + * out-of-sequence EXT_INFO that's still within the + * encrypted session is only a _protocol_ violation, not + * an attack; secondly, any data we set in response to + * such an illegal EXT_INFO won't have a chance to affect + * the session before the BPP aborts it anyway. + */ + uint32_t nexts = get_uint32(pktin); + for (uint32_t i = 0; i < nexts && !get_err(pktin); i++) { + ptrlen extname = get_string(pktin); + ptrlen extvalue = get_string(pktin); + if (ptrlen_eq_string(extname, "server-sig-algs")) { + /* + * Server has sent a list of signature algorithms + * it will potentially accept for user + * authentication keys. Check in particular + * whether the RFC 8332 improved versions of + * ssh-rsa are in the list, and set flags in the + * BPP if so. + * + * TODO: another thing we _could_ do here is to + * record a full list of the algorithm identifiers + * we've seen, whether we understand them + * ourselves or not. Then we could use that as a + * pre-filter during userauth, to skip keys in the + * SSH agent if we already know the server can't + * possibly accept them. (Even if the key + * algorithm is one that the agent and the server + * both understand but we do not.) + */ + ptrlen algname; + while (get_commasep_word(&extvalue, &algname)) { + if (ptrlen_eq_string(algname, "rsa-sha2-256")) + ppl->bpp->ext_info_rsa_sha256_ok = true; + if (ptrlen_eq_string(algname, "rsa-sha2-512")) + ppl->bpp->ext_info_rsa_sha512_ok = true; + } + } + } + pq_pop(ppl->in_pq); + break; + } + + default: + return false; + } + } + + return false; +} + +static bool ssh2_transport_filter_queue(struct ssh2_transport_state *s) +{ + PktIn *pktin; + + while (1) { + if (ssh2_common_filter_queue(&s->ppl)) + return true; + if ((pktin = pq_peek(s->ppl.in_pq)) == NULL) + return false; + + /* Pass on packets to the next layer if they're outside + * the range reserved for the transport protocol. */ + if (pktin->type >= 50) { + /* ... except that we shouldn't tolerate higher-layer + * packets coming from the server before we've seen + * the first NEWKEYS. */ + if (!s->higher_layer_ok) { + ssh_proto_error(s->ppl.ssh, "Received premature higher-" + "layer packet, type %d (%s)", pktin->type, + ssh2_pkt_type(s->ppl.bpp->pls->kctx, + s->ppl.bpp->pls->actx, + pktin->type)); + return true; + } + + pq_pop(s->ppl.in_pq); + pq_push(&s->pq_in_higher, pktin); + } else { + /* Anything else is a transport-layer packet that the main + * process_queue coroutine should handle. */ + return false; + } + } +} + +PktIn *ssh2_transport_pop(struct ssh2_transport_state *s) +{ + if (ssh2_transport_filter_queue(s)) + return NULL; /* we've been freed */ + return pq_pop(s->ppl.in_pq); +} + +static void ssh2_write_kexinit_lists( + BinarySink *pktout, + struct kexinit_algorithm kexlists[NKEXLIST][MAXKEXLIST], + Conf *conf, const SshServerConfig *ssc, int remote_bugs, + const char *hk_host, int hk_port, const ssh_keyalg *hk_prev, + ssh_transient_hostkey_cache *thc, + ssh_key *const *our_hostkeys, int our_nhostkeys, + bool first_time, bool can_gssapi_keyex, bool transient_hostkey_mode) +{ + int i, j, k; + bool warn; + + int n_preferred_kex; + const ssh_kexes *preferred_kex[KEX_MAX + 1]; /* +1 for GSSAPI */ + int n_preferred_hk; + int preferred_hk[HK_MAX]; + int n_preferred_ciphers; + const ssh2_ciphers *preferred_ciphers[CIPHER_MAX]; + const ssh_compression_alg *preferred_comp; + const ssh2_macalg *const *maclist; + int nmacs; + + struct kexinit_algorithm *alg; + + /* + * Set up the preferred key exchange. (NULL => warn below here) + */ + n_preferred_kex = 0; + if (can_gssapi_keyex) + preferred_kex[n_preferred_kex++] = &ssh_gssk5_sha1_kex; + for (i = 0; i < KEX_MAX; i++) { + switch (conf_get_int_int(conf, CONF_ssh_kexlist, i)) { + case KEX_DHGEX: + preferred_kex[n_preferred_kex++] = + &ssh_diffiehellman_gex; + break; + case KEX_DHGROUP14: + preferred_kex[n_preferred_kex++] = + &ssh_diffiehellman_group14; + break; + case KEX_DHGROUP1: + preferred_kex[n_preferred_kex++] = + &ssh_diffiehellman_group1; + break; + case KEX_RSA: + preferred_kex[n_preferred_kex++] = + &ssh_rsa_kex; + break; + case KEX_ECDH: + preferred_kex[n_preferred_kex++] = + &ssh_ecdh_kex; + break; + case KEX_WARN: + /* Flag for later. Don't bother if it's the last in + * the list. */ + if (i < KEX_MAX - 1) { + preferred_kex[n_preferred_kex++] = NULL; + } + break; + } + } + + /* + * Set up the preferred host key types. These are just the ids + * in the enum in putty.h, so 'warn below here' is indicated + * by HK_WARN. + */ + n_preferred_hk = 0; + for (i = 0; i < HK_MAX; i++) { + int id = conf_get_int_int(conf, CONF_ssh_hklist, i); + /* As above, don't bother with HK_WARN if it's last in the + * list */ + if (id != HK_WARN || i < HK_MAX - 1) + preferred_hk[n_preferred_hk++] = id; + } + + /* + * Set up the preferred ciphers. (NULL => warn below here) + */ + n_preferred_ciphers = 0; + for (i = 0; i < CIPHER_MAX; i++) { + switch (conf_get_int_int(conf, CONF_ssh_cipherlist, i)) { + case CIPHER_BLOWFISH: + preferred_ciphers[n_preferred_ciphers++] = &ssh2_blowfish; + break; + case CIPHER_DES: + if (conf_get_bool(conf, CONF_ssh2_des_cbc)) + preferred_ciphers[n_preferred_ciphers++] = &ssh2_des; + break; + case CIPHER_3DES: + preferred_ciphers[n_preferred_ciphers++] = &ssh2_3des; + break; + case CIPHER_AES: + preferred_ciphers[n_preferred_ciphers++] = &ssh2_aes; + break; + case CIPHER_ARCFOUR: + preferred_ciphers[n_preferred_ciphers++] = &ssh2_arcfour; + break; + case CIPHER_CHACHA20: + preferred_ciphers[n_preferred_ciphers++] = &ssh2_ccp; + break; + case CIPHER_WARN: + /* Flag for later. Don't bother if it's the last in + * the list. */ + if (i < CIPHER_MAX - 1) { + preferred_ciphers[n_preferred_ciphers++] = NULL; + } + break; + } + } + + /* + * Set up preferred compression. + */ + if (conf_get_bool(conf, CONF_compression)) + preferred_comp = &ssh_zlib; + else + preferred_comp = &ssh_comp_none; + + for (i = 0; i < NKEXLIST; i++) + for (j = 0; j < MAXKEXLIST; j++) + kexlists[i][j].name = NULL; + /* List key exchange algorithms. */ + warn = false; + for (i = 0; i < n_preferred_kex; i++) { + const ssh_kexes *k = preferred_kex[i]; + if (!k) warn = true; + else for (j = 0; j < k->nkexes; j++) { + alg = ssh2_kexinit_addalg(kexlists[KEXLIST_KEX], + k->list[j]->name); + alg->u.kex.kex = k->list[j]; + alg->u.kex.warn = warn; + } + } + /* List server host key algorithms. */ + if (our_hostkeys) { + /* + * In server mode, we just list the algorithms that match the + * host keys we actually have. + */ + for (i = 0; i < our_nhostkeys; i++) { + const ssh_keyalg *keyalg = ssh_key_alg(our_hostkeys[i]); + + alg = ssh2_kexinit_addalg(kexlists[KEXLIST_HOSTKEY], + keyalg->ssh_id); + alg->u.hk.hostkey = keyalg; + alg->u.hk.hkflags = 0; + alg->u.hk.warn = false; + + if (keyalg == &ssh_rsa) { + alg = ssh2_kexinit_addalg(kexlists[KEXLIST_HOSTKEY], + "rsa-sha2-256"); + alg->u.hk.hostkey = keyalg; + alg->u.hk.hkflags = SSH_AGENT_RSA_SHA2_256; + alg->u.hk.warn = false; + + alg = ssh2_kexinit_addalg(kexlists[KEXLIST_HOSTKEY], + "rsa-sha2-512"); + alg->u.hk.hostkey = keyalg; + alg->u.hk.hkflags = SSH_AGENT_RSA_SHA2_512; + alg->u.hk.warn = false; + } + } + } else if (first_time) { + /* + * In the first key exchange, we list all the algorithms we're + * prepared to cope with, but (if configured to) we prefer + * those algorithms for which we have a host key for this + * host. + * + * If the host key algorithm is below the warning + * threshold, we warn even if we did already have a key + * for it, on the basis that if the user has just + * reconfigured that host key type to be warned about, + * they surely _do_ want to be alerted that a server + * they're actually connecting to is using it. + */ + warn = false; + for (i = 0; i < n_preferred_hk; i++) { + if (preferred_hk[i] == HK_WARN) + warn = true; + for (j = 0; j < lenof(ssh2_hostkey_algs); j++) { + if (ssh2_hostkey_algs[j].id != preferred_hk[i]) + continue; + if (conf_get_bool(conf, CONF_ssh_prefer_known_hostkeys) && + have_ssh_host_key(hk_host, hk_port, + ssh2_hostkey_algs[j].alg->cache_id)) { + alg = ssh2_kexinit_addalg(kexlists[KEXLIST_HOSTKEY], + ssh2_hostkey_algs[j].alg->ssh_id); + alg->u.hk.hostkey = ssh2_hostkey_algs[j].alg; + alg->u.hk.warn = warn; + } + } + } + warn = false; + for (i = 0; i < n_preferred_hk; i++) { + if (preferred_hk[i] == HK_WARN) + warn = true; + for (j = 0; j < lenof(ssh2_hostkey_algs); j++) { + if (ssh2_hostkey_algs[j].id != preferred_hk[i]) + continue; + alg = ssh2_kexinit_addalg(kexlists[KEXLIST_HOSTKEY], + ssh2_hostkey_algs[j].alg->ssh_id); + alg->u.hk.hostkey = ssh2_hostkey_algs[j].alg; + alg->u.hk.warn = warn; + } + } +#ifndef NO_GSSAPI + } else if (transient_hostkey_mode) { + /* + * If we've previously done a GSSAPI KEX, then we list + * precisely the algorithms for which a previous GSS key + * exchange has delivered us a host key, because we expect + * one of exactly those keys to be used in any subsequent + * non-GSS-based rekey. + * + * An exception is if this is the key exchange we + * triggered for the purposes of populating that cache - + * in which case the cache will currently be empty, which + * isn't helpful! + */ + warn = false; + for (i = 0; i < n_preferred_hk; i++) { + if (preferred_hk[i] == HK_WARN) + warn = true; + for (j = 0; j < lenof(ssh2_hostkey_algs); j++) { + if (ssh2_hostkey_algs[j].id != preferred_hk[i]) + continue; + if (ssh_transient_hostkey_cache_has( + thc, ssh2_hostkey_algs[j].alg)) { + alg = ssh2_kexinit_addalg(kexlists[KEXLIST_HOSTKEY], + ssh2_hostkey_algs[j].alg->ssh_id); + alg->u.hk.hostkey = ssh2_hostkey_algs[j].alg; + alg->u.hk.warn = warn; + } + } + } +#endif + } else { + /* + * In subsequent key exchanges, we list only the host key + * algorithm that was selected in the first key exchange, + * so that we keep getting the same host key and hence + * don't have to interrupt the user's session to ask for + * reverification. + */ + assert(hk_prev); + alg = ssh2_kexinit_addalg(kexlists[KEXLIST_HOSTKEY], hk_prev->ssh_id); + alg->u.hk.hostkey = hk_prev; + alg->u.hk.warn = false; + } + if (can_gssapi_keyex) { + alg = ssh2_kexinit_addalg(kexlists[KEXLIST_HOSTKEY], "null"); + alg->u.hk.hostkey = NULL; + } + /* List encryption algorithms (client->server then server->client). */ + for (k = KEXLIST_CSCIPHER; k <= KEXLIST_SCCIPHER; k++) { + warn = false; +#ifdef FUZZING + alg = ssh2_kexinit_addalg(kexlists[k], "none"); + alg->u.cipher.cipher = NULL; + alg->u.cipher.warn = warn; +#endif /* FUZZING */ + for (i = 0; i < n_preferred_ciphers; i++) { + const ssh2_ciphers *c = preferred_ciphers[i]; + if (!c) warn = true; + else for (j = 0; j < c->nciphers; j++) { + alg = ssh2_kexinit_addalg(kexlists[k], + c->list[j]->ssh2_id); + alg->u.cipher.cipher = c->list[j]; + alg->u.cipher.warn = warn; + } + } + } + + /* + * Be prepared to work around the buggy MAC problem. + */ + if (remote_bugs & BUG_SSH2_HMAC) { + maclist = buggymacs; + nmacs = lenof(buggymacs); + } else { + maclist = macs; + nmacs = lenof(macs); + } + + /* List MAC algorithms (client->server then server->client). */ + for (j = KEXLIST_CSMAC; j <= KEXLIST_SCMAC; j++) { +#ifdef FUZZING + alg = ssh2_kexinit_addalg(kexlists[j], "none"); + alg->u.mac.mac = NULL; + alg->u.mac.etm = false; +#endif /* FUZZING */ + for (i = 0; i < nmacs; i++) { + alg = ssh2_kexinit_addalg(kexlists[j], maclist[i]->name); + alg->u.mac.mac = maclist[i]; + alg->u.mac.etm = false; + } + for (i = 0; i < nmacs; i++) { + /* For each MAC, there may also be an ETM version, + * which we list second. */ + if (maclist[i]->etm_name) { + alg = ssh2_kexinit_addalg(kexlists[j], maclist[i]->etm_name); + alg->u.mac.mac = maclist[i]; + alg->u.mac.etm = true; + } + } + } + + /* List client->server compression algorithms, + * then server->client compression algorithms. (We use the + * same set twice.) */ + for (j = KEXLIST_CSCOMP; j <= KEXLIST_SCCOMP; j++) { + assert(lenof(compressions) > 1); + /* Prefer non-delayed versions */ + alg = ssh2_kexinit_addalg(kexlists[j], preferred_comp->name); + alg->u.comp.comp = preferred_comp; + alg->u.comp.delayed = false; + if (preferred_comp->delayed_name) { + alg = ssh2_kexinit_addalg(kexlists[j], + preferred_comp->delayed_name); + alg->u.comp.comp = preferred_comp; + alg->u.comp.delayed = true; + } + for (i = 0; i < lenof(compressions); i++) { + const ssh_compression_alg *c = compressions[i]; + alg = ssh2_kexinit_addalg(kexlists[j], c->name); + alg->u.comp.comp = c; + alg->u.comp.delayed = false; + if (c->delayed_name) { + alg = ssh2_kexinit_addalg(kexlists[j], c->delayed_name); + alg->u.comp.comp = c; + alg->u.comp.delayed = true; + } + } + } + + /* + * Finally, format the lists into text and write them into the + * outgoing KEXINIT packet. + */ + for (i = 0; i < NKEXLIST; i++) { + strbuf *list = strbuf_new(); + if (ssc && ssc->kex_override[i].ptr) { + put_datapl(list, ssc->kex_override[i]); + } else { + for (j = 0; j < MAXKEXLIST; j++) { + if (kexlists[i][j].name == NULL) break; + add_to_commasep(list, kexlists[i][j].name); + } + } + if (i == KEXLIST_KEX && first_time) { + if (our_hostkeys) /* we're the server */ + add_to_commasep(list, "ext-info-s"); + else /* we're the client */ + add_to_commasep(list, "ext-info-c"); + } + put_stringsb(pktout, list); + } + /* List client->server languages. Empty list. */ + put_stringz(pktout, ""); + /* List server->client languages. Empty list. */ + put_stringz(pktout, ""); +} + +static bool ssh2_scan_kexinits( + ptrlen client_kexinit, ptrlen server_kexinit, + struct kexinit_algorithm kexlists[NKEXLIST][MAXKEXLIST], + const ssh_kex **kex_alg, const ssh_keyalg **hostkey_alg, + transport_direction *cs, transport_direction *sc, + bool *warn_kex, bool *warn_hk, bool *warn_cscipher, bool *warn_sccipher, + Ssh *ssh, bool *ignore_guess_cs_packet, bool *ignore_guess_sc_packet, + int *n_server_hostkeys, int server_hostkeys[MAXKEXLIST], unsigned *hkflags, + bool *can_send_ext_info) +{ + BinarySource client[1], server[1]; + int i; + bool guess_correct; + ptrlen clists[NKEXLIST], slists[NKEXLIST]; + const struct kexinit_algorithm *selected[NKEXLIST]; + + BinarySource_BARE_INIT_PL(client, client_kexinit); + BinarySource_BARE_INIT_PL(server, server_kexinit); + + /* Skip packet type bytes and random cookies. */ + get_data(client, 1 + 16); + get_data(server, 1 + 16); + + guess_correct = true; + + /* Find the matching string in each list, and map it to its + * kexinit_algorithm structure. */ + for (i = 0; i < NKEXLIST; i++) { + ptrlen clist, slist, cword, sword, found; + bool cfirst, sfirst; + int j; + + clists[i] = get_string(client); + slists[i] = get_string(server); + if (get_err(client) || get_err(server)) { + /* Report a better error than the spurious "Couldn't + * agree" that we'd generate if we pressed on regardless + * and treated the empty get_string() result as genuine */ + ssh_proto_error(ssh, "KEXINIT packet was incomplete"); + return false; + } + + for (cfirst = true, clist = clists[i]; + get_commasep_word(&clist, &cword); cfirst = false) + for (sfirst = true, slist = slists[i]; + get_commasep_word(&slist, &sword); sfirst = false) + if (ptrlen_eq_ptrlen(cword, sword)) { + found = cword; + goto found_match; + } + + /* No matching string found in the two lists. Delay reporting + * a fatal error until below, because sometimes it turns out + * not to be fatal. */ + selected[i] = NULL; + + /* + * However, even if a failure to agree on any algorithm at all + * is not completely fatal (e.g. because it's the MAC + * negotiation for a cipher that comes with a built-in MAC), + * it still invalidates the guessed key exchange packet. (RFC + * 4253 section 7, not contradicted by OpenSSH's + * PROTOCOL.chacha20poly1305 or as far as I can see by their + * code.) + */ + guess_correct = false; + + continue; + + found_match: + + selected[i] = NULL; + for (j = 0; j < MAXKEXLIST; j++) { + if (kexlists[i][j].name && + ptrlen_eq_string(found, kexlists[i][j].name)) { + selected[i] = &kexlists[i][j]; + break; + } + } + if (!selected[i]) { + /* + * In the client, this should never happen! But in the + * server, where we allow manual override on the command + * line of the exact KEXINIT strings, it can happen + * because the command line contained a typo. So we + * produce a reasonably useful message instead of an + * assertion failure. + */ + ssh_sw_abort(ssh, "Selected %s \"%.*s\" does not correspond to " + "any supported algorithm", + kexlist_descr[i], PTRLEN_PRINTF(found)); + return false; + } + + /* + * If the kex or host key algorithm is not the first one in + * both sides' lists, that means the guessed key exchange + * packet (if any) is officially wrong. + */ + if ((i == KEXLIST_KEX || i == KEXLIST_HOSTKEY) && !(cfirst || sfirst)) + guess_correct = false; + } + + /* + * Skip language strings in both KEXINITs, and read the flags + * saying whether a guessed KEX packet follows. + */ + get_string(client); + get_string(client); + get_string(server); + get_string(server); + if (ignore_guess_cs_packet) + *ignore_guess_cs_packet = get_bool(client) && !guess_correct; + if (ignore_guess_sc_packet) + *ignore_guess_sc_packet = get_bool(server) && !guess_correct; + + /* + * Now transcribe the selected algorithm set into the output data. + */ + for (i = 0; i < NKEXLIST; i++) { + const struct kexinit_algorithm *alg; + + /* + * If we've already selected a cipher which requires a + * particular MAC, then just select that. This is the case in + * which it's not a fatal error if the actual MAC string lists + * didn't include any matching error. + */ + if (i == KEXLIST_CSMAC && cs->cipher && + cs->cipher->required_mac) { + cs->mac = cs->cipher->required_mac; + cs->etm_mode = !!(cs->mac->etm_name); + continue; + } + if (i == KEXLIST_SCMAC && sc->cipher && + sc->cipher->required_mac) { + sc->mac = sc->cipher->required_mac; + sc->etm_mode = !!(sc->mac->etm_name); + continue; + } + + alg = selected[i]; + if (!alg) { + /* + * Otherwise, any match failure _is_ a fatal error. + */ + ssh_sw_abort(ssh, "Couldn't agree a %s (available: %.*s)", + kexlist_descr[i], PTRLEN_PRINTF(slists[i])); + return false; + } + + switch (i) { + case KEXLIST_KEX: + *kex_alg = alg->u.kex.kex; + *warn_kex = alg->u.kex.warn; + break; + + case KEXLIST_HOSTKEY: + /* + * Ignore an unexpected/inappropriate offer of "null", + * we offer "null" when we're willing to use GSS KEX, + * but it is only acceptable when GSSKEX is actually + * selected. + */ + if (alg->u.hk.hostkey == NULL && + (*kex_alg)->main_type != KEXTYPE_GSS) + continue; + + *hostkey_alg = alg->u.hk.hostkey; + *hkflags = alg->u.hk.hkflags; + *warn_hk = alg->u.hk.warn; + break; + + case KEXLIST_CSCIPHER: + cs->cipher = alg->u.cipher.cipher; + *warn_cscipher = alg->u.cipher.warn; + break; + + case KEXLIST_SCCIPHER: + sc->cipher = alg->u.cipher.cipher; + *warn_sccipher = alg->u.cipher.warn; + break; + + case KEXLIST_CSMAC: + cs->mac = alg->u.mac.mac; + cs->etm_mode = alg->u.mac.etm; + break; + + case KEXLIST_SCMAC: + sc->mac = alg->u.mac.mac; + sc->etm_mode = alg->u.mac.etm; + break; + + case KEXLIST_CSCOMP: + cs->comp = alg->u.comp.comp; + cs->comp_delayed = alg->u.comp.delayed; + break; + + case KEXLIST_SCCOMP: + sc->comp = alg->u.comp.comp; + sc->comp_delayed = alg->u.comp.delayed; + break; + + default: + unreachable("Bad list index in scan_kexinits"); + } + } + + /* + * Check whether the other side advertised support for EXT_INFO. + */ + { + ptrlen extinfo_advert = + (server_hostkeys ? PTRLEN_LITERAL("ext-info-c") : + PTRLEN_LITERAL("ext-info-s")); + ptrlen list = (server_hostkeys ? clists[KEXLIST_KEX] : + slists[KEXLIST_KEX]); + for (ptrlen word; get_commasep_word(&list, &word) ;) + if (ptrlen_eq_ptrlen(word, extinfo_advert)) + *can_send_ext_info = true; + } + + if (server_hostkeys) { + /* + * Finally, make an auxiliary pass over the server's host key + * list to find all the host key algorithms offered by the + * server which we know about at all, whether we selected each + * one or not. We return these as a list of indices into the + * constant ssh2_hostkey_algs[] array. + */ + *n_server_hostkeys = 0; + + ptrlen list = slists[KEXLIST_HOSTKEY]; + for (ptrlen word; get_commasep_word(&list, &word) ;) { + for (i = 0; i < lenof(ssh2_hostkey_algs); i++) + if (ptrlen_eq_string(word, ssh2_hostkey_algs[i].alg->ssh_id)) { + server_hostkeys[(*n_server_hostkeys)++] = i; + break; + } + } + } + + return true; +} + +void ssh2transport_finalise_exhash(struct ssh2_transport_state *s) +{ + put_mp_ssh2(s->exhash, s->K); + assert(ssh_hash_alg(s->exhash)->hlen <= sizeof(s->exchange_hash)); + ssh_hash_final(s->exhash, s->exchange_hash); + s->exhash = NULL; + +#if 0 + debug("Exchange hash is:\n"); + dmemdump(s->exchange_hash, s->kex_alg->hash->hlen); +#endif +} + +static void ssh2_transport_process_queue(PacketProtocolLayer *ppl) +{ + struct ssh2_transport_state *s = + container_of(ppl, struct ssh2_transport_state, ppl); + PktIn *pktin; + PktOut *pktout; + + /* Filter centrally handled messages off the front of the queue on + * every entry to this coroutine, no matter where we're resuming + * from, even if we're _not_ looping on pq_pop. That way we can + * still proactively handle those messages even if we're waiting + * for a user response. */ + if (ssh2_transport_filter_queue(s)) + return; /* we've been freed */ + + crBegin(s->crState); + + s->in.cipher = s->out.cipher = NULL; + s->in.mac = s->out.mac = NULL; + s->in.comp = s->out.comp = NULL; + + s->got_session_id = false; + s->need_gss_transient_hostkey = false; + s->warned_about_no_gss_transient_hostkey = false; + + begin_key_exchange: + +#ifndef NO_GSSAPI + if (s->need_gss_transient_hostkey) { + /* + * This flag indicates a special case in which we must not do + * GSS key exchange even if we could. (See comments below, + * where the flag was set on the previous key exchange.) + */ + s->can_gssapi_keyex = false; + } else if (conf_get_bool(s->conf, CONF_try_gssapi_kex)) { + /* + * We always check if we have GSS creds before we come up with + * the kex algorithm list, otherwise future rekeys will fail + * when creds expire. To make this so, this code section must + * follow the begin_key_exchange label above, otherwise this + * section would execute just once per-connection. + * + * Update GSS state unless the reason we're here is that a + * timer just checked the GSS state and decided that we should + * rekey to update delegated credentials. In that case, the + * state is "fresh". + */ + if (s->rekey_class != RK_GSS_UPDATE) + ssh2_transport_gss_update(s, true); + + /* Do GSSAPI KEX when capable */ + s->can_gssapi_keyex = s->gss_status & GSS_KEX_CAPABLE; + + /* + * But not when failure is likely. [ GSS implementations may + * attempt (and fail) to use a ticket that is almost expired + * when retrieved from the ccache that actually expires by the + * time the server receives it. ] + * + * Note: The first time always try KEXGSS if we can, failures + * will be very rare, and disabling the initial GSS KEX is + * worse. Some day GSS libraries will ignore cached tickets + * whose lifetime is critically short, and will instead use + * fresh ones. + */ + if (!s->got_session_id && (s->gss_status & GSS_CTXT_MAYFAIL) != 0) + s->can_gssapi_keyex = false; + s->gss_delegate = conf_get_bool(s->conf, CONF_gssapifwd); + } else { + s->can_gssapi_keyex = false; + } +#endif + + s->ppl.bpp->pls->kctx = SSH2_PKTCTX_NOKEX; + + /* + * Construct our KEXINIT packet, in a strbuf so we can refer to it + * later. + */ + strbuf_clear(s->client_kexinit); + put_byte(s->outgoing_kexinit, SSH2_MSG_KEXINIT); + random_read(strbuf_append(s->outgoing_kexinit, 16), 16); + ssh2_write_kexinit_lists( + BinarySink_UPCAST(s->outgoing_kexinit), s->kexlists, + s->conf, s->ssc, s->ppl.remote_bugs, + s->savedhost, s->savedport, s->hostkey_alg, s->thc, + s->hostkeys, s->nhostkeys, + !s->got_session_id, s->can_gssapi_keyex, + s->gss_kex_used && !s->need_gss_transient_hostkey); + /* First KEX packet does _not_ follow, because we're not that brave. */ + put_bool(s->outgoing_kexinit, false); + put_uint32(s->outgoing_kexinit, 0); /* reserved */ + + /* + * Send our KEXINIT. + */ + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEXINIT); + put_data(pktout, s->outgoing_kexinit->u + 1, + s->outgoing_kexinit->len - 1); /* omit initial packet type byte */ + pq_push(s->ppl.out_pq, pktout); + + /* + * Flag that KEX is in progress. + */ + s->kex_in_progress = true; + + /* + * Wait for the other side's KEXINIT, and save it. + */ + crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL); + if (pktin->type != SSH2_MSG_KEXINIT) { + ssh_proto_error(s->ppl.ssh, "Received unexpected packet when " + "expecting KEXINIT, type %d (%s)", pktin->type, + ssh2_pkt_type(s->ppl.bpp->pls->kctx, + s->ppl.bpp->pls->actx, pktin->type)); + return; + } + strbuf_clear(s->incoming_kexinit); + put_byte(s->incoming_kexinit, SSH2_MSG_KEXINIT); + put_data(s->incoming_kexinit, get_ptr(pktin), get_avail(pktin)); + + /* + * Work through the two KEXINIT packets in parallel to find the + * selected algorithm identifiers. + */ + { + int nhk, hks[MAXKEXLIST], i, j; + + if (!ssh2_scan_kexinits( + ptrlen_from_strbuf(s->client_kexinit), + ptrlen_from_strbuf(s->server_kexinit), + s->kexlists, &s->kex_alg, &s->hostkey_alg, s->cstrans, + s->sctrans, &s->warn_kex, &s->warn_hk, &s->warn_cscipher, + &s->warn_sccipher, s->ppl.ssh, NULL, &s->ignorepkt, &nhk, hks, + &s->hkflags, &s->can_send_ext_info)) + return; /* false means a fatal error function was called */ + + /* + * In addition to deciding which host key we're actually going + * to use, we should make a list of the host keys offered by + * the server which we _don't_ have cached. These will be + * offered as cross-certification options by ssh_get_specials. + * + * We also count the key we're currently using for KEX as one + * we've already got, because by the time this menu becomes + * visible, it will be. + */ + s->n_uncert_hostkeys = 0; + + for (i = 0; i < nhk; i++) { + j = hks[i]; + if (ssh2_hostkey_algs[j].alg != s->hostkey_alg && + !have_ssh_host_key(s->savedhost, s->savedport, + ssh2_hostkey_algs[j].alg->cache_id)) { + s->uncert_hostkeys[s->n_uncert_hostkeys++] = j; + } + } + } + + if (s->warn_kex) { + s->dlgret = ssh2_transport_confirm_weak_crypto_primitive( + s, "key-exchange algorithm", s->kex_alg->name, s->kex_alg); + crMaybeWaitUntilV(s->dlgret >= 0); + if (s->dlgret == 0) { + ssh_user_close(s->ppl.ssh, "User aborted at kex warning"); + return; + } + } + + if (s->warn_hk) { + int j, k; + char *betteralgs; + + /* + * Change warning box wording depending on why we chose a + * warning-level host key algorithm. If it's because + * that's all we have *cached*, list the host keys we + * could usefully cross-certify. Otherwise, use the same + * standard wording as any other weak crypto primitive. + */ + betteralgs = NULL; + for (j = 0; j < s->n_uncert_hostkeys; j++) { + const struct ssh_signkey_with_user_pref_id *hktype = + &ssh2_hostkey_algs[s->uncert_hostkeys[j]]; + bool better = false; + for (k = 0; k < HK_MAX; k++) { + int id = conf_get_int_int(s->conf, CONF_ssh_hklist, k); + if (id == HK_WARN) { + break; + } else if (id == hktype->id) { + better = true; + break; + } + } + if (better) { + if (betteralgs) { + char *old_ba = betteralgs; + betteralgs = dupcat(betteralgs, ",", hktype->alg->ssh_id); + sfree(old_ba); + } else { + betteralgs = dupstr(hktype->alg->ssh_id); + } + } + } + if (betteralgs) { + /* Use the special warning prompt that lets us provide + * a list of better algorithms */ + s->dlgret = seat_confirm_weak_cached_hostkey( + s->ppl.seat, s->hostkey_alg->ssh_id, betteralgs, + ssh2_transport_dialog_callback, s); + sfree(betteralgs); + } else { + /* If none exist, use the more general 'weak crypto' + * warning prompt */ + s->dlgret = ssh2_transport_confirm_weak_crypto_primitive( + s, "host key type", s->hostkey_alg->ssh_id, + s->hostkey_alg); + } + crMaybeWaitUntilV(s->dlgret >= 0); + if (s->dlgret == 0) { + ssh_user_close(s->ppl.ssh, "User aborted at host key warning"); + return; + } + } + + if (s->warn_cscipher) { + s->dlgret = ssh2_transport_confirm_weak_crypto_primitive( + s, "client-to-server cipher", s->out.cipher->ssh2_id, + s->out.cipher); + crMaybeWaitUntilV(s->dlgret >= 0); + if (s->dlgret == 0) { + ssh_user_close(s->ppl.ssh, "User aborted at cipher warning"); + return; + } + } + + if (s->warn_sccipher) { + s->dlgret = ssh2_transport_confirm_weak_crypto_primitive( + s, "server-to-client cipher", s->in.cipher->ssh2_id, + s->in.cipher); + crMaybeWaitUntilV(s->dlgret >= 0); + if (s->dlgret == 0) { + ssh_user_close(s->ppl.ssh, "User aborted at cipher warning"); + return; + } + } + + /* + * If the other side has sent an initial key exchange packet that + * we must treat as a wrong guess, wait for it, and discard it. + */ + if (s->ignorepkt) + crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL); + + /* + * Actually perform the key exchange. + */ + s->exhash = ssh_hash_new(s->kex_alg->hash); + put_stringz(s->exhash, s->client_greeting); + put_stringz(s->exhash, s->server_greeting); + put_string(s->exhash, s->client_kexinit->u, s->client_kexinit->len); + put_string(s->exhash, s->server_kexinit->u, s->server_kexinit->len); + s->crStateKex = 0; + while (1) { + bool aborted = false; + ssh2kex_coroutine(s, &aborted); + if (aborted) + return; /* disaster: our entire state has been freed */ + if (!s->crStateKex) + break; /* kex phase has terminated normally */ + crReturnV; + } + + /* + * 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(s->session_id)); + memcpy(s->session_id, s->exchange_hash, sizeof(s->exchange_hash)); + s->session_id_len = s->kex_alg->hash->hlen; + assert(s->session_id_len <= sizeof(s->session_id)); + s->got_session_id = true; + } + + /* + * Send SSH2_MSG_NEWKEYS. + */ + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_NEWKEYS); + pq_push(s->ppl.out_pq, pktout); + /* Start counting down the outgoing-data limit for these cipher keys. */ + dts_reset(&s->stats->out, s->max_data_size); + + /* + * Force the BPP to synchronously marshal all packets up to and + * including that NEWKEYS into wire format, before we switch over + * to new crypto. + */ + ssh_bpp_handle_output(s->ppl.bpp); + + /* + * We've sent outgoing NEWKEYS, so create and initialise outgoing + * session keys. + */ + { + strbuf *cipher_key = strbuf_new_nm(); + strbuf *cipher_iv = strbuf_new_nm(); + strbuf *mac_key = strbuf_new_nm(); + + if (s->out.cipher) { + ssh2_mkkey(s, cipher_iv, s->K, s->exchange_hash, + 'A' + s->out.mkkey_adjust, s->out.cipher->blksize); + ssh2_mkkey(s, cipher_key, s->K, s->exchange_hash, + 'C' + s->out.mkkey_adjust, + s->out.cipher->padded_keybytes); + } + if (s->out.mac) { + ssh2_mkkey(s, mac_key, s->K, s->exchange_hash, + 'E' + s->out.mkkey_adjust, s->out.mac->keylen); + } + + ssh2_bpp_new_outgoing_crypto( + s->ppl.bpp, + s->out.cipher, cipher_key->u, cipher_iv->u, + s->out.mac, s->out.etm_mode, mac_key->u, + s->out.comp, s->out.comp_delayed); + + strbuf_free(cipher_key); + strbuf_free(cipher_iv); + strbuf_free(mac_key); + } + + /* + * If that was our first key exchange, this is the moment to send + * our EXT_INFO, if we're sending one. + */ + if (!s->post_newkeys_ext_info) { + s->post_newkeys_ext_info = true; /* never do this again */ + if (s->can_send_ext_info) { + strbuf *extinfo = strbuf_new(); + uint32_t n_exts = 0; + + if (s->ssc) { + /* Server->client EXT_INFO lists our supported user + * key algorithms. */ + n_exts++; + put_stringz(extinfo, "server-sig-algs"); + strbuf *list = strbuf_new(); + for (size_t i = 0; i < n_keyalgs; i++) + add_to_commasep(list, all_keyalgs[i]->ssh_id); + put_stringsb(extinfo, list); + } else { + /* Client->server EXT_INFO is currently not sent, but here's + * where we should put things in it if we ever want to. */ + } + + /* Only send EXT_INFO if it's non-empty */ + if (n_exts) { + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_EXT_INFO); + put_uint32(pktout, n_exts); + put_datapl(pktout, ptrlen_from_strbuf(extinfo)); + pq_push(s->ppl.out_pq, pktout); + } + + strbuf_free(extinfo); + } + } + + /* + * Now our end of the key exchange is complete, we can send all + * our queued higher-layer packets. Transfer the whole of the next + * layer's outgoing queue on to our own. + */ + pq_concatenate(s->ppl.out_pq, s->ppl.out_pq, &s->pq_out_higher); + + /* + * Expect SSH2_MSG_NEWKEYS from server. + */ + crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL); + if (pktin->type != SSH2_MSG_NEWKEYS) { + ssh_proto_error(s->ppl.ssh, "Received unexpected packet when " + "expecting SSH_MSG_NEWKEYS, type %d (%s)", + pktin->type, + ssh2_pkt_type(s->ppl.bpp->pls->kctx, + s->ppl.bpp->pls->actx, + pktin->type)); + return; + } + /* Start counting down the incoming-data limit for these cipher keys. */ + dts_reset(&s->stats->in, s->max_data_size); + + /* + * We've seen incoming NEWKEYS, so create and initialise + * incoming session keys. + */ + { + strbuf *cipher_key = strbuf_new_nm(); + strbuf *cipher_iv = strbuf_new_nm(); + strbuf *mac_key = strbuf_new_nm(); + + if (s->in.cipher) { + ssh2_mkkey(s, cipher_iv, s->K, s->exchange_hash, + 'A' + s->in.mkkey_adjust, s->in.cipher->blksize); + ssh2_mkkey(s, cipher_key, s->K, s->exchange_hash, + 'C' + s->in.mkkey_adjust, + s->in.cipher->padded_keybytes); + } + if (s->in.mac) { + ssh2_mkkey(s, mac_key, s->K, s->exchange_hash, + 'E' + s->in.mkkey_adjust, s->in.mac->keylen); + } + + ssh2_bpp_new_incoming_crypto( + s->ppl.bpp, + s->in.cipher, cipher_key->u, cipher_iv->u, + s->in.mac, s->in.etm_mode, mac_key->u, + s->in.comp, s->in.comp_delayed); + + strbuf_free(cipher_key); + strbuf_free(cipher_iv); + strbuf_free(mac_key); + } + + /* + * Free shared secret. + */ + mp_free(s->K); s->K = NULL; + + /* + * Update the specials menu to list the remaining uncertified host + * keys. + */ + seat_update_specials_menu(s->ppl.seat); + + /* + * Key exchange is over. Loop straight back round if we have a + * deferred rekey reason. + */ + if (s->deferred_rekey_reason) { + ppl_logevent("%s", s->deferred_rekey_reason); + pktin = NULL; + s->deferred_rekey_reason = NULL; + goto begin_key_exchange; + } + + /* + * Otherwise, schedule a timer for our next rekey. + */ + s->kex_in_progress = false; + s->last_rekey = GETTICKCOUNT(); + (void) ssh2_transport_timer_update(s, 0); + + /* + * Now we're encrypting. Get the next-layer protocol started if it + * hasn't already, and then sit here waiting for reasons to go + * back to the start and do a repeat key exchange. One of those + * reasons is that we receive KEXINIT from the other end; the + * other is if we find rekey_reason is non-NULL, i.e. we've + * decided to initiate a rekey ourselves for some reason. + */ + if (!s->higher_layer_ok) { + if (!s->hostkeys) { + /* We're the client, so send SERVICE_REQUEST. */ + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_SERVICE_REQUEST); + put_stringz(pktout, s->higher_layer->vt->name); + pq_push(s->ppl.out_pq, pktout); + crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL); + if (pktin->type != SSH2_MSG_SERVICE_ACCEPT) { + ssh_sw_abort(s->ppl.ssh, "Server refused request to start " + "'%s' protocol", s->higher_layer->vt->name); + return; + } + } else { + ptrlen service_name; + + /* We're the server, so expect SERVICE_REQUEST. */ + crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL); + if (pktin->type != SSH2_MSG_SERVICE_REQUEST) { + ssh_proto_error(s->ppl.ssh, "Received unexpected packet when " + "expecting SERVICE_REQUEST, type %d (%s)", + pktin->type, + ssh2_pkt_type(s->ppl.bpp->pls->kctx, + s->ppl.bpp->pls->actx, + pktin->type)); + return; + } + service_name = get_string(pktin); + if (!ptrlen_eq_string(service_name, s->higher_layer->vt->name)) { + ssh_proto_error(s->ppl.ssh, "Client requested service " + "'%.*s' when we only support '%s'", + PTRLEN_PRINTF(service_name), + s->higher_layer->vt->name); + return; + } + + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_SERVICE_ACCEPT); + put_stringz(pktout, s->higher_layer->vt->name); + pq_push(s->ppl.out_pq, pktout); + } + + s->higher_layer_ok = true; + queue_idempotent_callback(&s->higher_layer->ic_process_queue); + } + + s->rekey_class = RK_NONE; + do { + crReturnV; + + /* Pass through outgoing packets from the higher layer. */ + pq_concatenate(s->ppl.out_pq, s->ppl.out_pq, &s->pq_out_higher); + + /* Wait for either a KEXINIT, or something setting + * s->rekey_class. This call to ssh2_transport_pop also has + * the side effect of transferring incoming packets _to_ the + * higher layer (via filter_queue). */ + if ((pktin = ssh2_transport_pop(s)) != NULL) { + if (pktin->type != SSH2_MSG_KEXINIT) { + ssh_proto_error(s->ppl.ssh, "Received unexpected transport-" + "layer packet outside a key exchange, " + "type %d (%s)", pktin->type, + ssh2_pkt_type(s->ppl.bpp->pls->kctx, + s->ppl.bpp->pls->actx, + pktin->type)); + return; + } + pq_push_front(s->ppl.in_pq, pktin); + ppl_logevent("Remote side initiated key re-exchange"); + s->rekey_class = RK_SERVER; + } + + if (s->rekey_class == RK_POST_USERAUTH) { + /* + * userauth has seen a USERAUTH_SUCCESS. This may be the + * moment to do an immediate rekey with different + * parameters. But it may not; so here we turn that rekey + * class into either RK_NONE or RK_NORMAL. + * + * Currently the only reason for this is if we've done a + * GSS key exchange and don't have anything in our + * transient hostkey cache, in which case we should make + * an attempt to populate the cache now. + */ + if (s->need_gss_transient_hostkey) { + s->rekey_reason = "populating transient host key cache"; + s->rekey_class = RK_NORMAL; + } else { + /* No need to rekey at this time. */ + s->rekey_class = RK_NONE; + } + } + + if (!s->rekey_class) { + /* If we don't yet have any other reason to rekey, check + * if we've hit our data limit in either direction. */ + if (s->stats->in.expired) { + s->rekey_reason = "too much data received"; + s->rekey_class = RK_NORMAL; + } else if (s->stats->out.expired) { + s->rekey_reason = "too much data sent"; + s->rekey_class = RK_NORMAL; + } + } + + if (s->rekey_class != RK_NONE && s->rekey_class != RK_SERVER) { + /* + * 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 ((s->ppl.remote_bugs & BUG_SSH2_REKEY)) { + ppl_logevent("Remote bug prevents key re-exchange (%s)", + s->rekey_reason); + /* Reset the counters, so that at least this message doesn't + * hit the event log _too_ often. */ + dts_reset(&s->stats->in, s->max_data_size); + dts_reset(&s->stats->out, s->max_data_size); + (void) ssh2_transport_timer_update(s, 0); + s->rekey_class = RK_NONE; + } else { + ppl_logevent("Initiating key re-exchange (%s)", + s->rekey_reason); + } + } + } while (s->rekey_class == RK_NONE); + + /* Once we exit the above loop, we really are rekeying. */ + goto begin_key_exchange; + + crFinishV; +} + +static void ssh2_transport_higher_layer_packet_callback(void *context) +{ + PacketProtocolLayer *ppl = (PacketProtocolLayer *)context; + ssh_ppl_process_queue(ppl); +} + +static void ssh2_transport_timer(void *ctx, unsigned long now) +{ + struct ssh2_transport_state *s = (struct ssh2_transport_state *)ctx; + unsigned long mins; + unsigned long ticks; + + if (s->kex_in_progress || now != s->next_rekey) + return; + + mins = sanitise_rekey_time(conf_get_int(s->conf, CONF_ssh_rekey_time), 60); + if (mins == 0) + return; + + /* Rekey if enough time has elapsed */ + ticks = mins * 60 * TICKSPERSEC; + if (now - s->last_rekey > ticks - 30*TICKSPERSEC) { + s->rekey_reason = "timeout"; + s->rekey_class = RK_NORMAL; + queue_idempotent_callback(&s->ppl.ic_process_queue); + return; + } + +#ifndef NO_GSSAPI + /* + * Rekey now if we have a new cred or context expires this cycle, + * but not if this is unsafe. + */ + if (conf_get_int(s->conf, CONF_gssapirekey)) { + ssh2_transport_gss_update(s, false); + if ((s->gss_status & GSS_KEX_CAPABLE) != 0 && + (s->gss_status & GSS_CTXT_MAYFAIL) == 0 && + (s->gss_status & (GSS_CRED_UPDATED|GSS_CTXT_EXPIRES)) != 0) { + s->rekey_reason = "GSS credentials updated"; + s->rekey_class = RK_GSS_UPDATE; + queue_idempotent_callback(&s->ppl.ic_process_queue); + return; + } + } +#endif + + /* Try again later. */ + (void) ssh2_transport_timer_update(s, 0); +} + +/* + * The rekey_time is zero except when re-configuring. + * + * We either schedule the next timer and return false, or return true + * to run the callback now, which will call us again to re-schedule on + * completion. + */ +static bool ssh2_transport_timer_update(struct ssh2_transport_state *s, + unsigned long rekey_time) +{ + unsigned long mins; + unsigned long ticks; + + mins = sanitise_rekey_time(conf_get_int(s->conf, CONF_ssh_rekey_time), 60); + ticks = mins * 60 * TICKSPERSEC; + + /* Handle change from previous setting */ + if (rekey_time != 0 && rekey_time != mins) { + unsigned long next; + unsigned long now = GETTICKCOUNT(); + + mins = rekey_time; + ticks = mins * 60 * TICKSPERSEC; + next = s->last_rekey + ticks; + + /* If overdue, caller will rekey synchronously now */ + if (now - s->last_rekey > ticks) + return true; + ticks = next - now; + } + +#ifndef NO_GSSAPI + if (s->gss_kex_used) { + /* + * If we've used GSSAPI key exchange, then we should + * periodically check whether we need to do another one to + * pass new credentials to the server. + */ + unsigned long gssmins; + + /* Check cascade conditions more frequently if configured */ + gssmins = sanitise_rekey_time( + conf_get_int(s->conf, CONF_gssapirekey), GSS_DEF_REKEY_MINS); + if (gssmins > 0) { + if (gssmins < mins) + ticks = (mins = gssmins) * 60 * TICKSPERSEC; + + if ((s->gss_status & GSS_KEX_CAPABLE) != 0) { + /* + * Run next timer even sooner if it would otherwise be + * too close to the context expiration time + */ + if ((s->gss_status & GSS_CTXT_EXPIRES) == 0 && + s->gss_ctxt_lifetime - mins * 60 < 2 * MIN_CTXT_LIFETIME) + ticks -= 2 * MIN_CTXT_LIFETIME * TICKSPERSEC; + } + } + } +#endif + + /* Schedule the next timer */ + s->next_rekey = schedule_timer(ticks, ssh2_transport_timer, s); + return false; +} + +void ssh2_transport_dialog_callback(void *loginv, int ret) +{ + struct ssh2_transport_state *s = (struct ssh2_transport_state *)loginv; + s->dlgret = ret; + ssh_ppl_process_queue(&s->ppl); +} + +#ifndef NO_GSSAPI +/* + * This is called at the beginning of each SSH rekey to determine + * whether we are GSS capable, and if we did GSS key exchange, and are + * delegating credentials, it is also called periodically to determine + * whether we should rekey in order to delegate (more) fresh + * credentials. This is called "credential cascading". + * + * On Windows, with SSPI, we may not get the credential expiration, as + * Windows automatically renews from cached passwords, so the + * credential effectively never expires. Since we still want to + * cascade when the local TGT is updated, we use the expiration of a + * newly obtained context as a proxy for the expiration of the TGT. + */ +static void ssh2_transport_gss_update(struct ssh2_transport_state *s, + bool definitely_rekeying) +{ + PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ + int gss_stat; + time_t gss_cred_expiry; + unsigned long mins; + Ssh_gss_buf gss_sndtok; + Ssh_gss_buf gss_rcvtok; + Ssh_gss_ctx gss_ctx; + + s->gss_status = 0; + + /* + * Nothing to do if no GSSAPI libraries are configured or GSSAPI + * auth is not enabled. + */ + if (s->shgss->libs->nlibraries == 0) + return; + if (!conf_get_bool(s->conf, CONF_try_gssapi_auth) && + !conf_get_bool(s->conf, CONF_try_gssapi_kex)) + return; + + /* Import server name and cache it */ + if (s->shgss->srv_name == GSS_C_NO_NAME) { + gss_stat = s->shgss->lib->import_name( + s->shgss->lib, s->fullhostname, &s->shgss->srv_name); + if (gss_stat != SSH_GSS_OK) { + if (gss_stat == SSH_GSS_BAD_HOST_NAME) + ppl_logevent("GSSAPI import name failed - Bad service name;" + " won't use GSS key exchange"); + else + ppl_logevent("GSSAPI import name failed;" + " won't use GSS key exchange"); + return; + } + } + + /* + * Do we (still) have credentials? Capture the credential + * expiration when available + */ + gss_stat = s->shgss->lib->acquire_cred( + s->shgss->lib, &gss_ctx, &gss_cred_expiry); + if (gss_stat != SSH_GSS_OK) + return; + + SSH_GSS_CLEAR_BUF(&gss_sndtok); + SSH_GSS_CLEAR_BUF(&gss_rcvtok); + + /* + * When acquire_cred yields no useful expiration, get a proxy for + * the cred expiration from the context expiration. + */ + gss_stat = s->shgss->lib->init_sec_context( + s->shgss->lib, &gss_ctx, s->shgss->srv_name, + 0 /* don't delegate */, &gss_rcvtok, &gss_sndtok, + (gss_cred_expiry == GSS_NO_EXPIRATION ? &gss_cred_expiry : NULL), + &s->gss_ctxt_lifetime); + + /* This context was for testing only. */ + if (gss_ctx) + s->shgss->lib->release_cred(s->shgss->lib, &gss_ctx); + + if (gss_stat != SSH_GSS_OK && + gss_stat != SSH_GSS_S_CONTINUE_NEEDED) { + /* + * No point in verbosely interrupting the user to tell them we + * couldn't get GSS credentials, if this was only a check + * between key exchanges to see if fresh ones were available. + * When we do do a rekey, this message (if displayed) will + * appear among the standard rekey blurb, but when we're not, + * it shouldn't pop up all the time regardless. + */ + if (definitely_rekeying) + ppl_logevent("No GSSAPI security context available"); + + return; + } + + if (gss_sndtok.length) + s->shgss->lib->free_tok(s->shgss->lib, &gss_sndtok); + + s->gss_status |= GSS_KEX_CAPABLE; + + /* + * When rekeying to cascade, avoding doing this too close to the + * context expiration time, since the key exchange might fail. + */ + if (s->gss_ctxt_lifetime < MIN_CTXT_LIFETIME) + s->gss_status |= GSS_CTXT_MAYFAIL; + + /* + * If we're not delegating credentials, rekeying is not used to + * refresh them. We must avoid setting GSS_CRED_UPDATED or + * GSS_CTXT_EXPIRES when credential delegation is disabled. + */ + if (!conf_get_bool(s->conf, CONF_gssapifwd)) + return; + + if (s->gss_cred_expiry != GSS_NO_EXPIRATION && + difftime(gss_cred_expiry, s->gss_cred_expiry) > 0) + s->gss_status |= GSS_CRED_UPDATED; + + mins = sanitise_rekey_time( + conf_get_int(s->conf, CONF_gssapirekey), GSS_DEF_REKEY_MINS); + if (mins > 0 && s->gss_ctxt_lifetime <= mins * 60) + s->gss_status |= GSS_CTXT_EXPIRES; +} +#endif /* NO_GSSAPI */ + +ptrlen ssh2_transport_get_session_id(PacketProtocolLayer *ppl) +{ + struct ssh2_transport_state *s; + + assert(ppl->vt == &ssh2_transport_vtable); + s = container_of(ppl, struct ssh2_transport_state, ppl); + + assert(s->got_session_id); + return make_ptrlen(s->session_id, s->session_id_len); +} + +void ssh2_transport_notify_auth_done(PacketProtocolLayer *ppl) +{ + struct ssh2_transport_state *s; + + assert(ppl->vt == &ssh2_transport_vtable); + s = container_of(ppl, struct ssh2_transport_state, ppl); + + s->rekey_reason = NULL; /* will be filled in later */ + s->rekey_class = RK_POST_USERAUTH; + queue_idempotent_callback(&s->ppl.ic_process_queue); +} + +static bool ssh2_transport_get_specials( + PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx) +{ + struct ssh2_transport_state *s = + container_of(ppl, struct ssh2_transport_state, ppl); + bool need_separator = false; + bool toret = false; + + if (ssh_ppl_get_specials(s->higher_layer, add_special, ctx)) { + need_separator = true; + toret = true; + } + + /* + * Don't bother offering rekey-based specials if we've decided the + * remote won't cope with it, since we wouldn't bother sending it + * if asked anyway. + */ + if (!(s->ppl.remote_bugs & BUG_SSH2_REKEY)) { + if (need_separator) { + add_special(ctx, NULL, SS_SEP, 0); + need_separator = false; + } + + add_special(ctx, "Repeat key exchange", SS_REKEY, 0); + toret = true; + + if (s->n_uncert_hostkeys) { + int i; + + add_special(ctx, NULL, SS_SEP, 0); + add_special(ctx, "Cache new host key type", SS_SUBMENU, 0); + for (i = 0; i < s->n_uncert_hostkeys; i++) { + const ssh_keyalg *alg = + ssh2_hostkey_algs[s->uncert_hostkeys[i]].alg; + + add_special(ctx, alg->ssh_id, SS_XCERT, s->uncert_hostkeys[i]); + } + add_special(ctx, NULL, SS_EXITMENU, 0); + } + } + + return toret; +} + +static void ssh2_transport_special_cmd(PacketProtocolLayer *ppl, + SessionSpecialCode code, int arg) +{ + struct ssh2_transport_state *s = + container_of(ppl, struct ssh2_transport_state, ppl); + + if (code == SS_REKEY) { + if (!s->kex_in_progress) { + s->rekey_reason = "at user request"; + s->rekey_class = RK_NORMAL; + queue_idempotent_callback(&s->ppl.ic_process_queue); + } + } else if (code == SS_XCERT) { + if (!s->kex_in_progress) { + s->cross_certifying = s->hostkey_alg = ssh2_hostkey_algs[arg].alg; + s->rekey_reason = "cross-certifying new host key"; + s->rekey_class = RK_NORMAL; + queue_idempotent_callback(&s->ppl.ic_process_queue); + } + } else { + /* Send everything else to the next layer up. This includes + * SS_PING/SS_NOP, which we _could_ handle here - but it's + * better to put them in the connection layer, so they'll + * still work in bare connection mode. */ + ssh_ppl_special_cmd(s->higher_layer, code, arg); + } +} + +/* Safely convert rekey_time to unsigned long minutes */ +static unsigned long sanitise_rekey_time(int rekey_time, unsigned long def) +{ + if (rekey_time < 0 || rekey_time > MAX_TICK_MINS) + rekey_time = def; + return (unsigned long)rekey_time; +} + +static void ssh2_transport_set_max_data_size(struct ssh2_transport_state *s) +{ + s->max_data_size = parse_blocksize( + conf_get_str(s->conf, CONF_ssh_rekey_data)); +} + +static void ssh2_transport_reconfigure(PacketProtocolLayer *ppl, Conf *conf) +{ + struct ssh2_transport_state *s; + const char *rekey_reason = NULL; + bool rekey_mandatory = false; + unsigned long old_max_data_size, rekey_time; + int i; + + assert(ppl->vt == &ssh2_transport_vtable); + s = container_of(ppl, struct ssh2_transport_state, ppl); + + rekey_time = sanitise_rekey_time( + conf_get_int(conf, CONF_ssh_rekey_time), 60); + if (ssh2_transport_timer_update(s, rekey_time)) + rekey_reason = "timeout shortened"; + + old_max_data_size = s->max_data_size; + ssh2_transport_set_max_data_size(s); + if (old_max_data_size != s->max_data_size && + s->max_data_size != 0) { + if (s->max_data_size < old_max_data_size) { + unsigned long diff = old_max_data_size - s->max_data_size; + + dts_consume(&s->stats->out, diff); + dts_consume(&s->stats->in, diff); + if (s->stats->out.expired || s->stats->in.expired) + rekey_reason = "data limit lowered"; + } else { + unsigned long diff = s->max_data_size - old_max_data_size; + if (s->stats->out.running) + s->stats->out.remaining += diff; + if (s->stats->in.running) + s->stats->in.remaining += diff; + } + } + + if (conf_get_bool(s->conf, CONF_compression) != + conf_get_bool(conf, CONF_compression)) { + rekey_reason = "compression setting changed"; + rekey_mandatory = true; + } + + for (i = 0; i < CIPHER_MAX; i++) + if (conf_get_int_int(s->conf, CONF_ssh_cipherlist, i) != + conf_get_int_int(conf, CONF_ssh_cipherlist, i)) { + rekey_reason = "cipher settings changed"; + rekey_mandatory = true; + } + if (conf_get_bool(s->conf, CONF_ssh2_des_cbc) != + conf_get_bool(conf, CONF_ssh2_des_cbc)) { + rekey_reason = "cipher settings changed"; + rekey_mandatory = true; + } + + conf_free(s->conf); + s->conf = conf_copy(conf); + + if (rekey_reason) { + if (!s->kex_in_progress && !ssh2_bpp_rekey_inadvisable(s->ppl.bpp)) { + s->rekey_reason = rekey_reason; + s->rekey_class = RK_NORMAL; + queue_idempotent_callback(&s->ppl.ic_process_queue); + } else if (rekey_mandatory) { + s->deferred_rekey_reason = rekey_reason; + } + } + + /* Also pass the configuration along to our higher layer */ + ssh_ppl_reconfigure(s->higher_layer, conf); +} + +static bool ssh2_transport_want_user_input(PacketProtocolLayer *ppl) +{ + struct ssh2_transport_state *s = + container_of(ppl, struct ssh2_transport_state, ppl); + + /* Just delegate this to the higher layer */ + return ssh_ppl_want_user_input(s->higher_layer); +} + +static void ssh2_transport_got_user_input(PacketProtocolLayer *ppl) +{ + struct ssh2_transport_state *s = + container_of(ppl, struct ssh2_transport_state, ppl); + + /* Just delegate this to the higher layer */ + ssh_ppl_got_user_input(s->higher_layer); +} + +static int weak_algorithm_compare(void *av, void *bv) +{ + uintptr_t a = (uintptr_t)av, b = (uintptr_t)bv; + return a < b ? -1 : a > b ? +1 : 0; +} + +/* + * Wrapper on seat_confirm_weak_crypto_primitive(), which uses the + * tree234 s->weak_algorithms_consented_to to ensure we ask at most + * once about any given crypto primitive. + */ +static int ssh2_transport_confirm_weak_crypto_primitive( + struct ssh2_transport_state *s, const char *type, const char *name, + const void *alg) +{ + if (find234(s->weak_algorithms_consented_to, (void *)alg, NULL)) + return 1; + add234(s->weak_algorithms_consented_to, (void *)alg); + + return seat_confirm_weak_crypto_primitive( + s->ppl.seat, type, name, ssh2_transport_dialog_callback, s); +} + +static size_t ssh2_transport_queued_data_size(PacketProtocolLayer *ppl) +{ + struct ssh2_transport_state *s = + container_of(ppl, struct ssh2_transport_state, ppl); + + return (ssh_ppl_default_queued_data_size(ppl) + + ssh_ppl_queued_data_size(s->higher_layer)); +} diff --git a/ssh/transport2.h b/ssh/transport2.h new file mode 100644 index 00000000..80c39ef2 --- /dev/null +++ b/ssh/transport2.h @@ -0,0 +1,245 @@ +/* + * Header connecting the pieces of the SSH-2 transport layer. + */ + +#ifndef PUTTY_SSH2TRANSPORT_H +#define PUTTY_SSH2TRANSPORT_H + +#ifndef NO_GSSAPI +#include "gssc.h" +#include "gss.h" +#define MIN_CTXT_LIFETIME 5 /* Avoid rekey with short lifetime (seconds) */ +#define GSS_KEX_CAPABLE (1<<0) /* Can do GSS KEX */ +#define GSS_CRED_UPDATED (1<<1) /* Cred updated since previous delegation */ +#define GSS_CTXT_EXPIRES (1<<2) /* Context expires before next timer */ +#define GSS_CTXT_MAYFAIL (1<<3) /* Context may expire during handshake */ +#endif + +#define DH_MIN_SIZE 1024 +#define DH_MAX_SIZE 8192 + +#define MAXKEXLIST 16 +struct kexinit_algorithm { + const char *name; + union { + struct { + const ssh_kex *kex; + bool warn; + } kex; + struct { + const ssh_keyalg *hostkey; + unsigned hkflags; + bool warn; + } hk; + struct { + const ssh_cipheralg *cipher; + bool warn; + } cipher; + struct { + const ssh2_macalg *mac; + bool etm; + } mac; + struct { + const ssh_compression_alg *comp; + bool delayed; + } comp; + } u; +}; + +#define HOSTKEY_ALGORITHMS(X) \ + X(HK_ED25519, ssh_ecdsa_ed25519) \ + X(HK_ED448, ssh_ecdsa_ed448) \ + X(HK_ECDSA, ssh_ecdsa_nistp256) \ + X(HK_ECDSA, ssh_ecdsa_nistp384) \ + X(HK_ECDSA, ssh_ecdsa_nistp521) \ + X(HK_DSA, ssh_dss) \ + X(HK_RSA, ssh_rsa_sha512) \ + X(HK_RSA, ssh_rsa_sha256) \ + X(HK_RSA, ssh_rsa) \ + /* end of list */ +#define COUNT_HOSTKEY_ALGORITHM(type, alg) +1 +#define N_HOSTKEY_ALGORITHMS (0 HOSTKEY_ALGORITHMS(COUNT_HOSTKEY_ALGORITHM)) + +struct ssh_signkey_with_user_pref_id { + const ssh_keyalg *alg; + int id; +}; +extern const struct ssh_signkey_with_user_pref_id + ssh2_hostkey_algs[N_HOSTKEY_ALGORITHMS]; + +/* + * Enumeration of high-level classes of reason why we might need to do + * a repeat key exchange. A full detailed reason in human-readable + * string form for the Event Log is also provided, but this enum type + * is used to discriminate between classes of reason that the code + * needs to treat differently. + * + * RK_NONE == 0 is the value indicating that no rekey is currently + * needed at all. RK_INITIAL indicates that we haven't even done the + * _first_ key exchange yet. RK_SERVER indicates that we're rekeying + * because the server asked for it, not because we decided it + * ourselves. RK_NORMAL is the usual case. RK_GSS_UPDATE indicates + * that we're rekeying because we've just got new GSSAPI credentials + * (hence there's no point in doing a preliminary check for new GSS + * creds, because we already know the answer); RK_POST_USERAUTH + * indicates that _if_ we're going to need a post-userauth immediate + * rekey for any reason, this is the moment to do it. + * + * So RK_POST_USERAUTH only tells the transport layer to _consider_ + * rekeying, not to definitely do it. Also, that one enum value is + * special in that the user-readable reason text is passed in to the + * transport layer as NULL, whereas fills in the reason text after it + * decides whether it needs a rekey at all. In the other cases, + * rekey_reason is passed in to the at the same time as rekey_class. + */ +typedef enum RekeyClass { + RK_NONE = 0, + RK_INITIAL, + RK_SERVER, + RK_NORMAL, + RK_POST_USERAUTH, + RK_GSS_UPDATE +} RekeyClass; + +typedef struct transport_direction { + const ssh_cipheralg *cipher; + const ssh2_macalg *mac; + bool etm_mode; + const ssh_compression_alg *comp; + bool comp_delayed; + int mkkey_adjust; +} transport_direction; + +struct ssh2_transport_state { + int crState, crStateKex; + + PacketProtocolLayer *higher_layer; + PktInQueue pq_in_higher; + PktOutQueue pq_out_higher; + IdempotentCallback ic_pq_out_higher; + + Conf *conf; + char *savedhost; + int savedport; + const char *rekey_reason; + enum RekeyClass rekey_class; + + unsigned long max_data_size; + + const ssh_kex *kex_alg; + const ssh_keyalg *hostkey_alg; + char *hostkey_str; /* string representation, for easy checking in rekeys */ + unsigned char session_id[MAX_HASH_LEN]; + int session_id_len; + int dh_min_size, dh_max_size; + bool dh_got_size_bounds; + dh_ctx *dh_ctx; + ssh_hash *exhash; + + struct DataTransferStats *stats; + + const SshServerConfig *ssc; + + char *client_greeting, *server_greeting; + + bool kex_in_progress; + unsigned long next_rekey, last_rekey; + const char *deferred_rekey_reason; + bool higher_layer_ok; + + /* + * Fully qualified host name, which we need if doing GSSAPI. + */ + char *fullhostname; + + /* shgss is outside the ifdef on purpose to keep APIs simple. If + * NO_GSSAPI is not defined, then it's just an opaque structure + * tag and the pointer will be NULL. */ + struct ssh_connection_shared_gss_state *shgss; +#ifndef NO_GSSAPI + int gss_status; + time_t gss_cred_expiry; /* Re-delegate if newer */ + unsigned long gss_ctxt_lifetime; /* Re-delegate when short */ +#endif + ssh_transient_hostkey_cache *thc; + + bool gss_kex_used; + + int nbits, pbits; + bool warn_kex, warn_hk, warn_cscipher, warn_sccipher; + mp_int *p, *g, *e, *f, *K; + strbuf *outgoing_kexinit, *incoming_kexinit; + strbuf *client_kexinit, *server_kexinit; /* aliases to the above */ + int kex_init_value, kex_reply_value; + transport_direction in, out, *cstrans, *sctrans; + ptrlen hostkeydata, sigdata; + strbuf *hostkeyblob; + char *keystr; + ssh_key *hkey; /* actual host key */ + unsigned hkflags; /* signing flags, used in server */ + RSAKey *rsa_kex_key; /* for RSA kex */ + bool rsa_kex_key_needs_freeing; + ecdh_key *ecdh_key; /* for ECDH kex */ + unsigned char exchange_hash[MAX_HASH_LEN]; + bool can_gssapi_keyex; + bool need_gss_transient_hostkey; + bool warned_about_no_gss_transient_hostkey; + bool got_session_id; + bool can_send_ext_info, post_newkeys_ext_info; + int dlgret; + bool guessok; + bool ignorepkt; + struct kexinit_algorithm kexlists[NKEXLIST][MAXKEXLIST]; +#ifndef NO_GSSAPI + Ssh_gss_buf gss_buf; + Ssh_gss_buf gss_rcvtok, gss_sndtok; + Ssh_gss_stat gss_stat; + Ssh_gss_buf mic; + bool init_token_sent; + bool complete_rcvd; + bool gss_delegate; +#endif + + /* List of crypto primitives below the warning threshold that the + * user has already clicked OK to, so that we don't keep asking + * about them again during rekeys. This directly stores pointers + * to the algorithm vtables, compared by pointer value (which is + * not a determinism hazard, because we're only using it as a + * set). */ + tree234 *weak_algorithms_consented_to; + + /* + * List of host key algorithms for which we _don't_ have a stored + * host key. These are indices into the main hostkey_algs[] array + */ + int uncert_hostkeys[N_HOSTKEY_ALGORITHMS]; + int n_uncert_hostkeys; + + /* + * Indicate that the current rekey is intended to finish with a + * newly cross-certified host key. To double-check that we + * certified the right one, we set this to point to the host key + * algorithm we expect it to be. + */ + const ssh_keyalg *cross_certifying; + + ssh_key *const *hostkeys; + int nhostkeys; + + PacketProtocolLayer ppl; +}; + +/* Helpers shared between transport and kex */ +PktIn *ssh2_transport_pop(struct ssh2_transport_state *s); +void ssh2_transport_dialog_callback(void *, int); + +/* Provided by transport for use in kex */ +void ssh2transport_finalise_exhash(struct ssh2_transport_state *s); + +/* Provided by kex for use in transport. Must set the 'aborted' flag + * if it throws a connection-terminating error, so that the caller + * won't have to check that by looking inside its state parameter + * which might already have been freed. */ +void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted); + +#endif /* PUTTY_SSH2TRANSPORT_H */ diff --git a/ssh/ttymode-list.h b/ssh/ttymode-list.h new file mode 100644 index 00000000..8fffe1c5 --- /dev/null +++ b/ssh/ttymode-list.h @@ -0,0 +1,179 @@ +/* + * List of SSH terminal modes, indicating whether SSH types them as + * char or boolean, and if they're boolean, which POSIX flags field of + * a termios structure they appear in, and what bit mask removes them + * (e.g. CS7 and CS8 aren't single bits). + * + * Sources: RFC 4254, SSH-1 RFC-1.2.31, POSIX 2017, and the Linux + * termios manpage for flags not specified by POSIX. + * + * This is a separate header file rather than my usual style of a + * parametric list macro, because in this case I need to be able to + * #ifdef out each mode in case it's not defined on a particular + * target system. + * + * If you want only the locally defined modes, #define + * TTYMODES_LOCAL_ONLY before including this header. + */ +#if !defined TTYMODES_LOCAL_ONLY || defined VINTR +TTYMODE_CHAR(INTR, 1, VINTR) +#endif +#if !defined TTYMODES_LOCAL_ONLY || defined VQUIT +TTYMODE_CHAR(QUIT, 2, VQUIT) +#endif +#if !defined TTYMODES_LOCAL_ONLY || defined VERASE +TTYMODE_CHAR(ERASE, 3, VERASE) +#endif +#if !defined TTYMODES_LOCAL_ONLY || defined VKILL +TTYMODE_CHAR(KILL, 4, VKILL) +#endif +#if !defined TTYMODES_LOCAL_ONLY || defined VEOF +TTYMODE_CHAR(EOF, 5, VEOF) +#endif +#if !defined TTYMODES_LOCAL_ONLY || defined VEOL +TTYMODE_CHAR(EOL, 6, VEOL) +#endif +#if !defined TTYMODES_LOCAL_ONLY || defined VEOL2 +TTYMODE_CHAR(EOL2, 7, VEOL2) +#endif +#if !defined TTYMODES_LOCAL_ONLY || defined VSTART +TTYMODE_CHAR(START, 8, VSTART) +#endif +#if !defined TTYMODES_LOCAL_ONLY || defined VSTOP +TTYMODE_CHAR(STOP, 9, VSTOP) +#endif +#if !defined TTYMODES_LOCAL_ONLY || defined VSUSP +TTYMODE_CHAR(SUSP, 10, VSUSP) +#endif +#if !defined TTYMODES_LOCAL_ONLY || defined VDSUSP +TTYMODE_CHAR(DSUSP, 11, VDSUSP) +#endif +#if !defined TTYMODES_LOCAL_ONLY || defined VREPRINT +TTYMODE_CHAR(REPRINT, 12, VREPRINT) +#endif +#if !defined TTYMODES_LOCAL_ONLY || defined VWERASE +TTYMODE_CHAR(WERASE, 13, VWERASE) +#endif +#if !defined TTYMODES_LOCAL_ONLY || defined VLNEXT +TTYMODE_CHAR(LNEXT, 14, VLNEXT) +#endif +#if !defined TTYMODES_LOCAL_ONLY || defined VFLUSH +TTYMODE_CHAR(FLUSH, 15, VFLUSH) +#endif +#if !defined TTYMODES_LOCAL_ONLY || defined VSWTCH +TTYMODE_CHAR(SWTCH, 16, VSWTCH) +#endif +#if !defined TTYMODES_LOCAL_ONLY || defined VSTATUS +TTYMODE_CHAR(STATUS, 17, VSTATUS) +#endif +#if !defined TTYMODES_LOCAL_ONLY || defined VDISCARD +TTYMODE_CHAR(DISCARD, 18, VDISCARD) +#endif +#if !defined TTYMODES_LOCAL_ONLY || defined IGNPAR +TTYMODE_FLAG(IGNPAR, 30, i, IGNPAR) +#endif +#if !defined TTYMODES_LOCAL_ONLY || defined PARMRK +TTYMODE_FLAG(PARMRK, 31, i, PARMRK) +#endif +#if !defined TTYMODES_LOCAL_ONLY || defined INPCK +TTYMODE_FLAG(INPCK, 32, i, INPCK) +#endif +#if !defined TTYMODES_LOCAL_ONLY || defined ISTRIP +TTYMODE_FLAG(ISTRIP, 33, i, ISTRIP) +#endif +#if !defined TTYMODES_LOCAL_ONLY || defined INLCR +TTYMODE_FLAG(INLCR, 34, i, INLCR) +#endif +#if !defined TTYMODES_LOCAL_ONLY || defined IGNCR +TTYMODE_FLAG(IGNCR, 35, i, IGNCR) +#endif +#if !defined TTYMODES_LOCAL_ONLY || defined ICRNL +TTYMODE_FLAG(ICRNL, 36, i, ICRNL) +#endif +#if !defined TTYMODES_LOCAL_ONLY || defined IUCLC +TTYMODE_FLAG(IUCLC, 37, i, IUCLC) +#endif +#if !defined TTYMODES_LOCAL_ONLY || defined IXON +TTYMODE_FLAG(IXON, 38, i, IXON) +#endif +#if !defined TTYMODES_LOCAL_ONLY || defined IXANY +TTYMODE_FLAG(IXANY, 39, i, IXANY) +#endif +#if !defined TTYMODES_LOCAL_ONLY || defined IXOFF +TTYMODE_FLAG(IXOFF, 40, i, IXOFF) +#endif +#if !defined TTYMODES_LOCAL_ONLY || defined IMAXBEL +TTYMODE_FLAG(IMAXBEL, 41, i, IMAXBEL) +#endif +#if !defined TTYMODES_LOCAL_ONLY || defined IUTF8 +TTYMODE_FLAG(IUTF8, 42, i, IUTF8) +#endif +#if !defined TTYMODES_LOCAL_ONLY || defined ISIG +TTYMODE_FLAG(ISIG, 50, l, ISIG) +#endif +#if !defined TTYMODES_LOCAL_ONLY || defined ICANON +TTYMODE_FLAG(ICANON, 51, l, ICANON) +#endif +#if !defined TTYMODES_LOCAL_ONLY || defined XCASE +TTYMODE_FLAG(XCASE, 52, l, XCASE) +#endif +#if !defined TTYMODES_LOCAL_ONLY || defined ECHO +TTYMODE_FLAG(ECHO, 53, l, ECHO) +#endif +#if !defined TTYMODES_LOCAL_ONLY || defined ECHOE +TTYMODE_FLAG(ECHOE, 54, l, ECHOE) +#endif +#if !defined TTYMODES_LOCAL_ONLY || defined ECHOK +TTYMODE_FLAG(ECHOK, 55, l, ECHOK) +#endif +#if !defined TTYMODES_LOCAL_ONLY || defined ECHONL +TTYMODE_FLAG(ECHONL, 56, l, ECHONL) +#endif +#if !defined TTYMODES_LOCAL_ONLY || defined NOFLSH +TTYMODE_FLAG(NOFLSH, 57, l, NOFLSH) +#endif +#if !defined TTYMODES_LOCAL_ONLY || defined TOSTOP +TTYMODE_FLAG(TOSTOP, 58, l, TOSTOP) +#endif +#if !defined TTYMODES_LOCAL_ONLY || defined IEXTEN +TTYMODE_FLAG(IEXTEN, 59, l, IEXTEN) +#endif +#if !defined TTYMODES_LOCAL_ONLY || defined ECHOCTL +TTYMODE_FLAG(ECHOCTL, 60, l, ECHOCTL) +#endif +#if !defined TTYMODES_LOCAL_ONLY || defined ECHOKE +TTYMODE_FLAG(ECHOKE, 61, l, ECHOKE) +#endif +#if !defined TTYMODES_LOCAL_ONLY || defined PENDIN +TTYMODE_FLAG(PENDIN, 62, l, PENDIN) +#endif +#if !defined TTYMODES_LOCAL_ONLY || defined OPOST +TTYMODE_FLAG(OPOST, 70, o, OPOST) +#endif +#if !defined TTYMODES_LOCAL_ONLY || defined OLCUC +TTYMODE_FLAG(OLCUC, 71, o, OLCUC) +#endif +#if !defined TTYMODES_LOCAL_ONLY || defined ONLCR +TTYMODE_FLAG(ONLCR, 72, o, ONLCR) +#endif +#if !defined TTYMODES_LOCAL_ONLY || defined OCRNL +TTYMODE_FLAG(OCRNL, 73, o, OCRNL) +#endif +#if !defined TTYMODES_LOCAL_ONLY || defined ONOCR +TTYMODE_FLAG(ONOCR, 74, o, ONOCR) +#endif +#if !defined TTYMODES_LOCAL_ONLY || defined ONLRET +TTYMODE_FLAG(ONLRET, 75, o, ONLRET) +#endif +#if !defined TTYMODES_LOCAL_ONLY || defined CS7 +TTYMODE_FLAG(CS7, 90, c, CSIZE) +#endif +#if !defined TTYMODES_LOCAL_ONLY || defined CS8 +TTYMODE_FLAG(CS8, 91, c, CSIZE) +#endif +#if !defined TTYMODES_LOCAL_ONLY || defined PARENB +TTYMODE_FLAG(PARENB, 92, c, PARENB) +#endif +#if !defined TTYMODES_LOCAL_ONLY || defined PARODD +TTYMODE_FLAG(PARODD, 93, c, PARODD) +#endif diff --git a/ssh/userauth2-client.c b/ssh/userauth2-client.c new file mode 100644 index 00000000..a8f5b2c5 --- /dev/null +++ b/ssh/userauth2-client.c @@ -0,0 +1,1961 @@ +/* + * Packet protocol layer for the client side of the SSH-2 userauth + * protocol (RFC 4252). + */ + +#include + +#include "putty.h" +#include "ssh.h" +#include "bpp.h" +#include "ppl.h" +#include "sshcr.h" + +#ifndef NO_GSSAPI +#include "gssc.h" +#include "gss.h" +#endif + +#define BANNER_LIMIT 131072 + +typedef struct agent_key { + strbuf *blob, *comment; + ptrlen algorithm; +} agent_key; + +struct ssh2_userauth_state { + int crState; + + PacketProtocolLayer *transport_layer, *successor_layer; + Filename *keyfile; + bool show_banner, tryagent, change_username; + char *hostname, *fullhostname; + char *default_username; + bool try_ki_auth, try_gssapi_auth, try_gssapi_kex_auth, gssapi_fwd; + + ptrlen session_id; + enum { + AUTH_TYPE_NONE, + AUTH_TYPE_PUBLICKEY, + AUTH_TYPE_PUBLICKEY_OFFER_LOUD, + AUTH_TYPE_PUBLICKEY_OFFER_QUIET, + AUTH_TYPE_PASSWORD, + AUTH_TYPE_GSSAPI, /* always QUIET */ + AUTH_TYPE_KEYBOARD_INTERACTIVE, + AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET + } type; + bool need_pw, can_pubkey, can_passwd, can_keyb_inter; + int userpass_ret; + bool tried_pubkey_config, done_agent; + struct ssh_connection_shared_gss_state *shgss; +#ifndef NO_GSSAPI + bool can_gssapi; + bool can_gssapi_keyex_auth; + bool tried_gssapi; + bool tried_gssapi_keyex_auth; + time_t gss_cred_expiry; + Ssh_gss_buf gss_buf; + Ssh_gss_buf gss_rcvtok, gss_sndtok; + Ssh_gss_stat gss_stat; +#endif + bool suppress_wait_for_response_packet; + strbuf *last_methods_string; + bool kbd_inter_refused; + prompts_t *cur_prompt; + uint32_t num_prompts; + const char *username; + char *locally_allocated_username; + char *password; + bool got_username; + strbuf *publickey_blob; + bool privatekey_available, privatekey_encrypted; + char *publickey_algorithm; + char *publickey_comment; + void *agent_response_to_free; + ptrlen agent_response; + BinarySource asrc[1]; /* for reading SSH agent response */ + size_t agent_keys_len; + agent_key *agent_keys; + size_t agent_key_index, agent_key_limit; + ptrlen agent_keyalg; + unsigned signflags; + int len; + PktOut *pktout; + bool want_user_input; + + agent_pending_query *auth_agent_query; + bufchain banner; + bufchain_sink banner_bs; + StripCtrlChars *banner_scc; + bool banner_scc_initialised; + + StripCtrlChars *ki_scc; + bool ki_scc_initialised; + bool ki_printed_header; + + PacketProtocolLayer ppl; +}; + +static void ssh2_userauth_free(PacketProtocolLayer *); +static void ssh2_userauth_process_queue(PacketProtocolLayer *); +static bool ssh2_userauth_get_specials( + PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx); +static void ssh2_userauth_special_cmd(PacketProtocolLayer *ppl, + SessionSpecialCode code, int arg); +static bool ssh2_userauth_want_user_input(PacketProtocolLayer *ppl); +static void ssh2_userauth_got_user_input(PacketProtocolLayer *ppl); +static void ssh2_userauth_reconfigure(PacketProtocolLayer *ppl, Conf *conf); + +static void ssh2_userauth_agent_query(struct ssh2_userauth_state *, strbuf *); +static void ssh2_userauth_agent_callback(void *, void *, int); +static void ssh2_userauth_add_sigblob( + struct ssh2_userauth_state *s, PktOut *pkt, ptrlen pkblob, ptrlen sigblob); +static void ssh2_userauth_add_session_id( + struct ssh2_userauth_state *s, strbuf *sigdata); +#ifndef NO_GSSAPI +static PktOut *ssh2_userauth_gss_packet( + struct ssh2_userauth_state *s, const char *authtype); +#endif +static void ssh2_userauth_antispoof_msg( + struct ssh2_userauth_state *s, const char *msg); + +static const PacketProtocolLayerVtable ssh2_userauth_vtable = { + .free = ssh2_userauth_free, + .process_queue = ssh2_userauth_process_queue, + .get_specials = ssh2_userauth_get_specials, + .special_cmd = ssh2_userauth_special_cmd, + .want_user_input = ssh2_userauth_want_user_input, + .got_user_input = ssh2_userauth_got_user_input, + .reconfigure = ssh2_userauth_reconfigure, + .queued_data_size = ssh_ppl_default_queued_data_size, + .name = "ssh-userauth", +}; + +PacketProtocolLayer *ssh2_userauth_new( + PacketProtocolLayer *successor_layer, + const char *hostname, const char *fullhostname, + Filename *keyfile, bool show_banner, bool tryagent, + const char *default_username, bool change_username, + bool try_ki_auth, bool try_gssapi_auth, bool try_gssapi_kex_auth, + bool gssapi_fwd, struct ssh_connection_shared_gss_state *shgss) +{ + struct ssh2_userauth_state *s = snew(struct ssh2_userauth_state); + memset(s, 0, sizeof(*s)); + s->ppl.vt = &ssh2_userauth_vtable; + + s->successor_layer = successor_layer; + s->hostname = dupstr(hostname); + s->fullhostname = dupstr(fullhostname); + s->keyfile = filename_copy(keyfile); + s->show_banner = show_banner; + s->tryagent = tryagent; + s->default_username = dupstr(default_username); + s->change_username = change_username; + s->try_ki_auth = try_ki_auth; + s->try_gssapi_auth = try_gssapi_auth; + s->try_gssapi_kex_auth = try_gssapi_kex_auth; + s->gssapi_fwd = gssapi_fwd; + s->shgss = shgss; + s->last_methods_string = strbuf_new(); + bufchain_init(&s->banner); + bufchain_sink_init(&s->banner_bs, &s->banner); + + return &s->ppl; +} + +void ssh2_userauth_set_transport_layer(PacketProtocolLayer *userauth, + PacketProtocolLayer *transport) +{ + struct ssh2_userauth_state *s = + container_of(userauth, struct ssh2_userauth_state, ppl); + s->transport_layer = transport; +} + +static void ssh2_userauth_free(PacketProtocolLayer *ppl) +{ + struct ssh2_userauth_state *s = + container_of(ppl, struct ssh2_userauth_state, ppl); + bufchain_clear(&s->banner); + + if (s->successor_layer) + ssh_ppl_free(s->successor_layer); + + if (s->agent_keys) { + for (size_t i = 0; i < s->agent_keys_len; i++) { + strbuf_free(s->agent_keys[i].blob); + strbuf_free(s->agent_keys[i].comment); + } + sfree(s->agent_keys); + } + sfree(s->agent_response_to_free); + if (s->auth_agent_query) + agent_cancel_query(s->auth_agent_query); + filename_free(s->keyfile); + sfree(s->default_username); + sfree(s->locally_allocated_username); + sfree(s->hostname); + sfree(s->fullhostname); + sfree(s->publickey_comment); + sfree(s->publickey_algorithm); + if (s->publickey_blob) + strbuf_free(s->publickey_blob); + strbuf_free(s->last_methods_string); + if (s->banner_scc) + stripctrl_free(s->banner_scc); + if (s->ki_scc) + stripctrl_free(s->ki_scc); + sfree(s); +} + +static void ssh2_userauth_filter_queue(struct ssh2_userauth_state *s) +{ + PktIn *pktin; + ptrlen string; + + while ((pktin = pq_peek(s->ppl.in_pq)) != NULL) { + switch (pktin->type) { + case SSH2_MSG_USERAUTH_BANNER: + if (!s->show_banner) { + pq_pop(s->ppl.in_pq); + break; + } + + string = get_string(pktin); + if (string.len > BANNER_LIMIT - bufchain_size(&s->banner)) + string.len = BANNER_LIMIT - bufchain_size(&s->banner); + if (!s->banner_scc_initialised) { + s->banner_scc = seat_stripctrl_new( + s->ppl.seat, BinarySink_UPCAST(&s->banner_bs), SIC_BANNER); + if (s->banner_scc) + stripctrl_enable_line_limiting(s->banner_scc); + s->banner_scc_initialised = true; + } + if (s->banner_scc) + put_datapl(s->banner_scc, string); + else + put_datapl(&s->banner_bs, string); + pq_pop(s->ppl.in_pq); + break; + + default: + return; + } + } +} + +static PktIn *ssh2_userauth_pop(struct ssh2_userauth_state *s) +{ + ssh2_userauth_filter_queue(s); + return pq_pop(s->ppl.in_pq); +} + +static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) +{ + struct ssh2_userauth_state *s = + container_of(ppl, struct ssh2_userauth_state, ppl); + PktIn *pktin; + + ssh2_userauth_filter_queue(s); /* no matter why we were called */ + + crBegin(s->crState); + +#ifndef NO_GSSAPI + s->tried_gssapi = false; + s->tried_gssapi_keyex_auth = false; +#endif + + /* + * Misc one-time setup for authentication. + */ + s->publickey_blob = NULL; + s->session_id = ssh2_transport_get_session_id(s->transport_layer); + + /* + * Load the public half of any configured public key file for + * later use. + */ + if (!filename_is_null(s->keyfile)) { + int keytype; + ppl_logevent("Reading key file \"%s\"", + filename_to_str(s->keyfile)); + keytype = key_type(s->keyfile); + if (keytype == SSH_KEYTYPE_SSH2 || + keytype == SSH_KEYTYPE_SSH2_PUBLIC_RFC4716 || + keytype == SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH) { + const char *error; + s->publickey_blob = strbuf_new(); + if (ppk_loadpub_f(s->keyfile, &s->publickey_algorithm, + BinarySink_UPCAST(s->publickey_blob), + &s->publickey_comment, &error)) { + s->privatekey_available = (keytype == SSH_KEYTYPE_SSH2); + if (!s->privatekey_available) + ppl_logevent("Key file contains public key only"); + s->privatekey_encrypted = ppk_encrypted_f(s->keyfile, NULL); + } else { + ppl_logevent("Unable to load key (%s)", error); + ppl_printf("Unable to load key file \"%s\" (%s)\r\n", + filename_to_str(s->keyfile), error); + strbuf_free(s->publickey_blob); + s->publickey_blob = NULL; + } + } else { + ppl_logevent("Unable to use this key file (%s)", + key_type_to_str(keytype)); + ppl_printf("Unable to use key file \"%s\" (%s)\r\n", + filename_to_str(s->keyfile), + key_type_to_str(keytype)); + s->publickey_blob = NULL; + } + } + + /* + * Find out about any keys Pageant has (but if there's a public + * key configured, filter out all others). + */ + if (s->tryagent && agent_exists()) { + ppl_logevent("Pageant is running. Requesting keys."); + + /* Request the keys held by the agent. */ + { + strbuf *request = strbuf_new_for_agent_query(); + put_byte(request, SSH2_AGENTC_REQUEST_IDENTITIES); + ssh2_userauth_agent_query(s, request); + strbuf_free(request); + crWaitUntilV(!s->auth_agent_query); + } + BinarySource_BARE_INIT_PL(s->asrc, s->agent_response); + + get_uint32(s->asrc); /* skip length field */ + if (get_byte(s->asrc) == SSH2_AGENT_IDENTITIES_ANSWER) { + size_t nkeys = get_uint32(s->asrc); + size_t origpos = s->asrc->pos; + + /* + * Check that the agent response is well formed. + */ + for (size_t i = 0; i < nkeys; i++) { + get_string(s->asrc); /* blob */ + get_string(s->asrc); /* comment */ + if (get_err(s->asrc)) { + ppl_logevent("Pageant's response was truncated"); + goto done_agent_query; + } + } + + /* + * Copy the list of public-key blobs out of the Pageant + * response. + */ + BinarySource_REWIND_TO(s->asrc, origpos); + s->agent_keys_len = nkeys; + s->agent_keys = snewn(s->agent_keys_len, agent_key); + for (size_t i = 0; i < nkeys; i++) { + s->agent_keys[i].blob = strbuf_new(); + put_datapl(s->agent_keys[i].blob, get_string(s->asrc)); + s->agent_keys[i].comment = strbuf_new(); + put_datapl(s->agent_keys[i].comment, get_string(s->asrc)); + + /* Also, extract the algorithm string from the start + * of the public-key blob. */ + BinarySource src[1]; + BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf( + s->agent_keys[i].blob)); + s->agent_keys[i].algorithm = get_string(src); + } + + ppl_logevent("Pageant has %"SIZEu" SSH-2 keys", nkeys); + + if (s->publickey_blob) { + /* + * If we've been given a specific public key blob, + * filter the list of keys to try from the agent down + * to only that one, or none if it's not there. + */ + ptrlen our_blob = ptrlen_from_strbuf(s->publickey_blob); + size_t i; + + for (i = 0; i < nkeys; i++) { + if (ptrlen_eq_ptrlen(our_blob, ptrlen_from_strbuf( + s->agent_keys[i].blob))) + break; + } + + if (i < nkeys) { + ppl_logevent("Pageant key #%"SIZEu" matches " + "configured key file", i); + s->agent_key_index = i; + s->agent_key_limit = i+1; + } else { + ppl_logevent("Configured key file not in Pageant"); + s->agent_key_index = 0; + s->agent_key_limit = 0; + } + } else { + /* + * Otherwise, try them all. + */ + s->agent_key_index = 0; + s->agent_key_limit = nkeys; + } + } else { + ppl_logevent("Failed to get reply from Pageant"); + } + done_agent_query:; + } + + /* + * 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->got_username = false; + while (1) { + /* + * Get a username. + */ + if (s->got_username && !s->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 ((s->username = s->default_username) == NULL) { + s->cur_prompt = new_prompts(); + s->cur_prompt->to_server = true; + s->cur_prompt->from_server = false; + s->cur_prompt->name = dupstr("SSH login name"); + add_prompt(s->cur_prompt, dupstr("login as: "), true); + s->userpass_ret = seat_get_userpass_input( + s->ppl.seat, s->cur_prompt, NULL); + while (1) { + while (s->userpass_ret < 0 && + bufchain_size(s->ppl.user_input) > 0) + s->userpass_ret = seat_get_userpass_input( + s->ppl.seat, s->cur_prompt, s->ppl.user_input); + + if (s->userpass_ret >= 0) + break; + + s->want_user_input = true; + crReturnV; + s->want_user_input = false; + } + if (!s->userpass_ret) { + /* + * seat_get_userpass_input() failed to get a username. + * Terminate. + */ + free_prompts(s->cur_prompt); + ssh_user_close(s->ppl.ssh, "No username provided"); + return; + } + sfree(s->locally_allocated_username); /* for change_username */ + s->username = s->locally_allocated_username = + prompt_get_result(s->cur_prompt->prompts[0]); + free_prompts(s->cur_prompt); + } else { + if (seat_verbose(s->ppl.seat) || seat_interactive(s->ppl.seat)) + ppl_printf("Using username \"%s\".\r\n", s->username); + } + 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. + */ + s->ppl.bpp->pls->actx = SSH2_PKTCTX_NOAUTH; + + s->pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST); + put_stringz(s->pktout, s->username); + put_stringz(s->pktout, s->successor_layer->vt->name); + put_stringz(s->pktout, "none"); /* method */ + pq_push(s->ppl.out_pq, s->pktout); + s->type = AUTH_TYPE_NONE; + + s->tried_pubkey_config = false; + s->kbd_inter_refused = false; + s->done_agent = false; + + while (1) { + /* + * Wait for the result of the last authentication request, + * unless the request terminated for some reason on our + * own side. + */ + if (s->suppress_wait_for_response_packet) { + pktin = NULL; + s->suppress_wait_for_response_packet = false; + } else { + crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL); + } + + /* + * 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.) + * + * 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.) + * + * The banner data has been sanitised already by this + * point, but we still need to precede and follow it with + * anti-spoofing header lines. + */ + if (bufchain_size(&s->banner) && + (seat_verbose(s->ppl.seat) || seat_interactive(s->ppl.seat))) { + if (s->banner_scc) { + ssh2_userauth_antispoof_msg( + s, "Pre-authentication banner message from server:"); + seat_set_trust_status(s->ppl.seat, false); + } + + bool mid_line = false; + while (bufchain_size(&s->banner) > 0) { + ptrlen data = bufchain_prefix(&s->banner); + seat_stderr_pl(s->ppl.seat, data); + mid_line = + (((const char *)data.ptr)[data.len-1] != '\n'); + bufchain_consume(&s->banner, data.len); + } + bufchain_clear(&s->banner); + + if (mid_line) + seat_stderr_pl(s->ppl.seat, PTRLEN_LITERAL("\r\n")); + + if (s->banner_scc) { + seat_set_trust_status(s->ppl.seat, true); + ssh2_userauth_antispoof_msg( + s, "End of banner message from server"); + } + } + + if (pktin && pktin->type == SSH2_MSG_USERAUTH_SUCCESS) { + ppl_logevent("Access granted"); + goto userauth_success; + } + + if (pktin && pktin->type != SSH2_MSG_USERAUTH_FAILURE && + s->type != AUTH_TYPE_GSSAPI) { + ssh_proto_error(s->ppl.ssh, "Received unexpected packet " + "in response to authentication request, " + "type %d (%s)", pktin->type, + ssh2_pkt_type(s->ppl.bpp->pls->kctx, + s->ppl.bpp->pls->actx, + pktin->type)); + return; + } + + /* + * 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 && pktin->type == SSH2_MSG_USERAUTH_FAILURE) { + ptrlen methods = get_string(pktin); + bool partial_success = get_bool(pktin); + + if (!partial_success) { + /* + * We have received an unequivocal Access + * Denied. This can translate to a variety of + * messages, or no message at all. + * + * For forms of authentication which are attempted + * implicitly, by which I mean without printing + * anything in the window indicating that we're + * trying them, we should never print 'Access + * denied'. + * + * If we do print a message saying that we're + * attempting some kind of authentication, it's OK + * to print a followup message saying it failed - + * but the message may sometimes be more specific + * than simply '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) + ppl_printf("Server refused our key\r\n"); + ppl_logevent("Server refused our key"); + } else if (s->type == AUTH_TYPE_PUBLICKEY) { + /* This _shouldn't_ happen except by a + * protocol bug causing client and server to + * disagree on what is a correct signature. */ + ppl_printf("Server refused public-key signature" + " despite accepting key!\r\n"); + ppl_logevent("Server refused public-key signature" + " despite accepting key!"); + } else if (s->type==AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET) { + /* quiet, so no ppl_printf */ + ppl_logevent("Server refused keyboard-interactive " + "authentication"); + } else if (s->type==AUTH_TYPE_GSSAPI) { + /* always quiet, so no ppl_printf */ + /* also, the code down in the GSSAPI block has + * already logged this in the Event Log */ + } else if (s->type == AUTH_TYPE_KEYBOARD_INTERACTIVE) { + ppl_logevent("Keyboard-interactive authentication " + "failed"); + ppl_printf("Access denied\r\n"); + } else { + assert(s->type == AUTH_TYPE_PASSWORD); + ppl_logevent("Password authentication failed"); + ppl_printf("Access denied\r\n"); + + if (s->change_username) { + /* XXX perhaps we should allow + * keyboard-interactive to do this too? */ + goto try_new_username; + } + } + } else { + ppl_printf("Further authentication required\r\n"); + ppl_logevent("Further authentication required"); + } + + /* + * Save the methods string for use in error messages. + */ + strbuf_clear(s->last_methods_string); + put_datapl(s->last_methods_string, methods); + + /* + * Scan it for method identifiers we know about. + */ + bool srv_pubkey = false, srv_passwd = false; + bool srv_keyb_inter = false; +#ifndef NO_GSSAPI + bool srv_gssapi = false, srv_gssapi_keyex_auth = false; +#endif + + for (ptrlen method; get_commasep_word(&methods, &method) ;) { + if (ptrlen_eq_string(method, "publickey")) + srv_pubkey = true; + else if (ptrlen_eq_string(method, "password")) + srv_passwd = true; + else if (ptrlen_eq_string(method, "keyboard-interactive")) + srv_keyb_inter = true; +#ifndef NO_GSSAPI + else if (ptrlen_eq_string(method, "gssapi-with-mic")) + srv_gssapi = true; + else if (ptrlen_eq_string(method, "gssapi-keyex")) + srv_gssapi_keyex_auth = true; +#endif + } + + /* + * And combine those flags with our own configuration + * and context to set the main can_foo variables. + */ + s->can_pubkey = srv_pubkey; + s->can_passwd = srv_passwd; + s->can_keyb_inter = s->try_ki_auth && srv_keyb_inter; +#ifndef NO_GSSAPI + s->can_gssapi = s->try_gssapi_auth && srv_gssapi && + s->shgss->libs->nlibraries > 0; + s->can_gssapi_keyex_auth = s->try_gssapi_kex_auth && + srv_gssapi_keyex_auth && + s->shgss->libs->nlibraries > 0 && s->shgss->ctx; +#endif + } + + s->ppl.bpp->pls->actx = SSH2_PKTCTX_NOAUTH; + +#ifndef NO_GSSAPI + if (s->can_gssapi_keyex_auth && !s->tried_gssapi_keyex_auth) { + + /* gssapi-keyex authentication */ + + s->type = AUTH_TYPE_GSSAPI; + s->tried_gssapi_keyex_auth = true; + s->ppl.bpp->pls->actx = SSH2_PKTCTX_GSSAPI; + + if (s->shgss->lib->gsslogmsg) + ppl_logevent("%s", s->shgss->lib->gsslogmsg); + + ppl_logevent("Trying gssapi-keyex..."); + s->pktout = ssh2_userauth_gss_packet(s, "gssapi-keyex"); + pq_push(s->ppl.out_pq, s->pktout); + s->shgss->lib->release_cred(s->shgss->lib, &s->shgss->ctx); + s->shgss->ctx = NULL; + + continue; + } else +#endif /* NO_GSSAPI */ + + if (s->can_pubkey && !s->done_agent && + s->agent_key_index < s->agent_key_limit) { + + /* + * Attempt public-key authentication using a key from Pageant. + */ + s->agent_keyalg = s->agent_keys[s->agent_key_index].algorithm; + s->signflags = 0; + if (ptrlen_eq_string(s->agent_keyalg, "ssh-rsa")) { + /* Try to upgrade ssh-rsa to one of the rsa-sha2-* family, + * if the server has announced support for them. */ + if (s->ppl.bpp->ext_info_rsa_sha512_ok) { + s->agent_keyalg = PTRLEN_LITERAL("rsa-sha2-512"); + s->signflags = SSH_AGENT_RSA_SHA2_512; + } else if (s->ppl.bpp->ext_info_rsa_sha256_ok) { + s->agent_keyalg = PTRLEN_LITERAL("rsa-sha2-256"); + s->signflags = SSH_AGENT_RSA_SHA2_256; + } + } + + s->ppl.bpp->pls->actx = SSH2_PKTCTX_PUBLICKEY; + + ppl_logevent("Trying Pageant key #%"SIZEu, s->agent_key_index); + + /* See if server will accept it */ + s->pktout = ssh_bpp_new_pktout( + s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST); + put_stringz(s->pktout, s->username); + put_stringz(s->pktout, s->successor_layer->vt->name); + put_stringz(s->pktout, "publickey"); + /* method */ + put_bool(s->pktout, false); /* no signature included */ + put_stringpl(s->pktout, s->agent_keyalg); + put_stringpl(s->pktout, ptrlen_from_strbuf( + s->agent_keys[s->agent_key_index].blob)); + pq_push(s->ppl.out_pq, s->pktout); + s->type = AUTH_TYPE_PUBLICKEY_OFFER_QUIET; + + crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL); + if (pktin->type != SSH2_MSG_USERAUTH_PK_OK) { + + /* Offer of key refused, presumably via + * USERAUTH_FAILURE. Requeue for the next iteration. */ + pq_push_front(s->ppl.in_pq, pktin); + + } else { + strbuf *agentreq, *sigdata; + ptrlen comment = ptrlen_from_strbuf( + s->agent_keys[s->agent_key_index].comment); + + if (seat_verbose(s->ppl.seat)) + ppl_printf("Authenticating with public key " + "\"%.*s\" from agent\r\n", + PTRLEN_PRINTF(comment)); + + /* + * Server is willing to accept the key. + * Construct a SIGN_REQUEST. + */ + s->pktout = ssh_bpp_new_pktout( + s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST); + put_stringz(s->pktout, s->username); + put_stringz(s->pktout, s->successor_layer->vt->name); + put_stringz(s->pktout, "publickey"); + /* method */ + put_bool(s->pktout, true); /* signature included */ + put_stringpl(s->pktout, s->agent_keyalg); + put_stringpl(s->pktout, ptrlen_from_strbuf( + s->agent_keys[s->agent_key_index].blob)); + + /* Ask agent for signature. */ + agentreq = strbuf_new_for_agent_query(); + put_byte(agentreq, SSH2_AGENTC_SIGN_REQUEST); + put_stringpl(agentreq, ptrlen_from_strbuf( + s->agent_keys[s->agent_key_index].blob)); + /* Now the data to be signed... */ + sigdata = strbuf_new(); + ssh2_userauth_add_session_id(s, sigdata); + put_data(sigdata, s->pktout->data + 5, + s->pktout->length - 5); + put_stringsb(agentreq, sigdata); + /* And finally the flags word. */ + put_uint32(agentreq, s->signflags); + ssh2_userauth_agent_query(s, agentreq); + strbuf_free(agentreq); + crWaitUntilV(!s->auth_agent_query); + + if (s->agent_response.ptr) { + ptrlen sigblob; + BinarySource src[1]; + BinarySource_BARE_INIT(src, s->agent_response.ptr, + s->agent_response.len); + get_uint32(src); /* skip length field */ + if (get_byte(src) == SSH2_AGENT_SIGN_RESPONSE && + (sigblob = get_string(src), !get_err(src))) { + ppl_logevent("Sending Pageant's response"); + ssh2_userauth_add_sigblob( + s, s->pktout, + ptrlen_from_strbuf( + s->agent_keys[s->agent_key_index].blob), + sigblob); + pq_push(s->ppl.out_pq, s->pktout); + s->type = AUTH_TYPE_PUBLICKEY; + } else { + ppl_logevent("Pageant refused signing request"); + ppl_printf("Pageant failed to " + "provide a signature\r\n"); + s->suppress_wait_for_response_packet = true; + ssh_free_pktout(s->pktout); + } + } else { + ppl_logevent("Pageant failed to respond to " + "signing request"); + ppl_printf("Pageant failed to " + "respond to signing request\r\n"); + s->suppress_wait_for_response_packet = true; + ssh_free_pktout(s->pktout); + } + } + + /* Do we have any keys left to try? */ + if (++s->agent_key_index >= s->agent_key_limit) + s->done_agent = true; + + } else if (s->can_pubkey && s->publickey_blob && + s->privatekey_available && !s->tried_pubkey_config) { + + ssh2_userkey *key; /* not live over crReturn */ + char *passphrase; /* not live over crReturn */ + + s->ppl.bpp->pls->actx = SSH2_PKTCTX_PUBLICKEY; + + s->tried_pubkey_config = true; + + /* + * Try the public key supplied in the configuration. + * + * First, try to upgrade its algorithm. + */ + if (!strcmp(s->publickey_algorithm, "ssh-rsa")) { + /* Try to upgrade ssh-rsa to one of the rsa-sha2-* family, + * if the server has announced support for them. */ + if (s->ppl.bpp->ext_info_rsa_sha512_ok) { + sfree(s->publickey_algorithm); + s->publickey_algorithm = dupstr("rsa-sha2-512"); + s->signflags = SSH_AGENT_RSA_SHA2_512; + } else if (s->ppl.bpp->ext_info_rsa_sha256_ok) { + sfree(s->publickey_algorithm); + s->publickey_algorithm = dupstr("rsa-sha2-256"); + s->signflags = SSH_AGENT_RSA_SHA2_256; + } + } + + /* + * Offer the public blob to see if the server is willing to + * accept it. + */ + s->pktout = ssh_bpp_new_pktout( + s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST); + put_stringz(s->pktout, s->username); + put_stringz(s->pktout, s->successor_layer->vt->name); + put_stringz(s->pktout, "publickey"); /* method */ + put_bool(s->pktout, false); + /* no signature included */ + put_stringz(s->pktout, s->publickey_algorithm); + put_string(s->pktout, s->publickey_blob->s, + s->publickey_blob->len); + pq_push(s->ppl.out_pq, s->pktout); + ppl_logevent("Offered public key"); + + crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL); + if (pktin->type != SSH2_MSG_USERAUTH_PK_OK) { + /* Key refused. Give up. */ + pq_push_front(s->ppl.in_pq, pktin); + s->type = AUTH_TYPE_PUBLICKEY_OFFER_LOUD; + continue; /* process this new message */ + } + ppl_logevent("Offer of public key accepted"); + + /* + * Actually attempt a serious authentication using + * the key. + */ + if (seat_verbose(s->ppl.seat)) + ppl_printf("Authenticating with public key \"%s\"\r\n", + s->publickey_comment); + + key = NULL; + while (!key) { + const char *error; /* not live over crReturn */ + if (s->privatekey_encrypted) { + /* + * Get a passphrase from the user. + */ + s->cur_prompt = new_prompts(); + s->cur_prompt->to_server = false; + s->cur_prompt->from_server = false; + s->cur_prompt->name = dupstr("SSH key passphrase"); + add_prompt(s->cur_prompt, + dupprintf("Passphrase for key \"%s\": ", + s->publickey_comment), + false); + s->userpass_ret = seat_get_userpass_input( + s->ppl.seat, s->cur_prompt, NULL); + while (1) { + while (s->userpass_ret < 0 && + bufchain_size(s->ppl.user_input) > 0) + s->userpass_ret = seat_get_userpass_input( + s->ppl.seat, s->cur_prompt, + s->ppl.user_input); + + if (s->userpass_ret >= 0) + break; + + s->want_user_input = true; + crReturnV; + s->want_user_input = false; + } + if (!s->userpass_ret) { + /* Failed to get a passphrase. Terminate. */ + free_prompts(s->cur_prompt); + ssh_bpp_queue_disconnect( + s->ppl.bpp, "Unable to authenticate", + SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER); + ssh_user_close(s->ppl.ssh, "User aborted at " + "passphrase prompt"); + return; + } + passphrase = + prompt_get_result(s->cur_prompt->prompts[0]); + free_prompts(s->cur_prompt); + } else { + passphrase = NULL; /* no passphrase needed */ + } + + /* + * Try decrypting the key. + */ + key = ppk_load_f(s->keyfile, passphrase, &error); + if (passphrase) { + /* burn the evidence */ + smemclr(passphrase, strlen(passphrase)); + sfree(passphrase); + } + if (key == SSH2_WRONG_PASSPHRASE || key == NULL) { + if (passphrase && + (key == SSH2_WRONG_PASSPHRASE)) { + ppl_printf("Wrong passphrase\r\n"); + key = NULL; + /* and loop again */ + } else { + ppl_printf("Unable to load private key (%s)\r\n", + error); + key = NULL; + s->suppress_wait_for_response_packet = true; + break; /* try something else */ + } + } else { + /* FIXME: if we ever support variable signature + * flags, this is somewhere they'll need to be + * put */ + char *invalid = ssh_key_invalid(key->key, 0); + if (invalid) { + ppl_printf("Cannot use this private key (%s)\r\n", + invalid); + ssh_key_free(key->key); + sfree(key->comment); + sfree(key); + sfree(invalid); + key = NULL; + s->suppress_wait_for_response_packet = true; + break; /* try something else */ + } + } + } + + if (key) { + strbuf *pkblob, *sigdata, *sigblob; + + /* + * 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 = ssh_bpp_new_pktout( + s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST); + put_stringz(s->pktout, s->username); + put_stringz(s->pktout, s->successor_layer->vt->name); + put_stringz(s->pktout, "publickey"); /* method */ + put_bool(s->pktout, true); /* signature follows */ + put_stringz(s->pktout, s->publickey_algorithm); + pkblob = strbuf_new(); + ssh_key_public_blob(key->key, BinarySink_UPCAST(pkblob)); + put_string(s->pktout, pkblob->s, pkblob->len); + + /* + * The data to be signed is: + * + * string session-id + * + * followed by everything so far placed in the + * outgoing packet. + */ + sigdata = strbuf_new(); + ssh2_userauth_add_session_id(s, sigdata); + put_data(sigdata, s->pktout->data + 5, + s->pktout->length - 5); + sigblob = strbuf_new(); + ssh_key_sign(key->key, ptrlen_from_strbuf(sigdata), + s->signflags, BinarySink_UPCAST(sigblob)); + strbuf_free(sigdata); + ssh2_userauth_add_sigblob( + s, s->pktout, ptrlen_from_strbuf(pkblob), + ptrlen_from_strbuf(sigblob)); + strbuf_free(pkblob); + strbuf_free(sigblob); + + pq_push(s->ppl.out_pq, s->pktout); + ppl_logevent("Sent public key signature"); + s->type = AUTH_TYPE_PUBLICKEY; + ssh_key_free(key->key); + sfree(key->comment); + sfree(key); + } + +#ifndef NO_GSSAPI + } else if (s->can_gssapi && !s->tried_gssapi) { + + /* gssapi-with-mic authentication */ + + ptrlen data; + + s->type = AUTH_TYPE_GSSAPI; + s->tried_gssapi = true; + s->ppl.bpp->pls->actx = SSH2_PKTCTX_GSSAPI; + + if (s->shgss->lib->gsslogmsg) + ppl_logevent("%s", s->shgss->lib->gsslogmsg); + + /* Sending USERAUTH_REQUEST with "gssapi-with-mic" method */ + ppl_logevent("Trying gssapi-with-mic..."); + s->pktout = ssh_bpp_new_pktout( + s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST); + put_stringz(s->pktout, s->username); + put_stringz(s->pktout, s->successor_layer->vt->name); + put_stringz(s->pktout, "gssapi-with-mic"); + ppl_logevent("Attempting GSSAPI authentication"); + + /* add mechanism info */ + s->shgss->lib->indicate_mech(s->shgss->lib, &s->gss_buf); + + /* number of GSSAPI mechanisms */ + put_uint32(s->pktout, 1); + + /* length of OID + 2 */ + put_uint32(s->pktout, s->gss_buf.length + 2); + put_byte(s->pktout, SSH2_GSS_OIDTYPE); + + /* length of OID */ + put_byte(s->pktout, s->gss_buf.length); + + put_data(s->pktout, s->gss_buf.value, s->gss_buf.length); + pq_push(s->ppl.out_pq, s->pktout); + crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL); + if (pktin->type != SSH2_MSG_USERAUTH_GSSAPI_RESPONSE) { + ppl_logevent("GSSAPI authentication request refused"); + pq_push_front(s->ppl.in_pq, pktin); + continue; + } + + /* check returned packet ... */ + + data = get_string(pktin); + s->gss_rcvtok.value = (char *)data.ptr; + s->gss_rcvtok.length = data.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) ) { + ppl_logevent("GSSAPI authentication - wrong response " + "from server"); + continue; + } + + /* Import server name if not cached from KEX */ + if (s->shgss->srv_name == GSS_C_NO_NAME) { + s->gss_stat = s->shgss->lib->import_name( + s->shgss->lib, s->fullhostname, &s->shgss->srv_name); + if (s->gss_stat != SSH_GSS_OK) { + if (s->gss_stat == SSH_GSS_BAD_HOST_NAME) + ppl_logevent("GSSAPI import name failed -" + " Bad service name"); + else + ppl_logevent("GSSAPI import name failed"); + continue; + } + } + + /* Allocate our gss_ctx */ + s->gss_stat = s->shgss->lib->acquire_cred( + s->shgss->lib, &s->shgss->ctx, NULL); + if (s->gss_stat != SSH_GSS_OK) { + ppl_logevent("GSSAPI authentication failed to get " + "credentials"); + /* The failure was on our side, so the server + * won't be sending a response packet indicating + * failure. Avoid waiting for it next time round + * the loop. */ + s->suppress_wait_for_response_packet = true; + continue; + } + + /* initial tokens are empty */ + SSH_GSS_CLEAR_BUF(&s->gss_rcvtok); + SSH_GSS_CLEAR_BUF(&s->gss_sndtok); + + /* now enter the loop */ + do { + /* + * When acquire_cred yields no useful expiration, go with + * the service ticket expiration. + */ + s->gss_stat = s->shgss->lib->init_sec_context + (s->shgss->lib, + &s->shgss->ctx, + s->shgss->srv_name, + s->gssapi_fwd, + &s->gss_rcvtok, + &s->gss_sndtok, + NULL, + NULL); + + if (s->gss_stat!=SSH_GSS_S_COMPLETE && + s->gss_stat!=SSH_GSS_S_CONTINUE_NEEDED) { + ppl_logevent("GSSAPI authentication initialisation " + "failed"); + + if (s->shgss->lib->display_status(s->shgss->lib, + s->shgss->ctx, &s->gss_buf) == SSH_GSS_OK) { + ppl_logevent("%s", (char *)s->gss_buf.value); + sfree(s->gss_buf.value); + } + + pq_push_front(s->ppl.in_pq, pktin); + break; + } + ppl_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 = + ssh_bpp_new_pktout( + s->ppl.bpp, SSH2_MSG_USERAUTH_GSSAPI_TOKEN); + put_string(s->pktout, + s->gss_sndtok.value, s->gss_sndtok.length); + pq_push(s->ppl.out_pq, s->pktout); + s->shgss->lib->free_tok(s->shgss->lib, &s->gss_sndtok); + } + + if (s->gss_stat == SSH_GSS_S_CONTINUE_NEEDED) { + crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL); + + if (pktin->type == SSH2_MSG_USERAUTH_GSSAPI_ERRTOK) { + /* + * Per RFC 4462 section 3.9, this packet + * type MUST immediately precede an + * ordinary USERAUTH_FAILURE. + * + * We currently don't know how to do + * anything with the GSSAPI error token + * contained in this packet, so we ignore + * it and just wait for the following + * FAILURE. + */ + crMaybeWaitUntilV( + (pktin = ssh2_userauth_pop(s)) != NULL); + if (pktin->type != SSH2_MSG_USERAUTH_FAILURE) { + ssh_proto_error( + s->ppl.ssh, "Received unexpected packet " + "after SSH_MSG_USERAUTH_GSSAPI_ERRTOK " + "(expected SSH_MSG_USERAUTH_FAILURE): " + "type %d (%s)", pktin->type, + ssh2_pkt_type(s->ppl.bpp->pls->kctx, + s->ppl.bpp->pls->actx, + pktin->type)); + return; + } + } + + if (pktin->type == SSH2_MSG_USERAUTH_FAILURE) { + ppl_logevent("GSSAPI authentication failed"); + s->gss_stat = SSH_GSS_FAILURE; + pq_push_front(s->ppl.in_pq, pktin); + break; + } else if (pktin->type != + SSH2_MSG_USERAUTH_GSSAPI_TOKEN) { + ppl_logevent("GSSAPI authentication -" + " bad server response"); + s->gss_stat = SSH_GSS_FAILURE; + break; + } + data = get_string(pktin); + s->gss_rcvtok.value = (char *)data.ptr; + s->gss_rcvtok.length = data.len; + } + } while (s-> gss_stat == SSH_GSS_S_CONTINUE_NEEDED); + + if (s->gss_stat != SSH_GSS_OK) { + s->shgss->lib->release_cred(s->shgss->lib, &s->shgss->ctx); + continue; + } + ppl_logevent("GSSAPI authentication loop finished OK"); + + /* Now send the MIC */ + + s->pktout = ssh2_userauth_gss_packet(s, "gssapi-with-mic"); + pq_push(s->ppl.out_pq, s->pktout); + + s->shgss->lib->release_cred(s->shgss->lib, &s->shgss->ctx); + continue; +#endif + } else if (s->can_keyb_inter && !s->kbd_inter_refused) { + + /* + * Keyboard-interactive authentication. + */ + + s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE; + + s->ppl.bpp->pls->actx = SSH2_PKTCTX_KBDINTER; + + s->pktout = ssh_bpp_new_pktout( + s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST); + put_stringz(s->pktout, s->username); + put_stringz(s->pktout, s->successor_layer->vt->name); + put_stringz(s->pktout, "keyboard-interactive"); + /* method */ + put_stringz(s->pktout, ""); /* lang */ + put_stringz(s->pktout, ""); /* submethods */ + pq_push(s->ppl.out_pq, s->pktout); + + ppl_logevent("Attempting keyboard-interactive authentication"); + + if (!s->ki_scc_initialised) { + s->ki_scc = seat_stripctrl_new( + s->ppl.seat, NULL, SIC_KI_PROMPTS); + if (s->ki_scc) + stripctrl_enable_line_limiting(s->ki_scc); + s->ki_scc_initialised = true; + } + + crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL); + 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. */ + pq_push_front(s->ppl.in_pq, pktin); + s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET; + s->kbd_inter_refused = true; /* don't try it again */ + continue; + } + + s->ki_printed_header = false; + + /* + * Loop while the server continues to send INFO_REQUESTs. + */ + while (pktin->type == SSH2_MSG_USERAUTH_INFO_REQUEST) { + + ptrlen name, inst; + strbuf *sb; + + /* + * We've got a fresh USERAUTH_INFO_REQUEST. + * Get the preamble and start building a prompt. + */ + name = get_string(pktin); + inst = get_string(pktin); + get_string(pktin); /* skip language tag */ + s->cur_prompt = new_prompts(); + s->cur_prompt->to_server = true; + s->cur_prompt->from_server = true; + + /* + * Get any prompt(s) from the packet. + */ + s->num_prompts = get_uint32(pktin); + for (uint32_t i = 0; i < s->num_prompts; i++) { + ptrlen prompt = get_string(pktin); + bool echo = get_bool(pktin); + + if (get_err(pktin)) { + ssh_proto_error( + s->ppl.ssh, "Server sent truncated " + "SSH_MSG_USERAUTH_INFO_REQUEST packet"); + return; + } + + sb = strbuf_new(); + if (!prompt.len) { + put_datapl(sb, PTRLEN_LITERAL( + ": ")); + } else if (s->ki_scc) { + stripctrl_retarget( + s->ki_scc, BinarySink_UPCAST(sb)); + put_datapl(s->ki_scc, prompt); + stripctrl_retarget(s->ki_scc, NULL); + } else { + put_datapl(sb, prompt); + } + add_prompt(s->cur_prompt, strbuf_to_str(sb), echo); + } + + /* + * Make the header strings. This includes the + * 'name' (optional dialog-box title) and + * 'instruction' from the server. + * + * First, display our disambiguating header line + * if this is the first time round the loop - + * _unless_ the server has sent a completely empty + * k-i packet with no prompts _or_ text, which + * apparently some do. In that situation there's + * no need to alert the user that the following + * text is server- supplied, because, well, _what_ + * text? + * + * We also only do this if we got a stripctrl, + * because if we didn't, that suggests this is all + * being done via dialog boxes anyway. + */ + if (!s->ki_printed_header && s->ki_scc && + (s->num_prompts || name.len || inst.len)) { + ssh2_userauth_antispoof_msg( + s, "Keyboard-interactive authentication " + "prompts from server:"); + s->ki_printed_header = true; + seat_set_trust_status(s->ppl.seat, false); + } + + sb = strbuf_new(); + if (name.len) { + if (s->ki_scc) { + stripctrl_retarget(s->ki_scc, + BinarySink_UPCAST(sb)); + put_datapl(s->ki_scc, name); + stripctrl_retarget(s->ki_scc, NULL); + } else { + put_datapl(sb, name); + } + s->cur_prompt->name_reqd = true; + } else { + put_datapl(sb, PTRLEN_LITERAL( + "SSH server authentication")); + s->cur_prompt->name_reqd = false; + } + s->cur_prompt->name = strbuf_to_str(sb); + + sb = strbuf_new(); + if (inst.len) { + if (s->ki_scc) { + stripctrl_retarget(s->ki_scc, + BinarySink_UPCAST(sb)); + put_datapl(s->ki_scc, inst); + stripctrl_retarget(s->ki_scc, NULL); + } else { + put_datapl(sb, inst); + } + s->cur_prompt->instr_reqd = true; + } else { + s->cur_prompt->instr_reqd = false; + } + if (sb->len) + s->cur_prompt->instruction = strbuf_to_str(sb); + else + strbuf_free(sb); + + /* + * Our prompts_t is fully constructed now. Get the + * user's response(s). + */ + s->userpass_ret = seat_get_userpass_input( + s->ppl.seat, s->cur_prompt, NULL); + while (1) { + while (s->userpass_ret < 0 && + bufchain_size(s->ppl.user_input) > 0) + s->userpass_ret = seat_get_userpass_input( + s->ppl.seat, s->cur_prompt, s->ppl.user_input); + + if (s->userpass_ret >= 0) + break; + + s->want_user_input = true; + crReturnV; + s->want_user_input = false; + } + if (!s->userpass_ret) { + /* + * Failed to get responses. Terminate. + */ + free_prompts(s->cur_prompt); + ssh_bpp_queue_disconnect( + s->ppl.bpp, "Unable to authenticate", + SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER); + ssh_user_close(s->ppl.ssh, "User aborted during " + "keyboard-interactive authentication"); + return; + } + + /* + * Send the response(s) to the server. + */ + s->pktout = ssh_bpp_new_pktout( + s->ppl.bpp, SSH2_MSG_USERAUTH_INFO_RESPONSE); + put_uint32(s->pktout, s->num_prompts); + for (uint32_t i = 0; i < s->num_prompts; i++) { + put_stringz(s->pktout, prompt_get_result_ref( + s->cur_prompt->prompts[i])); + } + s->pktout->minlen = 256; + pq_push(s->ppl.out_pq, s->pktout); + + /* + * Free the prompts structure from this iteration. + * If there's another, a new one will be allocated + * when we return to the top of this while loop. + */ + free_prompts(s->cur_prompt); + + /* + * Get the next packet in case it's another + * INFO_REQUEST. + */ + crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL); + + } + + /* + * Print our trailer line, if we printed a header. + */ + if (s->ki_printed_header) { + seat_set_trust_status(s->ppl.seat, true); + ssh2_userauth_antispoof_msg( + s, "End of keyboard-interactive prompts from server"); + } + + /* + * We should have SUCCESS or FAILURE now. + */ + pq_push_front(s->ppl.in_pq, pktin); + + } else if (s->can_passwd) { + + /* + * Plain old password authentication. + */ + bool changereq_first_time; /* not live over crReturn */ + + s->ppl.bpp->pls->actx = SSH2_PKTCTX_PASSWORD; + + s->cur_prompt = new_prompts(); + s->cur_prompt->to_server = true; + s->cur_prompt->from_server = false; + s->cur_prompt->name = dupstr("SSH password"); + add_prompt(s->cur_prompt, dupprintf("%s@%s's password: ", + s->username, s->hostname), + false); + + s->userpass_ret = seat_get_userpass_input( + s->ppl.seat, s->cur_prompt, NULL); + while (1) { + while (s->userpass_ret < 0 && + bufchain_size(s->ppl.user_input) > 0) + s->userpass_ret = seat_get_userpass_input( + s->ppl.seat, s->cur_prompt, s->ppl.user_input); + + if (s->userpass_ret >= 0) + break; + + s->want_user_input = true; + crReturnV; + s->want_user_input = false; + } + if (!s->userpass_ret) { + /* + * Failed to get responses. Terminate. + */ + free_prompts(s->cur_prompt); + ssh_bpp_queue_disconnect( + s->ppl.bpp, "Unable to authenticate", + SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER); + ssh_user_close(s->ppl.ssh, "User aborted during password " + "authentication"); + return; + } + /* + * Squirrel away the password. (We may need it later if + * asked to change it.) + */ + s->password = prompt_get_result(s->cur_prompt->prompts[0]); + 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 = ssh_bpp_new_pktout( + s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST); + put_stringz(s->pktout, s->username); + put_stringz(s->pktout, s->successor_layer->vt->name); + put_stringz(s->pktout, "password"); + put_bool(s->pktout, false); + put_stringz(s->pktout, s->password); + s->pktout->minlen = 256; + pq_push(s->ppl.out_pq, s->pktout); + ppl_logevent("Sent password"); + s->type = AUTH_TYPE_PASSWORD; + + /* + * Wait for next packet, in case it's a password change + * request. + */ + crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL); + 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. + */ + + bool got_new = false; /* not live over crReturn */ + ptrlen prompt; /* not live over crReturn */ + + { + const char *msg; + if (changereq_first_time) + msg = "Server requested password change"; + else + msg = "Server rejected new password"; + ppl_logevent("%s", msg); + ppl_printf("%s\r\n", msg); + } + + prompt = get_string(pktin); + + s->cur_prompt = new_prompts(); + s->cur_prompt->to_server = true; + s->cur_prompt->from_server = false; + s->cur_prompt->name = dupstr("New SSH password"); + s->cur_prompt->instruction = mkstr(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); + add_prompt(s->cur_prompt, dupstr("Enter new password: "), + false); + add_prompt(s->cur_prompt, dupstr("Confirm new password: "), + false); + + /* + * Loop until the user manages to enter the same + * password twice. + */ + while (!got_new) { + s->userpass_ret = seat_get_userpass_input( + s->ppl.seat, s->cur_prompt, NULL); + while (1) { + while (s->userpass_ret < 0 && + bufchain_size(s->ppl.user_input) > 0) + s->userpass_ret = seat_get_userpass_input( + s->ppl.seat, s->cur_prompt, + s->ppl.user_input); + + if (s->userpass_ret >= 0) + break; + + s->want_user_input = true; + crReturnV; + s->want_user_input = false; + } + if (!s->userpass_ret) { + /* + * Failed to get responses. Terminate. + */ + /* burn the evidence */ + free_prompts(s->cur_prompt); + smemclr(s->password, strlen(s->password)); + sfree(s->password); + ssh_bpp_queue_disconnect( + s->ppl.bpp, "Unable to authenticate", + SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER); + ssh_user_close(s->ppl.ssh, "User aborted during " + "password changing"); + return; + } + + /* + * 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->s[0]) { + smemclr(s->password, strlen(s->password)); + /* burn the evidence */ + sfree(s->password); + s->password = prompt_get_result( + s->cur_prompt->prompts[0]); + } + + /* + * Check the two new passwords match. + */ + got_new = !strcmp( + prompt_get_result_ref(s->cur_prompt->prompts[1]), + prompt_get_result_ref(s->cur_prompt->prompts[2])); + if (!got_new) + /* They don't. Silly user. */ + ppl_printf("Passwords do not match\r\n"); + + } + + /* + * Send the new password (along with the old one). + * (see above for padding rationale) + */ + s->pktout = ssh_bpp_new_pktout( + s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST); + put_stringz(s->pktout, s->username); + put_stringz(s->pktout, s->successor_layer->vt->name); + put_stringz(s->pktout, "password"); + put_bool(s->pktout, true); + put_stringz(s->pktout, s->password); + put_stringz(s->pktout, prompt_get_result_ref( + s->cur_prompt->prompts[1])); + free_prompts(s->cur_prompt); + s->pktout->minlen = 256; + pq_push(s->ppl.out_pq, s->pktout); + ppl_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.) + */ + crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL); + 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. + */ + pq_push_front(s->ppl.in_pq, pktin); + + /* + * We don't need the old password any more, in any + * case. Burn the evidence. + */ + smemclr(s->password, strlen(s->password)); + sfree(s->password); + + } else { + ssh_bpp_queue_disconnect( + s->ppl.bpp, + "No supported authentication methods available", + SSH2_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE); + ssh_sw_abort(s->ppl.ssh, "No supported authentication methods " + "available (server sent: %s)", + s->last_methods_string->s); + return; + } + + } + try_new_username:; + } + + userauth_success: + /* + * We've just received USERAUTH_SUCCESS, and we haven't sent + * any packets since. Signal the transport layer to consider + * doing an immediate rekey, if it has any reason to want to. + */ + ssh2_transport_notify_auth_done(s->transport_layer); + + /* + * Finally, hand over to our successor layer, and return + * immediately without reaching the crFinishV: ssh_ppl_replace + * will have freed us, so crFinishV's zeroing-out of crState would + * be a use-after-free bug. + */ + { + PacketProtocolLayer *successor = s->successor_layer; + s->successor_layer = NULL; /* avoid freeing it ourself */ + ssh_ppl_replace(&s->ppl, successor); + return; /* we've just freed s, so avoid even touching s->crState */ + } + + crFinishV; +} + +static void ssh2_userauth_add_session_id( + struct ssh2_userauth_state *s, strbuf *sigdata) +{ + if (s->ppl.remote_bugs & BUG_SSH2_PK_SESSIONID) { + put_datapl(sigdata, s->session_id); + } else { + put_stringpl(sigdata, s->session_id); + } +} + +static void ssh2_userauth_agent_query( + struct ssh2_userauth_state *s, strbuf *req) +{ + void *response; + int response_len; + + sfree(s->agent_response_to_free); + s->agent_response_to_free = NULL; + + s->auth_agent_query = agent_query(req, &response, &response_len, + ssh2_userauth_agent_callback, s); + if (!s->auth_agent_query) + ssh2_userauth_agent_callback(s, response, response_len); +} + +static void ssh2_userauth_agent_callback(void *uav, void *reply, int replylen) +{ + struct ssh2_userauth_state *s = (struct ssh2_userauth_state *)uav; + + s->auth_agent_query = NULL; + s->agent_response_to_free = reply; + s->agent_response = make_ptrlen(reply, replylen); + + queue_idempotent_callback(&s->ppl.ic_process_queue); +} + +/* + * 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 just appends the sig blob unmodified as a string, except + * that it optionally breaks it open and fiddle with it to work around + * BUG_SSH2_RSA_PADDING. + */ +static void ssh2_userauth_add_sigblob( + struct ssh2_userauth_state *s, PktOut *pkt, ptrlen pkblob, ptrlen sigblob) +{ + BinarySource pk[1], sig[1]; + BinarySource_BARE_INIT_PL(pk, pkblob); + BinarySource_BARE_INIT_PL(sig, sigblob); + + /* 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 ((s->ppl.remote_bugs & BUG_SSH2_RSA_PADDING) && + ptrlen_eq_string(get_string(pk), "ssh-rsa") && + ptrlen_eq_string(get_string(sig), "ssh-rsa")) { + ptrlen mod_mp, sig_mp; + size_t sig_prefix_len; + + /* + * Find the modulus and signature integers. + */ + get_string(pk); /* skip over exponent */ + mod_mp = get_string(pk); /* remember modulus */ + sig_prefix_len = sig->pos; + sig_mp = get_string(sig); + if (get_err(pk) || get_err(sig)) + goto give_up; + + /* + * Find the byte length of the modulus, not counting leading + * zeroes. + */ + while (mod_mp.len > 0 && *(const char *)mod_mp.ptr == 0) { + mod_mp.len--; + mod_mp.ptr = (const char *)mod_mp.ptr + 1; + } + + /* debug("modulus length is %d\n", len); */ + /* debug("signature length is %d\n", siglen); */ + + if (mod_mp.len > sig_mp.len) { + strbuf *substr = strbuf_new(); + put_data(substr, sigblob.ptr, sig_prefix_len); + put_uint32(substr, mod_mp.len); + put_padding(substr, mod_mp.len - sig_mp.len, 0); + put_datapl(substr, sig_mp); + put_stringsb(pkt, substr); + return; + } + + /* Otherwise fall through and do it the easy way. We also come + * here as a fallback if we discover above that the key blob + * is misformatted in some way. */ + give_up:; + } + + put_stringpl(pkt, sigblob); +} + +#ifndef NO_GSSAPI +static PktOut *ssh2_userauth_gss_packet( + struct ssh2_userauth_state *s, const char *authtype) +{ + strbuf *sb; + PktOut *p; + Ssh_gss_buf buf; + Ssh_gss_buf mic; + + /* + * The mic is computed over the session id + intended + * USERAUTH_REQUEST packet. + */ + sb = strbuf_new(); + put_stringpl(sb, s->session_id); + put_byte(sb, SSH2_MSG_USERAUTH_REQUEST); + put_stringz(sb, s->username); + put_stringz(sb, s->successor_layer->vt->name); + put_stringz(sb, authtype); + + /* Compute the mic */ + buf.value = sb->s; + buf.length = sb->len; + s->shgss->lib->get_mic(s->shgss->lib, s->shgss->ctx, &buf, &mic); + strbuf_free(sb); + + /* Now we can build the real packet */ + if (strcmp(authtype, "gssapi-with-mic") == 0) { + p = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_USERAUTH_GSSAPI_MIC); + } else { + p = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST); + put_stringz(p, s->username); + put_stringz(p, s->successor_layer->vt->name); + put_stringz(p, authtype); + } + put_string(p, mic.value, mic.length); + + return p; +} +#endif + +static bool ssh2_userauth_get_specials( + PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx) +{ + /* No specials provided by this layer. */ + return false; +} + +static void ssh2_userauth_special_cmd(PacketProtocolLayer *ppl, + SessionSpecialCode code, int arg) +{ + /* No specials provided by this layer. */ +} + +static bool ssh2_userauth_want_user_input(PacketProtocolLayer *ppl) +{ + struct ssh2_userauth_state *s = + container_of(ppl, struct ssh2_userauth_state, ppl); + return s->want_user_input; +} + +static void ssh2_userauth_got_user_input(PacketProtocolLayer *ppl) +{ + struct ssh2_userauth_state *s = + container_of(ppl, struct ssh2_userauth_state, ppl); + if (s->want_user_input) + queue_idempotent_callback(&s->ppl.ic_process_queue); +} + +static void ssh2_userauth_reconfigure(PacketProtocolLayer *ppl, Conf *conf) +{ + struct ssh2_userauth_state *s = + container_of(ppl, struct ssh2_userauth_state, ppl); + ssh_ppl_reconfigure(s->successor_layer, conf); +} + +static void ssh2_userauth_antispoof_msg( + struct ssh2_userauth_state *s, const char *msg) +{ + strbuf *sb = strbuf_new(); + if (seat_set_trust_status(s->ppl.seat, true)) { + /* + * If the seat can directly indicate that this message is + * generated by the client, then we can just use the message + * unmodified as an unspoofable header. + */ + put_datapl(sb, ptrlen_from_asciz(msg)); + } else { + /* + * Otherwise, add enough padding around it that the server + * wouldn't be able to mimic it within our line-length + * constraint. + */ + strbuf_catf(sb, "-- %s ", msg); + while (sb->len < 78) + put_byte(sb, '-'); + } + put_datapl(sb, PTRLEN_LITERAL("\r\n")); + seat_stderr_pl(s->ppl.seat, ptrlen_from_strbuf(sb)); + strbuf_free(sb); +} diff --git a/ssh/userauth2-server.c b/ssh/userauth2-server.c new file mode 100644 index 00000000..da1e79c6 --- /dev/null +++ b/ssh/userauth2-server.c @@ -0,0 +1,377 @@ +/* + * Packet protocol layer for the server side of the SSH-2 userauth + * protocol (RFC 4252). + */ + +#include + +#include "putty.h" +#include "ssh.h" +#include "bpp.h" +#include "ppl.h" +#include "sshcr.h" +#include "server.h" + +#ifndef NO_GSSAPI +#include "gssc.h" +#include "gss.h" +#endif + +struct ssh2_userauth_server_state { + int crState; + + PacketProtocolLayer *transport_layer, *successor_layer; + ptrlen session_id; + + AuthPolicy *authpolicy; + const SshServerConfig *ssc; + + ptrlen username, service, method; + unsigned methods, this_method; + bool partial_success; + + AuthKbdInt *aki; + + PacketProtocolLayer ppl; +}; + +static void ssh2_userauth_server_free(PacketProtocolLayer *); +static void ssh2_userauth_server_process_queue(PacketProtocolLayer *); + +static const PacketProtocolLayerVtable ssh2_userauth_server_vtable = { + .free = ssh2_userauth_server_free, + .process_queue = ssh2_userauth_server_process_queue, + .queued_data_size = ssh_ppl_default_queued_data_size, + .name = "ssh-userauth", + /* other methods are NULL */ +}; + +static void free_auth_kbdint(AuthKbdInt *aki) +{ + int i; + + if (!aki) + return; + + sfree(aki->title); + sfree(aki->instruction); + for (i = 0; i < aki->nprompts; i++) + sfree(aki->prompts[i].prompt); + sfree(aki->prompts); + sfree(aki); +} + +PacketProtocolLayer *ssh2_userauth_server_new( + PacketProtocolLayer *successor_layer, AuthPolicy *authpolicy, + const SshServerConfig *ssc) +{ + struct ssh2_userauth_server_state *s = + snew(struct ssh2_userauth_server_state); + memset(s, 0, sizeof(*s)); + s->ppl.vt = &ssh2_userauth_server_vtable; + + s->successor_layer = successor_layer; + s->authpolicy = authpolicy; + s->ssc = ssc; + + return &s->ppl; +} + +void ssh2_userauth_server_set_transport_layer(PacketProtocolLayer *userauth, + PacketProtocolLayer *transport) +{ + struct ssh2_userauth_server_state *s = + container_of(userauth, struct ssh2_userauth_server_state, ppl); + s->transport_layer = transport; +} + +static void ssh2_userauth_server_free(PacketProtocolLayer *ppl) +{ + struct ssh2_userauth_server_state *s = + container_of(ppl, struct ssh2_userauth_server_state, ppl); + + if (s->successor_layer) + ssh_ppl_free(s->successor_layer); + + free_auth_kbdint(s->aki); + + sfree(s); +} + +static PktIn *ssh2_userauth_server_pop(struct ssh2_userauth_server_state *s) +{ + return pq_pop(s->ppl.in_pq); +} + +static void ssh2_userauth_server_add_session_id( + struct ssh2_userauth_server_state *s, strbuf *sigdata) +{ + if (s->ppl.remote_bugs & BUG_SSH2_PK_SESSIONID) { + put_datapl(sigdata, s->session_id); + } else { + put_stringpl(sigdata, s->session_id); + } +} + +static void ssh2_userauth_server_process_queue(PacketProtocolLayer *ppl) +{ + struct ssh2_userauth_server_state *s = + container_of(ppl, struct ssh2_userauth_server_state, ppl); + PktIn *pktin; + PktOut *pktout; + + crBegin(s->crState); + + s->session_id = ssh2_transport_get_session_id(s->transport_layer); + + if (s->ssc->banner.ptr) { + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_USERAUTH_BANNER); + put_stringpl(pktout, s->ssc->banner); + put_stringz(pktout, ""); /* language tag */ + pq_push(s->ppl.out_pq, pktout); + } + + while (1) { + crMaybeWaitUntilV((pktin = ssh2_userauth_server_pop(s)) != NULL); + if (pktin->type != SSH2_MSG_USERAUTH_REQUEST) { + ssh_proto_error(s->ppl.ssh, "Received unexpected packet when " + "expecting USERAUTH_REQUEST, type %d (%s)", + pktin->type, + ssh2_pkt_type(s->ppl.bpp->pls->kctx, + s->ppl.bpp->pls->actx, pktin->type)); + return; + } + + s->username = get_string(pktin); + s->service = get_string(pktin); + s->method = get_string(pktin); + + if (!ptrlen_eq_string(s->service, s->successor_layer->vt->name)) { + /* + * Unconditionally reject authentication for any service + * other than the one we're going to hand over to. + */ + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_USERAUTH_FAILURE); + put_stringz(pktout, ""); + put_bool(pktout, false); + pq_push(s->ppl.out_pq, pktout); + continue; + } + + s->methods = auth_methods(s->authpolicy); + s->partial_success = false; + + if (ptrlen_eq_string(s->method, "none")) { + s->this_method = AUTHMETHOD_NONE; + if (!(s->methods & s->this_method)) + goto failure; + + if (!auth_none(s->authpolicy, s->username)) + goto failure; + } else if (ptrlen_eq_string(s->method, "password")) { + bool changing; + ptrlen password, new_password, *new_password_ptr; + + s->this_method = AUTHMETHOD_PASSWORD; + if (!(s->methods & s->this_method)) + goto failure; + + changing = get_bool(pktin); + password = get_string(pktin); + + if (changing) { + new_password = get_string(pktin); + new_password_ptr = &new_password; + } else { + new_password_ptr = NULL; + } + + int result = auth_password(s->authpolicy, s->username, + password, new_password_ptr); + if (result == 2) { + pktout = ssh_bpp_new_pktout( + s->ppl.bpp, SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ); + put_stringz(pktout, "Please change your password"); + put_stringz(pktout, ""); /* language tag */ + pq_push(s->ppl.out_pq, pktout); + continue; /* skip USERAUTH_{SUCCESS,FAILURE} epilogue */ + } else if (result != 1) { + goto failure; + } + } else if (ptrlen_eq_string(s->method, "publickey")) { + bool has_signature, success, send_pk_ok, key_really_ok; + ptrlen algorithm, blob, signature; + const ssh_keyalg *keyalg; + ssh_key *key; + strbuf *sigdata; + + s->this_method = AUTHMETHOD_PUBLICKEY; + if (!(s->methods & s->this_method)) + goto failure; + + has_signature = get_bool(pktin); + algorithm = get_string(pktin); + blob = get_string(pktin); + + key_really_ok = auth_publickey(s->authpolicy, s->username, blob); + send_pk_ok = key_really_ok || + s->ssc->stunt_pretend_to_accept_any_pubkey; + + if (!has_signature) { + if (!send_pk_ok) + goto failure; + + pktout = ssh_bpp_new_pktout( + s->ppl.bpp, SSH2_MSG_USERAUTH_PK_OK); + put_stringpl(pktout, algorithm); + put_stringpl(pktout, blob); + pq_push(s->ppl.out_pq, pktout); + continue; /* skip USERAUTH_{SUCCESS,FAILURE} epilogue */ + } + + if (!key_really_ok) + goto failure; + + keyalg = find_pubkey_alg_len(algorithm); + if (!keyalg) + goto failure; + key = ssh_key_new_pub(keyalg, blob); + if (!key) + goto failure; + + sigdata = strbuf_new(); + ssh2_userauth_server_add_session_id(s, sigdata); + put_byte(sigdata, SSH2_MSG_USERAUTH_REQUEST); + put_stringpl(sigdata, s->username); + put_stringpl(sigdata, s->service); + put_stringpl(sigdata, s->method); + put_bool(sigdata, has_signature); + put_stringpl(sigdata, algorithm); + put_stringpl(sigdata, blob); + + signature = get_string(pktin); + success = ssh_key_verify(key, signature, + ptrlen_from_strbuf(sigdata)); + ssh_key_free(key); + strbuf_free(sigdata); + + if (!success) + goto failure; + } else if (ptrlen_eq_string(s->method, "keyboard-interactive")) { + int i, ok; + unsigned n; + + s->this_method = AUTHMETHOD_KBDINT; + if (!(s->methods & s->this_method)) + goto failure; + + do { + s->aki = auth_kbdint_prompts(s->authpolicy, s->username); + if (!s->aki) + goto failure; + + pktout = ssh_bpp_new_pktout( + s->ppl.bpp, SSH2_MSG_USERAUTH_INFO_REQUEST); + put_stringz(pktout, s->aki->title); + put_stringz(pktout, s->aki->instruction); + put_stringz(pktout, ""); /* language tag */ + put_uint32(pktout, s->aki->nprompts); + for (i = 0; i < s->aki->nprompts; i++) { + put_stringz(pktout, s->aki->prompts[i].prompt); + put_bool(pktout, s->aki->prompts[i].echo); + } + pq_push(s->ppl.out_pq, pktout); + + crMaybeWaitUntilV( + (pktin = ssh2_userauth_server_pop(s)) != NULL); + if (pktin->type != SSH2_MSG_USERAUTH_INFO_RESPONSE) { + ssh_proto_error( + s->ppl.ssh, "Received unexpected packet when " + "expecting USERAUTH_INFO_RESPONSE, type %d (%s)", + pktin->type, + ssh2_pkt_type(s->ppl.bpp->pls->kctx, + s->ppl.bpp->pls->actx, pktin->type)); + return; + } + + n = get_uint32(pktin); + if (n != s->aki->nprompts) { + ssh_proto_error( + s->ppl.ssh, "Received %u keyboard-interactive " + "responses after sending %u prompts", + n, s->aki->nprompts); + return; + } + + { + ptrlen *responses = snewn(s->aki->nprompts, ptrlen); + for (i = 0; i < s->aki->nprompts; i++) + responses[i] = get_string(pktin); + ok = auth_kbdint_responses(s->authpolicy, responses); + sfree(responses); + } + + free_auth_kbdint(s->aki); + s->aki = NULL; + } while (ok == 0); + + if (ok <= 0) + goto failure; + } else { + goto failure; + } + + /* + * If we get here, we've successfully completed this + * authentication step. + */ + if (auth_successful(s->authpolicy, s->username, s->this_method)) { + /* + * ... and it was the last one, so we're completely done. + */ + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_USERAUTH_SUCCESS); + pq_push(s->ppl.out_pq, pktout); + break; + } else { + /* + * ... but another is required, so fall through to + * generation of USERAUTH_FAILURE, having first refreshed + * the bit mask of available methods. + */ + s->methods = auth_methods(s->authpolicy); + } + s->partial_success = true; + + failure: + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_USERAUTH_FAILURE); + { + strbuf *list = strbuf_new(); + if (s->methods & AUTHMETHOD_NONE) + add_to_commasep(list, "none"); + if (s->methods & AUTHMETHOD_PASSWORD) + add_to_commasep(list, "password"); + if (s->methods & AUTHMETHOD_PUBLICKEY) + add_to_commasep(list, "publickey"); + if (s->methods & AUTHMETHOD_KBDINT) + add_to_commasep(list, "keyboard-interactive"); + put_stringsb(pktout, list); + } + put_bool(pktout, s->partial_success); + pq_push(s->ppl.out_pq, pktout); + } + + /* + * Finally, hand over to our successor layer, and return + * immediately without reaching the crFinishV: ssh_ppl_replace + * will have freed us, so crFinishV's zeroing-out of crState would + * be a use-after-free bug. + */ + { + PacketProtocolLayer *successor = s->successor_layer; + s->successor_layer = NULL; /* avoid freeing it ourself */ + ssh_ppl_replace(&s->ppl, successor); + return; /* we've just freed s, so avoid even touching s->crState */ + } + + crFinishV; +} diff --git a/ssh/verstring.c b/ssh/verstring.c new file mode 100644 index 00000000..2de2ac6c --- /dev/null +++ b/ssh/verstring.c @@ -0,0 +1,628 @@ +/* + * Code to handle the initial SSH version string exchange. + */ + +#include +#include +#include + +#include "putty.h" +#include "ssh.h" +#include "bpp.h" +#include "sshcr.h" + +#define PREFIX_MAXLEN 64 + +struct ssh_verstring_state { + int crState; + + Conf *conf; + ptrlen prefix_wanted; + char *our_protoversion; + struct ssh_version_receiver *receiver; + + bool send_early; + + bool found_prefix; + int major_protoversion; + int remote_bugs; + char prefix[PREFIX_MAXLEN]; + char *impl_name; + strbuf *vstring; + char *protoversion; + const char *softwareversion; + + char *our_vstring; + int i; + + BinaryPacketProtocol bpp; +}; + +static void ssh_verstring_free(BinaryPacketProtocol *bpp); +static void ssh_verstring_handle_input(BinaryPacketProtocol *bpp); +static void ssh_verstring_handle_output(BinaryPacketProtocol *bpp); +static PktOut *ssh_verstring_new_pktout(int type); +static void ssh_verstring_queue_disconnect(BinaryPacketProtocol *bpp, + const char *msg, int category); + +static const BinaryPacketProtocolVtable ssh_verstring_vtable = { + .free = ssh_verstring_free, + .handle_input = ssh_verstring_handle_input, + .handle_output = ssh_verstring_handle_output, + .new_pktout = ssh_verstring_new_pktout, + .queue_disconnect = ssh_verstring_queue_disconnect, + .packet_size_limit = 0xFFFFFFFF, /* no special limit for this bpp */ +}; + +static void ssh_detect_bugs(struct ssh_verstring_state *s); +static bool ssh_version_includes_v1(const char *ver); +static bool ssh_version_includes_v2(const char *ver); + +BinaryPacketProtocol *ssh_verstring_new( + Conf *conf, LogContext *logctx, bool bare_connection_mode, + const char *protoversion, struct ssh_version_receiver *rcv, + bool server_mode, const char *impl_name) +{ + struct ssh_verstring_state *s = snew(struct ssh_verstring_state); + + memset(s, 0, sizeof(struct ssh_verstring_state)); + + if (!bare_connection_mode) { + s->prefix_wanted = PTRLEN_LITERAL("SSH-"); + } else { + /* + * Ordinary SSH begins with the banner "SSH-x.y-...". Here, + * we're going to be speaking just the ssh-connection + * subprotocol, extracted and given a trivial binary packet + * protocol, so we need a new banner. + * + * The new banner is like the ordinary SSH banner, but + * replaces the prefix 'SSH-' at the start with a new name. In + * proper SSH style (though of course this part of the proper + * SSH protocol _isn't_ subject to this kind of + * DNS-domain-based extension), we define the new name in our + * extension space. + */ + s->prefix_wanted = PTRLEN_LITERAL( + "SSHCONNECTION@putty.projects.tartarus.org-"); + } + assert(s->prefix_wanted.len <= PREFIX_MAXLEN); + + s->conf = conf_copy(conf); + s->bpp.logctx = logctx; + s->our_protoversion = dupstr(protoversion); + s->receiver = rcv; + s->impl_name = dupstr(impl_name); + s->vstring = strbuf_new(); + + /* + * We send our version string early if we can. But if it includes + * SSH-1, we can't, because we have to take the other end into + * account too (see below). + * + * In server mode, we do send early. + */ + s->send_early = server_mode || !ssh_version_includes_v1(protoversion); + + s->bpp.vt = &ssh_verstring_vtable; + ssh_bpp_common_setup(&s->bpp); + return &s->bpp; +} + +void ssh_verstring_free(BinaryPacketProtocol *bpp) +{ + struct ssh_verstring_state *s = + container_of(bpp, struct ssh_verstring_state, bpp); + conf_free(s->conf); + sfree(s->impl_name); + strbuf_free(s->vstring); + sfree(s->protoversion); + sfree(s->our_vstring); + sfree(s->our_protoversion); + sfree(s); +} + +static int ssh_versioncmp(const char *a, const 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; +} + +static bool ssh_version_includes_v1(const char *ver) +{ + return ssh_versioncmp(ver, "2.0") < 0; +} + +static bool ssh_version_includes_v2(const char *ver) +{ + return ssh_versioncmp(ver, "1.99") >= 0; +} + +static void ssh_verstring_send(struct ssh_verstring_state *s) +{ + BinaryPacketProtocol *bpp = &s->bpp; /* for bpp_logevent */ + char *p; + int sv_pos; + + /* + * Construct our outgoing version string. + */ + s->our_vstring = dupprintf( + "%.*s%s-%s%s", + (int)s->prefix_wanted.len, (const char *)s->prefix_wanted.ptr, + s->our_protoversion, s->impl_name, sshver); + sv_pos = s->prefix_wanted.len + strlen(s->our_protoversion) + 1; + + /* Convert minus signs and spaces in the software version string + * into underscores. */ + for (p = s->our_vstring + sv_pos; *p; p++) { + if (*p == '-' || *p == ' ') + *p = '_'; + } + +#ifdef FUZZING + /* + * Replace the first character of the string with an "I" if we're + * compiling this code for fuzzing - i.e. the protocol prefix + * becomes "ISH-" instead of "SSH-". + * + * This is irrelevant to any real client software (the only thing + * reading the output of PuTTY built for fuzzing is the fuzzer, + * which can adapt to whatever it sees anyway). But it's a safety + * precaution making it difficult to accidentally run such a + * version of PuTTY (which would be hugely insecure) against a + * live peer implementation. + * + * (So the replacement prefix "ISH" notionally stands for + * 'Insecure Shell', of course.) + */ + s->our_vstring[0] = 'I'; +#endif + + /* + * Now send that version string, plus trailing \r\n or just \n + * (the latter in SSH-1 mode). + */ + bufchain_add(s->bpp.out_raw, s->our_vstring, strlen(s->our_vstring)); + if (ssh_version_includes_v2(s->our_protoversion)) + bufchain_add(s->bpp.out_raw, "\015", 1); + bufchain_add(s->bpp.out_raw, "\012", 1); + + bpp_logevent("We claim version: %s", s->our_vstring); +} + +#define BPP_WAITFOR(minlen) do \ + { \ + bool success; \ + crMaybeWaitUntilV( \ + (success = (bufchain_size(s->bpp.in_raw) >= (minlen))) || \ + s->bpp.input_eof); \ + if (!success) \ + goto eof; \ + } while (0) + +void ssh_verstring_handle_input(BinaryPacketProtocol *bpp) +{ + struct ssh_verstring_state *s = + container_of(bpp, struct ssh_verstring_state, bpp); + + crBegin(s->crState); + + /* + * If we're sending our version string up front before seeing the + * other side's, then do it now. + */ + if (s->send_early) + ssh_verstring_send(s); + + /* + * Search for a line beginning with the protocol name prefix in + * the input. + */ + s->i = 0; + while (1) { + /* + * Every time round this loop, we're at the start of a new + * line, so look for the prefix. + */ + BPP_WAITFOR(s->prefix_wanted.len); + bufchain_fetch(s->bpp.in_raw, s->prefix, s->prefix_wanted.len); + if (!memcmp(s->prefix, s->prefix_wanted.ptr, s->prefix_wanted.len)) { + bufchain_consume(s->bpp.in_raw, s->prefix_wanted.len); + ssh_check_frozen(s->bpp.ssh); + break; + } + + /* + * If we didn't find it, consume data until we see a newline. + */ + while (1) { + ptrlen data; + char *nl; + + /* Wait to receive at least 1 byte, but then consume more + * than that if it's there. */ + BPP_WAITFOR(1); + data = bufchain_prefix(s->bpp.in_raw); + if ((nl = memchr(data.ptr, '\012', data.len)) != NULL) { + bufchain_consume(s->bpp.in_raw, nl - (char *)data.ptr + 1); + ssh_check_frozen(s->bpp.ssh); + break; + } else { + bufchain_consume(s->bpp.in_raw, data.len); + ssh_check_frozen(s->bpp.ssh); + } + } + } + + s->found_prefix = true; + + /* + * Copy the greeting line so far into vstring. + */ + put_data(s->vstring, s->prefix_wanted.ptr, s->prefix_wanted.len); + + /* + * Now read the rest of the greeting line. + */ + s->i = 0; + do { + ptrlen data; + char *nl; + + BPP_WAITFOR(1); + data = bufchain_prefix(s->bpp.in_raw); + if ((nl = memchr(data.ptr, '\012', data.len)) != NULL) { + data.len = nl - (char *)data.ptr + 1; + } + + put_datapl(s->vstring, data); + bufchain_consume(s->bpp.in_raw, data.len); + ssh_check_frozen(s->bpp.ssh); + + } while (s->vstring->s[s->vstring->len-1] != '\012'); + + /* + * Trim \r and \n from the version string, and replace them with + * a NUL terminator. + */ + while (s->vstring->len > 0 && + (s->vstring->s[s->vstring->len-1] == '\r' || + s->vstring->s[s->vstring->len-1] == '\n')) + strbuf_shrink_by(s->vstring, 1); + + bpp_logevent("Remote version: %s", s->vstring->s); + + /* + * Pick out the protocol version and software version. The former + * goes in a separately allocated string, so that s->vstring + * remains intact for later use in key exchange; the latter is the + * tail of s->vstring, so it doesn't need to be allocated. + */ + { + const char *pv_start = s->vstring->s + s->prefix_wanted.len; + int pv_len = strcspn(pv_start, "-"); + s->protoversion = dupprintf("%.*s", pv_len, pv_start); + s->softwareversion = pv_start + pv_len; + if (*s->softwareversion) { + assert(*s->softwareversion == '-'); + s->softwareversion++; + } + } + + ssh_detect_bugs(s); + + /* + * Figure out what actual SSH protocol version we're speaking. + */ + if (ssh_version_includes_v2(s->our_protoversion) && + ssh_version_includes_v2(s->protoversion)) { + /* + * We're doing SSH-2. + */ + s->major_protoversion = 2; + } else if (ssh_version_includes_v1(s->our_protoversion) && + ssh_version_includes_v1(s->protoversion)) { + /* + * We're doing SSH-1. + */ + s->major_protoversion = 1; + + /* + * There are multiple minor versions of SSH-1, and the + * protocol does not specify that the minimum of client + * and server versions is used. So we must adjust our + * outgoing protocol version to be no higher than that of + * the other side. + */ + if (!s->send_early && + ssh_versioncmp(s->our_protoversion, s->protoversion) > 0) { + sfree(s->our_protoversion); + s->our_protoversion = dupstr(s->protoversion); + } + } else { + /* + * Unable to agree on a major protocol version at all. + */ + if (!ssh_version_includes_v2(s->our_protoversion)) { + ssh_sw_abort(s->bpp.ssh, + "SSH protocol version 1 required by our " + "configuration but not provided by remote"); + } else { + ssh_sw_abort(s->bpp.ssh, + "SSH protocol version 2 required by our " + "configuration but remote only provides " + "(old, insecure) SSH-1"); + } + crStopV; + } + + bpp_logevent("Using SSH protocol version %d", s->major_protoversion); + + if (!s->send_early) { + /* + * If we didn't send our version string early, construct and + * send it now, because now we know what it is. + */ + ssh_verstring_send(s); + } + + /* + * And we're done. Notify our receiver that we now know our + * protocol version. This will cause it to disconnect us from the + * input stream and ultimately free us, because our job is now + * done. + */ + s->receiver->got_ssh_version(s->receiver, s->major_protoversion); + return; + + eof: + ssh_remote_error(s->bpp.ssh, + "Remote side unexpectedly closed network connection"); + return; /* avoid touching s now it's been freed */ + + crFinishV; +} + +static PktOut *ssh_verstring_new_pktout(int type) +{ + unreachable("Should never try to send packets during SSH version " + "string exchange"); +} + +static void ssh_verstring_handle_output(BinaryPacketProtocol *bpp) +{ + if (pq_peek(&bpp->out_pq)) { + unreachable("Should never try to send packets during SSH version " + "string exchange"); + } +} + +/* + * Examine the remote side's version string, and compare it against a + * list of known buggy implementations. + */ +static void ssh_detect_bugs(struct ssh_verstring_state *s) +{ + BinaryPacketProtocol *bpp = &s->bpp; /* for bpp_logevent */ + const char *imp = s->softwareversion; + + s->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 (conf_get_int(s->conf, CONF_sshbug_ignore1) == FORCE_ON || + (conf_get_int(s->conf, CONF_sshbug_ignore1) == AUTO && + (!strcmp(imp, "1.2.18") || !strcmp(imp, "1.2.19") || + !strcmp(imp, "1.2.20") || !strcmp(imp, "1.2.21") || + !strcmp(imp, "1.2.22") || !strcmp(imp, "Cisco-1.25") || + !strcmp(imp, "OSU_1.4alpha3") || !strcmp(imp, "OSU_1.5alpha4")))) { + /* + * These versions don't support SSH1_MSG_IGNORE, so we have + * to use a different defence against password length + * sniffing. + */ + s->remote_bugs |= BUG_CHOKES_ON_SSH1_IGNORE; + bpp_logevent("We believe remote version has SSH-1 ignore bug"); + } + + if (conf_get_int(s->conf, CONF_sshbug_plainpw1) == FORCE_ON || + (conf_get_int(s->conf, CONF_sshbug_plainpw1) == AUTO && + (!strcmp(imp, "Cisco-1.25") || !strcmp(imp, "OSU_1.4alpha3")))) { + /* + * These versions need a plain password sent; they can't + * handle having a null and a random length of data after + * the password. + */ + s->remote_bugs |= BUG_NEEDS_SSH1_PLAIN_PASSWORD; + bpp_logevent("We believe remote version needs a " + "plain SSH-1 password"); + } + + if (conf_get_int(s->conf, CONF_sshbug_rsa1) == FORCE_ON || + (conf_get_int(s->conf, CONF_sshbug_rsa1) == AUTO && + (!strcmp(imp, "Cisco-1.25")))) { + /* + * These versions apparently have no clue whatever about + * RSA authentication and will panic and die if they see + * an AUTH_RSA message. + */ + s->remote_bugs |= BUG_CHOKES_ON_RSA; + bpp_logevent("We believe remote version can't handle SSH-1 " + "RSA authentication"); + } + + if (conf_get_int(s->conf, CONF_sshbug_hmac2) == FORCE_ON || + (conf_get_int(s->conf, CONF_sshbug_hmac2) == AUTO && + !wc_match("* VShell", imp) && + (wc_match("2.1.0*", imp) || wc_match("2.0.*", imp) || + wc_match("2.2.0*", imp) || wc_match("2.3.0*", imp) || + wc_match("2.1 *", imp)))) { + /* + * These versions have the HMAC bug. + */ + s->remote_bugs |= BUG_SSH2_HMAC; + bpp_logevent("We believe remote version has SSH-2 HMAC bug"); + } + + if (conf_get_int(s->conf, CONF_sshbug_derivekey2) == FORCE_ON || + (conf_get_int(s->conf, CONF_sshbug_derivekey2) == AUTO && + !wc_match("* VShell", imp) && + (wc_match("2.0.0*", imp) || wc_match("2.0.10*", imp) ))) { + /* + * These versions have the key-derivation bug (failing to + * include the literal shared secret in the hashes that + * generate the keys). + */ + s->remote_bugs |= BUG_SSH2_DERIVEKEY; + bpp_logevent("We believe remote version has SSH-2 " + "key-derivation bug"); + } + + if (conf_get_int(s->conf, CONF_sshbug_rsapad2) == FORCE_ON || + (conf_get_int(s->conf, CONF_sshbug_rsapad2) == AUTO && + (wc_match("OpenSSH_2.[5-9]*", imp) || + wc_match("OpenSSH_3.[0-2]*", imp) || + wc_match("mod_sftp/0.[0-8]*", imp) || + wc_match("mod_sftp/0.9.[0-8]", imp)))) { + /* + * These versions have the SSH-2 RSA padding bug. + */ + s->remote_bugs |= BUG_SSH2_RSA_PADDING; + bpp_logevent("We believe remote version has SSH-2 RSA padding bug"); + } + + if (conf_get_int(s->conf, CONF_sshbug_pksessid2) == FORCE_ON || + (conf_get_int(s->conf, CONF_sshbug_pksessid2) == AUTO && + wc_match("OpenSSH_2.[0-2]*", imp))) { + /* + * These versions have the SSH-2 session-ID bug in + * public-key authentication. + */ + s->remote_bugs |= BUG_SSH2_PK_SESSIONID; + bpp_logevent("We believe remote version has SSH-2 " + "public-key-session-ID bug"); + } + + if (conf_get_int(s->conf, CONF_sshbug_rekey2) == FORCE_ON || + (conf_get_int(s->conf, CONF_sshbug_rekey2) == AUTO && + (wc_match("DigiSSH_2.0", imp) || + wc_match("OpenSSH_2.[0-4]*", imp) || + wc_match("OpenSSH_2.5.[0-3]*", imp) || + wc_match("Sun_SSH_1.0", imp) || + wc_match("Sun_SSH_1.0.1", imp) || + /* 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. + */ + s->remote_bugs |= BUG_SSH2_REKEY; + bpp_logevent("We believe remote version has SSH-2 rekey bug"); + } + + if (conf_get_int(s->conf, CONF_sshbug_maxpkt2) == FORCE_ON || + (conf_get_int(s->conf, CONF_sshbug_maxpkt2) == AUTO && + (wc_match("1.36_sshlib GlobalSCAPE", imp) || + wc_match("1.36 sshlib: GlobalScape", imp)))) { + /* + * This version ignores our makpkt and needs to be throttled. + */ + s->remote_bugs |= BUG_SSH2_MAXPKT; + bpp_logevent("We believe remote version ignores SSH-2 " + "maximum packet size"); + } + + if (conf_get_int(s->conf, CONF_sshbug_ignore2) == FORCE_ON) { + /* + * Servers that don't support SSH2_MSG_IGNORE. Currently, + * none detected automatically. + */ + s->remote_bugs |= BUG_CHOKES_ON_SSH2_IGNORE; + bpp_logevent("We believe remote version has SSH-2 ignore bug"); + } + + if (conf_get_int(s->conf, CONF_sshbug_oldgex2) == FORCE_ON || + (conf_get_int(s->conf, CONF_sshbug_oldgex2) == AUTO && + (wc_match("OpenSSH_2.[235]*", imp)))) { + /* + * These versions only support the original (pre-RFC4419) + * SSH-2 GEX request, and disconnect with a protocol error if + * we use the newer version. + */ + s->remote_bugs |= BUG_SSH2_OLDGEX; + bpp_logevent("We believe remote version has outdated SSH-2 GEX"); + } + + if (conf_get_int(s->conf, CONF_sshbug_winadj) == FORCE_ON) { + /* + * Servers that don't support our winadj request for one + * reason or another. Currently, none detected automatically. + */ + s->remote_bugs |= BUG_CHOKES_ON_WINADJ; + bpp_logevent("We believe remote version has winadj bug"); + } + + if (conf_get_int(s->conf, CONF_sshbug_chanreq) == FORCE_ON || + (conf_get_int(s->conf, CONF_sshbug_chanreq) == AUTO && + (wc_match("OpenSSH_[2-5].*", imp) || + wc_match("OpenSSH_6.[0-6]*", imp) || + wc_match("dropbear_0.[2-4][0-9]*", imp) || + wc_match("dropbear_0.5[01]*", imp)))) { + /* + * These versions have the SSH-2 channel request bug. + * OpenSSH 6.7 and above do not: + * https://bugzilla.mindrot.org/show_bug.cgi?id=1818 + * dropbear_0.52 and above do not: + * https://secure.ucc.asn.au/hg/dropbear/rev/cd02449b709c + */ + s->remote_bugs |= BUG_SENDS_LATE_REQUEST_REPLY; + bpp_logevent("We believe remote version has SSH-2 " + "channel request bug"); + } +} + +const char *ssh_verstring_get_remote(BinaryPacketProtocol *bpp) +{ + struct ssh_verstring_state *s = + container_of(bpp, struct ssh_verstring_state, bpp); + return s->vstring->s; +} + +const char *ssh_verstring_get_local(BinaryPacketProtocol *bpp) +{ + struct ssh_verstring_state *s = + container_of(bpp, struct ssh_verstring_state, bpp); + return s->our_vstring; +} + +int ssh_verstring_get_bugs(BinaryPacketProtocol *bpp) +{ + struct ssh_verstring_state *s = + container_of(bpp, struct ssh_verstring_state, bpp); + return s->remote_bugs; +} + +static void ssh_verstring_queue_disconnect(BinaryPacketProtocol *bpp, + const char *msg, int category) +{ + /* No way to send disconnect messages at this stage of the protocol! */ +} diff --git a/ssh/x11fwd.c b/ssh/x11fwd.c new file mode 100644 index 00000000..49dca95a --- /dev/null +++ b/ssh/x11fwd.c @@ -0,0 +1,639 @@ +/* + * Platform-independent bits of X11 forwarding. + */ + +#include +#include +#include +#include + +#include "putty.h" +#include "ssh.h" +#include "channel.h" +#include "tree234.h" + +struct XDMSeen { + unsigned int time; + unsigned char clientid[6]; +}; + +typedef struct X11Connection { + unsigned char firstpkt[12]; /* first X data packet */ + tree234 *authtree; + struct X11Display *disp; + char *auth_protocol; + unsigned char *auth_data; + int data_read, auth_plen, auth_psize, auth_dlen, auth_dsize; + bool verified; + bool input_wanted; + bool no_data_sent_to_x_client; + char *peer_addr; + int peer_port; + SshChannel *c; /* channel structure held by SSH backend */ + Socket *s; + + Plug plug; + Channel chan; +} X11Connection; + +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)); +} + +struct X11FakeAuth *x11_invent_fake_auth(tree234 *authtree, int authtype) +{ + struct X11FakeAuth *auth = snew(struct X11FakeAuth); + int i; + + /* + * This function has the job of inventing a set of X11 fake auth + * data, and adding it to 'authtree'. We must preserve the + * property that for any given actual authorisation attempt, _at + * most one_ thing in the tree can possibly match it. + * + * For MIT-MAGIC-COOKIE-1, that's not too difficult: the match + * criterion is simply that the entire cookie is correct, so we + * just have to make sure we don't make up two cookies the same. + * (Vanishingly unlikely, but we check anyway to be sure, and go + * round again inventing a new cookie if add234 tells us the one + * we thought of is already in use.) + * + * For XDM-AUTHORIZATION-1, it's a little more fiddly. The setup + * with XA1 is that half the cookie is used as a DES key with + * which to CBC-encrypt an assortment of stuff. Happily, the stuff + * encrypted _begins_ with the other half of the cookie, and the + * IV is always zero, which means that any valid XA1 authorisation + * attempt for a given cookie must begin with the same cipher + * block, consisting of the DES ECB encryption of the first half + * of the cookie using the second half as a key. So we compute + * that cipher block here and now, and use it as the sorting key + * for distinguishing XA1 entries in the tree. + */ + + if (authtype == X11_MIT) { + auth->proto = X11_MIT; + + /* MIT-MAGIC-COOKIE-1. Cookie size is 128 bits (16 bytes). */ + auth->datalen = 16; + auth->data = snewn(auth->datalen, unsigned char); + auth->xa1_firstblock = NULL; + + while (1) { + random_read(auth->data, auth->datalen); + if (add234(authtree, auth) == auth) + break; + } + + auth->xdmseen = NULL; + } else { + assert(authtype == X11_XDM); + auth->proto = X11_XDM; + + /* XDM-AUTHORIZATION-1. Cookie size is 16 bytes; byte 8 is zero. */ + auth->datalen = 16; + auth->data = snewn(auth->datalen, unsigned char); + auth->xa1_firstblock = snewn(8, unsigned char); + memset(auth->xa1_firstblock, 0, 8); + + while (1) { + random_read(auth->data, 15); + auth->data[15] = auth->data[8]; + auth->data[8] = 0; + + memcpy(auth->xa1_firstblock, auth->data, 8); + des_encrypt_xdmauth(auth->data + 9, auth->xa1_firstblock, 8); + if (add234(authtree, auth) == auth) + break; + } + + auth->xdmseen = newtree234(xdmseen_cmp); + } + auth->protoname = dupstr(x11_authnames[auth->proto]); + auth->datastring = snewn(auth->datalen * 2 + 1, char); + for (i = 0; i < auth->datalen; i++) + sprintf(auth->datastring + i*2, "%02x", + auth->data[i]); + + auth->disp = NULL; + auth->share_cs = NULL; + auth->share_chan = NULL; + + return auth; +} + +void x11_free_fake_auth(struct X11FakeAuth *auth) +{ + if (auth->data) + smemclr(auth->data, auth->datalen); + sfree(auth->data); + sfree(auth->protoname); + sfree(auth->datastring); + sfree(auth->xa1_firstblock); + if (auth->xdmseen != NULL) { + struct XDMSeen *seen; + while ((seen = delpos234(auth->xdmseen, 0)) != NULL) + sfree(seen); + freetree234(auth->xdmseen); + } + sfree(auth); +} + +int x11_authcmp(void *av, void *bv) +{ + struct X11FakeAuth *a = (struct X11FakeAuth *)av; + struct X11FakeAuth *b = (struct X11FakeAuth *)bv; + + if (a->proto < b->proto) + return -1; + else if (a->proto > b->proto) + return +1; + + if (a->proto == X11_MIT) { + if (a->datalen < b->datalen) + return -1; + else if (a->datalen > b->datalen) + return +1; + + return memcmp(a->data, b->data, a->datalen); + } else { + assert(a->proto == X11_XDM); + + return memcmp(a->xa1_firstblock, b->xa1_firstblock, 8); + } +} + +#define XDM_MAXSKEW 20*60 /* 20 minute clock skew should be OK */ + +static const char *x11_verify(unsigned long peer_ip, int peer_port, + tree234 *authtree, char *proto, + unsigned char *data, int dlen, + struct X11FakeAuth **auth_ret) +{ + struct X11FakeAuth match_dummy; /* for passing to find234 */ + struct X11FakeAuth *auth; + + /* + * First, do a lookup in our tree to find the only authorisation + * record that _might_ match. + */ + if (!strcmp(proto, x11_authnames[X11_MIT])) { + /* + * Just look up the whole cookie that was presented to us, + * which x11_authcmp will compare against the cookies we + * currently believe in. + */ + match_dummy.proto = X11_MIT; + match_dummy.datalen = dlen; + match_dummy.data = data; + } else if (!strcmp(proto, x11_authnames[X11_XDM])) { + /* + * Look up the first cipher block, against the stored first + * cipher blocks for the XDM-AUTHORIZATION-1 cookies we + * currently know. (See comment in x11_invent_fake_auth.) + */ + match_dummy.proto = X11_XDM; + match_dummy.xa1_firstblock = data; + } else { + return "Unsupported authorisation protocol"; + } + + if ((auth = find234(authtree, &match_dummy, 0)) == NULL) + return "Authorisation not recognised"; + + /* + * If we're using MIT-MAGIC-COOKIE-1, that was all we needed. If + * we're doing XDM-AUTHORIZATION-1, though, we have to check the + * rest of the auth data. + */ + if (auth->proto == X11_XDM) { + unsigned long t; + time_t tim; + int i; + struct XDMSeen *seen, *ret; + + if (dlen != 24) + return "XDM-AUTHORIZATION-1 data was wrong length"; + if (peer_port == -1) + return "cannot do XDM-AUTHORIZATION-1 without remote address data"; + des_decrypt_xdmauth(auth->data+9, data, 24); + if (memcmp(auth->data, data, 8) != 0) + return "XDM-AUTHORIZATION-1 data failed check"; /* cookie wrong */ + if (GET_32BIT_MSB_FIRST(data+8) != peer_ip) + return "XDM-AUTHORIZATION-1 data failed check"; /* IP wrong */ + if ((int)GET_16BIT_MSB_FIRST(data+12) != peer_port) + return "XDM-AUTHORIZATION-1 data failed check"; /* port wrong */ + 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 (((unsigned long)t - (unsigned long)tim + + XDM_MAXSKEW) > 2*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(auth->xdmseen != NULL); + ret = add234(auth->xdmseen, seen); + if (ret != seen) { + sfree(seen); + return "XDM-AUTHORIZATION-1 data replayed"; + } + /* While we're here, purge entries too old to be replayed. */ + for (;;) { + seen = index234(auth->xdmseen, 0); + assert(seen != NULL); + if (t - seen->time <= XDM_MAXSKEW) + break; + sfree(delpos234(auth->xdmseen, 0)); + } + } + /* implement other protocols here if ever required */ + + *auth_ret = auth; + return NULL; +} + +static void x11_log(Plug *p, PlugLogType type, SockAddr *addr, int port, + const char *error_msg, int error_code) +{ + /* We have no interface to the logging module here, so we drop these. */ +} + +static void x11_send_init_error(struct X11Connection *conn, + const char *err_message); + +static void x11_closing(Plug *plug, const char *error_msg, int error_code, + bool calling_back) +{ + struct X11Connection *xconn = container_of( + plug, struct X11Connection, plug); + + if (error_msg) { + /* + * Socket error. If we're still at the connection setup stage, + * construct an X11 error packet passing on the problem. + */ + if (xconn->no_data_sent_to_x_client) { + char *err_message = dupprintf("unable to connect to forwarded " + "X server: %s", error_msg); + x11_send_init_error(xconn, err_message); + sfree(err_message); + } + + /* + * Whether we did that or not, now we slam the connection + * shut. + */ + sshfwd_initiate_close(xconn->c, error_msg); + } else { + /* + * Ordinary EOF received on socket. Send an EOF on the SSH + * channel. + */ + if (xconn->c) + sshfwd_write_eof(xconn->c); + } +} + +static void x11_receive(Plug *plug, int urgent, const char *data, size_t len) +{ + struct X11Connection *xconn = container_of( + plug, struct X11Connection, plug); + + xconn->no_data_sent_to_x_client = false; + sshfwd_write(xconn->c, data, len); +} + +static void x11_sent(Plug *plug, size_t bufsize) +{ + struct X11Connection *xconn = container_of( + plug, struct X11Connection, plug); + + sshfwd_unthrottle(xconn->c, bufsize); +} + +static const PlugVtable X11Connection_plugvt = { + .log = x11_log, + .closing = x11_closing, + .receive = x11_receive, + .sent = x11_sent, +}; + +static void x11_chan_free(Channel *chan); +static size_t x11_send( + Channel *chan, bool is_stderr, const void *vdata, size_t len); +static void x11_send_eof(Channel *chan); +static void x11_set_input_wanted(Channel *chan, bool wanted); +static char *x11_log_close_msg(Channel *chan); + +static const ChannelVtable X11Connection_channelvt = { + .free = x11_chan_free, + .open_confirmation = chan_remotely_opened_confirmation, + .open_failed = chan_remotely_opened_failure, + .send = x11_send, + .send_eof = x11_send_eof, + .set_input_wanted = x11_set_input_wanted, + .log_close_msg = x11_log_close_msg, + .want_close = chan_default_want_close, + .rcvd_exit_status = chan_no_exit_status, + .rcvd_exit_signal = chan_no_exit_signal, + .rcvd_exit_signal_numeric = chan_no_exit_signal_numeric, + .run_shell = chan_no_run_shell, + .run_command = chan_no_run_command, + .run_subsystem = chan_no_run_subsystem, + .enable_x11_forwarding = chan_no_enable_x11_forwarding, + .enable_agent_forwarding = chan_no_enable_agent_forwarding, + .allocate_pty = chan_no_allocate_pty, + .set_env = chan_no_set_env, + .send_break = chan_no_send_break, + .send_signal = chan_no_send_signal, + .change_window_size = chan_no_change_window_size, + .request_response = chan_no_request_response, +}; + +/* + * Called to set up the X11Connection structure, though this does not + * yet connect to an actual server. + */ +Channel *x11_new_channel(tree234 *authtree, SshChannel *c, + const char *peeraddr, int peerport, + bool connection_sharing_possible) +{ + struct X11Connection *xconn; + + /* + * Open socket. + */ + xconn = snew(struct X11Connection); + xconn->plug.vt = &X11Connection_plugvt; + xconn->chan.vt = &X11Connection_channelvt; + xconn->chan.initial_fixed_window_size = + (connection_sharing_possible ? 128 : 0); + xconn->auth_protocol = NULL; + xconn->authtree = authtree; + xconn->verified = false; + xconn->data_read = 0; + xconn->input_wanted = true; + xconn->no_data_sent_to_x_client = true; + xconn->c = c; + + /* + * We don't actually open a local socket to the X server just yet, + * because we don't know which one it is. Instead, we'll wait + * until we see the incoming authentication data, which may tell + * us what display to connect to, or whether we have to divert + * this X forwarding channel to a connection-sharing downstream + * rather than handling it ourself. + */ + xconn->disp = NULL; + xconn->s = NULL; + + /* + * Stash the peer address we were given in its original text form. + */ + xconn->peer_addr = peeraddr ? dupstr(peeraddr) : NULL; + xconn->peer_port = peerport; + + return &xconn->chan; +} + +static void x11_chan_free(Channel *chan) +{ + assert(chan->vt == &X11Connection_channelvt); + X11Connection *xconn = container_of(chan, X11Connection, chan); + + if (xconn->auth_protocol) { + sfree(xconn->auth_protocol); + sfree(xconn->auth_data); + } + + if (xconn->s) + sk_close(xconn->s); + + sfree(xconn->peer_addr); + sfree(xconn); +} + +static void x11_set_input_wanted(Channel *chan, bool wanted) +{ + assert(chan->vt == &X11Connection_channelvt); + X11Connection *xconn = container_of(chan, X11Connection, chan); + + xconn->input_wanted = wanted; + if (xconn->s) + sk_set_frozen(xconn->s, !xconn->input_wanted); +} + +static void x11_send_init_error(struct X11Connection *xconn, + const char *err_message) +{ + char *full_message; + int msglen, msgsize; + unsigned char *reply; + + full_message = dupprintf("%s X11 proxy: %s\n", appname, err_message); + + msglen = strlen(full_message); + reply = snewn(8 + msglen+1 + 4, unsigned char); /* include zero */ + msgsize = (msglen + 3) & ~3; + reply[0] = 0; /* failure */ + reply[1] = msglen; /* length of reason string */ + memcpy(reply + 2, xconn->firstpkt + 2, 4); /* major/minor proto vsn */ + PUT_16BIT_X11(xconn->firstpkt[0], reply + 6, msgsize >> 2);/* data len */ + memset(reply + 8, 0, msgsize); + memcpy(reply + 8, full_message, msglen); + sshfwd_write(xconn->c, reply, 8 + msgsize); + sshfwd_write_eof(xconn->c); + xconn->no_data_sent_to_x_client = false; + sfree(reply); + sfree(full_message); +} + +/* + * Called to send data down the raw connection. + */ +static size_t x11_send( + Channel *chan, bool is_stderr, const void *vdata, size_t len) +{ + assert(chan->vt == &X11Connection_channelvt); + X11Connection *xconn = container_of(chan, X11Connection, chan); + const char *data = (const char *)vdata; + + /* + * Read the first packet. + */ + while (len > 0 && xconn->data_read < 12) + xconn->firstpkt[xconn->data_read++] = (unsigned char) (len--, *data++); + if (xconn->data_read < 12) + return 0; + + /* + * If we have not allocated the auth_protocol and auth_data + * strings, do so now. + */ + if (!xconn->auth_protocol) { + char endian = xconn->firstpkt[0]; + xconn->auth_plen = GET_16BIT_X11(endian, xconn->firstpkt + 6); + xconn->auth_dlen = GET_16BIT_X11(endian, xconn->firstpkt + 8); + xconn->auth_psize = (xconn->auth_plen + 3) & ~3; + xconn->auth_dsize = (xconn->auth_dlen + 3) & ~3; + /* Leave room for a terminating zero, to make our lives easier. */ + xconn->auth_protocol = snewn(xconn->auth_psize + 1, char); + xconn->auth_data = snewn(xconn->auth_dsize, unsigned char); + } + + /* + * Read the auth_protocol and auth_data strings. + */ + while (len > 0 && + xconn->data_read < 12 + xconn->auth_psize) + xconn->auth_protocol[xconn->data_read++ - 12] = (len--, *data++); + while (len > 0 && + xconn->data_read < 12 + xconn->auth_psize + xconn->auth_dsize) + xconn->auth_data[xconn->data_read++ - 12 - + xconn->auth_psize] = (unsigned char) (len--, *data++); + if (xconn->data_read < 12 + xconn->auth_psize + xconn->auth_dsize) + return 0; + + /* + * If we haven't verified the authorisation, do so now. + */ + if (!xconn->verified) { + const char *err; + struct X11FakeAuth *auth_matched = NULL; + unsigned long peer_ip; + int peer_port; + int protomajor, protominor; + void *greeting; + int greeting_len; + unsigned char *socketdata; + int socketdatalen; + char new_peer_addr[32]; + int new_peer_port; + char endian = xconn->firstpkt[0]; + + protomajor = GET_16BIT_X11(endian, xconn->firstpkt + 2); + protominor = GET_16BIT_X11(endian, xconn->firstpkt + 4); + + assert(!xconn->s); + + xconn->auth_protocol[xconn->auth_plen] = '\0'; /* ASCIZ */ + + peer_ip = 0; /* placate optimiser */ + if (x11_parse_ip(xconn->peer_addr, &peer_ip)) + peer_port = xconn->peer_port; + else + peer_port = -1; /* signal no peer address data available */ + + err = x11_verify(peer_ip, peer_port, + xconn->authtree, xconn->auth_protocol, + xconn->auth_data, xconn->auth_dlen, &auth_matched); + if (err) { + x11_send_init_error(xconn, err); + return 0; + } + assert(auth_matched); + + /* + * If this auth points to a connection-sharing downstream + * rather than an X display we know how to connect to + * directly, pass it off to the sharing module now. (This will + * have the side effect of freeing xconn.) + */ + if (auth_matched->share_cs) { + sshfwd_x11_sharing_handover(xconn->c, auth_matched->share_cs, + auth_matched->share_chan, + xconn->peer_addr, xconn->peer_port, + xconn->firstpkt[0], + protomajor, protominor, data, len); + return 0; + } + + /* + * Now we know we're going to accept the connection, and what + * X display to connect to. Actually connect to it. + */ + xconn->chan.initial_fixed_window_size = 0; + sshfwd_window_override_removed(xconn->c); + xconn->disp = auth_matched->disp; + xconn->s = new_connection(sk_addr_dup(xconn->disp->addr), + xconn->disp->realhost, xconn->disp->port, + false, true, false, false, &xconn->plug, + sshfwd_get_conf(xconn->c)); + if ((err = sk_socket_error(xconn->s)) != NULL) { + char *err_message = dupprintf("unable to connect to" + " forwarded X server: %s", err); + x11_send_init_error(xconn, err_message); + sfree(err_message); + return 0; + } + + /* + * Write a new connection header containing our replacement + * auth data. + */ + socketdatalen = 0; /* placate compiler warning */ + socketdata = sk_getxdmdata(xconn->s, &socketdatalen); + if (socketdata && socketdatalen==6) { + sprintf(new_peer_addr, "%d.%d.%d.%d", socketdata[0], + socketdata[1], socketdata[2], socketdata[3]); + new_peer_port = GET_16BIT_MSB_FIRST(socketdata + 4); + } else { + strcpy(new_peer_addr, "0.0.0.0"); + new_peer_port = 0; + } + + greeting = x11_make_greeting(xconn->firstpkt[0], + protomajor, protominor, + xconn->disp->localauthproto, + xconn->disp->localauthdata, + xconn->disp->localauthdatalen, + new_peer_addr, new_peer_port, + &greeting_len); + + sk_write(xconn->s, greeting, greeting_len); + + smemclr(greeting, greeting_len); + sfree(greeting); + + /* + * Now we're done. + */ + xconn->verified = true; + } + + /* + * After initialisation, just copy data simply. + */ + + return sk_write(xconn->s, data, len); +} + +static void x11_send_eof(Channel *chan) +{ + assert(chan->vt == &X11Connection_channelvt); + X11Connection *xconn = container_of(chan, X11Connection, chan); + + if (xconn->s) { + sk_write_eof(xconn->s); + } else { + /* + * If EOF is received from the X client before we've got to + * the point of actually connecting to an X server, then we + * should send an EOF back to the client so that the + * forwarded channel will be terminated. + */ + if (xconn->c) + sshfwd_write_eof(xconn->c); + } +} + +static char *x11_log_close_msg(Channel *chan) +{ + return dupstr("Forwarded X11 connection terminated"); +} diff --git a/ssh/zlib.c b/ssh/zlib.c new file mode 100644 index 00000000..9ad04ed2 --- /dev/null +++ b/ssh/zlib.c @@ -0,0 +1,1253 @@ +/* + * 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 +#include +#include + +#include "defs.h" +#include "ssh.h" + +/* ---------------------------------------------------------------------- + * 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, + const unsigned char *data, int len); + +/* + * 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(const 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, + const unsigned char *data, int len) +{ + struct LZ77InternalContext *st = ctx->ictx; + int i, distance, off, nmatch, matchlen, advance; + struct Match defermatch, matches[MAXMATCH]; + int deferchr; + + assert(st->npending <= HASHCHARS); + + /* + * Add any pending characters from last time to the window. (We + * might not be able to.) + * + * This leaves st->pending empty in the usual case (when len >= + * HASHCHARS); otherwise it leaves st->pending empty enough that + * adding all the remaining 'len' characters will not push it past + * HASHCHARS in size. + */ + 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) { + + if (len >= HASHCHARS) { + /* + * Hash the next few characters. + */ + int 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; + } + + 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 { + assert(st->npending < HASHCHARS); + 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 { + strbuf *outbuf; + unsigned long outbits; + int noutbits; + bool firstblock; +}; + +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) { + put_byte(out->outbuf, 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 (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; + + 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 = lenof(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 = lenof(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); + } +} + +struct ssh_zlib_compressor { + struct LZ77Context ectx; + ssh_compressor sc; +}; + +ssh_compressor *zlib_compress_init(void) +{ + struct Outbuf *out; + struct ssh_zlib_compressor *comp = snew(struct ssh_zlib_compressor); + + lz77_init(&comp->ectx); + comp->sc.vt = &ssh_zlib; + comp->ectx.literal = zlib_literal; + comp->ectx.match = zlib_match; + + out = snew(struct Outbuf); + out->outbuf = NULL; + out->outbits = out->noutbits = 0; + out->firstblock = true; + comp->ectx.userdata = out; + + return &comp->sc; +} + +void zlib_compress_cleanup(ssh_compressor *sc) +{ + struct ssh_zlib_compressor *comp = + container_of(sc, struct ssh_zlib_compressor, sc); + struct Outbuf *out = (struct Outbuf *)comp->ectx.userdata; + if (out->outbuf) + strbuf_free(out->outbuf); + sfree(out); + sfree(comp->ectx.ictx); + sfree(comp); +} + +void zlib_compress_block(ssh_compressor *sc, + const unsigned char *block, int len, + unsigned char **outblock, int *outlen, + int minlen) +{ + struct ssh_zlib_compressor *comp = + container_of(sc, struct ssh_zlib_compressor, sc); + struct Outbuf *out = (struct Outbuf *) comp->ectx.userdata; + bool in_block; + + assert(!out->outbuf); + out->outbuf = strbuf_new_nm(); + + /* + * 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 = false; + + in_block = false; + } else + in_block = true; + + 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(&comp->ectx, block, len); + + /* + * 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 */ + + /* + * If we've been asked to pad out the compressed data until it's + * at least a given length, do so by emitting further empty static + * blocks. + */ + while (out->outbuf->len < minlen) { + outbits(out, 0, 7); /* close block */ + outbits(out, 2, 3); /* open new static block */ + } + + *outlen = out->outbuf->len; + *outblock = (unsigned char *)strbuf_to_str(out->outbuf); + out->outbuf = NULL; +} + +/* ---------------------------------------------------------------------- + * 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((size_t)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]; + + /* + * Array that accumulates the code lengths sent in the header of a + * dynamic-Huffman-tree block. + * + * There are 286 actual symbols in the literal/length alphabet + * (256 literals plus 20 length categories), and 30 symbols in the + * distance alphabet. However, the block header transmits the + * number of code lengths for the former alphabet as a 5-bit value + * HLIT to be added to 257, and the latter as a 5-bit value HDIST + * to be added to 1. This means that the number of _code lengths_ + * can go as high as 288 for the symbol alphabet and 32 for the + * distance alphabet - each of those values being 2 more than the + * maximum number of actual symbols. + * + * It's tempting to rule that sending out-of-range HLIT or HDIST + * is therefore just illegal, and to fault it when we initially + * receive that header. But instead I've chosen to permit the + * Huffman-code definition to include code length entries for + * those unused symbols; if a header of that form is transmitted, + * then the effect will be that in the main body of the block, + * some bit sequence(s) will generate an illegal symbol number, + * and _that_ will be faulted as a decoding error. + * + * Rationale: this can already happen! The standard Huffman code + * used in a _static_ block for the literal/length alphabet is + * defined in such a way that it includes codes for symbols 287 + * and 288, which are then never actually sent in the body of the + * block. And I think that if the standard static tree definition + * is willing to include Huffman codes that don't correspond to a + * symbol, then it's an excessive restriction on dynamic tables + * not to permit them to do the same. In particular, it would be + * strange for a dynamic block not to be able to exactly mimic + * either or both of the Huffman codes used by a static block for + * the corresponding alphabet. + * + * So we place no constraint on HLIT or HDIST during code + * construction, and we make this array large enough to include + * the maximum number of code lengths that can possibly arise as a + * result. It's only trying to _use_ the junk Huffman codes after + * table construction is completed that will provoke a decode + * error. + */ + unsigned char lengths[288 + 32]; + + unsigned long bits; + int nbits; + unsigned char window[WINSIZE]; + int winpos; + strbuf *outblk; + + ssh_decompressor dc; +}; + +ssh_decompressor *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; + dctx->outblk = NULL; + + dctx->dc.vt = &ssh_zlib; + return &dctx->dc; +} + +void zlib_decompress_cleanup(ssh_decompressor *dc) +{ + struct zlib_decompress_ctx *dctx = + container_of(dc, struct zlib_decompress_ctx, dc); + + 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); + if (dctx->outblk) + strbuf_free(dctx->outblk); + 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); + put_byte(dctx->outblk, c); +} + +#define EATBITS(n) ( dctx->nbits -= (n), dctx->bits >>= (n) ) + +bool zlib_decompress_block(ssh_decompressor *dc, + const unsigned char *block, int len, + unsigned char **outblock, int *outlen) +{ + struct zlib_decompress_ctx *dctx = + container_of(dc, struct zlib_decompress_ctx, dc); + 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 + }; + + assert(!dctx->outblk); + dctx->outblk = strbuf_new_nm(); + + 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) { + dctx->state = GOTLENSYM; + dctx->sym = code; + } else { + /* literal/length symbols 286 and 287 are invalid */ + goto decode_error; + } + 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; + if (code >= 30) /* dist symbols 30 and 31 are invalid */ + 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: + *outlen = dctx->outblk->len; + *outblock = (unsigned char *)strbuf_to_str(dctx->outblk); + dctx->outblk = NULL; + return true; + + decode_error: + *outblock = NULL; + *outlen = 0; + return false; +} + +const ssh_compression_alg ssh_zlib = { + .name = "zlib", + .delayed_name = "zlib@openssh.com", /* delayed version */ + .compress_new = zlib_compress_init, + .compress_free = zlib_compress_cleanup, + .compress = zlib_compress_block, + .decompress_new = zlib_decompress_init, + .decompress_free = zlib_decompress_cleanup, + .decompress = zlib_decompress_block, + .text_name = "zlib (RFC1950)", +}; diff --git a/ssh1bpp.c b/ssh1bpp.c deleted file mode 100644 index 46eccbeb..00000000 --- a/ssh1bpp.c +++ /dev/null @@ -1,387 +0,0 @@ -/* - * Binary packet protocol for SSH-1. - */ - -#include - -#include "putty.h" -#include "ssh.h" -#include "sshbpp.h" -#include "sshcr.h" - -struct ssh1_bpp_state { - int crState; - long len, pad, biglen, length, maxlen; - unsigned char *data; - uint32_t realcrc, gotcrc; - int chunk; - PktIn *pktin; - - ssh_cipher *cipher_in, *cipher_out; - - struct crcda_ctx *crcda_ctx; - uint8_t iv[8]; /* for crcda */ - - bool pending_compression_request; - ssh_compressor *compctx; - ssh_decompressor *decompctx; - - BinaryPacketProtocol bpp; -}; - -static void ssh1_bpp_free(BinaryPacketProtocol *bpp); -static void ssh1_bpp_handle_input(BinaryPacketProtocol *bpp); -static void ssh1_bpp_handle_output(BinaryPacketProtocol *bpp); -static void ssh1_bpp_queue_disconnect(BinaryPacketProtocol *bpp, - const char *msg, int category); -static PktOut *ssh1_bpp_new_pktout(int type); - -static const BinaryPacketProtocolVtable ssh1_bpp_vtable = { - .free = ssh1_bpp_free, - .handle_input = ssh1_bpp_handle_input, - .handle_output = ssh1_bpp_handle_output, - .new_pktout = ssh1_bpp_new_pktout, - .queue_disconnect = ssh1_bpp_queue_disconnect, - .packet_size_limit = 0xFFFFFFFF, /* no special limit for this bpp */ -}; - -BinaryPacketProtocol *ssh1_bpp_new(LogContext *logctx) -{ - struct ssh1_bpp_state *s = snew(struct ssh1_bpp_state); - memset(s, 0, sizeof(*s)); - s->bpp.vt = &ssh1_bpp_vtable; - s->bpp.logctx = logctx; - ssh_bpp_common_setup(&s->bpp); - return &s->bpp; -} - -static void ssh1_bpp_free(BinaryPacketProtocol *bpp) -{ - struct ssh1_bpp_state *s = container_of(bpp, struct ssh1_bpp_state, bpp); - if (s->cipher_in) - ssh_cipher_free(s->cipher_in); - if (s->cipher_out) - ssh_cipher_free(s->cipher_out); - if (s->compctx) - ssh_compressor_free(s->compctx); - if (s->decompctx) - ssh_decompressor_free(s->decompctx); - if (s->crcda_ctx) - crcda_free_context(s->crcda_ctx); - sfree(s->pktin); - sfree(s); -} - -void ssh1_bpp_new_cipher(BinaryPacketProtocol *bpp, - const ssh_cipheralg *cipher, - const void *session_key) -{ - struct ssh1_bpp_state *s; - assert(bpp->vt == &ssh1_bpp_vtable); - s = container_of(bpp, struct ssh1_bpp_state, bpp); - - assert(!s->cipher_in); - assert(!s->cipher_out); - - if (cipher) { - s->cipher_in = ssh_cipher_new(cipher); - s->cipher_out = ssh_cipher_new(cipher); - ssh_cipher_setkey(s->cipher_in, session_key); - ssh_cipher_setkey(s->cipher_out, session_key); - - assert(!s->crcda_ctx); - s->crcda_ctx = crcda_make_context(); - - bpp_logevent("Initialised %s encryption", cipher->text_name); - - memset(s->iv, 0, sizeof(s->iv)); - - assert(cipher->blksize <= sizeof(s->iv)); - ssh_cipher_setiv(s->cipher_in, s->iv); - ssh_cipher_setiv(s->cipher_out, s->iv); - } -} - -void ssh1_bpp_start_compression(BinaryPacketProtocol *bpp) -{ - struct ssh1_bpp_state *s; - assert(bpp->vt == &ssh1_bpp_vtable); - s = container_of(bpp, struct ssh1_bpp_state, bpp); - - assert(!s->compctx); - assert(!s->decompctx); - - s->compctx = ssh_compressor_new(&ssh_zlib); - s->decompctx = ssh_decompressor_new(&ssh_zlib); - - bpp_logevent("Started zlib (RFC1950) compression"); -} - -#define BPP_READ(ptr, len) do \ - { \ - bool success; \ - crMaybeWaitUntilV((success = bufchain_try_fetch_consume( \ - s->bpp.in_raw, ptr, len)) || \ - s->bpp.input_eof); \ - if (!success) \ - goto eof; \ - ssh_check_frozen(s->bpp.ssh); \ - } while (0) - -static void ssh1_bpp_handle_input(BinaryPacketProtocol *bpp) -{ - struct ssh1_bpp_state *s = container_of(bpp, struct ssh1_bpp_state, bpp); - - crBegin(s->crState); - - while (1) { - s->maxlen = 0; - s->length = 0; - - { - unsigned char lenbuf[4]; - BPP_READ(lenbuf, 4); - s->len = toint(GET_32BIT_MSB_FIRST(lenbuf)); - } - - if (s->len < 5 || s->len > 262144) { /* SSH1.5-mandated max size */ - ssh_sw_abort(s->bpp.ssh, - "Out-of-range packet length from remote suggests" - " data stream corruption"); - crStopV; - } - - s->pad = 8 - (s->len % 8); - s->biglen = s->len + s->pad; - s->length = s->len - 5; - - /* - * Allocate the packet to return, now we know its length. - */ - s->pktin = snew_plus(PktIn, s->biglen); - s->pktin->qnode.prev = s->pktin->qnode.next = NULL; - s->pktin->qnode.on_free_queue = false; - s->pktin->type = 0; - - s->maxlen = s->biglen; - s->data = snew_plus_get_aux(s->pktin); - - BPP_READ(s->data, s->biglen); - - if (s->cipher_in && detect_attack(s->crcda_ctx, - s->data, s->biglen, s->iv)) { - ssh_sw_abort(s->bpp.ssh, - "Network attack (CRC compensation) detected!"); - crStopV; - } - /* Save the last cipher block, to be passed to the next call - * to detect_attack */ - assert(s->biglen >= 8); - memcpy(s->iv, s->data + s->biglen - 8, sizeof(s->iv)); - - if (s->cipher_in) - ssh_cipher_decrypt(s->cipher_in, s->data, s->biglen); - - s->realcrc = crc32_ssh1(make_ptrlen(s->data, s->biglen - 4)); - s->gotcrc = GET_32BIT_MSB_FIRST(s->data + s->biglen - 4); - if (s->gotcrc != s->realcrc) { - ssh_sw_abort(s->bpp.ssh, "Incorrect CRC received on packet"); - crStopV; - } - - if (s->decompctx) { - unsigned char *decompblk; - int decomplen; - if (!ssh_decompressor_decompress( - s->decompctx, s->data + s->pad, s->length + 1, - &decompblk, &decomplen)) { - ssh_sw_abort(s->bpp.ssh, - "Zlib decompression encountered invalid data"); - crStopV; - } - - if (s->maxlen < s->pad + decomplen) { - PktIn *old_pktin = s->pktin; - - s->maxlen = s->pad + decomplen; - s->pktin = snew_plus(PktIn, s->maxlen); - *s->pktin = *old_pktin; /* structure copy */ - s->data = snew_plus_get_aux(s->pktin); - - smemclr(old_pktin, s->biglen); - sfree(old_pktin); - } - - memcpy(s->data + s->pad, decompblk, decomplen); - sfree(decompblk); - s->length = decomplen - 1; - } - - /* - * Now we can find the bounds of the semantic content of the - * packet, and the initial type byte. - */ - s->data += s->pad; - s->pktin->type = *s->data++; - BinarySource_INIT(s->pktin, s->data, s->length); - - if (s->bpp.logctx) { - logblank_t blanks[MAX_BLANKS]; - int nblanks = ssh1_censor_packet( - s->bpp.pls, s->pktin->type, false, - make_ptrlen(s->data, s->length), blanks); - log_packet(s->bpp.logctx, PKT_INCOMING, s->pktin->type, - ssh1_pkt_type(s->pktin->type), - get_ptr(s->pktin), get_avail(s->pktin), nblanks, blanks, - NULL, 0, NULL); - } - - s->pktin->qnode.formal_size = get_avail(s->pktin); - pq_push(&s->bpp.in_pq, s->pktin); - - { - int type = s->pktin->type; - s->pktin = NULL; - - switch (type) { - case SSH1_SMSG_SUCCESS: - case SSH1_SMSG_FAILURE: - if (s->pending_compression_request) { - /* - * This is the response to - * SSH1_CMSG_REQUEST_COMPRESSION. - */ - if (type == SSH1_SMSG_SUCCESS) { - /* - * If the response was positive, start - * compression. - */ - ssh1_bpp_start_compression(&s->bpp); - } - - /* - * Either way, cancel the pending flag, and - * schedule a run of our output side in case we - * had any packets queued up in the meantime. - */ - s->pending_compression_request = false; - queue_idempotent_callback(&s->bpp.ic_out_pq); - } - break; - } - } - } - - eof: - if (!s->bpp.expect_close) { - ssh_remote_error(s->bpp.ssh, - "Remote side unexpectedly closed network connection"); - } else { - ssh_remote_eof(s->bpp.ssh, "Remote side closed network connection"); - } - return; /* avoid touching s now it's been freed */ - - crFinishV; -} - -static PktOut *ssh1_bpp_new_pktout(int pkt_type) -{ - PktOut *pkt = ssh_new_packet(); - pkt->length = 4 + 8; /* space for length + max padding */ - put_byte(pkt, pkt_type); - pkt->prefix = pkt->length; - pkt->type = pkt_type; - pkt->downstream_id = 0; - pkt->additional_log_text = NULL; - return pkt; -} - -static void ssh1_bpp_format_packet(struct ssh1_bpp_state *s, PktOut *pkt) -{ - int pad, biglen, pktoffs; - uint32_t crc; - int len; - - if (s->bpp.logctx) { - ptrlen pktdata = make_ptrlen(pkt->data + pkt->prefix, - pkt->length - pkt->prefix); - logblank_t blanks[MAX_BLANKS]; - int nblanks = ssh1_censor_packet( - s->bpp.pls, pkt->type, true, pktdata, blanks); - log_packet(s->bpp.logctx, PKT_OUTGOING, pkt->type, - ssh1_pkt_type(pkt->type), - pktdata.ptr, pktdata.len, nblanks, blanks, - NULL, 0, NULL); - } - - if (s->compctx) { - unsigned char *compblk; - int complen; - ssh_compressor_compress(s->compctx, pkt->data + 12, pkt->length - 12, - &compblk, &complen, 0); - /* Replace the uncompressed packet data with the compressed - * version. */ - pkt->length = 12; - put_data(pkt, compblk, complen); - sfree(compblk); - } - - put_uint32(pkt, 0); /* space for CRC */ - len = pkt->length - 4 - 8; /* len(type+data+CRC) */ - pad = 8 - (len % 8); - pktoffs = 8 - pad; - biglen = len + pad; /* len(padding+type+data+CRC) */ - - random_read(pkt->data + pktoffs, 4+8 - pktoffs); - crc = crc32_ssh1( - make_ptrlen(pkt->data + pktoffs + 4, biglen - 4)); /* all ex len */ - PUT_32BIT_MSB_FIRST(pkt->data + pktoffs + 4 + biglen - 4, crc); - PUT_32BIT_MSB_FIRST(pkt->data + pktoffs, len); - - if (s->cipher_out) - ssh_cipher_encrypt(s->cipher_out, pkt->data + pktoffs + 4, biglen); - - bufchain_add(s->bpp.out_raw, pkt->data + pktoffs, - biglen + 4); /* len(length+padding+type+data+CRC) */ -} - -static void ssh1_bpp_handle_output(BinaryPacketProtocol *bpp) -{ - struct ssh1_bpp_state *s = container_of(bpp, struct ssh1_bpp_state, bpp); - PktOut *pkt; - - if (s->pending_compression_request) { - /* - * Don't send any output packets while we're awaiting a - * response to SSH1_CMSG_REQUEST_COMPRESSION, because if they - * cross over in transit with the responding SSH1_CMSG_SUCCESS - * then the other end could decode them with the wrong - * compression settings. - */ - return; - } - - while ((pkt = pq_pop(&s->bpp.out_pq)) != NULL) { - int type = pkt->type; - ssh1_bpp_format_packet(s, pkt); - ssh_free_pktout(pkt); - - if (type == SSH1_CMSG_REQUEST_COMPRESSION) { - /* - * When we see the actual compression request go past, set - * the pending flag, and stop processing packets this - * time. - */ - s->pending_compression_request = true; - break; - } - } -} - -static void ssh1_bpp_queue_disconnect(BinaryPacketProtocol *bpp, - const char *msg, int category) -{ - PktOut *pkt = ssh_bpp_new_pktout(bpp, SSH1_MSG_DISCONNECT); - put_stringz(pkt, msg); - pq_push(&bpp->out_pq, pkt); -} diff --git a/ssh1censor.c b/ssh1censor.c deleted file mode 100644 index 8dacd3a0..00000000 --- a/ssh1censor.c +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Packet-censoring code for SSH-1, used to identify sensitive fields - * like passwords so that the logging system can avoid writing them - * into log files. - */ - -#include - -#include "putty.h" -#include "ssh.h" - -int ssh1_censor_packet( - const PacketLogSettings *pls, int type, bool sender_is_client, - ptrlen pkt, logblank_t *blanks) -{ - int nblanks = 0; - ptrlen str; - BinarySource src[1]; - - BinarySource_BARE_INIT_PL(src, pkt); - - if (pls->omit_data && - (type == SSH1_SMSG_STDOUT_DATA || - type == SSH1_SMSG_STDERR_DATA || - type == SSH1_CMSG_STDIN_DATA || - type == SSH1_MSG_CHANNEL_DATA)) { - /* "Session data" packets - omit the data string. */ - if (type == SSH1_MSG_CHANNEL_DATA) - get_uint32(src); /* skip channel id */ - str = get_string(src); - if (!get_err(src)) { - assert(nblanks < MAX_BLANKS); - blanks[nblanks].offset = src->pos - str.len; - blanks[nblanks].type = PKTLOG_OMIT; - blanks[nblanks].len = str.len; - nblanks++; - } - } - - if (sender_is_client && pls->omit_passwords) { - if (type == SSH1_CMSG_AUTH_PASSWORD || - type == SSH1_CMSG_AUTH_TIS_RESPONSE || - type == SSH1_CMSG_AUTH_CCARD_RESPONSE) { - /* If this is a password or similar packet, blank the - * password(s). */ - assert(nblanks < MAX_BLANKS); - blanks[nblanks].offset = 0; - blanks[nblanks].len = pkt.len; - blanks[nblanks].type = PKTLOG_BLANK; - nblanks++; - } else if (type == SSH1_CMSG_X11_REQUEST_FORWARDING) { - /* - * If this is an X forwarding request packet, blank the - * fake auth data. - * - * Note that while we blank the X authentication data - * here, we don't take any special action to blank the - * start of an X11 channel, so using MIT-MAGIC-COOKIE-1 - * and actually opening an X connection without having - * session blanking enabled is likely to leak your cookie - * into the log. - */ - get_string(src); /* skip protocol name */ - str = get_string(src); - if (!get_err(src)) { - assert(nblanks < MAX_BLANKS); - blanks[nblanks].offset = src->pos - str.len; - blanks[nblanks].type = PKTLOG_BLANK; - blanks[nblanks].len = str.len; - nblanks++; - } - } - } - - return nblanks; -} diff --git a/ssh1connection-client.c b/ssh1connection-client.c deleted file mode 100644 index cf7dd04e..00000000 --- a/ssh1connection-client.c +++ /dev/null @@ -1,547 +0,0 @@ -/* - * Client-specific parts of the SSH-1 connection layer. - */ - -#include - -#include "putty.h" -#include "ssh.h" -#include "sshbpp.h" -#include "sshppl.h" -#include "sshchan.h" -#include "sshcr.h" -#include "ssh1connection.h" - -void ssh1_connection_direction_specific_setup( - struct ssh1_connection_state *s) -{ - if (!s->mainchan) { - /* - * Start up the main session, by telling mainchan.c to do it - * all just as it would in SSH-2, and translating those - * concepts to SSH-1's non-channel-shaped idea of the main - * session. - */ - s->mainchan = mainchan_new( - &s->ppl, &s->cl, s->conf, s->term_width, s->term_height, - false /* is_simple */, NULL); - } -} - -typedef void (*sf_handler_fn_t)(struct ssh1_connection_state *s, - bool success, void *ctx); - -struct outstanding_succfail { - sf_handler_fn_t handler; - void *ctx; - struct outstanding_succfail *next; - - /* - * The 'trivial' flag is set if this handler is in response to a - * request for which the SSH-1 protocol doesn't actually specify a - * response packet. The client of this system (mainchan.c) will - * expect to get an acknowledgment regardless, so we arrange to - * send that ack immediately after the rest of the queue empties. - */ - bool trivial; -}; - -static void ssh1_connection_process_trivial_succfails(void *vs); - -static void ssh1_queue_succfail_handler( - struct ssh1_connection_state *s, sf_handler_fn_t handler, void *ctx, - bool trivial) -{ - struct outstanding_succfail *osf = snew(struct outstanding_succfail); - osf->handler = handler; - osf->ctx = ctx; - osf->trivial = trivial; - osf->next = NULL; - if (s->succfail_tail) - s->succfail_tail->next = osf; - else - s->succfail_head = osf; - s->succfail_tail = osf; - - /* In case this one was trivial and the queue was already empty, - * we should make sure we run the handler promptly, and the - * easiest way is to queue it anyway and then run a trivials pass - * by callback. */ - queue_toplevel_callback(ssh1_connection_process_trivial_succfails, s); -} - -static void ssh1_connection_process_succfail( - struct ssh1_connection_state *s, bool success) -{ - struct outstanding_succfail *prevhead = s->succfail_head; - s->succfail_head = s->succfail_head->next; - if (!s->succfail_head) - s->succfail_tail = NULL; - prevhead->handler(s, success, prevhead->ctx); - sfree(prevhead); -} - -static void ssh1_connection_process_trivial_succfails(void *vs) -{ - struct ssh1_connection_state *s = (struct ssh1_connection_state *)vs; - while (s->succfail_head && s->succfail_head->trivial) - ssh1_connection_process_succfail(s, true); -} - -bool ssh1_handle_direction_specific_packet( - struct ssh1_connection_state *s, PktIn *pktin) -{ - PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ - - PktOut *pktout; - struct ssh1_channel *c; - unsigned remid; - struct ssh_rportfwd pf, *pfp; - ptrlen host, data; - int port; - - switch (pktin->type) { - case SSH1_SMSG_SUCCESS: - case SSH1_SMSG_FAILURE: - if (!s->succfail_head) { - ssh_remote_error(s->ppl.ssh, - "Received %s with no outstanding request", - ssh1_pkt_type(pktin->type)); - return true; - } - - ssh1_connection_process_succfail( - s, pktin->type == SSH1_SMSG_SUCCESS); - queue_toplevel_callback( - ssh1_connection_process_trivial_succfails, s); - - return true; - - case SSH1_SMSG_X11_OPEN: - remid = get_uint32(pktin); - - /* Refuse if X11 forwarding is disabled. */ - if (!s->X11_fwd_enabled) { - pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH1_MSG_CHANNEL_OPEN_FAILURE); - put_uint32(pktout, remid); - pq_push(s->ppl.out_pq, pktout); - ppl_logevent("Rejected X11 connect request"); - } else { - c = snew(struct ssh1_channel); - c->connlayer = s; - ssh1_channel_init(c); - c->remoteid = remid; - c->chan = x11_new_channel(s->x11authtree, &c->sc, - NULL, -1, false); - c->remoteid = remid; - c->halfopen = false; - - pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION); - put_uint32(pktout, c->remoteid); - put_uint32(pktout, c->localid); - pq_push(s->ppl.out_pq, pktout); - ppl_logevent("Opened X11 forward channel"); - } - - return true; - - case SSH1_SMSG_AGENT_OPEN: - remid = get_uint32(pktin); - - /* Refuse if agent forwarding is disabled. */ - if (!ssh_agent_forwarding_permitted(&s->cl)) { - pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH1_MSG_CHANNEL_OPEN_FAILURE); - put_uint32(pktout, remid); - pq_push(s->ppl.out_pq, pktout); - } else { - c = snew(struct ssh1_channel); - c->connlayer = s; - ssh1_channel_init(c); - c->remoteid = remid; - c->halfopen = false; - - /* - * If possible, make a stream-oriented connection to the - * agent and set up an ordinary port-forwarding type - * channel over it. - */ - Plug *plug; - Channel *ch = portfwd_raw_new(&s->cl, &plug, true); - Socket *skt = agent_connect(plug); - if (!sk_socket_error(skt)) { - portfwd_raw_setup(ch, skt, &c->sc); - c->chan = ch; - } else { - portfwd_raw_free(ch); - - /* - * Otherwise, fall back to the old-fashioned system of - * parsing the forwarded data stream ourselves for - * message boundaries, and passing each individual - * message to the one-off agent_query(). - */ - c->chan = agentf_new(&c->sc); - } - - pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION); - put_uint32(pktout, c->remoteid); - put_uint32(pktout, c->localid); - pq_push(s->ppl.out_pq, pktout); - } - - return true; - - case SSH1_MSG_PORT_OPEN: - remid = get_uint32(pktin); - host = get_string(pktin); - port = toint(get_uint32(pktin)); - - pf.dhost = mkstr(host); - pf.dport = port; - pfp = find234(s->rportfwds, &pf, NULL); - - if (!pfp) { - ppl_logevent("Rejected remote port open request for %s:%d", - pf.dhost, port); - pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH1_MSG_CHANNEL_OPEN_FAILURE); - put_uint32(pktout, remid); - pq_push(s->ppl.out_pq, pktout); - } else { - char *err; - - c = snew(struct ssh1_channel); - c->connlayer = s; - ppl_logevent("Received remote port open request for %s:%d", - pf.dhost, port); - err = portfwdmgr_connect( - s->portfwdmgr, &c->chan, pf.dhost, port, - &c->sc, pfp->addressfamily); - - if (err) { - ppl_logevent("Port open failed: %s", err); - sfree(err); - ssh1_channel_free(c); - pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH1_MSG_CHANNEL_OPEN_FAILURE); - put_uint32(pktout, remid); - pq_push(s->ppl.out_pq, pktout); - } else { - ssh1_channel_init(c); - c->remoteid = remid; - c->halfopen = false; - pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION); - put_uint32(pktout, c->remoteid); - put_uint32(pktout, c->localid); - pq_push(s->ppl.out_pq, pktout); - ppl_logevent("Forwarded port opened successfully"); - } - } - - sfree(pf.dhost); - - return true; - - case SSH1_SMSG_STDOUT_DATA: - case SSH1_SMSG_STDERR_DATA: - data = get_string(pktin); - if (!get_err(pktin)) { - int bufsize = seat_output( - s->ppl.seat, pktin->type == SSH1_SMSG_STDERR_DATA, - data.ptr, data.len); - if (!s->stdout_throttling && bufsize > SSH1_BUFFER_LIMIT) { - s->stdout_throttling = true; - ssh_throttle_conn(s->ppl.ssh, +1); - } - } - - return true; - - case SSH1_SMSG_EXIT_STATUS: { - int exitcode = get_uint32(pktin); - ppl_logevent("Server sent command exit status %d", exitcode); - ssh_got_exitcode(s->ppl.ssh, exitcode); - - s->session_terminated = true; - return true; - } - - default: - return false; - } -} - -static void ssh1mainchan_succfail_wantreply(struct ssh1_connection_state *s, - bool success, void *ctx) -{ - chan_request_response(s->mainchan_chan, success); -} - -static void ssh1mainchan_succfail_nowantreply(struct ssh1_connection_state *s, - bool success, void *ctx) -{ -} - -static void ssh1mainchan_queue_response(struct ssh1_connection_state *s, - bool want_reply, bool trivial) -{ - sf_handler_fn_t handler = (want_reply ? ssh1mainchan_succfail_wantreply : - ssh1mainchan_succfail_nowantreply); - ssh1_queue_succfail_handler(s, handler, NULL, trivial); -} - -static void ssh1mainchan_request_x11_forwarding( - SshChannel *sc, bool want_reply, const char *authproto, - const char *authdata, int screen_number, bool oneshot) -{ - struct ssh1_connection_state *s = - container_of(sc, struct ssh1_connection_state, mainchan_sc); - PktOut *pktout; - - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_X11_REQUEST_FORWARDING); - put_stringz(pktout, authproto); - put_stringz(pktout, authdata); - if (s->local_protoflags & SSH1_PROTOFLAG_SCREEN_NUMBER) - put_uint32(pktout, screen_number); - pq_push(s->ppl.out_pq, pktout); - - ssh1mainchan_queue_response(s, want_reply, false); -} - -static void ssh1mainchan_request_agent_forwarding( - SshChannel *sc, bool want_reply) -{ - struct ssh1_connection_state *s = - container_of(sc, struct ssh1_connection_state, mainchan_sc); - PktOut *pktout; - - pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH1_CMSG_AGENT_REQUEST_FORWARDING); - pq_push(s->ppl.out_pq, pktout); - - ssh1mainchan_queue_response(s, want_reply, false); -} - -static void ssh1mainchan_request_pty( - SshChannel *sc, bool want_reply, Conf *conf, int w, int h) -{ - struct ssh1_connection_state *s = - container_of(sc, struct ssh1_connection_state, mainchan_sc); - PktOut *pktout; - - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_REQUEST_PTY); - put_stringz(pktout, conf_get_str(s->conf, CONF_termtype)); - put_uint32(pktout, h); - put_uint32(pktout, w); - put_uint32(pktout, 0); /* width in pixels */ - put_uint32(pktout, 0); /* height in pixels */ - write_ttymodes_to_packet( - BinarySink_UPCAST(pktout), 1, - get_ttymodes_from_conf(s->ppl.seat, conf)); - pq_push(s->ppl.out_pq, pktout); - - ssh1mainchan_queue_response(s, want_reply, false); -} - -static bool ssh1mainchan_send_env_var( - SshChannel *sc, bool want_reply, const char *var, const char *value) -{ - return false; /* SSH-1 doesn't support this at all */ -} - -static void ssh1mainchan_start_shell(SshChannel *sc, bool want_reply) -{ - struct ssh1_connection_state *s = - container_of(sc, struct ssh1_connection_state, mainchan_sc); - PktOut *pktout; - - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_EXEC_SHELL); - pq_push(s->ppl.out_pq, pktout); - - ssh1mainchan_queue_response(s, want_reply, true); -} - -static void ssh1mainchan_start_command( - SshChannel *sc, bool want_reply, const char *command) -{ - struct ssh1_connection_state *s = - container_of(sc, struct ssh1_connection_state, mainchan_sc); - PktOut *pktout; - - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_EXEC_CMD); - put_stringz(pktout, command); - pq_push(s->ppl.out_pq, pktout); - - ssh1mainchan_queue_response(s, want_reply, true); -} - -static bool ssh1mainchan_start_subsystem( - SshChannel *sc, bool want_reply, const char *subsystem) -{ - return false; /* SSH-1 doesn't support this at all */ -} - -static bool ssh1mainchan_send_serial_break( - SshChannel *sc, bool want_reply, int length) -{ - return false; /* SSH-1 doesn't support this at all */ -} - -static bool ssh1mainchan_send_signal( - SshChannel *sc, bool want_reply, const char *signame) -{ - return false; /* SSH-1 doesn't support this at all */ -} - -static void ssh1mainchan_send_terminal_size_change( - SshChannel *sc, int w, int h) -{ - struct ssh1_connection_state *s = - container_of(sc, struct ssh1_connection_state, mainchan_sc); - PktOut *pktout; - - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_WINDOW_SIZE); - put_uint32(pktout, h); - put_uint32(pktout, w); - put_uint32(pktout, 0); /* width in pixels */ - put_uint32(pktout, 0); /* height in pixels */ - pq_push(s->ppl.out_pq, pktout); -} - -static void ssh1mainchan_hint_channel_is_simple(SshChannel *sc) -{ -} - -static size_t ssh1mainchan_write( - SshChannel *sc, bool is_stderr, const void *data, size_t len) -{ - struct ssh1_connection_state *s = - container_of(sc, struct ssh1_connection_state, mainchan_sc); - PktOut *pktout; - - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_STDIN_DATA); - put_string(pktout, data, len); - pq_push(s->ppl.out_pq, pktout); - - return 0; -} - -static void ssh1mainchan_write_eof(SshChannel *sc) -{ - struct ssh1_connection_state *s = - container_of(sc, struct ssh1_connection_state, mainchan_sc); - PktOut *pktout; - - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_EOF); - pq_push(s->ppl.out_pq, pktout); -} - -static const SshChannelVtable ssh1mainchan_vtable = { - .write = ssh1mainchan_write, - .write_eof = ssh1mainchan_write_eof, - .request_x11_forwarding = ssh1mainchan_request_x11_forwarding, - .request_agent_forwarding = ssh1mainchan_request_agent_forwarding, - .request_pty = ssh1mainchan_request_pty, - .send_env_var = ssh1mainchan_send_env_var, - .start_shell = ssh1mainchan_start_shell, - .start_command = ssh1mainchan_start_command, - .start_subsystem = ssh1mainchan_start_subsystem, - .send_serial_break = ssh1mainchan_send_serial_break, - .send_signal = ssh1mainchan_send_signal, - .send_terminal_size_change = ssh1mainchan_send_terminal_size_change, - .hint_channel_is_simple = ssh1mainchan_hint_channel_is_simple, - /* other methods are NULL */ -}; - -static void ssh1_session_confirm_callback(void *vctx) -{ - struct ssh1_connection_state *s = (struct ssh1_connection_state *)vctx; - chan_open_confirmation(s->mainchan_chan); -} - -SshChannel *ssh1_session_open(ConnectionLayer *cl, Channel *chan) -{ - struct ssh1_connection_state *s = - container_of(cl, struct ssh1_connection_state, cl); - s->mainchan_sc.vt = &ssh1mainchan_vtable; - s->mainchan_sc.cl = &s->cl; - s->mainchan_chan = chan; - queue_toplevel_callback(ssh1_session_confirm_callback, s); - return &s->mainchan_sc; -} - -static void ssh1_rportfwd_response(struct ssh1_connection_state *s, - bool success, void *ctx) -{ - PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ - struct ssh_rportfwd *rpf = (struct ssh_rportfwd *)ctx; - - if (success) { - ppl_logevent("Remote port forwarding from %s enabled", - rpf->log_description); - } else { - ppl_logevent("Remote port forwarding from %s refused", - rpf->log_description); - - struct ssh_rportfwd *realpf = del234(s->rportfwds, rpf); - assert(realpf == rpf); - portfwdmgr_close(s->portfwdmgr, rpf->pfr); - free_rportfwd(rpf); - } -} - -struct ssh_rportfwd *ssh1_rportfwd_alloc( - ConnectionLayer *cl, - const char *shost, int sport, const char *dhost, int dport, - int addressfamily, const char *log_description, PortFwdRecord *pfr, - ssh_sharing_connstate *share_ctx) -{ - struct ssh1_connection_state *s = - container_of(cl, struct ssh1_connection_state, cl); - struct ssh_rportfwd *rpf = snew(struct ssh_rportfwd); - - rpf->shost = dupstr(shost); - rpf->sport = sport; - rpf->dhost = dupstr(dhost); - rpf->dport = dport; - rpf->addressfamily = addressfamily; - rpf->log_description = dupstr(log_description); - rpf->pfr = pfr; - - if (add234(s->rportfwds, rpf) != rpf) { - free_rportfwd(rpf); - return NULL; - } - - PktOut *pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH1_CMSG_PORT_FORWARD_REQUEST); - put_uint32(pktout, rpf->sport); - put_stringz(pktout, rpf->dhost); - put_uint32(pktout, rpf->dport); - pq_push(s->ppl.out_pq, pktout); - - ssh1_queue_succfail_handler(s, ssh1_rportfwd_response, rpf, false); - - return rpf; -} - -SshChannel *ssh1_serverside_x11_open( - ConnectionLayer *cl, Channel *chan, const SocketPeerInfo *pi) -{ - unreachable("Should never be called in the client"); -} - -SshChannel *ssh1_serverside_agent_open(ConnectionLayer *cl, Channel *chan) -{ - unreachable("Should never be called in the client"); -} - -bool ssh1_connection_need_antispoof_prompt(struct ssh1_connection_state *s) -{ - return !seat_set_trust_status(s->ppl.seat, false); -} diff --git a/ssh1connection-server.c b/ssh1connection-server.c deleted file mode 100644 index 4d55abe5..00000000 --- a/ssh1connection-server.c +++ /dev/null @@ -1,365 +0,0 @@ -/* - * Server-specific parts of the SSH-1 connection layer. - */ - -#include - -#include "putty.h" -#include "ssh.h" -#include "sshbpp.h" -#include "sshppl.h" -#include "sshchan.h" -#include "sshcr.h" -#include "ssh1connection.h" -#include "sshserver.h" - -static size_t ssh1sesschan_write(SshChannel *c, bool is_stderr, - const void *, size_t); -static void ssh1sesschan_write_eof(SshChannel *c); -static void ssh1sesschan_initiate_close(SshChannel *c, const char *err); -static void ssh1sesschan_send_exit_status(SshChannel *c, int status); -static void ssh1sesschan_send_exit_signal( - SshChannel *c, ptrlen signame, bool core_dumped, ptrlen msg); - -static const SshChannelVtable ssh1sesschan_vtable = { - .write = ssh1sesschan_write, - .write_eof = ssh1sesschan_write_eof, - .initiate_close = ssh1sesschan_initiate_close, - .send_exit_status = ssh1sesschan_send_exit_status, - .send_exit_signal = ssh1sesschan_send_exit_signal, - /* everything else is NULL */ -}; - -void ssh1connection_server_configure( - PacketProtocolLayer *ppl, const SshServerConfig *ssc) -{ - struct ssh1_connection_state *s = - container_of(ppl, struct ssh1_connection_state, ppl); - s->ssc = ssc; -} - -void ssh1_connection_direction_specific_setup( - struct ssh1_connection_state *s) -{ - if (!s->mainchan_chan) { - s->mainchan_sc.vt = &ssh1sesschan_vtable; - s->mainchan_sc.cl = &s->cl; - s->mainchan_chan = sesschan_new( - &s->mainchan_sc, s->ppl.logctx, NULL, s->ssc); - } -} - -bool ssh1_handle_direction_specific_packet( - struct ssh1_connection_state *s, PktIn *pktin) -{ - PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ - PktOut *pktout; - struct ssh1_channel *c; - unsigned remid; - ptrlen host, cmd, data; - char *host_str, *err; - int port, listenport; - bool success; - - switch (pktin->type) { - case SSH1_CMSG_EXEC_SHELL: - if (s->finished_setup) - goto unexpected_setup_packet; - - ppl_logevent("Client requested a shell"); - chan_run_shell(s->mainchan_chan); - s->finished_setup = true; - return true; - - case SSH1_CMSG_EXEC_CMD: - if (s->finished_setup) - goto unexpected_setup_packet; - - cmd = get_string(pktin); - ppl_logevent("Client sent command '%.*s'", PTRLEN_PRINTF(cmd)); - chan_run_command(s->mainchan_chan, cmd); - s->finished_setup = true; - return true; - - case SSH1_CMSG_REQUEST_COMPRESSION: - if (s->compressing || !s->ssc->ssh1_allow_compression) { - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_SMSG_FAILURE); - pq_push(s->ppl.out_pq, pktout); - } else { - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_SMSG_SUCCESS); - pq_push(s->ppl.out_pq, pktout); - /* Synchronous run of output formatting, to ensure that - * success packet is converted into wire format before we - * start compressing */ - ssh_bpp_handle_output(s->ppl.bpp); - /* And now ensure that the _next_ packet will be the first - * compressed one. */ - ssh1_bpp_start_compression(s->ppl.bpp); - s->compressing = true; - } - - return true; - - case SSH1_CMSG_REQUEST_PTY: { - if (s->finished_setup) - goto unexpected_setup_packet; - - ptrlen termtype = get_string(pktin); - unsigned height = get_uint32(pktin); - unsigned width = get_uint32(pktin); - unsigned pixwidth = get_uint32(pktin); - unsigned pixheight = get_uint32(pktin); - struct ssh_ttymodes modes = read_ttymodes_from_packet( - BinarySource_UPCAST(pktin), 1); - - if (get_err(pktin)) { - ppl_logevent("Unable to decode pty request packet"); - success = false; - } else if (!chan_allocate_pty( - s->mainchan_chan, termtype, width, height, - pixwidth, pixheight, modes)) { - ppl_logevent("Unable to allocate a pty"); - success = false; - } else { - success = true; - } - - pktout = ssh_bpp_new_pktout( - s->ppl.bpp, (success ? SSH1_SMSG_SUCCESS : SSH1_SMSG_FAILURE)); - pq_push(s->ppl.out_pq, pktout); - return true; - } - - case SSH1_CMSG_PORT_FORWARD_REQUEST: - if (s->finished_setup) - goto unexpected_setup_packet; - - listenport = toint(get_uint32(pktin)); - host = get_string(pktin); - port = toint(get_uint32(pktin)); - - ppl_logevent("Client requested port %d forward to %.*s:%d", - listenport, PTRLEN_PRINTF(host), port); - - host_str = mkstr(host); - success = portfwdmgr_listen( - s->portfwdmgr, NULL, listenport, host_str, port, s->conf); - sfree(host_str); - - pktout = ssh_bpp_new_pktout( - s->ppl.bpp, (success ? SSH1_SMSG_SUCCESS : SSH1_SMSG_FAILURE)); - pq_push(s->ppl.out_pq, pktout); - return true; - - case SSH1_CMSG_X11_REQUEST_FORWARDING: { - if (s->finished_setup) - goto unexpected_setup_packet; - - ptrlen authproto = get_string(pktin); - ptrlen authdata = get_string(pktin); - unsigned screen_number = 0; - if (s->remote_protoflags & SSH1_PROTOFLAG_SCREEN_NUMBER) - screen_number = get_uint32(pktin); - - success = chan_enable_x11_forwarding( - s->mainchan_chan, false, authproto, authdata, screen_number); - - pktout = ssh_bpp_new_pktout( - s->ppl.bpp, (success ? SSH1_SMSG_SUCCESS : SSH1_SMSG_FAILURE)); - pq_push(s->ppl.out_pq, pktout); - return true; - } - - case SSH1_CMSG_AGENT_REQUEST_FORWARDING: - if (s->finished_setup) - goto unexpected_setup_packet; - - success = chan_enable_agent_forwarding(s->mainchan_chan); - - pktout = ssh_bpp_new_pktout( - s->ppl.bpp, (success ? SSH1_SMSG_SUCCESS : SSH1_SMSG_FAILURE)); - pq_push(s->ppl.out_pq, pktout); - return true; - - case SSH1_CMSG_STDIN_DATA: - data = get_string(pktin); - chan_send(s->mainchan_chan, false, data.ptr, data.len); - return true; - - case SSH1_CMSG_EOF: - chan_send_eof(s->mainchan_chan); - return true; - - case SSH1_CMSG_WINDOW_SIZE: - return true; - - case SSH1_MSG_PORT_OPEN: - remid = get_uint32(pktin); - host = get_string(pktin); - port = toint(get_uint32(pktin)); - - host_str = mkstr(host); - - ppl_logevent("Received request to connect to port %s:%d", - host_str, port); - c = snew(struct ssh1_channel); - c->connlayer = s; - err = portfwdmgr_connect( - s->portfwdmgr, &c->chan, host_str, port, - &c->sc, ADDRTYPE_UNSPEC); - - sfree(host_str); - - if (err) { - ppl_logevent("Port open failed: %s", err); - sfree(err); - ssh1_channel_free(c); - pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH1_MSG_CHANNEL_OPEN_FAILURE); - put_uint32(pktout, remid); - pq_push(s->ppl.out_pq, pktout); - } else { - ssh1_channel_init(c); - c->remoteid = remid; - c->halfopen = false; - pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION); - put_uint32(pktout, c->remoteid); - put_uint32(pktout, c->localid); - pq_push(s->ppl.out_pq, pktout); - ppl_logevent("Forwarded port opened successfully"); - } - - return true; - - case SSH1_CMSG_EXIT_CONFIRMATION: - if (!s->sent_exit_status) { - ssh_proto_error(s->ppl.ssh, "Received SSH1_CMSG_EXIT_CONFIRMATION" - " without having sent SSH1_SMSG_EXIT_STATUS"); - return true; - } - ppl_logevent("Client sent exit confirmation"); - return true; - - default: - return false; - } - - unexpected_setup_packet: - ssh_proto_error(s->ppl.ssh, "Received unexpected setup packet after the " - "setup phase, type %d (%s)", pktin->type, - ssh1_pkt_type(pktin->type)); - /* FIXME: ensure caller copes with us just having freed the whole layer */ - return true; -} - -SshChannel *ssh1_session_open(ConnectionLayer *cl, Channel *chan) -{ - unreachable("Should never be called in the server"); -} - -struct ssh_rportfwd *ssh1_rportfwd_alloc( - ConnectionLayer *cl, - const char *shost, int sport, const char *dhost, int dport, - int addressfamily, const char *log_description, PortFwdRecord *pfr, - ssh_sharing_connstate *share_ctx) -{ - unreachable("Should never be called in the server"); -} - -static size_t ssh1sesschan_write(SshChannel *sc, bool is_stderr, - const void *data, size_t len) -{ - struct ssh1_connection_state *s = - container_of(sc, struct ssh1_connection_state, mainchan_sc); - PktOut *pktout; - - pktout = ssh_bpp_new_pktout( - s->ppl.bpp, - (is_stderr ? SSH1_SMSG_STDERR_DATA : SSH1_SMSG_STDOUT_DATA)); - put_string(pktout, data, len); - pq_push(s->ppl.out_pq, pktout); - - return 0; -} - -static void ssh1sesschan_write_eof(SshChannel *sc) -{ - /* SSH-1 can't represent server-side EOF */ - /* FIXME: some kind of check-termination system, whereby once this has been called _and_ we've had an exit status _and_ we've got no other channels open, we send the actual EXIT_STATUS message */ -} - -static void ssh1sesschan_initiate_close(SshChannel *sc, const char *err) -{ - /* SSH-1 relies on the client to close the connection in the end */ -} - -static void ssh1sesschan_send_exit_status(SshChannel *sc, int status) -{ - struct ssh1_connection_state *s = - container_of(sc, struct ssh1_connection_state, mainchan_sc); - PktOut *pktout; - - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_SMSG_EXIT_STATUS); - put_uint32(pktout, status); - pq_push(s->ppl.out_pq, pktout); - - s->sent_exit_status = true; -} - -static void ssh1sesschan_send_exit_signal( - SshChannel *sc, ptrlen signame, bool core_dumped, ptrlen msg) -{ - /* SSH-1 has no separate representation for signals */ - ssh1sesschan_send_exit_status(sc, 128); -} - -SshChannel *ssh1_serverside_x11_open( - ConnectionLayer *cl, Channel *chan, const SocketPeerInfo *pi) -{ - struct ssh1_connection_state *s = - container_of(cl, struct ssh1_connection_state, cl); - PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ - struct ssh1_channel *c = snew(struct ssh1_channel); - PktOut *pktout; - - c->connlayer = s; - ssh1_channel_init(c); - c->halfopen = true; - c->chan = chan; - - ppl_logevent("Forwarding X11 connection to client"); - - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_SMSG_X11_OPEN); - put_uint32(pktout, c->localid); - pq_push(s->ppl.out_pq, pktout); - - return &c->sc; -} - -SshChannel *ssh1_serverside_agent_open(ConnectionLayer *cl, Channel *chan) -{ - struct ssh1_connection_state *s = - container_of(cl, struct ssh1_connection_state, cl); - PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ - struct ssh1_channel *c = snew(struct ssh1_channel); - PktOut *pktout; - - c->connlayer = s; - ssh1_channel_init(c); - c->halfopen = true; - c->chan = chan; - - ppl_logevent("Forwarding agent connection to client"); - - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_SMSG_AGENT_OPEN); - put_uint32(pktout, c->localid); - pq_push(s->ppl.out_pq, pktout); - - return &c->sc; -} - -bool ssh1_connection_need_antispoof_prompt(struct ssh1_connection_state *s) -{ - return false; -} diff --git a/ssh1connection.c b/ssh1connection.c deleted file mode 100644 index d805dd29..00000000 --- a/ssh1connection.c +++ /dev/null @@ -1,815 +0,0 @@ -/* - * Packet protocol layer for the SSH-1 'connection protocol', i.e. - * everything after authentication finishes. - */ - -#include - -#include "putty.h" -#include "ssh.h" -#include "sshbpp.h" -#include "sshppl.h" -#include "sshchan.h" -#include "sshcr.h" -#include "ssh1connection.h" - -static int ssh1_rportfwd_cmp(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 void ssh1_connection_free(PacketProtocolLayer *); -static void ssh1_connection_process_queue(PacketProtocolLayer *); -static void ssh1_connection_special_cmd(PacketProtocolLayer *ppl, - SessionSpecialCode code, int arg); -static bool ssh1_connection_want_user_input(PacketProtocolLayer *ppl); -static void ssh1_connection_got_user_input(PacketProtocolLayer *ppl); -static void ssh1_connection_reconfigure(PacketProtocolLayer *ppl, Conf *conf); - -static const PacketProtocolLayerVtable ssh1_connection_vtable = { - .free = ssh1_connection_free, - .process_queue = ssh1_connection_process_queue, - .get_specials = ssh1_common_get_specials, - .special_cmd = ssh1_connection_special_cmd, - .want_user_input = ssh1_connection_want_user_input, - .got_user_input = ssh1_connection_got_user_input, - .reconfigure = ssh1_connection_reconfigure, - .queued_data_size = ssh_ppl_default_queued_data_size, - .name = NULL, /* no layer names in SSH-1 */ -}; - -static void ssh1_rportfwd_remove( - ConnectionLayer *cl, struct ssh_rportfwd *rpf); -static SshChannel *ssh1_lportfwd_open( - ConnectionLayer *cl, const char *hostname, int port, - const char *description, const SocketPeerInfo *pi, Channel *chan); -static struct X11FakeAuth *ssh1_add_x11_display( - ConnectionLayer *cl, int authtype, struct X11Display *disp); -static bool ssh1_agent_forwarding_permitted(ConnectionLayer *cl); -static void ssh1_terminal_size(ConnectionLayer *cl, int width, int height); -static void ssh1_stdout_unthrottle(ConnectionLayer *cl, size_t bufsize); -static size_t ssh1_stdin_backlog(ConnectionLayer *cl); -static void ssh1_throttle_all_channels(ConnectionLayer *cl, bool throttled); -static bool ssh1_ldisc_option(ConnectionLayer *cl, int option); -static void ssh1_set_ldisc_option(ConnectionLayer *cl, int option, bool value); -static void ssh1_enable_x_fwd(ConnectionLayer *cl); -static void ssh1_set_wants_user_input(ConnectionLayer *cl, bool wanted); - -static const ConnectionLayerVtable ssh1_connlayer_vtable = { - .rportfwd_alloc = ssh1_rportfwd_alloc, - .rportfwd_remove = ssh1_rportfwd_remove, - .lportfwd_open = ssh1_lportfwd_open, - .session_open = ssh1_session_open, - .serverside_x11_open = ssh1_serverside_x11_open, - .serverside_agent_open = ssh1_serverside_agent_open, - .add_x11_display = ssh1_add_x11_display, - .agent_forwarding_permitted = ssh1_agent_forwarding_permitted, - .terminal_size = ssh1_terminal_size, - .stdout_unthrottle = ssh1_stdout_unthrottle, - .stdin_backlog = ssh1_stdin_backlog, - .throttle_all_channels = ssh1_throttle_all_channels, - .ldisc_option = ssh1_ldisc_option, - .set_ldisc_option = ssh1_set_ldisc_option, - .enable_x_fwd = ssh1_enable_x_fwd, - .set_wants_user_input = ssh1_set_wants_user_input, - /* other methods are NULL */ -}; - -static size_t ssh1channel_write( - SshChannel *c, bool is_stderr, const void *buf, size_t len); -static void ssh1channel_write_eof(SshChannel *c); -static void ssh1channel_initiate_close(SshChannel *c, const char *err); -static void ssh1channel_unthrottle(SshChannel *c, size_t bufsize); -static Conf *ssh1channel_get_conf(SshChannel *c); -static void ssh1channel_window_override_removed(SshChannel *c) { /* ignore */ } - -static const SshChannelVtable ssh1channel_vtable = { - .write = ssh1channel_write, - .write_eof = ssh1channel_write_eof, - .initiate_close = ssh1channel_initiate_close, - .unthrottle = ssh1channel_unthrottle, - .get_conf = ssh1channel_get_conf, - .window_override_removed = ssh1channel_window_override_removed, - /* everything else is NULL */ -}; - -static void ssh1_channel_try_eof(struct ssh1_channel *c); -static void ssh1_channel_close_local(struct ssh1_channel *c, - const char *reason); -static void ssh1_channel_destroy(struct ssh1_channel *c); -static void ssh1_channel_check_close(struct ssh1_channel *c); - -static int ssh1_channelcmp(void *av, void *bv) -{ - const struct ssh1_channel *a = (const struct ssh1_channel *) av; - const struct ssh1_channel *b = (const struct ssh1_channel *) bv; - if (a->localid < b->localid) - return -1; - if (a->localid > b->localid) - return +1; - return 0; -} - -static int ssh1_channelfind(void *av, void *bv) -{ - const unsigned *a = (const unsigned *) av; - const struct ssh1_channel *b = (const struct ssh1_channel *) bv; - if (*a < b->localid) - return -1; - if (*a > b->localid) - return +1; - return 0; -} - -void ssh1_channel_free(struct ssh1_channel *c) -{ - if (c->chan) - chan_free(c->chan); - sfree(c); -} - -PacketProtocolLayer *ssh1_connection_new( - Ssh *ssh, Conf *conf, ConnectionLayer **cl_out) -{ - struct ssh1_connection_state *s = snew(struct ssh1_connection_state); - memset(s, 0, sizeof(*s)); - s->ppl.vt = &ssh1_connection_vtable; - - s->conf = conf_copy(conf); - - s->channels = newtree234(ssh1_channelcmp); - - s->x11authtree = newtree234(x11_authcmp); - - /* Need to get the log context for s->cl now, because we won't be - * helpfully notified when a copy is written into s->ppl by our - * owner. */ - s->cl.vt = &ssh1_connlayer_vtable; - s->cl.logctx = ssh_get_logctx(ssh); - - s->portfwdmgr = portfwdmgr_new(&s->cl); - s->rportfwds = newtree234(ssh1_rportfwd_cmp); - - *cl_out = &s->cl; - return &s->ppl; -} - -static void ssh1_connection_free(PacketProtocolLayer *ppl) -{ - struct ssh1_connection_state *s = - container_of(ppl, struct ssh1_connection_state, ppl); - struct X11FakeAuth *auth; - struct ssh1_channel *c; - struct ssh_rportfwd *rpf; - - conf_free(s->conf); - - while ((c = delpos234(s->channels, 0)) != NULL) - ssh1_channel_free(c); - freetree234(s->channels); - if (s->mainchan_chan) - chan_free(s->mainchan_chan); - - if (s->x11disp) - x11_free_display(s->x11disp); - while ((auth = delpos234(s->x11authtree, 0)) != NULL) - x11_free_fake_auth(auth); - freetree234(s->x11authtree); - - while ((rpf = delpos234(s->rportfwds, 0)) != NULL) - free_rportfwd(rpf); - freetree234(s->rportfwds); - portfwdmgr_free(s->portfwdmgr); - - if (s->antispoof_prompt) - free_prompts(s->antispoof_prompt); - - delete_callbacks_for_context(s); - - sfree(s); -} - -void ssh1_connection_set_protoflags(PacketProtocolLayer *ppl, - int local, int remote) -{ - assert(ppl->vt == &ssh1_connection_vtable); - struct ssh1_connection_state *s = - container_of(ppl, struct ssh1_connection_state, ppl); - s->local_protoflags = local; - s->remote_protoflags = remote; -} - -static bool ssh1_connection_filter_queue(struct ssh1_connection_state *s) -{ - PktIn *pktin; - ptrlen data; - struct ssh1_channel *c; - unsigned localid; - bool expect_halfopen; - - while (1) { - if (ssh1_common_filter_queue(&s->ppl)) - return true; - if ((pktin = pq_peek(s->ppl.in_pq)) == NULL) - return false; - - switch (pktin->type) { - case SSH1_MSG_CHANNEL_DATA: - case SSH1_MSG_CHANNEL_OPEN_CONFIRMATION: - case SSH1_MSG_CHANNEL_OPEN_FAILURE: - case SSH1_MSG_CHANNEL_CLOSE: - case SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION: - /* - * Common preliminary code for all the messages from the - * server that cite one of our channel ids: look up that - * channel id, check it exists, and if it's for a sharing - * downstream, pass it on. - */ - localid = get_uint32(pktin); - c = find234(s->channels, &localid, ssh1_channelfind); - - expect_halfopen = ( - pktin->type == SSH1_MSG_CHANNEL_OPEN_CONFIRMATION || - pktin->type == SSH1_MSG_CHANNEL_OPEN_FAILURE); - - if (!c || c->halfopen != expect_halfopen) { - ssh_remote_error( - s->ppl.ssh, "Received %s for %s channel %u", - ssh1_pkt_type(pktin->type), - !c ? "nonexistent" : c->halfopen ? "half-open" : "open", - localid); - return true; - } - - switch (pktin->type) { - case SSH1_MSG_CHANNEL_OPEN_CONFIRMATION: - assert(c->halfopen); - c->remoteid = get_uint32(pktin); - c->halfopen = false; - c->throttling_conn = false; - - chan_open_confirmation(c->chan); - - /* - * Now that the channel is fully open, it's possible - * in principle to immediately close it. Check whether - * it wants us to! - * - * This can occur if a local socket error occurred - * between us sending out CHANNEL_OPEN and receiving - * OPEN_CONFIRMATION. If that happens, all we can do - * is immediately initiate close proceedings now that - * we know the server's id to put in the close - * message. We'll have handled that in this code by - * having already turned c->chan into a zombie, so its - * want_close method (which ssh1_channel_check_close - * will consult) will already be returning true. - */ - ssh1_channel_check_close(c); - - if (c->pending_eof) - ssh1_channel_try_eof(c); /* in case we had a pending EOF */ - break; - - case SSH1_MSG_CHANNEL_OPEN_FAILURE: - assert(c->halfopen); - - chan_open_failed(c->chan, NULL); - chan_free(c->chan); - - del234(s->channels, c); - ssh1_channel_free(c); - break; - - case SSH1_MSG_CHANNEL_DATA: - data = get_string(pktin); - if (!get_err(pktin)) { - int bufsize = chan_send( - c->chan, false, data.ptr, data.len); - - if (!c->throttling_conn && bufsize > SSH1_BUFFER_LIMIT) { - c->throttling_conn = true; - ssh_throttle_conn(s->ppl.ssh, +1); - } - } - break; - - case SSH1_MSG_CHANNEL_CLOSE: - if (!(c->closes & CLOSES_RCVD_CLOSE)) { - c->closes |= CLOSES_RCVD_CLOSE; - chan_send_eof(c->chan); - ssh1_channel_check_close(c); - } - break; - - case SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION: - if (!(c->closes & CLOSES_RCVD_CLOSECONF)) { - if (!(c->closes & CLOSES_SENT_CLOSE)) { - ssh_remote_error( - s->ppl.ssh, - "Received CHANNEL_CLOSE_CONFIRMATION for channel" - " %u for which we never sent CHANNEL_CLOSE\n", - c->localid); - return true; - } - - c->closes |= CLOSES_RCVD_CLOSECONF; - ssh1_channel_check_close(c); - } - break; - } - - pq_pop(s->ppl.in_pq); - break; - - default: - if (ssh1_handle_direction_specific_packet(s, pktin)) { - pq_pop(s->ppl.in_pq); - if (ssh1_check_termination(s)) - return true; - } else { - return false; - } - } - } -} - -static PktIn *ssh1_connection_pop(struct ssh1_connection_state *s) -{ - ssh1_connection_filter_queue(s); - return pq_pop(s->ppl.in_pq); -} - -static void ssh1_connection_process_queue(PacketProtocolLayer *ppl) -{ - struct ssh1_connection_state *s = - container_of(ppl, struct ssh1_connection_state, ppl); - PktIn *pktin; - - if (ssh1_connection_filter_queue(s)) /* no matter why we were called */ - return; - - crBegin(s->crState); - - /* - * Signal the seat that authentication is done, so that it can - * deploy spoofing defences. If it doesn't have any, deploy our - * own fallback one. - * - * We do this here rather than at the end of userauth, because we - * might not have gone through userauth at all (if we're a - * connection-sharing downstream). - */ - if (ssh1_connection_need_antispoof_prompt(s)) { - s->antispoof_prompt = new_prompts(); - s->antispoof_prompt->to_server = true; - s->antispoof_prompt->from_server = false; - s->antispoof_prompt->name = dupstr("Authentication successful"); - add_prompt( - s->antispoof_prompt, - dupstr("Access granted. Press Return to begin session. "), false); - s->antispoof_ret = seat_get_userpass_input( - s->ppl.seat, s->antispoof_prompt, NULL); - while (1) { - while (s->antispoof_ret < 0 && - bufchain_size(s->ppl.user_input) > 0) - s->antispoof_ret = seat_get_userpass_input( - s->ppl.seat, s->antispoof_prompt, s->ppl.user_input); - - if (s->antispoof_ret >= 0) - break; - - s->want_user_input = true; - crReturnV; - s->want_user_input = false; - } - free_prompts(s->antispoof_prompt); - s->antispoof_prompt = NULL; - } - - portfwdmgr_config(s->portfwdmgr, s->conf); - s->portfwdmgr_configured = true; - - while (!s->finished_setup) { - ssh1_connection_direction_specific_setup(s); - crReturnV; - } - - while (1) { - - /* - * By this point, most incoming packets are already being - * handled by filter_queue, and we need only pay attention to - * the unusual ones. - */ - - if ((pktin = ssh1_connection_pop(s)) != NULL) { - ssh_proto_error(s->ppl.ssh, "Unexpected packet received, " - "type %d (%s)", pktin->type, - ssh1_pkt_type(pktin->type)); - return; - } - crReturnV; - } - - crFinishV; -} - -static void ssh1_channel_check_close(struct ssh1_channel *c) -{ - struct ssh1_connection_state *s = c->connlayer; - PktOut *pktout; - - if (c->halfopen) { - /* - * If we've sent out our own CHANNEL_OPEN but not yet seen - * either OPEN_CONFIRMATION or OPEN_FAILURE in response, then - * it's too early to be sending close messages of any kind. - */ - return; - } - - if ((!((CLOSES_SENT_CLOSE | CLOSES_RCVD_CLOSE) & ~c->closes) || - chan_want_close(c->chan, (c->closes & CLOSES_SENT_CLOSE), - (c->closes & CLOSES_RCVD_CLOSE))) && - !(c->closes & CLOSES_SENT_CLOSECONF)) { - /* - * We have both sent and received CLOSE (or the channel type - * doesn't need us to), which means the channel is in final - * wind-up. Send CLOSE and/or CLOSE_CONFIRMATION, whichever we - * haven't sent yet. - */ - if (!(c->closes & CLOSES_SENT_CLOSE)) { - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_MSG_CHANNEL_CLOSE); - put_uint32(pktout, c->remoteid); - pq_push(s->ppl.out_pq, pktout); - c->closes |= CLOSES_SENT_CLOSE; - } - if (c->closes & CLOSES_RCVD_CLOSE) { - pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION); - put_uint32(pktout, c->remoteid); - pq_push(s->ppl.out_pq, pktout); - c->closes |= CLOSES_SENT_CLOSECONF; - } - } - - if (!((CLOSES_SENT_CLOSECONF | CLOSES_RCVD_CLOSECONF) & ~c->closes)) { - /* - * We have both sent and received CLOSE_CONFIRMATION, which - * means we're completely done with the channel. - */ - ssh1_channel_destroy(c); - } -} - -static void ssh1_channel_try_eof(struct ssh1_channel *c) -{ - struct ssh1_connection_state *s = c->connlayer; - PktOut *pktout; - assert(c->pending_eof); /* precondition for calling us */ - if (c->halfopen) - return; /* can't close: not even opened yet */ - - c->pending_eof = false; /* we're about to send it */ - - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_MSG_CHANNEL_CLOSE); - put_uint32(pktout, c->remoteid); - pq_push(s->ppl.out_pq, pktout); - c->closes |= CLOSES_SENT_CLOSE; - - ssh1_channel_check_close(c); -} - -/* - * Close any local socket and free any local resources associated with - * a channel. This converts the channel into a zombie. - */ -static void ssh1_channel_close_local(struct ssh1_channel *c, - const char *reason) -{ - struct ssh1_connection_state *s = c->connlayer; - PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ - char *msg = chan_log_close_msg(c->chan); - - if (msg != NULL) { - ppl_logevent("%s%s%s", msg, reason ? " " : "", reason ? reason : ""); - sfree(msg); - } - - chan_free(c->chan); - c->chan = zombiechan_new(); -} - -static void ssh1_check_termination_callback(void *vctx) -{ - struct ssh1_connection_state *s = (struct ssh1_connection_state *)vctx; - ssh1_check_termination(s); -} - -static void ssh1_channel_destroy(struct ssh1_channel *c) -{ - struct ssh1_connection_state *s = c->connlayer; - - ssh1_channel_close_local(c, NULL); - del234(s->channels, c); - ssh1_channel_free(c); - - /* - * If that was the last channel left open, we might need to - * terminate. But we'll be a bit cautious, by doing that in a - * toplevel callback, just in case anything on the current call - * stack objects to this entire PPL being freed. - */ - queue_toplevel_callback(ssh1_check_termination_callback, s); -} - -bool ssh1_check_termination(struct ssh1_connection_state *s) -{ - /* - * Decide whether we should terminate the SSH connection now. - * Called after a channel goes away, or when the main session - * returns SSH1_SMSG_EXIT_STATUS; we terminate when none of either - * is left. - */ - if (s->session_terminated && count234(s->channels) == 0) { - PktOut *pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH1_CMSG_EXIT_CONFIRMATION); - pq_push(s->ppl.out_pq, pktout); - - ssh_user_close(s->ppl.ssh, "Session finished"); - return true; - } - - return false; -} - -/* - * Set up most of a new ssh1_channel. Leaves chan untouched (since it - * will sometimes have been filled in before calling this). - */ -void ssh1_channel_init(struct ssh1_channel *c) -{ - struct ssh1_connection_state *s = c->connlayer; - c->closes = 0; - c->pending_eof = false; - c->throttling_conn = false; - c->sc.vt = &ssh1channel_vtable; - c->sc.cl = &s->cl; - c->localid = alloc_channel_id(s->channels, struct ssh1_channel); - add234(s->channels, c); -} - -static Conf *ssh1channel_get_conf(SshChannel *sc) -{ - struct ssh1_channel *c = container_of(sc, struct ssh1_channel, sc); - struct ssh1_connection_state *s = c->connlayer; - return s->conf; -} - -static void ssh1channel_write_eof(SshChannel *sc) -{ - struct ssh1_channel *c = container_of(sc, struct ssh1_channel, sc); - - if (c->closes & CLOSES_SENT_CLOSE) - return; - - c->pending_eof = true; - ssh1_channel_try_eof(c); -} - -static void ssh1channel_initiate_close(SshChannel *sc, const char *err) -{ - struct ssh1_channel *c = container_of(sc, struct ssh1_channel, sc); - char *reason; - - reason = err ? dupprintf("due to local error: %s", err) : NULL; - ssh1_channel_close_local(c, reason); - sfree(reason); - c->pending_eof = false; /* this will confuse a zombie channel */ - - ssh1_channel_check_close(c); -} - -static void ssh1channel_unthrottle(SshChannel *sc, size_t bufsize) -{ - struct ssh1_channel *c = container_of(sc, struct ssh1_channel, sc); - struct ssh1_connection_state *s = c->connlayer; - - if (c->throttling_conn && bufsize <= SSH1_BUFFER_LIMIT) { - c->throttling_conn = false; - ssh_throttle_conn(s->ppl.ssh, -1); - } -} - -static size_t ssh1channel_write( - SshChannel *sc, bool is_stderr, const void *buf, size_t len) -{ - struct ssh1_channel *c = container_of(sc, struct ssh1_channel, sc); - struct ssh1_connection_state *s = c->connlayer; - - assert(!(c->closes & CLOSES_SENT_CLOSE)); - - PktOut *pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_MSG_CHANNEL_DATA); - put_uint32(pktout, c->remoteid); - put_string(pktout, buf, len); - pq_push(s->ppl.out_pq, pktout); - - /* - * In SSH-1 we can return 0 here - implying that channels 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; -} - -static struct X11FakeAuth *ssh1_add_x11_display( - ConnectionLayer *cl, int authtype, struct X11Display *disp) -{ - struct ssh1_connection_state *s = - container_of(cl, struct ssh1_connection_state, cl); - struct X11FakeAuth *auth = x11_invent_fake_auth(s->x11authtree, authtype); - auth->disp = disp; - return auth; -} - -static SshChannel *ssh1_lportfwd_open( - ConnectionLayer *cl, const char *hostname, int port, - const char *description, const SocketPeerInfo *pi, Channel *chan) -{ - struct ssh1_connection_state *s = - container_of(cl, struct ssh1_connection_state, cl); - PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ - struct ssh1_channel *c = snew(struct ssh1_channel); - PktOut *pktout; - - c->connlayer = s; - ssh1_channel_init(c); - c->halfopen = true; - c->chan = chan; - - ppl_logevent("Opening connection to %s:%d for %s", - hostname, port, description); - - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_MSG_PORT_OPEN); - put_uint32(pktout, c->localid); - put_stringz(pktout, hostname); - put_uint32(pktout, port); - /* originator string would go here, but we didn't specify - * SSH_PROTOFLAG_HOST_IN_FWD_OPEN */ - pq_push(s->ppl.out_pq, pktout); - - return &c->sc; -} - -static void ssh1_rportfwd_remove(ConnectionLayer *cl, struct ssh_rportfwd *rpf) -{ - /* - * We cannot cancel listening ports on the server side in SSH-1! - * There's no message to support it. - */ -} - -static bool ssh1_agent_forwarding_permitted(ConnectionLayer *cl) -{ - struct ssh1_connection_state *s = - container_of(cl, struct ssh1_connection_state, cl); - return conf_get_bool(s->conf, CONF_agentfwd) && agent_exists(); -} - -static void ssh1_connection_special_cmd(PacketProtocolLayer *ppl, - SessionSpecialCode code, int arg) -{ - struct ssh1_connection_state *s = - container_of(ppl, struct ssh1_connection_state, ppl); - PktOut *pktout; - - if (code == SS_PING || code == SS_NOP) { - if (!(s->ppl.remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE)) { - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_MSG_IGNORE); - put_stringz(pktout, ""); - pq_push(s->ppl.out_pq, pktout); - } - } else if (s->mainchan) { - mainchan_special_cmd(s->mainchan, code, arg); - } -} - -static void ssh1_terminal_size(ConnectionLayer *cl, int width, int height) -{ - struct ssh1_connection_state *s = - container_of(cl, struct ssh1_connection_state, cl); - - s->term_width = width; - s->term_height = height; - if (s->mainchan) - mainchan_terminal_size(s->mainchan, width, height); -} - -static void ssh1_stdout_unthrottle(ConnectionLayer *cl, size_t bufsize) -{ - struct ssh1_connection_state *s = - container_of(cl, struct ssh1_connection_state, cl); - - if (s->stdout_throttling && bufsize < SSH1_BUFFER_LIMIT) { - s->stdout_throttling = false; - ssh_throttle_conn(s->ppl.ssh, -1); - } -} - -static size_t ssh1_stdin_backlog(ConnectionLayer *cl) -{ - return 0; -} - -static void ssh1_throttle_all_channels(ConnectionLayer *cl, bool throttled) -{ - struct ssh1_connection_state *s = - container_of(cl, struct ssh1_connection_state, cl); - struct ssh1_channel *c; - int i; - - for (i = 0; NULL != (c = index234(s->channels, i)); i++) - chan_set_input_wanted(c->chan, !throttled); -} - -static bool ssh1_ldisc_option(ConnectionLayer *cl, int option) -{ - struct ssh1_connection_state *s = - container_of(cl, struct ssh1_connection_state, cl); - - return s->ldisc_opts[option]; -} - -static void ssh1_set_ldisc_option(ConnectionLayer *cl, int option, bool value) -{ - struct ssh1_connection_state *s = - container_of(cl, struct ssh1_connection_state, cl); - - s->ldisc_opts[option] = value; -} - -static void ssh1_enable_x_fwd(ConnectionLayer *cl) -{ - struct ssh1_connection_state *s = - container_of(cl, struct ssh1_connection_state, cl); - - s->X11_fwd_enabled = true; -} - -static void ssh1_set_wants_user_input(ConnectionLayer *cl, bool wanted) -{ - struct ssh1_connection_state *s = - container_of(cl, struct ssh1_connection_state, cl); - - s->want_user_input = wanted; - s->finished_setup = true; -} - -static bool ssh1_connection_want_user_input(PacketProtocolLayer *ppl) -{ - struct ssh1_connection_state *s = - container_of(ppl, struct ssh1_connection_state, ppl); - - return s->want_user_input; -} - -static void ssh1_connection_got_user_input(PacketProtocolLayer *ppl) -{ - struct ssh1_connection_state *s = - container_of(ppl, struct ssh1_connection_state, ppl); - - while (s->mainchan && bufchain_size(s->ppl.user_input) > 0) { - /* - * Add user input to the main channel's buffer. - */ - ptrlen data = bufchain_prefix(s->ppl.user_input); - if (data.len > 512) - data.len = 512; - sshfwd_write(&s->mainchan_sc, data.ptr, data.len); - bufchain_consume(s->ppl.user_input, data.len); - } -} - -static void ssh1_connection_reconfigure(PacketProtocolLayer *ppl, Conf *conf) -{ - struct ssh1_connection_state *s = - container_of(ppl, struct ssh1_connection_state, ppl); - - conf_free(s->conf); - s->conf = conf_copy(conf); - - if (s->portfwdmgr_configured) - portfwdmgr_config(s->portfwdmgr, s->conf); -} diff --git a/ssh1connection.h b/ssh1connection.h deleted file mode 100644 index 44370787..00000000 --- a/ssh1connection.h +++ /dev/null @@ -1,123 +0,0 @@ -struct ssh1_channel; - -struct outstanding_succfail; - -struct ssh1_connection_state { - int crState; - - Conf *conf; - int local_protoflags, remote_protoflags; - - tree234 *channels; /* indexed by local id */ - - /* In SSH-1, the main session doesn't take the form of a 'channel' - * according to the wire protocol. But we want to use the same API - * for it, so we define an SshChannel here - but one that uses a - * separate vtable from the usual one, so it doesn't map to a - * struct ssh1_channel as all the others do. */ - SshChannel mainchan_sc; - Channel *mainchan_chan; /* the other end of mainchan_sc */ - mainchan *mainchan; /* and its subtype */ - - bool got_pty; - bool ldisc_opts[LD_N_OPTIONS]; - bool stdout_throttling; - bool want_user_input; - bool session_terminated; - int term_width, term_height, term_width_orig, term_height_orig; - - bool X11_fwd_enabled; - struct X11Display *x11disp; - struct X11FakeAuth *x11auth; - tree234 *x11authtree; - - tree234 *rportfwds; - PortFwdManager *portfwdmgr; - bool portfwdmgr_configured; - - bool finished_setup; - - /* - * These store the list of requests that we're waiting for - * SSH_SMSG_{SUCCESS,FAILURE} replies to. (Those messages don't - * come with any indication of what they're in response to, so we - * have to keep track of the queue ourselves.) - */ - struct outstanding_succfail *succfail_head, *succfail_tail; - - bool compressing; /* used in server mode only */ - bool sent_exit_status; /* also for server mode */ - - prompts_t *antispoof_prompt; - int antispoof_ret; - - const SshServerConfig *ssc; - - ConnectionLayer cl; - PacketProtocolLayer ppl; -}; - -struct ssh1_channel { - struct ssh1_connection_state *connlayer; - - unsigned remoteid, localid; - int type; - /* True if we opened this channel but server hasn't confirmed. */ - bool halfopen; - - /* Bitmap of whether we've sent/received CHANNEL_CLOSE and - * CHANNEL_CLOSE_CONFIRMATION. */ -#define CLOSES_SENT_CLOSE 1 -#define CLOSES_SENT_CLOSECONF 2 -#define CLOSES_RCVD_CLOSE 4 -#define CLOSES_RCVD_CLOSECONF 8 - int closes; - - /* - * This flag indicates that an EOF is pending on the outgoing side - * of the channel: that is, wherever we're getting the data for - * this channel has sent us some data followed by EOF. We can't - * actually send the EOF until we've finished sending the data, so - * we set this flag instead to remind us to do so once our buffer - * is clear. - */ - bool pending_eof; - - /* - * True if this channel is causing the underlying connection to be - * throttled. - */ - bool throttling_conn; - - /* - * True if we currently have backed-up data on the direction of - * this channel pointing out of the SSH connection, and therefore - * would prefer the 'Channel' implementation not to read further - * local input if possible. - */ - bool throttled_by_backlog; - - Channel *chan; /* handle the client side of this channel, if not */ - SshChannel sc; /* entry point for chan to talk back to */ -}; - -SshChannel *ssh1_session_open(ConnectionLayer *cl, Channel *chan); -void ssh1_channel_init(struct ssh1_channel *c); -void ssh1_channel_free(struct ssh1_channel *c); -struct ssh_rportfwd *ssh1_rportfwd_alloc( - ConnectionLayer *cl, - const char *shost, int sport, const char *dhost, int dport, - int addressfamily, const char *log_description, PortFwdRecord *pfr, - ssh_sharing_connstate *share_ctx); -SshChannel *ssh1_serverside_x11_open( - ConnectionLayer *cl, Channel *chan, const SocketPeerInfo *pi); -SshChannel *ssh1_serverside_agent_open(ConnectionLayer *cl, Channel *chan); - -void ssh1_connection_direction_specific_setup( - struct ssh1_connection_state *s); -bool ssh1_handle_direction_specific_packet( - struct ssh1_connection_state *s, PktIn *pktin); - -bool ssh1_check_termination(struct ssh1_connection_state *s); - -bool ssh1_connection_need_antispoof_prompt(struct ssh1_connection_state *s); diff --git a/ssh1login-server.c b/ssh1login-server.c deleted file mode 100644 index c9c10eef..00000000 --- a/ssh1login-server.c +++ /dev/null @@ -1,447 +0,0 @@ -/* - * Packet protocol layer for the SSH-1 login phase, from the server side. - */ - -#include - -#include "putty.h" -#include "mpint.h" -#include "ssh.h" -#include "sshbpp.h" -#include "sshppl.h" -#include "sshcr.h" -#include "sshserver.h" -#include "sshkeygen.h" - -struct ssh1_login_server_state { - int crState; - - PacketProtocolLayer *successor_layer; - - const SshServerConfig *ssc; - - int remote_protoflags; - int local_protoflags; - unsigned long supported_ciphers_mask, supported_auths_mask; - unsigned cipher_type; - - unsigned char cookie[8]; - unsigned char session_key[32]; - unsigned char session_id[16]; - char *username_str; - ptrlen username; - - RSAKey *servkey, *hostkey; - bool servkey_generated_here; - mp_int *sesskey; - - AuthPolicy *authpolicy; - unsigned ap_methods, current_method; - unsigned char auth_rsa_expected_response[16]; - RSAKey *authkey; - bool auth_successful; - - PacketProtocolLayer ppl; -}; - -static void ssh1_login_server_free(PacketProtocolLayer *); -static void ssh1_login_server_process_queue(PacketProtocolLayer *); - -static bool ssh1_login_server_get_specials( - PacketProtocolLayer *ppl, add_special_fn_t add_special, - void *ctx) { return false; } -static void ssh1_login_server_special_cmd(PacketProtocolLayer *ppl, - SessionSpecialCode code, int arg) {} -static bool ssh1_login_server_want_user_input( - PacketProtocolLayer *ppl) { return false; } -static void ssh1_login_server_got_user_input(PacketProtocolLayer *ppl) {} -static void ssh1_login_server_reconfigure( - PacketProtocolLayer *ppl, Conf *conf) {} - -static const PacketProtocolLayerVtable ssh1_login_server_vtable = { - .free = ssh1_login_server_free, - .process_queue = ssh1_login_server_process_queue, - .get_specials = ssh1_login_server_get_specials, - .special_cmd = ssh1_login_server_special_cmd, - .want_user_input = ssh1_login_server_want_user_input, - .got_user_input = ssh1_login_server_got_user_input, - .reconfigure = ssh1_login_server_reconfigure, - .queued_data_size = ssh_ppl_default_queued_data_size, - .name = NULL, /* no layer names in SSH-1 */ -}; - -PacketProtocolLayer *ssh1_login_server_new( - PacketProtocolLayer *successor_layer, RSAKey *hostkey, - AuthPolicy *authpolicy, const SshServerConfig *ssc) -{ - struct ssh1_login_server_state *s = snew(struct ssh1_login_server_state); - memset(s, 0, sizeof(*s)); - s->ppl.vt = &ssh1_login_server_vtable; - - s->ssc = ssc; - s->hostkey = hostkey; - s->authpolicy = authpolicy; - - s->successor_layer = successor_layer; - return &s->ppl; -} - -static void ssh1_login_server_free(PacketProtocolLayer *ppl) -{ - struct ssh1_login_server_state *s = - container_of(ppl, struct ssh1_login_server_state, ppl); - - if (s->successor_layer) - ssh_ppl_free(s->successor_layer); - - if (s->servkey_generated_here && s->servkey) { - freersakey(s->servkey); - sfree(s->servkey); - } - - smemclr(s->session_key, sizeof(s->session_key)); - sfree(s->username_str); - - sfree(s); -} - -static bool ssh1_login_server_filter_queue(struct ssh1_login_server_state *s) -{ - return ssh1_common_filter_queue(&s->ppl); -} - -static PktIn *ssh1_login_server_pop(struct ssh1_login_server_state *s) -{ - if (ssh1_login_server_filter_queue(s)) - return NULL; - return pq_pop(s->ppl.in_pq); -} - -static void ssh1_login_server_process_queue(PacketProtocolLayer *ppl) -{ - struct ssh1_login_server_state *s = - container_of(ppl, struct ssh1_login_server_state, ppl); - PktIn *pktin; - PktOut *pktout; - int i; - - /* Filter centrally handled messages off the front of the queue on - * every entry to this coroutine, no matter where we're resuming - * from, even if we're _not_ looping on pq_pop. That way we can - * still proactively handle those messages even if we're waiting - * for a user response. */ - if (ssh1_login_server_filter_queue(s)) - return; - - crBegin(s->crState); - - if (!s->servkey) { - int server_key_bits = s->hostkey->bytes - 256; - if (server_key_bits < 512) - server_key_bits = s->hostkey->bytes + 256; - s->servkey = snew(RSAKey); - - PrimeGenerationContext *pgc = primegen_new_context( - &primegen_probabilistic); - ProgressReceiver null_progress; - null_progress.vt = &null_progress_vt; - rsa_generate(s->servkey, server_key_bits, false, pgc, &null_progress); - primegen_free_context(pgc); - - s->servkey->comment = NULL; - s->servkey_generated_here = true; - } - - s->local_protoflags = SSH1_PROTOFLAGS_SUPPORTED; - s->supported_ciphers_mask = s->ssc->ssh1_cipher_mask; - s->supported_auths_mask = 0; - s->ap_methods = auth_methods(s->authpolicy); - if (s->ap_methods & AUTHMETHOD_PASSWORD) - s->supported_auths_mask |= (1U << SSH1_AUTH_PASSWORD); - if (s->ap_methods & AUTHMETHOD_PUBLICKEY) - s->supported_auths_mask |= (1U << SSH1_AUTH_RSA); - if (s->ap_methods & AUTHMETHOD_TIS) - s->supported_auths_mask |= (1U << SSH1_AUTH_TIS); - if (s->ap_methods & AUTHMETHOD_CRYPTOCARD) - s->supported_auths_mask |= (1U << SSH1_AUTH_CCARD); - - random_read(s->cookie, 8); - - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_SMSG_PUBLIC_KEY); - put_data(pktout, s->cookie, 8); - rsa_ssh1_public_blob(BinarySink_UPCAST(pktout), - s->servkey, RSA_SSH1_EXPONENT_FIRST); - rsa_ssh1_public_blob(BinarySink_UPCAST(pktout), - s->hostkey, RSA_SSH1_EXPONENT_FIRST); - put_uint32(pktout, s->local_protoflags); - put_uint32(pktout, s->supported_ciphers_mask); - put_uint32(pktout, s->supported_auths_mask); - pq_push(s->ppl.out_pq, pktout); - - crMaybeWaitUntilV((pktin = ssh1_login_server_pop(s)) != NULL); - if (pktin->type != SSH1_CMSG_SESSION_KEY) { - ssh_proto_error(s->ppl.ssh, "Received unexpected packet in response" - " to initial public key packet, type %d (%s)", - pktin->type, ssh1_pkt_type(pktin->type)); - return; - } - - { - ptrlen client_cookie; - s->cipher_type = get_byte(pktin); - client_cookie = get_data(pktin, 8); - s->sesskey = get_mp_ssh1(pktin); - s->remote_protoflags = get_uint32(pktin); - - if (get_err(pktin)) { - ssh_proto_error(s->ppl.ssh, "Unable to parse session key packet"); - return; - } - if (!ptrlen_eq_ptrlen(client_cookie, make_ptrlen(s->cookie, 8))) { - ssh_proto_error(s->ppl.ssh, - "Client sent incorrect anti-spoofing cookie"); - return; - } - } - if (s->cipher_type >= 32 || - !((s->supported_ciphers_mask >> s->cipher_type) & 1)) { - ssh_proto_error(s->ppl.ssh, "Client selected an unsupported cipher"); - return; - } - - { - RSAKey *smaller, *larger; - strbuf *data = strbuf_new_nm(); - - if (mp_get_nbits(s->hostkey->modulus) > - mp_get_nbits(s->servkey->modulus)) { - larger = s->hostkey; - smaller = s->servkey; - } else { - smaller = s->hostkey; - larger = s->servkey; - } - - if (rsa_ssh1_decrypt_pkcs1(s->sesskey, larger, data)) { - mp_free(s->sesskey); - s->sesskey = mp_from_bytes_be(ptrlen_from_strbuf(data)); - strbuf_clear(data); - if (rsa_ssh1_decrypt_pkcs1(s->sesskey, smaller, data) && - data->len == sizeof(s->session_key)) { - memcpy(s->session_key, data->u, sizeof(s->session_key)); - mp_free(s->sesskey); - s->sesskey = NULL; /* indicates success */ - } - } - - strbuf_free(data); - } - if (s->sesskey) { - ssh_proto_error(s->ppl.ssh, "Failed to decrypt session key"); - return; - } - - ssh1_compute_session_id(s->session_id, s->cookie, s->hostkey, s->servkey); - - for (i = 0; i < 16; i++) - s->session_key[i] ^= s->session_id[i]; - - { - const ssh_cipheralg *cipher = - (s->cipher_type == SSH1_CIPHER_BLOWFISH ? &ssh_blowfish_ssh1 : - s->cipher_type == SSH1_CIPHER_DES ? &ssh_des : &ssh_3des_ssh1); - ssh1_bpp_new_cipher(s->ppl.bpp, cipher, s->session_key); - } - - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_SMSG_SUCCESS); - pq_push(s->ppl.out_pq, pktout); - - crMaybeWaitUntilV((pktin = ssh1_login_server_pop(s)) != NULL); - if (pktin->type != SSH1_CMSG_USER) { - ssh_proto_error(s->ppl.ssh, "Received unexpected packet while " - "expecting username, type %d (%s)", - pktin->type, ssh1_pkt_type(pktin->type)); - return; - } - s->username = get_string(pktin); - s->username.ptr = s->username_str = mkstr(s->username); - ppl_logevent("Received username '%.*s'", PTRLEN_PRINTF(s->username)); - - s->auth_successful = auth_none(s->authpolicy, s->username); - while (1) { - /* Signal failed authentication */ - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_SMSG_FAILURE); - pq_push(s->ppl.out_pq, pktout); - - crMaybeWaitUntilV((pktin = ssh1_login_server_pop(s)) != NULL); - if (pktin->type == SSH1_CMSG_AUTH_PASSWORD) { - s->current_method = AUTHMETHOD_PASSWORD; - if (!(s->ap_methods & s->current_method)) - continue; - - ptrlen password = get_string(pktin); - - /* Tolerate historic traffic-analysis defence of NUL + - * garbage on the end of the binary password string */ - char *nul = memchr(password.ptr, '\0', password.len); - if (nul) - password.len = (const char *)nul - (const char *)password.ptr; - - if (auth_password(s->authpolicy, s->username, password, NULL)) - goto auth_success; - } else if (pktin->type == SSH1_CMSG_AUTH_RSA) { - s->current_method = AUTHMETHOD_PUBLICKEY; - if (!(s->ap_methods & s->current_method)) - continue; - - { - mp_int *modulus = get_mp_ssh1(pktin); - s->authkey = auth_publickey_ssh1( - s->authpolicy, s->username, modulus); - - if (!s->authkey && - s->ssc->stunt_pretend_to_accept_any_pubkey) { - mp_int *zero = mp_from_integer(0); - mp_int *fake_challenge = mp_random_in_range(zero, modulus); - - pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH1_SMSG_AUTH_RSA_CHALLENGE); - put_mp_ssh1(pktout, fake_challenge); - pq_push(s->ppl.out_pq, pktout); - - mp_free(zero); - mp_free(fake_challenge); - } - - mp_free(modulus); - } - - if (!s->authkey && - !s->ssc->stunt_pretend_to_accept_any_pubkey) - continue; - - if (s->authkey && s->authkey->bytes < 32) { - ppl_logevent("Auth key far too small"); - continue; - } - - if (s->authkey) { - unsigned char *rsabuf = - snewn(s->authkey->bytes, unsigned char); - - random_read(rsabuf, 32); - - { - ssh_hash *h = ssh_hash_new(&ssh_md5); - put_data(h, rsabuf, 32); - put_data(h, s->session_id, 16); - ssh_hash_final(h, s->auth_rsa_expected_response); - } - - if (!rsa_ssh1_encrypt(rsabuf, 32, s->authkey)) { - sfree(rsabuf); - ppl_logevent("Failed to encrypt auth challenge"); - continue; - } - - mp_int *bn = mp_from_bytes_be( - make_ptrlen(rsabuf, s->authkey->bytes)); - smemclr(rsabuf, s->authkey->bytes); - sfree(rsabuf); - - pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH1_SMSG_AUTH_RSA_CHALLENGE); - put_mp_ssh1(pktout, bn); - pq_push(s->ppl.out_pq, pktout); - - mp_free(bn); - } - - crMaybeWaitUntilV((pktin = ssh1_login_server_pop(s)) != NULL); - if (pktin->type != SSH1_CMSG_AUTH_RSA_RESPONSE) { - ssh_proto_error(s->ppl.ssh, "Received unexpected packet in " - "response to RSA auth challenge, type %d (%s)", - pktin->type, ssh1_pkt_type(pktin->type)); - return; - } - - if (!s->authkey) - continue; - - { - ptrlen response = get_data(pktin, 16); - ptrlen expected = make_ptrlen( - s->auth_rsa_expected_response, 16); - if (!ptrlen_eq_ptrlen(response, expected)) { - ppl_logevent("Wrong response to auth challenge"); - continue; - } - } - - goto auth_success; - } else if (pktin->type == SSH1_CMSG_AUTH_TIS || - pktin->type == SSH1_CMSG_AUTH_CCARD) { - char *challenge; - unsigned response_type; - ptrlen response; - - s->current_method = (pktin->type == SSH1_CMSG_AUTH_TIS ? - AUTHMETHOD_TIS : AUTHMETHOD_CRYPTOCARD); - if (!(s->ap_methods & s->current_method)) - continue; - - challenge = auth_ssh1int_challenge( - s->authpolicy, s->current_method, s->username); - if (!challenge) - continue; - pktout = ssh_bpp_new_pktout( - s->ppl.bpp, - (s->current_method == AUTHMETHOD_TIS ? - SSH1_SMSG_AUTH_TIS_CHALLENGE : - SSH1_SMSG_AUTH_CCARD_CHALLENGE)); - put_stringz(pktout, challenge); - pq_push(s->ppl.out_pq, pktout); - sfree(challenge); - - crMaybeWaitUntilV((pktin = ssh1_login_server_pop(s)) != NULL); - response_type = (s->current_method == AUTHMETHOD_TIS ? - SSH1_CMSG_AUTH_TIS_RESPONSE : - SSH1_CMSG_AUTH_CCARD_RESPONSE); - if (pktin->type != response_type) { - ssh_proto_error(s->ppl.ssh, "Received unexpected packet in " - "response to %s challenge, type %d (%s)", - (s->current_method == AUTHMETHOD_TIS ? - "TIS" : "CryptoCard"), - pktin->type, ssh1_pkt_type(pktin->type)); - return; - } - - response = get_string(pktin); - - if (auth_ssh1int_response(s->authpolicy, response)) - goto auth_success; - } - } - - auth_success: - if (!auth_successful(s->authpolicy, s->username, s->current_method)) { - ssh_sw_abort(s->ppl.ssh, "Multiple authentications required but SSH-1" - " cannot perform them"); - return; - } - - /* Signal successful authentication */ - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_SMSG_SUCCESS); - pq_push(s->ppl.out_pq, pktout); - - ssh1_connection_set_protoflags( - s->successor_layer, s->local_protoflags, s->remote_protoflags); - { - PacketProtocolLayer *successor = s->successor_layer; - s->successor_layer = NULL; /* avoid freeing it ourself */ - ssh_ppl_replace(&s->ppl, successor); - return; /* we've just freed s, so avoid even touching s->crState */ - } - - crFinishV; -} diff --git a/ssh1login.c b/ssh1login.c deleted file mode 100644 index 8486cbf0..00000000 --- a/ssh1login.c +++ /dev/null @@ -1,1242 +0,0 @@ -/* - * Packet protocol layer for the SSH-1 login phase (combining what - * SSH-2 would think of as key exchange and user authentication). - */ - -#include - -#include "putty.h" -#include "ssh.h" -#include "mpint.h" -#include "sshbpp.h" -#include "sshppl.h" -#include "sshcr.h" - -typedef struct agent_key { - RSAKey key; - strbuf *comment; - ptrlen blob; /* only used during initial parsing of agent response */ -} agent_key; - -struct ssh1_login_state { - int crState; - - PacketProtocolLayer *successor_layer; - - Conf *conf; - - char *savedhost; - int savedport; - bool try_agent_auth; - - int remote_protoflags; - int local_protoflags; - unsigned char session_key[32]; - char *username; - agent_pending_query *auth_agent_query; - - int len; - unsigned char *rsabuf; - unsigned long supported_ciphers_mask, supported_auths_mask; - bool tried_publickey, tried_agent; - bool tis_auth_refused, ccard_auth_refused; - unsigned char cookie[8]; - unsigned char session_id[16]; - int cipher_type; - strbuf *publickey_blob; - char *publickey_comment; - bool privatekey_available, privatekey_encrypted; - prompts_t *cur_prompt; - int userpass_ret; - char c; - int pwpkt_type; - void *agent_response_to_free; - ptrlen agent_response; - BinarySource asrc[1]; /* response from SSH agent */ - size_t agent_keys_len; - agent_key *agent_keys; - size_t agent_key_index, agent_key_limit; - bool authed; - RSAKey key; - int dlgret; - Filename *keyfile; - RSAKey servkey, hostkey; - bool want_user_input; - - StripCtrlChars *tis_scc; - bool tis_scc_initialised; - - PacketProtocolLayer ppl; -}; - -static void ssh1_login_free(PacketProtocolLayer *); -static void ssh1_login_process_queue(PacketProtocolLayer *); -static void ssh1_login_dialog_callback(void *, int); -static void ssh1_login_special_cmd(PacketProtocolLayer *ppl, - SessionSpecialCode code, int arg); -static bool ssh1_login_want_user_input(PacketProtocolLayer *ppl); -static void ssh1_login_got_user_input(PacketProtocolLayer *ppl); -static void ssh1_login_reconfigure(PacketProtocolLayer *ppl, Conf *conf); - -static const PacketProtocolLayerVtable ssh1_login_vtable = { - .free = ssh1_login_free, - .process_queue = ssh1_login_process_queue, - .get_specials = ssh1_common_get_specials, - .special_cmd = ssh1_login_special_cmd, - .want_user_input = ssh1_login_want_user_input, - .got_user_input = ssh1_login_got_user_input, - .reconfigure = ssh1_login_reconfigure, - .queued_data_size = ssh_ppl_default_queued_data_size, - .name = NULL, /* no layer names in SSH-1 */ -}; - -static void ssh1_login_agent_query(struct ssh1_login_state *s, strbuf *req); -static void ssh1_login_agent_callback(void *loginv, void *reply, int replylen); - -PacketProtocolLayer *ssh1_login_new( - Conf *conf, const char *host, int port, - PacketProtocolLayer *successor_layer) -{ - struct ssh1_login_state *s = snew(struct ssh1_login_state); - memset(s, 0, sizeof(*s)); - s->ppl.vt = &ssh1_login_vtable; - - s->conf = conf_copy(conf); - s->savedhost = dupstr(host); - s->savedport = port; - s->successor_layer = successor_layer; - return &s->ppl; -} - -static void ssh1_login_free(PacketProtocolLayer *ppl) -{ - struct ssh1_login_state *s = - container_of(ppl, struct ssh1_login_state, ppl); - - if (s->successor_layer) - ssh_ppl_free(s->successor_layer); - - conf_free(s->conf); - sfree(s->savedhost); - sfree(s->rsabuf); - sfree(s->username); - if (s->publickey_blob) - strbuf_free(s->publickey_blob); - sfree(s->publickey_comment); - if (s->cur_prompt) - free_prompts(s->cur_prompt); - if (s->agent_keys) { - for (size_t i = 0; i < s->agent_keys_len; i++) { - freersakey(&s->agent_keys[i].key); - strbuf_free(s->agent_keys[i].comment); - } - sfree(s->agent_keys); - } - sfree(s->agent_response_to_free); - if (s->auth_agent_query) - agent_cancel_query(s->auth_agent_query); - sfree(s); -} - -static bool ssh1_login_filter_queue(struct ssh1_login_state *s) -{ - return ssh1_common_filter_queue(&s->ppl); -} - -static PktIn *ssh1_login_pop(struct ssh1_login_state *s) -{ - if (ssh1_login_filter_queue(s)) - return NULL; - return pq_pop(s->ppl.in_pq); -} - -static void ssh1_login_setup_tis_scc(struct ssh1_login_state *s); - -static void ssh1_login_process_queue(PacketProtocolLayer *ppl) -{ - struct ssh1_login_state *s = - container_of(ppl, struct ssh1_login_state, ppl); - PktIn *pktin; - PktOut *pkt; - int i; - - /* Filter centrally handled messages off the front of the queue on - * every entry to this coroutine, no matter where we're resuming - * from, even if we're _not_ looping on pq_pop. That way we can - * still proactively handle those messages even if we're waiting - * for a user response. */ - if (ssh1_login_filter_queue(s)) - return; - - crBegin(s->crState); - - crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL); - - if (pktin->type != SSH1_SMSG_PUBLIC_KEY) { - ssh_proto_error(s->ppl.ssh, "Public key packet not received"); - return; - } - - ppl_logevent("Received public keys"); - - { - ptrlen pl = get_data(pktin, 8); - memcpy(s->cookie, pl.ptr, pl.len); - } - - get_rsa_ssh1_pub(pktin, &s->servkey, RSA_SSH1_EXPONENT_FIRST); - get_rsa_ssh1_pub(pktin, &s->hostkey, RSA_SSH1_EXPONENT_FIRST); - - s->hostkey.comment = NULL; /* avoid confusing rsa_ssh1_fingerprint */ - - /* - * Log the host key fingerprint. - */ - if (!get_err(pktin)) { - char *fingerprint = rsa_ssh1_fingerprint(&s->hostkey); - ppl_logevent("Host key fingerprint is:"); - ppl_logevent(" %s", fingerprint); - sfree(fingerprint); - } - - s->remote_protoflags = get_uint32(pktin); - s->supported_ciphers_mask = get_uint32(pktin); - s->supported_auths_mask = get_uint32(pktin); - - if (get_err(pktin)) { - ssh_proto_error(s->ppl.ssh, "Bad SSH-1 public key packet"); - return; - } - - if ((s->ppl.remote_bugs & BUG_CHOKES_ON_RSA)) - s->supported_auths_mask &= ~(1 << SSH1_AUTH_RSA); - - s->local_protoflags = - s->remote_protoflags & SSH1_PROTOFLAGS_SUPPORTED; - s->local_protoflags |= SSH1_PROTOFLAG_SCREEN_NUMBER; - - ssh1_compute_session_id(s->session_id, s->cookie, - &s->hostkey, &s->servkey); - - random_read(s->session_key, 32); - - /* - * Verify that the `bits' and `bytes' parameters match. - */ - if (s->hostkey.bits > s->hostkey.bytes * 8 || - s->servkey.bits > s->servkey.bytes * 8) { - ssh_proto_error(s->ppl.ssh, "SSH-1 public keys were badly formatted"); - return; - } - - s->len = 32; - if (s->len < s->hostkey.bytes) - s->len = s->hostkey.bytes; - if (s->len < s->servkey.bytes) - s->len = s->servkey.bytes; - - s->rsabuf = snewn(s->len, unsigned char); - - /* - * Verify the host key. - */ - { - /* - * First format the key into a string. - */ - char *keystr = rsastr_fmt(&s->hostkey); - char **fingerprints = rsa_ssh1_fake_all_fingerprints(&s->hostkey); - - /* First check against manually configured host keys. */ - s->dlgret = verify_ssh_manual_host_key(s->conf, fingerprints, NULL); - if (s->dlgret == 0) { /* did not match */ - ssh2_free_all_fingerprints(fingerprints); - sfree(keystr); - ssh_proto_error(s->ppl.ssh, "Host key did not appear in manually " - "configured list"); - return; - } else if (s->dlgret < 0) { /* none configured; use standard handling */ - char *keydisp = ssh1_pubkey_str(&s->hostkey); - s->dlgret = seat_verify_ssh_host_key( - s->ppl.seat, s->savedhost, s->savedport, "rsa", keystr, - keydisp, fingerprints, ssh1_login_dialog_callback, s); - sfree(keydisp); - ssh2_free_all_fingerprints(fingerprints); - sfree(keystr); -#ifdef FUZZING - s->dlgret = 1; -#endif - crMaybeWaitUntilV(s->dlgret >= 0); - - if (s->dlgret == 0) { - ssh_user_close(s->ppl.ssh, - "User aborted at host key verification"); - return; - } - } else { - ssh2_free_all_fingerprints(fingerprints); - sfree(keystr); - } - } - - for (i = 0; i < 32; i++) { - s->rsabuf[i] = s->session_key[i]; - if (i < 16) - s->rsabuf[i] ^= s->session_id[i]; - } - - { - RSAKey *smaller = (s->hostkey.bytes > s->servkey.bytes ? - &s->servkey : &s->hostkey); - RSAKey *larger = (s->hostkey.bytes > s->servkey.bytes ? - &s->hostkey : &s->servkey); - - if (!rsa_ssh1_encrypt(s->rsabuf, 32, smaller) || - !rsa_ssh1_encrypt(s->rsabuf, smaller->bytes, larger)) { - ssh_proto_error(s->ppl.ssh, "SSH-1 public key encryptions failed " - "due to bad formatting"); - return; - } - } - - ppl_logevent("Encrypted session key"); - - { - bool cipher_chosen = false, warn = false; - const char *cipher_string = NULL; - int i; - for (i = 0; !cipher_chosen && i < CIPHER_MAX; i++) { - int next_cipher = conf_get_int_int( - s->conf, CONF_ssh_cipherlist, i); - if (next_cipher == CIPHER_WARN) { - /* If/when we choose a cipher, warn about it */ - warn = true; - } else if (next_cipher == CIPHER_AES) { - /* XXX Probably don't need to mention this. */ - ppl_logevent("AES not supported in SSH-1, skipping"); - } else { - switch (next_cipher) { - case CIPHER_3DES: s->cipher_type = SSH1_CIPHER_3DES; - cipher_string = "3DES"; break; - case CIPHER_BLOWFISH: s->cipher_type = SSH1_CIPHER_BLOWFISH; - cipher_string = "Blowfish"; break; - case CIPHER_DES: s->cipher_type = SSH1_CIPHER_DES; - cipher_string = "single-DES"; break; - } - if (s->supported_ciphers_mask & (1 << s->cipher_type)) - cipher_chosen = true; - } - } - if (!cipher_chosen) { - if ((s->supported_ciphers_mask & (1 << SSH1_CIPHER_3DES)) == 0) { - ssh_proto_error(s->ppl.ssh, "Server violates SSH-1 protocol " - "by not supporting 3DES encryption"); - } else { - /* shouldn't happen */ - ssh_sw_abort(s->ppl.ssh, "No supported ciphers found"); - } - return; - } - - /* Warn about chosen cipher if necessary. */ - if (warn) { - s->dlgret = seat_confirm_weak_crypto_primitive( - s->ppl.seat, "cipher", cipher_string, - ssh1_login_dialog_callback, s); - crMaybeWaitUntilV(s->dlgret >= 0); - if (s->dlgret == 0) { - ssh_user_close(s->ppl.ssh, "User aborted at cipher warning"); - return; - } - } - } - - switch (s->cipher_type) { - case SSH1_CIPHER_3DES: - ppl_logevent("Using 3DES encryption"); - break; - case SSH1_CIPHER_DES: - ppl_logevent("Using single-DES encryption"); - break; - case SSH1_CIPHER_BLOWFISH: - ppl_logevent("Using Blowfish encryption"); - break; - } - - pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_SESSION_KEY); - put_byte(pkt, s->cipher_type); - put_data(pkt, s->cookie, 8); - put_uint16(pkt, s->len * 8); - put_data(pkt, s->rsabuf, s->len); - put_uint32(pkt, s->local_protoflags); - pq_push(s->ppl.out_pq, pkt); - - ppl_logevent("Trying to enable encryption..."); - - sfree(s->rsabuf); - s->rsabuf = NULL; - - /* - * Force the BPP to synchronously marshal all packets up to and - * including the SESSION_KEY into wire format, before we turn on - * crypto. - */ - ssh_bpp_handle_output(s->ppl.bpp); - - { - const ssh_cipheralg *cipher = - (s->cipher_type == SSH1_CIPHER_BLOWFISH ? &ssh_blowfish_ssh1 : - s->cipher_type == SSH1_CIPHER_DES ? &ssh_des : &ssh_3des_ssh1); - ssh1_bpp_new_cipher(s->ppl.bpp, cipher, s->session_key); - } - - freersakey(&s->servkey); - freersakey(&s->hostkey); - crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL); - - if (pktin->type != SSH1_SMSG_SUCCESS) { - ssh_proto_error(s->ppl.ssh, "Encryption not successfully enabled"); - return; - } - - ppl_logevent("Successfully started encryption"); - - if ((s->username = get_remote_username(s->conf)) == NULL) { - s->cur_prompt = new_prompts(); - s->cur_prompt->to_server = true; - s->cur_prompt->from_server = false; - s->cur_prompt->name = dupstr("SSH login name"); - add_prompt(s->cur_prompt, dupstr("login as: "), true); - s->userpass_ret = seat_get_userpass_input( - s->ppl.seat, s->cur_prompt, NULL); - while (1) { - while (s->userpass_ret < 0 && - bufchain_size(s->ppl.user_input) > 0) - s->userpass_ret = seat_get_userpass_input( - s->ppl.seat, s->cur_prompt, s->ppl.user_input); - - if (s->userpass_ret >= 0) - break; - - s->want_user_input = true; - crReturnV; - s->want_user_input = false; - } - if (!s->userpass_ret) { - /* - * Failed to get a username. Terminate. - */ - ssh_user_close(s->ppl.ssh, "No username provided"); - return; - } - s->username = prompt_get_result(s->cur_prompt->prompts[0]); - free_prompts(s->cur_prompt); - s->cur_prompt = NULL; - } - - pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_USER); - put_stringz(pkt, s->username); - pq_push(s->ppl.out_pq, pkt); - - ppl_logevent("Sent username \"%s\"", s->username); - if (seat_verbose(s->ppl.seat) || seat_interactive(s->ppl.seat)) - ppl_printf("Sent username \"%s\"\r\n", s->username); - - crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL); - - if (!(s->supported_auths_mask & (1 << SSH1_AUTH_RSA))) { - /* We must not attempt PK auth. Pretend we've already tried it. */ - s->tried_publickey = s->tried_agent = true; - } else { - s->tried_publickey = s->tried_agent = false; - } - s->tis_auth_refused = s->ccard_auth_refused = false; - - /* - * Load the public half of any configured keyfile for later use. - */ - s->keyfile = conf_get_filename(s->conf, CONF_keyfile); - if (!filename_is_null(s->keyfile)) { - int keytype; - ppl_logevent("Reading key file \"%s\"", filename_to_str(s->keyfile)); - keytype = key_type(s->keyfile); - if (keytype == SSH_KEYTYPE_SSH1 || - keytype == SSH_KEYTYPE_SSH1_PUBLIC) { - const char *error; - s->publickey_blob = strbuf_new(); - if (rsa1_loadpub_f(s->keyfile, - BinarySink_UPCAST(s->publickey_blob), - &s->publickey_comment, &error)) { - s->privatekey_available = (keytype == SSH_KEYTYPE_SSH1); - if (!s->privatekey_available) - ppl_logevent("Key file contains public key only"); - s->privatekey_encrypted = rsa1_encrypted_f(s->keyfile, NULL); - } else { - ppl_logevent("Unable to load key (%s)", error); - ppl_printf("Unable to load key file \"%s\" (%s)\r\n", - filename_to_str(s->keyfile), error); - - strbuf_free(s->publickey_blob); - s->publickey_blob = NULL; - } - } else { - ppl_logevent("Unable to use this key file (%s)", - key_type_to_str(keytype)); - ppl_printf("Unable to use key file \"%s\" (%s)\r\n", - filename_to_str(s->keyfile), - key_type_to_str(keytype)); - } - } - - /* Check whether we're configured to try Pageant, and also whether - * it's available. */ - s->try_agent_auth = (conf_get_bool(s->conf, CONF_tryagent) && - agent_exists()); - - while (pktin->type == SSH1_SMSG_FAILURE) { - s->pwpkt_type = SSH1_CMSG_AUTH_PASSWORD; - - if (s->try_agent_auth && !s->tried_agent) { - /* - * Attempt RSA authentication using Pageant. - */ - s->authed = false; - s->tried_agent = true; - ppl_logevent("Pageant is running. Requesting keys."); - - /* Request the keys held by the agent. */ - { - strbuf *request = strbuf_new_for_agent_query(); - put_byte(request, SSH1_AGENTC_REQUEST_RSA_IDENTITIES); - ssh1_login_agent_query(s, request); - strbuf_free(request); - crMaybeWaitUntilV(!s->auth_agent_query); - } - BinarySource_BARE_INIT_PL(s->asrc, s->agent_response); - - get_uint32(s->asrc); /* skip length field */ - if (get_byte(s->asrc) == SSH1_AGENT_RSA_IDENTITIES_ANSWER) { - size_t nkeys = get_uint32(s->asrc); - size_t origpos = s->asrc->pos; - - /* - * Check that the agent response is well formed. - */ - for (size_t i = 0; i < nkeys; i++) { - get_rsa_ssh1_pub(s->asrc, NULL, RSA_SSH1_EXPONENT_FIRST); - get_string(s->asrc); /* comment */ - if (get_err(s->asrc)) { - ppl_logevent("Pageant's response was truncated"); - goto parsed_agent_query; - } - } - - /* - * Copy the list of public-key blobs out of the Pageant - * response. - */ - BinarySource_REWIND_TO(s->asrc, origpos); - s->agent_keys_len = nkeys; - s->agent_keys = snewn(s->agent_keys_len, agent_key); - for (size_t i = 0; i < nkeys; i++) { - memset(&s->agent_keys[i].key, 0, - sizeof(s->agent_keys[i].key)); - - const char *blobstart = get_ptr(s->asrc); - get_rsa_ssh1_pub(s->asrc, &s->agent_keys[i].key, - RSA_SSH1_EXPONENT_FIRST); - const char *blobend = get_ptr(s->asrc); - - s->agent_keys[i].comment = strbuf_new(); - put_datapl(s->agent_keys[i].comment, get_string(s->asrc)); - - s->agent_keys[i].blob = make_ptrlen( - blobstart, blobend - blobstart); - } - - ppl_logevent("Pageant has %"SIZEu" SSH-1 keys", nkeys); - - if (s->publickey_blob) { - /* - * If we've been given a specific public key blob, - * filter the list of keys to try from the agent - * down to only that one, or none if it's not - * there. - */ - ptrlen our_blob = ptrlen_from_strbuf(s->publickey_blob); - size_t i; - - for (i = 0; i < nkeys; i++) { - if (ptrlen_eq_ptrlen(our_blob, s->agent_keys[i].blob)) - break; - } - - if (i < nkeys) { - ppl_logevent("Pageant key #%"SIZEu" matches " - "configured key file", i); - s->agent_key_index = i; - s->agent_key_limit = i+1; - } else { - ppl_logevent("Configured key file not in Pageant"); - s->agent_key_index = 0; - s->agent_key_limit = 0; - } - } else { - /* - * Otherwise, try them all. - */ - s->agent_key_index = 0; - s->agent_key_limit = nkeys; - } - } else { - ppl_logevent("Failed to get reply from Pageant"); - } - parsed_agent_query:; - - for (; s->agent_key_index < s->agent_key_limit; - s->agent_key_index++) { - ppl_logevent("Trying Pageant key #%"SIZEu, s->agent_key_index); - pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_AUTH_RSA); - put_mp_ssh1(pkt, - s->agent_keys[s->agent_key_index].key.modulus); - pq_push(s->ppl.out_pq, pkt); - crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) - != NULL); - if (pktin->type != SSH1_SMSG_AUTH_RSA_CHALLENGE) { - ppl_logevent("Key refused"); - continue; - } - ppl_logevent("Received RSA challenge"); - - { - mp_int *challenge = get_mp_ssh1(pktin); - if (get_err(pktin)) { - mp_free(challenge); - ssh_proto_error(s->ppl.ssh, "Server's RSA challenge " - "was badly formatted"); - return; - } - - strbuf *agentreq = strbuf_new_for_agent_query(); - put_byte(agentreq, SSH1_AGENTC_RSA_CHALLENGE); - - rsa_ssh1_public_blob( - BinarySink_UPCAST(agentreq), - &s->agent_keys[s->agent_key_index].key, - RSA_SSH1_EXPONENT_FIRST); - - put_mp_ssh1(agentreq, challenge); - mp_free(challenge); - - put_data(agentreq, s->session_id, 16); - put_uint32(agentreq, 1); /* response format */ - ssh1_login_agent_query(s, agentreq); - strbuf_free(agentreq); - crMaybeWaitUntilV(!s->auth_agent_query); - } - - { - const unsigned char *ret = s->agent_response.ptr; - if (ret) { - if (s->agent_response.len >= 5+16 && - ret[4] == SSH1_AGENT_RSA_RESPONSE) { - ppl_logevent("Sending Pageant's response"); - pkt = ssh_bpp_new_pktout( - s->ppl.bpp, SSH1_CMSG_AUTH_RSA_RESPONSE); - put_data(pkt, ret + 5, 16); - pq_push(s->ppl.out_pq, pkt); - crMaybeWaitUntilV( - (pktin = ssh1_login_pop(s)) - != NULL); - if (pktin->type == SSH1_SMSG_SUCCESS) { - ppl_logevent("Pageant's response " - "accepted"); - if (seat_verbose(s->ppl.seat)) { - ptrlen comment = ptrlen_from_strbuf( - s->agent_keys[s->agent_key_index]. - comment); - ppl_printf("Authenticated using RSA " - "key \"%.*s\" from " - "agent\r\n", - PTRLEN_PRINTF(comment)); - } - s->authed = true; - } else - ppl_logevent("Pageant's response not " - "accepted"); - } else { - ppl_logevent("Pageant failed to answer " - "challenge"); - sfree((char *)ret); - } - } else { - ppl_logevent("No reply received from Pageant"); - } - } - if (s->authed) - break; - } - if (s->authed) - break; - } - if (s->publickey_blob && s->privatekey_available && - !s->tried_publickey) { - /* - * Try public key authentication with the specified - * key file. - */ - bool got_passphrase; /* need not be kept over crReturn */ - if (seat_verbose(s->ppl.seat)) - ppl_printf("Trying public key authentication.\r\n"); - ppl_logevent("Trying public key \"%s\"", - filename_to_str(s->keyfile)); - s->tried_publickey = true; - got_passphrase = false; - while (!got_passphrase) { - /* - * Get a passphrase, if necessary. - */ - int retd; - char *passphrase = NULL; /* only written after crReturn */ - const char *error; - if (!s->privatekey_encrypted) { - if (seat_verbose(s->ppl.seat)) - ppl_printf("No passphrase required.\r\n"); - passphrase = NULL; - } else { - s->cur_prompt = new_prompts(); - s->cur_prompt->to_server = false; - s->cur_prompt->from_server = false; - s->cur_prompt->name = dupstr("SSH key passphrase"); - add_prompt(s->cur_prompt, - dupprintf("Passphrase for key \"%s\": ", - s->publickey_comment), false); - s->userpass_ret = seat_get_userpass_input( - s->ppl.seat, s->cur_prompt, NULL); - while (1) { - while (s->userpass_ret < 0 && - bufchain_size(s->ppl.user_input) > 0) - s->userpass_ret = seat_get_userpass_input( - s->ppl.seat, s->cur_prompt, s->ppl.user_input); - - if (s->userpass_ret >= 0) - break; - - s->want_user_input = true; - crReturnV; - s->want_user_input = false; - } - if (!s->userpass_ret) { - /* Failed to get a passphrase. Terminate. */ - ssh_user_close(s->ppl.ssh, - "User aborted at passphrase prompt"); - return; - } - passphrase = prompt_get_result(s->cur_prompt->prompts[0]); - free_prompts(s->cur_prompt); - s->cur_prompt = NULL; - } - /* - * Try decrypting key with passphrase. - */ - retd = rsa1_load_f(s->keyfile, &s->key, passphrase, &error); - if (passphrase) { - smemclr(passphrase, strlen(passphrase)); - sfree(passphrase); - } - if (retd == 1) { - /* Correct passphrase. */ - got_passphrase = true; - } else if (retd == 0) { - ppl_printf("Couldn't load private key from %s (%s).\r\n", - filename_to_str(s->keyfile), error); - got_passphrase = false; - break; /* go and try something else */ - } else if (retd == -1) { - ppl_printf("Wrong passphrase.\r\n"); - got_passphrase = false; - /* and try again */ - } else { - unreachable("unexpected return from rsa1_load_f()"); - } - } - - if (got_passphrase) { - - /* - * Send a public key attempt. - */ - pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_AUTH_RSA); - put_mp_ssh1(pkt, s->key.modulus); - pq_push(s->ppl.out_pq, pkt); - - crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) - != NULL); - if (pktin->type == SSH1_SMSG_FAILURE) { - ppl_printf("Server refused our public key.\r\n"); - continue; /* go and try something else */ - } - if (pktin->type != SSH1_SMSG_AUTH_RSA_CHALLENGE) { - ssh_proto_error(s->ppl.ssh, "Received unexpected packet" - " in response to offer of public key, " - "type %d (%s)", pktin->type, - ssh1_pkt_type(pktin->type)); - return; - } - - { - int i; - unsigned char buffer[32]; - mp_int *challenge, *response; - - challenge = get_mp_ssh1(pktin); - if (get_err(pktin)) { - mp_free(challenge); - ssh_proto_error(s->ppl.ssh, "Server's RSA challenge " - "was badly formatted"); - return; - } - response = rsa_ssh1_decrypt(challenge, &s->key); - freersapriv(&s->key); /* burn the evidence */ - - for (i = 0; i < 32; i++) { - buffer[i] = mp_get_byte(response, 31 - i); - } - - { - ssh_hash *h = ssh_hash_new(&ssh_md5); - put_data(h, buffer, 32); - put_data(h, s->session_id, 16); - ssh_hash_final(h, buffer); - } - - pkt = ssh_bpp_new_pktout( - s->ppl.bpp, SSH1_CMSG_AUTH_RSA_RESPONSE); - put_data(pkt, buffer, 16); - pq_push(s->ppl.out_pq, pkt); - - mp_free(challenge); - mp_free(response); - } - - crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) - != NULL); - if (pktin->type == SSH1_SMSG_FAILURE) { - if (seat_verbose(s->ppl.seat)) - ppl_printf("Failed to authenticate with" - " our public key.\r\n"); - continue; /* go and try something else */ - } else if (pktin->type != SSH1_SMSG_SUCCESS) { - ssh_proto_error(s->ppl.ssh, "Received unexpected packet" - " in response to RSA authentication, " - "type %d (%s)", pktin->type, - ssh1_pkt_type(pktin->type)); - return; - } - - break; /* we're through! */ - } - - } - - /* - * Otherwise, try various forms of password-like authentication. - */ - s->cur_prompt = new_prompts(); - - if (conf_get_bool(s->conf, CONF_try_tis_auth) && - (s->supported_auths_mask & (1 << SSH1_AUTH_TIS)) && - !s->tis_auth_refused) { - ssh1_login_setup_tis_scc(s); - s->pwpkt_type = SSH1_CMSG_AUTH_TIS_RESPONSE; - ppl_logevent("Requested TIS authentication"); - pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_AUTH_TIS); - pq_push(s->ppl.out_pq, pkt); - crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL); - if (pktin->type == SSH1_SMSG_FAILURE) { - ppl_logevent("TIS authentication declined"); - if (seat_interactive(s->ppl.seat)) - ppl_printf("TIS authentication refused.\r\n"); - s->tis_auth_refused = true; - continue; - } else if (pktin->type == SSH1_SMSG_AUTH_TIS_CHALLENGE) { - ptrlen challenge = get_string(pktin); - if (get_err(pktin)) { - ssh_proto_error(s->ppl.ssh, "TIS challenge packet was " - "badly formed"); - return; - } - ppl_logevent("Received TIS challenge"); - s->cur_prompt->to_server = true; - s->cur_prompt->from_server = true; - s->cur_prompt->name = dupstr("SSH TIS authentication"); - - strbuf *sb = strbuf_new(); - put_datapl(sb, PTRLEN_LITERAL("\ --- TIS authentication challenge from server: ---------------------------------\ -\r\n")); - if (s->tis_scc) { - stripctrl_retarget(s->tis_scc, BinarySink_UPCAST(sb)); - put_datapl(s->tis_scc, challenge); - stripctrl_retarget(s->tis_scc, NULL); - } else { - put_datapl(sb, challenge); - } - if (!ptrlen_endswith(challenge, PTRLEN_LITERAL("\n"), NULL)) - put_datapl(sb, PTRLEN_LITERAL("\r\n")); - put_datapl(sb, PTRLEN_LITERAL("\ --- End of TIS authentication challenge from server: --------------------------\ -\r\n")); - - s->cur_prompt->instruction = strbuf_to_str(sb); - s->cur_prompt->instr_reqd = true; - add_prompt(s->cur_prompt, dupstr( - "TIS authentication response: "), false); - } else { - ssh_proto_error(s->ppl.ssh, "Received unexpected packet" - " in response to TIS authentication, " - "type %d (%s)", pktin->type, - ssh1_pkt_type(pktin->type)); - return; - } - } else if (conf_get_bool(s->conf, CONF_try_tis_auth) && - (s->supported_auths_mask & (1 << SSH1_AUTH_CCARD)) && - !s->ccard_auth_refused) { - ssh1_login_setup_tis_scc(s); - s->pwpkt_type = SSH1_CMSG_AUTH_CCARD_RESPONSE; - ppl_logevent("Requested CryptoCard authentication"); - pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_AUTH_CCARD); - pq_push(s->ppl.out_pq, pkt); - crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL); - if (pktin->type == SSH1_SMSG_FAILURE) { - ppl_logevent("CryptoCard authentication declined"); - ppl_printf("CryptoCard authentication refused.\r\n"); - s->ccard_auth_refused = true; - continue; - } else if (pktin->type == SSH1_SMSG_AUTH_CCARD_CHALLENGE) { - ptrlen challenge = get_string(pktin); - if (get_err(pktin)) { - ssh_proto_error(s->ppl.ssh, "CryptoCard challenge packet " - "was badly formed"); - return; - } - ppl_logevent("Received CryptoCard challenge"); - s->cur_prompt->to_server = true; - s->cur_prompt->from_server = true; - s->cur_prompt->name = dupstr("SSH CryptoCard authentication"); - - strbuf *sb = strbuf_new(); - put_datapl(sb, PTRLEN_LITERAL("\ --- CryptoCard authentication challenge from server: --------------------------\ -\r\n")); - if (s->tis_scc) { - stripctrl_retarget(s->tis_scc, BinarySink_UPCAST(sb)); - put_datapl(s->tis_scc, challenge); - stripctrl_retarget(s->tis_scc, NULL); - } else { - put_datapl(sb, challenge); - } - if (!ptrlen_endswith(challenge, PTRLEN_LITERAL("\n"), NULL)) - put_datapl(sb, PTRLEN_LITERAL("\r\n")); - put_datapl(sb, PTRLEN_LITERAL("\ --- End of CryptoCard authentication challenge from server: -------------------\ -\r\n")); - - s->cur_prompt->instruction = strbuf_to_str(sb); - s->cur_prompt->instr_reqd = true; - add_prompt(s->cur_prompt, dupstr( - "CryptoCard authentication response: "), false); - } else { - ssh_proto_error(s->ppl.ssh, "Received unexpected packet" - " in response to TIS authentication, " - "type %d (%s)", pktin->type, - ssh1_pkt_type(pktin->type)); - return; - } - } - if (s->pwpkt_type == SSH1_CMSG_AUTH_PASSWORD) { - if ((s->supported_auths_mask & (1 << SSH1_AUTH_PASSWORD)) == 0) { - ssh_sw_abort(s->ppl.ssh, "No supported authentication methods " - "available"); - return; - } - s->cur_prompt->to_server = true; - s->cur_prompt->from_server = false; - s->cur_prompt->name = dupstr("SSH password"); - add_prompt(s->cur_prompt, dupprintf("%s@%s's password: ", - s->username, s->savedhost), - false); - } - - /* - * Show password prompt, having first obtained it via a TIS - * or CryptoCard exchange if we're doing TIS or CryptoCard - * authentication. - */ - s->userpass_ret = seat_get_userpass_input( - s->ppl.seat, s->cur_prompt, NULL); - while (1) { - while (s->userpass_ret < 0 && - bufchain_size(s->ppl.user_input) > 0) - s->userpass_ret = seat_get_userpass_input( - s->ppl.seat, s->cur_prompt, s->ppl.user_input); - - if (s->userpass_ret >= 0) - break; - - s->want_user_input = true; - crReturnV; - s->want_user_input = false; - } - if (!s->userpass_ret) { - /* - * Failed to get a password (for example - * because one was supplied on the command line - * which has already failed to work). Terminate. - */ - ssh_user_close(s->ppl.ssh, "User aborted at password prompt"); - return; - } - - 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 (!(s->ppl.remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE) && - !(s->ppl.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; - const char *pw = prompt_get_result_ref( - s->cur_prompt->prompts[0]); - - pwlen = strlen(pw); - if (pwlen < 16) { - bottom = 0; /* zero length passwords are OK! :-) */ - top = 15; - } else { - bottom = pwlen & ~7; - top = bottom + 7; - } - - assert(pwlen >= bottom && pwlen <= top); - - for (i = bottom; i <= top; i++) { - if (i == pwlen) { - pkt = ssh_bpp_new_pktout(s->ppl.bpp, s->pwpkt_type); - put_stringz(pkt, pw); - pq_push(s->ppl.out_pq, pkt); - } else { - strbuf *random_data = strbuf_new_nm(); - random_read(strbuf_append(random_data, i), i); - - pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_MSG_IGNORE); - put_stringsb(pkt, random_data); - pq_push(s->ppl.out_pq, pkt); - } - } - ppl_logevent("Sending password with camouflage packets"); - } - else if (!(s->ppl.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. - */ - strbuf *padded_pw = strbuf_new_nm(); - - ppl_logevent("Sending length-padded password"); - pkt = ssh_bpp_new_pktout(s->ppl.bpp, s->pwpkt_type); - put_asciz(padded_pw, prompt_get_result_ref( - s->cur_prompt->prompts[0])); - size_t pad = 63 & -padded_pw->len; - random_read(strbuf_append(padded_pw, pad), pad); - put_stringsb(pkt, padded_pw); - pq_push(s->ppl.out_pq, pkt); - } else { - /* - * The server is believed unable to cope with - * any of our password camouflage methods. - */ - ppl_logevent("Sending unpadded password"); - pkt = ssh_bpp_new_pktout(s->ppl.bpp, s->pwpkt_type); - put_stringz(pkt, prompt_get_result_ref( - s->cur_prompt->prompts[0])); - pq_push(s->ppl.out_pq, pkt); - } - } else { - pkt = ssh_bpp_new_pktout(s->ppl.bpp, s->pwpkt_type); - put_stringz(pkt, prompt_get_result_ref(s->cur_prompt->prompts[0])); - pq_push(s->ppl.out_pq, pkt); - } - ppl_logevent("Sent password"); - free_prompts(s->cur_prompt); - s->cur_prompt = NULL; - crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL); - if (pktin->type == SSH1_SMSG_FAILURE) { - if (seat_verbose(s->ppl.seat)) - ppl_printf("Access denied\r\n"); - ppl_logevent("Authentication refused"); - } else if (pktin->type != SSH1_SMSG_SUCCESS) { - ssh_proto_error(s->ppl.ssh, "Received unexpected packet" - " in response to password authentication, type %d " - "(%s)", pktin->type, ssh1_pkt_type(pktin->type)); - return; - } - } - - ppl_logevent("Authentication successful"); - - if (conf_get_bool(s->conf, CONF_compression)) { - ppl_logevent("Requesting compression"); - pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_REQUEST_COMPRESSION); - put_uint32(pkt, 6); /* gzip compression level */ - pq_push(s->ppl.out_pq, pkt); - crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL); - if (pktin->type == SSH1_SMSG_SUCCESS) { - /* - * We don't have to actually do anything here: the SSH-1 - * BPP will take care of automatically starting the - * compression, by recognising our outgoing request packet - * and the success response. (Horrible, but it's the - * easiest way to avoid race conditions if other packets - * cross in transit.) - */ - } else if (pktin->type == SSH1_SMSG_FAILURE) { - ppl_logevent("Server refused to enable compression"); - ppl_printf("Server refused to compress\r\n"); - } else { - ssh_proto_error(s->ppl.ssh, "Received unexpected packet" - " in response to compression request, type %d " - "(%s)", pktin->type, ssh1_pkt_type(pktin->type)); - return; - } - } - - ssh1_connection_set_protoflags( - s->successor_layer, s->local_protoflags, s->remote_protoflags); - { - PacketProtocolLayer *successor = s->successor_layer; - s->successor_layer = NULL; /* avoid freeing it ourself */ - ssh_ppl_replace(&s->ppl, successor); - return; /* we've just freed s, so avoid even touching s->crState */ - } - - crFinishV; -} - -static void ssh1_login_setup_tis_scc(struct ssh1_login_state *s) -{ - if (s->tis_scc_initialised) - return; - s->tis_scc = seat_stripctrl_new(s->ppl.seat, NULL, SIC_KI_PROMPTS); - if (s->tis_scc) - stripctrl_enable_line_limiting(s->tis_scc); - s->tis_scc_initialised = true; -} - -static void ssh1_login_dialog_callback(void *loginv, int ret) -{ - struct ssh1_login_state *s = (struct ssh1_login_state *)loginv; - s->dlgret = ret; - ssh_ppl_process_queue(&s->ppl); -} - -static void ssh1_login_agent_query(struct ssh1_login_state *s, strbuf *req) -{ - void *response; - int response_len; - - sfree(s->agent_response_to_free); - s->agent_response_to_free = NULL; - - s->auth_agent_query = agent_query(req, &response, &response_len, - ssh1_login_agent_callback, s); - if (!s->auth_agent_query) - ssh1_login_agent_callback(s, response, response_len); -} - -static void ssh1_login_agent_callback(void *loginv, void *reply, int replylen) -{ - struct ssh1_login_state *s = (struct ssh1_login_state *)loginv; - - s->auth_agent_query = NULL; - s->agent_response_to_free = reply; - s->agent_response = make_ptrlen(reply, replylen); - - queue_idempotent_callback(&s->ppl.ic_process_queue); -} - -static void ssh1_login_special_cmd(PacketProtocolLayer *ppl, - SessionSpecialCode code, int arg) -{ - struct ssh1_login_state *s = - container_of(ppl, struct ssh1_login_state, ppl); - PktOut *pktout; - - if (code == SS_PING || code == SS_NOP) { - if (!(s->ppl.remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE)) { - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_MSG_IGNORE); - put_stringz(pktout, ""); - pq_push(s->ppl.out_pq, pktout); - } - } -} - -static bool ssh1_login_want_user_input(PacketProtocolLayer *ppl) -{ - struct ssh1_login_state *s = - container_of(ppl, struct ssh1_login_state, ppl); - return s->want_user_input; -} - -static void ssh1_login_got_user_input(PacketProtocolLayer *ppl) -{ - struct ssh1_login_state *s = - container_of(ppl, struct ssh1_login_state, ppl); - if (s->want_user_input) - queue_idempotent_callback(&s->ppl.ic_process_queue); -} - -static void ssh1_login_reconfigure(PacketProtocolLayer *ppl, Conf *conf) -{ - struct ssh1_login_state *s = - container_of(ppl, struct ssh1_login_state, ppl); - ssh_ppl_reconfigure(s->successor_layer, conf); -} diff --git a/ssh2bpp-bare.c b/ssh2bpp-bare.c deleted file mode 100644 index 90f196e8..00000000 --- a/ssh2bpp-bare.c +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Trivial binary packet protocol for the 'bare' ssh-connection - * protocol used in PuTTY's SSH-2 connection sharing system. - */ - -#include - -#include "putty.h" -#include "ssh.h" -#include "sshbpp.h" -#include "sshcr.h" - -struct ssh2_bare_bpp_state { - int crState; - long packetlen, maxlen; - unsigned char *data; - unsigned long incoming_sequence, outgoing_sequence; - PktIn *pktin; - - BinaryPacketProtocol bpp; -}; - -static void ssh2_bare_bpp_free(BinaryPacketProtocol *bpp); -static void ssh2_bare_bpp_handle_input(BinaryPacketProtocol *bpp); -static void ssh2_bare_bpp_handle_output(BinaryPacketProtocol *bpp); -static PktOut *ssh2_bare_bpp_new_pktout(int type); - -static const BinaryPacketProtocolVtable ssh2_bare_bpp_vtable = { - .free = ssh2_bare_bpp_free, - .handle_input = ssh2_bare_bpp_handle_input, - .handle_output = ssh2_bare_bpp_handle_output, - .new_pktout = ssh2_bare_bpp_new_pktout, - .queue_disconnect = ssh2_bpp_queue_disconnect, /* in sshcommon.c */ - - /* packet size limit, per protocol spec in sshshare.c comment */ - .packet_size_limit = 0x4000, -}; - -BinaryPacketProtocol *ssh2_bare_bpp_new(LogContext *logctx) -{ - struct ssh2_bare_bpp_state *s = snew(struct ssh2_bare_bpp_state); - memset(s, 0, sizeof(*s)); - s->bpp.vt = &ssh2_bare_bpp_vtable; - s->bpp.logctx = logctx; - ssh_bpp_common_setup(&s->bpp); - return &s->bpp; -} - -static void ssh2_bare_bpp_free(BinaryPacketProtocol *bpp) -{ - struct ssh2_bare_bpp_state *s = - container_of(bpp, struct ssh2_bare_bpp_state, bpp); - sfree(s->pktin); - sfree(s); -} - -#define BPP_READ(ptr, len) do \ - { \ - bool success; \ - crMaybeWaitUntilV((success = bufchain_try_fetch_consume( \ - s->bpp.in_raw, ptr, len)) || \ - s->bpp.input_eof); \ - if (!success) \ - goto eof; \ - ssh_check_frozen(s->bpp.ssh); \ - } while (0) - -static void ssh2_bare_bpp_handle_input(BinaryPacketProtocol *bpp) -{ - struct ssh2_bare_bpp_state *s = - container_of(bpp, struct ssh2_bare_bpp_state, bpp); - - crBegin(s->crState); - - while (1) { - /* Read the length field. */ - { - unsigned char lenbuf[4]; - BPP_READ(lenbuf, 4); - s->packetlen = toint(GET_32BIT_MSB_FIRST(lenbuf)); - } - - if (s->packetlen <= 0 || s->packetlen >= (long)OUR_V2_PACKETLIMIT) { - ssh_sw_abort(s->bpp.ssh, "Invalid packet length received"); - crStopV; - } - - /* - * Allocate the packet to return, now we know its length. - */ - s->pktin = snew_plus(PktIn, s->packetlen); - s->pktin->qnode.prev = s->pktin->qnode.next = NULL; - s->pktin->qnode.on_free_queue = false; - s->maxlen = 0; - s->data = snew_plus_get_aux(s->pktin); - - s->pktin->sequence = s->incoming_sequence++; - - /* - * Read the remainder of the packet. - */ - BPP_READ(s->data, s->packetlen); - - /* - * The data we just read is precisely the initial type byte - * followed by the packet payload. - */ - s->pktin->type = s->data[0]; - s->data++; - s->packetlen--; - BinarySource_INIT(s->pktin, s->data, s->packetlen); - - if (s->pktin->type == SSH2_MSG_EXT_INFO) { - /* - * Mild layer violation: EXT_INFO is not permitted in the - * bare ssh-connection protocol. Faulting it here means - * that ssh2_common_filter_queue doesn't receive it in the - * first place unless it's legal to have sent it. - */ - ssh_proto_error(s->bpp.ssh, "Remote side sent SSH2_MSG_EXT_INFO " - "in bare connection protocol"); - return; - } - - /* - * Log incoming packet, possibly omitting sensitive fields. - */ - if (s->bpp.logctx) { - logblank_t blanks[MAX_BLANKS]; - int nblanks = ssh2_censor_packet( - s->bpp.pls, s->pktin->type, false, - make_ptrlen(s->data, s->packetlen), blanks); - log_packet(s->bpp.logctx, PKT_INCOMING, s->pktin->type, - ssh2_pkt_type(s->bpp.pls->kctx, s->bpp.pls->actx, - s->pktin->type), - get_ptr(s->pktin), get_avail(s->pktin), nblanks, blanks, - &s->pktin->sequence, 0, NULL); - } - - if (ssh2_bpp_check_unimplemented(&s->bpp, s->pktin)) { - sfree(s->pktin); - s->pktin = NULL; - continue; - } - - s->pktin->qnode.formal_size = get_avail(s->pktin); - pq_push(&s->bpp.in_pq, s->pktin); - s->pktin = NULL; - } - - eof: - if (!s->bpp.expect_close) { - ssh_remote_error(s->bpp.ssh, - "Remote side unexpectedly closed network connection"); - } else { - ssh_remote_eof(s->bpp.ssh, "Remote side closed network connection"); - } - return; /* avoid touching s now it's been freed */ - - crFinishV; -} - -static PktOut *ssh2_bare_bpp_new_pktout(int pkt_type) -{ - PktOut *pkt = ssh_new_packet(); - pkt->length = 4; /* space for packet length */ - pkt->type = pkt_type; - put_byte(pkt, pkt_type); - return pkt; -} - -static void ssh2_bare_bpp_format_packet(struct ssh2_bare_bpp_state *s, - PktOut *pkt) -{ - if (s->bpp.logctx) { - ptrlen pktdata = make_ptrlen(pkt->data + 5, pkt->length - 5); - logblank_t blanks[MAX_BLANKS]; - int nblanks = ssh2_censor_packet( - s->bpp.pls, pkt->type, true, pktdata, blanks); - log_packet(s->bpp.logctx, PKT_OUTGOING, pkt->type, - ssh2_pkt_type(s->bpp.pls->kctx, s->bpp.pls->actx, - pkt->type), - pktdata.ptr, pktdata.len, nblanks, blanks, - &s->outgoing_sequence, - pkt->downstream_id, pkt->additional_log_text); - } - - s->outgoing_sequence++; /* only for diagnostics, really */ - - PUT_32BIT_MSB_FIRST(pkt->data, pkt->length - 4); - bufchain_add(s->bpp.out_raw, pkt->data, pkt->length); -} - -static void ssh2_bare_bpp_handle_output(BinaryPacketProtocol *bpp) -{ - struct ssh2_bare_bpp_state *s = - container_of(bpp, struct ssh2_bare_bpp_state, bpp); - PktOut *pkt; - - while ((pkt = pq_pop(&s->bpp.out_pq)) != NULL) { - ssh2_bare_bpp_format_packet(s, pkt); - ssh_free_pktout(pkt); - } -} diff --git a/ssh2bpp.c b/ssh2bpp.c deleted file mode 100644 index 09b23e5a..00000000 --- a/ssh2bpp.c +++ /dev/null @@ -1,982 +0,0 @@ -/* - * Binary packet protocol for SSH-2. - */ - -#include - -#include "putty.h" -#include "ssh.h" -#include "sshbpp.h" -#include "sshcr.h" - -struct ssh2_bpp_direction { - unsigned long sequence; - ssh_cipher *cipher; - ssh2_mac *mac; - bool etm_mode; - const ssh_compression_alg *pending_compression; -}; - -struct ssh2_bpp_state { - int crState; - long len, pad, payload, packetlen, maclen, length, maxlen; - unsigned char *buf; - size_t bufsize; - unsigned char *data; - unsigned cipherblk; - PktIn *pktin; - struct DataTransferStats *stats; - bool cbc_ignore_workaround; - - struct ssh2_bpp_direction in, out; - /* comp and decomp logically belong in the per-direction - * substructure, except that they have different types */ - ssh_decompressor *in_decomp; - ssh_compressor *out_comp; - - bool is_server; - bool pending_newkeys; - bool pending_compression, seen_userauth_success; - bool enforce_next_packet_is_userauth_success; - unsigned nnewkeys; - int prev_type; - - BinaryPacketProtocol bpp; -}; - -static void ssh2_bpp_free(BinaryPacketProtocol *bpp); -static void ssh2_bpp_handle_input(BinaryPacketProtocol *bpp); -static void ssh2_bpp_handle_output(BinaryPacketProtocol *bpp); -static PktOut *ssh2_bpp_new_pktout(int type); - -static const BinaryPacketProtocolVtable ssh2_bpp_vtable = { - .free = ssh2_bpp_free, - .handle_input = ssh2_bpp_handle_input, - .handle_output = ssh2_bpp_handle_output, - .new_pktout = ssh2_bpp_new_pktout, - .queue_disconnect = ssh2_bpp_queue_disconnect, /* in sshcommon.c */ - .packet_size_limit = 0xFFFFFFFF, /* no special limit for this bpp */ -}; - -BinaryPacketProtocol *ssh2_bpp_new( - LogContext *logctx, struct DataTransferStats *stats, bool is_server) -{ - struct ssh2_bpp_state *s = snew(struct ssh2_bpp_state); - memset(s, 0, sizeof(*s)); - s->bpp.vt = &ssh2_bpp_vtable; - s->bpp.logctx = logctx; - s->stats = stats; - s->is_server = is_server; - ssh_bpp_common_setup(&s->bpp); - return &s->bpp; -} - -static void ssh2_bpp_free_outgoing_crypto(struct ssh2_bpp_state *s) -{ - /* - * We must free the MAC before the cipher, because sometimes the - * MAC is not actually separately allocated but just a different - * facet of the same object as the cipher, in which case - * ssh2_mac_free does nothing and ssh_cipher_free does the actual - * freeing. So if we freed the cipher first and then tried to - * dereference the MAC's vtable pointer to find out how to free - * that too, we'd be accessing freed memory. - */ - if (s->out.mac) - ssh2_mac_free(s->out.mac); - if (s->out.cipher) - ssh_cipher_free(s->out.cipher); - if (s->out_comp) - ssh_compressor_free(s->out_comp); -} - -static void ssh2_bpp_free_incoming_crypto(struct ssh2_bpp_state *s) -{ - /* As above, take care to free in.mac before in.cipher */ - if (s->in.mac) - ssh2_mac_free(s->in.mac); - if (s->in.cipher) - ssh_cipher_free(s->in.cipher); - if (s->in_decomp) - ssh_decompressor_free(s->in_decomp); -} - -static void ssh2_bpp_free(BinaryPacketProtocol *bpp) -{ - struct ssh2_bpp_state *s = container_of(bpp, struct ssh2_bpp_state, bpp); - sfree(s->buf); - ssh2_bpp_free_outgoing_crypto(s); - ssh2_bpp_free_incoming_crypto(s); - sfree(s->pktin); - sfree(s); -} - -void ssh2_bpp_new_outgoing_crypto( - BinaryPacketProtocol *bpp, - const ssh_cipheralg *cipher, const void *ckey, const void *iv, - const ssh2_macalg *mac, bool etm_mode, const void *mac_key, - const ssh_compression_alg *compression, bool delayed_compression) -{ - struct ssh2_bpp_state *s; - assert(bpp->vt == &ssh2_bpp_vtable); - s = container_of(bpp, struct ssh2_bpp_state, bpp); - - ssh2_bpp_free_outgoing_crypto(s); - - if (cipher) { - s->out.cipher = ssh_cipher_new(cipher); - ssh_cipher_setkey(s->out.cipher, ckey); - ssh_cipher_setiv(s->out.cipher, iv); - - s->cbc_ignore_workaround = ( - (ssh_cipher_alg(s->out.cipher)->flags & SSH_CIPHER_IS_CBC) && - !(s->bpp.remote_bugs & BUG_CHOKES_ON_SSH2_IGNORE)); - - bpp_logevent("Initialised %s outbound encryption", - ssh_cipher_alg(s->out.cipher)->text_name); - } else { - s->out.cipher = NULL; - s->cbc_ignore_workaround = false; - } - s->out.etm_mode = etm_mode; - if (mac) { - s->out.mac = ssh2_mac_new(mac, s->out.cipher); - ssh2_mac_setkey(s->out.mac, make_ptrlen(mac_key, mac->keylen)); - - bpp_logevent("Initialised %s outbound MAC algorithm%s%s", - ssh2_mac_text_name(s->out.mac), - etm_mode ? " (in ETM mode)" : "", - (s->out.cipher && - ssh_cipher_alg(s->out.cipher)->required_mac ? - " (required by cipher)" : "")); - } else { - s->out.mac = NULL; - } - - if (delayed_compression && !s->seen_userauth_success) { - s->out.pending_compression = compression; - s->out_comp = NULL; - - bpp_logevent("Will enable %s compression after user authentication", - s->out.pending_compression->text_name); - } else { - s->out.pending_compression = NULL; - - /* 'compression' is always non-NULL, because no compression is - * indicated by ssh_comp_none. But this setup call may return a - * null out_comp. */ - s->out_comp = ssh_compressor_new(compression); - - if (s->out_comp) - bpp_logevent("Initialised %s compression", - ssh_compressor_alg(s->out_comp)->text_name); - } -} - -void ssh2_bpp_new_incoming_crypto( - BinaryPacketProtocol *bpp, - const ssh_cipheralg *cipher, const void *ckey, const void *iv, - const ssh2_macalg *mac, bool etm_mode, const void *mac_key, - const ssh_compression_alg *compression, bool delayed_compression) -{ - struct ssh2_bpp_state *s; - assert(bpp->vt == &ssh2_bpp_vtable); - s = container_of(bpp, struct ssh2_bpp_state, bpp); - - ssh2_bpp_free_incoming_crypto(s); - - if (cipher) { - s->in.cipher = ssh_cipher_new(cipher); - ssh_cipher_setkey(s->in.cipher, ckey); - ssh_cipher_setiv(s->in.cipher, iv); - - bpp_logevent("Initialised %s inbound encryption", - ssh_cipher_alg(s->in.cipher)->text_name); - } else { - s->in.cipher = NULL; - } - s->in.etm_mode = etm_mode; - if (mac) { - s->in.mac = ssh2_mac_new(mac, s->in.cipher); - ssh2_mac_setkey(s->in.mac, make_ptrlen(mac_key, mac->keylen)); - - bpp_logevent("Initialised %s inbound MAC algorithm%s%s", - ssh2_mac_text_name(s->in.mac), - etm_mode ? " (in ETM mode)" : "", - (s->in.cipher && - ssh_cipher_alg(s->in.cipher)->required_mac ? - " (required by cipher)" : "")); - } else { - s->in.mac = NULL; - } - - if (delayed_compression && !s->seen_userauth_success) { - s->in.pending_compression = compression; - s->in_decomp = NULL; - - bpp_logevent("Will enable %s decompression after user authentication", - s->in.pending_compression->text_name); - } else { - s->in.pending_compression = NULL; - - /* 'compression' is always non-NULL, because no compression is - * indicated by ssh_comp_none. But this setup call may return a - * null in_decomp. */ - s->in_decomp = ssh_decompressor_new(compression); - - if (s->in_decomp) - bpp_logevent("Initialised %s decompression", - ssh_decompressor_alg(s->in_decomp)->text_name); - } - - /* Clear the pending_newkeys flag, so that handle_input below will - * start consuming the input data again. */ - s->pending_newkeys = false; - - /* And schedule a run of handle_input, in case there's already - * input data in the queue. */ - queue_idempotent_callback(&s->bpp.ic_in_raw); -} - -bool ssh2_bpp_rekey_inadvisable(BinaryPacketProtocol *bpp) -{ - struct ssh2_bpp_state *s; - assert(bpp->vt == &ssh2_bpp_vtable); - s = container_of(bpp, struct ssh2_bpp_state, bpp); - - return s->pending_compression; -} - -static void ssh2_bpp_enable_pending_compression(struct ssh2_bpp_state *s) -{ - BinaryPacketProtocol *bpp = &s->bpp; /* for bpp_logevent */ - - if (s->in.pending_compression) { - s->in_decomp = ssh_decompressor_new(s->in.pending_compression); - bpp_logevent("Initialised delayed %s decompression", - ssh_decompressor_alg(s->in_decomp)->text_name); - s->in.pending_compression = NULL; - } - if (s->out.pending_compression) { - s->out_comp = ssh_compressor_new(s->out.pending_compression); - bpp_logevent("Initialised delayed %s compression", - ssh_compressor_alg(s->out_comp)->text_name); - s->out.pending_compression = NULL; - } -} - -#define BPP_READ(ptr, len) do \ - { \ - bool success; \ - crMaybeWaitUntilV((success = bufchain_try_fetch_consume( \ - s->bpp.in_raw, ptr, len)) || \ - s->bpp.input_eof); \ - if (!success) \ - goto eof; \ - ssh_check_frozen(s->bpp.ssh); \ - } while (0) - -#define userauth_range(pkttype) ((unsigned)((pkttype) - 50) < 20) - -static void ssh2_bpp_handle_input(BinaryPacketProtocol *bpp) -{ - struct ssh2_bpp_state *s = container_of(bpp, struct ssh2_bpp_state, bpp); - - crBegin(s->crState); - - while (1) { - s->maxlen = 0; - s->length = 0; - if (s->in.cipher) - s->cipherblk = ssh_cipher_alg(s->in.cipher)->blksize; - else - s->cipherblk = 8; - if (s->cipherblk < 8) - s->cipherblk = 8; - s->maclen = s->in.mac ? ssh2_mac_alg(s->in.mac)->len : 0; - - if (s->in.cipher && - (ssh_cipher_alg(s->in.cipher)->flags & SSH_CIPHER_IS_CBC) && - s->in.mac && !s->in.etm_mode) { - /* - * 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. - * - * This defence is unnecessary in OpenSSH ETM mode, because - * the whole point of ETM mode is that the attacker can't - * tweak the ciphertext stream at all without the MAC - * detecting it before we decrypt anything. - */ - - /* - * Make sure we have buffer space for a maximum-size packet. - */ - unsigned buflimit = OUR_V2_PACKETLIMIT + s->maclen; - if (s->bufsize < buflimit) { - s->bufsize = buflimit; - s->buf = sresize(s->buf, s->bufsize, unsigned char); - } - - /* Read an amount corresponding to the MAC. */ - BPP_READ(s->buf, s->maclen); - - s->packetlen = 0; - ssh2_mac_start(s->in.mac); - put_uint32(s->in.mac, s->in.sequence); - - for (;;) { /* Once around this loop per cipher block. */ - /* Read another cipher-block's worth, and tack it on to - * the end. */ - BPP_READ(s->buf + (s->packetlen + s->maclen), s->cipherblk); - /* Decrypt one more block (a little further back in - * the stream). */ - ssh_cipher_decrypt(s->in.cipher, - s->buf + s->packetlen, s->cipherblk); - - /* Feed that block to the MAC. */ - put_data(s->in.mac, - s->buf + s->packetlen, s->cipherblk); - s->packetlen += s->cipherblk; - - /* See if that gives us a valid packet. */ - if (ssh2_mac_verresult(s->in.mac, s->buf + s->packetlen) && - ((s->len = toint(GET_32BIT_MSB_FIRST(s->buf))) == - s->packetlen-4)) - break; - if (s->packetlen >= (long)OUR_V2_PACKETLIMIT) { - ssh_sw_abort(s->bpp.ssh, - "No valid incoming packet found"); - crStopV; - } - } - s->maxlen = s->packetlen + s->maclen; - - /* - * Now transfer the data into an output packet. - */ - s->pktin = snew_plus(PktIn, s->maxlen); - s->pktin->qnode.prev = s->pktin->qnode.next = NULL; - s->pktin->type = 0; - s->pktin->qnode.on_free_queue = false; - s->data = snew_plus_get_aux(s->pktin); - memcpy(s->data, s->buf, s->maxlen); - } else if (s->in.mac && s->in.etm_mode) { - if (s->bufsize < 4) { - s->bufsize = 4; - s->buf = sresize(s->buf, s->bufsize, unsigned char); - } - - /* - * OpenSSH encrypt-then-MAC mode: the packet length is - * unencrypted, unless the cipher supports length encryption. - */ - BPP_READ(s->buf, 4); - - /* Cipher supports length decryption, so do it */ - if (s->in.cipher && (ssh_cipher_alg(s->in.cipher)->flags & - SSH_CIPHER_SEPARATE_LENGTH)) { - /* Keep the packet the same though, so the MAC passes */ - unsigned char len[4]; - memcpy(len, s->buf, 4); - ssh_cipher_decrypt_length( - s->in.cipher, len, 4, s->in.sequence); - s->len = toint(GET_32BIT_MSB_FIRST(len)); - } else { - s->len = toint(GET_32BIT_MSB_FIRST(s->buf)); - } - - /* - * _Completely_ silly lengths should be stomped on before they - * do us any more damage. - */ - if (s->len < 0 || s->len > (long)OUR_V2_PACKETLIMIT || - s->len % s->cipherblk != 0) { - ssh_sw_abort(s->bpp.ssh, - "Incoming packet length field was garbled"); - crStopV; - } - - /* - * So now we can work out the total packet length. - */ - s->packetlen = s->len + 4; - - /* - * Allocate the packet to return, now we know its length. - */ - s->pktin = snew_plus(PktIn, OUR_V2_PACKETLIMIT + s->maclen); - s->pktin->qnode.prev = s->pktin->qnode.next = NULL; - s->pktin->type = 0; - s->pktin->qnode.on_free_queue = false; - s->data = snew_plus_get_aux(s->pktin); - memcpy(s->data, s->buf, 4); - - /* - * Read the remainder of the packet. - */ - BPP_READ(s->data + 4, s->packetlen + s->maclen - 4); - - /* - * Check the MAC. - */ - if (s->in.mac && !ssh2_mac_verify( - s->in.mac, s->data, s->len + 4, s->in.sequence)) { - ssh_sw_abort(s->bpp.ssh, "Incorrect MAC received on packet"); - crStopV; - } - - /* Decrypt everything between the length field and the MAC. */ - if (s->in.cipher) - ssh_cipher_decrypt( - s->in.cipher, s->data + 4, s->packetlen - 4); - } else { - if (s->bufsize < s->cipherblk) { - s->bufsize = s->cipherblk; - s->buf = sresize(s->buf, s->bufsize, unsigned char); - } - - /* - * Acquire and decrypt the first block of the packet. This will - * contain the length and padding details. - */ - BPP_READ(s->buf, s->cipherblk); - - if (s->in.cipher) - ssh_cipher_decrypt(s->in.cipher, s->buf, s->cipherblk); - - /* - * Now get the length figure. - */ - s->len = toint(GET_32BIT_MSB_FIRST(s->buf)); - - /* - * _Completely_ silly lengths should be stomped on before they - * do us any more damage. - */ - if (s->len < 0 || s->len > (long)OUR_V2_PACKETLIMIT || - (s->len + 4) % s->cipherblk != 0) { - ssh_sw_abort(s->bpp.ssh, - "Incoming packet was garbled on decryption"); - crStopV; - } - - /* - * So now we can work out the total packet length. - */ - s->packetlen = s->len + 4; - - /* - * Allocate the packet to return, now we know its length. - */ - s->maxlen = s->packetlen + s->maclen; - s->pktin = snew_plus(PktIn, s->maxlen); - s->pktin->qnode.prev = s->pktin->qnode.next = NULL; - s->pktin->type = 0; - s->pktin->qnode.on_free_queue = false; - s->data = snew_plus_get_aux(s->pktin); - memcpy(s->data, s->buf, s->cipherblk); - - /* - * Read and decrypt the remainder of the packet. - */ - BPP_READ(s->data + s->cipherblk, - s->packetlen + s->maclen - s->cipherblk); - - /* Decrypt everything _except_ the MAC. */ - if (s->in.cipher) - ssh_cipher_decrypt( - s->in.cipher, - s->data + s->cipherblk, s->packetlen - s->cipherblk); - - /* - * Check the MAC. - */ - if (s->in.mac && !ssh2_mac_verify( - s->in.mac, s->data, s->len + 4, s->in.sequence)) { - ssh_sw_abort(s->bpp.ssh, "Incorrect MAC received on packet"); - crStopV; - } - } - /* Get and sanity-check the amount of random padding. */ - s->pad = s->data[4]; - if (s->pad < 4 || s->len - s->pad < 1) { - ssh_sw_abort(s->bpp.ssh, - "Invalid padding length on received packet"); - crStopV; - } - /* - * This enables us to deduce the payload length. - */ - s->payload = s->len - s->pad - 1; - - s->length = s->payload + 5; - - dts_consume(&s->stats->in, s->packetlen); - - s->pktin->sequence = s->in.sequence++; - - s->length = s->packetlen - s->pad; - assert(s->length >= 0); - - /* - * Decompress packet payload. - */ - { - unsigned char *newpayload; - int newlen; - if (s->in_decomp && ssh_decompressor_decompress( - s->in_decomp, s->data + 5, s->length - 5, - &newpayload, &newlen)) { - if (s->maxlen < newlen + 5) { - PktIn *old_pktin = s->pktin; - - s->maxlen = newlen + 5; - s->pktin = snew_plus(PktIn, s->maxlen); - *s->pktin = *old_pktin; /* structure copy */ - s->data = snew_plus_get_aux(s->pktin); - - smemclr(old_pktin, s->packetlen + s->maclen); - sfree(old_pktin); - } - s->length = 5 + newlen; - memcpy(s->data + 5, newpayload, newlen); - sfree(newpayload); - } - } - - /* - * Now we can identify the semantic content of the packet, - * and also the initial type byte. - */ - if (s->length <= 5) { /* == 5 we hope, but robustness */ - /* - * RFC 4253 doesn't explicitly say that completely empty - * packets with no type byte are forbidden. We handle them - * here by giving them a type code larger than 0xFF, which - * will be picked up at the next layer and trigger - * SSH_MSG_UNIMPLEMENTED. - */ - s->pktin->type = SSH_MSG_NO_TYPE_CODE; - s->data += 5; - s->length = 0; - } else { - s->pktin->type = s->data[5]; - s->data += 6; - s->length -= 6; - } - BinarySource_INIT(s->pktin, s->data, s->length); - - if (s->bpp.logctx) { - logblank_t blanks[MAX_BLANKS]; - int nblanks = ssh2_censor_packet( - s->bpp.pls, s->pktin->type, false, - make_ptrlen(s->data, s->length), blanks); - log_packet(s->bpp.logctx, PKT_INCOMING, s->pktin->type, - ssh2_pkt_type(s->bpp.pls->kctx, s->bpp.pls->actx, - s->pktin->type), - s->data, s->length, nblanks, blanks, - &s->pktin->sequence, 0, NULL); - } - - if (ssh2_bpp_check_unimplemented(&s->bpp, s->pktin)) { - sfree(s->pktin); - s->pktin = NULL; - continue; - } - - s->pktin->qnode.formal_size = get_avail(s->pktin); - pq_push(&s->bpp.in_pq, s->pktin); - - { - int type = s->pktin->type; - int prev_type = s->prev_type; - s->prev_type = type; - s->pktin = NULL; - - if (s->enforce_next_packet_is_userauth_success) { - /* See EXT_INFO handler below */ - if (type != SSH2_MSG_USERAUTH_SUCCESS) { - ssh_proto_error(s->bpp.ssh, - "Remote side sent SSH2_MSG_EXT_INFO " - "not either preceded by NEWKEYS or " - "followed by USERAUTH_SUCCESS"); - return; - } - s->enforce_next_packet_is_userauth_success = false; - } - - if (type == SSH2_MSG_NEWKEYS) { - if (s->nnewkeys < 2) - s->nnewkeys++; - /* - * Mild layer violation: in this situation we must - * suspend processing of the input byte stream until - * the transport layer has initialised the new keys by - * calling ssh2_bpp_new_incoming_crypto above. - */ - s->pending_newkeys = true; - crWaitUntilV(!s->pending_newkeys); - continue; - } - - if (type == SSH2_MSG_USERAUTH_SUCCESS && !s->is_server) { - /* - * Another one: if we were configured with OpenSSH's - * deferred compression which is triggered on receipt - * of USERAUTH_SUCCESS, then this is the moment to - * turn on compression. - */ - ssh2_bpp_enable_pending_compression(s); - - /* - * Whether or not we were doing delayed compression in - * _this_ set of crypto parameters, we should set a - * flag indicating that we're now authenticated, so - * that a delayed compression method enabled in any - * future rekey will be treated as un-delayed. - */ - s->seen_userauth_success = true; - } - - if (type == SSH2_MSG_EXT_INFO) { - /* - * And another: enforce that an incoming EXT_INFO is - * either the message immediately after the initial - * NEWKEYS, or (if we're the client) the one - * immediately before USERAUTH_SUCCESS. - */ - if (prev_type == SSH2_MSG_NEWKEYS && s->nnewkeys == 1) { - /* OK - this is right after the first NEWKEYS. */ - } else if (s->is_server) { - /* We're the server, so they're the client. - * Clients may not send EXT_INFO at _any_ other - * time. */ - ssh_proto_error(s->bpp.ssh, - "Remote side sent SSH2_MSG_EXT_INFO " - "that was not immediately after the " - "initial NEWKEYS"); - return; - } else if (s->nnewkeys > 0 && s->seen_userauth_success) { - /* We're the client, so they're the server. In - * that case they may also send EXT_INFO - * immediately before USERAUTH_SUCCESS. Error out - * immediately if this can't _possibly_ be that - * moment (because we haven't even seen NEWKEYS - * yet, or because we've already seen - * USERAUTH_SUCCESS). */ - ssh_proto_error(s->bpp.ssh, - "Remote side sent SSH2_MSG_EXT_INFO " - "after USERAUTH_SUCCESS"); - return; - } else { - /* This _could_ be OK, provided the next packet is - * USERAUTH_SUCCESS. Set a flag to remember to - * fault it if not. */ - s->enforce_next_packet_is_userauth_success = true; - } - } - - if (s->pending_compression && userauth_range(type)) { - /* - * Receiving any userauth message at all indicates - * that we're not about to turn on delayed compression - * - either because we just _have_ done, or because - * this message is a USERAUTH_FAILURE or some kind of - * intermediate 'please send more data' continuation - * message. Either way, we turn off the outgoing - * packet blockage for now, and release any queued - * output packets, so that we can make another attempt - * to authenticate. The next userauth packet we send - * will re-block the output direction. - */ - s->pending_compression = false; - queue_idempotent_callback(&s->bpp.ic_out_pq); - } - } - } - - eof: - /* - * We've seen EOF. But we might have pushed stuff on the outgoing - * packet queue first, and that stuff _might_ include a DISCONNECT - * message, in which case we'd like to use that as the diagnostic. - * So first wait for the queue to have been processed. - */ - crMaybeWaitUntilV(!pq_peek(&s->bpp.in_pq)); - if (!s->bpp.expect_close) { - ssh_remote_error(s->bpp.ssh, - "Remote side unexpectedly closed network connection"); - } else { - ssh_remote_eof(s->bpp.ssh, "Remote side closed network connection"); - } - return; /* avoid touching s now it's been freed */ - - crFinishV; -} - -static PktOut *ssh2_bpp_new_pktout(int pkt_type) -{ - PktOut *pkt = ssh_new_packet(); - pkt->length = 5; /* space for packet length + padding length */ - pkt->minlen = 0; - pkt->type = pkt_type; - put_byte(pkt, pkt_type); - pkt->prefix = pkt->length; - return pkt; -} - -static void ssh2_bpp_format_packet_inner(struct ssh2_bpp_state *s, PktOut *pkt) -{ - int origlen, cipherblk, maclen, padding, unencrypted_prefix, i; - - if (s->bpp.logctx) { - ptrlen pktdata = make_ptrlen(pkt->data + pkt->prefix, - pkt->length - pkt->prefix); - logblank_t blanks[MAX_BLANKS]; - int nblanks = ssh2_censor_packet( - s->bpp.pls, pkt->type, true, pktdata, blanks); - log_packet(s->bpp.logctx, PKT_OUTGOING, pkt->type, - ssh2_pkt_type(s->bpp.pls->kctx, s->bpp.pls->actx, - pkt->type), - pktdata.ptr, pktdata.len, nblanks, blanks, &s->out.sequence, - pkt->downstream_id, pkt->additional_log_text); - } - - cipherblk = s->out.cipher ? ssh_cipher_alg(s->out.cipher)->blksize : 8; - cipherblk = cipherblk < 8 ? 8 : cipherblk; /* or 8 if blksize < 8 */ - - if (s->out_comp) { - unsigned char *newpayload; - int minlen, newlen; - - /* - * Compress packet payload. - */ - minlen = pkt->minlen; - if (minlen) { - /* - * Work out how much compressed data we need (at least) to - * make the overall packet length come to pkt->minlen. - */ - if (s->out.mac) - minlen -= ssh2_mac_alg(s->out.mac)->len; - minlen -= 8; /* length field + min padding */ - } - - ssh_compressor_compress(s->out_comp, pkt->data + 5, pkt->length - 5, - &newpayload, &newlen, minlen); - pkt->length = 5; - put_data(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. - */ - padding = 4; - unencrypted_prefix = (s->out.mac && s->out.etm_mode) ? 4 : 0; - padding += - (cipherblk - (pkt->length - unencrypted_prefix + padding) % cipherblk) - % cipherblk; - assert(padding <= 255); - maclen = s->out.mac ? ssh2_mac_alg(s->out.mac)->len : 0; - origlen = pkt->length; - for (i = 0; i < padding; i++) - put_byte(pkt, 0); /* make space for random padding */ - random_read(pkt->data + origlen, padding); - pkt->data[4] = padding; - PUT_32BIT_MSB_FIRST(pkt->data, origlen + padding - 4); - - /* Encrypt length if the scheme requires it */ - if (s->out.cipher && - (ssh_cipher_alg(s->out.cipher)->flags & SSH_CIPHER_SEPARATE_LENGTH)) { - ssh_cipher_encrypt_length(s->out.cipher, pkt->data, 4, - s->out.sequence); - } - - put_padding(pkt, maclen, 0); - - if (s->out.mac && s->out.etm_mode) { - /* - * OpenSSH-defined encrypt-then-MAC protocol. - */ - if (s->out.cipher) - ssh_cipher_encrypt(s->out.cipher, - pkt->data + 4, origlen + padding - 4); - ssh2_mac_generate(s->out.mac, pkt->data, origlen + padding, - s->out.sequence); - } else { - /* - * SSH-2 standard protocol. - */ - if (s->out.mac) - ssh2_mac_generate(s->out.mac, pkt->data, origlen + padding, - s->out.sequence); - if (s->out.cipher) - ssh_cipher_encrypt(s->out.cipher, pkt->data, origlen + padding); - } - - s->out.sequence++; /* whether or not we MACed */ - - dts_consume(&s->stats->out, origlen + padding); -} - -static void ssh2_bpp_format_packet(struct ssh2_bpp_state *s, PktOut *pkt) -{ - if (pkt->minlen > 0 && !s->out_comp) { - /* - * If we've been told to pad the packet out to a given minimum - * length, but we're not compressing (and hence can't get the - * compression to do the padding by pointlessly opening and - * closing zlib blocks), then our other strategy is to precede - * this message with an SSH_MSG_IGNORE that makes it up to the - * right length. - * - * A third option in principle, and the most obviously - * sensible, would be to set the explicit padding field in the - * packet to more than its minimum value. Sadly, that turns - * out to break some servers (our institutional memory thinks - * Cisco in particular) and so we abandoned that idea shortly - * after trying it. - */ - - /* - * Calculate the length we expect the real packet to have. - */ - int block, length; - PktOut *ignore_pkt; - - block = s->out.cipher ? ssh_cipher_alg(s->out.cipher)->blksize : 0; - if (block < 8) - block = 8; - length = pkt->length; - length += 4; /* minimum 4 byte padding */ - length += block-1; - length -= (length % block); - if (s->out.mac) - length += ssh2_mac_alg(s->out.mac)->len; - - if (length < pkt->minlen) { - /* - * We need an ignore message. Calculate its length. - */ - length = pkt->minlen - length; - - /* - * And work backwards from that to the length of the - * contained string. - */ - if (s->out.mac) - length -= ssh2_mac_alg(s->out.mac)->len; - length -= 8; /* length field + min padding */ - length -= 5; /* type code + string length prefix */ - - if (length < 0) - length = 0; - - ignore_pkt = ssh2_bpp_new_pktout(SSH2_MSG_IGNORE); - put_uint32(ignore_pkt, length); - size_t origlen = ignore_pkt->length; - for (size_t i = 0; i < length; i++) - put_byte(ignore_pkt, 0); /* make space for random padding */ - random_read(ignore_pkt->data + origlen, length); - ssh2_bpp_format_packet_inner(s, ignore_pkt); - bufchain_add(s->bpp.out_raw, ignore_pkt->data, ignore_pkt->length); - ssh_free_pktout(ignore_pkt); - } - } - - ssh2_bpp_format_packet_inner(s, pkt); - bufchain_add(s->bpp.out_raw, pkt->data, pkt->length); -} - -static void ssh2_bpp_handle_output(BinaryPacketProtocol *bpp) -{ - struct ssh2_bpp_state *s = container_of(bpp, struct ssh2_bpp_state, bpp); - PktOut *pkt; - int n_userauth; - - /* - * Count the userauth packets in the queue. - */ - n_userauth = 0; - for (pkt = pq_first(&s->bpp.out_pq); pkt != NULL; - pkt = pq_next(&s->bpp.out_pq, pkt)) - if (userauth_range(pkt->type)) - n_userauth++; - - if (s->pending_compression && !n_userauth) { - /* - * We're currently blocked from sending any outgoing packets - * until the other end tells us whether we're going to have to - * enable compression or not. - * - * If our end has pushed a userauth packet on the queue, that - * must mean it knows that a USERAUTH_SUCCESS is not - * immediately forthcoming, so we unblock ourselves and send - * up to and including that packet. But in this if statement, - * there aren't any, so we're still blocked. - */ - return; - } - - if (s->cbc_ignore_workaround) { - /* - * When using a CBC-mode cipher in SSH-2, 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 inserting an - * SSH_MSG_IGNORE if the last cipher block of the previous - * packet has already been sent to the network (which we - * approximate conservatively by checking if it's vanished - * from out_raw). - */ - if (bufchain_size(s->bpp.out_raw) < - (ssh_cipher_alg(s->out.cipher)->blksize + - ssh2_mac_alg(s->out.mac)->len)) { - /* - * There's less data in out_raw than the MAC size plus the - * cipher block size, which means at least one byte of - * that cipher block must already have left. Add an - * IGNORE. - */ - pkt = ssh_bpp_new_pktout(&s->bpp, SSH2_MSG_IGNORE); - put_stringz(pkt, ""); - ssh2_bpp_format_packet(s, pkt); - } - } - - while ((pkt = pq_pop(&s->bpp.out_pq)) != NULL) { - int type = pkt->type; - - if (userauth_range(type)) - n_userauth--; - - ssh2_bpp_format_packet(s, pkt); - ssh_free_pktout(pkt); - - if (n_userauth == 0 && s->out.pending_compression && !s->is_server) { - /* - * This is the last userauth packet in the queue, so - * unless our side decides to send another one in future, - * we have to assume will potentially provoke - * USERAUTH_SUCCESS. Block (non-userauth) outgoing packets - * until we see the reply. - */ - s->pending_compression = true; - return; - } else if (type == SSH2_MSG_USERAUTH_SUCCESS && s->is_server) { - ssh2_bpp_enable_pending_compression(s); - } - } -} diff --git a/ssh2censor.c b/ssh2censor.c deleted file mode 100644 index 31ad8149..00000000 --- a/ssh2censor.c +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Packet-censoring code for SSH-2, used to identify sensitive fields - * like passwords so that the logging system can avoid writing them - * into log files. - */ - -#include - -#include "putty.h" -#include "ssh.h" - -int ssh2_censor_packet( - const PacketLogSettings *pls, int type, bool sender_is_client, - ptrlen pkt, logblank_t *blanks) -{ - int nblanks = 0; - ptrlen str; - BinarySource src[1]; - - BinarySource_BARE_INIT_PL(src, pkt); - - if (pls->omit_data && - (type == SSH2_MSG_CHANNEL_DATA || - type == SSH2_MSG_CHANNEL_EXTENDED_DATA)) { - /* "Session data" packets - omit the data string. */ - get_uint32(src); /* skip channel id */ - if (type == SSH2_MSG_CHANNEL_EXTENDED_DATA) - get_uint32(src); /* skip extended data type */ - str = get_string(src); - if (!get_err(src)) { - assert(nblanks < MAX_BLANKS); - blanks[nblanks].offset = src->pos - str.len; - blanks[nblanks].type = PKTLOG_OMIT; - blanks[nblanks].len = str.len; - nblanks++; - } - } - - if (sender_is_client && pls->omit_passwords) { - if (type == SSH2_MSG_USERAUTH_REQUEST) { - /* If this is a password packet, blank the password(s). */ - get_string(src); /* username */ - get_string(src); /* service name */ - str = get_string(src); /* auth method */ - if (ptrlen_eq_string(str, "password")) { - get_bool(src); - /* Blank the password field. */ - str = get_string(src); - if (!get_err(src)) { - assert(nblanks < MAX_BLANKS); - blanks[nblanks].offset = src->pos - str.len; - blanks[nblanks].type = PKTLOG_BLANK; - blanks[nblanks].len = str.len; - nblanks++; - /* If there's another password field beyond it - * (change of password), blank that too. */ - str = get_string(src); - if (!get_err(src)) - blanks[nblanks-1].len = - src->pos - blanks[nblanks].offset; - } - } - } else if (pls->actx == SSH2_PKTCTX_KBDINTER && - type == SSH2_MSG_USERAUTH_INFO_RESPONSE) { - /* If this is a keyboard-interactive response packet, - * blank the responses. */ - get_uint32(src); - assert(nblanks < MAX_BLANKS); - blanks[nblanks].offset = src->pos; - blanks[nblanks].type = PKTLOG_BLANK; - do { - str = get_string(src); - } while (!get_err(src)); - blanks[nblanks].len = src->pos - blanks[nblanks].offset; - nblanks++; - } else if (type == SSH2_MSG_CHANNEL_REQUEST) { - /* - * If this is an X forwarding request packet, blank the - * fake auth data. - * - * Note that while we blank the X authentication data - * here, we don't take any special action to blank the - * start of an X11 channel, so using MIT-MAGIC-COOKIE-1 - * and actually opening an X connection without having - * session blanking enabled is likely to leak your cookie - * into the log. - */ - get_uint32(src); - str = get_string(src); - if (ptrlen_eq_string(str, "x11-req")) { - get_bool(src); - get_bool(src); - get_string(src); - str = get_string(src); - if (!get_err(src)) { - assert(nblanks < MAX_BLANKS); - blanks[nblanks].offset = src->pos - str.len; - blanks[nblanks].type = PKTLOG_BLANK; - blanks[nblanks].len = str.len; - nblanks++; - } - } - } - } - - return nblanks; -} diff --git a/ssh2connection-client.c b/ssh2connection-client.c deleted file mode 100644 index 0b13efe6..00000000 --- a/ssh2connection-client.c +++ /dev/null @@ -1,505 +0,0 @@ -/* - * Client-specific parts of the SSH-2 connection layer. - */ - -#include - -#include "putty.h" -#include "ssh.h" -#include "sshbpp.h" -#include "sshppl.h" -#include "sshchan.h" -#include "sshcr.h" -#include "ssh2connection.h" - -static ChanopenResult chan_open_x11( - struct ssh2_connection_state *s, SshChannel *sc, - ptrlen peeraddr, int peerport) -{ - PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ - char *peeraddr_str; - Channel *ch; - - ppl_logevent("Received X11 connect request from %.*s:%d", - PTRLEN_PRINTF(peeraddr), peerport); - - if (!s->X11_fwd_enabled && !s->connshare) { - CHANOPEN_RETURN_FAILURE( - SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED, - ("X11 forwarding is not enabled")); - } - - peeraddr_str = peeraddr.ptr ? mkstr(peeraddr) : NULL; - ch = x11_new_channel( - s->x11authtree, sc, peeraddr_str, peerport, s->connshare != NULL); - sfree(peeraddr_str); - ppl_logevent("Opened X11 forward channel"); - CHANOPEN_RETURN_SUCCESS(ch); -} - -static ChanopenResult chan_open_forwarded_tcpip( - struct ssh2_connection_state *s, SshChannel *sc, - ptrlen fwdaddr, int fwdport, ptrlen peeraddr, int peerport) -{ - PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ - struct ssh_rportfwd pf, *realpf; - Channel *ch; - char *err; - - ppl_logevent("Received remote port %.*s:%d open request from %.*s:%d", - PTRLEN_PRINTF(fwdaddr), fwdport, - PTRLEN_PRINTF(peeraddr), peerport); - - pf.shost = mkstr(fwdaddr); - pf.sport = fwdport; - realpf = find234(s->rportfwds, &pf, NULL); - sfree(pf.shost); - - if (realpf == NULL) { - CHANOPEN_RETURN_FAILURE( - SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED, - ("Remote port is not recognised")); - } - - if (realpf->share_ctx) { - /* - * This port forwarding is on behalf of a connection-sharing - * downstream. - */ - CHANOPEN_RETURN_DOWNSTREAM(realpf->share_ctx); - } - - err = portfwdmgr_connect( - s->portfwdmgr, &ch, realpf->dhost, realpf->dport, - sc, realpf->addressfamily); - ppl_logevent("Attempting to forward remote port to %s:%d", - realpf->dhost, realpf->dport); - if (err != NULL) { - ppl_logevent("Port open failed: %s", err); - sfree(err); - CHANOPEN_RETURN_FAILURE( - SSH2_OPEN_CONNECT_FAILED, - ("Port open failed")); - } - - ppl_logevent("Forwarded port opened successfully"); - CHANOPEN_RETURN_SUCCESS(ch); -} - -static ChanopenResult chan_open_auth_agent( - struct ssh2_connection_state *s, SshChannel *sc) -{ - if (!ssh_agent_forwarding_permitted(&s->cl)) { - CHANOPEN_RETURN_FAILURE( - SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED, - ("Agent forwarding is not enabled")); - } - - /* - * If possible, make a stream-oriented connection to the agent and - * set up an ordinary port-forwarding type channel over it. - */ - Plug *plug; - Channel *ch = portfwd_raw_new(&s->cl, &plug, true); - Socket *skt = agent_connect(plug); - - if (!sk_socket_error(skt)) { - portfwd_raw_setup(ch, skt, sc); - CHANOPEN_RETURN_SUCCESS(ch); - } else { - portfwd_raw_free(ch); - /* - * Otherwise, fall back to the old-fashioned system of parsing the - * forwarded data stream ourselves for message boundaries, and - * passing each individual message to the one-off agent_query(). - */ - CHANOPEN_RETURN_SUCCESS(agentf_new(sc)); - } -} - -ChanopenResult ssh2_connection_parse_channel_open( - struct ssh2_connection_state *s, ptrlen type, - PktIn *pktin, SshChannel *sc) -{ - if (ptrlen_eq_string(type, "x11")) { - ptrlen peeraddr = get_string(pktin); - int peerport = get_uint32(pktin); - - return chan_open_x11(s, sc, peeraddr, peerport); - } else if (ptrlen_eq_string(type, "forwarded-tcpip")) { - ptrlen fwdaddr = get_string(pktin); - int fwdport = toint(get_uint32(pktin)); - ptrlen peeraddr = get_string(pktin); - int peerport = toint(get_uint32(pktin)); - - return chan_open_forwarded_tcpip( - s, sc, fwdaddr, fwdport, peeraddr, peerport); - } else if (ptrlen_eq_string(type, "auth-agent@openssh.com")) { - return chan_open_auth_agent(s, sc); - } else { - CHANOPEN_RETURN_FAILURE( - SSH2_OPEN_UNKNOWN_CHANNEL_TYPE, - ("Unsupported channel type requested")); - } -} - -bool ssh2_connection_parse_global_request( - struct ssh2_connection_state *s, ptrlen type, PktIn *pktin) -{ - /* - * We don't know of any global requests that an SSH client needs - * to honour. - */ - return false; -} - -PktOut *ssh2_portfwd_chanopen( - struct ssh2_connection_state *s, struct ssh2_channel *c, - const char *hostname, int port, - const char *description, const SocketPeerInfo *peerinfo) -{ - PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ - PktOut *pktout; - - /* - * In client mode, this function is called by portfwdmgr in - * response to PortListeners that were set up in - * portfwdmgr_config, which means that the hostname and port - * parameters will indicate the host we want to tell the server to - * connect _to_. - */ - - ppl_logevent("Opening connection to %s:%d for %s", - hostname, port, description); - - pktout = ssh2_chanopen_init(c, "direct-tcpip"); - { - char *trimmed_host = host_strduptrim(hostname); - put_stringz(pktout, trimmed_host); - sfree(trimmed_host); - } - put_uint32(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. - */ - put_stringz(pktout, "0.0.0.0"); - put_uint32(pktout, 0); - - return pktout; -} - -static int ssh2_rportfwd_cmp(void *av, void *bv) -{ - struct ssh_rportfwd *a = (struct ssh_rportfwd *) av; - struct ssh_rportfwd *b = (struct ssh_rportfwd *) bv; - int i; - if ( (i = strcmp(a->shost, b->shost)) != 0) - return i < 0 ? -1 : +1; - if (a->sport > b->sport) - return +1; - if (a->sport < b->sport) - return -1; - return 0; -} - -static void ssh2_rportfwd_globreq_response(struct ssh2_connection_state *s, - PktIn *pktin, void *ctx) -{ - PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ - struct ssh_rportfwd *rpf = (struct ssh_rportfwd *)ctx; - - if (pktin->type == SSH2_MSG_REQUEST_SUCCESS) { - ppl_logevent("Remote port forwarding from %s enabled", - rpf->log_description); - } else { - ppl_logevent("Remote port forwarding from %s refused", - rpf->log_description); - - struct ssh_rportfwd *realpf = del234(s->rportfwds, rpf); - assert(realpf == rpf); - portfwdmgr_close(s->portfwdmgr, rpf->pfr); - free_rportfwd(rpf); - } -} - -struct ssh_rportfwd *ssh2_rportfwd_alloc( - ConnectionLayer *cl, - const char *shost, int sport, const char *dhost, int dport, - int addressfamily, const char *log_description, PortFwdRecord *pfr, - ssh_sharing_connstate *share_ctx) -{ - struct ssh2_connection_state *s = - container_of(cl, struct ssh2_connection_state, cl); - struct ssh_rportfwd *rpf = snew(struct ssh_rportfwd); - - if (!s->rportfwds) - s->rportfwds = newtree234(ssh2_rportfwd_cmp); - - rpf->shost = dupstr(shost); - rpf->sport = sport; - rpf->dhost = dupstr(dhost); - rpf->dport = dport; - rpf->addressfamily = addressfamily; - rpf->log_description = dupstr(log_description); - rpf->pfr = pfr; - rpf->share_ctx = share_ctx; - - if (add234(s->rportfwds, rpf) != rpf) { - free_rportfwd(rpf); - return NULL; - } - - if (!rpf->share_ctx) { - PktOut *pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH2_MSG_GLOBAL_REQUEST); - put_stringz(pktout, "tcpip-forward"); - put_bool(pktout, true); /* want reply */ - put_stringz(pktout, rpf->shost); - put_uint32(pktout, rpf->sport); - pq_push(s->ppl.out_pq, pktout); - - ssh2_queue_global_request_handler( - s, ssh2_rportfwd_globreq_response, rpf); - } - - return rpf; -} - -void ssh2_rportfwd_remove(ConnectionLayer *cl, struct ssh_rportfwd *rpf) -{ - struct ssh2_connection_state *s = - container_of(cl, struct ssh2_connection_state, cl); - - if (rpf->share_ctx) { - /* - * We don't manufacture a cancel-tcpip-forward message for - * remote port forwardings being removed on behalf of a - * downstream; we just pass through the one the downstream - * sent to us. - */ - } else { - PktOut *pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH2_MSG_GLOBAL_REQUEST); - put_stringz(pktout, "cancel-tcpip-forward"); - put_bool(pktout, false); /* _don't_ want reply */ - put_stringz(pktout, rpf->shost); - put_uint32(pktout, rpf->sport); - pq_push(s->ppl.out_pq, pktout); - } - - assert(s->rportfwds); - struct ssh_rportfwd *realpf = del234(s->rportfwds, rpf); - assert(realpf == rpf); - free_rportfwd(rpf); -} - -SshChannel *ssh2_session_open(ConnectionLayer *cl, Channel *chan) -{ - struct ssh2_connection_state *s = - container_of(cl, struct ssh2_connection_state, cl); - PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ - struct ssh2_channel *c = snew(struct ssh2_channel); - PktOut *pktout; - - c->connlayer = s; - ssh2_channel_init(c); - c->halfopen = true; - c->chan = chan; - - ppl_logevent("Opening main session channel"); - - pktout = ssh2_chanopen_init(c, "session"); - pq_push(s->ppl.out_pq, pktout); - - return &c->sc; -} - -SshChannel *ssh2_serverside_x11_open( - ConnectionLayer *cl, Channel *chan, const SocketPeerInfo *pi) -{ - unreachable("Should never be called in the client"); -} - -SshChannel *ssh2_serverside_agent_open(ConnectionLayer *cl, Channel *chan) -{ - unreachable("Should never be called in the client"); -} - -static void ssh2_channel_response( - struct ssh2_channel *c, PktIn *pkt, void *ctx) -{ - /* If pkt==NULL (because this handler has been called in response - * to CHANNEL_CLOSE arriving while the request was still - * outstanding), we treat that the same as CHANNEL_FAILURE. */ - chan_request_response(c->chan, - pkt && pkt->type == SSH2_MSG_CHANNEL_SUCCESS); -} - -void ssh2channel_start_shell(SshChannel *sc, bool want_reply) -{ - struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc); - struct ssh2_connection_state *s = c->connlayer; - - PktOut *pktout = ssh2_chanreq_init( - c, "shell", want_reply ? ssh2_channel_response : NULL, NULL); - pq_push(s->ppl.out_pq, pktout); -} - -void ssh2channel_start_command( - SshChannel *sc, bool want_reply, const char *command) -{ - struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc); - struct ssh2_connection_state *s = c->connlayer; - - PktOut *pktout = ssh2_chanreq_init( - c, "exec", want_reply ? ssh2_channel_response : NULL, NULL); - put_stringz(pktout, command); - pq_push(s->ppl.out_pq, pktout); -} - -bool ssh2channel_start_subsystem( - SshChannel *sc, bool want_reply, const char *subsystem) -{ - struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc); - struct ssh2_connection_state *s = c->connlayer; - - PktOut *pktout = ssh2_chanreq_init( - c, "subsystem", want_reply ? ssh2_channel_response : NULL, NULL); - put_stringz(pktout, subsystem); - pq_push(s->ppl.out_pq, pktout); - - return true; -} - -void ssh2channel_send_exit_status(SshChannel *sc, int status) -{ - unreachable("Should never be called in the client"); -} - -void ssh2channel_send_exit_signal( - SshChannel *sc, ptrlen signame, bool core_dumped, ptrlen msg) -{ - unreachable("Should never be called in the client"); -} - -void ssh2channel_send_exit_signal_numeric( - SshChannel *sc, int signum, bool core_dumped, ptrlen msg) -{ - unreachable("Should never be called in the client"); -} - -void ssh2channel_request_x11_forwarding( - SshChannel *sc, bool want_reply, const char *authproto, - const char *authdata, int screen_number, bool oneshot) -{ - struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc); - struct ssh2_connection_state *s = c->connlayer; - - PktOut *pktout = ssh2_chanreq_init( - c, "x11-req", want_reply ? ssh2_channel_response : NULL, NULL); - put_bool(pktout, oneshot); - put_stringz(pktout, authproto); - put_stringz(pktout, authdata); - put_uint32(pktout, screen_number); - pq_push(s->ppl.out_pq, pktout); -} - -void ssh2channel_request_agent_forwarding(SshChannel *sc, bool want_reply) -{ - struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc); - struct ssh2_connection_state *s = c->connlayer; - - PktOut *pktout = ssh2_chanreq_init( - c, "auth-agent-req@openssh.com", - want_reply ? ssh2_channel_response : NULL, NULL); - pq_push(s->ppl.out_pq, pktout); -} - -void ssh2channel_request_pty( - SshChannel *sc, bool want_reply, Conf *conf, int w, int h) -{ - struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc); - struct ssh2_connection_state *s = c->connlayer; - strbuf *modebuf; - - PktOut *pktout = ssh2_chanreq_init( - c, "pty-req", want_reply ? ssh2_channel_response : NULL, NULL); - put_stringz(pktout, conf_get_str(conf, CONF_termtype)); - put_uint32(pktout, w); - put_uint32(pktout, h); - put_uint32(pktout, 0); /* pixel width */ - put_uint32(pktout, 0); /* pixel height */ - modebuf = strbuf_new(); - write_ttymodes_to_packet( - BinarySink_UPCAST(modebuf), 2, - get_ttymodes_from_conf(s->ppl.seat, conf)); - put_stringsb(pktout, modebuf); - pq_push(s->ppl.out_pq, pktout); -} - -bool ssh2channel_send_env_var( - SshChannel *sc, bool want_reply, const char *var, const char *value) -{ - struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc); - struct ssh2_connection_state *s = c->connlayer; - - PktOut *pktout = ssh2_chanreq_init( - c, "env", want_reply ? ssh2_channel_response : NULL, NULL); - put_stringz(pktout, var); - put_stringz(pktout, value); - pq_push(s->ppl.out_pq, pktout); - - return true; -} - -bool ssh2channel_send_serial_break(SshChannel *sc, bool want_reply, int length) -{ - struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc); - struct ssh2_connection_state *s = c->connlayer; - - PktOut *pktout = ssh2_chanreq_init( - c, "break", want_reply ? ssh2_channel_response : NULL, NULL); - put_uint32(pktout, length); - pq_push(s->ppl.out_pq, pktout); - - return true; -} - -bool ssh2channel_send_signal( - SshChannel *sc, bool want_reply, const char *signame) -{ - struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc); - struct ssh2_connection_state *s = c->connlayer; - - PktOut *pktout = ssh2_chanreq_init( - c, "signal", want_reply ? ssh2_channel_response : NULL, NULL); - put_stringz(pktout, signame); - pq_push(s->ppl.out_pq, pktout); - - return true; -} - -void ssh2channel_send_terminal_size_change(SshChannel *sc, int w, int h) -{ - struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc); - struct ssh2_connection_state *s = c->connlayer; - - PktOut *pktout = ssh2_chanreq_init(c, "window-change", NULL, NULL); - put_uint32(pktout, w); - put_uint32(pktout, h); - put_uint32(pktout, 0); /* pixel width */ - put_uint32(pktout, 0); /* pixel height */ - pq_push(s->ppl.out_pq, pktout); -} - -bool ssh2_connection_need_antispoof_prompt(struct ssh2_connection_state *s) -{ - bool success = seat_set_trust_status(s->ppl.seat, false); - return (!success && !ssh_is_bare(s->ppl.ssh)); -} diff --git a/ssh2connection-server.c b/ssh2connection-server.c deleted file mode 100644 index 1467db11..00000000 --- a/ssh2connection-server.c +++ /dev/null @@ -1,306 +0,0 @@ -/* - * Server-specific parts of the SSH-2 connection layer. - */ - -#include - -#include "putty.h" -#include "ssh.h" -#include "sshbpp.h" -#include "sshppl.h" -#include "sshchan.h" -#include "sshcr.h" -#include "ssh2connection.h" -#include "sshserver.h" - -void ssh2connection_server_configure( - PacketProtocolLayer *ppl, const SftpServerVtable *sftpserver_vt, - const SshServerConfig *ssc) -{ - struct ssh2_connection_state *s = - container_of(ppl, struct ssh2_connection_state, ppl); - s->sftpserver_vt = sftpserver_vt; - s->ssc = ssc; -} - -static ChanopenResult chan_open_session( - struct ssh2_connection_state *s, SshChannel *sc) -{ - PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ - - ppl_logevent("Opened session channel"); - CHANOPEN_RETURN_SUCCESS(sesschan_new(sc, s->ppl.logctx, - s->sftpserver_vt, s->ssc)); -} - -static ChanopenResult chan_open_direct_tcpip( - struct ssh2_connection_state *s, SshChannel *sc, - ptrlen dstaddr, int dstport, ptrlen peeraddr, int peerport) -{ - PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ - Channel *ch; - char *dstaddr_str, *err; - - dstaddr_str = mkstr(dstaddr); - - ppl_logevent("Received request to connect to port %s:%d (from %.*s:%d)", - dstaddr_str, dstport, PTRLEN_PRINTF(peeraddr), peerport); - err = portfwdmgr_connect( - s->portfwdmgr, &ch, dstaddr_str, dstport, sc, ADDRTYPE_UNSPEC); - - sfree(dstaddr_str); - - if (err != NULL) { - ppl_logevent("Port open failed: %s", err); - sfree(err); - CHANOPEN_RETURN_FAILURE( - SSH2_OPEN_CONNECT_FAILED, ("Connection failed")); - } - - ppl_logevent("Port opened successfully"); - CHANOPEN_RETURN_SUCCESS(ch); -} - -ChanopenResult ssh2_connection_parse_channel_open( - struct ssh2_connection_state *s, ptrlen type, - PktIn *pktin, SshChannel *sc) -{ - if (ptrlen_eq_string(type, "session")) { - return chan_open_session(s, sc); - } else if (ptrlen_eq_string(type, "direct-tcpip")) { - ptrlen dstaddr = get_string(pktin); - int dstport = toint(get_uint32(pktin)); - ptrlen peeraddr = get_string(pktin); - int peerport = toint(get_uint32(pktin)); - return chan_open_direct_tcpip( - s, sc, dstaddr, dstport, peeraddr, peerport); - } else { - CHANOPEN_RETURN_FAILURE( - SSH2_OPEN_UNKNOWN_CHANNEL_TYPE, - ("Unsupported channel type requested")); - } -} - -bool ssh2_connection_parse_global_request( - struct ssh2_connection_state *s, ptrlen type, PktIn *pktin) -{ - if (ptrlen_eq_string(type, "tcpip-forward")) { - char *host = mkstr(get_string(pktin)); - unsigned port = get_uint32(pktin); - /* In SSH-2, the host/port we listen on are the same host/port - * we want reported back to us when a connection comes in, - * because that's what we tell the client */ - bool toret = portfwdmgr_listen( - s->portfwdmgr, host, port, host, port, s->conf); - sfree(host); - return toret; - } else if (ptrlen_eq_string(type, "cancel-tcpip-forward")) { - char *host = mkstr(get_string(pktin)); - unsigned port = get_uint32(pktin); - bool toret = portfwdmgr_unlisten(s->portfwdmgr, host, port); - sfree(host); - return toret; - } else { - /* Unrecognised request. */ - return false; - } -} - -PktOut *ssh2_portfwd_chanopen( - struct ssh2_connection_state *s, struct ssh2_channel *c, - const char *hostname, int port, - const char *description, const SocketPeerInfo *pi) -{ - PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ - PktOut *pktout; - - /* - * In server mode, this function is called by portfwdmgr in - * response to PortListeners that were set up by calling - * portfwdmgr_listen, which means that the hostname and port - * parameters will identify the listening socket on which a - * connection just came in. - */ - - if (pi && pi->log_text) - ppl_logevent("Forwarding connection to listening port %s:%d from %s", - hostname, port, pi->log_text); - else - ppl_logevent("Forwarding connection to listening port %s:%d", - hostname, port); - - pktout = ssh2_chanopen_init(c, "forwarded-tcpip"); - put_stringz(pktout, hostname); - put_uint32(pktout, port); - put_stringz(pktout, (pi && pi->addr_text ? pi->addr_text : "0.0.0.0")); - put_uint32(pktout, (pi && pi->port >= 0 ? pi->port : 0)); - - return pktout; -} - -struct ssh_rportfwd *ssh2_rportfwd_alloc( - ConnectionLayer *cl, - const char *shost, int sport, const char *dhost, int dport, - int addressfamily, const char *log_description, PortFwdRecord *pfr, - ssh_sharing_connstate *share_ctx) -{ - unreachable("Should never be called in the server"); -} - -void ssh2_rportfwd_remove(ConnectionLayer *cl, struct ssh_rportfwd *rpf) -{ - unreachable("Should never be called in the server"); -} - -SshChannel *ssh2_session_open(ConnectionLayer *cl, Channel *chan) -{ - unreachable("Should never be called in the server"); -} - -SshChannel *ssh2_serverside_x11_open( - ConnectionLayer *cl, Channel *chan, const SocketPeerInfo *pi) -{ - struct ssh2_connection_state *s = - container_of(cl, struct ssh2_connection_state, cl); - PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ - struct ssh2_channel *c = snew(struct ssh2_channel); - PktOut *pktout; - - c->connlayer = s; - ssh2_channel_init(c); - c->halfopen = true; - c->chan = chan; - - ppl_logevent("Forwarding X11 channel to client"); - - pktout = ssh2_chanopen_init(c, "x11"); - put_stringz(pktout, (pi && pi->addr_text ? pi->addr_text : "0.0.0.0")); - put_uint32(pktout, (pi && pi->port >= 0 ? pi->port : 0)); - pq_push(s->ppl.out_pq, pktout); - - return &c->sc; -} - -SshChannel *ssh2_serverside_agent_open(ConnectionLayer *cl, Channel *chan) -{ - struct ssh2_connection_state *s = - container_of(cl, struct ssh2_connection_state, cl); - PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ - struct ssh2_channel *c = snew(struct ssh2_channel); - PktOut *pktout; - - c->connlayer = s; - ssh2_channel_init(c); - c->halfopen = true; - c->chan = chan; - - ppl_logevent("Forwarding SSH agent to client"); - - pktout = ssh2_chanopen_init(c, "auth-agent@openssh.com"); - pq_push(s->ppl.out_pq, pktout); - - return &c->sc; -} - -void ssh2channel_start_shell(SshChannel *sc, bool want_reply) -{ - unreachable("Should never be called in the server"); -} - -void ssh2channel_start_command( - SshChannel *sc, bool want_reply, const char *command) -{ - unreachable("Should never be called in the server"); -} - -bool ssh2channel_start_subsystem( - SshChannel *sc, bool want_reply, const char *subsystem) -{ - unreachable("Should never be called in the server"); -} - -void ssh2channel_send_exit_status(SshChannel *sc, int status) -{ - struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc); - struct ssh2_connection_state *s = c->connlayer; - - PktOut *pktout = ssh2_chanreq_init(c, "exit-status", NULL, NULL); - put_uint32(pktout, status); - - pq_push(s->ppl.out_pq, pktout); -} - -void ssh2channel_send_exit_signal( - SshChannel *sc, ptrlen signame, bool core_dumped, ptrlen msg) -{ - struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc); - struct ssh2_connection_state *s = c->connlayer; - - PktOut *pktout = ssh2_chanreq_init(c, "exit-signal", NULL, NULL); - put_stringpl(pktout, signame); - put_bool(pktout, core_dumped); - put_stringpl(pktout, msg); - put_stringz(pktout, ""); /* language tag */ - - pq_push(s->ppl.out_pq, pktout); -} - -void ssh2channel_send_exit_signal_numeric( - SshChannel *sc, int signum, bool core_dumped, ptrlen msg) -{ - struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc); - struct ssh2_connection_state *s = c->connlayer; - - PktOut *pktout = ssh2_chanreq_init(c, "exit-signal", NULL, NULL); - put_uint32(pktout, signum); - put_bool(pktout, core_dumped); - put_stringpl(pktout, msg); - put_stringz(pktout, ""); /* language tag */ - - pq_push(s->ppl.out_pq, pktout); -} - -void ssh2channel_request_x11_forwarding( - SshChannel *sc, bool want_reply, const char *authproto, - const char *authdata, int screen_number, bool oneshot) -{ - unreachable("Should never be called in the server"); -} - -void ssh2channel_request_agent_forwarding(SshChannel *sc, bool want_reply) -{ - unreachable("Should never be called in the server"); -} - -void ssh2channel_request_pty( - SshChannel *sc, bool want_reply, Conf *conf, int w, int h) -{ - unreachable("Should never be called in the server"); -} - -bool ssh2channel_send_env_var( - SshChannel *sc, bool want_reply, const char *var, const char *value) -{ - unreachable("Should never be called in the server"); -} - -bool ssh2channel_send_serial_break(SshChannel *sc, bool want_reply, int length) -{ - unreachable("Should never be called in the server"); -} - -bool ssh2channel_send_signal( - SshChannel *sc, bool want_reply, const char *signame) -{ - unreachable("Should never be called in the server"); -} - -void ssh2channel_send_terminal_size_change(SshChannel *sc, int w, int h) -{ - unreachable("Should never be called in the server"); -} - -bool ssh2_connection_need_antispoof_prompt(struct ssh2_connection_state *s) -{ - return false; -} diff --git a/ssh2connection.c b/ssh2connection.c deleted file mode 100644 index ecb421ea..00000000 --- a/ssh2connection.c +++ /dev/null @@ -1,1745 +0,0 @@ -/* - * Packet protocol layer for the SSH-2 connection protocol (RFC 4254). - */ - -#include - -#include "putty.h" -#include "ssh.h" -#include "sshbpp.h" -#include "sshppl.h" -#include "sshchan.h" -#include "sshcr.h" -#include "ssh2connection.h" - -static void ssh2_connection_free(PacketProtocolLayer *); -static void ssh2_connection_process_queue(PacketProtocolLayer *); -static bool ssh2_connection_get_specials( - PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx); -static void ssh2_connection_special_cmd(PacketProtocolLayer *ppl, - SessionSpecialCode code, int arg); -static bool ssh2_connection_want_user_input(PacketProtocolLayer *ppl); -static void ssh2_connection_got_user_input(PacketProtocolLayer *ppl); -static void ssh2_connection_reconfigure(PacketProtocolLayer *ppl, Conf *conf); - -static const PacketProtocolLayerVtable ssh2_connection_vtable = { - .free = ssh2_connection_free, - .process_queue = ssh2_connection_process_queue, - .get_specials = ssh2_connection_get_specials, - .special_cmd = ssh2_connection_special_cmd, - .want_user_input = ssh2_connection_want_user_input, - .got_user_input = ssh2_connection_got_user_input, - .reconfigure = ssh2_connection_reconfigure, - .queued_data_size = ssh_ppl_default_queued_data_size, - .name = "ssh-connection", -}; - -static SshChannel *ssh2_lportfwd_open( - ConnectionLayer *cl, const char *hostname, int port, - const char *description, const SocketPeerInfo *pi, Channel *chan); -static struct X11FakeAuth *ssh2_add_x11_display( - ConnectionLayer *cl, int authtype, struct X11Display *x11disp); -static struct X11FakeAuth *ssh2_add_sharing_x11_display( - ConnectionLayer *cl, int authtype, ssh_sharing_connstate *share_cs, - share_channel *share_chan); -static void ssh2_remove_sharing_x11_display(ConnectionLayer *cl, - struct X11FakeAuth *auth); -static void ssh2_send_packet_from_downstream( - ConnectionLayer *cl, unsigned id, int type, - const void *pkt, int pktlen, const char *additional_log_text); -static unsigned ssh2_alloc_sharing_channel( - ConnectionLayer *cl, ssh_sharing_connstate *connstate); -static void ssh2_delete_sharing_channel( - ConnectionLayer *cl, unsigned localid); -static void ssh2_sharing_queue_global_request( - ConnectionLayer *cl, ssh_sharing_connstate *share_ctx); -static void ssh2_sharing_no_more_downstreams(ConnectionLayer *cl); -static bool ssh2_agent_forwarding_permitted(ConnectionLayer *cl); -static void ssh2_terminal_size(ConnectionLayer *cl, int width, int height); -static void ssh2_stdout_unthrottle(ConnectionLayer *cl, size_t bufsize); -static size_t ssh2_stdin_backlog(ConnectionLayer *cl); -static void ssh2_throttle_all_channels(ConnectionLayer *cl, bool throttled); -static bool ssh2_ldisc_option(ConnectionLayer *cl, int option); -static void ssh2_set_ldisc_option(ConnectionLayer *cl, int option, bool value); -static void ssh2_enable_x_fwd(ConnectionLayer *cl); -static void ssh2_set_wants_user_input(ConnectionLayer *cl, bool wanted); - -static const ConnectionLayerVtable ssh2_connlayer_vtable = { - .rportfwd_alloc = ssh2_rportfwd_alloc, - .rportfwd_remove = ssh2_rportfwd_remove, - .lportfwd_open = ssh2_lportfwd_open, - .session_open = ssh2_session_open, - .serverside_x11_open = ssh2_serverside_x11_open, - .serverside_agent_open = ssh2_serverside_agent_open, - .add_x11_display = ssh2_add_x11_display, - .add_sharing_x11_display = ssh2_add_sharing_x11_display, - .remove_sharing_x11_display = ssh2_remove_sharing_x11_display, - .send_packet_from_downstream = ssh2_send_packet_from_downstream, - .alloc_sharing_channel = ssh2_alloc_sharing_channel, - .delete_sharing_channel = ssh2_delete_sharing_channel, - .sharing_queue_global_request = ssh2_sharing_queue_global_request, - .sharing_no_more_downstreams = ssh2_sharing_no_more_downstreams, - .agent_forwarding_permitted = ssh2_agent_forwarding_permitted, - .terminal_size = ssh2_terminal_size, - .stdout_unthrottle = ssh2_stdout_unthrottle, - .stdin_backlog = ssh2_stdin_backlog, - .throttle_all_channels = ssh2_throttle_all_channels, - .ldisc_option = ssh2_ldisc_option, - .set_ldisc_option = ssh2_set_ldisc_option, - .enable_x_fwd = ssh2_enable_x_fwd, - .set_wants_user_input = ssh2_set_wants_user_input, -}; - -static char *ssh2_channel_open_failure_error_text(PktIn *pktin) -{ - static const char *const reasons[] = { - NULL, - "Administratively prohibited", - "Connect failed", - "Unknown channel type", - "Resource shortage", - }; - unsigned reason_code; - const char *reason_code_string; - char reason_code_buf[256]; - ptrlen reason; - - reason_code = get_uint32(pktin); - if (reason_code < lenof(reasons) && reasons[reason_code]) { - reason_code_string = reasons[reason_code]; - } else { - reason_code_string = reason_code_buf; - sprintf(reason_code_buf, "unknown reason code %#x", reason_code); - } - - reason = get_string(pktin); - - return dupprintf("%s [%.*s]", reason_code_string, PTRLEN_PRINTF(reason)); -} - -static size_t ssh2channel_write( - SshChannel *c, bool is_stderr, const void *buf, size_t len); -static void ssh2channel_write_eof(SshChannel *c); -static void ssh2channel_initiate_close(SshChannel *c, const char *err); -static void ssh2channel_unthrottle(SshChannel *c, size_t bufsize); -static Conf *ssh2channel_get_conf(SshChannel *c); -static void ssh2channel_window_override_removed(SshChannel *c); -static void ssh2channel_x11_sharing_handover( - SshChannel *c, ssh_sharing_connstate *share_cs, share_channel *share_chan, - const char *peer_addr, int peer_port, int endian, - int protomajor, int protominor, const void *initial_data, int initial_len); -static void ssh2channel_hint_channel_is_simple(SshChannel *c); - -static const SshChannelVtable ssh2channel_vtable = { - .write = ssh2channel_write, - .write_eof = ssh2channel_write_eof, - .initiate_close = ssh2channel_initiate_close, - .unthrottle = ssh2channel_unthrottle, - .get_conf = ssh2channel_get_conf, - .window_override_removed = ssh2channel_window_override_removed, - .x11_sharing_handover = ssh2channel_x11_sharing_handover, - .send_exit_status = ssh2channel_send_exit_status, - .send_exit_signal = ssh2channel_send_exit_signal, - .send_exit_signal_numeric = ssh2channel_send_exit_signal_numeric, - .request_x11_forwarding = ssh2channel_request_x11_forwarding, - .request_agent_forwarding = ssh2channel_request_agent_forwarding, - .request_pty = ssh2channel_request_pty, - .send_env_var = ssh2channel_send_env_var, - .start_shell = ssh2channel_start_shell, - .start_command = ssh2channel_start_command, - .start_subsystem = ssh2channel_start_subsystem, - .send_serial_break = ssh2channel_send_serial_break, - .send_signal = ssh2channel_send_signal, - .send_terminal_size_change = ssh2channel_send_terminal_size_change, - .hint_channel_is_simple = ssh2channel_hint_channel_is_simple, -}; - -static void ssh2_channel_check_close(struct ssh2_channel *c); -static void ssh2_channel_try_eof(struct ssh2_channel *c); -static void ssh2_set_window(struct ssh2_channel *c, int newwin); -static size_t ssh2_try_send(struct ssh2_channel *c); -static void ssh2_try_send_and_unthrottle(struct ssh2_channel *c); -static void ssh2_channel_check_throttle(struct ssh2_channel *c); -static void ssh2_channel_close_local(struct ssh2_channel *c, - const char *reason); -static void ssh2_channel_destroy(struct ssh2_channel *c); - -static void ssh2_check_termination(struct ssh2_connection_state *s); - -struct outstanding_global_request { - gr_handler_fn_t handler; - void *ctx; - struct outstanding_global_request *next; -}; -void ssh2_queue_global_request_handler( - struct ssh2_connection_state *s, gr_handler_fn_t handler, void *ctx) -{ - struct outstanding_global_request *ogr = - snew(struct outstanding_global_request); - ogr->handler = handler; - ogr->ctx = ctx; - if (s->globreq_tail) - s->globreq_tail->next = ogr; - else - s->globreq_head = ogr; - s->globreq_tail = ogr; -} - -static int ssh2_channelcmp(void *av, void *bv) -{ - const struct ssh2_channel *a = (const struct ssh2_channel *) av; - const struct ssh2_channel *b = (const struct ssh2_channel *) bv; - if (a->localid < b->localid) - return -1; - if (a->localid > b->localid) - return +1; - return 0; -} - -static int ssh2_channelfind(void *av, void *bv) -{ - const unsigned *a = (const unsigned *) av; - const struct ssh2_channel *b = (const struct ssh2_channel *) bv; - if (*a < b->localid) - return -1; - if (*a > b->localid) - return +1; - return 0; -} - -/* - * Each channel has a queue of outstanding CHANNEL_REQUESTS and their - * handlers. - */ -struct outstanding_channel_request { - cr_handler_fn_t handler; - void *ctx; - struct outstanding_channel_request *next; -}; - -static void ssh2_channel_free(struct ssh2_channel *c) -{ - bufchain_clear(&c->outbuffer); - bufchain_clear(&c->errbuffer); - while (c->chanreq_head) { - struct outstanding_channel_request *chanreq = c->chanreq_head; - c->chanreq_head = c->chanreq_head->next; - sfree(chanreq); - } - if (c->chan) { - struct ssh2_connection_state *s = c->connlayer; - if (s->mainchan_sc == &c->sc) { - s->mainchan = NULL; - s->mainchan_sc = NULL; - } - chan_free(c->chan); - } - sfree(c); -} - -PacketProtocolLayer *ssh2_connection_new( - Ssh *ssh, ssh_sharing_state *connshare, bool is_simple, - Conf *conf, const char *peer_verstring, ConnectionLayer **cl_out) -{ - struct ssh2_connection_state *s = snew(struct ssh2_connection_state); - memset(s, 0, sizeof(*s)); - s->ppl.vt = &ssh2_connection_vtable; - - s->conf = conf_copy(conf); - - s->ssh_is_simple = is_simple; - - /* - * If the ssh_no_shell option is enabled, we disable the usual - * termination check, so that we persist even in the absence of - * any at all channels (because our purpose is probably to be a - * background port forwarder). - */ - s->persistent = conf_get_bool(s->conf, CONF_ssh_no_shell); - - s->connshare = connshare; - s->peer_verstring = dupstr(peer_verstring); - - s->channels = newtree234(ssh2_channelcmp); - - s->x11authtree = newtree234(x11_authcmp); - - /* Need to get the log context for s->cl now, because we won't be - * helpfully notified when a copy is written into s->ppl by our - * owner. */ - s->cl.vt = &ssh2_connlayer_vtable; - s->cl.logctx = ssh_get_logctx(ssh); - - s->portfwdmgr = portfwdmgr_new(&s->cl); - - *cl_out = &s->cl; - if (s->connshare) - ssh_connshare_provide_connlayer(s->connshare, &s->cl); - - return &s->ppl; -} - -static void ssh2_connection_free(PacketProtocolLayer *ppl) -{ - struct ssh2_connection_state *s = - container_of(ppl, struct ssh2_connection_state, ppl); - struct X11FakeAuth *auth; - struct ssh2_channel *c; - struct ssh_rportfwd *rpf; - - sfree(s->peer_verstring); - - conf_free(s->conf); - - while ((c = delpos234(s->channels, 0)) != NULL) - ssh2_channel_free(c); - freetree234(s->channels); - - while ((auth = delpos234(s->x11authtree, 0)) != NULL) { - if (auth->disp) - x11_free_display(auth->disp); - x11_free_fake_auth(auth); - } - freetree234(s->x11authtree); - - if (s->rportfwds) { - while ((rpf = delpos234(s->rportfwds, 0)) != NULL) - free_rportfwd(rpf); - freetree234(s->rportfwds); - } - portfwdmgr_free(s->portfwdmgr); - - if (s->antispoof_prompt) - free_prompts(s->antispoof_prompt); - - delete_callbacks_for_context(s); - - sfree(s); -} - -static bool ssh2_connection_filter_queue(struct ssh2_connection_state *s) -{ - PktIn *pktin; - PktOut *pktout; - ptrlen type, data; - struct ssh2_channel *c; - struct outstanding_channel_request *ocr; - unsigned localid, remid, winsize, pktsize, ext_type; - bool want_reply, reply_success, expect_halfopen; - ChanopenResult chanopen_result; - PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ - - while (1) { - if (ssh2_common_filter_queue(&s->ppl)) - return true; - if ((pktin = pq_peek(s->ppl.in_pq)) == NULL) - return false; - - switch (pktin->type) { - case SSH2_MSG_GLOBAL_REQUEST: - type = get_string(pktin); - want_reply = get_bool(pktin); - - reply_success = ssh2_connection_parse_global_request( - s, type, pktin); - - if (want_reply) { - int type = (reply_success ? SSH2_MSG_REQUEST_SUCCESS : - SSH2_MSG_REQUEST_FAILURE); - pktout = ssh_bpp_new_pktout(s->ppl.bpp, type); - pq_push(s->ppl.out_pq, pktout); - } - pq_pop(s->ppl.in_pq); - break; - - case SSH2_MSG_REQUEST_SUCCESS: - case SSH2_MSG_REQUEST_FAILURE: - if (!s->globreq_head) { - ssh_proto_error( - s->ppl.ssh, - "Received %s with no outstanding global request", - ssh2_pkt_type(s->ppl.bpp->pls->kctx, s->ppl.bpp->pls->actx, - pktin->type)); - return true; - } - - s->globreq_head->handler(s, pktin, s->globreq_head->ctx); - { - struct outstanding_global_request *tmp = s->globreq_head; - s->globreq_head = s->globreq_head->next; - sfree(tmp); - } - - pq_pop(s->ppl.in_pq); - break; - - case SSH2_MSG_CHANNEL_OPEN: - type = get_string(pktin); - c = snew(struct ssh2_channel); - c->connlayer = s; - c->chan = NULL; - - remid = get_uint32(pktin); - winsize = get_uint32(pktin); - pktsize = get_uint32(pktin); - - chanopen_result = ssh2_connection_parse_channel_open( - s, type, pktin, &c->sc); - - if (chanopen_result.outcome == CHANOPEN_RESULT_DOWNSTREAM) { - /* - * This channel-open request needs to go to a - * connection-sharing downstream, so abandon our own - * channel-open procedure and just pass the message on - * to sshshare.c. - */ - share_got_pkt_from_server( - chanopen_result.u.downstream.share_ctx, pktin->type, - BinarySource_UPCAST(pktin)->data, - BinarySource_UPCAST(pktin)->len); - sfree(c); - break; - } - - c->remoteid = remid; - c->halfopen = false; - if (chanopen_result.outcome == CHANOPEN_RESULT_FAILURE) { - pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH2_MSG_CHANNEL_OPEN_FAILURE); - put_uint32(pktout, c->remoteid); - put_uint32(pktout, chanopen_result.u.failure.reason_code); - put_stringz(pktout, chanopen_result.u.failure.wire_message); - put_stringz(pktout, "en"); /* language tag */ - pq_push(s->ppl.out_pq, pktout); - ppl_logevent("Rejected channel open: %s", - chanopen_result.u.failure.wire_message); - sfree(chanopen_result.u.failure.wire_message); - sfree(c); - } else { - c->chan = chanopen_result.u.success.channel; - ssh2_channel_init(c); - c->remwindow = winsize; - c->remmaxpkt = pktsize; - if (c->remmaxpkt > s->ppl.bpp->vt->packet_size_limit) - c->remmaxpkt = s->ppl.bpp->vt->packet_size_limit; - if (c->chan->initial_fixed_window_size) { - c->locwindow = c->locmaxwin = c->remlocwin = - c->chan->initial_fixed_window_size; - } - pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH2_MSG_CHANNEL_OPEN_CONFIRMATION); - put_uint32(pktout, c->remoteid); - put_uint32(pktout, c->localid); - put_uint32(pktout, c->locwindow); - put_uint32(pktout, OUR_V2_MAXPKT); /* our max pkt size */ - pq_push(s->ppl.out_pq, pktout); - } - - pq_pop(s->ppl.in_pq); - break; - - case SSH2_MSG_CHANNEL_DATA: - case SSH2_MSG_CHANNEL_EXTENDED_DATA: - case SSH2_MSG_CHANNEL_WINDOW_ADJUST: - case SSH2_MSG_CHANNEL_REQUEST: - case SSH2_MSG_CHANNEL_EOF: - case SSH2_MSG_CHANNEL_CLOSE: - case SSH2_MSG_CHANNEL_OPEN_CONFIRMATION: - case SSH2_MSG_CHANNEL_OPEN_FAILURE: - case SSH2_MSG_CHANNEL_SUCCESS: - case SSH2_MSG_CHANNEL_FAILURE: - /* - * Common preliminary code for all the messages from the - * server that cite one of our channel ids: look up that - * channel id, check it exists, and if it's for a sharing - * downstream, pass it on. - */ - localid = get_uint32(pktin); - c = find234(s->channels, &localid, ssh2_channelfind); - - if (c && c->sharectx) { - share_got_pkt_from_server(c->sharectx, pktin->type, - BinarySource_UPCAST(pktin)->data, - BinarySource_UPCAST(pktin)->len); - pq_pop(s->ppl.in_pq); - break; - } - - expect_halfopen = ( - pktin->type == SSH2_MSG_CHANNEL_OPEN_CONFIRMATION || - pktin->type == SSH2_MSG_CHANNEL_OPEN_FAILURE); - - if (!c || c->halfopen != expect_halfopen) { - ssh_proto_error(s->ppl.ssh, - "Received %s for %s channel %u", - ssh2_pkt_type(s->ppl.bpp->pls->kctx, - s->ppl.bpp->pls->actx, - pktin->type), - (!c ? "nonexistent" : - c->halfopen ? "half-open" : "open"), - localid); - return true; - } - - switch (pktin->type) { - case SSH2_MSG_CHANNEL_OPEN_CONFIRMATION: - assert(c->halfopen); - c->remoteid = get_uint32(pktin); - c->halfopen = false; - c->remwindow = get_uint32(pktin); - c->remmaxpkt = get_uint32(pktin); - if (c->remmaxpkt > s->ppl.bpp->vt->packet_size_limit) - c->remmaxpkt = s->ppl.bpp->vt->packet_size_limit; - - chan_open_confirmation(c->chan); - - /* - * Now that the channel is fully open, it's possible - * in principle to immediately close it. Check whether - * it wants us to! - * - * This can occur if a local socket error occurred - * between us sending out CHANNEL_OPEN and receiving - * OPEN_CONFIRMATION. If that happens, all we can do - * is immediately initiate close proceedings now that - * we know the server's id to put in the close - * message. We'll have handled that in this code by - * having already turned c->chan into a zombie, so its - * want_close method (which ssh2_channel_check_close - * will consult) will already be returning true. - */ - ssh2_channel_check_close(c); - - if (c->pending_eof) - ssh2_channel_try_eof(c); /* in case we had a pending EOF */ - break; - - case SSH2_MSG_CHANNEL_OPEN_FAILURE: { - assert(c->halfopen); - - char *err = ssh2_channel_open_failure_error_text(pktin); - chan_open_failed(c->chan, err); - sfree(err); - - del234(s->channels, c); - ssh2_channel_free(c); - - break; - } - - case SSH2_MSG_CHANNEL_DATA: - case SSH2_MSG_CHANNEL_EXTENDED_DATA: - ext_type = (pktin->type == SSH2_MSG_CHANNEL_DATA ? 0 : - get_uint32(pktin)); - data = get_string(pktin); - if (!get_err(pktin)) { - int bufsize; - c->locwindow -= data.len; - c->remlocwin -= data.len; - if (ext_type != 0 && ext_type != SSH2_EXTENDED_DATA_STDERR) - data.len = 0; /* ignore unknown extended data */ - bufsize = chan_send( - c->chan, ext_type == SSH2_EXTENDED_DATA_STDERR, - data.ptr, data.len); - - /* - * The channel may have turned into a connection- - * shared one as a result of that chan_send, e.g. - * if the data we just provided completed the X11 - * auth phase and caused a callback to - * x11_sharing_handover. If so, do nothing - * further. - */ - if (c->sharectx) - 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->remlocwin <= 0 && - c->throttle_state == UNTHROTTLED && - c->locmaxwin < 0x40000000) - c->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. - */ - if (bufsize < c->locmaxwin) - ssh2_set_window(c, c->locmaxwin - bufsize); - - /* - * 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->locmaxwin || - (s->ssh_is_simple && bufsize>0)) && - !c->throttling_conn) { - c->throttling_conn = true; - ssh_throttle_conn(s->ppl.ssh, +1); - } - } - break; - - case SSH2_MSG_CHANNEL_WINDOW_ADJUST: - if (!(c->closes & CLOSES_SENT_EOF)) { - c->remwindow += get_uint32(pktin); - ssh2_try_send_and_unthrottle(c); - } - break; - - case SSH2_MSG_CHANNEL_REQUEST: - type = get_string(pktin); - want_reply = get_bool(pktin); - - reply_success = false; - - if (c->closes & CLOSES_SENT_CLOSE) { - /* - * We don't reply to channel requests after we've - * sent CHANNEL_CLOSE for the channel, because our - * reply might cross in the network with the other - * side's CHANNEL_CLOSE and arrive after they have - * wound the channel up completely. - */ - want_reply = false; - } - - /* - * Try every channel request name we recognise, no - * matter what the channel, and see if the Channel - * instance will accept it. - */ - if (ptrlen_eq_string(type, "exit-status")) { - int exitcode = toint(get_uint32(pktin)); - reply_success = chan_rcvd_exit_status(c->chan, exitcode); - } else if (ptrlen_eq_string(type, "exit-signal")) { - ptrlen signame; - int signum; - bool core = false; - ptrlen errmsg; - int format; - - /* - * 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. - */ - - size_t startpos = BinarySource_UPCAST(pktin)->pos; - - for (format = 0; format < 2; format++) { - BinarySource_UPCAST(pktin)->pos = startpos; - BinarySource_UPCAST(pktin)->err = BSE_NO_ERROR; - - /* placate compiler warnings about unin */ - signame = make_ptrlen(NULL, 0); - signum = 0; - - if (format == 0) /* standard string-based format */ - signame = get_string(pktin); - else /* nonstandard integer format */ - signum = toint(get_uint32(pktin)); - - core = get_bool(pktin); - errmsg = get_string(pktin); /* error message */ - get_string(pktin); /* language tag */ - - if (!get_err(pktin) && get_avail(pktin) == 0) - break; /* successful parse */ - } - - switch (format) { - case 0: - reply_success = chan_rcvd_exit_signal( - c->chan, signame, core, errmsg); - break; - case 1: - reply_success = chan_rcvd_exit_signal_numeric( - c->chan, signum, core, errmsg); - break; - default: - /* Couldn't parse this message in either format */ - reply_success = false; - break; - } - } else if (ptrlen_eq_string(type, "shell")) { - reply_success = chan_run_shell(c->chan); - } else if (ptrlen_eq_string(type, "exec")) { - ptrlen command = get_string(pktin); - reply_success = chan_run_command(c->chan, command); - } else if (ptrlen_eq_string(type, "subsystem")) { - ptrlen subsys = get_string(pktin); - reply_success = chan_run_subsystem(c->chan, subsys); - } else if (ptrlen_eq_string(type, "x11-req")) { - bool oneshot = get_bool(pktin); - ptrlen authproto = get_string(pktin); - ptrlen authdata = get_string(pktin); - unsigned screen_number = get_uint32(pktin); - reply_success = chan_enable_x11_forwarding( - c->chan, oneshot, authproto, authdata, screen_number); - } else if (ptrlen_eq_string(type, - "auth-agent-req@openssh.com")) { - reply_success = chan_enable_agent_forwarding(c->chan); - } else if (ptrlen_eq_string(type, "pty-req")) { - ptrlen termtype = get_string(pktin); - unsigned width = get_uint32(pktin); - unsigned height = get_uint32(pktin); - unsigned pixwidth = get_uint32(pktin); - unsigned pixheight = get_uint32(pktin); - ptrlen encoded_modes = get_string(pktin); - BinarySource bs_modes[1]; - struct ssh_ttymodes modes; - - BinarySource_BARE_INIT_PL(bs_modes, encoded_modes); - modes = read_ttymodes_from_packet(bs_modes, 2); - if (get_err(bs_modes) || get_avail(bs_modes) > 0) { - ppl_logevent("Unable to decode terminal mode string"); - reply_success = false; - } else { - reply_success = chan_allocate_pty( - c->chan, termtype, width, height, - pixwidth, pixheight, modes); - } - } else if (ptrlen_eq_string(type, "env")) { - ptrlen var = get_string(pktin); - ptrlen value = get_string(pktin); - - reply_success = chan_set_env(c->chan, var, value); - } else if (ptrlen_eq_string(type, "break")) { - unsigned length = get_uint32(pktin); - - reply_success = chan_send_break(c->chan, length); - } else if (ptrlen_eq_string(type, "signal")) { - ptrlen signame = get_string(pktin); - - reply_success = chan_send_signal(c->chan, signame); - } else if (ptrlen_eq_string(type, "window-change")) { - unsigned width = get_uint32(pktin); - unsigned height = get_uint32(pktin); - unsigned pixwidth = get_uint32(pktin); - unsigned pixheight = get_uint32(pktin); - reply_success = chan_change_window_size( - c->chan, width, height, pixwidth, pixheight); - } - if (want_reply) { - int type = (reply_success ? SSH2_MSG_CHANNEL_SUCCESS : - SSH2_MSG_CHANNEL_FAILURE); - pktout = ssh_bpp_new_pktout(s->ppl.bpp, type); - put_uint32(pktout, c->remoteid); - pq_push(s->ppl.out_pq, pktout); - } - break; - - case SSH2_MSG_CHANNEL_SUCCESS: - case SSH2_MSG_CHANNEL_FAILURE: - ocr = c->chanreq_head; - if (!ocr) { - ssh_proto_error( - s->ppl.ssh, - "Received %s for channel %d with no outstanding " - "channel request", - ssh2_pkt_type(s->ppl.bpp->pls->kctx, - s->ppl.bpp->pls->actx, pktin->type), - c->localid); - return true; - } - ocr->handler(c, pktin, ocr->ctx); - c->chanreq_head = ocr->next; - sfree(ocr); - /* - * We may now initiate channel-closing procedures, if - * that CHANNEL_REQUEST was the last thing outstanding - * before we send CHANNEL_CLOSE. - */ - ssh2_channel_check_close(c); - break; - - case SSH2_MSG_CHANNEL_EOF: - if (!(c->closes & CLOSES_RCVD_EOF)) { - c->closes |= CLOSES_RCVD_EOF; - chan_send_eof(c->chan); - ssh2_channel_check_close(c); - } - break; - - case SSH2_MSG_CHANNEL_CLOSE: - /* - * When we receive CLOSE on a channel, we assume it - * comes with an implied EOF if we haven't seen EOF - * yet. - */ - if (!(c->closes & CLOSES_RCVD_EOF)) { - c->closes |= CLOSES_RCVD_EOF; - chan_send_eof(c->chan); - } - - if (!(s->ppl.remote_bugs & BUG_SENDS_LATE_REQUEST_REPLY)) { - /* - * It also means we stop expecting to see replies - * to any outstanding channel requests, so clean - * those up too. (ssh_chanreq_init will enforce by - * assertion that we don't subsequently put - * anything back on this list.) - */ - while (c->chanreq_head) { - struct outstanding_channel_request *ocr = - c->chanreq_head; - ocr->handler(c, NULL, ocr->ctx); - c->chanreq_head = ocr->next; - sfree(ocr); - } - } - - /* - * And we also send an outgoing EOF, if we haven't - * already, on the assumption that CLOSE is a pretty - * forceful announcement that the remote side is doing - * away with the entire channel. (If it had wanted to - * send us EOF and continue receiving data from us, it - * would have just sent CHANNEL_EOF.) - */ - if (!(c->closes & CLOSES_SENT_EOF)) { - /* - * Abandon any buffered data we still wanted to - * send to this channel. Receiving a CHANNEL_CLOSE - * is an indication that the server really wants - * to get on and _destroy_ this channel, and it - * isn't going to send us any further - * WINDOW_ADJUSTs to permit us to send pending - * stuff. - */ - bufchain_clear(&c->outbuffer); - bufchain_clear(&c->errbuffer); - - /* - * Send outgoing EOF. - */ - sshfwd_write_eof(&c->sc); - - /* - * Make sure we don't read any more from whatever - * our local data source is for this channel. - * (This will pick up on the changes made by - * sshfwd_write_eof.) - */ - ssh2_channel_check_throttle(c); - } - - /* - * Now process the actual close. - */ - if (!(c->closes & CLOSES_RCVD_CLOSE)) { - c->closes |= CLOSES_RCVD_CLOSE; - ssh2_channel_check_close(c); - } - - break; - } - - pq_pop(s->ppl.in_pq); - break; - - default: - return false; - } - } -} - -static void ssh2_handle_winadj_response(struct ssh2_channel *c, - PktIn *pktin, void *ctx) -{ - unsigned *sizep = ctx; - - /* - * Winadj responses should always be failures. However, at least - * one server ("boks_sshd") is known to return SUCCESS for channel - * requests it's never heard of, such as "winadj@putty". Raised - * with foxt.com as bug 090916-090424, but for the sake of a quiet - * life, we don't worry about what kind of response we got. - */ - - c->remlocwin += *sizep; - sfree(sizep); - /* - * winadj messages are only sent when the window is fully open, so - * if we get an ack of one, we know any pending unthrottle is - * complete. - */ - if (c->throttle_state == UNTHROTTLING) - c->throttle_state = UNTHROTTLED; -} - -static void ssh2_set_window(struct ssh2_channel *c, int newwin) -{ - struct ssh2_connection_state *s = c->connlayer; - - /* - * Never send WINDOW_ADJUST for a channel that the remote side has - * already sent EOF on; there's no point, since it won't be - * sending any more data anyway. Ditto if _we've_ already sent - * CLOSE. - */ - if (c->closes & (CLOSES_RCVD_EOF | CLOSES_SENT_CLOSE)) - return; - - /* - * If the client-side Channel is in an initial setup phase with a - * fixed window size, e.g. for an X11 channel when we're still - * waiting to see its initial auth and may yet hand it off to a - * downstream, don't send any WINDOW_ADJUST either. - */ - if (c->chan->initial_fixed_window_size) - 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 ((s->ppl.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->locwindow) { - PktOut *pktout; - unsigned *up; - - /* - * In order to keep track of how much window the client - * actually has available, we'd like it to acknowledge each - * WINDOW_ADJUST. We can't do that directly, so we accompany - * 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. - */ - if (newwin == c->locmaxwin && - !(s->ppl.remote_bugs & BUG_CHOKES_ON_WINADJ)) { - up = snew(unsigned); - *up = newwin - c->locwindow; - pktout = ssh2_chanreq_init(c, "winadj@putty.projects.tartarus.org", - ssh2_handle_winadj_response, up); - pq_push(s->ppl.out_pq, pktout); - - if (c->throttle_state != UNTHROTTLED) - c->throttle_state = UNTHROTTLING; - } else { - /* Pretend the WINDOW_ADJUST was acked immediately. */ - c->remlocwin = newwin; - c->throttle_state = THROTTLED; - } - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_CHANNEL_WINDOW_ADJUST); - put_uint32(pktout, c->remoteid); - put_uint32(pktout, newwin - c->locwindow); - pq_push(s->ppl.out_pq, pktout); - c->locwindow = newwin; - } -} - -static PktIn *ssh2_connection_pop(struct ssh2_connection_state *s) -{ - ssh2_connection_filter_queue(s); - return pq_pop(s->ppl.in_pq); -} - -static void ssh2_connection_process_queue(PacketProtocolLayer *ppl) -{ - struct ssh2_connection_state *s = - container_of(ppl, struct ssh2_connection_state, ppl); - PktIn *pktin; - - if (ssh2_connection_filter_queue(s)) /* no matter why we were called */ - return; - - crBegin(s->crState); - - if (s->connshare) - share_activate(s->connshare, s->peer_verstring); - - /* - * Signal the seat that authentication is done, so that it can - * deploy spoofing defences. If it doesn't have any, deploy our - * own fallback one. - * - * We do this here rather than at the end of userauth, because we - * might not have gone through userauth at all (if we're a - * connection-sharing downstream). - */ - if (ssh2_connection_need_antispoof_prompt(s)) { - s->antispoof_prompt = new_prompts(); - s->antispoof_prompt->to_server = true; - s->antispoof_prompt->from_server = false; - s->antispoof_prompt->name = dupstr("Authentication successful"); - add_prompt( - s->antispoof_prompt, - dupstr("Access granted. Press Return to begin session. "), false); - s->antispoof_ret = seat_get_userpass_input( - s->ppl.seat, s->antispoof_prompt, NULL); - while (1) { - while (s->antispoof_ret < 0 && - bufchain_size(s->ppl.user_input) > 0) - s->antispoof_ret = seat_get_userpass_input( - s->ppl.seat, s->antispoof_prompt, s->ppl.user_input); - - if (s->antispoof_ret >= 0) - break; - - s->want_user_input = true; - crReturnV; - s->want_user_input = false; - } - free_prompts(s->antispoof_prompt); - s->antispoof_prompt = NULL; - } - - /* - * Enable port forwardings. - */ - portfwdmgr_config(s->portfwdmgr, s->conf); - s->portfwdmgr_configured = true; - - /* - * Create the main session channel, if any. - */ - s->mainchan = mainchan_new( - &s->ppl, &s->cl, s->conf, s->term_width, s->term_height, - s->ssh_is_simple, &s->mainchan_sc); - s->started = true; - - /* - * Transfer data! - */ - - while (1) { - if ((pktin = ssh2_connection_pop(s)) != NULL) { - - /* - * _All_ the connection-layer packets we expect to - * receive are now handled by the dispatch table. - * Anything that reaches here must be bogus. - */ - - ssh_proto_error(s->ppl.ssh, "Received unexpected connection-layer " - "packet, type %d (%s)", pktin->type, - ssh2_pkt_type(s->ppl.bpp->pls->kctx, - s->ppl.bpp->pls->actx, - pktin->type)); - return; - } - crReturnV; - } - - crFinishV; -} - -static void ssh2_channel_check_close(struct ssh2_channel *c) -{ - struct ssh2_connection_state *s = c->connlayer; - PktOut *pktout; - - if (c->halfopen) { - /* - * If we've sent out our own CHANNEL_OPEN but not yet seen - * either OPEN_CONFIRMATION or OPEN_FAILURE in response, then - * it's too early to be sending close messages of any kind. - */ - return; - } - - if (chan_want_close(c->chan, (c->closes & CLOSES_SENT_EOF), - (c->closes & CLOSES_RCVD_EOF)) && - !c->chanreq_head && - !(c->closes & CLOSES_SENT_CLOSE)) { - /* - * We have both sent and received EOF (or the channel is a - * zombie), and we have no outstanding channel requests, which - * means the channel is in final wind-up. But we haven't sent - * CLOSE, so let's do so now. - */ - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_CHANNEL_CLOSE); - put_uint32(pktout, c->remoteid); - pq_push(s->ppl.out_pq, pktout); - c->closes |= CLOSES_SENT_EOF | CLOSES_SENT_CLOSE; - } - - if (!((CLOSES_SENT_CLOSE | CLOSES_RCVD_CLOSE) & ~c->closes)) { - assert(c->chanreq_head == NULL); - /* - * We have both sent and received CLOSE, which means we're - * completely done with the channel. - */ - ssh2_channel_destroy(c); - } -} - -static void ssh2_channel_try_eof(struct ssh2_channel *c) -{ - struct ssh2_connection_state *s = c->connlayer; - PktOut *pktout; - assert(c->pending_eof); /* precondition for calling us */ - if (c->halfopen) - return; /* can't close: not even opened yet */ - if (bufchain_size(&c->outbuffer) > 0 || bufchain_size(&c->errbuffer) > 0) - return; /* can't send EOF: pending outgoing data */ - - c->pending_eof = false; /* we're about to send it */ - - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_CHANNEL_EOF); - put_uint32(pktout, c->remoteid); - pq_push(s->ppl.out_pq, pktout); - c->closes |= CLOSES_SENT_EOF; - ssh2_channel_check_close(c); -} - -/* - * Attempt to send data on an SSH-2 channel. - */ -static size_t ssh2_try_send(struct ssh2_channel *c) -{ - struct ssh2_connection_state *s = c->connlayer; - PktOut *pktout; - size_t bufsize; - - if (!c->halfopen) { - while (c->remwindow > 0 && - (bufchain_size(&c->outbuffer) > 0 || - bufchain_size(&c->errbuffer) > 0)) { - bufchain *buf = (bufchain_size(&c->errbuffer) > 0 ? - &c->errbuffer : &c->outbuffer); - - ptrlen data = bufchain_prefix(buf); - if (data.len > c->remwindow) - data.len = c->remwindow; - if (data.len > c->remmaxpkt) - data.len = c->remmaxpkt; - if (buf == &c->errbuffer) { - pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH2_MSG_CHANNEL_EXTENDED_DATA); - put_uint32(pktout, c->remoteid); - put_uint32(pktout, SSH2_EXTENDED_DATA_STDERR); - } else { - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_CHANNEL_DATA); - put_uint32(pktout, c->remoteid); - } - put_stringpl(pktout, data); - pq_push(s->ppl.out_pq, pktout); - bufchain_consume(buf, data.len); - c->remwindow -= data.len; - } - } - - /* - * After having sent as much data as we can, return the amount - * still buffered. - */ - bufsize = bufchain_size(&c->outbuffer) + bufchain_size(&c->errbuffer); - - /* - * And if there's no data pending but we need to send an EOF, send - * it. - */ - if (!bufsize && c->pending_eof) - ssh2_channel_try_eof(c); - - return bufsize; -} - -static void ssh2_try_send_and_unthrottle(struct ssh2_channel *c) -{ - int bufsize; - if (c->closes & CLOSES_SENT_EOF) - return; /* don't send on channels we've EOFed */ - bufsize = ssh2_try_send(c); - if (bufsize == 0) { - c->throttled_by_backlog = false; - ssh2_channel_check_throttle(c); - } -} - -static void ssh2_channel_check_throttle(struct ssh2_channel *c) -{ - /* - * We don't want this channel to read further input if this - * particular channel has a backed-up SSH window, or if the - * outgoing side of the whole SSH connection is currently - * throttled, or if this channel already has an outgoing EOF - * either sent or pending. - */ - chan_set_input_wanted(c->chan, - !c->throttled_by_backlog && - !c->connlayer->all_channels_throttled && - !c->pending_eof && - !(c->closes & CLOSES_SENT_EOF)); -} - -/* - * Close any local socket and free any local resources associated with - * a channel. This converts the channel into a zombie. - */ -static void ssh2_channel_close_local(struct ssh2_channel *c, - const char *reason) -{ - struct ssh2_connection_state *s = c->connlayer; - PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ - char *msg = NULL; - - if (c->sharectx) - return; - - msg = chan_log_close_msg(c->chan); - - if (msg) - ppl_logevent("%s%s%s", msg, reason ? " " : "", reason ? reason : ""); - - sfree(msg); - - chan_free(c->chan); - c->chan = zombiechan_new(); -} - -static void ssh2_check_termination_callback(void *vctx) -{ - struct ssh2_connection_state *s = (struct ssh2_connection_state *)vctx; - ssh2_check_termination(s); -} - -static void ssh2_channel_destroy(struct ssh2_channel *c) -{ - struct ssh2_connection_state *s = c->connlayer; - - assert(c->chanreq_head == NULL); - - ssh2_channel_close_local(c, NULL); - del234(s->channels, c); - ssh2_channel_free(c); - - /* - * If that was the last channel left open, we might need to - * terminate. But we'll be a bit cautious, by doing that in a - * toplevel callback, just in case anything on the current call - * stack objects to this entire PPL being freed. - */ - queue_toplevel_callback(ssh2_check_termination_callback, s); -} - -static void ssh2_check_termination(struct ssh2_connection_state *s) -{ - /* - * Decide whether we should terminate the SSH connection now. - * Called after a channel or a downstream goes away. The general - * policy is that we terminate when none of either is left. - */ - - if (s->persistent) - return; /* persistent mode: never proactively terminate */ - - if (!s->started) { - /* At startup, we don't have any channels open because we - * haven't got round to opening the main one yet. In that - * situation, we don't want to terminate, even if a sharing - * connection opens and closes and causes a call to this - * function. */ - return; - } - - if (count234(s->channels) == 0 && - !(s->connshare && share_ndownstreams(s->connshare) > 0)) { - /* - * We used to send SSH_MSG_DISCONNECT here, because I'd - * believed that _every_ conforming SSH-2 connection had to - * end with a disconnect being sent by at least one side; - * apparently I was wrong and it's perfectly OK to - * unceremoniously slam the connection shut when you're done, - * and indeed OpenSSH feels this is more polite than sending a - * DISCONNECT. So now we don't. - */ - ssh_user_close(s->ppl.ssh, "All channels closed"); - return; - } -} - -/* - * Set up most of a new ssh2_channel. Nulls out sharectx, but leaves - * chan untouched (since it will sometimes have been filled in before - * calling this). - */ -void ssh2_channel_init(struct ssh2_channel *c) -{ - struct ssh2_connection_state *s = c->connlayer; - c->closes = 0; - c->pending_eof = false; - c->throttling_conn = false; - c->throttled_by_backlog = false; - c->sharectx = NULL; - c->locwindow = c->locmaxwin = c->remlocwin = - s->ssh_is_simple ? OUR_V2_BIGWIN : OUR_V2_WINSIZE; - c->chanreq_head = NULL; - c->throttle_state = UNTHROTTLED; - bufchain_init(&c->outbuffer); - bufchain_init(&c->errbuffer); - c->sc.vt = &ssh2channel_vtable; - c->sc.cl = &s->cl; - c->localid = alloc_channel_id(s->channels, struct ssh2_channel); - add234(s->channels, c); -} - -/* - * Construct the common parts of a CHANNEL_OPEN. - */ -PktOut *ssh2_chanopen_init(struct ssh2_channel *c, const char *type) -{ - struct ssh2_connection_state *s = c->connlayer; - PktOut *pktout; - - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_CHANNEL_OPEN); - put_stringz(pktout, type); - put_uint32(pktout, c->localid); - put_uint32(pktout, c->locwindow); /* our window size */ - put_uint32(pktout, OUR_V2_MAXPKT); /* our max pkt size */ - return pktout; -} - -/* - * Construct the common parts of a CHANNEL_REQUEST. If handler is not - * NULL then a reply will be requested and the handler will be called - * when it arrives. The returned packet is ready to have any - * request-specific data added and be sent. Note that if a handler is - * provided, it's essential that the request actually be sent. - * - * The handler will usually be passed the response packet in pktin. If - * pktin is NULL, this means that no reply will ever be forthcoming - * (e.g. because the entire connection is being destroyed, or because - * the server initiated channel closure before we saw the response) - * and the handler should free any storage it's holding. - */ -PktOut *ssh2_chanreq_init(struct ssh2_channel *c, const char *type, - cr_handler_fn_t handler, void *ctx) -{ - struct ssh2_connection_state *s = c->connlayer; - PktOut *pktout; - - assert(!(c->closes & (CLOSES_SENT_CLOSE | CLOSES_RCVD_CLOSE))); - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_CHANNEL_REQUEST); - put_uint32(pktout, c->remoteid); - put_stringz(pktout, type); - put_bool(pktout, handler != NULL); - if (handler != NULL) { - struct outstanding_channel_request *ocr = - snew(struct outstanding_channel_request); - - ocr->handler = handler; - ocr->ctx = ctx; - ocr->next = NULL; - if (!c->chanreq_head) - c->chanreq_head = ocr; - else - c->chanreq_tail->next = ocr; - c->chanreq_tail = ocr; - } - return pktout; -} - -static Conf *ssh2channel_get_conf(SshChannel *sc) -{ - struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc); - struct ssh2_connection_state *s = c->connlayer; - return s->conf; -} - -static void ssh2channel_write_eof(SshChannel *sc) -{ - struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc); - - if (c->closes & CLOSES_SENT_EOF) - return; - - c->pending_eof = true; - ssh2_channel_try_eof(c); -} - -static void ssh2channel_initiate_close(SshChannel *sc, const char *err) -{ - struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc); - char *reason; - - reason = err ? dupprintf("due to local error: %s", err) : NULL; - ssh2_channel_close_local(c, reason); - sfree(reason); - c->pending_eof = false; /* this will confuse a zombie channel */ - - ssh2_channel_check_close(c); -} - -static void ssh2channel_unthrottle(SshChannel *sc, size_t bufsize) -{ - struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc); - struct ssh2_connection_state *s = c->connlayer; - size_t buflimit; - - buflimit = s->ssh_is_simple ? 0 : c->locmaxwin; - if (bufsize < buflimit) - ssh2_set_window(c, buflimit - bufsize); - - if (c->throttling_conn && bufsize <= buflimit) { - c->throttling_conn = false; - ssh_throttle_conn(s->ppl.ssh, -1); - } -} - -static size_t ssh2channel_write( - SshChannel *sc, bool is_stderr, const void *buf, size_t len) -{ - struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc); - assert(!(c->closes & CLOSES_SENT_EOF)); - bufchain_add(is_stderr ? &c->errbuffer : &c->outbuffer, buf, len); - return ssh2_try_send(c); -} - -static void ssh2channel_x11_sharing_handover( - SshChannel *sc, ssh_sharing_connstate *share_cs, share_channel *share_chan, - const char *peer_addr, int peer_port, int endian, - int protomajor, int protominor, const void *initial_data, int initial_len) -{ - struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc); - /* - * This function is called when we've just discovered that an X - * forwarding channel on which we'd been handling the initial auth - * ourselves turns out to be destined for a connection-sharing - * downstream. So we turn the channel into a sharing one, meaning - * that we completely stop tracking windows and buffering data and - * just pass more or less unmodified SSH messages back and forth. - */ - c->sharectx = share_cs; - share_setup_x11_channel(share_cs, share_chan, - c->localid, c->remoteid, c->remwindow, - c->remmaxpkt, c->locwindow, - peer_addr, peer_port, endian, - protomajor, protominor, - initial_data, initial_len); - chan_free(c->chan); - c->chan = NULL; -} - -static void ssh2channel_window_override_removed(SshChannel *sc) -{ - struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc); - struct ssh2_connection_state *s = c->connlayer; - - /* - * This function is called when a client-side Channel has just - * stopped requiring an initial fixed-size window. - */ - assert(!c->chan->initial_fixed_window_size); - ssh2_set_window(c, s->ssh_is_simple ? OUR_V2_BIGWIN : OUR_V2_WINSIZE); -} - -static void ssh2channel_hint_channel_is_simple(SshChannel *sc) -{ - struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc); - struct ssh2_connection_state *s = c->connlayer; - - PktOut *pktout = ssh2_chanreq_init( - c, "simple@putty.projects.tartarus.org", NULL, NULL); - pq_push(s->ppl.out_pq, pktout); -} - -static SshChannel *ssh2_lportfwd_open( - ConnectionLayer *cl, const char *hostname, int port, - const char *description, const SocketPeerInfo *pi, Channel *chan) -{ - struct ssh2_connection_state *s = - container_of(cl, struct ssh2_connection_state, cl); - struct ssh2_channel *c = snew(struct ssh2_channel); - PktOut *pktout; - - c->connlayer = s; - ssh2_channel_init(c); - c->halfopen = true; - c->chan = chan; - - pktout = ssh2_portfwd_chanopen(s, c, hostname, port, description, pi); - pq_push(s->ppl.out_pq, pktout); - - return &c->sc; -} - -static void ssh2_sharing_globreq_response( - struct ssh2_connection_state *s, PktIn *pktin, void *ctx) -{ - ssh_sharing_connstate *cs = (ssh_sharing_connstate *)ctx; - share_got_pkt_from_server(cs, pktin->type, - BinarySource_UPCAST(pktin)->data, - BinarySource_UPCAST(pktin)->len); -} - -static void ssh2_sharing_queue_global_request( - ConnectionLayer *cl, ssh_sharing_connstate *cs) -{ - struct ssh2_connection_state *s = - container_of(cl, struct ssh2_connection_state, cl); - ssh2_queue_global_request_handler(s, ssh2_sharing_globreq_response, cs); -} - -static void ssh2_sharing_no_more_downstreams(ConnectionLayer *cl) -{ - struct ssh2_connection_state *s = - container_of(cl, struct ssh2_connection_state, cl); - queue_toplevel_callback(ssh2_check_termination_callback, s); -} - -static struct X11FakeAuth *ssh2_add_x11_display( - ConnectionLayer *cl, int authtype, struct X11Display *disp) -{ - struct ssh2_connection_state *s = - container_of(cl, struct ssh2_connection_state, cl); - struct X11FakeAuth *auth = x11_invent_fake_auth(s->x11authtree, authtype); - auth->disp = disp; - return auth; -} - -static struct X11FakeAuth *ssh2_add_sharing_x11_display( - ConnectionLayer *cl, int authtype, ssh_sharing_connstate *share_cs, - share_channel *share_chan) -{ - struct ssh2_connection_state *s = - container_of(cl, struct ssh2_connection_state, cl); - struct X11FakeAuth *auth; - - /* - * Make up a new set of fake X11 auth data, and add it to the tree - * of currently valid ones with an indication of the sharing - * context that it's relevant to. - */ - auth = x11_invent_fake_auth(s->x11authtree, authtype); - auth->share_cs = share_cs; - auth->share_chan = share_chan; - - return auth; -} - -static void ssh2_remove_sharing_x11_display( - ConnectionLayer *cl, struct X11FakeAuth *auth) -{ - struct ssh2_connection_state *s = - container_of(cl, struct ssh2_connection_state, cl); - del234(s->x11authtree, auth); - x11_free_fake_auth(auth); -} - -static unsigned ssh2_alloc_sharing_channel( - ConnectionLayer *cl, ssh_sharing_connstate *connstate) -{ - struct ssh2_connection_state *s = - container_of(cl, struct ssh2_connection_state, cl); - struct ssh2_channel *c = snew(struct ssh2_channel); - - c->connlayer = s; - ssh2_channel_init(c); - c->chan = NULL; - c->sharectx = connstate; - return c->localid; -} - -static void ssh2_delete_sharing_channel(ConnectionLayer *cl, unsigned localid) -{ - struct ssh2_connection_state *s = - container_of(cl, struct ssh2_connection_state, cl); - struct ssh2_channel *c = find234(s->channels, &localid, ssh2_channelfind); - if (c) - ssh2_channel_destroy(c); -} - -static void ssh2_send_packet_from_downstream( - ConnectionLayer *cl, unsigned id, int type, - const void *data, int datalen, const char *additional_log_text) -{ - struct ssh2_connection_state *s = - container_of(cl, struct ssh2_connection_state, cl); - PktOut *pkt = ssh_bpp_new_pktout(s->ppl.bpp, type); - pkt->downstream_id = id; - pkt->additional_log_text = additional_log_text; - put_data(pkt, data, datalen); - pq_push(s->ppl.out_pq, pkt); -} - -static bool ssh2_agent_forwarding_permitted(ConnectionLayer *cl) -{ - struct ssh2_connection_state *s = - container_of(cl, struct ssh2_connection_state, cl); - return conf_get_bool(s->conf, CONF_agentfwd) && agent_exists(); -} - -static bool ssh2_connection_get_specials( - PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx) -{ - struct ssh2_connection_state *s = - container_of(ppl, struct ssh2_connection_state, ppl); - bool toret = false; - - if (s->mainchan) { - mainchan_get_specials(s->mainchan, add_special, ctx); - toret = true; - } - - /* - * 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 (!(s->ppl.remote_bugs & BUG_CHOKES_ON_SSH2_IGNORE)) { - if (toret) - add_special(ctx, NULL, SS_SEP, 0); - - add_special(ctx, "IGNORE message", SS_NOP, 0); - toret = true; - } - - return toret; -} - -static void ssh2_connection_special_cmd(PacketProtocolLayer *ppl, - SessionSpecialCode code, int arg) -{ - struct ssh2_connection_state *s = - container_of(ppl, struct ssh2_connection_state, ppl); - PktOut *pktout; - - if (code == SS_PING || code == SS_NOP) { - if (!(s->ppl.remote_bugs & BUG_CHOKES_ON_SSH2_IGNORE)) { - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_IGNORE); - put_stringz(pktout, ""); - pq_push(s->ppl.out_pq, pktout); - } - } else if (s->mainchan) { - mainchan_special_cmd(s->mainchan, code, arg); - } -} - -static void ssh2_terminal_size(ConnectionLayer *cl, int width, int height) -{ - struct ssh2_connection_state *s = - container_of(cl, struct ssh2_connection_state, cl); - - s->term_width = width; - s->term_height = height; - if (s->mainchan) - mainchan_terminal_size(s->mainchan, width, height); -} - -static void ssh2_stdout_unthrottle(ConnectionLayer *cl, size_t bufsize) -{ - struct ssh2_connection_state *s = - container_of(cl, struct ssh2_connection_state, cl); - - if (s->mainchan) - sshfwd_unthrottle(s->mainchan_sc, bufsize); -} - -static size_t ssh2_stdin_backlog(ConnectionLayer *cl) -{ - struct ssh2_connection_state *s = - container_of(cl, struct ssh2_connection_state, cl); - struct ssh2_channel *c; - - if (!s->mainchan) - return 0; - c = container_of(s->mainchan_sc, struct ssh2_channel, sc); - return s->mainchan ? - bufchain_size(&c->outbuffer) + bufchain_size(&c->errbuffer) : 0; -} - -static void ssh2_throttle_all_channels(ConnectionLayer *cl, bool throttled) -{ - struct ssh2_connection_state *s = - container_of(cl, struct ssh2_connection_state, cl); - struct ssh2_channel *c; - int i; - - s->all_channels_throttled = throttled; - - for (i = 0; NULL != (c = index234(s->channels, i)); i++) - if (!c->sharectx) - ssh2_channel_check_throttle(c); -} - -static bool ssh2_ldisc_option(ConnectionLayer *cl, int option) -{ - struct ssh2_connection_state *s = - container_of(cl, struct ssh2_connection_state, cl); - - return s->ldisc_opts[option]; -} - -static void ssh2_set_ldisc_option(ConnectionLayer *cl, int option, bool value) -{ - struct ssh2_connection_state *s = - container_of(cl, struct ssh2_connection_state, cl); - - s->ldisc_opts[option] = value; -} - -static void ssh2_enable_x_fwd(ConnectionLayer *cl) -{ - struct ssh2_connection_state *s = - container_of(cl, struct ssh2_connection_state, cl); - - s->X11_fwd_enabled = true; -} - -static void ssh2_set_wants_user_input(ConnectionLayer *cl, bool wanted) -{ - struct ssh2_connection_state *s = - container_of(cl, struct ssh2_connection_state, cl); - - s->want_user_input = wanted; -} - -static bool ssh2_connection_want_user_input(PacketProtocolLayer *ppl) -{ - struct ssh2_connection_state *s = - container_of(ppl, struct ssh2_connection_state, ppl); - return s->want_user_input; -} - -static void ssh2_connection_got_user_input(PacketProtocolLayer *ppl) -{ - struct ssh2_connection_state *s = - container_of(ppl, struct ssh2_connection_state, ppl); - - while (s->mainchan && bufchain_size(s->ppl.user_input) > 0) { - /* - * Add user input to the main channel's buffer. - */ - ptrlen data = bufchain_prefix(s->ppl.user_input); - sshfwd_write(s->mainchan_sc, data.ptr, data.len); - bufchain_consume(s->ppl.user_input, data.len); - } -} - -static void ssh2_connection_reconfigure(PacketProtocolLayer *ppl, Conf *conf) -{ - struct ssh2_connection_state *s = - container_of(ppl, struct ssh2_connection_state, ppl); - - conf_free(s->conf); - s->conf = conf_copy(conf); - - if (s->portfwdmgr_configured) - portfwdmgr_config(s->portfwdmgr, s->conf); -} diff --git a/ssh2connection.h b/ssh2connection.h deleted file mode 100644 index d3bb240a..00000000 --- a/ssh2connection.h +++ /dev/null @@ -1,235 +0,0 @@ -#ifndef PUTTY_SSH2CONNECTION_H -#define PUTTY_SSH2CONNECTION_H - -struct outstanding_channel_request; -struct outstanding_global_request; - -struct ssh2_connection_state { - int crState; - - ssh_sharing_state *connshare; - char *peer_verstring; - - mainchan *mainchan; - SshChannel *mainchan_sc; - bool ldisc_opts[LD_N_OPTIONS]; - int session_attempt, session_status; - int term_width, term_height; - bool want_user_input; - - bool ssh_is_simple; - bool persistent; - bool started; - - Conf *conf; - - tree234 *channels; /* indexed by local id */ - bool all_channels_throttled; - - bool X11_fwd_enabled; - tree234 *x11authtree; - - bool got_pty; - - tree234 *rportfwds; - PortFwdManager *portfwdmgr; - bool portfwdmgr_configured; - - prompts_t *antispoof_prompt; - int antispoof_ret; - - const SftpServerVtable *sftpserver_vt; - const SshServerConfig *ssc; - - /* - * These store the list of global requests that we're waiting for - * replies to. (REQUEST_FAILURE doesn't come with any indication - * of what message caused it, so we have to keep track of the - * queue ourselves.) - */ - struct outstanding_global_request *globreq_head, *globreq_tail; - - ConnectionLayer cl; - PacketProtocolLayer ppl; -}; - -typedef void (*gr_handler_fn_t)(struct ssh2_connection_state *s, - PktIn *pktin, void *ctx); -void ssh2_queue_global_request_handler( - struct ssh2_connection_state *s, gr_handler_fn_t handler, void *ctx); - -struct ssh2_channel { - struct ssh2_connection_state *connlayer; - - unsigned remoteid, localid; - int type; - /* True if we opened this channel but server hasn't confirmed. */ - bool halfopen; - - /* Bitmap of whether we've sent/received CHANNEL_EOF and - * CHANNEL_CLOSE. */ -#define CLOSES_SENT_EOF 1 -#define CLOSES_SENT_CLOSE 2 -#define CLOSES_RCVD_EOF 4 -#define CLOSES_RCVD_CLOSE 8 - int closes; - - /* - * This flag indicates that an EOF is pending on the outgoing side - * of the channel: that is, wherever we're getting the data for - * this channel has sent us some data followed by EOF. We can't - * actually send the EOF until we've finished sending the data, so - * we set this flag instead to remind us to do so once our buffer - * is clear. - */ - bool pending_eof; - - /* - * True if this channel is causing the underlying connection to be - * throttled. - */ - bool throttling_conn; - - /* - * True if we currently have backed-up data on the direction of - * this channel pointing out of the SSH connection, and therefore - * would prefer the 'Channel' implementation not to read further - * local input if possible. - */ - bool throttled_by_backlog; - - bufchain outbuffer, errbuffer; - 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 channel requests that we're waiting for - * replies to. (CHANNEL_FAILURE doesn't come with any indication - * of what message caused it, so we have to keep track of the - * queue ourselves.) - */ - struct outstanding_channel_request *chanreq_head, *chanreq_tail; - - enum { THROTTLED, UNTHROTTLING, UNTHROTTLED } throttle_state; - - ssh_sharing_connstate *sharectx; /* sharing context, if this is a - * downstream channel */ - Channel *chan; /* handle the client side of this channel, if not */ - SshChannel sc; /* entry point for chan to talk back to */ -}; - -typedef void (*cr_handler_fn_t)(struct ssh2_channel *, PktIn *, void *); - -void ssh2_channel_init(struct ssh2_channel *c); -PktOut *ssh2_chanreq_init(struct ssh2_channel *c, const char *type, - cr_handler_fn_t handler, void *ctx); - -typedef enum ChanopenOutcome { - CHANOPEN_RESULT_FAILURE, - CHANOPEN_RESULT_SUCCESS, - CHANOPEN_RESULT_DOWNSTREAM, -} ChanopenOutcome; - -typedef struct ChanopenResult { - ChanopenOutcome outcome; - union { - struct { - char *wire_message; /* must be freed by recipient */ - unsigned reason_code; - } failure; - struct { - Channel *channel; - } success; - struct { - ssh_sharing_connstate *share_ctx; - } downstream; - } u; -} ChanopenResult; - -PktOut *ssh2_chanopen_init(struct ssh2_channel *c, const char *type); - -PktOut *ssh2_portfwd_chanopen( - struct ssh2_connection_state *s, struct ssh2_channel *c, - const char *hostname, int port, - const char *description, const SocketPeerInfo *peerinfo); - -struct ssh_rportfwd *ssh2_rportfwd_alloc( - ConnectionLayer *cl, - const char *shost, int sport, const char *dhost, int dport, - int addressfamily, const char *log_description, PortFwdRecord *pfr, - ssh_sharing_connstate *share_ctx); -void ssh2_rportfwd_remove( - ConnectionLayer *cl, struct ssh_rportfwd *rpf); -SshChannel *ssh2_session_open(ConnectionLayer *cl, Channel *chan); -SshChannel *ssh2_serverside_x11_open( - ConnectionLayer *cl, Channel *chan, const SocketPeerInfo *pi); -SshChannel *ssh2_serverside_agent_open(ConnectionLayer *cl, Channel *chan); - -void ssh2channel_send_exit_status(SshChannel *c, int status); -void ssh2channel_send_exit_signal( - SshChannel *c, ptrlen signame, bool core_dumped, ptrlen msg); -void ssh2channel_send_exit_signal_numeric( - SshChannel *c, int signum, bool core_dumped, ptrlen msg); -void ssh2channel_request_x11_forwarding( - SshChannel *c, bool want_reply, const char *authproto, - const char *authdata, int screen_number, bool oneshot); -void ssh2channel_request_agent_forwarding(SshChannel *c, bool want_reply); -void ssh2channel_request_pty( - SshChannel *c, bool want_reply, Conf *conf, int w, int h); -bool ssh2channel_send_env_var( - SshChannel *c, bool want_reply, const char *var, const char *value); -void ssh2channel_start_shell(SshChannel *c, bool want_reply); -void ssh2channel_start_command( - SshChannel *c, bool want_reply, const char *command); -bool ssh2channel_start_subsystem( - SshChannel *c, bool want_reply, const char *subsystem); -bool ssh2channel_send_env_var( - SshChannel *c, bool want_reply, const char *var, const char *value); -bool ssh2channel_send_serial_break( - SshChannel *c, bool want_reply, int length); -bool ssh2channel_send_signal( - SshChannel *c, bool want_reply, const char *signame); -void ssh2channel_send_terminal_size_change(SshChannel *c, int w, int h); - -#define CHANOPEN_RETURN_FAILURE(code, msgparams) do \ - { \ - ChanopenResult toret; \ - toret.outcome = CHANOPEN_RESULT_FAILURE; \ - toret.u.failure.reason_code = code; \ - toret.u.failure.wire_message = dupprintf msgparams; \ - return toret; \ - } while (0) - -#define CHANOPEN_RETURN_SUCCESS(chan) do \ - { \ - ChanopenResult toret; \ - toret.outcome = CHANOPEN_RESULT_SUCCESS; \ - toret.u.success.channel = chan; \ - return toret; \ - } while (0) - -#define CHANOPEN_RETURN_DOWNSTREAM(shctx) do \ - { \ - ChanopenResult toret; \ - toret.outcome = CHANOPEN_RESULT_DOWNSTREAM; \ - toret.u.downstream.share_ctx = shctx; \ - return toret; \ - } while (0) - -ChanopenResult ssh2_connection_parse_channel_open( - struct ssh2_connection_state *s, ptrlen type, - PktIn *pktin, SshChannel *sc); - -bool ssh2_connection_parse_global_request( - struct ssh2_connection_state *s, ptrlen type, PktIn *pktin); - -bool ssh2_connection_need_antispoof_prompt(struct ssh2_connection_state *s); - -#endif /* PUTTY_SSH2CONNECTION_H */ diff --git a/ssh2kex-client.c b/ssh2kex-client.c deleted file mode 100644 index 1dd960c1..00000000 --- a/ssh2kex-client.c +++ /dev/null @@ -1,930 +0,0 @@ -/* - * Client side of key exchange for the SSH-2 transport protocol (RFC 4253). - */ - -#include - -#include "putty.h" -#include "ssh.h" -#include "sshbpp.h" -#include "sshppl.h" -#include "sshcr.h" -#include "storage.h" -#include "ssh2transport.h" -#include "mpint.h" - -/* - * Another copy of the symbol defined in mpunsafe.c. See the comment - * there. - */ -const int deliberate_symbol_clash = 12345; - -void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) -{ - PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ - PktIn *pktin; - PktOut *pktout; - - crBegin(s->crStateKex); - - if (s->kex_alg->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->out.cipher ? s->out.cipher->real_keybits : 0; - scbits = s->in.cipher ? s->in.cipher->real_keybits : 0; - 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 > s->kex_alg->hash->hlen * 8) - s->nbits = s->kex_alg->hash->hlen * 8; - - /* - * If we're doing Diffie-Hellman group exchange, start by - * requesting a group. - */ - if (dh_is_gex(s->kex_alg)) { - ppl_logevent("Doing Diffie-Hellman group exchange"); - s->ppl.bpp->pls->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); - if (s->pbits < DH_MIN_SIZE) - s->pbits = DH_MIN_SIZE; - if (s->pbits > DH_MAX_SIZE) - s->pbits = DH_MAX_SIZE; - if ((s->ppl.remote_bugs & BUG_SSH2_OLDGEX)) { - pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH2_MSG_KEX_DH_GEX_REQUEST_OLD); - put_uint32(pktout, s->pbits); - } else { - pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH2_MSG_KEX_DH_GEX_REQUEST); - put_uint32(pktout, DH_MIN_SIZE); - put_uint32(pktout, s->pbits); - put_uint32(pktout, DH_MAX_SIZE); - } - pq_push(s->ppl.out_pq, pktout); - - crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL); - if (pktin->type != SSH2_MSG_KEX_DH_GEX_GROUP) { - ssh_proto_error(s->ppl.ssh, "Received unexpected packet when " - "expecting Diffie-Hellman group, type %d (%s)", - pktin->type, - ssh2_pkt_type(s->ppl.bpp->pls->kctx, - s->ppl.bpp->pls->actx, - pktin->type)); - *aborted = true; - return; - } - s->p = get_mp_ssh2(pktin); - s->g = get_mp_ssh2(pktin); - if (get_err(pktin)) { - ssh_proto_error(s->ppl.ssh, - "Unable to parse Diffie-Hellman group packet"); - *aborted = true; - return; - } - s->dh_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; - - ppl_logevent("Doing Diffie-Hellman key exchange using %d-bit " - "modulus and hash %s with a server-supplied group", - dh_modulus_bit_size(s->dh_ctx), - ssh_hash_alg(s->exhash)->text_name); - } else { - s->ppl.bpp->pls->kctx = SSH2_PKTCTX_DHGROUP; - s->dh_ctx = dh_setup_group(s->kex_alg); - s->kex_init_value = SSH2_MSG_KEXDH_INIT; - s->kex_reply_value = SSH2_MSG_KEXDH_REPLY; - - ppl_logevent("Doing Diffie-Hellman key exchange using %d-bit " - "modulus and hash %s with standard group \"%s\"", - dh_modulus_bit_size(s->dh_ctx), - ssh_hash_alg(s->exhash)->text_name, - s->kex_alg->groupname); - } - - /* - * Now generate and send e for Diffie-Hellman. - */ - seat_set_busy_status(s->ppl.seat, BUSY_CPU); - s->e = dh_create_e(s->dh_ctx, s->nbits * 2); - pktout = ssh_bpp_new_pktout(s->ppl.bpp, s->kex_init_value); - put_mp_ssh2(pktout, s->e); - pq_push(s->ppl.out_pq, pktout); - - seat_set_busy_status(s->ppl.seat, BUSY_WAITING); - crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL); - if (pktin->type != s->kex_reply_value) { - ssh_proto_error(s->ppl.ssh, "Received unexpected packet when " - "expecting Diffie-Hellman reply, type %d (%s)", - pktin->type, - ssh2_pkt_type(s->ppl.bpp->pls->kctx, - s->ppl.bpp->pls->actx, - pktin->type)); - *aborted = true; - return; - } - seat_set_busy_status(s->ppl.seat, BUSY_CPU); - s->hostkeydata = get_string(pktin); - s->hkey = ssh_key_new_pub(s->hostkey_alg, s->hostkeydata); - s->f = get_mp_ssh2(pktin); - s->sigdata = get_string(pktin); - if (get_err(pktin)) { - ssh_proto_error(s->ppl.ssh, - "Unable to parse Diffie-Hellman reply packet"); - *aborted = true; - return; - } - - { - const char *err = dh_validate_f(s->dh_ctx, s->f); - if (err) { - ssh_proto_error(s->ppl.ssh, "Diffie-Hellman reply failed " - "validation: %s", err); - *aborted = true; - return; - } - } - s->K = dh_find_K(s->dh_ctx, s->f); - - /* We assume everything from now on will be quick, and it might - * involve user interaction. */ - seat_set_busy_status(s->ppl.seat, BUSY_NOT); - - put_stringpl(s->exhash, s->hostkeydata); - if (dh_is_gex(s->kex_alg)) { - if (!(s->ppl.remote_bugs & BUG_SSH2_OLDGEX)) - put_uint32(s->exhash, DH_MIN_SIZE); - put_uint32(s->exhash, s->pbits); - if (!(s->ppl.remote_bugs & BUG_SSH2_OLDGEX)) - put_uint32(s->exhash, DH_MAX_SIZE); - put_mp_ssh2(s->exhash, s->p); - put_mp_ssh2(s->exhash, s->g); - } - put_mp_ssh2(s->exhash, s->e); - put_mp_ssh2(s->exhash, s->f); - - dh_cleanup(s->dh_ctx); - s->dh_ctx = NULL; - mp_free(s->f); s->f = NULL; - if (dh_is_gex(s->kex_alg)) { - mp_free(s->g); s->g = NULL; - mp_free(s->p); s->p = NULL; - } - } else if (s->kex_alg->main_type == KEXTYPE_ECDH) { - - ppl_logevent("Doing ECDH key exchange with curve %s and hash %s", - ssh_ecdhkex_curve_textname(s->kex_alg), - ssh_hash_alg(s->exhash)->text_name); - s->ppl.bpp->pls->kctx = SSH2_PKTCTX_ECDHKEX; - - s->ecdh_key = ssh_ecdhkex_newkey(s->kex_alg); - if (!s->ecdh_key) { - ssh_sw_abort(s->ppl.ssh, "Unable to generate key for ECDH"); - *aborted = true; - return; - } - - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEX_ECDH_INIT); - { - strbuf *pubpoint = strbuf_new(); - ssh_ecdhkex_getpublic(s->ecdh_key, BinarySink_UPCAST(pubpoint)); - put_stringsb(pktout, pubpoint); - } - - pq_push(s->ppl.out_pq, pktout); - - crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL); - if (pktin->type != SSH2_MSG_KEX_ECDH_REPLY) { - ssh_proto_error(s->ppl.ssh, "Received unexpected packet when " - "expecting ECDH reply, type %d (%s)", pktin->type, - ssh2_pkt_type(s->ppl.bpp->pls->kctx, - s->ppl.bpp->pls->actx, - pktin->type)); - *aborted = true; - return; - } - - s->hostkeydata = get_string(pktin); - put_stringpl(s->exhash, s->hostkeydata); - s->hkey = ssh_key_new_pub(s->hostkey_alg, s->hostkeydata); - - { - strbuf *pubpoint = strbuf_new(); - ssh_ecdhkex_getpublic(s->ecdh_key, BinarySink_UPCAST(pubpoint)); - put_string(s->exhash, pubpoint->u, pubpoint->len); - strbuf_free(pubpoint); - } - - { - ptrlen keydata = get_string(pktin); - put_stringpl(s->exhash, keydata); - s->K = ssh_ecdhkex_getkey(s->ecdh_key, keydata); - if (!get_err(pktin) && !s->K) { - ssh_proto_error(s->ppl.ssh, "Received invalid elliptic curve " - "point in ECDH reply"); - *aborted = true; - return; - } - } - - s->sigdata = get_string(pktin); - if (get_err(pktin)) { - ssh_proto_error(s->ppl.ssh, "Unable to parse ECDH reply packet"); - *aborted = true; - return; - } - - ssh_ecdhkex_freekey(s->ecdh_key); - s->ecdh_key = NULL; -#ifndef NO_GSSAPI - } else if (s->kex_alg->main_type == KEXTYPE_GSS) { - ptrlen data; - - s->ppl.bpp->pls->kctx = SSH2_PKTCTX_GSSKEX; - s->init_token_sent = false; - s->complete_rcvd = false; - s->hkey = NULL; - s->keystr = NULL; - - /* - * 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... - * - * This is rote from the KEXTYPE_DH section above. - */ - { - int csbits, scbits; - - csbits = s->out.cipher->real_keybits; - scbits = s->in.cipher->real_keybits; - 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 > s->kex_alg->hash->hlen * 8) - s->nbits = s->kex_alg->hash->hlen * 8; - - if (dh_is_gex(s->kex_alg)) { - /* - * Work out how big a DH group we will need to allow that - * much data. - */ - s->pbits = 512 << ((s->nbits - 1) / 64); - ppl_logevent("Doing GSSAPI (with Kerberos V5) Diffie-Hellman " - "group exchange, with minimum %d bits", s->pbits); - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEXGSS_GROUPREQ); - put_uint32(pktout, s->pbits); /* min */ - put_uint32(pktout, s->pbits); /* preferred */ - put_uint32(pktout, s->pbits * 2); /* max */ - pq_push(s->ppl.out_pq, pktout); - - crMaybeWaitUntilV( - (pktin = ssh2_transport_pop(s)) != NULL); - if (pktin->type != SSH2_MSG_KEXGSS_GROUP) { - ssh_proto_error(s->ppl.ssh, "Received unexpected packet when " - "expecting Diffie-Hellman group, type %d (%s)", - pktin->type, - ssh2_pkt_type(s->ppl.bpp->pls->kctx, - s->ppl.bpp->pls->actx, - pktin->type)); - *aborted = true; - return; - } - s->p = get_mp_ssh2(pktin); - s->g = get_mp_ssh2(pktin); - if (get_err(pktin)) { - ssh_proto_error(s->ppl.ssh, - "Unable to parse Diffie-Hellman group packet"); - *aborted = true; - return; - } - s->dh_ctx = dh_setup_gex(s->p, s->g); - } else { - s->dh_ctx = dh_setup_group(s->kex_alg); - ppl_logevent("Using GSSAPI (with Kerberos V5) Diffie-Hellman with" - " standard group \"%s\"", s->kex_alg->groupname); - } - - ppl_logevent("Doing GSSAPI (with Kerberos V5) Diffie-Hellman key " - "exchange with hash %s", ssh_hash_alg(s->exhash)->text_name); - /* Now generate e for Diffie-Hellman. */ - seat_set_busy_status(s->ppl.seat, BUSY_CPU); - s->e = dh_create_e(s->dh_ctx, s->nbits * 2); - - if (s->shgss->lib->gsslogmsg) - ppl_logevent("%s", s->shgss->lib->gsslogmsg); - - /* initial tokens are empty */ - SSH_GSS_CLEAR_BUF(&s->gss_rcvtok); - SSH_GSS_CLEAR_BUF(&s->gss_sndtok); - SSH_GSS_CLEAR_BUF(&s->mic); - s->gss_stat = s->shgss->lib->acquire_cred( - s->shgss->lib, &s->shgss->ctx, &s->gss_cred_expiry); - if (s->gss_stat != SSH_GSS_OK) { - ssh_sw_abort(s->ppl.ssh, - "GSSAPI key exchange failed to initialise"); - *aborted = true; - return; - } - - /* now enter the loop */ - assert(s->shgss->srv_name); - do { - /* - * When acquire_cred yields no useful expiration, go with the - * service ticket expiration. - */ - s->gss_stat = s->shgss->lib->init_sec_context( - s->shgss->lib, &s->shgss->ctx, s->shgss->srv_name, - s->gss_delegate, &s->gss_rcvtok, &s->gss_sndtok, - (s->gss_cred_expiry == GSS_NO_EXPIRATION ? - &s->gss_cred_expiry : NULL), NULL); - SSH_GSS_CLEAR_BUF(&s->gss_rcvtok); - - if (s->gss_stat == SSH_GSS_S_COMPLETE && s->complete_rcvd) - break; /* MIC is verified after the loop */ - - if (s->gss_stat != SSH_GSS_S_COMPLETE && - s->gss_stat != SSH_GSS_S_CONTINUE_NEEDED) { - if (s->shgss->lib->display_status( - s->shgss->lib, s->shgss->ctx, - &s->gss_buf) == SSH_GSS_OK) { - char *err = s->gss_buf.value; - ssh_sw_abort(s->ppl.ssh, - "GSSAPI key exchange failed to initialise " - "context: %s", err); - sfree(err); - *aborted = true; - return; - } - } - assert(s->gss_stat == SSH_GSS_S_COMPLETE || - s->gss_stat == SSH_GSS_S_CONTINUE_NEEDED); - - if (!s->init_token_sent) { - s->init_token_sent = true; - pktout = ssh_bpp_new_pktout(s->ppl.bpp, - SSH2_MSG_KEXGSS_INIT); - if (s->gss_sndtok.length == 0) { - ssh_sw_abort(s->ppl.ssh, "GSSAPI key exchange failed: " - "no initial context token"); - *aborted = true; - return; - } - put_string(pktout, - s->gss_sndtok.value, s->gss_sndtok.length); - put_mp_ssh2(pktout, s->e); - pq_push(s->ppl.out_pq, pktout); - s->shgss->lib->free_tok(s->shgss->lib, &s->gss_sndtok); - ppl_logevent("GSSAPI key exchange initialised"); - } else if (s->gss_sndtok.length != 0) { - pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH2_MSG_KEXGSS_CONTINUE); - put_string(pktout, - s->gss_sndtok.value, s->gss_sndtok.length); - pq_push(s->ppl.out_pq, pktout); - s->shgss->lib->free_tok(s->shgss->lib, &s->gss_sndtok); - } - - if (s->gss_stat == SSH_GSS_S_COMPLETE && s->complete_rcvd) - break; - - wait_for_gss_token: - crMaybeWaitUntilV( - (pktin = ssh2_transport_pop(s)) != NULL); - switch (pktin->type) { - case SSH2_MSG_KEXGSS_CONTINUE: - data = get_string(pktin); - s->gss_rcvtok.value = (char *)data.ptr; - s->gss_rcvtok.length = data.len; - continue; - case SSH2_MSG_KEXGSS_COMPLETE: - s->complete_rcvd = true; - s->f = get_mp_ssh2(pktin); - data = get_string(pktin); - s->mic.value = (char *)data.ptr; - s->mic.length = data.len; - /* If there's a final token we loop to consume it */ - if (get_bool(pktin)) { - data = get_string(pktin); - s->gss_rcvtok.value = (char *)data.ptr; - s->gss_rcvtok.length = data.len; - continue; - } - break; - case SSH2_MSG_KEXGSS_HOSTKEY: - s->hostkeydata = get_string(pktin); - if (s->hostkey_alg) { - s->hkey = ssh_key_new_pub(s->hostkey_alg, - s->hostkeydata); - put_stringpl(s->exhash, s->hostkeydata); - } - /* - * Can't loop as we have no token to pass to - * init_sec_context. - */ - goto wait_for_gss_token; - case SSH2_MSG_KEXGSS_ERROR: - /* - * We have no use for the server's major and minor - * status. The minor status is really only - * meaningful to the server, and with luck the major - * status means something to us (but not really all - * that much). The string is more meaningful, and - * hopefully the server sends any error tokens, as - * that will produce the most useful information for - * us. - */ - get_uint32(pktin); /* server's major status */ - get_uint32(pktin); /* server's minor status */ - data = get_string(pktin); - ppl_logevent("GSSAPI key exchange failed; " - "server's message: %.*s", PTRLEN_PRINTF(data)); - /* Language tag, but we have no use for it */ - get_string(pktin); - /* - * Wait for an error token, if there is one, or the - * server's disconnect. The error token, if there - * is one, must follow the SSH2_MSG_KEXGSS_ERROR - * message, per the RFC. - */ - goto wait_for_gss_token; - default: - ssh_proto_error(s->ppl.ssh, "Received unexpected packet " - "during GSSAPI key exchange, type %d (%s)", - pktin->type, - ssh2_pkt_type(s->ppl.bpp->pls->kctx, - s->ppl.bpp->pls->actx, - pktin->type)); - *aborted = true; - return; - } - } while (s->gss_rcvtok.length || - s->gss_stat == SSH_GSS_S_CONTINUE_NEEDED || - !s->complete_rcvd); - - { - const char *err = dh_validate_f(s->dh_ctx, s->f); - if (err) { - ssh_proto_error(s->ppl.ssh, "GSSAPI reply failed " - "validation: %s", err); - *aborted = true; - return; - } - } - s->K = dh_find_K(s->dh_ctx, s->f); - - /* We assume everything from now on will be quick, and it might - * involve user interaction. */ - seat_set_busy_status(s->ppl.seat, BUSY_NOT); - - if (!s->hkey) - put_stringz(s->exhash, ""); - if (dh_is_gex(s->kex_alg)) { - /* min, preferred, max */ - put_uint32(s->exhash, s->pbits); - put_uint32(s->exhash, s->pbits); - put_uint32(s->exhash, s->pbits * 2); - - put_mp_ssh2(s->exhash, s->p); - put_mp_ssh2(s->exhash, s->g); - } - put_mp_ssh2(s->exhash, s->e); - put_mp_ssh2(s->exhash, s->f); - - /* - * MIC verification is done below, after we compute the hash - * used as the MIC input. - */ - - dh_cleanup(s->dh_ctx); - s->dh_ctx = NULL; - mp_free(s->f); s->f = NULL; - if (dh_is_gex(s->kex_alg)) { - mp_free(s->g); s->g = NULL; - mp_free(s->p); s->p = NULL; - } -#endif - } else { - ptrlen rsakeydata; - - assert(s->kex_alg->main_type == KEXTYPE_RSA); - ppl_logevent("Doing RSA key exchange with hash %s", - ssh_hash_alg(s->exhash)->text_name); - s->ppl.bpp->pls->kctx = SSH2_PKTCTX_RSAKEX; - /* - * RSA key exchange. First expect a KEXRSA_PUBKEY packet - * from the server. - */ - crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL); - if (pktin->type != SSH2_MSG_KEXRSA_PUBKEY) { - ssh_proto_error(s->ppl.ssh, "Received unexpected packet when " - "expecting RSA public key, type %d (%s)", - pktin->type, - ssh2_pkt_type(s->ppl.bpp->pls->kctx, - s->ppl.bpp->pls->actx, - pktin->type)); - *aborted = true; - return; - } - - s->hostkeydata = get_string(pktin); - put_stringpl(s->exhash, s->hostkeydata); - s->hkey = ssh_key_new_pub(s->hostkey_alg, s->hostkeydata); - - rsakeydata = get_string(pktin); - - s->rsa_kex_key = ssh_rsakex_newkey(rsakeydata); - if (!s->rsa_kex_key) { - ssh_proto_error(s->ppl.ssh, - "Unable to parse RSA public key packet"); - *aborted = true; - return; - } - s->rsa_kex_key_needs_freeing = true; - - put_stringpl(s->exhash, rsakeydata); - - /* - * 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->rsa_kex_key); - - const struct ssh_rsa_kex_extra *extra = - (const struct ssh_rsa_kex_extra *)s->kex_alg->extra; - if (klen < extra->minklen) { - ssh_proto_error(s->ppl.ssh, "Server sent %d-bit RSA key, " - "less than the minimum size %d for %s " - "key exchange", klen, extra->minklen, - s->kex_alg->name); - *aborted = true; - return; - } - - int nbits = klen - (2*s->kex_alg->hash->hlen*8 + 49); - assert(nbits > 0); - - strbuf *buf, *outstr; - - mp_int *tmp = mp_random_bits(nbits - 1); - s->K = mp_power_2(nbits - 1); - mp_add_into(s->K, s->K, tmp); - mp_free(tmp); - - /* - * Encode this as an mpint. - */ - buf = strbuf_new_nm(); - put_mp_ssh2(buf, s->K); - - /* - * Encrypt it with the given RSA key. - */ - outstr = ssh_rsakex_encrypt(s->rsa_kex_key, s->kex_alg->hash, - ptrlen_from_strbuf(buf)); - - /* - * And send it off in a return packet. - */ - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEXRSA_SECRET); - put_stringpl(pktout, ptrlen_from_strbuf(outstr)); - pq_push(s->ppl.out_pq, pktout); - - put_stringsb(s->exhash, outstr); /* frees outstr */ - - strbuf_free(buf); - } - - ssh_rsakex_freekey(s->rsa_kex_key); - s->rsa_kex_key = NULL; - s->rsa_kex_key_needs_freeing = false; - - crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL); - if (pktin->type != SSH2_MSG_KEXRSA_DONE) { - ssh_proto_error(s->ppl.ssh, "Received unexpected packet when " - "expecting RSA kex signature, type %d (%s)", - pktin->type, - ssh2_pkt_type(s->ppl.bpp->pls->kctx, - s->ppl.bpp->pls->actx, - pktin->type)); - *aborted = true; - return; - } - - s->sigdata = get_string(pktin); - if (get_err(pktin)) { - ssh_proto_error(s->ppl.ssh, "Unable to parse RSA kex signature"); - *aborted = true; - return; - } - } - - ssh2transport_finalise_exhash(s); - -#ifndef NO_GSSAPI - if (s->kex_alg->main_type == KEXTYPE_GSS) { - Ssh_gss_buf gss_buf; - SSH_GSS_CLEAR_BUF(&s->gss_buf); - - gss_buf.value = s->exchange_hash; - gss_buf.length = s->kex_alg->hash->hlen; - s->gss_stat = s->shgss->lib->verify_mic( - s->shgss->lib, s->shgss->ctx, &gss_buf, &s->mic); - if (s->gss_stat != SSH_GSS_OK) { - if (s->shgss->lib->display_status( - s->shgss->lib, s->shgss->ctx, &s->gss_buf) == SSH_GSS_OK) { - char *err = s->gss_buf.value; - ssh_sw_abort(s->ppl.ssh, "GSSAPI key exchange MIC was " - "not valid: %s", err); - sfree(err); - } else { - ssh_sw_abort(s->ppl.ssh, "GSSAPI key exchange MIC was " - "not valid"); - } - *aborted = true; - return; - } - - s->gss_kex_used = true; - - /*- - * If this the first KEX, save the GSS context for "gssapi-keyex" - * authentication. - * - * http://tools.ietf.org/html/rfc4462#section-4 - * - * This method may be used only if the initial key exchange was - * performed using a GSS-API-based key exchange method defined in - * accordance with Section 2. The GSS-API context used with this - * method is always that established during an initial GSS-API-based - * key exchange. Any context established during key exchange for the - * purpose of rekeying MUST NOT be used with this method. - */ - if (s->got_session_id) { - s->shgss->lib->release_cred(s->shgss->lib, &s->shgss->ctx); - } - ppl_logevent("GSSAPI Key Exchange complete!"); - } -#endif - - s->dh_ctx = NULL; - - /* In GSS keyex there's no hostkey signature to verify */ - if (s->kex_alg->main_type != KEXTYPE_GSS) { - if (!s->hkey) { - ssh_proto_error(s->ppl.ssh, "Server's host key is invalid"); - *aborted = true; - return; - } - - if (!ssh_key_verify( - s->hkey, s->sigdata, - make_ptrlen(s->exchange_hash, s->kex_alg->hash->hlen))) { -#ifndef FUZZING - ssh_proto_error(s->ppl.ssh, "Signature from server's host key " - "is invalid"); - *aborted = true; - return; -#endif - } - } - - s->keystr = (s->hkey ? ssh_key_cache_str(s->hkey) : NULL); -#ifndef NO_GSSAPI - if (s->gss_kex_used) { - /* - * In a GSS-based session, check the host key (if any) against - * the transient host key cache. - */ - if (s->kex_alg->main_type == KEXTYPE_GSS) { - - /* - * We've just done a GSS key exchange. If it gave us a - * host key, store it. - */ - if (s->hkey) { - char *fingerprint = ssh2_fingerprint( - s->hkey, SSH_FPTYPE_DEFAULT); - ppl_logevent("GSS kex provided fallback host key:"); - ppl_logevent("%s", fingerprint); - sfree(fingerprint); - - ssh_transient_hostkey_cache_add(s->thc, s->hkey); - } else if (!ssh_transient_hostkey_cache_non_empty(s->thc)) { - /* - * But if it didn't, then we currently have no - * fallback host key to use in subsequent non-GSS - * rekeys. So we should immediately trigger a non-GSS - * rekey of our own, to set one up, before the session - * keys have been used for anything else. - * - * This is similar to the cross-certification done at - * user request in the permanent host key cache, but - * here we do it automatically, once, at session - * startup, and only add the key to the transient - * cache. - */ - if (s->hostkey_alg) { - s->need_gss_transient_hostkey = true; - } else { - /* - * If we negotiated the "null" host key algorithm - * in the key exchange, that's an indication that - * no host key at all is available from the server - * (both because we listed "null" last, and - * because RFC 4462 section 5 says that a server - * MUST NOT offer "null" as a host key algorithm - * unless that is the only algorithm it provides - * at all). - * - * In that case we actually _can't_ perform a - * non-GSSAPI key exchange, so it's pointless to - * attempt one proactively. This is also likely to - * cause trouble later if a rekey is required at a - * moment whne GSS credentials are not available, - * but someone setting up a server in this - * configuration presumably accepts that as a - * consequence. - */ - if (!s->warned_about_no_gss_transient_hostkey) { - ppl_logevent("No fallback host key available"); - s->warned_about_no_gss_transient_hostkey = true; - } - } - } - } else { - /* - * We've just done a fallback key exchange, so make - * sure the host key it used is in the cache of keys - * we previously received in GSS kexes. - * - * An exception is if this was the non-GSS key exchange we - * triggered on purpose to populate the transient cache. - */ - assert(s->hkey); /* only KEXTYPE_GSS lets this be null */ - char *fingerprint = ssh2_fingerprint(s->hkey, SSH_FPTYPE_DEFAULT); - - if (s->need_gss_transient_hostkey) { - ppl_logevent("Post-GSS rekey provided fallback host key:"); - ppl_logevent("%s", fingerprint); - ssh_transient_hostkey_cache_add(s->thc, s->hkey); - s->need_gss_transient_hostkey = false; - } else if (!ssh_transient_hostkey_cache_verify(s->thc, s->hkey)) { - ppl_logevent("Non-GSS rekey after initial GSS kex " - "used host key:"); - ppl_logevent("%s", fingerprint); - sfree(fingerprint); - ssh_sw_abort(s->ppl.ssh, "Server's host key did not match any " - "used in previous GSS kex"); - *aborted = true; - return; - } - - sfree(fingerprint); - } - } else -#endif /* NO_GSSAPI */ - if (!s->got_session_id) { - /* - * Make a note of any other host key formats that are available. - */ - { - int i, j, nkeys = 0; - char *list = NULL; - for (i = 0; i < lenof(ssh2_hostkey_algs); i++) { - if (ssh2_hostkey_algs[i].alg == s->hostkey_alg) - continue; - - for (j = 0; j < s->n_uncert_hostkeys; j++) - if (s->uncert_hostkeys[j] == i) - break; - - if (j < s->n_uncert_hostkeys) { - char *newlist; - if (list) - newlist = dupprintf( - "%s/%s", list, - ssh2_hostkey_algs[i].alg->ssh_id); - else - newlist = dupprintf( - "%s", ssh2_hostkey_algs[i].alg->ssh_id); - sfree(list); - list = newlist; - nkeys++; - } - } - if (list) { - ppl_logevent("Server also has %s host key%s, but we " - "don't know %s", list, - nkeys > 1 ? "s" : "", - nkeys > 1 ? "any of them" : "it"); - sfree(list); - } - } - - /* - * Authenticate remote host: verify host key. (We've already - * checked the signature of the exchange hash.) - */ - char **fingerprints = ssh2_all_fingerprints(s->hkey); - FingerprintType fptype_default = - ssh2_pick_default_fingerprint(fingerprints); - ppl_logevent("Host key fingerprint is:"); - ppl_logevent("%s", fingerprints[fptype_default]); - /* First check against manually configured host keys. */ - s->dlgret = verify_ssh_manual_host_key( - s->conf, fingerprints, s->hkey); - if (s->dlgret == 0) { /* did not match */ - ssh2_free_all_fingerprints(fingerprints); - ssh_sw_abort(s->ppl.ssh, "Host key did not appear in manually " - "configured list"); - *aborted = true; - return; - } else if (s->dlgret < 0) { /* none configured; use standard handling */ - ssh2_userkey uk = { .key = s->hkey, .comment = NULL }; - char *keydisp = ssh2_pubkey_openssh_str(&uk); - s->dlgret = seat_verify_ssh_host_key( - s->ppl.seat, s->savedhost, s->savedport, - ssh_key_cache_id(s->hkey), s->keystr, keydisp, - fingerprints, ssh2_transport_dialog_callback, s); - sfree(keydisp); - ssh2_free_all_fingerprints(fingerprints); -#ifdef FUZZING - s->dlgret = 1; -#endif - crMaybeWaitUntilV(s->dlgret >= 0); - if (s->dlgret == 0) { - ssh_user_close(s->ppl.ssh, - "User aborted at host key verification"); - *aborted = true; - return; - } - } - - /* - * Save this host key, to check against the one presented in - * subsequent rekeys. - */ - s->hostkey_str = s->keystr; - s->keystr = NULL; - } else if (s->cross_certifying) { - assert(s->hkey); - assert(ssh_key_alg(s->hkey) == s->cross_certifying); - - char *fingerprint = ssh2_fingerprint(s->hkey, SSH_FPTYPE_DEFAULT); - ppl_logevent("Storing additional host key for this host:"); - ppl_logevent("%s", fingerprint); - sfree(fingerprint); - - store_host_key(s->savedhost, s->savedport, - ssh_key_cache_id(s->hkey), s->keystr); - /* - * Don't forget to store the new key as the one we'll be - * re-checking in future normal rekeys. - */ - s->hostkey_str = s->keystr; - s->keystr = NULL; - } else { - /* - * In a rekey, we never present an interactive host key - * verification request to the user. Instead, we simply - * enforce that the key we're seeing this time is identical to - * the one we saw before. - */ - assert(s->keystr); /* filled in by prior key exchange */ - if (strcmp(s->hostkey_str, s->keystr)) { -#ifndef FUZZING - ssh_sw_abort(s->ppl.ssh, - "Host key was different in repeat key exchange"); - *aborted = true; - return; -#endif - } - } - - sfree(s->keystr); - s->keystr = NULL; - if (s->hkey) { - ssh_key_free(s->hkey); - s->hkey = NULL; - } - - crFinishV; -} diff --git a/ssh2kex-server.c b/ssh2kex-server.c deleted file mode 100644 index 1fbb588b..00000000 --- a/ssh2kex-server.c +++ /dev/null @@ -1,330 +0,0 @@ -/* - * Server side of key exchange for the SSH-2 transport protocol (RFC 4253). - */ - -#include - -#include "putty.h" -#include "ssh.h" -#include "sshbpp.h" -#include "sshppl.h" -#include "sshcr.h" -#include "sshserver.h" -#include "sshkeygen.h" -#include "storage.h" -#include "ssh2transport.h" -#include "mpint.h" - -void ssh2_transport_provide_hostkeys(PacketProtocolLayer *ppl, - ssh_key *const *hostkeys, int nhostkeys) -{ - struct ssh2_transport_state *s = - container_of(ppl, struct ssh2_transport_state, ppl); - - s->hostkeys = hostkeys; - s->nhostkeys = nhostkeys; -} - -static strbuf *finalise_and_sign_exhash(struct ssh2_transport_state *s) -{ - strbuf *sb; - ssh2transport_finalise_exhash(s); - sb = strbuf_new(); - ssh_key_sign( - s->hkey, make_ptrlen(s->exchange_hash, s->kex_alg->hash->hlen), - s->hkflags, BinarySink_UPCAST(sb)); - return sb; -} - -void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) -{ - PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ - PktIn *pktin; - PktOut *pktout; - - crBegin(s->crStateKex); - - { - int i; - for (i = 0; i < s->nhostkeys; i++) - if (ssh_key_alg(s->hostkeys[i]) == s->hostkey_alg) { - s->hkey = s->hostkeys[i]; - break; - } - assert(s->hkey); - } - - strbuf_clear(s->hostkeyblob); - ssh_key_public_blob(s->hkey, BinarySink_UPCAST(s->hostkeyblob)); - s->hostkeydata = ptrlen_from_strbuf(s->hostkeyblob); - - put_stringpl(s->exhash, s->hostkeydata); - - if (s->kex_alg->main_type == KEXTYPE_DH) { - /* - * If we're doing Diffie-Hellman group exchange, start by - * waiting for the group request. - */ - if (dh_is_gex(s->kex_alg)) { - ppl_logevent("Doing Diffie-Hellman group exchange"); - s->ppl.bpp->pls->kctx = SSH2_PKTCTX_DHGEX; - - crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL); - if (pktin->type != SSH2_MSG_KEX_DH_GEX_REQUEST && - pktin->type != SSH2_MSG_KEX_DH_GEX_REQUEST_OLD) { - ssh_proto_error(s->ppl.ssh, "Received unexpected packet when " - "expecting Diffie-Hellman group exchange " - "request, type %d (%s)", pktin->type, - ssh2_pkt_type(s->ppl.bpp->pls->kctx, - s->ppl.bpp->pls->actx, - pktin->type)); - *aborted = true; - return; - } - - if (pktin->type != SSH2_MSG_KEX_DH_GEX_REQUEST_OLD) { - s->dh_got_size_bounds = true; - s->dh_min_size = get_uint32(pktin); - s->pbits = get_uint32(pktin); - s->dh_max_size = get_uint32(pktin); - } else { - s->dh_got_size_bounds = false; - s->pbits = get_uint32(pktin); - } - - /* - * This is a hopeless strategy for making a secure DH - * group! It's good enough for testing a client against, - * but not for serious use. - */ - PrimeGenerationContext *pgc = primegen_new_context( - &primegen_probabilistic); - ProgressReceiver null_progress; - null_progress.vt = &null_progress_vt; - s->p = primegen_generate(pgc, pcs_new(s->pbits), &null_progress); - primegen_free_context(pgc); - - s->g = mp_from_integer(2); - s->dh_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; - - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEX_DH_GEX_GROUP); - put_mp_ssh2(pktout, s->p); - put_mp_ssh2(pktout, s->g); - pq_push(s->ppl.out_pq, pktout); - } else { - s->ppl.bpp->pls->kctx = SSH2_PKTCTX_DHGROUP; - s->dh_ctx = dh_setup_group(s->kex_alg); - s->kex_init_value = SSH2_MSG_KEXDH_INIT; - s->kex_reply_value = SSH2_MSG_KEXDH_REPLY; - ppl_logevent("Using Diffie-Hellman with standard group \"%s\"", - s->kex_alg->groupname); - } - - ppl_logevent("Doing Diffie-Hellman key exchange with hash %s", - ssh_hash_alg(s->exhash)->text_name); - - /* - * Generate e for Diffie-Hellman. - */ - s->e = dh_create_e(s->dh_ctx, s->nbits * 2); - - /* - * Wait to receive f. - */ - crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL); - if (pktin->type != s->kex_init_value) { - ssh_proto_error(s->ppl.ssh, "Received unexpected packet when " - "expecting Diffie-Hellman initial packet, " - "type %d (%s)", pktin->type, - ssh2_pkt_type(s->ppl.bpp->pls->kctx, - s->ppl.bpp->pls->actx, - pktin->type)); - *aborted = true; - return; - } - s->f = get_mp_ssh2(pktin); - if (get_err(pktin)) { - ssh_proto_error(s->ppl.ssh, - "Unable to parse Diffie-Hellman initial packet"); - *aborted = true; - return; - } - - { - const char *err = dh_validate_f(s->dh_ctx, s->f); - if (err) { - ssh_proto_error(s->ppl.ssh, "Diffie-Hellman initial packet " - "failed validation: %s", err); - *aborted = true; - return; - } - } - s->K = dh_find_K(s->dh_ctx, s->f); - - if (dh_is_gex(s->kex_alg)) { - if (s->dh_got_size_bounds) - put_uint32(s->exhash, s->dh_min_size); - put_uint32(s->exhash, s->pbits); - if (s->dh_got_size_bounds) - put_uint32(s->exhash, s->dh_max_size); - put_mp_ssh2(s->exhash, s->p); - put_mp_ssh2(s->exhash, s->g); - } - put_mp_ssh2(s->exhash, s->f); - put_mp_ssh2(s->exhash, s->e); - - pktout = ssh_bpp_new_pktout(s->ppl.bpp, s->kex_reply_value); - put_stringpl(pktout, s->hostkeydata); - put_mp_ssh2(pktout, s->e); - put_stringsb(pktout, finalise_and_sign_exhash(s)); - pq_push(s->ppl.out_pq, pktout); - - dh_cleanup(s->dh_ctx); - s->dh_ctx = NULL; - mp_free(s->f); s->f = NULL; - if (dh_is_gex(s->kex_alg)) { - mp_free(s->g); s->g = NULL; - mp_free(s->p); s->p = NULL; - } - } else if (s->kex_alg->main_type == KEXTYPE_ECDH) { - ppl_logevent("Doing ECDH key exchange with curve %s and hash %s", - ssh_ecdhkex_curve_textname(s->kex_alg), - ssh_hash_alg(s->exhash)->text_name); - s->ppl.bpp->pls->kctx = SSH2_PKTCTX_ECDHKEX; - - s->ecdh_key = ssh_ecdhkex_newkey(s->kex_alg); - if (!s->ecdh_key) { - ssh_sw_abort(s->ppl.ssh, "Unable to generate key for ECDH"); - *aborted = true; - return; - } - - crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL); - if (pktin->type != SSH2_MSG_KEX_ECDH_INIT) { - ssh_proto_error(s->ppl.ssh, "Received unexpected packet when " - "expecting ECDH initial packet, type %d (%s)", - pktin->type, - ssh2_pkt_type(s->ppl.bpp->pls->kctx, - s->ppl.bpp->pls->actx, - pktin->type)); - *aborted = true; - return; - } - - { - ptrlen keydata = get_string(pktin); - put_stringpl(s->exhash, keydata); - - s->K = ssh_ecdhkex_getkey(s->ecdh_key, keydata); - if (!get_err(pktin) && !s->K) { - ssh_proto_error(s->ppl.ssh, "Received invalid elliptic curve " - "point in ECDH initial packet"); - *aborted = true; - return; - } - } - - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEX_ECDH_REPLY); - put_stringpl(pktout, s->hostkeydata); - { - strbuf *pubpoint = strbuf_new(); - ssh_ecdhkex_getpublic(s->ecdh_key, BinarySink_UPCAST(pubpoint)); - put_string(s->exhash, pubpoint->u, pubpoint->len); - put_stringsb(pktout, pubpoint); - } - put_stringsb(pktout, finalise_and_sign_exhash(s)); - pq_push(s->ppl.out_pq, pktout); - - ssh_ecdhkex_freekey(s->ecdh_key); - s->ecdh_key = NULL; - } else if (s->kex_alg->main_type == KEXTYPE_GSS) { - ssh_sw_abort(s->ppl.ssh, "GSS key exchange not supported in server"); - } else { - assert(s->kex_alg->main_type == KEXTYPE_RSA); - ppl_logevent("Doing RSA key exchange with hash %s", - ssh_hash_alg(s->exhash)->text_name); - s->ppl.bpp->pls->kctx = SSH2_PKTCTX_RSAKEX; - - const struct ssh_rsa_kex_extra *extra = - (const struct ssh_rsa_kex_extra *)s->kex_alg->extra; - - if (s->ssc && s->ssc->rsa_kex_key) { - int klen = ssh_rsakex_klen(s->ssc->rsa_kex_key); - if (klen >= extra->minklen) { - ppl_logevent("Using configured %d-bit RSA key", klen); - s->rsa_kex_key = s->ssc->rsa_kex_key; - } else { - ppl_logevent("Configured %d-bit RSA key is too short (min %d)", - klen, extra->minklen); - } - } - - if (!s->rsa_kex_key) { - ppl_logevent("Generating a %d-bit RSA key", extra->minklen); - - s->rsa_kex_key = snew(RSAKey); - - PrimeGenerationContext *pgc = primegen_new_context( - &primegen_probabilistic); - ProgressReceiver null_progress; - null_progress.vt = &null_progress_vt; - rsa_generate(s->rsa_kex_key, extra->minklen, false, - pgc, &null_progress); - primegen_free_context(pgc); - - s->rsa_kex_key->comment = NULL; - s->rsa_kex_key_needs_freeing = true; - } - - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEXRSA_PUBKEY); - put_stringpl(pktout, s->hostkeydata); - { - strbuf *pubblob = strbuf_new(); - ssh_key_public_blob(&s->rsa_kex_key->sshk, - BinarySink_UPCAST(pubblob)); - put_string(s->exhash, pubblob->u, pubblob->len); - put_stringsb(pktout, pubblob); - } - pq_push(s->ppl.out_pq, pktout); - - crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL); - if (pktin->type != SSH2_MSG_KEXRSA_SECRET) { - ssh_proto_error(s->ppl.ssh, "Received unexpected packet when " - "expecting RSA kex secret, type %d (%s)", - pktin->type, - ssh2_pkt_type(s->ppl.bpp->pls->kctx, - s->ppl.bpp->pls->actx, - pktin->type)); - *aborted = true; - return; - } - - { - ptrlen encrypted_secret = get_string(pktin); - put_stringpl(s->exhash, encrypted_secret); - s->K = ssh_rsakex_decrypt( - s->rsa_kex_key, s->kex_alg->hash, encrypted_secret); - } - - if (!s->K) { - ssh_proto_error(s->ppl.ssh, "Unable to decrypt RSA kex secret"); - *aborted = true; - return; - } - - if (s->rsa_kex_key_needs_freeing) { - ssh_rsakex_freekey(s->rsa_kex_key); - sfree(s->rsa_kex_key); - } - s->rsa_kex_key = NULL; - s->rsa_kex_key_needs_freeing = false; - - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEXRSA_DONE); - put_stringsb(pktout, finalise_and_sign_exhash(s)); - pq_push(s->ppl.out_pq, pktout); - } - - crFinishV; -} diff --git a/ssh2transhk.c b/ssh2transhk.c deleted file mode 100644 index 2e77fdf9..00000000 --- a/ssh2transhk.c +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Data structure managing host keys in sessions based on GSSAPI KEX. - * - * In a session we started with a GSSAPI key exchange, the concept of - * 'host key' has completely different lifetime and security semantics - * from the usual ones. Per RFC 4462 section 2.1, we assume that any - * host key delivered to us in the course of a GSSAPI key exchange is - * _solely_ there to use as a transient fallback within the same - * session, if at the time of a subsequent rekey the GSS credentials - * are temporarily invalid and so a non-GSS KEX method has to be used. - * - * In particular, in a GSS-based SSH deployment, host keys may not - * even _be_ persistent identities for the server; it would be - * legitimate for a server to generate a fresh one routinely if it - * wanted to, like SSH-1 server keys. - * - * So, in this mode, we never touch the persistent host key cache at - * all, either to check keys against it _or_ to store keys in it. - * Instead, we maintain an in-memory cache of host keys that have been - * mentioned in GSS key exchanges within this particular session, and - * we permit precisely those host keys in non-GSS rekeys. - */ - -#include - -#include "putty.h" -#include "ssh.h" - -struct ssh_transient_hostkey_cache { - tree234 *cache; -}; - -struct ssh_transient_hostkey_cache_entry { - const ssh_keyalg *alg; - strbuf *pub_blob; -}; - -static int ssh_transient_hostkey_cache_cmp(void *av, void *bv) -{ - const struct ssh_transient_hostkey_cache_entry - *a = (const struct ssh_transient_hostkey_cache_entry *)av, - *b = (const struct ssh_transient_hostkey_cache_entry *)bv; - return strcmp(a->alg->ssh_id, b->alg->ssh_id); -} - -static int ssh_transient_hostkey_cache_find(void *av, void *bv) -{ - const ssh_keyalg *aalg = (const ssh_keyalg *)av; - const struct ssh_transient_hostkey_cache_entry - *b = (const struct ssh_transient_hostkey_cache_entry *)bv; - return strcmp(aalg->ssh_id, b->alg->ssh_id); -} - -ssh_transient_hostkey_cache *ssh_transient_hostkey_cache_new(void) -{ - ssh_transient_hostkey_cache *thc = snew(ssh_transient_hostkey_cache); - thc->cache = newtree234(ssh_transient_hostkey_cache_cmp); - return thc; -} - -void ssh_transient_hostkey_cache_free(ssh_transient_hostkey_cache *thc) -{ - struct ssh_transient_hostkey_cache_entry *ent; - while ((ent = delpos234(thc->cache, 0)) != NULL) { - strbuf_free(ent->pub_blob); - sfree(ent); - } - freetree234(thc->cache); - sfree(thc); -} - -void ssh_transient_hostkey_cache_add( - ssh_transient_hostkey_cache *thc, ssh_key *key) -{ - struct ssh_transient_hostkey_cache_entry *ent, *retd; - - if ((ent = find234(thc->cache, (void *)ssh_key_alg(key), - ssh_transient_hostkey_cache_find)) != NULL) { - del234(thc->cache, ent); - strbuf_free(ent->pub_blob); - sfree(ent); - } - - ent = snew(struct ssh_transient_hostkey_cache_entry); - ent->alg = ssh_key_alg(key); - ent->pub_blob = strbuf_new(); - ssh_key_public_blob(key, BinarySink_UPCAST(ent->pub_blob)); - retd = add234(thc->cache, ent); - assert(retd == ent); -} - -bool ssh_transient_hostkey_cache_verify( - ssh_transient_hostkey_cache *thc, ssh_key *key) -{ - struct ssh_transient_hostkey_cache_entry *ent; - bool toret = false; - - if ((ent = find234(thc->cache, (void *)ssh_key_alg(key), - ssh_transient_hostkey_cache_find)) != NULL) { - strbuf *this_blob = strbuf_new(); - ssh_key_public_blob(key, BinarySink_UPCAST(this_blob)); - - if (this_blob->len == ent->pub_blob->len && - !memcmp(this_blob->s, ent->pub_blob->s, - this_blob->len)) - toret = true; - - strbuf_free(this_blob); - } - - return toret; -} - -bool ssh_transient_hostkey_cache_has( - ssh_transient_hostkey_cache *thc, const ssh_keyalg *alg) -{ - struct ssh_transient_hostkey_cache_entry *ent = - find234(thc->cache, (void *)alg, - ssh_transient_hostkey_cache_find); - return ent != NULL; -} - -bool ssh_transient_hostkey_cache_non_empty(ssh_transient_hostkey_cache *thc) -{ - return count234(thc->cache) > 0; -} diff --git a/ssh2transport.c b/ssh2transport.c deleted file mode 100644 index 4e1b443d..00000000 --- a/ssh2transport.c +++ /dev/null @@ -1,2181 +0,0 @@ -/* - * Packet protocol layer for the SSH-2 transport protocol (RFC 4253). - */ - -#include - -#include "putty.h" -#include "ssh.h" -#include "sshbpp.h" -#include "sshppl.h" -#include "sshcr.h" -#include "sshserver.h" -#include "storage.h" -#include "ssh2transport.h" -#include "mpint.h" - -const struct ssh_signkey_with_user_pref_id ssh2_hostkey_algs[] = { - #define ARRAYENT_HOSTKEY_ALGORITHM(type, alg) { &alg, type }, - HOSTKEY_ALGORITHMS(ARRAYENT_HOSTKEY_ALGORITHM) -}; - -const static ssh2_macalg *const macs[] = { - &ssh_hmac_sha256, &ssh_hmac_sha1, &ssh_hmac_sha1_96, &ssh_hmac_md5 -}; -const static ssh2_macalg *const buggymacs[] = { - &ssh_hmac_sha1_buggy, &ssh_hmac_sha1_96_buggy, &ssh_hmac_md5 -}; - -static ssh_compressor *ssh_comp_none_init(void) -{ - return NULL; -} -static void ssh_comp_none_cleanup(ssh_compressor *handle) -{ -} -static ssh_decompressor *ssh_decomp_none_init(void) -{ - return NULL; -} -static void ssh_decomp_none_cleanup(ssh_decompressor *handle) -{ -} -static void ssh_comp_none_block(ssh_compressor *handle, - const unsigned char *block, int len, - unsigned char **outblock, int *outlen, - int minlen) -{ -} -static bool ssh_decomp_none_block(ssh_decompressor *handle, - const unsigned char *block, int len, - unsigned char **outblock, int *outlen) -{ - return false; -} -static const ssh_compression_alg ssh_comp_none = { - .name = "none", - .delayed_name = NULL, - .compress_new = ssh_comp_none_init, - .compress_free = ssh_comp_none_cleanup, - .compress = ssh_comp_none_block, - .decompress_new = ssh_decomp_none_init, - .decompress_free = ssh_decomp_none_cleanup, - .decompress = ssh_decomp_none_block, - .text_name = NULL, -}; -const static ssh_compression_alg *const compressions[] = { - &ssh_zlib, &ssh_comp_none -}; - -static void ssh2_transport_free(PacketProtocolLayer *); -static void ssh2_transport_process_queue(PacketProtocolLayer *); -static bool ssh2_transport_get_specials( - PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx); -static void ssh2_transport_special_cmd(PacketProtocolLayer *ppl, - SessionSpecialCode code, int arg); -static bool ssh2_transport_want_user_input(PacketProtocolLayer *ppl); -static void ssh2_transport_got_user_input(PacketProtocolLayer *ppl); -static void ssh2_transport_reconfigure(PacketProtocolLayer *ppl, Conf *conf); -static size_t ssh2_transport_queued_data_size(PacketProtocolLayer *ppl); - -static void ssh2_transport_set_max_data_size(struct ssh2_transport_state *s); -static unsigned long sanitise_rekey_time(int rekey_time, unsigned long def); -static void ssh2_transport_higher_layer_packet_callback(void *context); - -static const PacketProtocolLayerVtable ssh2_transport_vtable = { - .free = ssh2_transport_free, - .process_queue = ssh2_transport_process_queue, - .get_specials = ssh2_transport_get_specials, - .special_cmd = ssh2_transport_special_cmd, - .want_user_input = ssh2_transport_want_user_input, - .got_user_input = ssh2_transport_got_user_input, - .reconfigure = ssh2_transport_reconfigure, - .queued_data_size = ssh2_transport_queued_data_size, - .name = NULL, /* no protocol name for this layer */ -}; - -#ifndef NO_GSSAPI -static void ssh2_transport_gss_update(struct ssh2_transport_state *s, - bool definitely_rekeying); -#endif - -static bool ssh2_transport_timer_update(struct ssh2_transport_state *s, - unsigned long rekey_time); -static int ssh2_transport_confirm_weak_crypto_primitive( - struct ssh2_transport_state *s, const char *type, const char *name, - const void *alg); - -static const char *const kexlist_descr[NKEXLIST] = { - "key exchange algorithm", - "host key algorithm", - "client-to-server cipher", - "server-to-client cipher", - "client-to-server MAC", - "server-to-client MAC", - "client-to-server compression method", - "server-to-client compression method" -}; - -static int weak_algorithm_compare(void *av, void *bv); - -PacketProtocolLayer *ssh2_transport_new( - Conf *conf, const char *host, int port, const char *fullhostname, - const char *client_greeting, const char *server_greeting, - struct ssh_connection_shared_gss_state *shgss, - struct DataTransferStats *stats, PacketProtocolLayer *higher_layer, - const SshServerConfig *ssc) -{ - struct ssh2_transport_state *s = snew(struct ssh2_transport_state); - memset(s, 0, sizeof(*s)); - s->ppl.vt = &ssh2_transport_vtable; - - s->conf = conf_copy(conf); - s->savedhost = dupstr(host); - s->savedport = port; - s->fullhostname = dupstr(fullhostname); - s->shgss = shgss; - s->client_greeting = dupstr(client_greeting); - s->server_greeting = dupstr(server_greeting); - s->stats = stats; - s->hostkeyblob = strbuf_new(); - - pq_in_init(&s->pq_in_higher); - pq_out_init(&s->pq_out_higher); - s->pq_out_higher.pqb.ic = &s->ic_pq_out_higher; - s->ic_pq_out_higher.fn = ssh2_transport_higher_layer_packet_callback; - s->ic_pq_out_higher.ctx = &s->ppl; - - s->higher_layer = higher_layer; - s->higher_layer->selfptr = &s->higher_layer; - ssh_ppl_setup_queues(s->higher_layer, &s->pq_in_higher, &s->pq_out_higher); - -#ifndef NO_GSSAPI - s->gss_cred_expiry = GSS_NO_EXPIRATION; - s->shgss->srv_name = GSS_C_NO_NAME; - s->shgss->ctx = NULL; -#endif - s->thc = ssh_transient_hostkey_cache_new(); - s->gss_kex_used = false; - - s->outgoing_kexinit = strbuf_new(); - s->incoming_kexinit = strbuf_new(); - if (ssc) { - s->ssc = ssc; - s->client_kexinit = s->incoming_kexinit; - s->server_kexinit = s->outgoing_kexinit; - s->cstrans = &s->in; - s->sctrans = &s->out; - s->out.mkkey_adjust = 1; - } else { - s->client_kexinit = s->outgoing_kexinit; - s->server_kexinit = s->incoming_kexinit; - s->cstrans = &s->out; - s->sctrans = &s->in; - s->in.mkkey_adjust = 1; - } - - s->weak_algorithms_consented_to = newtree234(weak_algorithm_compare); - - ssh2_transport_set_max_data_size(s); - - return &s->ppl; -} - -static void ssh2_transport_free(PacketProtocolLayer *ppl) -{ - struct ssh2_transport_state *s = - container_of(ppl, struct ssh2_transport_state, ppl); - - /* - * As our last act before being freed, move any outgoing packets - * off our higher layer's output queue on to our own output queue. - * We might be being freed while the SSH connection is still alive - * (because we're initiating shutdown from our end), in which case - * we don't want those last few packets to get lost. - * - * (If our owner were to have already destroyed our output pq - * before wanting to free us, then it would have to reset our - * publicly visible out_pq field to NULL to inhibit this attempt. - * But that's not how I expect the shutdown sequence to go in - * practice.) - */ - if (s->ppl.out_pq) - pq_concatenate(s->ppl.out_pq, s->ppl.out_pq, &s->pq_out_higher); - - conf_free(s->conf); - - ssh_ppl_free(s->higher_layer); - - pq_in_clear(&s->pq_in_higher); - pq_out_clear(&s->pq_out_higher); - - sfree(s->savedhost); - sfree(s->fullhostname); - sfree(s->client_greeting); - sfree(s->server_greeting); - sfree(s->keystr); - sfree(s->hostkey_str); - strbuf_free(s->hostkeyblob); - if (s->hkey && !s->hostkeys) { - ssh_key_free(s->hkey); - s->hkey = NULL; - } - if (s->f) mp_free(s->f); - if (s->p) mp_free(s->p); - if (s->g) mp_free(s->g); - if (s->K) mp_free(s->K); - if (s->dh_ctx) - dh_cleanup(s->dh_ctx); - if (s->rsa_kex_key_needs_freeing) { - ssh_rsakex_freekey(s->rsa_kex_key); - sfree(s->rsa_kex_key); - } - if (s->ecdh_key) - ssh_ecdhkex_freekey(s->ecdh_key); - if (s->exhash) - ssh_hash_free(s->exhash); - strbuf_free(s->outgoing_kexinit); - strbuf_free(s->incoming_kexinit); - ssh_transient_hostkey_cache_free(s->thc); - - freetree234(s->weak_algorithms_consented_to); - - expire_timer_context(s); - sfree(s); -} - -/* - * SSH-2 key derivation (RFC 4253 section 7.2). - */ -static void ssh2_mkkey( - struct ssh2_transport_state *s, strbuf *out, - mp_int *K, unsigned char *H, char chr, int keylen) -{ - int hlen = s->kex_alg->hash->hlen; - int keylen_padded; - unsigned char *key; - ssh_hash *h; - - if (keylen == 0) - return; - - /* - * Round the requested amount of key material up to a multiple of - * the length of the hash we're using to make it. This makes life - * simpler because then we can just write each hash output block - * straight into the output buffer without fiddling about - * truncating the last one. Since it's going into a strbuf, and - * strbufs are always smemclr()ed on free, there's no need to - * worry about leaving extra potentially-sensitive data in memory - * that the caller didn't ask for. - */ - keylen_padded = ((keylen + hlen - 1) / hlen) * hlen; - - strbuf_clear(out); - key = strbuf_append(out, keylen_padded); - - /* First hlen bytes. */ - h = ssh_hash_new(s->kex_alg->hash); - if (!(s->ppl.remote_bugs & BUG_SSH2_DERIVEKEY)) - put_mp_ssh2(h, K); - put_data(h, H, hlen); - put_byte(h, chr); - put_data(h, s->session_id, s->session_id_len); - ssh_hash_digest(h, key); - - /* Subsequent blocks of hlen bytes. */ - if (keylen_padded > hlen) { - int offset; - - ssh_hash_reset(h); - if (!(s->ppl.remote_bugs & BUG_SSH2_DERIVEKEY)) - put_mp_ssh2(h, K); - put_data(h, H, hlen); - - for (offset = hlen; offset < keylen_padded; offset += hlen) { - put_data(h, key + offset - hlen, hlen); - ssh_hash_digest_nondestructive(h, key + offset); - } - - } - - ssh_hash_free(h); -} - -/* - * Find a slot in a KEXINIT algorithm list to use for a new algorithm. - * If the algorithm is already in the list, return a pointer to its - * entry, otherwise return an entry from the end of the list. - * This assumes that every time a particular name is passed in, it - * comes from the same string constant. If this isn't true, this - * function may need to be rewritten to use strcmp() instead. - */ -static struct kexinit_algorithm *ssh2_kexinit_addalg(struct kexinit_algorithm - *list, const char *name) -{ - int i; - - for (i = 0; i < MAXKEXLIST; i++) - if (list[i].name == NULL || list[i].name == name) { - list[i].name = name; - return &list[i]; - } - - unreachable("Should never run out of space in KEXINIT list"); -} - -bool ssh2_common_filter_queue(PacketProtocolLayer *ppl) -{ - 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", - }; - - PktIn *pktin; - ptrlen msg; - int reason; - - while ((pktin = pq_peek(ppl->in_pq)) != NULL) { - switch (pktin->type) { - case SSH2_MSG_DISCONNECT: - reason = get_uint32(pktin); - msg = get_string(pktin); - - ssh_remote_error( - ppl->ssh, "Remote side sent disconnect message\n" - "type %d (%s):\n\"%.*s\"", reason, - ((reason > 0 && reason < lenof(ssh2_disconnect_reasons)) ? - ssh2_disconnect_reasons[reason] : "unknown"), - PTRLEN_PRINTF(msg)); - /* don't try to pop the queue, because we've been freed! */ - return true; /* indicate that we've been freed */ - - case SSH2_MSG_DEBUG: - /* XXX maybe we should actually take notice of the return value */ - get_bool(pktin); - msg = get_string(pktin); - ppl_logevent("Remote debug message: %.*s", PTRLEN_PRINTF(msg)); - pq_pop(ppl->in_pq); - break; - - case SSH2_MSG_IGNORE: - /* Do nothing, because we're ignoring it! Duhh. */ - pq_pop(ppl->in_pq); - break; - - case SSH2_MSG_EXT_INFO: { - /* - * The BPP enforces that these turn up only at legal - * points in the protocol. In particular, it will not pass - * an EXT_INFO on to us if it arrives before encryption is - * enabled (which is when a MITM could inject one - * maliciously). - * - * However, one of the criteria for legality is that a - * server is permitted to send this message immediately - * _before_ USERAUTH_SUCCESS. So we may receive this - * message not yet knowing whether it's legal to have sent - * it - we won't know until the BPP processes the next - * packet. - * - * But that should be OK, because firstly, an - * out-of-sequence EXT_INFO that's still within the - * encrypted session is only a _protocol_ violation, not - * an attack; secondly, any data we set in response to - * such an illegal EXT_INFO won't have a chance to affect - * the session before the BPP aborts it anyway. - */ - uint32_t nexts = get_uint32(pktin); - for (uint32_t i = 0; i < nexts && !get_err(pktin); i++) { - ptrlen extname = get_string(pktin); - ptrlen extvalue = get_string(pktin); - if (ptrlen_eq_string(extname, "server-sig-algs")) { - /* - * Server has sent a list of signature algorithms - * it will potentially accept for user - * authentication keys. Check in particular - * whether the RFC 8332 improved versions of - * ssh-rsa are in the list, and set flags in the - * BPP if so. - * - * TODO: another thing we _could_ do here is to - * record a full list of the algorithm identifiers - * we've seen, whether we understand them - * ourselves or not. Then we could use that as a - * pre-filter during userauth, to skip keys in the - * SSH agent if we already know the server can't - * possibly accept them. (Even if the key - * algorithm is one that the agent and the server - * both understand but we do not.) - */ - ptrlen algname; - while (get_commasep_word(&extvalue, &algname)) { - if (ptrlen_eq_string(algname, "rsa-sha2-256")) - ppl->bpp->ext_info_rsa_sha256_ok = true; - if (ptrlen_eq_string(algname, "rsa-sha2-512")) - ppl->bpp->ext_info_rsa_sha512_ok = true; - } - } - } - pq_pop(ppl->in_pq); - break; - } - - default: - return false; - } - } - - return false; -} - -static bool ssh2_transport_filter_queue(struct ssh2_transport_state *s) -{ - PktIn *pktin; - - while (1) { - if (ssh2_common_filter_queue(&s->ppl)) - return true; - if ((pktin = pq_peek(s->ppl.in_pq)) == NULL) - return false; - - /* Pass on packets to the next layer if they're outside - * the range reserved for the transport protocol. */ - if (pktin->type >= 50) { - /* ... except that we shouldn't tolerate higher-layer - * packets coming from the server before we've seen - * the first NEWKEYS. */ - if (!s->higher_layer_ok) { - ssh_proto_error(s->ppl.ssh, "Received premature higher-" - "layer packet, type %d (%s)", pktin->type, - ssh2_pkt_type(s->ppl.bpp->pls->kctx, - s->ppl.bpp->pls->actx, - pktin->type)); - return true; - } - - pq_pop(s->ppl.in_pq); - pq_push(&s->pq_in_higher, pktin); - } else { - /* Anything else is a transport-layer packet that the main - * process_queue coroutine should handle. */ - return false; - } - } -} - -PktIn *ssh2_transport_pop(struct ssh2_transport_state *s) -{ - if (ssh2_transport_filter_queue(s)) - return NULL; /* we've been freed */ - return pq_pop(s->ppl.in_pq); -} - -static void ssh2_write_kexinit_lists( - BinarySink *pktout, - struct kexinit_algorithm kexlists[NKEXLIST][MAXKEXLIST], - Conf *conf, const SshServerConfig *ssc, int remote_bugs, - const char *hk_host, int hk_port, const ssh_keyalg *hk_prev, - ssh_transient_hostkey_cache *thc, - ssh_key *const *our_hostkeys, int our_nhostkeys, - bool first_time, bool can_gssapi_keyex, bool transient_hostkey_mode) -{ - int i, j, k; - bool warn; - - int n_preferred_kex; - const ssh_kexes *preferred_kex[KEX_MAX + 1]; /* +1 for GSSAPI */ - int n_preferred_hk; - int preferred_hk[HK_MAX]; - int n_preferred_ciphers; - const ssh2_ciphers *preferred_ciphers[CIPHER_MAX]; - const ssh_compression_alg *preferred_comp; - const ssh2_macalg *const *maclist; - int nmacs; - - struct kexinit_algorithm *alg; - - /* - * Set up the preferred key exchange. (NULL => warn below here) - */ - n_preferred_kex = 0; - if (can_gssapi_keyex) - preferred_kex[n_preferred_kex++] = &ssh_gssk5_sha1_kex; - for (i = 0; i < KEX_MAX; i++) { - switch (conf_get_int_int(conf, CONF_ssh_kexlist, i)) { - case KEX_DHGEX: - preferred_kex[n_preferred_kex++] = - &ssh_diffiehellman_gex; - break; - case KEX_DHGROUP14: - preferred_kex[n_preferred_kex++] = - &ssh_diffiehellman_group14; - break; - case KEX_DHGROUP1: - preferred_kex[n_preferred_kex++] = - &ssh_diffiehellman_group1; - break; - case KEX_RSA: - preferred_kex[n_preferred_kex++] = - &ssh_rsa_kex; - break; - case KEX_ECDH: - preferred_kex[n_preferred_kex++] = - &ssh_ecdh_kex; - break; - case KEX_WARN: - /* Flag for later. Don't bother if it's the last in - * the list. */ - if (i < KEX_MAX - 1) { - preferred_kex[n_preferred_kex++] = NULL; - } - break; - } - } - - /* - * Set up the preferred host key types. These are just the ids - * in the enum in putty.h, so 'warn below here' is indicated - * by HK_WARN. - */ - n_preferred_hk = 0; - for (i = 0; i < HK_MAX; i++) { - int id = conf_get_int_int(conf, CONF_ssh_hklist, i); - /* As above, don't bother with HK_WARN if it's last in the - * list */ - if (id != HK_WARN || i < HK_MAX - 1) - preferred_hk[n_preferred_hk++] = id; - } - - /* - * Set up the preferred ciphers. (NULL => warn below here) - */ - n_preferred_ciphers = 0; - for (i = 0; i < CIPHER_MAX; i++) { - switch (conf_get_int_int(conf, CONF_ssh_cipherlist, i)) { - case CIPHER_BLOWFISH: - preferred_ciphers[n_preferred_ciphers++] = &ssh2_blowfish; - break; - case CIPHER_DES: - if (conf_get_bool(conf, CONF_ssh2_des_cbc)) - preferred_ciphers[n_preferred_ciphers++] = &ssh2_des; - break; - case CIPHER_3DES: - preferred_ciphers[n_preferred_ciphers++] = &ssh2_3des; - break; - case CIPHER_AES: - preferred_ciphers[n_preferred_ciphers++] = &ssh2_aes; - break; - case CIPHER_ARCFOUR: - preferred_ciphers[n_preferred_ciphers++] = &ssh2_arcfour; - break; - case CIPHER_CHACHA20: - preferred_ciphers[n_preferred_ciphers++] = &ssh2_ccp; - break; - case CIPHER_WARN: - /* Flag for later. Don't bother if it's the last in - * the list. */ - if (i < CIPHER_MAX - 1) { - preferred_ciphers[n_preferred_ciphers++] = NULL; - } - break; - } - } - - /* - * Set up preferred compression. - */ - if (conf_get_bool(conf, CONF_compression)) - preferred_comp = &ssh_zlib; - else - preferred_comp = &ssh_comp_none; - - for (i = 0; i < NKEXLIST; i++) - for (j = 0; j < MAXKEXLIST; j++) - kexlists[i][j].name = NULL; - /* List key exchange algorithms. */ - warn = false; - for (i = 0; i < n_preferred_kex; i++) { - const ssh_kexes *k = preferred_kex[i]; - if (!k) warn = true; - else for (j = 0; j < k->nkexes; j++) { - alg = ssh2_kexinit_addalg(kexlists[KEXLIST_KEX], - k->list[j]->name); - alg->u.kex.kex = k->list[j]; - alg->u.kex.warn = warn; - } - } - /* List server host key algorithms. */ - if (our_hostkeys) { - /* - * In server mode, we just list the algorithms that match the - * host keys we actually have. - */ - for (i = 0; i < our_nhostkeys; i++) { - const ssh_keyalg *keyalg = ssh_key_alg(our_hostkeys[i]); - - alg = ssh2_kexinit_addalg(kexlists[KEXLIST_HOSTKEY], - keyalg->ssh_id); - alg->u.hk.hostkey = keyalg; - alg->u.hk.hkflags = 0; - alg->u.hk.warn = false; - - if (keyalg == &ssh_rsa) { - alg = ssh2_kexinit_addalg(kexlists[KEXLIST_HOSTKEY], - "rsa-sha2-256"); - alg->u.hk.hostkey = keyalg; - alg->u.hk.hkflags = SSH_AGENT_RSA_SHA2_256; - alg->u.hk.warn = false; - - alg = ssh2_kexinit_addalg(kexlists[KEXLIST_HOSTKEY], - "rsa-sha2-512"); - alg->u.hk.hostkey = keyalg; - alg->u.hk.hkflags = SSH_AGENT_RSA_SHA2_512; - alg->u.hk.warn = false; - } - } - } else if (first_time) { - /* - * In the first key exchange, we list all the algorithms we're - * prepared to cope with, but (if configured to) we prefer - * those algorithms for which we have a host key for this - * host. - * - * If the host key algorithm is below the warning - * threshold, we warn even if we did already have a key - * for it, on the basis that if the user has just - * reconfigured that host key type to be warned about, - * they surely _do_ want to be alerted that a server - * they're actually connecting to is using it. - */ - warn = false; - for (i = 0; i < n_preferred_hk; i++) { - if (preferred_hk[i] == HK_WARN) - warn = true; - for (j = 0; j < lenof(ssh2_hostkey_algs); j++) { - if (ssh2_hostkey_algs[j].id != preferred_hk[i]) - continue; - if (conf_get_bool(conf, CONF_ssh_prefer_known_hostkeys) && - have_ssh_host_key(hk_host, hk_port, - ssh2_hostkey_algs[j].alg->cache_id)) { - alg = ssh2_kexinit_addalg(kexlists[KEXLIST_HOSTKEY], - ssh2_hostkey_algs[j].alg->ssh_id); - alg->u.hk.hostkey = ssh2_hostkey_algs[j].alg; - alg->u.hk.warn = warn; - } - } - } - warn = false; - for (i = 0; i < n_preferred_hk; i++) { - if (preferred_hk[i] == HK_WARN) - warn = true; - for (j = 0; j < lenof(ssh2_hostkey_algs); j++) { - if (ssh2_hostkey_algs[j].id != preferred_hk[i]) - continue; - alg = ssh2_kexinit_addalg(kexlists[KEXLIST_HOSTKEY], - ssh2_hostkey_algs[j].alg->ssh_id); - alg->u.hk.hostkey = ssh2_hostkey_algs[j].alg; - alg->u.hk.warn = warn; - } - } -#ifndef NO_GSSAPI - } else if (transient_hostkey_mode) { - /* - * If we've previously done a GSSAPI KEX, then we list - * precisely the algorithms for which a previous GSS key - * exchange has delivered us a host key, because we expect - * one of exactly those keys to be used in any subsequent - * non-GSS-based rekey. - * - * An exception is if this is the key exchange we - * triggered for the purposes of populating that cache - - * in which case the cache will currently be empty, which - * isn't helpful! - */ - warn = false; - for (i = 0; i < n_preferred_hk; i++) { - if (preferred_hk[i] == HK_WARN) - warn = true; - for (j = 0; j < lenof(ssh2_hostkey_algs); j++) { - if (ssh2_hostkey_algs[j].id != preferred_hk[i]) - continue; - if (ssh_transient_hostkey_cache_has( - thc, ssh2_hostkey_algs[j].alg)) { - alg = ssh2_kexinit_addalg(kexlists[KEXLIST_HOSTKEY], - ssh2_hostkey_algs[j].alg->ssh_id); - alg->u.hk.hostkey = ssh2_hostkey_algs[j].alg; - alg->u.hk.warn = warn; - } - } - } -#endif - } else { - /* - * In subsequent key exchanges, we list only the host key - * algorithm that was selected in the first key exchange, - * so that we keep getting the same host key and hence - * don't have to interrupt the user's session to ask for - * reverification. - */ - assert(hk_prev); - alg = ssh2_kexinit_addalg(kexlists[KEXLIST_HOSTKEY], hk_prev->ssh_id); - alg->u.hk.hostkey = hk_prev; - alg->u.hk.warn = false; - } - if (can_gssapi_keyex) { - alg = ssh2_kexinit_addalg(kexlists[KEXLIST_HOSTKEY], "null"); - alg->u.hk.hostkey = NULL; - } - /* List encryption algorithms (client->server then server->client). */ - for (k = KEXLIST_CSCIPHER; k <= KEXLIST_SCCIPHER; k++) { - warn = false; -#ifdef FUZZING - alg = ssh2_kexinit_addalg(kexlists[k], "none"); - alg->u.cipher.cipher = NULL; - alg->u.cipher.warn = warn; -#endif /* FUZZING */ - for (i = 0; i < n_preferred_ciphers; i++) { - const ssh2_ciphers *c = preferred_ciphers[i]; - if (!c) warn = true; - else for (j = 0; j < c->nciphers; j++) { - alg = ssh2_kexinit_addalg(kexlists[k], - c->list[j]->ssh2_id); - alg->u.cipher.cipher = c->list[j]; - alg->u.cipher.warn = warn; - } - } - } - - /* - * Be prepared to work around the buggy MAC problem. - */ - if (remote_bugs & BUG_SSH2_HMAC) { - maclist = buggymacs; - nmacs = lenof(buggymacs); - } else { - maclist = macs; - nmacs = lenof(macs); - } - - /* List MAC algorithms (client->server then server->client). */ - for (j = KEXLIST_CSMAC; j <= KEXLIST_SCMAC; j++) { -#ifdef FUZZING - alg = ssh2_kexinit_addalg(kexlists[j], "none"); - alg->u.mac.mac = NULL; - alg->u.mac.etm = false; -#endif /* FUZZING */ - for (i = 0; i < nmacs; i++) { - alg = ssh2_kexinit_addalg(kexlists[j], maclist[i]->name); - alg->u.mac.mac = maclist[i]; - alg->u.mac.etm = false; - } - for (i = 0; i < nmacs; i++) { - /* For each MAC, there may also be an ETM version, - * which we list second. */ - if (maclist[i]->etm_name) { - alg = ssh2_kexinit_addalg(kexlists[j], maclist[i]->etm_name); - alg->u.mac.mac = maclist[i]; - alg->u.mac.etm = true; - } - } - } - - /* List client->server compression algorithms, - * then server->client compression algorithms. (We use the - * same set twice.) */ - for (j = KEXLIST_CSCOMP; j <= KEXLIST_SCCOMP; j++) { - assert(lenof(compressions) > 1); - /* Prefer non-delayed versions */ - alg = ssh2_kexinit_addalg(kexlists[j], preferred_comp->name); - alg->u.comp.comp = preferred_comp; - alg->u.comp.delayed = false; - if (preferred_comp->delayed_name) { - alg = ssh2_kexinit_addalg(kexlists[j], - preferred_comp->delayed_name); - alg->u.comp.comp = preferred_comp; - alg->u.comp.delayed = true; - } - for (i = 0; i < lenof(compressions); i++) { - const ssh_compression_alg *c = compressions[i]; - alg = ssh2_kexinit_addalg(kexlists[j], c->name); - alg->u.comp.comp = c; - alg->u.comp.delayed = false; - if (c->delayed_name) { - alg = ssh2_kexinit_addalg(kexlists[j], c->delayed_name); - alg->u.comp.comp = c; - alg->u.comp.delayed = true; - } - } - } - - /* - * Finally, format the lists into text and write them into the - * outgoing KEXINIT packet. - */ - for (i = 0; i < NKEXLIST; i++) { - strbuf *list = strbuf_new(); - if (ssc && ssc->kex_override[i].ptr) { - put_datapl(list, ssc->kex_override[i]); - } else { - for (j = 0; j < MAXKEXLIST; j++) { - if (kexlists[i][j].name == NULL) break; - add_to_commasep(list, kexlists[i][j].name); - } - } - if (i == KEXLIST_KEX && first_time) { - if (our_hostkeys) /* we're the server */ - add_to_commasep(list, "ext-info-s"); - else /* we're the client */ - add_to_commasep(list, "ext-info-c"); - } - put_stringsb(pktout, list); - } - /* List client->server languages. Empty list. */ - put_stringz(pktout, ""); - /* List server->client languages. Empty list. */ - put_stringz(pktout, ""); -} - -static bool ssh2_scan_kexinits( - ptrlen client_kexinit, ptrlen server_kexinit, - struct kexinit_algorithm kexlists[NKEXLIST][MAXKEXLIST], - const ssh_kex **kex_alg, const ssh_keyalg **hostkey_alg, - transport_direction *cs, transport_direction *sc, - bool *warn_kex, bool *warn_hk, bool *warn_cscipher, bool *warn_sccipher, - Ssh *ssh, bool *ignore_guess_cs_packet, bool *ignore_guess_sc_packet, - int *n_server_hostkeys, int server_hostkeys[MAXKEXLIST], unsigned *hkflags, - bool *can_send_ext_info) -{ - BinarySource client[1], server[1]; - int i; - bool guess_correct; - ptrlen clists[NKEXLIST], slists[NKEXLIST]; - const struct kexinit_algorithm *selected[NKEXLIST]; - - BinarySource_BARE_INIT_PL(client, client_kexinit); - BinarySource_BARE_INIT_PL(server, server_kexinit); - - /* Skip packet type bytes and random cookies. */ - get_data(client, 1 + 16); - get_data(server, 1 + 16); - - guess_correct = true; - - /* Find the matching string in each list, and map it to its - * kexinit_algorithm structure. */ - for (i = 0; i < NKEXLIST; i++) { - ptrlen clist, slist, cword, sword, found; - bool cfirst, sfirst; - int j; - - clists[i] = get_string(client); - slists[i] = get_string(server); - if (get_err(client) || get_err(server)) { - /* Report a better error than the spurious "Couldn't - * agree" that we'd generate if we pressed on regardless - * and treated the empty get_string() result as genuine */ - ssh_proto_error(ssh, "KEXINIT packet was incomplete"); - return false; - } - - for (cfirst = true, clist = clists[i]; - get_commasep_word(&clist, &cword); cfirst = false) - for (sfirst = true, slist = slists[i]; - get_commasep_word(&slist, &sword); sfirst = false) - if (ptrlen_eq_ptrlen(cword, sword)) { - found = cword; - goto found_match; - } - - /* No matching string found in the two lists. Delay reporting - * a fatal error until below, because sometimes it turns out - * not to be fatal. */ - selected[i] = NULL; - - /* - * However, even if a failure to agree on any algorithm at all - * is not completely fatal (e.g. because it's the MAC - * negotiation for a cipher that comes with a built-in MAC), - * it still invalidates the guessed key exchange packet. (RFC - * 4253 section 7, not contradicted by OpenSSH's - * PROTOCOL.chacha20poly1305 or as far as I can see by their - * code.) - */ - guess_correct = false; - - continue; - - found_match: - - selected[i] = NULL; - for (j = 0; j < MAXKEXLIST; j++) { - if (kexlists[i][j].name && - ptrlen_eq_string(found, kexlists[i][j].name)) { - selected[i] = &kexlists[i][j]; - break; - } - } - if (!selected[i]) { - /* - * In the client, this should never happen! But in the - * server, where we allow manual override on the command - * line of the exact KEXINIT strings, it can happen - * because the command line contained a typo. So we - * produce a reasonably useful message instead of an - * assertion failure. - */ - ssh_sw_abort(ssh, "Selected %s \"%.*s\" does not correspond to " - "any supported algorithm", - kexlist_descr[i], PTRLEN_PRINTF(found)); - return false; - } - - /* - * If the kex or host key algorithm is not the first one in - * both sides' lists, that means the guessed key exchange - * packet (if any) is officially wrong. - */ - if ((i == KEXLIST_KEX || i == KEXLIST_HOSTKEY) && !(cfirst || sfirst)) - guess_correct = false; - } - - /* - * Skip language strings in both KEXINITs, and read the flags - * saying whether a guessed KEX packet follows. - */ - get_string(client); - get_string(client); - get_string(server); - get_string(server); - if (ignore_guess_cs_packet) - *ignore_guess_cs_packet = get_bool(client) && !guess_correct; - if (ignore_guess_sc_packet) - *ignore_guess_sc_packet = get_bool(server) && !guess_correct; - - /* - * Now transcribe the selected algorithm set into the output data. - */ - for (i = 0; i < NKEXLIST; i++) { - const struct kexinit_algorithm *alg; - - /* - * If we've already selected a cipher which requires a - * particular MAC, then just select that. This is the case in - * which it's not a fatal error if the actual MAC string lists - * didn't include any matching error. - */ - if (i == KEXLIST_CSMAC && cs->cipher && - cs->cipher->required_mac) { - cs->mac = cs->cipher->required_mac; - cs->etm_mode = !!(cs->mac->etm_name); - continue; - } - if (i == KEXLIST_SCMAC && sc->cipher && - sc->cipher->required_mac) { - sc->mac = sc->cipher->required_mac; - sc->etm_mode = !!(sc->mac->etm_name); - continue; - } - - alg = selected[i]; - if (!alg) { - /* - * Otherwise, any match failure _is_ a fatal error. - */ - ssh_sw_abort(ssh, "Couldn't agree a %s (available: %.*s)", - kexlist_descr[i], PTRLEN_PRINTF(slists[i])); - return false; - } - - switch (i) { - case KEXLIST_KEX: - *kex_alg = alg->u.kex.kex; - *warn_kex = alg->u.kex.warn; - break; - - case KEXLIST_HOSTKEY: - /* - * Ignore an unexpected/inappropriate offer of "null", - * we offer "null" when we're willing to use GSS KEX, - * but it is only acceptable when GSSKEX is actually - * selected. - */ - if (alg->u.hk.hostkey == NULL && - (*kex_alg)->main_type != KEXTYPE_GSS) - continue; - - *hostkey_alg = alg->u.hk.hostkey; - *hkflags = alg->u.hk.hkflags; - *warn_hk = alg->u.hk.warn; - break; - - case KEXLIST_CSCIPHER: - cs->cipher = alg->u.cipher.cipher; - *warn_cscipher = alg->u.cipher.warn; - break; - - case KEXLIST_SCCIPHER: - sc->cipher = alg->u.cipher.cipher; - *warn_sccipher = alg->u.cipher.warn; - break; - - case KEXLIST_CSMAC: - cs->mac = alg->u.mac.mac; - cs->etm_mode = alg->u.mac.etm; - break; - - case KEXLIST_SCMAC: - sc->mac = alg->u.mac.mac; - sc->etm_mode = alg->u.mac.etm; - break; - - case KEXLIST_CSCOMP: - cs->comp = alg->u.comp.comp; - cs->comp_delayed = alg->u.comp.delayed; - break; - - case KEXLIST_SCCOMP: - sc->comp = alg->u.comp.comp; - sc->comp_delayed = alg->u.comp.delayed; - break; - - default: - unreachable("Bad list index in scan_kexinits"); - } - } - - /* - * Check whether the other side advertised support for EXT_INFO. - */ - { - ptrlen extinfo_advert = - (server_hostkeys ? PTRLEN_LITERAL("ext-info-c") : - PTRLEN_LITERAL("ext-info-s")); - ptrlen list = (server_hostkeys ? clists[KEXLIST_KEX] : - slists[KEXLIST_KEX]); - for (ptrlen word; get_commasep_word(&list, &word) ;) - if (ptrlen_eq_ptrlen(word, extinfo_advert)) - *can_send_ext_info = true; - } - - if (server_hostkeys) { - /* - * Finally, make an auxiliary pass over the server's host key - * list to find all the host key algorithms offered by the - * server which we know about at all, whether we selected each - * one or not. We return these as a list of indices into the - * constant ssh2_hostkey_algs[] array. - */ - *n_server_hostkeys = 0; - - ptrlen list = slists[KEXLIST_HOSTKEY]; - for (ptrlen word; get_commasep_word(&list, &word) ;) { - for (i = 0; i < lenof(ssh2_hostkey_algs); i++) - if (ptrlen_eq_string(word, ssh2_hostkey_algs[i].alg->ssh_id)) { - server_hostkeys[(*n_server_hostkeys)++] = i; - break; - } - } - } - - return true; -} - -void ssh2transport_finalise_exhash(struct ssh2_transport_state *s) -{ - put_mp_ssh2(s->exhash, s->K); - assert(ssh_hash_alg(s->exhash)->hlen <= sizeof(s->exchange_hash)); - ssh_hash_final(s->exhash, s->exchange_hash); - s->exhash = NULL; - -#if 0 - debug("Exchange hash is:\n"); - dmemdump(s->exchange_hash, s->kex_alg->hash->hlen); -#endif -} - -static void ssh2_transport_process_queue(PacketProtocolLayer *ppl) -{ - struct ssh2_transport_state *s = - container_of(ppl, struct ssh2_transport_state, ppl); - PktIn *pktin; - PktOut *pktout; - - /* Filter centrally handled messages off the front of the queue on - * every entry to this coroutine, no matter where we're resuming - * from, even if we're _not_ looping on pq_pop. That way we can - * still proactively handle those messages even if we're waiting - * for a user response. */ - if (ssh2_transport_filter_queue(s)) - return; /* we've been freed */ - - crBegin(s->crState); - - s->in.cipher = s->out.cipher = NULL; - s->in.mac = s->out.mac = NULL; - s->in.comp = s->out.comp = NULL; - - s->got_session_id = false; - s->need_gss_transient_hostkey = false; - s->warned_about_no_gss_transient_hostkey = false; - - begin_key_exchange: - -#ifndef NO_GSSAPI - if (s->need_gss_transient_hostkey) { - /* - * This flag indicates a special case in which we must not do - * GSS key exchange even if we could. (See comments below, - * where the flag was set on the previous key exchange.) - */ - s->can_gssapi_keyex = false; - } else if (conf_get_bool(s->conf, CONF_try_gssapi_kex)) { - /* - * We always check if we have GSS creds before we come up with - * the kex algorithm list, otherwise future rekeys will fail - * when creds expire. To make this so, this code section must - * follow the begin_key_exchange label above, otherwise this - * section would execute just once per-connection. - * - * Update GSS state unless the reason we're here is that a - * timer just checked the GSS state and decided that we should - * rekey to update delegated credentials. In that case, the - * state is "fresh". - */ - if (s->rekey_class != RK_GSS_UPDATE) - ssh2_transport_gss_update(s, true); - - /* Do GSSAPI KEX when capable */ - s->can_gssapi_keyex = s->gss_status & GSS_KEX_CAPABLE; - - /* - * But not when failure is likely. [ GSS implementations may - * attempt (and fail) to use a ticket that is almost expired - * when retrieved from the ccache that actually expires by the - * time the server receives it. ] - * - * Note: The first time always try KEXGSS if we can, failures - * will be very rare, and disabling the initial GSS KEX is - * worse. Some day GSS libraries will ignore cached tickets - * whose lifetime is critically short, and will instead use - * fresh ones. - */ - if (!s->got_session_id && (s->gss_status & GSS_CTXT_MAYFAIL) != 0) - s->can_gssapi_keyex = false; - s->gss_delegate = conf_get_bool(s->conf, CONF_gssapifwd); - } else { - s->can_gssapi_keyex = false; - } -#endif - - s->ppl.bpp->pls->kctx = SSH2_PKTCTX_NOKEX; - - /* - * Construct our KEXINIT packet, in a strbuf so we can refer to it - * later. - */ - strbuf_clear(s->client_kexinit); - put_byte(s->outgoing_kexinit, SSH2_MSG_KEXINIT); - random_read(strbuf_append(s->outgoing_kexinit, 16), 16); - ssh2_write_kexinit_lists( - BinarySink_UPCAST(s->outgoing_kexinit), s->kexlists, - s->conf, s->ssc, s->ppl.remote_bugs, - s->savedhost, s->savedport, s->hostkey_alg, s->thc, - s->hostkeys, s->nhostkeys, - !s->got_session_id, s->can_gssapi_keyex, - s->gss_kex_used && !s->need_gss_transient_hostkey); - /* First KEX packet does _not_ follow, because we're not that brave. */ - put_bool(s->outgoing_kexinit, false); - put_uint32(s->outgoing_kexinit, 0); /* reserved */ - - /* - * Send our KEXINIT. - */ - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEXINIT); - put_data(pktout, s->outgoing_kexinit->u + 1, - s->outgoing_kexinit->len - 1); /* omit initial packet type byte */ - pq_push(s->ppl.out_pq, pktout); - - /* - * Flag that KEX is in progress. - */ - s->kex_in_progress = true; - - /* - * Wait for the other side's KEXINIT, and save it. - */ - crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL); - if (pktin->type != SSH2_MSG_KEXINIT) { - ssh_proto_error(s->ppl.ssh, "Received unexpected packet when " - "expecting KEXINIT, type %d (%s)", pktin->type, - ssh2_pkt_type(s->ppl.bpp->pls->kctx, - s->ppl.bpp->pls->actx, pktin->type)); - return; - } - strbuf_clear(s->incoming_kexinit); - put_byte(s->incoming_kexinit, SSH2_MSG_KEXINIT); - put_data(s->incoming_kexinit, get_ptr(pktin), get_avail(pktin)); - - /* - * Work through the two KEXINIT packets in parallel to find the - * selected algorithm identifiers. - */ - { - int nhk, hks[MAXKEXLIST], i, j; - - if (!ssh2_scan_kexinits( - ptrlen_from_strbuf(s->client_kexinit), - ptrlen_from_strbuf(s->server_kexinit), - s->kexlists, &s->kex_alg, &s->hostkey_alg, s->cstrans, - s->sctrans, &s->warn_kex, &s->warn_hk, &s->warn_cscipher, - &s->warn_sccipher, s->ppl.ssh, NULL, &s->ignorepkt, &nhk, hks, - &s->hkflags, &s->can_send_ext_info)) - return; /* false means a fatal error function was called */ - - /* - * In addition to deciding which host key we're actually going - * to use, we should make a list of the host keys offered by - * the server which we _don't_ have cached. These will be - * offered as cross-certification options by ssh_get_specials. - * - * We also count the key we're currently using for KEX as one - * we've already got, because by the time this menu becomes - * visible, it will be. - */ - s->n_uncert_hostkeys = 0; - - for (i = 0; i < nhk; i++) { - j = hks[i]; - if (ssh2_hostkey_algs[j].alg != s->hostkey_alg && - !have_ssh_host_key(s->savedhost, s->savedport, - ssh2_hostkey_algs[j].alg->cache_id)) { - s->uncert_hostkeys[s->n_uncert_hostkeys++] = j; - } - } - } - - if (s->warn_kex) { - s->dlgret = ssh2_transport_confirm_weak_crypto_primitive( - s, "key-exchange algorithm", s->kex_alg->name, s->kex_alg); - crMaybeWaitUntilV(s->dlgret >= 0); - if (s->dlgret == 0) { - ssh_user_close(s->ppl.ssh, "User aborted at kex warning"); - return; - } - } - - if (s->warn_hk) { - int j, k; - char *betteralgs; - - /* - * Change warning box wording depending on why we chose a - * warning-level host key algorithm. If it's because - * that's all we have *cached*, list the host keys we - * could usefully cross-certify. Otherwise, use the same - * standard wording as any other weak crypto primitive. - */ - betteralgs = NULL; - for (j = 0; j < s->n_uncert_hostkeys; j++) { - const struct ssh_signkey_with_user_pref_id *hktype = - &ssh2_hostkey_algs[s->uncert_hostkeys[j]]; - bool better = false; - for (k = 0; k < HK_MAX; k++) { - int id = conf_get_int_int(s->conf, CONF_ssh_hklist, k); - if (id == HK_WARN) { - break; - } else if (id == hktype->id) { - better = true; - break; - } - } - if (better) { - if (betteralgs) { - char *old_ba = betteralgs; - betteralgs = dupcat(betteralgs, ",", hktype->alg->ssh_id); - sfree(old_ba); - } else { - betteralgs = dupstr(hktype->alg->ssh_id); - } - } - } - if (betteralgs) { - /* Use the special warning prompt that lets us provide - * a list of better algorithms */ - s->dlgret = seat_confirm_weak_cached_hostkey( - s->ppl.seat, s->hostkey_alg->ssh_id, betteralgs, - ssh2_transport_dialog_callback, s); - sfree(betteralgs); - } else { - /* If none exist, use the more general 'weak crypto' - * warning prompt */ - s->dlgret = ssh2_transport_confirm_weak_crypto_primitive( - s, "host key type", s->hostkey_alg->ssh_id, - s->hostkey_alg); - } - crMaybeWaitUntilV(s->dlgret >= 0); - if (s->dlgret == 0) { - ssh_user_close(s->ppl.ssh, "User aborted at host key warning"); - return; - } - } - - if (s->warn_cscipher) { - s->dlgret = ssh2_transport_confirm_weak_crypto_primitive( - s, "client-to-server cipher", s->out.cipher->ssh2_id, - s->out.cipher); - crMaybeWaitUntilV(s->dlgret >= 0); - if (s->dlgret == 0) { - ssh_user_close(s->ppl.ssh, "User aborted at cipher warning"); - return; - } - } - - if (s->warn_sccipher) { - s->dlgret = ssh2_transport_confirm_weak_crypto_primitive( - s, "server-to-client cipher", s->in.cipher->ssh2_id, - s->in.cipher); - crMaybeWaitUntilV(s->dlgret >= 0); - if (s->dlgret == 0) { - ssh_user_close(s->ppl.ssh, "User aborted at cipher warning"); - return; - } - } - - /* - * If the other side has sent an initial key exchange packet that - * we must treat as a wrong guess, wait for it, and discard it. - */ - if (s->ignorepkt) - crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL); - - /* - * Actually perform the key exchange. - */ - s->exhash = ssh_hash_new(s->kex_alg->hash); - put_stringz(s->exhash, s->client_greeting); - put_stringz(s->exhash, s->server_greeting); - put_string(s->exhash, s->client_kexinit->u, s->client_kexinit->len); - put_string(s->exhash, s->server_kexinit->u, s->server_kexinit->len); - s->crStateKex = 0; - while (1) { - bool aborted = false; - ssh2kex_coroutine(s, &aborted); - if (aborted) - return; /* disaster: our entire state has been freed */ - if (!s->crStateKex) - break; /* kex phase has terminated normally */ - crReturnV; - } - - /* - * 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(s->session_id)); - memcpy(s->session_id, s->exchange_hash, sizeof(s->exchange_hash)); - s->session_id_len = s->kex_alg->hash->hlen; - assert(s->session_id_len <= sizeof(s->session_id)); - s->got_session_id = true; - } - - /* - * Send SSH2_MSG_NEWKEYS. - */ - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_NEWKEYS); - pq_push(s->ppl.out_pq, pktout); - /* Start counting down the outgoing-data limit for these cipher keys. */ - dts_reset(&s->stats->out, s->max_data_size); - - /* - * Force the BPP to synchronously marshal all packets up to and - * including that NEWKEYS into wire format, before we switch over - * to new crypto. - */ - ssh_bpp_handle_output(s->ppl.bpp); - - /* - * We've sent outgoing NEWKEYS, so create and initialise outgoing - * session keys. - */ - { - strbuf *cipher_key = strbuf_new_nm(); - strbuf *cipher_iv = strbuf_new_nm(); - strbuf *mac_key = strbuf_new_nm(); - - if (s->out.cipher) { - ssh2_mkkey(s, cipher_iv, s->K, s->exchange_hash, - 'A' + s->out.mkkey_adjust, s->out.cipher->blksize); - ssh2_mkkey(s, cipher_key, s->K, s->exchange_hash, - 'C' + s->out.mkkey_adjust, - s->out.cipher->padded_keybytes); - } - if (s->out.mac) { - ssh2_mkkey(s, mac_key, s->K, s->exchange_hash, - 'E' + s->out.mkkey_adjust, s->out.mac->keylen); - } - - ssh2_bpp_new_outgoing_crypto( - s->ppl.bpp, - s->out.cipher, cipher_key->u, cipher_iv->u, - s->out.mac, s->out.etm_mode, mac_key->u, - s->out.comp, s->out.comp_delayed); - - strbuf_free(cipher_key); - strbuf_free(cipher_iv); - strbuf_free(mac_key); - } - - /* - * If that was our first key exchange, this is the moment to send - * our EXT_INFO, if we're sending one. - */ - if (!s->post_newkeys_ext_info) { - s->post_newkeys_ext_info = true; /* never do this again */ - if (s->can_send_ext_info) { - strbuf *extinfo = strbuf_new(); - uint32_t n_exts = 0; - - if (s->ssc) { - /* Server->client EXT_INFO lists our supported user - * key algorithms. */ - n_exts++; - put_stringz(extinfo, "server-sig-algs"); - strbuf *list = strbuf_new(); - for (size_t i = 0; i < n_keyalgs; i++) - add_to_commasep(list, all_keyalgs[i]->ssh_id); - put_stringsb(extinfo, list); - } else { - /* Client->server EXT_INFO is currently not sent, but here's - * where we should put things in it if we ever want to. */ - } - - /* Only send EXT_INFO if it's non-empty */ - if (n_exts) { - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_EXT_INFO); - put_uint32(pktout, n_exts); - put_datapl(pktout, ptrlen_from_strbuf(extinfo)); - pq_push(s->ppl.out_pq, pktout); - } - - strbuf_free(extinfo); - } - } - - /* - * Now our end of the key exchange is complete, we can send all - * our queued higher-layer packets. Transfer the whole of the next - * layer's outgoing queue on to our own. - */ - pq_concatenate(s->ppl.out_pq, s->ppl.out_pq, &s->pq_out_higher); - - /* - * Expect SSH2_MSG_NEWKEYS from server. - */ - crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL); - if (pktin->type != SSH2_MSG_NEWKEYS) { - ssh_proto_error(s->ppl.ssh, "Received unexpected packet when " - "expecting SSH_MSG_NEWKEYS, type %d (%s)", - pktin->type, - ssh2_pkt_type(s->ppl.bpp->pls->kctx, - s->ppl.bpp->pls->actx, - pktin->type)); - return; - } - /* Start counting down the incoming-data limit for these cipher keys. */ - dts_reset(&s->stats->in, s->max_data_size); - - /* - * We've seen incoming NEWKEYS, so create and initialise - * incoming session keys. - */ - { - strbuf *cipher_key = strbuf_new_nm(); - strbuf *cipher_iv = strbuf_new_nm(); - strbuf *mac_key = strbuf_new_nm(); - - if (s->in.cipher) { - ssh2_mkkey(s, cipher_iv, s->K, s->exchange_hash, - 'A' + s->in.mkkey_adjust, s->in.cipher->blksize); - ssh2_mkkey(s, cipher_key, s->K, s->exchange_hash, - 'C' + s->in.mkkey_adjust, - s->in.cipher->padded_keybytes); - } - if (s->in.mac) { - ssh2_mkkey(s, mac_key, s->K, s->exchange_hash, - 'E' + s->in.mkkey_adjust, s->in.mac->keylen); - } - - ssh2_bpp_new_incoming_crypto( - s->ppl.bpp, - s->in.cipher, cipher_key->u, cipher_iv->u, - s->in.mac, s->in.etm_mode, mac_key->u, - s->in.comp, s->in.comp_delayed); - - strbuf_free(cipher_key); - strbuf_free(cipher_iv); - strbuf_free(mac_key); - } - - /* - * Free shared secret. - */ - mp_free(s->K); s->K = NULL; - - /* - * Update the specials menu to list the remaining uncertified host - * keys. - */ - seat_update_specials_menu(s->ppl.seat); - - /* - * Key exchange is over. Loop straight back round if we have a - * deferred rekey reason. - */ - if (s->deferred_rekey_reason) { - ppl_logevent("%s", s->deferred_rekey_reason); - pktin = NULL; - s->deferred_rekey_reason = NULL; - goto begin_key_exchange; - } - - /* - * Otherwise, schedule a timer for our next rekey. - */ - s->kex_in_progress = false; - s->last_rekey = GETTICKCOUNT(); - (void) ssh2_transport_timer_update(s, 0); - - /* - * Now we're encrypting. Get the next-layer protocol started if it - * hasn't already, and then sit here waiting for reasons to go - * back to the start and do a repeat key exchange. One of those - * reasons is that we receive KEXINIT from the other end; the - * other is if we find rekey_reason is non-NULL, i.e. we've - * decided to initiate a rekey ourselves for some reason. - */ - if (!s->higher_layer_ok) { - if (!s->hostkeys) { - /* We're the client, so send SERVICE_REQUEST. */ - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_SERVICE_REQUEST); - put_stringz(pktout, s->higher_layer->vt->name); - pq_push(s->ppl.out_pq, pktout); - crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL); - if (pktin->type != SSH2_MSG_SERVICE_ACCEPT) { - ssh_sw_abort(s->ppl.ssh, "Server refused request to start " - "'%s' protocol", s->higher_layer->vt->name); - return; - } - } else { - ptrlen service_name; - - /* We're the server, so expect SERVICE_REQUEST. */ - crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL); - if (pktin->type != SSH2_MSG_SERVICE_REQUEST) { - ssh_proto_error(s->ppl.ssh, "Received unexpected packet when " - "expecting SERVICE_REQUEST, type %d (%s)", - pktin->type, - ssh2_pkt_type(s->ppl.bpp->pls->kctx, - s->ppl.bpp->pls->actx, - pktin->type)); - return; - } - service_name = get_string(pktin); - if (!ptrlen_eq_string(service_name, s->higher_layer->vt->name)) { - ssh_proto_error(s->ppl.ssh, "Client requested service " - "'%.*s' when we only support '%s'", - PTRLEN_PRINTF(service_name), - s->higher_layer->vt->name); - return; - } - - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_SERVICE_ACCEPT); - put_stringz(pktout, s->higher_layer->vt->name); - pq_push(s->ppl.out_pq, pktout); - } - - s->higher_layer_ok = true; - queue_idempotent_callback(&s->higher_layer->ic_process_queue); - } - - s->rekey_class = RK_NONE; - do { - crReturnV; - - /* Pass through outgoing packets from the higher layer. */ - pq_concatenate(s->ppl.out_pq, s->ppl.out_pq, &s->pq_out_higher); - - /* Wait for either a KEXINIT, or something setting - * s->rekey_class. This call to ssh2_transport_pop also has - * the side effect of transferring incoming packets _to_ the - * higher layer (via filter_queue). */ - if ((pktin = ssh2_transport_pop(s)) != NULL) { - if (pktin->type != SSH2_MSG_KEXINIT) { - ssh_proto_error(s->ppl.ssh, "Received unexpected transport-" - "layer packet outside a key exchange, " - "type %d (%s)", pktin->type, - ssh2_pkt_type(s->ppl.bpp->pls->kctx, - s->ppl.bpp->pls->actx, - pktin->type)); - return; - } - pq_push_front(s->ppl.in_pq, pktin); - ppl_logevent("Remote side initiated key re-exchange"); - s->rekey_class = RK_SERVER; - } - - if (s->rekey_class == RK_POST_USERAUTH) { - /* - * userauth has seen a USERAUTH_SUCCESS. This may be the - * moment to do an immediate rekey with different - * parameters. But it may not; so here we turn that rekey - * class into either RK_NONE or RK_NORMAL. - * - * Currently the only reason for this is if we've done a - * GSS key exchange and don't have anything in our - * transient hostkey cache, in which case we should make - * an attempt to populate the cache now. - */ - if (s->need_gss_transient_hostkey) { - s->rekey_reason = "populating transient host key cache"; - s->rekey_class = RK_NORMAL; - } else { - /* No need to rekey at this time. */ - s->rekey_class = RK_NONE; - } - } - - if (!s->rekey_class) { - /* If we don't yet have any other reason to rekey, check - * if we've hit our data limit in either direction. */ - if (s->stats->in.expired) { - s->rekey_reason = "too much data received"; - s->rekey_class = RK_NORMAL; - } else if (s->stats->out.expired) { - s->rekey_reason = "too much data sent"; - s->rekey_class = RK_NORMAL; - } - } - - if (s->rekey_class != RK_NONE && s->rekey_class != RK_SERVER) { - /* - * 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 ((s->ppl.remote_bugs & BUG_SSH2_REKEY)) { - ppl_logevent("Remote bug prevents key re-exchange (%s)", - s->rekey_reason); - /* Reset the counters, so that at least this message doesn't - * hit the event log _too_ often. */ - dts_reset(&s->stats->in, s->max_data_size); - dts_reset(&s->stats->out, s->max_data_size); - (void) ssh2_transport_timer_update(s, 0); - s->rekey_class = RK_NONE; - } else { - ppl_logevent("Initiating key re-exchange (%s)", - s->rekey_reason); - } - } - } while (s->rekey_class == RK_NONE); - - /* Once we exit the above loop, we really are rekeying. */ - goto begin_key_exchange; - - crFinishV; -} - -static void ssh2_transport_higher_layer_packet_callback(void *context) -{ - PacketProtocolLayer *ppl = (PacketProtocolLayer *)context; - ssh_ppl_process_queue(ppl); -} - -static void ssh2_transport_timer(void *ctx, unsigned long now) -{ - struct ssh2_transport_state *s = (struct ssh2_transport_state *)ctx; - unsigned long mins; - unsigned long ticks; - - if (s->kex_in_progress || now != s->next_rekey) - return; - - mins = sanitise_rekey_time(conf_get_int(s->conf, CONF_ssh_rekey_time), 60); - if (mins == 0) - return; - - /* Rekey if enough time has elapsed */ - ticks = mins * 60 * TICKSPERSEC; - if (now - s->last_rekey > ticks - 30*TICKSPERSEC) { - s->rekey_reason = "timeout"; - s->rekey_class = RK_NORMAL; - queue_idempotent_callback(&s->ppl.ic_process_queue); - return; - } - -#ifndef NO_GSSAPI - /* - * Rekey now if we have a new cred or context expires this cycle, - * but not if this is unsafe. - */ - if (conf_get_int(s->conf, CONF_gssapirekey)) { - ssh2_transport_gss_update(s, false); - if ((s->gss_status & GSS_KEX_CAPABLE) != 0 && - (s->gss_status & GSS_CTXT_MAYFAIL) == 0 && - (s->gss_status & (GSS_CRED_UPDATED|GSS_CTXT_EXPIRES)) != 0) { - s->rekey_reason = "GSS credentials updated"; - s->rekey_class = RK_GSS_UPDATE; - queue_idempotent_callback(&s->ppl.ic_process_queue); - return; - } - } -#endif - - /* Try again later. */ - (void) ssh2_transport_timer_update(s, 0); -} - -/* - * The rekey_time is zero except when re-configuring. - * - * We either schedule the next timer and return false, or return true - * to run the callback now, which will call us again to re-schedule on - * completion. - */ -static bool ssh2_transport_timer_update(struct ssh2_transport_state *s, - unsigned long rekey_time) -{ - unsigned long mins; - unsigned long ticks; - - mins = sanitise_rekey_time(conf_get_int(s->conf, CONF_ssh_rekey_time), 60); - ticks = mins * 60 * TICKSPERSEC; - - /* Handle change from previous setting */ - if (rekey_time != 0 && rekey_time != mins) { - unsigned long next; - unsigned long now = GETTICKCOUNT(); - - mins = rekey_time; - ticks = mins * 60 * TICKSPERSEC; - next = s->last_rekey + ticks; - - /* If overdue, caller will rekey synchronously now */ - if (now - s->last_rekey > ticks) - return true; - ticks = next - now; - } - -#ifndef NO_GSSAPI - if (s->gss_kex_used) { - /* - * If we've used GSSAPI key exchange, then we should - * periodically check whether we need to do another one to - * pass new credentials to the server. - */ - unsigned long gssmins; - - /* Check cascade conditions more frequently if configured */ - gssmins = sanitise_rekey_time( - conf_get_int(s->conf, CONF_gssapirekey), GSS_DEF_REKEY_MINS); - if (gssmins > 0) { - if (gssmins < mins) - ticks = (mins = gssmins) * 60 * TICKSPERSEC; - - if ((s->gss_status & GSS_KEX_CAPABLE) != 0) { - /* - * Run next timer even sooner if it would otherwise be - * too close to the context expiration time - */ - if ((s->gss_status & GSS_CTXT_EXPIRES) == 0 && - s->gss_ctxt_lifetime - mins * 60 < 2 * MIN_CTXT_LIFETIME) - ticks -= 2 * MIN_CTXT_LIFETIME * TICKSPERSEC; - } - } - } -#endif - - /* Schedule the next timer */ - s->next_rekey = schedule_timer(ticks, ssh2_transport_timer, s); - return false; -} - -void ssh2_transport_dialog_callback(void *loginv, int ret) -{ - struct ssh2_transport_state *s = (struct ssh2_transport_state *)loginv; - s->dlgret = ret; - ssh_ppl_process_queue(&s->ppl); -} - -#ifndef NO_GSSAPI -/* - * This is called at the beginning of each SSH rekey to determine - * whether we are GSS capable, and if we did GSS key exchange, and are - * delegating credentials, it is also called periodically to determine - * whether we should rekey in order to delegate (more) fresh - * credentials. This is called "credential cascading". - * - * On Windows, with SSPI, we may not get the credential expiration, as - * Windows automatically renews from cached passwords, so the - * credential effectively never expires. Since we still want to - * cascade when the local TGT is updated, we use the expiration of a - * newly obtained context as a proxy for the expiration of the TGT. - */ -static void ssh2_transport_gss_update(struct ssh2_transport_state *s, - bool definitely_rekeying) -{ - PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ - int gss_stat; - time_t gss_cred_expiry; - unsigned long mins; - Ssh_gss_buf gss_sndtok; - Ssh_gss_buf gss_rcvtok; - Ssh_gss_ctx gss_ctx; - - s->gss_status = 0; - - /* - * Nothing to do if no GSSAPI libraries are configured or GSSAPI - * auth is not enabled. - */ - if (s->shgss->libs->nlibraries == 0) - return; - if (!conf_get_bool(s->conf, CONF_try_gssapi_auth) && - !conf_get_bool(s->conf, CONF_try_gssapi_kex)) - return; - - /* Import server name and cache it */ - if (s->shgss->srv_name == GSS_C_NO_NAME) { - gss_stat = s->shgss->lib->import_name( - s->shgss->lib, s->fullhostname, &s->shgss->srv_name); - if (gss_stat != SSH_GSS_OK) { - if (gss_stat == SSH_GSS_BAD_HOST_NAME) - ppl_logevent("GSSAPI import name failed - Bad service name;" - " won't use GSS key exchange"); - else - ppl_logevent("GSSAPI import name failed;" - " won't use GSS key exchange"); - return; - } - } - - /* - * Do we (still) have credentials? Capture the credential - * expiration when available - */ - gss_stat = s->shgss->lib->acquire_cred( - s->shgss->lib, &gss_ctx, &gss_cred_expiry); - if (gss_stat != SSH_GSS_OK) - return; - - SSH_GSS_CLEAR_BUF(&gss_sndtok); - SSH_GSS_CLEAR_BUF(&gss_rcvtok); - - /* - * When acquire_cred yields no useful expiration, get a proxy for - * the cred expiration from the context expiration. - */ - gss_stat = s->shgss->lib->init_sec_context( - s->shgss->lib, &gss_ctx, s->shgss->srv_name, - 0 /* don't delegate */, &gss_rcvtok, &gss_sndtok, - (gss_cred_expiry == GSS_NO_EXPIRATION ? &gss_cred_expiry : NULL), - &s->gss_ctxt_lifetime); - - /* This context was for testing only. */ - if (gss_ctx) - s->shgss->lib->release_cred(s->shgss->lib, &gss_ctx); - - if (gss_stat != SSH_GSS_OK && - gss_stat != SSH_GSS_S_CONTINUE_NEEDED) { - /* - * No point in verbosely interrupting the user to tell them we - * couldn't get GSS credentials, if this was only a check - * between key exchanges to see if fresh ones were available. - * When we do do a rekey, this message (if displayed) will - * appear among the standard rekey blurb, but when we're not, - * it shouldn't pop up all the time regardless. - */ - if (definitely_rekeying) - ppl_logevent("No GSSAPI security context available"); - - return; - } - - if (gss_sndtok.length) - s->shgss->lib->free_tok(s->shgss->lib, &gss_sndtok); - - s->gss_status |= GSS_KEX_CAPABLE; - - /* - * When rekeying to cascade, avoding doing this too close to the - * context expiration time, since the key exchange might fail. - */ - if (s->gss_ctxt_lifetime < MIN_CTXT_LIFETIME) - s->gss_status |= GSS_CTXT_MAYFAIL; - - /* - * If we're not delegating credentials, rekeying is not used to - * refresh them. We must avoid setting GSS_CRED_UPDATED or - * GSS_CTXT_EXPIRES when credential delegation is disabled. - */ - if (!conf_get_bool(s->conf, CONF_gssapifwd)) - return; - - if (s->gss_cred_expiry != GSS_NO_EXPIRATION && - difftime(gss_cred_expiry, s->gss_cred_expiry) > 0) - s->gss_status |= GSS_CRED_UPDATED; - - mins = sanitise_rekey_time( - conf_get_int(s->conf, CONF_gssapirekey), GSS_DEF_REKEY_MINS); - if (mins > 0 && s->gss_ctxt_lifetime <= mins * 60) - s->gss_status |= GSS_CTXT_EXPIRES; -} -#endif /* NO_GSSAPI */ - -ptrlen ssh2_transport_get_session_id(PacketProtocolLayer *ppl) -{ - struct ssh2_transport_state *s; - - assert(ppl->vt == &ssh2_transport_vtable); - s = container_of(ppl, struct ssh2_transport_state, ppl); - - assert(s->got_session_id); - return make_ptrlen(s->session_id, s->session_id_len); -} - -void ssh2_transport_notify_auth_done(PacketProtocolLayer *ppl) -{ - struct ssh2_transport_state *s; - - assert(ppl->vt == &ssh2_transport_vtable); - s = container_of(ppl, struct ssh2_transport_state, ppl); - - s->rekey_reason = NULL; /* will be filled in later */ - s->rekey_class = RK_POST_USERAUTH; - queue_idempotent_callback(&s->ppl.ic_process_queue); -} - -static bool ssh2_transport_get_specials( - PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx) -{ - struct ssh2_transport_state *s = - container_of(ppl, struct ssh2_transport_state, ppl); - bool need_separator = false; - bool toret = false; - - if (ssh_ppl_get_specials(s->higher_layer, add_special, ctx)) { - need_separator = true; - toret = true; - } - - /* - * Don't bother offering rekey-based specials if we've decided the - * remote won't cope with it, since we wouldn't bother sending it - * if asked anyway. - */ - if (!(s->ppl.remote_bugs & BUG_SSH2_REKEY)) { - if (need_separator) { - add_special(ctx, NULL, SS_SEP, 0); - need_separator = false; - } - - add_special(ctx, "Repeat key exchange", SS_REKEY, 0); - toret = true; - - if (s->n_uncert_hostkeys) { - int i; - - add_special(ctx, NULL, SS_SEP, 0); - add_special(ctx, "Cache new host key type", SS_SUBMENU, 0); - for (i = 0; i < s->n_uncert_hostkeys; i++) { - const ssh_keyalg *alg = - ssh2_hostkey_algs[s->uncert_hostkeys[i]].alg; - - add_special(ctx, alg->ssh_id, SS_XCERT, s->uncert_hostkeys[i]); - } - add_special(ctx, NULL, SS_EXITMENU, 0); - } - } - - return toret; -} - -static void ssh2_transport_special_cmd(PacketProtocolLayer *ppl, - SessionSpecialCode code, int arg) -{ - struct ssh2_transport_state *s = - container_of(ppl, struct ssh2_transport_state, ppl); - - if (code == SS_REKEY) { - if (!s->kex_in_progress) { - s->rekey_reason = "at user request"; - s->rekey_class = RK_NORMAL; - queue_idempotent_callback(&s->ppl.ic_process_queue); - } - } else if (code == SS_XCERT) { - if (!s->kex_in_progress) { - s->cross_certifying = s->hostkey_alg = ssh2_hostkey_algs[arg].alg; - s->rekey_reason = "cross-certifying new host key"; - s->rekey_class = RK_NORMAL; - queue_idempotent_callback(&s->ppl.ic_process_queue); - } - } else { - /* Send everything else to the next layer up. This includes - * SS_PING/SS_NOP, which we _could_ handle here - but it's - * better to put them in the connection layer, so they'll - * still work in bare connection mode. */ - ssh_ppl_special_cmd(s->higher_layer, code, arg); - } -} - -/* Safely convert rekey_time to unsigned long minutes */ -static unsigned long sanitise_rekey_time(int rekey_time, unsigned long def) -{ - if (rekey_time < 0 || rekey_time > MAX_TICK_MINS) - rekey_time = def; - return (unsigned long)rekey_time; -} - -static void ssh2_transport_set_max_data_size(struct ssh2_transport_state *s) -{ - s->max_data_size = parse_blocksize( - conf_get_str(s->conf, CONF_ssh_rekey_data)); -} - -static void ssh2_transport_reconfigure(PacketProtocolLayer *ppl, Conf *conf) -{ - struct ssh2_transport_state *s; - const char *rekey_reason = NULL; - bool rekey_mandatory = false; - unsigned long old_max_data_size, rekey_time; - int i; - - assert(ppl->vt == &ssh2_transport_vtable); - s = container_of(ppl, struct ssh2_transport_state, ppl); - - rekey_time = sanitise_rekey_time( - conf_get_int(conf, CONF_ssh_rekey_time), 60); - if (ssh2_transport_timer_update(s, rekey_time)) - rekey_reason = "timeout shortened"; - - old_max_data_size = s->max_data_size; - ssh2_transport_set_max_data_size(s); - if (old_max_data_size != s->max_data_size && - s->max_data_size != 0) { - if (s->max_data_size < old_max_data_size) { - unsigned long diff = old_max_data_size - s->max_data_size; - - dts_consume(&s->stats->out, diff); - dts_consume(&s->stats->in, diff); - if (s->stats->out.expired || s->stats->in.expired) - rekey_reason = "data limit lowered"; - } else { - unsigned long diff = s->max_data_size - old_max_data_size; - if (s->stats->out.running) - s->stats->out.remaining += diff; - if (s->stats->in.running) - s->stats->in.remaining += diff; - } - } - - if (conf_get_bool(s->conf, CONF_compression) != - conf_get_bool(conf, CONF_compression)) { - rekey_reason = "compression setting changed"; - rekey_mandatory = true; - } - - for (i = 0; i < CIPHER_MAX; i++) - if (conf_get_int_int(s->conf, CONF_ssh_cipherlist, i) != - conf_get_int_int(conf, CONF_ssh_cipherlist, i)) { - rekey_reason = "cipher settings changed"; - rekey_mandatory = true; - } - if (conf_get_bool(s->conf, CONF_ssh2_des_cbc) != - conf_get_bool(conf, CONF_ssh2_des_cbc)) { - rekey_reason = "cipher settings changed"; - rekey_mandatory = true; - } - - conf_free(s->conf); - s->conf = conf_copy(conf); - - if (rekey_reason) { - if (!s->kex_in_progress && !ssh2_bpp_rekey_inadvisable(s->ppl.bpp)) { - s->rekey_reason = rekey_reason; - s->rekey_class = RK_NORMAL; - queue_idempotent_callback(&s->ppl.ic_process_queue); - } else if (rekey_mandatory) { - s->deferred_rekey_reason = rekey_reason; - } - } - - /* Also pass the configuration along to our higher layer */ - ssh_ppl_reconfigure(s->higher_layer, conf); -} - -static bool ssh2_transport_want_user_input(PacketProtocolLayer *ppl) -{ - struct ssh2_transport_state *s = - container_of(ppl, struct ssh2_transport_state, ppl); - - /* Just delegate this to the higher layer */ - return ssh_ppl_want_user_input(s->higher_layer); -} - -static void ssh2_transport_got_user_input(PacketProtocolLayer *ppl) -{ - struct ssh2_transport_state *s = - container_of(ppl, struct ssh2_transport_state, ppl); - - /* Just delegate this to the higher layer */ - ssh_ppl_got_user_input(s->higher_layer); -} - -static int weak_algorithm_compare(void *av, void *bv) -{ - uintptr_t a = (uintptr_t)av, b = (uintptr_t)bv; - return a < b ? -1 : a > b ? +1 : 0; -} - -/* - * Wrapper on seat_confirm_weak_crypto_primitive(), which uses the - * tree234 s->weak_algorithms_consented_to to ensure we ask at most - * once about any given crypto primitive. - */ -static int ssh2_transport_confirm_weak_crypto_primitive( - struct ssh2_transport_state *s, const char *type, const char *name, - const void *alg) -{ - if (find234(s->weak_algorithms_consented_to, (void *)alg, NULL)) - return 1; - add234(s->weak_algorithms_consented_to, (void *)alg); - - return seat_confirm_weak_crypto_primitive( - s->ppl.seat, type, name, ssh2_transport_dialog_callback, s); -} - -static size_t ssh2_transport_queued_data_size(PacketProtocolLayer *ppl) -{ - struct ssh2_transport_state *s = - container_of(ppl, struct ssh2_transport_state, ppl); - - return (ssh_ppl_default_queued_data_size(ppl) + - ssh_ppl_queued_data_size(s->higher_layer)); -} diff --git a/ssh2transport.h b/ssh2transport.h deleted file mode 100644 index 349c06f0..00000000 --- a/ssh2transport.h +++ /dev/null @@ -1,245 +0,0 @@ -/* - * Header connecting the pieces of the SSH-2 transport layer. - */ - -#ifndef PUTTY_SSH2TRANSPORT_H -#define PUTTY_SSH2TRANSPORT_H - -#ifndef NO_GSSAPI -#include "sshgssc.h" -#include "sshgss.h" -#define MIN_CTXT_LIFETIME 5 /* Avoid rekey with short lifetime (seconds) */ -#define GSS_KEX_CAPABLE (1<<0) /* Can do GSS KEX */ -#define GSS_CRED_UPDATED (1<<1) /* Cred updated since previous delegation */ -#define GSS_CTXT_EXPIRES (1<<2) /* Context expires before next timer */ -#define GSS_CTXT_MAYFAIL (1<<3) /* Context may expire during handshake */ -#endif - -#define DH_MIN_SIZE 1024 -#define DH_MAX_SIZE 8192 - -#define MAXKEXLIST 16 -struct kexinit_algorithm { - const char *name; - union { - struct { - const ssh_kex *kex; - bool warn; - } kex; - struct { - const ssh_keyalg *hostkey; - unsigned hkflags; - bool warn; - } hk; - struct { - const ssh_cipheralg *cipher; - bool warn; - } cipher; - struct { - const ssh2_macalg *mac; - bool etm; - } mac; - struct { - const ssh_compression_alg *comp; - bool delayed; - } comp; - } u; -}; - -#define HOSTKEY_ALGORITHMS(X) \ - X(HK_ED25519, ssh_ecdsa_ed25519) \ - X(HK_ED448, ssh_ecdsa_ed448) \ - X(HK_ECDSA, ssh_ecdsa_nistp256) \ - X(HK_ECDSA, ssh_ecdsa_nistp384) \ - X(HK_ECDSA, ssh_ecdsa_nistp521) \ - X(HK_DSA, ssh_dss) \ - X(HK_RSA, ssh_rsa_sha512) \ - X(HK_RSA, ssh_rsa_sha256) \ - X(HK_RSA, ssh_rsa) \ - /* end of list */ -#define COUNT_HOSTKEY_ALGORITHM(type, alg) +1 -#define N_HOSTKEY_ALGORITHMS (0 HOSTKEY_ALGORITHMS(COUNT_HOSTKEY_ALGORITHM)) - -struct ssh_signkey_with_user_pref_id { - const ssh_keyalg *alg; - int id; -}; -extern const struct ssh_signkey_with_user_pref_id - ssh2_hostkey_algs[N_HOSTKEY_ALGORITHMS]; - -/* - * Enumeration of high-level classes of reason why we might need to do - * a repeat key exchange. A full detailed reason in human-readable - * string form for the Event Log is also provided, but this enum type - * is used to discriminate between classes of reason that the code - * needs to treat differently. - * - * RK_NONE == 0 is the value indicating that no rekey is currently - * needed at all. RK_INITIAL indicates that we haven't even done the - * _first_ key exchange yet. RK_SERVER indicates that we're rekeying - * because the server asked for it, not because we decided it - * ourselves. RK_NORMAL is the usual case. RK_GSS_UPDATE indicates - * that we're rekeying because we've just got new GSSAPI credentials - * (hence there's no point in doing a preliminary check for new GSS - * creds, because we already know the answer); RK_POST_USERAUTH - * indicates that _if_ we're going to need a post-userauth immediate - * rekey for any reason, this is the moment to do it. - * - * So RK_POST_USERAUTH only tells the transport layer to _consider_ - * rekeying, not to definitely do it. Also, that one enum value is - * special in that the user-readable reason text is passed in to the - * transport layer as NULL, whereas fills in the reason text after it - * decides whether it needs a rekey at all. In the other cases, - * rekey_reason is passed in to the at the same time as rekey_class. - */ -typedef enum RekeyClass { - RK_NONE = 0, - RK_INITIAL, - RK_SERVER, - RK_NORMAL, - RK_POST_USERAUTH, - RK_GSS_UPDATE -} RekeyClass; - -typedef struct transport_direction { - const ssh_cipheralg *cipher; - const ssh2_macalg *mac; - bool etm_mode; - const ssh_compression_alg *comp; - bool comp_delayed; - int mkkey_adjust; -} transport_direction; - -struct ssh2_transport_state { - int crState, crStateKex; - - PacketProtocolLayer *higher_layer; - PktInQueue pq_in_higher; - PktOutQueue pq_out_higher; - IdempotentCallback ic_pq_out_higher; - - Conf *conf; - char *savedhost; - int savedport; - const char *rekey_reason; - enum RekeyClass rekey_class; - - unsigned long max_data_size; - - const ssh_kex *kex_alg; - const ssh_keyalg *hostkey_alg; - char *hostkey_str; /* string representation, for easy checking in rekeys */ - unsigned char session_id[MAX_HASH_LEN]; - int session_id_len; - int dh_min_size, dh_max_size; - bool dh_got_size_bounds; - dh_ctx *dh_ctx; - ssh_hash *exhash; - - struct DataTransferStats *stats; - - const SshServerConfig *ssc; - - char *client_greeting, *server_greeting; - - bool kex_in_progress; - unsigned long next_rekey, last_rekey; - const char *deferred_rekey_reason; - bool higher_layer_ok; - - /* - * Fully qualified host name, which we need if doing GSSAPI. - */ - char *fullhostname; - - /* shgss is outside the ifdef on purpose to keep APIs simple. If - * NO_GSSAPI is not defined, then it's just an opaque structure - * tag and the pointer will be NULL. */ - struct ssh_connection_shared_gss_state *shgss; -#ifndef NO_GSSAPI - int gss_status; - time_t gss_cred_expiry; /* Re-delegate if newer */ - unsigned long gss_ctxt_lifetime; /* Re-delegate when short */ -#endif - ssh_transient_hostkey_cache *thc; - - bool gss_kex_used; - - int nbits, pbits; - bool warn_kex, warn_hk, warn_cscipher, warn_sccipher; - mp_int *p, *g, *e, *f, *K; - strbuf *outgoing_kexinit, *incoming_kexinit; - strbuf *client_kexinit, *server_kexinit; /* aliases to the above */ - int kex_init_value, kex_reply_value; - transport_direction in, out, *cstrans, *sctrans; - ptrlen hostkeydata, sigdata; - strbuf *hostkeyblob; - char *keystr; - ssh_key *hkey; /* actual host key */ - unsigned hkflags; /* signing flags, used in server */ - RSAKey *rsa_kex_key; /* for RSA kex */ - bool rsa_kex_key_needs_freeing; - ecdh_key *ecdh_key; /* for ECDH kex */ - unsigned char exchange_hash[MAX_HASH_LEN]; - bool can_gssapi_keyex; - bool need_gss_transient_hostkey; - bool warned_about_no_gss_transient_hostkey; - bool got_session_id; - bool can_send_ext_info, post_newkeys_ext_info; - int dlgret; - bool guessok; - bool ignorepkt; - struct kexinit_algorithm kexlists[NKEXLIST][MAXKEXLIST]; -#ifndef NO_GSSAPI - Ssh_gss_buf gss_buf; - Ssh_gss_buf gss_rcvtok, gss_sndtok; - Ssh_gss_stat gss_stat; - Ssh_gss_buf mic; - bool init_token_sent; - bool complete_rcvd; - bool gss_delegate; -#endif - - /* List of crypto primitives below the warning threshold that the - * user has already clicked OK to, so that we don't keep asking - * about them again during rekeys. This directly stores pointers - * to the algorithm vtables, compared by pointer value (which is - * not a determinism hazard, because we're only using it as a - * set). */ - tree234 *weak_algorithms_consented_to; - - /* - * List of host key algorithms for which we _don't_ have a stored - * host key. These are indices into the main hostkey_algs[] array - */ - int uncert_hostkeys[N_HOSTKEY_ALGORITHMS]; - int n_uncert_hostkeys; - - /* - * Indicate that the current rekey is intended to finish with a - * newly cross-certified host key. To double-check that we - * certified the right one, we set this to point to the host key - * algorithm we expect it to be. - */ - const ssh_keyalg *cross_certifying; - - ssh_key *const *hostkeys; - int nhostkeys; - - PacketProtocolLayer ppl; -}; - -/* Helpers shared between transport and kex */ -PktIn *ssh2_transport_pop(struct ssh2_transport_state *s); -void ssh2_transport_dialog_callback(void *, int); - -/* Provided by transport for use in kex */ -void ssh2transport_finalise_exhash(struct ssh2_transport_state *s); - -/* Provided by kex for use in transport. Must set the 'aborted' flag - * if it throws a connection-terminating error, so that the caller - * won't have to check that by looking inside its state parameter - * which might already have been freed. */ -void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted); - -#endif /* PUTTY_SSH2TRANSPORT_H */ diff --git a/ssh2userauth-server.c b/ssh2userauth-server.c deleted file mode 100644 index cacddee6..00000000 --- a/ssh2userauth-server.c +++ /dev/null @@ -1,377 +0,0 @@ -/* - * Packet protocol layer for the server side of the SSH-2 userauth - * protocol (RFC 4252). - */ - -#include - -#include "putty.h" -#include "ssh.h" -#include "sshbpp.h" -#include "sshppl.h" -#include "sshcr.h" -#include "sshserver.h" - -#ifndef NO_GSSAPI -#include "sshgssc.h" -#include "sshgss.h" -#endif - -struct ssh2_userauth_server_state { - int crState; - - PacketProtocolLayer *transport_layer, *successor_layer; - ptrlen session_id; - - AuthPolicy *authpolicy; - const SshServerConfig *ssc; - - ptrlen username, service, method; - unsigned methods, this_method; - bool partial_success; - - AuthKbdInt *aki; - - PacketProtocolLayer ppl; -}; - -static void ssh2_userauth_server_free(PacketProtocolLayer *); -static void ssh2_userauth_server_process_queue(PacketProtocolLayer *); - -static const PacketProtocolLayerVtable ssh2_userauth_server_vtable = { - .free = ssh2_userauth_server_free, - .process_queue = ssh2_userauth_server_process_queue, - .queued_data_size = ssh_ppl_default_queued_data_size, - .name = "ssh-userauth", - /* other methods are NULL */ -}; - -static void free_auth_kbdint(AuthKbdInt *aki) -{ - int i; - - if (!aki) - return; - - sfree(aki->title); - sfree(aki->instruction); - for (i = 0; i < aki->nprompts; i++) - sfree(aki->prompts[i].prompt); - sfree(aki->prompts); - sfree(aki); -} - -PacketProtocolLayer *ssh2_userauth_server_new( - PacketProtocolLayer *successor_layer, AuthPolicy *authpolicy, - const SshServerConfig *ssc) -{ - struct ssh2_userauth_server_state *s = - snew(struct ssh2_userauth_server_state); - memset(s, 0, sizeof(*s)); - s->ppl.vt = &ssh2_userauth_server_vtable; - - s->successor_layer = successor_layer; - s->authpolicy = authpolicy; - s->ssc = ssc; - - return &s->ppl; -} - -void ssh2_userauth_server_set_transport_layer(PacketProtocolLayer *userauth, - PacketProtocolLayer *transport) -{ - struct ssh2_userauth_server_state *s = - container_of(userauth, struct ssh2_userauth_server_state, ppl); - s->transport_layer = transport; -} - -static void ssh2_userauth_server_free(PacketProtocolLayer *ppl) -{ - struct ssh2_userauth_server_state *s = - container_of(ppl, struct ssh2_userauth_server_state, ppl); - - if (s->successor_layer) - ssh_ppl_free(s->successor_layer); - - free_auth_kbdint(s->aki); - - sfree(s); -} - -static PktIn *ssh2_userauth_server_pop(struct ssh2_userauth_server_state *s) -{ - return pq_pop(s->ppl.in_pq); -} - -static void ssh2_userauth_server_add_session_id( - struct ssh2_userauth_server_state *s, strbuf *sigdata) -{ - if (s->ppl.remote_bugs & BUG_SSH2_PK_SESSIONID) { - put_datapl(sigdata, s->session_id); - } else { - put_stringpl(sigdata, s->session_id); - } -} - -static void ssh2_userauth_server_process_queue(PacketProtocolLayer *ppl) -{ - struct ssh2_userauth_server_state *s = - container_of(ppl, struct ssh2_userauth_server_state, ppl); - PktIn *pktin; - PktOut *pktout; - - crBegin(s->crState); - - s->session_id = ssh2_transport_get_session_id(s->transport_layer); - - if (s->ssc->banner.ptr) { - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_USERAUTH_BANNER); - put_stringpl(pktout, s->ssc->banner); - put_stringz(pktout, ""); /* language tag */ - pq_push(s->ppl.out_pq, pktout); - } - - while (1) { - crMaybeWaitUntilV((pktin = ssh2_userauth_server_pop(s)) != NULL); - if (pktin->type != SSH2_MSG_USERAUTH_REQUEST) { - ssh_proto_error(s->ppl.ssh, "Received unexpected packet when " - "expecting USERAUTH_REQUEST, type %d (%s)", - pktin->type, - ssh2_pkt_type(s->ppl.bpp->pls->kctx, - s->ppl.bpp->pls->actx, pktin->type)); - return; - } - - s->username = get_string(pktin); - s->service = get_string(pktin); - s->method = get_string(pktin); - - if (!ptrlen_eq_string(s->service, s->successor_layer->vt->name)) { - /* - * Unconditionally reject authentication for any service - * other than the one we're going to hand over to. - */ - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_USERAUTH_FAILURE); - put_stringz(pktout, ""); - put_bool(pktout, false); - pq_push(s->ppl.out_pq, pktout); - continue; - } - - s->methods = auth_methods(s->authpolicy); - s->partial_success = false; - - if (ptrlen_eq_string(s->method, "none")) { - s->this_method = AUTHMETHOD_NONE; - if (!(s->methods & s->this_method)) - goto failure; - - if (!auth_none(s->authpolicy, s->username)) - goto failure; - } else if (ptrlen_eq_string(s->method, "password")) { - bool changing; - ptrlen password, new_password, *new_password_ptr; - - s->this_method = AUTHMETHOD_PASSWORD; - if (!(s->methods & s->this_method)) - goto failure; - - changing = get_bool(pktin); - password = get_string(pktin); - - if (changing) { - new_password = get_string(pktin); - new_password_ptr = &new_password; - } else { - new_password_ptr = NULL; - } - - int result = auth_password(s->authpolicy, s->username, - password, new_password_ptr); - if (result == 2) { - pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ); - put_stringz(pktout, "Please change your password"); - put_stringz(pktout, ""); /* language tag */ - pq_push(s->ppl.out_pq, pktout); - continue; /* skip USERAUTH_{SUCCESS,FAILURE} epilogue */ - } else if (result != 1) { - goto failure; - } - } else if (ptrlen_eq_string(s->method, "publickey")) { - bool has_signature, success, send_pk_ok, key_really_ok; - ptrlen algorithm, blob, signature; - const ssh_keyalg *keyalg; - ssh_key *key; - strbuf *sigdata; - - s->this_method = AUTHMETHOD_PUBLICKEY; - if (!(s->methods & s->this_method)) - goto failure; - - has_signature = get_bool(pktin); - algorithm = get_string(pktin); - blob = get_string(pktin); - - key_really_ok = auth_publickey(s->authpolicy, s->username, blob); - send_pk_ok = key_really_ok || - s->ssc->stunt_pretend_to_accept_any_pubkey; - - if (!has_signature) { - if (!send_pk_ok) - goto failure; - - pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH2_MSG_USERAUTH_PK_OK); - put_stringpl(pktout, algorithm); - put_stringpl(pktout, blob); - pq_push(s->ppl.out_pq, pktout); - continue; /* skip USERAUTH_{SUCCESS,FAILURE} epilogue */ - } - - if (!key_really_ok) - goto failure; - - keyalg = find_pubkey_alg_len(algorithm); - if (!keyalg) - goto failure; - key = ssh_key_new_pub(keyalg, blob); - if (!key) - goto failure; - - sigdata = strbuf_new(); - ssh2_userauth_server_add_session_id(s, sigdata); - put_byte(sigdata, SSH2_MSG_USERAUTH_REQUEST); - put_stringpl(sigdata, s->username); - put_stringpl(sigdata, s->service); - put_stringpl(sigdata, s->method); - put_bool(sigdata, has_signature); - put_stringpl(sigdata, algorithm); - put_stringpl(sigdata, blob); - - signature = get_string(pktin); - success = ssh_key_verify(key, signature, - ptrlen_from_strbuf(sigdata)); - ssh_key_free(key); - strbuf_free(sigdata); - - if (!success) - goto failure; - } else if (ptrlen_eq_string(s->method, "keyboard-interactive")) { - int i, ok; - unsigned n; - - s->this_method = AUTHMETHOD_KBDINT; - if (!(s->methods & s->this_method)) - goto failure; - - do { - s->aki = auth_kbdint_prompts(s->authpolicy, s->username); - if (!s->aki) - goto failure; - - pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH2_MSG_USERAUTH_INFO_REQUEST); - put_stringz(pktout, s->aki->title); - put_stringz(pktout, s->aki->instruction); - put_stringz(pktout, ""); /* language tag */ - put_uint32(pktout, s->aki->nprompts); - for (i = 0; i < s->aki->nprompts; i++) { - put_stringz(pktout, s->aki->prompts[i].prompt); - put_bool(pktout, s->aki->prompts[i].echo); - } - pq_push(s->ppl.out_pq, pktout); - - crMaybeWaitUntilV( - (pktin = ssh2_userauth_server_pop(s)) != NULL); - if (pktin->type != SSH2_MSG_USERAUTH_INFO_RESPONSE) { - ssh_proto_error( - s->ppl.ssh, "Received unexpected packet when " - "expecting USERAUTH_INFO_RESPONSE, type %d (%s)", - pktin->type, - ssh2_pkt_type(s->ppl.bpp->pls->kctx, - s->ppl.bpp->pls->actx, pktin->type)); - return; - } - - n = get_uint32(pktin); - if (n != s->aki->nprompts) { - ssh_proto_error( - s->ppl.ssh, "Received %u keyboard-interactive " - "responses after sending %u prompts", - n, s->aki->nprompts); - return; - } - - { - ptrlen *responses = snewn(s->aki->nprompts, ptrlen); - for (i = 0; i < s->aki->nprompts; i++) - responses[i] = get_string(pktin); - ok = auth_kbdint_responses(s->authpolicy, responses); - sfree(responses); - } - - free_auth_kbdint(s->aki); - s->aki = NULL; - } while (ok == 0); - - if (ok <= 0) - goto failure; - } else { - goto failure; - } - - /* - * If we get here, we've successfully completed this - * authentication step. - */ - if (auth_successful(s->authpolicy, s->username, s->this_method)) { - /* - * ... and it was the last one, so we're completely done. - */ - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_USERAUTH_SUCCESS); - pq_push(s->ppl.out_pq, pktout); - break; - } else { - /* - * ... but another is required, so fall through to - * generation of USERAUTH_FAILURE, having first refreshed - * the bit mask of available methods. - */ - s->methods = auth_methods(s->authpolicy); - } - s->partial_success = true; - - failure: - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_USERAUTH_FAILURE); - { - strbuf *list = strbuf_new(); - if (s->methods & AUTHMETHOD_NONE) - add_to_commasep(list, "none"); - if (s->methods & AUTHMETHOD_PASSWORD) - add_to_commasep(list, "password"); - if (s->methods & AUTHMETHOD_PUBLICKEY) - add_to_commasep(list, "publickey"); - if (s->methods & AUTHMETHOD_KBDINT) - add_to_commasep(list, "keyboard-interactive"); - put_stringsb(pktout, list); - } - put_bool(pktout, s->partial_success); - pq_push(s->ppl.out_pq, pktout); - } - - /* - * Finally, hand over to our successor layer, and return - * immediately without reaching the crFinishV: ssh_ppl_replace - * will have freed us, so crFinishV's zeroing-out of crState would - * be a use-after-free bug. - */ - { - PacketProtocolLayer *successor = s->successor_layer; - s->successor_layer = NULL; /* avoid freeing it ourself */ - ssh_ppl_replace(&s->ppl, successor); - return; /* we've just freed s, so avoid even touching s->crState */ - } - - crFinishV; -} diff --git a/ssh2userauth.c b/ssh2userauth.c deleted file mode 100644 index 01243baf..00000000 --- a/ssh2userauth.c +++ /dev/null @@ -1,1961 +0,0 @@ -/* - * Packet protocol layer for the client side of the SSH-2 userauth - * protocol (RFC 4252). - */ - -#include - -#include "putty.h" -#include "ssh.h" -#include "sshbpp.h" -#include "sshppl.h" -#include "sshcr.h" - -#ifndef NO_GSSAPI -#include "sshgssc.h" -#include "sshgss.h" -#endif - -#define BANNER_LIMIT 131072 - -typedef struct agent_key { - strbuf *blob, *comment; - ptrlen algorithm; -} agent_key; - -struct ssh2_userauth_state { - int crState; - - PacketProtocolLayer *transport_layer, *successor_layer; - Filename *keyfile; - bool show_banner, tryagent, change_username; - char *hostname, *fullhostname; - char *default_username; - bool try_ki_auth, try_gssapi_auth, try_gssapi_kex_auth, gssapi_fwd; - - ptrlen session_id; - enum { - AUTH_TYPE_NONE, - AUTH_TYPE_PUBLICKEY, - AUTH_TYPE_PUBLICKEY_OFFER_LOUD, - AUTH_TYPE_PUBLICKEY_OFFER_QUIET, - AUTH_TYPE_PASSWORD, - AUTH_TYPE_GSSAPI, /* always QUIET */ - AUTH_TYPE_KEYBOARD_INTERACTIVE, - AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET - } type; - bool need_pw, can_pubkey, can_passwd, can_keyb_inter; - int userpass_ret; - bool tried_pubkey_config, done_agent; - struct ssh_connection_shared_gss_state *shgss; -#ifndef NO_GSSAPI - bool can_gssapi; - bool can_gssapi_keyex_auth; - bool tried_gssapi; - bool tried_gssapi_keyex_auth; - time_t gss_cred_expiry; - Ssh_gss_buf gss_buf; - Ssh_gss_buf gss_rcvtok, gss_sndtok; - Ssh_gss_stat gss_stat; -#endif - bool suppress_wait_for_response_packet; - strbuf *last_methods_string; - bool kbd_inter_refused; - prompts_t *cur_prompt; - uint32_t num_prompts; - const char *username; - char *locally_allocated_username; - char *password; - bool got_username; - strbuf *publickey_blob; - bool privatekey_available, privatekey_encrypted; - char *publickey_algorithm; - char *publickey_comment; - void *agent_response_to_free; - ptrlen agent_response; - BinarySource asrc[1]; /* for reading SSH agent response */ - size_t agent_keys_len; - agent_key *agent_keys; - size_t agent_key_index, agent_key_limit; - ptrlen agent_keyalg; - unsigned signflags; - int len; - PktOut *pktout; - bool want_user_input; - - agent_pending_query *auth_agent_query; - bufchain banner; - bufchain_sink banner_bs; - StripCtrlChars *banner_scc; - bool banner_scc_initialised; - - StripCtrlChars *ki_scc; - bool ki_scc_initialised; - bool ki_printed_header; - - PacketProtocolLayer ppl; -}; - -static void ssh2_userauth_free(PacketProtocolLayer *); -static void ssh2_userauth_process_queue(PacketProtocolLayer *); -static bool ssh2_userauth_get_specials( - PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx); -static void ssh2_userauth_special_cmd(PacketProtocolLayer *ppl, - SessionSpecialCode code, int arg); -static bool ssh2_userauth_want_user_input(PacketProtocolLayer *ppl); -static void ssh2_userauth_got_user_input(PacketProtocolLayer *ppl); -static void ssh2_userauth_reconfigure(PacketProtocolLayer *ppl, Conf *conf); - -static void ssh2_userauth_agent_query(struct ssh2_userauth_state *, strbuf *); -static void ssh2_userauth_agent_callback(void *, void *, int); -static void ssh2_userauth_add_sigblob( - struct ssh2_userauth_state *s, PktOut *pkt, ptrlen pkblob, ptrlen sigblob); -static void ssh2_userauth_add_session_id( - struct ssh2_userauth_state *s, strbuf *sigdata); -#ifndef NO_GSSAPI -static PktOut *ssh2_userauth_gss_packet( - struct ssh2_userauth_state *s, const char *authtype); -#endif -static void ssh2_userauth_antispoof_msg( - struct ssh2_userauth_state *s, const char *msg); - -static const PacketProtocolLayerVtable ssh2_userauth_vtable = { - .free = ssh2_userauth_free, - .process_queue = ssh2_userauth_process_queue, - .get_specials = ssh2_userauth_get_specials, - .special_cmd = ssh2_userauth_special_cmd, - .want_user_input = ssh2_userauth_want_user_input, - .got_user_input = ssh2_userauth_got_user_input, - .reconfigure = ssh2_userauth_reconfigure, - .queued_data_size = ssh_ppl_default_queued_data_size, - .name = "ssh-userauth", -}; - -PacketProtocolLayer *ssh2_userauth_new( - PacketProtocolLayer *successor_layer, - const char *hostname, const char *fullhostname, - Filename *keyfile, bool show_banner, bool tryagent, - const char *default_username, bool change_username, - bool try_ki_auth, bool try_gssapi_auth, bool try_gssapi_kex_auth, - bool gssapi_fwd, struct ssh_connection_shared_gss_state *shgss) -{ - struct ssh2_userauth_state *s = snew(struct ssh2_userauth_state); - memset(s, 0, sizeof(*s)); - s->ppl.vt = &ssh2_userauth_vtable; - - s->successor_layer = successor_layer; - s->hostname = dupstr(hostname); - s->fullhostname = dupstr(fullhostname); - s->keyfile = filename_copy(keyfile); - s->show_banner = show_banner; - s->tryagent = tryagent; - s->default_username = dupstr(default_username); - s->change_username = change_username; - s->try_ki_auth = try_ki_auth; - s->try_gssapi_auth = try_gssapi_auth; - s->try_gssapi_kex_auth = try_gssapi_kex_auth; - s->gssapi_fwd = gssapi_fwd; - s->shgss = shgss; - s->last_methods_string = strbuf_new(); - bufchain_init(&s->banner); - bufchain_sink_init(&s->banner_bs, &s->banner); - - return &s->ppl; -} - -void ssh2_userauth_set_transport_layer(PacketProtocolLayer *userauth, - PacketProtocolLayer *transport) -{ - struct ssh2_userauth_state *s = - container_of(userauth, struct ssh2_userauth_state, ppl); - s->transport_layer = transport; -} - -static void ssh2_userauth_free(PacketProtocolLayer *ppl) -{ - struct ssh2_userauth_state *s = - container_of(ppl, struct ssh2_userauth_state, ppl); - bufchain_clear(&s->banner); - - if (s->successor_layer) - ssh_ppl_free(s->successor_layer); - - if (s->agent_keys) { - for (size_t i = 0; i < s->agent_keys_len; i++) { - strbuf_free(s->agent_keys[i].blob); - strbuf_free(s->agent_keys[i].comment); - } - sfree(s->agent_keys); - } - sfree(s->agent_response_to_free); - if (s->auth_agent_query) - agent_cancel_query(s->auth_agent_query); - filename_free(s->keyfile); - sfree(s->default_username); - sfree(s->locally_allocated_username); - sfree(s->hostname); - sfree(s->fullhostname); - sfree(s->publickey_comment); - sfree(s->publickey_algorithm); - if (s->publickey_blob) - strbuf_free(s->publickey_blob); - strbuf_free(s->last_methods_string); - if (s->banner_scc) - stripctrl_free(s->banner_scc); - if (s->ki_scc) - stripctrl_free(s->ki_scc); - sfree(s); -} - -static void ssh2_userauth_filter_queue(struct ssh2_userauth_state *s) -{ - PktIn *pktin; - ptrlen string; - - while ((pktin = pq_peek(s->ppl.in_pq)) != NULL) { - switch (pktin->type) { - case SSH2_MSG_USERAUTH_BANNER: - if (!s->show_banner) { - pq_pop(s->ppl.in_pq); - break; - } - - string = get_string(pktin); - if (string.len > BANNER_LIMIT - bufchain_size(&s->banner)) - string.len = BANNER_LIMIT - bufchain_size(&s->banner); - if (!s->banner_scc_initialised) { - s->banner_scc = seat_stripctrl_new( - s->ppl.seat, BinarySink_UPCAST(&s->banner_bs), SIC_BANNER); - if (s->banner_scc) - stripctrl_enable_line_limiting(s->banner_scc); - s->banner_scc_initialised = true; - } - if (s->banner_scc) - put_datapl(s->banner_scc, string); - else - put_datapl(&s->banner_bs, string); - pq_pop(s->ppl.in_pq); - break; - - default: - return; - } - } -} - -static PktIn *ssh2_userauth_pop(struct ssh2_userauth_state *s) -{ - ssh2_userauth_filter_queue(s); - return pq_pop(s->ppl.in_pq); -} - -static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) -{ - struct ssh2_userauth_state *s = - container_of(ppl, struct ssh2_userauth_state, ppl); - PktIn *pktin; - - ssh2_userauth_filter_queue(s); /* no matter why we were called */ - - crBegin(s->crState); - -#ifndef NO_GSSAPI - s->tried_gssapi = false; - s->tried_gssapi_keyex_auth = false; -#endif - - /* - * Misc one-time setup for authentication. - */ - s->publickey_blob = NULL; - s->session_id = ssh2_transport_get_session_id(s->transport_layer); - - /* - * Load the public half of any configured public key file for - * later use. - */ - if (!filename_is_null(s->keyfile)) { - int keytype; - ppl_logevent("Reading key file \"%s\"", - filename_to_str(s->keyfile)); - keytype = key_type(s->keyfile); - if (keytype == SSH_KEYTYPE_SSH2 || - keytype == SSH_KEYTYPE_SSH2_PUBLIC_RFC4716 || - keytype == SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH) { - const char *error; - s->publickey_blob = strbuf_new(); - if (ppk_loadpub_f(s->keyfile, &s->publickey_algorithm, - BinarySink_UPCAST(s->publickey_blob), - &s->publickey_comment, &error)) { - s->privatekey_available = (keytype == SSH_KEYTYPE_SSH2); - if (!s->privatekey_available) - ppl_logevent("Key file contains public key only"); - s->privatekey_encrypted = ppk_encrypted_f(s->keyfile, NULL); - } else { - ppl_logevent("Unable to load key (%s)", error); - ppl_printf("Unable to load key file \"%s\" (%s)\r\n", - filename_to_str(s->keyfile), error); - strbuf_free(s->publickey_blob); - s->publickey_blob = NULL; - } - } else { - ppl_logevent("Unable to use this key file (%s)", - key_type_to_str(keytype)); - ppl_printf("Unable to use key file \"%s\" (%s)\r\n", - filename_to_str(s->keyfile), - key_type_to_str(keytype)); - s->publickey_blob = NULL; - } - } - - /* - * Find out about any keys Pageant has (but if there's a public - * key configured, filter out all others). - */ - if (s->tryagent && agent_exists()) { - ppl_logevent("Pageant is running. Requesting keys."); - - /* Request the keys held by the agent. */ - { - strbuf *request = strbuf_new_for_agent_query(); - put_byte(request, SSH2_AGENTC_REQUEST_IDENTITIES); - ssh2_userauth_agent_query(s, request); - strbuf_free(request); - crWaitUntilV(!s->auth_agent_query); - } - BinarySource_BARE_INIT_PL(s->asrc, s->agent_response); - - get_uint32(s->asrc); /* skip length field */ - if (get_byte(s->asrc) == SSH2_AGENT_IDENTITIES_ANSWER) { - size_t nkeys = get_uint32(s->asrc); - size_t origpos = s->asrc->pos; - - /* - * Check that the agent response is well formed. - */ - for (size_t i = 0; i < nkeys; i++) { - get_string(s->asrc); /* blob */ - get_string(s->asrc); /* comment */ - if (get_err(s->asrc)) { - ppl_logevent("Pageant's response was truncated"); - goto done_agent_query; - } - } - - /* - * Copy the list of public-key blobs out of the Pageant - * response. - */ - BinarySource_REWIND_TO(s->asrc, origpos); - s->agent_keys_len = nkeys; - s->agent_keys = snewn(s->agent_keys_len, agent_key); - for (size_t i = 0; i < nkeys; i++) { - s->agent_keys[i].blob = strbuf_new(); - put_datapl(s->agent_keys[i].blob, get_string(s->asrc)); - s->agent_keys[i].comment = strbuf_new(); - put_datapl(s->agent_keys[i].comment, get_string(s->asrc)); - - /* Also, extract the algorithm string from the start - * of the public-key blob. */ - BinarySource src[1]; - BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf( - s->agent_keys[i].blob)); - s->agent_keys[i].algorithm = get_string(src); - } - - ppl_logevent("Pageant has %"SIZEu" SSH-2 keys", nkeys); - - if (s->publickey_blob) { - /* - * If we've been given a specific public key blob, - * filter the list of keys to try from the agent down - * to only that one, or none if it's not there. - */ - ptrlen our_blob = ptrlen_from_strbuf(s->publickey_blob); - size_t i; - - for (i = 0; i < nkeys; i++) { - if (ptrlen_eq_ptrlen(our_blob, ptrlen_from_strbuf( - s->agent_keys[i].blob))) - break; - } - - if (i < nkeys) { - ppl_logevent("Pageant key #%"SIZEu" matches " - "configured key file", i); - s->agent_key_index = i; - s->agent_key_limit = i+1; - } else { - ppl_logevent("Configured key file not in Pageant"); - s->agent_key_index = 0; - s->agent_key_limit = 0; - } - } else { - /* - * Otherwise, try them all. - */ - s->agent_key_index = 0; - s->agent_key_limit = nkeys; - } - } else { - ppl_logevent("Failed to get reply from Pageant"); - } - done_agent_query:; - } - - /* - * 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->got_username = false; - while (1) { - /* - * Get a username. - */ - if (s->got_username && !s->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 ((s->username = s->default_username) == NULL) { - s->cur_prompt = new_prompts(); - s->cur_prompt->to_server = true; - s->cur_prompt->from_server = false; - s->cur_prompt->name = dupstr("SSH login name"); - add_prompt(s->cur_prompt, dupstr("login as: "), true); - s->userpass_ret = seat_get_userpass_input( - s->ppl.seat, s->cur_prompt, NULL); - while (1) { - while (s->userpass_ret < 0 && - bufchain_size(s->ppl.user_input) > 0) - s->userpass_ret = seat_get_userpass_input( - s->ppl.seat, s->cur_prompt, s->ppl.user_input); - - if (s->userpass_ret >= 0) - break; - - s->want_user_input = true; - crReturnV; - s->want_user_input = false; - } - if (!s->userpass_ret) { - /* - * seat_get_userpass_input() failed to get a username. - * Terminate. - */ - free_prompts(s->cur_prompt); - ssh_user_close(s->ppl.ssh, "No username provided"); - return; - } - sfree(s->locally_allocated_username); /* for change_username */ - s->username = s->locally_allocated_username = - prompt_get_result(s->cur_prompt->prompts[0]); - free_prompts(s->cur_prompt); - } else { - if (seat_verbose(s->ppl.seat) || seat_interactive(s->ppl.seat)) - ppl_printf("Using username \"%s\".\r\n", s->username); - } - 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. - */ - s->ppl.bpp->pls->actx = SSH2_PKTCTX_NOAUTH; - - s->pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST); - put_stringz(s->pktout, s->username); - put_stringz(s->pktout, s->successor_layer->vt->name); - put_stringz(s->pktout, "none"); /* method */ - pq_push(s->ppl.out_pq, s->pktout); - s->type = AUTH_TYPE_NONE; - - s->tried_pubkey_config = false; - s->kbd_inter_refused = false; - s->done_agent = false; - - while (1) { - /* - * Wait for the result of the last authentication request, - * unless the request terminated for some reason on our - * own side. - */ - if (s->suppress_wait_for_response_packet) { - pktin = NULL; - s->suppress_wait_for_response_packet = false; - } else { - crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL); - } - - /* - * 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.) - * - * 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.) - * - * The banner data has been sanitised already by this - * point, but we still need to precede and follow it with - * anti-spoofing header lines. - */ - if (bufchain_size(&s->banner) && - (seat_verbose(s->ppl.seat) || seat_interactive(s->ppl.seat))) { - if (s->banner_scc) { - ssh2_userauth_antispoof_msg( - s, "Pre-authentication banner message from server:"); - seat_set_trust_status(s->ppl.seat, false); - } - - bool mid_line = false; - while (bufchain_size(&s->banner) > 0) { - ptrlen data = bufchain_prefix(&s->banner); - seat_stderr_pl(s->ppl.seat, data); - mid_line = - (((const char *)data.ptr)[data.len-1] != '\n'); - bufchain_consume(&s->banner, data.len); - } - bufchain_clear(&s->banner); - - if (mid_line) - seat_stderr_pl(s->ppl.seat, PTRLEN_LITERAL("\r\n")); - - if (s->banner_scc) { - seat_set_trust_status(s->ppl.seat, true); - ssh2_userauth_antispoof_msg( - s, "End of banner message from server"); - } - } - - if (pktin && pktin->type == SSH2_MSG_USERAUTH_SUCCESS) { - ppl_logevent("Access granted"); - goto userauth_success; - } - - if (pktin && pktin->type != SSH2_MSG_USERAUTH_FAILURE && - s->type != AUTH_TYPE_GSSAPI) { - ssh_proto_error(s->ppl.ssh, "Received unexpected packet " - "in response to authentication request, " - "type %d (%s)", pktin->type, - ssh2_pkt_type(s->ppl.bpp->pls->kctx, - s->ppl.bpp->pls->actx, - pktin->type)); - return; - } - - /* - * 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 && pktin->type == SSH2_MSG_USERAUTH_FAILURE) { - ptrlen methods = get_string(pktin); - bool partial_success = get_bool(pktin); - - if (!partial_success) { - /* - * We have received an unequivocal Access - * Denied. This can translate to a variety of - * messages, or no message at all. - * - * For forms of authentication which are attempted - * implicitly, by which I mean without printing - * anything in the window indicating that we're - * trying them, we should never print 'Access - * denied'. - * - * If we do print a message saying that we're - * attempting some kind of authentication, it's OK - * to print a followup message saying it failed - - * but the message may sometimes be more specific - * than simply '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) - ppl_printf("Server refused our key\r\n"); - ppl_logevent("Server refused our key"); - } else if (s->type == AUTH_TYPE_PUBLICKEY) { - /* This _shouldn't_ happen except by a - * protocol bug causing client and server to - * disagree on what is a correct signature. */ - ppl_printf("Server refused public-key signature" - " despite accepting key!\r\n"); - ppl_logevent("Server refused public-key signature" - " despite accepting key!"); - } else if (s->type==AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET) { - /* quiet, so no ppl_printf */ - ppl_logevent("Server refused keyboard-interactive " - "authentication"); - } else if (s->type==AUTH_TYPE_GSSAPI) { - /* always quiet, so no ppl_printf */ - /* also, the code down in the GSSAPI block has - * already logged this in the Event Log */ - } else if (s->type == AUTH_TYPE_KEYBOARD_INTERACTIVE) { - ppl_logevent("Keyboard-interactive authentication " - "failed"); - ppl_printf("Access denied\r\n"); - } else { - assert(s->type == AUTH_TYPE_PASSWORD); - ppl_logevent("Password authentication failed"); - ppl_printf("Access denied\r\n"); - - if (s->change_username) { - /* XXX perhaps we should allow - * keyboard-interactive to do this too? */ - goto try_new_username; - } - } - } else { - ppl_printf("Further authentication required\r\n"); - ppl_logevent("Further authentication required"); - } - - /* - * Save the methods string for use in error messages. - */ - strbuf_clear(s->last_methods_string); - put_datapl(s->last_methods_string, methods); - - /* - * Scan it for method identifiers we know about. - */ - bool srv_pubkey = false, srv_passwd = false; - bool srv_keyb_inter = false; -#ifndef NO_GSSAPI - bool srv_gssapi = false, srv_gssapi_keyex_auth = false; -#endif - - for (ptrlen method; get_commasep_word(&methods, &method) ;) { - if (ptrlen_eq_string(method, "publickey")) - srv_pubkey = true; - else if (ptrlen_eq_string(method, "password")) - srv_passwd = true; - else if (ptrlen_eq_string(method, "keyboard-interactive")) - srv_keyb_inter = true; -#ifndef NO_GSSAPI - else if (ptrlen_eq_string(method, "gssapi-with-mic")) - srv_gssapi = true; - else if (ptrlen_eq_string(method, "gssapi-keyex")) - srv_gssapi_keyex_auth = true; -#endif - } - - /* - * And combine those flags with our own configuration - * and context to set the main can_foo variables. - */ - s->can_pubkey = srv_pubkey; - s->can_passwd = srv_passwd; - s->can_keyb_inter = s->try_ki_auth && srv_keyb_inter; -#ifndef NO_GSSAPI - s->can_gssapi = s->try_gssapi_auth && srv_gssapi && - s->shgss->libs->nlibraries > 0; - s->can_gssapi_keyex_auth = s->try_gssapi_kex_auth && - srv_gssapi_keyex_auth && - s->shgss->libs->nlibraries > 0 && s->shgss->ctx; -#endif - } - - s->ppl.bpp->pls->actx = SSH2_PKTCTX_NOAUTH; - -#ifndef NO_GSSAPI - if (s->can_gssapi_keyex_auth && !s->tried_gssapi_keyex_auth) { - - /* gssapi-keyex authentication */ - - s->type = AUTH_TYPE_GSSAPI; - s->tried_gssapi_keyex_auth = true; - s->ppl.bpp->pls->actx = SSH2_PKTCTX_GSSAPI; - - if (s->shgss->lib->gsslogmsg) - ppl_logevent("%s", s->shgss->lib->gsslogmsg); - - ppl_logevent("Trying gssapi-keyex..."); - s->pktout = ssh2_userauth_gss_packet(s, "gssapi-keyex"); - pq_push(s->ppl.out_pq, s->pktout); - s->shgss->lib->release_cred(s->shgss->lib, &s->shgss->ctx); - s->shgss->ctx = NULL; - - continue; - } else -#endif /* NO_GSSAPI */ - - if (s->can_pubkey && !s->done_agent && - s->agent_key_index < s->agent_key_limit) { - - /* - * Attempt public-key authentication using a key from Pageant. - */ - s->agent_keyalg = s->agent_keys[s->agent_key_index].algorithm; - s->signflags = 0; - if (ptrlen_eq_string(s->agent_keyalg, "ssh-rsa")) { - /* Try to upgrade ssh-rsa to one of the rsa-sha2-* family, - * if the server has announced support for them. */ - if (s->ppl.bpp->ext_info_rsa_sha512_ok) { - s->agent_keyalg = PTRLEN_LITERAL("rsa-sha2-512"); - s->signflags = SSH_AGENT_RSA_SHA2_512; - } else if (s->ppl.bpp->ext_info_rsa_sha256_ok) { - s->agent_keyalg = PTRLEN_LITERAL("rsa-sha2-256"); - s->signflags = SSH_AGENT_RSA_SHA2_256; - } - } - - s->ppl.bpp->pls->actx = SSH2_PKTCTX_PUBLICKEY; - - ppl_logevent("Trying Pageant key #%"SIZEu, s->agent_key_index); - - /* See if server will accept it */ - s->pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST); - put_stringz(s->pktout, s->username); - put_stringz(s->pktout, s->successor_layer->vt->name); - put_stringz(s->pktout, "publickey"); - /* method */ - put_bool(s->pktout, false); /* no signature included */ - put_stringpl(s->pktout, s->agent_keyalg); - put_stringpl(s->pktout, ptrlen_from_strbuf( - s->agent_keys[s->agent_key_index].blob)); - pq_push(s->ppl.out_pq, s->pktout); - s->type = AUTH_TYPE_PUBLICKEY_OFFER_QUIET; - - crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL); - if (pktin->type != SSH2_MSG_USERAUTH_PK_OK) { - - /* Offer of key refused, presumably via - * USERAUTH_FAILURE. Requeue for the next iteration. */ - pq_push_front(s->ppl.in_pq, pktin); - - } else { - strbuf *agentreq, *sigdata; - ptrlen comment = ptrlen_from_strbuf( - s->agent_keys[s->agent_key_index].comment); - - if (seat_verbose(s->ppl.seat)) - ppl_printf("Authenticating with public key " - "\"%.*s\" from agent\r\n", - PTRLEN_PRINTF(comment)); - - /* - * Server is willing to accept the key. - * Construct a SIGN_REQUEST. - */ - s->pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST); - put_stringz(s->pktout, s->username); - put_stringz(s->pktout, s->successor_layer->vt->name); - put_stringz(s->pktout, "publickey"); - /* method */ - put_bool(s->pktout, true); /* signature included */ - put_stringpl(s->pktout, s->agent_keyalg); - put_stringpl(s->pktout, ptrlen_from_strbuf( - s->agent_keys[s->agent_key_index].blob)); - - /* Ask agent for signature. */ - agentreq = strbuf_new_for_agent_query(); - put_byte(agentreq, SSH2_AGENTC_SIGN_REQUEST); - put_stringpl(agentreq, ptrlen_from_strbuf( - s->agent_keys[s->agent_key_index].blob)); - /* Now the data to be signed... */ - sigdata = strbuf_new(); - ssh2_userauth_add_session_id(s, sigdata); - put_data(sigdata, s->pktout->data + 5, - s->pktout->length - 5); - put_stringsb(agentreq, sigdata); - /* And finally the flags word. */ - put_uint32(agentreq, s->signflags); - ssh2_userauth_agent_query(s, agentreq); - strbuf_free(agentreq); - crWaitUntilV(!s->auth_agent_query); - - if (s->agent_response.ptr) { - ptrlen sigblob; - BinarySource src[1]; - BinarySource_BARE_INIT(src, s->agent_response.ptr, - s->agent_response.len); - get_uint32(src); /* skip length field */ - if (get_byte(src) == SSH2_AGENT_SIGN_RESPONSE && - (sigblob = get_string(src), !get_err(src))) { - ppl_logevent("Sending Pageant's response"); - ssh2_userauth_add_sigblob( - s, s->pktout, - ptrlen_from_strbuf( - s->agent_keys[s->agent_key_index].blob), - sigblob); - pq_push(s->ppl.out_pq, s->pktout); - s->type = AUTH_TYPE_PUBLICKEY; - } else { - ppl_logevent("Pageant refused signing request"); - ppl_printf("Pageant failed to " - "provide a signature\r\n"); - s->suppress_wait_for_response_packet = true; - ssh_free_pktout(s->pktout); - } - } else { - ppl_logevent("Pageant failed to respond to " - "signing request"); - ppl_printf("Pageant failed to " - "respond to signing request\r\n"); - s->suppress_wait_for_response_packet = true; - ssh_free_pktout(s->pktout); - } - } - - /* Do we have any keys left to try? */ - if (++s->agent_key_index >= s->agent_key_limit) - s->done_agent = true; - - } else if (s->can_pubkey && s->publickey_blob && - s->privatekey_available && !s->tried_pubkey_config) { - - ssh2_userkey *key; /* not live over crReturn */ - char *passphrase; /* not live over crReturn */ - - s->ppl.bpp->pls->actx = SSH2_PKTCTX_PUBLICKEY; - - s->tried_pubkey_config = true; - - /* - * Try the public key supplied in the configuration. - * - * First, try to upgrade its algorithm. - */ - if (!strcmp(s->publickey_algorithm, "ssh-rsa")) { - /* Try to upgrade ssh-rsa to one of the rsa-sha2-* family, - * if the server has announced support for them. */ - if (s->ppl.bpp->ext_info_rsa_sha512_ok) { - sfree(s->publickey_algorithm); - s->publickey_algorithm = dupstr("rsa-sha2-512"); - s->signflags = SSH_AGENT_RSA_SHA2_512; - } else if (s->ppl.bpp->ext_info_rsa_sha256_ok) { - sfree(s->publickey_algorithm); - s->publickey_algorithm = dupstr("rsa-sha2-256"); - s->signflags = SSH_AGENT_RSA_SHA2_256; - } - } - - /* - * Offer the public blob to see if the server is willing to - * accept it. - */ - s->pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST); - put_stringz(s->pktout, s->username); - put_stringz(s->pktout, s->successor_layer->vt->name); - put_stringz(s->pktout, "publickey"); /* method */ - put_bool(s->pktout, false); - /* no signature included */ - put_stringz(s->pktout, s->publickey_algorithm); - put_string(s->pktout, s->publickey_blob->s, - s->publickey_blob->len); - pq_push(s->ppl.out_pq, s->pktout); - ppl_logevent("Offered public key"); - - crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL); - if (pktin->type != SSH2_MSG_USERAUTH_PK_OK) { - /* Key refused. Give up. */ - pq_push_front(s->ppl.in_pq, pktin); - s->type = AUTH_TYPE_PUBLICKEY_OFFER_LOUD; - continue; /* process this new message */ - } - ppl_logevent("Offer of public key accepted"); - - /* - * Actually attempt a serious authentication using - * the key. - */ - if (seat_verbose(s->ppl.seat)) - ppl_printf("Authenticating with public key \"%s\"\r\n", - s->publickey_comment); - - key = NULL; - while (!key) { - const char *error; /* not live over crReturn */ - if (s->privatekey_encrypted) { - /* - * Get a passphrase from the user. - */ - s->cur_prompt = new_prompts(); - s->cur_prompt->to_server = false; - s->cur_prompt->from_server = false; - s->cur_prompt->name = dupstr("SSH key passphrase"); - add_prompt(s->cur_prompt, - dupprintf("Passphrase for key \"%s\": ", - s->publickey_comment), - false); - s->userpass_ret = seat_get_userpass_input( - s->ppl.seat, s->cur_prompt, NULL); - while (1) { - while (s->userpass_ret < 0 && - bufchain_size(s->ppl.user_input) > 0) - s->userpass_ret = seat_get_userpass_input( - s->ppl.seat, s->cur_prompt, - s->ppl.user_input); - - if (s->userpass_ret >= 0) - break; - - s->want_user_input = true; - crReturnV; - s->want_user_input = false; - } - if (!s->userpass_ret) { - /* Failed to get a passphrase. Terminate. */ - free_prompts(s->cur_prompt); - ssh_bpp_queue_disconnect( - s->ppl.bpp, "Unable to authenticate", - SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER); - ssh_user_close(s->ppl.ssh, "User aborted at " - "passphrase prompt"); - return; - } - passphrase = - prompt_get_result(s->cur_prompt->prompts[0]); - free_prompts(s->cur_prompt); - } else { - passphrase = NULL; /* no passphrase needed */ - } - - /* - * Try decrypting the key. - */ - key = ppk_load_f(s->keyfile, passphrase, &error); - if (passphrase) { - /* burn the evidence */ - smemclr(passphrase, strlen(passphrase)); - sfree(passphrase); - } - if (key == SSH2_WRONG_PASSPHRASE || key == NULL) { - if (passphrase && - (key == SSH2_WRONG_PASSPHRASE)) { - ppl_printf("Wrong passphrase\r\n"); - key = NULL; - /* and loop again */ - } else { - ppl_printf("Unable to load private key (%s)\r\n", - error); - key = NULL; - s->suppress_wait_for_response_packet = true; - break; /* try something else */ - } - } else { - /* FIXME: if we ever support variable signature - * flags, this is somewhere they'll need to be - * put */ - char *invalid = ssh_key_invalid(key->key, 0); - if (invalid) { - ppl_printf("Cannot use this private key (%s)\r\n", - invalid); - ssh_key_free(key->key); - sfree(key->comment); - sfree(key); - sfree(invalid); - key = NULL; - s->suppress_wait_for_response_packet = true; - break; /* try something else */ - } - } - } - - if (key) { - strbuf *pkblob, *sigdata, *sigblob; - - /* - * 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 = ssh_bpp_new_pktout( - s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST); - put_stringz(s->pktout, s->username); - put_stringz(s->pktout, s->successor_layer->vt->name); - put_stringz(s->pktout, "publickey"); /* method */ - put_bool(s->pktout, true); /* signature follows */ - put_stringz(s->pktout, s->publickey_algorithm); - pkblob = strbuf_new(); - ssh_key_public_blob(key->key, BinarySink_UPCAST(pkblob)); - put_string(s->pktout, pkblob->s, pkblob->len); - - /* - * The data to be signed is: - * - * string session-id - * - * followed by everything so far placed in the - * outgoing packet. - */ - sigdata = strbuf_new(); - ssh2_userauth_add_session_id(s, sigdata); - put_data(sigdata, s->pktout->data + 5, - s->pktout->length - 5); - sigblob = strbuf_new(); - ssh_key_sign(key->key, ptrlen_from_strbuf(sigdata), - s->signflags, BinarySink_UPCAST(sigblob)); - strbuf_free(sigdata); - ssh2_userauth_add_sigblob( - s, s->pktout, ptrlen_from_strbuf(pkblob), - ptrlen_from_strbuf(sigblob)); - strbuf_free(pkblob); - strbuf_free(sigblob); - - pq_push(s->ppl.out_pq, s->pktout); - ppl_logevent("Sent public key signature"); - s->type = AUTH_TYPE_PUBLICKEY; - ssh_key_free(key->key); - sfree(key->comment); - sfree(key); - } - -#ifndef NO_GSSAPI - } else if (s->can_gssapi && !s->tried_gssapi) { - - /* gssapi-with-mic authentication */ - - ptrlen data; - - s->type = AUTH_TYPE_GSSAPI; - s->tried_gssapi = true; - s->ppl.bpp->pls->actx = SSH2_PKTCTX_GSSAPI; - - if (s->shgss->lib->gsslogmsg) - ppl_logevent("%s", s->shgss->lib->gsslogmsg); - - /* Sending USERAUTH_REQUEST with "gssapi-with-mic" method */ - ppl_logevent("Trying gssapi-with-mic..."); - s->pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST); - put_stringz(s->pktout, s->username); - put_stringz(s->pktout, s->successor_layer->vt->name); - put_stringz(s->pktout, "gssapi-with-mic"); - ppl_logevent("Attempting GSSAPI authentication"); - - /* add mechanism info */ - s->shgss->lib->indicate_mech(s->shgss->lib, &s->gss_buf); - - /* number of GSSAPI mechanisms */ - put_uint32(s->pktout, 1); - - /* length of OID + 2 */ - put_uint32(s->pktout, s->gss_buf.length + 2); - put_byte(s->pktout, SSH2_GSS_OIDTYPE); - - /* length of OID */ - put_byte(s->pktout, s->gss_buf.length); - - put_data(s->pktout, s->gss_buf.value, s->gss_buf.length); - pq_push(s->ppl.out_pq, s->pktout); - crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL); - if (pktin->type != SSH2_MSG_USERAUTH_GSSAPI_RESPONSE) { - ppl_logevent("GSSAPI authentication request refused"); - pq_push_front(s->ppl.in_pq, pktin); - continue; - } - - /* check returned packet ... */ - - data = get_string(pktin); - s->gss_rcvtok.value = (char *)data.ptr; - s->gss_rcvtok.length = data.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) ) { - ppl_logevent("GSSAPI authentication - wrong response " - "from server"); - continue; - } - - /* Import server name if not cached from KEX */ - if (s->shgss->srv_name == GSS_C_NO_NAME) { - s->gss_stat = s->shgss->lib->import_name( - s->shgss->lib, s->fullhostname, &s->shgss->srv_name); - if (s->gss_stat != SSH_GSS_OK) { - if (s->gss_stat == SSH_GSS_BAD_HOST_NAME) - ppl_logevent("GSSAPI import name failed -" - " Bad service name"); - else - ppl_logevent("GSSAPI import name failed"); - continue; - } - } - - /* Allocate our gss_ctx */ - s->gss_stat = s->shgss->lib->acquire_cred( - s->shgss->lib, &s->shgss->ctx, NULL); - if (s->gss_stat != SSH_GSS_OK) { - ppl_logevent("GSSAPI authentication failed to get " - "credentials"); - /* The failure was on our side, so the server - * won't be sending a response packet indicating - * failure. Avoid waiting for it next time round - * the loop. */ - s->suppress_wait_for_response_packet = true; - continue; - } - - /* initial tokens are empty */ - SSH_GSS_CLEAR_BUF(&s->gss_rcvtok); - SSH_GSS_CLEAR_BUF(&s->gss_sndtok); - - /* now enter the loop */ - do { - /* - * When acquire_cred yields no useful expiration, go with - * the service ticket expiration. - */ - s->gss_stat = s->shgss->lib->init_sec_context - (s->shgss->lib, - &s->shgss->ctx, - s->shgss->srv_name, - s->gssapi_fwd, - &s->gss_rcvtok, - &s->gss_sndtok, - NULL, - NULL); - - if (s->gss_stat!=SSH_GSS_S_COMPLETE && - s->gss_stat!=SSH_GSS_S_CONTINUE_NEEDED) { - ppl_logevent("GSSAPI authentication initialisation " - "failed"); - - if (s->shgss->lib->display_status(s->shgss->lib, - s->shgss->ctx, &s->gss_buf) == SSH_GSS_OK) { - ppl_logevent("%s", (char *)s->gss_buf.value); - sfree(s->gss_buf.value); - } - - pq_push_front(s->ppl.in_pq, pktin); - break; - } - ppl_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 = - ssh_bpp_new_pktout( - s->ppl.bpp, SSH2_MSG_USERAUTH_GSSAPI_TOKEN); - put_string(s->pktout, - s->gss_sndtok.value, s->gss_sndtok.length); - pq_push(s->ppl.out_pq, s->pktout); - s->shgss->lib->free_tok(s->shgss->lib, &s->gss_sndtok); - } - - if (s->gss_stat == SSH_GSS_S_CONTINUE_NEEDED) { - crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL); - - if (pktin->type == SSH2_MSG_USERAUTH_GSSAPI_ERRTOK) { - /* - * Per RFC 4462 section 3.9, this packet - * type MUST immediately precede an - * ordinary USERAUTH_FAILURE. - * - * We currently don't know how to do - * anything with the GSSAPI error token - * contained in this packet, so we ignore - * it and just wait for the following - * FAILURE. - */ - crMaybeWaitUntilV( - (pktin = ssh2_userauth_pop(s)) != NULL); - if (pktin->type != SSH2_MSG_USERAUTH_FAILURE) { - ssh_proto_error( - s->ppl.ssh, "Received unexpected packet " - "after SSH_MSG_USERAUTH_GSSAPI_ERRTOK " - "(expected SSH_MSG_USERAUTH_FAILURE): " - "type %d (%s)", pktin->type, - ssh2_pkt_type(s->ppl.bpp->pls->kctx, - s->ppl.bpp->pls->actx, - pktin->type)); - return; - } - } - - if (pktin->type == SSH2_MSG_USERAUTH_FAILURE) { - ppl_logevent("GSSAPI authentication failed"); - s->gss_stat = SSH_GSS_FAILURE; - pq_push_front(s->ppl.in_pq, pktin); - break; - } else if (pktin->type != - SSH2_MSG_USERAUTH_GSSAPI_TOKEN) { - ppl_logevent("GSSAPI authentication -" - " bad server response"); - s->gss_stat = SSH_GSS_FAILURE; - break; - } - data = get_string(pktin); - s->gss_rcvtok.value = (char *)data.ptr; - s->gss_rcvtok.length = data.len; - } - } while (s-> gss_stat == SSH_GSS_S_CONTINUE_NEEDED); - - if (s->gss_stat != SSH_GSS_OK) { - s->shgss->lib->release_cred(s->shgss->lib, &s->shgss->ctx); - continue; - } - ppl_logevent("GSSAPI authentication loop finished OK"); - - /* Now send the MIC */ - - s->pktout = ssh2_userauth_gss_packet(s, "gssapi-with-mic"); - pq_push(s->ppl.out_pq, s->pktout); - - s->shgss->lib->release_cred(s->shgss->lib, &s->shgss->ctx); - continue; -#endif - } else if (s->can_keyb_inter && !s->kbd_inter_refused) { - - /* - * Keyboard-interactive authentication. - */ - - s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE; - - s->ppl.bpp->pls->actx = SSH2_PKTCTX_KBDINTER; - - s->pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST); - put_stringz(s->pktout, s->username); - put_stringz(s->pktout, s->successor_layer->vt->name); - put_stringz(s->pktout, "keyboard-interactive"); - /* method */ - put_stringz(s->pktout, ""); /* lang */ - put_stringz(s->pktout, ""); /* submethods */ - pq_push(s->ppl.out_pq, s->pktout); - - ppl_logevent("Attempting keyboard-interactive authentication"); - - if (!s->ki_scc_initialised) { - s->ki_scc = seat_stripctrl_new( - s->ppl.seat, NULL, SIC_KI_PROMPTS); - if (s->ki_scc) - stripctrl_enable_line_limiting(s->ki_scc); - s->ki_scc_initialised = true; - } - - crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL); - 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. */ - pq_push_front(s->ppl.in_pq, pktin); - s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET; - s->kbd_inter_refused = true; /* don't try it again */ - continue; - } - - s->ki_printed_header = false; - - /* - * Loop while the server continues to send INFO_REQUESTs. - */ - while (pktin->type == SSH2_MSG_USERAUTH_INFO_REQUEST) { - - ptrlen name, inst; - strbuf *sb; - - /* - * We've got a fresh USERAUTH_INFO_REQUEST. - * Get the preamble and start building a prompt. - */ - name = get_string(pktin); - inst = get_string(pktin); - get_string(pktin); /* skip language tag */ - s->cur_prompt = new_prompts(); - s->cur_prompt->to_server = true; - s->cur_prompt->from_server = true; - - /* - * Get any prompt(s) from the packet. - */ - s->num_prompts = get_uint32(pktin); - for (uint32_t i = 0; i < s->num_prompts; i++) { - ptrlen prompt = get_string(pktin); - bool echo = get_bool(pktin); - - if (get_err(pktin)) { - ssh_proto_error( - s->ppl.ssh, "Server sent truncated " - "SSH_MSG_USERAUTH_INFO_REQUEST packet"); - return; - } - - sb = strbuf_new(); - if (!prompt.len) { - put_datapl(sb, PTRLEN_LITERAL( - ": ")); - } else if (s->ki_scc) { - stripctrl_retarget( - s->ki_scc, BinarySink_UPCAST(sb)); - put_datapl(s->ki_scc, prompt); - stripctrl_retarget(s->ki_scc, NULL); - } else { - put_datapl(sb, prompt); - } - add_prompt(s->cur_prompt, strbuf_to_str(sb), echo); - } - - /* - * Make the header strings. This includes the - * 'name' (optional dialog-box title) and - * 'instruction' from the server. - * - * First, display our disambiguating header line - * if this is the first time round the loop - - * _unless_ the server has sent a completely empty - * k-i packet with no prompts _or_ text, which - * apparently some do. In that situation there's - * no need to alert the user that the following - * text is server- supplied, because, well, _what_ - * text? - * - * We also only do this if we got a stripctrl, - * because if we didn't, that suggests this is all - * being done via dialog boxes anyway. - */ - if (!s->ki_printed_header && s->ki_scc && - (s->num_prompts || name.len || inst.len)) { - ssh2_userauth_antispoof_msg( - s, "Keyboard-interactive authentication " - "prompts from server:"); - s->ki_printed_header = true; - seat_set_trust_status(s->ppl.seat, false); - } - - sb = strbuf_new(); - if (name.len) { - if (s->ki_scc) { - stripctrl_retarget(s->ki_scc, - BinarySink_UPCAST(sb)); - put_datapl(s->ki_scc, name); - stripctrl_retarget(s->ki_scc, NULL); - } else { - put_datapl(sb, name); - } - s->cur_prompt->name_reqd = true; - } else { - put_datapl(sb, PTRLEN_LITERAL( - "SSH server authentication")); - s->cur_prompt->name_reqd = false; - } - s->cur_prompt->name = strbuf_to_str(sb); - - sb = strbuf_new(); - if (inst.len) { - if (s->ki_scc) { - stripctrl_retarget(s->ki_scc, - BinarySink_UPCAST(sb)); - put_datapl(s->ki_scc, inst); - stripctrl_retarget(s->ki_scc, NULL); - } else { - put_datapl(sb, inst); - } - s->cur_prompt->instr_reqd = true; - } else { - s->cur_prompt->instr_reqd = false; - } - if (sb->len) - s->cur_prompt->instruction = strbuf_to_str(sb); - else - strbuf_free(sb); - - /* - * Our prompts_t is fully constructed now. Get the - * user's response(s). - */ - s->userpass_ret = seat_get_userpass_input( - s->ppl.seat, s->cur_prompt, NULL); - while (1) { - while (s->userpass_ret < 0 && - bufchain_size(s->ppl.user_input) > 0) - s->userpass_ret = seat_get_userpass_input( - s->ppl.seat, s->cur_prompt, s->ppl.user_input); - - if (s->userpass_ret >= 0) - break; - - s->want_user_input = true; - crReturnV; - s->want_user_input = false; - } - if (!s->userpass_ret) { - /* - * Failed to get responses. Terminate. - */ - free_prompts(s->cur_prompt); - ssh_bpp_queue_disconnect( - s->ppl.bpp, "Unable to authenticate", - SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER); - ssh_user_close(s->ppl.ssh, "User aborted during " - "keyboard-interactive authentication"); - return; - } - - /* - * Send the response(s) to the server. - */ - s->pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH2_MSG_USERAUTH_INFO_RESPONSE); - put_uint32(s->pktout, s->num_prompts); - for (uint32_t i = 0; i < s->num_prompts; i++) { - put_stringz(s->pktout, prompt_get_result_ref( - s->cur_prompt->prompts[i])); - } - s->pktout->minlen = 256; - pq_push(s->ppl.out_pq, s->pktout); - - /* - * Free the prompts structure from this iteration. - * If there's another, a new one will be allocated - * when we return to the top of this while loop. - */ - free_prompts(s->cur_prompt); - - /* - * Get the next packet in case it's another - * INFO_REQUEST. - */ - crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL); - - } - - /* - * Print our trailer line, if we printed a header. - */ - if (s->ki_printed_header) { - seat_set_trust_status(s->ppl.seat, true); - ssh2_userauth_antispoof_msg( - s, "End of keyboard-interactive prompts from server"); - } - - /* - * We should have SUCCESS or FAILURE now. - */ - pq_push_front(s->ppl.in_pq, pktin); - - } else if (s->can_passwd) { - - /* - * Plain old password authentication. - */ - bool changereq_first_time; /* not live over crReturn */ - - s->ppl.bpp->pls->actx = SSH2_PKTCTX_PASSWORD; - - s->cur_prompt = new_prompts(); - s->cur_prompt->to_server = true; - s->cur_prompt->from_server = false; - s->cur_prompt->name = dupstr("SSH password"); - add_prompt(s->cur_prompt, dupprintf("%s@%s's password: ", - s->username, s->hostname), - false); - - s->userpass_ret = seat_get_userpass_input( - s->ppl.seat, s->cur_prompt, NULL); - while (1) { - while (s->userpass_ret < 0 && - bufchain_size(s->ppl.user_input) > 0) - s->userpass_ret = seat_get_userpass_input( - s->ppl.seat, s->cur_prompt, s->ppl.user_input); - - if (s->userpass_ret >= 0) - break; - - s->want_user_input = true; - crReturnV; - s->want_user_input = false; - } - if (!s->userpass_ret) { - /* - * Failed to get responses. Terminate. - */ - free_prompts(s->cur_prompt); - ssh_bpp_queue_disconnect( - s->ppl.bpp, "Unable to authenticate", - SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER); - ssh_user_close(s->ppl.ssh, "User aborted during password " - "authentication"); - return; - } - /* - * Squirrel away the password. (We may need it later if - * asked to change it.) - */ - s->password = prompt_get_result(s->cur_prompt->prompts[0]); - 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 = ssh_bpp_new_pktout( - s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST); - put_stringz(s->pktout, s->username); - put_stringz(s->pktout, s->successor_layer->vt->name); - put_stringz(s->pktout, "password"); - put_bool(s->pktout, false); - put_stringz(s->pktout, s->password); - s->pktout->minlen = 256; - pq_push(s->ppl.out_pq, s->pktout); - ppl_logevent("Sent password"); - s->type = AUTH_TYPE_PASSWORD; - - /* - * Wait for next packet, in case it's a password change - * request. - */ - crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL); - 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. - */ - - bool got_new = false; /* not live over crReturn */ - ptrlen prompt; /* not live over crReturn */ - - { - const char *msg; - if (changereq_first_time) - msg = "Server requested password change"; - else - msg = "Server rejected new password"; - ppl_logevent("%s", msg); - ppl_printf("%s\r\n", msg); - } - - prompt = get_string(pktin); - - s->cur_prompt = new_prompts(); - s->cur_prompt->to_server = true; - s->cur_prompt->from_server = false; - s->cur_prompt->name = dupstr("New SSH password"); - s->cur_prompt->instruction = mkstr(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); - add_prompt(s->cur_prompt, dupstr("Enter new password: "), - false); - add_prompt(s->cur_prompt, dupstr("Confirm new password: "), - false); - - /* - * Loop until the user manages to enter the same - * password twice. - */ - while (!got_new) { - s->userpass_ret = seat_get_userpass_input( - s->ppl.seat, s->cur_prompt, NULL); - while (1) { - while (s->userpass_ret < 0 && - bufchain_size(s->ppl.user_input) > 0) - s->userpass_ret = seat_get_userpass_input( - s->ppl.seat, s->cur_prompt, - s->ppl.user_input); - - if (s->userpass_ret >= 0) - break; - - s->want_user_input = true; - crReturnV; - s->want_user_input = false; - } - if (!s->userpass_ret) { - /* - * Failed to get responses. Terminate. - */ - /* burn the evidence */ - free_prompts(s->cur_prompt); - smemclr(s->password, strlen(s->password)); - sfree(s->password); - ssh_bpp_queue_disconnect( - s->ppl.bpp, "Unable to authenticate", - SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER); - ssh_user_close(s->ppl.ssh, "User aborted during " - "password changing"); - return; - } - - /* - * 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->s[0]) { - smemclr(s->password, strlen(s->password)); - /* burn the evidence */ - sfree(s->password); - s->password = prompt_get_result( - s->cur_prompt->prompts[0]); - } - - /* - * Check the two new passwords match. - */ - got_new = !strcmp( - prompt_get_result_ref(s->cur_prompt->prompts[1]), - prompt_get_result_ref(s->cur_prompt->prompts[2])); - if (!got_new) - /* They don't. Silly user. */ - ppl_printf("Passwords do not match\r\n"); - - } - - /* - * Send the new password (along with the old one). - * (see above for padding rationale) - */ - s->pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST); - put_stringz(s->pktout, s->username); - put_stringz(s->pktout, s->successor_layer->vt->name); - put_stringz(s->pktout, "password"); - put_bool(s->pktout, true); - put_stringz(s->pktout, s->password); - put_stringz(s->pktout, prompt_get_result_ref( - s->cur_prompt->prompts[1])); - free_prompts(s->cur_prompt); - s->pktout->minlen = 256; - pq_push(s->ppl.out_pq, s->pktout); - ppl_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.) - */ - crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL); - 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. - */ - pq_push_front(s->ppl.in_pq, pktin); - - /* - * We don't need the old password any more, in any - * case. Burn the evidence. - */ - smemclr(s->password, strlen(s->password)); - sfree(s->password); - - } else { - ssh_bpp_queue_disconnect( - s->ppl.bpp, - "No supported authentication methods available", - SSH2_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE); - ssh_sw_abort(s->ppl.ssh, "No supported authentication methods " - "available (server sent: %s)", - s->last_methods_string->s); - return; - } - - } - try_new_username:; - } - - userauth_success: - /* - * We've just received USERAUTH_SUCCESS, and we haven't sent - * any packets since. Signal the transport layer to consider - * doing an immediate rekey, if it has any reason to want to. - */ - ssh2_transport_notify_auth_done(s->transport_layer); - - /* - * Finally, hand over to our successor layer, and return - * immediately without reaching the crFinishV: ssh_ppl_replace - * will have freed us, so crFinishV's zeroing-out of crState would - * be a use-after-free bug. - */ - { - PacketProtocolLayer *successor = s->successor_layer; - s->successor_layer = NULL; /* avoid freeing it ourself */ - ssh_ppl_replace(&s->ppl, successor); - return; /* we've just freed s, so avoid even touching s->crState */ - } - - crFinishV; -} - -static void ssh2_userauth_add_session_id( - struct ssh2_userauth_state *s, strbuf *sigdata) -{ - if (s->ppl.remote_bugs & BUG_SSH2_PK_SESSIONID) { - put_datapl(sigdata, s->session_id); - } else { - put_stringpl(sigdata, s->session_id); - } -} - -static void ssh2_userauth_agent_query( - struct ssh2_userauth_state *s, strbuf *req) -{ - void *response; - int response_len; - - sfree(s->agent_response_to_free); - s->agent_response_to_free = NULL; - - s->auth_agent_query = agent_query(req, &response, &response_len, - ssh2_userauth_agent_callback, s); - if (!s->auth_agent_query) - ssh2_userauth_agent_callback(s, response, response_len); -} - -static void ssh2_userauth_agent_callback(void *uav, void *reply, int replylen) -{ - struct ssh2_userauth_state *s = (struct ssh2_userauth_state *)uav; - - s->auth_agent_query = NULL; - s->agent_response_to_free = reply; - s->agent_response = make_ptrlen(reply, replylen); - - queue_idempotent_callback(&s->ppl.ic_process_queue); -} - -/* - * 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 just appends the sig blob unmodified as a string, except - * that it optionally breaks it open and fiddle with it to work around - * BUG_SSH2_RSA_PADDING. - */ -static void ssh2_userauth_add_sigblob( - struct ssh2_userauth_state *s, PktOut *pkt, ptrlen pkblob, ptrlen sigblob) -{ - BinarySource pk[1], sig[1]; - BinarySource_BARE_INIT_PL(pk, pkblob); - BinarySource_BARE_INIT_PL(sig, sigblob); - - /* 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 ((s->ppl.remote_bugs & BUG_SSH2_RSA_PADDING) && - ptrlen_eq_string(get_string(pk), "ssh-rsa") && - ptrlen_eq_string(get_string(sig), "ssh-rsa")) { - ptrlen mod_mp, sig_mp; - size_t sig_prefix_len; - - /* - * Find the modulus and signature integers. - */ - get_string(pk); /* skip over exponent */ - mod_mp = get_string(pk); /* remember modulus */ - sig_prefix_len = sig->pos; - sig_mp = get_string(sig); - if (get_err(pk) || get_err(sig)) - goto give_up; - - /* - * Find the byte length of the modulus, not counting leading - * zeroes. - */ - while (mod_mp.len > 0 && *(const char *)mod_mp.ptr == 0) { - mod_mp.len--; - mod_mp.ptr = (const char *)mod_mp.ptr + 1; - } - - /* debug("modulus length is %d\n", len); */ - /* debug("signature length is %d\n", siglen); */ - - if (mod_mp.len > sig_mp.len) { - strbuf *substr = strbuf_new(); - put_data(substr, sigblob.ptr, sig_prefix_len); - put_uint32(substr, mod_mp.len); - put_padding(substr, mod_mp.len - sig_mp.len, 0); - put_datapl(substr, sig_mp); - put_stringsb(pkt, substr); - return; - } - - /* Otherwise fall through and do it the easy way. We also come - * here as a fallback if we discover above that the key blob - * is misformatted in some way. */ - give_up:; - } - - put_stringpl(pkt, sigblob); -} - -#ifndef NO_GSSAPI -static PktOut *ssh2_userauth_gss_packet( - struct ssh2_userauth_state *s, const char *authtype) -{ - strbuf *sb; - PktOut *p; - Ssh_gss_buf buf; - Ssh_gss_buf mic; - - /* - * The mic is computed over the session id + intended - * USERAUTH_REQUEST packet. - */ - sb = strbuf_new(); - put_stringpl(sb, s->session_id); - put_byte(sb, SSH2_MSG_USERAUTH_REQUEST); - put_stringz(sb, s->username); - put_stringz(sb, s->successor_layer->vt->name); - put_stringz(sb, authtype); - - /* Compute the mic */ - buf.value = sb->s; - buf.length = sb->len; - s->shgss->lib->get_mic(s->shgss->lib, s->shgss->ctx, &buf, &mic); - strbuf_free(sb); - - /* Now we can build the real packet */ - if (strcmp(authtype, "gssapi-with-mic") == 0) { - p = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_USERAUTH_GSSAPI_MIC); - } else { - p = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST); - put_stringz(p, s->username); - put_stringz(p, s->successor_layer->vt->name); - put_stringz(p, authtype); - } - put_string(p, mic.value, mic.length); - - return p; -} -#endif - -static bool ssh2_userauth_get_specials( - PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx) -{ - /* No specials provided by this layer. */ - return false; -} - -static void ssh2_userauth_special_cmd(PacketProtocolLayer *ppl, - SessionSpecialCode code, int arg) -{ - /* No specials provided by this layer. */ -} - -static bool ssh2_userauth_want_user_input(PacketProtocolLayer *ppl) -{ - struct ssh2_userauth_state *s = - container_of(ppl, struct ssh2_userauth_state, ppl); - return s->want_user_input; -} - -static void ssh2_userauth_got_user_input(PacketProtocolLayer *ppl) -{ - struct ssh2_userauth_state *s = - container_of(ppl, struct ssh2_userauth_state, ppl); - if (s->want_user_input) - queue_idempotent_callback(&s->ppl.ic_process_queue); -} - -static void ssh2_userauth_reconfigure(PacketProtocolLayer *ppl, Conf *conf) -{ - struct ssh2_userauth_state *s = - container_of(ppl, struct ssh2_userauth_state, ppl); - ssh_ppl_reconfigure(s->successor_layer, conf); -} - -static void ssh2_userauth_antispoof_msg( - struct ssh2_userauth_state *s, const char *msg) -{ - strbuf *sb = strbuf_new(); - if (seat_set_trust_status(s->ppl.seat, true)) { - /* - * If the seat can directly indicate that this message is - * generated by the client, then we can just use the message - * unmodified as an unspoofable header. - */ - put_datapl(sb, ptrlen_from_asciz(msg)); - } else { - /* - * Otherwise, add enough padding around it that the server - * wouldn't be able to mimic it within our line-length - * constraint. - */ - strbuf_catf(sb, "-- %s ", msg); - while (sb->len < 78) - put_byte(sb, '-'); - } - put_datapl(sb, PTRLEN_LITERAL("\r\n")); - seat_stderr_pl(s->ppl.seat, ptrlen_from_strbuf(sb)); - strbuf_free(sb); -} diff --git a/sshbpp.h b/sshbpp.h deleted file mode 100644 index 87e7d7e7..00000000 --- a/sshbpp.h +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Abstraction of the binary packet protocols used in SSH. - */ - -#ifndef PUTTY_SSHBPP_H -#define PUTTY_SSHBPP_H - -typedef struct BinaryPacketProtocolVtable BinaryPacketProtocolVtable; - -struct BinaryPacketProtocolVtable { - void (*free)(BinaryPacketProtocol *); - void (*handle_input)(BinaryPacketProtocol *); - void (*handle_output)(BinaryPacketProtocol *); - PktOut *(*new_pktout)(int type); - void (*queue_disconnect)(BinaryPacketProtocol *, - const char *msg, int category); - uint32_t packet_size_limit; -}; - -struct BinaryPacketProtocol { - const struct BinaryPacketProtocolVtable *vt; - bufchain *in_raw, *out_raw; - bool input_eof; /* set this if in_raw will never be added to again */ - PktInQueue in_pq; - PktOutQueue out_pq; - PacketLogSettings *pls; - LogContext *logctx; - Ssh *ssh; - - /* ic_in_raw is filled in by the BPP (probably by calling - * ssh_bpp_common_setup). The BPP's owner triggers it when data is - * added to in_raw, and also when the BPP is newly created. */ - IdempotentCallback ic_in_raw; - - /* ic_out_pq is entirely internal to the BPP itself; it's used as - * the callback on out_pq. */ - IdempotentCallback ic_out_pq; - - /* Information that all packet layers sharing this BPP will - * potentially be interested in. */ - int remote_bugs; - bool ext_info_rsa_sha256_ok, ext_info_rsa_sha512_ok; - - /* Set this if remote connection closure should not generate an - * error message (either because it's not to be treated as an - * error at all, or because some other error message has already - * been emitted). */ - bool expect_close; -}; - -static inline void ssh_bpp_handle_input(BinaryPacketProtocol *bpp) -{ bpp->vt->handle_input(bpp); } -static inline void ssh_bpp_handle_output(BinaryPacketProtocol *bpp) -{ bpp->vt->handle_output(bpp); } -static inline PktOut *ssh_bpp_new_pktout(BinaryPacketProtocol *bpp, int type) -{ return bpp->vt->new_pktout(type); } -static inline void ssh_bpp_queue_disconnect(BinaryPacketProtocol *bpp, - const char *msg, int category) -{ bpp->vt->queue_disconnect(bpp, msg, category); } - -/* ssh_bpp_free is more than just a macro wrapper on the vtable; it - * does centralised parts of the freeing too. */ -void ssh_bpp_free(BinaryPacketProtocol *bpp); - -BinaryPacketProtocol *ssh1_bpp_new(LogContext *logctx); -void ssh1_bpp_new_cipher(BinaryPacketProtocol *bpp, - const ssh_cipheralg *cipher, - const void *session_key); -/* This is only called from outside the BPP in server mode; in client - * mode the BPP detects compression start time automatically by - * snooping message types */ -void ssh1_bpp_start_compression(BinaryPacketProtocol *bpp); - -/* Helper routine which does common BPP initialisation, e.g. setting - * up in_pq and out_pq, and initialising input_consumer. */ -void ssh_bpp_common_setup(BinaryPacketProtocol *); - -/* Common helper functions between the SSH-2 full and bare BPPs */ -void ssh2_bpp_queue_disconnect(BinaryPacketProtocol *bpp, - const char *msg, int category); -bool ssh2_bpp_check_unimplemented(BinaryPacketProtocol *bpp, PktIn *pktin); - -/* Convenience macro for BPPs to send formatted strings to the Event - * Log. Assumes a function parameter called 'bpp' is in scope. */ -#define bpp_logevent(...) ( \ - logevent_and_free((bpp)->logctx, dupprintf(__VA_ARGS__))) - -/* - * Structure that tracks how much data is sent and received, for - * purposes of triggering an SSH-2 rekey when either one gets over a - * configured limit. In each direction, the flag 'running' indicates - * that we haven't hit the limit yet, and 'remaining' tracks how much - * longer until we do. The function dts_consume() subtracts a given - * amount from the counter in a particular direction, and sets - * 'expired' if the limit has been hit. - * - * The limit is sticky: once 'running' has flipped to false, - * 'remaining' is no longer decremented, so it shouldn't dangerously - * wrap round. - */ -struct DataTransferStatsDirection { - bool running, expired; - unsigned long remaining; -}; -struct DataTransferStats { - struct DataTransferStatsDirection in, out; -}; -static inline void dts_consume(struct DataTransferStatsDirection *s, - unsigned long size_consumed) -{ - if (s->running) { - if (s->remaining <= size_consumed) { - s->running = false; - s->expired = true; - } else { - s->remaining -= size_consumed; - } - } -} -static inline void dts_reset(struct DataTransferStatsDirection *s, - unsigned long starting_size) -{ - s->expired = false; - s->remaining = starting_size; - /* - * The semantics of setting CONF_ssh_rekey_data to zero are to - * disable data-volume based rekeying completely. So if the - * starting size is actually zero, we don't set 'running' to true - * in the first place, which means we won't ever set the expired - * flag. - */ - s->running = (starting_size != 0); -} - -BinaryPacketProtocol *ssh2_bpp_new( - LogContext *logctx, struct DataTransferStats *stats, bool is_server); -void ssh2_bpp_new_outgoing_crypto( - BinaryPacketProtocol *bpp, - const ssh_cipheralg *cipher, const void *ckey, const void *iv, - const ssh2_macalg *mac, bool etm_mode, const void *mac_key, - const ssh_compression_alg *compression, bool delayed_compression); -void ssh2_bpp_new_incoming_crypto( - BinaryPacketProtocol *bpp, - const ssh_cipheralg *cipher, const void *ckey, const void *iv, - const ssh2_macalg *mac, bool etm_mode, const void *mac_key, - const ssh_compression_alg *compression, bool delayed_compression); - -/* - * A query method specific to the interface between ssh2transport and - * ssh2bpp. If true, it indicates that we're potentially in the - * race-condition-prone part of delayed compression setup and so - * asynchronous outgoing transport-layer packets are currently not - * being sent, which means in particular that it would be a bad idea - * to start a rekey because then we'd stop responding to anything - * _other_ than transport-layer packets and deadlock the protocol. - */ -bool ssh2_bpp_rekey_inadvisable(BinaryPacketProtocol *bpp); - -BinaryPacketProtocol *ssh2_bare_bpp_new(LogContext *logctx); - -/* - * The initial code to handle the SSH version exchange is also - * structured as an implementation of BinaryPacketProtocol, because - * that makes it easy to switch from that to the next BPP once it - * tells us which one we're using. - */ -struct ssh_version_receiver { - void (*got_ssh_version)(struct ssh_version_receiver *rcv, - int major_version); -}; -BinaryPacketProtocol *ssh_verstring_new( - Conf *conf, LogContext *logctx, bool bare_connection_mode, - const char *protoversion, struct ssh_version_receiver *rcv, - bool server_mode, const char *impl_name); -const char *ssh_verstring_get_remote(BinaryPacketProtocol *); -const char *ssh_verstring_get_local(BinaryPacketProtocol *); -int ssh_verstring_get_bugs(BinaryPacketProtocol *); - -#endif /* PUTTY_SSHBPP_H */ diff --git a/sshchan.h b/sshchan.h deleted file mode 100644 index 14dfccb5..00000000 --- a/sshchan.h +++ /dev/null @@ -1,316 +0,0 @@ -/* - * Abstraction of the various ways to handle the local end of an SSH - * connection-layer channel. - */ - -#ifndef PUTTY_SSHCHAN_H -#define PUTTY_SSHCHAN_H - -typedef struct ChannelVtable ChannelVtable; - -struct ChannelVtable { - void (*free)(Channel *); - - /* Called for channel types that were created at the same time as - * we sent an outgoing CHANNEL_OPEN, when the confirmation comes - * back from the server indicating that the channel has been - * opened, or the failure message indicating that it hasn't, - * respectively. In the latter case, this must _not_ free the - * Channel structure - the client will call the free method - * separately. But it might do logging or other local cleanup. */ - void (*open_confirmation)(Channel *); - void (*open_failed)(Channel *, const char *error_text); - - size_t (*send)(Channel *, bool is_stderr, const void *buf, size_t len); - void (*send_eof)(Channel *); - void (*set_input_wanted)(Channel *, bool wanted); - - char *(*log_close_msg)(Channel *); - - bool (*want_close)(Channel *, bool sent_local_eof, bool rcvd_remote_eof); - - /* A method for every channel request we know of. All of these - * return true for success or false for failure. */ - bool (*rcvd_exit_status)(Channel *, int status); - bool (*rcvd_exit_signal)( - Channel *chan, ptrlen signame, bool core_dumped, ptrlen msg); - bool (*rcvd_exit_signal_numeric)( - Channel *chan, int signum, bool core_dumped, ptrlen msg); - bool (*run_shell)(Channel *chan); - bool (*run_command)(Channel *chan, ptrlen command); - bool (*run_subsystem)(Channel *chan, ptrlen subsys); - bool (*enable_x11_forwarding)( - Channel *chan, bool oneshot, ptrlen authproto, ptrlen authdata, - unsigned screen_number); - bool (*enable_agent_forwarding)(Channel *chan); - bool (*allocate_pty)( - Channel *chan, ptrlen termtype, unsigned width, unsigned height, - unsigned pixwidth, unsigned pixheight, struct ssh_ttymodes modes); - bool (*set_env)(Channel *chan, ptrlen var, ptrlen value); - bool (*send_break)(Channel *chan, unsigned length); - bool (*send_signal)(Channel *chan, ptrlen signame); - bool (*change_window_size)( - Channel *chan, unsigned width, unsigned height, - unsigned pixwidth, unsigned pixheight); - - /* A method for signalling success/failure responses to channel - * requests initiated from the SshChannel vtable with want_reply - * true. */ - void (*request_response)(Channel *, bool success); -}; - -struct Channel { - const struct ChannelVtable *vt; - unsigned initial_fixed_window_size; -}; - -static inline void chan_free(Channel *ch) -{ ch->vt->free(ch); } -static inline void chan_open_confirmation(Channel *ch) -{ ch->vt->open_confirmation(ch); } -static inline void chan_open_failed(Channel *ch, const char *err) -{ ch->vt->open_failed(ch, err); } -static inline size_t chan_send( - Channel *ch, bool err, const void *buf, size_t len) -{ return ch->vt->send(ch, err, buf, len); } -static inline void chan_send_eof(Channel *ch) -{ ch->vt->send_eof(ch); } -static inline void chan_set_input_wanted(Channel *ch, bool wanted) -{ ch->vt->set_input_wanted(ch, wanted); } -static inline char *chan_log_close_msg(Channel *ch) -{ return ch->vt->log_close_msg(ch); } -static inline bool chan_want_close(Channel *ch, bool leof, bool reof) -{ return ch->vt->want_close(ch, leof, reof); } -static inline bool chan_rcvd_exit_status(Channel *ch, int status) -{ return ch->vt->rcvd_exit_status(ch, status); } -static inline bool chan_rcvd_exit_signal( - Channel *ch, ptrlen sig, bool core, ptrlen msg) -{ return ch->vt->rcvd_exit_signal(ch, sig, core, msg); } -static inline bool chan_rcvd_exit_signal_numeric( - Channel *ch, int sig, bool core, ptrlen msg) -{ return ch->vt->rcvd_exit_signal_numeric(ch, sig, core, msg); } -static inline bool chan_run_shell(Channel *ch) -{ return ch->vt->run_shell(ch); } -static inline bool chan_run_command(Channel *ch, ptrlen cmd) -{ return ch->vt->run_command(ch, cmd); } -static inline bool chan_run_subsystem(Channel *ch, ptrlen subsys) -{ return ch->vt->run_subsystem(ch, subsys); } -static inline bool chan_enable_x11_forwarding( - Channel *ch, bool oneshot, ptrlen ap, ptrlen ad, unsigned scr) -{ return ch->vt->enable_x11_forwarding(ch, oneshot, ap, ad, scr); } -static inline bool chan_enable_agent_forwarding(Channel *ch) -{ return ch->vt->enable_agent_forwarding(ch); } -static inline bool chan_allocate_pty( - Channel *ch, ptrlen termtype, unsigned w, unsigned h, - unsigned pw, unsigned ph, struct ssh_ttymodes modes) -{ return ch->vt->allocate_pty(ch, termtype, w, h, pw, ph, modes); } -static inline bool chan_set_env(Channel *ch, ptrlen var, ptrlen value) -{ return ch->vt->set_env(ch, var, value); } -static inline bool chan_send_break(Channel *ch, unsigned length) -{ return ch->vt->send_break(ch, length); } -static inline bool chan_send_signal(Channel *ch, ptrlen signame) -{ return ch->vt->send_signal(ch, signame); } -static inline bool chan_change_window_size( - Channel *ch, unsigned w, unsigned h, unsigned pw, unsigned ph) -{ return ch->vt->change_window_size(ch, w, h, pw, ph); } -static inline void chan_request_response(Channel *ch, bool success) -{ ch->vt->request_response(ch, success); } - -/* - * Reusable methods you can put in vtables to give default handling of - * some of those functions. - */ - -/* open_confirmation / open_failed for any channel it doesn't apply to */ -void chan_remotely_opened_confirmation(Channel *chan); -void chan_remotely_opened_failure(Channel *chan, const char *errtext); - -/* want_close for any channel that wants the default behaviour of not - * closing until both directions have had an EOF */ -bool chan_default_want_close(Channel *, bool, bool); - -/* default implementations that refuse all the channel requests */ -bool chan_no_exit_status(Channel *, int); -bool chan_no_exit_signal(Channel *, ptrlen, bool, ptrlen); -bool chan_no_exit_signal_numeric(Channel *, int, bool, ptrlen); -bool chan_no_run_shell(Channel *chan); -bool chan_no_run_command(Channel *chan, ptrlen command); -bool chan_no_run_subsystem(Channel *chan, ptrlen subsys); -bool chan_no_enable_x11_forwarding( - Channel *chan, bool oneshot, ptrlen authproto, ptrlen authdata, - unsigned screen_number); -bool chan_no_enable_agent_forwarding(Channel *chan); -bool chan_no_allocate_pty( - Channel *chan, ptrlen termtype, unsigned width, unsigned height, - unsigned pixwidth, unsigned pixheight, struct ssh_ttymodes modes); -bool chan_no_set_env(Channel *chan, ptrlen var, ptrlen value); -bool chan_no_send_break(Channel *chan, unsigned length); -bool chan_no_send_signal(Channel *chan, ptrlen signame); -bool chan_no_change_window_size( - Channel *chan, unsigned width, unsigned height, - unsigned pixwidth, unsigned pixheight); - -/* default implementation that never expects to receive a response */ -void chan_no_request_response(Channel *, bool); - -/* - * Constructor for a trivial do-nothing implementation of - * ChannelVtable. Used for 'zombie' channels, i.e. channels whose - * proper local source of data has been shut down or otherwise stopped - * existing, but the SSH side is still there and needs some kind of a - * Channel implementation to talk to. In particular, the want_close - * method for this channel always returns 'yes, please close this - * channel asap', regardless of whether local and/or remote EOF have - * been sent - indeed, even if _neither_ has. - */ -Channel *zombiechan_new(void); - -/* ---------------------------------------------------------------------- - * This structure is owned by an SSH connection layer, and identifies - * the connection layer's end of the channel, for the Channel - * implementation to talk back to. - */ - -typedef struct SshChannelVtable SshChannelVtable; - -struct SshChannelVtable { - size_t (*write)(SshChannel *c, bool is_stderr, const void *, size_t); - void (*write_eof)(SshChannel *c); - void (*initiate_close)(SshChannel *c, const char *err); - void (*unthrottle)(SshChannel *c, size_t bufsize); - Conf *(*get_conf)(SshChannel *c); - void (*window_override_removed)(SshChannel *c); - void (*x11_sharing_handover)(SshChannel *c, - ssh_sharing_connstate *share_cs, - share_channel *share_chan, - const char *peer_addr, int peer_port, - int endian, int protomajor, int protominor, - const void *initial_data, int initial_len); - - /* - * All the outgoing channel requests we support. Each one has a - * want_reply flag, which will cause a callback to - * chan_request_response when the result is available. - * - * The ones that return 'bool' use it to indicate that the SSH - * protocol in use doesn't support this request at all. - * - * (It's also intentional that not all of them have a want_reply - * flag: the ones that don't are because SSH-1 has no method for - * signalling success or failure of that request, or because we - * wouldn't do anything usefully different with the reply in any - * case.) - */ - void (*send_exit_status)(SshChannel *c, int status); - void (*send_exit_signal)( - SshChannel *c, ptrlen signame, bool core_dumped, ptrlen msg); - void (*send_exit_signal_numeric)( - SshChannel *c, int signum, bool core_dumped, ptrlen msg); - void (*request_x11_forwarding)( - SshChannel *c, bool want_reply, const char *authproto, - const char *authdata, int screen_number, bool oneshot); - void (*request_agent_forwarding)( - SshChannel *c, bool want_reply); - void (*request_pty)( - SshChannel *c, bool want_reply, Conf *conf, int w, int h); - bool (*send_env_var)( - SshChannel *c, bool want_reply, const char *var, const char *value); - void (*start_shell)( - SshChannel *c, bool want_reply); - void (*start_command)( - SshChannel *c, bool want_reply, const char *command); - bool (*start_subsystem)( - SshChannel *c, bool want_reply, const char *subsystem); - bool (*send_serial_break)( - SshChannel *c, bool want_reply, int length); /* length=0 for default */ - bool (*send_signal)( - SshChannel *c, bool want_reply, const char *signame); - void (*send_terminal_size_change)( - SshChannel *c, int w, int h); - void (*hint_channel_is_simple)(SshChannel *c); -}; - -struct SshChannel { - const struct SshChannelVtable *vt; - ConnectionLayer *cl; -}; - -static inline size_t sshfwd_write_ext( - SshChannel *c, bool is_stderr, const void *data, size_t len) -{ return c->vt->write(c, is_stderr, data, len); } -static inline size_t sshfwd_write(SshChannel *c, const void *data, size_t len) -{ return sshfwd_write_ext(c, false, data, len); } -static inline void sshfwd_write_eof(SshChannel *c) -{ c->vt->write_eof(c); } -static inline void sshfwd_initiate_close(SshChannel *c, const char *err) -{ c->vt->initiate_close(c, err); } -static inline void sshfwd_unthrottle(SshChannel *c, size_t bufsize) -{ c->vt->unthrottle(c, bufsize); } -static inline Conf *sshfwd_get_conf(SshChannel *c) -{ return c->vt->get_conf(c); } -static inline void sshfwd_window_override_removed(SshChannel *c) -{ c->vt->window_override_removed(c); } -static inline void sshfwd_x11_sharing_handover( - SshChannel *c, ssh_sharing_connstate *cs, share_channel *sch, - const char *addr, int port, int endian, int maj, int min, - const void *idata, int ilen) -{ c->vt->x11_sharing_handover(c, cs, sch, addr, port, endian, - maj, min, idata, ilen); } -static inline void sshfwd_send_exit_status(SshChannel *c, int status) -{ c->vt->send_exit_status(c, status); } -static inline void sshfwd_send_exit_signal( - SshChannel *c, ptrlen signame, bool core_dumped, ptrlen msg) -{ c->vt->send_exit_signal(c, signame, core_dumped, msg); } -static inline void sshfwd_send_exit_signal_numeric( - SshChannel *c, int signum, bool core_dumped, ptrlen msg) -{ c->vt->send_exit_signal_numeric(c, signum, core_dumped, msg); } -static inline void sshfwd_request_x11_forwarding( - SshChannel *c, bool want_reply, const char *proto, - const char *data, int scr, bool once) -{ c->vt->request_x11_forwarding(c, want_reply, proto, data, scr, once); } -static inline void sshfwd_request_agent_forwarding( - SshChannel *c, bool want_reply) -{ c->vt->request_agent_forwarding(c, want_reply); } -static inline void sshfwd_request_pty( - SshChannel *c, bool want_reply, Conf *conf, int w, int h) -{ c->vt->request_pty(c, want_reply, conf, w, h); } -static inline bool sshfwd_send_env_var( - SshChannel *c, bool want_reply, const char *var, const char *value) -{ return c->vt->send_env_var(c, want_reply, var, value); } -static inline void sshfwd_start_shell( - SshChannel *c, bool want_reply) -{ c->vt->start_shell(c, want_reply); } -static inline void sshfwd_start_command( - SshChannel *c, bool want_reply, const char *command) -{ c->vt->start_command(c, want_reply, command); } -static inline bool sshfwd_start_subsystem( - SshChannel *c, bool want_reply, const char *subsystem) -{ return c->vt->start_subsystem(c, want_reply, subsystem); } -static inline bool sshfwd_send_serial_break( - SshChannel *c, bool want_reply, int length) -{ return c->vt->send_serial_break(c, want_reply, length); } -static inline bool sshfwd_send_signal( - SshChannel *c, bool want_reply, const char *signame) -{ return c->vt->send_signal(c, want_reply, signame); } -static inline void sshfwd_send_terminal_size_change( - SshChannel *c, int w, int h) -{ c->vt->send_terminal_size_change(c, w, h); } -static inline void sshfwd_hint_channel_is_simple(SshChannel *c) -{ c->vt->hint_channel_is_simple(c); } - -/* ---------------------------------------------------------------------- - * The 'main' or primary channel of the SSH connection is special, - * because it's the one that's connected directly to parts of the - * frontend such as the terminal and the specials menu. So it exposes - * a richer API. - */ - -mainchan *mainchan_new( - PacketProtocolLayer *ppl, ConnectionLayer *cl, Conf *conf, - int term_width, int term_height, bool is_simple, SshChannel **sc_out); -void mainchan_get_specials( - mainchan *mc, add_special_fn_t add_special, void *ctx); -void mainchan_special_cmd(mainchan *mc, SessionSpecialCode code, int arg); -void mainchan_terminal_size(mainchan *mc, int width, int height); - -#endif /* PUTTY_SSHCHAN_H */ diff --git a/sshcommon.c b/sshcommon.c deleted file mode 100644 index 5485e33a..00000000 --- a/sshcommon.c +++ /dev/null @@ -1,946 +0,0 @@ -/* - * Supporting routines used in common by all the various components of - * the SSH system. - */ - -#include -#include - -#include "putty.h" -#include "mpint.h" -#include "ssh.h" -#include "sshbpp.h" -#include "sshppl.h" -#include "sshchan.h" - -/* ---------------------------------------------------------------------- - * Implementation of PacketQueue. - */ - -static void pq_ensure_unlinked(PacketQueueNode *node) -{ - if (node->on_free_queue) { - node->next->prev = node->prev; - node->prev->next = node->next; - } else { - assert(!node->next); - assert(!node->prev); - } -} - -void pq_base_push(PacketQueueBase *pqb, PacketQueueNode *node) -{ - pq_ensure_unlinked(node); - node->next = &pqb->end; - node->prev = pqb->end.prev; - node->next->prev = node; - node->prev->next = node; - pqb->total_size += node->formal_size; - - if (pqb->ic) - queue_idempotent_callback(pqb->ic); -} - -void pq_base_push_front(PacketQueueBase *pqb, PacketQueueNode *node) -{ - pq_ensure_unlinked(node); - node->prev = &pqb->end; - node->next = pqb->end.next; - node->next->prev = node; - node->prev->next = node; - pqb->total_size += node->formal_size; - - if (pqb->ic) - queue_idempotent_callback(pqb->ic); -} - -static PacketQueueNode pktin_freeq_head = { - &pktin_freeq_head, &pktin_freeq_head, true -}; - -static void pktin_free_queue_callback(void *vctx) -{ - while (pktin_freeq_head.next != &pktin_freeq_head) { - PacketQueueNode *node = pktin_freeq_head.next; - PktIn *pktin = container_of(node, PktIn, qnode); - pktin_freeq_head.next = node->next; - sfree(pktin); - } - - pktin_freeq_head.prev = &pktin_freeq_head; -} - -static IdempotentCallback ic_pktin_free = { - pktin_free_queue_callback, NULL, false -}; - -static inline void pq_unlink_common(PacketQueueBase *pqb, - PacketQueueNode *node) -{ - node->next->prev = node->prev; - node->prev->next = node->next; - - /* Check total_size doesn't drift out of sync downwards, by - * ensuring it doesn't underflow when we do this subtraction */ - assert(pqb->total_size >= node->formal_size); - pqb->total_size -= node->formal_size; - - /* Check total_size doesn't drift out of sync upwards, by checking - * that it's returned to exactly zero whenever a queue is - * emptied */ - assert(pqb->end.next != &pqb->end || pqb->total_size == 0); -} - -static PktIn *pq_in_after(PacketQueueBase *pqb, - PacketQueueNode *prev, bool pop) -{ - PacketQueueNode *node = prev->next; - if (node == &pqb->end) - return NULL; - - if (pop) { - pq_unlink_common(pqb, node); - - node->prev = pktin_freeq_head.prev; - node->next = &pktin_freeq_head; - node->next->prev = node; - node->prev->next = node; - node->on_free_queue = true; - - queue_idempotent_callback(&ic_pktin_free); - } - - return container_of(node, PktIn, qnode); -} - -static PktOut *pq_out_after(PacketQueueBase *pqb, - PacketQueueNode *prev, bool pop) -{ - PacketQueueNode *node = prev->next; - if (node == &pqb->end) - return NULL; - - if (pop) { - pq_unlink_common(pqb, node); - - node->prev = node->next = NULL; - } - - return container_of(node, PktOut, qnode); -} - -void pq_in_init(PktInQueue *pq) -{ - pq->pqb.ic = NULL; - pq->pqb.end.next = pq->pqb.end.prev = &pq->pqb.end; - pq->after = pq_in_after; - pq->pqb.total_size = 0; -} - -void pq_out_init(PktOutQueue *pq) -{ - pq->pqb.ic = NULL; - pq->pqb.end.next = pq->pqb.end.prev = &pq->pqb.end; - pq->after = pq_out_after; - pq->pqb.total_size = 0; -} - -void pq_in_clear(PktInQueue *pq) -{ - PktIn *pkt; - pq->pqb.ic = NULL; - while ((pkt = pq_pop(pq)) != NULL) { - /* No need to actually free these packets: pq_pop on a - * PktInQueue will automatically move them to the free - * queue. */ - } -} - -void pq_out_clear(PktOutQueue *pq) -{ - PktOut *pkt; - pq->pqb.ic = NULL; - while ((pkt = pq_pop(pq)) != NULL) - ssh_free_pktout(pkt); -} - -/* - * Concatenate the contents of the two queues q1 and q2, and leave the - * result in qdest. qdest must be either empty, or one of the input - * queues. - */ -void pq_base_concatenate(PacketQueueBase *qdest, - PacketQueueBase *q1, PacketQueueBase *q2) -{ - struct PacketQueueNode *head1, *tail1, *head2, *tail2; - - size_t total_size = q1->total_size + q2->total_size; - - /* - * Extract the contents from both input queues, and empty them. - */ - - head1 = (q1->end.next == &q1->end ? NULL : q1->end.next); - tail1 = (q1->end.prev == &q1->end ? NULL : q1->end.prev); - head2 = (q2->end.next == &q2->end ? NULL : q2->end.next); - tail2 = (q2->end.prev == &q2->end ? NULL : q2->end.prev); - - q1->end.next = q1->end.prev = &q1->end; - q2->end.next = q2->end.prev = &q2->end; - q1->total_size = q2->total_size = 0; - - /* - * Link the two lists together, handling the case where one or - * both is empty. - */ - - if (tail1) - tail1->next = head2; - else - head1 = head2; - - if (head2) - head2->prev = tail1; - else - tail2 = tail1; - - /* - * Check the destination queue is currently empty. (If it was one - * of the input queues, then it will be, because we emptied both - * of those just a moment ago.) - */ - - assert(qdest->end.next == &qdest->end); - assert(qdest->end.prev == &qdest->end); - - /* - * If our concatenated list has anything in it, then put it in - * dest. - */ - - if (!head1) { - assert(!tail2); - } else { - assert(tail2); - qdest->end.next = head1; - qdest->end.prev = tail2; - head1->prev = &qdest->end; - tail2->next = &qdest->end; - - if (qdest->ic) - queue_idempotent_callback(qdest->ic); - } - - qdest->total_size = total_size; -} - -/* ---------------------------------------------------------------------- - * Low-level functions for the packet structures themselves. - */ - -static void ssh_pkt_BinarySink_write(BinarySink *bs, - const void *data, size_t len); -PktOut *ssh_new_packet(void) -{ - PktOut *pkt = snew(PktOut); - - BinarySink_INIT(pkt, ssh_pkt_BinarySink_write); - pkt->data = NULL; - pkt->length = 0; - pkt->maxlen = 0; - pkt->downstream_id = 0; - pkt->additional_log_text = NULL; - pkt->qnode.next = pkt->qnode.prev = NULL; - pkt->qnode.on_free_queue = false; - - return pkt; -} - -static void ssh_pkt_adddata(PktOut *pkt, const void *data, int len) -{ - sgrowarrayn_nm(pkt->data, pkt->maxlen, pkt->length, len); - memcpy(pkt->data + pkt->length, data, len); - pkt->length += len; - pkt->qnode.formal_size = pkt->length; -} - -static void ssh_pkt_BinarySink_write(BinarySink *bs, - const void *data, size_t len) -{ - PktOut *pkt = BinarySink_DOWNCAST(bs, PktOut); - ssh_pkt_adddata(pkt, data, len); -} - -void ssh_free_pktout(PktOut *pkt) -{ - sfree(pkt->data); - sfree(pkt); -} - -/* ---------------------------------------------------------------------- - * Implement zombiechan_new() and its trivial vtable. - */ - -static void zombiechan_free(Channel *chan); -static size_t zombiechan_send( - Channel *chan, bool is_stderr, const void *, size_t); -static void zombiechan_set_input_wanted(Channel *chan, bool wanted); -static void zombiechan_do_nothing(Channel *chan); -static void zombiechan_open_failure(Channel *chan, const char *); -static bool zombiechan_want_close(Channel *chan, bool sent_eof, bool rcvd_eof); -static char *zombiechan_log_close_msg(Channel *chan) { return NULL; } - -static const ChannelVtable zombiechan_channelvt = { - .free = zombiechan_free, - .open_confirmation = zombiechan_do_nothing, - .open_failed = zombiechan_open_failure, - .send = zombiechan_send, - .send_eof = zombiechan_do_nothing, - .set_input_wanted = zombiechan_set_input_wanted, - .log_close_msg = zombiechan_log_close_msg, - .want_close = zombiechan_want_close, - .rcvd_exit_status = chan_no_exit_status, - .rcvd_exit_signal = chan_no_exit_signal, - .rcvd_exit_signal_numeric = chan_no_exit_signal_numeric, - .run_shell = chan_no_run_shell, - .run_command = chan_no_run_command, - .run_subsystem = chan_no_run_subsystem, - .enable_x11_forwarding = chan_no_enable_x11_forwarding, - .enable_agent_forwarding = chan_no_enable_agent_forwarding, - .allocate_pty = chan_no_allocate_pty, - .set_env = chan_no_set_env, - .send_break = chan_no_send_break, - .send_signal = chan_no_send_signal, - .change_window_size = chan_no_change_window_size, - .request_response = chan_no_request_response, -}; - -Channel *zombiechan_new(void) -{ - Channel *chan = snew(Channel); - chan->vt = &zombiechan_channelvt; - chan->initial_fixed_window_size = 0; - return chan; -} - -static void zombiechan_free(Channel *chan) -{ - assert(chan->vt == &zombiechan_channelvt); - sfree(chan); -} - -static void zombiechan_do_nothing(Channel *chan) -{ - assert(chan->vt == &zombiechan_channelvt); -} - -static void zombiechan_open_failure(Channel *chan, const char *errtext) -{ - assert(chan->vt == &zombiechan_channelvt); -} - -static size_t zombiechan_send(Channel *chan, bool is_stderr, - const void *data, size_t length) -{ - assert(chan->vt == &zombiechan_channelvt); - return 0; -} - -static void zombiechan_set_input_wanted(Channel *chan, bool enable) -{ - assert(chan->vt == &zombiechan_channelvt); -} - -static bool zombiechan_want_close(Channel *chan, bool sent_eof, bool rcvd_eof) -{ - return true; -} - -/* ---------------------------------------------------------------------- - * Common routines for handling SSH tty modes. - */ - -static unsigned real_ttymode_opcode(unsigned our_opcode, int ssh_version) -{ - switch (our_opcode) { - case TTYMODE_ISPEED: - return ssh_version == 1 ? TTYMODE_ISPEED_SSH1 : TTYMODE_ISPEED_SSH2; - case TTYMODE_OSPEED: - return ssh_version == 1 ? TTYMODE_OSPEED_SSH1 : TTYMODE_OSPEED_SSH2; - default: - return our_opcode; - } -} - -static unsigned our_ttymode_opcode(unsigned real_opcode, int ssh_version) -{ - if (ssh_version == 1) { - switch (real_opcode) { - case TTYMODE_ISPEED_SSH1: - return TTYMODE_ISPEED; - case TTYMODE_OSPEED_SSH1: - return TTYMODE_OSPEED; - default: - return real_opcode; - } - } else { - switch (real_opcode) { - case TTYMODE_ISPEED_SSH2: - return TTYMODE_ISPEED; - case TTYMODE_OSPEED_SSH2: - return TTYMODE_OSPEED; - default: - return real_opcode; - } - } -} - -struct ssh_ttymodes get_ttymodes_from_conf(Seat *seat, Conf *conf) -{ - struct ssh_ttymodes modes; - size_t i; - - static const struct mode_name_type { - const char *mode; - int opcode; - enum { TYPE_CHAR, TYPE_BOOL } type; - } modes_names_types[] = { - #define TTYMODE_CHAR(name, val, index) { #name, val, TYPE_CHAR }, - #define TTYMODE_FLAG(name, val, field, mask) { #name, val, TYPE_BOOL }, - #include "sshttymodes.h" - #undef TTYMODE_CHAR - #undef TTYMODE_FLAG - }; - - memset(&modes, 0, sizeof(modes)); - - for (i = 0; i < lenof(modes_names_types); i++) { - const struct mode_name_type *mode = &modes_names_types[i]; - const char *sval = conf_get_str_str(conf, CONF_ttymodes, mode->mode); - char *to_free = NULL; - - if (!sval) - sval = "N"; /* just in case */ - - /* - * sval[0] can be - * - 'V', indicating that an explicit value follows it; - * - 'A', indicating that we should pass the value through from - * the local environment via get_ttymode; or - * - 'N', indicating that we should explicitly not send this - * mode. - */ - if (sval[0] == 'A') { - sval = to_free = seat_get_ttymode(seat, mode->mode); - } else if (sval[0] == 'V') { - sval++; /* skip the 'V' */ - } else { - /* else 'N', or something from the future we don't understand */ - continue; - } - - if (sval) { - /* - * Parse the string representation of the tty mode - * into the integer value it will take on the wire. - */ - unsigned ival = 0; - - switch (mode->type) { - case TYPE_CHAR: - if (*sval) { - char *next = NULL; - /* We know ctrlparse won't write to the string, so - * casting away const is ugly but allowable. */ - ival = ctrlparse((char *)sval, &next); - if (!next) - ival = sval[0]; - } else { - ival = 255; /* special value meaning "don't set" */ - } - break; - case TYPE_BOOL: - if (stricmp(sval, "yes") == 0 || - stricmp(sval, "on") == 0 || - stricmp(sval, "true") == 0 || - stricmp(sval, "+") == 0) - ival = 1; /* true */ - else if (stricmp(sval, "no") == 0 || - stricmp(sval, "off") == 0 || - stricmp(sval, "false") == 0 || - stricmp(sval, "-") == 0) - ival = 0; /* false */ - else - ival = (atoi(sval) != 0); - break; - default: - unreachable("Bad mode->type"); - } - - modes.have_mode[mode->opcode] = true; - modes.mode_val[mode->opcode] = ival; - } - - sfree(to_free); - } - - { - unsigned ospeed, ispeed; - - /* Unpick the terminal-speed config string. */ - ospeed = ispeed = 38400; /* last-resort defaults */ - sscanf(conf_get_str(conf, CONF_termspeed), "%u,%u", &ospeed, &ispeed); - /* Currently we unconditionally set these */ - modes.have_mode[TTYMODE_ISPEED] = true; - modes.mode_val[TTYMODE_ISPEED] = ispeed; - modes.have_mode[TTYMODE_OSPEED] = true; - modes.mode_val[TTYMODE_OSPEED] = ospeed; - } - - return modes; -} - -struct ssh_ttymodes read_ttymodes_from_packet( - BinarySource *bs, int ssh_version) -{ - struct ssh_ttymodes modes; - memset(&modes, 0, sizeof(modes)); - - while (1) { - unsigned real_opcode, our_opcode; - - real_opcode = get_byte(bs); - if (real_opcode == TTYMODE_END_OF_LIST) - break; - if (real_opcode >= 160) { - /* - * RFC 4254 (and the SSH 1.5 spec): "Opcodes 160 to 255 - * are not yet defined, and cause parsing to stop (they - * should only be used after any other data)." - * - * My interpretation of this is that if one of these - * opcodes appears, it's not a parse _error_, but it is - * something that we don't know how to parse even well - * enough to step over it to find the next opcode, so we - * stop parsing now and assume that the rest of the string - * is composed entirely of things we don't understand and - * (as usual for unsupported terminal modes) silently - * ignore. - */ - return modes; - } - - our_opcode = our_ttymode_opcode(real_opcode, ssh_version); - assert(our_opcode < TTYMODE_LIMIT); - modes.have_mode[our_opcode] = true; - - if (ssh_version == 1 && real_opcode >= 1 && real_opcode <= 127) - modes.mode_val[our_opcode] = get_byte(bs); - else - modes.mode_val[our_opcode] = get_uint32(bs); - } - - return modes; -} - -void write_ttymodes_to_packet(BinarySink *bs, int ssh_version, - struct ssh_ttymodes modes) -{ - unsigned i; - - for (i = 0; i < TTYMODE_LIMIT; i++) { - if (modes.have_mode[i]) { - unsigned val = modes.mode_val[i]; - unsigned opcode = real_ttymode_opcode(i, ssh_version); - - put_byte(bs, opcode); - if (ssh_version == 1 && opcode >= 1 && opcode <= 127) - put_byte(bs, val); - else - put_uint32(bs, val); - } - } - - put_byte(bs, TTYMODE_END_OF_LIST); -} - -/* ---------------------------------------------------------------------- - * Routine for allocating a new channel ID, given a means of finding - * the index field in a given channel structure. - */ - -unsigned alloc_channel_id_general(tree234 *channels, size_t localid_offset) -{ - const unsigned CHANNEL_NUMBER_OFFSET = 256; - search234_state ss; - - /* - * First-fit allocation of channel numbers: we always pick the - * lowest unused one. - * - * Every channel before that, and no channel after it, has an ID - * exactly equal to its tree index plus CHANNEL_NUMBER_OFFSET. So - * we can use the search234 system to identify the length of that - * initial sequence, in a single log-time pass down the channels - * tree. - */ - search234_start(&ss, channels); - while (ss.element) { - unsigned localid = *(unsigned *)((char *)ss.element + localid_offset); - if (localid == ss.index + CHANNEL_NUMBER_OFFSET) - search234_step(&ss, +1); - else - search234_step(&ss, -1); - } - - /* - * Now ss.index gives exactly the number of channels in that - * initial sequence. So adding CHANNEL_NUMBER_OFFSET to it must - * give precisely the lowest unused channel number. - */ - return ss.index + CHANNEL_NUMBER_OFFSET; -} - -/* ---------------------------------------------------------------------- - * Functions for handling the comma-separated strings used to store - * lists of protocol identifiers in SSH-2. - */ - -void add_to_commasep(strbuf *buf, const char *data) -{ - if (buf->len > 0) - put_byte(buf, ','); - put_data(buf, data, strlen(data)); -} - -bool get_commasep_word(ptrlen *list, ptrlen *word) -{ - const char *comma; - - /* - * Discard empty list elements, should there be any, because we - * never want to return one as if it was a real string. (This - * introduces a mild tolerance of badly formatted data in lists we - * receive, but I think that's acceptable.) - */ - while (list->len > 0 && *(const char *)list->ptr == ',') { - list->ptr = (const char *)list->ptr + 1; - list->len--; - } - - if (!list->len) - return false; - - comma = memchr(list->ptr, ',', list->len); - if (!comma) { - *word = *list; - list->len = 0; - } else { - size_t wordlen = comma - (const char *)list->ptr; - word->ptr = list->ptr; - word->len = wordlen; - list->ptr = (const char *)list->ptr + wordlen + 1; - list->len -= wordlen + 1; - } - return true; -} - -/* ---------------------------------------------------------------------- - * Functions for translating SSH packet type codes into their symbolic - * string names. - */ - -#define TRANSLATE_UNIVERSAL(y, name, value) \ - if (type == value) return #name; -#define TRANSLATE_KEX(y, name, value, ctx) \ - if (type == value && pkt_kctx == ctx) return #name; -#define TRANSLATE_AUTH(y, name, value, ctx) \ - if (type == value && pkt_actx == ctx) return #name; - -const char *ssh1_pkt_type(int type) -{ - SSH1_MESSAGE_TYPES(TRANSLATE_UNIVERSAL, y); - return "unknown"; -} -const char *ssh2_pkt_type(Pkt_KCtx pkt_kctx, Pkt_ACtx pkt_actx, int type) -{ - SSH2_MESSAGE_TYPES(TRANSLATE_UNIVERSAL, TRANSLATE_KEX, TRANSLATE_AUTH, y); - return "unknown"; -} - -#undef TRANSLATE_UNIVERSAL -#undef TRANSLATE_KEX -#undef TRANSLATE_AUTH - -/* ---------------------------------------------------------------------- - * Common helper function for clients and implementations of - * PacketProtocolLayer. - */ - -void ssh_ppl_replace(PacketProtocolLayer *old, PacketProtocolLayer *new) -{ - new->bpp = old->bpp; - ssh_ppl_setup_queues(new, old->in_pq, old->out_pq); - new->selfptr = old->selfptr; - new->user_input = old->user_input; - new->seat = old->seat; - new->ssh = old->ssh; - - *new->selfptr = new; - ssh_ppl_free(old); - - /* The new layer might need to be the first one that sends a - * packet, so trigger a call to its main coroutine immediately. If - * it doesn't need to go first, the worst that will do is return - * straight away. */ - queue_idempotent_callback(&new->ic_process_queue); -} - -void ssh_ppl_free(PacketProtocolLayer *ppl) -{ - delete_callbacks_for_context(ppl); - ppl->vt->free(ppl); -} - -static void ssh_ppl_ic_process_queue_callback(void *context) -{ - PacketProtocolLayer *ppl = (PacketProtocolLayer *)context; - ssh_ppl_process_queue(ppl); -} - -void ssh_ppl_setup_queues(PacketProtocolLayer *ppl, - PktInQueue *inq, PktOutQueue *outq) -{ - ppl->in_pq = inq; - ppl->out_pq = outq; - ppl->in_pq->pqb.ic = &ppl->ic_process_queue; - ppl->ic_process_queue.fn = ssh_ppl_ic_process_queue_callback; - ppl->ic_process_queue.ctx = ppl; - - /* If there's already something on the input queue, it will want - * handling immediately. */ - if (pq_peek(ppl->in_pq)) - queue_idempotent_callback(&ppl->ic_process_queue); -} - -void ssh_ppl_user_output_string_and_free(PacketProtocolLayer *ppl, char *text) -{ - /* Messages sent via this function are from the SSH layer, not - * from the server-side process, so they always have the stderr - * flag set. */ - seat_stderr_pl(ppl->seat, ptrlen_from_asciz(text)); - sfree(text); -} - -size_t ssh_ppl_default_queued_data_size(PacketProtocolLayer *ppl) -{ - return ppl->out_pq->pqb.total_size; -} - -/* ---------------------------------------------------------------------- - * Common helper functions for clients and implementations of - * BinaryPacketProtocol. - */ - -static void ssh_bpp_input_raw_data_callback(void *context) -{ - BinaryPacketProtocol *bpp = (BinaryPacketProtocol *)context; - Ssh *ssh = bpp->ssh; /* in case bpp is about to get freed */ - ssh_bpp_handle_input(bpp); - /* If we've now cleared enough backlog on the input connection, we - * may need to unfreeze it. */ - ssh_conn_processed_data(ssh); -} - -static void ssh_bpp_output_packet_callback(void *context) -{ - BinaryPacketProtocol *bpp = (BinaryPacketProtocol *)context; - ssh_bpp_handle_output(bpp); -} - -void ssh_bpp_common_setup(BinaryPacketProtocol *bpp) -{ - pq_in_init(&bpp->in_pq); - pq_out_init(&bpp->out_pq); - bpp->input_eof = false; - bpp->ic_in_raw.fn = ssh_bpp_input_raw_data_callback; - bpp->ic_in_raw.ctx = bpp; - bpp->ic_out_pq.fn = ssh_bpp_output_packet_callback; - bpp->ic_out_pq.ctx = bpp; - bpp->out_pq.pqb.ic = &bpp->ic_out_pq; -} - -void ssh_bpp_free(BinaryPacketProtocol *bpp) -{ - delete_callbacks_for_context(bpp); - bpp->vt->free(bpp); -} - -void ssh2_bpp_queue_disconnect(BinaryPacketProtocol *bpp, - const char *msg, int category) -{ - PktOut *pkt = ssh_bpp_new_pktout(bpp, SSH2_MSG_DISCONNECT); - put_uint32(pkt, category); - put_stringz(pkt, msg); - put_stringz(pkt, "en"); /* language tag */ - pq_push(&bpp->out_pq, pkt); -} - -#define BITMAP_UNIVERSAL(y, name, value) \ - | (value >= y && value < y+32 ? 1UL << (value-y) : 0) -#define BITMAP_CONDITIONAL(y, name, value, ctx) \ - BITMAP_UNIVERSAL(y, name, value) -#define SSH2_BITMAP_WORD(y) \ - (0 SSH2_MESSAGE_TYPES(BITMAP_UNIVERSAL, BITMAP_CONDITIONAL, \ - BITMAP_CONDITIONAL, (32*y))) - -bool ssh2_bpp_check_unimplemented(BinaryPacketProtocol *bpp, PktIn *pktin) -{ - static const unsigned valid_bitmap[] = { - SSH2_BITMAP_WORD(0), - SSH2_BITMAP_WORD(1), - SSH2_BITMAP_WORD(2), - SSH2_BITMAP_WORD(3), - SSH2_BITMAP_WORD(4), - SSH2_BITMAP_WORD(5), - SSH2_BITMAP_WORD(6), - SSH2_BITMAP_WORD(7), - }; - - if (pktin->type < 0x100 && - !((valid_bitmap[pktin->type >> 5] >> (pktin->type & 0x1F)) & 1)) { - PktOut *pkt = ssh_bpp_new_pktout(bpp, SSH2_MSG_UNIMPLEMENTED); - put_uint32(pkt, pktin->sequence); - pq_push(&bpp->out_pq, pkt); - return true; - } - - return false; -} - -#undef BITMAP_UNIVERSAL -#undef BITMAP_CONDITIONAL -#undef SSH1_BITMAP_WORD - -/* ---------------------------------------------------------------------- - * Function to check a host key against any manually configured in Conf. - */ - -int verify_ssh_manual_host_key(Conf *conf, char **fingerprints, ssh_key *key) -{ - if (!conf_get_str_nthstrkey(conf, CONF_ssh_manual_hostkeys, 0)) - return -1; /* no manual keys configured */ - - if (fingerprints) { - for (size_t i = 0; i < SSH_N_FPTYPES; i++) { - /* - * Each fingerprint string we've been given will have - * things like 'ssh-rsa 2048' at the front of it. Strip - * those off and narrow down to just the hash at the end - * of the string. - */ - const char *fingerprint = fingerprints[i]; - if (!fingerprint) - continue; - const char *p = strrchr(fingerprint, ' '); - fingerprint = p ? p+1 : fingerprint; - if (conf_get_str_str_opt(conf, CONF_ssh_manual_hostkeys, - fingerprint)) - return 1; /* success */ - } - } - - if (key) { - /* - * Construct the base64-encoded public key blob and see if - * that's listed. - */ - strbuf *binblob; - char *base64blob; - int atoms, i; - binblob = strbuf_new(); - ssh_key_public_blob(key, BinarySink_UPCAST(binblob)); - atoms = (binblob->len + 2) / 3; - base64blob = snewn(atoms * 4 + 1, char); - for (i = 0; i < atoms; i++) - base64_encode_atom(binblob->u + 3*i, - binblob->len - 3*i, base64blob + 4*i); - base64blob[atoms * 4] = '\0'; - strbuf_free(binblob); - if (conf_get_str_str_opt(conf, CONF_ssh_manual_hostkeys, base64blob)) { - sfree(base64blob); - return 1; /* success */ - } - sfree(base64blob); - } - - return 0; -} - -/* ---------------------------------------------------------------------- - * Common functions shared between SSH-1 layers. - */ - -bool ssh1_common_get_specials( - PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx) -{ - /* - * 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 (!(ppl->remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE)) { - add_special(ctx, "IGNORE message", SS_NOP, 0); - return true; - } - - return false; -} - -bool ssh1_common_filter_queue(PacketProtocolLayer *ppl) -{ - PktIn *pktin; - ptrlen msg; - - while ((pktin = pq_peek(ppl->in_pq)) != NULL) { - switch (pktin->type) { - case SSH1_MSG_DISCONNECT: - msg = get_string(pktin); - ssh_remote_error(ppl->ssh, - "Remote side sent disconnect message:\n\"%.*s\"", - PTRLEN_PRINTF(msg)); - /* don't try to pop the queue, because we've been freed! */ - return true; /* indicate that we've been freed */ - - case SSH1_MSG_DEBUG: - msg = get_string(pktin); - ppl_logevent("Remote debug message: %.*s", PTRLEN_PRINTF(msg)); - pq_pop(ppl->in_pq); - break; - - case SSH1_MSG_IGNORE: - /* Do nothing, because we're ignoring it! Duhh. */ - pq_pop(ppl->in_pq); - break; - - default: - return false; - } - } - - return false; -} - -void ssh1_compute_session_id( - unsigned char *session_id, const unsigned char *cookie, - RSAKey *hostkey, RSAKey *servkey) -{ - ssh_hash *hash = ssh_hash_new(&ssh_md5); - - for (size_t i = (mp_get_nbits(hostkey->modulus) + 7) / 8; i-- ;) - put_byte(hash, mp_get_byte(hostkey->modulus, i)); - for (size_t i = (mp_get_nbits(servkey->modulus) + 7) / 8; i-- ;) - put_byte(hash, mp_get_byte(servkey->modulus, i)); - put_data(hash, cookie, 8); - ssh_hash_final(hash, session_id); -} diff --git a/sshcrcda.c b/sshcrcda.c deleted file mode 100644 index 18044754..00000000 --- a/sshcrcda.c +++ /dev/null @@ -1,171 +0,0 @@ -/* $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 - * - * - * Modified for use in PuTTY by Simon Tatham - */ - -#include -#include "misc.h" -#include "ssh.h" - -/* SSH Constants */ -#define SSH_MAXBLOCKS (32 * 1024) -#define SSH_BLOCKSIZE (8) - -/* Hashing constants */ -#define HASH_MINSIZE (8 * 1024) -#define HASH_ENTRYSIZE (sizeof(uint16_t)) -#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)) - -static const uint8_t ONE[4] = { 1, 0, 0, 0 }; -static const uint8_t ZERO[4] = { 0, 0, 0, 0 }; - -struct crcda_ctx { - uint16_t *h; - uint32_t n; -}; - -struct crcda_ctx *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(struct crcda_ctx *ctx) -{ - if (ctx) { - sfree(ctx->h); - ctx->h = NULL; - sfree(ctx); - } -} - -static void crc_update(uint32_t *a, const void *b) -{ - *a = crc32_update(*a, make_ptrlen(b, 4)); -} - -/* detect if a block is used in a particular pattern */ -static bool check_crc(const uint8_t *S, const uint8_t *buf, - uint32_t len, const uint8_t *IV) -{ - uint32_t crc; - const uint8_t *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 */ -bool detect_attack(struct crcda_ctx *ctx, - const unsigned char *buf, uint32_t len, - const unsigned char *IV) -{ - register uint32_t i, j; - uint32_t l; - register const uint8_t *c; - const uint8_t *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_t); - } else { - if (l > ctx->n) { - ctx->n = l; - ctx->h = sresize(ctx->h, ctx->n, uint16_t); - } - } - - 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 true; /* attack detected */ - else - break; - } - for (d = buf; d < c; d += SSH_BLOCKSIZE) { - if (!CMP(c, d)) { - if ((check_crc(c, buf, len, IV))) - return true; /* attack detected */ - else - break; - } - } - } - return false; /* 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) { - assert(IV); /* or we wouldn't have stored HASH_IV above */ - if (!CMP(c, IV)) { - if (check_crc(c, buf, len, IV)) - return true; /* attack detected */ - else - break; - } - } else if (!CMP(c, buf + ctx->h[i] * SSH_BLOCKSIZE)) { - if (check_crc(c, buf, len, IV)) - return true; /* attack detected */ - else - break; - } - } - ctx->h[i] = j; - } - return false; /* ok */ -} diff --git a/sshgss.h b/sshgss.h deleted file mode 100644 index c640636d..00000000 --- a/sshgss.h +++ /dev/null @@ -1,217 +0,0 @@ -#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_BAD_MIC, - SSH_GSS_NO_CREDS, - 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; - -#define GSS_NO_EXPIRATION ((time_t)-1) - -#define GSS_DEF_REKEY_MINS 2 /* Default minutes between GSS cache checks */ - -/* 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(Conf *conf); -void ssh_gss_cleanup(struct ssh_gss_liblist *list); - -/* - * Fills in buf with a string describing the GSSAPI mechanism in - * use. buf->data is not dynamically allocated. - */ -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, time_t *expiry, - unsigned long *lifetime); - -/* - * 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 *, - time_t *expiry); - -/* - * 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); - -/* - * Validates an input MIC for some input data. - */ -typedef Ssh_gss_stat (*t_ssh_gss_verify_mic)(struct ssh_gss_library *lib, - Ssh_gss_ctx ctx, - Ssh_gss_buf *in_data, - Ssh_gss_buf *in_mic); - -/* - * 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_verify_mic verify_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; -}; - -/* - * State that has to be shared between all GSSAPI-using parts of the - * same SSH connection, in particular between GSS key exchange and the - * subsequent trivial userauth method that reuses its output. - */ -struct ssh_connection_shared_gss_state { - struct ssh_gss_liblist *libs; - struct ssh_gss_library *lib; - Ssh_gss_name srv_name; - Ssh_gss_ctx ctx; -}; - -#endif /* NO_GSSAPI */ - -#endif /*PUTTY_SSHGSS_H*/ diff --git a/sshgssc.c b/sshgssc.c deleted file mode 100644 index d9f62c39..00000000 --- a/sshgssc.c +++ /dev/null @@ -1,288 +0,0 @@ -#include "putty.h" - -#include -#include -#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); - - 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, - time_t *expiry) -{ - struct gssapi_functions *gss = &lib->u.gssapi; - gss_OID_set_desc k5only = { 1, GSS_MECH_KRB5 }; - gss_cred_id_t cred; - OM_uint32 dummy; - OM_uint32 time_rec; - gssapi_ssh_gss_ctx *gssctx = snew(gssapi_ssh_gss_ctx); - - gssctx->ctx = GSS_C_NO_CONTEXT; - gssctx->expiry = 0; - - gssctx->maj_stat = - gss->acquire_cred(&gssctx->min_stat, GSS_C_NO_NAME, GSS_C_INDEFINITE, - &k5only, GSS_C_INITIATE, &cred, - (gss_OID_set *)0, &time_rec); - - if (gssctx->maj_stat != GSS_S_COMPLETE) { - sfree(gssctx); - return SSH_GSS_FAILURE; - } - - /* - * When the credential lifetime is not yet available due to deferred - * processing, gss_acquire_cred should return a 0 lifetime which is - * distinct from GSS_C_INDEFINITE which signals a crential that never - * expires. However, not all implementations get this right, and with - * Kerberos, initiator credentials always expire at some point. So when - * lifetime is 0 or GSS_C_INDEFINITE we call gss_inquire_cred_by_mech() to - * complete deferred processing. - */ - if (time_rec == GSS_C_INDEFINITE || time_rec == 0) { - gssctx->maj_stat = - gss->inquire_cred_by_mech(&gssctx->min_stat, cred, - (gss_OID) GSS_MECH_KRB5, - GSS_C_NO_NAME, - &time_rec, - NULL, - NULL); - } - (void) gss->release_cred(&dummy, &cred); - - if (gssctx->maj_stat != GSS_S_COMPLETE) { - sfree(gssctx); - return SSH_GSS_FAILURE; - } - - if (time_rec != GSS_C_INDEFINITE) - gssctx->expiry = time(NULL) + time_rec; - else - gssctx->expiry = GSS_NO_EXPIRATION; - - if (expiry) { - *expiry = gssctx->expiry; - } - - *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, - time_t *expiry, - unsigned long *lifetime) -{ - struct gssapi_functions *gss = &lib->u.gssapi; - gssapi_ssh_gss_ctx *gssctx = (gssapi_ssh_gss_ctx*) *ctx; - OM_uint32 ret_flags; - OM_uint32 lifetime_rec; - - 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, - &lifetime_rec); - - if (lifetime) { - if (lifetime_rec == GSS_C_INDEFINITE) - *lifetime = ULONG_MAX; - else - *lifetime = lifetime_rec; - } - if (expiry) { - if (lifetime_rec == GSS_C_INDEFINITE) - *expiry = GSS_NO_EXPIRATION; - else - *expiry = time(NULL) + lifetime_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); - *ctx = NULL; - - 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_verify_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->verify_mic(&(gssctx->min_stat), gssctx->ctx, buf, hash, NULL); -} - -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->verify_mic = ssh_gssapi_verify_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 diff --git a/sshgssc.h b/sshgssc.h deleted file mode 100644 index 07fac009..00000000 --- a/sshgssc.h +++ /dev/null @@ -1,24 +0,0 @@ -#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; - time_t expiry; -} 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*/ diff --git a/sshnogss.c b/sshnogss.c deleted file mode 100644 index fa4ac65f..00000000 --- a/sshnogss.c +++ /dev/null @@ -1,19 +0,0 @@ -#include "putty.h" -#ifndef NO_GSSAPI - -/* For platforms not supporting GSSAPI */ - -struct ssh_gss_liblist *ssh_gss_setup(Conf *conf) -{ - struct ssh_gss_liblist *list = snew(struct ssh_gss_liblist *); - list->libraries = NULL; - list->nlibraries = 0; - return list; -} - -void ssh_gss_cleanup(struct ssh_gss_liblist *list) -{ - sfree(list); -} - -#endif /* NO_GSSAPI */ diff --git a/sshppl.h b/sshppl.h deleted file mode 100644 index b339d67c..00000000 --- a/sshppl.h +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Abstraction of the various layers of SSH packet-level protocol, - * general enough to take in all three of the main SSH-2 layers and - * both of the SSH-1 phases. - */ - -#ifndef PUTTY_SSHPPL_H -#define PUTTY_SSHPPL_H - -typedef void (*packet_handler_fn_t)(PacketProtocolLayer *ppl, PktIn *pktin); -typedef struct PacketProtocolLayerVtable PacketProtocolLayerVtable; - -struct PacketProtocolLayerVtable { - void (*free)(PacketProtocolLayer *); - void (*process_queue)(PacketProtocolLayer *ppl); - bool (*get_specials)( - PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx); - void (*special_cmd)( - PacketProtocolLayer *ppl, SessionSpecialCode code, int arg); - bool (*want_user_input)(PacketProtocolLayer *ppl); - void (*got_user_input)(PacketProtocolLayer *ppl); - void (*reconfigure)(PacketProtocolLayer *ppl, Conf *conf); - size_t (*queued_data_size)(PacketProtocolLayer *ppl); - - /* Protocol-level name of this layer. */ - const char *name; -}; - -struct PacketProtocolLayer { - const struct PacketProtocolLayerVtable *vt; - - /* Link to the underlying SSH BPP. */ - BinaryPacketProtocol *bpp; - - /* Queue from which the layer receives its input packets, and one - * to put its output packets on. */ - PktInQueue *in_pq; - PktOutQueue *out_pq; - - /* Idempotent callback that in_pq will be linked to, causing a - * call to the process_queue method. in_pq points to this, so it - * will be automatically triggered by pushing things on the - * layer's input queue, but it can also be triggered on purpose. */ - IdempotentCallback ic_process_queue; - - /* Owner's pointer to this layer. Permits a layer to unilaterally - * abdicate in favour of a replacement, by overwriting this - * pointer and then freeing itself. */ - PacketProtocolLayer **selfptr; - - /* Bufchain of keyboard input from the user, for login prompts and - * similar. */ - bufchain *user_input; - - /* Logging and error-reporting facilities. */ - LogContext *logctx; - Seat *seat; /* for dialog boxes, session output etc */ - Ssh *ssh; /* for session termination + assorted connection-layer ops */ - - /* Known bugs in the remote implementation. */ - unsigned remote_bugs; -}; - -static inline void ssh_ppl_process_queue(PacketProtocolLayer *ppl) -{ ppl->vt->process_queue(ppl); } -static inline bool ssh_ppl_get_specials( - PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx) -{ return ppl->vt->get_specials(ppl, add_special, ctx); } -static inline void ssh_ppl_special_cmd( - PacketProtocolLayer *ppl, SessionSpecialCode code, int arg) -{ ppl->vt->special_cmd(ppl, code, arg); } -static inline bool ssh_ppl_want_user_input(PacketProtocolLayer *ppl) -{ return ppl->vt->want_user_input(ppl); } -static inline void ssh_ppl_got_user_input(PacketProtocolLayer *ppl) -{ ppl->vt->got_user_input(ppl); } -static inline void ssh_ppl_reconfigure(PacketProtocolLayer *ppl, Conf *conf) -{ ppl->vt->reconfigure(ppl, conf); } -static inline size_t ssh_ppl_queued_data_size(PacketProtocolLayer *ppl) -{ return ppl->vt->queued_data_size(ppl); } - -/* ssh_ppl_free is more than just a macro wrapper on the vtable; it - * does centralised parts of the freeing too. */ -void ssh_ppl_free(PacketProtocolLayer *ppl); - -/* Helper routine to point a PPL at its input and output queues. Also - * sets up the IdempotentCallback on the input queue to trigger a call - * to process_queue whenever packets are added to it. */ -void ssh_ppl_setup_queues(PacketProtocolLayer *ppl, - PktInQueue *inq, PktOutQueue *outq); - -/* Routine a PPL can call to abdicate in favour of a replacement, by - * overwriting ppl->selfptr. Has the side effect of freeing 'old', so - * if 'old' actually called this (which is likely) then it should - * avoid dereferencing itself on return from this function! */ -void ssh_ppl_replace(PacketProtocolLayer *old, PacketProtocolLayer *new); - -/* Default implementation of queued_data_size, which just adds up the - * sizes of all the packets in pq_out. A layer can override this if it - * has other things to take into account as well. */ -size_t ssh_ppl_default_queued_data_size(PacketProtocolLayer *ppl); - -PacketProtocolLayer *ssh1_login_new( - Conf *conf, const char *host, int port, - PacketProtocolLayer *successor_layer); -PacketProtocolLayer *ssh1_connection_new( - Ssh *ssh, Conf *conf, ConnectionLayer **cl_out); - -struct DataTransferStats; -struct ssh_connection_shared_gss_state; -PacketProtocolLayer *ssh2_transport_new( - Conf *conf, const char *host, int port, const char *fullhostname, - const char *client_greeting, const char *server_greeting, - struct ssh_connection_shared_gss_state *shgss, - struct DataTransferStats *stats, PacketProtocolLayer *higher_layer, - const SshServerConfig *ssc); -PacketProtocolLayer *ssh2_userauth_new( - PacketProtocolLayer *successor_layer, - const char *hostname, const char *fullhostname, - Filename *keyfile, bool show_banner, bool tryagent, - const char *default_username, bool change_username, - bool try_ki_auth, - bool try_gssapi_auth, bool try_gssapi_kex_auth, - bool gssapi_fwd, struct ssh_connection_shared_gss_state *shgss); -PacketProtocolLayer *ssh2_connection_new( - Ssh *ssh, ssh_sharing_state *connshare, bool is_simple, - Conf *conf, const char *peer_verstring, ConnectionLayer **cl_out); - -/* Can't put this in the userauth constructor without having a - * dependency loop at setup time (transport and userauth can't _both_ - * be constructed second and given a pointer to the other). */ -void ssh2_userauth_set_transport_layer(PacketProtocolLayer *userauth, - PacketProtocolLayer *transport); - -/* Convenience macro for protocol layers to send formatted strings to - * the Event Log. Assumes a function parameter called 'ppl' is in - * scope. */ -#define ppl_logevent(...) ( \ - logevent_and_free((ppl)->logctx, dupprintf(__VA_ARGS__))) - -/* Convenience macro for protocol layers to send formatted strings to - * the terminal. Also expects 'ppl' to be in scope. */ -#define ppl_printf(...) \ - ssh_ppl_user_output_string_and_free(ppl, dupprintf(__VA_ARGS__)) -void ssh_ppl_user_output_string_and_free(PacketProtocolLayer *ppl, char *text); - -/* Methods for userauth to communicate back to the transport layer */ -ptrlen ssh2_transport_get_session_id(PacketProtocolLayer *ssh2_transport_ptr); -void ssh2_transport_notify_auth_done(PacketProtocolLayer *ssh2_transport_ptr); - -/* Shared method between ssh2 layers (defined in ssh2transport.c) to - * handle the common packets between login and connection: DISCONNECT, - * DEBUG and IGNORE. Those messages are handled by the ssh2transport - * layer if we have one, but in bare ssh2-connection mode they have to - * be handled by ssh2connection. */ -bool ssh2_common_filter_queue(PacketProtocolLayer *ppl); - -/* Methods for ssh1login to pass protocol flags to ssh1connection */ -void ssh1_connection_set_protoflags( - PacketProtocolLayer *ppl, int local, int remote); - -/* Shared get_specials method between the two ssh1 layers */ -bool ssh1_common_get_specials(PacketProtocolLayer *, add_special_fn_t, void *); - -/* Other shared functions between ssh1 layers */ -bool ssh1_common_filter_queue(PacketProtocolLayer *ppl); -void ssh1_compute_session_id( - unsigned char *session_id, const unsigned char *cookie, - RSAKey *hostkey, RSAKey *servkey); - -/* Method used by the SSH server */ -void ssh2_transport_provide_hostkeys(PacketProtocolLayer *ssh2_transport_ptr, - ssh_key *const *hostkeys, int nhostkeys); - -#endif /* PUTTY_SSHPPL_H */ diff --git a/sshserver.c b/sshserver.c deleted file mode 100644 index cc1c880d..00000000 --- a/sshserver.c +++ /dev/null @@ -1,591 +0,0 @@ -/* - * Top-level code for SSH server implementation. - */ - -#include -#include - -#include "putty.h" -#include "ssh.h" -#include "sshbpp.h" -#include "sshppl.h" -#include "sshchan.h" -#include "sshserver.h" -#ifndef NO_GSSAPI -#include "sshgssc.h" -#include "sshgss.h" -#endif - -struct Ssh { int dummy; }; - -typedef struct server server; -struct server { - bufchain in_raw, out_raw; - IdempotentCallback ic_out_raw; - bool pending_close; - - bufchain dummy_user_input; /* we never put anything on this */ - - PacketLogSettings pls; - LogContext *logctx; - struct DataTransferStats stats; - - int remote_bugs; - - Socket *socket; - Plug plug; - int conn_throttle_count; - bool frozen; - - Conf *conf; - const SshServerConfig *ssc; - ssh_key *const *hostkeys; - int nhostkeys; - RSAKey *hostkey1; - AuthPolicy *authpolicy; - LogPolicy *logpolicy; - const SftpServerVtable *sftpserver_vt; - - agentfwd *stunt_agentfwd; - - Seat seat; - Ssh ssh; - struct ssh_version_receiver version_receiver; - - BinaryPacketProtocol *bpp; - PacketProtocolLayer *base_layer; - ConnectionLayer *cl; - -#ifndef NO_GSSAPI - struct ssh_connection_shared_gss_state gss_state; -#endif -}; - -static void ssh_server_free_callback(void *vsrv); -static void server_got_ssh_version(struct ssh_version_receiver *rcv, - int major_version); -static void server_connect_bpp(server *srv); -static void server_bpp_output_raw_data_callback(void *vctx); - -void share_activate(ssh_sharing_state *sharestate, - const char *server_verstring) {} -void ssh_connshare_provide_connlayer(ssh_sharing_state *sharestate, - ConnectionLayer *cl) {} -int share_ndownstreams(ssh_sharing_state *sharestate) { return 0; } -void share_got_pkt_from_server(ssh_sharing_connstate *cs, int type, - const void *vpkt, int pktlen) {} -void share_setup_x11_channel(ssh_sharing_connstate *cs, share_channel *chan, - unsigned upstream_id, unsigned server_id, - unsigned server_currwin, unsigned server_maxpkt, - unsigned client_adjusted_window, - const char *peer_addr, int peer_port, int endian, - int protomajor, int protominor, - const void *initial_data, int initial_len) {} -Channel *agentf_new(SshChannel *c) { return NULL; } -bool agent_exists(void) { return false; } -void ssh_got_exitcode(Ssh *ssh, int exitcode) {} -void ssh_check_frozen(Ssh *ssh) {} - -mainchan *mainchan_new( - PacketProtocolLayer *ppl, ConnectionLayer *cl, Conf *conf, - int term_width, int term_height, bool is_simple, SshChannel **sc_out) -{ return NULL; } -void mainchan_get_specials( - mainchan *mc, add_special_fn_t add_special, void *ctx) {} -void mainchan_special_cmd(mainchan *mc, SessionSpecialCode code, int arg) {} -void mainchan_terminal_size(mainchan *mc, int width, int height) {} - -/* Seat functions to ensure we don't get choosy about crypto - as the - * server, it's not up to us to give user warnings */ -static int server_confirm_weak_crypto_primitive( - Seat *seat, const char *algtype, const char *algname, - void (*callback)(void *ctx, int result), void *ctx) { return 1; } -static int server_confirm_weak_cached_hostkey( - Seat *seat, const char *algname, const char *betteralgs, - void (*callback)(void *ctx, int result), void *ctx) { return 1; } - -static const SeatVtable server_seat_vt = { - .output = nullseat_output, - .eof = nullseat_eof, - .get_userpass_input = nullseat_get_userpass_input, - .notify_remote_exit = nullseat_notify_remote_exit, - .connection_fatal = nullseat_connection_fatal, - .update_specials_menu = nullseat_update_specials_menu, - .get_ttymode = nullseat_get_ttymode, - .set_busy_status = nullseat_set_busy_status, - .verify_ssh_host_key = nullseat_verify_ssh_host_key, - .confirm_weak_crypto_primitive = server_confirm_weak_crypto_primitive, - .confirm_weak_cached_hostkey = server_confirm_weak_cached_hostkey, - .is_utf8 = nullseat_is_never_utf8, - .echoedit_update = nullseat_echoedit_update, - .get_x_display = nullseat_get_x_display, - .get_windowid = nullseat_get_windowid, - .get_window_pixel_size = nullseat_get_window_pixel_size, - .stripctrl_new = nullseat_stripctrl_new, - .set_trust_status = nullseat_set_trust_status, - .verbose = nullseat_verbose_no, - .interactive = nullseat_interactive_no, - .get_cursor_position = nullseat_get_cursor_position, -}; - -static void server_socket_log(Plug *plug, PlugLogType type, SockAddr *addr, - int port, const char *error_msg, int error_code) -{ - /* server *srv = container_of(plug, server, plug); */ - /* FIXME */ -} - -static void server_closing(Plug *plug, const char *error_msg, int error_code, - bool calling_back) -{ - server *srv = container_of(plug, server, plug); - if (error_msg) { - ssh_remote_error(&srv->ssh, "%s", error_msg); - } else if (srv->bpp) { - srv->bpp->input_eof = true; - queue_idempotent_callback(&srv->bpp->ic_in_raw); - } -} - -static void server_receive( - Plug *plug, int urgent, const char *data, size_t len) -{ - server *srv = container_of(plug, server, plug); - - /* Log raw data, if we're in that mode. */ - if (srv->logctx) - log_packet(srv->logctx, PKT_INCOMING, -1, NULL, data, len, - 0, NULL, NULL, 0, NULL); - - bufchain_add(&srv->in_raw, data, len); - if (!srv->frozen && srv->bpp) - queue_idempotent_callback(&srv->bpp->ic_in_raw); -} - -static void server_sent(Plug *plug, size_t bufsize) -{ -#ifdef FIXME - server *srv = container_of(plug, server, plug); - - /* - * If the send backlog on the SSH socket itself clears, we should - * unthrottle the whole world if it was throttled. Also trigger an - * extra call to the consumer of the BPP's output, to try to send - * some more data off its bufchain. - */ - if (bufsize < SSH_MAX_BACKLOG) { - srv_throttle_all(srv, 0, bufsize); - queue_idempotent_callback(&srv->ic_out_raw); - } -#endif -} - -LogContext *ssh_get_logctx(Ssh *ssh) -{ - server *srv = container_of(ssh, server, ssh); - return srv->logctx; -} - -void ssh_throttle_conn(Ssh *ssh, int adjust) -{ - server *srv = container_of(ssh, server, ssh); - int old_count = srv->conn_throttle_count; - bool frozen; - - srv->conn_throttle_count += adjust; - assert(srv->conn_throttle_count >= 0); - - if (srv->conn_throttle_count && !old_count) { - frozen = true; - } else if (!srv->conn_throttle_count && old_count) { - frozen = false; - } else { - return; /* don't change current frozen state */ - } - - srv->frozen = frozen; - - if (srv->socket) { - sk_set_frozen(srv->socket, frozen); - - /* - * Now process any SSH connection data that was stashed in our - * queue while we were frozen. - */ - queue_idempotent_callback(&srv->bpp->ic_in_raw); - } -} - -void ssh_conn_processed_data(Ssh *ssh) -{ - /* FIXME: we could add the same check_frozen_state system as we - * have in ssh.c, but because that was originally added to work - * around a peculiarity of the GUI event loop, I haven't yet. */ -} - -Conf *make_ssh_server_conf(void) -{ - Conf *conf = conf_new(); - load_open_settings(NULL, conf); - /* In Uppity, we support even the legacy des-cbc cipher by - * default, so that it will be available if the user forces it by - * overriding the KEXINIT strings. If the user wants it _not_ - * supported, of course, they can override KEXINIT in the other - * direction. */ - conf_set_bool(conf, CONF_ssh2_des_cbc, true); - return conf; -} - -static const PlugVtable ssh_server_plugvt = { - .log = server_socket_log, - .closing = server_closing, - .receive = server_receive, - .sent = server_sent, -}; - -Plug *ssh_server_plug( - Conf *conf, const SshServerConfig *ssc, - ssh_key *const *hostkeys, int nhostkeys, - RSAKey *hostkey1, AuthPolicy *authpolicy, LogPolicy *logpolicy, - const SftpServerVtable *sftpserver_vt) -{ - server *srv = snew(server); - - memset(srv, 0, sizeof(server)); - - srv->plug.vt = &ssh_server_plugvt; - srv->conf = conf_copy(conf); - srv->ssc = ssc; - srv->logctx = log_init(logpolicy, conf); - conf_set_bool(srv->conf, CONF_ssh_no_shell, true); - srv->nhostkeys = nhostkeys; - srv->hostkeys = hostkeys; - srv->hostkey1 = hostkey1; - srv->authpolicy = authpolicy; - srv->logpolicy = logpolicy; - srv->sftpserver_vt = sftpserver_vt; - - srv->seat.vt = &server_seat_vt; - - bufchain_init(&srv->in_raw); - bufchain_init(&srv->out_raw); - bufchain_init(&srv->dummy_user_input); - -#ifndef NO_GSSAPI - /* FIXME: replace with sensible */ - srv->gss_state.libs = snew(struct ssh_gss_liblist); - srv->gss_state.libs->nlibraries = 0; -#endif - - return &srv->plug; -} - -void ssh_server_start(Plug *plug, Socket *socket) -{ - server *srv = container_of(plug, server, plug); - const char *our_protoversion; - - if (srv->ssc->bare_connection) { - our_protoversion = "2.0"; /* SSH-2 only */ - } else if (srv->hostkey1 && srv->nhostkeys) { - our_protoversion = "1.99"; /* offer both SSH-1 and SSH-2 */ - } else if (srv->hostkey1) { - our_protoversion = "1.5"; /* SSH-1 only */ - } else { - assert(srv->nhostkeys); - our_protoversion = "2.0"; /* SSH-2 only */ - } - - srv->socket = socket; - - srv->ic_out_raw.fn = server_bpp_output_raw_data_callback; - srv->ic_out_raw.ctx = srv; - srv->version_receiver.got_ssh_version = server_got_ssh_version; - srv->bpp = ssh_verstring_new( - srv->conf, srv->logctx, srv->ssc->bare_connection, - our_protoversion, &srv->version_receiver, - true, srv->ssc->application_name); - server_connect_bpp(srv); - queue_idempotent_callback(&srv->bpp->ic_in_raw); -} - -static void ssh_server_free_callback(void *vsrv) -{ - server *srv = (server *)vsrv; - - logeventf(srv->logctx, "freeing server instance"); - - bufchain_clear(&srv->in_raw); - bufchain_clear(&srv->out_raw); - bufchain_clear(&srv->dummy_user_input); - - if (srv->socket) - sk_close(srv->socket); - - if (srv->stunt_agentfwd) - agentfwd_free(srv->stunt_agentfwd); - - if (srv->base_layer) - ssh_ppl_free(srv->base_layer); - if (srv->bpp) - ssh_bpp_free(srv->bpp); - - delete_callbacks_for_context(srv); - - conf_free(srv->conf); - log_free(srv->logctx); - -#ifndef NO_GSSAPI - sfree(srv->gss_state.libs); /* FIXME: replace with sensible */ -#endif - - LogPolicy *lp = srv->logpolicy; - sfree(srv); - - server_instance_terminated(lp); -} - -static void server_connect_bpp(server *srv) -{ - srv->bpp->ssh = &srv->ssh; - srv->bpp->in_raw = &srv->in_raw; - srv->bpp->out_raw = &srv->out_raw; - bufchain_set_callback(srv->bpp->out_raw, &srv->ic_out_raw); - srv->bpp->pls = &srv->pls; - srv->bpp->logctx = srv->logctx; - srv->bpp->remote_bugs = srv->remote_bugs; - /* Servers don't really have a notion of 'unexpected' connection - * closure. The client is free to close if it likes. */ - srv->bpp->expect_close = true; -} - -static void server_connect_ppl(server *srv, PacketProtocolLayer *ppl) -{ - ppl->bpp = srv->bpp; - ppl->user_input = &srv->dummy_user_input; - ppl->logctx = srv->logctx; - ppl->ssh = &srv->ssh; - ppl->seat = &srv->seat; - ppl->remote_bugs = srv->remote_bugs; -} - -static void server_bpp_output_raw_data_callback(void *vctx) -{ - server *srv = (server *)vctx; - - if (!srv->socket) - return; - - while (bufchain_size(&srv->out_raw) > 0) { - size_t backlog; - - ptrlen data = bufchain_prefix(&srv->out_raw); - - if (srv->logctx) - log_packet(srv->logctx, PKT_OUTGOING, -1, NULL, data.ptr, data.len, - 0, NULL, NULL, 0, NULL); - backlog = sk_write(srv->socket, data.ptr, data.len); - - bufchain_consume(&srv->out_raw, data.len); - - if (backlog > SSH_MAX_BACKLOG) { -#ifdef FIXME - ssh_throttle_all(ssh, 1, backlog); -#endif - return; - } - } - - if (srv->pending_close) { - sk_close(srv->socket); - srv->socket = NULL; - queue_toplevel_callback(ssh_server_free_callback, srv); - } -} - -static void server_shutdown_internal(server *srv) -{ - /* - * We only need to free the base PPL, which will free the others - * (if any) transitively. - */ - if (srv->base_layer) { - ssh_ppl_free(srv->base_layer); - srv->base_layer = NULL; - } - - srv->cl = NULL; -} - -static void server_initiate_connection_close(server *srv) -{ - /* Wind up everything above the BPP. */ - server_shutdown_internal(srv); - - /* Force any remaining queued SSH packets through the BPP, and - * schedule closing the network socket after they go out. */ - ssh_bpp_handle_output(srv->bpp); - srv->pending_close = true; - queue_idempotent_callback(&srv->ic_out_raw); - - /* Now we expect the other end to close the connection too in - * response, so arrange that we'll receive notification of that - * via ssh_remote_eof. */ - srv->bpp->expect_close = true; -} - -#define GET_FORMATTED_MSG(fmt) \ - char *msg; \ - va_list ap; \ - va_start(ap, fmt); \ - msg = dupvprintf(fmt, ap); \ - va_end(ap); - -#define LOG_FORMATTED_MSG(logctx, fmt) do \ - { \ - va_list ap; \ - va_start(ap, fmt); \ - logeventvf(logctx, fmt, ap); \ - va_end(ap); \ - } while (0) - -void ssh_remote_error(Ssh *ssh, const char *fmt, ...) -{ - server *srv = container_of(ssh, server, ssh); - LOG_FORMATTED_MSG(srv->logctx, fmt); - queue_toplevel_callback(ssh_server_free_callback, srv); -} - -void ssh_remote_eof(Ssh *ssh, const char *fmt, ...) -{ - server *srv = container_of(ssh, server, ssh); - LOG_FORMATTED_MSG(srv->logctx, fmt); - queue_toplevel_callback(ssh_server_free_callback, srv); -} - -void ssh_proto_error(Ssh *ssh, const char *fmt, ...) -{ - server *srv = container_of(ssh, server, ssh); - if (srv->base_layer) { - GET_FORMATTED_MSG(fmt); - ssh_bpp_queue_disconnect(srv->bpp, msg, - SSH2_DISCONNECT_PROTOCOL_ERROR); - server_initiate_connection_close(srv); - logeventf(srv->logctx, "Protocol error: %s", msg); - sfree(msg); - } -} - -void ssh_sw_abort(Ssh *ssh, const char *fmt, ...) -{ - server *srv = container_of(ssh, server, ssh); - LOG_FORMATTED_MSG(srv->logctx, fmt); - queue_toplevel_callback(ssh_server_free_callback, srv); -} - -void ssh_user_close(Ssh *ssh, const char *fmt, ...) -{ - server *srv = container_of(ssh, server, ssh); - LOG_FORMATTED_MSG(srv->logctx, fmt); - queue_toplevel_callback(ssh_server_free_callback, srv); -} - -static void server_got_ssh_version(struct ssh_version_receiver *rcv, - int major_version) -{ - server *srv = container_of(rcv, server, version_receiver); - BinaryPacketProtocol *old_bpp; - PacketProtocolLayer *connection_layer; - - old_bpp = srv->bpp; - srv->remote_bugs = ssh_verstring_get_bugs(old_bpp); - - if (srv->ssc->bare_connection) { - srv->bpp = ssh2_bare_bpp_new(srv->logctx); - server_connect_bpp(srv); - - connection_layer = ssh2_connection_new( - &srv->ssh, NULL, false, srv->conf, - ssh_verstring_get_local(old_bpp), &srv->cl); - ssh2connection_server_configure(connection_layer, - srv->sftpserver_vt, srv->ssc); - server_connect_ppl(srv, connection_layer); - - srv->base_layer = connection_layer; - } else if (major_version == 2) { - PacketProtocolLayer *userauth_layer, *transport_child_layer; - - srv->bpp = ssh2_bpp_new(srv->logctx, &srv->stats, true); - server_connect_bpp(srv); - - connection_layer = ssh2_connection_new( - &srv->ssh, NULL, false, srv->conf, - ssh_verstring_get_local(old_bpp), &srv->cl); - ssh2connection_server_configure(connection_layer, - srv->sftpserver_vt, srv->ssc); - server_connect_ppl(srv, connection_layer); - - if (conf_get_bool(srv->conf, CONF_ssh_no_userauth)) { - userauth_layer = NULL; - transport_child_layer = connection_layer; - } else { - userauth_layer = ssh2_userauth_server_new( - connection_layer, srv->authpolicy, srv->ssc); - server_connect_ppl(srv, userauth_layer); - transport_child_layer = userauth_layer; - } - - srv->base_layer = ssh2_transport_new( - srv->conf, NULL, 0, NULL, - ssh_verstring_get_remote(old_bpp), - ssh_verstring_get_local(old_bpp), -#ifndef NO_GSSAPI - &srv->gss_state, -#else - NULL, -#endif - &srv->stats, transport_child_layer, srv->ssc); - ssh2_transport_provide_hostkeys( - srv->base_layer, srv->hostkeys, srv->nhostkeys); - if (userauth_layer) - ssh2_userauth_server_set_transport_layer( - userauth_layer, srv->base_layer); - server_connect_ppl(srv, srv->base_layer); - - } else { - srv->bpp = ssh1_bpp_new(srv->logctx); - server_connect_bpp(srv); - - connection_layer = ssh1_connection_new(&srv->ssh, srv->conf, &srv->cl); - ssh1connection_server_configure(connection_layer, srv->ssc); - server_connect_ppl(srv, connection_layer); - - srv->base_layer = ssh1_login_server_new( - connection_layer, srv->hostkey1, srv->authpolicy, srv->ssc); - server_connect_ppl(srv, srv->base_layer); - } - - /* Connect the base layer - whichever it is - to the BPP, and set - * up its selfptr. */ - srv->base_layer->selfptr = &srv->base_layer; - ssh_ppl_setup_queues(srv->base_layer, &srv->bpp->in_pq, &srv->bpp->out_pq); - -#ifdef FIXME // we probably will want one of these, in the end - srv->pinger = pinger_new(srv->conf, &srv->backend); -#endif - - if (srv->ssc->stunt_open_unconditional_agent_socket) { - char *socketname; - srv->stunt_agentfwd = agentfwd_new(srv->cl, &socketname); - if (srv->stunt_agentfwd) { - logeventf(srv->logctx, "opened unconditional agent socket at %s\n", - socketname); - sfree(socketname); - } - } - - queue_idempotent_callback(&srv->bpp->ic_in_raw); - ssh_ppl_process_queue(srv->base_layer); - - ssh_bpp_free(old_bpp); -} diff --git a/sshserver.h b/sshserver.h deleted file mode 100644 index 5cc393df..00000000 --- a/sshserver.h +++ /dev/null @@ -1,143 +0,0 @@ -typedef struct AuthPolicy AuthPolicy; - -struct SshServerConfig { - const char *application_name; - const char *session_starting_dir; - - RSAKey *rsa_kex_key; - - /* - * In all of these ptrlens, setting the 'ptr' member to NULL means - * that we're not overriding the default configuration. - */ - ptrlen banner; /* default here is 'no banner' */ - ptrlen kex_override[NKEXLIST]; - - bool exit_signal_numeric; /* mimic an old server bug */ - - unsigned long ssh1_cipher_mask; - bool ssh1_allow_compression; - bool bare_connection; - - bool stunt_pretend_to_accept_any_pubkey; - bool stunt_open_unconditional_agent_socket; -}; - -Plug *ssh_server_plug( - Conf *conf, const SshServerConfig *ssc, - ssh_key *const *hostkeys, int nhostkeys, - RSAKey *hostkey1, AuthPolicy *authpolicy, LogPolicy *logpolicy, - const SftpServerVtable *sftpserver_vt); -void ssh_server_start(Plug *plug, Socket *socket); - -void server_instance_terminated(LogPolicy *logpolicy); -void platform_logevent(const char *msg); - -#define AUTHMETHODS(X) \ - X(NONE) \ - X(PASSWORD) \ - X(PUBLICKEY) \ - X(KBDINT) \ - X(TIS) \ - X(CRYPTOCARD) \ - /* end of list */ - -#define AUTHMETHOD_BIT_INDEX(name) AUTHMETHOD_BIT_INDEX_##name, -enum { AUTHMETHODS(AUTHMETHOD_BIT_INDEX) AUTHMETHOD_BIT_INDEX_dummy }; -#define AUTHMETHOD_BIT_VALUE(name) \ - AUTHMETHOD_##name = 1 << AUTHMETHOD_BIT_INDEX_##name, -enum { AUTHMETHODS(AUTHMETHOD_BIT_VALUE) AUTHMETHOD_BIT_VALUE_dummy }; - -typedef struct AuthKbdInt AuthKbdInt; -typedef struct AuthKbdIntPrompt AuthKbdIntPrompt; -struct AuthKbdInt { - char *title, *instruction; /* both need freeing */ - int nprompts; - AuthKbdIntPrompt *prompts; /* the array itself needs freeing */ -}; -struct AuthKbdIntPrompt { - char *prompt; /* needs freeing */ - bool echo; -}; - -unsigned auth_methods(AuthPolicy *); -bool auth_none(AuthPolicy *, ptrlen username); - -int auth_password(AuthPolicy *, ptrlen username, ptrlen password, - ptrlen *opt_new_password); -/* auth_password returns 1 for 'accepted', 0 for 'rejected', and 2 for - * 'ok but now you need to change your password' */ - -bool auth_publickey(AuthPolicy *, ptrlen username, ptrlen public_blob); -/* auth_publickey_ssh1 must return the whole public key given the modulus, - * because the SSH-1 client never transmits the exponent over the wire. - * The key remains owned by the AuthPolicy. */ - -AuthKbdInt *auth_kbdint_prompts(AuthPolicy *, ptrlen username); -/* auth_kbdint_prompts returns NULL to trigger auth failure */ -int auth_kbdint_responses(AuthPolicy *, const ptrlen *responses); -/* auth_kbdint_responses returns >0 for success, <0 for failure, and 0 - * to indicate that we haven't decided yet and further prompts are - * coming */ - -/* The very similar SSH-1 TIS and CryptoCard methods are combined into - * a single API for AuthPolicy, which takes a method argument */ -char *auth_ssh1int_challenge(AuthPolicy *, unsigned method, ptrlen username); -bool auth_ssh1int_response(AuthPolicy *, ptrlen response); - -RSAKey *auth_publickey_ssh1( - AuthPolicy *ap, ptrlen username, mp_int *rsa_modulus); -/* auth_successful returns false if further authentication is needed */ -bool auth_successful(AuthPolicy *, ptrlen username, unsigned method); - -PacketProtocolLayer *ssh2_userauth_server_new( - PacketProtocolLayer *successor_layer, AuthPolicy *authpolicy, - const SshServerConfig *ssc); -void ssh2_userauth_server_set_transport_layer( - PacketProtocolLayer *userauth, PacketProtocolLayer *transport); - -void ssh2connection_server_configure( - PacketProtocolLayer *ppl, const SftpServerVtable *sftpserver_vt, - const SshServerConfig *ssc); -void ssh1connection_server_configure( - PacketProtocolLayer *ppl, const SshServerConfig *ssc); - -PacketProtocolLayer *ssh1_login_server_new( - PacketProtocolLayer *successor_layer, RSAKey *hostkey, - AuthPolicy *authpolicy, const SshServerConfig *ssc); - -Channel *sesschan_new(SshChannel *c, LogContext *logctx, - const SftpServerVtable *sftpserver_vt, - const SshServerConfig *ssc); - -Backend *pty_backend_create( - Seat *seat, LogContext *logctx, Conf *conf, char **argv, const char *cmd, - struct ssh_ttymodes ttymodes, bool pipes_instead_of_pty, const char *dir, - const char *const *env_vars_to_unset); -int pty_backend_exit_signum(Backend *be); -ptrlen pty_backend_exit_signame(Backend *be, char **aux_msg); - -/* - * Establish a listening X server. Return value is the _number_ of - * Sockets that it established pointing at the given Plug. (0 - * indicates complete failure.) The socket pointers themselves are - * written into sockets[], up to a possible total of MAX_X11_SOCKETS. - * - * The supplied Conf has necessary environment variables written into - * it. (And is also used to open the port listeners, though that - * shouldn't affect anything.) - */ -#define MAX_X11_SOCKETS 2 -int platform_make_x11_server(Plug *plug, const char *progname, int mindisp, - const char *screen_number_suffix, - ptrlen authproto, ptrlen authdata, - Socket **sockets, Conf *conf); - -Conf *make_ssh_server_conf(void); - -/* Provided by Unix front end programs to uxsftpserver.c */ -void make_unix_sftp_filehandle_key(void *data, size_t size); - -typedef struct agentfwd agentfwd; -agentfwd *agentfwd_new(ConnectionLayer *cl, char **socketname_out); -void agentfwd_free(agentfwd *agent); diff --git a/sshshare.c b/sshshare.c deleted file mode 100644 index b5da8657..00000000 --- a/sshshare.c +++ /dev/null @@ -1,2180 +0,0 @@ -/* - * Support for SSH connection sharing, i.e. permitting one PuTTY to - * open its own channels over the SSH session being run by another. - */ - -/* - * Discussion and technical documentation - * ====================================== - * - * The basic strategy for PuTTY's implementation of SSH connection - * sharing is to have a single 'upstream' PuTTY process, which manages - * the real SSH connection and all the cryptography, and then zero or - * more 'downstream' PuTTYs, which never talk to the real host but - * only talk to the upstream through local IPC (Unix-domain sockets or - * Windows named pipes). - * - * The downstreams communicate with the upstream using a protocol - * derived from SSH itself, which I'll document in detail below. In - * brief, though: the downstream->upstream protocol uses a trivial - * binary packet protocol (just length/type/data) to encapsulate - * unencrypted SSH messages, and downstreams talk to the upstream more - * or less as if it was an SSH server itself. (So downstreams can - * themselves open multiple SSH channels, for example, by sending - * multiple SSH2_MSG_CHANNEL_OPENs; they can send CHANNEL_REQUESTs of - * their choice within each channel, and they handle their own - * WINDOW_ADJUST messages.) - * - * The upstream would ideally handle these downstreams by just putting - * their messages into the queue for proper SSH-2 encapsulation and - * encryption and sending them straight on to the server. However, - * that's not quite feasible as written, because client-side channel - * IDs could easily conflict (between multiple downstreams, or between - * a downstream and the upstream). To protect against that, the - * upstream rewrites the client-side channel IDs in messages it passes - * on to the server, so that it's performing what you might describe - * as 'channel-number NAT'. Then the upstream remembers which of its - * own channel IDs are channels it's managing itself, and which are - * placeholders associated with a particular downstream, so that when - * replies come in from the server they can be sent on to the relevant - * downstream (after un-NATting the channel number, of course). - * - * Global requests from downstreams are only accepted if the upstream - * knows what to do about them; currently the only such requests are - * the ones having to do with remote-to-local port forwarding (in - * which, again, the upstream remembers that some of the forwardings - * it's asked the server to set up were on behalf of particular - * downstreams, and sends the incoming CHANNEL_OPENs to those - * downstreams when connections come in). - * - * Other fiddly pieces of this mechanism are X forwarding and - * (OpenSSH-style) agent forwarding. Both of these have a fundamental - * problem arising from the protocol design: that the CHANNEL_OPEN - * from the server introducing a forwarded connection does not carry - * any indication of which session channel gave rise to it; so if - * session channels from multiple downstreams enable those forwarding - * methods, it's hard for the upstream to know which downstream to - * send the resulting connections back to. - * - * For X forwarding, we can work around this in a really painful way - * by using the fake X11 authorisation data sent to the server as part - * of the forwarding setup: upstream ensures that every X forwarding - * request carries distinguishable fake auth data, and then when X - * connections come in it waits to see the auth data in the X11 setup - * message before it decides which downstream to pass the connection - * on to. - * - * For agent forwarding, that workaround is unavailable. As a result, - * this system (and, as far as I can think of, any other system too) - * has the fundamental constraint that it can only forward one SSH - * agent - it can't forward two agents to different session channels. - * So downstreams can request agent forwarding if they like, but if - * they do, they'll get whatever SSH agent is known to the upstream - * (if any) forwarded to their sessions. - * - * Downstream-to-upstream protocol - * ------------------------------- - * - * Here I document in detail the protocol spoken between PuTTY - * downstreams and upstreams over local IPC. The IPC mechanism can - * vary between host platforms, but the protocol is the same. - * - * The protocol commences with a version exchange which is exactly - * like the SSH-2 one, in that each side sends a single line of text - * of the form - * - * -- [comments] \r\n - * - * The only difference is that in real SSH-2, is the string - * "SSH", whereas in this protocol the string is - * "SSHCONNECTION@putty.projects.tartarus.org". - * - * (The SSH RFCs allow many protocol-level identifier namespaces to be - * extended by implementors without central standardisation as long as - * they suffix "@" and a domain name they control to their new ids. - * RFC 4253 does not define this particular name to be changeable at - * all, but I like to think this is obviously how it would have done - * so if the working group had foreseen the need :-) - * - * Thereafter, all data exchanged consists of a sequence of binary - * packets concatenated end-to-end, each of which is of the form - * - * uint32 length of packet, N - * byte[N] N bytes of packet data - * - * and, since these are SSH-2 messages, the first data byte is taken - * to be the packet type code. - * - * These messages are interpreted as those of an SSH connection, after - * userauth completes, and without any repeat key exchange. - * Specifically, any message from the SSH Connection Protocol is - * permitted, and also SSH_MSG_IGNORE, SSH_MSG_DEBUG, - * SSH_MSG_DISCONNECT and SSH_MSG_UNIMPLEMENTED from the SSH Transport - * Protocol. - * - * This protocol imposes a few additional requirements, over and above - * those of the standard SSH Connection Protocol: - * - * Message sizes are not permitted to exceed 0x4010 (16400) bytes, - * including their length header. - * - * When the server (i.e. really the PuTTY upstream) sends - * SSH_MSG_CHANNEL_OPEN with channel type "x11", and the client - * (downstream) responds with SSH_MSG_CHANNEL_OPEN_CONFIRMATION, that - * confirmation message MUST include an initial window size of at - * least 256. (Rationale: this is a bit of a fudge which makes it - * easier, by eliminating the possibility of nasty edge cases, for an - * upstream to arrange not to pass the CHANNEL_OPEN on to downstream - * until after it's seen the X11 auth data to decide which downstream - * it needs to go to.) - */ - -#include -#include -#include -#include -#include - -#include "putty.h" -#include "tree234.h" -#include "ssh.h" -#include "sshcr.h" - -struct ssh_sharing_state { - char *sockname; /* the socket name, kept for cleanup */ - Socket *listensock; /* the master listening Socket */ - tree234 *connections; /* holds ssh_sharing_connstates */ - unsigned nextid; /* preferred id for next connstate */ - ConnectionLayer *cl; /* instance of the ssh connection layer */ - char *server_verstring; /* server version string after "SSH-" */ - - Plug plug; -}; - -struct share_globreq; - -struct ssh_sharing_connstate { - unsigned id; /* used to identify this downstream in log messages */ - - Socket *sock; /* the Socket for this connection */ - struct ssh_sharing_state *parent; - - int crLine; /* coroutine state for share_receive */ - - bool sent_verstring, got_verstring; - int curr_packetlen; - - unsigned char recvbuf[0x4010]; - size_t recvlen; - - /* - * Assorted state we have to remember about this downstream, so - * that we can clean it up appropriately when the downstream goes - * away. - */ - - /* Channels which don't have a downstream id, i.e. we've passed a - * CHANNEL_OPEN down from the server but not had an - * OPEN_CONFIRMATION or OPEN_FAILURE back. If downstream goes - * away, we respond to all of these with OPEN_FAILURE. */ - tree234 *halfchannels; /* stores 'struct share_halfchannel' */ - - /* Channels which do have a downstream id. We need to index these - * by both server id and upstream id, so we can find a channel - * when handling either an upward or a downward message referring - * to it. */ - tree234 *channels_by_us; /* stores 'struct share_channel' */ - tree234 *channels_by_server; /* stores 'struct share_channel' */ - - /* Another class of channel which doesn't have a downstream id. - * The difference between these and halfchannels is that xchannels - * do have an *upstream* id, because upstream has already accepted - * the channel request from the server. This arises in the case of - * X forwarding, where we have to accept the request and read the - * X authorisation data before we know whether the channel needs - * to be forwarded to a downstream. */ - tree234 *xchannels_by_us; /* stores 'struct share_xchannel' */ - tree234 *xchannels_by_server; /* stores 'struct share_xchannel' */ - - /* Remote port forwarding requests in force. */ - tree234 *forwardings; /* stores 'struct share_forwarding' */ - - /* Global requests we've sent on to the server, pending replies. */ - struct share_globreq *globreq_head, *globreq_tail; - - Plug plug; -}; - -struct share_halfchannel { - unsigned server_id; -}; - -/* States of a share_channel. */ -enum { - OPEN, - SENT_CLOSE, - RCVD_CLOSE, - /* Downstream has sent CHANNEL_OPEN but server hasn't replied yet. - * If downstream goes away when a channel is in this state, we - * must wait for the server's response before starting to send - * CLOSE. Channels in this state are also not held in - * channels_by_server, because their server_id field is - * meaningless. */ - UNACKNOWLEDGED -}; - -struct share_channel { - unsigned downstream_id, upstream_id, server_id; - int downstream_maxpkt; - int state; - /* - * Some channels (specifically, channels on which downstream has - * sent "x11-req") have the additional function of storing a set - * of downstream X authorisation data and a handle to an upstream - * fake set. - */ - struct X11FakeAuth *x11_auth_upstream; - int x11_auth_proto; - char *x11_auth_data; - int x11_auth_datalen; - bool x11_one_shot; -}; - -struct share_forwarding { - char *host; - int port; - bool active; /* has the server sent REQUEST_SUCCESS? */ - struct ssh_rportfwd *rpf; -}; - -struct share_xchannel_message { - struct share_xchannel_message *next; - int type; - unsigned char *data; - int datalen; -}; - -struct share_xchannel { - unsigned upstream_id, server_id; - - /* - * xchannels come in two flavours: live and dead. Live ones are - * waiting for an OPEN_CONFIRMATION or OPEN_FAILURE from - * downstream; dead ones have had an OPEN_FAILURE, so they only - * exist as a means of letting us conveniently respond to further - * channel messages from the server until such time as the server - * sends us CHANNEL_CLOSE. - */ - bool live; - - /* - * When we receive OPEN_CONFIRMATION, we will need to send a - * WINDOW_ADJUST to the server to synchronise the windows. For - * this purpose we need to know what window we have so far offered - * the server. We record this as exactly the value in the - * OPEN_CONFIRMATION that upstream sent us, adjusted by the amount - * by which the two X greetings differed in length. - */ - int window; - - /* - * Linked list of SSH messages from the server relating to this - * channel, which we queue up until downstream sends us an - * OPEN_CONFIRMATION and we can belatedly send them all on. - */ - struct share_xchannel_message *msghead, *msgtail; -}; - -enum { - GLOBREQ_TCPIP_FORWARD, - GLOBREQ_CANCEL_TCPIP_FORWARD -}; - -struct share_globreq { - struct share_globreq *next; - int type; - bool want_reply; - struct share_forwarding *fwd; -}; - -static int share_connstate_cmp(void *av, void *bv) -{ - const struct ssh_sharing_connstate *a = - (const struct ssh_sharing_connstate *)av; - const struct ssh_sharing_connstate *b = - (const struct ssh_sharing_connstate *)bv; - - if (a->id < b->id) - return -1; - else if (a->id > b->id) - return +1; - else - return 0; -} - -static unsigned share_find_unused_id -(struct ssh_sharing_state *sharestate, unsigned first) -{ - int low_orig, low, mid, high, high_orig; - struct ssh_sharing_connstate *cs; - unsigned ret; - - /* - * Find the lowest unused downstream ID greater or equal to - * 'first'. - * - * Begin by seeing if 'first' itself is available. If it is, we'll - * just return it; if it's already in the tree, we'll find the - * tree index where it appears and use that for the next stage. - */ - { - struct ssh_sharing_connstate dummy; - dummy.id = first; - cs = findrelpos234(sharestate->connections, &dummy, NULL, - REL234_GE, &low_orig); - if (!cs) - return first; - } - - /* - * Now binary-search using the counted B-tree, to find the largest - * ID which is in a contiguous sequence from the beginning of that - * range. - */ - low = low_orig; - high = high_orig = count234(sharestate->connections); - while (high - low > 1) { - mid = (high + low) / 2; - cs = index234(sharestate->connections, mid); - if (cs->id == first + (mid - low_orig)) - low = mid; /* this one is still in the sequence */ - else - high = mid; /* this one is past the end */ - } - - /* - * Now low is the tree index of the largest ID in the initial - * sequence. So the return value is one more than low's id, and we - * know low's id is given by the formula in the binary search loop - * above. - * - * (If an SSH connection went on for _enormously_ long, we might - * reach a point where all ids from 'first' to UINT_MAX were in - * use. In that situation the formula below would wrap round by - * one and return zero, which is conveniently the right way to - * signal 'no id available' from this function.) - */ - ret = first + (low - low_orig) + 1; - { - struct ssh_sharing_connstate dummy; - dummy.id = ret; - assert(NULL == find234(sharestate->connections, &dummy, NULL)); - } - return ret; -} - -static int share_halfchannel_cmp(void *av, void *bv) -{ - const struct share_halfchannel *a = (const struct share_halfchannel *)av; - const struct share_halfchannel *b = (const struct share_halfchannel *)bv; - - if (a->server_id < b->server_id) - return -1; - else if (a->server_id > b->server_id) - return +1; - else - return 0; -} - -static int share_channel_us_cmp(void *av, void *bv) -{ - const struct share_channel *a = (const struct share_channel *)av; - const struct share_channel *b = (const struct share_channel *)bv; - - if (a->upstream_id < b->upstream_id) - return -1; - else if (a->upstream_id > b->upstream_id) - return +1; - else - return 0; -} - -static int share_channel_server_cmp(void *av, void *bv) -{ - const struct share_channel *a = (const struct share_channel *)av; - const struct share_channel *b = (const struct share_channel *)bv; - - if (a->server_id < b->server_id) - return -1; - else if (a->server_id > b->server_id) - return +1; - else - return 0; -} - -static int share_xchannel_us_cmp(void *av, void *bv) -{ - const struct share_xchannel *a = (const struct share_xchannel *)av; - const struct share_xchannel *b = (const struct share_xchannel *)bv; - - if (a->upstream_id < b->upstream_id) - return -1; - else if (a->upstream_id > b->upstream_id) - return +1; - else - return 0; -} - -static int share_xchannel_server_cmp(void *av, void *bv) -{ - const struct share_xchannel *a = (const struct share_xchannel *)av; - const struct share_xchannel *b = (const struct share_xchannel *)bv; - - if (a->server_id < b->server_id) - return -1; - else if (a->server_id > b->server_id) - return +1; - else - return 0; -} - -static int share_forwarding_cmp(void *av, void *bv) -{ - const struct share_forwarding *a = (const struct share_forwarding *)av; - const struct share_forwarding *b = (const struct share_forwarding *)bv; - int i; - - if ((i = strcmp(a->host, b->host)) != 0) - return i; - else if (a->port < b->port) - return -1; - else if (a->port > b->port) - return +1; - else - return 0; -} - -static void share_xchannel_free(struct share_xchannel *xc) -{ - while (xc->msghead) { - struct share_xchannel_message *tmp = xc->msghead; - xc->msghead = tmp->next; - sfree(tmp); - } - sfree(xc); -} - -static void share_connstate_free(struct ssh_sharing_connstate *cs) -{ - struct share_halfchannel *hc; - struct share_xchannel *xc; - struct share_channel *chan; - struct share_forwarding *fwd; - - while ((hc = (struct share_halfchannel *) - delpos234(cs->halfchannels, 0)) != NULL) - sfree(hc); - freetree234(cs->halfchannels); - - /* All channels live in 'channels_by_us' but only some in - * 'channels_by_server', so we use the former to find the list of - * ones to free */ - freetree234(cs->channels_by_server); - while ((chan = (struct share_channel *) - delpos234(cs->channels_by_us, 0)) != NULL) - sfree(chan); - freetree234(cs->channels_by_us); - - /* But every xchannel is in both trees, so it doesn't matter which - * we use to free them. */ - while ((xc = (struct share_xchannel *) - delpos234(cs->xchannels_by_us, 0)) != NULL) - share_xchannel_free(xc); - freetree234(cs->xchannels_by_us); - freetree234(cs->xchannels_by_server); - - while ((fwd = (struct share_forwarding *) - delpos234(cs->forwardings, 0)) != NULL) - sfree(fwd); - freetree234(cs->forwardings); - - while (cs->globreq_head) { - struct share_globreq *globreq = cs->globreq_head; - cs->globreq_head = cs->globreq_head->next; - sfree(globreq); - } - - if (cs->sock) - sk_close(cs->sock); - - sfree(cs); -} - -void sharestate_free(ssh_sharing_state *sharestate) -{ - struct ssh_sharing_connstate *cs; - - platform_ssh_share_cleanup(sharestate->sockname); - - while ((cs = (struct ssh_sharing_connstate *) - delpos234(sharestate->connections, 0)) != NULL) { - share_connstate_free(cs); - } - freetree234(sharestate->connections); - if (sharestate->listensock) { - sk_close(sharestate->listensock); - sharestate->listensock = NULL; - } - sfree(sharestate->server_verstring); - sfree(sharestate->sockname); - sfree(sharestate); -} - -static struct share_halfchannel *share_add_halfchannel - (struct ssh_sharing_connstate *cs, unsigned server_id) -{ - struct share_halfchannel *hc = snew(struct share_halfchannel); - hc->server_id = server_id; - if (add234(cs->halfchannels, hc) != hc) { - /* Duplicate?! */ - sfree(hc); - return NULL; - } else { - return hc; - } -} - -static struct share_halfchannel *share_find_halfchannel - (struct ssh_sharing_connstate *cs, unsigned server_id) -{ - struct share_halfchannel dummyhc; - dummyhc.server_id = server_id; - return find234(cs->halfchannels, &dummyhc, NULL); -} - -static void share_remove_halfchannel(struct ssh_sharing_connstate *cs, - struct share_halfchannel *hc) -{ - del234(cs->halfchannels, hc); - sfree(hc); -} - -static struct share_channel *share_add_channel - (struct ssh_sharing_connstate *cs, unsigned downstream_id, - unsigned upstream_id, unsigned server_id, int state, int maxpkt) -{ - struct share_channel *chan = snew(struct share_channel); - chan->downstream_id = downstream_id; - chan->upstream_id = upstream_id; - chan->server_id = server_id; - chan->state = state; - chan->downstream_maxpkt = maxpkt; - chan->x11_auth_upstream = NULL; - chan->x11_auth_data = NULL; - chan->x11_auth_proto = -1; - chan->x11_auth_datalen = 0; - chan->x11_one_shot = false; - if (add234(cs->channels_by_us, chan) != chan) { - sfree(chan); - return NULL; - } - if (chan->state != UNACKNOWLEDGED) { - if (add234(cs->channels_by_server, chan) != chan) { - del234(cs->channels_by_us, chan); - sfree(chan); - return NULL; - } - } - return chan; -} - -static void share_channel_set_server_id(struct ssh_sharing_connstate *cs, - struct share_channel *chan, - unsigned server_id, int newstate) -{ - chan->server_id = server_id; - chan->state = newstate; - assert(newstate != UNACKNOWLEDGED); - add234(cs->channels_by_server, chan); -} - -static struct share_channel *share_find_channel_by_upstream - (struct ssh_sharing_connstate *cs, unsigned upstream_id) -{ - struct share_channel dummychan; - dummychan.upstream_id = upstream_id; - return find234(cs->channels_by_us, &dummychan, NULL); -} - -static struct share_channel *share_find_channel_by_server - (struct ssh_sharing_connstate *cs, unsigned server_id) -{ - struct share_channel dummychan; - dummychan.server_id = server_id; - return find234(cs->channels_by_server, &dummychan, NULL); -} - -static void share_remove_channel(struct ssh_sharing_connstate *cs, - struct share_channel *chan) -{ - del234(cs->channels_by_us, chan); - del234(cs->channels_by_server, chan); - if (chan->x11_auth_upstream) - ssh_remove_sharing_x11_display(cs->parent->cl, - chan->x11_auth_upstream); - sfree(chan->x11_auth_data); - sfree(chan); -} - -static struct share_xchannel *share_add_xchannel - (struct ssh_sharing_connstate *cs, - unsigned upstream_id, unsigned server_id) -{ - struct share_xchannel *xc = snew(struct share_xchannel); - xc->upstream_id = upstream_id; - xc->server_id = server_id; - xc->live = true; - xc->msghead = xc->msgtail = NULL; - if (add234(cs->xchannels_by_us, xc) != xc) { - sfree(xc); - return NULL; - } - if (add234(cs->xchannels_by_server, xc) != xc) { - del234(cs->xchannels_by_us, xc); - sfree(xc); - return NULL; - } - return xc; -} - -static struct share_xchannel *share_find_xchannel_by_upstream - (struct ssh_sharing_connstate *cs, unsigned upstream_id) -{ - struct share_xchannel dummyxc; - dummyxc.upstream_id = upstream_id; - return find234(cs->xchannels_by_us, &dummyxc, NULL); -} - -static struct share_xchannel *share_find_xchannel_by_server - (struct ssh_sharing_connstate *cs, unsigned server_id) -{ - struct share_xchannel dummyxc; - dummyxc.server_id = server_id; - return find234(cs->xchannels_by_server, &dummyxc, NULL); -} - -static void share_remove_xchannel(struct ssh_sharing_connstate *cs, - struct share_xchannel *xc) -{ - del234(cs->xchannels_by_us, xc); - del234(cs->xchannels_by_server, xc); - share_xchannel_free(xc); -} - -static struct share_forwarding *share_add_forwarding - (struct ssh_sharing_connstate *cs, - const char *host, int port) -{ - struct share_forwarding *fwd = snew(struct share_forwarding); - fwd->host = dupstr(host); - fwd->port = port; - fwd->active = false; - if (add234(cs->forwardings, fwd) != fwd) { - /* Duplicate?! */ - sfree(fwd); - return NULL; - } - return fwd; -} - -static struct share_forwarding *share_find_forwarding - (struct ssh_sharing_connstate *cs, const char *host, int port) -{ - struct share_forwarding dummyfwd, *ret; - dummyfwd.host = dupstr(host); - dummyfwd.port = port; - ret = find234(cs->forwardings, &dummyfwd, NULL); - sfree(dummyfwd.host); - return ret; -} - -static void share_remove_forwarding(struct ssh_sharing_connstate *cs, - struct share_forwarding *fwd) -{ - del234(cs->forwardings, fwd); - sfree(fwd); -} - -static PRINTF_LIKE(2, 3) void log_downstream(struct ssh_sharing_connstate *cs, - const char *logfmt, ...) -{ - va_list ap; - char *buf; - - va_start(ap, logfmt); - buf = dupvprintf(logfmt, ap); - va_end(ap); - logeventf(cs->parent->cl->logctx, - "Connection sharing downstream #%u: %s", cs->id, buf); - sfree(buf); -} - -static PRINTF_LIKE(2, 3) void log_general(struct ssh_sharing_state *sharestate, - const char *logfmt, ...) -{ - va_list ap; - char *buf; - - va_start(ap, logfmt); - buf = dupvprintf(logfmt, ap); - va_end(ap); - logeventf(sharestate->cl->logctx, "Connection sharing: %s", buf); - sfree(buf); -} - -static void send_packet_to_downstream(struct ssh_sharing_connstate *cs, - int type, const void *pkt, int pktlen, - struct share_channel *chan) -{ - strbuf *packet; - - if (!cs->sock) /* throw away all packets destined for a dead downstream */ - return; - - if (type == SSH2_MSG_CHANNEL_DATA) { - /* - * Special case which we take care of at a low level, so as to - * be sure to apply it in all cases. On rare occasions we - * might find that we have a channel for which the - * downstream's maximum packet size exceeds the max packet - * size we presented to the server on its behalf. (This can - * occur in X11 forwarding, where we have to send _our_ - * CHANNEL_OPEN_CONFIRMATION before we discover which if any - * downstream the channel is destined for, so if that - * downstream turns out to present a smaller max packet size - * then we're in this situation.) - * - * If that happens, we just chop up the packet into pieces and - * send them as separate CHANNEL_DATA packets. - */ - BinarySource src[1]; - unsigned channel; - ptrlen data; - - BinarySource_BARE_INIT(src, pkt, pktlen); - channel = get_uint32(src); - data = get_string(src); - - do { - int this_len = (data.len > chan->downstream_maxpkt ? - chan->downstream_maxpkt : data.len); - - packet = strbuf_new_nm(); - put_uint32(packet, 0); /* placeholder for length field */ - put_byte(packet, type); - put_uint32(packet, channel); - put_uint32(packet, this_len); - put_data(packet, data.ptr, this_len); - data.ptr = (const char *)data.ptr + this_len; - data.len -= this_len; - PUT_32BIT_MSB_FIRST(packet->s, packet->len-4); - sk_write(cs->sock, packet->s, packet->len); - strbuf_free(packet); - } while (data.len > 0); - } else { - /* - * Just do the obvious thing. - */ - packet = strbuf_new_nm(); - put_uint32(packet, 0); /* placeholder for length field */ - put_byte(packet, type); - put_data(packet, pkt, pktlen); - PUT_32BIT_MSB_FIRST(packet->s, packet->len-4); - sk_write(cs->sock, packet->s, packet->len); - strbuf_free(packet); - } -} - -static void share_try_cleanup(struct ssh_sharing_connstate *cs) -{ - int i; - struct share_halfchannel *hc; - struct share_channel *chan; - struct share_forwarding *fwd; - - /* - * Any half-open channels, i.e. those for which we'd received - * CHANNEL_OPEN from the server but not passed back a response - * from downstream, should be responded to with OPEN_FAILURE. - */ - while ((hc = (struct share_halfchannel *) - index234(cs->halfchannels, 0)) != NULL) { - static const char reason[] = "PuTTY downstream no longer available"; - static const char lang[] = "en"; - strbuf *packet; - - packet = strbuf_new(); - put_uint32(packet, hc->server_id); - put_uint32(packet, SSH2_OPEN_CONNECT_FAILED); - put_stringz(packet, reason); - put_stringz(packet, lang); - ssh_send_packet_from_downstream( - cs->parent->cl, cs->id, SSH2_MSG_CHANNEL_OPEN_FAILURE, - packet->s, packet->len, - "cleanup after downstream went away"); - strbuf_free(packet); - - share_remove_halfchannel(cs, hc); - } - - /* - * Any actually open channels should have a CHANNEL_CLOSE sent for - * them, unless we've already done so. We won't be able to - * actually clean them up until CHANNEL_CLOSE comes back from the - * server, though (unless the server happens to have sent a CLOSE - * already). - * - * Another annoying exception is UNACKNOWLEDGED channels, i.e. - * we've _sent_ a CHANNEL_OPEN to the server but not received an - * OPEN_CONFIRMATION or OPEN_FAILURE. We must wait for a reply - * before closing the channel, because until we see that reply we - * won't have the server's channel id to put in the close message. - */ - for (i = 0; (chan = (struct share_channel *) - index234(cs->channels_by_us, i)) != NULL; i++) { - strbuf *packet; - - if (chan->state != SENT_CLOSE && chan->state != UNACKNOWLEDGED) { - packet = strbuf_new(); - put_uint32(packet, chan->server_id); - ssh_send_packet_from_downstream( - cs->parent->cl, cs->id, SSH2_MSG_CHANNEL_CLOSE, - packet->s, packet->len, - "cleanup after downstream went away"); - strbuf_free(packet); - - if (chan->state != RCVD_CLOSE) { - chan->state = SENT_CLOSE; - } else { - /* In this case, we _can_ clear up the channel now. */ - ssh_delete_sharing_channel(cs->parent->cl, chan->upstream_id); - share_remove_channel(cs, chan); - i--; /* don't accidentally skip one as a result */ - } - } - } - - /* - * Any remote port forwardings we're managing on behalf of this - * downstream should be cancelled. Again, we must defer those for - * which we haven't yet seen REQUEST_SUCCESS/FAILURE. - * - * We take a fire-and-forget approach during cleanup, not - * bothering to set want_reply. - */ - for (i = 0; (fwd = (struct share_forwarding *) - index234(cs->forwardings, i)) != NULL; i++) { - if (fwd->active) { - strbuf *packet = strbuf_new(); - put_stringz(packet, "cancel-tcpip-forward"); - put_bool(packet, false); /* !want_reply */ - put_stringz(packet, fwd->host); - put_uint32(packet, fwd->port); - ssh_send_packet_from_downstream( - cs->parent->cl, cs->id, SSH2_MSG_GLOBAL_REQUEST, - packet->s, packet->len, - "cleanup after downstream went away"); - strbuf_free(packet); - - ssh_rportfwd_remove(cs->parent->cl, fwd->rpf); - share_remove_forwarding(cs, fwd); - i--; /* don't accidentally skip one as a result */ - } - } - - if (count234(cs->halfchannels) == 0 && - count234(cs->channels_by_us) == 0 && - count234(cs->forwardings) == 0) { - struct ssh_sharing_state *sharestate = cs->parent; - - /* - * Now we're _really_ done, so we can get rid of cs completely. - */ - del234(sharestate->connections, cs); - log_downstream(cs, "disconnected"); - share_connstate_free(cs); - - /* - * And if this was the last downstream, notify the connection - * layer, because it might now be time to wind up the whole - * SSH connection. - */ - if (count234(sharestate->connections) == 0 && sharestate->cl) - ssh_sharing_no_more_downstreams(sharestate->cl); - } -} - -static void share_begin_cleanup(struct ssh_sharing_connstate *cs) -{ - - sk_close(cs->sock); - cs->sock = NULL; - - share_try_cleanup(cs); -} - -static void share_disconnect(struct ssh_sharing_connstate *cs, - const char *message) -{ - strbuf *packet = strbuf_new(); - put_uint32(packet, SSH2_DISCONNECT_PROTOCOL_ERROR); - put_stringz(packet, message); - put_stringz(packet, "en"); /* language */ - send_packet_to_downstream(cs, SSH2_MSG_DISCONNECT, - packet->s, packet->len, NULL); - strbuf_free(packet); - - share_begin_cleanup(cs); -} - -static void share_closing(Plug *plug, const char *error_msg, int error_code, - bool calling_back) -{ - struct ssh_sharing_connstate *cs = container_of( - plug, struct ssh_sharing_connstate, plug); - - if (error_msg) { -#ifdef BROKEN_PIPE_ERROR_CODE - /* - * Most of the time, we log what went wrong when a downstream - * disappears with a socket error. One exception, though, is - * receiving EPIPE when we haven't received a protocol version - * string from the downstream, because that can happen as a result - * of plink -shareexists (opening the connection and instantly - * closing it again without bothering to read our version string). - * So that one case is not treated as a log-worthy error. - */ - if (error_code == BROKEN_PIPE_ERROR_CODE && !cs->got_verstring) - /* do nothing */; - else -#endif - log_downstream(cs, "Socket error: %s", error_msg); - } - share_begin_cleanup(cs); -} - -/* - * Append a message to the end of an xchannel's queue. - */ -static void share_xchannel_add_message( - struct share_xchannel *xc, int type, const void *data, int len) -{ - struct share_xchannel_message *msg; - - /* - * Allocate the 'struct share_xchannel_message' and the actual - * data in one unit. - */ - msg = snew_plus(struct share_xchannel_message, len); - msg->data = snew_plus_get_aux(msg); - msg->datalen = len; - msg->type = type; - memcpy(msg->data, data, len); - - /* - * Queue it in the xchannel. - */ - if (xc->msgtail) - xc->msgtail->next = msg; - else - xc->msghead = msg; - msg->next = NULL; - xc->msgtail = msg; -} - -void share_dead_xchannel_respond(struct ssh_sharing_connstate *cs, - struct share_xchannel *xc) -{ - /* - * Handle queued incoming messages from the server destined for an - * xchannel which is dead (i.e. downstream sent OPEN_FAILURE). - */ - bool delete = false; - while (xc->msghead) { - struct share_xchannel_message *msg = xc->msghead; - xc->msghead = msg->next; - - if (msg->type == SSH2_MSG_CHANNEL_REQUEST && msg->datalen > 4) { - /* - * A CHANNEL_REQUEST is responded to by sending - * CHANNEL_FAILURE, if it has want_reply set. - */ - BinarySource src[1]; - BinarySource_BARE_INIT(src, msg->data, msg->datalen); - get_uint32(src); /* skip channel id */ - get_string(src); /* skip request type */ - if (get_bool(src)) { - strbuf *packet = strbuf_new(); - put_uint32(packet, xc->server_id); - ssh_send_packet_from_downstream - (cs->parent->cl, cs->id, SSH2_MSG_CHANNEL_FAILURE, - packet->s, packet->len, - "downstream refused X channel open"); - strbuf_free(packet); - } - } else if (msg->type == SSH2_MSG_CHANNEL_CLOSE) { - /* - * On CHANNEL_CLOSE we can discard the channel completely. - */ - delete = true; - } - - sfree(msg); - } - xc->msgtail = NULL; - if (delete) { - ssh_delete_sharing_channel(cs->parent->cl, xc->upstream_id); - share_remove_xchannel(cs, xc); - } -} - -void share_xchannel_confirmation(struct ssh_sharing_connstate *cs, - struct share_xchannel *xc, - struct share_channel *chan, - unsigned downstream_window) -{ - strbuf *packet; - - /* - * Send all the queued messages downstream. - */ - while (xc->msghead) { - struct share_xchannel_message *msg = xc->msghead; - xc->msghead = msg->next; - - if (msg->datalen >= 4) - PUT_32BIT_MSB_FIRST(msg->data, chan->downstream_id); - send_packet_to_downstream(cs, msg->type, - msg->data, msg->datalen, chan); - - sfree(msg); - } - - /* - * Send a WINDOW_ADJUST back upstream, to synchronise the window - * size downstream thinks it's presented with the one we've - * actually presented. - */ - packet = strbuf_new(); - put_uint32(packet, xc->server_id); - put_uint32(packet, downstream_window - xc->window); - ssh_send_packet_from_downstream( - cs->parent->cl, cs->id, SSH2_MSG_CHANNEL_WINDOW_ADJUST, - packet->s, packet->len, - "window adjustment after downstream accepted X channel"); - strbuf_free(packet); -} - -void share_xchannel_failure(struct ssh_sharing_connstate *cs, - struct share_xchannel *xc) -{ - /* - * If downstream refuses to open our X channel at all for some - * reason, we must respond by sending an emergency CLOSE upstream. - */ - strbuf *packet = strbuf_new(); - put_uint32(packet, xc->server_id); - ssh_send_packet_from_downstream( - cs->parent->cl, cs->id, SSH2_MSG_CHANNEL_CLOSE, - packet->s, packet->len, - "downstream refused X channel open"); - strbuf_free(packet); - - /* - * Now mark the xchannel as dead, and respond to anything sent on - * it until we see CLOSE for it in turn. - */ - xc->live = false; - share_dead_xchannel_respond(cs, xc); -} - -void share_setup_x11_channel(ssh_sharing_connstate *cs, share_channel *chan, - unsigned upstream_id, unsigned server_id, - unsigned server_currwin, unsigned server_maxpkt, - unsigned client_adjusted_window, - const char *peer_addr, int peer_port, int endian, - int protomajor, int protominor, - const void *initial_data, int initial_len) -{ - struct share_xchannel *xc; - void *greeting; - int greeting_len; - strbuf *packet; - - /* - * Create an xchannel containing data we've already received from - * the X client, and preload it with a CHANNEL_DATA message - * containing our own made-up authorisation greeting and any - * additional data sent from the server so far. - */ - xc = share_add_xchannel(cs, upstream_id, server_id); - greeting = x11_make_greeting(endian, protomajor, protominor, - chan->x11_auth_proto, - chan->x11_auth_data, chan->x11_auth_datalen, - peer_addr, peer_port, &greeting_len); - packet = strbuf_new_nm(); - put_uint32(packet, 0); /* leave the channel id field unfilled - we - * don't know the downstream id yet */ - put_uint32(packet, greeting_len + initial_len); - put_data(packet, greeting, greeting_len); - put_data(packet, initial_data, initial_len); - sfree(greeting); - share_xchannel_add_message(xc, SSH2_MSG_CHANNEL_DATA, - packet->s, packet->len); - strbuf_free(packet); - - xc->window = client_adjusted_window + greeting_len; - - /* - * Send on a CHANNEL_OPEN to downstream. - */ - packet = strbuf_new(); - put_stringz(packet, "x11"); - put_uint32(packet, server_id); - put_uint32(packet, server_currwin); - put_uint32(packet, server_maxpkt); - put_stringz(packet, peer_addr); - put_uint32(packet, peer_port); - send_packet_to_downstream(cs, SSH2_MSG_CHANNEL_OPEN, - packet->s, packet->len, NULL); - strbuf_free(packet); - - /* - * If this was a once-only X forwarding, clean it up now. - */ - if (chan->x11_one_shot) { - ssh_remove_sharing_x11_display(cs->parent->cl, - chan->x11_auth_upstream); - chan->x11_auth_upstream = NULL; - sfree(chan->x11_auth_data); - chan->x11_auth_proto = -1; - chan->x11_auth_datalen = 0; - chan->x11_one_shot = false; - } -} - -void share_got_pkt_from_server(ssh_sharing_connstate *cs, int type, - const void *vpkt, int pktlen) -{ - const unsigned char *pkt = (const unsigned char *)vpkt; - struct share_globreq *globreq; - size_t id_pos; - unsigned upstream_id, server_id; - struct share_channel *chan; - struct share_xchannel *xc; - BinarySource src[1]; - - BinarySource_BARE_INIT(src, pkt, pktlen); - - switch (type) { - case SSH2_MSG_REQUEST_SUCCESS: - case SSH2_MSG_REQUEST_FAILURE: - globreq = cs->globreq_head; - assert(globreq); /* should match the queue in ssh.c */ - if (globreq->type == GLOBREQ_TCPIP_FORWARD) { - if (type == SSH2_MSG_REQUEST_FAILURE) { - share_remove_forwarding(cs, globreq->fwd); - } else { - globreq->fwd->active = true; - } - } else if (globreq->type == GLOBREQ_CANCEL_TCPIP_FORWARD) { - if (type == SSH2_MSG_REQUEST_SUCCESS) { - share_remove_forwarding(cs, globreq->fwd); - } - } - if (globreq->want_reply) { - send_packet_to_downstream(cs, type, pkt, pktlen, NULL); - } - cs->globreq_head = globreq->next; - sfree(globreq); - if (cs->globreq_head == NULL) - cs->globreq_tail = NULL; - - if (!cs->sock) { - /* Retry cleaning up this connection, in case that reply - * was the last thing we were waiting for. */ - share_try_cleanup(cs); - } - - break; - - case SSH2_MSG_CHANNEL_OPEN: - get_string(src); - server_id = get_uint32(src); - assert(!get_err(src)); - share_add_halfchannel(cs, server_id); - - send_packet_to_downstream(cs, type, pkt, pktlen, NULL); - break; - - case SSH2_MSG_CHANNEL_OPEN_CONFIRMATION: - case SSH2_MSG_CHANNEL_OPEN_FAILURE: - case SSH2_MSG_CHANNEL_CLOSE: - case SSH2_MSG_CHANNEL_WINDOW_ADJUST: - case SSH2_MSG_CHANNEL_DATA: - case SSH2_MSG_CHANNEL_EXTENDED_DATA: - case SSH2_MSG_CHANNEL_EOF: - case SSH2_MSG_CHANNEL_REQUEST: - case SSH2_MSG_CHANNEL_SUCCESS: - case SSH2_MSG_CHANNEL_FAILURE: - /* - * All these messages have the recipient channel id as the - * first uint32 field in the packet. Substitute the downstream - * channel id for our one and pass the packet downstream. - */ - id_pos = src->pos; - upstream_id = get_uint32(src); - if ((chan = share_find_channel_by_upstream(cs, upstream_id)) != NULL) { - /* - * The normal case: this id refers to an open channel. - */ - unsigned char *rewritten = snewn(pktlen, unsigned char); - memcpy(rewritten, pkt, pktlen); - PUT_32BIT_MSB_FIRST(rewritten + id_pos, chan->downstream_id); - send_packet_to_downstream(cs, type, rewritten, pktlen, chan); - sfree(rewritten); - - /* - * Update the channel state, for messages that need it. - */ - if (type == SSH2_MSG_CHANNEL_OPEN_CONFIRMATION) { - if (chan->state == UNACKNOWLEDGED && pktlen >= 8) { - share_channel_set_server_id( - cs, chan, GET_32BIT_MSB_FIRST(pkt+4), OPEN); - if (!cs->sock) { - /* Retry cleaning up this connection, so that we - * can send an immediate CLOSE on this channel for - * which we now know the server id. */ - share_try_cleanup(cs); - } - } - } else if (type == SSH2_MSG_CHANNEL_OPEN_FAILURE) { - ssh_delete_sharing_channel(cs->parent->cl, chan->upstream_id); - share_remove_channel(cs, chan); - } else if (type == SSH2_MSG_CHANNEL_CLOSE) { - if (chan->state == SENT_CLOSE) { - ssh_delete_sharing_channel(cs->parent->cl, - chan->upstream_id); - share_remove_channel(cs, chan); - if (!cs->sock) { - /* Retry cleaning up this connection, in case this - * channel closure was the last thing we were - * waiting for. */ - share_try_cleanup(cs); - } - } else { - chan->state = RCVD_CLOSE; - } - } - } else if ((xc = share_find_xchannel_by_upstream(cs, upstream_id)) - != NULL) { - /* - * The unusual case: this id refers to an xchannel. Add it - * to the xchannel's queue. - */ - share_xchannel_add_message(xc, type, pkt, pktlen); - - /* If the xchannel is dead, then also respond to it (which - * may involve deleting the channel). */ - if (!xc->live) - share_dead_xchannel_respond(cs, xc); - } - break; - - default: - unreachable("This packet type should never have come from ssh.c"); - } -} - -static void share_got_pkt_from_downstream(struct ssh_sharing_connstate *cs, - int type, - unsigned char *pkt, int pktlen) -{ - ptrlen request_name; - struct share_forwarding *fwd; - size_t id_pos; - unsigned maxpkt; - unsigned old_id, new_id, server_id; - struct share_globreq *globreq; - struct share_channel *chan; - struct share_halfchannel *hc; - struct share_xchannel *xc; - strbuf *packet; - char *err = NULL; - BinarySource src[1]; - size_t wantreplypos; - bool orig_wantreply; - - BinarySource_BARE_INIT(src, pkt, pktlen); - - switch (type) { - case SSH2_MSG_DISCONNECT: - /* - * This message stops here: if downstream is disconnecting - * from us, that doesn't mean we want to disconnect from the - * SSH server. Close the downstream connection and start - * cleanup. - */ - share_begin_cleanup(cs); - break; - - case SSH2_MSG_GLOBAL_REQUEST: - /* - * The only global requests we understand are "tcpip-forward" - * and "cancel-tcpip-forward". Since those require us to - * maintain state, we must assume that other global requests - * will probably require that too, and so we don't forward on - * any request we don't understand. - */ - request_name = get_string(src); - wantreplypos = src->pos; - orig_wantreply = get_bool(src); - - if (ptrlen_eq_string(request_name, "tcpip-forward")) { - ptrlen hostpl; - char *host; - int port; - struct ssh_rportfwd *rpf; - - /* - * Pick the packet apart to find the want_reply field and - * the host/port we're going to ask to listen on. - */ - hostpl = get_string(src); - port = toint(get_uint32(src)); - if (get_err(src)) { - err = dupprintf("Truncated GLOBAL_REQUEST packet"); - goto confused; - } - host = mkstr(hostpl); - - /* - * See if we can allocate space in ssh.c's tree of remote - * port forwardings. If we can't, it's because another - * client sharing this connection has already allocated - * the identical port forwarding, so we take it on - * ourselves to manufacture a failure packet and send it - * back to downstream. - */ - rpf = ssh_rportfwd_alloc( - cs->parent->cl, host, port, NULL, 0, 0, NULL, NULL, cs); - if (!rpf) { - if (orig_wantreply) { - send_packet_to_downstream(cs, SSH2_MSG_REQUEST_FAILURE, - "", 0, NULL); - } - } else { - /* - * We've managed to make space for this forwarding - * locally. Pass the request on to the SSH server, but - * set want_reply even if it wasn't originally set, so - * that we know whether this forwarding needs to be - * cleaned up if downstream goes away. - */ - pkt[wantreplypos] = 1; - ssh_send_packet_from_downstream - (cs->parent->cl, cs->id, type, pkt, pktlen, - orig_wantreply ? NULL : "upstream added want_reply flag"); - fwd = share_add_forwarding(cs, host, port); - ssh_sharing_queue_global_request(cs->parent->cl, cs); - - if (fwd) { - globreq = snew(struct share_globreq); - globreq->next = NULL; - if (cs->globreq_tail) - cs->globreq_tail->next = globreq; - else - cs->globreq_head = globreq; - globreq->fwd = fwd; - globreq->want_reply = orig_wantreply; - globreq->type = GLOBREQ_TCPIP_FORWARD; - - fwd->rpf = rpf; - } - } - - sfree(host); - } else if (ptrlen_eq_string(request_name, "cancel-tcpip-forward")) { - ptrlen hostpl; - char *host; - int port; - struct share_forwarding *fwd; - - /* - * Pick the packet apart to find the want_reply field and - * the host/port we're going to ask to listen on. - */ - hostpl = get_string(src); - port = toint(get_uint32(src)); - if (get_err(src)) { - err = dupprintf("Truncated GLOBAL_REQUEST packet"); - goto confused; - } - host = mkstr(hostpl); - - /* - * Look up the existing forwarding with these details. - */ - fwd = share_find_forwarding(cs, host, port); - if (!fwd) { - if (orig_wantreply) { - send_packet_to_downstream(cs, SSH2_MSG_REQUEST_FAILURE, - "", 0, NULL); - } - } else { - /* - * Tell ssh.c to stop sending us channel-opens for - * this forwarding. - */ - ssh_rportfwd_remove(cs->parent->cl, fwd->rpf); - - /* - * Pass the cancel request on to the SSH server, but - * set want_reply even if it wasn't originally set, so - * that _we_ know whether the forwarding has been - * deleted even if downstream doesn't want to know. - */ - pkt[wantreplypos] = 1; - ssh_send_packet_from_downstream - (cs->parent->cl, cs->id, type, pkt, pktlen, - orig_wantreply ? NULL : "upstream added want_reply flag"); - ssh_sharing_queue_global_request(cs->parent->cl, cs); - - /* - * And queue a globreq so that when the reply comes - * back we know to cancel it. - */ - globreq = snew(struct share_globreq); - globreq->next = NULL; - if (cs->globreq_tail) - cs->globreq_tail->next = globreq; - else - cs->globreq_head = globreq; - globreq->fwd = fwd; - globreq->want_reply = orig_wantreply; - globreq->type = GLOBREQ_CANCEL_TCPIP_FORWARD; - } - - sfree(host); - } else { - /* - * Request we don't understand. Manufacture a failure - * message if an answer was required. - */ - if (orig_wantreply) - send_packet_to_downstream(cs, SSH2_MSG_REQUEST_FAILURE, - "", 0, NULL); - } - break; - - case SSH2_MSG_CHANNEL_OPEN: - /* Sender channel id comes after the channel type string */ - get_string(src); - id_pos = src->pos; - old_id = get_uint32(src); - new_id = ssh_alloc_sharing_channel(cs->parent->cl, cs); - get_uint32(src); /* skip initial window size */ - maxpkt = get_uint32(src); - if (get_err(src)) { - err = dupprintf("Truncated CHANNEL_OPEN packet"); - goto confused; - } - share_add_channel(cs, old_id, new_id, 0, UNACKNOWLEDGED, maxpkt); - PUT_32BIT_MSB_FIRST(pkt + id_pos, new_id); - ssh_send_packet_from_downstream(cs->parent->cl, cs->id, - type, pkt, pktlen, NULL); - break; - - case SSH2_MSG_CHANNEL_OPEN_CONFIRMATION: - if (pktlen < 16) { - err = dupprintf("Truncated CHANNEL_OPEN_CONFIRMATION packet"); - goto confused; - } - - server_id = get_uint32(src); - id_pos = src->pos; - old_id = get_uint32(src); - get_uint32(src); /* skip initial window size */ - maxpkt = get_uint32(src); - if (get_err(src)) { - err = dupprintf("Truncated CHANNEL_OPEN_CONFIRMATION packet"); - goto confused; - } - - /* This server id may refer to either a halfchannel or an xchannel. */ - hc = NULL, xc = NULL; /* placate optimiser */ - if ((hc = share_find_halfchannel(cs, server_id)) != NULL) { - new_id = ssh_alloc_sharing_channel(cs->parent->cl, cs); - } else if ((xc = share_find_xchannel_by_server(cs, server_id)) - != NULL) { - new_id = xc->upstream_id; - } else { - err = dupprintf("CHANNEL_OPEN_CONFIRMATION packet cited unknown channel %u", (unsigned)server_id); - goto confused; - } - - PUT_32BIT_MSB_FIRST(pkt + id_pos, new_id); - - chan = share_add_channel(cs, old_id, new_id, server_id, OPEN, maxpkt); - - if (hc) { - ssh_send_packet_from_downstream(cs->parent->cl, cs->id, - type, pkt, pktlen, NULL); - share_remove_halfchannel(cs, hc); - } else if (xc) { - unsigned downstream_window = GET_32BIT_MSB_FIRST(pkt + 8); - if (downstream_window < 256) { - err = dupprintf("Initial window size for x11 channel must be at least 256 (got %u)", downstream_window); - goto confused; - } - share_xchannel_confirmation(cs, xc, chan, downstream_window); - share_remove_xchannel(cs, xc); - } - - break; - - case SSH2_MSG_CHANNEL_OPEN_FAILURE: - server_id = get_uint32(src); - if (get_err(src)) { - err = dupprintf("Truncated CHANNEL_OPEN_FAILURE packet"); - goto confused; - } - - /* This server id may refer to either a halfchannel or an xchannel. */ - if ((hc = share_find_halfchannel(cs, server_id)) != NULL) { - ssh_send_packet_from_downstream(cs->parent->cl, cs->id, - type, pkt, pktlen, NULL); - share_remove_halfchannel(cs, hc); - } else if ((xc = share_find_xchannel_by_server(cs, server_id)) - != NULL) { - share_xchannel_failure(cs, xc); - } else { - err = dupprintf("CHANNEL_OPEN_FAILURE packet cited unknown channel %u", (unsigned)server_id); - goto confused; - } - - break; - - case SSH2_MSG_CHANNEL_WINDOW_ADJUST: - case SSH2_MSG_CHANNEL_DATA: - case SSH2_MSG_CHANNEL_EXTENDED_DATA: - case SSH2_MSG_CHANNEL_EOF: - case SSH2_MSG_CHANNEL_CLOSE: - case SSH2_MSG_CHANNEL_REQUEST: - case SSH2_MSG_CHANNEL_SUCCESS: - case SSH2_MSG_CHANNEL_FAILURE: - case SSH2_MSG_IGNORE: - case SSH2_MSG_DEBUG: - server_id = get_uint32(src); - - if (type == SSH2_MSG_CHANNEL_REQUEST) { - request_name = get_string(src); - - /* - * Agent forwarding requests from downstream are treated - * specially. Because OpenSSHD doesn't let us enable agent - * forwarding independently per session channel, and in - * particular because the OpenSSH-defined agent forwarding - * protocol does not mark agent-channel requests with the - * id of the session channel they originate from, the only - * way we can implement agent forwarding in a - * connection-shared PuTTY is to forward the _upstream_ - * agent. Hence, we unilaterally deny agent forwarding - * requests from downstreams if we aren't prepared to - * forward an agent ourselves. - * - * (If we are, then we dutifully pass agent forwarding - * requests upstream. OpenSSHD has the curious behaviour - * that all but the first such request will be rejected, - * but all session channels opened after the first request - * get agent forwarding enabled whether they ask for it or - * not; but that's not our concern, since other SSH - * servers supporting the same piece of protocol might in - * principle at least manage to enable agent forwarding on - * precisely the channels that requested it, even if the - * subsequent CHANNEL_OPENs still can't be associated with - * a parent session channel.) - */ - if (ptrlen_eq_string(request_name, "auth-agent-req@openssh.com") && - !ssh_agent_forwarding_permitted(cs->parent->cl)) { - - chan = share_find_channel_by_server(cs, server_id); - if (chan) { - packet = strbuf_new(); - put_uint32(packet, chan->downstream_id); - send_packet_to_downstream( - cs, SSH2_MSG_CHANNEL_FAILURE, - packet->s, packet->len, NULL); - strbuf_free(packet); - } else { - char *buf = dupprintf("Agent forwarding request for " - "unrecognised channel %u", server_id); - share_disconnect(cs, buf); - sfree(buf); - return; - } - break; - } - - /* - * Another thing we treat specially is X11 forwarding - * requests. For these, we have to make up another set of - * X11 auth data, and enter it into our SSH connection's - * list of possible X11 authorisation credentials so that - * when we see an X11 channel open request we can know - * whether it's one to handle locally or one to pass on to - * a downstream, and if the latter, which one. - */ - if (ptrlen_eq_string(request_name, "x11-req")) { - bool want_reply, single_connection; - int screen; - ptrlen auth_data; - int auth_proto; - - chan = share_find_channel_by_server(cs, server_id); - if (!chan) { - char *buf = dupprintf("X11 forwarding request for " - "unrecognised channel %u", server_id); - share_disconnect(cs, buf); - sfree(buf); - return; - } - - /* - * Pick apart the whole message to find the downstream - * auth details. - */ - want_reply = get_bool(src); - single_connection = get_bool(src); - auth_proto = x11_identify_auth_proto(get_string(src)); - auth_data = get_string(src); - screen = toint(get_uint32(src)); - if (get_err(src)) { - err = dupprintf("Truncated CHANNEL_REQUEST(\"x11-req\")" - " packet"); - goto confused; - } - - if (auth_proto < 0) { - /* Reject due to not understanding downstream's - * requested authorisation method. */ - packet = strbuf_new(); - put_uint32(packet, chan->downstream_id); - send_packet_to_downstream( - cs, SSH2_MSG_CHANNEL_FAILURE, - packet->s, packet->len, NULL); - strbuf_free(packet); - break; - } - - chan->x11_auth_proto = auth_proto; - chan->x11_auth_data = x11_dehexify(auth_data, - &chan->x11_auth_datalen); - chan->x11_auth_upstream = - ssh_add_sharing_x11_display(cs->parent->cl, auth_proto, - cs, chan); - chan->x11_one_shot = single_connection; - - /* - * Now construct a replacement X forwarding request, - * containing our own auth data, and send that to the - * server. - */ - packet = strbuf_new_nm(); - put_uint32(packet, server_id); - put_stringz(packet, "x11-req"); - put_bool(packet, want_reply); - put_bool(packet, single_connection); - put_stringz(packet, chan->x11_auth_upstream->protoname); - put_stringz(packet, chan->x11_auth_upstream->datastring); - put_uint32(packet, screen); - ssh_send_packet_from_downstream( - cs->parent->cl, cs->id, SSH2_MSG_CHANNEL_REQUEST, - packet->s, packet->len, NULL); - strbuf_free(packet); - - break; - } - } - - ssh_send_packet_from_downstream(cs->parent->cl, cs->id, - type, pkt, pktlen, NULL); - if (type == SSH2_MSG_CHANNEL_CLOSE && pktlen >= 4) { - chan = share_find_channel_by_server(cs, server_id); - if (chan) { - if (chan->state == RCVD_CLOSE) { - ssh_delete_sharing_channel(cs->parent->cl, - chan->upstream_id); - share_remove_channel(cs, chan); - } else { - chan->state = SENT_CLOSE; - } - } - } - break; - - default: - err = dupprintf("Unexpected packet type %d\n", type); - goto confused; - - /* - * Any other packet type is unexpected. In particular, we - * never pass GLOBAL_REQUESTs downstream, so we never expect - * to see SSH2_MSG_REQUEST_{SUCCESS,FAILURE}. - */ - confused: - assert(err != NULL); - share_disconnect(cs, err); - sfree(err); - break; - } -} - -/* - * An extra coroutine macro, specific to this code which is consuming - * 'const char *data'. - */ -#define crGetChar(c) do \ - { \ - while (len == 0) { \ - *crLine =__LINE__; return; case __LINE__:; \ - } \ - len--; \ - (c) = (unsigned char)*data++; \ - } while (0) - -static void share_receive(Plug *plug, int urgent, const char *data, size_t len) -{ - ssh_sharing_connstate *cs = container_of( - plug, ssh_sharing_connstate, plug); - static const char expected_verstring_prefix[] = - "SSHCONNECTION@putty.projects.tartarus.org-2.0-"; - unsigned char c; - - crBegin(cs->crLine); - - /* - * First read the version string from downstream. - */ - cs->recvlen = 0; - while (1) { - crGetChar(c); - if (c == '\012') - break; - if (cs->recvlen >= sizeof(cs->recvbuf)) { - char *buf = dupprintf("Version string far too long\n"); - share_disconnect(cs, buf); - sfree(buf); - goto dead; - } - cs->recvbuf[cs->recvlen++] = c; - } - - /* - * Now parse the version string to make sure it's at least vaguely - * sensible, and log it. - */ - if (cs->recvlen < sizeof(expected_verstring_prefix)-1 || - memcmp(cs->recvbuf, expected_verstring_prefix, - sizeof(expected_verstring_prefix) - 1)) { - char *buf = dupprintf("Version string did not have expected prefix\n"); - share_disconnect(cs, buf); - sfree(buf); - goto dead; - } - if (cs->recvlen > 0 && cs->recvbuf[cs->recvlen-1] == '\015') - cs->recvlen--; /* trim off \r before \n */ - ptrlen verstring = make_ptrlen(cs->recvbuf, cs->recvlen); - log_downstream(cs, "Downstream version string: %.*s", - PTRLEN_PRINTF(verstring)); - cs->got_verstring = true; - - /* - * Loop round reading packets. - */ - while (1) { - cs->recvlen = 0; - while (cs->recvlen < 4) { - crGetChar(c); - cs->recvbuf[cs->recvlen++] = c; - } - cs->curr_packetlen = toint(GET_32BIT_MSB_FIRST(cs->recvbuf) + 4); - if (cs->curr_packetlen < 5 || - cs->curr_packetlen > sizeof(cs->recvbuf)) { - char *buf = dupprintf("Bad packet length %u\n", - (unsigned)cs->curr_packetlen); - share_disconnect(cs, buf); - sfree(buf); - goto dead; - } - while (cs->recvlen < cs->curr_packetlen) { - crGetChar(c); - cs->recvbuf[cs->recvlen++] = c; - } - - share_got_pkt_from_downstream(cs, cs->recvbuf[4], - cs->recvbuf + 5, cs->recvlen - 5); - } - - dead:; - crFinishV; -} - -static void share_sent(Plug *plug, size_t bufsize) -{ - /* ssh_sharing_connstate *cs = container_of( - plug, ssh_sharing_connstate, plug); */ - - /* - * We do nothing here, because we expect that there won't be a - * need to throttle and unthrottle the connection to a downstream. - * It should automatically throttle itself: if the SSH server - * sends huge amounts of data on all channels then it'll run out - * of window until our downstream sends it back some - * WINDOW_ADJUSTs. - */ -} - -static void share_listen_closing(Plug *plug, const char *error_msg, - int error_code, bool calling_back) -{ - ssh_sharing_state *sharestate = - container_of(plug, ssh_sharing_state, plug); - if (error_msg) - log_general(sharestate, "listening socket: %s", error_msg); - sk_close(sharestate->listensock); - sharestate->listensock = NULL; -} - -static void share_send_verstring(ssh_sharing_connstate *cs) -{ - char *fullstring = dupcat("SSHCONNECTION@putty.projects.tartarus.org-2.0-", - cs->parent->server_verstring, "\015\012"); - sk_write(cs->sock, fullstring, strlen(fullstring)); - sfree(fullstring); - - cs->sent_verstring = true; -} - -int share_ndownstreams(ssh_sharing_state *sharestate) -{ - return count234(sharestate->connections); -} - -void share_activate(ssh_sharing_state *sharestate, - const char *server_verstring) -{ - /* - * Indication from ssh.c that we are now ready to begin serving - * any downstreams that have already connected to us. - */ - struct ssh_sharing_connstate *cs; - int i; - - /* - * Trim the server's version string down to just the software - * version component, removing "SSH-2.0-" or whatever at the - * front. - */ - for (i = 0; i < 2; i++) { - server_verstring += strcspn(server_verstring, "-"); - if (*server_verstring) - server_verstring++; - } - - sharestate->server_verstring = dupstr(server_verstring); - - for (i = 0; (cs = (struct ssh_sharing_connstate *) - index234(sharestate->connections, i)) != NULL; i++) { - assert(!cs->sent_verstring); - share_send_verstring(cs); - } -} - -static const PlugVtable ssh_sharing_conn_plugvt = { - .closing = share_closing, - .receive = share_receive, - .sent = share_sent, -}; - -static int share_listen_accepting(Plug *plug, - accept_fn_t constructor, accept_ctx_t ctx) -{ - struct ssh_sharing_state *sharestate = container_of( - plug, struct ssh_sharing_state, plug); - struct ssh_sharing_connstate *cs; - const char *err; - SocketPeerInfo *peerinfo; - - /* - * A new downstream has connected to us. - */ - cs = snew(struct ssh_sharing_connstate); - cs->plug.vt = &ssh_sharing_conn_plugvt; - cs->parent = sharestate; - - if ((cs->id = share_find_unused_id(sharestate, sharestate->nextid)) == 0 && - (cs->id = share_find_unused_id(sharestate, 1)) == 0) { - sfree(cs); - return 1; - } - sharestate->nextid = cs->id + 1; - if (sharestate->nextid == 0) - sharestate->nextid++; /* only happens in VERY long-running upstreams */ - - cs->sock = constructor(ctx, &cs->plug); - if ((err = sk_socket_error(cs->sock)) != NULL) { - sfree(cs); - return err != NULL; - } - - sk_set_frozen(cs->sock, false); - - add234(cs->parent->connections, cs); - - cs->sent_verstring = false; - if (sharestate->server_verstring) - share_send_verstring(cs); - - cs->got_verstring = false; - cs->recvlen = 0; - cs->crLine = 0; - cs->halfchannels = newtree234(share_halfchannel_cmp); - cs->channels_by_us = newtree234(share_channel_us_cmp); - cs->channels_by_server = newtree234(share_channel_server_cmp); - cs->xchannels_by_us = newtree234(share_xchannel_us_cmp); - cs->xchannels_by_server = newtree234(share_xchannel_server_cmp); - cs->forwardings = newtree234(share_forwarding_cmp); - cs->globreq_head = cs->globreq_tail = NULL; - - peerinfo = sk_peer_info(cs->sock); - log_downstream(cs, "connected%s%s", - (peerinfo && peerinfo->log_text ? " from " : ""), - (peerinfo && peerinfo->log_text ? peerinfo->log_text : "")); - sk_free_peer_info(peerinfo); - - return 0; -} - -/* - * Decide on the string used to identify the connection point between - * upstream and downstream (be it a Windows named pipe or a - * Unix-domain socket or whatever else). - * - * I wondered about making this a SHA hash of all sorts of pieces of - * the PuTTY configuration - essentially everything PuTTY uses to know - * where and how to make a connection, including all the proxy details - * (or rather, all the _relevant_ ones - only including settings that - * other settings didn't prevent from having any effect), plus the - * username. However, I think it's better to keep it really simple: - * the connection point identifier is derived from the hostname and - * port used to index the host-key cache (not necessarily where we - * _physically_ connected to, in cases involving proxies or - * CONF_loghost), plus the username if one is specified. - * - * The per-platform code will quite likely hash or obfuscate this name - * in turn, for privacy from other users; failing that, it might - * transform it to avoid dangerous filename characters and so on. But - * that doesn't matter to us: for us, the point is that two session - * configurations which return the same string from this function will - * be treated as potentially shareable with each other. - */ -char *ssh_share_sockname(const char *host, int port, Conf *conf) -{ - char *username = NULL; - char *sockname; - - /* Include the username we're logging in as in the hash, unless - * we're using a protocol for which it's completely irrelevant. */ - if (conf_get_int(conf, CONF_protocol) != PROT_SSHCONN) - username = get_remote_username(conf); - - if (port == 22) { - if (username) - sockname = dupprintf("%s@%s", username, host); - else - sockname = dupprintf("%s", host); - } else { - if (username) - sockname = dupprintf("%s@%s:%d", username, host, port); - else - sockname = dupprintf("%s:%d", host, port); - } - - sfree(username); - return sockname; -} - -bool ssh_share_test_for_upstream(const char *host, int port, Conf *conf) -{ - char *sockname, *logtext, *ds_err, *us_err; - int result; - Socket *sock; - - sockname = ssh_share_sockname(host, port, conf); - - sock = NULL; - logtext = ds_err = us_err = NULL; - result = platform_ssh_share(sockname, conf, nullplug, (Plug *)NULL, &sock, - &logtext, &ds_err, &us_err, false, true); - - sfree(logtext); - sfree(ds_err); - sfree(us_err); - sfree(sockname); - - if (result == SHARE_NONE) { - assert(sock == NULL); - return false; - } else { - assert(result == SHARE_DOWNSTREAM); - sk_close(sock); - return true; - } -} - -static const PlugVtable ssh_sharing_listen_plugvt = { - .closing = share_listen_closing, - .accepting = share_listen_accepting, -}; - -void ssh_connshare_provide_connlayer(ssh_sharing_state *sharestate, - ConnectionLayer *cl) -{ - sharestate->cl = cl; -} - -/* - * Init function for connection sharing. We either open a listening - * socket and become an upstream, or connect to an existing one and - * become a downstream, or do neither. We are responsible for deciding - * which of these to do (including checking the Conf to see if - * connection sharing is even enabled in the first place). If we - * become a downstream, we return the Socket with which we connected - * to the upstream; otherwise (whether or not we have established an - * upstream) we return NULL. - */ -Socket *ssh_connection_sharing_init( - const char *host, int port, Conf *conf, LogContext *logctx, - Plug *sshplug, ssh_sharing_state **state) -{ - int result; - bool can_upstream, can_downstream; - char *logtext, *ds_err, *us_err; - char *sockname; - Socket *sock, *toret = NULL; - struct ssh_sharing_state *sharestate; - - if (!conf_get_bool(conf, CONF_ssh_connection_sharing)) - return NULL; /* do not share anything */ - can_upstream = share_can_be_upstream && - conf_get_bool(conf, CONF_ssh_connection_sharing_upstream); - can_downstream = share_can_be_downstream && - conf_get_bool(conf, CONF_ssh_connection_sharing_downstream); - if (!can_upstream && !can_downstream) - return NULL; - - sockname = ssh_share_sockname(host, port, conf); - - /* - * Create a data structure for the listening plug if we turn out - * to be an upstream. - */ - sharestate = snew(struct ssh_sharing_state); - sharestate->plug.vt = &ssh_sharing_listen_plugvt; - sharestate->listensock = NULL; - sharestate->cl = NULL; - - /* - * Now hand off to a per-platform routine that either connects to - * an existing upstream (using 'ssh' as the plug), establishes our - * own upstream (using 'sharestate' as the plug), or forks off a - * separate upstream and then connects to that. It will return a - * code telling us which kind of socket it put in 'sock'. - */ - sock = NULL; - logtext = ds_err = us_err = NULL; - result = platform_ssh_share( - sockname, conf, sshplug, &sharestate->plug, &sock, &logtext, - &ds_err, &us_err, can_upstream, can_downstream); - switch (result) { - case SHARE_NONE: - /* - * We aren't sharing our connection at all (e.g. something - * went wrong setting the socket up). Free the upstream - * structure and return NULL. - */ - - if (logtext) { - /* For this result, if 'logtext' is not NULL then it is an - * error message indicating a reason why connection sharing - * couldn't be set up _at all_ */ - logeventf(logctx, - "Could not set up connection sharing: %s", logtext); - } else { - /* Failing that, ds_err and us_err indicate why we - * couldn't be a downstream and an upstream respectively */ - if (ds_err) - logeventf(logctx, "Could not set up connection sharing" - " as downstream: %s", ds_err); - if (us_err) - logeventf(logctx, "Could not set up connection sharing" - " as upstream: %s", us_err); - } - - assert(sock == NULL); - *state = NULL; - sfree(sharestate); - sfree(sockname); - break; - - case SHARE_DOWNSTREAM: - /* - * We are downstream, so free sharestate which it turns out we - * don't need after all, and return the downstream socket as a - * replacement for an ordinary SSH connection. - */ - - /* 'logtext' is a local endpoint address */ - logeventf(logctx, "Using existing shared connection at %s", logtext); - - *state = NULL; - sfree(sharestate); - sfree(sockname); - toret = sock; - break; - - case SHARE_UPSTREAM: - /* - * We are upstream. Set up sharestate properly and pass a copy - * to the caller; return NULL, to tell ssh.c that it has to - * make an ordinary connection after all. - */ - - /* 'logtext' is a local endpoint address */ - logeventf(logctx, "Sharing this connection at %s", logtext); - - *state = sharestate; - sharestate->listensock = sock; - sharestate->connections = newtree234(share_connstate_cmp); - sharestate->server_verstring = NULL; - sharestate->sockname = sockname; - sharestate->nextid = 1; - break; - } - - sfree(logtext); - sfree(ds_err); - sfree(us_err); - return toret; -} diff --git a/sshsignals.h b/sshsignals.h deleted file mode 100644 index b213c34f..00000000 --- a/sshsignals.h +++ /dev/null @@ -1,53 +0,0 @@ -/* - * List of signal names known to SSH, indicating whether PuTTY's UI - * for special session commands likes to put them in the main specials - * menu or in a submenu (and if the former, what title they have). - * - * This is a separate header file rather than my usual style of a - * parametric list macro, because in this case I need to be able to - * #ifdef out each mode in case it's not defined on a particular - * target system. - * - * If you want only the locally defined signals, #define - * SIGNALS_LOCAL_ONLY before including this header. - */ - -#if !defined SIGNALS_LOCAL_ONLY || defined SIGINT -SIGNAL_MAIN(INT, "Interrupt") -#endif -#if !defined SIGNALS_LOCAL_ONLY || defined SIGTERM -SIGNAL_MAIN(TERM, "Terminate") -#endif -#if !defined SIGNALS_LOCAL_ONLY || defined SIGKILL -SIGNAL_MAIN(KILL, "Kill") -#endif -#if !defined SIGNALS_LOCAL_ONLY || defined SIGQUIT -SIGNAL_MAIN(QUIT, "Quit") -#endif -#if !defined SIGNALS_LOCAL_ONLY || defined SIGHUP -SIGNAL_MAIN(HUP, "Hangup") -#endif -#if !defined SIGNALS_LOCAL_ONLY || defined SIGABRT -SIGNAL_SUB(ABRT) -#endif -#if !defined SIGNALS_LOCAL_ONLY || defined SIGALRM -SIGNAL_SUB(ALRM) -#endif -#if !defined SIGNALS_LOCAL_ONLY || defined SIGFPE -SIGNAL_SUB(FPE) -#endif -#if !defined SIGNALS_LOCAL_ONLY || defined SIGILL -SIGNAL_SUB(ILL) -#endif -#if !defined SIGNALS_LOCAL_ONLY || defined SIGPIPE -SIGNAL_SUB(PIPE) -#endif -#if !defined SIGNALS_LOCAL_ONLY || defined SIGSEGV -SIGNAL_SUB(SEGV) -#endif -#if !defined SIGNALS_LOCAL_ONLY || defined SIGUSR1 -SIGNAL_SUB(USR1) -#endif -#if !defined SIGNALS_LOCAL_ONLY || defined SIGUSR2 -SIGNAL_SUB(USR2) -#endif diff --git a/sshttymodes.h b/sshttymodes.h deleted file mode 100644 index 8fffe1c5..00000000 --- a/sshttymodes.h +++ /dev/null @@ -1,179 +0,0 @@ -/* - * List of SSH terminal modes, indicating whether SSH types them as - * char or boolean, and if they're boolean, which POSIX flags field of - * a termios structure they appear in, and what bit mask removes them - * (e.g. CS7 and CS8 aren't single bits). - * - * Sources: RFC 4254, SSH-1 RFC-1.2.31, POSIX 2017, and the Linux - * termios manpage for flags not specified by POSIX. - * - * This is a separate header file rather than my usual style of a - * parametric list macro, because in this case I need to be able to - * #ifdef out each mode in case it's not defined on a particular - * target system. - * - * If you want only the locally defined modes, #define - * TTYMODES_LOCAL_ONLY before including this header. - */ -#if !defined TTYMODES_LOCAL_ONLY || defined VINTR -TTYMODE_CHAR(INTR, 1, VINTR) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined VQUIT -TTYMODE_CHAR(QUIT, 2, VQUIT) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined VERASE -TTYMODE_CHAR(ERASE, 3, VERASE) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined VKILL -TTYMODE_CHAR(KILL, 4, VKILL) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined VEOF -TTYMODE_CHAR(EOF, 5, VEOF) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined VEOL -TTYMODE_CHAR(EOL, 6, VEOL) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined VEOL2 -TTYMODE_CHAR(EOL2, 7, VEOL2) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined VSTART -TTYMODE_CHAR(START, 8, VSTART) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined VSTOP -TTYMODE_CHAR(STOP, 9, VSTOP) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined VSUSP -TTYMODE_CHAR(SUSP, 10, VSUSP) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined VDSUSP -TTYMODE_CHAR(DSUSP, 11, VDSUSP) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined VREPRINT -TTYMODE_CHAR(REPRINT, 12, VREPRINT) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined VWERASE -TTYMODE_CHAR(WERASE, 13, VWERASE) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined VLNEXT -TTYMODE_CHAR(LNEXT, 14, VLNEXT) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined VFLUSH -TTYMODE_CHAR(FLUSH, 15, VFLUSH) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined VSWTCH -TTYMODE_CHAR(SWTCH, 16, VSWTCH) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined VSTATUS -TTYMODE_CHAR(STATUS, 17, VSTATUS) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined VDISCARD -TTYMODE_CHAR(DISCARD, 18, VDISCARD) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined IGNPAR -TTYMODE_FLAG(IGNPAR, 30, i, IGNPAR) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined PARMRK -TTYMODE_FLAG(PARMRK, 31, i, PARMRK) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined INPCK -TTYMODE_FLAG(INPCK, 32, i, INPCK) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined ISTRIP -TTYMODE_FLAG(ISTRIP, 33, i, ISTRIP) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined INLCR -TTYMODE_FLAG(INLCR, 34, i, INLCR) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined IGNCR -TTYMODE_FLAG(IGNCR, 35, i, IGNCR) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined ICRNL -TTYMODE_FLAG(ICRNL, 36, i, ICRNL) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined IUCLC -TTYMODE_FLAG(IUCLC, 37, i, IUCLC) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined IXON -TTYMODE_FLAG(IXON, 38, i, IXON) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined IXANY -TTYMODE_FLAG(IXANY, 39, i, IXANY) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined IXOFF -TTYMODE_FLAG(IXOFF, 40, i, IXOFF) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined IMAXBEL -TTYMODE_FLAG(IMAXBEL, 41, i, IMAXBEL) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined IUTF8 -TTYMODE_FLAG(IUTF8, 42, i, IUTF8) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined ISIG -TTYMODE_FLAG(ISIG, 50, l, ISIG) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined ICANON -TTYMODE_FLAG(ICANON, 51, l, ICANON) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined XCASE -TTYMODE_FLAG(XCASE, 52, l, XCASE) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined ECHO -TTYMODE_FLAG(ECHO, 53, l, ECHO) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined ECHOE -TTYMODE_FLAG(ECHOE, 54, l, ECHOE) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined ECHOK -TTYMODE_FLAG(ECHOK, 55, l, ECHOK) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined ECHONL -TTYMODE_FLAG(ECHONL, 56, l, ECHONL) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined NOFLSH -TTYMODE_FLAG(NOFLSH, 57, l, NOFLSH) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined TOSTOP -TTYMODE_FLAG(TOSTOP, 58, l, TOSTOP) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined IEXTEN -TTYMODE_FLAG(IEXTEN, 59, l, IEXTEN) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined ECHOCTL -TTYMODE_FLAG(ECHOCTL, 60, l, ECHOCTL) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined ECHOKE -TTYMODE_FLAG(ECHOKE, 61, l, ECHOKE) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined PENDIN -TTYMODE_FLAG(PENDIN, 62, l, PENDIN) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined OPOST -TTYMODE_FLAG(OPOST, 70, o, OPOST) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined OLCUC -TTYMODE_FLAG(OLCUC, 71, o, OLCUC) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined ONLCR -TTYMODE_FLAG(ONLCR, 72, o, ONLCR) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined OCRNL -TTYMODE_FLAG(OCRNL, 73, o, OCRNL) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined ONOCR -TTYMODE_FLAG(ONOCR, 74, o, ONOCR) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined ONLRET -TTYMODE_FLAG(ONLRET, 75, o, ONLRET) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined CS7 -TTYMODE_FLAG(CS7, 90, c, CSIZE) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined CS8 -TTYMODE_FLAG(CS8, 91, c, CSIZE) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined PARENB -TTYMODE_FLAG(PARENB, 92, c, PARENB) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined PARODD -TTYMODE_FLAG(PARODD, 93, c, PARODD) -#endif diff --git a/sshverstring.c b/sshverstring.c deleted file mode 100644 index 8951e4cb..00000000 --- a/sshverstring.c +++ /dev/null @@ -1,628 +0,0 @@ -/* - * Code to handle the initial SSH version string exchange. - */ - -#include -#include -#include - -#include "putty.h" -#include "ssh.h" -#include "sshbpp.h" -#include "sshcr.h" - -#define PREFIX_MAXLEN 64 - -struct ssh_verstring_state { - int crState; - - Conf *conf; - ptrlen prefix_wanted; - char *our_protoversion; - struct ssh_version_receiver *receiver; - - bool send_early; - - bool found_prefix; - int major_protoversion; - int remote_bugs; - char prefix[PREFIX_MAXLEN]; - char *impl_name; - strbuf *vstring; - char *protoversion; - const char *softwareversion; - - char *our_vstring; - int i; - - BinaryPacketProtocol bpp; -}; - -static void ssh_verstring_free(BinaryPacketProtocol *bpp); -static void ssh_verstring_handle_input(BinaryPacketProtocol *bpp); -static void ssh_verstring_handle_output(BinaryPacketProtocol *bpp); -static PktOut *ssh_verstring_new_pktout(int type); -static void ssh_verstring_queue_disconnect(BinaryPacketProtocol *bpp, - const char *msg, int category); - -static const BinaryPacketProtocolVtable ssh_verstring_vtable = { - .free = ssh_verstring_free, - .handle_input = ssh_verstring_handle_input, - .handle_output = ssh_verstring_handle_output, - .new_pktout = ssh_verstring_new_pktout, - .queue_disconnect = ssh_verstring_queue_disconnect, - .packet_size_limit = 0xFFFFFFFF, /* no special limit for this bpp */ -}; - -static void ssh_detect_bugs(struct ssh_verstring_state *s); -static bool ssh_version_includes_v1(const char *ver); -static bool ssh_version_includes_v2(const char *ver); - -BinaryPacketProtocol *ssh_verstring_new( - Conf *conf, LogContext *logctx, bool bare_connection_mode, - const char *protoversion, struct ssh_version_receiver *rcv, - bool server_mode, const char *impl_name) -{ - struct ssh_verstring_state *s = snew(struct ssh_verstring_state); - - memset(s, 0, sizeof(struct ssh_verstring_state)); - - if (!bare_connection_mode) { - s->prefix_wanted = PTRLEN_LITERAL("SSH-"); - } else { - /* - * Ordinary SSH begins with the banner "SSH-x.y-...". Here, - * we're going to be speaking just the ssh-connection - * subprotocol, extracted and given a trivial binary packet - * protocol, so we need a new banner. - * - * The new banner is like the ordinary SSH banner, but - * replaces the prefix 'SSH-' at the start with a new name. In - * proper SSH style (though of course this part of the proper - * SSH protocol _isn't_ subject to this kind of - * DNS-domain-based extension), we define the new name in our - * extension space. - */ - s->prefix_wanted = PTRLEN_LITERAL( - "SSHCONNECTION@putty.projects.tartarus.org-"); - } - assert(s->prefix_wanted.len <= PREFIX_MAXLEN); - - s->conf = conf_copy(conf); - s->bpp.logctx = logctx; - s->our_protoversion = dupstr(protoversion); - s->receiver = rcv; - s->impl_name = dupstr(impl_name); - s->vstring = strbuf_new(); - - /* - * We send our version string early if we can. But if it includes - * SSH-1, we can't, because we have to take the other end into - * account too (see below). - * - * In server mode, we do send early. - */ - s->send_early = server_mode || !ssh_version_includes_v1(protoversion); - - s->bpp.vt = &ssh_verstring_vtable; - ssh_bpp_common_setup(&s->bpp); - return &s->bpp; -} - -void ssh_verstring_free(BinaryPacketProtocol *bpp) -{ - struct ssh_verstring_state *s = - container_of(bpp, struct ssh_verstring_state, bpp); - conf_free(s->conf); - sfree(s->impl_name); - strbuf_free(s->vstring); - sfree(s->protoversion); - sfree(s->our_vstring); - sfree(s->our_protoversion); - sfree(s); -} - -static int ssh_versioncmp(const char *a, const 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; -} - -static bool ssh_version_includes_v1(const char *ver) -{ - return ssh_versioncmp(ver, "2.0") < 0; -} - -static bool ssh_version_includes_v2(const char *ver) -{ - return ssh_versioncmp(ver, "1.99") >= 0; -} - -static void ssh_verstring_send(struct ssh_verstring_state *s) -{ - BinaryPacketProtocol *bpp = &s->bpp; /* for bpp_logevent */ - char *p; - int sv_pos; - - /* - * Construct our outgoing version string. - */ - s->our_vstring = dupprintf( - "%.*s%s-%s%s", - (int)s->prefix_wanted.len, (const char *)s->prefix_wanted.ptr, - s->our_protoversion, s->impl_name, sshver); - sv_pos = s->prefix_wanted.len + strlen(s->our_protoversion) + 1; - - /* Convert minus signs and spaces in the software version string - * into underscores. */ - for (p = s->our_vstring + sv_pos; *p; p++) { - if (*p == '-' || *p == ' ') - *p = '_'; - } - -#ifdef FUZZING - /* - * Replace the first character of the string with an "I" if we're - * compiling this code for fuzzing - i.e. the protocol prefix - * becomes "ISH-" instead of "SSH-". - * - * This is irrelevant to any real client software (the only thing - * reading the output of PuTTY built for fuzzing is the fuzzer, - * which can adapt to whatever it sees anyway). But it's a safety - * precaution making it difficult to accidentally run such a - * version of PuTTY (which would be hugely insecure) against a - * live peer implementation. - * - * (So the replacement prefix "ISH" notionally stands for - * 'Insecure Shell', of course.) - */ - s->our_vstring[0] = 'I'; -#endif - - /* - * Now send that version string, plus trailing \r\n or just \n - * (the latter in SSH-1 mode). - */ - bufchain_add(s->bpp.out_raw, s->our_vstring, strlen(s->our_vstring)); - if (ssh_version_includes_v2(s->our_protoversion)) - bufchain_add(s->bpp.out_raw, "\015", 1); - bufchain_add(s->bpp.out_raw, "\012", 1); - - bpp_logevent("We claim version: %s", s->our_vstring); -} - -#define BPP_WAITFOR(minlen) do \ - { \ - bool success; \ - crMaybeWaitUntilV( \ - (success = (bufchain_size(s->bpp.in_raw) >= (minlen))) || \ - s->bpp.input_eof); \ - if (!success) \ - goto eof; \ - } while (0) - -void ssh_verstring_handle_input(BinaryPacketProtocol *bpp) -{ - struct ssh_verstring_state *s = - container_of(bpp, struct ssh_verstring_state, bpp); - - crBegin(s->crState); - - /* - * If we're sending our version string up front before seeing the - * other side's, then do it now. - */ - if (s->send_early) - ssh_verstring_send(s); - - /* - * Search for a line beginning with the protocol name prefix in - * the input. - */ - s->i = 0; - while (1) { - /* - * Every time round this loop, we're at the start of a new - * line, so look for the prefix. - */ - BPP_WAITFOR(s->prefix_wanted.len); - bufchain_fetch(s->bpp.in_raw, s->prefix, s->prefix_wanted.len); - if (!memcmp(s->prefix, s->prefix_wanted.ptr, s->prefix_wanted.len)) { - bufchain_consume(s->bpp.in_raw, s->prefix_wanted.len); - ssh_check_frozen(s->bpp.ssh); - break; - } - - /* - * If we didn't find it, consume data until we see a newline. - */ - while (1) { - ptrlen data; - char *nl; - - /* Wait to receive at least 1 byte, but then consume more - * than that if it's there. */ - BPP_WAITFOR(1); - data = bufchain_prefix(s->bpp.in_raw); - if ((nl = memchr(data.ptr, '\012', data.len)) != NULL) { - bufchain_consume(s->bpp.in_raw, nl - (char *)data.ptr + 1); - ssh_check_frozen(s->bpp.ssh); - break; - } else { - bufchain_consume(s->bpp.in_raw, data.len); - ssh_check_frozen(s->bpp.ssh); - } - } - } - - s->found_prefix = true; - - /* - * Copy the greeting line so far into vstring. - */ - put_data(s->vstring, s->prefix_wanted.ptr, s->prefix_wanted.len); - - /* - * Now read the rest of the greeting line. - */ - s->i = 0; - do { - ptrlen data; - char *nl; - - BPP_WAITFOR(1); - data = bufchain_prefix(s->bpp.in_raw); - if ((nl = memchr(data.ptr, '\012', data.len)) != NULL) { - data.len = nl - (char *)data.ptr + 1; - } - - put_datapl(s->vstring, data); - bufchain_consume(s->bpp.in_raw, data.len); - ssh_check_frozen(s->bpp.ssh); - - } while (s->vstring->s[s->vstring->len-1] != '\012'); - - /* - * Trim \r and \n from the version string, and replace them with - * a NUL terminator. - */ - while (s->vstring->len > 0 && - (s->vstring->s[s->vstring->len-1] == '\r' || - s->vstring->s[s->vstring->len-1] == '\n')) - strbuf_shrink_by(s->vstring, 1); - - bpp_logevent("Remote version: %s", s->vstring->s); - - /* - * Pick out the protocol version and software version. The former - * goes in a separately allocated string, so that s->vstring - * remains intact for later use in key exchange; the latter is the - * tail of s->vstring, so it doesn't need to be allocated. - */ - { - const char *pv_start = s->vstring->s + s->prefix_wanted.len; - int pv_len = strcspn(pv_start, "-"); - s->protoversion = dupprintf("%.*s", pv_len, pv_start); - s->softwareversion = pv_start + pv_len; - if (*s->softwareversion) { - assert(*s->softwareversion == '-'); - s->softwareversion++; - } - } - - ssh_detect_bugs(s); - - /* - * Figure out what actual SSH protocol version we're speaking. - */ - if (ssh_version_includes_v2(s->our_protoversion) && - ssh_version_includes_v2(s->protoversion)) { - /* - * We're doing SSH-2. - */ - s->major_protoversion = 2; - } else if (ssh_version_includes_v1(s->our_protoversion) && - ssh_version_includes_v1(s->protoversion)) { - /* - * We're doing SSH-1. - */ - s->major_protoversion = 1; - - /* - * There are multiple minor versions of SSH-1, and the - * protocol does not specify that the minimum of client - * and server versions is used. So we must adjust our - * outgoing protocol version to be no higher than that of - * the other side. - */ - if (!s->send_early && - ssh_versioncmp(s->our_protoversion, s->protoversion) > 0) { - sfree(s->our_protoversion); - s->our_protoversion = dupstr(s->protoversion); - } - } else { - /* - * Unable to agree on a major protocol version at all. - */ - if (!ssh_version_includes_v2(s->our_protoversion)) { - ssh_sw_abort(s->bpp.ssh, - "SSH protocol version 1 required by our " - "configuration but not provided by remote"); - } else { - ssh_sw_abort(s->bpp.ssh, - "SSH protocol version 2 required by our " - "configuration but remote only provides " - "(old, insecure) SSH-1"); - } - crStopV; - } - - bpp_logevent("Using SSH protocol version %d", s->major_protoversion); - - if (!s->send_early) { - /* - * If we didn't send our version string early, construct and - * send it now, because now we know what it is. - */ - ssh_verstring_send(s); - } - - /* - * And we're done. Notify our receiver that we now know our - * protocol version. This will cause it to disconnect us from the - * input stream and ultimately free us, because our job is now - * done. - */ - s->receiver->got_ssh_version(s->receiver, s->major_protoversion); - return; - - eof: - ssh_remote_error(s->bpp.ssh, - "Remote side unexpectedly closed network connection"); - return; /* avoid touching s now it's been freed */ - - crFinishV; -} - -static PktOut *ssh_verstring_new_pktout(int type) -{ - unreachable("Should never try to send packets during SSH version " - "string exchange"); -} - -static void ssh_verstring_handle_output(BinaryPacketProtocol *bpp) -{ - if (pq_peek(&bpp->out_pq)) { - unreachable("Should never try to send packets during SSH version " - "string exchange"); - } -} - -/* - * Examine the remote side's version string, and compare it against a - * list of known buggy implementations. - */ -static void ssh_detect_bugs(struct ssh_verstring_state *s) -{ - BinaryPacketProtocol *bpp = &s->bpp; /* for bpp_logevent */ - const char *imp = s->softwareversion; - - s->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 (conf_get_int(s->conf, CONF_sshbug_ignore1) == FORCE_ON || - (conf_get_int(s->conf, CONF_sshbug_ignore1) == AUTO && - (!strcmp(imp, "1.2.18") || !strcmp(imp, "1.2.19") || - !strcmp(imp, "1.2.20") || !strcmp(imp, "1.2.21") || - !strcmp(imp, "1.2.22") || !strcmp(imp, "Cisco-1.25") || - !strcmp(imp, "OSU_1.4alpha3") || !strcmp(imp, "OSU_1.5alpha4")))) { - /* - * These versions don't support SSH1_MSG_IGNORE, so we have - * to use a different defence against password length - * sniffing. - */ - s->remote_bugs |= BUG_CHOKES_ON_SSH1_IGNORE; - bpp_logevent("We believe remote version has SSH-1 ignore bug"); - } - - if (conf_get_int(s->conf, CONF_sshbug_plainpw1) == FORCE_ON || - (conf_get_int(s->conf, CONF_sshbug_plainpw1) == AUTO && - (!strcmp(imp, "Cisco-1.25") || !strcmp(imp, "OSU_1.4alpha3")))) { - /* - * These versions need a plain password sent; they can't - * handle having a null and a random length of data after - * the password. - */ - s->remote_bugs |= BUG_NEEDS_SSH1_PLAIN_PASSWORD; - bpp_logevent("We believe remote version needs a " - "plain SSH-1 password"); - } - - if (conf_get_int(s->conf, CONF_sshbug_rsa1) == FORCE_ON || - (conf_get_int(s->conf, CONF_sshbug_rsa1) == AUTO && - (!strcmp(imp, "Cisco-1.25")))) { - /* - * These versions apparently have no clue whatever about - * RSA authentication and will panic and die if they see - * an AUTH_RSA message. - */ - s->remote_bugs |= BUG_CHOKES_ON_RSA; - bpp_logevent("We believe remote version can't handle SSH-1 " - "RSA authentication"); - } - - if (conf_get_int(s->conf, CONF_sshbug_hmac2) == FORCE_ON || - (conf_get_int(s->conf, CONF_sshbug_hmac2) == AUTO && - !wc_match("* VShell", imp) && - (wc_match("2.1.0*", imp) || wc_match("2.0.*", imp) || - wc_match("2.2.0*", imp) || wc_match("2.3.0*", imp) || - wc_match("2.1 *", imp)))) { - /* - * These versions have the HMAC bug. - */ - s->remote_bugs |= BUG_SSH2_HMAC; - bpp_logevent("We believe remote version has SSH-2 HMAC bug"); - } - - if (conf_get_int(s->conf, CONF_sshbug_derivekey2) == FORCE_ON || - (conf_get_int(s->conf, CONF_sshbug_derivekey2) == AUTO && - !wc_match("* VShell", imp) && - (wc_match("2.0.0*", imp) || wc_match("2.0.10*", imp) ))) { - /* - * These versions have the key-derivation bug (failing to - * include the literal shared secret in the hashes that - * generate the keys). - */ - s->remote_bugs |= BUG_SSH2_DERIVEKEY; - bpp_logevent("We believe remote version has SSH-2 " - "key-derivation bug"); - } - - if (conf_get_int(s->conf, CONF_sshbug_rsapad2) == FORCE_ON || - (conf_get_int(s->conf, CONF_sshbug_rsapad2) == AUTO && - (wc_match("OpenSSH_2.[5-9]*", imp) || - wc_match("OpenSSH_3.[0-2]*", imp) || - wc_match("mod_sftp/0.[0-8]*", imp) || - wc_match("mod_sftp/0.9.[0-8]", imp)))) { - /* - * These versions have the SSH-2 RSA padding bug. - */ - s->remote_bugs |= BUG_SSH2_RSA_PADDING; - bpp_logevent("We believe remote version has SSH-2 RSA padding bug"); - } - - if (conf_get_int(s->conf, CONF_sshbug_pksessid2) == FORCE_ON || - (conf_get_int(s->conf, CONF_sshbug_pksessid2) == AUTO && - wc_match("OpenSSH_2.[0-2]*", imp))) { - /* - * These versions have the SSH-2 session-ID bug in - * public-key authentication. - */ - s->remote_bugs |= BUG_SSH2_PK_SESSIONID; - bpp_logevent("We believe remote version has SSH-2 " - "public-key-session-ID bug"); - } - - if (conf_get_int(s->conf, CONF_sshbug_rekey2) == FORCE_ON || - (conf_get_int(s->conf, CONF_sshbug_rekey2) == AUTO && - (wc_match("DigiSSH_2.0", imp) || - wc_match("OpenSSH_2.[0-4]*", imp) || - wc_match("OpenSSH_2.5.[0-3]*", imp) || - wc_match("Sun_SSH_1.0", imp) || - wc_match("Sun_SSH_1.0.1", imp) || - /* 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. - */ - s->remote_bugs |= BUG_SSH2_REKEY; - bpp_logevent("We believe remote version has SSH-2 rekey bug"); - } - - if (conf_get_int(s->conf, CONF_sshbug_maxpkt2) == FORCE_ON || - (conf_get_int(s->conf, CONF_sshbug_maxpkt2) == AUTO && - (wc_match("1.36_sshlib GlobalSCAPE", imp) || - wc_match("1.36 sshlib: GlobalScape", imp)))) { - /* - * This version ignores our makpkt and needs to be throttled. - */ - s->remote_bugs |= BUG_SSH2_MAXPKT; - bpp_logevent("We believe remote version ignores SSH-2 " - "maximum packet size"); - } - - if (conf_get_int(s->conf, CONF_sshbug_ignore2) == FORCE_ON) { - /* - * Servers that don't support SSH2_MSG_IGNORE. Currently, - * none detected automatically. - */ - s->remote_bugs |= BUG_CHOKES_ON_SSH2_IGNORE; - bpp_logevent("We believe remote version has SSH-2 ignore bug"); - } - - if (conf_get_int(s->conf, CONF_sshbug_oldgex2) == FORCE_ON || - (conf_get_int(s->conf, CONF_sshbug_oldgex2) == AUTO && - (wc_match("OpenSSH_2.[235]*", imp)))) { - /* - * These versions only support the original (pre-RFC4419) - * SSH-2 GEX request, and disconnect with a protocol error if - * we use the newer version. - */ - s->remote_bugs |= BUG_SSH2_OLDGEX; - bpp_logevent("We believe remote version has outdated SSH-2 GEX"); - } - - if (conf_get_int(s->conf, CONF_sshbug_winadj) == FORCE_ON) { - /* - * Servers that don't support our winadj request for one - * reason or another. Currently, none detected automatically. - */ - s->remote_bugs |= BUG_CHOKES_ON_WINADJ; - bpp_logevent("We believe remote version has winadj bug"); - } - - if (conf_get_int(s->conf, CONF_sshbug_chanreq) == FORCE_ON || - (conf_get_int(s->conf, CONF_sshbug_chanreq) == AUTO && - (wc_match("OpenSSH_[2-5].*", imp) || - wc_match("OpenSSH_6.[0-6]*", imp) || - wc_match("dropbear_0.[2-4][0-9]*", imp) || - wc_match("dropbear_0.5[01]*", imp)))) { - /* - * These versions have the SSH-2 channel request bug. - * OpenSSH 6.7 and above do not: - * https://bugzilla.mindrot.org/show_bug.cgi?id=1818 - * dropbear_0.52 and above do not: - * https://secure.ucc.asn.au/hg/dropbear/rev/cd02449b709c - */ - s->remote_bugs |= BUG_SENDS_LATE_REQUEST_REPLY; - bpp_logevent("We believe remote version has SSH-2 " - "channel request bug"); - } -} - -const char *ssh_verstring_get_remote(BinaryPacketProtocol *bpp) -{ - struct ssh_verstring_state *s = - container_of(bpp, struct ssh_verstring_state, bpp); - return s->vstring->s; -} - -const char *ssh_verstring_get_local(BinaryPacketProtocol *bpp) -{ - struct ssh_verstring_state *s = - container_of(bpp, struct ssh_verstring_state, bpp); - return s->our_vstring; -} - -int ssh_verstring_get_bugs(BinaryPacketProtocol *bpp) -{ - struct ssh_verstring_state *s = - container_of(bpp, struct ssh_verstring_state, bpp); - return s->remote_bugs; -} - -static void ssh_verstring_queue_disconnect(BinaryPacketProtocol *bpp, - const char *msg, int category) -{ - /* No way to send disconnect messages at this stage of the protocol! */ -} diff --git a/sshzlib.c b/sshzlib.c deleted file mode 100644 index 9ad04ed2..00000000 --- a/sshzlib.c +++ /dev/null @@ -1,1253 +0,0 @@ -/* - * 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 -#include -#include - -#include "defs.h" -#include "ssh.h" - -/* ---------------------------------------------------------------------- - * 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, - const unsigned char *data, int len); - -/* - * 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(const 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, - const unsigned char *data, int len) -{ - struct LZ77InternalContext *st = ctx->ictx; - int i, distance, off, nmatch, matchlen, advance; - struct Match defermatch, matches[MAXMATCH]; - int deferchr; - - assert(st->npending <= HASHCHARS); - - /* - * Add any pending characters from last time to the window. (We - * might not be able to.) - * - * This leaves st->pending empty in the usual case (when len >= - * HASHCHARS); otherwise it leaves st->pending empty enough that - * adding all the remaining 'len' characters will not push it past - * HASHCHARS in size. - */ - 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) { - - if (len >= HASHCHARS) { - /* - * Hash the next few characters. - */ - int 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; - } - - 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 { - assert(st->npending < HASHCHARS); - 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 { - strbuf *outbuf; - unsigned long outbits; - int noutbits; - bool firstblock; -}; - -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) { - put_byte(out->outbuf, 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 (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; - - 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 = lenof(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 = lenof(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); - } -} - -struct ssh_zlib_compressor { - struct LZ77Context ectx; - ssh_compressor sc; -}; - -ssh_compressor *zlib_compress_init(void) -{ - struct Outbuf *out; - struct ssh_zlib_compressor *comp = snew(struct ssh_zlib_compressor); - - lz77_init(&comp->ectx); - comp->sc.vt = &ssh_zlib; - comp->ectx.literal = zlib_literal; - comp->ectx.match = zlib_match; - - out = snew(struct Outbuf); - out->outbuf = NULL; - out->outbits = out->noutbits = 0; - out->firstblock = true; - comp->ectx.userdata = out; - - return &comp->sc; -} - -void zlib_compress_cleanup(ssh_compressor *sc) -{ - struct ssh_zlib_compressor *comp = - container_of(sc, struct ssh_zlib_compressor, sc); - struct Outbuf *out = (struct Outbuf *)comp->ectx.userdata; - if (out->outbuf) - strbuf_free(out->outbuf); - sfree(out); - sfree(comp->ectx.ictx); - sfree(comp); -} - -void zlib_compress_block(ssh_compressor *sc, - const unsigned char *block, int len, - unsigned char **outblock, int *outlen, - int minlen) -{ - struct ssh_zlib_compressor *comp = - container_of(sc, struct ssh_zlib_compressor, sc); - struct Outbuf *out = (struct Outbuf *) comp->ectx.userdata; - bool in_block; - - assert(!out->outbuf); - out->outbuf = strbuf_new_nm(); - - /* - * 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 = false; - - in_block = false; - } else - in_block = true; - - 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(&comp->ectx, block, len); - - /* - * 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 */ - - /* - * If we've been asked to pad out the compressed data until it's - * at least a given length, do so by emitting further empty static - * blocks. - */ - while (out->outbuf->len < minlen) { - outbits(out, 0, 7); /* close block */ - outbits(out, 2, 3); /* open new static block */ - } - - *outlen = out->outbuf->len; - *outblock = (unsigned char *)strbuf_to_str(out->outbuf); - out->outbuf = NULL; -} - -/* ---------------------------------------------------------------------- - * 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((size_t)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]; - - /* - * Array that accumulates the code lengths sent in the header of a - * dynamic-Huffman-tree block. - * - * There are 286 actual symbols in the literal/length alphabet - * (256 literals plus 20 length categories), and 30 symbols in the - * distance alphabet. However, the block header transmits the - * number of code lengths for the former alphabet as a 5-bit value - * HLIT to be added to 257, and the latter as a 5-bit value HDIST - * to be added to 1. This means that the number of _code lengths_ - * can go as high as 288 for the symbol alphabet and 32 for the - * distance alphabet - each of those values being 2 more than the - * maximum number of actual symbols. - * - * It's tempting to rule that sending out-of-range HLIT or HDIST - * is therefore just illegal, and to fault it when we initially - * receive that header. But instead I've chosen to permit the - * Huffman-code definition to include code length entries for - * those unused symbols; if a header of that form is transmitted, - * then the effect will be that in the main body of the block, - * some bit sequence(s) will generate an illegal symbol number, - * and _that_ will be faulted as a decoding error. - * - * Rationale: this can already happen! The standard Huffman code - * used in a _static_ block for the literal/length alphabet is - * defined in such a way that it includes codes for symbols 287 - * and 288, which are then never actually sent in the body of the - * block. And I think that if the standard static tree definition - * is willing to include Huffman codes that don't correspond to a - * symbol, then it's an excessive restriction on dynamic tables - * not to permit them to do the same. In particular, it would be - * strange for a dynamic block not to be able to exactly mimic - * either or both of the Huffman codes used by a static block for - * the corresponding alphabet. - * - * So we place no constraint on HLIT or HDIST during code - * construction, and we make this array large enough to include - * the maximum number of code lengths that can possibly arise as a - * result. It's only trying to _use_ the junk Huffman codes after - * table construction is completed that will provoke a decode - * error. - */ - unsigned char lengths[288 + 32]; - - unsigned long bits; - int nbits; - unsigned char window[WINSIZE]; - int winpos; - strbuf *outblk; - - ssh_decompressor dc; -}; - -ssh_decompressor *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; - dctx->outblk = NULL; - - dctx->dc.vt = &ssh_zlib; - return &dctx->dc; -} - -void zlib_decompress_cleanup(ssh_decompressor *dc) -{ - struct zlib_decompress_ctx *dctx = - container_of(dc, struct zlib_decompress_ctx, dc); - - 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); - if (dctx->outblk) - strbuf_free(dctx->outblk); - 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); - put_byte(dctx->outblk, c); -} - -#define EATBITS(n) ( dctx->nbits -= (n), dctx->bits >>= (n) ) - -bool zlib_decompress_block(ssh_decompressor *dc, - const unsigned char *block, int len, - unsigned char **outblock, int *outlen) -{ - struct zlib_decompress_ctx *dctx = - container_of(dc, struct zlib_decompress_ctx, dc); - 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 - }; - - assert(!dctx->outblk); - dctx->outblk = strbuf_new_nm(); - - 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) { - dctx->state = GOTLENSYM; - dctx->sym = code; - } else { - /* literal/length symbols 286 and 287 are invalid */ - goto decode_error; - } - 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; - if (code >= 30) /* dist symbols 30 and 31 are invalid */ - 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: - *outlen = dctx->outblk->len; - *outblock = (unsigned char *)strbuf_to_str(dctx->outblk); - dctx->outblk = NULL; - return true; - - decode_error: - *outblock = NULL; - *outlen = 0; - return false; -} - -const ssh_compression_alg ssh_zlib = { - .name = "zlib", - .delayed_name = "zlib@openssh.com", /* delayed version */ - .compress_new = zlib_compress_init, - .compress_free = zlib_compress_cleanup, - .compress = zlib_compress_block, - .decompress_new = zlib_decompress_init, - .decompress_free = zlib_decompress_cleanup, - .decompress = zlib_decompress_block, - .text_name = "zlib (RFC1950)", -}; diff --git a/testzlib.c b/testzlib.c index 0e7b424b..0ef4ef19 100644 --- a/testzlib.c +++ b/testzlib.c @@ -1,5 +1,5 @@ /* - * Main program to compile sshzlib.c into a zlib decoding tool. + * Main program to compile ssh/zlib.c into a zlib decoding tool. * * This is potentially a handy tool in its own right for picking apart * Zip files or PDFs or PNGs, because it accepts the bare Deflate diff --git a/unix/CMakeLists.txt b/unix/CMakeLists.txt index 9a79f145..c13abdc5 100644 --- a/unix/CMakeLists.txt +++ b/unix/CMakeLists.txt @@ -88,7 +88,7 @@ add_executable(psocks ${CMAKE_SOURCE_DIR}/psocks.c ${CMAKE_SOURCE_DIR}/norand.c ${CMAKE_SOURCE_DIR}/nocproxy.c - ${CMAKE_SOURCE_DIR}/portfwd.c + ${CMAKE_SOURCE_DIR}/ssh/portfwd.c uxnogtk.c) target_link_libraries(psocks eventloop console network utils) @@ -97,7 +97,7 @@ add_executable(psusan uxpsusan.c ${CMAKE_SOURCE_DIR}/be_none.c ${CMAKE_SOURCE_DIR}/nogss.c - ${CMAKE_SOURCE_DIR}/scpserver.c + ${CMAKE_SOURCE_DIR}/ssh/scpserver.c uxnogtk.c uxpty.c) target_link_libraries(psusan @@ -130,13 +130,13 @@ target_link_libraries(testsc crypto utils) add_executable(testzlib ${CMAKE_SOURCE_DIR}/testzlib.c - ${CMAKE_SOURCE_DIR}/sshzlib.c) + ${CMAKE_SOURCE_DIR}/ssh/zlib.c) target_link_libraries(testzlib utils) add_executable(uppity uxserver.c ${CMAKE_SOURCE_DIR}/be_none.c - ${CMAKE_SOURCE_DIR}/scpserver.c + ${CMAKE_SOURCE_DIR}/ssh/scpserver.c uxnogtk.c uxpty.c ${CMAKE_SOURCE_DIR}/nogss.c) @@ -161,7 +161,7 @@ if(GTK_FOUND) gtkask.c ux_x11.c uxnoise.c - ${CMAKE_SOURCE_DIR}/x11fwd.c) + ${CMAKE_SOURCE_DIR}/ssh/x11fwd.c) target_link_libraries(pageant guimisc eventloop console agent settings network crypto utils ${GTK_LIBRARIES}) diff --git a/unix/platform.h b/unix/platform.h index 62f86a74..6d88d84b 100644 --- a/unix/platform.h +++ b/unix/platform.h @@ -67,7 +67,7 @@ struct FontSpec *fontspec_new(const char *name); extern const struct BackendVtable pty_backend; -#define BROKEN_PIPE_ERROR_CODE EPIPE /* used in sshshare.c */ +#define BROKEN_PIPE_ERROR_CODE EPIPE /* used in ssh/sharing.c */ /* * Under GTK, we send MA_CLICK _and_ MA_2CLK, or MA_CLICK _and_ diff --git a/unix/uxgss.c b/unix/uxgss.c index 2b4e8537..cd9971c7 100644 --- a/unix/uxgss.c +++ b/unix/uxgss.c @@ -1,8 +1,8 @@ #include "putty.h" #ifndef NO_GSSAPI -#include "pgssapi.h" -#include "sshgss.h" -#include "sshgssc.h" +#include "ssh/pgssapi.h" +#include "ssh/gss.h" +#include "ssh/gssc.h" /* Unix code to set up the GSSAPI library list. */ diff --git a/unix/uxpsusan.c b/unix/uxpsusan.c index c60728ce..a9312c40 100644 --- a/unix/uxpsusan.c +++ b/unix/uxpsusan.c @@ -39,7 +39,7 @@ #include "putty.h" #include "mpint.h" #include "ssh.h" -#include "sshserver.h" +#include "ssh/server.h" const char *const appname = "psusan"; diff --git a/unix/uxpty.c b/unix/uxpty.c index 7d65a4fc..30e48e50 100644 --- a/unix/uxpty.c +++ b/unix/uxpty.c @@ -25,7 +25,7 @@ #include "putty.h" #include "ssh.h" -#include "sshserver.h" /* to check the prototypes of server-needed things */ +#include "ssh/server.h" /* to check the prototypes of server-needed things */ #include "tree234.h" #ifndef OMIT_UTMP @@ -828,7 +828,7 @@ static void copy_ttymodes_into_termios( } #define TTYMODES_LOCAL_ONLY /* omit any that this platform doesn't know */ -#include "sshttymodes.h" +#include "ssh/ttymode-list.h" #undef TTYMODES_LOCAL_ONLY #undef TTYMODE_CHAR @@ -1468,7 +1468,7 @@ static void pty_special(Backend *be, SessionSpecialCode code, int arg) #define SIGNAL_SUB(name) if (code == SS_SIG ## name) sig = SIG ## name; #define SIGNAL_MAIN(name, text) SIGNAL_SUB(name) #define SIGNALS_LOCAL_ONLY - #include "sshsignals.h" + #include "ssh/signal-list.h" #undef SIGNAL_SUB #undef SIGNAL_MAIN #undef SIGNALS_LOCAL_ONLY @@ -1564,7 +1564,7 @@ ptrlen pty_backend_exit_signame(Backend *be, char **aux_msg) } #define SIGNAL_MAIN(s, desc) SIGNAL_SUB(s) #define SIGNALS_LOCAL_ONLY - #include "sshsignals.h" + #include "ssh/signal-list.h" #undef SIGNAL_MAIN #undef SIGNAL_SUB #undef SIGNALS_LOCAL_ONLY diff --git a/unix/uxserver.c b/unix/uxserver.c index 448c6515..1a35451b 100644 --- a/unix/uxserver.c +++ b/unix/uxserver.c @@ -41,7 +41,7 @@ #include "putty.h" #include "mpint.h" #include "ssh.h" -#include "sshserver.h" +#include "ssh/server.h" const char *const appname = "uppity"; diff --git a/unix/uxsftpserver.c b/unix/uxsftpserver.c index acefe9bd..7257c5c9 100644 --- a/unix/uxsftpserver.c +++ b/unix/uxsftpserver.c @@ -20,8 +20,8 @@ #include "putty.h" #include "ssh.h" -#include "sshserver.h" -#include "sftp.h" +#include "ssh/server.h" +#include "ssh/sftp.h" #include "tree234.h" typedef struct UnixSftpServer UnixSftpServer; diff --git a/utils/sshutils.c b/utils/sshutils.c index 1ee3342b..49e82219 100644 --- a/utils/sshutils.c +++ b/utils/sshutils.c @@ -8,7 +8,7 @@ #include "putty.h" #include "ssh.h" -#include "sshchan.h" +#include "ssh/channel.h" /* ---------------------------------------------------------------------- * Centralised standard methods for other channel implementations to diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt index d063b9d3..c37abd1e 100644 --- a/windows/CMakeLists.txt +++ b/windows/CMakeLists.txt @@ -111,7 +111,7 @@ add_executable(psocks ${CMAKE_SOURCE_DIR}/psocks.c ${CMAKE_SOURCE_DIR}/norand.c ${CMAKE_SOURCE_DIR}/nocproxy.c - ${CMAKE_SOURCE_DIR}/portfwd.c) + ${CMAKE_SOURCE_DIR}/ssh/portfwd.c) target_link_libraries(psocks eventloop console network utils ${platform_libraries}) diff --git a/windows/platform.h b/windows/platform.h index cd89d411..0011ed24 100644 --- a/windows/platform.h +++ b/windows/platform.h @@ -121,7 +121,7 @@ static inline uintmax_t strtoumax(const char *nptr, char **endptr, int base) #define strnicmp strncasecmp #endif -#define BROKEN_PIPE_ERROR_CODE ERROR_BROKEN_PIPE /* used in sshshare.c */ +#define BROKEN_PIPE_ERROR_CODE ERROR_BROKEN_PIPE /* used in ssh/sharing.c */ /* * Dynamically linked functions. These come in two flavours: @@ -261,7 +261,7 @@ void write_aclip(int clipboard, char *, int, bool); * 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 :-) + * implementation of XDM-AUTHORIZATION-1 in ssh/x11fwd.c :-) */ #define sk_getxdmdata(socket, lenp) (NULL) diff --git a/windows/wingss.c b/windows/wingss.c index ae36eb13..0b47d9a7 100644 --- a/windows/wingss.c +++ b/windows/wingss.c @@ -6,9 +6,9 @@ #define SECURITY_WIN32 #include -#include "pgssapi.h" -#include "sshgss.h" -#include "sshgssc.h" +#include "ssh/pgssapi.h" +#include "ssh/gss.h" +#include "ssh/gssc.h" #include "misc.h" diff --git a/x11disp.c b/x11disp.c index 58ffc1e6..1dbc9c07 100644 --- a/x11disp.c +++ b/x11disp.c @@ -10,7 +10,7 @@ #include "putty.h" #include "ssh.h" -#include "sshchan.h" +#include "ssh/channel.h" #include "tree234.h" struct X11Display *x11_setup_display(const char *display, Conf *conf, diff --git a/x11fwd.c b/x11fwd.c deleted file mode 100644 index a5066dd1..00000000 --- a/x11fwd.c +++ /dev/null @@ -1,639 +0,0 @@ -/* - * Platform-independent bits of X11 forwarding. - */ - -#include -#include -#include -#include - -#include "putty.h" -#include "ssh.h" -#include "sshchan.h" -#include "tree234.h" - -struct XDMSeen { - unsigned int time; - unsigned char clientid[6]; -}; - -typedef struct X11Connection { - unsigned char firstpkt[12]; /* first X data packet */ - tree234 *authtree; - struct X11Display *disp; - char *auth_protocol; - unsigned char *auth_data; - int data_read, auth_plen, auth_psize, auth_dlen, auth_dsize; - bool verified; - bool input_wanted; - bool no_data_sent_to_x_client; - char *peer_addr; - int peer_port; - SshChannel *c; /* channel structure held by SSH backend */ - Socket *s; - - Plug plug; - Channel chan; -} X11Connection; - -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)); -} - -struct X11FakeAuth *x11_invent_fake_auth(tree234 *authtree, int authtype) -{ - struct X11FakeAuth *auth = snew(struct X11FakeAuth); - int i; - - /* - * This function has the job of inventing a set of X11 fake auth - * data, and adding it to 'authtree'. We must preserve the - * property that for any given actual authorisation attempt, _at - * most one_ thing in the tree can possibly match it. - * - * For MIT-MAGIC-COOKIE-1, that's not too difficult: the match - * criterion is simply that the entire cookie is correct, so we - * just have to make sure we don't make up two cookies the same. - * (Vanishingly unlikely, but we check anyway to be sure, and go - * round again inventing a new cookie if add234 tells us the one - * we thought of is already in use.) - * - * For XDM-AUTHORIZATION-1, it's a little more fiddly. The setup - * with XA1 is that half the cookie is used as a DES key with - * which to CBC-encrypt an assortment of stuff. Happily, the stuff - * encrypted _begins_ with the other half of the cookie, and the - * IV is always zero, which means that any valid XA1 authorisation - * attempt for a given cookie must begin with the same cipher - * block, consisting of the DES ECB encryption of the first half - * of the cookie using the second half as a key. So we compute - * that cipher block here and now, and use it as the sorting key - * for distinguishing XA1 entries in the tree. - */ - - if (authtype == X11_MIT) { - auth->proto = X11_MIT; - - /* MIT-MAGIC-COOKIE-1. Cookie size is 128 bits (16 bytes). */ - auth->datalen = 16; - auth->data = snewn(auth->datalen, unsigned char); - auth->xa1_firstblock = NULL; - - while (1) { - random_read(auth->data, auth->datalen); - if (add234(authtree, auth) == auth) - break; - } - - auth->xdmseen = NULL; - } else { - assert(authtype == X11_XDM); - auth->proto = X11_XDM; - - /* XDM-AUTHORIZATION-1. Cookie size is 16 bytes; byte 8 is zero. */ - auth->datalen = 16; - auth->data = snewn(auth->datalen, unsigned char); - auth->xa1_firstblock = snewn(8, unsigned char); - memset(auth->xa1_firstblock, 0, 8); - - while (1) { - random_read(auth->data, 15); - auth->data[15] = auth->data[8]; - auth->data[8] = 0; - - memcpy(auth->xa1_firstblock, auth->data, 8); - des_encrypt_xdmauth(auth->data + 9, auth->xa1_firstblock, 8); - if (add234(authtree, auth) == auth) - break; - } - - auth->xdmseen = newtree234(xdmseen_cmp); - } - auth->protoname = dupstr(x11_authnames[auth->proto]); - auth->datastring = snewn(auth->datalen * 2 + 1, char); - for (i = 0; i < auth->datalen; i++) - sprintf(auth->datastring + i*2, "%02x", - auth->data[i]); - - auth->disp = NULL; - auth->share_cs = NULL; - auth->share_chan = NULL; - - return auth; -} - -void x11_free_fake_auth(struct X11FakeAuth *auth) -{ - if (auth->data) - smemclr(auth->data, auth->datalen); - sfree(auth->data); - sfree(auth->protoname); - sfree(auth->datastring); - sfree(auth->xa1_firstblock); - if (auth->xdmseen != NULL) { - struct XDMSeen *seen; - while ((seen = delpos234(auth->xdmseen, 0)) != NULL) - sfree(seen); - freetree234(auth->xdmseen); - } - sfree(auth); -} - -int x11_authcmp(void *av, void *bv) -{ - struct X11FakeAuth *a = (struct X11FakeAuth *)av; - struct X11FakeAuth *b = (struct X11FakeAuth *)bv; - - if (a->proto < b->proto) - return -1; - else if (a->proto > b->proto) - return +1; - - if (a->proto == X11_MIT) { - if (a->datalen < b->datalen) - return -1; - else if (a->datalen > b->datalen) - return +1; - - return memcmp(a->data, b->data, a->datalen); - } else { - assert(a->proto == X11_XDM); - - return memcmp(a->xa1_firstblock, b->xa1_firstblock, 8); - } -} - -#define XDM_MAXSKEW 20*60 /* 20 minute clock skew should be OK */ - -static const char *x11_verify(unsigned long peer_ip, int peer_port, - tree234 *authtree, char *proto, - unsigned char *data, int dlen, - struct X11FakeAuth **auth_ret) -{ - struct X11FakeAuth match_dummy; /* for passing to find234 */ - struct X11FakeAuth *auth; - - /* - * First, do a lookup in our tree to find the only authorisation - * record that _might_ match. - */ - if (!strcmp(proto, x11_authnames[X11_MIT])) { - /* - * Just look up the whole cookie that was presented to us, - * which x11_authcmp will compare against the cookies we - * currently believe in. - */ - match_dummy.proto = X11_MIT; - match_dummy.datalen = dlen; - match_dummy.data = data; - } else if (!strcmp(proto, x11_authnames[X11_XDM])) { - /* - * Look up the first cipher block, against the stored first - * cipher blocks for the XDM-AUTHORIZATION-1 cookies we - * currently know. (See comment in x11_invent_fake_auth.) - */ - match_dummy.proto = X11_XDM; - match_dummy.xa1_firstblock = data; - } else { - return "Unsupported authorisation protocol"; - } - - if ((auth = find234(authtree, &match_dummy, 0)) == NULL) - return "Authorisation not recognised"; - - /* - * If we're using MIT-MAGIC-COOKIE-1, that was all we needed. If - * we're doing XDM-AUTHORIZATION-1, though, we have to check the - * rest of the auth data. - */ - if (auth->proto == X11_XDM) { - unsigned long t; - time_t tim; - int i; - struct XDMSeen *seen, *ret; - - if (dlen != 24) - return "XDM-AUTHORIZATION-1 data was wrong length"; - if (peer_port == -1) - return "cannot do XDM-AUTHORIZATION-1 without remote address data"; - des_decrypt_xdmauth(auth->data+9, data, 24); - if (memcmp(auth->data, data, 8) != 0) - return "XDM-AUTHORIZATION-1 data failed check"; /* cookie wrong */ - if (GET_32BIT_MSB_FIRST(data+8) != peer_ip) - return "XDM-AUTHORIZATION-1 data failed check"; /* IP wrong */ - if ((int)GET_16BIT_MSB_FIRST(data+12) != peer_port) - return "XDM-AUTHORIZATION-1 data failed check"; /* port wrong */ - 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 (((unsigned long)t - (unsigned long)tim - + XDM_MAXSKEW) > 2*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(auth->xdmseen != NULL); - ret = add234(auth->xdmseen, seen); - if (ret != seen) { - sfree(seen); - return "XDM-AUTHORIZATION-1 data replayed"; - } - /* While we're here, purge entries too old to be replayed. */ - for (;;) { - seen = index234(auth->xdmseen, 0); - assert(seen != NULL); - if (t - seen->time <= XDM_MAXSKEW) - break; - sfree(delpos234(auth->xdmseen, 0)); - } - } - /* implement other protocols here if ever required */ - - *auth_ret = auth; - return NULL; -} - -static void x11_log(Plug *p, PlugLogType type, SockAddr *addr, int port, - const char *error_msg, int error_code) -{ - /* We have no interface to the logging module here, so we drop these. */ -} - -static void x11_send_init_error(struct X11Connection *conn, - const char *err_message); - -static void x11_closing(Plug *plug, const char *error_msg, int error_code, - bool calling_back) -{ - struct X11Connection *xconn = container_of( - plug, struct X11Connection, plug); - - if (error_msg) { - /* - * Socket error. If we're still at the connection setup stage, - * construct an X11 error packet passing on the problem. - */ - if (xconn->no_data_sent_to_x_client) { - char *err_message = dupprintf("unable to connect to forwarded " - "X server: %s", error_msg); - x11_send_init_error(xconn, err_message); - sfree(err_message); - } - - /* - * Whether we did that or not, now we slam the connection - * shut. - */ - sshfwd_initiate_close(xconn->c, error_msg); - } else { - /* - * Ordinary EOF received on socket. Send an EOF on the SSH - * channel. - */ - if (xconn->c) - sshfwd_write_eof(xconn->c); - } -} - -static void x11_receive(Plug *plug, int urgent, const char *data, size_t len) -{ - struct X11Connection *xconn = container_of( - plug, struct X11Connection, plug); - - xconn->no_data_sent_to_x_client = false; - sshfwd_write(xconn->c, data, len); -} - -static void x11_sent(Plug *plug, size_t bufsize) -{ - struct X11Connection *xconn = container_of( - plug, struct X11Connection, plug); - - sshfwd_unthrottle(xconn->c, bufsize); -} - -static const PlugVtable X11Connection_plugvt = { - .log = x11_log, - .closing = x11_closing, - .receive = x11_receive, - .sent = x11_sent, -}; - -static void x11_chan_free(Channel *chan); -static size_t x11_send( - Channel *chan, bool is_stderr, const void *vdata, size_t len); -static void x11_send_eof(Channel *chan); -static void x11_set_input_wanted(Channel *chan, bool wanted); -static char *x11_log_close_msg(Channel *chan); - -static const ChannelVtable X11Connection_channelvt = { - .free = x11_chan_free, - .open_confirmation = chan_remotely_opened_confirmation, - .open_failed = chan_remotely_opened_failure, - .send = x11_send, - .send_eof = x11_send_eof, - .set_input_wanted = x11_set_input_wanted, - .log_close_msg = x11_log_close_msg, - .want_close = chan_default_want_close, - .rcvd_exit_status = chan_no_exit_status, - .rcvd_exit_signal = chan_no_exit_signal, - .rcvd_exit_signal_numeric = chan_no_exit_signal_numeric, - .run_shell = chan_no_run_shell, - .run_command = chan_no_run_command, - .run_subsystem = chan_no_run_subsystem, - .enable_x11_forwarding = chan_no_enable_x11_forwarding, - .enable_agent_forwarding = chan_no_enable_agent_forwarding, - .allocate_pty = chan_no_allocate_pty, - .set_env = chan_no_set_env, - .send_break = chan_no_send_break, - .send_signal = chan_no_send_signal, - .change_window_size = chan_no_change_window_size, - .request_response = chan_no_request_response, -}; - -/* - * Called to set up the X11Connection structure, though this does not - * yet connect to an actual server. - */ -Channel *x11_new_channel(tree234 *authtree, SshChannel *c, - const char *peeraddr, int peerport, - bool connection_sharing_possible) -{ - struct X11Connection *xconn; - - /* - * Open socket. - */ - xconn = snew(struct X11Connection); - xconn->plug.vt = &X11Connection_plugvt; - xconn->chan.vt = &X11Connection_channelvt; - xconn->chan.initial_fixed_window_size = - (connection_sharing_possible ? 128 : 0); - xconn->auth_protocol = NULL; - xconn->authtree = authtree; - xconn->verified = false; - xconn->data_read = 0; - xconn->input_wanted = true; - xconn->no_data_sent_to_x_client = true; - xconn->c = c; - - /* - * We don't actually open a local socket to the X server just yet, - * because we don't know which one it is. Instead, we'll wait - * until we see the incoming authentication data, which may tell - * us what display to connect to, or whether we have to divert - * this X forwarding channel to a connection-sharing downstream - * rather than handling it ourself. - */ - xconn->disp = NULL; - xconn->s = NULL; - - /* - * Stash the peer address we were given in its original text form. - */ - xconn->peer_addr = peeraddr ? dupstr(peeraddr) : NULL; - xconn->peer_port = peerport; - - return &xconn->chan; -} - -static void x11_chan_free(Channel *chan) -{ - assert(chan->vt == &X11Connection_channelvt); - X11Connection *xconn = container_of(chan, X11Connection, chan); - - if (xconn->auth_protocol) { - sfree(xconn->auth_protocol); - sfree(xconn->auth_data); - } - - if (xconn->s) - sk_close(xconn->s); - - sfree(xconn->peer_addr); - sfree(xconn); -} - -static void x11_set_input_wanted(Channel *chan, bool wanted) -{ - assert(chan->vt == &X11Connection_channelvt); - X11Connection *xconn = container_of(chan, X11Connection, chan); - - xconn->input_wanted = wanted; - if (xconn->s) - sk_set_frozen(xconn->s, !xconn->input_wanted); -} - -static void x11_send_init_error(struct X11Connection *xconn, - const char *err_message) -{ - char *full_message; - int msglen, msgsize; - unsigned char *reply; - - full_message = dupprintf("%s X11 proxy: %s\n", appname, err_message); - - msglen = strlen(full_message); - reply = snewn(8 + msglen+1 + 4, unsigned char); /* include zero */ - msgsize = (msglen + 3) & ~3; - reply[0] = 0; /* failure */ - reply[1] = msglen; /* length of reason string */ - memcpy(reply + 2, xconn->firstpkt + 2, 4); /* major/minor proto vsn */ - PUT_16BIT_X11(xconn->firstpkt[0], reply + 6, msgsize >> 2);/* data len */ - memset(reply + 8, 0, msgsize); - memcpy(reply + 8, full_message, msglen); - sshfwd_write(xconn->c, reply, 8 + msgsize); - sshfwd_write_eof(xconn->c); - xconn->no_data_sent_to_x_client = false; - sfree(reply); - sfree(full_message); -} - -/* - * Called to send data down the raw connection. - */ -static size_t x11_send( - Channel *chan, bool is_stderr, const void *vdata, size_t len) -{ - assert(chan->vt == &X11Connection_channelvt); - X11Connection *xconn = container_of(chan, X11Connection, chan); - const char *data = (const char *)vdata; - - /* - * Read the first packet. - */ - while (len > 0 && xconn->data_read < 12) - xconn->firstpkt[xconn->data_read++] = (unsigned char) (len--, *data++); - if (xconn->data_read < 12) - return 0; - - /* - * If we have not allocated the auth_protocol and auth_data - * strings, do so now. - */ - if (!xconn->auth_protocol) { - char endian = xconn->firstpkt[0]; - xconn->auth_plen = GET_16BIT_X11(endian, xconn->firstpkt + 6); - xconn->auth_dlen = GET_16BIT_X11(endian, xconn->firstpkt + 8); - xconn->auth_psize = (xconn->auth_plen + 3) & ~3; - xconn->auth_dsize = (xconn->auth_dlen + 3) & ~3; - /* Leave room for a terminating zero, to make our lives easier. */ - xconn->auth_protocol = snewn(xconn->auth_psize + 1, char); - xconn->auth_data = snewn(xconn->auth_dsize, unsigned char); - } - - /* - * Read the auth_protocol and auth_data strings. - */ - while (len > 0 && - xconn->data_read < 12 + xconn->auth_psize) - xconn->auth_protocol[xconn->data_read++ - 12] = (len--, *data++); - while (len > 0 && - xconn->data_read < 12 + xconn->auth_psize + xconn->auth_dsize) - xconn->auth_data[xconn->data_read++ - 12 - - xconn->auth_psize] = (unsigned char) (len--, *data++); - if (xconn->data_read < 12 + xconn->auth_psize + xconn->auth_dsize) - return 0; - - /* - * If we haven't verified the authorisation, do so now. - */ - if (!xconn->verified) { - const char *err; - struct X11FakeAuth *auth_matched = NULL; - unsigned long peer_ip; - int peer_port; - int protomajor, protominor; - void *greeting; - int greeting_len; - unsigned char *socketdata; - int socketdatalen; - char new_peer_addr[32]; - int new_peer_port; - char endian = xconn->firstpkt[0]; - - protomajor = GET_16BIT_X11(endian, xconn->firstpkt + 2); - protominor = GET_16BIT_X11(endian, xconn->firstpkt + 4); - - assert(!xconn->s); - - xconn->auth_protocol[xconn->auth_plen] = '\0'; /* ASCIZ */ - - peer_ip = 0; /* placate optimiser */ - if (x11_parse_ip(xconn->peer_addr, &peer_ip)) - peer_port = xconn->peer_port; - else - peer_port = -1; /* signal no peer address data available */ - - err = x11_verify(peer_ip, peer_port, - xconn->authtree, xconn->auth_protocol, - xconn->auth_data, xconn->auth_dlen, &auth_matched); - if (err) { - x11_send_init_error(xconn, err); - return 0; - } - assert(auth_matched); - - /* - * If this auth points to a connection-sharing downstream - * rather than an X display we know how to connect to - * directly, pass it off to the sharing module now. (This will - * have the side effect of freeing xconn.) - */ - if (auth_matched->share_cs) { - sshfwd_x11_sharing_handover(xconn->c, auth_matched->share_cs, - auth_matched->share_chan, - xconn->peer_addr, xconn->peer_port, - xconn->firstpkt[0], - protomajor, protominor, data, len); - return 0; - } - - /* - * Now we know we're going to accept the connection, and what - * X display to connect to. Actually connect to it. - */ - xconn->chan.initial_fixed_window_size = 0; - sshfwd_window_override_removed(xconn->c); - xconn->disp = auth_matched->disp; - xconn->s = new_connection(sk_addr_dup(xconn->disp->addr), - xconn->disp->realhost, xconn->disp->port, - false, true, false, false, &xconn->plug, - sshfwd_get_conf(xconn->c)); - if ((err = sk_socket_error(xconn->s)) != NULL) { - char *err_message = dupprintf("unable to connect to" - " forwarded X server: %s", err); - x11_send_init_error(xconn, err_message); - sfree(err_message); - return 0; - } - - /* - * Write a new connection header containing our replacement - * auth data. - */ - socketdatalen = 0; /* placate compiler warning */ - socketdata = sk_getxdmdata(xconn->s, &socketdatalen); - if (socketdata && socketdatalen==6) { - sprintf(new_peer_addr, "%d.%d.%d.%d", socketdata[0], - socketdata[1], socketdata[2], socketdata[3]); - new_peer_port = GET_16BIT_MSB_FIRST(socketdata + 4); - } else { - strcpy(new_peer_addr, "0.0.0.0"); - new_peer_port = 0; - } - - greeting = x11_make_greeting(xconn->firstpkt[0], - protomajor, protominor, - xconn->disp->localauthproto, - xconn->disp->localauthdata, - xconn->disp->localauthdatalen, - new_peer_addr, new_peer_port, - &greeting_len); - - sk_write(xconn->s, greeting, greeting_len); - - smemclr(greeting, greeting_len); - sfree(greeting); - - /* - * Now we're done. - */ - xconn->verified = true; - } - - /* - * After initialisation, just copy data simply. - */ - - return sk_write(xconn->s, data, len); -} - -static void x11_send_eof(Channel *chan) -{ - assert(chan->vt == &X11Connection_channelvt); - X11Connection *xconn = container_of(chan, X11Connection, chan); - - if (xconn->s) { - sk_write_eof(xconn->s); - } else { - /* - * If EOF is received from the X client before we've got to - * the point of actually connecting to an X server, then we - * should send an EOF back to the client so that the - * forwarded channel will be terminated. - */ - if (xconn->c) - sshfwd_write_eof(xconn->c); - } -} - -static char *x11_log_close_msg(Channel *chan) -{ - return dupstr("Forwarded X11 connection terminated"); -} -- cgit v1.2.3 From 8f0f5b69c0667900873738ce9bd37dbb1e6c28e8 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 22 Apr 2021 17:59:16 +0100 Subject: Move key-generation code into its own subdir. Including mpunsafe.{h,c}, which should be an extra defence against inadvertently using it outside the keygen library. --- CMakeLists.txt | 3 +- keygen/CMakeLists.txt | 10 + keygen/dsa.c | 103 +++++++ keygen/ecdsa.c | 37 +++ keygen/millerrabin.c | 214 ++++++++++++++ keygen/mpunsafe.c | 57 ++++ keygen/mpunsafe.h | 46 +++ keygen/pockle.c | 450 ++++++++++++++++++++++++++++ keygen/prime.c | 762 ++++++++++++++++++++++++++++++++++++++++++++++++ keygen/primecandidate.c | 445 ++++++++++++++++++++++++++++ keygen/rsa.c | 292 +++++++++++++++++++ keygen/smallprimes.c | 44 +++ millerrabin.c | 214 -------------- mpunsafe.c | 57 ---- mpunsafe.h | 46 --- pockle.c | 450 ---------------------------- primecandidate.c | 445 ---------------------------- smallprimes.c | 44 --- sshdssg.c | 103 ------- sshecdsag.c | 37 --- sshprime.c | 762 ------------------------------------------------ sshrsag.c | 292 ------------------- 22 files changed, 2461 insertions(+), 2452 deletions(-) create mode 100644 keygen/CMakeLists.txt create mode 100644 keygen/dsa.c create mode 100644 keygen/ecdsa.c create mode 100644 keygen/millerrabin.c create mode 100644 keygen/mpunsafe.c create mode 100644 keygen/mpunsafe.h create mode 100644 keygen/pockle.c create mode 100644 keygen/prime.c create mode 100644 keygen/primecandidate.c create mode 100644 keygen/rsa.c create mode 100644 keygen/smallprimes.c delete mode 100644 millerrabin.c delete mode 100644 mpunsafe.c delete mode 100644 mpunsafe.h delete mode 100644 pockle.c delete mode 100644 primecandidate.c delete mode 100644 smallprimes.c delete mode 100644 sshdssg.c delete mode 100644 sshecdsag.c delete mode 100644 sshprime.c delete mode 100644 sshrsag.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 3b28c065..2a944fc7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,9 +30,8 @@ add_library(network STATIC be_misc.c nullplug.c errsock.c proxy.c logging.c x11disp.c) add_library(keygen STATIC - millerrabin.c mpunsafe.c pockle.c primecandidate.c smallprimes.c - sshdssg.c sshecdsag.c sshprime.c sshrsag.c import.c) +add_subdirectory(keygen) add_library(agent STATIC sshpubk.c pageant.c aqsync.c) diff --git a/keygen/CMakeLists.txt b/keygen/CMakeLists.txt new file mode 100644 index 00000000..17eea2b1 --- /dev/null +++ b/keygen/CMakeLists.txt @@ -0,0 +1,10 @@ +add_sources_from_current_dir(keygen + dsa.c + ecdsa.c + millerrabin.c + mpunsafe.c + pockle.c + prime.c + primecandidate.c + rsa.c + smallprimes.c) diff --git a/keygen/dsa.c b/keygen/dsa.c new file mode 100644 index 00000000..3b527256 --- /dev/null +++ b/keygen/dsa.c @@ -0,0 +1,103 @@ +/* + * DSS key generation. + */ + +#include "misc.h" +#include "ssh.h" +#include "sshkeygen.h" +#include "mpint.h" + +int dsa_generate(struct dss_key *key, int bits, PrimeGenerationContext *pgc, + ProgressReceiver *prog) +{ + /* + * Progress-reporting setup. + * + * DSA generation involves three potentially long jobs: inventing + * the small prime q, the large prime p, and finding an order-q + * element of the multiplicative group of p. + * + * The latter is done by finding an element whose order is + * _divisible_ by q and raising it to the power of (p-1)/q. Every + * element whose order is not divisible by q is a qth power of q + * distinct elements whose order _is_ divisible by q, so the + * probability of not finding a suitable element on the first try + * is in the region of 1/q, i.e. at most 2^-159. + * + * (So the probability of success will end up indistinguishable + * from 1 in IEEE standard floating point! But what can you do.) + */ + ProgressPhase phase_q = primegen_add_progress_phase(pgc, prog, 160); + ProgressPhase phase_p = primegen_add_progress_phase(pgc, prog, bits); + double g_failure_probability = 1.0 + / (double)(1ULL << 53) + / (double)(1ULL << 53) + / (double)(1ULL << 53); + ProgressPhase phase_g = progress_add_probabilistic( + prog, estimate_modexp_cost(bits), 1.0 - g_failure_probability); + progress_ready(prog); + + PrimeCandidateSource *pcs; + + /* + * Generate q: a prime of length 160. + */ + progress_start_phase(prog, phase_q); + pcs = pcs_new(160); + mp_int *q = primegen_generate(pgc, pcs, prog); + progress_report_phase_complete(prog); + + /* + * Now generate p: a prime of length `bits', such that p-1 is + * divisible by q. + */ + progress_start_phase(prog, phase_p); + pcs = pcs_new(bits); + pcs_require_residue_1_mod_prime(pcs, q); + mp_int *p = primegen_generate(pgc, pcs, prog); + progress_report_phase_complete(prog); + + /* + * 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. + */ + progress_start_phase(prog, phase_g); + mp_int *power = mp_div(p, q); /* this is floor(p/q) == (p-1)/q */ + mp_int *h = mp_from_integer(2); + mp_int *g; + while (1) { + progress_report_attempt(prog); + g = mp_modpow(h, power, p); + if (mp_hs_integer(g, 2)) + break; /* got one */ + mp_free(g); + mp_add_integer_into(h, h, 1); + } + mp_free(h); + mp_free(power); + progress_report_phase_complete(prog); + + /* + * 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. + */ + mp_int *two = mp_from_integer(2); + mp_int *qm1 = mp_copy(q); + mp_sub_integer_into(qm1, qm1, 1); + mp_int *x = mp_random_in_range(two, qm1); + mp_free(two); + mp_free(qm1); + + key->sshk.vt = &ssh_dss; + + key->p = p; + key->q = q; + key->g = g; + key->x = x; + key->y = mp_modpow(key->g, key->x, key->p); + + return 1; +} diff --git a/keygen/ecdsa.c b/keygen/ecdsa.c new file mode 100644 index 00000000..28a723b2 --- /dev/null +++ b/keygen/ecdsa.c @@ -0,0 +1,37 @@ +/* + * EC key generation. + */ + +#include "ssh.h" +#include "sshkeygen.h" +#include "mpint.h" + +int ecdsa_generate(struct ecdsa_key *ek, int bits) +{ + if (!ec_nist_alg_and_curve_by_bits(bits, &ek->curve, &ek->sshk.vt)) + return 0; + + mp_int *one = mp_from_integer(1); + ek->privateKey = mp_random_in_range(one, ek->curve->w.G_order); + mp_free(one); + + ek->publicKey = ecdsa_public(ek->privateKey, ek->sshk.vt); + + return 1; +} + +int eddsa_generate(struct eddsa_key *ek, int bits) +{ + if (!ec_ed_alg_and_curve_by_bits(bits, &ek->curve, &ek->sshk.vt)) + return 0; + + /* EdDSA secret keys are just 32 bytes of hash preimage; the + * 64-byte SHA-512 hash of that key will be used when signing, + * but the form of the key stored on disk is the preimage + * only. */ + ek->privateKey = mp_random_bits(bits); + + ek->publicKey = eddsa_public(ek->privateKey, ek->sshk.vt); + + return 1; +} diff --git a/keygen/millerrabin.c b/keygen/millerrabin.c new file mode 100644 index 00000000..3358bc51 --- /dev/null +++ b/keygen/millerrabin.c @@ -0,0 +1,214 @@ +/* + * millerrabin.c: Miller-Rabin probabilistic primality testing, as + * declared in sshkeygen.h. + */ + +#include +#include "ssh.h" +#include "sshkeygen.h" +#include "mpint.h" +#include "mpunsafe.h" + +/* + * 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.) + */ + +struct MillerRabin { + MontyContext *mc; + + size_t k; + mp_int *q; + + mp_int *two, *pm1, *m_pm1; +}; + +MillerRabin *miller_rabin_new(mp_int *p) +{ + MillerRabin *mr = snew(MillerRabin); + + assert(mp_hs_integer(p, 2)); + assert(mp_get_bit(p, 0) == 1); + + mr->k = 1; + while (!mp_get_bit(p, mr->k)) + mr->k++; + mr->q = mp_rshift_safe(p, mr->k); + + mr->two = mp_from_integer(2); + + mr->pm1 = mp_unsafe_copy(p); + mp_sub_integer_into(mr->pm1, mr->pm1, 1); + + mr->mc = monty_new(p); + mr->m_pm1 = monty_import(mr->mc, mr->pm1); + + return mr; +} + +void miller_rabin_free(MillerRabin *mr) +{ + mp_free(mr->q); + mp_free(mr->two); + mp_free(mr->pm1); + mp_free(mr->m_pm1); + monty_free(mr->mc); + smemclr(mr, sizeof(*mr)); + sfree(mr); +} + +struct mr_result { + bool passed; + bool potential_primitive_root; +}; + +static struct mr_result miller_rabin_test_inner(MillerRabin *mr, mp_int *w) +{ + /* + * Compute w^q mod p. + */ + mp_int *wqp = monty_pow(mr->mc, w, mr->q); + + /* + * See if this is 1, or if it is -1, or if it becomes -1 + * when squared at most k-1 times. + */ + struct mr_result result; + result.passed = false; + result.potential_primitive_root = false; + + if (mp_cmp_eq(wqp, monty_identity(mr->mc))) { + result.passed = true; + } else { + for (size_t i = 0; i < mr->k; i++) { + if (mp_cmp_eq(wqp, mr->m_pm1)) { + result.passed = true; + result.potential_primitive_root = (i == mr->k - 1); + break; + } + if (i == mr->k - 1) + break; + monty_mul_into(mr->mc, wqp, wqp, wqp); + } + } + + mp_free(wqp); + + return result; +} + +bool miller_rabin_test_random(MillerRabin *mr) +{ + mp_int *mw = mp_random_in_range(mr->two, mr->pm1); + struct mr_result result = miller_rabin_test_inner(mr, mw); + mp_free(mw); + return result.passed; +} + +mp_int *miller_rabin_find_potential_primitive_root(MillerRabin *mr) +{ + while (true) { + mp_int *mw = mp_unsafe_shrink(mp_random_in_range(mr->two, mr->pm1)); + struct mr_result result = miller_rabin_test_inner(mr, mw); + + if (result.passed && result.potential_primitive_root) { + mp_int *pr = monty_export(mr->mc, mw); + mp_free(mw); + return pr; + } + + mp_free(mw); + + if (!result.passed) { + return NULL; + } + } +} + +unsigned miller_rabin_checks_needed(unsigned bits) +{ + /* Table 4.4 from Handbook of Applied Cryptography */ + return (bits >= 1300 ? 2 : bits >= 850 ? 3 : bits >= 650 ? 4 : + bits >= 550 ? 5 : bits >= 450 ? 6 : bits >= 400 ? 7 : + bits >= 350 ? 8 : bits >= 300 ? 9 : bits >= 250 ? 12 : + bits >= 200 ? 15 : bits >= 150 ? 18 : 27); +} + diff --git a/keygen/mpunsafe.c b/keygen/mpunsafe.c new file mode 100644 index 00000000..f33532c4 --- /dev/null +++ b/keygen/mpunsafe.c @@ -0,0 +1,57 @@ +#include +#include +#include + +#include "defs.h" +#include "misc.h" +#include "puttymem.h" + +#include "mpint.h" +#include "crypto/mpint_i.h" + +/* + * This global symbol is also defined in ssh/kex2-client.c, to ensure + * that these unsafe non-constant-time mp_int functions can't end up + * accidentally linked in to any PuTTY tool that actually makes an SSH + * client connection. + * + * (Only _client_ connections, however. Uppity, being a test server + * only, is exempt.) + */ +const int deliberate_symbol_clash = 12345; + +static size_t mp_unsafe_words_needed(mp_int *x) +{ + size_t words = x->nw; + while (words > 1 && !x->w[words-1]) + words--; + return words; +} + +mp_int *mp_unsafe_shrink(mp_int *x) +{ + x->nw = mp_unsafe_words_needed(x); + /* This potentially leaves some allocated words between the new + * and old values of x->nw, which won't be wiped by mp_free now + * that x->nw doesn't mention that they exist. But we've just + * checked they're all zero, so we don't need to wipe them now + * either. */ + return x; +} + +mp_int *mp_unsafe_copy(mp_int *x) +{ + mp_int *copy = mp_make_sized(mp_unsafe_words_needed(x)); + mp_copy_into(copy, x); + return copy; +} + +uint32_t mp_unsafe_mod_integer(mp_int *x, uint32_t modulus) +{ + uint64_t accumulator = 0; + for (size_t i = mp_max_bytes(x); i-- > 0 ;) { + accumulator = 0x100 * accumulator + mp_get_byte(x, i); + accumulator %= modulus; + } + return accumulator; +} diff --git a/keygen/mpunsafe.h b/keygen/mpunsafe.h new file mode 100644 index 00000000..0b6ba3bd --- /dev/null +++ b/keygen/mpunsafe.h @@ -0,0 +1,46 @@ +/* + * mpunsafe.h: functions that deal with mp_ints in ways that are *not* + * expected to be constant-time. Used during key generation, in which + * constant run time is a lost cause anyway. + * + * These functions are in a separate header, so that you can easily + * check that you're not calling them in the wrong context. They're + * also defined in a separate source file, which is only linked in to + * the key generation tools. Furthermore, that source file also + * defines a global symbol that intentionally conflicts with one + * defined in the SSH client code, so that any attempt to put these + * functions into the same binary as the live SSH client + * implementation will cause a link-time failure. They should only be + * linked into PuTTYgen and auxiliary test programs. + * + * Also, just in case those precautions aren't enough, all the unsafe + * functions have 'unsafe' in the name. + */ + +#ifndef PUTTY_MPINT_UNSAFE_H +#define PUTTY_MPINT_UNSAFE_H + +/* + * The most obvious unsafe thing you want to do with an mp_int is to + * get rid of leading zero words in its representation, so that its + * nominal size is as close as possible to its true size, and you + * don't waste any time processing it. + * + * mp_unsafe_shrink performs this operation in place, mutating the + * size field of the mp_int it's given. It returns the same pointer it + * was given. + * + * mp_unsafe_copy leaves the original mp_int alone and makes a new one + * with the minimal size. + */ +mp_int *mp_unsafe_shrink(mp_int *m); +mp_int *mp_unsafe_copy(mp_int *m); + +/* + * Compute the residue of x mod m. This is implemented in the most + * obvious way using the C % operator, which won't be constant-time on + * many C implementations. + */ +uint32_t mp_unsafe_mod_integer(mp_int *x, uint32_t m); + +#endif /* PUTTY_MPINT_UNSAFE_H */ diff --git a/keygen/pockle.c b/keygen/pockle.c new file mode 100644 index 00000000..60017e33 --- /dev/null +++ b/keygen/pockle.c @@ -0,0 +1,450 @@ +#include +#include "ssh.h" +#include "sshkeygen.h" +#include "mpint.h" +#include "mpunsafe.h" +#include "tree234.h" + +typedef struct PocklePrimeRecord PocklePrimeRecord; + +struct Pockle { + tree234 *tree; + + PocklePrimeRecord **list; + size_t nlist, listsize; +}; + +struct PocklePrimeRecord { + mp_int *prime; + PocklePrimeRecord **factors; + size_t nfactors; + mp_int *witness; + + size_t index; /* index in pockle->list */ +}; + +static int ppr_cmp(void *av, void *bv) +{ + PocklePrimeRecord *a = (PocklePrimeRecord *)av; + PocklePrimeRecord *b = (PocklePrimeRecord *)bv; + return mp_cmp_hs(a->prime, b->prime) - mp_cmp_hs(b->prime, a->prime); +} + +static int ppr_find(void *av, void *bv) +{ + mp_int *a = (mp_int *)av; + PocklePrimeRecord *b = (PocklePrimeRecord *)bv; + return mp_cmp_hs(a, b->prime) - mp_cmp_hs(b->prime, a); +} + +Pockle *pockle_new(void) +{ + Pockle *pockle = snew(Pockle); + pockle->tree = newtree234(ppr_cmp); + pockle->list = NULL; + pockle->nlist = pockle->listsize = 0; + return pockle; +} + +void pockle_free(Pockle *pockle) +{ + pockle_release(pockle, 0); + assert(count234(pockle->tree) == 0); + freetree234(pockle->tree); + sfree(pockle->list); + sfree(pockle); +} + +static PockleStatus pockle_insert(Pockle *pockle, mp_int *p, mp_int **factors, + size_t nfactors, mp_int *w) +{ + PocklePrimeRecord *pr = snew(PocklePrimeRecord); + pr->prime = mp_copy(p); + + PocklePrimeRecord *found = add234(pockle->tree, pr); + if (pr != found) { + /* it was already in there */ + mp_free(pr->prime); + sfree(pr); + return POCKLE_OK; + } + + if (w) { + pr->factors = snewn(nfactors, PocklePrimeRecord *); + for (size_t i = 0; i < nfactors; i++) { + pr->factors[i] = find234(pockle->tree, factors[i], ppr_find); + assert(pr->factors[i]); + } + pr->nfactors = nfactors; + pr->witness = mp_copy(w); + } else { + pr->factors = NULL; + pr->nfactors = 0; + pr->witness = NULL; + } + pr->index = pockle->nlist; + + sgrowarray(pockle->list, pockle->listsize, pockle->nlist); + pockle->list[pockle->nlist++] = pr; + return POCKLE_OK; +} + +size_t pockle_mark(Pockle *pockle) +{ + return pockle->nlist; +} + +void pockle_release(Pockle *pockle, size_t mark) +{ + while (pockle->nlist > mark) { + PocklePrimeRecord *pr = pockle->list[--pockle->nlist]; + del234(pockle->tree, pr); + mp_free(pr->prime); + if (pr->witness) + mp_free(pr->witness); + sfree(pr->factors); + sfree(pr); + } +} + +PockleStatus pockle_add_small_prime(Pockle *pockle, mp_int *p) +{ + if (mp_hs_integer(p, (1ULL << 32))) + return POCKLE_SMALL_PRIME_NOT_SMALL; + + uint32_t val = mp_get_integer(p); + + if (val < 2) + return POCKLE_PRIME_SMALLER_THAN_2; + + init_smallprimes(); + for (size_t i = 0; i < NSMALLPRIMES; i++) { + if (val == smallprimes[i]) + break; /* success */ + if (val % smallprimes[i] == 0) + return POCKLE_SMALL_PRIME_NOT_PRIME; + } + + return pockle_insert(pockle, p, NULL, 0, NULL); +} + +PockleStatus pockle_add_prime(Pockle *pockle, mp_int *p, + mp_int **factors, size_t nfactors, + mp_int *witness) +{ + MontyContext *mc = NULL; + mp_int *x = NULL, *f = NULL, *w = NULL; + PockleStatus status; + + /* + * We're going to try to verify that p is prime by using + * Pocklington's theorem. The idea is that we're given w such that + * w^{p-1} == 1 (mod p) (1) + * and for a collection of primes q | p-1, + * w^{(p-1)/q} - 1 is coprime to p. (2) + * + * Suppose r is a prime factor of p itself. Consider the + * multiplicative order of w mod r. By (1), r | w^{p-1}-1. But by + * (2), r does not divide w^{(p-1)/q}-1. So the order of w mod r + * is a factor of p-1, but not a factor of (p-1)/q. Hence, the + * largest power of q that divides p-1 must also divide ord w. + * + * Repeating this reasoning for all q, we find that the product of + * all the q (which we'll denote f) must divide ord w, which in + * turn divides r-1. So f | r-1 for any r | p. + * + * In particular, this means f < r. That is, all primes r | p are + * bigger than f. So if f > sqrt(p), then we've shown p is prime, + * because otherwise it would have to be the product of at least + * two factors bigger than its own square root. + * + * With an extra check, we can also show p to be prime even if + * we're only given enough factors to make f > cbrt(p). See below + * for that part, when we come to it. + */ + + /* + * Start by checking p > 1. It certainly can't be prime otherwise! + * (And since we're going to prove it prime by showing all its + * prime factors are large, we do also have to know it _has_ at + * least one prime factor for that to tell us anything.) + */ + if (!mp_hs_integer(p, 2)) + return POCKLE_PRIME_SMALLER_THAN_2; + + /* + * Check that all the factors we've been given really are primes + * (in the sense that we already had them in our index). Make the + * product f, and check it really does divide p-1. + */ + x = mp_copy(p); + mp_sub_integer_into(x, x, 1); + f = mp_from_integer(1); + for (size_t i = 0; i < nfactors; i++) { + mp_int *q = factors[i]; + + if (!find234(pockle->tree, q, ppr_find)) { + status = POCKLE_FACTOR_NOT_KNOWN_PRIME; + goto out; + } + + mp_int *quotient = mp_new(mp_max_bits(x)); + mp_int *residue = mp_new(mp_max_bits(q)); + mp_divmod_into(x, q, quotient, residue); + + unsigned exact = mp_eq_integer(residue, 0); + mp_free(residue); + + mp_free(x); + x = quotient; + + if (!exact) { + status = POCKLE_FACTOR_NOT_A_FACTOR; + goto out; + } + + mp_int *tmp = f; + f = mp_unsafe_shrink(mp_mul(tmp, q)); + mp_free(tmp); + } + + /* + * Check that f > cbrt(p). + */ + mp_int *f2 = mp_mul(f, f); + mp_int *f3 = mp_mul(f2, f); + bool too_big = mp_cmp_hs(p, f3); + mp_free(f3); + mp_free(f2); + if (too_big) { + status = POCKLE_PRODUCT_OF_FACTORS_TOO_SMALL; + goto out; + } + + /* + * Now do the extra check that allows us to get away with only + * having f > cbrt(p) instead of f > sqrt(p). + * + * If we can show that f | r-1 for any r | p, then we've ruled out + * p being a product of _more_ than two primes (because then it + * would be the product of at least three things bigger than its + * own cube root). But we still have to rule out it being a + * product of exactly two. + * + * Suppose for the sake of contradiction that p is the product of + * two prime factors. We know both of those factors would have to + * be congruent to 1 mod f. So we'd have to have + * + * p = (uf+1)(vf+1) = (uv)f^2 + (u+v)f + 1 (3) + * + * We can't have uv >= f, or else that expression would come to at + * least f^3, i.e. it would exceed p. So uv < f. Hence, u,v < f as + * well. + * + * Can we have u+v >= f? If we did, then we could write v >= f-u, + * and hence f > uv >= u(f-u). That can be rearranged to show that + * u^2 > (u-1)f; decrementing the LHS makes the inequality no + * longer necessarily strict, so we have u^2-1 >= (u-1)f, and + * dividing off u-1 gives u+1 >= f. But we know u < f, so the only + * way this could happen would be if u=f-1, which makes v=1. But + * _then_ (3) gives us p = (f-1)f^2 + f^2 + 1 = f^3+1. But that + * can't be true if f^3 > p. So we can't have u+v >= f either, by + * contradiction. + * + * After all that, what have we shown? We've shown that we can + * write p = (uv)f^2 + (u+v)f + 1, with both uv and u+v strictly + * less than f. In other words, if you write down p in base f, it + * has exactly three digits, and they are uv, u+v and 1. + * + * But that means we can _find_ u and v: we know p and f, so we + * can just extract those digits of p's base-f representation. + * Once we've done so, they give the sum and product of the + * potential u,v. And given the sum and product of two numbers, + * you can make a quadratic which has those numbers as roots. + * + * We don't actually have to _solve_ the quadratic: all we have to + * do is check if its discriminant is a perfect square. If not, + * we'll know that no integers u,v can match this description. + */ + { + /* We already have x = (p-1)/f. So we just need to write x in + * the form aF + b, and then we have a=uv and b=u+v. */ + mp_int *a = mp_new(mp_max_bits(x)); + mp_int *b = mp_new(mp_max_bits(f)); + mp_divmod_into(x, f, a, b); + assert(!mp_cmp_hs(a, f)); + assert(!mp_cmp_hs(b, f)); + + /* If a=0, then that means p < f^2, so we don't need to do + * this check at all: the straightforward Pocklington theorem + * is all we need. */ + if (!mp_eq_integer(a, 0)) { + unsigned perfect_square = 0; + + mp_int *bsq = mp_mul(b, b); + mp_lshift_fixed_into(a, a, 2); + + if (mp_cmp_hs(bsq, a)) { + /* b^2-4a is non-negative, so it might be a square. + * Check it. */ + mp_int *discriminant = mp_sub(bsq, a); + mp_int *remainder = mp_new(mp_max_bits(discriminant)); + mp_int *root = mp_nthroot(discriminant, 2, remainder); + perfect_square = mp_eq_integer(remainder, 0); + mp_free(discriminant); + mp_free(root); + mp_free(remainder); + } + + mp_free(bsq); + + if (perfect_square) { + mp_free(b); + mp_free(a); + status = POCKLE_DISCRIMINANT_IS_SQUARE; + goto out; + } + } + mp_free(b); + mp_free(a); + } + + /* + * Now we've done all the checks that are cheaper than a modpow, + * so we've ruled out as many things as possible before having to + * do any hard work. But there's nothing for it now: make a + * MontyContext. + */ + mc = monty_new(p); + w = monty_import(mc, witness); + + /* + * The initial Fermat check: is w^{p-1} itself congruent to 1 mod + * p? + */ + { + mp_int *pm1 = mp_copy(p); + mp_sub_integer_into(pm1, pm1, 1); + mp_int *power = monty_pow(mc, w, pm1); + unsigned fermat_pass = mp_cmp_eq(power, monty_identity(mc)); + mp_free(power); + mp_free(pm1); + + if (!fermat_pass) { + status = POCKLE_FERMAT_TEST_FAILED; + goto out; + } + } + + /* + * And now, for each factor q, is w^{(p-1)/q}-1 coprime to p? + */ + for (size_t i = 0; i < nfactors; i++) { + mp_int *q = factors[i]; + mp_int *exponent = mp_unsafe_shrink(mp_div(p, q)); + mp_int *power = monty_pow(mc, w, exponent); + mp_int *power_extracted = monty_export(mc, power); + mp_sub_integer_into(power_extracted, power_extracted, 1); + + unsigned coprime = mp_coprime(power_extracted, p); + if (!coprime) { + /* + * If w^{(p-1)/q}-1 is not coprime to p, the test has + * failed. But it makes a difference why. If the power of + * w turned out to be 1, so that we took gcd(1-1,p) = + * gcd(0,p) = p, that's like an inconclusive Fermat or M-R + * test: it might just mean you picked a witness integer + * that wasn't a primitive root. But if the power is any + * _other_ value mod p that is not coprime to p, it means + * we've detected that the number is *actually not prime*! + */ + if (mp_eq_integer(power_extracted, 0)) + status = POCKLE_WITNESS_POWER_IS_1; + else + status = POCKLE_WITNESS_POWER_NOT_COPRIME; + } + + mp_free(exponent); + mp_free(power); + mp_free(power_extracted); + + if (!coprime) + goto out; /* with the status we set up above */ + } + + /* + * Success! p is prime. Insert it into our tree234 of known + * primes, so that future calls to this function can cite it in + * evidence of larger numbers' primality. + */ + status = pockle_insert(pockle, p, factors, nfactors, witness); + + out: + if (x) + mp_free(x); + if (f) + mp_free(f); + if (w) + mp_free(w); + if (mc) + monty_free(mc); + return status; +} + +static void mp_write_decimal(strbuf *sb, mp_int *x) +{ + char *s = mp_get_decimal(x); + ptrlen pl = ptrlen_from_asciz(s); + put_datapl(sb, pl); + smemclr(s, pl.len); + sfree(s); +} + +strbuf *pockle_mpu(Pockle *pockle, mp_int *p) +{ + strbuf *sb = strbuf_new_nm(); + PocklePrimeRecord *pr = find234(pockle->tree, p, ppr_find); + assert(pr); + + bool *needed = snewn(pockle->nlist, bool); + memset(needed, 0, pockle->nlist * sizeof(bool)); + needed[pr->index] = true; + + strbuf_catf(sb, "[MPU - Primality Certificate]\nVersion 1.0\nBase 10\n\n" + "Proof for:\nN "); + mp_write_decimal(sb, p); + strbuf_catf(sb, "\n"); + + for (size_t index = pockle->nlist; index-- > 0 ;) { + if (!needed[index]) + continue; + pr = pockle->list[index]; + + if (mp_get_nbits(pr->prime) <= 64) { + strbuf_catf(sb, "\nType Small\nN "); + mp_write_decimal(sb, pr->prime); + strbuf_catf(sb, "\n"); + } else { + assert(pr->witness); + strbuf_catf(sb, "\nType BLS5\nN "); + mp_write_decimal(sb, pr->prime); + strbuf_catf(sb, "\n"); + for (size_t i = 0; i < pr->nfactors; i++) { + strbuf_catf(sb, "Q[%"SIZEu"] ", i+1); + mp_write_decimal(sb, pr->factors[i]->prime); + assert(pr->factors[i]->index < index); + needed[pr->factors[i]->index] = true; + strbuf_catf(sb, "\n"); + } + for (size_t i = 0; i < pr->nfactors + 1; i++) { + strbuf_catf(sb, "A[%"SIZEu"] ", i); + mp_write_decimal(sb, pr->witness); + strbuf_catf(sb, "\n"); + } + strbuf_catf(sb, "----\n"); + } + } + sfree(needed); + + return sb; +} diff --git a/keygen/prime.c b/keygen/prime.c new file mode 100644 index 00000000..d9bdebba --- /dev/null +++ b/keygen/prime.c @@ -0,0 +1,762 @@ +/* + * Prime generation. + */ + +#include +#include + +#include "ssh.h" +#include "mpint.h" +#include "mpunsafe.h" +#include "sshkeygen.h" + +/* ---------------------------------------------------------------------- + * Standard probabilistic prime-generation algorithm: + * + * - get a number from our PrimeCandidateSource which will at least + * avoid being divisible by any prime under 2^16 + * + * - 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. + */ + +static PrimeGenerationContext *probprime_new_context( + const PrimeGenerationPolicy *policy) +{ + PrimeGenerationContext *ctx = snew(PrimeGenerationContext); + ctx->vt = policy; + return ctx; +} + +static void probprime_free_context(PrimeGenerationContext *ctx) +{ + sfree(ctx); +} + +static ProgressPhase probprime_add_progress_phase( + const PrimeGenerationPolicy *policy, + ProgressReceiver *prog, unsigned bits) +{ + /* + * The density of primes near x is 1/(log x). When x is about 2^b, + * that's 1/(b log 2). + * + * But we're only doing the expensive part of the process (the M-R + * checks) for a number that passes the initial winnowing test of + * having no factor less than 2^16 (at least, unless the prime is + * so small that PrimeCandidateSource gives up on that winnowing). + * The density of _those_ numbers is about 1/19.76. So the odds of + * hitting a prime per expensive attempt are boosted by a factor + * of 19.76. + */ + const double log_2 = 0.693147180559945309417232121458; + double winnow_factor = (bits < 32 ? 1.0 : 19.76); + double prob = winnow_factor / (bits * log_2); + + /* + * Estimate the cost of prime generation as the cost of the M-R + * modexps. + */ + double cost = (miller_rabin_checks_needed(bits) * + estimate_modexp_cost(bits)); + return progress_add_probabilistic(prog, cost, prob); +} + +static mp_int *probprime_generate( + PrimeGenerationContext *ctx, + PrimeCandidateSource *pcs, ProgressReceiver *prog) +{ + pcs_ready(pcs); + + while (true) { + progress_report_attempt(prog); + + mp_int *p = pcs_generate(pcs); + if (!p) { + pcs_free(pcs); + return NULL; + } + + MillerRabin *mr = miller_rabin_new(p); + bool known_bad = false; + unsigned nchecks = miller_rabin_checks_needed(mp_get_nbits(p)); + for (unsigned check = 0; check < nchecks; check++) { + if (!miller_rabin_test_random(mr)) { + known_bad = true; + break; + } + } + miller_rabin_free(mr); + + if (!known_bad) { + /* + * We have a prime! + */ + pcs_free(pcs); + return p; + } + + mp_free(p); + } +} + +static strbuf *null_mpu_certificate(PrimeGenerationContext *ctx, mp_int *p) +{ + return NULL; +} + +const PrimeGenerationPolicy primegen_probabilistic = { + probprime_add_progress_phase, + probprime_new_context, + probprime_free_context, + probprime_generate, + null_mpu_certificate, +}; + +/* ---------------------------------------------------------------------- + * Alternative provable-prime algorithm, based on the following paper: + * + * [MAURER] Maurer, U.M. Fast generation of prime numbers and secure + * public-key cryptographic parameters. J. Cryptology 8, 123–155 + * (1995). https://doi.org/10.1007/BF00202269 + */ + +typedef enum SubprimePolicy { + SPP_FAST, + SPP_MAURER_SIMPLE, + SPP_MAURER_COMPLEX, +} SubprimePolicy; + +typedef struct ProvablePrimePolicyExtra { + SubprimePolicy spp; +} ProvablePrimePolicyExtra; + +typedef struct ProvablePrimeContext ProvablePrimeContext; +struct ProvablePrimeContext { + Pockle *pockle; + PrimeGenerationContext pgc; + const ProvablePrimePolicyExtra *extra; +}; + +static PrimeGenerationContext *provableprime_new_context( + const PrimeGenerationPolicy *policy) +{ + ProvablePrimeContext *ppc = snew(ProvablePrimeContext); + ppc->pgc.vt = policy; + ppc->pockle = pockle_new(); + ppc->extra = policy->extra; + return &ppc->pgc; +} + +static void provableprime_free_context(PrimeGenerationContext *ctx) +{ + ProvablePrimeContext *ppc = container_of(ctx, ProvablePrimeContext, pgc); + pockle_free(ppc->pockle); + sfree(ppc); +} + +static ProgressPhase provableprime_add_progress_phase( + const PrimeGenerationPolicy *policy, + ProgressReceiver *prog, unsigned bits) +{ + /* + * Estimating the cost of making a _provable_ prime is difficult + * because of all the recursions to smaller sizes. + * + * Once you have enough factors of p-1 to certify primality of p, + * the remaining work in provable prime generation is not very + * different from probabilistic: you generate a random candidate, + * test its primality probabilistically, and use the witness value + * generated as a byproduct of that test for the full Pocklington + * verification. The expensive part, as usual, is made of modpows. + * + * The Pocklington test needs at least two modpows (one for the + * Fermat check, and one per known factor of p-1). + * + * The prior M-R step needs an unknown number, because we iterate + * until we find a value whose order is divisible by the largest + * power of 2 that divides p-1, say 2^j. That excludes half the + * possible witness values (specifically, the quadratic residues), + * so we expect to need on average two M-R operations to find one. + * But that's only if the number _is_ prime - as usual, it's also + * possible that we hit a non-prime and have to try again. + * + * So, if we were only estimating the cost of that final step, it + * would look a lot like the probabilistic version: we'd have to + * estimate the expected total number of modexps by knowing + * something about the density of primes among our candidate + * integers, and then multiply that by estimate_modexp_cost(bits). + * But the problem is that we also have to _find_ a smaller prime, + * so we have to recurse. + * + * In the MAURER_SIMPLE version of the algorithm, you recurse to + * any one of a range of possible smaller sizes i, each with + * probability proportional to 1/i. So your expected time to + * generate an n-bit prime is given by a horrible recurrence of + * the form E_n = S_n + (sum E_i/i) / (sum 1/i), in which S_n is + * the expected cost of the final step once you have your smaller + * primes, and both sums are over ceil(n/2) <= i <= n-20. + * + * At this point I ran out of effort to actually do the maths + * rigorously, so instead I did the empirical experiment of + * generating that sequence in Python and plotting it on a graph. + * My Python code is here, in case I need it again: + +from math import log + +alpha = log(3)/log(2) + 1 # exponent for modexp using Karatsuba mult + +E = [1] * 16 # assume generating tiny primes is trivial + +for n in range(len(E), 4096): + + # Expected time for sub-generations, as a weighted mean of prior + # values of the same sequence. + lo = (n+1)//2 + hi = n-20 + if lo <= hi: + subrange = range(lo, hi+1) + num = sum(E[i]/i for i in subrange) + den = sum(1/i for i in subrange) + else: + num, den = 0, 1 + + # Constant term (cost of final step). + # Similar to probprime_add_progress_phase. + winnow_factor = 1 if n < 32 else 19.76 + prob = winnow_factor / (n * log(2)) + cost = 4 * n**alpha / prob + + E.append(cost + num / den) + +for i, p in enumerate(E): + try: + print(log(i), log(p)) + except ValueError: + continue + + * The output loop prints the logs of both i and E_i, so that when + * I plot the resulting data file in gnuplot I get a log-log + * diagram. That showed me some early noise and then a very + * straight-looking line; feeding the straight part of the graph + * to linear-regression analysis reported that it fits the line + * + * log E_n = -1.7901825337965498 + 3.6199197179662517 * log(n) + * => E_n = 0.16692969657466802 * n^3.6199197179662517 + * + * So my somewhat empirical estimate is that Maurer prime + * generation costs about 0.167 * bits^3.62, in the same arbitrary + * time units used by estimate_modexp_cost. + */ + + return progress_add_linear(prog, 0.167 * pow(bits, 3.62)); +} + +static mp_int *primegen_small(Pockle *pockle, PrimeCandidateSource *pcs) +{ + assert(pcs_get_bits(pcs) <= 32); + + pcs_ready(pcs); + + while (true) { + mp_int *p = pcs_generate(pcs); + if (!p) { + pcs_free(pcs); + return NULL; + } + if (pockle_add_small_prime(pockle, p) == POCKLE_OK) { + pcs_free(pcs); + return p; + } + mp_free(p); + } +} + +#ifdef DEBUG_PRIMEGEN +static void timestamp(FILE *fp) +{ + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + fprintf(fp, "%lu.%09lu: ", (unsigned long)ts.tv_sec, + (unsigned long)ts.tv_nsec); +} +static PRINTF_LIKE(1, 2) void debug_f(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + timestamp(stderr); + vfprintf(stderr, fmt, ap); + fputc('\n', stderr); + va_end(ap); +} +static void debug_f_mp(const char *fmt, mp_int *x, ...) +{ + va_list ap; + va_start(ap, x); + timestamp(stderr); + vfprintf(stderr, fmt, ap); + mp_dump(stderr, "", x, "\n"); + va_end(ap); +} +#else +#define debug_f(...) ((void)0) +#define debug_f_mp(...) ((void)0) +#endif + +static double uniform_random_double(void) +{ + unsigned char randbuf[8]; + random_read(randbuf, 8); + return GET_64BIT_MSB_FIRST(randbuf) * 0x1.0p-64; +} + +static mp_int *mp_ceil_div(mp_int *n, mp_int *d) +{ + mp_int *nplus = mp_add(n, d); + mp_sub_integer_into(nplus, nplus, 1); + mp_int *toret = mp_div(nplus, d); + mp_free(nplus); + return toret; +} + +static mp_int *provableprime_generate_inner( + ProvablePrimeContext *ppc, PrimeCandidateSource *pcs, + ProgressReceiver *prog, double progress_origin, double progress_scale) +{ + unsigned bits = pcs_get_bits(pcs); + assert(bits > 1); + + if (bits <= 32) { + debug_f("ppgi(%u) -> small", bits); + return primegen_small(ppc->pockle, pcs); + } + + unsigned min_bits_needed, max_bits_needed; + { + /* + * Find the product of all the prime factors we already know + * about. + */ + mp_int *size_got = mp_from_integer(1); + size_t nfactors; + mp_int **factors = pcs_get_known_prime_factors(pcs, &nfactors); + for (size_t i = 0; i < nfactors; i++) { + mp_int *to_free = size_got; + size_got = mp_unsafe_shrink(mp_mul(size_got, factors[i])); + mp_free(to_free); + } + + /* + * Find the largest cofactor we might be able to use, and the + * smallest one we can get away with. + */ + mp_int *upperbound = pcs_get_upper_bound(pcs); + mp_int *size_needed = mp_nthroot(upperbound, 3, NULL); + debug_f_mp("upperbound = ", upperbound); + { + mp_int *to_free = upperbound; + upperbound = mp_unsafe_shrink(mp_div(upperbound, size_got)); + mp_free(to_free); + } + debug_f_mp("size_needed = ", size_needed); + { + mp_int *to_free = size_needed; + size_needed = mp_unsafe_shrink(mp_ceil_div(size_needed, size_got)); + mp_free(to_free); + } + + max_bits_needed = pcs_get_bits_remaining(pcs); + + /* + * We need a prime that is greater than or equal to + * 'size_needed' in order for the product of all our known + * factors of p-1 to exceed the cube root of the largest value + * p might take. + * + * Since pcs_new wants a size specified in bits, we must count + * the bits in size_needed and then add 1. Otherwise we might + * get a value with the same bit count as size_needed but + * slightly smaller than it. + * + * An exception is if size_needed = 1. In that case the + * product of existing known factors is _already_ enough, so + * we don't need to generate an extra factor at all. + */ + if (mp_hs_integer(size_needed, 2)) { + min_bits_needed = mp_get_nbits(size_needed) + 1; + } else { + min_bits_needed = 0; + } + + mp_free(upperbound); + mp_free(size_needed); + mp_free(size_got); + } + + double progress = 0.0; + + if (min_bits_needed) { + debug_f("ppgi(%u) recursing, need [%u,%u] more bits", + bits, min_bits_needed, max_bits_needed); + + unsigned *sizes = NULL; + size_t nsizes = 0, sizesize = 0; + + unsigned real_min = max_bits_needed / 2; + unsigned real_max = (max_bits_needed >= 20 ? + max_bits_needed - 20 : 0); + if (real_min < min_bits_needed) + real_min = min_bits_needed; + if (real_max < real_min) + real_max = real_min; + debug_f("ppgi(%u) revised bits interval = [%u,%u]", + bits, real_min, real_max); + + switch (ppc->extra->spp) { + case SPP_FAST: + /* + * Always pick the smallest subsidiary prime we can get + * away with: just over n/3 bits. + * + * This is not a good mode for cryptographic prime + * generation, because it skews the distribution of primes + * greatly, and worse, it skews them in a direction that + * heads away from the properties crypto algorithms tend + * to like. + * + * (For both discrete-log systems and RSA, people have + * tended to recommend in the past that p-1 should have a + * _large_ factor if possible. There's some disagreement + * on which algorithms this is really necessary for, but + * certainly I've never seen anyone recommend arranging a + * _small_ factor on purpose.) + * + * I originally implemented this mode because it was + * convenient for debugging - it wastes as little time as + * possible on finding a sub-prime and lets you get to the + * interesting part! And I leave it in the code because it + * might still be useful for _something_. Because it's + * cryptographically questionable, it's not selectable in + * the UI of either version of PuTTYgen proper; but it can + * be accessed through testcrypt, and if for some reason a + * definite prime is needed for non-crypto purposes, it + * may still be the fastest way to put your hands on one. + */ + debug_f("ppgi(%u) fast mode, just ask for %u bits", + bits, min_bits_needed); + sgrowarray(sizes, sizesize, nsizes); + sizes[nsizes++] = min_bits_needed; + break; + case SPP_MAURER_SIMPLE: { + /* + * Select the size of the subsidiary prime at random from + * sqrt(outputprime) up to outputprime/2^20, in such a way + * that the probability distribution matches that of the + * largest prime factor of a random n-bit number. + * + * Per [MAURER] section 3.4, the cumulative distribution + * function of this relative size is 1+log2(x), for x in + * [1/2,1]. You can generate a value from the distribution + * given by a cdf by applying the inverse cdf to a uniform + * value in [0,1]. Simplifying that in this case, what we + * have to do is raise 2 to the power of a random real + * number between -1 and 0. (And that gives you the number + * of _bits_ in the sub-prime, as a factor of the desired + * output number of bits.) + * + * We also require that the subsidiary prime q is at least + * 20 bits smaller than the output one, to give us a + * fighting chance of there being _any_ prime we can find + * such that q | p-1. + * + * (But these rules have to be applied in an order that + * still leaves us _some_ interval of possible sizes we + * can pick!) + */ + maurer_simple: + debug_f("ppgi(%u) Maurer simple mode", bits); + + unsigned sub_bits; + do { + double uniform = uniform_random_double(); + sub_bits = real_max * pow(2.0, uniform - 1) + 0.5; + debug_f(" ... %.6f -> %u?", uniform, sub_bits); + } while (!(real_min <= sub_bits && sub_bits <= real_max)); + + debug_f("ppgi(%u) asking for %u bits", bits, sub_bits); + sgrowarray(sizes, sizesize, nsizes); + sizes[nsizes++] = sub_bits; + + break; + } + case SPP_MAURER_COMPLEX: { + /* + * In this mode, we may generate multiple factors of p-1 + * which between them add up to at least n/2 bits, in such + * a way that those are guaranteed to be the largest + * factors of p-1 and that they have the same probability + * distribution as the largest k factors would have in a + * random integer. The idea is that this more elaborate + * procedure gets as close as possible to the same + * probability distribution you'd get by selecting a + * completely random prime (if you feasibly could). + * + * Algorithm from Appendix 1 of [MAURER]: we generate + * random real numbers that sum to at most 1, by choosing + * each one uniformly from the range [0, 1 - sum of all + * the previous ones]. We maintain them in a list in + * decreasing order, and we stop as soon as we find an + * initial subsequence of the list s_1,...,s_r such that + * s_1 + ... + s_{r-1} + 2 s_r > 1. In particular, this + * guarantees that the sum of that initial subsequence is + * at least 1/2, so we end up with enough factors to + * satisfy Pocklington. + */ + + if (max_bits_needed / 2 + 1 > real_max) { + /* Early exit path in the case where this algorithm + * can't possibly generate a value in the range we + * need. In that situation, fall back to Maurer + * simple. */ + debug_f("ppgi(%u) skipping GenerateSizeList, " + "real_max too small", bits); + goto maurer_simple; /* sorry! */ + } + + double *s = NULL; + size_t ns, ssize = 0; + + while (true) { + debug_f("ppgi(%u) starting GenerateSizeList", bits); + ns = 0; + double range = 1.0; + while (true) { + /* Generate the next number */ + double u = uniform_random_double() * range; + range -= u; + debug_f(" u_%"SIZEu" = %g", ns, u); + + /* Insert it in the list */ + sgrowarray(s, ssize, ns); + size_t i; + for (i = ns; i > 0 && s[i-1] < u; i--) + s[i] = s[i-1]; + s[i] = u; + ns++; + debug_f(" inserting as s[%"SIZEu"]", i); + + /* Look for a suitable initial subsequence */ + double sum = 0; + for (i = 0; i < ns; i++) { + sum += s[i]; + if (sum + s[i] > 1.0) { + debug_f(" s[0..%"SIZEu"] works!", i); + + /* Truncate the sequence here, and stop + * generating random real numbers. */ + ns = i+1; + goto got_list; + } + } + } + + got_list:; + /* + * Now translate those real numbers into actual bit + * counts, and do a last-minute check to make sure + * their product is going to be in range. + * + * We have to check both the min and max sizes of the + * total. A b-bit number is in [2^{b-1},2^b). So the + * product of numbers of sizes b_1,...,b_k is at least + * 2^{\sum (b_i-1)}, and less than 2^{\sum b_i}. + */ + nsizes = 0; + + unsigned min_total = 0, max_total = 0; + + for (size_t i = 0; i < ns; i++) { + /* These sizes are measured in actual entropy, so + * add 1 bit each time to account for the + * zero-information leading 1 */ + unsigned this_size = max_bits_needed * s[i] + 1; + debug_f(" bits[%"SIZEu"] = %u", i, this_size); + sgrowarray(sizes, sizesize, nsizes); + sizes[nsizes++] = this_size; + + min_total += this_size - 1; + max_total += this_size; + } + + debug_f(" total bits = [%u,%u)", min_total, max_total); + if (min_total < real_min || max_total > real_max+1) { + debug_f(" total out of range, try again"); + } else { + debug_f(" success! %"SIZEu" sub-primes totalling [%u,%u) " + "bits", nsizes, min_total, max_total); + break; + } + } + + smemclr(s, ssize * sizeof(*s)); + sfree(s); + break; + } + default: + unreachable("bad subprime policy"); + } + + for (size_t i = 0; i < nsizes; i++) { + unsigned sub_bits = sizes[i]; + double progress_in_this_prime = (double)sub_bits / bits; + mp_int *q = provableprime_generate_inner( + ppc, pcs_new(sub_bits), + prog, progress_origin + progress_scale * progress, + progress_scale * progress_in_this_prime); + progress += progress_in_this_prime; + assert(q); + debug_f_mp("ppgi(%u) got factor ", q, bits); + pcs_require_residue_1_mod_prime(pcs, q); + mp_free(q); + } + + smemclr(sizes, sizesize * sizeof(*sizes)); + sfree(sizes); + } else { + debug_f("ppgi(%u) no need to recurse", bits); + } + + debug_f("ppgi(%u) ready, %u bits remaining", + bits, pcs_get_bits_remaining(pcs)); + pcs_ready(pcs); + + while (true) { + mp_int *p = pcs_generate(pcs); + if (!p) { + pcs_free(pcs); + return NULL; + } + + debug_f_mp("provable_step p=", p); + + MillerRabin *mr = miller_rabin_new(p); + debug_f("provable_step mr setup done"); + mp_int *witness = miller_rabin_find_potential_primitive_root(mr); + miller_rabin_free(mr); + + if (!witness) { + debug_f("provable_step mr failed"); + mp_free(p); + continue; + } + + size_t nfactors; + mp_int **factors = pcs_get_known_prime_factors(pcs, &nfactors); + PockleStatus st = pockle_add_prime( + ppc->pockle, p, factors, nfactors, witness); + + if (st != POCKLE_OK) { + debug_f("provable_step proof failed %d", (int)st); + + /* + * Check by assertion that the error status is not one of + * the ones we ought to have ruled out already by + * construction. If there's a bug in this code that means + * we can _never_ pass this test (e.g. picking products of + * factors that never quite reach cbrt(n)), we'd rather + * fail an assertion than loop forever. + */ + assert(st == POCKLE_DISCRIMINANT_IS_SQUARE || + st == POCKLE_WITNESS_POWER_IS_1 || + st == POCKLE_WITNESS_POWER_NOT_COPRIME); + + mp_free(p); + if (witness) + mp_free(witness); + continue; + } + + mp_free(witness); + pcs_free(pcs); + debug_f_mp("ppgi(%u) done, got ", p, bits); + progress_report(prog, progress_origin + progress_scale); + return p; + } +} + +static mp_int *provableprime_generate( + PrimeGenerationContext *ctx, + PrimeCandidateSource *pcs, ProgressReceiver *prog) +{ + ProvablePrimeContext *ppc = container_of(ctx, ProvablePrimeContext, pgc); + mp_int *p = provableprime_generate_inner(ppc, pcs, prog, 0.0, 1.0); + + return p; +} + +static inline strbuf *provableprime_mpu_certificate( + PrimeGenerationContext *ctx, mp_int *p) +{ + ProvablePrimeContext *ppc = container_of(ctx, ProvablePrimeContext, pgc); + return pockle_mpu(ppc->pockle, p); +} + +#define DECLARE_POLICY(name, policy) \ + static const struct ProvablePrimePolicyExtra \ + pppextra_##name = {policy}; \ + const PrimeGenerationPolicy name = { \ + provableprime_add_progress_phase, \ + provableprime_new_context, \ + provableprime_free_context, \ + provableprime_generate, \ + provableprime_mpu_certificate, \ + &pppextra_##name, \ + } + +DECLARE_POLICY(primegen_provable_fast, SPP_FAST); +DECLARE_POLICY(primegen_provable_maurer_simple, SPP_MAURER_SIMPLE); +DECLARE_POLICY(primegen_provable_maurer_complex, SPP_MAURER_COMPLEX); + +/* ---------------------------------------------------------------------- + * Reusable null implementation of the progress-reporting API. + */ + +static inline ProgressPhase null_progress_add(void) { + ProgressPhase ph = { .n = 0 }; + return ph; +} +ProgressPhase null_progress_add_linear( + ProgressReceiver *prog, double c) { return null_progress_add(); } +ProgressPhase null_progress_add_probabilistic( + ProgressReceiver *prog, double c, double p) { return null_progress_add(); } +void null_progress_ready(ProgressReceiver *prog) {} +void null_progress_start_phase(ProgressReceiver *prog, ProgressPhase phase) {} +void null_progress_report(ProgressReceiver *prog, double progress) {} +void null_progress_report_attempt(ProgressReceiver *prog) {} +void null_progress_report_phase_complete(ProgressReceiver *prog) {} +const ProgressReceiverVtable null_progress_vt = { + .add_linear = null_progress_add_linear, + .add_probabilistic = null_progress_add_probabilistic, + .ready = null_progress_ready, + .start_phase = null_progress_start_phase, + .report = null_progress_report, + .report_attempt = null_progress_report_attempt, + .report_phase_complete = null_progress_report_phase_complete, +}; + +/* ---------------------------------------------------------------------- + * Helper function for progress estimation. + */ + +double estimate_modexp_cost(unsigned bits) +{ + /* + * A modexp of n bits goes roughly like O(n^2.58), on the grounds + * that our modmul is O(n^1.58) (Karatsuba) and you need O(n) of + * them in a modexp. + */ + return pow(bits, 2.58); +} diff --git a/keygen/primecandidate.c b/keygen/primecandidate.c new file mode 100644 index 00000000..cf55919e --- /dev/null +++ b/keygen/primecandidate.c @@ -0,0 +1,445 @@ +/* + * primecandidate.c: implementation of the PrimeCandidateSource + * abstraction declared in sshkeygen.h. + */ + +#include +#include "ssh.h" +#include "mpint.h" +#include "mpunsafe.h" +#include "sshkeygen.h" + +struct avoid { + unsigned mod, res; +}; + +struct PrimeCandidateSource { + unsigned bits; + bool ready, try_sophie_germain; + bool one_shot, thrown_away_my_shot; + + /* We'll start by making up a random number strictly less than this ... */ + mp_int *limit; + + /* ... then we'll multiply by 'factor', and add 'addend'. */ + mp_int *factor, *addend; + + /* Then we'll try to add a small multiple of 'factor' to it to + * avoid it being a multiple of any small prime. Also, for RSA, we + * may need to avoid it being _this_ multiple of _this_: */ + unsigned avoid_residue, avoid_modulus; + + /* Once we're actually running, this will be the complete list of + * (modulus, residue) pairs we want to avoid. */ + struct avoid *avoids; + size_t navoids, avoidsize; + + /* List of known primes that our number will be congruent to 1 modulo */ + mp_int **kps; + size_t nkps, kpsize; +}; + +PrimeCandidateSource *pcs_new_with_firstbits(unsigned bits, + unsigned first, unsigned nfirst) +{ + PrimeCandidateSource *s = snew(PrimeCandidateSource); + + assert(first >> (nfirst-1) == 1); + + s->bits = bits; + s->ready = false; + s->try_sophie_germain = false; + s->one_shot = false; + s->thrown_away_my_shot = false; + + s->kps = NULL; + s->nkps = s->kpsize = 0; + + s->avoids = NULL; + s->navoids = s->avoidsize = 0; + + /* Make the number that's the lower limit of our range */ + mp_int *firstmp = mp_from_integer(first); + mp_int *base = mp_lshift_fixed(firstmp, bits - nfirst); + mp_free(firstmp); + + /* Set the low bit of that, because all (nontrivial) primes are odd */ + mp_set_bit(base, 0, 1); + + /* That's our addend. Now initialise factor to 2, to ensure we + * only generate odd numbers */ + s->factor = mp_from_integer(2); + s->addend = base; + + /* And that means the limit of our random numbers must be one + * factor of two _less_ than the position of the low bit of + * 'first', because we'll be multiplying the random number by + * 2 immediately afterwards. */ + s->limit = mp_power_2(bits - nfirst - 1); + + /* avoid_modulus == 0 signals that there's no extra residue to avoid */ + s->avoid_residue = 1; + s->avoid_modulus = 0; + + return s; +} + +PrimeCandidateSource *pcs_new(unsigned bits) +{ + return pcs_new_with_firstbits(bits, 1, 1); +} + +void pcs_free(PrimeCandidateSource *s) +{ + mp_free(s->limit); + mp_free(s->factor); + mp_free(s->addend); + for (size_t i = 0; i < s->nkps; i++) + mp_free(s->kps[i]); + sfree(s->avoids); + sfree(s->kps); + sfree(s); +} + +void pcs_try_sophie_germain(PrimeCandidateSource *s) +{ + s->try_sophie_germain = true; +} + +void pcs_set_oneshot(PrimeCandidateSource *s) +{ + s->one_shot = true; +} + +static void pcs_require_residue_inner(PrimeCandidateSource *s, + mp_int *mod, mp_int *res) +{ + /* + * We already have a factor and addend. Ensure this one doesn't + * contradict it. + */ + mp_int *gcd = mp_gcd(mod, s->factor); + mp_int *test1 = mp_mod(s->addend, gcd); + mp_int *test2 = mp_mod(res, gcd); + assert(mp_cmp_eq(test1, test2)); + mp_free(test1); + mp_free(test2); + + /* + * Reduce our input factor and addend, which are constraints on + * the ultimate output number, so that they're constraints on the + * initial cofactor we're going to make up. + * + * If we're generating x and we want to ensure ax+b == r (mod m), + * how does that work? We've already checked that b == r modulo g + * = gcd(a,m), i.e. r-b is a multiple of g, and so are a and m. So + * let's write a=gA, m=gM, (r-b)=gR, and then we can start by + * dividing that off: + * + * ax == r-b (mod m ) + * => gAx == gR (mod gM) + * => Ax == R (mod M) + * + * Now the moduli A,M are coprime, which makes things easier. + * + * We're going to need to generate the x in this equation by + * generating a new smaller value y, multiplying it by M, and + * adding some constant K. So we have x = My + K, and we need to + * work out what K will satisfy the above equation. In other + * words, we need A(My+K) == R (mod M), and the AMy term vanishes, + * so we just need AK == R (mod M). So our congruence is solved by + * setting K to be R * A^{-1} mod M. + */ + mp_int *A = mp_div(s->factor, gcd); + mp_int *M = mp_div(mod, gcd); + mp_int *Rpre = mp_modsub(res, s->addend, mod); + mp_int *R = mp_div(Rpre, gcd); + mp_int *Ainv = mp_invert(A, M); + mp_int *K = mp_modmul(R, Ainv, M); + + mp_free(gcd); + mp_free(Rpre); + mp_free(Ainv); + mp_free(A); + mp_free(R); + + /* + * So we know we have to transform our existing (factor, addend) + * pair into (factor * M, addend * factor * K). Now we just need + * to work out what the limit should be on the random value we're + * generating. + * + * If we need My+K < old_limit, then y < (old_limit-K)/M. But the + * RHS is a fraction, so in integers, we need y < ceil of it. + */ + assert(!mp_cmp_hs(K, s->limit)); + mp_int *dividend = mp_add(s->limit, M); + mp_sub_integer_into(dividend, dividend, 1); + mp_sub_into(dividend, dividend, K); + mp_free(s->limit); + s->limit = mp_div(dividend, M); + mp_free(dividend); + + /* + * Now just update the real factor and addend, and we're done. + */ + + mp_int *addend_old = s->addend; + mp_int *tmp = mp_mul(s->factor, K); /* use the _old_ value of factor */ + s->addend = mp_add(s->addend, tmp); + mp_free(tmp); + mp_free(addend_old); + + mp_int *factor_old = s->factor; + s->factor = mp_mul(s->factor, M); + mp_free(factor_old); + + mp_free(M); + mp_free(K); + s->factor = mp_unsafe_shrink(s->factor); + s->addend = mp_unsafe_shrink(s->addend); + s->limit = mp_unsafe_shrink(s->limit); +} + +void pcs_require_residue(PrimeCandidateSource *s, + mp_int *mod, mp_int *res_orig) +{ + /* + * Reduce the input residue to its least non-negative value, in + * case it was given as a larger equivalent value. + */ + mp_int *res_reduced = mp_mod(res_orig, mod); + pcs_require_residue_inner(s, mod, res_reduced); + mp_free(res_reduced); +} + +void pcs_require_residue_1(PrimeCandidateSource *s, mp_int *mod) +{ + mp_int *res = mp_from_integer(1); + pcs_require_residue(s, mod, res); + mp_free(res); +} + +void pcs_require_residue_1_mod_prime(PrimeCandidateSource *s, mp_int *mod) +{ + pcs_require_residue_1(s, mod); + + sgrowarray(s->kps, s->kpsize, s->nkps); + s->kps[s->nkps++] = mp_copy(mod); +} + +void pcs_avoid_residue_small(PrimeCandidateSource *s, + unsigned mod, unsigned res) +{ + assert(!s->avoid_modulus); /* can't cope with more than one */ + s->avoid_modulus = mod; + s->avoid_residue = res % mod; /* reduce, just in case */ +} + +static int avoid_cmp(const void *av, const void *bv) +{ + const struct avoid *a = (const struct avoid *)av; + const struct avoid *b = (const struct avoid *)bv; + return a->mod < b->mod ? -1 : a->mod > b->mod ? +1 : 0; +} + +static uint64_t invert(uint64_t a, uint64_t m) +{ + int64_t v0 = a, i0 = 1; + int64_t v1 = m, i1 = 0; + while (v0) { + int64_t tmp, q = v1 / v0; + tmp = v0; v0 = v1 - q*v0; v1 = tmp; + tmp = i0; i0 = i1 - q*i0; i1 = tmp; + } + assert(v1 == 1 || v1 == -1); + return i1 * v1; +} + +void pcs_ready(PrimeCandidateSource *s) +{ + /* + * List all the small (modulus, residue) pairs we want to avoid. + */ + + init_smallprimes(); + +#define ADD_AVOID(newmod, newres) do { \ + sgrowarray(s->avoids, s->avoidsize, s->navoids); \ + s->avoids[s->navoids].mod = (newmod); \ + s->avoids[s->navoids].res = (newres); \ + s->navoids++; \ + } while (0) + + unsigned limit = (mp_hs_integer(s->addend, 65536) ? 65536 : + mp_get_integer(s->addend)); + + /* + * Don't be divisible by any small prime, or at least, any prime + * smaller than our output number might actually manage to be. (If + * asked to generate a really small prime, it would be + * embarrassing to rule out legitimate answers on the grounds that + * they were divisible by themselves.) + */ + for (size_t i = 0; i < NSMALLPRIMES && smallprimes[i] < limit; i++) + ADD_AVOID(smallprimes[i], 0); + + if (s->try_sophie_germain) { + /* + * If we're aiming to generate a Sophie Germain prime (i.e. p + * such that 2p+1 is also prime), then we also want to ensure + * 2p+1 is not congruent to 0 mod any small prime, because if + * it is, we'll waste a lot of time generating a p for which + * 2p+1 can't possibly work. So we have to avoid an extra + * residue mod each odd q. + * + * We can simplify: 2p+1 == 0 (mod q) + * => 2p == -1 (mod q) + * => p == -2^{-1} (mod q) + * + * There's no need to do Euclid's algorithm to compute those + * inverses, because for any odd q, the modular inverse of -2 + * mod q is just (q-1)/2. (Proof: multiplying it by -2 gives + * 1-q, which is congruent to 1 mod q.) + */ + for (size_t i = 0; i < NSMALLPRIMES && smallprimes[i] < limit; i++) + if (smallprimes[i] != 2) + ADD_AVOID(smallprimes[i], (smallprimes[i] - 1) / 2); + } + + /* + * Finally, if there's a particular modulus and residue we've been + * told to avoid, put it on the list. + */ + if (s->avoid_modulus) + ADD_AVOID(s->avoid_modulus, s->avoid_residue); + +#undef ADD_AVOID + + /* + * Sort our to-avoid list by modulus. Partly this is so that we'll + * check the smaller moduli first during the live runs, which lets + * us spot most failing cases earlier rather than later. Also, it + * brings equal moduli together, so that we can reuse the residue + * we computed from a previous one. + */ + qsort(s->avoids, s->navoids, sizeof(*s->avoids), avoid_cmp); + + /* + * Next, adjust each of these moduli to take account of our factor + * and addend. If we want factor*x+addend to avoid being congruent + * to 'res' modulo 'mod', then x itself must avoid being congruent + * to (res - addend) * factor^{-1}. + * + * If factor == 0 modulo mod, then the answer will have a fixed + * residue anyway, so we can discard it from our list to test. + */ + int64_t factor_m = 0, addend_m = 0, last_mod = 0; + + size_t out = 0; + for (size_t i = 0; i < s->navoids; i++) { + int64_t mod = s->avoids[i].mod, res = s->avoids[i].res; + if (mod != last_mod) { + last_mod = mod; + addend_m = mp_unsafe_mod_integer(s->addend, mod); + factor_m = mp_unsafe_mod_integer(s->factor, mod); + } + + if (factor_m == 0) { + assert(res != addend_m); + continue; + } + + res = (res - addend_m) * invert(factor_m, mod); + res %= mod; + if (res < 0) + res += mod; + + s->avoids[out].mod = mod; + s->avoids[out].res = res; + out++; + } + + s->navoids = out; + + s->ready = true; +} + +mp_int *pcs_generate(PrimeCandidateSource *s) +{ + assert(s->ready); + if (s->one_shot) { + if (s->thrown_away_my_shot) + return NULL; + s->thrown_away_my_shot = true; + } + + while (true) { + mp_int *x = mp_random_upto(s->limit); + + int64_t x_res = 0, last_mod = 0; + bool ok = true; + + for (size_t i = 0; i < s->navoids; i++) { + int64_t mod = s->avoids[i].mod, avoid_res = s->avoids[i].res; + + if (mod != last_mod) { + last_mod = mod; + x_res = mp_unsafe_mod_integer(x, mod); + } + + if (x_res == avoid_res) { + ok = false; + break; + } + } + + if (!ok) { + mp_free(x); + continue; /* try a new x */ + } + + /* + * We've found a viable x. Make the final output value. + */ + mp_int *toret = mp_new(s->bits); + mp_mul_into(toret, x, s->factor); + mp_add_into(toret, toret, s->addend); + mp_free(x); + return toret; + } +} + +void pcs_inspect(PrimeCandidateSource *pcs, mp_int **limit_out, + mp_int **factor_out, mp_int **addend_out) +{ + *limit_out = mp_copy(pcs->limit); + *factor_out = mp_copy(pcs->factor); + *addend_out = mp_copy(pcs->addend); +} + +unsigned pcs_get_bits(PrimeCandidateSource *pcs) +{ + return pcs->bits; +} + +unsigned pcs_get_bits_remaining(PrimeCandidateSource *pcs) +{ + return mp_get_nbits(pcs->limit); +} + +mp_int *pcs_get_upper_bound(PrimeCandidateSource *pcs) +{ + /* Compute (limit-1) * factor + addend */ + mp_int *tmp = mp_mul(pcs->limit, pcs->factor); + mp_int *bound = mp_add(tmp, pcs->addend); + mp_free(tmp); + mp_sub_into(bound, bound, pcs->factor); + return bound; +} + +mp_int **pcs_get_known_prime_factors(PrimeCandidateSource *pcs, size_t *nout) +{ + *nout = pcs->nkps; + return pcs->kps; +} diff --git a/keygen/rsa.c b/keygen/rsa.c new file mode 100644 index 00000000..b9676e7a --- /dev/null +++ b/keygen/rsa.c @@ -0,0 +1,292 @@ +/* + * RSA key generation. + */ + +#include + +#include "ssh.h" +#include "sshkeygen.h" +#include "mpint.h" + +#define RSA_EXPONENT 65537 + +#define NFIRSTBITS 13 +static void invent_firstbits(unsigned *one, unsigned *two, + unsigned min_separation); + +typedef struct RSAPrimeDetails RSAPrimeDetails; +struct RSAPrimeDetails { + bool strong; + int bits, bitsm1m1, bitsm1, bitsp1; + unsigned firstbits; + ProgressPhase phase_main, phase_m1m1, phase_m1, phase_p1; +}; + +#define STRONG_MARGIN (20 + NFIRSTBITS) + +static RSAPrimeDetails setup_rsa_prime( + int bits, bool strong, PrimeGenerationContext *pgc, ProgressReceiver *prog) +{ + RSAPrimeDetails pd; + pd.bits = bits; + if (strong) { + pd.bitsm1 = (bits - STRONG_MARGIN) / 2; + pd.bitsp1 = (bits - STRONG_MARGIN) - pd.bitsm1; + pd.bitsm1m1 = (pd.bitsm1 - STRONG_MARGIN) / 2; + if (pd.bitsm1m1 < STRONG_MARGIN) { + /* Absurdly small prime, but we should at least not crash. */ + strong = false; + } + } + pd.strong = strong; + + if (pd.strong) { + pd.phase_m1m1 = primegen_add_progress_phase(pgc, prog, pd.bitsm1m1); + pd.phase_m1 = primegen_add_progress_phase(pgc, prog, pd.bitsm1); + pd.phase_p1 = primegen_add_progress_phase(pgc, prog, pd.bitsp1); + } + pd.phase_main = primegen_add_progress_phase(pgc, prog, pd.bits); + + return pd; +} + +static mp_int *generate_rsa_prime( + RSAPrimeDetails pd, PrimeGenerationContext *pgc, ProgressReceiver *prog) +{ + mp_int *m1m1 = NULL, *m1 = NULL, *p1 = NULL, *p = NULL; + PrimeCandidateSource *pcs; + + if (pd.strong) { + progress_start_phase(prog, pd.phase_m1m1); + pcs = pcs_new_with_firstbits(pd.bitsm1m1, pd.firstbits, NFIRSTBITS); + m1m1 = primegen_generate(pgc, pcs, prog); + progress_report_phase_complete(prog); + + progress_start_phase(prog, pd.phase_m1); + pcs = pcs_new_with_firstbits(pd.bitsm1, pd.firstbits, NFIRSTBITS); + pcs_require_residue_1_mod_prime(pcs, m1m1); + m1 = primegen_generate(pgc, pcs, prog); + progress_report_phase_complete(prog); + + progress_start_phase(prog, pd.phase_p1); + pcs = pcs_new_with_firstbits(pd.bitsp1, pd.firstbits, NFIRSTBITS); + p1 = primegen_generate(pgc, pcs, prog); + progress_report_phase_complete(prog); + } + + progress_start_phase(prog, pd.phase_main); + pcs = pcs_new_with_firstbits(pd.bits, pd.firstbits, NFIRSTBITS); + pcs_avoid_residue_small(pcs, RSA_EXPONENT, 1); + if (pd.strong) { + pcs_require_residue_1_mod_prime(pcs, m1); + mp_int *p1_minus_1 = mp_copy(p1); + mp_sub_integer_into(p1_minus_1, p1, 1); + pcs_require_residue(pcs, p1, p1_minus_1); + mp_free(p1_minus_1); + } + p = primegen_generate(pgc, pcs, prog); + progress_report_phase_complete(prog); + + if (m1m1) + mp_free(m1m1); + if (m1) + mp_free(m1); + if (p1) + mp_free(p1); + + return p; +} + +int rsa_generate(RSAKey *key, int bits, bool strong, + PrimeGenerationContext *pgc, ProgressReceiver *prog) +{ + key->sshk.vt = &ssh_rsa; + + /* + * We don't generate e; we just use a standard one always. + */ + mp_int *exponent = mp_from_integer(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.) + * + * We give a min_separation of 2 to invent_firstbits(), ensuring + * that the two primes won't be very close to each other. (The + * chance of them being _dangerously_ close is negligible - even + * more so than an attacker guessing a whole 256-bit session key - + * but it doesn't cost much to make sure.) + */ + int qbits = bits / 2; + int pbits = bits - qbits; + assert(pbits >= qbits); + + RSAPrimeDetails pd = setup_rsa_prime(pbits, strong, pgc, prog); + RSAPrimeDetails qd = setup_rsa_prime(qbits, strong, pgc, prog); + progress_ready(prog); + + invent_firstbits(&pd.firstbits, &qd.firstbits, 2); + + mp_int *p = generate_rsa_prime(pd, pgc, prog); + mp_int *q = generate_rsa_prime(qd, pgc, prog); + + /* + * Ensure p > q, by swapping them if not. + * + * We only need to do this if the two primes were generated with + * the same number of bits (i.e. if the requested key size is + * even) - otherwise it's already guaranteed! + */ + if (pbits == qbits) { + mp_cond_swap(p, q, mp_cmp_hs(q, p)); + } else { + assert(mp_cmp_hs(p, q)); + } + + /* + * 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). + */ + mp_int *modulus = mp_mul(p, q); + mp_int *pm1 = mp_copy(p); + mp_sub_integer_into(pm1, pm1, 1); + mp_int *qm1 = mp_copy(q); + mp_sub_integer_into(qm1, qm1, 1); + mp_int *phi_n = mp_mul(pm1, qm1); + mp_free(pm1); + mp_free(qm1); + mp_int *private_exponent = mp_invert(exponent, phi_n); + mp_free(phi_n); + mp_int *iqmp = mp_invert(q, p); + + /* + * Populate the returned structure. + */ + key->modulus = modulus; + key->exponent = exponent; + key->private_exponent = private_exponent; + key->p = p; + key->q = q; + key->iqmp = iqmp; + + key->bits = mp_get_nbits(modulus); + key->bytes = (key->bits + 7) / 8; + + return 1; +} + +/* + * Invent a pair of values suitable for use as the 'firstbits' values + * for the two RSA primes, such that their product is at least 2, and + * such that their difference is also at least min_separation. + * + * This is used for generating RSA keys which have exactly the + * specified number of bits rather than one fewer - if you generate an + * a-bit and a b-bit number completely at random and multiply them + * together, you could end up with either an (ab-1)-bit number or an + * (ab)-bit number. The former happens log(2)*2-1 of the time (about + * 39%) and, though actually harmless, every time it occurs it has a + * non-zero probability of sparking a user email along the lines of + * 'Hey, I asked PuTTYgen for a 2048-bit key and I only got 2047 bits! + * Bug!' + */ +static inline unsigned firstbits_b_min( + unsigned a, unsigned lo, unsigned hi, unsigned min_separation) +{ + /* To get a large enough product, b must be at least this much */ + unsigned b_min = (2*lo*lo + a - 1) / a; + /* Now enforce a hi) + b_min = hi; + return b_min; +} + +static void invent_firstbits(unsigned *one, unsigned *two, + unsigned min_separation) +{ + /* + * We'll pick 12 initial bits (number selected at random) for each + * prime, not counting the leading 1. So we want to return two + * values in the range [2^12,2^13) whose product is at least 2^25. + * + * Strategy: count up all the viable pairs, then select a random + * number in that range and use it to pick a pair. + * + * To keep things simple, we'll ensure a < b, and randomly swap + * them at the end. + */ + const unsigned lo = 1<<12, hi = 1<<13, minproduct = 2*lo*lo; + unsigned a, b; + + /* + * Count up the number of prefixes of b that would be valid for + * each prefix of a. + */ + mp_int *total = mp_new(32); + for (a = lo; a < hi; a++) { + unsigned b_min = firstbits_b_min(a, lo, hi, min_separation); + mp_add_integer_into(total, total, hi - b_min); + } + + /* + * Make up a random number in the range [0,2*total). + */ + mp_int *mlo = mp_from_integer(0), *mhi = mp_new(32); + mp_lshift_fixed_into(mhi, total, 1); + mp_int *randval = mp_random_in_range(mlo, mhi); + mp_free(mlo); + mp_free(mhi); + + /* + * Use the low bit of randval as our swap indicator, leaving the + * rest of it in the range [0,total). + */ + unsigned swap = mp_get_bit(randval, 0); + mp_rshift_fixed_into(randval, randval, 1); + + /* + * Now do the same counting loop again to make the actual choice. + */ + a = b = 0; + for (unsigned a_candidate = lo; a_candidate < hi; a_candidate++) { + unsigned b_min = firstbits_b_min(a_candidate, lo, hi, min_separation); + unsigned limit = hi - b_min; + + unsigned b_candidate = b_min + mp_get_integer(randval); + unsigned use_it = 1 ^ mp_hs_integer(randval, limit); + a ^= (a ^ a_candidate) & -use_it; + b ^= (b ^ b_candidate) & -use_it; + + mp_sub_integer_into(randval, randval, limit); + } + + mp_free(randval); + mp_free(total); + + /* + * Check everything came out right. + */ + assert(lo <= a); + assert(a < hi); + assert(lo <= b); + assert(b < hi); + assert(a * b >= minproduct); + assert(b >= a + min_separation); + + /* + * Last-minute optional swap of a and b. + */ + unsigned diff = (a ^ b) & (-swap); + a ^= diff; + b ^= diff; + + *one = a; + *two = b; +} diff --git a/keygen/smallprimes.c b/keygen/smallprimes.c new file mode 100644 index 00000000..a43b0bde --- /dev/null +++ b/keygen/smallprimes.c @@ -0,0 +1,44 @@ +/* + * smallprimes.c: implementation of the array of small primes defined + * in sshkeygen.h. + */ + +#include +#include "ssh.h" +#include "sshkeygen.h" + +/* The real array that stores the primes. It has to be writable in + * this module, but outside this module, we only expose the + * const-qualified pointer 'smallprimes' so that nobody else can + * accidentally overwrite it. */ +static unsigned short smallprimes_array[NSMALLPRIMES]; + +const unsigned short *const smallprimes = smallprimes_array; + +void init_smallprimes(void) +{ + if (smallprimes_array[0]) + return; /* already done */ + + bool A[65536]; + + for (size_t i = 2; i < lenof(A); i++) + A[i] = true; + + for (size_t i = 2; i < lenof(A); i++) { + if (!A[i]) + continue; + for (size_t j = 2*i; j < lenof(A); j += i) + A[j] = false; + } + + size_t pos = 0; + for (size_t i = 2; i < lenof(A); i++) { + if (A[i]) { + assert(pos < NSMALLPRIMES); + smallprimes_array[pos++] = i; + } + } + + assert(pos == NSMALLPRIMES); +} diff --git a/millerrabin.c b/millerrabin.c deleted file mode 100644 index 3358bc51..00000000 --- a/millerrabin.c +++ /dev/null @@ -1,214 +0,0 @@ -/* - * millerrabin.c: Miller-Rabin probabilistic primality testing, as - * declared in sshkeygen.h. - */ - -#include -#include "ssh.h" -#include "sshkeygen.h" -#include "mpint.h" -#include "mpunsafe.h" - -/* - * 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.) - */ - -struct MillerRabin { - MontyContext *mc; - - size_t k; - mp_int *q; - - mp_int *two, *pm1, *m_pm1; -}; - -MillerRabin *miller_rabin_new(mp_int *p) -{ - MillerRabin *mr = snew(MillerRabin); - - assert(mp_hs_integer(p, 2)); - assert(mp_get_bit(p, 0) == 1); - - mr->k = 1; - while (!mp_get_bit(p, mr->k)) - mr->k++; - mr->q = mp_rshift_safe(p, mr->k); - - mr->two = mp_from_integer(2); - - mr->pm1 = mp_unsafe_copy(p); - mp_sub_integer_into(mr->pm1, mr->pm1, 1); - - mr->mc = monty_new(p); - mr->m_pm1 = monty_import(mr->mc, mr->pm1); - - return mr; -} - -void miller_rabin_free(MillerRabin *mr) -{ - mp_free(mr->q); - mp_free(mr->two); - mp_free(mr->pm1); - mp_free(mr->m_pm1); - monty_free(mr->mc); - smemclr(mr, sizeof(*mr)); - sfree(mr); -} - -struct mr_result { - bool passed; - bool potential_primitive_root; -}; - -static struct mr_result miller_rabin_test_inner(MillerRabin *mr, mp_int *w) -{ - /* - * Compute w^q mod p. - */ - mp_int *wqp = monty_pow(mr->mc, w, mr->q); - - /* - * See if this is 1, or if it is -1, or if it becomes -1 - * when squared at most k-1 times. - */ - struct mr_result result; - result.passed = false; - result.potential_primitive_root = false; - - if (mp_cmp_eq(wqp, monty_identity(mr->mc))) { - result.passed = true; - } else { - for (size_t i = 0; i < mr->k; i++) { - if (mp_cmp_eq(wqp, mr->m_pm1)) { - result.passed = true; - result.potential_primitive_root = (i == mr->k - 1); - break; - } - if (i == mr->k - 1) - break; - monty_mul_into(mr->mc, wqp, wqp, wqp); - } - } - - mp_free(wqp); - - return result; -} - -bool miller_rabin_test_random(MillerRabin *mr) -{ - mp_int *mw = mp_random_in_range(mr->two, mr->pm1); - struct mr_result result = miller_rabin_test_inner(mr, mw); - mp_free(mw); - return result.passed; -} - -mp_int *miller_rabin_find_potential_primitive_root(MillerRabin *mr) -{ - while (true) { - mp_int *mw = mp_unsafe_shrink(mp_random_in_range(mr->two, mr->pm1)); - struct mr_result result = miller_rabin_test_inner(mr, mw); - - if (result.passed && result.potential_primitive_root) { - mp_int *pr = monty_export(mr->mc, mw); - mp_free(mw); - return pr; - } - - mp_free(mw); - - if (!result.passed) { - return NULL; - } - } -} - -unsigned miller_rabin_checks_needed(unsigned bits) -{ - /* Table 4.4 from Handbook of Applied Cryptography */ - return (bits >= 1300 ? 2 : bits >= 850 ? 3 : bits >= 650 ? 4 : - bits >= 550 ? 5 : bits >= 450 ? 6 : bits >= 400 ? 7 : - bits >= 350 ? 8 : bits >= 300 ? 9 : bits >= 250 ? 12 : - bits >= 200 ? 15 : bits >= 150 ? 18 : 27); -} - diff --git a/mpunsafe.c b/mpunsafe.c deleted file mode 100644 index f33532c4..00000000 --- a/mpunsafe.c +++ /dev/null @@ -1,57 +0,0 @@ -#include -#include -#include - -#include "defs.h" -#include "misc.h" -#include "puttymem.h" - -#include "mpint.h" -#include "crypto/mpint_i.h" - -/* - * This global symbol is also defined in ssh/kex2-client.c, to ensure - * that these unsafe non-constant-time mp_int functions can't end up - * accidentally linked in to any PuTTY tool that actually makes an SSH - * client connection. - * - * (Only _client_ connections, however. Uppity, being a test server - * only, is exempt.) - */ -const int deliberate_symbol_clash = 12345; - -static size_t mp_unsafe_words_needed(mp_int *x) -{ - size_t words = x->nw; - while (words > 1 && !x->w[words-1]) - words--; - return words; -} - -mp_int *mp_unsafe_shrink(mp_int *x) -{ - x->nw = mp_unsafe_words_needed(x); - /* This potentially leaves some allocated words between the new - * and old values of x->nw, which won't be wiped by mp_free now - * that x->nw doesn't mention that they exist. But we've just - * checked they're all zero, so we don't need to wipe them now - * either. */ - return x; -} - -mp_int *mp_unsafe_copy(mp_int *x) -{ - mp_int *copy = mp_make_sized(mp_unsafe_words_needed(x)); - mp_copy_into(copy, x); - return copy; -} - -uint32_t mp_unsafe_mod_integer(mp_int *x, uint32_t modulus) -{ - uint64_t accumulator = 0; - for (size_t i = mp_max_bytes(x); i-- > 0 ;) { - accumulator = 0x100 * accumulator + mp_get_byte(x, i); - accumulator %= modulus; - } - return accumulator; -} diff --git a/mpunsafe.h b/mpunsafe.h deleted file mode 100644 index 0b6ba3bd..00000000 --- a/mpunsafe.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * mpunsafe.h: functions that deal with mp_ints in ways that are *not* - * expected to be constant-time. Used during key generation, in which - * constant run time is a lost cause anyway. - * - * These functions are in a separate header, so that you can easily - * check that you're not calling them in the wrong context. They're - * also defined in a separate source file, which is only linked in to - * the key generation tools. Furthermore, that source file also - * defines a global symbol that intentionally conflicts with one - * defined in the SSH client code, so that any attempt to put these - * functions into the same binary as the live SSH client - * implementation will cause a link-time failure. They should only be - * linked into PuTTYgen and auxiliary test programs. - * - * Also, just in case those precautions aren't enough, all the unsafe - * functions have 'unsafe' in the name. - */ - -#ifndef PUTTY_MPINT_UNSAFE_H -#define PUTTY_MPINT_UNSAFE_H - -/* - * The most obvious unsafe thing you want to do with an mp_int is to - * get rid of leading zero words in its representation, so that its - * nominal size is as close as possible to its true size, and you - * don't waste any time processing it. - * - * mp_unsafe_shrink performs this operation in place, mutating the - * size field of the mp_int it's given. It returns the same pointer it - * was given. - * - * mp_unsafe_copy leaves the original mp_int alone and makes a new one - * with the minimal size. - */ -mp_int *mp_unsafe_shrink(mp_int *m); -mp_int *mp_unsafe_copy(mp_int *m); - -/* - * Compute the residue of x mod m. This is implemented in the most - * obvious way using the C % operator, which won't be constant-time on - * many C implementations. - */ -uint32_t mp_unsafe_mod_integer(mp_int *x, uint32_t m); - -#endif /* PUTTY_MPINT_UNSAFE_H */ diff --git a/pockle.c b/pockle.c deleted file mode 100644 index 60017e33..00000000 --- a/pockle.c +++ /dev/null @@ -1,450 +0,0 @@ -#include -#include "ssh.h" -#include "sshkeygen.h" -#include "mpint.h" -#include "mpunsafe.h" -#include "tree234.h" - -typedef struct PocklePrimeRecord PocklePrimeRecord; - -struct Pockle { - tree234 *tree; - - PocklePrimeRecord **list; - size_t nlist, listsize; -}; - -struct PocklePrimeRecord { - mp_int *prime; - PocklePrimeRecord **factors; - size_t nfactors; - mp_int *witness; - - size_t index; /* index in pockle->list */ -}; - -static int ppr_cmp(void *av, void *bv) -{ - PocklePrimeRecord *a = (PocklePrimeRecord *)av; - PocklePrimeRecord *b = (PocklePrimeRecord *)bv; - return mp_cmp_hs(a->prime, b->prime) - mp_cmp_hs(b->prime, a->prime); -} - -static int ppr_find(void *av, void *bv) -{ - mp_int *a = (mp_int *)av; - PocklePrimeRecord *b = (PocklePrimeRecord *)bv; - return mp_cmp_hs(a, b->prime) - mp_cmp_hs(b->prime, a); -} - -Pockle *pockle_new(void) -{ - Pockle *pockle = snew(Pockle); - pockle->tree = newtree234(ppr_cmp); - pockle->list = NULL; - pockle->nlist = pockle->listsize = 0; - return pockle; -} - -void pockle_free(Pockle *pockle) -{ - pockle_release(pockle, 0); - assert(count234(pockle->tree) == 0); - freetree234(pockle->tree); - sfree(pockle->list); - sfree(pockle); -} - -static PockleStatus pockle_insert(Pockle *pockle, mp_int *p, mp_int **factors, - size_t nfactors, mp_int *w) -{ - PocklePrimeRecord *pr = snew(PocklePrimeRecord); - pr->prime = mp_copy(p); - - PocklePrimeRecord *found = add234(pockle->tree, pr); - if (pr != found) { - /* it was already in there */ - mp_free(pr->prime); - sfree(pr); - return POCKLE_OK; - } - - if (w) { - pr->factors = snewn(nfactors, PocklePrimeRecord *); - for (size_t i = 0; i < nfactors; i++) { - pr->factors[i] = find234(pockle->tree, factors[i], ppr_find); - assert(pr->factors[i]); - } - pr->nfactors = nfactors; - pr->witness = mp_copy(w); - } else { - pr->factors = NULL; - pr->nfactors = 0; - pr->witness = NULL; - } - pr->index = pockle->nlist; - - sgrowarray(pockle->list, pockle->listsize, pockle->nlist); - pockle->list[pockle->nlist++] = pr; - return POCKLE_OK; -} - -size_t pockle_mark(Pockle *pockle) -{ - return pockle->nlist; -} - -void pockle_release(Pockle *pockle, size_t mark) -{ - while (pockle->nlist > mark) { - PocklePrimeRecord *pr = pockle->list[--pockle->nlist]; - del234(pockle->tree, pr); - mp_free(pr->prime); - if (pr->witness) - mp_free(pr->witness); - sfree(pr->factors); - sfree(pr); - } -} - -PockleStatus pockle_add_small_prime(Pockle *pockle, mp_int *p) -{ - if (mp_hs_integer(p, (1ULL << 32))) - return POCKLE_SMALL_PRIME_NOT_SMALL; - - uint32_t val = mp_get_integer(p); - - if (val < 2) - return POCKLE_PRIME_SMALLER_THAN_2; - - init_smallprimes(); - for (size_t i = 0; i < NSMALLPRIMES; i++) { - if (val == smallprimes[i]) - break; /* success */ - if (val % smallprimes[i] == 0) - return POCKLE_SMALL_PRIME_NOT_PRIME; - } - - return pockle_insert(pockle, p, NULL, 0, NULL); -} - -PockleStatus pockle_add_prime(Pockle *pockle, mp_int *p, - mp_int **factors, size_t nfactors, - mp_int *witness) -{ - MontyContext *mc = NULL; - mp_int *x = NULL, *f = NULL, *w = NULL; - PockleStatus status; - - /* - * We're going to try to verify that p is prime by using - * Pocklington's theorem. The idea is that we're given w such that - * w^{p-1} == 1 (mod p) (1) - * and for a collection of primes q | p-1, - * w^{(p-1)/q} - 1 is coprime to p. (2) - * - * Suppose r is a prime factor of p itself. Consider the - * multiplicative order of w mod r. By (1), r | w^{p-1}-1. But by - * (2), r does not divide w^{(p-1)/q}-1. So the order of w mod r - * is a factor of p-1, but not a factor of (p-1)/q. Hence, the - * largest power of q that divides p-1 must also divide ord w. - * - * Repeating this reasoning for all q, we find that the product of - * all the q (which we'll denote f) must divide ord w, which in - * turn divides r-1. So f | r-1 for any r | p. - * - * In particular, this means f < r. That is, all primes r | p are - * bigger than f. So if f > sqrt(p), then we've shown p is prime, - * because otherwise it would have to be the product of at least - * two factors bigger than its own square root. - * - * With an extra check, we can also show p to be prime even if - * we're only given enough factors to make f > cbrt(p). See below - * for that part, when we come to it. - */ - - /* - * Start by checking p > 1. It certainly can't be prime otherwise! - * (And since we're going to prove it prime by showing all its - * prime factors are large, we do also have to know it _has_ at - * least one prime factor for that to tell us anything.) - */ - if (!mp_hs_integer(p, 2)) - return POCKLE_PRIME_SMALLER_THAN_2; - - /* - * Check that all the factors we've been given really are primes - * (in the sense that we already had them in our index). Make the - * product f, and check it really does divide p-1. - */ - x = mp_copy(p); - mp_sub_integer_into(x, x, 1); - f = mp_from_integer(1); - for (size_t i = 0; i < nfactors; i++) { - mp_int *q = factors[i]; - - if (!find234(pockle->tree, q, ppr_find)) { - status = POCKLE_FACTOR_NOT_KNOWN_PRIME; - goto out; - } - - mp_int *quotient = mp_new(mp_max_bits(x)); - mp_int *residue = mp_new(mp_max_bits(q)); - mp_divmod_into(x, q, quotient, residue); - - unsigned exact = mp_eq_integer(residue, 0); - mp_free(residue); - - mp_free(x); - x = quotient; - - if (!exact) { - status = POCKLE_FACTOR_NOT_A_FACTOR; - goto out; - } - - mp_int *tmp = f; - f = mp_unsafe_shrink(mp_mul(tmp, q)); - mp_free(tmp); - } - - /* - * Check that f > cbrt(p). - */ - mp_int *f2 = mp_mul(f, f); - mp_int *f3 = mp_mul(f2, f); - bool too_big = mp_cmp_hs(p, f3); - mp_free(f3); - mp_free(f2); - if (too_big) { - status = POCKLE_PRODUCT_OF_FACTORS_TOO_SMALL; - goto out; - } - - /* - * Now do the extra check that allows us to get away with only - * having f > cbrt(p) instead of f > sqrt(p). - * - * If we can show that f | r-1 for any r | p, then we've ruled out - * p being a product of _more_ than two primes (because then it - * would be the product of at least three things bigger than its - * own cube root). But we still have to rule out it being a - * product of exactly two. - * - * Suppose for the sake of contradiction that p is the product of - * two prime factors. We know both of those factors would have to - * be congruent to 1 mod f. So we'd have to have - * - * p = (uf+1)(vf+1) = (uv)f^2 + (u+v)f + 1 (3) - * - * We can't have uv >= f, or else that expression would come to at - * least f^3, i.e. it would exceed p. So uv < f. Hence, u,v < f as - * well. - * - * Can we have u+v >= f? If we did, then we could write v >= f-u, - * and hence f > uv >= u(f-u). That can be rearranged to show that - * u^2 > (u-1)f; decrementing the LHS makes the inequality no - * longer necessarily strict, so we have u^2-1 >= (u-1)f, and - * dividing off u-1 gives u+1 >= f. But we know u < f, so the only - * way this could happen would be if u=f-1, which makes v=1. But - * _then_ (3) gives us p = (f-1)f^2 + f^2 + 1 = f^3+1. But that - * can't be true if f^3 > p. So we can't have u+v >= f either, by - * contradiction. - * - * After all that, what have we shown? We've shown that we can - * write p = (uv)f^2 + (u+v)f + 1, with both uv and u+v strictly - * less than f. In other words, if you write down p in base f, it - * has exactly three digits, and they are uv, u+v and 1. - * - * But that means we can _find_ u and v: we know p and f, so we - * can just extract those digits of p's base-f representation. - * Once we've done so, they give the sum and product of the - * potential u,v. And given the sum and product of two numbers, - * you can make a quadratic which has those numbers as roots. - * - * We don't actually have to _solve_ the quadratic: all we have to - * do is check if its discriminant is a perfect square. If not, - * we'll know that no integers u,v can match this description. - */ - { - /* We already have x = (p-1)/f. So we just need to write x in - * the form aF + b, and then we have a=uv and b=u+v. */ - mp_int *a = mp_new(mp_max_bits(x)); - mp_int *b = mp_new(mp_max_bits(f)); - mp_divmod_into(x, f, a, b); - assert(!mp_cmp_hs(a, f)); - assert(!mp_cmp_hs(b, f)); - - /* If a=0, then that means p < f^2, so we don't need to do - * this check at all: the straightforward Pocklington theorem - * is all we need. */ - if (!mp_eq_integer(a, 0)) { - unsigned perfect_square = 0; - - mp_int *bsq = mp_mul(b, b); - mp_lshift_fixed_into(a, a, 2); - - if (mp_cmp_hs(bsq, a)) { - /* b^2-4a is non-negative, so it might be a square. - * Check it. */ - mp_int *discriminant = mp_sub(bsq, a); - mp_int *remainder = mp_new(mp_max_bits(discriminant)); - mp_int *root = mp_nthroot(discriminant, 2, remainder); - perfect_square = mp_eq_integer(remainder, 0); - mp_free(discriminant); - mp_free(root); - mp_free(remainder); - } - - mp_free(bsq); - - if (perfect_square) { - mp_free(b); - mp_free(a); - status = POCKLE_DISCRIMINANT_IS_SQUARE; - goto out; - } - } - mp_free(b); - mp_free(a); - } - - /* - * Now we've done all the checks that are cheaper than a modpow, - * so we've ruled out as many things as possible before having to - * do any hard work. But there's nothing for it now: make a - * MontyContext. - */ - mc = monty_new(p); - w = monty_import(mc, witness); - - /* - * The initial Fermat check: is w^{p-1} itself congruent to 1 mod - * p? - */ - { - mp_int *pm1 = mp_copy(p); - mp_sub_integer_into(pm1, pm1, 1); - mp_int *power = monty_pow(mc, w, pm1); - unsigned fermat_pass = mp_cmp_eq(power, monty_identity(mc)); - mp_free(power); - mp_free(pm1); - - if (!fermat_pass) { - status = POCKLE_FERMAT_TEST_FAILED; - goto out; - } - } - - /* - * And now, for each factor q, is w^{(p-1)/q}-1 coprime to p? - */ - for (size_t i = 0; i < nfactors; i++) { - mp_int *q = factors[i]; - mp_int *exponent = mp_unsafe_shrink(mp_div(p, q)); - mp_int *power = monty_pow(mc, w, exponent); - mp_int *power_extracted = monty_export(mc, power); - mp_sub_integer_into(power_extracted, power_extracted, 1); - - unsigned coprime = mp_coprime(power_extracted, p); - if (!coprime) { - /* - * If w^{(p-1)/q}-1 is not coprime to p, the test has - * failed. But it makes a difference why. If the power of - * w turned out to be 1, so that we took gcd(1-1,p) = - * gcd(0,p) = p, that's like an inconclusive Fermat or M-R - * test: it might just mean you picked a witness integer - * that wasn't a primitive root. But if the power is any - * _other_ value mod p that is not coprime to p, it means - * we've detected that the number is *actually not prime*! - */ - if (mp_eq_integer(power_extracted, 0)) - status = POCKLE_WITNESS_POWER_IS_1; - else - status = POCKLE_WITNESS_POWER_NOT_COPRIME; - } - - mp_free(exponent); - mp_free(power); - mp_free(power_extracted); - - if (!coprime) - goto out; /* with the status we set up above */ - } - - /* - * Success! p is prime. Insert it into our tree234 of known - * primes, so that future calls to this function can cite it in - * evidence of larger numbers' primality. - */ - status = pockle_insert(pockle, p, factors, nfactors, witness); - - out: - if (x) - mp_free(x); - if (f) - mp_free(f); - if (w) - mp_free(w); - if (mc) - monty_free(mc); - return status; -} - -static void mp_write_decimal(strbuf *sb, mp_int *x) -{ - char *s = mp_get_decimal(x); - ptrlen pl = ptrlen_from_asciz(s); - put_datapl(sb, pl); - smemclr(s, pl.len); - sfree(s); -} - -strbuf *pockle_mpu(Pockle *pockle, mp_int *p) -{ - strbuf *sb = strbuf_new_nm(); - PocklePrimeRecord *pr = find234(pockle->tree, p, ppr_find); - assert(pr); - - bool *needed = snewn(pockle->nlist, bool); - memset(needed, 0, pockle->nlist * sizeof(bool)); - needed[pr->index] = true; - - strbuf_catf(sb, "[MPU - Primality Certificate]\nVersion 1.0\nBase 10\n\n" - "Proof for:\nN "); - mp_write_decimal(sb, p); - strbuf_catf(sb, "\n"); - - for (size_t index = pockle->nlist; index-- > 0 ;) { - if (!needed[index]) - continue; - pr = pockle->list[index]; - - if (mp_get_nbits(pr->prime) <= 64) { - strbuf_catf(sb, "\nType Small\nN "); - mp_write_decimal(sb, pr->prime); - strbuf_catf(sb, "\n"); - } else { - assert(pr->witness); - strbuf_catf(sb, "\nType BLS5\nN "); - mp_write_decimal(sb, pr->prime); - strbuf_catf(sb, "\n"); - for (size_t i = 0; i < pr->nfactors; i++) { - strbuf_catf(sb, "Q[%"SIZEu"] ", i+1); - mp_write_decimal(sb, pr->factors[i]->prime); - assert(pr->factors[i]->index < index); - needed[pr->factors[i]->index] = true; - strbuf_catf(sb, "\n"); - } - for (size_t i = 0; i < pr->nfactors + 1; i++) { - strbuf_catf(sb, "A[%"SIZEu"] ", i); - mp_write_decimal(sb, pr->witness); - strbuf_catf(sb, "\n"); - } - strbuf_catf(sb, "----\n"); - } - } - sfree(needed); - - return sb; -} diff --git a/primecandidate.c b/primecandidate.c deleted file mode 100644 index cf55919e..00000000 --- a/primecandidate.c +++ /dev/null @@ -1,445 +0,0 @@ -/* - * primecandidate.c: implementation of the PrimeCandidateSource - * abstraction declared in sshkeygen.h. - */ - -#include -#include "ssh.h" -#include "mpint.h" -#include "mpunsafe.h" -#include "sshkeygen.h" - -struct avoid { - unsigned mod, res; -}; - -struct PrimeCandidateSource { - unsigned bits; - bool ready, try_sophie_germain; - bool one_shot, thrown_away_my_shot; - - /* We'll start by making up a random number strictly less than this ... */ - mp_int *limit; - - /* ... then we'll multiply by 'factor', and add 'addend'. */ - mp_int *factor, *addend; - - /* Then we'll try to add a small multiple of 'factor' to it to - * avoid it being a multiple of any small prime. Also, for RSA, we - * may need to avoid it being _this_ multiple of _this_: */ - unsigned avoid_residue, avoid_modulus; - - /* Once we're actually running, this will be the complete list of - * (modulus, residue) pairs we want to avoid. */ - struct avoid *avoids; - size_t navoids, avoidsize; - - /* List of known primes that our number will be congruent to 1 modulo */ - mp_int **kps; - size_t nkps, kpsize; -}; - -PrimeCandidateSource *pcs_new_with_firstbits(unsigned bits, - unsigned first, unsigned nfirst) -{ - PrimeCandidateSource *s = snew(PrimeCandidateSource); - - assert(first >> (nfirst-1) == 1); - - s->bits = bits; - s->ready = false; - s->try_sophie_germain = false; - s->one_shot = false; - s->thrown_away_my_shot = false; - - s->kps = NULL; - s->nkps = s->kpsize = 0; - - s->avoids = NULL; - s->navoids = s->avoidsize = 0; - - /* Make the number that's the lower limit of our range */ - mp_int *firstmp = mp_from_integer(first); - mp_int *base = mp_lshift_fixed(firstmp, bits - nfirst); - mp_free(firstmp); - - /* Set the low bit of that, because all (nontrivial) primes are odd */ - mp_set_bit(base, 0, 1); - - /* That's our addend. Now initialise factor to 2, to ensure we - * only generate odd numbers */ - s->factor = mp_from_integer(2); - s->addend = base; - - /* And that means the limit of our random numbers must be one - * factor of two _less_ than the position of the low bit of - * 'first', because we'll be multiplying the random number by - * 2 immediately afterwards. */ - s->limit = mp_power_2(bits - nfirst - 1); - - /* avoid_modulus == 0 signals that there's no extra residue to avoid */ - s->avoid_residue = 1; - s->avoid_modulus = 0; - - return s; -} - -PrimeCandidateSource *pcs_new(unsigned bits) -{ - return pcs_new_with_firstbits(bits, 1, 1); -} - -void pcs_free(PrimeCandidateSource *s) -{ - mp_free(s->limit); - mp_free(s->factor); - mp_free(s->addend); - for (size_t i = 0; i < s->nkps; i++) - mp_free(s->kps[i]); - sfree(s->avoids); - sfree(s->kps); - sfree(s); -} - -void pcs_try_sophie_germain(PrimeCandidateSource *s) -{ - s->try_sophie_germain = true; -} - -void pcs_set_oneshot(PrimeCandidateSource *s) -{ - s->one_shot = true; -} - -static void pcs_require_residue_inner(PrimeCandidateSource *s, - mp_int *mod, mp_int *res) -{ - /* - * We already have a factor and addend. Ensure this one doesn't - * contradict it. - */ - mp_int *gcd = mp_gcd(mod, s->factor); - mp_int *test1 = mp_mod(s->addend, gcd); - mp_int *test2 = mp_mod(res, gcd); - assert(mp_cmp_eq(test1, test2)); - mp_free(test1); - mp_free(test2); - - /* - * Reduce our input factor and addend, which are constraints on - * the ultimate output number, so that they're constraints on the - * initial cofactor we're going to make up. - * - * If we're generating x and we want to ensure ax+b == r (mod m), - * how does that work? We've already checked that b == r modulo g - * = gcd(a,m), i.e. r-b is a multiple of g, and so are a and m. So - * let's write a=gA, m=gM, (r-b)=gR, and then we can start by - * dividing that off: - * - * ax == r-b (mod m ) - * => gAx == gR (mod gM) - * => Ax == R (mod M) - * - * Now the moduli A,M are coprime, which makes things easier. - * - * We're going to need to generate the x in this equation by - * generating a new smaller value y, multiplying it by M, and - * adding some constant K. So we have x = My + K, and we need to - * work out what K will satisfy the above equation. In other - * words, we need A(My+K) == R (mod M), and the AMy term vanishes, - * so we just need AK == R (mod M). So our congruence is solved by - * setting K to be R * A^{-1} mod M. - */ - mp_int *A = mp_div(s->factor, gcd); - mp_int *M = mp_div(mod, gcd); - mp_int *Rpre = mp_modsub(res, s->addend, mod); - mp_int *R = mp_div(Rpre, gcd); - mp_int *Ainv = mp_invert(A, M); - mp_int *K = mp_modmul(R, Ainv, M); - - mp_free(gcd); - mp_free(Rpre); - mp_free(Ainv); - mp_free(A); - mp_free(R); - - /* - * So we know we have to transform our existing (factor, addend) - * pair into (factor * M, addend * factor * K). Now we just need - * to work out what the limit should be on the random value we're - * generating. - * - * If we need My+K < old_limit, then y < (old_limit-K)/M. But the - * RHS is a fraction, so in integers, we need y < ceil of it. - */ - assert(!mp_cmp_hs(K, s->limit)); - mp_int *dividend = mp_add(s->limit, M); - mp_sub_integer_into(dividend, dividend, 1); - mp_sub_into(dividend, dividend, K); - mp_free(s->limit); - s->limit = mp_div(dividend, M); - mp_free(dividend); - - /* - * Now just update the real factor and addend, and we're done. - */ - - mp_int *addend_old = s->addend; - mp_int *tmp = mp_mul(s->factor, K); /* use the _old_ value of factor */ - s->addend = mp_add(s->addend, tmp); - mp_free(tmp); - mp_free(addend_old); - - mp_int *factor_old = s->factor; - s->factor = mp_mul(s->factor, M); - mp_free(factor_old); - - mp_free(M); - mp_free(K); - s->factor = mp_unsafe_shrink(s->factor); - s->addend = mp_unsafe_shrink(s->addend); - s->limit = mp_unsafe_shrink(s->limit); -} - -void pcs_require_residue(PrimeCandidateSource *s, - mp_int *mod, mp_int *res_orig) -{ - /* - * Reduce the input residue to its least non-negative value, in - * case it was given as a larger equivalent value. - */ - mp_int *res_reduced = mp_mod(res_orig, mod); - pcs_require_residue_inner(s, mod, res_reduced); - mp_free(res_reduced); -} - -void pcs_require_residue_1(PrimeCandidateSource *s, mp_int *mod) -{ - mp_int *res = mp_from_integer(1); - pcs_require_residue(s, mod, res); - mp_free(res); -} - -void pcs_require_residue_1_mod_prime(PrimeCandidateSource *s, mp_int *mod) -{ - pcs_require_residue_1(s, mod); - - sgrowarray(s->kps, s->kpsize, s->nkps); - s->kps[s->nkps++] = mp_copy(mod); -} - -void pcs_avoid_residue_small(PrimeCandidateSource *s, - unsigned mod, unsigned res) -{ - assert(!s->avoid_modulus); /* can't cope with more than one */ - s->avoid_modulus = mod; - s->avoid_residue = res % mod; /* reduce, just in case */ -} - -static int avoid_cmp(const void *av, const void *bv) -{ - const struct avoid *a = (const struct avoid *)av; - const struct avoid *b = (const struct avoid *)bv; - return a->mod < b->mod ? -1 : a->mod > b->mod ? +1 : 0; -} - -static uint64_t invert(uint64_t a, uint64_t m) -{ - int64_t v0 = a, i0 = 1; - int64_t v1 = m, i1 = 0; - while (v0) { - int64_t tmp, q = v1 / v0; - tmp = v0; v0 = v1 - q*v0; v1 = tmp; - tmp = i0; i0 = i1 - q*i0; i1 = tmp; - } - assert(v1 == 1 || v1 == -1); - return i1 * v1; -} - -void pcs_ready(PrimeCandidateSource *s) -{ - /* - * List all the small (modulus, residue) pairs we want to avoid. - */ - - init_smallprimes(); - -#define ADD_AVOID(newmod, newres) do { \ - sgrowarray(s->avoids, s->avoidsize, s->navoids); \ - s->avoids[s->navoids].mod = (newmod); \ - s->avoids[s->navoids].res = (newres); \ - s->navoids++; \ - } while (0) - - unsigned limit = (mp_hs_integer(s->addend, 65536) ? 65536 : - mp_get_integer(s->addend)); - - /* - * Don't be divisible by any small prime, or at least, any prime - * smaller than our output number might actually manage to be. (If - * asked to generate a really small prime, it would be - * embarrassing to rule out legitimate answers on the grounds that - * they were divisible by themselves.) - */ - for (size_t i = 0; i < NSMALLPRIMES && smallprimes[i] < limit; i++) - ADD_AVOID(smallprimes[i], 0); - - if (s->try_sophie_germain) { - /* - * If we're aiming to generate a Sophie Germain prime (i.e. p - * such that 2p+1 is also prime), then we also want to ensure - * 2p+1 is not congruent to 0 mod any small prime, because if - * it is, we'll waste a lot of time generating a p for which - * 2p+1 can't possibly work. So we have to avoid an extra - * residue mod each odd q. - * - * We can simplify: 2p+1 == 0 (mod q) - * => 2p == -1 (mod q) - * => p == -2^{-1} (mod q) - * - * There's no need to do Euclid's algorithm to compute those - * inverses, because for any odd q, the modular inverse of -2 - * mod q is just (q-1)/2. (Proof: multiplying it by -2 gives - * 1-q, which is congruent to 1 mod q.) - */ - for (size_t i = 0; i < NSMALLPRIMES && smallprimes[i] < limit; i++) - if (smallprimes[i] != 2) - ADD_AVOID(smallprimes[i], (smallprimes[i] - 1) / 2); - } - - /* - * Finally, if there's a particular modulus and residue we've been - * told to avoid, put it on the list. - */ - if (s->avoid_modulus) - ADD_AVOID(s->avoid_modulus, s->avoid_residue); - -#undef ADD_AVOID - - /* - * Sort our to-avoid list by modulus. Partly this is so that we'll - * check the smaller moduli first during the live runs, which lets - * us spot most failing cases earlier rather than later. Also, it - * brings equal moduli together, so that we can reuse the residue - * we computed from a previous one. - */ - qsort(s->avoids, s->navoids, sizeof(*s->avoids), avoid_cmp); - - /* - * Next, adjust each of these moduli to take account of our factor - * and addend. If we want factor*x+addend to avoid being congruent - * to 'res' modulo 'mod', then x itself must avoid being congruent - * to (res - addend) * factor^{-1}. - * - * If factor == 0 modulo mod, then the answer will have a fixed - * residue anyway, so we can discard it from our list to test. - */ - int64_t factor_m = 0, addend_m = 0, last_mod = 0; - - size_t out = 0; - for (size_t i = 0; i < s->navoids; i++) { - int64_t mod = s->avoids[i].mod, res = s->avoids[i].res; - if (mod != last_mod) { - last_mod = mod; - addend_m = mp_unsafe_mod_integer(s->addend, mod); - factor_m = mp_unsafe_mod_integer(s->factor, mod); - } - - if (factor_m == 0) { - assert(res != addend_m); - continue; - } - - res = (res - addend_m) * invert(factor_m, mod); - res %= mod; - if (res < 0) - res += mod; - - s->avoids[out].mod = mod; - s->avoids[out].res = res; - out++; - } - - s->navoids = out; - - s->ready = true; -} - -mp_int *pcs_generate(PrimeCandidateSource *s) -{ - assert(s->ready); - if (s->one_shot) { - if (s->thrown_away_my_shot) - return NULL; - s->thrown_away_my_shot = true; - } - - while (true) { - mp_int *x = mp_random_upto(s->limit); - - int64_t x_res = 0, last_mod = 0; - bool ok = true; - - for (size_t i = 0; i < s->navoids; i++) { - int64_t mod = s->avoids[i].mod, avoid_res = s->avoids[i].res; - - if (mod != last_mod) { - last_mod = mod; - x_res = mp_unsafe_mod_integer(x, mod); - } - - if (x_res == avoid_res) { - ok = false; - break; - } - } - - if (!ok) { - mp_free(x); - continue; /* try a new x */ - } - - /* - * We've found a viable x. Make the final output value. - */ - mp_int *toret = mp_new(s->bits); - mp_mul_into(toret, x, s->factor); - mp_add_into(toret, toret, s->addend); - mp_free(x); - return toret; - } -} - -void pcs_inspect(PrimeCandidateSource *pcs, mp_int **limit_out, - mp_int **factor_out, mp_int **addend_out) -{ - *limit_out = mp_copy(pcs->limit); - *factor_out = mp_copy(pcs->factor); - *addend_out = mp_copy(pcs->addend); -} - -unsigned pcs_get_bits(PrimeCandidateSource *pcs) -{ - return pcs->bits; -} - -unsigned pcs_get_bits_remaining(PrimeCandidateSource *pcs) -{ - return mp_get_nbits(pcs->limit); -} - -mp_int *pcs_get_upper_bound(PrimeCandidateSource *pcs) -{ - /* Compute (limit-1) * factor + addend */ - mp_int *tmp = mp_mul(pcs->limit, pcs->factor); - mp_int *bound = mp_add(tmp, pcs->addend); - mp_free(tmp); - mp_sub_into(bound, bound, pcs->factor); - return bound; -} - -mp_int **pcs_get_known_prime_factors(PrimeCandidateSource *pcs, size_t *nout) -{ - *nout = pcs->nkps; - return pcs->kps; -} diff --git a/smallprimes.c b/smallprimes.c deleted file mode 100644 index a43b0bde..00000000 --- a/smallprimes.c +++ /dev/null @@ -1,44 +0,0 @@ -/* - * smallprimes.c: implementation of the array of small primes defined - * in sshkeygen.h. - */ - -#include -#include "ssh.h" -#include "sshkeygen.h" - -/* The real array that stores the primes. It has to be writable in - * this module, but outside this module, we only expose the - * const-qualified pointer 'smallprimes' so that nobody else can - * accidentally overwrite it. */ -static unsigned short smallprimes_array[NSMALLPRIMES]; - -const unsigned short *const smallprimes = smallprimes_array; - -void init_smallprimes(void) -{ - if (smallprimes_array[0]) - return; /* already done */ - - bool A[65536]; - - for (size_t i = 2; i < lenof(A); i++) - A[i] = true; - - for (size_t i = 2; i < lenof(A); i++) { - if (!A[i]) - continue; - for (size_t j = 2*i; j < lenof(A); j += i) - A[j] = false; - } - - size_t pos = 0; - for (size_t i = 2; i < lenof(A); i++) { - if (A[i]) { - assert(pos < NSMALLPRIMES); - smallprimes_array[pos++] = i; - } - } - - assert(pos == NSMALLPRIMES); -} diff --git a/sshdssg.c b/sshdssg.c deleted file mode 100644 index 3b527256..00000000 --- a/sshdssg.c +++ /dev/null @@ -1,103 +0,0 @@ -/* - * DSS key generation. - */ - -#include "misc.h" -#include "ssh.h" -#include "sshkeygen.h" -#include "mpint.h" - -int dsa_generate(struct dss_key *key, int bits, PrimeGenerationContext *pgc, - ProgressReceiver *prog) -{ - /* - * Progress-reporting setup. - * - * DSA generation involves three potentially long jobs: inventing - * the small prime q, the large prime p, and finding an order-q - * element of the multiplicative group of p. - * - * The latter is done by finding an element whose order is - * _divisible_ by q and raising it to the power of (p-1)/q. Every - * element whose order is not divisible by q is a qth power of q - * distinct elements whose order _is_ divisible by q, so the - * probability of not finding a suitable element on the first try - * is in the region of 1/q, i.e. at most 2^-159. - * - * (So the probability of success will end up indistinguishable - * from 1 in IEEE standard floating point! But what can you do.) - */ - ProgressPhase phase_q = primegen_add_progress_phase(pgc, prog, 160); - ProgressPhase phase_p = primegen_add_progress_phase(pgc, prog, bits); - double g_failure_probability = 1.0 - / (double)(1ULL << 53) - / (double)(1ULL << 53) - / (double)(1ULL << 53); - ProgressPhase phase_g = progress_add_probabilistic( - prog, estimate_modexp_cost(bits), 1.0 - g_failure_probability); - progress_ready(prog); - - PrimeCandidateSource *pcs; - - /* - * Generate q: a prime of length 160. - */ - progress_start_phase(prog, phase_q); - pcs = pcs_new(160); - mp_int *q = primegen_generate(pgc, pcs, prog); - progress_report_phase_complete(prog); - - /* - * Now generate p: a prime of length `bits', such that p-1 is - * divisible by q. - */ - progress_start_phase(prog, phase_p); - pcs = pcs_new(bits); - pcs_require_residue_1_mod_prime(pcs, q); - mp_int *p = primegen_generate(pgc, pcs, prog); - progress_report_phase_complete(prog); - - /* - * 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. - */ - progress_start_phase(prog, phase_g); - mp_int *power = mp_div(p, q); /* this is floor(p/q) == (p-1)/q */ - mp_int *h = mp_from_integer(2); - mp_int *g; - while (1) { - progress_report_attempt(prog); - g = mp_modpow(h, power, p); - if (mp_hs_integer(g, 2)) - break; /* got one */ - mp_free(g); - mp_add_integer_into(h, h, 1); - } - mp_free(h); - mp_free(power); - progress_report_phase_complete(prog); - - /* - * 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. - */ - mp_int *two = mp_from_integer(2); - mp_int *qm1 = mp_copy(q); - mp_sub_integer_into(qm1, qm1, 1); - mp_int *x = mp_random_in_range(two, qm1); - mp_free(two); - mp_free(qm1); - - key->sshk.vt = &ssh_dss; - - key->p = p; - key->q = q; - key->g = g; - key->x = x; - key->y = mp_modpow(key->g, key->x, key->p); - - return 1; -} diff --git a/sshecdsag.c b/sshecdsag.c deleted file mode 100644 index 28a723b2..00000000 --- a/sshecdsag.c +++ /dev/null @@ -1,37 +0,0 @@ -/* - * EC key generation. - */ - -#include "ssh.h" -#include "sshkeygen.h" -#include "mpint.h" - -int ecdsa_generate(struct ecdsa_key *ek, int bits) -{ - if (!ec_nist_alg_and_curve_by_bits(bits, &ek->curve, &ek->sshk.vt)) - return 0; - - mp_int *one = mp_from_integer(1); - ek->privateKey = mp_random_in_range(one, ek->curve->w.G_order); - mp_free(one); - - ek->publicKey = ecdsa_public(ek->privateKey, ek->sshk.vt); - - return 1; -} - -int eddsa_generate(struct eddsa_key *ek, int bits) -{ - if (!ec_ed_alg_and_curve_by_bits(bits, &ek->curve, &ek->sshk.vt)) - return 0; - - /* EdDSA secret keys are just 32 bytes of hash preimage; the - * 64-byte SHA-512 hash of that key will be used when signing, - * but the form of the key stored on disk is the preimage - * only. */ - ek->privateKey = mp_random_bits(bits); - - ek->publicKey = eddsa_public(ek->privateKey, ek->sshk.vt); - - return 1; -} diff --git a/sshprime.c b/sshprime.c deleted file mode 100644 index d9bdebba..00000000 --- a/sshprime.c +++ /dev/null @@ -1,762 +0,0 @@ -/* - * Prime generation. - */ - -#include -#include - -#include "ssh.h" -#include "mpint.h" -#include "mpunsafe.h" -#include "sshkeygen.h" - -/* ---------------------------------------------------------------------- - * Standard probabilistic prime-generation algorithm: - * - * - get a number from our PrimeCandidateSource which will at least - * avoid being divisible by any prime under 2^16 - * - * - 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. - */ - -static PrimeGenerationContext *probprime_new_context( - const PrimeGenerationPolicy *policy) -{ - PrimeGenerationContext *ctx = snew(PrimeGenerationContext); - ctx->vt = policy; - return ctx; -} - -static void probprime_free_context(PrimeGenerationContext *ctx) -{ - sfree(ctx); -} - -static ProgressPhase probprime_add_progress_phase( - const PrimeGenerationPolicy *policy, - ProgressReceiver *prog, unsigned bits) -{ - /* - * The density of primes near x is 1/(log x). When x is about 2^b, - * that's 1/(b log 2). - * - * But we're only doing the expensive part of the process (the M-R - * checks) for a number that passes the initial winnowing test of - * having no factor less than 2^16 (at least, unless the prime is - * so small that PrimeCandidateSource gives up on that winnowing). - * The density of _those_ numbers is about 1/19.76. So the odds of - * hitting a prime per expensive attempt are boosted by a factor - * of 19.76. - */ - const double log_2 = 0.693147180559945309417232121458; - double winnow_factor = (bits < 32 ? 1.0 : 19.76); - double prob = winnow_factor / (bits * log_2); - - /* - * Estimate the cost of prime generation as the cost of the M-R - * modexps. - */ - double cost = (miller_rabin_checks_needed(bits) * - estimate_modexp_cost(bits)); - return progress_add_probabilistic(prog, cost, prob); -} - -static mp_int *probprime_generate( - PrimeGenerationContext *ctx, - PrimeCandidateSource *pcs, ProgressReceiver *prog) -{ - pcs_ready(pcs); - - while (true) { - progress_report_attempt(prog); - - mp_int *p = pcs_generate(pcs); - if (!p) { - pcs_free(pcs); - return NULL; - } - - MillerRabin *mr = miller_rabin_new(p); - bool known_bad = false; - unsigned nchecks = miller_rabin_checks_needed(mp_get_nbits(p)); - for (unsigned check = 0; check < nchecks; check++) { - if (!miller_rabin_test_random(mr)) { - known_bad = true; - break; - } - } - miller_rabin_free(mr); - - if (!known_bad) { - /* - * We have a prime! - */ - pcs_free(pcs); - return p; - } - - mp_free(p); - } -} - -static strbuf *null_mpu_certificate(PrimeGenerationContext *ctx, mp_int *p) -{ - return NULL; -} - -const PrimeGenerationPolicy primegen_probabilistic = { - probprime_add_progress_phase, - probprime_new_context, - probprime_free_context, - probprime_generate, - null_mpu_certificate, -}; - -/* ---------------------------------------------------------------------- - * Alternative provable-prime algorithm, based on the following paper: - * - * [MAURER] Maurer, U.M. Fast generation of prime numbers and secure - * public-key cryptographic parameters. J. Cryptology 8, 123–155 - * (1995). https://doi.org/10.1007/BF00202269 - */ - -typedef enum SubprimePolicy { - SPP_FAST, - SPP_MAURER_SIMPLE, - SPP_MAURER_COMPLEX, -} SubprimePolicy; - -typedef struct ProvablePrimePolicyExtra { - SubprimePolicy spp; -} ProvablePrimePolicyExtra; - -typedef struct ProvablePrimeContext ProvablePrimeContext; -struct ProvablePrimeContext { - Pockle *pockle; - PrimeGenerationContext pgc; - const ProvablePrimePolicyExtra *extra; -}; - -static PrimeGenerationContext *provableprime_new_context( - const PrimeGenerationPolicy *policy) -{ - ProvablePrimeContext *ppc = snew(ProvablePrimeContext); - ppc->pgc.vt = policy; - ppc->pockle = pockle_new(); - ppc->extra = policy->extra; - return &ppc->pgc; -} - -static void provableprime_free_context(PrimeGenerationContext *ctx) -{ - ProvablePrimeContext *ppc = container_of(ctx, ProvablePrimeContext, pgc); - pockle_free(ppc->pockle); - sfree(ppc); -} - -static ProgressPhase provableprime_add_progress_phase( - const PrimeGenerationPolicy *policy, - ProgressReceiver *prog, unsigned bits) -{ - /* - * Estimating the cost of making a _provable_ prime is difficult - * because of all the recursions to smaller sizes. - * - * Once you have enough factors of p-1 to certify primality of p, - * the remaining work in provable prime generation is not very - * different from probabilistic: you generate a random candidate, - * test its primality probabilistically, and use the witness value - * generated as a byproduct of that test for the full Pocklington - * verification. The expensive part, as usual, is made of modpows. - * - * The Pocklington test needs at least two modpows (one for the - * Fermat check, and one per known factor of p-1). - * - * The prior M-R step needs an unknown number, because we iterate - * until we find a value whose order is divisible by the largest - * power of 2 that divides p-1, say 2^j. That excludes half the - * possible witness values (specifically, the quadratic residues), - * so we expect to need on average two M-R operations to find one. - * But that's only if the number _is_ prime - as usual, it's also - * possible that we hit a non-prime and have to try again. - * - * So, if we were only estimating the cost of that final step, it - * would look a lot like the probabilistic version: we'd have to - * estimate the expected total number of modexps by knowing - * something about the density of primes among our candidate - * integers, and then multiply that by estimate_modexp_cost(bits). - * But the problem is that we also have to _find_ a smaller prime, - * so we have to recurse. - * - * In the MAURER_SIMPLE version of the algorithm, you recurse to - * any one of a range of possible smaller sizes i, each with - * probability proportional to 1/i. So your expected time to - * generate an n-bit prime is given by a horrible recurrence of - * the form E_n = S_n + (sum E_i/i) / (sum 1/i), in which S_n is - * the expected cost of the final step once you have your smaller - * primes, and both sums are over ceil(n/2) <= i <= n-20. - * - * At this point I ran out of effort to actually do the maths - * rigorously, so instead I did the empirical experiment of - * generating that sequence in Python and plotting it on a graph. - * My Python code is here, in case I need it again: - -from math import log - -alpha = log(3)/log(2) + 1 # exponent for modexp using Karatsuba mult - -E = [1] * 16 # assume generating tiny primes is trivial - -for n in range(len(E), 4096): - - # Expected time for sub-generations, as a weighted mean of prior - # values of the same sequence. - lo = (n+1)//2 - hi = n-20 - if lo <= hi: - subrange = range(lo, hi+1) - num = sum(E[i]/i for i in subrange) - den = sum(1/i for i in subrange) - else: - num, den = 0, 1 - - # Constant term (cost of final step). - # Similar to probprime_add_progress_phase. - winnow_factor = 1 if n < 32 else 19.76 - prob = winnow_factor / (n * log(2)) - cost = 4 * n**alpha / prob - - E.append(cost + num / den) - -for i, p in enumerate(E): - try: - print(log(i), log(p)) - except ValueError: - continue - - * The output loop prints the logs of both i and E_i, so that when - * I plot the resulting data file in gnuplot I get a log-log - * diagram. That showed me some early noise and then a very - * straight-looking line; feeding the straight part of the graph - * to linear-regression analysis reported that it fits the line - * - * log E_n = -1.7901825337965498 + 3.6199197179662517 * log(n) - * => E_n = 0.16692969657466802 * n^3.6199197179662517 - * - * So my somewhat empirical estimate is that Maurer prime - * generation costs about 0.167 * bits^3.62, in the same arbitrary - * time units used by estimate_modexp_cost. - */ - - return progress_add_linear(prog, 0.167 * pow(bits, 3.62)); -} - -static mp_int *primegen_small(Pockle *pockle, PrimeCandidateSource *pcs) -{ - assert(pcs_get_bits(pcs) <= 32); - - pcs_ready(pcs); - - while (true) { - mp_int *p = pcs_generate(pcs); - if (!p) { - pcs_free(pcs); - return NULL; - } - if (pockle_add_small_prime(pockle, p) == POCKLE_OK) { - pcs_free(pcs); - return p; - } - mp_free(p); - } -} - -#ifdef DEBUG_PRIMEGEN -static void timestamp(FILE *fp) -{ - struct timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - fprintf(fp, "%lu.%09lu: ", (unsigned long)ts.tv_sec, - (unsigned long)ts.tv_nsec); -} -static PRINTF_LIKE(1, 2) void debug_f(const char *fmt, ...) -{ - va_list ap; - va_start(ap, fmt); - timestamp(stderr); - vfprintf(stderr, fmt, ap); - fputc('\n', stderr); - va_end(ap); -} -static void debug_f_mp(const char *fmt, mp_int *x, ...) -{ - va_list ap; - va_start(ap, x); - timestamp(stderr); - vfprintf(stderr, fmt, ap); - mp_dump(stderr, "", x, "\n"); - va_end(ap); -} -#else -#define debug_f(...) ((void)0) -#define debug_f_mp(...) ((void)0) -#endif - -static double uniform_random_double(void) -{ - unsigned char randbuf[8]; - random_read(randbuf, 8); - return GET_64BIT_MSB_FIRST(randbuf) * 0x1.0p-64; -} - -static mp_int *mp_ceil_div(mp_int *n, mp_int *d) -{ - mp_int *nplus = mp_add(n, d); - mp_sub_integer_into(nplus, nplus, 1); - mp_int *toret = mp_div(nplus, d); - mp_free(nplus); - return toret; -} - -static mp_int *provableprime_generate_inner( - ProvablePrimeContext *ppc, PrimeCandidateSource *pcs, - ProgressReceiver *prog, double progress_origin, double progress_scale) -{ - unsigned bits = pcs_get_bits(pcs); - assert(bits > 1); - - if (bits <= 32) { - debug_f("ppgi(%u) -> small", bits); - return primegen_small(ppc->pockle, pcs); - } - - unsigned min_bits_needed, max_bits_needed; - { - /* - * Find the product of all the prime factors we already know - * about. - */ - mp_int *size_got = mp_from_integer(1); - size_t nfactors; - mp_int **factors = pcs_get_known_prime_factors(pcs, &nfactors); - for (size_t i = 0; i < nfactors; i++) { - mp_int *to_free = size_got; - size_got = mp_unsafe_shrink(mp_mul(size_got, factors[i])); - mp_free(to_free); - } - - /* - * Find the largest cofactor we might be able to use, and the - * smallest one we can get away with. - */ - mp_int *upperbound = pcs_get_upper_bound(pcs); - mp_int *size_needed = mp_nthroot(upperbound, 3, NULL); - debug_f_mp("upperbound = ", upperbound); - { - mp_int *to_free = upperbound; - upperbound = mp_unsafe_shrink(mp_div(upperbound, size_got)); - mp_free(to_free); - } - debug_f_mp("size_needed = ", size_needed); - { - mp_int *to_free = size_needed; - size_needed = mp_unsafe_shrink(mp_ceil_div(size_needed, size_got)); - mp_free(to_free); - } - - max_bits_needed = pcs_get_bits_remaining(pcs); - - /* - * We need a prime that is greater than or equal to - * 'size_needed' in order for the product of all our known - * factors of p-1 to exceed the cube root of the largest value - * p might take. - * - * Since pcs_new wants a size specified in bits, we must count - * the bits in size_needed and then add 1. Otherwise we might - * get a value with the same bit count as size_needed but - * slightly smaller than it. - * - * An exception is if size_needed = 1. In that case the - * product of existing known factors is _already_ enough, so - * we don't need to generate an extra factor at all. - */ - if (mp_hs_integer(size_needed, 2)) { - min_bits_needed = mp_get_nbits(size_needed) + 1; - } else { - min_bits_needed = 0; - } - - mp_free(upperbound); - mp_free(size_needed); - mp_free(size_got); - } - - double progress = 0.0; - - if (min_bits_needed) { - debug_f("ppgi(%u) recursing, need [%u,%u] more bits", - bits, min_bits_needed, max_bits_needed); - - unsigned *sizes = NULL; - size_t nsizes = 0, sizesize = 0; - - unsigned real_min = max_bits_needed / 2; - unsigned real_max = (max_bits_needed >= 20 ? - max_bits_needed - 20 : 0); - if (real_min < min_bits_needed) - real_min = min_bits_needed; - if (real_max < real_min) - real_max = real_min; - debug_f("ppgi(%u) revised bits interval = [%u,%u]", - bits, real_min, real_max); - - switch (ppc->extra->spp) { - case SPP_FAST: - /* - * Always pick the smallest subsidiary prime we can get - * away with: just over n/3 bits. - * - * This is not a good mode for cryptographic prime - * generation, because it skews the distribution of primes - * greatly, and worse, it skews them in a direction that - * heads away from the properties crypto algorithms tend - * to like. - * - * (For both discrete-log systems and RSA, people have - * tended to recommend in the past that p-1 should have a - * _large_ factor if possible. There's some disagreement - * on which algorithms this is really necessary for, but - * certainly I've never seen anyone recommend arranging a - * _small_ factor on purpose.) - * - * I originally implemented this mode because it was - * convenient for debugging - it wastes as little time as - * possible on finding a sub-prime and lets you get to the - * interesting part! And I leave it in the code because it - * might still be useful for _something_. Because it's - * cryptographically questionable, it's not selectable in - * the UI of either version of PuTTYgen proper; but it can - * be accessed through testcrypt, and if for some reason a - * definite prime is needed for non-crypto purposes, it - * may still be the fastest way to put your hands on one. - */ - debug_f("ppgi(%u) fast mode, just ask for %u bits", - bits, min_bits_needed); - sgrowarray(sizes, sizesize, nsizes); - sizes[nsizes++] = min_bits_needed; - break; - case SPP_MAURER_SIMPLE: { - /* - * Select the size of the subsidiary prime at random from - * sqrt(outputprime) up to outputprime/2^20, in such a way - * that the probability distribution matches that of the - * largest prime factor of a random n-bit number. - * - * Per [MAURER] section 3.4, the cumulative distribution - * function of this relative size is 1+log2(x), for x in - * [1/2,1]. You can generate a value from the distribution - * given by a cdf by applying the inverse cdf to a uniform - * value in [0,1]. Simplifying that in this case, what we - * have to do is raise 2 to the power of a random real - * number between -1 and 0. (And that gives you the number - * of _bits_ in the sub-prime, as a factor of the desired - * output number of bits.) - * - * We also require that the subsidiary prime q is at least - * 20 bits smaller than the output one, to give us a - * fighting chance of there being _any_ prime we can find - * such that q | p-1. - * - * (But these rules have to be applied in an order that - * still leaves us _some_ interval of possible sizes we - * can pick!) - */ - maurer_simple: - debug_f("ppgi(%u) Maurer simple mode", bits); - - unsigned sub_bits; - do { - double uniform = uniform_random_double(); - sub_bits = real_max * pow(2.0, uniform - 1) + 0.5; - debug_f(" ... %.6f -> %u?", uniform, sub_bits); - } while (!(real_min <= sub_bits && sub_bits <= real_max)); - - debug_f("ppgi(%u) asking for %u bits", bits, sub_bits); - sgrowarray(sizes, sizesize, nsizes); - sizes[nsizes++] = sub_bits; - - break; - } - case SPP_MAURER_COMPLEX: { - /* - * In this mode, we may generate multiple factors of p-1 - * which between them add up to at least n/2 bits, in such - * a way that those are guaranteed to be the largest - * factors of p-1 and that they have the same probability - * distribution as the largest k factors would have in a - * random integer. The idea is that this more elaborate - * procedure gets as close as possible to the same - * probability distribution you'd get by selecting a - * completely random prime (if you feasibly could). - * - * Algorithm from Appendix 1 of [MAURER]: we generate - * random real numbers that sum to at most 1, by choosing - * each one uniformly from the range [0, 1 - sum of all - * the previous ones]. We maintain them in a list in - * decreasing order, and we stop as soon as we find an - * initial subsequence of the list s_1,...,s_r such that - * s_1 + ... + s_{r-1} + 2 s_r > 1. In particular, this - * guarantees that the sum of that initial subsequence is - * at least 1/2, so we end up with enough factors to - * satisfy Pocklington. - */ - - if (max_bits_needed / 2 + 1 > real_max) { - /* Early exit path in the case where this algorithm - * can't possibly generate a value in the range we - * need. In that situation, fall back to Maurer - * simple. */ - debug_f("ppgi(%u) skipping GenerateSizeList, " - "real_max too small", bits); - goto maurer_simple; /* sorry! */ - } - - double *s = NULL; - size_t ns, ssize = 0; - - while (true) { - debug_f("ppgi(%u) starting GenerateSizeList", bits); - ns = 0; - double range = 1.0; - while (true) { - /* Generate the next number */ - double u = uniform_random_double() * range; - range -= u; - debug_f(" u_%"SIZEu" = %g", ns, u); - - /* Insert it in the list */ - sgrowarray(s, ssize, ns); - size_t i; - for (i = ns; i > 0 && s[i-1] < u; i--) - s[i] = s[i-1]; - s[i] = u; - ns++; - debug_f(" inserting as s[%"SIZEu"]", i); - - /* Look for a suitable initial subsequence */ - double sum = 0; - for (i = 0; i < ns; i++) { - sum += s[i]; - if (sum + s[i] > 1.0) { - debug_f(" s[0..%"SIZEu"] works!", i); - - /* Truncate the sequence here, and stop - * generating random real numbers. */ - ns = i+1; - goto got_list; - } - } - } - - got_list:; - /* - * Now translate those real numbers into actual bit - * counts, and do a last-minute check to make sure - * their product is going to be in range. - * - * We have to check both the min and max sizes of the - * total. A b-bit number is in [2^{b-1},2^b). So the - * product of numbers of sizes b_1,...,b_k is at least - * 2^{\sum (b_i-1)}, and less than 2^{\sum b_i}. - */ - nsizes = 0; - - unsigned min_total = 0, max_total = 0; - - for (size_t i = 0; i < ns; i++) { - /* These sizes are measured in actual entropy, so - * add 1 bit each time to account for the - * zero-information leading 1 */ - unsigned this_size = max_bits_needed * s[i] + 1; - debug_f(" bits[%"SIZEu"] = %u", i, this_size); - sgrowarray(sizes, sizesize, nsizes); - sizes[nsizes++] = this_size; - - min_total += this_size - 1; - max_total += this_size; - } - - debug_f(" total bits = [%u,%u)", min_total, max_total); - if (min_total < real_min || max_total > real_max+1) { - debug_f(" total out of range, try again"); - } else { - debug_f(" success! %"SIZEu" sub-primes totalling [%u,%u) " - "bits", nsizes, min_total, max_total); - break; - } - } - - smemclr(s, ssize * sizeof(*s)); - sfree(s); - break; - } - default: - unreachable("bad subprime policy"); - } - - for (size_t i = 0; i < nsizes; i++) { - unsigned sub_bits = sizes[i]; - double progress_in_this_prime = (double)sub_bits / bits; - mp_int *q = provableprime_generate_inner( - ppc, pcs_new(sub_bits), - prog, progress_origin + progress_scale * progress, - progress_scale * progress_in_this_prime); - progress += progress_in_this_prime; - assert(q); - debug_f_mp("ppgi(%u) got factor ", q, bits); - pcs_require_residue_1_mod_prime(pcs, q); - mp_free(q); - } - - smemclr(sizes, sizesize * sizeof(*sizes)); - sfree(sizes); - } else { - debug_f("ppgi(%u) no need to recurse", bits); - } - - debug_f("ppgi(%u) ready, %u bits remaining", - bits, pcs_get_bits_remaining(pcs)); - pcs_ready(pcs); - - while (true) { - mp_int *p = pcs_generate(pcs); - if (!p) { - pcs_free(pcs); - return NULL; - } - - debug_f_mp("provable_step p=", p); - - MillerRabin *mr = miller_rabin_new(p); - debug_f("provable_step mr setup done"); - mp_int *witness = miller_rabin_find_potential_primitive_root(mr); - miller_rabin_free(mr); - - if (!witness) { - debug_f("provable_step mr failed"); - mp_free(p); - continue; - } - - size_t nfactors; - mp_int **factors = pcs_get_known_prime_factors(pcs, &nfactors); - PockleStatus st = pockle_add_prime( - ppc->pockle, p, factors, nfactors, witness); - - if (st != POCKLE_OK) { - debug_f("provable_step proof failed %d", (int)st); - - /* - * Check by assertion that the error status is not one of - * the ones we ought to have ruled out already by - * construction. If there's a bug in this code that means - * we can _never_ pass this test (e.g. picking products of - * factors that never quite reach cbrt(n)), we'd rather - * fail an assertion than loop forever. - */ - assert(st == POCKLE_DISCRIMINANT_IS_SQUARE || - st == POCKLE_WITNESS_POWER_IS_1 || - st == POCKLE_WITNESS_POWER_NOT_COPRIME); - - mp_free(p); - if (witness) - mp_free(witness); - continue; - } - - mp_free(witness); - pcs_free(pcs); - debug_f_mp("ppgi(%u) done, got ", p, bits); - progress_report(prog, progress_origin + progress_scale); - return p; - } -} - -static mp_int *provableprime_generate( - PrimeGenerationContext *ctx, - PrimeCandidateSource *pcs, ProgressReceiver *prog) -{ - ProvablePrimeContext *ppc = container_of(ctx, ProvablePrimeContext, pgc); - mp_int *p = provableprime_generate_inner(ppc, pcs, prog, 0.0, 1.0); - - return p; -} - -static inline strbuf *provableprime_mpu_certificate( - PrimeGenerationContext *ctx, mp_int *p) -{ - ProvablePrimeContext *ppc = container_of(ctx, ProvablePrimeContext, pgc); - return pockle_mpu(ppc->pockle, p); -} - -#define DECLARE_POLICY(name, policy) \ - static const struct ProvablePrimePolicyExtra \ - pppextra_##name = {policy}; \ - const PrimeGenerationPolicy name = { \ - provableprime_add_progress_phase, \ - provableprime_new_context, \ - provableprime_free_context, \ - provableprime_generate, \ - provableprime_mpu_certificate, \ - &pppextra_##name, \ - } - -DECLARE_POLICY(primegen_provable_fast, SPP_FAST); -DECLARE_POLICY(primegen_provable_maurer_simple, SPP_MAURER_SIMPLE); -DECLARE_POLICY(primegen_provable_maurer_complex, SPP_MAURER_COMPLEX); - -/* ---------------------------------------------------------------------- - * Reusable null implementation of the progress-reporting API. - */ - -static inline ProgressPhase null_progress_add(void) { - ProgressPhase ph = { .n = 0 }; - return ph; -} -ProgressPhase null_progress_add_linear( - ProgressReceiver *prog, double c) { return null_progress_add(); } -ProgressPhase null_progress_add_probabilistic( - ProgressReceiver *prog, double c, double p) { return null_progress_add(); } -void null_progress_ready(ProgressReceiver *prog) {} -void null_progress_start_phase(ProgressReceiver *prog, ProgressPhase phase) {} -void null_progress_report(ProgressReceiver *prog, double progress) {} -void null_progress_report_attempt(ProgressReceiver *prog) {} -void null_progress_report_phase_complete(ProgressReceiver *prog) {} -const ProgressReceiverVtable null_progress_vt = { - .add_linear = null_progress_add_linear, - .add_probabilistic = null_progress_add_probabilistic, - .ready = null_progress_ready, - .start_phase = null_progress_start_phase, - .report = null_progress_report, - .report_attempt = null_progress_report_attempt, - .report_phase_complete = null_progress_report_phase_complete, -}; - -/* ---------------------------------------------------------------------- - * Helper function for progress estimation. - */ - -double estimate_modexp_cost(unsigned bits) -{ - /* - * A modexp of n bits goes roughly like O(n^2.58), on the grounds - * that our modmul is O(n^1.58) (Karatsuba) and you need O(n) of - * them in a modexp. - */ - return pow(bits, 2.58); -} diff --git a/sshrsag.c b/sshrsag.c deleted file mode 100644 index b9676e7a..00000000 --- a/sshrsag.c +++ /dev/null @@ -1,292 +0,0 @@ -/* - * RSA key generation. - */ - -#include - -#include "ssh.h" -#include "sshkeygen.h" -#include "mpint.h" - -#define RSA_EXPONENT 65537 - -#define NFIRSTBITS 13 -static void invent_firstbits(unsigned *one, unsigned *two, - unsigned min_separation); - -typedef struct RSAPrimeDetails RSAPrimeDetails; -struct RSAPrimeDetails { - bool strong; - int bits, bitsm1m1, bitsm1, bitsp1; - unsigned firstbits; - ProgressPhase phase_main, phase_m1m1, phase_m1, phase_p1; -}; - -#define STRONG_MARGIN (20 + NFIRSTBITS) - -static RSAPrimeDetails setup_rsa_prime( - int bits, bool strong, PrimeGenerationContext *pgc, ProgressReceiver *prog) -{ - RSAPrimeDetails pd; - pd.bits = bits; - if (strong) { - pd.bitsm1 = (bits - STRONG_MARGIN) / 2; - pd.bitsp1 = (bits - STRONG_MARGIN) - pd.bitsm1; - pd.bitsm1m1 = (pd.bitsm1 - STRONG_MARGIN) / 2; - if (pd.bitsm1m1 < STRONG_MARGIN) { - /* Absurdly small prime, but we should at least not crash. */ - strong = false; - } - } - pd.strong = strong; - - if (pd.strong) { - pd.phase_m1m1 = primegen_add_progress_phase(pgc, prog, pd.bitsm1m1); - pd.phase_m1 = primegen_add_progress_phase(pgc, prog, pd.bitsm1); - pd.phase_p1 = primegen_add_progress_phase(pgc, prog, pd.bitsp1); - } - pd.phase_main = primegen_add_progress_phase(pgc, prog, pd.bits); - - return pd; -} - -static mp_int *generate_rsa_prime( - RSAPrimeDetails pd, PrimeGenerationContext *pgc, ProgressReceiver *prog) -{ - mp_int *m1m1 = NULL, *m1 = NULL, *p1 = NULL, *p = NULL; - PrimeCandidateSource *pcs; - - if (pd.strong) { - progress_start_phase(prog, pd.phase_m1m1); - pcs = pcs_new_with_firstbits(pd.bitsm1m1, pd.firstbits, NFIRSTBITS); - m1m1 = primegen_generate(pgc, pcs, prog); - progress_report_phase_complete(prog); - - progress_start_phase(prog, pd.phase_m1); - pcs = pcs_new_with_firstbits(pd.bitsm1, pd.firstbits, NFIRSTBITS); - pcs_require_residue_1_mod_prime(pcs, m1m1); - m1 = primegen_generate(pgc, pcs, prog); - progress_report_phase_complete(prog); - - progress_start_phase(prog, pd.phase_p1); - pcs = pcs_new_with_firstbits(pd.bitsp1, pd.firstbits, NFIRSTBITS); - p1 = primegen_generate(pgc, pcs, prog); - progress_report_phase_complete(prog); - } - - progress_start_phase(prog, pd.phase_main); - pcs = pcs_new_with_firstbits(pd.bits, pd.firstbits, NFIRSTBITS); - pcs_avoid_residue_small(pcs, RSA_EXPONENT, 1); - if (pd.strong) { - pcs_require_residue_1_mod_prime(pcs, m1); - mp_int *p1_minus_1 = mp_copy(p1); - mp_sub_integer_into(p1_minus_1, p1, 1); - pcs_require_residue(pcs, p1, p1_minus_1); - mp_free(p1_minus_1); - } - p = primegen_generate(pgc, pcs, prog); - progress_report_phase_complete(prog); - - if (m1m1) - mp_free(m1m1); - if (m1) - mp_free(m1); - if (p1) - mp_free(p1); - - return p; -} - -int rsa_generate(RSAKey *key, int bits, bool strong, - PrimeGenerationContext *pgc, ProgressReceiver *prog) -{ - key->sshk.vt = &ssh_rsa; - - /* - * We don't generate e; we just use a standard one always. - */ - mp_int *exponent = mp_from_integer(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.) - * - * We give a min_separation of 2 to invent_firstbits(), ensuring - * that the two primes won't be very close to each other. (The - * chance of them being _dangerously_ close is negligible - even - * more so than an attacker guessing a whole 256-bit session key - - * but it doesn't cost much to make sure.) - */ - int qbits = bits / 2; - int pbits = bits - qbits; - assert(pbits >= qbits); - - RSAPrimeDetails pd = setup_rsa_prime(pbits, strong, pgc, prog); - RSAPrimeDetails qd = setup_rsa_prime(qbits, strong, pgc, prog); - progress_ready(prog); - - invent_firstbits(&pd.firstbits, &qd.firstbits, 2); - - mp_int *p = generate_rsa_prime(pd, pgc, prog); - mp_int *q = generate_rsa_prime(qd, pgc, prog); - - /* - * Ensure p > q, by swapping them if not. - * - * We only need to do this if the two primes were generated with - * the same number of bits (i.e. if the requested key size is - * even) - otherwise it's already guaranteed! - */ - if (pbits == qbits) { - mp_cond_swap(p, q, mp_cmp_hs(q, p)); - } else { - assert(mp_cmp_hs(p, q)); - } - - /* - * 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). - */ - mp_int *modulus = mp_mul(p, q); - mp_int *pm1 = mp_copy(p); - mp_sub_integer_into(pm1, pm1, 1); - mp_int *qm1 = mp_copy(q); - mp_sub_integer_into(qm1, qm1, 1); - mp_int *phi_n = mp_mul(pm1, qm1); - mp_free(pm1); - mp_free(qm1); - mp_int *private_exponent = mp_invert(exponent, phi_n); - mp_free(phi_n); - mp_int *iqmp = mp_invert(q, p); - - /* - * Populate the returned structure. - */ - key->modulus = modulus; - key->exponent = exponent; - key->private_exponent = private_exponent; - key->p = p; - key->q = q; - key->iqmp = iqmp; - - key->bits = mp_get_nbits(modulus); - key->bytes = (key->bits + 7) / 8; - - return 1; -} - -/* - * Invent a pair of values suitable for use as the 'firstbits' values - * for the two RSA primes, such that their product is at least 2, and - * such that their difference is also at least min_separation. - * - * This is used for generating RSA keys which have exactly the - * specified number of bits rather than one fewer - if you generate an - * a-bit and a b-bit number completely at random and multiply them - * together, you could end up with either an (ab-1)-bit number or an - * (ab)-bit number. The former happens log(2)*2-1 of the time (about - * 39%) and, though actually harmless, every time it occurs it has a - * non-zero probability of sparking a user email along the lines of - * 'Hey, I asked PuTTYgen for a 2048-bit key and I only got 2047 bits! - * Bug!' - */ -static inline unsigned firstbits_b_min( - unsigned a, unsigned lo, unsigned hi, unsigned min_separation) -{ - /* To get a large enough product, b must be at least this much */ - unsigned b_min = (2*lo*lo + a - 1) / a; - /* Now enforce a hi) - b_min = hi; - return b_min; -} - -static void invent_firstbits(unsigned *one, unsigned *two, - unsigned min_separation) -{ - /* - * We'll pick 12 initial bits (number selected at random) for each - * prime, not counting the leading 1. So we want to return two - * values in the range [2^12,2^13) whose product is at least 2^25. - * - * Strategy: count up all the viable pairs, then select a random - * number in that range and use it to pick a pair. - * - * To keep things simple, we'll ensure a < b, and randomly swap - * them at the end. - */ - const unsigned lo = 1<<12, hi = 1<<13, minproduct = 2*lo*lo; - unsigned a, b; - - /* - * Count up the number of prefixes of b that would be valid for - * each prefix of a. - */ - mp_int *total = mp_new(32); - for (a = lo; a < hi; a++) { - unsigned b_min = firstbits_b_min(a, lo, hi, min_separation); - mp_add_integer_into(total, total, hi - b_min); - } - - /* - * Make up a random number in the range [0,2*total). - */ - mp_int *mlo = mp_from_integer(0), *mhi = mp_new(32); - mp_lshift_fixed_into(mhi, total, 1); - mp_int *randval = mp_random_in_range(mlo, mhi); - mp_free(mlo); - mp_free(mhi); - - /* - * Use the low bit of randval as our swap indicator, leaving the - * rest of it in the range [0,total). - */ - unsigned swap = mp_get_bit(randval, 0); - mp_rshift_fixed_into(randval, randval, 1); - - /* - * Now do the same counting loop again to make the actual choice. - */ - a = b = 0; - for (unsigned a_candidate = lo; a_candidate < hi; a_candidate++) { - unsigned b_min = firstbits_b_min(a_candidate, lo, hi, min_separation); - unsigned limit = hi - b_min; - - unsigned b_candidate = b_min + mp_get_integer(randval); - unsigned use_it = 1 ^ mp_hs_integer(randval, limit); - a ^= (a ^ a_candidate) & -use_it; - b ^= (b ^ b_candidate) & -use_it; - - mp_sub_integer_into(randval, randval, limit); - } - - mp_free(randval); - mp_free(total); - - /* - * Check everything came out right. - */ - assert(lo <= a); - assert(a < hi); - assert(lo <= b); - assert(b < hi); - assert(a * b >= minproduct); - assert(b >= a + min_separation); - - /* - * Last-minute optional swap of a and b. - */ - unsigned diff = (a ^ b) & (-swap); - a ^= diff; - b ^= diff; - - *one = a; - *two = b; -} -- cgit v1.2.3 From 419e5e2230850f1555d44e62417e8f606cb68e6e Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 22 Apr 2021 18:17:35 +0100 Subject: Move other backends into a subdirectory. This is the last of the subdirectory creations I had planned. This one is almost too footling to bother with (it hardly declutters the top level very much). One useful side effect is that I've included testback.c (containing the null and loopback backends) in the otherbackends library, which means it will now actually be _compiled_ even when nothing's using it, and we'll spot bit-rot promptly when internal APIs change. (And, to prove the point, I've immediately had to fix some bit-rot.) --- CMakeLists.txt | 2 +- otherbackends/CMakeLists.txt | 6 + otherbackends/raw.c | 330 +++++++++++++ otherbackends/rlogin.c | 428 +++++++++++++++++ otherbackends/supdup.c | 923 ++++++++++++++++++++++++++++++++++++ otherbackends/telnet.c | 1070 ++++++++++++++++++++++++++++++++++++++++++ otherbackends/testback.c | 214 +++++++++ raw.c | 330 ------------- rlogin.c | 428 ----------------- supdup.c | 923 ------------------------------------ telnet.c | 1070 ------------------------------------------ testback.c | 214 --------- 12 files changed, 2972 insertions(+), 2966 deletions(-) create mode 100644 otherbackends/CMakeLists.txt create mode 100644 otherbackends/raw.c create mode 100644 otherbackends/rlogin.c create mode 100644 otherbackends/supdup.c create mode 100644 otherbackends/telnet.c create mode 100644 otherbackends/testback.c delete mode 100644 raw.c delete mode 100644 rlogin.c delete mode 100644 supdup.c delete mode 100644 telnet.c delete mode 100644 testback.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 2a944fc7..4bf6eacd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -51,9 +51,9 @@ add_library(sftpclient STATIC add_subdirectory(ssh) add_library(otherbackends STATIC - telnet.c rlogin.c raw.c supdup.c $ $) +add_subdirectory(otherbackends) add_executable(testcrypt testcrypt.c sshpubk.c ssh/crc-attack-detector.c) diff --git a/otherbackends/CMakeLists.txt b/otherbackends/CMakeLists.txt new file mode 100644 index 00000000..099d1253 --- /dev/null +++ b/otherbackends/CMakeLists.txt @@ -0,0 +1,6 @@ +add_sources_from_current_dir(otherbackends + raw.c + rlogin.c + supdup.c + telnet.c + testback.c) diff --git a/otherbackends/raw.c b/otherbackends/raw.c new file mode 100644 index 00000000..0c454985 --- /dev/null +++ b/otherbackends/raw.c @@ -0,0 +1,330 @@ +/* + * "Raw" backend. + */ + +#include +#include +#include + +#include "putty.h" + +#define RAW_MAX_BACKLOG 4096 + +typedef struct Raw Raw; +struct Raw { + Socket *s; + bool closed_on_socket_error; + size_t bufsize; + Seat *seat; + LogContext *logctx; + bool sent_console_eof, sent_socket_eof, session_started; + + Conf *conf; + + Plug plug; + Backend backend; +}; + +static void raw_size(Backend *be, int width, int height); + +static void c_write(Raw *raw, const void *buf, size_t len) +{ + size_t backlog = seat_stdout(raw->seat, buf, len); + sk_set_frozen(raw->s, backlog > RAW_MAX_BACKLOG); +} + +static void raw_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, + const char *error_msg, int error_code) +{ + Raw *raw = container_of(plug, Raw, plug); + backend_socket_log(raw->seat, raw->logctx, type, addr, port, + error_msg, error_code, raw->conf, raw->session_started); +} + +static void raw_check_close(Raw *raw) +{ + /* + * Called after we send EOF on either the socket or the console. + * Its job is to wind up the session once we have sent EOF on both. + */ + if (raw->sent_console_eof && raw->sent_socket_eof) { + if (raw->s) { + sk_close(raw->s); + raw->s = NULL; + seat_notify_remote_exit(raw->seat); + } + } +} + +static void raw_closing(Plug *plug, const char *error_msg, int error_code, + bool calling_back) +{ + Raw *raw = container_of(plug, Raw, plug); + + if (error_msg) { + /* A socket error has occurred. */ + if (raw->s) { + sk_close(raw->s); + raw->s = NULL; + raw->closed_on_socket_error = true; + seat_notify_remote_exit(raw->seat); + } + logevent(raw->logctx, error_msg); + seat_connection_fatal(raw->seat, "%s", error_msg); + } else { + /* Otherwise, the remote side closed the connection normally. */ + if (!raw->sent_console_eof && seat_eof(raw->seat)) { + /* + * The front end wants us to close the outgoing side of the + * connection as soon as we see EOF from the far end. + */ + if (!raw->sent_socket_eof) { + if (raw->s) + sk_write_eof(raw->s); + raw->sent_socket_eof= true; + } + } + raw->sent_console_eof = true; + raw_check_close(raw); + } +} + +static void raw_receive(Plug *plug, int urgent, const char *data, size_t len) +{ + Raw *raw = container_of(plug, Raw, plug); + c_write(raw, data, len); + /* We count 'session start', for proxy logging purposes, as being + * when data is received from the network and printed. */ + raw->session_started = true; +} + +static void raw_sent(Plug *plug, size_t bufsize) +{ + Raw *raw = container_of(plug, Raw, plug); + raw->bufsize = bufsize; +} + +static const PlugVtable Raw_plugvt = { + .log = raw_log, + .closing = raw_closing, + .receive = raw_receive, + .sent = raw_sent, +}; + +/* + * 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 char *raw_init(const BackendVtable *vt, Seat *seat, + Backend **backend_handle, LogContext *logctx, + Conf *conf, const char *host, int port, + char **realhost, bool nodelay, bool keepalive) +{ + SockAddr *addr; + const char *err; + Raw *raw; + int addressfamily; + char *loghost; + + /* No local authentication phase in this protocol */ + seat_set_trust_status(seat, false); + + raw = snew(Raw); + raw->plug.vt = &Raw_plugvt; + raw->backend.vt = vt; + raw->s = NULL; + raw->closed_on_socket_error = false; + *backend_handle = &raw->backend; + raw->sent_console_eof = raw->sent_socket_eof = false; + raw->bufsize = 0; + raw->session_started = false; + raw->conf = conf_copy(conf); + + raw->seat = seat; + raw->logctx = logctx; + + addressfamily = conf_get_int(conf, CONF_addressfamily); + /* + * Try to find host. + */ + addr = name_lookup(host, port, realhost, conf, addressfamily, + raw->logctx, "main connection"); + if ((err = sk_addr_error(addr)) != NULL) { + sk_addr_free(addr); + return dupstr(err); + } + + if (port < 0) + port = 23; /* default telnet port */ + + /* + * Open socket. + */ + raw->s = new_connection(addr, *realhost, port, false, true, nodelay, + keepalive, &raw->plug, conf); + if ((err = sk_socket_error(raw->s)) != NULL) + return dupstr(err); + + loghost = conf_get_str(conf, CONF_loghost); + if (*loghost) { + char *colon; + + sfree(*realhost); + *realhost = dupstr(loghost); + + colon = host_strrchr(*realhost, ':'); + if (colon) + *colon++ = '\0'; + } + + return NULL; +} + +static void raw_free(Backend *be) +{ + Raw *raw = container_of(be, Raw, backend); + + if (raw->s) + sk_close(raw->s); + conf_free(raw->conf); + sfree(raw); +} + +/* + * Stub routine (we don't have any need to reconfigure this backend). + */ +static void raw_reconfig(Backend *be, Conf *conf) +{ +} + +/* + * Called to send data down the raw connection. + */ +static size_t raw_send(Backend *be, const char *buf, size_t len) +{ + Raw *raw = container_of(be, Raw, backend); + + 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 size_t raw_sendbuffer(Backend *be) +{ + Raw *raw = container_of(be, Raw, backend); + return raw->bufsize; +} + +/* + * Called to set the size of the window + */ +static void raw_size(Backend *be, int width, int height) +{ + /* Do nothing! */ + return; +} + +/* + * Send raw special codes. We only handle outgoing EOF here. + */ +static void raw_special(Backend *be, SessionSpecialCode code, int arg) +{ + Raw *raw = container_of(be, Raw, backend); + if (code == SS_EOF && raw->s) { + sk_write_eof(raw->s); + raw->sent_socket_eof= true; + raw_check_close(raw); + } + + return; +} + +/* + * Return a list of the special codes that make sense in this + * protocol. + */ +static const SessionSpecial *raw_get_specials(Backend *be) +{ + return NULL; +} + +static bool raw_connected(Backend *be) +{ + Raw *raw = container_of(be, Raw, backend); + return raw->s != NULL; +} + +static bool raw_sendok(Backend *be) +{ + return true; +} + +static void raw_unthrottle(Backend *be, size_t backlog) +{ + Raw *raw = container_of(be, Raw, backend); + sk_set_frozen(raw->s, backlog > RAW_MAX_BACKLOG); +} + +static bool raw_ldisc(Backend *be, int option) +{ + if (option == LD_EDIT || option == LD_ECHO) + return true; + return false; +} + +static void raw_provide_ldisc(Backend *be, Ldisc *ldisc) +{ + /* This is a stub. */ +} + +static int raw_exitcode(Backend *be) +{ + Raw *raw = container_of(be, Raw, backend); + if (raw->s != NULL) + return -1; /* still connected */ + else if (raw->closed_on_socket_error) + return INT_MAX; /* a socket error counts as an unclean exit */ + else + /* Exit codes are a meaningless concept in the Raw protocol */ + return 0; +} + +/* + * cfg_info for Raw does nothing at all. + */ +static int raw_cfg_info(Backend *be) +{ + return 0; +} + +const BackendVtable raw_backend = { + .init = raw_init, + .free = raw_free, + .reconfig = raw_reconfig, + .send = raw_send, + .sendbuffer = raw_sendbuffer, + .size = raw_size, + .special = raw_special, + .get_specials = raw_get_specials, + .connected = raw_connected, + .exitcode = raw_exitcode, + .sendok = raw_sendok, + .ldisc_option_state = raw_ldisc, + .provide_ldisc = raw_provide_ldisc, + .unthrottle = raw_unthrottle, + .cfg_info = raw_cfg_info, + .id = "raw", + .displayname = "Raw", + .protocol = PROT_RAW, + .default_port = 0, +}; diff --git a/otherbackends/rlogin.c b/otherbackends/rlogin.c new file mode 100644 index 00000000..2a3714e0 --- /dev/null +++ b/otherbackends/rlogin.c @@ -0,0 +1,428 @@ +/* + * Rlogin backend. + */ + +#include +#include +#include +#include + +#include "putty.h" + +#define RLOGIN_MAX_BACKLOG 4096 + +typedef struct Rlogin Rlogin; +struct Rlogin { + Socket *s; + bool closed_on_socket_error; + int bufsize; + bool firstbyte; + bool cansize; + int term_width, term_height; + Seat *seat; + LogContext *logctx; + + Conf *conf; + + /* In case we need to read a username from the terminal before starting */ + prompts_t *prompt; + + Plug plug; + Backend backend; +}; + +static void c_write(Rlogin *rlogin, const void *buf, size_t len) +{ + size_t backlog = seat_stdout(rlogin->seat, buf, len); + sk_set_frozen(rlogin->s, backlog > RLOGIN_MAX_BACKLOG); +} + +static void rlogin_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, + const char *error_msg, int error_code) +{ + Rlogin *rlogin = container_of(plug, Rlogin, plug); + backend_socket_log(rlogin->seat, rlogin->logctx, type, addr, port, + error_msg, error_code, + rlogin->conf, !rlogin->firstbyte); +} + +static void rlogin_closing(Plug *plug, const char *error_msg, int error_code, + bool calling_back) +{ + Rlogin *rlogin = container_of(plug, Rlogin, plug); + + /* + * We don't implement independent EOF in each direction for Telnet + * connections; as soon as we get word that the remote side has + * sent us EOF, we wind up the whole connection. + */ + + if (rlogin->s) { + sk_close(rlogin->s); + rlogin->s = NULL; + if (error_msg) + rlogin->closed_on_socket_error = true; + seat_notify_remote_exit(rlogin->seat); + } + if (error_msg) { + /* A socket error has occurred. */ + logevent(rlogin->logctx, error_msg); + seat_connection_fatal(rlogin->seat, "%s", error_msg); + } /* Otherwise, the remote side closed the connection normally. */ +} + +static void rlogin_receive( + Plug *plug, int urgent, const char *data, size_t len) +{ + Rlogin *rlogin = container_of(plug, Rlogin, plug); + if (len == 0) + return; + if (urgent == 2) { + char c; + + c = *data++; + len--; + if (c == '\x80') { + rlogin->cansize = true; + backend_size(&rlogin->backend, + 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 = false; + } + if (len > 0) + c_write(rlogin, data, len); + } +} + +static void rlogin_sent(Plug *plug, size_t bufsize) +{ + Rlogin *rlogin = container_of(plug, 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); + p = conf_get_str(rlogin->conf, CONF_localusername); + sk_write(rlogin->s, p, strlen(p)); + sk_write(rlogin->s, &z, 1); + sk_write(rlogin->s, ruser, strlen(ruser)); + sk_write(rlogin->s, &z, 1); + p = conf_get_str(rlogin->conf, CONF_termtype); + sk_write(rlogin->s, p, strlen(p)); + sk_write(rlogin->s, "/", 1); + p = conf_get_str(rlogin->conf, CONF_termspeed); + sk_write(rlogin->s, p, strspn(p, "0123456789")); + rlogin->bufsize = sk_write(rlogin->s, &z, 1); + + rlogin->prompt = NULL; +} + +static const PlugVtable Rlogin_plugvt = { + .log = rlogin_log, + .closing = rlogin_closing, + .receive = rlogin_receive, + .sent = rlogin_sent, +}; + +/* + * 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 char *rlogin_init(const BackendVtable *vt, Seat *seat, + Backend **backend_handle, LogContext *logctx, + Conf *conf, const char *host, int port, + char **realhost, bool nodelay, bool keepalive) +{ + SockAddr *addr; + const char *err; + Rlogin *rlogin; + char *ruser; + int addressfamily; + char *loghost; + + rlogin = snew(Rlogin); + rlogin->plug.vt = &Rlogin_plugvt; + rlogin->backend.vt = vt; + rlogin->s = NULL; + rlogin->closed_on_socket_error = false; + rlogin->seat = seat; + rlogin->logctx = logctx; + rlogin->term_width = conf_get_int(conf, CONF_width); + rlogin->term_height = conf_get_int(conf, CONF_height); + rlogin->firstbyte = true; + rlogin->cansize = false; + rlogin->prompt = NULL; + rlogin->conf = conf_copy(conf); + *backend_handle = &rlogin->backend; + + addressfamily = conf_get_int(conf, CONF_addressfamily); + /* + * Try to find host. + */ + addr = name_lookup(host, port, realhost, conf, addressfamily, + rlogin->logctx, "rlogin connection"); + if ((err = sk_addr_error(addr)) != NULL) { + sk_addr_free(addr); + return dupstr(err); + } + + if (port < 0) + port = 513; /* default rlogin port */ + + /* + * Open socket. + */ + rlogin->s = new_connection(addr, *realhost, port, true, false, + nodelay, keepalive, &rlogin->plug, conf); + if ((err = sk_socket_error(rlogin->s)) != NULL) + return dupstr(err); + + loghost = conf_get_str(conf, CONF_loghost); + if (*loghost) { + char *colon; + + sfree(*realhost); + *realhost = dupstr(loghost); + + colon = host_strrchr(*realhost, ':'); + if (colon) + *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 ((ruser = get_remote_username(conf)) != NULL) { + /* Next terminal output will come from server */ + seat_set_trust_status(rlogin->seat, false); + rlogin_startup(rlogin, ruser); + sfree(ruser); + } else { + int ret; + + rlogin->prompt = new_prompts(); + rlogin->prompt->to_server = true; + rlogin->prompt->from_server = false; + rlogin->prompt->name = dupstr("Rlogin login name"); + add_prompt(rlogin->prompt, dupstr("rlogin username: "), true); + ret = seat_get_userpass_input(rlogin->seat, rlogin->prompt, NULL); + if (ret >= 0) { + /* Next terminal output will come from server */ + seat_set_trust_status(rlogin->seat, false); + rlogin_startup(rlogin, prompt_get_result_ref( + rlogin->prompt->prompts[0])); + } + } + + return NULL; +} + +static void rlogin_free(Backend *be) +{ + Rlogin *rlogin = container_of(be, Rlogin, backend); + + if (rlogin->prompt) + free_prompts(rlogin->prompt); + if (rlogin->s) + sk_close(rlogin->s); + conf_free(rlogin->conf); + sfree(rlogin); +} + +/* + * Stub routine (we don't have any need to reconfigure this backend). + */ +static void rlogin_reconfig(Backend *be, Conf *conf) +{ +} + +/* + * Called to send data down the rlogin connection. + */ +static size_t rlogin_send(Backend *be, const char *buf, size_t len) +{ + Rlogin *rlogin = container_of(be, Rlogin, backend); + bufchain bc; + + if (rlogin->s == NULL) + return 0; + + bufchain_init(&bc); + bufchain_add(&bc, buf, len); + + if (rlogin->prompt) { + /* + * We're still prompting for a username, and aren't talking + * directly to the network connection yet. + */ + int ret = seat_get_userpass_input(rlogin->seat, rlogin->prompt, &bc); + if (ret >= 0) { + /* Next terminal output will come from server */ + seat_set_trust_status(rlogin->seat, false); + rlogin_startup(rlogin, prompt_get_result_ref( + rlogin->prompt->prompts[0])); + /* that nulls out rlogin->prompt, so then we'll start sending + * data down the wire in the obvious way */ + } + } + + if (!rlogin->prompt) { + while (bufchain_size(&bc) > 0) { + ptrlen data = bufchain_prefix(&bc); + rlogin->bufsize = sk_write(rlogin->s, data.ptr, data.len); + bufchain_consume(&bc, len); + } + } + + bufchain_clear(&bc); + + return rlogin->bufsize; +} + +/* + * Called to query the current socket sendability status. + */ +static size_t rlogin_sendbuffer(Backend *be) +{ + Rlogin *rlogin = container_of(be, Rlogin, backend); + return rlogin->bufsize; +} + +/* + * Called to set the size of the window + */ +static void rlogin_size(Backend *be, int width, int height) +{ + Rlogin *rlogin = container_of(be, Rlogin, backend); + 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(Backend *be, SessionSpecialCode code, int arg) +{ + /* Do nothing! */ + return; +} + +/* + * Return a list of the special codes that make sense in this + * protocol. + */ +static const SessionSpecial *rlogin_get_specials(Backend *be) +{ + return NULL; +} + +static bool rlogin_connected(Backend *be) +{ + Rlogin *rlogin = container_of(be, Rlogin, backend); + return rlogin->s != NULL; +} + +static bool rlogin_sendok(Backend *be) +{ + /* Rlogin *rlogin = container_of(be, Rlogin, backend); */ + return true; +} + +static void rlogin_unthrottle(Backend *be, size_t backlog) +{ + Rlogin *rlogin = container_of(be, Rlogin, backend); + sk_set_frozen(rlogin->s, backlog > RLOGIN_MAX_BACKLOG); +} + +static bool rlogin_ldisc(Backend *be, int option) +{ + /* Rlogin *rlogin = container_of(be, Rlogin, backend); */ + return false; +} + +static void rlogin_provide_ldisc(Backend *be, Ldisc *ldisc) +{ + /* This is a stub. */ +} + +static int rlogin_exitcode(Backend *be) +{ + Rlogin *rlogin = container_of(be, Rlogin, backend); + if (rlogin->s != NULL) + return -1; /* still connected */ + else if (rlogin->closed_on_socket_error) + return INT_MAX; /* a socket error counts as an unclean exit */ + else + /* If we ever implement RSH, we'll probably need to do this properly */ + return 0; +} + +/* + * cfg_info for rlogin does nothing at all. + */ +static int rlogin_cfg_info(Backend *be) +{ + return 0; +} + +const BackendVtable rlogin_backend = { + .init = rlogin_init, + .free = rlogin_free, + .reconfig = rlogin_reconfig, + .send = rlogin_send, + .sendbuffer = rlogin_sendbuffer, + .size = rlogin_size, + .special = rlogin_special, + .get_specials = rlogin_get_specials, + .connected = rlogin_connected, + .exitcode = rlogin_exitcode, + .sendok = rlogin_sendok, + .ldisc_option_state = rlogin_ldisc, + .provide_ldisc = rlogin_provide_ldisc, + .unthrottle = rlogin_unthrottle, + .cfg_info = rlogin_cfg_info, + .id = "rlogin", + .displayname = "Rlogin", + .protocol = PROT_RLOGIN, + .default_port = 513, +}; diff --git a/otherbackends/supdup.c b/otherbackends/supdup.c new file mode 100644 index 00000000..f210ebe3 --- /dev/null +++ b/otherbackends/supdup.c @@ -0,0 +1,923 @@ +/* +* Supdup backend +*/ + +#include +#include +#include + +#include "putty.h" + +/* + * TTYOPT FUNCTION BITS (36-bit bitmasks) +*/ +#define TOALT 0200000000000LL // Characters 0175 and 0176 are converted to altmode (0033) on input +#define TOCLC 0100000000000LL // (user option bit) Convert lower-case input to upper-case +#define TOERS 0040000000000LL // Selective erase is supported +#define TOMVB 0010000000000LL // Backspacing is supported +#define TOSAI 0004000000000LL // Stanford/ITS extended ASCII graphics character set is supported +#define TOSA1 0002000000000LL // (user option bit) Characters 0001-0037 displayed using Stanford/ITS chars +#define TOOVR 0001000000000LL // Overprinting is supported +#define TOMVU 0000400000000LL // Moving cursor upwards is supported +#define TOMOR 0000200000000LL // (user option bit) System should provide **MORE** processing +#define TOROL 0000100000000LL // (user option bit) Terminal should scroll instead of wrapping +#define TOLWR 0000020000000LL // Lowercase characters are supported +#define TOFCI 0000010000000LL // Terminal can generate CONTROL and META characters +#define TOLID 0000002000000LL // Line insert/delete operations supported +#define TOCID 0000001000000LL // Character insert/delete operations supported +#define TPCBS 0000000000040LL // Terminal is using the "intelligent terminal protocol" (must be on) +#define TPORS 0000000000010LL // Server should process output resets + +// Initialization words (36-bit constants) +#define WORDS 0777773000000 // Negative number of config words to send (6) in high 18 bits +#define TCTYP 0000000000007 // Defines the terminal type (MUST be 7) +#define TTYROL 0000000000001 // Scroll amount for terminal (1 line at a time) + + +// %TD opcodes +// +#define TDMOV 0200 // Cursor positioning +#define TDMV1 0201 // Internal cursor positioning +#define TDEOF 0202 // Erase to end of screen +#define TDEOL 0203 // Erase to end of line +#define TDDLF 0204 // Clear the character the cursor is on +#define TDCRL 0207 // Carriage return +#define TDNOP 0210 // No-op; should be ignored. +#define TDBS 0211 // Backspace (not in official SUPDUP spec) +#define TDLF 0212 // Linefeed (not in official SUPDUP spec) +#define TDCR 0213 // Carriage Return (ditto) +#define TDORS 0214 // Output reset +#define TDQOT 0215 // Quotes the following character +#define TDFS 0216 // Non-destructive forward space +#define TDMV0 0217 // General cursor positioning code +#define TDCLR 0220 // Erase the screen, home cursor +#define TDBEL 0221 // Generate an audio tone, bell, whatever +#define TDILP 0223 // Insert blank lines at the cursor +#define TDDLP 0224 // Delete lines at the cursor +#define TDICP 0225 // Insert blanks at cursor +#define TDDCP 0226 // Delete characters at cursor +#define TDBOW 0227 // Display black chars on white screen +#define TDRST 0230 // Reset %TDBOW + +/* Maximum number of octets following a %TD code. */ +#define TD_ARGS_MAX 4 + +typedef struct supdup_tag Supdup; +struct supdup_tag +{ + Socket *s; + bool closed_on_socket_error; + + Seat *seat; + LogContext *logctx; + int term_width, term_height; + + long long ttyopt; + long tcmxv; + long tcmxh; + + bool sent_location; + + Conf *conf; + + int bufsize; + + enum { + CONNECTING, // waiting for %TDNOP from server after sending connection params + CONNECTED // %TDNOP received, connected. + } state; + + enum { + TD_TOPLEVEL, + TD_ARGS, + TD_ARGSDONE + } tdstate; + + int td_code; + int td_argcount; + char td_args[TD_ARGS_MAX]; + int td_argindex; + + void (*print) (strbuf *outbuf, int c); + + Pinger *pinger; + + Plug plug; + Backend backend; +}; + +#define SUPDUP_MAX_BACKLOG 4096 + +static void c_write(Supdup *supdup, unsigned char *buf, int len) +{ + size_t backlog = seat_stdout(supdup->seat, buf, len); + sk_set_frozen(supdup->s, backlog > SUPDUP_MAX_BACKLOG); +} + +static void supdup_send_location(Supdup *supdup) +{ + char locHeader[] = { 0300, 0302 }; + char* locString = conf_get_str(supdup->conf, CONF_supdup_location); + + sk_write(supdup->s, locHeader, sizeof(locHeader)); + sk_write(supdup->s, locString, strlen(locString) + 1); // include NULL terminator +} + +static void print_ascii(strbuf *outbuf, int c) +{ + /* In ASCII mode, ignore control characters. The server shouldn't + send them. */ + if (c >= 040 && c < 0177) + put_byte (outbuf, c); +} + +static void print_its(strbuf *outbuf, int c) +{ + /* The ITS character set is documented in RFC 734. */ + static const char *map[] = { + "\xc2\xb7", "\342\206\223", "\316\261", "\316\262", + "\342\210\247", "\302\254", "\316\265", "\317\200", + "\316\273", "\xce\xb3", "\xce\xb4", "\xe2\x86\x91", + "\xc2\xb1", "\xe2\x8a\x95", "\342\210\236", "\342\210\202", + "\342\212\202", "\342\212\203", "\342\210\251", "\342\210\252", + "\342\210\200", "\342\210\203", "\xe2\x8a\x97", "\342\206\224", + "\xe2\x86\x90", "\342\206\222", "\xe2\x89\xa0", "\xe2\x97\x8a", + "\342\211\244", "\342\211\245", "\342\211\241", "\342\210\250", + " ", "!", "\"", "#", "$", "%", "&", "'", + "(", ")", "*", "+", ",", "-", ".", "/", + "0", "1", "2", "3", "4", "5", "6", "7", + "8", "9", ":", ";", "<", "=", ">", "?", + "@", "A", "B", "C", "D", "E", "F", "G", + "H", "I", "J", "K", "L", "M", "N", "O", + "P", "Q", "R", "S", "T", "U", "V", "W", + "X", "Y", "Z", "[", "\\", "]", "^", "_", + "`", "a", "b", "c", "d", "e", "f", "g", + "h", "i", "j", "k", "l", "m", "n", "o", + "p", "q", "r", "s", "t", "u", "v", "w", + "x", "y", "z", "{", "|", "}", "~", "\xe2\x88\xab" + }; + + put_data (outbuf, map[c], strlen(map[c])); +} + +static void print_waits(strbuf *outbuf, int c) +{ + /* The WAITS character set used at the Stanford AI Lab is documented + here: https://www.saildart.org/allow/sail-charset-utf8.html */ + static const char *map[] = { + "", "\342\206\223", "\316\261", "\316\262", + "\342\210\247", "\302\254", "\316\265", "\317\200", + "\316\273", "", "", "", + "", "", "\342\210\236", "\342\210\202", + "\342\212\202", "\342\212\203", "\342\210\251", "\342\210\252", + "\342\210\200", "\342\210\203", "\xe2\x8a\x97", "\342\206\224", + "_", "\342\206\222", "~", "\xe2\x89\xa0", + "\342\211\244", "\342\211\245", "\342\211\241", "\342\210\250", + " ", "!", "\"", "#", "$", "%", "&", "'", + "(", ")", "*", "+", ",", "-", ".", "/", + "0", "1", "2", "3", "4", "5", "6", "7", + "8", "9", ":", ";", "<", "=", ">", "?", + "@", "A", "B", "C", "D", "E", "F", "G", + "H", "I", "J", "K", "L", "M", "N", "O", + "P", "Q", "R", "S", "T", "U", "V", "W", + "X", "Y", "Z", "[", "\\", "]", "\xe2\x86\x91", "\xe2\x86\x90", + "`", "a", "b", "c", "d", "e", "f", "g", + "h", "i", "j", "k", "l", "m", "n", "o", + "p", "q", "r", "s", "t", "u", "v", "w", + "x", "y", "z", "{", "|", "\xe2\x97\x8a", "}", "" + }; + + put_data (outbuf, map[c], strlen(map[c])); +} + +static void do_toplevel(Supdup *supdup, strbuf *outbuf, int c) +{ + // Toplevel: Waiting for a %TD code or a printable character + if (c >= 0200) { + // Handle SUPDUP %TD codes (codes greater than or equal to 200) + supdup->td_argindex = 0; + supdup->td_code = c; + switch (c) { + case TDMOV: + // %TD codes using 4 arguments + supdup->td_argcount = 4; + supdup->tdstate = TD_ARGS; + break; + + case TDMV0: + case TDMV1: + // %TD codes using 2 arguments + supdup->td_argcount = 2; + supdup->tdstate = TD_ARGS; + break; + + case TDQOT: + case TDILP: + case TDDLP: + case TDICP: + case TDDCP: + // %TD codes using 1 argument + supdup->td_argcount = 1; + supdup->tdstate = TD_ARGS; + break; + + case TDEOF: + case TDEOL: + case TDDLF: + case TDCRL: + case TDNOP: + case TDORS: + case TDFS: + case TDCLR: + case TDBEL: + case TDBOW: + case TDRST: + case TDBS: + case TDCR: + case TDLF: + // %TD codes using 0 arguments + supdup->td_argcount = 0; + supdup->tdstate = TD_ARGSDONE; + break; + + default: + // Unhandled, ignore + break; + } + } else { + supdup->print(outbuf, c); + } +} + +static void do_args(Supdup *supdup, strbuf *outbuf, int c) +{ + // Collect up args for %TD code + if (supdup->td_argindex < TD_ARGS_MAX) { + supdup->td_args[supdup->td_argindex] = c; + supdup->td_argindex++; + + if (supdup->td_argcount == supdup->td_argindex) { + // No more args, %TD code is ready to go. + supdup->tdstate = TD_ARGSDONE; + } + } else { + // Should never hit this state, if we do we will just + // return to TOPLEVEL. + supdup->tdstate = TD_TOPLEVEL; + } +} + +static void do_argsdone(Supdup *supdup, strbuf *outbuf, int c) +{ + char buf[4]; + int x, y; + + // Arguments for %TD code have been collected; dispatch based + // on the %TD code we're handling. + switch (supdup->td_code) { + case TDMOV: + /* + General cursor position code. Followed by four bytes; + the first two are the "old" vertical and horizontal + positions and may be ignored. The next two are the new + vertical and horizontal positions. The cursor should be + moved to this position. + */ + + // We only care about the new position. + strbuf_catf(outbuf, "\033[%d;%dH", supdup->td_args[2]+1, supdup->td_args[3]+1); + break; + + case TDMV0: + case TDMV1: + /* + General cursor position code. Followed by two bytes; + the new vertical and horizontal positions. + */ + strbuf_catf(outbuf, "\033[%d;%dH", supdup->td_args[0]+1, supdup->td_args[1]+1); + break; + + case TDEOF: + /* + Erase to end of screen. This is an optional function + since many terminals do not support this. If the + terminal does not support this function, it should be + treated the same as %TDEOL. + + %TDEOF does an erase to end of line, then erases all + lines lower on the screen than the cursor. The cursor + does not move. + */ + strbuf_catf(outbuf, "\033[J"); + break; + + case TDEOL: + /* + Erase to end of line. This erases the character + position the cursor is at and all positions to the right + on the same line. The cursor does not move. + */ + strbuf_catf(outbuf, "\033[K"); + break; + + case TDDLF: + /* + Clear the character position the cursor is on. The + cursor does not move. + */ + strbuf_catf(outbuf, "\033[X"); + break; + + case TDCRL: + /* + If the cursor is not on the bottom line of the screen, + move cursor to the beginning of the next line and clear + that line. If the cursor is at the bottom line, scroll + up. + */ + strbuf_catf(outbuf, "\015\012"); + break; + + case TDNOP: + /* + No-op; should be ignored. + */ + break; + + case TDORS: + /* + Output reset. This code serves as a data mark for + aborting output much as IAC DM does in the ordinary + TELNET protocol. + */ + outbuf->len = 0; + if (!seat_get_cursor_position(supdup->seat, &x, &y)) + x = y = 0; + buf[0] = 034; + buf[1] = 020; + buf[2] = y; + buf[3] = x; + sk_write(supdup->s, buf, 4); + break; + + case TDQOT: + /* + Quotes the following character. This is used when + sending 8-bit codes which are not %TD codes, for + instance when loading programs into an intelligent + terminal. The following character should be passed + through intact to the terminal. + */ + + put_byte(outbuf, supdup->td_args[0]); + break; + + case TDFS: + /* + Non-destructive forward space. The cursor moves right + one position; this code will not be sent at the end of a + line. + */ + + strbuf_catf(outbuf, "\033[C"); + break; + + case TDCLR: + /* + Erase the screen. Home the cursor to the top left hand + corner of the screen. + */ + strbuf_catf(outbuf, "\033[2J\033[H"); + break; + + case TDBEL: + /* + Generate an audio tone, bell, whatever. + */ + + strbuf_catf(outbuf, "\007"); + break; + + case TDILP: + /* + Insert blank lines at the cursor; followed by a byte + containing a count of the number of blank lines to + insert. The cursor is unmoved. The line the cursor is + on and all lines below it move down; lines moved off the + bottom of the screen are lost. + */ + strbuf_catf(outbuf, "\033[%dL", supdup->td_args[0]); + break; + + case TDDLP: + /* + Delete lines at the cursor; followed by a count. The + cursor is unmoved. The first line deleted is the one + the cursor is on. Lines below those deleted move up. + Newly- created lines at the bottom of the screen are + blank. + */ + strbuf_catf(outbuf, "\033[%dM", supdup->td_args[0]); + break; + + case TDICP: + /* + Insert blank character positions at the cursor; followed + by a count. The cursor is unmoved. The character the + cursor is on and all characters to the right on the + current line move to the right; characters moved off the + end of the line are lost. + */ + strbuf_catf(outbuf, "\033[%d@", supdup->td_args[0]); + break; + + case TDDCP: + /* + Delete characters at the cursor; followed by a count. + The cursor is unmoved. The first character deleted is + the one the cursor is on. Newly-created characters at + the end of the line are blank. + */ + strbuf_catf(outbuf, "\033[%dP", supdup->td_args[0]); + break; + + case TDBOW: + case TDRST: + /* + Display black characters on white screen. + HIGHLY OPTIONAL. + */ + + // Since this is HIGHLY OPTIONAL, I'm not going + // to implement it yet. + break; + + /* + * Non-standard (whatever "standard" means here) SUPDUP + * commands. These are used (at the very least) by + * Genera's SUPDUP implementation. Cannot find any + * official documentation, behavior is based on UNIX + * SUPDUP implementation from MIT. + */ + case TDBS: + /* + * Backspace -- move cursor back one character (does not + * appear to wrap...) + */ + put_byte(outbuf, '\010'); + break; + + case TDLF: + /* + * Linefeed -- move cursor down one line (again, no wrapping) + */ + put_byte(outbuf, '\012'); + break; + + case TDCR: + /* + * Carriage return -- move cursor to start of current line. + */ + put_byte(outbuf, '\015'); + break; + } + + // Return to top level to pick up the next %TD code or + // printable character. + supdup->tdstate = TD_TOPLEVEL; +} + +static void term_out_supdup(Supdup *supdup, strbuf *outbuf, int c) +{ + if (supdup->tdstate == TD_TOPLEVEL) { + do_toplevel (supdup, outbuf, c); + } else if (supdup->tdstate == TD_ARGS) { + do_args (supdup, outbuf, c); + } + + // If all arguments for a %TD code are ready, we will execute the code now. + if (supdup->tdstate == TD_ARGSDONE) { + do_argsdone (supdup, outbuf, c); + } +} + +static void do_supdup_read(Supdup *supdup, const char *buf, size_t len) +{ + strbuf *outbuf = strbuf_new(); + + while (len--) { + int c = (unsigned char)*buf++; + switch (supdup->state) { + case CONNECTING: + // "Following the transmission of the terminal options by + // the user, the server should respond with an ASCII + // greeting message, terminated with a %TDNOP code..." + if (TDNOP == c) { + // Greeting done, switch to the CONNECTED state. + supdup->state = CONNECTED; + supdup->tdstate = TD_TOPLEVEL; + } else { + // Forward the greeting message (which is straight + // ASCII, no controls) on so it gets displayed TODO: + // filter out only printable chars? + put_byte(outbuf, c); + } + break; + + case CONNECTED: + // "All transmissions from the server after the %TDNOP + // [see above] are either printing characters or virtual + // terminal display codes." Forward these on to the + // frontend which will decide what to do with them. + term_out_supdup(supdup, outbuf, c); + /* + * Hack to make Symbolics Genera SUPDUP happy: Wait until + * after we're connected (finished the initial handshake + * and have gotten additional data) before sending the + * location string. For some reason doing so earlier + * causes the Symbolics SUPDUP to end up in an odd state. + */ + if (!supdup->sent_location) { + supdup_send_location(supdup); + supdup->sent_location = true; + } + break; + } + + if (outbuf->len >= 4096) { + c_write(supdup, outbuf->u, outbuf->len); + outbuf->len = 0; + } + } + + if (outbuf->len) + c_write(supdup, outbuf->u, outbuf->len); + strbuf_free(outbuf); +} + +static void supdup_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, + const char *error_msg, int error_code) +{ + Supdup *supdup = container_of(plug, Supdup, plug); + backend_socket_log(supdup->seat, supdup->logctx, type, addr, port, + error_msg, error_code, + supdup->conf, supdup->state != CONNECTING); +} + +static void supdup_closing(Plug *plug, const char *error_msg, int error_code, + bool calling_back) +{ + Supdup *supdup = container_of(plug, Supdup, plug); + + /* + * We don't implement independent EOF in each direction for Telnet + * connections; as soon as we get word that the remote side has + * sent us EOF, we wind up the whole connection. + */ + + if (supdup->s) { + sk_close(supdup->s); + supdup->s = NULL; + if (error_msg) + supdup->closed_on_socket_error = true; + seat_notify_remote_exit(supdup->seat); + } + if (error_msg) { + logevent(supdup->logctx, error_msg); + seat_connection_fatal(supdup->seat, "%s", error_msg); + } + /* Otherwise, the remote side closed the connection normally. */ +} + +static void supdup_receive(Plug *plug, int urgent, const char *data, size_t len) +{ + Supdup *supdup = container_of(plug, Supdup, plug); + do_supdup_read(supdup, data, len); +} + +static void supdup_sent(Plug *plug, size_t bufsize) +{ + Supdup *supdup = container_of(plug, Supdup, plug); + supdup->bufsize = bufsize; +} + +static void supdup_send_36bits(Supdup *supdup, unsigned long long thirtysix) +{ + // + // From RFC734: + // "Each word is sent through the 8-bit connection as six + // 6-bit bytes, most-significant first." + // + // Split the 36-bit word into 6 6-bit "bytes", packed into + // 8-bit bytes and send, most-significant byte first. + // + for (int i = 5; i >= 0; i--) { + char sixBits = (thirtysix >> (i * 6)) & 077; + sk_write(supdup->s, &sixBits, 1); + } +} + +static void supdup_send_config(Supdup *supdup) +{ + supdup_send_36bits(supdup, WORDS); // negative length + supdup_send_36bits(supdup, TCTYP); // terminal type + supdup_send_36bits(supdup, supdup->ttyopt); // options + supdup_send_36bits(supdup, supdup->tcmxv); // height + supdup_send_36bits(supdup, supdup->tcmxh); // width + supdup_send_36bits(supdup, TTYROL); // scroll amount +} + +/* +* Called to set up the Supdup 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 char *supdup_init(const BackendVtable *x, Seat *seat, + Backend **backend_handle, + LogContext *logctx, Conf *conf, + const char *host, int port, char **realhost, + bool nodelay, bool keepalive) +{ + static const PlugVtable fn_table = { + .log = supdup_log, + .closing = supdup_closing, + .receive = supdup_receive, + .sent = supdup_sent, + }; + SockAddr *addr; + const char *err; + Supdup *supdup; + char *loghost; + int addressfamily; + const char *utf8 = "\033%G"; + + supdup = snew(struct supdup_tag); + supdup->plug.vt = &fn_table; + supdup->backend.vt = &supdup_backend; + supdup->logctx = logctx; + supdup->conf = conf_copy(conf); + supdup->s = NULL; + supdup->closed_on_socket_error = false; + supdup->seat = seat; + supdup->term_width = conf_get_int(supdup->conf, CONF_width); + supdup->term_height = conf_get_int(supdup->conf, CONF_height); + supdup->pinger = NULL; + supdup->sent_location = false; + *backend_handle = &supdup->backend; + + switch (conf_get_int(supdup->conf, CONF_supdup_ascii_set)) { + case SUPDUP_CHARSET_ASCII: + supdup->print = print_ascii; + break; + case SUPDUP_CHARSET_ITS: + supdup->print = print_its; + break; + case SUPDUP_CHARSET_WAITS: + supdup->print = print_waits; + break; + } + + /* + * Try to find host. + */ + { + char *buf; + addressfamily = conf_get_int(supdup->conf, CONF_addressfamily); + buf = dupprintf("Looking up host \"%s\"%s", host, + (addressfamily == ADDRTYPE_IPV4 ? " (IPv4)" : + (addressfamily == ADDRTYPE_IPV6 ? " (IPv6)" : + ""))); + logevent(supdup->logctx, buf); + sfree(buf); + } + addr = name_lookup(host, port, realhost, supdup->conf, addressfamily, NULL, ""); + if ((err = sk_addr_error(addr)) != NULL) { + sk_addr_free(addr); + return dupstr(err); + } + + if (port < 0) + port = 0137; /* default supdup port */ + + /* + * Open socket. + */ + supdup->s = new_connection(addr, *realhost, port, false, true, + nodelay, keepalive, &supdup->plug, supdup->conf); + if ((err = sk_socket_error(supdup->s)) != NULL) + return dupstr(err); + + supdup->pinger = pinger_new(supdup->conf, &supdup->backend); + + /* + * We can send special commands from the start. + */ + seat_update_specials_menu(supdup->seat); + + /* + * loghost overrides realhost, if specified. + */ + loghost = conf_get_str(supdup->conf, CONF_loghost); + if (*loghost) { + char *colon; + + sfree(*realhost); + *realhost = dupstr(loghost); + + colon = host_strrchr(*realhost, ':'); + if (colon) + *colon++ = '\0'; + } + + /* + * Set up TTYOPTS based on config + */ + int ascii_set = conf_get_int(supdup->conf, CONF_supdup_ascii_set); + int more_processing = conf_get_bool(supdup->conf, CONF_supdup_more); + int scrolling = conf_get_bool(supdup->conf, CONF_supdup_scroll); + supdup->ttyopt = + TOERS | + TOMVB | + (ascii_set == SUPDUP_CHARSET_ASCII ? 0 : TOSAI | TOSA1) | + TOMVU | + TOLWR | + TOLID | + TOCID | + TPCBS | + (scrolling ? TOROL : 0) | + (more_processing ? TOMOR : 0) | + TPORS; + + supdup->tcmxh = supdup->term_width - 1; // -1 "..one column is used to indicate line continuation." + supdup->tcmxv = supdup->term_height; + + /* + * Send our configuration words to the server + */ + supdup_send_config(supdup); + + /* + * We next expect a connection message followed by %TDNOP from the server + */ + supdup->state = CONNECTING; + seat_set_trust_status(supdup->seat, false); + + /* Make sure the terminal is in UTF-8 mode. */ + c_write(supdup, (unsigned char *)utf8, strlen(utf8)); + + return NULL; +} + + +static void supdup_free(Backend *be) +{ + Supdup *supdup = container_of(be, Supdup, backend); + + if (supdup->s) + sk_close(supdup->s); + if (supdup->pinger) + pinger_free(supdup->pinger); + conf_free(supdup->conf); + sfree(supdup); +} + +/* +* Reconfigure the Supdup backend. +*/ +static void supdup_reconfig(Backend *be, Conf *conf) +{ + /* Nothing to do; SUPDUP cannot be reconfigured while running. */ +} + +/* +* Called to send data down the Supdup connection. +*/ +static size_t supdup_send(Backend *be, const char *buf, size_t len) +{ + Supdup *supdup = container_of(be, Supdup, backend); + char c; + int i; + + if (supdup->s == NULL) + return 0; + + for (i = 0; i < len; i++) { + if (buf[i] == 034) + supdup->bufsize = sk_write(supdup->s, "\034\034", 2); + else { + c = buf[i] & 0177; + supdup->bufsize = sk_write(supdup->s, &c, 1); + } + } + return supdup->bufsize; +} + +/* +* Called to query the current socket sendability status. +*/ +static size_t supdup_sendbuffer(Backend *be) +{ + Supdup *supdup = container_of(be, Supdup, backend); + return supdup->bufsize; +} + +/* +* Called to set the size of the window from Supdup's POV. +*/ +static void supdup_size(Backend *be, int width, int height) +{ + Supdup *supdup = container_of(be, Supdup, backend); + + supdup->term_width = width; + supdup->term_height = height; + + // + // SUPDUP does not support resizing the terminal after connection + // establishment. + // +} + +/* +* Send Telnet special codes. +*/ +static void supdup_special(Backend *be, SessionSpecialCode code, int arg) +{ +} + +static const SessionSpecial *supdup_get_specials(Backend *be) +{ + return NULL; +} + +static bool supdup_connected(Backend *be) +{ + Supdup *supdup = container_of(be, Supdup, backend); + return supdup->s != NULL; +} + +static bool supdup_sendok(Backend *be) +{ + return 1; +} + +static void supdup_unthrottle(Backend *be, size_t backlog) +{ + Supdup *supdup = container_of(be, Supdup, backend); + sk_set_frozen(supdup->s, backlog > SUPDUP_MAX_BACKLOG); +} + +static bool supdup_ldisc(Backend *be, int option) +{ + /* No support for echoing or local editing. */ + return false; +} + +static void supdup_provide_ldisc(Backend *be, Ldisc *ldisc) +{ +} + +static int supdup_exitcode(Backend *be) +{ + Supdup *supdup = container_of(be, Supdup, backend); + if (supdup->s != NULL) + return -1; /* still connected */ + else if (supdup->closed_on_socket_error) + return INT_MAX; /* a socket error counts as an unclean exit */ + else + /* Supdup doesn't transmit exit codes back to the client */ + return 0; +} + +/* +* cfg_info for Dupdup does nothing at all. +*/ +static int supdup_cfg_info(Backend *be) +{ + return 0; +} + +const BackendVtable supdup_backend = { + .init = supdup_init, + .free = supdup_free, + .reconfig = supdup_reconfig, + .send = supdup_send, + .sendbuffer = supdup_sendbuffer, + .size = supdup_size, + .special = supdup_special, + .get_specials = supdup_get_specials, + .connected = supdup_connected, + .exitcode = supdup_exitcode, + .sendok = supdup_sendok, + .ldisc_option_state = supdup_ldisc, + .provide_ldisc = supdup_provide_ldisc, + .unthrottle = supdup_unthrottle, + .cfg_info = supdup_cfg_info, + .id = "supdup", + .displayname = "SUPDUP", + .protocol = PROT_SUPDUP, + .default_port = 0137, + .flags = BACKEND_RESIZE_FORBIDDEN | BACKEND_NEEDS_TERMINAL, +}; diff --git a/otherbackends/telnet.c b/otherbackends/telnet.c new file mode 100644 index 00000000..3a60e646 --- /dev/null +++ b/otherbackends/telnet.c @@ -0,0 +1,1070 @@ +/* + * Telnet backend. + */ + +#include +#include +#include + +#include "putty.h" + +#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 const char *telopt(int opt) +{ +#define telnet_str(x,y) case TELOPT_##x: return #x; + switch (opt) { + TELOPTS(telnet_str) + default: + return ""; + } +#undef telnet_str +} + +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 Telnet; +struct Telnet { + Socket *s; + bool closed_on_socket_error; + + Seat *seat; + LogContext *logctx; + Ldisc *ldisc; + int term_width, term_height; + + int opt_states[NUM_OPTS]; + + bool echoing, editing; + bool activated; + size_t bufsize; + bool in_synch; + int sb_opt; + strbuf *sb_buf; + bool session_started; + + enum { + TOP_LEVEL, SEENIAC, SEENWILL, SEENWONT, SEENDO, SEENDONT, + SEENSB, SUBNEGOT, SUBNEG_IAC, SEENCR + } state; + + Conf *conf; + + Pinger *pinger; + + Plug plug; + Backend backend; +}; + +#define TELNET_MAX_BACKLOG 4096 + +#define SB_DELTA 1024 + +static void c_write(Telnet *telnet, const void *buf, size_t len) +{ + size_t backlog = seat_stdout(telnet->seat, buf, len); + sk_set_frozen(telnet->s, backlog > TELNET_MAX_BACKLOG); +} + +static void log_option(Telnet *telnet, const char *sender, int cmd, int option) +{ + /* + * The strange-looking "" below is there to avoid a + * trigraph - a double question mark followed by > maps to a + * closing brace character! + */ + logeventf(telnet->logctx, "%s:\t%s %s", sender, + (cmd == WILL ? "WILL" : cmd == WONT ? "WONT" : + cmd == DO ? "DO" : cmd == DONT ? "DONT" : ""), + telopt(option)); +} + +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, 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, bool 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_echoedit_update(telnet->ldisc); + + /* 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) + backend_size(&telnet->backend, + 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, true); +} + +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, false); +} + +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, false); + 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, *p, *q; + int var, value, n, bsize; + char *e, *eval, *ekey, *user; + + switch (telnet->sb_opt) { + case TELOPT_TSPEED: + if (telnet->sb_buf->len == 1 && telnet->sb_buf->u[0] == TELQUAL_SEND) { + char *termspeed = conf_get_str(telnet->conf, CONF_termspeed); + b = snewn(20 + strlen(termspeed), unsigned char); + b[0] = IAC; + b[1] = SB; + b[2] = TELOPT_TSPEED; + b[3] = TELQUAL_IS; + strcpy((char *)(b + 4), termspeed); + n = 4 + strlen(termspeed); + b[n] = IAC; + b[n + 1] = SE; + telnet->bufsize = sk_write(telnet->s, b, n + 2); + logevent(telnet->logctx, "server:\tSB TSPEED SEND"); + logeventf(telnet->logctx, "client:\tSB TSPEED IS %s", termspeed); + sfree(b); + } else + logevent(telnet->logctx, "server:\tSB TSPEED "); + break; + case TELOPT_TTYPE: + if (telnet->sb_buf->len == 1 && telnet->sb_buf->u[0] == TELQUAL_SEND) { + char *termtype = conf_get_str(telnet->conf, CONF_termtype); + b = snewn(20 + strlen(termtype), unsigned char); + b[0] = IAC; + b[1] = SB; + b[2] = TELOPT_TTYPE; + b[3] = TELQUAL_IS; + for (n = 0; termtype[n]; n++) + b[n + 4] = (termtype[n] >= 'a' && termtype[n] <= 'z' ? + termtype[n] + 'A' - 'a' : + termtype[n]); + b[n + 4] = IAC; + b[n + 5] = SE; + telnet->bufsize = sk_write(telnet->s, b, n + 6); + b[n + 4] = 0; + logevent(telnet->logctx, "server:\tSB TTYPE SEND"); + logeventf(telnet->logctx, "client:\tSB TTYPE IS %s", b + 4); + sfree(b); + } else + logevent(telnet->logctx, "server:\tSB TTYPE \r\n"); + break; + case TELOPT_OLD_ENVIRON: + case TELOPT_NEW_ENVIRON: + p = telnet->sb_buf->u; + q = p + telnet->sb_buf->len; + if (p < q && *p == TELQUAL_SEND) { + p++; + logeventf(telnet->logctx, "server:\tSB %s SEND", + telopt(telnet->sb_opt)); + if (telnet->sb_opt == TELOPT_OLD_ENVIRON) { + if (conf_get_bool(telnet->conf, CONF_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; + } + bsize = 20; + for (eval = conf_get_str_strs(telnet->conf, CONF_environmt, + NULL, &ekey); + eval != NULL; + eval = conf_get_str_strs(telnet->conf, CONF_environmt, + ekey, &ekey)) + bsize += strlen(ekey) + strlen(eval) + 2; + user = get_remote_username(telnet->conf); + if (user) + bsize += 6 + strlen(user); + + b = snewn(bsize, unsigned char); + b[0] = IAC; + b[1] = SB; + b[2] = telnet->sb_opt; + b[3] = TELQUAL_IS; + n = 4; + for (eval = conf_get_str_strs(telnet->conf, CONF_environmt, + NULL, &ekey); + eval != NULL; + eval = conf_get_str_strs(telnet->conf, CONF_environmt, + ekey, &ekey)) { + b[n++] = var; + for (e = ekey; *e; e++) + b[n++] = *e; + b[n++] = value; + for (e = eval; *e; e++) + b[n++] = *e; + } + if (user) { + b[n++] = var; + b[n++] = 'U'; + b[n++] = 'S'; + b[n++] = 'E'; + b[n++] = 'R'; + b[n++] = value; + for (e = user; *e; e++) + b[n++] = *e; + } + b[n++] = IAC; + b[n++] = SE; + telnet->bufsize = sk_write(telnet->s, b, n); + if (n == 6) { + logeventf(telnet->logctx, "client:\tSB %s IS ", + telopt(telnet->sb_opt)); + } else { + logeventf(telnet->logctx, "client:\tSB %s IS:", + telopt(telnet->sb_opt)); + for (eval = conf_get_str_strs(telnet->conf, CONF_environmt, + NULL, &ekey); + eval != NULL; + eval = conf_get_str_strs(telnet->conf, CONF_environmt, + ekey, &ekey)) { + logeventf(telnet->logctx, "\t%s=%s", ekey, eval); + } + if (user) + logeventf(telnet->logctx, "\tUSER=%s", user); + } + sfree(b); + sfree(user); + } + break; + } +} + +static void do_telnet_read(Telnet *telnet, const char *buf, size_t len) +{ + strbuf *outbuf = strbuf_new_nm(); + + 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) + put_byte(outbuf, 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 = false; +#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 = false; + telnet->state = TOP_LEVEL; + } else { + /* ignore everything else; print it if it's IAC */ + if (c == IAC) { + put_byte(outbuf, 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; + strbuf_clear(telnet->sb_buf); + telnet->state = SUBNEGOT; + break; + case SUBNEGOT: + if (c == IAC) + telnet->state = SUBNEG_IAC; + else { + subneg_addchar: + put_byte(telnet->sb_buf, 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 (outbuf->len >= 4096) { + c_write(telnet, outbuf->u, outbuf->len); + strbuf_clear(outbuf); + } + } + + if (outbuf->len) + c_write(telnet, outbuf->u, outbuf->len); + strbuf_free(outbuf); +} + +static void telnet_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, + const char *error_msg, int error_code) +{ + Telnet *telnet = container_of(plug, Telnet, plug); + backend_socket_log(telnet->seat, telnet->logctx, type, addr, port, + error_msg, error_code, telnet->conf, + telnet->session_started); +} + +static void telnet_closing(Plug *plug, const char *error_msg, int error_code, + bool calling_back) +{ + Telnet *telnet = container_of(plug, Telnet, plug); + + /* + * We don't implement independent EOF in each direction for Telnet + * connections; as soon as we get word that the remote side has + * sent us EOF, we wind up the whole connection. + */ + + if (telnet->s) { + sk_close(telnet->s); + telnet->s = NULL; + if (error_msg) + telnet->closed_on_socket_error = true; + seat_notify_remote_exit(telnet->seat); + } + if (error_msg) { + logevent(telnet->logctx, error_msg); + seat_connection_fatal(telnet->seat, "%s", error_msg); + } + /* Otherwise, the remote side closed the connection normally. */ +} + +static void telnet_receive( + Plug *plug, int urgent, const char *data, size_t len) +{ + Telnet *telnet = container_of(plug, Telnet, plug); + if (urgent) + telnet->in_synch = true; + telnet->session_started = true; + do_telnet_read(telnet, data, len); +} + +static void telnet_sent(Plug *plug, size_t bufsize) +{ + Telnet *telnet = container_of(plug, Telnet, plug); + telnet->bufsize = bufsize; +} + +static const PlugVtable Telnet_plugvt = { + .log = telnet_log, + .closing = telnet_closing, + .receive = telnet_receive, + .sent = telnet_sent, +}; + +/* + * 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 char *telnet_init(const BackendVtable *vt, Seat *seat, + Backend **backend_handle, LogContext *logctx, + Conf *conf, const char *host, int port, + char **realhost, bool nodelay, bool keepalive) +{ + SockAddr *addr; + const char *err; + Telnet *telnet; + char *loghost; + int addressfamily; + + /* No local authentication phase in this protocol */ + seat_set_trust_status(seat, false); + + telnet = snew(Telnet); + telnet->plug.vt = &Telnet_plugvt; + telnet->backend.vt = vt; + telnet->conf = conf_copy(conf); + telnet->s = NULL; + telnet->closed_on_socket_error = false; + telnet->echoing = true; + telnet->editing = true; + telnet->activated = false; + telnet->sb_buf = strbuf_new(); + telnet->seat = seat; + telnet->logctx = logctx; + telnet->term_width = conf_get_int(telnet->conf, CONF_width); + telnet->term_height = conf_get_int(telnet->conf, CONF_height); + telnet->state = TOP_LEVEL; + telnet->ldisc = NULL; + telnet->pinger = NULL; + telnet->session_started = true; + *backend_handle = &telnet->backend; + + /* + * Try to find host. + */ + addressfamily = conf_get_int(telnet->conf, CONF_addressfamily); + addr = name_lookup(host, port, realhost, telnet->conf, addressfamily, + telnet->logctx, "Telnet connection"); + if ((err = sk_addr_error(addr)) != NULL) { + sk_addr_free(addr); + return dupstr(err); + } + + if (port < 0) + port = 23; /* default telnet port */ + + /* + * Open socket. + */ + telnet->s = new_connection(addr, *realhost, port, false, true, nodelay, + keepalive, &telnet->plug, telnet->conf); + if ((err = sk_socket_error(telnet->s)) != NULL) + return dupstr(err); + + telnet->pinger = pinger_new(telnet->conf, &telnet->backend); + + /* + * Initialise option states. + */ + if (conf_get_bool(telnet->conf, CONF_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. + */ + seat_update_specials_menu(telnet->seat); + + /* + * loghost overrides realhost, if specified. + */ + loghost = conf_get_str(telnet->conf, CONF_loghost); + if (*loghost) { + char *colon; + + sfree(*realhost); + *realhost = dupstr(loghost); + + colon = host_strrchr(*realhost, ':'); + if (colon) + *colon++ = '\0'; + } + + return NULL; +} + +static void telnet_free(Backend *be) +{ + Telnet *telnet = container_of(be, Telnet, backend); + + strbuf_free(telnet->sb_buf); + if (telnet->s) + sk_close(telnet->s); + if (telnet->pinger) + pinger_free(telnet->pinger); + conf_free(telnet->conf); + sfree(telnet); +} +/* + * Reconfigure the Telnet backend. There's no immediate action + * necessary, in this backend: we just save the fresh config for + * any subsequent negotiations. + */ +static void telnet_reconfig(Backend *be, Conf *conf) +{ + Telnet *telnet = container_of(be, Telnet, backend); + pinger_reconfig(telnet->pinger, telnet->conf, conf); + conf_free(telnet->conf); + telnet->conf = conf_copy(conf); +} + +/* + * Called to send data down the Telnet connection. + */ +static size_t telnet_send(Backend *be, const char *buf, size_t len) +{ + Telnet *telnet = container_of(be, Telnet, backend); + 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, q, p - q); + + while (p < end && !iswritable(*p)) { + telnet->bufsize = + sk_write(telnet->s, *p == IAC ? iac : cr, 2); + p++; + } + } + + return telnet->bufsize; +} + +/* + * Called to query the current socket sendability status. + */ +static size_t telnet_sendbuffer(Backend *be) +{ + Telnet *telnet = container_of(be, Telnet, backend); + return telnet->bufsize; +} + +/* + * Called to set the size of the window from Telnet's POV. + */ +static void telnet_size(Backend *be, int width, int height) +{ + Telnet *telnet = container_of(be, Telnet, backend); + unsigned char b[24]; + int n; + + 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, b, n); + logeventf(telnet->logctx, "client:\tSB NAWS %d,%d", + telnet->term_width, telnet->term_height); +} + +/* + * Send Telnet special codes. + */ +static void telnet_special(Backend *be, SessionSpecialCode code, int arg) +{ + Telnet *telnet = container_of(be, Telnet, backend); + unsigned char b[2]; + + if (telnet->s == NULL) + return; + + b[0] = IAC; + switch (code) { + case SS_AYT: + b[1] = AYT; + telnet->bufsize = sk_write(telnet->s, b, 2); + break; + case SS_BRK: + b[1] = BREAK; + telnet->bufsize = sk_write(telnet->s, b, 2); + break; + case SS_EC: + b[1] = EC; + telnet->bufsize = sk_write(telnet->s, b, 2); + break; + case SS_EL: + b[1] = EL; + telnet->bufsize = sk_write(telnet->s, b, 2); + break; + case SS_GA: + b[1] = GA; + telnet->bufsize = sk_write(telnet->s, b, 2); + break; + case SS_NOP: + b[1] = NOP; + telnet->bufsize = sk_write(telnet->s, b, 2); + break; + case SS_ABORT: + b[1] = ABORT; + telnet->bufsize = sk_write(telnet->s, b, 2); + break; + case SS_AO: + b[1] = AO; + telnet->bufsize = sk_write(telnet->s, b, 2); + break; + case SS_IP: + b[1] = IP; + telnet->bufsize = sk_write(telnet->s, b, 2); + break; + case SS_SUSP: + b[1] = SUSP; + telnet->bufsize = sk_write(telnet->s, b, 2); + break; + case SS_EOR: + b[1] = EOR; + telnet->bufsize = sk_write(telnet->s, b, 2); + break; + case SS_EOF: + b[1] = xEOF; + telnet->bufsize = sk_write(telnet->s, b, 2); + break; + case SS_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 SS_SYNCH: + b[1] = DM; + telnet->bufsize = sk_write(telnet->s, b, 1); + telnet->bufsize = sk_write_oob(telnet->s, b + 1, 1); + break; + case SS_PING: + if (telnet->opt_states[o_they_sga.index] == ACTIVE) { + b[1] = NOP; + telnet->bufsize = sk_write(telnet->s, b, 2); + } + break; + default: + break; /* never heard of it */ + } +} + +static const SessionSpecial *telnet_get_specials(Backend *be) +{ + static const SessionSpecial specials[] = { + {"Are You There", SS_AYT}, + {"Break", SS_BRK}, + {"Synch", SS_SYNCH}, + {"Erase Character", SS_EC}, + {"Erase Line", SS_EL}, + {"Go Ahead", SS_GA}, + {"No Operation", SS_NOP}, + {NULL, SS_SEP}, + {"Abort Process", SS_ABORT}, + {"Abort Output", SS_AO}, + {"Interrupt Process", SS_IP}, + {"Suspend Process", SS_SUSP}, + {NULL, SS_SEP}, + {"End Of Record", SS_EOR}, + {"End Of File", SS_EOF}, + {NULL, SS_EXITMENU} + }; + return specials; +} + +static bool telnet_connected(Backend *be) +{ + Telnet *telnet = container_of(be, Telnet, backend); + return telnet->s != NULL; +} + +static bool telnet_sendok(Backend *be) +{ + /* Telnet *telnet = container_of(be, Telnet, backend); */ + return true; +} + +static void telnet_unthrottle(Backend *be, size_t backlog) +{ + Telnet *telnet = container_of(be, Telnet, backend); + sk_set_frozen(telnet->s, backlog > TELNET_MAX_BACKLOG); +} + +static bool telnet_ldisc(Backend *be, int option) +{ + Telnet *telnet = container_of(be, Telnet, backend); + if (option == LD_ECHO) + return telnet->echoing; + if (option == LD_EDIT) + return telnet->editing; + return false; +} + +static void telnet_provide_ldisc(Backend *be, Ldisc *ldisc) +{ + Telnet *telnet = container_of(be, Telnet, backend); + telnet->ldisc = ldisc; +} + +static int telnet_exitcode(Backend *be) +{ + Telnet *telnet = container_of(be, Telnet, backend); + if (telnet->s != NULL) + return -1; /* still connected */ + else if (telnet->closed_on_socket_error) + return INT_MAX; /* a socket error counts as an unclean exit */ + else + /* Telnet doesn't transmit exit codes back to the client */ + return 0; +} + +/* + * cfg_info for Telnet does nothing at all. + */ +static int telnet_cfg_info(Backend *be) +{ + return 0; +} + +const BackendVtable telnet_backend = { + .init = telnet_init, + .free = telnet_free, + .reconfig = telnet_reconfig, + .send = telnet_send, + .sendbuffer = telnet_sendbuffer, + .size = telnet_size, + .special = telnet_special, + .get_specials = telnet_get_specials, + .connected = telnet_connected, + .exitcode = telnet_exitcode, + .sendok = telnet_sendok, + .ldisc_option_state = telnet_ldisc, + .provide_ldisc = telnet_provide_ldisc, + .unthrottle = telnet_unthrottle, + .cfg_info = telnet_cfg_info, + .id = "telnet", + .displayname = "Telnet", + .protocol = PROT_TELNET, + .default_port = 23, +}; diff --git a/otherbackends/testback.c b/otherbackends/testback.c new file mode 100644 index 00000000..8a158439 --- /dev/null +++ b/otherbackends/testback.c @@ -0,0 +1,214 @@ +/* + * 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 +#include + +#include "putty.h" + +static char *null_init(const BackendVtable *, Seat *, Backend **, LogContext *, + Conf *, const char *, int, char **, bool, bool); +static char *loop_init(const BackendVtable *, Seat *, Backend **, LogContext *, + Conf *, const char *, int, char **, bool, bool); +static void null_free(Backend *); +static void loop_free(Backend *); +static void null_reconfig(Backend *, Conf *); +static size_t null_send(Backend *, const char *, size_t); +static size_t loop_send(Backend *, const char *, size_t); +static size_t null_sendbuffer(Backend *); +static void null_size(Backend *, int, int); +static void null_special(Backend *, SessionSpecialCode, int); +static const SessionSpecial *null_get_specials(Backend *); +static bool null_connected(Backend *); +static int null_exitcode(Backend *); +static bool null_sendok(Backend *); +static bool null_ldisc(Backend *, int); +static void null_provide_ldisc(Backend *, Ldisc *); +static void null_unthrottle(Backend *, size_t); +static int null_cfg_info(Backend *); + +const BackendVtable null_backend = { + .init = null_init, + .free = null_free, + .reconfig = null_reconfig, + .send = null_send, + .sendbuffer = null_sendbuffer, + .size = null_size, + .special = null_special, + .get_specials = null_get_specials, + .connected = null_connected, + .exitcode = null_exitcode, + .sendok = null_sendok, + .ldisc_option_state = null_ldisc, + .provide_ldisc = null_provide_ldisc, + .unthrottle = null_unthrottle, + .cfg_info = null_cfg_info, + .id = "null", + .displayname = "null", + .protocol = -1, + .default_port = 0, +}; + +const BackendVtable loop_backend = { + .init = loop_init, + .free = loop_free, + .reconfig = null_reconfig, + .send = loop_send, + .sendbuffer = null_sendbuffer, + .size = null_size, + .special = null_special, + .get_specials = null_get_specials, + .connected = null_connected, + .exitcode = null_exitcode, + .sendok = null_sendok, + .ldisc_option_state = null_ldisc, + .provide_ldisc = null_provide_ldisc, + .unthrottle = null_unthrottle, + .cfg_info = null_cfg_info, + .id = "loop", + .displayname = "loop", + .protocol = -1, + .default_port = 0, +}; + +struct loop_state { + Seat *seat; + Backend backend; +}; + +static char *null_init(const BackendVtable *vt, Seat *seat, + Backend **backend_handle, LogContext *logctx, + Conf *conf, const char *host, int port, + char **realhost, bool nodelay, bool keepalive) { + /* No local authentication phase in this protocol */ + seat_set_trust_status(seat, false); + + *backend_handle = NULL; + return NULL; +} + +static char *loop_init(const BackendVtable *vt, Seat *seat, + Backend **backend_handle, LogContext *logctx, + Conf *conf, const char *host, int port, + char **realhost, bool nodelay, bool keepalive) { + struct loop_state *st = snew(struct loop_state); + + /* No local authentication phase in this protocol */ + seat_set_trust_status(seat, false); + + st->seat = seat; + *backend_handle = &st->backend; + return NULL; +} + +static void null_free(Backend *be) +{ + +} + +static void loop_free(Backend *be) +{ + struct loop_state *st = container_of(be, struct loop_state, backend); + + sfree(st); +} + +static void null_reconfig(Backend *be, Conf *conf) { + +} + +static size_t null_send(Backend *be, const char *buf, size_t len) { + + return 0; +} + +static size_t loop_send(Backend *be, const char *buf, size_t len) { + struct loop_state *st = container_of(be, struct loop_state, backend); + + return seat_output(st->seat, 0, buf, len); +} + +static size_t null_sendbuffer(Backend *be) { + + return 0; +} + +static void null_size(Backend *be, int width, int height) { + +} + +static void null_special(Backend *be, SessionSpecialCode code, int arg) { + +} + +static const SessionSpecial *null_get_specials (Backend *be) { + + return NULL; +} + +static bool null_connected(Backend *be) { + + return false; +} + +static int null_exitcode(Backend *be) { + + return 0; +} + +static bool null_sendok(Backend *be) { + + return true; +} + +static void null_unthrottle(Backend *be, size_t backlog) { + +} + +static bool null_ldisc(Backend *be, int option) { + + return false; +} + +static void null_provide_ldisc (Backend *be, Ldisc *ldisc) { + +} + +static int null_cfg_info(Backend *be) +{ + return 0; +} + + +/* + * Emacs magic: + * Local Variables: + * c-file-style: "simon" + * End: + */ diff --git a/raw.c b/raw.c deleted file mode 100644 index 0c454985..00000000 --- a/raw.c +++ /dev/null @@ -1,330 +0,0 @@ -/* - * "Raw" backend. - */ - -#include -#include -#include - -#include "putty.h" - -#define RAW_MAX_BACKLOG 4096 - -typedef struct Raw Raw; -struct Raw { - Socket *s; - bool closed_on_socket_error; - size_t bufsize; - Seat *seat; - LogContext *logctx; - bool sent_console_eof, sent_socket_eof, session_started; - - Conf *conf; - - Plug plug; - Backend backend; -}; - -static void raw_size(Backend *be, int width, int height); - -static void c_write(Raw *raw, const void *buf, size_t len) -{ - size_t backlog = seat_stdout(raw->seat, buf, len); - sk_set_frozen(raw->s, backlog > RAW_MAX_BACKLOG); -} - -static void raw_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, - const char *error_msg, int error_code) -{ - Raw *raw = container_of(plug, Raw, plug); - backend_socket_log(raw->seat, raw->logctx, type, addr, port, - error_msg, error_code, raw->conf, raw->session_started); -} - -static void raw_check_close(Raw *raw) -{ - /* - * Called after we send EOF on either the socket or the console. - * Its job is to wind up the session once we have sent EOF on both. - */ - if (raw->sent_console_eof && raw->sent_socket_eof) { - if (raw->s) { - sk_close(raw->s); - raw->s = NULL; - seat_notify_remote_exit(raw->seat); - } - } -} - -static void raw_closing(Plug *plug, const char *error_msg, int error_code, - bool calling_back) -{ - Raw *raw = container_of(plug, Raw, plug); - - if (error_msg) { - /* A socket error has occurred. */ - if (raw->s) { - sk_close(raw->s); - raw->s = NULL; - raw->closed_on_socket_error = true; - seat_notify_remote_exit(raw->seat); - } - logevent(raw->logctx, error_msg); - seat_connection_fatal(raw->seat, "%s", error_msg); - } else { - /* Otherwise, the remote side closed the connection normally. */ - if (!raw->sent_console_eof && seat_eof(raw->seat)) { - /* - * The front end wants us to close the outgoing side of the - * connection as soon as we see EOF from the far end. - */ - if (!raw->sent_socket_eof) { - if (raw->s) - sk_write_eof(raw->s); - raw->sent_socket_eof= true; - } - } - raw->sent_console_eof = true; - raw_check_close(raw); - } -} - -static void raw_receive(Plug *plug, int urgent, const char *data, size_t len) -{ - Raw *raw = container_of(plug, Raw, plug); - c_write(raw, data, len); - /* We count 'session start', for proxy logging purposes, as being - * when data is received from the network and printed. */ - raw->session_started = true; -} - -static void raw_sent(Plug *plug, size_t bufsize) -{ - Raw *raw = container_of(plug, Raw, plug); - raw->bufsize = bufsize; -} - -static const PlugVtable Raw_plugvt = { - .log = raw_log, - .closing = raw_closing, - .receive = raw_receive, - .sent = raw_sent, -}; - -/* - * 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 char *raw_init(const BackendVtable *vt, Seat *seat, - Backend **backend_handle, LogContext *logctx, - Conf *conf, const char *host, int port, - char **realhost, bool nodelay, bool keepalive) -{ - SockAddr *addr; - const char *err; - Raw *raw; - int addressfamily; - char *loghost; - - /* No local authentication phase in this protocol */ - seat_set_trust_status(seat, false); - - raw = snew(Raw); - raw->plug.vt = &Raw_plugvt; - raw->backend.vt = vt; - raw->s = NULL; - raw->closed_on_socket_error = false; - *backend_handle = &raw->backend; - raw->sent_console_eof = raw->sent_socket_eof = false; - raw->bufsize = 0; - raw->session_started = false; - raw->conf = conf_copy(conf); - - raw->seat = seat; - raw->logctx = logctx; - - addressfamily = conf_get_int(conf, CONF_addressfamily); - /* - * Try to find host. - */ - addr = name_lookup(host, port, realhost, conf, addressfamily, - raw->logctx, "main connection"); - if ((err = sk_addr_error(addr)) != NULL) { - sk_addr_free(addr); - return dupstr(err); - } - - if (port < 0) - port = 23; /* default telnet port */ - - /* - * Open socket. - */ - raw->s = new_connection(addr, *realhost, port, false, true, nodelay, - keepalive, &raw->plug, conf); - if ((err = sk_socket_error(raw->s)) != NULL) - return dupstr(err); - - loghost = conf_get_str(conf, CONF_loghost); - if (*loghost) { - char *colon; - - sfree(*realhost); - *realhost = dupstr(loghost); - - colon = host_strrchr(*realhost, ':'); - if (colon) - *colon++ = '\0'; - } - - return NULL; -} - -static void raw_free(Backend *be) -{ - Raw *raw = container_of(be, Raw, backend); - - if (raw->s) - sk_close(raw->s); - conf_free(raw->conf); - sfree(raw); -} - -/* - * Stub routine (we don't have any need to reconfigure this backend). - */ -static void raw_reconfig(Backend *be, Conf *conf) -{ -} - -/* - * Called to send data down the raw connection. - */ -static size_t raw_send(Backend *be, const char *buf, size_t len) -{ - Raw *raw = container_of(be, Raw, backend); - - 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 size_t raw_sendbuffer(Backend *be) -{ - Raw *raw = container_of(be, Raw, backend); - return raw->bufsize; -} - -/* - * Called to set the size of the window - */ -static void raw_size(Backend *be, int width, int height) -{ - /* Do nothing! */ - return; -} - -/* - * Send raw special codes. We only handle outgoing EOF here. - */ -static void raw_special(Backend *be, SessionSpecialCode code, int arg) -{ - Raw *raw = container_of(be, Raw, backend); - if (code == SS_EOF && raw->s) { - sk_write_eof(raw->s); - raw->sent_socket_eof= true; - raw_check_close(raw); - } - - return; -} - -/* - * Return a list of the special codes that make sense in this - * protocol. - */ -static const SessionSpecial *raw_get_specials(Backend *be) -{ - return NULL; -} - -static bool raw_connected(Backend *be) -{ - Raw *raw = container_of(be, Raw, backend); - return raw->s != NULL; -} - -static bool raw_sendok(Backend *be) -{ - return true; -} - -static void raw_unthrottle(Backend *be, size_t backlog) -{ - Raw *raw = container_of(be, Raw, backend); - sk_set_frozen(raw->s, backlog > RAW_MAX_BACKLOG); -} - -static bool raw_ldisc(Backend *be, int option) -{ - if (option == LD_EDIT || option == LD_ECHO) - return true; - return false; -} - -static void raw_provide_ldisc(Backend *be, Ldisc *ldisc) -{ - /* This is a stub. */ -} - -static int raw_exitcode(Backend *be) -{ - Raw *raw = container_of(be, Raw, backend); - if (raw->s != NULL) - return -1; /* still connected */ - else if (raw->closed_on_socket_error) - return INT_MAX; /* a socket error counts as an unclean exit */ - else - /* Exit codes are a meaningless concept in the Raw protocol */ - return 0; -} - -/* - * cfg_info for Raw does nothing at all. - */ -static int raw_cfg_info(Backend *be) -{ - return 0; -} - -const BackendVtable raw_backend = { - .init = raw_init, - .free = raw_free, - .reconfig = raw_reconfig, - .send = raw_send, - .sendbuffer = raw_sendbuffer, - .size = raw_size, - .special = raw_special, - .get_specials = raw_get_specials, - .connected = raw_connected, - .exitcode = raw_exitcode, - .sendok = raw_sendok, - .ldisc_option_state = raw_ldisc, - .provide_ldisc = raw_provide_ldisc, - .unthrottle = raw_unthrottle, - .cfg_info = raw_cfg_info, - .id = "raw", - .displayname = "Raw", - .protocol = PROT_RAW, - .default_port = 0, -}; diff --git a/rlogin.c b/rlogin.c deleted file mode 100644 index 2a3714e0..00000000 --- a/rlogin.c +++ /dev/null @@ -1,428 +0,0 @@ -/* - * Rlogin backend. - */ - -#include -#include -#include -#include - -#include "putty.h" - -#define RLOGIN_MAX_BACKLOG 4096 - -typedef struct Rlogin Rlogin; -struct Rlogin { - Socket *s; - bool closed_on_socket_error; - int bufsize; - bool firstbyte; - bool cansize; - int term_width, term_height; - Seat *seat; - LogContext *logctx; - - Conf *conf; - - /* In case we need to read a username from the terminal before starting */ - prompts_t *prompt; - - Plug plug; - Backend backend; -}; - -static void c_write(Rlogin *rlogin, const void *buf, size_t len) -{ - size_t backlog = seat_stdout(rlogin->seat, buf, len); - sk_set_frozen(rlogin->s, backlog > RLOGIN_MAX_BACKLOG); -} - -static void rlogin_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, - const char *error_msg, int error_code) -{ - Rlogin *rlogin = container_of(plug, Rlogin, plug); - backend_socket_log(rlogin->seat, rlogin->logctx, type, addr, port, - error_msg, error_code, - rlogin->conf, !rlogin->firstbyte); -} - -static void rlogin_closing(Plug *plug, const char *error_msg, int error_code, - bool calling_back) -{ - Rlogin *rlogin = container_of(plug, Rlogin, plug); - - /* - * We don't implement independent EOF in each direction for Telnet - * connections; as soon as we get word that the remote side has - * sent us EOF, we wind up the whole connection. - */ - - if (rlogin->s) { - sk_close(rlogin->s); - rlogin->s = NULL; - if (error_msg) - rlogin->closed_on_socket_error = true; - seat_notify_remote_exit(rlogin->seat); - } - if (error_msg) { - /* A socket error has occurred. */ - logevent(rlogin->logctx, error_msg); - seat_connection_fatal(rlogin->seat, "%s", error_msg); - } /* Otherwise, the remote side closed the connection normally. */ -} - -static void rlogin_receive( - Plug *plug, int urgent, const char *data, size_t len) -{ - Rlogin *rlogin = container_of(plug, Rlogin, plug); - if (len == 0) - return; - if (urgent == 2) { - char c; - - c = *data++; - len--; - if (c == '\x80') { - rlogin->cansize = true; - backend_size(&rlogin->backend, - 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 = false; - } - if (len > 0) - c_write(rlogin, data, len); - } -} - -static void rlogin_sent(Plug *plug, size_t bufsize) -{ - Rlogin *rlogin = container_of(plug, 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); - p = conf_get_str(rlogin->conf, CONF_localusername); - sk_write(rlogin->s, p, strlen(p)); - sk_write(rlogin->s, &z, 1); - sk_write(rlogin->s, ruser, strlen(ruser)); - sk_write(rlogin->s, &z, 1); - p = conf_get_str(rlogin->conf, CONF_termtype); - sk_write(rlogin->s, p, strlen(p)); - sk_write(rlogin->s, "/", 1); - p = conf_get_str(rlogin->conf, CONF_termspeed); - sk_write(rlogin->s, p, strspn(p, "0123456789")); - rlogin->bufsize = sk_write(rlogin->s, &z, 1); - - rlogin->prompt = NULL; -} - -static const PlugVtable Rlogin_plugvt = { - .log = rlogin_log, - .closing = rlogin_closing, - .receive = rlogin_receive, - .sent = rlogin_sent, -}; - -/* - * 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 char *rlogin_init(const BackendVtable *vt, Seat *seat, - Backend **backend_handle, LogContext *logctx, - Conf *conf, const char *host, int port, - char **realhost, bool nodelay, bool keepalive) -{ - SockAddr *addr; - const char *err; - Rlogin *rlogin; - char *ruser; - int addressfamily; - char *loghost; - - rlogin = snew(Rlogin); - rlogin->plug.vt = &Rlogin_plugvt; - rlogin->backend.vt = vt; - rlogin->s = NULL; - rlogin->closed_on_socket_error = false; - rlogin->seat = seat; - rlogin->logctx = logctx; - rlogin->term_width = conf_get_int(conf, CONF_width); - rlogin->term_height = conf_get_int(conf, CONF_height); - rlogin->firstbyte = true; - rlogin->cansize = false; - rlogin->prompt = NULL; - rlogin->conf = conf_copy(conf); - *backend_handle = &rlogin->backend; - - addressfamily = conf_get_int(conf, CONF_addressfamily); - /* - * Try to find host. - */ - addr = name_lookup(host, port, realhost, conf, addressfamily, - rlogin->logctx, "rlogin connection"); - if ((err = sk_addr_error(addr)) != NULL) { - sk_addr_free(addr); - return dupstr(err); - } - - if (port < 0) - port = 513; /* default rlogin port */ - - /* - * Open socket. - */ - rlogin->s = new_connection(addr, *realhost, port, true, false, - nodelay, keepalive, &rlogin->plug, conf); - if ((err = sk_socket_error(rlogin->s)) != NULL) - return dupstr(err); - - loghost = conf_get_str(conf, CONF_loghost); - if (*loghost) { - char *colon; - - sfree(*realhost); - *realhost = dupstr(loghost); - - colon = host_strrchr(*realhost, ':'); - if (colon) - *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 ((ruser = get_remote_username(conf)) != NULL) { - /* Next terminal output will come from server */ - seat_set_trust_status(rlogin->seat, false); - rlogin_startup(rlogin, ruser); - sfree(ruser); - } else { - int ret; - - rlogin->prompt = new_prompts(); - rlogin->prompt->to_server = true; - rlogin->prompt->from_server = false; - rlogin->prompt->name = dupstr("Rlogin login name"); - add_prompt(rlogin->prompt, dupstr("rlogin username: "), true); - ret = seat_get_userpass_input(rlogin->seat, rlogin->prompt, NULL); - if (ret >= 0) { - /* Next terminal output will come from server */ - seat_set_trust_status(rlogin->seat, false); - rlogin_startup(rlogin, prompt_get_result_ref( - rlogin->prompt->prompts[0])); - } - } - - return NULL; -} - -static void rlogin_free(Backend *be) -{ - Rlogin *rlogin = container_of(be, Rlogin, backend); - - if (rlogin->prompt) - free_prompts(rlogin->prompt); - if (rlogin->s) - sk_close(rlogin->s); - conf_free(rlogin->conf); - sfree(rlogin); -} - -/* - * Stub routine (we don't have any need to reconfigure this backend). - */ -static void rlogin_reconfig(Backend *be, Conf *conf) -{ -} - -/* - * Called to send data down the rlogin connection. - */ -static size_t rlogin_send(Backend *be, const char *buf, size_t len) -{ - Rlogin *rlogin = container_of(be, Rlogin, backend); - bufchain bc; - - if (rlogin->s == NULL) - return 0; - - bufchain_init(&bc); - bufchain_add(&bc, buf, len); - - if (rlogin->prompt) { - /* - * We're still prompting for a username, and aren't talking - * directly to the network connection yet. - */ - int ret = seat_get_userpass_input(rlogin->seat, rlogin->prompt, &bc); - if (ret >= 0) { - /* Next terminal output will come from server */ - seat_set_trust_status(rlogin->seat, false); - rlogin_startup(rlogin, prompt_get_result_ref( - rlogin->prompt->prompts[0])); - /* that nulls out rlogin->prompt, so then we'll start sending - * data down the wire in the obvious way */ - } - } - - if (!rlogin->prompt) { - while (bufchain_size(&bc) > 0) { - ptrlen data = bufchain_prefix(&bc); - rlogin->bufsize = sk_write(rlogin->s, data.ptr, data.len); - bufchain_consume(&bc, len); - } - } - - bufchain_clear(&bc); - - return rlogin->bufsize; -} - -/* - * Called to query the current socket sendability status. - */ -static size_t rlogin_sendbuffer(Backend *be) -{ - Rlogin *rlogin = container_of(be, Rlogin, backend); - return rlogin->bufsize; -} - -/* - * Called to set the size of the window - */ -static void rlogin_size(Backend *be, int width, int height) -{ - Rlogin *rlogin = container_of(be, Rlogin, backend); - 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(Backend *be, SessionSpecialCode code, int arg) -{ - /* Do nothing! */ - return; -} - -/* - * Return a list of the special codes that make sense in this - * protocol. - */ -static const SessionSpecial *rlogin_get_specials(Backend *be) -{ - return NULL; -} - -static bool rlogin_connected(Backend *be) -{ - Rlogin *rlogin = container_of(be, Rlogin, backend); - return rlogin->s != NULL; -} - -static bool rlogin_sendok(Backend *be) -{ - /* Rlogin *rlogin = container_of(be, Rlogin, backend); */ - return true; -} - -static void rlogin_unthrottle(Backend *be, size_t backlog) -{ - Rlogin *rlogin = container_of(be, Rlogin, backend); - sk_set_frozen(rlogin->s, backlog > RLOGIN_MAX_BACKLOG); -} - -static bool rlogin_ldisc(Backend *be, int option) -{ - /* Rlogin *rlogin = container_of(be, Rlogin, backend); */ - return false; -} - -static void rlogin_provide_ldisc(Backend *be, Ldisc *ldisc) -{ - /* This is a stub. */ -} - -static int rlogin_exitcode(Backend *be) -{ - Rlogin *rlogin = container_of(be, Rlogin, backend); - if (rlogin->s != NULL) - return -1; /* still connected */ - else if (rlogin->closed_on_socket_error) - return INT_MAX; /* a socket error counts as an unclean exit */ - else - /* If we ever implement RSH, we'll probably need to do this properly */ - return 0; -} - -/* - * cfg_info for rlogin does nothing at all. - */ -static int rlogin_cfg_info(Backend *be) -{ - return 0; -} - -const BackendVtable rlogin_backend = { - .init = rlogin_init, - .free = rlogin_free, - .reconfig = rlogin_reconfig, - .send = rlogin_send, - .sendbuffer = rlogin_sendbuffer, - .size = rlogin_size, - .special = rlogin_special, - .get_specials = rlogin_get_specials, - .connected = rlogin_connected, - .exitcode = rlogin_exitcode, - .sendok = rlogin_sendok, - .ldisc_option_state = rlogin_ldisc, - .provide_ldisc = rlogin_provide_ldisc, - .unthrottle = rlogin_unthrottle, - .cfg_info = rlogin_cfg_info, - .id = "rlogin", - .displayname = "Rlogin", - .protocol = PROT_RLOGIN, - .default_port = 513, -}; diff --git a/supdup.c b/supdup.c deleted file mode 100644 index f210ebe3..00000000 --- a/supdup.c +++ /dev/null @@ -1,923 +0,0 @@ -/* -* Supdup backend -*/ - -#include -#include -#include - -#include "putty.h" - -/* - * TTYOPT FUNCTION BITS (36-bit bitmasks) -*/ -#define TOALT 0200000000000LL // Characters 0175 and 0176 are converted to altmode (0033) on input -#define TOCLC 0100000000000LL // (user option bit) Convert lower-case input to upper-case -#define TOERS 0040000000000LL // Selective erase is supported -#define TOMVB 0010000000000LL // Backspacing is supported -#define TOSAI 0004000000000LL // Stanford/ITS extended ASCII graphics character set is supported -#define TOSA1 0002000000000LL // (user option bit) Characters 0001-0037 displayed using Stanford/ITS chars -#define TOOVR 0001000000000LL // Overprinting is supported -#define TOMVU 0000400000000LL // Moving cursor upwards is supported -#define TOMOR 0000200000000LL // (user option bit) System should provide **MORE** processing -#define TOROL 0000100000000LL // (user option bit) Terminal should scroll instead of wrapping -#define TOLWR 0000020000000LL // Lowercase characters are supported -#define TOFCI 0000010000000LL // Terminal can generate CONTROL and META characters -#define TOLID 0000002000000LL // Line insert/delete operations supported -#define TOCID 0000001000000LL // Character insert/delete operations supported -#define TPCBS 0000000000040LL // Terminal is using the "intelligent terminal protocol" (must be on) -#define TPORS 0000000000010LL // Server should process output resets - -// Initialization words (36-bit constants) -#define WORDS 0777773000000 // Negative number of config words to send (6) in high 18 bits -#define TCTYP 0000000000007 // Defines the terminal type (MUST be 7) -#define TTYROL 0000000000001 // Scroll amount for terminal (1 line at a time) - - -// %TD opcodes -// -#define TDMOV 0200 // Cursor positioning -#define TDMV1 0201 // Internal cursor positioning -#define TDEOF 0202 // Erase to end of screen -#define TDEOL 0203 // Erase to end of line -#define TDDLF 0204 // Clear the character the cursor is on -#define TDCRL 0207 // Carriage return -#define TDNOP 0210 // No-op; should be ignored. -#define TDBS 0211 // Backspace (not in official SUPDUP spec) -#define TDLF 0212 // Linefeed (not in official SUPDUP spec) -#define TDCR 0213 // Carriage Return (ditto) -#define TDORS 0214 // Output reset -#define TDQOT 0215 // Quotes the following character -#define TDFS 0216 // Non-destructive forward space -#define TDMV0 0217 // General cursor positioning code -#define TDCLR 0220 // Erase the screen, home cursor -#define TDBEL 0221 // Generate an audio tone, bell, whatever -#define TDILP 0223 // Insert blank lines at the cursor -#define TDDLP 0224 // Delete lines at the cursor -#define TDICP 0225 // Insert blanks at cursor -#define TDDCP 0226 // Delete characters at cursor -#define TDBOW 0227 // Display black chars on white screen -#define TDRST 0230 // Reset %TDBOW - -/* Maximum number of octets following a %TD code. */ -#define TD_ARGS_MAX 4 - -typedef struct supdup_tag Supdup; -struct supdup_tag -{ - Socket *s; - bool closed_on_socket_error; - - Seat *seat; - LogContext *logctx; - int term_width, term_height; - - long long ttyopt; - long tcmxv; - long tcmxh; - - bool sent_location; - - Conf *conf; - - int bufsize; - - enum { - CONNECTING, // waiting for %TDNOP from server after sending connection params - CONNECTED // %TDNOP received, connected. - } state; - - enum { - TD_TOPLEVEL, - TD_ARGS, - TD_ARGSDONE - } tdstate; - - int td_code; - int td_argcount; - char td_args[TD_ARGS_MAX]; - int td_argindex; - - void (*print) (strbuf *outbuf, int c); - - Pinger *pinger; - - Plug plug; - Backend backend; -}; - -#define SUPDUP_MAX_BACKLOG 4096 - -static void c_write(Supdup *supdup, unsigned char *buf, int len) -{ - size_t backlog = seat_stdout(supdup->seat, buf, len); - sk_set_frozen(supdup->s, backlog > SUPDUP_MAX_BACKLOG); -} - -static void supdup_send_location(Supdup *supdup) -{ - char locHeader[] = { 0300, 0302 }; - char* locString = conf_get_str(supdup->conf, CONF_supdup_location); - - sk_write(supdup->s, locHeader, sizeof(locHeader)); - sk_write(supdup->s, locString, strlen(locString) + 1); // include NULL terminator -} - -static void print_ascii(strbuf *outbuf, int c) -{ - /* In ASCII mode, ignore control characters. The server shouldn't - send them. */ - if (c >= 040 && c < 0177) - put_byte (outbuf, c); -} - -static void print_its(strbuf *outbuf, int c) -{ - /* The ITS character set is documented in RFC 734. */ - static const char *map[] = { - "\xc2\xb7", "\342\206\223", "\316\261", "\316\262", - "\342\210\247", "\302\254", "\316\265", "\317\200", - "\316\273", "\xce\xb3", "\xce\xb4", "\xe2\x86\x91", - "\xc2\xb1", "\xe2\x8a\x95", "\342\210\236", "\342\210\202", - "\342\212\202", "\342\212\203", "\342\210\251", "\342\210\252", - "\342\210\200", "\342\210\203", "\xe2\x8a\x97", "\342\206\224", - "\xe2\x86\x90", "\342\206\222", "\xe2\x89\xa0", "\xe2\x97\x8a", - "\342\211\244", "\342\211\245", "\342\211\241", "\342\210\250", - " ", "!", "\"", "#", "$", "%", "&", "'", - "(", ")", "*", "+", ",", "-", ".", "/", - "0", "1", "2", "3", "4", "5", "6", "7", - "8", "9", ":", ";", "<", "=", ">", "?", - "@", "A", "B", "C", "D", "E", "F", "G", - "H", "I", "J", "K", "L", "M", "N", "O", - "P", "Q", "R", "S", "T", "U", "V", "W", - "X", "Y", "Z", "[", "\\", "]", "^", "_", - "`", "a", "b", "c", "d", "e", "f", "g", - "h", "i", "j", "k", "l", "m", "n", "o", - "p", "q", "r", "s", "t", "u", "v", "w", - "x", "y", "z", "{", "|", "}", "~", "\xe2\x88\xab" - }; - - put_data (outbuf, map[c], strlen(map[c])); -} - -static void print_waits(strbuf *outbuf, int c) -{ - /* The WAITS character set used at the Stanford AI Lab is documented - here: https://www.saildart.org/allow/sail-charset-utf8.html */ - static const char *map[] = { - "", "\342\206\223", "\316\261", "\316\262", - "\342\210\247", "\302\254", "\316\265", "\317\200", - "\316\273", "", "", "", - "", "", "\342\210\236", "\342\210\202", - "\342\212\202", "\342\212\203", "\342\210\251", "\342\210\252", - "\342\210\200", "\342\210\203", "\xe2\x8a\x97", "\342\206\224", - "_", "\342\206\222", "~", "\xe2\x89\xa0", - "\342\211\244", "\342\211\245", "\342\211\241", "\342\210\250", - " ", "!", "\"", "#", "$", "%", "&", "'", - "(", ")", "*", "+", ",", "-", ".", "/", - "0", "1", "2", "3", "4", "5", "6", "7", - "8", "9", ":", ";", "<", "=", ">", "?", - "@", "A", "B", "C", "D", "E", "F", "G", - "H", "I", "J", "K", "L", "M", "N", "O", - "P", "Q", "R", "S", "T", "U", "V", "W", - "X", "Y", "Z", "[", "\\", "]", "\xe2\x86\x91", "\xe2\x86\x90", - "`", "a", "b", "c", "d", "e", "f", "g", - "h", "i", "j", "k", "l", "m", "n", "o", - "p", "q", "r", "s", "t", "u", "v", "w", - "x", "y", "z", "{", "|", "\xe2\x97\x8a", "}", "" - }; - - put_data (outbuf, map[c], strlen(map[c])); -} - -static void do_toplevel(Supdup *supdup, strbuf *outbuf, int c) -{ - // Toplevel: Waiting for a %TD code or a printable character - if (c >= 0200) { - // Handle SUPDUP %TD codes (codes greater than or equal to 200) - supdup->td_argindex = 0; - supdup->td_code = c; - switch (c) { - case TDMOV: - // %TD codes using 4 arguments - supdup->td_argcount = 4; - supdup->tdstate = TD_ARGS; - break; - - case TDMV0: - case TDMV1: - // %TD codes using 2 arguments - supdup->td_argcount = 2; - supdup->tdstate = TD_ARGS; - break; - - case TDQOT: - case TDILP: - case TDDLP: - case TDICP: - case TDDCP: - // %TD codes using 1 argument - supdup->td_argcount = 1; - supdup->tdstate = TD_ARGS; - break; - - case TDEOF: - case TDEOL: - case TDDLF: - case TDCRL: - case TDNOP: - case TDORS: - case TDFS: - case TDCLR: - case TDBEL: - case TDBOW: - case TDRST: - case TDBS: - case TDCR: - case TDLF: - // %TD codes using 0 arguments - supdup->td_argcount = 0; - supdup->tdstate = TD_ARGSDONE; - break; - - default: - // Unhandled, ignore - break; - } - } else { - supdup->print(outbuf, c); - } -} - -static void do_args(Supdup *supdup, strbuf *outbuf, int c) -{ - // Collect up args for %TD code - if (supdup->td_argindex < TD_ARGS_MAX) { - supdup->td_args[supdup->td_argindex] = c; - supdup->td_argindex++; - - if (supdup->td_argcount == supdup->td_argindex) { - // No more args, %TD code is ready to go. - supdup->tdstate = TD_ARGSDONE; - } - } else { - // Should never hit this state, if we do we will just - // return to TOPLEVEL. - supdup->tdstate = TD_TOPLEVEL; - } -} - -static void do_argsdone(Supdup *supdup, strbuf *outbuf, int c) -{ - char buf[4]; - int x, y; - - // Arguments for %TD code have been collected; dispatch based - // on the %TD code we're handling. - switch (supdup->td_code) { - case TDMOV: - /* - General cursor position code. Followed by four bytes; - the first two are the "old" vertical and horizontal - positions and may be ignored. The next two are the new - vertical and horizontal positions. The cursor should be - moved to this position. - */ - - // We only care about the new position. - strbuf_catf(outbuf, "\033[%d;%dH", supdup->td_args[2]+1, supdup->td_args[3]+1); - break; - - case TDMV0: - case TDMV1: - /* - General cursor position code. Followed by two bytes; - the new vertical and horizontal positions. - */ - strbuf_catf(outbuf, "\033[%d;%dH", supdup->td_args[0]+1, supdup->td_args[1]+1); - break; - - case TDEOF: - /* - Erase to end of screen. This is an optional function - since many terminals do not support this. If the - terminal does not support this function, it should be - treated the same as %TDEOL. - - %TDEOF does an erase to end of line, then erases all - lines lower on the screen than the cursor. The cursor - does not move. - */ - strbuf_catf(outbuf, "\033[J"); - break; - - case TDEOL: - /* - Erase to end of line. This erases the character - position the cursor is at and all positions to the right - on the same line. The cursor does not move. - */ - strbuf_catf(outbuf, "\033[K"); - break; - - case TDDLF: - /* - Clear the character position the cursor is on. The - cursor does not move. - */ - strbuf_catf(outbuf, "\033[X"); - break; - - case TDCRL: - /* - If the cursor is not on the bottom line of the screen, - move cursor to the beginning of the next line and clear - that line. If the cursor is at the bottom line, scroll - up. - */ - strbuf_catf(outbuf, "\015\012"); - break; - - case TDNOP: - /* - No-op; should be ignored. - */ - break; - - case TDORS: - /* - Output reset. This code serves as a data mark for - aborting output much as IAC DM does in the ordinary - TELNET protocol. - */ - outbuf->len = 0; - if (!seat_get_cursor_position(supdup->seat, &x, &y)) - x = y = 0; - buf[0] = 034; - buf[1] = 020; - buf[2] = y; - buf[3] = x; - sk_write(supdup->s, buf, 4); - break; - - case TDQOT: - /* - Quotes the following character. This is used when - sending 8-bit codes which are not %TD codes, for - instance when loading programs into an intelligent - terminal. The following character should be passed - through intact to the terminal. - */ - - put_byte(outbuf, supdup->td_args[0]); - break; - - case TDFS: - /* - Non-destructive forward space. The cursor moves right - one position; this code will not be sent at the end of a - line. - */ - - strbuf_catf(outbuf, "\033[C"); - break; - - case TDCLR: - /* - Erase the screen. Home the cursor to the top left hand - corner of the screen. - */ - strbuf_catf(outbuf, "\033[2J\033[H"); - break; - - case TDBEL: - /* - Generate an audio tone, bell, whatever. - */ - - strbuf_catf(outbuf, "\007"); - break; - - case TDILP: - /* - Insert blank lines at the cursor; followed by a byte - containing a count of the number of blank lines to - insert. The cursor is unmoved. The line the cursor is - on and all lines below it move down; lines moved off the - bottom of the screen are lost. - */ - strbuf_catf(outbuf, "\033[%dL", supdup->td_args[0]); - break; - - case TDDLP: - /* - Delete lines at the cursor; followed by a count. The - cursor is unmoved. The first line deleted is the one - the cursor is on. Lines below those deleted move up. - Newly- created lines at the bottom of the screen are - blank. - */ - strbuf_catf(outbuf, "\033[%dM", supdup->td_args[0]); - break; - - case TDICP: - /* - Insert blank character positions at the cursor; followed - by a count. The cursor is unmoved. The character the - cursor is on and all characters to the right on the - current line move to the right; characters moved off the - end of the line are lost. - */ - strbuf_catf(outbuf, "\033[%d@", supdup->td_args[0]); - break; - - case TDDCP: - /* - Delete characters at the cursor; followed by a count. - The cursor is unmoved. The first character deleted is - the one the cursor is on. Newly-created characters at - the end of the line are blank. - */ - strbuf_catf(outbuf, "\033[%dP", supdup->td_args[0]); - break; - - case TDBOW: - case TDRST: - /* - Display black characters on white screen. - HIGHLY OPTIONAL. - */ - - // Since this is HIGHLY OPTIONAL, I'm not going - // to implement it yet. - break; - - /* - * Non-standard (whatever "standard" means here) SUPDUP - * commands. These are used (at the very least) by - * Genera's SUPDUP implementation. Cannot find any - * official documentation, behavior is based on UNIX - * SUPDUP implementation from MIT. - */ - case TDBS: - /* - * Backspace -- move cursor back one character (does not - * appear to wrap...) - */ - put_byte(outbuf, '\010'); - break; - - case TDLF: - /* - * Linefeed -- move cursor down one line (again, no wrapping) - */ - put_byte(outbuf, '\012'); - break; - - case TDCR: - /* - * Carriage return -- move cursor to start of current line. - */ - put_byte(outbuf, '\015'); - break; - } - - // Return to top level to pick up the next %TD code or - // printable character. - supdup->tdstate = TD_TOPLEVEL; -} - -static void term_out_supdup(Supdup *supdup, strbuf *outbuf, int c) -{ - if (supdup->tdstate == TD_TOPLEVEL) { - do_toplevel (supdup, outbuf, c); - } else if (supdup->tdstate == TD_ARGS) { - do_args (supdup, outbuf, c); - } - - // If all arguments for a %TD code are ready, we will execute the code now. - if (supdup->tdstate == TD_ARGSDONE) { - do_argsdone (supdup, outbuf, c); - } -} - -static void do_supdup_read(Supdup *supdup, const char *buf, size_t len) -{ - strbuf *outbuf = strbuf_new(); - - while (len--) { - int c = (unsigned char)*buf++; - switch (supdup->state) { - case CONNECTING: - // "Following the transmission of the terminal options by - // the user, the server should respond with an ASCII - // greeting message, terminated with a %TDNOP code..." - if (TDNOP == c) { - // Greeting done, switch to the CONNECTED state. - supdup->state = CONNECTED; - supdup->tdstate = TD_TOPLEVEL; - } else { - // Forward the greeting message (which is straight - // ASCII, no controls) on so it gets displayed TODO: - // filter out only printable chars? - put_byte(outbuf, c); - } - break; - - case CONNECTED: - // "All transmissions from the server after the %TDNOP - // [see above] are either printing characters or virtual - // terminal display codes." Forward these on to the - // frontend which will decide what to do with them. - term_out_supdup(supdup, outbuf, c); - /* - * Hack to make Symbolics Genera SUPDUP happy: Wait until - * after we're connected (finished the initial handshake - * and have gotten additional data) before sending the - * location string. For some reason doing so earlier - * causes the Symbolics SUPDUP to end up in an odd state. - */ - if (!supdup->sent_location) { - supdup_send_location(supdup); - supdup->sent_location = true; - } - break; - } - - if (outbuf->len >= 4096) { - c_write(supdup, outbuf->u, outbuf->len); - outbuf->len = 0; - } - } - - if (outbuf->len) - c_write(supdup, outbuf->u, outbuf->len); - strbuf_free(outbuf); -} - -static void supdup_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, - const char *error_msg, int error_code) -{ - Supdup *supdup = container_of(plug, Supdup, plug); - backend_socket_log(supdup->seat, supdup->logctx, type, addr, port, - error_msg, error_code, - supdup->conf, supdup->state != CONNECTING); -} - -static void supdup_closing(Plug *plug, const char *error_msg, int error_code, - bool calling_back) -{ - Supdup *supdup = container_of(plug, Supdup, plug); - - /* - * We don't implement independent EOF in each direction for Telnet - * connections; as soon as we get word that the remote side has - * sent us EOF, we wind up the whole connection. - */ - - if (supdup->s) { - sk_close(supdup->s); - supdup->s = NULL; - if (error_msg) - supdup->closed_on_socket_error = true; - seat_notify_remote_exit(supdup->seat); - } - if (error_msg) { - logevent(supdup->logctx, error_msg); - seat_connection_fatal(supdup->seat, "%s", error_msg); - } - /* Otherwise, the remote side closed the connection normally. */ -} - -static void supdup_receive(Plug *plug, int urgent, const char *data, size_t len) -{ - Supdup *supdup = container_of(plug, Supdup, plug); - do_supdup_read(supdup, data, len); -} - -static void supdup_sent(Plug *plug, size_t bufsize) -{ - Supdup *supdup = container_of(plug, Supdup, plug); - supdup->bufsize = bufsize; -} - -static void supdup_send_36bits(Supdup *supdup, unsigned long long thirtysix) -{ - // - // From RFC734: - // "Each word is sent through the 8-bit connection as six - // 6-bit bytes, most-significant first." - // - // Split the 36-bit word into 6 6-bit "bytes", packed into - // 8-bit bytes and send, most-significant byte first. - // - for (int i = 5; i >= 0; i--) { - char sixBits = (thirtysix >> (i * 6)) & 077; - sk_write(supdup->s, &sixBits, 1); - } -} - -static void supdup_send_config(Supdup *supdup) -{ - supdup_send_36bits(supdup, WORDS); // negative length - supdup_send_36bits(supdup, TCTYP); // terminal type - supdup_send_36bits(supdup, supdup->ttyopt); // options - supdup_send_36bits(supdup, supdup->tcmxv); // height - supdup_send_36bits(supdup, supdup->tcmxh); // width - supdup_send_36bits(supdup, TTYROL); // scroll amount -} - -/* -* Called to set up the Supdup 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 char *supdup_init(const BackendVtable *x, Seat *seat, - Backend **backend_handle, - LogContext *logctx, Conf *conf, - const char *host, int port, char **realhost, - bool nodelay, bool keepalive) -{ - static const PlugVtable fn_table = { - .log = supdup_log, - .closing = supdup_closing, - .receive = supdup_receive, - .sent = supdup_sent, - }; - SockAddr *addr; - const char *err; - Supdup *supdup; - char *loghost; - int addressfamily; - const char *utf8 = "\033%G"; - - supdup = snew(struct supdup_tag); - supdup->plug.vt = &fn_table; - supdup->backend.vt = &supdup_backend; - supdup->logctx = logctx; - supdup->conf = conf_copy(conf); - supdup->s = NULL; - supdup->closed_on_socket_error = false; - supdup->seat = seat; - supdup->term_width = conf_get_int(supdup->conf, CONF_width); - supdup->term_height = conf_get_int(supdup->conf, CONF_height); - supdup->pinger = NULL; - supdup->sent_location = false; - *backend_handle = &supdup->backend; - - switch (conf_get_int(supdup->conf, CONF_supdup_ascii_set)) { - case SUPDUP_CHARSET_ASCII: - supdup->print = print_ascii; - break; - case SUPDUP_CHARSET_ITS: - supdup->print = print_its; - break; - case SUPDUP_CHARSET_WAITS: - supdup->print = print_waits; - break; - } - - /* - * Try to find host. - */ - { - char *buf; - addressfamily = conf_get_int(supdup->conf, CONF_addressfamily); - buf = dupprintf("Looking up host \"%s\"%s", host, - (addressfamily == ADDRTYPE_IPV4 ? " (IPv4)" : - (addressfamily == ADDRTYPE_IPV6 ? " (IPv6)" : - ""))); - logevent(supdup->logctx, buf); - sfree(buf); - } - addr = name_lookup(host, port, realhost, supdup->conf, addressfamily, NULL, ""); - if ((err = sk_addr_error(addr)) != NULL) { - sk_addr_free(addr); - return dupstr(err); - } - - if (port < 0) - port = 0137; /* default supdup port */ - - /* - * Open socket. - */ - supdup->s = new_connection(addr, *realhost, port, false, true, - nodelay, keepalive, &supdup->plug, supdup->conf); - if ((err = sk_socket_error(supdup->s)) != NULL) - return dupstr(err); - - supdup->pinger = pinger_new(supdup->conf, &supdup->backend); - - /* - * We can send special commands from the start. - */ - seat_update_specials_menu(supdup->seat); - - /* - * loghost overrides realhost, if specified. - */ - loghost = conf_get_str(supdup->conf, CONF_loghost); - if (*loghost) { - char *colon; - - sfree(*realhost); - *realhost = dupstr(loghost); - - colon = host_strrchr(*realhost, ':'); - if (colon) - *colon++ = '\0'; - } - - /* - * Set up TTYOPTS based on config - */ - int ascii_set = conf_get_int(supdup->conf, CONF_supdup_ascii_set); - int more_processing = conf_get_bool(supdup->conf, CONF_supdup_more); - int scrolling = conf_get_bool(supdup->conf, CONF_supdup_scroll); - supdup->ttyopt = - TOERS | - TOMVB | - (ascii_set == SUPDUP_CHARSET_ASCII ? 0 : TOSAI | TOSA1) | - TOMVU | - TOLWR | - TOLID | - TOCID | - TPCBS | - (scrolling ? TOROL : 0) | - (more_processing ? TOMOR : 0) | - TPORS; - - supdup->tcmxh = supdup->term_width - 1; // -1 "..one column is used to indicate line continuation." - supdup->tcmxv = supdup->term_height; - - /* - * Send our configuration words to the server - */ - supdup_send_config(supdup); - - /* - * We next expect a connection message followed by %TDNOP from the server - */ - supdup->state = CONNECTING; - seat_set_trust_status(supdup->seat, false); - - /* Make sure the terminal is in UTF-8 mode. */ - c_write(supdup, (unsigned char *)utf8, strlen(utf8)); - - return NULL; -} - - -static void supdup_free(Backend *be) -{ - Supdup *supdup = container_of(be, Supdup, backend); - - if (supdup->s) - sk_close(supdup->s); - if (supdup->pinger) - pinger_free(supdup->pinger); - conf_free(supdup->conf); - sfree(supdup); -} - -/* -* Reconfigure the Supdup backend. -*/ -static void supdup_reconfig(Backend *be, Conf *conf) -{ - /* Nothing to do; SUPDUP cannot be reconfigured while running. */ -} - -/* -* Called to send data down the Supdup connection. -*/ -static size_t supdup_send(Backend *be, const char *buf, size_t len) -{ - Supdup *supdup = container_of(be, Supdup, backend); - char c; - int i; - - if (supdup->s == NULL) - return 0; - - for (i = 0; i < len; i++) { - if (buf[i] == 034) - supdup->bufsize = sk_write(supdup->s, "\034\034", 2); - else { - c = buf[i] & 0177; - supdup->bufsize = sk_write(supdup->s, &c, 1); - } - } - return supdup->bufsize; -} - -/* -* Called to query the current socket sendability status. -*/ -static size_t supdup_sendbuffer(Backend *be) -{ - Supdup *supdup = container_of(be, Supdup, backend); - return supdup->bufsize; -} - -/* -* Called to set the size of the window from Supdup's POV. -*/ -static void supdup_size(Backend *be, int width, int height) -{ - Supdup *supdup = container_of(be, Supdup, backend); - - supdup->term_width = width; - supdup->term_height = height; - - // - // SUPDUP does not support resizing the terminal after connection - // establishment. - // -} - -/* -* Send Telnet special codes. -*/ -static void supdup_special(Backend *be, SessionSpecialCode code, int arg) -{ -} - -static const SessionSpecial *supdup_get_specials(Backend *be) -{ - return NULL; -} - -static bool supdup_connected(Backend *be) -{ - Supdup *supdup = container_of(be, Supdup, backend); - return supdup->s != NULL; -} - -static bool supdup_sendok(Backend *be) -{ - return 1; -} - -static void supdup_unthrottle(Backend *be, size_t backlog) -{ - Supdup *supdup = container_of(be, Supdup, backend); - sk_set_frozen(supdup->s, backlog > SUPDUP_MAX_BACKLOG); -} - -static bool supdup_ldisc(Backend *be, int option) -{ - /* No support for echoing or local editing. */ - return false; -} - -static void supdup_provide_ldisc(Backend *be, Ldisc *ldisc) -{ -} - -static int supdup_exitcode(Backend *be) -{ - Supdup *supdup = container_of(be, Supdup, backend); - if (supdup->s != NULL) - return -1; /* still connected */ - else if (supdup->closed_on_socket_error) - return INT_MAX; /* a socket error counts as an unclean exit */ - else - /* Supdup doesn't transmit exit codes back to the client */ - return 0; -} - -/* -* cfg_info for Dupdup does nothing at all. -*/ -static int supdup_cfg_info(Backend *be) -{ - return 0; -} - -const BackendVtable supdup_backend = { - .init = supdup_init, - .free = supdup_free, - .reconfig = supdup_reconfig, - .send = supdup_send, - .sendbuffer = supdup_sendbuffer, - .size = supdup_size, - .special = supdup_special, - .get_specials = supdup_get_specials, - .connected = supdup_connected, - .exitcode = supdup_exitcode, - .sendok = supdup_sendok, - .ldisc_option_state = supdup_ldisc, - .provide_ldisc = supdup_provide_ldisc, - .unthrottle = supdup_unthrottle, - .cfg_info = supdup_cfg_info, - .id = "supdup", - .displayname = "SUPDUP", - .protocol = PROT_SUPDUP, - .default_port = 0137, - .flags = BACKEND_RESIZE_FORBIDDEN | BACKEND_NEEDS_TERMINAL, -}; diff --git a/telnet.c b/telnet.c deleted file mode 100644 index 3a60e646..00000000 --- a/telnet.c +++ /dev/null @@ -1,1070 +0,0 @@ -/* - * Telnet backend. - */ - -#include -#include -#include - -#include "putty.h" - -#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 const char *telopt(int opt) -{ -#define telnet_str(x,y) case TELOPT_##x: return #x; - switch (opt) { - TELOPTS(telnet_str) - default: - return ""; - } -#undef telnet_str -} - -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 Telnet; -struct Telnet { - Socket *s; - bool closed_on_socket_error; - - Seat *seat; - LogContext *logctx; - Ldisc *ldisc; - int term_width, term_height; - - int opt_states[NUM_OPTS]; - - bool echoing, editing; - bool activated; - size_t bufsize; - bool in_synch; - int sb_opt; - strbuf *sb_buf; - bool session_started; - - enum { - TOP_LEVEL, SEENIAC, SEENWILL, SEENWONT, SEENDO, SEENDONT, - SEENSB, SUBNEGOT, SUBNEG_IAC, SEENCR - } state; - - Conf *conf; - - Pinger *pinger; - - Plug plug; - Backend backend; -}; - -#define TELNET_MAX_BACKLOG 4096 - -#define SB_DELTA 1024 - -static void c_write(Telnet *telnet, const void *buf, size_t len) -{ - size_t backlog = seat_stdout(telnet->seat, buf, len); - sk_set_frozen(telnet->s, backlog > TELNET_MAX_BACKLOG); -} - -static void log_option(Telnet *telnet, const char *sender, int cmd, int option) -{ - /* - * The strange-looking "" below is there to avoid a - * trigraph - a double question mark followed by > maps to a - * closing brace character! - */ - logeventf(telnet->logctx, "%s:\t%s %s", sender, - (cmd == WILL ? "WILL" : cmd == WONT ? "WONT" : - cmd == DO ? "DO" : cmd == DONT ? "DONT" : ""), - telopt(option)); -} - -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, 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, bool 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_echoedit_update(telnet->ldisc); - - /* 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) - backend_size(&telnet->backend, - 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, true); -} - -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, false); -} - -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, false); - 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, *p, *q; - int var, value, n, bsize; - char *e, *eval, *ekey, *user; - - switch (telnet->sb_opt) { - case TELOPT_TSPEED: - if (telnet->sb_buf->len == 1 && telnet->sb_buf->u[0] == TELQUAL_SEND) { - char *termspeed = conf_get_str(telnet->conf, CONF_termspeed); - b = snewn(20 + strlen(termspeed), unsigned char); - b[0] = IAC; - b[1] = SB; - b[2] = TELOPT_TSPEED; - b[3] = TELQUAL_IS; - strcpy((char *)(b + 4), termspeed); - n = 4 + strlen(termspeed); - b[n] = IAC; - b[n + 1] = SE; - telnet->bufsize = sk_write(telnet->s, b, n + 2); - logevent(telnet->logctx, "server:\tSB TSPEED SEND"); - logeventf(telnet->logctx, "client:\tSB TSPEED IS %s", termspeed); - sfree(b); - } else - logevent(telnet->logctx, "server:\tSB TSPEED "); - break; - case TELOPT_TTYPE: - if (telnet->sb_buf->len == 1 && telnet->sb_buf->u[0] == TELQUAL_SEND) { - char *termtype = conf_get_str(telnet->conf, CONF_termtype); - b = snewn(20 + strlen(termtype), unsigned char); - b[0] = IAC; - b[1] = SB; - b[2] = TELOPT_TTYPE; - b[3] = TELQUAL_IS; - for (n = 0; termtype[n]; n++) - b[n + 4] = (termtype[n] >= 'a' && termtype[n] <= 'z' ? - termtype[n] + 'A' - 'a' : - termtype[n]); - b[n + 4] = IAC; - b[n + 5] = SE; - telnet->bufsize = sk_write(telnet->s, b, n + 6); - b[n + 4] = 0; - logevent(telnet->logctx, "server:\tSB TTYPE SEND"); - logeventf(telnet->logctx, "client:\tSB TTYPE IS %s", b + 4); - sfree(b); - } else - logevent(telnet->logctx, "server:\tSB TTYPE \r\n"); - break; - case TELOPT_OLD_ENVIRON: - case TELOPT_NEW_ENVIRON: - p = telnet->sb_buf->u; - q = p + telnet->sb_buf->len; - if (p < q && *p == TELQUAL_SEND) { - p++; - logeventf(telnet->logctx, "server:\tSB %s SEND", - telopt(telnet->sb_opt)); - if (telnet->sb_opt == TELOPT_OLD_ENVIRON) { - if (conf_get_bool(telnet->conf, CONF_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; - } - bsize = 20; - for (eval = conf_get_str_strs(telnet->conf, CONF_environmt, - NULL, &ekey); - eval != NULL; - eval = conf_get_str_strs(telnet->conf, CONF_environmt, - ekey, &ekey)) - bsize += strlen(ekey) + strlen(eval) + 2; - user = get_remote_username(telnet->conf); - if (user) - bsize += 6 + strlen(user); - - b = snewn(bsize, unsigned char); - b[0] = IAC; - b[1] = SB; - b[2] = telnet->sb_opt; - b[3] = TELQUAL_IS; - n = 4; - for (eval = conf_get_str_strs(telnet->conf, CONF_environmt, - NULL, &ekey); - eval != NULL; - eval = conf_get_str_strs(telnet->conf, CONF_environmt, - ekey, &ekey)) { - b[n++] = var; - for (e = ekey; *e; e++) - b[n++] = *e; - b[n++] = value; - for (e = eval; *e; e++) - b[n++] = *e; - } - if (user) { - b[n++] = var; - b[n++] = 'U'; - b[n++] = 'S'; - b[n++] = 'E'; - b[n++] = 'R'; - b[n++] = value; - for (e = user; *e; e++) - b[n++] = *e; - } - b[n++] = IAC; - b[n++] = SE; - telnet->bufsize = sk_write(telnet->s, b, n); - if (n == 6) { - logeventf(telnet->logctx, "client:\tSB %s IS ", - telopt(telnet->sb_opt)); - } else { - logeventf(telnet->logctx, "client:\tSB %s IS:", - telopt(telnet->sb_opt)); - for (eval = conf_get_str_strs(telnet->conf, CONF_environmt, - NULL, &ekey); - eval != NULL; - eval = conf_get_str_strs(telnet->conf, CONF_environmt, - ekey, &ekey)) { - logeventf(telnet->logctx, "\t%s=%s", ekey, eval); - } - if (user) - logeventf(telnet->logctx, "\tUSER=%s", user); - } - sfree(b); - sfree(user); - } - break; - } -} - -static void do_telnet_read(Telnet *telnet, const char *buf, size_t len) -{ - strbuf *outbuf = strbuf_new_nm(); - - 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) - put_byte(outbuf, 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 = false; -#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 = false; - telnet->state = TOP_LEVEL; - } else { - /* ignore everything else; print it if it's IAC */ - if (c == IAC) { - put_byte(outbuf, 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; - strbuf_clear(telnet->sb_buf); - telnet->state = SUBNEGOT; - break; - case SUBNEGOT: - if (c == IAC) - telnet->state = SUBNEG_IAC; - else { - subneg_addchar: - put_byte(telnet->sb_buf, 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 (outbuf->len >= 4096) { - c_write(telnet, outbuf->u, outbuf->len); - strbuf_clear(outbuf); - } - } - - if (outbuf->len) - c_write(telnet, outbuf->u, outbuf->len); - strbuf_free(outbuf); -} - -static void telnet_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, - const char *error_msg, int error_code) -{ - Telnet *telnet = container_of(plug, Telnet, plug); - backend_socket_log(telnet->seat, telnet->logctx, type, addr, port, - error_msg, error_code, telnet->conf, - telnet->session_started); -} - -static void telnet_closing(Plug *plug, const char *error_msg, int error_code, - bool calling_back) -{ - Telnet *telnet = container_of(plug, Telnet, plug); - - /* - * We don't implement independent EOF in each direction for Telnet - * connections; as soon as we get word that the remote side has - * sent us EOF, we wind up the whole connection. - */ - - if (telnet->s) { - sk_close(telnet->s); - telnet->s = NULL; - if (error_msg) - telnet->closed_on_socket_error = true; - seat_notify_remote_exit(telnet->seat); - } - if (error_msg) { - logevent(telnet->logctx, error_msg); - seat_connection_fatal(telnet->seat, "%s", error_msg); - } - /* Otherwise, the remote side closed the connection normally. */ -} - -static void telnet_receive( - Plug *plug, int urgent, const char *data, size_t len) -{ - Telnet *telnet = container_of(plug, Telnet, plug); - if (urgent) - telnet->in_synch = true; - telnet->session_started = true; - do_telnet_read(telnet, data, len); -} - -static void telnet_sent(Plug *plug, size_t bufsize) -{ - Telnet *telnet = container_of(plug, Telnet, plug); - telnet->bufsize = bufsize; -} - -static const PlugVtable Telnet_plugvt = { - .log = telnet_log, - .closing = telnet_closing, - .receive = telnet_receive, - .sent = telnet_sent, -}; - -/* - * 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 char *telnet_init(const BackendVtable *vt, Seat *seat, - Backend **backend_handle, LogContext *logctx, - Conf *conf, const char *host, int port, - char **realhost, bool nodelay, bool keepalive) -{ - SockAddr *addr; - const char *err; - Telnet *telnet; - char *loghost; - int addressfamily; - - /* No local authentication phase in this protocol */ - seat_set_trust_status(seat, false); - - telnet = snew(Telnet); - telnet->plug.vt = &Telnet_plugvt; - telnet->backend.vt = vt; - telnet->conf = conf_copy(conf); - telnet->s = NULL; - telnet->closed_on_socket_error = false; - telnet->echoing = true; - telnet->editing = true; - telnet->activated = false; - telnet->sb_buf = strbuf_new(); - telnet->seat = seat; - telnet->logctx = logctx; - telnet->term_width = conf_get_int(telnet->conf, CONF_width); - telnet->term_height = conf_get_int(telnet->conf, CONF_height); - telnet->state = TOP_LEVEL; - telnet->ldisc = NULL; - telnet->pinger = NULL; - telnet->session_started = true; - *backend_handle = &telnet->backend; - - /* - * Try to find host. - */ - addressfamily = conf_get_int(telnet->conf, CONF_addressfamily); - addr = name_lookup(host, port, realhost, telnet->conf, addressfamily, - telnet->logctx, "Telnet connection"); - if ((err = sk_addr_error(addr)) != NULL) { - sk_addr_free(addr); - return dupstr(err); - } - - if (port < 0) - port = 23; /* default telnet port */ - - /* - * Open socket. - */ - telnet->s = new_connection(addr, *realhost, port, false, true, nodelay, - keepalive, &telnet->plug, telnet->conf); - if ((err = sk_socket_error(telnet->s)) != NULL) - return dupstr(err); - - telnet->pinger = pinger_new(telnet->conf, &telnet->backend); - - /* - * Initialise option states. - */ - if (conf_get_bool(telnet->conf, CONF_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. - */ - seat_update_specials_menu(telnet->seat); - - /* - * loghost overrides realhost, if specified. - */ - loghost = conf_get_str(telnet->conf, CONF_loghost); - if (*loghost) { - char *colon; - - sfree(*realhost); - *realhost = dupstr(loghost); - - colon = host_strrchr(*realhost, ':'); - if (colon) - *colon++ = '\0'; - } - - return NULL; -} - -static void telnet_free(Backend *be) -{ - Telnet *telnet = container_of(be, Telnet, backend); - - strbuf_free(telnet->sb_buf); - if (telnet->s) - sk_close(telnet->s); - if (telnet->pinger) - pinger_free(telnet->pinger); - conf_free(telnet->conf); - sfree(telnet); -} -/* - * Reconfigure the Telnet backend. There's no immediate action - * necessary, in this backend: we just save the fresh config for - * any subsequent negotiations. - */ -static void telnet_reconfig(Backend *be, Conf *conf) -{ - Telnet *telnet = container_of(be, Telnet, backend); - pinger_reconfig(telnet->pinger, telnet->conf, conf); - conf_free(telnet->conf); - telnet->conf = conf_copy(conf); -} - -/* - * Called to send data down the Telnet connection. - */ -static size_t telnet_send(Backend *be, const char *buf, size_t len) -{ - Telnet *telnet = container_of(be, Telnet, backend); - 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, q, p - q); - - while (p < end && !iswritable(*p)) { - telnet->bufsize = - sk_write(telnet->s, *p == IAC ? iac : cr, 2); - p++; - } - } - - return telnet->bufsize; -} - -/* - * Called to query the current socket sendability status. - */ -static size_t telnet_sendbuffer(Backend *be) -{ - Telnet *telnet = container_of(be, Telnet, backend); - return telnet->bufsize; -} - -/* - * Called to set the size of the window from Telnet's POV. - */ -static void telnet_size(Backend *be, int width, int height) -{ - Telnet *telnet = container_of(be, Telnet, backend); - unsigned char b[24]; - int n; - - 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, b, n); - logeventf(telnet->logctx, "client:\tSB NAWS %d,%d", - telnet->term_width, telnet->term_height); -} - -/* - * Send Telnet special codes. - */ -static void telnet_special(Backend *be, SessionSpecialCode code, int arg) -{ - Telnet *telnet = container_of(be, Telnet, backend); - unsigned char b[2]; - - if (telnet->s == NULL) - return; - - b[0] = IAC; - switch (code) { - case SS_AYT: - b[1] = AYT; - telnet->bufsize = sk_write(telnet->s, b, 2); - break; - case SS_BRK: - b[1] = BREAK; - telnet->bufsize = sk_write(telnet->s, b, 2); - break; - case SS_EC: - b[1] = EC; - telnet->bufsize = sk_write(telnet->s, b, 2); - break; - case SS_EL: - b[1] = EL; - telnet->bufsize = sk_write(telnet->s, b, 2); - break; - case SS_GA: - b[1] = GA; - telnet->bufsize = sk_write(telnet->s, b, 2); - break; - case SS_NOP: - b[1] = NOP; - telnet->bufsize = sk_write(telnet->s, b, 2); - break; - case SS_ABORT: - b[1] = ABORT; - telnet->bufsize = sk_write(telnet->s, b, 2); - break; - case SS_AO: - b[1] = AO; - telnet->bufsize = sk_write(telnet->s, b, 2); - break; - case SS_IP: - b[1] = IP; - telnet->bufsize = sk_write(telnet->s, b, 2); - break; - case SS_SUSP: - b[1] = SUSP; - telnet->bufsize = sk_write(telnet->s, b, 2); - break; - case SS_EOR: - b[1] = EOR; - telnet->bufsize = sk_write(telnet->s, b, 2); - break; - case SS_EOF: - b[1] = xEOF; - telnet->bufsize = sk_write(telnet->s, b, 2); - break; - case SS_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 SS_SYNCH: - b[1] = DM; - telnet->bufsize = sk_write(telnet->s, b, 1); - telnet->bufsize = sk_write_oob(telnet->s, b + 1, 1); - break; - case SS_PING: - if (telnet->opt_states[o_they_sga.index] == ACTIVE) { - b[1] = NOP; - telnet->bufsize = sk_write(telnet->s, b, 2); - } - break; - default: - break; /* never heard of it */ - } -} - -static const SessionSpecial *telnet_get_specials(Backend *be) -{ - static const SessionSpecial specials[] = { - {"Are You There", SS_AYT}, - {"Break", SS_BRK}, - {"Synch", SS_SYNCH}, - {"Erase Character", SS_EC}, - {"Erase Line", SS_EL}, - {"Go Ahead", SS_GA}, - {"No Operation", SS_NOP}, - {NULL, SS_SEP}, - {"Abort Process", SS_ABORT}, - {"Abort Output", SS_AO}, - {"Interrupt Process", SS_IP}, - {"Suspend Process", SS_SUSP}, - {NULL, SS_SEP}, - {"End Of Record", SS_EOR}, - {"End Of File", SS_EOF}, - {NULL, SS_EXITMENU} - }; - return specials; -} - -static bool telnet_connected(Backend *be) -{ - Telnet *telnet = container_of(be, Telnet, backend); - return telnet->s != NULL; -} - -static bool telnet_sendok(Backend *be) -{ - /* Telnet *telnet = container_of(be, Telnet, backend); */ - return true; -} - -static void telnet_unthrottle(Backend *be, size_t backlog) -{ - Telnet *telnet = container_of(be, Telnet, backend); - sk_set_frozen(telnet->s, backlog > TELNET_MAX_BACKLOG); -} - -static bool telnet_ldisc(Backend *be, int option) -{ - Telnet *telnet = container_of(be, Telnet, backend); - if (option == LD_ECHO) - return telnet->echoing; - if (option == LD_EDIT) - return telnet->editing; - return false; -} - -static void telnet_provide_ldisc(Backend *be, Ldisc *ldisc) -{ - Telnet *telnet = container_of(be, Telnet, backend); - telnet->ldisc = ldisc; -} - -static int telnet_exitcode(Backend *be) -{ - Telnet *telnet = container_of(be, Telnet, backend); - if (telnet->s != NULL) - return -1; /* still connected */ - else if (telnet->closed_on_socket_error) - return INT_MAX; /* a socket error counts as an unclean exit */ - else - /* Telnet doesn't transmit exit codes back to the client */ - return 0; -} - -/* - * cfg_info for Telnet does nothing at all. - */ -static int telnet_cfg_info(Backend *be) -{ - return 0; -} - -const BackendVtable telnet_backend = { - .init = telnet_init, - .free = telnet_free, - .reconfig = telnet_reconfig, - .send = telnet_send, - .sendbuffer = telnet_sendbuffer, - .size = telnet_size, - .special = telnet_special, - .get_specials = telnet_get_specials, - .connected = telnet_connected, - .exitcode = telnet_exitcode, - .sendok = telnet_sendok, - .ldisc_option_state = telnet_ldisc, - .provide_ldisc = telnet_provide_ldisc, - .unthrottle = telnet_unthrottle, - .cfg_info = telnet_cfg_info, - .id = "telnet", - .displayname = "Telnet", - .protocol = PROT_TELNET, - .default_port = 23, -}; diff --git a/testback.c b/testback.c deleted file mode 100644 index 173786ed..00000000 --- a/testback.c +++ /dev/null @@ -1,214 +0,0 @@ -/* - * 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 -#include - -#include "putty.h" - -static char *null_init(const BackendVtable *, Seat *, Backend **, LogContext *, - Conf *, const char *, int, char **, bool, bool); -static char *loop_init(const BackendVtable *, Seat *, Backend **, LogContext *, - Conf *, const char *, int, char **, bool, bool); -static void null_free(Backend *); -static void loop_free(Backend *); -static void null_reconfig(Backend *, Conf *); -static size_t null_send(Backend *, const char *, size_t); -static size_t loop_send(Backend *, const char *, size_t); -static size_t null_sendbuffer(Backend *); -static void null_size(Backend *, int, int); -static void null_special(Backend *, SessionSpecialCode, int); -static const SessionSpecial *null_get_specials(Backend *); -static int null_connected(Backend *); -static int null_exitcode(Backend *); -static int null_sendok(Backend *); -static int null_ldisc(Backend *, int); -static void null_provide_ldisc(Backend *, Ldisc *); -static void null_unthrottle(Backend *, size_t); -static int null_cfg_info(Backend *); - -const BackendVtable null_backend = { - .init = null_init, - .free = null_free, - .reconfig = null_reconfig, - .send = null_send, - .sendbuffer = null_sendbuffer, - .size = null_size, - .special = null_special, - .get_specials = null_get_specials, - .connected = null_connected, - .exitcode = null_exitcode, - .sendok = null_sendok, - .ldisc_option_state = null_ldisc, - .provide_ldisc = null_provide_ldisc, - .unthrottle = null_unthrottle, - .cfg_info = null_cfg_info, - .id = "null", - .displayname = "null", - .protocol = -1, - .default_port = 0, -}; - -const BackendVtable loop_backend = { - .init = loop_init, - .free = loop_free, - .reconfig = null_reconfig, - .send = loop_send, - .sendbuffer = null_sendbuffer, - .size = null_size, - .special = null_special, - .get_specials = null_get_specials, - .connected = null_connected, - .exitcode = null_exitcode, - .sendok = null_sendok, - .ldisc_option_state = null_ldisc, - .provide_ldisc = null_provide_ldisc, - .unthrottle = null_unthrottle, - .cfg_info = null_cfg_info, - .id = "loop", - .displayname = "loop", - .protocol = -1, - .default_port = 0, -}; - -struct loop_state { - Seat *seat; - Backend backend; -}; - -static char *null_init(const BackendVtable *vt, Seat *seat, - Backend **backend_handle, LogContext *logctx, - Conf *conf, const char *host, int port, - char **realhost, bool nodelay, bool keepalive) { - /* No local authentication phase in this protocol */ - seat_set_trust_status(seat, false); - - *backend_handle = NULL; - return NULL; -} - -static char *loop_init(const BackendVtable *vt, Seat *seat, - Backend **backend_handle, LogContext *logctx, - Conf *conf, const char *host, int port, - char **realhost, bool nodelay, bool keepalive) { - struct loop_state *st = snew(struct loop_state); - - /* No local authentication phase in this protocol */ - seat_set_trust_status(seat, false); - - st->seat = seat; - *backend_handle = &st->backend; - return NULL; -} - -static void null_free(Backend *be) -{ - -} - -static void loop_free(Backend *be) -{ - struct loop_state *st = container_of(be, struct loop_state, backend); - - sfree(st); -} - -static void null_reconfig(Backend *be, Conf *conf) { - -} - -static size_t null_send(Backend *be, const char *buf, size_t len) { - - return 0; -} - -static size_t loop_send(Backend *be, const char *buf, size_t len) { - struct loop_state *st = container_of(be, struct loop_state, backend); - - return seat_output(st->seat, 0, buf, len); -} - -static size_t null_sendbuffer(Backend *be) { - - return 0; -} - -static void null_size(Backend *be, int width, int height) { - -} - -static void null_special(Backend *be, SessionSpecialCode code, int arg) { - -} - -static const SessionSpecial *null_get_specials (Backend *be) { - - return NULL; -} - -static int null_connected(Backend *be) { - - return 0; -} - -static int null_exitcode(Backend *be) { - - return 0; -} - -static int null_sendok(Backend *be) { - - return 1; -} - -static void null_unthrottle(Backend *be, size_t backlog) { - -} - -static int null_ldisc(Backend *be, int option) { - - return 0; -} - -static void null_provide_ldisc (Backend *be, Ldisc *ldisc) { - -} - -static int null_cfg_info(Backend *be) -{ - return 0; -} - - -/* - * Emacs magic: - * Local Variables: - * c-file-style: "simon" - * End: - */ -- cgit v1.2.3 From 1c039d0a7b31c181a2d87021a98e85b75e5b1ee8 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 22 Apr 2021 18:28:35 +0100 Subject: Spelling: standardise on "DSA", not "DSS". This code base has always been a bit confused about which spelling it likes to use to refer to that signature algorithm. The SSH protocol id is "ssh-dss". But everyone I know refers to it as the Digital Signature _Algorithm_, not the Digital Signature _Standard_. When I moved everything down into the crypto subdir, I took the opportunity to rename sshdss.c to dsa.c. Now I'm doing the rest of the job: all internal identifiers and code comments refer to DSA, and the spelling "dss" only survives in externally visible identifiers that have to remain constant. (Such identifiers include the SSH protocol id, and also the string id used to identify the key type in PuTTY's own host key cache. We can't change the latter without causing everyone a backwards-compatibility headache, and if we _did_ ever decide to do that, we'd surely want to do a much more thorough job of making the cache format more sensible!) --- cmdgen.c | 6 +- crypto/dsa.c | 256 +++++++++++++++++++++++++++--------------------------- crypto/ecc-ssh.c | 2 +- import.c | 12 +-- keygen/dsa.c | 6 +- ssh.h | 6 +- ssh/transport2.h | 2 +- sshkeygen.h | 2 +- sshpubk.c | 2 +- testcrypt.c | 8 +- windows/winpgen.c | 10 +-- windows/winpgnt.c | 2 +- 12 files changed, 157 insertions(+), 157 deletions(-) diff --git a/cmdgen.c b/cmdgen.c index 920317f5..3301b410 100644 --- a/cmdgen.c +++ b/cmdgen.c @@ -878,10 +878,10 @@ int main(int argc, char **argv) PrimeGenerationContext *pgc = primegen_new_context(primegen); if (keytype == DSA) { - struct dss_key *dsskey = snew(struct dss_key); - dsa_generate(dsskey, bits, pgc, &cmdgen_progress); + struct dsa_key *dsakey = snew(struct dsa_key); + dsa_generate(dsakey, bits, pgc, &cmdgen_progress); ssh2key = snew(ssh2_userkey); - ssh2key->key = &dsskey->sshk; + ssh2key->key = &dsakey->sshk; ssh1key = NULL; } else if (keytype == ECDSA) { struct ecdsa_key *ek = snew(struct ecdsa_key); diff --git a/crypto/dsa.c b/crypto/dsa.c index 9976648e..43b51c8c 100644 --- a/crypto/dsa.c +++ b/crypto/dsa.c @@ -10,49 +10,49 @@ #include "mpint.h" #include "misc.h" -static void dss_freekey(ssh_key *key); /* forward reference */ +static void dsa_freekey(ssh_key *key); /* forward reference */ -static ssh_key *dss_new_pub(const ssh_keyalg *self, ptrlen data) +static ssh_key *dsa_new_pub(const ssh_keyalg *self, ptrlen data) { BinarySource src[1]; - struct dss_key *dss; + struct dsa_key *dsa; BinarySource_BARE_INIT_PL(src, data); if (!ptrlen_eq_string(get_string(src), "ssh-dss")) return NULL; - dss = snew(struct dss_key); - dss->sshk.vt = &ssh_dss; - dss->p = get_mp_ssh2(src); - dss->q = get_mp_ssh2(src); - dss->g = get_mp_ssh2(src); - dss->y = get_mp_ssh2(src); - dss->x = NULL; + dsa = snew(struct dsa_key); + dsa->sshk.vt = &ssh_dsa; + dsa->p = get_mp_ssh2(src); + dsa->q = get_mp_ssh2(src); + dsa->g = get_mp_ssh2(src); + dsa->y = get_mp_ssh2(src); + dsa->x = NULL; if (get_err(src) || - mp_eq_integer(dss->p, 0) || mp_eq_integer(dss->q, 0)) { + mp_eq_integer(dsa->p, 0) || mp_eq_integer(dsa->q, 0)) { /* Invalid key. */ - dss_freekey(&dss->sshk); + dsa_freekey(&dsa->sshk); return NULL; } - return &dss->sshk; + return &dsa->sshk; } -static void dss_freekey(ssh_key *key) +static void dsa_freekey(ssh_key *key) { - struct dss_key *dss = container_of(key, struct dss_key, sshk); - if (dss->p) - mp_free(dss->p); - if (dss->q) - mp_free(dss->q); - if (dss->g) - mp_free(dss->g); - if (dss->y) - mp_free(dss->y); - if (dss->x) - mp_free(dss->x); - sfree(dss); + struct dsa_key *dsa = container_of(key, struct dsa_key, sshk); + if (dsa->p) + mp_free(dsa->p); + if (dsa->q) + mp_free(dsa->q); + if (dsa->g) + mp_free(dsa->g); + if (dsa->y) + mp_free(dsa->y); + if (dsa->x) + mp_free(dsa->x); + sfree(dsa); } static void append_hex_to_strbuf(strbuf *sb, mp_int *x) @@ -67,55 +67,55 @@ static void append_hex_to_strbuf(strbuf *sb, mp_int *x) sfree(hex); } -static char *dss_cache_str(ssh_key *key) +static char *dsa_cache_str(ssh_key *key) { - struct dss_key *dss = container_of(key, struct dss_key, sshk); + struct dsa_key *dsa = container_of(key, struct dsa_key, sshk); strbuf *sb = strbuf_new(); - if (!dss->p) { + if (!dsa->p) { strbuf_free(sb); return NULL; } - append_hex_to_strbuf(sb, dss->p); - append_hex_to_strbuf(sb, dss->q); - append_hex_to_strbuf(sb, dss->g); - append_hex_to_strbuf(sb, dss->y); + append_hex_to_strbuf(sb, dsa->p); + append_hex_to_strbuf(sb, dsa->q); + append_hex_to_strbuf(sb, dsa->g); + append_hex_to_strbuf(sb, dsa->y); return strbuf_to_str(sb); } -static key_components *dss_components(ssh_key *key) +static key_components *dsa_components(ssh_key *key) { - struct dss_key *dss = container_of(key, struct dss_key, sshk); + struct dsa_key *dsa = container_of(key, struct dsa_key, sshk); key_components *kc = key_components_new(); key_components_add_text(kc, "key_type", "DSA"); - assert(dss->p); - key_components_add_mp(kc, "p", dss->p); - key_components_add_mp(kc, "q", dss->q); - key_components_add_mp(kc, "g", dss->g); - key_components_add_mp(kc, "public_y", dss->y); - if (dss->x) - key_components_add_mp(kc, "private_x", dss->x); + assert(dsa->p); + key_components_add_mp(kc, "p", dsa->p); + key_components_add_mp(kc, "q", dsa->q); + key_components_add_mp(kc, "g", dsa->g); + key_components_add_mp(kc, "public_y", dsa->y); + if (dsa->x) + key_components_add_mp(kc, "private_x", dsa->x); return kc; } -static char *dss_invalid(ssh_key *key, unsigned flags) +static char *dsa_invalid(ssh_key *key, unsigned flags) { /* No validity criterion will stop us from using a DSA key at all */ return NULL; } -static bool dss_verify(ssh_key *key, ptrlen sig, ptrlen data) +static bool dsa_verify(ssh_key *key, ptrlen sig, ptrlen data) { - struct dss_key *dss = container_of(key, struct dss_key, sshk); + struct dsa_key *dsa = container_of(key, struct dsa_key, sshk); BinarySource src[1]; unsigned char hash[20]; bool toret; - if (!dss->p) + if (!dsa->p) return false; BinarySource_BARE_INIT_PL(src, sig); @@ -155,8 +155,8 @@ static bool dss_verify(ssh_key *key, ptrlen sig, ptrlen data) unsigned invalid = 0; invalid |= mp_eq_integer(r, 0); invalid |= mp_eq_integer(s, 0); - invalid |= mp_cmp_hs(r, dss->q); - invalid |= mp_cmp_hs(s, dss->q); + invalid |= mp_cmp_hs(r, dsa->q); + invalid |= mp_cmp_hs(s, dsa->q); if (invalid) { mp_free(r); mp_free(s); @@ -166,7 +166,7 @@ static bool dss_verify(ssh_key *key, ptrlen sig, ptrlen data) /* * Step 1. w <- s^-1 mod q. */ - mp_int *w = mp_invert(s, dss->q); + mp_int *w = mp_invert(s, dsa->q); if (!w) { mp_free(r); mp_free(s); @@ -178,20 +178,20 @@ static bool dss_verify(ssh_key *key, ptrlen sig, ptrlen data) */ hash_simple(&ssh_sha1, data, hash); mp_int *sha = mp_from_bytes_be(make_ptrlen(hash, 20)); - mp_int *u1 = mp_modmul(sha, w, dss->q); + mp_int *u1 = mp_modmul(sha, w, dsa->q); /* * Step 3. u2 <- r * w mod q. */ - mp_int *u2 = mp_modmul(r, w, dss->q); + mp_int *u2 = mp_modmul(r, w, dsa->q); /* * Step 4. v <- (g^u1 * y^u2 mod p) mod q. */ - mp_int *gu1p = mp_modpow(dss->g, u1, dss->p); - mp_int *yu2p = mp_modpow(dss->y, u2, dss->p); - mp_int *gu1yu2p = mp_modmul(gu1p, yu2p, dss->p); - mp_int *v = mp_mod(gu1yu2p, dss->q); + mp_int *gu1p = mp_modpow(dsa->g, u1, dsa->p); + mp_int *yu2p = mp_modpow(dsa->y, u2, dsa->p); + mp_int *gu1yu2p = mp_modmul(gu1p, yu2p, dsa->p); + mp_int *v = mp_mod(gu1yu2p, dsa->q); /* * Step 5. v should now be equal to r. @@ -213,57 +213,57 @@ static bool dss_verify(ssh_key *key, ptrlen sig, ptrlen data) return toret; } -static void dss_public_blob(ssh_key *key, BinarySink *bs) +static void dsa_public_blob(ssh_key *key, BinarySink *bs) { - struct dss_key *dss = container_of(key, struct dss_key, sshk); + struct dsa_key *dsa = container_of(key, struct dsa_key, sshk); put_stringz(bs, "ssh-dss"); - put_mp_ssh2(bs, dss->p); - put_mp_ssh2(bs, dss->q); - put_mp_ssh2(bs, dss->g); - put_mp_ssh2(bs, dss->y); + put_mp_ssh2(bs, dsa->p); + put_mp_ssh2(bs, dsa->q); + put_mp_ssh2(bs, dsa->g); + put_mp_ssh2(bs, dsa->y); } -static void dss_private_blob(ssh_key *key, BinarySink *bs) +static void dsa_private_blob(ssh_key *key, BinarySink *bs) { - struct dss_key *dss = container_of(key, struct dss_key, sshk); + struct dsa_key *dsa = container_of(key, struct dsa_key, sshk); - put_mp_ssh2(bs, dss->x); + put_mp_ssh2(bs, dsa->x); } -static ssh_key *dss_new_priv(const ssh_keyalg *self, ptrlen pub, ptrlen priv) +static ssh_key *dsa_new_priv(const ssh_keyalg *self, ptrlen pub, ptrlen priv) { BinarySource src[1]; ssh_key *sshk; - struct dss_key *dss; + struct dsa_key *dsa; ptrlen hash; unsigned char digest[20]; mp_int *ytest; - sshk = dss_new_pub(self, pub); + sshk = dsa_new_pub(self, pub); if (!sshk) return NULL; - dss = container_of(sshk, struct dss_key, sshk); + dsa = container_of(sshk, struct dsa_key, sshk); BinarySource_BARE_INIT_PL(src, priv); - dss->x = get_mp_ssh2(src); + dsa->x = get_mp_ssh2(src); if (get_err(src)) { - dss_freekey(&dss->sshk); + dsa_freekey(&dsa->sshk); return NULL; } /* - * Check the obsolete hash in the old DSS key format. + * Check the obsolete hash in the old DSA key format. */ hash = get_string(src); if (hash.len == 20) { ssh_hash *h = ssh_hash_new(&ssh_sha1); - put_mp_ssh2(h, dss->p); - put_mp_ssh2(h, dss->q); - put_mp_ssh2(h, dss->g); + put_mp_ssh2(h, dsa->p); + put_mp_ssh2(h, dsa->q); + put_mp_ssh2(h, dsa->g); ssh_hash_final(h, digest); if (!smemeq(hash.ptr, digest, 20)) { - dss_freekey(&dss->sshk); + dsa_freekey(&dsa->sshk); return NULL; } } @@ -271,75 +271,75 @@ static ssh_key *dss_new_priv(const ssh_keyalg *self, ptrlen pub, ptrlen priv) /* * Now ensure g^x mod p really is y. */ - ytest = mp_modpow(dss->g, dss->x, dss->p); - if (!mp_cmp_eq(ytest, dss->y)) { + ytest = mp_modpow(dsa->g, dsa->x, dsa->p); + if (!mp_cmp_eq(ytest, dsa->y)) { mp_free(ytest); - dss_freekey(&dss->sshk); + dsa_freekey(&dsa->sshk); return NULL; } mp_free(ytest); - return &dss->sshk; + return &dsa->sshk; } -static ssh_key *dss_new_priv_openssh(const ssh_keyalg *self, +static ssh_key *dsa_new_priv_openssh(const ssh_keyalg *self, BinarySource *src) { - struct dss_key *dss; + struct dsa_key *dsa; - dss = snew(struct dss_key); - dss->sshk.vt = &ssh_dss; + dsa = snew(struct dsa_key); + dsa->sshk.vt = &ssh_dsa; - dss->p = get_mp_ssh2(src); - dss->q = get_mp_ssh2(src); - dss->g = get_mp_ssh2(src); - dss->y = get_mp_ssh2(src); - dss->x = get_mp_ssh2(src); + dsa->p = get_mp_ssh2(src); + dsa->q = get_mp_ssh2(src); + dsa->g = get_mp_ssh2(src); + dsa->y = get_mp_ssh2(src); + dsa->x = get_mp_ssh2(src); if (get_err(src) || - mp_eq_integer(dss->q, 0) || mp_eq_integer(dss->p, 0)) { + mp_eq_integer(dsa->q, 0) || mp_eq_integer(dsa->p, 0)) { /* Invalid key. */ - dss_freekey(&dss->sshk); + dsa_freekey(&dsa->sshk); return NULL; } - return &dss->sshk; + return &dsa->sshk; } -static void dss_openssh_blob(ssh_key *key, BinarySink *bs) +static void dsa_openssh_blob(ssh_key *key, BinarySink *bs) { - struct dss_key *dss = container_of(key, struct dss_key, sshk); + struct dsa_key *dsa = container_of(key, struct dsa_key, sshk); - put_mp_ssh2(bs, dss->p); - put_mp_ssh2(bs, dss->q); - put_mp_ssh2(bs, dss->g); - put_mp_ssh2(bs, dss->y); - put_mp_ssh2(bs, dss->x); + put_mp_ssh2(bs, dsa->p); + put_mp_ssh2(bs, dsa->q); + put_mp_ssh2(bs, dsa->g); + put_mp_ssh2(bs, dsa->y); + put_mp_ssh2(bs, dsa->x); } -static int dss_pubkey_bits(const ssh_keyalg *self, ptrlen pub) +static int dsa_pubkey_bits(const ssh_keyalg *self, ptrlen pub) { ssh_key *sshk; - struct dss_key *dss; + struct dsa_key *dsa; int ret; - sshk = dss_new_pub(self, pub); + sshk = dsa_new_pub(self, pub); if (!sshk) return -1; - dss = container_of(sshk, struct dss_key, sshk); - ret = mp_get_nbits(dss->p); - dss_freekey(&dss->sshk); + dsa = container_of(sshk, struct dsa_key, sshk); + ret = mp_get_nbits(dsa->p); + dsa_freekey(&dsa->sshk); return ret; } -mp_int *dss_gen_k(const char *id_string, mp_int *modulus, +mp_int *dsa_gen_k(const char *id_string, mp_int *modulus, mp_int *private_key, unsigned char *digest, int digest_len) { /* - * The basic DSS signing algorithm is: + * The basic DSA signing algorithm is: * * - invent a random k between 1 and q-1 (exclusive). * - Compute r = (g^k mod p) mod q. @@ -445,29 +445,29 @@ mp_int *dss_gen_k(const char *id_string, mp_int *modulus, return k; } -static void dss_sign(ssh_key *key, ptrlen data, unsigned flags, BinarySink *bs) +static void dsa_sign(ssh_key *key, ptrlen data, unsigned flags, BinarySink *bs) { - struct dss_key *dss = container_of(key, struct dss_key, sshk); + struct dsa_key *dsa = container_of(key, struct dsa_key, sshk); unsigned char digest[20]; int i; hash_simple(&ssh_sha1, data, digest); - mp_int *k = dss_gen_k("DSA deterministic k generator", dss->q, dss->x, + mp_int *k = dsa_gen_k("DSA deterministic k generator", dsa->q, dsa->x, digest, sizeof(digest)); - mp_int *kinv = mp_invert(k, dss->q); /* k^-1 mod q */ + mp_int *kinv = mp_invert(k, dsa->q); /* k^-1 mod q */ /* * Now we have k, so just go ahead and compute the signature. */ - mp_int *gkp = mp_modpow(dss->g, k, dss->p); /* g^k mod p */ - mp_int *r = mp_mod(gkp, dss->q); /* r = (g^k mod p) mod q */ + mp_int *gkp = mp_modpow(dsa->g, k, dsa->p); /* g^k mod p */ + mp_int *r = mp_mod(gkp, dsa->q); /* r = (g^k mod p) mod q */ mp_free(gkp); mp_int *hash = mp_from_bytes_be(make_ptrlen(digest, 20)); - mp_int *xr = mp_mul(dss->x, r); + mp_int *xr = mp_mul(dsa->x, r); mp_int *hxr = mp_add(xr, hash); /* hash + x*r */ - mp_int *s = mp_modmul(kinv, hxr, dss->q); /* s = k^-1 * (hash+x*r) mod q */ + mp_int *s = mp_modmul(kinv, hxr, dsa->q); /* s = k^-1 * (hash+x*r) mod q */ mp_free(hxr); mp_free(xr); mp_free(kinv); @@ -484,20 +484,20 @@ static void dss_sign(ssh_key *key, ptrlen data, unsigned flags, BinarySink *bs) mp_free(s); } -const ssh_keyalg ssh_dss = { - .new_pub = dss_new_pub, - .new_priv = dss_new_priv, - .new_priv_openssh = dss_new_priv_openssh, - .freekey = dss_freekey, - .invalid = dss_invalid, - .sign = dss_sign, - .verify = dss_verify, - .public_blob = dss_public_blob, - .private_blob = dss_private_blob, - .openssh_blob = dss_openssh_blob, - .cache_str = dss_cache_str, - .components = dss_components, - .pubkey_bits = dss_pubkey_bits, +const ssh_keyalg ssh_dsa = { + .new_pub = dsa_new_pub, + .new_priv = dsa_new_priv, + .new_priv_openssh = dsa_new_priv_openssh, + .freekey = dsa_freekey, + .invalid = dsa_invalid, + .sign = dsa_sign, + .verify = dsa_verify, + .public_blob = dsa_public_blob, + .private_blob = dsa_private_blob, + .openssh_blob = dsa_openssh_blob, + .cache_str = dsa_cache_str, + .components = dsa_components, + .pubkey_bits = dsa_pubkey_bits, .ssh_id = "ssh-dss", .cache_id = "dss", }; diff --git a/crypto/ecc-ssh.c b/crypto/ecc-ssh.c index c227f30f..d985866d 100644 --- a/crypto/ecc-ssh.c +++ b/crypto/ecc-ssh.c @@ -1117,7 +1117,7 @@ static void ecdsa_sign(ssh_key *key, ptrlen data, { unsigned char digest[20]; hash_simple(&ssh_sha1, data, digest); - k = dss_gen_k( + k = dsa_gen_k( "ECDSA deterministic k generator", ek->curve->w.G_order, ek->privateKey, digest, sizeof(digest)); } diff --git a/import.c b/import.c index 553fa750..ccff2c14 100644 --- a/import.c +++ b/import.c @@ -775,7 +775,7 @@ static ssh2_userkey *openssh_pem_read( */ assert(privptr > 0); /* should have bombed by now if not */ retkey = snew(ssh2_userkey); - alg = (key->keytype == OP_RSA ? &ssh_rsa : &ssh_dss); + alg = (key->keytype == OP_RSA ? &ssh_rsa : &ssh_dsa); retkey->key = ssh_key_new_priv( alg, make_ptrlen(blob->u, privptr), make_ptrlen(blob->u+privptr, blob->len-privptr)); @@ -841,11 +841,11 @@ static bool openssh_pem_write( * line. */ if (ssh_key_alg(key->key) == &ssh_rsa || - ssh_key_alg(key->key) == &ssh_dss) { + ssh_key_alg(key->key) == &ssh_dsa) { strbuf *seq; /* - * The RSA and DSS handlers share some code because the two + * The RSA and DSA handlers share some code because the two * key types have very similar ASN.1 representations, as a * plain SEQUENCE of big integers. So we set up a list of * bignums per key type and then construct the actual blob in @@ -1634,7 +1634,7 @@ static bool openssh_auto_write( * assume that anything not in that fixed list is newer, and hence * will use the new format. */ - if (ssh_key_alg(key->key) == &ssh_dss || + if (ssh_key_alg(key->key) == &ssh_dsa || ssh_key_alg(key->key) == &ssh_rsa || ssh_key_alg(key->key) == &ssh_ecdsa_nistp256 || ssh_key_alg(key->key) == &ssh_ecdsa_nistp384 || @@ -2111,7 +2111,7 @@ static ssh2_userkey *sshcom_read( goto error; } - alg = &ssh_dss; + alg = &ssh_dsa; put_stringz(blob, "ssh-dss"); put_mp_ssh2_from_string(blob, p); put_mp_ssh2_from_string(blob, q); @@ -2202,7 +2202,7 @@ static bool sshcom_write( nnumbers = 6; initial_zero = false; type = "if-modn{sign{rsa-pkcs1-sha1},encrypt{rsa-pkcs1v2-oaep}}"; - } else if (ssh_key_alg(key->key) == &ssh_dss) { + } else if (ssh_key_alg(key->key) == &ssh_dsa) { ptrlen p, q, g, y, x; /* diff --git a/keygen/dsa.c b/keygen/dsa.c index 3b527256..a7ea4f53 100644 --- a/keygen/dsa.c +++ b/keygen/dsa.c @@ -1,5 +1,5 @@ /* - * DSS key generation. + * DSA key generation. */ #include "misc.h" @@ -7,7 +7,7 @@ #include "sshkeygen.h" #include "mpint.h" -int dsa_generate(struct dss_key *key, int bits, PrimeGenerationContext *pgc, +int dsa_generate(struct dsa_key *key, int bits, PrimeGenerationContext *pgc, ProgressReceiver *prog) { /* @@ -91,7 +91,7 @@ int dsa_generate(struct dss_key *key, int bits, PrimeGenerationContext *pgc, mp_free(two); mp_free(qm1); - key->sshk.vt = &ssh_dss; + key->sshk.vt = &ssh_dsa; key->p = p; key->q = q; diff --git a/ssh.h b/ssh.h index 61a95c8e..61fc6d14 100644 --- a/ssh.h +++ b/ssh.h @@ -446,7 +446,7 @@ struct RSAKey { ssh_key sshk; }; -struct dss_key { +struct dsa_key { mp_int *p, *q, *g, *y, *x; ssh_key sshk; }; @@ -614,7 +614,7 @@ mp_int *ssh_ecdhkex_getkey(ecdh_key *key, ptrlen remoteKey); /* * Helper function for k generation in DSA, reused in ECDSA */ -mp_int *dss_gen_k(const char *id_string, +mp_int *dsa_gen_k(const char *id_string, mp_int *modulus, mp_int *private_key, unsigned char *digest, int digest_len); @@ -1019,7 +1019,7 @@ extern const ssh_kex ssh_ec_kex_nistp256; extern const ssh_kex ssh_ec_kex_nistp384; extern const ssh_kex ssh_ec_kex_nistp521; extern const ssh_kexes ssh_ecdh_kex; -extern const ssh_keyalg ssh_dss; +extern const ssh_keyalg ssh_dsa; extern const ssh_keyalg ssh_rsa; extern const ssh_keyalg ssh_rsa_sha256; extern const ssh_keyalg ssh_rsa_sha512; diff --git a/ssh/transport2.h b/ssh/transport2.h index 80c39ef2..46cd67f9 100644 --- a/ssh/transport2.h +++ b/ssh/transport2.h @@ -52,7 +52,7 @@ struct kexinit_algorithm { X(HK_ECDSA, ssh_ecdsa_nistp256) \ X(HK_ECDSA, ssh_ecdsa_nistp384) \ X(HK_ECDSA, ssh_ecdsa_nistp521) \ - X(HK_DSA, ssh_dss) \ + X(HK_DSA, ssh_dsa) \ X(HK_RSA, ssh_rsa_sha512) \ X(HK_RSA, ssh_rsa_sha256) \ X(HK_RSA, ssh_rsa) \ diff --git a/sshkeygen.h b/sshkeygen.h index 971a3633..dc0024b9 100644 --- a/sshkeygen.h +++ b/sshkeygen.h @@ -286,7 +286,7 @@ extern const PrimeGenerationPolicy primegen_provable_maurer_complex; int rsa_generate(RSAKey *key, int bits, bool strong, PrimeGenerationContext *pgc, ProgressReceiver *prog); -int dsa_generate(struct dss_key *key, int bits, PrimeGenerationContext *pgc, +int dsa_generate(struct dsa_key *key, int bits, PrimeGenerationContext *pgc, ProgressReceiver *prog); int ecdsa_generate(struct ecdsa_key *key, int bits); int eddsa_generate(struct eddsa_key *key, int bits); diff --git a/sshpubk.c b/sshpubk.c index 0aa5e0b6..f7a9bb33 100644 --- a/sshpubk.c +++ b/sshpubk.c @@ -563,7 +563,7 @@ const ssh_keyalg *const all_keyalgs[] = { &ssh_rsa, &ssh_rsa_sha256, &ssh_rsa_sha512, - &ssh_dss, + &ssh_dsa, &ssh_ecdsa_nistp256, &ssh_ecdsa_nistp384, &ssh_ecdsa_nistp521, diff --git a/testcrypt.c b/testcrypt.c index a370a667..c5c66b4b 100644 --- a/testcrypt.c +++ b/testcrypt.c @@ -270,7 +270,7 @@ static const ssh_keyalg *get_keyalg(BinarySource *in) const char *key; const ssh_keyalg *value; } algs[] = { - {"dsa", &ssh_dss}, + {"dsa", &ssh_dsa}, {"rsa", &ssh_rsa}, {"ed25519", &ssh_ecdsa_ed25519}, {"ed448", &ssh_ecdsa_ed448}, @@ -1239,9 +1239,9 @@ ssh_key *rsa_generate_wrapper(int bits, bool strong, ssh_key *dsa_generate_wrapper(int bits, PrimeGenerationContext *pgc) { - struct dss_key *dsskey = snew(struct dss_key); - dsa_generate(dsskey, bits, pgc, &null_progress); - return &dsskey->sshk; + struct dsa_key *dsakey = snew(struct dsa_key); + dsa_generate(dsakey, bits, pgc, &null_progress); + return &dsakey->sshk; } #define dsa_generate dsa_generate_wrapper diff --git a/windows/winpgen.c b/windows/winpgen.c index 065ada80..e1c1b5b3 100644 --- a/windows/winpgen.c +++ b/windows/winpgen.c @@ -593,7 +593,7 @@ struct rsa_key_thread_params { bool rsa_strong; union { RSAKey *key; - struct dss_key *dsskey; + struct dsa_key *dsakey; struct ecdsa_key *eckey; struct eddsa_key *edkey; }; @@ -610,7 +610,7 @@ static DWORD WINAPI generate_key_thread(void *param) PrimeGenerationContext *pgc = primegen_new_context(params->primepolicy); if (params->keytype == DSA) - dsa_generate(params->dsskey, params->key_bits, pgc, &prog.rec); + dsa_generate(params->dsakey, params->key_bits, pgc, &prog.rec); else if (params->keytype == ECDSA) ecdsa_generate(params->eckey, params->curve_bits); else if (params->keytype == EDDSA) @@ -645,7 +645,7 @@ struct MainDlgState { unsigned *entropy; union { RSAKey key; - struct dss_key dsskey; + struct dsa_key dsakey; struct ecdsa_key eckey; struct eddsa_key edkey; }; @@ -1158,7 +1158,7 @@ static void start_generating_key(HWND hwnd, struct MainDlgState *state) params->primepolicy = state->primepolicy; params->rsa_strong = state->rsa_strong; params->key = &state->key; - params->dsskey = &state->dsskey; + params->dsakey = &state->dsakey; if (!CreateThread(NULL, 0, generate_key_thread, params, 0, &threadid)) { @@ -1828,7 +1828,7 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, SendDlgItemMessage(hwnd, IDC_PROGRESS, PBM_SETPOS, PROGRESSRANGE, 0); if (state->ssh2) { if (state->keytype == DSA) { - state->ssh2key.key = &state->dsskey.sshk; + state->ssh2key.key = &state->dsakey.sshk; } else if (state->keytype == ECDSA) { state->ssh2key.key = &state->eckey.sshk; } else if (state->keytype == EDDSA) { diff --git a/windows/winpgnt.c b/windows/winpgnt.c index 1f3d7080..bda08c93 100644 --- a/windows/winpgnt.c +++ b/windows/winpgnt.c @@ -366,7 +366,7 @@ static void keylist_update_callback( ptrlen algname = get_string(src); const ssh_keyalg *alg = find_pubkey_alg_len(algname); - bool include_bit_count = (alg == &ssh_dss && alg == &ssh_rsa); + bool include_bit_count = (alg == &ssh_dsa && alg == &ssh_rsa); int wordnumber = 0; for (const char *p = fingerprint; *p; p++) { -- cgit v1.2.3 From d9f217323e5218bb4b881901255ee46ced3d6777 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 24 Apr 2021 07:51:15 +0100 Subject: Break up gtkmisc.c. It's another file that should have been subdivided into lots of tiny separate things in the utils library - especially since for some reason I made a completely separate 'guimisc' cmake-level library for it when there was no need. --- unix/CMakeLists.txt | 21 ++-- unix/gtkmisc.c | 223 --------------------------------- unix/utils/align_label_left.c | 20 +++ unix/utils/buildinfo_gtk_version.c | 14 +++ unix/utils/get_label_text_dimensions.c | 42 +++++++ unix/utils/get_x11_display.c | 34 +++++ unix/utils/our_dialog.c | 135 ++++++++++++++++++++ unix/utils/string_width.c | 18 +++ 8 files changed, 275 insertions(+), 232 deletions(-) delete mode 100644 unix/gtkmisc.c create mode 100644 unix/utils/align_label_left.c create mode 100644 unix/utils/buildinfo_gtk_version.c create mode 100644 unix/utils/get_label_text_dimensions.c create mode 100644 unix/utils/get_x11_display.c create mode 100644 unix/utils/our_dialog.c create mode 100644 unix/utils/string_width.c diff --git a/unix/CMakeLists.txt b/unix/CMakeLists.txt index c13abdc5..e33fbff2 100644 --- a/unix/CMakeLists.txt +++ b/unix/CMakeLists.txt @@ -145,14 +145,17 @@ target_link_libraries(uppity if(GTK_FOUND) add_sources_from_current_dir(utils + utils/align_label_left.c + utils/buildinfo_gtk_version.c + utils/get_label_text_dimensions.c + utils/get_x11_display.c + utils/our_dialog.c + utils/string_width.c gtkcols.c) add_sources_from_current_dir(guiterminal gtkwin.c gtkfont.c gtkdlg.c gtkcfg.c gtkcomm.c uxcfg.c uxucs.c uxprint.c) add_dependencies(guiterminal generated_licence_h) # gtkdlg.c uses licence.h - add_library(guimisc STATIC - gtkmisc.c) - add_executable(pageant uxpgnt.c ${CMAKE_SOURCE_DIR}/be_misc.c @@ -163,7 +166,7 @@ if(GTK_FOUND) uxnoise.c ${CMAKE_SOURCE_DIR}/ssh/x11fwd.c) target_link_libraries(pageant - guimisc eventloop console agent settings network crypto utils + eventloop console agent settings network crypto utils ${GTK_LIBRARIES}) installed_program(pageant) @@ -174,7 +177,7 @@ if(GTK_FOUND) ${CMAKE_SOURCE_DIR}/nogss.c uxpty.c) target_link_libraries(pterm - guiterminal guimisc eventloop settings charset utils + guiterminal eventloop settings charset utils ${GTK_LIBRARIES} ${X11_LIBRARIES}) installed_program(pterm) @@ -186,7 +189,7 @@ if(GTK_FOUND) ${CMAKE_SOURCE_DIR}/nogss.c uxpty.c) target_link_libraries(ptermapp - guiterminal guimisc eventloop settings charset utils + guiterminal eventloop settings charset utils ${GTK_LIBRARIES} ${X11_LIBRARIES}) add_executable(putty @@ -194,7 +197,7 @@ if(GTK_FOUND) gtkmain.c ${CMAKE_SOURCE_DIR}/be_all_s.c) target_link_libraries(putty - guiterminal guimisc eventloop sshclient otherbackends settings + guiterminal eventloop sshclient otherbackends settings network crypto charset utils ${GTK_LIBRARIES} ${X11_LIBRARIES}) set_target_properties(putty @@ -207,7 +210,7 @@ if(GTK_FOUND) ${CMAKE_SOURCE_DIR}/nocmdline.c ${CMAKE_SOURCE_DIR}/be_all_s.c) target_link_libraries(puttyapp - guiterminal guimisc eventloop sshclient otherbackends settings + guiterminal eventloop sshclient otherbackends settings network crypto charset utils ${GTK_LIBRARIES} ${X11_LIBRARIES}) @@ -219,6 +222,6 @@ if(GTK_FOUND) ${CMAKE_SOURCE_DIR}/norand.c ${CMAKE_SOURCE_DIR}/nocproxy.c) target_link_libraries(puttytel - guiterminal guimisc eventloop otherbackends settings network charset utils + guiterminal eventloop otherbackends settings network charset utils ${GTK_LIBRARIES} ${X11_LIBRARIES}) endif() diff --git a/unix/gtkmisc.c b/unix/gtkmisc.c deleted file mode 100644 index c0c9682a..00000000 --- a/unix/gtkmisc.c +++ /dev/null @@ -1,223 +0,0 @@ -/* - * Miscellaneous GTK helper functions. - */ - -#include -#include -#include -#include - -#include -#if !GTK_CHECK_VERSION(3,0,0) -#include -#endif - -#include "putty.h" -#include "gtkcompat.h" - -#ifndef NOT_X_WINDOWS -#include -#include -#endif - -void get_label_text_dimensions(const char *text, int *width, int *height) -{ - /* - * Determine the dimensions of a piece of text in the standard - * font used in GTK interface elements like labels. We do this by - * instantiating an actual GtkLabel, and then querying its size. - * - * But GTK2 and GTK3 require us to query the size completely - * differently. I'm sure there ought to be an easier approach than - * the way I'm doing this in GTK3, too! - */ - GtkWidget *label = gtk_label_new(text); - -#if GTK_CHECK_VERSION(3,0,0) - PangoLayout *layout = gtk_label_get_layout(GTK_LABEL(label)); - PangoRectangle logrect; - pango_layout_get_extents(layout, NULL, &logrect); - if (width) - *width = logrect.width / PANGO_SCALE; - if (height) - *height = logrect.height / PANGO_SCALE; -#else - GtkRequisition req; - gtk_widget_size_request(label, &req); - if (width) - *width = req.width; - if (height) - *height = req.height; -#endif - - g_object_ref_sink(G_OBJECT(label)); -#if GTK_CHECK_VERSION(2,10,0) - g_object_unref(label); -#endif -} - -int string_width(const char *text) -{ - int ret; - get_label_text_dimensions(text, &ret, NULL); - return ret; -} - -void align_label_left(GtkLabel *label) -{ -#if GTK_CHECK_VERSION(3,16,0) - gtk_label_set_xalign(label, 0.0); -#elif GTK_CHECK_VERSION(3,14,0) - gtk_widget_set_halign(GTK_WIDGET(label), GTK_ALIGN_START); -#else - gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0); -#endif -} - -/* ---------------------------------------------------------------------- - * Functions to arrange controls in a basically dialog-like window. - * - * The best method for doing this has varied wildly with versions of - * GTK, hence the set of wrapper functions here. - * - * In GTK 1, a GtkDialog has an 'action_area' at the bottom, which is - * a GtkHBox which stretches to cover the full width of the dialog. So - * we can either add buttons or other widgets to that box directly, or - * alternatively we can fill the hbox with some layout class of our - * own such as a Columns widget. - * - * In GTK 2, the action area has become a GtkHButtonBox, and its - * layout behaviour seems to be different and not what we want. 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. - * - * In GTK 3, we typically don't even want to use GtkDialog at all, - * because GTK 3 has become a lot more restrictive about what you can - * sensibly use GtkDialog for - it deprecates direct access to the - * action area in favour of making you provide nothing but - * dialog-ending buttons in the form of (text, response code) pairs, - * so you can't put any other kind of control in there, or fiddle with - * alignment and positioning, or even have a button that _doesn't_ end - * the dialog (e.g. 'View Licence' in our About box). So instead of - * GtkDialog, we use a straight-up GtkWindow and have it contain a - * vbox as its (unique) child widget; and we implement the action area - * by adding a separator and another widget at the bottom of that - * vbox. - */ - -GtkWidget *our_dialog_new(void) -{ -#if GTK_CHECK_VERSION(3,0,0) - /* - * See comment in our_dialog_set_action_area(): in GTK 3, we use - * GtkWindow in place of GtkDialog for most purposes. - */ - GtkWidget *w = gtk_window_new(GTK_WINDOW_TOPLEVEL); - GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 8); - gtk_container_add(GTK_CONTAINER(w), vbox); - gtk_widget_show(vbox); - return w; -#else - return gtk_dialog_new(); -#endif -} - -void our_dialog_set_action_area(GtkWindow *dlg, GtkWidget *w) -{ -#if !GTK_CHECK_VERSION(2,0,0) - - gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dlg)->action_area), - w, true, true, 0); - -#elif !GTK_CHECK_VERSION(3,0,0) - - 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(gtk_dialog_get_content_area(GTK_DIALOG(dlg))), - align, false, true, 0); - - w = gtk_hseparator_new(); - gtk_box_pack_end(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dlg))), - w, false, true, 0); - gtk_widget_show(w); - gtk_widget_hide(gtk_dialog_get_action_area(GTK_DIALOG(dlg))); - g_object_set(G_OBJECT(dlg), "has-separator", true, (const char *)NULL); - -#else /* GTK 3 */ - - /* GtkWindow is a GtkBin, hence contains exactly one child, which - * here we always expect to be a vbox */ - GtkBox *vbox = GTK_BOX(gtk_bin_get_child(GTK_BIN(dlg))); - GtkWidget *sep; - - g_object_set(G_OBJECT(w), "margin", 8, (const char *)NULL); - gtk_box_pack_end(vbox, w, false, true, 0); - - sep = gtk_hseparator_new(); - gtk_box_pack_end(vbox, sep, false, true, 0); - gtk_widget_show(sep); - -#endif -} - -GtkBox *our_dialog_make_action_hbox(GtkWindow *dlg) -{ -#if GTK_CHECK_VERSION(3,0,0) - GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); - our_dialog_set_action_area(dlg, hbox); - g_object_set(G_OBJECT(hbox), "margin", 0, (const char *)NULL); - g_object_set(G_OBJECT(hbox), "spacing", 8, (const char *)NULL); - gtk_widget_show(hbox); - return GTK_BOX(hbox); -#else /* not GTK 3 */ - return GTK_BOX(gtk_dialog_get_action_area(GTK_DIALOG(dlg))); -#endif -} - -void our_dialog_add_to_content_area(GtkWindow *dlg, GtkWidget *w, - bool expand, bool fill, guint padding) -{ -#if GTK_CHECK_VERSION(3,0,0) - /* GtkWindow is a GtkBin, hence contains exactly one child, which - * here we always expect to be a vbox */ - GtkBox *vbox = GTK_BOX(gtk_bin_get_child(GTK_BIN(dlg))); - - gtk_box_pack_start(vbox, w, expand, fill, padding); -#else - gtk_box_pack_start - (GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dlg))), - w, expand, fill, padding); -#endif -} - -char *buildinfo_gtk_version(void) -{ - return dupprintf("%d.%d.%d", - GTK_MAJOR_VERSION, GTK_MINOR_VERSION, GTK_MICRO_VERSION); -} - -#ifndef NOT_X_WINDOWS -Display *get_x11_display(void) -{ -#if GTK_CHECK_VERSION(3,0,0) - if (!GDK_IS_X11_DISPLAY(gdk_display_get_default())) - return NULL; -#endif - return GDK_DISPLAY_XDISPLAY(gdk_display_get_default()); -} -#endif diff --git a/unix/utils/align_label_left.c b/unix/utils/align_label_left.c new file mode 100644 index 00000000..eaae01ab --- /dev/null +++ b/unix/utils/align_label_left.c @@ -0,0 +1,20 @@ +/* + * Helper function to align the text in a GtkLabel to the left, which + * has to be done in several different ways depending on GTK version. + */ + +#include +#include "putty.h" +#include "gtkcompat.h" +#include "gtkmisc.h" + +void align_label_left(GtkLabel *label) +{ +#if GTK_CHECK_VERSION(3,16,0) + gtk_label_set_xalign(label, 0.0); +#elif GTK_CHECK_VERSION(3,14,0) + gtk_widget_set_halign(GTK_WIDGET(label), GTK_ALIGN_START); +#else + gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0); +#endif +} diff --git a/unix/utils/buildinfo_gtk_version.c b/unix/utils/buildinfo_gtk_version.c new file mode 100644 index 00000000..a2ec187e --- /dev/null +++ b/unix/utils/buildinfo_gtk_version.c @@ -0,0 +1,14 @@ +/* + * Return the version of GTK we were compiled against, for buildinfo. + */ + +#include +#include "putty.h" +#include "gtkcompat.h" +#include "gtkmisc.h" + +char *buildinfo_gtk_version(void) +{ + return dupprintf("%d.%d.%d", + GTK_MAJOR_VERSION, GTK_MINOR_VERSION, GTK_MICRO_VERSION); +} diff --git a/unix/utils/get_label_text_dimensions.c b/unix/utils/get_label_text_dimensions.c new file mode 100644 index 00000000..fe1c13af --- /dev/null +++ b/unix/utils/get_label_text_dimensions.c @@ -0,0 +1,42 @@ +/* + * Determine the dimensions of a piece of text in the standard + * font used in GTK interface elements like labels. We do this by + * instantiating an actual GtkLabel, and then querying its size. + */ + +#include +#include "putty.h" +#include "gtkcompat.h" +#include "gtkmisc.h" + +void get_label_text_dimensions(const char *text, int *width, int *height) +{ + GtkWidget *label = gtk_label_new(text); + + /* + * GTK2 and GTK3 require us to query the size completely + * differently. I'm sure there ought to be an easier approach than + * the way I'm doing this in GTK3, too! + */ +#if GTK_CHECK_VERSION(3,0,0) + PangoLayout *layout = gtk_label_get_layout(GTK_LABEL(label)); + PangoRectangle logrect; + pango_layout_get_extents(layout, NULL, &logrect); + if (width) + *width = logrect.width / PANGO_SCALE; + if (height) + *height = logrect.height / PANGO_SCALE; +#else + GtkRequisition req; + gtk_widget_size_request(label, &req); + if (width) + *width = req.width; + if (height) + *height = req.height; +#endif + + g_object_ref_sink(G_OBJECT(label)); +#if GTK_CHECK_VERSION(2,10,0) + g_object_unref(label); +#endif +} diff --git a/unix/utils/get_x11_display.c b/unix/utils/get_x11_display.c new file mode 100644 index 00000000..d9462c27 --- /dev/null +++ b/unix/utils/get_x11_display.c @@ -0,0 +1,34 @@ +/* + * Return the Xlib 'Display *' underlying our GTK environment, if any. + */ + +#include +#include "putty.h" +#include "gtkcompat.h" +#include "gtkmisc.h" + +#ifndef NOT_X_WINDOWS + +#include +#include + +Display *get_x11_display(void) +{ +#if GTK_CHECK_VERSION(3,0,0) + if (!GDK_IS_X11_DISPLAY(gdk_display_get_default())) + return NULL; +#endif + return GDK_DISPLAY_XDISPLAY(gdk_display_get_default()); +} + +#else + +/* + * Include _something_ in this file to prevent an annoying compiler + * warning, and to avoid having to condition out this file in + * CMakeLists. It's in a library, so this variable shouldn't end up in + * any actual program, because nothing will refer to it. + */ +const int get_x11_display_dummy_variable = 0; + +#endif diff --git a/unix/utils/our_dialog.c b/unix/utils/our_dialog.c new file mode 100644 index 00000000..5b9995d4 --- /dev/null +++ b/unix/utils/our_dialog.c @@ -0,0 +1,135 @@ +/* + * Functions to arrange controls in a basically dialog-like window. + * + * The best method for doing this has varied wildly with versions of + * GTK, hence the set of wrapper functions here. + * + * In GTK 1, a GtkDialog has an 'action_area' at the bottom, which is + * a GtkHBox which stretches to cover the full width of the dialog. So + * we can either add buttons or other widgets to that box directly, or + * alternatively we can fill the hbox with some layout class of our + * own such as a Columns widget. + * + * In GTK 2, the action area has become a GtkHButtonBox, and its + * layout behaviour seems to be different and not what we want. 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. + * + * In GTK 3, we typically don't even want to use GtkDialog at all, + * because GTK 3 has become a lot more restrictive about what you can + * sensibly use GtkDialog for - it deprecates direct access to the + * action area in favour of making you provide nothing but + * dialog-ending buttons in the form of (text, response code) pairs, + * so you can't put any other kind of control in there, or fiddle with + * alignment and positioning, or even have a button that _doesn't_ end + * the dialog (e.g. 'View Licence' in our About box). So instead of + * GtkDialog, we use a straight-up GtkWindow and have it contain a + * vbox as its (unique) child widget; and we implement the action area + * by adding a separator and another widget at the bottom of that + * vbox. + */ + +#include +#include "putty.h" +#include "gtkcompat.h" +#include "gtkmisc.h" + +GtkWidget *our_dialog_new(void) +{ +#if GTK_CHECK_VERSION(3,0,0) + /* + * See comment in our_dialog_set_action_area(): in GTK 3, we use + * GtkWindow in place of GtkDialog for most purposes. + */ + GtkWidget *w = gtk_window_new(GTK_WINDOW_TOPLEVEL); + GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 8); + gtk_container_add(GTK_CONTAINER(w), vbox); + gtk_widget_show(vbox); + return w; +#else + return gtk_dialog_new(); +#endif +} + +void our_dialog_set_action_area(GtkWindow *dlg, GtkWidget *w) +{ +#if !GTK_CHECK_VERSION(2,0,0) + + gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dlg)->action_area), + w, true, true, 0); + +#elif !GTK_CHECK_VERSION(3,0,0) + + 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(gtk_dialog_get_content_area(GTK_DIALOG(dlg))), + align, false, true, 0); + + w = gtk_hseparator_new(); + gtk_box_pack_end(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dlg))), + w, false, true, 0); + gtk_widget_show(w); + gtk_widget_hide(gtk_dialog_get_action_area(GTK_DIALOG(dlg))); + g_object_set(G_OBJECT(dlg), "has-separator", true, (const char *)NULL); + +#else /* GTK 3 */ + + /* GtkWindow is a GtkBin, hence contains exactly one child, which + * here we always expect to be a vbox */ + GtkBox *vbox = GTK_BOX(gtk_bin_get_child(GTK_BIN(dlg))); + GtkWidget *sep; + + g_object_set(G_OBJECT(w), "margin", 8, (const char *)NULL); + gtk_box_pack_end(vbox, w, false, true, 0); + + sep = gtk_hseparator_new(); + gtk_box_pack_end(vbox, sep, false, true, 0); + gtk_widget_show(sep); + +#endif +} + +GtkBox *our_dialog_make_action_hbox(GtkWindow *dlg) +{ +#if GTK_CHECK_VERSION(3,0,0) + GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); + our_dialog_set_action_area(dlg, hbox); + g_object_set(G_OBJECT(hbox), "margin", 0, (const char *)NULL); + g_object_set(G_OBJECT(hbox), "spacing", 8, (const char *)NULL); + gtk_widget_show(hbox); + return GTK_BOX(hbox); +#else /* not GTK 3 */ + return GTK_BOX(gtk_dialog_get_action_area(GTK_DIALOG(dlg))); +#endif +} + +void our_dialog_add_to_content_area(GtkWindow *dlg, GtkWidget *w, + bool expand, bool fill, guint padding) +{ +#if GTK_CHECK_VERSION(3,0,0) + /* GtkWindow is a GtkBin, hence contains exactly one child, which + * here we always expect to be a vbox */ + GtkBox *vbox = GTK_BOX(gtk_bin_get_child(GTK_BIN(dlg))); + + gtk_box_pack_start(vbox, w, expand, fill, padding); +#else + gtk_box_pack_start + (GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dlg))), + w, expand, fill, padding); +#endif +} diff --git a/unix/utils/string_width.c b/unix/utils/string_width.c new file mode 100644 index 00000000..c944308f --- /dev/null +++ b/unix/utils/string_width.c @@ -0,0 +1,18 @@ +/* + * Return the width of a string in the font used in GTK controls. Used + * as a means of picking a sensible size for dialog boxes and pieces + * of them, in a way that should adapt sensibly to changes in font and + * resolution. + */ + +#include +#include "putty.h" +#include "gtkcompat.h" +#include "gtkmisc.h" + +int string_width(const char *text) +{ + int ret; + get_label_text_dimensions(text, &ret, NULL); + return ret; +} -- cgit v1.2.3 From f39c51f9a74bdf2b00c4939c02abf12090781446 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 23 Apr 2021 06:19:05 +0100 Subject: Rename most of the platform source files. This gets rid of all those annoying 'win', 'ux' and 'gtk' prefixes which made filenames annoying to type and to tab-complete. Also, as with my other recent renaming sprees, I've taken the opportunity to expand and clarify some of the names so that they're not such cryptic abbreviations. --- unix/CMakeLists.txt | 99 +- unix/agent-client.c | 226 ++ unix/agent-socket.c | 91 + unix/askpass.c | 662 ++++++ unix/cliloop.c | 125 + unix/columns.c | 1201 ++++++++++ unix/columns.h | 65 + unix/config-gtk.c | 160 ++ unix/config-unix.c | 70 + unix/console.c | 530 +++++ unix/dialog.c | 4195 +++++++++++++++++++++++++++++++++ unix/fd-socket.c | 357 +++ unix/gss.c | 175 ++ unix/gtk-common.c | 241 ++ unix/gtkapp.c | 340 --- unix/gtkask.c | 662 ------ unix/gtkcfg.c | 160 -- unix/gtkcols.c | 1201 ---------- unix/gtkcols.h | 65 - unix/gtkcomm.c | 241 -- unix/gtkdlg.c | 4195 --------------------------------- unix/gtkfont.c | 3808 ------------------------------ unix/gtkfont.h | 186 -- unix/gtkmain.c | 673 ------ unix/gtkwin.c | 5431 ------------------------------------------- unix/keygen-noise.c | 64 + unix/local-proxy.c | 105 + unix/main-gtk-application.c | 340 +++ unix/main-gtk-simple.c | 673 ++++++ unix/network.c | 1761 ++++++++++++++ unix/no-gtk.c | 11 + unix/noise.c | 130 ++ unix/pageant.c | 1509 ++++++++++++ unix/peerinfo.c | 32 + unix/plink.c | 964 ++++++++ unix/printing.c | 58 + unix/psocks.c | 176 ++ unix/psusan.c | 422 ++++ unix/pterm-config-xpm.c | 150 ++ unix/pterm-xpm.c | 143 ++ unix/pterm.c | 50 + unix/pty.c | 1602 +++++++++++++ unix/putty-config-xpm.c | 150 ++ unix/putty-xpm.c | 147 ++ unix/putty.c | 92 + unix/serial.c | 595 +++++ unix/sftp.c | 578 +++++ unix/sftpserver.c | 703 ++++++ unix/sharing.c | 370 +++ unix/storage.c | 822 +++++++ unix/unicode.c | 268 +++ unix/unifont.c | 3808 ++++++++++++++++++++++++++++++ unix/unifont.h | 186 ++ unix/uppity.c | 850 +++++++ unix/ux_x11.c | 208 -- unix/uxagentc.c | 226 -- unix/uxagentsock.c | 91 - unix/uxcfg.c | 70 - unix/uxcliloop.c | 125 - unix/uxcons.c | 530 ----- unix/uxfdsock.c | 357 --- unix/uxgen.c | 64 - unix/uxgss.c | 175 -- unix/uxnet.c | 1761 -------------- unix/uxnogtk.c | 11 - unix/uxnoise.c | 130 -- unix/uxpeer.c | 32 - unix/uxpgnt.c | 1509 ------------ unix/uxplink.c | 964 -------- unix/uxprint.c | 58 - unix/uxproxy.c | 105 - unix/uxpsusan.c | 422 ---- unix/uxpterm.c | 50 - unix/uxpty.c | 1602 ------------- unix/uxputty.c | 92 - unix/uxser.c | 595 ----- unix/uxserver.c | 850 ------- unix/uxsftp.c | 578 ----- unix/uxsftpserver.c | 703 ------ unix/uxshare.c | 370 --- unix/uxsocks.c | 176 -- unix/uxstore.c | 822 ------- unix/uxucs.c | 268 --- unix/window.c | 5431 +++++++++++++++++++++++++++++++++++++++++++ unix/x11.c | 208 ++ unix/xpmptcfg.c | 150 -- unix/xpmpterm.c | 143 -- unix/xpmpucfg.c | 150 -- unix/xpmputty.c | 147 -- windows/CMakeLists.txt | 66 +- windows/agent-client.c | 303 +++ windows/cliloop.c | 136 ++ windows/config.c | 405 ++++ windows/console.c | 452 ++++ windows/controls.c | 2600 +++++++++++++++++++++ windows/cryptoapi.h | 27 + windows/dialog.c | 1156 +++++++++ windows/gss.c | 660 ++++++ windows/handle-io.c | 727 ++++++ windows/handle-socket.c | 343 +++ windows/help.c | 250 ++ windows/help.h | 207 ++ windows/help.rc2 | 8 + windows/jump-list.c | 748 ++++++ windows/local-proxy.c | 107 + windows/named-pipe-client.c | 94 + windows/named-pipe-server.c | 236 ++ windows/network.c | 1825 +++++++++++++++ windows/no-jump-list.c | 8 + windows/nohelp.c | 15 + windows/noise.c | 142 ++ windows/pageant.c | 1696 ++++++++++++++ windows/pageant.rc | 2 +- windows/platform.h | 2 +- windows/plink.c | 533 +++++ windows/printing.c | 224 ++ windows/psocks.c | 24 + windows/putty-common.rc2 | 137 ++ windows/putty-rc.h | 49 + windows/putty.rc | 4 +- windows/puttygen.c | 2024 ++++++++++++++++ windows/puttygen.rc | 2 +- windows/puttytel.rc | 4 +- windows/security-api.h | 49 + windows/select-cli.c | 78 + windows/select-gui.c | 38 + windows/serial.c | 465 ++++ windows/sftp.c | 650 ++++++ windows/sharing.c | 143 ++ windows/storage.c | 873 +++++++ windows/unicode.c | 1213 ++++++++++ windows/utils/capi.c | 85 - windows/utils/cryptoapi.c | 85 + windows/utils/security.c | 4 +- windows/win-gui-seat.h | 14 + windows/win_res.h | 49 - windows/win_res.rc2 | 137 -- windows/wincapi.h | 27 - windows/wincfg.c | 405 ---- windows/wincliloop.c | 136 -- windows/wincons.c | 452 ---- windows/winctrls.c | 2600 --------------------- windows/windlg.c | 1156 --------- windows/window.c | 6 +- windows/wingss.c | 660 ------ windows/winhandl.c | 727 ------ windows/winhelp.c | 250 -- windows/winhelp.h | 207 -- windows/winhelp.rc2 | 8 - windows/winhsock.c | 343 --- windows/winjump.c | 748 ------ windows/winnet.c | 1825 --------------- windows/winnohlp.c | 15 - windows/winnoise.c | 142 -- windows/winnojmp.c | 8 - windows/winnpc.c | 94 - windows/winnps.c | 236 -- windows/winpgen.c | 2024 ---------------- windows/winpgnt.c | 1696 -------------- windows/winpgntc.c | 303 --- windows/winplink.c | 533 ----- windows/winprint.c | 224 -- windows/winproxy.c | 107 - windows/winseat.h | 14 - windows/winsecur.h | 49 - windows/winselcli.c | 78 - windows/winselgui.c | 38 - windows/winser.c | 465 ---- windows/winsftp.c | 650 ------ windows/winshare.c | 143 -- windows/winsocks.c | 24 - windows/winstore.c | 873 ------- windows/winucs.c | 1213 ---------- windows/winx11.c | 19 - windows/x11.c | 19 + 175 files changed, 49355 insertions(+), 49352 deletions(-) create mode 100644 unix/agent-client.c create mode 100644 unix/agent-socket.c create mode 100644 unix/askpass.c create mode 100644 unix/cliloop.c create mode 100644 unix/columns.c create mode 100644 unix/columns.h create mode 100644 unix/config-gtk.c create mode 100644 unix/config-unix.c create mode 100644 unix/console.c create mode 100644 unix/dialog.c create mode 100644 unix/fd-socket.c create mode 100644 unix/gss.c create mode 100644 unix/gtk-common.c delete mode 100644 unix/gtkapp.c delete mode 100644 unix/gtkask.c delete mode 100644 unix/gtkcfg.c delete mode 100644 unix/gtkcols.c delete mode 100644 unix/gtkcols.h delete mode 100644 unix/gtkcomm.c delete mode 100644 unix/gtkdlg.c delete mode 100644 unix/gtkfont.c delete mode 100644 unix/gtkfont.h delete mode 100644 unix/gtkmain.c delete mode 100644 unix/gtkwin.c create mode 100644 unix/keygen-noise.c create mode 100644 unix/local-proxy.c create mode 100644 unix/main-gtk-application.c create mode 100644 unix/main-gtk-simple.c create mode 100644 unix/network.c create mode 100644 unix/no-gtk.c create mode 100644 unix/noise.c create mode 100644 unix/pageant.c create mode 100644 unix/peerinfo.c create mode 100644 unix/plink.c create mode 100644 unix/printing.c create mode 100644 unix/psocks.c create mode 100644 unix/psusan.c create mode 100644 unix/pterm-config-xpm.c create mode 100644 unix/pterm-xpm.c create mode 100644 unix/pterm.c create mode 100644 unix/pty.c create mode 100644 unix/putty-config-xpm.c create mode 100644 unix/putty-xpm.c create mode 100644 unix/putty.c create mode 100644 unix/serial.c create mode 100644 unix/sftp.c create mode 100644 unix/sftpserver.c create mode 100644 unix/sharing.c create mode 100644 unix/storage.c create mode 100644 unix/unicode.c create mode 100644 unix/unifont.c create mode 100644 unix/unifont.h create mode 100644 unix/uppity.c delete mode 100644 unix/ux_x11.c delete mode 100644 unix/uxagentc.c delete mode 100644 unix/uxagentsock.c delete mode 100644 unix/uxcfg.c delete mode 100644 unix/uxcliloop.c delete mode 100644 unix/uxcons.c delete mode 100644 unix/uxfdsock.c delete mode 100644 unix/uxgen.c delete mode 100644 unix/uxgss.c delete mode 100644 unix/uxnet.c delete mode 100644 unix/uxnogtk.c delete mode 100644 unix/uxnoise.c delete mode 100644 unix/uxpeer.c delete mode 100644 unix/uxpgnt.c delete mode 100644 unix/uxplink.c delete mode 100644 unix/uxprint.c delete mode 100644 unix/uxproxy.c delete mode 100644 unix/uxpsusan.c delete mode 100644 unix/uxpterm.c delete mode 100644 unix/uxpty.c delete mode 100644 unix/uxputty.c delete mode 100644 unix/uxser.c delete mode 100644 unix/uxserver.c delete mode 100644 unix/uxsftp.c delete mode 100644 unix/uxsftpserver.c delete mode 100644 unix/uxshare.c delete mode 100644 unix/uxsocks.c delete mode 100644 unix/uxstore.c delete mode 100644 unix/uxucs.c create mode 100644 unix/window.c create mode 100644 unix/x11.c delete mode 100644 unix/xpmptcfg.c delete mode 100644 unix/xpmpterm.c delete mode 100644 unix/xpmpucfg.c delete mode 100644 unix/xpmputty.c create mode 100644 windows/agent-client.c create mode 100644 windows/cliloop.c create mode 100644 windows/config.c create mode 100644 windows/console.c create mode 100644 windows/controls.c create mode 100644 windows/cryptoapi.h create mode 100644 windows/dialog.c create mode 100644 windows/gss.c create mode 100644 windows/handle-io.c create mode 100644 windows/handle-socket.c create mode 100644 windows/help.c create mode 100644 windows/help.h create mode 100644 windows/help.rc2 create mode 100644 windows/jump-list.c create mode 100644 windows/local-proxy.c create mode 100644 windows/named-pipe-client.c create mode 100644 windows/named-pipe-server.c create mode 100644 windows/network.c create mode 100644 windows/no-jump-list.c create mode 100644 windows/nohelp.c create mode 100644 windows/noise.c create mode 100644 windows/pageant.c create mode 100644 windows/plink.c create mode 100644 windows/printing.c create mode 100644 windows/psocks.c create mode 100644 windows/putty-common.rc2 create mode 100644 windows/putty-rc.h create mode 100644 windows/puttygen.c create mode 100644 windows/security-api.h create mode 100644 windows/select-cli.c create mode 100644 windows/select-gui.c create mode 100644 windows/serial.c create mode 100644 windows/sftp.c create mode 100644 windows/sharing.c create mode 100644 windows/storage.c create mode 100644 windows/unicode.c delete mode 100644 windows/utils/capi.c create mode 100644 windows/utils/cryptoapi.c create mode 100644 windows/win-gui-seat.h delete mode 100644 windows/win_res.h delete mode 100644 windows/win_res.rc2 delete mode 100644 windows/wincapi.h delete mode 100644 windows/wincfg.c delete mode 100644 windows/wincliloop.c delete mode 100644 windows/wincons.c delete mode 100644 windows/winctrls.c delete mode 100644 windows/windlg.c delete mode 100644 windows/wingss.c delete mode 100644 windows/winhandl.c delete mode 100644 windows/winhelp.c delete mode 100644 windows/winhelp.h delete mode 100644 windows/winhelp.rc2 delete mode 100644 windows/winhsock.c delete mode 100644 windows/winjump.c delete mode 100644 windows/winnet.c delete mode 100644 windows/winnohlp.c delete mode 100644 windows/winnoise.c delete mode 100644 windows/winnojmp.c delete mode 100644 windows/winnpc.c delete mode 100644 windows/winnps.c delete mode 100644 windows/winpgen.c delete mode 100644 windows/winpgnt.c delete mode 100644 windows/winpgntc.c delete mode 100644 windows/winplink.c delete mode 100644 windows/winprint.c delete mode 100644 windows/winproxy.c delete mode 100644 windows/winseat.h delete mode 100644 windows/winsecur.h delete mode 100644 windows/winselcli.c delete mode 100644 windows/winselgui.c delete mode 100644 windows/winser.c delete mode 100644 windows/winsftp.c delete mode 100644 windows/winshare.c delete mode 100644 windows/winsocks.c delete mode 100644 windows/winstore.c delete mode 100644 windows/winucs.c delete mode 100644 windows/winx11.c create mode 100644 windows/x11.c diff --git a/unix/CMakeLists.txt b/unix/CMakeLists.txt index e33fbff2..56786b94 100644 --- a/unix/CMakeLists.txt +++ b/unix/CMakeLists.txt @@ -19,38 +19,41 @@ add_sources_from_current_dir(utils utils/signal.c utils/x11_ignore_error.c # Compiled icon pixmap files - xpmpucfg.c xpmputty.c xpmptcfg.c xpmpterm.c + putty-xpm.c + putty-config-xpm.c + pterm-xpm.c + pterm-config-xpm.c # We want the ISO C implementation of ltime(), because we don't have # a local better alternative ../utils/ltime.c) add_sources_from_current_dir(eventloop - uxcliloop.c uxsel.c) + cliloop.c uxsel.c) add_sources_from_current_dir(console - uxcons.c) + console.c) add_sources_from_current_dir(settings - uxstore.c) + storage.c) add_sources_from_current_dir(network - uxnet.c uxfdsock.c uxagentsock.c uxpeer.c uxproxy.c ux_x11.c) + network.c fd-socket.c agent-socket.c peerinfo.c local-proxy.c x11.c) add_sources_from_current_dir(sshcommon - uxnoise.c) + noise.c) add_sources_from_current_dir(sshclient - uxgss.c uxagentc.c uxshare.c) + gss.c agent-client.c sharing.c) add_sources_from_current_dir(sshserver - uxsftpserver.c procnet.c) + sftpserver.c procnet.c) add_sources_from_current_dir(sftpclient - uxsftp.c) + sftp.c) add_sources_from_current_dir(otherbackends - uxser.c) + serial.c) add_sources_from_current_dir(agent - uxagentc.c) + agent-client.c) add_executable(fuzzterm ${CMAKE_SOURCE_DIR}/fuzzterm.c ${CMAKE_SOURCE_DIR}/be_none.c ${CMAKE_SOURCE_DIR}/logging.c ${CMAKE_SOURCE_DIR}/noprint.c - uxucs.c - uxnogtk.c) + unicode.c + no-gtk.c) add_dependencies(fuzzterm generated_licence_h) target_link_libraries(fuzzterm guiterminal eventloop charset settings utils) @@ -59,9 +62,9 @@ add_executable(osxlaunch osxlaunch.c) add_executable(plink - uxplink.c + plink.c ${CMAKE_SOURCE_DIR}/be_all_s.c - uxnogtk.c) + no-gtk.c) target_link_libraries(plink eventloop noterminal console sshclient otherbackends settings network crypto utils) @@ -70,7 +73,7 @@ installed_program(plink) add_executable(pscp ${CMAKE_SOURCE_DIR}/pscp.c ${CMAKE_SOURCE_DIR}/be_ssh.c - uxnogtk.c) + no-gtk.c) target_link_libraries(pscp sftpclient eventloop console sshclient settings network crypto utils) installed_program(pscp) @@ -78,38 +81,38 @@ installed_program(pscp) add_executable(psftp ${CMAKE_SOURCE_DIR}/psftp.c ${CMAKE_SOURCE_DIR}/be_ssh.c - uxnogtk.c) + no-gtk.c) target_link_libraries(psftp sftpclient eventloop console sshclient settings network crypto utils) installed_program(psftp) add_executable(psocks - uxsocks.c + psocks.c ${CMAKE_SOURCE_DIR}/psocks.c ${CMAKE_SOURCE_DIR}/norand.c ${CMAKE_SOURCE_DIR}/nocproxy.c ${CMAKE_SOURCE_DIR}/ssh/portfwd.c - uxnogtk.c) + no-gtk.c) target_link_libraries(psocks eventloop console network utils) add_executable(psusan - uxpsusan.c + psusan.c ${CMAKE_SOURCE_DIR}/be_none.c ${CMAKE_SOURCE_DIR}/nogss.c ${CMAKE_SOURCE_DIR}/ssh/scpserver.c - uxnogtk.c - uxpty.c) + no-gtk.c + pty.c) target_link_libraries(psusan eventloop sshserver keygen settings network crypto utils) installed_program(psusan) add_library(puttygen-common OBJECT ${CMAKE_SOURCE_DIR}/notiming.c - uxgen.c - uxnogtk.c - uxnoise.c - uxstore.c + keygen-noise.c + no-gtk.c + noise.c + storage.c ${CMAKE_SOURCE_DIR}/sshpubk.c ${CMAKE_SOURCE_DIR}/sshrand.c) @@ -134,11 +137,11 @@ add_executable(testzlib target_link_libraries(testzlib utils) add_executable(uppity - uxserver.c + uppity.c ${CMAKE_SOURCE_DIR}/be_none.c ${CMAKE_SOURCE_DIR}/ssh/scpserver.c - uxnogtk.c - uxpty.c + no-gtk.c + pty.c ${CMAKE_SOURCE_DIR}/nogss.c) target_link_libraries(uppity eventloop sshserver keygen settings network crypto utils) @@ -151,19 +154,19 @@ if(GTK_FOUND) utils/get_x11_display.c utils/our_dialog.c utils/string_width.c - gtkcols.c) + columns.c) add_sources_from_current_dir(guiterminal - gtkwin.c gtkfont.c gtkdlg.c gtkcfg.c gtkcomm.c uxcfg.c uxucs.c uxprint.c) - add_dependencies(guiterminal generated_licence_h) # gtkdlg.c uses licence.h + window.c unifont.c dialog.c config-gtk.c gtk-common.c config-unix.c unicode.c printing.c) + add_dependencies(guiterminal generated_licence_h) # dialog.c uses licence.h add_executable(pageant - uxpgnt.c + pageant.c ${CMAKE_SOURCE_DIR}/be_misc.c ${CMAKE_SOURCE_DIR}/be_none.c ${CMAKE_SOURCE_DIR}/nogss.c - gtkask.c - ux_x11.c - uxnoise.c + askpass.c + x11.c + noise.c ${CMAKE_SOURCE_DIR}/ssh/x11fwd.c) target_link_libraries(pageant eventloop console agent settings network crypto utils @@ -171,30 +174,30 @@ if(GTK_FOUND) installed_program(pageant) add_executable(pterm - uxpterm.c - gtkmain.c + pterm.c + main-gtk-simple.c ${CMAKE_SOURCE_DIR}/be_none.c ${CMAKE_SOURCE_DIR}/nogss.c - uxpty.c) + pty.c) target_link_libraries(pterm guiterminal eventloop settings charset utils ${GTK_LIBRARIES} ${X11_LIBRARIES}) installed_program(pterm) add_executable(ptermapp - uxpterm.c - gtkapp.c + pterm.c + main-gtk-application.c ${CMAKE_SOURCE_DIR}/nocmdline.c ${CMAKE_SOURCE_DIR}/be_none.c ${CMAKE_SOURCE_DIR}/nogss.c - uxpty.c) + pty.c) target_link_libraries(ptermapp guiterminal eventloop settings charset utils ${GTK_LIBRARIES} ${X11_LIBRARIES}) add_executable(putty - uxputty.c - gtkmain.c + putty.c + main-gtk-simple.c ${CMAKE_SOURCE_DIR}/be_all_s.c) target_link_libraries(putty guiterminal eventloop sshclient otherbackends settings @@ -205,8 +208,8 @@ if(GTK_FOUND) installed_program(putty) add_executable(puttyapp - uxputty.c - gtkapp.c + putty.c + main-gtk-application.c ${CMAKE_SOURCE_DIR}/nocmdline.c ${CMAKE_SOURCE_DIR}/be_all_s.c) target_link_libraries(puttyapp @@ -215,8 +218,8 @@ if(GTK_FOUND) ${GTK_LIBRARIES} ${X11_LIBRARIES}) add_executable(puttytel - uxputty.c - gtkmain.c + putty.c + main-gtk-simple.c ${CMAKE_SOURCE_DIR}/be_nos_s.c ${CMAKE_SOURCE_DIR}/nogss.c ${CMAKE_SOURCE_DIR}/norand.c diff --git a/unix/agent-client.c b/unix/agent-client.c new file mode 100644 index 00000000..e4d671d2 --- /dev/null +++ b/unix/agent-client.c @@ -0,0 +1,226 @@ +/* + * SSH agent client code. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "putty.h" +#include "misc.h" +#include "tree234.h" +#include "puttymem.h" + +bool agent_exists(void) +{ + const char *p = getenv("SSH_AUTH_SOCK"); + if (p && *p) + return true; + return false; +} + +static tree234 *agent_pending_queries; +struct agent_pending_query { + 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) +{ + agent_pending_query *a = (agent_pending_query *) av; + agent_pending_query *b = (agent_pending_query *) 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; + agent_pending_query *b = (agent_pending_query *) bv; + if (afd < b->fd) + return -1; + if (afd > b->fd) + return +1; + return 0; +} + +/* + * Attempt to read from an agent socket fd. Returns false if the + * expected response is as yet incomplete; returns true if it's either + * complete (conn->retbuf non-NULL and filled with something useful) + * or has failed totally (conn->retbuf is NULL). + */ +static bool agent_try_read(agent_pending_query *conn) +{ + int ret; + + ret = read(conn->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; + return true; + } + conn->retlen += ret; + if (conn->retsize == 4 && conn->retlen == 4) { + conn->retsize = toint(GET_32BIT_MSB_FIRST(conn->retbuf) + 4); + if (conn->retsize <= 0) { + conn->retbuf = NULL; + conn->retlen = 0; + return true; /* way too large */ + } + assert(conn->retbuf == conn->sizebuf); + conn->retbuf = snewn(conn->retsize, char); + memcpy(conn->retbuf, conn->sizebuf, 4); + } + + if (conn->retlen < conn->retsize) + return false; /* more data to come */ + + return true; +} + +void agent_cancel_query(agent_pending_query *conn) +{ + uxsel_del(conn->fd); + close(conn->fd); + del234(agent_pending_queries, conn); + if (conn->retbuf && conn->retbuf != conn->sizebuf) + sfree(conn->retbuf); + sfree(conn); +} + +static void agent_select_result(int fd, int event) +{ + agent_pending_query *conn; + + assert(event == SELECT_R); /* not selecting for anything but R */ + + conn = find234(agent_pending_queries, &fd, agent_connfind); + if (!conn) { + uxsel_del(fd); + return; + } + + if (!agent_try_read(conn)) + return; /* more data to come */ + + /* + * We have now completed the agent query. Do the callback. + */ + conn->callback(conn->callback_ctx, conn->retbuf, conn->retlen); + /* Null out conn->retbuf, since ownership of that buffer has + * passed to the callback. */ + conn->retbuf = NULL; + agent_cancel_query(conn); +} + +static const char *agent_socket_path(void) +{ + return getenv("SSH_AUTH_SOCK"); +} + +Socket *agent_connect(Plug *plug) +{ + const char *path = agent_socket_path(); + if (!path) + return new_error_socket_fmt(plug, "SSH_AUTH_SOCK not set"); + return sk_new(unix_sock_addr(path), 0, false, false, false, false, plug); +} + +agent_pending_query *agent_query( + strbuf *query, void **out, int *outlen, + void (*callback)(void *, void *, int), void *callback_ctx) +{ + const char *name; + int sock; + struct sockaddr_un addr; + int done; + agent_pending_query *conn; + + name = agent_socket_path(); + if (!name || strlen(name) >= sizeof(addr.sun_path)) + 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; + strcpy(addr.sun_path, name); + if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + close(sock); + goto failure; + } + + strbuf_finalise_agent_query(query); + + for (done = 0; done < query->len ;) { + int ret = write(sock, query->s + done, + query->len - done); + if (ret <= 0) { + close(sock); + goto failure; + } + done += ret; + } + + conn = snew(agent_pending_query); + conn->fd = sock; + conn->retbuf = conn->sizebuf; + conn->retsize = 4; + conn->retlen = 0; + conn->callback = callback; + conn->callback_ctx = callback_ctx; + + if (!callback) { + /* + * Bodge to permit making deliberately synchronous agent + * requests. Used by Unix Pageant in command-line client mode, + * which is legit because it really is true that no other part + * of the program is trying to get anything useful done + * simultaneously. But this special case shouldn't be used in + * any more general program. + */ + no_nonblock(conn->fd); + while (!agent_try_read(conn)) + /* empty loop body */; + + *out = conn->retbuf; + *outlen = conn->retlen; + sfree(conn); + return NULL; + } + + /* + * Otherwise do it properly: add conn to the tree of agent + * connections currently in flight, return 0 to indicate that the + * response hasn't been received yet, and call the callback when + * select_result comes back to us. + */ + if (!agent_pending_queries) + agent_pending_queries = newtree234(agent_conncmp); + add234(agent_pending_queries, conn); + + uxsel_set(sock, SELECT_R, agent_select_result); + return conn; + + failure: + *out = NULL; + *outlen = 0; + return NULL; +} diff --git a/unix/agent-socket.c b/unix/agent-socket.c new file mode 100644 index 00000000..7e6187c6 --- /dev/null +++ b/unix/agent-socket.c @@ -0,0 +1,91 @@ +#include +#include +#include +#include +#include +#include + +#include + +#include "putty.h" +#include "ssh.h" +#include "misc.h" +#include "pageant.h" + +Socket *platform_make_agent_socket( + Plug *plug, const char *dirprefix, char **error, char **name) +{ + char *username, *socketdir, *socketname, *errw; + const char *err; + Socket *sock; + + *name = NULL; + + username = get_username(); + socketdir = dupprintf("%s.%s", dirprefix, username); + sfree(username); + + assert(*socketdir == '/'); + if ((errw = make_dir_and_check_ours(socketdir)) != NULL) { + *error = dupprintf("%s: %s\n", socketdir, errw); + sfree(errw); + sfree(socketdir); + return NULL; + } + + socketname = dupprintf("%s/pageant.%d", socketdir, (int)getpid()); + sock = new_unix_listener(unix_sock_addr(socketname), plug); + if ((err = sk_socket_error(sock)) != NULL) { + *error = dupprintf("%s: %s\n", socketname, err); + sk_close(sock); + sfree(socketname); + rmdir(socketdir); + sfree(socketdir); + return NULL; + } + + /* + * Spawn a subprocess which will try to reliably delete our socket + * and its containing directory when we terminate, in case we die + * unexpectedly. + */ + { + int cleanup_pipe[2]; + pid_t pid; + + /* Don't worry if pipe or fork fails; it's not _that_ critical. */ + if (!pipe(cleanup_pipe)) { + if ((pid = fork()) == 0) { + int buf[1024]; + /* + * Our parent process holds the writing end of + * this pipe, and writes nothing to it. Hence, + * we expect read() to return EOF as soon as + * that process terminates. + */ + + close(0); + close(1); + close(2); + + setpgid(0, 0); + close(cleanup_pipe[1]); + while (read(cleanup_pipe[0], buf, sizeof(buf)) > 0); + unlink(socketname); + rmdir(socketdir); + _exit(0); + } else if (pid < 0) { + close(cleanup_pipe[0]); + close(cleanup_pipe[1]); + } else { + close(cleanup_pipe[0]); + cloexec(cleanup_pipe[1]); + } + } + } + + *name = socketname; + *error = NULL; + sfree(socketdir); + return sock; +} diff --git a/unix/askpass.c b/unix/askpass.c new file mode 100644 index 00000000..841912d9 --- /dev/null +++ b/unix/askpass.c @@ -0,0 +1,662 @@ +/* + * GTK implementation of a GUI password/passphrase prompt. + */ + +#include +#include +#include + +#include + +#include +#include +#if !GTK_CHECK_VERSION(3,0,0) +#include +#endif + +#include "defs.h" +#include "unifont.h" +#include "gtkcompat.h" +#include "gtkmisc.h" + +#include "putty.h" +#include "ssh.h" +#include "misc.h" + +#define N_DRAWING_AREAS 3 + +struct drawing_area_ctx { + GtkWidget *area; +#ifndef DRAW_DEFAULT_CAIRO + GdkColor *cols; +#endif + int width, height; + enum { NOT_CURRENT, CURRENT, GREYED_OUT } state; +}; + +struct askpass_ctx { + GtkWidget *dialog, *promptlabel; + struct drawing_area_ctx drawingareas[N_DRAWING_AREAS]; + int active_area; +#if GTK_CHECK_VERSION(2,0,0) + GtkIMContext *imc; +#endif +#ifndef DRAW_DEFAULT_CAIRO + GdkColormap *colmap; + GdkColor cols[3]; +#endif + char *error_message; /* if we finish without a passphrase */ + char *passphrase; /* if we finish with one */ + int passlen, passsize; +#if GTK_CHECK_VERSION(3,20,0) + GdkSeat *seat; /* for gdk_seat_grab */ +#elif GTK_CHECK_VERSION(3,0,0) + GdkDevice *keyboard; /* for gdk_device_grab */ +#endif + + int nattempts; +}; + +static prng *keypress_prng = NULL; +static void feed_keypress_prng(void *data, int size) +{ + put_data(keypress_prng, data, size); +} +void random_add_noise(NoiseSourceId source, const void *noise, int length) +{ + if (keypress_prng) + prng_add_entropy(keypress_prng, source, make_ptrlen(noise, length)); +} +static void setup_keypress_prng(void) +{ + keypress_prng = prng_new(&ssh_sha256); + prng_seed_begin(keypress_prng); + noise_get_heavy(feed_keypress_prng); + prng_seed_finish(keypress_prng); +} +static void cleanup_keypress_prng(void) +{ + prng_free(keypress_prng); +} +static uint64_t keypress_prng_value(void) +{ + /* + * Don't actually put the passphrase keystrokes themselves into + * the PRNG; that doesn't seem like the course of wisdom when + * that's precisely what the information displayed on the screen + * is trying _not_ to be correlated to. + */ + noise_ultralight(NOISE_SOURCE_KEY, 0); + uint8_t data[8]; + prng_read(keypress_prng, data, 8); + return GET_64BIT_MSB_FIRST(data); +} +static int choose_new_area(int prev_area) +{ + int reduced = keypress_prng_value() % (N_DRAWING_AREAS - 1); + return (prev_area + 1 + reduced) % N_DRAWING_AREAS; +} + +static void visually_acknowledge_keypress(struct askpass_ctx *ctx) +{ + int new_active = choose_new_area(ctx->active_area); + ctx->drawingareas[ctx->active_area].state = NOT_CURRENT; + gtk_widget_queue_draw(ctx->drawingareas[ctx->active_area].area); + ctx->drawingareas[new_active].state = CURRENT; + gtk_widget_queue_draw(ctx->drawingareas[new_active].area); + ctx->active_area = new_active; +} + +static int last_char_len(struct askpass_ctx *ctx) +{ + /* + * GTK always encodes in UTF-8, so we can do this in a fixed way. + */ + int i; + assert(ctx->passlen > 0); + i = ctx->passlen - 1; + while ((unsigned)((unsigned char)ctx->passphrase[i] - 0x80) < 0x40) { + if (i == 0) + break; + i--; + } + return ctx->passlen - i; +} + +static void add_text_to_passphrase(struct askpass_ctx *ctx, gchar *str) +{ + int len = strlen(str); + if (ctx->passlen + len >= ctx->passsize) { + /* Take some care with buffer expansion, because there are + * pieces of passphrase in the old buffer so we should ensure + * realloc doesn't leave a copy lying around in the address + * space. */ + int oldsize = ctx->passsize; + char *newbuf; + + ctx->passsize = (ctx->passlen + len) * 5 / 4 + 1024; + newbuf = snewn(ctx->passsize, char); + memcpy(newbuf, ctx->passphrase, oldsize); + smemclr(ctx->passphrase, oldsize); + sfree(ctx->passphrase); + ctx->passphrase = newbuf; + } + strcpy(ctx->passphrase + ctx->passlen, str); + ctx->passlen += len; + visually_acknowledge_keypress(ctx); +} + +static void cancel_askpass(struct askpass_ctx *ctx, const char *msg) +{ + smemclr(ctx->passphrase, ctx->passsize); + ctx->passphrase = NULL; + ctx->error_message = dupstr(msg); + gtk_main_quit(); +} + +static gboolean askpass_dialog_closed(GtkWidget *widget, GdkEvent *event, + gpointer data) +{ + struct askpass_ctx *ctx = (struct askpass_ctx *)data; + cancel_askpass(ctx, "passphrase input cancelled"); + /* Don't destroy dialog yet, so gtk_askpass_cleanup() can do its work */ + return true; +} + +static gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) +{ + struct askpass_ctx *ctx = (struct askpass_ctx *)data; + + if (event->keyval == GDK_KEY_Return && + event->type == GDK_KEY_PRESS) { + gtk_main_quit(); + } else if (event->keyval == GDK_KEY_Escape && + event->type == GDK_KEY_PRESS) { + cancel_askpass(ctx, "passphrase input cancelled"); + } else { +#if GTK_CHECK_VERSION(2,0,0) + if (gtk_im_context_filter_keypress(ctx->imc, event)) + return true; +#endif + + if (event->type == GDK_KEY_PRESS) { + if (!strcmp(event->string, "\x15")) { + /* Ctrl-U. Wipe out the whole line */ + ctx->passlen = 0; + visually_acknowledge_keypress(ctx); + } else if (!strcmp(event->string, "\x17")) { + /* Ctrl-W. Delete back to the last space->nonspace + * boundary. We interpret 'space' in a really simple + * way (mimicking terminal drivers), and don't attempt + * to second-guess exciting Unicode space + * characters. */ + while (ctx->passlen > 0) { + char deleted, prior; + ctx->passlen -= last_char_len(ctx); + deleted = ctx->passphrase[ctx->passlen]; + prior = (ctx->passlen == 0 ? ' ' : + ctx->passphrase[ctx->passlen-1]); + if (!g_ascii_isspace(deleted) && g_ascii_isspace(prior)) + break; + } + visually_acknowledge_keypress(ctx); + } else if (event->keyval == GDK_KEY_BackSpace) { + /* Backspace. Delete one character. */ + if (ctx->passlen > 0) + ctx->passlen -= last_char_len(ctx); + visually_acknowledge_keypress(ctx); +#if !GTK_CHECK_VERSION(2,0,0) + } else if (event->string[0]) { + add_text_to_passphrase(ctx, event->string); +#endif + } + } + } + return true; +} + +#if GTK_CHECK_VERSION(2,0,0) +static void input_method_commit_event(GtkIMContext *imc, gchar *str, + gpointer data) +{ + struct askpass_ctx *ctx = (struct askpass_ctx *)data; + add_text_to_passphrase(ctx, str); +} +#endif + +static gint configure_area(GtkWidget *widget, GdkEventConfigure *event, + gpointer data) +{ + struct drawing_area_ctx *ctx = (struct drawing_area_ctx *)data; + ctx->width = event->width; + ctx->height = event->height; + gtk_widget_queue_draw(widget); + return true; +} + +#ifdef DRAW_DEFAULT_CAIRO +static void askpass_redraw_cairo(cairo_t *cr, struct drawing_area_ctx *ctx) +{ + double rgbval = (ctx->state == CURRENT ? 0 : + ctx->state == NOT_CURRENT ? 1 : 0.5); + cairo_set_source_rgb(cr, rgbval, rgbval, rgbval); + cairo_paint(cr); +} +#else +static void askpass_redraw_gdk(GdkWindow *win, struct drawing_area_ctx *ctx) +{ + GdkGC *gc = gdk_gc_new(win); + gdk_gc_set_foreground(gc, &ctx->cols[ctx->state]); + gdk_draw_rectangle(win, gc, true, 0, 0, ctx->width, ctx->height); + gdk_gc_unref(gc); +} +#endif + +#if GTK_CHECK_VERSION(3,0,0) +static gint draw_area(GtkWidget *widget, cairo_t *cr, gpointer data) +{ + struct drawing_area_ctx *ctx = (struct drawing_area_ctx *)data; + askpass_redraw_cairo(cr, ctx); + return true; +} +#else +static gint expose_area(GtkWidget *widget, GdkEventExpose *event, + gpointer data) +{ + struct drawing_area_ctx *ctx = (struct drawing_area_ctx *)data; + +#ifdef DRAW_DEFAULT_CAIRO + cairo_t *cr = gdk_cairo_create(gtk_widget_get_window(ctx->area)); + askpass_redraw_cairo(cr, ctx); + cairo_destroy(cr); +#else + askpass_redraw_gdk(gtk_widget_get_window(ctx->area), ctx); +#endif + + return true; +} +#endif + +static gboolean try_grab_keyboard(gpointer vctx) +{ + struct askpass_ctx *ctx = (struct askpass_ctx *)vctx; + int i, ret; + +#if GTK_CHECK_VERSION(3,20,0) + /* + * Grabbing the keyboard in GTK 3.20 requires the new notion of + * GdkSeat. + */ + GdkSeat *seat; + GdkWindow *gdkw = gtk_widget_get_window(ctx->dialog); + if (!GDK_IS_WINDOW(gdkw) || !gdk_window_is_visible(gdkw)) + goto fail; + + seat = gdk_display_get_default_seat + (gtk_widget_get_display(ctx->dialog)); + if (!seat) + goto fail; + + ctx->seat = seat; + ret = gdk_seat_grab(seat, gdkw, GDK_SEAT_CAPABILITY_KEYBOARD, + true, NULL, NULL, NULL, NULL); + + /* + * For some reason GDK 3.22 hides the GDK window as a side effect + * of a failed grab. I've no idea why. But if we're going to retry + * the grab, then we need to unhide it again or else we'll just + * get GDK_GRAB_NOT_VIEWABLE on every subsequent attempt. + */ + if (ret != GDK_GRAB_SUCCESS) + gdk_window_show(gdkw); + +#elif GTK_CHECK_VERSION(3,0,0) + /* + * And it has to be done differently again prior to GTK 3.20. + */ + GdkDeviceManager *dm; + GdkDevice *pointer, *keyboard; + + dm = gdk_display_get_device_manager + (gtk_widget_get_display(ctx->dialog)); + if (!dm) + goto fail; + + pointer = gdk_device_manager_get_client_pointer(dm); + if (!pointer) + goto fail; + keyboard = gdk_device_get_associated_device(pointer); + if (!keyboard) + goto fail; + if (gdk_device_get_source(keyboard) != GDK_SOURCE_KEYBOARD) + goto fail; + + ctx->keyboard = keyboard; + ret = gdk_device_grab(ctx->keyboard, + gtk_widget_get_window(ctx->dialog), + GDK_OWNERSHIP_NONE, + true, + GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK, + NULL, + GDK_CURRENT_TIME); +#else + /* + * It's much simpler in GTK 1 and 2! + */ + ret = gdk_keyboard_grab(gtk_widget_get_window(ctx->dialog), + false, GDK_CURRENT_TIME); +#endif + if (ret != GDK_GRAB_SUCCESS) + goto fail; + + /* + * Now that we've got the keyboard grab, connect up our keyboard + * handlers. + */ +#if GTK_CHECK_VERSION(2,0,0) + g_signal_connect(G_OBJECT(ctx->imc), "commit", + G_CALLBACK(input_method_commit_event), ctx); +#endif + g_signal_connect(G_OBJECT(ctx->dialog), "key_press_event", + G_CALLBACK(key_event), ctx); + g_signal_connect(G_OBJECT(ctx->dialog), "key_release_event", + G_CALLBACK(key_event), ctx); +#if GTK_CHECK_VERSION(2,0,0) + gtk_im_context_set_client_window(ctx->imc, + gtk_widget_get_window(ctx->dialog)); +#endif + + /* + * And repaint the key-acknowledgment drawing areas as not greyed + * out. + */ + ctx->active_area = keypress_prng_value() % N_DRAWING_AREAS; + for (i = 0; i < N_DRAWING_AREAS; i++) { + ctx->drawingareas[i].state = + (i == ctx->active_area ? CURRENT : NOT_CURRENT); + gtk_widget_queue_draw(ctx->drawingareas[i].area); + } + + return false; + + fail: + /* + * If we didn't get the grab, reschedule ourself on a timer to try + * again later. + * + * We have to do this rather than just trying once, because there + * is at least one important situation in which the grab may fail + * the first time: any user who is launching an add-key operation + * off some kind of window manager hotkey will almost by + * definition be running this script with a keyboard grab already + * active, namely the one-key grab that the WM (or whatever) uses + * to detect presses of the hotkey. So at the very least we have + * to give the user time to release that key. + */ + if (++ctx->nattempts >= 4) { + cancel_askpass(ctx, "unable to grab keyboard after 5 seconds"); + } else { + g_timeout_add(1000/8, try_grab_keyboard, ctx); + } + return false; +} + +void realize(GtkWidget *widget, gpointer vctx) +{ + struct askpass_ctx *ctx = (struct askpass_ctx *)vctx; + + gtk_grab_add(ctx->dialog); + + /* + * Schedule the first attempt at the keyboard grab. + */ + ctx->nattempts = 0; +#if GTK_CHECK_VERSION(3,20,0) + ctx->seat = NULL; +#elif GTK_CHECK_VERSION(3,0,0) + ctx->keyboard = NULL; +#endif + + g_idle_add(try_grab_keyboard, ctx); +} + +static const char *gtk_askpass_setup(struct askpass_ctx *ctx, + const char *window_title, + const char *prompt_text) +{ + int i; + GtkBox *action_area; + + ctx->passlen = 0; + ctx->passsize = 2048; + ctx->passphrase = snewn(ctx->passsize, char); + + /* + * Create widgets. + */ + ctx->dialog = our_dialog_new(); + gtk_window_set_title(GTK_WINDOW(ctx->dialog), window_title); + gtk_window_set_position(GTK_WINDOW(ctx->dialog), GTK_WIN_POS_CENTER); + g_signal_connect(G_OBJECT(ctx->dialog), "delete-event", + G_CALLBACK(askpass_dialog_closed), ctx); + ctx->promptlabel = gtk_label_new(prompt_text); + align_label_left(GTK_LABEL(ctx->promptlabel)); + gtk_widget_show(ctx->promptlabel); + gtk_label_set_line_wrap(GTK_LABEL(ctx->promptlabel), true); +#if GTK_CHECK_VERSION(3,0,0) + gtk_label_set_width_chars(GTK_LABEL(ctx->promptlabel), 48); +#endif + int margin = string_width("MM"); +#if GTK_CHECK_VERSION(3,12,0) + gtk_widget_set_margin_start(ctx->promptlabel, margin); + gtk_widget_set_margin_end(ctx->promptlabel, margin); +#else + gtk_misc_set_padding(GTK_MISC(ctx->promptlabel), margin, 0); +#endif + our_dialog_add_to_content_area(GTK_WINDOW(ctx->dialog), + ctx->promptlabel, true, true, 0); +#if GTK_CHECK_VERSION(2,0,0) + ctx->imc = gtk_im_multicontext_new(); +#endif +#ifndef DRAW_DEFAULT_CAIRO + { + gboolean success[2]; + ctx->colmap = gdk_colormap_get_system(); + ctx->cols[0].red = ctx->cols[0].green = ctx->cols[0].blue = 0xFFFF; + ctx->cols[1].red = ctx->cols[1].green = ctx->cols[1].blue = 0; + ctx->cols[2].red = ctx->cols[2].green = ctx->cols[2].blue = 0x8000; + gdk_colormap_alloc_colors(ctx->colmap, ctx->cols, 2, + false, true, success); + if (!success[0] || !success[1]) + return "unable to allocate colours"; + } +#endif + + action_area = our_dialog_make_action_hbox(GTK_WINDOW(ctx->dialog)); + + for (i = 0; i < N_DRAWING_AREAS; i++) { + ctx->drawingareas[i].area = gtk_drawing_area_new(); +#ifndef DRAW_DEFAULT_CAIRO + ctx->drawingareas[i].cols = ctx->cols; +#endif + ctx->drawingareas[i].state = GREYED_OUT; + ctx->drawingareas[i].width = ctx->drawingareas[i].height = 0; + /* It would be nice to choose this size in some more + * context-sensitive way, like measuring the size of some + * piece of template text. */ + gtk_widget_set_size_request(ctx->drawingareas[i].area, 32, 32); + gtk_box_pack_end(action_area, ctx->drawingareas[i].area, + true, true, 5); + g_signal_connect(G_OBJECT(ctx->drawingareas[i].area), + "configure_event", + G_CALLBACK(configure_area), + &ctx->drawingareas[i]); +#if GTK_CHECK_VERSION(3,0,0) + g_signal_connect(G_OBJECT(ctx->drawingareas[i].area), + "draw", + G_CALLBACK(draw_area), + &ctx->drawingareas[i]); +#else + g_signal_connect(G_OBJECT(ctx->drawingareas[i].area), + "expose_event", + G_CALLBACK(expose_area), + &ctx->drawingareas[i]); +#endif + +#if GTK_CHECK_VERSION(3,0,0) + g_object_set(G_OBJECT(ctx->drawingareas[i].area), + "margin-bottom", 8, (const char *)NULL); +#endif + + gtk_widget_show(ctx->drawingareas[i].area); + } + ctx->active_area = -1; + + /* + * Arrange to receive key events. We don't really need to worry + * from a UI perspective about which widget gets the events, as + * long as we know which it is so we can catch them. So we'll pick + * the prompt label at random, and we'll use gtk_grab_add to + * ensure key events go to it. + */ + gtk_widget_set_sensitive(ctx->dialog, true); + +#if GTK_CHECK_VERSION(2,0,0) + gtk_window_set_keep_above(GTK_WINDOW(ctx->dialog), true); +#endif + + /* + * Wait for the key-receiving widget to actually be created, in + * order to call gtk_grab_add on it. + */ + g_signal_connect(G_OBJECT(ctx->dialog), "realize", + G_CALLBACK(realize), ctx); + + /* + * Show the window. + */ + gtk_widget_show(ctx->dialog); + + return NULL; +} + +static void gtk_askpass_cleanup(struct askpass_ctx *ctx) +{ +#if GTK_CHECK_VERSION(3,20,0) + if (ctx->seat) + gdk_seat_ungrab(ctx->seat); +#elif GTK_CHECK_VERSION(3,0,0) + if (ctx->keyboard) + gdk_device_ungrab(ctx->keyboard, GDK_CURRENT_TIME); +#else + gdk_keyboard_ungrab(GDK_CURRENT_TIME); +#endif + gtk_grab_remove(ctx->promptlabel); + + if (ctx->passphrase) { + assert(ctx->passlen < ctx->passsize); + ctx->passphrase[ctx->passlen] = '\0'; + } + + gtk_widget_destroy(ctx->dialog); +} + +static bool setup_gtk(const char *display) +{ + static bool gtk_initialised = false; + int argc; + char *real_argv[3]; + char **argv = real_argv; + bool ret; + + if (gtk_initialised) + return true; + + argc = 0; + argv[argc++] = dupstr("dummy"); + argv[argc++] = dupprintf("--display=%s", display); + argv[argc] = NULL; + ret = gtk_init_check(&argc, &argv); + while (argc > 0) + sfree(argv[--argc]); + + gtk_initialised = ret; + return ret; +} + +const bool buildinfo_gtk_relevant = true; + +char *gtk_askpass_main(const char *display, const char *wintitle, + const char *prompt, bool *success) +{ + struct askpass_ctx ctx[1]; + const char *err; + + ctx->passphrase = NULL; + ctx->error_message = NULL; + + /* In case gtk_init hasn't been called yet by the program */ + if (!setup_gtk(display)) { + *success = false; + return dupstr("unable to initialise GTK"); + } + + if ((err = gtk_askpass_setup(ctx, wintitle, prompt)) != NULL) { + *success = false; + return dupprintf("%s", err); + } + setup_keypress_prng(); + gtk_main(); + cleanup_keypress_prng(); + gtk_askpass_cleanup(ctx); + + if (ctx->passphrase) { + *success = true; + return ctx->passphrase; + } else { + *success = false; + return ctx->error_message; + } +} + +#ifdef TEST_ASKPASS +void modalfatalbox(const 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); +} + +int main(int argc, char **argv) +{ + bool success; + int exitcode; + char *ret; + + gtk_init(&argc, &argv); + + if (argc != 2) { + success = false; + ret = dupprintf("usage: %s ", argv[0]); + } else { + ret = gtk_askpass_main(NULL, "Enter passphrase", argv[1], &success); + } + + if (!success) { + fputs(ret, stderr); + fputc('\n', stderr); + exitcode = 1; + } else { + fputs(ret, stdout); + fputc('\n', stdout); + exitcode = 0; + } + + smemclr(ret, strlen(ret)); + return exitcode; +} +#endif diff --git a/unix/cliloop.c b/unix/cliloop.c new file mode 100644 index 00000000..23a808da --- /dev/null +++ b/unix/cliloop.c @@ -0,0 +1,125 @@ +#include + +#include "putty.h" + +void cli_main_loop(cliloop_pw_setup_t pw_setup, + cliloop_pw_check_t pw_check, + cliloop_continue_t cont, void *ctx) +{ + unsigned long now = GETTICKCOUNT(); + + int *fdlist = NULL; + size_t fdsize = 0; + + pollwrapper *pw = pollwrap_new(); + + while (true) { + int rwx; + int ret; + int fdstate; + unsigned long next; + + pollwrap_clear(pw); + + if (!pw_setup(ctx, pw)) + break; /* our client signalled emergency exit */ + + /* Count the currently active fds. */ + size_t nfds = 0; + for (int fd = first_fd(&fdstate, &rwx); fd >= 0; + fd = next_fd(&fdstate, &rwx)) + nfds++; + + /* Expand the fdlist buffer if necessary. */ + sgrowarray(fdlist, fdsize, nfds); + + /* + * Add all currently open uxsel fds to pw, and store them in + * fdlist as well. + */ + size_t fdcount = 0; + for (int fd = first_fd(&fdstate, &rwx); fd >= 0; + fd = next_fd(&fdstate, &rwx)) { + fdlist[fdcount++] = fd; + pollwrap_add_fd_rwx(pw, fd, rwx); + } + + if (toplevel_callback_pending()) { + ret = pollwrap_poll_instant(pw); + } else if (run_timers(now, &next)) { + do { + unsigned long then; + long ticks; + + then = now; + now = GETTICKCOUNT(); + if (now - then > next - then) + ticks = 0; + else + ticks = next - now; + + bool overflow = false; + if (ticks > INT_MAX) { + ticks = INT_MAX; + overflow = true; + } + + ret = pollwrap_poll_timeout(pw, ticks); + if (ret == 0 && !overflow) + now = next; + else + now = GETTICKCOUNT(); + } while (ret < 0 && errno == EINTR); + } else { + ret = pollwrap_poll_endless(pw); + } + + if (ret < 0 && errno == EINTR) + continue; + + if (ret < 0) { + perror("poll"); + exit(1); + } + + bool found_fd = (ret > 0); + + for (size_t i = 0; i < fdcount; i++) { + int fd = fdlist[i]; + int rwx = pollwrap_get_fd_rwx(pw, fd); + /* + * We must process exceptional notifications before + * ordinary readability ones, or we may go straight + * past the urgent marker. + */ + if (rwx & SELECT_X) + select_result(fd, SELECT_X); + if (rwx & SELECT_R) + select_result(fd, SELECT_R); + if (rwx & SELECT_W) + select_result(fd, SELECT_W); + } + + pw_check(ctx, pw); + + bool ran_callback = run_toplevel_callbacks(); + + if (!cont(ctx, found_fd, ran_callback)) + break; + } + + pollwrap_free(pw); + sfree(fdlist); +} + +bool cliloop_no_pw_setup(void *ctx, pollwrapper *pw) { return true; } +void cliloop_no_pw_check(void *ctx, pollwrapper *pw) {} +bool cliloop_always_continue(void *ctx, bool fd, bool cb) { return true; } + +/* + * Any application using this main loop doesn't need to do anything + * when uxsel adds or removes an fd, because we synchronously re-check + * the current list every time we go round the main loop above. + */ +uxsel_id *uxsel_input_add(int fd, int rwx) { return NULL; } +void uxsel_input_remove(uxsel_id *id) { } diff --git a/unix/columns.c b/unix/columns.c new file mode 100644 index 00000000..d1c0cb28 --- /dev/null +++ b/unix/columns.c @@ -0,0 +1,1201 @@ +/* + * gtkcols.c - implementation of the `Columns' GTK layout container. + */ + +#include +#include +#include "defs.h" +#include "gtkcompat.h" +#include "columns.h" + +#if GTK_CHECK_VERSION(2,0,0) +/* The "focus" method lives in GtkWidget from GTK 2 onwards, but it + * was in GtkContainer in GTK 1 */ +#define FOCUS_METHOD_SUPERCLASS GtkWidget +#define FOCUS_METHOD_LOCATION widget_class /* used in columns_init */ +#define CHILD_FOCUS(cont, dir) gtk_widget_child_focus(GTK_WIDGET(cont), dir) +#else +#define FOCUS_METHOD_SUPERCLASS GtkContainer +#define FOCUS_METHOD_LOCATION container_class +#define CHILD_FOCUS(cont, dir) gtk_container_focus(GTK_CONTAINER(cont), dir) +#endif + +static void columns_init(Columns *cols); +static void columns_class_init(ColumnsClass *klass); +#if !GTK_CHECK_VERSION(2,0,0) +static void columns_finalize(GtkObject *object); +#else +static void columns_finalize(GObject *object); +#endif +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); +static gint columns_focus(FOCUS_METHOD_SUPERCLASS *container, + GtkDirectionType dir); +static GType columns_child_type(GtkContainer *container); +#if GTK_CHECK_VERSION(3,0,0) +static void columns_get_preferred_width(GtkWidget *widget, + gint *min, gint *nat); +static void columns_get_preferred_height(GtkWidget *widget, + gint *min, gint *nat); +static void columns_get_preferred_width_for_height(GtkWidget *widget, + gint height, + gint *min, gint *nat); +static void columns_get_preferred_height_for_width(GtkWidget *widget, + gint width, + gint *min, gint *nat); +#else +static void columns_size_request(GtkWidget *widget, GtkRequisition *req); +#endif +static void columns_size_allocate(GtkWidget *widget, GtkAllocation *alloc); + +static GtkContainerClass *parent_class = NULL; + +#if !GTK_CHECK_VERSION(2,0,0) +GType columns_get_type(void) +{ + static GType 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 + +static gint (*columns_inherited_focus)(FOCUS_METHOD_SUPERCLASS *container, + GtkDirectionType direction); + +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 + + object_class->finalize = columns_finalize; + 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 +#if GTK_CHECK_VERSION(3,0,0) + widget_class->get_preferred_width = columns_get_preferred_width; + widget_class->get_preferred_height = columns_get_preferred_height; + widget_class->get_preferred_width_for_height = + columns_get_preferred_width_for_height; + widget_class->get_preferred_height_for_width = + columns_get_preferred_height_for_width; +#else + widget_class->size_request = columns_size_request; +#endif + 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; + + /* Save the previous value of this method. */ + if (!columns_inherited_focus) + columns_inherited_focus = FOCUS_METHOD_LOCATION->focus; + FOCUS_METHOD_LOCATION->focus = columns_focus; +} + +static void columns_init(Columns *cols) +{ + gtk_widget_set_has_window(GTK_WIDGET(cols), false); + + cols->children = NULL; + cols->spacing = 0; +} + +static void columns_child_free(gpointer vchild) +{ + ColumnsChild *child = (ColumnsChild *)vchild; + if (child->percentages) + g_free(child->percentages); + g_free(child); +} + +static void columns_finalize( +#if !GTK_CHECK_VERSION(2,0,0) + GtkObject *object +#else + GObject *object +#endif + ) +{ + Columns *cols; + + g_return_if_fail(object != NULL); + g_return_if_fail(IS_COLUMNS(object)); + + cols = COLUMNS(object); + +#if !GTK_CHECK_VERSION(2,0,0) + { + GList *node; + for (node = cols->children; node; node = node->next) + if (node->data) + columns_child_free(node->data); + } + g_list_free(cols->children); +#else + g_list_free_full(cols->children, columns_child_free); +#endif + + cols->children = NULL; + +#if !GTK_CHECK_VERSION(2,0,0) + GTK_OBJECT_CLASS(parent_class)->finalize(object); +#else + G_OBJECT_CLASS(parent_class)->finalize(object); +#endif +} + +/* + * 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_mapped(GTK_WIDGET(cols), true); + + for (children = cols->children; + children && (child = children->data); + children = children->next) { + if (child->widget && + gtk_widget_get_visible(child->widget) && + !gtk_widget_get_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_set_mapped(GTK_WIDGET(cols), false); + + for (children = cols->children; + children && (child = children->data); + children = children->next) { + if (child->widget && + gtk_widget_get_visible(child->widget) && + gtk_widget_get_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; + bool 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_get_visible(widget); + gtk_widget_unparent(widget); + cols->children = g_list_remove_link(cols->children, children); + g_list_free(children); + + if (child->same_height_as) { + g_return_if_fail(child->same_height_as->same_height_as == child); + child->same_height_as->same_height_as = NULL; + if (gtk_widget_get_visible(child->same_height_as->widget)) + gtk_widget_queue_resize(GTK_WIDGET(container)); + } + + 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); + 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 GType 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(gtk_widget_get_parent(child) == NULL); + + childdata = g_new(ColumnsChild, 1); + childdata->widget = child; + childdata->colstart = colstart; + childdata->colspan = colspan; + childdata->force_left = false; + childdata->same_height_as = NULL; + childdata->percentages = NULL; + + 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_widget_get_realized(GTK_WIDGET(cols))) + gtk_widget_realize(child); + + if (gtk_widget_get_visible(GTK_WIDGET(cols)) && + gtk_widget_get_visible(child)) { + if (gtk_widget_get_mapped(GTK_WIDGET(cols))) + gtk_widget_map(child); + gtk_widget_queue_resize(child); + } +} + +static ColumnsChild *columns_find_child(Columns *cols, GtkWidget *widget) +{ + GList *children; + ColumnsChild *child; + + for (children = cols->children; + children && (child = children->data); + children = children->next) { + + if (child->widget == widget) + return child; + } + + return NULL; +} + +void columns_force_left_align(Columns *cols, GtkWidget *widget) +{ + ColumnsChild *child; + + g_return_if_fail(cols != NULL); + g_return_if_fail(IS_COLUMNS(cols)); + g_return_if_fail(widget != NULL); + + child = columns_find_child(cols, widget); + g_return_if_fail(child != NULL); + + child->force_left = true; + if (gtk_widget_get_visible(widget)) + gtk_widget_queue_resize(GTK_WIDGET(cols)); +} + +void columns_force_same_height(Columns *cols, GtkWidget *cw1, GtkWidget *cw2) +{ + ColumnsChild *child1, *child2; + + g_return_if_fail(cols != NULL); + g_return_if_fail(IS_COLUMNS(cols)); + g_return_if_fail(cw1 != NULL); + g_return_if_fail(cw2 != NULL); + + child1 = columns_find_child(cols, cw1); + g_return_if_fail(child1 != NULL); + child2 = columns_find_child(cols, cw2); + g_return_if_fail(child2 != NULL); + + child1->same_height_as = child2; + child2->same_height_as = child1; + if (gtk_widget_get_visible(cw1) || gtk_widget_get_visible(cw2)) + gtk_widget_queue_resize(GTK_WIDGET(cols)); +} + +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); + break; + } +} + +/* + * Override GtkContainer's focus movement so the user can + * explicitly specify the tab order. + */ +static gint columns_focus(FOCUS_METHOD_SUPERCLASS *super, GtkDirectionType dir) +{ + Columns *cols; + GList *pos; + GtkWidget *focuschild; + + g_return_val_if_fail(super != NULL, false); + g_return_val_if_fail(IS_COLUMNS(super), false); + + cols = COLUMNS(super); + + if (!gtk_widget_is_drawable(GTK_WIDGET(cols)) || + !gtk_widget_is_sensitive(GTK_WIDGET(cols))) + return false; + + if (!gtk_widget_get_can_focus(GTK_WIDGET(cols)) && + (dir == GTK_DIR_TAB_FORWARD || dir == GTK_DIR_TAB_BACKWARD)) { + + focuschild = gtk_container_get_focus_child(GTK_CONTAINER(cols)); + gtk_container_set_focus_child(GTK_CONTAINER(cols), 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_is_drawable(child) && + GTK_IS_CONTAINER(child) && + !gtk_widget_has_focus(child)) { + if (CHILD_FOCUS(child, dir)) + return true; + } + } + } else if (gtk_widget_is_drawable(child)) { + if (GTK_IS_CONTAINER(child)) { + if (CHILD_FOCUS(child, dir)) + return true; + } else if (gtk_widget_get_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(super, dir); +} + +/* + * Underlying parts of the layout algorithm, to compute the Columns + * container's width or height given the widths or heights of its + * children. These will be called in various ways with different + * notions of width and height in use, so we abstract them out and + * pass them a 'get width' or 'get height' function pointer. + */ + +typedef gint (*widget_dim_fn_t)(ColumnsChild *child); + +static gint columns_compute_width(Columns *cols, widget_dim_fn_t get_width) +{ + ColumnsChild *child; + GList *children; + gint i, ncols, colspan, retwidth, childwidth; + const gint *percentages; + static const gint onecol[] = { 100 }; + +#ifdef COLUMNS_WIDTH_DIAGNOSTICS + printf("compute_width(%p): start\n", cols); +#endif + + retwidth = 0; + + ncols = 1; + percentages = onecol; + + for (children = cols->children; + children && (child = children->data); + children = children->next) { + + if (!child->widget) { + /* Column reconfiguration. */ + ncols = child->ncols; + percentages = child->percentages; + continue; + } + + /* Only take visible widgets into account. */ + if (!gtk_widget_get_visible(child->widget)) + continue; + + childwidth = get_width(child); + colspan = child->colspan ? child->colspan : ncols-child->colstart; + assert(colspan > 0); + +#ifdef COLUMNS_WIDTH_DIAGNOSTICS + printf("compute_width(%p): ", cols); + if (GTK_IS_LABEL(child->widget)) + printf("label %p '%s' wrap=%s: ", child->widget, + gtk_label_get_text(GTK_LABEL(child->widget)), + (gtk_label_get_line_wrap(GTK_LABEL(child->widget)) + ? "true" : "false")); + else + printf("widget %p: ", child->widget); + { + gint min, nat; + gtk_widget_get_preferred_width(child->widget, &min, &nat); + printf("minwidth=%d natwidth=%d ", min, nat); + } + printf("thiswidth=%d span=%d\n", childwidth, colspan); +#endif + + /* + * To compute width: we know that childwidth + 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 = childwidth + cols->spacing; + /* + * Since childwidth is (at least sometimes) 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; +#ifdef COLUMNS_WIDTH_DIAGNOSTICS + printf("compute_width(%p): after %p, thiswid=%d fullwid=%d\n", + cols, child->widget, thiswid, fullwid); +#endif + + /* + * 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 (retwidth < fullwid - cols->spacing) + retwidth = fullwid - cols->spacing; + } + } + + retwidth += 2*gtk_container_get_border_width(GTK_CONTAINER(cols)); + +#ifdef COLUMNS_WIDTH_DIAGNOSTICS + printf("compute_width(%p): done, returning %d\n", cols, retwidth); +#endif + + return retwidth; +} + +static void columns_alloc_horiz(Columns *cols, gint ourwidth, + widget_dim_fn_t get_width) +{ + ColumnsChild *child; + GList *children; + gint i, ncols, colspan, border, *colxpos, childwidth; + + border = gtk_container_get_border_width(GTK_CONTAINER(cols)); + + ncols = 1; + /* 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] = ourwidth - 2*border + cols->spacing; + + for (children = cols->children; + children && (child = children->data); + children = children->next) { + + if (!child->widget) { + gint percent; + + /* Column reconfiguration. */ + ncols = child->ncols; + colxpos = g_renew(gint, colxpos, ncols + 1); + colxpos[0] = 0; + percent = 0; + for (i = 0; i < ncols; i++) { + percent += child->percentages[i]; + colxpos[i+1] = (((ourwidth - 2*border) + cols->spacing) + * percent / 100); + } + continue; + } + + /* Only take visible widgets into account. */ + if (!gtk_widget_get_visible(child->widget)) + continue; + + childwidth = get_width(child); + 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. + */ + child->x = colxpos[child->colstart]; + if (child->force_left) + child->w = childwidth; + else + child->w = (colxpos[child->colstart+colspan] - + colxpos[child->colstart] - cols->spacing); + } + + g_free(colxpos); +} + +static gint columns_compute_height(Columns *cols, widget_dim_fn_t get_height) +{ + ColumnsChild *child; + GList *children; + gint i, ncols, colspan, *colypos, retheight, childheight; + + retheight = cols->spacing; + + ncols = 1; + colypos = g_new(gint, 1); + colypos[0] = 0; + + for (children = cols->children; + children && (child = children->data); + children = children->next) { + + if (!child->widget) { + /* Column reconfiguration. */ + for (i = 1; i < ncols; i++) { + if (colypos[0] < colypos[i]) + colypos[0] = colypos[i]; + } + ncols = child->ncols; + 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_get_visible(child->widget)) + continue; + + childheight = get_height(child); + if (child->same_height_as) { + gint childheight2 = get_height(child->same_height_as); + if (childheight < childheight2) + childheight = childheight2; + } + colspan = child->colspan ? child->colspan : ncols-child->colstart; + + /* + * 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 childheight 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 height. + */ + { + int topy, boty; + + topy = 0; + for (i = 0; i < colspan; i++) { + if (topy < colypos[child->colstart+i]) + topy = colypos[child->colstart+i]; + } + boty = topy + childheight + cols->spacing; + for (i = 0; i < colspan; i++) { + colypos[child->colstart+i] = boty; + } + + if (retheight < boty - cols->spacing) + retheight = boty - cols->spacing; + } + } + + retheight += 2*gtk_container_get_border_width(GTK_CONTAINER(cols)); + + g_free(colypos); + + return retheight; +} + +static void columns_alloc_vert(Columns *cols, gint ourheight, + widget_dim_fn_t get_height) +{ + ColumnsChild *child; + GList *children; + gint i, ncols, colspan, *colypos, realheight, fakeheight; + + ncols = 1; + /* 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) { + if (!child->widget) { + /* Column reconfiguration. */ + for (i = 1; i < ncols; i++) { + if (colypos[0] < colypos[i]) + colypos[0] = colypos[i]; + } + ncols = child->ncols; + 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_get_visible(child->widget)) + continue; + + realheight = fakeheight = get_height(child); + if (child->same_height_as) { + gint childheight2 = get_height(child->same_height_as); + if (fakeheight < childheight2) + fakeheight = childheight2; + } + colspan = child->colspan ? child->colspan : ncols-child->colstart; + + /* + * 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]; + } + child->y = topy + fakeheight/2 - realheight/2; + child->h = realheight; + boty = topy + fakeheight + cols->spacing; + for (i = 0; i < colspan; i++) { + colypos[child->colstart+i] = boty; + } + } + } + + g_free(colypos); +} + +/* + * 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. + */ + +#if !GTK_CHECK_VERSION(3,0,0) + +static gint columns_gtk2_get_width(ColumnsChild *child) +{ + GtkRequisition creq; + gtk_widget_size_request(child->widget, &creq); + return creq.width; +} + +static gint columns_gtk2_get_height(ColumnsChild *child) +{ + GtkRequisition creq; + gtk_widget_size_request(child->widget, &creq); + return creq.height; +} + +static void columns_size_request(GtkWidget *widget, GtkRequisition *req) +{ + Columns *cols; + + g_return_if_fail(widget != NULL); + g_return_if_fail(IS_COLUMNS(widget)); + g_return_if_fail(req != NULL); + + cols = COLUMNS(widget); + + req->width = columns_compute_width(cols, columns_gtk2_get_width); + req->height = columns_compute_height(cols, columns_gtk2_get_height); +} + +static void columns_size_allocate(GtkWidget *widget, GtkAllocation *alloc) +{ + Columns *cols; + ColumnsChild *child; + GList *children; + gint border; + + g_return_if_fail(widget != NULL); + g_return_if_fail(IS_COLUMNS(widget)); + g_return_if_fail(alloc != NULL); + + cols = COLUMNS(widget); + gtk_widget_set_allocation(widget, alloc); + + border = gtk_container_get_border_width(GTK_CONTAINER(cols)); + + columns_alloc_horiz(cols, alloc->width, columns_gtk2_get_width); + columns_alloc_vert(cols, alloc->height, columns_gtk2_get_height); + + for (children = cols->children; + children && (child = children->data); + children = children->next) { + if (child->widget && gtk_widget_get_visible(child->widget)) { + GtkAllocation call; + call.x = alloc->x + border + child->x; + call.y = alloc->y + border + child->y; + call.width = child->w; + call.height = child->h; + gtk_widget_size_allocate(child->widget, &call); + } + } +} + +#else /* GTK_CHECK_VERSION(3,0,0) */ + +static gint columns_gtk3_get_min_width(ColumnsChild *child) +{ + gint ret; + gtk_widget_get_preferred_width(child->widget, &ret, NULL); + return ret; +} + +static gint columns_gtk3_get_nat_width(ColumnsChild *child) +{ + gint ret; + + if ((GTK_IS_LABEL(child->widget) && + gtk_label_get_line_wrap(GTK_LABEL(child->widget))) || + GTK_IS_ENTRY(child->widget)) { + /* + * We treat wrapping GtkLabels as a special case in this + * layout class, because the whole point of those is that I + * _don't_ want them to take up extra horizontal space for + * long text, but instead to wrap it to whatever size is used + * by the rest of the layout. + * + * GtkEntry gets similar treatment, because in OS X GTK I've + * found that it requests a natural width regardless of the + * output of gtk_entry_set_width_chars. + */ + gtk_widget_get_preferred_width(child->widget, &ret, NULL); + } else { + gtk_widget_get_preferred_width(child->widget, NULL, &ret); + } + return ret; +} + +static gint columns_gtk3_get_minfh_width(ColumnsChild *child) +{ + gint ret; + gtk_widget_get_preferred_width_for_height(child->widget, child->h, + &ret, NULL); + return ret; +} + +static gint columns_gtk3_get_natfh_width(ColumnsChild *child) +{ + gint ret; + gtk_widget_get_preferred_width_for_height(child->widget, child->h, + NULL, &ret); + return ret; +} + +static gint columns_gtk3_get_min_height(ColumnsChild *child) +{ + gint ret; + gtk_widget_get_preferred_height(child->widget, &ret, NULL); + return ret; +} + +static gint columns_gtk3_get_nat_height(ColumnsChild *child) +{ + gint ret; + gtk_widget_get_preferred_height(child->widget, NULL, &ret); + return ret; +} + +static gint columns_gtk3_get_minfw_height(ColumnsChild *child) +{ + gint ret; + gtk_widget_get_preferred_height_for_width(child->widget, child->w, + &ret, NULL); + return ret; +} + +static gint columns_gtk3_get_natfw_height(ColumnsChild *child) +{ + gint ret; + gtk_widget_get_preferred_height_for_width(child->widget, child->w, + NULL, &ret); + return ret; +} + +static void columns_get_preferred_width(GtkWidget *widget, + gint *min, gint *nat) +{ + Columns *cols; + + g_return_if_fail(widget != NULL); + g_return_if_fail(IS_COLUMNS(widget)); + + cols = COLUMNS(widget); + + if (min) + *min = columns_compute_width(cols, columns_gtk3_get_min_width); + if (nat) + *nat = columns_compute_width(cols, columns_gtk3_get_nat_width); +} + +static void columns_get_preferred_height(GtkWidget *widget, + gint *min, gint *nat) +{ + Columns *cols; + + g_return_if_fail(widget != NULL); + g_return_if_fail(IS_COLUMNS(widget)); + + cols = COLUMNS(widget); + + if (min) + *min = columns_compute_height(cols, columns_gtk3_get_min_height); + if (nat) + *nat = columns_compute_height(cols, columns_gtk3_get_nat_height); +} + +static void columns_get_preferred_width_for_height(GtkWidget *widget, + gint height, + gint *min, gint *nat) +{ + Columns *cols; + + g_return_if_fail(widget != NULL); + g_return_if_fail(IS_COLUMNS(widget)); + + cols = COLUMNS(widget); + + /* FIXME: which one should the get-height function here be? */ + columns_alloc_vert(cols, height, columns_gtk3_get_nat_height); + + if (min) + *min = columns_compute_width(cols, columns_gtk3_get_minfh_width); + if (nat) + *nat = columns_compute_width(cols, columns_gtk3_get_natfh_width); +} + +static void columns_get_preferred_height_for_width(GtkWidget *widget, + gint width, + gint *min, gint *nat) +{ + Columns *cols; + + g_return_if_fail(widget != NULL); + g_return_if_fail(IS_COLUMNS(widget)); + + cols = COLUMNS(widget); + + /* FIXME: which one should the get-height function here be? */ + columns_alloc_horiz(cols, width, columns_gtk3_get_nat_width); + + if (min) + *min = columns_compute_height(cols, columns_gtk3_get_minfw_height); + if (nat) + *nat = columns_compute_height(cols, columns_gtk3_get_natfw_height); +} + +static void columns_size_allocate(GtkWidget *widget, GtkAllocation *alloc) +{ + Columns *cols; + ColumnsChild *child; + GList *children; + gint border; + + g_return_if_fail(widget != NULL); + g_return_if_fail(IS_COLUMNS(widget)); + g_return_if_fail(alloc != NULL); + + cols = COLUMNS(widget); + gtk_widget_set_allocation(widget, alloc); + + border = gtk_container_get_border_width(GTK_CONTAINER(cols)); + + columns_alloc_horiz(cols, alloc->width, columns_gtk3_get_min_width); + columns_alloc_vert(cols, alloc->height, columns_gtk3_get_minfw_height); + + for (children = cols->children; + children && (child = children->data); + children = children->next) { + if (child->widget && gtk_widget_get_visible(child->widget)) { + GtkAllocation call; + call.x = alloc->x + border + child->x; + call.y = alloc->y + border + child->y; + call.width = child->w; + call.height = child->h; + gtk_widget_size_allocate(child->widget, &call); + } + } +} + +#endif diff --git a/unix/columns.h b/unix/columns.h new file mode 100644 index 00000000..a850a166 --- /dev/null +++ b/unix/columns.h @@ -0,0 +1,65 @@ +/* + * columns.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 +#include + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#define TYPE_COLUMNS (columns_get_type()) +#define COLUMNS(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), TYPE_COLUMNS, Columns)) +#define COLUMNS_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), TYPE_COLUMNS, ColumnsClass)) +#define IS_COLUMNS(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), TYPE_COLUMNS)) +#define IS_COLUMNS_CLASS(klass) (G_TYPE_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; + bool force_left; /* for recalcitrant GtkLabels */ + ColumnsChild *same_height_as; + /* Otherwise, this entry represents a change in the column setup. */ + gint ncols; + gint *percentages; + gint x, y, w, h; /* used during an individual size computation */ +}; + +GType 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); +void columns_force_same_height(Columns *cols, GtkWidget *ch1, GtkWidget *ch2); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* COLUMNS_H */ diff --git a/unix/config-gtk.c b/unix/config-gtk.c new file mode 100644 index 00000000..93c48ce6 --- /dev/null +++ b/unix/config-gtk.c @@ -0,0 +1,160 @@ +/* + * gtkcfg.c - the GTK-specific parts of the PuTTY configuration + * box. + */ + +#include +#include + +#include "putty.h" +#include "dialog.h" +#include "storage.h" + +static void about_handler(union control *ctrl, dlgparam *dlg, + void *data, int event) +{ + if (event == EVENT_ACTION) { + about_box(ctrl->generic.context.p); + } +} + +void gtk_setup_config_box(struct controlbox *b, bool 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), + conf_checkbox_handler, + I(CONF_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 == CONF_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--; + ctrl_free_set(s2); + 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), + conf_fontsel_handler, I(CONF_font)); + ctrl_fontsel(s, "Font used for wide (CJK) text", 'w', + HELPCTX(no_help), + conf_fontsel_handler, I(CONF_widefont)); + s = ctrl_getset(b, "Window/Fonts", "fontbold", + "Fonts for displaying bolded text"); + ctrl_fontsel(s, "Font used for bolded text", 'b', + HELPCTX(no_help), + conf_fontsel_handler, I(CONF_boldfont)); + ctrl_fontsel(s, "Font used for bold wide text", 'i', + HELPCTX(no_help), + conf_fontsel_handler, I(CONF_wideboldfont)); + ctrl_checkbox(s, "Use shadow bold instead of bold fonts", 'u', + HELPCTX(no_help), + conf_checkbox_handler, + I(CONF_shadowbold)); + ctrl_text(s, "(Note that bold fonts or shadow bolding are only" + " used if you have not requested bolding to be done by" + " changing the text colour.)", + HELPCTX(no_help)); + ctrl_editbox(s, "Horizontal offset for shadow bold:", 'z', 20, + HELPCTX(no_help), conf_editbox_handler, + I(CONF_shadowboldoffset), I(-1)); + + /* + * Markus Kuhn feels, not totally unreasonably, that it's good + * for all applications to shift into UTF-8 mode if they notice + * that they've been started with a LANG setting dictating it, + * 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), + conf_checkbox_handler, + I(CONF_utf8_override)); + +#ifdef OSX_META_KEY_CONFIG + /* + * On OS X, there are multiple reasonable opinions about whether + * Option or Command (or both, or neither) should act as a Meta + * key, or whether they should have their normal OS functions. + */ + s = ctrl_getset(b, "Terminal/Keyboard", "meta", + "Choose the Meta key:"); + ctrl_checkbox(s, "Option key acts as Meta", 'p', + HELPCTX(no_help), + conf_checkbox_handler, I(CONF_osx_option_meta)); + ctrl_checkbox(s, "Command key acts as Meta", 'm', + HELPCTX(no_help), + conf_checkbox_handler, I(CONF_osx_command_meta)); +#endif + + 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), conf_editbox_handler, + I(CONF_winclass), I(1)); + } +} diff --git a/unix/config-unix.c b/unix/config-unix.c new file mode 100644 index 00000000..8397a0ac --- /dev/null +++ b/unix/config-unix.c @@ -0,0 +1,70 @@ +/* + * uxcfg.c - the Unix-specific parts of the PuTTY configuration + * box. + */ + +#include +#include + +#include "putty.h" +#include "dialog.h" +#include "storage.h" + +void unix_setup_config_box(struct controlbox *b, bool midsession, int protocol) +{ + struct controlset *s; + union control *c; + + /* + * The Conf structure contains two Unix-specific elements which + * are not configured in here: stamp_utmp and login_shell. This + * is because pterm does not put up a configuration box right at + * the start, which is the only time when these elements would + * be useful to configure. + */ + + /* + * On Unix, we don't have a drop-down list for the printer + * control. + */ + 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 = false; + + /* + * 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 == CONF_proxy_type) { + assert(c->generic.handler == conf_radiobutton_handler); + c->radio.nbuttons++; + c->radio.buttons = + sresize(c->radio.buttons, c->radio.nbuttons, char *); + c->radio.buttons[c->radio.nbuttons-1] = + dupstr("Local"); + 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 == CONF_proxy_telnet_command) { + assert(c->generic.handler == conf_editbox_handler); + sfree(c->generic.label); + c->generic.label = dupstr("Telnet command, or local" + " proxy command"); + break; + } + } + } +} diff --git a/unix/console.c b/unix/console.c new file mode 100644 index 00000000..90e73a98 --- /dev/null +++ b/unix/console.c @@ -0,0 +1,530 @@ +/* + * uxcons.c: various interactive-prompt routines shared between the + * Unix console PuTTY tools + */ + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "putty.h" +#include "storage.h" +#include "ssh.h" +#include "console.h" + +static struct termios orig_termios_stderr; +static bool 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 (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); +} + +void cleanup_exit(int code) +{ + /* + * Clean up. + */ + sk_cleanup(); + random_save_seed(); + exit(code); +} + +void console_print_error_msg(const char *prefix, const char *msg) +{ + struct termios cf; + premsg(&cf); + fputs(prefix, stderr); + fputs(": ", stderr); + fputs(msg, stderr); + fputc('\n', stderr); + fflush(stderr); + postmsg(&cf); +} + +/* + * Wrapper around Unix read(2), suitable for use on a file descriptor + * that's been set into nonblocking mode. Handles EAGAIN/EWOULDBLOCK + * by means of doing a one-fd poll and then trying again; all other + * errors (including errors from poll) are returned to the caller. + */ +static int block_and_read(int fd, void *buf, size_t len) +{ + int ret; + pollwrapper *pw = pollwrap_new(); + + while ((ret = read(fd, buf, len)) < 0 && ( +#ifdef EAGAIN + (errno == EAGAIN) || +#endif +#ifdef EWOULDBLOCK + (errno == EWOULDBLOCK) || +#endif + false)) { + + pollwrap_clear(pw); + pollwrap_add_fd_rwx(pw, fd, SELECT_R); + do { + ret = pollwrap_poll_endless(pw); + } while (ret < 0 && errno == EINTR); + assert(ret != 0); + if (ret < 0) { + pollwrap_free(pw); + return ret; + } + assert(pollwrap_check_fd_rwx(pw, fd, SELECT_R)); + } + + pollwrap_free(pw); + return ret; +} + +int console_verify_ssh_host_key( + Seat *seat, const char *host, int port, const char *keytype, + char *keystr, const char *keydisp, char **fingerprints, + void (*callback)(void *ctx, int result), void *ctx) +{ + int ret; + + char line[32]; + struct termios cf; + const char *common_fmt, *intro, *prompt; + + /* + * 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 */ + common_fmt = hk_wrongmsg_common_fmt; + intro = hk_wrongmsg_interactive_intro; + prompt = hk_wrongmsg_interactive_prompt; + } else { /* key was absent */ + common_fmt = hk_absentmsg_common_fmt; + intro = hk_absentmsg_interactive_intro; + prompt = hk_absentmsg_interactive_prompt; + } + + FingerprintType fptype_default = + ssh2_pick_default_fingerprint(fingerprints); + + fprintf(stderr, common_fmt, keytype, fingerprints[fptype_default]); + if (console_batch_mode) { + fputs(console_abandoned_msg, stderr); + return 0; + } + + fputs(intro, stderr); + fflush(stderr); + while (true) { + fputs(prompt, stderr); + 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 (block_and_read(0, line, sizeof(line) - 1) <= 0) + /* handled below */; + tcsetattr(0, TCSANOW, &oldmode); + + if (line[0] == 'i' || line[0] == 'I') { + fprintf(stderr, "Full public key:\n%s\n", keydisp); + if (fingerprints[SSH_FPTYPE_SHA256]) + fprintf(stderr, "SHA256 key fingerprint:\n%s\n", + fingerprints[SSH_FPTYPE_SHA256]); + if (fingerprints[SSH_FPTYPE_MD5]) + fprintf(stderr, "MD5 key fingerprint:\n%s\n", + fingerprints[SSH_FPTYPE_MD5]); + } else { + break; + } + } + + /* In case of misplaced reflexes from another program, also recognise 'q' + * as 'abandon connection rather than trust this key' */ + if (line[0] != '\0' && line[0] != '\r' && line[0] != '\n' && + line[0] != 'q' && line[0] != 'Q') { + if (line[0] == 'y' || line[0] == 'Y') + store_host_key(host, port, keytype, keystr); + postmsg(&cf); + return 1; + } else { + fputs(console_abandoned_msg, stderr); + postmsg(&cf); + return 0; + } +} + +int console_confirm_weak_crypto_primitive( + Seat *seat, const char *algtype, const char *algname, + void (*callback)(void *ctx, int result), void *ctx) +{ + char line[32]; + struct termios cf; + + premsg(&cf); + fprintf(stderr, weakcrypto_msg_common_fmt, algtype, algname); + + if (console_batch_mode) { + fputs(console_abandoned_msg, stderr); + postmsg(&cf); + return 0; + } + + fputs(console_continue_prompt, stderr); + 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 (block_and_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 { + fputs(console_abandoned_msg, stderr); + postmsg(&cf); + return 0; + } +} + +int console_confirm_weak_cached_hostkey( + Seat *seat, const char *algname, const char *betteralgs, + void (*callback)(void *ctx, int result), void *ctx) +{ + char line[32]; + struct termios cf; + + premsg(&cf); + fprintf(stderr, weakhk_msg_common_fmt, algname, betteralgs); + + if (console_batch_mode) { + fputs(console_abandoned_msg, stderr); + postmsg(&cf); + return 0; + } + + fputs(console_continue_prompt, stderr); + 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 (block_and_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 { + fputs(console_abandoned_msg, stderr); + 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 console_askappend(LogPolicy *lp, 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 (block_and_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; +} + +bool console_antispoof_prompt = true; +bool console_set_trust_status(Seat *seat, bool trusted) +{ + if (console_batch_mode || !is_interactive() || !console_antispoof_prompt) { + /* + * In batch mode, we don't need to worry about the server + * mimicking our interactive authentication, because the user + * already knows not to expect any. + * + * If standard input isn't connected to a terminal, likewise, + * because even if the server did send a spoof authentication + * prompt, the user couldn't respond to it via the terminal + * anyway. + * + * We also vacuously return success if the user has purposely + * disabled the antispoof prompt. + */ + return true; + } + + return false; +} + +/* + * 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_logging_error(LogPolicy *lp, const char *string) +{ + /* Errors setting up logging are considered important, so they're + * displayed to standard error even when not in verbose mode */ + struct termios cf; + premsg(&cf); + fprintf(stderr, "%s\n", string); + fflush(stderr); + postmsg(&cf); +} + + +void console_eventlog(LogPolicy *lp, const char *string) +{ + /* Ordinary Event Log entries are displayed in the same way as + * logging errors, but only in verbose mode */ + if (lp_verbose(lp)) + console_logging_error(lp, string); +} + +StripCtrlChars *console_stripctrl_new( + Seat *seat, BinarySink *bs_out, SeatInteractionContext sic) +{ + return stripctrl_new(bs_out, false, 0); +} + +/* + * Special functions to read and print to the console for password + * prompts and the like. Uses /dev/tty or stdin/stderr, in that order + * of preference; also sanitises escape sequences out of the text, on + * the basis that it might have been sent by a hostile SSH server + * doing malicious keyboard-interactive. + */ +static void console_open(FILE **outfp, int *infd) +{ + int fd; + + if ((fd = open("/dev/tty", O_RDWR)) >= 0) { + *infd = fd; + *outfp = fdopen(*infd, "w"); + } else { + *infd = 0; + *outfp = stderr; + } +} +static void console_close(FILE *outfp, int infd) +{ + if (outfp != stderr) + fclose(outfp); /* will automatically close infd too */ +} + +static void console_write(FILE *outfp, ptrlen data) +{ + fwrite(data.ptr, 1, data.len, outfp); + fflush(outfp); +} + +int console_get_userpass_input(prompts_t *p) +{ + size_t curr_prompt; + FILE *outfp = NULL; + int infd; + + /* + * Zero all the results, in case we abort half-way through. + */ + { + int i; + for (i = 0; i < p->n_prompts; i++) + prompt_set_result(p->prompts[i], ""); + } + + if (p->n_prompts && console_batch_mode) + return 0; + + console_open(&outfp, &infd); + + /* + * Preamble. + */ + /* We only print the `name' caption if we have to... */ + if (p->name_reqd && p->name) { + ptrlen plname = ptrlen_from_asciz(p->name); + console_write(outfp, plname); + if (!ptrlen_endswith(plname, PTRLEN_LITERAL("\n"), NULL)) + console_write(outfp, PTRLEN_LITERAL("\n")); + } + /* ...but we always print any `instruction'. */ + if (p->instruction) { + ptrlen plinst = ptrlen_from_asciz(p->instruction); + console_write(outfp, plinst); + if (!ptrlen_endswith(plinst, PTRLEN_LITERAL("\n"), NULL)) + console_write(outfp, PTRLEN_LITERAL("\n")); + } + + for (curr_prompt = 0; curr_prompt < p->n_prompts; curr_prompt++) { + + struct termios oldmode, newmode; + prompt_t *pr = p->prompts[curr_prompt]; + + tcgetattr(infd, &oldmode); + newmode = oldmode; + newmode.c_lflag |= ISIG | ICANON; + if (!pr->echo) + newmode.c_lflag &= ~ECHO; + else + newmode.c_lflag |= ECHO; + tcsetattr(infd, TCSANOW, &newmode); + + console_write(outfp, ptrlen_from_asciz(pr->prompt)); + + bool failed = false; + while (1) { + size_t toread = 65536; + size_t prev_result_len = pr->result->len; + void *ptr = strbuf_append(pr->result, toread); + int ret = read(infd, ptr, toread); + + if (ret <= 0) { + failed = true; + break; + } + + strbuf_shrink_to(pr->result, prev_result_len + ret); + if (strbuf_chomp(pr->result, '\n')) + break; + } + + tcsetattr(infd, TCSANOW, &oldmode); + + if (!pr->echo) + console_write(outfp, PTRLEN_LITERAL("\n")); + + if (failed) { + console_close(outfp, infd); + return 0; /* failure due to read error */ + } + } + + console_close(outfp, infd); + + return 1; /* success */ +} + +bool is_interactive(void) +{ + return isatty(0); +} + +/* + * X11-forwarding-related things suitable for console. + */ + +char *platform_get_x_display(void) { + return dupstr(getenv("DISPLAY")); +} diff --git a/unix/dialog.c b/unix/dialog.c new file mode 100644 index 00000000..f6a98a55 --- /dev/null +++ b/unix/dialog.c @@ -0,0 +1,4195 @@ +/* + * gtkdlg.c - GTK implementation of the PuTTY configuration box. + */ + +#include +#include +#include +#include + +#include +#if !GTK_CHECK_VERSION(3,0,0) +#include +#endif + +#define MAY_REFER_TO_GTK_IN_HEADERS + +#include "putty.h" +#include "gtkcompat.h" +#include "columns.h" +#include "unifont.h" +#include "gtkmisc.h" + +#ifndef NOT_X_WINDOWS +#include +#include +#include +#include "x11misc.h" +#endif + +#include "storage.h" +#include "dialog.h" +#include "tree234.h" +#include "licence.h" +#include "ssh.h" + +#if GTK_CHECK_VERSION(2,0,0) +/* Decide which of GtkFileChooserDialog and GtkFileSelection to use */ +#define USE_GTK_FILE_CHOOSER_DIALOG +#endif + +struct Shortcut { + GtkWidget *widget; + struct uctrl *uc; + int action; +}; + +struct Shortcuts { + struct Shortcut sc[128]; +}; + +struct selparam; + +struct uctrl { + union control *ctrl; + GtkWidget *toplevel; + 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 */ + struct selparam *sp; /* which switchable pane of the box we're in */ + guint entrysig; + guint textsig; + int nclicks; +}; + +struct dlgparam { + tree234 *byctrl, *bywidget; + void *data; + struct { + unsigned char r, g, b; /* 0-255 */ + bool ok; + } coloursel_result; + /* `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; +#else + size_t nselparams; + struct selparam **selparams; +#endif + struct selparam *curr_panel; + struct controlbox *ctrlbox; + int retval; + post_dialog_fn_t after; + void *afterctx; +}; +#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 +#if GTK_CHECK_VERSION(3,0,0) +static void colourchoose_response(GtkDialog *dialog, + gint response_id, gpointer data); +#else +static void coloursel_ok(GtkButton *button, gpointer data); +static void coloursel_cancel(GtkButton *button, gpointer data); +#endif +static void dlgparam_destroy(GtkWidget *widget, gpointer data); +static 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->curr_panel = NULL; + 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); + 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 = gtk_widget_get_parent(w); + } while (w); + return ret; +} + +union control *dlg_last_focused(union control *ctrl, dlgparam *dp) +{ + if (dp->currfocus != ctrl) + return dp->currfocus; + else + return dp->lastfocus; +} + +void dlg_radiobutton_set(union control *ctrl, dlgparam *dp, int which) +{ + 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, dlgparam *dp) +{ + 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, dlgparam *dp, bool checked) +{ + 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); +} + +bool dlg_checkbox_get(union control *ctrl, dlgparam *dp) +{ + 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, dlgparam *dp, char const *text) +{ + 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 + * Conf structure, which is precisely where our `text' pointer + * is probably pointing, so the second editing operation will + * insert that instead of the string we originally asked for. + * + * Hence, we must take our own copy of the text before we do + * this. + */ + tmpstring = dupstr(text); + gtk_entry_set_text(GTK_ENTRY(entry), tmpstring); + sfree(tmpstring); +} + +char *dlg_editbox_get(union control *ctrl, dlgparam *dp) +{ + 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) { + return dupstr(gtk_entry_get_text + (GTK_ENTRY(gtk_bin_get_child(GTK_BIN(uc->combo))))); + } +#endif + + if (uc->entry) { + return dupstr(gtk_entry_get_text(GTK_ENTRY(uc->entry))); + } + + unreachable("bad control type in editbox_get"); +} + +#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, dlgparam *dp) +{ + 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 + unreachable("bad control type in listbox_clear"); +} + +void dlg_listbox_del(union control *ctrl, dlgparam *dp, int index) +{ + 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 + unreachable("bad control type in listbox_del"); +} + +void dlg_listbox_add(union control *ctrl, dlgparam *dp, char const *text) +{ + dlg_listbox_addwithid(ctrl, dp, 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, dlgparam *dp, + char const *text, int id) +{ + 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); + + g_object_set_data(G_OBJECT(menuitem), "user-data", + GINT_TO_POINTER(id)); + g_signal_connect(G_OBJECT(menuitem), "activate", + G_CALLBACK(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); + + g_object_set_data(G_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) { + g_signal_connect(G_OBJECT(listitem), "key_press_event", + G_CALLBACK(listitem_multi_key), uc->adj); + } else { + g_signal_connect(G_OBJECT(listitem), "key_press_event", + G_CALLBACK(listitem_single_key), uc->adj); + } + g_signal_connect(G_OBJECT(listitem), "focus_in_event", + G_CALLBACK(widget_focus), dp); + g_signal_connect(G_OBJECT(listitem), "button_press_event", + G_CALLBACK(listitem_button_press), dp); + g_signal_connect(G_OBJECT(listitem), "button_release_event", + G_CALLBACK(listitem_button_release), dp); + g_object_set_data(G_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 + unreachable("bad control type in listbox_addwithid"); + done: + dp->flags &= ~FLAG_UPDATING_COMBO_LIST; +} + +int dlg_listbox_getid(union control *ctrl, dlgparam *dp, int index) +{ + 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; + GObject *item; + + children = gtk_container_children(GTK_CONTAINER(uc->menu ? uc->menu : + uc->list)); + item = G_OBJECT(g_list_nth_data(children, index)); + g_list_free(children); + + return GPOINTER_TO_INT(g_object_get_data(G_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 + unreachable("bad control type in listbox_getid"); + return -1; /* placate dataflow analysis */ +} + +/* dlg_listbox_index returns <0 if no single element is selected. */ +int dlg_listbox_index(union control *ctrl, dlgparam *dp) +{ + 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 + unreachable("bad control type in listbox_index"); + return -1; /* placate dataflow analysis */ +} + +bool dlg_listbox_issel(union control *ctrl, dlgparam *dp, int index) +{ + 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; + bool 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 + unreachable("bad control type in listbox_issel"); + return false; /* placate dataflow analysis */ +} + +void dlg_listbox_select(union control *ctrl, dlgparam *dp, int index) +{ + 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) { + bool 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 + unreachable("bad control type in listbox_select"); +} + +void dlg_text_set(union control *ctrl, dlgparam *dp, char const *text) +{ + 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, dlgparam *dp, char const *text) +{ + 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: + unreachable("bad control type in label_change"); + } +} + +void dlg_filesel_set(union control *ctrl, dlgparam *dp, Filename *fn) +{ + struct uctrl *uc = dlg_find_byctrl(dp, ctrl); + /* We must copy fn->path before passing it to gtk_entry_set_text. + * See comment in dlg_editbox_set() for the reasons. */ + char *duppath = dupstr(fn->path); + assert(uc->ctrl->generic.type == CTRL_FILESELECT); + assert(uc->entry != NULL); + gtk_entry_set_text(GTK_ENTRY(uc->entry), duppath); + sfree(duppath); +} + +Filename *dlg_filesel_get(union control *ctrl, dlgparam *dp) +{ + struct uctrl *uc = dlg_find_byctrl(dp, ctrl); + assert(uc->ctrl->generic.type == CTRL_FILESELECT); + assert(uc->entry != NULL); + return filename_from_str(gtk_entry_get_text(GTK_ENTRY(uc->entry))); +} + +void dlg_fontsel_set(union control *ctrl, dlgparam *dp, FontSpec *fs) +{ + struct uctrl *uc = dlg_find_byctrl(dp, ctrl); + /* We must copy fs->name before passing it to gtk_entry_set_text. + * See comment in dlg_editbox_set() for the reasons. */ + char *dupname = dupstr(fs->name); + assert(uc->ctrl->generic.type == CTRL_FONTSELECT); + assert(uc->entry != NULL); + gtk_entry_set_text(GTK_ENTRY(uc->entry), dupname); + sfree(dupname); +} + +FontSpec *dlg_fontsel_get(union control *ctrl, dlgparam *dp) +{ + struct uctrl *uc = dlg_find_byctrl(dp, ctrl); + assert(uc->ctrl->generic.type == CTRL_FONTSELECT); + assert(uc->entry != NULL); + return fontspec_new(gtk_entry_get_text(GTK_ENTRY(uc->entry))); +} + +/* + * Bracketing a large set of updates in these two functions will + * cause the front end (if possible) to delay updating the screen + * until it's all complete, thus avoiding flicker. + */ +void dlg_update_start(union control *ctrl, dlgparam *dp) +{ + /* + * 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, dlgparam *dp) +{ + /* + * 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, dlgparam *dp) +{ + 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. + */ + for (int 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 + unreachable("bad control type in set_focus"); + } +} + +/* + * 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(dlgparam *dp) +{ + gdk_display_beep(gdk_display_get_default()); +} + +static void set_transient_window_pos(GtkWidget *parent, GtkWidget *child) +{ +#if !GTK_CHECK_VERSION(2,0,0) + 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_get_window(GTK_WIDGET(parent)), &x, &y); + gdk_window_get_size(gtk_widget_get_window(GTK_WIDGET(parent)), &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); +#endif +} + +void trivial_post_dialog_fn(void *vctx, int result) +{ +} + +void dlg_error_msg(dlgparam *dp, const char *msg) +{ + create_message_box( + dp->window, "Error", msg, + string_width("Some sort of text about a config-box error message"), + false, &buttons_ok, trivial_post_dialog_fn, NULL); +} + +/* + * 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(dlgparam *dp, int value) +{ + dp->retval = value; + gtk_widget_destroy(dp->window); +} + +void dlg_refresh(union control *ctrl, dlgparam *dp) +{ + 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, dlgparam *dp, int r, int g, int b) +{ + struct uctrl *uc = dlg_find_byctrl(dp, ctrl); + +#if GTK_CHECK_VERSION(3,0,0) + GtkWidget *coloursel = + gtk_color_chooser_dialog_new("Select a colour", + GTK_WINDOW(dp->window)); + gtk_color_chooser_set_use_alpha(GTK_COLOR_CHOOSER(coloursel), false); +#else + GtkWidget *okbutton, *cancelbutton; + GtkWidget *coloursel = + gtk_color_selection_dialog_new("Select a colour"); + GtkColorSelectionDialog *ccs = GTK_COLOR_SELECTION_DIALOG(coloursel); + GtkColorSelection *cs = GTK_COLOR_SELECTION + (gtk_color_selection_dialog_get_color_selection(ccs)); + gtk_color_selection_set_has_opacity_control(cs, false); +#endif + + dp->coloursel_result.ok = false; + + gtk_window_set_modal(GTK_WINDOW(coloursel), true); + +#if GTK_CHECK_VERSION(3,0,0) + { + GdkRGBA rgba; + rgba.red = r / 255.0; + rgba.green = g / 255.0; + rgba.blue = b / 255.0; + rgba.alpha = 1.0; /* fully opaque! */ + gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(coloursel), &rgba); + } +#elif GTK_CHECK_VERSION(2,0,0) + { + GdkColor col; + col.red = r * 0x0101; + col.green = g * 0x0101; + col.blue = b * 0x0101; + gtk_color_selection_set_current_color(cs, &col); + } +#else + { + gdouble cvals[4]; + 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(cs, cvals); + } +#endif + + g_object_set_data(G_OBJECT(coloursel), "user-data", (gpointer)uc); + +#if GTK_CHECK_VERSION(3,0,0) + g_signal_connect(G_OBJECT(coloursel), "response", + G_CALLBACK(colourchoose_response), (gpointer)dp); +#else + +#if GTK_CHECK_VERSION(2,0,0) + g_object_get(G_OBJECT(ccs), + "ok-button", &okbutton, + "cancel-button", &cancelbutton, + (const char *)NULL); +#else + okbutton = ccs->ok_button; + cancelbutton = ccs->cancel_button; +#endif + g_object_set_data(G_OBJECT(okbutton), "user-data", + (gpointer)coloursel); + g_object_set_data(G_OBJECT(cancelbutton), "user-data", + (gpointer)coloursel); + g_signal_connect(G_OBJECT(okbutton), "clicked", + G_CALLBACK(coloursel_ok), (gpointer)dp); + g_signal_connect(G_OBJECT(cancelbutton), "clicked", + G_CALLBACK(coloursel_cancel), (gpointer)dp); + g_signal_connect_swapped(G_OBJECT(okbutton), "clicked", + G_CALLBACK(gtk_widget_destroy), + (gpointer)coloursel); + g_signal_connect_swapped(G_OBJECT(cancelbutton), "clicked", + G_CALLBACK(gtk_widget_destroy), + (gpointer)coloursel); +#endif + gtk_widget_show(coloursel); +} + +bool dlg_coloursel_results(union control *ctrl, dlgparam *dp, + int *r, int *g, int *b) +{ + if (dp->coloursel_result.ok) { + *r = dp->coloursel_result.r; + *g = dp->coloursel_result.g; + *b = dp->coloursel_result.b; + return true; + } else + return false; +} + +/* ---------------------------------------------------------------------- + * 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. + */ + GtkWidget *parent = gtk_widget_get_parent(widget); + if (event->keyval == GDK_KEY_Return && parent != NULL) { + gboolean return_val; + g_signal_stop_emission_by_name(G_OBJECT(widget), "key_press_event"); + g_signal_emit_by_name(G_OBJECT(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, bool 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; + + g_signal_stop_emission_by_name(G_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_display_beep(gdk_display_get_default()); + 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 = g_object_get_data(G_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) */ + +#ifdef USE_GTK_FILE_CHOOSER_DIALOG +static void filechoose_response(GtkDialog *dialog, gint response, + gpointer data) +{ + /* struct dlgparam *dp = (struct dlgparam *)data; */ + struct uctrl *uc = g_object_get_data(G_OBJECT(dialog), "user-data"); + if (response == GTK_RESPONSE_ACCEPT) { + gchar *name = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); + gtk_entry_set_text(GTK_ENTRY(uc->entry), name); + g_free(name); + } + gtk_widget_destroy(GTK_WIDGET(dialog)); +} +#else +static void filesel_ok(GtkButton *button, gpointer data) +{ + /* struct dlgparam *dp = (struct dlgparam *)data; */ + gpointer filesel = g_object_get_data(G_OBJECT(button), "user-data"); + struct uctrl *uc = g_object_get_data(G_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); +} +#endif + +static void fontsel_ok(GtkButton *button, gpointer data) +{ + /* struct dlgparam *dp = (struct dlgparam *)data; */ + +#if !GTK_CHECK_VERSION(2,0,0) + + gpointer fontsel = g_object_get_data(G_OBJECT(button), "user-data"); + struct uctrl *uc = g_object_get_data(G_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 *)g_object_get_data + (G_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 +} + +#if GTK_CHECK_VERSION(3,0,0) + +static void colourchoose_response(GtkDialog *dialog, + gint response_id, gpointer data) +{ + struct dlgparam *dp = (struct dlgparam *)data; + struct uctrl *uc = g_object_get_data(G_OBJECT(dialog), "user-data"); + + if (response_id == GTK_RESPONSE_OK) { + GdkRGBA rgba; + gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(dialog), &rgba); + dp->coloursel_result.r = (int) (255 * rgba.red); + dp->coloursel_result.g = (int) (255 * rgba.green); + dp->coloursel_result.b = (int) (255 * rgba.blue); + dp->coloursel_result.ok = true; + } else { + dp->coloursel_result.ok = false; + } + + uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_CALLBACK); + + gtk_widget_destroy(GTK_WIDGET(dialog)); +} + +#else /* GTK 1/2 coloursel response handlers */ + +static void coloursel_ok(GtkButton *button, gpointer data) +{ + struct dlgparam *dp = (struct dlgparam *)data; + gpointer coloursel = g_object_get_data(G_OBJECT(button), "user-data"); + struct uctrl *uc = g_object_get_data(G_OBJECT(coloursel), "user-data"); + +#if GTK_CHECK_VERSION(2,0,0) + { + GtkColorSelection *cs = GTK_COLOR_SELECTION + (gtk_color_selection_dialog_get_color_selection + (GTK_COLOR_SELECTION_DIALOG(coloursel))); + GdkColor col; + gtk_color_selection_get_current_color(cs, &col); + dp->coloursel_result.r = col.red / 0x0100; + dp->coloursel_result.g = col.green / 0x0100; + dp->coloursel_result.b = col.blue / 0x0100; + } +#else + { + GtkColorSelection *cs = GTK_COLOR_SELECTION + (gtk_color_selection_dialog_get_color_selection + (GTK_COLOR_SELECTION_DIALOG(coloursel))); + gdouble cvals[4]; + gtk_color_selection_get_color(cs, 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]); + } +#endif + 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 = g_object_get_data(G_OBJECT(button), "user-data"); + struct uctrl *uc = g_object_get_data(G_OBJECT(coloursel), "user-data"); + dp->coloursel_result.ok = false; + uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_CALLBACK); +} + +#endif /* end of coloursel response handlers */ + +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) { +#ifdef USE_GTK_FILE_CHOOSER_DIALOG + GtkWidget *filechoose = gtk_file_chooser_dialog_new + (uc->ctrl->fileselect.title, GTK_WINDOW(dp->window), + (uc->ctrl->fileselect.for_writing ? + GTK_FILE_CHOOSER_ACTION_SAVE : + GTK_FILE_CHOOSER_ACTION_OPEN), + STANDARD_CANCEL_LABEL, GTK_RESPONSE_CANCEL, + STANDARD_OPEN_LABEL, GTK_RESPONSE_ACCEPT, + (const gchar *)NULL); + gtk_window_set_modal(GTK_WINDOW(filechoose), true); + g_object_set_data(G_OBJECT(filechoose), "user-data", (gpointer)uc); + g_signal_connect(G_OBJECT(filechoose), "response", + G_CALLBACK(filechoose_response), (gpointer)dp); + gtk_widget_show(filechoose); +#else + GtkWidget *filesel = + gtk_file_selection_new(uc->ctrl->fileselect.title); + gtk_window_set_modal(GTK_WINDOW(filesel), true); + g_object_set_data + (G_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "user-data", + (gpointer)filesel); + g_object_set_data(G_OBJECT(filesel), "user-data", (gpointer)uc); + g_signal_connect + (G_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "clicked", + G_CALLBACK(filesel_ok), (gpointer)dp); + g_signal_connect_swapped + (G_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "clicked", + G_CALLBACK(gtk_widget_destroy), (gpointer)filesel); + g_signal_connect_swapped + (G_OBJECT(GTK_FILE_SELECTION(filesel)->cancel_button), "clicked", + G_CALLBACK(gtk_widget_destroy), (gpointer)filesel); + gtk_widget_show(filesel); +#endif + } + + 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 = get_x11_display(); + Atom fontprop = XInternAtom(disp, "FONT", False); + unsigned long ret; + + assert(disp); /* this is GTK1! */ + + 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); + } + } + g_object_set_data + (G_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->ok_button), + "user-data", (gpointer)fontsel); + g_object_set_data(G_OBJECT(fontsel), "user-data", (gpointer)uc); + g_signal_connect + (G_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->ok_button), + "clicked", G_CALLBACK(fontsel_ok), (gpointer)dp); + g_signal_connect_swapped + (G_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->ok_button), + "clicked", G_CALLBACK(gtk_widget_destroy), + (gpointer)fontsel); + g_signal_connect_swapped + (G_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->cancel_button), + "clicked", G_CALLBACK(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); + + g_object_set_data(G_OBJECT(fontsel->ok_button), + "user-data", (gpointer)fontsel); + fontsel->user_data = uc; + g_signal_connect(G_OBJECT(fontsel->ok_button), "clicked", + G_CALLBACK(fontsel_ok), (gpointer)dp); + g_signal_connect_swapped(G_OBJECT(fontsel->ok_button), "clicked", + G_CALLBACK(unifontsel_destroy), + (gpointer)fontsel); + g_signal_connect_swapped(G_OBJECT(fontsel->cancel_button),"clicked", + G_CALLBACK(unifontsel_destroy), + (gpointer)fontsel); + + gtk_widget_show(GTK_WIDGET(fontsel->window)); + +#endif /* !GTK_CHECK_VERSION(2,0,0) */ + + } +} + +#if !GTK_CHECK_VERSION(3,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_size_request(uc->text, alloc->width, -1); + gtk_label_set_text(GTK_LABEL(uc->text), uc->ctrl->generic.label); + g_signal_handler_disconnect(G_OBJECT(uc->text), uc->textsig); +} +#endif + +/* ---------------------------------------------------------------------- + * 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 selparam *sp, struct Shortcuts *scs, + struct controlset *s, GtkWindow *win) +{ + Columns *cols; + GtkWidget *ret; + int i; + + if (!s->boxname) { + /* This controlset is a panel title. */ + assert(s->boxtitle); + 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; + bool 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->sp = sp; + uc->ctrl = ctrl; + 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_can_default(w, true); + if (ctrl->button.isdefault) + gtk_window_set_default(win, w); + if (ctrl->button.iscancel) + dp->cancelbutton = w; + } + g_signal_connect(G_OBJECT(w), "clicked", + G_CALLBACK(button_clicked), dp); + g_signal_connect(G_OBJECT(w), "focus_in_event", + G_CALLBACK(widget_focus), dp); + shortcut_add(scs, gtk_bin_get_child(GTK_BIN(w)), + ctrl->button.shortcut, SHORTCUT_UCTRL, uc); + break; + case CTRL_CHECKBOX: + w = gtk_check_button_new_with_label(ctrl->generic.label); + g_signal_connect(G_OBJECT(w), "toggled", + G_CALLBACK(button_toggled), dp); + g_signal_connect(G_OBJECT(w), "focus_in_event", + G_CALLBACK(widget_focus), dp); + shortcut_add(scs, gtk_bin_get_child(GTK_BIN(w)), + 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_get_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); + g_signal_connect(G_OBJECT(b), "toggled", + G_CALLBACK(button_toggled), dp); + g_signal_connect(G_OBJECT(b), "focus_in_event", + G_CALLBACK(widget_focus), dp); + if (ctrl->radio.shortcuts) { + shortcut_add(scs, gtk_bin_get_child(GTK_BIN(b)), + ctrl->radio.shortcuts[i], + SHORTCUT_UCTRL, uc); + } + } + break; + } + case CTRL_EDITBOX: { + 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_new_with_model_and_entry + (GTK_TREE_MODEL(uc->listmodel)); + g_object_set(G_OBJECT(w), "entry-text-column", 1, + (const char *)NULL); + /* 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 = + g_signal_connect(G_OBJECT(signalobject), "changed", + G_CALLBACK(editbox_changed), dp); + g_signal_connect(G_OBJECT(signalobject), "key_press_event", + G_CALLBACK(editbox_key), dp); + g_signal_connect(G_OBJECT(signalobject), "focus_in_event", + G_CALLBACK(widget_focus), dp); + g_signal_connect(G_OBJECT(signalobject), "focus_out_event", + G_CALLBACK(editbox_lostfocus), dp); + g_signal_connect(G_OBJECT(signalobject), "focus_out_event", + G_CALLBACK(editbox_lostfocus), dp); + +#if !GTK_CHECK_VERSION(3,0,0) + /* + * 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. + */ + { + GtkRequisition req; + gtk_widget_size_request(w, &req); + gtk_widget_set_size_request(w, 10, req.height); + } +#else + /* + * In GTK 3, this is still true, but there's a special + * method for GtkEntry in particular to fix it. + */ + if (GTK_IS_ENTRY(w)) + gtk_entry_set_width_chars(GTK_ENTRY(w), 1); +#endif + + 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); + columns_force_same_height(COLUMNS(container), + label, w); + } + gtk_widget_show(label); + gtk_widget_show(w); + + w = container; + uc->label = label; + } + break; + } + case CTRL_FILESELECT: + case CTRL_FONTSELECT: { + GtkWidget *ww; + const 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(); +#if !GTK_CHECK_VERSION(3,0,0) + { + GtkRequisition req; + gtk_widget_size_request(ww, &req); + gtk_widget_set_size_request(ww, 10, req.height); + } +#else + gtk_entry_set_width_chars(GTK_ENTRY(ww), 1); +#endif + 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); + + columns_force_same_height(COLUMNS(w), uc->entry, uc->button); + + g_signal_connect(G_OBJECT(uc->entry), "key_press_event", + G_CALLBACK(editbox_key), dp); + uc->entrysig = + g_signal_connect(G_OBJECT(uc->entry), "changed", + G_CALLBACK(editbox_changed), dp); + g_signal_connect(G_OBJECT(uc->entry), "focus_in_event", + G_CALLBACK(widget_focus), dp); + g_signal_connect(G_OBJECT(uc->button), "focus_in_event", + G_CALLBACK(widget_focus), dp); + g_signal_connect(G_OBJECT(ww), "clicked", + G_CALLBACK(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); + g_object_set_data(G_OBJECT(uc->menu), "user-data", + (gpointer)uc->optmenu); + g_signal_connect(G_OBJECT(uc->optmenu), "focus_in_event", + G_CALLBACK(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); + + g_signal_connect(G_OBJECT(w), "focus_in_event", + G_CALLBACK(widget_focus), 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); + g_signal_connect(G_OBJECT(uc->list), "selection-changed", + G_CALLBACK(list_selchange), dp); + g_signal_connect(G_OBJECT(uc->list), "focus_in_event", + G_CALLBACK(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_size_request(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); + g_signal_connect(G_OBJECT(button), "clicked", + G_CALLBACK(draglist_up), dp); + g_signal_connect(G_OBJECT(button), "focus_in_event", + G_CALLBACK(widget_focus), dp); + button = gtk_button_new_with_label("Down"); + columns_add(COLUMNS(cols), button, 1, 1); + gtk_widget_show(button); + g_signal_connect(G_OBJECT(button), "clicked", + G_CALLBACK(draglist_down), dp); + g_signal_connect(G_OBJECT(button), "focus_in_event", + G_CALLBACK(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; + g_signal_connect(G_OBJECT(w), "row-activated", + G_CALLBACK(listbox_doubleclick), dp); + g_signal_connect(G_OBJECT(w), "focus_in_event", + G_CALLBACK(widget_focus), 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; + GtkCellRenderer *cellrend; + /* + * It appears that GTK 2 doesn't leave us any + * particularly sensible way to honour the + * "percentages" specification in the ctrl + * structure. + */ + cellrend = gtk_cell_renderer_text_new(); + if (!ctrl->listbox.hscroll) { + g_object_set(G_OBJECT(cellrend), + "ellipsize", PANGO_ELLIPSIZE_END, + "ellipsize-set", true, + (const char *)NULL); + } + column = gtk_tree_view_column_new_with_attributes + ("heading", cellrend, "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; + + label = gtk_label_new(ctrl->generic.label); +#if GTK_CHECK_VERSION(3,0,0) + gtk_label_set_width_chars(GTK_LABEL(label), 3); +#endif + + shortcut_add(scs, label, ctrl->listbox.shortcut, + SHORTCUT_UCTRL, uc); + + 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); + columns_force_same_height(COLUMNS(container), + label, w); + } + gtk_widget_show(label); + gtk_widget_show(w); + + w = container; + uc->label = label; + } + + break; + case CTRL_TEXT: +#if !GTK_CHECK_VERSION(3,0,0) + /* + * Wrapping text widgets don't sit well with the GTK2 + * 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"); + uc->textsig = + g_signal_connect(G_OBJECT(w), "size-allocate", + G_CALLBACK(label_sizealloc), dp); +#else + /* + * In GTK3, this is all fixed, because the main aim of the + * new 'height-for-width' geometry management is to make + * wrapping labels behave sensibly. So now we can just do + * the obvious thing. + */ + uc->text = w = gtk_label_new(uc->ctrl->generic.label); +#endif + align_label_left(GTK_LABEL(w)); + gtk_label_set_line_wrap(GTK_LABEL(w), true); + 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); + if (ctrl->generic.align_next_to) { + /* + * Implement align_next_to by simply forcing the two + * controls to have the same height of size allocation. At + * least for the controls we're currently doing this with, + * the GTK layout system will automatically vertically + * centre each control within its allocation, which will + * get the two controls aligned alongside each other + * reasonably well. + */ + struct uctrl *uc2 = dlg_find_byctrl( + dp, ctrl->generic.align_next_to); + assert(uc2); + columns_force_same_height(cols, w, uc2->toplevel); + +#if GTK_CHECK_VERSION(3, 10, 0) + /* Slightly nicer to align baselines than just vertically + * centring, where the option is available */ + gtk_widget_set_valign(w, GTK_ALIGN_BASELINE); + gtk_widget_set_valign(uc2->toplevel, GTK_ALIGN_BASELINE); +#endif + } + 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_current_page(sp->panels, page_num); + + sp->dp->curr_panel = sp; + 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); + + sp->dp->curr_panel = sp; + dlg_refresh(NULL, sp->dp); + + sp->dp->shortcuts = &sp->shortcuts; + sp->dp->currtreeitem = sp->treeitem; +} +#endif + +bool dlg_is_visible(union control *ctrl, dlgparam *dp) +{ + struct uctrl *uc = dlg_find_byctrl(dp, ctrl); + /* + * A control is visible if it belongs to _no_ notebook page (i.e. + * it's one of the config-box-global buttons like Load or About), + * or if it belongs to the currently selected page. + */ + return uc->sp == NULL || uc->sp == dp->curr_panel; +} + +#if !GTK_CHECK_VERSION(2,0,0) +static bool 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; + + g_signal_stop_emission_by_name(G_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 + +gint win_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data) +{ + struct dlgparam *dp = (struct dlgparam *)data; + + if (event->keyval == GDK_KEY_Escape && dp->cancelbutton) { + g_signal_emit_by_name(G_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); + g_signal_emit_by_name(G_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. */ + g_signal_emit_by_name(G_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]); + g_signal_emit_by_name + (G_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; + g_signal_emit_by_name(G_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 + unreachable("bad listbox type in win_key_press"); + } + break; + } + } + + return false; +} + +#if !GTK_CHECK_VERSION(2,0,0) +gint 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]; + bool 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; + } + } + } + } + g_signal_stop_emission_by_name(G_OBJECT(widget), "key_press_event"); + if (j >= 0) { + g_signal_emit_by_name(G_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) { + g_signal_stop_emission_by_name(G_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) { + g_signal_stop_emission_by_name(G_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); + const gchar *currstr; + gchar *pattern; + int i; + +#if !GTK_CHECK_VERSION(2,0,0) + { + gchar *currstr_nonconst; + gtk_label_get(label, &currstr_nonconst); + currstr = currstr_nonconst; + } +#else + currstr = gtk_label_get_text(label); +#endif + + for (i = 0; currstr[i]; i++) + if (tolower((unsigned char)currstr[i]) == chr) { + pattern = dupprintf("%*s_", i, ""); + gtk_label_set_pattern(label, pattern); + 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 || action == SHORTCUT_TREE) { + 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); +} + +static 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); + g_object_ref_sink(G_OBJECT(listitem)); + return req.height; +#else + int height; + GtkCellRenderer *cr = gtk_cell_renderer_text_new(); +#if GTK_CHECK_VERSION(3,0,0) + { + GtkRequisition req; + /* + * Since none of my list items wraps in this GUI, no + * interesting width-for-height behaviour should be happening, + * so I don't think it should matter here whether I ask for + * the minimum or natural height. + */ + gtk_cell_renderer_get_preferred_size(cr, w, &req, NULL); + height = req.height; + } +#else + gtk_cell_renderer_get_size(cr, w, NULL, NULL, NULL, NULL, &height); +#endif + g_object_ref(G_OBJECT(cr)); + g_object_ref_sink(G_OBJECT(cr)); + g_object_unref(G_OBJECT(cr)); + return height; +#endif +} + +#if GTK_CHECK_VERSION(2,0,0) +void initial_treeview_collapse(struct dlgparam *dp, GtkWidget *tree) +{ + /* + * Collapse the deeper branches of the treeview into the state we + * like them to start off in. See comment below in do_config_box. + */ + int i; + for (i = 0; i < dp->nselparams; i++) + if (dp->selparams[i]->depth >= 2) + gtk_tree_view_collapse_row(GTK_TREE_VIEW(tree), + dp->selparams[i]->treepath); +} +#endif + +#if GTK_CHECK_VERSION(3,0,0) +void treeview_map_event(GtkWidget *tree, gpointer data) +{ + struct dlgparam *dp = (struct dlgparam *)data; + GtkAllocation alloc; + gtk_widget_get_allocation(tree, &alloc); + gtk_widget_set_size_request(tree, alloc.width, -1); + initial_treeview_collapse(dp, tree); +} +#endif + +GtkWidget *create_config_box(const char *title, Conf *conf, + bool midsession, int protcfginfo, + post_dialog_fn_t after, void *afterctx) +{ + GtkWidget *window, *hbox, *vbox, *cols, *label, + *tree, *treescroll, *panels, *panelvbox; + int index, level, protocol; + 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; + size_t nselparams = 0, selparamsize = 0; + + dp = snew(struct dlgparam); + dp->after = after; + dp->afterctx = afterctx; + + dlg_init(dp); + + for (index = 0; index < lenof(scs.sc); index++) { + scs.sc[index].action = SHORTCUT_EMPTY; + } + + window = our_dialog_new(); + + dp->ctrlbox = ctrl_new_box(); + protocol = conf_get_int(conf, CONF_protocol); + setup_config_box(dp->ctrlbox, midsession, protocol, protcfginfo); + unix_setup_config_box(dp->ctrlbox, midsession, protocol); + gtk_setup_config_box(dp->ctrlbox, midsession, window); + + gtk_window_set_title(GTK_WINDOW(window), title); + hbox = gtk_hbox_new(false, 4); + our_dialog_add_to_content_area(GTK_WINDOW(window), 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); + g_signal_connect(G_OBJECT(tree), "focus", G_CALLBACK(tree_focus), dp); +#endif + g_signal_connect(G_OBJECT(tree), "focus_in_event", + G_CALLBACK(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 < dp->ctrlbox->nctrlsets; index++) { + struct controlset *s = dp->ctrlbox->ctrlsets[index]; + GtkWidget *w; + + if (!*s->pathname) { + w = layout_ctrls(dp, NULL, &scs, s, GTK_WINDOW(window)); + + our_dialog_set_action_area(GTK_WINDOW(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 + bool 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); + + struct selparam *sp = snew(struct selparam); + + if (first) { + gint page_num; + + page_num = gtk_notebook_page_num(GTK_NOTEBOOK(panels), + panelvbox); + gtk_notebook_set_current_page(GTK_NOTEBOOK(panels), + page_num); + + dp->curr_panel = sp; + } + + sgrowarray(selparams, selparamsize, nselparams); + selparams[nselparams] = sp; + sp->dp = dp; + sp->panels = GTK_NOTEBOOK(panels); + sp->panel = panelvbox; + sp->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; + + sp->depth = j; + if (j > 0) { + sp->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), + sp->treepath, false); + } else { + sp->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; + + g_signal_connect(G_OBJECT(treeitem), "key_press_event", + G_CALLBACK(tree_key_press), dp); + g_signal_connect(G_OBJECT(treeitem), "focus_in_event", + G_CALLBACK(widget_focus), dp); + + gtk_widget_show(treeitem); + + if (first) + gtk_tree_select_child(GTK_TREE(tree), treeitem); + sp->treeitem = treeitem; +#endif + + level = j+1; + nselparams++; + } + + w = layout_ctrls(dp, selparams[nselparams-1], + &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) + /* + * 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, and then when the + * collapsed branches are expanded later, the tree view will + * jarringly change size. + * + * So instead we start with everything expanded; then, once the + * tree view has computed its resulting width requirement, we + * collapse the relevant rows, but force the width to be the value + * we just retrieved. This arranges that the tree view is wide + * enough to have all branches expanded without further resizing. + */ + + dp->nselparams = nselparams; + dp->selparams = selparams; + +#if !GTK_CHECK_VERSION(3,0,0) + { + /* + * In GTK2, we can just do the job right now. + */ + GtkRequisition req; + gtk_widget_size_request(tree, &req); + initial_treeview_collapse(dp, tree); + gtk_widget_set_size_request(tree, req.width, -1); + } +#else + /* + * But in GTK3, we have to wait until the widget is about to be + * mapped, because the size computation won't have been done yet. + */ + g_signal_connect(G_OBJECT(tree), "map", + G_CALLBACK(treeview_map_event), dp); +#endif /* GTK 2 vs 3 */ +#endif /* GTK 2+ vs 1 */ + +#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++) { + g_signal_connect(G_OBJECT(selparams[index]->treeitem), "select", + G_CALLBACK(treeitem_sel), + selparams[index]); + dp->treeitems[index] = selparams[index]->treeitem; + } +#endif + + dp->data = conf; + 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 = -1; + dp->window = window; + + 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 < dp->ctrlbox->nctrlsets; index++) { + struct controlset *s = dp->ctrlbox->ctrlsets[index]; + bool done = false; + 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 = true; + break; + } + } + if (done) + break; + } + + g_signal_connect(G_OBJECT(window), "destroy", + G_CALLBACK(dlgparam_destroy), dp); + g_signal_connect(G_OBJECT(window), "key_press_event", + G_CALLBACK(win_key_press), dp); + + return window; +} + +static void dlgparam_destroy(GtkWidget *widget, gpointer data) +{ + struct dlgparam *dp = (struct dlgparam *)data; + dp->after(dp->afterctx, dp->retval); + dlg_cleanup(dp); + ctrl_free_box(dp->ctrlbox); +#if GTK_CHECK_VERSION(2,0,0) + if (dp->selparams) { + for (size_t i = 0; i < dp->nselparams; i++) { + if (dp->selparams[i]->treepath) + gtk_tree_path_free(dp->selparams[i]->treepath); + sfree(dp->selparams[i]); + } + sfree(dp->selparams); + } +#endif + sfree(dp); +} + +static void messagebox_handler(union control *ctrl, dlgparam *dp, + void *data, int event) +{ + if (event == EVENT_ACTION) + dlg_end(dp, ctrl->generic.context.i); +} + +static const struct message_box_button button_array_yn[] = { + {"Yes", 'y', +1, 1}, + {"No", 'n', -1, 0}, +}; +const struct message_box_buttons buttons_yn = { + button_array_yn, lenof(button_array_yn), +}; +static const struct message_box_button button_array_ok[] = { + {"OK", 'o', 1, 1}, +}; +const struct message_box_buttons buttons_ok = { + button_array_ok, lenof(button_array_ok), +}; + +static GtkWidget *create_message_box_general( + GtkWidget *parentwin, const char *title, const char *msg, int minwid, + bool selectable, const struct message_box_buttons *buttons, + post_dialog_fn_t after, void *afterctx, + GtkWidget *(*action_postproc)(GtkWidget *, void *), void *postproc_ctx) +{ + GtkWidget *window, *w0, *w1; + struct controlset *s0, *s1; + union control *c, *textctrl; + struct dlgparam *dp; + struct Shortcuts scs; + int i, index, ncols, min_type; + + dp = snew(struct dlgparam); + dp->after = after; + dp->afterctx = afterctx; + + dlg_init(dp); + + for (index = 0; index < lenof(scs.sc); index++) { + scs.sc[index].action = SHORTCUT_EMPTY; + } + + dp->ctrlbox = ctrl_new_box(); + + /* + * Count up the number of buttons and find out what kinds there + * are. + */ + ncols = 0; + min_type = +1; + for (i = 0; i < buttons->nbuttons; i++) { + const struct message_box_button *button = &buttons->buttons[i]; + ncols++; + if (min_type > button->type) + min_type = button->type; + assert(button->value >= 0); /* <0 means no return value available */ + } + + s0 = ctrl_getset(dp->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; + index = 0; + for (i = 0; i < buttons->nbuttons; i++) { + const struct message_box_button *button = &buttons->buttons[i]; + c = ctrl_pushbutton(s0, button->title, button->shortcut, + HELPCTX(no_help), messagebox_handler, + I(button->value)); + c->generic.column = index++; + if (button->type > 0) + c->button.isdefault = true; + + /* We always arrange that _some_ button is labelled as + * 'iscancel', so that pressing Escape will always cause + * win_key_press to do something. The button we choose is + * whichever has the smallest type value: this means that real + * cancel buttons (labelled -1) will be picked if one is + * there, or in cases where the options are yes/no (1,0) then + * no will be picked, and if there's only one option (a box + * that really is just showing a _message_ and not even asking + * a question) then that will be picked. */ + if (button->type == min_type) + c->button.iscancel = true; + } + + s1 = ctrl_getset(dp->ctrlbox, "x", "", ""); + textctrl = ctrl_text(s1, msg, HELPCTX(no_help)); + + window = our_dialog_new(); + gtk_window_set_title(GTK_WINDOW(window), title); + w0 = layout_ctrls(dp, NULL, &scs, s0, GTK_WINDOW(window)); + if (action_postproc) + w0 = action_postproc(w0, postproc_ctx); + our_dialog_set_action_area(GTK_WINDOW(window), w0); + gtk_widget_show(w0); + w1 = layout_ctrls(dp, NULL, &scs, s1, GTK_WINDOW(window)); + gtk_container_set_border_width(GTK_CONTAINER(w1), 10); + gtk_widget_set_size_request(w1, minwid+20, -1); + our_dialog_add_to_content_area(GTK_WINDOW(window), w1, true, true, 0); + gtk_widget_show(w1); + + dp->shortcuts = &scs; + dp->lastfocus = NULL; + dp->retval = 0; + dp->window = window; + + if (selectable) { +#if GTK_CHECK_VERSION(2,0,0) + struct uctrl *uc = dlg_find_byctrl(dp, textctrl); + gtk_label_set_selectable(GTK_LABEL(uc->text), true); + + /* + * GTK selectable labels have a habit of selecting their + * entire contents when they gain focus. It's ugly to have + * text in a message box start up all selected, so we suppress + * this by manually selecting none of it - but we must do this + * when the widget _already has_ focus, otherwise our work + * will be undone when it gains it shortly. + */ + gtk_widget_grab_focus(uc->text); + gtk_label_select_region(GTK_LABEL(uc->text), 0, 0); +#else + (void)textctrl; /* placate warning */ +#endif + } + + 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_container_set_focus_child(GTK_CONTAINER(window), NULL); + gtk_widget_show(window); + gtk_window_set_focus(GTK_WINDOW(window), NULL); + +#if !GTK_CHECK_VERSION(2,0,0) + dp->currtreeitem = NULL; + dp->treeitems = NULL; +#else + dp->selparams = NULL; +#endif + + g_signal_connect(G_OBJECT(window), "destroy", + G_CALLBACK(dlgparam_destroy), dp); + g_signal_connect(G_OBJECT(window), "key_press_event", + G_CALLBACK(win_key_press), dp); + + return window; +} + +GtkWidget *create_message_box( + GtkWidget *parentwin, const char *title, const char *msg, int minwid, + bool selectable, const struct message_box_buttons *buttons, + post_dialog_fn_t after, void *afterctx) +{ + return create_message_box_general( + parentwin, title, msg, minwid, selectable, buttons, after, afterctx, + NULL /* action_postproc */, NULL /* postproc_ctx */); +} + +struct verify_ssh_host_key_dialog_ctx { + char *host; + int port; + char *keytype; + char *keystr; + char *more_info; + void (*callback)(void *callback_ctx, int result); + void *callback_ctx; + Seat *seat; + + GtkWidget *main_dialog; + GtkWidget *more_info_dialog; +}; + +static void verify_ssh_host_key_result_callback(void *vctx, int result) +{ + struct verify_ssh_host_key_dialog_ctx *ctx = + (struct verify_ssh_host_key_dialog_ctx *)vctx; + + if (result >= 0) { + int logical_result; + + /* + * Convert the dialog-box return value (one of three + * possibilities) into the return value we pass back to the SSH + * code (one of only two possibilities, because the SSH code + * doesn't care whether we saved the host key or not). + */ + if (result == 2) { + store_host_key(ctx->host, ctx->port, ctx->keytype, ctx->keystr); + logical_result = 1; /* continue with connection */ + } else if (result == 1) { + logical_result = 1; /* continue with connection */ + } else { + logical_result = 0; /* do not continue with connection */ + } + + ctx->callback(ctx->callback_ctx, logical_result); + } + + /* + * Clean up this context structure, whether or not a result was + * ever actually delivered from the dialog box. + */ + unregister_dialog(ctx->seat, DIALOG_SLOT_NETWORK_PROMPT); + + if (ctx->more_info_dialog) + gtk_widget_destroy(ctx->more_info_dialog); + + sfree(ctx->host); + sfree(ctx->keytype); + sfree(ctx->keystr); + sfree(ctx->more_info); + sfree(ctx); +} + +static GtkWidget *add_more_info_button(GtkWidget *w, void *vctx) +{ + GtkWidget *box = gtk_hbox_new(false, 10); + gtk_widget_show(box); + gtk_box_pack_end(GTK_BOX(box), w, false, true, 0); + GtkWidget *button = gtk_button_new_with_label("More info..."); + gtk_widget_show(button); + gtk_box_pack_start(GTK_BOX(box), button, false, true, 0); + *(GtkWidget **)vctx = button; + return box; +} + +static void more_info_closed(void *vctx, int result) +{ + struct verify_ssh_host_key_dialog_ctx *ctx = + (struct verify_ssh_host_key_dialog_ctx *)vctx; + + ctx->more_info_dialog = NULL; +} + +static void more_info_button_clicked(GtkButton *button, gpointer vctx) +{ + struct verify_ssh_host_key_dialog_ctx *ctx = + (struct verify_ssh_host_key_dialog_ctx *)vctx; + + if (ctx->more_info_dialog) + return; + + ctx->more_info_dialog = create_message_box( + ctx->main_dialog, "Host key information", ctx->more_info, + string_width("SHA256 fingerprint: ecdsa-sha2-nistp521 521 " + "abcdefghkmnopqrsuvwxyzABCDEFGHJKLMNOPQRSTUW"), true, + &buttons_ok, more_info_closed, ctx); +} + +int gtk_seat_verify_ssh_host_key( + Seat *seat, const char *host, int port, const char *keytype, + char *keystr, const char *keydisp, char **fingerprints, + 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."; + static const struct message_box_button button_array_hostkey[] = { + {"Accept", 'a', 0, 2}, + {"Connect Once", 'o', 0, 1}, + {"Cancel", 'c', -1, 0}, + }; + static const struct message_box_buttons buttons_hostkey = { + button_array_hostkey, lenof(button_array_hostkey), + }; + + char *text; + int ret; + struct verify_ssh_host_key_dialog_ctx *result_ctx; + GtkWidget *mainwin, *msgbox; + + /* + * Verify the key. + */ + ret = verify_host_key(host, port, keytype, keystr); + + if (ret == 0) /* success - key matched OK */ + return 1; + + FingerprintType fptype_default = + ssh2_pick_default_fingerprint(fingerprints); + + text = dupprintf((ret == 2 ? wrongtxt : absenttxt), keytype, + fingerprints[fptype_default]); + + result_ctx = snew(struct verify_ssh_host_key_dialog_ctx); + result_ctx->callback = callback; + result_ctx->callback_ctx = ctx; + result_ctx->host = dupstr(host); + result_ctx->port = port; + result_ctx->keytype = dupstr(keytype); + result_ctx->keystr = dupstr(keystr); + result_ctx->seat = seat; + + mainwin = GTK_WIDGET(gtk_seat_get_window(seat)); + GtkWidget *more_info_button = NULL; + msgbox = create_message_box_general( + mainwin, "PuTTY Security Alert", text, + string_width(fingerprints[fptype_default]), true, + &buttons_hostkey, verify_ssh_host_key_result_callback, result_ctx, + add_more_info_button, &more_info_button); + + result_ctx->main_dialog = msgbox; + result_ctx->more_info_dialog = NULL; + + strbuf *sb = strbuf_new(); + if (fingerprints[SSH_FPTYPE_SHA256]) + strbuf_catf(sb, "SHA256 fingerprint: %s\n", + fingerprints[SSH_FPTYPE_SHA256]); + if (fingerprints[SSH_FPTYPE_MD5]) + strbuf_catf(sb, "MD5 fingerprint: %s\n", + fingerprints[SSH_FPTYPE_MD5]); + strbuf_catf(sb, "Full text of host's public key:"); + /* We have to manually wrap the public key, or else the GtkLabel + * will resize itself to accommodate the longest word, which will + * lead to a hilariously wide message box. */ + for (const char *p = keydisp, *q = p + strlen(p); p < q ;) { + size_t linelen = q-p; + if (linelen > 72) + linelen = 72; + put_byte(sb, '\n'); + put_data(sb, p, linelen); + p += linelen; + } + result_ctx->more_info = strbuf_to_str(sb); + + g_signal_connect(G_OBJECT(more_info_button), "clicked", + G_CALLBACK(more_info_button_clicked), result_ctx); + + register_dialog(seat, DIALOG_SLOT_NETWORK_PROMPT, msgbox); + + sfree(text); + + return -1; /* dialog still in progress */ +} + +struct simple_prompt_result_ctx { + void (*callback)(void *callback_ctx, int result); + void *callback_ctx; + Seat *seat; + enum DialogSlot dialog_slot; +}; + +static void simple_prompt_result_callback(void *vctx, int result) +{ + struct simple_prompt_result_ctx *ctx = + (struct simple_prompt_result_ctx *)vctx; + + unregister_dialog(ctx->seat, ctx->dialog_slot); + + if (result >= 0) + ctx->callback(ctx->callback_ctx, result); + + /* + * Clean up this context structure, whether or not a result was + * ever actually delivered from the dialog box. + */ + sfree(ctx); +} + +/* + * Ask whether the selected algorithm is acceptable (since it was + * below the configured 'warn' threshold). + */ +int gtk_seat_confirm_weak_crypto_primitive( + Seat *seat, 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; + struct simple_prompt_result_ctx *result_ctx; + GtkWidget *mainwin, *msgbox; + + text = dupprintf(msg, algtype, algname); + + result_ctx = snew(struct simple_prompt_result_ctx); + result_ctx->callback = callback; + result_ctx->callback_ctx = ctx; + result_ctx->seat = seat; + result_ctx->dialog_slot = DIALOG_SLOT_NETWORK_PROMPT; + + mainwin = GTK_WIDGET(gtk_seat_get_window(seat)); + msgbox = create_message_box( + mainwin, "PuTTY Security Alert", text, + string_width("Reasonably long line of text as a width template"), + false, &buttons_yn, simple_prompt_result_callback, result_ctx); + register_dialog(seat, result_ctx->dialog_slot, msgbox); + + sfree(text); + + return -1; /* dialog still in progress */ +} + +int gtk_seat_confirm_weak_cached_hostkey( + Seat *seat, const char *algname, const char *betteralgs, + void (*callback)(void *ctx, int result), void *ctx) +{ + static const char msg[] = + "The first host key type we have stored for this server\n" + "is %s, which is below the configured warning threshold.\n" + "The server also provides the following types of host key\n" + "above the threshold, which we do not have stored:\n" + "%s\n" + "Continue with connection?"; + + char *text; + struct simple_prompt_result_ctx *result_ctx; + GtkWidget *mainwin, *msgbox; + + text = dupprintf(msg, algname, betteralgs); + + result_ctx = snew(struct simple_prompt_result_ctx); + result_ctx->callback = callback; + result_ctx->callback_ctx = ctx; + result_ctx->seat = seat; + result_ctx->dialog_slot = DIALOG_SLOT_NETWORK_PROMPT; + + mainwin = GTK_WIDGET(gtk_seat_get_window(seat)); + msgbox = create_message_box( + mainwin, "PuTTY Security Alert", text, + string_width("is ecdsa-nistp521, which is below the configured" + " warning threshold."), + false, &buttons_yn, simple_prompt_result_callback, result_ctx); + register_dialog(seat, result_ctx->dialog_slot, msgbox); + + sfree(text); + + return -1; /* dialog still in progress */ +} + +void old_keyfile_warning(void) +{ + /* + * This should never happen on Unix. We hope. + */ +} + +void nonfatal_message_box(void *window, const char *msg) +{ + char *title = dupcat(appname, " Error"); + create_message_box( + window, title, msg, + string_width("REASONABLY LONG LINE OF TEXT FOR BASIC SANITY"), + false, &buttons_ok, trivial_post_dialog_fn, NULL); + sfree(title); +} + +void nonfatal(const char *p, ...) +{ + va_list ap; + char *msg; + va_start(ap, p); + msg = dupvprintf(p, ap); + va_end(ap); + nonfatal_message_box(NULL, msg); + sfree(msg); +} + +static GtkWidget *aboutbox = NULL; + +static void about_window_destroyed(GtkWidget *widget, gpointer data) +{ + aboutbox = NULL; +} + +static void about_close_clicked(GtkButton *button, gpointer data) +{ + gtk_widget_destroy(aboutbox); + aboutbox = NULL; +} + +static void about_key_press(GtkWidget *widget, GdkEventKey *event, + gpointer data) +{ + if (event->keyval == GDK_KEY_Escape && aboutbox) { + gtk_widget_destroy(aboutbox); + aboutbox = NULL; + } +} + +static void licence_clicked(GtkButton *button, gpointer data) +{ + char *title; + + title = dupcat(appname, " Licence"); + assert(aboutbox != NULL); + create_message_box(aboutbox, title, LICENCE_TEXT("\n\n"), + string_width("LONGISH LINE OF TEXT SO THE LICENCE" + " BOX ISN'T EXCESSIVELY TALL AND THIN"), + true, &buttons_ok, trivial_post_dialog_fn, NULL); + sfree(title); +} + +void about_box(void *window) +{ + GtkWidget *w; + GtkBox *action_area; + char *title; + + if (aboutbox) { + gtk_widget_grab_focus(aboutbox); + return; + } + + aboutbox = our_dialog_new(); + gtk_container_set_border_width(GTK_CONTAINER(aboutbox), 10); + title = dupcat("About ", appname); + gtk_window_set_title(GTK_WINDOW(aboutbox), title); + sfree(title); + + g_signal_connect(G_OBJECT(aboutbox), "destroy", + G_CALLBACK(about_window_destroyed), NULL); + + w = gtk_button_new_with_label("Close"); + gtk_widget_set_can_default(w, true); + gtk_window_set_default(GTK_WINDOW(aboutbox), w); + action_area = our_dialog_make_action_hbox(GTK_WINDOW(aboutbox)); + gtk_box_pack_end(action_area, w, false, false, 0); + g_signal_connect(G_OBJECT(w), "clicked", + G_CALLBACK(about_close_clicked), NULL); + gtk_widget_show(w); + + w = gtk_button_new_with_label("View Licence"); + gtk_widget_set_can_default(w, true); + gtk_box_pack_end(action_area, w, false, false, 0); + g_signal_connect(G_OBJECT(w), "clicked", + G_CALLBACK(licence_clicked), NULL); + gtk_widget_show(w); + + { + char *buildinfo_text = buildinfo("\n"); + char *label_text = dupprintf + ("%s\n\n%s\n\n%s\n\n%s", + appname, ver, buildinfo_text, + "Copyright " SHORT_COPYRIGHT_DETAILS ". All rights reserved"); + w = gtk_label_new(label_text); + gtk_label_set_justify(GTK_LABEL(w), GTK_JUSTIFY_CENTER); +#if GTK_CHECK_VERSION(2,0,0) + gtk_label_set_selectable(GTK_LABEL(w), true); +#endif + sfree(label_text); + } + our_dialog_add_to_content_area(GTK_WINDOW(aboutbox), w, false, false, 0); +#if GTK_CHECK_VERSION(2,0,0) + /* + * Same precautions against initial select-all as in + * create_message_box(). + */ + gtk_widget_grab_focus(w); + gtk_label_select_region(GTK_LABEL(w), 0, 0); +#endif + gtk_widget_show(w); + + g_signal_connect(G_OBJECT(aboutbox), "key_press_event", + G_CALLBACK(about_key_press), NULL); + + set_transient_window_pos(GTK_WIDGET(window), aboutbox); + if (window) + gtk_window_set_transient_for(GTK_WINDOW(aboutbox), + GTK_WINDOW(window)); + gtk_container_set_focus_child(GTK_CONTAINER(aboutbox), NULL); + gtk_widget_show(aboutbox); + gtk_window_set_focus(GTK_WINDOW(aboutbox), NULL); +} + +#define LOGEVENT_INITIAL_MAX 128 +#define LOGEVENT_CIRCULAR_MAX 128 + +struct eventlog_stuff { + GtkWidget *parentwin, *window; + struct controlbox *eventbox; + struct Shortcuts scs; + struct dlgparam dp; + union control *listctrl; + char **events_initial; + char **events_circular; + int ninitial, ncircular, circular_first; + strbuf *seldata; + int sellen; + bool ignore_selchange; +}; + +static void eventlog_destroy(GtkWidget *widget, gpointer data) +{ + eventlog_stuff *es = (eventlog_stuff *)data; + + es->window = NULL; + dlg_cleanup(&es->dp); + ctrl_free_box(es->eventbox); +} +static void eventlog_ok_handler(union control *ctrl, dlgparam *dp, + void *data, int event) +{ + if (event == EVENT_ACTION) + dlg_end(dp, 0); +} +static void eventlog_list_handler(union control *ctrl, dlgparam *dp, + void *data, int event) +{ + eventlog_stuff *es = (eventlog_stuff *)data; + + if (event == EVENT_REFRESH) { + int i; + + dlg_update_start(ctrl, dp); + dlg_listbox_clear(ctrl, dp); + for (i = 0; i < es->ninitial; i++) { + dlg_listbox_add(ctrl, dp, es->events_initial[i]); + } + for (i = 0; i < es->ncircular; i++) { + dlg_listbox_add(ctrl, dp, es->events_circular[(es->circular_first + i) % LOGEVENT_CIRCULAR_MAX]); + } + dlg_update_done(ctrl, dp); + } else if (event == EVENT_SELCHANGE) { + int i; + + /* + * 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. + */ + strbuf_clear(es->seldata); + for (i = 0; i < es->ninitial; i++) { + if (dlg_listbox_issel(ctrl, dp, i)) + strbuf_catf(es->seldata, "%s\n", es->events_initial[i]); + } + for (i = 0; i < es->ncircular; i++) { + if (dlg_listbox_issel(ctrl, dp, es->ninitial + i)) { + int j = (es->circular_first + i) % LOGEVENT_CIRCULAR_MAX; + strbuf_catf(es->seldata, "%s\n", es->events_circular[j]); + } + } + + if (gtk_selection_owner_set(es->window, GDK_SELECTION_PRIMARY, + GDK_CURRENT_TIME)) { + 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) +{ + eventlog_stuff *es = (eventlog_stuff *)data; + + gtk_selection_data_set(seldata, gtk_selection_data_get_target(seldata), 8, + es->seldata->u, es->seldata->len); +} + +gint eventlog_selection_clear(GtkWidget *widget, GdkEventSelection *seldata, + gpointer data) +{ + eventlog_stuff *es = (eventlog_stuff *)data; + struct uctrl *uc; + + /* + * Deselect everything in the list box. + */ + uc = dlg_find_byctrl(&es->dp, es->listctrl); + es->ignore_selchange = true; +#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 = false; + + return true; +} + +void showeventlog(eventlog_stuff *es, void *parentwin) +{ + 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 = our_dialog_new(); + title = dupcat(appname, " Event Log"); + gtk_window_set_title(GTK_WINDOW(window), title); + sfree(title); + w0 = layout_ctrls(&es->dp, NULL, &es->scs, s0, GTK_WINDOW(window)); + our_dialog_set_action_area(GTK_WINDOW(window), w0); + gtk_widget_show(w0); + w1 = layout_ctrls(&es->dp, NULL, &es->scs, s1, GTK_WINDOW(window)); + gtk_container_set_border_width(GTK_CONTAINER(w1), 10); + gtk_widget_set_size_request(w1, 20 + string_width + ("LINE OF TEXT GIVING WIDTH OF EVENT LOG IS " + "QUITE LONG 'COS SSH LOG ENTRIES ARE WIDE"), + -1); + our_dialog_add_to_content_area(GTK_WINDOW(window), 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); + + g_signal_connect(G_OBJECT(window), "destroy", + G_CALLBACK(eventlog_destroy), es); + g_signal_connect(G_OBJECT(window), "key_press_event", + G_CALLBACK(win_key_press), &es->dp); + g_signal_connect(G_OBJECT(window), "selection_get", + G_CALLBACK(eventlog_selection_get), es); + g_signal_connect(G_OBJECT(window), "selection_clear_event", + G_CALLBACK(eventlog_selection_clear), es); +} + +eventlog_stuff *eventlogstuff_new(void) +{ + eventlog_stuff *es = snew(eventlog_stuff); + memset(es, 0, sizeof(*es)); + es->seldata = strbuf_new(); + return es; +} + +void eventlogstuff_free(eventlog_stuff *es) +{ + int i; + + if (es->events_initial) { + for (i = 0; i < LOGEVENT_INITIAL_MAX; i++) + sfree(es->events_initial[i]); + sfree(es->events_initial); + } + if (es->events_circular) { + for (i = 0; i < LOGEVENT_CIRCULAR_MAX; i++) + sfree(es->events_circular[i]); + sfree(es->events_circular); + } + strbuf_free(es->seldata); + + sfree(es); +} + +void logevent_dlg(eventlog_stuff *es, const char *string) +{ + char timebuf[40]; + struct tm tm; + char **location; + size_t i; + + if (es->ninitial == 0) { + es->events_initial = sresize(es->events_initial, LOGEVENT_INITIAL_MAX, char *); + for (i = 0; i < LOGEVENT_INITIAL_MAX; i++) + es->events_initial[i] = NULL; + es->events_circular = sresize(es->events_circular, LOGEVENT_CIRCULAR_MAX, char *); + for (i = 0; i < LOGEVENT_CIRCULAR_MAX; i++) + es->events_circular[i] = NULL; + } + + if (es->ninitial < LOGEVENT_INITIAL_MAX) + location = &es->events_initial[es->ninitial]; + else + location = &es->events_circular[(es->circular_first + es->ncircular) % LOGEVENT_CIRCULAR_MAX]; + + tm=ltime(); + strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S\t", &tm); + + sfree(*location); + *location = dupcat(timebuf, string); + if (es->window) { + dlg_listbox_add(es->listctrl, &es->dp, *location); + } + if (es->ninitial < LOGEVENT_INITIAL_MAX) { + es->ninitial++; + } else if (es->ncircular < LOGEVENT_CIRCULAR_MAX) { + es->ncircular++; + } else if (es->ncircular == LOGEVENT_CIRCULAR_MAX) { + es->circular_first = (es->circular_first + 1) % LOGEVENT_CIRCULAR_MAX; + sfree(es->events_circular[es->circular_first]); + es->events_circular[es->circular_first] = dupstr(".."); + } +} + +int gtkdlg_askappend(Seat *seat, 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."; + static const struct message_box_button button_array_append[] = { + {"Overwrite", 'o', 1, 2}, + {"Append", 'a', 0, 1}, + {"Disable", 'd', -1, 0}, + }; + static const struct message_box_buttons buttons_append = { + button_array_append, lenof(button_array_append), + }; + + char *message; + char *mbtitle; + struct simple_prompt_result_ctx *result_ctx; + GtkWidget *mainwin, *msgbox; + + message = dupprintf(msgtemplate, FILENAME_MAX, filename->path); + mbtitle = dupprintf("%s Log to File", appname); + + result_ctx = snew(struct simple_prompt_result_ctx); + result_ctx->callback = callback; + result_ctx->callback_ctx = ctx; + result_ctx->seat = seat; + result_ctx->dialog_slot = DIALOG_SLOT_LOGFILE_PROMPT; + + mainwin = GTK_WIDGET(gtk_seat_get_window(seat)); + msgbox = create_message_box( + mainwin, mbtitle, message, + string_width("LINE OF TEXT SUITABLE FOR THE ASKAPPEND WIDTH"), + false, &buttons_append, simple_prompt_result_callback, result_ctx); + register_dialog(seat, result_ctx->dialog_slot, msgbox); + + sfree(message); + sfree(mbtitle); + + return -1; /* dialog still in progress */ +} diff --git a/unix/fd-socket.c b/unix/fd-socket.c new file mode 100644 index 00000000..7697d995 --- /dev/null +++ b/unix/fd-socket.c @@ -0,0 +1,357 @@ +/* + * uxfdsock.c: implementation of Socket that just talks to two + * existing input and output file descriptors. + */ + +#include +#include +#include +#include +#include + +#include "tree234.h" +#include "putty.h" +#include "network.h" + +typedef struct FdSocket { + int outfd, infd, inerrfd; + + bufchain pending_output_data; + bufchain pending_input_data; + ProxyStderrBuf psb; + enum { EOF_NO, EOF_PENDING, EOF_SENT } outgoingeof; + + int pending_error; + + Plug *plug; + + Socket sock; +} FdSocket; + +static void fdsocket_select_result_input(int fd, int event); +static void fdsocket_select_result_output(int fd, int event); +static void fdsocket_select_result_input_error(int fd, int event); + +/* + * Trees to look up the fds in. + */ +static tree234 *fdsocket_by_outfd; +static tree234 *fdsocket_by_infd; +static tree234 *fdsocket_by_inerrfd; + +static int fdsocket_infd_cmp(void *av, void *bv) +{ + FdSocket *a = (FdSocket *)av; + FdSocket *b = (FdSocket *)bv; + if (a->infd < b->infd) + return -1; + if (a->infd > b->infd) + return +1; + return 0; +} +static int fdsocket_infd_find(void *av, void *bv) +{ + int a = *(int *)av; + FdSocket *b = (FdSocket *)bv; + if (a < b->infd) + return -1; + if (a > b->infd) + return +1; + return 0; +} +static int fdsocket_inerrfd_cmp(void *av, void *bv) +{ + FdSocket *a = (FdSocket *)av; + FdSocket *b = (FdSocket *)bv; + if (a->inerrfd < b->inerrfd) + return -1; + if (a->inerrfd > b->inerrfd) + return +1; + return 0; +} +static int fdsocket_inerrfd_find(void *av, void *bv) +{ + int a = *(int *)av; + FdSocket *b = (FdSocket *)bv; + if (a < b->inerrfd) + return -1; + if (a > b->inerrfd) + return +1; + return 0; +} +static int fdsocket_outfd_cmp(void *av, void *bv) +{ + FdSocket *a = (FdSocket *)av; + FdSocket *b = (FdSocket *)bv; + if (a->outfd < b->outfd) + return -1; + if (a->outfd > b->outfd) + return +1; + return 0; +} +static int fdsocket_outfd_find(void *av, void *bv) +{ + int a = *(int *)av; + FdSocket *b = (FdSocket *)bv; + if (a < b->outfd) + return -1; + if (a > b->outfd) + return +1; + return 0; +} + +static Plug *fdsocket_plug(Socket *s, Plug *p) +{ + FdSocket *fds = container_of(s, FdSocket, sock); + Plug *ret = fds->plug; + if (p) + fds->plug = p; + return ret; +} + +static void fdsocket_close(Socket *s) +{ + FdSocket *fds = container_of(s, FdSocket, sock); + + if (fds->outfd >= 0) { + del234(fdsocket_by_outfd, fds); + uxsel_del(fds->outfd); + close(fds->outfd); + } + + if (fds->infd >= 0) { + del234(fdsocket_by_infd, fds); + uxsel_del(fds->infd); + close(fds->infd); + } + + if (fds->inerrfd >= 0) { + del234(fdsocket_by_inerrfd, fds); + uxsel_del(fds->inerrfd); + close(fds->inerrfd); + } + + bufchain_clear(&fds->pending_input_data); + bufchain_clear(&fds->pending_output_data); + + delete_callbacks_for_context(fds); + + sfree(fds); +} + +static void fdsocket_error_callback(void *vs) +{ + FdSocket *fds = (FdSocket *)vs; + + /* + * Just in case other socket work has caused this socket to vanish + * or become somehow non-erroneous before this callback arrived... + */ + if (!fds->pending_error) + return; + + /* + * An error has occurred on this socket. Pass it to the plug. + */ + plug_closing(fds->plug, strerror(fds->pending_error), + fds->pending_error, 0); +} + +static int fdsocket_try_send(FdSocket *fds) +{ + int sent = 0; + + while (bufchain_size(&fds->pending_output_data) > 0) { + ssize_t ret; + + ptrlen data = bufchain_prefix(&fds->pending_output_data); + ret = write(fds->outfd, data.ptr, data.len); + noise_ultralight(NOISE_SOURCE_IOID, ret); + if (ret < 0 && errno != EWOULDBLOCK) { + if (!fds->pending_error) { + fds->pending_error = errno; + queue_toplevel_callback(fdsocket_error_callback, fds); + } + return 0; + } else if (ret <= 0) { + break; + } else { + bufchain_consume(&fds->pending_output_data, ret); + sent += ret; + } + } + + if (fds->outgoingeof == EOF_PENDING) { + del234(fdsocket_by_outfd, fds); + close(fds->outfd); + uxsel_del(fds->outfd); + fds->outfd = -1; + fds->outgoingeof = EOF_SENT; + } + + if (bufchain_size(&fds->pending_output_data) == 0) + uxsel_del(fds->outfd); + else + uxsel_set(fds->outfd, SELECT_W, fdsocket_select_result_output); + + return sent; +} + +static size_t fdsocket_write(Socket *s, const void *data, size_t len) +{ + FdSocket *fds = container_of(s, FdSocket, sock); + + assert(fds->outgoingeof == EOF_NO); + + bufchain_add(&fds->pending_output_data, data, len); + + fdsocket_try_send(fds); + + return bufchain_size(&fds->pending_output_data); +} + +static size_t fdsocket_write_oob(Socket *s, const void *data, size_t len) +{ + /* + * oob data is treated as inband; nasty, but nothing really + * better we can do + */ + return fdsocket_write(s, data, len); +} + +static void fdsocket_write_eof(Socket *s) +{ + FdSocket *fds = container_of(s, FdSocket, sock); + + assert(fds->outgoingeof == EOF_NO); + fds->outgoingeof = EOF_PENDING; + + fdsocket_try_send(fds); +} + +static void fdsocket_set_frozen(Socket *s, bool is_frozen) +{ + FdSocket *fds = container_of(s, FdSocket, sock); + + if (fds->infd < 0) + return; + + if (is_frozen) + uxsel_del(fds->infd); + else + uxsel_set(fds->infd, SELECT_R, fdsocket_select_result_input); +} + +static const char *fdsocket_socket_error(Socket *s) +{ + return NULL; +} + +static void fdsocket_select_result_input(int fd, int event) +{ + FdSocket *fds; + char buf[20480]; + int retd; + + if (!(fds = find234(fdsocket_by_infd, &fd, fdsocket_infd_find))) + return; + + retd = read(fds->infd, buf, sizeof(buf)); + if (retd > 0) { + plug_receive(fds->plug, 0, buf, retd); + } else { + if (retd < 0) { + plug_closing(fds->plug, strerror(errno), errno, 0); + } else { + plug_closing(fds->plug, NULL, 0, 0); + } + del234(fdsocket_by_infd, fds); + uxsel_del(fds->infd); + close(fds->infd); + fds->infd = -1; + } +} + +static void fdsocket_select_result_output(int fd, int event) +{ + FdSocket *fds; + + if (!(fds = find234(fdsocket_by_outfd, &fd, fdsocket_outfd_find))) + return; + + if (fdsocket_try_send(fds)) + plug_sent(fds->plug, bufchain_size(&fds->pending_output_data)); +} + +static void fdsocket_select_result_input_error(int fd, int event) +{ + FdSocket *fds; + char buf[20480]; + int retd; + + if (!(fds = find234(fdsocket_by_inerrfd, &fd, fdsocket_inerrfd_find))) + return; + + retd = read(fd, buf, sizeof(buf)); + if (retd > 0) { + log_proxy_stderr(fds->plug, &fds->psb, buf, retd); + } else { + del234(fdsocket_by_inerrfd, fds); + uxsel_del(fds->inerrfd); + close(fds->inerrfd); + fds->inerrfd = -1; + } +} + +static const SocketVtable FdSocket_sockvt = { + .plug = fdsocket_plug, + .close = fdsocket_close, + .write = fdsocket_write, + .write_oob = fdsocket_write_oob, + .write_eof = fdsocket_write_eof, + .set_frozen = fdsocket_set_frozen, + .socket_error = fdsocket_socket_error, + .peer_info = NULL, +}; + +Socket *make_fd_socket(int infd, int outfd, int inerrfd, Plug *plug) +{ + FdSocket *fds; + + fds = snew(FdSocket); + fds->sock.vt = &FdSocket_sockvt; + fds->plug = plug; + fds->outgoingeof = EOF_NO; + fds->pending_error = 0; + + fds->infd = infd; + fds->outfd = outfd; + fds->inerrfd = inerrfd; + + bufchain_init(&fds->pending_input_data); + bufchain_init(&fds->pending_output_data); + psb_init(&fds->psb); + + if (fds->outfd >= 0) { + if (!fdsocket_by_outfd) + fdsocket_by_outfd = newtree234(fdsocket_outfd_cmp); + add234(fdsocket_by_outfd, fds); + } + + if (fds->infd >= 0) { + if (!fdsocket_by_infd) + fdsocket_by_infd = newtree234(fdsocket_infd_cmp); + add234(fdsocket_by_infd, fds); + uxsel_set(fds->infd, SELECT_R, fdsocket_select_result_input); + } + + if (fds->inerrfd >= 0) { + assert(fds->inerrfd != fds->infd); + if (!fdsocket_by_inerrfd) + fdsocket_by_inerrfd = newtree234(fdsocket_inerrfd_cmp); + add234(fdsocket_by_inerrfd, fds); + uxsel_set(fds->inerrfd, SELECT_R, fdsocket_select_result_input_error); + } + + return &fds->sock; +} diff --git a/unix/gss.c b/unix/gss.c new file mode 100644 index 00000000..cd9971c7 --- /dev/null +++ b/unix/gss.c @@ -0,0 +1,175 @@ +#include "putty.h" +#ifndef NO_GSSAPI +#include "ssh/pgssapi.h" +#include "ssh/gss.h" +#include "ssh/gssc.h" + +/* Unix code to set up the GSSAPI library list. */ + +#if !defined NO_LIBDL && !defined STATIC_GSSAPI && !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(verify_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); + BIND_GSS_FN(acquire_cred); + BIND_GSS_FN(inquire_cred_by_mech); + +#undef BIND_GSS_FN + + ssh_gssapi_bind_fns(lib); +} + +/* Dynamically load gssapi libs. */ +struct ssh_gss_liblist *ssh_gss_setup(Conf *conf) +{ + void *gsslib; + char *gsspath; + struct ssh_gss_liblist *list = snew(struct ssh_gss_liblist); + + list->libraries = snewn(4, struct ssh_gss_library); + list->nlibraries = 0; + + /* 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 */ + gsspath = conf_get_filename(conf, CONF_ssh_gss_custom)->path; + if (*gsspath && (gsslib = dlopen(gsspath, RTLD_LAZY)) != NULL) + gss_init(&list->libraries[list->nlibraries++], gsslib, + 3, dupprintf("Using GSSAPI from user-specified" + " library '%s'", gsspath)); + + 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 + +/* Dynamically load gssapi libs. */ +struct ssh_gss_liblist *ssh_gss_setup(Conf *conf) +{ + struct ssh_gss_liblist *list = snew(struct ssh_gss_liblist); + + list->libraries = snew(struct ssh_gss_library); + list->nlibraries = 1; + + 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(verify_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); + BIND_GSS_FN(acquire_cred); + BIND_GSS_FN(inquire_cred_by_mech); + +#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 */ diff --git a/unix/gtk-common.c b/unix/gtk-common.c new file mode 100644 index 00000000..af39a83c --- /dev/null +++ b/unix/gtk-common.c @@ -0,0 +1,241 @@ +/* + * gtkcomm.c: machinery in the GTK front end which is common to all + * programs that run a session in a terminal window, and also common + * across all _sessions_ rather than specific to one session. (Timers, + * uxsel etc.) + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if !GTK_CHECK_VERSION(3,0,0) +#include +#endif + +#if GTK_CHECK_VERSION(2,0,0) +#include +#endif + +#define MAY_REFER_TO_GTK_IN_HEADERS + +#include "putty.h" +#include "terminal.h" +#include "gtkcompat.h" +#include "unifont.h" +#include "gtkmisc.h" + +#ifndef NOT_X_WINDOWS +#include +#include +#include +#include +#endif + +#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 + +/* ---------------------------------------------------------------------- + * File descriptors and uxsel. + */ + +struct uxsel_id { +#if GTK_CHECK_VERSION(2,0,0) + GIOChannel *chan; + guint watch_id; +#else + int id; +#endif +}; + +#if GTK_CHECK_VERSION(2,0,0) +gboolean fd_input_func(GIOChannel *source, GIOCondition condition, + gpointer data) +{ + int sourcefd = g_io_channel_unix_get_fd(source); + /* + * We must process exceptional notifications before ordinary + * readability ones, or we may go straight past the urgent + * marker. + */ + if (condition & G_IO_PRI) + select_result(sourcefd, SELECT_X); + if (condition & (G_IO_IN | G_IO_HUP)) + select_result(sourcefd, SELECT_R); + if (condition & G_IO_OUT) + select_result(sourcefd, SELECT_W); + + return true; +} +#else +void fd_input_func(gpointer data, gint sourcefd, GdkInputCondition condition) +{ + if (condition & GDK_INPUT_EXCEPTION) + select_result(sourcefd, SELECT_X); + if (condition & GDK_INPUT_READ) + select_result(sourcefd, SELECT_R); + if (condition & GDK_INPUT_WRITE) + select_result(sourcefd, SELECT_W); +} +#endif + +uxsel_id *uxsel_input_add(int fd, int rwx) { + uxsel_id *id = snew(uxsel_id); + +#if GTK_CHECK_VERSION(2,0,0) + int flags = 0; + if (rwx & SELECT_R) flags |= G_IO_IN | G_IO_HUP; + if (rwx & SELECT_W) flags |= G_IO_OUT; + if (rwx & SELECT_X) flags |= G_IO_PRI; + id->chan = g_io_channel_unix_new(fd); + g_io_channel_set_encoding(id->chan, NULL, NULL); + id->watch_id = g_io_add_watch_full(id->chan, GDK_PRIORITY_REDRAW+1, flags, + fd_input_func, NULL, NULL); +#else + int flags = 0; + if (rwx & SELECT_R) flags |= GDK_INPUT_READ; + if (rwx & SELECT_W) flags |= GDK_INPUT_WRITE; + if (rwx & SELECT_X) flags |= GDK_INPUT_EXCEPTION; + assert(flags); + id->id = gdk_input_add(fd, flags, fd_input_func, NULL); +#endif + + return id; +} + +void uxsel_input_remove(uxsel_id *id) { +#if GTK_CHECK_VERSION(2,0,0) + g_source_remove(id->watch_id); + g_io_channel_unref(id->chan); +#else + gdk_input_remove(id->id); +#endif + sfree(id); +} + +/* ---------------------------------------------------------------------- + * Timers. + */ + +static guint timer_id = 0; + +static gint timer_trigger(gpointer data) +{ + unsigned long now = GPOINTER_TO_LONG(data); + unsigned long next, then; + long ticks; + + /* + * Destroy the timer we got here on. + */ + if (timer_id) { + g_source_remove(timer_id); + timer_id = 0; + } + + /* + * run_timers() may cause a call to timer_change_notify, in which + * case a new timer will already have been set up and left in + * timer_id. If it hasn't, and run_timers reports that some timing + * still needs to be done, we do it ourselves. + */ + if (run_timers(now, &next) && !timer_id) { + then = now; + now = GETTICKCOUNT(); + if (now - then > next - then) + ticks = 0; + else + ticks = next - now; + timer_id = g_timeout_add(ticks, timer_trigger, LONG_TO_GPOINTER(next)); + } + + /* + * Returning false means 'don't call this timer again', which + * _should_ be redundant given that we removed it above, but just + * in case, return false anyway. + */ + return false; +} + +void timer_change_notify(unsigned long next) +{ + long ticks; + + if (timer_id) + g_source_remove(timer_id); + + ticks = next - GETTICKCOUNT(); + if (ticks <= 0) + ticks = 1; /* just in case */ + + timer_id = g_timeout_add(ticks, timer_trigger, LONG_TO_GPOINTER(next)); +} + +/* ---------------------------------------------------------------------- + * Toplevel callbacks. + */ + +static guint toplevel_callback_idle_id; +static bool idle_fn_scheduled; + +static void notify_toplevel_callback(void *); + +static gint idle_toplevel_callback_func(gpointer data) +{ + run_toplevel_callbacks(); + + /* + * If we've emptied our toplevel callback queue, unschedule + * ourself. Otherwise, leave ourselves pending so we'll be called + * again to deal with more callbacks after another round of the + * event loop. + */ + if (!toplevel_callback_pending() && idle_fn_scheduled) { + g_source_remove(toplevel_callback_idle_id); + idle_fn_scheduled = false; + } + + return true; +} + +static void notify_toplevel_callback(void *vctx) +{ + if (!idle_fn_scheduled) { + toplevel_callback_idle_id = + g_idle_add(idle_toplevel_callback_func, NULL); + idle_fn_scheduled = true; + } +} + +/* ---------------------------------------------------------------------- + * Setup function. The real main program must call this. + */ + +void gtkcomm_setup(void) +{ + uxsel_init(); + request_callback_notifications(notify_toplevel_callback, NULL); +} diff --git a/unix/gtkapp.c b/unix/gtkapp.c deleted file mode 100644 index fb924009..00000000 --- a/unix/gtkapp.c +++ /dev/null @@ -1,340 +0,0 @@ -/* - * gtkapp.c: a top-level front end to GUI PuTTY and pterm, using - * GtkApplication. Suitable for OS X. Currently unfinished. - * - * (You could run it on ordinary Linux GTK too, in principle, but I - * don't think it would be particularly useful to do so, even once - * it's fully working.) - */ - -/* - -Building this for OS X is currently broken, because the new -CMake-based build system doesn't support it yet. Probably what needs -doing is to add it back in to unix/CMakeLists.txt under a condition -like if(CMAKE_SYSTEM_NAME MATCHES "Darwin"). - -*/ - -/* - -TODO list for a sensible GTK3 PuTTY/pterm on OS X: - -Still to do on the application menu bar: items that have to vary with -context or user action (saved sessions and mid-session special -commands), and disabling/enabling the main actions in parallel with -their counterparts in the Ctrl-rightclick context menu. - -Mouse wheel events and trackpad scrolling gestures don't work quite -right in the terminal drawing area. This seems to be a combination of -two things, neither of which I completely understand yet. Firstly, on -OS X GTK my trackpad seems to generate GDK scroll events for which -gdk_event_get_scroll_deltas returns integers rather than integer -multiples of 1/30, so we end up scrolling by very large amounts; -secondly, the window doesn't seem to receive a GTK "draw" event until -after the entire scroll gesture is complete, which means we don't get -constant visual feedback on how much we're scrolling by. - -There doesn't seem to be a resize handle on terminal windows. Then -again, they do seem to _be_ resizable; the handle just isn't shown. -Perhaps that's a feature (certainly in a scrollbarless configuration -the handle gets in the way of the bottom right character cell in the -terminal itself), but it would be nice to at least understand _why_ it -happens and perhaps include an option to put it back again. - -A slight oddity with menus that pop up directly under the mouse -pointer: mousing over the menu items doesn't highlight them initially, -but if I mouse off the menu and back on (without un-popping-it-up) -then suddenly that does work. I don't know if this is something I can -fix, though; it might very well be a quirk of the underlying GTK. - -Does OS X have a standard system of online help that I could tie into? - -Need to work out what if anything we can do with Pageant on OS X. -Perhaps it's too much bother and we should just talk to the -system-provided SSH agent? Or perhaps not. - -Nice-to-have: a custom right-click menu from the application's dock -tile, listing the saved sessions for quick launch. As far as I know -there's nothing built in to GtkApplication that can produce this, but -it's possible we might be able to drop a piece of native Cocoa code in -under ifdef, substituting an application delegate of our own which -forwards all methods we're not interested in to the GTK-provided one? - -At the point where this becomes polished enough to publish pre-built, -I suppose I'll have to look into OS X code signing. -https://wiki.gnome.org/Projects/GTK%2B/OSX/Bundling has some links. - - */ - -#include -#include - -#include - -#include - -#define MAY_REFER_TO_GTK_IN_HEADERS - -#include "putty.h" -#include "gtkmisc.h" - -char *x_get_default(const char *key) { return NULL; } - -const bool buildinfo_gtk_relevant = true; - -#if !GTK_CHECK_VERSION(3,0,0) -/* This front end only works in GTK 3. If that's not what we've got, - * it's easier to just turn this program into a trivial stub by ifdef - * in the source than it is to remove it in the makefile edifice. */ -int main(int argc, char **argv) -{ - fprintf(stderr, "GtkApplication frontend doesn't work pre-GTK3\n"); - return 1; -} -GtkWidget *make_gtk_toplevel_window(GtkFrontend *frontend) { return NULL; } -void launch_duplicate_session(Conf *conf) {} -void launch_new_session(void) {} -void launch_saved_session(const char *str) {} -void session_window_closed(void) {} -void window_setup_error(const char *errmsg) {} -#else /* GTK_CHECK_VERSION(3,0,0) */ - -static void startup(GApplication *app, gpointer user_data) -{ - GMenu *menubar, *menu, *section; - - menubar = g_menu_new(); - - menu = g_menu_new(); - g_menu_append_submenu(menubar, "File", G_MENU_MODEL(menu)); - - section = g_menu_new(); - g_menu_append_section(menu, NULL, G_MENU_MODEL(section)); - g_menu_append(section, "New Window", "app.newwin"); - - menu = g_menu_new(); - g_menu_append_submenu(menubar, "Edit", G_MENU_MODEL(menu)); - - section = g_menu_new(); - g_menu_append_section(menu, NULL, G_MENU_MODEL(section)); - g_menu_append(section, "Copy", "win.copy"); - g_menu_append(section, "Paste", "win.paste"); - g_menu_append(section, "Copy All", "win.copyall"); - - menu = g_menu_new(); - g_menu_append_submenu(menubar, "Window", G_MENU_MODEL(menu)); - - section = g_menu_new(); - g_menu_append_section(menu, NULL, G_MENU_MODEL(section)); - g_menu_append(section, "Restart Session", "win.restart"); - g_menu_append(section, "Duplicate Session", "win.duplicate"); - - section = g_menu_new(); - g_menu_append_section(menu, NULL, G_MENU_MODEL(section)); - g_menu_append(section, "Change Settings", "win.changesettings"); - - if (use_event_log) { - section = g_menu_new(); - g_menu_append_section(menu, NULL, G_MENU_MODEL(section)); - g_menu_append(section, "Event Log", "win.eventlog"); - } - - section = g_menu_new(); - g_menu_append_section(menu, NULL, G_MENU_MODEL(section)); - g_menu_append(section, "Clear Scrollback", "win.clearscrollback"); - g_menu_append(section, "Reset Terminal", "win.resetterm"); - -#if GTK_CHECK_VERSION(3,12,0) -#define SET_ACCEL(app, command, accel) do \ - { \ - static const char *const accels[] = { accel, NULL }; \ - gtk_application_set_accels_for_action( \ - GTK_APPLICATION(app), command, accels); \ - } while (0) -#else - /* The Gtk function used above was new in 3.12; the one below - * was deprecated from 3.14. */ -#define SET_ACCEL(app, command, accel) \ - gtk_application_add_accelerator(GTK_APPLICATION(app), accel, \ - command, NULL) -#endif - - SET_ACCEL(app, "app.newwin", "n"); - SET_ACCEL(app, "win.copy", "c"); - SET_ACCEL(app, "win.paste", "v"); - -#undef SET_ACCEL - - gtk_application_set_menubar(GTK_APPLICATION(app), - G_MENU_MODEL(menubar)); -} - -#define WIN_ACTION_LIST(X) \ - X("copy", MA_COPY) \ - X("paste", MA_PASTE) \ - X("copyall", MA_COPY_ALL) \ - X("duplicate", MA_DUPLICATE_SESSION) \ - X("restart", MA_RESTART_SESSION) \ - X("changesettings", MA_CHANGE_SETTINGS) \ - X("clearscrollback", MA_CLEAR_SCROLLBACK) \ - X("resetterm", MA_RESET_TERMINAL) \ - X("eventlog", MA_EVENT_LOG) \ - /* end of list */ - -#define WIN_ACTION_CALLBACK(name, id) \ -static void win_action_cb_ ## id(GSimpleAction *a, GVariant *p, gpointer d) \ -{ app_menu_action(d, id); } -WIN_ACTION_LIST(WIN_ACTION_CALLBACK) -#undef WIN_ACTION_CALLBACK - -static const GActionEntry win_actions[] = { -#define WIN_ACTION_ENTRY(name, id) { name, win_action_cb_ ## id }, -WIN_ACTION_LIST(WIN_ACTION_ENTRY) -#undef WIN_ACTION_ENTRY -}; - -static GtkApplication *app; -GtkWidget *make_gtk_toplevel_window(GtkFrontend *frontend) -{ - GtkWidget *win = gtk_application_window_new(app); - g_action_map_add_action_entries(G_ACTION_MAP(win), - win_actions, - G_N_ELEMENTS(win_actions), - frontend); - return win; -} - -void launch_duplicate_session(Conf *conf) -{ - assert(!dup_check_launchable || conf_launchable(conf)); - g_application_hold(G_APPLICATION(app)); - new_session_window(conf_copy(conf), NULL); -} - -void session_window_closed(void) -{ - g_application_release(G_APPLICATION(app)); -} - -static void post_initial_config_box(void *vctx, int result) -{ - Conf *conf = (Conf *)vctx; - - if (result > 0) { - new_session_window(conf, NULL); - } else if (result == 0) { - conf_free(conf); - g_application_release(G_APPLICATION(app)); - } -} - -void launch_saved_session(const char *str) -{ - Conf *conf = conf_new(); - do_defaults(str, conf); - - g_application_hold(G_APPLICATION(app)); - - if (!conf_launchable(conf)) { - initial_config_box(conf, post_initial_config_box, conf); - } else { - new_session_window(conf, NULL); - } -} - -void launch_new_session(void) -{ - /* Same as launch_saved_session except that we pass NULL to - * do_defaults. */ - launch_saved_session(NULL); -} - -void new_app_win(GtkApplication *app) -{ - launch_new_session(); -} - -static void window_setup_error_callback(void *vctx, int result) -{ - g_application_release(G_APPLICATION(app)); -} - -void window_setup_error(const char *errmsg) -{ - create_message_box(NULL, "Error creating session window", errmsg, - string_width("Some sort of fiddly error message that " - "might be technical"), - true, &buttons_ok, window_setup_error_callback, NULL); -} - -static void activate(GApplication *app, - gpointer user_data) -{ - new_app_win(GTK_APPLICATION(app)); -} - -static void newwin_cb(GSimpleAction *action, - GVariant *parameter, - gpointer user_data) -{ - new_app_win(GTK_APPLICATION(user_data)); -} - -static void quit_cb(GSimpleAction *action, - GVariant *parameter, - gpointer user_data) -{ - g_application_quit(G_APPLICATION(user_data)); -} - -static void about_cb(GSimpleAction *action, - GVariant *parameter, - gpointer user_data) -{ - about_box(NULL); -} - -static const GActionEntry app_actions[] = { - { "newwin", newwin_cb }, - { "about", about_cb }, - { "quit", quit_cb }, -}; - -int main(int argc, char **argv) -{ - int status; - - /* Call the function in ux{putty,pterm}.c to do app-type - * specific setup */ - setup(false); /* false means we are not a one-session process */ - - if (argc > 1) { - pty_osx_envrestore_prefix = argv[--argc]; - } - - { - const char *home = getenv("HOME"); - if (home) { - if (chdir(home)) {} - } - } - - gtkcomm_setup(); - - app = gtk_application_new("org.tartarus.projects.putty.macputty", - G_APPLICATION_FLAGS_NONE); - g_signal_connect(app, "activate", G_CALLBACK(activate), NULL); - g_signal_connect(app, "startup", G_CALLBACK(startup), NULL); - g_action_map_add_action_entries(G_ACTION_MAP(app), - app_actions, - G_N_ELEMENTS(app_actions), - app); - - status = g_application_run(G_APPLICATION(app), argc, argv); - g_object_unref(app); - - return status; -} - -#endif /* GTK_CHECK_VERSION(3,0,0) */ diff --git a/unix/gtkask.c b/unix/gtkask.c deleted file mode 100644 index 4e95ba1a..00000000 --- a/unix/gtkask.c +++ /dev/null @@ -1,662 +0,0 @@ -/* - * GTK implementation of a GUI password/passphrase prompt. - */ - -#include -#include -#include - -#include - -#include -#include -#if !GTK_CHECK_VERSION(3,0,0) -#include -#endif - -#include "defs.h" -#include "gtkfont.h" -#include "gtkcompat.h" -#include "gtkmisc.h" - -#include "putty.h" -#include "ssh.h" -#include "misc.h" - -#define N_DRAWING_AREAS 3 - -struct drawing_area_ctx { - GtkWidget *area; -#ifndef DRAW_DEFAULT_CAIRO - GdkColor *cols; -#endif - int width, height; - enum { NOT_CURRENT, CURRENT, GREYED_OUT } state; -}; - -struct askpass_ctx { - GtkWidget *dialog, *promptlabel; - struct drawing_area_ctx drawingareas[N_DRAWING_AREAS]; - int active_area; -#if GTK_CHECK_VERSION(2,0,0) - GtkIMContext *imc; -#endif -#ifndef DRAW_DEFAULT_CAIRO - GdkColormap *colmap; - GdkColor cols[3]; -#endif - char *error_message; /* if we finish without a passphrase */ - char *passphrase; /* if we finish with one */ - int passlen, passsize; -#if GTK_CHECK_VERSION(3,20,0) - GdkSeat *seat; /* for gdk_seat_grab */ -#elif GTK_CHECK_VERSION(3,0,0) - GdkDevice *keyboard; /* for gdk_device_grab */ -#endif - - int nattempts; -}; - -static prng *keypress_prng = NULL; -static void feed_keypress_prng(void *data, int size) -{ - put_data(keypress_prng, data, size); -} -void random_add_noise(NoiseSourceId source, const void *noise, int length) -{ - if (keypress_prng) - prng_add_entropy(keypress_prng, source, make_ptrlen(noise, length)); -} -static void setup_keypress_prng(void) -{ - keypress_prng = prng_new(&ssh_sha256); - prng_seed_begin(keypress_prng); - noise_get_heavy(feed_keypress_prng); - prng_seed_finish(keypress_prng); -} -static void cleanup_keypress_prng(void) -{ - prng_free(keypress_prng); -} -static uint64_t keypress_prng_value(void) -{ - /* - * Don't actually put the passphrase keystrokes themselves into - * the PRNG; that doesn't seem like the course of wisdom when - * that's precisely what the information displayed on the screen - * is trying _not_ to be correlated to. - */ - noise_ultralight(NOISE_SOURCE_KEY, 0); - uint8_t data[8]; - prng_read(keypress_prng, data, 8); - return GET_64BIT_MSB_FIRST(data); -} -static int choose_new_area(int prev_area) -{ - int reduced = keypress_prng_value() % (N_DRAWING_AREAS - 1); - return (prev_area + 1 + reduced) % N_DRAWING_AREAS; -} - -static void visually_acknowledge_keypress(struct askpass_ctx *ctx) -{ - int new_active = choose_new_area(ctx->active_area); - ctx->drawingareas[ctx->active_area].state = NOT_CURRENT; - gtk_widget_queue_draw(ctx->drawingareas[ctx->active_area].area); - ctx->drawingareas[new_active].state = CURRENT; - gtk_widget_queue_draw(ctx->drawingareas[new_active].area); - ctx->active_area = new_active; -} - -static int last_char_len(struct askpass_ctx *ctx) -{ - /* - * GTK always encodes in UTF-8, so we can do this in a fixed way. - */ - int i; - assert(ctx->passlen > 0); - i = ctx->passlen - 1; - while ((unsigned)((unsigned char)ctx->passphrase[i] - 0x80) < 0x40) { - if (i == 0) - break; - i--; - } - return ctx->passlen - i; -} - -static void add_text_to_passphrase(struct askpass_ctx *ctx, gchar *str) -{ - int len = strlen(str); - if (ctx->passlen + len >= ctx->passsize) { - /* Take some care with buffer expansion, because there are - * pieces of passphrase in the old buffer so we should ensure - * realloc doesn't leave a copy lying around in the address - * space. */ - int oldsize = ctx->passsize; - char *newbuf; - - ctx->passsize = (ctx->passlen + len) * 5 / 4 + 1024; - newbuf = snewn(ctx->passsize, char); - memcpy(newbuf, ctx->passphrase, oldsize); - smemclr(ctx->passphrase, oldsize); - sfree(ctx->passphrase); - ctx->passphrase = newbuf; - } - strcpy(ctx->passphrase + ctx->passlen, str); - ctx->passlen += len; - visually_acknowledge_keypress(ctx); -} - -static void cancel_askpass(struct askpass_ctx *ctx, const char *msg) -{ - smemclr(ctx->passphrase, ctx->passsize); - ctx->passphrase = NULL; - ctx->error_message = dupstr(msg); - gtk_main_quit(); -} - -static gboolean askpass_dialog_closed(GtkWidget *widget, GdkEvent *event, - gpointer data) -{ - struct askpass_ctx *ctx = (struct askpass_ctx *)data; - cancel_askpass(ctx, "passphrase input cancelled"); - /* Don't destroy dialog yet, so gtk_askpass_cleanup() can do its work */ - return true; -} - -static gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) -{ - struct askpass_ctx *ctx = (struct askpass_ctx *)data; - - if (event->keyval == GDK_KEY_Return && - event->type == GDK_KEY_PRESS) { - gtk_main_quit(); - } else if (event->keyval == GDK_KEY_Escape && - event->type == GDK_KEY_PRESS) { - cancel_askpass(ctx, "passphrase input cancelled"); - } else { -#if GTK_CHECK_VERSION(2,0,0) - if (gtk_im_context_filter_keypress(ctx->imc, event)) - return true; -#endif - - if (event->type == GDK_KEY_PRESS) { - if (!strcmp(event->string, "\x15")) { - /* Ctrl-U. Wipe out the whole line */ - ctx->passlen = 0; - visually_acknowledge_keypress(ctx); - } else if (!strcmp(event->string, "\x17")) { - /* Ctrl-W. Delete back to the last space->nonspace - * boundary. We interpret 'space' in a really simple - * way (mimicking terminal drivers), and don't attempt - * to second-guess exciting Unicode space - * characters. */ - while (ctx->passlen > 0) { - char deleted, prior; - ctx->passlen -= last_char_len(ctx); - deleted = ctx->passphrase[ctx->passlen]; - prior = (ctx->passlen == 0 ? ' ' : - ctx->passphrase[ctx->passlen-1]); - if (!g_ascii_isspace(deleted) && g_ascii_isspace(prior)) - break; - } - visually_acknowledge_keypress(ctx); - } else if (event->keyval == GDK_KEY_BackSpace) { - /* Backspace. Delete one character. */ - if (ctx->passlen > 0) - ctx->passlen -= last_char_len(ctx); - visually_acknowledge_keypress(ctx); -#if !GTK_CHECK_VERSION(2,0,0) - } else if (event->string[0]) { - add_text_to_passphrase(ctx, event->string); -#endif - } - } - } - return true; -} - -#if GTK_CHECK_VERSION(2,0,0) -static void input_method_commit_event(GtkIMContext *imc, gchar *str, - gpointer data) -{ - struct askpass_ctx *ctx = (struct askpass_ctx *)data; - add_text_to_passphrase(ctx, str); -} -#endif - -static gint configure_area(GtkWidget *widget, GdkEventConfigure *event, - gpointer data) -{ - struct drawing_area_ctx *ctx = (struct drawing_area_ctx *)data; - ctx->width = event->width; - ctx->height = event->height; - gtk_widget_queue_draw(widget); - return true; -} - -#ifdef DRAW_DEFAULT_CAIRO -static void askpass_redraw_cairo(cairo_t *cr, struct drawing_area_ctx *ctx) -{ - double rgbval = (ctx->state == CURRENT ? 0 : - ctx->state == NOT_CURRENT ? 1 : 0.5); - cairo_set_source_rgb(cr, rgbval, rgbval, rgbval); - cairo_paint(cr); -} -#else -static void askpass_redraw_gdk(GdkWindow *win, struct drawing_area_ctx *ctx) -{ - GdkGC *gc = gdk_gc_new(win); - gdk_gc_set_foreground(gc, &ctx->cols[ctx->state]); - gdk_draw_rectangle(win, gc, true, 0, 0, ctx->width, ctx->height); - gdk_gc_unref(gc); -} -#endif - -#if GTK_CHECK_VERSION(3,0,0) -static gint draw_area(GtkWidget *widget, cairo_t *cr, gpointer data) -{ - struct drawing_area_ctx *ctx = (struct drawing_area_ctx *)data; - askpass_redraw_cairo(cr, ctx); - return true; -} -#else -static gint expose_area(GtkWidget *widget, GdkEventExpose *event, - gpointer data) -{ - struct drawing_area_ctx *ctx = (struct drawing_area_ctx *)data; - -#ifdef DRAW_DEFAULT_CAIRO - cairo_t *cr = gdk_cairo_create(gtk_widget_get_window(ctx->area)); - askpass_redraw_cairo(cr, ctx); - cairo_destroy(cr); -#else - askpass_redraw_gdk(gtk_widget_get_window(ctx->area), ctx); -#endif - - return true; -} -#endif - -static gboolean try_grab_keyboard(gpointer vctx) -{ - struct askpass_ctx *ctx = (struct askpass_ctx *)vctx; - int i, ret; - -#if GTK_CHECK_VERSION(3,20,0) - /* - * Grabbing the keyboard in GTK 3.20 requires the new notion of - * GdkSeat. - */ - GdkSeat *seat; - GdkWindow *gdkw = gtk_widget_get_window(ctx->dialog); - if (!GDK_IS_WINDOW(gdkw) || !gdk_window_is_visible(gdkw)) - goto fail; - - seat = gdk_display_get_default_seat - (gtk_widget_get_display(ctx->dialog)); - if (!seat) - goto fail; - - ctx->seat = seat; - ret = gdk_seat_grab(seat, gdkw, GDK_SEAT_CAPABILITY_KEYBOARD, - true, NULL, NULL, NULL, NULL); - - /* - * For some reason GDK 3.22 hides the GDK window as a side effect - * of a failed grab. I've no idea why. But if we're going to retry - * the grab, then we need to unhide it again or else we'll just - * get GDK_GRAB_NOT_VIEWABLE on every subsequent attempt. - */ - if (ret != GDK_GRAB_SUCCESS) - gdk_window_show(gdkw); - -#elif GTK_CHECK_VERSION(3,0,0) - /* - * And it has to be done differently again prior to GTK 3.20. - */ - GdkDeviceManager *dm; - GdkDevice *pointer, *keyboard; - - dm = gdk_display_get_device_manager - (gtk_widget_get_display(ctx->dialog)); - if (!dm) - goto fail; - - pointer = gdk_device_manager_get_client_pointer(dm); - if (!pointer) - goto fail; - keyboard = gdk_device_get_associated_device(pointer); - if (!keyboard) - goto fail; - if (gdk_device_get_source(keyboard) != GDK_SOURCE_KEYBOARD) - goto fail; - - ctx->keyboard = keyboard; - ret = gdk_device_grab(ctx->keyboard, - gtk_widget_get_window(ctx->dialog), - GDK_OWNERSHIP_NONE, - true, - GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK, - NULL, - GDK_CURRENT_TIME); -#else - /* - * It's much simpler in GTK 1 and 2! - */ - ret = gdk_keyboard_grab(gtk_widget_get_window(ctx->dialog), - false, GDK_CURRENT_TIME); -#endif - if (ret != GDK_GRAB_SUCCESS) - goto fail; - - /* - * Now that we've got the keyboard grab, connect up our keyboard - * handlers. - */ -#if GTK_CHECK_VERSION(2,0,0) - g_signal_connect(G_OBJECT(ctx->imc), "commit", - G_CALLBACK(input_method_commit_event), ctx); -#endif - g_signal_connect(G_OBJECT(ctx->dialog), "key_press_event", - G_CALLBACK(key_event), ctx); - g_signal_connect(G_OBJECT(ctx->dialog), "key_release_event", - G_CALLBACK(key_event), ctx); -#if GTK_CHECK_VERSION(2,0,0) - gtk_im_context_set_client_window(ctx->imc, - gtk_widget_get_window(ctx->dialog)); -#endif - - /* - * And repaint the key-acknowledgment drawing areas as not greyed - * out. - */ - ctx->active_area = keypress_prng_value() % N_DRAWING_AREAS; - for (i = 0; i < N_DRAWING_AREAS; i++) { - ctx->drawingareas[i].state = - (i == ctx->active_area ? CURRENT : NOT_CURRENT); - gtk_widget_queue_draw(ctx->drawingareas[i].area); - } - - return false; - - fail: - /* - * If we didn't get the grab, reschedule ourself on a timer to try - * again later. - * - * We have to do this rather than just trying once, because there - * is at least one important situation in which the grab may fail - * the first time: any user who is launching an add-key operation - * off some kind of window manager hotkey will almost by - * definition be running this script with a keyboard grab already - * active, namely the one-key grab that the WM (or whatever) uses - * to detect presses of the hotkey. So at the very least we have - * to give the user time to release that key. - */ - if (++ctx->nattempts >= 4) { - cancel_askpass(ctx, "unable to grab keyboard after 5 seconds"); - } else { - g_timeout_add(1000/8, try_grab_keyboard, ctx); - } - return false; -} - -void realize(GtkWidget *widget, gpointer vctx) -{ - struct askpass_ctx *ctx = (struct askpass_ctx *)vctx; - - gtk_grab_add(ctx->dialog); - - /* - * Schedule the first attempt at the keyboard grab. - */ - ctx->nattempts = 0; -#if GTK_CHECK_VERSION(3,20,0) - ctx->seat = NULL; -#elif GTK_CHECK_VERSION(3,0,0) - ctx->keyboard = NULL; -#endif - - g_idle_add(try_grab_keyboard, ctx); -} - -static const char *gtk_askpass_setup(struct askpass_ctx *ctx, - const char *window_title, - const char *prompt_text) -{ - int i; - GtkBox *action_area; - - ctx->passlen = 0; - ctx->passsize = 2048; - ctx->passphrase = snewn(ctx->passsize, char); - - /* - * Create widgets. - */ - ctx->dialog = our_dialog_new(); - gtk_window_set_title(GTK_WINDOW(ctx->dialog), window_title); - gtk_window_set_position(GTK_WINDOW(ctx->dialog), GTK_WIN_POS_CENTER); - g_signal_connect(G_OBJECT(ctx->dialog), "delete-event", - G_CALLBACK(askpass_dialog_closed), ctx); - ctx->promptlabel = gtk_label_new(prompt_text); - align_label_left(GTK_LABEL(ctx->promptlabel)); - gtk_widget_show(ctx->promptlabel); - gtk_label_set_line_wrap(GTK_LABEL(ctx->promptlabel), true); -#if GTK_CHECK_VERSION(3,0,0) - gtk_label_set_width_chars(GTK_LABEL(ctx->promptlabel), 48); -#endif - int margin = string_width("MM"); -#if GTK_CHECK_VERSION(3,12,0) - gtk_widget_set_margin_start(ctx->promptlabel, margin); - gtk_widget_set_margin_end(ctx->promptlabel, margin); -#else - gtk_misc_set_padding(GTK_MISC(ctx->promptlabel), margin, 0); -#endif - our_dialog_add_to_content_area(GTK_WINDOW(ctx->dialog), - ctx->promptlabel, true, true, 0); -#if GTK_CHECK_VERSION(2,0,0) - ctx->imc = gtk_im_multicontext_new(); -#endif -#ifndef DRAW_DEFAULT_CAIRO - { - gboolean success[2]; - ctx->colmap = gdk_colormap_get_system(); - ctx->cols[0].red = ctx->cols[0].green = ctx->cols[0].blue = 0xFFFF; - ctx->cols[1].red = ctx->cols[1].green = ctx->cols[1].blue = 0; - ctx->cols[2].red = ctx->cols[2].green = ctx->cols[2].blue = 0x8000; - gdk_colormap_alloc_colors(ctx->colmap, ctx->cols, 2, - false, true, success); - if (!success[0] || !success[1]) - return "unable to allocate colours"; - } -#endif - - action_area = our_dialog_make_action_hbox(GTK_WINDOW(ctx->dialog)); - - for (i = 0; i < N_DRAWING_AREAS; i++) { - ctx->drawingareas[i].area = gtk_drawing_area_new(); -#ifndef DRAW_DEFAULT_CAIRO - ctx->drawingareas[i].cols = ctx->cols; -#endif - ctx->drawingareas[i].state = GREYED_OUT; - ctx->drawingareas[i].width = ctx->drawingareas[i].height = 0; - /* It would be nice to choose this size in some more - * context-sensitive way, like measuring the size of some - * piece of template text. */ - gtk_widget_set_size_request(ctx->drawingareas[i].area, 32, 32); - gtk_box_pack_end(action_area, ctx->drawingareas[i].area, - true, true, 5); - g_signal_connect(G_OBJECT(ctx->drawingareas[i].area), - "configure_event", - G_CALLBACK(configure_area), - &ctx->drawingareas[i]); -#if GTK_CHECK_VERSION(3,0,0) - g_signal_connect(G_OBJECT(ctx->drawingareas[i].area), - "draw", - G_CALLBACK(draw_area), - &ctx->drawingareas[i]); -#else - g_signal_connect(G_OBJECT(ctx->drawingareas[i].area), - "expose_event", - G_CALLBACK(expose_area), - &ctx->drawingareas[i]); -#endif - -#if GTK_CHECK_VERSION(3,0,0) - g_object_set(G_OBJECT(ctx->drawingareas[i].area), - "margin-bottom", 8, (const char *)NULL); -#endif - - gtk_widget_show(ctx->drawingareas[i].area); - } - ctx->active_area = -1; - - /* - * Arrange to receive key events. We don't really need to worry - * from a UI perspective about which widget gets the events, as - * long as we know which it is so we can catch them. So we'll pick - * the prompt label at random, and we'll use gtk_grab_add to - * ensure key events go to it. - */ - gtk_widget_set_sensitive(ctx->dialog, true); - -#if GTK_CHECK_VERSION(2,0,0) - gtk_window_set_keep_above(GTK_WINDOW(ctx->dialog), true); -#endif - - /* - * Wait for the key-receiving widget to actually be created, in - * order to call gtk_grab_add on it. - */ - g_signal_connect(G_OBJECT(ctx->dialog), "realize", - G_CALLBACK(realize), ctx); - - /* - * Show the window. - */ - gtk_widget_show(ctx->dialog); - - return NULL; -} - -static void gtk_askpass_cleanup(struct askpass_ctx *ctx) -{ -#if GTK_CHECK_VERSION(3,20,0) - if (ctx->seat) - gdk_seat_ungrab(ctx->seat); -#elif GTK_CHECK_VERSION(3,0,0) - if (ctx->keyboard) - gdk_device_ungrab(ctx->keyboard, GDK_CURRENT_TIME); -#else - gdk_keyboard_ungrab(GDK_CURRENT_TIME); -#endif - gtk_grab_remove(ctx->promptlabel); - - if (ctx->passphrase) { - assert(ctx->passlen < ctx->passsize); - ctx->passphrase[ctx->passlen] = '\0'; - } - - gtk_widget_destroy(ctx->dialog); -} - -static bool setup_gtk(const char *display) -{ - static bool gtk_initialised = false; - int argc; - char *real_argv[3]; - char **argv = real_argv; - bool ret; - - if (gtk_initialised) - return true; - - argc = 0; - argv[argc++] = dupstr("dummy"); - argv[argc++] = dupprintf("--display=%s", display); - argv[argc] = NULL; - ret = gtk_init_check(&argc, &argv); - while (argc > 0) - sfree(argv[--argc]); - - gtk_initialised = ret; - return ret; -} - -const bool buildinfo_gtk_relevant = true; - -char *gtk_askpass_main(const char *display, const char *wintitle, - const char *prompt, bool *success) -{ - struct askpass_ctx ctx[1]; - const char *err; - - ctx->passphrase = NULL; - ctx->error_message = NULL; - - /* In case gtk_init hasn't been called yet by the program */ - if (!setup_gtk(display)) { - *success = false; - return dupstr("unable to initialise GTK"); - } - - if ((err = gtk_askpass_setup(ctx, wintitle, prompt)) != NULL) { - *success = false; - return dupprintf("%s", err); - } - setup_keypress_prng(); - gtk_main(); - cleanup_keypress_prng(); - gtk_askpass_cleanup(ctx); - - if (ctx->passphrase) { - *success = true; - return ctx->passphrase; - } else { - *success = false; - return ctx->error_message; - } -} - -#ifdef TEST_ASKPASS -void modalfatalbox(const 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); -} - -int main(int argc, char **argv) -{ - bool success; - int exitcode; - char *ret; - - gtk_init(&argc, &argv); - - if (argc != 2) { - success = false; - ret = dupprintf("usage: %s ", argv[0]); - } else { - ret = gtk_askpass_main(NULL, "Enter passphrase", argv[1], &success); - } - - if (!success) { - fputs(ret, stderr); - fputc('\n', stderr); - exitcode = 1; - } else { - fputs(ret, stdout); - fputc('\n', stdout); - exitcode = 0; - } - - smemclr(ret, strlen(ret)); - return exitcode; -} -#endif diff --git a/unix/gtkcfg.c b/unix/gtkcfg.c deleted file mode 100644 index 93c48ce6..00000000 --- a/unix/gtkcfg.c +++ /dev/null @@ -1,160 +0,0 @@ -/* - * gtkcfg.c - the GTK-specific parts of the PuTTY configuration - * box. - */ - -#include -#include - -#include "putty.h" -#include "dialog.h" -#include "storage.h" - -static void about_handler(union control *ctrl, dlgparam *dlg, - void *data, int event) -{ - if (event == EVENT_ACTION) { - about_box(ctrl->generic.context.p); - } -} - -void gtk_setup_config_box(struct controlbox *b, bool 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), - conf_checkbox_handler, - I(CONF_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 == CONF_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--; - ctrl_free_set(s2); - 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), - conf_fontsel_handler, I(CONF_font)); - ctrl_fontsel(s, "Font used for wide (CJK) text", 'w', - HELPCTX(no_help), - conf_fontsel_handler, I(CONF_widefont)); - s = ctrl_getset(b, "Window/Fonts", "fontbold", - "Fonts for displaying bolded text"); - ctrl_fontsel(s, "Font used for bolded text", 'b', - HELPCTX(no_help), - conf_fontsel_handler, I(CONF_boldfont)); - ctrl_fontsel(s, "Font used for bold wide text", 'i', - HELPCTX(no_help), - conf_fontsel_handler, I(CONF_wideboldfont)); - ctrl_checkbox(s, "Use shadow bold instead of bold fonts", 'u', - HELPCTX(no_help), - conf_checkbox_handler, - I(CONF_shadowbold)); - ctrl_text(s, "(Note that bold fonts or shadow bolding are only" - " used if you have not requested bolding to be done by" - " changing the text colour.)", - HELPCTX(no_help)); - ctrl_editbox(s, "Horizontal offset for shadow bold:", 'z', 20, - HELPCTX(no_help), conf_editbox_handler, - I(CONF_shadowboldoffset), I(-1)); - - /* - * Markus Kuhn feels, not totally unreasonably, that it's good - * for all applications to shift into UTF-8 mode if they notice - * that they've been started with a LANG setting dictating it, - * 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), - conf_checkbox_handler, - I(CONF_utf8_override)); - -#ifdef OSX_META_KEY_CONFIG - /* - * On OS X, there are multiple reasonable opinions about whether - * Option or Command (or both, or neither) should act as a Meta - * key, or whether they should have their normal OS functions. - */ - s = ctrl_getset(b, "Terminal/Keyboard", "meta", - "Choose the Meta key:"); - ctrl_checkbox(s, "Option key acts as Meta", 'p', - HELPCTX(no_help), - conf_checkbox_handler, I(CONF_osx_option_meta)); - ctrl_checkbox(s, "Command key acts as Meta", 'm', - HELPCTX(no_help), - conf_checkbox_handler, I(CONF_osx_command_meta)); -#endif - - 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), conf_editbox_handler, - I(CONF_winclass), I(1)); - } -} diff --git a/unix/gtkcols.c b/unix/gtkcols.c deleted file mode 100644 index 35c02875..00000000 --- a/unix/gtkcols.c +++ /dev/null @@ -1,1201 +0,0 @@ -/* - * gtkcols.c - implementation of the `Columns' GTK layout container. - */ - -#include -#include -#include "defs.h" -#include "gtkcompat.h" -#include "gtkcols.h" - -#if GTK_CHECK_VERSION(2,0,0) -/* The "focus" method lives in GtkWidget from GTK 2 onwards, but it - * was in GtkContainer in GTK 1 */ -#define FOCUS_METHOD_SUPERCLASS GtkWidget -#define FOCUS_METHOD_LOCATION widget_class /* used in columns_init */ -#define CHILD_FOCUS(cont, dir) gtk_widget_child_focus(GTK_WIDGET(cont), dir) -#else -#define FOCUS_METHOD_SUPERCLASS GtkContainer -#define FOCUS_METHOD_LOCATION container_class -#define CHILD_FOCUS(cont, dir) gtk_container_focus(GTK_CONTAINER(cont), dir) -#endif - -static void columns_init(Columns *cols); -static void columns_class_init(ColumnsClass *klass); -#if !GTK_CHECK_VERSION(2,0,0) -static void columns_finalize(GtkObject *object); -#else -static void columns_finalize(GObject *object); -#endif -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); -static gint columns_focus(FOCUS_METHOD_SUPERCLASS *container, - GtkDirectionType dir); -static GType columns_child_type(GtkContainer *container); -#if GTK_CHECK_VERSION(3,0,0) -static void columns_get_preferred_width(GtkWidget *widget, - gint *min, gint *nat); -static void columns_get_preferred_height(GtkWidget *widget, - gint *min, gint *nat); -static void columns_get_preferred_width_for_height(GtkWidget *widget, - gint height, - gint *min, gint *nat); -static void columns_get_preferred_height_for_width(GtkWidget *widget, - gint width, - gint *min, gint *nat); -#else -static void columns_size_request(GtkWidget *widget, GtkRequisition *req); -#endif -static void columns_size_allocate(GtkWidget *widget, GtkAllocation *alloc); - -static GtkContainerClass *parent_class = NULL; - -#if !GTK_CHECK_VERSION(2,0,0) -GType columns_get_type(void) -{ - static GType 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 - -static gint (*columns_inherited_focus)(FOCUS_METHOD_SUPERCLASS *container, - GtkDirectionType direction); - -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 - - object_class->finalize = columns_finalize; - 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 -#if GTK_CHECK_VERSION(3,0,0) - widget_class->get_preferred_width = columns_get_preferred_width; - widget_class->get_preferred_height = columns_get_preferred_height; - widget_class->get_preferred_width_for_height = - columns_get_preferred_width_for_height; - widget_class->get_preferred_height_for_width = - columns_get_preferred_height_for_width; -#else - widget_class->size_request = columns_size_request; -#endif - 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; - - /* Save the previous value of this method. */ - if (!columns_inherited_focus) - columns_inherited_focus = FOCUS_METHOD_LOCATION->focus; - FOCUS_METHOD_LOCATION->focus = columns_focus; -} - -static void columns_init(Columns *cols) -{ - gtk_widget_set_has_window(GTK_WIDGET(cols), false); - - cols->children = NULL; - cols->spacing = 0; -} - -static void columns_child_free(gpointer vchild) -{ - ColumnsChild *child = (ColumnsChild *)vchild; - if (child->percentages) - g_free(child->percentages); - g_free(child); -} - -static void columns_finalize( -#if !GTK_CHECK_VERSION(2,0,0) - GtkObject *object -#else - GObject *object -#endif - ) -{ - Columns *cols; - - g_return_if_fail(object != NULL); - g_return_if_fail(IS_COLUMNS(object)); - - cols = COLUMNS(object); - -#if !GTK_CHECK_VERSION(2,0,0) - { - GList *node; - for (node = cols->children; node; node = node->next) - if (node->data) - columns_child_free(node->data); - } - g_list_free(cols->children); -#else - g_list_free_full(cols->children, columns_child_free); -#endif - - cols->children = NULL; - -#if !GTK_CHECK_VERSION(2,0,0) - GTK_OBJECT_CLASS(parent_class)->finalize(object); -#else - G_OBJECT_CLASS(parent_class)->finalize(object); -#endif -} - -/* - * 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_mapped(GTK_WIDGET(cols), true); - - for (children = cols->children; - children && (child = children->data); - children = children->next) { - if (child->widget && - gtk_widget_get_visible(child->widget) && - !gtk_widget_get_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_set_mapped(GTK_WIDGET(cols), false); - - for (children = cols->children; - children && (child = children->data); - children = children->next) { - if (child->widget && - gtk_widget_get_visible(child->widget) && - gtk_widget_get_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; - bool 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_get_visible(widget); - gtk_widget_unparent(widget); - cols->children = g_list_remove_link(cols->children, children); - g_list_free(children); - - if (child->same_height_as) { - g_return_if_fail(child->same_height_as->same_height_as == child); - child->same_height_as->same_height_as = NULL; - if (gtk_widget_get_visible(child->same_height_as->widget)) - gtk_widget_queue_resize(GTK_WIDGET(container)); - } - - 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); - 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 GType 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(gtk_widget_get_parent(child) == NULL); - - childdata = g_new(ColumnsChild, 1); - childdata->widget = child; - childdata->colstart = colstart; - childdata->colspan = colspan; - childdata->force_left = false; - childdata->same_height_as = NULL; - childdata->percentages = NULL; - - 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_widget_get_realized(GTK_WIDGET(cols))) - gtk_widget_realize(child); - - if (gtk_widget_get_visible(GTK_WIDGET(cols)) && - gtk_widget_get_visible(child)) { - if (gtk_widget_get_mapped(GTK_WIDGET(cols))) - gtk_widget_map(child); - gtk_widget_queue_resize(child); - } -} - -static ColumnsChild *columns_find_child(Columns *cols, GtkWidget *widget) -{ - GList *children; - ColumnsChild *child; - - for (children = cols->children; - children && (child = children->data); - children = children->next) { - - if (child->widget == widget) - return child; - } - - return NULL; -} - -void columns_force_left_align(Columns *cols, GtkWidget *widget) -{ - ColumnsChild *child; - - g_return_if_fail(cols != NULL); - g_return_if_fail(IS_COLUMNS(cols)); - g_return_if_fail(widget != NULL); - - child = columns_find_child(cols, widget); - g_return_if_fail(child != NULL); - - child->force_left = true; - if (gtk_widget_get_visible(widget)) - gtk_widget_queue_resize(GTK_WIDGET(cols)); -} - -void columns_force_same_height(Columns *cols, GtkWidget *cw1, GtkWidget *cw2) -{ - ColumnsChild *child1, *child2; - - g_return_if_fail(cols != NULL); - g_return_if_fail(IS_COLUMNS(cols)); - g_return_if_fail(cw1 != NULL); - g_return_if_fail(cw2 != NULL); - - child1 = columns_find_child(cols, cw1); - g_return_if_fail(child1 != NULL); - child2 = columns_find_child(cols, cw2); - g_return_if_fail(child2 != NULL); - - child1->same_height_as = child2; - child2->same_height_as = child1; - if (gtk_widget_get_visible(cw1) || gtk_widget_get_visible(cw2)) - gtk_widget_queue_resize(GTK_WIDGET(cols)); -} - -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); - break; - } -} - -/* - * Override GtkContainer's focus movement so the user can - * explicitly specify the tab order. - */ -static gint columns_focus(FOCUS_METHOD_SUPERCLASS *super, GtkDirectionType dir) -{ - Columns *cols; - GList *pos; - GtkWidget *focuschild; - - g_return_val_if_fail(super != NULL, false); - g_return_val_if_fail(IS_COLUMNS(super), false); - - cols = COLUMNS(super); - - if (!gtk_widget_is_drawable(GTK_WIDGET(cols)) || - !gtk_widget_is_sensitive(GTK_WIDGET(cols))) - return false; - - if (!gtk_widget_get_can_focus(GTK_WIDGET(cols)) && - (dir == GTK_DIR_TAB_FORWARD || dir == GTK_DIR_TAB_BACKWARD)) { - - focuschild = gtk_container_get_focus_child(GTK_CONTAINER(cols)); - gtk_container_set_focus_child(GTK_CONTAINER(cols), 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_is_drawable(child) && - GTK_IS_CONTAINER(child) && - !gtk_widget_has_focus(child)) { - if (CHILD_FOCUS(child, dir)) - return true; - } - } - } else if (gtk_widget_is_drawable(child)) { - if (GTK_IS_CONTAINER(child)) { - if (CHILD_FOCUS(child, dir)) - return true; - } else if (gtk_widget_get_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(super, dir); -} - -/* - * Underlying parts of the layout algorithm, to compute the Columns - * container's width or height given the widths or heights of its - * children. These will be called in various ways with different - * notions of width and height in use, so we abstract them out and - * pass them a 'get width' or 'get height' function pointer. - */ - -typedef gint (*widget_dim_fn_t)(ColumnsChild *child); - -static gint columns_compute_width(Columns *cols, widget_dim_fn_t get_width) -{ - ColumnsChild *child; - GList *children; - gint i, ncols, colspan, retwidth, childwidth; - const gint *percentages; - static const gint onecol[] = { 100 }; - -#ifdef COLUMNS_WIDTH_DIAGNOSTICS - printf("compute_width(%p): start\n", cols); -#endif - - retwidth = 0; - - ncols = 1; - percentages = onecol; - - for (children = cols->children; - children && (child = children->data); - children = children->next) { - - if (!child->widget) { - /* Column reconfiguration. */ - ncols = child->ncols; - percentages = child->percentages; - continue; - } - - /* Only take visible widgets into account. */ - if (!gtk_widget_get_visible(child->widget)) - continue; - - childwidth = get_width(child); - colspan = child->colspan ? child->colspan : ncols-child->colstart; - assert(colspan > 0); - -#ifdef COLUMNS_WIDTH_DIAGNOSTICS - printf("compute_width(%p): ", cols); - if (GTK_IS_LABEL(child->widget)) - printf("label %p '%s' wrap=%s: ", child->widget, - gtk_label_get_text(GTK_LABEL(child->widget)), - (gtk_label_get_line_wrap(GTK_LABEL(child->widget)) - ? "true" : "false")); - else - printf("widget %p: ", child->widget); - { - gint min, nat; - gtk_widget_get_preferred_width(child->widget, &min, &nat); - printf("minwidth=%d natwidth=%d ", min, nat); - } - printf("thiswidth=%d span=%d\n", childwidth, colspan); -#endif - - /* - * To compute width: we know that childwidth + 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 = childwidth + cols->spacing; - /* - * Since childwidth is (at least sometimes) 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; -#ifdef COLUMNS_WIDTH_DIAGNOSTICS - printf("compute_width(%p): after %p, thiswid=%d fullwid=%d\n", - cols, child->widget, thiswid, fullwid); -#endif - - /* - * 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 (retwidth < fullwid - cols->spacing) - retwidth = fullwid - cols->spacing; - } - } - - retwidth += 2*gtk_container_get_border_width(GTK_CONTAINER(cols)); - -#ifdef COLUMNS_WIDTH_DIAGNOSTICS - printf("compute_width(%p): done, returning %d\n", cols, retwidth); -#endif - - return retwidth; -} - -static void columns_alloc_horiz(Columns *cols, gint ourwidth, - widget_dim_fn_t get_width) -{ - ColumnsChild *child; - GList *children; - gint i, ncols, colspan, border, *colxpos, childwidth; - - border = gtk_container_get_border_width(GTK_CONTAINER(cols)); - - ncols = 1; - /* 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] = ourwidth - 2*border + cols->spacing; - - for (children = cols->children; - children && (child = children->data); - children = children->next) { - - if (!child->widget) { - gint percent; - - /* Column reconfiguration. */ - ncols = child->ncols; - colxpos = g_renew(gint, colxpos, ncols + 1); - colxpos[0] = 0; - percent = 0; - for (i = 0; i < ncols; i++) { - percent += child->percentages[i]; - colxpos[i+1] = (((ourwidth - 2*border) + cols->spacing) - * percent / 100); - } - continue; - } - - /* Only take visible widgets into account. */ - if (!gtk_widget_get_visible(child->widget)) - continue; - - childwidth = get_width(child); - 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. - */ - child->x = colxpos[child->colstart]; - if (child->force_left) - child->w = childwidth; - else - child->w = (colxpos[child->colstart+colspan] - - colxpos[child->colstart] - cols->spacing); - } - - g_free(colxpos); -} - -static gint columns_compute_height(Columns *cols, widget_dim_fn_t get_height) -{ - ColumnsChild *child; - GList *children; - gint i, ncols, colspan, *colypos, retheight, childheight; - - retheight = cols->spacing; - - ncols = 1; - colypos = g_new(gint, 1); - colypos[0] = 0; - - for (children = cols->children; - children && (child = children->data); - children = children->next) { - - if (!child->widget) { - /* Column reconfiguration. */ - for (i = 1; i < ncols; i++) { - if (colypos[0] < colypos[i]) - colypos[0] = colypos[i]; - } - ncols = child->ncols; - 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_get_visible(child->widget)) - continue; - - childheight = get_height(child); - if (child->same_height_as) { - gint childheight2 = get_height(child->same_height_as); - if (childheight < childheight2) - childheight = childheight2; - } - colspan = child->colspan ? child->colspan : ncols-child->colstart; - - /* - * 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 childheight 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 height. - */ - { - int topy, boty; - - topy = 0; - for (i = 0; i < colspan; i++) { - if (topy < colypos[child->colstart+i]) - topy = colypos[child->colstart+i]; - } - boty = topy + childheight + cols->spacing; - for (i = 0; i < colspan; i++) { - colypos[child->colstart+i] = boty; - } - - if (retheight < boty - cols->spacing) - retheight = boty - cols->spacing; - } - } - - retheight += 2*gtk_container_get_border_width(GTK_CONTAINER(cols)); - - g_free(colypos); - - return retheight; -} - -static void columns_alloc_vert(Columns *cols, gint ourheight, - widget_dim_fn_t get_height) -{ - ColumnsChild *child; - GList *children; - gint i, ncols, colspan, *colypos, realheight, fakeheight; - - ncols = 1; - /* 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) { - if (!child->widget) { - /* Column reconfiguration. */ - for (i = 1; i < ncols; i++) { - if (colypos[0] < colypos[i]) - colypos[0] = colypos[i]; - } - ncols = child->ncols; - 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_get_visible(child->widget)) - continue; - - realheight = fakeheight = get_height(child); - if (child->same_height_as) { - gint childheight2 = get_height(child->same_height_as); - if (fakeheight < childheight2) - fakeheight = childheight2; - } - colspan = child->colspan ? child->colspan : ncols-child->colstart; - - /* - * 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]; - } - child->y = topy + fakeheight/2 - realheight/2; - child->h = realheight; - boty = topy + fakeheight + cols->spacing; - for (i = 0; i < colspan; i++) { - colypos[child->colstart+i] = boty; - } - } - } - - g_free(colypos); -} - -/* - * 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. - */ - -#if !GTK_CHECK_VERSION(3,0,0) - -static gint columns_gtk2_get_width(ColumnsChild *child) -{ - GtkRequisition creq; - gtk_widget_size_request(child->widget, &creq); - return creq.width; -} - -static gint columns_gtk2_get_height(ColumnsChild *child) -{ - GtkRequisition creq; - gtk_widget_size_request(child->widget, &creq); - return creq.height; -} - -static void columns_size_request(GtkWidget *widget, GtkRequisition *req) -{ - Columns *cols; - - g_return_if_fail(widget != NULL); - g_return_if_fail(IS_COLUMNS(widget)); - g_return_if_fail(req != NULL); - - cols = COLUMNS(widget); - - req->width = columns_compute_width(cols, columns_gtk2_get_width); - req->height = columns_compute_height(cols, columns_gtk2_get_height); -} - -static void columns_size_allocate(GtkWidget *widget, GtkAllocation *alloc) -{ - Columns *cols; - ColumnsChild *child; - GList *children; - gint border; - - g_return_if_fail(widget != NULL); - g_return_if_fail(IS_COLUMNS(widget)); - g_return_if_fail(alloc != NULL); - - cols = COLUMNS(widget); - gtk_widget_set_allocation(widget, alloc); - - border = gtk_container_get_border_width(GTK_CONTAINER(cols)); - - columns_alloc_horiz(cols, alloc->width, columns_gtk2_get_width); - columns_alloc_vert(cols, alloc->height, columns_gtk2_get_height); - - for (children = cols->children; - children && (child = children->data); - children = children->next) { - if (child->widget && gtk_widget_get_visible(child->widget)) { - GtkAllocation call; - call.x = alloc->x + border + child->x; - call.y = alloc->y + border + child->y; - call.width = child->w; - call.height = child->h; - gtk_widget_size_allocate(child->widget, &call); - } - } -} - -#else /* GTK_CHECK_VERSION(3,0,0) */ - -static gint columns_gtk3_get_min_width(ColumnsChild *child) -{ - gint ret; - gtk_widget_get_preferred_width(child->widget, &ret, NULL); - return ret; -} - -static gint columns_gtk3_get_nat_width(ColumnsChild *child) -{ - gint ret; - - if ((GTK_IS_LABEL(child->widget) && - gtk_label_get_line_wrap(GTK_LABEL(child->widget))) || - GTK_IS_ENTRY(child->widget)) { - /* - * We treat wrapping GtkLabels as a special case in this - * layout class, because the whole point of those is that I - * _don't_ want them to take up extra horizontal space for - * long text, but instead to wrap it to whatever size is used - * by the rest of the layout. - * - * GtkEntry gets similar treatment, because in OS X GTK I've - * found that it requests a natural width regardless of the - * output of gtk_entry_set_width_chars. - */ - gtk_widget_get_preferred_width(child->widget, &ret, NULL); - } else { - gtk_widget_get_preferred_width(child->widget, NULL, &ret); - } - return ret; -} - -static gint columns_gtk3_get_minfh_width(ColumnsChild *child) -{ - gint ret; - gtk_widget_get_preferred_width_for_height(child->widget, child->h, - &ret, NULL); - return ret; -} - -static gint columns_gtk3_get_natfh_width(ColumnsChild *child) -{ - gint ret; - gtk_widget_get_preferred_width_for_height(child->widget, child->h, - NULL, &ret); - return ret; -} - -static gint columns_gtk3_get_min_height(ColumnsChild *child) -{ - gint ret; - gtk_widget_get_preferred_height(child->widget, &ret, NULL); - return ret; -} - -static gint columns_gtk3_get_nat_height(ColumnsChild *child) -{ - gint ret; - gtk_widget_get_preferred_height(child->widget, NULL, &ret); - return ret; -} - -static gint columns_gtk3_get_minfw_height(ColumnsChild *child) -{ - gint ret; - gtk_widget_get_preferred_height_for_width(child->widget, child->w, - &ret, NULL); - return ret; -} - -static gint columns_gtk3_get_natfw_height(ColumnsChild *child) -{ - gint ret; - gtk_widget_get_preferred_height_for_width(child->widget, child->w, - NULL, &ret); - return ret; -} - -static void columns_get_preferred_width(GtkWidget *widget, - gint *min, gint *nat) -{ - Columns *cols; - - g_return_if_fail(widget != NULL); - g_return_if_fail(IS_COLUMNS(widget)); - - cols = COLUMNS(widget); - - if (min) - *min = columns_compute_width(cols, columns_gtk3_get_min_width); - if (nat) - *nat = columns_compute_width(cols, columns_gtk3_get_nat_width); -} - -static void columns_get_preferred_height(GtkWidget *widget, - gint *min, gint *nat) -{ - Columns *cols; - - g_return_if_fail(widget != NULL); - g_return_if_fail(IS_COLUMNS(widget)); - - cols = COLUMNS(widget); - - if (min) - *min = columns_compute_height(cols, columns_gtk3_get_min_height); - if (nat) - *nat = columns_compute_height(cols, columns_gtk3_get_nat_height); -} - -static void columns_get_preferred_width_for_height(GtkWidget *widget, - gint height, - gint *min, gint *nat) -{ - Columns *cols; - - g_return_if_fail(widget != NULL); - g_return_if_fail(IS_COLUMNS(widget)); - - cols = COLUMNS(widget); - - /* FIXME: which one should the get-height function here be? */ - columns_alloc_vert(cols, height, columns_gtk3_get_nat_height); - - if (min) - *min = columns_compute_width(cols, columns_gtk3_get_minfh_width); - if (nat) - *nat = columns_compute_width(cols, columns_gtk3_get_natfh_width); -} - -static void columns_get_preferred_height_for_width(GtkWidget *widget, - gint width, - gint *min, gint *nat) -{ - Columns *cols; - - g_return_if_fail(widget != NULL); - g_return_if_fail(IS_COLUMNS(widget)); - - cols = COLUMNS(widget); - - /* FIXME: which one should the get-height function here be? */ - columns_alloc_horiz(cols, width, columns_gtk3_get_nat_width); - - if (min) - *min = columns_compute_height(cols, columns_gtk3_get_minfw_height); - if (nat) - *nat = columns_compute_height(cols, columns_gtk3_get_natfw_height); -} - -static void columns_size_allocate(GtkWidget *widget, GtkAllocation *alloc) -{ - Columns *cols; - ColumnsChild *child; - GList *children; - gint border; - - g_return_if_fail(widget != NULL); - g_return_if_fail(IS_COLUMNS(widget)); - g_return_if_fail(alloc != NULL); - - cols = COLUMNS(widget); - gtk_widget_set_allocation(widget, alloc); - - border = gtk_container_get_border_width(GTK_CONTAINER(cols)); - - columns_alloc_horiz(cols, alloc->width, columns_gtk3_get_min_width); - columns_alloc_vert(cols, alloc->height, columns_gtk3_get_minfw_height); - - for (children = cols->children; - children && (child = children->data); - children = children->next) { - if (child->widget && gtk_widget_get_visible(child->widget)) { - GtkAllocation call; - call.x = alloc->x + border + child->x; - call.y = alloc->y + border + child->y; - call.width = child->w; - call.height = child->h; - gtk_widget_size_allocate(child->widget, &call); - } - } -} - -#endif diff --git a/unix/gtkcols.h b/unix/gtkcols.h deleted file mode 100644 index e589cf79..00000000 --- a/unix/gtkcols.h +++ /dev/null @@ -1,65 +0,0 @@ -/* - * 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 -#include - -#ifdef __cplusplus -extern "C" { -#endif /* __cplusplus */ - -#define TYPE_COLUMNS (columns_get_type()) -#define COLUMNS(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), TYPE_COLUMNS, Columns)) -#define COLUMNS_CLASS(klass) \ - (G_TYPE_CHECK_CLASS_CAST((klass), TYPE_COLUMNS, ColumnsClass)) -#define IS_COLUMNS(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), TYPE_COLUMNS)) -#define IS_COLUMNS_CLASS(klass) (G_TYPE_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; - bool force_left; /* for recalcitrant GtkLabels */ - ColumnsChild *same_height_as; - /* Otherwise, this entry represents a change in the column setup. */ - gint ncols; - gint *percentages; - gint x, y, w, h; /* used during an individual size computation */ -}; - -GType 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); -void columns_force_same_height(Columns *cols, GtkWidget *ch1, GtkWidget *ch2); - -#ifdef __cplusplus -} -#endif /* __cplusplus */ - -#endif /* COLUMNS_H */ diff --git a/unix/gtkcomm.c b/unix/gtkcomm.c deleted file mode 100644 index fa52bfb4..00000000 --- a/unix/gtkcomm.c +++ /dev/null @@ -1,241 +0,0 @@ -/* - * gtkcomm.c: machinery in the GTK front end which is common to all - * programs that run a session in a terminal window, and also common - * across all _sessions_ rather than specific to one session. (Timers, - * uxsel etc.) - */ - -#define _GNU_SOURCE - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#if !GTK_CHECK_VERSION(3,0,0) -#include -#endif - -#if GTK_CHECK_VERSION(2,0,0) -#include -#endif - -#define MAY_REFER_TO_GTK_IN_HEADERS - -#include "putty.h" -#include "terminal.h" -#include "gtkcompat.h" -#include "gtkfont.h" -#include "gtkmisc.h" - -#ifndef NOT_X_WINDOWS -#include -#include -#include -#include -#endif - -#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 - -/* ---------------------------------------------------------------------- - * File descriptors and uxsel. - */ - -struct uxsel_id { -#if GTK_CHECK_VERSION(2,0,0) - GIOChannel *chan; - guint watch_id; -#else - int id; -#endif -}; - -#if GTK_CHECK_VERSION(2,0,0) -gboolean fd_input_func(GIOChannel *source, GIOCondition condition, - gpointer data) -{ - int sourcefd = g_io_channel_unix_get_fd(source); - /* - * We must process exceptional notifications before ordinary - * readability ones, or we may go straight past the urgent - * marker. - */ - if (condition & G_IO_PRI) - select_result(sourcefd, SELECT_X); - if (condition & (G_IO_IN | G_IO_HUP)) - select_result(sourcefd, SELECT_R); - if (condition & G_IO_OUT) - select_result(sourcefd, SELECT_W); - - return true; -} -#else -void fd_input_func(gpointer data, gint sourcefd, GdkInputCondition condition) -{ - if (condition & GDK_INPUT_EXCEPTION) - select_result(sourcefd, SELECT_X); - if (condition & GDK_INPUT_READ) - select_result(sourcefd, SELECT_R); - if (condition & GDK_INPUT_WRITE) - select_result(sourcefd, SELECT_W); -} -#endif - -uxsel_id *uxsel_input_add(int fd, int rwx) { - uxsel_id *id = snew(uxsel_id); - -#if GTK_CHECK_VERSION(2,0,0) - int flags = 0; - if (rwx & SELECT_R) flags |= G_IO_IN | G_IO_HUP; - if (rwx & SELECT_W) flags |= G_IO_OUT; - if (rwx & SELECT_X) flags |= G_IO_PRI; - id->chan = g_io_channel_unix_new(fd); - g_io_channel_set_encoding(id->chan, NULL, NULL); - id->watch_id = g_io_add_watch_full(id->chan, GDK_PRIORITY_REDRAW+1, flags, - fd_input_func, NULL, NULL); -#else - int flags = 0; - if (rwx & SELECT_R) flags |= GDK_INPUT_READ; - if (rwx & SELECT_W) flags |= GDK_INPUT_WRITE; - if (rwx & SELECT_X) flags |= GDK_INPUT_EXCEPTION; - assert(flags); - id->id = gdk_input_add(fd, flags, fd_input_func, NULL); -#endif - - return id; -} - -void uxsel_input_remove(uxsel_id *id) { -#if GTK_CHECK_VERSION(2,0,0) - g_source_remove(id->watch_id); - g_io_channel_unref(id->chan); -#else - gdk_input_remove(id->id); -#endif - sfree(id); -} - -/* ---------------------------------------------------------------------- - * Timers. - */ - -static guint timer_id = 0; - -static gint timer_trigger(gpointer data) -{ - unsigned long now = GPOINTER_TO_LONG(data); - unsigned long next, then; - long ticks; - - /* - * Destroy the timer we got here on. - */ - if (timer_id) { - g_source_remove(timer_id); - timer_id = 0; - } - - /* - * run_timers() may cause a call to timer_change_notify, in which - * case a new timer will already have been set up and left in - * timer_id. If it hasn't, and run_timers reports that some timing - * still needs to be done, we do it ourselves. - */ - if (run_timers(now, &next) && !timer_id) { - then = now; - now = GETTICKCOUNT(); - if (now - then > next - then) - ticks = 0; - else - ticks = next - now; - timer_id = g_timeout_add(ticks, timer_trigger, LONG_TO_GPOINTER(next)); - } - - /* - * Returning false means 'don't call this timer again', which - * _should_ be redundant given that we removed it above, but just - * in case, return false anyway. - */ - return false; -} - -void timer_change_notify(unsigned long next) -{ - long ticks; - - if (timer_id) - g_source_remove(timer_id); - - ticks = next - GETTICKCOUNT(); - if (ticks <= 0) - ticks = 1; /* just in case */ - - timer_id = g_timeout_add(ticks, timer_trigger, LONG_TO_GPOINTER(next)); -} - -/* ---------------------------------------------------------------------- - * Toplevel callbacks. - */ - -static guint toplevel_callback_idle_id; -static bool idle_fn_scheduled; - -static void notify_toplevel_callback(void *); - -static gint idle_toplevel_callback_func(gpointer data) -{ - run_toplevel_callbacks(); - - /* - * If we've emptied our toplevel callback queue, unschedule - * ourself. Otherwise, leave ourselves pending so we'll be called - * again to deal with more callbacks after another round of the - * event loop. - */ - if (!toplevel_callback_pending() && idle_fn_scheduled) { - g_source_remove(toplevel_callback_idle_id); - idle_fn_scheduled = false; - } - - return true; -} - -static void notify_toplevel_callback(void *vctx) -{ - if (!idle_fn_scheduled) { - toplevel_callback_idle_id = - g_idle_add(idle_toplevel_callback_func, NULL); - idle_fn_scheduled = true; - } -} - -/* ---------------------------------------------------------------------- - * Setup function. The real main program must call this. - */ - -void gtkcomm_setup(void) -{ - uxsel_init(); - request_callback_notifications(notify_toplevel_callback, NULL); -} diff --git a/unix/gtkdlg.c b/unix/gtkdlg.c deleted file mode 100644 index 8e7421db..00000000 --- a/unix/gtkdlg.c +++ /dev/null @@ -1,4195 +0,0 @@ -/* - * gtkdlg.c - GTK implementation of the PuTTY configuration box. - */ - -#include -#include -#include -#include - -#include -#if !GTK_CHECK_VERSION(3,0,0) -#include -#endif - -#define MAY_REFER_TO_GTK_IN_HEADERS - -#include "putty.h" -#include "gtkcompat.h" -#include "gtkcols.h" -#include "gtkfont.h" -#include "gtkmisc.h" - -#ifndef NOT_X_WINDOWS -#include -#include -#include -#include "x11misc.h" -#endif - -#include "storage.h" -#include "dialog.h" -#include "tree234.h" -#include "licence.h" -#include "ssh.h" - -#if GTK_CHECK_VERSION(2,0,0) -/* Decide which of GtkFileChooserDialog and GtkFileSelection to use */ -#define USE_GTK_FILE_CHOOSER_DIALOG -#endif - -struct Shortcut { - GtkWidget *widget; - struct uctrl *uc; - int action; -}; - -struct Shortcuts { - struct Shortcut sc[128]; -}; - -struct selparam; - -struct uctrl { - union control *ctrl; - GtkWidget *toplevel; - 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 */ - struct selparam *sp; /* which switchable pane of the box we're in */ - guint entrysig; - guint textsig; - int nclicks; -}; - -struct dlgparam { - tree234 *byctrl, *bywidget; - void *data; - struct { - unsigned char r, g, b; /* 0-255 */ - bool ok; - } coloursel_result; - /* `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; -#else - size_t nselparams; - struct selparam **selparams; -#endif - struct selparam *curr_panel; - struct controlbox *ctrlbox; - int retval; - post_dialog_fn_t after; - void *afterctx; -}; -#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 -#if GTK_CHECK_VERSION(3,0,0) -static void colourchoose_response(GtkDialog *dialog, - gint response_id, gpointer data); -#else -static void coloursel_ok(GtkButton *button, gpointer data); -static void coloursel_cancel(GtkButton *button, gpointer data); -#endif -static void dlgparam_destroy(GtkWidget *widget, gpointer data); -static 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->curr_panel = NULL; - 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); - 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 = gtk_widget_get_parent(w); - } while (w); - return ret; -} - -union control *dlg_last_focused(union control *ctrl, dlgparam *dp) -{ - if (dp->currfocus != ctrl) - return dp->currfocus; - else - return dp->lastfocus; -} - -void dlg_radiobutton_set(union control *ctrl, dlgparam *dp, int which) -{ - 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, dlgparam *dp) -{ - 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, dlgparam *dp, bool checked) -{ - 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); -} - -bool dlg_checkbox_get(union control *ctrl, dlgparam *dp) -{ - 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, dlgparam *dp, char const *text) -{ - 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 - * Conf structure, which is precisely where our `text' pointer - * is probably pointing, so the second editing operation will - * insert that instead of the string we originally asked for. - * - * Hence, we must take our own copy of the text before we do - * this. - */ - tmpstring = dupstr(text); - gtk_entry_set_text(GTK_ENTRY(entry), tmpstring); - sfree(tmpstring); -} - -char *dlg_editbox_get(union control *ctrl, dlgparam *dp) -{ - 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) { - return dupstr(gtk_entry_get_text - (GTK_ENTRY(gtk_bin_get_child(GTK_BIN(uc->combo))))); - } -#endif - - if (uc->entry) { - return dupstr(gtk_entry_get_text(GTK_ENTRY(uc->entry))); - } - - unreachable("bad control type in editbox_get"); -} - -#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, dlgparam *dp) -{ - 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 - unreachable("bad control type in listbox_clear"); -} - -void dlg_listbox_del(union control *ctrl, dlgparam *dp, int index) -{ - 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 - unreachable("bad control type in listbox_del"); -} - -void dlg_listbox_add(union control *ctrl, dlgparam *dp, char const *text) -{ - dlg_listbox_addwithid(ctrl, dp, 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, dlgparam *dp, - char const *text, int id) -{ - 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); - - g_object_set_data(G_OBJECT(menuitem), "user-data", - GINT_TO_POINTER(id)); - g_signal_connect(G_OBJECT(menuitem), "activate", - G_CALLBACK(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); - - g_object_set_data(G_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) { - g_signal_connect(G_OBJECT(listitem), "key_press_event", - G_CALLBACK(listitem_multi_key), uc->adj); - } else { - g_signal_connect(G_OBJECT(listitem), "key_press_event", - G_CALLBACK(listitem_single_key), uc->adj); - } - g_signal_connect(G_OBJECT(listitem), "focus_in_event", - G_CALLBACK(widget_focus), dp); - g_signal_connect(G_OBJECT(listitem), "button_press_event", - G_CALLBACK(listitem_button_press), dp); - g_signal_connect(G_OBJECT(listitem), "button_release_event", - G_CALLBACK(listitem_button_release), dp); - g_object_set_data(G_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 - unreachable("bad control type in listbox_addwithid"); - done: - dp->flags &= ~FLAG_UPDATING_COMBO_LIST; -} - -int dlg_listbox_getid(union control *ctrl, dlgparam *dp, int index) -{ - 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; - GObject *item; - - children = gtk_container_children(GTK_CONTAINER(uc->menu ? uc->menu : - uc->list)); - item = G_OBJECT(g_list_nth_data(children, index)); - g_list_free(children); - - return GPOINTER_TO_INT(g_object_get_data(G_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 - unreachable("bad control type in listbox_getid"); - return -1; /* placate dataflow analysis */ -} - -/* dlg_listbox_index returns <0 if no single element is selected. */ -int dlg_listbox_index(union control *ctrl, dlgparam *dp) -{ - 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 - unreachable("bad control type in listbox_index"); - return -1; /* placate dataflow analysis */ -} - -bool dlg_listbox_issel(union control *ctrl, dlgparam *dp, int index) -{ - 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; - bool 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 - unreachable("bad control type in listbox_issel"); - return false; /* placate dataflow analysis */ -} - -void dlg_listbox_select(union control *ctrl, dlgparam *dp, int index) -{ - 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) { - bool 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 - unreachable("bad control type in listbox_select"); -} - -void dlg_text_set(union control *ctrl, dlgparam *dp, char const *text) -{ - 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, dlgparam *dp, char const *text) -{ - 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: - unreachable("bad control type in label_change"); - } -} - -void dlg_filesel_set(union control *ctrl, dlgparam *dp, Filename *fn) -{ - struct uctrl *uc = dlg_find_byctrl(dp, ctrl); - /* We must copy fn->path before passing it to gtk_entry_set_text. - * See comment in dlg_editbox_set() for the reasons. */ - char *duppath = dupstr(fn->path); - assert(uc->ctrl->generic.type == CTRL_FILESELECT); - assert(uc->entry != NULL); - gtk_entry_set_text(GTK_ENTRY(uc->entry), duppath); - sfree(duppath); -} - -Filename *dlg_filesel_get(union control *ctrl, dlgparam *dp) -{ - struct uctrl *uc = dlg_find_byctrl(dp, ctrl); - assert(uc->ctrl->generic.type == CTRL_FILESELECT); - assert(uc->entry != NULL); - return filename_from_str(gtk_entry_get_text(GTK_ENTRY(uc->entry))); -} - -void dlg_fontsel_set(union control *ctrl, dlgparam *dp, FontSpec *fs) -{ - struct uctrl *uc = dlg_find_byctrl(dp, ctrl); - /* We must copy fs->name before passing it to gtk_entry_set_text. - * See comment in dlg_editbox_set() for the reasons. */ - char *dupname = dupstr(fs->name); - assert(uc->ctrl->generic.type == CTRL_FONTSELECT); - assert(uc->entry != NULL); - gtk_entry_set_text(GTK_ENTRY(uc->entry), dupname); - sfree(dupname); -} - -FontSpec *dlg_fontsel_get(union control *ctrl, dlgparam *dp) -{ - struct uctrl *uc = dlg_find_byctrl(dp, ctrl); - assert(uc->ctrl->generic.type == CTRL_FONTSELECT); - assert(uc->entry != NULL); - return fontspec_new(gtk_entry_get_text(GTK_ENTRY(uc->entry))); -} - -/* - * Bracketing a large set of updates in these two functions will - * cause the front end (if possible) to delay updating the screen - * until it's all complete, thus avoiding flicker. - */ -void dlg_update_start(union control *ctrl, dlgparam *dp) -{ - /* - * 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, dlgparam *dp) -{ - /* - * 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, dlgparam *dp) -{ - 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. - */ - for (int 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 - unreachable("bad control type in set_focus"); - } -} - -/* - * 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(dlgparam *dp) -{ - gdk_display_beep(gdk_display_get_default()); -} - -static void set_transient_window_pos(GtkWidget *parent, GtkWidget *child) -{ -#if !GTK_CHECK_VERSION(2,0,0) - 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_get_window(GTK_WIDGET(parent)), &x, &y); - gdk_window_get_size(gtk_widget_get_window(GTK_WIDGET(parent)), &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); -#endif -} - -void trivial_post_dialog_fn(void *vctx, int result) -{ -} - -void dlg_error_msg(dlgparam *dp, const char *msg) -{ - create_message_box( - dp->window, "Error", msg, - string_width("Some sort of text about a config-box error message"), - false, &buttons_ok, trivial_post_dialog_fn, NULL); -} - -/* - * 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(dlgparam *dp, int value) -{ - dp->retval = value; - gtk_widget_destroy(dp->window); -} - -void dlg_refresh(union control *ctrl, dlgparam *dp) -{ - 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, dlgparam *dp, int r, int g, int b) -{ - struct uctrl *uc = dlg_find_byctrl(dp, ctrl); - -#if GTK_CHECK_VERSION(3,0,0) - GtkWidget *coloursel = - gtk_color_chooser_dialog_new("Select a colour", - GTK_WINDOW(dp->window)); - gtk_color_chooser_set_use_alpha(GTK_COLOR_CHOOSER(coloursel), false); -#else - GtkWidget *okbutton, *cancelbutton; - GtkWidget *coloursel = - gtk_color_selection_dialog_new("Select a colour"); - GtkColorSelectionDialog *ccs = GTK_COLOR_SELECTION_DIALOG(coloursel); - GtkColorSelection *cs = GTK_COLOR_SELECTION - (gtk_color_selection_dialog_get_color_selection(ccs)); - gtk_color_selection_set_has_opacity_control(cs, false); -#endif - - dp->coloursel_result.ok = false; - - gtk_window_set_modal(GTK_WINDOW(coloursel), true); - -#if GTK_CHECK_VERSION(3,0,0) - { - GdkRGBA rgba; - rgba.red = r / 255.0; - rgba.green = g / 255.0; - rgba.blue = b / 255.0; - rgba.alpha = 1.0; /* fully opaque! */ - gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(coloursel), &rgba); - } -#elif GTK_CHECK_VERSION(2,0,0) - { - GdkColor col; - col.red = r * 0x0101; - col.green = g * 0x0101; - col.blue = b * 0x0101; - gtk_color_selection_set_current_color(cs, &col); - } -#else - { - gdouble cvals[4]; - 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(cs, cvals); - } -#endif - - g_object_set_data(G_OBJECT(coloursel), "user-data", (gpointer)uc); - -#if GTK_CHECK_VERSION(3,0,0) - g_signal_connect(G_OBJECT(coloursel), "response", - G_CALLBACK(colourchoose_response), (gpointer)dp); -#else - -#if GTK_CHECK_VERSION(2,0,0) - g_object_get(G_OBJECT(ccs), - "ok-button", &okbutton, - "cancel-button", &cancelbutton, - (const char *)NULL); -#else - okbutton = ccs->ok_button; - cancelbutton = ccs->cancel_button; -#endif - g_object_set_data(G_OBJECT(okbutton), "user-data", - (gpointer)coloursel); - g_object_set_data(G_OBJECT(cancelbutton), "user-data", - (gpointer)coloursel); - g_signal_connect(G_OBJECT(okbutton), "clicked", - G_CALLBACK(coloursel_ok), (gpointer)dp); - g_signal_connect(G_OBJECT(cancelbutton), "clicked", - G_CALLBACK(coloursel_cancel), (gpointer)dp); - g_signal_connect_swapped(G_OBJECT(okbutton), "clicked", - G_CALLBACK(gtk_widget_destroy), - (gpointer)coloursel); - g_signal_connect_swapped(G_OBJECT(cancelbutton), "clicked", - G_CALLBACK(gtk_widget_destroy), - (gpointer)coloursel); -#endif - gtk_widget_show(coloursel); -} - -bool dlg_coloursel_results(union control *ctrl, dlgparam *dp, - int *r, int *g, int *b) -{ - if (dp->coloursel_result.ok) { - *r = dp->coloursel_result.r; - *g = dp->coloursel_result.g; - *b = dp->coloursel_result.b; - return true; - } else - return false; -} - -/* ---------------------------------------------------------------------- - * 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. - */ - GtkWidget *parent = gtk_widget_get_parent(widget); - if (event->keyval == GDK_KEY_Return && parent != NULL) { - gboolean return_val; - g_signal_stop_emission_by_name(G_OBJECT(widget), "key_press_event"); - g_signal_emit_by_name(G_OBJECT(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, bool 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; - - g_signal_stop_emission_by_name(G_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_display_beep(gdk_display_get_default()); - 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 = g_object_get_data(G_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) */ - -#ifdef USE_GTK_FILE_CHOOSER_DIALOG -static void filechoose_response(GtkDialog *dialog, gint response, - gpointer data) -{ - /* struct dlgparam *dp = (struct dlgparam *)data; */ - struct uctrl *uc = g_object_get_data(G_OBJECT(dialog), "user-data"); - if (response == GTK_RESPONSE_ACCEPT) { - gchar *name = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); - gtk_entry_set_text(GTK_ENTRY(uc->entry), name); - g_free(name); - } - gtk_widget_destroy(GTK_WIDGET(dialog)); -} -#else -static void filesel_ok(GtkButton *button, gpointer data) -{ - /* struct dlgparam *dp = (struct dlgparam *)data; */ - gpointer filesel = g_object_get_data(G_OBJECT(button), "user-data"); - struct uctrl *uc = g_object_get_data(G_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); -} -#endif - -static void fontsel_ok(GtkButton *button, gpointer data) -{ - /* struct dlgparam *dp = (struct dlgparam *)data; */ - -#if !GTK_CHECK_VERSION(2,0,0) - - gpointer fontsel = g_object_get_data(G_OBJECT(button), "user-data"); - struct uctrl *uc = g_object_get_data(G_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 *)g_object_get_data - (G_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 -} - -#if GTK_CHECK_VERSION(3,0,0) - -static void colourchoose_response(GtkDialog *dialog, - gint response_id, gpointer data) -{ - struct dlgparam *dp = (struct dlgparam *)data; - struct uctrl *uc = g_object_get_data(G_OBJECT(dialog), "user-data"); - - if (response_id == GTK_RESPONSE_OK) { - GdkRGBA rgba; - gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(dialog), &rgba); - dp->coloursel_result.r = (int) (255 * rgba.red); - dp->coloursel_result.g = (int) (255 * rgba.green); - dp->coloursel_result.b = (int) (255 * rgba.blue); - dp->coloursel_result.ok = true; - } else { - dp->coloursel_result.ok = false; - } - - uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_CALLBACK); - - gtk_widget_destroy(GTK_WIDGET(dialog)); -} - -#else /* GTK 1/2 coloursel response handlers */ - -static void coloursel_ok(GtkButton *button, gpointer data) -{ - struct dlgparam *dp = (struct dlgparam *)data; - gpointer coloursel = g_object_get_data(G_OBJECT(button), "user-data"); - struct uctrl *uc = g_object_get_data(G_OBJECT(coloursel), "user-data"); - -#if GTK_CHECK_VERSION(2,0,0) - { - GtkColorSelection *cs = GTK_COLOR_SELECTION - (gtk_color_selection_dialog_get_color_selection - (GTK_COLOR_SELECTION_DIALOG(coloursel))); - GdkColor col; - gtk_color_selection_get_current_color(cs, &col); - dp->coloursel_result.r = col.red / 0x0100; - dp->coloursel_result.g = col.green / 0x0100; - dp->coloursel_result.b = col.blue / 0x0100; - } -#else - { - GtkColorSelection *cs = GTK_COLOR_SELECTION - (gtk_color_selection_dialog_get_color_selection - (GTK_COLOR_SELECTION_DIALOG(coloursel))); - gdouble cvals[4]; - gtk_color_selection_get_color(cs, 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]); - } -#endif - 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 = g_object_get_data(G_OBJECT(button), "user-data"); - struct uctrl *uc = g_object_get_data(G_OBJECT(coloursel), "user-data"); - dp->coloursel_result.ok = false; - uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_CALLBACK); -} - -#endif /* end of coloursel response handlers */ - -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) { -#ifdef USE_GTK_FILE_CHOOSER_DIALOG - GtkWidget *filechoose = gtk_file_chooser_dialog_new - (uc->ctrl->fileselect.title, GTK_WINDOW(dp->window), - (uc->ctrl->fileselect.for_writing ? - GTK_FILE_CHOOSER_ACTION_SAVE : - GTK_FILE_CHOOSER_ACTION_OPEN), - STANDARD_CANCEL_LABEL, GTK_RESPONSE_CANCEL, - STANDARD_OPEN_LABEL, GTK_RESPONSE_ACCEPT, - (const gchar *)NULL); - gtk_window_set_modal(GTK_WINDOW(filechoose), true); - g_object_set_data(G_OBJECT(filechoose), "user-data", (gpointer)uc); - g_signal_connect(G_OBJECT(filechoose), "response", - G_CALLBACK(filechoose_response), (gpointer)dp); - gtk_widget_show(filechoose); -#else - GtkWidget *filesel = - gtk_file_selection_new(uc->ctrl->fileselect.title); - gtk_window_set_modal(GTK_WINDOW(filesel), true); - g_object_set_data - (G_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "user-data", - (gpointer)filesel); - g_object_set_data(G_OBJECT(filesel), "user-data", (gpointer)uc); - g_signal_connect - (G_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "clicked", - G_CALLBACK(filesel_ok), (gpointer)dp); - g_signal_connect_swapped - (G_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "clicked", - G_CALLBACK(gtk_widget_destroy), (gpointer)filesel); - g_signal_connect_swapped - (G_OBJECT(GTK_FILE_SELECTION(filesel)->cancel_button), "clicked", - G_CALLBACK(gtk_widget_destroy), (gpointer)filesel); - gtk_widget_show(filesel); -#endif - } - - 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 = get_x11_display(); - Atom fontprop = XInternAtom(disp, "FONT", False); - unsigned long ret; - - assert(disp); /* this is GTK1! */ - - 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); - } - } - g_object_set_data - (G_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->ok_button), - "user-data", (gpointer)fontsel); - g_object_set_data(G_OBJECT(fontsel), "user-data", (gpointer)uc); - g_signal_connect - (G_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->ok_button), - "clicked", G_CALLBACK(fontsel_ok), (gpointer)dp); - g_signal_connect_swapped - (G_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->ok_button), - "clicked", G_CALLBACK(gtk_widget_destroy), - (gpointer)fontsel); - g_signal_connect_swapped - (G_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->cancel_button), - "clicked", G_CALLBACK(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); - - g_object_set_data(G_OBJECT(fontsel->ok_button), - "user-data", (gpointer)fontsel); - fontsel->user_data = uc; - g_signal_connect(G_OBJECT(fontsel->ok_button), "clicked", - G_CALLBACK(fontsel_ok), (gpointer)dp); - g_signal_connect_swapped(G_OBJECT(fontsel->ok_button), "clicked", - G_CALLBACK(unifontsel_destroy), - (gpointer)fontsel); - g_signal_connect_swapped(G_OBJECT(fontsel->cancel_button),"clicked", - G_CALLBACK(unifontsel_destroy), - (gpointer)fontsel); - - gtk_widget_show(GTK_WIDGET(fontsel->window)); - -#endif /* !GTK_CHECK_VERSION(2,0,0) */ - - } -} - -#if !GTK_CHECK_VERSION(3,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_size_request(uc->text, alloc->width, -1); - gtk_label_set_text(GTK_LABEL(uc->text), uc->ctrl->generic.label); - g_signal_handler_disconnect(G_OBJECT(uc->text), uc->textsig); -} -#endif - -/* ---------------------------------------------------------------------- - * 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 selparam *sp, struct Shortcuts *scs, - struct controlset *s, GtkWindow *win) -{ - Columns *cols; - GtkWidget *ret; - int i; - - if (!s->boxname) { - /* This controlset is a panel title. */ - assert(s->boxtitle); - 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; - bool 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->sp = sp; - uc->ctrl = ctrl; - 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_can_default(w, true); - if (ctrl->button.isdefault) - gtk_window_set_default(win, w); - if (ctrl->button.iscancel) - dp->cancelbutton = w; - } - g_signal_connect(G_OBJECT(w), "clicked", - G_CALLBACK(button_clicked), dp); - g_signal_connect(G_OBJECT(w), "focus_in_event", - G_CALLBACK(widget_focus), dp); - shortcut_add(scs, gtk_bin_get_child(GTK_BIN(w)), - ctrl->button.shortcut, SHORTCUT_UCTRL, uc); - break; - case CTRL_CHECKBOX: - w = gtk_check_button_new_with_label(ctrl->generic.label); - g_signal_connect(G_OBJECT(w), "toggled", - G_CALLBACK(button_toggled), dp); - g_signal_connect(G_OBJECT(w), "focus_in_event", - G_CALLBACK(widget_focus), dp); - shortcut_add(scs, gtk_bin_get_child(GTK_BIN(w)), - 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_get_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); - g_signal_connect(G_OBJECT(b), "toggled", - G_CALLBACK(button_toggled), dp); - g_signal_connect(G_OBJECT(b), "focus_in_event", - G_CALLBACK(widget_focus), dp); - if (ctrl->radio.shortcuts) { - shortcut_add(scs, gtk_bin_get_child(GTK_BIN(b)), - ctrl->radio.shortcuts[i], - SHORTCUT_UCTRL, uc); - } - } - break; - } - case CTRL_EDITBOX: { - 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_new_with_model_and_entry - (GTK_TREE_MODEL(uc->listmodel)); - g_object_set(G_OBJECT(w), "entry-text-column", 1, - (const char *)NULL); - /* 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 = - g_signal_connect(G_OBJECT(signalobject), "changed", - G_CALLBACK(editbox_changed), dp); - g_signal_connect(G_OBJECT(signalobject), "key_press_event", - G_CALLBACK(editbox_key), dp); - g_signal_connect(G_OBJECT(signalobject), "focus_in_event", - G_CALLBACK(widget_focus), dp); - g_signal_connect(G_OBJECT(signalobject), "focus_out_event", - G_CALLBACK(editbox_lostfocus), dp); - g_signal_connect(G_OBJECT(signalobject), "focus_out_event", - G_CALLBACK(editbox_lostfocus), dp); - -#if !GTK_CHECK_VERSION(3,0,0) - /* - * 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. - */ - { - GtkRequisition req; - gtk_widget_size_request(w, &req); - gtk_widget_set_size_request(w, 10, req.height); - } -#else - /* - * In GTK 3, this is still true, but there's a special - * method for GtkEntry in particular to fix it. - */ - if (GTK_IS_ENTRY(w)) - gtk_entry_set_width_chars(GTK_ENTRY(w), 1); -#endif - - 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); - columns_force_same_height(COLUMNS(container), - label, w); - } - gtk_widget_show(label); - gtk_widget_show(w); - - w = container; - uc->label = label; - } - break; - } - case CTRL_FILESELECT: - case CTRL_FONTSELECT: { - GtkWidget *ww; - const 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(); -#if !GTK_CHECK_VERSION(3,0,0) - { - GtkRequisition req; - gtk_widget_size_request(ww, &req); - gtk_widget_set_size_request(ww, 10, req.height); - } -#else - gtk_entry_set_width_chars(GTK_ENTRY(ww), 1); -#endif - 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); - - columns_force_same_height(COLUMNS(w), uc->entry, uc->button); - - g_signal_connect(G_OBJECT(uc->entry), "key_press_event", - G_CALLBACK(editbox_key), dp); - uc->entrysig = - g_signal_connect(G_OBJECT(uc->entry), "changed", - G_CALLBACK(editbox_changed), dp); - g_signal_connect(G_OBJECT(uc->entry), "focus_in_event", - G_CALLBACK(widget_focus), dp); - g_signal_connect(G_OBJECT(uc->button), "focus_in_event", - G_CALLBACK(widget_focus), dp); - g_signal_connect(G_OBJECT(ww), "clicked", - G_CALLBACK(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); - g_object_set_data(G_OBJECT(uc->menu), "user-data", - (gpointer)uc->optmenu); - g_signal_connect(G_OBJECT(uc->optmenu), "focus_in_event", - G_CALLBACK(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); - - g_signal_connect(G_OBJECT(w), "focus_in_event", - G_CALLBACK(widget_focus), 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); - g_signal_connect(G_OBJECT(uc->list), "selection-changed", - G_CALLBACK(list_selchange), dp); - g_signal_connect(G_OBJECT(uc->list), "focus_in_event", - G_CALLBACK(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_size_request(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); - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(draglist_up), dp); - g_signal_connect(G_OBJECT(button), "focus_in_event", - G_CALLBACK(widget_focus), dp); - button = gtk_button_new_with_label("Down"); - columns_add(COLUMNS(cols), button, 1, 1); - gtk_widget_show(button); - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(draglist_down), dp); - g_signal_connect(G_OBJECT(button), "focus_in_event", - G_CALLBACK(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; - g_signal_connect(G_OBJECT(w), "row-activated", - G_CALLBACK(listbox_doubleclick), dp); - g_signal_connect(G_OBJECT(w), "focus_in_event", - G_CALLBACK(widget_focus), 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; - GtkCellRenderer *cellrend; - /* - * It appears that GTK 2 doesn't leave us any - * particularly sensible way to honour the - * "percentages" specification in the ctrl - * structure. - */ - cellrend = gtk_cell_renderer_text_new(); - if (!ctrl->listbox.hscroll) { - g_object_set(G_OBJECT(cellrend), - "ellipsize", PANGO_ELLIPSIZE_END, - "ellipsize-set", true, - (const char *)NULL); - } - column = gtk_tree_view_column_new_with_attributes - ("heading", cellrend, "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; - - label = gtk_label_new(ctrl->generic.label); -#if GTK_CHECK_VERSION(3,0,0) - gtk_label_set_width_chars(GTK_LABEL(label), 3); -#endif - - shortcut_add(scs, label, ctrl->listbox.shortcut, - SHORTCUT_UCTRL, uc); - - 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); - columns_force_same_height(COLUMNS(container), - label, w); - } - gtk_widget_show(label); - gtk_widget_show(w); - - w = container; - uc->label = label; - } - - break; - case CTRL_TEXT: -#if !GTK_CHECK_VERSION(3,0,0) - /* - * Wrapping text widgets don't sit well with the GTK2 - * 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"); - uc->textsig = - g_signal_connect(G_OBJECT(w), "size-allocate", - G_CALLBACK(label_sizealloc), dp); -#else - /* - * In GTK3, this is all fixed, because the main aim of the - * new 'height-for-width' geometry management is to make - * wrapping labels behave sensibly. So now we can just do - * the obvious thing. - */ - uc->text = w = gtk_label_new(uc->ctrl->generic.label); -#endif - align_label_left(GTK_LABEL(w)); - gtk_label_set_line_wrap(GTK_LABEL(w), true); - 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); - if (ctrl->generic.align_next_to) { - /* - * Implement align_next_to by simply forcing the two - * controls to have the same height of size allocation. At - * least for the controls we're currently doing this with, - * the GTK layout system will automatically vertically - * centre each control within its allocation, which will - * get the two controls aligned alongside each other - * reasonably well. - */ - struct uctrl *uc2 = dlg_find_byctrl( - dp, ctrl->generic.align_next_to); - assert(uc2); - columns_force_same_height(cols, w, uc2->toplevel); - -#if GTK_CHECK_VERSION(3, 10, 0) - /* Slightly nicer to align baselines than just vertically - * centring, where the option is available */ - gtk_widget_set_valign(w, GTK_ALIGN_BASELINE); - gtk_widget_set_valign(uc2->toplevel, GTK_ALIGN_BASELINE); -#endif - } - 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_current_page(sp->panels, page_num); - - sp->dp->curr_panel = sp; - 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); - - sp->dp->curr_panel = sp; - dlg_refresh(NULL, sp->dp); - - sp->dp->shortcuts = &sp->shortcuts; - sp->dp->currtreeitem = sp->treeitem; -} -#endif - -bool dlg_is_visible(union control *ctrl, dlgparam *dp) -{ - struct uctrl *uc = dlg_find_byctrl(dp, ctrl); - /* - * A control is visible if it belongs to _no_ notebook page (i.e. - * it's one of the config-box-global buttons like Load or About), - * or if it belongs to the currently selected page. - */ - return uc->sp == NULL || uc->sp == dp->curr_panel; -} - -#if !GTK_CHECK_VERSION(2,0,0) -static bool 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; - - g_signal_stop_emission_by_name(G_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 - -gint win_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data) -{ - struct dlgparam *dp = (struct dlgparam *)data; - - if (event->keyval == GDK_KEY_Escape && dp->cancelbutton) { - g_signal_emit_by_name(G_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); - g_signal_emit_by_name(G_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. */ - g_signal_emit_by_name(G_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]); - g_signal_emit_by_name - (G_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; - g_signal_emit_by_name(G_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 - unreachable("bad listbox type in win_key_press"); - } - break; - } - } - - return false; -} - -#if !GTK_CHECK_VERSION(2,0,0) -gint 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]; - bool 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; - } - } - } - } - g_signal_stop_emission_by_name(G_OBJECT(widget), "key_press_event"); - if (j >= 0) { - g_signal_emit_by_name(G_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) { - g_signal_stop_emission_by_name(G_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) { - g_signal_stop_emission_by_name(G_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); - const gchar *currstr; - gchar *pattern; - int i; - -#if !GTK_CHECK_VERSION(2,0,0) - { - gchar *currstr_nonconst; - gtk_label_get(label, &currstr_nonconst); - currstr = currstr_nonconst; - } -#else - currstr = gtk_label_get_text(label); -#endif - - for (i = 0; currstr[i]; i++) - if (tolower((unsigned char)currstr[i]) == chr) { - pattern = dupprintf("%*s_", i, ""); - gtk_label_set_pattern(label, pattern); - 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 || action == SHORTCUT_TREE) { - 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); -} - -static 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); - g_object_ref_sink(G_OBJECT(listitem)); - return req.height; -#else - int height; - GtkCellRenderer *cr = gtk_cell_renderer_text_new(); -#if GTK_CHECK_VERSION(3,0,0) - { - GtkRequisition req; - /* - * Since none of my list items wraps in this GUI, no - * interesting width-for-height behaviour should be happening, - * so I don't think it should matter here whether I ask for - * the minimum or natural height. - */ - gtk_cell_renderer_get_preferred_size(cr, w, &req, NULL); - height = req.height; - } -#else - gtk_cell_renderer_get_size(cr, w, NULL, NULL, NULL, NULL, &height); -#endif - g_object_ref(G_OBJECT(cr)); - g_object_ref_sink(G_OBJECT(cr)); - g_object_unref(G_OBJECT(cr)); - return height; -#endif -} - -#if GTK_CHECK_VERSION(2,0,0) -void initial_treeview_collapse(struct dlgparam *dp, GtkWidget *tree) -{ - /* - * Collapse the deeper branches of the treeview into the state we - * like them to start off in. See comment below in do_config_box. - */ - int i; - for (i = 0; i < dp->nselparams; i++) - if (dp->selparams[i]->depth >= 2) - gtk_tree_view_collapse_row(GTK_TREE_VIEW(tree), - dp->selparams[i]->treepath); -} -#endif - -#if GTK_CHECK_VERSION(3,0,0) -void treeview_map_event(GtkWidget *tree, gpointer data) -{ - struct dlgparam *dp = (struct dlgparam *)data; - GtkAllocation alloc; - gtk_widget_get_allocation(tree, &alloc); - gtk_widget_set_size_request(tree, alloc.width, -1); - initial_treeview_collapse(dp, tree); -} -#endif - -GtkWidget *create_config_box(const char *title, Conf *conf, - bool midsession, int protcfginfo, - post_dialog_fn_t after, void *afterctx) -{ - GtkWidget *window, *hbox, *vbox, *cols, *label, - *tree, *treescroll, *panels, *panelvbox; - int index, level, protocol; - 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; - size_t nselparams = 0, selparamsize = 0; - - dp = snew(struct dlgparam); - dp->after = after; - dp->afterctx = afterctx; - - dlg_init(dp); - - for (index = 0; index < lenof(scs.sc); index++) { - scs.sc[index].action = SHORTCUT_EMPTY; - } - - window = our_dialog_new(); - - dp->ctrlbox = ctrl_new_box(); - protocol = conf_get_int(conf, CONF_protocol); - setup_config_box(dp->ctrlbox, midsession, protocol, protcfginfo); - unix_setup_config_box(dp->ctrlbox, midsession, protocol); - gtk_setup_config_box(dp->ctrlbox, midsession, window); - - gtk_window_set_title(GTK_WINDOW(window), title); - hbox = gtk_hbox_new(false, 4); - our_dialog_add_to_content_area(GTK_WINDOW(window), 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); - g_signal_connect(G_OBJECT(tree), "focus", G_CALLBACK(tree_focus), dp); -#endif - g_signal_connect(G_OBJECT(tree), "focus_in_event", - G_CALLBACK(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 < dp->ctrlbox->nctrlsets; index++) { - struct controlset *s = dp->ctrlbox->ctrlsets[index]; - GtkWidget *w; - - if (!*s->pathname) { - w = layout_ctrls(dp, NULL, &scs, s, GTK_WINDOW(window)); - - our_dialog_set_action_area(GTK_WINDOW(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 - bool 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); - - struct selparam *sp = snew(struct selparam); - - if (first) { - gint page_num; - - page_num = gtk_notebook_page_num(GTK_NOTEBOOK(panels), - panelvbox); - gtk_notebook_set_current_page(GTK_NOTEBOOK(panels), - page_num); - - dp->curr_panel = sp; - } - - sgrowarray(selparams, selparamsize, nselparams); - selparams[nselparams] = sp; - sp->dp = dp; - sp->panels = GTK_NOTEBOOK(panels); - sp->panel = panelvbox; - sp->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; - - sp->depth = j; - if (j > 0) { - sp->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), - sp->treepath, false); - } else { - sp->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; - - g_signal_connect(G_OBJECT(treeitem), "key_press_event", - G_CALLBACK(tree_key_press), dp); - g_signal_connect(G_OBJECT(treeitem), "focus_in_event", - G_CALLBACK(widget_focus), dp); - - gtk_widget_show(treeitem); - - if (first) - gtk_tree_select_child(GTK_TREE(tree), treeitem); - sp->treeitem = treeitem; -#endif - - level = j+1; - nselparams++; - } - - w = layout_ctrls(dp, selparams[nselparams-1], - &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) - /* - * 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, and then when the - * collapsed branches are expanded later, the tree view will - * jarringly change size. - * - * So instead we start with everything expanded; then, once the - * tree view has computed its resulting width requirement, we - * collapse the relevant rows, but force the width to be the value - * we just retrieved. This arranges that the tree view is wide - * enough to have all branches expanded without further resizing. - */ - - dp->nselparams = nselparams; - dp->selparams = selparams; - -#if !GTK_CHECK_VERSION(3,0,0) - { - /* - * In GTK2, we can just do the job right now. - */ - GtkRequisition req; - gtk_widget_size_request(tree, &req); - initial_treeview_collapse(dp, tree); - gtk_widget_set_size_request(tree, req.width, -1); - } -#else - /* - * But in GTK3, we have to wait until the widget is about to be - * mapped, because the size computation won't have been done yet. - */ - g_signal_connect(G_OBJECT(tree), "map", - G_CALLBACK(treeview_map_event), dp); -#endif /* GTK 2 vs 3 */ -#endif /* GTK 2+ vs 1 */ - -#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++) { - g_signal_connect(G_OBJECT(selparams[index]->treeitem), "select", - G_CALLBACK(treeitem_sel), - selparams[index]); - dp->treeitems[index] = selparams[index]->treeitem; - } -#endif - - dp->data = conf; - 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 = -1; - dp->window = window; - - 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 < dp->ctrlbox->nctrlsets; index++) { - struct controlset *s = dp->ctrlbox->ctrlsets[index]; - bool done = false; - 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 = true; - break; - } - } - if (done) - break; - } - - g_signal_connect(G_OBJECT(window), "destroy", - G_CALLBACK(dlgparam_destroy), dp); - g_signal_connect(G_OBJECT(window), "key_press_event", - G_CALLBACK(win_key_press), dp); - - return window; -} - -static void dlgparam_destroy(GtkWidget *widget, gpointer data) -{ - struct dlgparam *dp = (struct dlgparam *)data; - dp->after(dp->afterctx, dp->retval); - dlg_cleanup(dp); - ctrl_free_box(dp->ctrlbox); -#if GTK_CHECK_VERSION(2,0,0) - if (dp->selparams) { - for (size_t i = 0; i < dp->nselparams; i++) { - if (dp->selparams[i]->treepath) - gtk_tree_path_free(dp->selparams[i]->treepath); - sfree(dp->selparams[i]); - } - sfree(dp->selparams); - } -#endif - sfree(dp); -} - -static void messagebox_handler(union control *ctrl, dlgparam *dp, - void *data, int event) -{ - if (event == EVENT_ACTION) - dlg_end(dp, ctrl->generic.context.i); -} - -static const struct message_box_button button_array_yn[] = { - {"Yes", 'y', +1, 1}, - {"No", 'n', -1, 0}, -}; -const struct message_box_buttons buttons_yn = { - button_array_yn, lenof(button_array_yn), -}; -static const struct message_box_button button_array_ok[] = { - {"OK", 'o', 1, 1}, -}; -const struct message_box_buttons buttons_ok = { - button_array_ok, lenof(button_array_ok), -}; - -static GtkWidget *create_message_box_general( - GtkWidget *parentwin, const char *title, const char *msg, int minwid, - bool selectable, const struct message_box_buttons *buttons, - post_dialog_fn_t after, void *afterctx, - GtkWidget *(*action_postproc)(GtkWidget *, void *), void *postproc_ctx) -{ - GtkWidget *window, *w0, *w1; - struct controlset *s0, *s1; - union control *c, *textctrl; - struct dlgparam *dp; - struct Shortcuts scs; - int i, index, ncols, min_type; - - dp = snew(struct dlgparam); - dp->after = after; - dp->afterctx = afterctx; - - dlg_init(dp); - - for (index = 0; index < lenof(scs.sc); index++) { - scs.sc[index].action = SHORTCUT_EMPTY; - } - - dp->ctrlbox = ctrl_new_box(); - - /* - * Count up the number of buttons and find out what kinds there - * are. - */ - ncols = 0; - min_type = +1; - for (i = 0; i < buttons->nbuttons; i++) { - const struct message_box_button *button = &buttons->buttons[i]; - ncols++; - if (min_type > button->type) - min_type = button->type; - assert(button->value >= 0); /* <0 means no return value available */ - } - - s0 = ctrl_getset(dp->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; - index = 0; - for (i = 0; i < buttons->nbuttons; i++) { - const struct message_box_button *button = &buttons->buttons[i]; - c = ctrl_pushbutton(s0, button->title, button->shortcut, - HELPCTX(no_help), messagebox_handler, - I(button->value)); - c->generic.column = index++; - if (button->type > 0) - c->button.isdefault = true; - - /* We always arrange that _some_ button is labelled as - * 'iscancel', so that pressing Escape will always cause - * win_key_press to do something. The button we choose is - * whichever has the smallest type value: this means that real - * cancel buttons (labelled -1) will be picked if one is - * there, or in cases where the options are yes/no (1,0) then - * no will be picked, and if there's only one option (a box - * that really is just showing a _message_ and not even asking - * a question) then that will be picked. */ - if (button->type == min_type) - c->button.iscancel = true; - } - - s1 = ctrl_getset(dp->ctrlbox, "x", "", ""); - textctrl = ctrl_text(s1, msg, HELPCTX(no_help)); - - window = our_dialog_new(); - gtk_window_set_title(GTK_WINDOW(window), title); - w0 = layout_ctrls(dp, NULL, &scs, s0, GTK_WINDOW(window)); - if (action_postproc) - w0 = action_postproc(w0, postproc_ctx); - our_dialog_set_action_area(GTK_WINDOW(window), w0); - gtk_widget_show(w0); - w1 = layout_ctrls(dp, NULL, &scs, s1, GTK_WINDOW(window)); - gtk_container_set_border_width(GTK_CONTAINER(w1), 10); - gtk_widget_set_size_request(w1, minwid+20, -1); - our_dialog_add_to_content_area(GTK_WINDOW(window), w1, true, true, 0); - gtk_widget_show(w1); - - dp->shortcuts = &scs; - dp->lastfocus = NULL; - dp->retval = 0; - dp->window = window; - - if (selectable) { -#if GTK_CHECK_VERSION(2,0,0) - struct uctrl *uc = dlg_find_byctrl(dp, textctrl); - gtk_label_set_selectable(GTK_LABEL(uc->text), true); - - /* - * GTK selectable labels have a habit of selecting their - * entire contents when they gain focus. It's ugly to have - * text in a message box start up all selected, so we suppress - * this by manually selecting none of it - but we must do this - * when the widget _already has_ focus, otherwise our work - * will be undone when it gains it shortly. - */ - gtk_widget_grab_focus(uc->text); - gtk_label_select_region(GTK_LABEL(uc->text), 0, 0); -#else - (void)textctrl; /* placate warning */ -#endif - } - - 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_container_set_focus_child(GTK_CONTAINER(window), NULL); - gtk_widget_show(window); - gtk_window_set_focus(GTK_WINDOW(window), NULL); - -#if !GTK_CHECK_VERSION(2,0,0) - dp->currtreeitem = NULL; - dp->treeitems = NULL; -#else - dp->selparams = NULL; -#endif - - g_signal_connect(G_OBJECT(window), "destroy", - G_CALLBACK(dlgparam_destroy), dp); - g_signal_connect(G_OBJECT(window), "key_press_event", - G_CALLBACK(win_key_press), dp); - - return window; -} - -GtkWidget *create_message_box( - GtkWidget *parentwin, const char *title, const char *msg, int minwid, - bool selectable, const struct message_box_buttons *buttons, - post_dialog_fn_t after, void *afterctx) -{ - return create_message_box_general( - parentwin, title, msg, minwid, selectable, buttons, after, afterctx, - NULL /* action_postproc */, NULL /* postproc_ctx */); -} - -struct verify_ssh_host_key_dialog_ctx { - char *host; - int port; - char *keytype; - char *keystr; - char *more_info; - void (*callback)(void *callback_ctx, int result); - void *callback_ctx; - Seat *seat; - - GtkWidget *main_dialog; - GtkWidget *more_info_dialog; -}; - -static void verify_ssh_host_key_result_callback(void *vctx, int result) -{ - struct verify_ssh_host_key_dialog_ctx *ctx = - (struct verify_ssh_host_key_dialog_ctx *)vctx; - - if (result >= 0) { - int logical_result; - - /* - * Convert the dialog-box return value (one of three - * possibilities) into the return value we pass back to the SSH - * code (one of only two possibilities, because the SSH code - * doesn't care whether we saved the host key or not). - */ - if (result == 2) { - store_host_key(ctx->host, ctx->port, ctx->keytype, ctx->keystr); - logical_result = 1; /* continue with connection */ - } else if (result == 1) { - logical_result = 1; /* continue with connection */ - } else { - logical_result = 0; /* do not continue with connection */ - } - - ctx->callback(ctx->callback_ctx, logical_result); - } - - /* - * Clean up this context structure, whether or not a result was - * ever actually delivered from the dialog box. - */ - unregister_dialog(ctx->seat, DIALOG_SLOT_NETWORK_PROMPT); - - if (ctx->more_info_dialog) - gtk_widget_destroy(ctx->more_info_dialog); - - sfree(ctx->host); - sfree(ctx->keytype); - sfree(ctx->keystr); - sfree(ctx->more_info); - sfree(ctx); -} - -static GtkWidget *add_more_info_button(GtkWidget *w, void *vctx) -{ - GtkWidget *box = gtk_hbox_new(false, 10); - gtk_widget_show(box); - gtk_box_pack_end(GTK_BOX(box), w, false, true, 0); - GtkWidget *button = gtk_button_new_with_label("More info..."); - gtk_widget_show(button); - gtk_box_pack_start(GTK_BOX(box), button, false, true, 0); - *(GtkWidget **)vctx = button; - return box; -} - -static void more_info_closed(void *vctx, int result) -{ - struct verify_ssh_host_key_dialog_ctx *ctx = - (struct verify_ssh_host_key_dialog_ctx *)vctx; - - ctx->more_info_dialog = NULL; -} - -static void more_info_button_clicked(GtkButton *button, gpointer vctx) -{ - struct verify_ssh_host_key_dialog_ctx *ctx = - (struct verify_ssh_host_key_dialog_ctx *)vctx; - - if (ctx->more_info_dialog) - return; - - ctx->more_info_dialog = create_message_box( - ctx->main_dialog, "Host key information", ctx->more_info, - string_width("SHA256 fingerprint: ecdsa-sha2-nistp521 521 " - "abcdefghkmnopqrsuvwxyzABCDEFGHJKLMNOPQRSTUW"), true, - &buttons_ok, more_info_closed, ctx); -} - -int gtk_seat_verify_ssh_host_key( - Seat *seat, const char *host, int port, const char *keytype, - char *keystr, const char *keydisp, char **fingerprints, - 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."; - static const struct message_box_button button_array_hostkey[] = { - {"Accept", 'a', 0, 2}, - {"Connect Once", 'o', 0, 1}, - {"Cancel", 'c', -1, 0}, - }; - static const struct message_box_buttons buttons_hostkey = { - button_array_hostkey, lenof(button_array_hostkey), - }; - - char *text; - int ret; - struct verify_ssh_host_key_dialog_ctx *result_ctx; - GtkWidget *mainwin, *msgbox; - - /* - * Verify the key. - */ - ret = verify_host_key(host, port, keytype, keystr); - - if (ret == 0) /* success - key matched OK */ - return 1; - - FingerprintType fptype_default = - ssh2_pick_default_fingerprint(fingerprints); - - text = dupprintf((ret == 2 ? wrongtxt : absenttxt), keytype, - fingerprints[fptype_default]); - - result_ctx = snew(struct verify_ssh_host_key_dialog_ctx); - result_ctx->callback = callback; - result_ctx->callback_ctx = ctx; - result_ctx->host = dupstr(host); - result_ctx->port = port; - result_ctx->keytype = dupstr(keytype); - result_ctx->keystr = dupstr(keystr); - result_ctx->seat = seat; - - mainwin = GTK_WIDGET(gtk_seat_get_window(seat)); - GtkWidget *more_info_button = NULL; - msgbox = create_message_box_general( - mainwin, "PuTTY Security Alert", text, - string_width(fingerprints[fptype_default]), true, - &buttons_hostkey, verify_ssh_host_key_result_callback, result_ctx, - add_more_info_button, &more_info_button); - - result_ctx->main_dialog = msgbox; - result_ctx->more_info_dialog = NULL; - - strbuf *sb = strbuf_new(); - if (fingerprints[SSH_FPTYPE_SHA256]) - strbuf_catf(sb, "SHA256 fingerprint: %s\n", - fingerprints[SSH_FPTYPE_SHA256]); - if (fingerprints[SSH_FPTYPE_MD5]) - strbuf_catf(sb, "MD5 fingerprint: %s\n", - fingerprints[SSH_FPTYPE_MD5]); - strbuf_catf(sb, "Full text of host's public key:"); - /* We have to manually wrap the public key, or else the GtkLabel - * will resize itself to accommodate the longest word, which will - * lead to a hilariously wide message box. */ - for (const char *p = keydisp, *q = p + strlen(p); p < q ;) { - size_t linelen = q-p; - if (linelen > 72) - linelen = 72; - put_byte(sb, '\n'); - put_data(sb, p, linelen); - p += linelen; - } - result_ctx->more_info = strbuf_to_str(sb); - - g_signal_connect(G_OBJECT(more_info_button), "clicked", - G_CALLBACK(more_info_button_clicked), result_ctx); - - register_dialog(seat, DIALOG_SLOT_NETWORK_PROMPT, msgbox); - - sfree(text); - - return -1; /* dialog still in progress */ -} - -struct simple_prompt_result_ctx { - void (*callback)(void *callback_ctx, int result); - void *callback_ctx; - Seat *seat; - enum DialogSlot dialog_slot; -}; - -static void simple_prompt_result_callback(void *vctx, int result) -{ - struct simple_prompt_result_ctx *ctx = - (struct simple_prompt_result_ctx *)vctx; - - unregister_dialog(ctx->seat, ctx->dialog_slot); - - if (result >= 0) - ctx->callback(ctx->callback_ctx, result); - - /* - * Clean up this context structure, whether or not a result was - * ever actually delivered from the dialog box. - */ - sfree(ctx); -} - -/* - * Ask whether the selected algorithm is acceptable (since it was - * below the configured 'warn' threshold). - */ -int gtk_seat_confirm_weak_crypto_primitive( - Seat *seat, 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; - struct simple_prompt_result_ctx *result_ctx; - GtkWidget *mainwin, *msgbox; - - text = dupprintf(msg, algtype, algname); - - result_ctx = snew(struct simple_prompt_result_ctx); - result_ctx->callback = callback; - result_ctx->callback_ctx = ctx; - result_ctx->seat = seat; - result_ctx->dialog_slot = DIALOG_SLOT_NETWORK_PROMPT; - - mainwin = GTK_WIDGET(gtk_seat_get_window(seat)); - msgbox = create_message_box( - mainwin, "PuTTY Security Alert", text, - string_width("Reasonably long line of text as a width template"), - false, &buttons_yn, simple_prompt_result_callback, result_ctx); - register_dialog(seat, result_ctx->dialog_slot, msgbox); - - sfree(text); - - return -1; /* dialog still in progress */ -} - -int gtk_seat_confirm_weak_cached_hostkey( - Seat *seat, const char *algname, const char *betteralgs, - void (*callback)(void *ctx, int result), void *ctx) -{ - static const char msg[] = - "The first host key type we have stored for this server\n" - "is %s, which is below the configured warning threshold.\n" - "The server also provides the following types of host key\n" - "above the threshold, which we do not have stored:\n" - "%s\n" - "Continue with connection?"; - - char *text; - struct simple_prompt_result_ctx *result_ctx; - GtkWidget *mainwin, *msgbox; - - text = dupprintf(msg, algname, betteralgs); - - result_ctx = snew(struct simple_prompt_result_ctx); - result_ctx->callback = callback; - result_ctx->callback_ctx = ctx; - result_ctx->seat = seat; - result_ctx->dialog_slot = DIALOG_SLOT_NETWORK_PROMPT; - - mainwin = GTK_WIDGET(gtk_seat_get_window(seat)); - msgbox = create_message_box( - mainwin, "PuTTY Security Alert", text, - string_width("is ecdsa-nistp521, which is below the configured" - " warning threshold."), - false, &buttons_yn, simple_prompt_result_callback, result_ctx); - register_dialog(seat, result_ctx->dialog_slot, msgbox); - - sfree(text); - - return -1; /* dialog still in progress */ -} - -void old_keyfile_warning(void) -{ - /* - * This should never happen on Unix. We hope. - */ -} - -void nonfatal_message_box(void *window, const char *msg) -{ - char *title = dupcat(appname, " Error"); - create_message_box( - window, title, msg, - string_width("REASONABLY LONG LINE OF TEXT FOR BASIC SANITY"), - false, &buttons_ok, trivial_post_dialog_fn, NULL); - sfree(title); -} - -void nonfatal(const char *p, ...) -{ - va_list ap; - char *msg; - va_start(ap, p); - msg = dupvprintf(p, ap); - va_end(ap); - nonfatal_message_box(NULL, msg); - sfree(msg); -} - -static GtkWidget *aboutbox = NULL; - -static void about_window_destroyed(GtkWidget *widget, gpointer data) -{ - aboutbox = NULL; -} - -static void about_close_clicked(GtkButton *button, gpointer data) -{ - gtk_widget_destroy(aboutbox); - aboutbox = NULL; -} - -static void about_key_press(GtkWidget *widget, GdkEventKey *event, - gpointer data) -{ - if (event->keyval == GDK_KEY_Escape && aboutbox) { - gtk_widget_destroy(aboutbox); - aboutbox = NULL; - } -} - -static void licence_clicked(GtkButton *button, gpointer data) -{ - char *title; - - title = dupcat(appname, " Licence"); - assert(aboutbox != NULL); - create_message_box(aboutbox, title, LICENCE_TEXT("\n\n"), - string_width("LONGISH LINE OF TEXT SO THE LICENCE" - " BOX ISN'T EXCESSIVELY TALL AND THIN"), - true, &buttons_ok, trivial_post_dialog_fn, NULL); - sfree(title); -} - -void about_box(void *window) -{ - GtkWidget *w; - GtkBox *action_area; - char *title; - - if (aboutbox) { - gtk_widget_grab_focus(aboutbox); - return; - } - - aboutbox = our_dialog_new(); - gtk_container_set_border_width(GTK_CONTAINER(aboutbox), 10); - title = dupcat("About ", appname); - gtk_window_set_title(GTK_WINDOW(aboutbox), title); - sfree(title); - - g_signal_connect(G_OBJECT(aboutbox), "destroy", - G_CALLBACK(about_window_destroyed), NULL); - - w = gtk_button_new_with_label("Close"); - gtk_widget_set_can_default(w, true); - gtk_window_set_default(GTK_WINDOW(aboutbox), w); - action_area = our_dialog_make_action_hbox(GTK_WINDOW(aboutbox)); - gtk_box_pack_end(action_area, w, false, false, 0); - g_signal_connect(G_OBJECT(w), "clicked", - G_CALLBACK(about_close_clicked), NULL); - gtk_widget_show(w); - - w = gtk_button_new_with_label("View Licence"); - gtk_widget_set_can_default(w, true); - gtk_box_pack_end(action_area, w, false, false, 0); - g_signal_connect(G_OBJECT(w), "clicked", - G_CALLBACK(licence_clicked), NULL); - gtk_widget_show(w); - - { - char *buildinfo_text = buildinfo("\n"); - char *label_text = dupprintf - ("%s\n\n%s\n\n%s\n\n%s", - appname, ver, buildinfo_text, - "Copyright " SHORT_COPYRIGHT_DETAILS ". All rights reserved"); - w = gtk_label_new(label_text); - gtk_label_set_justify(GTK_LABEL(w), GTK_JUSTIFY_CENTER); -#if GTK_CHECK_VERSION(2,0,0) - gtk_label_set_selectable(GTK_LABEL(w), true); -#endif - sfree(label_text); - } - our_dialog_add_to_content_area(GTK_WINDOW(aboutbox), w, false, false, 0); -#if GTK_CHECK_VERSION(2,0,0) - /* - * Same precautions against initial select-all as in - * create_message_box(). - */ - gtk_widget_grab_focus(w); - gtk_label_select_region(GTK_LABEL(w), 0, 0); -#endif - gtk_widget_show(w); - - g_signal_connect(G_OBJECT(aboutbox), "key_press_event", - G_CALLBACK(about_key_press), NULL); - - set_transient_window_pos(GTK_WIDGET(window), aboutbox); - if (window) - gtk_window_set_transient_for(GTK_WINDOW(aboutbox), - GTK_WINDOW(window)); - gtk_container_set_focus_child(GTK_CONTAINER(aboutbox), NULL); - gtk_widget_show(aboutbox); - gtk_window_set_focus(GTK_WINDOW(aboutbox), NULL); -} - -#define LOGEVENT_INITIAL_MAX 128 -#define LOGEVENT_CIRCULAR_MAX 128 - -struct eventlog_stuff { - GtkWidget *parentwin, *window; - struct controlbox *eventbox; - struct Shortcuts scs; - struct dlgparam dp; - union control *listctrl; - char **events_initial; - char **events_circular; - int ninitial, ncircular, circular_first; - strbuf *seldata; - int sellen; - bool ignore_selchange; -}; - -static void eventlog_destroy(GtkWidget *widget, gpointer data) -{ - eventlog_stuff *es = (eventlog_stuff *)data; - - es->window = NULL; - dlg_cleanup(&es->dp); - ctrl_free_box(es->eventbox); -} -static void eventlog_ok_handler(union control *ctrl, dlgparam *dp, - void *data, int event) -{ - if (event == EVENT_ACTION) - dlg_end(dp, 0); -} -static void eventlog_list_handler(union control *ctrl, dlgparam *dp, - void *data, int event) -{ - eventlog_stuff *es = (eventlog_stuff *)data; - - if (event == EVENT_REFRESH) { - int i; - - dlg_update_start(ctrl, dp); - dlg_listbox_clear(ctrl, dp); - for (i = 0; i < es->ninitial; i++) { - dlg_listbox_add(ctrl, dp, es->events_initial[i]); - } - for (i = 0; i < es->ncircular; i++) { - dlg_listbox_add(ctrl, dp, es->events_circular[(es->circular_first + i) % LOGEVENT_CIRCULAR_MAX]); - } - dlg_update_done(ctrl, dp); - } else if (event == EVENT_SELCHANGE) { - int i; - - /* - * 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. - */ - strbuf_clear(es->seldata); - for (i = 0; i < es->ninitial; i++) { - if (dlg_listbox_issel(ctrl, dp, i)) - strbuf_catf(es->seldata, "%s\n", es->events_initial[i]); - } - for (i = 0; i < es->ncircular; i++) { - if (dlg_listbox_issel(ctrl, dp, es->ninitial + i)) { - int j = (es->circular_first + i) % LOGEVENT_CIRCULAR_MAX; - strbuf_catf(es->seldata, "%s\n", es->events_circular[j]); - } - } - - if (gtk_selection_owner_set(es->window, GDK_SELECTION_PRIMARY, - GDK_CURRENT_TIME)) { - 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) -{ - eventlog_stuff *es = (eventlog_stuff *)data; - - gtk_selection_data_set(seldata, gtk_selection_data_get_target(seldata), 8, - es->seldata->u, es->seldata->len); -} - -gint eventlog_selection_clear(GtkWidget *widget, GdkEventSelection *seldata, - gpointer data) -{ - eventlog_stuff *es = (eventlog_stuff *)data; - struct uctrl *uc; - - /* - * Deselect everything in the list box. - */ - uc = dlg_find_byctrl(&es->dp, es->listctrl); - es->ignore_selchange = true; -#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 = false; - - return true; -} - -void showeventlog(eventlog_stuff *es, void *parentwin) -{ - 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 = our_dialog_new(); - title = dupcat(appname, " Event Log"); - gtk_window_set_title(GTK_WINDOW(window), title); - sfree(title); - w0 = layout_ctrls(&es->dp, NULL, &es->scs, s0, GTK_WINDOW(window)); - our_dialog_set_action_area(GTK_WINDOW(window), w0); - gtk_widget_show(w0); - w1 = layout_ctrls(&es->dp, NULL, &es->scs, s1, GTK_WINDOW(window)); - gtk_container_set_border_width(GTK_CONTAINER(w1), 10); - gtk_widget_set_size_request(w1, 20 + string_width - ("LINE OF TEXT GIVING WIDTH OF EVENT LOG IS " - "QUITE LONG 'COS SSH LOG ENTRIES ARE WIDE"), - -1); - our_dialog_add_to_content_area(GTK_WINDOW(window), 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); - - g_signal_connect(G_OBJECT(window), "destroy", - G_CALLBACK(eventlog_destroy), es); - g_signal_connect(G_OBJECT(window), "key_press_event", - G_CALLBACK(win_key_press), &es->dp); - g_signal_connect(G_OBJECT(window), "selection_get", - G_CALLBACK(eventlog_selection_get), es); - g_signal_connect(G_OBJECT(window), "selection_clear_event", - G_CALLBACK(eventlog_selection_clear), es); -} - -eventlog_stuff *eventlogstuff_new(void) -{ - eventlog_stuff *es = snew(eventlog_stuff); - memset(es, 0, sizeof(*es)); - es->seldata = strbuf_new(); - return es; -} - -void eventlogstuff_free(eventlog_stuff *es) -{ - int i; - - if (es->events_initial) { - for (i = 0; i < LOGEVENT_INITIAL_MAX; i++) - sfree(es->events_initial[i]); - sfree(es->events_initial); - } - if (es->events_circular) { - for (i = 0; i < LOGEVENT_CIRCULAR_MAX; i++) - sfree(es->events_circular[i]); - sfree(es->events_circular); - } - strbuf_free(es->seldata); - - sfree(es); -} - -void logevent_dlg(eventlog_stuff *es, const char *string) -{ - char timebuf[40]; - struct tm tm; - char **location; - size_t i; - - if (es->ninitial == 0) { - es->events_initial = sresize(es->events_initial, LOGEVENT_INITIAL_MAX, char *); - for (i = 0; i < LOGEVENT_INITIAL_MAX; i++) - es->events_initial[i] = NULL; - es->events_circular = sresize(es->events_circular, LOGEVENT_CIRCULAR_MAX, char *); - for (i = 0; i < LOGEVENT_CIRCULAR_MAX; i++) - es->events_circular[i] = NULL; - } - - if (es->ninitial < LOGEVENT_INITIAL_MAX) - location = &es->events_initial[es->ninitial]; - else - location = &es->events_circular[(es->circular_first + es->ncircular) % LOGEVENT_CIRCULAR_MAX]; - - tm=ltime(); - strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S\t", &tm); - - sfree(*location); - *location = dupcat(timebuf, string); - if (es->window) { - dlg_listbox_add(es->listctrl, &es->dp, *location); - } - if (es->ninitial < LOGEVENT_INITIAL_MAX) { - es->ninitial++; - } else if (es->ncircular < LOGEVENT_CIRCULAR_MAX) { - es->ncircular++; - } else if (es->ncircular == LOGEVENT_CIRCULAR_MAX) { - es->circular_first = (es->circular_first + 1) % LOGEVENT_CIRCULAR_MAX; - sfree(es->events_circular[es->circular_first]); - es->events_circular[es->circular_first] = dupstr(".."); - } -} - -int gtkdlg_askappend(Seat *seat, 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."; - static const struct message_box_button button_array_append[] = { - {"Overwrite", 'o', 1, 2}, - {"Append", 'a', 0, 1}, - {"Disable", 'd', -1, 0}, - }; - static const struct message_box_buttons buttons_append = { - button_array_append, lenof(button_array_append), - }; - - char *message; - char *mbtitle; - struct simple_prompt_result_ctx *result_ctx; - GtkWidget *mainwin, *msgbox; - - message = dupprintf(msgtemplate, FILENAME_MAX, filename->path); - mbtitle = dupprintf("%s Log to File", appname); - - result_ctx = snew(struct simple_prompt_result_ctx); - result_ctx->callback = callback; - result_ctx->callback_ctx = ctx; - result_ctx->seat = seat; - result_ctx->dialog_slot = DIALOG_SLOT_LOGFILE_PROMPT; - - mainwin = GTK_WIDGET(gtk_seat_get_window(seat)); - msgbox = create_message_box( - mainwin, mbtitle, message, - string_width("LINE OF TEXT SUITABLE FOR THE ASKAPPEND WIDTH"), - false, &buttons_append, simple_prompt_result_callback, result_ctx); - register_dialog(seat, result_ctx->dialog_slot, msgbox); - - sfree(message); - sfree(mbtitle); - - return -1; /* dialog still in progress */ -} diff --git a/unix/gtkfont.c b/unix/gtkfont.c deleted file mode 100644 index b910d14b..00000000 --- a/unix/gtkfont.c +++ /dev/null @@ -1,3808 +0,0 @@ -/* - * 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 -#include -#include - -#include -#if !GTK_CHECK_VERSION(3,0,0) -#include -#endif - -#define MAY_REFER_TO_GTK_IN_HEADERS - -#include "putty.h" -#include "gtkfont.h" -#include "gtkcompat.h" -#include "gtkmisc.h" -#include "tree234.h" - -#ifndef NOT_X_WINDOWS -#include -#include -#include -#include -#include "x11misc.h" -#endif - -/* - * 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. - */ - -#if !GLIB_CHECK_VERSION(1,3,7) -#define g_ascii_strcasecmp g_strcasecmp -#define g_ascii_strncasecmp g_strncasecmp -#endif - -/* - * Ad-hoc vtable mechanism to allow font structures to be - * polymorphic. - * - * Any instance of `unifont' used in the vtable functions will - * actually be an element of a larger structure containing data - * specific to the subtype. - */ - -#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 UnifontVtable *fontclass); - -struct UnifontVtable { - /* - * `Methods' of the `class'. - */ - unifont *(*create)(GtkWidget *widget, const char *name, bool wide, - bool bold, int shadowoffset, bool shadowalways); - unifont *(*create_fallback)(GtkWidget *widget, int height, bool wide, - bool bold, int shadowoffset, - bool shadowalways); - void (*destroy)(unifont *font); - bool (*has_glyph)(unifont *font, wchar_t glyph); - void (*draw_text)(unifont_drawctx *ctx, unifont *font, - int x, int y, const wchar_t *string, int len, - bool wide, bool bold, int cellwidth); - void (*draw_combining)(unifont_drawctx *ctx, unifont *font, - int x, int y, const wchar_t *string, int len, - bool wide, bool 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, bool resolve_aliases); - char *(*scale_fontname)(GtkWidget *widget, const char *name, int size); - char *(*size_increment)(unifont *font, int increment); - - /* - * `Static data members' of the `class'. - */ - const char *prefix; -}; - -#ifndef NOT_X_WINDOWS - -/* ---------------------------------------------------------------------- - * X11 font implementation, directly using Xlib calls. Conditioned out - * if X11 fonts aren't available at all (e.g. building with GTK3 for a - * back end other than X). - */ - -static bool x11font_has_glyph(unifont *font, wchar_t glyph); -static void x11font_draw_text(unifont_drawctx *ctx, unifont *font, - int x, int y, const wchar_t *string, int len, - bool wide, bool bold, int cellwidth); -static void x11font_draw_combining(unifont_drawctx *ctx, unifont *font, - int x, int y, const wchar_t *string, - int len, bool wide, bool bold, - int cellwidth); -static unifont *x11font_create(GtkWidget *widget, const char *name, - bool wide, bool bold, - int shadowoffset, bool 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, - bool resolve_aliases); -static char *x11font_scale_fontname(GtkWidget *widget, const char *name, - int size); -static char *x11font_size_increment(unifont *font, int increment); - -#ifdef DRAW_TEXT_CAIRO -struct cairo_cached_glyph { - cairo_surface_t *surface; - unsigned char *bitmap; -}; -#endif - -/* - * Structure storing a single physical XFontStruct, plus associated - * data. - */ -typedef struct x11font_individual { - /* The XFontStruct itself. */ - XFontStruct *xfs; - - /* - * The `allocated' flag indicates whether we've tried to fetch - * this subfont already (thus distinguishing xfs==NULL because we - * haven't tried yet from xfs==NULL because we tried and failed, - * so that we don't keep trying and failing subsequently). - */ - bool allocated; - -#ifdef DRAW_TEXT_CAIRO - /* - * A cache of glyph bitmaps downloaded from the X server when - * we're in Cairo rendering mode. If glyphcache itself is - * non-NULL, then entries in [0,nglyphs) are expected to be - * initialised to either NULL or a bitmap pointer. - */ - struct cairo_cached_glyph *glyphcache; - int nglyphs; - - /* - * X server paraphernalia for actually downloading the glyphs. - */ - Pixmap pixmap; - GC gc; - int pixwidth, pixheight, pixoriginx, pixoriginy; - - /* - * Paraphernalia for loading the resulting bitmaps into Cairo. - */ - int rowsize, allsize, indexflip; -#endif - -} x11font_individual; - -struct x11font { - /* - * Copy of the X display handle, so we don't have to keep - * extracting it from GDK. - */ - Display *disp; - /* - * Individual physical X fonts. We store a number of these, for - * automatically guessed bold and wide variants. - */ - x11font_individual fonts[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 XDrawString or XDrawString16, etc. - */ - bool 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. - */ - bool variable; - /* - * real_charset is the charset used when translating text into the - * font's internal encoding inside draw_text(). This need not be - * the same as the public_charset provided to the client; for - * example, public_charset might be CS_ISO8859_1 while - * real_charset is CS_ISO8859_1_X11. - */ - int real_charset; - /* - * Data passed in to unifont_create(). - */ - int shadowoffset; - bool wide, bold, shadowalways; - - unifont u; -}; - -static const UnifontVtable x11font_vtable = { - .create = x11font_create, - .create_fallback = NULL, /* no fallback fonts in X11 */ - .destroy = x11font_destroy, - .has_glyph = x11font_has_glyph, - .draw_text = x11font_draw_text, - .draw_combining = x11font_draw_combining, - .enum_fonts = x11font_enum_fonts, - .canonify_fontname = x11font_canonify_fontname, - .scale_fontname = x11font_scale_fontname, - .size_increment = x11font_size_increment, - .prefix = "server", -}; - -#define XLFD_STRING_PARTS_LIST(S,I) \ - S(foundry) \ - S(family_name) \ - S(weight_name) \ - S(slant) \ - S(setwidth_name) \ - S(add_style_name) \ - I(pixel_size) \ - I(point_size) \ - I(resolution_x) \ - I(resolution_y) \ - S(spacing) \ - I(average_width) \ - S(charset_registry) \ - S(charset_encoding) \ - /* end of list */ - -/* Special value for int fields that xlfd_recompose will render as "*" */ -#define XLFD_INT_WILDCARD INT_MIN - -struct xlfd_decomposed { -#define STR_FIELD(f) const char *f; -#define INT_FIELD(f) int f; - XLFD_STRING_PARTS_LIST(STR_FIELD, INT_FIELD) -#undef STR_FIELD -#undef INT_FIELD -}; - -static struct xlfd_decomposed *xlfd_decompose(const char *xlfd) -{ - char *p, *components[14]; - struct xlfd_decomposed *dec; - int i; - - if (!xlfd) - return NULL; - - dec = snew_plus(struct xlfd_decomposed, strlen(xlfd) + 1); - p = snew_plus_get_aux(dec); - strcpy(p, xlfd); - - for (i = 0; i < 14; i++) { - if (*p != '-') { - /* Malformed XLFD: not enough '-' */ - sfree(dec); - return NULL; - } - *p++ = '\0'; - components[i] = p; - p += strcspn(p, "-"); - } - if (*p) { - /* Malformed XLFD: too many '-' */ - sfree(dec); - return NULL; - } - - i = 0; -#define STORE_STR(f) dec->f = components[i++]; -#define STORE_INT(f) dec->f = atoi(components[i++]); - XLFD_STRING_PARTS_LIST(STORE_STR, STORE_INT) -#undef STORE_STR -#undef STORE_INT - - return dec; -} - -static char *xlfd_recompose(const struct xlfd_decomposed *dec) -{ -#define FMT_STR(f) "-%s" -#define ARG_STR(f) , dec->f -#define FMT_INT(f) "%s%.*d" -#define ARG_INT(f) \ - , dec->f == XLFD_INT_WILDCARD ? "-*" : "-" \ - , dec->f == XLFD_INT_WILDCARD ? 0 : 1 \ - , dec->f == XLFD_INT_WILDCARD ? 0 : dec->f - return dupprintf(XLFD_STRING_PARTS_LIST(FMT_STR, FMT_INT) - XLFD_STRING_PARTS_LIST(ARG_STR, ARG_INT)); -#undef FMT_STR -#undef ARG_STR -#undef FMT_INT -#undef ARG_INT -} - -static char *x11_guess_derived_font_name(Display *disp, XFontStruct *xfs, - bool bold, bool wide) -{ - Atom fontprop = XInternAtom(disp, "FONT", False); - unsigned long ret; - if (XGetFontProperty(xfs, fontprop, &ret)) { - char *name = XGetAtomName(disp, (Atom)ret); - struct xlfd_decomposed *xlfd = xlfd_decompose(name); - if (!xlfd) - return NULL; - - if (bold) - xlfd->weight_name = "bold"; - - if (wide) { - /* Width name obviously may have changed. */ - /* Additional style may now become e.g. `ja' or `ko'. */ - xlfd->setwidth_name = xlfd->add_style_name = "*"; - - /* Expect to double the average width. */ - xlfd->average_width *= 2; - } - - { - char *ret = xlfd_recompose(xlfd); - sfree(xlfd); - return ret; - } - } - return NULL; -} - -static int x11_font_width(XFontStruct *xfs, bool sixteen_bit) -{ - if (sixteen_bit) { - XChar2b space; - space.byte1 = 0; - space.byte2 = '0'; - return XTextWidth16(xfs, &space, 1); - } else { - return XTextWidth(xfs, "0", 1); - } -} - -static const XCharStruct *x11_char_struct( - XFontStruct *xfs, unsigned char byte1, unsigned char byte2) -{ - int index; - - /* - * The man page for XQueryFont is rather confusing about how the - * per_char array in the XFontStruct is laid out, because it gives - * formulae for determining the two-byte X character code _from_ - * an index into the per_char array. Going the other way, it's - * rather simpler: - * - * The valid character codes have byte1 between min_byte1 and - * max_byte1 inclusive, and byte2 between min_char_or_byte2 and - * max_char_or_byte2 inclusive. This gives a rectangle of size - * (max_byte2-min_byte1+1) by - * (max_char_or_byte2-min_char_or_byte2+1), which is precisely the - * rectangle encoded in the per_char array. Hence, given a - * character code which is valid in the sense that it falls - * somewhere in that rectangle, its index in per_char is given by - * setting - * - * x = byte2 - min_char_or_byte2 - * y = byte1 - min_byte1 - * index = y * (max_char_or_byte2-min_char_or_byte2+1) + x - * - * If min_byte1 and min_byte2 are both zero, that's a special case - * which can be treated as if min_byte2 was 1 instead, i.e. the - * per_char array just runs from min_char_or_byte2 to - * max_char_or_byte2 inclusive, and byte1 should always be zero. - */ - - if (byte2 < xfs->min_char_or_byte2 || byte2 > xfs->max_char_or_byte2) - return NULL; - - if (xfs->min_byte1 == 0 && xfs->max_byte1 == 0) { - index = byte2 - xfs->min_char_or_byte2; - } else { - if (byte1 < xfs->min_byte1 || byte1 > xfs->max_byte1) - return NULL; - index = ((byte2 - xfs->min_char_or_byte2) + - ((byte1 - xfs->min_byte1) * - (xfs->max_char_or_byte2 - xfs->min_char_or_byte2 + 1))); - } - - if (!xfs->per_char) /* per_char NULL => everything in range exists */ - return &xfs->max_bounds; - - return &xfs->per_char[index]; -} - -static bool x11_font_has_glyph( - XFontStruct *xfs, unsigned char byte1, unsigned char byte2) -{ - /* - * Not to be confused with x11font_has_glyph, which is a method of - * the x11font 'class' and hence takes a unifont as argument. This - * is the low-level function which grubs about in an actual - * XFontStruct to see if a given glyph exists. - * - * We must do this ourselves rather than letting Xlib's - * XTextExtents16 do the job, because XTextExtents will helpfully - * substitute the font's default_char for any missing glyph and - * not tell us it did so, which precisely won't help us find out - * which glyphs _are_ missing. - */ - const XCharStruct *xcs = x11_char_struct(xfs, byte1, byte2); - return xcs && (xcs->ascent + xcs->descent > 0 || xcs->width > 0); -} - -static unifont *x11font_create(GtkWidget *widget, const char *name, - bool wide, bool bold, - int shadowoffset, bool shadowalways) -{ - struct x11font *xfont; - XFontStruct *xfs; - Display *disp; - Atom charset_registry, charset_encoding, spacing; - unsigned long registry_ret, encoding_ret, spacing_ret; - int pubcs, realcs; - bool sixteen_bit, variable; - int i; - - if ((disp = get_x11_display()) == NULL) - return NULL; - - xfs = XLoadQueryFont(disp, name); - if (!xfs) - return NULL; - - 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); - 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 - * low character positions, it is assumed that those - * glyphs are the VT100 line-drawing character set. - */ - if (pubcs == CS_ISO8859_1) { - int ch; - for (ch = 1; ch < 32; ch++) - if (!x11_font_has_glyph(xfs, 0, ch)) - break; - if (ch == 32) - realcs = CS_ISO8859_1_X11; - } - - sfree(encoding); - } - } - - 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(xfs, sixteen_bit); - xfont->u.ascent = xfs->ascent; - xfont->u.descent = xfs->descent; - xfont->u.height = xfont->u.ascent + xfont->u.descent; - xfont->u.public_charset = pubcs; - xfont->u.want_fallback = true; - xfont->u.strikethrough_y = xfont->u.ascent - (xfont->u.ascent * 3 / 8); -#ifdef DRAW_TEXT_GDK - xfont->u.preferred_drawtype = DRAWTYPE_GDK; -#elif defined DRAW_TEXT_CAIRO - xfont->u.preferred_drawtype = DRAWTYPE_CAIRO; -#else -#error No drawtype available at all -#endif - xfont->disp = disp; - xfont->real_charset = realcs; - xfont->sixteen_bit = sixteen_bit; - xfont->variable = variable; - xfont->wide = wide; - xfont->bold = bold; - xfont->shadowoffset = shadowoffset; - xfont->shadowalways = shadowalways; - - for (i = 0; i < lenof(xfont->fonts); i++) { - xfont->fonts[i].xfs = NULL; - xfont->fonts[i].allocated = false; -#ifdef DRAW_TEXT_CAIRO - xfont->fonts[i].glyphcache = NULL; - xfont->fonts[i].nglyphs = 0; - xfont->fonts[i].pixmap = None; - xfont->fonts[i].gc = None; -#endif - } - xfont->fonts[0].xfs = xfs; - xfont->fonts[0].allocated = true; - - return &xfont->u; -} - -static void x11font_destroy(unifont *font) -{ - struct x11font *xfont = container_of(font, struct x11font, u); - Display *disp = xfont->disp; - int i; - - for (i = 0; i < lenof(xfont->fonts); i++) { - if (xfont->fonts[i].xfs) - XFreeFont(disp, xfont->fonts[i].xfs); -#ifdef DRAW_TEXT_CAIRO - if (xfont->fonts[i].gc != None) - XFreeGC(disp, xfont->fonts[i].gc); - if (xfont->fonts[i].pixmap != None) - XFreePixmap(disp, xfont->fonts[i].pixmap); - if (xfont->fonts[i].glyphcache) { - int j; - for (j = 0; j < xfont->fonts[i].nglyphs; j++) { - cairo_surface_destroy(xfont->fonts[i].glyphcache[j].surface); - sfree(xfont->fonts[i].glyphcache[j].bitmap); - } - sfree(xfont->fonts[i].glyphcache); - } -#endif - } - sfree(xfont); -} - -static void x11_alloc_subfont(struct x11font *xfont, int sfid) -{ - Display *disp = xfont->disp; - char *derived_name = x11_guess_derived_font_name - (disp, xfont->fonts[0].xfs, sfid & 1, !!(sfid & 2)); - xfont->fonts[sfid].xfs = XLoadQueryFont(disp, derived_name); - xfont->fonts[sfid].allocated = true; - sfree(derived_name); - /* Note that xfont->fonts[sfid].xfs may still be NULL, if XLQF failed. */ -} - -static bool x11font_has_glyph(unifont *font, wchar_t glyph) -{ - struct x11font *xfont = container_of(font, struct x11font, u); - - if (xfont->sixteen_bit) { - /* - * This X font has 16-bit character indices, which means - * we can directly use our Unicode input value. - */ - return x11_font_has_glyph(xfont->fonts[0].xfs, - glyph >> 8, glyph & 0xFF); - } else { - /* - * This X font has 8-bit indices, so we must convert to the - * appropriate character set. - */ - char sbstring[2]; - int sblen = wc_to_mb(xfont->real_charset, 0, &glyph, 1, - sbstring, 2, "", NULL); - if (sblen == 0 || !sbstring[0]) - return false; /* not even in the charset */ - - return x11_font_has_glyph(xfont->fonts[0].xfs, 0, - (unsigned char)sbstring[0]); - } -} - -#if !GTK_CHECK_VERSION(2,0,0) -#define GDK_DRAWABLE_XID(d) GDK_WINDOW_XWINDOW(d) /* GTK1's name for this */ -#elif GTK_CHECK_VERSION(3,0,0) -#define GDK_DRAWABLE_XID(d) GDK_WINDOW_XID(d) /* GTK3's name for this */ -#endif - -static int x11font_width_16(unifont_drawctx *ctx, x11font_individual *xfi, - const void *vstring, int start, int length) -{ - const XChar2b *string = (const XChar2b *)vstring; - return XTextWidth16(xfi->xfs, string+start, length); -} - -static int x11font_width_8(unifont_drawctx *ctx, x11font_individual *xfi, - const void *vstring, int start, int length) -{ - const char *string = (const char *)vstring; - return XTextWidth(xfi->xfs, string+start, length); -} - -#ifdef DRAW_TEXT_GDK -static void x11font_gdk_setup(unifont_drawctx *ctx, x11font_individual *xfi, - Display *disp) -{ - XSetFont(disp, GDK_GC_XGC(ctx->u.gdk.gc), xfi->xfs->fid); -} - -static void x11font_gdk_draw_16(unifont_drawctx *ctx, x11font_individual *xfi, - Display *disp, int x, int y, - const void *vstring, int start, int length) -{ - const XChar2b *string = (const XChar2b *)vstring; - XDrawString16(disp, GDK_DRAWABLE_XID(ctx->u.gdk.target), - GDK_GC_XGC(ctx->u.gdk.gc), x, y, string+start, length); -} - -static void x11font_gdk_draw_8(unifont_drawctx *ctx, x11font_individual *xfi, - Display *disp, int x, int y, - const void *vstring, int start, int length) -{ - const char *string = (const char *)vstring; - XDrawString(disp, GDK_DRAWABLE_XID(ctx->u.gdk.target), - GDK_GC_XGC(ctx->u.gdk.gc), x, y, string+start, length); -} -#endif - -#ifdef DRAW_TEXT_CAIRO -static void x11font_cairo_setup( - unifont_drawctx *ctx, x11font_individual *xfi, Display *disp) -{ - if (xfi->pixmap == None) { - XGCValues gcvals; - GdkWindow *widgetwin = gtk_widget_get_window(ctx->u.cairo.widget); - int widgetscr = GDK_SCREEN_XNUMBER(gdk_window_get_screen(widgetwin)); - - xfi->pixwidth = - xfi->xfs->max_bounds.rbearing - xfi->xfs->min_bounds.lbearing; - xfi->pixheight = - xfi->xfs->max_bounds.ascent + xfi->xfs->max_bounds.descent; - xfi->pixoriginx = -xfi->xfs->min_bounds.lbearing; - xfi->pixoriginy = xfi->xfs->max_bounds.ascent; - - xfi->rowsize = cairo_format_stride_for_width(CAIRO_FORMAT_A1, - xfi->pixwidth); - xfi->allsize = xfi->rowsize * xfi->pixheight; - - { - /* - * Test host endianness and use it to set xfi->indexflip, - * which is XORed into our left-shift counts in order to - * implement the CAIRO_FORMAT_A1 specification, in which - * each bitmap byte is oriented LSB-first on little-endian - * platforms and MSB-first on big-endian ones. - * - * This is the same technique Cairo itself uses to test - * endianness, so hopefully it'll work in any situation - * where Cairo is usable at all. - */ - static const int endianness_test = 1; - xfi->indexflip = (*((char *) &endianness_test) == 1) ? 0 : 7; - } - - xfi->pixmap = XCreatePixmap - (disp, - GDK_DRAWABLE_XID(gtk_widget_get_window(ctx->u.cairo.widget)), - xfi->pixwidth, xfi->pixheight, 1); - gcvals.foreground = WhitePixel(disp, widgetscr); - gcvals.background = BlackPixel(disp, widgetscr); - gcvals.font = xfi->xfs->fid; - xfi->gc = XCreateGC(disp, xfi->pixmap, - GCForeground | GCBackground | GCFont, &gcvals); - } -} - -static void x11font_cairo_cache_glyph( - Display *disp, x11font_individual *xfi, int glyphindex) -{ - XImage *image; - int x, y; - unsigned char *bitmap; - const XCharStruct *xcs = x11_char_struct(xfi->xfs, glyphindex >> 8, - glyphindex & 0xFF); - - bitmap = snewn(xfi->allsize, unsigned char); - memset(bitmap, 0, xfi->allsize); - - image = XGetImage(disp, xfi->pixmap, 0, 0, - xfi->pixwidth, xfi->pixheight, AllPlanes, XYPixmap); - for (y = xfi->pixoriginy - xcs->ascent; - y < xfi->pixoriginy + xcs->descent; y++) { - for (x = xfi->pixoriginx + xcs->lbearing; - x < xfi->pixoriginx + xcs->rbearing; x++) { - unsigned long pixel = XGetPixel(image, x, y); - if (pixel) { - int byteindex = y * xfi->rowsize + x/8; - int bitindex = (x & 7) ^ xfi->indexflip; - bitmap[byteindex] |= 1U << bitindex; - } - } - } - XDestroyImage(image); - - if (xfi->nglyphs <= glyphindex) { - /* Round up to the next multiple of 256 on the general - * principle that Unicode characters come in contiguous blocks - * often used together */ - int old_nglyphs = xfi->nglyphs; - xfi->nglyphs = (glyphindex + 0x100) & ~0xFF; - xfi->glyphcache = sresize(xfi->glyphcache, xfi->nglyphs, - struct cairo_cached_glyph); - - while (old_nglyphs < xfi->nglyphs) { - xfi->glyphcache[old_nglyphs].surface = NULL; - xfi->glyphcache[old_nglyphs].bitmap = NULL; - old_nglyphs++; - } - } - xfi->glyphcache[glyphindex].bitmap = bitmap; - xfi->glyphcache[glyphindex].surface = cairo_image_surface_create_for_data - (bitmap, CAIRO_FORMAT_A1, xfi->pixwidth, xfi->pixheight, xfi->rowsize); -} - -static void x11font_cairo_draw_glyph(unifont_drawctx *ctx, - x11font_individual *xfi, int x, int y, - int glyphindex) -{ - if (xfi->glyphcache[glyphindex].surface) { - cairo_mask_surface(ctx->u.cairo.cr, - xfi->glyphcache[glyphindex].surface, - x - xfi->pixoriginx, y - xfi->pixoriginy); - } -} - -static void x11font_cairo_draw_16( - unifont_drawctx *ctx, x11font_individual *xfi, Display *disp, - int x, int y, const void *vstring, int start, int length) -{ - const XChar2b *string = (const XChar2b *)vstring + start; - int i; - for (i = 0; i < length; i++) { - if (x11_font_has_glyph(xfi->xfs, string[i].byte1, string[i].byte2)) { - int glyphindex = (256 * (unsigned char)string[i].byte1 + - (unsigned char)string[i].byte2); - if (glyphindex >= xfi->nglyphs || - !xfi->glyphcache[glyphindex].surface) { - XDrawImageString16(disp, xfi->pixmap, xfi->gc, - xfi->pixoriginx, xfi->pixoriginy, - string+i, 1); - x11font_cairo_cache_glyph(disp, xfi, glyphindex); - } - x11font_cairo_draw_glyph(ctx, xfi, x, y, glyphindex); - x += XTextWidth16(xfi->xfs, string+i, 1); - } - } -} - -static void x11font_cairo_draw_8( - unifont_drawctx *ctx, x11font_individual *xfi, Display *disp, - int x, int y, const void *vstring, int start, int length) -{ - const char *string = (const char *)vstring + start; - int i; - for (i = 0; i < length; i++) { - if (x11_font_has_glyph(xfi->xfs, 0, string[i])) { - int glyphindex = (unsigned char)string[i]; - if (glyphindex >= xfi->nglyphs || - !xfi->glyphcache[glyphindex].surface) { - XDrawImageString(disp, xfi->pixmap, xfi->gc, - xfi->pixoriginx, xfi->pixoriginy, - string+i, 1); - x11font_cairo_cache_glyph(disp, xfi, glyphindex); - } - x11font_cairo_draw_glyph(ctx, xfi, x, y, glyphindex); - x += XTextWidth(xfi->xfs, string+i, 1); - } - } -} -#endif /* DRAW_TEXT_CAIRO */ - -struct x11font_drawfuncs { - int (*width)(unifont_drawctx *ctx, x11font_individual *xfi, - const void *vstring, int start, int length); - void (*setup)(unifont_drawctx *ctx, x11font_individual *xfi, - Display *disp); - void (*draw)(unifont_drawctx *ctx, x11font_individual *xfi, Display *disp, - int x, int y, const void *vstring, int start, int length); -}; - -/* - * This array has two entries per compiled-in drawtype; of each pair, - * the first is for an 8-bit font and the second for 16-bit. - */ -static const struct x11font_drawfuncs x11font_drawfuncs[2*DRAWTYPE_NTYPES] = { -#ifdef DRAW_TEXT_GDK - /* gdk, 8-bit */ - { - x11font_width_8, - x11font_gdk_setup, - x11font_gdk_draw_8, - }, - /* gdk, 16-bit */ - { - x11font_width_16, - x11font_gdk_setup, - x11font_gdk_draw_16, - }, -#endif -#ifdef DRAW_TEXT_CAIRO - /* cairo, 8-bit */ - { - x11font_width_8, - x11font_cairo_setup, - x11font_cairo_draw_8, - }, - /* [3] cairo, 16-bit */ - { - x11font_width_16, - x11font_cairo_setup, - x11font_cairo_draw_16, - }, -#endif -}; - -static void x11font_really_draw_text( - const struct x11font_drawfuncs *dfns, unifont_drawctx *ctx, - x11font_individual *xfi, Display *disp, - int x, int y, const void *string, int nchars, - int shadowoffset, bool fontvariable, int cellwidth) -{ - int start = 0, step, nsteps; - bool centre; - - if (fontvariable) { - /* - * In a variable-pitch font, we draw one character at a - * time, and centre it in the character cell. - */ - step = 1; - nsteps = nchars; - centre = true; - } else { - /* - * In a fixed-pitch font, we can draw the whole lot in one go. - */ - step = nchars; - nsteps = 1; - centre = false; - } - - dfns->setup(ctx, xfi, disp); - - while (nsteps-- > 0) { - int X = x; - if (centre) - X += (cellwidth - dfns->width(ctx, xfi, string, start, step)) / 2; - - dfns->draw(ctx, xfi, disp, X, y, string, start, step); - if (shadowoffset) - dfns->draw(ctx, xfi, disp, X + shadowoffset, y, - string, start, step); - - x += cellwidth; - start += step; - } -} - -static void x11font_draw_text(unifont_drawctx *ctx, unifont *font, - int x, int y, const wchar_t *string, int len, - bool wide, bool bold, int cellwidth) -{ - struct x11font *xfont = container_of(font, struct x11font, u); - int sfid; - int shadowoffset = 0; - int mult = (wide ? 2 : 1); - int index = 2 * (int)ctx->type; - - wide = wide && !xfont->wide; - bold = bold && !xfont->bold; - - /* - * Decide which subfont we're using, and whether we have to - * use shadow bold. - */ - if (xfont->shadowalways && bold) { - shadowoffset = xfont->shadowoffset; - bold = false; - } - sfid = 2 * wide + bold; - if (!xfont->fonts[sfid].allocated) - x11_alloc_subfont(xfont, sfid); - if (bold && !xfont->fonts[sfid].xfs) { - bold = false; - shadowoffset = xfont->shadowoffset; - sfid = 2 * wide + bold; - if (!xfont->fonts[sfid].allocated) - x11_alloc_subfont(xfont, sfid); - } - - if (!xfont->fonts[sfid].xfs) - return; /* we've tried our best, but no luck */ - - if (xfont->sixteen_bit) { - /* - * This X font has 16-bit character indices, which means - * we can directly use our Unicode input string. - */ - XChar2b *xcs; - int i; - - xcs = snewn(len, XChar2b); - for (i = 0; i < len; i++) { - xcs[i].byte1 = string[i] >> 8; - xcs[i].byte2 = string[i]; - } - - x11font_really_draw_text(x11font_drawfuncs + index + 1, ctx, - &xfont->fonts[sfid], xfont->disp, x, y, - xcs, len, shadowoffset, - xfont->variable, cellwidth * mult); - sfree(xcs); - } else { - /* - * This X font has 8-bit indices, so we must convert to the - * appropriate character set. - */ - char *sbstring = snewn(len+1, char); - int sblen = wc_to_mb(xfont->real_charset, 0, string, len, - sbstring, len+1, ".", NULL); - x11font_really_draw_text(x11font_drawfuncs + index + 0, ctx, - &xfont->fonts[sfid], xfont->disp, x, y, - sbstring, sblen, shadowoffset, - xfont->variable, cellwidth * mult); - sfree(sbstring); - } -} - -static void x11font_draw_combining(unifont_drawctx *ctx, unifont *font, - int x, int y, const wchar_t *string, - int len, bool wide, bool bold, - int cellwidth) -{ - /* - * For server-side fonts, there's no sophisticated system for - * combining characters intelligently, so the best we can do is to - * overprint them on each other in the obvious way. - */ - int i; - for (i = 0; i < len; i++) - x11font_draw_text(ctx, font, x, y, string+i, 1, wide, bold, cellwidth); -} - -static void x11font_enum_fonts(GtkWidget *widget, - fontsel_add_entry callback, void *callback_ctx) -{ - Display *disp; - char **fontnames; - char *tmp = NULL; - int nnames, i, max, tmpsize; - - if ((disp = get_x11_display()) == NULL) - return; - - max = 32768; - while (1) { - fontnames = XListFonts(disp, "*", max, &nnames); - if (nnames >= max) { - XFreeFontNames(fontnames); - max *= 2; - } else - break; - } - - tmpsize = 0; - - for (i = 0; i < nnames; i++) { - struct xlfd_decomposed *xlfd = xlfd_decompose(fontnames[i]); - if (xlfd) { - char *p, *font, *style, *stylekey, *charset; - int weightkey, slantkey, setwidthkey; - int thistmpsize; - - /* - * Convert a dismembered XLFD into the format we'll be - * using in the font selector. - */ - - thistmpsize = 4 * strlen(fontnames[i]) + 256; - if (tmpsize < thistmpsize) { - tmpsize = thistmpsize; - tmp = sresize(tmp, tmpsize, char); - } - p = tmp; - - /* - * Font name is in the form "family (foundry)". (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)", xlfd->family_name, xlfd->foundry); - - /* - * Character set. - */ - charset = p; - p += 1 + sprintf(p, "%s-%s", xlfd->charset_registry, - xlfd->charset_encoding); - - /* - * Style is a mixture of quite a lot of the fields, - * with some strange formatting. - */ - style = p; - p += sprintf(p, "%s", xlfd->weight_name[0] ? xlfd->weight_name : - "regular"); - if (!g_ascii_strcasecmp(xlfd->slant, "i")) - p += sprintf(p, " italic"); - else if (!g_ascii_strcasecmp(xlfd->slant, "o")) - p += sprintf(p, " oblique"); - else if (!g_ascii_strcasecmp(xlfd->slant, "ri")) - p += sprintf(p, " reverse italic"); - else if (!g_ascii_strcasecmp(xlfd->slant, "ro")) - p += sprintf(p, " reverse oblique"); - else if (!g_ascii_strcasecmp(xlfd->slant, "ot")) - p += sprintf(p, " other-slant"); - if (xlfd->setwidth_name[0] && - g_ascii_strcasecmp(xlfd->setwidth_name, "normal")) - p += sprintf(p, " %s", xlfd->setwidth_name); - if (!g_ascii_strcasecmp(xlfd->spacing, "m")) - p += sprintf(p, " [M]"); - if (!g_ascii_strcasecmp(xlfd->spacing, "c")) - p += sprintf(p, " [C]"); - if (xlfd->add_style_name[0]) - p += sprintf(p, " %s", xlfd->add_style_name); - - /* - * 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_ascii_strcasecmp(xlfd->weight_name, "medium") || - !g_ascii_strcasecmp(xlfd->weight_name, "regular") || - !g_ascii_strcasecmp(xlfd->weight_name, "normal") || - !g_ascii_strcasecmp(xlfd->weight_name, "book")) - weightkey = 0; - else if (!g_ascii_strncasecmp(xlfd->weight_name, "demi", 4) || - !g_ascii_strncasecmp(xlfd->weight_name, "semi", 4)) - weightkey = 1; - else - weightkey = 2; - if (!g_ascii_strcasecmp(xlfd->slant, "r")) - slantkey = 0; - else if (!g_ascii_strncasecmp(xlfd->slant, "r", 1)) - slantkey = 2; - else - slantkey = 1; - if (!g_ascii_strcasecmp(xlfd->setwidth_name, "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(xlfd->weight_name), xlfd->weight_name, - slantkey, (int)strlen(xlfd->slant), xlfd->slant, - setwidthkey, - (int)strlen(xlfd->setwidth_name), xlfd->setwidth_name, - (int)strlen(xlfd->spacing), xlfd->spacing, - (int)strlen(xlfd->add_style_name), xlfd->add_style_name); - - assert(p - tmp < thistmpsize); - - /* - * Flags: we need to know whether this is a monospaced - * font, which we do by examining the spacing field - * again. - */ - int flags = FONTFLAG_SERVERSIDE; - if (!strchr("CcMm", xlfd->spacing[0])) - flags |= FONTFLAG_NONMONOSPACED; - - /* - * Some fonts have a pixel size of zero, meaning they're - * treated as scalable. For these purposes, we only want - * fonts whose pixel size we actually know, so filter - * those out. - */ - if (xlfd->pixel_size) - callback(callback_ctx, fontnames[i], font, charset, - style, stylekey, xlfd->pixel_size, flags, - &x11font_vtable); - - sfree(xlfd); - } 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); - - sfree(xlfd); - } - } - XFreeFontNames(fontnames); - sfree(tmp); -} - -static char *x11font_canonify_fontname(GtkWidget *widget, const char *name, - int *size, int *flags, - bool 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. - */ - XFontStruct *xfs; - Display *disp; - Atom fontprop, fontprop2; - unsigned long ret; - - if ((disp = get_x11_display()) == NULL) - return NULL; - - xfs = XLoadQueryFont(disp, name); - - if (!xfs) - return NULL; /* didn't make sense to us, sorry */ - - 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; - XFreeFont(disp, xfs); - if (flags) { - if (name[0] == '-' || resolve_aliases) - *flags = FONTFLAG_SERVERSIDE; - else - *flags = FONTFLAG_SERVERALIAS; - } - return dupstr(name[0] == '-' || resolve_aliases ? - newname : name); - } - } - } - - XFreeFont(disp, xfs); - - return NULL; /* something went wrong */ -} - -static char *x11font_scale_fontname(GtkWidget *widget, const char *name, - int size) -{ - return NULL; /* shan't */ -} - -static char *x11font_size_increment(unifont *font, int increment) -{ - struct x11font *xfont = container_of(font, struct x11font, u); - Display *disp = xfont->disp; - Atom fontprop = XInternAtom(disp, "FONT", False); - char *returned_name = NULL; - unsigned long ret; - if (XGetFontProperty(xfont->fonts[0].xfs, fontprop, &ret)) { - struct xlfd_decomposed *xlfd; - struct xlfd_decomposed *xlfd_best; - char *wc; - char **fontnames; - int nnames, i, max; - - xlfd = xlfd_decompose(XGetAtomName(disp, (Atom)ret)); - if (!xlfd) - return NULL; - - /* - * Form a wildcard consisting of everything in the - * original XLFD except for the size-related fields. - */ - { - struct xlfd_decomposed xlfd_wc = *xlfd; /* structure copy */ - xlfd_wc.pixel_size = XLFD_INT_WILDCARD; - xlfd_wc.point_size = XLFD_INT_WILDCARD; - xlfd_wc.average_width = XLFD_INT_WILDCARD; - wc = xlfd_recompose(&xlfd_wc); - } - - /* - * Fetch all the font names matching that wildcard. - */ - max = 32768; - while (1) { - fontnames = XListFonts(disp, wc, max, &nnames); - if (nnames >= max) { - XFreeFontNames(fontnames); - max *= 2; - } else - break; - } - - sfree(wc); - - /* - * Iterate over those to find the one closest in size to the - * original font, in the correct direction. - */ - -#define FLIPPED_SIZE(xlfd) \ - (((xlfd)->pixel_size + (xlfd)->point_size) * \ - (increment < 0 ? -1 : +1)) - - xlfd_best = NULL; - for (i = 0; i < nnames; i++) { - struct xlfd_decomposed *xlfd2 = xlfd_decompose(fontnames[i]); - if (!xlfd2) - continue; - - if (xlfd2->pixel_size != 0 && - FLIPPED_SIZE(xlfd2) > FLIPPED_SIZE(xlfd) && - (!xlfd_best || FLIPPED_SIZE(xlfd2)u.vt->prefix, ":", bare_returned_name); - sfree(bare_returned_name); - } - - XFreeFontNames(fontnames); - sfree(xlfd); - sfree(xlfd_best); - } - return returned_name; -} - -#endif /* NOT_X_WINDOWS */ - -#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 bool pangofont_has_glyph(unifont *font, wchar_t glyph); -static void pangofont_draw_text(unifont_drawctx *ctx, unifont *font, - int x, int y, const wchar_t *string, int len, - bool wide, bool bold, int cellwidth); -static void pangofont_draw_combining(unifont_drawctx *ctx, unifont *font, - int x, int y, const wchar_t *string, - int len, bool wide, bool bold, - int cellwidth); -static unifont *pangofont_create(GtkWidget *widget, const char *name, - bool wide, bool bold, - int shadowoffset, bool shadowalways); -static unifont *pangofont_create_fallback(GtkWidget *widget, int height, - bool wide, bool bold, - int shadowoffset, bool 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, - bool resolve_aliases); -static char *pangofont_scale_fontname(GtkWidget *widget, const char *name, - int size); -static char *pangofont_size_increment(unifont *font, int increment); - -struct pangofont { - /* - * Pango objects. - */ - PangoFontDescription *desc; - PangoFontset *fset; - /* - * The containing widget. - */ - GtkWidget *widget; - /* - * Data passed in to unifont_create(). - */ - int shadowoffset; - bool bold, shadowalways; - /* - * Cache of character widths, indexed by Unicode code point. In - * pixels; -1 means we haven't asked Pango about this character - * before. - */ - int *widthcache; - unsigned nwidthcache; - - struct unifont u; -}; - -static const UnifontVtable pangofont_vtable = { - .create = pangofont_create, - .create_fallback = pangofont_create_fallback, - .destroy = pangofont_destroy, - .has_glyph = pangofont_has_glyph, - .draw_text = pangofont_draw_text, - .draw_combining = pangofont_draw_combining, - .enum_fonts = pangofont_enum_fonts, - .canonify_fontname = pangofont_canonify_fontname, - .scale_fontname = pangofont_scale_fontname, - .size_increment = pangofont_size_increment, - .prefix = "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 bool pangofont_check_desc_makes_sense(PangoContext *ctx, - PangoFontDescription *desc) -{ -#ifndef PANGO_PRE_1POINT6 - PangoFontMap *map; -#endif - PangoFontFamily **families; - int i, nfamilies; - bool 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_ascii_strcasecmp(pango_font_family_get_name(families[i]), - pango_font_description_get_family(desc))) { - matched = true; - break; - } - } - g_free(families); - - return matched; -} - -static unifont *pangofont_create_internal(GtkWidget *widget, - PangoContext *ctx, - PangoFontDescription *desc, - bool wide, bool bold, - int shadowoffset, bool shadowalways) -{ - struct pangofont *pfont; -#ifndef PANGO_PRE_1POINT6 - PangoFontMap *map; -#endif - PangoFontset *fset; - PangoFontMetrics *metrics; - -#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_CEIL(pango_font_metrics_get_ascent(metrics)); - pfont->u.descent = - PANGO_PIXELS_CEIL(pango_font_metrics_get_descent(metrics)); - pfont->u.height = pfont->u.ascent + pfont->u.descent; - pfont->u.strikethrough_y = - PANGO_PIXELS(pango_font_metrics_get_ascent(metrics) - - pango_font_metrics_get_strikethrough_position(metrics)); - pfont->u.want_fallback = false; -#ifdef DRAW_TEXT_CAIRO - pfont->u.preferred_drawtype = DRAWTYPE_CAIRO; -#elif defined DRAW_TEXT_GDK - pfont->u.preferred_drawtype = DRAWTYPE_GDK; -#else -#error No drawtype available at all -#endif - /* The Pango API is hardwired to UTF-8 */ - pfont->u.public_charset = CS_UTF8; - pfont->desc = desc; - pfont->fset = fset; - pfont->widget = widget; - pfont->bold = bold; - pfont->shadowoffset = shadowoffset; - pfont->shadowalways = shadowalways; - pfont->widthcache = NULL; - pfont->nwidthcache = 0; - - pango_font_metrics_unref(metrics); - - return &pfont->u; -} - -static unifont *pangofont_create(GtkWidget *widget, const char *name, - bool wide, bool bold, - int shadowoffset, bool shadowalways) -{ - PangoContext *ctx; - PangoFontDescription *desc; - - desc = pango_font_description_from_string(name); - if (!desc) - return NULL; - ctx = gtk_widget_get_pango_context(widget); - if (!ctx) { - pango_font_description_free(desc); - return NULL; - } - if (!pangofont_check_desc_makes_sense(ctx, desc)) { - pango_font_description_free(desc); - return NULL; - } - return pangofont_create_internal(widget, ctx, desc, wide, bold, - shadowoffset, shadowalways); -} - -static unifont *pangofont_create_fallback(GtkWidget *widget, int height, - bool wide, bool bold, - int shadowoffset, bool shadowalways) -{ - PangoContext *ctx; - PangoFontDescription *desc; - - desc = pango_font_description_from_string("Monospace"); - if (!desc) - return NULL; - ctx = gtk_widget_get_pango_context(widget); - if (!ctx) { - pango_font_description_free(desc); - return NULL; - } - pango_font_description_set_absolute_size(desc, height * PANGO_SCALE); - return pangofont_create_internal(widget, ctx, desc, wide, bold, - shadowoffset, shadowalways); -} - -static void pangofont_destroy(unifont *font) -{ - struct pangofont *pfont = container_of(font, struct pangofont, u); - pango_font_description_free(pfont->desc); - sfree(pfont->widthcache); - g_object_unref(pfont->fset); - sfree(pfont); -} - -static int pangofont_char_width(PangoLayout *layout, struct pangofont *pfont, - wchar_t uchr, const char *utfchr, int utflen) -{ - /* - * Here we check whether a character has the same width as the - * character cell it'll be drawn in. Because profiling showed that - * asking Pango for text sizes was a huge bottleneck when we were - * calling it every time we needed to know this, we instead call - * it only on characters we don't already know about, and cache - * the results. - */ - - if ((unsigned)uchr >= pfont->nwidthcache) { - unsigned newsize = ((int)uchr + 0x100) & ~0xFF; - pfont->widthcache = sresize(pfont->widthcache, newsize, int); - while (pfont->nwidthcache < newsize) - pfont->widthcache[pfont->nwidthcache++] = -1; - } - - if (pfont->widthcache[uchr] < 0) { - PangoRectangle rect; - pango_layout_set_text(layout, utfchr, utflen); - pango_layout_get_extents(layout, NULL, &rect); - pfont->widthcache[uchr] = rect.width; - } - - return pfont->widthcache[uchr]; -} - -static bool pangofont_has_glyph(unifont *font, wchar_t glyph) -{ - /* Pango implements font fallback, so assume it has everything */ - return true; -} - -#ifdef DRAW_TEXT_GDK -static void pango_gdk_draw_layout(unifont_drawctx *ctx, - gint x, gint y, PangoLayout *layout) -{ - gdk_draw_layout(ctx->u.gdk.target, ctx->u.gdk.gc, x, y, layout); -} -#endif - -#ifdef DRAW_TEXT_CAIRO -static void pango_cairo_draw_layout(unifont_drawctx *ctx, - gint x, gint y, PangoLayout *layout) -{ - cairo_move_to(ctx->u.cairo.cr, x, y); - pango_cairo_show_layout(ctx->u.cairo.cr, layout); -} -#endif - -static void pangofont_draw_internal(unifont_drawctx *ctx, unifont *font, - int x, int y, const wchar_t *string, - int len, bool wide, bool bold, - int cellwidth, bool combining) -{ - struct pangofont *pfont = container_of(font, struct pangofont, u); - PangoLayout *layout; - PangoRectangle rect; - char *utfstring, *utfptr; - int utflen; - bool shadowbold = false; - void (*draw_layout)(unifont_drawctx *ctx, - gint x, gint y, PangoLayout *layout) = NULL; - -#ifdef DRAW_TEXT_GDK - if (ctx->type == DRAWTYPE_GDK) { - draw_layout = pango_gdk_draw_layout; - } -#endif -#ifdef DRAW_TEXT_CAIRO - if (ctx->type == DRAWTYPE_CAIRO) { - draw_layout = pango_cairo_draw_layout; - } -#endif - assert(draw_layout); - - 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); - } - } - - /* - * Pango always expects UTF-8, so convert the input wide character - * string to UTF-8. - */ - utfstring = snewn(len*6+1, char); /* UTF-8 has max 6 bytes/char */ - utflen = wc_to_mb(CS_UTF8, 0, string, len, - utfstring, len*6+1, ".", NULL); - - utfptr = utfstring; - while (utflen > 0) { - int clen, n; - int desired = cellwidth * PANGO_SCALE; - - /* - * 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. - */ - - if (combining) { - /* - * For a character with combining stuff, we just dump the - * whole lot in one go, and expect it to take up just one - * character cell. - */ - clen = utflen; - n = 1; - } else { - /* - * Start by extracting a single UTF-8 character from the - * string. - */ - clen = 1; - while (clen < utflen && - (unsigned char)utfptr[clen] >= 0x80 && - (unsigned char)utfptr[clen] < 0xC0) - clen++; - n = 1; - - if (is_rtl(string[0]) || - pangofont_char_width(layout, pfont, string[n-1], - utfptr, clen) != desired) { - /* - * If this character is a right-to-left one, or has an - * unusual width, then we must display it on its own. - */ - } else { - /* - * Try to amalgamate a contiguous string of characters - * with the expected sensible width, for the common case - * in which we're using a monospaced font and everything - * works as expected. - */ - while (clen < utflen) { - int oldclen = clen; - clen++; /* skip UTF-8 introducer byte */ - while (clen < utflen && - (unsigned char)utfptr[clen] >= 0x80 && - (unsigned char)utfptr[clen] < 0xC0) - clen++; - n++; - if (is_rtl(string[n-1]) || - pangofont_char_width(layout, pfont, - string[n-1], utfptr + oldclen, - clen - oldclen) != desired) { - clen = oldclen; - n--; - break; - } - } - } - } - - pango_layout_set_text(layout, utfptr, clen); - pango_layout_get_pixel_extents(layout, NULL, &rect); - - draw_layout(ctx, - x + (n*cellwidth - rect.width)/2, - y + (pfont->u.height - rect.height)/2, layout); - if (shadowbold) - draw_layout(ctx, - x + (n*cellwidth - rect.width)/2 + pfont->shadowoffset, - y + (pfont->u.height - rect.height)/2, layout); - - utflen -= clen; - utfptr += clen; - string += n; - x += n * cellwidth; - } - - sfree(utfstring); - - g_object_unref(layout); -} - -static void pangofont_draw_text(unifont_drawctx *ctx, unifont *font, - int x, int y, const wchar_t *string, int len, - bool wide, bool bold, int cellwidth) -{ - pangofont_draw_internal(ctx, font, x, y, string, len, wide, bold, - cellwidth, false); -} - -static void pangofont_draw_combining(unifont_drawctx *ctx, unifont *font, - int x, int y, const wchar_t *string, - int len, bool wide, bool bold, - int cellwidth) -{ - wchar_t *tmpstring = NULL; - if (mk_wcwidth(string[0]) == 0) { - /* - * If we've been told to draw a sequence of _only_ combining - * characters, prefix a space so that they have something to - * combine with. - */ - tmpstring = snewn(len+1, wchar_t); - memcpy(tmpstring+1, string, len * sizeof(wchar_t)); - tmpstring[0] = L' '; - string = tmpstring; - len++; - } - pangofont_draw_internal(ctx, font, x, y, string, len, wide, bold, - cellwidth, true); - sfree(tmpstring); -} - -/* - * 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, *stylekey; - - pango_font_description_set_size(desc, sizes[k]); - - fullname = pango_font_description_to_string(desc); - - /* - * Construct the sorting key for font styles. - */ - { - strbuf *buf = strbuf_new(); - - int weight = pango_font_description_get_weight(desc); - /* Weight: normal, then lighter, then bolder */ - if (weight <= PANGO_WEIGHT_NORMAL) - weight = PANGO_WEIGHT_NORMAL - weight; - strbuf_catf(buf, "%4d", weight); - - strbuf_catf(buf, " %2d", - pango_font_description_get_style(desc)); - - int stretch = pango_font_description_get_stretch(desc); - /* Stretch: closer to normal sorts earlier */ - stretch = 2 * abs(PANGO_STRETCH_NORMAL - stretch) + - (stretch < PANGO_STRETCH_NORMAL); - strbuf_catf(buf, " %2d", stretch); - - strbuf_catf(buf, " %2d", - pango_font_description_get_variant(desc)); - - stylekey = strbuf_to_str(buf); - } - - /* - * 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); - - sfree(stylekey); - 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, - bool 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; -} - -static char *pangofont_size_increment(unifont *font, int increment) -{ - struct pangofont *pfont = container_of(font, struct pangofont, u); - PangoFontDescription *desc; - int size; - char *newname, *retname; - - desc = pango_font_description_copy_static(pfont->desc); - - size = pango_font_description_get_size(desc); - size += PANGO_SCALE * increment; - - if (size <= 0) { - retname = NULL; - } else { - pango_font_description_set_size(desc, size); - newname = pango_font_description_to_string(desc); - retname = dupcat(pfont->u.vt->prefix, ":", 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.) - * - * The 'multifont' subclass is omitted here, as discussed above. - */ -static const struct UnifontVtable *unifont_types[] = { -#if GTK_CHECK_VERSION(2,0,0) - &pangofont_vtable, -#endif -#ifndef NOT_X_WINDOWS - &x11font_vtable, -#endif -}; - -/* - * 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, bool wide, - bool bold, int shadowoffset, bool 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(unifont_drawctx *ctx, unifont *font, - int x, int y, const wchar_t *string, int len, - bool wide, bool bold, int cellwidth) -{ - font->vt->draw_text(ctx, font, x, y, string, len, wide, bold, cellwidth); -} - -void unifont_draw_combining(unifont_drawctx *ctx, unifont *font, - int x, int y, const wchar_t *string, int len, - bool wide, bool bold, int cellwidth) -{ - font->vt->draw_combining(ctx, font, x, y, string, len, wide, bold, - cellwidth); -} - -char *unifont_size_increment(unifont *font, int increment) -{ - return font->vt->size_increment(font, increment); -} - -/* ---------------------------------------------------------------------- - * Multiple-font wrapper. This is a type of unifont which encapsulates - * up to two other unifonts, permitting missing glyphs in the main - * font to be filled in by a fallback font. - * - * This is a type of unifont just like the previous two, but it has a - * separate constructor which is manually called by the client, so it - * doesn't appear in the list of available font types enumerated by - * unifont_create. This means it's not used by unifontsel either, so - * it doesn't need to support any methods except draw_text and - * destroy. - */ - -static void multifont_draw_text(unifont_drawctx *ctx, unifont *font, - int x, int y, const wchar_t *string, int len, - bool wide, bool bold, int cellwidth); -static void multifont_draw_combining(unifont_drawctx *ctx, unifont *font, - int x, int y, const wchar_t *string, - int len, bool wide, bool bold, - int cellwidth); -static void multifont_destroy(unifont *font); -static char *multifont_size_increment(unifont *font, int increment); - -struct multifont { - unifont *main; - unifont *fallback; - - struct unifont u; -}; - -static const UnifontVtable multifont_vtable = { - .create = NULL, /* creation is done specially */ - .create_fallback = NULL, - .destroy = multifont_destroy, - .has_glyph = NULL, - .draw_text = multifont_draw_text, - .draw_combining = multifont_draw_combining, - .enum_fonts = NULL, - .canonify_fontname = NULL, - .scale_fontname = NULL, - .size_increment = multifont_size_increment, - .prefix = "client", -}; - -unifont *multifont_create(GtkWidget *widget, const char *name, - bool wide, bool bold, - int shadowoffset, bool shadowalways) -{ - int i; - unifont *font, *fallback; - struct multifont *mfont; - - font = unifont_create(widget, name, wide, bold, - shadowoffset, shadowalways); - if (!font) - return NULL; - - fallback = NULL; - if (font->want_fallback) { - for (i = 0; i < lenof(unifont_types); i++) { - if (unifont_types[i]->create_fallback) { - fallback = unifont_types[i]->create_fallback - (widget, font->height, wide, bold, - shadowoffset, shadowalways); - if (fallback) - break; - } - } - } - - /* - * Construct our multifont. Public members are all copied from the - * primary font we're wrapping. - */ - mfont = snew(struct multifont); - mfont->u.vt = &multifont_vtable; - mfont->u.width = font->width; - mfont->u.ascent = font->ascent; - mfont->u.descent = font->descent; - mfont->u.height = font->height; - mfont->u.strikethrough_y = font->strikethrough_y; - mfont->u.public_charset = font->public_charset; - mfont->u.want_fallback = false; /* shouldn't be needed, but just in case */ - mfont->u.preferred_drawtype = font->preferred_drawtype; - mfont->main = font; - mfont->fallback = fallback; - - return &mfont->u; -} - -static void multifont_destroy(unifont *font) -{ - struct multifont *mfont = container_of(font, struct multifont, u); - unifont_destroy(mfont->main); - if (mfont->fallback) - unifont_destroy(mfont->fallback); - sfree(mfont); -} - -typedef void (*unifont_draw_func_t)(unifont_drawctx *ctx, unifont *font, - int x, int y, const wchar_t *string, - int len, bool wide, bool bold, - int cellwidth); - -static void multifont_draw_main(unifont_drawctx *ctx, unifont *font, int x, - int y, const wchar_t *string, int len, - bool wide, bool bold, int cellwidth, - int cellinc, unifont_draw_func_t draw) -{ - struct multifont *mfont = container_of(font, struct multifont, u); - unifont *f; - bool ok; - int i; - - while (len > 0) { - /* - * Find a maximal sequence of characters which are, or are - * not, supported by our main font. - */ - ok = mfont->main->vt->has_glyph(mfont->main, string[0]); - for (i = 1; - i < len && - !mfont->main->vt->has_glyph(mfont->main, string[i]) == !ok; - i++); - - /* - * Now display it. - */ - f = ok ? mfont->main : mfont->fallback; - if (f) - draw(ctx, f, x, y, string, i, wide, bold, cellwidth); - string += i; - len -= i; - x += i * cellinc; - } -} - -static void multifont_draw_text(unifont_drawctx *ctx, unifont *font, int x, - int y, const wchar_t *string, int len, - bool wide, bool bold, int cellwidth) -{ - multifont_draw_main(ctx, font, x, y, string, len, wide, bold, - cellwidth, cellwidth, unifont_draw_text); -} - -static void multifont_draw_combining(unifont_drawctx *ctx, unifont *font, - int x, int y, const wchar_t *string, - int len, bool wide, bool bold, - int cellwidth) -{ - multifont_draw_main(ctx, font, x, y, string, len, wide, bold, - cellwidth, 0, unifont_draw_combining); -} - -static char *multifont_size_increment(unifont *font, int increment) -{ - struct multifont *mfont = container_of(font, struct multifont, u); - return unifont_size_increment(mfont->main, increment); -} - -#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 { - GtkListStore *family_model, *style_model, *size_model; - GtkWidget *family_list, *style_list, *size_entry, *size_list; - GtkWidget *filter_buttons[4]; - int n_filter_buttons; - GtkWidget *preview_area; -#ifndef NO_BACKING_PIXMAPS - GdkPixmap *preview_pixmap; -#endif - 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; - bool inhibit_response; /* inhibit callbacks when we change GUI controls */ - - unifontsel u; -} 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 UnifontVtable *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_ascii_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_draw_preview_text(unifontsel_internal *fs); - -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); - unifontsel_draw_preview_text(fs); -} - -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; - - fs->inhibit_response = true; - - 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); - - fs->inhibit_response = false; -} - -static void unifontsel_setup_stylelist(unifontsel_internal *fs, - int start, int end) -{ - GtkTreeIter iter; - int i, listindex, minpos = -1, maxpos = -1; - bool 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, 4, PANGO_WEIGHT_NORMAL, -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, 4, PANGO_WEIGHT_BOLD, -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) { - /* _shouldn't_ happen unless font list is completely funted */ - break; - } - 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 < fs->n_filter_buttons; i++) { - int flagbit = GPOINTER_TO_INT(g_object_get_data - (G_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_inner(unifont_drawctx *dctx, - 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; - -#ifdef DRAW_TEXT_GDK - if (dctx->type == DRAWTYPE_GDK) { - gdk_gc_set_foreground(dctx->u.gdk.gc, &fs->preview_bg); - gdk_draw_rectangle(dctx->u.gdk.target, dctx->u.gdk.gc, 1, 0, 0, - fs->preview_width, fs->preview_height); - gdk_gc_set_foreground(dctx->u.gdk.gc, &fs->preview_fg); - } -#endif -#ifdef DRAW_TEXT_CAIRO - if (dctx->type == DRAWTYPE_CAIRO) { - cairo_set_source_rgb(dctx->u.cairo.cr, - fs->preview_bg.red / 65535.0, - fs->preview_bg.green / 65535.0, - fs->preview_bg.blue / 65535.0); - cairo_paint(dctx->u.cairo.cr); - cairo_set_source_rgb(dctx->u.cairo.cr, - fs->preview_fg.red / 65535.0, - fs->preview_fg.green / 65535.0, - fs->preview_fg.blue / 65535.0); - } -#endif - - 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(dctx, font, - 0, font->ascent, - L"bankrupt jilted showmen quiz convex fogey", - 41, false, false, font->width); - info->fontclass->draw_text(dctx, font, - 0, font->ascent + font->height, - L"BANKRUPT JILTED SHOWMEN QUIZ CONVEX FOGEY", - 41, false, false, font->width); - /* - * The ordering of punctuation here is also selected - * with some specific aims in mind. I put ` and ' - * together because some software (and people) still - * 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(dctx, font, - 0, font->ascent + font->height * 2, - L"0123456789!?,.:;<>()[]{}\\/`'\"+*-=~#_@|%&^$", - 42, false, false, font->width); - - info->fontclass->destroy(font); - } - - sfree(sizename); -} - -static void unifontsel_draw_preview_text(unifontsel_internal *fs) -{ - unifont_drawctx dctx; - GdkWindow *target; - -#ifndef NO_BACKING_PIXMAPS - target = fs->preview_pixmap; -#else - target = gtk_widget_get_window(fs->preview_area); -#endif - if (!target) /* we may be called when we haven't created everything yet */ - return; - - dctx.type = DRAWTYPE_DEFAULT; -#ifdef DRAW_TEXT_GDK - if (dctx.type == DRAWTYPE_GDK) { - dctx.u.gdk.target = target; - dctx.u.gdk.gc = gdk_gc_new(target); - } -#endif -#ifdef DRAW_TEXT_CAIRO - if (dctx.type == DRAWTYPE_CAIRO) { -#if GTK_CHECK_VERSION(3,22,0) - cairo_region_t *region; -#endif - - dctx.u.cairo.widget = GTK_WIDGET(fs->preview_area); - -#if GTK_CHECK_VERSION(3,22,0) - dctx.u.cairo.gdkwin = gtk_widget_get_window(dctx.u.cairo.widget); - region = gdk_window_get_clip_region(dctx.u.cairo.gdkwin); - dctx.u.cairo.drawctx = gdk_window_begin_draw_frame( - dctx.u.cairo.gdkwin, region); - dctx.u.cairo.cr = gdk_drawing_context_get_cairo_context( - dctx.u.cairo.drawctx); - cairo_region_destroy(region); -#else - dctx.u.cairo.cr = gdk_cairo_create(target); -#endif - } -#endif - - unifontsel_draw_preview_text_inner(&dctx, fs); - -#ifdef DRAW_TEXT_GDK - if (dctx.type == DRAWTYPE_GDK) { - gdk_gc_unref(dctx.u.gdk.gc); - } -#endif -#ifdef DRAW_TEXT_CAIRO - if (dctx.type == DRAWTYPE_CAIRO) { -#if GTK_CHECK_VERSION(3,22,0) - gdk_window_end_draw_frame(dctx.u.cairo.gdkwin, dctx.u.cairo.drawctx); -#else - cairo_destroy(dctx.u.cairo.cr); -#endif - } -#endif - - gdk_window_invalidate_rect(gtk_widget_get_window(fs->preview_area), - NULL, false); -} - -static void unifontsel_select_font(unifontsel_internal *fs, - fontinfo *info, int size, int leftlist, - bool size_is_explicit) -{ - int index; - int minval, maxval; - gboolean success; - 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); - success = gtk_tree_model_get_iter(GTK_TREE_MODEL(fs->family_model), - &iter, treepath); - assert(success); - 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_editable_set_editable(GTK_EDITABLE(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; - bool newstate = gtk_toggle_button_get_active(tb); - int newflags; - int flagbit = GPOINTER_TO_INT(g_object_get_data(G_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 UnifontVtable *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); - if (!below) - pos = -1; - above = index234(fs->fonts_by_selorder, pos+1); - - /* - * See if we've found it exactly, which is an easy special - * case. If we have, it'll be in `below' and not `above', - * because we did a REL234_LE rather than REL234_LT search. - */ - if (below && !fontinfo_selorder_compare(&info2, below)) - return below; - - /* - * Now we've either found two suitable fonts, one smaller and - * one larger, or we're at one or other extreme end of the - * 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); - if (!info) - return; /* _shouldn't_ happen unless font list is completely funted */ - info = update_for_intended_size(fs, info); - if (!info) - return; /* similarly shouldn't happen */ - 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); - if (!info) - return; /* _shouldn't_ happen unless font list is completely funted */ - info = update_for_intended_size(fs, info); - if (!info) - return; /* similarly shouldn't happen */ - 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); - if (!info) - return; /* _shouldn't_ happen unless font list is completely funted */ - 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); - } -} - -#if GTK_CHECK_VERSION(3,0,0) -static gint unifontsel_draw_area(GtkWidget *widget, cairo_t *cr, gpointer data) -{ - unifontsel_internal *fs = (unifontsel_internal *)data; - unifont_drawctx dctx; - - dctx.type = DRAWTYPE_CAIRO; - dctx.u.cairo.widget = widget; - dctx.u.cairo.cr = cr; - unifontsel_draw_preview_text_inner(&dctx, fs); - - return true; -} -#else -static gint unifontsel_expose_area(GtkWidget *widget, GdkEventExpose *event, - gpointer data) -{ - unifontsel_internal *fs = (unifontsel_internal *)data; - -#ifndef NO_BACKING_PIXMAPS - if (fs->preview_pixmap) { - gdk_draw_pixmap(gtk_widget_get_window(widget), - (gtk_widget_get_style(widget)->fg_gc - [gtk_widget_get_state(widget)]), - fs->preview_pixmap, - event->area.x, event->area.y, - event->area.x, event->area.y, - event->area.width, event->area.height); - } -#else - unifontsel_draw_preview_text(fs); -#endif - - return true; -} -#endif - -static gint unifontsel_configure_area(GtkWidget *widget, - GdkEventConfigure *event, gpointer data) -{ -#ifndef NO_BACKING_PIXMAPS - 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(gtk_widget_get_window(widget), - nx, ny, -1); - fs->preview_width = nx; - fs->preview_height = ny; - - unifontsel_draw_preview_text(fs); - } -#endif - - gdk_window_invalidate_rect(gtk_widget_get_window(widget), 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; - - { - int width, height; - - /* - * Invent some magic size constants. - */ - get_label_text_dimensions("Quite Long Font Name (Foundry)", - &width, &height); - font_width = width; - lists_height = 14 * height; - preview_height = 5 * height; - - get_label_text_dimensions("Italic Extra Condensed", &width, &height); - style_width = width; - - get_label_text_dimensions("48000", &width, &height); - size_width = width; - } - - /* - * 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), STANDARD_CANCEL_LABEL, GTK_RESPONSE_CANCEL); - fs->u.ok_button = gtk_dialog_add_button - (GTK_DIALOG(fs->u.window), STANDARD_OK_LABEL, 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. - */ -#if GTK_CHECK_VERSION(3,0,0) - table = gtk_grid_new(); - gtk_grid_set_column_spacing(GTK_GRID(table), 8); -#else - table = gtk_table_new(8, 3, false); - gtk_table_set_col_spacings(GTK_TABLE(table), 8); -#endif - gtk_widget_show(table); - -#if GTK_CHECK_VERSION(3,0,0) - /* GtkAlignment has become deprecated and we use the "margin" - * property */ - w = table; - g_object_set(G_OBJECT(w), "margin", 8, (const char *)NULL); -#elif 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 - /* In GTK < 2.4, even that isn't available */ - w = table; -#endif - - gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area - (GTK_DIALOG(fs->u.window))), - w, true, true, 0); - - label = gtk_label_new_with_mnemonic("_Font:"); - gtk_widget_show(label); - align_label_left(GTK_LABEL(label)); -#if GTK_CHECK_VERSION(3,0,0) - gtk_grid_attach(GTK_GRID(table), label, 0, 0, 1, 1); - g_object_set(G_OBJECT(label), "hexpand", true, (const char *)NULL); -#else - gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, 1, GTK_FILL, 0, 0, 0); -#endif - - /* - * 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); -#if GTK_CHECK_VERSION(3,0,0) - gtk_grid_attach(GTK_GRID(table), scroll, 0, 1, 1, 2); - g_object_set(G_OBJECT(scroll), "expand", true, (const char *)NULL); -#else - gtk_table_attach(GTK_TABLE(table), scroll, 0, 1, 1, 3, GTK_FILL, - GTK_EXPAND | GTK_FILL, 0, 0); -#endif - fs->family_model = model; - fs->family_list = w; - - label = gtk_label_new_with_mnemonic("_Style:"); - gtk_widget_show(label); - align_label_left(GTK_LABEL(label)); -#if GTK_CHECK_VERSION(3,0,0) - gtk_grid_attach(GTK_GRID(table), label, 1, 0, 1, 1); - g_object_set(G_OBJECT(label), "hexpand", true, (const char *)NULL); -#else - gtk_table_attach(GTK_TABLE(table), label, 1, 2, 0, 1, GTK_FILL, 0, 0, 0); -#endif - - /* - * 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. Also, since GTK3 at - * least doesn't seem to display insensitive elements differently - * by default, we add a further column to change their style. - */ - model = gtk_list_store_new(5, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT, - G_TYPE_BOOLEAN, 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 - ("Style", gtk_cell_renderer_text_new(), - "text", 0, "sensitive", 3, "weight", 4, (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); -#if GTK_CHECK_VERSION(3,0,0) - gtk_grid_attach(GTK_GRID(table), scroll, 1, 1, 1, 2); - g_object_set(G_OBJECT(scroll), "expand", true, (const char *)NULL); -#else - gtk_table_attach(GTK_TABLE(table), scroll, 1, 2, 1, 3, GTK_FILL, - GTK_EXPAND | GTK_FILL, 0, 0); -#endif - fs->style_model = model; - fs->style_list = w; - - label = gtk_label_new_with_mnemonic("Si_ze:"); - gtk_widget_show(label); - align_label_left(GTK_LABEL(label)); -#if GTK_CHECK_VERSION(3,0,0) - gtk_grid_attach(GTK_GRID(table), label, 2, 0, 1, 1); - g_object_set(G_OBJECT(label), "hexpand", true, (const char *)NULL); -#else - gtk_table_attach(GTK_TABLE(table), label, 2, 3, 0, 1, GTK_FILL, 0, 0, 0); -#endif - - /* - * 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); -#if GTK_CHECK_VERSION(3,0,0) - gtk_grid_attach(GTK_GRID(table), w, 2, 1, 1, 1); - g_object_set(G_OBJECT(w), "hexpand", true, (const char *)NULL); -#else - gtk_table_attach(GTK_TABLE(table), w, 2, 3, 1, 2, GTK_FILL, 0, 0, 0); -#endif - 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); -#if GTK_CHECK_VERSION(3,0,0) - gtk_grid_attach(GTK_GRID(table), scroll, 2, 2, 1, 1); - g_object_set(G_OBJECT(scroll), "expand", true, (const char *)NULL); -#else - gtk_table_attach(GTK_TABLE(table), scroll, 2, 3, 2, 3, GTK_FILL, - GTK_EXPAND | GTK_FILL, 0, 0); -#endif - fs->size_model = model; - fs->size_list = w; - - /* - * Preview widget. - */ - fs->preview_area = gtk_drawing_area_new(); -#ifndef NO_BACKING_PIXMAPS - fs->preview_pixmap = NULL; -#endif - 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; -#if !GTK_CHECK_VERSION(3,0,0) - 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); -#endif -#if GTK_CHECK_VERSION(3,0,0) - g_signal_connect(G_OBJECT(fs->preview_area), "draw", - G_CALLBACK(unifontsel_draw_area), fs); -#else - g_signal_connect(G_OBJECT(fs->preview_area), "expose_event", - G_CALLBACK(unifontsel_expose_area), fs); -#endif - g_signal_connect(G_OBJECT(fs->preview_area), "configure_event", - G_CALLBACK(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(3,0,0) - /* GtkAlignment has become deprecated and we use the "margin" - * property */ - g_object_set(G_OBJECT(w), "margin", 8, (const char *)NULL); -#elif 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); -#if GTK_CHECK_VERSION(3,0,0) - gtk_grid_attach(GTK_GRID(table), w, 0, 3, 3, 1); - g_object_set(G_OBJECT(w), "expand", true, (const char *)NULL); -#else - gtk_table_attach(GTK_TABLE(table), w, 0, 3, 3, 4, - GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 8); -#endif - - /* - * We only provide the checkboxes for client- and server-side - * fonts if we have the X11 back end available, because that's the - * only situation in which more than one class of font is - * available anyway. - */ - fs->n_filter_buttons = 0; -#ifndef NOT_X_WINDOWS - w = gtk_check_button_new_with_label("Show client-side fonts"); - g_object_set_data(G_OBJECT(w), "user-data", - GINT_TO_POINTER(FONTFLAG_CLIENTSIDE)); - g_signal_connect(G_OBJECT(w), "toggled", - G_CALLBACK(unifontsel_button_toggled), fs); - gtk_widget_show(w); - fs->filter_buttons[fs->n_filter_buttons++] = w; -#if GTK_CHECK_VERSION(3,0,0) - gtk_grid_attach(GTK_GRID(table), w, 0, 4, 3, 1); - g_object_set(G_OBJECT(w), "hexpand", true, (const char *)NULL); -#else - gtk_table_attach(GTK_TABLE(table), w, 0, 3, 4, 5, GTK_FILL, 0, 0, 0); -#endif - w = gtk_check_button_new_with_label("Show server-side fonts"); - g_object_set_data(G_OBJECT(w), "user-data", - GINT_TO_POINTER(FONTFLAG_SERVERSIDE)); - g_signal_connect(G_OBJECT(w), "toggled", - G_CALLBACK(unifontsel_button_toggled), fs); - gtk_widget_show(w); - fs->filter_buttons[fs->n_filter_buttons++] = w; -#if GTK_CHECK_VERSION(3,0,0) - gtk_grid_attach(GTK_GRID(table), w, 0, 5, 3, 1); - g_object_set(G_OBJECT(w), "hexpand", true, (const char *)NULL); -#else - gtk_table_attach(GTK_TABLE(table), w, 0, 3, 5, 6, GTK_FILL, 0, 0, 0); -#endif - w = gtk_check_button_new_with_label("Show server-side font aliases"); - g_object_set_data(G_OBJECT(w), "user-data", - GINT_TO_POINTER(FONTFLAG_SERVERALIAS)); - g_signal_connect(G_OBJECT(w), "toggled", - G_CALLBACK(unifontsel_button_toggled), fs); - gtk_widget_show(w); - fs->filter_buttons[fs->n_filter_buttons++] = w; -#if GTK_CHECK_VERSION(3,0,0) - gtk_grid_attach(GTK_GRID(table), w, 0, 6, 3, 1); - g_object_set(G_OBJECT(w), "hexpand", true, (const char *)NULL); -#else - gtk_table_attach(GTK_TABLE(table), w, 0, 3, 6, 7, GTK_FILL, 0, 0, 0); -#endif -#endif /* NOT_X_WINDOWS */ - w = gtk_check_button_new_with_label("Show non-monospaced fonts"); - g_object_set_data(G_OBJECT(w), "user-data", - GINT_TO_POINTER(FONTFLAG_NONMONOSPACED)); - g_signal_connect(G_OBJECT(w), "toggled", - G_CALLBACK(unifontsel_button_toggled), fs); - gtk_widget_show(w); - fs->filter_buttons[fs->n_filter_buttons++] = w; -#if GTK_CHECK_VERSION(3,0,0) - gtk_grid_attach(GTK_GRID(table), w, 0, 7, 3, 1); - g_object_set(G_OBJECT(w), "hexpand", true, (const char *)NULL); -#else - gtk_table_attach(GTK_TABLE(table), w, 0, 3, 7, 8, GTK_FILL, 0, 0, 0); -#endif - - assert(fs->n_filter_buttons <= 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 &fs->u; -} - -void unifontsel_destroy(unifontsel *fontsel) -{ - unifontsel_internal *fs = container_of(fontsel, unifontsel_internal, u); - fontinfo *info; - -#ifndef NO_BACKING_PIXMAPS - if (fs->preview_pixmap) - gdk_pixmap_unref(fs->preview_pixmap); -#endif - - 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 = container_of(fontsel, unifontsel_internal, u); - 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 = DEFAULT_GTK_FONT; - - /* - * 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 = container_of(fontsel, unifontsel_internal, u); - 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); - sfree(name); - return ret; - } - } - - return dupcat(fs->selected->fontclass->prefix, ":", - fs->selected->realname); -} - -#endif /* GTK_CHECK_VERSION(2,0,0) */ diff --git a/unix/gtkfont.h b/unix/gtkfont.h deleted file mode 100644 index e43a748d..00000000 --- a/unix/gtkfont.h +++ /dev/null @@ -1,186 +0,0 @@ -/* - * 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 - -/* - * We support two entirely different drawing systems: the old - * GDK1/GDK2 one which works on server-side X drawables, and the - * new-style Cairo one. GTK1 only supports GDK drawing; GTK3 only - * supports Cairo; GTK2 supports both, but deprecates GTK, so we only - * enable it if we aren't trying on purpose to compile without the - * deprecated functions. - * - * Our different font classes may prefer different drawing systems: X - * server-side fonts are a lot faster to draw with GDK, but for - * everything else we prefer Cairo, on general grounds of modernness - * and also in particular because its matrix-based scaling system - * gives much nicer results for double-width and double-height text - * when a scalable font is in use. - */ -#if !GTK_CHECK_VERSION(3,0,0) && !defined GDK_DISABLE_DEPRECATED -#define DRAW_TEXT_GDK -#endif -#if GTK_CHECK_VERSION(2,8,0) -#define DRAW_TEXT_CAIRO -#endif - -#if GTK_CHECK_VERSION(3,0,0) || defined GDK_DISABLE_DEPRECATED -/* - * Where the facility is available, we prefer to render text on to a - * persistent server-side pixmap, and redraw windows by simply - * blitting rectangles of that pixmap into them as needed. This is - * better for performance since we avoid expensive font rendering - * calls where possible, and it's particularly good over a non-local X - * connection because the response to an expose event can now be a - * very simple rectangle-copy operation rather than a lot of fiddly - * drawing or bitmap transfer. - * - * However, GTK is deprecating the use of server-side pixmaps, so we - * have to disable this mode under some circumstances. - */ -#define NO_BACKING_PIXMAPS -#endif - -/* - * Exports from gtkfont.c. - */ -typedef struct UnifontVtable UnifontVtable; /* contents internal to - * gtkfont.c */ -typedef struct unifont { - const struct UnifontVtable *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'. - */ - int public_charset; - - /* - * Font dimensions needed by clients. - */ - int width, height, ascent, descent, strikethrough_y; - - /* - * Indicates whether this font is capable of handling all glyphs - * (Pango fonts can do this because Pango automatically supplies - * missing glyphs from other fonts), or whether it would like a - * fallback font to cope with missing glyphs. - */ - bool want_fallback; - - /* - * Preferred drawing API to use when this class of font is active. - * (See the enum below, in unifont_drawctx.) - */ - int preferred_drawtype; -} unifont; - -/* A default drawtype, for the case where no font exists to make the - * decision with. */ -#ifdef DRAW_TEXT_CAIRO -#define DRAW_DEFAULT_CAIRO -#define DRAWTYPE_DEFAULT DRAWTYPE_CAIRO -#elif defined DRAW_TEXT_GDK -#define DRAW_DEFAULT_GDK -#define DRAWTYPE_DEFAULT DRAWTYPE_GDK -#else -#error No drawtype available at all -#endif - -/* - * Drawing context passed in to unifont_draw_text, which contains - * everything required to know where and how to draw the requested - * text. - */ -typedef struct unifont_drawctx { - enum { -#ifdef DRAW_TEXT_GDK - DRAWTYPE_GDK, -#endif -#ifdef DRAW_TEXT_CAIRO - DRAWTYPE_CAIRO, -#endif - DRAWTYPE_NTYPES - } type; - union { -#ifdef DRAW_TEXT_GDK - struct { - GdkDrawable *target; - GdkGC *gc; - } gdk; -#endif -#ifdef DRAW_TEXT_CAIRO - struct { - /* Need an actual widget, in order to backtrack to its X - * screen number when creating server-side pixmaps */ - GtkWidget *widget; - cairo_t *cr; - cairo_matrix_t origmatrix; -#if GTK_CHECK_VERSION(3,22,0) - GdkWindow *gdkwin; - GdkDrawingContext *drawctx; -#endif - } cairo; -#endif - } u; -} unifont_drawctx; - -unifont *unifont_create(GtkWidget *widget, const char *name, - bool wide, bool bold, - int shadowoffset, bool shadowalways); -void unifont_destroy(unifont *font); -void unifont_draw_text(unifont_drawctx *ctx, unifont *font, - int x, int y, const wchar_t *string, int len, - bool wide, bool bold, int cellwidth); -/* Same as unifont_draw_text, but expects 'string' to contain one - * normal char plus combining chars, and overdraws them all in the - * same character cell. */ -void unifont_draw_combining(unifont_drawctx *ctx, unifont *font, - int x, int y, const wchar_t *string, int len, - bool wide, bool bold, int cellwidth); -/* Return a name that will select a bigger/smaller font than this one, - * or NULL if no such name is available. */ -char *unifont_size_increment(unifont *font, int increment); - -/* - * This function behaves exactly like the low-level unifont_create, - * except that as well as the requested font it also allocates (if - * necessary) a fallback font for filling in replacement glyphs. - * - * Return value is usable with unifont_destroy and unifont_draw_text - * as if it were an ordinary unifont. - */ -unifont *multifont_create(GtkWidget *widget, const char *name, - bool wide, bool bold, - int shadowoffset, bool shadowalways); - -/* - * Unified font selector dialog. I can't be bothered to do a - * proper GTK subclassing today, so this will just be an ordinary - * data structure with some useful members. - * - * (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 */ diff --git a/unix/gtkmain.c b/unix/gtkmain.c deleted file mode 100644 index ec8f7da4..00000000 --- a/unix/gtkmain.c +++ /dev/null @@ -1,673 +0,0 @@ -/* - * gtkmain.c: the common main-program code between the straight-up - * Unix PuTTY and pterm, which they do not share with the - * multi-session gtkapp.c. - */ - -#define _GNU_SOURCE - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#if !GTK_CHECK_VERSION(3,0,0) -#include -#endif - -#if GTK_CHECK_VERSION(2,0,0) -#include -#endif - -#define MAY_REFER_TO_GTK_IN_HEADERS - -#include "putty.h" -#include "terminal.h" -#include "gtkcompat.h" -#include "gtkfont.h" -#include "gtkmisc.h" - -#ifndef NOT_X_WINDOWS -#include -#include -#include -#include -#include "x11misc.h" -#endif - -static char *progname, **gtkargvstart; -static int ngtkargs; - -static const char *app_name = "pterm"; - -char *x_get_default(const char *key) -{ -#ifndef NOT_X_WINDOWS - Display *disp; - if ((disp = get_x11_display()) == NULL) - return NULL; - return XGetDefault(disp, app_name, key); -#else - return NULL; -#endif -} - -void fork_and_exec_self(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 += ngtkargs; - while (va_arg(ap, char *) != NULL) - n++; - va_end(ap); - - args = snewn(n, char *); - args[0] = progname; - args[n-1] = NULL; - for (i = 0; i < ngtkargs; i++) - args[i+1] = 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"); - sfree(args); - 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(progname, args); - perror("exec"); - _exit(127); - - } else { - int status; - sfree(args); - waitpid(pid, &status, 0); - } - -} - -void launch_duplicate_session(Conf *conf) -{ - /* - * For this feature we must marshal conf and (possibly) pty_argv - * into a byte stream, create a pipe, and send this byte stream - * to the child through the pipe. - */ - int i, ret; - strbuf *serialised; - char option[80]; - int pipefd[2]; - - if (pipe(pipefd) < 0) { - perror("pipe"); - return; - } - - serialised = strbuf_new(); - - conf_serialise(BinarySink_UPCAST(serialised), conf); - if (use_pty_argv && pty_argv) - for (i = 0; pty_argv[i]; i++) - put_asciz(serialised, pty_argv[i]); - - sprintf(option, "---[%d,%zu]", pipefd[0], serialised->len); - noncloexec(pipefd[0]); - fork_and_exec_self(pipefd[1], option, NULL); - close(pipefd[0]); - - i = ret = 0; - while (i < serialised->len && - (ret = write(pipefd[1], serialised->s + i, - serialised->len - i)) > 0) - i += ret; - if (ret < 0) - perror("write to pipe"); - close(pipefd[1]); - strbuf_free(serialised); -} - -void launch_new_session(void) -{ - fork_and_exec_self(-1, NULL); -} - -void launch_saved_session(const char *str) -{ - fork_and_exec_self(-1, "-load", str, NULL); -} - -int read_dupsession_data(Conf *conf, char *arg) -{ - int fd, i, ret, size; - char *data; - BinarySource src[1]; - - 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); - } - - BinarySource_BARE_INIT(src, data, size); - if (!conf_deserialise(conf, src)) { - fprintf(stderr, "%s: malformed Duplicate Session data\n", appname); - exit(1); - } - if (use_pty_argv) { - int pty_argc = 0; - size_t argv_startpos = src->pos; - - while (get_asciz(src), !get_err(src)) - pty_argc++; - - src->err = BSE_NO_ERROR; - - if (pty_argc > 0) { - src->pos = argv_startpos; - - pty_argv = snewn(pty_argc + 1, char *); - pty_argv[pty_argc] = NULL; - for (i = 0; i < pty_argc; i++) - pty_argv[i] = dupstr(get_asciz(src)); - } - } - - if (get_err(src) || get_avail(src) > 0) { - fprintf(stderr, "%s: malformed Duplicate Session data\n", appname); - exit(1); - } - - sfree(data); - return 0; -} - -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, -sessionlog 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); - } -} - -static void version(FILE *fp) { - char *buildinfo_text = buildinfo("\n"); - if(fprintf(fp, "%s: %s\n%s\n", appname, ver, buildinfo_text) < 0 || - fflush(fp) < 0) { - perror("output error"); - exit(1); - } - sfree(buildinfo_text); -} - -static const char *geometry_string; - -void cmdline_error(const 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); -} - -void window_setup_error(const char *errmsg) -{ - fprintf(stderr, "%s: %s\n", appname, errmsg); - exit(1); -} - -bool do_cmdline(int argc, char **argv, bool do_everything, Conf *conf) -{ - bool err = false; - 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. I use the alternative if (1) - * {...} else ((void)0). - */ -#define EXPECTS_ARG if (1) { \ - if (--argc <= 0) { \ - err = true; \ - fprintf(stderr, "%s: %s expects an argument\n", appname, p); \ - continue; \ - } else \ - val = *++argv; \ - } else ((void)0) -#define SECOND_PASS_ONLY if (1) { \ - if (!do_everything) \ - continue; \ - } else ((void)0) - - while (--argc > 0) { - const 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, conf); - - 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")) { - FontSpec *fs; - EXPECTS_ARG; - SECOND_PASS_ONLY; - fs = fontspec_new(val); - conf_set_fontspec(conf, CONF_font, fs); - fontspec_free(fs); - - } else if (!strcmp(p, "-fb")) { - FontSpec *fs; - EXPECTS_ARG; - SECOND_PASS_ONLY; - fs = fontspec_new(val); - conf_set_fontspec(conf, CONF_boldfont, fs); - fontspec_free(fs); - - } else if (!strcmp(p, "-fw")) { - FontSpec *fs; - EXPECTS_ARG; - SECOND_PASS_ONLY; - fs = fontspec_new(val); - conf_set_fontspec(conf, CONF_widefont, fs); - fontspec_free(fs); - - } else if (!strcmp(p, "-fwb")) { - FontSpec *fs; - EXPECTS_ARG; - SECOND_PASS_ONLY; - fs = fontspec_new(val); - conf_set_fontspec(conf, CONF_wideboldfont, fs); - fontspec_free(fs); - - } else if (!strcmp(p, "-cs")) { - EXPECTS_ARG; - SECOND_PASS_ONLY; - conf_set_str(conf, CONF_line_codepage, val); - - } else if (!strcmp(p, "-geometry")) { - EXPECTS_ARG; - SECOND_PASS_ONLY; - geometry_string = val; - } else if (!strcmp(p, "-sl")) { - EXPECTS_ARG; - SECOND_PASS_ONLY; - conf_set_int(conf, CONF_savelines, atoi(val)); - - } else if (!strcmp(p, "-fg") || !strcmp(p, "-bg") || - !strcmp(p, "-bfg") || !strcmp(p, "-bbg") || - !strcmp(p, "-cfg") || !strcmp(p, "-cbg")) { - EXPECTS_ARG; - SECOND_PASS_ONLY; - - { -#if GTK_CHECK_VERSION(3,0,0) - GdkRGBA rgba; - bool success = gdk_rgba_parse(&rgba, val); -#else - GdkColor col; - bool success = gdk_color_parse(val, &col); -#endif - - if (!success) { - err = true; - fprintf(stderr, "%s: unable to parse colour \"%s\"\n", - appname, val); - } else { -#if GTK_CHECK_VERSION(3,0,0) - int r = rgba.red * 255; - int g = rgba.green * 255; - int b = rgba.blue * 255; -#else - int r = col.red / 256; - int g = col.green / 256; - int b = col.blue / 256; -#endif - - 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); - - conf_set_int_int(conf, CONF_colours, index*3+0, r); - conf_set_int_int(conf, CONF_colours, index*3+1, g); - conf_set_int_int(conf, CONF_colours, index*3+2, b); - } - } - - } 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 = true, fprintf(stderr, "%s: -e expects an argument\n", - appname); - - } else if (!strcmp(p, "-title")) { - EXPECTS_ARG; - SECOND_PASS_ONLY; - conf_set_str(conf, CONF_wintitle, val); - - } else if (!strcmp(p, "-log")) { - Filename *fn; - EXPECTS_ARG; - SECOND_PASS_ONLY; - fn = filename_from_str(val); - conf_set_filename(conf, CONF_logfilename, fn); - conf_set_int(conf, CONF_logtype, LGTYP_DEBUG); - filename_free(fn); - - } else if (!strcmp(p, "-ut-") || !strcmp(p, "+ut")) { - SECOND_PASS_ONLY; - conf_set_bool(conf, CONF_stamp_utmp, false); - - } else if (!strcmp(p, "-ut")) { - SECOND_PASS_ONLY; - conf_set_bool(conf, CONF_stamp_utmp, true); - - } else if (!strcmp(p, "-ls-") || !strcmp(p, "+ls")) { - SECOND_PASS_ONLY; - conf_set_bool(conf, CONF_login_shell, false); - - } else if (!strcmp(p, "-ls")) { - SECOND_PASS_ONLY; - conf_set_bool(conf, CONF_login_shell, true); - - } else if (!strcmp(p, "-nethack")) { - SECOND_PASS_ONLY; - conf_set_bool(conf, CONF_nethack_keypad, true); - - } else if (!strcmp(p, "-sb-") || !strcmp(p, "+sb")) { - SECOND_PASS_ONLY; - conf_set_bool(conf, CONF_scrollbar, false); - - } else if (!strcmp(p, "-sb")) { - SECOND_PASS_ONLY; - conf_set_bool(conf, CONF_scrollbar, true); - - } else if (!strcmp(p, "-name")) { - EXPECTS_ARG; - app_name = val; - - } else if (!strcmp(p, "-xrm")) { - EXPECTS_ARG; - provide_xrm_string(val, appname); - - } else if(!strcmp(p, "-help") || !strcmp(p, "--help")) { - help(stdout); - exit(0); - - } else if(!strcmp(p, "-version") || !strcmp(p, "--version")) { - version(stdout); - exit(0); - - } else if (!strcmp(p, "-pgpfp")) { - pgp_fingerprints(); - exit(1); - - } else if (p[0] != '-') { - /* Non-option arguments not handled by cmdline.c are errors. */ - if (do_everything) { - err = true; - fprintf(stderr, "%s: unexpected non-option argument '%s'\n", - appname, p); - } - - } else { - err = true; - fprintf(stderr, "%s: unrecognized option '%s'\n", appname, p); - } - } - - return err; -} - -GtkWidget *make_gtk_toplevel_window(GtkFrontend *frontend) -{ - return gtk_window_new(GTK_WINDOW_TOPLEVEL); -} - -const bool buildinfo_gtk_relevant = true; - -struct post_initial_config_box_ctx { - Conf *conf; - const char *geometry_string; -}; - -static void post_initial_config_box(void *vctx, int result) -{ - struct post_initial_config_box_ctx ctx = - *(struct post_initial_config_box_ctx *)vctx; - sfree(vctx); - - if (result > 0) { - new_session_window(ctx.conf, ctx.geometry_string); - } else { - /* In this main(), which only runs one session in total, a - * negative result from the initial config box means we simply - * terminate. */ - conf_free(ctx.conf); - gtk_main_quit(); - } -} - -void session_window_closed(void) -{ - gtk_main_quit(); -} - -int main(int argc, char **argv) -{ - Conf *conf; - bool need_config_box; - - setlocale(LC_CTYPE, ""); - - /* Call the function in ux{putty,pterm}.c to do app-type - * specific setup */ - setup(true); /* true means we are a one-session process */ - - progname = argv[0]; - - /* - * Copy the original argv before letting gtk_init fiddle with - * it. It will be required later. - */ - { - int i, oldargc; - gtkargvstart = snewn(argc-1, char *); - for (i = 1; i < argc; i++) - gtkargvstart[i-1] = dupstr(argv[i]); - oldargc = argc; - gtk_init(&argc, &argv); - ngtkargs = oldargc - argc; - } - - conf = conf_new(); - - gtkcomm_setup(); - - /* - * 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. However, we'll have to - * unblock it again when pterm forks. - */ - block_signal(SIGPIPE, true); - - if (argc > 1 && !strncmp(argv[1], "---", 3)) { - read_dupsession_data(conf, argv[1]); - /* Splatter this argument so it doesn't clutter a ps listing */ - smemclr(argv[1], strlen(argv[1])); - - assert(!dup_check_launchable || conf_launchable(conf)); - need_config_box = false; - } else { - if (do_cmdline(argc, argv, false, conf)) - exit(1); /* pre-defaults pass to get -class */ - do_defaults(NULL, conf); - if (do_cmdline(argc, argv, true, conf)) - exit(1); /* post-defaults, do everything */ - - cmdline_run_saved(conf); - - if (cmdline_tooltype & TOOLTYPE_HOST_ARG) - need_config_box = !cmdline_host_ok(conf); - else - need_config_box = false; - } - - if (need_config_box) { - /* - * Put up the initial config box, which will pass the provided - * parameters (with conf updated) to new_session_window() when - * (if) the user selects Open. Or it might close without - * creating a session window, if the user selects Cancel. Or - * it might just create the session window immediately if this - * is a pterm-style app which doesn't have an initial config - * box at all. - */ - struct post_initial_config_box_ctx *ctx = - snew(struct post_initial_config_box_ctx); - ctx->conf = conf; - ctx->geometry_string = geometry_string; - initial_config_box(conf, post_initial_config_box, ctx); - } else { - /* - * No initial config needed; just create the session window - * now. - */ - new_session_window(conf, geometry_string); - } - - gtk_main(); - - return 0; -} diff --git a/unix/gtkwin.c b/unix/gtkwin.c deleted file mode 100644 index 2a2353d4..00000000 --- a/unix/gtkwin.c +++ /dev/null @@ -1,5431 +0,0 @@ -/* - * gtkwin.c: the main code that runs a PuTTY terminal emulator and - * backend in a GTK window. - */ - -#define _GNU_SOURCE - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#if !GTK_CHECK_VERSION(3,0,0) -#include -#endif - -#if GTK_CHECK_VERSION(2,0,0) -#include -#endif - -#define MAY_REFER_TO_GTK_IN_HEADERS - -#include "putty.h" -#include "terminal.h" -#include "gtkcompat.h" -#include "gtkfont.h" -#include "gtkmisc.h" - -#ifndef NOT_X_WINDOWS -#include -#include -#include -#include -#endif - -#include "x11misc.h" - -GdkAtom compound_text_atom, utf8_string_atom; -static GdkAtom clipboard_atom -#if GTK_CHECK_VERSION(2,0,0) /* GTK1 will have to fill this in at startup */ - = GDK_SELECTION_CLIPBOARD -#endif - ; - -#ifdef JUST_USE_GTK_CLIPBOARD_UTF8 -/* - * Because calling gtk_clipboard_set_with_data triggers a call to the - * clipboard_clear function from the last time, we need to arrange a - * way to distinguish a real call to clipboard_clear for the _new_ - * instance of the clipboard data from the leftover call for the - * outgoing one. We do this by setting the user data field in our - * gtk_clipboard_set_with_data() call, instead of the obvious pointer - * to 'inst', to one of these. - */ -struct clipboard_data_instance { - char *pasteout_data_utf8; - int pasteout_data_utf8_len; - struct clipboard_state *state; - struct clipboard_data_instance *next, *prev; -}; -#endif - -struct clipboard_state { - GtkFrontend *inst; - int clipboard; - GdkAtom atom; -#ifdef JUST_USE_GTK_CLIPBOARD_UTF8 - GtkClipboard *gtkclipboard; - struct clipboard_data_instance *current_cdi; -#else - char *pasteout_data, *pasteout_data_ctext, *pasteout_data_utf8; - int pasteout_data_len, pasteout_data_ctext_len, pasteout_data_utf8_len; -#endif -}; - -typedef struct XpmHolder XpmHolder; /* only used for GTK 1 */ - -struct GtkFrontend { - GtkWidget *window, *area, *sbar; - gboolean sbar_visible; - gboolean drawing_area_got_size, drawing_area_realised; - gboolean drawing_area_setup_needed; - GtkBox *hbox; - GtkAdjustment *sbar_adjust; - GtkWidget *menu, *specialsmenu, *specialsitem1, *specialsitem2, - *restartitem; - GtkWidget *sessionsmenu; -#ifndef NOT_X_WINDOWS - Display *disp; -#endif -#ifndef NO_BACKING_PIXMAPS - /* - * Server-side pixmap which we use to cache the terminal window's - * contents. When we draw text in the terminal, we draw it to this - * pixmap first, and then blit from there to the actual window; - * this way, X expose events can be handled with an absolute - * minimum of network traffic, by just sending a command to - * re-blit an appropriate rectangle from this pixmap. - */ - GdkPixmap *pixmap; -#endif -#ifdef DRAW_TEXT_CAIRO - /* - * If we're drawing using Cairo, we cache the same image on the - * client side in a Cairo surface. - * - * In GTK2+Cairo, this happens _as well_ as having the server-side - * pixmap cache above; in GTK3+Cairo, server-side pixmaps are - * deprecated, so we _just_ have this client-side cache. In the - * latter case that means we have to transmit a big wodge of - * bitmap data over the X connection on every expose event; but - * GTK3 apparently deliberately provides no way to avoid that - * inefficiency, and at least this way we don't _also_ have to - * redo any font rendering just because the window was temporarily - * covered. - */ - cairo_surface_t *surface; -#endif - int backing_w, backing_h; -#if GTK_CHECK_VERSION(2,0,0) - GtkIMContext *imc; -#endif - unifont *fonts[4]; /* normal, bold, wide, widebold */ - int xpos, ypos, gravity; - bool gotpos; - GdkCursor *rawcursor, *textcursor, *blankcursor, *waitcursor, *currcursor; - GdkColor cols[OSC4_NCOLOURS]; /* indexed by xterm colour indices */ -#if !GTK_CHECK_VERSION(3,0,0) - GdkColormap *colmap; -#endif - bool direct_to_font; - struct clipboard_state clipstates[N_CLIPBOARDS]; -#ifdef JUST_USE_GTK_CLIPBOARD_UTF8 - /* Remember all clipboard_data_instance structures currently - * associated with this GtkFrontend, in case they're still around - * when it gets destroyed */ - struct clipboard_data_instance cdi_headtail; -#endif - int clipboard_ctrlshiftins, clipboard_ctrlshiftcv; - int font_width, font_height; - int width, height, scale; - bool ignore_sbar; - bool mouseptr_visible; - BusyStatus busy_status; - int alt_keycode; - int alt_digits; - char *wintitle; - char *icontitle; - int master_fd, master_func_id; - Ldisc *ldisc; - Backend *backend; - Terminal *term; - LogContext *logctx; - bool exited; - struct unicode_data ucsdata; - Conf *conf; - eventlog_stuff *eventlogstuff; - guint32 input_event_time; /* Timestamp of the most recent input event. */ - GtkWidget *dialogs[DIALOG_SLOT_LIMIT]; -#if GTK_CHECK_VERSION(3,4,0) - gdouble cumulative_scroll; -#endif - /* Cached things out of conf that we refer to a lot */ - int bold_style; - int window_border; - int cursor_type; - int drawtype; - int meta_mod_mask; -#ifdef OSX_META_KEY_CONFIG - int system_mod_mask; -#endif - bool send_raw_mouse; - bool pointer_indicates_raw_mouse; - unifont_drawctx uctx; -#if GTK_CHECK_VERSION(2,0,0) - GdkPixbuf *trust_sigil_pb; -#else - GdkPixmap *trust_sigil_pm; -#endif - int trust_sigil_w, trust_sigil_h; - - Seat seat; - TermWin termwin; - LogPolicy logpolicy; -}; - -static void cache_conf_values(GtkFrontend *inst) -{ - inst->bold_style = conf_get_int(inst->conf, CONF_bold_style); - inst->window_border = conf_get_int(inst->conf, CONF_window_border); - inst->cursor_type = conf_get_int(inst->conf, CONF_cursor_type); -#ifdef OSX_META_KEY_CONFIG - inst->meta_mod_mask = 0; - if (conf_get_bool(inst->conf, CONF_osx_option_meta)) - inst->meta_mod_mask |= GDK_MOD1_MASK; - if (conf_get_bool(inst->conf, CONF_osx_command_meta)) - inst->meta_mod_mask |= GDK_MOD2_MASK; - inst->system_mod_mask = GDK_MOD2_MASK & ~inst->meta_mod_mask; -#else - inst->meta_mod_mask = GDK_MOD1_MASK; -#endif -} - -static void start_backend(GtkFrontend *inst); -static void exit_callback(void *vinst); -static void destroy_inst_connection(GtkFrontend *inst); -static void delete_inst(GtkFrontend *inst); - -static void post_fatal_message_box_toplevel(void *vctx) -{ - GtkFrontend *inst = (GtkFrontend *)vctx; - gtk_widget_destroy(inst->window); -} - -static void post_fatal_message_box(void *vctx, int result) -{ - GtkFrontend *inst = (GtkFrontend *)vctx; - unregister_dialog(&inst->seat, DIALOG_SLOT_CONNECTION_FATAL); - queue_toplevel_callback(post_fatal_message_box_toplevel, inst); -} - -static void common_connfatal_message_box( - GtkFrontend *inst, const char *msg, post_dialog_fn_t postfn) -{ - char *title = dupcat(appname, " Fatal Error"); - GtkWidget *dialog = create_message_box( - inst->window, title, msg, - string_width("REASONABLY LONG LINE OF TEXT FOR BASIC SANITY"), - false, &buttons_ok, postfn, inst); - register_dialog(&inst->seat, DIALOG_SLOT_CONNECTION_FATAL, dialog); - sfree(title); -} - -void fatal_message_box(GtkFrontend *inst, const char *msg) -{ - common_connfatal_message_box(inst, msg, post_fatal_message_box); -} - -static void connection_fatal_callback(void *vctx) -{ - GtkFrontend *inst = (GtkFrontend *)vctx; - destroy_inst_connection(inst); -} - -static void post_nonfatal_message_box(void *vctx, int result) -{ - GtkFrontend *inst = (GtkFrontend *)vctx; - unregister_dialog(&inst->seat, DIALOG_SLOT_CONNECTION_FATAL); -} - -static void gtk_seat_connection_fatal(Seat *seat, const char *msg) -{ - GtkFrontend *inst = container_of(seat, GtkFrontend, seat); - if (conf_get_int(inst->conf, CONF_close_on_exit) == FORCE_ON) { - fatal_message_box(inst, msg); - } else { - common_connfatal_message_box(inst, msg, post_nonfatal_message_box); - } - - inst->exited = true; /* suppress normal exit handling */ - queue_toplevel_callback(connection_fatal_callback, inst); -} - -/* - * Default settings that are specific to pterm. - */ -FontSpec *platform_default_fontspec(const char *name) -{ - if (!strcmp(name, "Font")) - return fontspec_new(DEFAULT_GTK_FONT); - else - return fontspec_new(""); -} - -Filename *platform_default_filename(const char *name) -{ - if (!strcmp(name, "LogFileName")) - return filename_from_str("putty.log"); - else - return filename_from_str(""); -} - -char *platform_default_s(const char *name) -{ - if (!strcmp(name, "SerialLine")) - return dupstr("/dev/ttyS0"); - return NULL; -} - -bool platform_default_b(const char *name, bool def) -{ - if (!strcmp(name, "WinNameAlways")) { - /* X natively supports icon titles, so use 'em by default */ - return false; - } - return def; -} - -int platform_default_i(const char *name, int def) -{ - if (!strcmp(name, "CloseOnExit")) - return 2; /* maps to FORCE_ON after painful rearrangement :-( */ - return def; -} - -static char *gtk_seat_get_ttymode(Seat *seat, const char *mode) -{ - GtkFrontend *inst = container_of(seat, GtkFrontend, seat); - return term_get_ttymode(inst->term, mode); -} - -static size_t gtk_seat_output(Seat *seat, bool is_stderr, - const void *data, size_t len) -{ - GtkFrontend *inst = container_of(seat, GtkFrontend, seat); - return term_data(inst->term, is_stderr, data, len); -} - -static bool gtk_seat_eof(Seat *seat) -{ - /* GtkFrontend *inst = container_of(seat, GtkFrontend, seat); */ - return true; /* do respond to incoming EOF with outgoing */ -} - -static int gtk_seat_get_userpass_input(Seat *seat, prompts_t *p, - bufchain *input) -{ - GtkFrontend *inst = container_of(seat, GtkFrontend, seat); - int ret; - ret = cmdline_get_passwd_input(p); - if (ret == -1) - ret = term_get_userpass_input(inst->term, p, input); - return ret; -} - -static bool gtk_seat_is_utf8(Seat *seat) -{ - GtkFrontend *inst = container_of(seat, GtkFrontend, seat); - return inst->ucsdata.line_codepage == CS_UTF8; -} - -static void get_window_pixel_size(GtkFrontend *inst, int *w, int *h) -{ - /* - * 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), w, h); -#else - gdk_window_get_size(gtk_widget_get_window(inst->window), w, h); -#endif -} - -static bool gtk_seat_get_window_pixel_size(Seat *seat, int *w, int *h) -{ - GtkFrontend *inst = container_of(seat, GtkFrontend, seat); - get_window_pixel_size(inst, w, h); - return true; -} - -StripCtrlChars *gtk_seat_stripctrl_new( - Seat *seat, BinarySink *bs_out, SeatInteractionContext sic) -{ - GtkFrontend *inst = container_of(seat, GtkFrontend, seat); - return stripctrl_new_term(bs_out, false, 0, inst->term); -} - -static void gtk_seat_notify_remote_exit(Seat *seat); -static void gtk_seat_update_specials_menu(Seat *seat); -static void gtk_seat_set_busy_status(Seat *seat, BusyStatus status); -static const char *gtk_seat_get_x_display(Seat *seat); -#ifndef NOT_X_WINDOWS -static bool gtk_seat_get_windowid(Seat *seat, long *id); -#endif -static bool gtk_seat_set_trust_status(Seat *seat, bool trusted); -static bool gtk_seat_get_cursor_position(Seat *seat, int *x, int *y); - -static const SeatVtable gtk_seat_vt = { - .output = gtk_seat_output, - .eof = gtk_seat_eof, - .get_userpass_input = gtk_seat_get_userpass_input, - .notify_remote_exit = gtk_seat_notify_remote_exit, - .connection_fatal = gtk_seat_connection_fatal, - .update_specials_menu = gtk_seat_update_specials_menu, - .get_ttymode = gtk_seat_get_ttymode, - .set_busy_status = gtk_seat_set_busy_status, - .verify_ssh_host_key = gtk_seat_verify_ssh_host_key, - .confirm_weak_crypto_primitive = gtk_seat_confirm_weak_crypto_primitive, - .confirm_weak_cached_hostkey = gtk_seat_confirm_weak_cached_hostkey, - .is_utf8 = gtk_seat_is_utf8, - .echoedit_update = nullseat_echoedit_update, - .get_x_display = gtk_seat_get_x_display, -#ifdef NOT_X_WINDOWS - .get_windowid = nullseat_get_windowid, -#else - .get_windowid = gtk_seat_get_windowid, -#endif - .get_window_pixel_size = gtk_seat_get_window_pixel_size, - .stripctrl_new = gtk_seat_stripctrl_new, - .set_trust_status = gtk_seat_set_trust_status, - .verbose = nullseat_verbose_yes, - .interactive = nullseat_interactive_yes, - .get_cursor_position = gtk_seat_get_cursor_position, -}; - -static void gtk_eventlog(LogPolicy *lp, const char *string) -{ - GtkFrontend *inst = container_of(lp, GtkFrontend, logpolicy); - logevent_dlg(inst->eventlogstuff, string); -} - -static int gtk_askappend(LogPolicy *lp, Filename *filename, - void (*callback)(void *ctx, int result), void *ctx) -{ - GtkFrontend *inst = container_of(lp, GtkFrontend, logpolicy); - return gtkdlg_askappend(&inst->seat, filename, callback, ctx); -} - -static void gtk_logging_error(LogPolicy *lp, const char *event) -{ - GtkFrontend *inst = container_of(lp, GtkFrontend, logpolicy); - - /* Send 'can't open log file' errors to the terminal window. - * (Marked as stderr, although terminal.c won't care.) */ - seat_stderr_pl(&inst->seat, ptrlen_from_asciz(event)); - seat_stderr_pl(&inst->seat, PTRLEN_LITERAL("\r\n")); -} - -static const LogPolicyVtable gtk_logpolicy_vt = { - .eventlog = gtk_eventlog, - .askappend = gtk_askappend, - .logging_error = gtk_logging_error, - .verbose = null_lp_verbose_yes, -}; - -/* - * 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) -{ - 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. - */ -GtkWidget *gtk_seat_get_window(Seat *seat) -{ - GtkFrontend *inst = container_of(seat, GtkFrontend, seat); - return inst->window; -} - -/* - * Set and clear a pointer to a dialog box created as a result of the - * network code wanting to ask an asynchronous user question (e.g. - * 'what about this dodgy host key, then?'). - */ -void register_dialog(Seat *seat, enum DialogSlot slot, GtkWidget *dialog) -{ - GtkFrontend *inst; - assert(seat->vt == >k_seat_vt); - inst = container_of(seat, GtkFrontend, seat); - assert(slot < DIALOG_SLOT_LIMIT); - assert(!inst->dialogs[slot]); - inst->dialogs[slot] = dialog; -} -void unregister_dialog(Seat *seat, enum DialogSlot slot) -{ - GtkFrontend *inst; - assert(seat->vt == >k_seat_vt); - inst = container_of(seat, GtkFrontend, seat); - assert(slot < DIALOG_SLOT_LIMIT); - assert(inst->dialogs[slot]); - inst->dialogs[slot] = NULL; -} - -/* - * Minimise or restore the window in response to a server-side - * request. - */ -static void gtkwin_set_minimised(TermWin *tw, bool minimised) -{ - /* - * GTK 1.2 doesn't know how to do this. - */ -#if GTK_CHECK_VERSION(2,0,0) - GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); - if (minimised) - 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. - */ -static void gtkwin_move(TermWin *tw, int x, int y) -{ - GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); - /* - * 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) - /* in case we reset this at startup due to a geometry string */ - gtk_window_set_gravity(GTK_WINDOW(inst->window), GDK_GRAVITY_NORTH_EAST); - gtk_window_move(GTK_WINDOW(inst->window), x, y); -#else - gdk_window_move(gtk_widget_get_window(inst->window), x, y); -#endif -} - -/* - * Move the window to the top or bottom of the z-order in response - * to a server-side request. - */ -static void gtkwin_set_zorder(TermWin *tw, bool top) -{ - GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); - if (top) - gdk_window_raise(gtk_widget_get_window(inst->window)); - else - gdk_window_lower(gtk_widget_get_window(inst->window)); -} - -/* - * Refresh the window in response to a server-side request. - */ -static void gtkwin_refresh(TermWin *tw) -{ - GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); - term_invalidate(inst->term); -} - -/* - * Maximise or restore the window in response to a server-side - * request. - */ -static void gtkwin_set_maximised(TermWin *tw, bool maximised) -{ - /* - * GTK 1.2 doesn't know how to do this. - */ -#if GTK_CHECK_VERSION(2,0,0) - GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); - if (maximised) - gtk_window_maximize(GTK_WINDOW(inst->window)); - else - gtk_window_unmaximize(GTK_WINDOW(inst->window)); -#endif -} - -/* - * Find out whether a dialog box already exists for this window in a - * particular DialogSlot. If it does, uniconify it (if we can) and - * raise it, so that the user realises they've already been asked this - * question. - */ -static bool find_and_raise_dialog(GtkFrontend *inst, enum DialogSlot slot) -{ - GtkWidget *dialog = inst->dialogs[slot]; - if (!dialog) - return false; - -#if GTK_CHECK_VERSION(2,0,0) - gtk_window_deiconify(GTK_WINDOW(dialog)); -#endif - gdk_window_raise(gtk_widget_get_window(dialog)); - return true; -} - -static void warn_on_close_callback(void *vctx, int result) -{ - GtkFrontend *inst = (GtkFrontend *)vctx; - unregister_dialog(&inst->seat, DIALOG_SLOT_WARN_ON_CLOSE); - if (result) - gtk_widget_destroy(inst->window); -} - -/* - * Handle the 'delete window' event (e.g. user clicking the WM close - * button). The return value false means the window should close, and - * true means it shouldn't. - * - * (That's counterintuitive, but really, in GTK terms, true means 'I - * have done everything necessary to handle this event, so the default - * handler need not do anything', i.e. 'suppress default handler', - * i.e. 'do not close the window'.) - */ -gint delete_window(GtkWidget *widget, GdkEvent *event, GtkFrontend *inst) -{ - if (!inst->exited && conf_get_bool(inst->conf, CONF_warn_on_close)) { - /* - * We're not going to exit right now. We must put up a - * warn-on-close dialog, unless one already exists, in which - * case we'll just re-emphasise that one. - */ - if (!find_and_raise_dialog(inst, DIALOG_SLOT_WARN_ON_CLOSE)) { - char *title = dupcat(appname, " Exit Confirmation"); - char *msg, *additional = NULL; - if (inst->backend && inst->backend->vt->close_warn_text) { - additional = inst->backend->vt->close_warn_text(inst->backend); - } - msg = dupprintf("Are you sure you want to close this session?%s%s", - additional ? "\n" : "", - additional ? additional : ""); - GtkWidget *dialog = create_message_box( - inst->window, title, msg, - string_width("Most of the width of the above text"), - false, &buttons_yn, warn_on_close_callback, inst); - register_dialog(&inst->seat, DIALOG_SLOT_WARN_ON_CLOSE, dialog); - sfree(title); - sfree(msg); - sfree(additional); - } - return true; - } - return false; -} - -#if GTK_CHECK_VERSION(2,0,0) -static void window_state_event(GtkWidget *widget, GdkEventWindowState *event, - gpointer user_data) -{ - GtkFrontend *inst = (GtkFrontend *)user_data; - term_notify_minimised( - inst->term, event->new_window_state & GDK_WINDOW_STATE_ICONIFIED); -} -#endif - -static void update_mouseptr(GtkFrontend *inst) -{ - switch (inst->busy_status) { - case BUSY_NOT: - if (!inst->mouseptr_visible) { - gdk_window_set_cursor(gtk_widget_get_window(inst->area), - inst->blankcursor); - } else if (inst->pointer_indicates_raw_mouse) { - gdk_window_set_cursor(gtk_widget_get_window(inst->area), - inst->rawcursor); - } else { - gdk_window_set_cursor(gtk_widget_get_window(inst->area), - inst->textcursor); - } - break; - case BUSY_WAITING: /* XXX can we do better? */ - case BUSY_CPU: - /* We always display these cursors. */ - gdk_window_set_cursor(gtk_widget_get_window(inst->area), - inst->waitcursor); - break; - default: - unreachable("Bad busy_status"); - } -} - -static void show_mouseptr(GtkFrontend *inst, bool show) -{ - if (!conf_get_bool(inst->conf, CONF_hide_mouseptr)) - show = true; - inst->mouseptr_visible = show; - update_mouseptr(inst); -} - -static void draw_backing_rect(GtkFrontend *inst); - -static void drawing_area_setup(GtkFrontend *inst, int width, int height) -{ - int w, h, new_scale; - bool need_size = false; - - /* - * See if the terminal size has changed. - */ - w = (width - 2*inst->window_border) / inst->font_width; - h = (height - 2*inst->window_border) / inst->font_height; - if (w != inst->width || h != inst->height) { - /* - * Update conf. - */ - inst->width = w; - inst->height = h; - conf_set_int(inst->conf, CONF_width, inst->width); - conf_set_int(inst->conf, CONF_height, inst->height); - /* - * We'll need to tell terminal.c about the resize below. - */ - need_size = true; - /* - * And we must refresh the window's backing image. - */ - inst->drawing_area_setup_needed = true; - } - -#if GTK_CHECK_VERSION(3,10,0) - new_scale = gtk_widget_get_scale_factor(inst->area); - if (new_scale != inst->scale) - inst->drawing_area_setup_needed = true; -#else - new_scale = 1; -#endif - - int new_backing_w = w * inst->font_width + 2*inst->window_border; - int new_backing_h = h * inst->font_height + 2*inst->window_border; - new_backing_w *= new_scale; - new_backing_h *= new_scale; - - if (inst->backing_w != new_backing_w || inst->backing_h != new_backing_h) - inst->drawing_area_setup_needed = true; - - /* - * This event might be spurious; some GTK setups have been known - * to call it when nothing at all has changed. Check if we have - * any reason to proceed. - */ - if (!inst->drawing_area_setup_needed) - return; - - inst->drawing_area_setup_needed = false; - inst->scale = new_scale; - inst->backing_w = new_backing_w; - inst->backing_h = new_backing_h; - -#ifndef NO_BACKING_PIXMAPS - if (inst->pixmap) { - gdk_pixmap_unref(inst->pixmap); - inst->pixmap = NULL; - } - - inst->pixmap = gdk_pixmap_new(gtk_widget_get_window(inst->area), - inst->backing_w, inst->backing_h, -1); -#endif - -#ifdef DRAW_TEXT_CAIRO - if (inst->surface) { - cairo_surface_destroy(inst->surface); - inst->surface = NULL; - } - - inst->surface = cairo_image_surface_create( - CAIRO_FORMAT_ARGB32, inst->backing_w, inst->backing_h); -#endif - - draw_backing_rect(inst); - - if (need_size && inst->term) { - term_size(inst->term, h, w, conf_get_int(inst->conf, CONF_savelines)); - } - - if (inst->term) - term_invalidate(inst->term); - -#if GTK_CHECK_VERSION(2,0,0) - gtk_im_context_set_client_window( - inst->imc, gtk_widget_get_window(inst->area)); -#endif -} - -static void drawing_area_setup_simple(GtkFrontend *inst) -{ - /* - * Wrapper on drawing_area_setup which fetches the width and - * height of the drawing area. We go directly to the inner version - * in the case where a new size allocation comes in (just in case - * GTK hasn't installed it in the normal place yet). - */ -#if GTK_CHECK_VERSION(2,0,0) - GdkRectangle alloc; - gtk_widget_get_allocation(inst->area, &alloc); -#else - GtkAllocation alloc = inst->area->allocation; -#endif - drawing_area_setup(inst, alloc.width, alloc.height); -} - -static void area_realised(GtkWidget *widget, GtkFrontend *inst) -{ - inst->drawing_area_realised = true; - if (inst->drawing_area_realised && inst->drawing_area_got_size && - inst->drawing_area_setup_needed) - drawing_area_setup_simple(inst); -} - -static void area_size_allocate( - GtkWidget *widget, GdkRectangle *alloc, GtkFrontend *inst) -{ - inst->drawing_area_got_size = true; - if (inst->drawing_area_realised && inst->drawing_area_got_size) - drawing_area_setup(inst, alloc->width, alloc->height); -} - -#if GTK_CHECK_VERSION(3,10,0) -static void area_check_scale(GtkFrontend *inst) -{ - if (!inst->drawing_area_setup_needed && - inst->scale != gtk_widget_get_scale_factor(inst->area)) { - drawing_area_setup_simple(inst); - if (inst->term) { - term_invalidate(inst->term); - term_update(inst->term); - } - } -} -#endif - -static gboolean window_configured( - GtkWidget *widget, GdkEventConfigure *event, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - if (inst->term) { - term_notify_window_pos(inst->term, event->x, event->y); - term_notify_window_size_pixels( - inst->term, event->width, event->height); - } - return false; -} - -#if GTK_CHECK_VERSION(3,10,0) -static gboolean area_configured( - GtkWidget *widget, GdkEventConfigure *event, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - area_check_scale(inst); - return false; -} -#endif - -#ifdef DRAW_TEXT_CAIRO -static void cairo_setup_draw_ctx(GtkFrontend *inst) -{ - cairo_get_matrix(inst->uctx.u.cairo.cr, - &inst->uctx.u.cairo.origmatrix); - cairo_set_line_width(inst->uctx.u.cairo.cr, 1.0); - cairo_set_line_cap(inst->uctx.u.cairo.cr, CAIRO_LINE_CAP_SQUARE); - cairo_set_line_join(inst->uctx.u.cairo.cr, CAIRO_LINE_JOIN_MITER); - /* This antialiasing setting appears to be ignored for Pango - * font rendering but honoured for stroking and filling paths; - * I don't quite understand the logic of that, but I won't - * complain since it's exactly what I happen to want */ - cairo_set_antialias(inst->uctx.u.cairo.cr, CAIRO_ANTIALIAS_NONE); -} -#endif - -#if GTK_CHECK_VERSION(3,0,0) -static gint draw_area(GtkWidget *widget, cairo_t *cr, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - -#if GTK_CHECK_VERSION(3,10,0) - /* - * This may be the first we hear of the window scale having - * changed, in which case we must hastily reconstruct our backing - * surface before we copy the wrong one into the newly resized - * real window. - */ - area_check_scale(inst); -#endif - - /* - * GTK3 window redraw: we always expect Cairo to be enabled, so - * that inst->surface exists, and pixmaps to be disabled, so that - * inst->pixmap does not exist. Hence, we just blit from - * inst->surface to the window. - */ - if (inst->surface) { - GdkRectangle dirtyrect; - cairo_surface_t *target_surface; - double orig_sx, orig_sy; - cairo_matrix_t m; - - /* - * Furtle around in the Cairo setup to force the device scale - * back to 1, so that when we blit a collection of pixels from - * our backing surface into the window, they really are - * _pixels_ and not some confusing antialiased slightly-offset - * 2x2 rectangle of pixeloids. - * - * I have no idea whether GTK expects me not to mess with the - * device scale in the cairo_surface_t backing its window, so - * I carefully put it back when I've finished. - * - * In some GTK setups, the Cairo context we're given may not - * have a zero translation offset in its matrix, in which case - * we have to adjust that to compensate for the change of - * scale, or else the old translation offset (designed for the - * old scale) will be multiplied by the new scale instead and - * put everything in the wrong place. - */ - target_surface = cairo_get_target(cr); - cairo_get_matrix(cr, &m); - cairo_surface_get_device_scale(target_surface, &orig_sx, &orig_sy); - cairo_surface_set_device_scale(target_surface, 1.0, 1.0); - cairo_translate(cr, m.x0 * (orig_sx - 1.0), m.y0 * (orig_sy - 1.0)); - - gdk_cairo_get_clip_rectangle(cr, &dirtyrect); - - cairo_set_source_surface(cr, inst->surface, 0, 0); - cairo_rectangle(cr, dirtyrect.x, dirtyrect.y, - dirtyrect.width, dirtyrect.height); - cairo_fill(cr); - - cairo_surface_set_device_scale(target_surface, orig_sx, orig_sy); - } - - return true; -} -#else -gint expose_area(GtkWidget *widget, GdkEventExpose *event, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - -#ifndef NO_BACKING_PIXMAPS - /* - * Draw to the exposed part of the window from the server-side - * backing pixmap. - */ - if (inst->pixmap) { - gdk_draw_pixmap(gtk_widget_get_window(widget), - (gtk_widget_get_style(widget)->fg_gc - [gtk_widget_get_state(widget)]), - inst->pixmap, - event->area.x, event->area.y, - event->area.x, event->area.y, - event->area.width, event->area.height); - } -#else - /* - * Failing that, draw from the client-side Cairo surface. (We - * should never be compiled in a context where we have _neither_ - * inst->surface nor inst->pixmap.) - */ - if (inst->surface) { - cairo_t *cr = gdk_cairo_create(gtk_widget_get_window(widget)); - cairo_set_source_surface(cr, inst->surface, 0, 0); - cairo_rectangle(cr, event->area.x, event->area.y, - event->area.width, event->area.height); - cairo_fill(cr); - cairo_destroy(cr); - } -#endif - - return true; -} -#endif - -#define KEY_PRESSED(k) \ - (inst->keystate[(k) / 32] & (1 << ((k) % 32))) - -#ifdef KEY_EVENT_DIAGNOSTICS -char *dup_keyval_name(guint keyval) -{ - const char *name = gdk_keyval_name(keyval); - if (name) - return dupstr(name); - else - return dupprintf("UNKNOWN[%u]", (unsigned)keyval); -} -#endif - -static void change_font_size(GtkFrontend *inst, int increment); -static void key_pressed(GtkFrontend *inst); - -/* Subroutine used in key_event */ -static int return_key(GtkFrontend *inst, char *output, bool *special) -{ - int end; - - /* Ugly label so we can come here as a fallback from - * numeric keypad Enter handling */ - if (inst->term->cr_lf_return) { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Return in cr_lf_return mode, translating as 0d 0a\n"); -#endif - output[1] = '\015'; - output[2] = '\012'; - end = 3; - } else { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Return special case, translating as 0d + special\n"); -#endif - output[1] = '\015'; - end = 2; - *special = true; - } - - return end; -} - -gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - char output[256]; - wchar_t ucsoutput[2]; - int ucsval, start, end, output_charset; - bool special, use_ucsoutput; - bool force_format_numeric_keypad = false; - bool generated_something = false; - char num_keypad_key = '\0'; - const char *event_string = event->string ? event->string : ""; - - noise_ultralight(NOISE_SOURCE_KEY, event->keyval); - -#ifdef OSX_META_KEY_CONFIG - if (event->state & inst->system_mod_mask) - return false; /* let GTK process OS X Command key */ -#endif - - /* 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; - -#ifdef KEY_EVENT_DIAGNOSTICS - { - char *type_string, *state_string, *keyval_string, *string_string; - - type_string = (event->type == GDK_KEY_PRESS ? dupstr("PRESS") : - event->type == GDK_KEY_RELEASE ? dupstr("RELEASE") : - dupprintf("UNKNOWN[%d]", (int)event->type)); - - { - static const struct { - int mod_bit; - const char *name; - } mod_bits[] = { - {GDK_SHIFT_MASK, "SHIFT"}, - {GDK_LOCK_MASK, "LOCK"}, - {GDK_CONTROL_MASK, "CONTROL"}, - {GDK_MOD1_MASK, "MOD1"}, - {GDK_MOD2_MASK, "MOD2"}, - {GDK_MOD3_MASK, "MOD3"}, - {GDK_MOD4_MASK, "MOD4"}, - {GDK_MOD5_MASK, "MOD5"}, - {GDK_SUPER_MASK, "SUPER"}, - {GDK_HYPER_MASK, "HYPER"}, - {GDK_META_MASK, "META"}, - }; - int i; - int val = event->state; - - state_string = dupstr(""); - - for (i = 0; i < lenof(mod_bits); i++) { - if (val & mod_bits[i].mod_bit) { - char *old = state_string; - state_string = dupcat(state_string, - state_string[0] ? "|" : "", - mod_bits[i].name); - sfree(old); - - val &= ~mod_bits[i].mod_bit; - } - } - - if (val || !state_string[0]) { - char *old = state_string; - state_string = dupprintf("%s%s%d", state_string, - state_string[0] ? "|" : "", val); - sfree(old); - } - } - - keyval_string = dup_keyval_name(event->keyval); - - string_string = dupstr(""); - { - int i; - for (i = 0; event_string[i]; i++) { - char *old = string_string; - string_string = dupprintf("%s%s%02x", string_string, - string_string[0] ? " " : "", - (unsigned)event_string[i] & 0xFF); - sfree(old); - } - } - - debug("key_event: type=%s keyval=%s state=%s " - "hardware_keycode=%d is_modifier=%s string=[%s]\n", - type_string, keyval_string, state_string, - (int)event->hardware_keycode, - event->is_modifier ? "true" : "false", - string_string); - - sfree(type_string); - sfree(state_string); - sfree(keyval_string); - sfree(string_string); - } -#endif /* KEY_EVENT_DIAGNOSTICS */ - - /* - * 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) { - if ((event->keyval == GDK_KEY_Meta_L || - event->keyval == GDK_KEY_Meta_R || - event->keyval == GDK_KEY_Alt_L || - event->keyval == GDK_KEY_Alt_R) && - inst->alt_keycode >= 0 && inst->alt_digits > 1) { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - modifier release terminates Alt+numberpad input, " - "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 GTK_CHECK_VERSION(2,0,0) -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - key release, passing to IM\n"); -#endif - if (gtk_im_context_filter_keypress(inst->imc, event)) { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - key release accepted by IM\n"); -#endif - return true; - } else { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - key release not accepted by IM\n"); -#endif - } -#endif - } - - if (event->type == GDK_KEY_PRESS) { - /* - * 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_KEY_Meta_L || - event->keyval == GDK_KEY_Meta_R || - event->keyval == GDK_KEY_Alt_L || - event->keyval == GDK_KEY_Alt_R)) { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - modifier press potentially begins Alt+numberpad " - "input\n"); -#endif - inst->alt_keycode = -1; - inst->alt_digits = 0; - goto done; /* this generates nothing else */ - } - - /* - * If we're seeing a numberpad key press with Meta down, - * consider adding it to alt_keycode if that's sensible. - * Anything _else_ with Meta down cancels any possibility - * of an ALT keycode: we set alt_keycode to -2. - */ - if ((event->state & inst->meta_mod_mask) && inst->alt_keycode != -2) { - int digit = -1; - switch (event->keyval) { - case GDK_KEY_KP_0: case GDK_KEY_KP_Insert: digit = 0; break; - case GDK_KEY_KP_1: case GDK_KEY_KP_End: digit = 1; break; - case GDK_KEY_KP_2: case GDK_KEY_KP_Down: digit = 2; break; - case GDK_KEY_KP_3: case GDK_KEY_KP_Page_Down: digit = 3; break; - case GDK_KEY_KP_4: case GDK_KEY_KP_Left: digit = 4; break; - case GDK_KEY_KP_5: case GDK_KEY_KP_Begin: digit = 5; break; - case GDK_KEY_KP_6: case GDK_KEY_KP_Right: digit = 6; break; - case GDK_KEY_KP_7: case GDK_KEY_KP_Home: digit = 7; break; - case GDK_KEY_KP_8: case GDK_KEY_KP_Up: digit = 8; break; - case GDK_KEY_KP_9: case GDK_KEY_KP_Page_Up: digit = 9; break; - } - if (digit < 0) - inst->alt_keycode = -2; /* it's invalid */ - else { -#if defined(DEBUG) && defined(KEY_EVENT_DIAGNOSTICS) - int old_keycode = 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_EVENT_DIAGNOSTICS - debug(" - Alt+numberpad digit %d added to keycode %d" - " gives %d\n", digit, old_keycode, inst->alt_keycode); -#endif - /* Having used this digit, we now do nothing more with it. */ - goto done; - } - } - - if (event->keyval == GDK_KEY_greater && - (event->state & GDK_CONTROL_MASK)) { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Ctrl->: increase font size\n"); -#endif - change_font_size(inst, +1); - return true; - } - if (event->keyval == GDK_KEY_less && - (event->state & GDK_CONTROL_MASK)) { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Ctrl-<: increase font size\n"); -#endif - change_font_size(inst, -1); - return true; - } - - /* - * Shift-PgUp and Shift-PgDn don't even generate keystrokes - * at all. - */ - if (event->keyval == GDK_KEY_Page_Up && - ((event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) == - (GDK_CONTROL_MASK | GDK_SHIFT_MASK))) { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Ctrl-Shift-PgUp scroll\n"); -#endif - term_scroll(inst->term, 1, 0); - return true; - } - if (event->keyval == GDK_KEY_Page_Up && - (event->state & GDK_SHIFT_MASK)) { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Shift-PgUp scroll\n"); -#endif - term_scroll(inst->term, 0, -inst->height/2); - return true; - } - if (event->keyval == GDK_KEY_Page_Up && - (event->state & GDK_CONTROL_MASK)) { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Ctrl-PgUp scroll\n"); -#endif - term_scroll(inst->term, 0, -1); - return true; - } - if (event->keyval == GDK_KEY_Page_Down && - ((event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) == - (GDK_CONTROL_MASK | GDK_SHIFT_MASK))) { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Ctrl-shift-PgDn scroll\n"); -#endif - term_scroll(inst->term, -1, 0); - return true; - } - if (event->keyval == GDK_KEY_Page_Down && - (event->state & GDK_SHIFT_MASK)) { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Shift-PgDn scroll\n"); -#endif - term_scroll(inst->term, 0, +inst->height/2); - return true; - } - if (event->keyval == GDK_KEY_Page_Down && - (event->state & GDK_CONTROL_MASK)) { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Ctrl-PgDn scroll\n"); -#endif - term_scroll(inst->term, 0, +1); - return true; - } - - /* - * Neither do Shift-Ins or Ctrl-Ins (if enabled). - */ - if (event->keyval == GDK_KEY_Insert && - (event->state & GDK_SHIFT_MASK)) { - int cfgval = conf_get_int(inst->conf, CONF_ctrlshiftins); - - switch (cfgval) { - case CLIPUI_IMPLICIT: -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Shift-Insert: paste from PRIMARY\n"); -#endif - term_request_paste(inst->term, CLIP_PRIMARY); - return true; - case CLIPUI_EXPLICIT: -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Shift-Insert: paste from CLIPBOARD\n"); -#endif - term_request_paste(inst->term, CLIP_CLIPBOARD); - return true; - case CLIPUI_CUSTOM: -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Shift-Insert: paste from custom clipboard\n"); -#endif - term_request_paste(inst->term, inst->clipboard_ctrlshiftins); - return true; - default: -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Shift-Insert: no paste action\n"); -#endif - break; - } - } - if (event->keyval == GDK_KEY_Insert && - (event->state & GDK_CONTROL_MASK)) { - static const int clips_clipboard[] = { CLIP_CLIPBOARD }; - int cfgval = conf_get_int(inst->conf, CONF_ctrlshiftins); - - switch (cfgval) { - case CLIPUI_IMPLICIT: - /* do nothing; re-copy to PRIMARY is not needed */ -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Ctrl-Insert: non-copy to PRIMARY\n"); -#endif - return true; - case CLIPUI_EXPLICIT: -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Ctrl-Insert: copy to CLIPBOARD\n"); -#endif - term_request_copy(inst->term, - clips_clipboard, lenof(clips_clipboard)); - return true; - case CLIPUI_CUSTOM: -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Ctrl-Insert: copy to custom clipboard\n"); -#endif - term_request_copy(inst->term, - &inst->clipboard_ctrlshiftins, 1); - return true; - default: -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Ctrl-Insert: no copy action\n"); -#endif - break; - } - } - - /* - * Another pair of copy-paste keys. - */ - if ((event->state & GDK_SHIFT_MASK) && - (event->state & GDK_CONTROL_MASK) && - (event->keyval == GDK_KEY_C || event->keyval == GDK_KEY_c || - event->keyval == GDK_KEY_V || event->keyval == GDK_KEY_v)) { - int cfgval = conf_get_int(inst->conf, CONF_ctrlshiftcv); - bool paste = (event->keyval == GDK_KEY_V || - event->keyval == GDK_KEY_v); - - switch (cfgval) { - case CLIPUI_IMPLICIT: - if (paste) { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Ctrl-Shift-V: paste from PRIMARY\n"); -#endif - term_request_paste(inst->term, CLIP_PRIMARY); - } else { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Ctrl-Shift-C: non-copy to PRIMARY\n"); -#endif - } - return true; - case CLIPUI_EXPLICIT: - if (paste) { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Ctrl-Shift-V: paste from CLIPBOARD\n"); -#endif - term_request_paste(inst->term, CLIP_CLIPBOARD); - } else { - static const int clips[] = { CLIP_CLIPBOARD }; -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Ctrl-Shift-C: copy to CLIPBOARD\n"); -#endif - term_request_copy(inst->term, clips, lenof(clips)); - } - return true; - case CLIPUI_CUSTOM: - if (paste) { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Ctrl-Shift-V: paste from custom clipboard\n"); -#endif - term_request_paste(inst->term, - inst->clipboard_ctrlshiftcv); - } else { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Ctrl-Shift-C: copy to custom clipboard\n"); -#endif - term_request_copy(inst->term, - &inst->clipboard_ctrlshiftcv, 1); - } - 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 /* !GTK_CHECK_VERSION(2,0,0) */ - /* - * Most things can now be passed to - * gtk_im_context_filter_keypress without breaking anything - * below this point. An exception is the numeric keypad if - * we're in Nethack or application mode: the IM will eat - * numeric keypad presses if Num Lock is on, but we don't want - * it to. - */ - bool numeric = false; - bool nethack_mode = conf_get_bool(inst->conf, CONF_nethack_keypad); - bool app_keypad_mode = (inst->term->app_keypad_keys && - !conf_get_bool(inst->conf, CONF_no_applic_k)); - - switch (event->keyval) { - case GDK_KEY_Num_Lock: num_keypad_key = 'G'; break; - case GDK_KEY_KP_Divide: num_keypad_key = '/'; break; - case GDK_KEY_KP_Multiply: num_keypad_key = '*'; break; - case GDK_KEY_KP_Subtract: num_keypad_key = '-'; break; - case GDK_KEY_KP_Add: num_keypad_key = '+'; break; - case GDK_KEY_KP_Enter: num_keypad_key = '\r'; break; - case GDK_KEY_KP_0: num_keypad_key = '0'; numeric = true; break; - case GDK_KEY_KP_Insert: num_keypad_key = '0'; break; - case GDK_KEY_KP_1: num_keypad_key = '1'; numeric = true; break; - case GDK_KEY_KP_End: num_keypad_key = '1'; break; - case GDK_KEY_KP_2: num_keypad_key = '2'; numeric = true; break; - case GDK_KEY_KP_Down: num_keypad_key = '2'; break; - case GDK_KEY_KP_3: num_keypad_key = '3'; numeric = true; break; - case GDK_KEY_KP_Page_Down: num_keypad_key = '3'; break; - case GDK_KEY_KP_4: num_keypad_key = '4'; numeric = true; break; - case GDK_KEY_KP_Left: num_keypad_key = '4'; break; - case GDK_KEY_KP_5: num_keypad_key = '5'; numeric = true; break; - case GDK_KEY_KP_Begin: num_keypad_key = '5'; break; - case GDK_KEY_KP_6: num_keypad_key = '6'; numeric = true; break; - case GDK_KEY_KP_Right: num_keypad_key = '6'; break; - case GDK_KEY_KP_7: num_keypad_key = '7'; numeric = true; break; - case GDK_KEY_KP_Home: num_keypad_key = '7'; break; - case GDK_KEY_KP_8: num_keypad_key = '8'; numeric = true; break; - case GDK_KEY_KP_Up: num_keypad_key = '8'; break; - case GDK_KEY_KP_9: num_keypad_key = '9'; numeric = true; break; - case GDK_KEY_KP_Page_Up: num_keypad_key = '9'; break; - case GDK_KEY_KP_Decimal: num_keypad_key = '.'; numeric = true; break; - case GDK_KEY_KP_Delete: num_keypad_key = '.'; break; - } - if ((app_keypad_mode && num_keypad_key && - (numeric || inst->term->funky_type != FUNKY_XTERM)) || - (nethack_mode && num_keypad_key >= '1' && num_keypad_key <= '9')) { - /* In these modes, we override the keypad handling: - * regardless of Num Lock, the keys are handled by - * format_numeric_keypad_key below. */ - force_format_numeric_keypad = true; - } else { - bool try_filter = true; - -#ifdef META_MANUAL_MASK - if (event->state & META_MANUAL_MASK & inst->meta_mod_mask) { - /* - * If this key event had a Meta modifier bit set which - * is also in META_MANUAL_MASK, that means passing - * such an event to the GtkIMContext will be unhelpful - * (it will eat the keystroke and turn it into - * something not what we wanted). - */ -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Meta modifier requiring manual intervention, " - "suppressing IM filtering\n"); -#endif - try_filter = false; - } -#endif - - if (try_filter) { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - general key press, passing to IM\n"); -#endif - if (gtk_im_context_filter_keypress(inst->imc, event)) { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - key press accepted by IM\n"); -#endif - return true; - } else { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - key press not accepted by IM\n"); -#endif - } - } - } - - /* - * GDK 2.0 arranges to have done some translation for us: in - * GDK 2.0, event->string is encoded in the current locale. - * - * 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]; - const wchar_t *wp; - int wlen; - int ulen; - - wlen = mb_to_wc(DEFAULT_CODEPAGE, 0, - event_string, strlen(event_string), - widedata, lenof(widedata)-1); - -#ifdef KEY_EVENT_DIAGNOSTICS - { - char *string_string = dupstr(""); - int i; - - for (i = 0; i < wlen; i++) { - char *old = string_string; - string_string = dupprintf("%s%s%04x", string_string, - string_string[0] ? " " : "", - (unsigned)widedata[i]); - sfree(old); - } - debug(" - string translated into Unicode = [%s]\n", - string_string); - sfree(string_string); - } -#endif - - wp = widedata; - ulen = charset_from_unicode(&wp, &wlen, output+1, lenof(output)-2, - CS_UTF8, NULL, NULL, 0); - -#ifdef KEY_EVENT_DIAGNOSTICS - { - char *string_string = dupstr(""); - int i; - - for (i = 0; i < ulen; i++) { - char *old = string_string; - string_string = dupprintf("%s%s%02x", string_string, - string_string[0] ? " " : "", - (unsigned)output[i+1] & 0xFF); - sfree(old); - } - debug(" - string translated into UTF-8 = [%s]\n", - string_string); - sfree(string_string); - } -#endif - - output[1+ulen] = '\0'; - } -#endif /* !GTK_CHECK_VERSION(2,0,0) */ - - if (!output[1] && - (ucsval = keysym_to_unicode(event->keyval)) >= 0) { - ucsoutput[0] = '\033'; - ucsoutput[1] = ucsval; -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - keysym_to_unicode gave %04x\n", - (unsigned)ucsoutput[1]); -#endif - use_ucsoutput = true; - end = 2; - } else { - output[lenof(output)-1] = '\0'; - end = strlen(output); - } - if (event->state & inst->meta_mod_mask) { - start = 0; - if (end == 1) end = 0; - -#ifdef META_MANUAL_MASK - if (event->state & META_MANUAL_MASK) { - /* - * Key events which have a META_MANUAL_MASK meta bit - * set may have a keyval reflecting that, e.g. on OS X - * the Option key acts as an AltGr-like modifier and - * causes different Unicode characters to be output. - * - * To work around this, we clear the dangerous - * modifier bit and retranslate from the hardware - * keycode as if the key had been pressed without that - * modifier. Then we prefix Esc to *that*. - */ - guint new_keyval; - GdkModifierType consumed; - if (gdk_keymap_translate_keyboard_state - (gdk_keymap_get_for_display(gdk_display_get_default()), - event->hardware_keycode, event->state & ~META_MANUAL_MASK, - 0, &new_keyval, NULL, NULL, &consumed)) { - ucsoutput[0] = '\033'; - ucsoutput[1] = gdk_keyval_to_unicode(new_keyval); -#ifdef KEY_EVENT_DIAGNOSTICS - { - char *keyval_name = dup_keyval_name(new_keyval); - debug(" - retranslation for manual Meta: " - "new keyval = %s, Unicode = %04x\n", - keyval_name, (unsigned)ucsoutput[1]); - sfree(keyval_name); - } -#endif - use_ucsoutput = true; - end = 2; - } - } -#endif - } 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)) { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Ctrl-` special case, translating as 1c\n"); -#endif - output[1] = '\x1C'; - use_ucsoutput = false; - end = 2; - } - - /* Some GTK backends (e.g. Quartz) do not change event->string - * in response to the Control modifier. So we do it ourselves - * here, if it's not already happened. - * - * The translations below are in line with X11 policy as far - * as I know. */ - if ((event->state & GDK_CONTROL_MASK) && end == 2) { - int orig = use_ucsoutput ? ucsoutput[1] : output[1]; - int new = orig; - - if (new >= '3' && new <= '7') { - /* ^3,...,^7 map to 0x1B,...,0x1F */ - new += '\x1B' - '3'; - } else if (new == '2' || new == ' ') { - /* ^2 and ^Space are both ^@, i.e. \0 */ - new = '\0'; - } else if (new == '8') { - /* ^8 is DEL */ - new = '\x7F'; - } else if (new == '/') { - /* ^/ is the same as ^_ */ - new = '\x1F'; - } else if (new >= 0x40 && new < 0x7F) { - /* Everything anywhere near the alphabetics just gets - * masked. */ - new &= 0x1F; - } - /* Anything else, e.g. '0', is unchanged. */ - - if (orig == new) { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - manual Ctrl key handling did nothing\n"); -#endif - } else { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - manual Ctrl key handling: %02x -> %02x\n", - (unsigned)orig, (unsigned)new); -#endif - output[1] = new; - use_ucsoutput = false; - } - } - - /* Control-Break sends a Break special to the backend */ - if (event->keyval == GDK_KEY_Break && - (event->state & GDK_CONTROL_MASK)) { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Ctrl-Break special case, sending SS_BRK\n"); -#endif - if (inst->backend) - backend_special(inst->backend, SS_BRK, 0); - return true; - } - - /* We handle Return ourselves, because it needs to be flagged as - * special to ldisc. */ - if (event->keyval == GDK_KEY_Return) { - end = return_key(inst, output, &special); - use_ucsoutput = false; - } - - /* 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) { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Ctrl-{space,2,@} special case, translating as 00\n"); -#endif - 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)) { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Ctrl-Shift-space special case, translating as 00a0\n"); -#endif - 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_KEY_BackSpace && - !(event->state & GDK_SHIFT_MASK)) { - output[1] = conf_get_bool(inst->conf, CONF_bksp_is_delete) ? - '\x7F' : '\x08'; -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Backspace, translating as %02x\n", - (unsigned)output[1]); -#endif - use_ucsoutput = false; - end = 2; - special = true; - } - /* For Shift Backspace, do opposite of what is configured. */ - if (event->keyval == GDK_KEY_BackSpace && - (event->state & GDK_SHIFT_MASK)) { - output[1] = conf_get_bool(inst->conf, CONF_bksp_is_delete) ? - '\x08' : '\x7F'; -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Shift-Backspace, translating as %02x\n", - (unsigned)output[1]); -#endif - use_ucsoutput = false; - end = 2; - special = true; - } - - /* Shift-Tab is ESC [ Z */ - if (event->keyval == GDK_KEY_ISO_Left_Tab || - (event->keyval == GDK_KEY_Tab && - (event->state & GDK_SHIFT_MASK))) { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Shift-Tab, translating as ESC [ Z\n"); -#endif - 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_KEY_Tab && end <= 1) { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Tab, translating as 09\n"); -#endif - output[1] = '\t'; - end = 2; - } - - if (num_keypad_key && force_format_numeric_keypad) { - end = 1 + format_numeric_keypad_key( - output+1, inst->term, num_keypad_key, - event->state & GDK_SHIFT_MASK, - event->state & GDK_CONTROL_MASK); -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - numeric keypad key"); -#endif - use_ucsoutput = false; - goto done; - } - - switch (event->keyval) { - int fkey_number; - case GDK_KEY_F1: fkey_number = 1; goto numbered_function_key; - case GDK_KEY_F2: fkey_number = 2; goto numbered_function_key; - case GDK_KEY_F3: fkey_number = 3; goto numbered_function_key; - case GDK_KEY_F4: fkey_number = 4; goto numbered_function_key; - case GDK_KEY_F5: fkey_number = 5; goto numbered_function_key; - case GDK_KEY_F6: fkey_number = 6; goto numbered_function_key; - case GDK_KEY_F7: fkey_number = 7; goto numbered_function_key; - case GDK_KEY_F8: fkey_number = 8; goto numbered_function_key; - case GDK_KEY_F9: fkey_number = 9; goto numbered_function_key; - case GDK_KEY_F10: fkey_number = 10; goto numbered_function_key; - case GDK_KEY_F11: fkey_number = 11; goto numbered_function_key; - case GDK_KEY_F12: fkey_number = 12; goto numbered_function_key; - case GDK_KEY_F13: fkey_number = 13; goto numbered_function_key; - case GDK_KEY_F14: fkey_number = 14; goto numbered_function_key; - case GDK_KEY_F15: fkey_number = 15; goto numbered_function_key; - case GDK_KEY_F16: fkey_number = 16; goto numbered_function_key; - case GDK_KEY_F17: fkey_number = 17; goto numbered_function_key; - case GDK_KEY_F18: fkey_number = 18; goto numbered_function_key; - case GDK_KEY_F19: fkey_number = 19; goto numbered_function_key; - case GDK_KEY_F20: fkey_number = 20; goto numbered_function_key; - numbered_function_key: - end = 1 + format_function_key(output+1, inst->term, fkey_number, - event->state & GDK_SHIFT_MASK, - event->state & GDK_CONTROL_MASK); -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - function key F%d", fkey_number); -#endif - use_ucsoutput = false; - goto done; - - SmallKeypadKey sk_key; - case GDK_KEY_Home: case GDK_KEY_KP_Home: - sk_key = SKK_HOME; goto small_keypad_key; - case GDK_KEY_Insert: case GDK_KEY_KP_Insert: - sk_key = SKK_INSERT; goto small_keypad_key; - case GDK_KEY_Delete: case GDK_KEY_KP_Delete: - sk_key = SKK_DELETE; goto small_keypad_key; - case GDK_KEY_End: case GDK_KEY_KP_End: - sk_key = SKK_END; goto small_keypad_key; - case GDK_KEY_Page_Up: case GDK_KEY_KP_Page_Up: - sk_key = SKK_PGUP; goto small_keypad_key; - case GDK_KEY_Page_Down: case GDK_KEY_KP_Page_Down: - sk_key = SKK_PGDN; goto small_keypad_key; - small_keypad_key: - /* These keys don't generate terminal input with Ctrl */ - if (event->state & GDK_CONTROL_MASK) - break; - - end = 1 + format_small_keypad_key(output+1, inst->term, sk_key); -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - small keypad key"); -#endif - use_ucsoutput = false; - goto done; - - int xkey; - case GDK_KEY_Up: case GDK_KEY_KP_Up: - xkey = 'A'; goto arrow_key; - case GDK_KEY_Down: case GDK_KEY_KP_Down: - xkey = 'B'; goto arrow_key; - case GDK_KEY_Right: case GDK_KEY_KP_Right: - xkey = 'C'; goto arrow_key; - case GDK_KEY_Left: case GDK_KEY_KP_Left: - xkey = 'D'; goto arrow_key; - case GDK_KEY_Begin: case GDK_KEY_KP_Begin: - xkey = 'G'; goto arrow_key; - arrow_key: - end = 1 + format_arrow_key(output+1, inst->term, xkey, - event->state & GDK_CONTROL_MASK); -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - arrow key"); -#endif - use_ucsoutput = false; - goto done; - } - - if (num_keypad_key) { - end = 1 + format_numeric_keypad_key( - output+1, inst->term, num_keypad_key, - event->state & GDK_SHIFT_MASK, - event->state & GDK_CONTROL_MASK); -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - numeric keypad key"); -#endif - - if (end == 1 && num_keypad_key == '\r') { - /* Keypad Enter, lacking any other translation, - * becomes the same special Return code as normal - * Return. */ - end = return_key(inst, output, &special); - use_ucsoutput = false; - } - - use_ucsoutput = false; - goto done; - } - - goto done; - } - - done: - - if (end-start > 0) { - if (special) { -#ifdef KEY_EVENT_DIAGNOSTICS - char *string_string = dupstr(""); - int i; - - for (i = start; i < end; i++) { - char *old = string_string; - string_string = dupprintf("%s%s%02x", string_string, - string_string[0] ? " " : "", - (unsigned)output[i] & 0xFF); - sfree(old); - } - debug(" - final output, special, generic encoding = [%s]\n", - string_string); - sfree(string_string); -#endif - /* - * For special control characters, the character set - * should never matter. - */ - output[end] = '\0'; /* NUL-terminate */ - generated_something = true; - term_keyinput(inst->term, -1, output+start, -2); - } else if (!inst->direct_to_font) { - if (!use_ucsoutput) { -#ifdef KEY_EVENT_DIAGNOSTICS - char *string_string = dupstr(""); - int i; - - for (i = start; i < end; i++) { - char *old = string_string; - string_string = dupprintf("%s%s%02x", string_string, - string_string[0] ? " " : "", - (unsigned)output[i] & 0xFF); - sfree(old); - } - debug(" - final output in %s = [%s]\n", - charset_to_localenc(output_charset), string_string); - sfree(string_string); -#endif - generated_something = true; - term_keyinput(inst->term, output_charset, - output+start, end-start); - } else { -#ifdef KEY_EVENT_DIAGNOSTICS - char *string_string = dupstr(""); - int i; - - for (i = start; i < end; i++) { - char *old = string_string; - string_string = dupprintf("%s%s%04x", string_string, - string_string[0] ? " " : "", - (unsigned)ucsoutput[i]); - sfree(old); - } - debug(" - final output in Unicode = [%s]\n", - string_string); - sfree(string_string); -#endif - - /* - * We generated our own Unicode key data from the - * keysym, so use that instead. - */ - generated_something = true; - term_keyinputw(inst->term, ucsoutput+start, end-start); - } - } else { - /* - * In direct-to-font mode, we just send the string - * exactly as we received it. - */ -#ifdef KEY_EVENT_DIAGNOSTICS - char *string_string = dupstr(""); - int i; - - for (i = start; i < end; i++) { - char *old = string_string; - string_string = dupprintf("%s%s%02x", string_string, - string_string[0] ? " " : "", - (unsigned)output[i] & 0xFF); - sfree(old); - } - debug(" - final output in direct-to-font encoding = [%s]\n", - string_string); - sfree(string_string); -#endif - generated_something = true; - term_keyinput(inst->term, -1, output+start, end-start); - } - - show_mouseptr(inst, false); - } - - if (generated_something) - key_pressed(inst); - return true; -} - -#if GTK_CHECK_VERSION(2,0,0) -void input_method_commit_event(GtkIMContext *imc, gchar *str, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - -#ifdef KEY_EVENT_DIAGNOSTICS - char *string_string = dupstr(""); - int i; - - for (i = 0; str[i]; i++) { - char *old = string_string; - string_string = dupprintf("%s%s%02x", string_string, - string_string[0] ? " " : "", - (unsigned)str[i] & 0xFF); - sfree(old); - } - debug(" - IM commit event in UTF-8 = [%s]\n", string_string); - sfree(string_string); -#endif - - term_keyinput(inst->term, CS_UTF8, str, strlen(str)); - show_mouseptr(inst, false); - key_pressed(inst); -} -#endif - -#define SCROLL_INCREMENT_LINES 5 - -#if GTK_CHECK_VERSION(3,4,0) -gboolean scroll_internal(GtkFrontend *inst, gdouble delta, guint state, - gdouble ex, gdouble ey) -{ - int x, y; - bool shift, ctrl, alt, raw_mouse_mode; - - show_mouseptr(inst, true); - - shift = state & GDK_SHIFT_MASK; - ctrl = state & GDK_CONTROL_MASK; - alt = state & inst->meta_mod_mask; - - x = (ex - inst->window_border) / inst->font_width; - y = (ey - inst->window_border) / inst->font_height; - - raw_mouse_mode = (inst->send_raw_mouse && - !(shift && conf_get_bool(inst->conf, - CONF_mouse_override))); - - inst->cumulative_scroll += delta * SCROLL_INCREMENT_LINES; - - if (!raw_mouse_mode) { - int scroll_lines = (int)inst->cumulative_scroll; /* rounds toward 0 */ - if (scroll_lines) { - term_scroll(inst->term, 0, scroll_lines); - inst->cumulative_scroll -= scroll_lines; - } - return true; - } else { - int scroll_events = (int)(inst->cumulative_scroll / - SCROLL_INCREMENT_LINES); - if (scroll_events) { - int button; - - inst->cumulative_scroll -= scroll_events * SCROLL_INCREMENT_LINES; - - if (scroll_events > 0) { - button = MBT_WHEEL_DOWN; - } else { - button = MBT_WHEEL_UP; - scroll_events = -scroll_events; - } - - while (scroll_events-- > 0) { - term_mouse(inst->term, button, translate_button(button), - MA_CLICK, x, y, shift, ctrl, alt); - } - } - return true; - } -} -#endif - -static gboolean button_internal(GtkFrontend *inst, GdkEventButton *event) -{ - bool shift, ctrl, alt, raw_mouse_mode; - int x, y, button, act; - - /* Remember the timestamp. */ - inst->input_event_time = event->time; - - noise_ultralight(NOISE_SOURCE_MOUSEBUTTON, event->button); - - show_mouseptr(inst, true); - - shift = event->state & GDK_SHIFT_MASK; - ctrl = event->state & GDK_CONTROL_MASK; - alt = event->state & inst->meta_mod_mask; - - raw_mouse_mode = (inst->send_raw_mouse && - !(shift && conf_get_bool(inst->conf, - CONF_mouse_override))); - - if (!raw_mouse_mode) { - if (event->button == 4 && event->type == GDK_BUTTON_PRESS) { - term_scroll(inst->term, 0, -SCROLL_INCREMENT_LINES); - return true; - } - if (event->button == 5 && event->type == GDK_BUTTON_PRESS) { - term_scroll(inst->term, 0, +SCROLL_INCREMENT_LINES); - return true; - } - } - - if (event->button == 3 && ctrl) { -#if GTK_CHECK_VERSION(3,22,0) - gtk_menu_popup_at_pointer(GTK_MENU(inst->menu), (GdkEvent *)event); -#else - gtk_menu_popup(GTK_MENU(inst->menu), NULL, NULL, NULL, NULL, - event->button, event->time); -#endif - return true; - } - - if (event->button == 1) - button = MBT_LEFT; - else if (event->button == 2) - button = MBT_MIDDLE; - else if (event->button == 3) - button = MBT_RIGHT; - else if (event->button == 4) - button = MBT_WHEEL_UP; - else if (event->button == 5) - button = MBT_WHEEL_DOWN; - else - return false; /* don't even know what button! */ - - switch (event->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 (raw_mouse_mode && act != MA_CLICK && act != MA_RELEASE) - return true; /* we ignore these in raw mouse mode */ - - x = (event->x - inst->window_border) / inst->font_width; - y = (event->y - inst->window_border) / inst->font_height; - - term_mouse(inst->term, button, translate_button(button), act, - x, y, shift, ctrl, alt); - - return true; -} - -gboolean button_event(GtkWidget *widget, GdkEventButton *event, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - return button_internal(inst, event); -} - -#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) -{ - GtkFrontend *inst = (GtkFrontend *)data; - GdkScrollDirection dir; - -#if GTK_CHECK_VERSION(3,4,0) - gdouble dx, dy; - if (gdk_event_get_scroll_deltas((GdkEvent *)event, &dx, &dy)) { - return scroll_internal(inst, dy, event->state, event->x, event->y); - } else if (!gdk_event_get_scroll_direction((GdkEvent *)event, &dir)) { - return false; - } -#else - dir = event->direction; -#endif - - guint button; - GdkEventButton *event_button; - gboolean ret; - - if (dir == GDK_SCROLL_UP) - button = 4; - else if (dir == GDK_SCROLL_DOWN) - button = 5; - else - return false; - - event_button = (GdkEventButton *)gdk_event_new(GDK_BUTTON_PRESS); - event_button->window = g_object_ref(event->window); - event_button->send_event = event->send_event; - event_button->time = event->time; - event_button->x = event->x; - event_button->y = event->y; - event_button->axes = NULL; - event_button->state = event->state; - event_button->button = button; - event_button->device = g_object_ref(event->device); - event_button->x_root = event->x_root; - event_button->y_root = event->y_root; - ret = button_internal(inst, event_button); - gdk_event_free((GdkEvent *)event_button); - return ret; -} -#endif - -gint motion_event(GtkWidget *widget, GdkEventMotion *event, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - bool shift, ctrl, alt; - int x, y, button; - - /* Remember the timestamp. */ - inst->input_event_time = event->time; - - noise_ultralight(NOISE_SOURCE_MOUSEPOS, - ((uint32_t)event->x << 16) | (uint32_t)event->y); - - show_mouseptr(inst, true); - - shift = event->state & GDK_SHIFT_MASK; - ctrl = event->state & GDK_CONTROL_MASK; - alt = event->state & inst->meta_mod_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->window_border) / inst->font_width; - y = (event->y - inst->window_border) / inst->font_height; - - term_mouse(inst->term, button, translate_button(button), MA_DRAG, - x, y, shift, ctrl, alt); - - return true; -} - -static void key_pressed(GtkFrontend *inst) -{ - /* - * If our child process has exited but not closed, terminate on - * any keypress. - * - * This is a UI feature specific to GTK PuTTY, because GTK PuTTY - * will (at least sometimes) be running under X, and under X the - * window manager is sometimes absent (very occasionally on - * purpose, more usually temporarily because it's crashed). So - * it's useful to have a way to close an application window - * without depending on protocols like WM_DELETE_WINDOW that are - * typically generated by the WM (e.g. in response to a close - * button in the window frame). - */ - if (inst->exited) - gtk_widget_destroy(inst->window); -} - -static void exit_callback(void *vctx) -{ - GtkFrontend *inst = (GtkFrontend *)vctx; - int exitcode, close_on_exit; - - if (!inst->exited && - (exitcode = backend_exitcode(inst->backend)) >= 0) { - destroy_inst_connection(inst); - - close_on_exit = conf_get_int(inst->conf, CONF_close_on_exit); - if (close_on_exit == FORCE_ON || - (close_on_exit == AUTO && exitcode == 0)) { - gtk_widget_destroy(inst->window); - } - } -} - -static void gtk_seat_notify_remote_exit(Seat *seat) -{ - GtkFrontend *inst = container_of(seat, GtkFrontend, seat); - queue_toplevel_callback(exit_callback, inst); -} - -static void destroy_inst_connection(GtkFrontend *inst) -{ - inst->exited = true; - if (inst->ldisc) { - ldisc_free(inst->ldisc); - inst->ldisc = NULL; - } - if (inst->backend) { - backend_free(inst->backend); - inst->backend = NULL; - } - if (inst->term) - term_provide_backend(inst->term, NULL); - if (inst->menu) { - seat_update_specials_menu(&inst->seat); - gtk_widget_set_sensitive(inst->restartitem, true); - } -} - -static void delete_inst(GtkFrontend *inst) -{ - int dialog_slot; - for (dialog_slot = 0; dialog_slot < DIALOG_SLOT_LIMIT; dialog_slot++) { - if (inst->dialogs[dialog_slot]) { - gtk_widget_destroy(inst->dialogs[dialog_slot]); - inst->dialogs[dialog_slot] = NULL; - } - } - if (inst->window) { - gtk_widget_destroy(inst->window); - inst->window = NULL; - } - if (inst->menu) { - gtk_widget_destroy(inst->menu); - inst->menu = NULL; - } - destroy_inst_connection(inst); - if (inst->term) { - term_free(inst->term); - inst->term = NULL; - } - if (inst->conf) { - conf_free(inst->conf); - inst->conf = NULL; - } - if (inst->logctx) { - log_free(inst->logctx); - inst->logctx = NULL; - } -#if GTK_CHECK_VERSION(2,0,0) - if (inst->trust_sigil_pb) { - g_object_unref(G_OBJECT(inst->trust_sigil_pb)); - inst->trust_sigil_pb = NULL; - } -#else - if (inst->trust_sigil_pm) { - gdk_pixmap_unref(inst->trust_sigil_pm); - inst->trust_sigil_pm = NULL; - } -#endif - -#ifdef JUST_USE_GTK_CLIPBOARD_UTF8 - /* - * Clear up any in-flight clipboard_data_instances. We can't - * actually _free_ them, but we detach them from the inst that's - * about to be destroyed. - */ - while (inst->cdi_headtail.next != &inst->cdi_headtail) { - struct clipboard_data_instance *cdi = inst->cdi_headtail.next; - cdi->state = NULL; - cdi->next->prev = cdi->prev; - cdi->prev->next = cdi->next; - cdi->next = cdi->prev = cdi; - } -#endif - - /* - * Delete any top-level callbacks associated with inst, which - * would otherwise become stale-pointer dereferences waiting to - * happen. We do this last, because some of the above cleanups - * (notably shutting down the backend) might themelves queue such - * callbacks, so we need to make sure they don't do that _after_ - * we're supposed to have cleaned everything up. - */ - delete_callbacks_for_context(inst); - - eventlogstuff_free(inst->eventlogstuff); - - sfree(inst); -} - -void destroy(GtkWidget *widget, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - inst->window = NULL; - delete_inst(inst); - session_window_closed(); -} - -gint focus_event(GtkWidget *widget, GdkEventFocus *event, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - term_set_focus(inst->term, event->in); - term_update(inst->term); - show_mouseptr(inst, true); - return false; -} - -static void gtk_seat_set_busy_status(Seat *seat, BusyStatus status) -{ - GtkFrontend *inst = container_of(seat, GtkFrontend, seat); - inst->busy_status = status; - update_mouseptr(inst); -} - -static void gtkwin_set_raw_mouse_mode(TermWin *tw, bool activate) -{ - GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); - inst->send_raw_mouse = activate; -} - -static void gtkwin_set_raw_mouse_mode_pointer(TermWin *tw, bool activate) -{ - GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); - inst->pointer_indicates_raw_mouse = activate; - update_mouseptr(inst); -} - -#if GTK_CHECK_VERSION(2,0,0) -static void compute_whole_window_size(GtkFrontend *inst, - int wchars, int hchars, - int *wpix, int *hpix); -#endif - -static void gtkwin_request_resize(TermWin *tw, int w, int h) -{ - GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); - -#if !GTK_CHECK_VERSION(3,0,0) - - 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_pixel_size(inst, &large_x, &large_y); - large_x += 32; - large_y += 32; - - gtk_widget_set_size_request(inst->area, large_x, large_y); - 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->window_border; - area_y = inst->font_height * h + 2*inst->window_border; - - /* - * Now we must set the size request on the drawing area back to - * something sensible before we commit the real resize. Best - * way to do this, I think, is to set it to what the size is - * really going to end up being. - */ - gtk_widget_set_size_request(inst->area, area_x, area_y); -#if GTK_CHECK_VERSION(2,0,0) - gtk_window_resize(GTK_WINDOW(inst->window), - area_x + offset_x, area_y + offset_y); -#else - 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(gtk_widget_get_window(inst->window), - area_x + offset_x, area_y + offset_y); -#endif - -#else /* GTK_CHECK_VERSION(3,0,0) */ - - int wp, hp; - compute_whole_window_size(inst, w, h, &wp, &hp); - gtk_window_resize(GTK_WINDOW(inst->window), wp, hp); - -#endif - -} - -#if GTK_CHECK_VERSION(3,0,0) -char *colour_to_css(const GdkColor *col) -{ - GdkRGBA rgba; - rgba.red = col->red / 65535.0; - rgba.green = col->green / 65535.0; - rgba.blue = col->blue / 65535.0; - rgba.alpha = 1.0; - return gdk_rgba_to_string(&rgba); -} -#endif - -void set_gtk_widget_background(GtkWidget *widget, const GdkColor *col) -{ -#if GTK_CHECK_VERSION(3,0,0) - GtkCssProvider *provider = gtk_css_provider_new(); - char *col_css = colour_to_css(col); - char *data = dupprintf( - "#drawing-area, #top-level { background-color: %s; }\n", col_css); - gtk_css_provider_load_from_data(provider, data, -1, NULL); - GtkStyleContext *context = gtk_widget_get_style_context(widget); - gtk_style_context_add_provider(context, GTK_STYLE_PROVIDER(provider), - GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); - free(data); - free(col_css); -#else - if (gtk_widget_get_window(widget)) { - /* For GTK1, which doesn't have a 'const' on - * gdk_window_set_background's second parameter type. */ - GdkColor col_mutable = *col; - gdk_window_set_background(gtk_widget_get_window(widget), &col_mutable); - } -#endif -} - -void set_window_background(GtkFrontend *inst) -{ - if (inst->area) - set_gtk_widget_background(GTK_WIDGET(inst->area), &inst->cols[258]); - if (inst->window) - set_gtk_widget_background(GTK_WIDGET(inst->window), &inst->cols[258]); -} - -static void gtkwin_palette_set(TermWin *tw, unsigned start, unsigned ncolours, - const rgb *colours) -{ - GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); - - assert(start <= OSC4_NCOLOURS); - assert(ncolours <= OSC4_NCOLOURS - start); - -#if !GTK_CHECK_VERSION(3,0,0) - if (!inst->colmap) { - inst->colmap = gdk_colormap_get_system(); - } else { - gdk_colormap_free_colors(inst->colmap, inst->cols, OSC4_NCOLOURS); - } -#endif - - for (unsigned i = 0; i < ncolours; i++) { - const rgb *in = &colours[i]; - GdkColor *out = &inst->cols[start + i]; - - out->red = in->r * 0x0101; - out->green = in->g * 0x0101; - out->blue = in->b * 0x0101; - } - -#if !GTK_CHECK_VERSION(3,0,0) - { - gboolean success[OSC4_NCOLOURS]; - gdk_colormap_alloc_colors(inst->colmap, inst->cols + start, - ncolours, false, true, success); - for (unsigned i = 0; i < ncolours; i++) { - if (!success[i]) - g_error("%s: couldn't allocate colour %d (#%02x%02x%02x)\n", - appname, start + i, - conf_get_int_int(inst->conf, CONF_colours, i*3+0), - conf_get_int_int(inst->conf, CONF_colours, i*3+1), - conf_get_int_int(inst->conf, CONF_colours, i*3+2)); - } - } -#endif - - if (start <= OSC4_COLOUR_bg && OSC4_COLOUR_bg < start + ncolours) { - /* Default Background has changed, so ensure that space between text - * area and window border is refreshed. */ - set_window_background(inst); - if (inst->area && gtk_widget_get_window(inst->area)) { - draw_backing_rect(inst); - gtk_widget_queue_draw(inst->area); - } - } -} - -static void gtkwin_palette_get_overrides(TermWin *tw) -{ - /* GTK has no analogue of Windows's 'standard system colours', so GTK PuTTY - * has no config option to override the normally configured colours from - * it */ -} - -static struct clipboard_state *clipboard_from_atom( - GtkFrontend *inst, GdkAtom atom) -{ - int i; - - for (i = 0; i < N_CLIPBOARDS; i++) { - struct clipboard_state *state = &inst->clipstates[i]; - if (state->inst == inst && state->atom == atom) - return state; - } - - return NULL; -} - -#ifdef JUST_USE_GTK_CLIPBOARD_UTF8 - -/* ---------------------------------------------------------------------- - * Clipboard handling, using the high-level GtkClipboard interface in - * as hands-off a way as possible. We write and read the clipboard as - * UTF-8 text, and let GTK deal with converting to any other text - * formats it feels like. - */ - -void set_clipboard_atom(GtkFrontend *inst, int clipboard, GdkAtom atom) -{ - struct clipboard_state *state = &inst->clipstates[clipboard]; - - state->inst = inst; - state->clipboard = clipboard; - state->atom = atom; - - if (state->atom != GDK_NONE) { - state->gtkclipboard = gtk_clipboard_get_for_display( - gdk_display_get_default(), state->atom); - g_object_set_data(G_OBJECT(state->gtkclipboard), "user-data", state); - } else { - state->gtkclipboard = NULL; - } -} - -int init_clipboard(GtkFrontend *inst) -{ - set_clipboard_atom(inst, CLIP_PRIMARY, GDK_SELECTION_PRIMARY); - set_clipboard_atom(inst, CLIP_CLIPBOARD, clipboard_atom); - return true; -} - -static void clipboard_provide_data(GtkClipboard *clipboard, - GtkSelectionData *selection_data, - guint info, gpointer data) -{ - struct clipboard_data_instance *cdi = - (struct clipboard_data_instance *)data; - - if (cdi->state && cdi->state->current_cdi == cdi) { - gtk_selection_data_set_text(selection_data, cdi->pasteout_data_utf8, - cdi->pasteout_data_utf8_len); - } -} - -static void clipboard_clear(GtkClipboard *clipboard, gpointer data) -{ - struct clipboard_data_instance *cdi = - (struct clipboard_data_instance *)data; - - if (cdi->state && cdi->state->current_cdi == cdi) { - if (cdi->state->inst && cdi->state->inst->term) { - term_lost_clipboard_ownership(cdi->state->inst->term, - cdi->state->clipboard); - } - cdi->state->current_cdi = NULL; - } - sfree(cdi->pasteout_data_utf8); - cdi->next->prev = cdi->prev; - cdi->prev->next = cdi->next; - sfree(cdi); -} - -static void gtkwin_clip_write( - TermWin *tw, int clipboard, wchar_t *data, int *attr, - truecolour *truecolour, int len, bool must_deselect) -{ - GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); - struct clipboard_state *state = &inst->clipstates[clipboard]; - struct clipboard_data_instance *cdi; - - if (inst->direct_to_font) { - /* In this clipboard mode, we just can't paste if we're in - * direct-to-font mode. Fortunately, that shouldn't be - * important, because we'll only use this clipboard handling - * code on systems where that kind of font doesn't exist - * anyway. */ - return; - } - - if (!state->gtkclipboard) - return; - - cdi = snew(struct clipboard_data_instance); - cdi->state = state; - state->current_cdi = cdi; - cdi->pasteout_data_utf8 = snewn(len*6, char); - cdi->prev = inst->cdi_headtail.prev; - cdi->next = &inst->cdi_headtail; - cdi->next->prev = cdi; - cdi->prev->next = cdi; - { - const wchar_t *tmp = data; - int tmplen = len; - cdi->pasteout_data_utf8_len = - charset_from_unicode(&tmp, &tmplen, cdi->pasteout_data_utf8, - len*6, CS_UTF8, NULL, NULL, 0); - } - - /* - * It would be nice to just call gtk_clipboard_set_text() in place - * of all of the faffing below. Unfortunately, that won't give me - * access to the clipboard-clear event, which we use to visually - * deselect text in the terminal. - */ - { - GtkTargetList *targetlist; - GtkTargetEntry *targettable; - gint n_targets; - - targetlist = gtk_target_list_new(NULL, 0); - gtk_target_list_add_text_targets(targetlist, 0); - targettable = gtk_target_table_new_from_list(targetlist, &n_targets); - gtk_clipboard_set_with_data(state->gtkclipboard, targettable, - n_targets, clipboard_provide_data, - clipboard_clear, cdi); - gtk_target_table_free(targettable, n_targets); - gtk_target_list_unref(targetlist); - } -} - -static void clipboard_text_received(GtkClipboard *clipboard, - const gchar *text, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - wchar_t *paste; - int paste_len; - int length; - - if (!text) - return; - - length = strlen(text); - - paste = snewn(length, wchar_t); - paste_len = mb_to_wc(CS_UTF8, 0, text, length, paste, length); - - term_do_paste(inst->term, paste, paste_len); - - sfree(paste); -} - -static void gtkwin_clip_request_paste(TermWin *tw, int clipboard) -{ - GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); - struct clipboard_state *state = &inst->clipstates[clipboard]; - - if (!state->gtkclipboard) - return; - - gtk_clipboard_request_text(state->gtkclipboard, - clipboard_text_received, inst); -} - -#else /* JUST_USE_GTK_CLIPBOARD_UTF8 */ - -/* ---------------------------------------------------------------------- - * Clipboard handling for X, using the low-level gtk_selection_* - * interface, handling conversions to fiddly things like compound text - * ourselves, and storing in X cut buffers too. - * - * This version of the clipboard code has to be kept around for GTK1, - * which doesn't have the higher-level GtkClipboard interface at all. - * And since it works on GTK2 and GTK3 too and has had a good few - * years of shakedown and bug fixing, we might as well keep using it - * where it's applicable. - * - * It's _possible_ that we might be able to replicate all the - * important wrinkles of this code in GtkClipboard. (In particular, - * cut buffers or local analogue look as if they might be accessible - * via gtk_clipboard_set_can_store(), and delivering text in - * non-Unicode formats only in the direct-to-font case ought to be - * possible if we can figure out the right set of things to put in the - * GtkTargetList.) But that work can wait until there's a need for it! - */ - -#ifndef NOT_X_WINDOWS - -/* Store the data in a cut-buffer. */ -static void store_cutbuffer(GtkFrontend *inst, char *ptr, int len) -{ - if (inst->disp) { - /* ICCCM says we must rotate the buffers before storing to buffer 0. */ - XRotateBuffers(inst->disp, 1); - XStoreBytes(inst->disp, ptr, len); - } -} - -/* Retrieve data from a cut-buffer. - * Returned data needs to be freed with XFree(). - */ -static char *retrieve_cutbuffer(GtkFrontend *inst, int *nbytes) -{ - char *ptr; - if (!inst->disp) { - *nbytes = 0; - return NULL; - } - ptr = XFetchBytes(inst->disp, nbytes); - if (*nbytes <= 0 && ptr != 0) { - XFree(ptr); - ptr = 0; - } - return ptr; -} - -#endif /* NOT_X_WINDOWS */ - -static void gtkwin_clip_write( - TermWin *tw, int clipboard, wchar_t *data, int *attr, - truecolour *truecolour, int len, bool must_deselect) -{ - GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); - struct clipboard_state *state = &inst->clipstates[clipboard]; - - if (state->pasteout_data) - sfree(state->pasteout_data); - if (state->pasteout_data_ctext) - sfree(state->pasteout_data_ctext); - if (state->pasteout_data_utf8) - sfree(state->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) { - const wchar_t *tmp = data; - int tmplen = len; -#ifndef NOT_X_WINDOWS - XTextProperty tp; - char *list[1]; -#endif - - state->pasteout_data_utf8 = snewn(len*6, char); - state->pasteout_data_utf8_len = len*6; - state->pasteout_data_utf8_len = - charset_from_unicode(&tmp, &tmplen, state->pasteout_data_utf8, - state->pasteout_data_utf8_len, - CS_UTF8, NULL, NULL, 0); - if (state->pasteout_data_utf8_len == 0) { - sfree(state->pasteout_data_utf8); - state->pasteout_data_utf8 = NULL; - } else { - state->pasteout_data_utf8 = - sresize(state->pasteout_data_utf8, - state->pasteout_data_utf8_len + 1, char); - state->pasteout_data_utf8[state->pasteout_data_utf8_len] = '\0'; - } - - /* - * Now let Xlib convert our UTF-8 data into compound text. - */ -#ifndef NOT_X_WINDOWS - list[0] = state->pasteout_data_utf8; - if (inst->disp && Xutf8TextListToTextProperty( - inst->disp, list, 1, XCompoundTextStyle, &tp) == 0) { - state->pasteout_data_ctext = snewn(tp.nitems+1, char); - memcpy(state->pasteout_data_ctext, tp.value, tp.nitems); - state->pasteout_data_ctext_len = tp.nitems; - XFree(tp.value); - } else -#endif - { - state->pasteout_data_ctext = NULL; - state->pasteout_data_ctext_len = 0; - } - } else { - state->pasteout_data_utf8 = NULL; - state->pasteout_data_utf8_len = 0; - state->pasteout_data_ctext = NULL; - state->pasteout_data_ctext_len = 0; - } - - state->pasteout_data = snewn(len*6, char); - state->pasteout_data_len = len*6; - state->pasteout_data_len = wc_to_mb(inst->ucsdata.line_codepage, 0, - data, len, state->pasteout_data, - state->pasteout_data_len, - NULL, NULL); - if (state->pasteout_data_len == 0) { - sfree(state->pasteout_data); - state->pasteout_data = NULL; - } else { - state->pasteout_data = - sresize(state->pasteout_data, state->pasteout_data_len, char); - } - -#ifndef NOT_X_WINDOWS - /* The legacy X cut buffers go with PRIMARY, not any other clipboard */ - if (state->atom == GDK_SELECTION_PRIMARY) - store_cutbuffer(inst, state->pasteout_data, state->pasteout_data_len); -#endif - - if (gtk_selection_owner_set(inst->area, state->atom, - inst->input_event_time)) { -#if GTK_CHECK_VERSION(2,0,0) - gtk_selection_clear_targets(inst->area, state->atom); -#endif - gtk_selection_add_target(inst->area, state->atom, - GDK_SELECTION_TYPE_STRING, 1); - if (state->pasteout_data_ctext) - gtk_selection_add_target(inst->area, state->atom, - compound_text_atom, 1); - if (state->pasteout_data_utf8) - gtk_selection_add_target(inst->area, state->atom, - utf8_string_atom, 1); - } - - if (must_deselect) - term_lost_clipboard_ownership(inst->term, clipboard); -} - -static void selection_get(GtkWidget *widget, GtkSelectionData *seldata, - guint info, guint time_stamp, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - GdkAtom target = gtk_selection_data_get_target(seldata); - struct clipboard_state *state = clipboard_from_atom( - inst, gtk_selection_data_get_selection(seldata)); - - if (!state) - return; - - if (target == utf8_string_atom) - gtk_selection_data_set(seldata, target, 8, - (unsigned char *)state->pasteout_data_utf8, - state->pasteout_data_utf8_len); - else if (target == compound_text_atom) - gtk_selection_data_set(seldata, target, 8, - (unsigned char *)state->pasteout_data_ctext, - state->pasteout_data_ctext_len); - else - gtk_selection_data_set(seldata, target, 8, - (unsigned char *)state->pasteout_data, - state->pasteout_data_len); -} - -static gint selection_clear(GtkWidget *widget, GdkEventSelection *seldata, - gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - struct clipboard_state *state = clipboard_from_atom( - inst, seldata->selection); - - if (!state) - return true; - - term_lost_clipboard_ownership(inst->term, state->clipboard); - if (state->pasteout_data) - sfree(state->pasteout_data); - if (state->pasteout_data_ctext) - sfree(state->pasteout_data_ctext); - if (state->pasteout_data_utf8) - sfree(state->pasteout_data_utf8); - state->pasteout_data = NULL; - state->pasteout_data_len = 0; - state->pasteout_data_ctext = NULL; - state->pasteout_data_ctext_len = 0; - state->pasteout_data_utf8 = NULL; - state->pasteout_data_utf8_len = 0; - return true; -} - -static void gtkwin_clip_request_paste(TermWin *tw, int clipboard) -{ - GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); - struct clipboard_state *state = &inst->clipstates[clipboard]; - - /* - * 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, state->atom, 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, state->atom, - GDK_SELECTION_TYPE_STRING, - inst->input_event_time); - } -} - -static void selection_received(GtkWidget *widget, GtkSelectionData *seldata, - guint time, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - char *text; - int length; -#ifndef NOT_X_WINDOWS - char **list; - bool free_list_required = false; - bool free_required = false; -#endif - int charset; - GdkAtom seldata_target = gtk_selection_data_get_target(seldata); - GdkAtom seldata_type = gtk_selection_data_get_data_type(seldata); - const guchar *seldata_data = gtk_selection_data_get_data(seldata); - gint seldata_length = gtk_selection_data_get_length(seldata); - wchar_t *paste; - int paste_len; - struct clipboard_state *state = clipboard_from_atom( - inst, gtk_selection_data_get_selection(seldata)); - - if (!state) - return; - - 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, state->atom, - 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, state->atom, - 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) { -#ifndef NOT_X_WINDOWS - text = retrieve_cutbuffer(inst, &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 = true; -#else - return; -#endif - } else { - /* - * Convert COMPOUND_TEXT into UTF-8. - */ - if (seldata_type == compound_text_atom) { -#ifndef NOT_X_WINDOWS - XTextProperty tp; - int ret, count; - - tp.value = (unsigned char *)seldata_data; - tp.encoding = (Atom) seldata_type; - tp.format = gtk_selection_data_get_format(seldata); - tp.nitems = seldata_length; - ret = inst->disp == NULL ? -1 : - Xutf8TextPropertyToTextList(inst->disp, &tp, &list, &count); - if (ret == 0 && count == 1) { - text = list[0]; - length = strlen(list[0]); - charset = CS_UTF8; - free_list_required = true; - } else -#endif - { - /* - * Compound text failed; fall back to STRING. - */ - gtk_selection_convert(inst->area, state->atom, - GDK_SELECTION_TYPE_STRING, - inst->input_event_time); - return; - } - } else { - text = (char *)seldata_data; - length = seldata_length; - charset = (seldata_type == utf8_string_atom ? - CS_UTF8 : inst->ucsdata.line_codepage); - } - } - - paste = snewn(length, wchar_t); - paste_len = mb_to_wc(charset, 0, text, length, paste, length); - - term_do_paste(inst->term, paste, paste_len); - - sfree(paste); - -#ifndef NOT_X_WINDOWS - if (free_list_required) - XFreeStringList(list); - if (free_required) - XFree(text); -#endif -} - -static void init_one_clipboard(GtkFrontend *inst, int clipboard) -{ - struct clipboard_state *state = &inst->clipstates[clipboard]; - - state->inst = inst; - state->clipboard = clipboard; -} - -void set_clipboard_atom(GtkFrontend *inst, int clipboard, GdkAtom atom) -{ - struct clipboard_state *state = &inst->clipstates[clipboard]; - - state->inst = inst; - state->clipboard = clipboard; - - state->atom = atom; -} - -void init_clipboard(GtkFrontend *inst) -{ -#ifndef NOT_X_WINDOWS - /* - * Ensure that all the cut buffers exist - according to the ICCCM, - * we must do this before we start using cut buffers. - */ - if (inst->disp) { - unsigned char empty[] = ""; - x11_ignore_error(inst->disp, BadMatch); - XChangeProperty(inst->disp, GDK_ROOT_WINDOW(), XA_CUT_BUFFER0, - XA_STRING, 8, PropModeAppend, empty, 0); - x11_ignore_error(inst->disp, BadMatch); - XChangeProperty(inst->disp, GDK_ROOT_WINDOW(), XA_CUT_BUFFER1, - XA_STRING, 8, PropModeAppend, empty, 0); - x11_ignore_error(inst->disp, BadMatch); - XChangeProperty(inst->disp, GDK_ROOT_WINDOW(), XA_CUT_BUFFER2, - XA_STRING, 8, PropModeAppend, empty, 0); - x11_ignore_error(inst->disp, BadMatch); - XChangeProperty(inst->disp, GDK_ROOT_WINDOW(), XA_CUT_BUFFER3, - XA_STRING, 8, PropModeAppend, empty, 0); - x11_ignore_error(inst->disp, BadMatch); - XChangeProperty(inst->disp, GDK_ROOT_WINDOW(), XA_CUT_BUFFER4, - XA_STRING, 8, PropModeAppend, empty, 0); - x11_ignore_error(inst->disp, BadMatch); - XChangeProperty(inst->disp, GDK_ROOT_WINDOW(), XA_CUT_BUFFER5, - XA_STRING, 8, PropModeAppend, empty, 0); - x11_ignore_error(inst->disp, BadMatch); - XChangeProperty(inst->disp, GDK_ROOT_WINDOW(), XA_CUT_BUFFER6, - XA_STRING, 8, PropModeAppend, empty, 0); - x11_ignore_error(inst->disp, BadMatch); - XChangeProperty(inst->disp, GDK_ROOT_WINDOW(), XA_CUT_BUFFER7, - XA_STRING, 8, PropModeAppend, empty, 0); - } -#endif - - inst->clipstates[CLIP_PRIMARY].atom = GDK_SELECTION_PRIMARY; - inst->clipstates[CLIP_CLIPBOARD].atom = clipboard_atom; - init_one_clipboard(inst, CLIP_PRIMARY); - init_one_clipboard(inst, CLIP_CLIPBOARD); - - g_signal_connect(G_OBJECT(inst->area), "selection_received", - G_CALLBACK(selection_received), inst); - g_signal_connect(G_OBJECT(inst->area), "selection_get", - G_CALLBACK(selection_get), inst); - g_signal_connect(G_OBJECT(inst->area), "selection_clear_event", - G_CALLBACK(selection_clear), inst); -} - -/* - * End of selection/clipboard handling. - * ---------------------------------------------------------------------- - */ - -#endif /* JUST_USE_GTK_CLIPBOARD_UTF8 */ - -static void set_window_titles(GtkFrontend *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 (!conf_get_bool(inst->conf, CONF_win_name_always)) - gdk_window_set_icon_name(gtk_widget_get_window(inst->window), - inst->icontitle); -} - -static void gtkwin_set_title(TermWin *tw, const char *title) -{ - GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); - sfree(inst->wintitle); - inst->wintitle = dupstr(title); - set_window_titles(inst); -} - -static void gtkwin_set_icon_title(TermWin *tw, const char *title) -{ - GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); - sfree(inst->icontitle); - inst->icontitle = dupstr(title); - set_window_titles(inst); -} - -static void gtkwin_set_scrollbar(TermWin *tw, int total, int start, int page) -{ - GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); - if (!conf_get_bool(inst->conf, CONF_scrollbar)) - return; - inst->ignore_sbar = true; - gtk_adjustment_set_lower(inst->sbar_adjust, 0); - gtk_adjustment_set_upper(inst->sbar_adjust, total); - gtk_adjustment_set_value(inst->sbar_adjust, start); - gtk_adjustment_set_page_size(inst->sbar_adjust, page); - gtk_adjustment_set_step_increment(inst->sbar_adjust, 1); - gtk_adjustment_set_page_increment(inst->sbar_adjust, page/2); -#if !GTK_CHECK_VERSION(3,18,0) - gtk_adjustment_changed(inst->sbar_adjust); -#endif - inst->ignore_sbar = false; -} - -void scrollbar_moved(GtkAdjustment *adj, GtkFrontend *inst) -{ - if (!conf_get_bool(inst->conf, CONF_scrollbar)) - return; - if (!inst->ignore_sbar) - term_scroll(inst->term, 1, (int)gtk_adjustment_get_value(adj)); -} - -static void show_scrollbar(GtkFrontend *inst, gboolean visible) -{ - inst->sbar_visible = visible; - if (visible) - gtk_widget_show(inst->sbar); - else - gtk_widget_hide(inst->sbar); -} - -static void gtkwin_set_cursor_pos(TermWin *tw, 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). - */ -static void gtkwin_bell(TermWin *tw, int mode) -{ - /* GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); */ - if (mode == BELL_DEFAULT) - gdk_display_beep(gdk_display_get_default()); -} - -static int gtkwin_char_width(TermWin *tw, int uc) -{ - /* - * In this front end, double-width characters are handled using a - * separate font, so this can safely just return 1 always. - */ - return 1; -} - -static bool gtkwin_setup_draw_ctx(TermWin *tw) -{ - GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); - - if (!gtk_widget_get_window(inst->area)) - return false; - - inst->uctx.type = inst->drawtype; -#ifdef DRAW_TEXT_GDK - if (inst->uctx.type == DRAWTYPE_GDK) { - /* If we're doing GDK-based drawing, then we also expect - * inst->pixmap to exist. */ - inst->uctx.u.gdk.target = inst->pixmap; - inst->uctx.u.gdk.gc = gdk_gc_new(gtk_widget_get_window(inst->area)); - } -#endif -#ifdef DRAW_TEXT_CAIRO - if (inst->uctx.type == DRAWTYPE_CAIRO) { - inst->uctx.u.cairo.widget = GTK_WIDGET(inst->area); - /* If we're doing Cairo drawing, we expect inst->surface to - * exist, and we draw to that first, regardless of whether we - * subsequently copy the results to inst->pixmap. */ - inst->uctx.u.cairo.cr = cairo_create(inst->surface); - cairo_scale(inst->uctx.u.cairo.cr, inst->scale, inst->scale); - cairo_setup_draw_ctx(inst); - } -#endif - return true; -} - -static void gtkwin_free_draw_ctx(TermWin *tw) -{ - GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); -#ifdef DRAW_TEXT_GDK - if (inst->uctx.type == DRAWTYPE_GDK) { - gdk_gc_unref(inst->uctx.u.gdk.gc); - } -#endif -#ifdef DRAW_TEXT_CAIRO - if (inst->uctx.type == DRAWTYPE_CAIRO) { - cairo_destroy(inst->uctx.u.cairo.cr); - } -#endif -} - - -static void draw_update(GtkFrontend *inst, int x, int y, int w, int h) -{ -#if defined DRAW_TEXT_CAIRO && !defined NO_BACKING_PIXMAPS - if (inst->uctx.type == DRAWTYPE_CAIRO) { - /* - * If inst->surface and inst->pixmap both exist, then we've - * just drawn new content to the former which we must copy to - * the latter. - */ - cairo_t *cr = gdk_cairo_create(inst->pixmap); - cairo_set_source_surface(cr, inst->surface, 0, 0); - cairo_rectangle(cr, x, y, w, h); - cairo_fill(cr); - cairo_destroy(cr); - } -#endif - - /* - * Now we just queue a window redraw, which will cause - * inst->surface or inst->pixmap (whichever is appropriate for our - * compile mode) to be copied to the real window when we receive - * the resulting "expose" or "draw" event. - * - * Amazingly, this one API call is actually valid in all versions - * of GTK :-) - */ - gtk_widget_queue_draw_area(inst->area, x, y, w, h); -} - -#ifdef DRAW_TEXT_CAIRO -static void cairo_set_source_rgb_dim(cairo_t *cr, double r, double g, double b, - bool dim) -{ - if (dim) - cairo_set_source_rgb(cr, r * 2 / 3, g * 2 / 3, b * 2 / 3); - else - cairo_set_source_rgb(cr, r, g, b); -} -#endif - -static void draw_set_colour(GtkFrontend *inst, int col, bool dim) -{ -#ifdef DRAW_TEXT_GDK - if (inst->uctx.type == DRAWTYPE_GDK) { - if (dim) { -#if GTK_CHECK_VERSION(2,0,0) - GdkColor color; - color.red = inst->cols[col].red * 2 / 3; - color.green = inst->cols[col].green * 2 / 3; - color.blue = inst->cols[col].blue * 2 / 3; - gdk_gc_set_rgb_fg_color(inst->uctx.u.gdk.gc, &color); -#else - /* Poor GTK1 fallback */ - gdk_gc_set_foreground(inst->uctx.u.gdk.gc, &inst->cols[col]); -#endif - } else { - gdk_gc_set_foreground(inst->uctx.u.gdk.gc, &inst->cols[col]); - } - } -#endif -#ifdef DRAW_TEXT_CAIRO - if (inst->uctx.type == DRAWTYPE_CAIRO) { - cairo_set_source_rgb_dim(inst->uctx.u.cairo.cr, - inst->cols[col].red / 65535.0, - inst->cols[col].green / 65535.0, - inst->cols[col].blue / 65535.0, dim); - } -#endif -} - -static void draw_set_colour_rgb(GtkFrontend *inst, optionalrgb orgb, bool dim) -{ -#ifdef DRAW_TEXT_GDK - if (inst->uctx.type == DRAWTYPE_GDK) { -#if GTK_CHECK_VERSION(2,0,0) - GdkColor color; - color.red = orgb.r * 256; - color.green = orgb.g * 256; - color.blue = orgb.b * 256; - if (dim) { - color.red = color.red * 2 / 3; - color.green = color.green * 2 / 3; - color.blue = color.blue * 2 / 3; - } - gdk_gc_set_rgb_fg_color(inst->uctx.u.gdk.gc, &color); -#else - /* Poor GTK1 fallback */ - gdk_gc_set_foreground(inst->uctx.u.gdk.gc, &inst->cols[256]); -#endif - } -#endif -#ifdef DRAW_TEXT_CAIRO - if (inst->uctx.type == DRAWTYPE_CAIRO) { - cairo_set_source_rgb_dim(inst->uctx.u.cairo.cr, orgb.r / 255.0, - orgb.g / 255.0, orgb.b / 255.0, dim); - } -#endif -} - -static void draw_rectangle(GtkFrontend *inst, bool filled, - int x, int y, int w, int h) -{ -#ifdef DRAW_TEXT_GDK - if (inst->uctx.type == DRAWTYPE_GDK) { - gdk_draw_rectangle(inst->uctx.u.gdk.target, inst->uctx.u.gdk.gc, - filled, x, y, w, h); - } -#endif -#ifdef DRAW_TEXT_CAIRO - if (inst->uctx.type == DRAWTYPE_CAIRO) { - cairo_new_path(inst->uctx.u.cairo.cr); - if (filled) { - cairo_rectangle(inst->uctx.u.cairo.cr, x, y, w, h); - cairo_fill(inst->uctx.u.cairo.cr); - } else { - cairo_rectangle(inst->uctx.u.cairo.cr, - x + 0.5, y + 0.5, w, h); - cairo_close_path(inst->uctx.u.cairo.cr); - cairo_stroke(inst->uctx.u.cairo.cr); - } - } -#endif -} - -static void draw_clip(GtkFrontend *inst, int x, int y, int w, int h) -{ -#ifdef DRAW_TEXT_GDK - if (inst->uctx.type == DRAWTYPE_GDK) { - GdkRectangle r; - - r.x = x; - r.y = y; - r.width = w; - r.height = h; - - gdk_gc_set_clip_rectangle(inst->uctx.u.gdk.gc, &r); - } -#endif -#ifdef DRAW_TEXT_CAIRO - if (inst->uctx.type == DRAWTYPE_CAIRO) { - cairo_reset_clip(inst->uctx.u.cairo.cr); - cairo_new_path(inst->uctx.u.cairo.cr); - cairo_rectangle(inst->uctx.u.cairo.cr, x, y, w, h); - cairo_clip(inst->uctx.u.cairo.cr); - } -#endif -} - -static void draw_point(GtkFrontend *inst, int x, int y) -{ -#ifdef DRAW_TEXT_GDK - if (inst->uctx.type == DRAWTYPE_GDK) { - gdk_draw_point(inst->uctx.u.gdk.target, inst->uctx.u.gdk.gc, x, y); - } -#endif -#ifdef DRAW_TEXT_CAIRO - if (inst->uctx.type == DRAWTYPE_CAIRO) { - cairo_new_path(inst->uctx.u.cairo.cr); - cairo_rectangle(inst->uctx.u.cairo.cr, x, y, 1, 1); - cairo_fill(inst->uctx.u.cairo.cr); - } -#endif -} - -static void draw_line(GtkFrontend *inst, int x0, int y0, int x1, int y1) -{ -#ifdef DRAW_TEXT_GDK - if (inst->uctx.type == DRAWTYPE_GDK) { - gdk_draw_line(inst->uctx.u.gdk.target, inst->uctx.u.gdk.gc, - x0, y0, x1, y1); - } -#endif -#ifdef DRAW_TEXT_CAIRO - if (inst->uctx.type == DRAWTYPE_CAIRO) { - cairo_new_path(inst->uctx.u.cairo.cr); - cairo_move_to(inst->uctx.u.cairo.cr, x0 + 0.5, y0 + 0.5); - cairo_line_to(inst->uctx.u.cairo.cr, x1 + 0.5, y1 + 0.5); - cairo_stroke(inst->uctx.u.cairo.cr); - } -#endif -} - -static void draw_stretch_before(GtkFrontend *inst, int x, int y, - int w, bool wdouble, - int h, bool hdouble, bool hbothalf) -{ -#ifdef DRAW_TEXT_CAIRO - if (inst->uctx.type == DRAWTYPE_CAIRO) { - cairo_matrix_t matrix; - - matrix.xy = 0; - matrix.yx = 0; - - if (wdouble) { - matrix.xx = 2; - matrix.x0 = -x; - } else { - matrix.xx = 1; - matrix.x0 = 0; - } - - if (hdouble) { - matrix.yy = 2; - if (hbothalf) { - matrix.y0 = -(y+h); - } else { - matrix.y0 = -y; - } - } else { - matrix.yy = 1; - matrix.y0 = 0; - } - cairo_transform(inst->uctx.u.cairo.cr, &matrix); - } -#endif -} - -static void draw_stretch_after(GtkFrontend *inst, int x, int y, - int w, bool wdouble, - int h, bool hdouble, bool hbothalf) -{ -#ifdef DRAW_TEXT_GDK -#ifndef NO_BACKING_PIXMAPS - if (inst->uctx.type == DRAWTYPE_GDK) { - /* - * 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; - if (wdouble) { - for (i = 0; i < w; i++) { - gdk_draw_pixmap(inst->uctx.u.gdk.target, - inst->uctx.u.gdk.gc, - inst->uctx.u.gdk.target, - x + 2*i, y, - x + 2*i+1, y, - w - i, h); - } - w *= 2; - } - - if (hdouble) { - int dt, db; - /* Now stretch vertically, in the same way. */ - if (hbothalf) - dt = 0, db = 1; - else - dt = 1, db = 0; - for (i = 0; i < h; i += 2) { - gdk_draw_pixmap(inst->uctx.u.gdk.target, - inst->uctx.u.gdk.gc, - inst->uctx.u.gdk.target, - x, y + dt*i + db, - x, y + dt*(i+1), - w, h-i-1); - } - } - } -#else -#error No way to implement stretching in GDK without a reliable backing pixmap -#endif -#endif /* DRAW_TEXT_GDK */ -#ifdef DRAW_TEXT_CAIRO - if (inst->uctx.type == DRAWTYPE_CAIRO) { - cairo_set_matrix(inst->uctx.u.cairo.cr, - &inst->uctx.u.cairo.origmatrix); - } -#endif -} - -static void draw_backing_rect(GtkFrontend *inst) -{ - int w, h; - - if (!win_setup_draw_ctx(&inst->termwin)) - return; - - w = inst->width * inst->font_width + 2*inst->window_border; - h = inst->height * inst->font_height + 2*inst->window_border; - draw_set_colour(inst, 258, false); - draw_rectangle(inst, true, 0, 0, w, h); - draw_update(inst, 0, 0, w, h); - win_free_draw_ctx(&inst->termwin); -} - -/* - * 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'. - */ -static void do_text_internal( - GtkFrontend *inst, int x, int y, wchar_t *text, int len, - unsigned long attr, int lattr, truecolour truecolour) -{ - int ncombining; - int nfg, nbg, t, fontid, rlen, widefactor; - bool bold; - bool monochrome = - gdk_visual_get_depth(gtk_widget_get_visual(inst->area)) == 1; - - if (attr & TATTR_COMBINING) { - ncombining = len; - len = 1; - } else - ncombining = 1; - - if (monochrome) - truecolour.fg = truecolour.bg = optionalrgb_none; - - 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))) { - struct optionalrgb trgb; - - t = nfg; - nfg = nbg; - nbg = t; - - trgb = truecolour.fg; - truecolour.fg = truecolour.bg; - truecolour.bg = trgb; - } - if ((inst->bold_style & 2) && (attr & ATTR_BOLD)) { - if (nfg < 16) nfg |= 8; - else if (nfg >= 256) nfg |= 1; - } - if ((inst->bold_style & 2) && (attr & ATTR_BLINK)) { - if (nbg < 16) nbg |= 8; - else if (nbg >= 256) nbg |= 1; - } - if ((attr & TATTR_ACTCURS) && !monochrome) { - truecolour.fg = truecolour.bg = optionalrgb_none; - nfg = 260; - nbg = 261; - attr &= ~ATTR_DIM; /* don't dim the cursor */ - } - - fontid = 0; - - if (attr & ATTR_WIDE) { - widefactor = 2; - fontid |= 2; - } else { - widefactor = 1; - } - - if ((attr & ATTR_BOLD) && (inst->bold_style & 1)) { - bold = true; - fontid |= 1; - } else { - bold = false; - } - - 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 */ - if (len == 0) - return; /* rounded down half a double-width char to zero */ - } - rlen = len * 2; - } else - rlen = len; - - draw_clip(inst, - x*inst->font_width+inst->window_border, - y*inst->font_height+inst->window_border, - rlen*widefactor*inst->font_width, - inst->font_height); - - if ((lattr & LATTR_MODE) != LATTR_NORM) { - draw_stretch_before(inst, - x*inst->font_width+inst->window_border, - y*inst->font_height+inst->window_border, - rlen*widefactor*inst->font_width, true, - inst->font_height, - ((lattr & LATTR_MODE) != LATTR_WIDE), - ((lattr & LATTR_MODE) == LATTR_BOT)); - } - - if (truecolour.bg.enabled) - draw_set_colour_rgb(inst, truecolour.bg, attr & ATTR_DIM); - else - draw_set_colour(inst, nbg, attr & ATTR_DIM); - draw_rectangle(inst, true, - x*inst->font_width+inst->window_border, - y*inst->font_height+inst->window_border, - rlen*widefactor*inst->font_width, inst->font_height); - - if (truecolour.fg.enabled) - draw_set_colour_rgb(inst, truecolour.fg, attr & ATTR_DIM); - else - draw_set_colour(inst, nfg, attr & ATTR_DIM); - if (ncombining > 1) { - assert(len == 1); - unifont_draw_combining(&inst->uctx, inst->fonts[fontid], - x*inst->font_width+inst->window_border, - (y*inst->font_height+inst->window_border+ - inst->fonts[0]->ascent), - text, ncombining, widefactor > 1, - bold, inst->font_width); - } else { - unifont_draw_text(&inst->uctx, inst->fonts[fontid], - x*inst->font_width+inst->window_border, - (y*inst->font_height+inst->window_border+ - inst->fonts[0]->ascent), - text, len, widefactor > 1, - bold, inst->font_width); - } - - if (attr & ATTR_UNDER) { - int uheight = inst->fonts[0]->ascent + 1; - if (uheight >= inst->font_height) - uheight = inst->font_height - 1; - draw_line(inst, x*inst->font_width+inst->window_border, - y*inst->font_height + uheight + inst->window_border, - (x+len)*widefactor*inst->font_width-1+inst->window_border, - y*inst->font_height + uheight + inst->window_border); - } - - if (attr & ATTR_STRIKE) { - int sheight = inst->fonts[fontid]->strikethrough_y; - draw_line(inst, x*inst->font_width+inst->window_border, - y*inst->font_height + sheight + inst->window_border, - (x+len)*widefactor*inst->font_width-1+inst->window_border, - y*inst->font_height + sheight + inst->window_border); - } - - if ((lattr & LATTR_MODE) != LATTR_NORM) { - draw_stretch_after(inst, - x*inst->font_width+inst->window_border, - y*inst->font_height+inst->window_border, - rlen*widefactor*inst->font_width, true, - inst->font_height, - ((lattr & LATTR_MODE) != LATTR_WIDE), - ((lattr & LATTR_MODE) == LATTR_BOT)); - } -} - -static void gtkwin_draw_text( - TermWin *tw, int x, int y, wchar_t *text, int len, - unsigned long attr, int lattr, truecolour truecolour) -{ - GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); - int widefactor; - - do_text_internal(inst, x, y, text, len, attr, lattr, truecolour); - - 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; - } - - draw_update(inst, - x*inst->font_width+inst->window_border, - y*inst->font_height+inst->window_border, - len*widefactor*inst->font_width, inst->font_height); -} - -static void gtkwin_draw_cursor( - TermWin *tw, int x, int y, wchar_t *text, int len, - unsigned long attr, int lattr, truecolour truecolour) -{ - GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); - bool active, passive; - int widefactor; - - if (attr & TATTR_PASCURS) { - attr &= ~TATTR_PASCURS; - passive = true; - } else - passive = false; - if ((attr & TATTR_ACTCURS) && inst->cursor_type != 0) { - attr &= ~TATTR_ACTCURS; - active = true; - } else - active = false; - do_text_internal(inst, x, y, text, len, attr, lattr, truecolour); - - 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->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) { - draw_set_colour(inst, 261, false); - draw_rectangle(inst, false, - x*inst->font_width+inst->window_border, - y*inst->font_height+inst->window_border, - len*widefactor*inst->font_width-1, - inst->font_height-1); - } - } else { - int uheight; - int startx, starty, dx, dy, length, i; - - 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->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->window_border; - starty = y * inst->font_height + inst->window_border + uheight; - dx = 1; - dy = 0; - length = len * widefactor * char_width; - } else { - int xadjust = 0; - if (attr & TATTR_RIGHTCURS) - xadjust = char_width - 1; - startx = x * inst->font_width + inst->window_border + xadjust; - starty = y * inst->font_height + inst->window_border; - dx = 0; - dy = 1; - length = inst->font_height; - } - - draw_set_colour(inst, 261, false); - if (passive) { - for (i = 0; i < length; i++) { - if (i % 2 == 0) { - draw_point(inst, startx, starty); - } - startx += dx; - starty += dy; - } - } else if (active) { - draw_line(inst, startx, starty, - startx + (length-1) * dx, starty + (length-1) * dy); - } /* else no cursor (e.g., blinked off) */ - } - - draw_update(inst, - x*inst->font_width+inst->window_border, - y*inst->font_height+inst->window_border, - len*widefactor*inst->font_width, inst->font_height); - -#if GTK_CHECK_VERSION(2,0,0) - { - GdkRectangle cursorrect; - cursorrect.x = x*inst->font_width+inst->window_border; - cursorrect.y = y*inst->font_height+inst->window_border; - cursorrect.width = len*widefactor*inst->font_width; - cursorrect.height = inst->font_height; - gtk_im_context_set_cursor_location(inst->imc, &cursorrect); - } -#endif -} - -#if !GTK_CHECK_VERSION(2,0,0) -/* - * For GTK 1, manual code to scale an in-memory XPM, producing a new - * one as output. It will be ugly, but good enough to use as a trust - * sigil. - */ -struct XpmHolder { - char **strings; - size_t nstrings; -}; - -static void xpmholder_free(XpmHolder *xh) -{ - for (size_t i = 0; i < xh->nstrings; i++) - sfree(xh->strings[i]); - sfree(xh->strings); - sfree(xh); -} - -static XpmHolder *xpm_scale(const char *const *xpm, int wo, int ho) -{ - /* Get image dimensions, # colours, and chars-per-pixel */ - int wi = 0, hi = 0, nc = 0, cpp = 0; - int retd = sscanf(xpm[0], "%d %d %d %d", &wi, &hi, &nc, &cpp); - assert(retd == 4); - - /* Make output XpmHolder */ - XpmHolder *xh = snew(XpmHolder); - xh->nstrings = 1 + nc + ho; - xh->strings = snewn(xh->nstrings, char *); - - /* Set up header */ - xh->strings[0] = dupprintf("%d %d %d %d", wo, ho, nc, cpp); - for (int i = 0; i < nc; i++) - xh->strings[1 + i] = dupstr(xpm[1 + i]); - - /* Scale image */ - for (int yo = 0; yo < ho; yo++) { - int yi = yo * hi / ho; - char *ro = snewn(cpp * wo + 1, char); - ro[cpp * wo] = '\0'; - xh->strings[1 + nc + yo] = ro; - const char *ri = xpm[1 + nc + yi]; - - for (int xo = 0; xo < wo; xo++) { - int xi = xo * wi / wo; - memcpy(ro + cpp * xo, ri + cpp * xi, cpp); - } - } - - return xh; -} -#endif /* !GTK_CHECK_VERSION(2,0,0) */ - -static void gtkwin_draw_trust_sigil(TermWin *tw, int cx, int cy) -{ - GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); - - int x = cx * inst->font_width + inst->window_border; - int y = cy * inst->font_height + inst->window_border; - int w = 2*inst->font_width, h = inst->font_height; - - if (inst->trust_sigil_w != w || inst->trust_sigil_h != h || -#if GTK_CHECK_VERSION(2,0,0) - !inst->trust_sigil_pb -#else - !inst->trust_sigil_pm -#endif - ) { - -#if GTK_CHECK_VERSION(2,0,0) - if (inst->trust_sigil_pb) - g_object_unref(G_OBJECT(inst->trust_sigil_pb)); -#else - if (inst->trust_sigil_pm) - gdk_pixmap_unref(inst->trust_sigil_pm); -#endif - - int best_icon_index = 0; - unsigned score = UINT_MAX; - for (int i = 0; i < n_main_icon; i++) { - int iw, ih; - if (sscanf(main_icon[i][0], "%d %d", &iw, &ih) == 2) { - int this_excess = (iw + ih) - (w + h); - unsigned this_score = (abs(this_excess) | - (this_excess > 0 ? 0 : 0x80000000U)); - if (this_score < score) { - best_icon_index = i; - score = this_score; - } - } - } - -#if GTK_CHECK_VERSION(2,0,0) - GdkPixbuf *icon_unscaled = gdk_pixbuf_new_from_xpm_data( - (const gchar **)main_icon[best_icon_index]); - inst->trust_sigil_pb = gdk_pixbuf_scale_simple( - icon_unscaled, w, h, GDK_INTERP_BILINEAR); - g_object_unref(G_OBJECT(icon_unscaled)); -#else - XpmHolder *xh = xpm_scale(main_icon[best_icon_index], w, h); - inst->trust_sigil_pm = gdk_pixmap_create_from_xpm_d( - gtk_widget_get_window(inst->window), NULL, - &inst->cols[258], xh->strings); - xpmholder_free(xh); -#endif - - inst->trust_sigil_w = w; - inst->trust_sigil_h = h; - } - -#ifdef DRAW_TEXT_GDK - if (inst->uctx.type == DRAWTYPE_GDK) { -#if GTK_CHECK_VERSION(2,0,0) - gdk_draw_pixbuf(inst->uctx.u.gdk.target, inst->uctx.u.gdk.gc, - inst->trust_sigil_pb, 0, 0, x, y, w, h, - GDK_RGB_DITHER_NORMAL, 0, 0); -#else - gdk_draw_pixmap(inst->uctx.u.gdk.target, inst->uctx.u.gdk.gc, - inst->trust_sigil_pm, 0, 0, x, y, w, h); -#endif - } -#endif -#ifdef DRAW_TEXT_CAIRO - if (inst->uctx.type == DRAWTYPE_CAIRO) { - inst->uctx.u.cairo.widget = GTK_WIDGET(inst->area); - cairo_save(inst->uctx.u.cairo.cr); - cairo_translate(inst->uctx.u.cairo.cr, x, y); - gdk_cairo_set_source_pixbuf(inst->uctx.u.cairo.cr, - inst->trust_sigil_pb, 0, 0); - cairo_rectangle(inst->uctx.u.cairo.cr, 0, 0, w, h); - cairo_fill(inst->uctx.u.cairo.cr); - cairo_restore(inst->uctx.u.cairo.cr); - } -#endif - - draw_update(inst, x, y, w, h); -} - -GdkCursor *make_mouse_ptr(GtkFrontend *inst, int cursor_val) -{ - if (cursor_val == -1) { -#if GTK_CHECK_VERSION(2,16,0) - cursor_val = GDK_BLANK_CURSOR; -#else - /* - * Work around absence of GDK_BLANK_CURSOR by inventing a - * blank pixmap. - */ - GdkCursor *ret; - GdkColor bg = { 0, 0, 0, 0 }; - GdkPixmap *pm = gdk_pixmap_new(NULL, 1, 1, 1); - GdkGC *gc = gdk_gc_new(pm); - gdk_gc_set_foreground(gc, &bg); - gdk_draw_rectangle(pm, gc, 1, 0, 0, 1, 1); - gdk_gc_unref(gc); - ret = gdk_cursor_new_from_pixmap(pm, pm, &bg, &bg, 1, 1); - gdk_pixmap_unref(pm); - return ret; -#endif - } - - return gdk_cursor_new(cursor_val); -} - -void modalfatalbox(const 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); -} - -static const char *gtk_seat_get_x_display(Seat *seat) -{ - return gdk_get_display(); -} - -#ifndef NOT_X_WINDOWS -static bool gtk_seat_get_windowid(Seat *seat, long *id) -{ - GtkFrontend *inst = container_of(seat, GtkFrontend, seat); - GdkWindow *window = gtk_widget_get_window(inst->area); - if (!GDK_IS_X11_WINDOW(window)) - return false; - *id = GDK_WINDOW_XID(window); - return true; -} -#endif - -char *setup_fonts_ucs(GtkFrontend *inst) -{ - bool shadowbold = conf_get_bool(inst->conf, CONF_shadowbold); - int shadowboldoffset = conf_get_int(inst->conf, CONF_shadowboldoffset); - FontSpec *fs; - unifont *fonts[4]; - int i; - - fs = conf_get_fontspec(inst->conf, CONF_font); - fonts[0] = multifont_create(inst->area, fs->name, false, false, - shadowboldoffset, shadowbold); - if (!fonts[0]) { - return dupprintf("unable to load font \"%s\"", fs->name); - } - - fs = conf_get_fontspec(inst->conf, CONF_boldfont); - if (shadowbold || !fs->name[0]) { - fonts[1] = NULL; - } else { - fonts[1] = multifont_create(inst->area, fs->name, false, true, - shadowboldoffset, shadowbold); - if (!fonts[1]) { - if (fonts[0]) - unifont_destroy(fonts[0]); - return dupprintf("unable to load bold font \"%s\"", fs->name); - } - } - - fs = conf_get_fontspec(inst->conf, CONF_widefont); - if (fs->name[0]) { - fonts[2] = multifont_create(inst->area, fs->name, true, false, - shadowboldoffset, shadowbold); - if (!fonts[2]) { - for (i = 0; i < 2; i++) - if (fonts[i]) - unifont_destroy(fonts[i]); - return dupprintf("unable to load wide font \"%s\"", fs->name); - } - } else { - fonts[2] = NULL; - } - - fs = conf_get_fontspec(inst->conf, CONF_wideboldfont); - if (shadowbold || !fs->name[0]) { - fonts[3] = NULL; - } else { - fonts[3] = multifont_create(inst->area, fs->name, true, true, - shadowboldoffset, shadowbold); - if (!fonts[3]) { - for (i = 0; i < 3; i++) - if (fonts[i]) - unifont_destroy(fonts[i]); - return dupprintf("unable to load wide bold font \"%s\"", fs->name); - } - } - - /* - * Now we've got past all the possible error conditions, we can - * actually update our state. - */ - - for (i = 0; i < 4; i++) { - if (inst->fonts[i]) - unifont_destroy(inst->fonts[i]); - inst->fonts[i] = fonts[i]; - } - - if (inst->font_width != inst->fonts[0]->width || - inst->font_height != inst->fonts[0]->height) { - - inst->font_width = inst->fonts[0]->width; - inst->font_height = inst->fonts[0]->height; - - /* - * The font size has changed, so force the next call to - * drawing_area_setup to regenerate the backing surface. - */ - inst->drawing_area_setup_needed = true; - } - - inst->direct_to_font = init_ucs(&inst->ucsdata, - conf_get_str(inst->conf, CONF_line_codepage), - conf_get_bool(inst->conf, CONF_utf8_override), - inst->fonts[0]->public_charset, - conf_get_int(inst->conf, CONF_vtmode)); - - inst->drawtype = inst->fonts[0]->preferred_drawtype; - - return NULL; -} - -#if GTK_CHECK_VERSION(3,0,0) -struct find_app_menu_bar_ctx { - GtkWidget *area, *menubar; -}; -static void find_app_menu_bar(GtkWidget *widget, gpointer data) -{ - struct find_app_menu_bar_ctx *ctx = (struct find_app_menu_bar_ctx *)data; - if (widget != ctx->area && GTK_IS_MENU_BAR(widget)) - ctx->menubar = widget; -} -#endif - -static void compute_geom_hints(GtkFrontend *inst, GdkGeometry *geom) -{ - /* - * Unused fields in geom. - */ - geom->max_width = geom->max_height = -1; - geom->min_aspect = geom->max_aspect = 0; - - /* - * Set up the geometry fields we care about, with reference to - * just the drawing area. We'll correct for other widgets in a - * moment. - */ - geom->min_width = inst->font_width + 2*inst->window_border; - geom->min_height = inst->font_height + 2*inst->window_border; - geom->base_width = 2*inst->window_border; - geom->base_height = 2*inst->window_border; - geom->width_inc = inst->font_width; - geom->height_inc = inst->font_height; - - /* - * If we've got a scrollbar visible, then we must include its - * width as part of the base and min width, and also ensure that - * our window's minimum height is at least the height required by - * the scrollbar. - * - * In the latter case, we must also take care to arrange that - * (geom->min_height - geom->base_height) is an integer multiple of - * geom->height_inc, because if it's not, then some window managers - * (we know of xfwm4) get confused, with the effect that they - * resize our window to a height based on min_height instead of - * base_height, which we then round down and the window ends up - * too short. - */ - if (inst->sbar_visible) { - GtkRequisition req; - int min_sb_height; - -#if GTK_CHECK_VERSION(3,0,0) - gtk_widget_get_preferred_size(inst->sbar, &req, NULL); -#else - gtk_widget_size_request(inst->sbar, &req); -#endif - - /* Compute rounded-up scrollbar height. */ - min_sb_height = req.height; - min_sb_height += geom->height_inc - 1; - min_sb_height -= ((min_sb_height - geom->base_height%geom->height_inc) - % geom->height_inc); - - geom->min_width += req.width; - geom->base_width += req.width; - if (geom->min_height < min_sb_height) - geom->min_height = min_sb_height; - } - -#if GTK_CHECK_VERSION(3,0,0) - /* - * And if we're running a gtkapp.c based program and - * GtkApplicationWindow has given us a menu bar inside the window, - * then we must take that into account as well. - * - * In its unbounded wisdom, GtkApplicationWindow doesn't actually - * give us a direct function call to _find_ the menu bar widget. - * Fortunately, we can find it by enumerating the children of the - * top-level window and looking for one we didn't put there - * ourselves. - */ - { - struct find_app_menu_bar_ctx ctx[1]; - ctx->area = inst->area; - ctx->menubar = NULL; - gtk_container_foreach(GTK_CONTAINER(inst->window), - find_app_menu_bar, ctx); - - if (ctx->menubar) { - GtkRequisition req; - int min_menu_width; - gtk_widget_get_preferred_size(ctx->menubar, NULL, &req); - - /* - * This time, the height adjustment is easy (the menu bar - * sits above everything), but we have to take care with - * the _width_ to ensure we keep min_width and base_width - * congruent modulo width_inc. - */ - geom->min_height += req.height; - geom->base_height += req.height; - - min_menu_width = req.width; - min_menu_width += geom->width_inc - 1; - min_menu_width -= - ((min_menu_width - geom->base_width%geom->width_inc) - % geom->width_inc); - if (geom->min_width < min_menu_width) - geom->min_width = min_menu_width; - } - } -#endif -} - -void set_geom_hints(GtkFrontend *inst) -{ - const struct BackendVtable *vt; - GdkGeometry geom; - gint flags = GDK_HINT_MIN_SIZE | GDK_HINT_BASE_SIZE | GDK_HINT_RESIZE_INC; - compute_geom_hints(inst, &geom); -#if GTK_CHECK_VERSION(2,0,0) - if (inst->gotpos) - flags |= GDK_HINT_USER_POS; -#endif - vt = backend_vt_from_proto(conf_get_int(inst->conf, CONF_protocol)); - if (vt && vt->flags & BACKEND_RESIZE_FORBIDDEN) { - /* Window resizing forbidden. Set both minimum and maximum - * dimensions to be the initial size. */ - geom.min_width = inst->width*inst->font_width + 2*inst->window_border; - geom.min_height = inst->height*inst->font_height + 2*inst->window_border; - geom.max_width = geom.min_width; - geom.max_height = geom.min_height; - flags |= GDK_HINT_MAX_SIZE; - } - gtk_window_set_geometry_hints(GTK_WINDOW(inst->window), - NULL, &geom, flags); -} - -#if GTK_CHECK_VERSION(2,0,0) -static void compute_whole_window_size(GtkFrontend *inst, - int wchars, int hchars, - int *wpix, int *hpix) -{ - GdkGeometry geom; - compute_geom_hints(inst, &geom); - if (wpix) *wpix = geom.base_width + wchars * geom.width_inc; - if (hpix) *hpix = geom.base_height + hchars * geom.height_inc; -} -#endif - -void clear_scrollback_menuitem(GtkMenuItem *item, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - term_clrsb(inst->term); -} - -void reset_terminal_menuitem(GtkMenuItem *item, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - term_pwron(inst->term, true); - if (inst->ldisc) - ldisc_echoedit_update(inst->ldisc); -} - -void copy_clipboard_menuitem(GtkMenuItem *item, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - static const int clips[] = { MENU_CLIPBOARD }; - term_request_copy(inst->term, clips, lenof(clips)); -} - -void paste_clipboard_menuitem(GtkMenuItem *item, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - term_request_paste(inst->term, MENU_CLIPBOARD); -} - -void copy_all_menuitem(GtkMenuItem *item, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - static const int clips[] = { COPYALL_CLIPBOARDS }; - term_copyall(inst->term, clips, lenof(clips)); -} - -void special_menuitem(GtkMenuItem *item, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - SessionSpecial *sc = g_object_get_data(G_OBJECT(item), "user-data"); - - if (inst->backend) - backend_special(inst->backend, sc->code, sc->arg); -} - -void about_menuitem(GtkMenuItem *item, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - about_box(inst->window); -} - -void event_log_menuitem(GtkMenuItem *item, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - showeventlog(inst->eventlogstuff, inst->window); -} - -void setup_clipboards(GtkFrontend *inst, Terminal *term, Conf *conf) -{ - assert(term->mouse_select_clipboards[0] == CLIP_LOCAL); - - term->n_mouse_select_clipboards = 1; - term->mouse_select_clipboards[ - term->n_mouse_select_clipboards++] = MOUSE_SELECT_CLIPBOARD; - - if (conf_get_bool(conf, CONF_mouseautocopy)) { - term->mouse_select_clipboards[ - term->n_mouse_select_clipboards++] = CLIP_CLIPBOARD; - } - - set_clipboard_atom(inst, CLIP_CUSTOM_1, GDK_NONE); - set_clipboard_atom(inst, CLIP_CUSTOM_2, GDK_NONE); - set_clipboard_atom(inst, CLIP_CUSTOM_3, GDK_NONE); - - switch (conf_get_int(conf, CONF_mousepaste)) { - case CLIPUI_IMPLICIT: - term->mouse_paste_clipboard = MOUSE_PASTE_CLIPBOARD; - break; - case CLIPUI_EXPLICIT: - term->mouse_paste_clipboard = CLIP_CLIPBOARD; - break; - case CLIPUI_CUSTOM: - term->mouse_paste_clipboard = CLIP_CUSTOM_1; - set_clipboard_atom(inst, CLIP_CUSTOM_1, - gdk_atom_intern( - conf_get_str(conf, CONF_mousepaste_custom), - false)); - break; - default: - term->mouse_paste_clipboard = CLIP_NULL; - break; - } - - if (conf_get_int(conf, CONF_ctrlshiftins) == CLIPUI_CUSTOM) { - GdkAtom atom = gdk_atom_intern( - conf_get_str(conf, CONF_ctrlshiftins_custom), false); - struct clipboard_state *state = clipboard_from_atom(inst, atom); - if (state) { - inst->clipboard_ctrlshiftins = state->clipboard; - } else { - inst->clipboard_ctrlshiftins = CLIP_CUSTOM_2; - set_clipboard_atom(inst, CLIP_CUSTOM_2, atom); - } - } - - if (conf_get_int(conf, CONF_ctrlshiftcv) == CLIPUI_CUSTOM) { - GdkAtom atom = gdk_atom_intern( - conf_get_str(conf, CONF_ctrlshiftcv_custom), false); - struct clipboard_state *state = clipboard_from_atom(inst, atom); - if (state) { - inst->clipboard_ctrlshiftins = state->clipboard; - } else { - inst->clipboard_ctrlshiftcv = CLIP_CUSTOM_3; - set_clipboard_atom(inst, CLIP_CUSTOM_3, atom); - } - } -} - -struct after_change_settings_dialog_ctx { - GtkFrontend *inst; - Conf *newconf; -}; - -static void after_change_settings_dialog(void *vctx, int retval); - -void change_settings_menuitem(GtkMenuItem *item, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - struct after_change_settings_dialog_ctx *ctx; - GtkWidget *dialog; - char *title; - - if (find_and_raise_dialog(inst, DIALOG_SLOT_RECONFIGURE)) - return; - - title = dupcat(appname, " Reconfiguration"); - - ctx = snew(struct after_change_settings_dialog_ctx); - ctx->inst = inst; - ctx->newconf = conf_copy(inst->conf); - - term_pre_reconfig(inst->term, ctx->newconf); - - dialog = create_config_box( - title, ctx->newconf, true, - inst->backend ? backend_cfg_info(inst->backend) : 0, - after_change_settings_dialog, ctx); - register_dialog(&inst->seat, DIALOG_SLOT_RECONFIGURE, dialog); - - sfree(title); -} - -static void after_change_settings_dialog(void *vctx, int retval) -{ - struct after_change_settings_dialog_ctx ctx = - *(struct after_change_settings_dialog_ctx *)vctx; - GtkFrontend *inst = ctx.inst; - Conf *oldconf = inst->conf, *newconf = ctx.newconf; - bool need_size; - - sfree(vctx); /* we've copied this already */ - - unregister_dialog(&inst->seat, DIALOG_SLOT_RECONFIGURE); - - if (retval > 0) { - inst->conf = newconf; - - /* Pass new config data to the logging module */ - log_reconfig(inst->logctx, inst->conf); - /* - * Flush the line discipline's edit buffer in the case - * where local editing has just been disabled. - */ - if (inst->ldisc) { - ldisc_configure(inst->ldisc, inst->conf); - ldisc_echoedit_update(inst->ldisc); - } - /* Pass new config data to the terminal */ - term_reconfig(inst->term, inst->conf); - setup_clipboards(inst, inst->term, inst->conf); - /* Pass new config data to the back end */ - if (inst->backend) - backend_reconfig(inst->backend, inst->conf); - - cache_conf_values(inst); - - need_size = false; - - /* - * If the scrollbar needs to be shown, hidden, or moved - * from one end to the other of the window, do so now. - */ - if (conf_get_bool(oldconf, CONF_scrollbar) != - conf_get_bool(newconf, CONF_scrollbar)) { - show_scrollbar(inst, conf_get_bool(newconf, CONF_scrollbar)); - need_size = true; - } - if (conf_get_bool(oldconf, CONF_scrollbar_on_left) != - conf_get_bool(newconf, CONF_scrollbar_on_left)) { - gtk_box_reorder_child(inst->hbox, inst->sbar, - conf_get_bool(newconf, CONF_scrollbar_on_left) - ? 0 : 1); - } - - /* - * Redo the whole tangled fonts and Unicode mess if - * necessary. - */ - if (strcmp(conf_get_fontspec(oldconf, CONF_font)->name, - conf_get_fontspec(newconf, CONF_font)->name) || - strcmp(conf_get_fontspec(oldconf, CONF_boldfont)->name, - conf_get_fontspec(newconf, CONF_boldfont)->name) || - strcmp(conf_get_fontspec(oldconf, CONF_widefont)->name, - conf_get_fontspec(newconf, CONF_widefont)->name) || - strcmp(conf_get_fontspec(oldconf, CONF_wideboldfont)->name, - conf_get_fontspec(newconf, CONF_wideboldfont)->name) || - strcmp(conf_get_str(oldconf, CONF_line_codepage), - conf_get_str(newconf, CONF_line_codepage)) || - conf_get_bool(oldconf, CONF_utf8_override) != - conf_get_bool(newconf, CONF_utf8_override) || - conf_get_int(oldconf, CONF_vtmode) != - conf_get_int(newconf, CONF_vtmode) || - conf_get_bool(oldconf, CONF_shadowbold) != - conf_get_bool(newconf, CONF_shadowbold) || - conf_get_int(oldconf, CONF_shadowboldoffset) != - conf_get_int(newconf, CONF_shadowboldoffset)) { - char *errmsg = setup_fonts_ucs(inst); - if (errmsg) { - char *msgboxtext = - dupprintf("Could not change fonts in terminal window: %s\n", - errmsg); - create_message_box( - inst->window, "Font setup error", msgboxtext, - string_width("Could not change fonts in terminal window:"), - false, &buttons_ok, trivial_post_dialog_fn, NULL); - sfree(msgboxtext); - sfree(errmsg); - } else { - need_size = true; - } - } - - /* - * Resize the window. - */ - if (conf_get_int(oldconf, CONF_width) != - conf_get_int(newconf, CONF_width) || - conf_get_int(oldconf, CONF_height) != - conf_get_int(newconf, CONF_height) || - conf_get_int(oldconf, CONF_window_border) != - conf_get_int(newconf, CONF_window_border) || - need_size) { - set_geom_hints(inst); - win_request_resize(&inst->termwin, - conf_get_int(newconf, CONF_width), - conf_get_int(newconf, CONF_height)); - } else { - /* - * The above will have caused a call to term_size() for - * us if it happened. If the user has fiddled with only - * the scrollback size, the above will not have - * happened and we will need an explicit term_size() - * here. - */ - if (conf_get_int(oldconf, CONF_savelines) != - conf_get_int(newconf, CONF_savelines)) - term_size(inst->term, inst->term->rows, inst->term->cols, - conf_get_int(newconf, CONF_savelines)); - } - - term_invalidate(inst->term); - - /* - * We do an explicit full redraw here to ensure the window - * border has been redrawn as well as the text area. - */ - gtk_widget_queue_draw(inst->area); - - conf_free(oldconf); - } else { - conf_free(newconf); - } -} - -static void change_font_size(GtkFrontend *inst, int increment) -{ - static const int conf_keys[lenof(inst->fonts)] = { - CONF_font, CONF_boldfont, CONF_widefont, CONF_wideboldfont, - }; - FontSpec *oldfonts[lenof(inst->fonts)]; - FontSpec *newfonts[lenof(inst->fonts)]; - char *errmsg = NULL; - int i; - - for (i = 0; i < lenof(newfonts); i++) - oldfonts[i] = newfonts[i] = NULL; - - for (i = 0; i < lenof(inst->fonts); i++) { - if (inst->fonts[i]) { - char *newname = unifont_size_increment(inst->fonts[i], increment); - if (!newname) - goto cleanup; - newfonts[i] = fontspec_new(newname); - sfree(newname); - } - } - - for (i = 0; i < lenof(newfonts); i++) { - if (newfonts[i]) { - oldfonts[i] = fontspec_copy( - conf_get_fontspec(inst->conf, conf_keys[i])); - conf_set_fontspec(inst->conf, conf_keys[i], newfonts[i]); - } - } - - errmsg = setup_fonts_ucs(inst); - if (errmsg) - goto cleanup; - - /* Success, so suppress putting everything back */ - for (i = 0; i < lenof(newfonts); i++) { - if (oldfonts[i]) { - fontspec_free(oldfonts[i]); - oldfonts[i] = NULL; - } - } - - set_geom_hints(inst); - win_request_resize(&inst->termwin, conf_get_int(inst->conf, CONF_width), - conf_get_int(inst->conf, CONF_height)); - term_invalidate(inst->term); - gtk_widget_queue_draw(inst->area); - - cleanup: - for (i = 0; i < lenof(oldfonts); i++) { - if (oldfonts[i]) { - conf_set_fontspec(inst->conf, conf_keys[i], oldfonts[i]); - fontspec_free(oldfonts[i]); - } - if (newfonts[i]) - fontspec_free(newfonts[i]); - } - sfree(errmsg); -} - -void dup_session_menuitem(GtkMenuItem *item, gpointer gdata) -{ - GtkFrontend *inst = (GtkFrontend *)gdata; - - launch_duplicate_session(inst->conf); -} - -void new_session_menuitem(GtkMenuItem *item, gpointer data) -{ - launch_new_session(); -} - -void restart_session_menuitem(GtkMenuItem *item, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - - if (!inst->backend) { - logevent(inst->logctx, "----- Session restarted -----"); - term_pwron(inst->term, false); - start_backend(inst); - inst->exited = false; - } -} - -void saved_session_menuitem(GtkMenuItem *item, gpointer data) -{ - char *str = (char *)g_object_get_data(G_OBJECT(item), "user-data"); - - launch_saved_session(str); -} - -void saved_session_freedata(GtkMenuItem *item, gpointer data) -{ - char *str = (char *)g_object_get_data(G_OBJECT(item), "user-data"); - - sfree(str); -} - -void app_menu_action(GtkFrontend *frontend, enum MenuAction action) -{ - GtkFrontend *inst = (GtkFrontend *)frontend; - switch (action) { - case MA_COPY: - copy_clipboard_menuitem(NULL, inst); - break; - case MA_PASTE: - paste_clipboard_menuitem(NULL, inst); - break; - case MA_COPY_ALL: - copy_all_menuitem(NULL, inst); - break; - case MA_DUPLICATE_SESSION: - dup_session_menuitem(NULL, inst); - break; - case MA_RESTART_SESSION: - restart_session_menuitem(NULL, inst); - break; - case MA_CHANGE_SETTINGS: - change_settings_menuitem(NULL, inst); - break; - case MA_CLEAR_SCROLLBACK: - clear_scrollback_menuitem(NULL, inst); - break; - case MA_RESET_TERMINAL: - reset_terminal_menuitem(NULL, inst); - break; - case MA_EVENT_LOG: - event_log_menuitem(NULL, inst); - break; - } -} - -static void update_savedsess_menu(GtkMenuItem *menuitem, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)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); - g_object_set_data(G_OBJECT(menuitem), "user-data", - dupstr(sesslist.sessions[i])); - g_signal_connect(G_OBJECT(menuitem), "activate", - G_CALLBACK(saved_session_menuitem), - inst); - g_signal_connect(G_OBJECT(menuitem), "destroy", - G_CALLBACK(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) -{ -#if GTK_CHECK_VERSION(2,0,0) - GList *iconlist; - int n; -#else - GdkPixmap *iconpm; - GdkBitmap *iconmask; -#endif - - if (!n_icon) - return; - - gtk_widget_realize(window); -#if GTK_CHECK_VERSION(2,0,0) - gtk_window_set_icon(GTK_WINDOW(window), - gdk_pixbuf_new_from_xpm_data((const gchar **)icon[0])); -#else - iconpm = gdk_pixmap_create_from_xpm_d(gtk_widget_get_window(window), - &iconmask, NULL, (gchar **)icon[0]); - gdk_window_set_icon(gtk_widget_get_window(window), NULL, iconpm, iconmask); -#endif - -#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])); - } - gtk_window_set_icon_list(GTK_WINDOW(window), iconlist); -#endif -} - -static void free_special_cmd(gpointer data) { sfree(data); } - -static void gtk_seat_update_specials_menu(Seat *seat) -{ - GtkFrontend *inst = container_of(seat, GtkFrontend, seat); - const SessionSpecial *specials; - - if (inst->backend) - specials = backend_get_specials(inst->backend); - 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 SS_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 SS_EXITMENU: - nesting--; - if (nesting) { - menu = saved_menu; /* XXX lame stacking */ - saved_menu = NULL; - } - break; - case SS_SEP: - menuitem = gtk_menu_item_new(); - break; - default: { - menuitem = gtk_menu_item_new_with_label(specials[i].name); - SessionSpecial *sc = snew(SessionSpecial); - *sc = specials[i]; /* structure copy */ - g_object_set_data_full(G_OBJECT(menuitem), "user-data", - sc, free_special_cmd); - g_signal_connect(G_OBJECT(menuitem), "activate", - G_CALLBACK(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(GtkFrontend *inst) -{ - const struct BackendVtable *vt; - char *error, *realhost; - - vt = select_backend(inst->conf); - - seat_set_trust_status(&inst->seat, true); - error = backend_init(vt, &inst->seat, &inst->backend, - inst->logctx, inst->conf, - conf_get_str(inst->conf, CONF_host), - conf_get_int(inst->conf, CONF_port), - &realhost, - conf_get_bool(inst->conf, CONF_tcp_nodelay), - conf_get_bool(inst->conf, CONF_tcp_keepalives)); - - if (error) { - seat_connection_fatal(&inst->seat, - "Unable to open connection to %s:\n%s", - conf_dest(inst->conf), error); - sfree(error); - inst->exited = true; - return; - } - - term_setup_window_titles(inst->term, realhost); - sfree(realhost); - - term_provide_backend(inst->term, inst->backend); - - inst->ldisc = ldisc_create(inst->conf, inst->term, inst->backend, - &inst->seat); - - gtk_widget_set_sensitive(inst->restartitem, false); -} - -#if GTK_CHECK_VERSION(2,0,0) -static void get_monitor_geometry(GtkWidget *widget, GdkRectangle *geometry) -{ -#if GTK_CHECK_VERSION(3,4,0) - GdkDisplay *display = gtk_widget_get_display(widget); - GdkWindow *gdkwindow = gtk_widget_get_window(widget); -# if GTK_CHECK_VERSION(3,22,0) - GdkMonitor *monitor; - if (gdkwindow) - monitor = gdk_display_get_monitor_at_window(display, gdkwindow); - else - monitor = gdk_display_get_monitor(display, 0); - gdk_monitor_get_geometry(monitor, geometry); -# else - GdkScreen *screen = gdk_display_get_default_screen(display); - gint monitor_num = gdk_screen_get_monitor_at_window(screen, gdkwindow); - gdk_screen_get_monitor_geometry(screen, monitor_num, geometry); -# endif -#else - geometry->x = geometry->y = 0; - geometry->width = gdk_screen_width(); - geometry->height = gdk_screen_height(); -#endif -} -#endif - -static const TermWinVtable gtk_termwin_vt = { - .setup_draw_ctx = gtkwin_setup_draw_ctx, - .draw_text = gtkwin_draw_text, - .draw_cursor = gtkwin_draw_cursor, - .draw_trust_sigil = gtkwin_draw_trust_sigil, - .char_width = gtkwin_char_width, - .free_draw_ctx = gtkwin_free_draw_ctx, - .set_cursor_pos = gtkwin_set_cursor_pos, - .set_raw_mouse_mode = gtkwin_set_raw_mouse_mode, - .set_raw_mouse_mode_pointer = gtkwin_set_raw_mouse_mode_pointer, - .set_scrollbar = gtkwin_set_scrollbar, - .bell = gtkwin_bell, - .clip_write = gtkwin_clip_write, - .clip_request_paste = gtkwin_clip_request_paste, - .refresh = gtkwin_refresh, - .request_resize = gtkwin_request_resize, - .set_title = gtkwin_set_title, - .set_icon_title = gtkwin_set_icon_title, - .set_minimised = gtkwin_set_minimised, - .set_maximised = gtkwin_set_maximised, - .move = gtkwin_move, - .set_zorder = gtkwin_set_zorder, - .palette_set = gtkwin_palette_set, - .palette_get_overrides = gtkwin_palette_get_overrides, -}; - -void new_session_window(Conf *conf, const char *geometry_string) -{ - GtkFrontend *inst; - - prepare_session(conf); - - /* - * Create an instance structure and initialise to zeroes - */ - inst = snew(GtkFrontend); - memset(inst, 0, sizeof(*inst)); -#ifdef JUST_USE_GTK_CLIPBOARD_UTF8 - inst->cdi_headtail.next = inst->cdi_headtail.prev = &inst->cdi_headtail; -#endif - inst->alt_keycode = -1; /* this one needs _not_ to be zero */ - inst->busy_status = BUSY_NOT; - inst->conf = conf; - inst->wintitle = inst->icontitle = NULL; - inst->drawtype = DRAWTYPE_DEFAULT; -#if GTK_CHECK_VERSION(3,4,0) - inst->cumulative_scroll = 0.0; -#endif - inst->drawing_area_setup_needed = true; - - inst->termwin.vt = >k_termwin_vt; - inst->seat.vt = >k_seat_vt; - inst->logpolicy.vt = >k_logpolicy_vt; - -#ifndef NOT_X_WINDOWS - inst->disp = get_x11_display(); - if (geometry_string) { - int flags, x, y; - unsigned int w, h; - flags = XParseGeometry(geometry_string, &x, &y, &w, &h); - if (flags & WidthValue) - conf_set_int(conf, CONF_width, w); - if (flags & HeightValue) - conf_set_int(conf, CONF_height, h); - - if (flags & (XValue | YValue)) { - inst->xpos = x; - inst->ypos = y; - inst->gotpos = true; - inst->gravity = ((flags & XNegative ? 1 : 0) | - (flags & YNegative ? 2 : 0)); - } - } -#endif - - 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); - if (!clipboard_atom) - clipboard_atom = gdk_atom_intern("CLIPBOARD", false); - - inst->area = gtk_drawing_area_new(); - gtk_widget_set_name(GTK_WIDGET(inst->area), "drawing-area"); - - { - char *errmsg = setup_fonts_ucs(inst); - if (errmsg) { - window_setup_error(errmsg); - sfree(errmsg); - gtk_widget_destroy(inst->area); - sfree(inst); - return; - } - } - -#if GTK_CHECK_VERSION(2,0,0) - inst->imc = gtk_im_multicontext_new(); -#endif - - inst->window = make_gtk_toplevel_window(inst); - gtk_widget_set_name(GTK_WIDGET(inst->window), "top-level"); - { - const char *winclass = conf_get_str(inst->conf, CONF_winclass); - if (*winclass) { -#if GTK_CHECK_VERSION(3,22,0) -#ifndef NOT_X_WINDOWS - GdkWindow *gdkwin; - gtk_widget_realize(GTK_WIDGET(inst->window)); - gdkwin = gtk_widget_get_window(GTK_WIDGET(inst->window)); - if (inst->disp && gdk_window_ensure_native(gdkwin)) { - XClassHint *xch = XAllocClassHint(); - xch->res_name = (char *)winclass; - xch->res_class = (char *)winclass; - XSetClassHint(inst->disp, GDK_WINDOW_XID(gdkwin), xch); - XFree(xch); - } -#endif - /* - * If we do have NOT_X_WINDOWS set, then we don't have any - * function in GTK 3.22 equivalent to the above. But then, - * surely in that situation the deprecated - * gtk_window_set_wmclass wouldn't have done anything - * meaningful in previous GTKs either. - */ -#else - gtk_window_set_wmclass(GTK_WINDOW(inst->window), - winclass, winclass); -#endif - } - } - - inst->width = conf_get_int(inst->conf, CONF_width); - inst->height = conf_get_int(inst->conf, CONF_height); - cache_conf_values(inst); - - init_clipboard(inst); - - 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 (conf_get_bool(inst->conf, CONF_scrollbar_on_left)) - gtk_box_pack_start(inst->hbox, inst->sbar, false, false, 0); - gtk_box_pack_start(inst->hbox, inst->area, true, true, 0); - if (!conf_get_bool(inst->conf, CONF_scrollbar_on_left)) - gtk_box_pack_start(inst->hbox, inst->sbar, false, false, 0); - - gtk_container_add(GTK_CONTAINER(inst->window), GTK_WIDGET(inst->hbox)); - - gtk_widget_show(inst->area); - show_scrollbar(inst, conf_get_bool(inst->conf, CONF_scrollbar)); - gtk_widget_show(GTK_WIDGET(inst->hbox)); - - /* - * We must call gtk_widget_realize before setting up the geometry - * hints, so that GtkApplicationWindow will have actually created - * its menu bar (if it's going to) and hence compute_geom_hints - * can find it to take its size into account. - */ - gtk_widget_realize(inst->window); - set_geom_hints(inst); - -#if GTK_CHECK_VERSION(3,0,0) - { - int wp, hp; - compute_whole_window_size(inst, inst->width, inst->height, &wp, &hp); - gtk_window_set_default_size(GTK_WINDOW(inst->window), wp, hp); - } -#else - { - int w = inst->font_width * inst->width + 2*inst->window_border; - int h = inst->font_height * inst->height + 2*inst->window_border; -#if GTK_CHECK_VERSION(2,0,0) - gtk_widget_set_size_request(inst->area, w, h); -#else - gtk_drawing_area_size(GTK_DRAWING_AREA(inst->area), w, h); -#endif - } -#endif - -#if GTK_CHECK_VERSION(2,0,0) - if (inst->gotpos) { - static const GdkGravity gravities[] = { - GDK_GRAVITY_NORTH_WEST, - GDK_GRAVITY_NORTH_EAST, - GDK_GRAVITY_SOUTH_WEST, - GDK_GRAVITY_SOUTH_EAST, - }; - int x = inst->xpos, y = inst->ypos; - int wp, hp; - GdkRectangle monitor_geometry; - compute_whole_window_size(inst, inst->width, inst->height, &wp, &hp); - get_monitor_geometry(GTK_WIDGET(inst->window), &monitor_geometry); - if (inst->gravity & 1) x += (monitor_geometry.width - wp); - if (inst->gravity & 2) y += (monitor_geometry.height - hp); - gtk_window_set_gravity(GTK_WINDOW(inst->window), - gravities[inst->gravity & 3]); - gtk_window_move(GTK_WINDOW(inst->window), x, y); - } -#else - 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); - } -#endif - - g_signal_connect(G_OBJECT(inst->window), "destroy", - G_CALLBACK(destroy), inst); - g_signal_connect(G_OBJECT(inst->window), "delete_event", - G_CALLBACK(delete_window), inst); - g_signal_connect(G_OBJECT(inst->window), "key_press_event", - G_CALLBACK(key_event), inst); - g_signal_connect(G_OBJECT(inst->window), "key_release_event", - G_CALLBACK(key_event), inst); - g_signal_connect(G_OBJECT(inst->window), "focus_in_event", - G_CALLBACK(focus_event), inst); - g_signal_connect(G_OBJECT(inst->window), "focus_out_event", - G_CALLBACK(focus_event), inst); - g_signal_connect(G_OBJECT(inst->area), "realize", - G_CALLBACK(area_realised), inst); - g_signal_connect(G_OBJECT(inst->area), "size_allocate", - G_CALLBACK(area_size_allocate), inst); - g_signal_connect(G_OBJECT(inst->window), "configure_event", - G_CALLBACK(window_configured), inst); -#if GTK_CHECK_VERSION(3,10,0) - g_signal_connect(G_OBJECT(inst->area), "configure_event", - G_CALLBACK(area_configured), inst); -#endif -#if GTK_CHECK_VERSION(3,0,0) - g_signal_connect(G_OBJECT(inst->area), "draw", - G_CALLBACK(draw_area), inst); -#else - g_signal_connect(G_OBJECT(inst->area), "expose_event", - G_CALLBACK(expose_area), inst); -#endif - g_signal_connect(G_OBJECT(inst->area), "button_press_event", - G_CALLBACK(button_event), inst); - g_signal_connect(G_OBJECT(inst->area), "button_release_event", - G_CALLBACK(button_event), inst); -#if GTK_CHECK_VERSION(2,0,0) - g_signal_connect(G_OBJECT(inst->area), "scroll_event", - G_CALLBACK(scroll_event), inst); -#endif - g_signal_connect(G_OBJECT(inst->area), "motion_notify_event", - G_CALLBACK(motion_event), inst); -#if GTK_CHECK_VERSION(2,0,0) - g_signal_connect(G_OBJECT(inst->imc), "commit", - G_CALLBACK(input_method_commit_event), inst); -#endif - if (conf_get_bool(inst->conf, CONF_scrollbar)) - g_signal_connect(G_OBJECT(inst->sbar_adjust), "value_changed", - G_CALLBACK(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 -#if GTK_CHECK_VERSION(3,4,0) - | GDK_SMOOTH_SCROLL_MASK -#endif - ); - - 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; - - 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); \ - g_signal_connect(G_OBJECT(menuitem), "activate", \ - G_CALLBACK(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); - MKSEP(); - MKMENUITEM("Copy to " CLIPNAME_EXPLICIT_OBJECT, - copy_clipboard_menuitem); - MKMENUITEM("Paste from " CLIPNAME_EXPLICIT_OBJECT, - paste_clipboard_menuitem); - MKMENUITEM("Copy All", copy_all_menuitem); - MKSEP(); - s = dupcat("About ", appname); - 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); - inst->currcursor = inst->textcursor; - show_mouseptr(inst, true); - - inst->eventlogstuff = eventlogstuff_new(); - - inst->term = term_init(inst->conf, &inst->ucsdata, &inst->termwin); - setup_clipboards(inst, inst->term, inst->conf); - inst->logctx = log_init(&inst->logpolicy, inst->conf); - term_provide_logctx(inst->term, inst->logctx); - - term_size(inst->term, inst->height, inst->width, - conf_get_int(inst->conf, CONF_savelines)); - -#if GTK_CHECK_VERSION(2,0,0) - /* Delay this signal connection until after inst->term exists */ - g_signal_connect(G_OBJECT(inst->window), "window_state_event", - G_CALLBACK(window_state_event), inst); -#endif - - inst->exited = false; - - start_backend(inst); - - if (inst->ldisc) /* early backend failure might make this NULL already */ - ldisc_echoedit_update(inst->ldisc); /* cause ldisc to notice changes */ -} - -static bool gtk_seat_set_trust_status(Seat *seat, bool trusted) -{ - GtkFrontend *inst = container_of(seat, GtkFrontend, seat); - term_set_trust_status(inst->term, trusted); - return true; -} - -static bool gtk_seat_get_cursor_position(Seat *seat, int *x, int *y) -{ - GtkFrontend *inst = container_of(seat, GtkFrontend, seat); - if (inst->term) { - term_get_cursor_position(inst->term, x, y); - return true; - } - return false; -} diff --git a/unix/keygen-noise.c b/unix/keygen-noise.c new file mode 100644 index 00000000..da5e8f05 --- /dev/null +++ b/unix/keygen-noise.c @@ -0,0 +1,64 @@ +/* + * uxgen.c: Unix implementation of get_heavy_noise() from cmdgen.c. + */ + +#include +#include + +#include +#include + +#include "putty.h" + +char *get_random_data(int len, const char *device) +{ + char *buf = snewn(len, char); + int fd; + int ngot, ret; + + if (!device) { + static const char *const default_devices[] = { + "/dev/urandom", "/dev/random" + }; + size_t i; + + for (i = 0; i < lenof(default_devices); i++) { + if (access(default_devices[i], R_OK) == 0) { + device = default_devices[i]; + break; + } + } + + if (!device) { + sfree(buf); + fprintf(stderr, "puttygen: cannot find a readable " + "random number source; use --random-device\n"); + return NULL; + } + } + + fd = open(device, O_RDONLY); + if (fd < 0) { + sfree(buf); + fprintf(stderr, "puttygen: %s: open: %s\n", + device, strerror(errno)); + return NULL; + } + + ngot = 0; + while (ngot < len) { + ret = read(fd, buf+ngot, len-ngot); + if (ret < 0) { + close(fd); + sfree(buf); + fprintf(stderr, "puttygen: %s: read: %s\n", + device, strerror(errno)); + return NULL; + } + ngot += ret; + } + + close(fd); + + return buf; +} diff --git a/unix/local-proxy.c b/unix/local-proxy.c new file mode 100644 index 00000000..0a637bd9 --- /dev/null +++ b/unix/local-proxy.c @@ -0,0 +1,105 @@ +/* + * uxproxy.c: Unix implementation of platform_new_connection(), + * supporting an OpenSSH-like proxy command. + */ + +#include +#include +#include +#include +#include + +#include "tree234.h" +#include "putty.h" +#include "network.h" +#include "proxy.h" + +Socket *platform_new_connection(SockAddr *addr, const char *hostname, + int port, bool privport, + bool oobinline, bool nodelay, bool keepalive, + Plug *plug, Conf *conf) +{ + char *cmd; + + int to_cmd_pipe[2], from_cmd_pipe[2], cmd_err_pipe[2], pid, proxytype; + int infd, outfd, inerrfd; + + proxytype = conf_get_int(conf, CONF_proxy_type); + if (proxytype != PROXY_CMD && proxytype != PROXY_FUZZ) + return NULL; + + if (proxytype == PROXY_CMD) { + cmd = format_telnet_command(addr, port, conf); + + { + char *logmsg = dupprintf("Starting local proxy command: %s", cmd); + plug_log(plug, PLUGLOG_PROXY_MSG, NULL, 0, logmsg, 0); + sfree(logmsg); + } + + /* + * Create the pipes to the proxy command, and spawn the proxy + * command process. + */ + if (pipe(to_cmd_pipe) < 0 || + pipe(from_cmd_pipe) < 0 || + pipe(cmd_err_pipe) < 0) { + sfree(cmd); + return new_error_socket_fmt(plug, "pipe: %s", strerror(errno)); + } + cloexec(to_cmd_pipe[1]); + cloexec(from_cmd_pipe[0]); + cloexec(cmd_err_pipe[0]); + + pid = fork(); + 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]); + dup2(cmd_err_pipe[1], 2); + noncloexec(0); + noncloexec(1); + execl("/bin/sh", "sh", "-c", cmd, (void *)NULL); + _exit(255); + } + + sfree(cmd); + + if (pid < 0) + return new_error_socket_fmt(plug, "fork: %s", strerror(errno)); + + close(to_cmd_pipe[0]); + close(from_cmd_pipe[1]); + close(cmd_err_pipe[1]); + + outfd = to_cmd_pipe[1]; + infd = from_cmd_pipe[0]; + inerrfd = cmd_err_pipe[0]; + } else { + cmd = format_telnet_command(addr, port, conf); + outfd = open("/dev/null", O_WRONLY); + if (outfd == -1) { + sfree(cmd); + return new_error_socket_fmt( + plug, "/dev/null: %s", strerror(errno)); + } + infd = open(cmd, O_RDONLY); + if (infd == -1) { + Socket *toret = new_error_socket_fmt( + plug, "%s: %s", cmd, strerror(errno)); + sfree(cmd); + close(outfd); + return toret; + } + sfree(cmd); + inerrfd = -1; + } + + /* We are responsible for this and don't need it any more */ + sk_addr_free(addr); + + return make_fd_socket(infd, outfd, inerrfd, plug); +} diff --git a/unix/main-gtk-application.c b/unix/main-gtk-application.c new file mode 100644 index 00000000..fb924009 --- /dev/null +++ b/unix/main-gtk-application.c @@ -0,0 +1,340 @@ +/* + * gtkapp.c: a top-level front end to GUI PuTTY and pterm, using + * GtkApplication. Suitable for OS X. Currently unfinished. + * + * (You could run it on ordinary Linux GTK too, in principle, but I + * don't think it would be particularly useful to do so, even once + * it's fully working.) + */ + +/* + +Building this for OS X is currently broken, because the new +CMake-based build system doesn't support it yet. Probably what needs +doing is to add it back in to unix/CMakeLists.txt under a condition +like if(CMAKE_SYSTEM_NAME MATCHES "Darwin"). + +*/ + +/* + +TODO list for a sensible GTK3 PuTTY/pterm on OS X: + +Still to do on the application menu bar: items that have to vary with +context or user action (saved sessions and mid-session special +commands), and disabling/enabling the main actions in parallel with +their counterparts in the Ctrl-rightclick context menu. + +Mouse wheel events and trackpad scrolling gestures don't work quite +right in the terminal drawing area. This seems to be a combination of +two things, neither of which I completely understand yet. Firstly, on +OS X GTK my trackpad seems to generate GDK scroll events for which +gdk_event_get_scroll_deltas returns integers rather than integer +multiples of 1/30, so we end up scrolling by very large amounts; +secondly, the window doesn't seem to receive a GTK "draw" event until +after the entire scroll gesture is complete, which means we don't get +constant visual feedback on how much we're scrolling by. + +There doesn't seem to be a resize handle on terminal windows. Then +again, they do seem to _be_ resizable; the handle just isn't shown. +Perhaps that's a feature (certainly in a scrollbarless configuration +the handle gets in the way of the bottom right character cell in the +terminal itself), but it would be nice to at least understand _why_ it +happens and perhaps include an option to put it back again. + +A slight oddity with menus that pop up directly under the mouse +pointer: mousing over the menu items doesn't highlight them initially, +but if I mouse off the menu and back on (without un-popping-it-up) +then suddenly that does work. I don't know if this is something I can +fix, though; it might very well be a quirk of the underlying GTK. + +Does OS X have a standard system of online help that I could tie into? + +Need to work out what if anything we can do with Pageant on OS X. +Perhaps it's too much bother and we should just talk to the +system-provided SSH agent? Or perhaps not. + +Nice-to-have: a custom right-click menu from the application's dock +tile, listing the saved sessions for quick launch. As far as I know +there's nothing built in to GtkApplication that can produce this, but +it's possible we might be able to drop a piece of native Cocoa code in +under ifdef, substituting an application delegate of our own which +forwards all methods we're not interested in to the GTK-provided one? + +At the point where this becomes polished enough to publish pre-built, +I suppose I'll have to look into OS X code signing. +https://wiki.gnome.org/Projects/GTK%2B/OSX/Bundling has some links. + + */ + +#include +#include + +#include + +#include + +#define MAY_REFER_TO_GTK_IN_HEADERS + +#include "putty.h" +#include "gtkmisc.h" + +char *x_get_default(const char *key) { return NULL; } + +const bool buildinfo_gtk_relevant = true; + +#if !GTK_CHECK_VERSION(3,0,0) +/* This front end only works in GTK 3. If that's not what we've got, + * it's easier to just turn this program into a trivial stub by ifdef + * in the source than it is to remove it in the makefile edifice. */ +int main(int argc, char **argv) +{ + fprintf(stderr, "GtkApplication frontend doesn't work pre-GTK3\n"); + return 1; +} +GtkWidget *make_gtk_toplevel_window(GtkFrontend *frontend) { return NULL; } +void launch_duplicate_session(Conf *conf) {} +void launch_new_session(void) {} +void launch_saved_session(const char *str) {} +void session_window_closed(void) {} +void window_setup_error(const char *errmsg) {} +#else /* GTK_CHECK_VERSION(3,0,0) */ + +static void startup(GApplication *app, gpointer user_data) +{ + GMenu *menubar, *menu, *section; + + menubar = g_menu_new(); + + menu = g_menu_new(); + g_menu_append_submenu(menubar, "File", G_MENU_MODEL(menu)); + + section = g_menu_new(); + g_menu_append_section(menu, NULL, G_MENU_MODEL(section)); + g_menu_append(section, "New Window", "app.newwin"); + + menu = g_menu_new(); + g_menu_append_submenu(menubar, "Edit", G_MENU_MODEL(menu)); + + section = g_menu_new(); + g_menu_append_section(menu, NULL, G_MENU_MODEL(section)); + g_menu_append(section, "Copy", "win.copy"); + g_menu_append(section, "Paste", "win.paste"); + g_menu_append(section, "Copy All", "win.copyall"); + + menu = g_menu_new(); + g_menu_append_submenu(menubar, "Window", G_MENU_MODEL(menu)); + + section = g_menu_new(); + g_menu_append_section(menu, NULL, G_MENU_MODEL(section)); + g_menu_append(section, "Restart Session", "win.restart"); + g_menu_append(section, "Duplicate Session", "win.duplicate"); + + section = g_menu_new(); + g_menu_append_section(menu, NULL, G_MENU_MODEL(section)); + g_menu_append(section, "Change Settings", "win.changesettings"); + + if (use_event_log) { + section = g_menu_new(); + g_menu_append_section(menu, NULL, G_MENU_MODEL(section)); + g_menu_append(section, "Event Log", "win.eventlog"); + } + + section = g_menu_new(); + g_menu_append_section(menu, NULL, G_MENU_MODEL(section)); + g_menu_append(section, "Clear Scrollback", "win.clearscrollback"); + g_menu_append(section, "Reset Terminal", "win.resetterm"); + +#if GTK_CHECK_VERSION(3,12,0) +#define SET_ACCEL(app, command, accel) do \ + { \ + static const char *const accels[] = { accel, NULL }; \ + gtk_application_set_accels_for_action( \ + GTK_APPLICATION(app), command, accels); \ + } while (0) +#else + /* The Gtk function used above was new in 3.12; the one below + * was deprecated from 3.14. */ +#define SET_ACCEL(app, command, accel) \ + gtk_application_add_accelerator(GTK_APPLICATION(app), accel, \ + command, NULL) +#endif + + SET_ACCEL(app, "app.newwin", "n"); + SET_ACCEL(app, "win.copy", "c"); + SET_ACCEL(app, "win.paste", "v"); + +#undef SET_ACCEL + + gtk_application_set_menubar(GTK_APPLICATION(app), + G_MENU_MODEL(menubar)); +} + +#define WIN_ACTION_LIST(X) \ + X("copy", MA_COPY) \ + X("paste", MA_PASTE) \ + X("copyall", MA_COPY_ALL) \ + X("duplicate", MA_DUPLICATE_SESSION) \ + X("restart", MA_RESTART_SESSION) \ + X("changesettings", MA_CHANGE_SETTINGS) \ + X("clearscrollback", MA_CLEAR_SCROLLBACK) \ + X("resetterm", MA_RESET_TERMINAL) \ + X("eventlog", MA_EVENT_LOG) \ + /* end of list */ + +#define WIN_ACTION_CALLBACK(name, id) \ +static void win_action_cb_ ## id(GSimpleAction *a, GVariant *p, gpointer d) \ +{ app_menu_action(d, id); } +WIN_ACTION_LIST(WIN_ACTION_CALLBACK) +#undef WIN_ACTION_CALLBACK + +static const GActionEntry win_actions[] = { +#define WIN_ACTION_ENTRY(name, id) { name, win_action_cb_ ## id }, +WIN_ACTION_LIST(WIN_ACTION_ENTRY) +#undef WIN_ACTION_ENTRY +}; + +static GtkApplication *app; +GtkWidget *make_gtk_toplevel_window(GtkFrontend *frontend) +{ + GtkWidget *win = gtk_application_window_new(app); + g_action_map_add_action_entries(G_ACTION_MAP(win), + win_actions, + G_N_ELEMENTS(win_actions), + frontend); + return win; +} + +void launch_duplicate_session(Conf *conf) +{ + assert(!dup_check_launchable || conf_launchable(conf)); + g_application_hold(G_APPLICATION(app)); + new_session_window(conf_copy(conf), NULL); +} + +void session_window_closed(void) +{ + g_application_release(G_APPLICATION(app)); +} + +static void post_initial_config_box(void *vctx, int result) +{ + Conf *conf = (Conf *)vctx; + + if (result > 0) { + new_session_window(conf, NULL); + } else if (result == 0) { + conf_free(conf); + g_application_release(G_APPLICATION(app)); + } +} + +void launch_saved_session(const char *str) +{ + Conf *conf = conf_new(); + do_defaults(str, conf); + + g_application_hold(G_APPLICATION(app)); + + if (!conf_launchable(conf)) { + initial_config_box(conf, post_initial_config_box, conf); + } else { + new_session_window(conf, NULL); + } +} + +void launch_new_session(void) +{ + /* Same as launch_saved_session except that we pass NULL to + * do_defaults. */ + launch_saved_session(NULL); +} + +void new_app_win(GtkApplication *app) +{ + launch_new_session(); +} + +static void window_setup_error_callback(void *vctx, int result) +{ + g_application_release(G_APPLICATION(app)); +} + +void window_setup_error(const char *errmsg) +{ + create_message_box(NULL, "Error creating session window", errmsg, + string_width("Some sort of fiddly error message that " + "might be technical"), + true, &buttons_ok, window_setup_error_callback, NULL); +} + +static void activate(GApplication *app, + gpointer user_data) +{ + new_app_win(GTK_APPLICATION(app)); +} + +static void newwin_cb(GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + new_app_win(GTK_APPLICATION(user_data)); +} + +static void quit_cb(GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + g_application_quit(G_APPLICATION(user_data)); +} + +static void about_cb(GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + about_box(NULL); +} + +static const GActionEntry app_actions[] = { + { "newwin", newwin_cb }, + { "about", about_cb }, + { "quit", quit_cb }, +}; + +int main(int argc, char **argv) +{ + int status; + + /* Call the function in ux{putty,pterm}.c to do app-type + * specific setup */ + setup(false); /* false means we are not a one-session process */ + + if (argc > 1) { + pty_osx_envrestore_prefix = argv[--argc]; + } + + { + const char *home = getenv("HOME"); + if (home) { + if (chdir(home)) {} + } + } + + gtkcomm_setup(); + + app = gtk_application_new("org.tartarus.projects.putty.macputty", + G_APPLICATION_FLAGS_NONE); + g_signal_connect(app, "activate", G_CALLBACK(activate), NULL); + g_signal_connect(app, "startup", G_CALLBACK(startup), NULL); + g_action_map_add_action_entries(G_ACTION_MAP(app), + app_actions, + G_N_ELEMENTS(app_actions), + app); + + status = g_application_run(G_APPLICATION(app), argc, argv); + g_object_unref(app); + + return status; +} + +#endif /* GTK_CHECK_VERSION(3,0,0) */ diff --git a/unix/main-gtk-simple.c b/unix/main-gtk-simple.c new file mode 100644 index 00000000..4ee43e7b --- /dev/null +++ b/unix/main-gtk-simple.c @@ -0,0 +1,673 @@ +/* + * gtkmain.c: the common main-program code between the straight-up + * Unix PuTTY and pterm, which they do not share with the + * multi-session gtkapp.c. + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if !GTK_CHECK_VERSION(3,0,0) +#include +#endif + +#if GTK_CHECK_VERSION(2,0,0) +#include +#endif + +#define MAY_REFER_TO_GTK_IN_HEADERS + +#include "putty.h" +#include "terminal.h" +#include "gtkcompat.h" +#include "unifont.h" +#include "gtkmisc.h" + +#ifndef NOT_X_WINDOWS +#include +#include +#include +#include +#include "x11misc.h" +#endif + +static char *progname, **gtkargvstart; +static int ngtkargs; + +static const char *app_name = "pterm"; + +char *x_get_default(const char *key) +{ +#ifndef NOT_X_WINDOWS + Display *disp; + if ((disp = get_x11_display()) == NULL) + return NULL; + return XGetDefault(disp, app_name, key); +#else + return NULL; +#endif +} + +void fork_and_exec_self(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 += ngtkargs; + while (va_arg(ap, char *) != NULL) + n++; + va_end(ap); + + args = snewn(n, char *); + args[0] = progname; + args[n-1] = NULL; + for (i = 0; i < ngtkargs; i++) + args[i+1] = 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"); + sfree(args); + 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(progname, args); + perror("exec"); + _exit(127); + + } else { + int status; + sfree(args); + waitpid(pid, &status, 0); + } + +} + +void launch_duplicate_session(Conf *conf) +{ + /* + * For this feature we must marshal conf and (possibly) pty_argv + * into a byte stream, create a pipe, and send this byte stream + * to the child through the pipe. + */ + int i, ret; + strbuf *serialised; + char option[80]; + int pipefd[2]; + + if (pipe(pipefd) < 0) { + perror("pipe"); + return; + } + + serialised = strbuf_new(); + + conf_serialise(BinarySink_UPCAST(serialised), conf); + if (use_pty_argv && pty_argv) + for (i = 0; pty_argv[i]; i++) + put_asciz(serialised, pty_argv[i]); + + sprintf(option, "---[%d,%zu]", pipefd[0], serialised->len); + noncloexec(pipefd[0]); + fork_and_exec_self(pipefd[1], option, NULL); + close(pipefd[0]); + + i = ret = 0; + while (i < serialised->len && + (ret = write(pipefd[1], serialised->s + i, + serialised->len - i)) > 0) + i += ret; + if (ret < 0) + perror("write to pipe"); + close(pipefd[1]); + strbuf_free(serialised); +} + +void launch_new_session(void) +{ + fork_and_exec_self(-1, NULL); +} + +void launch_saved_session(const char *str) +{ + fork_and_exec_self(-1, "-load", str, NULL); +} + +int read_dupsession_data(Conf *conf, char *arg) +{ + int fd, i, ret, size; + char *data; + BinarySource src[1]; + + 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); + } + + BinarySource_BARE_INIT(src, data, size); + if (!conf_deserialise(conf, src)) { + fprintf(stderr, "%s: malformed Duplicate Session data\n", appname); + exit(1); + } + if (use_pty_argv) { + int pty_argc = 0; + size_t argv_startpos = src->pos; + + while (get_asciz(src), !get_err(src)) + pty_argc++; + + src->err = BSE_NO_ERROR; + + if (pty_argc > 0) { + src->pos = argv_startpos; + + pty_argv = snewn(pty_argc + 1, char *); + pty_argv[pty_argc] = NULL; + for (i = 0; i < pty_argc; i++) + pty_argv[i] = dupstr(get_asciz(src)); + } + } + + if (get_err(src) || get_avail(src) > 0) { + fprintf(stderr, "%s: malformed Duplicate Session data\n", appname); + exit(1); + } + + sfree(data); + return 0; +} + +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, -sessionlog 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); + } +} + +static void version(FILE *fp) { + char *buildinfo_text = buildinfo("\n"); + if(fprintf(fp, "%s: %s\n%s\n", appname, ver, buildinfo_text) < 0 || + fflush(fp) < 0) { + perror("output error"); + exit(1); + } + sfree(buildinfo_text); +} + +static const char *geometry_string; + +void cmdline_error(const 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); +} + +void window_setup_error(const char *errmsg) +{ + fprintf(stderr, "%s: %s\n", appname, errmsg); + exit(1); +} + +bool do_cmdline(int argc, char **argv, bool do_everything, Conf *conf) +{ + bool err = false; + 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. I use the alternative if (1) + * {...} else ((void)0). + */ +#define EXPECTS_ARG if (1) { \ + if (--argc <= 0) { \ + err = true; \ + fprintf(stderr, "%s: %s expects an argument\n", appname, p); \ + continue; \ + } else \ + val = *++argv; \ + } else ((void)0) +#define SECOND_PASS_ONLY if (1) { \ + if (!do_everything) \ + continue; \ + } else ((void)0) + + while (--argc > 0) { + const 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, conf); + + 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")) { + FontSpec *fs; + EXPECTS_ARG; + SECOND_PASS_ONLY; + fs = fontspec_new(val); + conf_set_fontspec(conf, CONF_font, fs); + fontspec_free(fs); + + } else if (!strcmp(p, "-fb")) { + FontSpec *fs; + EXPECTS_ARG; + SECOND_PASS_ONLY; + fs = fontspec_new(val); + conf_set_fontspec(conf, CONF_boldfont, fs); + fontspec_free(fs); + + } else if (!strcmp(p, "-fw")) { + FontSpec *fs; + EXPECTS_ARG; + SECOND_PASS_ONLY; + fs = fontspec_new(val); + conf_set_fontspec(conf, CONF_widefont, fs); + fontspec_free(fs); + + } else if (!strcmp(p, "-fwb")) { + FontSpec *fs; + EXPECTS_ARG; + SECOND_PASS_ONLY; + fs = fontspec_new(val); + conf_set_fontspec(conf, CONF_wideboldfont, fs); + fontspec_free(fs); + + } else if (!strcmp(p, "-cs")) { + EXPECTS_ARG; + SECOND_PASS_ONLY; + conf_set_str(conf, CONF_line_codepage, val); + + } else if (!strcmp(p, "-geometry")) { + EXPECTS_ARG; + SECOND_PASS_ONLY; + geometry_string = val; + } else if (!strcmp(p, "-sl")) { + EXPECTS_ARG; + SECOND_PASS_ONLY; + conf_set_int(conf, CONF_savelines, atoi(val)); + + } else if (!strcmp(p, "-fg") || !strcmp(p, "-bg") || + !strcmp(p, "-bfg") || !strcmp(p, "-bbg") || + !strcmp(p, "-cfg") || !strcmp(p, "-cbg")) { + EXPECTS_ARG; + SECOND_PASS_ONLY; + + { +#if GTK_CHECK_VERSION(3,0,0) + GdkRGBA rgba; + bool success = gdk_rgba_parse(&rgba, val); +#else + GdkColor col; + bool success = gdk_color_parse(val, &col); +#endif + + if (!success) { + err = true; + fprintf(stderr, "%s: unable to parse colour \"%s\"\n", + appname, val); + } else { +#if GTK_CHECK_VERSION(3,0,0) + int r = rgba.red * 255; + int g = rgba.green * 255; + int b = rgba.blue * 255; +#else + int r = col.red / 256; + int g = col.green / 256; + int b = col.blue / 256; +#endif + + 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); + + conf_set_int_int(conf, CONF_colours, index*3+0, r); + conf_set_int_int(conf, CONF_colours, index*3+1, g); + conf_set_int_int(conf, CONF_colours, index*3+2, b); + } + } + + } 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 = true, fprintf(stderr, "%s: -e expects an argument\n", + appname); + + } else if (!strcmp(p, "-title")) { + EXPECTS_ARG; + SECOND_PASS_ONLY; + conf_set_str(conf, CONF_wintitle, val); + + } else if (!strcmp(p, "-log")) { + Filename *fn; + EXPECTS_ARG; + SECOND_PASS_ONLY; + fn = filename_from_str(val); + conf_set_filename(conf, CONF_logfilename, fn); + conf_set_int(conf, CONF_logtype, LGTYP_DEBUG); + filename_free(fn); + + } else if (!strcmp(p, "-ut-") || !strcmp(p, "+ut")) { + SECOND_PASS_ONLY; + conf_set_bool(conf, CONF_stamp_utmp, false); + + } else if (!strcmp(p, "-ut")) { + SECOND_PASS_ONLY; + conf_set_bool(conf, CONF_stamp_utmp, true); + + } else if (!strcmp(p, "-ls-") || !strcmp(p, "+ls")) { + SECOND_PASS_ONLY; + conf_set_bool(conf, CONF_login_shell, false); + + } else if (!strcmp(p, "-ls")) { + SECOND_PASS_ONLY; + conf_set_bool(conf, CONF_login_shell, true); + + } else if (!strcmp(p, "-nethack")) { + SECOND_PASS_ONLY; + conf_set_bool(conf, CONF_nethack_keypad, true); + + } else if (!strcmp(p, "-sb-") || !strcmp(p, "+sb")) { + SECOND_PASS_ONLY; + conf_set_bool(conf, CONF_scrollbar, false); + + } else if (!strcmp(p, "-sb")) { + SECOND_PASS_ONLY; + conf_set_bool(conf, CONF_scrollbar, true); + + } else if (!strcmp(p, "-name")) { + EXPECTS_ARG; + app_name = val; + + } else if (!strcmp(p, "-xrm")) { + EXPECTS_ARG; + provide_xrm_string(val, appname); + + } else if(!strcmp(p, "-help") || !strcmp(p, "--help")) { + help(stdout); + exit(0); + + } else if(!strcmp(p, "-version") || !strcmp(p, "--version")) { + version(stdout); + exit(0); + + } else if (!strcmp(p, "-pgpfp")) { + pgp_fingerprints(); + exit(1); + + } else if (p[0] != '-') { + /* Non-option arguments not handled by cmdline.c are errors. */ + if (do_everything) { + err = true; + fprintf(stderr, "%s: unexpected non-option argument '%s'\n", + appname, p); + } + + } else { + err = true; + fprintf(stderr, "%s: unrecognized option '%s'\n", appname, p); + } + } + + return err; +} + +GtkWidget *make_gtk_toplevel_window(GtkFrontend *frontend) +{ + return gtk_window_new(GTK_WINDOW_TOPLEVEL); +} + +const bool buildinfo_gtk_relevant = true; + +struct post_initial_config_box_ctx { + Conf *conf; + const char *geometry_string; +}; + +static void post_initial_config_box(void *vctx, int result) +{ + struct post_initial_config_box_ctx ctx = + *(struct post_initial_config_box_ctx *)vctx; + sfree(vctx); + + if (result > 0) { + new_session_window(ctx.conf, ctx.geometry_string); + } else { + /* In this main(), which only runs one session in total, a + * negative result from the initial config box means we simply + * terminate. */ + conf_free(ctx.conf); + gtk_main_quit(); + } +} + +void session_window_closed(void) +{ + gtk_main_quit(); +} + +int main(int argc, char **argv) +{ + Conf *conf; + bool need_config_box; + + setlocale(LC_CTYPE, ""); + + /* Call the function in ux{putty,pterm}.c to do app-type + * specific setup */ + setup(true); /* true means we are a one-session process */ + + progname = argv[0]; + + /* + * Copy the original argv before letting gtk_init fiddle with + * it. It will be required later. + */ + { + int i, oldargc; + gtkargvstart = snewn(argc-1, char *); + for (i = 1; i < argc; i++) + gtkargvstart[i-1] = dupstr(argv[i]); + oldargc = argc; + gtk_init(&argc, &argv); + ngtkargs = oldargc - argc; + } + + conf = conf_new(); + + gtkcomm_setup(); + + /* + * 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. However, we'll have to + * unblock it again when pterm forks. + */ + block_signal(SIGPIPE, true); + + if (argc > 1 && !strncmp(argv[1], "---", 3)) { + read_dupsession_data(conf, argv[1]); + /* Splatter this argument so it doesn't clutter a ps listing */ + smemclr(argv[1], strlen(argv[1])); + + assert(!dup_check_launchable || conf_launchable(conf)); + need_config_box = false; + } else { + if (do_cmdline(argc, argv, false, conf)) + exit(1); /* pre-defaults pass to get -class */ + do_defaults(NULL, conf); + if (do_cmdline(argc, argv, true, conf)) + exit(1); /* post-defaults, do everything */ + + cmdline_run_saved(conf); + + if (cmdline_tooltype & TOOLTYPE_HOST_ARG) + need_config_box = !cmdline_host_ok(conf); + else + need_config_box = false; + } + + if (need_config_box) { + /* + * Put up the initial config box, which will pass the provided + * parameters (with conf updated) to new_session_window() when + * (if) the user selects Open. Or it might close without + * creating a session window, if the user selects Cancel. Or + * it might just create the session window immediately if this + * is a pterm-style app which doesn't have an initial config + * box at all. + */ + struct post_initial_config_box_ctx *ctx = + snew(struct post_initial_config_box_ctx); + ctx->conf = conf; + ctx->geometry_string = geometry_string; + initial_config_box(conf, post_initial_config_box, ctx); + } else { + /* + * No initial config needed; just create the session window + * now. + */ + new_session_window(conf, geometry_string); + } + + gtk_main(); + + return 0; +} diff --git a/unix/network.c b/unix/network.c new file mode 100644 index 00000000..bd6ebb1d --- /dev/null +++ b/unix/network.c @@ -0,0 +1,1761 @@ +/* + * Unix networking abstraction. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "putty.h" +#include "network.h" +#include "tree234.h" + +/* Solaris needs for SIOCATMARK. */ +#ifndef SIOCATMARK +#include +#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 { + struct sockaddr_storage storage; + struct sockaddr sa; + struct sockaddr_in sin; +#ifndef NO_IPV6 + struct sockaddr_in6 sin6; +#endif + struct sockaddr_un su; +}; + +/* + * 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; +}; + +typedef struct NetSocket NetSocket; +struct NetSocket { + const char *error; + int s; + Plug *plug; + bufchain output_data; + bool connected; /* irrelevant for listening sockets */ + bool writable; + bool frozen; /* this causes readability notifications to be ignored */ + bool localhost_only; /* for listening sockets */ + char oobdata[1]; + size_t sending_oob; + bool oobpending; /* is there OOB data available to read? */ + bool oobinline; + enum { EOF_NO, EOF_PENDING, EOF_SENT } outgoingeof; + bool incomingeof; + int pending_error; /* in case send() returns error */ + bool listener; + bool nodelay, keepalive; /* for connect()-type sockets */ + bool privport; + int 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. + */ + NetSocket *parent, *child; + + Socket sock; +}; + +struct SockAddr { + 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 +/* Here we gratuitously reference 'step' to avoid gcc warnings about + * 'set but not used' when compiling -DNO_IPV6 */ +#define SOCKADDR_FAMILY(addr, step) \ + ((addr)->superfamily == UNRESOLVED ? AF_UNSPEC : \ + (addr)->superfamily == UNIX ? AF_UNIX : \ + (step).curraddr ? AF_INET : 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(NetSocket *s); + +static int cmpfortree(void *av, void *bv) +{ + NetSocket *a = (NetSocket *) av, *b = (NetSocket *) 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) +{ + NetSocket *b = (NetSocket *) 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) +{ + NetSocket *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) +{ + if (host[0] == '/') { + *canonicalname = dupstr(host); + return unix_sock_addr(host); + } + + SockAddr *ret = snew(SockAddr); +#ifndef NO_IPV6 + struct addrinfo hints; + int err; +#else + unsigned long a; + struct hostent *h = NULL; + int n; +#endif + strbuf *realhost = strbuf_new(); + + /* Clear the structure and default to IPv4. */ + memset(ret, 0, sizeof(SockAddr)); + ret->superfamily = UNRESOLVED; + 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; + { + char *trimmed_host = host_strduptrim(host); /* strip [] on literals */ + err = getaddrinfo(trimmed_host, NULL, &hints, &ret->ais); + sfree(trimmed_host); + } + if (err != 0) { + ret->error = gai_strerror(err); + strbuf_free(realhost); + return ret; + } + ret->superfamily = IP; + + if (ret->ais->ai_canonname != NULL) + strbuf_catf(realhost, "%s", ret->ais->ai_canonname); + else + strbuf_catf(realhost, "%s", host); +#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"); + strbuf_free(realhost); + return ret; + } + /* This way we are always sure the h->h_name is valid :) */ + strbuf_clear(realhost); + strbuf_catf(realhost, "%s", h->h_name); + 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; + strbuf_clear(realhost); + strbuf_catf(realhost, "%s", host); + ret->addresses = snew(unsigned long); + ret->naddresses = 1; + ret->addresses[0] = ntohl(a); + } +#endif + *canonicalname = strbuf_to_str(realhost); + return ret; +} + +SockAddr *sk_nonamelookup(const char *host) +{ + SockAddr *ret = snew(SockAddr); + 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 bool 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) +{ + if (addr->superfamily == UNRESOLVED || addr->superfamily == UNIX) { + strncpy(buf, addr->hostname, buflen); + buf[buflen-1] = '\0'; + } else { +#ifndef NO_IPV6 + if (getnameinfo(addr->ais->ai_addr, addr->ais->ai_addrlen, buf, buflen, + NULL, 0, NI_NUMERICHOST) != 0) { + buf[0] = '\0'; + strncat(buf, "", 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 + } +} + +/* + * This constructs a SockAddr that points at one specific sub-address + * of a parent SockAddr. The returned SockAddr does not own all its + * own memory: it points into the old one's data structures, so it + * MUST NOT be used after the old one is freed, and it MUST NOT be + * passed to sk_addr_free. (The latter is why it's returned by value + * rather than dynamically allocated - that should clue in anyone + * writing a call to it that something is weird about it.) + */ +static SockAddr sk_extractaddr_tmp( + SockAddr *addr, const SockAddrStep *step) +{ + SockAddr toret; + toret = *addr; /* structure copy */ + toret.refcount = 1; + + if (addr->superfamily == IP) { +#ifndef NO_IPV6 + toret.ais = step->ai; +#else + assert(SOCKADDR_FAMILY(addr, *step) == AF_INET); + toret.addresses += step->curraddr; +#endif + } + + return toret; +} + +bool sk_addr_needs_port(SockAddr *addr) +{ + if (addr->superfamily == UNRESOLVED || addr->superfamily == UNIX) { + return false; + } else { + return true; + } +} + +bool sk_hostname_is_local(const 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 bool 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; + } +} + +bool sk_address_is_local(SockAddr *addr) +{ + if (addr->superfamily == UNRESOLVED) + return false; /* we don't know; assume not */ + else if (addr->superfamily == UNIX) + return true; + 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 + } +} + +bool sk_address_is_special_local(SockAddr *addr) +{ + return addr->superfamily == UNIX; +} + +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 + unreachable("bad address family in sk_addrcopy"); +#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_net_plug(Socket *sock, Plug *p) +{ + NetSocket *s = container_of(sock, NetSocket, sock); + Plug *ret = s->plug; + if (p) + s->plug = p; + return ret; +} + +static void sk_net_close(Socket *s); +static size_t sk_net_write(Socket *s, const void *data, size_t len); +static size_t sk_net_write_oob(Socket *s, const void *data, size_t len); +static void sk_net_write_eof(Socket *s); +static void sk_net_set_frozen(Socket *s, bool is_frozen); +static SocketPeerInfo *sk_net_peer_info(Socket *s); +static const char *sk_net_socket_error(Socket *s); + +static const SocketVtable NetSocket_sockvt = { + .plug = sk_net_plug, + .close = sk_net_close, + .write = sk_net_write, + .write_oob = sk_net_write_oob, + .write_eof = sk_net_write_eof, + .set_frozen = sk_net_set_frozen, + .socket_error = sk_net_socket_error, + .peer_info = sk_net_peer_info, +}; + +static Socket *sk_net_accept(accept_ctx_t ctx, Plug *plug) +{ + int sockfd = ctx.i; + NetSocket *ret; + + /* + * Create NetSocket structure. + */ + ret = snew(NetSocket); + ret->sock.vt = &NetSocket_sockvt; + ret->error = NULL; + ret->plug = plug; + bufchain_init(&ret->output_data); + ret->writable = true; /* to start with */ + ret->sending_oob = 0; + ret->frozen = true; + ret->localhost_only = false; /* unused, but best init anyway */ + ret->pending_error = 0; + ret->oobpending = false; + ret->outgoingeof = EOF_NO; + ret->incomingeof = false; + ret->listener = false; + ret->parent = ret->child = NULL; + ret->addr = NULL; + ret->connected = true; + + ret->s = sockfd; + + if (ret->s < 0) { + ret->error = strerror(errno); + return &ret->sock; + } + + ret->oobinline = false; + + uxsel_tell(ret); + add234(sktree, ret); + + return &ret->sock; +} + +static int try_connect(NetSocket *sock) +{ + int s; + union sockaddr_union u; + const union sockaddr_union *sa; + int err = 0; + short localport; + int salen, family; + + /* + * Remove the socket from the tree before we overwrite its + * internal socket id, because that forms part of the tree's + * sorting criterion. We'll add it back before exiting this + * function, whether we changed anything or not. + */ + del234(sktree, sock); + + if (sock->s >= 0) + close(sock->s); + + { + SockAddr thisaddr = sk_extractaddr_tmp( + sock->addr, &sock->step); + plug_log(sock->plug, PLUGLOG_CONNECT_TRYING, + &thisaddr, 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 = 1; + if (setsockopt(s, SOL_SOCKET, SO_OOBINLINE, + (void *) &b, sizeof(b)) < 0) { + err = errno; + close(s); + goto ret; + } + } + + if (sock->nodelay && family != AF_UNIX) { + int b = 1; + if (setsockopt(s, IPPROTO_TCP, TCP_NODELAY, + (void *) &b, sizeof(b)) < 0) { + err = errno; + close(s); + goto ret; + } + } + + if (sock->keepalive) { + int b = 1; + if (setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, + (void *) &b, sizeof(b)) < 0) { + err = errno; + close(s); + goto ret; + } + } + + /* + * 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(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: + unreachable("unknown address family"); + exit(1); /* XXX: GCC doesn't understand assert() on some systems. */ + } + + nonblock(s); + + 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 = true; + sock->writable = true; + + SockAddr thisaddr = sk_extractaddr_tmp(sock->addr, &sock->step); + plug_log(sock->plug, PLUGLOG_CONNECT_SUCCESS, + &thisaddr, sock->port, NULL, 0); + } + + uxsel_tell(sock); + + ret: + + /* + * No matter what happened, put the socket back in the tree. + */ + add234(sktree, sock); + + if (err) { + SockAddr thisaddr = sk_extractaddr_tmp( + sock->addr, &sock->step); + plug_log(sock->plug, PLUGLOG_CONNECT_FAILED, + &thisaddr, sock->port, strerror(err), err); + } + return err; +} + +Socket *sk_new(SockAddr *addr, int port, bool privport, bool oobinline, + bool nodelay, bool keepalive, Plug *plug) +{ + NetSocket *ret; + int err; + + /* + * Create NetSocket structure. + */ + ret = snew(NetSocket); + ret->sock.vt = &NetSocket_sockvt; + ret->error = NULL; + ret->plug = plug; + bufchain_init(&ret->output_data); + ret->connected = false; /* to start with */ + ret->writable = false; /* to start with */ + ret->sending_oob = 0; + ret->frozen = false; + ret->localhost_only = false; /* unused, but best init anyway */ + ret->pending_error = 0; + ret->parent = ret->child = NULL; + ret->oobpending = false; + ret->outgoingeof = EOF_NO; + ret->incomingeof = false; + ret->listener = false; + 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; + + do { + err = try_connect(ret); + } while (err && sk_nextaddr(ret->addr, &ret->step)); + + if (err) + ret->error = strerror(err); + + return &ret->sock; +} + +Socket *sk_newlistener(const char *srcaddr, int port, Plug *plug, + bool local_host_only, int orig_address_family) +{ + int s; +#ifndef NO_IPV6 + struct addrinfo hints, *ai = NULL; + char portstr[6]; +#endif + union sockaddr_union u; + union sockaddr_union *addr; + int addrlen; + NetSocket *ret; + int retcode; + int address_family; + int on = 1; + + /* + * Create NetSocket structure. + */ + ret = snew(NetSocket); + ret->sock.vt = &NetSocket_sockvt; + ret->error = NULL; + ret->plug = plug; + bufchain_init(&ret->output_data); + ret->writable = false; /* to start with */ + ret->sending_oob = 0; + ret->frozen = false; + ret->localhost_only = local_host_only; + ret->pending_error = 0; + ret->parent = ret->child = NULL; + ret->oobpending = false; + ret->outgoingeof = EOF_NO; + ret->incomingeof = false; + ret->listener = true; + ret->addr = NULL; + ret->s = -1; + + /* + * 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 &ret->sock; + } + + cloexec(s); + + ret->oobinline = false; + + if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, + (const char *)&on, sizeof(on)) < 0) { + ret->error = strerror(errno); + close(s); + return &ret->sock; + } + + 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); + { + char *trimmed_addr = host_strduptrim(srcaddr); + retcode = getaddrinfo(trimmed_addr, portstr, &hints, &ai); + sfree(trimmed_addr); + } + 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); + +#ifndef NO_IPV6 + if (ai) + freeaddrinfo(ai); +#endif + + if (retcode < 0) { + close(s); + ret->error = strerror(errno); + return &ret->sock; + } + + if (listen(s, SOMAXCONN) < 0) { + close(s); + ret->error = strerror(errno); + return &ret->sock; + } + +#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) { + NetSocket *other; + + other = container_of( + sk_newlistener(srcaddr, port, plug, + local_host_only, ADDRTYPE_IPV4), + NetSocket, sock); + + 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 &other->sock; + } + } + } +#endif + + ret->s = s; + + uxsel_tell(ret); + add234(sktree, ret); + + return &ret->sock; +} + +static void sk_net_close(Socket *sock) +{ + NetSocket *s = container_of(sock, NetSocket, sock); + + if (s->child) + sk_net_close(&s->child->sock); + + bufchain_clear(&s->output_data); + + del234(sktree, s); + if (s->s >= 0) { + uxsel_del(s->s); + close(s->s); + } + if (s->addr) + sk_addr_free(s->addr); + delete_callbacks_for_context(s); + sfree(s); +} + +void *sk_getxdmdata(Socket *sock, int *lenp) +{ + NetSocket *s; + union sockaddr_union u; + socklen_t addrlen; + char *buf; + static unsigned int unix_addr = 0xFFFFFFFF; + + /* + * We must check that this socket really _is_ a NetSocket before + * downcasting it. + */ + if (sock->vt != &NetSocket_sockvt) + return NULL; /* failure */ + s = container_of(sock, NetSocket, sock); + + 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; +} + +/* + * Deal with socket errors detected in try_send(). + */ +static void socket_error_callback(void *vs) +{ + NetSocket *s = (NetSocket *)vs; + + /* + * Just in case other socket work has caused this socket to vanish + * or become somehow non-erroneous before this callback arrived... + */ + if (!find234(sktree, s, NULL) || !s->pending_error) + return; + + /* + * An error has occurred on this socket. Pass it to the plug. + */ + plug_closing(s->plug, strerror(s->pending_error), s->pending_error, 0); +} + +/* + * The function which tries to send on a socket once it's deemed + * writable. + */ +void try_send(NetSocket *s) +{ + while (s->sending_oob || bufchain_size(&s->output_data) > 0) { + int nsent; + int err; + const void *data; + size_t len; + int urgentflag; + + if (s->sending_oob) { + urgentflag = MSG_OOB; + len = s->sending_oob; + data = &s->oobdata; + } else { + urgentflag = 0; + ptrlen bufdata = bufchain_prefix(&s->output_data); + data = bufdata.ptr; + len = bufdata.len; + } + nsent = send(s->s, data, len, urgentflag); + noise_ultralight(NOISE_SOURCE_IOLEN, 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; + /* + * Immediately cease selecting on this socket, so that + * we don't tight-loop repeatedly trying to do + * whatever it was that went wrong. + */ + uxsel_tell(s); + /* + * Arrange to be called back from the top level to + * deal with the error condition on this socket. + */ + queue_toplevel_callback(socket_error_callback, s); + return; + } + } else { + if (s->sending_oob) { + if (nsent < len) { + 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); + } + } + } + + /* + * If we reach here, we've finished sending everything we might + * have needed to send. Send EOF, if we need to. + */ + if (s->outgoingeof == EOF_PENDING) { + shutdown(s->s, SHUT_WR); + s->outgoingeof = EOF_SENT; + } + + /* + * Also update the select status, because we don't need to select + * for writing any more. + */ + uxsel_tell(s); +} + +static size_t sk_net_write(Socket *sock, const void *buf, size_t len) +{ + NetSocket *s = container_of(sock, NetSocket, sock); + + assert(s->outgoingeof == EOF_NO); + + /* + * 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 size_t sk_net_write_oob(Socket *sock, const void *buf, size_t len) +{ + NetSocket *s = container_of(sock, NetSocket, sock); + + assert(s->outgoingeof == EOF_NO); + + /* + * Replace the buffer list on the socket with the data. + */ + bufchain_clear(&s->output_data); + assert(len <= sizeof(s->oobdata)); + 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 void sk_net_write_eof(Socket *sock) +{ + NetSocket *s = container_of(sock, NetSocket, sock); + + assert(s->outgoingeof == EOF_NO); + + /* + * Mark the socket as pending outgoing EOF. + */ + s->outgoingeof = EOF_PENDING; + + /* + * Now try sending from the start of the buffer list. + */ + if (s->writable) + try_send(s); + + /* + * Update the select() status to correctly reflect whether or + * not we should be selecting for write. + */ + uxsel_tell(s); +} + +static void net_select_result(int fd, int event) +{ + int ret; + char buf[20480]; /* nice big buffer for plenty of speed */ + NetSocket *s; + bool atmark = true; + + /* Find the Socket structure */ + s = find234(sktree, &fd, cmpforsearch); + if (!s) + return; /* boggle */ + + noise_ultralight(NOISE_SOURCE_IOID, fd); + + switch (event) { + case SELECT_X: /* 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(NOISE_SOURCE_IOLEN, ret); + if (ret <= 0) { + 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; + } + 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 SELECT_R: /* 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); + accept_ctx_t actx; + int t; /* socket of connection */ + + memset(&su, 0, addrlen); + t = accept(s->s, &su.sa, &addrlen); + if (t < 0) { + break; + } + + nonblock(t); + actx.i = t; + + if ((!s->addr || s->addr->superfamily != UNIX) && + s->localhost_only && !sockaddr_is_loopback(&su.sa)) { + close(t); /* someone let nonlocal through?! */ + } else if (plug_accepting(s->plug, sk_net_accept, actx)) { + 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) + 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) { + int atmark_from_ioctl; + if (ioctl(s->s, SIOCATMARK, &atmark_from_ioctl) == 0) { + atmark = atmark_from_ioctl; + if (atmark) + s->oobpending = false; /* clear this indicator */ + } + } else + atmark = true; + + ret = recv(s->s, buf, s->oobpending ? 1 : sizeof(buf), 0); + noise_ultralight(NOISE_SOURCE_IOLEN, ret); + if (ret < 0) { + if (errno == EWOULDBLOCK) { + break; + } + } + if (ret < 0) { + plug_closing(s->plug, strerror(errno), errno, 0); + } else if (0 == ret) { + s->incomingeof = true; /* stop trying to read now */ + uxsel_tell(s); + 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; + } + plug_receive(s->plug, atmark ? 0 : 1, buf, ret); + } + break; + case SELECT_W: /* writable */ + if (!s->connected) { + /* + * select/poll reports a socket as _writable_ when an + * asynchronous connect() attempt either completes or + * fails. So first we must find out which. + */ + { + int err; + socklen_t errlen = sizeof(err); + char *errmsg = NULL; + if (getsockopt(s->s, SOL_SOCKET, SO_ERROR, &err, &errlen)<0) { + errmsg = dupprintf("getsockopt(SO_ERROR): %s", + strerror(errno)); + err = errno; /* got to put something in here */ + } else if (err != 0) { + errmsg = dupstr(strerror(err)); + } + if (errmsg) { + /* + * The asynchronous connection attempt failed. + * Report the problem via plug_log, and try again + * with the next candidate address, if we have + * more than one. + */ + SockAddr thisaddr; + assert(s->addr); + + thisaddr = sk_extractaddr_tmp(s->addr, &s->step); + plug_log(s->plug, PLUGLOG_CONNECT_FAILED, + &thisaddr, s->port, errmsg, err); + + while (err && s->addr && sk_nextaddr(s->addr, &s->step)) { + err = try_connect(s); + } + if (err) { + plug_closing(s->plug, strerror(err), err, 0); + return; /* socket is now presumably defunct */ + } + if (!s->connected) + return; /* another async attempt in progress */ + } else { + /* + * The connection attempt succeeded. + */ + SockAddr thisaddr = sk_extractaddr_tmp(s->addr, &s->step); + plug_log(s->plug, PLUGLOG_CONNECT_SUCCESS, + &thisaddr, s->port, NULL, 0); + } + } + + /* + * If we get here, we've managed to make a connection. + */ + if (s->addr) { + sk_addr_free(s->addr); + s->addr = NULL; + } + s->connected = true; + s->writable = true; + uxsel_tell(s); + } else { + size_t bufsize_before, bufsize_after; + s->writable = true; + 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; + } +} + +/* + * 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_net_socket_error(Socket *sock) +{ + NetSocket *s = container_of(sock, NetSocket, sock); + return s->error; +} + +static void sk_net_set_frozen(Socket *sock, bool is_frozen) +{ + NetSocket *s = container_of(sock, NetSocket, sock); + if (s->frozen == is_frozen) + return; + s->frozen = is_frozen; + uxsel_tell(s); +} + +static SocketPeerInfo *sk_net_peer_info(Socket *sock) +{ + NetSocket *s = container_of(sock, NetSocket, sock); + union sockaddr_union addr; + socklen_t addrlen = sizeof(addr); +#ifndef NO_IPV6 + char buf[INET6_ADDRSTRLEN]; +#endif + SocketPeerInfo *pi; + + if (getpeername(s->s, &addr.sa, &addrlen) < 0) + return NULL; + + pi = snew(SocketPeerInfo); + pi->addressfamily = ADDRTYPE_UNSPEC; + pi->addr_text = NULL; + pi->port = -1; + pi->log_text = NULL; + + if (addr.storage.ss_family == AF_INET) { + pi->addressfamily = ADDRTYPE_IPV4; + memcpy(pi->addr_bin.ipv4, &addr.sin.sin_addr, 4); + pi->port = ntohs(addr.sin.sin_port); + pi->addr_text = dupstr(inet_ntoa(addr.sin.sin_addr)); + pi->log_text = dupprintf("%s:%d", pi->addr_text, pi->port); + +#ifndef NO_IPV6 + } else if (addr.storage.ss_family == AF_INET6) { + pi->addressfamily = ADDRTYPE_IPV6; + memcpy(pi->addr_bin.ipv6, &addr.sin6.sin6_addr, 16); + pi->port = ntohs(addr.sin6.sin6_port); + pi->addr_text = dupstr( + inet_ntop(AF_INET6, &addr.sin6.sin6_addr, buf, sizeof(buf))); + pi->log_text = dupprintf("[%s]:%d", pi->addr_text, pi->port); +#endif + + } else if (addr.storage.ss_family == AF_UNIX) { + pi->addressfamily = ADDRTYPE_LOCAL; + + /* + * For Unix sockets, the source address is unlikely to be + * helpful, so we leave addr_txt NULL (and we certainly can't + * fill in port, obviously). Instead, we try SO_PEERCRED and + * try to get the source pid, and put that in the log text. + */ + int pid, uid, gid; + if (so_peercred(s->s, &pid, &uid, &gid)) { + char uidbuf[64], gidbuf[64]; + sprintf(uidbuf, "%d", uid); + sprintf(gidbuf, "%d", gid); + struct passwd *pw = getpwuid(uid); + struct group *gr = getgrgid(gid); + pi->log_text = dupprintf("pid %d (%s:%s)", pid, + pw ? pw->pw_name : uidbuf, + gr ? gr->gr_name : gidbuf); + } + } else { + sfree(pi); + return NULL; + } + + return pi; +} + +int sk_net_get_fd(Socket *sock) +{ + /* This function is not fully general: it only works on NetSocket */ + if (sock->vt != &NetSocket_sockvt) + return -1; /* failure */ + NetSocket *s = container_of(sock, NetSocket, sock); + return s->s; +} + +static void uxsel_tell(NetSocket *s) +{ + int rwx = 0; + if (!s->pending_error) { + if (s->listener) { + rwx |= SELECT_R; /* read == accept */ + } else { + if (!s->connected) + rwx |= SELECT_W; /* write == connect */ + if (s->connected && !s->frozen && !s->incomingeof) + rwx |= SELECT_R | SELECT_X; + if (bufchain_size(&s->output_data)) + rwx |= SELECT_W; + } + } + 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) +{ + size_t size = 0; + char *hostname = NULL; + do { + sgrowarray(hostname, size, size); + if ((gethostname(hostname, size) < 0) && (errno != ENAMETOOLONG)) { + sfree(hostname); + hostname = NULL; + break; + } + } while (strlen(hostname) >= size-1); + return hostname; +} + +SockAddr *platform_get_x11_unix_address(const char *sockpath, int displaynum) +{ + SockAddr *ret = snew(SockAddr); + 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; +} + +SockAddr *unix_sock_addr(const char *path) +{ + SockAddr *ret = snew(SockAddr); + int n; + + memset(ret, 0, sizeof *ret); + ret->superfamily = UNIX; + n = snprintf(ret->hostname, sizeof ret->hostname, "%s", path); + + if (n < 0) + ret->error = "snprintf failed"; + else if (n >= sizeof ret->hostname || + n >= sizeof(((struct sockaddr_un *)0)->sun_path)) + ret->error = "socket pathname too long"; + +#ifndef NO_IPV6 + ret->ais = NULL; +#else + ret->addresses = NULL; + ret->naddresses = 0; +#endif + ret->refcount = 1; + return ret; +} + +Socket *new_unix_listener(SockAddr *listenaddr, Plug *plug) +{ + int s; + union sockaddr_union u; + union sockaddr_union *addr; + int addrlen; + NetSocket *ret; + int retcode; + + /* + * Create NetSocket structure. + */ + ret = snew(NetSocket); + ret->sock.vt = &NetSocket_sockvt; + ret->error = NULL; + ret->plug = plug; + bufchain_init(&ret->output_data); + ret->writable = false; /* to start with */ + ret->sending_oob = 0; + ret->frozen = false; + ret->localhost_only = true; + ret->pending_error = 0; + ret->parent = ret->child = NULL; + ret->oobpending = false; + ret->outgoingeof = EOF_NO; + ret->incomingeof = false; + ret->listener = true; + ret->addr = listenaddr; + ret->s = -1; + + assert(listenaddr->superfamily == UNIX); + + /* + * Open socket. + */ + s = socket(AF_UNIX, SOCK_STREAM, 0); + if (s < 0) { + ret->error = strerror(errno); + return &ret->sock; + } + + cloexec(s); + + ret->oobinline = false; + + memset(&u, '\0', sizeof(u)); + u.su.sun_family = AF_UNIX; +#if __GNUC__ >= 8 +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wstringop-truncation" +#endif // __GNUC__ >= 8 + strncpy(u.su.sun_path, listenaddr->hostname, sizeof(u.su.sun_path)-1); +#if __GNUC__ >= 8 +# pragma GCC diagnostic pop +#endif // __GNUC__ >= 8 + addr = &u; + addrlen = sizeof(u.su); + + if (unlink(u.su.sun_path) < 0 && errno != ENOENT) { + close(s); + ret->error = strerror(errno); + return &ret->sock; + } + + retcode = bind(s, &addr->sa, addrlen); + if (retcode < 0) { + close(s); + ret->error = strerror(errno); + return &ret->sock; + } + + if (listen(s, SOMAXCONN) < 0) { + close(s); + ret->error = strerror(errno); + return &ret->sock; + } + + ret->s = s; + + uxsel_tell(ret); + add234(sktree, ret); + + return &ret->sock; +} diff --git a/unix/no-gtk.c b/unix/no-gtk.c new file mode 100644 index 00000000..c9028ebf --- /dev/null +++ b/unix/no-gtk.c @@ -0,0 +1,11 @@ +/* + * uxnogtk.c: link into non-GUI Unix programs so that they can tell + * buildinfo about a lack of GTK. + */ + +#include "putty.h" + +char *buildinfo_gtk_version(void) +{ + return NULL; +} diff --git a/unix/noise.c b/unix/noise.c new file mode 100644 index 00000000..0fbf8c4d --- /dev/null +++ b/unix/noise.c @@ -0,0 +1,130 @@ +/* + * Noise generation for PuTTY's cryptographic random number + * generator. + */ + +#include +#include +#include + +#include +#include +#include +#include + +#include "putty.h" +#include "ssh.h" +#include "storage.h" + +static bool read_dev_urandom(char *buf, int len) +{ + int fd; + int ngot, ret; + + fd = open("/dev/urandom", O_RDONLY); + if (fd < 0) + return false; + + ngot = 0; + while (ngot < len) { + ret = read(fd, buf+ngot, len-ngot); + if (ret < 0) { + close(fd); + return false; + } + ngot += ret; + } + + close(fd); + + return true; +} + +/* + * 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; + bool got_dev_urandom = false; + + if (read_dev_urandom(buf, 32)) { + got_dev_urandom = true; + 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); +} + +/* + * 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(NOISE_SOURCE_MEMINFO, buf, ret); + close(fd); + } + if ((fd = open("/proc/stat", O_RDONLY)) >= 0) { + while ( (ret = read(fd, buf, sizeof(buf))) > 0) + random_add_noise(NOISE_SOURCE_STAT, buf, ret); + close(fd); + } + getrusage(RUSAGE_SELF, &rusage); + random_add_noise(NOISE_SOURCE_RUSAGE, &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(NoiseSourceId id, unsigned long data) +{ + struct timeval tv; + gettimeofday(&tv, NULL); + random_add_noise(NOISE_SOURCE_TIME, &tv, sizeof(tv)); + random_add_noise(id, &data, sizeof(data)); +} + +uint64_t prng_reseed_time_ms(void) +{ + struct timeval tv; + gettimeofday(&tv, NULL); + return tv.tv_sec * 1000 + tv.tv_usec / 1000; +} diff --git a/unix/pageant.c b/unix/pageant.c new file mode 100644 index 00000000..f204ea9d --- /dev/null +++ b/unix/pageant.c @@ -0,0 +1,1509 @@ +/* + * Unix Pageant, more or less similar to ssh-agent. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "putty.h" +#include "ssh.h" +#include "misc.h" +#include "pageant.h" + +void cmdline_error(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + console_print_error_msg_fmt_v("pageant", fmt, ap); + va_end(ap); + exit(1); +} + +static void setup_sigchld_handler(void); + +typedef enum RuntimePromptType { + RTPROMPT_UNAVAILABLE, + RTPROMPT_DEBUG, + RTPROMPT_GUI, +} RuntimePromptType; + +static const char *progname; + +struct uxpgnt_client { + FILE *logfp; + strbuf *prompt_buf; + RuntimePromptType prompt_type; + bool prompt_active; + PageantClientDialogId *dlgid; + int passphrase_fd; + int termination_pid; + + PageantListenerClient plc; +}; + +static void uxpgnt_log(PageantListenerClient *plc, const char *fmt, va_list ap) +{ + struct uxpgnt_client *upc = container_of(plc, struct uxpgnt_client, plc); + + if (!upc->logfp) + return; + + fprintf(upc->logfp, "pageant: "); + vfprintf(upc->logfp, fmt, ap); + fprintf(upc->logfp, "\n"); +} + +static int make_pipe_to_askpass(const char *msg) +{ + int pipefds[2]; + + setup_sigchld_handler(); + + if (pipe(pipefds) < 0) + return -1; + + pid_t pid = fork(); + if (pid < 0) { + close(pipefds[0]); + close(pipefds[1]); + return -1; + } + + if (pid == 0) { + const char *args[5] = { + progname, "--gui-prompt", "--askpass", msg, NULL + }; + + dup2(pipefds[1], 1); + cloexec(pipefds[0]); + cloexec(pipefds[1]); + + /* + * See comment in fork_and_exec_self() in gtkmain.c. + */ + execv("/proc/self/exe", (char **)args); + execvp(progname, (char **)args); + perror("exec"); + _exit(127); + } + + close(pipefds[1]); + return pipefds[0]; +} + +static bool uxpgnt_ask_passphrase( + PageantListenerClient *plc, PageantClientDialogId *dlgid, + const char *comment) +{ + struct uxpgnt_client *upc = container_of(plc, struct uxpgnt_client, plc); + + assert(!upc->dlgid); /* Pageant core should be serialising requests */ + + char *msg = dupprintf( + "A client of Pageant wants to use the following encrypted key:\n" + "%s\n" + "If you intended this, enter the passphrase to decrypt the key.", + comment); + + switch (upc->prompt_type) { + case RTPROMPT_UNAVAILABLE: + sfree(msg); + return false; + + case RTPROMPT_GUI: + upc->passphrase_fd = make_pipe_to_askpass(msg); + sfree(msg); + if (upc->passphrase_fd < 0) + return false; /* something went wrong */ + break; + + case RTPROMPT_DEBUG: + fprintf(upc->logfp, "pageant passphrase request: %s\n", msg); + sfree(msg); + break; + } + + upc->prompt_active = true; + upc->dlgid = dlgid; + return true; +} + +static void passphrase_done(struct uxpgnt_client *upc, bool success) +{ + PageantClientDialogId *dlgid = upc->dlgid; + upc->dlgid = NULL; + upc->prompt_active = false; + + if (upc->logfp) + fprintf(upc->logfp, "pageant passphrase response: %s\n", + success ? "success" : "failure"); + + if (success) + pageant_passphrase_request_success( + dlgid, ptrlen_from_strbuf(upc->prompt_buf)); + else + pageant_passphrase_request_refused(dlgid); + + strbuf_free(upc->prompt_buf); + upc->prompt_buf = strbuf_new_nm(); +} + +static const PageantListenerClientVtable uxpgnt_vtable = { + .log = uxpgnt_log, + .ask_passphrase = uxpgnt_ask_passphrase, +}; + +/* + * More stubs. + */ +void random_save_seed(void) {} +void random_destroy_seed(void) {} +char *platform_default_s(const char *name) { return NULL; } +bool platform_default_b(const char *name, bool def) { return def; } +int platform_default_i(const char *name, int def) { return def; } +FontSpec *platform_default_fontspec(const char *name) { return fontspec_new(""); } +Filename *platform_default_filename(const char *name) { return filename_from_str(""); } +char *x_get_default(const char *key) { return NULL; } + +/* + * Short description of parameters. + */ +static void usage(void) +{ + printf("Pageant: SSH agent\n"); + printf("%s\n", ver); + printf("Usage: pageant [[--encrypted] key files]\n"); + printf(" pageant [[--encrypted] key files] --exec [args]\n"); + printf(" pageant -a [--encrypted] [key files]\n"); + printf(" pageant -d [key identifiers]\n"); + printf(" pageant -D\n"); + printf(" pageant -r [key identifiers]\n"); + printf(" pageant -R\n"); + printf(" pageant --public [key identifiers]\n"); + printf(" pageant ( --public-openssh | -L ) [key identifiers]\n"); + printf(" pageant -l [-E fptype]\n"); + printf("Lifetime options, for running Pageant as an agent:\n"); + printf(" -X run with the lifetime of the X server\n"); + printf(" -T run with the lifetime of the controlling tty\n"); + printf(" --permanent run permanently\n"); + printf(" --debug run in debugging mode, without forking\n"); + printf(" --exec run with the lifetime of that command\n"); + printf("Client options, for talking to an existing agent:\n"); + printf(" -a add key(s) to the existing agent\n"); + printf(" -l list currently loaded key fingerprints and comments\n"); + printf(" --public print public keys in RFC 4716 format\n"); + printf(" --public-openssh, -L print public keys in OpenSSH format\n"); + printf(" -d delete key(s) from the agent\n"); + printf(" -D delete all keys from the agent\n"); + printf(" -r re-encrypt keys in the agent (forget cleartext\n"); + printf(" -R re-encrypt all possible keys in the agent\n"); + printf("Other options:\n"); + printf(" -v verbose mode (in agent mode)\n"); + printf(" -s -c force POSIX or C shell syntax (in agent mode)\n"); + printf(" --symlink path create symlink to socket (in agent mode)\n"); + printf(" --encrypted when adding keys, don't decrypt\n"); + printf(" -E alg, --fptype alg fingerprint type for -l (sha256, md5)\n"); + printf(" --tty-prompt force tty-based passphrase prompt\n"); + printf(" --gui-prompt force GUI-based passphrase prompt\n"); + printf(" --askpass behave like a standalone askpass program\n"); + exit(1); +} + +static void version(void) +{ + char *buildinfo_text = buildinfo("\n"); + printf("pageant: %s\n%s\n", ver, buildinfo_text); + sfree(buildinfo_text); + exit(0); +} + +void keylist_update(void) +{ + /* Nothing needs doing in Unix Pageant */ +} + +#define PAGEANT_DIR_PREFIX "/tmp/pageant" + +const char *const appname = "Pageant"; + +static bool time_to_die = false; + +/* + * These functions are part of the plug for our connection to the X + * display, so they do get called. They needn't actually do anything, + * except that x11_closing has to signal back to the main loop that + * it's time to terminate. + */ +static void x11_log(Plug *p, PlugLogType type, SockAddr *addr, int port, + const char *error_msg, int error_code) {} +static void x11_receive(Plug *plug, int urgent, const char *data, size_t len) {} +static void x11_sent(Plug *plug, size_t bufsize) {} +static void x11_closing(Plug *plug, const char *error_msg, int error_code, + bool calling_back) +{ + time_to_die = true; +} +struct X11Connection { + Plug plug; +}; + +static char *socketname; +static enum { SHELL_AUTO, SHELL_SH, SHELL_CSH } shell_type = SHELL_AUTO; +void pageant_print_env(int pid) +{ + if (shell_type == SHELL_AUTO) { + /* Same policy as OpenSSH: if $SHELL ends in "csh" then assume + * it's csh-shaped. */ + const char *shell = getenv("SHELL"); + if (shell && strlen(shell) >= 3 && + !strcmp(shell + strlen(shell) - 3, "csh")) + shell_type = SHELL_CSH; + else + shell_type = SHELL_SH; + } + + /* + * These shell snippets could usefully pay some attention to + * escaping of interesting characters. I don't think it causes a + * problem at the moment, because the pathnames we use are so + * utterly boring, but it's a lurking bug waiting to happen once + * a bit more flexibility turns up. + */ + + switch (shell_type) { + case SHELL_SH: + printf("SSH_AUTH_SOCK=%s; export SSH_AUTH_SOCK;\n" + "SSH_AGENT_PID=%d; export SSH_AGENT_PID;\n", + socketname, pid); + break; + case SHELL_CSH: + printf("setenv SSH_AUTH_SOCK %s;\n" + "setenv SSH_AGENT_PID %d;\n", + socketname, pid); + break; + case SHELL_AUTO: + unreachable("SHELL_AUTO should have been eliminated by now"); + break; + } +} + +void pageant_fork_and_print_env(bool retain_tty) +{ + pid_t pid = fork(); + if (pid == -1) { + perror("fork"); + exit(1); + } else if (pid != 0) { + pageant_print_env(pid); + exit(0); + } + + /* + * Having forked off, we now daemonise ourselves as best we can. + * It's good practice in general to setsid() ourself out of any + * process group we didn't want to be part of, and to chdir("/") + * to avoid holding any directories open that we don't need in + * case someone wants to umount them; also, we should definitely + * close standard output (because it will very likely be pointing + * at a pipe from which some parent process is trying to read our + * environment variable dump, so if we hold open another copy of + * it then that process will never finish reading). We close + * standard input too on general principles, but not standard + * error, since we might need to shout a panicky error message + * down that one. + */ + if (chdir("/") < 0) { + /* should there be an error condition, nothing we can do about + * it anyway */ + } + close(0); + close(1); + if (retain_tty) { + /* Get out of our previous process group, to avoid being + * blasted by passing signals. But keep our controlling tty, + * so we can keep checking to see if we still have one. */ + setpgrp(); + } else { + /* Do that, but also leave our entire session and detach from + * the controlling tty (if any). */ + setsid(); + } +} + +static int signalpipe[2] = { -1, -1 }; + +static void sigchld(int signum) +{ + if (write(signalpipe[1], "x", 1) <= 0) + /* not much we can do about it */; +} + +static void setup_sigchld_handler(void) +{ + if (signalpipe[0] >= 0) + return; + + /* + * Set up the pipe we'll use to tell us about SIGCHLD. + */ + if (pipe(signalpipe) < 0) { + perror("pipe"); + exit(1); + } + putty_signal(SIGCHLD, sigchld); +} + +#define TTY_LIFE_POLL_INTERVAL (TICKSPERSEC * 30) +static void *dummy_timer_ctx; +static void tty_life_timer(void *ctx, unsigned long now) +{ + schedule_timer(TTY_LIFE_POLL_INTERVAL, tty_life_timer, &dummy_timer_ctx); +} + +typedef enum { + KEYACT_AGENT_LOAD, + KEYACT_AGENT_LOAD_ENCRYPTED, + KEYACT_CLIENT_BASE, + KEYACT_CLIENT_ADD = KEYACT_CLIENT_BASE, + KEYACT_CLIENT_ADD_ENCRYPTED, + KEYACT_CLIENT_DEL, + KEYACT_CLIENT_DEL_ALL, + KEYACT_CLIENT_LIST, + KEYACT_CLIENT_PUBLIC_OPENSSH, + KEYACT_CLIENT_PUBLIC, + KEYACT_CLIENT_SIGN, + KEYACT_CLIENT_REENCRYPT, + KEYACT_CLIENT_REENCRYPT_ALL, +} keyact; +struct cmdline_key_action { + struct cmdline_key_action *next; + keyact action; + const char *filename; +}; + +bool is_agent_action(keyact action) +{ + return action < KEYACT_CLIENT_BASE; +} + +static struct cmdline_key_action *keyact_head = NULL, *keyact_tail = NULL; +static uint32_t sign_flags = 0; + +void add_keyact(keyact action, const char *filename) +{ + struct cmdline_key_action *a = snew(struct cmdline_key_action); + a->action = action; + a->filename = filename; + a->next = NULL; + if (keyact_tail) + keyact_tail->next = a; + else + keyact_head = a; + keyact_tail = a; +} + +bool have_controlling_tty(void) +{ + int fd = open("/dev/tty", O_RDONLY); + if (fd < 0) { + if (errno != ENXIO) { + perror("/dev/tty: open"); + exit(1); + } + return false; + } else { + close(fd); + return true; + } +} + +static char **exec_args = NULL; +static enum { + LIFE_UNSPEC, LIFE_X11, LIFE_TTY, LIFE_DEBUG, LIFE_PERM, LIFE_EXEC +} life = LIFE_UNSPEC; +static const char *display = NULL; +static enum { + PROMPT_UNSPEC, PROMPT_TTY, PROMPT_GUI +} prompt_type = PROMPT_UNSPEC; +static FingerprintType key_list_fptype = SSH_FPTYPE_DEFAULT; + +static char *askpass_tty(const char *prompt) +{ + int ret; + prompts_t *p = new_prompts(); + p->to_server = false; + p->from_server = false; + p->name = dupstr("Pageant passphrase prompt"); + add_prompt(p, dupcat(prompt, ": "), false); + ret = console_get_userpass_input(p); + assert(ret >= 0); + + if (!ret) { + perror("pageant: unable to read passphrase"); + free_prompts(p); + return NULL; + } else { + char *passphrase = prompt_get_result(p->prompts[0]); + free_prompts(p); + return passphrase; + } +} + +static char *askpass_gui(const char *prompt) +{ + char *passphrase; + bool success; + + passphrase = gtk_askpass_main( + display, "Pageant passphrase prompt", prompt, &success); + if (!success) { + /* return value is error message */ + fprintf(stderr, "%s\n", passphrase); + sfree(passphrase); + passphrase = NULL; + } + return passphrase; +} + +static char *askpass(const char *prompt) +{ + if (prompt_type == PROMPT_TTY) { + if (!have_controlling_tty()) { + fprintf(stderr, "no controlling terminal available " + "for passphrase prompt\n"); + return NULL; + } + return askpass_tty(prompt); + } + + if (prompt_type == PROMPT_GUI) { + if (!display) { + fprintf(stderr, "no graphical display available " + "for passphrase prompt\n"); + return NULL; + } + return askpass_gui(prompt); + } + + if (have_controlling_tty()) { + return askpass_tty(prompt); + } else if (display) { + return askpass_gui(prompt); + } else { + fprintf(stderr, "no way to read a passphrase without tty or " + "X display\n"); + return NULL; + } +} + +static bool unix_add_keyfile(const char *filename_str, bool add_encrypted) +{ + Filename *filename = filename_from_str(filename_str); + int status; + bool ret; + char *err; + + ret = true; + + /* + * Try without a passphrase. + */ + status = pageant_add_keyfile(filename, NULL, &err, add_encrypted); + if (status == PAGEANT_ACTION_OK) { + goto cleanup; + } else if (status == PAGEANT_ACTION_FAILURE) { + fprintf(stderr, "pageant: %s: %s\n", filename_str, err); + ret = false; + goto cleanup; + } + + /* + * And now try prompting for a passphrase. + */ + while (1) { + char *prompt = dupprintf( + "Enter passphrase to load key '%s'", err); + char *passphrase = askpass(prompt); + sfree(err); + sfree(prompt); + err = NULL; + if (!passphrase) + break; + + status = pageant_add_keyfile(filename, passphrase, &err, + add_encrypted); + + smemclr(passphrase, strlen(passphrase)); + sfree(passphrase); + passphrase = NULL; + + if (status == PAGEANT_ACTION_OK) { + goto cleanup; + } else if (status == PAGEANT_ACTION_FAILURE) { + fprintf(stderr, "pageant: %s: %s\n", filename_str, err); + ret = false; + goto cleanup; + } + } + + cleanup: + sfree(err); + filename_free(filename); + return ret; +} + +void key_list_callback(void *ctx, char **fingerprints, const char *comment, + uint32_t ext_flags, struct pageant_pubkey *key) +{ + const char *mode = ""; + if (ext_flags & LIST_EXTENDED_FLAG_HAS_NO_CLEARTEXT_KEY) + mode = " (encrypted)"; + else if (ext_flags & LIST_EXTENDED_FLAG_HAS_ENCRYPTED_KEY_FILE) + mode = " (re-encryptable)"; + + FingerprintType this_type = + ssh2_pick_fingerprint(fingerprints, key_list_fptype); + printf("%s %s%s\n", fingerprints[this_type], comment, mode); +} + +struct key_find_ctx { + const char *string; + bool match_fp, match_comment; + bool match_fptypes[SSH_N_FPTYPES]; + struct pageant_pubkey *found; + int nfound; +}; + +static bool match_fingerprint_string( + const char *string_orig, char **fingerprints, + const struct key_find_ctx *ctx) +{ + const char *hash; + + for (unsigned fptype = 0; fptype < SSH_N_FPTYPES; fptype++) { + if (!ctx->match_fptypes[fptype]) + continue; + + const char *fingerprint = fingerprints[fptype]; + if (!fingerprint) + continue; + + /* Find the hash in the fingerprint string. It'll be the word + * at the end. */ + hash = strrchr(fingerprint, ' '); + assert(hash); + hash++; + + const char *string = string_orig; + bool case_sensitive; + const char *ignore_chars = ""; + + switch (fptype) { + case SSH_FPTYPE_MD5: + /* MD5 fingerprints are in hex, so disregard case differences. */ + case_sensitive = false; + /* And we don't really need to force the user to type the + * colons in between the digits, which are always the + * same. */ + ignore_chars = ":"; + break; + case SSH_FPTYPE_SHA256: + /* Skip over the "SHA256:" prefix, which we don't really + * want to force the user to type. On the other hand, + * tolerate it on the input string. */ + assert(strstartswith(hash, "SHA256:")); + hash += 7; + if (strstartswith(string, "SHA256:")) + string += 7; + /* SHA256 fingerprints are base64, which is intrinsically + * case sensitive. */ + case_sensitive = true; + break; + } + + /* Now see if the search string is a prefix of the full hash, + * neglecting colons and (where appropriate) case differences. */ + while (1) { + string += strspn(string, ignore_chars); + hash += strspn(hash, ignore_chars); + if (!*string) + return true; + char sc = *string, hc = *hash; + if (!case_sensitive) { + sc = tolower((unsigned char)sc); + hc = tolower((unsigned char)hc); + } + if (sc != hc) + break; + string++; + hash++; + } + } + + return false; +} + +void key_find_callback(void *vctx, char **fingerprints, + const char *comment, uint32_t ext_flags, + struct pageant_pubkey *key) +{ + struct key_find_ctx *ctx = (struct key_find_ctx *)vctx; + + if ((ctx->match_comment && !strcmp(ctx->string, comment)) || + (ctx->match_fp && match_fingerprint_string(ctx->string, fingerprints, + ctx))) + { + if (!ctx->found) + ctx->found = pageant_pubkey_copy(key); + ctx->nfound++; + } +} + +struct pageant_pubkey *find_key(const char *string, char **retstr) +{ + struct key_find_ctx ctx[1]; + struct pageant_pubkey key_in, *key_ret; + bool try_file = true, try_fp = true, try_comment = true; + bool file_errors = false; + bool try_all_fptypes = true; + FingerprintType fptype = SSH_FPTYPE_DEFAULT; + + /* + * Trim off disambiguating prefixes telling us how to interpret + * the provided string. + */ + if (!strncmp(string, "file:", 5)) { + string += 5; + try_fp = false; + try_comment = false; + file_errors = true; /* also report failure to load the file */ + } else if (!strncmp(string, "comment:", 8)) { + string += 8; + try_file = false; + try_fp = false; + } else if (!strncmp(string, "fp:", 3)) { + string += 3; + try_file = false; + try_comment = false; + } else if (!strncmp(string, "fingerprint:", 12)) { + string += 12; + try_file = false; + try_comment = false; + } else if (!strnicmp(string, "md5:", 4)) { + string += 4; + try_file = false; + try_comment = false; + try_all_fptypes = false; + fptype = SSH_FPTYPE_MD5; + } else if (!strncmp(string, "sha256:", 7)) { + string += 7; + try_file = false; + try_comment = false; + try_all_fptypes = false; + fptype = SSH_FPTYPE_SHA256; + } + + /* + * Try interpreting the string as a key file name. + */ + if (try_file) { + Filename *fn = filename_from_str(string); + int keytype = key_type(fn); + if (keytype == SSH_KEYTYPE_SSH1 || + keytype == SSH_KEYTYPE_SSH1_PUBLIC) { + const char *error; + + key_in.blob = strbuf_new(); + if (!rsa1_loadpub_f(fn, BinarySink_UPCAST(key_in.blob), + NULL, &error)) { + strbuf_free(key_in.blob); + key_in.blob = NULL; + if (file_errors) { + *retstr = dupprintf("unable to load file '%s': %s", + string, error); + filename_free(fn); + return NULL; + } + } else { + /* + * If we've successfully loaded the file, stop here - we + * already have a key blob and need not go to the agent to + * list things. + */ + key_in.ssh_version = 1; + key_in.comment = NULL; + key_ret = pageant_pubkey_copy(&key_in); + strbuf_free(key_in.blob); + key_in.blob = NULL; + filename_free(fn); + return key_ret; + } + } else if (keytype == SSH_KEYTYPE_SSH2 || + keytype == SSH_KEYTYPE_SSH2_PUBLIC_RFC4716 || + keytype == SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH) { + const char *error; + + key_in.blob = strbuf_new(); + if (!ppk_loadpub_f(fn, NULL, BinarySink_UPCAST(key_in.blob), + NULL, &error)) { + strbuf_free(key_in.blob); + key_in.blob = NULL; + if (file_errors) { + *retstr = dupprintf("unable to load file '%s': %s", + string, error); + filename_free(fn); + return NULL; + } + } else { + /* + * If we've successfully loaded the file, stop here - we + * already have a key blob and need not go to the agent to + * list things. + */ + key_in.ssh_version = 2; + key_in.comment = NULL; + key_ret = pageant_pubkey_copy(&key_in); + strbuf_free(key_in.blob); + key_in.blob = NULL; + filename_free(fn); + return key_ret; + } + } else { + if (file_errors) { + *retstr = dupprintf("unable to load key file '%s': %s", + string, key_type_to_str(keytype)); + filename_free(fn); + return NULL; + } + } + filename_free(fn); + } + + /* + * Failing that, go through the keys in the agent, and match + * against fingerprints and comments as appropriate. + */ + ctx->string = string; + ctx->match_fp = try_fp; + ctx->match_comment = try_comment; + for (unsigned i = 0; i < SSH_N_FPTYPES; i++) + ctx->match_fptypes[i] = (try_all_fptypes || i == fptype); + ctx->found = NULL; + ctx->nfound = 0; + if (pageant_enum_keys(key_find_callback, ctx, retstr) == + PAGEANT_ACTION_FAILURE) + return NULL; + + if (ctx->nfound == 0) { + *retstr = dupstr("no key matched"); + assert(!ctx->found); + return NULL; + } else if (ctx->nfound > 1) { + *retstr = dupstr("multiple keys matched"); + assert(ctx->found); + pageant_pubkey_free(ctx->found); + return NULL; + } + + assert(ctx->found); + return ctx->found; +} + +void run_client(void) +{ + const struct cmdline_key_action *act; + struct pageant_pubkey *key; + bool errors = false; + char *retstr; + LoadedFile *message = lf_new(AGENT_MAX_MSGLEN); + bool message_loaded = false, message_ok = false; + strbuf *signature = strbuf_new(); + + if (!agent_exists()) { + fprintf(stderr, "pageant: no agent running to talk to\n"); + exit(1); + } + + for (act = keyact_head; act; act = act->next) { + switch (act->action) { + case KEYACT_CLIENT_ADD: + case KEYACT_CLIENT_ADD_ENCRYPTED: + if (!unix_add_keyfile(act->filename, + act->action == KEYACT_CLIENT_ADD_ENCRYPTED)) + errors = true; + break; + case KEYACT_CLIENT_LIST: + if (pageant_enum_keys(key_list_callback, NULL, &retstr) == + PAGEANT_ACTION_FAILURE) { + fprintf(stderr, "pageant: listing keys: %s\n", retstr); + sfree(retstr); + errors = true; + } + break; + case KEYACT_CLIENT_DEL: + key = NULL; + if (!(key = find_key(act->filename, &retstr)) || + pageant_delete_key(key, &retstr) == PAGEANT_ACTION_FAILURE) { + fprintf(stderr, "pageant: deleting key '%s': %s\n", + act->filename, retstr); + sfree(retstr); + errors = true; + } + if (key) + pageant_pubkey_free(key); + break; + case KEYACT_CLIENT_REENCRYPT: + key = NULL; + if (!(key = find_key(act->filename, &retstr)) || + pageant_reencrypt_key(key, &retstr) == PAGEANT_ACTION_FAILURE) { + fprintf(stderr, "pageant: re-encrypting key '%s': %s\n", + act->filename, retstr); + sfree(retstr); + errors = true; + } + if (key) + pageant_pubkey_free(key); + break; + case KEYACT_CLIENT_PUBLIC_OPENSSH: + case KEYACT_CLIENT_PUBLIC: + key = NULL; + if (!(key = find_key(act->filename, &retstr))) { + fprintf(stderr, "pageant: finding key '%s': %s\n", + act->filename, retstr); + sfree(retstr); + errors = true; + } else { + FILE *fp = stdout; /* FIXME: add a -o option? */ + + if (key->ssh_version == 1) { + BinarySource src[1]; + RSAKey rkey; + + BinarySource_BARE_INIT(src, key->blob->u, key->blob->len); + memset(&rkey, 0, sizeof(rkey)); + rkey.comment = dupstr(key->comment); + get_rsa_ssh1_pub(src, &rkey, RSA_SSH1_EXPONENT_FIRST); + ssh1_write_pubkey(fp, &rkey); + freersakey(&rkey); + } else { + ssh2_write_pubkey(fp, key->comment, + key->blob->u, + key->blob->len, + (act->action == KEYACT_CLIENT_PUBLIC ? + SSH_KEYTYPE_SSH2_PUBLIC_RFC4716 : + SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH)); + } + pageant_pubkey_free(key); + } + break; + case KEYACT_CLIENT_DEL_ALL: + if (pageant_delete_all_keys(&retstr) == PAGEANT_ACTION_FAILURE) { + fprintf(stderr, "pageant: deleting all keys: %s\n", retstr); + sfree(retstr); + errors = true; + } + break; + case KEYACT_CLIENT_REENCRYPT_ALL: { + int status = pageant_reencrypt_all_keys(&retstr); + if (status == PAGEANT_ACTION_FAILURE) { + fprintf(stderr, "pageant: re-encrypting all keys: " + "%s\n", retstr); + sfree(retstr); + errors = true; + } else if (status == PAGEANT_ACTION_WARNING) { + fprintf(stderr, "pageant: re-encrypting all keys: " + "warning: %s\n", retstr); + sfree(retstr); + } + break; + } + case KEYACT_CLIENT_SIGN: + key = NULL; + if (!message_loaded) { + message_loaded = true; + switch(lf_load_fp(message, stdin)) { + case LF_TOO_BIG: + fprintf(stderr, "pageant: message to sign is too big\n"); + errors = true; + break; + case LF_ERROR: + fprintf(stderr, "pageant: reading message to sign: %s\n", + strerror(errno)); + errors = true; + break; + case LF_OK: + message_ok = true; + break; + } + } + if (!message_ok) + break; + strbuf_clear(signature); + if (!(key = find_key(act->filename, &retstr)) || + pageant_sign(key, ptrlen_from_lf(message), signature, + sign_flags, &retstr) == PAGEANT_ACTION_FAILURE) { + fprintf(stderr, "pageant: signing with key '%s': %s\n", + act->filename, retstr); + sfree(retstr); + errors = true; + } else { + fwrite(signature->s, 1, signature->len, stdout); + } + if (key) + pageant_pubkey_free(key); + break; + default: + unreachable("Invalid client action found"); + } + } + + lf_free(message); + strbuf_free(signature); + + if (errors) + exit(1); +} + +static const PlugVtable X11Connection_plugvt = { + .log = x11_log, + .closing = x11_closing, + .receive = x11_receive, + .sent = x11_sent, +}; + + +static bool agent_loop_pw_setup(void *vctx, pollwrapper *pw) +{ + struct uxpgnt_client *upc = (struct uxpgnt_client *)vctx; + + if (signalpipe[0] >= 0) { + pollwrap_add_fd_rwx(pw, signalpipe[0], SELECT_R); + } + + if (upc->prompt_active) + pollwrap_add_fd_rwx(pw, upc->passphrase_fd, SELECT_R); + + return true; +} + +static void agent_loop_pw_check(void *vctx, pollwrapper *pw) +{ + struct uxpgnt_client *upc = (struct uxpgnt_client *)vctx; + + if (life == LIFE_TTY) { + /* + * Every time we wake up (whether it was due to tty_timer + * elapsing or for any other reason), poll to see if we still + * have a controlling terminal. If we don't, then our + * containing tty session has ended, so it's time to clean up + * and leave. + */ + if (!have_controlling_tty()) { + time_to_die = true; + return; + } + } + + if (signalpipe[0] >= 0 && + pollwrap_check_fd_rwx(pw, signalpipe[0], SELECT_R)) { + char c[1]; + if (read(signalpipe[0], c, 1) <= 0) + /* ignore error */; + /* ignore its value; it'll be `x' */ + while (1) { + int status; + pid_t pid; + pid = waitpid(-1, &status, WNOHANG); + if (pid <= 0) + break; + if (pid == upc->termination_pid) + time_to_die = true; + } + } + + if (upc->prompt_active && + pollwrap_check_fd_rwx(pw, upc->passphrase_fd, SELECT_R)) { + char c; + int retd = read(upc->passphrase_fd, &c, 1); + + switch (upc->prompt_type) { + case RTPROMPT_GUI: + if (retd <= 0) { + close(upc->passphrase_fd); + upc->passphrase_fd = -1; + bool ok = (retd == 0); + if (!strbuf_chomp(upc->prompt_buf, '\n')) + ok = false; + passphrase_done(upc, ok); + } else { + put_byte(upc->prompt_buf, c); + } + break; + case RTPROMPT_DEBUG: + if (retd <= 0) { + passphrase_done(upc, false); + /* Now never try to read from stdin again */ + upc->prompt_type = RTPROMPT_UNAVAILABLE; + break; + } + + switch (c) { + case '\n': + case '\r': + passphrase_done(upc, true); + break; + case '\004': + passphrase_done(upc, false); + break; + case '\b': + case '\177': + strbuf_shrink_by(upc->prompt_buf, 1); + break; + case '\025': + strbuf_clear(upc->prompt_buf); + break; + default: + put_byte(upc->prompt_buf, c); + break; + } + break; + case RTPROMPT_UNAVAILABLE: + unreachable("Should never have started a prompt at all"); + } + } +} + +static bool agent_loop_continue(void *vctx, bool fd, bool cb) +{ + return !time_to_die; +} + +void run_agent(FILE *logfp, const char *symlink_path) +{ + const char *err; + char *errw; + struct pageant_listen_state *pl; + Plug *pl_plug; + Socket *sock; + bool errors = false; + Conf *conf; + const struct cmdline_key_action *act; + + pageant_init(); + + /* + * Start by loading any keys provided on the command line. + */ + for (act = keyact_head; act; act = act->next) { + assert(act->action == KEYACT_AGENT_LOAD || + act->action == KEYACT_AGENT_LOAD_ENCRYPTED); + if (!unix_add_keyfile(act->filename, + act->action == KEYACT_AGENT_LOAD_ENCRYPTED)) + errors = true; + } + if (errors) + exit(1); + + /* + * Set up a listening socket and run Pageant on it. + */ + struct uxpgnt_client upc[1]; + memset(upc, 0, sizeof(upc)); + upc->plc.vt = &uxpgnt_vtable; + upc->logfp = logfp; + upc->passphrase_fd = -1; + upc->termination_pid = -1; + upc->prompt_buf = strbuf_new_nm(); + upc->prompt_type = display ? RTPROMPT_GUI : RTPROMPT_UNAVAILABLE; + pl = pageant_listener_new(&pl_plug, &upc->plc); + sock = platform_make_agent_socket(pl_plug, PAGEANT_DIR_PREFIX, + &errw, &socketname); + if (!sock) { + fprintf(stderr, "pageant: %s\n", errw); + sfree(errw); + exit(1); + } + pageant_listener_got_socket(pl, sock); + + if (symlink_path) { + /* + * Try to make a symlink to the Unix socket, in a location of + * the user's choosing. + * + * If the link already exists, we want to replace it. There + * are two ways we could do this: either make it under another + * name and then rename it over the top, or remove the old + * link first. The former is what 'ln -sf' does, on the + * grounds that it's more atomic. But I think in this case, + * where the expected use case is that the previous agent has + * long since shut down, atomicity isn't a critical concern + * compared to not accidentally overwriting some non-symlink + * that might have important data in it! + */ + struct stat st; + if (lstat(symlink_path, &st) == 0 && S_ISLNK(st.st_mode)) + unlink(symlink_path); + if (symlink(socketname, symlink_path) < 0) + fprintf(stderr, "pageant: making symlink %s: %s\n", + symlink_path, strerror(errno)); + } + + conf = conf_new(); + conf_set_int(conf, CONF_proxy_type, PROXY_NONE); + + /* + * Lifetime preparations. + */ + if (life == LIFE_X11) { + struct X11Display *disp; + void *greeting; + int greetinglen; + Socket *s; + struct X11Connection *conn; + char *x11_setup_err; + + if (!display) { + fprintf(stderr, "pageant: no DISPLAY for -X mode\n"); + exit(1); + } + disp = x11_setup_display(display, conf, &x11_setup_err); + if (!disp) { + fprintf(stderr, "pageant: unable to connect to X server: %s\n", + x11_setup_err); + sfree(x11_setup_err); + exit(1); + } + + conn = snew(struct X11Connection); + conn->plug.vt = &X11Connection_plugvt; + s = new_connection(sk_addr_dup(disp->addr), + disp->realhost, disp->port, + false, true, false, false, &conn->plug, conf); + if ((err = sk_socket_error(s)) != NULL) { + fprintf(stderr, "pageant: unable to connect to X server: %s", err); + exit(1); + } + greeting = x11_make_greeting('B', 11, 0, disp->localauthproto, + disp->localauthdata, + disp->localauthdatalen, + NULL, 0, &greetinglen); + sk_write(s, greeting, greetinglen); + smemclr(greeting, greetinglen); + sfree(greeting); + + pageant_fork_and_print_env(false); + } else if (life == LIFE_TTY) { + schedule_timer(TTY_LIFE_POLL_INTERVAL, + tty_life_timer, &dummy_timer_ctx); + pageant_fork_and_print_env(true); + } else if (life == LIFE_PERM) { + pageant_fork_and_print_env(false); + } else if (life == LIFE_DEBUG) { + pageant_print_env(getpid()); + upc->logfp = stdout; + + struct termios orig_termios; + upc->passphrase_fd = fileno(stdin); + if (tcgetattr(upc->passphrase_fd, &orig_termios) == 0) { + struct termios new_termios = orig_termios; + new_termios.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL | ICANON); + + /* + * Try to set up a watchdog process that will restore + * termios if we crash or are killed. If successful, turn + * off echo, for runtime passphrase prompts. + */ + int pipefd[2]; + if (pipe(pipefd) == 0) { + pid_t pid = fork(); + if (pid == 0) { + tcsetattr(upc->passphrase_fd, TCSADRAIN, &new_termios); + close(pipefd[1]); + char buf[4096]; + while (read(pipefd[0], buf, sizeof(buf)) > 0); + tcsetattr(upc->passphrase_fd, TCSADRAIN, &new_termios); + _exit(0); + } else if (pid > 0) { + upc->prompt_type = RTPROMPT_DEBUG; + } + + close(pipefd[0]); + if (pid < 0) + close(pipefd[1]); + } + } + } else if (life == LIFE_EXEC) { + pid_t agentpid, pid; + + agentpid = getpid(); + setup_sigchld_handler(); + + pid = fork(); + if (pid < 0) { + perror("fork"); + exit(1); + } else if (pid == 0) { + setenv("SSH_AUTH_SOCK", socketname, true); + setenv("SSH_AGENT_PID", dupprintf("%d", (int)agentpid), true); + execvp(exec_args[0], exec_args); + perror("exec"); + _exit(127); + } else { + upc->termination_pid = pid; + } + } + + if (!upc->logfp) + upc->plc.suppress_logging = true; + + cli_main_loop(agent_loop_pw_setup, agent_loop_pw_check, + agent_loop_continue, upc); + + /* + * Before terminating, clean up our Unix socket file if possible. + */ + if (unlink(socketname) < 0) { + fprintf(stderr, "pageant: %s: %s\n", socketname, strerror(errno)); + exit(1); + } + + strbuf_free(upc->prompt_buf); + conf_free(conf); +} + +int main(int argc, char **argv) +{ + bool doing_opts = true; + keyact curr_keyact = KEYACT_AGENT_LOAD; + const char *standalone_askpass_prompt = NULL; + const char *symlink_path = NULL; + FILE *logfp = NULL; + + progname = argv[0]; + + /* + * Process the command line. + */ + while (--argc > 0) { + char *p = *++argv; + if (*p == '-' && doing_opts) { + if (!strcmp(p, "-V") || !strcmp(p, "--version")) { + version(); + } else if (!strcmp(p, "--help")) { + usage(); + exit(0); + } else if (!strcmp(p, "-v")) { + logfp = stderr; + } else if (!strcmp(p, "-a")) { + curr_keyact = KEYACT_CLIENT_ADD; + } else if (!strcmp(p, "-d")) { + curr_keyact = KEYACT_CLIENT_DEL; + } else if (!strcmp(p, "-r")) { + curr_keyact = KEYACT_CLIENT_REENCRYPT; + } else if (!strcmp(p, "-s")) { + shell_type = SHELL_SH; + } else if (!strcmp(p, "-c")) { + shell_type = SHELL_CSH; + } else if (!strcmp(p, "-D")) { + add_keyact(KEYACT_CLIENT_DEL_ALL, NULL); + } else if (!strcmp(p, "-R")) { + add_keyact(KEYACT_CLIENT_REENCRYPT_ALL, NULL); + } else if (!strcmp(p, "-l")) { + add_keyact(KEYACT_CLIENT_LIST, NULL); + } else if (!strcmp(p, "--public")) { + curr_keyact = KEYACT_CLIENT_PUBLIC; + } else if (!strcmp(p, "--public-openssh") || !strcmp(p, "-L")) { + curr_keyact = KEYACT_CLIENT_PUBLIC_OPENSSH; + } else if (!strcmp(p, "-X")) { + life = LIFE_X11; + } else if (!strcmp(p, "-T")) { + life = LIFE_TTY; + } else if (!strcmp(p, "--no-decrypt") || + !strcmp(p, "-no-decrypt") || + !strcmp(p, "--no_decrypt") || + !strcmp(p, "-no_decrypt") || + !strcmp(p, "--nodecrypt") || + !strcmp(p, "-nodecrypt") || + !strcmp(p, "--encrypted") || + !strcmp(p, "-encrypted")) { + if (curr_keyact == KEYACT_AGENT_LOAD) + curr_keyact = KEYACT_AGENT_LOAD_ENCRYPTED; + else if (curr_keyact == KEYACT_CLIENT_ADD) + curr_keyact = KEYACT_CLIENT_ADD_ENCRYPTED; + else { + fprintf(stderr, "pageant: unexpected -E while not adding " + "keys\n"); + exit(1); + } + } else if (!strcmp(p, "--debug")) { + life = LIFE_DEBUG; + } else if (!strcmp(p, "--test-sign")) { + curr_keyact = KEYACT_CLIENT_SIGN; + sign_flags = 0; + } else if (strstartswith(p, "--test-sign-with-flags=")) { + curr_keyact = KEYACT_CLIENT_SIGN; + sign_flags = atoi(p + strlen("--test-sign-with-flags=")); + } else if (!strcmp(p, "--permanent")) { + life = LIFE_PERM; + } else if (!strcmp(p, "--exec")) { + life = LIFE_EXEC; + /* Now all subsequent arguments go to the exec command. */ + if (--argc > 0) { + exec_args = ++argv; + argc = 0; /* force end of option processing */ + } else { + fprintf(stderr, "pageant: expected a command " + "after --exec\n"); + exit(1); + } + } else if (!strcmp(p, "--tty-prompt")) { + prompt_type = PROMPT_TTY; + } else if (!strcmp(p, "--gui-prompt")) { + prompt_type = PROMPT_GUI; + } else if (!strcmp(p, "--askpass")) { + if (--argc > 0) { + standalone_askpass_prompt = *++argv; + } else { + fprintf(stderr, "pageant: expected a prompt message " + "after --askpass\n"); + exit(1); + } + } else if (!strcmp(p, "--symlink")) { + if (--argc > 0) { + symlink_path = *++argv; + } else { + fprintf(stderr, "pageant: expected a pathname " + "after --symlink\n"); + exit(1); + } + } else if (!strcmp(p, "-E") || !strcmp(p, "--fptype")) { + const char *keyword; + if (--argc > 0) { + keyword = *++argv; + } else { + fprintf(stderr, "pageant: expected a type string " + "after %s\n", p); + exit(1); + } + if (!strcmp(keyword, "md5")) + key_list_fptype = SSH_FPTYPE_MD5; + else if (!strcmp(keyword, "sha256")) + key_list_fptype = SSH_FPTYPE_SHA256; + else { + fprintf(stderr, "pageant: unknown fingerprint type `%s'\n", + keyword); + exit(1); + } + } else if (!strcmp(p, "--")) { + doing_opts = false; + } else { + fprintf(stderr, "pageant: unrecognised option '%s'\n", p); + exit(1); + } + } else { + /* + * Non-option arguments (apart from those after --exec, + * which are treated specially above) are interpreted as + * the names of private key files to either add or delete + * from an agent. + */ + add_keyact(curr_keyact, p); + } + } + + if (life == LIFE_EXEC && !exec_args) { + fprintf(stderr, "pageant: expected a command with --exec\n"); + exit(1); + } + + if (!display) { + display = getenv("DISPLAY"); + if (display && !*display) + display = NULL; + } + + /* + * Deal with standalone-askpass mode. + */ + if (standalone_askpass_prompt) { + char *passphrase = askpass(standalone_askpass_prompt); + + if (!passphrase) + return 1; + + puts(passphrase); + fflush(stdout); + + smemclr(passphrase, strlen(passphrase)); + sfree(passphrase); + return 0; + } + + /* + * Block SIGPIPE, so that we'll get EPIPE individually on + * particular network connections that go wrong. + */ + putty_signal(SIGPIPE, SIG_IGN); + + sk_init(); + uxsel_init(); + + /* + * Now distinguish our two main running modes. Either we're + * actually starting up an agent, in which case we should have a + * lifetime mode, and no key actions of KEYACT_CLIENT_* type; or + * else we're contacting an existing agent to add or remove keys, + * in which case we should have no lifetime mode, and no key + * actions of KEYACT_AGENT_* type. + */ + { + bool has_agent_actions = false; + bool has_client_actions = false; + bool has_lifetime = false; + const struct cmdline_key_action *act; + + for (act = keyact_head; act; act = act->next) { + if (is_agent_action(act->action)) + has_agent_actions = true; + else + has_client_actions = true; + } + if (life != LIFE_UNSPEC) + has_lifetime = true; + + if (has_lifetime && has_client_actions) { + fprintf(stderr, "pageant: client key actions (-a, -d, -D, -r, -R, " + "-l, -L) do not go with an agent lifetime option\n"); + exit(1); + } + if (!has_lifetime && has_agent_actions) { + fprintf(stderr, "pageant: expected an agent lifetime option with" + " bare key file arguments\n"); + exit(1); + } + if (!has_lifetime && !has_client_actions) { + fprintf(stderr, "pageant: expected an agent lifetime option" + " or a client key action\n"); + exit(1); + } + + if (has_lifetime) { + run_agent(logfp, symlink_path); + } else if (has_client_actions) { + run_client(); + } + } + + return 0; +} diff --git a/unix/peerinfo.c b/unix/peerinfo.c new file mode 100644 index 00000000..11f03291 --- /dev/null +++ b/unix/peerinfo.c @@ -0,0 +1,32 @@ +/* + * Unix: wrapper for getsockopt(SO_PEERCRED), conditionalised on + * appropriate autoconfery. + */ + +#if HAVE_CMAKE_H +#include "cmake.h" +#endif + +#if HAVE_SO_PEERCRED +#define _GNU_SOURCE +#include +#endif + +#include + +#include "putty.h" + +bool so_peercred(int fd, int *pid, int *uid, int *gid) +{ +#if HAVE_SO_PEERCRED + struct ucred cr; + socklen_t crlen = sizeof(cr); + if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cr, &crlen) == 0) { + *pid = cr.pid; + *uid = cr.uid; + *gid = cr.gid; + return true; + } +#endif + return false; +} diff --git a/unix/plink.c b/unix/plink.c new file mode 100644 index 00000000..3e2a9b6b --- /dev/null +++ b/unix/plink.c @@ -0,0 +1,964 @@ +/* + * PLink - a command-line (stdin/stdout) variant of PuTTY. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "putty.h" +#include "ssh.h" +#include "storage.h" +#include "tree234.h" + +#define MAX_STDIN_BACKLOG 4096 + +static LogContext *logctx; + +static struct termios orig_termios; + +void cmdline_error(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + console_print_error_msg_fmt_v("plink", fmt, ap); + va_end(ap); + exit(1); +} + +static bool local_tty = false; /* do we have a local tty? */ + +static Backend *backend; +static Conf *conf; + +/* + * Default settings that are specific to Unix plink. + */ +char *platform_default_s(const char *name) +{ + if (!strcmp(name, "TermType")) + return dupstr(getenv("TERM")); + if (!strcmp(name, "SerialLine")) + return dupstr("/dev/ttyS0"); + return NULL; +} + +bool platform_default_b(const char *name, bool def) +{ + return def; +} + +int platform_default_i(const char *name, int def) +{ + return def; +} + +FontSpec *platform_default_fontspec(const char *name) +{ + return fontspec_new(""); +} + +Filename *platform_default_filename(const char *name) +{ + if (!strcmp(name, "LogFileName")) + return filename_from_str("putty.log"); + else + return filename_from_str(""); +} + +char *x_get_default(const char *key) +{ + return NULL; /* this is a stub */ +} +static void plink_echoedit_update(Seat *seat, bool echo, bool 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 the echoedit_update() method, 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 dupstr(""); +#endif + return dupprintf("^<%d>", c); +} + +static char *plink_get_ttymode(Seat *seat, 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) { \ + bool 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 +#if defined(IUTF8) + GET_BOOL("IUTF8", IUTF8, c_iflag, ); +#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); +} + +static bufchain stdout_data, stderr_data; +static bufchain_sink stdout_bcs, stderr_bcs; +static StripCtrlChars *stdout_scc, *stderr_scc; +static BinarySink *stdout_bs, *stderr_bs; + +static enum { EOF_NO, EOF_PENDING, EOF_SENT } outgoingeof; + +size_t try_output(bool is_stderr) +{ + bufchain *chain = (is_stderr ? &stderr_data : &stdout_data); + int fd = (is_stderr ? STDERR_FILENO : STDOUT_FILENO); + ssize_t ret; + + if (bufchain_size(chain) > 0) { + bool prev_nonblock = nonblock(fd); + ptrlen senddata; + do { + senddata = bufchain_prefix(chain); + ret = write(fd, senddata.ptr, senddata.len); + if (ret > 0) + bufchain_consume(chain, ret); + } while (ret == senddata.len && bufchain_size(chain) != 0); + if (!prev_nonblock) + no_nonblock(fd); + if (ret < 0 && errno != EAGAIN) { + perror(is_stderr ? "stderr: write" : "stdout: write"); + exit(1); + } + } + if (outgoingeof == EOF_PENDING && bufchain_size(&stdout_data) == 0) { + close(STDOUT_FILENO); + outgoingeof = EOF_SENT; + } + return bufchain_size(&stdout_data) + bufchain_size(&stderr_data); +} + +static size_t plink_output( + Seat *seat, bool is_stderr, const void *data, size_t len) +{ + assert(is_stderr || outgoingeof == EOF_NO); + + BinarySink *bs = is_stderr ? stderr_bs : stdout_bs; + put_data(bs, data, len); + + return try_output(is_stderr); +} + +static bool plink_eof(Seat *seat) +{ + assert(outgoingeof == EOF_NO); + outgoingeof = EOF_PENDING; + try_output(false); + return false; /* do not respond to incoming EOF with outgoing */ +} + +static int plink_get_userpass_input(Seat *seat, prompts_t *p, bufchain *input) +{ + int ret; + ret = cmdline_get_passwd_input(p); + if (ret == -1) + ret = console_get_userpass_input(p); + return ret; +} + +static bool plink_seat_interactive(Seat *seat) +{ + return (!*conf_get_str(conf, CONF_remote_cmd) && + !*conf_get_str(conf, CONF_remote_cmd2) && + !*conf_get_str(conf, CONF_ssh_nc_host)); +} + +static const SeatVtable plink_seat_vt = { + .output = plink_output, + .eof = plink_eof, + .get_userpass_input = plink_get_userpass_input, + .notify_remote_exit = nullseat_notify_remote_exit, + .connection_fatal = console_connection_fatal, + .update_specials_menu = nullseat_update_specials_menu, + .get_ttymode = plink_get_ttymode, + .set_busy_status = nullseat_set_busy_status, + .verify_ssh_host_key = console_verify_ssh_host_key, + .confirm_weak_crypto_primitive = console_confirm_weak_crypto_primitive, + .confirm_weak_cached_hostkey = console_confirm_weak_cached_hostkey, + .is_utf8 = nullseat_is_never_utf8, + .echoedit_update = plink_echoedit_update, + .get_x_display = nullseat_get_x_display, + .get_windowid = nullseat_get_windowid, + .get_window_pixel_size = nullseat_get_window_pixel_size, + .stripctrl_new = console_stripctrl_new, + .set_trust_status = console_set_trust_status, + .verbose = cmdline_seat_verbose, + .interactive = plink_seat_interactive, + .get_cursor_position = nullseat_get_cursor_position, +}; +static Seat plink_seat[1] = {{ &plink_seat_vt }}; + +/* + * 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; + backend_send(backend, p, q - p); + p = q; + } + break; + case FF: + if (*p == '\xff') { + backend_send(backend, p, 1); + p++; + state = NORMAL; + } else if (*p == '\0') { + p++; + state = FF00; + } else abort(); + break; + case FF00: + if (*p == '\0') { + backend_special(backend, SS_BRK, 0); + } 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; + backend_send(backend, p, 1); + } + } else { + /* INPCK not set. Assume we got a parity error. */ + backend_send(backend, p, 1); + } + } + p++; + state = NORMAL; + } + } +} + +static int signalpipe[2]; + +void sigwinch(int signum) +{ + if (write(signalpipe[1], "x", 1) <= 0) + /* not much we can do about it */; +} + +/* + * Short description of parameters. + */ +static void usage(void) +{ + printf("Plink: 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(" -ssh-connection\n"); + printf(" force use of the bare ssh-connection 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(" -proxycmd command\n"); + printf(" use 'command' as local proxy\n"); + printf(" -sercfg configuration-string (e.g. 19200,8,n,1,X)\n"); + printf(" Specify the serial configuration (serial only)\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 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 user authentication\n"); + printf(" -noagent disable use of Pageant\n"); + printf(" -agent enable use of Pageant\n"); + printf(" -noshare disable use of connection sharing\n"); + printf(" -share enable use of connection sharing\n"); + printf(" -hostkey keyid\n"); + printf(" manually specify a host key (may be repeated)\n"); + printf(" -sanitise-stderr, -sanitise-stdout, " + "-no-sanitise-stderr, -no-sanitise-stdout\n"); + printf(" do/don't strip control chars from standard " + "output/error\n"); + printf(" -no-antispoof omit anti-spoofing prompt after " + "authentication\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(" -sshlog file\n"); + printf(" -sshrawlog file\n"); + printf(" log protocol details to a file\n"); + printf(" -logoverwrite\n"); + printf(" -logappend\n"); + printf(" control what happens when a log file already exists\n"); + printf(" -shareexists\n"); + printf(" test whether a connection-sharing upstream exists\n"); + exit(1); +} + +static void version(void) +{ + char *buildinfo_text = buildinfo("\n"); + printf("plink: %s\n%s\n", ver, buildinfo_text); + sfree(buildinfo_text); + exit(0); +} + +void frontend_net_error_pending(void) {} + +const bool share_can_be_downstream = true; +const bool share_can_be_upstream = true; + +const bool buildinfo_gtk_relevant = false; + +const unsigned cmdline_tooltype = + TOOLTYPE_HOST_ARG | + TOOLTYPE_HOST_ARG_CAN_BE_SESSION | + TOOLTYPE_HOST_ARG_PROTOCOL_PREFIX | + TOOLTYPE_HOST_ARG_FROM_LAUNCHABLE_LOAD; + +static bool seen_stdin_eof = false; + +static bool plink_pw_setup(void *vctx, pollwrapper *pw) +{ + pollwrap_add_fd_rwx(pw, signalpipe[0], SELECT_R); + + if (!seen_stdin_eof && + backend_connected(backend) && + backend_sendok(backend) && + backend_sendbuffer(backend) < MAX_STDIN_BACKLOG) { + /* If we're OK to send, then try to read from stdin. */ + pollwrap_add_fd_rwx(pw, STDIN_FILENO, SELECT_R); + } + + if (bufchain_size(&stdout_data) > 0) { + /* If we have data for stdout, try to write to stdout. */ + pollwrap_add_fd_rwx(pw, STDOUT_FILENO, SELECT_W); + } + + if (bufchain_size(&stderr_data) > 0) { + /* If we have data for stderr, try to write to stderr. */ + pollwrap_add_fd_rwx(pw, STDERR_FILENO, SELECT_W); + } + + return true; +} + +static void plink_pw_check(void *vctx, pollwrapper *pw) +{ + if (pollwrap_check_fd_rwx(pw, signalpipe[0], SELECT_R)) { + char c[1]; + struct winsize size; + if (read(signalpipe[0], c, 1) <= 0) + /* ignore error */; + /* ignore its value; it'll be `x' */ + if (ioctl(STDIN_FILENO, TIOCGWINSZ, (void *)&size) >= 0) + backend_size(backend, size.ws_col, size.ws_row); + } + + if (pollwrap_check_fd_rwx(pw, STDIN_FILENO, SELECT_R)) { + char buf[4096]; + int ret; + + if (backend_connected(backend)) { + ret = read(STDIN_FILENO, buf, sizeof(buf)); + noise_ultralight(NOISE_SOURCE_IOLEN, ret); + if (ret < 0) { + perror("stdin: read"); + exit(1); + } else if (ret == 0) { + backend_special(backend, SS_EOF, 0); + seen_stdin_eof = true; + } else { + if (local_tty) + from_tty(buf, ret); + else + backend_send(backend, buf, ret); + } + } + } + + if (pollwrap_check_fd_rwx(pw, STDOUT_FILENO, SELECT_W)) { + backend_unthrottle(backend, try_output(false)); + } + + if (pollwrap_check_fd_rwx(pw, STDERR_FILENO, SELECT_W)) { + backend_unthrottle(backend, try_output(true)); + } +} + +static bool plink_continue(void *vctx, bool found_any_fd, + bool ran_any_callback) +{ + if (!backend_connected(backend) && + bufchain_size(&stdout_data) == 0 && bufchain_size(&stderr_data) == 0) + return false; /* terminate main loop */ + return true; +} + +int main(int argc, char **argv) +{ + int exitcode; + bool errors; + enum TriState sanitise_stdout = AUTO, sanitise_stderr = AUTO; + bool use_subsystem = false; + bool just_test_share_exists = false; + struct winsize size; + const struct BackendVtable *backvt; + + /* + * Initialise port and protocol to sensible defaults. (These + * will be overridden by more or less anything.) + */ + settings_set_default_protocol(PROT_SSH); + settings_set_default_port(22); + + bufchain_init(&stdout_data); + bufchain_init(&stderr_data); + bufchain_sink_init(&stdout_bcs, &stdout_data); + bufchain_sink_init(&stderr_bcs, &stderr_data); + stdout_bs = BinarySink_UPCAST(&stdout_bcs); + stderr_bs = BinarySink_UPCAST(&stderr_bcs); + outgoingeof = EOF_NO; + + stderr_tty_init(); + /* + * Process the command line. + */ + conf = conf_new(); + do_defaults(NULL, conf); + settings_set_default_protocol(conf_get_int(conf, CONF_protocol)); + settings_set_default_port(conf_get_int(conf, CONF_port)); + errors = false; + { + /* + * Override the default protocol if PLINK_PROTOCOL is set. + */ + char *p = getenv("PLINK_PROTOCOL"); + if (p) { + const struct BackendVtable *vt = backend_vt_from_name(p); + if (vt) { + settings_set_default_protocol(vt->protocol); + settings_set_default_port(vt->default_port); + conf_set_int(conf, CONF_protocol, vt->protocol); + conf_set_int(conf, CONF_port, vt->default_port); + } + } + } + while (--argc) { + char *p = *++argv; + int ret = cmdline_process_param(p, (argc > 1 ? argv[1] : NULL), + 1, conf); + if (ret == -2) { + fprintf(stderr, + "plink: option \"%s\" requires an argument\n", p); + errors = true; + } else if (ret == 2) { + --argc, ++argv; + } else if (ret == 1) { + continue; + } else if (!strcmp(p, "-batch")) { + console_batch_mode = true; + } else if (!strcmp(p, "-s")) { + /* Save status to write to conf later. */ + use_subsystem = true; + } else if (!strcmp(p, "-V") || !strcmp(p, "--version")) { + version(); + } else if (!strcmp(p, "--help")) { + usage(); + exit(0); + } else if (!strcmp(p, "-pgpfp")) { + pgp_fingerprints(); + exit(1); + } else if (!strcmp(p, "-o")) { + if (argc <= 1) { + fprintf(stderr, + "plink: option \"-o\" requires an argument\n"); + errors = true; + } else { + --argc; + /* Explicitly pass "plink" in place of appname for + * error reporting purposes. appname will have been + * set by be_foo.c to something more generic, probably + * "PuTTY". */ + provide_xrm_string(*++argv, "plink"); + } + } else if (!strcmp(p, "-shareexists")) { + just_test_share_exists = true; + } else if (!strcmp(p, "-fuzznet")) { + conf_set_int(conf, CONF_proxy_type, PROXY_FUZZ); + conf_set_str(conf, CONF_proxy_telnet_command, "%host"); + } else if (!strcmp(p, "-sanitise-stdout") || + !strcmp(p, "-sanitize-stdout")) { + sanitise_stdout = FORCE_ON; + } else if (!strcmp(p, "-no-sanitise-stdout") || + !strcmp(p, "-no-sanitize-stdout")) { + sanitise_stdout = FORCE_OFF; + } else if (!strcmp(p, "-sanitise-stderr") || + !strcmp(p, "-sanitize-stderr")) { + sanitise_stderr = FORCE_ON; + } else if (!strcmp(p, "-no-sanitise-stderr") || + !strcmp(p, "-no-sanitize-stderr")) { + sanitise_stderr = FORCE_OFF; + } else if (!strcmp(p, "-no-antispoof")) { + console_antispoof_prompt = false; + } else if (*p != '-') { + strbuf *cmdbuf = strbuf_new(); + + while (argc > 0) { + if (cmdbuf->len > 0) + put_byte(cmdbuf, ' '); /* add space separator */ + put_datapl(cmdbuf, ptrlen_from_asciz(p)); + if (--argc > 0) + p = *++argv; + } + + conf_set_str(conf, CONF_remote_cmd, cmdbuf->s); + conf_set_str(conf, CONF_remote_cmd2, ""); + conf_set_bool(conf, CONF_nopty, true); /* command => no tty */ + + strbuf_free(cmdbuf); + break; /* done with cmdline */ + } else { + fprintf(stderr, "plink: unknown option \"%s\"\n", p); + errors = true; + } + } + + if (errors) + return 1; + + if (!cmdline_host_ok(conf)) { + usage(); + } + + prepare_session(conf); + + /* + * Perform command-line overrides on session configuration. + */ + cmdline_run_saved(conf); + + /* + * If we have no better ideas for the remote username, use the local + * one, as 'ssh' does. + */ + if (conf_get_str(conf, CONF_username)[0] == '\0') { + char *user = get_username(); + if (user) { + conf_set_str(conf, CONF_username, user); + sfree(user); + } + } + + /* + * Apply subsystem status. + */ + if (use_subsystem) + conf_set_bool(conf, CONF_ssh_subsys, true); + + /* + * Select protocol. This is farmed out into a table in a + * separate file to enable an ssh-free variant. + */ + backvt = backend_vt_from_proto(conf_get_int(conf, CONF_protocol)); + if (!backvt) { + fprintf(stderr, + "Internal fault: Unsupported protocol found\n"); + return 1; + } + + if (backvt->flags & BACKEND_NEEDS_TERMINAL) { + fprintf(stderr, + "Plink doesn't support %s, which needs terminal emulation\n", + backvt->displayname); + return 1; + } + + /* + * Block SIGPIPE, so that we'll get EPIPE individually on + * particular network connections that go wrong. + */ + putty_signal(SIGPIPE, SIG_IGN); + + /* + * Set up the pipe we'll use to tell us about SIGWINCH. + */ + if (pipe(signalpipe) < 0) { + perror("pipe"); + exit(1); + } + /* We don't want the signal handler to block if the pipe's full. */ + nonblock(signalpipe[0]); + nonblock(signalpipe[1]); + cloexec(signalpipe[0]); + cloexec(signalpipe[1]); + putty_signal(SIGWINCH, sigwinch); + + /* + * Now that we've got the SIGWINCH handler installed, try to find + * out the initial terminal size. + */ + if (ioctl(STDIN_FILENO, TIOCGWINSZ, &size) >= 0) { + conf_set_int(conf, CONF_width, size.ws_col); + conf_set_int(conf, CONF_height, size.ws_row); + } + + /* + * Decide whether to sanitise control sequences out of standard + * output and standard error. + * + * If we weren't given a command-line override, we do this if (a) + * the fd in question is pointing at a terminal, and (b) we aren't + * trying to allocate a terminal as part of the session. + * + * (Rationale: the risk of control sequences is that they cause + * confusion when sent to a local terminal, so if there isn't one, + * no problem. Also, if we allocate a remote terminal, then we + * sent a terminal type, i.e. we told it what kind of escape + * sequences we _like_, i.e. we were expecting to receive some.) + */ + if (sanitise_stdout == FORCE_ON || + (sanitise_stdout == AUTO && isatty(STDOUT_FILENO) && + conf_get_bool(conf, CONF_nopty))) { + stdout_scc = stripctrl_new(stdout_bs, true, L'\0'); + stdout_bs = BinarySink_UPCAST(stdout_scc); + } + if (sanitise_stderr == FORCE_ON || + (sanitise_stderr == AUTO && isatty(STDERR_FILENO) && + conf_get_bool(conf, CONF_nopty))) { + stderr_scc = stripctrl_new(stderr_bs, true, L'\0'); + stderr_bs = BinarySink_UPCAST(stderr_scc); + } + + sk_init(); + uxsel_init(); + + /* + * 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 (conf_get_int(conf, CONF_protocol) == PROT_SSH && + !conf_get_bool(conf, CONF_x11_forward) && + !conf_get_bool(conf, CONF_agentfwd) && + !conf_get_str_nthstrkey(conf, CONF_portfwd, 0)) + conf_set_bool(conf, CONF_ssh_simple, true); + + if (just_test_share_exists) { + if (!backvt->test_for_upstream) { + fprintf(stderr, "Connection sharing not supported for this " + "connection type (%s)'\n", backvt->displayname); + return 1; + } + if (backvt->test_for_upstream(conf_get_str(conf, CONF_host), + conf_get_int(conf, CONF_port), conf)) + return 0; + else + return 1; + } + + /* + * Start up the connection. + */ + logctx = log_init(console_cli_logpolicy, conf); + { + char *error, *realhost; + /* nodelay is only useful if stdin is a terminal device */ + bool nodelay = conf_get_bool(conf, CONF_tcp_nodelay) && isatty(0); + + /* This is a good place for a fuzzer to fork us. */ +#ifdef __AFL_HAVE_MANUAL_CONTROL + __AFL_INIT(); +#endif + + error = backend_init(backvt, plink_seat, &backend, logctx, conf, + conf_get_str(conf, CONF_host), + conf_get_int(conf, CONF_port), + &realhost, nodelay, + conf_get_bool(conf, CONF_tcp_keepalives)); + if (error) { + fprintf(stderr, "Unable to open connection:\n%s\n", error); + sfree(error); + return 1; + } + ldisc_create(conf, NULL, backend, plink_seat); + sfree(realhost); + } + + /* + * 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); + seat_echoedit_update(plink_seat, 1, 1); + + cli_main_loop(plink_pw_setup, plink_pw_check, plink_continue, NULL); + + exitcode = backend_exitcode(backend); + 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 */ +} diff --git a/unix/printing.c b/unix/printing.c new file mode 100644 index 00000000..3de6d21b --- /dev/null +++ b/unix/printing.c @@ -0,0 +1,58 @@ +/* + * Printing interface for PuTTY. + */ + +#include +#include +#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, const void *data, size_t 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) { } diff --git a/unix/psocks.c b/unix/psocks.c new file mode 100644 index 00000000..748790b8 --- /dev/null +++ b/unix/psocks.c @@ -0,0 +1,176 @@ +/* + * Main program for Unix psocks. + */ + +#include +#include + +#include +#include +#include +#include +#include + +#include "putty.h" +#include "ssh.h" +#include "psocks.h" + +typedef struct PsocksDataSinkPopen { + stdio_sink sink[2]; + PsocksDataSink pds; +} PsocksDataSinkPopen; + +static void popen_free(PsocksDataSink *pds) +{ + PsocksDataSinkPopen *pdsp = container_of(pds, PsocksDataSinkPopen, pds); + for (size_t i = 0; i < 2; i++) + pclose(pdsp->sink[i].fp); + sfree(pdsp); +} + +static PsocksDataSink *open_pipes( + const char *cmd, const char *const *direction_args, + const char *index_arg, char **err) +{ + FILE *fp[2]; + char *errmsg = NULL; + + for (size_t i = 0; i < 2; i++) { + /* No escaping needed: the provided command is already + * shell-quoted, and our extra arguments are simple */ + char *command = dupprintf("%s %s %s", cmd, + direction_args[i], index_arg); + + fp[i] = popen(command, "w"); + sfree(command); + + if (!fp[i]) { + if (!errmsg) + errmsg = dupprintf("%s", strerror(errno)); + } + } + + if (errmsg) { + for (size_t i = 0; i < 2; i++) + if (fp[i]) + pclose(fp[i]); + *err = errmsg; + return NULL; + } + + PsocksDataSinkPopen *pdsp = snew(PsocksDataSinkPopen); + + for (size_t i = 0; i < 2; i++) { + setvbuf(fp[i], NULL, _IONBF, 0); + stdio_sink_init(&pdsp->sink[i], fp[i]); + pdsp->pds.s[i] = BinarySink_UPCAST(&pdsp->sink[i]); + } + + pdsp->pds.free = popen_free; + + return &pdsp->pds; +} + +static int signalpipe[2] = { -1, -1 }; +static void sigchld(int signum) +{ + if (write(signalpipe[1], "x", 1) <= 0) + /* not much we can do about it */; +} + +static pid_t subcommand_pid = -1; + +static bool still_running = true; + +static void start_subcommand(strbuf *args) +{ + pid_t pid; + + /* + * Set up the pipe we'll use to tell us about SIGCHLD. + */ + if (pipe(signalpipe) < 0) { + perror("pipe"); + exit(1); + } + putty_signal(SIGCHLD, sigchld); + + /* + * Make an array of argument pointers that execvp will like. + */ + size_t nargs = 0; + for (size_t i = 0; i < args->len; i++) + if (args->s[i] == '\0') + nargs++; + + char **exec_args = snewn(nargs + 1, char *); + char *p = args->s; + for (size_t a = 0; a < nargs; a++) { + exec_args[a] = p; + size_t len = strlen(p); + assert(len < args->len - (p - args->s)); + p += 1 + len; + } + exec_args[nargs] = NULL; + + pid = fork(); + if (pid < 0) { + perror("fork"); + exit(1); + } else if (pid == 0) { + execvp(exec_args[0], exec_args); + perror("exec"); + _exit(127); + } else { + subcommand_pid = pid; + sfree(exec_args); + } +} + +static const PsocksPlatform platform = { + open_pipes, + start_subcommand, +}; + +static bool psocks_pw_setup(void *ctx, pollwrapper *pw) +{ + if (signalpipe[0] >= 0) + pollwrap_add_fd_rwx(pw, signalpipe[0], SELECT_R); + return true; +} + +static void psocks_pw_check(void *ctx, pollwrapper *pw) +{ + if (signalpipe[0] >= 0 && + pollwrap_check_fd_rwx(pw, signalpipe[0], SELECT_R)) { + while (true) { + int status; + pid_t pid = waitpid(-1, &status, WNOHANG); + if (pid <= 0) + break; + if (pid == subcommand_pid) + still_running = false; + } + } +} + +static bool psocks_continue(void *ctx, bool found_any_fd, + bool ran_any_callback) +{ + return still_running; +} + +typedef bool (*cliloop_continue_t)(void *ctx, bool found_any_fd, + bool ran_any_callback); + +int main(int argc, char **argv) +{ + psocks_state *ps = psocks_new(&platform); + psocks_cmdline(ps, argc, argv); + + sk_init(); + uxsel_init(); + psocks_start(ps); + + cli_main_loop(psocks_pw_setup, psocks_pw_check, psocks_continue, NULL); +} diff --git a/unix/psusan.c b/unix/psusan.c new file mode 100644 index 00000000..a9312c40 --- /dev/null +++ b/unix/psusan.c @@ -0,0 +1,422 @@ +/* + * 'psusan': Pseudo Ssh for Untappable, Separately Authenticated Networks + * + * This is a standalone application that speaks on its standard I/O + * (or a listening Unix-domain socket) the server end of the bare + * ssh-connection protocol used by PuTTY's connection sharing. + * + * The idea of this tool is that you can use it to communicate across + * any 8-bit-clean data channel between two inconveniently separated + * domains, provided the channel is already (as the name suggests) + * adequately secured against eavesdropping and modification and + * already authenticated as the right user. + * + * If you're sitting at one end of such a channel and want to type + * commands into the other end, the most obvious thing to do is to run + * a terminal session directly over it. But if you run psusan at one + * end, and a PuTTY (or compatible) client at the other end, then you + * not only get a single terminal session: you get all the other SSH + * amenities, like the ability to spawn extra terminal sessions, + * forward ports or X11 connections, even forward an SSH agent. + * + * There are a surprising number of channels of that kind; see the man + * page for some examples. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "putty.h" +#include "mpint.h" +#include "ssh.h" +#include "ssh/server.h" + +const char *const appname = "psusan"; + +void modalfatalbox(const 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 nonfatal(const char *p, ...) +{ + va_list ap; + fprintf(stderr, "ERROR: "); + va_start(ap, p); + vfprintf(stderr, p, ap); + va_end(ap); + fputc('\n', stderr); +} + +char *platform_default_s(const char *name) +{ + return NULL; +} + +bool platform_default_b(const char *name, bool def) +{ + return def; +} + +int platform_default_i(const char *name, int def) +{ + return def; +} + +FontSpec *platform_default_fontspec(const char *name) +{ + return fontspec_new(""); +} + +Filename *platform_default_filename(const char *name) +{ + return filename_from_str(""); +} + +char *x_get_default(const char *key) +{ + return NULL; /* this is a stub */ +} + +void old_keyfile_warning(void) { } + +void timer_change_notify(unsigned long next) +{ +} + +char *platform_get_x_display(void) { return NULL; } + +void make_unix_sftp_filehandle_key(void *vdata, size_t size) +{ + /* psusan runs without a random number generator, so we can't make + * this up by random_read. Fortunately, psusan is also + * non-adversarial, so it's safe to generate this trivially. */ + unsigned char *data = (unsigned char *)vdata; + for (size_t i = 0; i < size; i++) + data[i] = (unsigned)rand() / ((unsigned)RAND_MAX / 256); +} + +static bool verbose; + +struct server_instance { + unsigned id; + LogPolicy logpolicy; +}; + +static void log_to_stderr(unsigned id, const char *msg) +{ + if (!verbose) + return; + if (id != (unsigned)-1) + fprintf(stderr, "#%u: ", id); + fputs(msg, stderr); + fputc('\n', stderr); + fflush(stderr); +} + +static void server_eventlog(LogPolicy *lp, const char *event) +{ + struct server_instance *inst = container_of( + lp, struct server_instance, logpolicy); + if (verbose) + log_to_stderr(inst->id, event); +} + +static void server_logging_error(LogPolicy *lp, const char *event) +{ + struct server_instance *inst = container_of( + lp, struct server_instance, logpolicy); + log_to_stderr(inst->id, event); /* unconditional */ +} + +static int server_askappend( + LogPolicy *lp, Filename *filename, + void (*callback)(void *ctx, int result), void *ctx) +{ + return 2; /* always overwrite (FIXME: could make this a cmdline option) */ +} + +static const LogPolicyVtable server_logpolicy_vt = { + .eventlog = server_eventlog, + .askappend = server_askappend, + .logging_error = server_logging_error, + .verbose = null_lp_verbose_no, +}; + +static void show_help(FILE *fp) +{ + fputs("usage: psusan [options]\n" + "options: --listen SOCKETPATH listen for connections on a Unix-domain socket\n" + " --listen-once (with --listen) stop after one connection\n" + " --verbose print log messages to standard error\n" + " --sessiondir DIR cwd for session subprocess (default $HOME)\n" + " --sshlog FILE write ssh-connection packet log to FILE\n" + " --sshrawlog FILE write packets and raw data log to FILE\n" + "also: psusan --help show this text\n" + " psusan --version show version information\n", fp); +} + +static void show_version_and_exit(void) +{ + char *buildinfo_text = buildinfo("\n"); + printf("%s: %s\n%s\n", appname, ver, buildinfo_text); + sfree(buildinfo_text); + exit(0); +} + +const bool buildinfo_gtk_relevant = false; + +static bool listening = false, listen_once = false; +static bool finished = false; +void server_instance_terminated(LogPolicy *lp) +{ + struct server_instance *inst = container_of( + lp, struct server_instance, logpolicy); + + if (listening && !listen_once) { + log_to_stderr(inst->id, "connection terminated"); + } else { + finished = true; + } + + sfree(inst); +} + +bool psusan_continue(void *ctx, bool fd, bool cb) +{ + return !finished; +} + +static bool longoptarg(const char *arg, const char *expected, + const char **val, int *argcp, char ***argvp) +{ + int len = strlen(expected); + if (memcmp(arg, expected, len)) + return false; + if (arg[len] == '=') { + *val = arg + len + 1; + return true; + } else if (arg[len] == '\0') { + if (--*argcp > 0) { + *val = *++*argvp; + return true; + } else { + fprintf(stderr, "%s: option %s expects an argument\n", + appname, expected); + exit(1); + } + } + return false; +} + +static bool longoptnoarg(const char *arg, const char *expected) +{ + int len = strlen(expected); + if (memcmp(arg, expected, len)) + return false; + if (arg[len] == '=') { + fprintf(stderr, "%s: option %s expects no argument\n", + appname, expected); + exit(1); + } else if (arg[len] == '\0') { + return true; + } + return false; +} + +struct server_config { + Conf *conf; + const SshServerConfig *ssc; + + unsigned next_id; + + Socket *listening_socket; + Plug listening_plug; +}; + +static Plug *server_conn_plug( + struct server_config *cfg, struct server_instance **inst_out) +{ + struct server_instance *inst = snew(struct server_instance); + + memset(inst, 0, sizeof(*inst)); + + inst->id = cfg->next_id++; + inst->logpolicy.vt = &server_logpolicy_vt; + + if (inst_out) + *inst_out = inst; + + return ssh_server_plug( + cfg->conf, cfg->ssc, NULL, 0, NULL, NULL, + &inst->logpolicy, &unix_live_sftpserver_vt); +} + +static void server_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, + const char *error_msg, int error_code) +{ + log_to_stderr(-1, error_msg); +} + +static void server_closing(Plug *plug, const char *error_msg, int error_code, + bool calling_back) +{ + log_to_stderr(-1, error_msg); +} + +static int server_accepting(Plug *p, accept_fn_t constructor, accept_ctx_t ctx) +{ + struct server_config *cfg = container_of( + p, struct server_config, listening_plug); + Socket *s; + const char *err; + + struct server_instance *inst; + + if (listen_once) { + if (!cfg->listening_socket) /* in case of rapid double-accept */ + return 1; + sk_close(cfg->listening_socket); + cfg->listening_socket = NULL; + } + + Plug *plug = server_conn_plug(cfg, &inst); + s = constructor(ctx, plug); + if ((err = sk_socket_error(s)) != NULL) + return 1; + + SocketPeerInfo *pi = sk_peer_info(s); + + char *msg = dupprintf("new connection from %s", pi->log_text); + log_to_stderr(inst->id, msg); + sfree(msg); + sk_free_peer_info(pi); + + sk_set_frozen(s, false); + ssh_server_start(plug, s); + return 0; +} + +static const PlugVtable server_plugvt = { + .log = server_log, + .closing = server_closing, + .accepting = server_accepting, +}; + +unsigned auth_methods(AuthPolicy *ap) +{ return 0; } +bool auth_none(AuthPolicy *ap, ptrlen username) +{ return false; } +int auth_password(AuthPolicy *ap, ptrlen username, ptrlen password, + ptrlen *new_password_opt) +{ return 0; } +bool auth_publickey(AuthPolicy *ap, ptrlen username, ptrlen public_blob) +{ return false; } +RSAKey *auth_publickey_ssh1( + AuthPolicy *ap, ptrlen username, mp_int *rsa_modulus) +{ return NULL; } +AuthKbdInt *auth_kbdint_prompts(AuthPolicy *ap, ptrlen username) +{ return NULL; } +int auth_kbdint_responses(AuthPolicy *ap, const ptrlen *responses) +{ return -1; } +char *auth_ssh1int_challenge(AuthPolicy *ap, unsigned method, ptrlen username) +{ return NULL; } +bool auth_ssh1int_response(AuthPolicy *ap, ptrlen response) +{ return false; } +bool auth_successful(AuthPolicy *ap, ptrlen username, unsigned method) +{ return false; } + +int main(int argc, char **argv) +{ + const char *listen_socket = NULL; + + SshServerConfig ssc; + + Conf *conf = make_ssh_server_conf(); + + memset(&ssc, 0, sizeof(ssc)); + + ssc.application_name = "PSUSAN"; + ssc.session_starting_dir = getenv("HOME"); + ssc.bare_connection = true; + + while (--argc > 0) { + const char *arg = *++argv; + const char *val; + + if (longoptnoarg(arg, "--help")) { + show_help(stdout); + exit(0); + } else if (longoptnoarg(arg, "--version")) { + show_version_and_exit(); + } else if (longoptnoarg(arg, "--verbose") || !strcmp(arg, "-v")) { + verbose = true; + } else if (longoptarg(arg, "--sessiondir", &val, &argc, &argv)) { + ssc.session_starting_dir = val; + } else if (longoptarg(arg, "--sshlog", &val, &argc, &argv) || + longoptarg(arg, "-sshlog", &val, &argc, &argv)) { + Filename *logfile = filename_from_str(val); + conf_set_filename(conf, CONF_logfilename, logfile); + filename_free(logfile); + conf_set_int(conf, CONF_logtype, LGTYP_PACKETS); + conf_set_int(conf, CONF_logxfovr, LGXF_OVR); + } else if (longoptarg(arg, "--sshrawlog", &val, &argc, &argv) || + longoptarg(arg, "-sshrawlog", &val, &argc, &argv)) { + Filename *logfile = filename_from_str(val); + conf_set_filename(conf, CONF_logfilename, logfile); + filename_free(logfile); + conf_set_int(conf, CONF_logtype, LGTYP_SSHRAW); + conf_set_int(conf, CONF_logxfovr, LGXF_OVR); + } else if (longoptarg(arg, "--listen", &val, &argc, &argv)) { + listen_socket = val; + } else if (!strcmp(arg, "--listen-once")) { + listen_once = true; + } else { + fprintf(stderr, "%s: unrecognised option '%s'\n", appname, arg); + exit(1); + } + } + + sk_init(); + uxsel_init(); + + struct server_config scfg; + scfg.conf = conf; + scfg.ssc = &ssc; + scfg.next_id = 0; + + if (listen_socket) { + listening = true; + scfg.listening_plug.vt = &server_plugvt; + SockAddr *addr = unix_sock_addr(listen_socket); + scfg.listening_socket = new_unix_listener(addr, &scfg.listening_plug); + char *msg = dupprintf("listening on Unix socket %s", listen_socket); + log_to_stderr(-1, msg); + sfree(msg); + } else { + struct server_instance *inst; + Plug *plug = server_conn_plug(&scfg, &inst); + ssh_server_start(plug, make_fd_socket(0, 1, -1, plug)); + log_to_stderr(inst->id, "running directly on stdio"); + } + + cli_main_loop(cliloop_no_pw_setup, cliloop_no_pw_check, + psusan_continue, NULL); + + return 0; +} diff --git a/unix/pterm-config-xpm.c b/unix/pterm-config-xpm.c new file mode 100644 index 00000000..92835c15 --- /dev/null +++ b/unix/pterm-config-xpm.c @@ -0,0 +1,150 @@ +/* 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; diff --git a/unix/pterm-xpm.c b/unix/pterm-xpm.c new file mode 100644 index 00000000..aea5e4e2 --- /dev/null +++ b/unix/pterm-xpm.c @@ -0,0 +1,143 @@ +/* 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; diff --git a/unix/pterm.c b/unix/pterm.c new file mode 100644 index 00000000..b18e3442 --- /dev/null +++ b/unix/pterm.c @@ -0,0 +1,50 @@ +/* + * pterm main program. + */ + +#include +#include + +#include "putty.h" + +const char *const appname = "pterm"; +const bool use_event_log = false; /* pterm doesn't need it */ +const bool new_session = false, saved_sessions = false; /* or these */ +const bool dup_check_launchable = false; /* no need to check host name + * in conf */ +const bool use_pty_argv = true; + +const unsigned cmdline_tooltype = TOOLTYPE_NONNETWORK; + +/* gtkwin.c will call this, and in pterm it's not needed */ +void noise_ultralight(NoiseSourceId id, unsigned long data) { } + +const struct BackendVtable *select_backend(Conf *conf) +{ + return &pty_backend; +} + +void initial_config_box(Conf *conf, post_dialog_fn_t after, void *afterctx) +{ + /* + * 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. So we do that and then just immediately call the + * post-dialog function with a positive result. + */ + conf_set_int(conf, CONF_protocol, -1); + after(afterctx, 1); +} + +void cleanup_exit(int code) +{ + exit(code); +} + +void setup(bool single) +{ + settings_set_default_protocol(-1); + + if (single) + pty_pre_init(); +} diff --git a/unix/pty.c b/unix/pty.c new file mode 100644 index 00000000..30e48e50 --- /dev/null +++ b/unix/pty.c @@ -0,0 +1,1602 @@ +/* + * Pseudo-tty backend for pterm. + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "putty.h" +#include "ssh.h" +#include "ssh/server.h" /* to check the prototypes of server-needed things */ +#include "tree234.h" + +#ifndef OMIT_UTMP +#include +#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 + +typedef struct Pty 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 */ + +typedef struct PtyFd { + int fd; + Pty *pty; +} PtyFd; + +struct Pty { + Conf *conf; + + int master_fd, slave_fd; + int pipefds[6]; + PtyFd fds[3]; + int master_i, master_o, master_e; + + Seat *seat; + char name[FILENAME_MAX]; + pid_t child_pid; + int term_width, term_height; + bool child_dead, finished; + int exit_code; + bufchain output_data; + bool pending_eof; + Backend backend; +}; + +/* + * We store all the (active) PtyFd structures in a tree sorted by fd, + * so that when we get an uxsel notification we know which backend + * instance is the owner of the pty that caused it, and then we can + * find out which fd is the relevant one too. + */ +static int ptyfd_compare(void *av, void *bv) +{ + PtyFd *a = (PtyFd *)av; + PtyFd *b = (PtyFd *)bv; + + if (a->fd < b->fd) + return -1; + else if (a->fd > b->fd) + return +1; + return 0; +} + +static int ptyfd_find(void *av, void *bv) +{ + int a = *(int *)av; + PtyFd *b = (PtyFd *)bv; + + if (a < b->fd) + return -1; + else if (a > b->fd) + return +1; + return 0; +} + +static tree234 *ptyfds = NULL; + +/* + * We also have a tree of Pty structures themselves, 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 = -1; +static int pty_utmp_helper_pipe = -1; +static bool 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; + +char *pty_osx_envrestore_prefix; + +static void pty_close(Pty *pty); +static void pty_try_write(Pty *pty); + +#ifndef OMIT_UTMP +static void setup_utmp(char *ttyname, char *location) +{ +#if HAVE_LASTLOG + struct lastlog lastlog_entry; + FILE *lastlog; +#endif + struct passwd *pw; + struct timeval tv; + + pw = getpwuid(getuid()); + if (!pw) + return; /* can't stamp utmp if we don't have a username */ + memset(&utmp_entry, 0, sizeof(utmp_entry)); + utmp_entry.ut_type = USER_PROCESS; + utmp_entry.ut_pid = getpid(); +#if __GNUC__ >= 8 +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wstringop-truncation" +#endif // __GNUC__ >= 8 + 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)); +#if __GNUC__ >= 8 +# pragma GCC diagnostic pop +#endif // __GNUC__ >= 8 + /* + * 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(); + +#if HAVE_UPDWTMPX + updwtmpx(WTMPX_FILE, &utmp_entry); +#endif + +#if 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 = true; + +} + +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; + +#if HAVE_UPDWTMPX + updwtmpx(WTMPX_FILE, &utmp_entry); +#endif + + 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 = false; /* 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 */; +} + +static void pty_setup_sigchld_handler(void) +{ + static bool setup = false; + if (!setup) { + putty_signal(SIGCHLD, sigchld_handler); + setup = true; + } +} + +#ifndef OMIT_UTMP +static void fatal_sig_handler(int signum) +{ + putty_signal(signum, SIG_DFL); + cleanup_utmp(); + 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 + + const int flags = O_RDWR +#ifdef O_NOCTTY + | O_NOCTTY +#endif + ; + +#if HAVE_POSIX_OPENPT +#ifdef SET_NONBLOCK_VIA_OPENPT + /* + * OS X, as of 10.10 at least, doesn't permit me to set O_NONBLOCK + * on pty master fds via the usual fcntl mechanism. Fortunately, + * it does let me work around this by adding O_NONBLOCK to the + * posix_openpt flags parameter, which isn't a documented use of + * the API but seems to work. So we'll do that for now. + */ + pty->master_fd = posix_openpt(flags | O_NONBLOCK); +#else + pty->master_fd = posix_openpt(flags); +#endif + + if (pty->master_fd < 0) { + perror("posix_openpt"); + exit(1); + } +#else + pty->master_fd = open("/dev/ptmx", flags); + + if (pty->master_fd < 0) { + perror("/dev/ptmx: open"); + exit(1); + } +#endif + + if (grantpt(pty->master_fd) < 0) { + perror("grantpt"); + exit(1); + } + + 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 + +#ifndef SET_NONBLOCK_VIA_OPENPT + nonblock(pty->master_fd); +#endif +} + +static Pty *new_pty_struct(void) +{ + Pty *pty = snew(Pty); + pty->conf = NULL; + pty->pending_eof = false; + bufchain_init(&pty->output_data); + return 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) +{ +#ifndef NO_PTY_PRE_INIT + + Pty *pty; + +#ifndef OMIT_UTMP + pid_t pid; + int pipefd[2]; +#endif + + pty = single_pty = new_pty_struct(); + + /* set the child signal handler straight away; it needs to be set + * before we ever fork. */ + pty_setup_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. */ + { +#if HAVE_SETRESUID && HAVE_SETRESGID + int gid = getgid(), uid = getuid(); + int setresgid(gid_t, gid_t, gid_t); + int setresuid(uid_t, uid_t, uid_t); + if (setresgid(gid, gid, gid) < 0) { + perror("setresgid"); + exit(1); + } + if (setresuid(uid, uid, uid) < 0) { + perror("setresuid"); + exit(1); + } +#else + if (setgid(getgid()) < 0) { + perror("setgid"); + exit(1); + } + if (setuid(getuid()) < 0) { + perror("setuid"); + exit(1); + } +#endif + } + +#endif /* NO_PTY_PRE_INIT */ + +} + +static void pty_try_wait(void); + +static void pty_real_select_result(Pty *pty, int fd, int event, int status) +{ + char buf[4096]; + int ret; + bool 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. + */ + pty->child_dead = true; + del234(ptys_by_pid, pty); + pty->exit_code = status; + + /* + * If this is an ordinary pty session, this is also the + * moment to terminate the whole backend. + * + * We _could_ instead 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. + */ + if (pty->master_fd >= 0) + finished = true; + } + } else { + if (event == SELECT_R) { + bool is_stdout = (fd == pty->master_o); + + ret = read(fd, buf, sizeof(buf)); + + /* + * Treat EIO on a pty master as equivalent to EOF (because + * that's how the kernel seems to report the event where + * the last process connected to the other end of the pty + * went away). + */ + if (fd == pty->master_fd && ret < 0 && errno == EIO) + ret = 0; + + if (ret == 0) { + /* + * EOF on this input fd, so to begin with, we may as + * well close it, and remove all references to it in + * the pty's fd fields. + */ + uxsel_del(fd); + close(fd); + if (pty->master_fd == fd) + pty->master_fd = -1; + if (pty->master_o == fd) + pty->master_o = -1; + if (pty->master_e == fd) + pty->master_e = -1; + + if (is_stdout) { + /* + * We assume a clean exit if the pty (or stdout + * pipe) 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; + pty_try_wait(); /* one last effort to collect exit code */ + if (!pty->child_dead) + pty->exit_code = 0; + } + } else if (ret < 0) { + perror("read pty master"); + exit(1); + } else if (ret > 0) { + seat_output(pty->seat, !is_stdout, buf, ret); + } + } else if (event == SELECT_W) { + /* + * Attempt to send data down the pty. + */ + pty_try_write(pty); + } + } + + if (finished && !pty->finished) { + int close_on_exit; + int i; + + for (i = 0; i < 3; i++) + if (pty->fds[i].fd >= 0) + uxsel_del(pty->fds[i].fd); + + pty_close(pty); + + 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. + */ + close_on_exit = conf_get_int(pty->conf, CONF_close_on_exit); + if (close_on_exit == FORCE_OFF || + (close_on_exit == AUTO && pty->exit_code != 0)) { + char *message; + if (WIFEXITED(pty->exit_code)) { + message = dupprintf( + "\r\n[pterm: process terminated with exit code %d]\r\n", + WEXITSTATUS(pty->exit_code)); + } else if (WIFSIGNALED(pty->exit_code)) { +#if !HAVE_STRSIGNAL + message = dupprintf( + "\r\n[pterm: process terminated on signal %d]\r\n", + WTERMSIG(pty->exit_code)); +#else + message = dupprintf( + "\r\n[pterm: process terminated on signal %d (%s)]\r\n", + WTERMSIG(pty->exit_code), + strsignal(WTERMSIG(pty->exit_code))); +#endif + } else { + /* _Shouldn't_ happen, but if it does, a vague message + * is better than no message at all */ + message = dupprintf("\r\n[pterm: process terminated]\r\n"); + } + seat_stdout_pl(pty->seat, ptrlen_from_asciz(message)); + sfree(message); + } + + seat_eof(pty->seat); + seat_notify_remote_exit(pty->seat); + } +} + +static void pty_try_wait(void) +{ + Pty *pty; + pid_t pid; + int status; + + do { + pid = waitpid(-1, &status, WNOHANG); + + pty = find234(ptys_by_pid, &pid, pty_find_by_pid); + + if (pty) + pty_real_select_result(pty, -1, -1, status); + } while (pid > 0); +} + +void pty_select_result(int fd, int event) +{ + if (fd == pty_signal_pipe[0]) { + char c[1]; + + if (read(pty_signal_pipe[0], c, 1) <= 0) + /* ignore error */; + /* ignore its value; it'll be `x' */ + + pty_try_wait(); + } else { + PtyFd *ptyfd = find234(ptyfds, &fd, ptyfd_find); + + if (ptyfd) + pty_real_select_result(ptyfd->pty, fd, event, 0); + } +} + +static void pty_uxsel_setup_fd(Pty *pty, int fd) +{ + int rwx = 0; + + if (fd < 0) + return; + + /* read from standard output and standard error pipes */ + if (pty->master_o == fd || pty->master_e == fd) + rwx |= SELECT_R; + /* write to standard input pipe if we have any data */ + if (pty->master_i == fd && bufchain_size(&pty->output_data)) + rwx |= SELECT_W; + + uxsel_set(fd, rwx, pty_select_result); +} + +static void pty_uxsel_setup(Pty *pty) +{ + /* + * We potentially have three separate fds here, but on the other + * hand, some of them might be the same (if they're a pty master). + * So we can't just call uxsel_set(master_o, SELECT_R) and then + * uxsel_set(master_i, SELECT_W), without the latter potentially + * undoing the work of the former if master_o == master_i. + * + * Instead, here we call a single uxsel on each one of these fds + * (if it exists at all), and for each one, check it against all + * three to see which bits to set. + */ + pty_uxsel_setup_fd(pty, pty->master_o); + pty_uxsel_setup_fd(pty, pty->master_e); + pty_uxsel_setup_fd(pty, pty->master_i); + + /* + * 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], SELECT_R, pty_select_result); +} + +static void copy_ttymodes_into_termios( + struct termios *attrs, struct ssh_ttymodes modes) +{ +#define TTYMODE_CHAR(name, ssh_opcode, cc_index) { \ + if (modes.have_mode[ssh_opcode]) { \ + unsigned value = modes.mode_val[ssh_opcode]; \ + /* normalise wire value of 255 to local _POSIX_VDISABLE */ \ + attrs->c_cc[cc_index] = (value == 255 ? \ + _POSIX_VDISABLE : value); \ + } \ + } + +#define TTYMODE_FLAG(flagval, ssh_opcode, field, flagmask) { \ + if (modes.have_mode[ssh_opcode]) { \ + attrs->c_##field##flag &= ~flagmask; \ + if (modes.mode_val[ssh_opcode]) \ + attrs->c_##field##flag |= flagval; \ + } \ + } + +#define TTYMODES_LOCAL_ONLY /* omit any that this platform doesn't know */ +#include "ssh/ttymode-list.h" + +#undef TTYMODES_LOCAL_ONLY +#undef TTYMODE_CHAR +#undef TTYMODE_FLAG + + if (modes.have_mode[TTYMODE_ISPEED]) + cfsetispeed(attrs, modes.mode_val[TTYMODE_ISPEED]); + if (modes.have_mode[TTYMODE_OSPEED]) + cfsetospeed(attrs, modes.mode_val[TTYMODE_OSPEED]); +} + +/* + * The main setup function for the pty back end. This doesn't match + * the signature of backend_init(), partly because it has to be able + * to take extra arguments such as an argv array, and also because + * once we're changing the type signature _anyway_ we can discard the + * stuff that's not really applicable to this backend like host names + * and port numbers. + */ +Backend *pty_backend_create( + Seat *seat, LogContext *logctx, Conf *conf, char **argv, const char *cmd, + struct ssh_ttymodes ttymodes, bool pipes_instead, const char *dir, + const char *const *env_vars_to_unset) +{ + int slavefd; + pid_t pid, pgrp; +#ifndef NOT_X_WINDOWS /* for Mac OS X native compilation */ + bool got_windowid; + long windowid; +#endif + Pty *pty; + int i; + + /* No local authentication phase in this protocol */ + seat_set_trust_status(seat, false); + + if (single_pty) { + pty = single_pty; + assert(pty->conf == NULL); + } else { + pty = new_pty_struct(); + pty->master_fd = pty->slave_fd = -1; +#ifndef OMIT_UTMP + pty_stamped_utmp = false; +#endif + } + for (i = 0; i < 6; i++) + pty->pipefds[i] = -1; + for (i = 0; i < 3; i++) { + pty->fds[i].fd = -1; + pty->fds[i].pty = 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->seat = seat; + pty->backend.vt = &pty_backend; + + pty->conf = conf_copy(conf); + pty->term_width = conf_get_int(conf, CONF_width); + pty->term_height = conf_get_int(conf, CONF_height); + + if (!ptyfds) + ptyfds = newtree234(ptyfd_compare); + + if (pipes_instead) { + if (pty->master_fd >= 0) { + /* If somehow we've got a pty master already and don't + * need it, throw it away! */ + close(pty->master_fd); +#ifndef OMIT_UTMP + if (pty_utmp_helper_pipe >= 0) { + close(pty_utmp_helper_pipe); /* don't need this either */ + pty_utmp_helper_pipe = -1; + } +#endif + } + + + for (i = 0; i < 6; i += 2) { + if (pipe(pty->pipefds + i) < 0) { + backend_free(&pty->backend); + return NULL; + } + } + + pty->fds[0].fd = pty->master_i = pty->pipefds[1]; + pty->fds[1].fd = pty->master_o = pty->pipefds[2]; + pty->fds[2].fd = pty->master_e = pty->pipefds[4]; + + add234(ptyfds, &pty->fds[0]); + add234(ptyfds, &pty->fds[1]); + add234(ptyfds, &pty->fds[2]); + } else { + if (pty->master_fd < 0) + pty_open_master(pty); + +#ifndef OMIT_UTMP + /* + * Stamp utmp (that is, tell the utmp helper process to do so), + * or not. + */ + if (pty_utmp_helper_pipe >= 0) { /* if it's < 0, we can't anyway */ + if (!conf_get_bool(conf, CONF_stamp_utmp)) { + /* We're not stamping utmp, so just let the child + * process die that was waiting to unstamp it later. */ + close(pty_utmp_helper_pipe); + pty_utmp_helper_pipe = -1; + } else { + const char *location = seat_get_x_display(pty->seat); + 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 + + pty->master_i = pty->master_fd; + pty->master_o = pty->master_fd; + pty->master_e = -1; + + pty->fds[0].fd = pty->master_fd; + add234(ptyfds, &pty->fds[0]); + } + +#ifndef NOT_X_WINDOWS /* for Mac OS X native compilation */ + got_windowid = seat_get_windowid(pty->seat, &windowid); +#endif + + /* + * Set up the signal handler to catch SIGCHLD, if pty_pre_init + * didn't already do it. + */ + pty_setup_sigchld_handler(); + + /* + * Fork and execute the command. + */ + pid = fork(); + if (pid < 0) { + perror("fork"); + exit(1); + } + + if (pid == 0) { + struct termios attrs; + + /* + * We are the child. + */ + + if (pty_osx_envrestore_prefix) { + int plen = strlen(pty_osx_envrestore_prefix); + extern char **environ; + char **ep; + + restart_osx_env_restore: + for (ep = environ; *ep; ep++) { + char *e = *ep; + + if (!strncmp(e, pty_osx_envrestore_prefix, plen)) { + bool unset = (e[plen] == 'u'); + char *pname = dupprintf("%.*s", (int)strcspn(e, "="), e); + char *name = pname + plen + 1; + char *value = e + strcspn(e, "="); + if (*value) value++; + value = dupstr(value); + if (unset) + unsetenv(name); + else + setenv(name, value, 1); + unsetenv(pname); + sfree(pname); + sfree(value); + goto restart_osx_env_restore; + } + } + } + + pgrp = getpid(); + + if (pipes_instead) { + int i; + dup2(pty->pipefds[0], 0); + dup2(pty->pipefds[3], 1); + dup2(pty->pipefds[5], 2); + for (i = 0; i < 6; i++) + close(pty->pipefds[i]); + + setsid(); + } else { + slavefd = pty_open_slave(pty); + if (slavefd < 0) { + perror("slave pty: open"); + _exit(1); + } + + close(pty->master_fd); + noncloexec(slavefd); + dup2(slavefd, 0); + dup2(slavefd, 1); + dup2(slavefd, 2); + close(slavefd); + setsid(); +#ifdef TIOCSCTTY + ioctl(0, TIOCSCTTY, 1); +#endif + tcsetpgrp(0, pgrp); + + /* + * Set up configuration-dependent termios settings on the new + * pty. Linux would have let us do this on the pty master + * before we forked, but that fails on OS X, so we do it here + * instead. + */ + if (tcgetattr(0, &attrs) == 0) { + /* + * Set the backspace character to be whichever of ^H and + * ^? is specified by bksp_is_delete. + */ + attrs.c_cc[VERASE] = conf_get_bool(conf, CONF_bksp_is_delete) + ? '\177' : '\010'; + + /* + * Set the IUTF8 bit iff the character set is UTF-8. + */ +#ifdef IUTF8 + if (seat_is_utf8(seat)) + attrs.c_iflag |= IUTF8; + else + attrs.c_iflag &= ~IUTF8; +#endif + + copy_ttymodes_into_termios(&attrs, ttymodes); + + tcsetattr(0, TCSANOW, &attrs); + } + } + + setpgid(pgrp, pgrp); + if (!pipes_instead) { + int ptyfd = open(pty->name, O_WRONLY, 0); + if (ptyfd >= 0) + close(ptyfd); + } + setpgid(pgrp, pgrp); + + if (env_vars_to_unset) + for (const char *const *p = env_vars_to_unset; *p; p++) + unsetenv(*p); + + if (!pipes_instead) { + char *term_env_var = dupprintf("TERM=%s", + conf_get_str(conf, CONF_termtype)); + putenv(term_env_var); + /* We mustn't free term_env_var, as putenv links it into the + * environment in place. + */ + } +#ifndef NOT_X_WINDOWS /* for Mac OS X native compilation */ + if (got_windowid) { + 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. + */ + } + { + /* + * In case we were invoked with a --display argument that + * doesn't match DISPLAY in our actual environment, we + * should set DISPLAY for processes running inside the + * terminal to match the display the terminal itself is + * on. + */ + const char *x_display = seat_get_x_display(pty->seat); + if (x_display) { + char *x_display_env_var = dupprintf("DISPLAY=%s", x_display); + putenv(x_display_env_var); + /* As above, we don't free this. */ + } else { + unsetenv("DISPLAY"); + } + } +#endif + { + char *key, *val; + + for (val = conf_get_str_strs(conf, CONF_environmt, NULL, &key); + val != NULL; + val = conf_get_str_strs(conf, CONF_environmt, key, &key)) { + char *varval = dupcat(key, "=", val); + 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. + */ + } + } + + if (dir) { + if (chdir(dir) < 0) { + /* Ignore the error - nothing we can sensibly do about it, + * and our existing cwd is as good a fallback as any. */ + } + } + + /* + * 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. SIGPIPE was also + * (potentially) blocked by us during 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(SIGPIPE, false); + if (argv || cmd) { + /* + * If we were given a separated argument list, try to exec + * it. + */ + if (argv) { + execvp(argv[0], argv); + } + /* + * Otherwise, if we were given a single command string, + * try passing that to $SHELL -c. + * + * In the case of pterm, this system of fallbacks arranges + * that we can _either_ follow 'pterm -e' with a list of + * argv elements to be fed directly to exec, _or_ with a + * single argument containing a command to be parsed by a + * shell (but, in cases of doubt, the former is more + * reliable). We arrange this by setting argv to the full + * argument list, and also setting cmd to the single + * element of argv if it's a length-1 list. + * + * A quick survey of other terminal emulators' -e options + * (as of Debian squeeze) suggests that: + * + * - xterm supports both modes, more or less like this + * - gnome-terminal will only accept a one-string shell command + * - Eterm, kterm and rxvt will only accept a list of + * argv elements (as did older versions of pterm). + * + * It therefore seems important to support both usage + * modes in order to be a drop-in replacement for either + * xterm or gnome-terminal, and hence for anyone's + * plausible uses of the Debian-style alias + * 'x-terminal-emulator'. + * + * In other use cases, a caller can set only one of argv + * and cmd to get a fixed handling of the input. + */ + if (cmd) { + char *shell = getenv("SHELL"); + if (shell) + execl(shell, shell, "-c", cmd, (void *)NULL); + } + } else { + const char *shell = getenv("SHELL"); + if (!shell) + shell = "/bin/sh"; + char *shellname; + if (conf_get_bool(conf, CONF_login_shell)) { + const char *p = strrchr(shell, '/'); + shellname = snewn(2+strlen(shell), char); + p = p ? p+1 : shell; + sprintf(shellname, "-%s", p); + } else + shellname = (char *)shell; + execl(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); + if (pty->pipefds[0] >= 0) { + close(pty->pipefds[0]); + pty->pipefds[0] = -1; + } + if (pty->pipefds[3] >= 0) { + close(pty->pipefds[3]); + pty->pipefds[3] = -1; + } + if (pty->pipefds[5] >= 0) { + close(pty->pipefds[5]); + pty->pipefds[5] = -1; + } + add234(ptys_by_pid, pty); + } + + pty_uxsel_setup(pty); + + return &pty->backend; +} + +/* + * This is the pty backend's _official_ init method, for BackendVtable + * purposes. Its job is just to be an API converter, ignoring the + * irrelevant input parameters and making up auxiliary outputs. Also + * it gets the argv array from the global variable pty_argv, expecting + * that it will have been invoked by pterm. + */ +static char *pty_init(const BackendVtable *vt, Seat *seat, + Backend **backend_handle, LogContext *logctx, + Conf *conf, const char *host, int port, + char **realhost, bool nodelay, bool keepalive) +{ + const char *cmd = NULL; + struct ssh_ttymodes modes; + + memset(&modes, 0, sizeof(modes)); + + if (pty_argv && pty_argv[0] && !pty_argv[1]) + cmd = pty_argv[0]; + + assert(vt == &pty_backend); + *backend_handle = pty_backend_create( + seat, logctx, conf, pty_argv, cmd, modes, false, NULL, NULL); + *realhost = dupstr(""); + return NULL; +} + +static void pty_reconfig(Backend *be, Conf *conf) +{ + Pty *pty = container_of(be, Pty, backend); + /* + * 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. + */ + conf_copy_into(pty->conf, conf); +} + +/* + * Stub routine (never called in pterm). + */ +static void pty_free(Backend *be) +{ + Pty *pty = container_of(be, Pty, backend); + int i; + + pty_close(pty); + + /* Either of these may fail `not found'. That's fine with us. */ + del234(ptys_by_pid, pty); + for (i = 0; i < 3; i++) + if (pty->fds[i].fd >= 0) + del234(ptyfds, &pty->fds[i]); + + bufchain_clear(&pty->output_data); + + conf_free(pty->conf); + pty->conf = NULL; + + if (pty == single_pty) { + /* + * Leave this structure around in case we need to Restart + * Session. + */ + } else { + sfree(pty); + } +} + +static void pty_try_write(Pty *pty) +{ + ssize_t ret; + + assert(pty->master_i >= 0); + + while (bufchain_size(&pty->output_data) > 0) { + ptrlen data = bufchain_prefix(&pty->output_data); + ret = write(pty->master_i, data.ptr, 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); + } + + if (pty->pending_eof && bufchain_size(&pty->output_data) == 0) { + /* This should only happen if pty->master_i is a pipe that + * doesn't alias either output fd */ + assert(pty->master_i != pty->master_o); + assert(pty->master_i != pty->master_e); + uxsel_del(pty->master_i); + close(pty->master_i); + pty->master_i = -1; + pty->pending_eof = false; + } + + pty_uxsel_setup(pty); +} + +/* + * Called to send data down the pty. + */ +static size_t pty_send(Backend *be, const char *buf, size_t len) +{ + Pty *pty = container_of(be, Pty, backend); + + if (pty->master_i < 0 || pty->pending_eof) + 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) +{ + int i; + + if (pty->master_o >= 0) + uxsel_del(pty->master_o); + if (pty->master_e >= 0) + uxsel_del(pty->master_e); + if (pty->master_i >= 0) + uxsel_del(pty->master_i); + + if (pty->master_fd >= 0) { + close(pty->master_fd); + pty->master_fd = -1; + } + for (i = 0; i < 6; i++) { + if (pty->pipefds[i] >= 0) + close(pty->pipefds[i]); + pty->pipefds[i] = -1; + } + pty->master_i = pty->master_o = pty->master_e = -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 size_t pty_sendbuffer(Backend *be) +{ + /* Pty *pty = container_of(be, Pty, backend); */ + return 0; +} + +/* + * Called to set the size of the window + */ +static void pty_size(Backend *be, int width, int height) +{ + Pty *pty = container_of(be, Pty, backend); + struct winsize size; + int xpixel = 0, ypixel = 0; + + pty->term_width = width; + pty->term_height = height; + + if (pty->master_fd < 0) + return; + + seat_get_window_pixel_size(pty->seat, &xpixel, &ypixel); + + size.ws_row = (unsigned short)pty->term_height; + size.ws_col = (unsigned short)pty->term_width; + size.ws_xpixel = (unsigned short)xpixel; + size.ws_ypixel = (unsigned short)ypixel; + ioctl(pty->master_fd, TIOCSWINSZ, (void *)&size); + return; +} + +/* + * Send special codes. + */ +static void pty_special(Backend *be, SessionSpecialCode code, int arg) +{ + Pty *pty = container_of(be, Pty, backend); + + if (code == SS_BRK) { + if (pty->master_fd >= 0) + tcsendbreak(pty->master_fd, 0); + return; + } + + if (code == SS_EOF) { + if (pty->master_i >= 0 && pty->master_i != pty->master_fd) { + pty->pending_eof = true; + pty_try_write(pty); + } + return; + } + + { + int sig = -1; + + #define SIGNAL_SUB(name) if (code == SS_SIG ## name) sig = SIG ## name; + #define SIGNAL_MAIN(name, text) SIGNAL_SUB(name) + #define SIGNALS_LOCAL_ONLY + #include "ssh/signal-list.h" + #undef SIGNAL_SUB + #undef SIGNAL_MAIN + #undef SIGNALS_LOCAL_ONLY + + if (sig != -1) { + if (!pty->child_dead) + kill(pty->child_pid, sig); + return; + } + } + + return; +} + +/* + * Return a list of the special codes that make sense in this + * protocol. + */ +static const SessionSpecial *pty_get_specials(Backend *be) +{ + /* Pty *pty = container_of(be, Pty, backend); */ + /* + * 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 bool pty_connected(Backend *be) +{ + /* Pty *pty = container_of(be, Pty, backend); */ + return true; +} + +static bool pty_sendok(Backend *be) +{ + /* Pty *pty = container_of(be, Pty, backend); */ + return true; +} + +static void pty_unthrottle(Backend *be, size_t backlog) +{ + /* Pty *pty = container_of(be, Pty, backend); */ + /* do nothing */ +} + +static bool pty_ldisc(Backend *be, int option) +{ + /* Pty *pty = container_of(be, Pty, backend); */ + return false; /* neither editing nor echoing */ +} + +static void pty_provide_ldisc(Backend *be, Ldisc *ldisc) +{ + /* Pty *pty = container_of(be, Pty, backend); */ + /* This is a stub. */ +} + +static int pty_exitcode(Backend *be) +{ + Pty *pty = container_of(be, Pty, backend); + if (!pty->finished) + return -1; /* not dead yet */ + else if (WIFSIGNALED(pty->exit_code)) + return 128 + WTERMSIG(pty->exit_code); + else + return WEXITSTATUS(pty->exit_code); +} + +int pty_backend_exit_signum(Backend *be) +{ + Pty *pty = container_of(be, Pty, backend); + + if (!pty->finished || !WIFSIGNALED(pty->exit_code)) + return -1; + + return WTERMSIG(pty->exit_code); +} + +ptrlen pty_backend_exit_signame(Backend *be, char **aux_msg) +{ + *aux_msg = NULL; + + int sig = pty_backend_exit_signum(be); + if (sig < 0) + return PTRLEN_LITERAL(""); + + #define SIGNAL_SUB(s) { \ + if (sig == SIG ## s) \ + return PTRLEN_LITERAL(#s); \ + } + #define SIGNAL_MAIN(s, desc) SIGNAL_SUB(s) + #define SIGNALS_LOCAL_ONLY + #include "ssh/signal-list.h" + #undef SIGNAL_MAIN + #undef SIGNAL_SUB + #undef SIGNALS_LOCAL_ONLY + + *aux_msg = dupprintf("untranslatable signal number %d: %s", + sig, strsignal(sig)); + return PTRLEN_LITERAL("HUP"); /* need some kind of default */ +} + +static int pty_cfg_info(Backend *be) +{ + /* Pty *pty = container_of(be, Pty, backend); */ + return 0; +} + +const BackendVtable pty_backend = { + .init = pty_init, + .free = pty_free, + .reconfig = pty_reconfig, + .send = pty_send, + .sendbuffer = pty_sendbuffer, + .size = pty_size, + .special = pty_special, + .get_specials = pty_get_specials, + .connected = pty_connected, + .exitcode = pty_exitcode, + .sendok = pty_sendok, + .ldisc_option_state = pty_ldisc, + .provide_ldisc = pty_provide_ldisc, + .unthrottle = pty_unthrottle, + .cfg_info = pty_cfg_info, + .id = "pty", + .displayname = "pty", + .protocol = -1, +}; diff --git a/unix/putty-config-xpm.c b/unix/putty-config-xpm.c new file mode 100644 index 00000000..34c5d491 --- /dev/null +++ b/unix/putty-config-xpm.c @@ -0,0 +1,150 @@ +/* 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; diff --git a/unix/putty-xpm.c b/unix/putty-xpm.c new file mode 100644 index 00000000..56d16bee --- /dev/null +++ b/unix/putty-xpm.c @@ -0,0 +1,147 @@ +/* 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; diff --git a/unix/putty.c b/unix/putty.c new file mode 100644 index 00000000..7a808087 --- /dev/null +++ b/unix/putty.c @@ -0,0 +1,92 @@ +/* + * Unix PuTTY main program. + */ + +#include +#include +#include +#include +#include +#include +#include + +#define MAY_REFER_TO_GTK_IN_HEADERS + +#include "putty.h" +#include "ssh.h" +#include "storage.h" + +#include "gtkcompat.h" + +/* + * Stubs to avoid uxpty.c needing to be linked in. + */ +const bool use_pty_argv = false; +char **pty_argv; /* never used */ +char *pty_osx_envrestore_prefix; + +/* + * Clean up and exit. + */ +void cleanup_exit(int code) +{ + /* + * Clean up. + */ + sk_cleanup(); + random_save_seed(); + exit(code); +} + +const struct BackendVtable *select_backend(Conf *conf) +{ + const struct BackendVtable *vt = + backend_vt_from_proto(conf_get_int(conf, CONF_protocol)); + assert(vt != NULL); + return vt; +} + +void initial_config_box(Conf *conf, post_dialog_fn_t after, void *afterctx) +{ + char *title = dupcat(appname, " Configuration"); + create_config_box(title, conf, false, 0, after, afterctx); + sfree(title); +} + +const bool use_event_log = true, new_session = true, saved_sessions = true; +const bool dup_check_launchable = true; + +/* + * 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); +} + +const bool share_can_be_downstream = true; +const bool share_can_be_upstream = true; + +const unsigned cmdline_tooltype = + TOOLTYPE_HOST_ARG | + TOOLTYPE_PORT_ARG | + TOOLTYPE_NO_VERBOSE_OPTION; + +void setup(bool single) +{ + sk_init(); + settings_set_default_protocol(be_default_protocol); + /* Find the appropriate default port. */ + { + const struct BackendVtable *vt = + backend_vt_from_proto(be_default_protocol); + settings_set_default_port(0); /* illegal */ + if (vt) + settings_set_default_port(vt->default_port); + } +} diff --git a/unix/serial.c b/unix/serial.c new file mode 100644 index 00000000..d4a1e0ba --- /dev/null +++ b/unix/serial.c @@ -0,0 +1,595 @@ +/* + * Serial back end (Unix-specific). + */ + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "putty.h" +#include "tree234.h" + +#define SERIAL_MAX_BACKLOG 4096 + +typedef struct Serial Serial; +struct Serial { + Seat *seat; + LogContext *logctx; + int fd; + bool finished; + size_t inbufsize; + bufchain output_data; + Backend backend; +}; + +/* + * 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 void serial_select_result(int fd, int event); +static void serial_uxsel_setup(Serial *serial); +static void serial_try_write(Serial *serial); + +static char *serial_configure(Serial *serial, Conf *conf) +{ + struct termios options; + int bflag, bval, speed, flow, parity; + const char *str; + + if (serial->fd < 0) + return dupstr("Unable to reconfigure already-closed " + "serial connection"); + + tcgetattr(serial->fd, &options); + + /* + * Find the appropriate baud rate flag. + */ + speed = conf_get_int(conf, CONF_serspeed); +#define SETBAUD(x) (bflag = B ## x, bval = x) +#define CHECKBAUD(x) do { if (speed >= 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); + logeventf(serial->logctx, "Configuring baud rate %d", bval); + + options.c_cflag &= ~CSIZE; + switch (conf_get_int(conf, CONF_serdatabits)) { + case 5: options.c_cflag |= CS5; break; + case 6: options.c_cflag |= CS6; break; + case 7: options.c_cflag |= CS7; break; + case 8: options.c_cflag |= CS8; break; + default: return dupstr("Invalid number of data bits " + "(need 5, 6, 7 or 8)"); + } + logeventf(serial->logctx, "Configuring %d data bits", + conf_get_int(conf, CONF_serdatabits)); + + if (conf_get_int(conf, CONF_serstopbits) >= 4) { + options.c_cflag |= CSTOPB; + } else { + options.c_cflag &= ~CSTOPB; + } + logeventf(serial->logctx, "Configuring %s", + (options.c_cflag & CSTOPB ? "2 stop bits" : "1 stop bit")); + + options.c_iflag &= ~(IXON|IXOFF); +#ifdef CRTSCTS + options.c_cflag &= ~CRTSCTS; +#endif +#ifdef CNEW_RTSCTS + options.c_cflag &= ~CNEW_RTSCTS; +#endif + flow = conf_get_int(conf, CONF_serflow); + if (flow == SER_FLOW_XONXOFF) { + options.c_iflag |= IXON | IXOFF; + str = "XON/XOFF"; + } else if (flow == 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"; + logeventf(serial->logctx, "Configuring %s flow control", str); + + /* Parity */ + parity = conf_get_int(conf, CONF_serparity); + if (parity == SER_PAR_ODD) { + options.c_cflag |= PARENB; + options.c_cflag |= PARODD; + str = "odd"; + } else if (parity == SER_PAR_EVEN) { + options.c_cflag |= PARENB; + options.c_cflag &= ~PARODD; + str = "even"; + } else { + options.c_cflag &= ~PARENB; + str = "no"; + } + logeventf(serial->logctx, "Configuring %s parity", str); + + 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 dupprintf("Configuring serial port: %s", strerror(errno)); + + 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 char *serial_init(const BackendVtable *vt, Seat *seat, + Backend **backend_handle, LogContext *logctx, + Conf *conf, const char *host, int port, + char **realhost, bool nodelay, bool keepalive) +{ + Serial *serial; + char *err; + char *line; + + /* No local authentication phase in this protocol */ + seat_set_trust_status(seat, false); + + serial = snew(Serial); + serial->backend.vt = vt; + *backend_handle = &serial->backend; + + serial->seat = seat; + serial->logctx = logctx; + serial->finished = false; + serial->inbufsize = 0; + bufchain_init(&serial->output_data); + + line = conf_get_str(conf, CONF_serline); + logeventf(serial->logctx, "Opening serial device %s", line); + + serial->fd = open(line, O_RDWR | O_NOCTTY | O_NDELAY | O_NONBLOCK); + if (serial->fd < 0) + return dupprintf("Opening serial port '%s': %s", + line, strerror(errno)); + + cloexec(serial->fd); + + err = serial_configure(serial, conf); + if (err) + return err; + + *realhost = dupstr(line); + + 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. + */ + seat_update_specials_menu(serial->seat); + + return NULL; +} + +static void serial_close(Serial *serial) +{ + if (serial->fd >= 0) { + uxsel_del(serial->fd); + close(serial->fd); + serial->fd = -1; + } +} + +static void serial_free(Backend *be) +{ + Serial *serial = container_of(be, Serial, backend); + + serial_close(serial); + + bufchain_clear(&serial->output_data); + + sfree(serial); +} + +static void serial_reconfig(Backend *be, Conf *conf) +{ + Serial *serial = container_of(be, Serial, backend); + + char *err = serial_configure(serial, conf); + if (err) { + /* + * FIXME: apart from freeing the dynamically allocated + * message, what should we do if this returns an error? + */ + sfree(err); + } +} + +static void serial_select_result(int fd, int event) +{ + Serial *serial; + char buf[4096]; + int ret; + bool finished = false; + + serial = find234(serial_by_fd, &fd, serial_find_by_fd); + + if (!serial) + return; /* 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; /* spurious */ +#endif +#ifdef EWOULDBLOCK + if (errno == EWOULDBLOCK) + return; /* spurious */ +#endif + perror("read serial port"); + exit(1); + } else if (ret > 0) { + serial->inbufsize = seat_stdout(serial->seat, 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; + + seat_notify_remote_exit(serial->seat); + } +} + +static void serial_uxsel_setup(Serial *serial) +{ + int rwx = 0; + + if (serial->inbufsize <= SERIAL_MAX_BACKLOG) + rwx |= SELECT_R; + if (bufchain_size(&serial->output_data)) + rwx |= SELECT_W; /* might also want to write to it */ + uxsel_set(serial->fd, rwx, serial_select_result); +} + +static void serial_try_write(Serial *serial) +{ + ssize_t ret; + + assert(serial->fd >= 0); + + while (bufchain_size(&serial->output_data) > 0) { + ptrlen data = bufchain_prefix(&serial->output_data); + ret = write(serial->fd, data.ptr, 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 size_t serial_send(Backend *be, const char *buf, size_t len) +{ + Serial *serial = container_of(be, Serial, backend); + + 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 size_t serial_sendbuffer(Backend *be) +{ + Serial *serial = container_of(be, Serial, backend); + return bufchain_size(&serial->output_data); +} + +/* + * Called to set the size of the window + */ +static void serial_size(Backend *be, int width, int height) +{ + /* Do nothing! */ + return; +} + +/* + * Send serial special codes. + */ +static void serial_special(Backend *be, SessionSpecialCode code, int arg) +{ + Serial *serial = container_of(be, Serial, backend); + + if (serial->fd >= 0 && code == SS_BRK) { + tcsendbreak(serial->fd, 0); + logevent(serial->logctx, "Sending serial break at user request"); + } + + return; +} + +/* + * Return a list of the special codes that make sense in this + * protocol. + */ +static const SessionSpecial *serial_get_specials(Backend *be) +{ + static const struct SessionSpecial specials[] = { + {"Break", SS_BRK}, + {NULL, SS_EXITMENU} + }; + return specials; +} + +static bool serial_connected(Backend *be) +{ + return true; /* always connected */ +} + +static bool serial_sendok(Backend *be) +{ + return true; +} + +static void serial_unthrottle(Backend *be, size_t backlog) +{ + Serial *serial = container_of(be, Serial, backend); + serial->inbufsize = backlog; + serial_uxsel_setup(serial); +} + +static bool serial_ldisc(Backend *be, int option) +{ + /* + * Local editing and local echo are off by default. + */ + return false; +} + +static void serial_provide_ldisc(Backend *be, Ldisc *ldisc) +{ + /* This is a stub. */ +} + +static int serial_exitcode(Backend *be) +{ + Serial *serial = container_of(be, Serial, backend); + 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(Backend *be) +{ + return 0; +} + +const BackendVtable serial_backend = { + .init = serial_init, + .free = serial_free, + .reconfig = serial_reconfig, + .send = serial_send, + .sendbuffer = serial_sendbuffer, + .size = serial_size, + .special = serial_special, + .get_specials = serial_get_specials, + .connected = serial_connected, + .exitcode = serial_exitcode, + .sendok = serial_sendok, + .ldisc_option_state = serial_ldisc, + .provide_ldisc = serial_provide_ldisc, + .unthrottle = serial_unthrottle, + .cfg_info = serial_cfg_info, + .id = "serial", + .displayname = "Serial", + .protocol = PROT_SERIAL, + .serial_parity_mask = ((1 << SER_PAR_NONE) | + (1 << SER_PAR_ODD) | + (1 << SER_PAR_EVEN)), + .serial_flow_mask = ((1 << SER_FLOW_NONE) | + (1 << SER_FLOW_XONXOFF) | + (1 << SER_FLOW_RTSCTS)), +}; diff --git a/unix/sftp.c b/unix/sftp.c new file mode 100644 index 00000000..89a81c92 --- /dev/null +++ b/unix/sftp.c @@ -0,0 +1,578 @@ +/* + * uxsftp.c: the Unix-specific parts of PSFTP and PSCP. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "putty.h" +#include "ssh.h" +#include "psftp.h" + +#if HAVE_GLOB_H +#include +#endif + +char *x_get_default(const char *key) +{ + return NULL; /* this is a stub */ +} + +void platform_get_x11_auth(struct X11Display *display, Conf *conf) +{ + /* Do nothing, therefore no auth. */ +} +const bool platform_uses_x11_unix_by_default = true; + +/* + * Default settings that are specific to PSFTP. + */ +char *platform_default_s(const char *name) +{ + return NULL; +} + +bool platform_default_b(const char *name, bool def) +{ + return def; +} + +int platform_default_i(const char *name, int def) +{ + return def; +} + +FontSpec *platform_default_fontspec(const char *name) +{ + return fontspec_new(""); +} + +Filename *platform_default_filename(const char *name) +{ + if (!strcmp(name, "LogFileName")) + return filename_from_str("putty.log"); + else + return filename_from_str(""); +} + +int filexfer_get_userpass_input(Seat *seat, prompts_t *p, bufchain *input) +{ + int ret; + ret = cmdline_get_passwd_input(p); + if (ret == -1) + ret = console_get_userpass_input(p); + 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; + size_t 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. + */ + sgrowarray(buffer, size, size); + } +} + +struct RFile { + int fd; +}; + +RFile *open_existing_file(const char *name, uint64_t *size, + unsigned long *mtime, unsigned long *atime, + long *perms) +{ + int fd; + RFile *ret; + + fd = open(name, O_RDONLY); + if (fd < 0) + return NULL; + + ret = snew(RFile); + ret->fd = fd; + + if (size || mtime || atime || perms) { + struct stat statbuf; + if (fstat(fd, &statbuf) < 0) { + fprintf(stderr, "%s: stat: %s\n", name, strerror(errno)); + memset(&statbuf, 0, sizeof(statbuf)); + } + + if (size) + *size = statbuf.st_size; + + if (mtime) + *mtime = statbuf.st_mtime; + + if (atime) + *atime = statbuf.st_atime; + + if (perms) + *perms = statbuf.st_mode; + } + + 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(const char *name, long perms) +{ + int fd; + WFile *ret; + + fd = open(name, O_CREAT | O_TRUNC | O_WRONLY, + (mode_t)(perms ? perms : 0666)); + if (fd < 0) + return NULL; + + ret = snew(WFile); + ret->fd = fd; + ret->name = dupstr(name); + + return ret; +} + + +WFile *open_existing_wfile(const char *name, uint64_t *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 = 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_t offset, int whence) +{ + int lseek_whence; + + 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, offset, lseek_whence) >= 0 ? 0 : -1; +} + +uint64_t get_file_posn(WFile *f) +{ + return lseek(f->fd, (off_t) 0, SEEK_CUR); +} + +int file_type(const 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(const char *name, const char **errmsg) +{ + DIR *dir; + DirHandle *ret; + + dir = opendir(name); + if (!dir) { + *errmsg = strerror(errno); + 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(const char *name, bool 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 { +#if HAVE_GLOB_H + 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; +#else + /* On a system without glob.h, we just have to return a + * failure code */ + return WCTYPE_NONEXISTENT; +#endif + } +} + +/* + * Actually return matching file names for a local wildcard. + */ +#if HAVE_GLOB_H +struct WildcardMatcher { + glob_t globbed; + int i; +}; +WildcardMatcher *begin_wildcard_matching(const 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); +} +#else +WildcardMatcher *begin_wildcard_matching(const char *name) +{ + return NULL; +} +char *wildcard_get_filename(WildcardMatcher *dir) +{ + unreachable("Can't construct a valid WildcardMatcher without "); +} +void finish_wildcard_matching(WildcardMatcher *dir) +{ + unreachable("Can't construct a valid WildcardMatcher without "); +} +#endif + +char *stripslashes(const char *str, bool local) +{ + char *p; + + /* + * On Unix, we do the same thing regardless of the 'local' + * parameter. + */ + p = strrchr(str, '/'); + if (p) str = p+1; + + return (char *)str; +} + +bool vet_filename(const char *name) +{ + if (strchr(name, '/')) + return false; + + if (name[0] == '.' && (!name[1] || (name[1] == '.' && !name[2]))) + return false; + + return true; +} + +bool create_directory(const char *name) +{ + return mkdir(name, 0777) == 0; +} + +char *dir_file_cat(const char *dir, const char *file) +{ + ptrlen dir_pl = ptrlen_from_asciz(dir); + return dupcat( + dir, ptrlen_endswith(dir_pl, PTRLEN_LITERAL("/"), NULL) ? "" : "/", + file); +} + +/* + * Do a select() between all currently active network fds and + * optionally stdin, using cli_main_loop. + */ + +struct ssh_sftp_mainloop_ctx { + bool include_stdin, no_fds_ok; + int toret; +}; +static bool ssh_sftp_pw_setup(void *vctx, pollwrapper *pw) +{ + struct ssh_sftp_mainloop_ctx *ctx = (struct ssh_sftp_mainloop_ctx *)vctx; + int fdstate, rwx; + + if (!ctx->no_fds_ok && !toplevel_callback_pending() && + first_fd(&fdstate, &rwx) < 0) { + ctx->toret = -1; + return false; /* terminate cli_main_loop */ + } + + if (ctx->include_stdin) + pollwrap_add_fd_rwx(pw, 0, SELECT_R); + + return true; +} +static void ssh_sftp_pw_check(void *vctx, pollwrapper *pw) +{ + struct ssh_sftp_mainloop_ctx *ctx = (struct ssh_sftp_mainloop_ctx *)vctx; + + if (ctx->include_stdin && pollwrap_check_fd_rwx(pw, 0, SELECT_R)) + ctx->toret = 1; +} +static bool ssh_sftp_mainloop_continue(void *vctx, bool found_any_fd, + bool ran_any_callback) +{ + struct ssh_sftp_mainloop_ctx *ctx = (struct ssh_sftp_mainloop_ctx *)vctx; + if (ctx->toret != 0 || found_any_fd || ran_any_callback) + return false; /* finish the loop */ + return true; +} +static int ssh_sftp_do_select(bool include_stdin, bool no_fds_ok) +{ + struct ssh_sftp_mainloop_ctx ctx[1]; + ctx->include_stdin = include_stdin; + ctx->no_fds_ok = no_fds_ok; + ctx->toret = 0; + + cli_main_loop(ssh_sftp_pw_setup, ssh_sftp_pw_check, + ssh_sftp_mainloop_continue, ctx); + + return ctx->toret; +} + +/* + * 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(const char *prompt, bool no_fds_ok) +{ + char *buf; + size_t buflen, bufsize; + int 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"); + sfree(buf); + return NULL; /* woop woop */ + } + if (ret > 0) { + sgrowarray(buf, bufsize, buflen); + ret = read(0, buf+buflen, 1); + if (ret < 0) { + perror("read"); + sfree(buf); + return NULL; + } + if (ret == 0) { + /* eof on stdin; no error, but no answer either */ + sfree(buf); + return NULL; + } + + if (buf[buflen++] == '\n') { + /* we have a full line */ + return buf; + } + } + } +} + +void frontend_net_error_pending(void) {} + +void platform_psftp_pre_conn_setup(LogPolicy *lp) {} + +const bool buildinfo_gtk_relevant = false; + +/* + * Main program: do platform-specific initialisation and then call + * psftp_main(). + */ +int main(int argc, char *argv[]) +{ + uxsel_init(); + return psftp_main(argc, argv); +} diff --git a/unix/sftpserver.c b/unix/sftpserver.c new file mode 100644 index 00000000..7257c5c9 --- /dev/null +++ b/unix/sftpserver.c @@ -0,0 +1,703 @@ +/* + * Implement the SftpServer abstraction, in the 'live' form (i.e. + * really operating on the Unix filesystem). + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "putty.h" +#include "ssh.h" +#include "ssh/server.h" +#include "ssh/sftp.h" +#include "tree234.h" + +typedef struct UnixSftpServer UnixSftpServer; + +struct UnixSftpServer { + unsigned *fdseqs; + bool *fdsopen; + size_t fdsize; + + tree234 *dirhandles; + int last_dirhandle_index; + + char handlekey[8]; + + SftpServer srv; +}; + +struct uss_dirhandle { + int index; + DIR *dp; +}; + +#define USS_DIRHANDLE_SEQ (0xFFFFFFFFU) + +static int uss_dirhandle_cmp(void *av, void *bv) +{ + struct uss_dirhandle *a = (struct uss_dirhandle *)av; + struct uss_dirhandle *b = (struct uss_dirhandle *)bv; + if (a->index < b->index) + return -1; + if (a->index > b->index) + return +1; + return 0; +} + +static SftpServer *uss_new(const SftpServerVtable *vt) +{ + UnixSftpServer *uss = snew(UnixSftpServer); + + memset(uss, 0, sizeof(UnixSftpServer)); + + uss->dirhandles = newtree234(uss_dirhandle_cmp); + uss->srv.vt = vt; + + make_unix_sftp_filehandle_key(uss->handlekey, sizeof(uss->handlekey)); + + return &uss->srv; +} + +static void uss_free(SftpServer *srv) +{ + UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv); + struct uss_dirhandle *udh; + + for (size_t i = 0; i < uss->fdsize; i++) + if (uss->fdsopen[i]) + close(i); + sfree(uss->fdseqs); + + while ((udh = delpos234(uss->dirhandles, 0)) != NULL) { + closedir(udh->dp); + sfree(udh); + } + + sfree(uss); +} + +static void uss_return_handle_raw( + UnixSftpServer *uss, SftpReplyBuilder *reply, int index, unsigned seq) +{ + unsigned char handlebuf[8]; + PUT_32BIT_MSB_FIRST(handlebuf, index); + PUT_32BIT_MSB_FIRST(handlebuf + 4, seq); + des_encrypt_xdmauth(uss->handlekey, handlebuf, 8); + fxp_reply_handle(reply, make_ptrlen(handlebuf, 8)); +} + +static bool uss_decode_handle( + UnixSftpServer *uss, ptrlen handle, int *index, unsigned *seq) +{ + unsigned char handlebuf[8]; + + if (handle.len != 8) + return false; + memcpy(handlebuf, handle.ptr, 8); + des_decrypt_xdmauth(uss->handlekey, handlebuf, 8); + *index = toint(GET_32BIT_MSB_FIRST(handlebuf)); + *seq = GET_32BIT_MSB_FIRST(handlebuf + 4); + return true; +} + +static void uss_return_new_handle( + UnixSftpServer *uss, SftpReplyBuilder *reply, int fd) +{ + assert(fd >= 0); + if (fd >= uss->fdsize) { + size_t old_size = uss->fdsize; + sgrowarray(uss->fdseqs, uss->fdsize, fd); + uss->fdsopen = sresize(uss->fdsopen, uss->fdsize, bool); + while (old_size < uss->fdsize) { + uss->fdseqs[old_size] = 0; + uss->fdsopen[old_size] = false; + old_size++; + } + } + assert(!uss->fdsopen[fd]); + uss->fdsopen[fd] = true; + if (++uss->fdseqs[fd] == USS_DIRHANDLE_SEQ) + uss->fdseqs[fd] = 0; + uss_return_handle_raw(uss, reply, fd, uss->fdseqs[fd]); +} + +static int uss_try_lookup_fd(UnixSftpServer *uss, ptrlen handle) +{ + int fd; + unsigned seq; + if (!uss_decode_handle(uss, handle, &fd, &seq) || + fd < 0 || fd >= uss->fdsize || + !uss->fdsopen[fd] || uss->fdseqs[fd] != seq) + return -1; + + return fd; +} + +static int uss_lookup_fd(UnixSftpServer *uss, SftpReplyBuilder *reply, + ptrlen handle) +{ + int fd = uss_try_lookup_fd(uss, handle); + if (fd < 0) + fxp_reply_error(reply, SSH_FX_FAILURE, "invalid file handle"); + return fd; +} + +static void uss_return_new_dirhandle( + UnixSftpServer *uss, SftpReplyBuilder *reply, DIR *dp) +{ + struct uss_dirhandle *udh = snew(struct uss_dirhandle); + udh->index = uss->last_dirhandle_index++; + udh->dp = dp; + struct uss_dirhandle *added = add234(uss->dirhandles, udh); + assert(added == udh); + uss_return_handle_raw(uss, reply, udh->index, USS_DIRHANDLE_SEQ); +} + +static struct uss_dirhandle *uss_try_lookup_dirhandle( + UnixSftpServer *uss, ptrlen handle) +{ + struct uss_dirhandle key, *udh; + unsigned seq; + + if (!uss_decode_handle(uss, handle, &key.index, &seq) || + seq != USS_DIRHANDLE_SEQ || + (udh = find234(uss->dirhandles, &key, NULL)) == NULL) + return NULL; + + return udh; +} + +static struct uss_dirhandle *uss_lookup_dirhandle( + UnixSftpServer *uss, SftpReplyBuilder *reply, ptrlen handle) +{ + struct uss_dirhandle *udh = uss_try_lookup_dirhandle(uss, handle); + if (!udh) + fxp_reply_error(reply, SSH_FX_FAILURE, "invalid file handle"); + return udh; +} + +static void uss_error(UnixSftpServer *uss, SftpReplyBuilder *reply) +{ + unsigned code = SSH_FX_FAILURE; + switch (errno) { + case ENOENT: + code = SSH_FX_NO_SUCH_FILE; + break; + case EPERM: + code = SSH_FX_PERMISSION_DENIED; + break; + } + fxp_reply_error(reply, code, strerror(errno)); +} + +static void uss_realpath(SftpServer *srv, SftpReplyBuilder *reply, + ptrlen path) +{ + UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv); + + char *inpath = mkstr(path); + char *outpath = realpath(inpath, NULL); + free(inpath); + + if (!outpath) { + uss_error(uss, reply); + } else { + fxp_reply_simple_name(reply, ptrlen_from_asciz(outpath)); + free(outpath); + } +} + +static void uss_open(SftpServer *srv, SftpReplyBuilder *reply, + ptrlen path, unsigned flags, struct fxp_attrs attrs) +{ + UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv); + + int openflags = 0; + if (!((SSH_FXF_READ | SSH_FXF_WRITE) &~ flags)) + openflags |= O_RDWR; + else if (flags & SSH_FXF_WRITE) + openflags |= O_WRONLY; + else if (flags & SSH_FXF_READ) + openflags |= O_RDONLY; + if (flags & SSH_FXF_APPEND) + openflags |= O_APPEND; + if (flags & SSH_FXF_CREAT) + openflags |= O_CREAT; + if (flags & SSH_FXF_TRUNC) + openflags |= O_TRUNC; + if (flags & SSH_FXF_EXCL) + openflags |= O_EXCL; + + char *pathstr = mkstr(path); + int fd = open(pathstr, openflags, GET_PERMISSIONS(attrs, 0777)); + free(pathstr); + + if (fd < 0) { + uss_error(uss, reply); + } else { + uss_return_new_handle(uss, reply, fd); + } +} + +static void uss_opendir(SftpServer *srv, SftpReplyBuilder *reply, + ptrlen path) +{ + UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv); + + char *pathstr = mkstr(path); + DIR *dp = opendir(pathstr); + free(pathstr); + + if (!dp) { + uss_error(uss, reply); + } else { + uss_return_new_dirhandle(uss, reply, dp); + } +} + +static void uss_close(SftpServer *srv, SftpReplyBuilder *reply, + ptrlen handle) +{ + UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv); + int fd; + struct uss_dirhandle *udh; + + if ((udh = uss_try_lookup_dirhandle(uss, handle)) != NULL) { + closedir(udh->dp); + del234(uss->dirhandles, udh); + sfree(udh); + fxp_reply_ok(reply); + } else if ((fd = uss_lookup_fd(uss, reply, handle)) >= 0) { + close(fd); + assert(0 <= fd && fd <= uss->fdsize); + uss->fdsopen[fd] = false; + fxp_reply_ok(reply); + } + /* if both failed, uss_lookup_fd will have filled in an error response */ +} + +static void uss_mkdir(SftpServer *srv, SftpReplyBuilder *reply, + ptrlen path, struct fxp_attrs attrs) +{ + UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv); + + char *pathstr = mkstr(path); + int status = mkdir(pathstr, GET_PERMISSIONS(attrs, 0777)); + free(pathstr); + + if (status < 0) { + uss_error(uss, reply); + } else { + fxp_reply_ok(reply); + } +} + +static void uss_rmdir(SftpServer *srv, SftpReplyBuilder *reply, ptrlen path) +{ + UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv); + + char *pathstr = mkstr(path); + int status = rmdir(pathstr); + free(pathstr); + + if (status < 0) { + uss_error(uss, reply); + } else { + fxp_reply_ok(reply); + } +} + +static void uss_remove(SftpServer *srv, SftpReplyBuilder *reply, + ptrlen path) +{ + UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv); + + char *pathstr = mkstr(path); + int status = unlink(pathstr); + free(pathstr); + + if (status < 0) { + uss_error(uss, reply); + } else { + fxp_reply_ok(reply); + } +} + +static void uss_rename(SftpServer *srv, SftpReplyBuilder *reply, + ptrlen srcpath, ptrlen dstpath) +{ + UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv); + + char *srcstr = mkstr(srcpath), *dststr = mkstr(dstpath); + int status = rename(srcstr, dststr); + free(srcstr); + free(dststr); + + if (status < 0) { + uss_error(uss, reply); + } else { + fxp_reply_ok(reply); + } +} + +static struct fxp_attrs uss_translate_struct_stat(const struct stat *st) +{ + struct fxp_attrs attrs; + + attrs.flags = (SSH_FILEXFER_ATTR_SIZE | + SSH_FILEXFER_ATTR_PERMISSIONS | + SSH_FILEXFER_ATTR_UIDGID | + SSH_FILEXFER_ATTR_ACMODTIME); + + attrs.size = st->st_size; + attrs.permissions = st->st_mode; + attrs.uid = st->st_uid; + attrs.gid = st->st_gid; + attrs.atime = st->st_atime; + attrs.mtime = st->st_mtime; + + return attrs; +} + +static void uss_reply_struct_stat(SftpReplyBuilder *reply, + const struct stat *st) +{ + fxp_reply_attrs(reply, uss_translate_struct_stat(st)); +} + +static void uss_stat(SftpServer *srv, SftpReplyBuilder *reply, + ptrlen path, bool follow_symlinks) +{ + UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv); + struct stat st; + + char *pathstr = mkstr(path); + int status = (follow_symlinks ? stat : lstat) (pathstr, &st); + free(pathstr); + + if (status < 0) { + uss_error(uss, reply); + } else { + uss_reply_struct_stat(reply, &st); + } +} + +static void uss_fstat(SftpServer *srv, SftpReplyBuilder *reply, + ptrlen handle) +{ + UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv); + struct stat st; + int fd; + + if ((fd = uss_lookup_fd(uss, reply, handle)) < 0) + return; + int status = fstat(fd, &st); + + if (status < 0) { + uss_error(uss, reply); + } else { + uss_reply_struct_stat(reply, &st); + } +} + +#if !HAVE_FUTIMES +static inline int futimes(int fd, const struct timeval tv[2]) +{ + /* If the OS doesn't support futimes(3) then we have to pretend it + * always returns failure */ + errno = EINVAL; + return -1; +} +#endif + +/* + * The guts of setstat and fsetstat, macroised so that they can call + * fchown(fd,...) or chown(path,...) depending on parameters. + */ +#define SETSTAT_GUTS(api_prefix, api_arg, attrs, success) do \ + { \ + if (attrs.flags & SSH_FILEXFER_ATTR_SIZE) \ + if (api_prefix(truncate)(api_arg, attrs.size) < 0) \ + success = false; \ + if (attrs.flags & SSH_FILEXFER_ATTR_UIDGID) \ + if (api_prefix(chown)(api_arg, attrs.uid, attrs.gid) < 0) \ + success = false; \ + if (attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) \ + if (api_prefix(chmod)(api_arg, attrs.permissions) < 0) \ + success = false; \ + if (attrs.flags & SSH_FILEXFER_ATTR_ACMODTIME) { \ + struct timeval tv[2]; \ + tv[0].tv_sec = attrs.atime; \ + tv[1].tv_sec = attrs.mtime; \ + tv[0].tv_usec = tv[1].tv_usec = 0; \ + if (api_prefix(utimes)(api_arg, tv) < 0) \ + success = false; \ + } \ + } while (0) + +#define PATH_PREFIX(func) func +#define FD_PREFIX(func) f ## func + +static void uss_setstat(SftpServer *srv, SftpReplyBuilder *reply, + ptrlen path, struct fxp_attrs attrs) +{ + UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv); + + char *pathstr = mkstr(path); + bool success = true; + SETSTAT_GUTS(PATH_PREFIX, pathstr, attrs, success); + free(pathstr); + + if (!success) { + uss_error(uss, reply); + } else { + fxp_reply_ok(reply); + } +} + +static void uss_fsetstat(SftpServer *srv, SftpReplyBuilder *reply, + ptrlen handle, struct fxp_attrs attrs) +{ + UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv); + int fd; + + if ((fd = uss_lookup_fd(uss, reply, handle)) < 0) + return; + + bool success = true; + SETSTAT_GUTS(FD_PREFIX, fd, attrs, success); + + if (!success) { + uss_error(uss, reply); + } else { + fxp_reply_ok(reply); + } +} + +static void uss_read(SftpServer *srv, SftpReplyBuilder *reply, + ptrlen handle, uint64_t offset, unsigned length) +{ + UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv); + int fd; + char *buf; + + if ((fd = uss_lookup_fd(uss, reply, handle)) < 0) + return; + + if ((buf = malloc(length)) == NULL) { + /* A rare case in which I bother to check malloc failure, + * because in this case we can localise the problem easily by + * turning it into a failure response from this one sftp + * request */ + fxp_reply_error(reply, SSH_FX_FAILURE, + "Out of memory for read buffer"); + return; + } + + char *p = buf; + + int status = lseek(fd, offset, SEEK_SET); + if (status >= 0 || errno == ESPIPE) { + bool seekable = (status >= 0); + while (length > 0) { + status = read(fd, p, length); + if (status <= 0) + break; + + unsigned bytes_read = status; + assert(bytes_read <= length); + length -= bytes_read; + p += bytes_read; + + if (!seekable) { + /* + * If the seek failed because the file is fundamentally + * not a seekable kind of thing, abandon this loop after + * one attempt, i.e. we just read whatever we could get + * and we don't mind returning a short buffer. + */ + } + } + } + + if (status < 0) { + uss_error(uss, reply); + } else if (p == buf) { + fxp_reply_error(reply, SSH_FX_EOF, "End of file"); + } else { + fxp_reply_data(reply, make_ptrlen(buf, p - buf)); + } + + free(buf); +} + +static void uss_write(SftpServer *srv, SftpReplyBuilder *reply, + ptrlen handle, uint64_t offset, ptrlen data) +{ + UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv); + int fd; + + if ((fd = uss_lookup_fd(uss, reply, handle)) < 0) + return; + + const char *p = data.ptr; + unsigned length = data.len; + + int status = lseek(fd, offset, SEEK_SET); + if (status >= 0 || errno == ESPIPE) { + + while (length > 0) { + status = write(fd, p, length); + assert(status != 0); + if (status < 0) + break; + + unsigned bytes_written = status; + assert(bytes_written <= length); + length -= bytes_written; + p += bytes_written; + } + } + + if (status < 0) { + uss_error(uss, reply); + } else { + fxp_reply_ok(reply); + } +} + +static void uss_readdir(SftpServer *srv, SftpReplyBuilder *reply, + ptrlen handle, int max_entries, bool omit_longname) +{ + UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv); + struct dirent *de; + struct uss_dirhandle *udh; + + if ((udh = uss_lookup_dirhandle(uss, reply, handle)) == NULL) + return; + + errno = 0; + de = readdir(udh->dp); + if (!de) { + if (errno == 0) { + fxp_reply_error(reply, SSH_FX_EOF, "End of directory"); + } else { + uss_error(uss, reply); + } + } else { + ptrlen longname = PTRLEN_LITERAL(""); + char *longnamebuf = NULL; + struct fxp_attrs attrs = no_attrs; + +#if defined HAVE_FSTATAT && defined HAVE_DIRFD + struct stat st; + if (!fstatat(dirfd(udh->dp), de->d_name, &st, AT_SYMLINK_NOFOLLOW)) { + char perms[11], *uidbuf = NULL, *gidbuf = NULL; + struct passwd *pwd; + struct group *grp; + const char *user, *group; + struct tm tm; + + attrs = uss_translate_struct_stat(&st); + + if (!omit_longname) { + + strcpy(perms, "----------"); + switch (st.st_mode & S_IFMT) { + case S_IFBLK: perms[0] = 'b'; break; + case S_IFCHR: perms[0] = 'c'; break; + case S_IFDIR: perms[0] = 'd'; break; + case S_IFIFO: perms[0] = 'p'; break; + case S_IFLNK: perms[0] = 'l'; break; + case S_IFSOCK: perms[0] = 's'; break; + } + if (st.st_mode & S_IRUSR) + perms[1] = 'r'; + if (st.st_mode & S_IWUSR) + perms[2] = 'w'; + if (st.st_mode & S_IXUSR) + perms[3] = (st.st_mode & S_ISUID ? 's' : 'x'); + else + perms[3] = (st.st_mode & S_ISUID ? 'S' : '-'); + if (st.st_mode & S_IRGRP) + perms[4] = 'r'; + if (st.st_mode & S_IWGRP) + perms[5] = 'w'; + if (st.st_mode & S_IXGRP) + perms[6] = (st.st_mode & S_ISGID ? 's' : 'x'); + else + perms[6] = (st.st_mode & S_ISGID ? 'S' : '-'); + if (st.st_mode & S_IROTH) + perms[7] = 'r'; + if (st.st_mode & S_IWOTH) + perms[8] = 'w'; + if (st.st_mode & S_IXOTH) + perms[9] = 'x'; + + if ((pwd = getpwuid(st.st_uid)) != NULL) + user = pwd->pw_name; + else + user = uidbuf = dupprintf("%u", (unsigned)st.st_uid); + if ((grp = getgrgid(st.st_gid)) != NULL) + group = grp->gr_name; + else + group = gidbuf = dupprintf("%u", (unsigned)st.st_gid); + + tm = *localtime(&st.st_mtime); + + longnamebuf = dupprintf( + "%s %3u %-8s %-8s %8"PRIuMAX" %.3s %2d %02d:%02d %s", + perms, (unsigned)st.st_nlink, user, group, + (uintmax_t)st.st_size, + (&"JanFebMarAprMayJunJulAugSepOctNovDec"[3*tm.tm_mon]), + tm.tm_mday, tm.tm_hour, tm.tm_min, de->d_name); + longname = ptrlen_from_asciz(longnamebuf); + + sfree(uidbuf); + sfree(gidbuf); + } + } +#endif + + /* FIXME: be able to return more than one, in which case we + * must also check max_entries */ + fxp_reply_name_count(reply, 1); + fxp_reply_full_name(reply, ptrlen_from_asciz(de->d_name), + longname, attrs); + + sfree(longnamebuf); + } +} + +const SftpServerVtable unix_live_sftpserver_vt = { + .new = uss_new, + .free = uss_free, + .realpath = uss_realpath, + .open = uss_open, + .opendir = uss_opendir, + .close = uss_close, + .mkdir = uss_mkdir, + .rmdir = uss_rmdir, + .remove = uss_remove, + .rename = uss_rename, + .stat = uss_stat, + .fstat = uss_fstat, + .setstat = uss_setstat, + .fsetstat = uss_fsetstat, + .read = uss_read, + .write = uss_write, + .readdir = uss_readdir, +}; diff --git a/unix/sharing.c b/unix/sharing.c new file mode 100644 index 00000000..f1ef2019 --- /dev/null +++ b/unix/sharing.c @@ -0,0 +1,370 @@ +/* + * Unix implementation of SSH connection-sharing IPC setup. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "tree234.h" +#include "putty.h" +#include "network.h" +#include "proxy.h" +#include "ssh.h" + +#define CONNSHARE_SOCKETDIR_PREFIX "/tmp/putty-connshare" +#define SALT_FILENAME "salt" +#define SALT_SIZE 64 +#ifndef PIPE_BUF +#define PIPE_BUF _POSIX_PIPE_BUF +#endif + +static char *make_parentdir_name(void) +{ + char *username, *parent; + + username = get_username(); + parent = dupprintf("%s.%s", CONNSHARE_SOCKETDIR_PREFIX, username); + sfree(username); + assert(*parent == '/'); + + return parent; +} + +static char *make_dirname(const char *pi_name, char **logtext) +{ + char *name, *parentdirname, *dirname, *err; + + /* + * First, create the top-level directory for all shared PuTTY + * connections owned by this user. + */ + parentdirname = make_parentdir_name(); + if ((err = make_dir_and_check_ours(parentdirname)) != NULL) { + *logtext = err; + sfree(parentdirname); + return NULL; + } + + /* + * Transform the platform-independent version of the connection + * identifier into the name we'll actually use for the directory + * containing the Unix socket. + * + * We do this by hashing the identifier with some user-specific + * secret information, to avoid the privacy leak of having + * "user@host" strings show up in 'netstat -x'. (Irritatingly, the + * full pathname of a Unix-domain socket _does_ show up in the + * 'netstat -x' output, at least on Linux, even if that socket is + * in a directory not readable to the user running netstat. You'd + * think putting things inside an 0700 directory would hide their + * names from other users, but no.) + * + * The secret information we use to salt the hash lives in a file + * inside the top-level directory we just created, so we must + * first create that file (with some fresh random data in it) if + * it's not already been done by a previous PuTTY. + */ + { + unsigned char saltbuf[SALT_SIZE]; + char *saltname; + int saltfd, i, ret; + + saltname = dupprintf("%s/%s", parentdirname, SALT_FILENAME); + saltfd = open(saltname, O_RDONLY); + if (saltfd < 0) { + char *tmpname; + int pid; + + if (errno != ENOENT) { + *logtext = dupprintf("%s: open: %s", saltname, + strerror(errno)); + sfree(saltname); + sfree(parentdirname); + return NULL; + } + + /* + * The salt file doesn't already exist, so try to create + * it. Another process may be attempting the same thing + * simultaneously, so we must do this carefully: we write + * a salt file under a different name, then hard-link it + * into place, which guarantees that we won't change the + * contents of an existing salt file. + */ + pid = getpid(); + for (i = 0;; i++) { + tmpname = dupprintf("%s/%s.tmp.%d.%d", + parentdirname, SALT_FILENAME, pid, i); + saltfd = open(tmpname, O_WRONLY | O_EXCL | O_CREAT, 0400); + if (saltfd >= 0) + break; + if (errno != EEXIST) { + *logtext = dupprintf("%s: open: %s", tmpname, + strerror(errno)); + sfree(tmpname); + sfree(saltname); + sfree(parentdirname); + return NULL; + } + sfree(tmpname); /* go round and try again with i+1 */ + } + /* + * Invent some random data. + */ + random_read(saltbuf, SALT_SIZE); + ret = write(saltfd, saltbuf, SALT_SIZE); + /* POSIX atomicity guarantee: because we wrote less than + * PIPE_BUF bytes, the write either completed in full or + * failed. */ + assert(SALT_SIZE < PIPE_BUF); + assert(ret < 0 || ret == SALT_SIZE); + if (ret < 0) { + close(saltfd); + *logtext = dupprintf("%s: write: %s", tmpname, + strerror(errno)); + sfree(tmpname); + sfree(saltname); + sfree(parentdirname); + return NULL; + } + if (close(saltfd) < 0) { + *logtext = dupprintf("%s: close: %s", tmpname, + strerror(errno)); + sfree(tmpname); + sfree(saltname); + sfree(parentdirname); + return NULL; + } + + /* + * Now attempt to hard-link our temp file into place. We + * tolerate EEXIST as an outcome, because that just means + * another PuTTY got their attempt in before we did (and + * we only care that there is a valid salt file we can + * agree on, no matter who created it). + */ + if (link(tmpname, saltname) < 0 && errno != EEXIST) { + *logtext = dupprintf("%s: link: %s", saltname, + strerror(errno)); + sfree(tmpname); + sfree(saltname); + sfree(parentdirname); + return NULL; + } + + /* + * Whether that succeeded or not, get rid of our temp file. + */ + if (unlink(tmpname) < 0) { + *logtext = dupprintf("%s: unlink: %s", tmpname, + strerror(errno)); + sfree(tmpname); + sfree(saltname); + sfree(parentdirname); + return NULL; + } + + /* + * And now we've arranged for there to be a salt file, so + * we can try to open it for reading again and this time + * expect it to work. + */ + sfree(tmpname); + + saltfd = open(saltname, O_RDONLY); + if (saltfd < 0) { + *logtext = dupprintf("%s: open: %s", saltname, + strerror(errno)); + sfree(saltname); + sfree(parentdirname); + return NULL; + } + } + + for (i = 0; i < SALT_SIZE; i++) { + ret = read(saltfd, saltbuf, SALT_SIZE); + if (ret <= 0) { + close(saltfd); + *logtext = dupprintf("%s: read: %s", saltname, + ret == 0 ? "unexpected EOF" : + strerror(errno)); + sfree(saltname); + sfree(parentdirname); + return NULL; + } + assert(0 < ret && ret <= SALT_SIZE - i); + i += ret; + } + + close(saltfd); + sfree(saltname); + + /* + * Now we've got our salt, hash it with the connection + * identifier to produce our actual socket name. + */ + { + unsigned char digest[32]; + char retbuf[65]; + + ssh_hash *h = ssh_hash_new(&ssh_sha256); + put_string(h, saltbuf, SALT_SIZE); + put_stringz(h, pi_name); + ssh_hash_final(h, digest); + + /* + * And make it printable. + */ + for (i = 0; i < 32; i++) { + sprintf(retbuf + 2*i, "%02x", digest[i]); + /* the last of those will also write the trailing NUL */ + } + + name = dupstr(retbuf); + } + + smemclr(saltbuf, sizeof(saltbuf)); + } + + dirname = dupprintf("%s/%s", parentdirname, name); + sfree(parentdirname); + sfree(name); + + return dirname; +} + +int platform_ssh_share(const char *pi_name, Conf *conf, + Plug *downplug, Plug *upplug, Socket **sock, + char **logtext, char **ds_err, char **us_err, + bool can_upstream, bool can_downstream) +{ + char *dirname, *lockname, *sockname, *err; + int lockfd; + Socket *retsock; + + /* + * Sort out what we're going to call the directory in which we + * keep the socket. This has the side effect of potentially + * creating its top-level containing dir and/or the salt file + * within that, if they don't already exist. + */ + dirname = make_dirname(pi_name, logtext); + if (!dirname) { + return SHARE_NONE; + } + + /* + * Now make sure the subdirectory exists. + */ + if ((err = make_dir_and_check_ours(dirname)) != NULL) { + *logtext = err; + sfree(dirname); + return SHARE_NONE; + } + + /* + * Acquire a lock on a file in that directory. + */ + lockname = dupcat(dirname, "/lock"); + lockfd = open(lockname, O_CREAT | O_RDWR | O_TRUNC, 0600); + if (lockfd < 0) { + *logtext = dupprintf("%s: open: %s", lockname, strerror(errno)); + sfree(dirname); + sfree(lockname); + return SHARE_NONE; + } + if (flock(lockfd, LOCK_EX) < 0) { + *logtext = dupprintf("%s: flock(LOCK_EX): %s", + lockname, strerror(errno)); + sfree(dirname); + sfree(lockname); + close(lockfd); + return SHARE_NONE; + } + + sockname = dupprintf("%s/socket", dirname); + + *logtext = NULL; + + if (can_downstream) { + retsock = new_connection(unix_sock_addr(sockname), + "", 0, false, true, false, false, + downplug, conf); + if (sk_socket_error(retsock) == NULL) { + sfree(*logtext); + *logtext = sockname; + *sock = retsock; + sfree(dirname); + sfree(lockname); + close(lockfd); + return SHARE_DOWNSTREAM; + } + sfree(*ds_err); + *ds_err = dupprintf("%s: %s", sockname, sk_socket_error(retsock)); + sk_close(retsock); + } + + if (can_upstream) { + retsock = new_unix_listener(unix_sock_addr(sockname), upplug); + if (sk_socket_error(retsock) == NULL) { + sfree(*logtext); + *logtext = sockname; + *sock = retsock; + sfree(dirname); + sfree(lockname); + close(lockfd); + return SHARE_UPSTREAM; + } + sfree(*us_err); + *us_err = dupprintf("%s: %s", sockname, sk_socket_error(retsock)); + sk_close(retsock); + } + + /* One of the above clauses ought to have happened. */ + assert(*logtext || *ds_err || *us_err); + + sfree(dirname); + sfree(lockname); + sfree(sockname); + close(lockfd); + return SHARE_NONE; +} + +void platform_ssh_share_cleanup(const char *name) +{ + char *dirname, *filename, *logtext; + + dirname = make_dirname(name, &logtext); + if (!dirname) { + sfree(logtext); /* we can't do much with this */ + return; + } + + filename = dupcat(dirname, "/socket"); + remove(filename); + sfree(filename); + + filename = dupcat(dirname, "/lock"); + remove(filename); + sfree(filename); + + rmdir(dirname); + + /* + * We deliberately _don't_ clean up the parent directory + * /tmp/putty-connshare., because if we leave it around + * then it reduces the ability for other users to be a nuisance by + * putting their own directory in the way of it. Also, the salt + * file in it can be reused. + */ + + sfree(dirname); +} diff --git a/unix/storage.c b/unix/storage.c new file mode 100644 index 00000000..9db713d1 --- /dev/null +++ b/unix/storage.c @@ -0,0 +1,822 @@ +/* + * uxstore.c: Unix-specific implementation of the interface defined + * in storage.h. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 void make_session_filename(const char *in, strbuf *out) +{ + if (!in || !*in) + in = "Default Settings"; + + 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')) { + put_byte(out, '%'); + put_byte(out, hex[((unsigned char) *in) >> 4]); + put_byte(out, hex[((unsigned char) *in) & 15]); + } else + put_byte(out, *in); + in++; + } +} + +static void decode_session_filename(const char *in, strbuf *out) +{ + 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); + + put_byte(out, (i << 4) + j); + in += 3; + } else { + put_byte(out, *in++); + } + } +} + +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; + char *xdg_dir, *old_dir, *old_dir2, *old_dir3, *home, *pwd_home; + + env = getenv("PUTTYDIR"); + if (env) + return dupstr(env); + + home = getenv("HOME"); + pwd = getpwuid(getuid()); + if (pwd && pwd->pw_dir) { + pwd_home = pwd->pw_dir; + } else { + pwd_home = NULL; + } + + xdg_dir = NULL; + env = getenv("XDG_CONFIG_HOME"); + if (env && *env) { + xdg_dir = dupprintf("%s/putty", env); + } + if (!xdg_dir) { + if (home) { + tmp = home; + } else if (pwd_home) { + tmp = pwd_home; + } else { + tmp = ""; + } + xdg_dir = dupprintf("%s/.config/putty", tmp); + } + if (xdg_dir && access(xdg_dir, F_OK) == 0) { + return xdg_dir; + } + + old_dir = old_dir2 = old_dir3 = NULL; + if (home) { + old_dir = dupprintf("%s/.putty", home); + } + if (pwd_home) { + old_dir2 = dupprintf("%s/.putty", pwd_home); + } + old_dir3 = dupstr("/.putty"); + + if (old_dir && access(old_dir, F_OK) == 0) { + ret = old_dir; + goto out; + } + if (old_dir2 && access(old_dir2, F_OK) == 0) { + ret = old_dir2; + goto out; + } + if (access(old_dir3, F_OK) == 0) { + ret = old_dir3; + goto out; + } +#ifdef XDG_DEFAULT + if (xdg_dir) { + ret = xdg_dir; + goto out; + } +#endif + ret = old_dir ? old_dir : (old_dir2 ? old_dir2 : old_dir3); + + out: + if (ret != old_dir) + sfree(old_dir); + if (ret != old_dir2) + sfree(old_dir2); + if (ret != old_dir3) + sfree(old_dir3); + if (ret != xdg_dir) + sfree(xdg_dir); + return ret; + } + 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) { + strbuf *sb = strbuf_new(); + tmp = make_filename(INDEX_SESSIONDIR, NULL); + strbuf_catf(sb, "%s/", tmp); + sfree(tmp); + make_session_filename(subname, sb); + return strbuf_to_str(sb); + } + 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; +} + +struct settings_w { + FILE *fp; +}; + +settings_w *open_settings_w(const char *sessionname, char **errmsg) +{ + char *filename, *err; + FILE *fp; + + *errmsg = NULL; + + /* + * Start by making sure the .putty directory and its sessions + * subdir actually exist. + */ + filename = make_filename(INDEX_DIR, NULL); + if ((err = make_dir_path(filename, 0700)) != NULL) { + *errmsg = dupprintf("Unable to save session: %s", err); + sfree(err); + sfree(filename); + return NULL; + } + sfree(filename); + + filename = make_filename(INDEX_SESSIONDIR, NULL); + if ((err = make_dir_path(filename, 0700)) != NULL) { + *errmsg = dupprintf("Unable to save session: %s", err); + sfree(err); + sfree(filename); + return NULL; + } + sfree(filename); + + filename = make_filename(INDEX_SESSION, sessionname); + fp = fopen(filename, "w"); + if (!fp) { + *errmsg = dupprintf("Unable to save session: open(\"%s\") " + "returned '%s'", filename, strerror(errno)); + sfree(filename); + return NULL; /* can't open */ + } + sfree(filename); + + settings_w *toret = snew(settings_w); + toret->fp = fp; + return toret; +} + +void write_setting_s(settings_w *handle, const char *key, const char *value) +{ + fprintf(handle->fp, "%s=%s\n", key, value); +} + +void write_setting_i(settings_w *handle, const char *key, int value) +{ + fprintf(handle->fp, "%s=%d\n", key, value); +} + +void close_settings_w(settings_w *handle) +{ + fclose(handle->fp); + sfree(handle); +} + +/* ---------------------------------------------------------------------- + * System for treating X resources as a fallback source of defaults, + * after data read from a saved-session disk file. + * + * The read_setting_* functions will call get_setting(key) as a + * fallback if the setting isn't in the file they loaded. That in turn + * will hand on to x_get_default, which the front end application + * provides, and which actually reads resources from the X server (if + * appropriate). In between, there's a tree234 of X-resource shaped + * settings living locally in this file: the front end can call + * provide_xrm_string() to insert a setting into this tree (typically + * in response to an -xrm command line option or similar), and those + * will override the actual X resources. + */ + +struct skeyval { + const char *key; + const char *value; +}; + +static tree234 *xrmtree = NULL; + +static 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(const char *string, const char *progname) +{ + const char *p, *q; + char *key; + struct skeyval *xrms, *ret; + + p = q = strchr(string, ':'); + if (!q) { + fprintf(stderr, "%s: expected a colon in resource string" + " \"%s\"\n", progname, 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); + } +} + +static 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); +} + +/* ---------------------------------------------------------------------- + * Main code for reading settings from a disk file, calling the above + * get_setting() as a fallback if necessary. + */ + +struct settings_r { + tree234 *t; +}; + +settings_r *open_settings_r(const char *sessionname) +{ + char *filename; + FILE *fp; + char *line; + settings_r *toret; + + filename = make_filename(INDEX_SESSION, sessionname); + fp = fopen(filename, "r"); + sfree(filename); + if (!fp) + return NULL; /* can't open */ + + toret = snew(settings_r); + toret->t = newtree234(keycmp); + + while ( (line = fgetline(fp)) ) { + char *value = strchr(line, '='); + struct skeyval *kv; + + if (!value) { + sfree(line); + 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(toret->t, kv); + + sfree(line); + } + + fclose(fp); + + return toret; +} + +char *read_setting_s(settings_r *handle, const char *key) +{ + const char *val; + struct skeyval tmp, *kv; + + tmp.key = key; + if (handle != NULL && + (kv = find234(handle->t, &tmp, NULL)) != NULL) { + val = kv->value; + assert(val != NULL); + } else + val = get_setting(key); + + if (!val) + return NULL; + else + return dupstr(val); +} + +int read_setting_i(settings_r *handle, const char *key, int defvalue) +{ + const char *val; + struct skeyval tmp, *kv; + + tmp.key = key; + if (handle != NULL && + (kv = find234(handle->t, &tmp, NULL)) != NULL) { + val = kv->value; + assert(val != NULL); + } else + val = get_setting(key); + + if (!val) + return defvalue; + else + return atoi(val); +} + +FontSpec *read_setting_fontspec(settings_r *handle, const char *name) +{ + /* + * In GTK1-only PuTTY, we used to store font names simply as a + * valid X font description string (logical or alias), under a + * bare key such as "Font". + * + * 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"); + char *tmp; + + if ((tmp = read_setting_s(handle, suffname)) != NULL) { + FontSpec *fs = fontspec_new(tmp); + sfree(suffname); + sfree(tmp); + return fs; /* got new-style name */ + } + sfree(suffname); + + /* Fall back to old-style name. */ + tmp = read_setting_s(handle, name); + if (tmp && *tmp) { + char *tmp2 = dupcat("server:", tmp); + FontSpec *fs = fontspec_new(tmp2); + sfree(tmp2); + sfree(tmp); + return fs; + } else { + sfree(tmp); + return NULL; + } +} +Filename *read_setting_filename(settings_r *handle, const char *name) +{ + char *tmp = read_setting_s(handle, name); + if (tmp) { + Filename *ret = filename_from_str(tmp); + sfree(tmp); + return ret; + } else + return NULL; +} + +void write_setting_fontspec(settings_w *handle, const char *name, FontSpec *fs) +{ + /* + * read_setting_fontspec had to handle two cases, but when + * writing our settings back out we simply always generate the + * new-style name. + */ + char *suffname = dupcat(name, "Name"); + write_setting_s(handle, suffname, fs->name); + sfree(suffname); +} +void write_setting_filename(settings_w *handle, + const char *name, Filename *result) +{ + write_setting_s(handle, name, result->path); +} + +void close_settings_r(settings_r *handle) +{ + struct skeyval *kv; + + if (!handle) + return; + + while ( (kv = index234(handle->t, 0)) != NULL) { + del234(handle->t, kv); + sfree((char *)kv->key); + sfree((char *)kv->value); + sfree(kv); + } + + freetree234(handle->t); + sfree(handle); +} + +void del_settings(const char *sessionname) +{ + char *filename; + filename = make_filename(INDEX_SESSION, sessionname); + unlink(filename); + sfree(filename); +} + +struct settings_e { + DIR *dp; +}; + +settings_e *enum_settings_start(void) +{ + DIR *dp; + char *filename; + + filename = make_filename(INDEX_SESSIONDIR, NULL); + dp = opendir(filename); + sfree(filename); + + settings_e *toret = snew(settings_e); + toret->dp = dp; + return toret; +} + +bool enum_settings_next(settings_e *handle, strbuf *out) +{ + struct dirent *de; + struct stat st; + strbuf *fullpath; + + if (!handle->dp) + return NULL; + + fullpath = strbuf_new(); + + char *sessiondir = make_filename(INDEX_SESSIONDIR, NULL); + put_datapl(fullpath, ptrlen_from_asciz(sessiondir)); + sfree(sessiondir); + put_byte(fullpath, '/'); + + size_t baselen = fullpath->len; + + while ( (de = readdir(handle->dp)) != NULL ) { + strbuf_shrink_to(fullpath, baselen); + put_datapl(fullpath, ptrlen_from_asciz(de->d_name)); + + if (stat(fullpath->s, &st) < 0 || !S_ISREG(st.st_mode)) + continue; /* try another one */ + + decode_session_filename(de->d_name, out); + strbuf_free(fullpath); + return true; + } + + strbuf_free(fullpath); + return false; +} + +void enum_settings_finish(settings_e *handle) +{ + if (handle->dp) + closedir(handle->dp); + sfree(handle); +} + +/* + * 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; +} + +bool have_ssh_host_key(const char *hostname, int port, + const char *keytype) +{ + /* + * If we have a host key, verify_host_key will return 0 or 2. + * If we don't have one, it'll return 1. + */ + return verify_host_key(hostname, port, keytype, "") != 1; +} + +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; + + /* + * Open both the old file and a new file. + */ + tmpfilename = make_filename(INDEX_HOSTKEYS_TMP, NULL); + wfp = fopen(tmpfilename, "w"); + if (!wfp && errno == ENOENT) { + char *dir, *errmsg; + + dir = make_filename(INDEX_DIR, NULL); + if ((errmsg = make_dir_path(dir, 0700)) != NULL) { + nonfatal("Unable to store host key: %s", errmsg); + sfree(errmsg); + sfree(dir); + sfree(tmpfilename); + return; + } + sfree(dir); + + wfp = fopen(tmpfilename, "w"); + } + if (!wfp) { + nonfatal("Unable to store host key: open(\"%s\") " + "returned '%s'", tmpfilename, strerror(errno)); + sfree(tmpfilename); + return; + } + filename = make_filename(INDEX_HOSTKEYS, NULL); + rfp = fopen(filename, "r"); + + newtext = dupprintf("%s@%d:%s %s\n", keytype, port, hostname, key); + headerlen = 1 + strcspn(newtext, " "); /* count the space too */ + + /* + * Copy all lines from the old file to the new one that _don't_ + * involve the same host key identifier as the one we're adding. + */ + if (rfp) { + while ( (line = fgetline(rfp)) ) { + if (strncmp(line, newtext, headerlen)) + fputs(line, wfp); + sfree(line); + } + fclose(rfp); + } + + /* + * Now add the new line at the end. + */ + fputs(newtext, wfp); + + fclose(wfp); + + if (rename(tmpfilename, filename) < 0) { + nonfatal("Unable to store host key: rename(\"%s\",\"%s\")" + " returned '%s'", tmpfilename, filename, + strerror(errno)); + } + + 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) { + if (errno != ENOENT) { + nonfatal("Unable to write random seed: open(\"%s\") " + "returned '%s'", fname, strerror(errno)); + sfree(fname); + return; + } + char *dir, *errmsg; + + dir = make_filename(INDEX_DIR, NULL); + if ((errmsg = make_dir_path(dir, 0700)) != NULL) { + nonfatal("Unable to write random seed: %s", errmsg); + sfree(errmsg); + sfree(fname); + sfree(dir); + return; + } + sfree(dir); + + fd = open(fname, O_CREAT | O_WRONLY, 0600); + if (fd < 0) { + nonfatal("Unable to write random seed: open(\"%s\") " + "returned '%s'", fname, strerror(errno)); + sfree(fname); + return; + } + } + + while (len > 0) { + int ret = write(fd, data, len); + if (ret < 0) { + nonfatal("Unable to write random seed: write " + "returned '%s'", strerror(errno)); + break; + } + len -= ret; + data = (char *)data + len; + } + + close(fd); + sfree(fname); +} + +void cleanup_all(void) +{ +} diff --git a/unix/unicode.c b/unix/unicode.c new file mode 100644 index 00000000..c1d76a42 --- /dev/null +++ b/unix/unicode.c @@ -0,0 +1,268 @@ +#include +#include +#include +#include +#include +#include + +#include + +#include "putty.h" +#include "charset.h" +#include "terminal.h" +#include "misc.h" + +/* + * Unix Unicode-handling routines. + */ + +bool is_dbcs_leadbyte(int codepage, char byte) +{ + return false; /* we don't do DBCS */ +} + +int mb_to_wc(int codepage, int flags, const char *mbstr, int mblen, + wchar_t *wcstr, int wclen) +{ + if (codepage == DEFAULT_CODEPAGE) { + int n = 0; + mbstate_t state; + + memset(&state, 0, sizeof state); + + 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; + } + + 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, const wchar_t *wcstr, int wclen, + char *mbstr, int mblen, const char *defchr, + struct unicode_data *ucsdata) +{ + if (codepage == DEFAULT_CODEPAGE) { + char output[MB_LEN_MAX]; + mbstate_t state; + int n = 0; + + memset(&state, 0, sizeof state); + + while (wclen > 0) { + size_t i = wcrtomb(output, wcstr[0], &state); + if (i == (size_t)-1 || i > n - mblen) + break; + memcpy(mbstr+n, output, i); + n += i; + wcstr++; + wclen--; + } + + 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. + */ +bool init_ucs(struct unicode_data *ucsdata, char *linecharset, + bool utf8_override, int font_charset, int vtmode) +{ + int i; + bool ret = false; + + /* + * 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 conf. + */ + 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 = true; + + /* + * Set up unitab_line, by translating each individual character + * in the line codepage into Unicode. + */ + for (i = 0; i < 256; i++) { + char c[1]; + const char *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]; + const char *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; + charset = charset_localenc_nth(index); + if (charset == CS_NONE) { + /* "Use font encoding" comes after all the named charsets */ + if (charset_localenc_nth(index-1) != CS_NONE) + return "Use font encoding"; + return NULL; + } + return charset_to_localenc(charset); +} + +int decode_codepage(char *cp_name) +{ + if (!cp_name || !*cp_name) + return CS_UTF8; + return charset_from_localenc(cp_name); +} diff --git a/unix/unifont.c b/unix/unifont.c new file mode 100644 index 00000000..d50a7810 --- /dev/null +++ b/unix/unifont.c @@ -0,0 +1,3808 @@ +/* + * 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 +#include +#include + +#include +#if !GTK_CHECK_VERSION(3,0,0) +#include +#endif + +#define MAY_REFER_TO_GTK_IN_HEADERS + +#include "putty.h" +#include "unifont.h" +#include "gtkcompat.h" +#include "gtkmisc.h" +#include "tree234.h" + +#ifndef NOT_X_WINDOWS +#include +#include +#include +#include +#include "x11misc.h" +#endif + +/* + * 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. + */ + +#if !GLIB_CHECK_VERSION(1,3,7) +#define g_ascii_strcasecmp g_strcasecmp +#define g_ascii_strncasecmp g_strncasecmp +#endif + +/* + * Ad-hoc vtable mechanism to allow font structures to be + * polymorphic. + * + * Any instance of `unifont' used in the vtable functions will + * actually be an element of a larger structure containing data + * specific to the subtype. + */ + +#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 UnifontVtable *fontclass); + +struct UnifontVtable { + /* + * `Methods' of the `class'. + */ + unifont *(*create)(GtkWidget *widget, const char *name, bool wide, + bool bold, int shadowoffset, bool shadowalways); + unifont *(*create_fallback)(GtkWidget *widget, int height, bool wide, + bool bold, int shadowoffset, + bool shadowalways); + void (*destroy)(unifont *font); + bool (*has_glyph)(unifont *font, wchar_t glyph); + void (*draw_text)(unifont_drawctx *ctx, unifont *font, + int x, int y, const wchar_t *string, int len, + bool wide, bool bold, int cellwidth); + void (*draw_combining)(unifont_drawctx *ctx, unifont *font, + int x, int y, const wchar_t *string, int len, + bool wide, bool 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, bool resolve_aliases); + char *(*scale_fontname)(GtkWidget *widget, const char *name, int size); + char *(*size_increment)(unifont *font, int increment); + + /* + * `Static data members' of the `class'. + */ + const char *prefix; +}; + +#ifndef NOT_X_WINDOWS + +/* ---------------------------------------------------------------------- + * X11 font implementation, directly using Xlib calls. Conditioned out + * if X11 fonts aren't available at all (e.g. building with GTK3 for a + * back end other than X). + */ + +static bool x11font_has_glyph(unifont *font, wchar_t glyph); +static void x11font_draw_text(unifont_drawctx *ctx, unifont *font, + int x, int y, const wchar_t *string, int len, + bool wide, bool bold, int cellwidth); +static void x11font_draw_combining(unifont_drawctx *ctx, unifont *font, + int x, int y, const wchar_t *string, + int len, bool wide, bool bold, + int cellwidth); +static unifont *x11font_create(GtkWidget *widget, const char *name, + bool wide, bool bold, + int shadowoffset, bool 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, + bool resolve_aliases); +static char *x11font_scale_fontname(GtkWidget *widget, const char *name, + int size); +static char *x11font_size_increment(unifont *font, int increment); + +#ifdef DRAW_TEXT_CAIRO +struct cairo_cached_glyph { + cairo_surface_t *surface; + unsigned char *bitmap; +}; +#endif + +/* + * Structure storing a single physical XFontStruct, plus associated + * data. + */ +typedef struct x11font_individual { + /* The XFontStruct itself. */ + XFontStruct *xfs; + + /* + * The `allocated' flag indicates whether we've tried to fetch + * this subfont already (thus distinguishing xfs==NULL because we + * haven't tried yet from xfs==NULL because we tried and failed, + * so that we don't keep trying and failing subsequently). + */ + bool allocated; + +#ifdef DRAW_TEXT_CAIRO + /* + * A cache of glyph bitmaps downloaded from the X server when + * we're in Cairo rendering mode. If glyphcache itself is + * non-NULL, then entries in [0,nglyphs) are expected to be + * initialised to either NULL or a bitmap pointer. + */ + struct cairo_cached_glyph *glyphcache; + int nglyphs; + + /* + * X server paraphernalia for actually downloading the glyphs. + */ + Pixmap pixmap; + GC gc; + int pixwidth, pixheight, pixoriginx, pixoriginy; + + /* + * Paraphernalia for loading the resulting bitmaps into Cairo. + */ + int rowsize, allsize, indexflip; +#endif + +} x11font_individual; + +struct x11font { + /* + * Copy of the X display handle, so we don't have to keep + * extracting it from GDK. + */ + Display *disp; + /* + * Individual physical X fonts. We store a number of these, for + * automatically guessed bold and wide variants. + */ + x11font_individual fonts[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 XDrawString or XDrawString16, etc. + */ + bool 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. + */ + bool variable; + /* + * real_charset is the charset used when translating text into the + * font's internal encoding inside draw_text(). This need not be + * the same as the public_charset provided to the client; for + * example, public_charset might be CS_ISO8859_1 while + * real_charset is CS_ISO8859_1_X11. + */ + int real_charset; + /* + * Data passed in to unifont_create(). + */ + int shadowoffset; + bool wide, bold, shadowalways; + + unifont u; +}; + +static const UnifontVtable x11font_vtable = { + .create = x11font_create, + .create_fallback = NULL, /* no fallback fonts in X11 */ + .destroy = x11font_destroy, + .has_glyph = x11font_has_glyph, + .draw_text = x11font_draw_text, + .draw_combining = x11font_draw_combining, + .enum_fonts = x11font_enum_fonts, + .canonify_fontname = x11font_canonify_fontname, + .scale_fontname = x11font_scale_fontname, + .size_increment = x11font_size_increment, + .prefix = "server", +}; + +#define XLFD_STRING_PARTS_LIST(S,I) \ + S(foundry) \ + S(family_name) \ + S(weight_name) \ + S(slant) \ + S(setwidth_name) \ + S(add_style_name) \ + I(pixel_size) \ + I(point_size) \ + I(resolution_x) \ + I(resolution_y) \ + S(spacing) \ + I(average_width) \ + S(charset_registry) \ + S(charset_encoding) \ + /* end of list */ + +/* Special value for int fields that xlfd_recompose will render as "*" */ +#define XLFD_INT_WILDCARD INT_MIN + +struct xlfd_decomposed { +#define STR_FIELD(f) const char *f; +#define INT_FIELD(f) int f; + XLFD_STRING_PARTS_LIST(STR_FIELD, INT_FIELD) +#undef STR_FIELD +#undef INT_FIELD +}; + +static struct xlfd_decomposed *xlfd_decompose(const char *xlfd) +{ + char *p, *components[14]; + struct xlfd_decomposed *dec; + int i; + + if (!xlfd) + return NULL; + + dec = snew_plus(struct xlfd_decomposed, strlen(xlfd) + 1); + p = snew_plus_get_aux(dec); + strcpy(p, xlfd); + + for (i = 0; i < 14; i++) { + if (*p != '-') { + /* Malformed XLFD: not enough '-' */ + sfree(dec); + return NULL; + } + *p++ = '\0'; + components[i] = p; + p += strcspn(p, "-"); + } + if (*p) { + /* Malformed XLFD: too many '-' */ + sfree(dec); + return NULL; + } + + i = 0; +#define STORE_STR(f) dec->f = components[i++]; +#define STORE_INT(f) dec->f = atoi(components[i++]); + XLFD_STRING_PARTS_LIST(STORE_STR, STORE_INT) +#undef STORE_STR +#undef STORE_INT + + return dec; +} + +static char *xlfd_recompose(const struct xlfd_decomposed *dec) +{ +#define FMT_STR(f) "-%s" +#define ARG_STR(f) , dec->f +#define FMT_INT(f) "%s%.*d" +#define ARG_INT(f) \ + , dec->f == XLFD_INT_WILDCARD ? "-*" : "-" \ + , dec->f == XLFD_INT_WILDCARD ? 0 : 1 \ + , dec->f == XLFD_INT_WILDCARD ? 0 : dec->f + return dupprintf(XLFD_STRING_PARTS_LIST(FMT_STR, FMT_INT) + XLFD_STRING_PARTS_LIST(ARG_STR, ARG_INT)); +#undef FMT_STR +#undef ARG_STR +#undef FMT_INT +#undef ARG_INT +} + +static char *x11_guess_derived_font_name(Display *disp, XFontStruct *xfs, + bool bold, bool wide) +{ + Atom fontprop = XInternAtom(disp, "FONT", False); + unsigned long ret; + if (XGetFontProperty(xfs, fontprop, &ret)) { + char *name = XGetAtomName(disp, (Atom)ret); + struct xlfd_decomposed *xlfd = xlfd_decompose(name); + if (!xlfd) + return NULL; + + if (bold) + xlfd->weight_name = "bold"; + + if (wide) { + /* Width name obviously may have changed. */ + /* Additional style may now become e.g. `ja' or `ko'. */ + xlfd->setwidth_name = xlfd->add_style_name = "*"; + + /* Expect to double the average width. */ + xlfd->average_width *= 2; + } + + { + char *ret = xlfd_recompose(xlfd); + sfree(xlfd); + return ret; + } + } + return NULL; +} + +static int x11_font_width(XFontStruct *xfs, bool sixteen_bit) +{ + if (sixteen_bit) { + XChar2b space; + space.byte1 = 0; + space.byte2 = '0'; + return XTextWidth16(xfs, &space, 1); + } else { + return XTextWidth(xfs, "0", 1); + } +} + +static const XCharStruct *x11_char_struct( + XFontStruct *xfs, unsigned char byte1, unsigned char byte2) +{ + int index; + + /* + * The man page for XQueryFont is rather confusing about how the + * per_char array in the XFontStruct is laid out, because it gives + * formulae for determining the two-byte X character code _from_ + * an index into the per_char array. Going the other way, it's + * rather simpler: + * + * The valid character codes have byte1 between min_byte1 and + * max_byte1 inclusive, and byte2 between min_char_or_byte2 and + * max_char_or_byte2 inclusive. This gives a rectangle of size + * (max_byte2-min_byte1+1) by + * (max_char_or_byte2-min_char_or_byte2+1), which is precisely the + * rectangle encoded in the per_char array. Hence, given a + * character code which is valid in the sense that it falls + * somewhere in that rectangle, its index in per_char is given by + * setting + * + * x = byte2 - min_char_or_byte2 + * y = byte1 - min_byte1 + * index = y * (max_char_or_byte2-min_char_or_byte2+1) + x + * + * If min_byte1 and min_byte2 are both zero, that's a special case + * which can be treated as if min_byte2 was 1 instead, i.e. the + * per_char array just runs from min_char_or_byte2 to + * max_char_or_byte2 inclusive, and byte1 should always be zero. + */ + + if (byte2 < xfs->min_char_or_byte2 || byte2 > xfs->max_char_or_byte2) + return NULL; + + if (xfs->min_byte1 == 0 && xfs->max_byte1 == 0) { + index = byte2 - xfs->min_char_or_byte2; + } else { + if (byte1 < xfs->min_byte1 || byte1 > xfs->max_byte1) + return NULL; + index = ((byte2 - xfs->min_char_or_byte2) + + ((byte1 - xfs->min_byte1) * + (xfs->max_char_or_byte2 - xfs->min_char_or_byte2 + 1))); + } + + if (!xfs->per_char) /* per_char NULL => everything in range exists */ + return &xfs->max_bounds; + + return &xfs->per_char[index]; +} + +static bool x11_font_has_glyph( + XFontStruct *xfs, unsigned char byte1, unsigned char byte2) +{ + /* + * Not to be confused with x11font_has_glyph, which is a method of + * the x11font 'class' and hence takes a unifont as argument. This + * is the low-level function which grubs about in an actual + * XFontStruct to see if a given glyph exists. + * + * We must do this ourselves rather than letting Xlib's + * XTextExtents16 do the job, because XTextExtents will helpfully + * substitute the font's default_char for any missing glyph and + * not tell us it did so, which precisely won't help us find out + * which glyphs _are_ missing. + */ + const XCharStruct *xcs = x11_char_struct(xfs, byte1, byte2); + return xcs && (xcs->ascent + xcs->descent > 0 || xcs->width > 0); +} + +static unifont *x11font_create(GtkWidget *widget, const char *name, + bool wide, bool bold, + int shadowoffset, bool shadowalways) +{ + struct x11font *xfont; + XFontStruct *xfs; + Display *disp; + Atom charset_registry, charset_encoding, spacing; + unsigned long registry_ret, encoding_ret, spacing_ret; + int pubcs, realcs; + bool sixteen_bit, variable; + int i; + + if ((disp = get_x11_display()) == NULL) + return NULL; + + xfs = XLoadQueryFont(disp, name); + if (!xfs) + return NULL; + + 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); + 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 + * low character positions, it is assumed that those + * glyphs are the VT100 line-drawing character set. + */ + if (pubcs == CS_ISO8859_1) { + int ch; + for (ch = 1; ch < 32; ch++) + if (!x11_font_has_glyph(xfs, 0, ch)) + break; + if (ch == 32) + realcs = CS_ISO8859_1_X11; + } + + sfree(encoding); + } + } + + 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(xfs, sixteen_bit); + xfont->u.ascent = xfs->ascent; + xfont->u.descent = xfs->descent; + xfont->u.height = xfont->u.ascent + xfont->u.descent; + xfont->u.public_charset = pubcs; + xfont->u.want_fallback = true; + xfont->u.strikethrough_y = xfont->u.ascent - (xfont->u.ascent * 3 / 8); +#ifdef DRAW_TEXT_GDK + xfont->u.preferred_drawtype = DRAWTYPE_GDK; +#elif defined DRAW_TEXT_CAIRO + xfont->u.preferred_drawtype = DRAWTYPE_CAIRO; +#else +#error No drawtype available at all +#endif + xfont->disp = disp; + xfont->real_charset = realcs; + xfont->sixteen_bit = sixteen_bit; + xfont->variable = variable; + xfont->wide = wide; + xfont->bold = bold; + xfont->shadowoffset = shadowoffset; + xfont->shadowalways = shadowalways; + + for (i = 0; i < lenof(xfont->fonts); i++) { + xfont->fonts[i].xfs = NULL; + xfont->fonts[i].allocated = false; +#ifdef DRAW_TEXT_CAIRO + xfont->fonts[i].glyphcache = NULL; + xfont->fonts[i].nglyphs = 0; + xfont->fonts[i].pixmap = None; + xfont->fonts[i].gc = None; +#endif + } + xfont->fonts[0].xfs = xfs; + xfont->fonts[0].allocated = true; + + return &xfont->u; +} + +static void x11font_destroy(unifont *font) +{ + struct x11font *xfont = container_of(font, struct x11font, u); + Display *disp = xfont->disp; + int i; + + for (i = 0; i < lenof(xfont->fonts); i++) { + if (xfont->fonts[i].xfs) + XFreeFont(disp, xfont->fonts[i].xfs); +#ifdef DRAW_TEXT_CAIRO + if (xfont->fonts[i].gc != None) + XFreeGC(disp, xfont->fonts[i].gc); + if (xfont->fonts[i].pixmap != None) + XFreePixmap(disp, xfont->fonts[i].pixmap); + if (xfont->fonts[i].glyphcache) { + int j; + for (j = 0; j < xfont->fonts[i].nglyphs; j++) { + cairo_surface_destroy(xfont->fonts[i].glyphcache[j].surface); + sfree(xfont->fonts[i].glyphcache[j].bitmap); + } + sfree(xfont->fonts[i].glyphcache); + } +#endif + } + sfree(xfont); +} + +static void x11_alloc_subfont(struct x11font *xfont, int sfid) +{ + Display *disp = xfont->disp; + char *derived_name = x11_guess_derived_font_name + (disp, xfont->fonts[0].xfs, sfid & 1, !!(sfid & 2)); + xfont->fonts[sfid].xfs = XLoadQueryFont(disp, derived_name); + xfont->fonts[sfid].allocated = true; + sfree(derived_name); + /* Note that xfont->fonts[sfid].xfs may still be NULL, if XLQF failed. */ +} + +static bool x11font_has_glyph(unifont *font, wchar_t glyph) +{ + struct x11font *xfont = container_of(font, struct x11font, u); + + if (xfont->sixteen_bit) { + /* + * This X font has 16-bit character indices, which means + * we can directly use our Unicode input value. + */ + return x11_font_has_glyph(xfont->fonts[0].xfs, + glyph >> 8, glyph & 0xFF); + } else { + /* + * This X font has 8-bit indices, so we must convert to the + * appropriate character set. + */ + char sbstring[2]; + int sblen = wc_to_mb(xfont->real_charset, 0, &glyph, 1, + sbstring, 2, "", NULL); + if (sblen == 0 || !sbstring[0]) + return false; /* not even in the charset */ + + return x11_font_has_glyph(xfont->fonts[0].xfs, 0, + (unsigned char)sbstring[0]); + } +} + +#if !GTK_CHECK_VERSION(2,0,0) +#define GDK_DRAWABLE_XID(d) GDK_WINDOW_XWINDOW(d) /* GTK1's name for this */ +#elif GTK_CHECK_VERSION(3,0,0) +#define GDK_DRAWABLE_XID(d) GDK_WINDOW_XID(d) /* GTK3's name for this */ +#endif + +static int x11font_width_16(unifont_drawctx *ctx, x11font_individual *xfi, + const void *vstring, int start, int length) +{ + const XChar2b *string = (const XChar2b *)vstring; + return XTextWidth16(xfi->xfs, string+start, length); +} + +static int x11font_width_8(unifont_drawctx *ctx, x11font_individual *xfi, + const void *vstring, int start, int length) +{ + const char *string = (const char *)vstring; + return XTextWidth(xfi->xfs, string+start, length); +} + +#ifdef DRAW_TEXT_GDK +static void x11font_gdk_setup(unifont_drawctx *ctx, x11font_individual *xfi, + Display *disp) +{ + XSetFont(disp, GDK_GC_XGC(ctx->u.gdk.gc), xfi->xfs->fid); +} + +static void x11font_gdk_draw_16(unifont_drawctx *ctx, x11font_individual *xfi, + Display *disp, int x, int y, + const void *vstring, int start, int length) +{ + const XChar2b *string = (const XChar2b *)vstring; + XDrawString16(disp, GDK_DRAWABLE_XID(ctx->u.gdk.target), + GDK_GC_XGC(ctx->u.gdk.gc), x, y, string+start, length); +} + +static void x11font_gdk_draw_8(unifont_drawctx *ctx, x11font_individual *xfi, + Display *disp, int x, int y, + const void *vstring, int start, int length) +{ + const char *string = (const char *)vstring; + XDrawString(disp, GDK_DRAWABLE_XID(ctx->u.gdk.target), + GDK_GC_XGC(ctx->u.gdk.gc), x, y, string+start, length); +} +#endif + +#ifdef DRAW_TEXT_CAIRO +static void x11font_cairo_setup( + unifont_drawctx *ctx, x11font_individual *xfi, Display *disp) +{ + if (xfi->pixmap == None) { + XGCValues gcvals; + GdkWindow *widgetwin = gtk_widget_get_window(ctx->u.cairo.widget); + int widgetscr = GDK_SCREEN_XNUMBER(gdk_window_get_screen(widgetwin)); + + xfi->pixwidth = + xfi->xfs->max_bounds.rbearing - xfi->xfs->min_bounds.lbearing; + xfi->pixheight = + xfi->xfs->max_bounds.ascent + xfi->xfs->max_bounds.descent; + xfi->pixoriginx = -xfi->xfs->min_bounds.lbearing; + xfi->pixoriginy = xfi->xfs->max_bounds.ascent; + + xfi->rowsize = cairo_format_stride_for_width(CAIRO_FORMAT_A1, + xfi->pixwidth); + xfi->allsize = xfi->rowsize * xfi->pixheight; + + { + /* + * Test host endianness and use it to set xfi->indexflip, + * which is XORed into our left-shift counts in order to + * implement the CAIRO_FORMAT_A1 specification, in which + * each bitmap byte is oriented LSB-first on little-endian + * platforms and MSB-first on big-endian ones. + * + * This is the same technique Cairo itself uses to test + * endianness, so hopefully it'll work in any situation + * where Cairo is usable at all. + */ + static const int endianness_test = 1; + xfi->indexflip = (*((char *) &endianness_test) == 1) ? 0 : 7; + } + + xfi->pixmap = XCreatePixmap + (disp, + GDK_DRAWABLE_XID(gtk_widget_get_window(ctx->u.cairo.widget)), + xfi->pixwidth, xfi->pixheight, 1); + gcvals.foreground = WhitePixel(disp, widgetscr); + gcvals.background = BlackPixel(disp, widgetscr); + gcvals.font = xfi->xfs->fid; + xfi->gc = XCreateGC(disp, xfi->pixmap, + GCForeground | GCBackground | GCFont, &gcvals); + } +} + +static void x11font_cairo_cache_glyph( + Display *disp, x11font_individual *xfi, int glyphindex) +{ + XImage *image; + int x, y; + unsigned char *bitmap; + const XCharStruct *xcs = x11_char_struct(xfi->xfs, glyphindex >> 8, + glyphindex & 0xFF); + + bitmap = snewn(xfi->allsize, unsigned char); + memset(bitmap, 0, xfi->allsize); + + image = XGetImage(disp, xfi->pixmap, 0, 0, + xfi->pixwidth, xfi->pixheight, AllPlanes, XYPixmap); + for (y = xfi->pixoriginy - xcs->ascent; + y < xfi->pixoriginy + xcs->descent; y++) { + for (x = xfi->pixoriginx + xcs->lbearing; + x < xfi->pixoriginx + xcs->rbearing; x++) { + unsigned long pixel = XGetPixel(image, x, y); + if (pixel) { + int byteindex = y * xfi->rowsize + x/8; + int bitindex = (x & 7) ^ xfi->indexflip; + bitmap[byteindex] |= 1U << bitindex; + } + } + } + XDestroyImage(image); + + if (xfi->nglyphs <= glyphindex) { + /* Round up to the next multiple of 256 on the general + * principle that Unicode characters come in contiguous blocks + * often used together */ + int old_nglyphs = xfi->nglyphs; + xfi->nglyphs = (glyphindex + 0x100) & ~0xFF; + xfi->glyphcache = sresize(xfi->glyphcache, xfi->nglyphs, + struct cairo_cached_glyph); + + while (old_nglyphs < xfi->nglyphs) { + xfi->glyphcache[old_nglyphs].surface = NULL; + xfi->glyphcache[old_nglyphs].bitmap = NULL; + old_nglyphs++; + } + } + xfi->glyphcache[glyphindex].bitmap = bitmap; + xfi->glyphcache[glyphindex].surface = cairo_image_surface_create_for_data + (bitmap, CAIRO_FORMAT_A1, xfi->pixwidth, xfi->pixheight, xfi->rowsize); +} + +static void x11font_cairo_draw_glyph(unifont_drawctx *ctx, + x11font_individual *xfi, int x, int y, + int glyphindex) +{ + if (xfi->glyphcache[glyphindex].surface) { + cairo_mask_surface(ctx->u.cairo.cr, + xfi->glyphcache[glyphindex].surface, + x - xfi->pixoriginx, y - xfi->pixoriginy); + } +} + +static void x11font_cairo_draw_16( + unifont_drawctx *ctx, x11font_individual *xfi, Display *disp, + int x, int y, const void *vstring, int start, int length) +{ + const XChar2b *string = (const XChar2b *)vstring + start; + int i; + for (i = 0; i < length; i++) { + if (x11_font_has_glyph(xfi->xfs, string[i].byte1, string[i].byte2)) { + int glyphindex = (256 * (unsigned char)string[i].byte1 + + (unsigned char)string[i].byte2); + if (glyphindex >= xfi->nglyphs || + !xfi->glyphcache[glyphindex].surface) { + XDrawImageString16(disp, xfi->pixmap, xfi->gc, + xfi->pixoriginx, xfi->pixoriginy, + string+i, 1); + x11font_cairo_cache_glyph(disp, xfi, glyphindex); + } + x11font_cairo_draw_glyph(ctx, xfi, x, y, glyphindex); + x += XTextWidth16(xfi->xfs, string+i, 1); + } + } +} + +static void x11font_cairo_draw_8( + unifont_drawctx *ctx, x11font_individual *xfi, Display *disp, + int x, int y, const void *vstring, int start, int length) +{ + const char *string = (const char *)vstring + start; + int i; + for (i = 0; i < length; i++) { + if (x11_font_has_glyph(xfi->xfs, 0, string[i])) { + int glyphindex = (unsigned char)string[i]; + if (glyphindex >= xfi->nglyphs || + !xfi->glyphcache[glyphindex].surface) { + XDrawImageString(disp, xfi->pixmap, xfi->gc, + xfi->pixoriginx, xfi->pixoriginy, + string+i, 1); + x11font_cairo_cache_glyph(disp, xfi, glyphindex); + } + x11font_cairo_draw_glyph(ctx, xfi, x, y, glyphindex); + x += XTextWidth(xfi->xfs, string+i, 1); + } + } +} +#endif /* DRAW_TEXT_CAIRO */ + +struct x11font_drawfuncs { + int (*width)(unifont_drawctx *ctx, x11font_individual *xfi, + const void *vstring, int start, int length); + void (*setup)(unifont_drawctx *ctx, x11font_individual *xfi, + Display *disp); + void (*draw)(unifont_drawctx *ctx, x11font_individual *xfi, Display *disp, + int x, int y, const void *vstring, int start, int length); +}; + +/* + * This array has two entries per compiled-in drawtype; of each pair, + * the first is for an 8-bit font and the second for 16-bit. + */ +static const struct x11font_drawfuncs x11font_drawfuncs[2*DRAWTYPE_NTYPES] = { +#ifdef DRAW_TEXT_GDK + /* gdk, 8-bit */ + { + x11font_width_8, + x11font_gdk_setup, + x11font_gdk_draw_8, + }, + /* gdk, 16-bit */ + { + x11font_width_16, + x11font_gdk_setup, + x11font_gdk_draw_16, + }, +#endif +#ifdef DRAW_TEXT_CAIRO + /* cairo, 8-bit */ + { + x11font_width_8, + x11font_cairo_setup, + x11font_cairo_draw_8, + }, + /* [3] cairo, 16-bit */ + { + x11font_width_16, + x11font_cairo_setup, + x11font_cairo_draw_16, + }, +#endif +}; + +static void x11font_really_draw_text( + const struct x11font_drawfuncs *dfns, unifont_drawctx *ctx, + x11font_individual *xfi, Display *disp, + int x, int y, const void *string, int nchars, + int shadowoffset, bool fontvariable, int cellwidth) +{ + int start = 0, step, nsteps; + bool centre; + + if (fontvariable) { + /* + * In a variable-pitch font, we draw one character at a + * time, and centre it in the character cell. + */ + step = 1; + nsteps = nchars; + centre = true; + } else { + /* + * In a fixed-pitch font, we can draw the whole lot in one go. + */ + step = nchars; + nsteps = 1; + centre = false; + } + + dfns->setup(ctx, xfi, disp); + + while (nsteps-- > 0) { + int X = x; + if (centre) + X += (cellwidth - dfns->width(ctx, xfi, string, start, step)) / 2; + + dfns->draw(ctx, xfi, disp, X, y, string, start, step); + if (shadowoffset) + dfns->draw(ctx, xfi, disp, X + shadowoffset, y, + string, start, step); + + x += cellwidth; + start += step; + } +} + +static void x11font_draw_text(unifont_drawctx *ctx, unifont *font, + int x, int y, const wchar_t *string, int len, + bool wide, bool bold, int cellwidth) +{ + struct x11font *xfont = container_of(font, struct x11font, u); + int sfid; + int shadowoffset = 0; + int mult = (wide ? 2 : 1); + int index = 2 * (int)ctx->type; + + wide = wide && !xfont->wide; + bold = bold && !xfont->bold; + + /* + * Decide which subfont we're using, and whether we have to + * use shadow bold. + */ + if (xfont->shadowalways && bold) { + shadowoffset = xfont->shadowoffset; + bold = false; + } + sfid = 2 * wide + bold; + if (!xfont->fonts[sfid].allocated) + x11_alloc_subfont(xfont, sfid); + if (bold && !xfont->fonts[sfid].xfs) { + bold = false; + shadowoffset = xfont->shadowoffset; + sfid = 2 * wide + bold; + if (!xfont->fonts[sfid].allocated) + x11_alloc_subfont(xfont, sfid); + } + + if (!xfont->fonts[sfid].xfs) + return; /* we've tried our best, but no luck */ + + if (xfont->sixteen_bit) { + /* + * This X font has 16-bit character indices, which means + * we can directly use our Unicode input string. + */ + XChar2b *xcs; + int i; + + xcs = snewn(len, XChar2b); + for (i = 0; i < len; i++) { + xcs[i].byte1 = string[i] >> 8; + xcs[i].byte2 = string[i]; + } + + x11font_really_draw_text(x11font_drawfuncs + index + 1, ctx, + &xfont->fonts[sfid], xfont->disp, x, y, + xcs, len, shadowoffset, + xfont->variable, cellwidth * mult); + sfree(xcs); + } else { + /* + * This X font has 8-bit indices, so we must convert to the + * appropriate character set. + */ + char *sbstring = snewn(len+1, char); + int sblen = wc_to_mb(xfont->real_charset, 0, string, len, + sbstring, len+1, ".", NULL); + x11font_really_draw_text(x11font_drawfuncs + index + 0, ctx, + &xfont->fonts[sfid], xfont->disp, x, y, + sbstring, sblen, shadowoffset, + xfont->variable, cellwidth * mult); + sfree(sbstring); + } +} + +static void x11font_draw_combining(unifont_drawctx *ctx, unifont *font, + int x, int y, const wchar_t *string, + int len, bool wide, bool bold, + int cellwidth) +{ + /* + * For server-side fonts, there's no sophisticated system for + * combining characters intelligently, so the best we can do is to + * overprint them on each other in the obvious way. + */ + int i; + for (i = 0; i < len; i++) + x11font_draw_text(ctx, font, x, y, string+i, 1, wide, bold, cellwidth); +} + +static void x11font_enum_fonts(GtkWidget *widget, + fontsel_add_entry callback, void *callback_ctx) +{ + Display *disp; + char **fontnames; + char *tmp = NULL; + int nnames, i, max, tmpsize; + + if ((disp = get_x11_display()) == NULL) + return; + + max = 32768; + while (1) { + fontnames = XListFonts(disp, "*", max, &nnames); + if (nnames >= max) { + XFreeFontNames(fontnames); + max *= 2; + } else + break; + } + + tmpsize = 0; + + for (i = 0; i < nnames; i++) { + struct xlfd_decomposed *xlfd = xlfd_decompose(fontnames[i]); + if (xlfd) { + char *p, *font, *style, *stylekey, *charset; + int weightkey, slantkey, setwidthkey; + int thistmpsize; + + /* + * Convert a dismembered XLFD into the format we'll be + * using in the font selector. + */ + + thistmpsize = 4 * strlen(fontnames[i]) + 256; + if (tmpsize < thistmpsize) { + tmpsize = thistmpsize; + tmp = sresize(tmp, tmpsize, char); + } + p = tmp; + + /* + * Font name is in the form "family (foundry)". (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)", xlfd->family_name, xlfd->foundry); + + /* + * Character set. + */ + charset = p; + p += 1 + sprintf(p, "%s-%s", xlfd->charset_registry, + xlfd->charset_encoding); + + /* + * Style is a mixture of quite a lot of the fields, + * with some strange formatting. + */ + style = p; + p += sprintf(p, "%s", xlfd->weight_name[0] ? xlfd->weight_name : + "regular"); + if (!g_ascii_strcasecmp(xlfd->slant, "i")) + p += sprintf(p, " italic"); + else if (!g_ascii_strcasecmp(xlfd->slant, "o")) + p += sprintf(p, " oblique"); + else if (!g_ascii_strcasecmp(xlfd->slant, "ri")) + p += sprintf(p, " reverse italic"); + else if (!g_ascii_strcasecmp(xlfd->slant, "ro")) + p += sprintf(p, " reverse oblique"); + else if (!g_ascii_strcasecmp(xlfd->slant, "ot")) + p += sprintf(p, " other-slant"); + if (xlfd->setwidth_name[0] && + g_ascii_strcasecmp(xlfd->setwidth_name, "normal")) + p += sprintf(p, " %s", xlfd->setwidth_name); + if (!g_ascii_strcasecmp(xlfd->spacing, "m")) + p += sprintf(p, " [M]"); + if (!g_ascii_strcasecmp(xlfd->spacing, "c")) + p += sprintf(p, " [C]"); + if (xlfd->add_style_name[0]) + p += sprintf(p, " %s", xlfd->add_style_name); + + /* + * 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_ascii_strcasecmp(xlfd->weight_name, "medium") || + !g_ascii_strcasecmp(xlfd->weight_name, "regular") || + !g_ascii_strcasecmp(xlfd->weight_name, "normal") || + !g_ascii_strcasecmp(xlfd->weight_name, "book")) + weightkey = 0; + else if (!g_ascii_strncasecmp(xlfd->weight_name, "demi", 4) || + !g_ascii_strncasecmp(xlfd->weight_name, "semi", 4)) + weightkey = 1; + else + weightkey = 2; + if (!g_ascii_strcasecmp(xlfd->slant, "r")) + slantkey = 0; + else if (!g_ascii_strncasecmp(xlfd->slant, "r", 1)) + slantkey = 2; + else + slantkey = 1; + if (!g_ascii_strcasecmp(xlfd->setwidth_name, "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(xlfd->weight_name), xlfd->weight_name, + slantkey, (int)strlen(xlfd->slant), xlfd->slant, + setwidthkey, + (int)strlen(xlfd->setwidth_name), xlfd->setwidth_name, + (int)strlen(xlfd->spacing), xlfd->spacing, + (int)strlen(xlfd->add_style_name), xlfd->add_style_name); + + assert(p - tmp < thistmpsize); + + /* + * Flags: we need to know whether this is a monospaced + * font, which we do by examining the spacing field + * again. + */ + int flags = FONTFLAG_SERVERSIDE; + if (!strchr("CcMm", xlfd->spacing[0])) + flags |= FONTFLAG_NONMONOSPACED; + + /* + * Some fonts have a pixel size of zero, meaning they're + * treated as scalable. For these purposes, we only want + * fonts whose pixel size we actually know, so filter + * those out. + */ + if (xlfd->pixel_size) + callback(callback_ctx, fontnames[i], font, charset, + style, stylekey, xlfd->pixel_size, flags, + &x11font_vtable); + + sfree(xlfd); + } 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); + + sfree(xlfd); + } + } + XFreeFontNames(fontnames); + sfree(tmp); +} + +static char *x11font_canonify_fontname(GtkWidget *widget, const char *name, + int *size, int *flags, + bool 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. + */ + XFontStruct *xfs; + Display *disp; + Atom fontprop, fontprop2; + unsigned long ret; + + if ((disp = get_x11_display()) == NULL) + return NULL; + + xfs = XLoadQueryFont(disp, name); + + if (!xfs) + return NULL; /* didn't make sense to us, sorry */ + + 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; + XFreeFont(disp, xfs); + if (flags) { + if (name[0] == '-' || resolve_aliases) + *flags = FONTFLAG_SERVERSIDE; + else + *flags = FONTFLAG_SERVERALIAS; + } + return dupstr(name[0] == '-' || resolve_aliases ? + newname : name); + } + } + } + + XFreeFont(disp, xfs); + + return NULL; /* something went wrong */ +} + +static char *x11font_scale_fontname(GtkWidget *widget, const char *name, + int size) +{ + return NULL; /* shan't */ +} + +static char *x11font_size_increment(unifont *font, int increment) +{ + struct x11font *xfont = container_of(font, struct x11font, u); + Display *disp = xfont->disp; + Atom fontprop = XInternAtom(disp, "FONT", False); + char *returned_name = NULL; + unsigned long ret; + if (XGetFontProperty(xfont->fonts[0].xfs, fontprop, &ret)) { + struct xlfd_decomposed *xlfd; + struct xlfd_decomposed *xlfd_best; + char *wc; + char **fontnames; + int nnames, i, max; + + xlfd = xlfd_decompose(XGetAtomName(disp, (Atom)ret)); + if (!xlfd) + return NULL; + + /* + * Form a wildcard consisting of everything in the + * original XLFD except for the size-related fields. + */ + { + struct xlfd_decomposed xlfd_wc = *xlfd; /* structure copy */ + xlfd_wc.pixel_size = XLFD_INT_WILDCARD; + xlfd_wc.point_size = XLFD_INT_WILDCARD; + xlfd_wc.average_width = XLFD_INT_WILDCARD; + wc = xlfd_recompose(&xlfd_wc); + } + + /* + * Fetch all the font names matching that wildcard. + */ + max = 32768; + while (1) { + fontnames = XListFonts(disp, wc, max, &nnames); + if (nnames >= max) { + XFreeFontNames(fontnames); + max *= 2; + } else + break; + } + + sfree(wc); + + /* + * Iterate over those to find the one closest in size to the + * original font, in the correct direction. + */ + +#define FLIPPED_SIZE(xlfd) \ + (((xlfd)->pixel_size + (xlfd)->point_size) * \ + (increment < 0 ? -1 : +1)) + + xlfd_best = NULL; + for (i = 0; i < nnames; i++) { + struct xlfd_decomposed *xlfd2 = xlfd_decompose(fontnames[i]); + if (!xlfd2) + continue; + + if (xlfd2->pixel_size != 0 && + FLIPPED_SIZE(xlfd2) > FLIPPED_SIZE(xlfd) && + (!xlfd_best || FLIPPED_SIZE(xlfd2)u.vt->prefix, ":", bare_returned_name); + sfree(bare_returned_name); + } + + XFreeFontNames(fontnames); + sfree(xlfd); + sfree(xlfd_best); + } + return returned_name; +} + +#endif /* NOT_X_WINDOWS */ + +#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 bool pangofont_has_glyph(unifont *font, wchar_t glyph); +static void pangofont_draw_text(unifont_drawctx *ctx, unifont *font, + int x, int y, const wchar_t *string, int len, + bool wide, bool bold, int cellwidth); +static void pangofont_draw_combining(unifont_drawctx *ctx, unifont *font, + int x, int y, const wchar_t *string, + int len, bool wide, bool bold, + int cellwidth); +static unifont *pangofont_create(GtkWidget *widget, const char *name, + bool wide, bool bold, + int shadowoffset, bool shadowalways); +static unifont *pangofont_create_fallback(GtkWidget *widget, int height, + bool wide, bool bold, + int shadowoffset, bool 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, + bool resolve_aliases); +static char *pangofont_scale_fontname(GtkWidget *widget, const char *name, + int size); +static char *pangofont_size_increment(unifont *font, int increment); + +struct pangofont { + /* + * Pango objects. + */ + PangoFontDescription *desc; + PangoFontset *fset; + /* + * The containing widget. + */ + GtkWidget *widget; + /* + * Data passed in to unifont_create(). + */ + int shadowoffset; + bool bold, shadowalways; + /* + * Cache of character widths, indexed by Unicode code point. In + * pixels; -1 means we haven't asked Pango about this character + * before. + */ + int *widthcache; + unsigned nwidthcache; + + struct unifont u; +}; + +static const UnifontVtable pangofont_vtable = { + .create = pangofont_create, + .create_fallback = pangofont_create_fallback, + .destroy = pangofont_destroy, + .has_glyph = pangofont_has_glyph, + .draw_text = pangofont_draw_text, + .draw_combining = pangofont_draw_combining, + .enum_fonts = pangofont_enum_fonts, + .canonify_fontname = pangofont_canonify_fontname, + .scale_fontname = pangofont_scale_fontname, + .size_increment = pangofont_size_increment, + .prefix = "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 bool pangofont_check_desc_makes_sense(PangoContext *ctx, + PangoFontDescription *desc) +{ +#ifndef PANGO_PRE_1POINT6 + PangoFontMap *map; +#endif + PangoFontFamily **families; + int i, nfamilies; + bool 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_ascii_strcasecmp(pango_font_family_get_name(families[i]), + pango_font_description_get_family(desc))) { + matched = true; + break; + } + } + g_free(families); + + return matched; +} + +static unifont *pangofont_create_internal(GtkWidget *widget, + PangoContext *ctx, + PangoFontDescription *desc, + bool wide, bool bold, + int shadowoffset, bool shadowalways) +{ + struct pangofont *pfont; +#ifndef PANGO_PRE_1POINT6 + PangoFontMap *map; +#endif + PangoFontset *fset; + PangoFontMetrics *metrics; + +#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_CEIL(pango_font_metrics_get_ascent(metrics)); + pfont->u.descent = + PANGO_PIXELS_CEIL(pango_font_metrics_get_descent(metrics)); + pfont->u.height = pfont->u.ascent + pfont->u.descent; + pfont->u.strikethrough_y = + PANGO_PIXELS(pango_font_metrics_get_ascent(metrics) - + pango_font_metrics_get_strikethrough_position(metrics)); + pfont->u.want_fallback = false; +#ifdef DRAW_TEXT_CAIRO + pfont->u.preferred_drawtype = DRAWTYPE_CAIRO; +#elif defined DRAW_TEXT_GDK + pfont->u.preferred_drawtype = DRAWTYPE_GDK; +#else +#error No drawtype available at all +#endif + /* The Pango API is hardwired to UTF-8 */ + pfont->u.public_charset = CS_UTF8; + pfont->desc = desc; + pfont->fset = fset; + pfont->widget = widget; + pfont->bold = bold; + pfont->shadowoffset = shadowoffset; + pfont->shadowalways = shadowalways; + pfont->widthcache = NULL; + pfont->nwidthcache = 0; + + pango_font_metrics_unref(metrics); + + return &pfont->u; +} + +static unifont *pangofont_create(GtkWidget *widget, const char *name, + bool wide, bool bold, + int shadowoffset, bool shadowalways) +{ + PangoContext *ctx; + PangoFontDescription *desc; + + desc = pango_font_description_from_string(name); + if (!desc) + return NULL; + ctx = gtk_widget_get_pango_context(widget); + if (!ctx) { + pango_font_description_free(desc); + return NULL; + } + if (!pangofont_check_desc_makes_sense(ctx, desc)) { + pango_font_description_free(desc); + return NULL; + } + return pangofont_create_internal(widget, ctx, desc, wide, bold, + shadowoffset, shadowalways); +} + +static unifont *pangofont_create_fallback(GtkWidget *widget, int height, + bool wide, bool bold, + int shadowoffset, bool shadowalways) +{ + PangoContext *ctx; + PangoFontDescription *desc; + + desc = pango_font_description_from_string("Monospace"); + if (!desc) + return NULL; + ctx = gtk_widget_get_pango_context(widget); + if (!ctx) { + pango_font_description_free(desc); + return NULL; + } + pango_font_description_set_absolute_size(desc, height * PANGO_SCALE); + return pangofont_create_internal(widget, ctx, desc, wide, bold, + shadowoffset, shadowalways); +} + +static void pangofont_destroy(unifont *font) +{ + struct pangofont *pfont = container_of(font, struct pangofont, u); + pango_font_description_free(pfont->desc); + sfree(pfont->widthcache); + g_object_unref(pfont->fset); + sfree(pfont); +} + +static int pangofont_char_width(PangoLayout *layout, struct pangofont *pfont, + wchar_t uchr, const char *utfchr, int utflen) +{ + /* + * Here we check whether a character has the same width as the + * character cell it'll be drawn in. Because profiling showed that + * asking Pango for text sizes was a huge bottleneck when we were + * calling it every time we needed to know this, we instead call + * it only on characters we don't already know about, and cache + * the results. + */ + + if ((unsigned)uchr >= pfont->nwidthcache) { + unsigned newsize = ((int)uchr + 0x100) & ~0xFF; + pfont->widthcache = sresize(pfont->widthcache, newsize, int); + while (pfont->nwidthcache < newsize) + pfont->widthcache[pfont->nwidthcache++] = -1; + } + + if (pfont->widthcache[uchr] < 0) { + PangoRectangle rect; + pango_layout_set_text(layout, utfchr, utflen); + pango_layout_get_extents(layout, NULL, &rect); + pfont->widthcache[uchr] = rect.width; + } + + return pfont->widthcache[uchr]; +} + +static bool pangofont_has_glyph(unifont *font, wchar_t glyph) +{ + /* Pango implements font fallback, so assume it has everything */ + return true; +} + +#ifdef DRAW_TEXT_GDK +static void pango_gdk_draw_layout(unifont_drawctx *ctx, + gint x, gint y, PangoLayout *layout) +{ + gdk_draw_layout(ctx->u.gdk.target, ctx->u.gdk.gc, x, y, layout); +} +#endif + +#ifdef DRAW_TEXT_CAIRO +static void pango_cairo_draw_layout(unifont_drawctx *ctx, + gint x, gint y, PangoLayout *layout) +{ + cairo_move_to(ctx->u.cairo.cr, x, y); + pango_cairo_show_layout(ctx->u.cairo.cr, layout); +} +#endif + +static void pangofont_draw_internal(unifont_drawctx *ctx, unifont *font, + int x, int y, const wchar_t *string, + int len, bool wide, bool bold, + int cellwidth, bool combining) +{ + struct pangofont *pfont = container_of(font, struct pangofont, u); + PangoLayout *layout; + PangoRectangle rect; + char *utfstring, *utfptr; + int utflen; + bool shadowbold = false; + void (*draw_layout)(unifont_drawctx *ctx, + gint x, gint y, PangoLayout *layout) = NULL; + +#ifdef DRAW_TEXT_GDK + if (ctx->type == DRAWTYPE_GDK) { + draw_layout = pango_gdk_draw_layout; + } +#endif +#ifdef DRAW_TEXT_CAIRO + if (ctx->type == DRAWTYPE_CAIRO) { + draw_layout = pango_cairo_draw_layout; + } +#endif + assert(draw_layout); + + 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); + } + } + + /* + * Pango always expects UTF-8, so convert the input wide character + * string to UTF-8. + */ + utfstring = snewn(len*6+1, char); /* UTF-8 has max 6 bytes/char */ + utflen = wc_to_mb(CS_UTF8, 0, string, len, + utfstring, len*6+1, ".", NULL); + + utfptr = utfstring; + while (utflen > 0) { + int clen, n; + int desired = cellwidth * PANGO_SCALE; + + /* + * 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. + */ + + if (combining) { + /* + * For a character with combining stuff, we just dump the + * whole lot in one go, and expect it to take up just one + * character cell. + */ + clen = utflen; + n = 1; + } else { + /* + * Start by extracting a single UTF-8 character from the + * string. + */ + clen = 1; + while (clen < utflen && + (unsigned char)utfptr[clen] >= 0x80 && + (unsigned char)utfptr[clen] < 0xC0) + clen++; + n = 1; + + if (is_rtl(string[0]) || + pangofont_char_width(layout, pfont, string[n-1], + utfptr, clen) != desired) { + /* + * If this character is a right-to-left one, or has an + * unusual width, then we must display it on its own. + */ + } else { + /* + * Try to amalgamate a contiguous string of characters + * with the expected sensible width, for the common case + * in which we're using a monospaced font and everything + * works as expected. + */ + while (clen < utflen) { + int oldclen = clen; + clen++; /* skip UTF-8 introducer byte */ + while (clen < utflen && + (unsigned char)utfptr[clen] >= 0x80 && + (unsigned char)utfptr[clen] < 0xC0) + clen++; + n++; + if (is_rtl(string[n-1]) || + pangofont_char_width(layout, pfont, + string[n-1], utfptr + oldclen, + clen - oldclen) != desired) { + clen = oldclen; + n--; + break; + } + } + } + } + + pango_layout_set_text(layout, utfptr, clen); + pango_layout_get_pixel_extents(layout, NULL, &rect); + + draw_layout(ctx, + x + (n*cellwidth - rect.width)/2, + y + (pfont->u.height - rect.height)/2, layout); + if (shadowbold) + draw_layout(ctx, + x + (n*cellwidth - rect.width)/2 + pfont->shadowoffset, + y + (pfont->u.height - rect.height)/2, layout); + + utflen -= clen; + utfptr += clen; + string += n; + x += n * cellwidth; + } + + sfree(utfstring); + + g_object_unref(layout); +} + +static void pangofont_draw_text(unifont_drawctx *ctx, unifont *font, + int x, int y, const wchar_t *string, int len, + bool wide, bool bold, int cellwidth) +{ + pangofont_draw_internal(ctx, font, x, y, string, len, wide, bold, + cellwidth, false); +} + +static void pangofont_draw_combining(unifont_drawctx *ctx, unifont *font, + int x, int y, const wchar_t *string, + int len, bool wide, bool bold, + int cellwidth) +{ + wchar_t *tmpstring = NULL; + if (mk_wcwidth(string[0]) == 0) { + /* + * If we've been told to draw a sequence of _only_ combining + * characters, prefix a space so that they have something to + * combine with. + */ + tmpstring = snewn(len+1, wchar_t); + memcpy(tmpstring+1, string, len * sizeof(wchar_t)); + tmpstring[0] = L' '; + string = tmpstring; + len++; + } + pangofont_draw_internal(ctx, font, x, y, string, len, wide, bold, + cellwidth, true); + sfree(tmpstring); +} + +/* + * 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, *stylekey; + + pango_font_description_set_size(desc, sizes[k]); + + fullname = pango_font_description_to_string(desc); + + /* + * Construct the sorting key for font styles. + */ + { + strbuf *buf = strbuf_new(); + + int weight = pango_font_description_get_weight(desc); + /* Weight: normal, then lighter, then bolder */ + if (weight <= PANGO_WEIGHT_NORMAL) + weight = PANGO_WEIGHT_NORMAL - weight; + strbuf_catf(buf, "%4d", weight); + + strbuf_catf(buf, " %2d", + pango_font_description_get_style(desc)); + + int stretch = pango_font_description_get_stretch(desc); + /* Stretch: closer to normal sorts earlier */ + stretch = 2 * abs(PANGO_STRETCH_NORMAL - stretch) + + (stretch < PANGO_STRETCH_NORMAL); + strbuf_catf(buf, " %2d", stretch); + + strbuf_catf(buf, " %2d", + pango_font_description_get_variant(desc)); + + stylekey = strbuf_to_str(buf); + } + + /* + * 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); + + sfree(stylekey); + 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, + bool 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; +} + +static char *pangofont_size_increment(unifont *font, int increment) +{ + struct pangofont *pfont = container_of(font, struct pangofont, u); + PangoFontDescription *desc; + int size; + char *newname, *retname; + + desc = pango_font_description_copy_static(pfont->desc); + + size = pango_font_description_get_size(desc); + size += PANGO_SCALE * increment; + + if (size <= 0) { + retname = NULL; + } else { + pango_font_description_set_size(desc, size); + newname = pango_font_description_to_string(desc); + retname = dupcat(pfont->u.vt->prefix, ":", 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.) + * + * The 'multifont' subclass is omitted here, as discussed above. + */ +static const struct UnifontVtable *unifont_types[] = { +#if GTK_CHECK_VERSION(2,0,0) + &pangofont_vtable, +#endif +#ifndef NOT_X_WINDOWS + &x11font_vtable, +#endif +}; + +/* + * 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, bool wide, + bool bold, int shadowoffset, bool 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(unifont_drawctx *ctx, unifont *font, + int x, int y, const wchar_t *string, int len, + bool wide, bool bold, int cellwidth) +{ + font->vt->draw_text(ctx, font, x, y, string, len, wide, bold, cellwidth); +} + +void unifont_draw_combining(unifont_drawctx *ctx, unifont *font, + int x, int y, const wchar_t *string, int len, + bool wide, bool bold, int cellwidth) +{ + font->vt->draw_combining(ctx, font, x, y, string, len, wide, bold, + cellwidth); +} + +char *unifont_size_increment(unifont *font, int increment) +{ + return font->vt->size_increment(font, increment); +} + +/* ---------------------------------------------------------------------- + * Multiple-font wrapper. This is a type of unifont which encapsulates + * up to two other unifonts, permitting missing glyphs in the main + * font to be filled in by a fallback font. + * + * This is a type of unifont just like the previous two, but it has a + * separate constructor which is manually called by the client, so it + * doesn't appear in the list of available font types enumerated by + * unifont_create. This means it's not used by unifontsel either, so + * it doesn't need to support any methods except draw_text and + * destroy. + */ + +static void multifont_draw_text(unifont_drawctx *ctx, unifont *font, + int x, int y, const wchar_t *string, int len, + bool wide, bool bold, int cellwidth); +static void multifont_draw_combining(unifont_drawctx *ctx, unifont *font, + int x, int y, const wchar_t *string, + int len, bool wide, bool bold, + int cellwidth); +static void multifont_destroy(unifont *font); +static char *multifont_size_increment(unifont *font, int increment); + +struct multifont { + unifont *main; + unifont *fallback; + + struct unifont u; +}; + +static const UnifontVtable multifont_vtable = { + .create = NULL, /* creation is done specially */ + .create_fallback = NULL, + .destroy = multifont_destroy, + .has_glyph = NULL, + .draw_text = multifont_draw_text, + .draw_combining = multifont_draw_combining, + .enum_fonts = NULL, + .canonify_fontname = NULL, + .scale_fontname = NULL, + .size_increment = multifont_size_increment, + .prefix = "client", +}; + +unifont *multifont_create(GtkWidget *widget, const char *name, + bool wide, bool bold, + int shadowoffset, bool shadowalways) +{ + int i; + unifont *font, *fallback; + struct multifont *mfont; + + font = unifont_create(widget, name, wide, bold, + shadowoffset, shadowalways); + if (!font) + return NULL; + + fallback = NULL; + if (font->want_fallback) { + for (i = 0; i < lenof(unifont_types); i++) { + if (unifont_types[i]->create_fallback) { + fallback = unifont_types[i]->create_fallback + (widget, font->height, wide, bold, + shadowoffset, shadowalways); + if (fallback) + break; + } + } + } + + /* + * Construct our multifont. Public members are all copied from the + * primary font we're wrapping. + */ + mfont = snew(struct multifont); + mfont->u.vt = &multifont_vtable; + mfont->u.width = font->width; + mfont->u.ascent = font->ascent; + mfont->u.descent = font->descent; + mfont->u.height = font->height; + mfont->u.strikethrough_y = font->strikethrough_y; + mfont->u.public_charset = font->public_charset; + mfont->u.want_fallback = false; /* shouldn't be needed, but just in case */ + mfont->u.preferred_drawtype = font->preferred_drawtype; + mfont->main = font; + mfont->fallback = fallback; + + return &mfont->u; +} + +static void multifont_destroy(unifont *font) +{ + struct multifont *mfont = container_of(font, struct multifont, u); + unifont_destroy(mfont->main); + if (mfont->fallback) + unifont_destroy(mfont->fallback); + sfree(mfont); +} + +typedef void (*unifont_draw_func_t)(unifont_drawctx *ctx, unifont *font, + int x, int y, const wchar_t *string, + int len, bool wide, bool bold, + int cellwidth); + +static void multifont_draw_main(unifont_drawctx *ctx, unifont *font, int x, + int y, const wchar_t *string, int len, + bool wide, bool bold, int cellwidth, + int cellinc, unifont_draw_func_t draw) +{ + struct multifont *mfont = container_of(font, struct multifont, u); + unifont *f; + bool ok; + int i; + + while (len > 0) { + /* + * Find a maximal sequence of characters which are, or are + * not, supported by our main font. + */ + ok = mfont->main->vt->has_glyph(mfont->main, string[0]); + for (i = 1; + i < len && + !mfont->main->vt->has_glyph(mfont->main, string[i]) == !ok; + i++); + + /* + * Now display it. + */ + f = ok ? mfont->main : mfont->fallback; + if (f) + draw(ctx, f, x, y, string, i, wide, bold, cellwidth); + string += i; + len -= i; + x += i * cellinc; + } +} + +static void multifont_draw_text(unifont_drawctx *ctx, unifont *font, int x, + int y, const wchar_t *string, int len, + bool wide, bool bold, int cellwidth) +{ + multifont_draw_main(ctx, font, x, y, string, len, wide, bold, + cellwidth, cellwidth, unifont_draw_text); +} + +static void multifont_draw_combining(unifont_drawctx *ctx, unifont *font, + int x, int y, const wchar_t *string, + int len, bool wide, bool bold, + int cellwidth) +{ + multifont_draw_main(ctx, font, x, y, string, len, wide, bold, + cellwidth, 0, unifont_draw_combining); +} + +static char *multifont_size_increment(unifont *font, int increment) +{ + struct multifont *mfont = container_of(font, struct multifont, u); + return unifont_size_increment(mfont->main, increment); +} + +#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 { + GtkListStore *family_model, *style_model, *size_model; + GtkWidget *family_list, *style_list, *size_entry, *size_list; + GtkWidget *filter_buttons[4]; + int n_filter_buttons; + GtkWidget *preview_area; +#ifndef NO_BACKING_PIXMAPS + GdkPixmap *preview_pixmap; +#endif + 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; + bool inhibit_response; /* inhibit callbacks when we change GUI controls */ + + unifontsel u; +} 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 UnifontVtable *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_ascii_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_draw_preview_text(unifontsel_internal *fs); + +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); + unifontsel_draw_preview_text(fs); +} + +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; + + fs->inhibit_response = true; + + 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); + + fs->inhibit_response = false; +} + +static void unifontsel_setup_stylelist(unifontsel_internal *fs, + int start, int end) +{ + GtkTreeIter iter; + int i, listindex, minpos = -1, maxpos = -1; + bool 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, 4, PANGO_WEIGHT_NORMAL, -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, 4, PANGO_WEIGHT_BOLD, -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) { + /* _shouldn't_ happen unless font list is completely funted */ + break; + } + 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 < fs->n_filter_buttons; i++) { + int flagbit = GPOINTER_TO_INT(g_object_get_data + (G_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_inner(unifont_drawctx *dctx, + 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; + +#ifdef DRAW_TEXT_GDK + if (dctx->type == DRAWTYPE_GDK) { + gdk_gc_set_foreground(dctx->u.gdk.gc, &fs->preview_bg); + gdk_draw_rectangle(dctx->u.gdk.target, dctx->u.gdk.gc, 1, 0, 0, + fs->preview_width, fs->preview_height); + gdk_gc_set_foreground(dctx->u.gdk.gc, &fs->preview_fg); + } +#endif +#ifdef DRAW_TEXT_CAIRO + if (dctx->type == DRAWTYPE_CAIRO) { + cairo_set_source_rgb(dctx->u.cairo.cr, + fs->preview_bg.red / 65535.0, + fs->preview_bg.green / 65535.0, + fs->preview_bg.blue / 65535.0); + cairo_paint(dctx->u.cairo.cr); + cairo_set_source_rgb(dctx->u.cairo.cr, + fs->preview_fg.red / 65535.0, + fs->preview_fg.green / 65535.0, + fs->preview_fg.blue / 65535.0); + } +#endif + + 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(dctx, font, + 0, font->ascent, + L"bankrupt jilted showmen quiz convex fogey", + 41, false, false, font->width); + info->fontclass->draw_text(dctx, font, + 0, font->ascent + font->height, + L"BANKRUPT JILTED SHOWMEN QUIZ CONVEX FOGEY", + 41, false, false, font->width); + /* + * The ordering of punctuation here is also selected + * with some specific aims in mind. I put ` and ' + * together because some software (and people) still + * 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(dctx, font, + 0, font->ascent + font->height * 2, + L"0123456789!?,.:;<>()[]{}\\/`'\"+*-=~#_@|%&^$", + 42, false, false, font->width); + + info->fontclass->destroy(font); + } + + sfree(sizename); +} + +static void unifontsel_draw_preview_text(unifontsel_internal *fs) +{ + unifont_drawctx dctx; + GdkWindow *target; + +#ifndef NO_BACKING_PIXMAPS + target = fs->preview_pixmap; +#else + target = gtk_widget_get_window(fs->preview_area); +#endif + if (!target) /* we may be called when we haven't created everything yet */ + return; + + dctx.type = DRAWTYPE_DEFAULT; +#ifdef DRAW_TEXT_GDK + if (dctx.type == DRAWTYPE_GDK) { + dctx.u.gdk.target = target; + dctx.u.gdk.gc = gdk_gc_new(target); + } +#endif +#ifdef DRAW_TEXT_CAIRO + if (dctx.type == DRAWTYPE_CAIRO) { +#if GTK_CHECK_VERSION(3,22,0) + cairo_region_t *region; +#endif + + dctx.u.cairo.widget = GTK_WIDGET(fs->preview_area); + +#if GTK_CHECK_VERSION(3,22,0) + dctx.u.cairo.gdkwin = gtk_widget_get_window(dctx.u.cairo.widget); + region = gdk_window_get_clip_region(dctx.u.cairo.gdkwin); + dctx.u.cairo.drawctx = gdk_window_begin_draw_frame( + dctx.u.cairo.gdkwin, region); + dctx.u.cairo.cr = gdk_drawing_context_get_cairo_context( + dctx.u.cairo.drawctx); + cairo_region_destroy(region); +#else + dctx.u.cairo.cr = gdk_cairo_create(target); +#endif + } +#endif + + unifontsel_draw_preview_text_inner(&dctx, fs); + +#ifdef DRAW_TEXT_GDK + if (dctx.type == DRAWTYPE_GDK) { + gdk_gc_unref(dctx.u.gdk.gc); + } +#endif +#ifdef DRAW_TEXT_CAIRO + if (dctx.type == DRAWTYPE_CAIRO) { +#if GTK_CHECK_VERSION(3,22,0) + gdk_window_end_draw_frame(dctx.u.cairo.gdkwin, dctx.u.cairo.drawctx); +#else + cairo_destroy(dctx.u.cairo.cr); +#endif + } +#endif + + gdk_window_invalidate_rect(gtk_widget_get_window(fs->preview_area), + NULL, false); +} + +static void unifontsel_select_font(unifontsel_internal *fs, + fontinfo *info, int size, int leftlist, + bool size_is_explicit) +{ + int index; + int minval, maxval; + gboolean success; + 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); + success = gtk_tree_model_get_iter(GTK_TREE_MODEL(fs->family_model), + &iter, treepath); + assert(success); + 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_editable_set_editable(GTK_EDITABLE(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; + bool newstate = gtk_toggle_button_get_active(tb); + int newflags; + int flagbit = GPOINTER_TO_INT(g_object_get_data(G_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 UnifontVtable *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); + if (!below) + pos = -1; + above = index234(fs->fonts_by_selorder, pos+1); + + /* + * See if we've found it exactly, which is an easy special + * case. If we have, it'll be in `below' and not `above', + * because we did a REL234_LE rather than REL234_LT search. + */ + if (below && !fontinfo_selorder_compare(&info2, below)) + return below; + + /* + * Now we've either found two suitable fonts, one smaller and + * one larger, or we're at one or other extreme end of the + * 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); + if (!info) + return; /* _shouldn't_ happen unless font list is completely funted */ + info = update_for_intended_size(fs, info); + if (!info) + return; /* similarly shouldn't happen */ + 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); + if (!info) + return; /* _shouldn't_ happen unless font list is completely funted */ + info = update_for_intended_size(fs, info); + if (!info) + return; /* similarly shouldn't happen */ + 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); + if (!info) + return; /* _shouldn't_ happen unless font list is completely funted */ + 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); + } +} + +#if GTK_CHECK_VERSION(3,0,0) +static gint unifontsel_draw_area(GtkWidget *widget, cairo_t *cr, gpointer data) +{ + unifontsel_internal *fs = (unifontsel_internal *)data; + unifont_drawctx dctx; + + dctx.type = DRAWTYPE_CAIRO; + dctx.u.cairo.widget = widget; + dctx.u.cairo.cr = cr; + unifontsel_draw_preview_text_inner(&dctx, fs); + + return true; +} +#else +static gint unifontsel_expose_area(GtkWidget *widget, GdkEventExpose *event, + gpointer data) +{ + unifontsel_internal *fs = (unifontsel_internal *)data; + +#ifndef NO_BACKING_PIXMAPS + if (fs->preview_pixmap) { + gdk_draw_pixmap(gtk_widget_get_window(widget), + (gtk_widget_get_style(widget)->fg_gc + [gtk_widget_get_state(widget)]), + fs->preview_pixmap, + event->area.x, event->area.y, + event->area.x, event->area.y, + event->area.width, event->area.height); + } +#else + unifontsel_draw_preview_text(fs); +#endif + + return true; +} +#endif + +static gint unifontsel_configure_area(GtkWidget *widget, + GdkEventConfigure *event, gpointer data) +{ +#ifndef NO_BACKING_PIXMAPS + 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(gtk_widget_get_window(widget), + nx, ny, -1); + fs->preview_width = nx; + fs->preview_height = ny; + + unifontsel_draw_preview_text(fs); + } +#endif + + gdk_window_invalidate_rect(gtk_widget_get_window(widget), 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; + + { + int width, height; + + /* + * Invent some magic size constants. + */ + get_label_text_dimensions("Quite Long Font Name (Foundry)", + &width, &height); + font_width = width; + lists_height = 14 * height; + preview_height = 5 * height; + + get_label_text_dimensions("Italic Extra Condensed", &width, &height); + style_width = width; + + get_label_text_dimensions("48000", &width, &height); + size_width = width; + } + + /* + * 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), STANDARD_CANCEL_LABEL, GTK_RESPONSE_CANCEL); + fs->u.ok_button = gtk_dialog_add_button + (GTK_DIALOG(fs->u.window), STANDARD_OK_LABEL, 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. + */ +#if GTK_CHECK_VERSION(3,0,0) + table = gtk_grid_new(); + gtk_grid_set_column_spacing(GTK_GRID(table), 8); +#else + table = gtk_table_new(8, 3, false); + gtk_table_set_col_spacings(GTK_TABLE(table), 8); +#endif + gtk_widget_show(table); + +#if GTK_CHECK_VERSION(3,0,0) + /* GtkAlignment has become deprecated and we use the "margin" + * property */ + w = table; + g_object_set(G_OBJECT(w), "margin", 8, (const char *)NULL); +#elif 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 + /* In GTK < 2.4, even that isn't available */ + w = table; +#endif + + gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area + (GTK_DIALOG(fs->u.window))), + w, true, true, 0); + + label = gtk_label_new_with_mnemonic("_Font:"); + gtk_widget_show(label); + align_label_left(GTK_LABEL(label)); +#if GTK_CHECK_VERSION(3,0,0) + gtk_grid_attach(GTK_GRID(table), label, 0, 0, 1, 1); + g_object_set(G_OBJECT(label), "hexpand", true, (const char *)NULL); +#else + gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, 1, GTK_FILL, 0, 0, 0); +#endif + + /* + * 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); +#if GTK_CHECK_VERSION(3,0,0) + gtk_grid_attach(GTK_GRID(table), scroll, 0, 1, 1, 2); + g_object_set(G_OBJECT(scroll), "expand", true, (const char *)NULL); +#else + gtk_table_attach(GTK_TABLE(table), scroll, 0, 1, 1, 3, GTK_FILL, + GTK_EXPAND | GTK_FILL, 0, 0); +#endif + fs->family_model = model; + fs->family_list = w; + + label = gtk_label_new_with_mnemonic("_Style:"); + gtk_widget_show(label); + align_label_left(GTK_LABEL(label)); +#if GTK_CHECK_VERSION(3,0,0) + gtk_grid_attach(GTK_GRID(table), label, 1, 0, 1, 1); + g_object_set(G_OBJECT(label), "hexpand", true, (const char *)NULL); +#else + gtk_table_attach(GTK_TABLE(table), label, 1, 2, 0, 1, GTK_FILL, 0, 0, 0); +#endif + + /* + * 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. Also, since GTK3 at + * least doesn't seem to display insensitive elements differently + * by default, we add a further column to change their style. + */ + model = gtk_list_store_new(5, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT, + G_TYPE_BOOLEAN, 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 + ("Style", gtk_cell_renderer_text_new(), + "text", 0, "sensitive", 3, "weight", 4, (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); +#if GTK_CHECK_VERSION(3,0,0) + gtk_grid_attach(GTK_GRID(table), scroll, 1, 1, 1, 2); + g_object_set(G_OBJECT(scroll), "expand", true, (const char *)NULL); +#else + gtk_table_attach(GTK_TABLE(table), scroll, 1, 2, 1, 3, GTK_FILL, + GTK_EXPAND | GTK_FILL, 0, 0); +#endif + fs->style_model = model; + fs->style_list = w; + + label = gtk_label_new_with_mnemonic("Si_ze:"); + gtk_widget_show(label); + align_label_left(GTK_LABEL(label)); +#if GTK_CHECK_VERSION(3,0,0) + gtk_grid_attach(GTK_GRID(table), label, 2, 0, 1, 1); + g_object_set(G_OBJECT(label), "hexpand", true, (const char *)NULL); +#else + gtk_table_attach(GTK_TABLE(table), label, 2, 3, 0, 1, GTK_FILL, 0, 0, 0); +#endif + + /* + * 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); +#if GTK_CHECK_VERSION(3,0,0) + gtk_grid_attach(GTK_GRID(table), w, 2, 1, 1, 1); + g_object_set(G_OBJECT(w), "hexpand", true, (const char *)NULL); +#else + gtk_table_attach(GTK_TABLE(table), w, 2, 3, 1, 2, GTK_FILL, 0, 0, 0); +#endif + 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); +#if GTK_CHECK_VERSION(3,0,0) + gtk_grid_attach(GTK_GRID(table), scroll, 2, 2, 1, 1); + g_object_set(G_OBJECT(scroll), "expand", true, (const char *)NULL); +#else + gtk_table_attach(GTK_TABLE(table), scroll, 2, 3, 2, 3, GTK_FILL, + GTK_EXPAND | GTK_FILL, 0, 0); +#endif + fs->size_model = model; + fs->size_list = w; + + /* + * Preview widget. + */ + fs->preview_area = gtk_drawing_area_new(); +#ifndef NO_BACKING_PIXMAPS + fs->preview_pixmap = NULL; +#endif + 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; +#if !GTK_CHECK_VERSION(3,0,0) + 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); +#endif +#if GTK_CHECK_VERSION(3,0,0) + g_signal_connect(G_OBJECT(fs->preview_area), "draw", + G_CALLBACK(unifontsel_draw_area), fs); +#else + g_signal_connect(G_OBJECT(fs->preview_area), "expose_event", + G_CALLBACK(unifontsel_expose_area), fs); +#endif + g_signal_connect(G_OBJECT(fs->preview_area), "configure_event", + G_CALLBACK(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(3,0,0) + /* GtkAlignment has become deprecated and we use the "margin" + * property */ + g_object_set(G_OBJECT(w), "margin", 8, (const char *)NULL); +#elif 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); +#if GTK_CHECK_VERSION(3,0,0) + gtk_grid_attach(GTK_GRID(table), w, 0, 3, 3, 1); + g_object_set(G_OBJECT(w), "expand", true, (const char *)NULL); +#else + gtk_table_attach(GTK_TABLE(table), w, 0, 3, 3, 4, + GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 8); +#endif + + /* + * We only provide the checkboxes for client- and server-side + * fonts if we have the X11 back end available, because that's the + * only situation in which more than one class of font is + * available anyway. + */ + fs->n_filter_buttons = 0; +#ifndef NOT_X_WINDOWS + w = gtk_check_button_new_with_label("Show client-side fonts"); + g_object_set_data(G_OBJECT(w), "user-data", + GINT_TO_POINTER(FONTFLAG_CLIENTSIDE)); + g_signal_connect(G_OBJECT(w), "toggled", + G_CALLBACK(unifontsel_button_toggled), fs); + gtk_widget_show(w); + fs->filter_buttons[fs->n_filter_buttons++] = w; +#if GTK_CHECK_VERSION(3,0,0) + gtk_grid_attach(GTK_GRID(table), w, 0, 4, 3, 1); + g_object_set(G_OBJECT(w), "hexpand", true, (const char *)NULL); +#else + gtk_table_attach(GTK_TABLE(table), w, 0, 3, 4, 5, GTK_FILL, 0, 0, 0); +#endif + w = gtk_check_button_new_with_label("Show server-side fonts"); + g_object_set_data(G_OBJECT(w), "user-data", + GINT_TO_POINTER(FONTFLAG_SERVERSIDE)); + g_signal_connect(G_OBJECT(w), "toggled", + G_CALLBACK(unifontsel_button_toggled), fs); + gtk_widget_show(w); + fs->filter_buttons[fs->n_filter_buttons++] = w; +#if GTK_CHECK_VERSION(3,0,0) + gtk_grid_attach(GTK_GRID(table), w, 0, 5, 3, 1); + g_object_set(G_OBJECT(w), "hexpand", true, (const char *)NULL); +#else + gtk_table_attach(GTK_TABLE(table), w, 0, 3, 5, 6, GTK_FILL, 0, 0, 0); +#endif + w = gtk_check_button_new_with_label("Show server-side font aliases"); + g_object_set_data(G_OBJECT(w), "user-data", + GINT_TO_POINTER(FONTFLAG_SERVERALIAS)); + g_signal_connect(G_OBJECT(w), "toggled", + G_CALLBACK(unifontsel_button_toggled), fs); + gtk_widget_show(w); + fs->filter_buttons[fs->n_filter_buttons++] = w; +#if GTK_CHECK_VERSION(3,0,0) + gtk_grid_attach(GTK_GRID(table), w, 0, 6, 3, 1); + g_object_set(G_OBJECT(w), "hexpand", true, (const char *)NULL); +#else + gtk_table_attach(GTK_TABLE(table), w, 0, 3, 6, 7, GTK_FILL, 0, 0, 0); +#endif +#endif /* NOT_X_WINDOWS */ + w = gtk_check_button_new_with_label("Show non-monospaced fonts"); + g_object_set_data(G_OBJECT(w), "user-data", + GINT_TO_POINTER(FONTFLAG_NONMONOSPACED)); + g_signal_connect(G_OBJECT(w), "toggled", + G_CALLBACK(unifontsel_button_toggled), fs); + gtk_widget_show(w); + fs->filter_buttons[fs->n_filter_buttons++] = w; +#if GTK_CHECK_VERSION(3,0,0) + gtk_grid_attach(GTK_GRID(table), w, 0, 7, 3, 1); + g_object_set(G_OBJECT(w), "hexpand", true, (const char *)NULL); +#else + gtk_table_attach(GTK_TABLE(table), w, 0, 3, 7, 8, GTK_FILL, 0, 0, 0); +#endif + + assert(fs->n_filter_buttons <= 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 &fs->u; +} + +void unifontsel_destroy(unifontsel *fontsel) +{ + unifontsel_internal *fs = container_of(fontsel, unifontsel_internal, u); + fontinfo *info; + +#ifndef NO_BACKING_PIXMAPS + if (fs->preview_pixmap) + gdk_pixmap_unref(fs->preview_pixmap); +#endif + + 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 = container_of(fontsel, unifontsel_internal, u); + 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 = DEFAULT_GTK_FONT; + + /* + * 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 = container_of(fontsel, unifontsel_internal, u); + 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); + sfree(name); + return ret; + } + } + + return dupcat(fs->selected->fontclass->prefix, ":", + fs->selected->realname); +} + +#endif /* GTK_CHECK_VERSION(2,0,0) */ diff --git a/unix/unifont.h b/unix/unifont.h new file mode 100644 index 00000000..e43a748d --- /dev/null +++ b/unix/unifont.h @@ -0,0 +1,186 @@ +/* + * 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 + +/* + * We support two entirely different drawing systems: the old + * GDK1/GDK2 one which works on server-side X drawables, and the + * new-style Cairo one. GTK1 only supports GDK drawing; GTK3 only + * supports Cairo; GTK2 supports both, but deprecates GTK, so we only + * enable it if we aren't trying on purpose to compile without the + * deprecated functions. + * + * Our different font classes may prefer different drawing systems: X + * server-side fonts are a lot faster to draw with GDK, but for + * everything else we prefer Cairo, on general grounds of modernness + * and also in particular because its matrix-based scaling system + * gives much nicer results for double-width and double-height text + * when a scalable font is in use. + */ +#if !GTK_CHECK_VERSION(3,0,0) && !defined GDK_DISABLE_DEPRECATED +#define DRAW_TEXT_GDK +#endif +#if GTK_CHECK_VERSION(2,8,0) +#define DRAW_TEXT_CAIRO +#endif + +#if GTK_CHECK_VERSION(3,0,0) || defined GDK_DISABLE_DEPRECATED +/* + * Where the facility is available, we prefer to render text on to a + * persistent server-side pixmap, and redraw windows by simply + * blitting rectangles of that pixmap into them as needed. This is + * better for performance since we avoid expensive font rendering + * calls where possible, and it's particularly good over a non-local X + * connection because the response to an expose event can now be a + * very simple rectangle-copy operation rather than a lot of fiddly + * drawing or bitmap transfer. + * + * However, GTK is deprecating the use of server-side pixmaps, so we + * have to disable this mode under some circumstances. + */ +#define NO_BACKING_PIXMAPS +#endif + +/* + * Exports from gtkfont.c. + */ +typedef struct UnifontVtable UnifontVtable; /* contents internal to + * gtkfont.c */ +typedef struct unifont { + const struct UnifontVtable *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'. + */ + int public_charset; + + /* + * Font dimensions needed by clients. + */ + int width, height, ascent, descent, strikethrough_y; + + /* + * Indicates whether this font is capable of handling all glyphs + * (Pango fonts can do this because Pango automatically supplies + * missing glyphs from other fonts), or whether it would like a + * fallback font to cope with missing glyphs. + */ + bool want_fallback; + + /* + * Preferred drawing API to use when this class of font is active. + * (See the enum below, in unifont_drawctx.) + */ + int preferred_drawtype; +} unifont; + +/* A default drawtype, for the case where no font exists to make the + * decision with. */ +#ifdef DRAW_TEXT_CAIRO +#define DRAW_DEFAULT_CAIRO +#define DRAWTYPE_DEFAULT DRAWTYPE_CAIRO +#elif defined DRAW_TEXT_GDK +#define DRAW_DEFAULT_GDK +#define DRAWTYPE_DEFAULT DRAWTYPE_GDK +#else +#error No drawtype available at all +#endif + +/* + * Drawing context passed in to unifont_draw_text, which contains + * everything required to know where and how to draw the requested + * text. + */ +typedef struct unifont_drawctx { + enum { +#ifdef DRAW_TEXT_GDK + DRAWTYPE_GDK, +#endif +#ifdef DRAW_TEXT_CAIRO + DRAWTYPE_CAIRO, +#endif + DRAWTYPE_NTYPES + } type; + union { +#ifdef DRAW_TEXT_GDK + struct { + GdkDrawable *target; + GdkGC *gc; + } gdk; +#endif +#ifdef DRAW_TEXT_CAIRO + struct { + /* Need an actual widget, in order to backtrack to its X + * screen number when creating server-side pixmaps */ + GtkWidget *widget; + cairo_t *cr; + cairo_matrix_t origmatrix; +#if GTK_CHECK_VERSION(3,22,0) + GdkWindow *gdkwin; + GdkDrawingContext *drawctx; +#endif + } cairo; +#endif + } u; +} unifont_drawctx; + +unifont *unifont_create(GtkWidget *widget, const char *name, + bool wide, bool bold, + int shadowoffset, bool shadowalways); +void unifont_destroy(unifont *font); +void unifont_draw_text(unifont_drawctx *ctx, unifont *font, + int x, int y, const wchar_t *string, int len, + bool wide, bool bold, int cellwidth); +/* Same as unifont_draw_text, but expects 'string' to contain one + * normal char plus combining chars, and overdraws them all in the + * same character cell. */ +void unifont_draw_combining(unifont_drawctx *ctx, unifont *font, + int x, int y, const wchar_t *string, int len, + bool wide, bool bold, int cellwidth); +/* Return a name that will select a bigger/smaller font than this one, + * or NULL if no such name is available. */ +char *unifont_size_increment(unifont *font, int increment); + +/* + * This function behaves exactly like the low-level unifont_create, + * except that as well as the requested font it also allocates (if + * necessary) a fallback font for filling in replacement glyphs. + * + * Return value is usable with unifont_destroy and unifont_draw_text + * as if it were an ordinary unifont. + */ +unifont *multifont_create(GtkWidget *widget, const char *name, + bool wide, bool bold, + int shadowoffset, bool shadowalways); + +/* + * Unified font selector dialog. I can't be bothered to do a + * proper GTK subclassing today, so this will just be an ordinary + * data structure with some useful members. + * + * (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 */ diff --git a/unix/uppity.c b/unix/uppity.c new file mode 100644 index 00000000..1a35451b --- /dev/null +++ b/unix/uppity.c @@ -0,0 +1,850 @@ +/* + * SSH server for Unix: main program. + * + * ====================================================================== + * + * This server is NOT SECURE! + * + * DO NOT DEPLOY IT IN A HOSTILE-FACING ENVIRONMENT! + * + * Its purpose is to speak the server end of everything PuTTY speaks + * on the client side, so that I can test that I haven't broken PuTTY + * when I reorganise its code, even things like RSA key exchange or + * chained auth methods which it's hard to find a server that speaks + * at all. + * + * It has no interaction with the OS's authentication system: the + * authentications it will accept are configurable by command-line + * option, and once you authenticate, it will run the connection + * protocol - including all subprocesses and shells - under the same + * Unix user id you started it under. + * + * It really is only suitable for testing the actual SSH protocol. + * Don't use it for anything more serious! + * + * ====================================================================== + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "putty.h" +#include "mpint.h" +#include "ssh.h" +#include "ssh/server.h" + +const char *const appname = "uppity"; + +void modalfatalbox(const 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 nonfatal(const char *p, ...) +{ + va_list ap; + fprintf(stderr, "ERROR: "); + va_start(ap, p); + vfprintf(stderr, p, ap); + va_end(ap); + fputc('\n', stderr); +} + +char *platform_default_s(const char *name) +{ + return NULL; +} + +bool platform_default_b(const char *name, bool def) +{ + return def; +} + +int platform_default_i(const char *name, int def) +{ + return def; +} + +FontSpec *platform_default_fontspec(const char *name) +{ + return fontspec_new(""); +} + +Filename *platform_default_filename(const char *name) +{ + return filename_from_str(""); +} + +char *x_get_default(const char *key) +{ + return NULL; /* this is a stub */ +} + +void old_keyfile_warning(void) { } + +void timer_change_notify(unsigned long next) +{ +} + +char *platform_get_x_display(void) { return NULL; } + +void make_unix_sftp_filehandle_key(void *data, size_t size) +{ + random_read(data, size); +} + +static bool verbose; + +struct AuthPolicyShared { + struct AuthPolicy_ssh1_pubkey *ssh1keys; + struct AuthPolicy_ssh2_pubkey *ssh2keys; +}; + +struct AuthPolicy { + struct AuthPolicyShared *shared; + int kbdint_state; +}; + +struct server_instance { + unsigned id; + AuthPolicy ap; + LogPolicy logpolicy; +}; + +static void log_to_stderr(unsigned id, const char *msg) +{ + if (id != (unsigned)-1) + fprintf(stderr, "#%u: ", id); + fputs(msg, stderr); + fputc('\n', stderr); + fflush(stderr); +} + +static void server_eventlog(LogPolicy *lp, const char *event) +{ + struct server_instance *inst = container_of( + lp, struct server_instance, logpolicy); + if (verbose) + log_to_stderr(inst->id, event); +} + +static void server_logging_error(LogPolicy *lp, const char *event) +{ + struct server_instance *inst = container_of( + lp, struct server_instance, logpolicy); + log_to_stderr(inst->id, event); /* unconditional */ +} + +static int server_askappend( + LogPolicy *lp, Filename *filename, + void (*callback)(void *ctx, int result), void *ctx) +{ + return 2; /* always overwrite (FIXME: could make this a cmdline option) */ +} + +static const LogPolicyVtable server_logpolicy_vt = { + .eventlog = server_eventlog, + .askappend = server_askappend, + .logging_error = server_logging_error, + .verbose = null_lp_verbose_no, +}; + +struct AuthPolicy_ssh1_pubkey { + RSAKey key; + struct AuthPolicy_ssh1_pubkey *next; +}; +struct AuthPolicy_ssh2_pubkey { + ptrlen public_blob; + struct AuthPolicy_ssh2_pubkey *next; +}; + +unsigned auth_methods(AuthPolicy *ap) +{ + return (AUTHMETHOD_PUBLICKEY | AUTHMETHOD_PASSWORD | AUTHMETHOD_KBDINT | + AUTHMETHOD_TIS | AUTHMETHOD_CRYPTOCARD); +} +bool auth_none(AuthPolicy *ap, ptrlen username) +{ + return false; +} +int auth_password(AuthPolicy *ap, ptrlen username, ptrlen password, + ptrlen *new_password_opt) +{ + const char *PHONY_GOOD_PASSWORD = "weasel"; + const char *PHONY_BAD_PASSWORD = "ferret"; + + if (!new_password_opt) { + /* Accept login with our preconfigured good password */ + if (ptrlen_eq_string(password, PHONY_GOOD_PASSWORD)) + return 1; + /* Don't outright reject the bad password, but insist on a change */ + if (ptrlen_eq_string(password, PHONY_BAD_PASSWORD)) + return 2; + /* Reject anything else */ + return 0; + } else { + /* In a password-change request, expect the bad password as input */ + if (!ptrlen_eq_string(password, PHONY_BAD_PASSWORD)) + return 0; + /* Accept a request to change it to the good password */ + if (ptrlen_eq_string(*new_password_opt, PHONY_GOOD_PASSWORD)) + return 1; + /* Outright reject a request to change it to the same password + * as it already 'was' */ + if (ptrlen_eq_string(*new_password_opt, PHONY_BAD_PASSWORD)) + return 0; + /* Anything else, pretend the new pw wasn't good enough, and + * re-request a change */ + return 2; + } +} +bool auth_publickey(AuthPolicy *ap, ptrlen username, ptrlen public_blob) +{ + struct AuthPolicy_ssh2_pubkey *iter; + for (iter = ap->shared->ssh2keys; iter; iter = iter->next) { + if (ptrlen_eq_ptrlen(public_blob, iter->public_blob)) + return true; + } + return false; +} +RSAKey *auth_publickey_ssh1( + AuthPolicy *ap, ptrlen username, mp_int *rsa_modulus) +{ + struct AuthPolicy_ssh1_pubkey *iter; + for (iter = ap->shared->ssh1keys; iter; iter = iter->next) { + if (mp_cmp_eq(rsa_modulus, iter->key.modulus)) + return &iter->key; + } + return NULL; +} +AuthKbdInt *auth_kbdint_prompts(AuthPolicy *ap, ptrlen username) +{ + AuthKbdInt *aki; + + switch (ap->kbdint_state) { + case 0: + aki = snew(AuthKbdInt); + aki->title = dupstr("Initial double prompt"); + aki->instruction = + dupstr("First prompt should echo, second should not"); + aki->nprompts = 2; + aki->prompts = snewn(aki->nprompts, AuthKbdIntPrompt); + aki->prompts[0].prompt = dupstr("Echoey prompt: "); + aki->prompts[0].echo = true; + aki->prompts[1].prompt = dupstr("Silent prompt: "); + aki->prompts[1].echo = false; + return aki; + case 1: + aki = snew(AuthKbdInt); + aki->title = dupstr("Zero-prompt step"); + aki->instruction = dupstr("Shouldn't see any prompts this time"); + aki->nprompts = 0; + aki->prompts = NULL; + return aki; + default: + ap->kbdint_state = 0; + return NULL; + } +} +int auth_kbdint_responses(AuthPolicy *ap, const ptrlen *responses) +{ + switch (ap->kbdint_state) { + case 0: + if (ptrlen_eq_string(responses[0], "stoat") && + ptrlen_eq_string(responses[1], "weasel")) { + ap->kbdint_state++; + return 0; /* those are the expected responses */ + } else { + ap->kbdint_state = 0; + return -1; + } + break; + case 1: + return +1; /* succeed after the zero-prompt step */ + default: + ap->kbdint_state = 0; + return -1; + } +} +char *auth_ssh1int_challenge(AuthPolicy *ap, unsigned method, ptrlen username) +{ + /* FIXME: test returning a challenge string without \n, and ensure + * it gets printed as a prompt in its own right, without PuTTY + * making up a "Response: " prompt to follow it */ + return dupprintf("This is a dummy %s challenge!\n", + (method == AUTHMETHOD_TIS ? "TIS" : "CryptoCard")); +} +bool auth_ssh1int_response(AuthPolicy *ap, ptrlen response) +{ + return ptrlen_eq_string(response, "otter"); +} +bool auth_successful(AuthPolicy *ap, ptrlen username, unsigned method) +{ + return true; +} + +static void safety_warning(FILE *fp) +{ + fputs(" =================================================\n" + " THIS SSH SERVER IS NOT WRITTEN TO BE SECURE!\n" + " DO NOT DEPLOY IT IN A HOSTILE-FACING ENVIRONMENT!\n" + " =================================================\n", fp); +} + +static void show_help(FILE *fp) +{ + safety_warning(fp); + fputs("\n" + "usage: uppity [options]\n" + "options: --listen [PORT|PATH] listen to a port on localhost, or Unix socket\n" + " --listen-once (with --listen) stop after one " + "connection\n" + " --hostkey KEY SSH host key (need at least one)\n" + " --rsakexkey KEY key for SSH-2 RSA key exchange " + "(in SSH-1 format)\n" + " --userkey KEY public key" + " acceptable for user authentication\n" + " --sessiondir DIR cwd for session subprocess (default $HOME)\n" + " --bannertext TEXT send TEXT as SSH-2 auth banner\n" + " --bannerfile FILE send contents of FILE as SSH-2 auth " + "banner\n" + " --kexinit-kex STR override list of SSH-2 KEX methods\n" + " --kexinit-hostkey STR override list of SSH-2 host key " + "types\n" + " --kexinit-cscipher STR override list of SSH-2 " + "client->server ciphers\n" + " --kexinit-sccipher STR override list of SSH-2 " + "server->client ciphers\n" + " --kexinit-csmac STR override list of SSH-2 " + "client->server MACs\n" + " --kexinit-scmac STR override list of SSH-2 " + "server->client MACs\n" + " --kexinit-cscomp STR override list of SSH-2 " + "c->s compression types\n" + " --kexinit-sccomp STR override list of SSH-2 " + "s->c compression types\n" + " --ssh1-ciphers STR override list of SSH-1 ciphers\n" + " --ssh1-no-compression forbid compression in SSH-1\n" + " --exitsignum send buggy numeric \"exit-signal\" " + "message\n" + " --verbose print event log messages to standard " + "error\n" + " --sshlog FILE write SSH packet log to FILE\n" + " --sshrawlog FILE write SSH packets + raw data log" + " to FILE\n" + "also: uppity --help show this text\n" + " uppity --version show version information\n" + "\n", fp); + safety_warning(fp); +} + +static void show_version_and_exit(void) +{ + char *buildinfo_text = buildinfo("\n"); + printf("%s: %s\n%s\n", appname, ver, buildinfo_text); + sfree(buildinfo_text); + exit(0); +} + +const bool buildinfo_gtk_relevant = false; + +static bool listening = false, listen_once = false; +static bool finished = false; +void server_instance_terminated(LogPolicy *lp) +{ + struct server_instance *inst = container_of( + lp, struct server_instance, logpolicy); + + if (listening && !listen_once) { + log_to_stderr(inst->id, "connection terminated"); + } else { + finished = true; + } + + sfree(inst); +} + +static bool longoptarg(const char *arg, const char *expected, + const char **val, int *argcp, char ***argvp) +{ + int len = strlen(expected); + if (memcmp(arg, expected, len)) + return false; + if (arg[len] == '=') { + *val = arg + len + 1; + return true; + } else if (arg[len] == '\0') { + if (--*argcp > 0) { + *val = *++*argvp; + return true; + } else { + fprintf(stderr, "%s: option %s expects an argument\n", + appname, expected); + exit(1); + } + } + return false; +} + +static bool longoptnoarg(const char *arg, const char *expected) +{ + int len = strlen(expected); + if (memcmp(arg, expected, len)) + return false; + if (arg[len] == '=') { + fprintf(stderr, "%s: option %s expects no argument\n", + appname, expected); + exit(1); + } else if (arg[len] == '\0') { + return true; + } + return false; +} + +struct server_config { + Conf *conf; + const SshServerConfig *ssc; + + ssh_key **hostkeys; + int nhostkeys; + + RSAKey *hostkey1; + + struct AuthPolicyShared *ap_shared; + + unsigned next_id; + + Socket *listening_socket; + Plug listening_plug; +}; + +static Plug *server_conn_plug( + struct server_config *cfg, struct server_instance **inst_out) +{ + struct server_instance *inst = snew(struct server_instance); + + memset(inst, 0, sizeof(*inst)); + + inst->id = cfg->next_id++; + inst->ap.shared = cfg->ap_shared; + inst->logpolicy.vt = &server_logpolicy_vt; + + if (inst_out) + *inst_out = inst; + + return ssh_server_plug( + cfg->conf, cfg->ssc, cfg->hostkeys, cfg->nhostkeys, cfg->hostkey1, + &inst->ap, &inst->logpolicy, &unix_live_sftpserver_vt); +} + +static void server_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, + const char *error_msg, int error_code) +{ + log_to_stderr((unsigned)-1, error_msg); +} + +static void server_closing(Plug *plug, const char *error_msg, int error_code, + bool calling_back) +{ + log_to_stderr((unsigned)-1, error_msg); +} + +static int server_accepting(Plug *p, accept_fn_t constructor, accept_ctx_t ctx) +{ + struct server_config *cfg = container_of( + p, struct server_config, listening_plug); + Socket *s; + const char *err; + + struct server_instance *inst; + + if (listen_once) { + if (!cfg->listening_socket) /* in case of rapid double-accept */ + return 1; + sk_close(cfg->listening_socket); + cfg->listening_socket = NULL; + } + + unsigned old_next_id = cfg->next_id; + + Plug *plug = server_conn_plug(cfg, &inst); + s = constructor(ctx, plug); + if ((err = sk_socket_error(s)) != NULL) + return 1; + + SocketPeerInfo *pi = sk_peer_info(s); + + if (pi->addressfamily != ADDRTYPE_LOCAL && !sk_peer_trusted(s)) { + fprintf(stderr, "rejected connection from %s (untrustworthy peer)\n", + pi->log_text); + sk_free_peer_info(pi); + sk_close(s); + cfg->next_id = old_next_id; + return 1; + } + + char *msg = dupprintf("new connection from %s", pi->log_text); + log_to_stderr(inst->id, msg); + sfree(msg); + sk_free_peer_info(pi); + + sk_set_frozen(s, false); + ssh_server_start(plug, s); + return 0; +} + +static const PlugVtable server_plugvt = { + .log = server_log, + .closing = server_closing, + .accepting = server_accepting, +}; + +int main(int argc, char **argv) +{ + int listen_port = -1; + const char *listen_socket = NULL; + + ssh_key **hostkeys = NULL; + size_t nhostkeys = 0, hostkeysize = 0; + RSAKey *hostkey1 = NULL; + + struct AuthPolicyShared aps; + SshServerConfig ssc; + + Conf *conf = make_ssh_server_conf(); + + aps.ssh1keys = NULL; + aps.ssh2keys = NULL; + + memset(&ssc, 0, sizeof(ssc)); + + ssc.application_name = "Uppity"; + ssc.session_starting_dir = getenv("HOME"); + ssc.ssh1_cipher_mask = SSH1_SUPPORTED_CIPHER_MASK; + ssc.ssh1_allow_compression = true; + + if (argc <= 1) { + /* + * We're going to terminate with an error message below, + * because there are no host keys. But we'll display the help + * as additional standard-error output, if nothing else so + * that people see the giant safety warning. + */ + show_help(stderr); + fputc('\n', stderr); + } + + while (--argc > 0) { + const char *arg = *++argv; + const char *val; + + if (!strcmp(arg, "--help")) { + show_help(stdout); + exit(0); + } else if (longoptnoarg(arg, "--version")) { + show_version_and_exit(); + } else if (longoptnoarg(arg, "--verbose") || !strcmp(arg, "-v")) { + verbose = true; + } else if (longoptarg(arg, "--listen", &val, &argc, &argv)) { + if (val[0] == '/') { + listen_port = -1; + listen_socket = val; + } else { + listen_port = atoi(val); + listen_socket = NULL; + } + } else if (!strcmp(arg, "--listen-once")) { + listen_once = true; + } else if (longoptarg(arg, "--hostkey", &val, &argc, &argv)) { + Filename *keyfile; + int keytype; + const char *error; + + keyfile = filename_from_str(val); + keytype = key_type(keyfile); + + if (keytype == SSH_KEYTYPE_SSH2) { + ssh2_userkey *uk; + ssh_key *key; + uk = ppk_load_f(keyfile, NULL, &error); + filename_free(keyfile); + if (!uk || !uk->key) { + fprintf(stderr, "%s: unable to load host key '%s': " + "%s\n", appname, val, error); + exit(1); + } + char *invalid = ssh_key_invalid(uk->key, 0); + if (invalid) { + fprintf(stderr, "%s: host key '%s' is unusable: " + "%s\n", appname, val, invalid); + exit(1); + } + key = uk->key; + sfree(uk->comment); + sfree(uk); + + for (int i = 0; i < nhostkeys; i++) + if (ssh_key_alg(hostkeys[i]) == ssh_key_alg(key)) { + fprintf(stderr, "%s: host key '%s' duplicates key " + "type %s\n", appname, val, + ssh_key_alg(key)->ssh_id); + exit(1); + } + + sgrowarray(hostkeys, hostkeysize, nhostkeys); + hostkeys[nhostkeys++] = key; + } else if (keytype == SSH_KEYTYPE_SSH1) { + if (hostkey1) { + fprintf(stderr, "%s: host key '%s' is a redundant " + "SSH-1 host key\n", appname, val); + exit(1); + } + hostkey1 = snew(RSAKey); + if (!rsa1_load_f(keyfile, hostkey1, NULL, &error)) { + fprintf(stderr, "%s: unable to load host key '%s': " + "%s\n", appname, val, error); + exit(1); + } + } else { + fprintf(stderr, "%s: '%s' is not loadable as a " + "private key (%s)", appname, val, + key_type_to_str(keytype)); + exit(1); + } + } else if (longoptarg(arg, "--rsakexkey", &val, &argc, &argv)) { + Filename *keyfile; + int keytype; + const char *error; + + keyfile = filename_from_str(val); + keytype = key_type(keyfile); + + if (keytype != SSH_KEYTYPE_SSH1) { + fprintf(stderr, "%s: '%s' is not loadable as an SSH-1 format " + "private key (%s)", appname, val, + key_type_to_str(keytype)); + exit(1); + } + + if (ssc.rsa_kex_key) { + freersakey(ssc.rsa_kex_key); + } else { + ssc.rsa_kex_key = snew(RSAKey); + } + + if (!rsa1_load_f(keyfile, ssc.rsa_kex_key, NULL, &error)) { + fprintf(stderr, "%s: unable to load RSA kex key '%s': " + "%s\n", appname, val, error); + exit(1); + } + + ssc.rsa_kex_key->sshk.vt = &ssh_rsa; + } else if (longoptarg(arg, "--userkey", &val, &argc, &argv)) { + Filename *keyfile; + int keytype; + const char *error; + + keyfile = filename_from_str(val); + keytype = key_type(keyfile); + + if (keytype == SSH_KEYTYPE_SSH2_PUBLIC_RFC4716 || + keytype == SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH) { + strbuf *sb = strbuf_new(); + struct AuthPolicy_ssh2_pubkey *node; + void *blob; + + if (!ppk_loadpub_f(keyfile, NULL, BinarySink_UPCAST(sb), + NULL, &error)) { + fprintf(stderr, "%s: unable to load user key '%s': " + "%s\n", appname, val, error); + exit(1); + } + + node = snew_plus(struct AuthPolicy_ssh2_pubkey, sb->len); + blob = snew_plus_get_aux(node); + memcpy(blob, sb->u, sb->len); + node->public_blob = make_ptrlen(blob, sb->len); + + node->next = aps.ssh2keys; + aps.ssh2keys = node; + + strbuf_free(sb); + } else if (keytype == SSH_KEYTYPE_SSH1_PUBLIC) { + strbuf *sb = strbuf_new(); + BinarySource src[1]; + struct AuthPolicy_ssh1_pubkey *node; + + if (!rsa1_loadpub_f(keyfile, BinarySink_UPCAST(sb), + NULL, &error)) { + fprintf(stderr, "%s: unable to load user key '%s': " + "%s\n", appname, val, error); + exit(1); + } + + node = snew(struct AuthPolicy_ssh1_pubkey); + BinarySource_BARE_INIT(src, sb->u, sb->len); + get_rsa_ssh1_pub(src, &node->key, RSA_SSH1_EXPONENT_FIRST); + + node->next = aps.ssh1keys; + aps.ssh1keys = node; + + strbuf_free(sb); + } else { + fprintf(stderr, "%s: '%s' is not loadable as a public key " + "(%s)\n", appname, val, key_type_to_str(keytype)); + exit(1); + } + } else if (longoptarg(arg, "--bannerfile", &val, &argc, &argv)) { + FILE *fp = fopen(val, "r"); + if (!fp) { + fprintf(stderr, "%s: %s: open: %s\n", appname, + val, strerror(errno)); + exit(1); + } + strbuf *sb = strbuf_new(); + if (!read_file_into(BinarySink_UPCAST(sb), fp)) { + fprintf(stderr, "%s: %s: read: %s\n", appname, + val, strerror(errno)); + exit(1); + } + fclose(fp); + ssc.banner = ptrlen_from_strbuf(sb); + } else if (longoptarg(arg, "--bannertext", &val, &argc, &argv)) { + ssc.banner = ptrlen_from_asciz(val); + } else if (longoptarg(arg, "--sessiondir", &val, &argc, &argv)) { + ssc.session_starting_dir = val; + } else if (longoptarg(arg, "--kexinit-kex", &val, &argc, &argv)) { + ssc.kex_override[KEXLIST_KEX] = ptrlen_from_asciz(val); + } else if (longoptarg(arg, "--kexinit-hostkey", &val, &argc, &argv)) { + ssc.kex_override[KEXLIST_HOSTKEY] = ptrlen_from_asciz(val); + } else if (longoptarg(arg, "--kexinit-cscipher", &val, &argc, &argv)) { + ssc.kex_override[KEXLIST_CSCIPHER] = ptrlen_from_asciz(val); + } else if (longoptarg(arg, "--kexinit-csmac", &val, &argc, &argv)) { + ssc.kex_override[KEXLIST_CSMAC] = ptrlen_from_asciz(val); + } else if (longoptarg(arg, "--kexinit-cscomp", &val, &argc, &argv)) { + ssc.kex_override[KEXLIST_CSCOMP] = ptrlen_from_asciz(val); + } else if (longoptarg(arg, "--kexinit-sccipher", &val, &argc, &argv)) { + ssc.kex_override[KEXLIST_SCCIPHER] = ptrlen_from_asciz(val); + } else if (longoptarg(arg, "--kexinit-scmac", &val, &argc, &argv)) { + ssc.kex_override[KEXLIST_SCMAC] = ptrlen_from_asciz(val); + } else if (longoptarg(arg, "--kexinit-sccomp", &val, &argc, &argv)) { + ssc.kex_override[KEXLIST_SCCOMP] = ptrlen_from_asciz(val); + } else if (longoptarg(arg, "--ssh1-ciphers", &val, &argc, &argv)) { + ptrlen list = ptrlen_from_asciz(val); + ptrlen word; + unsigned long mask = 0; + while (word = ptrlen_get_word(&list, ","), word.len != 0) { + +#define SSH1_CIPHER_CASE(bitpos, name) \ + if (ptrlen_eq_string(word, name)) { \ + mask |= 1U << bitpos; \ + continue; \ + } + SSH1_SUPPORTED_CIPHER_LIST(SSH1_CIPHER_CASE); +#undef SSH1_CIPHER_CASE + + fprintf(stderr, "%s: unrecognised SSH-1 cipher '%.*s'\n", + appname, PTRLEN_PRINTF(word)); + exit(1); + } + ssc.ssh1_cipher_mask = mask; + } else if (longoptnoarg(arg, "--ssh1-no-compression")) { + ssc.ssh1_allow_compression = false; + } else if (longoptnoarg(arg, "--exitsignum")) { + ssc.exit_signal_numeric = true; + } else if (longoptarg(arg, "--sshlog", &val, &argc, &argv) || + longoptarg(arg, "-sshlog", &val, &argc, &argv)) { + Filename *logfile = filename_from_str(val); + conf_set_filename(conf, CONF_logfilename, logfile); + filename_free(logfile); + conf_set_int(conf, CONF_logtype, LGTYP_PACKETS); + conf_set_int(conf, CONF_logxfovr, LGXF_OVR); + } else if (longoptarg(arg, "--sshrawlog", &val, &argc, &argv) || + longoptarg(arg, "-sshrawlog", &val, &argc, &argv)) { + Filename *logfile = filename_from_str(val); + conf_set_filename(conf, CONF_logfilename, logfile); + filename_free(logfile); + conf_set_int(conf, CONF_logtype, LGTYP_SSHRAW); + conf_set_int(conf, CONF_logxfovr, LGXF_OVR); + } else if (!strcmp(arg, "--pretend-to-accept-any-pubkey")) { + ssc.stunt_pretend_to_accept_any_pubkey = true; + } else if (!strcmp(arg, "--open-unconditional-agent-socket")) { + ssc.stunt_open_unconditional_agent_socket = true; + } else { + fprintf(stderr, "%s: unrecognised option '%s'\n", appname, arg); + exit(1); + } + } + + if (nhostkeys == 0 && !hostkey1) { + fprintf(stderr, "%s: specify at least one host key\n", appname); + exit(1); + } + + random_ref(); + + /* + * Block SIGPIPE, so that we'll get EPIPE individually on + * particular network connections that go wrong. + */ + putty_signal(SIGPIPE, SIG_IGN); + + sk_init(); + uxsel_init(); + + struct server_config scfg; + scfg.conf = conf; + scfg.ssc = &ssc; + scfg.hostkeys = hostkeys; + scfg.nhostkeys = nhostkeys; + scfg.hostkey1 = hostkey1; + scfg.ap_shared = &aps; + scfg.next_id = 0; + + if (listen_port >= 0 || listen_socket) { + listening = true; + scfg.listening_plug.vt = &server_plugvt; + char *msg; + if (listen_port >= 0) { + scfg.listening_socket = sk_newlistener( + NULL, listen_port, &scfg.listening_plug, true, + ADDRTYPE_UNSPEC); + msg = dupprintf("%s: listening on port %d", + appname, listen_port); + } else { + SockAddr *addr = unix_sock_addr(listen_socket); + scfg.listening_socket = new_unix_listener( + addr, &scfg.listening_plug); + msg = dupprintf("%s: listening on Unix socket %s", + appname, listen_socket); + } + + log_to_stderr(-1, msg); + sfree(msg); + } else { + struct server_instance *inst; + Plug *plug = server_conn_plug(&scfg, &inst); + ssh_server_start(plug, make_fd_socket(0, 1, -1, plug)); + log_to_stderr(inst->id, "speaking SSH on stdio"); + } + + cli_main_loop(cliloop_no_pw_setup, cliloop_no_pw_check, + cliloop_always_continue, NULL); + + return 0; +} diff --git a/unix/ux_x11.c b/unix/ux_x11.c deleted file mode 100644 index 7a0c2218..00000000 --- a/unix/ux_x11.c +++ /dev/null @@ -1,208 +0,0 @@ -/* - * ux_x11.c: fetch local auth data for X forwarding. - */ - -#include -#include -#include -#include -#include -#include -#include - -#include "putty.h" -#include "ssh.h" -#include "network.h" - -void platform_get_x11_auth(struct X11Display *disp, Conf *conf) -{ - char *xauthfile; - bool needs_free; - - /* - * Find the .Xauthority file. - */ - needs_free = false; - xauthfile = getenv("XAUTHORITY"); - if (!xauthfile) { - xauthfile = getenv("HOME"); - if (xauthfile) { - xauthfile = dupcat(xauthfile, "/.Xauthority"); - needs_free = true; - } - } - - if (xauthfile) { - x11_get_auth_from_authfile(disp, xauthfile); - if (needs_free) - sfree(xauthfile); - } -} - -const bool platform_uses_x11_unix_by_default = true; - -int platform_make_x11_server(Plug *plug, const char *progname, int mindisp, - const char *screen_number_suffix, - ptrlen authproto, ptrlen authdata, - Socket **sockets, Conf *conf) -{ - char *tmpdir; - char *authfilename = NULL; - strbuf *authfiledata = NULL; - char *unix_path = NULL; - - SockAddr *a_tcp = NULL, *a_unix = NULL; - - int authfd; - FILE *authfp; - - int displayno; - - authfiledata = strbuf_new_nm(); - - int nsockets = 0; - - /* - * Look for a free TCP port to run our server on. - */ - for (displayno = mindisp;; displayno++) { - const char *err; - int tcp_port = displayno + 6000; - int addrtype = ADDRTYPE_IPV4; - - sockets[nsockets] = new_listener( - NULL, tcp_port, plug, false, conf, addrtype); - - err = sk_socket_error(sockets[nsockets]); - if (!err) { - char *hostname = get_hostname(); - if (hostname) { - char *canonicalname = NULL; - a_tcp = sk_namelookup(hostname, &canonicalname, addrtype); - sfree(canonicalname); - } - sfree(hostname); - nsockets++; - break; /* success! */ - } else { - sk_close(sockets[nsockets]); - } - - if (!strcmp(err, strerror(EADDRINUSE))) /* yuck! */ - goto out; - } - - if (a_tcp) { - x11_format_auth_for_authfile( - BinarySink_UPCAST(authfiledata), - a_tcp, displayno, authproto, authdata); - } - - /* - * Try to establish the Unix-domain analogue. That may or may not - * work - file permissions in /tmp may prevent it, for example - - * but it's worth a try, and we don't consider it a fatal error if - * it doesn't work. - */ - unix_path = dupprintf("/tmp/.X11-unix/X%d", displayno); - a_unix = unix_sock_addr(unix_path); - - sockets[nsockets] = new_unix_listener(a_unix, plug); - if (!sk_socket_error(sockets[nsockets])) { - x11_format_auth_for_authfile( - BinarySink_UPCAST(authfiledata), - a_unix, displayno, authproto, authdata); - nsockets++; - } else { - sk_close(sockets[nsockets]); - sfree(unix_path); - unix_path = NULL; - } - - /* - * Decide where the authority data will be written. - */ - - tmpdir = getenv("TMPDIR"); - if (!tmpdir || !*tmpdir) - tmpdir = "/tmp"; - - authfilename = dupcat(tmpdir, "/", progname, "-Xauthority-XXXXXX"); - - { - int oldumask = umask(077); - authfd = mkstemp(authfilename); - umask(oldumask); - } - if (authfd < 0) { - while (nsockets-- > 0) - sk_close(sockets[nsockets]); - goto out; - } - - /* - * Spawn a subprocess which will try to reliably delete our - * auth file when we terminate, in case we die unexpectedly. - */ - { - int cleanup_pipe[2]; - pid_t pid; - - /* Don't worry if pipe or fork fails; it's not _that_ critical. */ - if (!pipe(cleanup_pipe)) { - if ((pid = fork()) == 0) { - int buf[1024]; - /* - * Our parent process holds the writing end of - * this pipe, and writes nothing to it. Hence, - * we expect read() to return EOF as soon as - * that process terminates. - */ - - close(0); - close(1); - close(2); - - setpgid(0, 0); - close(cleanup_pipe[1]); - close(authfd); - while (read(cleanup_pipe[0], buf, sizeof(buf)) > 0); - unlink(authfilename); - if (unix_path) - unlink(unix_path); - _exit(0); - } else if (pid < 0) { - close(cleanup_pipe[0]); - close(cleanup_pipe[1]); - } else { - close(cleanup_pipe[0]); - cloexec(cleanup_pipe[1]); - } - } - } - - authfp = fdopen(authfd, "wb"); - fwrite(authfiledata->u, 1, authfiledata->len, authfp); - fclose(authfp); - - { - char *display = dupprintf(":%d%s", displayno, screen_number_suffix); - conf_set_str_str(conf, CONF_environmt, "DISPLAY", display); - sfree(display); - } - conf_set_str_str(conf, CONF_environmt, "XAUTHORITY", authfilename); - - /* - * FIXME: return at least the DISPLAY and XAUTHORITY env settings, - * and perhaps also the display number - */ - - out: - if (a_tcp) - sk_addr_free(a_tcp); - /* a_unix doesn't need freeing, because new_unix_listener took it over */ - sfree(authfilename); - strbuf_free(authfiledata); - sfree(unix_path); - return nsockets; -} diff --git a/unix/uxagentc.c b/unix/uxagentc.c deleted file mode 100644 index e4d671d2..00000000 --- a/unix/uxagentc.c +++ /dev/null @@ -1,226 +0,0 @@ -/* - * SSH agent client code. - */ - -#include -#include -#include -#include -#include -#include -#include - -#include "putty.h" -#include "misc.h" -#include "tree234.h" -#include "puttymem.h" - -bool agent_exists(void) -{ - const char *p = getenv("SSH_AUTH_SOCK"); - if (p && *p) - return true; - return false; -} - -static tree234 *agent_pending_queries; -struct agent_pending_query { - 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) -{ - agent_pending_query *a = (agent_pending_query *) av; - agent_pending_query *b = (agent_pending_query *) 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; - agent_pending_query *b = (agent_pending_query *) bv; - if (afd < b->fd) - return -1; - if (afd > b->fd) - return +1; - return 0; -} - -/* - * Attempt to read from an agent socket fd. Returns false if the - * expected response is as yet incomplete; returns true if it's either - * complete (conn->retbuf non-NULL and filled with something useful) - * or has failed totally (conn->retbuf is NULL). - */ -static bool agent_try_read(agent_pending_query *conn) -{ - int ret; - - ret = read(conn->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; - return true; - } - conn->retlen += ret; - if (conn->retsize == 4 && conn->retlen == 4) { - conn->retsize = toint(GET_32BIT_MSB_FIRST(conn->retbuf) + 4); - if (conn->retsize <= 0) { - conn->retbuf = NULL; - conn->retlen = 0; - return true; /* way too large */ - } - assert(conn->retbuf == conn->sizebuf); - conn->retbuf = snewn(conn->retsize, char); - memcpy(conn->retbuf, conn->sizebuf, 4); - } - - if (conn->retlen < conn->retsize) - return false; /* more data to come */ - - return true; -} - -void agent_cancel_query(agent_pending_query *conn) -{ - uxsel_del(conn->fd); - close(conn->fd); - del234(agent_pending_queries, conn); - if (conn->retbuf && conn->retbuf != conn->sizebuf) - sfree(conn->retbuf); - sfree(conn); -} - -static void agent_select_result(int fd, int event) -{ - agent_pending_query *conn; - - assert(event == SELECT_R); /* not selecting for anything but R */ - - conn = find234(agent_pending_queries, &fd, agent_connfind); - if (!conn) { - uxsel_del(fd); - return; - } - - if (!agent_try_read(conn)) - return; /* more data to come */ - - /* - * We have now completed the agent query. Do the callback. - */ - conn->callback(conn->callback_ctx, conn->retbuf, conn->retlen); - /* Null out conn->retbuf, since ownership of that buffer has - * passed to the callback. */ - conn->retbuf = NULL; - agent_cancel_query(conn); -} - -static const char *agent_socket_path(void) -{ - return getenv("SSH_AUTH_SOCK"); -} - -Socket *agent_connect(Plug *plug) -{ - const char *path = agent_socket_path(); - if (!path) - return new_error_socket_fmt(plug, "SSH_AUTH_SOCK not set"); - return sk_new(unix_sock_addr(path), 0, false, false, false, false, plug); -} - -agent_pending_query *agent_query( - strbuf *query, void **out, int *outlen, - void (*callback)(void *, void *, int), void *callback_ctx) -{ - const char *name; - int sock; - struct sockaddr_un addr; - int done; - agent_pending_query *conn; - - name = agent_socket_path(); - if (!name || strlen(name) >= sizeof(addr.sun_path)) - 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; - strcpy(addr.sun_path, name); - if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) { - close(sock); - goto failure; - } - - strbuf_finalise_agent_query(query); - - for (done = 0; done < query->len ;) { - int ret = write(sock, query->s + done, - query->len - done); - if (ret <= 0) { - close(sock); - goto failure; - } - done += ret; - } - - conn = snew(agent_pending_query); - conn->fd = sock; - conn->retbuf = conn->sizebuf; - conn->retsize = 4; - conn->retlen = 0; - conn->callback = callback; - conn->callback_ctx = callback_ctx; - - if (!callback) { - /* - * Bodge to permit making deliberately synchronous agent - * requests. Used by Unix Pageant in command-line client mode, - * which is legit because it really is true that no other part - * of the program is trying to get anything useful done - * simultaneously. But this special case shouldn't be used in - * any more general program. - */ - no_nonblock(conn->fd); - while (!agent_try_read(conn)) - /* empty loop body */; - - *out = conn->retbuf; - *outlen = conn->retlen; - sfree(conn); - return NULL; - } - - /* - * Otherwise do it properly: add conn to the tree of agent - * connections currently in flight, return 0 to indicate that the - * response hasn't been received yet, and call the callback when - * select_result comes back to us. - */ - if (!agent_pending_queries) - agent_pending_queries = newtree234(agent_conncmp); - add234(agent_pending_queries, conn); - - uxsel_set(sock, SELECT_R, agent_select_result); - return conn; - - failure: - *out = NULL; - *outlen = 0; - return NULL; -} diff --git a/unix/uxagentsock.c b/unix/uxagentsock.c deleted file mode 100644 index 7e6187c6..00000000 --- a/unix/uxagentsock.c +++ /dev/null @@ -1,91 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#include - -#include "putty.h" -#include "ssh.h" -#include "misc.h" -#include "pageant.h" - -Socket *platform_make_agent_socket( - Plug *plug, const char *dirprefix, char **error, char **name) -{ - char *username, *socketdir, *socketname, *errw; - const char *err; - Socket *sock; - - *name = NULL; - - username = get_username(); - socketdir = dupprintf("%s.%s", dirprefix, username); - sfree(username); - - assert(*socketdir == '/'); - if ((errw = make_dir_and_check_ours(socketdir)) != NULL) { - *error = dupprintf("%s: %s\n", socketdir, errw); - sfree(errw); - sfree(socketdir); - return NULL; - } - - socketname = dupprintf("%s/pageant.%d", socketdir, (int)getpid()); - sock = new_unix_listener(unix_sock_addr(socketname), plug); - if ((err = sk_socket_error(sock)) != NULL) { - *error = dupprintf("%s: %s\n", socketname, err); - sk_close(sock); - sfree(socketname); - rmdir(socketdir); - sfree(socketdir); - return NULL; - } - - /* - * Spawn a subprocess which will try to reliably delete our socket - * and its containing directory when we terminate, in case we die - * unexpectedly. - */ - { - int cleanup_pipe[2]; - pid_t pid; - - /* Don't worry if pipe or fork fails; it's not _that_ critical. */ - if (!pipe(cleanup_pipe)) { - if ((pid = fork()) == 0) { - int buf[1024]; - /* - * Our parent process holds the writing end of - * this pipe, and writes nothing to it. Hence, - * we expect read() to return EOF as soon as - * that process terminates. - */ - - close(0); - close(1); - close(2); - - setpgid(0, 0); - close(cleanup_pipe[1]); - while (read(cleanup_pipe[0], buf, sizeof(buf)) > 0); - unlink(socketname); - rmdir(socketdir); - _exit(0); - } else if (pid < 0) { - close(cleanup_pipe[0]); - close(cleanup_pipe[1]); - } else { - close(cleanup_pipe[0]); - cloexec(cleanup_pipe[1]); - } - } - } - - *name = socketname; - *error = NULL; - sfree(socketdir); - return sock; -} diff --git a/unix/uxcfg.c b/unix/uxcfg.c deleted file mode 100644 index 8397a0ac..00000000 --- a/unix/uxcfg.c +++ /dev/null @@ -1,70 +0,0 @@ -/* - * uxcfg.c - the Unix-specific parts of the PuTTY configuration - * box. - */ - -#include -#include - -#include "putty.h" -#include "dialog.h" -#include "storage.h" - -void unix_setup_config_box(struct controlbox *b, bool midsession, int protocol) -{ - struct controlset *s; - union control *c; - - /* - * The Conf structure contains two Unix-specific elements which - * are not configured in here: stamp_utmp and login_shell. This - * is because pterm does not put up a configuration box right at - * the start, which is the only time when these elements would - * be useful to configure. - */ - - /* - * On Unix, we don't have a drop-down list for the printer - * control. - */ - 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 = false; - - /* - * 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 == CONF_proxy_type) { - assert(c->generic.handler == conf_radiobutton_handler); - c->radio.nbuttons++; - c->radio.buttons = - sresize(c->radio.buttons, c->radio.nbuttons, char *); - c->radio.buttons[c->radio.nbuttons-1] = - dupstr("Local"); - 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 == CONF_proxy_telnet_command) { - assert(c->generic.handler == conf_editbox_handler); - sfree(c->generic.label); - c->generic.label = dupstr("Telnet command, or local" - " proxy command"); - break; - } - } - } -} diff --git a/unix/uxcliloop.c b/unix/uxcliloop.c deleted file mode 100644 index 23a808da..00000000 --- a/unix/uxcliloop.c +++ /dev/null @@ -1,125 +0,0 @@ -#include - -#include "putty.h" - -void cli_main_loop(cliloop_pw_setup_t pw_setup, - cliloop_pw_check_t pw_check, - cliloop_continue_t cont, void *ctx) -{ - unsigned long now = GETTICKCOUNT(); - - int *fdlist = NULL; - size_t fdsize = 0; - - pollwrapper *pw = pollwrap_new(); - - while (true) { - int rwx; - int ret; - int fdstate; - unsigned long next; - - pollwrap_clear(pw); - - if (!pw_setup(ctx, pw)) - break; /* our client signalled emergency exit */ - - /* Count the currently active fds. */ - size_t nfds = 0; - for (int fd = first_fd(&fdstate, &rwx); fd >= 0; - fd = next_fd(&fdstate, &rwx)) - nfds++; - - /* Expand the fdlist buffer if necessary. */ - sgrowarray(fdlist, fdsize, nfds); - - /* - * Add all currently open uxsel fds to pw, and store them in - * fdlist as well. - */ - size_t fdcount = 0; - for (int fd = first_fd(&fdstate, &rwx); fd >= 0; - fd = next_fd(&fdstate, &rwx)) { - fdlist[fdcount++] = fd; - pollwrap_add_fd_rwx(pw, fd, rwx); - } - - if (toplevel_callback_pending()) { - ret = pollwrap_poll_instant(pw); - } else if (run_timers(now, &next)) { - do { - unsigned long then; - long ticks; - - then = now; - now = GETTICKCOUNT(); - if (now - then > next - then) - ticks = 0; - else - ticks = next - now; - - bool overflow = false; - if (ticks > INT_MAX) { - ticks = INT_MAX; - overflow = true; - } - - ret = pollwrap_poll_timeout(pw, ticks); - if (ret == 0 && !overflow) - now = next; - else - now = GETTICKCOUNT(); - } while (ret < 0 && errno == EINTR); - } else { - ret = pollwrap_poll_endless(pw); - } - - if (ret < 0 && errno == EINTR) - continue; - - if (ret < 0) { - perror("poll"); - exit(1); - } - - bool found_fd = (ret > 0); - - for (size_t i = 0; i < fdcount; i++) { - int fd = fdlist[i]; - int rwx = pollwrap_get_fd_rwx(pw, fd); - /* - * We must process exceptional notifications before - * ordinary readability ones, or we may go straight - * past the urgent marker. - */ - if (rwx & SELECT_X) - select_result(fd, SELECT_X); - if (rwx & SELECT_R) - select_result(fd, SELECT_R); - if (rwx & SELECT_W) - select_result(fd, SELECT_W); - } - - pw_check(ctx, pw); - - bool ran_callback = run_toplevel_callbacks(); - - if (!cont(ctx, found_fd, ran_callback)) - break; - } - - pollwrap_free(pw); - sfree(fdlist); -} - -bool cliloop_no_pw_setup(void *ctx, pollwrapper *pw) { return true; } -void cliloop_no_pw_check(void *ctx, pollwrapper *pw) {} -bool cliloop_always_continue(void *ctx, bool fd, bool cb) { return true; } - -/* - * Any application using this main loop doesn't need to do anything - * when uxsel adds or removes an fd, because we synchronously re-check - * the current list every time we go round the main loop above. - */ -uxsel_id *uxsel_input_add(int fd, int rwx) { return NULL; } -void uxsel_input_remove(uxsel_id *id) { } diff --git a/unix/uxcons.c b/unix/uxcons.c deleted file mode 100644 index 90e73a98..00000000 --- a/unix/uxcons.c +++ /dev/null @@ -1,530 +0,0 @@ -/* - * uxcons.c: various interactive-prompt routines shared between the - * Unix console PuTTY tools - */ - -#include -#include -#include -#include - -#include -#include -#include -#include - -#include "putty.h" -#include "storage.h" -#include "ssh.h" -#include "console.h" - -static struct termios orig_termios_stderr; -static bool 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 (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); -} - -void cleanup_exit(int code) -{ - /* - * Clean up. - */ - sk_cleanup(); - random_save_seed(); - exit(code); -} - -void console_print_error_msg(const char *prefix, const char *msg) -{ - struct termios cf; - premsg(&cf); - fputs(prefix, stderr); - fputs(": ", stderr); - fputs(msg, stderr); - fputc('\n', stderr); - fflush(stderr); - postmsg(&cf); -} - -/* - * Wrapper around Unix read(2), suitable for use on a file descriptor - * that's been set into nonblocking mode. Handles EAGAIN/EWOULDBLOCK - * by means of doing a one-fd poll and then trying again; all other - * errors (including errors from poll) are returned to the caller. - */ -static int block_and_read(int fd, void *buf, size_t len) -{ - int ret; - pollwrapper *pw = pollwrap_new(); - - while ((ret = read(fd, buf, len)) < 0 && ( -#ifdef EAGAIN - (errno == EAGAIN) || -#endif -#ifdef EWOULDBLOCK - (errno == EWOULDBLOCK) || -#endif - false)) { - - pollwrap_clear(pw); - pollwrap_add_fd_rwx(pw, fd, SELECT_R); - do { - ret = pollwrap_poll_endless(pw); - } while (ret < 0 && errno == EINTR); - assert(ret != 0); - if (ret < 0) { - pollwrap_free(pw); - return ret; - } - assert(pollwrap_check_fd_rwx(pw, fd, SELECT_R)); - } - - pollwrap_free(pw); - return ret; -} - -int console_verify_ssh_host_key( - Seat *seat, const char *host, int port, const char *keytype, - char *keystr, const char *keydisp, char **fingerprints, - void (*callback)(void *ctx, int result), void *ctx) -{ - int ret; - - char line[32]; - struct termios cf; - const char *common_fmt, *intro, *prompt; - - /* - * 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 */ - common_fmt = hk_wrongmsg_common_fmt; - intro = hk_wrongmsg_interactive_intro; - prompt = hk_wrongmsg_interactive_prompt; - } else { /* key was absent */ - common_fmt = hk_absentmsg_common_fmt; - intro = hk_absentmsg_interactive_intro; - prompt = hk_absentmsg_interactive_prompt; - } - - FingerprintType fptype_default = - ssh2_pick_default_fingerprint(fingerprints); - - fprintf(stderr, common_fmt, keytype, fingerprints[fptype_default]); - if (console_batch_mode) { - fputs(console_abandoned_msg, stderr); - return 0; - } - - fputs(intro, stderr); - fflush(stderr); - while (true) { - fputs(prompt, stderr); - 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 (block_and_read(0, line, sizeof(line) - 1) <= 0) - /* handled below */; - tcsetattr(0, TCSANOW, &oldmode); - - if (line[0] == 'i' || line[0] == 'I') { - fprintf(stderr, "Full public key:\n%s\n", keydisp); - if (fingerprints[SSH_FPTYPE_SHA256]) - fprintf(stderr, "SHA256 key fingerprint:\n%s\n", - fingerprints[SSH_FPTYPE_SHA256]); - if (fingerprints[SSH_FPTYPE_MD5]) - fprintf(stderr, "MD5 key fingerprint:\n%s\n", - fingerprints[SSH_FPTYPE_MD5]); - } else { - break; - } - } - - /* In case of misplaced reflexes from another program, also recognise 'q' - * as 'abandon connection rather than trust this key' */ - if (line[0] != '\0' && line[0] != '\r' && line[0] != '\n' && - line[0] != 'q' && line[0] != 'Q') { - if (line[0] == 'y' || line[0] == 'Y') - store_host_key(host, port, keytype, keystr); - postmsg(&cf); - return 1; - } else { - fputs(console_abandoned_msg, stderr); - postmsg(&cf); - return 0; - } -} - -int console_confirm_weak_crypto_primitive( - Seat *seat, const char *algtype, const char *algname, - void (*callback)(void *ctx, int result), void *ctx) -{ - char line[32]; - struct termios cf; - - premsg(&cf); - fprintf(stderr, weakcrypto_msg_common_fmt, algtype, algname); - - if (console_batch_mode) { - fputs(console_abandoned_msg, stderr); - postmsg(&cf); - return 0; - } - - fputs(console_continue_prompt, stderr); - 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 (block_and_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 { - fputs(console_abandoned_msg, stderr); - postmsg(&cf); - return 0; - } -} - -int console_confirm_weak_cached_hostkey( - Seat *seat, const char *algname, const char *betteralgs, - void (*callback)(void *ctx, int result), void *ctx) -{ - char line[32]; - struct termios cf; - - premsg(&cf); - fprintf(stderr, weakhk_msg_common_fmt, algname, betteralgs); - - if (console_batch_mode) { - fputs(console_abandoned_msg, stderr); - postmsg(&cf); - return 0; - } - - fputs(console_continue_prompt, stderr); - 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 (block_and_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 { - fputs(console_abandoned_msg, stderr); - 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 console_askappend(LogPolicy *lp, 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 (block_and_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; -} - -bool console_antispoof_prompt = true; -bool console_set_trust_status(Seat *seat, bool trusted) -{ - if (console_batch_mode || !is_interactive() || !console_antispoof_prompt) { - /* - * In batch mode, we don't need to worry about the server - * mimicking our interactive authentication, because the user - * already knows not to expect any. - * - * If standard input isn't connected to a terminal, likewise, - * because even if the server did send a spoof authentication - * prompt, the user couldn't respond to it via the terminal - * anyway. - * - * We also vacuously return success if the user has purposely - * disabled the antispoof prompt. - */ - return true; - } - - return false; -} - -/* - * 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_logging_error(LogPolicy *lp, const char *string) -{ - /* Errors setting up logging are considered important, so they're - * displayed to standard error even when not in verbose mode */ - struct termios cf; - premsg(&cf); - fprintf(stderr, "%s\n", string); - fflush(stderr); - postmsg(&cf); -} - - -void console_eventlog(LogPolicy *lp, const char *string) -{ - /* Ordinary Event Log entries are displayed in the same way as - * logging errors, but only in verbose mode */ - if (lp_verbose(lp)) - console_logging_error(lp, string); -} - -StripCtrlChars *console_stripctrl_new( - Seat *seat, BinarySink *bs_out, SeatInteractionContext sic) -{ - return stripctrl_new(bs_out, false, 0); -} - -/* - * Special functions to read and print to the console for password - * prompts and the like. Uses /dev/tty or stdin/stderr, in that order - * of preference; also sanitises escape sequences out of the text, on - * the basis that it might have been sent by a hostile SSH server - * doing malicious keyboard-interactive. - */ -static void console_open(FILE **outfp, int *infd) -{ - int fd; - - if ((fd = open("/dev/tty", O_RDWR)) >= 0) { - *infd = fd; - *outfp = fdopen(*infd, "w"); - } else { - *infd = 0; - *outfp = stderr; - } -} -static void console_close(FILE *outfp, int infd) -{ - if (outfp != stderr) - fclose(outfp); /* will automatically close infd too */ -} - -static void console_write(FILE *outfp, ptrlen data) -{ - fwrite(data.ptr, 1, data.len, outfp); - fflush(outfp); -} - -int console_get_userpass_input(prompts_t *p) -{ - size_t curr_prompt; - FILE *outfp = NULL; - int infd; - - /* - * Zero all the results, in case we abort half-way through. - */ - { - int i; - for (i = 0; i < p->n_prompts; i++) - prompt_set_result(p->prompts[i], ""); - } - - if (p->n_prompts && console_batch_mode) - return 0; - - console_open(&outfp, &infd); - - /* - * Preamble. - */ - /* We only print the `name' caption if we have to... */ - if (p->name_reqd && p->name) { - ptrlen plname = ptrlen_from_asciz(p->name); - console_write(outfp, plname); - if (!ptrlen_endswith(plname, PTRLEN_LITERAL("\n"), NULL)) - console_write(outfp, PTRLEN_LITERAL("\n")); - } - /* ...but we always print any `instruction'. */ - if (p->instruction) { - ptrlen plinst = ptrlen_from_asciz(p->instruction); - console_write(outfp, plinst); - if (!ptrlen_endswith(plinst, PTRLEN_LITERAL("\n"), NULL)) - console_write(outfp, PTRLEN_LITERAL("\n")); - } - - for (curr_prompt = 0; curr_prompt < p->n_prompts; curr_prompt++) { - - struct termios oldmode, newmode; - prompt_t *pr = p->prompts[curr_prompt]; - - tcgetattr(infd, &oldmode); - newmode = oldmode; - newmode.c_lflag |= ISIG | ICANON; - if (!pr->echo) - newmode.c_lflag &= ~ECHO; - else - newmode.c_lflag |= ECHO; - tcsetattr(infd, TCSANOW, &newmode); - - console_write(outfp, ptrlen_from_asciz(pr->prompt)); - - bool failed = false; - while (1) { - size_t toread = 65536; - size_t prev_result_len = pr->result->len; - void *ptr = strbuf_append(pr->result, toread); - int ret = read(infd, ptr, toread); - - if (ret <= 0) { - failed = true; - break; - } - - strbuf_shrink_to(pr->result, prev_result_len + ret); - if (strbuf_chomp(pr->result, '\n')) - break; - } - - tcsetattr(infd, TCSANOW, &oldmode); - - if (!pr->echo) - console_write(outfp, PTRLEN_LITERAL("\n")); - - if (failed) { - console_close(outfp, infd); - return 0; /* failure due to read error */ - } - } - - console_close(outfp, infd); - - return 1; /* success */ -} - -bool is_interactive(void) -{ - return isatty(0); -} - -/* - * X11-forwarding-related things suitable for console. - */ - -char *platform_get_x_display(void) { - return dupstr(getenv("DISPLAY")); -} diff --git a/unix/uxfdsock.c b/unix/uxfdsock.c deleted file mode 100644 index 7697d995..00000000 --- a/unix/uxfdsock.c +++ /dev/null @@ -1,357 +0,0 @@ -/* - * uxfdsock.c: implementation of Socket that just talks to two - * existing input and output file descriptors. - */ - -#include -#include -#include -#include -#include - -#include "tree234.h" -#include "putty.h" -#include "network.h" - -typedef struct FdSocket { - int outfd, infd, inerrfd; - - bufchain pending_output_data; - bufchain pending_input_data; - ProxyStderrBuf psb; - enum { EOF_NO, EOF_PENDING, EOF_SENT } outgoingeof; - - int pending_error; - - Plug *plug; - - Socket sock; -} FdSocket; - -static void fdsocket_select_result_input(int fd, int event); -static void fdsocket_select_result_output(int fd, int event); -static void fdsocket_select_result_input_error(int fd, int event); - -/* - * Trees to look up the fds in. - */ -static tree234 *fdsocket_by_outfd; -static tree234 *fdsocket_by_infd; -static tree234 *fdsocket_by_inerrfd; - -static int fdsocket_infd_cmp(void *av, void *bv) -{ - FdSocket *a = (FdSocket *)av; - FdSocket *b = (FdSocket *)bv; - if (a->infd < b->infd) - return -1; - if (a->infd > b->infd) - return +1; - return 0; -} -static int fdsocket_infd_find(void *av, void *bv) -{ - int a = *(int *)av; - FdSocket *b = (FdSocket *)bv; - if (a < b->infd) - return -1; - if (a > b->infd) - return +1; - return 0; -} -static int fdsocket_inerrfd_cmp(void *av, void *bv) -{ - FdSocket *a = (FdSocket *)av; - FdSocket *b = (FdSocket *)bv; - if (a->inerrfd < b->inerrfd) - return -1; - if (a->inerrfd > b->inerrfd) - return +1; - return 0; -} -static int fdsocket_inerrfd_find(void *av, void *bv) -{ - int a = *(int *)av; - FdSocket *b = (FdSocket *)bv; - if (a < b->inerrfd) - return -1; - if (a > b->inerrfd) - return +1; - return 0; -} -static int fdsocket_outfd_cmp(void *av, void *bv) -{ - FdSocket *a = (FdSocket *)av; - FdSocket *b = (FdSocket *)bv; - if (a->outfd < b->outfd) - return -1; - if (a->outfd > b->outfd) - return +1; - return 0; -} -static int fdsocket_outfd_find(void *av, void *bv) -{ - int a = *(int *)av; - FdSocket *b = (FdSocket *)bv; - if (a < b->outfd) - return -1; - if (a > b->outfd) - return +1; - return 0; -} - -static Plug *fdsocket_plug(Socket *s, Plug *p) -{ - FdSocket *fds = container_of(s, FdSocket, sock); - Plug *ret = fds->plug; - if (p) - fds->plug = p; - return ret; -} - -static void fdsocket_close(Socket *s) -{ - FdSocket *fds = container_of(s, FdSocket, sock); - - if (fds->outfd >= 0) { - del234(fdsocket_by_outfd, fds); - uxsel_del(fds->outfd); - close(fds->outfd); - } - - if (fds->infd >= 0) { - del234(fdsocket_by_infd, fds); - uxsel_del(fds->infd); - close(fds->infd); - } - - if (fds->inerrfd >= 0) { - del234(fdsocket_by_inerrfd, fds); - uxsel_del(fds->inerrfd); - close(fds->inerrfd); - } - - bufchain_clear(&fds->pending_input_data); - bufchain_clear(&fds->pending_output_data); - - delete_callbacks_for_context(fds); - - sfree(fds); -} - -static void fdsocket_error_callback(void *vs) -{ - FdSocket *fds = (FdSocket *)vs; - - /* - * Just in case other socket work has caused this socket to vanish - * or become somehow non-erroneous before this callback arrived... - */ - if (!fds->pending_error) - return; - - /* - * An error has occurred on this socket. Pass it to the plug. - */ - plug_closing(fds->plug, strerror(fds->pending_error), - fds->pending_error, 0); -} - -static int fdsocket_try_send(FdSocket *fds) -{ - int sent = 0; - - while (bufchain_size(&fds->pending_output_data) > 0) { - ssize_t ret; - - ptrlen data = bufchain_prefix(&fds->pending_output_data); - ret = write(fds->outfd, data.ptr, data.len); - noise_ultralight(NOISE_SOURCE_IOID, ret); - if (ret < 0 && errno != EWOULDBLOCK) { - if (!fds->pending_error) { - fds->pending_error = errno; - queue_toplevel_callback(fdsocket_error_callback, fds); - } - return 0; - } else if (ret <= 0) { - break; - } else { - bufchain_consume(&fds->pending_output_data, ret); - sent += ret; - } - } - - if (fds->outgoingeof == EOF_PENDING) { - del234(fdsocket_by_outfd, fds); - close(fds->outfd); - uxsel_del(fds->outfd); - fds->outfd = -1; - fds->outgoingeof = EOF_SENT; - } - - if (bufchain_size(&fds->pending_output_data) == 0) - uxsel_del(fds->outfd); - else - uxsel_set(fds->outfd, SELECT_W, fdsocket_select_result_output); - - return sent; -} - -static size_t fdsocket_write(Socket *s, const void *data, size_t len) -{ - FdSocket *fds = container_of(s, FdSocket, sock); - - assert(fds->outgoingeof == EOF_NO); - - bufchain_add(&fds->pending_output_data, data, len); - - fdsocket_try_send(fds); - - return bufchain_size(&fds->pending_output_data); -} - -static size_t fdsocket_write_oob(Socket *s, const void *data, size_t len) -{ - /* - * oob data is treated as inband; nasty, but nothing really - * better we can do - */ - return fdsocket_write(s, data, len); -} - -static void fdsocket_write_eof(Socket *s) -{ - FdSocket *fds = container_of(s, FdSocket, sock); - - assert(fds->outgoingeof == EOF_NO); - fds->outgoingeof = EOF_PENDING; - - fdsocket_try_send(fds); -} - -static void fdsocket_set_frozen(Socket *s, bool is_frozen) -{ - FdSocket *fds = container_of(s, FdSocket, sock); - - if (fds->infd < 0) - return; - - if (is_frozen) - uxsel_del(fds->infd); - else - uxsel_set(fds->infd, SELECT_R, fdsocket_select_result_input); -} - -static const char *fdsocket_socket_error(Socket *s) -{ - return NULL; -} - -static void fdsocket_select_result_input(int fd, int event) -{ - FdSocket *fds; - char buf[20480]; - int retd; - - if (!(fds = find234(fdsocket_by_infd, &fd, fdsocket_infd_find))) - return; - - retd = read(fds->infd, buf, sizeof(buf)); - if (retd > 0) { - plug_receive(fds->plug, 0, buf, retd); - } else { - if (retd < 0) { - plug_closing(fds->plug, strerror(errno), errno, 0); - } else { - plug_closing(fds->plug, NULL, 0, 0); - } - del234(fdsocket_by_infd, fds); - uxsel_del(fds->infd); - close(fds->infd); - fds->infd = -1; - } -} - -static void fdsocket_select_result_output(int fd, int event) -{ - FdSocket *fds; - - if (!(fds = find234(fdsocket_by_outfd, &fd, fdsocket_outfd_find))) - return; - - if (fdsocket_try_send(fds)) - plug_sent(fds->plug, bufchain_size(&fds->pending_output_data)); -} - -static void fdsocket_select_result_input_error(int fd, int event) -{ - FdSocket *fds; - char buf[20480]; - int retd; - - if (!(fds = find234(fdsocket_by_inerrfd, &fd, fdsocket_inerrfd_find))) - return; - - retd = read(fd, buf, sizeof(buf)); - if (retd > 0) { - log_proxy_stderr(fds->plug, &fds->psb, buf, retd); - } else { - del234(fdsocket_by_inerrfd, fds); - uxsel_del(fds->inerrfd); - close(fds->inerrfd); - fds->inerrfd = -1; - } -} - -static const SocketVtable FdSocket_sockvt = { - .plug = fdsocket_plug, - .close = fdsocket_close, - .write = fdsocket_write, - .write_oob = fdsocket_write_oob, - .write_eof = fdsocket_write_eof, - .set_frozen = fdsocket_set_frozen, - .socket_error = fdsocket_socket_error, - .peer_info = NULL, -}; - -Socket *make_fd_socket(int infd, int outfd, int inerrfd, Plug *plug) -{ - FdSocket *fds; - - fds = snew(FdSocket); - fds->sock.vt = &FdSocket_sockvt; - fds->plug = plug; - fds->outgoingeof = EOF_NO; - fds->pending_error = 0; - - fds->infd = infd; - fds->outfd = outfd; - fds->inerrfd = inerrfd; - - bufchain_init(&fds->pending_input_data); - bufchain_init(&fds->pending_output_data); - psb_init(&fds->psb); - - if (fds->outfd >= 0) { - if (!fdsocket_by_outfd) - fdsocket_by_outfd = newtree234(fdsocket_outfd_cmp); - add234(fdsocket_by_outfd, fds); - } - - if (fds->infd >= 0) { - if (!fdsocket_by_infd) - fdsocket_by_infd = newtree234(fdsocket_infd_cmp); - add234(fdsocket_by_infd, fds); - uxsel_set(fds->infd, SELECT_R, fdsocket_select_result_input); - } - - if (fds->inerrfd >= 0) { - assert(fds->inerrfd != fds->infd); - if (!fdsocket_by_inerrfd) - fdsocket_by_inerrfd = newtree234(fdsocket_inerrfd_cmp); - add234(fdsocket_by_inerrfd, fds); - uxsel_set(fds->inerrfd, SELECT_R, fdsocket_select_result_input_error); - } - - return &fds->sock; -} diff --git a/unix/uxgen.c b/unix/uxgen.c deleted file mode 100644 index da5e8f05..00000000 --- a/unix/uxgen.c +++ /dev/null @@ -1,64 +0,0 @@ -/* - * uxgen.c: Unix implementation of get_heavy_noise() from cmdgen.c. - */ - -#include -#include - -#include -#include - -#include "putty.h" - -char *get_random_data(int len, const char *device) -{ - char *buf = snewn(len, char); - int fd; - int ngot, ret; - - if (!device) { - static const char *const default_devices[] = { - "/dev/urandom", "/dev/random" - }; - size_t i; - - for (i = 0; i < lenof(default_devices); i++) { - if (access(default_devices[i], R_OK) == 0) { - device = default_devices[i]; - break; - } - } - - if (!device) { - sfree(buf); - fprintf(stderr, "puttygen: cannot find a readable " - "random number source; use --random-device\n"); - return NULL; - } - } - - fd = open(device, O_RDONLY); - if (fd < 0) { - sfree(buf); - fprintf(stderr, "puttygen: %s: open: %s\n", - device, strerror(errno)); - return NULL; - } - - ngot = 0; - while (ngot < len) { - ret = read(fd, buf+ngot, len-ngot); - if (ret < 0) { - close(fd); - sfree(buf); - fprintf(stderr, "puttygen: %s: read: %s\n", - device, strerror(errno)); - return NULL; - } - ngot += ret; - } - - close(fd); - - return buf; -} diff --git a/unix/uxgss.c b/unix/uxgss.c deleted file mode 100644 index cd9971c7..00000000 --- a/unix/uxgss.c +++ /dev/null @@ -1,175 +0,0 @@ -#include "putty.h" -#ifndef NO_GSSAPI -#include "ssh/pgssapi.h" -#include "ssh/gss.h" -#include "ssh/gssc.h" - -/* Unix code to set up the GSSAPI library list. */ - -#if !defined NO_LIBDL && !defined STATIC_GSSAPI && !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(verify_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); - BIND_GSS_FN(acquire_cred); - BIND_GSS_FN(inquire_cred_by_mech); - -#undef BIND_GSS_FN - - ssh_gssapi_bind_fns(lib); -} - -/* Dynamically load gssapi libs. */ -struct ssh_gss_liblist *ssh_gss_setup(Conf *conf) -{ - void *gsslib; - char *gsspath; - struct ssh_gss_liblist *list = snew(struct ssh_gss_liblist); - - list->libraries = snewn(4, struct ssh_gss_library); - list->nlibraries = 0; - - /* 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 */ - gsspath = conf_get_filename(conf, CONF_ssh_gss_custom)->path; - if (*gsspath && (gsslib = dlopen(gsspath, RTLD_LAZY)) != NULL) - gss_init(&list->libraries[list->nlibraries++], gsslib, - 3, dupprintf("Using GSSAPI from user-specified" - " library '%s'", gsspath)); - - 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 - -/* Dynamically load gssapi libs. */ -struct ssh_gss_liblist *ssh_gss_setup(Conf *conf) -{ - struct ssh_gss_liblist *list = snew(struct ssh_gss_liblist); - - list->libraries = snew(struct ssh_gss_library); - list->nlibraries = 1; - - 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(verify_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); - BIND_GSS_FN(acquire_cred); - BIND_GSS_FN(inquire_cred_by_mech); - -#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 */ diff --git a/unix/uxnet.c b/unix/uxnet.c deleted file mode 100644 index bd6ebb1d..00000000 --- a/unix/uxnet.c +++ /dev/null @@ -1,1761 +0,0 @@ -/* - * Unix networking abstraction. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "putty.h" -#include "network.h" -#include "tree234.h" - -/* Solaris needs for SIOCATMARK. */ -#ifndef SIOCATMARK -#include -#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 { - struct sockaddr_storage storage; - struct sockaddr sa; - struct sockaddr_in sin; -#ifndef NO_IPV6 - struct sockaddr_in6 sin6; -#endif - struct sockaddr_un su; -}; - -/* - * 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; -}; - -typedef struct NetSocket NetSocket; -struct NetSocket { - const char *error; - int s; - Plug *plug; - bufchain output_data; - bool connected; /* irrelevant for listening sockets */ - bool writable; - bool frozen; /* this causes readability notifications to be ignored */ - bool localhost_only; /* for listening sockets */ - char oobdata[1]; - size_t sending_oob; - bool oobpending; /* is there OOB data available to read? */ - bool oobinline; - enum { EOF_NO, EOF_PENDING, EOF_SENT } outgoingeof; - bool incomingeof; - int pending_error; /* in case send() returns error */ - bool listener; - bool nodelay, keepalive; /* for connect()-type sockets */ - bool privport; - int 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. - */ - NetSocket *parent, *child; - - Socket sock; -}; - -struct SockAddr { - 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 -/* Here we gratuitously reference 'step' to avoid gcc warnings about - * 'set but not used' when compiling -DNO_IPV6 */ -#define SOCKADDR_FAMILY(addr, step) \ - ((addr)->superfamily == UNRESOLVED ? AF_UNSPEC : \ - (addr)->superfamily == UNIX ? AF_UNIX : \ - (step).curraddr ? AF_INET : 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(NetSocket *s); - -static int cmpfortree(void *av, void *bv) -{ - NetSocket *a = (NetSocket *) av, *b = (NetSocket *) 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) -{ - NetSocket *b = (NetSocket *) 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) -{ - NetSocket *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) -{ - if (host[0] == '/') { - *canonicalname = dupstr(host); - return unix_sock_addr(host); - } - - SockAddr *ret = snew(SockAddr); -#ifndef NO_IPV6 - struct addrinfo hints; - int err; -#else - unsigned long a; - struct hostent *h = NULL; - int n; -#endif - strbuf *realhost = strbuf_new(); - - /* Clear the structure and default to IPv4. */ - memset(ret, 0, sizeof(SockAddr)); - ret->superfamily = UNRESOLVED; - 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; - { - char *trimmed_host = host_strduptrim(host); /* strip [] on literals */ - err = getaddrinfo(trimmed_host, NULL, &hints, &ret->ais); - sfree(trimmed_host); - } - if (err != 0) { - ret->error = gai_strerror(err); - strbuf_free(realhost); - return ret; - } - ret->superfamily = IP; - - if (ret->ais->ai_canonname != NULL) - strbuf_catf(realhost, "%s", ret->ais->ai_canonname); - else - strbuf_catf(realhost, "%s", host); -#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"); - strbuf_free(realhost); - return ret; - } - /* This way we are always sure the h->h_name is valid :) */ - strbuf_clear(realhost); - strbuf_catf(realhost, "%s", h->h_name); - 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; - strbuf_clear(realhost); - strbuf_catf(realhost, "%s", host); - ret->addresses = snew(unsigned long); - ret->naddresses = 1; - ret->addresses[0] = ntohl(a); - } -#endif - *canonicalname = strbuf_to_str(realhost); - return ret; -} - -SockAddr *sk_nonamelookup(const char *host) -{ - SockAddr *ret = snew(SockAddr); - 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 bool 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) -{ - if (addr->superfamily == UNRESOLVED || addr->superfamily == UNIX) { - strncpy(buf, addr->hostname, buflen); - buf[buflen-1] = '\0'; - } else { -#ifndef NO_IPV6 - if (getnameinfo(addr->ais->ai_addr, addr->ais->ai_addrlen, buf, buflen, - NULL, 0, NI_NUMERICHOST) != 0) { - buf[0] = '\0'; - strncat(buf, "", 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 - } -} - -/* - * This constructs a SockAddr that points at one specific sub-address - * of a parent SockAddr. The returned SockAddr does not own all its - * own memory: it points into the old one's data structures, so it - * MUST NOT be used after the old one is freed, and it MUST NOT be - * passed to sk_addr_free. (The latter is why it's returned by value - * rather than dynamically allocated - that should clue in anyone - * writing a call to it that something is weird about it.) - */ -static SockAddr sk_extractaddr_tmp( - SockAddr *addr, const SockAddrStep *step) -{ - SockAddr toret; - toret = *addr; /* structure copy */ - toret.refcount = 1; - - if (addr->superfamily == IP) { -#ifndef NO_IPV6 - toret.ais = step->ai; -#else - assert(SOCKADDR_FAMILY(addr, *step) == AF_INET); - toret.addresses += step->curraddr; -#endif - } - - return toret; -} - -bool sk_addr_needs_port(SockAddr *addr) -{ - if (addr->superfamily == UNRESOLVED || addr->superfamily == UNIX) { - return false; - } else { - return true; - } -} - -bool sk_hostname_is_local(const 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 bool 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; - } -} - -bool sk_address_is_local(SockAddr *addr) -{ - if (addr->superfamily == UNRESOLVED) - return false; /* we don't know; assume not */ - else if (addr->superfamily == UNIX) - return true; - 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 - } -} - -bool sk_address_is_special_local(SockAddr *addr) -{ - return addr->superfamily == UNIX; -} - -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 - unreachable("bad address family in sk_addrcopy"); -#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_net_plug(Socket *sock, Plug *p) -{ - NetSocket *s = container_of(sock, NetSocket, sock); - Plug *ret = s->plug; - if (p) - s->plug = p; - return ret; -} - -static void sk_net_close(Socket *s); -static size_t sk_net_write(Socket *s, const void *data, size_t len); -static size_t sk_net_write_oob(Socket *s, const void *data, size_t len); -static void sk_net_write_eof(Socket *s); -static void sk_net_set_frozen(Socket *s, bool is_frozen); -static SocketPeerInfo *sk_net_peer_info(Socket *s); -static const char *sk_net_socket_error(Socket *s); - -static const SocketVtable NetSocket_sockvt = { - .plug = sk_net_plug, - .close = sk_net_close, - .write = sk_net_write, - .write_oob = sk_net_write_oob, - .write_eof = sk_net_write_eof, - .set_frozen = sk_net_set_frozen, - .socket_error = sk_net_socket_error, - .peer_info = sk_net_peer_info, -}; - -static Socket *sk_net_accept(accept_ctx_t ctx, Plug *plug) -{ - int sockfd = ctx.i; - NetSocket *ret; - - /* - * Create NetSocket structure. - */ - ret = snew(NetSocket); - ret->sock.vt = &NetSocket_sockvt; - ret->error = NULL; - ret->plug = plug; - bufchain_init(&ret->output_data); - ret->writable = true; /* to start with */ - ret->sending_oob = 0; - ret->frozen = true; - ret->localhost_only = false; /* unused, but best init anyway */ - ret->pending_error = 0; - ret->oobpending = false; - ret->outgoingeof = EOF_NO; - ret->incomingeof = false; - ret->listener = false; - ret->parent = ret->child = NULL; - ret->addr = NULL; - ret->connected = true; - - ret->s = sockfd; - - if (ret->s < 0) { - ret->error = strerror(errno); - return &ret->sock; - } - - ret->oobinline = false; - - uxsel_tell(ret); - add234(sktree, ret); - - return &ret->sock; -} - -static int try_connect(NetSocket *sock) -{ - int s; - union sockaddr_union u; - const union sockaddr_union *sa; - int err = 0; - short localport; - int salen, family; - - /* - * Remove the socket from the tree before we overwrite its - * internal socket id, because that forms part of the tree's - * sorting criterion. We'll add it back before exiting this - * function, whether we changed anything or not. - */ - del234(sktree, sock); - - if (sock->s >= 0) - close(sock->s); - - { - SockAddr thisaddr = sk_extractaddr_tmp( - sock->addr, &sock->step); - plug_log(sock->plug, PLUGLOG_CONNECT_TRYING, - &thisaddr, 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 = 1; - if (setsockopt(s, SOL_SOCKET, SO_OOBINLINE, - (void *) &b, sizeof(b)) < 0) { - err = errno; - close(s); - goto ret; - } - } - - if (sock->nodelay && family != AF_UNIX) { - int b = 1; - if (setsockopt(s, IPPROTO_TCP, TCP_NODELAY, - (void *) &b, sizeof(b)) < 0) { - err = errno; - close(s); - goto ret; - } - } - - if (sock->keepalive) { - int b = 1; - if (setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, - (void *) &b, sizeof(b)) < 0) { - err = errno; - close(s); - goto ret; - } - } - - /* - * 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(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: - unreachable("unknown address family"); - exit(1); /* XXX: GCC doesn't understand assert() on some systems. */ - } - - nonblock(s); - - 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 = true; - sock->writable = true; - - SockAddr thisaddr = sk_extractaddr_tmp(sock->addr, &sock->step); - plug_log(sock->plug, PLUGLOG_CONNECT_SUCCESS, - &thisaddr, sock->port, NULL, 0); - } - - uxsel_tell(sock); - - ret: - - /* - * No matter what happened, put the socket back in the tree. - */ - add234(sktree, sock); - - if (err) { - SockAddr thisaddr = sk_extractaddr_tmp( - sock->addr, &sock->step); - plug_log(sock->plug, PLUGLOG_CONNECT_FAILED, - &thisaddr, sock->port, strerror(err), err); - } - return err; -} - -Socket *sk_new(SockAddr *addr, int port, bool privport, bool oobinline, - bool nodelay, bool keepalive, Plug *plug) -{ - NetSocket *ret; - int err; - - /* - * Create NetSocket structure. - */ - ret = snew(NetSocket); - ret->sock.vt = &NetSocket_sockvt; - ret->error = NULL; - ret->plug = plug; - bufchain_init(&ret->output_data); - ret->connected = false; /* to start with */ - ret->writable = false; /* to start with */ - ret->sending_oob = 0; - ret->frozen = false; - ret->localhost_only = false; /* unused, but best init anyway */ - ret->pending_error = 0; - ret->parent = ret->child = NULL; - ret->oobpending = false; - ret->outgoingeof = EOF_NO; - ret->incomingeof = false; - ret->listener = false; - 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; - - do { - err = try_connect(ret); - } while (err && sk_nextaddr(ret->addr, &ret->step)); - - if (err) - ret->error = strerror(err); - - return &ret->sock; -} - -Socket *sk_newlistener(const char *srcaddr, int port, Plug *plug, - bool local_host_only, int orig_address_family) -{ - int s; -#ifndef NO_IPV6 - struct addrinfo hints, *ai = NULL; - char portstr[6]; -#endif - union sockaddr_union u; - union sockaddr_union *addr; - int addrlen; - NetSocket *ret; - int retcode; - int address_family; - int on = 1; - - /* - * Create NetSocket structure. - */ - ret = snew(NetSocket); - ret->sock.vt = &NetSocket_sockvt; - ret->error = NULL; - ret->plug = plug; - bufchain_init(&ret->output_data); - ret->writable = false; /* to start with */ - ret->sending_oob = 0; - ret->frozen = false; - ret->localhost_only = local_host_only; - ret->pending_error = 0; - ret->parent = ret->child = NULL; - ret->oobpending = false; - ret->outgoingeof = EOF_NO; - ret->incomingeof = false; - ret->listener = true; - ret->addr = NULL; - ret->s = -1; - - /* - * 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 &ret->sock; - } - - cloexec(s); - - ret->oobinline = false; - - if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, - (const char *)&on, sizeof(on)) < 0) { - ret->error = strerror(errno); - close(s); - return &ret->sock; - } - - 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); - { - char *trimmed_addr = host_strduptrim(srcaddr); - retcode = getaddrinfo(trimmed_addr, portstr, &hints, &ai); - sfree(trimmed_addr); - } - 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); - -#ifndef NO_IPV6 - if (ai) - freeaddrinfo(ai); -#endif - - if (retcode < 0) { - close(s); - ret->error = strerror(errno); - return &ret->sock; - } - - if (listen(s, SOMAXCONN) < 0) { - close(s); - ret->error = strerror(errno); - return &ret->sock; - } - -#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) { - NetSocket *other; - - other = container_of( - sk_newlistener(srcaddr, port, plug, - local_host_only, ADDRTYPE_IPV4), - NetSocket, sock); - - 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 &other->sock; - } - } - } -#endif - - ret->s = s; - - uxsel_tell(ret); - add234(sktree, ret); - - return &ret->sock; -} - -static void sk_net_close(Socket *sock) -{ - NetSocket *s = container_of(sock, NetSocket, sock); - - if (s->child) - sk_net_close(&s->child->sock); - - bufchain_clear(&s->output_data); - - del234(sktree, s); - if (s->s >= 0) { - uxsel_del(s->s); - close(s->s); - } - if (s->addr) - sk_addr_free(s->addr); - delete_callbacks_for_context(s); - sfree(s); -} - -void *sk_getxdmdata(Socket *sock, int *lenp) -{ - NetSocket *s; - union sockaddr_union u; - socklen_t addrlen; - char *buf; - static unsigned int unix_addr = 0xFFFFFFFF; - - /* - * We must check that this socket really _is_ a NetSocket before - * downcasting it. - */ - if (sock->vt != &NetSocket_sockvt) - return NULL; /* failure */ - s = container_of(sock, NetSocket, sock); - - 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; -} - -/* - * Deal with socket errors detected in try_send(). - */ -static void socket_error_callback(void *vs) -{ - NetSocket *s = (NetSocket *)vs; - - /* - * Just in case other socket work has caused this socket to vanish - * or become somehow non-erroneous before this callback arrived... - */ - if (!find234(sktree, s, NULL) || !s->pending_error) - return; - - /* - * An error has occurred on this socket. Pass it to the plug. - */ - plug_closing(s->plug, strerror(s->pending_error), s->pending_error, 0); -} - -/* - * The function which tries to send on a socket once it's deemed - * writable. - */ -void try_send(NetSocket *s) -{ - while (s->sending_oob || bufchain_size(&s->output_data) > 0) { - int nsent; - int err; - const void *data; - size_t len; - int urgentflag; - - if (s->sending_oob) { - urgentflag = MSG_OOB; - len = s->sending_oob; - data = &s->oobdata; - } else { - urgentflag = 0; - ptrlen bufdata = bufchain_prefix(&s->output_data); - data = bufdata.ptr; - len = bufdata.len; - } - nsent = send(s->s, data, len, urgentflag); - noise_ultralight(NOISE_SOURCE_IOLEN, 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; - /* - * Immediately cease selecting on this socket, so that - * we don't tight-loop repeatedly trying to do - * whatever it was that went wrong. - */ - uxsel_tell(s); - /* - * Arrange to be called back from the top level to - * deal with the error condition on this socket. - */ - queue_toplevel_callback(socket_error_callback, s); - return; - } - } else { - if (s->sending_oob) { - if (nsent < len) { - 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); - } - } - } - - /* - * If we reach here, we've finished sending everything we might - * have needed to send. Send EOF, if we need to. - */ - if (s->outgoingeof == EOF_PENDING) { - shutdown(s->s, SHUT_WR); - s->outgoingeof = EOF_SENT; - } - - /* - * Also update the select status, because we don't need to select - * for writing any more. - */ - uxsel_tell(s); -} - -static size_t sk_net_write(Socket *sock, const void *buf, size_t len) -{ - NetSocket *s = container_of(sock, NetSocket, sock); - - assert(s->outgoingeof == EOF_NO); - - /* - * 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 size_t sk_net_write_oob(Socket *sock, const void *buf, size_t len) -{ - NetSocket *s = container_of(sock, NetSocket, sock); - - assert(s->outgoingeof == EOF_NO); - - /* - * Replace the buffer list on the socket with the data. - */ - bufchain_clear(&s->output_data); - assert(len <= sizeof(s->oobdata)); - 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 void sk_net_write_eof(Socket *sock) -{ - NetSocket *s = container_of(sock, NetSocket, sock); - - assert(s->outgoingeof == EOF_NO); - - /* - * Mark the socket as pending outgoing EOF. - */ - s->outgoingeof = EOF_PENDING; - - /* - * Now try sending from the start of the buffer list. - */ - if (s->writable) - try_send(s); - - /* - * Update the select() status to correctly reflect whether or - * not we should be selecting for write. - */ - uxsel_tell(s); -} - -static void net_select_result(int fd, int event) -{ - int ret; - char buf[20480]; /* nice big buffer for plenty of speed */ - NetSocket *s; - bool atmark = true; - - /* Find the Socket structure */ - s = find234(sktree, &fd, cmpforsearch); - if (!s) - return; /* boggle */ - - noise_ultralight(NOISE_SOURCE_IOID, fd); - - switch (event) { - case SELECT_X: /* 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(NOISE_SOURCE_IOLEN, ret); - if (ret <= 0) { - 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; - } - 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 SELECT_R: /* 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); - accept_ctx_t actx; - int t; /* socket of connection */ - - memset(&su, 0, addrlen); - t = accept(s->s, &su.sa, &addrlen); - if (t < 0) { - break; - } - - nonblock(t); - actx.i = t; - - if ((!s->addr || s->addr->superfamily != UNIX) && - s->localhost_only && !sockaddr_is_loopback(&su.sa)) { - close(t); /* someone let nonlocal through?! */ - } else if (plug_accepting(s->plug, sk_net_accept, actx)) { - 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) - 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) { - int atmark_from_ioctl; - if (ioctl(s->s, SIOCATMARK, &atmark_from_ioctl) == 0) { - atmark = atmark_from_ioctl; - if (atmark) - s->oobpending = false; /* clear this indicator */ - } - } else - atmark = true; - - ret = recv(s->s, buf, s->oobpending ? 1 : sizeof(buf), 0); - noise_ultralight(NOISE_SOURCE_IOLEN, ret); - if (ret < 0) { - if (errno == EWOULDBLOCK) { - break; - } - } - if (ret < 0) { - plug_closing(s->plug, strerror(errno), errno, 0); - } else if (0 == ret) { - s->incomingeof = true; /* stop trying to read now */ - uxsel_tell(s); - 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; - } - plug_receive(s->plug, atmark ? 0 : 1, buf, ret); - } - break; - case SELECT_W: /* writable */ - if (!s->connected) { - /* - * select/poll reports a socket as _writable_ when an - * asynchronous connect() attempt either completes or - * fails. So first we must find out which. - */ - { - int err; - socklen_t errlen = sizeof(err); - char *errmsg = NULL; - if (getsockopt(s->s, SOL_SOCKET, SO_ERROR, &err, &errlen)<0) { - errmsg = dupprintf("getsockopt(SO_ERROR): %s", - strerror(errno)); - err = errno; /* got to put something in here */ - } else if (err != 0) { - errmsg = dupstr(strerror(err)); - } - if (errmsg) { - /* - * The asynchronous connection attempt failed. - * Report the problem via plug_log, and try again - * with the next candidate address, if we have - * more than one. - */ - SockAddr thisaddr; - assert(s->addr); - - thisaddr = sk_extractaddr_tmp(s->addr, &s->step); - plug_log(s->plug, PLUGLOG_CONNECT_FAILED, - &thisaddr, s->port, errmsg, err); - - while (err && s->addr && sk_nextaddr(s->addr, &s->step)) { - err = try_connect(s); - } - if (err) { - plug_closing(s->plug, strerror(err), err, 0); - return; /* socket is now presumably defunct */ - } - if (!s->connected) - return; /* another async attempt in progress */ - } else { - /* - * The connection attempt succeeded. - */ - SockAddr thisaddr = sk_extractaddr_tmp(s->addr, &s->step); - plug_log(s->plug, PLUGLOG_CONNECT_SUCCESS, - &thisaddr, s->port, NULL, 0); - } - } - - /* - * If we get here, we've managed to make a connection. - */ - if (s->addr) { - sk_addr_free(s->addr); - s->addr = NULL; - } - s->connected = true; - s->writable = true; - uxsel_tell(s); - } else { - size_t bufsize_before, bufsize_after; - s->writable = true; - 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; - } -} - -/* - * 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_net_socket_error(Socket *sock) -{ - NetSocket *s = container_of(sock, NetSocket, sock); - return s->error; -} - -static void sk_net_set_frozen(Socket *sock, bool is_frozen) -{ - NetSocket *s = container_of(sock, NetSocket, sock); - if (s->frozen == is_frozen) - return; - s->frozen = is_frozen; - uxsel_tell(s); -} - -static SocketPeerInfo *sk_net_peer_info(Socket *sock) -{ - NetSocket *s = container_of(sock, NetSocket, sock); - union sockaddr_union addr; - socklen_t addrlen = sizeof(addr); -#ifndef NO_IPV6 - char buf[INET6_ADDRSTRLEN]; -#endif - SocketPeerInfo *pi; - - if (getpeername(s->s, &addr.sa, &addrlen) < 0) - return NULL; - - pi = snew(SocketPeerInfo); - pi->addressfamily = ADDRTYPE_UNSPEC; - pi->addr_text = NULL; - pi->port = -1; - pi->log_text = NULL; - - if (addr.storage.ss_family == AF_INET) { - pi->addressfamily = ADDRTYPE_IPV4; - memcpy(pi->addr_bin.ipv4, &addr.sin.sin_addr, 4); - pi->port = ntohs(addr.sin.sin_port); - pi->addr_text = dupstr(inet_ntoa(addr.sin.sin_addr)); - pi->log_text = dupprintf("%s:%d", pi->addr_text, pi->port); - -#ifndef NO_IPV6 - } else if (addr.storage.ss_family == AF_INET6) { - pi->addressfamily = ADDRTYPE_IPV6; - memcpy(pi->addr_bin.ipv6, &addr.sin6.sin6_addr, 16); - pi->port = ntohs(addr.sin6.sin6_port); - pi->addr_text = dupstr( - inet_ntop(AF_INET6, &addr.sin6.sin6_addr, buf, sizeof(buf))); - pi->log_text = dupprintf("[%s]:%d", pi->addr_text, pi->port); -#endif - - } else if (addr.storage.ss_family == AF_UNIX) { - pi->addressfamily = ADDRTYPE_LOCAL; - - /* - * For Unix sockets, the source address is unlikely to be - * helpful, so we leave addr_txt NULL (and we certainly can't - * fill in port, obviously). Instead, we try SO_PEERCRED and - * try to get the source pid, and put that in the log text. - */ - int pid, uid, gid; - if (so_peercred(s->s, &pid, &uid, &gid)) { - char uidbuf[64], gidbuf[64]; - sprintf(uidbuf, "%d", uid); - sprintf(gidbuf, "%d", gid); - struct passwd *pw = getpwuid(uid); - struct group *gr = getgrgid(gid); - pi->log_text = dupprintf("pid %d (%s:%s)", pid, - pw ? pw->pw_name : uidbuf, - gr ? gr->gr_name : gidbuf); - } - } else { - sfree(pi); - return NULL; - } - - return pi; -} - -int sk_net_get_fd(Socket *sock) -{ - /* This function is not fully general: it only works on NetSocket */ - if (sock->vt != &NetSocket_sockvt) - return -1; /* failure */ - NetSocket *s = container_of(sock, NetSocket, sock); - return s->s; -} - -static void uxsel_tell(NetSocket *s) -{ - int rwx = 0; - if (!s->pending_error) { - if (s->listener) { - rwx |= SELECT_R; /* read == accept */ - } else { - if (!s->connected) - rwx |= SELECT_W; /* write == connect */ - if (s->connected && !s->frozen && !s->incomingeof) - rwx |= SELECT_R | SELECT_X; - if (bufchain_size(&s->output_data)) - rwx |= SELECT_W; - } - } - 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) -{ - size_t size = 0; - char *hostname = NULL; - do { - sgrowarray(hostname, size, size); - if ((gethostname(hostname, size) < 0) && (errno != ENAMETOOLONG)) { - sfree(hostname); - hostname = NULL; - break; - } - } while (strlen(hostname) >= size-1); - return hostname; -} - -SockAddr *platform_get_x11_unix_address(const char *sockpath, int displaynum) -{ - SockAddr *ret = snew(SockAddr); - 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; -} - -SockAddr *unix_sock_addr(const char *path) -{ - SockAddr *ret = snew(SockAddr); - int n; - - memset(ret, 0, sizeof *ret); - ret->superfamily = UNIX; - n = snprintf(ret->hostname, sizeof ret->hostname, "%s", path); - - if (n < 0) - ret->error = "snprintf failed"; - else if (n >= sizeof ret->hostname || - n >= sizeof(((struct sockaddr_un *)0)->sun_path)) - ret->error = "socket pathname too long"; - -#ifndef NO_IPV6 - ret->ais = NULL; -#else - ret->addresses = NULL; - ret->naddresses = 0; -#endif - ret->refcount = 1; - return ret; -} - -Socket *new_unix_listener(SockAddr *listenaddr, Plug *plug) -{ - int s; - union sockaddr_union u; - union sockaddr_union *addr; - int addrlen; - NetSocket *ret; - int retcode; - - /* - * Create NetSocket structure. - */ - ret = snew(NetSocket); - ret->sock.vt = &NetSocket_sockvt; - ret->error = NULL; - ret->plug = plug; - bufchain_init(&ret->output_data); - ret->writable = false; /* to start with */ - ret->sending_oob = 0; - ret->frozen = false; - ret->localhost_only = true; - ret->pending_error = 0; - ret->parent = ret->child = NULL; - ret->oobpending = false; - ret->outgoingeof = EOF_NO; - ret->incomingeof = false; - ret->listener = true; - ret->addr = listenaddr; - ret->s = -1; - - assert(listenaddr->superfamily == UNIX); - - /* - * Open socket. - */ - s = socket(AF_UNIX, SOCK_STREAM, 0); - if (s < 0) { - ret->error = strerror(errno); - return &ret->sock; - } - - cloexec(s); - - ret->oobinline = false; - - memset(&u, '\0', sizeof(u)); - u.su.sun_family = AF_UNIX; -#if __GNUC__ >= 8 -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wstringop-truncation" -#endif // __GNUC__ >= 8 - strncpy(u.su.sun_path, listenaddr->hostname, sizeof(u.su.sun_path)-1); -#if __GNUC__ >= 8 -# pragma GCC diagnostic pop -#endif // __GNUC__ >= 8 - addr = &u; - addrlen = sizeof(u.su); - - if (unlink(u.su.sun_path) < 0 && errno != ENOENT) { - close(s); - ret->error = strerror(errno); - return &ret->sock; - } - - retcode = bind(s, &addr->sa, addrlen); - if (retcode < 0) { - close(s); - ret->error = strerror(errno); - return &ret->sock; - } - - if (listen(s, SOMAXCONN) < 0) { - close(s); - ret->error = strerror(errno); - return &ret->sock; - } - - ret->s = s; - - uxsel_tell(ret); - add234(sktree, ret); - - return &ret->sock; -} diff --git a/unix/uxnogtk.c b/unix/uxnogtk.c deleted file mode 100644 index c9028ebf..00000000 --- a/unix/uxnogtk.c +++ /dev/null @@ -1,11 +0,0 @@ -/* - * uxnogtk.c: link into non-GUI Unix programs so that they can tell - * buildinfo about a lack of GTK. - */ - -#include "putty.h" - -char *buildinfo_gtk_version(void) -{ - return NULL; -} diff --git a/unix/uxnoise.c b/unix/uxnoise.c deleted file mode 100644 index 0fbf8c4d..00000000 --- a/unix/uxnoise.c +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Noise generation for PuTTY's cryptographic random number - * generator. - */ - -#include -#include -#include - -#include -#include -#include -#include - -#include "putty.h" -#include "ssh.h" -#include "storage.h" - -static bool read_dev_urandom(char *buf, int len) -{ - int fd; - int ngot, ret; - - fd = open("/dev/urandom", O_RDONLY); - if (fd < 0) - return false; - - ngot = 0; - while (ngot < len) { - ret = read(fd, buf+ngot, len-ngot); - if (ret < 0) { - close(fd); - return false; - } - ngot += ret; - } - - close(fd); - - return true; -} - -/* - * 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; - bool got_dev_urandom = false; - - if (read_dev_urandom(buf, 32)) { - got_dev_urandom = true; - 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); -} - -/* - * 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(NOISE_SOURCE_MEMINFO, buf, ret); - close(fd); - } - if ((fd = open("/proc/stat", O_RDONLY)) >= 0) { - while ( (ret = read(fd, buf, sizeof(buf))) > 0) - random_add_noise(NOISE_SOURCE_STAT, buf, ret); - close(fd); - } - getrusage(RUSAGE_SELF, &rusage); - random_add_noise(NOISE_SOURCE_RUSAGE, &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(NoiseSourceId id, unsigned long data) -{ - struct timeval tv; - gettimeofday(&tv, NULL); - random_add_noise(NOISE_SOURCE_TIME, &tv, sizeof(tv)); - random_add_noise(id, &data, sizeof(data)); -} - -uint64_t prng_reseed_time_ms(void) -{ - struct timeval tv; - gettimeofday(&tv, NULL); - return tv.tv_sec * 1000 + tv.tv_usec / 1000; -} diff --git a/unix/uxpeer.c b/unix/uxpeer.c deleted file mode 100644 index 11f03291..00000000 --- a/unix/uxpeer.c +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Unix: wrapper for getsockopt(SO_PEERCRED), conditionalised on - * appropriate autoconfery. - */ - -#if HAVE_CMAKE_H -#include "cmake.h" -#endif - -#if HAVE_SO_PEERCRED -#define _GNU_SOURCE -#include -#endif - -#include - -#include "putty.h" - -bool so_peercred(int fd, int *pid, int *uid, int *gid) -{ -#if HAVE_SO_PEERCRED - struct ucred cr; - socklen_t crlen = sizeof(cr); - if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cr, &crlen) == 0) { - *pid = cr.pid; - *uid = cr.uid; - *gid = cr.gid; - return true; - } -#endif - return false; -} diff --git a/unix/uxpgnt.c b/unix/uxpgnt.c deleted file mode 100644 index f204ea9d..00000000 --- a/unix/uxpgnt.c +++ /dev/null @@ -1,1509 +0,0 @@ -/* - * Unix Pageant, more or less similar to ssh-agent. - */ - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include "putty.h" -#include "ssh.h" -#include "misc.h" -#include "pageant.h" - -void cmdline_error(const char *fmt, ...) -{ - va_list ap; - va_start(ap, fmt); - console_print_error_msg_fmt_v("pageant", fmt, ap); - va_end(ap); - exit(1); -} - -static void setup_sigchld_handler(void); - -typedef enum RuntimePromptType { - RTPROMPT_UNAVAILABLE, - RTPROMPT_DEBUG, - RTPROMPT_GUI, -} RuntimePromptType; - -static const char *progname; - -struct uxpgnt_client { - FILE *logfp; - strbuf *prompt_buf; - RuntimePromptType prompt_type; - bool prompt_active; - PageantClientDialogId *dlgid; - int passphrase_fd; - int termination_pid; - - PageantListenerClient plc; -}; - -static void uxpgnt_log(PageantListenerClient *plc, const char *fmt, va_list ap) -{ - struct uxpgnt_client *upc = container_of(plc, struct uxpgnt_client, plc); - - if (!upc->logfp) - return; - - fprintf(upc->logfp, "pageant: "); - vfprintf(upc->logfp, fmt, ap); - fprintf(upc->logfp, "\n"); -} - -static int make_pipe_to_askpass(const char *msg) -{ - int pipefds[2]; - - setup_sigchld_handler(); - - if (pipe(pipefds) < 0) - return -1; - - pid_t pid = fork(); - if (pid < 0) { - close(pipefds[0]); - close(pipefds[1]); - return -1; - } - - if (pid == 0) { - const char *args[5] = { - progname, "--gui-prompt", "--askpass", msg, NULL - }; - - dup2(pipefds[1], 1); - cloexec(pipefds[0]); - cloexec(pipefds[1]); - - /* - * See comment in fork_and_exec_self() in gtkmain.c. - */ - execv("/proc/self/exe", (char **)args); - execvp(progname, (char **)args); - perror("exec"); - _exit(127); - } - - close(pipefds[1]); - return pipefds[0]; -} - -static bool uxpgnt_ask_passphrase( - PageantListenerClient *plc, PageantClientDialogId *dlgid, - const char *comment) -{ - struct uxpgnt_client *upc = container_of(plc, struct uxpgnt_client, plc); - - assert(!upc->dlgid); /* Pageant core should be serialising requests */ - - char *msg = dupprintf( - "A client of Pageant wants to use the following encrypted key:\n" - "%s\n" - "If you intended this, enter the passphrase to decrypt the key.", - comment); - - switch (upc->prompt_type) { - case RTPROMPT_UNAVAILABLE: - sfree(msg); - return false; - - case RTPROMPT_GUI: - upc->passphrase_fd = make_pipe_to_askpass(msg); - sfree(msg); - if (upc->passphrase_fd < 0) - return false; /* something went wrong */ - break; - - case RTPROMPT_DEBUG: - fprintf(upc->logfp, "pageant passphrase request: %s\n", msg); - sfree(msg); - break; - } - - upc->prompt_active = true; - upc->dlgid = dlgid; - return true; -} - -static void passphrase_done(struct uxpgnt_client *upc, bool success) -{ - PageantClientDialogId *dlgid = upc->dlgid; - upc->dlgid = NULL; - upc->prompt_active = false; - - if (upc->logfp) - fprintf(upc->logfp, "pageant passphrase response: %s\n", - success ? "success" : "failure"); - - if (success) - pageant_passphrase_request_success( - dlgid, ptrlen_from_strbuf(upc->prompt_buf)); - else - pageant_passphrase_request_refused(dlgid); - - strbuf_free(upc->prompt_buf); - upc->prompt_buf = strbuf_new_nm(); -} - -static const PageantListenerClientVtable uxpgnt_vtable = { - .log = uxpgnt_log, - .ask_passphrase = uxpgnt_ask_passphrase, -}; - -/* - * More stubs. - */ -void random_save_seed(void) {} -void random_destroy_seed(void) {} -char *platform_default_s(const char *name) { return NULL; } -bool platform_default_b(const char *name, bool def) { return def; } -int platform_default_i(const char *name, int def) { return def; } -FontSpec *platform_default_fontspec(const char *name) { return fontspec_new(""); } -Filename *platform_default_filename(const char *name) { return filename_from_str(""); } -char *x_get_default(const char *key) { return NULL; } - -/* - * Short description of parameters. - */ -static void usage(void) -{ - printf("Pageant: SSH agent\n"); - printf("%s\n", ver); - printf("Usage: pageant [[--encrypted] key files]\n"); - printf(" pageant [[--encrypted] key files] --exec [args]\n"); - printf(" pageant -a [--encrypted] [key files]\n"); - printf(" pageant -d [key identifiers]\n"); - printf(" pageant -D\n"); - printf(" pageant -r [key identifiers]\n"); - printf(" pageant -R\n"); - printf(" pageant --public [key identifiers]\n"); - printf(" pageant ( --public-openssh | -L ) [key identifiers]\n"); - printf(" pageant -l [-E fptype]\n"); - printf("Lifetime options, for running Pageant as an agent:\n"); - printf(" -X run with the lifetime of the X server\n"); - printf(" -T run with the lifetime of the controlling tty\n"); - printf(" --permanent run permanently\n"); - printf(" --debug run in debugging mode, without forking\n"); - printf(" --exec run with the lifetime of that command\n"); - printf("Client options, for talking to an existing agent:\n"); - printf(" -a add key(s) to the existing agent\n"); - printf(" -l list currently loaded key fingerprints and comments\n"); - printf(" --public print public keys in RFC 4716 format\n"); - printf(" --public-openssh, -L print public keys in OpenSSH format\n"); - printf(" -d delete key(s) from the agent\n"); - printf(" -D delete all keys from the agent\n"); - printf(" -r re-encrypt keys in the agent (forget cleartext\n"); - printf(" -R re-encrypt all possible keys in the agent\n"); - printf("Other options:\n"); - printf(" -v verbose mode (in agent mode)\n"); - printf(" -s -c force POSIX or C shell syntax (in agent mode)\n"); - printf(" --symlink path create symlink to socket (in agent mode)\n"); - printf(" --encrypted when adding keys, don't decrypt\n"); - printf(" -E alg, --fptype alg fingerprint type for -l (sha256, md5)\n"); - printf(" --tty-prompt force tty-based passphrase prompt\n"); - printf(" --gui-prompt force GUI-based passphrase prompt\n"); - printf(" --askpass behave like a standalone askpass program\n"); - exit(1); -} - -static void version(void) -{ - char *buildinfo_text = buildinfo("\n"); - printf("pageant: %s\n%s\n", ver, buildinfo_text); - sfree(buildinfo_text); - exit(0); -} - -void keylist_update(void) -{ - /* Nothing needs doing in Unix Pageant */ -} - -#define PAGEANT_DIR_PREFIX "/tmp/pageant" - -const char *const appname = "Pageant"; - -static bool time_to_die = false; - -/* - * These functions are part of the plug for our connection to the X - * display, so they do get called. They needn't actually do anything, - * except that x11_closing has to signal back to the main loop that - * it's time to terminate. - */ -static void x11_log(Plug *p, PlugLogType type, SockAddr *addr, int port, - const char *error_msg, int error_code) {} -static void x11_receive(Plug *plug, int urgent, const char *data, size_t len) {} -static void x11_sent(Plug *plug, size_t bufsize) {} -static void x11_closing(Plug *plug, const char *error_msg, int error_code, - bool calling_back) -{ - time_to_die = true; -} -struct X11Connection { - Plug plug; -}; - -static char *socketname; -static enum { SHELL_AUTO, SHELL_SH, SHELL_CSH } shell_type = SHELL_AUTO; -void pageant_print_env(int pid) -{ - if (shell_type == SHELL_AUTO) { - /* Same policy as OpenSSH: if $SHELL ends in "csh" then assume - * it's csh-shaped. */ - const char *shell = getenv("SHELL"); - if (shell && strlen(shell) >= 3 && - !strcmp(shell + strlen(shell) - 3, "csh")) - shell_type = SHELL_CSH; - else - shell_type = SHELL_SH; - } - - /* - * These shell snippets could usefully pay some attention to - * escaping of interesting characters. I don't think it causes a - * problem at the moment, because the pathnames we use are so - * utterly boring, but it's a lurking bug waiting to happen once - * a bit more flexibility turns up. - */ - - switch (shell_type) { - case SHELL_SH: - printf("SSH_AUTH_SOCK=%s; export SSH_AUTH_SOCK;\n" - "SSH_AGENT_PID=%d; export SSH_AGENT_PID;\n", - socketname, pid); - break; - case SHELL_CSH: - printf("setenv SSH_AUTH_SOCK %s;\n" - "setenv SSH_AGENT_PID %d;\n", - socketname, pid); - break; - case SHELL_AUTO: - unreachable("SHELL_AUTO should have been eliminated by now"); - break; - } -} - -void pageant_fork_and_print_env(bool retain_tty) -{ - pid_t pid = fork(); - if (pid == -1) { - perror("fork"); - exit(1); - } else if (pid != 0) { - pageant_print_env(pid); - exit(0); - } - - /* - * Having forked off, we now daemonise ourselves as best we can. - * It's good practice in general to setsid() ourself out of any - * process group we didn't want to be part of, and to chdir("/") - * to avoid holding any directories open that we don't need in - * case someone wants to umount them; also, we should definitely - * close standard output (because it will very likely be pointing - * at a pipe from which some parent process is trying to read our - * environment variable dump, so if we hold open another copy of - * it then that process will never finish reading). We close - * standard input too on general principles, but not standard - * error, since we might need to shout a panicky error message - * down that one. - */ - if (chdir("/") < 0) { - /* should there be an error condition, nothing we can do about - * it anyway */ - } - close(0); - close(1); - if (retain_tty) { - /* Get out of our previous process group, to avoid being - * blasted by passing signals. But keep our controlling tty, - * so we can keep checking to see if we still have one. */ - setpgrp(); - } else { - /* Do that, but also leave our entire session and detach from - * the controlling tty (if any). */ - setsid(); - } -} - -static int signalpipe[2] = { -1, -1 }; - -static void sigchld(int signum) -{ - if (write(signalpipe[1], "x", 1) <= 0) - /* not much we can do about it */; -} - -static void setup_sigchld_handler(void) -{ - if (signalpipe[0] >= 0) - return; - - /* - * Set up the pipe we'll use to tell us about SIGCHLD. - */ - if (pipe(signalpipe) < 0) { - perror("pipe"); - exit(1); - } - putty_signal(SIGCHLD, sigchld); -} - -#define TTY_LIFE_POLL_INTERVAL (TICKSPERSEC * 30) -static void *dummy_timer_ctx; -static void tty_life_timer(void *ctx, unsigned long now) -{ - schedule_timer(TTY_LIFE_POLL_INTERVAL, tty_life_timer, &dummy_timer_ctx); -} - -typedef enum { - KEYACT_AGENT_LOAD, - KEYACT_AGENT_LOAD_ENCRYPTED, - KEYACT_CLIENT_BASE, - KEYACT_CLIENT_ADD = KEYACT_CLIENT_BASE, - KEYACT_CLIENT_ADD_ENCRYPTED, - KEYACT_CLIENT_DEL, - KEYACT_CLIENT_DEL_ALL, - KEYACT_CLIENT_LIST, - KEYACT_CLIENT_PUBLIC_OPENSSH, - KEYACT_CLIENT_PUBLIC, - KEYACT_CLIENT_SIGN, - KEYACT_CLIENT_REENCRYPT, - KEYACT_CLIENT_REENCRYPT_ALL, -} keyact; -struct cmdline_key_action { - struct cmdline_key_action *next; - keyact action; - const char *filename; -}; - -bool is_agent_action(keyact action) -{ - return action < KEYACT_CLIENT_BASE; -} - -static struct cmdline_key_action *keyact_head = NULL, *keyact_tail = NULL; -static uint32_t sign_flags = 0; - -void add_keyact(keyact action, const char *filename) -{ - struct cmdline_key_action *a = snew(struct cmdline_key_action); - a->action = action; - a->filename = filename; - a->next = NULL; - if (keyact_tail) - keyact_tail->next = a; - else - keyact_head = a; - keyact_tail = a; -} - -bool have_controlling_tty(void) -{ - int fd = open("/dev/tty", O_RDONLY); - if (fd < 0) { - if (errno != ENXIO) { - perror("/dev/tty: open"); - exit(1); - } - return false; - } else { - close(fd); - return true; - } -} - -static char **exec_args = NULL; -static enum { - LIFE_UNSPEC, LIFE_X11, LIFE_TTY, LIFE_DEBUG, LIFE_PERM, LIFE_EXEC -} life = LIFE_UNSPEC; -static const char *display = NULL; -static enum { - PROMPT_UNSPEC, PROMPT_TTY, PROMPT_GUI -} prompt_type = PROMPT_UNSPEC; -static FingerprintType key_list_fptype = SSH_FPTYPE_DEFAULT; - -static char *askpass_tty(const char *prompt) -{ - int ret; - prompts_t *p = new_prompts(); - p->to_server = false; - p->from_server = false; - p->name = dupstr("Pageant passphrase prompt"); - add_prompt(p, dupcat(prompt, ": "), false); - ret = console_get_userpass_input(p); - assert(ret >= 0); - - if (!ret) { - perror("pageant: unable to read passphrase"); - free_prompts(p); - return NULL; - } else { - char *passphrase = prompt_get_result(p->prompts[0]); - free_prompts(p); - return passphrase; - } -} - -static char *askpass_gui(const char *prompt) -{ - char *passphrase; - bool success; - - passphrase = gtk_askpass_main( - display, "Pageant passphrase prompt", prompt, &success); - if (!success) { - /* return value is error message */ - fprintf(stderr, "%s\n", passphrase); - sfree(passphrase); - passphrase = NULL; - } - return passphrase; -} - -static char *askpass(const char *prompt) -{ - if (prompt_type == PROMPT_TTY) { - if (!have_controlling_tty()) { - fprintf(stderr, "no controlling terminal available " - "for passphrase prompt\n"); - return NULL; - } - return askpass_tty(prompt); - } - - if (prompt_type == PROMPT_GUI) { - if (!display) { - fprintf(stderr, "no graphical display available " - "for passphrase prompt\n"); - return NULL; - } - return askpass_gui(prompt); - } - - if (have_controlling_tty()) { - return askpass_tty(prompt); - } else if (display) { - return askpass_gui(prompt); - } else { - fprintf(stderr, "no way to read a passphrase without tty or " - "X display\n"); - return NULL; - } -} - -static bool unix_add_keyfile(const char *filename_str, bool add_encrypted) -{ - Filename *filename = filename_from_str(filename_str); - int status; - bool ret; - char *err; - - ret = true; - - /* - * Try without a passphrase. - */ - status = pageant_add_keyfile(filename, NULL, &err, add_encrypted); - if (status == PAGEANT_ACTION_OK) { - goto cleanup; - } else if (status == PAGEANT_ACTION_FAILURE) { - fprintf(stderr, "pageant: %s: %s\n", filename_str, err); - ret = false; - goto cleanup; - } - - /* - * And now try prompting for a passphrase. - */ - while (1) { - char *prompt = dupprintf( - "Enter passphrase to load key '%s'", err); - char *passphrase = askpass(prompt); - sfree(err); - sfree(prompt); - err = NULL; - if (!passphrase) - break; - - status = pageant_add_keyfile(filename, passphrase, &err, - add_encrypted); - - smemclr(passphrase, strlen(passphrase)); - sfree(passphrase); - passphrase = NULL; - - if (status == PAGEANT_ACTION_OK) { - goto cleanup; - } else if (status == PAGEANT_ACTION_FAILURE) { - fprintf(stderr, "pageant: %s: %s\n", filename_str, err); - ret = false; - goto cleanup; - } - } - - cleanup: - sfree(err); - filename_free(filename); - return ret; -} - -void key_list_callback(void *ctx, char **fingerprints, const char *comment, - uint32_t ext_flags, struct pageant_pubkey *key) -{ - const char *mode = ""; - if (ext_flags & LIST_EXTENDED_FLAG_HAS_NO_CLEARTEXT_KEY) - mode = " (encrypted)"; - else if (ext_flags & LIST_EXTENDED_FLAG_HAS_ENCRYPTED_KEY_FILE) - mode = " (re-encryptable)"; - - FingerprintType this_type = - ssh2_pick_fingerprint(fingerprints, key_list_fptype); - printf("%s %s%s\n", fingerprints[this_type], comment, mode); -} - -struct key_find_ctx { - const char *string; - bool match_fp, match_comment; - bool match_fptypes[SSH_N_FPTYPES]; - struct pageant_pubkey *found; - int nfound; -}; - -static bool match_fingerprint_string( - const char *string_orig, char **fingerprints, - const struct key_find_ctx *ctx) -{ - const char *hash; - - for (unsigned fptype = 0; fptype < SSH_N_FPTYPES; fptype++) { - if (!ctx->match_fptypes[fptype]) - continue; - - const char *fingerprint = fingerprints[fptype]; - if (!fingerprint) - continue; - - /* Find the hash in the fingerprint string. It'll be the word - * at the end. */ - hash = strrchr(fingerprint, ' '); - assert(hash); - hash++; - - const char *string = string_orig; - bool case_sensitive; - const char *ignore_chars = ""; - - switch (fptype) { - case SSH_FPTYPE_MD5: - /* MD5 fingerprints are in hex, so disregard case differences. */ - case_sensitive = false; - /* And we don't really need to force the user to type the - * colons in between the digits, which are always the - * same. */ - ignore_chars = ":"; - break; - case SSH_FPTYPE_SHA256: - /* Skip over the "SHA256:" prefix, which we don't really - * want to force the user to type. On the other hand, - * tolerate it on the input string. */ - assert(strstartswith(hash, "SHA256:")); - hash += 7; - if (strstartswith(string, "SHA256:")) - string += 7; - /* SHA256 fingerprints are base64, which is intrinsically - * case sensitive. */ - case_sensitive = true; - break; - } - - /* Now see if the search string is a prefix of the full hash, - * neglecting colons and (where appropriate) case differences. */ - while (1) { - string += strspn(string, ignore_chars); - hash += strspn(hash, ignore_chars); - if (!*string) - return true; - char sc = *string, hc = *hash; - if (!case_sensitive) { - sc = tolower((unsigned char)sc); - hc = tolower((unsigned char)hc); - } - if (sc != hc) - break; - string++; - hash++; - } - } - - return false; -} - -void key_find_callback(void *vctx, char **fingerprints, - const char *comment, uint32_t ext_flags, - struct pageant_pubkey *key) -{ - struct key_find_ctx *ctx = (struct key_find_ctx *)vctx; - - if ((ctx->match_comment && !strcmp(ctx->string, comment)) || - (ctx->match_fp && match_fingerprint_string(ctx->string, fingerprints, - ctx))) - { - if (!ctx->found) - ctx->found = pageant_pubkey_copy(key); - ctx->nfound++; - } -} - -struct pageant_pubkey *find_key(const char *string, char **retstr) -{ - struct key_find_ctx ctx[1]; - struct pageant_pubkey key_in, *key_ret; - bool try_file = true, try_fp = true, try_comment = true; - bool file_errors = false; - bool try_all_fptypes = true; - FingerprintType fptype = SSH_FPTYPE_DEFAULT; - - /* - * Trim off disambiguating prefixes telling us how to interpret - * the provided string. - */ - if (!strncmp(string, "file:", 5)) { - string += 5; - try_fp = false; - try_comment = false; - file_errors = true; /* also report failure to load the file */ - } else if (!strncmp(string, "comment:", 8)) { - string += 8; - try_file = false; - try_fp = false; - } else if (!strncmp(string, "fp:", 3)) { - string += 3; - try_file = false; - try_comment = false; - } else if (!strncmp(string, "fingerprint:", 12)) { - string += 12; - try_file = false; - try_comment = false; - } else if (!strnicmp(string, "md5:", 4)) { - string += 4; - try_file = false; - try_comment = false; - try_all_fptypes = false; - fptype = SSH_FPTYPE_MD5; - } else if (!strncmp(string, "sha256:", 7)) { - string += 7; - try_file = false; - try_comment = false; - try_all_fptypes = false; - fptype = SSH_FPTYPE_SHA256; - } - - /* - * Try interpreting the string as a key file name. - */ - if (try_file) { - Filename *fn = filename_from_str(string); - int keytype = key_type(fn); - if (keytype == SSH_KEYTYPE_SSH1 || - keytype == SSH_KEYTYPE_SSH1_PUBLIC) { - const char *error; - - key_in.blob = strbuf_new(); - if (!rsa1_loadpub_f(fn, BinarySink_UPCAST(key_in.blob), - NULL, &error)) { - strbuf_free(key_in.blob); - key_in.blob = NULL; - if (file_errors) { - *retstr = dupprintf("unable to load file '%s': %s", - string, error); - filename_free(fn); - return NULL; - } - } else { - /* - * If we've successfully loaded the file, stop here - we - * already have a key blob and need not go to the agent to - * list things. - */ - key_in.ssh_version = 1; - key_in.comment = NULL; - key_ret = pageant_pubkey_copy(&key_in); - strbuf_free(key_in.blob); - key_in.blob = NULL; - filename_free(fn); - return key_ret; - } - } else if (keytype == SSH_KEYTYPE_SSH2 || - keytype == SSH_KEYTYPE_SSH2_PUBLIC_RFC4716 || - keytype == SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH) { - const char *error; - - key_in.blob = strbuf_new(); - if (!ppk_loadpub_f(fn, NULL, BinarySink_UPCAST(key_in.blob), - NULL, &error)) { - strbuf_free(key_in.blob); - key_in.blob = NULL; - if (file_errors) { - *retstr = dupprintf("unable to load file '%s': %s", - string, error); - filename_free(fn); - return NULL; - } - } else { - /* - * If we've successfully loaded the file, stop here - we - * already have a key blob and need not go to the agent to - * list things. - */ - key_in.ssh_version = 2; - key_in.comment = NULL; - key_ret = pageant_pubkey_copy(&key_in); - strbuf_free(key_in.blob); - key_in.blob = NULL; - filename_free(fn); - return key_ret; - } - } else { - if (file_errors) { - *retstr = dupprintf("unable to load key file '%s': %s", - string, key_type_to_str(keytype)); - filename_free(fn); - return NULL; - } - } - filename_free(fn); - } - - /* - * Failing that, go through the keys in the agent, and match - * against fingerprints and comments as appropriate. - */ - ctx->string = string; - ctx->match_fp = try_fp; - ctx->match_comment = try_comment; - for (unsigned i = 0; i < SSH_N_FPTYPES; i++) - ctx->match_fptypes[i] = (try_all_fptypes || i == fptype); - ctx->found = NULL; - ctx->nfound = 0; - if (pageant_enum_keys(key_find_callback, ctx, retstr) == - PAGEANT_ACTION_FAILURE) - return NULL; - - if (ctx->nfound == 0) { - *retstr = dupstr("no key matched"); - assert(!ctx->found); - return NULL; - } else if (ctx->nfound > 1) { - *retstr = dupstr("multiple keys matched"); - assert(ctx->found); - pageant_pubkey_free(ctx->found); - return NULL; - } - - assert(ctx->found); - return ctx->found; -} - -void run_client(void) -{ - const struct cmdline_key_action *act; - struct pageant_pubkey *key; - bool errors = false; - char *retstr; - LoadedFile *message = lf_new(AGENT_MAX_MSGLEN); - bool message_loaded = false, message_ok = false; - strbuf *signature = strbuf_new(); - - if (!agent_exists()) { - fprintf(stderr, "pageant: no agent running to talk to\n"); - exit(1); - } - - for (act = keyact_head; act; act = act->next) { - switch (act->action) { - case KEYACT_CLIENT_ADD: - case KEYACT_CLIENT_ADD_ENCRYPTED: - if (!unix_add_keyfile(act->filename, - act->action == KEYACT_CLIENT_ADD_ENCRYPTED)) - errors = true; - break; - case KEYACT_CLIENT_LIST: - if (pageant_enum_keys(key_list_callback, NULL, &retstr) == - PAGEANT_ACTION_FAILURE) { - fprintf(stderr, "pageant: listing keys: %s\n", retstr); - sfree(retstr); - errors = true; - } - break; - case KEYACT_CLIENT_DEL: - key = NULL; - if (!(key = find_key(act->filename, &retstr)) || - pageant_delete_key(key, &retstr) == PAGEANT_ACTION_FAILURE) { - fprintf(stderr, "pageant: deleting key '%s': %s\n", - act->filename, retstr); - sfree(retstr); - errors = true; - } - if (key) - pageant_pubkey_free(key); - break; - case KEYACT_CLIENT_REENCRYPT: - key = NULL; - if (!(key = find_key(act->filename, &retstr)) || - pageant_reencrypt_key(key, &retstr) == PAGEANT_ACTION_FAILURE) { - fprintf(stderr, "pageant: re-encrypting key '%s': %s\n", - act->filename, retstr); - sfree(retstr); - errors = true; - } - if (key) - pageant_pubkey_free(key); - break; - case KEYACT_CLIENT_PUBLIC_OPENSSH: - case KEYACT_CLIENT_PUBLIC: - key = NULL; - if (!(key = find_key(act->filename, &retstr))) { - fprintf(stderr, "pageant: finding key '%s': %s\n", - act->filename, retstr); - sfree(retstr); - errors = true; - } else { - FILE *fp = stdout; /* FIXME: add a -o option? */ - - if (key->ssh_version == 1) { - BinarySource src[1]; - RSAKey rkey; - - BinarySource_BARE_INIT(src, key->blob->u, key->blob->len); - memset(&rkey, 0, sizeof(rkey)); - rkey.comment = dupstr(key->comment); - get_rsa_ssh1_pub(src, &rkey, RSA_SSH1_EXPONENT_FIRST); - ssh1_write_pubkey(fp, &rkey); - freersakey(&rkey); - } else { - ssh2_write_pubkey(fp, key->comment, - key->blob->u, - key->blob->len, - (act->action == KEYACT_CLIENT_PUBLIC ? - SSH_KEYTYPE_SSH2_PUBLIC_RFC4716 : - SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH)); - } - pageant_pubkey_free(key); - } - break; - case KEYACT_CLIENT_DEL_ALL: - if (pageant_delete_all_keys(&retstr) == PAGEANT_ACTION_FAILURE) { - fprintf(stderr, "pageant: deleting all keys: %s\n", retstr); - sfree(retstr); - errors = true; - } - break; - case KEYACT_CLIENT_REENCRYPT_ALL: { - int status = pageant_reencrypt_all_keys(&retstr); - if (status == PAGEANT_ACTION_FAILURE) { - fprintf(stderr, "pageant: re-encrypting all keys: " - "%s\n", retstr); - sfree(retstr); - errors = true; - } else if (status == PAGEANT_ACTION_WARNING) { - fprintf(stderr, "pageant: re-encrypting all keys: " - "warning: %s\n", retstr); - sfree(retstr); - } - break; - } - case KEYACT_CLIENT_SIGN: - key = NULL; - if (!message_loaded) { - message_loaded = true; - switch(lf_load_fp(message, stdin)) { - case LF_TOO_BIG: - fprintf(stderr, "pageant: message to sign is too big\n"); - errors = true; - break; - case LF_ERROR: - fprintf(stderr, "pageant: reading message to sign: %s\n", - strerror(errno)); - errors = true; - break; - case LF_OK: - message_ok = true; - break; - } - } - if (!message_ok) - break; - strbuf_clear(signature); - if (!(key = find_key(act->filename, &retstr)) || - pageant_sign(key, ptrlen_from_lf(message), signature, - sign_flags, &retstr) == PAGEANT_ACTION_FAILURE) { - fprintf(stderr, "pageant: signing with key '%s': %s\n", - act->filename, retstr); - sfree(retstr); - errors = true; - } else { - fwrite(signature->s, 1, signature->len, stdout); - } - if (key) - pageant_pubkey_free(key); - break; - default: - unreachable("Invalid client action found"); - } - } - - lf_free(message); - strbuf_free(signature); - - if (errors) - exit(1); -} - -static const PlugVtable X11Connection_plugvt = { - .log = x11_log, - .closing = x11_closing, - .receive = x11_receive, - .sent = x11_sent, -}; - - -static bool agent_loop_pw_setup(void *vctx, pollwrapper *pw) -{ - struct uxpgnt_client *upc = (struct uxpgnt_client *)vctx; - - if (signalpipe[0] >= 0) { - pollwrap_add_fd_rwx(pw, signalpipe[0], SELECT_R); - } - - if (upc->prompt_active) - pollwrap_add_fd_rwx(pw, upc->passphrase_fd, SELECT_R); - - return true; -} - -static void agent_loop_pw_check(void *vctx, pollwrapper *pw) -{ - struct uxpgnt_client *upc = (struct uxpgnt_client *)vctx; - - if (life == LIFE_TTY) { - /* - * Every time we wake up (whether it was due to tty_timer - * elapsing or for any other reason), poll to see if we still - * have a controlling terminal. If we don't, then our - * containing tty session has ended, so it's time to clean up - * and leave. - */ - if (!have_controlling_tty()) { - time_to_die = true; - return; - } - } - - if (signalpipe[0] >= 0 && - pollwrap_check_fd_rwx(pw, signalpipe[0], SELECT_R)) { - char c[1]; - if (read(signalpipe[0], c, 1) <= 0) - /* ignore error */; - /* ignore its value; it'll be `x' */ - while (1) { - int status; - pid_t pid; - pid = waitpid(-1, &status, WNOHANG); - if (pid <= 0) - break; - if (pid == upc->termination_pid) - time_to_die = true; - } - } - - if (upc->prompt_active && - pollwrap_check_fd_rwx(pw, upc->passphrase_fd, SELECT_R)) { - char c; - int retd = read(upc->passphrase_fd, &c, 1); - - switch (upc->prompt_type) { - case RTPROMPT_GUI: - if (retd <= 0) { - close(upc->passphrase_fd); - upc->passphrase_fd = -1; - bool ok = (retd == 0); - if (!strbuf_chomp(upc->prompt_buf, '\n')) - ok = false; - passphrase_done(upc, ok); - } else { - put_byte(upc->prompt_buf, c); - } - break; - case RTPROMPT_DEBUG: - if (retd <= 0) { - passphrase_done(upc, false); - /* Now never try to read from stdin again */ - upc->prompt_type = RTPROMPT_UNAVAILABLE; - break; - } - - switch (c) { - case '\n': - case '\r': - passphrase_done(upc, true); - break; - case '\004': - passphrase_done(upc, false); - break; - case '\b': - case '\177': - strbuf_shrink_by(upc->prompt_buf, 1); - break; - case '\025': - strbuf_clear(upc->prompt_buf); - break; - default: - put_byte(upc->prompt_buf, c); - break; - } - break; - case RTPROMPT_UNAVAILABLE: - unreachable("Should never have started a prompt at all"); - } - } -} - -static bool agent_loop_continue(void *vctx, bool fd, bool cb) -{ - return !time_to_die; -} - -void run_agent(FILE *logfp, const char *symlink_path) -{ - const char *err; - char *errw; - struct pageant_listen_state *pl; - Plug *pl_plug; - Socket *sock; - bool errors = false; - Conf *conf; - const struct cmdline_key_action *act; - - pageant_init(); - - /* - * Start by loading any keys provided on the command line. - */ - for (act = keyact_head; act; act = act->next) { - assert(act->action == KEYACT_AGENT_LOAD || - act->action == KEYACT_AGENT_LOAD_ENCRYPTED); - if (!unix_add_keyfile(act->filename, - act->action == KEYACT_AGENT_LOAD_ENCRYPTED)) - errors = true; - } - if (errors) - exit(1); - - /* - * Set up a listening socket and run Pageant on it. - */ - struct uxpgnt_client upc[1]; - memset(upc, 0, sizeof(upc)); - upc->plc.vt = &uxpgnt_vtable; - upc->logfp = logfp; - upc->passphrase_fd = -1; - upc->termination_pid = -1; - upc->prompt_buf = strbuf_new_nm(); - upc->prompt_type = display ? RTPROMPT_GUI : RTPROMPT_UNAVAILABLE; - pl = pageant_listener_new(&pl_plug, &upc->plc); - sock = platform_make_agent_socket(pl_plug, PAGEANT_DIR_PREFIX, - &errw, &socketname); - if (!sock) { - fprintf(stderr, "pageant: %s\n", errw); - sfree(errw); - exit(1); - } - pageant_listener_got_socket(pl, sock); - - if (symlink_path) { - /* - * Try to make a symlink to the Unix socket, in a location of - * the user's choosing. - * - * If the link already exists, we want to replace it. There - * are two ways we could do this: either make it under another - * name and then rename it over the top, or remove the old - * link first. The former is what 'ln -sf' does, on the - * grounds that it's more atomic. But I think in this case, - * where the expected use case is that the previous agent has - * long since shut down, atomicity isn't a critical concern - * compared to not accidentally overwriting some non-symlink - * that might have important data in it! - */ - struct stat st; - if (lstat(symlink_path, &st) == 0 && S_ISLNK(st.st_mode)) - unlink(symlink_path); - if (symlink(socketname, symlink_path) < 0) - fprintf(stderr, "pageant: making symlink %s: %s\n", - symlink_path, strerror(errno)); - } - - conf = conf_new(); - conf_set_int(conf, CONF_proxy_type, PROXY_NONE); - - /* - * Lifetime preparations. - */ - if (life == LIFE_X11) { - struct X11Display *disp; - void *greeting; - int greetinglen; - Socket *s; - struct X11Connection *conn; - char *x11_setup_err; - - if (!display) { - fprintf(stderr, "pageant: no DISPLAY for -X mode\n"); - exit(1); - } - disp = x11_setup_display(display, conf, &x11_setup_err); - if (!disp) { - fprintf(stderr, "pageant: unable to connect to X server: %s\n", - x11_setup_err); - sfree(x11_setup_err); - exit(1); - } - - conn = snew(struct X11Connection); - conn->plug.vt = &X11Connection_plugvt; - s = new_connection(sk_addr_dup(disp->addr), - disp->realhost, disp->port, - false, true, false, false, &conn->plug, conf); - if ((err = sk_socket_error(s)) != NULL) { - fprintf(stderr, "pageant: unable to connect to X server: %s", err); - exit(1); - } - greeting = x11_make_greeting('B', 11, 0, disp->localauthproto, - disp->localauthdata, - disp->localauthdatalen, - NULL, 0, &greetinglen); - sk_write(s, greeting, greetinglen); - smemclr(greeting, greetinglen); - sfree(greeting); - - pageant_fork_and_print_env(false); - } else if (life == LIFE_TTY) { - schedule_timer(TTY_LIFE_POLL_INTERVAL, - tty_life_timer, &dummy_timer_ctx); - pageant_fork_and_print_env(true); - } else if (life == LIFE_PERM) { - pageant_fork_and_print_env(false); - } else if (life == LIFE_DEBUG) { - pageant_print_env(getpid()); - upc->logfp = stdout; - - struct termios orig_termios; - upc->passphrase_fd = fileno(stdin); - if (tcgetattr(upc->passphrase_fd, &orig_termios) == 0) { - struct termios new_termios = orig_termios; - new_termios.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL | ICANON); - - /* - * Try to set up a watchdog process that will restore - * termios if we crash or are killed. If successful, turn - * off echo, for runtime passphrase prompts. - */ - int pipefd[2]; - if (pipe(pipefd) == 0) { - pid_t pid = fork(); - if (pid == 0) { - tcsetattr(upc->passphrase_fd, TCSADRAIN, &new_termios); - close(pipefd[1]); - char buf[4096]; - while (read(pipefd[0], buf, sizeof(buf)) > 0); - tcsetattr(upc->passphrase_fd, TCSADRAIN, &new_termios); - _exit(0); - } else if (pid > 0) { - upc->prompt_type = RTPROMPT_DEBUG; - } - - close(pipefd[0]); - if (pid < 0) - close(pipefd[1]); - } - } - } else if (life == LIFE_EXEC) { - pid_t agentpid, pid; - - agentpid = getpid(); - setup_sigchld_handler(); - - pid = fork(); - if (pid < 0) { - perror("fork"); - exit(1); - } else if (pid == 0) { - setenv("SSH_AUTH_SOCK", socketname, true); - setenv("SSH_AGENT_PID", dupprintf("%d", (int)agentpid), true); - execvp(exec_args[0], exec_args); - perror("exec"); - _exit(127); - } else { - upc->termination_pid = pid; - } - } - - if (!upc->logfp) - upc->plc.suppress_logging = true; - - cli_main_loop(agent_loop_pw_setup, agent_loop_pw_check, - agent_loop_continue, upc); - - /* - * Before terminating, clean up our Unix socket file if possible. - */ - if (unlink(socketname) < 0) { - fprintf(stderr, "pageant: %s: %s\n", socketname, strerror(errno)); - exit(1); - } - - strbuf_free(upc->prompt_buf); - conf_free(conf); -} - -int main(int argc, char **argv) -{ - bool doing_opts = true; - keyact curr_keyact = KEYACT_AGENT_LOAD; - const char *standalone_askpass_prompt = NULL; - const char *symlink_path = NULL; - FILE *logfp = NULL; - - progname = argv[0]; - - /* - * Process the command line. - */ - while (--argc > 0) { - char *p = *++argv; - if (*p == '-' && doing_opts) { - if (!strcmp(p, "-V") || !strcmp(p, "--version")) { - version(); - } else if (!strcmp(p, "--help")) { - usage(); - exit(0); - } else if (!strcmp(p, "-v")) { - logfp = stderr; - } else if (!strcmp(p, "-a")) { - curr_keyact = KEYACT_CLIENT_ADD; - } else if (!strcmp(p, "-d")) { - curr_keyact = KEYACT_CLIENT_DEL; - } else if (!strcmp(p, "-r")) { - curr_keyact = KEYACT_CLIENT_REENCRYPT; - } else if (!strcmp(p, "-s")) { - shell_type = SHELL_SH; - } else if (!strcmp(p, "-c")) { - shell_type = SHELL_CSH; - } else if (!strcmp(p, "-D")) { - add_keyact(KEYACT_CLIENT_DEL_ALL, NULL); - } else if (!strcmp(p, "-R")) { - add_keyact(KEYACT_CLIENT_REENCRYPT_ALL, NULL); - } else if (!strcmp(p, "-l")) { - add_keyact(KEYACT_CLIENT_LIST, NULL); - } else if (!strcmp(p, "--public")) { - curr_keyact = KEYACT_CLIENT_PUBLIC; - } else if (!strcmp(p, "--public-openssh") || !strcmp(p, "-L")) { - curr_keyact = KEYACT_CLIENT_PUBLIC_OPENSSH; - } else if (!strcmp(p, "-X")) { - life = LIFE_X11; - } else if (!strcmp(p, "-T")) { - life = LIFE_TTY; - } else if (!strcmp(p, "--no-decrypt") || - !strcmp(p, "-no-decrypt") || - !strcmp(p, "--no_decrypt") || - !strcmp(p, "-no_decrypt") || - !strcmp(p, "--nodecrypt") || - !strcmp(p, "-nodecrypt") || - !strcmp(p, "--encrypted") || - !strcmp(p, "-encrypted")) { - if (curr_keyact == KEYACT_AGENT_LOAD) - curr_keyact = KEYACT_AGENT_LOAD_ENCRYPTED; - else if (curr_keyact == KEYACT_CLIENT_ADD) - curr_keyact = KEYACT_CLIENT_ADD_ENCRYPTED; - else { - fprintf(stderr, "pageant: unexpected -E while not adding " - "keys\n"); - exit(1); - } - } else if (!strcmp(p, "--debug")) { - life = LIFE_DEBUG; - } else if (!strcmp(p, "--test-sign")) { - curr_keyact = KEYACT_CLIENT_SIGN; - sign_flags = 0; - } else if (strstartswith(p, "--test-sign-with-flags=")) { - curr_keyact = KEYACT_CLIENT_SIGN; - sign_flags = atoi(p + strlen("--test-sign-with-flags=")); - } else if (!strcmp(p, "--permanent")) { - life = LIFE_PERM; - } else if (!strcmp(p, "--exec")) { - life = LIFE_EXEC; - /* Now all subsequent arguments go to the exec command. */ - if (--argc > 0) { - exec_args = ++argv; - argc = 0; /* force end of option processing */ - } else { - fprintf(stderr, "pageant: expected a command " - "after --exec\n"); - exit(1); - } - } else if (!strcmp(p, "--tty-prompt")) { - prompt_type = PROMPT_TTY; - } else if (!strcmp(p, "--gui-prompt")) { - prompt_type = PROMPT_GUI; - } else if (!strcmp(p, "--askpass")) { - if (--argc > 0) { - standalone_askpass_prompt = *++argv; - } else { - fprintf(stderr, "pageant: expected a prompt message " - "after --askpass\n"); - exit(1); - } - } else if (!strcmp(p, "--symlink")) { - if (--argc > 0) { - symlink_path = *++argv; - } else { - fprintf(stderr, "pageant: expected a pathname " - "after --symlink\n"); - exit(1); - } - } else if (!strcmp(p, "-E") || !strcmp(p, "--fptype")) { - const char *keyword; - if (--argc > 0) { - keyword = *++argv; - } else { - fprintf(stderr, "pageant: expected a type string " - "after %s\n", p); - exit(1); - } - if (!strcmp(keyword, "md5")) - key_list_fptype = SSH_FPTYPE_MD5; - else if (!strcmp(keyword, "sha256")) - key_list_fptype = SSH_FPTYPE_SHA256; - else { - fprintf(stderr, "pageant: unknown fingerprint type `%s'\n", - keyword); - exit(1); - } - } else if (!strcmp(p, "--")) { - doing_opts = false; - } else { - fprintf(stderr, "pageant: unrecognised option '%s'\n", p); - exit(1); - } - } else { - /* - * Non-option arguments (apart from those after --exec, - * which are treated specially above) are interpreted as - * the names of private key files to either add or delete - * from an agent. - */ - add_keyact(curr_keyact, p); - } - } - - if (life == LIFE_EXEC && !exec_args) { - fprintf(stderr, "pageant: expected a command with --exec\n"); - exit(1); - } - - if (!display) { - display = getenv("DISPLAY"); - if (display && !*display) - display = NULL; - } - - /* - * Deal with standalone-askpass mode. - */ - if (standalone_askpass_prompt) { - char *passphrase = askpass(standalone_askpass_prompt); - - if (!passphrase) - return 1; - - puts(passphrase); - fflush(stdout); - - smemclr(passphrase, strlen(passphrase)); - sfree(passphrase); - return 0; - } - - /* - * Block SIGPIPE, so that we'll get EPIPE individually on - * particular network connections that go wrong. - */ - putty_signal(SIGPIPE, SIG_IGN); - - sk_init(); - uxsel_init(); - - /* - * Now distinguish our two main running modes. Either we're - * actually starting up an agent, in which case we should have a - * lifetime mode, and no key actions of KEYACT_CLIENT_* type; or - * else we're contacting an existing agent to add or remove keys, - * in which case we should have no lifetime mode, and no key - * actions of KEYACT_AGENT_* type. - */ - { - bool has_agent_actions = false; - bool has_client_actions = false; - bool has_lifetime = false; - const struct cmdline_key_action *act; - - for (act = keyact_head; act; act = act->next) { - if (is_agent_action(act->action)) - has_agent_actions = true; - else - has_client_actions = true; - } - if (life != LIFE_UNSPEC) - has_lifetime = true; - - if (has_lifetime && has_client_actions) { - fprintf(stderr, "pageant: client key actions (-a, -d, -D, -r, -R, " - "-l, -L) do not go with an agent lifetime option\n"); - exit(1); - } - if (!has_lifetime && has_agent_actions) { - fprintf(stderr, "pageant: expected an agent lifetime option with" - " bare key file arguments\n"); - exit(1); - } - if (!has_lifetime && !has_client_actions) { - fprintf(stderr, "pageant: expected an agent lifetime option" - " or a client key action\n"); - exit(1); - } - - if (has_lifetime) { - run_agent(logfp, symlink_path); - } else if (has_client_actions) { - run_client(); - } - } - - return 0; -} diff --git a/unix/uxplink.c b/unix/uxplink.c deleted file mode 100644 index 3e2a9b6b..00000000 --- a/unix/uxplink.c +++ /dev/null @@ -1,964 +0,0 @@ -/* - * PLink - a command-line (stdin/stdout) variant of PuTTY. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "putty.h" -#include "ssh.h" -#include "storage.h" -#include "tree234.h" - -#define MAX_STDIN_BACKLOG 4096 - -static LogContext *logctx; - -static struct termios orig_termios; - -void cmdline_error(const char *fmt, ...) -{ - va_list ap; - va_start(ap, fmt); - console_print_error_msg_fmt_v("plink", fmt, ap); - va_end(ap); - exit(1); -} - -static bool local_tty = false; /* do we have a local tty? */ - -static Backend *backend; -static Conf *conf; - -/* - * Default settings that are specific to Unix plink. - */ -char *platform_default_s(const char *name) -{ - if (!strcmp(name, "TermType")) - return dupstr(getenv("TERM")); - if (!strcmp(name, "SerialLine")) - return dupstr("/dev/ttyS0"); - return NULL; -} - -bool platform_default_b(const char *name, bool def) -{ - return def; -} - -int platform_default_i(const char *name, int def) -{ - return def; -} - -FontSpec *platform_default_fontspec(const char *name) -{ - return fontspec_new(""); -} - -Filename *platform_default_filename(const char *name) -{ - if (!strcmp(name, "LogFileName")) - return filename_from_str("putty.log"); - else - return filename_from_str(""); -} - -char *x_get_default(const char *key) -{ - return NULL; /* this is a stub */ -} -static void plink_echoedit_update(Seat *seat, bool echo, bool 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 the echoedit_update() method, 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 dupstr(""); -#endif - return dupprintf("^<%d>", c); -} - -static char *plink_get_ttymode(Seat *seat, 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) { \ - bool 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 -#if defined(IUTF8) - GET_BOOL("IUTF8", IUTF8, c_iflag, ); -#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); -} - -static bufchain stdout_data, stderr_data; -static bufchain_sink stdout_bcs, stderr_bcs; -static StripCtrlChars *stdout_scc, *stderr_scc; -static BinarySink *stdout_bs, *stderr_bs; - -static enum { EOF_NO, EOF_PENDING, EOF_SENT } outgoingeof; - -size_t try_output(bool is_stderr) -{ - bufchain *chain = (is_stderr ? &stderr_data : &stdout_data); - int fd = (is_stderr ? STDERR_FILENO : STDOUT_FILENO); - ssize_t ret; - - if (bufchain_size(chain) > 0) { - bool prev_nonblock = nonblock(fd); - ptrlen senddata; - do { - senddata = bufchain_prefix(chain); - ret = write(fd, senddata.ptr, senddata.len); - if (ret > 0) - bufchain_consume(chain, ret); - } while (ret == senddata.len && bufchain_size(chain) != 0); - if (!prev_nonblock) - no_nonblock(fd); - if (ret < 0 && errno != EAGAIN) { - perror(is_stderr ? "stderr: write" : "stdout: write"); - exit(1); - } - } - if (outgoingeof == EOF_PENDING && bufchain_size(&stdout_data) == 0) { - close(STDOUT_FILENO); - outgoingeof = EOF_SENT; - } - return bufchain_size(&stdout_data) + bufchain_size(&stderr_data); -} - -static size_t plink_output( - Seat *seat, bool is_stderr, const void *data, size_t len) -{ - assert(is_stderr || outgoingeof == EOF_NO); - - BinarySink *bs = is_stderr ? stderr_bs : stdout_bs; - put_data(bs, data, len); - - return try_output(is_stderr); -} - -static bool plink_eof(Seat *seat) -{ - assert(outgoingeof == EOF_NO); - outgoingeof = EOF_PENDING; - try_output(false); - return false; /* do not respond to incoming EOF with outgoing */ -} - -static int plink_get_userpass_input(Seat *seat, prompts_t *p, bufchain *input) -{ - int ret; - ret = cmdline_get_passwd_input(p); - if (ret == -1) - ret = console_get_userpass_input(p); - return ret; -} - -static bool plink_seat_interactive(Seat *seat) -{ - return (!*conf_get_str(conf, CONF_remote_cmd) && - !*conf_get_str(conf, CONF_remote_cmd2) && - !*conf_get_str(conf, CONF_ssh_nc_host)); -} - -static const SeatVtable plink_seat_vt = { - .output = plink_output, - .eof = plink_eof, - .get_userpass_input = plink_get_userpass_input, - .notify_remote_exit = nullseat_notify_remote_exit, - .connection_fatal = console_connection_fatal, - .update_specials_menu = nullseat_update_specials_menu, - .get_ttymode = plink_get_ttymode, - .set_busy_status = nullseat_set_busy_status, - .verify_ssh_host_key = console_verify_ssh_host_key, - .confirm_weak_crypto_primitive = console_confirm_weak_crypto_primitive, - .confirm_weak_cached_hostkey = console_confirm_weak_cached_hostkey, - .is_utf8 = nullseat_is_never_utf8, - .echoedit_update = plink_echoedit_update, - .get_x_display = nullseat_get_x_display, - .get_windowid = nullseat_get_windowid, - .get_window_pixel_size = nullseat_get_window_pixel_size, - .stripctrl_new = console_stripctrl_new, - .set_trust_status = console_set_trust_status, - .verbose = cmdline_seat_verbose, - .interactive = plink_seat_interactive, - .get_cursor_position = nullseat_get_cursor_position, -}; -static Seat plink_seat[1] = {{ &plink_seat_vt }}; - -/* - * 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; - backend_send(backend, p, q - p); - p = q; - } - break; - case FF: - if (*p == '\xff') { - backend_send(backend, p, 1); - p++; - state = NORMAL; - } else if (*p == '\0') { - p++; - state = FF00; - } else abort(); - break; - case FF00: - if (*p == '\0') { - backend_special(backend, SS_BRK, 0); - } 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; - backend_send(backend, p, 1); - } - } else { - /* INPCK not set. Assume we got a parity error. */ - backend_send(backend, p, 1); - } - } - p++; - state = NORMAL; - } - } -} - -static int signalpipe[2]; - -void sigwinch(int signum) -{ - if (write(signalpipe[1], "x", 1) <= 0) - /* not much we can do about it */; -} - -/* - * Short description of parameters. - */ -static void usage(void) -{ - printf("Plink: 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(" -ssh-connection\n"); - printf(" force use of the bare ssh-connection 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(" -proxycmd command\n"); - printf(" use 'command' as local proxy\n"); - printf(" -sercfg configuration-string (e.g. 19200,8,n,1,X)\n"); - printf(" Specify the serial configuration (serial only)\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 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 user authentication\n"); - printf(" -noagent disable use of Pageant\n"); - printf(" -agent enable use of Pageant\n"); - printf(" -noshare disable use of connection sharing\n"); - printf(" -share enable use of connection sharing\n"); - printf(" -hostkey keyid\n"); - printf(" manually specify a host key (may be repeated)\n"); - printf(" -sanitise-stderr, -sanitise-stdout, " - "-no-sanitise-stderr, -no-sanitise-stdout\n"); - printf(" do/don't strip control chars from standard " - "output/error\n"); - printf(" -no-antispoof omit anti-spoofing prompt after " - "authentication\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(" -sshlog file\n"); - printf(" -sshrawlog file\n"); - printf(" log protocol details to a file\n"); - printf(" -logoverwrite\n"); - printf(" -logappend\n"); - printf(" control what happens when a log file already exists\n"); - printf(" -shareexists\n"); - printf(" test whether a connection-sharing upstream exists\n"); - exit(1); -} - -static void version(void) -{ - char *buildinfo_text = buildinfo("\n"); - printf("plink: %s\n%s\n", ver, buildinfo_text); - sfree(buildinfo_text); - exit(0); -} - -void frontend_net_error_pending(void) {} - -const bool share_can_be_downstream = true; -const bool share_can_be_upstream = true; - -const bool buildinfo_gtk_relevant = false; - -const unsigned cmdline_tooltype = - TOOLTYPE_HOST_ARG | - TOOLTYPE_HOST_ARG_CAN_BE_SESSION | - TOOLTYPE_HOST_ARG_PROTOCOL_PREFIX | - TOOLTYPE_HOST_ARG_FROM_LAUNCHABLE_LOAD; - -static bool seen_stdin_eof = false; - -static bool plink_pw_setup(void *vctx, pollwrapper *pw) -{ - pollwrap_add_fd_rwx(pw, signalpipe[0], SELECT_R); - - if (!seen_stdin_eof && - backend_connected(backend) && - backend_sendok(backend) && - backend_sendbuffer(backend) < MAX_STDIN_BACKLOG) { - /* If we're OK to send, then try to read from stdin. */ - pollwrap_add_fd_rwx(pw, STDIN_FILENO, SELECT_R); - } - - if (bufchain_size(&stdout_data) > 0) { - /* If we have data for stdout, try to write to stdout. */ - pollwrap_add_fd_rwx(pw, STDOUT_FILENO, SELECT_W); - } - - if (bufchain_size(&stderr_data) > 0) { - /* If we have data for stderr, try to write to stderr. */ - pollwrap_add_fd_rwx(pw, STDERR_FILENO, SELECT_W); - } - - return true; -} - -static void plink_pw_check(void *vctx, pollwrapper *pw) -{ - if (pollwrap_check_fd_rwx(pw, signalpipe[0], SELECT_R)) { - char c[1]; - struct winsize size; - if (read(signalpipe[0], c, 1) <= 0) - /* ignore error */; - /* ignore its value; it'll be `x' */ - if (ioctl(STDIN_FILENO, TIOCGWINSZ, (void *)&size) >= 0) - backend_size(backend, size.ws_col, size.ws_row); - } - - if (pollwrap_check_fd_rwx(pw, STDIN_FILENO, SELECT_R)) { - char buf[4096]; - int ret; - - if (backend_connected(backend)) { - ret = read(STDIN_FILENO, buf, sizeof(buf)); - noise_ultralight(NOISE_SOURCE_IOLEN, ret); - if (ret < 0) { - perror("stdin: read"); - exit(1); - } else if (ret == 0) { - backend_special(backend, SS_EOF, 0); - seen_stdin_eof = true; - } else { - if (local_tty) - from_tty(buf, ret); - else - backend_send(backend, buf, ret); - } - } - } - - if (pollwrap_check_fd_rwx(pw, STDOUT_FILENO, SELECT_W)) { - backend_unthrottle(backend, try_output(false)); - } - - if (pollwrap_check_fd_rwx(pw, STDERR_FILENO, SELECT_W)) { - backend_unthrottle(backend, try_output(true)); - } -} - -static bool plink_continue(void *vctx, bool found_any_fd, - bool ran_any_callback) -{ - if (!backend_connected(backend) && - bufchain_size(&stdout_data) == 0 && bufchain_size(&stderr_data) == 0) - return false; /* terminate main loop */ - return true; -} - -int main(int argc, char **argv) -{ - int exitcode; - bool errors; - enum TriState sanitise_stdout = AUTO, sanitise_stderr = AUTO; - bool use_subsystem = false; - bool just_test_share_exists = false; - struct winsize size; - const struct BackendVtable *backvt; - - /* - * Initialise port and protocol to sensible defaults. (These - * will be overridden by more or less anything.) - */ - settings_set_default_protocol(PROT_SSH); - settings_set_default_port(22); - - bufchain_init(&stdout_data); - bufchain_init(&stderr_data); - bufchain_sink_init(&stdout_bcs, &stdout_data); - bufchain_sink_init(&stderr_bcs, &stderr_data); - stdout_bs = BinarySink_UPCAST(&stdout_bcs); - stderr_bs = BinarySink_UPCAST(&stderr_bcs); - outgoingeof = EOF_NO; - - stderr_tty_init(); - /* - * Process the command line. - */ - conf = conf_new(); - do_defaults(NULL, conf); - settings_set_default_protocol(conf_get_int(conf, CONF_protocol)); - settings_set_default_port(conf_get_int(conf, CONF_port)); - errors = false; - { - /* - * Override the default protocol if PLINK_PROTOCOL is set. - */ - char *p = getenv("PLINK_PROTOCOL"); - if (p) { - const struct BackendVtable *vt = backend_vt_from_name(p); - if (vt) { - settings_set_default_protocol(vt->protocol); - settings_set_default_port(vt->default_port); - conf_set_int(conf, CONF_protocol, vt->protocol); - conf_set_int(conf, CONF_port, vt->default_port); - } - } - } - while (--argc) { - char *p = *++argv; - int ret = cmdline_process_param(p, (argc > 1 ? argv[1] : NULL), - 1, conf); - if (ret == -2) { - fprintf(stderr, - "plink: option \"%s\" requires an argument\n", p); - errors = true; - } else if (ret == 2) { - --argc, ++argv; - } else if (ret == 1) { - continue; - } else if (!strcmp(p, "-batch")) { - console_batch_mode = true; - } else if (!strcmp(p, "-s")) { - /* Save status to write to conf later. */ - use_subsystem = true; - } else if (!strcmp(p, "-V") || !strcmp(p, "--version")) { - version(); - } else if (!strcmp(p, "--help")) { - usage(); - exit(0); - } else if (!strcmp(p, "-pgpfp")) { - pgp_fingerprints(); - exit(1); - } else if (!strcmp(p, "-o")) { - if (argc <= 1) { - fprintf(stderr, - "plink: option \"-o\" requires an argument\n"); - errors = true; - } else { - --argc; - /* Explicitly pass "plink" in place of appname for - * error reporting purposes. appname will have been - * set by be_foo.c to something more generic, probably - * "PuTTY". */ - provide_xrm_string(*++argv, "plink"); - } - } else if (!strcmp(p, "-shareexists")) { - just_test_share_exists = true; - } else if (!strcmp(p, "-fuzznet")) { - conf_set_int(conf, CONF_proxy_type, PROXY_FUZZ); - conf_set_str(conf, CONF_proxy_telnet_command, "%host"); - } else if (!strcmp(p, "-sanitise-stdout") || - !strcmp(p, "-sanitize-stdout")) { - sanitise_stdout = FORCE_ON; - } else if (!strcmp(p, "-no-sanitise-stdout") || - !strcmp(p, "-no-sanitize-stdout")) { - sanitise_stdout = FORCE_OFF; - } else if (!strcmp(p, "-sanitise-stderr") || - !strcmp(p, "-sanitize-stderr")) { - sanitise_stderr = FORCE_ON; - } else if (!strcmp(p, "-no-sanitise-stderr") || - !strcmp(p, "-no-sanitize-stderr")) { - sanitise_stderr = FORCE_OFF; - } else if (!strcmp(p, "-no-antispoof")) { - console_antispoof_prompt = false; - } else if (*p != '-') { - strbuf *cmdbuf = strbuf_new(); - - while (argc > 0) { - if (cmdbuf->len > 0) - put_byte(cmdbuf, ' '); /* add space separator */ - put_datapl(cmdbuf, ptrlen_from_asciz(p)); - if (--argc > 0) - p = *++argv; - } - - conf_set_str(conf, CONF_remote_cmd, cmdbuf->s); - conf_set_str(conf, CONF_remote_cmd2, ""); - conf_set_bool(conf, CONF_nopty, true); /* command => no tty */ - - strbuf_free(cmdbuf); - break; /* done with cmdline */ - } else { - fprintf(stderr, "plink: unknown option \"%s\"\n", p); - errors = true; - } - } - - if (errors) - return 1; - - if (!cmdline_host_ok(conf)) { - usage(); - } - - prepare_session(conf); - - /* - * Perform command-line overrides on session configuration. - */ - cmdline_run_saved(conf); - - /* - * If we have no better ideas for the remote username, use the local - * one, as 'ssh' does. - */ - if (conf_get_str(conf, CONF_username)[0] == '\0') { - char *user = get_username(); - if (user) { - conf_set_str(conf, CONF_username, user); - sfree(user); - } - } - - /* - * Apply subsystem status. - */ - if (use_subsystem) - conf_set_bool(conf, CONF_ssh_subsys, true); - - /* - * Select protocol. This is farmed out into a table in a - * separate file to enable an ssh-free variant. - */ - backvt = backend_vt_from_proto(conf_get_int(conf, CONF_protocol)); - if (!backvt) { - fprintf(stderr, - "Internal fault: Unsupported protocol found\n"); - return 1; - } - - if (backvt->flags & BACKEND_NEEDS_TERMINAL) { - fprintf(stderr, - "Plink doesn't support %s, which needs terminal emulation\n", - backvt->displayname); - return 1; - } - - /* - * Block SIGPIPE, so that we'll get EPIPE individually on - * particular network connections that go wrong. - */ - putty_signal(SIGPIPE, SIG_IGN); - - /* - * Set up the pipe we'll use to tell us about SIGWINCH. - */ - if (pipe(signalpipe) < 0) { - perror("pipe"); - exit(1); - } - /* We don't want the signal handler to block if the pipe's full. */ - nonblock(signalpipe[0]); - nonblock(signalpipe[1]); - cloexec(signalpipe[0]); - cloexec(signalpipe[1]); - putty_signal(SIGWINCH, sigwinch); - - /* - * Now that we've got the SIGWINCH handler installed, try to find - * out the initial terminal size. - */ - if (ioctl(STDIN_FILENO, TIOCGWINSZ, &size) >= 0) { - conf_set_int(conf, CONF_width, size.ws_col); - conf_set_int(conf, CONF_height, size.ws_row); - } - - /* - * Decide whether to sanitise control sequences out of standard - * output and standard error. - * - * If we weren't given a command-line override, we do this if (a) - * the fd in question is pointing at a terminal, and (b) we aren't - * trying to allocate a terminal as part of the session. - * - * (Rationale: the risk of control sequences is that they cause - * confusion when sent to a local terminal, so if there isn't one, - * no problem. Also, if we allocate a remote terminal, then we - * sent a terminal type, i.e. we told it what kind of escape - * sequences we _like_, i.e. we were expecting to receive some.) - */ - if (sanitise_stdout == FORCE_ON || - (sanitise_stdout == AUTO && isatty(STDOUT_FILENO) && - conf_get_bool(conf, CONF_nopty))) { - stdout_scc = stripctrl_new(stdout_bs, true, L'\0'); - stdout_bs = BinarySink_UPCAST(stdout_scc); - } - if (sanitise_stderr == FORCE_ON || - (sanitise_stderr == AUTO && isatty(STDERR_FILENO) && - conf_get_bool(conf, CONF_nopty))) { - stderr_scc = stripctrl_new(stderr_bs, true, L'\0'); - stderr_bs = BinarySink_UPCAST(stderr_scc); - } - - sk_init(); - uxsel_init(); - - /* - * 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 (conf_get_int(conf, CONF_protocol) == PROT_SSH && - !conf_get_bool(conf, CONF_x11_forward) && - !conf_get_bool(conf, CONF_agentfwd) && - !conf_get_str_nthstrkey(conf, CONF_portfwd, 0)) - conf_set_bool(conf, CONF_ssh_simple, true); - - if (just_test_share_exists) { - if (!backvt->test_for_upstream) { - fprintf(stderr, "Connection sharing not supported for this " - "connection type (%s)'\n", backvt->displayname); - return 1; - } - if (backvt->test_for_upstream(conf_get_str(conf, CONF_host), - conf_get_int(conf, CONF_port), conf)) - return 0; - else - return 1; - } - - /* - * Start up the connection. - */ - logctx = log_init(console_cli_logpolicy, conf); - { - char *error, *realhost; - /* nodelay is only useful if stdin is a terminal device */ - bool nodelay = conf_get_bool(conf, CONF_tcp_nodelay) && isatty(0); - - /* This is a good place for a fuzzer to fork us. */ -#ifdef __AFL_HAVE_MANUAL_CONTROL - __AFL_INIT(); -#endif - - error = backend_init(backvt, plink_seat, &backend, logctx, conf, - conf_get_str(conf, CONF_host), - conf_get_int(conf, CONF_port), - &realhost, nodelay, - conf_get_bool(conf, CONF_tcp_keepalives)); - if (error) { - fprintf(stderr, "Unable to open connection:\n%s\n", error); - sfree(error); - return 1; - } - ldisc_create(conf, NULL, backend, plink_seat); - sfree(realhost); - } - - /* - * 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); - seat_echoedit_update(plink_seat, 1, 1); - - cli_main_loop(plink_pw_setup, plink_pw_check, plink_continue, NULL); - - exitcode = backend_exitcode(backend); - 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 */ -} diff --git a/unix/uxprint.c b/unix/uxprint.c deleted file mode 100644 index 3de6d21b..00000000 --- a/unix/uxprint.c +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Printing interface for PuTTY. - */ - -#include -#include -#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, const void *data, size_t 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) { } diff --git a/unix/uxproxy.c b/unix/uxproxy.c deleted file mode 100644 index 0a637bd9..00000000 --- a/unix/uxproxy.c +++ /dev/null @@ -1,105 +0,0 @@ -/* - * uxproxy.c: Unix implementation of platform_new_connection(), - * supporting an OpenSSH-like proxy command. - */ - -#include -#include -#include -#include -#include - -#include "tree234.h" -#include "putty.h" -#include "network.h" -#include "proxy.h" - -Socket *platform_new_connection(SockAddr *addr, const char *hostname, - int port, bool privport, - bool oobinline, bool nodelay, bool keepalive, - Plug *plug, Conf *conf) -{ - char *cmd; - - int to_cmd_pipe[2], from_cmd_pipe[2], cmd_err_pipe[2], pid, proxytype; - int infd, outfd, inerrfd; - - proxytype = conf_get_int(conf, CONF_proxy_type); - if (proxytype != PROXY_CMD && proxytype != PROXY_FUZZ) - return NULL; - - if (proxytype == PROXY_CMD) { - cmd = format_telnet_command(addr, port, conf); - - { - char *logmsg = dupprintf("Starting local proxy command: %s", cmd); - plug_log(plug, PLUGLOG_PROXY_MSG, NULL, 0, logmsg, 0); - sfree(logmsg); - } - - /* - * Create the pipes to the proxy command, and spawn the proxy - * command process. - */ - if (pipe(to_cmd_pipe) < 0 || - pipe(from_cmd_pipe) < 0 || - pipe(cmd_err_pipe) < 0) { - sfree(cmd); - return new_error_socket_fmt(plug, "pipe: %s", strerror(errno)); - } - cloexec(to_cmd_pipe[1]); - cloexec(from_cmd_pipe[0]); - cloexec(cmd_err_pipe[0]); - - pid = fork(); - 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]); - dup2(cmd_err_pipe[1], 2); - noncloexec(0); - noncloexec(1); - execl("/bin/sh", "sh", "-c", cmd, (void *)NULL); - _exit(255); - } - - sfree(cmd); - - if (pid < 0) - return new_error_socket_fmt(plug, "fork: %s", strerror(errno)); - - close(to_cmd_pipe[0]); - close(from_cmd_pipe[1]); - close(cmd_err_pipe[1]); - - outfd = to_cmd_pipe[1]; - infd = from_cmd_pipe[0]; - inerrfd = cmd_err_pipe[0]; - } else { - cmd = format_telnet_command(addr, port, conf); - outfd = open("/dev/null", O_WRONLY); - if (outfd == -1) { - sfree(cmd); - return new_error_socket_fmt( - plug, "/dev/null: %s", strerror(errno)); - } - infd = open(cmd, O_RDONLY); - if (infd == -1) { - Socket *toret = new_error_socket_fmt( - plug, "%s: %s", cmd, strerror(errno)); - sfree(cmd); - close(outfd); - return toret; - } - sfree(cmd); - inerrfd = -1; - } - - /* We are responsible for this and don't need it any more */ - sk_addr_free(addr); - - return make_fd_socket(infd, outfd, inerrfd, plug); -} diff --git a/unix/uxpsusan.c b/unix/uxpsusan.c deleted file mode 100644 index a9312c40..00000000 --- a/unix/uxpsusan.c +++ /dev/null @@ -1,422 +0,0 @@ -/* - * 'psusan': Pseudo Ssh for Untappable, Separately Authenticated Networks - * - * This is a standalone application that speaks on its standard I/O - * (or a listening Unix-domain socket) the server end of the bare - * ssh-connection protocol used by PuTTY's connection sharing. - * - * The idea of this tool is that you can use it to communicate across - * any 8-bit-clean data channel between two inconveniently separated - * domains, provided the channel is already (as the name suggests) - * adequately secured against eavesdropping and modification and - * already authenticated as the right user. - * - * If you're sitting at one end of such a channel and want to type - * commands into the other end, the most obvious thing to do is to run - * a terminal session directly over it. But if you run psusan at one - * end, and a PuTTY (or compatible) client at the other end, then you - * not only get a single terminal session: you get all the other SSH - * amenities, like the ability to spawn extra terminal sessions, - * forward ports or X11 connections, even forward an SSH agent. - * - * There are a surprising number of channels of that kind; see the man - * page for some examples. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "putty.h" -#include "mpint.h" -#include "ssh.h" -#include "ssh/server.h" - -const char *const appname = "psusan"; - -void modalfatalbox(const 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 nonfatal(const char *p, ...) -{ - va_list ap; - fprintf(stderr, "ERROR: "); - va_start(ap, p); - vfprintf(stderr, p, ap); - va_end(ap); - fputc('\n', stderr); -} - -char *platform_default_s(const char *name) -{ - return NULL; -} - -bool platform_default_b(const char *name, bool def) -{ - return def; -} - -int platform_default_i(const char *name, int def) -{ - return def; -} - -FontSpec *platform_default_fontspec(const char *name) -{ - return fontspec_new(""); -} - -Filename *platform_default_filename(const char *name) -{ - return filename_from_str(""); -} - -char *x_get_default(const char *key) -{ - return NULL; /* this is a stub */ -} - -void old_keyfile_warning(void) { } - -void timer_change_notify(unsigned long next) -{ -} - -char *platform_get_x_display(void) { return NULL; } - -void make_unix_sftp_filehandle_key(void *vdata, size_t size) -{ - /* psusan runs without a random number generator, so we can't make - * this up by random_read. Fortunately, psusan is also - * non-adversarial, so it's safe to generate this trivially. */ - unsigned char *data = (unsigned char *)vdata; - for (size_t i = 0; i < size; i++) - data[i] = (unsigned)rand() / ((unsigned)RAND_MAX / 256); -} - -static bool verbose; - -struct server_instance { - unsigned id; - LogPolicy logpolicy; -}; - -static void log_to_stderr(unsigned id, const char *msg) -{ - if (!verbose) - return; - if (id != (unsigned)-1) - fprintf(stderr, "#%u: ", id); - fputs(msg, stderr); - fputc('\n', stderr); - fflush(stderr); -} - -static void server_eventlog(LogPolicy *lp, const char *event) -{ - struct server_instance *inst = container_of( - lp, struct server_instance, logpolicy); - if (verbose) - log_to_stderr(inst->id, event); -} - -static void server_logging_error(LogPolicy *lp, const char *event) -{ - struct server_instance *inst = container_of( - lp, struct server_instance, logpolicy); - log_to_stderr(inst->id, event); /* unconditional */ -} - -static int server_askappend( - LogPolicy *lp, Filename *filename, - void (*callback)(void *ctx, int result), void *ctx) -{ - return 2; /* always overwrite (FIXME: could make this a cmdline option) */ -} - -static const LogPolicyVtable server_logpolicy_vt = { - .eventlog = server_eventlog, - .askappend = server_askappend, - .logging_error = server_logging_error, - .verbose = null_lp_verbose_no, -}; - -static void show_help(FILE *fp) -{ - fputs("usage: psusan [options]\n" - "options: --listen SOCKETPATH listen for connections on a Unix-domain socket\n" - " --listen-once (with --listen) stop after one connection\n" - " --verbose print log messages to standard error\n" - " --sessiondir DIR cwd for session subprocess (default $HOME)\n" - " --sshlog FILE write ssh-connection packet log to FILE\n" - " --sshrawlog FILE write packets and raw data log to FILE\n" - "also: psusan --help show this text\n" - " psusan --version show version information\n", fp); -} - -static void show_version_and_exit(void) -{ - char *buildinfo_text = buildinfo("\n"); - printf("%s: %s\n%s\n", appname, ver, buildinfo_text); - sfree(buildinfo_text); - exit(0); -} - -const bool buildinfo_gtk_relevant = false; - -static bool listening = false, listen_once = false; -static bool finished = false; -void server_instance_terminated(LogPolicy *lp) -{ - struct server_instance *inst = container_of( - lp, struct server_instance, logpolicy); - - if (listening && !listen_once) { - log_to_stderr(inst->id, "connection terminated"); - } else { - finished = true; - } - - sfree(inst); -} - -bool psusan_continue(void *ctx, bool fd, bool cb) -{ - return !finished; -} - -static bool longoptarg(const char *arg, const char *expected, - const char **val, int *argcp, char ***argvp) -{ - int len = strlen(expected); - if (memcmp(arg, expected, len)) - return false; - if (arg[len] == '=') { - *val = arg + len + 1; - return true; - } else if (arg[len] == '\0') { - if (--*argcp > 0) { - *val = *++*argvp; - return true; - } else { - fprintf(stderr, "%s: option %s expects an argument\n", - appname, expected); - exit(1); - } - } - return false; -} - -static bool longoptnoarg(const char *arg, const char *expected) -{ - int len = strlen(expected); - if (memcmp(arg, expected, len)) - return false; - if (arg[len] == '=') { - fprintf(stderr, "%s: option %s expects no argument\n", - appname, expected); - exit(1); - } else if (arg[len] == '\0') { - return true; - } - return false; -} - -struct server_config { - Conf *conf; - const SshServerConfig *ssc; - - unsigned next_id; - - Socket *listening_socket; - Plug listening_plug; -}; - -static Plug *server_conn_plug( - struct server_config *cfg, struct server_instance **inst_out) -{ - struct server_instance *inst = snew(struct server_instance); - - memset(inst, 0, sizeof(*inst)); - - inst->id = cfg->next_id++; - inst->logpolicy.vt = &server_logpolicy_vt; - - if (inst_out) - *inst_out = inst; - - return ssh_server_plug( - cfg->conf, cfg->ssc, NULL, 0, NULL, NULL, - &inst->logpolicy, &unix_live_sftpserver_vt); -} - -static void server_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, - const char *error_msg, int error_code) -{ - log_to_stderr(-1, error_msg); -} - -static void server_closing(Plug *plug, const char *error_msg, int error_code, - bool calling_back) -{ - log_to_stderr(-1, error_msg); -} - -static int server_accepting(Plug *p, accept_fn_t constructor, accept_ctx_t ctx) -{ - struct server_config *cfg = container_of( - p, struct server_config, listening_plug); - Socket *s; - const char *err; - - struct server_instance *inst; - - if (listen_once) { - if (!cfg->listening_socket) /* in case of rapid double-accept */ - return 1; - sk_close(cfg->listening_socket); - cfg->listening_socket = NULL; - } - - Plug *plug = server_conn_plug(cfg, &inst); - s = constructor(ctx, plug); - if ((err = sk_socket_error(s)) != NULL) - return 1; - - SocketPeerInfo *pi = sk_peer_info(s); - - char *msg = dupprintf("new connection from %s", pi->log_text); - log_to_stderr(inst->id, msg); - sfree(msg); - sk_free_peer_info(pi); - - sk_set_frozen(s, false); - ssh_server_start(plug, s); - return 0; -} - -static const PlugVtable server_plugvt = { - .log = server_log, - .closing = server_closing, - .accepting = server_accepting, -}; - -unsigned auth_methods(AuthPolicy *ap) -{ return 0; } -bool auth_none(AuthPolicy *ap, ptrlen username) -{ return false; } -int auth_password(AuthPolicy *ap, ptrlen username, ptrlen password, - ptrlen *new_password_opt) -{ return 0; } -bool auth_publickey(AuthPolicy *ap, ptrlen username, ptrlen public_blob) -{ return false; } -RSAKey *auth_publickey_ssh1( - AuthPolicy *ap, ptrlen username, mp_int *rsa_modulus) -{ return NULL; } -AuthKbdInt *auth_kbdint_prompts(AuthPolicy *ap, ptrlen username) -{ return NULL; } -int auth_kbdint_responses(AuthPolicy *ap, const ptrlen *responses) -{ return -1; } -char *auth_ssh1int_challenge(AuthPolicy *ap, unsigned method, ptrlen username) -{ return NULL; } -bool auth_ssh1int_response(AuthPolicy *ap, ptrlen response) -{ return false; } -bool auth_successful(AuthPolicy *ap, ptrlen username, unsigned method) -{ return false; } - -int main(int argc, char **argv) -{ - const char *listen_socket = NULL; - - SshServerConfig ssc; - - Conf *conf = make_ssh_server_conf(); - - memset(&ssc, 0, sizeof(ssc)); - - ssc.application_name = "PSUSAN"; - ssc.session_starting_dir = getenv("HOME"); - ssc.bare_connection = true; - - while (--argc > 0) { - const char *arg = *++argv; - const char *val; - - if (longoptnoarg(arg, "--help")) { - show_help(stdout); - exit(0); - } else if (longoptnoarg(arg, "--version")) { - show_version_and_exit(); - } else if (longoptnoarg(arg, "--verbose") || !strcmp(arg, "-v")) { - verbose = true; - } else if (longoptarg(arg, "--sessiondir", &val, &argc, &argv)) { - ssc.session_starting_dir = val; - } else if (longoptarg(arg, "--sshlog", &val, &argc, &argv) || - longoptarg(arg, "-sshlog", &val, &argc, &argv)) { - Filename *logfile = filename_from_str(val); - conf_set_filename(conf, CONF_logfilename, logfile); - filename_free(logfile); - conf_set_int(conf, CONF_logtype, LGTYP_PACKETS); - conf_set_int(conf, CONF_logxfovr, LGXF_OVR); - } else if (longoptarg(arg, "--sshrawlog", &val, &argc, &argv) || - longoptarg(arg, "-sshrawlog", &val, &argc, &argv)) { - Filename *logfile = filename_from_str(val); - conf_set_filename(conf, CONF_logfilename, logfile); - filename_free(logfile); - conf_set_int(conf, CONF_logtype, LGTYP_SSHRAW); - conf_set_int(conf, CONF_logxfovr, LGXF_OVR); - } else if (longoptarg(arg, "--listen", &val, &argc, &argv)) { - listen_socket = val; - } else if (!strcmp(arg, "--listen-once")) { - listen_once = true; - } else { - fprintf(stderr, "%s: unrecognised option '%s'\n", appname, arg); - exit(1); - } - } - - sk_init(); - uxsel_init(); - - struct server_config scfg; - scfg.conf = conf; - scfg.ssc = &ssc; - scfg.next_id = 0; - - if (listen_socket) { - listening = true; - scfg.listening_plug.vt = &server_plugvt; - SockAddr *addr = unix_sock_addr(listen_socket); - scfg.listening_socket = new_unix_listener(addr, &scfg.listening_plug); - char *msg = dupprintf("listening on Unix socket %s", listen_socket); - log_to_stderr(-1, msg); - sfree(msg); - } else { - struct server_instance *inst; - Plug *plug = server_conn_plug(&scfg, &inst); - ssh_server_start(plug, make_fd_socket(0, 1, -1, plug)); - log_to_stderr(inst->id, "running directly on stdio"); - } - - cli_main_loop(cliloop_no_pw_setup, cliloop_no_pw_check, - psusan_continue, NULL); - - return 0; -} diff --git a/unix/uxpterm.c b/unix/uxpterm.c deleted file mode 100644 index b18e3442..00000000 --- a/unix/uxpterm.c +++ /dev/null @@ -1,50 +0,0 @@ -/* - * pterm main program. - */ - -#include -#include - -#include "putty.h" - -const char *const appname = "pterm"; -const bool use_event_log = false; /* pterm doesn't need it */ -const bool new_session = false, saved_sessions = false; /* or these */ -const bool dup_check_launchable = false; /* no need to check host name - * in conf */ -const bool use_pty_argv = true; - -const unsigned cmdline_tooltype = TOOLTYPE_NONNETWORK; - -/* gtkwin.c will call this, and in pterm it's not needed */ -void noise_ultralight(NoiseSourceId id, unsigned long data) { } - -const struct BackendVtable *select_backend(Conf *conf) -{ - return &pty_backend; -} - -void initial_config_box(Conf *conf, post_dialog_fn_t after, void *afterctx) -{ - /* - * 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. So we do that and then just immediately call the - * post-dialog function with a positive result. - */ - conf_set_int(conf, CONF_protocol, -1); - after(afterctx, 1); -} - -void cleanup_exit(int code) -{ - exit(code); -} - -void setup(bool single) -{ - settings_set_default_protocol(-1); - - if (single) - pty_pre_init(); -} diff --git a/unix/uxpty.c b/unix/uxpty.c deleted file mode 100644 index 30e48e50..00000000 --- a/unix/uxpty.c +++ /dev/null @@ -1,1602 +0,0 @@ -/* - * Pseudo-tty backend for pterm. - */ - -#define _GNU_SOURCE - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "putty.h" -#include "ssh.h" -#include "ssh/server.h" /* to check the prototypes of server-needed things */ -#include "tree234.h" - -#ifndef OMIT_UTMP -#include -#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 - -typedef struct Pty 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 */ - -typedef struct PtyFd { - int fd; - Pty *pty; -} PtyFd; - -struct Pty { - Conf *conf; - - int master_fd, slave_fd; - int pipefds[6]; - PtyFd fds[3]; - int master_i, master_o, master_e; - - Seat *seat; - char name[FILENAME_MAX]; - pid_t child_pid; - int term_width, term_height; - bool child_dead, finished; - int exit_code; - bufchain output_data; - bool pending_eof; - Backend backend; -}; - -/* - * We store all the (active) PtyFd structures in a tree sorted by fd, - * so that when we get an uxsel notification we know which backend - * instance is the owner of the pty that caused it, and then we can - * find out which fd is the relevant one too. - */ -static int ptyfd_compare(void *av, void *bv) -{ - PtyFd *a = (PtyFd *)av; - PtyFd *b = (PtyFd *)bv; - - if (a->fd < b->fd) - return -1; - else if (a->fd > b->fd) - return +1; - return 0; -} - -static int ptyfd_find(void *av, void *bv) -{ - int a = *(int *)av; - PtyFd *b = (PtyFd *)bv; - - if (a < b->fd) - return -1; - else if (a > b->fd) - return +1; - return 0; -} - -static tree234 *ptyfds = NULL; - -/* - * We also have a tree of Pty structures themselves, 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 = -1; -static int pty_utmp_helper_pipe = -1; -static bool 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; - -char *pty_osx_envrestore_prefix; - -static void pty_close(Pty *pty); -static void pty_try_write(Pty *pty); - -#ifndef OMIT_UTMP -static void setup_utmp(char *ttyname, char *location) -{ -#if HAVE_LASTLOG - struct lastlog lastlog_entry; - FILE *lastlog; -#endif - struct passwd *pw; - struct timeval tv; - - pw = getpwuid(getuid()); - if (!pw) - return; /* can't stamp utmp if we don't have a username */ - memset(&utmp_entry, 0, sizeof(utmp_entry)); - utmp_entry.ut_type = USER_PROCESS; - utmp_entry.ut_pid = getpid(); -#if __GNUC__ >= 8 -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wstringop-truncation" -#endif // __GNUC__ >= 8 - 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)); -#if __GNUC__ >= 8 -# pragma GCC diagnostic pop -#endif // __GNUC__ >= 8 - /* - * 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(); - -#if HAVE_UPDWTMPX - updwtmpx(WTMPX_FILE, &utmp_entry); -#endif - -#if 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 = true; - -} - -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; - -#if HAVE_UPDWTMPX - updwtmpx(WTMPX_FILE, &utmp_entry); -#endif - - 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 = false; /* 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 */; -} - -static void pty_setup_sigchld_handler(void) -{ - static bool setup = false; - if (!setup) { - putty_signal(SIGCHLD, sigchld_handler); - setup = true; - } -} - -#ifndef OMIT_UTMP -static void fatal_sig_handler(int signum) -{ - putty_signal(signum, SIG_DFL); - cleanup_utmp(); - 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 - - const int flags = O_RDWR -#ifdef O_NOCTTY - | O_NOCTTY -#endif - ; - -#if HAVE_POSIX_OPENPT -#ifdef SET_NONBLOCK_VIA_OPENPT - /* - * OS X, as of 10.10 at least, doesn't permit me to set O_NONBLOCK - * on pty master fds via the usual fcntl mechanism. Fortunately, - * it does let me work around this by adding O_NONBLOCK to the - * posix_openpt flags parameter, which isn't a documented use of - * the API but seems to work. So we'll do that for now. - */ - pty->master_fd = posix_openpt(flags | O_NONBLOCK); -#else - pty->master_fd = posix_openpt(flags); -#endif - - if (pty->master_fd < 0) { - perror("posix_openpt"); - exit(1); - } -#else - pty->master_fd = open("/dev/ptmx", flags); - - if (pty->master_fd < 0) { - perror("/dev/ptmx: open"); - exit(1); - } -#endif - - if (grantpt(pty->master_fd) < 0) { - perror("grantpt"); - exit(1); - } - - 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 - -#ifndef SET_NONBLOCK_VIA_OPENPT - nonblock(pty->master_fd); -#endif -} - -static Pty *new_pty_struct(void) -{ - Pty *pty = snew(Pty); - pty->conf = NULL; - pty->pending_eof = false; - bufchain_init(&pty->output_data); - return 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) -{ -#ifndef NO_PTY_PRE_INIT - - Pty *pty; - -#ifndef OMIT_UTMP - pid_t pid; - int pipefd[2]; -#endif - - pty = single_pty = new_pty_struct(); - - /* set the child signal handler straight away; it needs to be set - * before we ever fork. */ - pty_setup_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. */ - { -#if HAVE_SETRESUID && HAVE_SETRESGID - int gid = getgid(), uid = getuid(); - int setresgid(gid_t, gid_t, gid_t); - int setresuid(uid_t, uid_t, uid_t); - if (setresgid(gid, gid, gid) < 0) { - perror("setresgid"); - exit(1); - } - if (setresuid(uid, uid, uid) < 0) { - perror("setresuid"); - exit(1); - } -#else - if (setgid(getgid()) < 0) { - perror("setgid"); - exit(1); - } - if (setuid(getuid()) < 0) { - perror("setuid"); - exit(1); - } -#endif - } - -#endif /* NO_PTY_PRE_INIT */ - -} - -static void pty_try_wait(void); - -static void pty_real_select_result(Pty *pty, int fd, int event, int status) -{ - char buf[4096]; - int ret; - bool 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. - */ - pty->child_dead = true; - del234(ptys_by_pid, pty); - pty->exit_code = status; - - /* - * If this is an ordinary pty session, this is also the - * moment to terminate the whole backend. - * - * We _could_ instead 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. - */ - if (pty->master_fd >= 0) - finished = true; - } - } else { - if (event == SELECT_R) { - bool is_stdout = (fd == pty->master_o); - - ret = read(fd, buf, sizeof(buf)); - - /* - * Treat EIO on a pty master as equivalent to EOF (because - * that's how the kernel seems to report the event where - * the last process connected to the other end of the pty - * went away). - */ - if (fd == pty->master_fd && ret < 0 && errno == EIO) - ret = 0; - - if (ret == 0) { - /* - * EOF on this input fd, so to begin with, we may as - * well close it, and remove all references to it in - * the pty's fd fields. - */ - uxsel_del(fd); - close(fd); - if (pty->master_fd == fd) - pty->master_fd = -1; - if (pty->master_o == fd) - pty->master_o = -1; - if (pty->master_e == fd) - pty->master_e = -1; - - if (is_stdout) { - /* - * We assume a clean exit if the pty (or stdout - * pipe) 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; - pty_try_wait(); /* one last effort to collect exit code */ - if (!pty->child_dead) - pty->exit_code = 0; - } - } else if (ret < 0) { - perror("read pty master"); - exit(1); - } else if (ret > 0) { - seat_output(pty->seat, !is_stdout, buf, ret); - } - } else if (event == SELECT_W) { - /* - * Attempt to send data down the pty. - */ - pty_try_write(pty); - } - } - - if (finished && !pty->finished) { - int close_on_exit; - int i; - - for (i = 0; i < 3; i++) - if (pty->fds[i].fd >= 0) - uxsel_del(pty->fds[i].fd); - - pty_close(pty); - - 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. - */ - close_on_exit = conf_get_int(pty->conf, CONF_close_on_exit); - if (close_on_exit == FORCE_OFF || - (close_on_exit == AUTO && pty->exit_code != 0)) { - char *message; - if (WIFEXITED(pty->exit_code)) { - message = dupprintf( - "\r\n[pterm: process terminated with exit code %d]\r\n", - WEXITSTATUS(pty->exit_code)); - } else if (WIFSIGNALED(pty->exit_code)) { -#if !HAVE_STRSIGNAL - message = dupprintf( - "\r\n[pterm: process terminated on signal %d]\r\n", - WTERMSIG(pty->exit_code)); -#else - message = dupprintf( - "\r\n[pterm: process terminated on signal %d (%s)]\r\n", - WTERMSIG(pty->exit_code), - strsignal(WTERMSIG(pty->exit_code))); -#endif - } else { - /* _Shouldn't_ happen, but if it does, a vague message - * is better than no message at all */ - message = dupprintf("\r\n[pterm: process terminated]\r\n"); - } - seat_stdout_pl(pty->seat, ptrlen_from_asciz(message)); - sfree(message); - } - - seat_eof(pty->seat); - seat_notify_remote_exit(pty->seat); - } -} - -static void pty_try_wait(void) -{ - Pty *pty; - pid_t pid; - int status; - - do { - pid = waitpid(-1, &status, WNOHANG); - - pty = find234(ptys_by_pid, &pid, pty_find_by_pid); - - if (pty) - pty_real_select_result(pty, -1, -1, status); - } while (pid > 0); -} - -void pty_select_result(int fd, int event) -{ - if (fd == pty_signal_pipe[0]) { - char c[1]; - - if (read(pty_signal_pipe[0], c, 1) <= 0) - /* ignore error */; - /* ignore its value; it'll be `x' */ - - pty_try_wait(); - } else { - PtyFd *ptyfd = find234(ptyfds, &fd, ptyfd_find); - - if (ptyfd) - pty_real_select_result(ptyfd->pty, fd, event, 0); - } -} - -static void pty_uxsel_setup_fd(Pty *pty, int fd) -{ - int rwx = 0; - - if (fd < 0) - return; - - /* read from standard output and standard error pipes */ - if (pty->master_o == fd || pty->master_e == fd) - rwx |= SELECT_R; - /* write to standard input pipe if we have any data */ - if (pty->master_i == fd && bufchain_size(&pty->output_data)) - rwx |= SELECT_W; - - uxsel_set(fd, rwx, pty_select_result); -} - -static void pty_uxsel_setup(Pty *pty) -{ - /* - * We potentially have three separate fds here, but on the other - * hand, some of them might be the same (if they're a pty master). - * So we can't just call uxsel_set(master_o, SELECT_R) and then - * uxsel_set(master_i, SELECT_W), without the latter potentially - * undoing the work of the former if master_o == master_i. - * - * Instead, here we call a single uxsel on each one of these fds - * (if it exists at all), and for each one, check it against all - * three to see which bits to set. - */ - pty_uxsel_setup_fd(pty, pty->master_o); - pty_uxsel_setup_fd(pty, pty->master_e); - pty_uxsel_setup_fd(pty, pty->master_i); - - /* - * 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], SELECT_R, pty_select_result); -} - -static void copy_ttymodes_into_termios( - struct termios *attrs, struct ssh_ttymodes modes) -{ -#define TTYMODE_CHAR(name, ssh_opcode, cc_index) { \ - if (modes.have_mode[ssh_opcode]) { \ - unsigned value = modes.mode_val[ssh_opcode]; \ - /* normalise wire value of 255 to local _POSIX_VDISABLE */ \ - attrs->c_cc[cc_index] = (value == 255 ? \ - _POSIX_VDISABLE : value); \ - } \ - } - -#define TTYMODE_FLAG(flagval, ssh_opcode, field, flagmask) { \ - if (modes.have_mode[ssh_opcode]) { \ - attrs->c_##field##flag &= ~flagmask; \ - if (modes.mode_val[ssh_opcode]) \ - attrs->c_##field##flag |= flagval; \ - } \ - } - -#define TTYMODES_LOCAL_ONLY /* omit any that this platform doesn't know */ -#include "ssh/ttymode-list.h" - -#undef TTYMODES_LOCAL_ONLY -#undef TTYMODE_CHAR -#undef TTYMODE_FLAG - - if (modes.have_mode[TTYMODE_ISPEED]) - cfsetispeed(attrs, modes.mode_val[TTYMODE_ISPEED]); - if (modes.have_mode[TTYMODE_OSPEED]) - cfsetospeed(attrs, modes.mode_val[TTYMODE_OSPEED]); -} - -/* - * The main setup function for the pty back end. This doesn't match - * the signature of backend_init(), partly because it has to be able - * to take extra arguments such as an argv array, and also because - * once we're changing the type signature _anyway_ we can discard the - * stuff that's not really applicable to this backend like host names - * and port numbers. - */ -Backend *pty_backend_create( - Seat *seat, LogContext *logctx, Conf *conf, char **argv, const char *cmd, - struct ssh_ttymodes ttymodes, bool pipes_instead, const char *dir, - const char *const *env_vars_to_unset) -{ - int slavefd; - pid_t pid, pgrp; -#ifndef NOT_X_WINDOWS /* for Mac OS X native compilation */ - bool got_windowid; - long windowid; -#endif - Pty *pty; - int i; - - /* No local authentication phase in this protocol */ - seat_set_trust_status(seat, false); - - if (single_pty) { - pty = single_pty; - assert(pty->conf == NULL); - } else { - pty = new_pty_struct(); - pty->master_fd = pty->slave_fd = -1; -#ifndef OMIT_UTMP - pty_stamped_utmp = false; -#endif - } - for (i = 0; i < 6; i++) - pty->pipefds[i] = -1; - for (i = 0; i < 3; i++) { - pty->fds[i].fd = -1; - pty->fds[i].pty = 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->seat = seat; - pty->backend.vt = &pty_backend; - - pty->conf = conf_copy(conf); - pty->term_width = conf_get_int(conf, CONF_width); - pty->term_height = conf_get_int(conf, CONF_height); - - if (!ptyfds) - ptyfds = newtree234(ptyfd_compare); - - if (pipes_instead) { - if (pty->master_fd >= 0) { - /* If somehow we've got a pty master already and don't - * need it, throw it away! */ - close(pty->master_fd); -#ifndef OMIT_UTMP - if (pty_utmp_helper_pipe >= 0) { - close(pty_utmp_helper_pipe); /* don't need this either */ - pty_utmp_helper_pipe = -1; - } -#endif - } - - - for (i = 0; i < 6; i += 2) { - if (pipe(pty->pipefds + i) < 0) { - backend_free(&pty->backend); - return NULL; - } - } - - pty->fds[0].fd = pty->master_i = pty->pipefds[1]; - pty->fds[1].fd = pty->master_o = pty->pipefds[2]; - pty->fds[2].fd = pty->master_e = pty->pipefds[4]; - - add234(ptyfds, &pty->fds[0]); - add234(ptyfds, &pty->fds[1]); - add234(ptyfds, &pty->fds[2]); - } else { - if (pty->master_fd < 0) - pty_open_master(pty); - -#ifndef OMIT_UTMP - /* - * Stamp utmp (that is, tell the utmp helper process to do so), - * or not. - */ - if (pty_utmp_helper_pipe >= 0) { /* if it's < 0, we can't anyway */ - if (!conf_get_bool(conf, CONF_stamp_utmp)) { - /* We're not stamping utmp, so just let the child - * process die that was waiting to unstamp it later. */ - close(pty_utmp_helper_pipe); - pty_utmp_helper_pipe = -1; - } else { - const char *location = seat_get_x_display(pty->seat); - 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 - - pty->master_i = pty->master_fd; - pty->master_o = pty->master_fd; - pty->master_e = -1; - - pty->fds[0].fd = pty->master_fd; - add234(ptyfds, &pty->fds[0]); - } - -#ifndef NOT_X_WINDOWS /* for Mac OS X native compilation */ - got_windowid = seat_get_windowid(pty->seat, &windowid); -#endif - - /* - * Set up the signal handler to catch SIGCHLD, if pty_pre_init - * didn't already do it. - */ - pty_setup_sigchld_handler(); - - /* - * Fork and execute the command. - */ - pid = fork(); - if (pid < 0) { - perror("fork"); - exit(1); - } - - if (pid == 0) { - struct termios attrs; - - /* - * We are the child. - */ - - if (pty_osx_envrestore_prefix) { - int plen = strlen(pty_osx_envrestore_prefix); - extern char **environ; - char **ep; - - restart_osx_env_restore: - for (ep = environ; *ep; ep++) { - char *e = *ep; - - if (!strncmp(e, pty_osx_envrestore_prefix, plen)) { - bool unset = (e[plen] == 'u'); - char *pname = dupprintf("%.*s", (int)strcspn(e, "="), e); - char *name = pname + plen + 1; - char *value = e + strcspn(e, "="); - if (*value) value++; - value = dupstr(value); - if (unset) - unsetenv(name); - else - setenv(name, value, 1); - unsetenv(pname); - sfree(pname); - sfree(value); - goto restart_osx_env_restore; - } - } - } - - pgrp = getpid(); - - if (pipes_instead) { - int i; - dup2(pty->pipefds[0], 0); - dup2(pty->pipefds[3], 1); - dup2(pty->pipefds[5], 2); - for (i = 0; i < 6; i++) - close(pty->pipefds[i]); - - setsid(); - } else { - slavefd = pty_open_slave(pty); - if (slavefd < 0) { - perror("slave pty: open"); - _exit(1); - } - - close(pty->master_fd); - noncloexec(slavefd); - dup2(slavefd, 0); - dup2(slavefd, 1); - dup2(slavefd, 2); - close(slavefd); - setsid(); -#ifdef TIOCSCTTY - ioctl(0, TIOCSCTTY, 1); -#endif - tcsetpgrp(0, pgrp); - - /* - * Set up configuration-dependent termios settings on the new - * pty. Linux would have let us do this on the pty master - * before we forked, but that fails on OS X, so we do it here - * instead. - */ - if (tcgetattr(0, &attrs) == 0) { - /* - * Set the backspace character to be whichever of ^H and - * ^? is specified by bksp_is_delete. - */ - attrs.c_cc[VERASE] = conf_get_bool(conf, CONF_bksp_is_delete) - ? '\177' : '\010'; - - /* - * Set the IUTF8 bit iff the character set is UTF-8. - */ -#ifdef IUTF8 - if (seat_is_utf8(seat)) - attrs.c_iflag |= IUTF8; - else - attrs.c_iflag &= ~IUTF8; -#endif - - copy_ttymodes_into_termios(&attrs, ttymodes); - - tcsetattr(0, TCSANOW, &attrs); - } - } - - setpgid(pgrp, pgrp); - if (!pipes_instead) { - int ptyfd = open(pty->name, O_WRONLY, 0); - if (ptyfd >= 0) - close(ptyfd); - } - setpgid(pgrp, pgrp); - - if (env_vars_to_unset) - for (const char *const *p = env_vars_to_unset; *p; p++) - unsetenv(*p); - - if (!pipes_instead) { - char *term_env_var = dupprintf("TERM=%s", - conf_get_str(conf, CONF_termtype)); - putenv(term_env_var); - /* We mustn't free term_env_var, as putenv links it into the - * environment in place. - */ - } -#ifndef NOT_X_WINDOWS /* for Mac OS X native compilation */ - if (got_windowid) { - 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. - */ - } - { - /* - * In case we were invoked with a --display argument that - * doesn't match DISPLAY in our actual environment, we - * should set DISPLAY for processes running inside the - * terminal to match the display the terminal itself is - * on. - */ - const char *x_display = seat_get_x_display(pty->seat); - if (x_display) { - char *x_display_env_var = dupprintf("DISPLAY=%s", x_display); - putenv(x_display_env_var); - /* As above, we don't free this. */ - } else { - unsetenv("DISPLAY"); - } - } -#endif - { - char *key, *val; - - for (val = conf_get_str_strs(conf, CONF_environmt, NULL, &key); - val != NULL; - val = conf_get_str_strs(conf, CONF_environmt, key, &key)) { - char *varval = dupcat(key, "=", val); - 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. - */ - } - } - - if (dir) { - if (chdir(dir) < 0) { - /* Ignore the error - nothing we can sensibly do about it, - * and our existing cwd is as good a fallback as any. */ - } - } - - /* - * 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. SIGPIPE was also - * (potentially) blocked by us during 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(SIGPIPE, false); - if (argv || cmd) { - /* - * If we were given a separated argument list, try to exec - * it. - */ - if (argv) { - execvp(argv[0], argv); - } - /* - * Otherwise, if we were given a single command string, - * try passing that to $SHELL -c. - * - * In the case of pterm, this system of fallbacks arranges - * that we can _either_ follow 'pterm -e' with a list of - * argv elements to be fed directly to exec, _or_ with a - * single argument containing a command to be parsed by a - * shell (but, in cases of doubt, the former is more - * reliable). We arrange this by setting argv to the full - * argument list, and also setting cmd to the single - * element of argv if it's a length-1 list. - * - * A quick survey of other terminal emulators' -e options - * (as of Debian squeeze) suggests that: - * - * - xterm supports both modes, more or less like this - * - gnome-terminal will only accept a one-string shell command - * - Eterm, kterm and rxvt will only accept a list of - * argv elements (as did older versions of pterm). - * - * It therefore seems important to support both usage - * modes in order to be a drop-in replacement for either - * xterm or gnome-terminal, and hence for anyone's - * plausible uses of the Debian-style alias - * 'x-terminal-emulator'. - * - * In other use cases, a caller can set only one of argv - * and cmd to get a fixed handling of the input. - */ - if (cmd) { - char *shell = getenv("SHELL"); - if (shell) - execl(shell, shell, "-c", cmd, (void *)NULL); - } - } else { - const char *shell = getenv("SHELL"); - if (!shell) - shell = "/bin/sh"; - char *shellname; - if (conf_get_bool(conf, CONF_login_shell)) { - const char *p = strrchr(shell, '/'); - shellname = snewn(2+strlen(shell), char); - p = p ? p+1 : shell; - sprintf(shellname, "-%s", p); - } else - shellname = (char *)shell; - execl(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); - if (pty->pipefds[0] >= 0) { - close(pty->pipefds[0]); - pty->pipefds[0] = -1; - } - if (pty->pipefds[3] >= 0) { - close(pty->pipefds[3]); - pty->pipefds[3] = -1; - } - if (pty->pipefds[5] >= 0) { - close(pty->pipefds[5]); - pty->pipefds[5] = -1; - } - add234(ptys_by_pid, pty); - } - - pty_uxsel_setup(pty); - - return &pty->backend; -} - -/* - * This is the pty backend's _official_ init method, for BackendVtable - * purposes. Its job is just to be an API converter, ignoring the - * irrelevant input parameters and making up auxiliary outputs. Also - * it gets the argv array from the global variable pty_argv, expecting - * that it will have been invoked by pterm. - */ -static char *pty_init(const BackendVtable *vt, Seat *seat, - Backend **backend_handle, LogContext *logctx, - Conf *conf, const char *host, int port, - char **realhost, bool nodelay, bool keepalive) -{ - const char *cmd = NULL; - struct ssh_ttymodes modes; - - memset(&modes, 0, sizeof(modes)); - - if (pty_argv && pty_argv[0] && !pty_argv[1]) - cmd = pty_argv[0]; - - assert(vt == &pty_backend); - *backend_handle = pty_backend_create( - seat, logctx, conf, pty_argv, cmd, modes, false, NULL, NULL); - *realhost = dupstr(""); - return NULL; -} - -static void pty_reconfig(Backend *be, Conf *conf) -{ - Pty *pty = container_of(be, Pty, backend); - /* - * 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. - */ - conf_copy_into(pty->conf, conf); -} - -/* - * Stub routine (never called in pterm). - */ -static void pty_free(Backend *be) -{ - Pty *pty = container_of(be, Pty, backend); - int i; - - pty_close(pty); - - /* Either of these may fail `not found'. That's fine with us. */ - del234(ptys_by_pid, pty); - for (i = 0; i < 3; i++) - if (pty->fds[i].fd >= 0) - del234(ptyfds, &pty->fds[i]); - - bufchain_clear(&pty->output_data); - - conf_free(pty->conf); - pty->conf = NULL; - - if (pty == single_pty) { - /* - * Leave this structure around in case we need to Restart - * Session. - */ - } else { - sfree(pty); - } -} - -static void pty_try_write(Pty *pty) -{ - ssize_t ret; - - assert(pty->master_i >= 0); - - while (bufchain_size(&pty->output_data) > 0) { - ptrlen data = bufchain_prefix(&pty->output_data); - ret = write(pty->master_i, data.ptr, 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); - } - - if (pty->pending_eof && bufchain_size(&pty->output_data) == 0) { - /* This should only happen if pty->master_i is a pipe that - * doesn't alias either output fd */ - assert(pty->master_i != pty->master_o); - assert(pty->master_i != pty->master_e); - uxsel_del(pty->master_i); - close(pty->master_i); - pty->master_i = -1; - pty->pending_eof = false; - } - - pty_uxsel_setup(pty); -} - -/* - * Called to send data down the pty. - */ -static size_t pty_send(Backend *be, const char *buf, size_t len) -{ - Pty *pty = container_of(be, Pty, backend); - - if (pty->master_i < 0 || pty->pending_eof) - 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) -{ - int i; - - if (pty->master_o >= 0) - uxsel_del(pty->master_o); - if (pty->master_e >= 0) - uxsel_del(pty->master_e); - if (pty->master_i >= 0) - uxsel_del(pty->master_i); - - if (pty->master_fd >= 0) { - close(pty->master_fd); - pty->master_fd = -1; - } - for (i = 0; i < 6; i++) { - if (pty->pipefds[i] >= 0) - close(pty->pipefds[i]); - pty->pipefds[i] = -1; - } - pty->master_i = pty->master_o = pty->master_e = -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 size_t pty_sendbuffer(Backend *be) -{ - /* Pty *pty = container_of(be, Pty, backend); */ - return 0; -} - -/* - * Called to set the size of the window - */ -static void pty_size(Backend *be, int width, int height) -{ - Pty *pty = container_of(be, Pty, backend); - struct winsize size; - int xpixel = 0, ypixel = 0; - - pty->term_width = width; - pty->term_height = height; - - if (pty->master_fd < 0) - return; - - seat_get_window_pixel_size(pty->seat, &xpixel, &ypixel); - - size.ws_row = (unsigned short)pty->term_height; - size.ws_col = (unsigned short)pty->term_width; - size.ws_xpixel = (unsigned short)xpixel; - size.ws_ypixel = (unsigned short)ypixel; - ioctl(pty->master_fd, TIOCSWINSZ, (void *)&size); - return; -} - -/* - * Send special codes. - */ -static void pty_special(Backend *be, SessionSpecialCode code, int arg) -{ - Pty *pty = container_of(be, Pty, backend); - - if (code == SS_BRK) { - if (pty->master_fd >= 0) - tcsendbreak(pty->master_fd, 0); - return; - } - - if (code == SS_EOF) { - if (pty->master_i >= 0 && pty->master_i != pty->master_fd) { - pty->pending_eof = true; - pty_try_write(pty); - } - return; - } - - { - int sig = -1; - - #define SIGNAL_SUB(name) if (code == SS_SIG ## name) sig = SIG ## name; - #define SIGNAL_MAIN(name, text) SIGNAL_SUB(name) - #define SIGNALS_LOCAL_ONLY - #include "ssh/signal-list.h" - #undef SIGNAL_SUB - #undef SIGNAL_MAIN - #undef SIGNALS_LOCAL_ONLY - - if (sig != -1) { - if (!pty->child_dead) - kill(pty->child_pid, sig); - return; - } - } - - return; -} - -/* - * Return a list of the special codes that make sense in this - * protocol. - */ -static const SessionSpecial *pty_get_specials(Backend *be) -{ - /* Pty *pty = container_of(be, Pty, backend); */ - /* - * 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 bool pty_connected(Backend *be) -{ - /* Pty *pty = container_of(be, Pty, backend); */ - return true; -} - -static bool pty_sendok(Backend *be) -{ - /* Pty *pty = container_of(be, Pty, backend); */ - return true; -} - -static void pty_unthrottle(Backend *be, size_t backlog) -{ - /* Pty *pty = container_of(be, Pty, backend); */ - /* do nothing */ -} - -static bool pty_ldisc(Backend *be, int option) -{ - /* Pty *pty = container_of(be, Pty, backend); */ - return false; /* neither editing nor echoing */ -} - -static void pty_provide_ldisc(Backend *be, Ldisc *ldisc) -{ - /* Pty *pty = container_of(be, Pty, backend); */ - /* This is a stub. */ -} - -static int pty_exitcode(Backend *be) -{ - Pty *pty = container_of(be, Pty, backend); - if (!pty->finished) - return -1; /* not dead yet */ - else if (WIFSIGNALED(pty->exit_code)) - return 128 + WTERMSIG(pty->exit_code); - else - return WEXITSTATUS(pty->exit_code); -} - -int pty_backend_exit_signum(Backend *be) -{ - Pty *pty = container_of(be, Pty, backend); - - if (!pty->finished || !WIFSIGNALED(pty->exit_code)) - return -1; - - return WTERMSIG(pty->exit_code); -} - -ptrlen pty_backend_exit_signame(Backend *be, char **aux_msg) -{ - *aux_msg = NULL; - - int sig = pty_backend_exit_signum(be); - if (sig < 0) - return PTRLEN_LITERAL(""); - - #define SIGNAL_SUB(s) { \ - if (sig == SIG ## s) \ - return PTRLEN_LITERAL(#s); \ - } - #define SIGNAL_MAIN(s, desc) SIGNAL_SUB(s) - #define SIGNALS_LOCAL_ONLY - #include "ssh/signal-list.h" - #undef SIGNAL_MAIN - #undef SIGNAL_SUB - #undef SIGNALS_LOCAL_ONLY - - *aux_msg = dupprintf("untranslatable signal number %d: %s", - sig, strsignal(sig)); - return PTRLEN_LITERAL("HUP"); /* need some kind of default */ -} - -static int pty_cfg_info(Backend *be) -{ - /* Pty *pty = container_of(be, Pty, backend); */ - return 0; -} - -const BackendVtable pty_backend = { - .init = pty_init, - .free = pty_free, - .reconfig = pty_reconfig, - .send = pty_send, - .sendbuffer = pty_sendbuffer, - .size = pty_size, - .special = pty_special, - .get_specials = pty_get_specials, - .connected = pty_connected, - .exitcode = pty_exitcode, - .sendok = pty_sendok, - .ldisc_option_state = pty_ldisc, - .provide_ldisc = pty_provide_ldisc, - .unthrottle = pty_unthrottle, - .cfg_info = pty_cfg_info, - .id = "pty", - .displayname = "pty", - .protocol = -1, -}; diff --git a/unix/uxputty.c b/unix/uxputty.c deleted file mode 100644 index 7a808087..00000000 --- a/unix/uxputty.c +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Unix PuTTY main program. - */ - -#include -#include -#include -#include -#include -#include -#include - -#define MAY_REFER_TO_GTK_IN_HEADERS - -#include "putty.h" -#include "ssh.h" -#include "storage.h" - -#include "gtkcompat.h" - -/* - * Stubs to avoid uxpty.c needing to be linked in. - */ -const bool use_pty_argv = false; -char **pty_argv; /* never used */ -char *pty_osx_envrestore_prefix; - -/* - * Clean up and exit. - */ -void cleanup_exit(int code) -{ - /* - * Clean up. - */ - sk_cleanup(); - random_save_seed(); - exit(code); -} - -const struct BackendVtable *select_backend(Conf *conf) -{ - const struct BackendVtable *vt = - backend_vt_from_proto(conf_get_int(conf, CONF_protocol)); - assert(vt != NULL); - return vt; -} - -void initial_config_box(Conf *conf, post_dialog_fn_t after, void *afterctx) -{ - char *title = dupcat(appname, " Configuration"); - create_config_box(title, conf, false, 0, after, afterctx); - sfree(title); -} - -const bool use_event_log = true, new_session = true, saved_sessions = true; -const bool dup_check_launchable = true; - -/* - * 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); -} - -const bool share_can_be_downstream = true; -const bool share_can_be_upstream = true; - -const unsigned cmdline_tooltype = - TOOLTYPE_HOST_ARG | - TOOLTYPE_PORT_ARG | - TOOLTYPE_NO_VERBOSE_OPTION; - -void setup(bool single) -{ - sk_init(); - settings_set_default_protocol(be_default_protocol); - /* Find the appropriate default port. */ - { - const struct BackendVtable *vt = - backend_vt_from_proto(be_default_protocol); - settings_set_default_port(0); /* illegal */ - if (vt) - settings_set_default_port(vt->default_port); - } -} diff --git a/unix/uxser.c b/unix/uxser.c deleted file mode 100644 index d4a1e0ba..00000000 --- a/unix/uxser.c +++ /dev/null @@ -1,595 +0,0 @@ -/* - * Serial back end (Unix-specific). - */ - -#include -#include -#include -#include - -#include -#include -#include -#include - -#include "putty.h" -#include "tree234.h" - -#define SERIAL_MAX_BACKLOG 4096 - -typedef struct Serial Serial; -struct Serial { - Seat *seat; - LogContext *logctx; - int fd; - bool finished; - size_t inbufsize; - bufchain output_data; - Backend backend; -}; - -/* - * 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 void serial_select_result(int fd, int event); -static void serial_uxsel_setup(Serial *serial); -static void serial_try_write(Serial *serial); - -static char *serial_configure(Serial *serial, Conf *conf) -{ - struct termios options; - int bflag, bval, speed, flow, parity; - const char *str; - - if (serial->fd < 0) - return dupstr("Unable to reconfigure already-closed " - "serial connection"); - - tcgetattr(serial->fd, &options); - - /* - * Find the appropriate baud rate flag. - */ - speed = conf_get_int(conf, CONF_serspeed); -#define SETBAUD(x) (bflag = B ## x, bval = x) -#define CHECKBAUD(x) do { if (speed >= 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); - logeventf(serial->logctx, "Configuring baud rate %d", bval); - - options.c_cflag &= ~CSIZE; - switch (conf_get_int(conf, CONF_serdatabits)) { - case 5: options.c_cflag |= CS5; break; - case 6: options.c_cflag |= CS6; break; - case 7: options.c_cflag |= CS7; break; - case 8: options.c_cflag |= CS8; break; - default: return dupstr("Invalid number of data bits " - "(need 5, 6, 7 or 8)"); - } - logeventf(serial->logctx, "Configuring %d data bits", - conf_get_int(conf, CONF_serdatabits)); - - if (conf_get_int(conf, CONF_serstopbits) >= 4) { - options.c_cflag |= CSTOPB; - } else { - options.c_cflag &= ~CSTOPB; - } - logeventf(serial->logctx, "Configuring %s", - (options.c_cflag & CSTOPB ? "2 stop bits" : "1 stop bit")); - - options.c_iflag &= ~(IXON|IXOFF); -#ifdef CRTSCTS - options.c_cflag &= ~CRTSCTS; -#endif -#ifdef CNEW_RTSCTS - options.c_cflag &= ~CNEW_RTSCTS; -#endif - flow = conf_get_int(conf, CONF_serflow); - if (flow == SER_FLOW_XONXOFF) { - options.c_iflag |= IXON | IXOFF; - str = "XON/XOFF"; - } else if (flow == 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"; - logeventf(serial->logctx, "Configuring %s flow control", str); - - /* Parity */ - parity = conf_get_int(conf, CONF_serparity); - if (parity == SER_PAR_ODD) { - options.c_cflag |= PARENB; - options.c_cflag |= PARODD; - str = "odd"; - } else if (parity == SER_PAR_EVEN) { - options.c_cflag |= PARENB; - options.c_cflag &= ~PARODD; - str = "even"; - } else { - options.c_cflag &= ~PARENB; - str = "no"; - } - logeventf(serial->logctx, "Configuring %s parity", str); - - 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 dupprintf("Configuring serial port: %s", strerror(errno)); - - 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 char *serial_init(const BackendVtable *vt, Seat *seat, - Backend **backend_handle, LogContext *logctx, - Conf *conf, const char *host, int port, - char **realhost, bool nodelay, bool keepalive) -{ - Serial *serial; - char *err; - char *line; - - /* No local authentication phase in this protocol */ - seat_set_trust_status(seat, false); - - serial = snew(Serial); - serial->backend.vt = vt; - *backend_handle = &serial->backend; - - serial->seat = seat; - serial->logctx = logctx; - serial->finished = false; - serial->inbufsize = 0; - bufchain_init(&serial->output_data); - - line = conf_get_str(conf, CONF_serline); - logeventf(serial->logctx, "Opening serial device %s", line); - - serial->fd = open(line, O_RDWR | O_NOCTTY | O_NDELAY | O_NONBLOCK); - if (serial->fd < 0) - return dupprintf("Opening serial port '%s': %s", - line, strerror(errno)); - - cloexec(serial->fd); - - err = serial_configure(serial, conf); - if (err) - return err; - - *realhost = dupstr(line); - - 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. - */ - seat_update_specials_menu(serial->seat); - - return NULL; -} - -static void serial_close(Serial *serial) -{ - if (serial->fd >= 0) { - uxsel_del(serial->fd); - close(serial->fd); - serial->fd = -1; - } -} - -static void serial_free(Backend *be) -{ - Serial *serial = container_of(be, Serial, backend); - - serial_close(serial); - - bufchain_clear(&serial->output_data); - - sfree(serial); -} - -static void serial_reconfig(Backend *be, Conf *conf) -{ - Serial *serial = container_of(be, Serial, backend); - - char *err = serial_configure(serial, conf); - if (err) { - /* - * FIXME: apart from freeing the dynamically allocated - * message, what should we do if this returns an error? - */ - sfree(err); - } -} - -static void serial_select_result(int fd, int event) -{ - Serial *serial; - char buf[4096]; - int ret; - bool finished = false; - - serial = find234(serial_by_fd, &fd, serial_find_by_fd); - - if (!serial) - return; /* 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; /* spurious */ -#endif -#ifdef EWOULDBLOCK - if (errno == EWOULDBLOCK) - return; /* spurious */ -#endif - perror("read serial port"); - exit(1); - } else if (ret > 0) { - serial->inbufsize = seat_stdout(serial->seat, 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; - - seat_notify_remote_exit(serial->seat); - } -} - -static void serial_uxsel_setup(Serial *serial) -{ - int rwx = 0; - - if (serial->inbufsize <= SERIAL_MAX_BACKLOG) - rwx |= SELECT_R; - if (bufchain_size(&serial->output_data)) - rwx |= SELECT_W; /* might also want to write to it */ - uxsel_set(serial->fd, rwx, serial_select_result); -} - -static void serial_try_write(Serial *serial) -{ - ssize_t ret; - - assert(serial->fd >= 0); - - while (bufchain_size(&serial->output_data) > 0) { - ptrlen data = bufchain_prefix(&serial->output_data); - ret = write(serial->fd, data.ptr, 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 size_t serial_send(Backend *be, const char *buf, size_t len) -{ - Serial *serial = container_of(be, Serial, backend); - - 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 size_t serial_sendbuffer(Backend *be) -{ - Serial *serial = container_of(be, Serial, backend); - return bufchain_size(&serial->output_data); -} - -/* - * Called to set the size of the window - */ -static void serial_size(Backend *be, int width, int height) -{ - /* Do nothing! */ - return; -} - -/* - * Send serial special codes. - */ -static void serial_special(Backend *be, SessionSpecialCode code, int arg) -{ - Serial *serial = container_of(be, Serial, backend); - - if (serial->fd >= 0 && code == SS_BRK) { - tcsendbreak(serial->fd, 0); - logevent(serial->logctx, "Sending serial break at user request"); - } - - return; -} - -/* - * Return a list of the special codes that make sense in this - * protocol. - */ -static const SessionSpecial *serial_get_specials(Backend *be) -{ - static const struct SessionSpecial specials[] = { - {"Break", SS_BRK}, - {NULL, SS_EXITMENU} - }; - return specials; -} - -static bool serial_connected(Backend *be) -{ - return true; /* always connected */ -} - -static bool serial_sendok(Backend *be) -{ - return true; -} - -static void serial_unthrottle(Backend *be, size_t backlog) -{ - Serial *serial = container_of(be, Serial, backend); - serial->inbufsize = backlog; - serial_uxsel_setup(serial); -} - -static bool serial_ldisc(Backend *be, int option) -{ - /* - * Local editing and local echo are off by default. - */ - return false; -} - -static void serial_provide_ldisc(Backend *be, Ldisc *ldisc) -{ - /* This is a stub. */ -} - -static int serial_exitcode(Backend *be) -{ - Serial *serial = container_of(be, Serial, backend); - 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(Backend *be) -{ - return 0; -} - -const BackendVtable serial_backend = { - .init = serial_init, - .free = serial_free, - .reconfig = serial_reconfig, - .send = serial_send, - .sendbuffer = serial_sendbuffer, - .size = serial_size, - .special = serial_special, - .get_specials = serial_get_specials, - .connected = serial_connected, - .exitcode = serial_exitcode, - .sendok = serial_sendok, - .ldisc_option_state = serial_ldisc, - .provide_ldisc = serial_provide_ldisc, - .unthrottle = serial_unthrottle, - .cfg_info = serial_cfg_info, - .id = "serial", - .displayname = "Serial", - .protocol = PROT_SERIAL, - .serial_parity_mask = ((1 << SER_PAR_NONE) | - (1 << SER_PAR_ODD) | - (1 << SER_PAR_EVEN)), - .serial_flow_mask = ((1 << SER_FLOW_NONE) | - (1 << SER_FLOW_XONXOFF) | - (1 << SER_FLOW_RTSCTS)), -}; diff --git a/unix/uxserver.c b/unix/uxserver.c deleted file mode 100644 index 1a35451b..00000000 --- a/unix/uxserver.c +++ /dev/null @@ -1,850 +0,0 @@ -/* - * SSH server for Unix: main program. - * - * ====================================================================== - * - * This server is NOT SECURE! - * - * DO NOT DEPLOY IT IN A HOSTILE-FACING ENVIRONMENT! - * - * Its purpose is to speak the server end of everything PuTTY speaks - * on the client side, so that I can test that I haven't broken PuTTY - * when I reorganise its code, even things like RSA key exchange or - * chained auth methods which it's hard to find a server that speaks - * at all. - * - * It has no interaction with the OS's authentication system: the - * authentications it will accept are configurable by command-line - * option, and once you authenticate, it will run the connection - * protocol - including all subprocesses and shells - under the same - * Unix user id you started it under. - * - * It really is only suitable for testing the actual SSH protocol. - * Don't use it for anything more serious! - * - * ====================================================================== - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "putty.h" -#include "mpint.h" -#include "ssh.h" -#include "ssh/server.h" - -const char *const appname = "uppity"; - -void modalfatalbox(const 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 nonfatal(const char *p, ...) -{ - va_list ap; - fprintf(stderr, "ERROR: "); - va_start(ap, p); - vfprintf(stderr, p, ap); - va_end(ap); - fputc('\n', stderr); -} - -char *platform_default_s(const char *name) -{ - return NULL; -} - -bool platform_default_b(const char *name, bool def) -{ - return def; -} - -int platform_default_i(const char *name, int def) -{ - return def; -} - -FontSpec *platform_default_fontspec(const char *name) -{ - return fontspec_new(""); -} - -Filename *platform_default_filename(const char *name) -{ - return filename_from_str(""); -} - -char *x_get_default(const char *key) -{ - return NULL; /* this is a stub */ -} - -void old_keyfile_warning(void) { } - -void timer_change_notify(unsigned long next) -{ -} - -char *platform_get_x_display(void) { return NULL; } - -void make_unix_sftp_filehandle_key(void *data, size_t size) -{ - random_read(data, size); -} - -static bool verbose; - -struct AuthPolicyShared { - struct AuthPolicy_ssh1_pubkey *ssh1keys; - struct AuthPolicy_ssh2_pubkey *ssh2keys; -}; - -struct AuthPolicy { - struct AuthPolicyShared *shared; - int kbdint_state; -}; - -struct server_instance { - unsigned id; - AuthPolicy ap; - LogPolicy logpolicy; -}; - -static void log_to_stderr(unsigned id, const char *msg) -{ - if (id != (unsigned)-1) - fprintf(stderr, "#%u: ", id); - fputs(msg, stderr); - fputc('\n', stderr); - fflush(stderr); -} - -static void server_eventlog(LogPolicy *lp, const char *event) -{ - struct server_instance *inst = container_of( - lp, struct server_instance, logpolicy); - if (verbose) - log_to_stderr(inst->id, event); -} - -static void server_logging_error(LogPolicy *lp, const char *event) -{ - struct server_instance *inst = container_of( - lp, struct server_instance, logpolicy); - log_to_stderr(inst->id, event); /* unconditional */ -} - -static int server_askappend( - LogPolicy *lp, Filename *filename, - void (*callback)(void *ctx, int result), void *ctx) -{ - return 2; /* always overwrite (FIXME: could make this a cmdline option) */ -} - -static const LogPolicyVtable server_logpolicy_vt = { - .eventlog = server_eventlog, - .askappend = server_askappend, - .logging_error = server_logging_error, - .verbose = null_lp_verbose_no, -}; - -struct AuthPolicy_ssh1_pubkey { - RSAKey key; - struct AuthPolicy_ssh1_pubkey *next; -}; -struct AuthPolicy_ssh2_pubkey { - ptrlen public_blob; - struct AuthPolicy_ssh2_pubkey *next; -}; - -unsigned auth_methods(AuthPolicy *ap) -{ - return (AUTHMETHOD_PUBLICKEY | AUTHMETHOD_PASSWORD | AUTHMETHOD_KBDINT | - AUTHMETHOD_TIS | AUTHMETHOD_CRYPTOCARD); -} -bool auth_none(AuthPolicy *ap, ptrlen username) -{ - return false; -} -int auth_password(AuthPolicy *ap, ptrlen username, ptrlen password, - ptrlen *new_password_opt) -{ - const char *PHONY_GOOD_PASSWORD = "weasel"; - const char *PHONY_BAD_PASSWORD = "ferret"; - - if (!new_password_opt) { - /* Accept login with our preconfigured good password */ - if (ptrlen_eq_string(password, PHONY_GOOD_PASSWORD)) - return 1; - /* Don't outright reject the bad password, but insist on a change */ - if (ptrlen_eq_string(password, PHONY_BAD_PASSWORD)) - return 2; - /* Reject anything else */ - return 0; - } else { - /* In a password-change request, expect the bad password as input */ - if (!ptrlen_eq_string(password, PHONY_BAD_PASSWORD)) - return 0; - /* Accept a request to change it to the good password */ - if (ptrlen_eq_string(*new_password_opt, PHONY_GOOD_PASSWORD)) - return 1; - /* Outright reject a request to change it to the same password - * as it already 'was' */ - if (ptrlen_eq_string(*new_password_opt, PHONY_BAD_PASSWORD)) - return 0; - /* Anything else, pretend the new pw wasn't good enough, and - * re-request a change */ - return 2; - } -} -bool auth_publickey(AuthPolicy *ap, ptrlen username, ptrlen public_blob) -{ - struct AuthPolicy_ssh2_pubkey *iter; - for (iter = ap->shared->ssh2keys; iter; iter = iter->next) { - if (ptrlen_eq_ptrlen(public_blob, iter->public_blob)) - return true; - } - return false; -} -RSAKey *auth_publickey_ssh1( - AuthPolicy *ap, ptrlen username, mp_int *rsa_modulus) -{ - struct AuthPolicy_ssh1_pubkey *iter; - for (iter = ap->shared->ssh1keys; iter; iter = iter->next) { - if (mp_cmp_eq(rsa_modulus, iter->key.modulus)) - return &iter->key; - } - return NULL; -} -AuthKbdInt *auth_kbdint_prompts(AuthPolicy *ap, ptrlen username) -{ - AuthKbdInt *aki; - - switch (ap->kbdint_state) { - case 0: - aki = snew(AuthKbdInt); - aki->title = dupstr("Initial double prompt"); - aki->instruction = - dupstr("First prompt should echo, second should not"); - aki->nprompts = 2; - aki->prompts = snewn(aki->nprompts, AuthKbdIntPrompt); - aki->prompts[0].prompt = dupstr("Echoey prompt: "); - aki->prompts[0].echo = true; - aki->prompts[1].prompt = dupstr("Silent prompt: "); - aki->prompts[1].echo = false; - return aki; - case 1: - aki = snew(AuthKbdInt); - aki->title = dupstr("Zero-prompt step"); - aki->instruction = dupstr("Shouldn't see any prompts this time"); - aki->nprompts = 0; - aki->prompts = NULL; - return aki; - default: - ap->kbdint_state = 0; - return NULL; - } -} -int auth_kbdint_responses(AuthPolicy *ap, const ptrlen *responses) -{ - switch (ap->kbdint_state) { - case 0: - if (ptrlen_eq_string(responses[0], "stoat") && - ptrlen_eq_string(responses[1], "weasel")) { - ap->kbdint_state++; - return 0; /* those are the expected responses */ - } else { - ap->kbdint_state = 0; - return -1; - } - break; - case 1: - return +1; /* succeed after the zero-prompt step */ - default: - ap->kbdint_state = 0; - return -1; - } -} -char *auth_ssh1int_challenge(AuthPolicy *ap, unsigned method, ptrlen username) -{ - /* FIXME: test returning a challenge string without \n, and ensure - * it gets printed as a prompt in its own right, without PuTTY - * making up a "Response: " prompt to follow it */ - return dupprintf("This is a dummy %s challenge!\n", - (method == AUTHMETHOD_TIS ? "TIS" : "CryptoCard")); -} -bool auth_ssh1int_response(AuthPolicy *ap, ptrlen response) -{ - return ptrlen_eq_string(response, "otter"); -} -bool auth_successful(AuthPolicy *ap, ptrlen username, unsigned method) -{ - return true; -} - -static void safety_warning(FILE *fp) -{ - fputs(" =================================================\n" - " THIS SSH SERVER IS NOT WRITTEN TO BE SECURE!\n" - " DO NOT DEPLOY IT IN A HOSTILE-FACING ENVIRONMENT!\n" - " =================================================\n", fp); -} - -static void show_help(FILE *fp) -{ - safety_warning(fp); - fputs("\n" - "usage: uppity [options]\n" - "options: --listen [PORT|PATH] listen to a port on localhost, or Unix socket\n" - " --listen-once (with --listen) stop after one " - "connection\n" - " --hostkey KEY SSH host key (need at least one)\n" - " --rsakexkey KEY key for SSH-2 RSA key exchange " - "(in SSH-1 format)\n" - " --userkey KEY public key" - " acceptable for user authentication\n" - " --sessiondir DIR cwd for session subprocess (default $HOME)\n" - " --bannertext TEXT send TEXT as SSH-2 auth banner\n" - " --bannerfile FILE send contents of FILE as SSH-2 auth " - "banner\n" - " --kexinit-kex STR override list of SSH-2 KEX methods\n" - " --kexinit-hostkey STR override list of SSH-2 host key " - "types\n" - " --kexinit-cscipher STR override list of SSH-2 " - "client->server ciphers\n" - " --kexinit-sccipher STR override list of SSH-2 " - "server->client ciphers\n" - " --kexinit-csmac STR override list of SSH-2 " - "client->server MACs\n" - " --kexinit-scmac STR override list of SSH-2 " - "server->client MACs\n" - " --kexinit-cscomp STR override list of SSH-2 " - "c->s compression types\n" - " --kexinit-sccomp STR override list of SSH-2 " - "s->c compression types\n" - " --ssh1-ciphers STR override list of SSH-1 ciphers\n" - " --ssh1-no-compression forbid compression in SSH-1\n" - " --exitsignum send buggy numeric \"exit-signal\" " - "message\n" - " --verbose print event log messages to standard " - "error\n" - " --sshlog FILE write SSH packet log to FILE\n" - " --sshrawlog FILE write SSH packets + raw data log" - " to FILE\n" - "also: uppity --help show this text\n" - " uppity --version show version information\n" - "\n", fp); - safety_warning(fp); -} - -static void show_version_and_exit(void) -{ - char *buildinfo_text = buildinfo("\n"); - printf("%s: %s\n%s\n", appname, ver, buildinfo_text); - sfree(buildinfo_text); - exit(0); -} - -const bool buildinfo_gtk_relevant = false; - -static bool listening = false, listen_once = false; -static bool finished = false; -void server_instance_terminated(LogPolicy *lp) -{ - struct server_instance *inst = container_of( - lp, struct server_instance, logpolicy); - - if (listening && !listen_once) { - log_to_stderr(inst->id, "connection terminated"); - } else { - finished = true; - } - - sfree(inst); -} - -static bool longoptarg(const char *arg, const char *expected, - const char **val, int *argcp, char ***argvp) -{ - int len = strlen(expected); - if (memcmp(arg, expected, len)) - return false; - if (arg[len] == '=') { - *val = arg + len + 1; - return true; - } else if (arg[len] == '\0') { - if (--*argcp > 0) { - *val = *++*argvp; - return true; - } else { - fprintf(stderr, "%s: option %s expects an argument\n", - appname, expected); - exit(1); - } - } - return false; -} - -static bool longoptnoarg(const char *arg, const char *expected) -{ - int len = strlen(expected); - if (memcmp(arg, expected, len)) - return false; - if (arg[len] == '=') { - fprintf(stderr, "%s: option %s expects no argument\n", - appname, expected); - exit(1); - } else if (arg[len] == '\0') { - return true; - } - return false; -} - -struct server_config { - Conf *conf; - const SshServerConfig *ssc; - - ssh_key **hostkeys; - int nhostkeys; - - RSAKey *hostkey1; - - struct AuthPolicyShared *ap_shared; - - unsigned next_id; - - Socket *listening_socket; - Plug listening_plug; -}; - -static Plug *server_conn_plug( - struct server_config *cfg, struct server_instance **inst_out) -{ - struct server_instance *inst = snew(struct server_instance); - - memset(inst, 0, sizeof(*inst)); - - inst->id = cfg->next_id++; - inst->ap.shared = cfg->ap_shared; - inst->logpolicy.vt = &server_logpolicy_vt; - - if (inst_out) - *inst_out = inst; - - return ssh_server_plug( - cfg->conf, cfg->ssc, cfg->hostkeys, cfg->nhostkeys, cfg->hostkey1, - &inst->ap, &inst->logpolicy, &unix_live_sftpserver_vt); -} - -static void server_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, - const char *error_msg, int error_code) -{ - log_to_stderr((unsigned)-1, error_msg); -} - -static void server_closing(Plug *plug, const char *error_msg, int error_code, - bool calling_back) -{ - log_to_stderr((unsigned)-1, error_msg); -} - -static int server_accepting(Plug *p, accept_fn_t constructor, accept_ctx_t ctx) -{ - struct server_config *cfg = container_of( - p, struct server_config, listening_plug); - Socket *s; - const char *err; - - struct server_instance *inst; - - if (listen_once) { - if (!cfg->listening_socket) /* in case of rapid double-accept */ - return 1; - sk_close(cfg->listening_socket); - cfg->listening_socket = NULL; - } - - unsigned old_next_id = cfg->next_id; - - Plug *plug = server_conn_plug(cfg, &inst); - s = constructor(ctx, plug); - if ((err = sk_socket_error(s)) != NULL) - return 1; - - SocketPeerInfo *pi = sk_peer_info(s); - - if (pi->addressfamily != ADDRTYPE_LOCAL && !sk_peer_trusted(s)) { - fprintf(stderr, "rejected connection from %s (untrustworthy peer)\n", - pi->log_text); - sk_free_peer_info(pi); - sk_close(s); - cfg->next_id = old_next_id; - return 1; - } - - char *msg = dupprintf("new connection from %s", pi->log_text); - log_to_stderr(inst->id, msg); - sfree(msg); - sk_free_peer_info(pi); - - sk_set_frozen(s, false); - ssh_server_start(plug, s); - return 0; -} - -static const PlugVtable server_plugvt = { - .log = server_log, - .closing = server_closing, - .accepting = server_accepting, -}; - -int main(int argc, char **argv) -{ - int listen_port = -1; - const char *listen_socket = NULL; - - ssh_key **hostkeys = NULL; - size_t nhostkeys = 0, hostkeysize = 0; - RSAKey *hostkey1 = NULL; - - struct AuthPolicyShared aps; - SshServerConfig ssc; - - Conf *conf = make_ssh_server_conf(); - - aps.ssh1keys = NULL; - aps.ssh2keys = NULL; - - memset(&ssc, 0, sizeof(ssc)); - - ssc.application_name = "Uppity"; - ssc.session_starting_dir = getenv("HOME"); - ssc.ssh1_cipher_mask = SSH1_SUPPORTED_CIPHER_MASK; - ssc.ssh1_allow_compression = true; - - if (argc <= 1) { - /* - * We're going to terminate with an error message below, - * because there are no host keys. But we'll display the help - * as additional standard-error output, if nothing else so - * that people see the giant safety warning. - */ - show_help(stderr); - fputc('\n', stderr); - } - - while (--argc > 0) { - const char *arg = *++argv; - const char *val; - - if (!strcmp(arg, "--help")) { - show_help(stdout); - exit(0); - } else if (longoptnoarg(arg, "--version")) { - show_version_and_exit(); - } else if (longoptnoarg(arg, "--verbose") || !strcmp(arg, "-v")) { - verbose = true; - } else if (longoptarg(arg, "--listen", &val, &argc, &argv)) { - if (val[0] == '/') { - listen_port = -1; - listen_socket = val; - } else { - listen_port = atoi(val); - listen_socket = NULL; - } - } else if (!strcmp(arg, "--listen-once")) { - listen_once = true; - } else if (longoptarg(arg, "--hostkey", &val, &argc, &argv)) { - Filename *keyfile; - int keytype; - const char *error; - - keyfile = filename_from_str(val); - keytype = key_type(keyfile); - - if (keytype == SSH_KEYTYPE_SSH2) { - ssh2_userkey *uk; - ssh_key *key; - uk = ppk_load_f(keyfile, NULL, &error); - filename_free(keyfile); - if (!uk || !uk->key) { - fprintf(stderr, "%s: unable to load host key '%s': " - "%s\n", appname, val, error); - exit(1); - } - char *invalid = ssh_key_invalid(uk->key, 0); - if (invalid) { - fprintf(stderr, "%s: host key '%s' is unusable: " - "%s\n", appname, val, invalid); - exit(1); - } - key = uk->key; - sfree(uk->comment); - sfree(uk); - - for (int i = 0; i < nhostkeys; i++) - if (ssh_key_alg(hostkeys[i]) == ssh_key_alg(key)) { - fprintf(stderr, "%s: host key '%s' duplicates key " - "type %s\n", appname, val, - ssh_key_alg(key)->ssh_id); - exit(1); - } - - sgrowarray(hostkeys, hostkeysize, nhostkeys); - hostkeys[nhostkeys++] = key; - } else if (keytype == SSH_KEYTYPE_SSH1) { - if (hostkey1) { - fprintf(stderr, "%s: host key '%s' is a redundant " - "SSH-1 host key\n", appname, val); - exit(1); - } - hostkey1 = snew(RSAKey); - if (!rsa1_load_f(keyfile, hostkey1, NULL, &error)) { - fprintf(stderr, "%s: unable to load host key '%s': " - "%s\n", appname, val, error); - exit(1); - } - } else { - fprintf(stderr, "%s: '%s' is not loadable as a " - "private key (%s)", appname, val, - key_type_to_str(keytype)); - exit(1); - } - } else if (longoptarg(arg, "--rsakexkey", &val, &argc, &argv)) { - Filename *keyfile; - int keytype; - const char *error; - - keyfile = filename_from_str(val); - keytype = key_type(keyfile); - - if (keytype != SSH_KEYTYPE_SSH1) { - fprintf(stderr, "%s: '%s' is not loadable as an SSH-1 format " - "private key (%s)", appname, val, - key_type_to_str(keytype)); - exit(1); - } - - if (ssc.rsa_kex_key) { - freersakey(ssc.rsa_kex_key); - } else { - ssc.rsa_kex_key = snew(RSAKey); - } - - if (!rsa1_load_f(keyfile, ssc.rsa_kex_key, NULL, &error)) { - fprintf(stderr, "%s: unable to load RSA kex key '%s': " - "%s\n", appname, val, error); - exit(1); - } - - ssc.rsa_kex_key->sshk.vt = &ssh_rsa; - } else if (longoptarg(arg, "--userkey", &val, &argc, &argv)) { - Filename *keyfile; - int keytype; - const char *error; - - keyfile = filename_from_str(val); - keytype = key_type(keyfile); - - if (keytype == SSH_KEYTYPE_SSH2_PUBLIC_RFC4716 || - keytype == SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH) { - strbuf *sb = strbuf_new(); - struct AuthPolicy_ssh2_pubkey *node; - void *blob; - - if (!ppk_loadpub_f(keyfile, NULL, BinarySink_UPCAST(sb), - NULL, &error)) { - fprintf(stderr, "%s: unable to load user key '%s': " - "%s\n", appname, val, error); - exit(1); - } - - node = snew_plus(struct AuthPolicy_ssh2_pubkey, sb->len); - blob = snew_plus_get_aux(node); - memcpy(blob, sb->u, sb->len); - node->public_blob = make_ptrlen(blob, sb->len); - - node->next = aps.ssh2keys; - aps.ssh2keys = node; - - strbuf_free(sb); - } else if (keytype == SSH_KEYTYPE_SSH1_PUBLIC) { - strbuf *sb = strbuf_new(); - BinarySource src[1]; - struct AuthPolicy_ssh1_pubkey *node; - - if (!rsa1_loadpub_f(keyfile, BinarySink_UPCAST(sb), - NULL, &error)) { - fprintf(stderr, "%s: unable to load user key '%s': " - "%s\n", appname, val, error); - exit(1); - } - - node = snew(struct AuthPolicy_ssh1_pubkey); - BinarySource_BARE_INIT(src, sb->u, sb->len); - get_rsa_ssh1_pub(src, &node->key, RSA_SSH1_EXPONENT_FIRST); - - node->next = aps.ssh1keys; - aps.ssh1keys = node; - - strbuf_free(sb); - } else { - fprintf(stderr, "%s: '%s' is not loadable as a public key " - "(%s)\n", appname, val, key_type_to_str(keytype)); - exit(1); - } - } else if (longoptarg(arg, "--bannerfile", &val, &argc, &argv)) { - FILE *fp = fopen(val, "r"); - if (!fp) { - fprintf(stderr, "%s: %s: open: %s\n", appname, - val, strerror(errno)); - exit(1); - } - strbuf *sb = strbuf_new(); - if (!read_file_into(BinarySink_UPCAST(sb), fp)) { - fprintf(stderr, "%s: %s: read: %s\n", appname, - val, strerror(errno)); - exit(1); - } - fclose(fp); - ssc.banner = ptrlen_from_strbuf(sb); - } else if (longoptarg(arg, "--bannertext", &val, &argc, &argv)) { - ssc.banner = ptrlen_from_asciz(val); - } else if (longoptarg(arg, "--sessiondir", &val, &argc, &argv)) { - ssc.session_starting_dir = val; - } else if (longoptarg(arg, "--kexinit-kex", &val, &argc, &argv)) { - ssc.kex_override[KEXLIST_KEX] = ptrlen_from_asciz(val); - } else if (longoptarg(arg, "--kexinit-hostkey", &val, &argc, &argv)) { - ssc.kex_override[KEXLIST_HOSTKEY] = ptrlen_from_asciz(val); - } else if (longoptarg(arg, "--kexinit-cscipher", &val, &argc, &argv)) { - ssc.kex_override[KEXLIST_CSCIPHER] = ptrlen_from_asciz(val); - } else if (longoptarg(arg, "--kexinit-csmac", &val, &argc, &argv)) { - ssc.kex_override[KEXLIST_CSMAC] = ptrlen_from_asciz(val); - } else if (longoptarg(arg, "--kexinit-cscomp", &val, &argc, &argv)) { - ssc.kex_override[KEXLIST_CSCOMP] = ptrlen_from_asciz(val); - } else if (longoptarg(arg, "--kexinit-sccipher", &val, &argc, &argv)) { - ssc.kex_override[KEXLIST_SCCIPHER] = ptrlen_from_asciz(val); - } else if (longoptarg(arg, "--kexinit-scmac", &val, &argc, &argv)) { - ssc.kex_override[KEXLIST_SCMAC] = ptrlen_from_asciz(val); - } else if (longoptarg(arg, "--kexinit-sccomp", &val, &argc, &argv)) { - ssc.kex_override[KEXLIST_SCCOMP] = ptrlen_from_asciz(val); - } else if (longoptarg(arg, "--ssh1-ciphers", &val, &argc, &argv)) { - ptrlen list = ptrlen_from_asciz(val); - ptrlen word; - unsigned long mask = 0; - while (word = ptrlen_get_word(&list, ","), word.len != 0) { - -#define SSH1_CIPHER_CASE(bitpos, name) \ - if (ptrlen_eq_string(word, name)) { \ - mask |= 1U << bitpos; \ - continue; \ - } - SSH1_SUPPORTED_CIPHER_LIST(SSH1_CIPHER_CASE); -#undef SSH1_CIPHER_CASE - - fprintf(stderr, "%s: unrecognised SSH-1 cipher '%.*s'\n", - appname, PTRLEN_PRINTF(word)); - exit(1); - } - ssc.ssh1_cipher_mask = mask; - } else if (longoptnoarg(arg, "--ssh1-no-compression")) { - ssc.ssh1_allow_compression = false; - } else if (longoptnoarg(arg, "--exitsignum")) { - ssc.exit_signal_numeric = true; - } else if (longoptarg(arg, "--sshlog", &val, &argc, &argv) || - longoptarg(arg, "-sshlog", &val, &argc, &argv)) { - Filename *logfile = filename_from_str(val); - conf_set_filename(conf, CONF_logfilename, logfile); - filename_free(logfile); - conf_set_int(conf, CONF_logtype, LGTYP_PACKETS); - conf_set_int(conf, CONF_logxfovr, LGXF_OVR); - } else if (longoptarg(arg, "--sshrawlog", &val, &argc, &argv) || - longoptarg(arg, "-sshrawlog", &val, &argc, &argv)) { - Filename *logfile = filename_from_str(val); - conf_set_filename(conf, CONF_logfilename, logfile); - filename_free(logfile); - conf_set_int(conf, CONF_logtype, LGTYP_SSHRAW); - conf_set_int(conf, CONF_logxfovr, LGXF_OVR); - } else if (!strcmp(arg, "--pretend-to-accept-any-pubkey")) { - ssc.stunt_pretend_to_accept_any_pubkey = true; - } else if (!strcmp(arg, "--open-unconditional-agent-socket")) { - ssc.stunt_open_unconditional_agent_socket = true; - } else { - fprintf(stderr, "%s: unrecognised option '%s'\n", appname, arg); - exit(1); - } - } - - if (nhostkeys == 0 && !hostkey1) { - fprintf(stderr, "%s: specify at least one host key\n", appname); - exit(1); - } - - random_ref(); - - /* - * Block SIGPIPE, so that we'll get EPIPE individually on - * particular network connections that go wrong. - */ - putty_signal(SIGPIPE, SIG_IGN); - - sk_init(); - uxsel_init(); - - struct server_config scfg; - scfg.conf = conf; - scfg.ssc = &ssc; - scfg.hostkeys = hostkeys; - scfg.nhostkeys = nhostkeys; - scfg.hostkey1 = hostkey1; - scfg.ap_shared = &aps; - scfg.next_id = 0; - - if (listen_port >= 0 || listen_socket) { - listening = true; - scfg.listening_plug.vt = &server_plugvt; - char *msg; - if (listen_port >= 0) { - scfg.listening_socket = sk_newlistener( - NULL, listen_port, &scfg.listening_plug, true, - ADDRTYPE_UNSPEC); - msg = dupprintf("%s: listening on port %d", - appname, listen_port); - } else { - SockAddr *addr = unix_sock_addr(listen_socket); - scfg.listening_socket = new_unix_listener( - addr, &scfg.listening_plug); - msg = dupprintf("%s: listening on Unix socket %s", - appname, listen_socket); - } - - log_to_stderr(-1, msg); - sfree(msg); - } else { - struct server_instance *inst; - Plug *plug = server_conn_plug(&scfg, &inst); - ssh_server_start(plug, make_fd_socket(0, 1, -1, plug)); - log_to_stderr(inst->id, "speaking SSH on stdio"); - } - - cli_main_loop(cliloop_no_pw_setup, cliloop_no_pw_check, - cliloop_always_continue, NULL); - - return 0; -} diff --git a/unix/uxsftp.c b/unix/uxsftp.c deleted file mode 100644 index 89a81c92..00000000 --- a/unix/uxsftp.c +++ /dev/null @@ -1,578 +0,0 @@ -/* - * uxsftp.c: the Unix-specific parts of PSFTP and PSCP. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "putty.h" -#include "ssh.h" -#include "psftp.h" - -#if HAVE_GLOB_H -#include -#endif - -char *x_get_default(const char *key) -{ - return NULL; /* this is a stub */ -} - -void platform_get_x11_auth(struct X11Display *display, Conf *conf) -{ - /* Do nothing, therefore no auth. */ -} -const bool platform_uses_x11_unix_by_default = true; - -/* - * Default settings that are specific to PSFTP. - */ -char *platform_default_s(const char *name) -{ - return NULL; -} - -bool platform_default_b(const char *name, bool def) -{ - return def; -} - -int platform_default_i(const char *name, int def) -{ - return def; -} - -FontSpec *platform_default_fontspec(const char *name) -{ - return fontspec_new(""); -} - -Filename *platform_default_filename(const char *name) -{ - if (!strcmp(name, "LogFileName")) - return filename_from_str("putty.log"); - else - return filename_from_str(""); -} - -int filexfer_get_userpass_input(Seat *seat, prompts_t *p, bufchain *input) -{ - int ret; - ret = cmdline_get_passwd_input(p); - if (ret == -1) - ret = console_get_userpass_input(p); - 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; - size_t 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. - */ - sgrowarray(buffer, size, size); - } -} - -struct RFile { - int fd; -}; - -RFile *open_existing_file(const char *name, uint64_t *size, - unsigned long *mtime, unsigned long *atime, - long *perms) -{ - int fd; - RFile *ret; - - fd = open(name, O_RDONLY); - if (fd < 0) - return NULL; - - ret = snew(RFile); - ret->fd = fd; - - if (size || mtime || atime || perms) { - struct stat statbuf; - if (fstat(fd, &statbuf) < 0) { - fprintf(stderr, "%s: stat: %s\n", name, strerror(errno)); - memset(&statbuf, 0, sizeof(statbuf)); - } - - if (size) - *size = statbuf.st_size; - - if (mtime) - *mtime = statbuf.st_mtime; - - if (atime) - *atime = statbuf.st_atime; - - if (perms) - *perms = statbuf.st_mode; - } - - 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(const char *name, long perms) -{ - int fd; - WFile *ret; - - fd = open(name, O_CREAT | O_TRUNC | O_WRONLY, - (mode_t)(perms ? perms : 0666)); - if (fd < 0) - return NULL; - - ret = snew(WFile); - ret->fd = fd; - ret->name = dupstr(name); - - return ret; -} - - -WFile *open_existing_wfile(const char *name, uint64_t *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 = 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_t offset, int whence) -{ - int lseek_whence; - - 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, offset, lseek_whence) >= 0 ? 0 : -1; -} - -uint64_t get_file_posn(WFile *f) -{ - return lseek(f->fd, (off_t) 0, SEEK_CUR); -} - -int file_type(const 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(const char *name, const char **errmsg) -{ - DIR *dir; - DirHandle *ret; - - dir = opendir(name); - if (!dir) { - *errmsg = strerror(errno); - 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(const char *name, bool 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 { -#if HAVE_GLOB_H - 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; -#else - /* On a system without glob.h, we just have to return a - * failure code */ - return WCTYPE_NONEXISTENT; -#endif - } -} - -/* - * Actually return matching file names for a local wildcard. - */ -#if HAVE_GLOB_H -struct WildcardMatcher { - glob_t globbed; - int i; -}; -WildcardMatcher *begin_wildcard_matching(const 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); -} -#else -WildcardMatcher *begin_wildcard_matching(const char *name) -{ - return NULL; -} -char *wildcard_get_filename(WildcardMatcher *dir) -{ - unreachable("Can't construct a valid WildcardMatcher without "); -} -void finish_wildcard_matching(WildcardMatcher *dir) -{ - unreachable("Can't construct a valid WildcardMatcher without "); -} -#endif - -char *stripslashes(const char *str, bool local) -{ - char *p; - - /* - * On Unix, we do the same thing regardless of the 'local' - * parameter. - */ - p = strrchr(str, '/'); - if (p) str = p+1; - - return (char *)str; -} - -bool vet_filename(const char *name) -{ - if (strchr(name, '/')) - return false; - - if (name[0] == '.' && (!name[1] || (name[1] == '.' && !name[2]))) - return false; - - return true; -} - -bool create_directory(const char *name) -{ - return mkdir(name, 0777) == 0; -} - -char *dir_file_cat(const char *dir, const char *file) -{ - ptrlen dir_pl = ptrlen_from_asciz(dir); - return dupcat( - dir, ptrlen_endswith(dir_pl, PTRLEN_LITERAL("/"), NULL) ? "" : "/", - file); -} - -/* - * Do a select() between all currently active network fds and - * optionally stdin, using cli_main_loop. - */ - -struct ssh_sftp_mainloop_ctx { - bool include_stdin, no_fds_ok; - int toret; -}; -static bool ssh_sftp_pw_setup(void *vctx, pollwrapper *pw) -{ - struct ssh_sftp_mainloop_ctx *ctx = (struct ssh_sftp_mainloop_ctx *)vctx; - int fdstate, rwx; - - if (!ctx->no_fds_ok && !toplevel_callback_pending() && - first_fd(&fdstate, &rwx) < 0) { - ctx->toret = -1; - return false; /* terminate cli_main_loop */ - } - - if (ctx->include_stdin) - pollwrap_add_fd_rwx(pw, 0, SELECT_R); - - return true; -} -static void ssh_sftp_pw_check(void *vctx, pollwrapper *pw) -{ - struct ssh_sftp_mainloop_ctx *ctx = (struct ssh_sftp_mainloop_ctx *)vctx; - - if (ctx->include_stdin && pollwrap_check_fd_rwx(pw, 0, SELECT_R)) - ctx->toret = 1; -} -static bool ssh_sftp_mainloop_continue(void *vctx, bool found_any_fd, - bool ran_any_callback) -{ - struct ssh_sftp_mainloop_ctx *ctx = (struct ssh_sftp_mainloop_ctx *)vctx; - if (ctx->toret != 0 || found_any_fd || ran_any_callback) - return false; /* finish the loop */ - return true; -} -static int ssh_sftp_do_select(bool include_stdin, bool no_fds_ok) -{ - struct ssh_sftp_mainloop_ctx ctx[1]; - ctx->include_stdin = include_stdin; - ctx->no_fds_ok = no_fds_ok; - ctx->toret = 0; - - cli_main_loop(ssh_sftp_pw_setup, ssh_sftp_pw_check, - ssh_sftp_mainloop_continue, ctx); - - return ctx->toret; -} - -/* - * 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(const char *prompt, bool no_fds_ok) -{ - char *buf; - size_t buflen, bufsize; - int 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"); - sfree(buf); - return NULL; /* woop woop */ - } - if (ret > 0) { - sgrowarray(buf, bufsize, buflen); - ret = read(0, buf+buflen, 1); - if (ret < 0) { - perror("read"); - sfree(buf); - return NULL; - } - if (ret == 0) { - /* eof on stdin; no error, but no answer either */ - sfree(buf); - return NULL; - } - - if (buf[buflen++] == '\n') { - /* we have a full line */ - return buf; - } - } - } -} - -void frontend_net_error_pending(void) {} - -void platform_psftp_pre_conn_setup(LogPolicy *lp) {} - -const bool buildinfo_gtk_relevant = false; - -/* - * Main program: do platform-specific initialisation and then call - * psftp_main(). - */ -int main(int argc, char *argv[]) -{ - uxsel_init(); - return psftp_main(argc, argv); -} diff --git a/unix/uxsftpserver.c b/unix/uxsftpserver.c deleted file mode 100644 index 7257c5c9..00000000 --- a/unix/uxsftpserver.c +++ /dev/null @@ -1,703 +0,0 @@ -/* - * Implement the SftpServer abstraction, in the 'live' form (i.e. - * really operating on the Unix filesystem). - */ - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "putty.h" -#include "ssh.h" -#include "ssh/server.h" -#include "ssh/sftp.h" -#include "tree234.h" - -typedef struct UnixSftpServer UnixSftpServer; - -struct UnixSftpServer { - unsigned *fdseqs; - bool *fdsopen; - size_t fdsize; - - tree234 *dirhandles; - int last_dirhandle_index; - - char handlekey[8]; - - SftpServer srv; -}; - -struct uss_dirhandle { - int index; - DIR *dp; -}; - -#define USS_DIRHANDLE_SEQ (0xFFFFFFFFU) - -static int uss_dirhandle_cmp(void *av, void *bv) -{ - struct uss_dirhandle *a = (struct uss_dirhandle *)av; - struct uss_dirhandle *b = (struct uss_dirhandle *)bv; - if (a->index < b->index) - return -1; - if (a->index > b->index) - return +1; - return 0; -} - -static SftpServer *uss_new(const SftpServerVtable *vt) -{ - UnixSftpServer *uss = snew(UnixSftpServer); - - memset(uss, 0, sizeof(UnixSftpServer)); - - uss->dirhandles = newtree234(uss_dirhandle_cmp); - uss->srv.vt = vt; - - make_unix_sftp_filehandle_key(uss->handlekey, sizeof(uss->handlekey)); - - return &uss->srv; -} - -static void uss_free(SftpServer *srv) -{ - UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv); - struct uss_dirhandle *udh; - - for (size_t i = 0; i < uss->fdsize; i++) - if (uss->fdsopen[i]) - close(i); - sfree(uss->fdseqs); - - while ((udh = delpos234(uss->dirhandles, 0)) != NULL) { - closedir(udh->dp); - sfree(udh); - } - - sfree(uss); -} - -static void uss_return_handle_raw( - UnixSftpServer *uss, SftpReplyBuilder *reply, int index, unsigned seq) -{ - unsigned char handlebuf[8]; - PUT_32BIT_MSB_FIRST(handlebuf, index); - PUT_32BIT_MSB_FIRST(handlebuf + 4, seq); - des_encrypt_xdmauth(uss->handlekey, handlebuf, 8); - fxp_reply_handle(reply, make_ptrlen(handlebuf, 8)); -} - -static bool uss_decode_handle( - UnixSftpServer *uss, ptrlen handle, int *index, unsigned *seq) -{ - unsigned char handlebuf[8]; - - if (handle.len != 8) - return false; - memcpy(handlebuf, handle.ptr, 8); - des_decrypt_xdmauth(uss->handlekey, handlebuf, 8); - *index = toint(GET_32BIT_MSB_FIRST(handlebuf)); - *seq = GET_32BIT_MSB_FIRST(handlebuf + 4); - return true; -} - -static void uss_return_new_handle( - UnixSftpServer *uss, SftpReplyBuilder *reply, int fd) -{ - assert(fd >= 0); - if (fd >= uss->fdsize) { - size_t old_size = uss->fdsize; - sgrowarray(uss->fdseqs, uss->fdsize, fd); - uss->fdsopen = sresize(uss->fdsopen, uss->fdsize, bool); - while (old_size < uss->fdsize) { - uss->fdseqs[old_size] = 0; - uss->fdsopen[old_size] = false; - old_size++; - } - } - assert(!uss->fdsopen[fd]); - uss->fdsopen[fd] = true; - if (++uss->fdseqs[fd] == USS_DIRHANDLE_SEQ) - uss->fdseqs[fd] = 0; - uss_return_handle_raw(uss, reply, fd, uss->fdseqs[fd]); -} - -static int uss_try_lookup_fd(UnixSftpServer *uss, ptrlen handle) -{ - int fd; - unsigned seq; - if (!uss_decode_handle(uss, handle, &fd, &seq) || - fd < 0 || fd >= uss->fdsize || - !uss->fdsopen[fd] || uss->fdseqs[fd] != seq) - return -1; - - return fd; -} - -static int uss_lookup_fd(UnixSftpServer *uss, SftpReplyBuilder *reply, - ptrlen handle) -{ - int fd = uss_try_lookup_fd(uss, handle); - if (fd < 0) - fxp_reply_error(reply, SSH_FX_FAILURE, "invalid file handle"); - return fd; -} - -static void uss_return_new_dirhandle( - UnixSftpServer *uss, SftpReplyBuilder *reply, DIR *dp) -{ - struct uss_dirhandle *udh = snew(struct uss_dirhandle); - udh->index = uss->last_dirhandle_index++; - udh->dp = dp; - struct uss_dirhandle *added = add234(uss->dirhandles, udh); - assert(added == udh); - uss_return_handle_raw(uss, reply, udh->index, USS_DIRHANDLE_SEQ); -} - -static struct uss_dirhandle *uss_try_lookup_dirhandle( - UnixSftpServer *uss, ptrlen handle) -{ - struct uss_dirhandle key, *udh; - unsigned seq; - - if (!uss_decode_handle(uss, handle, &key.index, &seq) || - seq != USS_DIRHANDLE_SEQ || - (udh = find234(uss->dirhandles, &key, NULL)) == NULL) - return NULL; - - return udh; -} - -static struct uss_dirhandle *uss_lookup_dirhandle( - UnixSftpServer *uss, SftpReplyBuilder *reply, ptrlen handle) -{ - struct uss_dirhandle *udh = uss_try_lookup_dirhandle(uss, handle); - if (!udh) - fxp_reply_error(reply, SSH_FX_FAILURE, "invalid file handle"); - return udh; -} - -static void uss_error(UnixSftpServer *uss, SftpReplyBuilder *reply) -{ - unsigned code = SSH_FX_FAILURE; - switch (errno) { - case ENOENT: - code = SSH_FX_NO_SUCH_FILE; - break; - case EPERM: - code = SSH_FX_PERMISSION_DENIED; - break; - } - fxp_reply_error(reply, code, strerror(errno)); -} - -static void uss_realpath(SftpServer *srv, SftpReplyBuilder *reply, - ptrlen path) -{ - UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv); - - char *inpath = mkstr(path); - char *outpath = realpath(inpath, NULL); - free(inpath); - - if (!outpath) { - uss_error(uss, reply); - } else { - fxp_reply_simple_name(reply, ptrlen_from_asciz(outpath)); - free(outpath); - } -} - -static void uss_open(SftpServer *srv, SftpReplyBuilder *reply, - ptrlen path, unsigned flags, struct fxp_attrs attrs) -{ - UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv); - - int openflags = 0; - if (!((SSH_FXF_READ | SSH_FXF_WRITE) &~ flags)) - openflags |= O_RDWR; - else if (flags & SSH_FXF_WRITE) - openflags |= O_WRONLY; - else if (flags & SSH_FXF_READ) - openflags |= O_RDONLY; - if (flags & SSH_FXF_APPEND) - openflags |= O_APPEND; - if (flags & SSH_FXF_CREAT) - openflags |= O_CREAT; - if (flags & SSH_FXF_TRUNC) - openflags |= O_TRUNC; - if (flags & SSH_FXF_EXCL) - openflags |= O_EXCL; - - char *pathstr = mkstr(path); - int fd = open(pathstr, openflags, GET_PERMISSIONS(attrs, 0777)); - free(pathstr); - - if (fd < 0) { - uss_error(uss, reply); - } else { - uss_return_new_handle(uss, reply, fd); - } -} - -static void uss_opendir(SftpServer *srv, SftpReplyBuilder *reply, - ptrlen path) -{ - UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv); - - char *pathstr = mkstr(path); - DIR *dp = opendir(pathstr); - free(pathstr); - - if (!dp) { - uss_error(uss, reply); - } else { - uss_return_new_dirhandle(uss, reply, dp); - } -} - -static void uss_close(SftpServer *srv, SftpReplyBuilder *reply, - ptrlen handle) -{ - UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv); - int fd; - struct uss_dirhandle *udh; - - if ((udh = uss_try_lookup_dirhandle(uss, handle)) != NULL) { - closedir(udh->dp); - del234(uss->dirhandles, udh); - sfree(udh); - fxp_reply_ok(reply); - } else if ((fd = uss_lookup_fd(uss, reply, handle)) >= 0) { - close(fd); - assert(0 <= fd && fd <= uss->fdsize); - uss->fdsopen[fd] = false; - fxp_reply_ok(reply); - } - /* if both failed, uss_lookup_fd will have filled in an error response */ -} - -static void uss_mkdir(SftpServer *srv, SftpReplyBuilder *reply, - ptrlen path, struct fxp_attrs attrs) -{ - UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv); - - char *pathstr = mkstr(path); - int status = mkdir(pathstr, GET_PERMISSIONS(attrs, 0777)); - free(pathstr); - - if (status < 0) { - uss_error(uss, reply); - } else { - fxp_reply_ok(reply); - } -} - -static void uss_rmdir(SftpServer *srv, SftpReplyBuilder *reply, ptrlen path) -{ - UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv); - - char *pathstr = mkstr(path); - int status = rmdir(pathstr); - free(pathstr); - - if (status < 0) { - uss_error(uss, reply); - } else { - fxp_reply_ok(reply); - } -} - -static void uss_remove(SftpServer *srv, SftpReplyBuilder *reply, - ptrlen path) -{ - UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv); - - char *pathstr = mkstr(path); - int status = unlink(pathstr); - free(pathstr); - - if (status < 0) { - uss_error(uss, reply); - } else { - fxp_reply_ok(reply); - } -} - -static void uss_rename(SftpServer *srv, SftpReplyBuilder *reply, - ptrlen srcpath, ptrlen dstpath) -{ - UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv); - - char *srcstr = mkstr(srcpath), *dststr = mkstr(dstpath); - int status = rename(srcstr, dststr); - free(srcstr); - free(dststr); - - if (status < 0) { - uss_error(uss, reply); - } else { - fxp_reply_ok(reply); - } -} - -static struct fxp_attrs uss_translate_struct_stat(const struct stat *st) -{ - struct fxp_attrs attrs; - - attrs.flags = (SSH_FILEXFER_ATTR_SIZE | - SSH_FILEXFER_ATTR_PERMISSIONS | - SSH_FILEXFER_ATTR_UIDGID | - SSH_FILEXFER_ATTR_ACMODTIME); - - attrs.size = st->st_size; - attrs.permissions = st->st_mode; - attrs.uid = st->st_uid; - attrs.gid = st->st_gid; - attrs.atime = st->st_atime; - attrs.mtime = st->st_mtime; - - return attrs; -} - -static void uss_reply_struct_stat(SftpReplyBuilder *reply, - const struct stat *st) -{ - fxp_reply_attrs(reply, uss_translate_struct_stat(st)); -} - -static void uss_stat(SftpServer *srv, SftpReplyBuilder *reply, - ptrlen path, bool follow_symlinks) -{ - UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv); - struct stat st; - - char *pathstr = mkstr(path); - int status = (follow_symlinks ? stat : lstat) (pathstr, &st); - free(pathstr); - - if (status < 0) { - uss_error(uss, reply); - } else { - uss_reply_struct_stat(reply, &st); - } -} - -static void uss_fstat(SftpServer *srv, SftpReplyBuilder *reply, - ptrlen handle) -{ - UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv); - struct stat st; - int fd; - - if ((fd = uss_lookup_fd(uss, reply, handle)) < 0) - return; - int status = fstat(fd, &st); - - if (status < 0) { - uss_error(uss, reply); - } else { - uss_reply_struct_stat(reply, &st); - } -} - -#if !HAVE_FUTIMES -static inline int futimes(int fd, const struct timeval tv[2]) -{ - /* If the OS doesn't support futimes(3) then we have to pretend it - * always returns failure */ - errno = EINVAL; - return -1; -} -#endif - -/* - * The guts of setstat and fsetstat, macroised so that they can call - * fchown(fd,...) or chown(path,...) depending on parameters. - */ -#define SETSTAT_GUTS(api_prefix, api_arg, attrs, success) do \ - { \ - if (attrs.flags & SSH_FILEXFER_ATTR_SIZE) \ - if (api_prefix(truncate)(api_arg, attrs.size) < 0) \ - success = false; \ - if (attrs.flags & SSH_FILEXFER_ATTR_UIDGID) \ - if (api_prefix(chown)(api_arg, attrs.uid, attrs.gid) < 0) \ - success = false; \ - if (attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) \ - if (api_prefix(chmod)(api_arg, attrs.permissions) < 0) \ - success = false; \ - if (attrs.flags & SSH_FILEXFER_ATTR_ACMODTIME) { \ - struct timeval tv[2]; \ - tv[0].tv_sec = attrs.atime; \ - tv[1].tv_sec = attrs.mtime; \ - tv[0].tv_usec = tv[1].tv_usec = 0; \ - if (api_prefix(utimes)(api_arg, tv) < 0) \ - success = false; \ - } \ - } while (0) - -#define PATH_PREFIX(func) func -#define FD_PREFIX(func) f ## func - -static void uss_setstat(SftpServer *srv, SftpReplyBuilder *reply, - ptrlen path, struct fxp_attrs attrs) -{ - UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv); - - char *pathstr = mkstr(path); - bool success = true; - SETSTAT_GUTS(PATH_PREFIX, pathstr, attrs, success); - free(pathstr); - - if (!success) { - uss_error(uss, reply); - } else { - fxp_reply_ok(reply); - } -} - -static void uss_fsetstat(SftpServer *srv, SftpReplyBuilder *reply, - ptrlen handle, struct fxp_attrs attrs) -{ - UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv); - int fd; - - if ((fd = uss_lookup_fd(uss, reply, handle)) < 0) - return; - - bool success = true; - SETSTAT_GUTS(FD_PREFIX, fd, attrs, success); - - if (!success) { - uss_error(uss, reply); - } else { - fxp_reply_ok(reply); - } -} - -static void uss_read(SftpServer *srv, SftpReplyBuilder *reply, - ptrlen handle, uint64_t offset, unsigned length) -{ - UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv); - int fd; - char *buf; - - if ((fd = uss_lookup_fd(uss, reply, handle)) < 0) - return; - - if ((buf = malloc(length)) == NULL) { - /* A rare case in which I bother to check malloc failure, - * because in this case we can localise the problem easily by - * turning it into a failure response from this one sftp - * request */ - fxp_reply_error(reply, SSH_FX_FAILURE, - "Out of memory for read buffer"); - return; - } - - char *p = buf; - - int status = lseek(fd, offset, SEEK_SET); - if (status >= 0 || errno == ESPIPE) { - bool seekable = (status >= 0); - while (length > 0) { - status = read(fd, p, length); - if (status <= 0) - break; - - unsigned bytes_read = status; - assert(bytes_read <= length); - length -= bytes_read; - p += bytes_read; - - if (!seekable) { - /* - * If the seek failed because the file is fundamentally - * not a seekable kind of thing, abandon this loop after - * one attempt, i.e. we just read whatever we could get - * and we don't mind returning a short buffer. - */ - } - } - } - - if (status < 0) { - uss_error(uss, reply); - } else if (p == buf) { - fxp_reply_error(reply, SSH_FX_EOF, "End of file"); - } else { - fxp_reply_data(reply, make_ptrlen(buf, p - buf)); - } - - free(buf); -} - -static void uss_write(SftpServer *srv, SftpReplyBuilder *reply, - ptrlen handle, uint64_t offset, ptrlen data) -{ - UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv); - int fd; - - if ((fd = uss_lookup_fd(uss, reply, handle)) < 0) - return; - - const char *p = data.ptr; - unsigned length = data.len; - - int status = lseek(fd, offset, SEEK_SET); - if (status >= 0 || errno == ESPIPE) { - - while (length > 0) { - status = write(fd, p, length); - assert(status != 0); - if (status < 0) - break; - - unsigned bytes_written = status; - assert(bytes_written <= length); - length -= bytes_written; - p += bytes_written; - } - } - - if (status < 0) { - uss_error(uss, reply); - } else { - fxp_reply_ok(reply); - } -} - -static void uss_readdir(SftpServer *srv, SftpReplyBuilder *reply, - ptrlen handle, int max_entries, bool omit_longname) -{ - UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv); - struct dirent *de; - struct uss_dirhandle *udh; - - if ((udh = uss_lookup_dirhandle(uss, reply, handle)) == NULL) - return; - - errno = 0; - de = readdir(udh->dp); - if (!de) { - if (errno == 0) { - fxp_reply_error(reply, SSH_FX_EOF, "End of directory"); - } else { - uss_error(uss, reply); - } - } else { - ptrlen longname = PTRLEN_LITERAL(""); - char *longnamebuf = NULL; - struct fxp_attrs attrs = no_attrs; - -#if defined HAVE_FSTATAT && defined HAVE_DIRFD - struct stat st; - if (!fstatat(dirfd(udh->dp), de->d_name, &st, AT_SYMLINK_NOFOLLOW)) { - char perms[11], *uidbuf = NULL, *gidbuf = NULL; - struct passwd *pwd; - struct group *grp; - const char *user, *group; - struct tm tm; - - attrs = uss_translate_struct_stat(&st); - - if (!omit_longname) { - - strcpy(perms, "----------"); - switch (st.st_mode & S_IFMT) { - case S_IFBLK: perms[0] = 'b'; break; - case S_IFCHR: perms[0] = 'c'; break; - case S_IFDIR: perms[0] = 'd'; break; - case S_IFIFO: perms[0] = 'p'; break; - case S_IFLNK: perms[0] = 'l'; break; - case S_IFSOCK: perms[0] = 's'; break; - } - if (st.st_mode & S_IRUSR) - perms[1] = 'r'; - if (st.st_mode & S_IWUSR) - perms[2] = 'w'; - if (st.st_mode & S_IXUSR) - perms[3] = (st.st_mode & S_ISUID ? 's' : 'x'); - else - perms[3] = (st.st_mode & S_ISUID ? 'S' : '-'); - if (st.st_mode & S_IRGRP) - perms[4] = 'r'; - if (st.st_mode & S_IWGRP) - perms[5] = 'w'; - if (st.st_mode & S_IXGRP) - perms[6] = (st.st_mode & S_ISGID ? 's' : 'x'); - else - perms[6] = (st.st_mode & S_ISGID ? 'S' : '-'); - if (st.st_mode & S_IROTH) - perms[7] = 'r'; - if (st.st_mode & S_IWOTH) - perms[8] = 'w'; - if (st.st_mode & S_IXOTH) - perms[9] = 'x'; - - if ((pwd = getpwuid(st.st_uid)) != NULL) - user = pwd->pw_name; - else - user = uidbuf = dupprintf("%u", (unsigned)st.st_uid); - if ((grp = getgrgid(st.st_gid)) != NULL) - group = grp->gr_name; - else - group = gidbuf = dupprintf("%u", (unsigned)st.st_gid); - - tm = *localtime(&st.st_mtime); - - longnamebuf = dupprintf( - "%s %3u %-8s %-8s %8"PRIuMAX" %.3s %2d %02d:%02d %s", - perms, (unsigned)st.st_nlink, user, group, - (uintmax_t)st.st_size, - (&"JanFebMarAprMayJunJulAugSepOctNovDec"[3*tm.tm_mon]), - tm.tm_mday, tm.tm_hour, tm.tm_min, de->d_name); - longname = ptrlen_from_asciz(longnamebuf); - - sfree(uidbuf); - sfree(gidbuf); - } - } -#endif - - /* FIXME: be able to return more than one, in which case we - * must also check max_entries */ - fxp_reply_name_count(reply, 1); - fxp_reply_full_name(reply, ptrlen_from_asciz(de->d_name), - longname, attrs); - - sfree(longnamebuf); - } -} - -const SftpServerVtable unix_live_sftpserver_vt = { - .new = uss_new, - .free = uss_free, - .realpath = uss_realpath, - .open = uss_open, - .opendir = uss_opendir, - .close = uss_close, - .mkdir = uss_mkdir, - .rmdir = uss_rmdir, - .remove = uss_remove, - .rename = uss_rename, - .stat = uss_stat, - .fstat = uss_fstat, - .setstat = uss_setstat, - .fsetstat = uss_fsetstat, - .read = uss_read, - .write = uss_write, - .readdir = uss_readdir, -}; diff --git a/unix/uxshare.c b/unix/uxshare.c deleted file mode 100644 index f1ef2019..00000000 --- a/unix/uxshare.c +++ /dev/null @@ -1,370 +0,0 @@ -/* - * Unix implementation of SSH connection-sharing IPC setup. - */ - -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include "tree234.h" -#include "putty.h" -#include "network.h" -#include "proxy.h" -#include "ssh.h" - -#define CONNSHARE_SOCKETDIR_PREFIX "/tmp/putty-connshare" -#define SALT_FILENAME "salt" -#define SALT_SIZE 64 -#ifndef PIPE_BUF -#define PIPE_BUF _POSIX_PIPE_BUF -#endif - -static char *make_parentdir_name(void) -{ - char *username, *parent; - - username = get_username(); - parent = dupprintf("%s.%s", CONNSHARE_SOCKETDIR_PREFIX, username); - sfree(username); - assert(*parent == '/'); - - return parent; -} - -static char *make_dirname(const char *pi_name, char **logtext) -{ - char *name, *parentdirname, *dirname, *err; - - /* - * First, create the top-level directory for all shared PuTTY - * connections owned by this user. - */ - parentdirname = make_parentdir_name(); - if ((err = make_dir_and_check_ours(parentdirname)) != NULL) { - *logtext = err; - sfree(parentdirname); - return NULL; - } - - /* - * Transform the platform-independent version of the connection - * identifier into the name we'll actually use for the directory - * containing the Unix socket. - * - * We do this by hashing the identifier with some user-specific - * secret information, to avoid the privacy leak of having - * "user@host" strings show up in 'netstat -x'. (Irritatingly, the - * full pathname of a Unix-domain socket _does_ show up in the - * 'netstat -x' output, at least on Linux, even if that socket is - * in a directory not readable to the user running netstat. You'd - * think putting things inside an 0700 directory would hide their - * names from other users, but no.) - * - * The secret information we use to salt the hash lives in a file - * inside the top-level directory we just created, so we must - * first create that file (with some fresh random data in it) if - * it's not already been done by a previous PuTTY. - */ - { - unsigned char saltbuf[SALT_SIZE]; - char *saltname; - int saltfd, i, ret; - - saltname = dupprintf("%s/%s", parentdirname, SALT_FILENAME); - saltfd = open(saltname, O_RDONLY); - if (saltfd < 0) { - char *tmpname; - int pid; - - if (errno != ENOENT) { - *logtext = dupprintf("%s: open: %s", saltname, - strerror(errno)); - sfree(saltname); - sfree(parentdirname); - return NULL; - } - - /* - * The salt file doesn't already exist, so try to create - * it. Another process may be attempting the same thing - * simultaneously, so we must do this carefully: we write - * a salt file under a different name, then hard-link it - * into place, which guarantees that we won't change the - * contents of an existing salt file. - */ - pid = getpid(); - for (i = 0;; i++) { - tmpname = dupprintf("%s/%s.tmp.%d.%d", - parentdirname, SALT_FILENAME, pid, i); - saltfd = open(tmpname, O_WRONLY | O_EXCL | O_CREAT, 0400); - if (saltfd >= 0) - break; - if (errno != EEXIST) { - *logtext = dupprintf("%s: open: %s", tmpname, - strerror(errno)); - sfree(tmpname); - sfree(saltname); - sfree(parentdirname); - return NULL; - } - sfree(tmpname); /* go round and try again with i+1 */ - } - /* - * Invent some random data. - */ - random_read(saltbuf, SALT_SIZE); - ret = write(saltfd, saltbuf, SALT_SIZE); - /* POSIX atomicity guarantee: because we wrote less than - * PIPE_BUF bytes, the write either completed in full or - * failed. */ - assert(SALT_SIZE < PIPE_BUF); - assert(ret < 0 || ret == SALT_SIZE); - if (ret < 0) { - close(saltfd); - *logtext = dupprintf("%s: write: %s", tmpname, - strerror(errno)); - sfree(tmpname); - sfree(saltname); - sfree(parentdirname); - return NULL; - } - if (close(saltfd) < 0) { - *logtext = dupprintf("%s: close: %s", tmpname, - strerror(errno)); - sfree(tmpname); - sfree(saltname); - sfree(parentdirname); - return NULL; - } - - /* - * Now attempt to hard-link our temp file into place. We - * tolerate EEXIST as an outcome, because that just means - * another PuTTY got their attempt in before we did (and - * we only care that there is a valid salt file we can - * agree on, no matter who created it). - */ - if (link(tmpname, saltname) < 0 && errno != EEXIST) { - *logtext = dupprintf("%s: link: %s", saltname, - strerror(errno)); - sfree(tmpname); - sfree(saltname); - sfree(parentdirname); - return NULL; - } - - /* - * Whether that succeeded or not, get rid of our temp file. - */ - if (unlink(tmpname) < 0) { - *logtext = dupprintf("%s: unlink: %s", tmpname, - strerror(errno)); - sfree(tmpname); - sfree(saltname); - sfree(parentdirname); - return NULL; - } - - /* - * And now we've arranged for there to be a salt file, so - * we can try to open it for reading again and this time - * expect it to work. - */ - sfree(tmpname); - - saltfd = open(saltname, O_RDONLY); - if (saltfd < 0) { - *logtext = dupprintf("%s: open: %s", saltname, - strerror(errno)); - sfree(saltname); - sfree(parentdirname); - return NULL; - } - } - - for (i = 0; i < SALT_SIZE; i++) { - ret = read(saltfd, saltbuf, SALT_SIZE); - if (ret <= 0) { - close(saltfd); - *logtext = dupprintf("%s: read: %s", saltname, - ret == 0 ? "unexpected EOF" : - strerror(errno)); - sfree(saltname); - sfree(parentdirname); - return NULL; - } - assert(0 < ret && ret <= SALT_SIZE - i); - i += ret; - } - - close(saltfd); - sfree(saltname); - - /* - * Now we've got our salt, hash it with the connection - * identifier to produce our actual socket name. - */ - { - unsigned char digest[32]; - char retbuf[65]; - - ssh_hash *h = ssh_hash_new(&ssh_sha256); - put_string(h, saltbuf, SALT_SIZE); - put_stringz(h, pi_name); - ssh_hash_final(h, digest); - - /* - * And make it printable. - */ - for (i = 0; i < 32; i++) { - sprintf(retbuf + 2*i, "%02x", digest[i]); - /* the last of those will also write the trailing NUL */ - } - - name = dupstr(retbuf); - } - - smemclr(saltbuf, sizeof(saltbuf)); - } - - dirname = dupprintf("%s/%s", parentdirname, name); - sfree(parentdirname); - sfree(name); - - return dirname; -} - -int platform_ssh_share(const char *pi_name, Conf *conf, - Plug *downplug, Plug *upplug, Socket **sock, - char **logtext, char **ds_err, char **us_err, - bool can_upstream, bool can_downstream) -{ - char *dirname, *lockname, *sockname, *err; - int lockfd; - Socket *retsock; - - /* - * Sort out what we're going to call the directory in which we - * keep the socket. This has the side effect of potentially - * creating its top-level containing dir and/or the salt file - * within that, if they don't already exist. - */ - dirname = make_dirname(pi_name, logtext); - if (!dirname) { - return SHARE_NONE; - } - - /* - * Now make sure the subdirectory exists. - */ - if ((err = make_dir_and_check_ours(dirname)) != NULL) { - *logtext = err; - sfree(dirname); - return SHARE_NONE; - } - - /* - * Acquire a lock on a file in that directory. - */ - lockname = dupcat(dirname, "/lock"); - lockfd = open(lockname, O_CREAT | O_RDWR | O_TRUNC, 0600); - if (lockfd < 0) { - *logtext = dupprintf("%s: open: %s", lockname, strerror(errno)); - sfree(dirname); - sfree(lockname); - return SHARE_NONE; - } - if (flock(lockfd, LOCK_EX) < 0) { - *logtext = dupprintf("%s: flock(LOCK_EX): %s", - lockname, strerror(errno)); - sfree(dirname); - sfree(lockname); - close(lockfd); - return SHARE_NONE; - } - - sockname = dupprintf("%s/socket", dirname); - - *logtext = NULL; - - if (can_downstream) { - retsock = new_connection(unix_sock_addr(sockname), - "", 0, false, true, false, false, - downplug, conf); - if (sk_socket_error(retsock) == NULL) { - sfree(*logtext); - *logtext = sockname; - *sock = retsock; - sfree(dirname); - sfree(lockname); - close(lockfd); - return SHARE_DOWNSTREAM; - } - sfree(*ds_err); - *ds_err = dupprintf("%s: %s", sockname, sk_socket_error(retsock)); - sk_close(retsock); - } - - if (can_upstream) { - retsock = new_unix_listener(unix_sock_addr(sockname), upplug); - if (sk_socket_error(retsock) == NULL) { - sfree(*logtext); - *logtext = sockname; - *sock = retsock; - sfree(dirname); - sfree(lockname); - close(lockfd); - return SHARE_UPSTREAM; - } - sfree(*us_err); - *us_err = dupprintf("%s: %s", sockname, sk_socket_error(retsock)); - sk_close(retsock); - } - - /* One of the above clauses ought to have happened. */ - assert(*logtext || *ds_err || *us_err); - - sfree(dirname); - sfree(lockname); - sfree(sockname); - close(lockfd); - return SHARE_NONE; -} - -void platform_ssh_share_cleanup(const char *name) -{ - char *dirname, *filename, *logtext; - - dirname = make_dirname(name, &logtext); - if (!dirname) { - sfree(logtext); /* we can't do much with this */ - return; - } - - filename = dupcat(dirname, "/socket"); - remove(filename); - sfree(filename); - - filename = dupcat(dirname, "/lock"); - remove(filename); - sfree(filename); - - rmdir(dirname); - - /* - * We deliberately _don't_ clean up the parent directory - * /tmp/putty-connshare., because if we leave it around - * then it reduces the ability for other users to be a nuisance by - * putting their own directory in the way of it. Also, the salt - * file in it can be reused. - */ - - sfree(dirname); -} diff --git a/unix/uxsocks.c b/unix/uxsocks.c deleted file mode 100644 index 748790b8..00000000 --- a/unix/uxsocks.c +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Main program for Unix psocks. - */ - -#include -#include - -#include -#include -#include -#include -#include - -#include "putty.h" -#include "ssh.h" -#include "psocks.h" - -typedef struct PsocksDataSinkPopen { - stdio_sink sink[2]; - PsocksDataSink pds; -} PsocksDataSinkPopen; - -static void popen_free(PsocksDataSink *pds) -{ - PsocksDataSinkPopen *pdsp = container_of(pds, PsocksDataSinkPopen, pds); - for (size_t i = 0; i < 2; i++) - pclose(pdsp->sink[i].fp); - sfree(pdsp); -} - -static PsocksDataSink *open_pipes( - const char *cmd, const char *const *direction_args, - const char *index_arg, char **err) -{ - FILE *fp[2]; - char *errmsg = NULL; - - for (size_t i = 0; i < 2; i++) { - /* No escaping needed: the provided command is already - * shell-quoted, and our extra arguments are simple */ - char *command = dupprintf("%s %s %s", cmd, - direction_args[i], index_arg); - - fp[i] = popen(command, "w"); - sfree(command); - - if (!fp[i]) { - if (!errmsg) - errmsg = dupprintf("%s", strerror(errno)); - } - } - - if (errmsg) { - for (size_t i = 0; i < 2; i++) - if (fp[i]) - pclose(fp[i]); - *err = errmsg; - return NULL; - } - - PsocksDataSinkPopen *pdsp = snew(PsocksDataSinkPopen); - - for (size_t i = 0; i < 2; i++) { - setvbuf(fp[i], NULL, _IONBF, 0); - stdio_sink_init(&pdsp->sink[i], fp[i]); - pdsp->pds.s[i] = BinarySink_UPCAST(&pdsp->sink[i]); - } - - pdsp->pds.free = popen_free; - - return &pdsp->pds; -} - -static int signalpipe[2] = { -1, -1 }; -static void sigchld(int signum) -{ - if (write(signalpipe[1], "x", 1) <= 0) - /* not much we can do about it */; -} - -static pid_t subcommand_pid = -1; - -static bool still_running = true; - -static void start_subcommand(strbuf *args) -{ - pid_t pid; - - /* - * Set up the pipe we'll use to tell us about SIGCHLD. - */ - if (pipe(signalpipe) < 0) { - perror("pipe"); - exit(1); - } - putty_signal(SIGCHLD, sigchld); - - /* - * Make an array of argument pointers that execvp will like. - */ - size_t nargs = 0; - for (size_t i = 0; i < args->len; i++) - if (args->s[i] == '\0') - nargs++; - - char **exec_args = snewn(nargs + 1, char *); - char *p = args->s; - for (size_t a = 0; a < nargs; a++) { - exec_args[a] = p; - size_t len = strlen(p); - assert(len < args->len - (p - args->s)); - p += 1 + len; - } - exec_args[nargs] = NULL; - - pid = fork(); - if (pid < 0) { - perror("fork"); - exit(1); - } else if (pid == 0) { - execvp(exec_args[0], exec_args); - perror("exec"); - _exit(127); - } else { - subcommand_pid = pid; - sfree(exec_args); - } -} - -static const PsocksPlatform platform = { - open_pipes, - start_subcommand, -}; - -static bool psocks_pw_setup(void *ctx, pollwrapper *pw) -{ - if (signalpipe[0] >= 0) - pollwrap_add_fd_rwx(pw, signalpipe[0], SELECT_R); - return true; -} - -static void psocks_pw_check(void *ctx, pollwrapper *pw) -{ - if (signalpipe[0] >= 0 && - pollwrap_check_fd_rwx(pw, signalpipe[0], SELECT_R)) { - while (true) { - int status; - pid_t pid = waitpid(-1, &status, WNOHANG); - if (pid <= 0) - break; - if (pid == subcommand_pid) - still_running = false; - } - } -} - -static bool psocks_continue(void *ctx, bool found_any_fd, - bool ran_any_callback) -{ - return still_running; -} - -typedef bool (*cliloop_continue_t)(void *ctx, bool found_any_fd, - bool ran_any_callback); - -int main(int argc, char **argv) -{ - psocks_state *ps = psocks_new(&platform); - psocks_cmdline(ps, argc, argv); - - sk_init(); - uxsel_init(); - psocks_start(ps); - - cli_main_loop(psocks_pw_setup, psocks_pw_check, psocks_continue, NULL); -} diff --git a/unix/uxstore.c b/unix/uxstore.c deleted file mode 100644 index 9db713d1..00000000 --- a/unix/uxstore.c +++ /dev/null @@ -1,822 +0,0 @@ -/* - * uxstore.c: Unix-specific implementation of the interface defined - * in storage.h. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#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 void make_session_filename(const char *in, strbuf *out) -{ - if (!in || !*in) - in = "Default Settings"; - - 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')) { - put_byte(out, '%'); - put_byte(out, hex[((unsigned char) *in) >> 4]); - put_byte(out, hex[((unsigned char) *in) & 15]); - } else - put_byte(out, *in); - in++; - } -} - -static void decode_session_filename(const char *in, strbuf *out) -{ - 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); - - put_byte(out, (i << 4) + j); - in += 3; - } else { - put_byte(out, *in++); - } - } -} - -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; - char *xdg_dir, *old_dir, *old_dir2, *old_dir3, *home, *pwd_home; - - env = getenv("PUTTYDIR"); - if (env) - return dupstr(env); - - home = getenv("HOME"); - pwd = getpwuid(getuid()); - if (pwd && pwd->pw_dir) { - pwd_home = pwd->pw_dir; - } else { - pwd_home = NULL; - } - - xdg_dir = NULL; - env = getenv("XDG_CONFIG_HOME"); - if (env && *env) { - xdg_dir = dupprintf("%s/putty", env); - } - if (!xdg_dir) { - if (home) { - tmp = home; - } else if (pwd_home) { - tmp = pwd_home; - } else { - tmp = ""; - } - xdg_dir = dupprintf("%s/.config/putty", tmp); - } - if (xdg_dir && access(xdg_dir, F_OK) == 0) { - return xdg_dir; - } - - old_dir = old_dir2 = old_dir3 = NULL; - if (home) { - old_dir = dupprintf("%s/.putty", home); - } - if (pwd_home) { - old_dir2 = dupprintf("%s/.putty", pwd_home); - } - old_dir3 = dupstr("/.putty"); - - if (old_dir && access(old_dir, F_OK) == 0) { - ret = old_dir; - goto out; - } - if (old_dir2 && access(old_dir2, F_OK) == 0) { - ret = old_dir2; - goto out; - } - if (access(old_dir3, F_OK) == 0) { - ret = old_dir3; - goto out; - } -#ifdef XDG_DEFAULT - if (xdg_dir) { - ret = xdg_dir; - goto out; - } -#endif - ret = old_dir ? old_dir : (old_dir2 ? old_dir2 : old_dir3); - - out: - if (ret != old_dir) - sfree(old_dir); - if (ret != old_dir2) - sfree(old_dir2); - if (ret != old_dir3) - sfree(old_dir3); - if (ret != xdg_dir) - sfree(xdg_dir); - return ret; - } - 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) { - strbuf *sb = strbuf_new(); - tmp = make_filename(INDEX_SESSIONDIR, NULL); - strbuf_catf(sb, "%s/", tmp); - sfree(tmp); - make_session_filename(subname, sb); - return strbuf_to_str(sb); - } - 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; -} - -struct settings_w { - FILE *fp; -}; - -settings_w *open_settings_w(const char *sessionname, char **errmsg) -{ - char *filename, *err; - FILE *fp; - - *errmsg = NULL; - - /* - * Start by making sure the .putty directory and its sessions - * subdir actually exist. - */ - filename = make_filename(INDEX_DIR, NULL); - if ((err = make_dir_path(filename, 0700)) != NULL) { - *errmsg = dupprintf("Unable to save session: %s", err); - sfree(err); - sfree(filename); - return NULL; - } - sfree(filename); - - filename = make_filename(INDEX_SESSIONDIR, NULL); - if ((err = make_dir_path(filename, 0700)) != NULL) { - *errmsg = dupprintf("Unable to save session: %s", err); - sfree(err); - sfree(filename); - return NULL; - } - sfree(filename); - - filename = make_filename(INDEX_SESSION, sessionname); - fp = fopen(filename, "w"); - if (!fp) { - *errmsg = dupprintf("Unable to save session: open(\"%s\") " - "returned '%s'", filename, strerror(errno)); - sfree(filename); - return NULL; /* can't open */ - } - sfree(filename); - - settings_w *toret = snew(settings_w); - toret->fp = fp; - return toret; -} - -void write_setting_s(settings_w *handle, const char *key, const char *value) -{ - fprintf(handle->fp, "%s=%s\n", key, value); -} - -void write_setting_i(settings_w *handle, const char *key, int value) -{ - fprintf(handle->fp, "%s=%d\n", key, value); -} - -void close_settings_w(settings_w *handle) -{ - fclose(handle->fp); - sfree(handle); -} - -/* ---------------------------------------------------------------------- - * System for treating X resources as a fallback source of defaults, - * after data read from a saved-session disk file. - * - * The read_setting_* functions will call get_setting(key) as a - * fallback if the setting isn't in the file they loaded. That in turn - * will hand on to x_get_default, which the front end application - * provides, and which actually reads resources from the X server (if - * appropriate). In between, there's a tree234 of X-resource shaped - * settings living locally in this file: the front end can call - * provide_xrm_string() to insert a setting into this tree (typically - * in response to an -xrm command line option or similar), and those - * will override the actual X resources. - */ - -struct skeyval { - const char *key; - const char *value; -}; - -static tree234 *xrmtree = NULL; - -static 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(const char *string, const char *progname) -{ - const char *p, *q; - char *key; - struct skeyval *xrms, *ret; - - p = q = strchr(string, ':'); - if (!q) { - fprintf(stderr, "%s: expected a colon in resource string" - " \"%s\"\n", progname, 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); - } -} - -static 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); -} - -/* ---------------------------------------------------------------------- - * Main code for reading settings from a disk file, calling the above - * get_setting() as a fallback if necessary. - */ - -struct settings_r { - tree234 *t; -}; - -settings_r *open_settings_r(const char *sessionname) -{ - char *filename; - FILE *fp; - char *line; - settings_r *toret; - - filename = make_filename(INDEX_SESSION, sessionname); - fp = fopen(filename, "r"); - sfree(filename); - if (!fp) - return NULL; /* can't open */ - - toret = snew(settings_r); - toret->t = newtree234(keycmp); - - while ( (line = fgetline(fp)) ) { - char *value = strchr(line, '='); - struct skeyval *kv; - - if (!value) { - sfree(line); - 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(toret->t, kv); - - sfree(line); - } - - fclose(fp); - - return toret; -} - -char *read_setting_s(settings_r *handle, const char *key) -{ - const char *val; - struct skeyval tmp, *kv; - - tmp.key = key; - if (handle != NULL && - (kv = find234(handle->t, &tmp, NULL)) != NULL) { - val = kv->value; - assert(val != NULL); - } else - val = get_setting(key); - - if (!val) - return NULL; - else - return dupstr(val); -} - -int read_setting_i(settings_r *handle, const char *key, int defvalue) -{ - const char *val; - struct skeyval tmp, *kv; - - tmp.key = key; - if (handle != NULL && - (kv = find234(handle->t, &tmp, NULL)) != NULL) { - val = kv->value; - assert(val != NULL); - } else - val = get_setting(key); - - if (!val) - return defvalue; - else - return atoi(val); -} - -FontSpec *read_setting_fontspec(settings_r *handle, const char *name) -{ - /* - * In GTK1-only PuTTY, we used to store font names simply as a - * valid X font description string (logical or alias), under a - * bare key such as "Font". - * - * 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"); - char *tmp; - - if ((tmp = read_setting_s(handle, suffname)) != NULL) { - FontSpec *fs = fontspec_new(tmp); - sfree(suffname); - sfree(tmp); - return fs; /* got new-style name */ - } - sfree(suffname); - - /* Fall back to old-style name. */ - tmp = read_setting_s(handle, name); - if (tmp && *tmp) { - char *tmp2 = dupcat("server:", tmp); - FontSpec *fs = fontspec_new(tmp2); - sfree(tmp2); - sfree(tmp); - return fs; - } else { - sfree(tmp); - return NULL; - } -} -Filename *read_setting_filename(settings_r *handle, const char *name) -{ - char *tmp = read_setting_s(handle, name); - if (tmp) { - Filename *ret = filename_from_str(tmp); - sfree(tmp); - return ret; - } else - return NULL; -} - -void write_setting_fontspec(settings_w *handle, const char *name, FontSpec *fs) -{ - /* - * read_setting_fontspec had to handle two cases, but when - * writing our settings back out we simply always generate the - * new-style name. - */ - char *suffname = dupcat(name, "Name"); - write_setting_s(handle, suffname, fs->name); - sfree(suffname); -} -void write_setting_filename(settings_w *handle, - const char *name, Filename *result) -{ - write_setting_s(handle, name, result->path); -} - -void close_settings_r(settings_r *handle) -{ - struct skeyval *kv; - - if (!handle) - return; - - while ( (kv = index234(handle->t, 0)) != NULL) { - del234(handle->t, kv); - sfree((char *)kv->key); - sfree((char *)kv->value); - sfree(kv); - } - - freetree234(handle->t); - sfree(handle); -} - -void del_settings(const char *sessionname) -{ - char *filename; - filename = make_filename(INDEX_SESSION, sessionname); - unlink(filename); - sfree(filename); -} - -struct settings_e { - DIR *dp; -}; - -settings_e *enum_settings_start(void) -{ - DIR *dp; - char *filename; - - filename = make_filename(INDEX_SESSIONDIR, NULL); - dp = opendir(filename); - sfree(filename); - - settings_e *toret = snew(settings_e); - toret->dp = dp; - return toret; -} - -bool enum_settings_next(settings_e *handle, strbuf *out) -{ - struct dirent *de; - struct stat st; - strbuf *fullpath; - - if (!handle->dp) - return NULL; - - fullpath = strbuf_new(); - - char *sessiondir = make_filename(INDEX_SESSIONDIR, NULL); - put_datapl(fullpath, ptrlen_from_asciz(sessiondir)); - sfree(sessiondir); - put_byte(fullpath, '/'); - - size_t baselen = fullpath->len; - - while ( (de = readdir(handle->dp)) != NULL ) { - strbuf_shrink_to(fullpath, baselen); - put_datapl(fullpath, ptrlen_from_asciz(de->d_name)); - - if (stat(fullpath->s, &st) < 0 || !S_ISREG(st.st_mode)) - continue; /* try another one */ - - decode_session_filename(de->d_name, out); - strbuf_free(fullpath); - return true; - } - - strbuf_free(fullpath); - return false; -} - -void enum_settings_finish(settings_e *handle) -{ - if (handle->dp) - closedir(handle->dp); - sfree(handle); -} - -/* - * 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; -} - -bool have_ssh_host_key(const char *hostname, int port, - const char *keytype) -{ - /* - * If we have a host key, verify_host_key will return 0 or 2. - * If we don't have one, it'll return 1. - */ - return verify_host_key(hostname, port, keytype, "") != 1; -} - -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; - - /* - * Open both the old file and a new file. - */ - tmpfilename = make_filename(INDEX_HOSTKEYS_TMP, NULL); - wfp = fopen(tmpfilename, "w"); - if (!wfp && errno == ENOENT) { - char *dir, *errmsg; - - dir = make_filename(INDEX_DIR, NULL); - if ((errmsg = make_dir_path(dir, 0700)) != NULL) { - nonfatal("Unable to store host key: %s", errmsg); - sfree(errmsg); - sfree(dir); - sfree(tmpfilename); - return; - } - sfree(dir); - - wfp = fopen(tmpfilename, "w"); - } - if (!wfp) { - nonfatal("Unable to store host key: open(\"%s\") " - "returned '%s'", tmpfilename, strerror(errno)); - sfree(tmpfilename); - return; - } - filename = make_filename(INDEX_HOSTKEYS, NULL); - rfp = fopen(filename, "r"); - - newtext = dupprintf("%s@%d:%s %s\n", keytype, port, hostname, key); - headerlen = 1 + strcspn(newtext, " "); /* count the space too */ - - /* - * Copy all lines from the old file to the new one that _don't_ - * involve the same host key identifier as the one we're adding. - */ - if (rfp) { - while ( (line = fgetline(rfp)) ) { - if (strncmp(line, newtext, headerlen)) - fputs(line, wfp); - sfree(line); - } - fclose(rfp); - } - - /* - * Now add the new line at the end. - */ - fputs(newtext, wfp); - - fclose(wfp); - - if (rename(tmpfilename, filename) < 0) { - nonfatal("Unable to store host key: rename(\"%s\",\"%s\")" - " returned '%s'", tmpfilename, filename, - strerror(errno)); - } - - 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) { - if (errno != ENOENT) { - nonfatal("Unable to write random seed: open(\"%s\") " - "returned '%s'", fname, strerror(errno)); - sfree(fname); - return; - } - char *dir, *errmsg; - - dir = make_filename(INDEX_DIR, NULL); - if ((errmsg = make_dir_path(dir, 0700)) != NULL) { - nonfatal("Unable to write random seed: %s", errmsg); - sfree(errmsg); - sfree(fname); - sfree(dir); - return; - } - sfree(dir); - - fd = open(fname, O_CREAT | O_WRONLY, 0600); - if (fd < 0) { - nonfatal("Unable to write random seed: open(\"%s\") " - "returned '%s'", fname, strerror(errno)); - sfree(fname); - return; - } - } - - while (len > 0) { - int ret = write(fd, data, len); - if (ret < 0) { - nonfatal("Unable to write random seed: write " - "returned '%s'", strerror(errno)); - break; - } - len -= ret; - data = (char *)data + len; - } - - close(fd); - sfree(fname); -} - -void cleanup_all(void) -{ -} diff --git a/unix/uxucs.c b/unix/uxucs.c deleted file mode 100644 index c1d76a42..00000000 --- a/unix/uxucs.c +++ /dev/null @@ -1,268 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#include - -#include "putty.h" -#include "charset.h" -#include "terminal.h" -#include "misc.h" - -/* - * Unix Unicode-handling routines. - */ - -bool is_dbcs_leadbyte(int codepage, char byte) -{ - return false; /* we don't do DBCS */ -} - -int mb_to_wc(int codepage, int flags, const char *mbstr, int mblen, - wchar_t *wcstr, int wclen) -{ - if (codepage == DEFAULT_CODEPAGE) { - int n = 0; - mbstate_t state; - - memset(&state, 0, sizeof state); - - 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; - } - - 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, const wchar_t *wcstr, int wclen, - char *mbstr, int mblen, const char *defchr, - struct unicode_data *ucsdata) -{ - if (codepage == DEFAULT_CODEPAGE) { - char output[MB_LEN_MAX]; - mbstate_t state; - int n = 0; - - memset(&state, 0, sizeof state); - - while (wclen > 0) { - size_t i = wcrtomb(output, wcstr[0], &state); - if (i == (size_t)-1 || i > n - mblen) - break; - memcpy(mbstr+n, output, i); - n += i; - wcstr++; - wclen--; - } - - 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. - */ -bool init_ucs(struct unicode_data *ucsdata, char *linecharset, - bool utf8_override, int font_charset, int vtmode) -{ - int i; - bool ret = false; - - /* - * 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 conf. - */ - 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 = true; - - /* - * Set up unitab_line, by translating each individual character - * in the line codepage into Unicode. - */ - for (i = 0; i < 256; i++) { - char c[1]; - const char *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]; - const char *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; - charset = charset_localenc_nth(index); - if (charset == CS_NONE) { - /* "Use font encoding" comes after all the named charsets */ - if (charset_localenc_nth(index-1) != CS_NONE) - return "Use font encoding"; - return NULL; - } - return charset_to_localenc(charset); -} - -int decode_codepage(char *cp_name) -{ - if (!cp_name || !*cp_name) - return CS_UTF8; - return charset_from_localenc(cp_name); -} diff --git a/unix/window.c b/unix/window.c new file mode 100644 index 00000000..4da0cb13 --- /dev/null +++ b/unix/window.c @@ -0,0 +1,5431 @@ +/* + * gtkwin.c: the main code that runs a PuTTY terminal emulator and + * backend in a GTK window. + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if !GTK_CHECK_VERSION(3,0,0) +#include +#endif + +#if GTK_CHECK_VERSION(2,0,0) +#include +#endif + +#define MAY_REFER_TO_GTK_IN_HEADERS + +#include "putty.h" +#include "terminal.h" +#include "gtkcompat.h" +#include "unifont.h" +#include "gtkmisc.h" + +#ifndef NOT_X_WINDOWS +#include +#include +#include +#include +#endif + +#include "x11misc.h" + +GdkAtom compound_text_atom, utf8_string_atom; +static GdkAtom clipboard_atom +#if GTK_CHECK_VERSION(2,0,0) /* GTK1 will have to fill this in at startup */ + = GDK_SELECTION_CLIPBOARD +#endif + ; + +#ifdef JUST_USE_GTK_CLIPBOARD_UTF8 +/* + * Because calling gtk_clipboard_set_with_data triggers a call to the + * clipboard_clear function from the last time, we need to arrange a + * way to distinguish a real call to clipboard_clear for the _new_ + * instance of the clipboard data from the leftover call for the + * outgoing one. We do this by setting the user data field in our + * gtk_clipboard_set_with_data() call, instead of the obvious pointer + * to 'inst', to one of these. + */ +struct clipboard_data_instance { + char *pasteout_data_utf8; + int pasteout_data_utf8_len; + struct clipboard_state *state; + struct clipboard_data_instance *next, *prev; +}; +#endif + +struct clipboard_state { + GtkFrontend *inst; + int clipboard; + GdkAtom atom; +#ifdef JUST_USE_GTK_CLIPBOARD_UTF8 + GtkClipboard *gtkclipboard; + struct clipboard_data_instance *current_cdi; +#else + char *pasteout_data, *pasteout_data_ctext, *pasteout_data_utf8; + int pasteout_data_len, pasteout_data_ctext_len, pasteout_data_utf8_len; +#endif +}; + +typedef struct XpmHolder XpmHolder; /* only used for GTK 1 */ + +struct GtkFrontend { + GtkWidget *window, *area, *sbar; + gboolean sbar_visible; + gboolean drawing_area_got_size, drawing_area_realised; + gboolean drawing_area_setup_needed; + GtkBox *hbox; + GtkAdjustment *sbar_adjust; + GtkWidget *menu, *specialsmenu, *specialsitem1, *specialsitem2, + *restartitem; + GtkWidget *sessionsmenu; +#ifndef NOT_X_WINDOWS + Display *disp; +#endif +#ifndef NO_BACKING_PIXMAPS + /* + * Server-side pixmap which we use to cache the terminal window's + * contents. When we draw text in the terminal, we draw it to this + * pixmap first, and then blit from there to the actual window; + * this way, X expose events can be handled with an absolute + * minimum of network traffic, by just sending a command to + * re-blit an appropriate rectangle from this pixmap. + */ + GdkPixmap *pixmap; +#endif +#ifdef DRAW_TEXT_CAIRO + /* + * If we're drawing using Cairo, we cache the same image on the + * client side in a Cairo surface. + * + * In GTK2+Cairo, this happens _as well_ as having the server-side + * pixmap cache above; in GTK3+Cairo, server-side pixmaps are + * deprecated, so we _just_ have this client-side cache. In the + * latter case that means we have to transmit a big wodge of + * bitmap data over the X connection on every expose event; but + * GTK3 apparently deliberately provides no way to avoid that + * inefficiency, and at least this way we don't _also_ have to + * redo any font rendering just because the window was temporarily + * covered. + */ + cairo_surface_t *surface; +#endif + int backing_w, backing_h; +#if GTK_CHECK_VERSION(2,0,0) + GtkIMContext *imc; +#endif + unifont *fonts[4]; /* normal, bold, wide, widebold */ + int xpos, ypos, gravity; + bool gotpos; + GdkCursor *rawcursor, *textcursor, *blankcursor, *waitcursor, *currcursor; + GdkColor cols[OSC4_NCOLOURS]; /* indexed by xterm colour indices */ +#if !GTK_CHECK_VERSION(3,0,0) + GdkColormap *colmap; +#endif + bool direct_to_font; + struct clipboard_state clipstates[N_CLIPBOARDS]; +#ifdef JUST_USE_GTK_CLIPBOARD_UTF8 + /* Remember all clipboard_data_instance structures currently + * associated with this GtkFrontend, in case they're still around + * when it gets destroyed */ + struct clipboard_data_instance cdi_headtail; +#endif + int clipboard_ctrlshiftins, clipboard_ctrlshiftcv; + int font_width, font_height; + int width, height, scale; + bool ignore_sbar; + bool mouseptr_visible; + BusyStatus busy_status; + int alt_keycode; + int alt_digits; + char *wintitle; + char *icontitle; + int master_fd, master_func_id; + Ldisc *ldisc; + Backend *backend; + Terminal *term; + LogContext *logctx; + bool exited; + struct unicode_data ucsdata; + Conf *conf; + eventlog_stuff *eventlogstuff; + guint32 input_event_time; /* Timestamp of the most recent input event. */ + GtkWidget *dialogs[DIALOG_SLOT_LIMIT]; +#if GTK_CHECK_VERSION(3,4,0) + gdouble cumulative_scroll; +#endif + /* Cached things out of conf that we refer to a lot */ + int bold_style; + int window_border; + int cursor_type; + int drawtype; + int meta_mod_mask; +#ifdef OSX_META_KEY_CONFIG + int system_mod_mask; +#endif + bool send_raw_mouse; + bool pointer_indicates_raw_mouse; + unifont_drawctx uctx; +#if GTK_CHECK_VERSION(2,0,0) + GdkPixbuf *trust_sigil_pb; +#else + GdkPixmap *trust_sigil_pm; +#endif + int trust_sigil_w, trust_sigil_h; + + Seat seat; + TermWin termwin; + LogPolicy logpolicy; +}; + +static void cache_conf_values(GtkFrontend *inst) +{ + inst->bold_style = conf_get_int(inst->conf, CONF_bold_style); + inst->window_border = conf_get_int(inst->conf, CONF_window_border); + inst->cursor_type = conf_get_int(inst->conf, CONF_cursor_type); +#ifdef OSX_META_KEY_CONFIG + inst->meta_mod_mask = 0; + if (conf_get_bool(inst->conf, CONF_osx_option_meta)) + inst->meta_mod_mask |= GDK_MOD1_MASK; + if (conf_get_bool(inst->conf, CONF_osx_command_meta)) + inst->meta_mod_mask |= GDK_MOD2_MASK; + inst->system_mod_mask = GDK_MOD2_MASK & ~inst->meta_mod_mask; +#else + inst->meta_mod_mask = GDK_MOD1_MASK; +#endif +} + +static void start_backend(GtkFrontend *inst); +static void exit_callback(void *vinst); +static void destroy_inst_connection(GtkFrontend *inst); +static void delete_inst(GtkFrontend *inst); + +static void post_fatal_message_box_toplevel(void *vctx) +{ + GtkFrontend *inst = (GtkFrontend *)vctx; + gtk_widget_destroy(inst->window); +} + +static void post_fatal_message_box(void *vctx, int result) +{ + GtkFrontend *inst = (GtkFrontend *)vctx; + unregister_dialog(&inst->seat, DIALOG_SLOT_CONNECTION_FATAL); + queue_toplevel_callback(post_fatal_message_box_toplevel, inst); +} + +static void common_connfatal_message_box( + GtkFrontend *inst, const char *msg, post_dialog_fn_t postfn) +{ + char *title = dupcat(appname, " Fatal Error"); + GtkWidget *dialog = create_message_box( + inst->window, title, msg, + string_width("REASONABLY LONG LINE OF TEXT FOR BASIC SANITY"), + false, &buttons_ok, postfn, inst); + register_dialog(&inst->seat, DIALOG_SLOT_CONNECTION_FATAL, dialog); + sfree(title); +} + +void fatal_message_box(GtkFrontend *inst, const char *msg) +{ + common_connfatal_message_box(inst, msg, post_fatal_message_box); +} + +static void connection_fatal_callback(void *vctx) +{ + GtkFrontend *inst = (GtkFrontend *)vctx; + destroy_inst_connection(inst); +} + +static void post_nonfatal_message_box(void *vctx, int result) +{ + GtkFrontend *inst = (GtkFrontend *)vctx; + unregister_dialog(&inst->seat, DIALOG_SLOT_CONNECTION_FATAL); +} + +static void gtk_seat_connection_fatal(Seat *seat, const char *msg) +{ + GtkFrontend *inst = container_of(seat, GtkFrontend, seat); + if (conf_get_int(inst->conf, CONF_close_on_exit) == FORCE_ON) { + fatal_message_box(inst, msg); + } else { + common_connfatal_message_box(inst, msg, post_nonfatal_message_box); + } + + inst->exited = true; /* suppress normal exit handling */ + queue_toplevel_callback(connection_fatal_callback, inst); +} + +/* + * Default settings that are specific to pterm. + */ +FontSpec *platform_default_fontspec(const char *name) +{ + if (!strcmp(name, "Font")) + return fontspec_new(DEFAULT_GTK_FONT); + else + return fontspec_new(""); +} + +Filename *platform_default_filename(const char *name) +{ + if (!strcmp(name, "LogFileName")) + return filename_from_str("putty.log"); + else + return filename_from_str(""); +} + +char *platform_default_s(const char *name) +{ + if (!strcmp(name, "SerialLine")) + return dupstr("/dev/ttyS0"); + return NULL; +} + +bool platform_default_b(const char *name, bool def) +{ + if (!strcmp(name, "WinNameAlways")) { + /* X natively supports icon titles, so use 'em by default */ + return false; + } + return def; +} + +int platform_default_i(const char *name, int def) +{ + if (!strcmp(name, "CloseOnExit")) + return 2; /* maps to FORCE_ON after painful rearrangement :-( */ + return def; +} + +static char *gtk_seat_get_ttymode(Seat *seat, const char *mode) +{ + GtkFrontend *inst = container_of(seat, GtkFrontend, seat); + return term_get_ttymode(inst->term, mode); +} + +static size_t gtk_seat_output(Seat *seat, bool is_stderr, + const void *data, size_t len) +{ + GtkFrontend *inst = container_of(seat, GtkFrontend, seat); + return term_data(inst->term, is_stderr, data, len); +} + +static bool gtk_seat_eof(Seat *seat) +{ + /* GtkFrontend *inst = container_of(seat, GtkFrontend, seat); */ + return true; /* do respond to incoming EOF with outgoing */ +} + +static int gtk_seat_get_userpass_input(Seat *seat, prompts_t *p, + bufchain *input) +{ + GtkFrontend *inst = container_of(seat, GtkFrontend, seat); + int ret; + ret = cmdline_get_passwd_input(p); + if (ret == -1) + ret = term_get_userpass_input(inst->term, p, input); + return ret; +} + +static bool gtk_seat_is_utf8(Seat *seat) +{ + GtkFrontend *inst = container_of(seat, GtkFrontend, seat); + return inst->ucsdata.line_codepage == CS_UTF8; +} + +static void get_window_pixel_size(GtkFrontend *inst, int *w, int *h) +{ + /* + * 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), w, h); +#else + gdk_window_get_size(gtk_widget_get_window(inst->window), w, h); +#endif +} + +static bool gtk_seat_get_window_pixel_size(Seat *seat, int *w, int *h) +{ + GtkFrontend *inst = container_of(seat, GtkFrontend, seat); + get_window_pixel_size(inst, w, h); + return true; +} + +StripCtrlChars *gtk_seat_stripctrl_new( + Seat *seat, BinarySink *bs_out, SeatInteractionContext sic) +{ + GtkFrontend *inst = container_of(seat, GtkFrontend, seat); + return stripctrl_new_term(bs_out, false, 0, inst->term); +} + +static void gtk_seat_notify_remote_exit(Seat *seat); +static void gtk_seat_update_specials_menu(Seat *seat); +static void gtk_seat_set_busy_status(Seat *seat, BusyStatus status); +static const char *gtk_seat_get_x_display(Seat *seat); +#ifndef NOT_X_WINDOWS +static bool gtk_seat_get_windowid(Seat *seat, long *id); +#endif +static bool gtk_seat_set_trust_status(Seat *seat, bool trusted); +static bool gtk_seat_get_cursor_position(Seat *seat, int *x, int *y); + +static const SeatVtable gtk_seat_vt = { + .output = gtk_seat_output, + .eof = gtk_seat_eof, + .get_userpass_input = gtk_seat_get_userpass_input, + .notify_remote_exit = gtk_seat_notify_remote_exit, + .connection_fatal = gtk_seat_connection_fatal, + .update_specials_menu = gtk_seat_update_specials_menu, + .get_ttymode = gtk_seat_get_ttymode, + .set_busy_status = gtk_seat_set_busy_status, + .verify_ssh_host_key = gtk_seat_verify_ssh_host_key, + .confirm_weak_crypto_primitive = gtk_seat_confirm_weak_crypto_primitive, + .confirm_weak_cached_hostkey = gtk_seat_confirm_weak_cached_hostkey, + .is_utf8 = gtk_seat_is_utf8, + .echoedit_update = nullseat_echoedit_update, + .get_x_display = gtk_seat_get_x_display, +#ifdef NOT_X_WINDOWS + .get_windowid = nullseat_get_windowid, +#else + .get_windowid = gtk_seat_get_windowid, +#endif + .get_window_pixel_size = gtk_seat_get_window_pixel_size, + .stripctrl_new = gtk_seat_stripctrl_new, + .set_trust_status = gtk_seat_set_trust_status, + .verbose = nullseat_verbose_yes, + .interactive = nullseat_interactive_yes, + .get_cursor_position = gtk_seat_get_cursor_position, +}; + +static void gtk_eventlog(LogPolicy *lp, const char *string) +{ + GtkFrontend *inst = container_of(lp, GtkFrontend, logpolicy); + logevent_dlg(inst->eventlogstuff, string); +} + +static int gtk_askappend(LogPolicy *lp, Filename *filename, + void (*callback)(void *ctx, int result), void *ctx) +{ + GtkFrontend *inst = container_of(lp, GtkFrontend, logpolicy); + return gtkdlg_askappend(&inst->seat, filename, callback, ctx); +} + +static void gtk_logging_error(LogPolicy *lp, const char *event) +{ + GtkFrontend *inst = container_of(lp, GtkFrontend, logpolicy); + + /* Send 'can't open log file' errors to the terminal window. + * (Marked as stderr, although terminal.c won't care.) */ + seat_stderr_pl(&inst->seat, ptrlen_from_asciz(event)); + seat_stderr_pl(&inst->seat, PTRLEN_LITERAL("\r\n")); +} + +static const LogPolicyVtable gtk_logpolicy_vt = { + .eventlog = gtk_eventlog, + .askappend = gtk_askappend, + .logging_error = gtk_logging_error, + .verbose = null_lp_verbose_yes, +}; + +/* + * 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) +{ + 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. + */ +GtkWidget *gtk_seat_get_window(Seat *seat) +{ + GtkFrontend *inst = container_of(seat, GtkFrontend, seat); + return inst->window; +} + +/* + * Set and clear a pointer to a dialog box created as a result of the + * network code wanting to ask an asynchronous user question (e.g. + * 'what about this dodgy host key, then?'). + */ +void register_dialog(Seat *seat, enum DialogSlot slot, GtkWidget *dialog) +{ + GtkFrontend *inst; + assert(seat->vt == >k_seat_vt); + inst = container_of(seat, GtkFrontend, seat); + assert(slot < DIALOG_SLOT_LIMIT); + assert(!inst->dialogs[slot]); + inst->dialogs[slot] = dialog; +} +void unregister_dialog(Seat *seat, enum DialogSlot slot) +{ + GtkFrontend *inst; + assert(seat->vt == >k_seat_vt); + inst = container_of(seat, GtkFrontend, seat); + assert(slot < DIALOG_SLOT_LIMIT); + assert(inst->dialogs[slot]); + inst->dialogs[slot] = NULL; +} + +/* + * Minimise or restore the window in response to a server-side + * request. + */ +static void gtkwin_set_minimised(TermWin *tw, bool minimised) +{ + /* + * GTK 1.2 doesn't know how to do this. + */ +#if GTK_CHECK_VERSION(2,0,0) + GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); + if (minimised) + 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. + */ +static void gtkwin_move(TermWin *tw, int x, int y) +{ + GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); + /* + * 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) + /* in case we reset this at startup due to a geometry string */ + gtk_window_set_gravity(GTK_WINDOW(inst->window), GDK_GRAVITY_NORTH_EAST); + gtk_window_move(GTK_WINDOW(inst->window), x, y); +#else + gdk_window_move(gtk_widget_get_window(inst->window), x, y); +#endif +} + +/* + * Move the window to the top or bottom of the z-order in response + * to a server-side request. + */ +static void gtkwin_set_zorder(TermWin *tw, bool top) +{ + GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); + if (top) + gdk_window_raise(gtk_widget_get_window(inst->window)); + else + gdk_window_lower(gtk_widget_get_window(inst->window)); +} + +/* + * Refresh the window in response to a server-side request. + */ +static void gtkwin_refresh(TermWin *tw) +{ + GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); + term_invalidate(inst->term); +} + +/* + * Maximise or restore the window in response to a server-side + * request. + */ +static void gtkwin_set_maximised(TermWin *tw, bool maximised) +{ + /* + * GTK 1.2 doesn't know how to do this. + */ +#if GTK_CHECK_VERSION(2,0,0) + GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); + if (maximised) + gtk_window_maximize(GTK_WINDOW(inst->window)); + else + gtk_window_unmaximize(GTK_WINDOW(inst->window)); +#endif +} + +/* + * Find out whether a dialog box already exists for this window in a + * particular DialogSlot. If it does, uniconify it (if we can) and + * raise it, so that the user realises they've already been asked this + * question. + */ +static bool find_and_raise_dialog(GtkFrontend *inst, enum DialogSlot slot) +{ + GtkWidget *dialog = inst->dialogs[slot]; + if (!dialog) + return false; + +#if GTK_CHECK_VERSION(2,0,0) + gtk_window_deiconify(GTK_WINDOW(dialog)); +#endif + gdk_window_raise(gtk_widget_get_window(dialog)); + return true; +} + +static void warn_on_close_callback(void *vctx, int result) +{ + GtkFrontend *inst = (GtkFrontend *)vctx; + unregister_dialog(&inst->seat, DIALOG_SLOT_WARN_ON_CLOSE); + if (result) + gtk_widget_destroy(inst->window); +} + +/* + * Handle the 'delete window' event (e.g. user clicking the WM close + * button). The return value false means the window should close, and + * true means it shouldn't. + * + * (That's counterintuitive, but really, in GTK terms, true means 'I + * have done everything necessary to handle this event, so the default + * handler need not do anything', i.e. 'suppress default handler', + * i.e. 'do not close the window'.) + */ +gint delete_window(GtkWidget *widget, GdkEvent *event, GtkFrontend *inst) +{ + if (!inst->exited && conf_get_bool(inst->conf, CONF_warn_on_close)) { + /* + * We're not going to exit right now. We must put up a + * warn-on-close dialog, unless one already exists, in which + * case we'll just re-emphasise that one. + */ + if (!find_and_raise_dialog(inst, DIALOG_SLOT_WARN_ON_CLOSE)) { + char *title = dupcat(appname, " Exit Confirmation"); + char *msg, *additional = NULL; + if (inst->backend && inst->backend->vt->close_warn_text) { + additional = inst->backend->vt->close_warn_text(inst->backend); + } + msg = dupprintf("Are you sure you want to close this session?%s%s", + additional ? "\n" : "", + additional ? additional : ""); + GtkWidget *dialog = create_message_box( + inst->window, title, msg, + string_width("Most of the width of the above text"), + false, &buttons_yn, warn_on_close_callback, inst); + register_dialog(&inst->seat, DIALOG_SLOT_WARN_ON_CLOSE, dialog); + sfree(title); + sfree(msg); + sfree(additional); + } + return true; + } + return false; +} + +#if GTK_CHECK_VERSION(2,0,0) +static void window_state_event(GtkWidget *widget, GdkEventWindowState *event, + gpointer user_data) +{ + GtkFrontend *inst = (GtkFrontend *)user_data; + term_notify_minimised( + inst->term, event->new_window_state & GDK_WINDOW_STATE_ICONIFIED); +} +#endif + +static void update_mouseptr(GtkFrontend *inst) +{ + switch (inst->busy_status) { + case BUSY_NOT: + if (!inst->mouseptr_visible) { + gdk_window_set_cursor(gtk_widget_get_window(inst->area), + inst->blankcursor); + } else if (inst->pointer_indicates_raw_mouse) { + gdk_window_set_cursor(gtk_widget_get_window(inst->area), + inst->rawcursor); + } else { + gdk_window_set_cursor(gtk_widget_get_window(inst->area), + inst->textcursor); + } + break; + case BUSY_WAITING: /* XXX can we do better? */ + case BUSY_CPU: + /* We always display these cursors. */ + gdk_window_set_cursor(gtk_widget_get_window(inst->area), + inst->waitcursor); + break; + default: + unreachable("Bad busy_status"); + } +} + +static void show_mouseptr(GtkFrontend *inst, bool show) +{ + if (!conf_get_bool(inst->conf, CONF_hide_mouseptr)) + show = true; + inst->mouseptr_visible = show; + update_mouseptr(inst); +} + +static void draw_backing_rect(GtkFrontend *inst); + +static void drawing_area_setup(GtkFrontend *inst, int width, int height) +{ + int w, h, new_scale; + bool need_size = false; + + /* + * See if the terminal size has changed. + */ + w = (width - 2*inst->window_border) / inst->font_width; + h = (height - 2*inst->window_border) / inst->font_height; + if (w != inst->width || h != inst->height) { + /* + * Update conf. + */ + inst->width = w; + inst->height = h; + conf_set_int(inst->conf, CONF_width, inst->width); + conf_set_int(inst->conf, CONF_height, inst->height); + /* + * We'll need to tell terminal.c about the resize below. + */ + need_size = true; + /* + * And we must refresh the window's backing image. + */ + inst->drawing_area_setup_needed = true; + } + +#if GTK_CHECK_VERSION(3,10,0) + new_scale = gtk_widget_get_scale_factor(inst->area); + if (new_scale != inst->scale) + inst->drawing_area_setup_needed = true; +#else + new_scale = 1; +#endif + + int new_backing_w = w * inst->font_width + 2*inst->window_border; + int new_backing_h = h * inst->font_height + 2*inst->window_border; + new_backing_w *= new_scale; + new_backing_h *= new_scale; + + if (inst->backing_w != new_backing_w || inst->backing_h != new_backing_h) + inst->drawing_area_setup_needed = true; + + /* + * This event might be spurious; some GTK setups have been known + * to call it when nothing at all has changed. Check if we have + * any reason to proceed. + */ + if (!inst->drawing_area_setup_needed) + return; + + inst->drawing_area_setup_needed = false; + inst->scale = new_scale; + inst->backing_w = new_backing_w; + inst->backing_h = new_backing_h; + +#ifndef NO_BACKING_PIXMAPS + if (inst->pixmap) { + gdk_pixmap_unref(inst->pixmap); + inst->pixmap = NULL; + } + + inst->pixmap = gdk_pixmap_new(gtk_widget_get_window(inst->area), + inst->backing_w, inst->backing_h, -1); +#endif + +#ifdef DRAW_TEXT_CAIRO + if (inst->surface) { + cairo_surface_destroy(inst->surface); + inst->surface = NULL; + } + + inst->surface = cairo_image_surface_create( + CAIRO_FORMAT_ARGB32, inst->backing_w, inst->backing_h); +#endif + + draw_backing_rect(inst); + + if (need_size && inst->term) { + term_size(inst->term, h, w, conf_get_int(inst->conf, CONF_savelines)); + } + + if (inst->term) + term_invalidate(inst->term); + +#if GTK_CHECK_VERSION(2,0,0) + gtk_im_context_set_client_window( + inst->imc, gtk_widget_get_window(inst->area)); +#endif +} + +static void drawing_area_setup_simple(GtkFrontend *inst) +{ + /* + * Wrapper on drawing_area_setup which fetches the width and + * height of the drawing area. We go directly to the inner version + * in the case where a new size allocation comes in (just in case + * GTK hasn't installed it in the normal place yet). + */ +#if GTK_CHECK_VERSION(2,0,0) + GdkRectangle alloc; + gtk_widget_get_allocation(inst->area, &alloc); +#else + GtkAllocation alloc = inst->area->allocation; +#endif + drawing_area_setup(inst, alloc.width, alloc.height); +} + +static void area_realised(GtkWidget *widget, GtkFrontend *inst) +{ + inst->drawing_area_realised = true; + if (inst->drawing_area_realised && inst->drawing_area_got_size && + inst->drawing_area_setup_needed) + drawing_area_setup_simple(inst); +} + +static void area_size_allocate( + GtkWidget *widget, GdkRectangle *alloc, GtkFrontend *inst) +{ + inst->drawing_area_got_size = true; + if (inst->drawing_area_realised && inst->drawing_area_got_size) + drawing_area_setup(inst, alloc->width, alloc->height); +} + +#if GTK_CHECK_VERSION(3,10,0) +static void area_check_scale(GtkFrontend *inst) +{ + if (!inst->drawing_area_setup_needed && + inst->scale != gtk_widget_get_scale_factor(inst->area)) { + drawing_area_setup_simple(inst); + if (inst->term) { + term_invalidate(inst->term); + term_update(inst->term); + } + } +} +#endif + +static gboolean window_configured( + GtkWidget *widget, GdkEventConfigure *event, gpointer data) +{ + GtkFrontend *inst = (GtkFrontend *)data; + if (inst->term) { + term_notify_window_pos(inst->term, event->x, event->y); + term_notify_window_size_pixels( + inst->term, event->width, event->height); + } + return false; +} + +#if GTK_CHECK_VERSION(3,10,0) +static gboolean area_configured( + GtkWidget *widget, GdkEventConfigure *event, gpointer data) +{ + GtkFrontend *inst = (GtkFrontend *)data; + area_check_scale(inst); + return false; +} +#endif + +#ifdef DRAW_TEXT_CAIRO +static void cairo_setup_draw_ctx(GtkFrontend *inst) +{ + cairo_get_matrix(inst->uctx.u.cairo.cr, + &inst->uctx.u.cairo.origmatrix); + cairo_set_line_width(inst->uctx.u.cairo.cr, 1.0); + cairo_set_line_cap(inst->uctx.u.cairo.cr, CAIRO_LINE_CAP_SQUARE); + cairo_set_line_join(inst->uctx.u.cairo.cr, CAIRO_LINE_JOIN_MITER); + /* This antialiasing setting appears to be ignored for Pango + * font rendering but honoured for stroking and filling paths; + * I don't quite understand the logic of that, but I won't + * complain since it's exactly what I happen to want */ + cairo_set_antialias(inst->uctx.u.cairo.cr, CAIRO_ANTIALIAS_NONE); +} +#endif + +#if GTK_CHECK_VERSION(3,0,0) +static gint draw_area(GtkWidget *widget, cairo_t *cr, gpointer data) +{ + GtkFrontend *inst = (GtkFrontend *)data; + +#if GTK_CHECK_VERSION(3,10,0) + /* + * This may be the first we hear of the window scale having + * changed, in which case we must hastily reconstruct our backing + * surface before we copy the wrong one into the newly resized + * real window. + */ + area_check_scale(inst); +#endif + + /* + * GTK3 window redraw: we always expect Cairo to be enabled, so + * that inst->surface exists, and pixmaps to be disabled, so that + * inst->pixmap does not exist. Hence, we just blit from + * inst->surface to the window. + */ + if (inst->surface) { + GdkRectangle dirtyrect; + cairo_surface_t *target_surface; + double orig_sx, orig_sy; + cairo_matrix_t m; + + /* + * Furtle around in the Cairo setup to force the device scale + * back to 1, so that when we blit a collection of pixels from + * our backing surface into the window, they really are + * _pixels_ and not some confusing antialiased slightly-offset + * 2x2 rectangle of pixeloids. + * + * I have no idea whether GTK expects me not to mess with the + * device scale in the cairo_surface_t backing its window, so + * I carefully put it back when I've finished. + * + * In some GTK setups, the Cairo context we're given may not + * have a zero translation offset in its matrix, in which case + * we have to adjust that to compensate for the change of + * scale, or else the old translation offset (designed for the + * old scale) will be multiplied by the new scale instead and + * put everything in the wrong place. + */ + target_surface = cairo_get_target(cr); + cairo_get_matrix(cr, &m); + cairo_surface_get_device_scale(target_surface, &orig_sx, &orig_sy); + cairo_surface_set_device_scale(target_surface, 1.0, 1.0); + cairo_translate(cr, m.x0 * (orig_sx - 1.0), m.y0 * (orig_sy - 1.0)); + + gdk_cairo_get_clip_rectangle(cr, &dirtyrect); + + cairo_set_source_surface(cr, inst->surface, 0, 0); + cairo_rectangle(cr, dirtyrect.x, dirtyrect.y, + dirtyrect.width, dirtyrect.height); + cairo_fill(cr); + + cairo_surface_set_device_scale(target_surface, orig_sx, orig_sy); + } + + return true; +} +#else +gint expose_area(GtkWidget *widget, GdkEventExpose *event, gpointer data) +{ + GtkFrontend *inst = (GtkFrontend *)data; + +#ifndef NO_BACKING_PIXMAPS + /* + * Draw to the exposed part of the window from the server-side + * backing pixmap. + */ + if (inst->pixmap) { + gdk_draw_pixmap(gtk_widget_get_window(widget), + (gtk_widget_get_style(widget)->fg_gc + [gtk_widget_get_state(widget)]), + inst->pixmap, + event->area.x, event->area.y, + event->area.x, event->area.y, + event->area.width, event->area.height); + } +#else + /* + * Failing that, draw from the client-side Cairo surface. (We + * should never be compiled in a context where we have _neither_ + * inst->surface nor inst->pixmap.) + */ + if (inst->surface) { + cairo_t *cr = gdk_cairo_create(gtk_widget_get_window(widget)); + cairo_set_source_surface(cr, inst->surface, 0, 0); + cairo_rectangle(cr, event->area.x, event->area.y, + event->area.width, event->area.height); + cairo_fill(cr); + cairo_destroy(cr); + } +#endif + + return true; +} +#endif + +#define KEY_PRESSED(k) \ + (inst->keystate[(k) / 32] & (1 << ((k) % 32))) + +#ifdef KEY_EVENT_DIAGNOSTICS +char *dup_keyval_name(guint keyval) +{ + const char *name = gdk_keyval_name(keyval); + if (name) + return dupstr(name); + else + return dupprintf("UNKNOWN[%u]", (unsigned)keyval); +} +#endif + +static void change_font_size(GtkFrontend *inst, int increment); +static void key_pressed(GtkFrontend *inst); + +/* Subroutine used in key_event */ +static int return_key(GtkFrontend *inst, char *output, bool *special) +{ + int end; + + /* Ugly label so we can come here as a fallback from + * numeric keypad Enter handling */ + if (inst->term->cr_lf_return) { +#ifdef KEY_EVENT_DIAGNOSTICS + debug(" - Return in cr_lf_return mode, translating as 0d 0a\n"); +#endif + output[1] = '\015'; + output[2] = '\012'; + end = 3; + } else { +#ifdef KEY_EVENT_DIAGNOSTICS + debug(" - Return special case, translating as 0d + special\n"); +#endif + output[1] = '\015'; + end = 2; + *special = true; + } + + return end; +} + +gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) +{ + GtkFrontend *inst = (GtkFrontend *)data; + char output[256]; + wchar_t ucsoutput[2]; + int ucsval, start, end, output_charset; + bool special, use_ucsoutput; + bool force_format_numeric_keypad = false; + bool generated_something = false; + char num_keypad_key = '\0'; + const char *event_string = event->string ? event->string : ""; + + noise_ultralight(NOISE_SOURCE_KEY, event->keyval); + +#ifdef OSX_META_KEY_CONFIG + if (event->state & inst->system_mod_mask) + return false; /* let GTK process OS X Command key */ +#endif + + /* 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; + +#ifdef KEY_EVENT_DIAGNOSTICS + { + char *type_string, *state_string, *keyval_string, *string_string; + + type_string = (event->type == GDK_KEY_PRESS ? dupstr("PRESS") : + event->type == GDK_KEY_RELEASE ? dupstr("RELEASE") : + dupprintf("UNKNOWN[%d]", (int)event->type)); + + { + static const struct { + int mod_bit; + const char *name; + } mod_bits[] = { + {GDK_SHIFT_MASK, "SHIFT"}, + {GDK_LOCK_MASK, "LOCK"}, + {GDK_CONTROL_MASK, "CONTROL"}, + {GDK_MOD1_MASK, "MOD1"}, + {GDK_MOD2_MASK, "MOD2"}, + {GDK_MOD3_MASK, "MOD3"}, + {GDK_MOD4_MASK, "MOD4"}, + {GDK_MOD5_MASK, "MOD5"}, + {GDK_SUPER_MASK, "SUPER"}, + {GDK_HYPER_MASK, "HYPER"}, + {GDK_META_MASK, "META"}, + }; + int i; + int val = event->state; + + state_string = dupstr(""); + + for (i = 0; i < lenof(mod_bits); i++) { + if (val & mod_bits[i].mod_bit) { + char *old = state_string; + state_string = dupcat(state_string, + state_string[0] ? "|" : "", + mod_bits[i].name); + sfree(old); + + val &= ~mod_bits[i].mod_bit; + } + } + + if (val || !state_string[0]) { + char *old = state_string; + state_string = dupprintf("%s%s%d", state_string, + state_string[0] ? "|" : "", val); + sfree(old); + } + } + + keyval_string = dup_keyval_name(event->keyval); + + string_string = dupstr(""); + { + int i; + for (i = 0; event_string[i]; i++) { + char *old = string_string; + string_string = dupprintf("%s%s%02x", string_string, + string_string[0] ? " " : "", + (unsigned)event_string[i] & 0xFF); + sfree(old); + } + } + + debug("key_event: type=%s keyval=%s state=%s " + "hardware_keycode=%d is_modifier=%s string=[%s]\n", + type_string, keyval_string, state_string, + (int)event->hardware_keycode, + event->is_modifier ? "true" : "false", + string_string); + + sfree(type_string); + sfree(state_string); + sfree(keyval_string); + sfree(string_string); + } +#endif /* KEY_EVENT_DIAGNOSTICS */ + + /* + * 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) { + if ((event->keyval == GDK_KEY_Meta_L || + event->keyval == GDK_KEY_Meta_R || + event->keyval == GDK_KEY_Alt_L || + event->keyval == GDK_KEY_Alt_R) && + inst->alt_keycode >= 0 && inst->alt_digits > 1) { +#ifdef KEY_EVENT_DIAGNOSTICS + debug(" - modifier release terminates Alt+numberpad input, " + "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 GTK_CHECK_VERSION(2,0,0) +#ifdef KEY_EVENT_DIAGNOSTICS + debug(" - key release, passing to IM\n"); +#endif + if (gtk_im_context_filter_keypress(inst->imc, event)) { +#ifdef KEY_EVENT_DIAGNOSTICS + debug(" - key release accepted by IM\n"); +#endif + return true; + } else { +#ifdef KEY_EVENT_DIAGNOSTICS + debug(" - key release not accepted by IM\n"); +#endif + } +#endif + } + + if (event->type == GDK_KEY_PRESS) { + /* + * 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_KEY_Meta_L || + event->keyval == GDK_KEY_Meta_R || + event->keyval == GDK_KEY_Alt_L || + event->keyval == GDK_KEY_Alt_R)) { +#ifdef KEY_EVENT_DIAGNOSTICS + debug(" - modifier press potentially begins Alt+numberpad " + "input\n"); +#endif + inst->alt_keycode = -1; + inst->alt_digits = 0; + goto done; /* this generates nothing else */ + } + + /* + * If we're seeing a numberpad key press with Meta down, + * consider adding it to alt_keycode if that's sensible. + * Anything _else_ with Meta down cancels any possibility + * of an ALT keycode: we set alt_keycode to -2. + */ + if ((event->state & inst->meta_mod_mask) && inst->alt_keycode != -2) { + int digit = -1; + switch (event->keyval) { + case GDK_KEY_KP_0: case GDK_KEY_KP_Insert: digit = 0; break; + case GDK_KEY_KP_1: case GDK_KEY_KP_End: digit = 1; break; + case GDK_KEY_KP_2: case GDK_KEY_KP_Down: digit = 2; break; + case GDK_KEY_KP_3: case GDK_KEY_KP_Page_Down: digit = 3; break; + case GDK_KEY_KP_4: case GDK_KEY_KP_Left: digit = 4; break; + case GDK_KEY_KP_5: case GDK_KEY_KP_Begin: digit = 5; break; + case GDK_KEY_KP_6: case GDK_KEY_KP_Right: digit = 6; break; + case GDK_KEY_KP_7: case GDK_KEY_KP_Home: digit = 7; break; + case GDK_KEY_KP_8: case GDK_KEY_KP_Up: digit = 8; break; + case GDK_KEY_KP_9: case GDK_KEY_KP_Page_Up: digit = 9; break; + } + if (digit < 0) + inst->alt_keycode = -2; /* it's invalid */ + else { +#if defined(DEBUG) && defined(KEY_EVENT_DIAGNOSTICS) + int old_keycode = 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_EVENT_DIAGNOSTICS + debug(" - Alt+numberpad digit %d added to keycode %d" + " gives %d\n", digit, old_keycode, inst->alt_keycode); +#endif + /* Having used this digit, we now do nothing more with it. */ + goto done; + } + } + + if (event->keyval == GDK_KEY_greater && + (event->state & GDK_CONTROL_MASK)) { +#ifdef KEY_EVENT_DIAGNOSTICS + debug(" - Ctrl->: increase font size\n"); +#endif + change_font_size(inst, +1); + return true; + } + if (event->keyval == GDK_KEY_less && + (event->state & GDK_CONTROL_MASK)) { +#ifdef KEY_EVENT_DIAGNOSTICS + debug(" - Ctrl-<: increase font size\n"); +#endif + change_font_size(inst, -1); + return true; + } + + /* + * Shift-PgUp and Shift-PgDn don't even generate keystrokes + * at all. + */ + if (event->keyval == GDK_KEY_Page_Up && + ((event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) == + (GDK_CONTROL_MASK | GDK_SHIFT_MASK))) { +#ifdef KEY_EVENT_DIAGNOSTICS + debug(" - Ctrl-Shift-PgUp scroll\n"); +#endif + term_scroll(inst->term, 1, 0); + return true; + } + if (event->keyval == GDK_KEY_Page_Up && + (event->state & GDK_SHIFT_MASK)) { +#ifdef KEY_EVENT_DIAGNOSTICS + debug(" - Shift-PgUp scroll\n"); +#endif + term_scroll(inst->term, 0, -inst->height/2); + return true; + } + if (event->keyval == GDK_KEY_Page_Up && + (event->state & GDK_CONTROL_MASK)) { +#ifdef KEY_EVENT_DIAGNOSTICS + debug(" - Ctrl-PgUp scroll\n"); +#endif + term_scroll(inst->term, 0, -1); + return true; + } + if (event->keyval == GDK_KEY_Page_Down && + ((event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) == + (GDK_CONTROL_MASK | GDK_SHIFT_MASK))) { +#ifdef KEY_EVENT_DIAGNOSTICS + debug(" - Ctrl-shift-PgDn scroll\n"); +#endif + term_scroll(inst->term, -1, 0); + return true; + } + if (event->keyval == GDK_KEY_Page_Down && + (event->state & GDK_SHIFT_MASK)) { +#ifdef KEY_EVENT_DIAGNOSTICS + debug(" - Shift-PgDn scroll\n"); +#endif + term_scroll(inst->term, 0, +inst->height/2); + return true; + } + if (event->keyval == GDK_KEY_Page_Down && + (event->state & GDK_CONTROL_MASK)) { +#ifdef KEY_EVENT_DIAGNOSTICS + debug(" - Ctrl-PgDn scroll\n"); +#endif + term_scroll(inst->term, 0, +1); + return true; + } + + /* + * Neither do Shift-Ins or Ctrl-Ins (if enabled). + */ + if (event->keyval == GDK_KEY_Insert && + (event->state & GDK_SHIFT_MASK)) { + int cfgval = conf_get_int(inst->conf, CONF_ctrlshiftins); + + switch (cfgval) { + case CLIPUI_IMPLICIT: +#ifdef KEY_EVENT_DIAGNOSTICS + debug(" - Shift-Insert: paste from PRIMARY\n"); +#endif + term_request_paste(inst->term, CLIP_PRIMARY); + return true; + case CLIPUI_EXPLICIT: +#ifdef KEY_EVENT_DIAGNOSTICS + debug(" - Shift-Insert: paste from CLIPBOARD\n"); +#endif + term_request_paste(inst->term, CLIP_CLIPBOARD); + return true; + case CLIPUI_CUSTOM: +#ifdef KEY_EVENT_DIAGNOSTICS + debug(" - Shift-Insert: paste from custom clipboard\n"); +#endif + term_request_paste(inst->term, inst->clipboard_ctrlshiftins); + return true; + default: +#ifdef KEY_EVENT_DIAGNOSTICS + debug(" - Shift-Insert: no paste action\n"); +#endif + break; + } + } + if (event->keyval == GDK_KEY_Insert && + (event->state & GDK_CONTROL_MASK)) { + static const int clips_clipboard[] = { CLIP_CLIPBOARD }; + int cfgval = conf_get_int(inst->conf, CONF_ctrlshiftins); + + switch (cfgval) { + case CLIPUI_IMPLICIT: + /* do nothing; re-copy to PRIMARY is not needed */ +#ifdef KEY_EVENT_DIAGNOSTICS + debug(" - Ctrl-Insert: non-copy to PRIMARY\n"); +#endif + return true; + case CLIPUI_EXPLICIT: +#ifdef KEY_EVENT_DIAGNOSTICS + debug(" - Ctrl-Insert: copy to CLIPBOARD\n"); +#endif + term_request_copy(inst->term, + clips_clipboard, lenof(clips_clipboard)); + return true; + case CLIPUI_CUSTOM: +#ifdef KEY_EVENT_DIAGNOSTICS + debug(" - Ctrl-Insert: copy to custom clipboard\n"); +#endif + term_request_copy(inst->term, + &inst->clipboard_ctrlshiftins, 1); + return true; + default: +#ifdef KEY_EVENT_DIAGNOSTICS + debug(" - Ctrl-Insert: no copy action\n"); +#endif + break; + } + } + + /* + * Another pair of copy-paste keys. + */ + if ((event->state & GDK_SHIFT_MASK) && + (event->state & GDK_CONTROL_MASK) && + (event->keyval == GDK_KEY_C || event->keyval == GDK_KEY_c || + event->keyval == GDK_KEY_V || event->keyval == GDK_KEY_v)) { + int cfgval = conf_get_int(inst->conf, CONF_ctrlshiftcv); + bool paste = (event->keyval == GDK_KEY_V || + event->keyval == GDK_KEY_v); + + switch (cfgval) { + case CLIPUI_IMPLICIT: + if (paste) { +#ifdef KEY_EVENT_DIAGNOSTICS + debug(" - Ctrl-Shift-V: paste from PRIMARY\n"); +#endif + term_request_paste(inst->term, CLIP_PRIMARY); + } else { +#ifdef KEY_EVENT_DIAGNOSTICS + debug(" - Ctrl-Shift-C: non-copy to PRIMARY\n"); +#endif + } + return true; + case CLIPUI_EXPLICIT: + if (paste) { +#ifdef KEY_EVENT_DIAGNOSTICS + debug(" - Ctrl-Shift-V: paste from CLIPBOARD\n"); +#endif + term_request_paste(inst->term, CLIP_CLIPBOARD); + } else { + static const int clips[] = { CLIP_CLIPBOARD }; +#ifdef KEY_EVENT_DIAGNOSTICS + debug(" - Ctrl-Shift-C: copy to CLIPBOARD\n"); +#endif + term_request_copy(inst->term, clips, lenof(clips)); + } + return true; + case CLIPUI_CUSTOM: + if (paste) { +#ifdef KEY_EVENT_DIAGNOSTICS + debug(" - Ctrl-Shift-V: paste from custom clipboard\n"); +#endif + term_request_paste(inst->term, + inst->clipboard_ctrlshiftcv); + } else { +#ifdef KEY_EVENT_DIAGNOSTICS + debug(" - Ctrl-Shift-C: copy to custom clipboard\n"); +#endif + term_request_copy(inst->term, + &inst->clipboard_ctrlshiftcv, 1); + } + 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 /* !GTK_CHECK_VERSION(2,0,0) */ + /* + * Most things can now be passed to + * gtk_im_context_filter_keypress without breaking anything + * below this point. An exception is the numeric keypad if + * we're in Nethack or application mode: the IM will eat + * numeric keypad presses if Num Lock is on, but we don't want + * it to. + */ + bool numeric = false; + bool nethack_mode = conf_get_bool(inst->conf, CONF_nethack_keypad); + bool app_keypad_mode = (inst->term->app_keypad_keys && + !conf_get_bool(inst->conf, CONF_no_applic_k)); + + switch (event->keyval) { + case GDK_KEY_Num_Lock: num_keypad_key = 'G'; break; + case GDK_KEY_KP_Divide: num_keypad_key = '/'; break; + case GDK_KEY_KP_Multiply: num_keypad_key = '*'; break; + case GDK_KEY_KP_Subtract: num_keypad_key = '-'; break; + case GDK_KEY_KP_Add: num_keypad_key = '+'; break; + case GDK_KEY_KP_Enter: num_keypad_key = '\r'; break; + case GDK_KEY_KP_0: num_keypad_key = '0'; numeric = true; break; + case GDK_KEY_KP_Insert: num_keypad_key = '0'; break; + case GDK_KEY_KP_1: num_keypad_key = '1'; numeric = true; break; + case GDK_KEY_KP_End: num_keypad_key = '1'; break; + case GDK_KEY_KP_2: num_keypad_key = '2'; numeric = true; break; + case GDK_KEY_KP_Down: num_keypad_key = '2'; break; + case GDK_KEY_KP_3: num_keypad_key = '3'; numeric = true; break; + case GDK_KEY_KP_Page_Down: num_keypad_key = '3'; break; + case GDK_KEY_KP_4: num_keypad_key = '4'; numeric = true; break; + case GDK_KEY_KP_Left: num_keypad_key = '4'; break; + case GDK_KEY_KP_5: num_keypad_key = '5'; numeric = true; break; + case GDK_KEY_KP_Begin: num_keypad_key = '5'; break; + case GDK_KEY_KP_6: num_keypad_key = '6'; numeric = true; break; + case GDK_KEY_KP_Right: num_keypad_key = '6'; break; + case GDK_KEY_KP_7: num_keypad_key = '7'; numeric = true; break; + case GDK_KEY_KP_Home: num_keypad_key = '7'; break; + case GDK_KEY_KP_8: num_keypad_key = '8'; numeric = true; break; + case GDK_KEY_KP_Up: num_keypad_key = '8'; break; + case GDK_KEY_KP_9: num_keypad_key = '9'; numeric = true; break; + case GDK_KEY_KP_Page_Up: num_keypad_key = '9'; break; + case GDK_KEY_KP_Decimal: num_keypad_key = '.'; numeric = true; break; + case GDK_KEY_KP_Delete: num_keypad_key = '.'; break; + } + if ((app_keypad_mode && num_keypad_key && + (numeric || inst->term->funky_type != FUNKY_XTERM)) || + (nethack_mode && num_keypad_key >= '1' && num_keypad_key <= '9')) { + /* In these modes, we override the keypad handling: + * regardless of Num Lock, the keys are handled by + * format_numeric_keypad_key below. */ + force_format_numeric_keypad = true; + } else { + bool try_filter = true; + +#ifdef META_MANUAL_MASK + if (event->state & META_MANUAL_MASK & inst->meta_mod_mask) { + /* + * If this key event had a Meta modifier bit set which + * is also in META_MANUAL_MASK, that means passing + * such an event to the GtkIMContext will be unhelpful + * (it will eat the keystroke and turn it into + * something not what we wanted). + */ +#ifdef KEY_EVENT_DIAGNOSTICS + debug(" - Meta modifier requiring manual intervention, " + "suppressing IM filtering\n"); +#endif + try_filter = false; + } +#endif + + if (try_filter) { +#ifdef KEY_EVENT_DIAGNOSTICS + debug(" - general key press, passing to IM\n"); +#endif + if (gtk_im_context_filter_keypress(inst->imc, event)) { +#ifdef KEY_EVENT_DIAGNOSTICS + debug(" - key press accepted by IM\n"); +#endif + return true; + } else { +#ifdef KEY_EVENT_DIAGNOSTICS + debug(" - key press not accepted by IM\n"); +#endif + } + } + } + + /* + * GDK 2.0 arranges to have done some translation for us: in + * GDK 2.0, event->string is encoded in the current locale. + * + * 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]; + const wchar_t *wp; + int wlen; + int ulen; + + wlen = mb_to_wc(DEFAULT_CODEPAGE, 0, + event_string, strlen(event_string), + widedata, lenof(widedata)-1); + +#ifdef KEY_EVENT_DIAGNOSTICS + { + char *string_string = dupstr(""); + int i; + + for (i = 0; i < wlen; i++) { + char *old = string_string; + string_string = dupprintf("%s%s%04x", string_string, + string_string[0] ? " " : "", + (unsigned)widedata[i]); + sfree(old); + } + debug(" - string translated into Unicode = [%s]\n", + string_string); + sfree(string_string); + } +#endif + + wp = widedata; + ulen = charset_from_unicode(&wp, &wlen, output+1, lenof(output)-2, + CS_UTF8, NULL, NULL, 0); + +#ifdef KEY_EVENT_DIAGNOSTICS + { + char *string_string = dupstr(""); + int i; + + for (i = 0; i < ulen; i++) { + char *old = string_string; + string_string = dupprintf("%s%s%02x", string_string, + string_string[0] ? " " : "", + (unsigned)output[i+1] & 0xFF); + sfree(old); + } + debug(" - string translated into UTF-8 = [%s]\n", + string_string); + sfree(string_string); + } +#endif + + output[1+ulen] = '\0'; + } +#endif /* !GTK_CHECK_VERSION(2,0,0) */ + + if (!output[1] && + (ucsval = keysym_to_unicode(event->keyval)) >= 0) { + ucsoutput[0] = '\033'; + ucsoutput[1] = ucsval; +#ifdef KEY_EVENT_DIAGNOSTICS + debug(" - keysym_to_unicode gave %04x\n", + (unsigned)ucsoutput[1]); +#endif + use_ucsoutput = true; + end = 2; + } else { + output[lenof(output)-1] = '\0'; + end = strlen(output); + } + if (event->state & inst->meta_mod_mask) { + start = 0; + if (end == 1) end = 0; + +#ifdef META_MANUAL_MASK + if (event->state & META_MANUAL_MASK) { + /* + * Key events which have a META_MANUAL_MASK meta bit + * set may have a keyval reflecting that, e.g. on OS X + * the Option key acts as an AltGr-like modifier and + * causes different Unicode characters to be output. + * + * To work around this, we clear the dangerous + * modifier bit and retranslate from the hardware + * keycode as if the key had been pressed without that + * modifier. Then we prefix Esc to *that*. + */ + guint new_keyval; + GdkModifierType consumed; + if (gdk_keymap_translate_keyboard_state + (gdk_keymap_get_for_display(gdk_display_get_default()), + event->hardware_keycode, event->state & ~META_MANUAL_MASK, + 0, &new_keyval, NULL, NULL, &consumed)) { + ucsoutput[0] = '\033'; + ucsoutput[1] = gdk_keyval_to_unicode(new_keyval); +#ifdef KEY_EVENT_DIAGNOSTICS + { + char *keyval_name = dup_keyval_name(new_keyval); + debug(" - retranslation for manual Meta: " + "new keyval = %s, Unicode = %04x\n", + keyval_name, (unsigned)ucsoutput[1]); + sfree(keyval_name); + } +#endif + use_ucsoutput = true; + end = 2; + } + } +#endif + } 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)) { +#ifdef KEY_EVENT_DIAGNOSTICS + debug(" - Ctrl-` special case, translating as 1c\n"); +#endif + output[1] = '\x1C'; + use_ucsoutput = false; + end = 2; + } + + /* Some GTK backends (e.g. Quartz) do not change event->string + * in response to the Control modifier. So we do it ourselves + * here, if it's not already happened. + * + * The translations below are in line with X11 policy as far + * as I know. */ + if ((event->state & GDK_CONTROL_MASK) && end == 2) { + int orig = use_ucsoutput ? ucsoutput[1] : output[1]; + int new = orig; + + if (new >= '3' && new <= '7') { + /* ^3,...,^7 map to 0x1B,...,0x1F */ + new += '\x1B' - '3'; + } else if (new == '2' || new == ' ') { + /* ^2 and ^Space are both ^@, i.e. \0 */ + new = '\0'; + } else if (new == '8') { + /* ^8 is DEL */ + new = '\x7F'; + } else if (new == '/') { + /* ^/ is the same as ^_ */ + new = '\x1F'; + } else if (new >= 0x40 && new < 0x7F) { + /* Everything anywhere near the alphabetics just gets + * masked. */ + new &= 0x1F; + } + /* Anything else, e.g. '0', is unchanged. */ + + if (orig == new) { +#ifdef KEY_EVENT_DIAGNOSTICS + debug(" - manual Ctrl key handling did nothing\n"); +#endif + } else { +#ifdef KEY_EVENT_DIAGNOSTICS + debug(" - manual Ctrl key handling: %02x -> %02x\n", + (unsigned)orig, (unsigned)new); +#endif + output[1] = new; + use_ucsoutput = false; + } + } + + /* Control-Break sends a Break special to the backend */ + if (event->keyval == GDK_KEY_Break && + (event->state & GDK_CONTROL_MASK)) { +#ifdef KEY_EVENT_DIAGNOSTICS + debug(" - Ctrl-Break special case, sending SS_BRK\n"); +#endif + if (inst->backend) + backend_special(inst->backend, SS_BRK, 0); + return true; + } + + /* We handle Return ourselves, because it needs to be flagged as + * special to ldisc. */ + if (event->keyval == GDK_KEY_Return) { + end = return_key(inst, output, &special); + use_ucsoutput = false; + } + + /* 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) { +#ifdef KEY_EVENT_DIAGNOSTICS + debug(" - Ctrl-{space,2,@} special case, translating as 00\n"); +#endif + 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)) { +#ifdef KEY_EVENT_DIAGNOSTICS + debug(" - Ctrl-Shift-space special case, translating as 00a0\n"); +#endif + 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_KEY_BackSpace && + !(event->state & GDK_SHIFT_MASK)) { + output[1] = conf_get_bool(inst->conf, CONF_bksp_is_delete) ? + '\x7F' : '\x08'; +#ifdef KEY_EVENT_DIAGNOSTICS + debug(" - Backspace, translating as %02x\n", + (unsigned)output[1]); +#endif + use_ucsoutput = false; + end = 2; + special = true; + } + /* For Shift Backspace, do opposite of what is configured. */ + if (event->keyval == GDK_KEY_BackSpace && + (event->state & GDK_SHIFT_MASK)) { + output[1] = conf_get_bool(inst->conf, CONF_bksp_is_delete) ? + '\x08' : '\x7F'; +#ifdef KEY_EVENT_DIAGNOSTICS + debug(" - Shift-Backspace, translating as %02x\n", + (unsigned)output[1]); +#endif + use_ucsoutput = false; + end = 2; + special = true; + } + + /* Shift-Tab is ESC [ Z */ + if (event->keyval == GDK_KEY_ISO_Left_Tab || + (event->keyval == GDK_KEY_Tab && + (event->state & GDK_SHIFT_MASK))) { +#ifdef KEY_EVENT_DIAGNOSTICS + debug(" - Shift-Tab, translating as ESC [ Z\n"); +#endif + 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_KEY_Tab && end <= 1) { +#ifdef KEY_EVENT_DIAGNOSTICS + debug(" - Tab, translating as 09\n"); +#endif + output[1] = '\t'; + end = 2; + } + + if (num_keypad_key && force_format_numeric_keypad) { + end = 1 + format_numeric_keypad_key( + output+1, inst->term, num_keypad_key, + event->state & GDK_SHIFT_MASK, + event->state & GDK_CONTROL_MASK); +#ifdef KEY_EVENT_DIAGNOSTICS + debug(" - numeric keypad key"); +#endif + use_ucsoutput = false; + goto done; + } + + switch (event->keyval) { + int fkey_number; + case GDK_KEY_F1: fkey_number = 1; goto numbered_function_key; + case GDK_KEY_F2: fkey_number = 2; goto numbered_function_key; + case GDK_KEY_F3: fkey_number = 3; goto numbered_function_key; + case GDK_KEY_F4: fkey_number = 4; goto numbered_function_key; + case GDK_KEY_F5: fkey_number = 5; goto numbered_function_key; + case GDK_KEY_F6: fkey_number = 6; goto numbered_function_key; + case GDK_KEY_F7: fkey_number = 7; goto numbered_function_key; + case GDK_KEY_F8: fkey_number = 8; goto numbered_function_key; + case GDK_KEY_F9: fkey_number = 9; goto numbered_function_key; + case GDK_KEY_F10: fkey_number = 10; goto numbered_function_key; + case GDK_KEY_F11: fkey_number = 11; goto numbered_function_key; + case GDK_KEY_F12: fkey_number = 12; goto numbered_function_key; + case GDK_KEY_F13: fkey_number = 13; goto numbered_function_key; + case GDK_KEY_F14: fkey_number = 14; goto numbered_function_key; + case GDK_KEY_F15: fkey_number = 15; goto numbered_function_key; + case GDK_KEY_F16: fkey_number = 16; goto numbered_function_key; + case GDK_KEY_F17: fkey_number = 17; goto numbered_function_key; + case GDK_KEY_F18: fkey_number = 18; goto numbered_function_key; + case GDK_KEY_F19: fkey_number = 19; goto numbered_function_key; + case GDK_KEY_F20: fkey_number = 20; goto numbered_function_key; + numbered_function_key: + end = 1 + format_function_key(output+1, inst->term, fkey_number, + event->state & GDK_SHIFT_MASK, + event->state & GDK_CONTROL_MASK); +#ifdef KEY_EVENT_DIAGNOSTICS + debug(" - function key F%d", fkey_number); +#endif + use_ucsoutput = false; + goto done; + + SmallKeypadKey sk_key; + case GDK_KEY_Home: case GDK_KEY_KP_Home: + sk_key = SKK_HOME; goto small_keypad_key; + case GDK_KEY_Insert: case GDK_KEY_KP_Insert: + sk_key = SKK_INSERT; goto small_keypad_key; + case GDK_KEY_Delete: case GDK_KEY_KP_Delete: + sk_key = SKK_DELETE; goto small_keypad_key; + case GDK_KEY_End: case GDK_KEY_KP_End: + sk_key = SKK_END; goto small_keypad_key; + case GDK_KEY_Page_Up: case GDK_KEY_KP_Page_Up: + sk_key = SKK_PGUP; goto small_keypad_key; + case GDK_KEY_Page_Down: case GDK_KEY_KP_Page_Down: + sk_key = SKK_PGDN; goto small_keypad_key; + small_keypad_key: + /* These keys don't generate terminal input with Ctrl */ + if (event->state & GDK_CONTROL_MASK) + break; + + end = 1 + format_small_keypad_key(output+1, inst->term, sk_key); +#ifdef KEY_EVENT_DIAGNOSTICS + debug(" - small keypad key"); +#endif + use_ucsoutput = false; + goto done; + + int xkey; + case GDK_KEY_Up: case GDK_KEY_KP_Up: + xkey = 'A'; goto arrow_key; + case GDK_KEY_Down: case GDK_KEY_KP_Down: + xkey = 'B'; goto arrow_key; + case GDK_KEY_Right: case GDK_KEY_KP_Right: + xkey = 'C'; goto arrow_key; + case GDK_KEY_Left: case GDK_KEY_KP_Left: + xkey = 'D'; goto arrow_key; + case GDK_KEY_Begin: case GDK_KEY_KP_Begin: + xkey = 'G'; goto arrow_key; + arrow_key: + end = 1 + format_arrow_key(output+1, inst->term, xkey, + event->state & GDK_CONTROL_MASK); +#ifdef KEY_EVENT_DIAGNOSTICS + debug(" - arrow key"); +#endif + use_ucsoutput = false; + goto done; + } + + if (num_keypad_key) { + end = 1 + format_numeric_keypad_key( + output+1, inst->term, num_keypad_key, + event->state & GDK_SHIFT_MASK, + event->state & GDK_CONTROL_MASK); +#ifdef KEY_EVENT_DIAGNOSTICS + debug(" - numeric keypad key"); +#endif + + if (end == 1 && num_keypad_key == '\r') { + /* Keypad Enter, lacking any other translation, + * becomes the same special Return code as normal + * Return. */ + end = return_key(inst, output, &special); + use_ucsoutput = false; + } + + use_ucsoutput = false; + goto done; + } + + goto done; + } + + done: + + if (end-start > 0) { + if (special) { +#ifdef KEY_EVENT_DIAGNOSTICS + char *string_string = dupstr(""); + int i; + + for (i = start; i < end; i++) { + char *old = string_string; + string_string = dupprintf("%s%s%02x", string_string, + string_string[0] ? " " : "", + (unsigned)output[i] & 0xFF); + sfree(old); + } + debug(" - final output, special, generic encoding = [%s]\n", + string_string); + sfree(string_string); +#endif + /* + * For special control characters, the character set + * should never matter. + */ + output[end] = '\0'; /* NUL-terminate */ + generated_something = true; + term_keyinput(inst->term, -1, output+start, -2); + } else if (!inst->direct_to_font) { + if (!use_ucsoutput) { +#ifdef KEY_EVENT_DIAGNOSTICS + char *string_string = dupstr(""); + int i; + + for (i = start; i < end; i++) { + char *old = string_string; + string_string = dupprintf("%s%s%02x", string_string, + string_string[0] ? " " : "", + (unsigned)output[i] & 0xFF); + sfree(old); + } + debug(" - final output in %s = [%s]\n", + charset_to_localenc(output_charset), string_string); + sfree(string_string); +#endif + generated_something = true; + term_keyinput(inst->term, output_charset, + output+start, end-start); + } else { +#ifdef KEY_EVENT_DIAGNOSTICS + char *string_string = dupstr(""); + int i; + + for (i = start; i < end; i++) { + char *old = string_string; + string_string = dupprintf("%s%s%04x", string_string, + string_string[0] ? " " : "", + (unsigned)ucsoutput[i]); + sfree(old); + } + debug(" - final output in Unicode = [%s]\n", + string_string); + sfree(string_string); +#endif + + /* + * We generated our own Unicode key data from the + * keysym, so use that instead. + */ + generated_something = true; + term_keyinputw(inst->term, ucsoutput+start, end-start); + } + } else { + /* + * In direct-to-font mode, we just send the string + * exactly as we received it. + */ +#ifdef KEY_EVENT_DIAGNOSTICS + char *string_string = dupstr(""); + int i; + + for (i = start; i < end; i++) { + char *old = string_string; + string_string = dupprintf("%s%s%02x", string_string, + string_string[0] ? " " : "", + (unsigned)output[i] & 0xFF); + sfree(old); + } + debug(" - final output in direct-to-font encoding = [%s]\n", + string_string); + sfree(string_string); +#endif + generated_something = true; + term_keyinput(inst->term, -1, output+start, end-start); + } + + show_mouseptr(inst, false); + } + + if (generated_something) + key_pressed(inst); + return true; +} + +#if GTK_CHECK_VERSION(2,0,0) +void input_method_commit_event(GtkIMContext *imc, gchar *str, gpointer data) +{ + GtkFrontend *inst = (GtkFrontend *)data; + +#ifdef KEY_EVENT_DIAGNOSTICS + char *string_string = dupstr(""); + int i; + + for (i = 0; str[i]; i++) { + char *old = string_string; + string_string = dupprintf("%s%s%02x", string_string, + string_string[0] ? " " : "", + (unsigned)str[i] & 0xFF); + sfree(old); + } + debug(" - IM commit event in UTF-8 = [%s]\n", string_string); + sfree(string_string); +#endif + + term_keyinput(inst->term, CS_UTF8, str, strlen(str)); + show_mouseptr(inst, false); + key_pressed(inst); +} +#endif + +#define SCROLL_INCREMENT_LINES 5 + +#if GTK_CHECK_VERSION(3,4,0) +gboolean scroll_internal(GtkFrontend *inst, gdouble delta, guint state, + gdouble ex, gdouble ey) +{ + int x, y; + bool shift, ctrl, alt, raw_mouse_mode; + + show_mouseptr(inst, true); + + shift = state & GDK_SHIFT_MASK; + ctrl = state & GDK_CONTROL_MASK; + alt = state & inst->meta_mod_mask; + + x = (ex - inst->window_border) / inst->font_width; + y = (ey - inst->window_border) / inst->font_height; + + raw_mouse_mode = (inst->send_raw_mouse && + !(shift && conf_get_bool(inst->conf, + CONF_mouse_override))); + + inst->cumulative_scroll += delta * SCROLL_INCREMENT_LINES; + + if (!raw_mouse_mode) { + int scroll_lines = (int)inst->cumulative_scroll; /* rounds toward 0 */ + if (scroll_lines) { + term_scroll(inst->term, 0, scroll_lines); + inst->cumulative_scroll -= scroll_lines; + } + return true; + } else { + int scroll_events = (int)(inst->cumulative_scroll / + SCROLL_INCREMENT_LINES); + if (scroll_events) { + int button; + + inst->cumulative_scroll -= scroll_events * SCROLL_INCREMENT_LINES; + + if (scroll_events > 0) { + button = MBT_WHEEL_DOWN; + } else { + button = MBT_WHEEL_UP; + scroll_events = -scroll_events; + } + + while (scroll_events-- > 0) { + term_mouse(inst->term, button, translate_button(button), + MA_CLICK, x, y, shift, ctrl, alt); + } + } + return true; + } +} +#endif + +static gboolean button_internal(GtkFrontend *inst, GdkEventButton *event) +{ + bool shift, ctrl, alt, raw_mouse_mode; + int x, y, button, act; + + /* Remember the timestamp. */ + inst->input_event_time = event->time; + + noise_ultralight(NOISE_SOURCE_MOUSEBUTTON, event->button); + + show_mouseptr(inst, true); + + shift = event->state & GDK_SHIFT_MASK; + ctrl = event->state & GDK_CONTROL_MASK; + alt = event->state & inst->meta_mod_mask; + + raw_mouse_mode = (inst->send_raw_mouse && + !(shift && conf_get_bool(inst->conf, + CONF_mouse_override))); + + if (!raw_mouse_mode) { + if (event->button == 4 && event->type == GDK_BUTTON_PRESS) { + term_scroll(inst->term, 0, -SCROLL_INCREMENT_LINES); + return true; + } + if (event->button == 5 && event->type == GDK_BUTTON_PRESS) { + term_scroll(inst->term, 0, +SCROLL_INCREMENT_LINES); + return true; + } + } + + if (event->button == 3 && ctrl) { +#if GTK_CHECK_VERSION(3,22,0) + gtk_menu_popup_at_pointer(GTK_MENU(inst->menu), (GdkEvent *)event); +#else + gtk_menu_popup(GTK_MENU(inst->menu), NULL, NULL, NULL, NULL, + event->button, event->time); +#endif + return true; + } + + if (event->button == 1) + button = MBT_LEFT; + else if (event->button == 2) + button = MBT_MIDDLE; + else if (event->button == 3) + button = MBT_RIGHT; + else if (event->button == 4) + button = MBT_WHEEL_UP; + else if (event->button == 5) + button = MBT_WHEEL_DOWN; + else + return false; /* don't even know what button! */ + + switch (event->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 (raw_mouse_mode && act != MA_CLICK && act != MA_RELEASE) + return true; /* we ignore these in raw mouse mode */ + + x = (event->x - inst->window_border) / inst->font_width; + y = (event->y - inst->window_border) / inst->font_height; + + term_mouse(inst->term, button, translate_button(button), act, + x, y, shift, ctrl, alt); + + return true; +} + +gboolean button_event(GtkWidget *widget, GdkEventButton *event, gpointer data) +{ + GtkFrontend *inst = (GtkFrontend *)data; + return button_internal(inst, event); +} + +#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) +{ + GtkFrontend *inst = (GtkFrontend *)data; + GdkScrollDirection dir; + +#if GTK_CHECK_VERSION(3,4,0) + gdouble dx, dy; + if (gdk_event_get_scroll_deltas((GdkEvent *)event, &dx, &dy)) { + return scroll_internal(inst, dy, event->state, event->x, event->y); + } else if (!gdk_event_get_scroll_direction((GdkEvent *)event, &dir)) { + return false; + } +#else + dir = event->direction; +#endif + + guint button; + GdkEventButton *event_button; + gboolean ret; + + if (dir == GDK_SCROLL_UP) + button = 4; + else if (dir == GDK_SCROLL_DOWN) + button = 5; + else + return false; + + event_button = (GdkEventButton *)gdk_event_new(GDK_BUTTON_PRESS); + event_button->window = g_object_ref(event->window); + event_button->send_event = event->send_event; + event_button->time = event->time; + event_button->x = event->x; + event_button->y = event->y; + event_button->axes = NULL; + event_button->state = event->state; + event_button->button = button; + event_button->device = g_object_ref(event->device); + event_button->x_root = event->x_root; + event_button->y_root = event->y_root; + ret = button_internal(inst, event_button); + gdk_event_free((GdkEvent *)event_button); + return ret; +} +#endif + +gint motion_event(GtkWidget *widget, GdkEventMotion *event, gpointer data) +{ + GtkFrontend *inst = (GtkFrontend *)data; + bool shift, ctrl, alt; + int x, y, button; + + /* Remember the timestamp. */ + inst->input_event_time = event->time; + + noise_ultralight(NOISE_SOURCE_MOUSEPOS, + ((uint32_t)event->x << 16) | (uint32_t)event->y); + + show_mouseptr(inst, true); + + shift = event->state & GDK_SHIFT_MASK; + ctrl = event->state & GDK_CONTROL_MASK; + alt = event->state & inst->meta_mod_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->window_border) / inst->font_width; + y = (event->y - inst->window_border) / inst->font_height; + + term_mouse(inst->term, button, translate_button(button), MA_DRAG, + x, y, shift, ctrl, alt); + + return true; +} + +static void key_pressed(GtkFrontend *inst) +{ + /* + * If our child process has exited but not closed, terminate on + * any keypress. + * + * This is a UI feature specific to GTK PuTTY, because GTK PuTTY + * will (at least sometimes) be running under X, and under X the + * window manager is sometimes absent (very occasionally on + * purpose, more usually temporarily because it's crashed). So + * it's useful to have a way to close an application window + * without depending on protocols like WM_DELETE_WINDOW that are + * typically generated by the WM (e.g. in response to a close + * button in the window frame). + */ + if (inst->exited) + gtk_widget_destroy(inst->window); +} + +static void exit_callback(void *vctx) +{ + GtkFrontend *inst = (GtkFrontend *)vctx; + int exitcode, close_on_exit; + + if (!inst->exited && + (exitcode = backend_exitcode(inst->backend)) >= 0) { + destroy_inst_connection(inst); + + close_on_exit = conf_get_int(inst->conf, CONF_close_on_exit); + if (close_on_exit == FORCE_ON || + (close_on_exit == AUTO && exitcode == 0)) { + gtk_widget_destroy(inst->window); + } + } +} + +static void gtk_seat_notify_remote_exit(Seat *seat) +{ + GtkFrontend *inst = container_of(seat, GtkFrontend, seat); + queue_toplevel_callback(exit_callback, inst); +} + +static void destroy_inst_connection(GtkFrontend *inst) +{ + inst->exited = true; + if (inst->ldisc) { + ldisc_free(inst->ldisc); + inst->ldisc = NULL; + } + if (inst->backend) { + backend_free(inst->backend); + inst->backend = NULL; + } + if (inst->term) + term_provide_backend(inst->term, NULL); + if (inst->menu) { + seat_update_specials_menu(&inst->seat); + gtk_widget_set_sensitive(inst->restartitem, true); + } +} + +static void delete_inst(GtkFrontend *inst) +{ + int dialog_slot; + for (dialog_slot = 0; dialog_slot < DIALOG_SLOT_LIMIT; dialog_slot++) { + if (inst->dialogs[dialog_slot]) { + gtk_widget_destroy(inst->dialogs[dialog_slot]); + inst->dialogs[dialog_slot] = NULL; + } + } + if (inst->window) { + gtk_widget_destroy(inst->window); + inst->window = NULL; + } + if (inst->menu) { + gtk_widget_destroy(inst->menu); + inst->menu = NULL; + } + destroy_inst_connection(inst); + if (inst->term) { + term_free(inst->term); + inst->term = NULL; + } + if (inst->conf) { + conf_free(inst->conf); + inst->conf = NULL; + } + if (inst->logctx) { + log_free(inst->logctx); + inst->logctx = NULL; + } +#if GTK_CHECK_VERSION(2,0,0) + if (inst->trust_sigil_pb) { + g_object_unref(G_OBJECT(inst->trust_sigil_pb)); + inst->trust_sigil_pb = NULL; + } +#else + if (inst->trust_sigil_pm) { + gdk_pixmap_unref(inst->trust_sigil_pm); + inst->trust_sigil_pm = NULL; + } +#endif + +#ifdef JUST_USE_GTK_CLIPBOARD_UTF8 + /* + * Clear up any in-flight clipboard_data_instances. We can't + * actually _free_ them, but we detach them from the inst that's + * about to be destroyed. + */ + while (inst->cdi_headtail.next != &inst->cdi_headtail) { + struct clipboard_data_instance *cdi = inst->cdi_headtail.next; + cdi->state = NULL; + cdi->next->prev = cdi->prev; + cdi->prev->next = cdi->next; + cdi->next = cdi->prev = cdi; + } +#endif + + /* + * Delete any top-level callbacks associated with inst, which + * would otherwise become stale-pointer dereferences waiting to + * happen. We do this last, because some of the above cleanups + * (notably shutting down the backend) might themelves queue such + * callbacks, so we need to make sure they don't do that _after_ + * we're supposed to have cleaned everything up. + */ + delete_callbacks_for_context(inst); + + eventlogstuff_free(inst->eventlogstuff); + + sfree(inst); +} + +void destroy(GtkWidget *widget, gpointer data) +{ + GtkFrontend *inst = (GtkFrontend *)data; + inst->window = NULL; + delete_inst(inst); + session_window_closed(); +} + +gint focus_event(GtkWidget *widget, GdkEventFocus *event, gpointer data) +{ + GtkFrontend *inst = (GtkFrontend *)data; + term_set_focus(inst->term, event->in); + term_update(inst->term); + show_mouseptr(inst, true); + return false; +} + +static void gtk_seat_set_busy_status(Seat *seat, BusyStatus status) +{ + GtkFrontend *inst = container_of(seat, GtkFrontend, seat); + inst->busy_status = status; + update_mouseptr(inst); +} + +static void gtkwin_set_raw_mouse_mode(TermWin *tw, bool activate) +{ + GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); + inst->send_raw_mouse = activate; +} + +static void gtkwin_set_raw_mouse_mode_pointer(TermWin *tw, bool activate) +{ + GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); + inst->pointer_indicates_raw_mouse = activate; + update_mouseptr(inst); +} + +#if GTK_CHECK_VERSION(2,0,0) +static void compute_whole_window_size(GtkFrontend *inst, + int wchars, int hchars, + int *wpix, int *hpix); +#endif + +static void gtkwin_request_resize(TermWin *tw, int w, int h) +{ + GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); + +#if !GTK_CHECK_VERSION(3,0,0) + + 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_pixel_size(inst, &large_x, &large_y); + large_x += 32; + large_y += 32; + + gtk_widget_set_size_request(inst->area, large_x, large_y); + 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->window_border; + area_y = inst->font_height * h + 2*inst->window_border; + + /* + * Now we must set the size request on the drawing area back to + * something sensible before we commit the real resize. Best + * way to do this, I think, is to set it to what the size is + * really going to end up being. + */ + gtk_widget_set_size_request(inst->area, area_x, area_y); +#if GTK_CHECK_VERSION(2,0,0) + gtk_window_resize(GTK_WINDOW(inst->window), + area_x + offset_x, area_y + offset_y); +#else + 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(gtk_widget_get_window(inst->window), + area_x + offset_x, area_y + offset_y); +#endif + +#else /* GTK_CHECK_VERSION(3,0,0) */ + + int wp, hp; + compute_whole_window_size(inst, w, h, &wp, &hp); + gtk_window_resize(GTK_WINDOW(inst->window), wp, hp); + +#endif + +} + +#if GTK_CHECK_VERSION(3,0,0) +char *colour_to_css(const GdkColor *col) +{ + GdkRGBA rgba; + rgba.red = col->red / 65535.0; + rgba.green = col->green / 65535.0; + rgba.blue = col->blue / 65535.0; + rgba.alpha = 1.0; + return gdk_rgba_to_string(&rgba); +} +#endif + +void set_gtk_widget_background(GtkWidget *widget, const GdkColor *col) +{ +#if GTK_CHECK_VERSION(3,0,0) + GtkCssProvider *provider = gtk_css_provider_new(); + char *col_css = colour_to_css(col); + char *data = dupprintf( + "#drawing-area, #top-level { background-color: %s; }\n", col_css); + gtk_css_provider_load_from_data(provider, data, -1, NULL); + GtkStyleContext *context = gtk_widget_get_style_context(widget); + gtk_style_context_add_provider(context, GTK_STYLE_PROVIDER(provider), + GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); + free(data); + free(col_css); +#else + if (gtk_widget_get_window(widget)) { + /* For GTK1, which doesn't have a 'const' on + * gdk_window_set_background's second parameter type. */ + GdkColor col_mutable = *col; + gdk_window_set_background(gtk_widget_get_window(widget), &col_mutable); + } +#endif +} + +void set_window_background(GtkFrontend *inst) +{ + if (inst->area) + set_gtk_widget_background(GTK_WIDGET(inst->area), &inst->cols[258]); + if (inst->window) + set_gtk_widget_background(GTK_WIDGET(inst->window), &inst->cols[258]); +} + +static void gtkwin_palette_set(TermWin *tw, unsigned start, unsigned ncolours, + const rgb *colours) +{ + GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); + + assert(start <= OSC4_NCOLOURS); + assert(ncolours <= OSC4_NCOLOURS - start); + +#if !GTK_CHECK_VERSION(3,0,0) + if (!inst->colmap) { + inst->colmap = gdk_colormap_get_system(); + } else { + gdk_colormap_free_colors(inst->colmap, inst->cols, OSC4_NCOLOURS); + } +#endif + + for (unsigned i = 0; i < ncolours; i++) { + const rgb *in = &colours[i]; + GdkColor *out = &inst->cols[start + i]; + + out->red = in->r * 0x0101; + out->green = in->g * 0x0101; + out->blue = in->b * 0x0101; + } + +#if !GTK_CHECK_VERSION(3,0,0) + { + gboolean success[OSC4_NCOLOURS]; + gdk_colormap_alloc_colors(inst->colmap, inst->cols + start, + ncolours, false, true, success); + for (unsigned i = 0; i < ncolours; i++) { + if (!success[i]) + g_error("%s: couldn't allocate colour %d (#%02x%02x%02x)\n", + appname, start + i, + conf_get_int_int(inst->conf, CONF_colours, i*3+0), + conf_get_int_int(inst->conf, CONF_colours, i*3+1), + conf_get_int_int(inst->conf, CONF_colours, i*3+2)); + } + } +#endif + + if (start <= OSC4_COLOUR_bg && OSC4_COLOUR_bg < start + ncolours) { + /* Default Background has changed, so ensure that space between text + * area and window border is refreshed. */ + set_window_background(inst); + if (inst->area && gtk_widget_get_window(inst->area)) { + draw_backing_rect(inst); + gtk_widget_queue_draw(inst->area); + } + } +} + +static void gtkwin_palette_get_overrides(TermWin *tw) +{ + /* GTK has no analogue of Windows's 'standard system colours', so GTK PuTTY + * has no config option to override the normally configured colours from + * it */ +} + +static struct clipboard_state *clipboard_from_atom( + GtkFrontend *inst, GdkAtom atom) +{ + int i; + + for (i = 0; i < N_CLIPBOARDS; i++) { + struct clipboard_state *state = &inst->clipstates[i]; + if (state->inst == inst && state->atom == atom) + return state; + } + + return NULL; +} + +#ifdef JUST_USE_GTK_CLIPBOARD_UTF8 + +/* ---------------------------------------------------------------------- + * Clipboard handling, using the high-level GtkClipboard interface in + * as hands-off a way as possible. We write and read the clipboard as + * UTF-8 text, and let GTK deal with converting to any other text + * formats it feels like. + */ + +void set_clipboard_atom(GtkFrontend *inst, int clipboard, GdkAtom atom) +{ + struct clipboard_state *state = &inst->clipstates[clipboard]; + + state->inst = inst; + state->clipboard = clipboard; + state->atom = atom; + + if (state->atom != GDK_NONE) { + state->gtkclipboard = gtk_clipboard_get_for_display( + gdk_display_get_default(), state->atom); + g_object_set_data(G_OBJECT(state->gtkclipboard), "user-data", state); + } else { + state->gtkclipboard = NULL; + } +} + +int init_clipboard(GtkFrontend *inst) +{ + set_clipboard_atom(inst, CLIP_PRIMARY, GDK_SELECTION_PRIMARY); + set_clipboard_atom(inst, CLIP_CLIPBOARD, clipboard_atom); + return true; +} + +static void clipboard_provide_data(GtkClipboard *clipboard, + GtkSelectionData *selection_data, + guint info, gpointer data) +{ + struct clipboard_data_instance *cdi = + (struct clipboard_data_instance *)data; + + if (cdi->state && cdi->state->current_cdi == cdi) { + gtk_selection_data_set_text(selection_data, cdi->pasteout_data_utf8, + cdi->pasteout_data_utf8_len); + } +} + +static void clipboard_clear(GtkClipboard *clipboard, gpointer data) +{ + struct clipboard_data_instance *cdi = + (struct clipboard_data_instance *)data; + + if (cdi->state && cdi->state->current_cdi == cdi) { + if (cdi->state->inst && cdi->state->inst->term) { + term_lost_clipboard_ownership(cdi->state->inst->term, + cdi->state->clipboard); + } + cdi->state->current_cdi = NULL; + } + sfree(cdi->pasteout_data_utf8); + cdi->next->prev = cdi->prev; + cdi->prev->next = cdi->next; + sfree(cdi); +} + +static void gtkwin_clip_write( + TermWin *tw, int clipboard, wchar_t *data, int *attr, + truecolour *truecolour, int len, bool must_deselect) +{ + GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); + struct clipboard_state *state = &inst->clipstates[clipboard]; + struct clipboard_data_instance *cdi; + + if (inst->direct_to_font) { + /* In this clipboard mode, we just can't paste if we're in + * direct-to-font mode. Fortunately, that shouldn't be + * important, because we'll only use this clipboard handling + * code on systems where that kind of font doesn't exist + * anyway. */ + return; + } + + if (!state->gtkclipboard) + return; + + cdi = snew(struct clipboard_data_instance); + cdi->state = state; + state->current_cdi = cdi; + cdi->pasteout_data_utf8 = snewn(len*6, char); + cdi->prev = inst->cdi_headtail.prev; + cdi->next = &inst->cdi_headtail; + cdi->next->prev = cdi; + cdi->prev->next = cdi; + { + const wchar_t *tmp = data; + int tmplen = len; + cdi->pasteout_data_utf8_len = + charset_from_unicode(&tmp, &tmplen, cdi->pasteout_data_utf8, + len*6, CS_UTF8, NULL, NULL, 0); + } + + /* + * It would be nice to just call gtk_clipboard_set_text() in place + * of all of the faffing below. Unfortunately, that won't give me + * access to the clipboard-clear event, which we use to visually + * deselect text in the terminal. + */ + { + GtkTargetList *targetlist; + GtkTargetEntry *targettable; + gint n_targets; + + targetlist = gtk_target_list_new(NULL, 0); + gtk_target_list_add_text_targets(targetlist, 0); + targettable = gtk_target_table_new_from_list(targetlist, &n_targets); + gtk_clipboard_set_with_data(state->gtkclipboard, targettable, + n_targets, clipboard_provide_data, + clipboard_clear, cdi); + gtk_target_table_free(targettable, n_targets); + gtk_target_list_unref(targetlist); + } +} + +static void clipboard_text_received(GtkClipboard *clipboard, + const gchar *text, gpointer data) +{ + GtkFrontend *inst = (GtkFrontend *)data; + wchar_t *paste; + int paste_len; + int length; + + if (!text) + return; + + length = strlen(text); + + paste = snewn(length, wchar_t); + paste_len = mb_to_wc(CS_UTF8, 0, text, length, paste, length); + + term_do_paste(inst->term, paste, paste_len); + + sfree(paste); +} + +static void gtkwin_clip_request_paste(TermWin *tw, int clipboard) +{ + GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); + struct clipboard_state *state = &inst->clipstates[clipboard]; + + if (!state->gtkclipboard) + return; + + gtk_clipboard_request_text(state->gtkclipboard, + clipboard_text_received, inst); +} + +#else /* JUST_USE_GTK_CLIPBOARD_UTF8 */ + +/* ---------------------------------------------------------------------- + * Clipboard handling for X, using the low-level gtk_selection_* + * interface, handling conversions to fiddly things like compound text + * ourselves, and storing in X cut buffers too. + * + * This version of the clipboard code has to be kept around for GTK1, + * which doesn't have the higher-level GtkClipboard interface at all. + * And since it works on GTK2 and GTK3 too and has had a good few + * years of shakedown and bug fixing, we might as well keep using it + * where it's applicable. + * + * It's _possible_ that we might be able to replicate all the + * important wrinkles of this code in GtkClipboard. (In particular, + * cut buffers or local analogue look as if they might be accessible + * via gtk_clipboard_set_can_store(), and delivering text in + * non-Unicode formats only in the direct-to-font case ought to be + * possible if we can figure out the right set of things to put in the + * GtkTargetList.) But that work can wait until there's a need for it! + */ + +#ifndef NOT_X_WINDOWS + +/* Store the data in a cut-buffer. */ +static void store_cutbuffer(GtkFrontend *inst, char *ptr, int len) +{ + if (inst->disp) { + /* ICCCM says we must rotate the buffers before storing to buffer 0. */ + XRotateBuffers(inst->disp, 1); + XStoreBytes(inst->disp, ptr, len); + } +} + +/* Retrieve data from a cut-buffer. + * Returned data needs to be freed with XFree(). + */ +static char *retrieve_cutbuffer(GtkFrontend *inst, int *nbytes) +{ + char *ptr; + if (!inst->disp) { + *nbytes = 0; + return NULL; + } + ptr = XFetchBytes(inst->disp, nbytes); + if (*nbytes <= 0 && ptr != 0) { + XFree(ptr); + ptr = 0; + } + return ptr; +} + +#endif /* NOT_X_WINDOWS */ + +static void gtkwin_clip_write( + TermWin *tw, int clipboard, wchar_t *data, int *attr, + truecolour *truecolour, int len, bool must_deselect) +{ + GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); + struct clipboard_state *state = &inst->clipstates[clipboard]; + + if (state->pasteout_data) + sfree(state->pasteout_data); + if (state->pasteout_data_ctext) + sfree(state->pasteout_data_ctext); + if (state->pasteout_data_utf8) + sfree(state->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) { + const wchar_t *tmp = data; + int tmplen = len; +#ifndef NOT_X_WINDOWS + XTextProperty tp; + char *list[1]; +#endif + + state->pasteout_data_utf8 = snewn(len*6, char); + state->pasteout_data_utf8_len = len*6; + state->pasteout_data_utf8_len = + charset_from_unicode(&tmp, &tmplen, state->pasteout_data_utf8, + state->pasteout_data_utf8_len, + CS_UTF8, NULL, NULL, 0); + if (state->pasteout_data_utf8_len == 0) { + sfree(state->pasteout_data_utf8); + state->pasteout_data_utf8 = NULL; + } else { + state->pasteout_data_utf8 = + sresize(state->pasteout_data_utf8, + state->pasteout_data_utf8_len + 1, char); + state->pasteout_data_utf8[state->pasteout_data_utf8_len] = '\0'; + } + + /* + * Now let Xlib convert our UTF-8 data into compound text. + */ +#ifndef NOT_X_WINDOWS + list[0] = state->pasteout_data_utf8; + if (inst->disp && Xutf8TextListToTextProperty( + inst->disp, list, 1, XCompoundTextStyle, &tp) == 0) { + state->pasteout_data_ctext = snewn(tp.nitems+1, char); + memcpy(state->pasteout_data_ctext, tp.value, tp.nitems); + state->pasteout_data_ctext_len = tp.nitems; + XFree(tp.value); + } else +#endif + { + state->pasteout_data_ctext = NULL; + state->pasteout_data_ctext_len = 0; + } + } else { + state->pasteout_data_utf8 = NULL; + state->pasteout_data_utf8_len = 0; + state->pasteout_data_ctext = NULL; + state->pasteout_data_ctext_len = 0; + } + + state->pasteout_data = snewn(len*6, char); + state->pasteout_data_len = len*6; + state->pasteout_data_len = wc_to_mb(inst->ucsdata.line_codepage, 0, + data, len, state->pasteout_data, + state->pasteout_data_len, + NULL, NULL); + if (state->pasteout_data_len == 0) { + sfree(state->pasteout_data); + state->pasteout_data = NULL; + } else { + state->pasteout_data = + sresize(state->pasteout_data, state->pasteout_data_len, char); + } + +#ifndef NOT_X_WINDOWS + /* The legacy X cut buffers go with PRIMARY, not any other clipboard */ + if (state->atom == GDK_SELECTION_PRIMARY) + store_cutbuffer(inst, state->pasteout_data, state->pasteout_data_len); +#endif + + if (gtk_selection_owner_set(inst->area, state->atom, + inst->input_event_time)) { +#if GTK_CHECK_VERSION(2,0,0) + gtk_selection_clear_targets(inst->area, state->atom); +#endif + gtk_selection_add_target(inst->area, state->atom, + GDK_SELECTION_TYPE_STRING, 1); + if (state->pasteout_data_ctext) + gtk_selection_add_target(inst->area, state->atom, + compound_text_atom, 1); + if (state->pasteout_data_utf8) + gtk_selection_add_target(inst->area, state->atom, + utf8_string_atom, 1); + } + + if (must_deselect) + term_lost_clipboard_ownership(inst->term, clipboard); +} + +static void selection_get(GtkWidget *widget, GtkSelectionData *seldata, + guint info, guint time_stamp, gpointer data) +{ + GtkFrontend *inst = (GtkFrontend *)data; + GdkAtom target = gtk_selection_data_get_target(seldata); + struct clipboard_state *state = clipboard_from_atom( + inst, gtk_selection_data_get_selection(seldata)); + + if (!state) + return; + + if (target == utf8_string_atom) + gtk_selection_data_set(seldata, target, 8, + (unsigned char *)state->pasteout_data_utf8, + state->pasteout_data_utf8_len); + else if (target == compound_text_atom) + gtk_selection_data_set(seldata, target, 8, + (unsigned char *)state->pasteout_data_ctext, + state->pasteout_data_ctext_len); + else + gtk_selection_data_set(seldata, target, 8, + (unsigned char *)state->pasteout_data, + state->pasteout_data_len); +} + +static gint selection_clear(GtkWidget *widget, GdkEventSelection *seldata, + gpointer data) +{ + GtkFrontend *inst = (GtkFrontend *)data; + struct clipboard_state *state = clipboard_from_atom( + inst, seldata->selection); + + if (!state) + return true; + + term_lost_clipboard_ownership(inst->term, state->clipboard); + if (state->pasteout_data) + sfree(state->pasteout_data); + if (state->pasteout_data_ctext) + sfree(state->pasteout_data_ctext); + if (state->pasteout_data_utf8) + sfree(state->pasteout_data_utf8); + state->pasteout_data = NULL; + state->pasteout_data_len = 0; + state->pasteout_data_ctext = NULL; + state->pasteout_data_ctext_len = 0; + state->pasteout_data_utf8 = NULL; + state->pasteout_data_utf8_len = 0; + return true; +} + +static void gtkwin_clip_request_paste(TermWin *tw, int clipboard) +{ + GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); + struct clipboard_state *state = &inst->clipstates[clipboard]; + + /* + * 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, state->atom, 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, state->atom, + GDK_SELECTION_TYPE_STRING, + inst->input_event_time); + } +} + +static void selection_received(GtkWidget *widget, GtkSelectionData *seldata, + guint time, gpointer data) +{ + GtkFrontend *inst = (GtkFrontend *)data; + char *text; + int length; +#ifndef NOT_X_WINDOWS + char **list; + bool free_list_required = false; + bool free_required = false; +#endif + int charset; + GdkAtom seldata_target = gtk_selection_data_get_target(seldata); + GdkAtom seldata_type = gtk_selection_data_get_data_type(seldata); + const guchar *seldata_data = gtk_selection_data_get_data(seldata); + gint seldata_length = gtk_selection_data_get_length(seldata); + wchar_t *paste; + int paste_len; + struct clipboard_state *state = clipboard_from_atom( + inst, gtk_selection_data_get_selection(seldata)); + + if (!state) + return; + + 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, state->atom, + 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, state->atom, + 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) { +#ifndef NOT_X_WINDOWS + text = retrieve_cutbuffer(inst, &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 = true; +#else + return; +#endif + } else { + /* + * Convert COMPOUND_TEXT into UTF-8. + */ + if (seldata_type == compound_text_atom) { +#ifndef NOT_X_WINDOWS + XTextProperty tp; + int ret, count; + + tp.value = (unsigned char *)seldata_data; + tp.encoding = (Atom) seldata_type; + tp.format = gtk_selection_data_get_format(seldata); + tp.nitems = seldata_length; + ret = inst->disp == NULL ? -1 : + Xutf8TextPropertyToTextList(inst->disp, &tp, &list, &count); + if (ret == 0 && count == 1) { + text = list[0]; + length = strlen(list[0]); + charset = CS_UTF8; + free_list_required = true; + } else +#endif + { + /* + * Compound text failed; fall back to STRING. + */ + gtk_selection_convert(inst->area, state->atom, + GDK_SELECTION_TYPE_STRING, + inst->input_event_time); + return; + } + } else { + text = (char *)seldata_data; + length = seldata_length; + charset = (seldata_type == utf8_string_atom ? + CS_UTF8 : inst->ucsdata.line_codepage); + } + } + + paste = snewn(length, wchar_t); + paste_len = mb_to_wc(charset, 0, text, length, paste, length); + + term_do_paste(inst->term, paste, paste_len); + + sfree(paste); + +#ifndef NOT_X_WINDOWS + if (free_list_required) + XFreeStringList(list); + if (free_required) + XFree(text); +#endif +} + +static void init_one_clipboard(GtkFrontend *inst, int clipboard) +{ + struct clipboard_state *state = &inst->clipstates[clipboard]; + + state->inst = inst; + state->clipboard = clipboard; +} + +void set_clipboard_atom(GtkFrontend *inst, int clipboard, GdkAtom atom) +{ + struct clipboard_state *state = &inst->clipstates[clipboard]; + + state->inst = inst; + state->clipboard = clipboard; + + state->atom = atom; +} + +void init_clipboard(GtkFrontend *inst) +{ +#ifndef NOT_X_WINDOWS + /* + * Ensure that all the cut buffers exist - according to the ICCCM, + * we must do this before we start using cut buffers. + */ + if (inst->disp) { + unsigned char empty[] = ""; + x11_ignore_error(inst->disp, BadMatch); + XChangeProperty(inst->disp, GDK_ROOT_WINDOW(), XA_CUT_BUFFER0, + XA_STRING, 8, PropModeAppend, empty, 0); + x11_ignore_error(inst->disp, BadMatch); + XChangeProperty(inst->disp, GDK_ROOT_WINDOW(), XA_CUT_BUFFER1, + XA_STRING, 8, PropModeAppend, empty, 0); + x11_ignore_error(inst->disp, BadMatch); + XChangeProperty(inst->disp, GDK_ROOT_WINDOW(), XA_CUT_BUFFER2, + XA_STRING, 8, PropModeAppend, empty, 0); + x11_ignore_error(inst->disp, BadMatch); + XChangeProperty(inst->disp, GDK_ROOT_WINDOW(), XA_CUT_BUFFER3, + XA_STRING, 8, PropModeAppend, empty, 0); + x11_ignore_error(inst->disp, BadMatch); + XChangeProperty(inst->disp, GDK_ROOT_WINDOW(), XA_CUT_BUFFER4, + XA_STRING, 8, PropModeAppend, empty, 0); + x11_ignore_error(inst->disp, BadMatch); + XChangeProperty(inst->disp, GDK_ROOT_WINDOW(), XA_CUT_BUFFER5, + XA_STRING, 8, PropModeAppend, empty, 0); + x11_ignore_error(inst->disp, BadMatch); + XChangeProperty(inst->disp, GDK_ROOT_WINDOW(), XA_CUT_BUFFER6, + XA_STRING, 8, PropModeAppend, empty, 0); + x11_ignore_error(inst->disp, BadMatch); + XChangeProperty(inst->disp, GDK_ROOT_WINDOW(), XA_CUT_BUFFER7, + XA_STRING, 8, PropModeAppend, empty, 0); + } +#endif + + inst->clipstates[CLIP_PRIMARY].atom = GDK_SELECTION_PRIMARY; + inst->clipstates[CLIP_CLIPBOARD].atom = clipboard_atom; + init_one_clipboard(inst, CLIP_PRIMARY); + init_one_clipboard(inst, CLIP_CLIPBOARD); + + g_signal_connect(G_OBJECT(inst->area), "selection_received", + G_CALLBACK(selection_received), inst); + g_signal_connect(G_OBJECT(inst->area), "selection_get", + G_CALLBACK(selection_get), inst); + g_signal_connect(G_OBJECT(inst->area), "selection_clear_event", + G_CALLBACK(selection_clear), inst); +} + +/* + * End of selection/clipboard handling. + * ---------------------------------------------------------------------- + */ + +#endif /* JUST_USE_GTK_CLIPBOARD_UTF8 */ + +static void set_window_titles(GtkFrontend *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 (!conf_get_bool(inst->conf, CONF_win_name_always)) + gdk_window_set_icon_name(gtk_widget_get_window(inst->window), + inst->icontitle); +} + +static void gtkwin_set_title(TermWin *tw, const char *title) +{ + GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); + sfree(inst->wintitle); + inst->wintitle = dupstr(title); + set_window_titles(inst); +} + +static void gtkwin_set_icon_title(TermWin *tw, const char *title) +{ + GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); + sfree(inst->icontitle); + inst->icontitle = dupstr(title); + set_window_titles(inst); +} + +static void gtkwin_set_scrollbar(TermWin *tw, int total, int start, int page) +{ + GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); + if (!conf_get_bool(inst->conf, CONF_scrollbar)) + return; + inst->ignore_sbar = true; + gtk_adjustment_set_lower(inst->sbar_adjust, 0); + gtk_adjustment_set_upper(inst->sbar_adjust, total); + gtk_adjustment_set_value(inst->sbar_adjust, start); + gtk_adjustment_set_page_size(inst->sbar_adjust, page); + gtk_adjustment_set_step_increment(inst->sbar_adjust, 1); + gtk_adjustment_set_page_increment(inst->sbar_adjust, page/2); +#if !GTK_CHECK_VERSION(3,18,0) + gtk_adjustment_changed(inst->sbar_adjust); +#endif + inst->ignore_sbar = false; +} + +void scrollbar_moved(GtkAdjustment *adj, GtkFrontend *inst) +{ + if (!conf_get_bool(inst->conf, CONF_scrollbar)) + return; + if (!inst->ignore_sbar) + term_scroll(inst->term, 1, (int)gtk_adjustment_get_value(adj)); +} + +static void show_scrollbar(GtkFrontend *inst, gboolean visible) +{ + inst->sbar_visible = visible; + if (visible) + gtk_widget_show(inst->sbar); + else + gtk_widget_hide(inst->sbar); +} + +static void gtkwin_set_cursor_pos(TermWin *tw, 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). + */ +static void gtkwin_bell(TermWin *tw, int mode) +{ + /* GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); */ + if (mode == BELL_DEFAULT) + gdk_display_beep(gdk_display_get_default()); +} + +static int gtkwin_char_width(TermWin *tw, int uc) +{ + /* + * In this front end, double-width characters are handled using a + * separate font, so this can safely just return 1 always. + */ + return 1; +} + +static bool gtkwin_setup_draw_ctx(TermWin *tw) +{ + GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); + + if (!gtk_widget_get_window(inst->area)) + return false; + + inst->uctx.type = inst->drawtype; +#ifdef DRAW_TEXT_GDK + if (inst->uctx.type == DRAWTYPE_GDK) { + /* If we're doing GDK-based drawing, then we also expect + * inst->pixmap to exist. */ + inst->uctx.u.gdk.target = inst->pixmap; + inst->uctx.u.gdk.gc = gdk_gc_new(gtk_widget_get_window(inst->area)); + } +#endif +#ifdef DRAW_TEXT_CAIRO + if (inst->uctx.type == DRAWTYPE_CAIRO) { + inst->uctx.u.cairo.widget = GTK_WIDGET(inst->area); + /* If we're doing Cairo drawing, we expect inst->surface to + * exist, and we draw to that first, regardless of whether we + * subsequently copy the results to inst->pixmap. */ + inst->uctx.u.cairo.cr = cairo_create(inst->surface); + cairo_scale(inst->uctx.u.cairo.cr, inst->scale, inst->scale); + cairo_setup_draw_ctx(inst); + } +#endif + return true; +} + +static void gtkwin_free_draw_ctx(TermWin *tw) +{ + GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); +#ifdef DRAW_TEXT_GDK + if (inst->uctx.type == DRAWTYPE_GDK) { + gdk_gc_unref(inst->uctx.u.gdk.gc); + } +#endif +#ifdef DRAW_TEXT_CAIRO + if (inst->uctx.type == DRAWTYPE_CAIRO) { + cairo_destroy(inst->uctx.u.cairo.cr); + } +#endif +} + + +static void draw_update(GtkFrontend *inst, int x, int y, int w, int h) +{ +#if defined DRAW_TEXT_CAIRO && !defined NO_BACKING_PIXMAPS + if (inst->uctx.type == DRAWTYPE_CAIRO) { + /* + * If inst->surface and inst->pixmap both exist, then we've + * just drawn new content to the former which we must copy to + * the latter. + */ + cairo_t *cr = gdk_cairo_create(inst->pixmap); + cairo_set_source_surface(cr, inst->surface, 0, 0); + cairo_rectangle(cr, x, y, w, h); + cairo_fill(cr); + cairo_destroy(cr); + } +#endif + + /* + * Now we just queue a window redraw, which will cause + * inst->surface or inst->pixmap (whichever is appropriate for our + * compile mode) to be copied to the real window when we receive + * the resulting "expose" or "draw" event. + * + * Amazingly, this one API call is actually valid in all versions + * of GTK :-) + */ + gtk_widget_queue_draw_area(inst->area, x, y, w, h); +} + +#ifdef DRAW_TEXT_CAIRO +static void cairo_set_source_rgb_dim(cairo_t *cr, double r, double g, double b, + bool dim) +{ + if (dim) + cairo_set_source_rgb(cr, r * 2 / 3, g * 2 / 3, b * 2 / 3); + else + cairo_set_source_rgb(cr, r, g, b); +} +#endif + +static void draw_set_colour(GtkFrontend *inst, int col, bool dim) +{ +#ifdef DRAW_TEXT_GDK + if (inst->uctx.type == DRAWTYPE_GDK) { + if (dim) { +#if GTK_CHECK_VERSION(2,0,0) + GdkColor color; + color.red = inst->cols[col].red * 2 / 3; + color.green = inst->cols[col].green * 2 / 3; + color.blue = inst->cols[col].blue * 2 / 3; + gdk_gc_set_rgb_fg_color(inst->uctx.u.gdk.gc, &color); +#else + /* Poor GTK1 fallback */ + gdk_gc_set_foreground(inst->uctx.u.gdk.gc, &inst->cols[col]); +#endif + } else { + gdk_gc_set_foreground(inst->uctx.u.gdk.gc, &inst->cols[col]); + } + } +#endif +#ifdef DRAW_TEXT_CAIRO + if (inst->uctx.type == DRAWTYPE_CAIRO) { + cairo_set_source_rgb_dim(inst->uctx.u.cairo.cr, + inst->cols[col].red / 65535.0, + inst->cols[col].green / 65535.0, + inst->cols[col].blue / 65535.0, dim); + } +#endif +} + +static void draw_set_colour_rgb(GtkFrontend *inst, optionalrgb orgb, bool dim) +{ +#ifdef DRAW_TEXT_GDK + if (inst->uctx.type == DRAWTYPE_GDK) { +#if GTK_CHECK_VERSION(2,0,0) + GdkColor color; + color.red = orgb.r * 256; + color.green = orgb.g * 256; + color.blue = orgb.b * 256; + if (dim) { + color.red = color.red * 2 / 3; + color.green = color.green * 2 / 3; + color.blue = color.blue * 2 / 3; + } + gdk_gc_set_rgb_fg_color(inst->uctx.u.gdk.gc, &color); +#else + /* Poor GTK1 fallback */ + gdk_gc_set_foreground(inst->uctx.u.gdk.gc, &inst->cols[256]); +#endif + } +#endif +#ifdef DRAW_TEXT_CAIRO + if (inst->uctx.type == DRAWTYPE_CAIRO) { + cairo_set_source_rgb_dim(inst->uctx.u.cairo.cr, orgb.r / 255.0, + orgb.g / 255.0, orgb.b / 255.0, dim); + } +#endif +} + +static void draw_rectangle(GtkFrontend *inst, bool filled, + int x, int y, int w, int h) +{ +#ifdef DRAW_TEXT_GDK + if (inst->uctx.type == DRAWTYPE_GDK) { + gdk_draw_rectangle(inst->uctx.u.gdk.target, inst->uctx.u.gdk.gc, + filled, x, y, w, h); + } +#endif +#ifdef DRAW_TEXT_CAIRO + if (inst->uctx.type == DRAWTYPE_CAIRO) { + cairo_new_path(inst->uctx.u.cairo.cr); + if (filled) { + cairo_rectangle(inst->uctx.u.cairo.cr, x, y, w, h); + cairo_fill(inst->uctx.u.cairo.cr); + } else { + cairo_rectangle(inst->uctx.u.cairo.cr, + x + 0.5, y + 0.5, w, h); + cairo_close_path(inst->uctx.u.cairo.cr); + cairo_stroke(inst->uctx.u.cairo.cr); + } + } +#endif +} + +static void draw_clip(GtkFrontend *inst, int x, int y, int w, int h) +{ +#ifdef DRAW_TEXT_GDK + if (inst->uctx.type == DRAWTYPE_GDK) { + GdkRectangle r; + + r.x = x; + r.y = y; + r.width = w; + r.height = h; + + gdk_gc_set_clip_rectangle(inst->uctx.u.gdk.gc, &r); + } +#endif +#ifdef DRAW_TEXT_CAIRO + if (inst->uctx.type == DRAWTYPE_CAIRO) { + cairo_reset_clip(inst->uctx.u.cairo.cr); + cairo_new_path(inst->uctx.u.cairo.cr); + cairo_rectangle(inst->uctx.u.cairo.cr, x, y, w, h); + cairo_clip(inst->uctx.u.cairo.cr); + } +#endif +} + +static void draw_point(GtkFrontend *inst, int x, int y) +{ +#ifdef DRAW_TEXT_GDK + if (inst->uctx.type == DRAWTYPE_GDK) { + gdk_draw_point(inst->uctx.u.gdk.target, inst->uctx.u.gdk.gc, x, y); + } +#endif +#ifdef DRAW_TEXT_CAIRO + if (inst->uctx.type == DRAWTYPE_CAIRO) { + cairo_new_path(inst->uctx.u.cairo.cr); + cairo_rectangle(inst->uctx.u.cairo.cr, x, y, 1, 1); + cairo_fill(inst->uctx.u.cairo.cr); + } +#endif +} + +static void draw_line(GtkFrontend *inst, int x0, int y0, int x1, int y1) +{ +#ifdef DRAW_TEXT_GDK + if (inst->uctx.type == DRAWTYPE_GDK) { + gdk_draw_line(inst->uctx.u.gdk.target, inst->uctx.u.gdk.gc, + x0, y0, x1, y1); + } +#endif +#ifdef DRAW_TEXT_CAIRO + if (inst->uctx.type == DRAWTYPE_CAIRO) { + cairo_new_path(inst->uctx.u.cairo.cr); + cairo_move_to(inst->uctx.u.cairo.cr, x0 + 0.5, y0 + 0.5); + cairo_line_to(inst->uctx.u.cairo.cr, x1 + 0.5, y1 + 0.5); + cairo_stroke(inst->uctx.u.cairo.cr); + } +#endif +} + +static void draw_stretch_before(GtkFrontend *inst, int x, int y, + int w, bool wdouble, + int h, bool hdouble, bool hbothalf) +{ +#ifdef DRAW_TEXT_CAIRO + if (inst->uctx.type == DRAWTYPE_CAIRO) { + cairo_matrix_t matrix; + + matrix.xy = 0; + matrix.yx = 0; + + if (wdouble) { + matrix.xx = 2; + matrix.x0 = -x; + } else { + matrix.xx = 1; + matrix.x0 = 0; + } + + if (hdouble) { + matrix.yy = 2; + if (hbothalf) { + matrix.y0 = -(y+h); + } else { + matrix.y0 = -y; + } + } else { + matrix.yy = 1; + matrix.y0 = 0; + } + cairo_transform(inst->uctx.u.cairo.cr, &matrix); + } +#endif +} + +static void draw_stretch_after(GtkFrontend *inst, int x, int y, + int w, bool wdouble, + int h, bool hdouble, bool hbothalf) +{ +#ifdef DRAW_TEXT_GDK +#ifndef NO_BACKING_PIXMAPS + if (inst->uctx.type == DRAWTYPE_GDK) { + /* + * 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; + if (wdouble) { + for (i = 0; i < w; i++) { + gdk_draw_pixmap(inst->uctx.u.gdk.target, + inst->uctx.u.gdk.gc, + inst->uctx.u.gdk.target, + x + 2*i, y, + x + 2*i+1, y, + w - i, h); + } + w *= 2; + } + + if (hdouble) { + int dt, db; + /* Now stretch vertically, in the same way. */ + if (hbothalf) + dt = 0, db = 1; + else + dt = 1, db = 0; + for (i = 0; i < h; i += 2) { + gdk_draw_pixmap(inst->uctx.u.gdk.target, + inst->uctx.u.gdk.gc, + inst->uctx.u.gdk.target, + x, y + dt*i + db, + x, y + dt*(i+1), + w, h-i-1); + } + } + } +#else +#error No way to implement stretching in GDK without a reliable backing pixmap +#endif +#endif /* DRAW_TEXT_GDK */ +#ifdef DRAW_TEXT_CAIRO + if (inst->uctx.type == DRAWTYPE_CAIRO) { + cairo_set_matrix(inst->uctx.u.cairo.cr, + &inst->uctx.u.cairo.origmatrix); + } +#endif +} + +static void draw_backing_rect(GtkFrontend *inst) +{ + int w, h; + + if (!win_setup_draw_ctx(&inst->termwin)) + return; + + w = inst->width * inst->font_width + 2*inst->window_border; + h = inst->height * inst->font_height + 2*inst->window_border; + draw_set_colour(inst, 258, false); + draw_rectangle(inst, true, 0, 0, w, h); + draw_update(inst, 0, 0, w, h); + win_free_draw_ctx(&inst->termwin); +} + +/* + * 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'. + */ +static void do_text_internal( + GtkFrontend *inst, int x, int y, wchar_t *text, int len, + unsigned long attr, int lattr, truecolour truecolour) +{ + int ncombining; + int nfg, nbg, t, fontid, rlen, widefactor; + bool bold; + bool monochrome = + gdk_visual_get_depth(gtk_widget_get_visual(inst->area)) == 1; + + if (attr & TATTR_COMBINING) { + ncombining = len; + len = 1; + } else + ncombining = 1; + + if (monochrome) + truecolour.fg = truecolour.bg = optionalrgb_none; + + 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))) { + struct optionalrgb trgb; + + t = nfg; + nfg = nbg; + nbg = t; + + trgb = truecolour.fg; + truecolour.fg = truecolour.bg; + truecolour.bg = trgb; + } + if ((inst->bold_style & 2) && (attr & ATTR_BOLD)) { + if (nfg < 16) nfg |= 8; + else if (nfg >= 256) nfg |= 1; + } + if ((inst->bold_style & 2) && (attr & ATTR_BLINK)) { + if (nbg < 16) nbg |= 8; + else if (nbg >= 256) nbg |= 1; + } + if ((attr & TATTR_ACTCURS) && !monochrome) { + truecolour.fg = truecolour.bg = optionalrgb_none; + nfg = 260; + nbg = 261; + attr &= ~ATTR_DIM; /* don't dim the cursor */ + } + + fontid = 0; + + if (attr & ATTR_WIDE) { + widefactor = 2; + fontid |= 2; + } else { + widefactor = 1; + } + + if ((attr & ATTR_BOLD) && (inst->bold_style & 1)) { + bold = true; + fontid |= 1; + } else { + bold = false; + } + + 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 */ + if (len == 0) + return; /* rounded down half a double-width char to zero */ + } + rlen = len * 2; + } else + rlen = len; + + draw_clip(inst, + x*inst->font_width+inst->window_border, + y*inst->font_height+inst->window_border, + rlen*widefactor*inst->font_width, + inst->font_height); + + if ((lattr & LATTR_MODE) != LATTR_NORM) { + draw_stretch_before(inst, + x*inst->font_width+inst->window_border, + y*inst->font_height+inst->window_border, + rlen*widefactor*inst->font_width, true, + inst->font_height, + ((lattr & LATTR_MODE) != LATTR_WIDE), + ((lattr & LATTR_MODE) == LATTR_BOT)); + } + + if (truecolour.bg.enabled) + draw_set_colour_rgb(inst, truecolour.bg, attr & ATTR_DIM); + else + draw_set_colour(inst, nbg, attr & ATTR_DIM); + draw_rectangle(inst, true, + x*inst->font_width+inst->window_border, + y*inst->font_height+inst->window_border, + rlen*widefactor*inst->font_width, inst->font_height); + + if (truecolour.fg.enabled) + draw_set_colour_rgb(inst, truecolour.fg, attr & ATTR_DIM); + else + draw_set_colour(inst, nfg, attr & ATTR_DIM); + if (ncombining > 1) { + assert(len == 1); + unifont_draw_combining(&inst->uctx, inst->fonts[fontid], + x*inst->font_width+inst->window_border, + (y*inst->font_height+inst->window_border+ + inst->fonts[0]->ascent), + text, ncombining, widefactor > 1, + bold, inst->font_width); + } else { + unifont_draw_text(&inst->uctx, inst->fonts[fontid], + x*inst->font_width+inst->window_border, + (y*inst->font_height+inst->window_border+ + inst->fonts[0]->ascent), + text, len, widefactor > 1, + bold, inst->font_width); + } + + if (attr & ATTR_UNDER) { + int uheight = inst->fonts[0]->ascent + 1; + if (uheight >= inst->font_height) + uheight = inst->font_height - 1; + draw_line(inst, x*inst->font_width+inst->window_border, + y*inst->font_height + uheight + inst->window_border, + (x+len)*widefactor*inst->font_width-1+inst->window_border, + y*inst->font_height + uheight + inst->window_border); + } + + if (attr & ATTR_STRIKE) { + int sheight = inst->fonts[fontid]->strikethrough_y; + draw_line(inst, x*inst->font_width+inst->window_border, + y*inst->font_height + sheight + inst->window_border, + (x+len)*widefactor*inst->font_width-1+inst->window_border, + y*inst->font_height + sheight + inst->window_border); + } + + if ((lattr & LATTR_MODE) != LATTR_NORM) { + draw_stretch_after(inst, + x*inst->font_width+inst->window_border, + y*inst->font_height+inst->window_border, + rlen*widefactor*inst->font_width, true, + inst->font_height, + ((lattr & LATTR_MODE) != LATTR_WIDE), + ((lattr & LATTR_MODE) == LATTR_BOT)); + } +} + +static void gtkwin_draw_text( + TermWin *tw, int x, int y, wchar_t *text, int len, + unsigned long attr, int lattr, truecolour truecolour) +{ + GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); + int widefactor; + + do_text_internal(inst, x, y, text, len, attr, lattr, truecolour); + + 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; + } + + draw_update(inst, + x*inst->font_width+inst->window_border, + y*inst->font_height+inst->window_border, + len*widefactor*inst->font_width, inst->font_height); +} + +static void gtkwin_draw_cursor( + TermWin *tw, int x, int y, wchar_t *text, int len, + unsigned long attr, int lattr, truecolour truecolour) +{ + GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); + bool active, passive; + int widefactor; + + if (attr & TATTR_PASCURS) { + attr &= ~TATTR_PASCURS; + passive = true; + } else + passive = false; + if ((attr & TATTR_ACTCURS) && inst->cursor_type != 0) { + attr &= ~TATTR_ACTCURS; + active = true; + } else + active = false; + do_text_internal(inst, x, y, text, len, attr, lattr, truecolour); + + 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->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) { + draw_set_colour(inst, 261, false); + draw_rectangle(inst, false, + x*inst->font_width+inst->window_border, + y*inst->font_height+inst->window_border, + len*widefactor*inst->font_width-1, + inst->font_height-1); + } + } else { + int uheight; + int startx, starty, dx, dy, length, i; + + 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->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->window_border; + starty = y * inst->font_height + inst->window_border + uheight; + dx = 1; + dy = 0; + length = len * widefactor * char_width; + } else { + int xadjust = 0; + if (attr & TATTR_RIGHTCURS) + xadjust = char_width - 1; + startx = x * inst->font_width + inst->window_border + xadjust; + starty = y * inst->font_height + inst->window_border; + dx = 0; + dy = 1; + length = inst->font_height; + } + + draw_set_colour(inst, 261, false); + if (passive) { + for (i = 0; i < length; i++) { + if (i % 2 == 0) { + draw_point(inst, startx, starty); + } + startx += dx; + starty += dy; + } + } else if (active) { + draw_line(inst, startx, starty, + startx + (length-1) * dx, starty + (length-1) * dy); + } /* else no cursor (e.g., blinked off) */ + } + + draw_update(inst, + x*inst->font_width+inst->window_border, + y*inst->font_height+inst->window_border, + len*widefactor*inst->font_width, inst->font_height); + +#if GTK_CHECK_VERSION(2,0,0) + { + GdkRectangle cursorrect; + cursorrect.x = x*inst->font_width+inst->window_border; + cursorrect.y = y*inst->font_height+inst->window_border; + cursorrect.width = len*widefactor*inst->font_width; + cursorrect.height = inst->font_height; + gtk_im_context_set_cursor_location(inst->imc, &cursorrect); + } +#endif +} + +#if !GTK_CHECK_VERSION(2,0,0) +/* + * For GTK 1, manual code to scale an in-memory XPM, producing a new + * one as output. It will be ugly, but good enough to use as a trust + * sigil. + */ +struct XpmHolder { + char **strings; + size_t nstrings; +}; + +static void xpmholder_free(XpmHolder *xh) +{ + for (size_t i = 0; i < xh->nstrings; i++) + sfree(xh->strings[i]); + sfree(xh->strings); + sfree(xh); +} + +static XpmHolder *xpm_scale(const char *const *xpm, int wo, int ho) +{ + /* Get image dimensions, # colours, and chars-per-pixel */ + int wi = 0, hi = 0, nc = 0, cpp = 0; + int retd = sscanf(xpm[0], "%d %d %d %d", &wi, &hi, &nc, &cpp); + assert(retd == 4); + + /* Make output XpmHolder */ + XpmHolder *xh = snew(XpmHolder); + xh->nstrings = 1 + nc + ho; + xh->strings = snewn(xh->nstrings, char *); + + /* Set up header */ + xh->strings[0] = dupprintf("%d %d %d %d", wo, ho, nc, cpp); + for (int i = 0; i < nc; i++) + xh->strings[1 + i] = dupstr(xpm[1 + i]); + + /* Scale image */ + for (int yo = 0; yo < ho; yo++) { + int yi = yo * hi / ho; + char *ro = snewn(cpp * wo + 1, char); + ro[cpp * wo] = '\0'; + xh->strings[1 + nc + yo] = ro; + const char *ri = xpm[1 + nc + yi]; + + for (int xo = 0; xo < wo; xo++) { + int xi = xo * wi / wo; + memcpy(ro + cpp * xo, ri + cpp * xi, cpp); + } + } + + return xh; +} +#endif /* !GTK_CHECK_VERSION(2,0,0) */ + +static void gtkwin_draw_trust_sigil(TermWin *tw, int cx, int cy) +{ + GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); + + int x = cx * inst->font_width + inst->window_border; + int y = cy * inst->font_height + inst->window_border; + int w = 2*inst->font_width, h = inst->font_height; + + if (inst->trust_sigil_w != w || inst->trust_sigil_h != h || +#if GTK_CHECK_VERSION(2,0,0) + !inst->trust_sigil_pb +#else + !inst->trust_sigil_pm +#endif + ) { + +#if GTK_CHECK_VERSION(2,0,0) + if (inst->trust_sigil_pb) + g_object_unref(G_OBJECT(inst->trust_sigil_pb)); +#else + if (inst->trust_sigil_pm) + gdk_pixmap_unref(inst->trust_sigil_pm); +#endif + + int best_icon_index = 0; + unsigned score = UINT_MAX; + for (int i = 0; i < n_main_icon; i++) { + int iw, ih; + if (sscanf(main_icon[i][0], "%d %d", &iw, &ih) == 2) { + int this_excess = (iw + ih) - (w + h); + unsigned this_score = (abs(this_excess) | + (this_excess > 0 ? 0 : 0x80000000U)); + if (this_score < score) { + best_icon_index = i; + score = this_score; + } + } + } + +#if GTK_CHECK_VERSION(2,0,0) + GdkPixbuf *icon_unscaled = gdk_pixbuf_new_from_xpm_data( + (const gchar **)main_icon[best_icon_index]); + inst->trust_sigil_pb = gdk_pixbuf_scale_simple( + icon_unscaled, w, h, GDK_INTERP_BILINEAR); + g_object_unref(G_OBJECT(icon_unscaled)); +#else + XpmHolder *xh = xpm_scale(main_icon[best_icon_index], w, h); + inst->trust_sigil_pm = gdk_pixmap_create_from_xpm_d( + gtk_widget_get_window(inst->window), NULL, + &inst->cols[258], xh->strings); + xpmholder_free(xh); +#endif + + inst->trust_sigil_w = w; + inst->trust_sigil_h = h; + } + +#ifdef DRAW_TEXT_GDK + if (inst->uctx.type == DRAWTYPE_GDK) { +#if GTK_CHECK_VERSION(2,0,0) + gdk_draw_pixbuf(inst->uctx.u.gdk.target, inst->uctx.u.gdk.gc, + inst->trust_sigil_pb, 0, 0, x, y, w, h, + GDK_RGB_DITHER_NORMAL, 0, 0); +#else + gdk_draw_pixmap(inst->uctx.u.gdk.target, inst->uctx.u.gdk.gc, + inst->trust_sigil_pm, 0, 0, x, y, w, h); +#endif + } +#endif +#ifdef DRAW_TEXT_CAIRO + if (inst->uctx.type == DRAWTYPE_CAIRO) { + inst->uctx.u.cairo.widget = GTK_WIDGET(inst->area); + cairo_save(inst->uctx.u.cairo.cr); + cairo_translate(inst->uctx.u.cairo.cr, x, y); + gdk_cairo_set_source_pixbuf(inst->uctx.u.cairo.cr, + inst->trust_sigil_pb, 0, 0); + cairo_rectangle(inst->uctx.u.cairo.cr, 0, 0, w, h); + cairo_fill(inst->uctx.u.cairo.cr); + cairo_restore(inst->uctx.u.cairo.cr); + } +#endif + + draw_update(inst, x, y, w, h); +} + +GdkCursor *make_mouse_ptr(GtkFrontend *inst, int cursor_val) +{ + if (cursor_val == -1) { +#if GTK_CHECK_VERSION(2,16,0) + cursor_val = GDK_BLANK_CURSOR; +#else + /* + * Work around absence of GDK_BLANK_CURSOR by inventing a + * blank pixmap. + */ + GdkCursor *ret; + GdkColor bg = { 0, 0, 0, 0 }; + GdkPixmap *pm = gdk_pixmap_new(NULL, 1, 1, 1); + GdkGC *gc = gdk_gc_new(pm); + gdk_gc_set_foreground(gc, &bg); + gdk_draw_rectangle(pm, gc, 1, 0, 0, 1, 1); + gdk_gc_unref(gc); + ret = gdk_cursor_new_from_pixmap(pm, pm, &bg, &bg, 1, 1); + gdk_pixmap_unref(pm); + return ret; +#endif + } + + return gdk_cursor_new(cursor_val); +} + +void modalfatalbox(const 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); +} + +static const char *gtk_seat_get_x_display(Seat *seat) +{ + return gdk_get_display(); +} + +#ifndef NOT_X_WINDOWS +static bool gtk_seat_get_windowid(Seat *seat, long *id) +{ + GtkFrontend *inst = container_of(seat, GtkFrontend, seat); + GdkWindow *window = gtk_widget_get_window(inst->area); + if (!GDK_IS_X11_WINDOW(window)) + return false; + *id = GDK_WINDOW_XID(window); + return true; +} +#endif + +char *setup_fonts_ucs(GtkFrontend *inst) +{ + bool shadowbold = conf_get_bool(inst->conf, CONF_shadowbold); + int shadowboldoffset = conf_get_int(inst->conf, CONF_shadowboldoffset); + FontSpec *fs; + unifont *fonts[4]; + int i; + + fs = conf_get_fontspec(inst->conf, CONF_font); + fonts[0] = multifont_create(inst->area, fs->name, false, false, + shadowboldoffset, shadowbold); + if (!fonts[0]) { + return dupprintf("unable to load font \"%s\"", fs->name); + } + + fs = conf_get_fontspec(inst->conf, CONF_boldfont); + if (shadowbold || !fs->name[0]) { + fonts[1] = NULL; + } else { + fonts[1] = multifont_create(inst->area, fs->name, false, true, + shadowboldoffset, shadowbold); + if (!fonts[1]) { + if (fonts[0]) + unifont_destroy(fonts[0]); + return dupprintf("unable to load bold font \"%s\"", fs->name); + } + } + + fs = conf_get_fontspec(inst->conf, CONF_widefont); + if (fs->name[0]) { + fonts[2] = multifont_create(inst->area, fs->name, true, false, + shadowboldoffset, shadowbold); + if (!fonts[2]) { + for (i = 0; i < 2; i++) + if (fonts[i]) + unifont_destroy(fonts[i]); + return dupprintf("unable to load wide font \"%s\"", fs->name); + } + } else { + fonts[2] = NULL; + } + + fs = conf_get_fontspec(inst->conf, CONF_wideboldfont); + if (shadowbold || !fs->name[0]) { + fonts[3] = NULL; + } else { + fonts[3] = multifont_create(inst->area, fs->name, true, true, + shadowboldoffset, shadowbold); + if (!fonts[3]) { + for (i = 0; i < 3; i++) + if (fonts[i]) + unifont_destroy(fonts[i]); + return dupprintf("unable to load wide bold font \"%s\"", fs->name); + } + } + + /* + * Now we've got past all the possible error conditions, we can + * actually update our state. + */ + + for (i = 0; i < 4; i++) { + if (inst->fonts[i]) + unifont_destroy(inst->fonts[i]); + inst->fonts[i] = fonts[i]; + } + + if (inst->font_width != inst->fonts[0]->width || + inst->font_height != inst->fonts[0]->height) { + + inst->font_width = inst->fonts[0]->width; + inst->font_height = inst->fonts[0]->height; + + /* + * The font size has changed, so force the next call to + * drawing_area_setup to regenerate the backing surface. + */ + inst->drawing_area_setup_needed = true; + } + + inst->direct_to_font = init_ucs(&inst->ucsdata, + conf_get_str(inst->conf, CONF_line_codepage), + conf_get_bool(inst->conf, CONF_utf8_override), + inst->fonts[0]->public_charset, + conf_get_int(inst->conf, CONF_vtmode)); + + inst->drawtype = inst->fonts[0]->preferred_drawtype; + + return NULL; +} + +#if GTK_CHECK_VERSION(3,0,0) +struct find_app_menu_bar_ctx { + GtkWidget *area, *menubar; +}; +static void find_app_menu_bar(GtkWidget *widget, gpointer data) +{ + struct find_app_menu_bar_ctx *ctx = (struct find_app_menu_bar_ctx *)data; + if (widget != ctx->area && GTK_IS_MENU_BAR(widget)) + ctx->menubar = widget; +} +#endif + +static void compute_geom_hints(GtkFrontend *inst, GdkGeometry *geom) +{ + /* + * Unused fields in geom. + */ + geom->max_width = geom->max_height = -1; + geom->min_aspect = geom->max_aspect = 0; + + /* + * Set up the geometry fields we care about, with reference to + * just the drawing area. We'll correct for other widgets in a + * moment. + */ + geom->min_width = inst->font_width + 2*inst->window_border; + geom->min_height = inst->font_height + 2*inst->window_border; + geom->base_width = 2*inst->window_border; + geom->base_height = 2*inst->window_border; + geom->width_inc = inst->font_width; + geom->height_inc = inst->font_height; + + /* + * If we've got a scrollbar visible, then we must include its + * width as part of the base and min width, and also ensure that + * our window's minimum height is at least the height required by + * the scrollbar. + * + * In the latter case, we must also take care to arrange that + * (geom->min_height - geom->base_height) is an integer multiple of + * geom->height_inc, because if it's not, then some window managers + * (we know of xfwm4) get confused, with the effect that they + * resize our window to a height based on min_height instead of + * base_height, which we then round down and the window ends up + * too short. + */ + if (inst->sbar_visible) { + GtkRequisition req; + int min_sb_height; + +#if GTK_CHECK_VERSION(3,0,0) + gtk_widget_get_preferred_size(inst->sbar, &req, NULL); +#else + gtk_widget_size_request(inst->sbar, &req); +#endif + + /* Compute rounded-up scrollbar height. */ + min_sb_height = req.height; + min_sb_height += geom->height_inc - 1; + min_sb_height -= ((min_sb_height - geom->base_height%geom->height_inc) + % geom->height_inc); + + geom->min_width += req.width; + geom->base_width += req.width; + if (geom->min_height < min_sb_height) + geom->min_height = min_sb_height; + } + +#if GTK_CHECK_VERSION(3,0,0) + /* + * And if we're running a gtkapp.c based program and + * GtkApplicationWindow has given us a menu bar inside the window, + * then we must take that into account as well. + * + * In its unbounded wisdom, GtkApplicationWindow doesn't actually + * give us a direct function call to _find_ the menu bar widget. + * Fortunately, we can find it by enumerating the children of the + * top-level window and looking for one we didn't put there + * ourselves. + */ + { + struct find_app_menu_bar_ctx ctx[1]; + ctx->area = inst->area; + ctx->menubar = NULL; + gtk_container_foreach(GTK_CONTAINER(inst->window), + find_app_menu_bar, ctx); + + if (ctx->menubar) { + GtkRequisition req; + int min_menu_width; + gtk_widget_get_preferred_size(ctx->menubar, NULL, &req); + + /* + * This time, the height adjustment is easy (the menu bar + * sits above everything), but we have to take care with + * the _width_ to ensure we keep min_width and base_width + * congruent modulo width_inc. + */ + geom->min_height += req.height; + geom->base_height += req.height; + + min_menu_width = req.width; + min_menu_width += geom->width_inc - 1; + min_menu_width -= + ((min_menu_width - geom->base_width%geom->width_inc) + % geom->width_inc); + if (geom->min_width < min_menu_width) + geom->min_width = min_menu_width; + } + } +#endif +} + +void set_geom_hints(GtkFrontend *inst) +{ + const struct BackendVtable *vt; + GdkGeometry geom; + gint flags = GDK_HINT_MIN_SIZE | GDK_HINT_BASE_SIZE | GDK_HINT_RESIZE_INC; + compute_geom_hints(inst, &geom); +#if GTK_CHECK_VERSION(2,0,0) + if (inst->gotpos) + flags |= GDK_HINT_USER_POS; +#endif + vt = backend_vt_from_proto(conf_get_int(inst->conf, CONF_protocol)); + if (vt && vt->flags & BACKEND_RESIZE_FORBIDDEN) { + /* Window resizing forbidden. Set both minimum and maximum + * dimensions to be the initial size. */ + geom.min_width = inst->width*inst->font_width + 2*inst->window_border; + geom.min_height = inst->height*inst->font_height + 2*inst->window_border; + geom.max_width = geom.min_width; + geom.max_height = geom.min_height; + flags |= GDK_HINT_MAX_SIZE; + } + gtk_window_set_geometry_hints(GTK_WINDOW(inst->window), + NULL, &geom, flags); +} + +#if GTK_CHECK_VERSION(2,0,0) +static void compute_whole_window_size(GtkFrontend *inst, + int wchars, int hchars, + int *wpix, int *hpix) +{ + GdkGeometry geom; + compute_geom_hints(inst, &geom); + if (wpix) *wpix = geom.base_width + wchars * geom.width_inc; + if (hpix) *hpix = geom.base_height + hchars * geom.height_inc; +} +#endif + +void clear_scrollback_menuitem(GtkMenuItem *item, gpointer data) +{ + GtkFrontend *inst = (GtkFrontend *)data; + term_clrsb(inst->term); +} + +void reset_terminal_menuitem(GtkMenuItem *item, gpointer data) +{ + GtkFrontend *inst = (GtkFrontend *)data; + term_pwron(inst->term, true); + if (inst->ldisc) + ldisc_echoedit_update(inst->ldisc); +} + +void copy_clipboard_menuitem(GtkMenuItem *item, gpointer data) +{ + GtkFrontend *inst = (GtkFrontend *)data; + static const int clips[] = { MENU_CLIPBOARD }; + term_request_copy(inst->term, clips, lenof(clips)); +} + +void paste_clipboard_menuitem(GtkMenuItem *item, gpointer data) +{ + GtkFrontend *inst = (GtkFrontend *)data; + term_request_paste(inst->term, MENU_CLIPBOARD); +} + +void copy_all_menuitem(GtkMenuItem *item, gpointer data) +{ + GtkFrontend *inst = (GtkFrontend *)data; + static const int clips[] = { COPYALL_CLIPBOARDS }; + term_copyall(inst->term, clips, lenof(clips)); +} + +void special_menuitem(GtkMenuItem *item, gpointer data) +{ + GtkFrontend *inst = (GtkFrontend *)data; + SessionSpecial *sc = g_object_get_data(G_OBJECT(item), "user-data"); + + if (inst->backend) + backend_special(inst->backend, sc->code, sc->arg); +} + +void about_menuitem(GtkMenuItem *item, gpointer data) +{ + GtkFrontend *inst = (GtkFrontend *)data; + about_box(inst->window); +} + +void event_log_menuitem(GtkMenuItem *item, gpointer data) +{ + GtkFrontend *inst = (GtkFrontend *)data; + showeventlog(inst->eventlogstuff, inst->window); +} + +void setup_clipboards(GtkFrontend *inst, Terminal *term, Conf *conf) +{ + assert(term->mouse_select_clipboards[0] == CLIP_LOCAL); + + term->n_mouse_select_clipboards = 1; + term->mouse_select_clipboards[ + term->n_mouse_select_clipboards++] = MOUSE_SELECT_CLIPBOARD; + + if (conf_get_bool(conf, CONF_mouseautocopy)) { + term->mouse_select_clipboards[ + term->n_mouse_select_clipboards++] = CLIP_CLIPBOARD; + } + + set_clipboard_atom(inst, CLIP_CUSTOM_1, GDK_NONE); + set_clipboard_atom(inst, CLIP_CUSTOM_2, GDK_NONE); + set_clipboard_atom(inst, CLIP_CUSTOM_3, GDK_NONE); + + switch (conf_get_int(conf, CONF_mousepaste)) { + case CLIPUI_IMPLICIT: + term->mouse_paste_clipboard = MOUSE_PASTE_CLIPBOARD; + break; + case CLIPUI_EXPLICIT: + term->mouse_paste_clipboard = CLIP_CLIPBOARD; + break; + case CLIPUI_CUSTOM: + term->mouse_paste_clipboard = CLIP_CUSTOM_1; + set_clipboard_atom(inst, CLIP_CUSTOM_1, + gdk_atom_intern( + conf_get_str(conf, CONF_mousepaste_custom), + false)); + break; + default: + term->mouse_paste_clipboard = CLIP_NULL; + break; + } + + if (conf_get_int(conf, CONF_ctrlshiftins) == CLIPUI_CUSTOM) { + GdkAtom atom = gdk_atom_intern( + conf_get_str(conf, CONF_ctrlshiftins_custom), false); + struct clipboard_state *state = clipboard_from_atom(inst, atom); + if (state) { + inst->clipboard_ctrlshiftins = state->clipboard; + } else { + inst->clipboard_ctrlshiftins = CLIP_CUSTOM_2; + set_clipboard_atom(inst, CLIP_CUSTOM_2, atom); + } + } + + if (conf_get_int(conf, CONF_ctrlshiftcv) == CLIPUI_CUSTOM) { + GdkAtom atom = gdk_atom_intern( + conf_get_str(conf, CONF_ctrlshiftcv_custom), false); + struct clipboard_state *state = clipboard_from_atom(inst, atom); + if (state) { + inst->clipboard_ctrlshiftins = state->clipboard; + } else { + inst->clipboard_ctrlshiftcv = CLIP_CUSTOM_3; + set_clipboard_atom(inst, CLIP_CUSTOM_3, atom); + } + } +} + +struct after_change_settings_dialog_ctx { + GtkFrontend *inst; + Conf *newconf; +}; + +static void after_change_settings_dialog(void *vctx, int retval); + +void change_settings_menuitem(GtkMenuItem *item, gpointer data) +{ + GtkFrontend *inst = (GtkFrontend *)data; + struct after_change_settings_dialog_ctx *ctx; + GtkWidget *dialog; + char *title; + + if (find_and_raise_dialog(inst, DIALOG_SLOT_RECONFIGURE)) + return; + + title = dupcat(appname, " Reconfiguration"); + + ctx = snew(struct after_change_settings_dialog_ctx); + ctx->inst = inst; + ctx->newconf = conf_copy(inst->conf); + + term_pre_reconfig(inst->term, ctx->newconf); + + dialog = create_config_box( + title, ctx->newconf, true, + inst->backend ? backend_cfg_info(inst->backend) : 0, + after_change_settings_dialog, ctx); + register_dialog(&inst->seat, DIALOG_SLOT_RECONFIGURE, dialog); + + sfree(title); +} + +static void after_change_settings_dialog(void *vctx, int retval) +{ + struct after_change_settings_dialog_ctx ctx = + *(struct after_change_settings_dialog_ctx *)vctx; + GtkFrontend *inst = ctx.inst; + Conf *oldconf = inst->conf, *newconf = ctx.newconf; + bool need_size; + + sfree(vctx); /* we've copied this already */ + + unregister_dialog(&inst->seat, DIALOG_SLOT_RECONFIGURE); + + if (retval > 0) { + inst->conf = newconf; + + /* Pass new config data to the logging module */ + log_reconfig(inst->logctx, inst->conf); + /* + * Flush the line discipline's edit buffer in the case + * where local editing has just been disabled. + */ + if (inst->ldisc) { + ldisc_configure(inst->ldisc, inst->conf); + ldisc_echoedit_update(inst->ldisc); + } + /* Pass new config data to the terminal */ + term_reconfig(inst->term, inst->conf); + setup_clipboards(inst, inst->term, inst->conf); + /* Pass new config data to the back end */ + if (inst->backend) + backend_reconfig(inst->backend, inst->conf); + + cache_conf_values(inst); + + need_size = false; + + /* + * If the scrollbar needs to be shown, hidden, or moved + * from one end to the other of the window, do so now. + */ + if (conf_get_bool(oldconf, CONF_scrollbar) != + conf_get_bool(newconf, CONF_scrollbar)) { + show_scrollbar(inst, conf_get_bool(newconf, CONF_scrollbar)); + need_size = true; + } + if (conf_get_bool(oldconf, CONF_scrollbar_on_left) != + conf_get_bool(newconf, CONF_scrollbar_on_left)) { + gtk_box_reorder_child(inst->hbox, inst->sbar, + conf_get_bool(newconf, CONF_scrollbar_on_left) + ? 0 : 1); + } + + /* + * Redo the whole tangled fonts and Unicode mess if + * necessary. + */ + if (strcmp(conf_get_fontspec(oldconf, CONF_font)->name, + conf_get_fontspec(newconf, CONF_font)->name) || + strcmp(conf_get_fontspec(oldconf, CONF_boldfont)->name, + conf_get_fontspec(newconf, CONF_boldfont)->name) || + strcmp(conf_get_fontspec(oldconf, CONF_widefont)->name, + conf_get_fontspec(newconf, CONF_widefont)->name) || + strcmp(conf_get_fontspec(oldconf, CONF_wideboldfont)->name, + conf_get_fontspec(newconf, CONF_wideboldfont)->name) || + strcmp(conf_get_str(oldconf, CONF_line_codepage), + conf_get_str(newconf, CONF_line_codepage)) || + conf_get_bool(oldconf, CONF_utf8_override) != + conf_get_bool(newconf, CONF_utf8_override) || + conf_get_int(oldconf, CONF_vtmode) != + conf_get_int(newconf, CONF_vtmode) || + conf_get_bool(oldconf, CONF_shadowbold) != + conf_get_bool(newconf, CONF_shadowbold) || + conf_get_int(oldconf, CONF_shadowboldoffset) != + conf_get_int(newconf, CONF_shadowboldoffset)) { + char *errmsg = setup_fonts_ucs(inst); + if (errmsg) { + char *msgboxtext = + dupprintf("Could not change fonts in terminal window: %s\n", + errmsg); + create_message_box( + inst->window, "Font setup error", msgboxtext, + string_width("Could not change fonts in terminal window:"), + false, &buttons_ok, trivial_post_dialog_fn, NULL); + sfree(msgboxtext); + sfree(errmsg); + } else { + need_size = true; + } + } + + /* + * Resize the window. + */ + if (conf_get_int(oldconf, CONF_width) != + conf_get_int(newconf, CONF_width) || + conf_get_int(oldconf, CONF_height) != + conf_get_int(newconf, CONF_height) || + conf_get_int(oldconf, CONF_window_border) != + conf_get_int(newconf, CONF_window_border) || + need_size) { + set_geom_hints(inst); + win_request_resize(&inst->termwin, + conf_get_int(newconf, CONF_width), + conf_get_int(newconf, CONF_height)); + } else { + /* + * The above will have caused a call to term_size() for + * us if it happened. If the user has fiddled with only + * the scrollback size, the above will not have + * happened and we will need an explicit term_size() + * here. + */ + if (conf_get_int(oldconf, CONF_savelines) != + conf_get_int(newconf, CONF_savelines)) + term_size(inst->term, inst->term->rows, inst->term->cols, + conf_get_int(newconf, CONF_savelines)); + } + + term_invalidate(inst->term); + + /* + * We do an explicit full redraw here to ensure the window + * border has been redrawn as well as the text area. + */ + gtk_widget_queue_draw(inst->area); + + conf_free(oldconf); + } else { + conf_free(newconf); + } +} + +static void change_font_size(GtkFrontend *inst, int increment) +{ + static const int conf_keys[lenof(inst->fonts)] = { + CONF_font, CONF_boldfont, CONF_widefont, CONF_wideboldfont, + }; + FontSpec *oldfonts[lenof(inst->fonts)]; + FontSpec *newfonts[lenof(inst->fonts)]; + char *errmsg = NULL; + int i; + + for (i = 0; i < lenof(newfonts); i++) + oldfonts[i] = newfonts[i] = NULL; + + for (i = 0; i < lenof(inst->fonts); i++) { + if (inst->fonts[i]) { + char *newname = unifont_size_increment(inst->fonts[i], increment); + if (!newname) + goto cleanup; + newfonts[i] = fontspec_new(newname); + sfree(newname); + } + } + + for (i = 0; i < lenof(newfonts); i++) { + if (newfonts[i]) { + oldfonts[i] = fontspec_copy( + conf_get_fontspec(inst->conf, conf_keys[i])); + conf_set_fontspec(inst->conf, conf_keys[i], newfonts[i]); + } + } + + errmsg = setup_fonts_ucs(inst); + if (errmsg) + goto cleanup; + + /* Success, so suppress putting everything back */ + for (i = 0; i < lenof(newfonts); i++) { + if (oldfonts[i]) { + fontspec_free(oldfonts[i]); + oldfonts[i] = NULL; + } + } + + set_geom_hints(inst); + win_request_resize(&inst->termwin, conf_get_int(inst->conf, CONF_width), + conf_get_int(inst->conf, CONF_height)); + term_invalidate(inst->term); + gtk_widget_queue_draw(inst->area); + + cleanup: + for (i = 0; i < lenof(oldfonts); i++) { + if (oldfonts[i]) { + conf_set_fontspec(inst->conf, conf_keys[i], oldfonts[i]); + fontspec_free(oldfonts[i]); + } + if (newfonts[i]) + fontspec_free(newfonts[i]); + } + sfree(errmsg); +} + +void dup_session_menuitem(GtkMenuItem *item, gpointer gdata) +{ + GtkFrontend *inst = (GtkFrontend *)gdata; + + launch_duplicate_session(inst->conf); +} + +void new_session_menuitem(GtkMenuItem *item, gpointer data) +{ + launch_new_session(); +} + +void restart_session_menuitem(GtkMenuItem *item, gpointer data) +{ + GtkFrontend *inst = (GtkFrontend *)data; + + if (!inst->backend) { + logevent(inst->logctx, "----- Session restarted -----"); + term_pwron(inst->term, false); + start_backend(inst); + inst->exited = false; + } +} + +void saved_session_menuitem(GtkMenuItem *item, gpointer data) +{ + char *str = (char *)g_object_get_data(G_OBJECT(item), "user-data"); + + launch_saved_session(str); +} + +void saved_session_freedata(GtkMenuItem *item, gpointer data) +{ + char *str = (char *)g_object_get_data(G_OBJECT(item), "user-data"); + + sfree(str); +} + +void app_menu_action(GtkFrontend *frontend, enum MenuAction action) +{ + GtkFrontend *inst = (GtkFrontend *)frontend; + switch (action) { + case MA_COPY: + copy_clipboard_menuitem(NULL, inst); + break; + case MA_PASTE: + paste_clipboard_menuitem(NULL, inst); + break; + case MA_COPY_ALL: + copy_all_menuitem(NULL, inst); + break; + case MA_DUPLICATE_SESSION: + dup_session_menuitem(NULL, inst); + break; + case MA_RESTART_SESSION: + restart_session_menuitem(NULL, inst); + break; + case MA_CHANGE_SETTINGS: + change_settings_menuitem(NULL, inst); + break; + case MA_CLEAR_SCROLLBACK: + clear_scrollback_menuitem(NULL, inst); + break; + case MA_RESET_TERMINAL: + reset_terminal_menuitem(NULL, inst); + break; + case MA_EVENT_LOG: + event_log_menuitem(NULL, inst); + break; + } +} + +static void update_savedsess_menu(GtkMenuItem *menuitem, gpointer data) +{ + GtkFrontend *inst = (GtkFrontend *)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); + g_object_set_data(G_OBJECT(menuitem), "user-data", + dupstr(sesslist.sessions[i])); + g_signal_connect(G_OBJECT(menuitem), "activate", + G_CALLBACK(saved_session_menuitem), + inst); + g_signal_connect(G_OBJECT(menuitem), "destroy", + G_CALLBACK(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) +{ +#if GTK_CHECK_VERSION(2,0,0) + GList *iconlist; + int n; +#else + GdkPixmap *iconpm; + GdkBitmap *iconmask; +#endif + + if (!n_icon) + return; + + gtk_widget_realize(window); +#if GTK_CHECK_VERSION(2,0,0) + gtk_window_set_icon(GTK_WINDOW(window), + gdk_pixbuf_new_from_xpm_data((const gchar **)icon[0])); +#else + iconpm = gdk_pixmap_create_from_xpm_d(gtk_widget_get_window(window), + &iconmask, NULL, (gchar **)icon[0]); + gdk_window_set_icon(gtk_widget_get_window(window), NULL, iconpm, iconmask); +#endif + +#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])); + } + gtk_window_set_icon_list(GTK_WINDOW(window), iconlist); +#endif +} + +static void free_special_cmd(gpointer data) { sfree(data); } + +static void gtk_seat_update_specials_menu(Seat *seat) +{ + GtkFrontend *inst = container_of(seat, GtkFrontend, seat); + const SessionSpecial *specials; + + if (inst->backend) + specials = backend_get_specials(inst->backend); + 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 SS_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 SS_EXITMENU: + nesting--; + if (nesting) { + menu = saved_menu; /* XXX lame stacking */ + saved_menu = NULL; + } + break; + case SS_SEP: + menuitem = gtk_menu_item_new(); + break; + default: { + menuitem = gtk_menu_item_new_with_label(specials[i].name); + SessionSpecial *sc = snew(SessionSpecial); + *sc = specials[i]; /* structure copy */ + g_object_set_data_full(G_OBJECT(menuitem), "user-data", + sc, free_special_cmd); + g_signal_connect(G_OBJECT(menuitem), "activate", + G_CALLBACK(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(GtkFrontend *inst) +{ + const struct BackendVtable *vt; + char *error, *realhost; + + vt = select_backend(inst->conf); + + seat_set_trust_status(&inst->seat, true); + error = backend_init(vt, &inst->seat, &inst->backend, + inst->logctx, inst->conf, + conf_get_str(inst->conf, CONF_host), + conf_get_int(inst->conf, CONF_port), + &realhost, + conf_get_bool(inst->conf, CONF_tcp_nodelay), + conf_get_bool(inst->conf, CONF_tcp_keepalives)); + + if (error) { + seat_connection_fatal(&inst->seat, + "Unable to open connection to %s:\n%s", + conf_dest(inst->conf), error); + sfree(error); + inst->exited = true; + return; + } + + term_setup_window_titles(inst->term, realhost); + sfree(realhost); + + term_provide_backend(inst->term, inst->backend); + + inst->ldisc = ldisc_create(inst->conf, inst->term, inst->backend, + &inst->seat); + + gtk_widget_set_sensitive(inst->restartitem, false); +} + +#if GTK_CHECK_VERSION(2,0,0) +static void get_monitor_geometry(GtkWidget *widget, GdkRectangle *geometry) +{ +#if GTK_CHECK_VERSION(3,4,0) + GdkDisplay *display = gtk_widget_get_display(widget); + GdkWindow *gdkwindow = gtk_widget_get_window(widget); +# if GTK_CHECK_VERSION(3,22,0) + GdkMonitor *monitor; + if (gdkwindow) + monitor = gdk_display_get_monitor_at_window(display, gdkwindow); + else + monitor = gdk_display_get_monitor(display, 0); + gdk_monitor_get_geometry(monitor, geometry); +# else + GdkScreen *screen = gdk_display_get_default_screen(display); + gint monitor_num = gdk_screen_get_monitor_at_window(screen, gdkwindow); + gdk_screen_get_monitor_geometry(screen, monitor_num, geometry); +# endif +#else + geometry->x = geometry->y = 0; + geometry->width = gdk_screen_width(); + geometry->height = gdk_screen_height(); +#endif +} +#endif + +static const TermWinVtable gtk_termwin_vt = { + .setup_draw_ctx = gtkwin_setup_draw_ctx, + .draw_text = gtkwin_draw_text, + .draw_cursor = gtkwin_draw_cursor, + .draw_trust_sigil = gtkwin_draw_trust_sigil, + .char_width = gtkwin_char_width, + .free_draw_ctx = gtkwin_free_draw_ctx, + .set_cursor_pos = gtkwin_set_cursor_pos, + .set_raw_mouse_mode = gtkwin_set_raw_mouse_mode, + .set_raw_mouse_mode_pointer = gtkwin_set_raw_mouse_mode_pointer, + .set_scrollbar = gtkwin_set_scrollbar, + .bell = gtkwin_bell, + .clip_write = gtkwin_clip_write, + .clip_request_paste = gtkwin_clip_request_paste, + .refresh = gtkwin_refresh, + .request_resize = gtkwin_request_resize, + .set_title = gtkwin_set_title, + .set_icon_title = gtkwin_set_icon_title, + .set_minimised = gtkwin_set_minimised, + .set_maximised = gtkwin_set_maximised, + .move = gtkwin_move, + .set_zorder = gtkwin_set_zorder, + .palette_set = gtkwin_palette_set, + .palette_get_overrides = gtkwin_palette_get_overrides, +}; + +void new_session_window(Conf *conf, const char *geometry_string) +{ + GtkFrontend *inst; + + prepare_session(conf); + + /* + * Create an instance structure and initialise to zeroes + */ + inst = snew(GtkFrontend); + memset(inst, 0, sizeof(*inst)); +#ifdef JUST_USE_GTK_CLIPBOARD_UTF8 + inst->cdi_headtail.next = inst->cdi_headtail.prev = &inst->cdi_headtail; +#endif + inst->alt_keycode = -1; /* this one needs _not_ to be zero */ + inst->busy_status = BUSY_NOT; + inst->conf = conf; + inst->wintitle = inst->icontitle = NULL; + inst->drawtype = DRAWTYPE_DEFAULT; +#if GTK_CHECK_VERSION(3,4,0) + inst->cumulative_scroll = 0.0; +#endif + inst->drawing_area_setup_needed = true; + + inst->termwin.vt = >k_termwin_vt; + inst->seat.vt = >k_seat_vt; + inst->logpolicy.vt = >k_logpolicy_vt; + +#ifndef NOT_X_WINDOWS + inst->disp = get_x11_display(); + if (geometry_string) { + int flags, x, y; + unsigned int w, h; + flags = XParseGeometry(geometry_string, &x, &y, &w, &h); + if (flags & WidthValue) + conf_set_int(conf, CONF_width, w); + if (flags & HeightValue) + conf_set_int(conf, CONF_height, h); + + if (flags & (XValue | YValue)) { + inst->xpos = x; + inst->ypos = y; + inst->gotpos = true; + inst->gravity = ((flags & XNegative ? 1 : 0) | + (flags & YNegative ? 2 : 0)); + } + } +#endif + + 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); + if (!clipboard_atom) + clipboard_atom = gdk_atom_intern("CLIPBOARD", false); + + inst->area = gtk_drawing_area_new(); + gtk_widget_set_name(GTK_WIDGET(inst->area), "drawing-area"); + + { + char *errmsg = setup_fonts_ucs(inst); + if (errmsg) { + window_setup_error(errmsg); + sfree(errmsg); + gtk_widget_destroy(inst->area); + sfree(inst); + return; + } + } + +#if GTK_CHECK_VERSION(2,0,0) + inst->imc = gtk_im_multicontext_new(); +#endif + + inst->window = make_gtk_toplevel_window(inst); + gtk_widget_set_name(GTK_WIDGET(inst->window), "top-level"); + { + const char *winclass = conf_get_str(inst->conf, CONF_winclass); + if (*winclass) { +#if GTK_CHECK_VERSION(3,22,0) +#ifndef NOT_X_WINDOWS + GdkWindow *gdkwin; + gtk_widget_realize(GTK_WIDGET(inst->window)); + gdkwin = gtk_widget_get_window(GTK_WIDGET(inst->window)); + if (inst->disp && gdk_window_ensure_native(gdkwin)) { + XClassHint *xch = XAllocClassHint(); + xch->res_name = (char *)winclass; + xch->res_class = (char *)winclass; + XSetClassHint(inst->disp, GDK_WINDOW_XID(gdkwin), xch); + XFree(xch); + } +#endif + /* + * If we do have NOT_X_WINDOWS set, then we don't have any + * function in GTK 3.22 equivalent to the above. But then, + * surely in that situation the deprecated + * gtk_window_set_wmclass wouldn't have done anything + * meaningful in previous GTKs either. + */ +#else + gtk_window_set_wmclass(GTK_WINDOW(inst->window), + winclass, winclass); +#endif + } + } + + inst->width = conf_get_int(inst->conf, CONF_width); + inst->height = conf_get_int(inst->conf, CONF_height); + cache_conf_values(inst); + + init_clipboard(inst); + + 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 (conf_get_bool(inst->conf, CONF_scrollbar_on_left)) + gtk_box_pack_start(inst->hbox, inst->sbar, false, false, 0); + gtk_box_pack_start(inst->hbox, inst->area, true, true, 0); + if (!conf_get_bool(inst->conf, CONF_scrollbar_on_left)) + gtk_box_pack_start(inst->hbox, inst->sbar, false, false, 0); + + gtk_container_add(GTK_CONTAINER(inst->window), GTK_WIDGET(inst->hbox)); + + gtk_widget_show(inst->area); + show_scrollbar(inst, conf_get_bool(inst->conf, CONF_scrollbar)); + gtk_widget_show(GTK_WIDGET(inst->hbox)); + + /* + * We must call gtk_widget_realize before setting up the geometry + * hints, so that GtkApplicationWindow will have actually created + * its menu bar (if it's going to) and hence compute_geom_hints + * can find it to take its size into account. + */ + gtk_widget_realize(inst->window); + set_geom_hints(inst); + +#if GTK_CHECK_VERSION(3,0,0) + { + int wp, hp; + compute_whole_window_size(inst, inst->width, inst->height, &wp, &hp); + gtk_window_set_default_size(GTK_WINDOW(inst->window), wp, hp); + } +#else + { + int w = inst->font_width * inst->width + 2*inst->window_border; + int h = inst->font_height * inst->height + 2*inst->window_border; +#if GTK_CHECK_VERSION(2,0,0) + gtk_widget_set_size_request(inst->area, w, h); +#else + gtk_drawing_area_size(GTK_DRAWING_AREA(inst->area), w, h); +#endif + } +#endif + +#if GTK_CHECK_VERSION(2,0,0) + if (inst->gotpos) { + static const GdkGravity gravities[] = { + GDK_GRAVITY_NORTH_WEST, + GDK_GRAVITY_NORTH_EAST, + GDK_GRAVITY_SOUTH_WEST, + GDK_GRAVITY_SOUTH_EAST, + }; + int x = inst->xpos, y = inst->ypos; + int wp, hp; + GdkRectangle monitor_geometry; + compute_whole_window_size(inst, inst->width, inst->height, &wp, &hp); + get_monitor_geometry(GTK_WIDGET(inst->window), &monitor_geometry); + if (inst->gravity & 1) x += (monitor_geometry.width - wp); + if (inst->gravity & 2) y += (monitor_geometry.height - hp); + gtk_window_set_gravity(GTK_WINDOW(inst->window), + gravities[inst->gravity & 3]); + gtk_window_move(GTK_WINDOW(inst->window), x, y); + } +#else + 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); + } +#endif + + g_signal_connect(G_OBJECT(inst->window), "destroy", + G_CALLBACK(destroy), inst); + g_signal_connect(G_OBJECT(inst->window), "delete_event", + G_CALLBACK(delete_window), inst); + g_signal_connect(G_OBJECT(inst->window), "key_press_event", + G_CALLBACK(key_event), inst); + g_signal_connect(G_OBJECT(inst->window), "key_release_event", + G_CALLBACK(key_event), inst); + g_signal_connect(G_OBJECT(inst->window), "focus_in_event", + G_CALLBACK(focus_event), inst); + g_signal_connect(G_OBJECT(inst->window), "focus_out_event", + G_CALLBACK(focus_event), inst); + g_signal_connect(G_OBJECT(inst->area), "realize", + G_CALLBACK(area_realised), inst); + g_signal_connect(G_OBJECT(inst->area), "size_allocate", + G_CALLBACK(area_size_allocate), inst); + g_signal_connect(G_OBJECT(inst->window), "configure_event", + G_CALLBACK(window_configured), inst); +#if GTK_CHECK_VERSION(3,10,0) + g_signal_connect(G_OBJECT(inst->area), "configure_event", + G_CALLBACK(area_configured), inst); +#endif +#if GTK_CHECK_VERSION(3,0,0) + g_signal_connect(G_OBJECT(inst->area), "draw", + G_CALLBACK(draw_area), inst); +#else + g_signal_connect(G_OBJECT(inst->area), "expose_event", + G_CALLBACK(expose_area), inst); +#endif + g_signal_connect(G_OBJECT(inst->area), "button_press_event", + G_CALLBACK(button_event), inst); + g_signal_connect(G_OBJECT(inst->area), "button_release_event", + G_CALLBACK(button_event), inst); +#if GTK_CHECK_VERSION(2,0,0) + g_signal_connect(G_OBJECT(inst->area), "scroll_event", + G_CALLBACK(scroll_event), inst); +#endif + g_signal_connect(G_OBJECT(inst->area), "motion_notify_event", + G_CALLBACK(motion_event), inst); +#if GTK_CHECK_VERSION(2,0,0) + g_signal_connect(G_OBJECT(inst->imc), "commit", + G_CALLBACK(input_method_commit_event), inst); +#endif + if (conf_get_bool(inst->conf, CONF_scrollbar)) + g_signal_connect(G_OBJECT(inst->sbar_adjust), "value_changed", + G_CALLBACK(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 +#if GTK_CHECK_VERSION(3,4,0) + | GDK_SMOOTH_SCROLL_MASK +#endif + ); + + 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; + + 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); \ + g_signal_connect(G_OBJECT(menuitem), "activate", \ + G_CALLBACK(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); + MKSEP(); + MKMENUITEM("Copy to " CLIPNAME_EXPLICIT_OBJECT, + copy_clipboard_menuitem); + MKMENUITEM("Paste from " CLIPNAME_EXPLICIT_OBJECT, + paste_clipboard_menuitem); + MKMENUITEM("Copy All", copy_all_menuitem); + MKSEP(); + s = dupcat("About ", appname); + 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); + inst->currcursor = inst->textcursor; + show_mouseptr(inst, true); + + inst->eventlogstuff = eventlogstuff_new(); + + inst->term = term_init(inst->conf, &inst->ucsdata, &inst->termwin); + setup_clipboards(inst, inst->term, inst->conf); + inst->logctx = log_init(&inst->logpolicy, inst->conf); + term_provide_logctx(inst->term, inst->logctx); + + term_size(inst->term, inst->height, inst->width, + conf_get_int(inst->conf, CONF_savelines)); + +#if GTK_CHECK_VERSION(2,0,0) + /* Delay this signal connection until after inst->term exists */ + g_signal_connect(G_OBJECT(inst->window), "window_state_event", + G_CALLBACK(window_state_event), inst); +#endif + + inst->exited = false; + + start_backend(inst); + + if (inst->ldisc) /* early backend failure might make this NULL already */ + ldisc_echoedit_update(inst->ldisc); /* cause ldisc to notice changes */ +} + +static bool gtk_seat_set_trust_status(Seat *seat, bool trusted) +{ + GtkFrontend *inst = container_of(seat, GtkFrontend, seat); + term_set_trust_status(inst->term, trusted); + return true; +} + +static bool gtk_seat_get_cursor_position(Seat *seat, int *x, int *y) +{ + GtkFrontend *inst = container_of(seat, GtkFrontend, seat); + if (inst->term) { + term_get_cursor_position(inst->term, x, y); + return true; + } + return false; +} diff --git a/unix/x11.c b/unix/x11.c new file mode 100644 index 00000000..7a0c2218 --- /dev/null +++ b/unix/x11.c @@ -0,0 +1,208 @@ +/* + * ux_x11.c: fetch local auth data for X forwarding. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "putty.h" +#include "ssh.h" +#include "network.h" + +void platform_get_x11_auth(struct X11Display *disp, Conf *conf) +{ + char *xauthfile; + bool needs_free; + + /* + * Find the .Xauthority file. + */ + needs_free = false; + xauthfile = getenv("XAUTHORITY"); + if (!xauthfile) { + xauthfile = getenv("HOME"); + if (xauthfile) { + xauthfile = dupcat(xauthfile, "/.Xauthority"); + needs_free = true; + } + } + + if (xauthfile) { + x11_get_auth_from_authfile(disp, xauthfile); + if (needs_free) + sfree(xauthfile); + } +} + +const bool platform_uses_x11_unix_by_default = true; + +int platform_make_x11_server(Plug *plug, const char *progname, int mindisp, + const char *screen_number_suffix, + ptrlen authproto, ptrlen authdata, + Socket **sockets, Conf *conf) +{ + char *tmpdir; + char *authfilename = NULL; + strbuf *authfiledata = NULL; + char *unix_path = NULL; + + SockAddr *a_tcp = NULL, *a_unix = NULL; + + int authfd; + FILE *authfp; + + int displayno; + + authfiledata = strbuf_new_nm(); + + int nsockets = 0; + + /* + * Look for a free TCP port to run our server on. + */ + for (displayno = mindisp;; displayno++) { + const char *err; + int tcp_port = displayno + 6000; + int addrtype = ADDRTYPE_IPV4; + + sockets[nsockets] = new_listener( + NULL, tcp_port, plug, false, conf, addrtype); + + err = sk_socket_error(sockets[nsockets]); + if (!err) { + char *hostname = get_hostname(); + if (hostname) { + char *canonicalname = NULL; + a_tcp = sk_namelookup(hostname, &canonicalname, addrtype); + sfree(canonicalname); + } + sfree(hostname); + nsockets++; + break; /* success! */ + } else { + sk_close(sockets[nsockets]); + } + + if (!strcmp(err, strerror(EADDRINUSE))) /* yuck! */ + goto out; + } + + if (a_tcp) { + x11_format_auth_for_authfile( + BinarySink_UPCAST(authfiledata), + a_tcp, displayno, authproto, authdata); + } + + /* + * Try to establish the Unix-domain analogue. That may or may not + * work - file permissions in /tmp may prevent it, for example - + * but it's worth a try, and we don't consider it a fatal error if + * it doesn't work. + */ + unix_path = dupprintf("/tmp/.X11-unix/X%d", displayno); + a_unix = unix_sock_addr(unix_path); + + sockets[nsockets] = new_unix_listener(a_unix, plug); + if (!sk_socket_error(sockets[nsockets])) { + x11_format_auth_for_authfile( + BinarySink_UPCAST(authfiledata), + a_unix, displayno, authproto, authdata); + nsockets++; + } else { + sk_close(sockets[nsockets]); + sfree(unix_path); + unix_path = NULL; + } + + /* + * Decide where the authority data will be written. + */ + + tmpdir = getenv("TMPDIR"); + if (!tmpdir || !*tmpdir) + tmpdir = "/tmp"; + + authfilename = dupcat(tmpdir, "/", progname, "-Xauthority-XXXXXX"); + + { + int oldumask = umask(077); + authfd = mkstemp(authfilename); + umask(oldumask); + } + if (authfd < 0) { + while (nsockets-- > 0) + sk_close(sockets[nsockets]); + goto out; + } + + /* + * Spawn a subprocess which will try to reliably delete our + * auth file when we terminate, in case we die unexpectedly. + */ + { + int cleanup_pipe[2]; + pid_t pid; + + /* Don't worry if pipe or fork fails; it's not _that_ critical. */ + if (!pipe(cleanup_pipe)) { + if ((pid = fork()) == 0) { + int buf[1024]; + /* + * Our parent process holds the writing end of + * this pipe, and writes nothing to it. Hence, + * we expect read() to return EOF as soon as + * that process terminates. + */ + + close(0); + close(1); + close(2); + + setpgid(0, 0); + close(cleanup_pipe[1]); + close(authfd); + while (read(cleanup_pipe[0], buf, sizeof(buf)) > 0); + unlink(authfilename); + if (unix_path) + unlink(unix_path); + _exit(0); + } else if (pid < 0) { + close(cleanup_pipe[0]); + close(cleanup_pipe[1]); + } else { + close(cleanup_pipe[0]); + cloexec(cleanup_pipe[1]); + } + } + } + + authfp = fdopen(authfd, "wb"); + fwrite(authfiledata->u, 1, authfiledata->len, authfp); + fclose(authfp); + + { + char *display = dupprintf(":%d%s", displayno, screen_number_suffix); + conf_set_str_str(conf, CONF_environmt, "DISPLAY", display); + sfree(display); + } + conf_set_str_str(conf, CONF_environmt, "XAUTHORITY", authfilename); + + /* + * FIXME: return at least the DISPLAY and XAUTHORITY env settings, + * and perhaps also the display number + */ + + out: + if (a_tcp) + sk_addr_free(a_tcp); + /* a_unix doesn't need freeing, because new_unix_listener took it over */ + sfree(authfilename); + strbuf_free(authfiledata); + sfree(unix_path); + return nsockets; +} diff --git a/unix/xpmptcfg.c b/unix/xpmptcfg.c deleted file mode 100644 index 92835c15..00000000 --- a/unix/xpmptcfg.c +++ /dev/null @@ -1,150 +0,0 @@ -/* 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; diff --git a/unix/xpmpterm.c b/unix/xpmpterm.c deleted file mode 100644 index aea5e4e2..00000000 --- a/unix/xpmpterm.c +++ /dev/null @@ -1,143 +0,0 @@ -/* 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; diff --git a/unix/xpmpucfg.c b/unix/xpmpucfg.c deleted file mode 100644 index 34c5d491..00000000 --- a/unix/xpmpucfg.c +++ /dev/null @@ -1,150 +0,0 @@ -/* 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; diff --git a/unix/xpmputty.c b/unix/xpmputty.c deleted file mode 100644 index 56d16bee..00000000 --- a/unix/xpmputty.c +++ /dev/null @@ -1,147 +0,0 @@ -/* 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; diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt index c37abd1e..739b49f3 100644 --- a/windows/CMakeLists.txt +++ b/windows/CMakeLists.txt @@ -2,7 +2,7 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) add_sources_from_current_dir(utils utils/arm_arch_queries.c - utils/capi.c + utils/cryptoapi.c utils/defaults.c utils/dll_hijacking_protection.c utils/dputs.c @@ -26,38 +26,38 @@ add_sources_from_current_dir(utils utils/split_into_argv.c utils/version.c utils/win_strerror.c - winucs.c) + unicode.c) if(NOT HAVE_STRTOUMAX) add_sources_from_current_dir(utils utils/strtoumax.c) endif() add_sources_from_current_dir(eventloop - wincliloop.c winhandl.c) + cliloop.c handle-io.c) add_sources_from_current_dir(console - winselcli.c winnohlp.c wincons.c) + select-cli.c nohelp.c console.c) add_sources_from_current_dir(settings - winstore.c) + storage.c) add_sources_from_current_dir(network - winnet.c winhsock.c winnpc.c winnps.c winproxy.c winx11.c) + network.c handle-socket.c named-pipe-client.c named-pipe-server.c local-proxy.c x11.c) add_sources_from_current_dir(sshcommon - winnoise.c) + noise.c) add_sources_from_current_dir(sshclient - winpgntc.c wingss.c winshare.c) + agent-client.c gss.c sharing.c) add_sources_from_current_dir(sftpclient - winsftp.c) + sftp.c) add_sources_from_current_dir(otherbackends - winser.c) + serial.c) add_sources_from_current_dir(agent - winpgntc.c) + agent-client.c) add_sources_from_current_dir(guiterminal - windlg.c winctrls.c wincfg.c winprint.c winjump.c sizetip.c) -add_dependencies(guiterminal generated_licence_h) # windlg.c uses licence.h + dialog.c controls.c config.c printing.c jump-list.c sizetip.c) +add_dependencies(guiterminal generated_licence_h) # dialog.c uses licence.h add_library(guimisc STATIC - winselgui.c) + select-gui.c) add_executable(pageant - winpgnt.c - winhelp.c + pageant.c + help.c pageant.rc) add_dependencies(pageant generated_licence_h) target_link_libraries(pageant @@ -69,10 +69,10 @@ set_target_properties(pageant PROPERTIES installed_program(pageant) add_executable(plink - winplink.c + plink.c ${CMAKE_SOURCE_DIR}/be_all_s.c - winnojmp.c - winnohlp.c + no-jump-list.c + nohelp.c plink.rc) add_dependencies(plink generated_licence_h) target_link_libraries(plink @@ -84,8 +84,8 @@ installed_program(plink) add_executable(pscp ${CMAKE_SOURCE_DIR}/pscp.c ${CMAKE_SOURCE_DIR}/be_ssh.c - winnojmp.c - winnohlp.c + no-jump-list.c + nohelp.c pscp.rc) add_dependencies(pscp generated_licence_h) target_link_libraries(pscp @@ -96,8 +96,8 @@ installed_program(pscp) add_executable(psftp ${CMAKE_SOURCE_DIR}/psftp.c ${CMAKE_SOURCE_DIR}/be_ssh.c - winnojmp.c - winnohlp.c + no-jump-list.c + nohelp.c psftp.rc) add_dependencies(psftp generated_licence_h) target_link_libraries(psftp @@ -106,8 +106,8 @@ target_link_libraries(psftp installed_program(psftp) add_executable(psocks - winsocks.c - winnohlp.c + psocks.c + nohelp.c ${CMAKE_SOURCE_DIR}/psocks.c ${CMAKE_SOURCE_DIR}/norand.c ${CMAKE_SOURCE_DIR}/nocproxy.c @@ -118,7 +118,7 @@ target_link_libraries(psocks add_executable(putty window.c - winhelp.c + help.c ${CMAKE_SOURCE_DIR}/be_all_s.c putty.rc) add_dependencies(putty generated_licence_h) @@ -133,7 +133,7 @@ installed_program(putty) add_executable(puttytel window.c - winhelp.c + help.c ${CMAKE_SOURCE_DIR}/be_nos_s.c ${CMAKE_SOURCE_DIR}/nogss.c ${CMAKE_SOURCE_DIR}/norand.c @@ -149,15 +149,15 @@ set_target_properties(puttytel PROPERTIES installed_program(puttytel) add_executable(puttygen - winpgen.c + puttygen.c ${CMAKE_SOURCE_DIR}/notiming.c - winnoise.c - winnojmp.c - winstore.c - winhelp.c + noise.c + no-jump-list.c + storage.c + help.c ${CMAKE_SOURCE_DIR}/sshpubk.c ${CMAKE_SOURCE_DIR}/sshrand.c - winctrls.c + controls.c puttygen.rc) add_dependencies(puttygen generated_licence_h) target_link_libraries(puttygen diff --git a/windows/agent-client.c b/windows/agent-client.c new file mode 100644 index 00000000..47123cc9 --- /dev/null +++ b/windows/agent-client.c @@ -0,0 +1,303 @@ +/* + * Pageant client code. + */ + +#include +#include +#include + +#include "putty.h" +#include "pageant.h" /* for AGENT_MAX_MSGLEN */ + +#include "security-api.h" +#include "cryptoapi.h" + +#define AGENT_COPYDATA_ID 0x804e50ba /* random goop */ + +static bool wm_copydata_agent_exists(void) +{ + HWND hwnd; + hwnd = FindWindow("Pageant", "Pageant"); + if (!hwnd) + return false; + else + return true; +} + +static void wm_copydata_agent_query(strbuf *query, void **out, int *outlen) +{ + 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; + + if (query->len > AGENT_MAX_MSGLEN) + return; /* query too large */ + + hwnd = FindWindow("Pageant", "Pageant"); + if (!hwnd) + return; /* *out == NULL, so failure */ + mapname = dupprintf("PageantRequest%08x", (unsigned)GetCurrentThreadId()); + + psa = NULL; + if (got_advapi()) { + /* + * Make the file mapping we create for communication with + * Pageant owned by the user SID rather than the default. This + * should make communication between processes with slightly + * different contexts more reliable: in particular, command + * 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(); + + 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; + } + } + } + } + + filemap = CreateFileMapping(INVALID_HANDLE_VALUE, psa, PAGE_READWRITE, + 0, AGENT_MAX_MSGLEN, mapname); + if (filemap == NULL || filemap == INVALID_HANDLE_VALUE) { + sfree(mapname); + return; /* *out == NULL, so failure */ + } + p = MapViewOfFile(filemap, FILE_MAP_WRITE, 0, 0, 0); + strbuf_finalise_agent_query(query); + memcpy(p, query->s, query->len); + cds.dwData = AGENT_COPYDATA_ID; + cds.cbData = 1 + strlen(mapname); + cds.lpData = mapname; + + /* + * 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) { + uint32_t length_field = GET_32BIT_MSB_FIRST(p); + if (length_field > 0 && length_field <= AGENT_MAX_MSGLEN - 4) { + retlen = length_field + 4; + ret = snewn(retlen, unsigned char); + memcpy(ret, p, retlen); + *out = ret; + *outlen = retlen; + } else { + /* + * If we get here, we received an out-of-range length + * field, either without space for a message type code or + * overflowing the FileMapping. + * + * Treat this as if Pageant didn't answer at all - which + * actually means we do nothing, and just don't fill in + * out and outlen. + */ + } + } + UnmapViewOfFile(p); + CloseHandle(filemap); + sfree(mapname); + if (psd) + LocalFree(psd); +} + +char *agent_named_pipe_name(void) +{ + char *username, *suffix, *pipename; + username = get_username(); + suffix = capi_obfuscate_string("Pageant"); + pipename = dupprintf("\\\\.\\pipe\\pageant.%s.%s", username, suffix); + sfree(username); + sfree(suffix); + return pipename; +} + +Socket *agent_connect(Plug *plug) +{ + char *pipename = agent_named_pipe_name(); + Socket *s = new_named_pipe_client(pipename, plug); + sfree(pipename); + return s; +} + +static bool named_pipe_agent_exists(void) +{ + char *pipename = agent_named_pipe_name(); + WIN32_FIND_DATA data; + HANDLE ffh = FindFirstFile(pipename, &data); + sfree(pipename); + if (ffh == INVALID_HANDLE_VALUE) + return false; + FindClose(ffh); + return true; +} + +bool agent_exists(void) +{ + return named_pipe_agent_exists() || wm_copydata_agent_exists(); +} + +struct agent_pending_query { + struct handle *handle; + strbuf *response; + void (*callback)(void *, void *, int); + void *callback_ctx; +}; + +static int named_pipe_agent_accumulate_response( + strbuf *sb, const void *data, size_t len) +{ + put_data(sb, data, len); + if (sb->len >= 4) { + uint32_t length_field = GET_32BIT_MSB_FIRST(sb->u); + if (length_field > AGENT_MAX_MSGLEN) + return -1; /* badly formatted message */ + + int overall_length = length_field + 4; + if (sb->len >= overall_length) + return overall_length; + } + + return 0; /* not done yet */ +} + +static size_t named_pipe_agent_gotdata( + struct handle *h, const void *data, size_t len, int err) +{ + agent_pending_query *pq = handle_get_privdata(h); + + if (err || len == 0) { + pq->callback(pq->callback_ctx, NULL, 0); + agent_cancel_query(pq); + return 0; + } + + int status = named_pipe_agent_accumulate_response(pq->response, data, len); + if (status == -1) { + pq->callback(pq->callback_ctx, NULL, 0); + agent_cancel_query(pq); + } else if (status > 0) { + void *response_buf = strbuf_to_str(pq->response); + pq->response = NULL; + pq->callback(pq->callback_ctx, response_buf, status); + agent_cancel_query(pq); + } + return 0; +} + +static agent_pending_query *named_pipe_agent_query( + strbuf *query, void **out, int *outlen, + void (*callback)(void *, void *, int), void *callback_ctx) +{ + agent_pending_query *pq = NULL; + char *err = NULL, *pipename = NULL; + strbuf *sb = NULL; + HANDLE pipehandle; + + pipename = agent_named_pipe_name(); + pipehandle = connect_to_named_pipe(pipename, &err); + if (pipehandle == INVALID_HANDLE_VALUE) + goto failure; + + strbuf_finalise_agent_query(query); + + for (DWORD done = 0; done < query->len ;) { + DWORD nwritten; + bool ret = WriteFile(pipehandle, query->s + done, query->len - done, + &nwritten, NULL); + if (!ret) + goto failure; + + done += nwritten; + } + + if (!callback) { + int status; + + sb = strbuf_new_nm(); + do { + char buf[1024]; + DWORD nread; + bool ret = ReadFile(pipehandle, buf, sizeof(buf), &nread, NULL); + if (!ret) + goto failure; + status = named_pipe_agent_accumulate_response(sb, buf, nread); + } while (status == 0); + + if (status == -1) + goto failure; + + *out = strbuf_to_str(sb); + *outlen = status; + sb = NULL; + pq = NULL; + goto out; + } + + pq = snew(agent_pending_query); + pq->handle = handle_input_new(pipehandle, named_pipe_agent_gotdata, pq, 0); + pipehandle = NULL; /* prevent it being closed below */ + pq->response = strbuf_new_nm(); + pq->callback = callback; + pq->callback_ctx = callback_ctx; + goto out; + + failure: + *out = NULL; + *outlen = 0; + pq = NULL; + + out: + sfree(err); + sfree(pipename); + if (pipehandle != INVALID_HANDLE_VALUE) + CloseHandle(pipehandle); + if (sb) + strbuf_free(sb); + return pq; +} + +void agent_cancel_query(agent_pending_query *pq) +{ + handle_free(pq->handle); + if (pq->response) + strbuf_free(pq->response); + sfree(pq); +} + +agent_pending_query *agent_query( + strbuf *query, void **out, int *outlen, + void (*callback)(void *, void *, int), void *callback_ctx) +{ + agent_pending_query *pq = named_pipe_agent_query( + query, out, outlen, callback, callback_ctx); + if (pq || *out) + return pq; + + wm_copydata_agent_query(query, out, outlen); + return NULL; +} diff --git a/windows/cliloop.c b/windows/cliloop.c new file mode 100644 index 00000000..26a4d3aa --- /dev/null +++ b/windows/cliloop.c @@ -0,0 +1,136 @@ +#include "putty.h" + +void cli_main_loop(cliloop_pre_t pre, cliloop_post_t post, void *ctx) +{ + SOCKET *sklist = NULL; + size_t skcount = 0, sksize = 0; + unsigned long now, next, then; + now = GETTICKCOUNT(); + + while (true) { + int nhandles; + HANDLE *handles; + DWORD n; + DWORD ticks; + + const HANDLE *extra_handles = NULL; + size_t n_extra_handles = 0; + if (!pre(ctx, &extra_handles, &n_extra_handles)) + break; + + if (toplevel_callback_pending()) { + ticks = 0; + next = now; + } else if (run_timers(now, &next)) { + then = now; + now = GETTICKCOUNT(); + if (now - then > next - then) + ticks = 0; + else + ticks = next - now; + } else { + ticks = INFINITE; + /* no need to initialise next here because we can never + * get WAIT_TIMEOUT */ + } + + handles = handle_get_events(&nhandles); + size_t winselcli_index = -(size_t)1; + size_t extra_base = nhandles; + if (winselcli_event != INVALID_HANDLE_VALUE) { + winselcli_index = extra_base++; + handles = sresize(handles, extra_base, HANDLE); + handles[winselcli_index] = winselcli_event; + } + size_t total_handles = extra_base + n_extra_handles; + handles = sresize(handles, total_handles, HANDLE); + for (size_t i = 0; i < n_extra_handles; i++) + handles[extra_base + i] = extra_handles[i]; + + n = WaitForMultipleObjects(total_handles, handles, false, ticks); + + size_t extra_handle_index = n_extra_handles; + + if ((unsigned)(n - WAIT_OBJECT_0) < (unsigned)nhandles) { + handle_got_event(handles[n - WAIT_OBJECT_0]); + } else if (winselcli_event != INVALID_HANDLE_VALUE && + n == WAIT_OBJECT_0 + winselcli_index) { + WSANETWORKEVENTS things; + SOCKET socket; + 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. */ + sgrowarray(sklist, sksize, i); + + /* 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(NOISE_SOURCE_IOID, socket); + + 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); + } + } + } + } else if (n >= WAIT_OBJECT_0 + extra_base && + n < WAIT_OBJECT_0 + extra_base + n_extra_handles) { + extra_handle_index = n - (WAIT_OBJECT_0 + extra_base); + } + + run_toplevel_callbacks(); + + if (n == WAIT_TIMEOUT) { + now = next; + } else { + now = GETTICKCOUNT(); + } + + sfree(handles); + + if (!post(ctx, extra_handle_index)) + break; + } + + sfree(sklist); +} + +bool cliloop_null_pre(void *vctx, const HANDLE **eh, size_t *neh) +{ return true; } +bool cliloop_null_post(void *vctx, size_t ehi) { return true; } diff --git a/windows/config.c b/windows/config.c new file mode 100644 index 00000000..fab3240f --- /dev/null +++ b/windows/config.c @@ -0,0 +1,405 @@ +/* + * wincfg.c - the Windows-specific parts of the PuTTY configuration + * box. + */ + +#include +#include + +#include "putty.h" +#include "dialog.h" +#include "storage.h" + +static void about_handler(union control *ctrl, dlgparam *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, dlgparam *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, dlgparam *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, bool has_help, + bool midsession, int protocol) +{ + const struct BackendVtable *backvt; + bool resize_forbidden = false; + 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), + conf_checkbox_handler, + I(CONF_scrollbar_in_fullscreen)); + /* + * Really this wants to go just after `Display scrollbar'. See + * if we can find that control, and do some shuffling. + */ + { + int i; + for (i = 0; i < s->ncontrols; i++) { + c = s->ctrls[i]; + if (c->generic.type == CTRL_CHECKBOX && + c->generic.context.i == CONF_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), + conf_checkbox_handler, I(CONF_compose_key)); + ctrl_checkbox(s, "Control-Alt is different from AltGr", 'd', + HELPCTX(keyboard_ctrlalt), + conf_checkbox_handler, I(CONF_ctrlaltkeys)); + + /* + * Windows allows an arbitrary .WAV to be played as a bell, and + * also the use of the PC speaker. For this we must search the + * existing controlset for the radio-button set controlling the + * `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 == CONF_beep) { + assert(c->generic.handler == conf_radiobutton_handler); + c->radio.nbuttons += 2; + c->radio.buttons = + sresize(c->radio.buttons, c->radio.nbuttons, char *); + c->radio.buttons[c->radio.nbuttons-1] = + dupstr("Play a custom sound file"); + 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), + conf_filesel_handler, I(CONF_bell_wavefile)); + + /* + * While we've got this box open, taskbar flashing on a bell is + * also Windows-specific. + */ + ctrl_radiobuttons(s, "Taskbar/caption indication on bell:", 'i', 3, + HELPCTX(bell_taskbar), + conf_radiobutton_handler, + I(CONF_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), + conf_checkbox_handler, I(CONF_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), + conf_radiobutton_handler, + I(CONF_font_quality), + "Antialiased", I(FQ_ANTIALIASED), + "Non-Antialiased", I(FQ_NONANTIALIASED), + "ClearType", I(FQ_CLEARTYPE), + "Default", I(FQ_DEFAULT), NULL); + + /* + * 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), + conf_checkbox_handler, + I(CONF_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 == CONF_vtmode) { + assert(c->generic.handler == conf_radiobutton_handler); + c->radio.nbuttons += 3; + c->radio.buttons = + sresize(c->radio.buttons, c->radio.nbuttons, char *); + c->radio.buttons[c->radio.nbuttons-3] = + dupstr("Font has XWindows encoding"); + 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/Copy", "format", + "Formatting of copied characters"); + ctrl_checkbox(s, "Copy to clipboard in RTF as well as plain text", 'f', + HELPCTX(copy_rtf), + conf_checkbox_handler, I(CONF_rtf_paste)); + + /* + * Windows often has no middle button, so we supply a selection + * mode in which the more critical Paste action is available on + * the right button instead. + */ + s = ctrl_getset(b, "Window/Selection", "mouse", + "Control use of mouse"); + ctrl_radiobuttons(s, "Action of mouse buttons:", 'm', 1, + HELPCTX(selection_buttons), + conf_radiobutton_handler, + I(CONF_mouse_is_xterm), + "Windows (Middle extends, Right brings up menu)", I(2), + "Compromise (Middle extends, Right pastes)", I(0), + "xterm (Right extends, Middle pastes)", I(1), NULL); + /* + * This really ought to go at the _top_ of its box, not the + * 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), + conf_checkbox_handler, I(CONF_try_palette)); + ctrl_checkbox(s, "Use system colours", 's', + HELPCTX(colours_system), + conf_checkbox_handler, I(CONF_system_colour)); + + + /* + * Resize-by-changing-font is a Windows insanity. + */ + + backvt = backend_vt_from_proto(protocol); + if (backvt) + resize_forbidden = (backvt->flags & BACKEND_RESIZE_FORBIDDEN); + if (!midsession || !resize_forbidden) { + s = ctrl_getset(b, "Window", "size", "Set the size of the window"); + ctrl_radiobuttons(s, "When window is resized:", 'z', 1, + HELPCTX(window_resize), + conf_radiobutton_handler, + I(CONF_resize_action), + "Change the number of rows and columns", I(RESIZE_TERM), + "Change the size of the font", I(RESIZE_FONT), + "Change font size only when maximised", I(RESIZE_EITHER), + "Forbid resizing completely", I(RESIZE_DISABLED), NULL); + } + + /* + * 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), + conf_checkbox_handler, I(CONF_alt_f4)); + ctrl_checkbox(s, "System menu appears on ALT-Space", 'y', + HELPCTX(behaviour_altspace), + conf_checkbox_handler, I(CONF_alt_space)); + ctrl_checkbox(s, "System menu appears on ALT alone", 'l', + HELPCTX(behaviour_altonly), + conf_checkbox_handler, I(CONF_alt_only)); + ctrl_checkbox(s, "Ensure window is always on top", 'e', + HELPCTX(behaviour_alwaysontop), + conf_checkbox_handler, I(CONF_alwaysontop)); + ctrl_checkbox(s, "Full screen on Alt-Enter", 'f', + HELPCTX(behaviour_altenter), + conf_checkbox_handler, + I(CONF_fullscreenonaltenter)); + + /* + * Windows supports a local-command proxy. This also means we + * must adjust the text on the `Telnet command' control. + */ + 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 == CONF_proxy_type) { + assert(c->generic.handler == conf_radiobutton_handler); + c->radio.nbuttons++; + c->radio.buttons = + sresize(c->radio.buttons, c->radio.nbuttons, char *); + c->radio.buttons[c->radio.nbuttons-1] = + dupstr("Local"); + 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 == CONF_proxy_telnet_command) { + assert(c->generic.handler == conf_editbox_handler); + sfree(c->generic.label); + c->generic.label = dupstr("Telnet command, or local" + " proxy command"); + break; + } + } + } + + /* + * $XAUTHORITY is not reliable on Windows, so we provide a + * means to override it. + */ + if (!midsession && backend_vt_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), + conf_filesel_handler, I(CONF_xauthfile)); + } +} diff --git a/windows/console.c b/windows/console.c new file mode 100644 index 00000000..414167b4 --- /dev/null +++ b/windows/console.c @@ -0,0 +1,452 @@ +/* + * wincons.c - various interactive-prompt routines shared between + * the Windows console PuTTY tools + */ + +#include +#include + +#include "putty.h" +#include "storage.h" +#include "ssh.h" +#include "console.h" + +void cleanup_exit(int code) +{ + /* + * Clean up. + */ + sk_cleanup(); + + random_save_seed(); + + exit(code); +} + +void console_print_error_msg(const char *prefix, const char *msg) +{ + fputs(prefix, stderr); + fputs(": ", stderr); + fputs(msg, stderr); + fputc('\n', stderr); + fflush(stderr); +} + +int console_verify_ssh_host_key( + Seat *seat, const char *host, int port, const char *keytype, + char *keystr, const char *keydisp, char **fingerprints, + void (*callback)(void *ctx, int result), void *ctx) +{ + int ret; + HANDLE hin; + DWORD savemode, i; + const char *common_fmt, *intro, *prompt; + + 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 */ + common_fmt = hk_wrongmsg_common_fmt; + intro = hk_wrongmsg_interactive_intro; + prompt = hk_wrongmsg_interactive_prompt; + } else { /* key was absent */ + common_fmt = hk_absentmsg_common_fmt; + intro = hk_absentmsg_interactive_intro; + prompt = hk_absentmsg_interactive_prompt; + } + + FingerprintType fptype_default = + ssh2_pick_default_fingerprint(fingerprints); + + fprintf(stderr, common_fmt, keytype, fingerprints[fptype_default]); + if (console_batch_mode) { + fputs(console_abandoned_msg, stderr); + return 0; + } + + fputs(intro, stderr); + fflush(stderr); + + while (true) { + fputs(prompt, stderr); + fflush(stderr); + + line[0] = '\0'; /* fail safe if ReadFile returns no data */ + + 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] == 'i' || line[0] == 'I') { + fprintf(stderr, "Full public key:\n%s\n", keydisp); + if (fingerprints[SSH_FPTYPE_SHA256]) + fprintf(stderr, "SHA256 key fingerprint:\n%s\n", + fingerprints[SSH_FPTYPE_SHA256]); + if (fingerprints[SSH_FPTYPE_MD5]) + fprintf(stderr, "MD5 key fingerprint:\n%s\n", + fingerprints[SSH_FPTYPE_MD5]); + } else { + break; + } + } + + /* In case of misplaced reflexes from another program, also recognise 'q' + * as 'abandon connection rather than trust this key' */ + if (line[0] != '\0' && line[0] != '\r' && line[0] != '\n' && + line[0] != 'q' && line[0] != 'Q') { + if (line[0] == 'y' || line[0] == 'Y') + store_host_key(host, port, keytype, keystr); + return 1; + } else { + fputs(console_abandoned_msg, stderr); + return 0; + } +} + +int console_confirm_weak_crypto_primitive( + Seat *seat, const char *algtype, const char *algname, + void (*callback)(void *ctx, int result), void *ctx) +{ + HANDLE hin; + DWORD savemode, i; + + char line[32]; + + fprintf(stderr, weakcrypto_msg_common_fmt, algtype, algname); + + if (console_batch_mode) { + fputs(console_abandoned_msg, stderr); + return 0; + } + + fputs(console_continue_prompt, stderr); + 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 { + fputs(console_abandoned_msg, stderr); + return 0; + } +} + +int console_confirm_weak_cached_hostkey( + Seat *seat, const char *algname, const char *betteralgs, + void (*callback)(void *ctx, int result), void *ctx) +{ + HANDLE hin; + DWORD savemode, i; + + char line[32]; + + fprintf(stderr, weakhk_msg_common_fmt, algname, betteralgs); + + if (console_batch_mode) { + fputs(console_abandoned_msg, stderr); + return 0; + } + + fputs(console_continue_prompt, stderr); + 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 { + fputs(console_abandoned_msg, stderr); + return 0; + } +} + +bool is_interactive(void) +{ + return is_console_handle(GetStdHandle(STD_INPUT_HANDLE)); +} + +bool console_antispoof_prompt = true; +bool console_set_trust_status(Seat *seat, bool trusted) +{ + if (console_batch_mode || !is_interactive() || !console_antispoof_prompt) { + /* + * In batch mode, we don't need to worry about the server + * mimicking our interactive authentication, because the user + * already knows not to expect any. + * + * If standard input isn't connected to a terminal, likewise, + * because even if the server did send a spoof authentication + * prompt, the user couldn't respond to it via the terminal + * anyway. + * + * We also vacuously return success if the user has purposely + * disabled the antispoof prompt. + */ + return true; + } + + return false; +} + +/* + * 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 console_askappend(LogPolicy *lp, 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 as of " PGP_MASTER_KEY_YEAR + " (" PGP_MASTER_KEY_DETAILS "):\n" + " " PGP_MASTER_KEY_FP "\n\n" + "Previous Master Key (" PGP_PREV_MASTER_KEY_YEAR + ", " PGP_PREV_MASTER_KEY_DETAILS "):\n" + " " PGP_PREV_MASTER_KEY_FP "\n", stdout); +} + +void console_logging_error(LogPolicy *lp, const char *string) +{ + /* Ordinary Event Log entries are displayed in the same way as + * logging errors, but only in verbose mode */ + fprintf(stderr, "%s\n", string); + fflush(stderr); +} + +void console_eventlog(LogPolicy *lp, const char *string) +{ + /* Ordinary Event Log entries are displayed in the same way as + * logging errors, but only in verbose mode */ + if (lp_verbose(lp)) + console_logging_error(lp, string); +} + +StripCtrlChars *console_stripctrl_new( + Seat *seat, BinarySink *bs_out, SeatInteractionContext sic) +{ + return stripctrl_new(bs_out, false, 0); +} + +static void console_write(HANDLE hout, ptrlen data) +{ + DWORD dummy; + WriteFile(hout, data.ptr, data.len, &dummy, NULL); +} + +int console_get_userpass_input(prompts_t *p) +{ + HANDLE hin = INVALID_HANDLE_VALUE, hout = INVALID_HANDLE_VALUE; + 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++) + prompt_set_result(p->prompts[i], ""); + } + + /* + * The prompts_t might contain a message to be displayed but no + * actual prompt. More usually, though, it will contain + * 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) { + ptrlen plname = ptrlen_from_asciz(p->name); + console_write(hout, plname); + if (!ptrlen_endswith(plname, PTRLEN_LITERAL("\n"), NULL)) + console_write(hout, PTRLEN_LITERAL("\n")); + } + /* ...but we always print any `instruction'. */ + if (p->instruction) { + ptrlen plinst = ptrlen_from_asciz(p->instruction); + console_write(hout, plinst); + if (!ptrlen_endswith(plinst, PTRLEN_LITERAL("\n"), NULL)) + console_write(hout, PTRLEN_LITERAL("\n")); + } + + for (curr_prompt = 0; curr_prompt < p->n_prompts; curr_prompt++) { + + DWORD savemode, newmode; + prompt_t *pr = p->prompts[curr_prompt]; + + 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_write(hout, ptrlen_from_asciz(pr->prompt)); + + bool failed = false; + while (1) { + /* + * Amount of data to try to read from the console in one + * go. This isn't completely arbitrary: a user reported + * that trying to read more than 31366 bytes at a time + * would fail with ERROR_NOT_ENOUGH_MEMORY on Windows 7, + * and Ruby's Win32 support module has evidence of a + * similar workaround: + * + * https://github.com/ruby/ruby/blob/0aa5195262d4193d3accf3e6b9bad236238b816b/win32/win32.c#L6842 + * + * To keep things simple, I stick with a nice round power + * of 2 rather than trying to go to the very limit of that + * bug. (We're typically reading user passphrases and the + * like here, so even this much is overkill really.) + */ + DWORD toread = 16384; + + size_t prev_result_len = pr->result->len; + void *ptr = strbuf_append(pr->result, toread); + + DWORD ret = 0; + if (!ReadFile(hin, ptr, toread, &ret, NULL) || ret == 0) { + failed = true; + break; + } + + strbuf_shrink_to(pr->result, prev_result_len + ret); + if (strbuf_chomp(pr->result, '\n')) { + strbuf_chomp(pr->result, '\r'); + break; + } + } + + SetConsoleMode(hin, savemode); + + if (!pr->echo) + console_write(hout, PTRLEN_LITERAL("\r\n")); + + if (failed) { + return 0; /* failure due to read error */ + } + } + + return 1; /* success */ +} diff --git a/windows/controls.c b/windows/controls.c new file mode 100644 index 00000000..59129eab --- /dev/null +++ b/windows/controls.c @@ -0,0 +1,2600 @@ +/* + * 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 +#include + +#include "putty.h" +#include "misc.h" +#include "dialog.h" + +#include + +#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 + +DECL_WINDOWS_FUNCTION(static, void, InitCommonControls, (void)); +DECL_WINDOWS_FUNCTION(static, BOOL, MakeDragList, (HWND)); +DECL_WINDOWS_FUNCTION(static, int, LBItemFromPt, (HWND, POINT, BOOL)); +DECL_WINDOWS_FUNCTION(static, void, DrawInsert, (HWND, HWND, int)); + +void init_common_controls(void) +{ + HMODULE comctl32_module = load_system32_dll("comctl32.dll"); + GET_WINDOWS_FUNCTION(comctl32_module, InitCommonControls); + GET_WINDOWS_FUNCTION(comctl32_module, MakeDragList); + GET_WINDOWS_FUNCTION(comctl32_module, LBItemFromPt); + GET_WINDOWS_FUNCTION(comctl32_module, DrawInsert); + p_InitCommonControls(); +} + +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)(ULONG_PTR)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, bool 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; + + r.left = GAPBETWEEN; + r.top = cp->ypos; + if (text) { + r.right = cp->width; + r.bottom = STATICHEIGHT; + cp->ypos += r.bottom + GAPWITHIN; + doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, text, id); + } else { + r.right = r.bottom = 0; + } + + 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 *); + if (!btext) + break; + (void) va_arg(ap, int); /* id */ + 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 *); + if (!btext) + break; + (void) va_arg(ap, int); /* id */ + 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, 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 *); + if (!btext) + break; + (void) va_arg(ap, int); /* id */ + nbuttons++; + } + 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 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; + + sfree(pwidths); + + 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, bool 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, bool 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 = doctl(cp, r, "LISTBOX", + WS_CHILD | WS_VISIBLE | WS_TABSTOP | + WS_VSCROLL | LBS_HASSTRINGS | LBS_USETABSTOPS, + WS_EX_CLIENTEDGE, + "", listid); + p_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 = p_LBItemFromPt(hwnd, cursor, scroll); + if (ret == -1) + return ret; + ret = p_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 = p_LBItemFromPt(hwnd, uppoint, false); + downitem = p_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, + bool 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 = p_LBItemFromPt(dlm->hWnd, dlm->ptCursor, true); + hdl->dragging = false; + /* XXX hack Q183115 */ + SetWindowLongPtr(hwnd, DWLP_MSGRESULT, true); + ret |= 1; break; + case DL_CANCELDRAG: + p_DrawInsert(hwnd, dlm->hWnd, -1); /* Clear arrow */ + SendDlgItemMessage(hwnd, hdl->listid, + LB_DELETESTRING, hdl->dummyitem, 0); + hdl->dragging = false; + ret |= 1; break; + case DL_DRAGGING: + hdl->dragging = true; + dest = pl_itemfrompt(dlm->hWnd, dlm->ptCursor, true); + if (dest > hdl->dummyitem) dest = hdl->dummyitem; + p_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; + p_DrawInsert (hwnd, dlm->hWnd, -1); + } + SendDlgItemMessage(hwnd, hdl->listid, + LB_DELETESTRING, hdl->dummyitem, 0); + if (hdl->dragging) { + hdl->dragging = false; + 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); +} + +static void move_windows(HWND hwnd, int base_id, int num_ids, LONG dy) +{ + if (!dy) + return; + for (int i = 0; i < num_ids; i++) { + HWND win = GetDlgItem(hwnd, base_id + i); + + RECT rect; + if (!GetWindowRect(win, &rect)) + continue; + + POINT p; + p.x = rect.left; + p.y = rect.top + dy; + if (!ScreenToClient(hwnd, &p)) + continue; + + SetWindowPos(win, NULL, p.x, p.y, 0, 0, + SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER); + } +} + +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, align_id_relative; + 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 = c->align_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 = c->align_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; + + /* For vertical alignment purposes, the most relevant control + * in a group is usually the last one. But that can be + * overridden occasionally. */ + align_id_relative = -1; + + /* + * 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); + data = fontspec_new("", false, 0, 0); + sfree(escaped); + break; + default: + unreachable("bad control type in winctrl_layout"); + } + + /* Translate the original align_id_relative of -1 into n-1 */ + if (align_id_relative < 0) + align_id_relative += num_ids; + + /* + * 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->align_id = c->base_id + align_id_relative; + 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 (ctrl->generic.align_next_to) { + /* + * Implement align_next_to by looking at the y extents + * of the two controls now that both are created, and + * moving one or the other downwards so that they're + * centred on a common horizontal line. + */ + struct winctrl *c2 = winctrl_findbyctrl( + wc, ctrl->generic.align_next_to); + HWND win1 = GetDlgItem(pos.hwnd, c->align_id); + HWND win2 = GetDlgItem(pos.hwnd, c2->align_id); + RECT rect1, rect2; + if (win1 && win2 && + GetWindowRect(win1, &rect1) && + GetWindowRect(win2, &rect2)) { + LONG top = (rect1.top < rect2.top ? rect1.top : rect2.top); + LONG bottom = (rect1.bottom > rect2.bottom ? + rect1.bottom : rect2.bottom); + move_windows(pos.hwnd, c->base_id, c->num_ids, + (top + bottom - rect1.top - rect1.bottom)/2); + move_windows(pos.hwnd, c2->base_id, c2->num_ids, + (top + bottom - rect2.top - rect2.bottom)/2); + } + } + } else { + sfree(data); + } + + 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, + bool 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, dlgparam *dp) +{ + return dp->focused == ctrl ? dp->lastfocused : dp->focused; +} + +/* + * The dialog-box procedure calls this function to handle Windows + * messages on a control we manage. + */ +bool winctrl_handle_command(struct dlgparam *dp, UINT msg, + WPARAM wParam, LPARAM lParam) +{ + struct winctrl *c; + union control *ctrl; + int i, id; + bool 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 false; + + /* + * 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 false; /* 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 false; /* 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 = false; + 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)) { + fs = fontspec_new(lf.lfFaceName, (lf.lfWeight == FW_BOLD), + cf.iPointSize / 10, lf.lfCharSet); + dlg_fontsel_set(ctrl, dp, fs); + fontspec_free(fs); + + ctrl->generic.handler(ctrl, dp, dp->data, EVENT_VALCHANGE); + } + } + break; + } + + /* + * 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. + */ +bool 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 false; /* 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 false; /* no help available for this ctrl */ + + launch_help(hwnd, c->ctrl->generic.helpctx.p); + return true; +} + +/* + * 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; +} + +bool dlg_is_visible(union control *ctrl, dlgparam *dp) +{ + /* + * In this implementation of the dialog box, we physically + * uncreate controls that aren't in a visible panel of the config + * box. So we can tell if a control is visible just by checking if + * it _exists_. + */ + return dlg_findbyctrl(dp, ctrl) != NULL; +} + +void dlg_radiobutton_set(union control *ctrl, dlgparam *dp, int whichbutton) +{ + 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, dlgparam *dp) +{ + 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; + unreachable("no radio button was checked"); +} + +void dlg_checkbox_set(union control *ctrl, dlgparam *dp, bool checked) +{ + struct winctrl *c = dlg_findbyctrl(dp, ctrl); + assert(c && c->ctrl->generic.type == CTRL_CHECKBOX); + CheckDlgButton(dp->hwnd, c->base_id, checked); +} + +bool dlg_checkbox_get(union control *ctrl, dlgparam *dp) +{ + 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, dlgparam *dp, char const *text) +{ + struct winctrl *c = dlg_findbyctrl(dp, ctrl); + assert(c && c->ctrl->generic.type == CTRL_EDITBOX); + SetDlgItemText(dp->hwnd, c->base_id+1, text); +} + +char *dlg_editbox_get(union control *ctrl, dlgparam *dp) +{ + struct winctrl *c = dlg_findbyctrl(dp, ctrl); + assert(c && c->ctrl->generic.type == CTRL_EDITBOX); + return GetDlgItemText_alloc(dp->hwnd, c->base_id+1); +} + +/* The `listbox' functions can also apply to combo boxes. */ +void dlg_listbox_clear(union control *ctrl, dlgparam *dp) +{ + 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, dlgparam *dp, int index) +{ + 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, dlgparam *dp, char const *text) +{ + 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, dlgparam *dp, + char const *text, int id) +{ + 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, dlgparam *dp, int index) +{ + 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, dlgparam *dp) +{ + 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; +} + +bool dlg_listbox_issel(union control *ctrl, dlgparam *dp, int index) +{ + 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, dlgparam *dp, int index) +{ + 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, dlgparam *dp, char const *text) +{ + 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, dlgparam *dp, char const *text) +{ + 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: + unreachable("bad control type in label_change"); + } + if (escaped) { + SetDlgItemText(dp->hwnd, id, escaped); + sfree(escaped); + } +} + +void dlg_filesel_set(union control *ctrl, dlgparam *dp, Filename *fn) +{ + struct winctrl *c = dlg_findbyctrl(dp, ctrl); + assert(c && c->ctrl->generic.type == CTRL_FILESELECT); + SetDlgItemText(dp->hwnd, c->base_id+1, fn->path); +} + +Filename *dlg_filesel_get(union control *ctrl, dlgparam *dp) +{ + struct winctrl *c = dlg_findbyctrl(dp, ctrl); + char *tmp; + Filename *ret; + assert(c && c->ctrl->generic.type == CTRL_FILESELECT); + tmp = GetDlgItemText_alloc(dp->hwnd, c->base_id+1); + ret = filename_from_str(tmp); + sfree(tmp); + return ret; +} + +void dlg_fontsel_set(union control *ctrl, dlgparam *dp, FontSpec *fs) +{ + char *buf, *boldstr; + struct winctrl *c = dlg_findbyctrl(dp, ctrl); + assert(c && c->ctrl->generic.type == CTRL_FONTSELECT); + + fontspec_free((FontSpec *)c->data); + c->data = fontspec_copy(fs); + + boldstr = (fs->isbold ? "bold, " : ""); + if (fs->height == 0) + buf = dupprintf("Font: %s, %sdefault height", fs->name, boldstr); + 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); +} + +FontSpec *dlg_fontsel_get(union control *ctrl, dlgparam *dp) +{ + struct winctrl *c = dlg_findbyctrl(dp, ctrl); + assert(c && c->ctrl->generic.type == CTRL_FONTSELECT); + return fontspec_copy((FontSpec *)c->data); +} + +/* + * Bracketing a large set of updates in these two functions will + * cause the front end (if possible) to delay updating the screen + * until it's all complete, thus avoiding flicker. + */ +void dlg_update_start(union control *ctrl, dlgparam *dp) +{ + 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, dlgparam *dp) +{ + 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, dlgparam *dp) +{ + struct winctrl *c = dlg_findbyctrl(dp, ctrl); + int id; + HWND ctl; + if (!c) + return; + switch (ctrl->generic.type) { + case CTRL_EDITBOX: id = c->base_id + 1; break; + case CTRL_RADIO: + for (id = c->base_id + ctrl->radio.nbuttons; id > 1; id--) + if (IsDlgButtonChecked(dp->hwnd, id)) + 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(dlgparam *dp) +{ + MessageBeep(0); +} + +void dlg_error_msg(dlgparam *dp, const char *msg) +{ + 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(dlgparam *dp, int value) +{ + dp->ended = true; + dp->endresult = value; +} + +void dlg_refresh(union control *ctrl, dlgparam *dp) +{ + 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, dlgparam *dp, int r, int g, int b) +{ + dp->coloursel_wanted = true; + dp->coloursel_result.r = r; + dp->coloursel_result.g = g; + dp->coloursel_result.b = b; +} + +bool dlg_coloursel_results(union control *ctrl, dlgparam *dp, + int *r, int *g, int *b) +{ + if (dp->coloursel_result.ok) { + *r = dp->coloursel_result.r; + *g = dp->coloursel_result.g; + *b = dp->coloursel_result.b; + return true; + } else + return false; +} + +void dlg_auto_set_fixed_pitch_flag(dlgparam *dp) +{ + Conf *conf = (Conf *)dp->data; + FontSpec *fs; + int quality; + HFONT hfont; + HDC hdc; + TEXTMETRIC tm; + bool 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 'Conf *' + * as dp->data. + */ + + quality = conf_get_int(conf, CONF_font_quality); + fs = conf_get_fontspec(conf, CONF_font); + + hfont = CreateFont(0, 0, 0, 0, FW_DONTCARE, false, false, false, + DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, + CLIP_DEFAULT_PRECIS, FONT_QUALITY(quality), + FIXED_PITCH | FF_DONTCARE, fs->name); + hdc = GetDC(NULL); + if (hdc && SelectObject(hdc, hfont) && GetTextMetrics(hdc, &tm)) { + /* Note that the TMPF_FIXED_PITCH bit is defined upside down :-( */ + is_var = (tm.tmPitchAndFamily & TMPF_FIXED_PITCH); + } else { + is_var = false; /* assume it's basically normal */ + } + if (hdc) + ReleaseDC(NULL, hdc); + if (hfont) + DeleteObject(hfont); + + if (is_var) + dp->fixed_pitch_fonts = false; +} + +bool dlg_get_fixed_pitch_flag(dlgparam *dp) +{ + return dp->fixed_pitch_fonts; +} + +void dlg_set_fixed_pitch_flag(dlgparam *dp, bool flag) +{ + dp->fixed_pitch_fonts = flag; +} + +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->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) +{ + sfree(dp->wintitle); + sfree(dp->errtitle); +} diff --git a/windows/cryptoapi.h b/windows/cryptoapi.h new file mode 100644 index 00000000..4ea7fe49 --- /dev/null +++ b/windows/cryptoapi.h @@ -0,0 +1,27 @@ +/* + * cryptoapi.h: Windows Crypto API functions defined in PuTTY that + * use the crypt32 library. Also centralises the machinery for + * dynamically loading that library, and our own functions using that + * in turn. + */ + +DECL_WINDOWS_FUNCTION(extern, BOOL, CryptProtectMemory, (LPVOID,DWORD,DWORD)); + +bool got_crypt(void); + +/* + * Function to obfuscate an input string into something usable as a + * pathname for a Windows named pipe. Uses CryptProtectMemory to make + * the obfuscation depend on a key Windows stores for the owning user, + * and then hashes the string as well to make it have a manageable + * length and be composed of filename-legal characters. + * + * Rationale: Windows's named pipes all live in the same namespace, so + * one user can see what pipes another user has open. This is an + * undesirable privacy leak: in particular, if we used unobfuscated + * names for the connection-sharing pipe names, it would permit one + * user to know what username@host another user is SSHing to. + * + * The returned string is dynamically allocated. + */ +char *capi_obfuscate_string(const char *realname); diff --git a/windows/dialog.c b/windows/dialog.c new file mode 100644 index 00000000..c1ac0599 --- /dev/null +++ b/windows/dialog.c @@ -0,0 +1,1156 @@ +/* + * windlg.c - dialogs for PuTTY(tel), including the configuration dialog. + */ + +#include +#include +#include +#include +#include +#include + +#include "putty.h" +#include "ssh.h" +#include "putty-rc.h" +#include "win-gui-seat.h" +#include "storage.h" +#include "dialog.h" +#include "licence.h" + +#include +#include +#include + +#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; + +#define LOGEVENT_INITIAL_MAX 128 +#define LOGEVENT_CIRCULAR_MAX 128 + +static char *events_initial[LOGEVENT_INITIAL_MAX]; +static char *events_circular[LOGEVENT_CIRCULAR_MAX]; +static int ninitial = 0, ncircular = 0, circular_first = 0; + +#define PRINTER_DISABLED_STRING "None (printing disabled)" + +void force_normal(HWND hwnd) +{ + static bool recurse = false; + + WINDOWPLACEMENT wp; + + if (recurse) + return; + recurse = true; + + wp.length = sizeof(wp); + if (GetWindowPlacement(hwnd, &wp) && wp.showCmd == SW_SHOWMAXIMIZED) { + wp.showCmd = SW_SHOWNORMAL; + SetWindowPlacement(hwnd, &wp); + } + recurse = false; +} + +static char *getevent(int i) +{ + if (i < ninitial) + return events_initial[i]; + if ((i -= ninitial) < ncircular) + return events_circular[(circular_first + i) % LOGEVENT_CIRCULAR_MAX]; + return NULL; +} + +static HWND logbox; +HWND event_log_window(void) { return logbox; } + +static INT_PTR 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 < ninitial; i++) + SendDlgItemMessage(hwnd, IDN_LIST, LB_ADDSTRING, + 0, (LPARAM) events_initial[i]); + for (i = 0; i < ncircular; i++) + SendDlgItemMessage(hwnd, IDN_LIST, LB_ADDSTRING, + 0, (LPARAM) events_circular[(circular_first + i) % LOGEVENT_CIRCULAR_MAX]); + 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(getevent(selitems[i])) + sizeof(sel_nl); + + clipdata = snewn(size, char); + if (clipdata) { + char *p = clipdata; + for (i = 0; i < count; i++) { + char *q = getevent(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(CLIP_SYSTEM, clipdata, size, true); + sfree(clipdata); + } + sfree(selitems); + + for (i = 0; i < (ninitial + ncircular); 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_PTR 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); + SetDlgItemText(hwnd, IDA_TEXT, LICENCE_TEXT("\r\n\r\n")); + 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_PTR 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); + char *buildinfo_text = buildinfo("\r\n"); + char *text = dupprintf + ("%s\r\n\r\n%s\r\n\r\n%s\r\n\r\n%s", + appname, ver, buildinfo_text, + "\251 " SHORT_COPYRIGHT_DETAILS ". All rights reserved."); + sfree(buildinfo_text); + SetDlgItemText(hwnd, IDA_TEXT, text); + MakeDlgItemBorderless(hwnd, IDA_TEXT); + sfree(text); + 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", + "https://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_PTR 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_PTR 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; + char *firstpath = 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; + firstpath = s->pathname; + } + + path = s->pathname; + } + + /* + * Put the treeview selection on to the first panel in the + * ctrlbox. + */ + TreeView_SelectItem(treeview, hfirst); + + /* + * And create the actual control set for that panel, to + * match the initial treeview selection. + */ + assert(firstpath); /* config.c must have given us _something_ */ + create_controls(hwnd, firstpath); + dlg_refresh(NULL, &dp); /* and set up control values */ + } + + /* + * 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; + } + } + } + + /* + * Now we've finished creating our initial set of controls, + * it's safe to actually show the window without risking setup + * flicker. + */ + ShowWindow(hwnd, SW_SHOWNORMAL); + + /* + * Set the flag that activates a couple of the other message + * handlers below, which were disabled until now to avoid + * spurious firing during the above setup procedure. + */ + 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) { + /* + * Selection-change events on the treeview cause us to do + * a flurry of control deletion and creation - but only + * after WM_INITDIALOG has finished. The initial + * selection-change event(s) during treeview setup are + * ignored. + */ + HTREEITEM i; + TVITEM item; + char buffer[64]; + + if (GetWindowLongPtr(hwnd, GWLP_USERDATA) != 1) + return 0; + + i = TreeView_GetSelection(((LPNMHDR) lParam)->hwndFrom); + + 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); + } +} + +bool do_config(Conf *conf) +{ + bool 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 = conf; + 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; +} + +bool do_reconfig(HWND hwnd, Conf *conf, int protcfginfo) +{ + Conf *backup_conf; + bool ret; + int protocol; + + backup_conf = conf_copy(conf); + + ctrlbox = ctrl_new_box(); + protocol = conf_get_int(conf, CONF_protocol); + setup_config_box(ctrlbox, true, protocol, protcfginfo); + win_setup_config_box(ctrlbox, &dp.hwnd, has_help(), true, protocol); + dp_init(&dp); + winctrl_init(&ctrls_base); + winctrl_init(&ctrls_panel); + dp_add_tree(&dp, &ctrls_base); + dp_add_tree(&dp, &ctrls_panel); + dp.wintitle = dupprintf("%s Reconfiguration", appname); + dp.errtitle = dupprintf("%s Error", appname); + dp.data = conf; + 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) + conf_copy_into(conf, backup_conf); + + conf_free(backup_conf); + + return ret; +} + +static void win_gui_eventlog(LogPolicy *lp, const char *string) +{ + char timebuf[40]; + char **location; + struct tm tm; + + tm=ltime(); + strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S\t", &tm); + + if (ninitial < LOGEVENT_INITIAL_MAX) + location = &events_initial[ninitial]; + else + location = &events_circular[(circular_first + ncircular) % LOGEVENT_CIRCULAR_MAX]; + + if (*location) + sfree(*location); + *location = dupcat(timebuf, string); + if (logbox) { + int count; + SendDlgItemMessage(logbox, IDN_LIST, LB_ADDSTRING, + 0, (LPARAM) *location); + count = SendDlgItemMessage(logbox, IDN_LIST, LB_GETCOUNT, 0, 0); + SendDlgItemMessage(logbox, IDN_LIST, LB_SETTOPINDEX, count - 1, 0); + } + if (ninitial < LOGEVENT_INITIAL_MAX) { + ninitial++; + } else if (ncircular < LOGEVENT_CIRCULAR_MAX) { + ncircular++; + } else if (ncircular == LOGEVENT_CIRCULAR_MAX) { + circular_first = (circular_first + 1) % LOGEVENT_CIRCULAR_MAX; + sfree(events_circular[circular_first]); + events_circular[circular_first] = dupstr(".."); + } +} + +static void win_gui_logging_error(LogPolicy *lp, const char *event) +{ + WinGuiSeat *wgs = container_of(lp, WinGuiSeat, logpolicy); + + /* Send 'can't open log file' errors to the terminal window. + * (Marked as stderr, although terminal.c won't care.) */ + seat_stderr_pl(&wgs->seat, ptrlen_from_asciz(event)); + seat_stderr_pl(&wgs->seat, PTRLEN_LITERAL("\r\n")); +} + +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); +} + +struct hostkey_dialog_ctx { + const char *const *keywords; + const char *const *values; + FingerprintType fptype_default; + char **fingerprints; + const char *keydisp; + LPCTSTR iconid; + const char *helpctx; +}; + +static INT_PTR CALLBACK HostKeyMoreInfoProc(HWND hwnd, UINT msg, + WPARAM wParam, LPARAM lParam) +{ + switch (msg) { + case WM_INITDIALOG: { + const struct hostkey_dialog_ctx *ctx = + (const struct hostkey_dialog_ctx *)lParam; + SetWindowLongPtr(hwnd, GWLP_USERDATA, (INT_PTR)ctx); + + if (ctx->fingerprints[SSH_FPTYPE_SHA256]) + SetDlgItemText(hwnd, IDC_HKI_SHA256, + ctx->fingerprints[SSH_FPTYPE_SHA256]); + if (ctx->fingerprints[SSH_FPTYPE_MD5]) + SetDlgItemText(hwnd, IDC_HKI_MD5, + ctx->fingerprints[SSH_FPTYPE_MD5]); + + SetDlgItemText(hwnd, IDA_TEXT, ctx->keydisp); + + return 1; + } + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDOK: + EndDialog(hwnd, 0); + return 0; + } + return 0; + case WM_CLOSE: + EndDialog(hwnd, 0); + return 0; + } + return 0; +} + +static INT_PTR CALLBACK HostKeyDialogProc(HWND hwnd, UINT msg, + WPARAM wParam, LPARAM lParam) +{ + switch (msg) { + case WM_INITDIALOG: { + strbuf *sb = strbuf_new(); + const struct hostkey_dialog_ctx *ctx = + (const struct hostkey_dialog_ctx *)lParam; + SetWindowLongPtr(hwnd, GWLP_USERDATA, (INT_PTR)ctx); + for (int id = 100;; id++) { + char buf[256]; + + if (!GetDlgItemText(hwnd, id, buf, (int)lenof(buf))) + break; + + strbuf_clear(sb); + for (const char *p = buf; *p ;) { + if (*p == '{') { + for (size_t i = 0; ctx->keywords[i]; i++) { + if (strstartswith(p, ctx->keywords[i])) { + p += strlen(ctx->keywords[i]); + put_datapl(sb, ptrlen_from_asciz(ctx->values[i])); + goto matched; + } + } + } else { + put_byte(sb, *p++); + } + matched:; + } + + SetDlgItemText(hwnd, id, sb->s); + } + strbuf_free(sb); + + SetDlgItemText(hwnd, IDC_HK_FINGERPRINT, + ctx->fingerprints[ctx->fptype_default]); + MakeDlgItemBorderless(hwnd, IDC_HK_FINGERPRINT); + + HANDLE icon = LoadImage( + NULL, ctx->iconid, IMAGE_ICON, + GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON), + LR_SHARED); + SendDlgItemMessage(hwnd, IDC_HK_ICON, STM_SETICON, (WPARAM)icon, 0); + + if (!has_help()) { + HWND item = GetDlgItem(hwnd, IDHELP); + if (item) + DestroyWindow(item); + } + + return 1; + } + case WM_CTLCOLORSTATIC: { + HDC hdc = (HDC)wParam; + HWND control = (HWND)lParam; + + if (GetWindowLongPtr(control, GWLP_ID) == IDC_HK_TITLE) { + SetBkMode(hdc, TRANSPARENT); + HFONT prev_font = (HFONT)SelectObject( + hdc, (HFONT)GetStockObject(SYSTEM_FONT)); + LOGFONT lf; + if (GetObject(prev_font, sizeof(lf), &lf)) { + lf.lfWeight = FW_BOLD; + lf.lfHeight = lf.lfHeight * 3 / 2; + HFONT bold_font = CreateFontIndirect(&lf); + if (bold_font) + SelectObject(hdc, bold_font); + } + return (INT_PTR)GetSysColorBrush(COLOR_BTNFACE); + } + return 0; + } + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDC_HK_ACCEPT: + case IDC_HK_ONCE: + case IDCANCEL: + EndDialog(hwnd, LOWORD(wParam)); + return 0; + case IDHELP: { + const struct hostkey_dialog_ctx *ctx = + (const struct hostkey_dialog_ctx *) + GetWindowLongPtr(hwnd, GWLP_USERDATA); + launch_help(hwnd, ctx->helpctx); + return 0; + } + case IDC_HK_MOREINFO: { + const struct hostkey_dialog_ctx *ctx = + (const struct hostkey_dialog_ctx *) + GetWindowLongPtr(hwnd, GWLP_USERDATA); + DialogBoxParam(hinst, MAKEINTRESOURCE(IDD_HK_MOREINFO), + hwnd, HostKeyMoreInfoProc, (LPARAM)ctx); + } + } + return 0; + case WM_CLOSE: + EndDialog(hwnd, IDCANCEL); + return 0; + } + return 0; +} + +int win_seat_verify_ssh_host_key( + Seat *seat, const char *host, int port, const char *keytype, + char *keystr, const char *keydisp, char **fingerprints, + void (*callback)(void *ctx, int result), void *ctx) +{ + int ret; + + WinGuiSeat *wgs = container_of(seat, WinGuiSeat, seat); + + /* + * Verify the key against the registry. + */ + ret = verify_host_key(host, port, keytype, keystr); + + if (ret == 0) /* success - key matched OK */ + return 1; + else { + static const char *const keywords[] = + { "{KEYTYPE}", "{APPNAME}", NULL }; + + const char *values[2]; + values[0] = keytype; + values[1] = appname; + + struct hostkey_dialog_ctx ctx[1]; + ctx->keywords = keywords; + ctx->values = values; + ctx->fingerprints = fingerprints; + ctx->fptype_default = ssh2_pick_default_fingerprint(fingerprints); + ctx->keydisp = keydisp; + ctx->iconid = (ret == 2 ? IDI_WARNING : IDI_QUESTION); + ctx->helpctx = (ret == 2 ? WINHELP_CTX_errors_hostkey_changed : + WINHELP_CTX_errors_hostkey_absent); + int dlgid = (ret == 2 ? IDD_HK_WRONG : IDD_HK_ABSENT); + int mbret = DialogBoxParam( + hinst, MAKEINTRESOURCE(dlgid), wgs->term_hwnd, + HostKeyDialogProc, (LPARAM)ctx); + assert(mbret==IDC_HK_ACCEPT || mbret==IDC_HK_ONCE || mbret==IDCANCEL); + if (mbret == IDC_HK_ACCEPT) { + store_host_key(host, port, keytype, keystr); + return 1; + } else if (mbret == IDC_HK_ONCE) + return 1; + } + return 0; /* abandon the connection */ +} + +/* + * Ask whether the selected algorithm is acceptable (since it was + * below the configured 'warn' threshold). + */ +int win_seat_confirm_weak_crypto_primitive( + Seat *seat, 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 %s, 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; +} + +int win_seat_confirm_weak_cached_hostkey( + Seat *seat, const char *algname, const char *betteralgs, + void (*callback)(void *ctx, int result), void *ctx) +{ + static const char mbtitle[] = "%s Security Alert"; + static const char msg[] = + "The first host key type we have stored for this server\n" + "is %s, which is below the configured warning threshold.\n" + "The server also provides the following types of host key\n" + "above the threshold, which we do not have stored:\n" + "%s\n" + "Do you want to continue with this connection?\n"; + char *message, *title; + int mbret; + + message = dupprintf(msg, algname, betteralgs); + 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). + */ +static int win_gui_askappend(LogPolicy *lp, 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; +} + +const LogPolicyVtable win_gui_logpolicy_vt = { + .eventlog = win_gui_eventlog, + .askappend = win_gui_askappend, + .logging_error = win_gui_logging_error, + .verbose = null_lp_verbose_yes, +}; + +/* + * 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); +} diff --git a/windows/gss.c b/windows/gss.c new file mode 100644 index 00000000..0b47d9a7 --- /dev/null +++ b/windows/gss.c @@ -0,0 +1,660 @@ +#ifndef NO_GSSAPI + +#include +#include "putty.h" + +#define SECURITY_WIN32 +#include + +#include "ssh/pgssapi.h" +#include "ssh/gss.h" +#include "ssh/gssc.h" + +#include "misc.h" + +#define UNIX_EPOCH 11644473600ULL /* Seconds from Windows epoch */ +#define CNS_PERSEC 10000000ULL /* # 100ns per second */ + +/* + * Note, as a special case, 0 relative to the Windows epoch (unspecified) maps + * to 0 relative to the POSIX epoch (unspecified)! + */ +#define TIME_WIN_TO_POSIX(ft, t) do { \ + ULARGE_INTEGER uli; \ + uli.LowPart = (ft).dwLowDateTime; \ + uli.HighPart = (ft).dwHighDateTime; \ + if (uli.QuadPart != 0) \ + uli.QuadPart = uli.QuadPart / CNS_PERSEC - UNIX_EPOCH; \ + (t) = (time_t) uli.QuadPart; \ +} while(0) + +/* Windows code to set up the GSSAPI library list. */ + +#ifdef _WIN64 +#define MIT_KERB_SUFFIX "64" +#else +#define MIT_KERB_SUFFIX "32" +#endif + +const int ngsslibs = 3; +const char *const gsslibnames[3] = { + "MIT Kerberos GSSAPI"MIT_KERB_SUFFIX".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, PVOID, + 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)); +DECL_WINDOWS_FUNCTION(static, SECURITY_STATUS, + VerifySignature, + (PCtxtHandle, PSecBufferDesc, ULONG, PULONG)); +DECL_WINDOWS_FUNCTION(static, DLL_DIRECTORY_COOKIE, + AddDllDirectory, + (PCWSTR)); + +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(Conf *conf) +{ + HMODULE module; + HKEY regkey; + struct ssh_gss_liblist *list = snew(struct ssh_gss_liblist); + char *path; + static HMODULE kernel32_module; + if (!kernel32_module) { + kernel32_module = load_system32_dll("kernel32.dll"); + } +#if !HAVE_ADDDLLDIRECTORY + /* Omit the type-check because older MSVCs don't have this function */ + GET_WINDOWS_FUNCTION_NO_TYPECHECK(kernel32_module, AddDllDirectory); +#else + GET_WINDOWS_FUNCTION(kernel32_module, AddDllDirectory); +#endif + + list->libraries = snewn(3, struct ssh_gss_library); + list->nlibraries = 0; + + /* MIT Kerberos GSSAPI implementation */ + 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, (LPBYTE)buffer, &size); + if (ret == ERROR_SUCCESS && type == REG_SZ) { + strcat (buffer, "\\bin"); + if(p_AddDllDirectory) { + /* Add MIT Kerberos' path to the DLL search path, + * it loads its own DLLs further down the road */ + wchar_t *dllPath = + dup_mb_to_wc(DEFAULT_CODEPAGE, 0, buffer); + p_AddDllDirectory(dllPath); + sfree(dllPath); + } + strcat (buffer, "\\gssapi"MIT_KERB_SUFFIX".dll"); + module = LoadLibraryEx (buffer, NULL, + LOAD_LIBRARY_SEARCH_SYSTEM32 | + LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | + LOAD_LIBRARY_SEARCH_USER_DIRS); + } + sfree(buffer); + } + RegCloseKey(regkey); + } + if (module) { + struct ssh_gss_library *lib = + &list->libraries[list->nlibraries++]; + + lib->id = 0; + lib->gsslogmsg = "Using GSSAPI from GSSAPI"MIT_KERB_SUFFIX".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(verify_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); + BIND_GSS_FN(acquire_cred); + BIND_GSS_FN(inquire_cred_by_mech); + +#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); + GET_WINDOWS_FUNCTION(module, VerifySignature); + + ssh_sspi_bind_fns(lib); + } + + /* + * Custom GSSAPI DLL. + */ + module = NULL; + path = conf_get_filename(conf, CONF_ssh_gss_custom)->path; + if (*path) { + if(p_AddDllDirectory) { + /* Add the custom directory as well in case it chainloads + * some other DLLs (e.g a non-installed MIT Kerberos + * instance) */ + int pathlen = strlen(path); + + while (pathlen > 0 && path[pathlen-1] != ':' && + path[pathlen-1] != '\\') + pathlen--; + + if (pathlen > 0 && path[pathlen-1] != '\\') + pathlen--; + + if (pathlen > 0) { + char *dirpath = dupprintf("%.*s", pathlen, path); + wchar_t *dllPath = dup_mb_to_wc(DEFAULT_CODEPAGE, 0, dirpath); + p_AddDllDirectory(dllPath); + sfree(dllPath); + sfree(dirpath); + } + } + + module = LoadLibraryEx(path, NULL, + LOAD_LIBRARY_SEARCH_SYSTEM32 | + LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | + LOAD_LIBRARY_SEARCH_USER_DIRS); + } + if (module) { + struct ssh_gss_library *lib = + &list->libraries[list->nlibraries++]; + + lib->id = 2; + lib->gsslogmsg = dupprintf("Using GSSAPI from user-specified" + " library '%s'", 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(verify_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); + BIND_GSS_FN(acquire_cred); + BIND_GSS_FN(inquire_cred_by_mech); + +#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); + + *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, + time_t *expiry) +{ + 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, + NULL); + + if (winctx->maj_stat != SEC_E_OK) { + p_FreeCredentialsHandle(&winctx->cred_handle); + sfree(winctx); + return SSH_GSS_FAILURE; + } + + /* Windows does not return a valid expiration from AcquireCredentials */ + if (expiry) + *expiry = GSS_NO_EXPIRATION; + + *ctx = (Ssh_gss_ctx) winctx; + return SSH_GSS_OK; +} + +static void localexp_to_exp_lifetime(TimeStamp *localexp, + time_t *expiry, unsigned long *lifetime) +{ + FILETIME nowUTC; + FILETIME expUTC; + time_t now; + time_t exp; + time_t delta; + + if (!lifetime && !expiry) + return; + + GetSystemTimeAsFileTime(&nowUTC); + TIME_WIN_TO_POSIX(nowUTC, now); + + if (lifetime) + *lifetime = 0; + if (expiry) + *expiry = GSS_NO_EXPIRATION; + + /* + * Type oddity: localexp is a pointer to 'TimeStamp', whereas + * LocalFileTimeToFileTime expects a pointer to FILETIME. However, + * despite having different formal type names from the compiler's + * point of view, these two structures are specified to be + * isomorphic in the MS documentation, so it's legitimate to copy + * between them: + * + * https://msdn.microsoft.com/en-us/library/windows/desktop/aa380511(v=vs.85).aspx + */ + { + FILETIME localexp_ft; + enum { vorpal_sword = 1 / (sizeof(*localexp) == sizeof(localexp_ft)) }; + memcpy(&localexp_ft, localexp, sizeof(localexp_ft)); + if (!LocalFileTimeToFileTime(&localexp_ft, &expUTC)) + return; + } + + TIME_WIN_TO_POSIX(expUTC, exp); + delta = exp - now; + if (exp == 0 || delta <= 0) + return; + + if (expiry) + *expiry = exp; + if (lifetime) { + if (delta <= ULONG_MAX) + *lifetime = (unsigned long)delta; + else + *lifetime = ULONG_MAX; + } +} + +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, + time_t *expiry, + unsigned long *lifetime) +{ + 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; + TimeStamp localexp; + + /* 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, + &localexp); + + localexp_to_exp_lifetime(&localexp, expiry, lifetime); + + /* 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; + const 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_verify_mic(struct ssh_gss_library *lib, + Ssh_gss_ctx ctx, + Ssh_gss_buf *buf, + Ssh_gss_buf *mic) +{ + winSsh_gss_ctx *winctx= (winSsh_gss_ctx *) ctx; + SecBufferDesc InputBufferDescriptor; + SecBuffer InputSecurityToken[2]; + ULONG qop; + + if (winctx == NULL) return SSH_GSS_FAILURE; + + winctx->maj_stat = 0; + + 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 = mic->length; + InputSecurityToken[1].pvBuffer = mic->value; + + winctx->maj_stat = p_VerifySignature(&winctx->context, + &InputBufferDescriptor, + 0, &qop); + 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->verify_mic = ssh_sspi_verify_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 diff --git a/windows/handle-io.c b/windows/handle-io.c new file mode 100644 index 00000000..085f30a0 --- /dev/null +++ b/windows/handle-io.c @@ -0,0 +1,727 @@ +/* + * 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 + +#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 */ + bool moribund; /* are we going to kill this soon? */ + bool done; /* request subthread to terminate */ + bool defunct; /* has the subthread already gone? */ + bool busy; /* operation currently in progress? */ + void *privdata; /* for client to remember who they are */ +}; + +typedef enum { HT_INPUT, HT_OUTPUT, HT_FOREIGN } HandleType; + +/* ---------------------------------------------------------------------- + * 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 */ + bool moribund; /* are we going to kill this soon? */ + bool done; /* request subthread to terminate */ + bool defunct; /* has the subthread already gone? */ + bool 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; + bool readret, finished; + int 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; + + /* + * If we just set ctx->len to 0, that means the read operation + * has returned end-of-file. Telling that to the main thread + * will cause it to set its 'defunct' flag and dispose of the + * handle structure at the next opportunity, in which case we + * mustn't touch ctx at all after the SetEvent. (Hence we do + * even _this_ check before the SetEvent.) + */ + finished = (ctx->len == 0); + + SetEvent(ctx->ev_to_main); + + if (finished) + break; + + WaitForSingleObject(ctx->ev_from_main, INFINITE); + if (ctx->done) { + /* + * The main thread has asked us to shut down. Send back an + * event indicating that we've done so. Hereafter we must + * not touch ctx at all, because the main thread might + * have freed it. + */ + SetEvent(ctx->ev_to_main); + break; + } + } + + if (povl) + CloseHandle(oev); + + return 0; +} + +/* + * This is called after a successful 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 */ + bool moribund; /* are we going to kill this soon? */ + bool done; /* request subthread to terminate */ + bool defunct; /* has the subthread already gone? */ + bool 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. + */ + const 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 */ + enum { EOF_NO, EOF_PENDING, EOF_SENT } outgoingeof; + + /* + * 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; + bool 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) { + /* + * The main thread has asked us to shut down. Send back an + * event indicating that we've done so. Hereafter we must + * not touch ctx at all, because the main thread might + * have freed it. + */ + 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) { + /* + * The write operation has suffered an error. Telling that + * to the main thread will cause it to set its 'defunct' + * flag and dispose of the handle structure at the next + * opportunity, so we must not touch ctx at all after + * this. + */ + break; + } + } + + if (povl) + CloseHandle(oev); + + return 0; +} + +static void handle_try_output(struct handle_output *ctx) +{ + if (!ctx->busy && bufchain_size(&ctx->queued_data)) { + ptrlen data = bufchain_prefix(&ctx->queued_data); + ctx->buffer = data.ptr; + ctx->len = min(data.len, ~(DWORD)0); + SetEvent(ctx->ev_from_main); + ctx->busy = true; + } else if (!ctx->busy && bufchain_size(&ctx->queued_data) == 0 && + ctx->outgoingeof == EOF_PENDING) { + CloseHandle(ctx->h); + ctx->h = INVALID_HANDLE_VALUE; + ctx->outgoingeof = EOF_SENT; + } +} + +/* ---------------------------------------------------------------------- + * 'Foreign events'. These are handle structures which just contain a + * single event object passed to us by another module such as + * winnps.c, so that they can make use of our handle_get_events / + * handle_got_event mechanism for communicating with application main + * loops. + */ +struct handle_foreign { + /* + * Copy of the handle_generic structure. + */ + HANDLE h; /* the handle itself */ + HANDLE ev_to_main; /* event used to signal main thread */ + HANDLE ev_from_main; /* event used to signal back to us */ + bool moribund; /* are we going to kill this soon? */ + bool done; /* request subthread to terminate */ + bool defunct; /* has the subthread already gone? */ + bool busy; /* operation currently in progress? */ + void *privdata; /* for client to remember who they are */ + + /* + * Our own data, just consisting of knowledge of who to call back. + */ + void (*callback)(void *); + void *ctx; +}; + +/* ---------------------------------------------------------------------- + * Unified code handling both input and output threads. + */ + +struct handle { + HandleType type; + union { + struct handle_generic g; + struct handle_input i; + struct handle_output o; + struct handle_foreign f; + } u; +}; + +static tree234 *handles_by_evtomain; + +static int handle_cmp_evtomain(void *av, void *bv) +{ + struct handle *a = (struct handle *)av; + struct handle *b = (struct handle *)bv; + + if ((uintptr_t)a->u.g.ev_to_main < (uintptr_t)b->u.g.ev_to_main) + return -1; + else if ((uintptr_t)a->u.g.ev_to_main > (uintptr_t)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 ((uintptr_t)*a < (uintptr_t)b->u.g.ev_to_main) + return -1; + else if ((uintptr_t)*a > (uintptr_t)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->type = HT_INPUT; + h->u.i.h = handle; + h->u.i.ev_to_main = CreateEvent(NULL, false, false, NULL); + h->u.i.ev_from_main = CreateEvent(NULL, false, false, NULL); + h->u.i.gotdata = gotdata; + h->u.i.defunct = false; + 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->type = HT_OUTPUT; + h->u.o.h = handle; + h->u.o.ev_to_main = CreateEvent(NULL, false, false, NULL); + h->u.o.ev_from_main = CreateEvent(NULL, false, false, NULL); + h->u.o.busy = false; + h->u.o.defunct = false; + h->u.o.moribund = false; + h->u.o.done = false; + h->u.o.privdata = privdata; + bufchain_init(&h->u.o.queued_data); + h->u.o.outgoingeof = EOF_NO; + h->u.o.sentdata = sentdata; + h->u.o.flags = flags; + + if (!handles_by_evtomain) + handles_by_evtomain = newtree234(handle_cmp_evtomain); + add234(handles_by_evtomain, h); + + CreateThread(NULL, 0, handle_output_threadfunc, + &h->u.o, 0, &out_threadid); + + return h; +} + +struct handle *handle_add_foreign_event(HANDLE event, + void (*callback)(void *), void *ctx) +{ + struct handle *h = snew(struct handle); + + h->type = HT_FOREIGN; + h->u.f.h = INVALID_HANDLE_VALUE; + h->u.f.ev_to_main = event; + h->u.f.ev_from_main = INVALID_HANDLE_VALUE; + h->u.f.defunct = true; /* we have no thread in the first place */ + h->u.f.moribund = false; + h->u.f.done = false; + h->u.f.privdata = NULL; + h->u.f.callback = callback; + h->u.f.ctx = ctx; + h->u.f.busy = true; + + if (!handles_by_evtomain) + handles_by_evtomain = newtree234(handle_cmp_evtomain); + add234(handles_by_evtomain, h); + + return h; +} + +size_t handle_write(struct handle *h, const void *data, size_t len) +{ + assert(h->type == HT_OUTPUT); + assert(h->u.o.outgoingeof == EOF_NO); + bufchain_add(&h->u.o.queued_data, data, len); + handle_try_output(&h->u.o); + return bufchain_size(&h->u.o.queued_data); +} + +void handle_write_eof(struct handle *h) +{ + /* + * This function is called when we want to proactively send an + * end-of-file notification on the handle. We can only do this by + * actually closing the handle - so never call this on a + * bidirectional handle if we're still interested in its incoming + * direction! + */ + assert(h->type == HT_OUTPUT); + if (h->u.o.outgoingeof == EOF_NO) { + h->u.o.outgoingeof = EOF_PENDING; + handle_try_output(&h->u.o); + } +} + +HANDLE *handle_get_events(int *nevents) +{ + HANDLE *ret; + struct handle *h; + int i; + size_t 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) { + sgrowarray(ret, size, n); + ret[n++] = h->u.g.ev_to_main; + } + } + } + + *nevents = n; + return ret; +} + +static void handle_destroy(struct handle *h) +{ + if (h->type == HT_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) +{ + assert(h && !h->u.g.moribund); + if (h->u.g.busy && h->type != HT_FOREIGN) { + /* + * If the handle is currently busy, we cannot immediately free + * it, because its subthread is in the middle of something. + * (Exception: foreign handles don't have a subthread.) + * + * 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. So + * we 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 one which we have either already + * signalled to die, or are waiting until its current I/O op + * completes to do so. Either way, it's treated as already + * dead from the external user's point of view, so we ignore + * the actual I/O result. We just signal the thread to die if + * we haven't yet done so, 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; + } + + switch (h->type) { + int backlog; + + case HT_INPUT: + 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.defunct = true; + h->u.i.gotdata(h, NULL, 0, h->u.i.readerr); + } else { + backlog = h->u.i.gotdata(h, h->u.i.buffer, h->u.i.len, 0); + handle_throttle(&h->u.i, backlog); + } + break; + + case HT_OUTPUT: + h->u.o.busy = false; + + /* + * A signal on an output handle means we have completed a + * write. Call the callback to indicate that the output + * 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.defunct = true; + h->u.o.sentdata(h, 0, h->u.o.writeerr); + } else { + bufchain_consume(&h->u.o.queued_data, h->u.o.lenwritten); + noise_ultralight(NOISE_SOURCE_IOLEN, h->u.o.lenwritten); + h->u.o.sentdata(h, bufchain_size(&h->u.o.queued_data), 0); + handle_try_output(&h->u.o); + } + break; + + case HT_FOREIGN: + /* Just call the callback. */ + h->u.f.callback(h->u.f.ctx); + break; + } +} + +void handle_unthrottle(struct handle *h, size_t backlog) +{ + assert(h->type == HT_INPUT); + handle_throttle(&h->u.i, backlog); +} + +size_t handle_backlog(struct handle *h) +{ + assert(h->type == HT_OUTPUT); + return bufchain_size(&h->u.o.queued_data); +} + +void *handle_get_privdata(struct handle *h) +{ + return h->u.g.privdata; +} + +static void handle_sink_write(BinarySink *bs, const void *data, size_t len) +{ + handle_sink *sink = BinarySink_DOWNCAST(bs, handle_sink); + handle_write(sink->h, data, len); +} + +void handle_sink_init(handle_sink *sink, struct handle *h) +{ + sink->h = h; + BinarySink_INIT(sink, handle_sink_write); +} diff --git a/windows/handle-socket.c b/windows/handle-socket.c new file mode 100644 index 00000000..93bb500a --- /dev/null +++ b/windows/handle-socket.c @@ -0,0 +1,343 @@ +/* + * General mechanism for wrapping up reading/writing of Windows + * HANDLEs into a PuTTY Socket abstraction. + */ + +#include +#include +#include + +#include "tree234.h" +#include "putty.h" +#include "network.h" + +typedef struct HandleSocket { + HANDLE send_H, recv_H, stderr_H; + struct handle *send_h, *recv_h, *stderr_h; + + /* + * Freezing one of these sockets is a slightly fiddly business, + * because the reads from the handle are happening in a separate + * thread as blocking system calls and so once one is in progress + * it can't sensibly be interrupted. Hence, after the user tries + * to freeze one of these sockets, it's unavoidable that we may + * receive one more load of data before we manage to get + * winhandl.c to stop reading. + */ + enum { + UNFROZEN, /* reading as normal */ + FREEZING, /* have been set to frozen but winhandl is still reading */ + FROZEN, /* really frozen - winhandl has been throttled */ + THAWING /* we're gradually releasing our remaining data */ + } frozen; + /* We buffer data here if we receive it from winhandl while frozen. */ + bufchain inputdata; + + /* Handle logging proxy error messages from stderr_H, if we have one. */ + ProxyStderrBuf psb; + + bool defer_close, deferred_close; /* in case of re-entrance */ + + char *error; + + Plug *plug; + + Socket sock; +} HandleSocket; + +static size_t handle_gotdata( + struct handle *h, const void *data, size_t len, int err) +{ + HandleSocket *hs = (HandleSocket *)handle_get_privdata(h); + + if (err) { + plug_closing(hs->plug, "Read error from handle", 0, 0); + return 0; + } else if (len == 0) { + plug_closing(hs->plug, NULL, 0, 0); + return 0; + } else { + assert(hs->frozen != FROZEN && hs->frozen != THAWING); + if (hs->frozen == FREEZING) { + /* + * If we've received data while this socket is supposed to + * be frozen (because the read winhandl.c started before + * sk_set_frozen was called has now returned) then buffer + * the data for when we unfreeze. + */ + bufchain_add(&hs->inputdata, data, len); + hs->frozen = FROZEN; + + /* + * And return a very large backlog, to prevent further + * data arriving from winhandl until we unfreeze. + */ + return INT_MAX; + } else { + plug_receive(hs->plug, 0, data, len); + return 0; + } + } +} + +static size_t handle_stderr( + struct handle *h, const void *data, size_t len, int err) +{ + HandleSocket *hs = (HandleSocket *)handle_get_privdata(h); + + if (!err && len > 0) + log_proxy_stderr(hs->plug, &hs->psb, data, len); + + return 0; +} + +static void handle_sentdata(struct handle *h, size_t new_backlog, int err) +{ + HandleSocket *hs = (HandleSocket *)handle_get_privdata(h); + + if (err) { + plug_closing(hs->plug, win_strerror(err), err, 0); + return; + } + + plug_sent(hs->plug, new_backlog); +} + +static Plug *sk_handle_plug(Socket *s, Plug *p) +{ + HandleSocket *hs = container_of(s, HandleSocket, sock); + Plug *ret = hs->plug; + if (p) + hs->plug = p; + return ret; +} + +static void sk_handle_close(Socket *s) +{ + HandleSocket *hs = container_of(s, HandleSocket, sock); + + if (hs->defer_close) { + hs->deferred_close = true; + return; + } + + handle_free(hs->send_h); + handle_free(hs->recv_h); + CloseHandle(hs->send_H); + if (hs->recv_H != hs->send_H) + CloseHandle(hs->recv_H); + bufchain_clear(&hs->inputdata); + + delete_callbacks_for_context(hs); + + sfree(hs); +} + +static size_t sk_handle_write(Socket *s, const void *data, size_t len) +{ + HandleSocket *hs = container_of(s, HandleSocket, sock); + + return handle_write(hs->send_h, data, len); +} + +static size_t sk_handle_write_oob(Socket *s, const void *data, size_t len) +{ + /* + * oob data is treated as inband; nasty, but nothing really + * better we can do + */ + return sk_handle_write(s, data, len); +} + +static void sk_handle_write_eof(Socket *s) +{ + HandleSocket *hs = container_of(s, HandleSocket, sock); + + handle_write_eof(hs->send_h); +} + +static void handle_socket_unfreeze(void *hsv) +{ + HandleSocket *hs = (HandleSocket *)hsv; + + /* + * If we've been put into a state other than THAWING since the + * last callback, then we're done. + */ + if (hs->frozen != THAWING) + return; + + /* + * Get some of the data we've buffered. + */ + ptrlen data = bufchain_prefix(&hs->inputdata); + assert(data.len > 0); + + /* + * Hand it off to the plug. Be careful of re-entrance - that might + * have the effect of trying to close this socket. + */ + hs->defer_close = true; + plug_receive(hs->plug, 0, data.ptr, data.len); + bufchain_consume(&hs->inputdata, data.len); + hs->defer_close = false; + if (hs->deferred_close) { + sk_handle_close(&hs->sock); + return; + } + + if (bufchain_size(&hs->inputdata) > 0) { + /* + * If there's still data in our buffer, stay in THAWING state, + * and reschedule ourself. + */ + queue_toplevel_callback(handle_socket_unfreeze, hs); + } else { + /* + * Otherwise, we've successfully thawed! + */ + hs->frozen = UNFROZEN; + handle_unthrottle(hs->recv_h, 0); + } +} + +static void sk_handle_set_frozen(Socket *s, bool is_frozen) +{ + HandleSocket *hs = container_of(s, HandleSocket, sock); + + if (is_frozen) { + switch (hs->frozen) { + case FREEZING: + case FROZEN: + return; /* nothing to do */ + + case THAWING: + /* + * We were in the middle of emptying our bufchain, and got + * frozen again. In that case, winhandl.c is already + * throttled, so just return to FROZEN state. The toplevel + * callback will notice and disable itself. + */ + hs->frozen = FROZEN; + break; + + case UNFROZEN: + /* + * The normal case. Go to FREEZING, and expect one more + * load of data from winhandl if we're unlucky. + */ + hs->frozen = FREEZING; + break; + } + } else { + switch (hs->frozen) { + case UNFROZEN: + case THAWING: + return; /* nothing to do */ + + case FREEZING: + /* + * If winhandl didn't send us any data throughout the time + * we were frozen, then we'll still be in this state and + * can just unfreeze in the trivial way. + */ + assert(bufchain_size(&hs->inputdata) == 0); + hs->frozen = UNFROZEN; + break; + + case FROZEN: + /* + * If we have buffered data, go to THAWING and start + * releasing it in top-level callbacks. + */ + hs->frozen = THAWING; + queue_toplevel_callback(handle_socket_unfreeze, hs); + } + } +} + +static const char *sk_handle_socket_error(Socket *s) +{ + HandleSocket *hs = container_of(s, HandleSocket, sock); + return hs->error; +} + +static SocketPeerInfo *sk_handle_peer_info(Socket *s) +{ + HandleSocket *hs = container_of(s, HandleSocket, sock); + ULONG pid; + static HMODULE kernel32_module; + DECL_WINDOWS_FUNCTION(static, BOOL, GetNamedPipeClientProcessId, + (HANDLE, PULONG)); + + if (!kernel32_module) { + kernel32_module = load_system32_dll("kernel32.dll"); +#if !HAVE_GETNAMEDPIPECLIENTPROCESSID + /* For older Visual Studio, and MinGW too (at least as of + * Ubuntu 16.04), this function isn't available in the header + * files to type-check. Ditto the toolchain I use for + * Coveritying the Windows code. */ + GET_WINDOWS_FUNCTION_NO_TYPECHECK( + kernel32_module, GetNamedPipeClientProcessId); +#else + GET_WINDOWS_FUNCTION( + kernel32_module, GetNamedPipeClientProcessId); +#endif + } + + /* + * Of course, not all handles managed by this module will be + * server ends of named pipes, but if they are, then it's useful + * to log what we can find out about the client end. + */ + if (p_GetNamedPipeClientProcessId && + p_GetNamedPipeClientProcessId(hs->send_H, &pid)) { + SocketPeerInfo *pi = snew(SocketPeerInfo); + pi->addressfamily = ADDRTYPE_LOCAL; + pi->addr_text = NULL; + pi->port = -1; + pi->log_text = dupprintf("process id %lu", (unsigned long)pid); + return pi; + } + + return NULL; +} + +static const SocketVtable HandleSocket_sockvt = { + .plug = sk_handle_plug, + .close = sk_handle_close, + .write = sk_handle_write, + .write_oob = sk_handle_write_oob, + .write_eof = sk_handle_write_eof, + .set_frozen = sk_handle_set_frozen, + .socket_error = sk_handle_socket_error, + .peer_info = sk_handle_peer_info, +}; + +Socket *make_handle_socket(HANDLE send_H, HANDLE recv_H, HANDLE stderr_H, + Plug *plug, bool overlapped) +{ + HandleSocket *hs; + int flags = (overlapped ? HANDLE_FLAG_OVERLAPPED : 0); + + hs = snew(HandleSocket); + hs->sock.vt = &HandleSocket_sockvt; + hs->plug = plug; + hs->error = NULL; + hs->frozen = UNFROZEN; + bufchain_init(&hs->inputdata); + psb_init(&hs->psb); + + hs->recv_H = recv_H; + hs->recv_h = handle_input_new(hs->recv_H, handle_gotdata, hs, flags); + hs->send_H = send_H; + hs->send_h = handle_output_new(hs->send_H, handle_sentdata, hs, flags); + hs->stderr_H = stderr_H; + if (hs->stderr_H) + hs->stderr_h = handle_input_new(hs->stderr_H, handle_stderr, + hs, flags); + + hs->defer_close = hs->deferred_close = false; + + return &hs->sock; +} diff --git a/windows/help.c b/windows/help.c new file mode 100644 index 00000000..daea282a --- /dev/null +++ b/windows/help.c @@ -0,0 +1,250 @@ +/* + * winhelp.c: centralised functions to launch Windows HTML Help files. + */ + +#include +#include +#include +#include + +#include "putty.h" +#include "putty-rc.h" + +#ifdef NO_HTMLHELP + +/* If htmlhelp.h is not available, we can't do any of this at all */ +bool has_help(void) { return false; } +void init_help(void) { } +void shutdown_help(void) { } +void launch_help(HWND hwnd, const char *topic) { } +void quit_help(HWND hwnd) { } + +#else + +#include + +static char *chm_path = NULL; +static bool chm_created_by_us = false; + +static bool requested_help; +DECL_WINDOWS_FUNCTION(static, HWND, HtmlHelpA, (HWND, LPCSTR, UINT, DWORD_PTR)); + +static HRSRC chm_hrsrc; +static DWORD chm_resource_size = 0; +static const void *chm_resource = NULL; + +int has_embedded_chm(void) +{ + static bool checked = false; + if (!checked) { + checked = true; + + chm_hrsrc = FindResource( + NULL, MAKEINTRESOURCE(ID_CUSTOM_CHMFILE), + MAKEINTRESOURCE(TYPE_CUSTOM_CHMFILE)); + } + return chm_hrsrc != NULL ? 1 : 0; +} + +static bool find_chm_resource(void) +{ + static bool checked = false; + if (checked) /* we've been here already */ + goto out; + checked = true; + + /* + * Look for a CHM file embedded in this executable as a custom + * resource. + */ + if (!has_embedded_chm()) /* set up chm_hrsrc and check if it's NULL */ + goto out; + + chm_resource_size = SizeofResource(NULL, chm_hrsrc); + if (chm_resource_size == 0) + goto out; + + HGLOBAL chm_hglobal = LoadResource(NULL, chm_hrsrc); + if (chm_hglobal == NULL) + goto out; + + chm_resource = (const uint8_t *)LockResource(chm_hglobal); + + out: + return chm_resource != NULL; +} + +static bool load_chm_resource(void) +{ + bool toret = false; + char *filename = NULL; + HANDLE filehandle = INVALID_HANDLE_VALUE; + bool created = false; + + static bool tried_to_load = false; + if (tried_to_load) + goto out; + tried_to_load = true; + + /* + * We've found it! Now write it out into a separate file, so that + * htmlhelp.exe can handle it. + */ + + /* GetTempPath is documented as returning a size of up to + * MAX_PATH+1 which does not count the NUL */ + char tempdir[MAX_PATH + 2]; + if (GetTempPath(sizeof(tempdir), tempdir) == 0) + goto out; + + unsigned long pid = GetCurrentProcessId(); + + for (uint64_t counter = 0;; counter++) { + filename = dupprintf( + "%s\\putty_%lu_%"PRIu64".chm", tempdir, pid, counter); + filehandle = CreateFile( + filename, GENERIC_WRITE, FILE_SHARE_READ, + NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL); + + if (filehandle != INVALID_HANDLE_VALUE) + break; /* success! */ + + if (GetLastError() != ERROR_FILE_EXISTS) + goto out; /* failed for some other reason! */ + + sfree(filename); + filename = NULL; + } + created = true; + + const uint8_t *p = (const uint8_t *)chm_resource; + for (DWORD pos = 0; pos < chm_resource_size; pos++) { + DWORD to_write = chm_resource_size - pos; + DWORD written = 0; + + if (!WriteFile(filehandle, p + pos, to_write, &written, NULL)) + goto out; + pos += written; + } + + chm_path = filename; + filename = NULL; + chm_created_by_us = true; + toret = true; + + out: + if (created && !toret) + DeleteFile(filename); + sfree(filename); + if (filehandle != INVALID_HANDLE_VALUE) + CloseHandle(filehandle); + return toret; +} + +static bool find_chm_from_installation(void) +{ + static const char *const reg_paths[] = { + "Software\\SimonTatham\\PuTTY64\\CHMPath", + "Software\\SimonTatham\\PuTTY\\CHMPath", + }; + + for (size_t i = 0; i < lenof(reg_paths); i++) { + char *filename = registry_get_string( + HKEY_LOCAL_MACHINE, reg_paths[i], NULL); + + if (filename) { + chm_path = filename; + chm_created_by_us = false; + return true; + } + } + + return false; +} + +void init_help(void) +{ + /* Just in case of multiple calls */ + static bool already_called = false; + if (already_called) + return; + already_called = true; + + /* + * Don't even try looking for the CHM file if we can't even find + * the HtmlHelp() API function. + */ + HINSTANCE dllHH = load_system32_dll("hhctrl.ocx"); + GET_WINDOWS_FUNCTION(dllHH, HtmlHelpA); + if (!p_HtmlHelpA) { + FreeLibrary(dllHH); + return; + } + + /* + * If there's a CHM file embedded in this executable, we should + * use that as the first choice. + */ + if (find_chm_resource()) + return; + + /* + * Otherwise, try looking for the CHM in the location that the + * installer marked in the registry. + */ + if (find_chm_from_installation()) + return; +} + +void shutdown_help(void) +{ + if (chm_path && chm_created_by_us) { + p_HtmlHelpA(NULL, NULL, HH_CLOSE_ALL, 0); + DeleteFile(chm_path); + } + sfree(chm_path); + chm_path = NULL; + chm_created_by_us = false; +} + +bool has_help(void) +{ + return chm_path != NULL || chm_resource != NULL; +} + +void launch_help(HWND hwnd, const char *topic) +{ + if (!chm_path && chm_resource) { + /* + * If we've been called without already having a file name for + * the CHM file, that might be because we've located it in our + * resource section but not written it to a temp file yet. Do + * so now, on first use. + */ + load_chm_resource(); + } + + /* If we _still_ don't have a CHM pathname, we just can't display help. */ + if (!chm_path) + return; + + if (topic) { + char *fname = dupprintf( + "%s::/%s.html>main", chm_path, topic); + p_HtmlHelpA(hwnd, fname, HH_DISPLAY_TOPIC, 0); + sfree(fname); + } else { + p_HtmlHelpA(hwnd, chm_path, HH_DISPLAY_TOPIC, 0); + } + requested_help = true; +} + +void quit_help(HWND hwnd) +{ + if (requested_help) + p_HtmlHelpA(NULL, NULL, HH_CLOSE_ALL, 0); + if (chm_path && chm_created_by_us) + DeleteFile(chm_path); +} + +#endif /* NO_HTMLHELP */ diff --git a/windows/help.h b/windows/help.h new file mode 100644 index 00000000..5b11af3c --- /dev/null +++ b/windows/help.h @@ -0,0 +1,207 @@ +/* + * help.h - define Windows Help context names. + * Each definition is simply a string which matches up with the + * section names in the Halibut 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 "config-hostname" +#define WINHELP_CTX_session_saved "config-saving" +#define WINHELP_CTX_session_coe "config-closeonexit" +#define WINHELP_CTX_logging_main "config-logging" +#define WINHELP_CTX_logging_filename "config-logfilename" +#define WINHELP_CTX_logging_exists "config-logfileexists" +#define WINHELP_CTX_logging_flush "config-logflush" +#define WINHELP_CTX_logging_header "config-logheader" +#define WINHELP_CTX_logging_ssh_omit_password "config-logssh" +#define WINHELP_CTX_logging_ssh_omit_data "config-logssh" +#define WINHELP_CTX_keyboard_backspace "config-backspace" +#define WINHELP_CTX_keyboard_homeend "config-homeend" +#define WINHELP_CTX_keyboard_funkeys "config-funkeys" +#define WINHELP_CTX_keyboard_appkeypad "config-appkeypad" +#define WINHELP_CTX_keyboard_appcursor "config-appcursor" +#define WINHELP_CTX_keyboard_nethack "config-nethack" +#define WINHELP_CTX_keyboard_compose "config-compose" +#define WINHELP_CTX_keyboard_ctrlalt "config-ctrlalt" +#define WINHELP_CTX_features_application "config-features-application" +#define WINHELP_CTX_features_mouse "config-features-mouse" +#define WINHELP_CTX_features_resize "config-features-resize" +#define WINHELP_CTX_features_altscreen "config-features-altscreen" +#define WINHELP_CTX_features_retitle "config-features-retitle" +#define WINHELP_CTX_features_qtitle "config-features-qtitle" +#define WINHELP_CTX_features_dbackspace "config-features-dbackspace" +#define WINHELP_CTX_features_charset "config-features-charset" +#define WINHELP_CTX_features_clearscroll "config-features-clearscroll" +#define WINHELP_CTX_features_arabicshaping "config-features-shaping" +#define WINHELP_CTX_features_bidi "config-features-bidi" +#define WINHELP_CTX_terminal_autowrap "config-autowrap" +#define WINHELP_CTX_terminal_decom "config-decom" +#define WINHELP_CTX_terminal_lfhascr "config-crlf" +#define WINHELP_CTX_terminal_crhaslf "config-lfcr" +#define WINHELP_CTX_terminal_bce "config-erase" +#define WINHELP_CTX_terminal_blink "config-blink" +#define WINHELP_CTX_terminal_answerback "config-answerback" +#define WINHELP_CTX_terminal_localecho "config-localecho" +#define WINHELP_CTX_terminal_localedit "config-localedit" +#define WINHELP_CTX_terminal_printing "config-printing" +#define WINHELP_CTX_supdup_location "supdup-location" +#define WINHELP_CTX_supdup_ascii "supdup-ascii" +#define WINHELP_CTX_supdup_more "supdup-more" +#define WINHELP_CTX_supdup_scroll "supdup-scroll" +#define WINHELP_CTX_bell_style "config-bellstyle" +#define WINHELP_CTX_bell_taskbar "config-belltaskbar" +#define WINHELP_CTX_bell_overload "config-bellovl" +#define WINHELP_CTX_window_size "config-winsize" +#define WINHELP_CTX_window_resize "config-winsizelock" +#define WINHELP_CTX_window_scrollback "config-scrollback" +#define WINHELP_CTX_window_erased "config-erasetoscrollback" +#define WINHELP_CTX_behaviour_closewarn "config-warnonclose" +#define WINHELP_CTX_behaviour_altf4 "config-altf4" +#define WINHELP_CTX_behaviour_altspace "config-altspace" +#define WINHELP_CTX_behaviour_altonly "config-altonly" +#define WINHELP_CTX_behaviour_alwaysontop "config-alwaysontop" +#define WINHELP_CTX_behaviour_altenter "config-fullscreen" +#define WINHELP_CTX_appearance_cursor "config-cursor" +#define WINHELP_CTX_appearance_font "config-font" +#define WINHELP_CTX_appearance_title "config-title" +#define WINHELP_CTX_appearance_hidemouse "config-mouseptr" +#define WINHELP_CTX_appearance_border "config-winborder" +#define WINHELP_CTX_connection_termtype "config-termtype" +#define WINHELP_CTX_connection_termspeed "config-termspeed" +#define WINHELP_CTX_connection_username "config-username" +#define WINHELP_CTX_connection_username_from_env "config-username-from-env" +#define WINHELP_CTX_connection_keepalive "config-keepalive" +#define WINHELP_CTX_connection_nodelay "config-nodelay" +#define WINHELP_CTX_connection_ipversion "config-address-family" +#define WINHELP_CTX_connection_tcpkeepalive "config-tcp-keepalives" +#define WINHELP_CTX_connection_loghost "config-loghost" +#define WINHELP_CTX_proxy_type "config-proxy-type" +#define WINHELP_CTX_proxy_main "config-proxy" +#define WINHELP_CTX_proxy_exclude "config-proxy-exclude" +#define WINHELP_CTX_proxy_dns "config-proxy-dns" +#define WINHELP_CTX_proxy_auth "config-proxy-auth" +#define WINHELP_CTX_proxy_command "config-proxy-command" +#define WINHELP_CTX_proxy_logging "config-proxy-logging" +#define WINHELP_CTX_telnet_environ "config-environ" +#define WINHELP_CTX_telnet_oldenviron "config-oldenviron" +#define WINHELP_CTX_telnet_passive "config-ptelnet" +#define WINHELP_CTX_telnet_specialkeys "config-telnetkey" +#define WINHELP_CTX_telnet_newline "config-telnetnl" +#define WINHELP_CTX_rlogin_localuser "config-rlogin-localuser" +#define WINHELP_CTX_ssh_nopty "config-ssh-pty" +#define WINHELP_CTX_ssh_ttymodes "config-ttymodes" +#define WINHELP_CTX_ssh_noshell "config-ssh-noshell" +#define WINHELP_CTX_ssh_ciphers "config-ssh-encryption" +#define WINHELP_CTX_ssh_protocol "config-ssh-prot" +#define WINHELP_CTX_ssh_command "config-command" +#define WINHELP_CTX_ssh_compress "config-ssh-comp" +#define WINHELP_CTX_ssh_share "config-ssh-sharing" +#define WINHELP_CTX_ssh_kexlist "config-ssh-kex-order" +#define WINHELP_CTX_ssh_hklist "config-ssh-hostkey-order" +#define WINHELP_CTX_ssh_hk_known "config-ssh-prefer-known-hostkeys" +#define WINHELP_CTX_ssh_gssapi_kex_delegation "config-ssh-kex-gssapi-delegation" +#define WINHELP_CTX_ssh_kex_repeat "config-ssh-kex-rekey" +#define WINHELP_CTX_ssh_kex_manual_hostkeys "config-ssh-kex-manual-hostkeys" +#define WINHELP_CTX_ssh_auth_bypass "config-ssh-noauth" +#define WINHELP_CTX_ssh_auth_banner "config-ssh-banner" +#define WINHELP_CTX_ssh_auth_privkey "config-ssh-privkey" +#define WINHELP_CTX_ssh_auth_agentfwd "config-ssh-agentfwd" +#define WINHELP_CTX_ssh_auth_changeuser "config-ssh-changeuser" +#define WINHELP_CTX_ssh_auth_pageant "config-ssh-tryagent" +#define WINHELP_CTX_ssh_auth_tis "config-ssh-tis" +#define WINHELP_CTX_ssh_auth_ki "config-ssh-ki" +#define WINHELP_CTX_ssh_gssapi "config-ssh-auth-gssapi" +#define WINHELP_CTX_ssh_gssapi_delegation "config-ssh-auth-gssapi-delegation" +#define WINHELP_CTX_ssh_gssapi_libraries "config-ssh-auth-gssapi-libraries" +#define WINHELP_CTX_selection_buttons "config-mouse" +#define WINHELP_CTX_selection_shiftdrag "config-mouseshift" +#define WINHELP_CTX_selection_rect "config-rectselect" +#define WINHELP_CTX_selection_linedraw "config-linedrawpaste" +#define WINHELP_CTX_selection_autocopy "config-selection-autocopy" +#define WINHELP_CTX_selection_clipactions "config-selection-clipactions" +#define WINHELP_CTX_selection_pastectrl "config-paste-ctrl-char" +#define WINHELP_CTX_copy_charclasses "config-charclasses" +#define WINHELP_CTX_copy_rtf "config-rtfcopy" +#define WINHELP_CTX_colours_ansi "config-ansicolour" +#define WINHELP_CTX_colours_xterm256 "config-xtermcolour" +#define WINHELP_CTX_colours_truecolour "config-truecolour" +#define WINHELP_CTX_colours_bold "config-boldcolour" +#define WINHELP_CTX_colours_system "config-syscolour" +#define WINHELP_CTX_colours_logpal "config-logpalette" +#define WINHELP_CTX_colours_config "config-colourcfg" +#define WINHELP_CTX_translation_codepage "config-charset" +#define WINHELP_CTX_translation_cjk_ambig_wide "config-cjk-ambig-wide" +#define WINHELP_CTX_translation_cyrillic "config-cyr" +#define WINHELP_CTX_translation_linedraw "config-linedraw" +#define WINHELP_CTX_translation_utf8linedraw "config-utf8linedraw" +#define WINHELP_CTX_ssh_tunnels_x11 "config-ssh-x11" +#define WINHELP_CTX_ssh_tunnels_x11auth "config-ssh-x11auth" +#define WINHELP_CTX_ssh_tunnels_xauthority "config-ssh-xauthority" +#define WINHELP_CTX_ssh_tunnels_portfwd "config-ssh-portfwd" +#define WINHELP_CTX_ssh_tunnels_portfwd_localhost "config-ssh-portfwd-localhost" +#define WINHELP_CTX_ssh_tunnels_portfwd_ipversion "config-ssh-portfwd-address-family" +#define WINHELP_CTX_ssh_bugs_ignore1 "config-ssh-bug-ignore1" +#define WINHELP_CTX_ssh_bugs_plainpw1 "config-ssh-bug-plainpw1" +#define WINHELP_CTX_ssh_bugs_rsa1 "config-ssh-bug-rsa1" +#define WINHELP_CTX_ssh_bugs_ignore2 "config-ssh-bug-ignore2" +#define WINHELP_CTX_ssh_bugs_hmac2 "config-ssh-bug-hmac2" +#define WINHELP_CTX_ssh_bugs_derivekey2 "config-ssh-bug-derivekey2" +#define WINHELP_CTX_ssh_bugs_rsapad2 "config-ssh-bug-sig" +#define WINHELP_CTX_ssh_bugs_pksessid2 "config-ssh-bug-pksessid2" +#define WINHELP_CTX_ssh_bugs_rekey2 "config-ssh-bug-rekey" +#define WINHELP_CTX_ssh_bugs_maxpkt2 "config-ssh-bug-maxpkt2" +#define WINHELP_CTX_ssh_bugs_winadj "config-ssh-bug-winadj" +#define WINHELP_CTX_ssh_bugs_chanreq "config-ssh-bug-chanreq" +#define WINHELP_CTX_ssh_bugs_oldgex2 "config-ssh-bug-oldgex2" +#define WINHELP_CTX_serial_line "config-serial-line" +#define WINHELP_CTX_serial_speed "config-serial-speed" +#define WINHELP_CTX_serial_databits "config-serial-databits" +#define WINHELP_CTX_serial_stopbits "config-serial-stopbits" +#define WINHELP_CTX_serial_parity "config-serial-parity" +#define WINHELP_CTX_serial_flow "config-serial-flow" + +#define WINHELP_CTX_pageant_general "pageant" +#define WINHELP_CTX_pageant_keylist "pageant-mainwin-keylist" +#define WINHELP_CTX_pageant_addkey "pageant-mainwin-addkey" +#define WINHELP_CTX_pageant_remkey "pageant-mainwin-remkey" +#define WINHELP_CTX_pageant_deferred "pageant-deferred-decryption" +#define WINHELP_CTX_pgpfingerprints "pgpkeys" +#define WINHELP_CTX_puttygen_general "pubkey-puttygen" +#define WINHELP_CTX_puttygen_keytype "puttygen-keytype" +#define WINHELP_CTX_puttygen_bits "puttygen-strength" +#define WINHELP_CTX_puttygen_generate "puttygen-generate" +#define WINHELP_CTX_puttygen_fingerprint "puttygen-fingerprint" +#define WINHELP_CTX_puttygen_comment "puttygen-comment" +#define WINHELP_CTX_puttygen_passphrase "puttygen-passphrase" +#define WINHELP_CTX_puttygen_savepriv "puttygen-savepriv" +#define WINHELP_CTX_puttygen_savepub "puttygen-savepub" +#define WINHELP_CTX_puttygen_pastekey "puttygen-pastekey" +#define WINHELP_CTX_puttygen_load "puttygen-load" +#define WINHELP_CTX_puttygen_conversions "puttygen-conversions" +#define WINHELP_CTX_puttygen_ppkver "puttygen-save-ppk-version" +#define WINHELP_CTX_puttygen_kdfparam "puttygen-save-passphrase-hashing" + +/* 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" +#define WINHELP_CTXID_errors_hostkey_absent 1 +#define WINHELP_CTX_errors_hostkey_changed "errors-hostkey-wrong" +#define WINHELP_CTXID_errors_hostkey_changed 2 +#define WINHELP_CTX_errors_cantloadkey "errors-cant-load-key" +#define WINHELP_CTXID_errors_cantloadkey 3 +#define WINHELP_CTX_option_cleanup "using-cleanup" +#define WINHELP_CTXID_option_cleanup 4 +#define WINHELP_CTX_pgp_fingerprints "pgpkeys" +#define WINHELP_CTXID_pgp_fingerprints 5 diff --git a/windows/help.rc2 b/windows/help.rc2 new file mode 100644 index 00000000..16bb41f0 --- /dev/null +++ b/windows/help.rc2 @@ -0,0 +1,8 @@ +#include "putty-rc.h" + +#ifdef EMBEDDED_CHM_FILE +ID_CUSTOM_CHMFILE TYPE_CUSTOM_CHMFILE EMBEDDED_CHM_FILE +#define HELPVER " (with embedded help)" +#else +#define HELPVER " (without embedded help)" +#endif diff --git a/windows/jump-list.c b/windows/jump-list.c new file mode 100644 index 00000000..358504fd --- /dev/null +++ b/windows/jump-list.c @@ -0,0 +1,748 @@ +/* + * 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 + +#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, ensuring that 'obj' really is a 'type **'. */ +#define typecheck(checkexpr, result) \ + (sizeof(checkexpr) ? (result) : (result)) +#define COMPTR(type, obj) &IID_##type, \ + typecheck((obj)-(type **)(obj), (void **)(void *)(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; + 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) { + settings_r *psettings_tmp = open_settings_r(sessionname); + if (!psettings_tmp) { + sfree(app_path); + return NULL; + } + close_settings_r(psettings_tmp); + } + + /* Create the new item. */ + if (!SUCCEEDED(CoCreateInstance(&CLSID_ShellLink, NULL, + CLSCTX_INPROC_SERVER, + COMPTR(IShellLink, &ret)))) { + sfree(app_path); + return NULL; + } + + /* Set path, parameters, icon and description. */ + ret->lpVtbl->SetPath(ret, app_path); + + if (sessionname) { + /* The leading space is reported to work around a Windows 10 + * behaviour change in which an argument string starting with + * '@' causes the SetArguments method to silently do the wrong + * thing. */ + param_string = dupcat(" @", sessionname); + } else { + param_string = dupstr(""); + } + ret->lpVtbl->SetArguments(ret, param_string); + sfree(param_string); + + if (sessionname) { + desc_string = dupcat("Connect to PuTTY session '", sessionname, "'"); + } else { + assert(appname); + desc_string = dupprintf("Run %.*s", + (int)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", + (int)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; + bool 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; + bool 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 ((osMajorVersion < 6) || (osMajorVersion == 6 && osMinorVersion < 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 ((osMajorVersion < 6) || (osMajorVersion == 6 && osMinorVersion < 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(); + } +} + +/* Set Explicit App User Model Id to fix removable media error with + jump lists */ + +bool set_explicit_app_user_model_id(void) +{ + DECL_WINDOWS_FUNCTION(static, HRESULT, SetCurrentProcessExplicitAppUserModelID, + (PCWSTR)); + + static HMODULE shell32_module = 0; + + if (!shell32_module) + { + shell32_module = load_system32_dll("Shell32.dll"); + /* + * We can't typecheck this function here, because it's defined + * in , which we're not including due to clashes + * with all the manual-COM machinery above. + */ + GET_WINDOWS_FUNCTION_NO_TYPECHECK( + shell32_module, SetCurrentProcessExplicitAppUserModelID); + } + + if (p_SetCurrentProcessExplicitAppUserModelID) + { + if (p_SetCurrentProcessExplicitAppUserModelID(L"SimonTatham.PuTTY") == S_OK) + { + return true; + } + return false; + } + /* Function doesn't exist, which is ok for Pre-7 systems */ + + return true; + +} diff --git a/windows/local-proxy.c b/windows/local-proxy.c new file mode 100644 index 00000000..94e31fcb --- /dev/null +++ b/windows/local-proxy.c @@ -0,0 +1,107 @@ +/* + * winproxy.c: Windows implementation of platform_new_connection(), + * supporting an OpenSSH-like proxy command via the winhandl.c + * mechanism. + */ + +#include +#include + +#include "tree234.h" +#include "putty.h" +#include "network.h" +#include "proxy.h" + +Socket *platform_new_connection(SockAddr *addr, const char *hostname, + int port, bool privport, + bool oobinline, bool nodelay, bool keepalive, + Plug *plug, Conf *conf) +{ + char *cmd; + HANDLE us_to_cmd, cmd_from_us; + HANDLE us_from_cmd, cmd_to_us; + HANDLE us_from_cmd_err, cmd_err_to_us; + SECURITY_ATTRIBUTES sa; + STARTUPINFO si; + PROCESS_INFORMATION pi; + + if (conf_get_int(conf, CONF_proxy_type) != PROXY_CMD) + return NULL; + + cmd = format_telnet_command(addr, port, conf); + + /* We are responsible for this and don't need it any more */ + sk_addr_free(addr); + + { + char *msg = dupprintf("Starting local proxy command: %s", cmd); + plug_log(plug, PLUGLOG_PROXY_MSG, NULL, 0, msg, 0); + sfree(msg); + } + + /* + * 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)) { + sfree(cmd); + return new_error_socket_fmt( + plug, "Unable to create pipes for proxy command: %s", + win_strerror(GetLastError())); + } + + if (!CreatePipe(&cmd_from_us, &us_to_cmd, &sa, 0)) { + sfree(cmd); + CloseHandle(us_from_cmd); + CloseHandle(cmd_to_us); + return new_error_socket_fmt( + plug, "Unable to create pipes for proxy command: %s", + win_strerror(GetLastError())); + } + + if (!CreatePipe(&us_from_cmd_err, &cmd_err_to_us, &sa, 0)) { + sfree(cmd); + CloseHandle(us_from_cmd); + CloseHandle(cmd_to_us); + CloseHandle(us_to_cmd); + CloseHandle(cmd_from_us); + return new_error_socket_fmt( + plug, "Unable to create pipes for proxy command: %s", + win_strerror(GetLastError())); + } + + SetHandleInformation(us_to_cmd, HANDLE_FLAG_INHERIT, 0); + SetHandleInformation(us_from_cmd, HANDLE_FLAG_INHERIT, 0); + if (us_from_cmd_err != NULL) + SetHandleInformation(us_from_cmd_err, 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 = cmd_err_to_us; + CreateProcess(NULL, cmd, NULL, NULL, true, + CREATE_NO_WINDOW | NORMAL_PRIORITY_CLASS, + NULL, NULL, &si, &pi); + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + + sfree(cmd); + + CloseHandle(cmd_from_us); + CloseHandle(cmd_to_us); + + if (cmd_err_to_us != NULL) + CloseHandle(cmd_err_to_us); + + return make_handle_socket(us_to_cmd, us_from_cmd, us_from_cmd_err, + plug, false); +} diff --git a/windows/named-pipe-client.c b/windows/named-pipe-client.c new file mode 100644 index 00000000..bd43998a --- /dev/null +++ b/windows/named-pipe-client.c @@ -0,0 +1,94 @@ +/* + * Windows support module which deals with being a named-pipe client. + */ + +#include +#include + +#include "tree234.h" +#include "putty.h" +#include "network.h" +#include "proxy.h" +#include "ssh.h" + +#include "security-api.h" + +HANDLE connect_to_named_pipe(const char *pipename, char **err) +{ + HANDLE pipehandle; + PSID usersid, pipeowner; + PSECURITY_DESCRIPTOR psd; + + assert(strncmp(pipename, "\\\\.\\pipe\\", 9) == 0); + assert(strchr(pipename + 9, '\\') == NULL); + + while (1) { + pipehandle = CreateFile(pipename, GENERIC_READ | GENERIC_WRITE, + 0, NULL, OPEN_EXISTING, + FILE_FLAG_OVERLAPPED, NULL); + + if (pipehandle != INVALID_HANDLE_VALUE) + break; + + if (GetLastError() != ERROR_PIPE_BUSY) { + *err = dupprintf( + "Unable to open named pipe '%s': %s", + pipename, win_strerror(GetLastError())); + return INVALID_HANDLE_VALUE; + } + + /* + * If we got ERROR_PIPE_BUSY, wait for the server to + * create a new pipe instance. (Since the server is + * expected to be winnps.c, which will do that immediately + * after a previous connection is accepted, that shouldn't + * take excessively long.) + */ + if (!WaitNamedPipe(pipename, NMPWAIT_USE_DEFAULT_WAIT)) { + *err = dupprintf( + "Error waiting for named pipe '%s': %s", + pipename, win_strerror(GetLastError())); + return INVALID_HANDLE_VALUE; + } + } + + if ((usersid = get_user_sid()) == NULL) { + CloseHandle(pipehandle); + *err = dupprintf( + "Unable to get user SID: %s", win_strerror(GetLastError())); + return INVALID_HANDLE_VALUE; + } + + if (p_GetSecurityInfo(pipehandle, SE_KERNEL_OBJECT, + OWNER_SECURITY_INFORMATION, + &pipeowner, NULL, NULL, NULL, + &psd) != ERROR_SUCCESS) { + CloseHandle(pipehandle); + *err = dupprintf( + "Unable to get named pipe security information: %s", + win_strerror(GetLastError())); + return INVALID_HANDLE_VALUE; + } + + if (!EqualSid(pipeowner, usersid)) { + CloseHandle(pipehandle); + LocalFree(psd); + *err = dupprintf( + "Owner of named pipe '%s' is not us", pipename); + return INVALID_HANDLE_VALUE; + } + + LocalFree(psd); + + return pipehandle; +} + +Socket *new_named_pipe_client(const char *pipename, Plug *plug) +{ + char *err = NULL; + HANDLE pipehandle = connect_to_named_pipe(pipename, &err); + if (pipehandle == INVALID_HANDLE_VALUE) + return new_error_socket_consume_string(plug, err); + else + return make_handle_socket(pipehandle, pipehandle, NULL, plug, true); +} diff --git a/windows/named-pipe-server.c b/windows/named-pipe-server.c new file mode 100644 index 00000000..cdda500d --- /dev/null +++ b/windows/named-pipe-server.c @@ -0,0 +1,236 @@ +/* + * Windows support module which deals with being a named-pipe server. + */ + +#include +#include + +#include "tree234.h" +#include "putty.h" +#include "network.h" +#include "proxy.h" +#include "ssh.h" + +#include "security-api.h" + +typedef struct NamedPipeServerSocket { + /* Parameters for (repeated) creation of named pipe objects */ + PSECURITY_DESCRIPTOR psd; + PACL acl; + char *pipename; + + /* The current named pipe object + attempt to connect to it */ + HANDLE pipehandle; + OVERLAPPED connect_ovl; + struct handle *callback_handle; /* winhandl.c's reference */ + + /* PuTTY Socket machinery */ + Plug *plug; + char *error; + + Socket sock; +} NamedPipeServerSocket; + +static Plug *sk_namedpipeserver_plug(Socket *s, Plug *p) +{ + NamedPipeServerSocket *ps = container_of(s, NamedPipeServerSocket, sock); + Plug *ret = ps->plug; + if (p) + ps->plug = p; + return ret; +} + +static void sk_namedpipeserver_close(Socket *s) +{ + NamedPipeServerSocket *ps = container_of(s, NamedPipeServerSocket, sock); + + if (ps->callback_handle) + handle_free(ps->callback_handle); + CloseHandle(ps->pipehandle); + CloseHandle(ps->connect_ovl.hEvent); + sfree(ps->error); + sfree(ps->pipename); + if (ps->acl) + LocalFree(ps->acl); + if (ps->psd) + LocalFree(ps->psd); + sfree(ps); +} + +static const char *sk_namedpipeserver_socket_error(Socket *s) +{ + NamedPipeServerSocket *ps = container_of(s, NamedPipeServerSocket, sock); + return ps->error; +} + +static SocketPeerInfo *sk_namedpipeserver_peer_info(Socket *s) +{ + return NULL; +} + +static bool create_named_pipe(NamedPipeServerSocket *ps, bool first_instance) +{ + SECURITY_ATTRIBUTES sa; + + memset(&sa, 0, sizeof(sa)); + sa.nLength = sizeof(sa); + sa.lpSecurityDescriptor = ps->psd; + sa.bInheritHandle = false; + + ps->pipehandle = CreateNamedPipe + (/* lpName */ + ps->pipename, + + /* dwOpenMode */ + PIPE_ACCESS_DUPLEX | + FILE_FLAG_OVERLAPPED | + (first_instance ? FILE_FLAG_FIRST_PIPE_INSTANCE : 0), + + /* dwPipeMode */ + PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT +#ifdef PIPE_REJECT_REMOTE_CLIENTS + | PIPE_REJECT_REMOTE_CLIENTS +#endif + , + + /* nMaxInstances */ + PIPE_UNLIMITED_INSTANCES, + + /* nOutBufferSize, nInBufferSize */ + 4096, 4096, /* FIXME: think harder about buffer sizes? */ + + /* nDefaultTimeOut */ + 0 /* default timeout */, + + /* lpSecurityAttributes */ + &sa); + + return ps->pipehandle != INVALID_HANDLE_VALUE; +} + +static Socket *named_pipe_accept(accept_ctx_t ctx, Plug *plug) +{ + HANDLE conn = (HANDLE)ctx.p; + + return make_handle_socket(conn, conn, NULL, plug, true); +} + +static void named_pipe_accept_loop(NamedPipeServerSocket *ps, + bool got_one_already) +{ + while (1) { + int error; + char *errmsg; + + if (got_one_already) { + /* If we were called with a connection already waiting, + * skip this step. */ + got_one_already = false; + error = 0; + } else { + /* + * Call ConnectNamedPipe, which might succeed or might + * tell us that an overlapped operation is in progress and + * we should wait for our event object. + */ + if (ConnectNamedPipe(ps->pipehandle, &ps->connect_ovl)) + error = 0; + else + error = GetLastError(); + + if (error == ERROR_IO_PENDING) + return; + } + + if (error == 0 || error == ERROR_PIPE_CONNECTED) { + /* + * We've successfully retrieved an incoming connection, so + * ps->pipehandle now refers to that connection. So + * convert that handle into a separate connection-type + * Socket, and create a fresh one to be the new listening + * pipe. + */ + HANDLE conn = ps->pipehandle; + accept_ctx_t actx; + + actx.p = (void *)conn; + if (plug_accepting(ps->plug, named_pipe_accept, actx)) { + /* + * If the plug didn't want the connection, might as + * well close this handle. + */ + CloseHandle(conn); + } + + if (!create_named_pipe(ps, false)) { + error = GetLastError(); + } else { + /* + * Go round again to see if more connections can be + * got, or to begin waiting on the event object. + */ + continue; + } + } + + errmsg = dupprintf("Error while listening to named pipe: %s", + win_strerror(error)); + plug_log(ps->plug, 1, sk_namedpipe_addr(ps->pipename), 0, + errmsg, error); + sfree(errmsg); + break; + } +} + +static void named_pipe_connect_callback(void *vps) +{ + NamedPipeServerSocket *ps = (NamedPipeServerSocket *)vps; + named_pipe_accept_loop(ps, true); +} + +/* + * This socket type is only used for listening, so it should never + * be asked to write or set_frozen. + */ +static const SocketVtable NamedPipeServerSocket_sockvt = { + .plug = sk_namedpipeserver_plug, + .close = sk_namedpipeserver_close, + .socket_error = sk_namedpipeserver_socket_error, + .peer_info = sk_namedpipeserver_peer_info, +}; + +Socket *new_named_pipe_listener(const char *pipename, Plug *plug) +{ + NamedPipeServerSocket *ret = snew(NamedPipeServerSocket); + ret->sock.vt = &NamedPipeServerSocket_sockvt; + ret->plug = plug; + ret->error = NULL; + ret->psd = NULL; + ret->pipename = dupstr(pipename); + ret->acl = NULL; + ret->callback_handle = NULL; + + assert(strncmp(pipename, "\\\\.\\pipe\\", 9) == 0); + assert(strchr(pipename + 9, '\\') == NULL); + + if (!make_private_security_descriptor(GENERIC_READ | GENERIC_WRITE, + &ret->psd, &ret->acl, &ret->error)) { + goto cleanup; + } + + if (!create_named_pipe(ret, true)) { + ret->error = dupprintf("unable to create named pipe '%s': %s", + pipename, win_strerror(GetLastError())); + goto cleanup; + } + + memset(&ret->connect_ovl, 0, sizeof(ret->connect_ovl)); + ret->connect_ovl.hEvent = CreateEvent(NULL, true, false, NULL); + ret->callback_handle = + handle_add_foreign_event(ret->connect_ovl.hEvent, + named_pipe_connect_callback, ret); + named_pipe_accept_loop(ret, false); + + cleanup: + return &ret->sock; +} diff --git a/windows/network.c b/windows/network.c new file mode 100644 index 00000000..3b4da3cc --- /dev/null +++ b/windows/network.c @@ -0,0 +1,1825 @@ +/* + * Windows networking abstraction. + * + * For the IPv6 code in here I am indebted to Jeroen Massar and + * unfix.org. + */ + +#include /* need to put this first, for winelib builds */ + +#include +#include +#include + +#define NEED_DECLARATION_OF_SELECT /* in order to initialise it */ + +#include "putty.h" +#include "network.h" +#include "tree234.h" +#include "ssh.h" + +#include + +#ifndef NO_IPV6 +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wmissing-braces" +#endif +const struct in6_addr in6addr_any = IN6ADDR_ANY_INIT; +const struct in6_addr in6addr_loopback = IN6ADDR_LOOPBACK_INIT; +#ifdef __clang__ +#pragma clang diagnostic pop +#endif +#endif + +#define ipv4_is_loopback(addr) \ + ((p_ntohl(addr.s_addr) & 0xFF000000L) == 0x7F000000L) + +/* + * 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; +}; + +typedef struct NetSocket NetSocket; +struct NetSocket { + const char *error; + SOCKET s; + Plug *plug; + bufchain output_data; + bool connected; + bool writable; + bool frozen; /* this causes readability notifications to be ignored */ + bool frozen_readable; /* this means we missed at least one readability + * notification while we were frozen */ + bool localhost_only; /* for listening sockets */ + char oobdata[1]; + size_t sending_oob; + bool oobinline, nodelay, keepalive, privport; + enum { EOF_NO, EOF_PENDING, EOF_SENT } outgoingeof; + SockAddr *addr; + SockAddrStep step; + int port; + int pending_error; /* in case send() returns error */ + /* + * 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. + */ + NetSocket *parent, *child; + + Socket sock; +}; + +struct SockAddr { + int refcount; + char *error; + bool resolved; + bool namedpipe; /* indicates that this SockAddr is phony, holding a Windows + * named pipe pathname instead of a network address */ +#ifndef NO_IPV6 + struct addrinfo *ais; /* Addresses IPv6 style. */ +#endif + unsigned long *addresses; /* Addresses IPv4 style. */ + int naddresses; + 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) +{ + NetSocket *a = (NetSocket *)av, *b = (NetSocket *)bv; + uintptr_t as = (uintptr_t) a->s, bs = (uintptr_t) 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) +{ + NetSocket *b = (NetSocket *)bv; + uintptr_t as = (uintptr_t) av, bs = (uintptr_t) 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, const char FAR *, inet_ntop, + (int, void FAR *, char *, size_t)); +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, shutdown, (SOCKET, int)); +DECL_WINDOWS_FUNCTION(static, int, ioctlsocket, + (SOCKET, long, u_long FAR *)); +DECL_WINDOWS_FUNCTION(static, SOCKET, accept, + (SOCKET, struct sockaddr FAR *, int FAR *)); +DECL_WINDOWS_FUNCTION(static, int, getpeername, + (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, DWORD hostlen, char FAR * serv, + DWORD 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 + +static bool 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; + } + + return true; +} + +DEF_WINDOWS_FUNCTION(WSAAsyncSelect); +DEF_WINDOWS_FUNCTION(WSAEventSelect); +DEF_WINDOWS_FUNCTION(WSAGetLastError); +DEF_WINDOWS_FUNCTION(WSAEnumNetworkEvents); +DEF_WINDOWS_FUNCTION(select); + +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) + modalfatalbox("Unable to load any WinSock library"); + +#ifndef NO_IPV6 + /* Check if we have getaddrinfo in Winsock */ + if (GetProcAddress(winsock_module, "getaddrinfo") != NULL) { + GET_WINDOWS_FUNCTION(winsock_module, getaddrinfo); + GET_WINDOWS_FUNCTION(winsock_module, freeaddrinfo); + GET_WINDOWS_FUNCTION_NO_TYPECHECK(winsock_module, getnameinfo); + /* This function would fail its type-check if we did one, + * because the VS header file provides an inline definition + * which is __cdecl instead of WINAPI. */ + GET_WINDOWS_FUNCTION_NO_TYPECHECK(winsock_module, gai_strerror); + } else { + /* Fall back to wship6.dll for Windows 2000 */ + wship6_module = load_system32_dll("wship6.dll"); + if (wship6_module) { + GET_WINDOWS_FUNCTION(wship6_module, getaddrinfo); + GET_WINDOWS_FUNCTION(wship6_module, freeaddrinfo); + /* See comment above about type check */ + GET_WINDOWS_FUNCTION_NO_TYPECHECK(wship6_module, getnameinfo); + GET_WINDOWS_FUNCTION_NO_TYPECHECK(winsock_module, gai_strerror); + } else { + } + } + GET_WINDOWS_FUNCTION(winsock2_module, WSAAddressToStringA); +#endif + + GET_WINDOWS_FUNCTION(winsock_module, WSAAsyncSelect); + GET_WINDOWS_FUNCTION(winsock_module, WSAEventSelect); + /* We don't type-check select because at least some MinGW versions + * of the Windows API headers seem to disagree with the + * documentation on whether the 'struct timeval *' pointer is + * const or not. */ + GET_WINDOWS_FUNCTION_NO_TYPECHECK(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_NO_TYPECHECK(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); + /* Older Visual Studio, and MinGW as of Ubuntu 16.04, don't know + * about this function at all, so can't type-check it. Also there + * seems to be some disagreement in the VS headers about whether + * the second argument is void * or const void *, so I omit the + * type check. */ + GET_WINDOWS_FUNCTION_NO_TYPECHECK(winsock_module, inet_ntop); + 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, shutdown); + GET_WINDOWS_FUNCTION(winsock_module, ioctlsocket); + GET_WINDOWS_FUNCTION(winsock_module, accept); + GET_WINDOWS_FUNCTION(winsock_module, getpeername); + 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)) { + modalfatalbox("Unable to initialise WinSock"); + } + + sktree = newtree234(cmpfortree); +} + +void sk_cleanup(void) +{ + NetSocket *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 +} + +const char *winsock_error_string(int error) +{ + /* + * Error codes we know about and have historically had reasonably + * sensible error messages for. + */ + switch (error) { + case WSAEACCES: + return "Network error: Permission denied"; + case WSAEADDRINUSE: + return "Network error: Address already in use"; + 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"; + } + + /* + * Handle any other error code by delegating to win_strerror. + */ + return win_strerror(error); +} + +SockAddr *sk_namelookup(const char *host, char **canonicalname, + int address_family) +{ + SockAddr *ret = snew(SockAddr); + 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(SockAddr)); +#ifndef NO_IPV6 + ret->ais = NULL; +#endif + ret->namedpipe = false; + 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 = 0; +#ifndef NO_IPV6 + /* + * Use getaddrinfo when it's available + */ + if (p_getaddrinfo) { + struct addrinfo hints; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = hint_family; + hints.ai_flags = AI_CANONNAME; + { + /* strip [] on IPv6 address literals */ + char *trimmed_host = host_strduptrim(host); + err = p_getaddrinfo(trimmed_host, NULL, &hints, &ret->ais); + sfree(trimmed_host); + } + if (err == 0) + ret->resolved = true; + } else +#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 = dupstr(realhost); + return ret; +} + +SockAddr *sk_nonamelookup(const char *host) +{ + SockAddr *ret = snew(SockAddr); + ret->error = NULL; + ret->resolved = false; +#ifndef NO_IPV6 + ret->ais = NULL; +#endif + ret->namedpipe = false; + ret->addresses = NULL; + ret->naddresses = 0; + ret->refcount = 1; + strncpy(ret->hostname, host, lenof(ret->hostname)); + ret->hostname[lenof(ret->hostname)-1] = '\0'; + return ret; +} + +SockAddr *sk_namedpipe_addr(const char *pipename) +{ + SockAddr *ret = snew(SockAddr); + ret->error = NULL; + ret->resolved = false; +#ifndef NO_IPV6 + ret->ais = NULL; +#endif + ret->namedpipe = true; + ret->addresses = NULL; + ret->naddresses = 0; + ret->refcount = 1; + strncpy(ret->hostname, pipename, lenof(ret->hostname)); + ret->hostname[lenof(ret->hostname)-1] = '\0'; + return ret; +} + +static bool 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, "", 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'; + } +} + +/* + * This constructs a SockAddr that points at one specific sub-address + * of a parent SockAddr. The returned SockAddr does not own all its + * own memory: it points into the old one's data structures, so it + * MUST NOT be used after the old one is freed, and it MUST NOT be + * passed to sk_addr_free. (The latter is why it's returned by value + * rather than dynamically allocated - that should clue in anyone + * writing a call to it that something is weird about it.) + */ +static SockAddr sk_extractaddr_tmp( + SockAddr *addr, const SockAddrStep *step) +{ + SockAddr toret; + toret = *addr; /* structure copy */ + toret.refcount = 1; + +#ifndef NO_IPV6 + toret.ais = step->ai; +#endif + if (SOCKADDR_FAMILY(addr, *step) == AF_INET +#ifndef NO_IPV6 + && !toret.ais +#endif + ) + toret.addresses += step->curraddr; + + return toret; +} + +bool sk_addr_needs_port(SockAddr *addr) +{ + return !addr->namedpipe; +} + +bool sk_hostname_is_local(const 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 bool ipv4_is_local_addr(struct in_addr addr) +{ + if (ipv4_is_loopback(addr)) + return true; /* loopback addresses are local */ + if (!n_local_interfaces) { + SOCKET s = p_socket(AF_INET, SOCK_DGRAM, 0); + DWORD retbytes; + + SetHandleInformation((HANDLE)s, HANDLE_FLAG_INHERIT, 0); + + 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 + n_local_interfaces = -1; + } + 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 true; /* this address is local */ + } + } + return false; /* this address is not local */ +} + +bool 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 sockaddr_in6 *)step.ai->ai_addr)->sin6_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 false; /* we don't know; assume not */ + } +} + +bool sk_address_is_special_local(SockAddr *addr) +{ + return false; /* no Unix-domain socket analogue here */ +} + +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 + unreachable("bad address family in sk_addrcopy"); + } 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_net_plug(Socket *sock, Plug *p) +{ + NetSocket *s = container_of(sock, NetSocket, sock); + Plug *ret = s->plug; + if (p) + s->plug = p; + return ret; +} + +static void sk_net_close(Socket *s); +static size_t sk_net_write(Socket *s, const void *data, size_t len); +static size_t sk_net_write_oob(Socket *s, const void *data, size_t len); +static void sk_net_write_eof(Socket *s); +static void sk_net_set_frozen(Socket *s, bool is_frozen); +static const char *sk_net_socket_error(Socket *s); +static SocketPeerInfo *sk_net_peer_info(Socket *s); + +static const SocketVtable NetSocket_sockvt = { + .plug = sk_net_plug, + .close = sk_net_close, + .write = sk_net_write, + .write_oob = sk_net_write_oob, + .write_eof = sk_net_write_eof, + .set_frozen = sk_net_set_frozen, + .socket_error = sk_net_socket_error, + .peer_info = sk_net_peer_info, +}; + +static Socket *sk_net_accept(accept_ctx_t ctx, Plug *plug) +{ + DWORD err; + const char *errstr; + NetSocket *ret; + + /* + * Create NetSocket structure. + */ + ret = snew(NetSocket); + ret->sock.vt = &NetSocket_sockvt; + ret->error = NULL; + ret->plug = plug; + bufchain_init(&ret->output_data); + ret->writable = true; /* to start with */ + ret->sending_oob = 0; + ret->outgoingeof = EOF_NO; + ret->frozen = true; + ret->frozen_readable = false; + ret->localhost_only = false; /* unused, but best init anyway */ + ret->pending_error = 0; + ret->parent = ret->child = NULL; + ret->addr = NULL; + + ret->s = (SOCKET)ctx.p; + + if (ret->s == INVALID_SOCKET) { + err = p_WSAGetLastError(); + ret->error = winsock_error_string(err); + return &ret->sock; + } + + ret->oobinline = false; + + /* 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, true); + if (errstr) { + ret->error = errstr; + return &ret->sock; + } + + add234(sktree, ret); + + return &ret->sock; +} + +static DWORD try_connect(NetSocket *sock) +{ + SOCKET s; +#ifndef NO_IPV6 + SOCKADDR_IN6 a6; +#endif + SOCKADDR_IN a; + DWORD err; + const char *errstr; + short localport; + int family; + + if (sock->s != INVALID_SOCKET) { + do_select(sock->s, false); + p_closesocket(sock->s); + } + + { + SockAddr thisaddr = sk_extractaddr_tmp( + sock->addr, &sock->step); + plug_log(sock->plug, PLUGLOG_CONNECT_TRYING, + &thisaddr, 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; + } + + SetHandleInformation((HANDLE)s, HANDLE_FLAG_INHERIT, 0); + + 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, true); + 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 = true; + SockAddr thisaddr = sk_extractaddr_tmp(sock->addr, &sock->step); + plug_log(sock->plug, PLUGLOG_CONNECT_SUCCESS, + &thisaddr, sock->port, NULL, 0); + } + + err = 0; + + ret: + + /* + * No matter what happened, put the socket back in the tree. + */ + add234(sktree, sock); + + if (err) { + SockAddr thisaddr = sk_extractaddr_tmp( + sock->addr, &sock->step); + plug_log(sock->plug, PLUGLOG_CONNECT_FAILED, + &thisaddr, sock->port, sock->error, err); + } + return err; +} + +Socket *sk_new(SockAddr *addr, int port, bool privport, bool oobinline, + bool nodelay, bool keepalive, Plug *plug) +{ + NetSocket *ret; + DWORD err; + + /* + * Create NetSocket structure. + */ + ret = snew(NetSocket); + ret->sock.vt = &NetSocket_sockvt; + ret->error = NULL; + ret->plug = plug; + bufchain_init(&ret->output_data); + ret->connected = false; /* to start with */ + ret->writable = false; /* to start with */ + ret->sending_oob = 0; + ret->outgoingeof = EOF_NO; + ret->frozen = false; + ret->frozen_readable = false; + ret->localhost_only = false; /* 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 &ret->sock; +} + +Socket *sk_newlistener(const char *srcaddr, int port, Plug *plug, + bool local_host_only, int orig_address_family) +{ + SOCKET s; +#ifndef NO_IPV6 + SOCKADDR_IN6 a6; +#endif + SOCKADDR_IN a; + + DWORD err; + const char *errstr; + NetSocket *ret; + int retcode; + + int address_family; + + /* + * Create NetSocket structure. + */ + ret = snew(NetSocket); + ret->sock.vt = &NetSocket_sockvt; + ret->error = NULL; + ret->plug = plug; + bufchain_init(&ret->output_data); + ret->writable = false; /* to start with */ + ret->sending_oob = 0; + ret->outgoingeof = EOF_NO; + ret->frozen = false; + ret->frozen_readable = false; + 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 &ret->sock; + } + + SetHandleInformation((HANDLE)s, HANDLE_FLAG_INHERIT, 0); + + ret->oobinline = false; + + { + BOOL on = true; + p_setsockopt(s, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, + (const char *)&on, sizeof(on)); + } + +#ifndef NO_IPV6 + if (address_family == AF_INET6) { + memset(&a6, 0, sizeof(a6)); + a6.sin6_family = AF_INET6; + if (local_host_only) + a6.sin6_addr = in6addr_loopback; + else + a6.sin6_addr = in6addr_any; + if (srcaddr != NULL && p_getaddrinfo) { + struct addrinfo hints; + struct addrinfo *ai; + int err; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET6; + hints.ai_flags = 0; + { + /* strip [] on IPv6 address literals */ + char *trimmed_addr = host_strduptrim(srcaddr); + err = p_getaddrinfo(trimmed_addr, NULL, &hints, &ai); + sfree(trimmed_addr); + } + if (err == 0 && ai->ai_family == AF_INET6) { + a6.sin6_addr = + ((struct sockaddr_in6 *)ai->ai_addr)->sin6_addr; + } + } + a6.sin6_port = p_htons(port); + } else +#endif + { + bool got_addr = false; + 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 = true; + } + } + + /* + * ... 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 &ret->sock; + } + + + if (p_listen(s, SOMAXCONN) == SOCKET_ERROR) { + p_closesocket(s); + ret->error = winsock_error_string(p_WSAGetLastError()); + return &ret->sock; + } + + /* Set up a select mechanism. This could be an AsyncSelect on a + * window, or an EventSelect on an event object. */ + errstr = do_select(s, true); + if (errstr) { + p_closesocket(s); + ret->error = errstr; + return &ret->sock; + } + + 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) { + Socket *other = sk_newlistener(srcaddr, port, plug, + local_host_only, ADDRTYPE_IPV6); + + if (other) { + NetSocket *ns = container_of(other, NetSocket, sock); + if (!ns->error) { + ns->parent = ret; + ret->child = ns; + } else { + sfree(ns); + } + } + } +#endif + + return &ret->sock; +} + +static void sk_net_close(Socket *sock) +{ + NetSocket *s = container_of(sock, NetSocket, sock); + + if (s->child) + sk_net_close(&s->child->sock); + + bufchain_clear(&s->output_data); + + del234(sktree, s); + do_select(s->s, false); + p_closesocket(s->s); + if (s->addr) + sk_addr_free(s->addr); + delete_callbacks_for_context(s); + sfree(s); +} + +/* + * Deal with socket errors detected in try_send(). + */ +static void socket_error_callback(void *vs) +{ + NetSocket *s = (NetSocket *)vs; + + /* + * Just in case other socket work has caused this socket to vanish + * or become somehow non-erroneous before this callback arrived... + */ + if (!find234(sktree, s, NULL) || !s->pending_error) + return; + + /* + * An error has occurred on this socket. Pass it to the plug. + */ + plug_closing(s->plug, winsock_error_string(s->pending_error), + s->pending_error, 0); +} + +/* + * The function which tries to send on a socket once it's deemed + * writable. + */ +void try_send(NetSocket *s) +{ + while (s->sending_oob || bufchain_size(&s->output_data) > 0) { + int nsent; + DWORD err; + const void *data; + size_t len; + int urgentflag; + + if (s->sending_oob) { + urgentflag = MSG_OOB; + len = s->sending_oob; + data = &s->oobdata; + } else { + urgentflag = 0; + ptrlen bufdata = bufchain_prefix(&s->output_data); + data = bufdata.ptr; + len = bufdata.len; + } + len = min(len, INT_MAX); /* WinSock send() takes an int */ + nsent = p_send(s->s, data, len, urgentflag); + noise_ultralight(NOISE_SOURCE_IOLEN, 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 send() returns a socket error, 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; + queue_toplevel_callback(socket_error_callback, s); + 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); + } + } + } + + /* + * If we reach here, we've finished sending everything we might + * have needed to send. Send EOF, if we need to. + */ + if (s->outgoingeof == EOF_PENDING) { + p_shutdown(s->s, SD_SEND); + s->outgoingeof = EOF_SENT; + } +} + +static size_t sk_net_write(Socket *sock, const void *buf, size_t len) +{ + NetSocket *s = container_of(sock, NetSocket, sock); + + assert(s->outgoingeof == EOF_NO); + + /* + * 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 size_t sk_net_write_oob(Socket *sock, const void *buf, size_t len) +{ + NetSocket *s = container_of(sock, NetSocket, sock); + + assert(s->outgoingeof == EOF_NO); + + /* + * Replace the buffer list on the socket with the data. + */ + bufchain_clear(&s->output_data); + assert(len <= sizeof(s->oobdata)); + 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; +} + +static void sk_net_write_eof(Socket *sock) +{ + NetSocket *s = container_of(sock, NetSocket, sock); + + assert(s->outgoingeof == EOF_NO); + + /* + * Mark the socket as pending outgoing EOF. + */ + s->outgoingeof = EOF_PENDING; + + /* + * Now try sending from the start of the buffer list. + */ + if (s->writable) + try_send(s); +} + +void select_result(WPARAM wParam, LPARAM lParam) +{ + int ret; + DWORD err; + char buf[20480]; /* nice big buffer for plenty of speed */ + NetSocket *s; + bool atmark; + + /* wParam is the socket itself */ + + if (wParam == 0) + return; /* boggle */ + + s = find234(sktree, (void *) wParam, cmpforsearch); + if (!s) + return; /* boggle */ + + if ((err = WSAGETSELECTERROR(lParam)) != 0) { + /* + * An error has occurred on this socket. Pass it to the + * plug. + */ + if (s->addr) { + SockAddr thisaddr = sk_extractaddr_tmp( + s->addr, &s->step); + plug_log(s->plug, PLUGLOG_CONNECT_FAILED, &thisaddr, s->port, + winsock_error_string(err), err); + while (err && s->addr && sk_nextaddr(s->addr, &s->step)) { + err = try_connect(s); + } + } + if (err != 0) + plug_closing(s->plug, winsock_error_string(err), err, 0); + return; + } + + noise_ultralight(NOISE_SOURCE_IOID, wParam); + + switch (WSAGETSELECTEVENT(lParam)) { + case FD_CONNECT: + s->connected = true; + s->writable = true; + + /* + * Once a socket is connected, we can stop falling back + * through the candidate addresses to connect to. But first, + * let the plug know we were successful. + */ + if (s->addr) { + SockAddr thisaddr = sk_extractaddr_tmp( + s->addr, &s->step); + plug_log(s->plug, PLUGLOG_CONNECT_SUCCESS, + &thisaddr, s->port, NULL, 0); + + 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 = true; + 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) { + u_long atmark_from_ioctl = 1; + p_ioctlsocket(s->s, SIOCATMARK, &atmark_from_ioctl); + /* + * 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. + */ + atmark = atmark_from_ioctl; + } else + atmark = true; + + ret = p_recv(s->s, buf, sizeof(buf), 0); + noise_ultralight(NOISE_SOURCE_IOLEN, ret); + if (ret < 0) { + err = p_WSAGetLastError(); + if (err == WSAEWOULDBLOCK) { + break; + } + } + if (ret < 0) { + plug_closing(s->plug, winsock_error_string(err), err, 0); + } else if (0 == ret) { + plug_closing(s->plug, NULL, 0, 0); + } else { + 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(NOISE_SOURCE_IOLEN, ret); + if (ret <= 0) { + int err = p_WSAGetLastError(); + plug_closing(s->plug, winsock_error_string(err), err, 0); + } else { + plug_receive(s->plug, 2, buf, ret); + } + break; + case FD_WRITE: { + int bufsize_before, bufsize_after; + s->writable = true; + 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. */ + do { + ret = p_recv(s->s, buf, sizeof(buf), 0); + if (ret < 0) { + err = p_WSAGetLastError(); + if (err == WSAEWOULDBLOCK) + break; + plug_closing(s->plug, winsock_error_string(err), err, 0); + } else { + if (ret) + plug_receive(s->plug, 0, buf, ret); + else + plug_closing(s->plug, NULL, 0, 0); + } + } while (ret > 0); + return; + 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 */ + accept_ctx_t actx; + + 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; + } + + actx.p = (void *)t; + +#ifndef NO_IPV6 + if (isa.ss_family == AF_INET && + s->localhost_only && + !ipv4_is_local_addr(((struct sockaddr_in *)&isa)->sin_addr)) +#else + if (s->localhost_only && !ipv4_is_local_addr(isa.sin_addr)) +#endif + { + p_closesocket(t); /* dodgy WinSock let nonlocal through */ + } else if (plug_accepting(s->plug, sk_net_accept, actx)) { + p_closesocket(t); /* denied or error */ + } + break; + } + } +} + +/* + * 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_net_socket_error(Socket *sock) +{ + NetSocket *s = container_of(sock, NetSocket, sock); + return s->error; +} + +static SocketPeerInfo *sk_net_peer_info(Socket *sock) +{ + NetSocket *s = container_of(sock, NetSocket, sock); +#ifdef NO_IPV6 + struct sockaddr_in addr; +#else + struct sockaddr_storage addr; + char buf[INET6_ADDRSTRLEN]; +#endif + int addrlen = sizeof(addr); + SocketPeerInfo *pi; + + if (p_getpeername(s->s, (struct sockaddr *)&addr, &addrlen) < 0) + return NULL; + + pi = snew(SocketPeerInfo); + pi->addressfamily = ADDRTYPE_UNSPEC; + pi->addr_text = NULL; + pi->port = -1; + pi->log_text = NULL; + + if (((struct sockaddr *)&addr)->sa_family == AF_INET) { + pi->addressfamily = ADDRTYPE_IPV4; + memcpy(pi->addr_bin.ipv4, &((struct sockaddr_in *)&addr)->sin_addr, 4); + pi->port = p_ntohs(((struct sockaddr_in *)&addr)->sin_port); + pi->addr_text = dupstr( + p_inet_ntoa(((struct sockaddr_in *)&addr)->sin_addr)); + pi->log_text = dupprintf("%s:%d", pi->addr_text, pi->port); + +#ifndef NO_IPV6 + } else if (((struct sockaddr *)&addr)->sa_family == AF_INET6) { + pi->addressfamily = ADDRTYPE_IPV6; + memcpy(pi->addr_bin.ipv6, + &((struct sockaddr_in6 *)&addr)->sin6_addr, 16); + pi->port = p_ntohs(((struct sockaddr_in6 *)&addr)->sin6_port); + pi->addr_text = dupstr( + p_inet_ntop(AF_INET6, &((struct sockaddr_in6 *)&addr)->sin6_addr, + buf, sizeof(buf))); + pi->log_text = dupprintf("[%s]:%d", pi->addr_text, pi->port); + +#endif + } else { + sfree(pi); + return NULL; + } + + return pi; +} + +static void sk_net_set_frozen(Socket *sock, bool is_frozen) +{ + NetSocket *s = container_of(sock, NetSocket, sock); + if (s->frozen == is_frozen) + return; + s->frozen = is_frozen; + if (!is_frozen) { + do_select(s->s, true); + if (s->frozen_readable) { + char c; + p_recv(s->s, &c, 1, MSG_PEEK); + } + } + s->frozen_readable = false; +} + +void socket_reselect_all(void) +{ + NetSocket *s; + int i; + + for (i = 0; (s = index234(sktree, i)) != NULL; i++) { + if (!s->frozen) + do_select(s->s, true); + } +} + +/* + * For Plink: enumerate all sockets currently active. + */ +SOCKET first_socket(int *state) +{ + NetSocket *s; + *state = 0; + s = index234(sktree, (*state)++); + return s ? s->s : INVALID_SOCKET; +} + +SOCKET next_socket(int *state) +{ + NetSocket *s = index234(sktree, (*state)++); + return s ? s->s : INVALID_SOCKET; +} + +bool socket_writable(SOCKET skt) +{ + NetSocket *s = find234(sktree, (void *)skt, cmpforsearch); + + if (s) + return bufchain_size(&s->output_data) > 0; + else + return false; +} + +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) +{ + char hostbuf[256]; /* MSDN docs for gethostname() promise this is enough */ + if (p_gethostname(hostbuf, sizeof(hostbuf)) < 0) + return NULL; + return dupstr(hostbuf); +} + +SockAddr *platform_get_x11_unix_address(const char *display, int displaynum) +{ + SockAddr *ret = snew(SockAddr); + memset(ret, 0, sizeof(SockAddr)); + ret->error = "unix sockets not supported on this platform"; + ret->refcount = 1; + return ret; +} diff --git a/windows/no-jump-list.c b/windows/no-jump-list.c new file mode 100644 index 00000000..dd61dc69 --- /dev/null +++ b/windows/no-jump-list.c @@ -0,0 +1,8 @@ +/* + * 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) {} diff --git a/windows/nohelp.c b/windows/nohelp.c new file mode 100644 index 00000000..62ddc65c --- /dev/null +++ b/windows/nohelp.c @@ -0,0 +1,15 @@ +/* + * nohelp.c: implement the has_embedded_chm() function for + * applications that have no help file at all, so that misc.c's + * buildinfo string knows not to talk meaninglessly about whether the + * nonexistent help file is present. + */ + +#include +#include +#include +#include + +#include "putty.h" + +int has_embedded_chm(void) { return -1; } diff --git a/windows/noise.c b/windows/noise.c new file mode 100644 index 00000000..65c4c92d --- /dev/null +++ b/windows/noise.c @@ -0,0 +1,142 @@ +/* + * Noise generation for PuTTY's cryptographic random number + * generator. + */ + +#include + +#include "putty.h" +#include "ssh.h" +#include "storage.h" + +#include + +DECL_WINDOWS_FUNCTION(static, BOOL, CryptAcquireContextA, + (HCRYPTPROV *, LPCTSTR, LPCTSTR, DWORD, DWORD)); +DECL_WINDOWS_FUNCTION(static, BOOL, CryptGenRandom, + (HCRYPTPROV, DWORD, BYTE *)); +DECL_WINDOWS_FUNCTION(static, BOOL, CryptReleaseContext, + (HCRYPTPROV, DWORD)); +static HMODULE wincrypt_module = NULL; + +bool win_read_random(void *buf, unsigned wanted) +{ + bool toret = false; + HCRYPTPROV crypt_provider; + + if (!wincrypt_module) { + wincrypt_module = load_system32_dll("advapi32.dll"); + GET_WINDOWS_FUNCTION(wincrypt_module, CryptAcquireContextA); + GET_WINDOWS_FUNCTION(wincrypt_module, CryptGenRandom); + GET_WINDOWS_FUNCTION(wincrypt_module, CryptReleaseContext); + } + + if (wincrypt_module && p_CryptAcquireContextA && + p_CryptGenRandom && p_CryptReleaseContext && + p_CryptAcquireContextA(&crypt_provider, NULL, NULL, PROV_RSA_FULL, + CRYPT_VERIFYCONTEXT)) { + toret = p_CryptGenRandom(crypt_provider, wanted, buf); + p_CryptReleaseContext(crypt_provider, 0); + } + + return toret; +} + +/* + * This function is called once, at PuTTY startup. + */ + +void noise_get_heavy(void (*func) (void *, int)) +{ + HANDLE srch; + WIN32_FIND_DATA finddata; + DWORD pid; + char winpath[MAX_PATH + 3]; + BYTE buf[32]; + + 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)); + + if (win_read_random(buf, sizeof(buf))) { + func(buf, sizeof(buf)); + smemclr(buf, sizeof(buf)); + } + + read_random_seed(func); +} + +/* + * 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(NOISE_SOURCE_FGWINDOW, &w, sizeof(w)); + w = GetCapture(); + random_add_noise(NOISE_SOURCE_CAPTURE, &w, sizeof(w)); + w = GetClipboardOwner(); + random_add_noise(NOISE_SOURCE_CLIPBOARD, &w, sizeof(w)); + z = GetQueueStatus(QS_ALLEVENTS); + random_add_noise(NOISE_SOURCE_QUEUE, &z, sizeof(z)); + + GetCursorPos(&pt); + random_add_noise(NOISE_SOURCE_CURSORPOS, &pt, sizeof(pt)); + + GlobalMemoryStatus(&memstat); + random_add_noise(NOISE_SOURCE_MEMINFO, &memstat, sizeof(memstat)); + + GetThreadTimes(GetCurrentThread(), times, times + 1, times + 2, + times + 3); + random_add_noise(NOISE_SOURCE_THREADTIME, ×, sizeof(times)); + GetProcessTimes(GetCurrentProcess(), times, times + 1, times + 2, + times + 3); + random_add_noise(NOISE_SOURCE_PROCTIME, ×, 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(NoiseSourceId id, unsigned long data) +{ + DWORD wintime; + LARGE_INTEGER perftime; + + random_add_noise(id, &data, sizeof(DWORD)); + + wintime = GetTickCount(); + random_add_noise(NOISE_SOURCE_TIME, &wintime, sizeof(DWORD)); + + if (QueryPerformanceCounter(&perftime)) + random_add_noise(NOISE_SOURCE_PERFCOUNT, &perftime, sizeof(perftime)); +} + +uint64_t prng_reseed_time_ms(void) +{ + FILETIME ft; + GetSystemTimeAsFileTime(&ft); + uint64_t value = ft.dwHighDateTime; + value = (value << 32) + ft.dwLowDateTime; + return value / 10000; /* 1 millisecond / 100ns */ +} diff --git a/windows/pageant.c b/windows/pageant.c new file mode 100644 index 00000000..9159a5aa --- /dev/null +++ b/windows/pageant.c @@ -0,0 +1,1696 @@ +/* + * Pageant: the PuTTY Authentication Agent. + */ + +#include +#include +#include +#include +#include +#include + +#include "putty.h" +#include "ssh.h" +#include "misc.h" +#include "tree234.h" +#include "security-api.h" +#include "cryptoapi.h" +#include "pageant.h" +#include "licence.h" +#include "pageant-rc.h" + +#include + +#include +#ifdef DEBUG_IPC +#define _WIN32_WINNT 0x0500 /* for ConvertSidToStringSid */ +#include +#endif + +#define WM_SYSTRAY (WM_APP + 6) +#define WM_SYSTRAY2 (WM_APP + 7) + +#define AGENT_COPYDATA_ID 0x804e50ba /* random goop */ + +#define APPNAME "Pageant" + +/* Titles and class names for invisible windows. IPCWINTITLE and + * IPCCLASSNAME are critical to backwards compatibility: WM_COPYDATA + * based Pageant clients will call FindWindow with those parameters + * and expect to find the Pageant IPC receiver. */ +#define TRAYWINTITLE "Pageant" +#define TRAYCLASSNAME "PageantSysTray" +#define IPCWINTITLE "Pageant" +#define IPCCLASSNAME "Pageant" + +static HWND traywindow; +static HWND keylist; +static HWND aboutbox; +static HMENU systray_menu, session_menu; +static bool already_running; +static FingerprintType fptype = SSH_FPTYPE_DEFAULT; + +static char *putty_path; +static bool restrict_putty_acl = false; + +/* CWD for "add key" file requester. */ +static filereq *keypath = NULL; + +/* 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_ADDKEY_ENCRYPTED 0x0040 +#define IDM_REMOVE_ALL 0x0050 +#define IDM_REENCRYPT_ALL 0x0060 +#define IDM_HELP 0x0070 +#define IDM_ABOUT 0x0080 +#define IDM_PUTTY 0x0090 +#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(const char *fmt, ...) +{ + va_list ap; + char *buf; + + va_start(ap, fmt); + buf = dupvprintf(fmt, ap); + va_end(ap); + MessageBox(traywindow, buf, "Pageant Fatal Error", + MB_SYSTEMMODAL | MB_ICONERROR | MB_OK); + sfree(buf); + exit(1); +} + +static bool has_security; + +struct PassphraseProcStruct { + bool modal; + const char *help_topic; + PageantClientDialogId *dlgid; + char *passphrase; + const char *comment; +}; + +/* + * Dialog-box function for the Licence box. + */ +static INT_PTR CALLBACK LicenceProc(HWND hwnd, UINT msg, + WPARAM wParam, LPARAM lParam) +{ + switch (msg) { + case WM_INITDIALOG: + SetDlgItemText(hwnd, IDC_LICENCE_TEXTBOX, LICENCE_TEXT("\r\n\r\n")); + 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_PTR CALLBACK AboutProc(HWND hwnd, UINT msg, + WPARAM wParam, LPARAM lParam) +{ + switch (msg) { + case WM_INITDIALOG: { + char *buildinfo_text = buildinfo("\r\n"); + char *text = dupprintf + ("Pageant\r\n\r\n%s\r\n\r\n%s\r\n\r\n%s", + ver, buildinfo_text, + "\251 " SHORT_COPYRIGHT_DETAILS ". All rights reserved."); + sfree(buildinfo_text); + SetDlgItemText(hwnd, IDC_ABOUT_TEXTBOX, text); + MakeDlgItemBorderless(hwnd, IDC_ABOUT_TEXTBOX); + sfree(text); + return 1; + } + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDOK: + case IDCANCEL: + aboutbox = NULL; + DestroyWindow(hwnd); + return 0; + case IDC_ABOUT_LICENCE: + EnableWindow(hwnd, 0); + DialogBox(hinst, MAKEINTRESOURCE(IDD_LICENCE), hwnd, LicenceProc); + EnableWindow(hwnd, 1); + SetActiveWindow(hwnd); + return 0; + case IDC_ABOUT_WEBSITE: + /* Load web browser */ + ShellExecute(hwnd, "open", + "https://www.chiark.greenend.org.uk/~sgtatham/putty/", + 0, 0, SW_SHOWDEFAULT); + return 0; + } + return 0; + case WM_CLOSE: + aboutbox = NULL; + DestroyWindow(hwnd); + return 0; + } + return 0; +} + +static HWND modal_passphrase_hwnd = NULL; +static HWND nonmodal_passphrase_hwnd = NULL; + +static void end_passphrase_dialog(HWND hwnd, INT_PTR result) +{ + struct PassphraseProcStruct *p = (struct PassphraseProcStruct *) + GetWindowLongPtr(hwnd, GWLP_USERDATA); + + if (p->modal) { + EndDialog(hwnd, result); + } else { + /* + * Destroy this passphrase dialog box before passing the + * results back to pageant.c, to avoid re-entrancy issues. + * + * If we successfully got a passphrase from the user, but it + * was _wrong_, then pageant_passphrase_request_success will + * respond by calling back - synchronously - to our + * ask_passphrase() implementation, which will expect the + * previous value of nonmodal_passphrase_hwnd to have already + * been cleaned up. + */ + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR) NULL); + DestroyWindow(hwnd); + nonmodal_passphrase_hwnd = NULL; + + if (result) + pageant_passphrase_request_success( + p->dlgid, ptrlen_from_asciz(p->passphrase)); + else + pageant_passphrase_request_refused(p->dlgid); + + burnstr(p->passphrase); + sfree(p); + } +} + +/* + * Dialog-box function for the passphrase box. + */ +static INT_PTR CALLBACK PassphraseProc(HWND hwnd, UINT msg, + WPARAM wParam, LPARAM lParam) +{ + struct PassphraseProcStruct *p; + + if (msg == WM_INITDIALOG) { + p = (struct PassphraseProcStruct *) lParam; + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR) p); + } else { + p = (struct PassphraseProcStruct *) + GetWindowLongPtr(hwnd, GWLP_USERDATA); + } + + switch (msg) { + case WM_INITDIALOG: { + if (p->modal) + modal_passphrase_hwnd = hwnd; + + /* + * 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); + if (!p->modal) + SetActiveWindow(hwnd); /* this won't have happened automatically */ + if (p->comment) + SetDlgItemText(hwnd, IDC_PASSPHRASE_FINGERPRINT, p->comment); + burnstr(p->passphrase); + p->passphrase = dupstr(""); + SetDlgItemText(hwnd, IDC_PASSPHRASE_EDITBOX, p->passphrase); + if (!p->help_topic || !has_help()) { + HWND item = GetDlgItem(hwnd, IDHELP); + if (item) + DestroyWindow(item); + } + return 0; + } + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDOK: + if (p->passphrase) + end_passphrase_dialog(hwnd, 1); + else + MessageBeep(0); + return 0; + case IDCANCEL: + end_passphrase_dialog(hwnd, 0); + return 0; + case IDHELP: + if (p->help_topic) + launch_help(hwnd, p->help_topic); + return 0; + case IDC_PASSPHRASE_EDITBOX: + if ((HIWORD(wParam) == EN_CHANGE) && p->passphrase) { + burnstr(p->passphrase); + p->passphrase = GetDlgItemText_alloc( + hwnd, IDC_PASSPHRASE_EDITBOX); + } + return 0; + } + return 0; + case WM_CLOSE: + end_passphrase_dialog(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); +} + +struct keylist_update_ctx { + bool enable_remove_controls; + bool enable_reencrypt_controls; +}; + +static void keylist_update_callback( + void *vctx, char **fingerprints, const char *comment, uint32_t ext_flags, + struct pageant_pubkey *key) +{ + struct keylist_update_ctx *ctx = (struct keylist_update_ctx *)vctx; + FingerprintType this_type = ssh2_pick_fingerprint(fingerprints, fptype); + const char *fingerprint = fingerprints[this_type]; + strbuf *listentry = strbuf_new(); + + /* There is at least one key, so the controls for removing keys + * should be enabled */ + ctx->enable_remove_controls = true; + + switch (key->ssh_version) { + case 1: { + strbuf_catf(listentry, "ssh1\t%s\t%s", fingerprint, comment); + + /* + * Replace the space in the fingerprint (between bit count and + * hash) with a tab, for nice alignment in the box. + */ + char *p = strchr(listentry->s, ' '); + if (p) + *p = '\t'; + break; + } + + case 2: { + /* + * For nice alignment in the list box, we would ideally want + * every entry to align to the tab stop settings, and have a + * column for algorithm name, one for bit count, one for hex + * fingerprint, and one for key comment. + * + * Unfortunately, some of the algorithm names are so long that + * they overflow into the bit-count field. Fortunately, at the + * moment, those are _precisely_ the algorithm names that + * don't need a bit count displayed anyway (because for + * NIST-style ECDSA the bit count is mentioned in the + * algorithm name, and for ssh-ed25519 there is only one + * possible value anyway). So we fudge this by simply omitting + * the bit count field in that situation. + * + * This is fragile not only in the face of further key types + * that don't follow this pattern, but also in the face of + * font metrics changes - the Windows semantics for list box + * tab stops is that \t aligns to the next one you haven't + * already exceeded, so I have to guess when the key type will + * overflow past the bit-count tab stop and leave out a tab + * character. Urgh. + */ + BinarySource src[1]; + BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(key->blob)); + ptrlen algname = get_string(src); + const ssh_keyalg *alg = find_pubkey_alg_len(algname); + + bool include_bit_count = (alg == &ssh_dsa && alg == &ssh_rsa); + + int wordnumber = 0; + for (const char *p = fingerprint; *p; p++) { + char c = *p; + if (c == ' ') { + if (wordnumber < 2) + c = '\t'; + wordnumber++; + } + if (include_bit_count || wordnumber != 1) + put_byte(listentry, c); + } + + strbuf_catf(listentry, "\t%s", comment); + break; + } + } + + if (ext_flags & LIST_EXTENDED_FLAG_HAS_NO_CLEARTEXT_KEY) { + strbuf_catf(listentry, "\t(encrypted)"); + } else if (ext_flags & LIST_EXTENDED_FLAG_HAS_ENCRYPTED_KEY_FILE) { + strbuf_catf(listentry, "\t(re-encryptable)"); + + /* At least one key can be re-encrypted */ + ctx->enable_reencrypt_controls = true; + } + + SendDlgItemMessage(keylist, IDC_KEYLIST_LISTBOX, + LB_ADDSTRING, 0, (LPARAM)listentry->s); + strbuf_free(listentry); +} + +/* + * Update the visible key list. + */ +void keylist_update(void) +{ + if (keylist) { + SendDlgItemMessage(keylist, IDC_KEYLIST_LISTBOX, + LB_RESETCONTENT, 0, 0); + + char *errmsg; + struct keylist_update_ctx ctx[1]; + ctx->enable_remove_controls = false; + ctx->enable_reencrypt_controls = false; + int status = pageant_enum_keys(keylist_update_callback, ctx, &errmsg); + assert(status == PAGEANT_ACTION_OK); + assert(!errmsg); + + SendDlgItemMessage(keylist, IDC_KEYLIST_LISTBOX, + LB_SETCURSEL, (WPARAM) - 1, 0); + + EnableWindow(GetDlgItem(keylist, IDC_KEYLIST_REMOVE), + ctx->enable_remove_controls); + EnableWindow(GetDlgItem(keylist, IDC_KEYLIST_REENCRYPT), + ctx->enable_reencrypt_controls); + } +} + +static void win_add_keyfile(Filename *filename, bool encrypted) +{ + char *err; + int ret; + + /* + * Try loading the key without a passphrase. (Or rather, without a + * _new_ passphrase; pageant_add_keyfile will take care of trying + * all the passphrases we've already stored.) + */ + ret = pageant_add_keyfile(filename, NULL, &err, encrypted); + if (ret == PAGEANT_ACTION_OK) { + goto done; + } else if (ret == PAGEANT_ACTION_FAILURE) { + goto error; + } + + /* + * OK, a passphrase is needed, and we've been given the key + * comment to use in the passphrase prompt. + */ + while (1) { + INT_PTR dlgret; + struct PassphraseProcStruct pps; + pps.modal = true; + pps.help_topic = NULL; /* this dialog has no help button */ + pps.dlgid = NULL; + pps.passphrase = NULL; + pps.comment = err; + dlgret = DialogBoxParam( + hinst, MAKEINTRESOURCE(IDD_LOAD_PASSPHRASE), + NULL, PassphraseProc, (LPARAM) &pps); + modal_passphrase_hwnd = NULL; + + if (!dlgret) { + burnstr(pps.passphrase); + goto done; /* operation cancelled */ + } + + sfree(err); + + assert(pps.passphrase != NULL); + + ret = pageant_add_keyfile(filename, pps.passphrase, &err, false); + burnstr(pps.passphrase); + + if (ret == PAGEANT_ACTION_OK) { + goto done; + } else if (ret == PAGEANT_ACTION_FAILURE) { + goto error; + } + } + + error: + message_box(traywindow, err, APPNAME, MB_OK | MB_ICONERROR, + HELPCTXID(errors_cantloadkey)); + done: + sfree(err); + return; +} + +/* + * Prompt for a key file to add, and add it. + */ +static void prompt_add_keyfile(bool encrypted) +{ + OPENFILENAME of; + char *filelist = snewn(8192, char); + + if (!keypath) keypath = filereq_new(); + memset(&of, 0, sizeof(of)); + of.hwndOwner = traywindow; + 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? */ + Filename *fn = filename_from_str(filelist); + win_add_keyfile(fn, encrypted); + filename_free(fn); + } else { + /* we are returned a bunch of strings, end to + * end. first string is the directory, the + * rest the filenames. terminated with an + * empty string. + */ + char *dir = filelist; + char *filewalker = filelist + strlen(dir) + 1; + while (*filewalker != '\0') { + char *filename = dupcat(dir, "\\", filewalker); + Filename *fn = filename_from_str(filename); + win_add_keyfile(fn, encrypted); + filename_free(fn); + sfree(filename); + filewalker += strlen(filewalker) + 1; + } + } + + keylist_update(); + pageant_forget_passphrases(); + } + sfree(filelist); +} + +/* + * Dialog-box function for the key list box. + */ +static INT_PTR CALLBACK KeyListProc(HWND hwnd, UINT msg, + WPARAM wParam, LPARAM lParam) +{ + static const struct { + const char *name; + FingerprintType value; + } fptypes[] = { + {"SHA256", SSH_FPTYPE_SHA256}, + {"MD5", SSH_FPTYPE_MD5}, + }; + + switch (msg) { + case WM_INITDIALOG: { + /* + * 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, IDC_KEYLIST_HELP); + if (item) + DestroyWindow(item); + } + + keylist = hwnd; + { + static int tabs[] = { 35, 75, 300 }; + SendDlgItemMessage(hwnd, IDC_KEYLIST_LISTBOX, LB_SETTABSTOPS, + sizeof(tabs) / sizeof(*tabs), + (LPARAM) tabs); + } + + int selection = 0; + for (size_t i = 0; i < lenof(fptypes); i++) { + SendDlgItemMessage(hwnd, IDC_KEYLIST_FPTYPE, CB_ADDSTRING, + 0, (LPARAM)fptypes[i].name); + if (fptype == fptypes[i].value) + selection = (int)i; + } + SendDlgItemMessage(hwnd, IDC_KEYLIST_FPTYPE, + CB_SETCURSEL, 0, selection); + + keylist_update(); + return 0; + } + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDOK: + case IDCANCEL: + keylist = NULL; + DestroyWindow(hwnd); + return 0; + case IDC_KEYLIST_ADDKEY: + case IDC_KEYLIST_ADDKEY_ENC: + if (HIWORD(wParam) == BN_CLICKED || + HIWORD(wParam) == BN_DOUBLECLICKED) { + if (modal_passphrase_hwnd) { + MessageBeep(MB_ICONERROR); + SetForegroundWindow(modal_passphrase_hwnd); + break; + } + prompt_add_keyfile(LOWORD(wParam) == IDC_KEYLIST_ADDKEY_ENC); + } + return 0; + case IDC_KEYLIST_REMOVE: + case IDC_KEYLIST_REENCRYPT: + 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, IDC_KEYLIST_LISTBOX, 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, IDC_KEYLIST_LISTBOX, LB_GETSELITEMS, + numSelected, (WPARAM)selectedArray); + + itemNum = numSelected - 1; + rCount = pageant_count_ssh1_keys(); + sCount = pageant_count_ssh2_keys(); + + /* 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--) { + if (selectedArray[itemNum] == rCount + i) { + switch (LOWORD(wParam)) { + case IDC_KEYLIST_REMOVE: + pageant_delete_nth_ssh2_key(i); + break; + case IDC_KEYLIST_REENCRYPT: + pageant_reencrypt_nth_ssh2_key(i); + break; + } + itemNum--; + } + } + + /* do the same for the rsa keys */ + for (i = rCount - 1; (itemNum >= 0) && (i >= 0); i--) { + if(selectedArray[itemNum] == i) { + switch (LOWORD(wParam)) { + case IDC_KEYLIST_REMOVE: + pageant_delete_nth_ssh1_key(i); + break; + case IDC_KEYLIST_REENCRYPT: + /* SSH-1 keys can't be re-encrypted */ + break; + } + itemNum--; + } + } + + sfree(selectedArray); + keylist_update(); + } + return 0; + case IDC_KEYLIST_HELP: + if (HIWORD(wParam) == BN_CLICKED || + HIWORD(wParam) == BN_DOUBLECLICKED) { + launch_help(hwnd, WINHELP_CTX_pageant_general); + } + return 0; + case IDC_KEYLIST_FPTYPE: + if (HIWORD(wParam) == CBN_SELCHANGE) { + int selection = SendDlgItemMessage( + hwnd, IDC_KEYLIST_FPTYPE, CB_GETCURSEL, 0, 0); + if (selection >= 0 && (size_t)selection < lenof(fptypes)) { + fptype = fptypes[selection].value; + keylist_update(); + } + } + return 0; + } + return 0; + case WM_HELP: { + int id = ((LPHELPINFO)lParam)->iCtrlId; + const char *topic = NULL; + switch (id) { + case IDC_KEYLIST_LISTBOX: + case IDC_KEYLIST_FPTYPE: + case IDC_KEYLIST_FPTYPE_STATIC: + topic = WINHELP_CTX_pageant_keylist; break; + case IDC_KEYLIST_ADDKEY: topic = WINHELP_CTX_pageant_addkey; break; + case IDC_KEYLIST_REMOVE: topic = WINHELP_CTX_pageant_remkey; break; + case IDC_KEYLIST_ADDKEY_ENC: + case IDC_KEYLIST_REENCRYPT: + topic = WINHELP_CTX_pageant_deferred; 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; + strbuf *sb; + + 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; + + sb = strbuf_new(); + while(ERROR_SUCCESS == RegEnumKey(hkey, index_key, buf, MAX_PATH)) { + if(strcmp(buf, PUTTY_DEFAULT) != 0) { + strbuf_clear(sb); + unescape_registry_key(buf, sb); + + 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 = sb->s; + InsertMenuItem(session_menu, index_menu, true, &mii); + index_menu++; + } + index_key++; + } + strbuf_free(sb); + + 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); + } +} + +/* + * Versions of Pageant prior to 0.61 expected this SID on incoming + * communications. For backwards compatibility, and more particularly + * for compatibility with derived works of PuTTY still using the old + * Pageant client code, we accept it as an alternative to the one + * returned from get_user_sid() in winpgntc.c. + */ +PSID get_default_sid(void) +{ + HANDLE proc = NULL; + DWORD sidlen; + PSECURITY_DESCRIPTOR psd = NULL; + PSID sid = NULL, copy = NULL, ret = NULL; + + if ((proc = OpenProcess(MAXIMUM_ALLOWED, false, + GetCurrentProcessId())) == NULL) + goto cleanup; + + if (p_GetSecurityInfo(proc, SE_KERNEL_OBJECT, OWNER_SECURITY_INFORMATION, + &sid, NULL, NULL, NULL, &psd) != ERROR_SUCCESS) + goto cleanup; + + sidlen = GetLengthSid(sid); + + copy = (PSID)smalloc(sidlen); + + if (!CopySid(sidlen, copy, sid)) + goto cleanup; + + /* Success. Move sid into the return value slot, and null it out + * to stop the cleanup code freeing it. */ + ret = copy; + copy = NULL; + + cleanup: + if (proc != NULL) + CloseHandle(proc); + if (psd != NULL) + LocalFree(psd); + if (copy != NULL) + sfree(copy); + + return ret; +} + +struct WmCopydataTransaction { + char *length, *body; + size_t bodysize, bodylen; + HANDLE ev_msg_ready, ev_reply_ready; +} wmct; + +static struct PageantClient wmcpc; + +static void wm_copydata_got_msg(void *vctx) +{ + pageant_handle_msg(&wmcpc, NULL, make_ptrlen(wmct.body, wmct.bodylen)); +} + +static void wm_copydata_got_response( + PageantClient *pc, PageantClientRequestId *reqid, ptrlen response) +{ + if (response.len > wmct.bodysize) { + /* Output would overflow message buffer. Replace with a + * failure message. */ + static const unsigned char failure[] = { SSH_AGENT_FAILURE }; + response = make_ptrlen(failure, lenof(failure)); + assert(response.len <= wmct.bodysize); + } + + PUT_32BIT_MSB_FIRST(wmct.length, response.len); + memcpy(wmct.body, response.ptr, response.len); + + SetEvent(wmct.ev_reply_ready); +} + +static bool ask_passphrase_common(PageantClientDialogId *dlgid, + const char *comment) +{ + /* Pageant core should be serialising requests, so we never expect + * a passphrase prompt to exist already at this point */ + assert(!nonmodal_passphrase_hwnd); + + struct PassphraseProcStruct *pps = snew(struct PassphraseProcStruct); + pps->modal = false; + pps->help_topic = WINHELP_CTX_pageant_deferred; + pps->dlgid = dlgid; + pps->passphrase = NULL; + pps->comment = comment; + + nonmodal_passphrase_hwnd = CreateDialogParam( + hinst, MAKEINTRESOURCE(IDD_ONDEMAND_PASSPHRASE), + NULL, PassphraseProc, (LPARAM)pps); + + /* + * Try to put this passphrase prompt into the foreground. + * + * This will probably not succeed in giving it the actual keyboard + * focus, because Windows is quite opposed to applications being + * able to suddenly steal the focus on their own initiative. + * + * That makes sense in a lot of situations, as a defensive + * measure. If you were about to type a password or other secret + * data into the window you already had focused, and some + * malicious app stole the focus, it might manage to trick you + * into typing your secrets into _it_ instead. + * + * In this case it's possible to regard the same defensive measure + * as counterproductive, because the effect if we _do_ steal focus + * is that you type something into our passphrase prompt that + * isn't the passphrase, and we fail to decrypt the key, and no + * harm is done. Whereas the effect of the user wrongly _assuming_ + * the new passphrase prompt has the focus is much worse: now you + * type your highly secret passphrase into some other window you + * didn't mean to trust with that information - such as the + * agent-forwarded PuTTY in which you just ran an ssh command, + * which the _whole point_ was to avoid telling your passphrase to! + * + * On the other hand, I'm sure _every_ application author can come + * up with an argument for why they think _they_ should be allowed + * to steal the focus. Probably most of them include the claim + * that no harm is done if their application receives data + * intended for something else, and of course that's not always + * true! + * + * In any case, I don't know of anything I can do about it, or + * anything I _should_ do about it if I could. If anyone thinks + * they can improve on all this, patches are welcome. + */ + SetForegroundWindow(nonmodal_passphrase_hwnd); + + return true; +} + +static bool wm_copydata_ask_passphrase( + PageantClient *pc, PageantClientDialogId *dlgid, const char *comment) +{ + return ask_passphrase_common(dlgid, comment); +} + +static const PageantClientVtable wmcpc_vtable = { + .log = NULL, /* no logging in this client */ + .got_response = wm_copydata_got_response, + .ask_passphrase = wm_copydata_ask_passphrase, +}; + +static char *answer_filemapping_message(const char *mapname) +{ + HANDLE maphandle = INVALID_HANDLE_VALUE; + void *mapaddr = NULL; + char *err = NULL; + size_t mapsize; + unsigned msglen; + + PSID mapsid = NULL; + PSID expectedsid = NULL; + PSID expectedsid_bc = NULL; + PSECURITY_DESCRIPTOR psd = NULL; + + wmct.length = wmct.body = NULL; + +#ifdef DEBUG_IPC + debug("mapname = \"%s\"\n", mapname); +#endif + + maphandle = OpenFileMapping(FILE_MAP_ALL_ACCESS, false, mapname); + if (maphandle == NULL || maphandle == INVALID_HANDLE_VALUE) { + err = dupprintf("OpenFileMapping(\"%s\"): %s", + mapname, win_strerror(GetLastError())); + goto cleanup; + } + +#ifdef DEBUG_IPC + debug("maphandle = %p\n", maphandle); +#endif + + if (has_security) { + DWORD retd; + + if ((expectedsid = get_user_sid()) == NULL) { + err = dupstr("unable to get user SID"); + goto cleanup; + } + + if ((expectedsid_bc = get_default_sid()) == NULL) { + err = dupstr("unable to get default SID"); + goto cleanup; + } + + if ((retd = p_GetSecurityInfo( + maphandle, SE_KERNEL_OBJECT, OWNER_SECURITY_INFORMATION, + &mapsid, NULL, NULL, NULL, &psd) != ERROR_SUCCESS)) { + err = dupprintf("unable to get owner of file mapping: " + "GetSecurityInfo returned: %s", + win_strerror(retd)); + goto cleanup; + } + +#ifdef DEBUG_IPC + { + LPTSTR ours, ours2, theirs; + ConvertSidToStringSid(mapsid, &theirs); + ConvertSidToStringSid(expectedsid, &ours); + ConvertSidToStringSid(expectedsid_bc, &ours2); + debug("got sids:\n oursnew=%s\n oursold=%s\n" + " theirs=%s\n", ours, ours2, theirs); + LocalFree(ours); + LocalFree(ours2); + LocalFree(theirs); + } +#endif + + if (!EqualSid(mapsid, expectedsid) && + !EqualSid(mapsid, expectedsid_bc)) { + err = dupstr("wrong owning SID of file mapping"); + goto cleanup; + } + } else + { +#ifdef DEBUG_IPC + debug("security APIs not present\n"); +#endif + } + + mapaddr = MapViewOfFile(maphandle, FILE_MAP_WRITE, 0, 0, 0); + if (!mapaddr) { + err = dupprintf("unable to obtain view of file mapping: %s", + win_strerror(GetLastError())); + goto cleanup; + } + +#ifdef DEBUG_IPC + debug("mapped address = %p\n", mapaddr); +#endif + + { + MEMORY_BASIC_INFORMATION mbi; + size_t mbiSize = VirtualQuery(mapaddr, &mbi, sizeof(mbi)); + if (mbiSize == 0) { + err = dupprintf("unable to query view of file mapping: %s", + win_strerror(GetLastError())); + goto cleanup; + } + if (mbiSize < (offsetof(MEMORY_BASIC_INFORMATION, RegionSize) + + sizeof(mbi.RegionSize))) { + err = dupstr("VirtualQuery returned too little data to get " + "region size"); + goto cleanup; + } + + mapsize = mbi.RegionSize; + } +#ifdef DEBUG_IPC + debug("region size = %"SIZEu"\n", mapsize); +#endif + if (mapsize < 5) { + err = dupstr("mapping smaller than smallest possible request"); + goto cleanup; + } + + wmct.length = (char *)mapaddr; + msglen = GET_32BIT_MSB_FIRST(wmct.length); + +#ifdef DEBUG_IPC + debug("msg length=%08x, msg type=%02x\n", + msglen, (unsigned)((unsigned char *) mapaddr)[4]); +#endif + + wmct.body = wmct.length + 4; + wmct.bodysize = mapsize - 4; + + if (msglen > wmct.bodysize) { + /* Incoming length field is too large. Emit a failure response + * without even trying to handle the request. + * + * (We know this must fit, because we checked mapsize >= 5 + * above.) */ + PUT_32BIT_MSB_FIRST(wmct.length, 1); + *wmct.body = SSH_AGENT_FAILURE; + } else { + wmct.bodylen = msglen; + SetEvent(wmct.ev_msg_ready); + WaitForSingleObject(wmct.ev_reply_ready, INFINITE); + } + + cleanup: + /* expectedsid has the lifetime of the program, so we don't free it */ + sfree(expectedsid_bc); + if (psd) + LocalFree(psd); + if (mapaddr) + UnmapViewOfFile(mapaddr); + if (maphandle != NULL && maphandle != INVALID_HANDLE_VALUE) + CloseHandle(maphandle); + return err; +} + +static void create_keylist_window(void) +{ + if (keylist) + return; + + keylist = CreateDialog(hinst, MAKEINTRESOURCE(IDD_KEYLIST), + NULL, KeyListProc); + ShowWindow(keylist, SW_SHOWNORMAL); +} + +static LRESULT CALLBACK TrayWndProc(HWND hwnd, UINT message, + WPARAM wParam, LPARAM lParam) +{ + static bool 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 = true; + update_sessions(); + SetForegroundWindow(hwnd); + TrackPopupMenu(systray_menu, + TPM_RIGHTALIGN | TPM_BOTTOMALIGN | + TPM_RIGHTBUTTON, + wParam, lParam, 0, hwnd, NULL); + menuinprogress = false; + } + break; + case WM_COMMAND: + case WM_SYSCOMMAND: { + unsigned command = wParam & ~0xF; /* low 4 bits reserved to Windows */ + switch (command) { + case IDM_PUTTY: { + TCHAR cmdline[10]; + cmdline[0] = '\0'; + if (restrict_putty_acl) + strcat(cmdline, "&R"); + + if((INT_PTR)ShellExecute(hwnd, NULL, putty_path, cmdline, + _T(""), SW_SHOW) <= 32) { + MessageBox(NULL, "Unable to execute PuTTY!", + "Error", MB_OK | MB_ICONERROR); + } + break; + } + case IDM_CLOSE: + if (modal_passphrase_hwnd) + SendMessage(modal_passphrase_hwnd, WM_CLOSE, 0, 0); + SendMessage(hwnd, WM_CLOSE, 0, 0); + break; + case IDM_VIEWKEYS: + create_keylist_window(); + /* + * 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: + case IDM_ADDKEY_ENCRYPTED: + if (modal_passphrase_hwnd) { + MessageBeep(MB_ICONERROR); + SetForegroundWindow(modal_passphrase_hwnd); + break; + } + prompt_add_keyfile(command == IDM_ADDKEY_ENCRYPTED); + break; + case IDM_REMOVE_ALL: + pageant_delete_all(); + keylist_update(); + break; + case IDM_REENCRYPT_ALL: + pageant_reencrypt_all(); + keylist_update(); + break; + case IDM_ABOUT: + if (!aboutbox) { + aboutbox = CreateDialog(hinst, MAKEINTRESOURCE(IDD_ABOUT), + 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); + param[0] = '\0'; + if (restrict_putty_acl) + strcat(param, "&R"); + strcat(param, "@"); + strcat(param, mii.dwTypeData); + if((INT_PTR)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; + } + + return DefWindowProc(hwnd, message, wParam, lParam); +} + +static LRESULT CALLBACK wm_copydata_WndProc(HWND hwnd, UINT message, + WPARAM wParam, LPARAM lParam) +{ + switch (message) { + case WM_COPYDATA: { + COPYDATASTRUCT *cds; + char *mapname, *err; + + 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! */ + err = answer_filemapping_message(mapname); + if (err) { +#ifdef DEBUG_IPC + debug("IPC failed: %s\n", err); +#endif + sfree(err); + return 0; + } + return 1; + } + } + + return DefWindowProc(hwnd, message, wParam, lParam); +} + +static DWORD WINAPI wm_copydata_threadfunc(void *param) +{ + HINSTANCE inst = *(HINSTANCE *)param; + + HWND ipchwnd = CreateWindow(IPCCLASSNAME, IPCWINTITLE, + WS_OVERLAPPEDWINDOW | WS_VSCROLL, + CW_USEDEFAULT, CW_USEDEFAULT, + 100, 100, NULL, NULL, inst, NULL); + ShowWindow(ipchwnd, SW_HIDE); + + MSG msg; + while (GetMessage(&msg, NULL, 0, 0) == 1) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + return 0; +} + +/* + * Fork and Exec the command in cmdline. [DBW] + */ +void spawn_cmd(const char *cmdline, const char *args, int show) +{ + if (ShellExecute(NULL, _T("open"), cmdline, + args, NULL, show) <= (HINSTANCE) 32) { + char *msg; + msg = dupprintf("Failed to run \"%s\": %s", cmdline, + win_strerror(GetLastError())); + MessageBox(NULL, msg, APPNAME, MB_OK | MB_ICONEXCLAMATION); + sfree(msg); + } +} + +void noise_ultralight(NoiseSourceId id, unsigned long data) +{ + /* Pageant doesn't use random numbers, so we ignore this */ +} + +void cleanup_exit(int code) +{ + shutdown_help(); + exit(code); +} + +static bool winpgnt_listener_ask_passphrase( + PageantListenerClient *plc, PageantClientDialogId *dlgid, + const char *comment) +{ + return ask_passphrase_common(dlgid, comment); +} + +struct winpgnt_client { + PageantListenerClient plc; +}; +static const PageantListenerClientVtable winpgnt_vtable = { + .log = NULL, /* no logging */ + .ask_passphrase = winpgnt_listener_ask_passphrase, +}; + +static struct winpgnt_client wpc[1]; + +HINSTANCE hinst; + +int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) +{ + MSG msg; + const char *command = NULL; + bool added_keys = false; + bool show_keylist_on_startup = false; + int argc, i; + char **argv, **argstart; + + dll_hijacking_protection(); + + hinst = inst; + + /* + * Determine whether we're an NT system (should have security + * APIs) or a non-NT system (don't do security). + */ + init_winver(); + has_security = (osPlatformId == VER_PLATFORM_WIN32_NT); + + if (has_security) { + /* + * Attempt to get the security API we need. + */ + if (!got_advapi()) { + MessageBox(NULL, + "Unable to access security APIs. Pageant will\n" + "not run, in case it causes a security breach.", + "Pageant Fatal Error", MB_ICONERROR | MB_OK); + return 1; + } + } + + /* + * 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 the cross-platform Pageant code. + */ + if (!already_running) { + pageant_init(); + } + + /* + * Process the command line and add keys as listed on it. + */ + split_into_argv(cmdline, &argc, &argv, &argstart); + bool doing_opts = true; + bool add_keys_encrypted = false; + for (i = 0; i < argc; i++) { + char *p = argv[i]; + if (*p == '-' && doing_opts) { + if (!strcmp(p, "-pgpfp")) { + pgp_fingerprints_msgbox(NULL); + return 1; + } else if (!strcmp(p, "-restrict-acl") || + !strcmp(p, "-restrict_acl") || + !strcmp(p, "-restrictacl")) { + restrict_process_acl(); + } else if (!strcmp(p, "-restrict-putty-acl") || + !strcmp(p, "-restrict_putty_acl")) { + restrict_putty_acl = true; + } else if (!strcmp(p, "--no-decrypt") || + !strcmp(p, "-no-decrypt") || + !strcmp(p, "--no_decrypt") || + !strcmp(p, "-no_decrypt") || + !strcmp(p, "--nodecrypt") || + !strcmp(p, "-nodecrypt") || + !strcmp(p, "--encrypted") || + !strcmp(p, "-encrypted")) { + add_keys_encrypted = true; + } else if (!strcmp(p, "-keylist") || !strcmp(p, "--keylist")) { + show_keylist_on_startup = true; + } else if (!strcmp(p, "-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 if (!strcmp(p, "--")) { + doing_opts = false; + } else { + char *msg = dupprintf("unrecognised command-line option\n" + "'%s'", p); + MessageBox(NULL, msg, "Pageant command-line syntax error", + MB_ICONERROR | MB_OK); + exit(1); + } + } else { + Filename *fn = filename_from_str(p); + win_add_keyfile(fn, add_keys_encrypted); + filename_free(fn); + added_keys = true; + } + } + + /* + * Forget any passphrase that we retained while going over + * command line keyfiles. + */ + pageant_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); + } + return 0; + } + + /* + * Set up a named-pipe listener. + */ + { + Plug *pl_plug; + wpc->plc.vt = &winpgnt_vtable; + wpc->plc.suppress_logging = true; + struct pageant_listen_state *pl = + pageant_listener_new(&pl_plug, &wpc->plc); + char *pipename = agent_named_pipe_name(); + Socket *sock = new_named_pipe_listener(pipename, pl_plug); + if (sk_socket_error(sock)) { + char *err = dupprintf("Unable to open named pipe at %s " + "for SSH agent:\n%s", pipename, + sk_socket_error(sock)); + MessageBox(NULL, err, "Pageant Error", MB_ICONERROR | MB_OK); + return 1; + } + pageant_listener_got_socket(pl, sock); + sfree(pipename); + } + + /* + * Set up window classes for two hidden windows: one that receives + * all the messages to do with our presence in the system tray, + * and one that receives the WM_COPYDATA message used by the + * old-style Pageant IPC system. + */ + + if (!prev) { + WNDCLASS wndclass; + + memset(&wndclass, 0, sizeof(wndclass)); + wndclass.lpfnWndProc = TrayWndProc; + wndclass.hInstance = inst; + wndclass.hIcon = LoadIcon(inst, MAKEINTRESOURCE(IDI_MAINICON)); + wndclass.lpszClassName = TRAYCLASSNAME; + + RegisterClass(&wndclass); + + memset(&wndclass, 0, sizeof(wndclass)); + wndclass.lpfnWndProc = wm_copydata_WndProc; + wndclass.hInstance = inst; + wndclass.lpszClassName = IPCCLASSNAME; + + RegisterClass(&wndclass); + } + + keylist = NULL; + + traywindow = CreateWindow(TRAYCLASSNAME, TRAYWINTITLE, + WS_OVERLAPPEDWINDOW | WS_VSCROLL, + CW_USEDEFAULT, CW_USEDEFAULT, + 100, 100, NULL, NULL, inst, NULL); + winselgui_set_hwnd(traywindow); + + /* Set up a system tray icon */ + AddTrayIcon(traywindow); + + /* 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_PTR) 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_ENABLED, IDM_ADDKEY_ENCRYPTED, + "Add key (encrypted)"); + AppendMenu(systray_menu, MF_SEPARATOR, 0, 0); + AppendMenu(systray_menu, MF_ENABLED, IDM_REMOVE_ALL, + "Remove All Keys"); + AppendMenu(systray_menu, MF_ENABLED, IDM_REENCRYPT_ALL, + "Re-encrypt All Keys"); + 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(traywindow, SW_HIDE); + + wmcpc.vt = &wmcpc_vtable; + wmcpc.suppress_logging = true; + pageant_register_client(&wmcpc); + DWORD wm_copydata_threadid; + wmct.ev_msg_ready = CreateEvent(NULL, false, false, NULL); + wmct.ev_reply_ready = CreateEvent(NULL, false, false, NULL); + CreateThread(NULL, 0, wm_copydata_threadfunc, + &inst, 0, &wm_copydata_threadid); + handle_add_foreign_event(wmct.ev_msg_ready, wm_copydata_got_msg, NULL); + + if (show_keylist_on_startup) + create_keylist_window(); + + /* + * Main message loop. + */ + while (true) { + 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); + } else + sfree(handles); + + while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) { + if (msg.message == WM_QUIT) + goto finished; /* two-level break */ + + if (IsWindow(keylist) && IsDialogMessage(keylist, &msg)) + continue; + if (IsWindow(aboutbox) && IsDialogMessage(aboutbox, &msg)) + continue; + if (IsWindow(nonmodal_passphrase_hwnd) && + IsDialogMessage(nonmodal_passphrase_hwnd, &msg)) + continue; + + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + run_toplevel_callbacks(); + } + finished: + + /* Clean up the system tray icon */ + { + NOTIFYICONDATA tnid; + + tnid.cbSize = sizeof(NOTIFYICONDATA); + tnid.hWnd = traywindow; + tnid.uID = 1; + + Shell_NotifyIcon(NIM_DELETE, &tnid); + + DestroyMenu(systray_menu); + } + + if (keypath) filereq_free(keypath); + + cleanup_exit(msg.wParam); + return msg.wParam; /* just in case optimiser complains */ +} diff --git a/windows/pageant.rc b/windows/pageant.rc index a4a15195..5bea40e7 100644 --- a/windows/pageant.rc +++ b/windows/pageant.rc @@ -9,7 +9,7 @@ #include "pageant-rc.h" -#include "winhelp.rc2" +#include "help.rc2" IDI_MAINICON ICON "pageant.ico" IDI_TRAYICON ICON "pageants.ico" diff --git a/windows/platform.h b/windows/platform.h index 0011ed24..f84f4236 100644 --- a/windows/platform.h +++ b/windows/platform.h @@ -25,7 +25,7 @@ #include "tree234.h" -#include "winhelp.h" +#include "help.h" #if defined _M_IX86 || defined _M_AMD64 #define BUILDINFO_PLATFORM "x86 Windows" diff --git a/windows/plink.c b/windows/plink.c new file mode 100644 index 00000000..3166178f --- /dev/null +++ b/windows/plink.c @@ -0,0 +1,533 @@ +/* + * PLink - a Windows command-line (stdin/stdout) variant of PuTTY. + */ + +#include +#include +#include +#include + +#include "putty.h" +#include "storage.h" +#include "tree234.h" +#include "security-api.h" + +void cmdline_error(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + console_print_error_msg_fmt_v("plink", fmt, ap); + va_end(ap); + exit(1); +} + +static HANDLE inhandle, outhandle, errhandle; +static struct handle *stdin_handle, *stdout_handle, *stderr_handle; +static handle_sink stdout_hs, stderr_hs; +static StripCtrlChars *stdout_scc, *stderr_scc; +static BinarySink *stdout_bs, *stderr_bs; +static DWORD orig_console_mode; + +static Backend *backend; +static LogContext *logctx; +static Conf *conf; + +static void plink_echoedit_update(Seat *seat, bool echo, bool 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); +} + +static size_t plink_output( + Seat *seat, bool is_stderr, const void *data, size_t len) +{ + BinarySink *bs = is_stderr ? stderr_bs : stdout_bs; + put_data(bs, data, len); + + return handle_backlog(stdout_handle) + handle_backlog(stderr_handle); +} + +static bool plink_eof(Seat *seat) +{ + handle_write_eof(stdout_handle); + return false; /* do not respond to incoming EOF with outgoing */ +} + +static int plink_get_userpass_input(Seat *seat, prompts_t *p, bufchain *input) +{ + int ret; + ret = cmdline_get_passwd_input(p); + if (ret == -1) + ret = console_get_userpass_input(p); + return ret; +} + +static bool plink_seat_interactive(Seat *seat) +{ + return (!*conf_get_str(conf, CONF_remote_cmd) && + !*conf_get_str(conf, CONF_remote_cmd2) && + !*conf_get_str(conf, CONF_ssh_nc_host)); +} + +static const SeatVtable plink_seat_vt = { + .output = plink_output, + .eof = plink_eof, + .get_userpass_input = plink_get_userpass_input, + .notify_remote_exit = nullseat_notify_remote_exit, + .connection_fatal = console_connection_fatal, + .update_specials_menu = nullseat_update_specials_menu, + .get_ttymode = nullseat_get_ttymode, + .set_busy_status = nullseat_set_busy_status, + .verify_ssh_host_key = console_verify_ssh_host_key, + .confirm_weak_crypto_primitive = console_confirm_weak_crypto_primitive, + .confirm_weak_cached_hostkey = console_confirm_weak_cached_hostkey, + .is_utf8 = nullseat_is_never_utf8, + .echoedit_update = plink_echoedit_update, + .get_x_display = nullseat_get_x_display, + .get_windowid = nullseat_get_windowid, + .get_window_pixel_size = nullseat_get_window_pixel_size, + .stripctrl_new = console_stripctrl_new, + .set_trust_status = console_set_trust_status, + .verbose = cmdline_seat_verbose, + .interactive = plink_seat_interactive, + .get_cursor_position = nullseat_get_cursor_position, +}; +static Seat plink_seat[1] = {{ &plink_seat_vt }}; + +static DWORD main_thread_id; + +/* + * Short description of parameters. + */ +static void usage(void) +{ + printf("Plink: 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(" -ssh-connection\n"); + printf(" force use of the bare ssh-connection 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(" -proxycmd command\n"); + printf(" use 'command' as local proxy\n"); + printf(" -sercfg configuration-string (e.g. 19200,8,n,1,X)\n"); + printf(" Specify the serial configuration (serial only)\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 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 user authentication\n"); + printf(" -noagent disable use of Pageant\n"); + printf(" -agent enable use of Pageant\n"); + printf(" -noshare disable use of connection sharing\n"); + printf(" -share enable use of connection sharing\n"); + printf(" -hostkey keyid\n"); + printf(" manually specify a host key (may be repeated)\n"); + printf(" -sanitise-stderr, -sanitise-stdout, " + "-no-sanitise-stderr, -no-sanitise-stdout\n"); + printf(" do/don't strip control chars from standard " + "output/error\n"); + printf(" -no-antispoof omit anti-spoofing prompt after " + "authentication\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(" -sshlog file\n"); + printf(" -sshrawlog file\n"); + printf(" log protocol details to a file\n"); + printf(" -logoverwrite\n"); + printf(" -logappend\n"); + printf(" control what happens when a log file already exists\n"); + printf(" -shareexists\n"); + printf(" test whether a connection-sharing upstream exists\n"); + exit(1); +} + +static void version(void) +{ + char *buildinfo_text = buildinfo("\n"); + printf("plink: %s\n%s\n", ver, buildinfo_text); + sfree(buildinfo_text); + exit(0); +} + +size_t stdin_gotdata(struct handle *h, const void *data, size_t len, int err) +{ + if (err) { + char buf[4096]; + FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, err, 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(NOISE_SOURCE_IOLEN, len); + if (backend_connected(backend)) { + if (len > 0) { + return backend_send(backend, data, len); + } else { + backend_special(backend, SS_EOF, 0); + return 0; + } + } else + return 0; +} + +void stdouterr_sent(struct handle *h, size_t new_backlog, int err) +{ + if (err) { + char buf[4096]; + FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, err, 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 (backend_connected(backend)) { + backend_unthrottle(backend, (handle_backlog(stdout_handle) + + handle_backlog(stderr_handle))); + } +} + +const bool share_can_be_downstream = true; +const bool share_can_be_upstream = true; + +const unsigned cmdline_tooltype = + TOOLTYPE_HOST_ARG | + TOOLTYPE_HOST_ARG_CAN_BE_SESSION | + TOOLTYPE_HOST_ARG_PROTOCOL_PREFIX | + TOOLTYPE_HOST_ARG_FROM_LAUNCHABLE_LOAD; + +static bool sending; + +static bool plink_mainloop_pre(void *vctx, const HANDLE **extra_handles, + size_t *n_extra_handles) +{ + if (!sending && backend_sendok(backend)) { + stdin_handle = handle_input_new(inhandle, stdin_gotdata, NULL, + 0); + sending = true; + } + + return true; +} + +static bool plink_mainloop_post(void *vctx, size_t extra_handle_index) +{ + if (sending) + handle_unthrottle(stdin_handle, backend_sendbuffer(backend)); + + if (!backend_connected(backend) && + handle_backlog(stdout_handle) + handle_backlog(stderr_handle) == 0) + return false; /* we closed the connection */ + + return true; +} + +int main(int argc, char **argv) +{ + int exitcode; + bool errors; + bool use_subsystem = false; + bool just_test_share_exists = false; + enum TriState sanitise_stdout = AUTO, sanitise_stderr = AUTO; + const struct BackendVtable *vt; + + dll_hijacking_protection(); + + /* + * Initialise port and protocol to sensible defaults. (These + * will be overridden by more or less anything.) + */ + settings_set_default_protocol(PROT_SSH); + settings_set_default_port(22); + + /* + * Process the command line. + */ + conf = conf_new(); + do_defaults(NULL, conf); + settings_set_default_protocol(conf_get_int(conf, CONF_protocol)); + settings_set_default_port(conf_get_int(conf, CONF_port)); + errors = false; + { + /* + * Override the default protocol if PLINK_PROTOCOL is set. + */ + char *p = getenv("PLINK_PROTOCOL"); + if (p) { + const struct BackendVtable *vt = backend_vt_from_name(p); + if (vt) { + settings_set_default_protocol(vt->protocol); + settings_set_default_port(vt->default_port); + conf_set_int(conf, CONF_protocol, vt->protocol); + conf_set_int(conf, CONF_port, vt->default_port); + } + } + } + while (--argc) { + char *p = *++argv; + int ret = cmdline_process_param(p, (argc > 1 ? argv[1] : NULL), + 1, conf); + if (ret == -2) { + fprintf(stderr, + "plink: option \"%s\" requires an argument\n", p); + errors = true; + } else if (ret == 2) { + --argc, ++argv; + } else if (ret == 1) { + continue; + } else if (!strcmp(p, "-batch")) { + console_batch_mode = true; + } else if (!strcmp(p, "-s")) { + /* Save status to write to conf later. */ + use_subsystem = true; + } else if (!strcmp(p, "-V") || !strcmp(p, "--version")) { + version(); + } else if (!strcmp(p, "--help")) { + usage(); + } else if (!strcmp(p, "-pgpfp")) { + pgp_fingerprints(); + exit(1); + } else if (!strcmp(p, "-shareexists")) { + just_test_share_exists = true; + } else if (!strcmp(p, "-sanitise-stdout") || + !strcmp(p, "-sanitize-stdout")) { + sanitise_stdout = FORCE_ON; + } else if (!strcmp(p, "-no-sanitise-stdout") || + !strcmp(p, "-no-sanitize-stdout")) { + sanitise_stdout = FORCE_OFF; + } else if (!strcmp(p, "-sanitise-stderr") || + !strcmp(p, "-sanitize-stderr")) { + sanitise_stderr = FORCE_ON; + } else if (!strcmp(p, "-no-sanitise-stderr") || + !strcmp(p, "-no-sanitize-stderr")) { + sanitise_stderr = FORCE_OFF; + } else if (!strcmp(p, "-no-antispoof")) { + console_antispoof_prompt = false; + } else if (*p != '-') { + strbuf *cmdbuf = strbuf_new(); + + while (argc > 0) { + if (cmdbuf->len > 0) + put_byte(cmdbuf, ' '); /* add space separator */ + put_datapl(cmdbuf, ptrlen_from_asciz(p)); + if (--argc > 0) + p = *++argv; + } + + conf_set_str(conf, CONF_remote_cmd, cmdbuf->s); + conf_set_str(conf, CONF_remote_cmd2, ""); + conf_set_bool(conf, CONF_nopty, true); /* command => no tty */ + + strbuf_free(cmdbuf); + break; /* done with cmdline */ + } else { + fprintf(stderr, "plink: unknown option \"%s\"\n", p); + errors = true; + } + } + + if (errors) + return 1; + + if (!cmdline_host_ok(conf)) { + usage(); + } + + prepare_session(conf); + + /* + * Perform command-line overrides on session configuration. + */ + cmdline_run_saved(conf); + + /* + * Apply subsystem status. + */ + if (use_subsystem) + conf_set_bool(conf, CONF_ssh_subsys, true); + + /* + * Select protocol. This is farmed out into a table in a + * separate file to enable an ssh-free variant. + */ + vt = backend_vt_from_proto(conf_get_int(conf, CONF_protocol)); + if (vt == NULL) { + fprintf(stderr, + "Internal fault: Unsupported protocol found\n"); + return 1; + } + + if (vt->flags & BACKEND_NEEDS_TERMINAL) { + fprintf(stderr, + "Plink doesn't support %s, which needs terminal emulation\n", + vt->displayname); + return 1; + } + + sk_init(); + if (p_WSAEventSelect == NULL) { + fprintf(stderr, "Plink requires WinSock 2\n"); + return 1; + } + + /* + * 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 (conf_get_int(conf, CONF_protocol) == PROT_SSH && + !conf_get_bool(conf, CONF_x11_forward) && + !conf_get_bool(conf, CONF_agentfwd) && + !conf_get_str_nthstrkey(conf, CONF_portfwd, 0)) + conf_set_bool(conf, CONF_ssh_simple, true); + + logctx = log_init(console_cli_logpolicy, conf); + + if (just_test_share_exists) { + if (!vt->test_for_upstream) { + fprintf(stderr, "Connection sharing not supported for this " + "connection type (%s)'\n", vt->displayname); + return 1; + } + if (vt->test_for_upstream(conf_get_str(conf, CONF_host), + conf_get_int(conf, CONF_port), conf)) + return 0; + else + return 1; + } + + if (restricted_acl()) { + lp_eventlog(console_cli_logpolicy, + "Running with restricted process ACL"); + } + + 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); + handle_sink_init(&stdout_hs, stdout_handle); + handle_sink_init(&stderr_hs, stderr_handle); + stdout_bs = BinarySink_UPCAST(&stdout_hs); + stderr_bs = BinarySink_UPCAST(&stderr_hs); + + /* + * Decide whether to sanitise control sequences out of standard + * output and standard error. + * + * If we weren't given a command-line override, we do this if (a) + * the fd in question is pointing at a console, and (b) we aren't + * trying to allocate a terminal as part of the session. + * + * (Rationale: the risk of control sequences is that they cause + * confusion when sent to a local console, so if there isn't one, + * no problem. Also, if we allocate a remote terminal, then we + * sent a terminal type, i.e. we told it what kind of escape + * sequences we _like_, i.e. we were expecting to receive some.) + */ + if (sanitise_stdout == FORCE_ON || + (sanitise_stdout == AUTO && is_console_handle(outhandle) && + conf_get_bool(conf, CONF_nopty))) { + stdout_scc = stripctrl_new(stdout_bs, true, L'\0'); + stdout_bs = BinarySink_UPCAST(stdout_scc); + } + if (sanitise_stderr == FORCE_ON || + (sanitise_stderr == AUTO && is_console_handle(errhandle) && + conf_get_bool(conf, CONF_nopty))) { + stderr_scc = stripctrl_new(stderr_bs, true, L'\0'); + stderr_bs = BinarySink_UPCAST(stderr_scc); + } + + /* + * Start up the connection. + */ + winselcli_setup(); /* ensure event object exists */ + { + char *error, *realhost; + /* nodelay is only useful if stdin is a character device (console) */ + bool nodelay = conf_get_bool(conf, CONF_tcp_nodelay) && + (GetFileType(GetStdHandle(STD_INPUT_HANDLE)) == FILE_TYPE_CHAR); + + error = backend_init(vt, plink_seat, &backend, logctx, conf, + conf_get_str(conf, CONF_host), + conf_get_int(conf, CONF_port), + &realhost, nodelay, + conf_get_bool(conf, CONF_tcp_keepalives)); + if (error) { + fprintf(stderr, "Unable to open connection:\n%s", error); + sfree(error); + return 1; + } + ldisc_create(conf, NULL, backend, plink_seat); + sfree(realhost); + } + + main_thread_id = GetCurrentThreadId(); + + sending = false; + + cli_main_loop(plink_mainloop_pre, plink_mainloop_post, NULL); + + exitcode = backend_exitcode(backend); + 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 */ +} diff --git a/windows/printing.c b/windows/printing.c new file mode 100644 index 00000000..e6b3531d --- /dev/null +++ b/windows/printing.c @@ -0,0 +1,224 @@ +/* + * Printing interface for PuTTY. + */ + +#include "putty.h" +#include + +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; +}; + +DECL_WINDOWS_FUNCTION(static, BOOL, EnumPrinters, + (DWORD, LPTSTR, DWORD, LPBYTE, DWORD, LPDWORD, LPDWORD)); +DECL_WINDOWS_FUNCTION(static, BOOL, OpenPrinter, + (LPTSTR, LPHANDLE, LPPRINTER_DEFAULTS)); +DECL_WINDOWS_FUNCTION(static, BOOL, ClosePrinter, (HANDLE)); +DECL_WINDOWS_FUNCTION(static, DWORD, StartDocPrinter, (HANDLE, DWORD, LPBYTE)); +DECL_WINDOWS_FUNCTION(static, BOOL, EndDocPrinter, (HANDLE)); +DECL_WINDOWS_FUNCTION(static, BOOL, StartPagePrinter, (HANDLE)); +DECL_WINDOWS_FUNCTION(static, BOOL, EndPagePrinter, (HANDLE)); +DECL_WINDOWS_FUNCTION(static, BOOL, WritePrinter, + (HANDLE, LPVOID, DWORD, LPDWORD)); + +static void init_winfuncs(void) +{ + static bool initialised = false; + if (initialised) + return; + { + HMODULE winspool_module = load_system32_dll("winspool.drv"); + /* Some MSDN documentation claims that some of the below functions + * should be loaded from spoolss.dll, but this doesn't seem to + * be reliable in practice. + * Nevertheless, we load spoolss.dll ourselves using our safe + * loading method, against the possibility that winspool.drv + * later loads it unsafely. */ + (void) load_system32_dll("spoolss.dll"); + GET_WINDOWS_FUNCTION_PP(winspool_module, EnumPrinters); + GET_WINDOWS_FUNCTION_PP(winspool_module, OpenPrinter); + GET_WINDOWS_FUNCTION_PP(winspool_module, ClosePrinter); + GET_WINDOWS_FUNCTION_PP(winspool_module, StartDocPrinter); + GET_WINDOWS_FUNCTION_PP(winspool_module, EndDocPrinter); + GET_WINDOWS_FUNCTION_PP(winspool_module, StartPagePrinter); + GET_WINDOWS_FUNCTION_PP(winspool_module, EndPagePrinter); + GET_WINDOWS_FUNCTION_PP(winspool_module, WritePrinter); + } + initialised = true; +} + +static bool printer_add_enum(int param, DWORD level, char **buffer, + int offset, int *nprinters_ptr) +{ + DWORD needed = 0, nprinters = 0; + + init_winfuncs(); + + *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. + */ + p_EnumPrinters(param, NULL, level, (LPBYTE)((*buffer)+offset), 512, + &needed, &nprinters); + + if (needed < 512) + needed = 512; + + *buffer = sresize(*buffer, offset+needed, char); + + if (p_EnumPrinters(param, NULL, level, (LPBYTE)((*buffer)+offset), + needed, &needed, &nprinters) == 0) + return false; + + *nprinters_ptr += nprinters; + + return true; +} + +printer_enum *printer_start_enum(int *nprinters_ptr) +{ + printer_enum *ret = snew(printer_enum); + char *buffer = NULL; + + *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 (osPlatformId != VER_PLATFORM_WIN32_NT) { + ret->enum_level = 5; + } else { + ret->enum_level = 4; + } + + if (!printer_add_enum(PRINTER_ENUM_LOCAL | PRINTER_ENUM_CONNECTIONS, + ret->enum_level, &buffer, 0, nprinters_ptr)) + goto error; + + 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; + bool jobstarted = false, pagestarted = false; + + init_winfuncs(); + + ret->hprinter = NULL; + if (!p_OpenPrinter(printer, &ret->hprinter, NULL)) + goto error; + + docinfo.pDocName = "PuTTY remote printer output"; + docinfo.pOutputFile = NULL; + docinfo.pDatatype = "RAW"; + + if (!p_StartDocPrinter(ret->hprinter, 1, (LPBYTE)&docinfo)) + goto error; + jobstarted = true; + + if (!p_StartPagePrinter(ret->hprinter)) + goto error; + pagestarted = true; + + return ret; + + error: + if (pagestarted) + p_EndPagePrinter(ret->hprinter); + if (jobstarted) + p_EndDocPrinter(ret->hprinter); + if (ret->hprinter) + p_ClosePrinter(ret->hprinter); + sfree(ret); + return NULL; +} + +void printer_job_data(printer_job *pj, const void *data, size_t len) +{ + DWORD written; + + if (!pj) + return; + + p_WritePrinter(pj->hprinter, (void *)data, len, &written); +} + +void printer_finish_job(printer_job *pj) +{ + if (!pj) + return; + + p_EndPagePrinter(pj->hprinter); + p_EndDocPrinter(pj->hprinter); + p_ClosePrinter(pj->hprinter); + sfree(pj); +} diff --git a/windows/psocks.c b/windows/psocks.c new file mode 100644 index 00000000..83ba364c --- /dev/null +++ b/windows/psocks.c @@ -0,0 +1,24 @@ +/* + * Main program for Windows psocks. + */ + +#include "putty.h" +#include "ssh.h" +#include "psocks.h" + +static const PsocksPlatform platform = { + NULL /* open_pipes */, + NULL /* start_subcommand */, +}; + +int main(int argc, char **argv) +{ + psocks_state *ps = psocks_new(&platform); + psocks_cmdline(ps, argc, argv); + + sk_init(); + winselcli_setup(); + psocks_start(ps); + + cli_main_loop(cliloop_null_pre, cliloop_null_post, NULL); +} diff --git a/windows/putty-common.rc2 b/windows/putty-common.rc2 new file mode 100644 index 00000000..79b79ea1 --- /dev/null +++ b/windows/putty-common.rc2 @@ -0,0 +1,137 @@ +/* + * 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. + * + * This file has the more or less arbitrary extension '.rc2' to avoid + * IDEs taking it to be a top-level resource script in its own right + * (which has been known to happen if the extension was '.rc'), and + * also to avoid the resource compiler ignoring everything included + * from it (which happens if the extension is '.h'). + */ + +#include "putty-rc.h" + +IDI_MAINICON ICON "putty.ico" + +IDI_CFGICON ICON "puttycfg.ico" + +/* Accelerators used: clw */ +IDD_ABOUTBOX DIALOG DISCARDABLE 140, 40, 270, 136 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "About PuTTY" +FONT 8, "MS Shell Dlg" +BEGIN + DEFPUSHBUTTON "&Close", IDOK, 216, 118, 48, 14 + PUSHBUTTON "View &Licence", IDA_LICENCE, 6, 118, 70, 14 + PUSHBUTTON "Visit &Web Site", IDA_WEB, 140, 118, 70, 14 + EDITTEXT IDA_TEXT, 10, 6, 250, 110, ES_READONLY | ES_MULTILINE | ES_CENTER, WS_EX_STATICEDGE +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, 326, 239 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "PuTTY Licence" +FONT 8, "MS Shell Dlg" +BEGIN + DEFPUSHBUTTON "OK", IDOK, 148, 219, 44, 14 + + EDITTEXT IDA_TEXT, 10, 10, 306, 200, ES_READONLY | ES_MULTILINE | ES_LEFT, WS_EX_STATICEDGE +END + +/* No accelerators used */ +IDD_HK_ABSENT DIALOG DISCARDABLE 50, 50, 340, 148 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "PuTTY Security Alert" +FONT 8, "MS Shell Dlg" +BEGIN + LTEXT "The server's host key is not cached in the registry. You have no", 100, 40, 20, 300, 8 + LTEXT "guarantee that the server is the computer you think it is.", 101, 40, 28, 300, 8 + LTEXT "The server's {KEYTYPE} key fingerprint is:", 102, 40, 40, 300, 8 + LTEXT "If you trust this host, press ""Accept"" to add the key to {APPNAME}'s", 103, 40, 60, 300, 8 + LTEXT "cache and carry on connecting.", 104, 40, 68, 300, 8 + LTEXT "If you want to carry on connecting just once, without adding the key", 105, 40, 80, 300, 8 + LTEXT "to the cache, press ""Connect Once"".", 106, 40, 88, 300, 8 + LTEXT "If you do not trust this host, press ""Cancel"" to abandon the connection.", 107, 40, 100, 300, 8 + + ICON "", IDC_HK_ICON, 10, 18, 0, 0 + + PUSHBUTTON "Cancel", IDCANCEL, 288, 128, 40, 14 + PUSHBUTTON "Accept", IDC_HK_ACCEPT, 168, 128, 40, 14 + PUSHBUTTON "Connect Once", IDC_HK_ONCE, 216, 128, 64, 14 + PUSHBUTTON "More info...", IDC_HK_MOREINFO, 60, 128, 64, 14 + PUSHBUTTON "Help", IDHELP, 12, 128, 40, 14 + + EDITTEXT IDC_HK_FINGERPRINT, 40, 48, 300, 12, ES_READONLY | ES_LEFT, 0 +END + +/* No accelerators used */ +IDD_HK_WRONG DIALOG DISCARDABLE 50, 50, 340, 188 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "PuTTY Security Alert" +FONT 8, "MS Shell Dlg" +BEGIN + LTEXT "WARNING - POTENTIAL SECURITY BREACH!", IDC_HK_TITLE, 40, 20, 300, 12 + + LTEXT "The server's host key does not match the one {APPNAME} has cached in", 100, 40, 36, 300, 8 + LTEXT "the registry. This means that either the server administrator has", 101, 40, 44, 300, 8 + LTEXT "changed the host key, or you have actually connected to another", 102, 40, 52, 300, 8 + LTEXT "computer pretending to be the server.", 103, 40, 60, 300, 8 + LTEXT "The new {KEYTYPE} key fingerprint is:", 104, 40, 72, 300, 8 + LTEXT "If you were expecting this change and trust the new key, press", 105, 40, 92, 300, 8 + LTEXT """Accept"" to update {APPNAME}'s cache and continue connecting.", 106, 40, 100, 300, 8 + LTEXT "If you want to carry on connecting but without updating the cache,", 107, 40, 112, 300, 8 + LTEXT "press ""Connect Once"".", 108, 40, 120, 300, 8 + LTEXT "If you want to abandon the connection completely, press ""Cancel"".", 109, 40, 132, 300, 8 + LTEXT "Pressing ""Cancel"" is the ONLY guaranteed safe choice.", 110, 40, 140, 300, 8 + + ICON "", IDC_HK_ICON, 10, 16, 0, 0 + + PUSHBUTTON "Cancel", IDCANCEL, 288, 168, 40, 14 + PUSHBUTTON "Accept", IDC_HK_ACCEPT, 168, 168, 40, 14 + PUSHBUTTON "Connect Once", IDC_HK_ONCE, 216, 168, 64, 14 + PUSHBUTTON "More info...", IDC_HK_MOREINFO, 60, 168, 64, 14 + PUSHBUTTON "Help", IDHELP, 12, 168, 40, 14 + + EDITTEXT IDC_HK_FINGERPRINT, 40, 80, 300, 12, ES_READONLY | ES_LEFT, 0 +END + +/* Accelerators used: clw */ +IDD_HK_MOREINFO DIALOG DISCARDABLE 140, 40, 400, 156 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "PuTTY: information about the server's host key" +FONT 8, "MS Shell Dlg" +BEGIN + LTEXT "SHA256 fingerprint:", 100, 12, 12, 80, 8 + EDITTEXT IDC_HKI_SHA256, 100, 10, 288, 12, ES_READONLY + LTEXT "MD5 fingerprint:", 101, 12, 28, 80, 8 + EDITTEXT IDC_HKI_MD5, 100, 26, 288, 12, ES_READONLY + LTEXT "Full public key:", 102, 12, 44, 376, 8 + EDITTEXT IDC_HKI_PUBKEY, 12, 54, 376, 64, ES_READONLY | ES_MULTILINE | ES_LEFT | ES_AUTOVSCROLL, WS_EX_STATICEDGE + DEFPUSHBUTTON "&Close", IDOK, 176, 130, 48, 14 +END + +#include "version.rc2" diff --git a/windows/putty-rc.h b/windows/putty-rc.h new file mode 100644 index 00000000..cdfae94a --- /dev/null +++ b/windows/putty-rc.h @@ -0,0 +1,49 @@ +/* + * putty-rc.h - constants shared between putty-common.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 IDD_HK_ABSENT 114 +#define IDD_HK_WRONG 115 +#define IDD_HK_MOREINFO 116 + +#define IDN_LIST 1001 +#define IDN_COPY 1002 + +#define IDA_ICON 1001 +#define IDA_TEXT 1002 +#define IDA_LICENCE 1003 +#define IDA_WEB 1004 + +#define IDC_TAB 1001 +#define IDC_TABSTATIC1 1002 +#define IDC_TABSTATIC2 1003 +#define IDC_TABLIST 1004 +#define IDC_HELPBTN 1005 +#define IDC_ABOUT 1006 + +#define IDC_HK_ICON 98 +#define IDC_HK_TITLE 99 +#define IDC_HK_ACCEPT 1001 +#define IDC_HK_ONCE 1000 +#define IDC_HK_FINGERPRINT 1002 +#define IDC_HK_MOREINFO 1003 + +#define IDC_HKI_SHA256 1000 +#define IDC_HKI_MD5 1001 +#define IDC_HKI_PUBKEY 1002 + +#define ID_CUSTOM_CHMFILE 2000 +#define TYPE_CUSTOM_CHMFILE 2000 + +#endif diff --git a/windows/putty.rc b/windows/putty.rc index 08969abf..887342c3 100644 --- a/windows/putty.rc +++ b/windows/putty.rc @@ -3,8 +3,8 @@ #define APPNAME "PuTTY" #define APPDESC "SSH, Telnet, Rlogin, and SUPDUP client" -#include "winhelp.rc2" -#include "win_res.rc2" +#include "help.rc2" +#include "putty-common.rc2" #ifndef NO_MANIFESTS 1 RT_MANIFEST "putty.mft" diff --git a/windows/puttygen.c b/windows/puttygen.c new file mode 100644 index 00000000..cf98b6fe --- /dev/null +++ b/windows/puttygen.c @@ -0,0 +1,2024 @@ +/* + * PuTTY key generation front end (Windows). + */ + +#include +#include +#include +#include + +#include "putty.h" +#include "ssh.h" +#include "sshkeygen.h" +#include "licence.h" +#include "security-api.h" +#include "puttygen-rc.h" + +#include + +#ifdef MSVC4 +#define ICON_BIG 1 +#endif + +#define WM_DONEKEY (WM_APP + 1) + +#define DEFAULT_KEY_BITS 2048 +#define DEFAULT_ECCURVE_INDEX 0 +#define DEFAULT_EDCURVE_INDEX 0 + +static char *cmdline_keyfile = NULL; + +/* + * Print a modal (Really Bad) message box and perform a fatal exit. + */ +void modalfatalbox(const 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); +} + +/* + * Print a non-fatal message box and do not exit. + */ +void nonfatal(const char *fmt, ...) +{ + va_list ap; + char *stuff; + + va_start(ap, fmt); + stuff = dupvprintf(fmt, ap); + va_end(ap); + MessageBox(NULL, stuff, "PuTTYgen Error", + MB_SYSTEMMODAL | MB_ICONERROR | MB_OK); + sfree(stuff); +} + +/* ---------------------------------------------------------------------- + * ProgressReceiver implementation. + */ + +#define PROGRESSRANGE 65535 + +struct progressphase { + double startpoint, total; + /* For exponential phases */ + double exp_probability, exp_current_value; +}; + +struct progress { + size_t nphases, phasessize; + struct progressphase *phases, *currphase; + + double scale; + HWND progbar; + + ProgressReceiver rec; +}; + +static ProgressPhase win_progress_add_linear( + ProgressReceiver *prog, double overall_cost) { + struct progress *p = container_of(prog, struct progress, rec); + + sgrowarray(p->phases, p->phasessize, p->nphases); + int phase = p->nphases++; + + p->phases[phase].total = overall_cost; + + ProgressPhase ph = { .n = phase }; + return ph; +} + +static ProgressPhase win_progress_add_probabilistic( + ProgressReceiver *prog, double cost_per_attempt, double probability) { + struct progress *p = container_of(prog, struct progress, rec); + + sgrowarray(p->phases, p->phasessize, p->nphases); + int phase = p->nphases++; + + p->phases[phase].exp_probability = 1.0 - probability; + p->phases[phase].exp_current_value = 1.0; + /* Expected number of attempts = 1 / probability of attempt succeeding */ + p->phases[phase].total = cost_per_attempt / probability; + + ProgressPhase ph = { .n = phase }; + return ph; +} + +static void win_progress_ready(ProgressReceiver *prog) +{ + struct progress *p = container_of(prog, struct progress, rec); + + double total = 0; + for (int i = 0; i < p->nphases; i++) { + p->phases[i].startpoint = total; + total += p->phases[i].total; + } + p->scale = PROGRESSRANGE / total; + + SendMessage(p->progbar, PBM_SETRANGE, 0, MAKELPARAM(0, PROGRESSRANGE)); +} + +static void win_progress_start_phase(ProgressReceiver *prog, + ProgressPhase phase) +{ + struct progress *p = container_of(prog, struct progress, rec); + + assert(phase.n < p->nphases); + p->currphase = &p->phases[phase.n]; +} + +static void win_progress_update(struct progress *p, double phasepos) +{ + double position = (p->currphase->startpoint + + p->currphase->total * phasepos); + position *= p->scale; + if (position < 0) + position = 0; + if (position > PROGRESSRANGE) + position = PROGRESSRANGE; + + SendMessage(p->progbar, PBM_SETPOS, (WPARAM)position, 0); +} + +static void win_progress_report(ProgressReceiver *prog, double progress) +{ + struct progress *p = container_of(prog, struct progress, rec); + + win_progress_update(p, progress); +} + +static void win_progress_report_attempt(ProgressReceiver *prog) +{ + struct progress *p = container_of(prog, struct progress, rec); + + p->currphase->exp_current_value *= p->currphase->exp_probability; + win_progress_update(p, 1.0 - p->currphase->exp_current_value); +} + +static void win_progress_report_phase_complete(ProgressReceiver *prog) +{ + struct progress *p = container_of(prog, struct progress, rec); + + win_progress_update(p, 1.0); +} + +static const ProgressReceiverVtable win_progress_vt = { + .add_linear = win_progress_add_linear, + .add_probabilistic = win_progress_add_probabilistic, + .ready = win_progress_ready, + .start_phase = win_progress_start_phase, + .report = win_progress_report, + .report_attempt = win_progress_report_attempt, + .report_phase_complete = win_progress_report_phase_complete, +}; + +static void win_progress_initialise(struct progress *p) +{ + p->nphases = p->phasessize = 0; + p->phases = p->currphase = NULL; + p->rec.vt = &win_progress_vt; +} + +static void win_progress_cleanup(struct progress *p) +{ + sfree(p->phases); +} + +struct PassphraseProcStruct { + char **passphrase; + char *comment; +}; + +/* + * Dialog-box function for the passphrase box. + */ +static INT_PTR 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); + burnstr(*passphrase); + *passphrase = dupstr(""); + 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) { + burnstr(*passphrase); + *passphrase = GetDlgItemText_alloc(hwnd, 102); + } + return 0; + } + return 0; + case WM_CLOSE: + EndDialog(hwnd, 0); + return 0; + } + return 0; +} + +static void try_get_dlg_item_uint32(HWND hwnd, int id, uint32_t *out) +{ + char buf[128]; + if (!GetDlgItemText(hwnd, id, buf, sizeof(buf))) + return; + + if (!*buf) + return; + + char *end; + unsigned long val = strtoul(buf, &end, 10); + if (*end) + return; + + if ((val >> 16) >> 16) + return; + + *out = val; +} + +static ppk_save_parameters save_params; + +struct PPKParams { + ppk_save_parameters params; + uint32_t time_passes, time_ms; +}; + +/* + * Dialog-box function for the passphrase box. + */ +static INT_PTR CALLBACK PPKParamsProc(HWND hwnd, UINT msg, + WPARAM wParam, LPARAM lParam) +{ + struct PPKParams *pp; + char *buf; + + if (msg == WM_INITDIALOG) { + pp = (struct PPKParams *)lParam; + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)pp); + } else { + pp = (struct PPKParams *)GetWindowLongPtr(hwnd, GWLP_USERDATA); + } + + switch (msg) { + case WM_INITDIALOG: + SetForegroundWindow(hwnd); + SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW); + + if (has_help()) + SetWindowLongPtr(hwnd, GWL_EXSTYLE, + GetWindowLongPtr(hwnd, GWL_EXSTYLE) | + WS_EX_CONTEXTHELP); + + /* + * 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); + } + + CheckRadioButton(hwnd, IDC_PPKVER_2, IDC_PPKVER_3, + IDC_PPKVER_2 + (pp->params.fmt_version - 2)); + + CheckRadioButton( + hwnd, IDC_KDF_ARGON2ID, IDC_KDF_ARGON2D, + (pp->params.argon2_flavour == Argon2id ? IDC_KDF_ARGON2ID : + pp->params.argon2_flavour == Argon2i ? IDC_KDF_ARGON2I : + /* pp->params.argon2_flavour == Argon2d ? */ IDC_KDF_ARGON2D)); + + buf = dupprintf("%"PRIu32, pp->params.argon2_mem); + SetDlgItemText(hwnd, IDC_ARGON2_MEM, buf); + sfree(buf); + + if (pp->params.argon2_passes_auto) { + CheckRadioButton(hwnd, IDC_PPK_AUTO_YES, IDC_PPK_AUTO_NO, + IDC_PPK_AUTO_YES); + buf = dupprintf("%"PRIu32, pp->time_ms); + SetDlgItemText(hwnd, IDC_ARGON2_TIME, buf); + sfree(buf); + } else { + CheckRadioButton(hwnd, IDC_PPK_AUTO_YES, IDC_PPK_AUTO_NO, + IDC_PPK_AUTO_NO); + buf = dupprintf("%"PRIu32, pp->time_passes); + SetDlgItemText(hwnd, IDC_ARGON2_TIME, buf); + sfree(buf); + } + + buf = dupprintf("%"PRIu32, pp->params.argon2_parallelism); + SetDlgItemText(hwnd, IDC_ARGON2_PARALLEL, buf); + sfree(buf); + + return 0; + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDOK: + EndDialog(hwnd, 1); + return 0; + case IDCANCEL: + EndDialog(hwnd, 0); + return 0; + case IDC_PPKVER_2: + pp->params.fmt_version = 2; + return 0; + case IDC_PPKVER_3: + pp->params.fmt_version = 3; + return 0; + case IDC_KDF_ARGON2ID: + pp->params.argon2_flavour = Argon2id; + return 0; + case IDC_KDF_ARGON2I: + pp->params.argon2_flavour = Argon2i; + return 0; + case IDC_KDF_ARGON2D: + pp->params.argon2_flavour = Argon2d; + return 0; + case IDC_ARGON2_MEM: + try_get_dlg_item_uint32(hwnd, IDC_ARGON2_MEM, + &pp->params.argon2_mem); + return 0; + case IDC_PPK_AUTO_YES: + pp->params.argon2_passes_auto = true; + buf = dupprintf("%"PRIu32, pp->time_ms); + SetDlgItemText(hwnd, IDC_ARGON2_TIME, buf); + sfree(buf); + return 0; + case IDC_PPK_AUTO_NO: + pp->params.argon2_passes_auto = false; + buf = dupprintf("%"PRIu32, pp->time_passes); + SetDlgItemText(hwnd, IDC_ARGON2_TIME, buf); + sfree(buf); + return 0; + case IDC_ARGON2_TIME: + try_get_dlg_item_uint32(hwnd, IDC_ARGON2_TIME, + pp->params.argon2_passes_auto ? + &pp->time_ms : &pp->time_passes); + return 0; + case IDC_ARGON2_PARALLEL: + try_get_dlg_item_uint32(hwnd, IDC_ARGON2_PARALLEL, + &pp->params.argon2_parallelism); + return 0; + } + return 0; + case WM_HELP: { + int id = ((LPHELPINFO)lParam)->iCtrlId; + const char *topic = NULL; + switch (id) { + case IDC_PPKVER_STATIC: + case IDC_PPKVER_2: + case IDC_PPKVER_3: + topic = WINHELP_CTX_puttygen_ppkver; break; + case IDC_KDF_STATIC: + case IDC_KDF_ARGON2ID: + case IDC_KDF_ARGON2I: + case IDC_KDF_ARGON2D: + case IDC_ARGON2_MEM_STATIC: + case IDC_ARGON2_MEM: + case IDC_ARGON2_MEM_STATIC2: + case IDC_ARGON2_TIME_STATIC: + case IDC_ARGON2_TIME: + case IDC_PPK_AUTO_YES: + case IDC_PPK_AUTO_NO: + case IDC_ARGON2_PARALLEL_STATIC: + case IDC_ARGON2_PARALLEL: + topic = WINHELP_CTX_puttygen_kdfparam; break; + } + if (topic) { + launch_help(hwnd, topic); + } else { + MessageBeep(0); + } + break; + } + 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 bool prompt_keyfile(HWND hwnd, char *dlgtitle, + char *filename, bool save, bool 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_PTR CALLBACK LicenceProc(HWND hwnd, UINT msg, + WPARAM wParam, LPARAM lParam) +{ + switch (msg) { + case WM_INITDIALOG: { + /* + * 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, 1000, LICENCE_TEXT("\r\n\r\n")); + 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_PTR 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); + } + + { + char *buildinfo_text = buildinfo("\r\n"); + char *text = dupprintf + ("PuTTYgen\r\n\r\n%s\r\n\r\n%s\r\n\r\n%s", + ver, buildinfo_text, + "\251 " SHORT_COPYRIGHT_DETAILS ". All rights reserved."); + sfree(buildinfo_text); + SetDlgItemText(hwnd, 1000, text); + MakeDlgItemBorderless(hwnd, 1000); + sfree(text); + } + 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; + case 102: + /* Load web browser */ + ShellExecute(hwnd, "open", + "https://www.chiark.greenend.org.uk/~sgtatham/putty/", + 0, 0, SW_SHOWDEFAULT); + return 0; + } + return 0; + case WM_CLOSE: + EndDialog(hwnd, 1); + return 0; + } + return 0; +} + +typedef enum {RSA, DSA, ECDSA, EDDSA} keytype; + +/* + * Thread to generate a key. + */ +struct rsa_key_thread_params { + HWND progressbar; /* notify this with progress */ + HWND dialog; /* notify this on completion */ + int key_bits; /* bits in key modulus (RSA, DSA) */ + int curve_bits; /* bits in elliptic curve (ECDSA) */ + keytype keytype; + const PrimeGenerationPolicy *primepolicy; + bool rsa_strong; + union { + RSAKey *key; + struct dsa_key *dsakey; + struct ecdsa_key *eckey; + struct eddsa_key *edkey; + }; +}; +static DWORD WINAPI generate_key_thread(void *param) +{ + struct rsa_key_thread_params *params = + (struct rsa_key_thread_params *) param; + struct progress prog; + prog.progbar = params->progressbar; + + win_progress_initialise(&prog); + + PrimeGenerationContext *pgc = primegen_new_context(params->primepolicy); + + if (params->keytype == DSA) + dsa_generate(params->dsakey, params->key_bits, pgc, &prog.rec); + else if (params->keytype == ECDSA) + ecdsa_generate(params->eckey, params->curve_bits); + else if (params->keytype == EDDSA) + eddsa_generate(params->edkey, params->curve_bits); + else + rsa_generate(params->key, params->key_bits, params->rsa_strong, + pgc, &prog.rec); + + primegen_free_context(pgc); + + PostMessage(params->dialog, WM_DONEKEY, 0, 0); + + win_progress_cleanup(&prog); + + sfree(params); + return 0; +} + +struct MainDlgState { + bool collecting_entropy; + bool generation_thread_exists; + bool key_exists; + int entropy_got, entropy_required, entropy_size; + int key_bits, curve_bits; + bool ssh2; + keytype keytype; + const PrimeGenerationPolicy *primepolicy; + bool rsa_strong; + FingerprintType fptype; + char **commentptr; /* points to key.comment or ssh2key.comment */ + ssh2_userkey ssh2key; + unsigned *entropy; + union { + RSAKey key; + struct dsa_key dsakey; + struct ecdsa_key eckey; + struct eddsa_key edkey; + }; + HMENU filemenu, keymenu, cvtmenu; +}; + +static void hidemany(HWND hwnd, const int *ids, bool hideit) +{ + while (*ids) { + ShowWindow(GetDlgItem(hwnd, *ids++), (hideit ? SW_HIDE : SW_SHOW)); + } +} + +static void setupbigedit1(HWND hwnd, int id, int idstatic, RSAKey *key) +{ + char *buffer = ssh1_pubkey_str(key); + SetDlgItemText(hwnd, id, buffer); + SetDlgItemText(hwnd, idstatic, + "&Public key for pasting into authorized_keys file:"); + sfree(buffer); +} + +static void setupbigedit2(HWND hwnd, int id, int idstatic, + ssh2_userkey *key) +{ + char *buffer = ssh2_pubkey_openssh_str(key); + SetDlgItemText(hwnd, id, buffer); + SetDlgItemText(hwnd, idstatic, "&Public key for pasting into " + "OpenSSH authorized_keys file:"); + sfree(buffer); +} + +/* + * 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); +} + +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_KEYSSH2ECDSA, IDC_KEYSSH2EDDSA, + IDC_PRIMEGEN_PROB, IDC_PRIMEGEN_MAURER_SIMPLE, IDC_PRIMEGEN_MAURER_COMPLEX, + IDC_RSA_STRONG, + IDC_FPTYPE_SHA256, IDC_FPTYPE_MD5, + IDC_PPK_PARAMS, + IDC_BITSSTATIC, IDC_BITS, + IDC_ECCURVESTATIC, IDC_ECCURVE, + IDC_EDCURVESTATIC, IDC_EDCURVE, + IDC_NOTHINGSTATIC, + IDC_ABOUT, + IDC_GIVEHELP, + IDC_IMPORT, + IDC_EXPORT_OPENSSH_AUTO, IDC_EXPORT_OPENSSH_NEW, + 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_KEYSSH2ECDSA), 1); + EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH2EDDSA), 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->keymenu, IDC_KEYSSH2ECDSA, + MF_ENABLED|MF_BYCOMMAND); + EnableMenuItem(state->keymenu, IDC_KEYSSH2EDDSA, + MF_ENABLED|MF_BYCOMMAND); + EnableMenuItem(state->cvtmenu, IDC_IMPORT, MF_ENABLED|MF_BYCOMMAND); + EnableMenuItem(state->cvtmenu, IDC_EXPORT_OPENSSH_AUTO, + MF_GRAYED|MF_BYCOMMAND); + EnableMenuItem(state->cvtmenu, IDC_EXPORT_OPENSSH_NEW, + 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_KEYSSH2ECDSA), 0); + EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH2EDDSA), 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->keymenu, IDC_KEYSSH2ECDSA, + MF_GRAYED|MF_BYCOMMAND); + EnableMenuItem(state->keymenu, IDC_KEYSSH2EDDSA, + MF_GRAYED|MF_BYCOMMAND); + EnableMenuItem(state->cvtmenu, IDC_IMPORT, MF_GRAYED|MF_BYCOMMAND); + EnableMenuItem(state->cvtmenu, IDC_EXPORT_OPENSSH_AUTO, + MF_GRAYED|MF_BYCOMMAND); + EnableMenuItem(state->cvtmenu, IDC_EXPORT_OPENSSH_NEW, + 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_KEYSSH2ECDSA), 1); + EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH2EDDSA), 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->keymenu, IDC_KEYSSH2ECDSA, + MF_ENABLED|MF_BYCOMMAND); + EnableMenuItem(state->keymenu, IDC_KEYSSH2EDDSA, + 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_AUTO, SSH_KEYTYPE_OPENSSH_AUTO); + do_export_menuitem(IDC_EXPORT_OPENSSH_NEW, SSH_KEYTYPE_OPENSSH_NEW); + do_export_menuitem(IDC_EXPORT_SSHCOM, SSH_KEYTYPE_SSHCOM); +#undef do_export_menuitem + break; + } +} + +/* + * Helper functions to set the key type, taking care of keeping the + * menu and radio button selections in sync and also showing/hiding + * the appropriate size/curve control for the current key type. + */ +void ui_update_key_type_ctrls(HWND hwnd) +{ + enum { BITS, ECCURVE, EDCURVE, NOTHING } which; + static const int bits_ids[] = { + IDC_BITSSTATIC, IDC_BITS, 0 + }; + static const int eccurve_ids[] = { + IDC_ECCURVESTATIC, IDC_ECCURVE, 0 + }; + static const int edcurve_ids[] = { + IDC_EDCURVESTATIC, IDC_EDCURVE, 0 + }; + static const int nothing_ids[] = { + IDC_NOTHINGSTATIC, 0 + }; + + if (IsDlgButtonChecked(hwnd, IDC_KEYSSH1) || + IsDlgButtonChecked(hwnd, IDC_KEYSSH2RSA) || + IsDlgButtonChecked(hwnd, IDC_KEYSSH2DSA)) { + which = BITS; + } else if (IsDlgButtonChecked(hwnd, IDC_KEYSSH2ECDSA)) { + which = ECCURVE; + } else if (IsDlgButtonChecked(hwnd, IDC_KEYSSH2EDDSA)) { + which = EDCURVE; + } else { + /* Currently not used since Ed25519 stopped being the only + * thing in its class, but I'll keep it here in case it comes + * in useful again */ + which = NOTHING; + } + + hidemany(hwnd, bits_ids, which != BITS); + hidemany(hwnd, eccurve_ids, which != ECCURVE); + hidemany(hwnd, edcurve_ids, which != EDCURVE); + hidemany(hwnd, nothing_ids, which != NOTHING); +} +void ui_set_key_type(HWND hwnd, struct MainDlgState *state, int button) +{ + CheckRadioButton(hwnd, IDC_KEYSSH1, IDC_KEYSSH2EDDSA, button); + CheckMenuRadioItem(state->keymenu, IDC_KEYSSH1, IDC_KEYSSH2EDDSA, + button, MF_BYCOMMAND); + ui_update_key_type_ctrls(hwnd); +} +void ui_set_primepolicy(HWND hwnd, struct MainDlgState *state, int option) +{ + CheckMenuRadioItem(state->keymenu, IDC_PRIMEGEN_PROB, + IDC_PRIMEGEN_MAURER_COMPLEX, option, MF_BYCOMMAND); + switch (option) { + case IDC_PRIMEGEN_PROB: + state->primepolicy = &primegen_probabilistic; + break; + case IDC_PRIMEGEN_MAURER_SIMPLE: + state->primepolicy = &primegen_provable_maurer_simple; + break; + case IDC_PRIMEGEN_MAURER_COMPLEX: + state->primepolicy = &primegen_provable_maurer_complex; + break; + } +} +void ui_set_rsa_strong(HWND hwnd, struct MainDlgState *state, bool enable) +{ + state->rsa_strong = enable; + CheckMenuItem(state->keymenu, IDC_RSA_STRONG, + (enable ? MF_CHECKED : 0) | MF_BYCOMMAND); +} +static FingerprintType idc_to_fptype(int option) +{ + switch (option) { + case IDC_FPTYPE_SHA256: + return SSH_FPTYPE_SHA256; + case IDC_FPTYPE_MD5: + return SSH_FPTYPE_MD5; + default: + unreachable("bad control id in idc_to_fptype"); + } +} +static int fptype_to_idc(FingerprintType fptype) +{ + switch (fptype) { + case SSH_FPTYPE_SHA256: + return IDC_FPTYPE_SHA256; + case SSH_FPTYPE_MD5: + return IDC_FPTYPE_MD5; + default: + unreachable("bad fptype in fptype_to_idc"); + } +} +void ui_set_fptype(HWND hwnd, struct MainDlgState *state, int option) +{ + CheckMenuRadioItem(state->keymenu, IDC_FPTYPE_SHA256, + IDC_FPTYPE_MD5, option, MF_BYCOMMAND); + + state->fptype = idc_to_fptype(option); + + if (state->key_exists && state->ssh2) { + char *fp = ssh2_fingerprint(state->ssh2key.key, state->fptype); + SetDlgItemText(hwnd, IDC_FINGERPRINT, fp); + sfree(fp); + } +} + +void load_key_file(HWND hwnd, struct MainDlgState *state, + Filename *filename, bool was_import_cmd) +{ + char *passphrase; + bool needs_pass; + int type, realtype; + int ret; + const char *errmsg = NULL; + char *comment; + RSAKey newkey1; + 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(hwnd, 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; + passphrase = NULL; + if (realtype == SSH_KEYTYPE_SSH1) + needs_pass = rsa1_encrypted_f(filename, &comment); + else if (realtype == SSH_KEYTYPE_SSH2) + needs_pass = ppk_encrypted_f(filename, &comment); + else + needs_pass = import_encrypted(filename, realtype, &comment); + do { + burnstr(passphrase); + passphrase = NULL; + + if (needs_pass) { + int dlgret; + struct PassphraseProcStruct pps; + pps.passphrase = &passphrase; + pps.comment = comment; + dlgret = DialogBoxParam(hinst, + MAKEINTRESOURCE(210), + NULL, PassphraseProc, + (LPARAM) &pps); + if (!dlgret) { + ret = -2; + break; + } + assert(passphrase != NULL); + } else + passphrase = dupstr(""); + if (type == SSH_KEYTYPE_SSH1) { + if (realtype == type) + ret = rsa1_load_f(filename, &newkey1, passphrase, &errmsg); + else + ret = import_ssh1(filename, realtype, &newkey1, + passphrase, &errmsg); + } else { + if (realtype == type) + newkey2 = ppk_load_f(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(hwnd, 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 *fingerprint, *savecomment; + + state->ssh2 = false; + state->commentptr = &state->key.comment; + state->key = newkey1; + + /* + * Set the key fingerprint. + */ + savecomment = state->key.comment; + state->key.comment = NULL; + fingerprint = rsa_ssh1_fingerprint(&state->key); + state->key.comment = savecomment; + SetDlgItemText(hwnd, IDC_FINGERPRINT, fingerprint); + sfree(fingerprint); + + /* + * 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 = ssh2_fingerprint(state->ssh2key.key, state->fptype); + 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); + } + } + burnstr(passphrase); +} + +static void start_generating_key(HWND hwnd, struct MainDlgState *state) +{ + static const char generating_msg[] = + "Please wait while a key is generated..."; + + struct rsa_key_thread_params *params; + DWORD threadid; + + 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->key_bits = state->key_bits; + params->curve_bits = state->curve_bits; + params->keytype = state->keytype; + params->primepolicy = state->primepolicy; + params->rsa_strong = state->rsa_strong; + params->key = &state->key; + params->dsakey = &state->dsakey; + + if (!CreateThread(NULL, 0, generate_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; + } +} + +/* + * Dialog-box function for the main PuTTYgen dialog box. + */ +static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, + WPARAM wParam, LPARAM lParam) +{ + 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_PTR) 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(menu1, MF_ENABLED, IDC_KEYSSH2ECDSA, "SSH-2 &ECDSA key"); + AppendMenu(menu1, MF_ENABLED, IDC_KEYSSH2EDDSA, "SSH-2 EdD&SA key"); + AppendMenu(menu1, MF_SEPARATOR, 0, 0); + AppendMenu(menu1, MF_ENABLED, IDC_PRIMEGEN_PROB, + "Use probable primes (fast)"); + AppendMenu(menu1, MF_ENABLED, IDC_PRIMEGEN_MAURER_SIMPLE, + "Use proven primes (slower)"); + AppendMenu(menu1, MF_ENABLED, IDC_PRIMEGEN_MAURER_COMPLEX, + "Use proven primes with even distribution (slowest)"); + AppendMenu(menu1, MF_SEPARATOR, 0, 0); + AppendMenu(menu1, MF_ENABLED, IDC_RSA_STRONG, + "Use \"strong\" primes as RSA key factors"); + AppendMenu(menu1, MF_SEPARATOR, 0, 0); + AppendMenu(menu1, MF_ENABLED, IDC_PPK_PARAMS, + "Parameters for saving key files..."); + AppendMenu(menu1, MF_SEPARATOR, 0, 0); + AppendMenu(menu1, MF_ENABLED, IDC_FPTYPE_SHA256, + "Show fingerprint as SHA256"); + AppendMenu(menu1, MF_ENABLED, IDC_FPTYPE_MD5, + "Show fingerprint as MD5"); + AppendMenu(menu, MF_POPUP | MF_ENABLED, (UINT_PTR) 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_AUTO, + "Export &OpenSSH key"); + AppendMenu(menu1, MF_ENABLED, IDC_EXPORT_OPENSSH_NEW, + "Export &OpenSSH key (force new file format)"); + AppendMenu(menu1, MF_ENABLED, IDC_EXPORT_SSHCOM, + "Export &ssh.com key"); + AppendMenu(menu, MF_POPUP | MF_ENABLED, (UINT_PTR) 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_PTR) 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; + int ymax; + + /* Accelerators used: acglops1rbvde */ + + 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, 82); + SendDlgItemMessage(hwnd, IDC_FINGERPRINT, EM_SETREADONLY, 1, + 0); + staticedit(&cp, "Key &comment:", IDC_COMMENTSTATIC, + IDC_COMMENTEDIT, 82); + staticpassedit(&cp, "Key p&assphrase:", IDC_PASSPHRASE1STATIC, + IDC_PASSPHRASE1EDIT, 82); + staticpassedit(&cp, "C&onfirm passphrase:", + IDC_PASSPHRASE2STATIC, IDC_PASSPHRASE2EDIT, 82); + 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, 5, + "&RSA", IDC_KEYSSH2RSA, + "&DSA", IDC_KEYSSH2DSA, + "&ECDSA", IDC_KEYSSH2ECDSA, + "EdD&SA", IDC_KEYSSH2EDDSA, + "SSH-&1 (RSA)", IDC_KEYSSH1, + NULL); + cp2 = cp; + staticedit(&cp2, "Number of &bits in a generated key:", + IDC_BITSSTATIC, IDC_BITS, 20); + ymax = cp2.ypos; + cp2 = cp; + staticddl(&cp2, "Cur&ve to use for generating this key:", + IDC_ECCURVESTATIC, IDC_ECCURVE, 30); + SendDlgItemMessage(hwnd, IDC_ECCURVE, CB_RESETCONTENT, 0, 0); + { + int i, bits; + const struct ec_curve *curve; + const ssh_keyalg *alg; + + for (i = 0; i < n_ec_nist_curve_lengths; i++) { + bits = ec_nist_curve_lengths[i]; + ec_nist_alg_and_curve_by_bits(bits, &curve, &alg); + SendDlgItemMessage(hwnd, IDC_ECCURVE, CB_ADDSTRING, 0, + (LPARAM)curve->textname); + } + } + ymax = ymax > cp2.ypos ? ymax : cp2.ypos; + cp2 = cp; + staticddl(&cp2, "Cur&ve to use for generating this key:", + IDC_EDCURVESTATIC, IDC_EDCURVE, 30); + SendDlgItemMessage(hwnd, IDC_EDCURVE, CB_RESETCONTENT, 0, 0); + { + int i, bits; + const struct ec_curve *curve; + const ssh_keyalg *alg; + + for (i = 0; i < n_ec_ed_curve_lengths; i++) { + bits = ec_ed_curve_lengths[i]; + ec_ed_alg_and_curve_by_bits(bits, &curve, &alg); + char *desc = dupprintf("%s (%d bits)", + curve->textname, bits); + SendDlgItemMessage(hwnd, IDC_EDCURVE, CB_ADDSTRING, 0, + (LPARAM)desc); + sfree(desc); + } + } + ymax = ymax > cp2.ypos ? ymax : cp2.ypos; + cp2 = cp; + statictext(&cp2, "(nothing to configure for this key type)", + 1, IDC_NOTHINGSTATIC); + ymax = ymax > cp2.ypos ? ymax : cp2.ypos; + cp.ypos = ymax; + endbox(&cp); + } + ui_set_key_type(hwnd, state, IDC_KEYSSH2RSA); + ui_set_primepolicy(hwnd, state, IDC_PRIMEGEN_PROB); + ui_set_rsa_strong(hwnd, state, false); + ui_set_fptype(hwnd, state, fptype_to_idc(SSH_FPTYPE_DEFAULT)); + SetDlgItemInt(hwnd, IDC_BITS, DEFAULT_KEY_BITS, false); + SendDlgItemMessage(hwnd, IDC_ECCURVE, CB_SETCURSEL, + DEFAULT_ECCURVE_INDEX, 0); + SendDlgItemMessage(hwnd, IDC_EDCURVE, CB_SETCURSEL, + DEFAULT_EDCURVE_INDEX, 0); + + /* + * 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) { + Filename *fn = filename_from_str(cmdline_keyfile); + load_key_file(hwnd, state, fn, false); + filename_free(fn); + } + + 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) { + /* + * Seed the entropy pool + */ + random_reseed( + make_ptrlen(state->entropy, state->entropy_size)); + smemclr(state->entropy, state->entropy_size); + sfree(state->entropy); + state->collecting_entropy = false; + + start_generating_key(hwnd, state); + } + } + break; + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDC_KEYSSH1: + case IDC_KEYSSH2RSA: + case IDC_KEYSSH2DSA: + case IDC_KEYSSH2ECDSA: + case IDC_KEYSSH2EDDSA: { + state = (struct MainDlgState *) + GetWindowLongPtr(hwnd, GWLP_USERDATA); + ui_set_key_type(hwnd, state, LOWORD(wParam)); + break; + } + case IDC_PRIMEGEN_PROB: + case IDC_PRIMEGEN_MAURER_SIMPLE: + case IDC_PRIMEGEN_MAURER_COMPLEX: { + state = (struct MainDlgState *) + GetWindowLongPtr(hwnd, GWLP_USERDATA); + ui_set_primepolicy(hwnd, state, LOWORD(wParam)); + break; + } + case IDC_FPTYPE_SHA256: + case IDC_FPTYPE_MD5: { + state = (struct MainDlgState *) + GetWindowLongPtr(hwnd, GWLP_USERDATA); + ui_set_fptype(hwnd, state, LOWORD(wParam)); + break; + } + case IDC_RSA_STRONG: { + state = (struct MainDlgState *) + GetWindowLongPtr(hwnd, GWLP_USERDATA); + ui_set_rsa_strong(hwnd, state, !state->rsa_strong); + break; + } + case IDC_PPK_PARAMS: { + struct PPKParams pp[1]; + pp->params = save_params; + if (pp->params.argon2_passes_auto) { + pp->time_ms = pp->params.argon2_milliseconds; + pp->time_passes = 13; + } else { + pp->time_ms = 100; + pp->time_passes = pp->params.argon2_passes; + } + int dlgret = DialogBoxParam(hinst, MAKEINTRESOURCE(215), + NULL, PPKParamsProc, (LPARAM)pp); + if (dlgret) { + if (pp->params.argon2_passes_auto) { + pp->params.argon2_milliseconds = pp->time_ms; + } else { + pp->params.argon2_passes = pp->time_passes; + } + save_params = pp->params; + } + 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) { + unsigned raw_entropy_required; + unsigned char *raw_entropy_buf; + BOOL ok; + + state->key_bits = GetDlgItemInt(hwnd, IDC_BITS, &ok, false); + if (!ok) + state->key_bits = DEFAULT_KEY_BITS; + state->ssh2 = true; + + if (IsDlgButtonChecked(hwnd, IDC_KEYSSH1)) { + state->ssh2 = false; + state->keytype = RSA; + } else if (IsDlgButtonChecked(hwnd, IDC_KEYSSH2RSA)) { + state->keytype = RSA; + } else if (IsDlgButtonChecked(hwnd, IDC_KEYSSH2DSA)) { + state->keytype = DSA; + } else if (IsDlgButtonChecked(hwnd, IDC_KEYSSH2ECDSA)) { + state->keytype = ECDSA; + int curveindex = SendDlgItemMessage(hwnd, IDC_ECCURVE, + CB_GETCURSEL, 0, 0); + assert(curveindex >= 0); + assert(curveindex < n_ec_nist_curve_lengths); + state->curve_bits = ec_nist_curve_lengths[curveindex]; + } else if (IsDlgButtonChecked(hwnd, IDC_KEYSSH2EDDSA)) { + state->keytype = EDDSA; + int curveindex = SendDlgItemMessage(hwnd, IDC_EDCURVE, + CB_GETCURSEL, 0, 0); + assert(curveindex >= 0); + assert(curveindex < n_ec_ed_curve_lengths); + state->curve_bits = ec_ed_curve_lengths[curveindex]; + } else { + /* Somehow, no button was checked */ + break; + } + + if ((state->keytype == RSA || state->keytype == DSA) && + state->key_bits < 256) { + char *message = dupprintf + ("PuTTYgen will not generate a key smaller than 256" + " bits.\nKey length reset to default %d. Continue?", + DEFAULT_KEY_BITS); + int ret = MessageBox(hwnd, message, "PuTTYgen Warning", + MB_ICONWARNING | MB_OKCANCEL); + sfree(message); + if (ret != IDOK) + break; + state->key_bits = DEFAULT_KEY_BITS; + SetDlgItemInt(hwnd, IDC_BITS, DEFAULT_KEY_BITS, false); + } else if ((state->keytype == RSA || state->keytype == DSA) && + state->key_bits < DEFAULT_KEY_BITS) { + char *message = dupprintf + ("Keys shorter than %d bits are not recommended. " + "Really generate this key?", DEFAULT_KEY_BITS); + int ret = MessageBox(hwnd, message, "PuTTYgen Warning", + MB_ICONWARNING | MB_OKCANCEL); + sfree(message); + if (ret != IDOK) + break; + } + + if (state->keytype == RSA || state->keytype == DSA) + raw_entropy_required = (state->key_bits / 2) * 2; + else if (state->keytype == ECDSA || state->keytype == EDDSA) + raw_entropy_required = (state->curve_bits / 2) * 2; + else + unreachable("we must have initialised keytype by now"); + + /* Bound the entropy collection above by the amount of + * data we can actually fit into the PRNG. Any more + * than that and it's doing no more good. */ + if (raw_entropy_required > random_seed_bits()) + raw_entropy_required = random_seed_bits(); + + raw_entropy_buf = snewn(raw_entropy_required, unsigned char); + if (win_read_random(raw_entropy_buf, raw_entropy_required)) { + /* + * If we can get entropy from CryptGenRandom, use + * it. But CryptGenRandom isn't a kernel-level + * CPRNG (according to Wikipedia), and papers have + * been published cryptanalysing it. So we'll + * still do manual entropy collection; we'll just + * do it _as well_ as this. + */ + random_reseed( + make_ptrlen(raw_entropy_buf, raw_entropy_required)); + } + + /* + * Manual entropy input, by making the user wave the + * mouse over the window a lot. + * + * 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, i.e. the + * number of _words_ of mouse data we want to collect + * is just the same as the number of _bits_ of entropy + * we want. + */ + state->entropy_required = raw_entropy_required; + + ui_set_state(hwnd, state, 1); + SetDlgItemText(hwnd, IDC_GENERATING, entropy_msg); + state->key_exists = false; + state->collecting_entropy = true; + + 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); + + smemclr(raw_entropy_buf, raw_entropy_required); + sfree(raw_entropy_buf); + } + break; + case IDC_SAVE: + case IDC_EXPORT_OPENSSH_AUTO: + case IDC_EXPORT_OPENSSH_NEW: + 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, *passphrase2; + int type, realtype; + + if (state->ssh2) + realtype = SSH_KEYTYPE_SSH2; + else + realtype = SSH_KEYTYPE_SSH1; + + if (LOWORD(wParam) == IDC_EXPORT_OPENSSH_AUTO) + type = SSH_KEYTYPE_OPENSSH_AUTO; + else if (LOWORD(wParam) == IDC_EXPORT_OPENSSH_NEW) + type = SSH_KEYTYPE_OPENSSH_NEW; + 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; + } + + passphrase = GetDlgItemText_alloc(hwnd, IDC_PASSPHRASE1EDIT); + passphrase2 = GetDlgItemText_alloc(hwnd, IDC_PASSPHRASE2EDIT); + if (strcmp(passphrase, passphrase2)) { + MessageBox(hwnd, + "The two passphrases given do not match.", + "PuTTYgen Error", MB_OK | MB_ICONERROR); + burnstr(passphrase); + burnstr(passphrase2); + break; + } + burnstr(passphrase2); + if (!*passphrase) { + int ret; + ret = MessageBox(hwnd, + "Are you sure you want to save this key\n" + "without a passphrase to protect it?", + "PuTTYgen Warning", + MB_YESNO | MB_ICONWARNING); + if (ret != IDYES) { + burnstr(passphrase); + break; + } + } + if (prompt_keyfile(hwnd, "Save private key as:", + filename, true, (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) { + burnstr(passphrase); + 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 = ppk_save_f(fn, &state->ssh2key, + *passphrase ? passphrase : NULL, + &save_params); + filename_free(fn); + } else { + Filename *fn = filename_from_str(filename); + if (type != realtype) + ret = export_ssh1(fn, type, &state->key, + *passphrase ? passphrase : NULL); + else + ret = rsa1_save_f(fn, &state->key, + *passphrase ? passphrase : NULL); + filename_free(fn); + } + if (ret <= 0) { + MessageBox(hwnd, "Unable to save key file", + "PuTTYgen Error", MB_OK | MB_ICONERROR); + } + } + burnstr(passphrase); + } + break; + case IDC_SAVEPUB: + if (HIWORD(wParam) != BN_CLICKED) + break; + state = + (struct MainDlgState *) GetWindowLongPtr(hwnd, GWLP_USERDATA); + if (state->key_exists) { + char filename[FILENAME_MAX]; + if (prompt_keyfile(hwnd, "Save public key as:", + filename, true, false)) { + 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; + } + fp = fopen(filename, "w"); + if (!fp) { + MessageBox(hwnd, "Unable to open key file", + "PuTTYgen Error", MB_OK | MB_ICONERROR); + } else { + if (state->ssh2) { + strbuf *blob = strbuf_new(); + ssh_key_public_blob( + state->ssh2key.key, BinarySink_UPCAST(blob)); + ssh2_write_pubkey(fp, state->ssh2key.comment, + blob->u, blob->len, + SSH_KEYTYPE_SSH2_PUBLIC_RFC4716); + strbuf_free(blob); + } else { + ssh1_write_pubkey(fp, &state->key); + } + if (fclose(fp) < 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, false, + LOWORD(wParam) == IDC_LOAD)) { + Filename *fn = filename_from_str(filename); + load_key_file(hwnd, state, fn, LOWORD(wParam) != IDC_LOAD); + filename_free(fn); + } + } + break; + } + return 0; + case WM_DONEKEY: + 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->keytype == DSA) { + state->ssh2key.key = &state->dsakey.sshk; + } else if (state->keytype == ECDSA) { + state->ssh2key.key = &state->eckey.sshk; + } else if (state->keytype == EDDSA) { + state->ssh2key.key = &state->edkey.sshk; + } else { + state->ssh2key.key = &state->key.sshk; + } + 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->keytype == DSA) + strftime(*state->commentptr, 30, "dsa-key-%Y%m%d", &tm); + else if (state->keytype == ECDSA) + strftime(*state->commentptr, 30, "ecdsa-key-%Y%m%d", &tm); + else if (state->keytype == EDDSA) + strftime(*state->commentptr, 30, "eddsa-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 *fp, *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) + fp = ssh2_fingerprint(state->ssh2key.key, state->fptype); + else + fp = rsa_ssh1_fingerprint(&state->key); + SetDlgItemText(hwnd, IDC_FINGERPRINT, fp); + sfree(fp); + *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; + const 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: + case IDC_KEYSSH2ECDSA: + case IDC_KEYSSH2EDDSA: + 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_AUTO: + case IDC_EXPORT_OPENSSH_NEW: + 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); +} + +HINSTANCE hinst; + +int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) +{ + int argc, i; + char **argv; + int ret; + + dll_hijacking_protection(); + + init_common_controls(); + hinst = inst; + + /* + * See if we can find our Help file. + */ + init_help(); + + split_into_argv(cmdline, &argc, &argv, NULL); + + for (i = 0; i < argc; i++) { + if (!strcmp(argv[i], "-pgpfp")) { + pgp_fingerprints_msgbox(NULL); + return 1; + } else if (!strcmp(argv[i], "-restrict-acl") || + !strcmp(argv[i], "-restrict_acl") || + !strcmp(argv[i], "-restrictacl")) { + restrict_process_acl(); + } else { + /* + * Assume the first argument to be a private key file, and + * attempt to load it. + */ + cmdline_keyfile = argv[i]; + break; + } + } + + save_params = ppk_save_default_parameters; + + random_setup_special(); + ret = DialogBox(hinst, MAKEINTRESOURCE(201), NULL, MainDlgProc) != IDOK; + + cleanup_exit(ret); + return ret; /* just in case optimiser complains */ +} diff --git a/windows/puttygen.rc b/windows/puttygen.rc index b910b6a3..d1cef50d 100644 --- a/windows/puttygen.rc +++ b/windows/puttygen.rc @@ -7,7 +7,7 @@ #define APPNAME "PuTTYgen" #define APPDESC "PuTTY SSH key generation utility" -#include "winhelp.rc2" +#include "help.rc2" #include "puttygen-rc.h" 200 ICON "puttygen.ico" diff --git a/windows/puttytel.rc b/windows/puttytel.rc index 259bc683..6f2bfaab 100644 --- a/windows/puttytel.rc +++ b/windows/puttytel.rc @@ -3,8 +3,8 @@ #define APPNAME "PuTTYtel" #define APPDESC "Telnet and Rlogin client" -#include "winhelp.rc2" -#include "win_res.rc2" +#include "help.rc2" +#include "putty-common.rc2" #ifndef NO_MANIFESTS 1 RT_MANIFEST "puttytel.mft" diff --git a/windows/security-api.h b/windows/security-api.h new file mode 100644 index 00000000..95e9353d --- /dev/null +++ b/windows/security-api.h @@ -0,0 +1,49 @@ +/* + * security-api.h: some miscellaneous security-related helper functions, + * defined in winsecur.c, that use the advapi32 library. Also + * centralises the machinery for dynamically loading that library. + */ + +#include + +/* + * Functions loaded from advapi32.dll. + */ +DECL_WINDOWS_FUNCTION(extern, BOOL, OpenProcessToken, + (HANDLE, DWORD, PHANDLE)); +DECL_WINDOWS_FUNCTION(extern, BOOL, GetTokenInformation, + (HANDLE, TOKEN_INFORMATION_CLASS, + LPVOID, DWORD, PDWORD)); +DECL_WINDOWS_FUNCTION(extern, BOOL, InitializeSecurityDescriptor, + (PSECURITY_DESCRIPTOR, DWORD)); +DECL_WINDOWS_FUNCTION(extern, BOOL, SetSecurityDescriptorOwner, + (PSECURITY_DESCRIPTOR, PSID, BOOL)); +DECL_WINDOWS_FUNCTION(extern, DWORD, GetSecurityInfo, + (HANDLE, SE_OBJECT_TYPE, SECURITY_INFORMATION, + PSID *, PSID *, PACL *, PACL *, + PSECURITY_DESCRIPTOR *)); +DECL_WINDOWS_FUNCTION(extern, DWORD, SetSecurityInfo, + (HANDLE, SE_OBJECT_TYPE, SECURITY_INFORMATION, + PSID, PSID, PACL, PACL)); +DECL_WINDOWS_FUNCTION(extern, DWORD, SetEntriesInAclA, + (ULONG, PEXPLICIT_ACCESS, PACL, PACL *)); +bool got_advapi(void); + +/* + * Find the SID describing the current user. The return value (if not + * NULL for some error-related reason) is smalloced. + */ +PSID get_user_sid(void); + +/* + * Construct a PSECURITY_DESCRIPTOR of the type used for named pipe + * servers, i.e. allowing access only to the current user id and also + * only local (i.e. not over SMB) connections. + * + * If this function returns true, then 'psd' and 'acl' will have been + * filled in with memory allocated using LocalAlloc (and hence must be + * freed later using LocalFree). If it returns false, then instead + * 'error' has been filled with a dynamically allocated error message. + */ +bool make_private_security_descriptor( + DWORD permissions, PSECURITY_DESCRIPTOR *psd, PACL *acl, char **error); diff --git a/windows/select-cli.c b/windows/select-cli.c new file mode 100644 index 00000000..f19a0bbe --- /dev/null +++ b/windows/select-cli.c @@ -0,0 +1,78 @@ +/* + * Implementation of do_select() for winnet.c to use, suitable for use + * when there's no GUI window to have network activity reported to. + * + * It uses WSAEventSelect, where available, to convert network + * activity into activity on an event object, for integration into an + * event loop that includes WaitForMultipleObjects. + * + * It also maintains a list of currently active sockets, which can be + * retrieved by a front end that wants to use WinSock's synchronous + * select() function. + */ + +#include "putty.h" + +static tree234 *winselcli_sockets; + +static int socket_cmp(void *av, void *bv) +{ + return memcmp(av, bv, sizeof(SOCKET)); +} + +HANDLE winselcli_event = INVALID_HANDLE_VALUE; + +void winselcli_setup(void) +{ + if (!winselcli_sockets) + winselcli_sockets = newtree234(socket_cmp); + + if (p_WSAEventSelect && winselcli_event == INVALID_HANDLE_VALUE) + winselcli_event = CreateEvent(NULL, false, false, NULL); +} + +SOCKET winselcli_unique_socket(void) +{ + if (!winselcli_sockets) + return INVALID_SOCKET; + + assert(count234(winselcli_sockets) <= 1); + + SOCKET *p = index234(winselcli_sockets, 0); + if (!p) + return INVALID_SOCKET; + + return *p; +} + +const char *do_select(SOCKET skt, bool enable) +{ + /* Check everything's been set up, for convenience of callers. */ + winselcli_setup(); + + if (enable) { + SOCKET *ptr = snew(SOCKET); + *ptr = skt; + if (add234(winselcli_sockets, ptr) != ptr) + sfree(ptr); /* already there */ + } else { + SOCKET *ptr = del234(winselcli_sockets, &skt); + if (ptr) + sfree(ptr); + } + + if (p_WSAEventSelect) { + int events; + if (enable) { + events = (FD_CONNECT | FD_READ | FD_WRITE | + FD_OOB | FD_CLOSE | FD_ACCEPT); + } else { + events = 0; + } + + if (p_WSAEventSelect(skt, winselcli_event, events) == SOCKET_ERROR) + return winsock_error_string(p_WSAGetLastError()); + } + + return NULL; +} diff --git a/windows/select-gui.c b/windows/select-gui.c new file mode 100644 index 00000000..48a15212 --- /dev/null +++ b/windows/select-gui.c @@ -0,0 +1,38 @@ +/* + * Implementation of do_select() for winnet.c to use, that uses + * WSAAsyncSelect to convert network activity into window messages, + * for integration into a GUI event loop. + */ + +#include "putty.h" + +static HWND winsel_hwnd = NULL; + +void winselgui_set_hwnd(HWND hwnd) +{ + winsel_hwnd = hwnd; +} + +void winselgui_clear_hwnd(void) +{ + winsel_hwnd = NULL; +} + +const char *do_select(SOCKET skt, bool enable) +{ + int msg, events; + if (enable) { + msg = WM_NETEVENT; + events = (FD_CONNECT | FD_READ | FD_WRITE | + FD_OOB | FD_CLOSE | FD_ACCEPT); + } else { + msg = events = 0; + } + + assert(winsel_hwnd); + + if (p_WSAAsyncSelect(skt, winsel_hwnd, msg, events) == SOCKET_ERROR) + return winsock_error_string(p_WSAGetLastError()); + + return NULL; +} diff --git a/windows/serial.c b/windows/serial.c new file mode 100644 index 00000000..7f4bcf2e --- /dev/null +++ b/windows/serial.c @@ -0,0 +1,465 @@ +/* + * Serial back end (Windows-specific). + */ + +#include +#include +#include + +#include "putty.h" + +#define SERIAL_MAX_BACKLOG 4096 + +typedef struct Serial Serial; +struct Serial { + HANDLE port; + struct handle *out, *in; + Seat *seat; + LogContext *logctx; + int bufsize; + long clearbreak_time; + bool break_in_progress; + Backend backend; +}; + +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 size_t serial_gotdata( + struct handle *h, const void *data, size_t len, int err) +{ + Serial *serial = (Serial *)handle_get_privdata(h); + if (err || 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 (!err) + error_msg = "End of file reading from serial device"; + else + error_msg = "Error reading from serial device"; + + serial_terminate(serial); + + seat_notify_remote_exit(serial->seat); + + logevent(serial->logctx, error_msg); + + seat_connection_fatal(serial->seat, "%s", error_msg); + + return 0; + } else { + return seat_stdout(serial->seat, data, len); + } +} + +static void serial_sentdata(struct handle *h, size_t new_backlog, int err) +{ + Serial *serial = (Serial *)handle_get_privdata(h); + if (err) { + const char *error_msg = "Error writing to serial device"; + + serial_terminate(serial); + + seat_notify_remote_exit(serial->seat); + + logevent(serial->logctx, error_msg); + + seat_connection_fatal(serial->seat, "%s", error_msg); + } else { + serial->bufsize = new_backlog; + } +} + +static char *serial_configure(Serial *serial, HANDLE serport, Conf *conf) +{ + 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)) { + 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 = conf_get_int(conf, CONF_serspeed); + logeventf(serial->logctx, "Configuring baud rate %lu", + (unsigned long)dcb.BaudRate); + + dcb.ByteSize = conf_get_int(conf, CONF_serdatabits); + logeventf(serial->logctx, "Configuring %u data bits", + (unsigned)dcb.ByteSize); + + switch (conf_get_int(conf, CONF_serstopbits)) { + case 2: dcb.StopBits = ONESTOPBIT; str = "1 stop bit"; break; + case 3: dcb.StopBits = ONE5STOPBITS; str = "1.5 stop bits"; break; + case 4: dcb.StopBits = TWOSTOPBITS; str = "2 stop bits"; break; + default: return dupstr("Invalid number of stop bits " + "(need 1, 1.5 or 2)"); + } + logeventf(serial->logctx, "Configuring %s", str); + + switch (conf_get_int(conf, CONF_serparity)) { + case SER_PAR_NONE: dcb.Parity = NOPARITY; str = "no"; break; + case SER_PAR_ODD: dcb.Parity = ODDPARITY; str = "odd"; break; + case SER_PAR_EVEN: dcb.Parity = EVENPARITY; str = "even"; break; + case SER_PAR_MARK: dcb.Parity = MARKPARITY; str = "mark"; break; + case SER_PAR_SPACE: dcb.Parity = SPACEPARITY; str = "space"; break; + } + logeventf(serial->logctx, "Configuring %s parity", str); + + switch (conf_get_int(conf, CONF_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; + } + logeventf(serial->logctx, "Configuring %s flow control", str); + + if (!SetCommState(serport, &dcb)) + return dupprintf("Configuring serial port: %s", + win_strerror(GetLastError())); + + timeouts.ReadIntervalTimeout = 1; + timeouts.ReadTotalTimeoutMultiplier = 0; + timeouts.ReadTotalTimeoutConstant = 0; + timeouts.WriteTotalTimeoutMultiplier = 0; + timeouts.WriteTotalTimeoutConstant = 0; + if (!SetCommTimeouts(serport, &timeouts)) + return dupprintf("Configuring serial timeouts: %s", + win_strerror(GetLastError())); + } + + 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 char *serial_init(const BackendVtable *vt, Seat *seat, + Backend **backend_handle, LogContext *logctx, + Conf *conf, const char *host, int port, + char **realhost, bool nodelay, bool keepalive) +{ + Serial *serial; + HANDLE serport; + char *err; + char *serline; + + /* No local authentication phase in this protocol */ + seat_set_trust_status(seat, false); + + serial = snew(Serial); + serial->port = INVALID_HANDLE_VALUE; + serial->out = serial->in = NULL; + serial->bufsize = 0; + serial->break_in_progress = false; + serial->backend.vt = vt; + *backend_handle = &serial->backend; + + serial->seat = seat; + serial->logctx = logctx; + + serline = conf_get_str(conf, CONF_serline); + logeventf(serial->logctx, "Opening serial device %s", serline); + + /* + * 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 + * + * ("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 + * (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(serline, '\\') ? "" : "\\\\.\\", serline); + serport = CreateFile(serfilename, GENERIC_READ | GENERIC_WRITE, 0, NULL, + OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); + if (serport == INVALID_HANDLE_VALUE) { + err = dupprintf("Opening '%s': %s", + serfilename, win_strerror(GetLastError())); + sfree(serfilename); + return err; + } + + sfree(serfilename); + + err = serial_configure(serial, serport, conf); + 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(serline); + + /* + * Specials are always available. + */ + seat_update_specials_menu(serial->seat); + + return NULL; +} + +static void serial_free(Backend *be) +{ + Serial *serial = container_of(be, Serial, backend); + + serial_terminate(serial); + expire_timer_context(serial); + sfree(serial); +} + +static void serial_reconfig(Backend *be, Conf *conf) +{ + Serial *serial = container_of(be, Serial, backend); + + serial_configure(serial, serial->port, conf); + + /* + * FIXME: what should we do if that call returned a non-NULL error + * message? + */ +} + +/* + * Called to send data down the serial connection. + */ +static size_t serial_send(Backend *be, const char *buf, size_t len) +{ + Serial *serial = container_of(be, Serial, backend); + + 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 size_t serial_sendbuffer(Backend *be) +{ + Serial *serial = container_of(be, Serial, backend); + return serial->bufsize; +} + +/* + * Called to set the size of the window + */ +static void serial_size(Backend *be, int width, int height) +{ + /* Do nothing! */ + return; +} + +static void serbreak_timer(void *ctx, unsigned long now) +{ + Serial *serial = (Serial *)ctx; + + if (now == serial->clearbreak_time && serial->port) { + ClearCommBreak(serial->port); + serial->break_in_progress = false; + logevent(serial->logctx, "Finished serial break"); + } +} + +/* + * Send serial special codes. + */ +static void serial_special(Backend *be, SessionSpecialCode code, int arg) +{ + Serial *serial = container_of(be, Serial, backend); + + if (serial->port && code == SS_BRK) { + logevent(serial->logctx, "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 SessionSpecial *serial_get_specials(Backend *be) +{ + static const SessionSpecial specials[] = { + {"Break", SS_BRK}, + {NULL, SS_EXITMENU} + }; + return specials; +} + +static bool serial_connected(Backend *be) +{ + return true; /* always connected */ +} + +static bool serial_sendok(Backend *be) +{ + return true; +} + +static void serial_unthrottle(Backend *be, size_t backlog) +{ + Serial *serial = container_of(be, Serial, backend); + if (serial->in) + handle_unthrottle(serial->in, backlog); +} + +static bool serial_ldisc(Backend *be, int option) +{ + /* + * Local editing and local echo are off by default. + */ + return false; +} + +static void serial_provide_ldisc(Backend *be, Ldisc *ldisc) +{ + /* This is a stub. */ +} + +static int serial_exitcode(Backend *be) +{ + Serial *serial = container_of(be, Serial, backend); + 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(Backend *be) +{ + return 0; +} + +const BackendVtable serial_backend = { + .init = serial_init, + .free = serial_free, + .reconfig = serial_reconfig, + .send = serial_send, + .sendbuffer = serial_sendbuffer, + .size = serial_size, + .special = serial_special, + .get_specials = serial_get_specials, + .connected = serial_connected, + .exitcode = serial_exitcode, + .sendok = serial_sendok, + .ldisc_option_state = serial_ldisc, + .provide_ldisc = serial_provide_ldisc, + .unthrottle = serial_unthrottle, + .cfg_info = serial_cfg_info, + .id = "serial", + .displayname = "Serial", + .protocol = PROT_SERIAL, + .serial_parity_mask = ((1 << SER_PAR_NONE) | + (1 << SER_PAR_ODD) | + (1 << SER_PAR_EVEN) | + (1 << SER_PAR_MARK) | + (1 << SER_PAR_SPACE)), + .serial_flow_mask = ((1 << SER_FLOW_NONE) | + (1 << SER_FLOW_XONXOFF) | + (1 << SER_FLOW_RTSCTS) | + (1 << SER_FLOW_DSRDTR)), +}; diff --git a/windows/sftp.c b/windows/sftp.c new file mode 100644 index 00000000..e316f8f8 --- /dev/null +++ b/windows/sftp.c @@ -0,0 +1,650 @@ +/* + * winsftp.c: the Windows-specific parts of PSFTP and PSCP. + */ + +#include /* need to put this first, for winelib builds */ +#include + +#define NEED_DECLARATION_OF_SELECT + +#include "putty.h" +#include "psftp.h" +#include "ssh.h" +#include "security-api.h" + +int filexfer_get_userpass_input(Seat *seat, prompts_t *p, bufchain *input) +{ + int ret; + ret = cmdline_get_passwd_input(p); + if (ret == -1) + ret = console_get_userpass_input(p); + return ret; +} + +void platform_get_x11_auth(struct X11Display *display, Conf *conf) +{ + /* Do nothing, therefore no auth. */ +} +const bool 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); + size_t len = GetCurrentDirectory(256, ret); + if (len > 256) + ret = sresize(ret, len, char); + GetCurrentDirectory(len, ret); + return ret; +} + +static inline uint64_t uint64_from_words(uint32_t hi, uint32_t lo) +{ + return (((uint64_t)hi) << 32) | lo; +} + +#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(const char *name, uint64_t *size, + unsigned long *mtime, unsigned long *atime, + long *perms) +{ + 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) { + DWORD lo, hi; + lo = GetFileSize(h, &hi); + *size = uint64_from_words(hi, lo); + } + + 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); + } + + if (perms) + *perms = -1; + + return ret; +} + +int read_from_file(RFile *f, void *buffer, int length) +{ + DWORD read; + if (!ReadFile(f->h, buffer, length, &read, NULL)) + return -1; /* error */ + else + return read; +} + +void close_rfile(RFile *f) +{ + CloseHandle(f->h); + sfree(f); +} + +struct WFile { + HANDLE h; +}; + +WFile *open_new_file(const char *name, long perms) +{ + 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(const char *name, uint64_t *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) { + DWORD lo, hi; + lo = GetFileSize(h, &hi); + *size = uint64_from_words(hi, lo); + } + + return ret; +} + +int write_to_file(WFile *f, void *buffer, int length) +{ + DWORD written; + if (!WriteFile(f->h, buffer, length, &written, NULL)) + 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_t 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; + } + + { + LONG lo = offset & 0xFFFFFFFFU, hi = offset >> 32; + SetFilePointer(f->h, lo, &hi, movemethod); + } + + if (GetLastError() != NO_ERROR) + return -1; + else + return 0; +} + +uint64_t get_file_posn(WFile *f) +{ + LONG lo, hi = 0; + + lo = SetFilePointer(f->h, 0L, &hi, FILE_CURRENT); + return uint64_from_words(hi, lo); +} + +int file_type(const 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(const char *name, const char **errmsg) +{ + HANDLE h; + WIN32_FIND_DATA fdat; + char *findfile; + DirHandle *ret; + + /* Enumerate files in dir `foo'. */ + findfile = dupcat(name, "/*"); + h = FindFirstFile(findfile, &fdat); + if (h == INVALID_HANDLE_VALUE) { + *errmsg = win_strerror(GetLastError()); + 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; + if (!FindNextFile(dir->h, &fdat)) + 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(const char *name, bool 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; +}; + +char *stripslashes(const char *str, bool local) +{ + char *p; + + /* + * On Windows, \ / : are all path component separators. + */ + + 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 (char *)str; +} + +WildcardMatcher *begin_wildcard_matching(const 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, true); + *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); + + return ret; +} + +char *wildcard_get_filename(WildcardMatcher *dir) +{ + while (!dir->name) { + WIN32_FIND_DATA fdat; + + if (!FindNextFile(dir->h, &fdat)) + 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); + } + + 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); +} + +bool vet_filename(const char *name) +{ + if (strchr(name, '/') || strchr(name, '\\') || strchr(name, ':')) + return false; + + if (!name[strspn(name, ".")]) /* entirely composed of dots */ + return false; + + return true; +} + +bool create_directory(const char *name) +{ + return CreateDirectory(name, NULL) != 0; +} + +char *dir_file_cat(const char *dir, const char *file) +{ + ptrlen dir_pl = ptrlen_from_asciz(dir); + return dupcat( + dir, (ptrlen_endswith(dir_pl, PTRLEN_LITERAL("\\"), NULL) || + ptrlen_endswith(dir_pl, PTRLEN_LITERAL("/"), NULL)) ? "" : "\\", + file); +} + +/* ---------------------------------------------------------------------- + * Platform-specific network handling. + */ +struct winsftp_cliloop_ctx { + HANDLE other_event; + int toret; +}; +static bool winsftp_cliloop_pre(void *vctx, const HANDLE **extra_handles, + size_t *n_extra_handles) +{ + struct winsftp_cliloop_ctx *ctx = (struct winsftp_cliloop_ctx *)vctx; + + if (ctx->other_event != INVALID_HANDLE_VALUE) { + *extra_handles = &ctx->other_event; + *n_extra_handles = 1; + } + + return true; +} +static bool winsftp_cliloop_post(void *vctx, size_t extra_handle_index) +{ + struct winsftp_cliloop_ctx *ctx = (struct winsftp_cliloop_ctx *)vctx; + + if (ctx->other_event != INVALID_HANDLE_VALUE && + extra_handle_index == 0) + ctx->toret = 1; /* other_event was set */ + + return false; /* always run only one loop iteration */ +} +int do_eventsel_loop(HANDLE other_event) +{ + struct winsftp_cliloop_ctx ctx[1]; + ctx->other_event = other_event; + ctx->toret = 0; + cli_main_loop(winsftp_cliloop_pre, winsftp_cliloop_post, ctx); + return ctx->toret; +} + +/* + * 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; + unsigned long now = GETTICKCOUNT(), then; + SOCKET skt = winselcli_unique_socket(); + + if (skt == INVALID_SOCKET) + return -1; /* doom */ + + if (socket_writable(skt)) + select_result((WPARAM) skt, (LPARAM) FD_WRITE); + + do { + unsigned long next; + long ticks; + struct timeval tv, *ptv; + + if (run_timers(now, &next)) { + then = now; + now = GETTICKCOUNT(); + if (now - then > next - then) + ticks = 0; + else + ticks = next - now; + tv.tv_sec = ticks / 1000; + tv.tv_usec = ticks % 1000 * 1000; + ptv = &tv; + } else { + ptv = NULL; + } + + FD_ZERO(&readfds); + FD_SET(skt, &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) skt, (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(const char *prompt, bool no_fds_ok) +{ + int ret; + struct command_read_ctx ctx[1]; + DWORD threadid; + HANDLE hThread; + + fputs(prompt, stdout); + fflush(stdout); + + if ((winselcli_unique_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; + + hThread = CreateThread(NULL, 0, command_read_thread, ctx, 0, &threadid); + if (!hThread) { + CloseHandle(ctx->event); + fprintf(stderr, "Unable to create command input thread\n"); + cleanup_exit(1); + } + + do { + ret = do_eventsel_loop(ctx->event); + + /* do_eventsel_loop can't return an error (unlike + * ssh_sftp_loop_iteration, which can return -1 if select goes + * wrong or if the socket doesn't exist). */ + assert(ret >= 0); + } while (ret == 0); + + CloseHandle(hThread); + CloseHandle(ctx->event); + + return ctx->line; +} + +void platform_psftp_pre_conn_setup(LogPolicy *lp) +{ + if (restricted_acl()) { + lp_eventlog(lp, "Running with restricted process ACL"); + } +} + +/* ---------------------------------------------------------------------- + * Main program. Parse arguments etc. + */ +int main(int argc, char *argv[]) +{ + int ret; + + dll_hijacking_protection(); + + ret = psftp_main(argc, argv); + + return ret; +} diff --git a/windows/sharing.c b/windows/sharing.c new file mode 100644 index 00000000..6ded0716 --- /dev/null +++ b/windows/sharing.c @@ -0,0 +1,143 @@ +/* + * Windows implementation of SSH connection-sharing IPC setup. + */ + +#include +#include + +#include "tree234.h" +#include "putty.h" +#include "network.h" +#include "proxy.h" +#include "ssh.h" + +#include "cryptoapi.h" +#include "security-api.h" + +#define CONNSHARE_PIPE_PREFIX "\\\\.\\pipe\\putty-connshare" +#define CONNSHARE_MUTEX_PREFIX "Local\\putty-connshare-mutex" + +static char *make_name(const char *prefix, const char *name) +{ + char *username, *retname; + + username = get_username(); + retname = dupprintf("%s.%s.%s", prefix, username, name); + sfree(username); + + return retname; +} + +int platform_ssh_share(const char *pi_name, Conf *conf, + Plug *downplug, Plug *upplug, Socket **sock, + char **logtext, char **ds_err, char **us_err, + bool can_upstream, bool can_downstream) +{ + char *name, *mutexname, *pipename; + HANDLE mutex; + Socket *retsock; + PSECURITY_DESCRIPTOR psd; + PACL acl; + + /* + * Transform the platform-independent version of the connection + * identifier into the obfuscated version we'll use for our + * Windows named pipe and mutex. A side effect of doing this is + * that it also eliminates any characters illegal in Windows pipe + * names. + */ + name = capi_obfuscate_string(pi_name); + if (!name) { + *logtext = dupprintf("Unable to call CryptProtectMemory: %s", + win_strerror(GetLastError())); + return SHARE_NONE; + } + + /* + * Make a mutex name out of the connection identifier, and lock it + * while we decide whether to be upstream or downstream. + */ + { + SECURITY_ATTRIBUTES sa; + + mutexname = make_name(CONNSHARE_MUTEX_PREFIX, name); + if (!make_private_security_descriptor(MUTEX_ALL_ACCESS, + &psd, &acl, logtext)) { + sfree(mutexname); + sfree(name); + return SHARE_NONE; + } + + memset(&sa, 0, sizeof(sa)); + sa.nLength = sizeof(sa); + sa.lpSecurityDescriptor = psd; + sa.bInheritHandle = false; + + mutex = CreateMutex(&sa, false, mutexname); + + if (!mutex) { + *logtext = dupprintf("CreateMutex(\"%s\") failed: %s", + mutexname, win_strerror(GetLastError())); + sfree(mutexname); + sfree(name); + LocalFree(psd); + LocalFree(acl); + return SHARE_NONE; + } + + sfree(mutexname); + LocalFree(psd); + LocalFree(acl); + + WaitForSingleObject(mutex, INFINITE); + } + + pipename = make_name(CONNSHARE_PIPE_PREFIX, name); + + *logtext = NULL; + + if (can_downstream) { + retsock = new_named_pipe_client(pipename, downplug); + if (sk_socket_error(retsock) == NULL) { + sfree(*logtext); + *logtext = pipename; + *sock = retsock; + sfree(name); + ReleaseMutex(mutex); + CloseHandle(mutex); + return SHARE_DOWNSTREAM; + } + sfree(*ds_err); + *ds_err = dupprintf("%s: %s", pipename, sk_socket_error(retsock)); + sk_close(retsock); + } + + if (can_upstream) { + retsock = new_named_pipe_listener(pipename, upplug); + if (sk_socket_error(retsock) == NULL) { + sfree(*logtext); + *logtext = pipename; + *sock = retsock; + sfree(name); + ReleaseMutex(mutex); + CloseHandle(mutex); + return SHARE_UPSTREAM; + } + sfree(*us_err); + *us_err = dupprintf("%s: %s", pipename, sk_socket_error(retsock)); + sk_close(retsock); + } + + /* One of the above clauses ought to have happened. */ + assert(*logtext || *ds_err || *us_err); + + sfree(pipename); + sfree(name); + ReleaseMutex(mutex); + CloseHandle(mutex); + return SHARE_NONE; +} + +void platform_ssh_share_cleanup(const char *name) +{ +} diff --git a/windows/storage.c b/windows/storage.c new file mode 100644 index 00000000..09e5c028 --- /dev/null +++ b/windows/storage.c @@ -0,0 +1,873 @@ +/* + * winstore.c: Windows-specific implementation of the interface + * defined in storage.h. + */ + +#include +#include +#include +#include +#include "putty.h" +#include "storage.h" + +#include +#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 bool tried_shgetfolderpath = false; +static HMODULE shell32_module = NULL; +DECL_WINDOWS_FUNCTION(static, HRESULT, SHGetFolderPathA, + (HWND, int, HANDLE, DWORD, LPSTR)); + +struct settings_w { + HKEY sesskey; +}; + +settings_w *open_settings_w(const char *sessionname, char **errmsg) +{ + HKEY subkey1, sesskey; + int ret; + strbuf *sb; + + *errmsg = NULL; + + if (!sessionname || !*sessionname) + sessionname = "Default Settings"; + + sb = strbuf_new(); + escape_registry_key(sessionname, sb); + + ret = RegCreateKey(HKEY_CURRENT_USER, puttystr, &subkey1); + if (ret != ERROR_SUCCESS) { + strbuf_free(sb); + *errmsg = dupprintf("Unable to create registry key\n" + "HKEY_CURRENT_USER\\%s", puttystr); + return NULL; + } + ret = RegCreateKey(subkey1, sb->s, &sesskey); + RegCloseKey(subkey1); + if (ret != ERROR_SUCCESS) { + *errmsg = dupprintf("Unable to create registry key\n" + "HKEY_CURRENT_USER\\%s\\%s", puttystr, sb->s); + strbuf_free(sb); + return NULL; + } + strbuf_free(sb); + + settings_w *toret = snew(settings_w); + toret->sesskey = sesskey; + return toret; +} + +void write_setting_s(settings_w *handle, const char *key, const char *value) +{ + if (handle) + RegSetValueEx(handle->sesskey, key, 0, REG_SZ, (CONST BYTE *)value, + 1 + strlen(value)); +} + +void write_setting_i(settings_w *handle, const char *key, int value) +{ + if (handle) + RegSetValueEx(handle->sesskey, key, 0, REG_DWORD, + (CONST BYTE *) &value, sizeof(value)); +} + +void close_settings_w(settings_w *handle) +{ + RegCloseKey(handle->sesskey); + sfree(handle); +} + +struct settings_r { + HKEY sesskey; +}; + +settings_r *open_settings_r(const char *sessionname) +{ + HKEY subkey1, sesskey; + strbuf *sb; + + if (!sessionname || !*sessionname) + sessionname = "Default Settings"; + + sb = strbuf_new(); + escape_registry_key(sessionname, sb); + + if (RegOpenKey(HKEY_CURRENT_USER, puttystr, &subkey1) != ERROR_SUCCESS) { + sesskey = NULL; + } else { + if (RegOpenKey(subkey1, sb->s, &sesskey) != ERROR_SUCCESS) { + sesskey = NULL; + } + RegCloseKey(subkey1); + } + + strbuf_free(sb); + + if (!sesskey) + return NULL; + + settings_r *toret = snew(settings_r); + toret->sesskey = sesskey; + return toret; +} + +char *read_setting_s(settings_r *handle, const char *key) +{ + DWORD type, allocsize, size; + char *ret; + + if (!handle) + return NULL; + + /* Find out the type and size of the data. */ + if (RegQueryValueEx(handle->sesskey, key, 0, + &type, NULL, &size) != ERROR_SUCCESS || + type != REG_SZ) + return NULL; + + allocsize = size+1; /* allow for an extra NUL if needed */ + ret = snewn(allocsize, char); + if (RegQueryValueEx(handle->sesskey, key, 0, + &type, (BYTE *)ret, &size) != ERROR_SUCCESS || + type != REG_SZ) { + sfree(ret); + return NULL; + } + assert(size < allocsize); + ret[size] = '\0'; /* add an extra NUL in case RegQueryValueEx + * didn't supply one */ + + return ret; +} + +int read_setting_i(settings_r *handle, const char *key, int defvalue) +{ + DWORD type, val, size; + size = sizeof(val); + + if (!handle || + RegQueryValueEx(handle->sesskey, key, 0, &type, + (BYTE *) &val, &size) != ERROR_SUCCESS || + size != sizeof(val) || type != REG_DWORD) + return defvalue; + else + return val; +} + +FontSpec *read_setting_fontspec(settings_r *handle, const char *name) +{ + char *settingname; + char *fontname; + FontSpec *ret; + int isbold, height, charset; + + fontname = read_setting_s(handle, name); + if (!fontname) + return NULL; + + settingname = dupcat(name, "IsBold"); + isbold = read_setting_i(handle, settingname, -1); + sfree(settingname); + if (isbold == -1) { + sfree(fontname); + return NULL; + } + + settingname = dupcat(name, "CharSet"); + charset = read_setting_i(handle, settingname, -1); + sfree(settingname); + if (charset == -1) { + sfree(fontname); + return NULL; + } + + settingname = dupcat(name, "Height"); + height = read_setting_i(handle, settingname, INT_MIN); + sfree(settingname); + if (height == INT_MIN) { + sfree(fontname); + return NULL; + } + + ret = fontspec_new(fontname, isbold, height, charset); + sfree(fontname); + return ret; +} + +void write_setting_fontspec(settings_w *handle, + const char *name, FontSpec *font) +{ + char *settingname; + + write_setting_s(handle, name, font->name); + settingname = dupcat(name, "IsBold"); + write_setting_i(handle, settingname, font->isbold); + sfree(settingname); + settingname = dupcat(name, "CharSet"); + write_setting_i(handle, settingname, font->charset); + sfree(settingname); + settingname = dupcat(name, "Height"); + write_setting_i(handle, settingname, font->height); + sfree(settingname); +} + +Filename *read_setting_filename(settings_r *handle, const char *name) +{ + char *tmp = read_setting_s(handle, name); + if (tmp) { + Filename *ret = filename_from_str(tmp); + sfree(tmp); + return ret; + } else + return NULL; +} + +void write_setting_filename(settings_w *handle, + const char *name, Filename *result) +{ + write_setting_s(handle, name, result->path); +} + +void close_settings_r(settings_r *handle) +{ + if (handle) { + RegCloseKey(handle->sesskey); + sfree(handle); + } +} + +void del_settings(const char *sessionname) +{ + HKEY subkey1; + strbuf *sb; + + if (RegOpenKey(HKEY_CURRENT_USER, puttystr, &subkey1) != ERROR_SUCCESS) + return; + + sb = strbuf_new(); + escape_registry_key(sessionname, sb); + RegDeleteKey(subkey1, sb->s); + strbuf_free(sb); + + RegCloseKey(subkey1); + + remove_session_from_jumplist(sessionname); +} + +struct settings_e { + HKEY key; + int i; +}; + +settings_e *enum_settings_start(void) +{ + settings_e *ret; + HKEY key; + + if (RegOpenKey(HKEY_CURRENT_USER, puttystr, &key) != ERROR_SUCCESS) + return NULL; + + ret = snew(settings_e); + if (ret) { + ret->key = key; + ret->i = 0; + } + + return ret; +} + +bool enum_settings_next(settings_e *e, strbuf *sb) +{ + size_t regbuf_size = MAX_PATH + 1; + char *regbuf = snewn(regbuf_size, char); + bool success; + + while (1) { + DWORD retd = RegEnumKey(e->key, e->i, regbuf, regbuf_size); + if (retd != ERROR_MORE_DATA) { + success = (retd == ERROR_SUCCESS); + break; + } + sgrowarray(regbuf, regbuf_size, regbuf_size); + } + + if (success) + unescape_registry_key(regbuf, sb); + + e->i++; + sfree(regbuf); + return success; +} + +void enum_settings_finish(settings_e *e) +{ + RegCloseKey(e->key); + sfree(e); +} + +static void hostkey_regname(strbuf *sb, const char *hostname, + int port, const char *keytype) +{ + strbuf_catf(sb, "%s@%d:", keytype, port); + escape_registry_key(hostname, sb); +} + +int verify_host_key(const char *hostname, int port, + const char *keytype, const char *key) +{ + char *otherstr; + strbuf *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. + */ + regname = strbuf_new(); + hostkey_regname(regname, hostname, port, keytype); + + if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_POS "\\SshHostKeys", + &rkey) != ERROR_SUCCESS) { + strbuf_free(regname); + return 1; /* key does not exist in registry */ + } + + readlen = len; + otherstr = snewn(len, char); + ret = RegQueryValueEx(rkey, regname->s, NULL, + &type, (BYTE *)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->s + 1 + strcspn(regname->s, ":"); + char *oldstyle = snewn(len + 10, char); /* safety margin */ + readlen = len; + ret = RegQueryValueEx(rkey, justhost, NULL, &type, + (BYTE *)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->s, 0, REG_SZ, (BYTE *)otherstr, + strlen(otherstr) + 1); + } + + sfree(oldstyle); + } + + RegCloseKey(rkey); + + compare = strcmp(otherstr, key); + + sfree(otherstr); + strbuf_free(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 */ +} + +bool have_ssh_host_key(const char *hostname, int port, + const char *keytype) +{ + /* + * If we have a host key, verify_host_key will return 0 or 2. + * If we don't have one, it'll return 1. + */ + return verify_host_key(hostname, port, keytype, "") != 1; +} + +void store_host_key(const char *hostname, int port, + const char *keytype, const char *key) +{ + strbuf *regname; + HKEY rkey; + + regname = strbuf_new(); + hostkey_regname(regname, hostname, port, keytype); + + if (RegCreateKey(HKEY_CURRENT_USER, PUTTY_REG_POS "\\SshHostKeys", + &rkey) == ERROR_SUCCESS) { + RegSetValueEx(rkey, regname->s, 0, REG_SZ, + (BYTE *)key, strlen(key) + 1); + RegCloseKey(rkey); + } /* else key does not exist in registry */ + + strbuf_free(regname); +} + +/* + * Open (or delete) the random seed file. + */ +enum { DEL, OPEN_R, OPEN_W }; +static bool try_random_seed(char const *path, int action, HANDLE *ret) +{ + if (action == DEL) { + if (!DeleteFile(path) && GetLastError() != ERROR_FILE_NOT_FOUND) { + nonfatal("Unable to delete '%s': %s", path, + win_strerror(GetLastError())); + } + *ret = INVALID_HANDLE_VALUE; + return false; /* so we'll do the next ones too */ + } + + *ret = CreateFile(path, + 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 bool try_random_seed_and_free(char *path, int action, HANDLE *hout) +{ + bool retd = try_random_seed(path, action, hout); + sfree(path); + return retd; +} + +static HANDLE access_random_seed(int action) +{ + HKEY rkey; + HANDLE rethandle; + + /* + * 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. + */ + { + char regpath[MAX_PATH + 1]; + DWORD type, size = sizeof(regpath); + if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_POS, &rkey) == + ERROR_SUCCESS) { + int ret = RegQueryValueEx(rkey, "RandSeedFile", + 0, &type, (BYTE *)regpath, &size); + RegCloseKey(rkey); + if (ret == ERROR_SUCCESS && type == REG_SZ && + try_random_seed(regpath, 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) { + char profile[MAX_PATH + 1]; + if (SUCCEEDED(p_SHGetFolderPathA(NULL, CSIDL_LOCAL_APPDATA, + NULL, SHGFP_TYPE_CURRENT, profile)) && + try_random_seed_and_free(dupcat(profile, "\\PUTTY.RND"), + action, &rethandle)) + return rethandle; + + if (SUCCEEDED(p_SHGetFolderPathA(NULL, CSIDL_APPDATA, + NULL, SHGFP_TYPE_CURRENT, profile)) && + try_random_seed_and_free(dupcat(profile, "\\PUTTY.RND"), + action, &rethandle)) + return rethandle; + } + + /* + * Failing that, try %HOMEDRIVE%%HOMEPATH% as a guess at the + * user's home directory. + */ + { + char drv[MAX_PATH], path[MAX_PATH]; + + DWORD drvlen = GetEnvironmentVariable("HOMEDRIVE", drv, sizeof(drv)); + DWORD pathlen = GetEnvironmentVariable("HOMEPATH", path, sizeof(path)); + + /* We permit %HOMEDRIVE% to expand to an empty string, but if + * %HOMEPATH% does that, we abort the attempt. Same if either + * variable overflows its buffer. */ + if (drvlen == 0) + drv[0] = '\0'; + + if (drvlen < lenof(drv) && pathlen < lenof(path) && pathlen > 0 && + try_random_seed_and_free( + dupcat(drv, path, "\\PUTTY.RND"), action, &rethandle)) + return rethandle; + } + + /* + * And finally, fall back to C:\WINDOWS. + */ + { + char windir[MAX_PATH]; + DWORD len = GetWindowsDirectory(windir, sizeof(windir)); + if (len < lenof(windir) && + try_random_seed_and_free( + dupcat(windir, "\\PUTTY.RND"), 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; + DWORD type; + DWORD 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, + (BYTE *)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, + (BYTE *)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. */ + settings_r *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, + (BYTE *)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 && ret == ERROR_SUCCESS) + *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) != JUMPLISTREG_OK) { + 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. + */ +} diff --git a/windows/unicode.c b/windows/unicode.c new file mode 100644 index 00000000..9ffff5e9 --- /dev/null +++ b/windows/unicode.c @@ -0,0 +1,1213 @@ +#include +#include +#include +#include +#include + +#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 -- 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[] = { + {"UTF-8", CP_UTF8}, + + {"ISO-8859-1:1998 (Latin-1, West Europe)", 0, 96, iso_8859_1}, + {"ISO-8859-2:1999 (Latin-2, East Europe)", 0, 96, iso_8859_2}, + {"ISO-8859-3:1999 (Latin-3, South Europe)", 0, 96, iso_8859_3}, + {"ISO-8859-4:1998 (Latin-4, North Europe)", 0, 96, iso_8859_4}, + {"ISO-8859-5:1999 (Latin/Cyrillic)", 0, 96, iso_8859_5}, + {"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}, + + {"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}, + {"CP852", 852}, + {"CP878", 20866}, + + {"Use font encoding", -1}, + + {0, 0} +}; + +static void link_font(WCHAR * line_tbl, WCHAR * font_tbl, WCHAR attr); + +void init_ucs(Conf *conf, struct unicode_data *ucsdata) +{ + int i, j; + bool used_dtf = false; + int vtmode; + + /* Decide on the Line and Font codepages */ + ucsdata->line_codepage = decode_codepage(conf_get_str(conf, + CONF_line_codepage)); + + if (ucsdata->font_codepage <= 0) { + ucsdata->font_codepage=0; + ucsdata->dbcs_screenfont=false; + } + + vtmode = conf_get_int(conf, CONF_vtmode); + if (vtmode == VT_OEMONLY) { + ucsdata->font_codepage = 437; + ucsdata->dbcs_screenfont = false; + 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 (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 (vtmode == VT_OEMANSI || vtmode == VT_XWINDOWS) + memcpy(ucsdata->unitab_scoacs, ucsdata->unitab_oemcp, + sizeof(ucsdata->unitab_scoacs)); + else + get_unitab(437, ucsdata->unitab_scoacs, 1); + + /* Collect line set ucs table */ + if (ucsdata->line_codepage == ucsdata->font_codepage && + (ucsdata->dbcs_screenfont || + vtmode == VT_POORMAN || ucsdata->font_codepage==0)) { + + /* For DBCS and POOR fonts force direct to font */ + used_dtf = true; + 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 (vtmode == VT_OEMANSI || vtmode == VT_XWINDOWS) + link_font(ucsdata->unitab_scoacs, ucsdata->unitab_oemcp, CSET_OEMCP); + + link_font(ucsdata->unitab_line, ucsdata->unitab_font, CSET_ACP); + link_font(ucsdata->unitab_scoacs, ucsdata->unitab_font, CSET_ACP); + link_font(ucsdata->unitab_xterm, ucsdata->unitab_font, CSET_ACP); + + if (vtmode == VT_OEMANSI || vtmode == VT_XWINDOWS) { + link_font(ucsdata->unitab_line, ucsdata->unitab_oemcp, CSET_OEMCP); + link_font(ucsdata->unitab_xterm, ucsdata->unitab_oemcp, CSET_OEMCP); + } + + if (ucsdata->dbcs_screenfont && + 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 (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 || !*cp_name) + return CP_UTF8; /* default */ + + for (cpi = cp_list; cpi->name; cpi++) { + s = cp_name; + d = cpi->name; + for (;;) { + while (*s && !isalnum(*s) && *s != ':') + s++; + while (*d && !isalnum(*d) && *d != ':') + d++; + if (*s == 0) { + codepage = cpi->codepage; + if (codepage == CP_UTF8) + goto break_break; + if (codepage == -1) + return codepage; + if (codepage == 0) { + codepage = 65536 + (cpi - cp_list); + goto break_break; + } + + if (GetCPInfo(codepage, &cpinfo) != 0) + goto break_break; + } + if (tolower((unsigned char)*s++) != tolower((unsigned char)*d++)) + break; + } + } + + d = cp_name; + if (tolower((unsigned char)d[0]) == 'c' && + tolower((unsigned char)d[1]) == 'p') + d += 2; + if (tolower((unsigned char)d[0]) == 'i' && + tolower((unsigned char)d[1]) == 'b' && + tolower((unsigned char)d[2]) == 'm') + d += 3; + for (s = d; *s >= '0' && *s <= '9'; s++); + if (*s == 0 && s != d) + codepage = atoi(d); /* CP999 or IBM999 */ + + if (codepage == CP_ACP) + codepage = GetACP(); + if (codepage == CP_OEMCP) + codepage = GetOEMCP(); + if (codepage > 65535) + codepage = -2; + + break_break:; + if (codepage != -1) { + if (codepage != CP_UTF8 && codepage < 65536) { + if (GetCPInfo(codepage, &cpinfo) == 0) { + 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, const wchar_t *wcstr, int wclen, + char *mbstr, int mblen, const char *defchr, + 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; + + #define WRITECH(chr) do \ + { \ + assert(p - mbstr < mblen); \ + *p++ = (char)(chr); \ + } while (0) + + if (ucsdata->uni_tbl && + (p1 = ucsdata->uni_tbl[(ch >> 8) & 0xFF]) != NULL && + (by = p1[ch & 0xFF]) != '\0') + WRITECH(by); + else if (ch < 0x80) + WRITECH(ch); + else if (defchr) + for (const char *q = defchr; *q; q++) + WRITECH(*q); +#if 1 + else + WRITECH('.'); +#endif + + #undef WRITECH + } + return p - mbstr; + } else { + int defused; + return WideCharToMultiByte(codepage, flags, wcstr, wclen, + mbstr, mblen, defchr, &defused); + } +} + +int mb_to_wc(int codepage, int flags, const char *mbstr, int mblen, + wchar_t *wcstr, int wclen) +{ + return MultiByteToWideChar(codepage, flags, mbstr, mblen, wcstr, wclen); +} + +bool is_dbcs_leadbyte(int codepage, char byte) +{ + return IsDBCSLeadByteEx(codepage, byte); +} diff --git a/windows/utils/capi.c b/windows/utils/capi.c deleted file mode 100644 index 3d38ae04..00000000 --- a/windows/utils/capi.c +++ /dev/null @@ -1,85 +0,0 @@ -/* - * windows/utils/capi.c: implementation of wincapi.h. - */ - -#include "putty.h" - -#include "putty.h" -#include "ssh.h" - -#include "wincapi.h" - -DEF_WINDOWS_FUNCTION(CryptProtectMemory); - -bool got_crypt(void) -{ - static bool attempted = false; - static bool successful; - static HMODULE crypt; - - if (!attempted) { - attempted = true; - crypt = load_system32_dll("crypt32.dll"); - successful = crypt && - GET_WINDOWS_FUNCTION(crypt, CryptProtectMemory); - } - return successful; -} - -char *capi_obfuscate_string(const char *realname) -{ - char *cryptdata; - int cryptlen; - unsigned char digest[32]; - char retbuf[65]; - int i; - - cryptlen = strlen(realname) + 1; - cryptlen += CRYPTPROTECTMEMORY_BLOCK_SIZE - 1; - cryptlen /= CRYPTPROTECTMEMORY_BLOCK_SIZE; - cryptlen *= CRYPTPROTECTMEMORY_BLOCK_SIZE; - - cryptdata = snewn(cryptlen, char); - memset(cryptdata, 0, cryptlen); - strcpy(cryptdata, realname); - - /* - * CRYPTPROTECTMEMORY_CROSS_PROCESS causes CryptProtectMemory to - * use the same key in all processes with this user id, meaning - * that the next PuTTY process calling this function with the same - * input will get the same data. - * - * (Contrast with CryptProtectData, which invents a new session - * key every time since its API permits returning more data than - * was input, so calling _that_ and hashing the output would not - * be stable.) - * - * We don't worry too much if this doesn't work for some reason. - * Omitting this step still has _some_ privacy value (in that - * another user can test-hash things to confirm guesses as to - * where you might be connecting to, but cannot invert SHA-256 in - * the absence of any plausible guess). So we don't abort if we - * can't call CryptProtectMemory at all, or if it fails. - */ - if (got_crypt()) - p_CryptProtectMemory(cryptdata, cryptlen, - CRYPTPROTECTMEMORY_CROSS_PROCESS); - - /* - * We don't want to give away the length of the hostname either, - * so having got it back out of CryptProtectMemory we now hash it. - */ - hash_simple(&ssh_sha256, make_ptrlen(cryptdata, cryptlen), digest); - - sfree(cryptdata); - - /* - * Finally, make printable. - */ - for (i = 0; i < 32; i++) { - sprintf(retbuf + 2*i, "%02x", digest[i]); - /* the last of those will also write the trailing NUL */ - } - - return dupstr(retbuf); -} diff --git a/windows/utils/cryptoapi.c b/windows/utils/cryptoapi.c new file mode 100644 index 00000000..1c017903 --- /dev/null +++ b/windows/utils/cryptoapi.c @@ -0,0 +1,85 @@ +/* + * windows/utils/cryptoapi.c: implementation of cryptoapi.h. + */ + +#include "putty.h" + +#include "putty.h" +#include "ssh.h" + +#include "cryptoapi.h" + +DEF_WINDOWS_FUNCTION(CryptProtectMemory); + +bool got_crypt(void) +{ + static bool attempted = false; + static bool successful; + static HMODULE crypt; + + if (!attempted) { + attempted = true; + crypt = load_system32_dll("crypt32.dll"); + successful = crypt && + GET_WINDOWS_FUNCTION(crypt, CryptProtectMemory); + } + return successful; +} + +char *capi_obfuscate_string(const char *realname) +{ + char *cryptdata; + int cryptlen; + unsigned char digest[32]; + char retbuf[65]; + int i; + + cryptlen = strlen(realname) + 1; + cryptlen += CRYPTPROTECTMEMORY_BLOCK_SIZE - 1; + cryptlen /= CRYPTPROTECTMEMORY_BLOCK_SIZE; + cryptlen *= CRYPTPROTECTMEMORY_BLOCK_SIZE; + + cryptdata = snewn(cryptlen, char); + memset(cryptdata, 0, cryptlen); + strcpy(cryptdata, realname); + + /* + * CRYPTPROTECTMEMORY_CROSS_PROCESS causes CryptProtectMemory to + * use the same key in all processes with this user id, meaning + * that the next PuTTY process calling this function with the same + * input will get the same data. + * + * (Contrast with CryptProtectData, which invents a new session + * key every time since its API permits returning more data than + * was input, so calling _that_ and hashing the output would not + * be stable.) + * + * We don't worry too much if this doesn't work for some reason. + * Omitting this step still has _some_ privacy value (in that + * another user can test-hash things to confirm guesses as to + * where you might be connecting to, but cannot invert SHA-256 in + * the absence of any plausible guess). So we don't abort if we + * can't call CryptProtectMemory at all, or if it fails. + */ + if (got_crypt()) + p_CryptProtectMemory(cryptdata, cryptlen, + CRYPTPROTECTMEMORY_CROSS_PROCESS); + + /* + * We don't want to give away the length of the hostname either, + * so having got it back out of CryptProtectMemory we now hash it. + */ + hash_simple(&ssh_sha256, make_ptrlen(cryptdata, cryptlen), digest); + + sfree(cryptdata); + + /* + * Finally, make printable. + */ + for (i = 0; i < 32; i++) { + sprintf(retbuf + 2*i, "%02x", digest[i]); + /* the last of those will also write the trailing NUL */ + } + + return dupstr(retbuf); +} diff --git a/windows/utils/security.c b/windows/utils/security.c index 69ac96f1..2d899b83 100644 --- a/windows/utils/security.c +++ b/windows/utils/security.c @@ -1,5 +1,5 @@ /* - * windows/utils/security.c: implementation of winsecur.h. + * windows/utils/security.c: implementation of security-api.h. */ #include @@ -7,7 +7,7 @@ #include "putty.h" -#include "winsecur.h" +#include "security-api.h" /* Initialised once, then kept around to reuse forever */ static PSID worldsid, networksid, usersid; diff --git a/windows/win-gui-seat.h b/windows/win-gui-seat.h new file mode 100644 index 00000000..c6b5fa96 --- /dev/null +++ b/windows/win-gui-seat.h @@ -0,0 +1,14 @@ +/* + * Small implementation of Seat and LogPolicy shared between window.c + * and windlg.c. + */ + +typedef struct WinGuiSeat WinGuiSeat; + +struct WinGuiSeat { + HWND term_hwnd; + Seat seat; + LogPolicy logpolicy; +}; + +extern const LogPolicyVtable win_gui_logpolicy_vt; /* in windlg.c */ diff --git a/windows/win_res.h b/windows/win_res.h deleted file mode 100644 index d34f6852..00000000 --- a/windows/win_res.h +++ /dev/null @@ -1,49 +0,0 @@ -/* - * 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 IDD_HK_ABSENT 114 -#define IDD_HK_WRONG 115 -#define IDD_HK_MOREINFO 116 - -#define IDN_LIST 1001 -#define IDN_COPY 1002 - -#define IDA_ICON 1001 -#define IDA_TEXT 1002 -#define IDA_LICENCE 1003 -#define IDA_WEB 1004 - -#define IDC_TAB 1001 -#define IDC_TABSTATIC1 1002 -#define IDC_TABSTATIC2 1003 -#define IDC_TABLIST 1004 -#define IDC_HELPBTN 1005 -#define IDC_ABOUT 1006 - -#define IDC_HK_ICON 98 -#define IDC_HK_TITLE 99 -#define IDC_HK_ACCEPT 1001 -#define IDC_HK_ONCE 1000 -#define IDC_HK_FINGERPRINT 1002 -#define IDC_HK_MOREINFO 1003 - -#define IDC_HKI_SHA256 1000 -#define IDC_HKI_MD5 1001 -#define IDC_HKI_PUBKEY 1002 - -#define ID_CUSTOM_CHMFILE 2000 -#define TYPE_CUSTOM_CHMFILE 2000 - -#endif diff --git a/windows/win_res.rc2 b/windows/win_res.rc2 deleted file mode 100644 index ccec3122..00000000 --- a/windows/win_res.rc2 +++ /dev/null @@ -1,137 +0,0 @@ -/* - * 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. - * - * This file has the more or less arbitrary extension '.rc2' to avoid - * IDEs taking it to be a top-level resource script in its own right - * (which has been known to happen if the extension was '.rc'), and - * also to avoid the resource compiler ignoring everything included - * from it (which happens if the extension is '.h'). - */ - -#include "win_res.h" - -IDI_MAINICON ICON "putty.ico" - -IDI_CFGICON ICON "puttycfg.ico" - -/* Accelerators used: clw */ -IDD_ABOUTBOX DIALOG DISCARDABLE 140, 40, 270, 136 -STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU -CAPTION "About PuTTY" -FONT 8, "MS Shell Dlg" -BEGIN - DEFPUSHBUTTON "&Close", IDOK, 216, 118, 48, 14 - PUSHBUTTON "View &Licence", IDA_LICENCE, 6, 118, 70, 14 - PUSHBUTTON "Visit &Web Site", IDA_WEB, 140, 118, 70, 14 - EDITTEXT IDA_TEXT, 10, 6, 250, 110, ES_READONLY | ES_MULTILINE | ES_CENTER, WS_EX_STATICEDGE -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, 326, 239 -STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU -CAPTION "PuTTY Licence" -FONT 8, "MS Shell Dlg" -BEGIN - DEFPUSHBUTTON "OK", IDOK, 148, 219, 44, 14 - - EDITTEXT IDA_TEXT, 10, 10, 306, 200, ES_READONLY | ES_MULTILINE | ES_LEFT, WS_EX_STATICEDGE -END - -/* No accelerators used */ -IDD_HK_ABSENT DIALOG DISCARDABLE 50, 50, 340, 148 -STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU -CAPTION "PuTTY Security Alert" -FONT 8, "MS Shell Dlg" -BEGIN - LTEXT "The server's host key is not cached in the registry. You have no", 100, 40, 20, 300, 8 - LTEXT "guarantee that the server is the computer you think it is.", 101, 40, 28, 300, 8 - LTEXT "The server's {KEYTYPE} key fingerprint is:", 102, 40, 40, 300, 8 - LTEXT "If you trust this host, press ""Accept"" to add the key to {APPNAME}'s", 103, 40, 60, 300, 8 - LTEXT "cache and carry on connecting.", 104, 40, 68, 300, 8 - LTEXT "If you want to carry on connecting just once, without adding the key", 105, 40, 80, 300, 8 - LTEXT "to the cache, press ""Connect Once"".", 106, 40, 88, 300, 8 - LTEXT "If you do not trust this host, press ""Cancel"" to abandon the connection.", 107, 40, 100, 300, 8 - - ICON "", IDC_HK_ICON, 10, 18, 0, 0 - - PUSHBUTTON "Cancel", IDCANCEL, 288, 128, 40, 14 - PUSHBUTTON "Accept", IDC_HK_ACCEPT, 168, 128, 40, 14 - PUSHBUTTON "Connect Once", IDC_HK_ONCE, 216, 128, 64, 14 - PUSHBUTTON "More info...", IDC_HK_MOREINFO, 60, 128, 64, 14 - PUSHBUTTON "Help", IDHELP, 12, 128, 40, 14 - - EDITTEXT IDC_HK_FINGERPRINT, 40, 48, 300, 12, ES_READONLY | ES_LEFT, 0 -END - -/* No accelerators used */ -IDD_HK_WRONG DIALOG DISCARDABLE 50, 50, 340, 188 -STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU -CAPTION "PuTTY Security Alert" -FONT 8, "MS Shell Dlg" -BEGIN - LTEXT "WARNING - POTENTIAL SECURITY BREACH!", IDC_HK_TITLE, 40, 20, 300, 12 - - LTEXT "The server's host key does not match the one {APPNAME} has cached in", 100, 40, 36, 300, 8 - LTEXT "the registry. This means that either the server administrator has", 101, 40, 44, 300, 8 - LTEXT "changed the host key, or you have actually connected to another", 102, 40, 52, 300, 8 - LTEXT "computer pretending to be the server.", 103, 40, 60, 300, 8 - LTEXT "The new {KEYTYPE} key fingerprint is:", 104, 40, 72, 300, 8 - LTEXT "If you were expecting this change and trust the new key, press", 105, 40, 92, 300, 8 - LTEXT """Accept"" to update {APPNAME}'s cache and continue connecting.", 106, 40, 100, 300, 8 - LTEXT "If you want to carry on connecting but without updating the cache,", 107, 40, 112, 300, 8 - LTEXT "press ""Connect Once"".", 108, 40, 120, 300, 8 - LTEXT "If you want to abandon the connection completely, press ""Cancel"".", 109, 40, 132, 300, 8 - LTEXT "Pressing ""Cancel"" is the ONLY guaranteed safe choice.", 110, 40, 140, 300, 8 - - ICON "", IDC_HK_ICON, 10, 16, 0, 0 - - PUSHBUTTON "Cancel", IDCANCEL, 288, 168, 40, 14 - PUSHBUTTON "Accept", IDC_HK_ACCEPT, 168, 168, 40, 14 - PUSHBUTTON "Connect Once", IDC_HK_ONCE, 216, 168, 64, 14 - PUSHBUTTON "More info...", IDC_HK_MOREINFO, 60, 168, 64, 14 - PUSHBUTTON "Help", IDHELP, 12, 168, 40, 14 - - EDITTEXT IDC_HK_FINGERPRINT, 40, 80, 300, 12, ES_READONLY | ES_LEFT, 0 -END - -/* Accelerators used: clw */ -IDD_HK_MOREINFO DIALOG DISCARDABLE 140, 40, 400, 156 -STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU -CAPTION "PuTTY: information about the server's host key" -FONT 8, "MS Shell Dlg" -BEGIN - LTEXT "SHA256 fingerprint:", 100, 12, 12, 80, 8 - EDITTEXT IDC_HKI_SHA256, 100, 10, 288, 12, ES_READONLY - LTEXT "MD5 fingerprint:", 101, 12, 28, 80, 8 - EDITTEXT IDC_HKI_MD5, 100, 26, 288, 12, ES_READONLY - LTEXT "Full public key:", 102, 12, 44, 376, 8 - EDITTEXT IDC_HKI_PUBKEY, 12, 54, 376, 64, ES_READONLY | ES_MULTILINE | ES_LEFT | ES_AUTOVSCROLL, WS_EX_STATICEDGE - DEFPUSHBUTTON "&Close", IDOK, 176, 130, 48, 14 -END - -#include "version.rc2" diff --git a/windows/wincapi.h b/windows/wincapi.h deleted file mode 100644 index 07f48cbe..00000000 --- a/windows/wincapi.h +++ /dev/null @@ -1,27 +0,0 @@ -/* - * wincapi.h: Windows Crypto API functions defined in wincapi.c that - * use the crypt32 library. Also centralises the machinery for - * dynamically loading that library, and our own functions using that - * in turn. - */ - -DECL_WINDOWS_FUNCTION(extern, BOOL, CryptProtectMemory, (LPVOID,DWORD,DWORD)); - -bool got_crypt(void); - -/* - * Function to obfuscate an input string into something usable as a - * pathname for a Windows named pipe. Uses CryptProtectMemory to make - * the obfuscation depend on a key Windows stores for the owning user, - * and then hashes the string as well to make it have a manageable - * length and be composed of filename-legal characters. - * - * Rationale: Windows's named pipes all live in the same namespace, so - * one user can see what pipes another user has open. This is an - * undesirable privacy leak: in particular, if we used unobfuscated - * names for the connection-sharing pipe names, it would permit one - * user to know what username@host another user is SSHing to. - * - * The returned string is dynamically allocated. - */ -char *capi_obfuscate_string(const char *realname); diff --git a/windows/wincfg.c b/windows/wincfg.c deleted file mode 100644 index fab3240f..00000000 --- a/windows/wincfg.c +++ /dev/null @@ -1,405 +0,0 @@ -/* - * wincfg.c - the Windows-specific parts of the PuTTY configuration - * box. - */ - -#include -#include - -#include "putty.h" -#include "dialog.h" -#include "storage.h" - -static void about_handler(union control *ctrl, dlgparam *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, dlgparam *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, dlgparam *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, bool has_help, - bool midsession, int protocol) -{ - const struct BackendVtable *backvt; - bool resize_forbidden = false; - 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), - conf_checkbox_handler, - I(CONF_scrollbar_in_fullscreen)); - /* - * Really this wants to go just after `Display scrollbar'. See - * if we can find that control, and do some shuffling. - */ - { - int i; - for (i = 0; i < s->ncontrols; i++) { - c = s->ctrls[i]; - if (c->generic.type == CTRL_CHECKBOX && - c->generic.context.i == CONF_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), - conf_checkbox_handler, I(CONF_compose_key)); - ctrl_checkbox(s, "Control-Alt is different from AltGr", 'd', - HELPCTX(keyboard_ctrlalt), - conf_checkbox_handler, I(CONF_ctrlaltkeys)); - - /* - * Windows allows an arbitrary .WAV to be played as a bell, and - * also the use of the PC speaker. For this we must search the - * existing controlset for the radio-button set controlling the - * `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 == CONF_beep) { - assert(c->generic.handler == conf_radiobutton_handler); - c->radio.nbuttons += 2; - c->radio.buttons = - sresize(c->radio.buttons, c->radio.nbuttons, char *); - c->radio.buttons[c->radio.nbuttons-1] = - dupstr("Play a custom sound file"); - 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), - conf_filesel_handler, I(CONF_bell_wavefile)); - - /* - * While we've got this box open, taskbar flashing on a bell is - * also Windows-specific. - */ - ctrl_radiobuttons(s, "Taskbar/caption indication on bell:", 'i', 3, - HELPCTX(bell_taskbar), - conf_radiobutton_handler, - I(CONF_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), - conf_checkbox_handler, I(CONF_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), - conf_radiobutton_handler, - I(CONF_font_quality), - "Antialiased", I(FQ_ANTIALIASED), - "Non-Antialiased", I(FQ_NONANTIALIASED), - "ClearType", I(FQ_CLEARTYPE), - "Default", I(FQ_DEFAULT), NULL); - - /* - * 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), - conf_checkbox_handler, - I(CONF_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 == CONF_vtmode) { - assert(c->generic.handler == conf_radiobutton_handler); - c->radio.nbuttons += 3; - c->radio.buttons = - sresize(c->radio.buttons, c->radio.nbuttons, char *); - c->radio.buttons[c->radio.nbuttons-3] = - dupstr("Font has XWindows encoding"); - 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/Copy", "format", - "Formatting of copied characters"); - ctrl_checkbox(s, "Copy to clipboard in RTF as well as plain text", 'f', - HELPCTX(copy_rtf), - conf_checkbox_handler, I(CONF_rtf_paste)); - - /* - * Windows often has no middle button, so we supply a selection - * mode in which the more critical Paste action is available on - * the right button instead. - */ - s = ctrl_getset(b, "Window/Selection", "mouse", - "Control use of mouse"); - ctrl_radiobuttons(s, "Action of mouse buttons:", 'm', 1, - HELPCTX(selection_buttons), - conf_radiobutton_handler, - I(CONF_mouse_is_xterm), - "Windows (Middle extends, Right brings up menu)", I(2), - "Compromise (Middle extends, Right pastes)", I(0), - "xterm (Right extends, Middle pastes)", I(1), NULL); - /* - * This really ought to go at the _top_ of its box, not the - * 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), - conf_checkbox_handler, I(CONF_try_palette)); - ctrl_checkbox(s, "Use system colours", 's', - HELPCTX(colours_system), - conf_checkbox_handler, I(CONF_system_colour)); - - - /* - * Resize-by-changing-font is a Windows insanity. - */ - - backvt = backend_vt_from_proto(protocol); - if (backvt) - resize_forbidden = (backvt->flags & BACKEND_RESIZE_FORBIDDEN); - if (!midsession || !resize_forbidden) { - s = ctrl_getset(b, "Window", "size", "Set the size of the window"); - ctrl_radiobuttons(s, "When window is resized:", 'z', 1, - HELPCTX(window_resize), - conf_radiobutton_handler, - I(CONF_resize_action), - "Change the number of rows and columns", I(RESIZE_TERM), - "Change the size of the font", I(RESIZE_FONT), - "Change font size only when maximised", I(RESIZE_EITHER), - "Forbid resizing completely", I(RESIZE_DISABLED), NULL); - } - - /* - * 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), - conf_checkbox_handler, I(CONF_alt_f4)); - ctrl_checkbox(s, "System menu appears on ALT-Space", 'y', - HELPCTX(behaviour_altspace), - conf_checkbox_handler, I(CONF_alt_space)); - ctrl_checkbox(s, "System menu appears on ALT alone", 'l', - HELPCTX(behaviour_altonly), - conf_checkbox_handler, I(CONF_alt_only)); - ctrl_checkbox(s, "Ensure window is always on top", 'e', - HELPCTX(behaviour_alwaysontop), - conf_checkbox_handler, I(CONF_alwaysontop)); - ctrl_checkbox(s, "Full screen on Alt-Enter", 'f', - HELPCTX(behaviour_altenter), - conf_checkbox_handler, - I(CONF_fullscreenonaltenter)); - - /* - * Windows supports a local-command proxy. This also means we - * must adjust the text on the `Telnet command' control. - */ - 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 == CONF_proxy_type) { - assert(c->generic.handler == conf_radiobutton_handler); - c->radio.nbuttons++; - c->radio.buttons = - sresize(c->radio.buttons, c->radio.nbuttons, char *); - c->radio.buttons[c->radio.nbuttons-1] = - dupstr("Local"); - 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 == CONF_proxy_telnet_command) { - assert(c->generic.handler == conf_editbox_handler); - sfree(c->generic.label); - c->generic.label = dupstr("Telnet command, or local" - " proxy command"); - break; - } - } - } - - /* - * $XAUTHORITY is not reliable on Windows, so we provide a - * means to override it. - */ - if (!midsession && backend_vt_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), - conf_filesel_handler, I(CONF_xauthfile)); - } -} diff --git a/windows/wincliloop.c b/windows/wincliloop.c deleted file mode 100644 index 26a4d3aa..00000000 --- a/windows/wincliloop.c +++ /dev/null @@ -1,136 +0,0 @@ -#include "putty.h" - -void cli_main_loop(cliloop_pre_t pre, cliloop_post_t post, void *ctx) -{ - SOCKET *sklist = NULL; - size_t skcount = 0, sksize = 0; - unsigned long now, next, then; - now = GETTICKCOUNT(); - - while (true) { - int nhandles; - HANDLE *handles; - DWORD n; - DWORD ticks; - - const HANDLE *extra_handles = NULL; - size_t n_extra_handles = 0; - if (!pre(ctx, &extra_handles, &n_extra_handles)) - break; - - if (toplevel_callback_pending()) { - ticks = 0; - next = now; - } else if (run_timers(now, &next)) { - then = now; - now = GETTICKCOUNT(); - if (now - then > next - then) - ticks = 0; - else - ticks = next - now; - } else { - ticks = INFINITE; - /* no need to initialise next here because we can never - * get WAIT_TIMEOUT */ - } - - handles = handle_get_events(&nhandles); - size_t winselcli_index = -(size_t)1; - size_t extra_base = nhandles; - if (winselcli_event != INVALID_HANDLE_VALUE) { - winselcli_index = extra_base++; - handles = sresize(handles, extra_base, HANDLE); - handles[winselcli_index] = winselcli_event; - } - size_t total_handles = extra_base + n_extra_handles; - handles = sresize(handles, total_handles, HANDLE); - for (size_t i = 0; i < n_extra_handles; i++) - handles[extra_base + i] = extra_handles[i]; - - n = WaitForMultipleObjects(total_handles, handles, false, ticks); - - size_t extra_handle_index = n_extra_handles; - - if ((unsigned)(n - WAIT_OBJECT_0) < (unsigned)nhandles) { - handle_got_event(handles[n - WAIT_OBJECT_0]); - } else if (winselcli_event != INVALID_HANDLE_VALUE && - n == WAIT_OBJECT_0 + winselcli_index) { - WSANETWORKEVENTS things; - SOCKET socket; - 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. */ - sgrowarray(sklist, sksize, i); - - /* 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(NOISE_SOURCE_IOID, socket); - - 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); - } - } - } - } else if (n >= WAIT_OBJECT_0 + extra_base && - n < WAIT_OBJECT_0 + extra_base + n_extra_handles) { - extra_handle_index = n - (WAIT_OBJECT_0 + extra_base); - } - - run_toplevel_callbacks(); - - if (n == WAIT_TIMEOUT) { - now = next; - } else { - now = GETTICKCOUNT(); - } - - sfree(handles); - - if (!post(ctx, extra_handle_index)) - break; - } - - sfree(sklist); -} - -bool cliloop_null_pre(void *vctx, const HANDLE **eh, size_t *neh) -{ return true; } -bool cliloop_null_post(void *vctx, size_t ehi) { return true; } diff --git a/windows/wincons.c b/windows/wincons.c deleted file mode 100644 index 414167b4..00000000 --- a/windows/wincons.c +++ /dev/null @@ -1,452 +0,0 @@ -/* - * wincons.c - various interactive-prompt routines shared between - * the Windows console PuTTY tools - */ - -#include -#include - -#include "putty.h" -#include "storage.h" -#include "ssh.h" -#include "console.h" - -void cleanup_exit(int code) -{ - /* - * Clean up. - */ - sk_cleanup(); - - random_save_seed(); - - exit(code); -} - -void console_print_error_msg(const char *prefix, const char *msg) -{ - fputs(prefix, stderr); - fputs(": ", stderr); - fputs(msg, stderr); - fputc('\n', stderr); - fflush(stderr); -} - -int console_verify_ssh_host_key( - Seat *seat, const char *host, int port, const char *keytype, - char *keystr, const char *keydisp, char **fingerprints, - void (*callback)(void *ctx, int result), void *ctx) -{ - int ret; - HANDLE hin; - DWORD savemode, i; - const char *common_fmt, *intro, *prompt; - - 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 */ - common_fmt = hk_wrongmsg_common_fmt; - intro = hk_wrongmsg_interactive_intro; - prompt = hk_wrongmsg_interactive_prompt; - } else { /* key was absent */ - common_fmt = hk_absentmsg_common_fmt; - intro = hk_absentmsg_interactive_intro; - prompt = hk_absentmsg_interactive_prompt; - } - - FingerprintType fptype_default = - ssh2_pick_default_fingerprint(fingerprints); - - fprintf(stderr, common_fmt, keytype, fingerprints[fptype_default]); - if (console_batch_mode) { - fputs(console_abandoned_msg, stderr); - return 0; - } - - fputs(intro, stderr); - fflush(stderr); - - while (true) { - fputs(prompt, stderr); - fflush(stderr); - - line[0] = '\0'; /* fail safe if ReadFile returns no data */ - - 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] == 'i' || line[0] == 'I') { - fprintf(stderr, "Full public key:\n%s\n", keydisp); - if (fingerprints[SSH_FPTYPE_SHA256]) - fprintf(stderr, "SHA256 key fingerprint:\n%s\n", - fingerprints[SSH_FPTYPE_SHA256]); - if (fingerprints[SSH_FPTYPE_MD5]) - fprintf(stderr, "MD5 key fingerprint:\n%s\n", - fingerprints[SSH_FPTYPE_MD5]); - } else { - break; - } - } - - /* In case of misplaced reflexes from another program, also recognise 'q' - * as 'abandon connection rather than trust this key' */ - if (line[0] != '\0' && line[0] != '\r' && line[0] != '\n' && - line[0] != 'q' && line[0] != 'Q') { - if (line[0] == 'y' || line[0] == 'Y') - store_host_key(host, port, keytype, keystr); - return 1; - } else { - fputs(console_abandoned_msg, stderr); - return 0; - } -} - -int console_confirm_weak_crypto_primitive( - Seat *seat, const char *algtype, const char *algname, - void (*callback)(void *ctx, int result), void *ctx) -{ - HANDLE hin; - DWORD savemode, i; - - char line[32]; - - fprintf(stderr, weakcrypto_msg_common_fmt, algtype, algname); - - if (console_batch_mode) { - fputs(console_abandoned_msg, stderr); - return 0; - } - - fputs(console_continue_prompt, stderr); - 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 { - fputs(console_abandoned_msg, stderr); - return 0; - } -} - -int console_confirm_weak_cached_hostkey( - Seat *seat, const char *algname, const char *betteralgs, - void (*callback)(void *ctx, int result), void *ctx) -{ - HANDLE hin; - DWORD savemode, i; - - char line[32]; - - fprintf(stderr, weakhk_msg_common_fmt, algname, betteralgs); - - if (console_batch_mode) { - fputs(console_abandoned_msg, stderr); - return 0; - } - - fputs(console_continue_prompt, stderr); - 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 { - fputs(console_abandoned_msg, stderr); - return 0; - } -} - -bool is_interactive(void) -{ - return is_console_handle(GetStdHandle(STD_INPUT_HANDLE)); -} - -bool console_antispoof_prompt = true; -bool console_set_trust_status(Seat *seat, bool trusted) -{ - if (console_batch_mode || !is_interactive() || !console_antispoof_prompt) { - /* - * In batch mode, we don't need to worry about the server - * mimicking our interactive authentication, because the user - * already knows not to expect any. - * - * If standard input isn't connected to a terminal, likewise, - * because even if the server did send a spoof authentication - * prompt, the user couldn't respond to it via the terminal - * anyway. - * - * We also vacuously return success if the user has purposely - * disabled the antispoof prompt. - */ - return true; - } - - return false; -} - -/* - * 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 console_askappend(LogPolicy *lp, 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 as of " PGP_MASTER_KEY_YEAR - " (" PGP_MASTER_KEY_DETAILS "):\n" - " " PGP_MASTER_KEY_FP "\n\n" - "Previous Master Key (" PGP_PREV_MASTER_KEY_YEAR - ", " PGP_PREV_MASTER_KEY_DETAILS "):\n" - " " PGP_PREV_MASTER_KEY_FP "\n", stdout); -} - -void console_logging_error(LogPolicy *lp, const char *string) -{ - /* Ordinary Event Log entries are displayed in the same way as - * logging errors, but only in verbose mode */ - fprintf(stderr, "%s\n", string); - fflush(stderr); -} - -void console_eventlog(LogPolicy *lp, const char *string) -{ - /* Ordinary Event Log entries are displayed in the same way as - * logging errors, but only in verbose mode */ - if (lp_verbose(lp)) - console_logging_error(lp, string); -} - -StripCtrlChars *console_stripctrl_new( - Seat *seat, BinarySink *bs_out, SeatInteractionContext sic) -{ - return stripctrl_new(bs_out, false, 0); -} - -static void console_write(HANDLE hout, ptrlen data) -{ - DWORD dummy; - WriteFile(hout, data.ptr, data.len, &dummy, NULL); -} - -int console_get_userpass_input(prompts_t *p) -{ - HANDLE hin = INVALID_HANDLE_VALUE, hout = INVALID_HANDLE_VALUE; - 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++) - prompt_set_result(p->prompts[i], ""); - } - - /* - * The prompts_t might contain a message to be displayed but no - * actual prompt. More usually, though, it will contain - * 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) { - ptrlen plname = ptrlen_from_asciz(p->name); - console_write(hout, plname); - if (!ptrlen_endswith(plname, PTRLEN_LITERAL("\n"), NULL)) - console_write(hout, PTRLEN_LITERAL("\n")); - } - /* ...but we always print any `instruction'. */ - if (p->instruction) { - ptrlen plinst = ptrlen_from_asciz(p->instruction); - console_write(hout, plinst); - if (!ptrlen_endswith(plinst, PTRLEN_LITERAL("\n"), NULL)) - console_write(hout, PTRLEN_LITERAL("\n")); - } - - for (curr_prompt = 0; curr_prompt < p->n_prompts; curr_prompt++) { - - DWORD savemode, newmode; - prompt_t *pr = p->prompts[curr_prompt]; - - 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_write(hout, ptrlen_from_asciz(pr->prompt)); - - bool failed = false; - while (1) { - /* - * Amount of data to try to read from the console in one - * go. This isn't completely arbitrary: a user reported - * that trying to read more than 31366 bytes at a time - * would fail with ERROR_NOT_ENOUGH_MEMORY on Windows 7, - * and Ruby's Win32 support module has evidence of a - * similar workaround: - * - * https://github.com/ruby/ruby/blob/0aa5195262d4193d3accf3e6b9bad236238b816b/win32/win32.c#L6842 - * - * To keep things simple, I stick with a nice round power - * of 2 rather than trying to go to the very limit of that - * bug. (We're typically reading user passphrases and the - * like here, so even this much is overkill really.) - */ - DWORD toread = 16384; - - size_t prev_result_len = pr->result->len; - void *ptr = strbuf_append(pr->result, toread); - - DWORD ret = 0; - if (!ReadFile(hin, ptr, toread, &ret, NULL) || ret == 0) { - failed = true; - break; - } - - strbuf_shrink_to(pr->result, prev_result_len + ret); - if (strbuf_chomp(pr->result, '\n')) { - strbuf_chomp(pr->result, '\r'); - break; - } - } - - SetConsoleMode(hin, savemode); - - if (!pr->echo) - console_write(hout, PTRLEN_LITERAL("\r\n")); - - if (failed) { - return 0; /* failure due to read error */ - } - } - - return 1; /* success */ -} diff --git a/windows/winctrls.c b/windows/winctrls.c deleted file mode 100644 index 59129eab..00000000 --- a/windows/winctrls.c +++ /dev/null @@ -1,2600 +0,0 @@ -/* - * 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 -#include - -#include "putty.h" -#include "misc.h" -#include "dialog.h" - -#include - -#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 - -DECL_WINDOWS_FUNCTION(static, void, InitCommonControls, (void)); -DECL_WINDOWS_FUNCTION(static, BOOL, MakeDragList, (HWND)); -DECL_WINDOWS_FUNCTION(static, int, LBItemFromPt, (HWND, POINT, BOOL)); -DECL_WINDOWS_FUNCTION(static, void, DrawInsert, (HWND, HWND, int)); - -void init_common_controls(void) -{ - HMODULE comctl32_module = load_system32_dll("comctl32.dll"); - GET_WINDOWS_FUNCTION(comctl32_module, InitCommonControls); - GET_WINDOWS_FUNCTION(comctl32_module, MakeDragList); - GET_WINDOWS_FUNCTION(comctl32_module, LBItemFromPt); - GET_WINDOWS_FUNCTION(comctl32_module, DrawInsert); - p_InitCommonControls(); -} - -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)(ULONG_PTR)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, bool 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; - - r.left = GAPBETWEEN; - r.top = cp->ypos; - if (text) { - r.right = cp->width; - r.bottom = STATICHEIGHT; - cp->ypos += r.bottom + GAPWITHIN; - doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, text, id); - } else { - r.right = r.bottom = 0; - } - - 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 *); - if (!btext) - break; - (void) va_arg(ap, int); /* id */ - 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 *); - if (!btext) - break; - (void) va_arg(ap, int); /* id */ - 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, 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 *); - if (!btext) - break; - (void) va_arg(ap, int); /* id */ - nbuttons++; - } - 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 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; - - sfree(pwidths); - - 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, bool 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, bool 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 = doctl(cp, r, "LISTBOX", - WS_CHILD | WS_VISIBLE | WS_TABSTOP | - WS_VSCROLL | LBS_HASSTRINGS | LBS_USETABSTOPS, - WS_EX_CLIENTEDGE, - "", listid); - p_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 = p_LBItemFromPt(hwnd, cursor, scroll); - if (ret == -1) - return ret; - ret = p_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 = p_LBItemFromPt(hwnd, uppoint, false); - downitem = p_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, - bool 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 = p_LBItemFromPt(dlm->hWnd, dlm->ptCursor, true); - hdl->dragging = false; - /* XXX hack Q183115 */ - SetWindowLongPtr(hwnd, DWLP_MSGRESULT, true); - ret |= 1; break; - case DL_CANCELDRAG: - p_DrawInsert(hwnd, dlm->hWnd, -1); /* Clear arrow */ - SendDlgItemMessage(hwnd, hdl->listid, - LB_DELETESTRING, hdl->dummyitem, 0); - hdl->dragging = false; - ret |= 1; break; - case DL_DRAGGING: - hdl->dragging = true; - dest = pl_itemfrompt(dlm->hWnd, dlm->ptCursor, true); - if (dest > hdl->dummyitem) dest = hdl->dummyitem; - p_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; - p_DrawInsert (hwnd, dlm->hWnd, -1); - } - SendDlgItemMessage(hwnd, hdl->listid, - LB_DELETESTRING, hdl->dummyitem, 0); - if (hdl->dragging) { - hdl->dragging = false; - 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); -} - -static void move_windows(HWND hwnd, int base_id, int num_ids, LONG dy) -{ - if (!dy) - return; - for (int i = 0; i < num_ids; i++) { - HWND win = GetDlgItem(hwnd, base_id + i); - - RECT rect; - if (!GetWindowRect(win, &rect)) - continue; - - POINT p; - p.x = rect.left; - p.y = rect.top + dy; - if (!ScreenToClient(hwnd, &p)) - continue; - - SetWindowPos(win, NULL, p.x, p.y, 0, 0, - SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER); - } -} - -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, align_id_relative; - 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 = c->align_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 = c->align_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; - - /* For vertical alignment purposes, the most relevant control - * in a group is usually the last one. But that can be - * overridden occasionally. */ - align_id_relative = -1; - - /* - * 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); - data = fontspec_new("", false, 0, 0); - sfree(escaped); - break; - default: - unreachable("bad control type in winctrl_layout"); - } - - /* Translate the original align_id_relative of -1 into n-1 */ - if (align_id_relative < 0) - align_id_relative += num_ids; - - /* - * 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->align_id = c->base_id + align_id_relative; - 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 (ctrl->generic.align_next_to) { - /* - * Implement align_next_to by looking at the y extents - * of the two controls now that both are created, and - * moving one or the other downwards so that they're - * centred on a common horizontal line. - */ - struct winctrl *c2 = winctrl_findbyctrl( - wc, ctrl->generic.align_next_to); - HWND win1 = GetDlgItem(pos.hwnd, c->align_id); - HWND win2 = GetDlgItem(pos.hwnd, c2->align_id); - RECT rect1, rect2; - if (win1 && win2 && - GetWindowRect(win1, &rect1) && - GetWindowRect(win2, &rect2)) { - LONG top = (rect1.top < rect2.top ? rect1.top : rect2.top); - LONG bottom = (rect1.bottom > rect2.bottom ? - rect1.bottom : rect2.bottom); - move_windows(pos.hwnd, c->base_id, c->num_ids, - (top + bottom - rect1.top - rect1.bottom)/2); - move_windows(pos.hwnd, c2->base_id, c2->num_ids, - (top + bottom - rect2.top - rect2.bottom)/2); - } - } - } else { - sfree(data); - } - - 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, - bool 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, dlgparam *dp) -{ - return dp->focused == ctrl ? dp->lastfocused : dp->focused; -} - -/* - * The dialog-box procedure calls this function to handle Windows - * messages on a control we manage. - */ -bool winctrl_handle_command(struct dlgparam *dp, UINT msg, - WPARAM wParam, LPARAM lParam) -{ - struct winctrl *c; - union control *ctrl; - int i, id; - bool 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 false; - - /* - * 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 false; /* 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 false; /* 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 = false; - 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)) { - fs = fontspec_new(lf.lfFaceName, (lf.lfWeight == FW_BOLD), - cf.iPointSize / 10, lf.lfCharSet); - dlg_fontsel_set(ctrl, dp, fs); - fontspec_free(fs); - - ctrl->generic.handler(ctrl, dp, dp->data, EVENT_VALCHANGE); - } - } - break; - } - - /* - * 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. - */ -bool 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 false; /* 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 false; /* no help available for this ctrl */ - - launch_help(hwnd, c->ctrl->generic.helpctx.p); - return true; -} - -/* - * 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; -} - -bool dlg_is_visible(union control *ctrl, dlgparam *dp) -{ - /* - * In this implementation of the dialog box, we physically - * uncreate controls that aren't in a visible panel of the config - * box. So we can tell if a control is visible just by checking if - * it _exists_. - */ - return dlg_findbyctrl(dp, ctrl) != NULL; -} - -void dlg_radiobutton_set(union control *ctrl, dlgparam *dp, int whichbutton) -{ - 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, dlgparam *dp) -{ - 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; - unreachable("no radio button was checked"); -} - -void dlg_checkbox_set(union control *ctrl, dlgparam *dp, bool checked) -{ - struct winctrl *c = dlg_findbyctrl(dp, ctrl); - assert(c && c->ctrl->generic.type == CTRL_CHECKBOX); - CheckDlgButton(dp->hwnd, c->base_id, checked); -} - -bool dlg_checkbox_get(union control *ctrl, dlgparam *dp) -{ - 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, dlgparam *dp, char const *text) -{ - struct winctrl *c = dlg_findbyctrl(dp, ctrl); - assert(c && c->ctrl->generic.type == CTRL_EDITBOX); - SetDlgItemText(dp->hwnd, c->base_id+1, text); -} - -char *dlg_editbox_get(union control *ctrl, dlgparam *dp) -{ - struct winctrl *c = dlg_findbyctrl(dp, ctrl); - assert(c && c->ctrl->generic.type == CTRL_EDITBOX); - return GetDlgItemText_alloc(dp->hwnd, c->base_id+1); -} - -/* The `listbox' functions can also apply to combo boxes. */ -void dlg_listbox_clear(union control *ctrl, dlgparam *dp) -{ - 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, dlgparam *dp, int index) -{ - 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, dlgparam *dp, char const *text) -{ - 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, dlgparam *dp, - char const *text, int id) -{ - 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, dlgparam *dp, int index) -{ - 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, dlgparam *dp) -{ - 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; -} - -bool dlg_listbox_issel(union control *ctrl, dlgparam *dp, int index) -{ - 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, dlgparam *dp, int index) -{ - 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, dlgparam *dp, char const *text) -{ - 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, dlgparam *dp, char const *text) -{ - 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: - unreachable("bad control type in label_change"); - } - if (escaped) { - SetDlgItemText(dp->hwnd, id, escaped); - sfree(escaped); - } -} - -void dlg_filesel_set(union control *ctrl, dlgparam *dp, Filename *fn) -{ - struct winctrl *c = dlg_findbyctrl(dp, ctrl); - assert(c && c->ctrl->generic.type == CTRL_FILESELECT); - SetDlgItemText(dp->hwnd, c->base_id+1, fn->path); -} - -Filename *dlg_filesel_get(union control *ctrl, dlgparam *dp) -{ - struct winctrl *c = dlg_findbyctrl(dp, ctrl); - char *tmp; - Filename *ret; - assert(c && c->ctrl->generic.type == CTRL_FILESELECT); - tmp = GetDlgItemText_alloc(dp->hwnd, c->base_id+1); - ret = filename_from_str(tmp); - sfree(tmp); - return ret; -} - -void dlg_fontsel_set(union control *ctrl, dlgparam *dp, FontSpec *fs) -{ - char *buf, *boldstr; - struct winctrl *c = dlg_findbyctrl(dp, ctrl); - assert(c && c->ctrl->generic.type == CTRL_FONTSELECT); - - fontspec_free((FontSpec *)c->data); - c->data = fontspec_copy(fs); - - boldstr = (fs->isbold ? "bold, " : ""); - if (fs->height == 0) - buf = dupprintf("Font: %s, %sdefault height", fs->name, boldstr); - 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); -} - -FontSpec *dlg_fontsel_get(union control *ctrl, dlgparam *dp) -{ - struct winctrl *c = dlg_findbyctrl(dp, ctrl); - assert(c && c->ctrl->generic.type == CTRL_FONTSELECT); - return fontspec_copy((FontSpec *)c->data); -} - -/* - * Bracketing a large set of updates in these two functions will - * cause the front end (if possible) to delay updating the screen - * until it's all complete, thus avoiding flicker. - */ -void dlg_update_start(union control *ctrl, dlgparam *dp) -{ - 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, dlgparam *dp) -{ - 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, dlgparam *dp) -{ - struct winctrl *c = dlg_findbyctrl(dp, ctrl); - int id; - HWND ctl; - if (!c) - return; - switch (ctrl->generic.type) { - case CTRL_EDITBOX: id = c->base_id + 1; break; - case CTRL_RADIO: - for (id = c->base_id + ctrl->radio.nbuttons; id > 1; id--) - if (IsDlgButtonChecked(dp->hwnd, id)) - 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(dlgparam *dp) -{ - MessageBeep(0); -} - -void dlg_error_msg(dlgparam *dp, const char *msg) -{ - 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(dlgparam *dp, int value) -{ - dp->ended = true; - dp->endresult = value; -} - -void dlg_refresh(union control *ctrl, dlgparam *dp) -{ - 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, dlgparam *dp, int r, int g, int b) -{ - dp->coloursel_wanted = true; - dp->coloursel_result.r = r; - dp->coloursel_result.g = g; - dp->coloursel_result.b = b; -} - -bool dlg_coloursel_results(union control *ctrl, dlgparam *dp, - int *r, int *g, int *b) -{ - if (dp->coloursel_result.ok) { - *r = dp->coloursel_result.r; - *g = dp->coloursel_result.g; - *b = dp->coloursel_result.b; - return true; - } else - return false; -} - -void dlg_auto_set_fixed_pitch_flag(dlgparam *dp) -{ - Conf *conf = (Conf *)dp->data; - FontSpec *fs; - int quality; - HFONT hfont; - HDC hdc; - TEXTMETRIC tm; - bool 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 'Conf *' - * as dp->data. - */ - - quality = conf_get_int(conf, CONF_font_quality); - fs = conf_get_fontspec(conf, CONF_font); - - hfont = CreateFont(0, 0, 0, 0, FW_DONTCARE, false, false, false, - DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, - CLIP_DEFAULT_PRECIS, FONT_QUALITY(quality), - FIXED_PITCH | FF_DONTCARE, fs->name); - hdc = GetDC(NULL); - if (hdc && SelectObject(hdc, hfont) && GetTextMetrics(hdc, &tm)) { - /* Note that the TMPF_FIXED_PITCH bit is defined upside down :-( */ - is_var = (tm.tmPitchAndFamily & TMPF_FIXED_PITCH); - } else { - is_var = false; /* assume it's basically normal */ - } - if (hdc) - ReleaseDC(NULL, hdc); - if (hfont) - DeleteObject(hfont); - - if (is_var) - dp->fixed_pitch_fonts = false; -} - -bool dlg_get_fixed_pitch_flag(dlgparam *dp) -{ - return dp->fixed_pitch_fonts; -} - -void dlg_set_fixed_pitch_flag(dlgparam *dp, bool flag) -{ - dp->fixed_pitch_fonts = flag; -} - -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->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) -{ - sfree(dp->wintitle); - sfree(dp->errtitle); -} diff --git a/windows/windlg.c b/windows/windlg.c deleted file mode 100644 index 9c5fdb76..00000000 --- a/windows/windlg.c +++ /dev/null @@ -1,1156 +0,0 @@ -/* - * windlg.c - dialogs for PuTTY(tel), including the configuration dialog. - */ - -#include -#include -#include -#include -#include -#include - -#include "putty.h" -#include "ssh.h" -#include "win_res.h" -#include "winseat.h" -#include "storage.h" -#include "dialog.h" -#include "licence.h" - -#include -#include -#include - -#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; - -#define LOGEVENT_INITIAL_MAX 128 -#define LOGEVENT_CIRCULAR_MAX 128 - -static char *events_initial[LOGEVENT_INITIAL_MAX]; -static char *events_circular[LOGEVENT_CIRCULAR_MAX]; -static int ninitial = 0, ncircular = 0, circular_first = 0; - -#define PRINTER_DISABLED_STRING "None (printing disabled)" - -void force_normal(HWND hwnd) -{ - static bool recurse = false; - - WINDOWPLACEMENT wp; - - if (recurse) - return; - recurse = true; - - wp.length = sizeof(wp); - if (GetWindowPlacement(hwnd, &wp) && wp.showCmd == SW_SHOWMAXIMIZED) { - wp.showCmd = SW_SHOWNORMAL; - SetWindowPlacement(hwnd, &wp); - } - recurse = false; -} - -static char *getevent(int i) -{ - if (i < ninitial) - return events_initial[i]; - if ((i -= ninitial) < ncircular) - return events_circular[(circular_first + i) % LOGEVENT_CIRCULAR_MAX]; - return NULL; -} - -static HWND logbox; -HWND event_log_window(void) { return logbox; } - -static INT_PTR 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 < ninitial; i++) - SendDlgItemMessage(hwnd, IDN_LIST, LB_ADDSTRING, - 0, (LPARAM) events_initial[i]); - for (i = 0; i < ncircular; i++) - SendDlgItemMessage(hwnd, IDN_LIST, LB_ADDSTRING, - 0, (LPARAM) events_circular[(circular_first + i) % LOGEVENT_CIRCULAR_MAX]); - 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(getevent(selitems[i])) + sizeof(sel_nl); - - clipdata = snewn(size, char); - if (clipdata) { - char *p = clipdata; - for (i = 0; i < count; i++) { - char *q = getevent(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(CLIP_SYSTEM, clipdata, size, true); - sfree(clipdata); - } - sfree(selitems); - - for (i = 0; i < (ninitial + ncircular); 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_PTR 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); - SetDlgItemText(hwnd, IDA_TEXT, LICENCE_TEXT("\r\n\r\n")); - 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_PTR 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); - char *buildinfo_text = buildinfo("\r\n"); - char *text = dupprintf - ("%s\r\n\r\n%s\r\n\r\n%s\r\n\r\n%s", - appname, ver, buildinfo_text, - "\251 " SHORT_COPYRIGHT_DETAILS ". All rights reserved."); - sfree(buildinfo_text); - SetDlgItemText(hwnd, IDA_TEXT, text); - MakeDlgItemBorderless(hwnd, IDA_TEXT); - sfree(text); - 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", - "https://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_PTR 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_PTR 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; - char *firstpath = 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; - firstpath = s->pathname; - } - - path = s->pathname; - } - - /* - * Put the treeview selection on to the first panel in the - * ctrlbox. - */ - TreeView_SelectItem(treeview, hfirst); - - /* - * And create the actual control set for that panel, to - * match the initial treeview selection. - */ - assert(firstpath); /* config.c must have given us _something_ */ - create_controls(hwnd, firstpath); - dlg_refresh(NULL, &dp); /* and set up control values */ - } - - /* - * 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; - } - } - } - - /* - * Now we've finished creating our initial set of controls, - * it's safe to actually show the window without risking setup - * flicker. - */ - ShowWindow(hwnd, SW_SHOWNORMAL); - - /* - * Set the flag that activates a couple of the other message - * handlers below, which were disabled until now to avoid - * spurious firing during the above setup procedure. - */ - 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) { - /* - * Selection-change events on the treeview cause us to do - * a flurry of control deletion and creation - but only - * after WM_INITDIALOG has finished. The initial - * selection-change event(s) during treeview setup are - * ignored. - */ - HTREEITEM i; - TVITEM item; - char buffer[64]; - - if (GetWindowLongPtr(hwnd, GWLP_USERDATA) != 1) - return 0; - - i = TreeView_GetSelection(((LPNMHDR) lParam)->hwndFrom); - - 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); - } -} - -bool do_config(Conf *conf) -{ - bool 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 = conf; - 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; -} - -bool do_reconfig(HWND hwnd, Conf *conf, int protcfginfo) -{ - Conf *backup_conf; - bool ret; - int protocol; - - backup_conf = conf_copy(conf); - - ctrlbox = ctrl_new_box(); - protocol = conf_get_int(conf, CONF_protocol); - setup_config_box(ctrlbox, true, protocol, protcfginfo); - win_setup_config_box(ctrlbox, &dp.hwnd, has_help(), true, protocol); - dp_init(&dp); - winctrl_init(&ctrls_base); - winctrl_init(&ctrls_panel); - dp_add_tree(&dp, &ctrls_base); - dp_add_tree(&dp, &ctrls_panel); - dp.wintitle = dupprintf("%s Reconfiguration", appname); - dp.errtitle = dupprintf("%s Error", appname); - dp.data = conf; - 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) - conf_copy_into(conf, backup_conf); - - conf_free(backup_conf); - - return ret; -} - -static void win_gui_eventlog(LogPolicy *lp, const char *string) -{ - char timebuf[40]; - char **location; - struct tm tm; - - tm=ltime(); - strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S\t", &tm); - - if (ninitial < LOGEVENT_INITIAL_MAX) - location = &events_initial[ninitial]; - else - location = &events_circular[(circular_first + ncircular) % LOGEVENT_CIRCULAR_MAX]; - - if (*location) - sfree(*location); - *location = dupcat(timebuf, string); - if (logbox) { - int count; - SendDlgItemMessage(logbox, IDN_LIST, LB_ADDSTRING, - 0, (LPARAM) *location); - count = SendDlgItemMessage(logbox, IDN_LIST, LB_GETCOUNT, 0, 0); - SendDlgItemMessage(logbox, IDN_LIST, LB_SETTOPINDEX, count - 1, 0); - } - if (ninitial < LOGEVENT_INITIAL_MAX) { - ninitial++; - } else if (ncircular < LOGEVENT_CIRCULAR_MAX) { - ncircular++; - } else if (ncircular == LOGEVENT_CIRCULAR_MAX) { - circular_first = (circular_first + 1) % LOGEVENT_CIRCULAR_MAX; - sfree(events_circular[circular_first]); - events_circular[circular_first] = dupstr(".."); - } -} - -static void win_gui_logging_error(LogPolicy *lp, const char *event) -{ - WinGuiSeat *wgs = container_of(lp, WinGuiSeat, logpolicy); - - /* Send 'can't open log file' errors to the terminal window. - * (Marked as stderr, although terminal.c won't care.) */ - seat_stderr_pl(&wgs->seat, ptrlen_from_asciz(event)); - seat_stderr_pl(&wgs->seat, PTRLEN_LITERAL("\r\n")); -} - -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); -} - -struct hostkey_dialog_ctx { - const char *const *keywords; - const char *const *values; - FingerprintType fptype_default; - char **fingerprints; - const char *keydisp; - LPCTSTR iconid; - const char *helpctx; -}; - -static INT_PTR CALLBACK HostKeyMoreInfoProc(HWND hwnd, UINT msg, - WPARAM wParam, LPARAM lParam) -{ - switch (msg) { - case WM_INITDIALOG: { - const struct hostkey_dialog_ctx *ctx = - (const struct hostkey_dialog_ctx *)lParam; - SetWindowLongPtr(hwnd, GWLP_USERDATA, (INT_PTR)ctx); - - if (ctx->fingerprints[SSH_FPTYPE_SHA256]) - SetDlgItemText(hwnd, IDC_HKI_SHA256, - ctx->fingerprints[SSH_FPTYPE_SHA256]); - if (ctx->fingerprints[SSH_FPTYPE_MD5]) - SetDlgItemText(hwnd, IDC_HKI_MD5, - ctx->fingerprints[SSH_FPTYPE_MD5]); - - SetDlgItemText(hwnd, IDA_TEXT, ctx->keydisp); - - return 1; - } - case WM_COMMAND: - switch (LOWORD(wParam)) { - case IDOK: - EndDialog(hwnd, 0); - return 0; - } - return 0; - case WM_CLOSE: - EndDialog(hwnd, 0); - return 0; - } - return 0; -} - -static INT_PTR CALLBACK HostKeyDialogProc(HWND hwnd, UINT msg, - WPARAM wParam, LPARAM lParam) -{ - switch (msg) { - case WM_INITDIALOG: { - strbuf *sb = strbuf_new(); - const struct hostkey_dialog_ctx *ctx = - (const struct hostkey_dialog_ctx *)lParam; - SetWindowLongPtr(hwnd, GWLP_USERDATA, (INT_PTR)ctx); - for (int id = 100;; id++) { - char buf[256]; - - if (!GetDlgItemText(hwnd, id, buf, (int)lenof(buf))) - break; - - strbuf_clear(sb); - for (const char *p = buf; *p ;) { - if (*p == '{') { - for (size_t i = 0; ctx->keywords[i]; i++) { - if (strstartswith(p, ctx->keywords[i])) { - p += strlen(ctx->keywords[i]); - put_datapl(sb, ptrlen_from_asciz(ctx->values[i])); - goto matched; - } - } - } else { - put_byte(sb, *p++); - } - matched:; - } - - SetDlgItemText(hwnd, id, sb->s); - } - strbuf_free(sb); - - SetDlgItemText(hwnd, IDC_HK_FINGERPRINT, - ctx->fingerprints[ctx->fptype_default]); - MakeDlgItemBorderless(hwnd, IDC_HK_FINGERPRINT); - - HANDLE icon = LoadImage( - NULL, ctx->iconid, IMAGE_ICON, - GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON), - LR_SHARED); - SendDlgItemMessage(hwnd, IDC_HK_ICON, STM_SETICON, (WPARAM)icon, 0); - - if (!has_help()) { - HWND item = GetDlgItem(hwnd, IDHELP); - if (item) - DestroyWindow(item); - } - - return 1; - } - case WM_CTLCOLORSTATIC: { - HDC hdc = (HDC)wParam; - HWND control = (HWND)lParam; - - if (GetWindowLongPtr(control, GWLP_ID) == IDC_HK_TITLE) { - SetBkMode(hdc, TRANSPARENT); - HFONT prev_font = (HFONT)SelectObject( - hdc, (HFONT)GetStockObject(SYSTEM_FONT)); - LOGFONT lf; - if (GetObject(prev_font, sizeof(lf), &lf)) { - lf.lfWeight = FW_BOLD; - lf.lfHeight = lf.lfHeight * 3 / 2; - HFONT bold_font = CreateFontIndirect(&lf); - if (bold_font) - SelectObject(hdc, bold_font); - } - return (INT_PTR)GetSysColorBrush(COLOR_BTNFACE); - } - return 0; - } - case WM_COMMAND: - switch (LOWORD(wParam)) { - case IDC_HK_ACCEPT: - case IDC_HK_ONCE: - case IDCANCEL: - EndDialog(hwnd, LOWORD(wParam)); - return 0; - case IDHELP: { - const struct hostkey_dialog_ctx *ctx = - (const struct hostkey_dialog_ctx *) - GetWindowLongPtr(hwnd, GWLP_USERDATA); - launch_help(hwnd, ctx->helpctx); - return 0; - } - case IDC_HK_MOREINFO: { - const struct hostkey_dialog_ctx *ctx = - (const struct hostkey_dialog_ctx *) - GetWindowLongPtr(hwnd, GWLP_USERDATA); - DialogBoxParam(hinst, MAKEINTRESOURCE(IDD_HK_MOREINFO), - hwnd, HostKeyMoreInfoProc, (LPARAM)ctx); - } - } - return 0; - case WM_CLOSE: - EndDialog(hwnd, IDCANCEL); - return 0; - } - return 0; -} - -int win_seat_verify_ssh_host_key( - Seat *seat, const char *host, int port, const char *keytype, - char *keystr, const char *keydisp, char **fingerprints, - void (*callback)(void *ctx, int result), void *ctx) -{ - int ret; - - WinGuiSeat *wgs = container_of(seat, WinGuiSeat, seat); - - /* - * Verify the key against the registry. - */ - ret = verify_host_key(host, port, keytype, keystr); - - if (ret == 0) /* success - key matched OK */ - return 1; - else { - static const char *const keywords[] = - { "{KEYTYPE}", "{APPNAME}", NULL }; - - const char *values[2]; - values[0] = keytype; - values[1] = appname; - - struct hostkey_dialog_ctx ctx[1]; - ctx->keywords = keywords; - ctx->values = values; - ctx->fingerprints = fingerprints; - ctx->fptype_default = ssh2_pick_default_fingerprint(fingerprints); - ctx->keydisp = keydisp; - ctx->iconid = (ret == 2 ? IDI_WARNING : IDI_QUESTION); - ctx->helpctx = (ret == 2 ? WINHELP_CTX_errors_hostkey_changed : - WINHELP_CTX_errors_hostkey_absent); - int dlgid = (ret == 2 ? IDD_HK_WRONG : IDD_HK_ABSENT); - int mbret = DialogBoxParam( - hinst, MAKEINTRESOURCE(dlgid), wgs->term_hwnd, - HostKeyDialogProc, (LPARAM)ctx); - assert(mbret==IDC_HK_ACCEPT || mbret==IDC_HK_ONCE || mbret==IDCANCEL); - if (mbret == IDC_HK_ACCEPT) { - store_host_key(host, port, keytype, keystr); - return 1; - } else if (mbret == IDC_HK_ONCE) - return 1; - } - return 0; /* abandon the connection */ -} - -/* - * Ask whether the selected algorithm is acceptable (since it was - * below the configured 'warn' threshold). - */ -int win_seat_confirm_weak_crypto_primitive( - Seat *seat, 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 %s, 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; -} - -int win_seat_confirm_weak_cached_hostkey( - Seat *seat, const char *algname, const char *betteralgs, - void (*callback)(void *ctx, int result), void *ctx) -{ - static const char mbtitle[] = "%s Security Alert"; - static const char msg[] = - "The first host key type we have stored for this server\n" - "is %s, which is below the configured warning threshold.\n" - "The server also provides the following types of host key\n" - "above the threshold, which we do not have stored:\n" - "%s\n" - "Do you want to continue with this connection?\n"; - char *message, *title; - int mbret; - - message = dupprintf(msg, algname, betteralgs); - 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). - */ -static int win_gui_askappend(LogPolicy *lp, 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; -} - -const LogPolicyVtable win_gui_logpolicy_vt = { - .eventlog = win_gui_eventlog, - .askappend = win_gui_askappend, - .logging_error = win_gui_logging_error, - .verbose = null_lp_verbose_yes, -}; - -/* - * 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); -} diff --git a/windows/window.c b/windows/window.c index d147b980..7876f7b8 100644 --- a/windows/window.c +++ b/windows/window.c @@ -15,9 +15,9 @@ #include "putty.h" #include "terminal.h" #include "storage.h" -#include "win_res.h" -#include "winsecur.h" -#include "winseat.h" +#include "putty-rc.h" +#include "security-api.h" +#include "win-gui-seat.h" #include "tree234.h" #ifdef NO_MULTIMON diff --git a/windows/wingss.c b/windows/wingss.c deleted file mode 100644 index 0b47d9a7..00000000 --- a/windows/wingss.c +++ /dev/null @@ -1,660 +0,0 @@ -#ifndef NO_GSSAPI - -#include -#include "putty.h" - -#define SECURITY_WIN32 -#include - -#include "ssh/pgssapi.h" -#include "ssh/gss.h" -#include "ssh/gssc.h" - -#include "misc.h" - -#define UNIX_EPOCH 11644473600ULL /* Seconds from Windows epoch */ -#define CNS_PERSEC 10000000ULL /* # 100ns per second */ - -/* - * Note, as a special case, 0 relative to the Windows epoch (unspecified) maps - * to 0 relative to the POSIX epoch (unspecified)! - */ -#define TIME_WIN_TO_POSIX(ft, t) do { \ - ULARGE_INTEGER uli; \ - uli.LowPart = (ft).dwLowDateTime; \ - uli.HighPart = (ft).dwHighDateTime; \ - if (uli.QuadPart != 0) \ - uli.QuadPart = uli.QuadPart / CNS_PERSEC - UNIX_EPOCH; \ - (t) = (time_t) uli.QuadPart; \ -} while(0) - -/* Windows code to set up the GSSAPI library list. */ - -#ifdef _WIN64 -#define MIT_KERB_SUFFIX "64" -#else -#define MIT_KERB_SUFFIX "32" -#endif - -const int ngsslibs = 3; -const char *const gsslibnames[3] = { - "MIT Kerberos GSSAPI"MIT_KERB_SUFFIX".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, PVOID, - 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)); -DECL_WINDOWS_FUNCTION(static, SECURITY_STATUS, - VerifySignature, - (PCtxtHandle, PSecBufferDesc, ULONG, PULONG)); -DECL_WINDOWS_FUNCTION(static, DLL_DIRECTORY_COOKIE, - AddDllDirectory, - (PCWSTR)); - -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(Conf *conf) -{ - HMODULE module; - HKEY regkey; - struct ssh_gss_liblist *list = snew(struct ssh_gss_liblist); - char *path; - static HMODULE kernel32_module; - if (!kernel32_module) { - kernel32_module = load_system32_dll("kernel32.dll"); - } -#if !HAVE_ADDDLLDIRECTORY - /* Omit the type-check because older MSVCs don't have this function */ - GET_WINDOWS_FUNCTION_NO_TYPECHECK(kernel32_module, AddDllDirectory); -#else - GET_WINDOWS_FUNCTION(kernel32_module, AddDllDirectory); -#endif - - list->libraries = snewn(3, struct ssh_gss_library); - list->nlibraries = 0; - - /* MIT Kerberos GSSAPI implementation */ - 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, (LPBYTE)buffer, &size); - if (ret == ERROR_SUCCESS && type == REG_SZ) { - strcat (buffer, "\\bin"); - if(p_AddDllDirectory) { - /* Add MIT Kerberos' path to the DLL search path, - * it loads its own DLLs further down the road */ - wchar_t *dllPath = - dup_mb_to_wc(DEFAULT_CODEPAGE, 0, buffer); - p_AddDllDirectory(dllPath); - sfree(dllPath); - } - strcat (buffer, "\\gssapi"MIT_KERB_SUFFIX".dll"); - module = LoadLibraryEx (buffer, NULL, - LOAD_LIBRARY_SEARCH_SYSTEM32 | - LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | - LOAD_LIBRARY_SEARCH_USER_DIRS); - } - sfree(buffer); - } - RegCloseKey(regkey); - } - if (module) { - struct ssh_gss_library *lib = - &list->libraries[list->nlibraries++]; - - lib->id = 0; - lib->gsslogmsg = "Using GSSAPI from GSSAPI"MIT_KERB_SUFFIX".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(verify_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); - BIND_GSS_FN(acquire_cred); - BIND_GSS_FN(inquire_cred_by_mech); - -#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); - GET_WINDOWS_FUNCTION(module, VerifySignature); - - ssh_sspi_bind_fns(lib); - } - - /* - * Custom GSSAPI DLL. - */ - module = NULL; - path = conf_get_filename(conf, CONF_ssh_gss_custom)->path; - if (*path) { - if(p_AddDllDirectory) { - /* Add the custom directory as well in case it chainloads - * some other DLLs (e.g a non-installed MIT Kerberos - * instance) */ - int pathlen = strlen(path); - - while (pathlen > 0 && path[pathlen-1] != ':' && - path[pathlen-1] != '\\') - pathlen--; - - if (pathlen > 0 && path[pathlen-1] != '\\') - pathlen--; - - if (pathlen > 0) { - char *dirpath = dupprintf("%.*s", pathlen, path); - wchar_t *dllPath = dup_mb_to_wc(DEFAULT_CODEPAGE, 0, dirpath); - p_AddDllDirectory(dllPath); - sfree(dllPath); - sfree(dirpath); - } - } - - module = LoadLibraryEx(path, NULL, - LOAD_LIBRARY_SEARCH_SYSTEM32 | - LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | - LOAD_LIBRARY_SEARCH_USER_DIRS); - } - if (module) { - struct ssh_gss_library *lib = - &list->libraries[list->nlibraries++]; - - lib->id = 2; - lib->gsslogmsg = dupprintf("Using GSSAPI from user-specified" - " library '%s'", 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(verify_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); - BIND_GSS_FN(acquire_cred); - BIND_GSS_FN(inquire_cred_by_mech); - -#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); - - *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, - time_t *expiry) -{ - 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, - NULL); - - if (winctx->maj_stat != SEC_E_OK) { - p_FreeCredentialsHandle(&winctx->cred_handle); - sfree(winctx); - return SSH_GSS_FAILURE; - } - - /* Windows does not return a valid expiration from AcquireCredentials */ - if (expiry) - *expiry = GSS_NO_EXPIRATION; - - *ctx = (Ssh_gss_ctx) winctx; - return SSH_GSS_OK; -} - -static void localexp_to_exp_lifetime(TimeStamp *localexp, - time_t *expiry, unsigned long *lifetime) -{ - FILETIME nowUTC; - FILETIME expUTC; - time_t now; - time_t exp; - time_t delta; - - if (!lifetime && !expiry) - return; - - GetSystemTimeAsFileTime(&nowUTC); - TIME_WIN_TO_POSIX(nowUTC, now); - - if (lifetime) - *lifetime = 0; - if (expiry) - *expiry = GSS_NO_EXPIRATION; - - /* - * Type oddity: localexp is a pointer to 'TimeStamp', whereas - * LocalFileTimeToFileTime expects a pointer to FILETIME. However, - * despite having different formal type names from the compiler's - * point of view, these two structures are specified to be - * isomorphic in the MS documentation, so it's legitimate to copy - * between them: - * - * https://msdn.microsoft.com/en-us/library/windows/desktop/aa380511(v=vs.85).aspx - */ - { - FILETIME localexp_ft; - enum { vorpal_sword = 1 / (sizeof(*localexp) == sizeof(localexp_ft)) }; - memcpy(&localexp_ft, localexp, sizeof(localexp_ft)); - if (!LocalFileTimeToFileTime(&localexp_ft, &expUTC)) - return; - } - - TIME_WIN_TO_POSIX(expUTC, exp); - delta = exp - now; - if (exp == 0 || delta <= 0) - return; - - if (expiry) - *expiry = exp; - if (lifetime) { - if (delta <= ULONG_MAX) - *lifetime = (unsigned long)delta; - else - *lifetime = ULONG_MAX; - } -} - -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, - time_t *expiry, - unsigned long *lifetime) -{ - 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; - TimeStamp localexp; - - /* 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, - &localexp); - - localexp_to_exp_lifetime(&localexp, expiry, lifetime); - - /* 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; - const 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_verify_mic(struct ssh_gss_library *lib, - Ssh_gss_ctx ctx, - Ssh_gss_buf *buf, - Ssh_gss_buf *mic) -{ - winSsh_gss_ctx *winctx= (winSsh_gss_ctx *) ctx; - SecBufferDesc InputBufferDescriptor; - SecBuffer InputSecurityToken[2]; - ULONG qop; - - if (winctx == NULL) return SSH_GSS_FAILURE; - - winctx->maj_stat = 0; - - 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 = mic->length; - InputSecurityToken[1].pvBuffer = mic->value; - - winctx->maj_stat = p_VerifySignature(&winctx->context, - &InputBufferDescriptor, - 0, &qop); - 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->verify_mic = ssh_sspi_verify_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 diff --git a/windows/winhandl.c b/windows/winhandl.c deleted file mode 100644 index 085f30a0..00000000 --- a/windows/winhandl.c +++ /dev/null @@ -1,727 +0,0 @@ -/* - * 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 - -#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 */ - bool moribund; /* are we going to kill this soon? */ - bool done; /* request subthread to terminate */ - bool defunct; /* has the subthread already gone? */ - bool busy; /* operation currently in progress? */ - void *privdata; /* for client to remember who they are */ -}; - -typedef enum { HT_INPUT, HT_OUTPUT, HT_FOREIGN } HandleType; - -/* ---------------------------------------------------------------------- - * 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 */ - bool moribund; /* are we going to kill this soon? */ - bool done; /* request subthread to terminate */ - bool defunct; /* has the subthread already gone? */ - bool 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; - bool readret, finished; - int 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; - - /* - * If we just set ctx->len to 0, that means the read operation - * has returned end-of-file. Telling that to the main thread - * will cause it to set its 'defunct' flag and dispose of the - * handle structure at the next opportunity, in which case we - * mustn't touch ctx at all after the SetEvent. (Hence we do - * even _this_ check before the SetEvent.) - */ - finished = (ctx->len == 0); - - SetEvent(ctx->ev_to_main); - - if (finished) - break; - - WaitForSingleObject(ctx->ev_from_main, INFINITE); - if (ctx->done) { - /* - * The main thread has asked us to shut down. Send back an - * event indicating that we've done so. Hereafter we must - * not touch ctx at all, because the main thread might - * have freed it. - */ - SetEvent(ctx->ev_to_main); - break; - } - } - - if (povl) - CloseHandle(oev); - - return 0; -} - -/* - * This is called after a successful 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 */ - bool moribund; /* are we going to kill this soon? */ - bool done; /* request subthread to terminate */ - bool defunct; /* has the subthread already gone? */ - bool 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. - */ - const 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 */ - enum { EOF_NO, EOF_PENDING, EOF_SENT } outgoingeof; - - /* - * 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; - bool 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) { - /* - * The main thread has asked us to shut down. Send back an - * event indicating that we've done so. Hereafter we must - * not touch ctx at all, because the main thread might - * have freed it. - */ - 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) { - /* - * The write operation has suffered an error. Telling that - * to the main thread will cause it to set its 'defunct' - * flag and dispose of the handle structure at the next - * opportunity, so we must not touch ctx at all after - * this. - */ - break; - } - } - - if (povl) - CloseHandle(oev); - - return 0; -} - -static void handle_try_output(struct handle_output *ctx) -{ - if (!ctx->busy && bufchain_size(&ctx->queued_data)) { - ptrlen data = bufchain_prefix(&ctx->queued_data); - ctx->buffer = data.ptr; - ctx->len = min(data.len, ~(DWORD)0); - SetEvent(ctx->ev_from_main); - ctx->busy = true; - } else if (!ctx->busy && bufchain_size(&ctx->queued_data) == 0 && - ctx->outgoingeof == EOF_PENDING) { - CloseHandle(ctx->h); - ctx->h = INVALID_HANDLE_VALUE; - ctx->outgoingeof = EOF_SENT; - } -} - -/* ---------------------------------------------------------------------- - * 'Foreign events'. These are handle structures which just contain a - * single event object passed to us by another module such as - * winnps.c, so that they can make use of our handle_get_events / - * handle_got_event mechanism for communicating with application main - * loops. - */ -struct handle_foreign { - /* - * Copy of the handle_generic structure. - */ - HANDLE h; /* the handle itself */ - HANDLE ev_to_main; /* event used to signal main thread */ - HANDLE ev_from_main; /* event used to signal back to us */ - bool moribund; /* are we going to kill this soon? */ - bool done; /* request subthread to terminate */ - bool defunct; /* has the subthread already gone? */ - bool busy; /* operation currently in progress? */ - void *privdata; /* for client to remember who they are */ - - /* - * Our own data, just consisting of knowledge of who to call back. - */ - void (*callback)(void *); - void *ctx; -}; - -/* ---------------------------------------------------------------------- - * Unified code handling both input and output threads. - */ - -struct handle { - HandleType type; - union { - struct handle_generic g; - struct handle_input i; - struct handle_output o; - struct handle_foreign f; - } u; -}; - -static tree234 *handles_by_evtomain; - -static int handle_cmp_evtomain(void *av, void *bv) -{ - struct handle *a = (struct handle *)av; - struct handle *b = (struct handle *)bv; - - if ((uintptr_t)a->u.g.ev_to_main < (uintptr_t)b->u.g.ev_to_main) - return -1; - else if ((uintptr_t)a->u.g.ev_to_main > (uintptr_t)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 ((uintptr_t)*a < (uintptr_t)b->u.g.ev_to_main) - return -1; - else if ((uintptr_t)*a > (uintptr_t)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->type = HT_INPUT; - h->u.i.h = handle; - h->u.i.ev_to_main = CreateEvent(NULL, false, false, NULL); - h->u.i.ev_from_main = CreateEvent(NULL, false, false, NULL); - h->u.i.gotdata = gotdata; - h->u.i.defunct = false; - 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->type = HT_OUTPUT; - h->u.o.h = handle; - h->u.o.ev_to_main = CreateEvent(NULL, false, false, NULL); - h->u.o.ev_from_main = CreateEvent(NULL, false, false, NULL); - h->u.o.busy = false; - h->u.o.defunct = false; - h->u.o.moribund = false; - h->u.o.done = false; - h->u.o.privdata = privdata; - bufchain_init(&h->u.o.queued_data); - h->u.o.outgoingeof = EOF_NO; - h->u.o.sentdata = sentdata; - h->u.o.flags = flags; - - if (!handles_by_evtomain) - handles_by_evtomain = newtree234(handle_cmp_evtomain); - add234(handles_by_evtomain, h); - - CreateThread(NULL, 0, handle_output_threadfunc, - &h->u.o, 0, &out_threadid); - - return h; -} - -struct handle *handle_add_foreign_event(HANDLE event, - void (*callback)(void *), void *ctx) -{ - struct handle *h = snew(struct handle); - - h->type = HT_FOREIGN; - h->u.f.h = INVALID_HANDLE_VALUE; - h->u.f.ev_to_main = event; - h->u.f.ev_from_main = INVALID_HANDLE_VALUE; - h->u.f.defunct = true; /* we have no thread in the first place */ - h->u.f.moribund = false; - h->u.f.done = false; - h->u.f.privdata = NULL; - h->u.f.callback = callback; - h->u.f.ctx = ctx; - h->u.f.busy = true; - - if (!handles_by_evtomain) - handles_by_evtomain = newtree234(handle_cmp_evtomain); - add234(handles_by_evtomain, h); - - return h; -} - -size_t handle_write(struct handle *h, const void *data, size_t len) -{ - assert(h->type == HT_OUTPUT); - assert(h->u.o.outgoingeof == EOF_NO); - bufchain_add(&h->u.o.queued_data, data, len); - handle_try_output(&h->u.o); - return bufchain_size(&h->u.o.queued_data); -} - -void handle_write_eof(struct handle *h) -{ - /* - * This function is called when we want to proactively send an - * end-of-file notification on the handle. We can only do this by - * actually closing the handle - so never call this on a - * bidirectional handle if we're still interested in its incoming - * direction! - */ - assert(h->type == HT_OUTPUT); - if (h->u.o.outgoingeof == EOF_NO) { - h->u.o.outgoingeof = EOF_PENDING; - handle_try_output(&h->u.o); - } -} - -HANDLE *handle_get_events(int *nevents) -{ - HANDLE *ret; - struct handle *h; - int i; - size_t 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) { - sgrowarray(ret, size, n); - ret[n++] = h->u.g.ev_to_main; - } - } - } - - *nevents = n; - return ret; -} - -static void handle_destroy(struct handle *h) -{ - if (h->type == HT_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) -{ - assert(h && !h->u.g.moribund); - if (h->u.g.busy && h->type != HT_FOREIGN) { - /* - * If the handle is currently busy, we cannot immediately free - * it, because its subthread is in the middle of something. - * (Exception: foreign handles don't have a subthread.) - * - * 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. So - * we 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 one which we have either already - * signalled to die, or are waiting until its current I/O op - * completes to do so. Either way, it's treated as already - * dead from the external user's point of view, so we ignore - * the actual I/O result. We just signal the thread to die if - * we haven't yet done so, 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; - } - - switch (h->type) { - int backlog; - - case HT_INPUT: - 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.defunct = true; - h->u.i.gotdata(h, NULL, 0, h->u.i.readerr); - } else { - backlog = h->u.i.gotdata(h, h->u.i.buffer, h->u.i.len, 0); - handle_throttle(&h->u.i, backlog); - } - break; - - case HT_OUTPUT: - h->u.o.busy = false; - - /* - * A signal on an output handle means we have completed a - * write. Call the callback to indicate that the output - * 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.defunct = true; - h->u.o.sentdata(h, 0, h->u.o.writeerr); - } else { - bufchain_consume(&h->u.o.queued_data, h->u.o.lenwritten); - noise_ultralight(NOISE_SOURCE_IOLEN, h->u.o.lenwritten); - h->u.o.sentdata(h, bufchain_size(&h->u.o.queued_data), 0); - handle_try_output(&h->u.o); - } - break; - - case HT_FOREIGN: - /* Just call the callback. */ - h->u.f.callback(h->u.f.ctx); - break; - } -} - -void handle_unthrottle(struct handle *h, size_t backlog) -{ - assert(h->type == HT_INPUT); - handle_throttle(&h->u.i, backlog); -} - -size_t handle_backlog(struct handle *h) -{ - assert(h->type == HT_OUTPUT); - return bufchain_size(&h->u.o.queued_data); -} - -void *handle_get_privdata(struct handle *h) -{ - return h->u.g.privdata; -} - -static void handle_sink_write(BinarySink *bs, const void *data, size_t len) -{ - handle_sink *sink = BinarySink_DOWNCAST(bs, handle_sink); - handle_write(sink->h, data, len); -} - -void handle_sink_init(handle_sink *sink, struct handle *h) -{ - sink->h = h; - BinarySink_INIT(sink, handle_sink_write); -} diff --git a/windows/winhelp.c b/windows/winhelp.c deleted file mode 100644 index df6ac37b..00000000 --- a/windows/winhelp.c +++ /dev/null @@ -1,250 +0,0 @@ -/* - * winhelp.c: centralised functions to launch Windows HTML Help files. - */ - -#include -#include -#include -#include - -#include "putty.h" -#include "win_res.h" - -#ifdef NO_HTMLHELP - -/* If htmlhelp.h is not available, we can't do any of this at all */ -bool has_help(void) { return false; } -void init_help(void) { } -void shutdown_help(void) { } -void launch_help(HWND hwnd, const char *topic) { } -void quit_help(HWND hwnd) { } - -#else - -#include - -static char *chm_path = NULL; -static bool chm_created_by_us = false; - -static bool requested_help; -DECL_WINDOWS_FUNCTION(static, HWND, HtmlHelpA, (HWND, LPCSTR, UINT, DWORD_PTR)); - -static HRSRC chm_hrsrc; -static DWORD chm_resource_size = 0; -static const void *chm_resource = NULL; - -int has_embedded_chm(void) -{ - static bool checked = false; - if (!checked) { - checked = true; - - chm_hrsrc = FindResource( - NULL, MAKEINTRESOURCE(ID_CUSTOM_CHMFILE), - MAKEINTRESOURCE(TYPE_CUSTOM_CHMFILE)); - } - return chm_hrsrc != NULL ? 1 : 0; -} - -static bool find_chm_resource(void) -{ - static bool checked = false; - if (checked) /* we've been here already */ - goto out; - checked = true; - - /* - * Look for a CHM file embedded in this executable as a custom - * resource. - */ - if (!has_embedded_chm()) /* set up chm_hrsrc and check if it's NULL */ - goto out; - - chm_resource_size = SizeofResource(NULL, chm_hrsrc); - if (chm_resource_size == 0) - goto out; - - HGLOBAL chm_hglobal = LoadResource(NULL, chm_hrsrc); - if (chm_hglobal == NULL) - goto out; - - chm_resource = (const uint8_t *)LockResource(chm_hglobal); - - out: - return chm_resource != NULL; -} - -static bool load_chm_resource(void) -{ - bool toret = false; - char *filename = NULL; - HANDLE filehandle = INVALID_HANDLE_VALUE; - bool created = false; - - static bool tried_to_load = false; - if (tried_to_load) - goto out; - tried_to_load = true; - - /* - * We've found it! Now write it out into a separate file, so that - * htmlhelp.exe can handle it. - */ - - /* GetTempPath is documented as returning a size of up to - * MAX_PATH+1 which does not count the NUL */ - char tempdir[MAX_PATH + 2]; - if (GetTempPath(sizeof(tempdir), tempdir) == 0) - goto out; - - unsigned long pid = GetCurrentProcessId(); - - for (uint64_t counter = 0;; counter++) { - filename = dupprintf( - "%s\\putty_%lu_%"PRIu64".chm", tempdir, pid, counter); - filehandle = CreateFile( - filename, GENERIC_WRITE, FILE_SHARE_READ, - NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL); - - if (filehandle != INVALID_HANDLE_VALUE) - break; /* success! */ - - if (GetLastError() != ERROR_FILE_EXISTS) - goto out; /* failed for some other reason! */ - - sfree(filename); - filename = NULL; - } - created = true; - - const uint8_t *p = (const uint8_t *)chm_resource; - for (DWORD pos = 0; pos < chm_resource_size; pos++) { - DWORD to_write = chm_resource_size - pos; - DWORD written = 0; - - if (!WriteFile(filehandle, p + pos, to_write, &written, NULL)) - goto out; - pos += written; - } - - chm_path = filename; - filename = NULL; - chm_created_by_us = true; - toret = true; - - out: - if (created && !toret) - DeleteFile(filename); - sfree(filename); - if (filehandle != INVALID_HANDLE_VALUE) - CloseHandle(filehandle); - return toret; -} - -static bool find_chm_from_installation(void) -{ - static const char *const reg_paths[] = { - "Software\\SimonTatham\\PuTTY64\\CHMPath", - "Software\\SimonTatham\\PuTTY\\CHMPath", - }; - - for (size_t i = 0; i < lenof(reg_paths); i++) { - char *filename = registry_get_string( - HKEY_LOCAL_MACHINE, reg_paths[i], NULL); - - if (filename) { - chm_path = filename; - chm_created_by_us = false; - return true; - } - } - - return false; -} - -void init_help(void) -{ - /* Just in case of multiple calls */ - static bool already_called = false; - if (already_called) - return; - already_called = true; - - /* - * Don't even try looking for the CHM file if we can't even find - * the HtmlHelp() API function. - */ - HINSTANCE dllHH = load_system32_dll("hhctrl.ocx"); - GET_WINDOWS_FUNCTION(dllHH, HtmlHelpA); - if (!p_HtmlHelpA) { - FreeLibrary(dllHH); - return; - } - - /* - * If there's a CHM file embedded in this executable, we should - * use that as the first choice. - */ - if (find_chm_resource()) - return; - - /* - * Otherwise, try looking for the CHM in the location that the - * installer marked in the registry. - */ - if (find_chm_from_installation()) - return; -} - -void shutdown_help(void) -{ - if (chm_path && chm_created_by_us) { - p_HtmlHelpA(NULL, NULL, HH_CLOSE_ALL, 0); - DeleteFile(chm_path); - } - sfree(chm_path); - chm_path = NULL; - chm_created_by_us = false; -} - -bool has_help(void) -{ - return chm_path != NULL || chm_resource != NULL; -} - -void launch_help(HWND hwnd, const char *topic) -{ - if (!chm_path && chm_resource) { - /* - * If we've been called without already having a file name for - * the CHM file, that might be because we've located it in our - * resource section but not written it to a temp file yet. Do - * so now, on first use. - */ - load_chm_resource(); - } - - /* If we _still_ don't have a CHM pathname, we just can't display help. */ - if (!chm_path) - return; - - if (topic) { - char *fname = dupprintf( - "%s::/%s.html>main", chm_path, topic); - p_HtmlHelpA(hwnd, fname, HH_DISPLAY_TOPIC, 0); - sfree(fname); - } else { - p_HtmlHelpA(hwnd, chm_path, HH_DISPLAY_TOPIC, 0); - } - requested_help = true; -} - -void quit_help(HWND hwnd) -{ - if (requested_help) - p_HtmlHelpA(NULL, NULL, HH_CLOSE_ALL, 0); - if (chm_path && chm_created_by_us) - DeleteFile(chm_path); -} - -#endif /* NO_HTMLHELP */ diff --git a/windows/winhelp.h b/windows/winhelp.h deleted file mode 100644 index ae5a7a7f..00000000 --- a/windows/winhelp.h +++ /dev/null @@ -1,207 +0,0 @@ -/* - * winhelp.h - define Windows Help context names. - * Each definition is simply a string which matches up with the - * section names in the Halibut 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 "config-hostname" -#define WINHELP_CTX_session_saved "config-saving" -#define WINHELP_CTX_session_coe "config-closeonexit" -#define WINHELP_CTX_logging_main "config-logging" -#define WINHELP_CTX_logging_filename "config-logfilename" -#define WINHELP_CTX_logging_exists "config-logfileexists" -#define WINHELP_CTX_logging_flush "config-logflush" -#define WINHELP_CTX_logging_header "config-logheader" -#define WINHELP_CTX_logging_ssh_omit_password "config-logssh" -#define WINHELP_CTX_logging_ssh_omit_data "config-logssh" -#define WINHELP_CTX_keyboard_backspace "config-backspace" -#define WINHELP_CTX_keyboard_homeend "config-homeend" -#define WINHELP_CTX_keyboard_funkeys "config-funkeys" -#define WINHELP_CTX_keyboard_appkeypad "config-appkeypad" -#define WINHELP_CTX_keyboard_appcursor "config-appcursor" -#define WINHELP_CTX_keyboard_nethack "config-nethack" -#define WINHELP_CTX_keyboard_compose "config-compose" -#define WINHELP_CTX_keyboard_ctrlalt "config-ctrlalt" -#define WINHELP_CTX_features_application "config-features-application" -#define WINHELP_CTX_features_mouse "config-features-mouse" -#define WINHELP_CTX_features_resize "config-features-resize" -#define WINHELP_CTX_features_altscreen "config-features-altscreen" -#define WINHELP_CTX_features_retitle "config-features-retitle" -#define WINHELP_CTX_features_qtitle "config-features-qtitle" -#define WINHELP_CTX_features_dbackspace "config-features-dbackspace" -#define WINHELP_CTX_features_charset "config-features-charset" -#define WINHELP_CTX_features_clearscroll "config-features-clearscroll" -#define WINHELP_CTX_features_arabicshaping "config-features-shaping" -#define WINHELP_CTX_features_bidi "config-features-bidi" -#define WINHELP_CTX_terminal_autowrap "config-autowrap" -#define WINHELP_CTX_terminal_decom "config-decom" -#define WINHELP_CTX_terminal_lfhascr "config-crlf" -#define WINHELP_CTX_terminal_crhaslf "config-lfcr" -#define WINHELP_CTX_terminal_bce "config-erase" -#define WINHELP_CTX_terminal_blink "config-blink" -#define WINHELP_CTX_terminal_answerback "config-answerback" -#define WINHELP_CTX_terminal_localecho "config-localecho" -#define WINHELP_CTX_terminal_localedit "config-localedit" -#define WINHELP_CTX_terminal_printing "config-printing" -#define WINHELP_CTX_supdup_location "supdup-location" -#define WINHELP_CTX_supdup_ascii "supdup-ascii" -#define WINHELP_CTX_supdup_more "supdup-more" -#define WINHELP_CTX_supdup_scroll "supdup-scroll" -#define WINHELP_CTX_bell_style "config-bellstyle" -#define WINHELP_CTX_bell_taskbar "config-belltaskbar" -#define WINHELP_CTX_bell_overload "config-bellovl" -#define WINHELP_CTX_window_size "config-winsize" -#define WINHELP_CTX_window_resize "config-winsizelock" -#define WINHELP_CTX_window_scrollback "config-scrollback" -#define WINHELP_CTX_window_erased "config-erasetoscrollback" -#define WINHELP_CTX_behaviour_closewarn "config-warnonclose" -#define WINHELP_CTX_behaviour_altf4 "config-altf4" -#define WINHELP_CTX_behaviour_altspace "config-altspace" -#define WINHELP_CTX_behaviour_altonly "config-altonly" -#define WINHELP_CTX_behaviour_alwaysontop "config-alwaysontop" -#define WINHELP_CTX_behaviour_altenter "config-fullscreen" -#define WINHELP_CTX_appearance_cursor "config-cursor" -#define WINHELP_CTX_appearance_font "config-font" -#define WINHELP_CTX_appearance_title "config-title" -#define WINHELP_CTX_appearance_hidemouse "config-mouseptr" -#define WINHELP_CTX_appearance_border "config-winborder" -#define WINHELP_CTX_connection_termtype "config-termtype" -#define WINHELP_CTX_connection_termspeed "config-termspeed" -#define WINHELP_CTX_connection_username "config-username" -#define WINHELP_CTX_connection_username_from_env "config-username-from-env" -#define WINHELP_CTX_connection_keepalive "config-keepalive" -#define WINHELP_CTX_connection_nodelay "config-nodelay" -#define WINHELP_CTX_connection_ipversion "config-address-family" -#define WINHELP_CTX_connection_tcpkeepalive "config-tcp-keepalives" -#define WINHELP_CTX_connection_loghost "config-loghost" -#define WINHELP_CTX_proxy_type "config-proxy-type" -#define WINHELP_CTX_proxy_main "config-proxy" -#define WINHELP_CTX_proxy_exclude "config-proxy-exclude" -#define WINHELP_CTX_proxy_dns "config-proxy-dns" -#define WINHELP_CTX_proxy_auth "config-proxy-auth" -#define WINHELP_CTX_proxy_command "config-proxy-command" -#define WINHELP_CTX_proxy_logging "config-proxy-logging" -#define WINHELP_CTX_telnet_environ "config-environ" -#define WINHELP_CTX_telnet_oldenviron "config-oldenviron" -#define WINHELP_CTX_telnet_passive "config-ptelnet" -#define WINHELP_CTX_telnet_specialkeys "config-telnetkey" -#define WINHELP_CTX_telnet_newline "config-telnetnl" -#define WINHELP_CTX_rlogin_localuser "config-rlogin-localuser" -#define WINHELP_CTX_ssh_nopty "config-ssh-pty" -#define WINHELP_CTX_ssh_ttymodes "config-ttymodes" -#define WINHELP_CTX_ssh_noshell "config-ssh-noshell" -#define WINHELP_CTX_ssh_ciphers "config-ssh-encryption" -#define WINHELP_CTX_ssh_protocol "config-ssh-prot" -#define WINHELP_CTX_ssh_command "config-command" -#define WINHELP_CTX_ssh_compress "config-ssh-comp" -#define WINHELP_CTX_ssh_share "config-ssh-sharing" -#define WINHELP_CTX_ssh_kexlist "config-ssh-kex-order" -#define WINHELP_CTX_ssh_hklist "config-ssh-hostkey-order" -#define WINHELP_CTX_ssh_hk_known "config-ssh-prefer-known-hostkeys" -#define WINHELP_CTX_ssh_gssapi_kex_delegation "config-ssh-kex-gssapi-delegation" -#define WINHELP_CTX_ssh_kex_repeat "config-ssh-kex-rekey" -#define WINHELP_CTX_ssh_kex_manual_hostkeys "config-ssh-kex-manual-hostkeys" -#define WINHELP_CTX_ssh_auth_bypass "config-ssh-noauth" -#define WINHELP_CTX_ssh_auth_banner "config-ssh-banner" -#define WINHELP_CTX_ssh_auth_privkey "config-ssh-privkey" -#define WINHELP_CTX_ssh_auth_agentfwd "config-ssh-agentfwd" -#define WINHELP_CTX_ssh_auth_changeuser "config-ssh-changeuser" -#define WINHELP_CTX_ssh_auth_pageant "config-ssh-tryagent" -#define WINHELP_CTX_ssh_auth_tis "config-ssh-tis" -#define WINHELP_CTX_ssh_auth_ki "config-ssh-ki" -#define WINHELP_CTX_ssh_gssapi "config-ssh-auth-gssapi" -#define WINHELP_CTX_ssh_gssapi_delegation "config-ssh-auth-gssapi-delegation" -#define WINHELP_CTX_ssh_gssapi_libraries "config-ssh-auth-gssapi-libraries" -#define WINHELP_CTX_selection_buttons "config-mouse" -#define WINHELP_CTX_selection_shiftdrag "config-mouseshift" -#define WINHELP_CTX_selection_rect "config-rectselect" -#define WINHELP_CTX_selection_linedraw "config-linedrawpaste" -#define WINHELP_CTX_selection_autocopy "config-selection-autocopy" -#define WINHELP_CTX_selection_clipactions "config-selection-clipactions" -#define WINHELP_CTX_selection_pastectrl "config-paste-ctrl-char" -#define WINHELP_CTX_copy_charclasses "config-charclasses" -#define WINHELP_CTX_copy_rtf "config-rtfcopy" -#define WINHELP_CTX_colours_ansi "config-ansicolour" -#define WINHELP_CTX_colours_xterm256 "config-xtermcolour" -#define WINHELP_CTX_colours_truecolour "config-truecolour" -#define WINHELP_CTX_colours_bold "config-boldcolour" -#define WINHELP_CTX_colours_system "config-syscolour" -#define WINHELP_CTX_colours_logpal "config-logpalette" -#define WINHELP_CTX_colours_config "config-colourcfg" -#define WINHELP_CTX_translation_codepage "config-charset" -#define WINHELP_CTX_translation_cjk_ambig_wide "config-cjk-ambig-wide" -#define WINHELP_CTX_translation_cyrillic "config-cyr" -#define WINHELP_CTX_translation_linedraw "config-linedraw" -#define WINHELP_CTX_translation_utf8linedraw "config-utf8linedraw" -#define WINHELP_CTX_ssh_tunnels_x11 "config-ssh-x11" -#define WINHELP_CTX_ssh_tunnels_x11auth "config-ssh-x11auth" -#define WINHELP_CTX_ssh_tunnels_xauthority "config-ssh-xauthority" -#define WINHELP_CTX_ssh_tunnels_portfwd "config-ssh-portfwd" -#define WINHELP_CTX_ssh_tunnels_portfwd_localhost "config-ssh-portfwd-localhost" -#define WINHELP_CTX_ssh_tunnels_portfwd_ipversion "config-ssh-portfwd-address-family" -#define WINHELP_CTX_ssh_bugs_ignore1 "config-ssh-bug-ignore1" -#define WINHELP_CTX_ssh_bugs_plainpw1 "config-ssh-bug-plainpw1" -#define WINHELP_CTX_ssh_bugs_rsa1 "config-ssh-bug-rsa1" -#define WINHELP_CTX_ssh_bugs_ignore2 "config-ssh-bug-ignore2" -#define WINHELP_CTX_ssh_bugs_hmac2 "config-ssh-bug-hmac2" -#define WINHELP_CTX_ssh_bugs_derivekey2 "config-ssh-bug-derivekey2" -#define WINHELP_CTX_ssh_bugs_rsapad2 "config-ssh-bug-sig" -#define WINHELP_CTX_ssh_bugs_pksessid2 "config-ssh-bug-pksessid2" -#define WINHELP_CTX_ssh_bugs_rekey2 "config-ssh-bug-rekey" -#define WINHELP_CTX_ssh_bugs_maxpkt2 "config-ssh-bug-maxpkt2" -#define WINHELP_CTX_ssh_bugs_winadj "config-ssh-bug-winadj" -#define WINHELP_CTX_ssh_bugs_chanreq "config-ssh-bug-chanreq" -#define WINHELP_CTX_ssh_bugs_oldgex2 "config-ssh-bug-oldgex2" -#define WINHELP_CTX_serial_line "config-serial-line" -#define WINHELP_CTX_serial_speed "config-serial-speed" -#define WINHELP_CTX_serial_databits "config-serial-databits" -#define WINHELP_CTX_serial_stopbits "config-serial-stopbits" -#define WINHELP_CTX_serial_parity "config-serial-parity" -#define WINHELP_CTX_serial_flow "config-serial-flow" - -#define WINHELP_CTX_pageant_general "pageant" -#define WINHELP_CTX_pageant_keylist "pageant-mainwin-keylist" -#define WINHELP_CTX_pageant_addkey "pageant-mainwin-addkey" -#define WINHELP_CTX_pageant_remkey "pageant-mainwin-remkey" -#define WINHELP_CTX_pageant_deferred "pageant-deferred-decryption" -#define WINHELP_CTX_pgpfingerprints "pgpkeys" -#define WINHELP_CTX_puttygen_general "pubkey-puttygen" -#define WINHELP_CTX_puttygen_keytype "puttygen-keytype" -#define WINHELP_CTX_puttygen_bits "puttygen-strength" -#define WINHELP_CTX_puttygen_generate "puttygen-generate" -#define WINHELP_CTX_puttygen_fingerprint "puttygen-fingerprint" -#define WINHELP_CTX_puttygen_comment "puttygen-comment" -#define WINHELP_CTX_puttygen_passphrase "puttygen-passphrase" -#define WINHELP_CTX_puttygen_savepriv "puttygen-savepriv" -#define WINHELP_CTX_puttygen_savepub "puttygen-savepub" -#define WINHELP_CTX_puttygen_pastekey "puttygen-pastekey" -#define WINHELP_CTX_puttygen_load "puttygen-load" -#define WINHELP_CTX_puttygen_conversions "puttygen-conversions" -#define WINHELP_CTX_puttygen_ppkver "puttygen-save-ppk-version" -#define WINHELP_CTX_puttygen_kdfparam "puttygen-save-passphrase-hashing" - -/* 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" -#define WINHELP_CTXID_errors_hostkey_absent 1 -#define WINHELP_CTX_errors_hostkey_changed "errors-hostkey-wrong" -#define WINHELP_CTXID_errors_hostkey_changed 2 -#define WINHELP_CTX_errors_cantloadkey "errors-cant-load-key" -#define WINHELP_CTXID_errors_cantloadkey 3 -#define WINHELP_CTX_option_cleanup "using-cleanup" -#define WINHELP_CTXID_option_cleanup 4 -#define WINHELP_CTX_pgp_fingerprints "pgpkeys" -#define WINHELP_CTXID_pgp_fingerprints 5 diff --git a/windows/winhelp.rc2 b/windows/winhelp.rc2 deleted file mode 100644 index e331629e..00000000 --- a/windows/winhelp.rc2 +++ /dev/null @@ -1,8 +0,0 @@ -#include "win_res.h" - -#ifdef EMBEDDED_CHM_FILE -ID_CUSTOM_CHMFILE TYPE_CUSTOM_CHMFILE EMBEDDED_CHM_FILE -#define HELPVER " (with embedded help)" -#else -#define HELPVER " (without embedded help)" -#endif diff --git a/windows/winhsock.c b/windows/winhsock.c deleted file mode 100644 index 93bb500a..00000000 --- a/windows/winhsock.c +++ /dev/null @@ -1,343 +0,0 @@ -/* - * General mechanism for wrapping up reading/writing of Windows - * HANDLEs into a PuTTY Socket abstraction. - */ - -#include -#include -#include - -#include "tree234.h" -#include "putty.h" -#include "network.h" - -typedef struct HandleSocket { - HANDLE send_H, recv_H, stderr_H; - struct handle *send_h, *recv_h, *stderr_h; - - /* - * Freezing one of these sockets is a slightly fiddly business, - * because the reads from the handle are happening in a separate - * thread as blocking system calls and so once one is in progress - * it can't sensibly be interrupted. Hence, after the user tries - * to freeze one of these sockets, it's unavoidable that we may - * receive one more load of data before we manage to get - * winhandl.c to stop reading. - */ - enum { - UNFROZEN, /* reading as normal */ - FREEZING, /* have been set to frozen but winhandl is still reading */ - FROZEN, /* really frozen - winhandl has been throttled */ - THAWING /* we're gradually releasing our remaining data */ - } frozen; - /* We buffer data here if we receive it from winhandl while frozen. */ - bufchain inputdata; - - /* Handle logging proxy error messages from stderr_H, if we have one. */ - ProxyStderrBuf psb; - - bool defer_close, deferred_close; /* in case of re-entrance */ - - char *error; - - Plug *plug; - - Socket sock; -} HandleSocket; - -static size_t handle_gotdata( - struct handle *h, const void *data, size_t len, int err) -{ - HandleSocket *hs = (HandleSocket *)handle_get_privdata(h); - - if (err) { - plug_closing(hs->plug, "Read error from handle", 0, 0); - return 0; - } else if (len == 0) { - plug_closing(hs->plug, NULL, 0, 0); - return 0; - } else { - assert(hs->frozen != FROZEN && hs->frozen != THAWING); - if (hs->frozen == FREEZING) { - /* - * If we've received data while this socket is supposed to - * be frozen (because the read winhandl.c started before - * sk_set_frozen was called has now returned) then buffer - * the data for when we unfreeze. - */ - bufchain_add(&hs->inputdata, data, len); - hs->frozen = FROZEN; - - /* - * And return a very large backlog, to prevent further - * data arriving from winhandl until we unfreeze. - */ - return INT_MAX; - } else { - plug_receive(hs->plug, 0, data, len); - return 0; - } - } -} - -static size_t handle_stderr( - struct handle *h, const void *data, size_t len, int err) -{ - HandleSocket *hs = (HandleSocket *)handle_get_privdata(h); - - if (!err && len > 0) - log_proxy_stderr(hs->plug, &hs->psb, data, len); - - return 0; -} - -static void handle_sentdata(struct handle *h, size_t new_backlog, int err) -{ - HandleSocket *hs = (HandleSocket *)handle_get_privdata(h); - - if (err) { - plug_closing(hs->plug, win_strerror(err), err, 0); - return; - } - - plug_sent(hs->plug, new_backlog); -} - -static Plug *sk_handle_plug(Socket *s, Plug *p) -{ - HandleSocket *hs = container_of(s, HandleSocket, sock); - Plug *ret = hs->plug; - if (p) - hs->plug = p; - return ret; -} - -static void sk_handle_close(Socket *s) -{ - HandleSocket *hs = container_of(s, HandleSocket, sock); - - if (hs->defer_close) { - hs->deferred_close = true; - return; - } - - handle_free(hs->send_h); - handle_free(hs->recv_h); - CloseHandle(hs->send_H); - if (hs->recv_H != hs->send_H) - CloseHandle(hs->recv_H); - bufchain_clear(&hs->inputdata); - - delete_callbacks_for_context(hs); - - sfree(hs); -} - -static size_t sk_handle_write(Socket *s, const void *data, size_t len) -{ - HandleSocket *hs = container_of(s, HandleSocket, sock); - - return handle_write(hs->send_h, data, len); -} - -static size_t sk_handle_write_oob(Socket *s, const void *data, size_t len) -{ - /* - * oob data is treated as inband; nasty, but nothing really - * better we can do - */ - return sk_handle_write(s, data, len); -} - -static void sk_handle_write_eof(Socket *s) -{ - HandleSocket *hs = container_of(s, HandleSocket, sock); - - handle_write_eof(hs->send_h); -} - -static void handle_socket_unfreeze(void *hsv) -{ - HandleSocket *hs = (HandleSocket *)hsv; - - /* - * If we've been put into a state other than THAWING since the - * last callback, then we're done. - */ - if (hs->frozen != THAWING) - return; - - /* - * Get some of the data we've buffered. - */ - ptrlen data = bufchain_prefix(&hs->inputdata); - assert(data.len > 0); - - /* - * Hand it off to the plug. Be careful of re-entrance - that might - * have the effect of trying to close this socket. - */ - hs->defer_close = true; - plug_receive(hs->plug, 0, data.ptr, data.len); - bufchain_consume(&hs->inputdata, data.len); - hs->defer_close = false; - if (hs->deferred_close) { - sk_handle_close(&hs->sock); - return; - } - - if (bufchain_size(&hs->inputdata) > 0) { - /* - * If there's still data in our buffer, stay in THAWING state, - * and reschedule ourself. - */ - queue_toplevel_callback(handle_socket_unfreeze, hs); - } else { - /* - * Otherwise, we've successfully thawed! - */ - hs->frozen = UNFROZEN; - handle_unthrottle(hs->recv_h, 0); - } -} - -static void sk_handle_set_frozen(Socket *s, bool is_frozen) -{ - HandleSocket *hs = container_of(s, HandleSocket, sock); - - if (is_frozen) { - switch (hs->frozen) { - case FREEZING: - case FROZEN: - return; /* nothing to do */ - - case THAWING: - /* - * We were in the middle of emptying our bufchain, and got - * frozen again. In that case, winhandl.c is already - * throttled, so just return to FROZEN state. The toplevel - * callback will notice and disable itself. - */ - hs->frozen = FROZEN; - break; - - case UNFROZEN: - /* - * The normal case. Go to FREEZING, and expect one more - * load of data from winhandl if we're unlucky. - */ - hs->frozen = FREEZING; - break; - } - } else { - switch (hs->frozen) { - case UNFROZEN: - case THAWING: - return; /* nothing to do */ - - case FREEZING: - /* - * If winhandl didn't send us any data throughout the time - * we were frozen, then we'll still be in this state and - * can just unfreeze in the trivial way. - */ - assert(bufchain_size(&hs->inputdata) == 0); - hs->frozen = UNFROZEN; - break; - - case FROZEN: - /* - * If we have buffered data, go to THAWING and start - * releasing it in top-level callbacks. - */ - hs->frozen = THAWING; - queue_toplevel_callback(handle_socket_unfreeze, hs); - } - } -} - -static const char *sk_handle_socket_error(Socket *s) -{ - HandleSocket *hs = container_of(s, HandleSocket, sock); - return hs->error; -} - -static SocketPeerInfo *sk_handle_peer_info(Socket *s) -{ - HandleSocket *hs = container_of(s, HandleSocket, sock); - ULONG pid; - static HMODULE kernel32_module; - DECL_WINDOWS_FUNCTION(static, BOOL, GetNamedPipeClientProcessId, - (HANDLE, PULONG)); - - if (!kernel32_module) { - kernel32_module = load_system32_dll("kernel32.dll"); -#if !HAVE_GETNAMEDPIPECLIENTPROCESSID - /* For older Visual Studio, and MinGW too (at least as of - * Ubuntu 16.04), this function isn't available in the header - * files to type-check. Ditto the toolchain I use for - * Coveritying the Windows code. */ - GET_WINDOWS_FUNCTION_NO_TYPECHECK( - kernel32_module, GetNamedPipeClientProcessId); -#else - GET_WINDOWS_FUNCTION( - kernel32_module, GetNamedPipeClientProcessId); -#endif - } - - /* - * Of course, not all handles managed by this module will be - * server ends of named pipes, but if they are, then it's useful - * to log what we can find out about the client end. - */ - if (p_GetNamedPipeClientProcessId && - p_GetNamedPipeClientProcessId(hs->send_H, &pid)) { - SocketPeerInfo *pi = snew(SocketPeerInfo); - pi->addressfamily = ADDRTYPE_LOCAL; - pi->addr_text = NULL; - pi->port = -1; - pi->log_text = dupprintf("process id %lu", (unsigned long)pid); - return pi; - } - - return NULL; -} - -static const SocketVtable HandleSocket_sockvt = { - .plug = sk_handle_plug, - .close = sk_handle_close, - .write = sk_handle_write, - .write_oob = sk_handle_write_oob, - .write_eof = sk_handle_write_eof, - .set_frozen = sk_handle_set_frozen, - .socket_error = sk_handle_socket_error, - .peer_info = sk_handle_peer_info, -}; - -Socket *make_handle_socket(HANDLE send_H, HANDLE recv_H, HANDLE stderr_H, - Plug *plug, bool overlapped) -{ - HandleSocket *hs; - int flags = (overlapped ? HANDLE_FLAG_OVERLAPPED : 0); - - hs = snew(HandleSocket); - hs->sock.vt = &HandleSocket_sockvt; - hs->plug = plug; - hs->error = NULL; - hs->frozen = UNFROZEN; - bufchain_init(&hs->inputdata); - psb_init(&hs->psb); - - hs->recv_H = recv_H; - hs->recv_h = handle_input_new(hs->recv_H, handle_gotdata, hs, flags); - hs->send_H = send_H; - hs->send_h = handle_output_new(hs->send_H, handle_sentdata, hs, flags); - hs->stderr_H = stderr_H; - if (hs->stderr_H) - hs->stderr_h = handle_input_new(hs->stderr_H, handle_stderr, - hs, flags); - - hs->defer_close = hs->deferred_close = false; - - return &hs->sock; -} diff --git a/windows/winjump.c b/windows/winjump.c deleted file mode 100644 index 358504fd..00000000 --- a/windows/winjump.c +++ /dev/null @@ -1,748 +0,0 @@ -/* - * 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 - -#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, ensuring that 'obj' really is a 'type **'. */ -#define typecheck(checkexpr, result) \ - (sizeof(checkexpr) ? (result) : (result)) -#define COMPTR(type, obj) &IID_##type, \ - typecheck((obj)-(type **)(obj), (void **)(void *)(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; - 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) { - settings_r *psettings_tmp = open_settings_r(sessionname); - if (!psettings_tmp) { - sfree(app_path); - return NULL; - } - close_settings_r(psettings_tmp); - } - - /* Create the new item. */ - if (!SUCCEEDED(CoCreateInstance(&CLSID_ShellLink, NULL, - CLSCTX_INPROC_SERVER, - COMPTR(IShellLink, &ret)))) { - sfree(app_path); - return NULL; - } - - /* Set path, parameters, icon and description. */ - ret->lpVtbl->SetPath(ret, app_path); - - if (sessionname) { - /* The leading space is reported to work around a Windows 10 - * behaviour change in which an argument string starting with - * '@' causes the SetArguments method to silently do the wrong - * thing. */ - param_string = dupcat(" @", sessionname); - } else { - param_string = dupstr(""); - } - ret->lpVtbl->SetArguments(ret, param_string); - sfree(param_string); - - if (sessionname) { - desc_string = dupcat("Connect to PuTTY session '", sessionname, "'"); - } else { - assert(appname); - desc_string = dupprintf("Run %.*s", - (int)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", - (int)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; - bool 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; - bool 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 ((osMajorVersion < 6) || (osMajorVersion == 6 && osMinorVersion < 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 ((osMajorVersion < 6) || (osMajorVersion == 6 && osMinorVersion < 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(); - } -} - -/* Set Explicit App User Model Id to fix removable media error with - jump lists */ - -bool set_explicit_app_user_model_id(void) -{ - DECL_WINDOWS_FUNCTION(static, HRESULT, SetCurrentProcessExplicitAppUserModelID, - (PCWSTR)); - - static HMODULE shell32_module = 0; - - if (!shell32_module) - { - shell32_module = load_system32_dll("Shell32.dll"); - /* - * We can't typecheck this function here, because it's defined - * in , which we're not including due to clashes - * with all the manual-COM machinery above. - */ - GET_WINDOWS_FUNCTION_NO_TYPECHECK( - shell32_module, SetCurrentProcessExplicitAppUserModelID); - } - - if (p_SetCurrentProcessExplicitAppUserModelID) - { - if (p_SetCurrentProcessExplicitAppUserModelID(L"SimonTatham.PuTTY") == S_OK) - { - return true; - } - return false; - } - /* Function doesn't exist, which is ok for Pre-7 systems */ - - return true; - -} diff --git a/windows/winnet.c b/windows/winnet.c deleted file mode 100644 index 3b4da3cc..00000000 --- a/windows/winnet.c +++ /dev/null @@ -1,1825 +0,0 @@ -/* - * Windows networking abstraction. - * - * For the IPv6 code in here I am indebted to Jeroen Massar and - * unfix.org. - */ - -#include /* need to put this first, for winelib builds */ - -#include -#include -#include - -#define NEED_DECLARATION_OF_SELECT /* in order to initialise it */ - -#include "putty.h" -#include "network.h" -#include "tree234.h" -#include "ssh.h" - -#include - -#ifndef NO_IPV6 -#ifdef __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wmissing-braces" -#endif -const struct in6_addr in6addr_any = IN6ADDR_ANY_INIT; -const struct in6_addr in6addr_loopback = IN6ADDR_LOOPBACK_INIT; -#ifdef __clang__ -#pragma clang diagnostic pop -#endif -#endif - -#define ipv4_is_loopback(addr) \ - ((p_ntohl(addr.s_addr) & 0xFF000000L) == 0x7F000000L) - -/* - * 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; -}; - -typedef struct NetSocket NetSocket; -struct NetSocket { - const char *error; - SOCKET s; - Plug *plug; - bufchain output_data; - bool connected; - bool writable; - bool frozen; /* this causes readability notifications to be ignored */ - bool frozen_readable; /* this means we missed at least one readability - * notification while we were frozen */ - bool localhost_only; /* for listening sockets */ - char oobdata[1]; - size_t sending_oob; - bool oobinline, nodelay, keepalive, privport; - enum { EOF_NO, EOF_PENDING, EOF_SENT } outgoingeof; - SockAddr *addr; - SockAddrStep step; - int port; - int pending_error; /* in case send() returns error */ - /* - * 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. - */ - NetSocket *parent, *child; - - Socket sock; -}; - -struct SockAddr { - int refcount; - char *error; - bool resolved; - bool namedpipe; /* indicates that this SockAddr is phony, holding a Windows - * named pipe pathname instead of a network address */ -#ifndef NO_IPV6 - struct addrinfo *ais; /* Addresses IPv6 style. */ -#endif - unsigned long *addresses; /* Addresses IPv4 style. */ - int naddresses; - 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) -{ - NetSocket *a = (NetSocket *)av, *b = (NetSocket *)bv; - uintptr_t as = (uintptr_t) a->s, bs = (uintptr_t) 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) -{ - NetSocket *b = (NetSocket *)bv; - uintptr_t as = (uintptr_t) av, bs = (uintptr_t) 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, const char FAR *, inet_ntop, - (int, void FAR *, char *, size_t)); -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, shutdown, (SOCKET, int)); -DECL_WINDOWS_FUNCTION(static, int, ioctlsocket, - (SOCKET, long, u_long FAR *)); -DECL_WINDOWS_FUNCTION(static, SOCKET, accept, - (SOCKET, struct sockaddr FAR *, int FAR *)); -DECL_WINDOWS_FUNCTION(static, int, getpeername, - (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, DWORD hostlen, char FAR * serv, - DWORD 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 - -static bool 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; - } - - return true; -} - -DEF_WINDOWS_FUNCTION(WSAAsyncSelect); -DEF_WINDOWS_FUNCTION(WSAEventSelect); -DEF_WINDOWS_FUNCTION(WSAGetLastError); -DEF_WINDOWS_FUNCTION(WSAEnumNetworkEvents); -DEF_WINDOWS_FUNCTION(select); - -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) - modalfatalbox("Unable to load any WinSock library"); - -#ifndef NO_IPV6 - /* Check if we have getaddrinfo in Winsock */ - if (GetProcAddress(winsock_module, "getaddrinfo") != NULL) { - GET_WINDOWS_FUNCTION(winsock_module, getaddrinfo); - GET_WINDOWS_FUNCTION(winsock_module, freeaddrinfo); - GET_WINDOWS_FUNCTION_NO_TYPECHECK(winsock_module, getnameinfo); - /* This function would fail its type-check if we did one, - * because the VS header file provides an inline definition - * which is __cdecl instead of WINAPI. */ - GET_WINDOWS_FUNCTION_NO_TYPECHECK(winsock_module, gai_strerror); - } else { - /* Fall back to wship6.dll for Windows 2000 */ - wship6_module = load_system32_dll("wship6.dll"); - if (wship6_module) { - GET_WINDOWS_FUNCTION(wship6_module, getaddrinfo); - GET_WINDOWS_FUNCTION(wship6_module, freeaddrinfo); - /* See comment above about type check */ - GET_WINDOWS_FUNCTION_NO_TYPECHECK(wship6_module, getnameinfo); - GET_WINDOWS_FUNCTION_NO_TYPECHECK(winsock_module, gai_strerror); - } else { - } - } - GET_WINDOWS_FUNCTION(winsock2_module, WSAAddressToStringA); -#endif - - GET_WINDOWS_FUNCTION(winsock_module, WSAAsyncSelect); - GET_WINDOWS_FUNCTION(winsock_module, WSAEventSelect); - /* We don't type-check select because at least some MinGW versions - * of the Windows API headers seem to disagree with the - * documentation on whether the 'struct timeval *' pointer is - * const or not. */ - GET_WINDOWS_FUNCTION_NO_TYPECHECK(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_NO_TYPECHECK(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); - /* Older Visual Studio, and MinGW as of Ubuntu 16.04, don't know - * about this function at all, so can't type-check it. Also there - * seems to be some disagreement in the VS headers about whether - * the second argument is void * or const void *, so I omit the - * type check. */ - GET_WINDOWS_FUNCTION_NO_TYPECHECK(winsock_module, inet_ntop); - 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, shutdown); - GET_WINDOWS_FUNCTION(winsock_module, ioctlsocket); - GET_WINDOWS_FUNCTION(winsock_module, accept); - GET_WINDOWS_FUNCTION(winsock_module, getpeername); - 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)) { - modalfatalbox("Unable to initialise WinSock"); - } - - sktree = newtree234(cmpfortree); -} - -void sk_cleanup(void) -{ - NetSocket *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 -} - -const char *winsock_error_string(int error) -{ - /* - * Error codes we know about and have historically had reasonably - * sensible error messages for. - */ - switch (error) { - case WSAEACCES: - return "Network error: Permission denied"; - case WSAEADDRINUSE: - return "Network error: Address already in use"; - 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"; - } - - /* - * Handle any other error code by delegating to win_strerror. - */ - return win_strerror(error); -} - -SockAddr *sk_namelookup(const char *host, char **canonicalname, - int address_family) -{ - SockAddr *ret = snew(SockAddr); - 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(SockAddr)); -#ifndef NO_IPV6 - ret->ais = NULL; -#endif - ret->namedpipe = false; - 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 = 0; -#ifndef NO_IPV6 - /* - * Use getaddrinfo when it's available - */ - if (p_getaddrinfo) { - struct addrinfo hints; - memset(&hints, 0, sizeof(hints)); - hints.ai_family = hint_family; - hints.ai_flags = AI_CANONNAME; - { - /* strip [] on IPv6 address literals */ - char *trimmed_host = host_strduptrim(host); - err = p_getaddrinfo(trimmed_host, NULL, &hints, &ret->ais); - sfree(trimmed_host); - } - if (err == 0) - ret->resolved = true; - } else -#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 = dupstr(realhost); - return ret; -} - -SockAddr *sk_nonamelookup(const char *host) -{ - SockAddr *ret = snew(SockAddr); - ret->error = NULL; - ret->resolved = false; -#ifndef NO_IPV6 - ret->ais = NULL; -#endif - ret->namedpipe = false; - ret->addresses = NULL; - ret->naddresses = 0; - ret->refcount = 1; - strncpy(ret->hostname, host, lenof(ret->hostname)); - ret->hostname[lenof(ret->hostname)-1] = '\0'; - return ret; -} - -SockAddr *sk_namedpipe_addr(const char *pipename) -{ - SockAddr *ret = snew(SockAddr); - ret->error = NULL; - ret->resolved = false; -#ifndef NO_IPV6 - ret->ais = NULL; -#endif - ret->namedpipe = true; - ret->addresses = NULL; - ret->naddresses = 0; - ret->refcount = 1; - strncpy(ret->hostname, pipename, lenof(ret->hostname)); - ret->hostname[lenof(ret->hostname)-1] = '\0'; - return ret; -} - -static bool 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, "", 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'; - } -} - -/* - * This constructs a SockAddr that points at one specific sub-address - * of a parent SockAddr. The returned SockAddr does not own all its - * own memory: it points into the old one's data structures, so it - * MUST NOT be used after the old one is freed, and it MUST NOT be - * passed to sk_addr_free. (The latter is why it's returned by value - * rather than dynamically allocated - that should clue in anyone - * writing a call to it that something is weird about it.) - */ -static SockAddr sk_extractaddr_tmp( - SockAddr *addr, const SockAddrStep *step) -{ - SockAddr toret; - toret = *addr; /* structure copy */ - toret.refcount = 1; - -#ifndef NO_IPV6 - toret.ais = step->ai; -#endif - if (SOCKADDR_FAMILY(addr, *step) == AF_INET -#ifndef NO_IPV6 - && !toret.ais -#endif - ) - toret.addresses += step->curraddr; - - return toret; -} - -bool sk_addr_needs_port(SockAddr *addr) -{ - return !addr->namedpipe; -} - -bool sk_hostname_is_local(const 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 bool ipv4_is_local_addr(struct in_addr addr) -{ - if (ipv4_is_loopback(addr)) - return true; /* loopback addresses are local */ - if (!n_local_interfaces) { - SOCKET s = p_socket(AF_INET, SOCK_DGRAM, 0); - DWORD retbytes; - - SetHandleInformation((HANDLE)s, HANDLE_FLAG_INHERIT, 0); - - 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 - n_local_interfaces = -1; - } - 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 true; /* this address is local */ - } - } - return false; /* this address is not local */ -} - -bool 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 sockaddr_in6 *)step.ai->ai_addr)->sin6_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 false; /* we don't know; assume not */ - } -} - -bool sk_address_is_special_local(SockAddr *addr) -{ - return false; /* no Unix-domain socket analogue here */ -} - -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 - unreachable("bad address family in sk_addrcopy"); - } 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_net_plug(Socket *sock, Plug *p) -{ - NetSocket *s = container_of(sock, NetSocket, sock); - Plug *ret = s->plug; - if (p) - s->plug = p; - return ret; -} - -static void sk_net_close(Socket *s); -static size_t sk_net_write(Socket *s, const void *data, size_t len); -static size_t sk_net_write_oob(Socket *s, const void *data, size_t len); -static void sk_net_write_eof(Socket *s); -static void sk_net_set_frozen(Socket *s, bool is_frozen); -static const char *sk_net_socket_error(Socket *s); -static SocketPeerInfo *sk_net_peer_info(Socket *s); - -static const SocketVtable NetSocket_sockvt = { - .plug = sk_net_plug, - .close = sk_net_close, - .write = sk_net_write, - .write_oob = sk_net_write_oob, - .write_eof = sk_net_write_eof, - .set_frozen = sk_net_set_frozen, - .socket_error = sk_net_socket_error, - .peer_info = sk_net_peer_info, -}; - -static Socket *sk_net_accept(accept_ctx_t ctx, Plug *plug) -{ - DWORD err; - const char *errstr; - NetSocket *ret; - - /* - * Create NetSocket structure. - */ - ret = snew(NetSocket); - ret->sock.vt = &NetSocket_sockvt; - ret->error = NULL; - ret->plug = plug; - bufchain_init(&ret->output_data); - ret->writable = true; /* to start with */ - ret->sending_oob = 0; - ret->outgoingeof = EOF_NO; - ret->frozen = true; - ret->frozen_readable = false; - ret->localhost_only = false; /* unused, but best init anyway */ - ret->pending_error = 0; - ret->parent = ret->child = NULL; - ret->addr = NULL; - - ret->s = (SOCKET)ctx.p; - - if (ret->s == INVALID_SOCKET) { - err = p_WSAGetLastError(); - ret->error = winsock_error_string(err); - return &ret->sock; - } - - ret->oobinline = false; - - /* 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, true); - if (errstr) { - ret->error = errstr; - return &ret->sock; - } - - add234(sktree, ret); - - return &ret->sock; -} - -static DWORD try_connect(NetSocket *sock) -{ - SOCKET s; -#ifndef NO_IPV6 - SOCKADDR_IN6 a6; -#endif - SOCKADDR_IN a; - DWORD err; - const char *errstr; - short localport; - int family; - - if (sock->s != INVALID_SOCKET) { - do_select(sock->s, false); - p_closesocket(sock->s); - } - - { - SockAddr thisaddr = sk_extractaddr_tmp( - sock->addr, &sock->step); - plug_log(sock->plug, PLUGLOG_CONNECT_TRYING, - &thisaddr, 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; - } - - SetHandleInformation((HANDLE)s, HANDLE_FLAG_INHERIT, 0); - - 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, true); - 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 = true; - SockAddr thisaddr = sk_extractaddr_tmp(sock->addr, &sock->step); - plug_log(sock->plug, PLUGLOG_CONNECT_SUCCESS, - &thisaddr, sock->port, NULL, 0); - } - - err = 0; - - ret: - - /* - * No matter what happened, put the socket back in the tree. - */ - add234(sktree, sock); - - if (err) { - SockAddr thisaddr = sk_extractaddr_tmp( - sock->addr, &sock->step); - plug_log(sock->plug, PLUGLOG_CONNECT_FAILED, - &thisaddr, sock->port, sock->error, err); - } - return err; -} - -Socket *sk_new(SockAddr *addr, int port, bool privport, bool oobinline, - bool nodelay, bool keepalive, Plug *plug) -{ - NetSocket *ret; - DWORD err; - - /* - * Create NetSocket structure. - */ - ret = snew(NetSocket); - ret->sock.vt = &NetSocket_sockvt; - ret->error = NULL; - ret->plug = plug; - bufchain_init(&ret->output_data); - ret->connected = false; /* to start with */ - ret->writable = false; /* to start with */ - ret->sending_oob = 0; - ret->outgoingeof = EOF_NO; - ret->frozen = false; - ret->frozen_readable = false; - ret->localhost_only = false; /* 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 &ret->sock; -} - -Socket *sk_newlistener(const char *srcaddr, int port, Plug *plug, - bool local_host_only, int orig_address_family) -{ - SOCKET s; -#ifndef NO_IPV6 - SOCKADDR_IN6 a6; -#endif - SOCKADDR_IN a; - - DWORD err; - const char *errstr; - NetSocket *ret; - int retcode; - - int address_family; - - /* - * Create NetSocket structure. - */ - ret = snew(NetSocket); - ret->sock.vt = &NetSocket_sockvt; - ret->error = NULL; - ret->plug = plug; - bufchain_init(&ret->output_data); - ret->writable = false; /* to start with */ - ret->sending_oob = 0; - ret->outgoingeof = EOF_NO; - ret->frozen = false; - ret->frozen_readable = false; - 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 &ret->sock; - } - - SetHandleInformation((HANDLE)s, HANDLE_FLAG_INHERIT, 0); - - ret->oobinline = false; - - { - BOOL on = true; - p_setsockopt(s, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, - (const char *)&on, sizeof(on)); - } - -#ifndef NO_IPV6 - if (address_family == AF_INET6) { - memset(&a6, 0, sizeof(a6)); - a6.sin6_family = AF_INET6; - if (local_host_only) - a6.sin6_addr = in6addr_loopback; - else - a6.sin6_addr = in6addr_any; - if (srcaddr != NULL && p_getaddrinfo) { - struct addrinfo hints; - struct addrinfo *ai; - int err; - - memset(&hints, 0, sizeof(hints)); - hints.ai_family = AF_INET6; - hints.ai_flags = 0; - { - /* strip [] on IPv6 address literals */ - char *trimmed_addr = host_strduptrim(srcaddr); - err = p_getaddrinfo(trimmed_addr, NULL, &hints, &ai); - sfree(trimmed_addr); - } - if (err == 0 && ai->ai_family == AF_INET6) { - a6.sin6_addr = - ((struct sockaddr_in6 *)ai->ai_addr)->sin6_addr; - } - } - a6.sin6_port = p_htons(port); - } else -#endif - { - bool got_addr = false; - 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 = true; - } - } - - /* - * ... 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 &ret->sock; - } - - - if (p_listen(s, SOMAXCONN) == SOCKET_ERROR) { - p_closesocket(s); - ret->error = winsock_error_string(p_WSAGetLastError()); - return &ret->sock; - } - - /* Set up a select mechanism. This could be an AsyncSelect on a - * window, or an EventSelect on an event object. */ - errstr = do_select(s, true); - if (errstr) { - p_closesocket(s); - ret->error = errstr; - return &ret->sock; - } - - 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) { - Socket *other = sk_newlistener(srcaddr, port, plug, - local_host_only, ADDRTYPE_IPV6); - - if (other) { - NetSocket *ns = container_of(other, NetSocket, sock); - if (!ns->error) { - ns->parent = ret; - ret->child = ns; - } else { - sfree(ns); - } - } - } -#endif - - return &ret->sock; -} - -static void sk_net_close(Socket *sock) -{ - NetSocket *s = container_of(sock, NetSocket, sock); - - if (s->child) - sk_net_close(&s->child->sock); - - bufchain_clear(&s->output_data); - - del234(sktree, s); - do_select(s->s, false); - p_closesocket(s->s); - if (s->addr) - sk_addr_free(s->addr); - delete_callbacks_for_context(s); - sfree(s); -} - -/* - * Deal with socket errors detected in try_send(). - */ -static void socket_error_callback(void *vs) -{ - NetSocket *s = (NetSocket *)vs; - - /* - * Just in case other socket work has caused this socket to vanish - * or become somehow non-erroneous before this callback arrived... - */ - if (!find234(sktree, s, NULL) || !s->pending_error) - return; - - /* - * An error has occurred on this socket. Pass it to the plug. - */ - plug_closing(s->plug, winsock_error_string(s->pending_error), - s->pending_error, 0); -} - -/* - * The function which tries to send on a socket once it's deemed - * writable. - */ -void try_send(NetSocket *s) -{ - while (s->sending_oob || bufchain_size(&s->output_data) > 0) { - int nsent; - DWORD err; - const void *data; - size_t len; - int urgentflag; - - if (s->sending_oob) { - urgentflag = MSG_OOB; - len = s->sending_oob; - data = &s->oobdata; - } else { - urgentflag = 0; - ptrlen bufdata = bufchain_prefix(&s->output_data); - data = bufdata.ptr; - len = bufdata.len; - } - len = min(len, INT_MAX); /* WinSock send() takes an int */ - nsent = p_send(s->s, data, len, urgentflag); - noise_ultralight(NOISE_SOURCE_IOLEN, 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 send() returns a socket error, 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; - queue_toplevel_callback(socket_error_callback, s); - 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); - } - } - } - - /* - * If we reach here, we've finished sending everything we might - * have needed to send. Send EOF, if we need to. - */ - if (s->outgoingeof == EOF_PENDING) { - p_shutdown(s->s, SD_SEND); - s->outgoingeof = EOF_SENT; - } -} - -static size_t sk_net_write(Socket *sock, const void *buf, size_t len) -{ - NetSocket *s = container_of(sock, NetSocket, sock); - - assert(s->outgoingeof == EOF_NO); - - /* - * 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 size_t sk_net_write_oob(Socket *sock, const void *buf, size_t len) -{ - NetSocket *s = container_of(sock, NetSocket, sock); - - assert(s->outgoingeof == EOF_NO); - - /* - * Replace the buffer list on the socket with the data. - */ - bufchain_clear(&s->output_data); - assert(len <= sizeof(s->oobdata)); - 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; -} - -static void sk_net_write_eof(Socket *sock) -{ - NetSocket *s = container_of(sock, NetSocket, sock); - - assert(s->outgoingeof == EOF_NO); - - /* - * Mark the socket as pending outgoing EOF. - */ - s->outgoingeof = EOF_PENDING; - - /* - * Now try sending from the start of the buffer list. - */ - if (s->writable) - try_send(s); -} - -void select_result(WPARAM wParam, LPARAM lParam) -{ - int ret; - DWORD err; - char buf[20480]; /* nice big buffer for plenty of speed */ - NetSocket *s; - bool atmark; - - /* wParam is the socket itself */ - - if (wParam == 0) - return; /* boggle */ - - s = find234(sktree, (void *) wParam, cmpforsearch); - if (!s) - return; /* boggle */ - - if ((err = WSAGETSELECTERROR(lParam)) != 0) { - /* - * An error has occurred on this socket. Pass it to the - * plug. - */ - if (s->addr) { - SockAddr thisaddr = sk_extractaddr_tmp( - s->addr, &s->step); - plug_log(s->plug, PLUGLOG_CONNECT_FAILED, &thisaddr, s->port, - winsock_error_string(err), err); - while (err && s->addr && sk_nextaddr(s->addr, &s->step)) { - err = try_connect(s); - } - } - if (err != 0) - plug_closing(s->plug, winsock_error_string(err), err, 0); - return; - } - - noise_ultralight(NOISE_SOURCE_IOID, wParam); - - switch (WSAGETSELECTEVENT(lParam)) { - case FD_CONNECT: - s->connected = true; - s->writable = true; - - /* - * Once a socket is connected, we can stop falling back - * through the candidate addresses to connect to. But first, - * let the plug know we were successful. - */ - if (s->addr) { - SockAddr thisaddr = sk_extractaddr_tmp( - s->addr, &s->step); - plug_log(s->plug, PLUGLOG_CONNECT_SUCCESS, - &thisaddr, s->port, NULL, 0); - - 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 = true; - 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) { - u_long atmark_from_ioctl = 1; - p_ioctlsocket(s->s, SIOCATMARK, &atmark_from_ioctl); - /* - * 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. - */ - atmark = atmark_from_ioctl; - } else - atmark = true; - - ret = p_recv(s->s, buf, sizeof(buf), 0); - noise_ultralight(NOISE_SOURCE_IOLEN, ret); - if (ret < 0) { - err = p_WSAGetLastError(); - if (err == WSAEWOULDBLOCK) { - break; - } - } - if (ret < 0) { - plug_closing(s->plug, winsock_error_string(err), err, 0); - } else if (0 == ret) { - plug_closing(s->plug, NULL, 0, 0); - } else { - 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(NOISE_SOURCE_IOLEN, ret); - if (ret <= 0) { - int err = p_WSAGetLastError(); - plug_closing(s->plug, winsock_error_string(err), err, 0); - } else { - plug_receive(s->plug, 2, buf, ret); - } - break; - case FD_WRITE: { - int bufsize_before, bufsize_after; - s->writable = true; - 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. */ - do { - ret = p_recv(s->s, buf, sizeof(buf), 0); - if (ret < 0) { - err = p_WSAGetLastError(); - if (err == WSAEWOULDBLOCK) - break; - plug_closing(s->plug, winsock_error_string(err), err, 0); - } else { - if (ret) - plug_receive(s->plug, 0, buf, ret); - else - plug_closing(s->plug, NULL, 0, 0); - } - } while (ret > 0); - return; - 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 */ - accept_ctx_t actx; - - 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; - } - - actx.p = (void *)t; - -#ifndef NO_IPV6 - if (isa.ss_family == AF_INET && - s->localhost_only && - !ipv4_is_local_addr(((struct sockaddr_in *)&isa)->sin_addr)) -#else - if (s->localhost_only && !ipv4_is_local_addr(isa.sin_addr)) -#endif - { - p_closesocket(t); /* dodgy WinSock let nonlocal through */ - } else if (plug_accepting(s->plug, sk_net_accept, actx)) { - p_closesocket(t); /* denied or error */ - } - break; - } - } -} - -/* - * 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_net_socket_error(Socket *sock) -{ - NetSocket *s = container_of(sock, NetSocket, sock); - return s->error; -} - -static SocketPeerInfo *sk_net_peer_info(Socket *sock) -{ - NetSocket *s = container_of(sock, NetSocket, sock); -#ifdef NO_IPV6 - struct sockaddr_in addr; -#else - struct sockaddr_storage addr; - char buf[INET6_ADDRSTRLEN]; -#endif - int addrlen = sizeof(addr); - SocketPeerInfo *pi; - - if (p_getpeername(s->s, (struct sockaddr *)&addr, &addrlen) < 0) - return NULL; - - pi = snew(SocketPeerInfo); - pi->addressfamily = ADDRTYPE_UNSPEC; - pi->addr_text = NULL; - pi->port = -1; - pi->log_text = NULL; - - if (((struct sockaddr *)&addr)->sa_family == AF_INET) { - pi->addressfamily = ADDRTYPE_IPV4; - memcpy(pi->addr_bin.ipv4, &((struct sockaddr_in *)&addr)->sin_addr, 4); - pi->port = p_ntohs(((struct sockaddr_in *)&addr)->sin_port); - pi->addr_text = dupstr( - p_inet_ntoa(((struct sockaddr_in *)&addr)->sin_addr)); - pi->log_text = dupprintf("%s:%d", pi->addr_text, pi->port); - -#ifndef NO_IPV6 - } else if (((struct sockaddr *)&addr)->sa_family == AF_INET6) { - pi->addressfamily = ADDRTYPE_IPV6; - memcpy(pi->addr_bin.ipv6, - &((struct sockaddr_in6 *)&addr)->sin6_addr, 16); - pi->port = p_ntohs(((struct sockaddr_in6 *)&addr)->sin6_port); - pi->addr_text = dupstr( - p_inet_ntop(AF_INET6, &((struct sockaddr_in6 *)&addr)->sin6_addr, - buf, sizeof(buf))); - pi->log_text = dupprintf("[%s]:%d", pi->addr_text, pi->port); - -#endif - } else { - sfree(pi); - return NULL; - } - - return pi; -} - -static void sk_net_set_frozen(Socket *sock, bool is_frozen) -{ - NetSocket *s = container_of(sock, NetSocket, sock); - if (s->frozen == is_frozen) - return; - s->frozen = is_frozen; - if (!is_frozen) { - do_select(s->s, true); - if (s->frozen_readable) { - char c; - p_recv(s->s, &c, 1, MSG_PEEK); - } - } - s->frozen_readable = false; -} - -void socket_reselect_all(void) -{ - NetSocket *s; - int i; - - for (i = 0; (s = index234(sktree, i)) != NULL; i++) { - if (!s->frozen) - do_select(s->s, true); - } -} - -/* - * For Plink: enumerate all sockets currently active. - */ -SOCKET first_socket(int *state) -{ - NetSocket *s; - *state = 0; - s = index234(sktree, (*state)++); - return s ? s->s : INVALID_SOCKET; -} - -SOCKET next_socket(int *state) -{ - NetSocket *s = index234(sktree, (*state)++); - return s ? s->s : INVALID_SOCKET; -} - -bool socket_writable(SOCKET skt) -{ - NetSocket *s = find234(sktree, (void *)skt, cmpforsearch); - - if (s) - return bufchain_size(&s->output_data) > 0; - else - return false; -} - -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) -{ - char hostbuf[256]; /* MSDN docs for gethostname() promise this is enough */ - if (p_gethostname(hostbuf, sizeof(hostbuf)) < 0) - return NULL; - return dupstr(hostbuf); -} - -SockAddr *platform_get_x11_unix_address(const char *display, int displaynum) -{ - SockAddr *ret = snew(SockAddr); - memset(ret, 0, sizeof(SockAddr)); - ret->error = "unix sockets not supported on this platform"; - ret->refcount = 1; - return ret; -} diff --git a/windows/winnohlp.c b/windows/winnohlp.c deleted file mode 100644 index 62ddc65c..00000000 --- a/windows/winnohlp.c +++ /dev/null @@ -1,15 +0,0 @@ -/* - * nohelp.c: implement the has_embedded_chm() function for - * applications that have no help file at all, so that misc.c's - * buildinfo string knows not to talk meaninglessly about whether the - * nonexistent help file is present. - */ - -#include -#include -#include -#include - -#include "putty.h" - -int has_embedded_chm(void) { return -1; } diff --git a/windows/winnoise.c b/windows/winnoise.c deleted file mode 100644 index 65c4c92d..00000000 --- a/windows/winnoise.c +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Noise generation for PuTTY's cryptographic random number - * generator. - */ - -#include - -#include "putty.h" -#include "ssh.h" -#include "storage.h" - -#include - -DECL_WINDOWS_FUNCTION(static, BOOL, CryptAcquireContextA, - (HCRYPTPROV *, LPCTSTR, LPCTSTR, DWORD, DWORD)); -DECL_WINDOWS_FUNCTION(static, BOOL, CryptGenRandom, - (HCRYPTPROV, DWORD, BYTE *)); -DECL_WINDOWS_FUNCTION(static, BOOL, CryptReleaseContext, - (HCRYPTPROV, DWORD)); -static HMODULE wincrypt_module = NULL; - -bool win_read_random(void *buf, unsigned wanted) -{ - bool toret = false; - HCRYPTPROV crypt_provider; - - if (!wincrypt_module) { - wincrypt_module = load_system32_dll("advapi32.dll"); - GET_WINDOWS_FUNCTION(wincrypt_module, CryptAcquireContextA); - GET_WINDOWS_FUNCTION(wincrypt_module, CryptGenRandom); - GET_WINDOWS_FUNCTION(wincrypt_module, CryptReleaseContext); - } - - if (wincrypt_module && p_CryptAcquireContextA && - p_CryptGenRandom && p_CryptReleaseContext && - p_CryptAcquireContextA(&crypt_provider, NULL, NULL, PROV_RSA_FULL, - CRYPT_VERIFYCONTEXT)) { - toret = p_CryptGenRandom(crypt_provider, wanted, buf); - p_CryptReleaseContext(crypt_provider, 0); - } - - return toret; -} - -/* - * This function is called once, at PuTTY startup. - */ - -void noise_get_heavy(void (*func) (void *, int)) -{ - HANDLE srch; - WIN32_FIND_DATA finddata; - DWORD pid; - char winpath[MAX_PATH + 3]; - BYTE buf[32]; - - 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)); - - if (win_read_random(buf, sizeof(buf))) { - func(buf, sizeof(buf)); - smemclr(buf, sizeof(buf)); - } - - read_random_seed(func); -} - -/* - * 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(NOISE_SOURCE_FGWINDOW, &w, sizeof(w)); - w = GetCapture(); - random_add_noise(NOISE_SOURCE_CAPTURE, &w, sizeof(w)); - w = GetClipboardOwner(); - random_add_noise(NOISE_SOURCE_CLIPBOARD, &w, sizeof(w)); - z = GetQueueStatus(QS_ALLEVENTS); - random_add_noise(NOISE_SOURCE_QUEUE, &z, sizeof(z)); - - GetCursorPos(&pt); - random_add_noise(NOISE_SOURCE_CURSORPOS, &pt, sizeof(pt)); - - GlobalMemoryStatus(&memstat); - random_add_noise(NOISE_SOURCE_MEMINFO, &memstat, sizeof(memstat)); - - GetThreadTimes(GetCurrentThread(), times, times + 1, times + 2, - times + 3); - random_add_noise(NOISE_SOURCE_THREADTIME, ×, sizeof(times)); - GetProcessTimes(GetCurrentProcess(), times, times + 1, times + 2, - times + 3); - random_add_noise(NOISE_SOURCE_PROCTIME, ×, 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(NoiseSourceId id, unsigned long data) -{ - DWORD wintime; - LARGE_INTEGER perftime; - - random_add_noise(id, &data, sizeof(DWORD)); - - wintime = GetTickCount(); - random_add_noise(NOISE_SOURCE_TIME, &wintime, sizeof(DWORD)); - - if (QueryPerformanceCounter(&perftime)) - random_add_noise(NOISE_SOURCE_PERFCOUNT, &perftime, sizeof(perftime)); -} - -uint64_t prng_reseed_time_ms(void) -{ - FILETIME ft; - GetSystemTimeAsFileTime(&ft); - uint64_t value = ft.dwHighDateTime; - value = (value << 32) + ft.dwLowDateTime; - return value / 10000; /* 1 millisecond / 100ns */ -} diff --git a/windows/winnojmp.c b/windows/winnojmp.c deleted file mode 100644 index dd61dc69..00000000 --- a/windows/winnojmp.c +++ /dev/null @@ -1,8 +0,0 @@ -/* - * 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) {} diff --git a/windows/winnpc.c b/windows/winnpc.c deleted file mode 100644 index 9f7fcb1c..00000000 --- a/windows/winnpc.c +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Windows support module which deals with being a named-pipe client. - */ - -#include -#include - -#include "tree234.h" -#include "putty.h" -#include "network.h" -#include "proxy.h" -#include "ssh.h" - -#include "winsecur.h" - -HANDLE connect_to_named_pipe(const char *pipename, char **err) -{ - HANDLE pipehandle; - PSID usersid, pipeowner; - PSECURITY_DESCRIPTOR psd; - - assert(strncmp(pipename, "\\\\.\\pipe\\", 9) == 0); - assert(strchr(pipename + 9, '\\') == NULL); - - while (1) { - pipehandle = CreateFile(pipename, GENERIC_READ | GENERIC_WRITE, - 0, NULL, OPEN_EXISTING, - FILE_FLAG_OVERLAPPED, NULL); - - if (pipehandle != INVALID_HANDLE_VALUE) - break; - - if (GetLastError() != ERROR_PIPE_BUSY) { - *err = dupprintf( - "Unable to open named pipe '%s': %s", - pipename, win_strerror(GetLastError())); - return INVALID_HANDLE_VALUE; - } - - /* - * If we got ERROR_PIPE_BUSY, wait for the server to - * create a new pipe instance. (Since the server is - * expected to be winnps.c, which will do that immediately - * after a previous connection is accepted, that shouldn't - * take excessively long.) - */ - if (!WaitNamedPipe(pipename, NMPWAIT_USE_DEFAULT_WAIT)) { - *err = dupprintf( - "Error waiting for named pipe '%s': %s", - pipename, win_strerror(GetLastError())); - return INVALID_HANDLE_VALUE; - } - } - - if ((usersid = get_user_sid()) == NULL) { - CloseHandle(pipehandle); - *err = dupprintf( - "Unable to get user SID: %s", win_strerror(GetLastError())); - return INVALID_HANDLE_VALUE; - } - - if (p_GetSecurityInfo(pipehandle, SE_KERNEL_OBJECT, - OWNER_SECURITY_INFORMATION, - &pipeowner, NULL, NULL, NULL, - &psd) != ERROR_SUCCESS) { - CloseHandle(pipehandle); - *err = dupprintf( - "Unable to get named pipe security information: %s", - win_strerror(GetLastError())); - return INVALID_HANDLE_VALUE; - } - - if (!EqualSid(pipeowner, usersid)) { - CloseHandle(pipehandle); - LocalFree(psd); - *err = dupprintf( - "Owner of named pipe '%s' is not us", pipename); - return INVALID_HANDLE_VALUE; - } - - LocalFree(psd); - - return pipehandle; -} - -Socket *new_named_pipe_client(const char *pipename, Plug *plug) -{ - char *err = NULL; - HANDLE pipehandle = connect_to_named_pipe(pipename, &err); - if (pipehandle == INVALID_HANDLE_VALUE) - return new_error_socket_consume_string(plug, err); - else - return make_handle_socket(pipehandle, pipehandle, NULL, plug, true); -} diff --git a/windows/winnps.c b/windows/winnps.c deleted file mode 100644 index 4f9b638d..00000000 --- a/windows/winnps.c +++ /dev/null @@ -1,236 +0,0 @@ -/* - * Windows support module which deals with being a named-pipe server. - */ - -#include -#include - -#include "tree234.h" -#include "putty.h" -#include "network.h" -#include "proxy.h" -#include "ssh.h" - -#include "winsecur.h" - -typedef struct NamedPipeServerSocket { - /* Parameters for (repeated) creation of named pipe objects */ - PSECURITY_DESCRIPTOR psd; - PACL acl; - char *pipename; - - /* The current named pipe object + attempt to connect to it */ - HANDLE pipehandle; - OVERLAPPED connect_ovl; - struct handle *callback_handle; /* winhandl.c's reference */ - - /* PuTTY Socket machinery */ - Plug *plug; - char *error; - - Socket sock; -} NamedPipeServerSocket; - -static Plug *sk_namedpipeserver_plug(Socket *s, Plug *p) -{ - NamedPipeServerSocket *ps = container_of(s, NamedPipeServerSocket, sock); - Plug *ret = ps->plug; - if (p) - ps->plug = p; - return ret; -} - -static void sk_namedpipeserver_close(Socket *s) -{ - NamedPipeServerSocket *ps = container_of(s, NamedPipeServerSocket, sock); - - if (ps->callback_handle) - handle_free(ps->callback_handle); - CloseHandle(ps->pipehandle); - CloseHandle(ps->connect_ovl.hEvent); - sfree(ps->error); - sfree(ps->pipename); - if (ps->acl) - LocalFree(ps->acl); - if (ps->psd) - LocalFree(ps->psd); - sfree(ps); -} - -static const char *sk_namedpipeserver_socket_error(Socket *s) -{ - NamedPipeServerSocket *ps = container_of(s, NamedPipeServerSocket, sock); - return ps->error; -} - -static SocketPeerInfo *sk_namedpipeserver_peer_info(Socket *s) -{ - return NULL; -} - -static bool create_named_pipe(NamedPipeServerSocket *ps, bool first_instance) -{ - SECURITY_ATTRIBUTES sa; - - memset(&sa, 0, sizeof(sa)); - sa.nLength = sizeof(sa); - sa.lpSecurityDescriptor = ps->psd; - sa.bInheritHandle = false; - - ps->pipehandle = CreateNamedPipe - (/* lpName */ - ps->pipename, - - /* dwOpenMode */ - PIPE_ACCESS_DUPLEX | - FILE_FLAG_OVERLAPPED | - (first_instance ? FILE_FLAG_FIRST_PIPE_INSTANCE : 0), - - /* dwPipeMode */ - PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT -#ifdef PIPE_REJECT_REMOTE_CLIENTS - | PIPE_REJECT_REMOTE_CLIENTS -#endif - , - - /* nMaxInstances */ - PIPE_UNLIMITED_INSTANCES, - - /* nOutBufferSize, nInBufferSize */ - 4096, 4096, /* FIXME: think harder about buffer sizes? */ - - /* nDefaultTimeOut */ - 0 /* default timeout */, - - /* lpSecurityAttributes */ - &sa); - - return ps->pipehandle != INVALID_HANDLE_VALUE; -} - -static Socket *named_pipe_accept(accept_ctx_t ctx, Plug *plug) -{ - HANDLE conn = (HANDLE)ctx.p; - - return make_handle_socket(conn, conn, NULL, plug, true); -} - -static void named_pipe_accept_loop(NamedPipeServerSocket *ps, - bool got_one_already) -{ - while (1) { - int error; - char *errmsg; - - if (got_one_already) { - /* If we were called with a connection already waiting, - * skip this step. */ - got_one_already = false; - error = 0; - } else { - /* - * Call ConnectNamedPipe, which might succeed or might - * tell us that an overlapped operation is in progress and - * we should wait for our event object. - */ - if (ConnectNamedPipe(ps->pipehandle, &ps->connect_ovl)) - error = 0; - else - error = GetLastError(); - - if (error == ERROR_IO_PENDING) - return; - } - - if (error == 0 || error == ERROR_PIPE_CONNECTED) { - /* - * We've successfully retrieved an incoming connection, so - * ps->pipehandle now refers to that connection. So - * convert that handle into a separate connection-type - * Socket, and create a fresh one to be the new listening - * pipe. - */ - HANDLE conn = ps->pipehandle; - accept_ctx_t actx; - - actx.p = (void *)conn; - if (plug_accepting(ps->plug, named_pipe_accept, actx)) { - /* - * If the plug didn't want the connection, might as - * well close this handle. - */ - CloseHandle(conn); - } - - if (!create_named_pipe(ps, false)) { - error = GetLastError(); - } else { - /* - * Go round again to see if more connections can be - * got, or to begin waiting on the event object. - */ - continue; - } - } - - errmsg = dupprintf("Error while listening to named pipe: %s", - win_strerror(error)); - plug_log(ps->plug, 1, sk_namedpipe_addr(ps->pipename), 0, - errmsg, error); - sfree(errmsg); - break; - } -} - -static void named_pipe_connect_callback(void *vps) -{ - NamedPipeServerSocket *ps = (NamedPipeServerSocket *)vps; - named_pipe_accept_loop(ps, true); -} - -/* - * This socket type is only used for listening, so it should never - * be asked to write or set_frozen. - */ -static const SocketVtable NamedPipeServerSocket_sockvt = { - .plug = sk_namedpipeserver_plug, - .close = sk_namedpipeserver_close, - .socket_error = sk_namedpipeserver_socket_error, - .peer_info = sk_namedpipeserver_peer_info, -}; - -Socket *new_named_pipe_listener(const char *pipename, Plug *plug) -{ - NamedPipeServerSocket *ret = snew(NamedPipeServerSocket); - ret->sock.vt = &NamedPipeServerSocket_sockvt; - ret->plug = plug; - ret->error = NULL; - ret->psd = NULL; - ret->pipename = dupstr(pipename); - ret->acl = NULL; - ret->callback_handle = NULL; - - assert(strncmp(pipename, "\\\\.\\pipe\\", 9) == 0); - assert(strchr(pipename + 9, '\\') == NULL); - - if (!make_private_security_descriptor(GENERIC_READ | GENERIC_WRITE, - &ret->psd, &ret->acl, &ret->error)) { - goto cleanup; - } - - if (!create_named_pipe(ret, true)) { - ret->error = dupprintf("unable to create named pipe '%s': %s", - pipename, win_strerror(GetLastError())); - goto cleanup; - } - - memset(&ret->connect_ovl, 0, sizeof(ret->connect_ovl)); - ret->connect_ovl.hEvent = CreateEvent(NULL, true, false, NULL); - ret->callback_handle = - handle_add_foreign_event(ret->connect_ovl.hEvent, - named_pipe_connect_callback, ret); - named_pipe_accept_loop(ret, false); - - cleanup: - return &ret->sock; -} diff --git a/windows/winpgen.c b/windows/winpgen.c deleted file mode 100644 index e1c1b5b3..00000000 --- a/windows/winpgen.c +++ /dev/null @@ -1,2024 +0,0 @@ -/* - * PuTTY key generation front end (Windows). - */ - -#include -#include -#include -#include - -#include "putty.h" -#include "ssh.h" -#include "sshkeygen.h" -#include "licence.h" -#include "winsecur.h" -#include "puttygen-rc.h" - -#include - -#ifdef MSVC4 -#define ICON_BIG 1 -#endif - -#define WM_DONEKEY (WM_APP + 1) - -#define DEFAULT_KEY_BITS 2048 -#define DEFAULT_ECCURVE_INDEX 0 -#define DEFAULT_EDCURVE_INDEX 0 - -static char *cmdline_keyfile = NULL; - -/* - * Print a modal (Really Bad) message box and perform a fatal exit. - */ -void modalfatalbox(const 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); -} - -/* - * Print a non-fatal message box and do not exit. - */ -void nonfatal(const char *fmt, ...) -{ - va_list ap; - char *stuff; - - va_start(ap, fmt); - stuff = dupvprintf(fmt, ap); - va_end(ap); - MessageBox(NULL, stuff, "PuTTYgen Error", - MB_SYSTEMMODAL | MB_ICONERROR | MB_OK); - sfree(stuff); -} - -/* ---------------------------------------------------------------------- - * ProgressReceiver implementation. - */ - -#define PROGRESSRANGE 65535 - -struct progressphase { - double startpoint, total; - /* For exponential phases */ - double exp_probability, exp_current_value; -}; - -struct progress { - size_t nphases, phasessize; - struct progressphase *phases, *currphase; - - double scale; - HWND progbar; - - ProgressReceiver rec; -}; - -static ProgressPhase win_progress_add_linear( - ProgressReceiver *prog, double overall_cost) { - struct progress *p = container_of(prog, struct progress, rec); - - sgrowarray(p->phases, p->phasessize, p->nphases); - int phase = p->nphases++; - - p->phases[phase].total = overall_cost; - - ProgressPhase ph = { .n = phase }; - return ph; -} - -static ProgressPhase win_progress_add_probabilistic( - ProgressReceiver *prog, double cost_per_attempt, double probability) { - struct progress *p = container_of(prog, struct progress, rec); - - sgrowarray(p->phases, p->phasessize, p->nphases); - int phase = p->nphases++; - - p->phases[phase].exp_probability = 1.0 - probability; - p->phases[phase].exp_current_value = 1.0; - /* Expected number of attempts = 1 / probability of attempt succeeding */ - p->phases[phase].total = cost_per_attempt / probability; - - ProgressPhase ph = { .n = phase }; - return ph; -} - -static void win_progress_ready(ProgressReceiver *prog) -{ - struct progress *p = container_of(prog, struct progress, rec); - - double total = 0; - for (int i = 0; i < p->nphases; i++) { - p->phases[i].startpoint = total; - total += p->phases[i].total; - } - p->scale = PROGRESSRANGE / total; - - SendMessage(p->progbar, PBM_SETRANGE, 0, MAKELPARAM(0, PROGRESSRANGE)); -} - -static void win_progress_start_phase(ProgressReceiver *prog, - ProgressPhase phase) -{ - struct progress *p = container_of(prog, struct progress, rec); - - assert(phase.n < p->nphases); - p->currphase = &p->phases[phase.n]; -} - -static void win_progress_update(struct progress *p, double phasepos) -{ - double position = (p->currphase->startpoint + - p->currphase->total * phasepos); - position *= p->scale; - if (position < 0) - position = 0; - if (position > PROGRESSRANGE) - position = PROGRESSRANGE; - - SendMessage(p->progbar, PBM_SETPOS, (WPARAM)position, 0); -} - -static void win_progress_report(ProgressReceiver *prog, double progress) -{ - struct progress *p = container_of(prog, struct progress, rec); - - win_progress_update(p, progress); -} - -static void win_progress_report_attempt(ProgressReceiver *prog) -{ - struct progress *p = container_of(prog, struct progress, rec); - - p->currphase->exp_current_value *= p->currphase->exp_probability; - win_progress_update(p, 1.0 - p->currphase->exp_current_value); -} - -static void win_progress_report_phase_complete(ProgressReceiver *prog) -{ - struct progress *p = container_of(prog, struct progress, rec); - - win_progress_update(p, 1.0); -} - -static const ProgressReceiverVtable win_progress_vt = { - .add_linear = win_progress_add_linear, - .add_probabilistic = win_progress_add_probabilistic, - .ready = win_progress_ready, - .start_phase = win_progress_start_phase, - .report = win_progress_report, - .report_attempt = win_progress_report_attempt, - .report_phase_complete = win_progress_report_phase_complete, -}; - -static void win_progress_initialise(struct progress *p) -{ - p->nphases = p->phasessize = 0; - p->phases = p->currphase = NULL; - p->rec.vt = &win_progress_vt; -} - -static void win_progress_cleanup(struct progress *p) -{ - sfree(p->phases); -} - -struct PassphraseProcStruct { - char **passphrase; - char *comment; -}; - -/* - * Dialog-box function for the passphrase box. - */ -static INT_PTR 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); - burnstr(*passphrase); - *passphrase = dupstr(""); - 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) { - burnstr(*passphrase); - *passphrase = GetDlgItemText_alloc(hwnd, 102); - } - return 0; - } - return 0; - case WM_CLOSE: - EndDialog(hwnd, 0); - return 0; - } - return 0; -} - -static void try_get_dlg_item_uint32(HWND hwnd, int id, uint32_t *out) -{ - char buf[128]; - if (!GetDlgItemText(hwnd, id, buf, sizeof(buf))) - return; - - if (!*buf) - return; - - char *end; - unsigned long val = strtoul(buf, &end, 10); - if (*end) - return; - - if ((val >> 16) >> 16) - return; - - *out = val; -} - -static ppk_save_parameters save_params; - -struct PPKParams { - ppk_save_parameters params; - uint32_t time_passes, time_ms; -}; - -/* - * Dialog-box function for the passphrase box. - */ -static INT_PTR CALLBACK PPKParamsProc(HWND hwnd, UINT msg, - WPARAM wParam, LPARAM lParam) -{ - struct PPKParams *pp; - char *buf; - - if (msg == WM_INITDIALOG) { - pp = (struct PPKParams *)lParam; - SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)pp); - } else { - pp = (struct PPKParams *)GetWindowLongPtr(hwnd, GWLP_USERDATA); - } - - switch (msg) { - case WM_INITDIALOG: - SetForegroundWindow(hwnd); - SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0, - SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW); - - if (has_help()) - SetWindowLongPtr(hwnd, GWL_EXSTYLE, - GetWindowLongPtr(hwnd, GWL_EXSTYLE) | - WS_EX_CONTEXTHELP); - - /* - * 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); - } - - CheckRadioButton(hwnd, IDC_PPKVER_2, IDC_PPKVER_3, - IDC_PPKVER_2 + (pp->params.fmt_version - 2)); - - CheckRadioButton( - hwnd, IDC_KDF_ARGON2ID, IDC_KDF_ARGON2D, - (pp->params.argon2_flavour == Argon2id ? IDC_KDF_ARGON2ID : - pp->params.argon2_flavour == Argon2i ? IDC_KDF_ARGON2I : - /* pp->params.argon2_flavour == Argon2d ? */ IDC_KDF_ARGON2D)); - - buf = dupprintf("%"PRIu32, pp->params.argon2_mem); - SetDlgItemText(hwnd, IDC_ARGON2_MEM, buf); - sfree(buf); - - if (pp->params.argon2_passes_auto) { - CheckRadioButton(hwnd, IDC_PPK_AUTO_YES, IDC_PPK_AUTO_NO, - IDC_PPK_AUTO_YES); - buf = dupprintf("%"PRIu32, pp->time_ms); - SetDlgItemText(hwnd, IDC_ARGON2_TIME, buf); - sfree(buf); - } else { - CheckRadioButton(hwnd, IDC_PPK_AUTO_YES, IDC_PPK_AUTO_NO, - IDC_PPK_AUTO_NO); - buf = dupprintf("%"PRIu32, pp->time_passes); - SetDlgItemText(hwnd, IDC_ARGON2_TIME, buf); - sfree(buf); - } - - buf = dupprintf("%"PRIu32, pp->params.argon2_parallelism); - SetDlgItemText(hwnd, IDC_ARGON2_PARALLEL, buf); - sfree(buf); - - return 0; - case WM_COMMAND: - switch (LOWORD(wParam)) { - case IDOK: - EndDialog(hwnd, 1); - return 0; - case IDCANCEL: - EndDialog(hwnd, 0); - return 0; - case IDC_PPKVER_2: - pp->params.fmt_version = 2; - return 0; - case IDC_PPKVER_3: - pp->params.fmt_version = 3; - return 0; - case IDC_KDF_ARGON2ID: - pp->params.argon2_flavour = Argon2id; - return 0; - case IDC_KDF_ARGON2I: - pp->params.argon2_flavour = Argon2i; - return 0; - case IDC_KDF_ARGON2D: - pp->params.argon2_flavour = Argon2d; - return 0; - case IDC_ARGON2_MEM: - try_get_dlg_item_uint32(hwnd, IDC_ARGON2_MEM, - &pp->params.argon2_mem); - return 0; - case IDC_PPK_AUTO_YES: - pp->params.argon2_passes_auto = true; - buf = dupprintf("%"PRIu32, pp->time_ms); - SetDlgItemText(hwnd, IDC_ARGON2_TIME, buf); - sfree(buf); - return 0; - case IDC_PPK_AUTO_NO: - pp->params.argon2_passes_auto = false; - buf = dupprintf("%"PRIu32, pp->time_passes); - SetDlgItemText(hwnd, IDC_ARGON2_TIME, buf); - sfree(buf); - return 0; - case IDC_ARGON2_TIME: - try_get_dlg_item_uint32(hwnd, IDC_ARGON2_TIME, - pp->params.argon2_passes_auto ? - &pp->time_ms : &pp->time_passes); - return 0; - case IDC_ARGON2_PARALLEL: - try_get_dlg_item_uint32(hwnd, IDC_ARGON2_PARALLEL, - &pp->params.argon2_parallelism); - return 0; - } - return 0; - case WM_HELP: { - int id = ((LPHELPINFO)lParam)->iCtrlId; - const char *topic = NULL; - switch (id) { - case IDC_PPKVER_STATIC: - case IDC_PPKVER_2: - case IDC_PPKVER_3: - topic = WINHELP_CTX_puttygen_ppkver; break; - case IDC_KDF_STATIC: - case IDC_KDF_ARGON2ID: - case IDC_KDF_ARGON2I: - case IDC_KDF_ARGON2D: - case IDC_ARGON2_MEM_STATIC: - case IDC_ARGON2_MEM: - case IDC_ARGON2_MEM_STATIC2: - case IDC_ARGON2_TIME_STATIC: - case IDC_ARGON2_TIME: - case IDC_PPK_AUTO_YES: - case IDC_PPK_AUTO_NO: - case IDC_ARGON2_PARALLEL_STATIC: - case IDC_ARGON2_PARALLEL: - topic = WINHELP_CTX_puttygen_kdfparam; break; - } - if (topic) { - launch_help(hwnd, topic); - } else { - MessageBeep(0); - } - break; - } - 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 bool prompt_keyfile(HWND hwnd, char *dlgtitle, - char *filename, bool save, bool 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_PTR CALLBACK LicenceProc(HWND hwnd, UINT msg, - WPARAM wParam, LPARAM lParam) -{ - switch (msg) { - case WM_INITDIALOG: { - /* - * 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, 1000, LICENCE_TEXT("\r\n\r\n")); - 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_PTR 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); - } - - { - char *buildinfo_text = buildinfo("\r\n"); - char *text = dupprintf - ("PuTTYgen\r\n\r\n%s\r\n\r\n%s\r\n\r\n%s", - ver, buildinfo_text, - "\251 " SHORT_COPYRIGHT_DETAILS ". All rights reserved."); - sfree(buildinfo_text); - SetDlgItemText(hwnd, 1000, text); - MakeDlgItemBorderless(hwnd, 1000); - sfree(text); - } - 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; - case 102: - /* Load web browser */ - ShellExecute(hwnd, "open", - "https://www.chiark.greenend.org.uk/~sgtatham/putty/", - 0, 0, SW_SHOWDEFAULT); - return 0; - } - return 0; - case WM_CLOSE: - EndDialog(hwnd, 1); - return 0; - } - return 0; -} - -typedef enum {RSA, DSA, ECDSA, EDDSA} keytype; - -/* - * Thread to generate a key. - */ -struct rsa_key_thread_params { - HWND progressbar; /* notify this with progress */ - HWND dialog; /* notify this on completion */ - int key_bits; /* bits in key modulus (RSA, DSA) */ - int curve_bits; /* bits in elliptic curve (ECDSA) */ - keytype keytype; - const PrimeGenerationPolicy *primepolicy; - bool rsa_strong; - union { - RSAKey *key; - struct dsa_key *dsakey; - struct ecdsa_key *eckey; - struct eddsa_key *edkey; - }; -}; -static DWORD WINAPI generate_key_thread(void *param) -{ - struct rsa_key_thread_params *params = - (struct rsa_key_thread_params *) param; - struct progress prog; - prog.progbar = params->progressbar; - - win_progress_initialise(&prog); - - PrimeGenerationContext *pgc = primegen_new_context(params->primepolicy); - - if (params->keytype == DSA) - dsa_generate(params->dsakey, params->key_bits, pgc, &prog.rec); - else if (params->keytype == ECDSA) - ecdsa_generate(params->eckey, params->curve_bits); - else if (params->keytype == EDDSA) - eddsa_generate(params->edkey, params->curve_bits); - else - rsa_generate(params->key, params->key_bits, params->rsa_strong, - pgc, &prog.rec); - - primegen_free_context(pgc); - - PostMessage(params->dialog, WM_DONEKEY, 0, 0); - - win_progress_cleanup(&prog); - - sfree(params); - return 0; -} - -struct MainDlgState { - bool collecting_entropy; - bool generation_thread_exists; - bool key_exists; - int entropy_got, entropy_required, entropy_size; - int key_bits, curve_bits; - bool ssh2; - keytype keytype; - const PrimeGenerationPolicy *primepolicy; - bool rsa_strong; - FingerprintType fptype; - char **commentptr; /* points to key.comment or ssh2key.comment */ - ssh2_userkey ssh2key; - unsigned *entropy; - union { - RSAKey key; - struct dsa_key dsakey; - struct ecdsa_key eckey; - struct eddsa_key edkey; - }; - HMENU filemenu, keymenu, cvtmenu; -}; - -static void hidemany(HWND hwnd, const int *ids, bool hideit) -{ - while (*ids) { - ShowWindow(GetDlgItem(hwnd, *ids++), (hideit ? SW_HIDE : SW_SHOW)); - } -} - -static void setupbigedit1(HWND hwnd, int id, int idstatic, RSAKey *key) -{ - char *buffer = ssh1_pubkey_str(key); - SetDlgItemText(hwnd, id, buffer); - SetDlgItemText(hwnd, idstatic, - "&Public key for pasting into authorized_keys file:"); - sfree(buffer); -} - -static void setupbigedit2(HWND hwnd, int id, int idstatic, - ssh2_userkey *key) -{ - char *buffer = ssh2_pubkey_openssh_str(key); - SetDlgItemText(hwnd, id, buffer); - SetDlgItemText(hwnd, idstatic, "&Public key for pasting into " - "OpenSSH authorized_keys file:"); - sfree(buffer); -} - -/* - * 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); -} - -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_KEYSSH2ECDSA, IDC_KEYSSH2EDDSA, - IDC_PRIMEGEN_PROB, IDC_PRIMEGEN_MAURER_SIMPLE, IDC_PRIMEGEN_MAURER_COMPLEX, - IDC_RSA_STRONG, - IDC_FPTYPE_SHA256, IDC_FPTYPE_MD5, - IDC_PPK_PARAMS, - IDC_BITSSTATIC, IDC_BITS, - IDC_ECCURVESTATIC, IDC_ECCURVE, - IDC_EDCURVESTATIC, IDC_EDCURVE, - IDC_NOTHINGSTATIC, - IDC_ABOUT, - IDC_GIVEHELP, - IDC_IMPORT, - IDC_EXPORT_OPENSSH_AUTO, IDC_EXPORT_OPENSSH_NEW, - 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_KEYSSH2ECDSA), 1); - EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH2EDDSA), 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->keymenu, IDC_KEYSSH2ECDSA, - MF_ENABLED|MF_BYCOMMAND); - EnableMenuItem(state->keymenu, IDC_KEYSSH2EDDSA, - MF_ENABLED|MF_BYCOMMAND); - EnableMenuItem(state->cvtmenu, IDC_IMPORT, MF_ENABLED|MF_BYCOMMAND); - EnableMenuItem(state->cvtmenu, IDC_EXPORT_OPENSSH_AUTO, - MF_GRAYED|MF_BYCOMMAND); - EnableMenuItem(state->cvtmenu, IDC_EXPORT_OPENSSH_NEW, - 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_KEYSSH2ECDSA), 0); - EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH2EDDSA), 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->keymenu, IDC_KEYSSH2ECDSA, - MF_GRAYED|MF_BYCOMMAND); - EnableMenuItem(state->keymenu, IDC_KEYSSH2EDDSA, - MF_GRAYED|MF_BYCOMMAND); - EnableMenuItem(state->cvtmenu, IDC_IMPORT, MF_GRAYED|MF_BYCOMMAND); - EnableMenuItem(state->cvtmenu, IDC_EXPORT_OPENSSH_AUTO, - MF_GRAYED|MF_BYCOMMAND); - EnableMenuItem(state->cvtmenu, IDC_EXPORT_OPENSSH_NEW, - 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_KEYSSH2ECDSA), 1); - EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH2EDDSA), 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->keymenu, IDC_KEYSSH2ECDSA, - MF_ENABLED|MF_BYCOMMAND); - EnableMenuItem(state->keymenu, IDC_KEYSSH2EDDSA, - 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_AUTO, SSH_KEYTYPE_OPENSSH_AUTO); - do_export_menuitem(IDC_EXPORT_OPENSSH_NEW, SSH_KEYTYPE_OPENSSH_NEW); - do_export_menuitem(IDC_EXPORT_SSHCOM, SSH_KEYTYPE_SSHCOM); -#undef do_export_menuitem - break; - } -} - -/* - * Helper functions to set the key type, taking care of keeping the - * menu and radio button selections in sync and also showing/hiding - * the appropriate size/curve control for the current key type. - */ -void ui_update_key_type_ctrls(HWND hwnd) -{ - enum { BITS, ECCURVE, EDCURVE, NOTHING } which; - static const int bits_ids[] = { - IDC_BITSSTATIC, IDC_BITS, 0 - }; - static const int eccurve_ids[] = { - IDC_ECCURVESTATIC, IDC_ECCURVE, 0 - }; - static const int edcurve_ids[] = { - IDC_EDCURVESTATIC, IDC_EDCURVE, 0 - }; - static const int nothing_ids[] = { - IDC_NOTHINGSTATIC, 0 - }; - - if (IsDlgButtonChecked(hwnd, IDC_KEYSSH1) || - IsDlgButtonChecked(hwnd, IDC_KEYSSH2RSA) || - IsDlgButtonChecked(hwnd, IDC_KEYSSH2DSA)) { - which = BITS; - } else if (IsDlgButtonChecked(hwnd, IDC_KEYSSH2ECDSA)) { - which = ECCURVE; - } else if (IsDlgButtonChecked(hwnd, IDC_KEYSSH2EDDSA)) { - which = EDCURVE; - } else { - /* Currently not used since Ed25519 stopped being the only - * thing in its class, but I'll keep it here in case it comes - * in useful again */ - which = NOTHING; - } - - hidemany(hwnd, bits_ids, which != BITS); - hidemany(hwnd, eccurve_ids, which != ECCURVE); - hidemany(hwnd, edcurve_ids, which != EDCURVE); - hidemany(hwnd, nothing_ids, which != NOTHING); -} -void ui_set_key_type(HWND hwnd, struct MainDlgState *state, int button) -{ - CheckRadioButton(hwnd, IDC_KEYSSH1, IDC_KEYSSH2EDDSA, button); - CheckMenuRadioItem(state->keymenu, IDC_KEYSSH1, IDC_KEYSSH2EDDSA, - button, MF_BYCOMMAND); - ui_update_key_type_ctrls(hwnd); -} -void ui_set_primepolicy(HWND hwnd, struct MainDlgState *state, int option) -{ - CheckMenuRadioItem(state->keymenu, IDC_PRIMEGEN_PROB, - IDC_PRIMEGEN_MAURER_COMPLEX, option, MF_BYCOMMAND); - switch (option) { - case IDC_PRIMEGEN_PROB: - state->primepolicy = &primegen_probabilistic; - break; - case IDC_PRIMEGEN_MAURER_SIMPLE: - state->primepolicy = &primegen_provable_maurer_simple; - break; - case IDC_PRIMEGEN_MAURER_COMPLEX: - state->primepolicy = &primegen_provable_maurer_complex; - break; - } -} -void ui_set_rsa_strong(HWND hwnd, struct MainDlgState *state, bool enable) -{ - state->rsa_strong = enable; - CheckMenuItem(state->keymenu, IDC_RSA_STRONG, - (enable ? MF_CHECKED : 0) | MF_BYCOMMAND); -} -static FingerprintType idc_to_fptype(int option) -{ - switch (option) { - case IDC_FPTYPE_SHA256: - return SSH_FPTYPE_SHA256; - case IDC_FPTYPE_MD5: - return SSH_FPTYPE_MD5; - default: - unreachable("bad control id in idc_to_fptype"); - } -} -static int fptype_to_idc(FingerprintType fptype) -{ - switch (fptype) { - case SSH_FPTYPE_SHA256: - return IDC_FPTYPE_SHA256; - case SSH_FPTYPE_MD5: - return IDC_FPTYPE_MD5; - default: - unreachable("bad fptype in fptype_to_idc"); - } -} -void ui_set_fptype(HWND hwnd, struct MainDlgState *state, int option) -{ - CheckMenuRadioItem(state->keymenu, IDC_FPTYPE_SHA256, - IDC_FPTYPE_MD5, option, MF_BYCOMMAND); - - state->fptype = idc_to_fptype(option); - - if (state->key_exists && state->ssh2) { - char *fp = ssh2_fingerprint(state->ssh2key.key, state->fptype); - SetDlgItemText(hwnd, IDC_FINGERPRINT, fp); - sfree(fp); - } -} - -void load_key_file(HWND hwnd, struct MainDlgState *state, - Filename *filename, bool was_import_cmd) -{ - char *passphrase; - bool needs_pass; - int type, realtype; - int ret; - const char *errmsg = NULL; - char *comment; - RSAKey newkey1; - 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(hwnd, 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; - passphrase = NULL; - if (realtype == SSH_KEYTYPE_SSH1) - needs_pass = rsa1_encrypted_f(filename, &comment); - else if (realtype == SSH_KEYTYPE_SSH2) - needs_pass = ppk_encrypted_f(filename, &comment); - else - needs_pass = import_encrypted(filename, realtype, &comment); - do { - burnstr(passphrase); - passphrase = NULL; - - if (needs_pass) { - int dlgret; - struct PassphraseProcStruct pps; - pps.passphrase = &passphrase; - pps.comment = comment; - dlgret = DialogBoxParam(hinst, - MAKEINTRESOURCE(210), - NULL, PassphraseProc, - (LPARAM) &pps); - if (!dlgret) { - ret = -2; - break; - } - assert(passphrase != NULL); - } else - passphrase = dupstr(""); - if (type == SSH_KEYTYPE_SSH1) { - if (realtype == type) - ret = rsa1_load_f(filename, &newkey1, passphrase, &errmsg); - else - ret = import_ssh1(filename, realtype, &newkey1, - passphrase, &errmsg); - } else { - if (realtype == type) - newkey2 = ppk_load_f(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(hwnd, 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 *fingerprint, *savecomment; - - state->ssh2 = false; - state->commentptr = &state->key.comment; - state->key = newkey1; - - /* - * Set the key fingerprint. - */ - savecomment = state->key.comment; - state->key.comment = NULL; - fingerprint = rsa_ssh1_fingerprint(&state->key); - state->key.comment = savecomment; - SetDlgItemText(hwnd, IDC_FINGERPRINT, fingerprint); - sfree(fingerprint); - - /* - * 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 = ssh2_fingerprint(state->ssh2key.key, state->fptype); - 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); - } - } - burnstr(passphrase); -} - -static void start_generating_key(HWND hwnd, struct MainDlgState *state) -{ - static const char generating_msg[] = - "Please wait while a key is generated..."; - - struct rsa_key_thread_params *params; - DWORD threadid; - - 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->key_bits = state->key_bits; - params->curve_bits = state->curve_bits; - params->keytype = state->keytype; - params->primepolicy = state->primepolicy; - params->rsa_strong = state->rsa_strong; - params->key = &state->key; - params->dsakey = &state->dsakey; - - if (!CreateThread(NULL, 0, generate_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; - } -} - -/* - * Dialog-box function for the main PuTTYgen dialog box. - */ -static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, - WPARAM wParam, LPARAM lParam) -{ - 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_PTR) 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(menu1, MF_ENABLED, IDC_KEYSSH2ECDSA, "SSH-2 &ECDSA key"); - AppendMenu(menu1, MF_ENABLED, IDC_KEYSSH2EDDSA, "SSH-2 EdD&SA key"); - AppendMenu(menu1, MF_SEPARATOR, 0, 0); - AppendMenu(menu1, MF_ENABLED, IDC_PRIMEGEN_PROB, - "Use probable primes (fast)"); - AppendMenu(menu1, MF_ENABLED, IDC_PRIMEGEN_MAURER_SIMPLE, - "Use proven primes (slower)"); - AppendMenu(menu1, MF_ENABLED, IDC_PRIMEGEN_MAURER_COMPLEX, - "Use proven primes with even distribution (slowest)"); - AppendMenu(menu1, MF_SEPARATOR, 0, 0); - AppendMenu(menu1, MF_ENABLED, IDC_RSA_STRONG, - "Use \"strong\" primes as RSA key factors"); - AppendMenu(menu1, MF_SEPARATOR, 0, 0); - AppendMenu(menu1, MF_ENABLED, IDC_PPK_PARAMS, - "Parameters for saving key files..."); - AppendMenu(menu1, MF_SEPARATOR, 0, 0); - AppendMenu(menu1, MF_ENABLED, IDC_FPTYPE_SHA256, - "Show fingerprint as SHA256"); - AppendMenu(menu1, MF_ENABLED, IDC_FPTYPE_MD5, - "Show fingerprint as MD5"); - AppendMenu(menu, MF_POPUP | MF_ENABLED, (UINT_PTR) 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_AUTO, - "Export &OpenSSH key"); - AppendMenu(menu1, MF_ENABLED, IDC_EXPORT_OPENSSH_NEW, - "Export &OpenSSH key (force new file format)"); - AppendMenu(menu1, MF_ENABLED, IDC_EXPORT_SSHCOM, - "Export &ssh.com key"); - AppendMenu(menu, MF_POPUP | MF_ENABLED, (UINT_PTR) 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_PTR) 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; - int ymax; - - /* Accelerators used: acglops1rbvde */ - - 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, 82); - SendDlgItemMessage(hwnd, IDC_FINGERPRINT, EM_SETREADONLY, 1, - 0); - staticedit(&cp, "Key &comment:", IDC_COMMENTSTATIC, - IDC_COMMENTEDIT, 82); - staticpassedit(&cp, "Key p&assphrase:", IDC_PASSPHRASE1STATIC, - IDC_PASSPHRASE1EDIT, 82); - staticpassedit(&cp, "C&onfirm passphrase:", - IDC_PASSPHRASE2STATIC, IDC_PASSPHRASE2EDIT, 82); - 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, 5, - "&RSA", IDC_KEYSSH2RSA, - "&DSA", IDC_KEYSSH2DSA, - "&ECDSA", IDC_KEYSSH2ECDSA, - "EdD&SA", IDC_KEYSSH2EDDSA, - "SSH-&1 (RSA)", IDC_KEYSSH1, - NULL); - cp2 = cp; - staticedit(&cp2, "Number of &bits in a generated key:", - IDC_BITSSTATIC, IDC_BITS, 20); - ymax = cp2.ypos; - cp2 = cp; - staticddl(&cp2, "Cur&ve to use for generating this key:", - IDC_ECCURVESTATIC, IDC_ECCURVE, 30); - SendDlgItemMessage(hwnd, IDC_ECCURVE, CB_RESETCONTENT, 0, 0); - { - int i, bits; - const struct ec_curve *curve; - const ssh_keyalg *alg; - - for (i = 0; i < n_ec_nist_curve_lengths; i++) { - bits = ec_nist_curve_lengths[i]; - ec_nist_alg_and_curve_by_bits(bits, &curve, &alg); - SendDlgItemMessage(hwnd, IDC_ECCURVE, CB_ADDSTRING, 0, - (LPARAM)curve->textname); - } - } - ymax = ymax > cp2.ypos ? ymax : cp2.ypos; - cp2 = cp; - staticddl(&cp2, "Cur&ve to use for generating this key:", - IDC_EDCURVESTATIC, IDC_EDCURVE, 30); - SendDlgItemMessage(hwnd, IDC_EDCURVE, CB_RESETCONTENT, 0, 0); - { - int i, bits; - const struct ec_curve *curve; - const ssh_keyalg *alg; - - for (i = 0; i < n_ec_ed_curve_lengths; i++) { - bits = ec_ed_curve_lengths[i]; - ec_ed_alg_and_curve_by_bits(bits, &curve, &alg); - char *desc = dupprintf("%s (%d bits)", - curve->textname, bits); - SendDlgItemMessage(hwnd, IDC_EDCURVE, CB_ADDSTRING, 0, - (LPARAM)desc); - sfree(desc); - } - } - ymax = ymax > cp2.ypos ? ymax : cp2.ypos; - cp2 = cp; - statictext(&cp2, "(nothing to configure for this key type)", - 1, IDC_NOTHINGSTATIC); - ymax = ymax > cp2.ypos ? ymax : cp2.ypos; - cp.ypos = ymax; - endbox(&cp); - } - ui_set_key_type(hwnd, state, IDC_KEYSSH2RSA); - ui_set_primepolicy(hwnd, state, IDC_PRIMEGEN_PROB); - ui_set_rsa_strong(hwnd, state, false); - ui_set_fptype(hwnd, state, fptype_to_idc(SSH_FPTYPE_DEFAULT)); - SetDlgItemInt(hwnd, IDC_BITS, DEFAULT_KEY_BITS, false); - SendDlgItemMessage(hwnd, IDC_ECCURVE, CB_SETCURSEL, - DEFAULT_ECCURVE_INDEX, 0); - SendDlgItemMessage(hwnd, IDC_EDCURVE, CB_SETCURSEL, - DEFAULT_EDCURVE_INDEX, 0); - - /* - * 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) { - Filename *fn = filename_from_str(cmdline_keyfile); - load_key_file(hwnd, state, fn, false); - filename_free(fn); - } - - 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) { - /* - * Seed the entropy pool - */ - random_reseed( - make_ptrlen(state->entropy, state->entropy_size)); - smemclr(state->entropy, state->entropy_size); - sfree(state->entropy); - state->collecting_entropy = false; - - start_generating_key(hwnd, state); - } - } - break; - case WM_COMMAND: - switch (LOWORD(wParam)) { - case IDC_KEYSSH1: - case IDC_KEYSSH2RSA: - case IDC_KEYSSH2DSA: - case IDC_KEYSSH2ECDSA: - case IDC_KEYSSH2EDDSA: { - state = (struct MainDlgState *) - GetWindowLongPtr(hwnd, GWLP_USERDATA); - ui_set_key_type(hwnd, state, LOWORD(wParam)); - break; - } - case IDC_PRIMEGEN_PROB: - case IDC_PRIMEGEN_MAURER_SIMPLE: - case IDC_PRIMEGEN_MAURER_COMPLEX: { - state = (struct MainDlgState *) - GetWindowLongPtr(hwnd, GWLP_USERDATA); - ui_set_primepolicy(hwnd, state, LOWORD(wParam)); - break; - } - case IDC_FPTYPE_SHA256: - case IDC_FPTYPE_MD5: { - state = (struct MainDlgState *) - GetWindowLongPtr(hwnd, GWLP_USERDATA); - ui_set_fptype(hwnd, state, LOWORD(wParam)); - break; - } - case IDC_RSA_STRONG: { - state = (struct MainDlgState *) - GetWindowLongPtr(hwnd, GWLP_USERDATA); - ui_set_rsa_strong(hwnd, state, !state->rsa_strong); - break; - } - case IDC_PPK_PARAMS: { - struct PPKParams pp[1]; - pp->params = save_params; - if (pp->params.argon2_passes_auto) { - pp->time_ms = pp->params.argon2_milliseconds; - pp->time_passes = 13; - } else { - pp->time_ms = 100; - pp->time_passes = pp->params.argon2_passes; - } - int dlgret = DialogBoxParam(hinst, MAKEINTRESOURCE(215), - NULL, PPKParamsProc, (LPARAM)pp); - if (dlgret) { - if (pp->params.argon2_passes_auto) { - pp->params.argon2_milliseconds = pp->time_ms; - } else { - pp->params.argon2_passes = pp->time_passes; - } - save_params = pp->params; - } - 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) { - unsigned raw_entropy_required; - unsigned char *raw_entropy_buf; - BOOL ok; - - state->key_bits = GetDlgItemInt(hwnd, IDC_BITS, &ok, false); - if (!ok) - state->key_bits = DEFAULT_KEY_BITS; - state->ssh2 = true; - - if (IsDlgButtonChecked(hwnd, IDC_KEYSSH1)) { - state->ssh2 = false; - state->keytype = RSA; - } else if (IsDlgButtonChecked(hwnd, IDC_KEYSSH2RSA)) { - state->keytype = RSA; - } else if (IsDlgButtonChecked(hwnd, IDC_KEYSSH2DSA)) { - state->keytype = DSA; - } else if (IsDlgButtonChecked(hwnd, IDC_KEYSSH2ECDSA)) { - state->keytype = ECDSA; - int curveindex = SendDlgItemMessage(hwnd, IDC_ECCURVE, - CB_GETCURSEL, 0, 0); - assert(curveindex >= 0); - assert(curveindex < n_ec_nist_curve_lengths); - state->curve_bits = ec_nist_curve_lengths[curveindex]; - } else if (IsDlgButtonChecked(hwnd, IDC_KEYSSH2EDDSA)) { - state->keytype = EDDSA; - int curveindex = SendDlgItemMessage(hwnd, IDC_EDCURVE, - CB_GETCURSEL, 0, 0); - assert(curveindex >= 0); - assert(curveindex < n_ec_ed_curve_lengths); - state->curve_bits = ec_ed_curve_lengths[curveindex]; - } else { - /* Somehow, no button was checked */ - break; - } - - if ((state->keytype == RSA || state->keytype == DSA) && - state->key_bits < 256) { - char *message = dupprintf - ("PuTTYgen will not generate a key smaller than 256" - " bits.\nKey length reset to default %d. Continue?", - DEFAULT_KEY_BITS); - int ret = MessageBox(hwnd, message, "PuTTYgen Warning", - MB_ICONWARNING | MB_OKCANCEL); - sfree(message); - if (ret != IDOK) - break; - state->key_bits = DEFAULT_KEY_BITS; - SetDlgItemInt(hwnd, IDC_BITS, DEFAULT_KEY_BITS, false); - } else if ((state->keytype == RSA || state->keytype == DSA) && - state->key_bits < DEFAULT_KEY_BITS) { - char *message = dupprintf - ("Keys shorter than %d bits are not recommended. " - "Really generate this key?", DEFAULT_KEY_BITS); - int ret = MessageBox(hwnd, message, "PuTTYgen Warning", - MB_ICONWARNING | MB_OKCANCEL); - sfree(message); - if (ret != IDOK) - break; - } - - if (state->keytype == RSA || state->keytype == DSA) - raw_entropy_required = (state->key_bits / 2) * 2; - else if (state->keytype == ECDSA || state->keytype == EDDSA) - raw_entropy_required = (state->curve_bits / 2) * 2; - else - unreachable("we must have initialised keytype by now"); - - /* Bound the entropy collection above by the amount of - * data we can actually fit into the PRNG. Any more - * than that and it's doing no more good. */ - if (raw_entropy_required > random_seed_bits()) - raw_entropy_required = random_seed_bits(); - - raw_entropy_buf = snewn(raw_entropy_required, unsigned char); - if (win_read_random(raw_entropy_buf, raw_entropy_required)) { - /* - * If we can get entropy from CryptGenRandom, use - * it. But CryptGenRandom isn't a kernel-level - * CPRNG (according to Wikipedia), and papers have - * been published cryptanalysing it. So we'll - * still do manual entropy collection; we'll just - * do it _as well_ as this. - */ - random_reseed( - make_ptrlen(raw_entropy_buf, raw_entropy_required)); - } - - /* - * Manual entropy input, by making the user wave the - * mouse over the window a lot. - * - * 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, i.e. the - * number of _words_ of mouse data we want to collect - * is just the same as the number of _bits_ of entropy - * we want. - */ - state->entropy_required = raw_entropy_required; - - ui_set_state(hwnd, state, 1); - SetDlgItemText(hwnd, IDC_GENERATING, entropy_msg); - state->key_exists = false; - state->collecting_entropy = true; - - 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); - - smemclr(raw_entropy_buf, raw_entropy_required); - sfree(raw_entropy_buf); - } - break; - case IDC_SAVE: - case IDC_EXPORT_OPENSSH_AUTO: - case IDC_EXPORT_OPENSSH_NEW: - 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, *passphrase2; - int type, realtype; - - if (state->ssh2) - realtype = SSH_KEYTYPE_SSH2; - else - realtype = SSH_KEYTYPE_SSH1; - - if (LOWORD(wParam) == IDC_EXPORT_OPENSSH_AUTO) - type = SSH_KEYTYPE_OPENSSH_AUTO; - else if (LOWORD(wParam) == IDC_EXPORT_OPENSSH_NEW) - type = SSH_KEYTYPE_OPENSSH_NEW; - 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; - } - - passphrase = GetDlgItemText_alloc(hwnd, IDC_PASSPHRASE1EDIT); - passphrase2 = GetDlgItemText_alloc(hwnd, IDC_PASSPHRASE2EDIT); - if (strcmp(passphrase, passphrase2)) { - MessageBox(hwnd, - "The two passphrases given do not match.", - "PuTTYgen Error", MB_OK | MB_ICONERROR); - burnstr(passphrase); - burnstr(passphrase2); - break; - } - burnstr(passphrase2); - if (!*passphrase) { - int ret; - ret = MessageBox(hwnd, - "Are you sure you want to save this key\n" - "without a passphrase to protect it?", - "PuTTYgen Warning", - MB_YESNO | MB_ICONWARNING); - if (ret != IDYES) { - burnstr(passphrase); - break; - } - } - if (prompt_keyfile(hwnd, "Save private key as:", - filename, true, (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) { - burnstr(passphrase); - 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 = ppk_save_f(fn, &state->ssh2key, - *passphrase ? passphrase : NULL, - &save_params); - filename_free(fn); - } else { - Filename *fn = filename_from_str(filename); - if (type != realtype) - ret = export_ssh1(fn, type, &state->key, - *passphrase ? passphrase : NULL); - else - ret = rsa1_save_f(fn, &state->key, - *passphrase ? passphrase : NULL); - filename_free(fn); - } - if (ret <= 0) { - MessageBox(hwnd, "Unable to save key file", - "PuTTYgen Error", MB_OK | MB_ICONERROR); - } - } - burnstr(passphrase); - } - break; - case IDC_SAVEPUB: - if (HIWORD(wParam) != BN_CLICKED) - break; - state = - (struct MainDlgState *) GetWindowLongPtr(hwnd, GWLP_USERDATA); - if (state->key_exists) { - char filename[FILENAME_MAX]; - if (prompt_keyfile(hwnd, "Save public key as:", - filename, true, false)) { - 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; - } - fp = fopen(filename, "w"); - if (!fp) { - MessageBox(hwnd, "Unable to open key file", - "PuTTYgen Error", MB_OK | MB_ICONERROR); - } else { - if (state->ssh2) { - strbuf *blob = strbuf_new(); - ssh_key_public_blob( - state->ssh2key.key, BinarySink_UPCAST(blob)); - ssh2_write_pubkey(fp, state->ssh2key.comment, - blob->u, blob->len, - SSH_KEYTYPE_SSH2_PUBLIC_RFC4716); - strbuf_free(blob); - } else { - ssh1_write_pubkey(fp, &state->key); - } - if (fclose(fp) < 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, false, - LOWORD(wParam) == IDC_LOAD)) { - Filename *fn = filename_from_str(filename); - load_key_file(hwnd, state, fn, LOWORD(wParam) != IDC_LOAD); - filename_free(fn); - } - } - break; - } - return 0; - case WM_DONEKEY: - 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->keytype == DSA) { - state->ssh2key.key = &state->dsakey.sshk; - } else if (state->keytype == ECDSA) { - state->ssh2key.key = &state->eckey.sshk; - } else if (state->keytype == EDDSA) { - state->ssh2key.key = &state->edkey.sshk; - } else { - state->ssh2key.key = &state->key.sshk; - } - 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->keytype == DSA) - strftime(*state->commentptr, 30, "dsa-key-%Y%m%d", &tm); - else if (state->keytype == ECDSA) - strftime(*state->commentptr, 30, "ecdsa-key-%Y%m%d", &tm); - else if (state->keytype == EDDSA) - strftime(*state->commentptr, 30, "eddsa-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 *fp, *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) - fp = ssh2_fingerprint(state->ssh2key.key, state->fptype); - else - fp = rsa_ssh1_fingerprint(&state->key); - SetDlgItemText(hwnd, IDC_FINGERPRINT, fp); - sfree(fp); - *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; - const 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: - case IDC_KEYSSH2ECDSA: - case IDC_KEYSSH2EDDSA: - 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_AUTO: - case IDC_EXPORT_OPENSSH_NEW: - 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); -} - -HINSTANCE hinst; - -int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) -{ - int argc, i; - char **argv; - int ret; - - dll_hijacking_protection(); - - init_common_controls(); - hinst = inst; - - /* - * See if we can find our Help file. - */ - init_help(); - - split_into_argv(cmdline, &argc, &argv, NULL); - - for (i = 0; i < argc; i++) { - if (!strcmp(argv[i], "-pgpfp")) { - pgp_fingerprints_msgbox(NULL); - return 1; - } else if (!strcmp(argv[i], "-restrict-acl") || - !strcmp(argv[i], "-restrict_acl") || - !strcmp(argv[i], "-restrictacl")) { - restrict_process_acl(); - } else { - /* - * Assume the first argument to be a private key file, and - * attempt to load it. - */ - cmdline_keyfile = argv[i]; - break; - } - } - - save_params = ppk_save_default_parameters; - - random_setup_special(); - ret = DialogBox(hinst, MAKEINTRESOURCE(201), NULL, MainDlgProc) != IDOK; - - cleanup_exit(ret); - return ret; /* just in case optimiser complains */ -} diff --git a/windows/winpgnt.c b/windows/winpgnt.c deleted file mode 100644 index 84291e6b..00000000 --- a/windows/winpgnt.c +++ /dev/null @@ -1,1696 +0,0 @@ -/* - * Pageant: the PuTTY Authentication Agent. - */ - -#include -#include -#include -#include -#include -#include - -#include "putty.h" -#include "ssh.h" -#include "misc.h" -#include "tree234.h" -#include "winsecur.h" -#include "wincapi.h" -#include "pageant.h" -#include "licence.h" -#include "pageant-rc.h" - -#include - -#include -#ifdef DEBUG_IPC -#define _WIN32_WINNT 0x0500 /* for ConvertSidToStringSid */ -#include -#endif - -#define WM_SYSTRAY (WM_APP + 6) -#define WM_SYSTRAY2 (WM_APP + 7) - -#define AGENT_COPYDATA_ID 0x804e50ba /* random goop */ - -#define APPNAME "Pageant" - -/* Titles and class names for invisible windows. IPCWINTITLE and - * IPCCLASSNAME are critical to backwards compatibility: WM_COPYDATA - * based Pageant clients will call FindWindow with those parameters - * and expect to find the Pageant IPC receiver. */ -#define TRAYWINTITLE "Pageant" -#define TRAYCLASSNAME "PageantSysTray" -#define IPCWINTITLE "Pageant" -#define IPCCLASSNAME "Pageant" - -static HWND traywindow; -static HWND keylist; -static HWND aboutbox; -static HMENU systray_menu, session_menu; -static bool already_running; -static FingerprintType fptype = SSH_FPTYPE_DEFAULT; - -static char *putty_path; -static bool restrict_putty_acl = false; - -/* CWD for "add key" file requester. */ -static filereq *keypath = NULL; - -/* 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_ADDKEY_ENCRYPTED 0x0040 -#define IDM_REMOVE_ALL 0x0050 -#define IDM_REENCRYPT_ALL 0x0060 -#define IDM_HELP 0x0070 -#define IDM_ABOUT 0x0080 -#define IDM_PUTTY 0x0090 -#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(const char *fmt, ...) -{ - va_list ap; - char *buf; - - va_start(ap, fmt); - buf = dupvprintf(fmt, ap); - va_end(ap); - MessageBox(traywindow, buf, "Pageant Fatal Error", - MB_SYSTEMMODAL | MB_ICONERROR | MB_OK); - sfree(buf); - exit(1); -} - -static bool has_security; - -struct PassphraseProcStruct { - bool modal; - const char *help_topic; - PageantClientDialogId *dlgid; - char *passphrase; - const char *comment; -}; - -/* - * Dialog-box function for the Licence box. - */ -static INT_PTR CALLBACK LicenceProc(HWND hwnd, UINT msg, - WPARAM wParam, LPARAM lParam) -{ - switch (msg) { - case WM_INITDIALOG: - SetDlgItemText(hwnd, IDC_LICENCE_TEXTBOX, LICENCE_TEXT("\r\n\r\n")); - 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_PTR CALLBACK AboutProc(HWND hwnd, UINT msg, - WPARAM wParam, LPARAM lParam) -{ - switch (msg) { - case WM_INITDIALOG: { - char *buildinfo_text = buildinfo("\r\n"); - char *text = dupprintf - ("Pageant\r\n\r\n%s\r\n\r\n%s\r\n\r\n%s", - ver, buildinfo_text, - "\251 " SHORT_COPYRIGHT_DETAILS ". All rights reserved."); - sfree(buildinfo_text); - SetDlgItemText(hwnd, IDC_ABOUT_TEXTBOX, text); - MakeDlgItemBorderless(hwnd, IDC_ABOUT_TEXTBOX); - sfree(text); - return 1; - } - case WM_COMMAND: - switch (LOWORD(wParam)) { - case IDOK: - case IDCANCEL: - aboutbox = NULL; - DestroyWindow(hwnd); - return 0; - case IDC_ABOUT_LICENCE: - EnableWindow(hwnd, 0); - DialogBox(hinst, MAKEINTRESOURCE(IDD_LICENCE), hwnd, LicenceProc); - EnableWindow(hwnd, 1); - SetActiveWindow(hwnd); - return 0; - case IDC_ABOUT_WEBSITE: - /* Load web browser */ - ShellExecute(hwnd, "open", - "https://www.chiark.greenend.org.uk/~sgtatham/putty/", - 0, 0, SW_SHOWDEFAULT); - return 0; - } - return 0; - case WM_CLOSE: - aboutbox = NULL; - DestroyWindow(hwnd); - return 0; - } - return 0; -} - -static HWND modal_passphrase_hwnd = NULL; -static HWND nonmodal_passphrase_hwnd = NULL; - -static void end_passphrase_dialog(HWND hwnd, INT_PTR result) -{ - struct PassphraseProcStruct *p = (struct PassphraseProcStruct *) - GetWindowLongPtr(hwnd, GWLP_USERDATA); - - if (p->modal) { - EndDialog(hwnd, result); - } else { - /* - * Destroy this passphrase dialog box before passing the - * results back to pageant.c, to avoid re-entrancy issues. - * - * If we successfully got a passphrase from the user, but it - * was _wrong_, then pageant_passphrase_request_success will - * respond by calling back - synchronously - to our - * ask_passphrase() implementation, which will expect the - * previous value of nonmodal_passphrase_hwnd to have already - * been cleaned up. - */ - SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR) NULL); - DestroyWindow(hwnd); - nonmodal_passphrase_hwnd = NULL; - - if (result) - pageant_passphrase_request_success( - p->dlgid, ptrlen_from_asciz(p->passphrase)); - else - pageant_passphrase_request_refused(p->dlgid); - - burnstr(p->passphrase); - sfree(p); - } -} - -/* - * Dialog-box function for the passphrase box. - */ -static INT_PTR CALLBACK PassphraseProc(HWND hwnd, UINT msg, - WPARAM wParam, LPARAM lParam) -{ - struct PassphraseProcStruct *p; - - if (msg == WM_INITDIALOG) { - p = (struct PassphraseProcStruct *) lParam; - SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR) p); - } else { - p = (struct PassphraseProcStruct *) - GetWindowLongPtr(hwnd, GWLP_USERDATA); - } - - switch (msg) { - case WM_INITDIALOG: { - if (p->modal) - modal_passphrase_hwnd = hwnd; - - /* - * 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); - if (!p->modal) - SetActiveWindow(hwnd); /* this won't have happened automatically */ - if (p->comment) - SetDlgItemText(hwnd, IDC_PASSPHRASE_FINGERPRINT, p->comment); - burnstr(p->passphrase); - p->passphrase = dupstr(""); - SetDlgItemText(hwnd, IDC_PASSPHRASE_EDITBOX, p->passphrase); - if (!p->help_topic || !has_help()) { - HWND item = GetDlgItem(hwnd, IDHELP); - if (item) - DestroyWindow(item); - } - return 0; - } - case WM_COMMAND: - switch (LOWORD(wParam)) { - case IDOK: - if (p->passphrase) - end_passphrase_dialog(hwnd, 1); - else - MessageBeep(0); - return 0; - case IDCANCEL: - end_passphrase_dialog(hwnd, 0); - return 0; - case IDHELP: - if (p->help_topic) - launch_help(hwnd, p->help_topic); - return 0; - case IDC_PASSPHRASE_EDITBOX: - if ((HIWORD(wParam) == EN_CHANGE) && p->passphrase) { - burnstr(p->passphrase); - p->passphrase = GetDlgItemText_alloc( - hwnd, IDC_PASSPHRASE_EDITBOX); - } - return 0; - } - return 0; - case WM_CLOSE: - end_passphrase_dialog(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); -} - -struct keylist_update_ctx { - bool enable_remove_controls; - bool enable_reencrypt_controls; -}; - -static void keylist_update_callback( - void *vctx, char **fingerprints, const char *comment, uint32_t ext_flags, - struct pageant_pubkey *key) -{ - struct keylist_update_ctx *ctx = (struct keylist_update_ctx *)vctx; - FingerprintType this_type = ssh2_pick_fingerprint(fingerprints, fptype); - const char *fingerprint = fingerprints[this_type]; - strbuf *listentry = strbuf_new(); - - /* There is at least one key, so the controls for removing keys - * should be enabled */ - ctx->enable_remove_controls = true; - - switch (key->ssh_version) { - case 1: { - strbuf_catf(listentry, "ssh1\t%s\t%s", fingerprint, comment); - - /* - * Replace the space in the fingerprint (between bit count and - * hash) with a tab, for nice alignment in the box. - */ - char *p = strchr(listentry->s, ' '); - if (p) - *p = '\t'; - break; - } - - case 2: { - /* - * For nice alignment in the list box, we would ideally want - * every entry to align to the tab stop settings, and have a - * column for algorithm name, one for bit count, one for hex - * fingerprint, and one for key comment. - * - * Unfortunately, some of the algorithm names are so long that - * they overflow into the bit-count field. Fortunately, at the - * moment, those are _precisely_ the algorithm names that - * don't need a bit count displayed anyway (because for - * NIST-style ECDSA the bit count is mentioned in the - * algorithm name, and for ssh-ed25519 there is only one - * possible value anyway). So we fudge this by simply omitting - * the bit count field in that situation. - * - * This is fragile not only in the face of further key types - * that don't follow this pattern, but also in the face of - * font metrics changes - the Windows semantics for list box - * tab stops is that \t aligns to the next one you haven't - * already exceeded, so I have to guess when the key type will - * overflow past the bit-count tab stop and leave out a tab - * character. Urgh. - */ - BinarySource src[1]; - BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(key->blob)); - ptrlen algname = get_string(src); - const ssh_keyalg *alg = find_pubkey_alg_len(algname); - - bool include_bit_count = (alg == &ssh_dsa && alg == &ssh_rsa); - - int wordnumber = 0; - for (const char *p = fingerprint; *p; p++) { - char c = *p; - if (c == ' ') { - if (wordnumber < 2) - c = '\t'; - wordnumber++; - } - if (include_bit_count || wordnumber != 1) - put_byte(listentry, c); - } - - strbuf_catf(listentry, "\t%s", comment); - break; - } - } - - if (ext_flags & LIST_EXTENDED_FLAG_HAS_NO_CLEARTEXT_KEY) { - strbuf_catf(listentry, "\t(encrypted)"); - } else if (ext_flags & LIST_EXTENDED_FLAG_HAS_ENCRYPTED_KEY_FILE) { - strbuf_catf(listentry, "\t(re-encryptable)"); - - /* At least one key can be re-encrypted */ - ctx->enable_reencrypt_controls = true; - } - - SendDlgItemMessage(keylist, IDC_KEYLIST_LISTBOX, - LB_ADDSTRING, 0, (LPARAM)listentry->s); - strbuf_free(listentry); -} - -/* - * Update the visible key list. - */ -void keylist_update(void) -{ - if (keylist) { - SendDlgItemMessage(keylist, IDC_KEYLIST_LISTBOX, - LB_RESETCONTENT, 0, 0); - - char *errmsg; - struct keylist_update_ctx ctx[1]; - ctx->enable_remove_controls = false; - ctx->enable_reencrypt_controls = false; - int status = pageant_enum_keys(keylist_update_callback, ctx, &errmsg); - assert(status == PAGEANT_ACTION_OK); - assert(!errmsg); - - SendDlgItemMessage(keylist, IDC_KEYLIST_LISTBOX, - LB_SETCURSEL, (WPARAM) - 1, 0); - - EnableWindow(GetDlgItem(keylist, IDC_KEYLIST_REMOVE), - ctx->enable_remove_controls); - EnableWindow(GetDlgItem(keylist, IDC_KEYLIST_REENCRYPT), - ctx->enable_reencrypt_controls); - } -} - -static void win_add_keyfile(Filename *filename, bool encrypted) -{ - char *err; - int ret; - - /* - * Try loading the key without a passphrase. (Or rather, without a - * _new_ passphrase; pageant_add_keyfile will take care of trying - * all the passphrases we've already stored.) - */ - ret = pageant_add_keyfile(filename, NULL, &err, encrypted); - if (ret == PAGEANT_ACTION_OK) { - goto done; - } else if (ret == PAGEANT_ACTION_FAILURE) { - goto error; - } - - /* - * OK, a passphrase is needed, and we've been given the key - * comment to use in the passphrase prompt. - */ - while (1) { - INT_PTR dlgret; - struct PassphraseProcStruct pps; - pps.modal = true; - pps.help_topic = NULL; /* this dialog has no help button */ - pps.dlgid = NULL; - pps.passphrase = NULL; - pps.comment = err; - dlgret = DialogBoxParam( - hinst, MAKEINTRESOURCE(IDD_LOAD_PASSPHRASE), - NULL, PassphraseProc, (LPARAM) &pps); - modal_passphrase_hwnd = NULL; - - if (!dlgret) { - burnstr(pps.passphrase); - goto done; /* operation cancelled */ - } - - sfree(err); - - assert(pps.passphrase != NULL); - - ret = pageant_add_keyfile(filename, pps.passphrase, &err, false); - burnstr(pps.passphrase); - - if (ret == PAGEANT_ACTION_OK) { - goto done; - } else if (ret == PAGEANT_ACTION_FAILURE) { - goto error; - } - } - - error: - message_box(traywindow, err, APPNAME, MB_OK | MB_ICONERROR, - HELPCTXID(errors_cantloadkey)); - done: - sfree(err); - return; -} - -/* - * Prompt for a key file to add, and add it. - */ -static void prompt_add_keyfile(bool encrypted) -{ - OPENFILENAME of; - char *filelist = snewn(8192, char); - - if (!keypath) keypath = filereq_new(); - memset(&of, 0, sizeof(of)); - of.hwndOwner = traywindow; - 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? */ - Filename *fn = filename_from_str(filelist); - win_add_keyfile(fn, encrypted); - filename_free(fn); - } else { - /* we are returned a bunch of strings, end to - * end. first string is the directory, the - * rest the filenames. terminated with an - * empty string. - */ - char *dir = filelist; - char *filewalker = filelist + strlen(dir) + 1; - while (*filewalker != '\0') { - char *filename = dupcat(dir, "\\", filewalker); - Filename *fn = filename_from_str(filename); - win_add_keyfile(fn, encrypted); - filename_free(fn); - sfree(filename); - filewalker += strlen(filewalker) + 1; - } - } - - keylist_update(); - pageant_forget_passphrases(); - } - sfree(filelist); -} - -/* - * Dialog-box function for the key list box. - */ -static INT_PTR CALLBACK KeyListProc(HWND hwnd, UINT msg, - WPARAM wParam, LPARAM lParam) -{ - static const struct { - const char *name; - FingerprintType value; - } fptypes[] = { - {"SHA256", SSH_FPTYPE_SHA256}, - {"MD5", SSH_FPTYPE_MD5}, - }; - - switch (msg) { - case WM_INITDIALOG: { - /* - * 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, IDC_KEYLIST_HELP); - if (item) - DestroyWindow(item); - } - - keylist = hwnd; - { - static int tabs[] = { 35, 75, 300 }; - SendDlgItemMessage(hwnd, IDC_KEYLIST_LISTBOX, LB_SETTABSTOPS, - sizeof(tabs) / sizeof(*tabs), - (LPARAM) tabs); - } - - int selection = 0; - for (size_t i = 0; i < lenof(fptypes); i++) { - SendDlgItemMessage(hwnd, IDC_KEYLIST_FPTYPE, CB_ADDSTRING, - 0, (LPARAM)fptypes[i].name); - if (fptype == fptypes[i].value) - selection = (int)i; - } - SendDlgItemMessage(hwnd, IDC_KEYLIST_FPTYPE, - CB_SETCURSEL, 0, selection); - - keylist_update(); - return 0; - } - case WM_COMMAND: - switch (LOWORD(wParam)) { - case IDOK: - case IDCANCEL: - keylist = NULL; - DestroyWindow(hwnd); - return 0; - case IDC_KEYLIST_ADDKEY: - case IDC_KEYLIST_ADDKEY_ENC: - if (HIWORD(wParam) == BN_CLICKED || - HIWORD(wParam) == BN_DOUBLECLICKED) { - if (modal_passphrase_hwnd) { - MessageBeep(MB_ICONERROR); - SetForegroundWindow(modal_passphrase_hwnd); - break; - } - prompt_add_keyfile(LOWORD(wParam) == IDC_KEYLIST_ADDKEY_ENC); - } - return 0; - case IDC_KEYLIST_REMOVE: - case IDC_KEYLIST_REENCRYPT: - 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, IDC_KEYLIST_LISTBOX, 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, IDC_KEYLIST_LISTBOX, LB_GETSELITEMS, - numSelected, (WPARAM)selectedArray); - - itemNum = numSelected - 1; - rCount = pageant_count_ssh1_keys(); - sCount = pageant_count_ssh2_keys(); - - /* 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--) { - if (selectedArray[itemNum] == rCount + i) { - switch (LOWORD(wParam)) { - case IDC_KEYLIST_REMOVE: - pageant_delete_nth_ssh2_key(i); - break; - case IDC_KEYLIST_REENCRYPT: - pageant_reencrypt_nth_ssh2_key(i); - break; - } - itemNum--; - } - } - - /* do the same for the rsa keys */ - for (i = rCount - 1; (itemNum >= 0) && (i >= 0); i--) { - if(selectedArray[itemNum] == i) { - switch (LOWORD(wParam)) { - case IDC_KEYLIST_REMOVE: - pageant_delete_nth_ssh1_key(i); - break; - case IDC_KEYLIST_REENCRYPT: - /* SSH-1 keys can't be re-encrypted */ - break; - } - itemNum--; - } - } - - sfree(selectedArray); - keylist_update(); - } - return 0; - case IDC_KEYLIST_HELP: - if (HIWORD(wParam) == BN_CLICKED || - HIWORD(wParam) == BN_DOUBLECLICKED) { - launch_help(hwnd, WINHELP_CTX_pageant_general); - } - return 0; - case IDC_KEYLIST_FPTYPE: - if (HIWORD(wParam) == CBN_SELCHANGE) { - int selection = SendDlgItemMessage( - hwnd, IDC_KEYLIST_FPTYPE, CB_GETCURSEL, 0, 0); - if (selection >= 0 && (size_t)selection < lenof(fptypes)) { - fptype = fptypes[selection].value; - keylist_update(); - } - } - return 0; - } - return 0; - case WM_HELP: { - int id = ((LPHELPINFO)lParam)->iCtrlId; - const char *topic = NULL; - switch (id) { - case IDC_KEYLIST_LISTBOX: - case IDC_KEYLIST_FPTYPE: - case IDC_KEYLIST_FPTYPE_STATIC: - topic = WINHELP_CTX_pageant_keylist; break; - case IDC_KEYLIST_ADDKEY: topic = WINHELP_CTX_pageant_addkey; break; - case IDC_KEYLIST_REMOVE: topic = WINHELP_CTX_pageant_remkey; break; - case IDC_KEYLIST_ADDKEY_ENC: - case IDC_KEYLIST_REENCRYPT: - topic = WINHELP_CTX_pageant_deferred; 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; - strbuf *sb; - - 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; - - sb = strbuf_new(); - while(ERROR_SUCCESS == RegEnumKey(hkey, index_key, buf, MAX_PATH)) { - if(strcmp(buf, PUTTY_DEFAULT) != 0) { - strbuf_clear(sb); - unescape_registry_key(buf, sb); - - 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 = sb->s; - InsertMenuItem(session_menu, index_menu, true, &mii); - index_menu++; - } - index_key++; - } - strbuf_free(sb); - - 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); - } -} - -/* - * Versions of Pageant prior to 0.61 expected this SID on incoming - * communications. For backwards compatibility, and more particularly - * for compatibility with derived works of PuTTY still using the old - * Pageant client code, we accept it as an alternative to the one - * returned from get_user_sid() in winpgntc.c. - */ -PSID get_default_sid(void) -{ - HANDLE proc = NULL; - DWORD sidlen; - PSECURITY_DESCRIPTOR psd = NULL; - PSID sid = NULL, copy = NULL, ret = NULL; - - if ((proc = OpenProcess(MAXIMUM_ALLOWED, false, - GetCurrentProcessId())) == NULL) - goto cleanup; - - if (p_GetSecurityInfo(proc, SE_KERNEL_OBJECT, OWNER_SECURITY_INFORMATION, - &sid, NULL, NULL, NULL, &psd) != ERROR_SUCCESS) - goto cleanup; - - sidlen = GetLengthSid(sid); - - copy = (PSID)smalloc(sidlen); - - if (!CopySid(sidlen, copy, sid)) - goto cleanup; - - /* Success. Move sid into the return value slot, and null it out - * to stop the cleanup code freeing it. */ - ret = copy; - copy = NULL; - - cleanup: - if (proc != NULL) - CloseHandle(proc); - if (psd != NULL) - LocalFree(psd); - if (copy != NULL) - sfree(copy); - - return ret; -} - -struct WmCopydataTransaction { - char *length, *body; - size_t bodysize, bodylen; - HANDLE ev_msg_ready, ev_reply_ready; -} wmct; - -static struct PageantClient wmcpc; - -static void wm_copydata_got_msg(void *vctx) -{ - pageant_handle_msg(&wmcpc, NULL, make_ptrlen(wmct.body, wmct.bodylen)); -} - -static void wm_copydata_got_response( - PageantClient *pc, PageantClientRequestId *reqid, ptrlen response) -{ - if (response.len > wmct.bodysize) { - /* Output would overflow message buffer. Replace with a - * failure message. */ - static const unsigned char failure[] = { SSH_AGENT_FAILURE }; - response = make_ptrlen(failure, lenof(failure)); - assert(response.len <= wmct.bodysize); - } - - PUT_32BIT_MSB_FIRST(wmct.length, response.len); - memcpy(wmct.body, response.ptr, response.len); - - SetEvent(wmct.ev_reply_ready); -} - -static bool ask_passphrase_common(PageantClientDialogId *dlgid, - const char *comment) -{ - /* Pageant core should be serialising requests, so we never expect - * a passphrase prompt to exist already at this point */ - assert(!nonmodal_passphrase_hwnd); - - struct PassphraseProcStruct *pps = snew(struct PassphraseProcStruct); - pps->modal = false; - pps->help_topic = WINHELP_CTX_pageant_deferred; - pps->dlgid = dlgid; - pps->passphrase = NULL; - pps->comment = comment; - - nonmodal_passphrase_hwnd = CreateDialogParam( - hinst, MAKEINTRESOURCE(IDD_ONDEMAND_PASSPHRASE), - NULL, PassphraseProc, (LPARAM)pps); - - /* - * Try to put this passphrase prompt into the foreground. - * - * This will probably not succeed in giving it the actual keyboard - * focus, because Windows is quite opposed to applications being - * able to suddenly steal the focus on their own initiative. - * - * That makes sense in a lot of situations, as a defensive - * measure. If you were about to type a password or other secret - * data into the window you already had focused, and some - * malicious app stole the focus, it might manage to trick you - * into typing your secrets into _it_ instead. - * - * In this case it's possible to regard the same defensive measure - * as counterproductive, because the effect if we _do_ steal focus - * is that you type something into our passphrase prompt that - * isn't the passphrase, and we fail to decrypt the key, and no - * harm is done. Whereas the effect of the user wrongly _assuming_ - * the new passphrase prompt has the focus is much worse: now you - * type your highly secret passphrase into some other window you - * didn't mean to trust with that information - such as the - * agent-forwarded PuTTY in which you just ran an ssh command, - * which the _whole point_ was to avoid telling your passphrase to! - * - * On the other hand, I'm sure _every_ application author can come - * up with an argument for why they think _they_ should be allowed - * to steal the focus. Probably most of them include the claim - * that no harm is done if their application receives data - * intended for something else, and of course that's not always - * true! - * - * In any case, I don't know of anything I can do about it, or - * anything I _should_ do about it if I could. If anyone thinks - * they can improve on all this, patches are welcome. - */ - SetForegroundWindow(nonmodal_passphrase_hwnd); - - return true; -} - -static bool wm_copydata_ask_passphrase( - PageantClient *pc, PageantClientDialogId *dlgid, const char *comment) -{ - return ask_passphrase_common(dlgid, comment); -} - -static const PageantClientVtable wmcpc_vtable = { - .log = NULL, /* no logging in this client */ - .got_response = wm_copydata_got_response, - .ask_passphrase = wm_copydata_ask_passphrase, -}; - -static char *answer_filemapping_message(const char *mapname) -{ - HANDLE maphandle = INVALID_HANDLE_VALUE; - void *mapaddr = NULL; - char *err = NULL; - size_t mapsize; - unsigned msglen; - - PSID mapsid = NULL; - PSID expectedsid = NULL; - PSID expectedsid_bc = NULL; - PSECURITY_DESCRIPTOR psd = NULL; - - wmct.length = wmct.body = NULL; - -#ifdef DEBUG_IPC - debug("mapname = \"%s\"\n", mapname); -#endif - - maphandle = OpenFileMapping(FILE_MAP_ALL_ACCESS, false, mapname); - if (maphandle == NULL || maphandle == INVALID_HANDLE_VALUE) { - err = dupprintf("OpenFileMapping(\"%s\"): %s", - mapname, win_strerror(GetLastError())); - goto cleanup; - } - -#ifdef DEBUG_IPC - debug("maphandle = %p\n", maphandle); -#endif - - if (has_security) { - DWORD retd; - - if ((expectedsid = get_user_sid()) == NULL) { - err = dupstr("unable to get user SID"); - goto cleanup; - } - - if ((expectedsid_bc = get_default_sid()) == NULL) { - err = dupstr("unable to get default SID"); - goto cleanup; - } - - if ((retd = p_GetSecurityInfo( - maphandle, SE_KERNEL_OBJECT, OWNER_SECURITY_INFORMATION, - &mapsid, NULL, NULL, NULL, &psd) != ERROR_SUCCESS)) { - err = dupprintf("unable to get owner of file mapping: " - "GetSecurityInfo returned: %s", - win_strerror(retd)); - goto cleanup; - } - -#ifdef DEBUG_IPC - { - LPTSTR ours, ours2, theirs; - ConvertSidToStringSid(mapsid, &theirs); - ConvertSidToStringSid(expectedsid, &ours); - ConvertSidToStringSid(expectedsid_bc, &ours2); - debug("got sids:\n oursnew=%s\n oursold=%s\n" - " theirs=%s\n", ours, ours2, theirs); - LocalFree(ours); - LocalFree(ours2); - LocalFree(theirs); - } -#endif - - if (!EqualSid(mapsid, expectedsid) && - !EqualSid(mapsid, expectedsid_bc)) { - err = dupstr("wrong owning SID of file mapping"); - goto cleanup; - } - } else - { -#ifdef DEBUG_IPC - debug("security APIs not present\n"); -#endif - } - - mapaddr = MapViewOfFile(maphandle, FILE_MAP_WRITE, 0, 0, 0); - if (!mapaddr) { - err = dupprintf("unable to obtain view of file mapping: %s", - win_strerror(GetLastError())); - goto cleanup; - } - -#ifdef DEBUG_IPC - debug("mapped address = %p\n", mapaddr); -#endif - - { - MEMORY_BASIC_INFORMATION mbi; - size_t mbiSize = VirtualQuery(mapaddr, &mbi, sizeof(mbi)); - if (mbiSize == 0) { - err = dupprintf("unable to query view of file mapping: %s", - win_strerror(GetLastError())); - goto cleanup; - } - if (mbiSize < (offsetof(MEMORY_BASIC_INFORMATION, RegionSize) + - sizeof(mbi.RegionSize))) { - err = dupstr("VirtualQuery returned too little data to get " - "region size"); - goto cleanup; - } - - mapsize = mbi.RegionSize; - } -#ifdef DEBUG_IPC - debug("region size = %"SIZEu"\n", mapsize); -#endif - if (mapsize < 5) { - err = dupstr("mapping smaller than smallest possible request"); - goto cleanup; - } - - wmct.length = (char *)mapaddr; - msglen = GET_32BIT_MSB_FIRST(wmct.length); - -#ifdef DEBUG_IPC - debug("msg length=%08x, msg type=%02x\n", - msglen, (unsigned)((unsigned char *) mapaddr)[4]); -#endif - - wmct.body = wmct.length + 4; - wmct.bodysize = mapsize - 4; - - if (msglen > wmct.bodysize) { - /* Incoming length field is too large. Emit a failure response - * without even trying to handle the request. - * - * (We know this must fit, because we checked mapsize >= 5 - * above.) */ - PUT_32BIT_MSB_FIRST(wmct.length, 1); - *wmct.body = SSH_AGENT_FAILURE; - } else { - wmct.bodylen = msglen; - SetEvent(wmct.ev_msg_ready); - WaitForSingleObject(wmct.ev_reply_ready, INFINITE); - } - - cleanup: - /* expectedsid has the lifetime of the program, so we don't free it */ - sfree(expectedsid_bc); - if (psd) - LocalFree(psd); - if (mapaddr) - UnmapViewOfFile(mapaddr); - if (maphandle != NULL && maphandle != INVALID_HANDLE_VALUE) - CloseHandle(maphandle); - return err; -} - -static void create_keylist_window(void) -{ - if (keylist) - return; - - keylist = CreateDialog(hinst, MAKEINTRESOURCE(IDD_KEYLIST), - NULL, KeyListProc); - ShowWindow(keylist, SW_SHOWNORMAL); -} - -static LRESULT CALLBACK TrayWndProc(HWND hwnd, UINT message, - WPARAM wParam, LPARAM lParam) -{ - static bool 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 = true; - update_sessions(); - SetForegroundWindow(hwnd); - TrackPopupMenu(systray_menu, - TPM_RIGHTALIGN | TPM_BOTTOMALIGN | - TPM_RIGHTBUTTON, - wParam, lParam, 0, hwnd, NULL); - menuinprogress = false; - } - break; - case WM_COMMAND: - case WM_SYSCOMMAND: { - unsigned command = wParam & ~0xF; /* low 4 bits reserved to Windows */ - switch (command) { - case IDM_PUTTY: { - TCHAR cmdline[10]; - cmdline[0] = '\0'; - if (restrict_putty_acl) - strcat(cmdline, "&R"); - - if((INT_PTR)ShellExecute(hwnd, NULL, putty_path, cmdline, - _T(""), SW_SHOW) <= 32) { - MessageBox(NULL, "Unable to execute PuTTY!", - "Error", MB_OK | MB_ICONERROR); - } - break; - } - case IDM_CLOSE: - if (modal_passphrase_hwnd) - SendMessage(modal_passphrase_hwnd, WM_CLOSE, 0, 0); - SendMessage(hwnd, WM_CLOSE, 0, 0); - break; - case IDM_VIEWKEYS: - create_keylist_window(); - /* - * 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: - case IDM_ADDKEY_ENCRYPTED: - if (modal_passphrase_hwnd) { - MessageBeep(MB_ICONERROR); - SetForegroundWindow(modal_passphrase_hwnd); - break; - } - prompt_add_keyfile(command == IDM_ADDKEY_ENCRYPTED); - break; - case IDM_REMOVE_ALL: - pageant_delete_all(); - keylist_update(); - break; - case IDM_REENCRYPT_ALL: - pageant_reencrypt_all(); - keylist_update(); - break; - case IDM_ABOUT: - if (!aboutbox) { - aboutbox = CreateDialog(hinst, MAKEINTRESOURCE(IDD_ABOUT), - 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); - param[0] = '\0'; - if (restrict_putty_acl) - strcat(param, "&R"); - strcat(param, "@"); - strcat(param, mii.dwTypeData); - if((INT_PTR)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; - } - - return DefWindowProc(hwnd, message, wParam, lParam); -} - -static LRESULT CALLBACK wm_copydata_WndProc(HWND hwnd, UINT message, - WPARAM wParam, LPARAM lParam) -{ - switch (message) { - case WM_COPYDATA: { - COPYDATASTRUCT *cds; - char *mapname, *err; - - 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! */ - err = answer_filemapping_message(mapname); - if (err) { -#ifdef DEBUG_IPC - debug("IPC failed: %s\n", err); -#endif - sfree(err); - return 0; - } - return 1; - } - } - - return DefWindowProc(hwnd, message, wParam, lParam); -} - -static DWORD WINAPI wm_copydata_threadfunc(void *param) -{ - HINSTANCE inst = *(HINSTANCE *)param; - - HWND ipchwnd = CreateWindow(IPCCLASSNAME, IPCWINTITLE, - WS_OVERLAPPEDWINDOW | WS_VSCROLL, - CW_USEDEFAULT, CW_USEDEFAULT, - 100, 100, NULL, NULL, inst, NULL); - ShowWindow(ipchwnd, SW_HIDE); - - MSG msg; - while (GetMessage(&msg, NULL, 0, 0) == 1) { - TranslateMessage(&msg); - DispatchMessage(&msg); - } - - return 0; -} - -/* - * Fork and Exec the command in cmdline. [DBW] - */ -void spawn_cmd(const char *cmdline, const char *args, int show) -{ - if (ShellExecute(NULL, _T("open"), cmdline, - args, NULL, show) <= (HINSTANCE) 32) { - char *msg; - msg = dupprintf("Failed to run \"%s\": %s", cmdline, - win_strerror(GetLastError())); - MessageBox(NULL, msg, APPNAME, MB_OK | MB_ICONEXCLAMATION); - sfree(msg); - } -} - -void noise_ultralight(NoiseSourceId id, unsigned long data) -{ - /* Pageant doesn't use random numbers, so we ignore this */ -} - -void cleanup_exit(int code) -{ - shutdown_help(); - exit(code); -} - -static bool winpgnt_listener_ask_passphrase( - PageantListenerClient *plc, PageantClientDialogId *dlgid, - const char *comment) -{ - return ask_passphrase_common(dlgid, comment); -} - -struct winpgnt_client { - PageantListenerClient plc; -}; -static const PageantListenerClientVtable winpgnt_vtable = { - .log = NULL, /* no logging */ - .ask_passphrase = winpgnt_listener_ask_passphrase, -}; - -static struct winpgnt_client wpc[1]; - -HINSTANCE hinst; - -int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) -{ - MSG msg; - const char *command = NULL; - bool added_keys = false; - bool show_keylist_on_startup = false; - int argc, i; - char **argv, **argstart; - - dll_hijacking_protection(); - - hinst = inst; - - /* - * Determine whether we're an NT system (should have security - * APIs) or a non-NT system (don't do security). - */ - init_winver(); - has_security = (osPlatformId == VER_PLATFORM_WIN32_NT); - - if (has_security) { - /* - * Attempt to get the security API we need. - */ - if (!got_advapi()) { - MessageBox(NULL, - "Unable to access security APIs. Pageant will\n" - "not run, in case it causes a security breach.", - "Pageant Fatal Error", MB_ICONERROR | MB_OK); - return 1; - } - } - - /* - * 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 the cross-platform Pageant code. - */ - if (!already_running) { - pageant_init(); - } - - /* - * Process the command line and add keys as listed on it. - */ - split_into_argv(cmdline, &argc, &argv, &argstart); - bool doing_opts = true; - bool add_keys_encrypted = false; - for (i = 0; i < argc; i++) { - char *p = argv[i]; - if (*p == '-' && doing_opts) { - if (!strcmp(p, "-pgpfp")) { - pgp_fingerprints_msgbox(NULL); - return 1; - } else if (!strcmp(p, "-restrict-acl") || - !strcmp(p, "-restrict_acl") || - !strcmp(p, "-restrictacl")) { - restrict_process_acl(); - } else if (!strcmp(p, "-restrict-putty-acl") || - !strcmp(p, "-restrict_putty_acl")) { - restrict_putty_acl = true; - } else if (!strcmp(p, "--no-decrypt") || - !strcmp(p, "-no-decrypt") || - !strcmp(p, "--no_decrypt") || - !strcmp(p, "-no_decrypt") || - !strcmp(p, "--nodecrypt") || - !strcmp(p, "-nodecrypt") || - !strcmp(p, "--encrypted") || - !strcmp(p, "-encrypted")) { - add_keys_encrypted = true; - } else if (!strcmp(p, "-keylist") || !strcmp(p, "--keylist")) { - show_keylist_on_startup = true; - } else if (!strcmp(p, "-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 if (!strcmp(p, "--")) { - doing_opts = false; - } else { - char *msg = dupprintf("unrecognised command-line option\n" - "'%s'", p); - MessageBox(NULL, msg, "Pageant command-line syntax error", - MB_ICONERROR | MB_OK); - exit(1); - } - } else { - Filename *fn = filename_from_str(p); - win_add_keyfile(fn, add_keys_encrypted); - filename_free(fn); - added_keys = true; - } - } - - /* - * Forget any passphrase that we retained while going over - * command line keyfiles. - */ - pageant_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); - } - return 0; - } - - /* - * Set up a named-pipe listener. - */ - { - Plug *pl_plug; - wpc->plc.vt = &winpgnt_vtable; - wpc->plc.suppress_logging = true; - struct pageant_listen_state *pl = - pageant_listener_new(&pl_plug, &wpc->plc); - char *pipename = agent_named_pipe_name(); - Socket *sock = new_named_pipe_listener(pipename, pl_plug); - if (sk_socket_error(sock)) { - char *err = dupprintf("Unable to open named pipe at %s " - "for SSH agent:\n%s", pipename, - sk_socket_error(sock)); - MessageBox(NULL, err, "Pageant Error", MB_ICONERROR | MB_OK); - return 1; - } - pageant_listener_got_socket(pl, sock); - sfree(pipename); - } - - /* - * Set up window classes for two hidden windows: one that receives - * all the messages to do with our presence in the system tray, - * and one that receives the WM_COPYDATA message used by the - * old-style Pageant IPC system. - */ - - if (!prev) { - WNDCLASS wndclass; - - memset(&wndclass, 0, sizeof(wndclass)); - wndclass.lpfnWndProc = TrayWndProc; - wndclass.hInstance = inst; - wndclass.hIcon = LoadIcon(inst, MAKEINTRESOURCE(IDI_MAINICON)); - wndclass.lpszClassName = TRAYCLASSNAME; - - RegisterClass(&wndclass); - - memset(&wndclass, 0, sizeof(wndclass)); - wndclass.lpfnWndProc = wm_copydata_WndProc; - wndclass.hInstance = inst; - wndclass.lpszClassName = IPCCLASSNAME; - - RegisterClass(&wndclass); - } - - keylist = NULL; - - traywindow = CreateWindow(TRAYCLASSNAME, TRAYWINTITLE, - WS_OVERLAPPEDWINDOW | WS_VSCROLL, - CW_USEDEFAULT, CW_USEDEFAULT, - 100, 100, NULL, NULL, inst, NULL); - winselgui_set_hwnd(traywindow); - - /* Set up a system tray icon */ - AddTrayIcon(traywindow); - - /* 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_PTR) 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_ENABLED, IDM_ADDKEY_ENCRYPTED, - "Add key (encrypted)"); - AppendMenu(systray_menu, MF_SEPARATOR, 0, 0); - AppendMenu(systray_menu, MF_ENABLED, IDM_REMOVE_ALL, - "Remove All Keys"); - AppendMenu(systray_menu, MF_ENABLED, IDM_REENCRYPT_ALL, - "Re-encrypt All Keys"); - 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(traywindow, SW_HIDE); - - wmcpc.vt = &wmcpc_vtable; - wmcpc.suppress_logging = true; - pageant_register_client(&wmcpc); - DWORD wm_copydata_threadid; - wmct.ev_msg_ready = CreateEvent(NULL, false, false, NULL); - wmct.ev_reply_ready = CreateEvent(NULL, false, false, NULL); - CreateThread(NULL, 0, wm_copydata_threadfunc, - &inst, 0, &wm_copydata_threadid); - handle_add_foreign_event(wmct.ev_msg_ready, wm_copydata_got_msg, NULL); - - if (show_keylist_on_startup) - create_keylist_window(); - - /* - * Main message loop. - */ - while (true) { - 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); - } else - sfree(handles); - - while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) { - if (msg.message == WM_QUIT) - goto finished; /* two-level break */ - - if (IsWindow(keylist) && IsDialogMessage(keylist, &msg)) - continue; - if (IsWindow(aboutbox) && IsDialogMessage(aboutbox, &msg)) - continue; - if (IsWindow(nonmodal_passphrase_hwnd) && - IsDialogMessage(nonmodal_passphrase_hwnd, &msg)) - continue; - - TranslateMessage(&msg); - DispatchMessage(&msg); - } - - run_toplevel_callbacks(); - } - finished: - - /* Clean up the system tray icon */ - { - NOTIFYICONDATA tnid; - - tnid.cbSize = sizeof(NOTIFYICONDATA); - tnid.hWnd = traywindow; - tnid.uID = 1; - - Shell_NotifyIcon(NIM_DELETE, &tnid); - - DestroyMenu(systray_menu); - } - - if (keypath) filereq_free(keypath); - - cleanup_exit(msg.wParam); - return msg.wParam; /* just in case optimiser complains */ -} diff --git a/windows/winpgntc.c b/windows/winpgntc.c deleted file mode 100644 index 0fc98cc5..00000000 --- a/windows/winpgntc.c +++ /dev/null @@ -1,303 +0,0 @@ -/* - * Pageant client code. - */ - -#include -#include -#include - -#include "putty.h" -#include "pageant.h" /* for AGENT_MAX_MSGLEN */ - -#include "winsecur.h" -#include "wincapi.h" - -#define AGENT_COPYDATA_ID 0x804e50ba /* random goop */ - -static bool wm_copydata_agent_exists(void) -{ - HWND hwnd; - hwnd = FindWindow("Pageant", "Pageant"); - if (!hwnd) - return false; - else - return true; -} - -static void wm_copydata_agent_query(strbuf *query, void **out, int *outlen) -{ - 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; - - if (query->len > AGENT_MAX_MSGLEN) - return; /* query too large */ - - hwnd = FindWindow("Pageant", "Pageant"); - if (!hwnd) - return; /* *out == NULL, so failure */ - mapname = dupprintf("PageantRequest%08x", (unsigned)GetCurrentThreadId()); - - psa = NULL; - if (got_advapi()) { - /* - * Make the file mapping we create for communication with - * Pageant owned by the user SID rather than the default. This - * should make communication between processes with slightly - * different contexts more reliable: in particular, command - * 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(); - - 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; - } - } - } - } - - filemap = CreateFileMapping(INVALID_HANDLE_VALUE, psa, PAGE_READWRITE, - 0, AGENT_MAX_MSGLEN, mapname); - if (filemap == NULL || filemap == INVALID_HANDLE_VALUE) { - sfree(mapname); - return; /* *out == NULL, so failure */ - } - p = MapViewOfFile(filemap, FILE_MAP_WRITE, 0, 0, 0); - strbuf_finalise_agent_query(query); - memcpy(p, query->s, query->len); - cds.dwData = AGENT_COPYDATA_ID; - cds.cbData = 1 + strlen(mapname); - cds.lpData = mapname; - - /* - * 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) { - uint32_t length_field = GET_32BIT_MSB_FIRST(p); - if (length_field > 0 && length_field <= AGENT_MAX_MSGLEN - 4) { - retlen = length_field + 4; - ret = snewn(retlen, unsigned char); - memcpy(ret, p, retlen); - *out = ret; - *outlen = retlen; - } else { - /* - * If we get here, we received an out-of-range length - * field, either without space for a message type code or - * overflowing the FileMapping. - * - * Treat this as if Pageant didn't answer at all - which - * actually means we do nothing, and just don't fill in - * out and outlen. - */ - } - } - UnmapViewOfFile(p); - CloseHandle(filemap); - sfree(mapname); - if (psd) - LocalFree(psd); -} - -char *agent_named_pipe_name(void) -{ - char *username, *suffix, *pipename; - username = get_username(); - suffix = capi_obfuscate_string("Pageant"); - pipename = dupprintf("\\\\.\\pipe\\pageant.%s.%s", username, suffix); - sfree(username); - sfree(suffix); - return pipename; -} - -Socket *agent_connect(Plug *plug) -{ - char *pipename = agent_named_pipe_name(); - Socket *s = new_named_pipe_client(pipename, plug); - sfree(pipename); - return s; -} - -static bool named_pipe_agent_exists(void) -{ - char *pipename = agent_named_pipe_name(); - WIN32_FIND_DATA data; - HANDLE ffh = FindFirstFile(pipename, &data); - sfree(pipename); - if (ffh == INVALID_HANDLE_VALUE) - return false; - FindClose(ffh); - return true; -} - -bool agent_exists(void) -{ - return named_pipe_agent_exists() || wm_copydata_agent_exists(); -} - -struct agent_pending_query { - struct handle *handle; - strbuf *response; - void (*callback)(void *, void *, int); - void *callback_ctx; -}; - -static int named_pipe_agent_accumulate_response( - strbuf *sb, const void *data, size_t len) -{ - put_data(sb, data, len); - if (sb->len >= 4) { - uint32_t length_field = GET_32BIT_MSB_FIRST(sb->u); - if (length_field > AGENT_MAX_MSGLEN) - return -1; /* badly formatted message */ - - int overall_length = length_field + 4; - if (sb->len >= overall_length) - return overall_length; - } - - return 0; /* not done yet */ -} - -static size_t named_pipe_agent_gotdata( - struct handle *h, const void *data, size_t len, int err) -{ - agent_pending_query *pq = handle_get_privdata(h); - - if (err || len == 0) { - pq->callback(pq->callback_ctx, NULL, 0); - agent_cancel_query(pq); - return 0; - } - - int status = named_pipe_agent_accumulate_response(pq->response, data, len); - if (status == -1) { - pq->callback(pq->callback_ctx, NULL, 0); - agent_cancel_query(pq); - } else if (status > 0) { - void *response_buf = strbuf_to_str(pq->response); - pq->response = NULL; - pq->callback(pq->callback_ctx, response_buf, status); - agent_cancel_query(pq); - } - return 0; -} - -static agent_pending_query *named_pipe_agent_query( - strbuf *query, void **out, int *outlen, - void (*callback)(void *, void *, int), void *callback_ctx) -{ - agent_pending_query *pq = NULL; - char *err = NULL, *pipename = NULL; - strbuf *sb = NULL; - HANDLE pipehandle; - - pipename = agent_named_pipe_name(); - pipehandle = connect_to_named_pipe(pipename, &err); - if (pipehandle == INVALID_HANDLE_VALUE) - goto failure; - - strbuf_finalise_agent_query(query); - - for (DWORD done = 0; done < query->len ;) { - DWORD nwritten; - bool ret = WriteFile(pipehandle, query->s + done, query->len - done, - &nwritten, NULL); - if (!ret) - goto failure; - - done += nwritten; - } - - if (!callback) { - int status; - - sb = strbuf_new_nm(); - do { - char buf[1024]; - DWORD nread; - bool ret = ReadFile(pipehandle, buf, sizeof(buf), &nread, NULL); - if (!ret) - goto failure; - status = named_pipe_agent_accumulate_response(sb, buf, nread); - } while (status == 0); - - if (status == -1) - goto failure; - - *out = strbuf_to_str(sb); - *outlen = status; - sb = NULL; - pq = NULL; - goto out; - } - - pq = snew(agent_pending_query); - pq->handle = handle_input_new(pipehandle, named_pipe_agent_gotdata, pq, 0); - pipehandle = NULL; /* prevent it being closed below */ - pq->response = strbuf_new_nm(); - pq->callback = callback; - pq->callback_ctx = callback_ctx; - goto out; - - failure: - *out = NULL; - *outlen = 0; - pq = NULL; - - out: - sfree(err); - sfree(pipename); - if (pipehandle != INVALID_HANDLE_VALUE) - CloseHandle(pipehandle); - if (sb) - strbuf_free(sb); - return pq; -} - -void agent_cancel_query(agent_pending_query *pq) -{ - handle_free(pq->handle); - if (pq->response) - strbuf_free(pq->response); - sfree(pq); -} - -agent_pending_query *agent_query( - strbuf *query, void **out, int *outlen, - void (*callback)(void *, void *, int), void *callback_ctx) -{ - agent_pending_query *pq = named_pipe_agent_query( - query, out, outlen, callback, callback_ctx); - if (pq || *out) - return pq; - - wm_copydata_agent_query(query, out, outlen); - return NULL; -} diff --git a/windows/winplink.c b/windows/winplink.c deleted file mode 100644 index 9bda0712..00000000 --- a/windows/winplink.c +++ /dev/null @@ -1,533 +0,0 @@ -/* - * PLink - a Windows command-line (stdin/stdout) variant of PuTTY. - */ - -#include -#include -#include -#include - -#include "putty.h" -#include "storage.h" -#include "tree234.h" -#include "winsecur.h" - -void cmdline_error(const char *fmt, ...) -{ - va_list ap; - va_start(ap, fmt); - console_print_error_msg_fmt_v("plink", fmt, ap); - va_end(ap); - exit(1); -} - -static HANDLE inhandle, outhandle, errhandle; -static struct handle *stdin_handle, *stdout_handle, *stderr_handle; -static handle_sink stdout_hs, stderr_hs; -static StripCtrlChars *stdout_scc, *stderr_scc; -static BinarySink *stdout_bs, *stderr_bs; -static DWORD orig_console_mode; - -static Backend *backend; -static LogContext *logctx; -static Conf *conf; - -static void plink_echoedit_update(Seat *seat, bool echo, bool 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); -} - -static size_t plink_output( - Seat *seat, bool is_stderr, const void *data, size_t len) -{ - BinarySink *bs = is_stderr ? stderr_bs : stdout_bs; - put_data(bs, data, len); - - return handle_backlog(stdout_handle) + handle_backlog(stderr_handle); -} - -static bool plink_eof(Seat *seat) -{ - handle_write_eof(stdout_handle); - return false; /* do not respond to incoming EOF with outgoing */ -} - -static int plink_get_userpass_input(Seat *seat, prompts_t *p, bufchain *input) -{ - int ret; - ret = cmdline_get_passwd_input(p); - if (ret == -1) - ret = console_get_userpass_input(p); - return ret; -} - -static bool plink_seat_interactive(Seat *seat) -{ - return (!*conf_get_str(conf, CONF_remote_cmd) && - !*conf_get_str(conf, CONF_remote_cmd2) && - !*conf_get_str(conf, CONF_ssh_nc_host)); -} - -static const SeatVtable plink_seat_vt = { - .output = plink_output, - .eof = plink_eof, - .get_userpass_input = plink_get_userpass_input, - .notify_remote_exit = nullseat_notify_remote_exit, - .connection_fatal = console_connection_fatal, - .update_specials_menu = nullseat_update_specials_menu, - .get_ttymode = nullseat_get_ttymode, - .set_busy_status = nullseat_set_busy_status, - .verify_ssh_host_key = console_verify_ssh_host_key, - .confirm_weak_crypto_primitive = console_confirm_weak_crypto_primitive, - .confirm_weak_cached_hostkey = console_confirm_weak_cached_hostkey, - .is_utf8 = nullseat_is_never_utf8, - .echoedit_update = plink_echoedit_update, - .get_x_display = nullseat_get_x_display, - .get_windowid = nullseat_get_windowid, - .get_window_pixel_size = nullseat_get_window_pixel_size, - .stripctrl_new = console_stripctrl_new, - .set_trust_status = console_set_trust_status, - .verbose = cmdline_seat_verbose, - .interactive = plink_seat_interactive, - .get_cursor_position = nullseat_get_cursor_position, -}; -static Seat plink_seat[1] = {{ &plink_seat_vt }}; - -static DWORD main_thread_id; - -/* - * Short description of parameters. - */ -static void usage(void) -{ - printf("Plink: 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(" -ssh-connection\n"); - printf(" force use of the bare ssh-connection 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(" -proxycmd command\n"); - printf(" use 'command' as local proxy\n"); - printf(" -sercfg configuration-string (e.g. 19200,8,n,1,X)\n"); - printf(" Specify the serial configuration (serial only)\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 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 user authentication\n"); - printf(" -noagent disable use of Pageant\n"); - printf(" -agent enable use of Pageant\n"); - printf(" -noshare disable use of connection sharing\n"); - printf(" -share enable use of connection sharing\n"); - printf(" -hostkey keyid\n"); - printf(" manually specify a host key (may be repeated)\n"); - printf(" -sanitise-stderr, -sanitise-stdout, " - "-no-sanitise-stderr, -no-sanitise-stdout\n"); - printf(" do/don't strip control chars from standard " - "output/error\n"); - printf(" -no-antispoof omit anti-spoofing prompt after " - "authentication\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(" -sshlog file\n"); - printf(" -sshrawlog file\n"); - printf(" log protocol details to a file\n"); - printf(" -logoverwrite\n"); - printf(" -logappend\n"); - printf(" control what happens when a log file already exists\n"); - printf(" -shareexists\n"); - printf(" test whether a connection-sharing upstream exists\n"); - exit(1); -} - -static void version(void) -{ - char *buildinfo_text = buildinfo("\n"); - printf("plink: %s\n%s\n", ver, buildinfo_text); - sfree(buildinfo_text); - exit(0); -} - -size_t stdin_gotdata(struct handle *h, const void *data, size_t len, int err) -{ - if (err) { - char buf[4096]; - FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, err, 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(NOISE_SOURCE_IOLEN, len); - if (backend_connected(backend)) { - if (len > 0) { - return backend_send(backend, data, len); - } else { - backend_special(backend, SS_EOF, 0); - return 0; - } - } else - return 0; -} - -void stdouterr_sent(struct handle *h, size_t new_backlog, int err) -{ - if (err) { - char buf[4096]; - FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, err, 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 (backend_connected(backend)) { - backend_unthrottle(backend, (handle_backlog(stdout_handle) + - handle_backlog(stderr_handle))); - } -} - -const bool share_can_be_downstream = true; -const bool share_can_be_upstream = true; - -const unsigned cmdline_tooltype = - TOOLTYPE_HOST_ARG | - TOOLTYPE_HOST_ARG_CAN_BE_SESSION | - TOOLTYPE_HOST_ARG_PROTOCOL_PREFIX | - TOOLTYPE_HOST_ARG_FROM_LAUNCHABLE_LOAD; - -static bool sending; - -static bool plink_mainloop_pre(void *vctx, const HANDLE **extra_handles, - size_t *n_extra_handles) -{ - if (!sending && backend_sendok(backend)) { - stdin_handle = handle_input_new(inhandle, stdin_gotdata, NULL, - 0); - sending = true; - } - - return true; -} - -static bool plink_mainloop_post(void *vctx, size_t extra_handle_index) -{ - if (sending) - handle_unthrottle(stdin_handle, backend_sendbuffer(backend)); - - if (!backend_connected(backend) && - handle_backlog(stdout_handle) + handle_backlog(stderr_handle) == 0) - return false; /* we closed the connection */ - - return true; -} - -int main(int argc, char **argv) -{ - int exitcode; - bool errors; - bool use_subsystem = false; - bool just_test_share_exists = false; - enum TriState sanitise_stdout = AUTO, sanitise_stderr = AUTO; - const struct BackendVtable *vt; - - dll_hijacking_protection(); - - /* - * Initialise port and protocol to sensible defaults. (These - * will be overridden by more or less anything.) - */ - settings_set_default_protocol(PROT_SSH); - settings_set_default_port(22); - - /* - * Process the command line. - */ - conf = conf_new(); - do_defaults(NULL, conf); - settings_set_default_protocol(conf_get_int(conf, CONF_protocol)); - settings_set_default_port(conf_get_int(conf, CONF_port)); - errors = false; - { - /* - * Override the default protocol if PLINK_PROTOCOL is set. - */ - char *p = getenv("PLINK_PROTOCOL"); - if (p) { - const struct BackendVtable *vt = backend_vt_from_name(p); - if (vt) { - settings_set_default_protocol(vt->protocol); - settings_set_default_port(vt->default_port); - conf_set_int(conf, CONF_protocol, vt->protocol); - conf_set_int(conf, CONF_port, vt->default_port); - } - } - } - while (--argc) { - char *p = *++argv; - int ret = cmdline_process_param(p, (argc > 1 ? argv[1] : NULL), - 1, conf); - if (ret == -2) { - fprintf(stderr, - "plink: option \"%s\" requires an argument\n", p); - errors = true; - } else if (ret == 2) { - --argc, ++argv; - } else if (ret == 1) { - continue; - } else if (!strcmp(p, "-batch")) { - console_batch_mode = true; - } else if (!strcmp(p, "-s")) { - /* Save status to write to conf later. */ - use_subsystem = true; - } else if (!strcmp(p, "-V") || !strcmp(p, "--version")) { - version(); - } else if (!strcmp(p, "--help")) { - usage(); - } else if (!strcmp(p, "-pgpfp")) { - pgp_fingerprints(); - exit(1); - } else if (!strcmp(p, "-shareexists")) { - just_test_share_exists = true; - } else if (!strcmp(p, "-sanitise-stdout") || - !strcmp(p, "-sanitize-stdout")) { - sanitise_stdout = FORCE_ON; - } else if (!strcmp(p, "-no-sanitise-stdout") || - !strcmp(p, "-no-sanitize-stdout")) { - sanitise_stdout = FORCE_OFF; - } else if (!strcmp(p, "-sanitise-stderr") || - !strcmp(p, "-sanitize-stderr")) { - sanitise_stderr = FORCE_ON; - } else if (!strcmp(p, "-no-sanitise-stderr") || - !strcmp(p, "-no-sanitize-stderr")) { - sanitise_stderr = FORCE_OFF; - } else if (!strcmp(p, "-no-antispoof")) { - console_antispoof_prompt = false; - } else if (*p != '-') { - strbuf *cmdbuf = strbuf_new(); - - while (argc > 0) { - if (cmdbuf->len > 0) - put_byte(cmdbuf, ' '); /* add space separator */ - put_datapl(cmdbuf, ptrlen_from_asciz(p)); - if (--argc > 0) - p = *++argv; - } - - conf_set_str(conf, CONF_remote_cmd, cmdbuf->s); - conf_set_str(conf, CONF_remote_cmd2, ""); - conf_set_bool(conf, CONF_nopty, true); /* command => no tty */ - - strbuf_free(cmdbuf); - break; /* done with cmdline */ - } else { - fprintf(stderr, "plink: unknown option \"%s\"\n", p); - errors = true; - } - } - - if (errors) - return 1; - - if (!cmdline_host_ok(conf)) { - usage(); - } - - prepare_session(conf); - - /* - * Perform command-line overrides on session configuration. - */ - cmdline_run_saved(conf); - - /* - * Apply subsystem status. - */ - if (use_subsystem) - conf_set_bool(conf, CONF_ssh_subsys, true); - - /* - * Select protocol. This is farmed out into a table in a - * separate file to enable an ssh-free variant. - */ - vt = backend_vt_from_proto(conf_get_int(conf, CONF_protocol)); - if (vt == NULL) { - fprintf(stderr, - "Internal fault: Unsupported protocol found\n"); - return 1; - } - - if (vt->flags & BACKEND_NEEDS_TERMINAL) { - fprintf(stderr, - "Plink doesn't support %s, which needs terminal emulation\n", - vt->displayname); - return 1; - } - - sk_init(); - if (p_WSAEventSelect == NULL) { - fprintf(stderr, "Plink requires WinSock 2\n"); - return 1; - } - - /* - * 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 (conf_get_int(conf, CONF_protocol) == PROT_SSH && - !conf_get_bool(conf, CONF_x11_forward) && - !conf_get_bool(conf, CONF_agentfwd) && - !conf_get_str_nthstrkey(conf, CONF_portfwd, 0)) - conf_set_bool(conf, CONF_ssh_simple, true); - - logctx = log_init(console_cli_logpolicy, conf); - - if (just_test_share_exists) { - if (!vt->test_for_upstream) { - fprintf(stderr, "Connection sharing not supported for this " - "connection type (%s)'\n", vt->displayname); - return 1; - } - if (vt->test_for_upstream(conf_get_str(conf, CONF_host), - conf_get_int(conf, CONF_port), conf)) - return 0; - else - return 1; - } - - if (restricted_acl()) { - lp_eventlog(console_cli_logpolicy, - "Running with restricted process ACL"); - } - - 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); - handle_sink_init(&stdout_hs, stdout_handle); - handle_sink_init(&stderr_hs, stderr_handle); - stdout_bs = BinarySink_UPCAST(&stdout_hs); - stderr_bs = BinarySink_UPCAST(&stderr_hs); - - /* - * Decide whether to sanitise control sequences out of standard - * output and standard error. - * - * If we weren't given a command-line override, we do this if (a) - * the fd in question is pointing at a console, and (b) we aren't - * trying to allocate a terminal as part of the session. - * - * (Rationale: the risk of control sequences is that they cause - * confusion when sent to a local console, so if there isn't one, - * no problem. Also, if we allocate a remote terminal, then we - * sent a terminal type, i.e. we told it what kind of escape - * sequences we _like_, i.e. we were expecting to receive some.) - */ - if (sanitise_stdout == FORCE_ON || - (sanitise_stdout == AUTO && is_console_handle(outhandle) && - conf_get_bool(conf, CONF_nopty))) { - stdout_scc = stripctrl_new(stdout_bs, true, L'\0'); - stdout_bs = BinarySink_UPCAST(stdout_scc); - } - if (sanitise_stderr == FORCE_ON || - (sanitise_stderr == AUTO && is_console_handle(errhandle) && - conf_get_bool(conf, CONF_nopty))) { - stderr_scc = stripctrl_new(stderr_bs, true, L'\0'); - stderr_bs = BinarySink_UPCAST(stderr_scc); - } - - /* - * Start up the connection. - */ - winselcli_setup(); /* ensure event object exists */ - { - char *error, *realhost; - /* nodelay is only useful if stdin is a character device (console) */ - bool nodelay = conf_get_bool(conf, CONF_tcp_nodelay) && - (GetFileType(GetStdHandle(STD_INPUT_HANDLE)) == FILE_TYPE_CHAR); - - error = backend_init(vt, plink_seat, &backend, logctx, conf, - conf_get_str(conf, CONF_host), - conf_get_int(conf, CONF_port), - &realhost, nodelay, - conf_get_bool(conf, CONF_tcp_keepalives)); - if (error) { - fprintf(stderr, "Unable to open connection:\n%s", error); - sfree(error); - return 1; - } - ldisc_create(conf, NULL, backend, plink_seat); - sfree(realhost); - } - - main_thread_id = GetCurrentThreadId(); - - sending = false; - - cli_main_loop(plink_mainloop_pre, plink_mainloop_post, NULL); - - exitcode = backend_exitcode(backend); - 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 */ -} diff --git a/windows/winprint.c b/windows/winprint.c deleted file mode 100644 index e6b3531d..00000000 --- a/windows/winprint.c +++ /dev/null @@ -1,224 +0,0 @@ -/* - * Printing interface for PuTTY. - */ - -#include "putty.h" -#include - -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; -}; - -DECL_WINDOWS_FUNCTION(static, BOOL, EnumPrinters, - (DWORD, LPTSTR, DWORD, LPBYTE, DWORD, LPDWORD, LPDWORD)); -DECL_WINDOWS_FUNCTION(static, BOOL, OpenPrinter, - (LPTSTR, LPHANDLE, LPPRINTER_DEFAULTS)); -DECL_WINDOWS_FUNCTION(static, BOOL, ClosePrinter, (HANDLE)); -DECL_WINDOWS_FUNCTION(static, DWORD, StartDocPrinter, (HANDLE, DWORD, LPBYTE)); -DECL_WINDOWS_FUNCTION(static, BOOL, EndDocPrinter, (HANDLE)); -DECL_WINDOWS_FUNCTION(static, BOOL, StartPagePrinter, (HANDLE)); -DECL_WINDOWS_FUNCTION(static, BOOL, EndPagePrinter, (HANDLE)); -DECL_WINDOWS_FUNCTION(static, BOOL, WritePrinter, - (HANDLE, LPVOID, DWORD, LPDWORD)); - -static void init_winfuncs(void) -{ - static bool initialised = false; - if (initialised) - return; - { - HMODULE winspool_module = load_system32_dll("winspool.drv"); - /* Some MSDN documentation claims that some of the below functions - * should be loaded from spoolss.dll, but this doesn't seem to - * be reliable in practice. - * Nevertheless, we load spoolss.dll ourselves using our safe - * loading method, against the possibility that winspool.drv - * later loads it unsafely. */ - (void) load_system32_dll("spoolss.dll"); - GET_WINDOWS_FUNCTION_PP(winspool_module, EnumPrinters); - GET_WINDOWS_FUNCTION_PP(winspool_module, OpenPrinter); - GET_WINDOWS_FUNCTION_PP(winspool_module, ClosePrinter); - GET_WINDOWS_FUNCTION_PP(winspool_module, StartDocPrinter); - GET_WINDOWS_FUNCTION_PP(winspool_module, EndDocPrinter); - GET_WINDOWS_FUNCTION_PP(winspool_module, StartPagePrinter); - GET_WINDOWS_FUNCTION_PP(winspool_module, EndPagePrinter); - GET_WINDOWS_FUNCTION_PP(winspool_module, WritePrinter); - } - initialised = true; -} - -static bool printer_add_enum(int param, DWORD level, char **buffer, - int offset, int *nprinters_ptr) -{ - DWORD needed = 0, nprinters = 0; - - init_winfuncs(); - - *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. - */ - p_EnumPrinters(param, NULL, level, (LPBYTE)((*buffer)+offset), 512, - &needed, &nprinters); - - if (needed < 512) - needed = 512; - - *buffer = sresize(*buffer, offset+needed, char); - - if (p_EnumPrinters(param, NULL, level, (LPBYTE)((*buffer)+offset), - needed, &needed, &nprinters) == 0) - return false; - - *nprinters_ptr += nprinters; - - return true; -} - -printer_enum *printer_start_enum(int *nprinters_ptr) -{ - printer_enum *ret = snew(printer_enum); - char *buffer = NULL; - - *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 (osPlatformId != VER_PLATFORM_WIN32_NT) { - ret->enum_level = 5; - } else { - ret->enum_level = 4; - } - - if (!printer_add_enum(PRINTER_ENUM_LOCAL | PRINTER_ENUM_CONNECTIONS, - ret->enum_level, &buffer, 0, nprinters_ptr)) - goto error; - - 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; - bool jobstarted = false, pagestarted = false; - - init_winfuncs(); - - ret->hprinter = NULL; - if (!p_OpenPrinter(printer, &ret->hprinter, NULL)) - goto error; - - docinfo.pDocName = "PuTTY remote printer output"; - docinfo.pOutputFile = NULL; - docinfo.pDatatype = "RAW"; - - if (!p_StartDocPrinter(ret->hprinter, 1, (LPBYTE)&docinfo)) - goto error; - jobstarted = true; - - if (!p_StartPagePrinter(ret->hprinter)) - goto error; - pagestarted = true; - - return ret; - - error: - if (pagestarted) - p_EndPagePrinter(ret->hprinter); - if (jobstarted) - p_EndDocPrinter(ret->hprinter); - if (ret->hprinter) - p_ClosePrinter(ret->hprinter); - sfree(ret); - return NULL; -} - -void printer_job_data(printer_job *pj, const void *data, size_t len) -{ - DWORD written; - - if (!pj) - return; - - p_WritePrinter(pj->hprinter, (void *)data, len, &written); -} - -void printer_finish_job(printer_job *pj) -{ - if (!pj) - return; - - p_EndPagePrinter(pj->hprinter); - p_EndDocPrinter(pj->hprinter); - p_ClosePrinter(pj->hprinter); - sfree(pj); -} diff --git a/windows/winproxy.c b/windows/winproxy.c deleted file mode 100644 index 94e31fcb..00000000 --- a/windows/winproxy.c +++ /dev/null @@ -1,107 +0,0 @@ -/* - * winproxy.c: Windows implementation of platform_new_connection(), - * supporting an OpenSSH-like proxy command via the winhandl.c - * mechanism. - */ - -#include -#include - -#include "tree234.h" -#include "putty.h" -#include "network.h" -#include "proxy.h" - -Socket *platform_new_connection(SockAddr *addr, const char *hostname, - int port, bool privport, - bool oobinline, bool nodelay, bool keepalive, - Plug *plug, Conf *conf) -{ - char *cmd; - HANDLE us_to_cmd, cmd_from_us; - HANDLE us_from_cmd, cmd_to_us; - HANDLE us_from_cmd_err, cmd_err_to_us; - SECURITY_ATTRIBUTES sa; - STARTUPINFO si; - PROCESS_INFORMATION pi; - - if (conf_get_int(conf, CONF_proxy_type) != PROXY_CMD) - return NULL; - - cmd = format_telnet_command(addr, port, conf); - - /* We are responsible for this and don't need it any more */ - sk_addr_free(addr); - - { - char *msg = dupprintf("Starting local proxy command: %s", cmd); - plug_log(plug, PLUGLOG_PROXY_MSG, NULL, 0, msg, 0); - sfree(msg); - } - - /* - * 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)) { - sfree(cmd); - return new_error_socket_fmt( - plug, "Unable to create pipes for proxy command: %s", - win_strerror(GetLastError())); - } - - if (!CreatePipe(&cmd_from_us, &us_to_cmd, &sa, 0)) { - sfree(cmd); - CloseHandle(us_from_cmd); - CloseHandle(cmd_to_us); - return new_error_socket_fmt( - plug, "Unable to create pipes for proxy command: %s", - win_strerror(GetLastError())); - } - - if (!CreatePipe(&us_from_cmd_err, &cmd_err_to_us, &sa, 0)) { - sfree(cmd); - CloseHandle(us_from_cmd); - CloseHandle(cmd_to_us); - CloseHandle(us_to_cmd); - CloseHandle(cmd_from_us); - return new_error_socket_fmt( - plug, "Unable to create pipes for proxy command: %s", - win_strerror(GetLastError())); - } - - SetHandleInformation(us_to_cmd, HANDLE_FLAG_INHERIT, 0); - SetHandleInformation(us_from_cmd, HANDLE_FLAG_INHERIT, 0); - if (us_from_cmd_err != NULL) - SetHandleInformation(us_from_cmd_err, 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 = cmd_err_to_us; - CreateProcess(NULL, cmd, NULL, NULL, true, - CREATE_NO_WINDOW | NORMAL_PRIORITY_CLASS, - NULL, NULL, &si, &pi); - CloseHandle(pi.hProcess); - CloseHandle(pi.hThread); - - sfree(cmd); - - CloseHandle(cmd_from_us); - CloseHandle(cmd_to_us); - - if (cmd_err_to_us != NULL) - CloseHandle(cmd_err_to_us); - - return make_handle_socket(us_to_cmd, us_from_cmd, us_from_cmd_err, - plug, false); -} diff --git a/windows/winseat.h b/windows/winseat.h deleted file mode 100644 index c6b5fa96..00000000 --- a/windows/winseat.h +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Small implementation of Seat and LogPolicy shared between window.c - * and windlg.c. - */ - -typedef struct WinGuiSeat WinGuiSeat; - -struct WinGuiSeat { - HWND term_hwnd; - Seat seat; - LogPolicy logpolicy; -}; - -extern const LogPolicyVtable win_gui_logpolicy_vt; /* in windlg.c */ diff --git a/windows/winsecur.h b/windows/winsecur.h deleted file mode 100644 index 6ca736a3..00000000 --- a/windows/winsecur.h +++ /dev/null @@ -1,49 +0,0 @@ -/* - * winsecur.h: some miscellaneous security-related helper functions, - * defined in winsecur.c, that use the advapi32 library. Also - * centralises the machinery for dynamically loading that library. - */ - -#include - -/* - * Functions loaded from advapi32.dll. - */ -DECL_WINDOWS_FUNCTION(extern, BOOL, OpenProcessToken, - (HANDLE, DWORD, PHANDLE)); -DECL_WINDOWS_FUNCTION(extern, BOOL, GetTokenInformation, - (HANDLE, TOKEN_INFORMATION_CLASS, - LPVOID, DWORD, PDWORD)); -DECL_WINDOWS_FUNCTION(extern, BOOL, InitializeSecurityDescriptor, - (PSECURITY_DESCRIPTOR, DWORD)); -DECL_WINDOWS_FUNCTION(extern, BOOL, SetSecurityDescriptorOwner, - (PSECURITY_DESCRIPTOR, PSID, BOOL)); -DECL_WINDOWS_FUNCTION(extern, DWORD, GetSecurityInfo, - (HANDLE, SE_OBJECT_TYPE, SECURITY_INFORMATION, - PSID *, PSID *, PACL *, PACL *, - PSECURITY_DESCRIPTOR *)); -DECL_WINDOWS_FUNCTION(extern, DWORD, SetSecurityInfo, - (HANDLE, SE_OBJECT_TYPE, SECURITY_INFORMATION, - PSID, PSID, PACL, PACL)); -DECL_WINDOWS_FUNCTION(extern, DWORD, SetEntriesInAclA, - (ULONG, PEXPLICIT_ACCESS, PACL, PACL *)); -bool got_advapi(void); - -/* - * Find the SID describing the current user. The return value (if not - * NULL for some error-related reason) is smalloced. - */ -PSID get_user_sid(void); - -/* - * Construct a PSECURITY_DESCRIPTOR of the type used for named pipe - * servers, i.e. allowing access only to the current user id and also - * only local (i.e. not over SMB) connections. - * - * If this function returns true, then 'psd' and 'acl' will have been - * filled in with memory allocated using LocalAlloc (and hence must be - * freed later using LocalFree). If it returns false, then instead - * 'error' has been filled with a dynamically allocated error message. - */ -bool make_private_security_descriptor( - DWORD permissions, PSECURITY_DESCRIPTOR *psd, PACL *acl, char **error); diff --git a/windows/winselcli.c b/windows/winselcli.c deleted file mode 100644 index f19a0bbe..00000000 --- a/windows/winselcli.c +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Implementation of do_select() for winnet.c to use, suitable for use - * when there's no GUI window to have network activity reported to. - * - * It uses WSAEventSelect, where available, to convert network - * activity into activity on an event object, for integration into an - * event loop that includes WaitForMultipleObjects. - * - * It also maintains a list of currently active sockets, which can be - * retrieved by a front end that wants to use WinSock's synchronous - * select() function. - */ - -#include "putty.h" - -static tree234 *winselcli_sockets; - -static int socket_cmp(void *av, void *bv) -{ - return memcmp(av, bv, sizeof(SOCKET)); -} - -HANDLE winselcli_event = INVALID_HANDLE_VALUE; - -void winselcli_setup(void) -{ - if (!winselcli_sockets) - winselcli_sockets = newtree234(socket_cmp); - - if (p_WSAEventSelect && winselcli_event == INVALID_HANDLE_VALUE) - winselcli_event = CreateEvent(NULL, false, false, NULL); -} - -SOCKET winselcli_unique_socket(void) -{ - if (!winselcli_sockets) - return INVALID_SOCKET; - - assert(count234(winselcli_sockets) <= 1); - - SOCKET *p = index234(winselcli_sockets, 0); - if (!p) - return INVALID_SOCKET; - - return *p; -} - -const char *do_select(SOCKET skt, bool enable) -{ - /* Check everything's been set up, for convenience of callers. */ - winselcli_setup(); - - if (enable) { - SOCKET *ptr = snew(SOCKET); - *ptr = skt; - if (add234(winselcli_sockets, ptr) != ptr) - sfree(ptr); /* already there */ - } else { - SOCKET *ptr = del234(winselcli_sockets, &skt); - if (ptr) - sfree(ptr); - } - - if (p_WSAEventSelect) { - int events; - if (enable) { - events = (FD_CONNECT | FD_READ | FD_WRITE | - FD_OOB | FD_CLOSE | FD_ACCEPT); - } else { - events = 0; - } - - if (p_WSAEventSelect(skt, winselcli_event, events) == SOCKET_ERROR) - return winsock_error_string(p_WSAGetLastError()); - } - - return NULL; -} diff --git a/windows/winselgui.c b/windows/winselgui.c deleted file mode 100644 index 48a15212..00000000 --- a/windows/winselgui.c +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Implementation of do_select() for winnet.c to use, that uses - * WSAAsyncSelect to convert network activity into window messages, - * for integration into a GUI event loop. - */ - -#include "putty.h" - -static HWND winsel_hwnd = NULL; - -void winselgui_set_hwnd(HWND hwnd) -{ - winsel_hwnd = hwnd; -} - -void winselgui_clear_hwnd(void) -{ - winsel_hwnd = NULL; -} - -const char *do_select(SOCKET skt, bool enable) -{ - int msg, events; - if (enable) { - msg = WM_NETEVENT; - events = (FD_CONNECT | FD_READ | FD_WRITE | - FD_OOB | FD_CLOSE | FD_ACCEPT); - } else { - msg = events = 0; - } - - assert(winsel_hwnd); - - if (p_WSAAsyncSelect(skt, winsel_hwnd, msg, events) == SOCKET_ERROR) - return winsock_error_string(p_WSAGetLastError()); - - return NULL; -} diff --git a/windows/winser.c b/windows/winser.c deleted file mode 100644 index 7f4bcf2e..00000000 --- a/windows/winser.c +++ /dev/null @@ -1,465 +0,0 @@ -/* - * Serial back end (Windows-specific). - */ - -#include -#include -#include - -#include "putty.h" - -#define SERIAL_MAX_BACKLOG 4096 - -typedef struct Serial Serial; -struct Serial { - HANDLE port; - struct handle *out, *in; - Seat *seat; - LogContext *logctx; - int bufsize; - long clearbreak_time; - bool break_in_progress; - Backend backend; -}; - -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 size_t serial_gotdata( - struct handle *h, const void *data, size_t len, int err) -{ - Serial *serial = (Serial *)handle_get_privdata(h); - if (err || 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 (!err) - error_msg = "End of file reading from serial device"; - else - error_msg = "Error reading from serial device"; - - serial_terminate(serial); - - seat_notify_remote_exit(serial->seat); - - logevent(serial->logctx, error_msg); - - seat_connection_fatal(serial->seat, "%s", error_msg); - - return 0; - } else { - return seat_stdout(serial->seat, data, len); - } -} - -static void serial_sentdata(struct handle *h, size_t new_backlog, int err) -{ - Serial *serial = (Serial *)handle_get_privdata(h); - if (err) { - const char *error_msg = "Error writing to serial device"; - - serial_terminate(serial); - - seat_notify_remote_exit(serial->seat); - - logevent(serial->logctx, error_msg); - - seat_connection_fatal(serial->seat, "%s", error_msg); - } else { - serial->bufsize = new_backlog; - } -} - -static char *serial_configure(Serial *serial, HANDLE serport, Conf *conf) -{ - 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)) { - 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 = conf_get_int(conf, CONF_serspeed); - logeventf(serial->logctx, "Configuring baud rate %lu", - (unsigned long)dcb.BaudRate); - - dcb.ByteSize = conf_get_int(conf, CONF_serdatabits); - logeventf(serial->logctx, "Configuring %u data bits", - (unsigned)dcb.ByteSize); - - switch (conf_get_int(conf, CONF_serstopbits)) { - case 2: dcb.StopBits = ONESTOPBIT; str = "1 stop bit"; break; - case 3: dcb.StopBits = ONE5STOPBITS; str = "1.5 stop bits"; break; - case 4: dcb.StopBits = TWOSTOPBITS; str = "2 stop bits"; break; - default: return dupstr("Invalid number of stop bits " - "(need 1, 1.5 or 2)"); - } - logeventf(serial->logctx, "Configuring %s", str); - - switch (conf_get_int(conf, CONF_serparity)) { - case SER_PAR_NONE: dcb.Parity = NOPARITY; str = "no"; break; - case SER_PAR_ODD: dcb.Parity = ODDPARITY; str = "odd"; break; - case SER_PAR_EVEN: dcb.Parity = EVENPARITY; str = "even"; break; - case SER_PAR_MARK: dcb.Parity = MARKPARITY; str = "mark"; break; - case SER_PAR_SPACE: dcb.Parity = SPACEPARITY; str = "space"; break; - } - logeventf(serial->logctx, "Configuring %s parity", str); - - switch (conf_get_int(conf, CONF_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; - } - logeventf(serial->logctx, "Configuring %s flow control", str); - - if (!SetCommState(serport, &dcb)) - return dupprintf("Configuring serial port: %s", - win_strerror(GetLastError())); - - timeouts.ReadIntervalTimeout = 1; - timeouts.ReadTotalTimeoutMultiplier = 0; - timeouts.ReadTotalTimeoutConstant = 0; - timeouts.WriteTotalTimeoutMultiplier = 0; - timeouts.WriteTotalTimeoutConstant = 0; - if (!SetCommTimeouts(serport, &timeouts)) - return dupprintf("Configuring serial timeouts: %s", - win_strerror(GetLastError())); - } - - 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 char *serial_init(const BackendVtable *vt, Seat *seat, - Backend **backend_handle, LogContext *logctx, - Conf *conf, const char *host, int port, - char **realhost, bool nodelay, bool keepalive) -{ - Serial *serial; - HANDLE serport; - char *err; - char *serline; - - /* No local authentication phase in this protocol */ - seat_set_trust_status(seat, false); - - serial = snew(Serial); - serial->port = INVALID_HANDLE_VALUE; - serial->out = serial->in = NULL; - serial->bufsize = 0; - serial->break_in_progress = false; - serial->backend.vt = vt; - *backend_handle = &serial->backend; - - serial->seat = seat; - serial->logctx = logctx; - - serline = conf_get_str(conf, CONF_serline); - logeventf(serial->logctx, "Opening serial device %s", serline); - - /* - * 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 - * - * ("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 - * (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(serline, '\\') ? "" : "\\\\.\\", serline); - serport = CreateFile(serfilename, GENERIC_READ | GENERIC_WRITE, 0, NULL, - OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); - if (serport == INVALID_HANDLE_VALUE) { - err = dupprintf("Opening '%s': %s", - serfilename, win_strerror(GetLastError())); - sfree(serfilename); - return err; - } - - sfree(serfilename); - - err = serial_configure(serial, serport, conf); - 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(serline); - - /* - * Specials are always available. - */ - seat_update_specials_menu(serial->seat); - - return NULL; -} - -static void serial_free(Backend *be) -{ - Serial *serial = container_of(be, Serial, backend); - - serial_terminate(serial); - expire_timer_context(serial); - sfree(serial); -} - -static void serial_reconfig(Backend *be, Conf *conf) -{ - Serial *serial = container_of(be, Serial, backend); - - serial_configure(serial, serial->port, conf); - - /* - * FIXME: what should we do if that call returned a non-NULL error - * message? - */ -} - -/* - * Called to send data down the serial connection. - */ -static size_t serial_send(Backend *be, const char *buf, size_t len) -{ - Serial *serial = container_of(be, Serial, backend); - - 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 size_t serial_sendbuffer(Backend *be) -{ - Serial *serial = container_of(be, Serial, backend); - return serial->bufsize; -} - -/* - * Called to set the size of the window - */ -static void serial_size(Backend *be, int width, int height) -{ - /* Do nothing! */ - return; -} - -static void serbreak_timer(void *ctx, unsigned long now) -{ - Serial *serial = (Serial *)ctx; - - if (now == serial->clearbreak_time && serial->port) { - ClearCommBreak(serial->port); - serial->break_in_progress = false; - logevent(serial->logctx, "Finished serial break"); - } -} - -/* - * Send serial special codes. - */ -static void serial_special(Backend *be, SessionSpecialCode code, int arg) -{ - Serial *serial = container_of(be, Serial, backend); - - if (serial->port && code == SS_BRK) { - logevent(serial->logctx, "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 SessionSpecial *serial_get_specials(Backend *be) -{ - static const SessionSpecial specials[] = { - {"Break", SS_BRK}, - {NULL, SS_EXITMENU} - }; - return specials; -} - -static bool serial_connected(Backend *be) -{ - return true; /* always connected */ -} - -static bool serial_sendok(Backend *be) -{ - return true; -} - -static void serial_unthrottle(Backend *be, size_t backlog) -{ - Serial *serial = container_of(be, Serial, backend); - if (serial->in) - handle_unthrottle(serial->in, backlog); -} - -static bool serial_ldisc(Backend *be, int option) -{ - /* - * Local editing and local echo are off by default. - */ - return false; -} - -static void serial_provide_ldisc(Backend *be, Ldisc *ldisc) -{ - /* This is a stub. */ -} - -static int serial_exitcode(Backend *be) -{ - Serial *serial = container_of(be, Serial, backend); - 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(Backend *be) -{ - return 0; -} - -const BackendVtable serial_backend = { - .init = serial_init, - .free = serial_free, - .reconfig = serial_reconfig, - .send = serial_send, - .sendbuffer = serial_sendbuffer, - .size = serial_size, - .special = serial_special, - .get_specials = serial_get_specials, - .connected = serial_connected, - .exitcode = serial_exitcode, - .sendok = serial_sendok, - .ldisc_option_state = serial_ldisc, - .provide_ldisc = serial_provide_ldisc, - .unthrottle = serial_unthrottle, - .cfg_info = serial_cfg_info, - .id = "serial", - .displayname = "Serial", - .protocol = PROT_SERIAL, - .serial_parity_mask = ((1 << SER_PAR_NONE) | - (1 << SER_PAR_ODD) | - (1 << SER_PAR_EVEN) | - (1 << SER_PAR_MARK) | - (1 << SER_PAR_SPACE)), - .serial_flow_mask = ((1 << SER_FLOW_NONE) | - (1 << SER_FLOW_XONXOFF) | - (1 << SER_FLOW_RTSCTS) | - (1 << SER_FLOW_DSRDTR)), -}; diff --git a/windows/winsftp.c b/windows/winsftp.c deleted file mode 100644 index 0c695d2e..00000000 --- a/windows/winsftp.c +++ /dev/null @@ -1,650 +0,0 @@ -/* - * winsftp.c: the Windows-specific parts of PSFTP and PSCP. - */ - -#include /* need to put this first, for winelib builds */ -#include - -#define NEED_DECLARATION_OF_SELECT - -#include "putty.h" -#include "psftp.h" -#include "ssh.h" -#include "winsecur.h" - -int filexfer_get_userpass_input(Seat *seat, prompts_t *p, bufchain *input) -{ - int ret; - ret = cmdline_get_passwd_input(p); - if (ret == -1) - ret = console_get_userpass_input(p); - return ret; -} - -void platform_get_x11_auth(struct X11Display *display, Conf *conf) -{ - /* Do nothing, therefore no auth. */ -} -const bool 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); - size_t len = GetCurrentDirectory(256, ret); - if (len > 256) - ret = sresize(ret, len, char); - GetCurrentDirectory(len, ret); - return ret; -} - -static inline uint64_t uint64_from_words(uint32_t hi, uint32_t lo) -{ - return (((uint64_t)hi) << 32) | lo; -} - -#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(const char *name, uint64_t *size, - unsigned long *mtime, unsigned long *atime, - long *perms) -{ - 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) { - DWORD lo, hi; - lo = GetFileSize(h, &hi); - *size = uint64_from_words(hi, lo); - } - - 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); - } - - if (perms) - *perms = -1; - - return ret; -} - -int read_from_file(RFile *f, void *buffer, int length) -{ - DWORD read; - if (!ReadFile(f->h, buffer, length, &read, NULL)) - return -1; /* error */ - else - return read; -} - -void close_rfile(RFile *f) -{ - CloseHandle(f->h); - sfree(f); -} - -struct WFile { - HANDLE h; -}; - -WFile *open_new_file(const char *name, long perms) -{ - 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(const char *name, uint64_t *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) { - DWORD lo, hi; - lo = GetFileSize(h, &hi); - *size = uint64_from_words(hi, lo); - } - - return ret; -} - -int write_to_file(WFile *f, void *buffer, int length) -{ - DWORD written; - if (!WriteFile(f->h, buffer, length, &written, NULL)) - 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_t 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; - } - - { - LONG lo = offset & 0xFFFFFFFFU, hi = offset >> 32; - SetFilePointer(f->h, lo, &hi, movemethod); - } - - if (GetLastError() != NO_ERROR) - return -1; - else - return 0; -} - -uint64_t get_file_posn(WFile *f) -{ - LONG lo, hi = 0; - - lo = SetFilePointer(f->h, 0L, &hi, FILE_CURRENT); - return uint64_from_words(hi, lo); -} - -int file_type(const 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(const char *name, const char **errmsg) -{ - HANDLE h; - WIN32_FIND_DATA fdat; - char *findfile; - DirHandle *ret; - - /* Enumerate files in dir `foo'. */ - findfile = dupcat(name, "/*"); - h = FindFirstFile(findfile, &fdat); - if (h == INVALID_HANDLE_VALUE) { - *errmsg = win_strerror(GetLastError()); - 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; - if (!FindNextFile(dir->h, &fdat)) - 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(const char *name, bool 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; -}; - -char *stripslashes(const char *str, bool local) -{ - char *p; - - /* - * On Windows, \ / : are all path component separators. - */ - - 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 (char *)str; -} - -WildcardMatcher *begin_wildcard_matching(const 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, true); - *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); - - return ret; -} - -char *wildcard_get_filename(WildcardMatcher *dir) -{ - while (!dir->name) { - WIN32_FIND_DATA fdat; - - if (!FindNextFile(dir->h, &fdat)) - 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); - } - - 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); -} - -bool vet_filename(const char *name) -{ - if (strchr(name, '/') || strchr(name, '\\') || strchr(name, ':')) - return false; - - if (!name[strspn(name, ".")]) /* entirely composed of dots */ - return false; - - return true; -} - -bool create_directory(const char *name) -{ - return CreateDirectory(name, NULL) != 0; -} - -char *dir_file_cat(const char *dir, const char *file) -{ - ptrlen dir_pl = ptrlen_from_asciz(dir); - return dupcat( - dir, (ptrlen_endswith(dir_pl, PTRLEN_LITERAL("\\"), NULL) || - ptrlen_endswith(dir_pl, PTRLEN_LITERAL("/"), NULL)) ? "" : "\\", - file); -} - -/* ---------------------------------------------------------------------- - * Platform-specific network handling. - */ -struct winsftp_cliloop_ctx { - HANDLE other_event; - int toret; -}; -static bool winsftp_cliloop_pre(void *vctx, const HANDLE **extra_handles, - size_t *n_extra_handles) -{ - struct winsftp_cliloop_ctx *ctx = (struct winsftp_cliloop_ctx *)vctx; - - if (ctx->other_event != INVALID_HANDLE_VALUE) { - *extra_handles = &ctx->other_event; - *n_extra_handles = 1; - } - - return true; -} -static bool winsftp_cliloop_post(void *vctx, size_t extra_handle_index) -{ - struct winsftp_cliloop_ctx *ctx = (struct winsftp_cliloop_ctx *)vctx; - - if (ctx->other_event != INVALID_HANDLE_VALUE && - extra_handle_index == 0) - ctx->toret = 1; /* other_event was set */ - - return false; /* always run only one loop iteration */ -} -int do_eventsel_loop(HANDLE other_event) -{ - struct winsftp_cliloop_ctx ctx[1]; - ctx->other_event = other_event; - ctx->toret = 0; - cli_main_loop(winsftp_cliloop_pre, winsftp_cliloop_post, ctx); - return ctx->toret; -} - -/* - * 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; - unsigned long now = GETTICKCOUNT(), then; - SOCKET skt = winselcli_unique_socket(); - - if (skt == INVALID_SOCKET) - return -1; /* doom */ - - if (socket_writable(skt)) - select_result((WPARAM) skt, (LPARAM) FD_WRITE); - - do { - unsigned long next; - long ticks; - struct timeval tv, *ptv; - - if (run_timers(now, &next)) { - then = now; - now = GETTICKCOUNT(); - if (now - then > next - then) - ticks = 0; - else - ticks = next - now; - tv.tv_sec = ticks / 1000; - tv.tv_usec = ticks % 1000 * 1000; - ptv = &tv; - } else { - ptv = NULL; - } - - FD_ZERO(&readfds); - FD_SET(skt, &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) skt, (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(const char *prompt, bool no_fds_ok) -{ - int ret; - struct command_read_ctx ctx[1]; - DWORD threadid; - HANDLE hThread; - - fputs(prompt, stdout); - fflush(stdout); - - if ((winselcli_unique_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; - - hThread = CreateThread(NULL, 0, command_read_thread, ctx, 0, &threadid); - if (!hThread) { - CloseHandle(ctx->event); - fprintf(stderr, "Unable to create command input thread\n"); - cleanup_exit(1); - } - - do { - ret = do_eventsel_loop(ctx->event); - - /* do_eventsel_loop can't return an error (unlike - * ssh_sftp_loop_iteration, which can return -1 if select goes - * wrong or if the socket doesn't exist). */ - assert(ret >= 0); - } while (ret == 0); - - CloseHandle(hThread); - CloseHandle(ctx->event); - - return ctx->line; -} - -void platform_psftp_pre_conn_setup(LogPolicy *lp) -{ - if (restricted_acl()) { - lp_eventlog(lp, "Running with restricted process ACL"); - } -} - -/* ---------------------------------------------------------------------- - * Main program. Parse arguments etc. - */ -int main(int argc, char *argv[]) -{ - int ret; - - dll_hijacking_protection(); - - ret = psftp_main(argc, argv); - - return ret; -} diff --git a/windows/winshare.c b/windows/winshare.c deleted file mode 100644 index 51a84438..00000000 --- a/windows/winshare.c +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Windows implementation of SSH connection-sharing IPC setup. - */ - -#include -#include - -#include "tree234.h" -#include "putty.h" -#include "network.h" -#include "proxy.h" -#include "ssh.h" - -#include "wincapi.h" -#include "winsecur.h" - -#define CONNSHARE_PIPE_PREFIX "\\\\.\\pipe\\putty-connshare" -#define CONNSHARE_MUTEX_PREFIX "Local\\putty-connshare-mutex" - -static char *make_name(const char *prefix, const char *name) -{ - char *username, *retname; - - username = get_username(); - retname = dupprintf("%s.%s.%s", prefix, username, name); - sfree(username); - - return retname; -} - -int platform_ssh_share(const char *pi_name, Conf *conf, - Plug *downplug, Plug *upplug, Socket **sock, - char **logtext, char **ds_err, char **us_err, - bool can_upstream, bool can_downstream) -{ - char *name, *mutexname, *pipename; - HANDLE mutex; - Socket *retsock; - PSECURITY_DESCRIPTOR psd; - PACL acl; - - /* - * Transform the platform-independent version of the connection - * identifier into the obfuscated version we'll use for our - * Windows named pipe and mutex. A side effect of doing this is - * that it also eliminates any characters illegal in Windows pipe - * names. - */ - name = capi_obfuscate_string(pi_name); - if (!name) { - *logtext = dupprintf("Unable to call CryptProtectMemory: %s", - win_strerror(GetLastError())); - return SHARE_NONE; - } - - /* - * Make a mutex name out of the connection identifier, and lock it - * while we decide whether to be upstream or downstream. - */ - { - SECURITY_ATTRIBUTES sa; - - mutexname = make_name(CONNSHARE_MUTEX_PREFIX, name); - if (!make_private_security_descriptor(MUTEX_ALL_ACCESS, - &psd, &acl, logtext)) { - sfree(mutexname); - sfree(name); - return SHARE_NONE; - } - - memset(&sa, 0, sizeof(sa)); - sa.nLength = sizeof(sa); - sa.lpSecurityDescriptor = psd; - sa.bInheritHandle = false; - - mutex = CreateMutex(&sa, false, mutexname); - - if (!mutex) { - *logtext = dupprintf("CreateMutex(\"%s\") failed: %s", - mutexname, win_strerror(GetLastError())); - sfree(mutexname); - sfree(name); - LocalFree(psd); - LocalFree(acl); - return SHARE_NONE; - } - - sfree(mutexname); - LocalFree(psd); - LocalFree(acl); - - WaitForSingleObject(mutex, INFINITE); - } - - pipename = make_name(CONNSHARE_PIPE_PREFIX, name); - - *logtext = NULL; - - if (can_downstream) { - retsock = new_named_pipe_client(pipename, downplug); - if (sk_socket_error(retsock) == NULL) { - sfree(*logtext); - *logtext = pipename; - *sock = retsock; - sfree(name); - ReleaseMutex(mutex); - CloseHandle(mutex); - return SHARE_DOWNSTREAM; - } - sfree(*ds_err); - *ds_err = dupprintf("%s: %s", pipename, sk_socket_error(retsock)); - sk_close(retsock); - } - - if (can_upstream) { - retsock = new_named_pipe_listener(pipename, upplug); - if (sk_socket_error(retsock) == NULL) { - sfree(*logtext); - *logtext = pipename; - *sock = retsock; - sfree(name); - ReleaseMutex(mutex); - CloseHandle(mutex); - return SHARE_UPSTREAM; - } - sfree(*us_err); - *us_err = dupprintf("%s: %s", pipename, sk_socket_error(retsock)); - sk_close(retsock); - } - - /* One of the above clauses ought to have happened. */ - assert(*logtext || *ds_err || *us_err); - - sfree(pipename); - sfree(name); - ReleaseMutex(mutex); - CloseHandle(mutex); - return SHARE_NONE; -} - -void platform_ssh_share_cleanup(const char *name) -{ -} diff --git a/windows/winsocks.c b/windows/winsocks.c deleted file mode 100644 index 83ba364c..00000000 --- a/windows/winsocks.c +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Main program for Windows psocks. - */ - -#include "putty.h" -#include "ssh.h" -#include "psocks.h" - -static const PsocksPlatform platform = { - NULL /* open_pipes */, - NULL /* start_subcommand */, -}; - -int main(int argc, char **argv) -{ - psocks_state *ps = psocks_new(&platform); - psocks_cmdline(ps, argc, argv); - - sk_init(); - winselcli_setup(); - psocks_start(ps); - - cli_main_loop(cliloop_null_pre, cliloop_null_post, NULL); -} diff --git a/windows/winstore.c b/windows/winstore.c deleted file mode 100644 index 09e5c028..00000000 --- a/windows/winstore.c +++ /dev/null @@ -1,873 +0,0 @@ -/* - * winstore.c: Windows-specific implementation of the interface - * defined in storage.h. - */ - -#include -#include -#include -#include -#include "putty.h" -#include "storage.h" - -#include -#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 bool tried_shgetfolderpath = false; -static HMODULE shell32_module = NULL; -DECL_WINDOWS_FUNCTION(static, HRESULT, SHGetFolderPathA, - (HWND, int, HANDLE, DWORD, LPSTR)); - -struct settings_w { - HKEY sesskey; -}; - -settings_w *open_settings_w(const char *sessionname, char **errmsg) -{ - HKEY subkey1, sesskey; - int ret; - strbuf *sb; - - *errmsg = NULL; - - if (!sessionname || !*sessionname) - sessionname = "Default Settings"; - - sb = strbuf_new(); - escape_registry_key(sessionname, sb); - - ret = RegCreateKey(HKEY_CURRENT_USER, puttystr, &subkey1); - if (ret != ERROR_SUCCESS) { - strbuf_free(sb); - *errmsg = dupprintf("Unable to create registry key\n" - "HKEY_CURRENT_USER\\%s", puttystr); - return NULL; - } - ret = RegCreateKey(subkey1, sb->s, &sesskey); - RegCloseKey(subkey1); - if (ret != ERROR_SUCCESS) { - *errmsg = dupprintf("Unable to create registry key\n" - "HKEY_CURRENT_USER\\%s\\%s", puttystr, sb->s); - strbuf_free(sb); - return NULL; - } - strbuf_free(sb); - - settings_w *toret = snew(settings_w); - toret->sesskey = sesskey; - return toret; -} - -void write_setting_s(settings_w *handle, const char *key, const char *value) -{ - if (handle) - RegSetValueEx(handle->sesskey, key, 0, REG_SZ, (CONST BYTE *)value, - 1 + strlen(value)); -} - -void write_setting_i(settings_w *handle, const char *key, int value) -{ - if (handle) - RegSetValueEx(handle->sesskey, key, 0, REG_DWORD, - (CONST BYTE *) &value, sizeof(value)); -} - -void close_settings_w(settings_w *handle) -{ - RegCloseKey(handle->sesskey); - sfree(handle); -} - -struct settings_r { - HKEY sesskey; -}; - -settings_r *open_settings_r(const char *sessionname) -{ - HKEY subkey1, sesskey; - strbuf *sb; - - if (!sessionname || !*sessionname) - sessionname = "Default Settings"; - - sb = strbuf_new(); - escape_registry_key(sessionname, sb); - - if (RegOpenKey(HKEY_CURRENT_USER, puttystr, &subkey1) != ERROR_SUCCESS) { - sesskey = NULL; - } else { - if (RegOpenKey(subkey1, sb->s, &sesskey) != ERROR_SUCCESS) { - sesskey = NULL; - } - RegCloseKey(subkey1); - } - - strbuf_free(sb); - - if (!sesskey) - return NULL; - - settings_r *toret = snew(settings_r); - toret->sesskey = sesskey; - return toret; -} - -char *read_setting_s(settings_r *handle, const char *key) -{ - DWORD type, allocsize, size; - char *ret; - - if (!handle) - return NULL; - - /* Find out the type and size of the data. */ - if (RegQueryValueEx(handle->sesskey, key, 0, - &type, NULL, &size) != ERROR_SUCCESS || - type != REG_SZ) - return NULL; - - allocsize = size+1; /* allow for an extra NUL if needed */ - ret = snewn(allocsize, char); - if (RegQueryValueEx(handle->sesskey, key, 0, - &type, (BYTE *)ret, &size) != ERROR_SUCCESS || - type != REG_SZ) { - sfree(ret); - return NULL; - } - assert(size < allocsize); - ret[size] = '\0'; /* add an extra NUL in case RegQueryValueEx - * didn't supply one */ - - return ret; -} - -int read_setting_i(settings_r *handle, const char *key, int defvalue) -{ - DWORD type, val, size; - size = sizeof(val); - - if (!handle || - RegQueryValueEx(handle->sesskey, key, 0, &type, - (BYTE *) &val, &size) != ERROR_SUCCESS || - size != sizeof(val) || type != REG_DWORD) - return defvalue; - else - return val; -} - -FontSpec *read_setting_fontspec(settings_r *handle, const char *name) -{ - char *settingname; - char *fontname; - FontSpec *ret; - int isbold, height, charset; - - fontname = read_setting_s(handle, name); - if (!fontname) - return NULL; - - settingname = dupcat(name, "IsBold"); - isbold = read_setting_i(handle, settingname, -1); - sfree(settingname); - if (isbold == -1) { - sfree(fontname); - return NULL; - } - - settingname = dupcat(name, "CharSet"); - charset = read_setting_i(handle, settingname, -1); - sfree(settingname); - if (charset == -1) { - sfree(fontname); - return NULL; - } - - settingname = dupcat(name, "Height"); - height = read_setting_i(handle, settingname, INT_MIN); - sfree(settingname); - if (height == INT_MIN) { - sfree(fontname); - return NULL; - } - - ret = fontspec_new(fontname, isbold, height, charset); - sfree(fontname); - return ret; -} - -void write_setting_fontspec(settings_w *handle, - const char *name, FontSpec *font) -{ - char *settingname; - - write_setting_s(handle, name, font->name); - settingname = dupcat(name, "IsBold"); - write_setting_i(handle, settingname, font->isbold); - sfree(settingname); - settingname = dupcat(name, "CharSet"); - write_setting_i(handle, settingname, font->charset); - sfree(settingname); - settingname = dupcat(name, "Height"); - write_setting_i(handle, settingname, font->height); - sfree(settingname); -} - -Filename *read_setting_filename(settings_r *handle, const char *name) -{ - char *tmp = read_setting_s(handle, name); - if (tmp) { - Filename *ret = filename_from_str(tmp); - sfree(tmp); - return ret; - } else - return NULL; -} - -void write_setting_filename(settings_w *handle, - const char *name, Filename *result) -{ - write_setting_s(handle, name, result->path); -} - -void close_settings_r(settings_r *handle) -{ - if (handle) { - RegCloseKey(handle->sesskey); - sfree(handle); - } -} - -void del_settings(const char *sessionname) -{ - HKEY subkey1; - strbuf *sb; - - if (RegOpenKey(HKEY_CURRENT_USER, puttystr, &subkey1) != ERROR_SUCCESS) - return; - - sb = strbuf_new(); - escape_registry_key(sessionname, sb); - RegDeleteKey(subkey1, sb->s); - strbuf_free(sb); - - RegCloseKey(subkey1); - - remove_session_from_jumplist(sessionname); -} - -struct settings_e { - HKEY key; - int i; -}; - -settings_e *enum_settings_start(void) -{ - settings_e *ret; - HKEY key; - - if (RegOpenKey(HKEY_CURRENT_USER, puttystr, &key) != ERROR_SUCCESS) - return NULL; - - ret = snew(settings_e); - if (ret) { - ret->key = key; - ret->i = 0; - } - - return ret; -} - -bool enum_settings_next(settings_e *e, strbuf *sb) -{ - size_t regbuf_size = MAX_PATH + 1; - char *regbuf = snewn(regbuf_size, char); - bool success; - - while (1) { - DWORD retd = RegEnumKey(e->key, e->i, regbuf, regbuf_size); - if (retd != ERROR_MORE_DATA) { - success = (retd == ERROR_SUCCESS); - break; - } - sgrowarray(regbuf, regbuf_size, regbuf_size); - } - - if (success) - unescape_registry_key(regbuf, sb); - - e->i++; - sfree(regbuf); - return success; -} - -void enum_settings_finish(settings_e *e) -{ - RegCloseKey(e->key); - sfree(e); -} - -static void hostkey_regname(strbuf *sb, const char *hostname, - int port, const char *keytype) -{ - strbuf_catf(sb, "%s@%d:", keytype, port); - escape_registry_key(hostname, sb); -} - -int verify_host_key(const char *hostname, int port, - const char *keytype, const char *key) -{ - char *otherstr; - strbuf *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. - */ - regname = strbuf_new(); - hostkey_regname(regname, hostname, port, keytype); - - if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_POS "\\SshHostKeys", - &rkey) != ERROR_SUCCESS) { - strbuf_free(regname); - return 1; /* key does not exist in registry */ - } - - readlen = len; - otherstr = snewn(len, char); - ret = RegQueryValueEx(rkey, regname->s, NULL, - &type, (BYTE *)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->s + 1 + strcspn(regname->s, ":"); - char *oldstyle = snewn(len + 10, char); /* safety margin */ - readlen = len; - ret = RegQueryValueEx(rkey, justhost, NULL, &type, - (BYTE *)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->s, 0, REG_SZ, (BYTE *)otherstr, - strlen(otherstr) + 1); - } - - sfree(oldstyle); - } - - RegCloseKey(rkey); - - compare = strcmp(otherstr, key); - - sfree(otherstr); - strbuf_free(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 */ -} - -bool have_ssh_host_key(const char *hostname, int port, - const char *keytype) -{ - /* - * If we have a host key, verify_host_key will return 0 or 2. - * If we don't have one, it'll return 1. - */ - return verify_host_key(hostname, port, keytype, "") != 1; -} - -void store_host_key(const char *hostname, int port, - const char *keytype, const char *key) -{ - strbuf *regname; - HKEY rkey; - - regname = strbuf_new(); - hostkey_regname(regname, hostname, port, keytype); - - if (RegCreateKey(HKEY_CURRENT_USER, PUTTY_REG_POS "\\SshHostKeys", - &rkey) == ERROR_SUCCESS) { - RegSetValueEx(rkey, regname->s, 0, REG_SZ, - (BYTE *)key, strlen(key) + 1); - RegCloseKey(rkey); - } /* else key does not exist in registry */ - - strbuf_free(regname); -} - -/* - * Open (or delete) the random seed file. - */ -enum { DEL, OPEN_R, OPEN_W }; -static bool try_random_seed(char const *path, int action, HANDLE *ret) -{ - if (action == DEL) { - if (!DeleteFile(path) && GetLastError() != ERROR_FILE_NOT_FOUND) { - nonfatal("Unable to delete '%s': %s", path, - win_strerror(GetLastError())); - } - *ret = INVALID_HANDLE_VALUE; - return false; /* so we'll do the next ones too */ - } - - *ret = CreateFile(path, - 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 bool try_random_seed_and_free(char *path, int action, HANDLE *hout) -{ - bool retd = try_random_seed(path, action, hout); - sfree(path); - return retd; -} - -static HANDLE access_random_seed(int action) -{ - HKEY rkey; - HANDLE rethandle; - - /* - * 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. - */ - { - char regpath[MAX_PATH + 1]; - DWORD type, size = sizeof(regpath); - if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_POS, &rkey) == - ERROR_SUCCESS) { - int ret = RegQueryValueEx(rkey, "RandSeedFile", - 0, &type, (BYTE *)regpath, &size); - RegCloseKey(rkey); - if (ret == ERROR_SUCCESS && type == REG_SZ && - try_random_seed(regpath, 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) { - char profile[MAX_PATH + 1]; - if (SUCCEEDED(p_SHGetFolderPathA(NULL, CSIDL_LOCAL_APPDATA, - NULL, SHGFP_TYPE_CURRENT, profile)) && - try_random_seed_and_free(dupcat(profile, "\\PUTTY.RND"), - action, &rethandle)) - return rethandle; - - if (SUCCEEDED(p_SHGetFolderPathA(NULL, CSIDL_APPDATA, - NULL, SHGFP_TYPE_CURRENT, profile)) && - try_random_seed_and_free(dupcat(profile, "\\PUTTY.RND"), - action, &rethandle)) - return rethandle; - } - - /* - * Failing that, try %HOMEDRIVE%%HOMEPATH% as a guess at the - * user's home directory. - */ - { - char drv[MAX_PATH], path[MAX_PATH]; - - DWORD drvlen = GetEnvironmentVariable("HOMEDRIVE", drv, sizeof(drv)); - DWORD pathlen = GetEnvironmentVariable("HOMEPATH", path, sizeof(path)); - - /* We permit %HOMEDRIVE% to expand to an empty string, but if - * %HOMEPATH% does that, we abort the attempt. Same if either - * variable overflows its buffer. */ - if (drvlen == 0) - drv[0] = '\0'; - - if (drvlen < lenof(drv) && pathlen < lenof(path) && pathlen > 0 && - try_random_seed_and_free( - dupcat(drv, path, "\\PUTTY.RND"), action, &rethandle)) - return rethandle; - } - - /* - * And finally, fall back to C:\WINDOWS. - */ - { - char windir[MAX_PATH]; - DWORD len = GetWindowsDirectory(windir, sizeof(windir)); - if (len < lenof(windir) && - try_random_seed_and_free( - dupcat(windir, "\\PUTTY.RND"), 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; - DWORD type; - DWORD 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, - (BYTE *)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, - (BYTE *)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. */ - settings_r *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, - (BYTE *)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 && ret == ERROR_SUCCESS) - *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) != JUMPLISTREG_OK) { - 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. - */ -} diff --git a/windows/winucs.c b/windows/winucs.c deleted file mode 100644 index 9ffff5e9..00000000 --- a/windows/winucs.c +++ /dev/null @@ -1,1213 +0,0 @@ -#include -#include -#include -#include -#include - -#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 -- 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[] = { - {"UTF-8", CP_UTF8}, - - {"ISO-8859-1:1998 (Latin-1, West Europe)", 0, 96, iso_8859_1}, - {"ISO-8859-2:1999 (Latin-2, East Europe)", 0, 96, iso_8859_2}, - {"ISO-8859-3:1999 (Latin-3, South Europe)", 0, 96, iso_8859_3}, - {"ISO-8859-4:1998 (Latin-4, North Europe)", 0, 96, iso_8859_4}, - {"ISO-8859-5:1999 (Latin/Cyrillic)", 0, 96, iso_8859_5}, - {"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}, - - {"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}, - {"CP852", 852}, - {"CP878", 20866}, - - {"Use font encoding", -1}, - - {0, 0} -}; - -static void link_font(WCHAR * line_tbl, WCHAR * font_tbl, WCHAR attr); - -void init_ucs(Conf *conf, struct unicode_data *ucsdata) -{ - int i, j; - bool used_dtf = false; - int vtmode; - - /* Decide on the Line and Font codepages */ - ucsdata->line_codepage = decode_codepage(conf_get_str(conf, - CONF_line_codepage)); - - if (ucsdata->font_codepage <= 0) { - ucsdata->font_codepage=0; - ucsdata->dbcs_screenfont=false; - } - - vtmode = conf_get_int(conf, CONF_vtmode); - if (vtmode == VT_OEMONLY) { - ucsdata->font_codepage = 437; - ucsdata->dbcs_screenfont = false; - 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 (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 (vtmode == VT_OEMANSI || vtmode == VT_XWINDOWS) - memcpy(ucsdata->unitab_scoacs, ucsdata->unitab_oemcp, - sizeof(ucsdata->unitab_scoacs)); - else - get_unitab(437, ucsdata->unitab_scoacs, 1); - - /* Collect line set ucs table */ - if (ucsdata->line_codepage == ucsdata->font_codepage && - (ucsdata->dbcs_screenfont || - vtmode == VT_POORMAN || ucsdata->font_codepage==0)) { - - /* For DBCS and POOR fonts force direct to font */ - used_dtf = true; - 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 (vtmode == VT_OEMANSI || vtmode == VT_XWINDOWS) - link_font(ucsdata->unitab_scoacs, ucsdata->unitab_oemcp, CSET_OEMCP); - - link_font(ucsdata->unitab_line, ucsdata->unitab_font, CSET_ACP); - link_font(ucsdata->unitab_scoacs, ucsdata->unitab_font, CSET_ACP); - link_font(ucsdata->unitab_xterm, ucsdata->unitab_font, CSET_ACP); - - if (vtmode == VT_OEMANSI || vtmode == VT_XWINDOWS) { - link_font(ucsdata->unitab_line, ucsdata->unitab_oemcp, CSET_OEMCP); - link_font(ucsdata->unitab_xterm, ucsdata->unitab_oemcp, CSET_OEMCP); - } - - if (ucsdata->dbcs_screenfont && - 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 (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 || !*cp_name) - return CP_UTF8; /* default */ - - for (cpi = cp_list; cpi->name; cpi++) { - s = cp_name; - d = cpi->name; - for (;;) { - while (*s && !isalnum(*s) && *s != ':') - s++; - while (*d && !isalnum(*d) && *d != ':') - d++; - if (*s == 0) { - codepage = cpi->codepage; - if (codepage == CP_UTF8) - goto break_break; - if (codepage == -1) - return codepage; - if (codepage == 0) { - codepage = 65536 + (cpi - cp_list); - goto break_break; - } - - if (GetCPInfo(codepage, &cpinfo) != 0) - goto break_break; - } - if (tolower((unsigned char)*s++) != tolower((unsigned char)*d++)) - break; - } - } - - d = cp_name; - if (tolower((unsigned char)d[0]) == 'c' && - tolower((unsigned char)d[1]) == 'p') - d += 2; - if (tolower((unsigned char)d[0]) == 'i' && - tolower((unsigned char)d[1]) == 'b' && - tolower((unsigned char)d[2]) == 'm') - d += 3; - for (s = d; *s >= '0' && *s <= '9'; s++); - if (*s == 0 && s != d) - codepage = atoi(d); /* CP999 or IBM999 */ - - if (codepage == CP_ACP) - codepage = GetACP(); - if (codepage == CP_OEMCP) - codepage = GetOEMCP(); - if (codepage > 65535) - codepage = -2; - - break_break:; - if (codepage != -1) { - if (codepage != CP_UTF8 && codepage < 65536) { - if (GetCPInfo(codepage, &cpinfo) == 0) { - 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, const wchar_t *wcstr, int wclen, - char *mbstr, int mblen, const char *defchr, - 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; - - #define WRITECH(chr) do \ - { \ - assert(p - mbstr < mblen); \ - *p++ = (char)(chr); \ - } while (0) - - if (ucsdata->uni_tbl && - (p1 = ucsdata->uni_tbl[(ch >> 8) & 0xFF]) != NULL && - (by = p1[ch & 0xFF]) != '\0') - WRITECH(by); - else if (ch < 0x80) - WRITECH(ch); - else if (defchr) - for (const char *q = defchr; *q; q++) - WRITECH(*q); -#if 1 - else - WRITECH('.'); -#endif - - #undef WRITECH - } - return p - mbstr; - } else { - int defused; - return WideCharToMultiByte(codepage, flags, wcstr, wclen, - mbstr, mblen, defchr, &defused); - } -} - -int mb_to_wc(int codepage, int flags, const char *mbstr, int mblen, - wchar_t *wcstr, int wclen) -{ - return MultiByteToWideChar(codepage, flags, mbstr, mblen, wcstr, wclen); -} - -bool is_dbcs_leadbyte(int codepage, char byte) -{ - return IsDBCSLeadByteEx(codepage, byte); -} diff --git a/windows/winx11.c b/windows/winx11.c deleted file mode 100644 index 800d8509..00000000 --- a/windows/winx11.c +++ /dev/null @@ -1,19 +0,0 @@ -/* - * winx11.c: fetch local auth data for X forwarding. - */ - -#include -#include -#include - -#include "putty.h" -#include "ssh.h" - -void platform_get_x11_auth(struct X11Display *disp, Conf *conf) -{ - char *xauthpath = conf_get_filename(conf, CONF_xauthfile)->path; - if (xauthpath[0]) - x11_get_auth_from_authfile(disp, xauthpath); -} - -const bool platform_uses_x11_unix_by_default = false; diff --git a/windows/x11.c b/windows/x11.c new file mode 100644 index 00000000..800d8509 --- /dev/null +++ b/windows/x11.c @@ -0,0 +1,19 @@ +/* + * winx11.c: fetch local auth data for X forwarding. + */ + +#include +#include +#include + +#include "putty.h" +#include "ssh.h" + +void platform_get_x11_auth(struct X11Display *disp, Conf *conf) +{ + char *xauthpath = conf_get_filename(conf, CONF_xauthfile)->path; + if (xauthpath[0]) + x11_get_auth_from_authfile(disp, xauthpath); +} + +const bool platform_uses_x11_unix_by_default = false; -- cgit v1.2.3 From 77940f8fa3c7da491933e2efbacad3dec165b21f Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 23 Apr 2021 06:46:02 +0100 Subject: Move some add_executable() calls to top-level CMakeLists. Now that the main source file of Plink in each platform directory has the same name, we can put centralise the main definition of the program in the main CMakeLists.txt, and in the platform directory, just add the few extra modules needed to clear up platform-specific details. The same goes for psocks. And PSCP and PSFTP could have been moved to the top level already - I just hadn't done it in the initial setup. --- CMakeLists.txt | 37 ++++++++++++++++++++++++++++++++++- cmake/platforms/unix.cmake | 4 ++-- cmake/platforms/windows.cmake | 2 -- cmake/setup.cmake | 10 +++++++--- unix/CMakeLists.txt | 38 ++++-------------------------------- windows/CMakeLists.txt | 45 ++++--------------------------------------- 6 files changed, 53 insertions(+), 83 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4bf6eacd..9edbc40e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -75,7 +75,42 @@ add_executable(test_wildcard target_compile_definitions(test_wildcard PRIVATE TEST) target_link_libraries(test_wildcard utils ${platform_libraries}) -foreach(subdir ${PLATFORM_SUBDIRS}) +add_executable(plink + ${platform}/plink.c + be_all_s.c) +target_link_libraries(plink + eventloop noterminal console sshclient otherbackends settings network crypto + utils + ${platform_libraries}) +installed_program(plink) + +add_executable(pscp + pscp.c + be_ssh.c) +target_link_libraries(pscp + sftpclient eventloop console sshclient settings network crypto utils + ${platform_libraries}) +installed_program(pscp) + +add_executable(psftp + psftp.c + be_ssh.c) +target_link_libraries(psftp + sftpclient eventloop console sshclient settings network crypto utils + ${platform_libraries}) +installed_program(psftp) + +add_executable(psocks + ${platform}/psocks.c + psocks.c + norand.c + nocproxy.c + ssh/portfwd.c) +target_link_libraries(psocks + eventloop console network utils + ${platform_libraries}) + +foreach(subdir ${platform} ${extra_dirs}) add_subdirectory(${subdir}) endforeach() diff --git a/cmake/platforms/unix.cmake b/cmake/platforms/unix.cmake index c09d5d8f..080896a6 100644 --- a/cmake/platforms/unix.cmake +++ b/cmake/platforms/unix.cmake @@ -1,5 +1,3 @@ -set(PLATFORM_SUBDIRS charset unix) - set(PUTTY_GSSAPI DYNAMIC CACHE STRING "Build PuTTY with dynamically or statically linked \ Kerberos / GSSAPI support, if possible") @@ -80,6 +78,8 @@ add_optional_system_lib(m pow) add_optional_system_lib(rt clock_gettime) add_optional_system_lib(xnet socket) +set(extra_dirs charset) + if(PUTTY_GSSAPI STREQUAL DYNAMIC) add_optional_system_lib(dl dlopen) if(HAVE_NO_LIBdl) diff --git a/cmake/platforms/windows.cmake b/cmake/platforms/windows.cmake index c3f2b97f..931812f2 100644 --- a/cmake/platforms/windows.cmake +++ b/cmake/platforms/windows.cmake @@ -1,5 +1,3 @@ -set(PLATFORM_SUBDIRS windows) - set(PUTTY_MINEFIELD OFF CACHE BOOL "Build PuTTY with its built-in memory debugger 'Minefield'") set(PUTTY_GSSAPI ON diff --git a/cmake/setup.cmake b/cmake/setup.cmake index 4bd599a4..ffc23184 100644 --- a/cmake/setup.cmake +++ b/cmake/setup.cmake @@ -61,16 +61,20 @@ function(add_sources_from_current_dir target) target_sources(${target} PRIVATE ${sources}) endfunction() +set(extra_dirs) if(CMAKE_SYSTEM_NAME MATCHES "Windows" OR WINELIB) - include(cmake/platforms/windows.cmake) + set(platform windows) else() - include(cmake/platforms/unix.cmake) + set(platform unix) endif() +include(cmake/platforms/${platform}.cmake) + include_directories( ${CMAKE_CURRENT_SOURCE_DIR} ${GENERATED_SOURCES_DIR} - ${PLATFORM_SUBDIRS}) + ${platform} + ${extra_dirs}) if(PUTTY_DEBUG) add_compile_definitions(DEBUG) diff --git a/unix/CMakeLists.txt b/unix/CMakeLists.txt index 56786b94..f724bbe7 100644 --- a/unix/CMakeLists.txt +++ b/unix/CMakeLists.txt @@ -61,40 +61,10 @@ target_link_libraries(fuzzterm add_executable(osxlaunch osxlaunch.c) -add_executable(plink - plink.c - ${CMAKE_SOURCE_DIR}/be_all_s.c - no-gtk.c) -target_link_libraries(plink - eventloop noterminal console sshclient otherbackends settings network crypto - utils) -installed_program(plink) - -add_executable(pscp - ${CMAKE_SOURCE_DIR}/pscp.c - ${CMAKE_SOURCE_DIR}/be_ssh.c - no-gtk.c) -target_link_libraries(pscp - sftpclient eventloop console sshclient settings network crypto utils) -installed_program(pscp) - -add_executable(psftp - ${CMAKE_SOURCE_DIR}/psftp.c - ${CMAKE_SOURCE_DIR}/be_ssh.c - no-gtk.c) -target_link_libraries(psftp - sftpclient eventloop console sshclient settings network crypto utils) -installed_program(psftp) - -add_executable(psocks - psocks.c - ${CMAKE_SOURCE_DIR}/psocks.c - ${CMAKE_SOURCE_DIR}/norand.c - ${CMAKE_SOURCE_DIR}/nocproxy.c - ${CMAKE_SOURCE_DIR}/ssh/portfwd.c - no-gtk.c) -target_link_libraries(psocks - eventloop console network utils) +add_sources_from_current_dir(plink no-gtk.c) +add_sources_from_current_dir(pscp no-gtk.c) +add_sources_from_current_dir(psftp no-gtk.c) +add_sources_from_current_dir(psocks no-gtk.c) add_executable(psusan psusan.c diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt index 739b49f3..288827cb 100644 --- a/windows/CMakeLists.txt +++ b/windows/CMakeLists.txt @@ -68,53 +68,16 @@ set_target_properties(pageant PROPERTIES LINK_FLAGS "${LFLAG_MANIFEST_NO}") installed_program(pageant) -add_executable(plink - plink.c - ${CMAKE_SOURCE_DIR}/be_all_s.c - no-jump-list.c - nohelp.c - plink.rc) +add_sources_from_current_dir(plink no-jump-list.c nohelp.c plink.rc) add_dependencies(plink generated_licence_h) -target_link_libraries(plink - eventloop console noterminal sshclient otherbackends settings network crypto - utils - ${platform_libraries}) -installed_program(plink) -add_executable(pscp - ${CMAKE_SOURCE_DIR}/pscp.c - ${CMAKE_SOURCE_DIR}/be_ssh.c - no-jump-list.c - nohelp.c - pscp.rc) +add_sources_from_current_dir(pscp no-jump-list.c nohelp.c pscp.rc) add_dependencies(pscp generated_licence_h) -target_link_libraries(pscp - sftpclient eventloop console sshclient settings network crypto utils - ${platform_libraries}) -installed_program(pscp) -add_executable(psftp - ${CMAKE_SOURCE_DIR}/psftp.c - ${CMAKE_SOURCE_DIR}/be_ssh.c - no-jump-list.c - nohelp.c - psftp.rc) +add_sources_from_current_dir(psftp no-jump-list.c nohelp.c psftp.rc) add_dependencies(psftp generated_licence_h) -target_link_libraries(psftp - sftpclient eventloop console sshclient settings network crypto utils - ${platform_libraries}) -installed_program(psftp) -add_executable(psocks - psocks.c - nohelp.c - ${CMAKE_SOURCE_DIR}/psocks.c - ${CMAKE_SOURCE_DIR}/norand.c - ${CMAKE_SOURCE_DIR}/nocproxy.c - ${CMAKE_SOURCE_DIR}/ssh/portfwd.c) -target_link_libraries(psocks - eventloop console network utils - ${platform_libraries}) +add_sources_from_current_dir(psocks nohelp.c) add_executable(putty window.c -- cgit v1.2.3 From f5d1d4ce4b1f54476d9603215345cd43f1e465c7 Mon Sep 17 00:00:00 2001 From: Jacob Nevins Date: Sat, 1 May 2021 18:44:08 +0100 Subject: Docs: typo. --- doc/pubkey.but | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/pubkey.but b/doc/pubkey.but index cad6988b..f40c9526 100644 --- a/doc/pubkey.but +++ b/doc/pubkey.but @@ -380,7 +380,7 @@ doesn't support any of the following options to control that. All of the following options only affect keys saved with passphrases. They control how much work is required to decrypt the key (which -happens every type you type its passphrase). This allows you to trade +happens every time you type its passphrase). This allows you to trade off the cost of legitimate use of the key against the resistance of the encrypted key to password-guessing attacks. -- cgit v1.2.3 From de7c826fa380663c865cfd4dee3a5d43f05298b0 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 29 Apr 2021 20:05:24 +0100 Subject: Spelling errors in the release checklist. 'master' is now spelled 'main', and 'testsc' has _never_ been spelled 'sctest' (oops). --- CHECKLST.txt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/CHECKLST.txt b/CHECKLST.txt index 5e9a7a08..c3a5365a 100644 --- a/CHECKLST.txt +++ b/CHECKLST.txt @@ -8,7 +8,7 @@ When we begin to work towards a release and want to enable pre-releases on the website: - Make a branch whose tip will be the current state of the - pre-release. Regardless of whether the branch is from master or + pre-release. Regardless of whether the branch is from main or from a prior release branch, the name of the branch must now be in the form 'pre-X.YZ', or else the website will fail to link to it properly in gitweb and the build script will check out the wrong @@ -74,7 +74,7 @@ Making a release candidate build generated. - If the release is on a branch (which I expect it generally will - be), merge that branch to master. + be), merge that branch to main. - Make a release-candidate build from the release tag, and put the build.out and build.log files somewhere safe. Normally I store @@ -108,7 +108,7 @@ Making a release candidate build * make sure they basically work * check they report the right version number * if there's any easily observable behaviour difference between - the release branch and master, arrange to observe it + the release branch and main, arrange to observe it * test that the Windows installer installs successfully + on x86 and Arm, and test that putty.exe runs in both cases * test that the Unix source tarball unpacks and builds @@ -118,7 +118,7 @@ Making a release candidate build + test-build with -DNOT_X_WINDOWS * feed the release-candidate source to Coverity and make sure it didn't turn up any last-minute problems - * make sure we have a clean run of sctest + * make sure we have a clean run of testsc * do some testing on a system with a completely clean slate (no prior saved session data) @@ -195,10 +195,10 @@ locally, this is the procedure for putting it up on the web. * run 'git push' in the website checkout * run 'git push' in the wishlist checkout * push from the main PuTTY checkout. Typically this one will be - pushing both the release tag and an update to the master branch, + pushing both the release tag and an update to the main branch, plus removing the pre-release branch, so you'll want some commands along these lines: - git push origin master # update the master branch + git push origin main # update the main branch git push origin --tags # should push the new release tag git push origin :pre-X.YZ # delete the pre-release branch -- cgit v1.2.3 From 4a8fc43d81e97594691f6afcf5e9adefdcbec9d7 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 3 May 2021 15:08:41 +0100 Subject: Prepare gitcommit.cmake to support multiple output types. I'm about to want to embed the current git commit into a Halibut source file, for which I'll need to add a second output mode to the existing script that finds it out. --- cmake/gitcommit.cmake | 6 +++++- cmake/setup.cmake | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/cmake/gitcommit.cmake b/cmake/gitcommit.cmake index 76753aa7..2be90292 100644 --- a/cmake/gitcommit.cmake +++ b/cmake/gitcommit.cmake @@ -37,7 +37,8 @@ else() endif() endif() -file(WRITE "${OUTPUT_FILE}" "\ +if(OUTPUT_TYPE STREQUAL header) + file(WRITE "${OUTPUT_FILE}" "\ /* * cmake_commit.h - string literal giving the source git commit, if known. * @@ -46,3 +47,6 @@ file(WRITE "${OUTPUT_FILE}" "\ const char commitid[] = \"${commit}\"; ") +else() + message(FATAL_ERROR "Set OUTPUT_TYPE when running this script") +endif() diff --git a/cmake/setup.cmake b/cmake/setup.cmake index ffc23184..8b64acc8 100644 --- a/cmake/setup.cmake +++ b/cmake/setup.cmake @@ -44,6 +44,7 @@ add_custom_target(check_git_commit -DGIT_EXECUTABLE=${GIT_EXECUTABLE} -DTOPLEVEL_SOURCE_DIR=${CMAKE_SOURCE_DIR} -DOUTPUT_FILE=${INTERMEDIATE_COMMIT_C} + -DOUTPUT_TYPE=header -P ${CMAKE_SOURCE_DIR}/cmake/gitcommit.cmake DEPENDS ${CMAKE_SOURCE_DIR}/cmake/gitcommit.cmake WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} -- cgit v1.2.3 From f60853ec664f431dfa06ab091e49a3f4110ab141 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 3 May 2021 16:21:26 +0100 Subject: Configurable CHM path in installer source. At the moment, it assumes the CHM lives in ../doc, which won't always be true once we start doing out-of-tree builds of the documentation. --- windows/installer.wxs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/windows/installer.wxs b/windows/installer.wxs index b221320e..c7231b02 100644 --- a/windows/installer.wxs +++ b/windows/installer.wxs @@ -91,6 +91,10 @@ + + + + @@ -238,7 +242,7 @@ https://msdn.microsoft.com/en-us/library/windows/desktop/dd391569(v=vs.85).aspx + Source="$(var.HelpFilePath)" KeyPath="yes"> -- cgit v1.2.3 From 31f496b59cb92f1cf14ce4064d57e9dd8cc751db Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 2 May 2021 15:34:57 +0100 Subject: Integrate the 'doc' subdir into the CMake system. The standalone separate doc/Makefile is gone, replaced by a CMakeLists.txt that makes 'doc' function as a subdirectory of the main CMake build system. This auto-detects Halibut, and if it's present, uses it to build the man pages and the various forms of the main manual, including the Windows CHM help file in particular. One awkward thing I had to do was to move just one config directive in blurb.but into its own file: the one that cites a relative path to the stylesheet file to put into the CHM. CMake builds often like to be out-of-tree, so there's no longer a fixed relative path between the build directory and chm.css. And Halibut has no concept of an include path to search for files cited by other files, so I can't fix that with an -I option on the Halibut command line. So I moved that single config directive into its own file, and had CMake write out a custom version of that file in the build directory citing the right path. (Perhaps in the longer term I should fix that omission in Halibut; out-of-tree friendliness seems like a useful feature. But even if I do, I still need this build to work now.) --- Buildscr | 48 ++++++++++---------- CMakeLists.txt | 2 + cmake/gitcommit.cmake | 10 ++++ doc/CMakeLists.txt | 123 ++++++++++++++++++++++++++++++++++++++++++++++++++ doc/Makefile | 85 ---------------------------------- doc/blurb.but | 1 - doc/chmextra.but | 6 +++ mkunxarc.sh | 3 -- 8 files changed, 166 insertions(+), 112 deletions(-) create mode 100644 doc/CMakeLists.txt delete mode 100644 doc/Makefile create mode 100644 doc/chmextra.but diff --git a/Buildscr b/Buildscr index 7fa35fba..199ea4ec 100644 --- a/Buildscr +++ b/Buildscr @@ -55,7 +55,7 @@ ifneq "$(SNAPSHOT)" "" set Puttytextver PuTTY development snapshot $(Date).$(vcs ifneq "$(SNAPSHOT)" "" set Textver Development snapshot $(Date).$(vcsid) ifeq "$(RELEASE)$(PRERELEASE)$(SNAPSHOT)" "" set Puttytextver PuTTY custom build $(Date).$(vcsid) ifeq "$(RELEASE)$(PRERELEASE)$(SNAPSHOT)" "" set Textver Custom build $(Date).$(vcsid) -set Docmakever VERSION="$(Puttytextver)" +in putty/doc do echo "\\versionid $(Puttytextver)" > version.but # Set up the version string for use in the SSH connection greeting. # @@ -122,9 +122,13 @@ in putty do echo '$#define BINARY_VERSION $(Winvercommas)' >> version.h # the source archive will still get a useful value. in putty do sed -i '/set(DEFAULT_COMMIT/s/unavailable/$(vcsfullid)/' cmake/gitcommit.cmake +in . do mkdir docbuild +in docbuild do cmake ../putty +in docbuild do make -j$(nproc) VERBOSE=1 doc +in putty/doc do cp ../../docbuild/doc/*.1 . + in putty do ./mksrcarc.sh -in putty do ./mkunxarc.sh '$(Uxarcsuffix)' $(Docmakever) -in putty/doc do make $(Docmakever) putty.chm -j$(nproc) +in putty do ./mkunxarc.sh '$(Uxarcsuffix)' delegate - # Run the test suite, under self-delegation so that we don't leave any @@ -194,10 +198,10 @@ ifneq "$(cross_winsigncode)" "" in putty/windows do $(cross_winsigncode) -N -i h in putty do for hash in md5 sha1 sha256 sha512; do for dir_plat in "build32 w32" "build64 w64" "abuild32 wa32" "abuild64 wa64"; do set -- $$dir_plat; (cd windows/$$1 && $${hash}sum *.exe | sed 's!\( \+\)!\1'$$2'/!;s!$$! (installer version)!') >> $${hash}sums.installer; done; done # Build a WiX MSI installer, for each build flavour. -in putty/windows with wixonlinux do candle -arch x86 -dRealPlatform=x86 -dDllOk=yes -dBuilddir=build32/ -dWinver="$(Winver)" -dPuttytextver="$(Puttytextver)" installer.wxs && light -ext WixUIExtension -ext WixUtilExtension -sval installer.wixobj -o installer32.msi -spdb -in putty/windows with wixonlinux do candle -arch x64 -dRealPlatform=x64 -dDllOk=yes -dBuilddir=build64/ -dWinver="$(Winver)" -dPuttytextver="$(Puttytextver)" installer.wxs && light -ext WixUIExtension -ext WixUtilExtension -sval installer.wixobj -o installer64.msi -spdb -in putty/windows with wixonlinux do candle -arch x64 -dRealPlatform=Arm -dDllOk=no -dBuilddir=abuild32/ -dWinver="$(Winver)" -dPuttytextver="$(Puttytextver)" installer.wxs && light -ext WixUIExtension -ext WixUtilExtension -sval installer.wixobj -o installera32.msi -spdb -in putty/windows with wixonlinux do candle -arch x64 -dRealPlatform=Arm64 -dDllOk=no -dBuilddir=abuild64/ -dWinver="$(Winver)" -dPuttytextver="$(Puttytextver)" installer.wxs && light -ext WixUIExtension -ext WixUtilExtension -sval installer.wixobj -o installera64.msi -spdb +in putty/windows with wixonlinux do candle -arch x86 -dRealPlatform=x86 -dDllOk=yes -dBuilddir=build32/ -dWinver="$(Winver)" -dPuttytextver="$(Puttytextver)" -dHelpFilePath="../../docbuild/doc/putty.chm" installer.wxs && light -ext WixUIExtension -ext WixUtilExtension -sval installer.wixobj -o installer32.msi -spdb +in putty/windows with wixonlinux do candle -arch x64 -dRealPlatform=x64 -dDllOk=yes -dBuilddir=build64/ -dWinver="$(Winver)" -dPuttytextver="$(Puttytextver)" -dHelpFilePath="../../docbuild/doc/putty.chm" installer.wxs && light -ext WixUIExtension -ext WixUtilExtension -sval installer.wixobj -o installer64.msi -spdb +in putty/windows with wixonlinux do candle -arch x64 -dRealPlatform=Arm -dDllOk=no -dBuilddir=abuild32/ -dWinver="$(Winver)" -dPuttytextver="$(Puttytextver)" -dHelpFilePath="../../docbuild/doc/putty.chm" installer.wxs && light -ext WixUIExtension -ext WixUtilExtension -sval installer.wixobj -o installera32.msi -spdb +in putty/windows with wixonlinux do candle -arch x64 -dRealPlatform=Arm64 -dDllOk=no -dBuilddir=abuild64/ -dWinver="$(Winver)" -dPuttytextver="$(Puttytextver)" -dHelpFilePath="../../docbuild/doc/putty.chm" installer.wxs && light -ext WixUIExtension -ext WixUtilExtension -sval installer.wixobj -o installera64.msi -spdb # Change the width field for our dialog background image so that it # doesn't stretch across the whole dialog. (WiX's default one does; we @@ -219,13 +223,13 @@ ifneq "$(cross_winsigncode)" "" in putty/windows do $(cross_winsigncode) -i http # Build the standalone binaries, in both 32- and 64-bit flavours. # These differ from the previous set in that they embed the help file. -in putty/windows/build32 with cmake_at_least_3.20 do cmake . -DPUTTY_EMBEDDED_CHM_FILE=$$(realpath ../../doc/putty.chm) +in putty/windows/build32 with cmake_at_least_3.20 do cmake . -DPUTTY_EMBEDDED_CHM_FILE=$$(realpath ../../../docbuild/doc/putty.chm) in putty/windows/build32 with cmake_at_least_3.20 do make -j$(nproc) VERBOSE=1 -in putty/windows/build64 with cmake_at_least_3.20 do cmake . -DPUTTY_EMBEDDED_CHM_FILE=$$(realpath ../../doc/putty.chm) +in putty/windows/build64 with cmake_at_least_3.20 do cmake . -DPUTTY_EMBEDDED_CHM_FILE=$$(realpath ../../../docbuild/doc/putty.chm) in putty/windows/build64 with cmake_at_least_3.20 do make -j$(nproc) VERBOSE=1 -in putty/windows/abuild32 with cmake_at_least_3.20 do cmake . -DPUTTY_EMBEDDED_CHM_FILE=$$(realpath ../../doc/putty.chm) +in putty/windows/abuild32 with cmake_at_least_3.20 do cmake . -DPUTTY_EMBEDDED_CHM_FILE=$$(realpath ../../../docbuild/doc/putty.chm) in putty/windows/abuild32 with cmake_at_least_3.20 do make -j$(nproc) VERBOSE=1 -in putty/windows/abuild64 with cmake_at_least_3.20 do cmake . -DPUTTY_EMBEDDED_CHM_FILE=$$(realpath ../../doc/putty.chm) +in putty/windows/abuild64 with cmake_at_least_3.20 do cmake . -DPUTTY_EMBEDDED_CHM_FILE=$$(realpath ../../../docbuild/doc/putty.chm) in putty/windows/abuild64 with cmake_at_least_3.20 do make -j$(nproc) VERBOSE=1 # Build the 'old' binaries, which should still run on all 32-bit @@ -253,14 +257,12 @@ in putty/windows do mkdir deliver in putty/windows do for subdir in build32 abuild32 build64 abuild64 buildold; do mkdir deliver/$$subdir; done in putty/windows do while read x; do mv $$x deliver/$$x; mv $$x.map deliver/$$x.map; done < to-sign.txt -in putty/doc do make mostlyclean -in putty/doc do make $(Docmakever) -j$(nproc) -in putty/windows/deliver/buildold do zip -k -j putty.zip `ls *.exe | grep -v puttytel` ../../doc/putty.chm -in putty/windows/deliver/build32 do zip -k -j putty.zip `ls *.exe | grep -v puttytel` ../../doc/putty.chm -in putty/windows/deliver/build64 do zip -k -j putty.zip `ls *.exe | grep -v puttytel` ../../doc/putty.chm -in putty/windows/deliver/abuild32 do zip -k -j putty.zip `ls *.exe | grep -v puttytel` ../../doc/putty.chm -in putty/windows/deliver/abuild64 do zip -k -j putty.zip `ls *.exe | grep -v puttytel` ../../doc/putty.chm -in putty/doc do zip puttydoc.zip *.html +in putty/windows/deliver/buildold do zip -k -j putty.zip `ls *.exe | grep -v puttytel` ../../../docbuild/doc/putty.chm +in putty/windows/deliver/build32 do zip -k -j putty.zip `ls *.exe | grep -v puttytel` ../../../docbuild/doc/putty.chm +in putty/windows/deliver/build64 do zip -k -j putty.zip `ls *.exe | grep -v puttytel` ../../../docbuild/doc/putty.chm +in putty/windows/deliver/abuild32 do zip -k -j putty.zip `ls *.exe | grep -v puttytel` ../../../docbuild/doc/putty.chm +in putty/windows/deliver/abuild64 do zip -k -j putty.zip `ls *.exe | grep -v puttytel` ../../../docbuild/doc/putty.chm +in docbuild/doc/html do zip puttydoc.zip *.html # Deliver the actual PuTTY release directory into a subdir `putty'. deliver putty/windows/deliver/buildold/*.exe putty/w32old/$@ @@ -277,10 +279,10 @@ deliver putty/windows/deliver/abuild32/*.exe putty/wa32/$@ deliver putty/windows/deliver/abuild32/putty.zip putty/wa32/$@ deliver putty/windows/deliver/abuild64/*.exe putty/wa64/$@ deliver putty/windows/deliver/abuild64/putty.zip putty/wa64/$@ -deliver putty/doc/puttydoc.zip putty/$@ -deliver putty/doc/putty.chm putty/$@ -deliver putty/doc/puttydoc.txt putty/$@ -deliver putty/doc/*.html putty/htmldoc/$@ +deliver docbuild/doc/html/puttydoc.zip putty/$@ +deliver docbuild/doc/putty.chm putty/$@ +deliver docbuild/doc/puttydoc.txt putty/$@ +deliver docbuild/doc/html/*.html putty/htmldoc/$@ deliver putty/putty-src.zip putty/$@ deliver putty/*.tar.gz putty/$@ diff --git a/CMakeLists.txt b/CMakeLists.txt index 9edbc40e..24f7981e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,6 +3,8 @@ project(putty LANGUAGES C) include(cmake/setup.cmake) +add_subdirectory(doc) + add_compile_definitions(HAVE_CMAKE_H) add_library(utils STATIC diff --git a/cmake/gitcommit.cmake b/cmake/gitcommit.cmake index 2be90292..30d1dab1 100644 --- a/cmake/gitcommit.cmake +++ b/cmake/gitcommit.cmake @@ -47,6 +47,16 @@ if(OUTPUT_TYPE STREQUAL header) const char commitid[] = \"${commit}\"; ") +elseif(OUTPUT_TYPE STREQUAL halibut) + if(commit STREQUAL "unavailable") + file(WRITE "${OUTPUT_FILE}" "\ +\\versionid no version information available +") + else() + file(WRITE "${OUTPUT_FILE}" "\ +\\versionid built from git commit ${commit} +") + endif() else() message(FATAL_ERROR "Set OUTPUT_TYPE when running this script") endif() diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt new file mode 100644 index 00000000..5ebd2032 --- /dev/null +++ b/doc/CMakeLists.txt @@ -0,0 +1,123 @@ +include(FindPerl) +find_program(HALIBUT halibut) + +set(doc_outputs) +set(manpage_outputs) + +if(HALIBUT AND PERL_EXECUTABLE) + # Build the main manual, which requires not only Halibut, but also + # Perl to run licence.pl to generate the copyright and licence + # sections from the master data outside this directory. + + # If this is a source archive in which a fixed version.but was + # provided, use that. Otherwise, infer one from the git checkout (if + # possible). + if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/version.but) + set(VERSION_BUT ${CMAKE_CURRENT_SOURCE_DIR}/version.but) + else() + set(VERSION_BUT ${CMAKE_CURRENT_BINARY_DIR}/cmake_version.but) + set(INTERMEDIATE_VERSION_BUT ${VERSION_BUT}.tmp) + add_custom_target(check_git_commit_for_doc + BYPRODUCTS ${INTERMEDIATE_VERSION_BUT} + COMMAND ${CMAKE_COMMAND} + -DGIT_EXECUTABLE=${GIT_EXECUTABLE} + -DTOPLEVEL_SOURCE_DIR=${CMAKE_SOURCE_DIR} + -DOUTPUT_FILE=${INTERMEDIATE_VERSION_BUT} + -DOUTPUT_TYPE=halibut + -P ${CMAKE_SOURCE_DIR}/cmake/gitcommit.cmake + DEPENDS ${CMAKE_SOURCE_DIR}/cmake/gitcommit.cmake + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + COMMENT "Checking current git commit") + add_custom_target(cmake_version_but + BYPRODUCTS ${VERSION_BUT} + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${INTERMEDIATE_VERSION_BUT} ${VERSION_BUT} + DEPENDS check_git_commit_for_doc ${INTERMEDIATE_VERSION_BUT} + COMMENT "Updating cmake_version.but") + endif() + + add_custom_command(OUTPUT copy.but + COMMAND ${PERL_EXECUTABLE} ${CMAKE_SOURCE_DIR}/licence.pl + --copyrightdoc -o copy.but + DEPENDS ${CMAKE_SOURCE_DIR}/licence.pl ${CMAKE_SOURCE_DIR}/LICENCE) + add_custom_command(OUTPUT licence.but + COMMAND ${PERL_EXECUTABLE} ${CMAKE_SOURCE_DIR}/licence.pl + --licencedoc -o licence.but + DEPENDS ${CMAKE_SOURCE_DIR}/licence.pl ${CMAKE_SOURCE_DIR}/LICENCE) + + set(manual_sources + ${CMAKE_CURRENT_BINARY_DIR}/copy.but + ${CMAKE_CURRENT_SOURCE_DIR}/blurb.but + ${CMAKE_CURRENT_SOURCE_DIR}/intro.but + ${CMAKE_CURRENT_SOURCE_DIR}/gs.but + ${CMAKE_CURRENT_SOURCE_DIR}/using.but + ${CMAKE_CURRENT_SOURCE_DIR}/config.but + ${CMAKE_CURRENT_SOURCE_DIR}/pscp.but + ${CMAKE_CURRENT_SOURCE_DIR}/psftp.but + ${CMAKE_CURRENT_SOURCE_DIR}/plink.but + ${CMAKE_CURRENT_SOURCE_DIR}/pubkey.but + ${CMAKE_CURRENT_SOURCE_DIR}/pageant.but + ${CMAKE_CURRENT_SOURCE_DIR}/errors.but + ${CMAKE_CURRENT_SOURCE_DIR}/faq.but + ${CMAKE_CURRENT_SOURCE_DIR}/feedback.but + ${CMAKE_CURRENT_SOURCE_DIR}/pubkeyfmt.but + ${CMAKE_CURRENT_BINARY_DIR}/licence.but + ${CMAKE_CURRENT_SOURCE_DIR}/udp.but + ${CMAKE_CURRENT_SOURCE_DIR}/pgpkeys.but + ${CMAKE_CURRENT_SOURCE_DIR}/sshnames.but + ${CMAKE_CURRENT_SOURCE_DIR}/index.but + ${VERSION_BUT}) + + # The HTML manual goes in a subdirectory, for convenience. + set(html_dir ${CMAKE_CURRENT_BINARY_DIR}/html) + file(MAKE_DIRECTORY ${html_dir}) + add_custom_command(OUTPUT ${html_dir}/index.html + COMMAND ${HALIBUT} --html ${manual_sources} + WORKING_DIRECTORY ${html_dir} + DEPENDS ${manual_sources}) + list(APPEND doc_outputs ${html_dir}/index.html) + + # Windows help. + file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/chmextra.but + "\\cfg{chm-extra-file}{${CMAKE_CURRENT_SOURCE_DIR}/chm.css}{chm.css}\n") + add_custom_command(OUTPUT putty.chm + COMMAND ${HALIBUT} --chm chmextra.but ${manual_sources} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + DEPENDS ${manual_sources}) + list(APPEND doc_outputs putty.chm) + + # Plain text. + add_custom_command(OUTPUT puttydoc.txt + COMMAND ${HALIBUT} --text ${manual_sources} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + DEPENDS ${manual_sources}) + list(APPEND doc_outputs puttydoc.txt) +endif() + +macro(manpage title section) + if(HALIBUT) + add_custom_command(OUTPUT ${title}.${section} + COMMAND ${HALIBUT} --man=${title}.${section} + ${CMAKE_CURRENT_SOURCE_DIR}/mancfg.but + ${CMAKE_CURRENT_SOURCE_DIR}/man-${title}.but + DEPENDS + mancfg.but man-${title}.but) + list(APPEND manpage_outputs ${title}.${section}) + endif() +endmacro() + +manpage(putty 1) +manpage(puttygen 1) +manpage(plink 1) +manpage(pscp 1) +manpage(psftp 1) +manpage(puttytel 1) +manpage(pterm 1) +manpage(pageant 1) +manpage(psocks 1) +manpage(psusan 1) + +add_custom_target(manpages ALL + DEPENDS ${manpage_outputs}) +add_custom_target(doc + DEPENDS ${doc_outputs} manpages) diff --git a/doc/Makefile b/doc/Makefile deleted file mode 100644 index d04be776..00000000 --- a/doc/Makefile +++ /dev/null @@ -1,85 +0,0 @@ -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 gives 'version -# unavailable'. -# -# 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 - printf '\\versionid $(VERSION)\n' > vstr.but -FORCE:; -else -VERSIONIDS=vids -endif - -CHAPTERS := $(SITE) copy blurb intro gs using config pscp psftp plink -CHAPTERS += pubkey pageant errors faq feedback pubkeyfmt licence udp -CHAPTERS += 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 --chm $(INPUTS) - -# During formal builds it's useful to be able to build this one alone. -putty.chm: $(INPUTS) - $(HALIBUT) --chm $(INPUTS) - -# We don't ship this any more. -putty.hlp: $(INPUTS) - $(HALIBUT) --winhelp $(INPUTS) - -putty.info: $(INPUTS) - $(HALIBUT) --info $(INPUTS) - -licence.but: ../licence.pl ../LICENCE - perl $< --licencedoc -o $@ -copy.but: ../licence.pl ../LICENCE - perl $< --copyrightdoc -o $@ - -MKMAN = $(HALIBUT) --man=$@ mancfg.but $< -MANPAGES = putty.1 puttygen.1 plink.1 pscp.1 psftp.1 puttytel.1 pterm.1 \ - pageant.1 psocks.1 psusan.1 -man: $(MANPAGES) - -putty.1: man-putty.but mancfg.but; $(MKMAN) -puttygen.1: man-puttygen.but mancfg.but; $(MKMAN) -plink.1: man-plink.but mancfg.but; $(MKMAN) -pscp.1: man-pscp.but mancfg.but; $(MKMAN) -psftp.1: man-psftp.but mancfg.but; $(MKMAN) -puttytel.1: man-puttytel.but mancfg.but; $(MKMAN) -pterm.1: man-pterm.but mancfg.but; $(MKMAN) -pageant.1: man-pageant.but mancfg.but; $(MKMAN) -psocks.1: man-psocks.but mancfg.but; $(MKMAN) -psusan.1: man-psusan.but mancfg.but; $(MKMAN) - -mostlyclean: - rm -f *.html *.txt *.hlp *.cnt *.1 *.info vstr.but *.hh[pck] -clean: mostlyclean - rm -f *.chm diff --git a/doc/blurb.but b/doc/blurb.but index f980e9f1..c68a6262 100644 --- a/doc/blurb.but +++ b/doc/blurb.but @@ -17,7 +17,6 @@ page.

} \cfg{chm-contents-filename}{index.html} \cfg{chm-template-filename}{%k.html} \cfg{chm-head-end}{} -\cfg{chm-extra-file}{chm.css} \cfg{xhtml-contents-filename}{index.html} \cfg{text-filename}{puttydoc.txt} diff --git a/doc/chmextra.but b/doc/chmextra.but new file mode 100644 index 00000000..8b8780c9 --- /dev/null +++ b/doc/chmextra.but @@ -0,0 +1,6 @@ +\# If you want to do a Halibut build of the CHM file by hand, without +\# the help of the CMake edifice, then include this file which will +\# refer to chm.css. The CMake edifice builds its own with a different +\# pathname in it, for the sake of out-of-tree builds. + +\cfg{chm-extra-file}{chm.css} diff --git a/mkunxarc.sh b/mkunxarc.sh index 75790b65..cb79c054 100755 --- a/mkunxarc.sh +++ b/mkunxarc.sh @@ -7,9 +7,6 @@ # - the options to put on the 'make' command line for the docs arcsuffix="$1" -docver="$2" - -(cd doc && make -s ${docver:+"$docver"}) relver=`cat LATEST.VER` arcname="putty$arcsuffix" -- cgit v1.2.3 From e706c044511e7b4ca792475b8b4642b34059378c Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 3 May 2021 16:25:21 +0100 Subject: Add the man pages to the 'make install' target. doc/CMakeLists.txt now sets a variable indicating that we either have, or can build, each individual man page. And when we call our installed_program() function to mark a program as official enough to put in 'make install', that function also installs the man page similarly if it exists, and warns if not. For the convenience of people building-and-installing from the .tar.gz we ship, I've arranged that they can still get the man pages installed without needing Halibut: the previous commit ensured that the prebuilt man pages are still in the tarball, and this one arranges that if we don't have Halibut but we do have prebuilt man pages, then we can 'build' them by copying from the prebuilt versions. --- CMakeLists.txt | 2 ++ cmake/platforms/unix.cmake | 8 ++++++++ doc/CMakeLists.txt | 8 ++++++++ 3 files changed, 18 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 24f7981e..1680223d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,6 +3,8 @@ project(putty LANGUAGES C) include(cmake/setup.cmake) +# Scan the docs directory first, so that when we start calling +# installed_program(), we'll know if we have man pages available add_subdirectory(doc) add_compile_definitions(HAVE_CMAKE_H) diff --git a/cmake/platforms/unix.cmake b/cmake/platforms/unix.cmake index 080896a6..c586577d 100644 --- a/cmake/platforms/unix.cmake +++ b/cmake/platforms/unix.cmake @@ -8,6 +8,7 @@ include(CheckIncludeFile) include(CheckLibraryExists) include(CheckSymbolExists) include(CheckCSourceCompiles) +include(GNUInstallDirs) set(CMAKE_REQUIRED_DEFINITIONS ${CMAKE_REQUIRED_DEFINITIONS} -D_DEFAULT_SOURCE -D_GNU_SOURCE) @@ -120,4 +121,11 @@ function(installed_program target) # CMAKE_INSTALL_BINDIR. install(TARGETS ${target}) endif() + + if(HAVE_MANPAGE_${target}_1) + install(FILES ${CMAKE_BINARY_DIR}/doc/${target}.1 + DESTINATION ${CMAKE_INSTALL_MANDIR}/man1) + else() + message(WARNING "Could not build man page ${target}.1") + endif() endfunction() diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index 5ebd2032..8404b994 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -103,6 +103,14 @@ macro(manpage title section) DEPENDS mancfg.but man-${title}.but) list(APPEND manpage_outputs ${title}.${section}) + set(HAVE_MANPAGE_${title}_${section} ON PARENT_SCOPE) + elseif(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${title}.${section}) + add_custom_command(OUTPUT ${title}.${section} + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${CMAKE_CURRENT_SOURCE_DIR}/${title}.${section} ${title}.${section} + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${title}.${section}) + list(APPEND manpage_outputs ${title}.${section}) + set(HAVE_MANPAGE_${title}_${section} ON PARENT_SCOPE) endif() endmacro() -- cgit v1.2.3 From c037aef285f052cec31a0c346d8ab1fa9fc171a3 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 3 May 2021 16:42:03 +0100 Subject: Better detection of NOT_X_WINDOWS. When building against the Mac Homebrew installation of GTK, you find that GTK exists, libX11 exists, but the integration between the two (in the form of the header file gdk/gdkx.h) doesn't exist. In that situation, we need to compile out X11 support. --- cmake/platforms/unix.cmake | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/cmake/platforms/unix.cmake b/cmake/platforms/unix.cmake index c586577d..156919e7 100644 --- a/cmake/platforms/unix.cmake +++ b/cmake/platforms/unix.cmake @@ -57,12 +57,21 @@ endif() include(cmake/gtk.cmake) +# See if we have X11 available. This requires libX11 itself, and also +# the GDK integration to X11. find_package(X11) -if(NOT X11_FOUND) - set(NOT_X_WINDOWS ON) -else() - set(NOT_X_WINDOWS OFF) -endif() + +function(check_x11) + list(APPEND CMAKE_REQUIRED_INCLUDES ${GTK_INCLUDE_DIRS}) + check_include_file(gdk/gdkx.h HAVE_GDK_GDKX_H) + + if(X11_FOUND AND HAVE_GDK_GDKX_H) + set(NOT_X_WINDOWS OFF PARENT_SCOPE) + else() + set(NOT_X_WINDOWS ON PARENT_SCOPE) + endif() +endfunction() +check_x11() include_directories(${CMAKE_SOURCE_DIR}/charset ${GTK_INCLUDE_DIRS} ${X11_INCLUDE_DIR}) link_directories(${GTK_LIBRARY_DIRS}) -- cgit v1.2.3 From c931c7f02adfdeec1ff4826de142b2cef598eb15 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 8 May 2021 10:24:14 +0100 Subject: gitcommit.cmake: stop needing TOPLEVEL_SOURCE_DIR. It's always the same as the cwd when the script is invoked, and by having the script get it _from_ its own cwd, we arrange a bit of automatic normalisation in situations where you need to invoke it with some non-canonical path like one ending in "/.." - which I'll do in the next commit. --- cmake/gitcommit.cmake | 12 ++++++------ cmake/setup.cmake | 1 - doc/CMakeLists.txt | 1 - 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/cmake/gitcommit.cmake b/cmake/gitcommit.cmake index 30d1dab1..37358208 100644 --- a/cmake/gitcommit.cmake +++ b/cmake/gitcommit.cmake @@ -3,19 +3,19 @@ set(DEFAULT_COMMIT "unavailable") set(commit "${DEFAULT_COMMIT}") +set(TOPLEVEL_SOURCE_DIR ${CMAKE_SOURCE_DIR}) + execute_process( - COMMAND ${GIT_EXECUTABLE} -C ${TOPLEVEL_SOURCE_DIR} - rev-parse --show-toplevel + COMMAND ${GIT_EXECUTABLE} rev-parse --show-toplevel OUTPUT_VARIABLE git_worktree ERROR_VARIABLE stderr RESULT_VARIABLE status) string(REGEX REPLACE "\n$" "" git_worktree "${git_worktree}") if(status EQUAL 0) - if(git_worktree STREQUAL TOPLEVEL_SOURCE_DIR) + if(git_worktree STREQUAL CMAKE_SOURCE_DIR) execute_process( - COMMAND ${GIT_EXECUTABLE} -C ${TOPLEVEL_SOURCE_DIR} - rev-parse HEAD + COMMAND ${GIT_EXECUTABLE} rev-parse HEAD OUTPUT_VARIABLE git_commit ERROR_VARIABLE stderr RESULT_VARIABLE status) @@ -28,7 +28,7 @@ if(status EQUAL 0) endif() else() if(commit STREQUAL "unavailable") - message("Unable to determine git commit: top-level source dir ${TOPLEVEL_SOURCE_DIR} is not the root of a repository") + message("Unable to determine git commit: top-level source dir ${CMAKE_SOURCE_DIR} is not the root of a repository") endif() endif() else() diff --git a/cmake/setup.cmake b/cmake/setup.cmake index 8b64acc8..c0965c49 100644 --- a/cmake/setup.cmake +++ b/cmake/setup.cmake @@ -42,7 +42,6 @@ add_custom_target(check_git_commit BYPRODUCTS ${INTERMEDIATE_COMMIT_C} COMMAND ${CMAKE_COMMAND} -DGIT_EXECUTABLE=${GIT_EXECUTABLE} - -DTOPLEVEL_SOURCE_DIR=${CMAKE_SOURCE_DIR} -DOUTPUT_FILE=${INTERMEDIATE_COMMIT_C} -DOUTPUT_TYPE=header -P ${CMAKE_SOURCE_DIR}/cmake/gitcommit.cmake diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index 8404b994..f7fcb4e2 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -21,7 +21,6 @@ if(HALIBUT AND PERL_EXECUTABLE) BYPRODUCTS ${INTERMEDIATE_VERSION_BUT} COMMAND ${CMAKE_COMMAND} -DGIT_EXECUTABLE=${GIT_EXECUTABLE} - -DTOPLEVEL_SOURCE_DIR=${CMAKE_SOURCE_DIR} -DOUTPUT_FILE=${INTERMEDIATE_VERSION_BUT} -DOUTPUT_TYPE=halibut -P ${CMAKE_SOURCE_DIR}/cmake/gitcommit.cmake -- cgit v1.2.3 From d77ecacc27adf4c92348a2a3beda5d9e57866af9 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 8 May 2021 10:11:56 +0100 Subject: Allow standalone cmake in the doc subdirectory. It's silly to require all the time-consuming cmake configuration for the source code, if all you want to do is to build the documentation. My own website update script will like this optimisation, and so will Buildscr. In order to make doc/CMakeLists.txt work standalone, I had to add a 'project' header (citing no languages, so that cmake won't even bother looking for a C compiler); include FindGit, which cmake/setup.cmake now won't be doing for it; change all references to CMAKE_SOURCE_DIR to CMAKE_CURRENT_SOURCE_DIR/.. (since now the former will be defined differently in a nested or standalone doc build); and spot whether we're nested or not in order to conditionalise things designed to interoperate with the parent CMakeLists. --- Buildscr | 42 ++++++++++++++++++++-------------------- doc/CMakeLists.txt | 56 +++++++++++++++++++++++++++++++++++++++--------------- 2 files changed, 62 insertions(+), 36 deletions(-) diff --git a/Buildscr b/Buildscr index 24db5913..c143c9df 100644 --- a/Buildscr +++ b/Buildscr @@ -123,9 +123,9 @@ in putty do echo '$#define BINARY_VERSION $(Winvercommas)' >> version.h in putty do sed -i '/set(DEFAULT_COMMIT/s/unavailable/$(vcsfullid)/' cmake/gitcommit.cmake in . do mkdir docbuild -in docbuild do cmake ../putty -in docbuild do make -j$(nproc) VERBOSE=1 doc -in putty/doc do cp ../../docbuild/doc/*.1 . +in docbuild do cmake ../putty/doc +in docbuild do make -j$(nproc) VERBOSE=1 +in putty/doc do cp ../../docbuild/*.1 . in putty do ./mksrcarc.sh in putty do ./mkunxarc.sh '$(Uxarcsuffix)' @@ -198,10 +198,10 @@ ifneq "$(cross_winsigncode)" "" in putty/windows do $(cross_winsigncode) -N -i h in putty do for hash in md5 sha1 sha256 sha512; do for dir_plat in "build32 w32" "build64 w64" "abuild32 wa32" "abuild64 wa64"; do set -- $$dir_plat; (cd windows/$$1 && $${hash}sum *.exe | sed 's!\( \+\)!\1'$$2'/!;s!$$! (installer version)!') >> $${hash}sums.installer; done; done # Build a WiX MSI installer, for each build flavour. -in putty/windows with wixonlinux do candle -arch x86 -dRealPlatform=x86 -dDllOk=yes -dBuilddir=build32/ -dWinver="$(Winver)" -dPuttytextver="$(Puttytextver)" -dHelpFilePath="../../docbuild/doc/putty.chm" installer.wxs && light -ext WixUIExtension -ext WixUtilExtension -sval installer.wixobj -o installer32.msi -spdb -in putty/windows with wixonlinux do candle -arch x64 -dRealPlatform=x64 -dDllOk=yes -dBuilddir=build64/ -dWinver="$(Winver)" -dPuttytextver="$(Puttytextver)" -dHelpFilePath="../../docbuild/doc/putty.chm" installer.wxs && light -ext WixUIExtension -ext WixUtilExtension -sval installer.wixobj -o installer64.msi -spdb -in putty/windows with wixonlinux do candle -arch x64 -dRealPlatform=Arm -dDllOk=no -dBuilddir=abuild32/ -dWinver="$(Winver)" -dPuttytextver="$(Puttytextver)" -dHelpFilePath="../../docbuild/doc/putty.chm" installer.wxs && light -ext WixUIExtension -ext WixUtilExtension -sval installer.wixobj -o installera32.msi -spdb -in putty/windows with wixonlinux do candle -arch x64 -dRealPlatform=Arm64 -dDllOk=no -dBuilddir=abuild64/ -dWinver="$(Winver)" -dPuttytextver="$(Puttytextver)" -dHelpFilePath="../../docbuild/doc/putty.chm" installer.wxs && light -ext WixUIExtension -ext WixUtilExtension -sval installer.wixobj -o installera64.msi -spdb +in putty/windows with wixonlinux do candle -arch x86 -dRealPlatform=x86 -dDllOk=yes -dBuilddir=build32/ -dWinver="$(Winver)" -dPuttytextver="$(Puttytextver)" -dHelpFilePath="../../docbuild/putty.chm" installer.wxs && light -ext WixUIExtension -ext WixUtilExtension -sval installer.wixobj -o installer32.msi -spdb +in putty/windows with wixonlinux do candle -arch x64 -dRealPlatform=x64 -dDllOk=yes -dBuilddir=build64/ -dWinver="$(Winver)" -dPuttytextver="$(Puttytextver)" -dHelpFilePath="../../docbuild/putty.chm" installer.wxs && light -ext WixUIExtension -ext WixUtilExtension -sval installer.wixobj -o installer64.msi -spdb +in putty/windows with wixonlinux do candle -arch x64 -dRealPlatform=Arm -dDllOk=no -dBuilddir=abuild32/ -dWinver="$(Winver)" -dPuttytextver="$(Puttytextver)" -dHelpFilePath="../../docbuild/putty.chm" installer.wxs && light -ext WixUIExtension -ext WixUtilExtension -sval installer.wixobj -o installera32.msi -spdb +in putty/windows with wixonlinux do candle -arch x64 -dRealPlatform=Arm64 -dDllOk=no -dBuilddir=abuild64/ -dWinver="$(Winver)" -dPuttytextver="$(Puttytextver)" -dHelpFilePath="../../docbuild/putty.chm" installer.wxs && light -ext WixUIExtension -ext WixUtilExtension -sval installer.wixobj -o installera64.msi -spdb # Change the width field for our dialog background image so that it # doesn't stretch across the whole dialog. (WiX's default one does; we @@ -223,13 +223,13 @@ ifneq "$(cross_winsigncode)" "" in putty/windows do $(cross_winsigncode) -i http # Build the standalone binaries, in both 32- and 64-bit flavours. # These differ from the previous set in that they embed the help file. -in putty/windows/build32 with cmake_at_least_3.20 do cmake . -DPUTTY_EMBEDDED_CHM_FILE=$$(realpath ../../../docbuild/doc/putty.chm) +in putty/windows/build32 with cmake_at_least_3.20 do cmake . -DPUTTY_EMBEDDED_CHM_FILE=$$(realpath ../../../docbuild/putty.chm) in putty/windows/build32 with cmake_at_least_3.20 do make -j$(nproc) VERBOSE=1 -in putty/windows/build64 with cmake_at_least_3.20 do cmake . -DPUTTY_EMBEDDED_CHM_FILE=$$(realpath ../../../docbuild/doc/putty.chm) +in putty/windows/build64 with cmake_at_least_3.20 do cmake . -DPUTTY_EMBEDDED_CHM_FILE=$$(realpath ../../../docbuild/putty.chm) in putty/windows/build64 with cmake_at_least_3.20 do make -j$(nproc) VERBOSE=1 -in putty/windows/abuild32 with cmake_at_least_3.20 do cmake . -DPUTTY_EMBEDDED_CHM_FILE=$$(realpath ../../../docbuild/doc/putty.chm) +in putty/windows/abuild32 with cmake_at_least_3.20 do cmake . -DPUTTY_EMBEDDED_CHM_FILE=$$(realpath ../../../docbuild/putty.chm) in putty/windows/abuild32 with cmake_at_least_3.20 do make -j$(nproc) VERBOSE=1 -in putty/windows/abuild64 with cmake_at_least_3.20 do cmake . -DPUTTY_EMBEDDED_CHM_FILE=$$(realpath ../../../docbuild/doc/putty.chm) +in putty/windows/abuild64 with cmake_at_least_3.20 do cmake . -DPUTTY_EMBEDDED_CHM_FILE=$$(realpath ../../../docbuild/putty.chm) in putty/windows/abuild64 with cmake_at_least_3.20 do make -j$(nproc) VERBOSE=1 # Build the 'old' binaries, which should still run on all 32-bit @@ -257,12 +257,12 @@ in putty/windows do mkdir deliver in putty/windows do for subdir in build32 abuild32 build64 abuild64 buildold; do mkdir deliver/$$subdir; done in putty/windows do while read x; do mv $$x deliver/$$x; mv $$x.map deliver/$$x.map; done < to-sign.txt -in putty/windows/deliver/buildold do zip -k -j putty.zip `ls *.exe | grep -v puttytel` ../../../docbuild/doc/putty.chm -in putty/windows/deliver/build32 do zip -k -j putty.zip `ls *.exe | grep -v puttytel` ../../../docbuild/doc/putty.chm -in putty/windows/deliver/build64 do zip -k -j putty.zip `ls *.exe | grep -v puttytel` ../../../docbuild/doc/putty.chm -in putty/windows/deliver/abuild32 do zip -k -j putty.zip `ls *.exe | grep -v puttytel` ../../../docbuild/doc/putty.chm -in putty/windows/deliver/abuild64 do zip -k -j putty.zip `ls *.exe | grep -v puttytel` ../../../docbuild/doc/putty.chm -in docbuild/doc/html do zip puttydoc.zip *.html +in putty/windows/deliver/buildold do zip -k -j putty.zip `ls *.exe | grep -v puttytel` ../../../docbuild/putty.chm +in putty/windows/deliver/build32 do zip -k -j putty.zip `ls *.exe | grep -v puttytel` ../../../docbuild/putty.chm +in putty/windows/deliver/build64 do zip -k -j putty.zip `ls *.exe | grep -v puttytel` ../../../docbuild/putty.chm +in putty/windows/deliver/abuild32 do zip -k -j putty.zip `ls *.exe | grep -v puttytel` ../../../docbuild/putty.chm +in putty/windows/deliver/abuild64 do zip -k -j putty.zip `ls *.exe | grep -v puttytel` ../../../docbuild/putty.chm +in docbuild/html do zip puttydoc.zip *.html # Deliver the actual PuTTY release directory into a subdir `putty'. deliver putty/windows/deliver/buildold/*.exe putty/w32old/$@ @@ -279,10 +279,10 @@ deliver putty/windows/deliver/abuild32/*.exe putty/wa32/$@ deliver putty/windows/deliver/abuild32/putty.zip putty/wa32/$@ deliver putty/windows/deliver/abuild64/*.exe putty/wa64/$@ deliver putty/windows/deliver/abuild64/putty.zip putty/wa64/$@ -deliver docbuild/doc/html/puttydoc.zip putty/$@ -deliver docbuild/doc/putty.chm putty/$@ -deliver docbuild/doc/puttydoc.txt putty/$@ -deliver docbuild/doc/html/*.html putty/htmldoc/$@ +deliver docbuild/html/puttydoc.zip putty/$@ +deliver docbuild/putty.chm putty/$@ +deliver docbuild/puttydoc.txt putty/$@ +deliver docbuild/html/*.html putty/htmldoc/$@ deliver putty/putty-src.zip putty/$@ deliver putty/*.tar.gz putty/$@ diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index f7fcb4e2..eb273758 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -1,3 +1,13 @@ +cmake_minimum_required(VERSION 3.12) +project(putty-documentation LANGUAGES) + +# This build script can be run standalone, or included as a +# subdirectory of the main PuTTY cmake build system. If the latter, a +# couple of things change: it has to set variables telling the rest of +# the build system what manpages are available to be installed, and it +# will change whether the 'make doc' target is included in 'make all'. + +include(FindGit) include(FindPerl) find_program(HALIBUT halibut) @@ -23,9 +33,9 @@ if(HALIBUT AND PERL_EXECUTABLE) -DGIT_EXECUTABLE=${GIT_EXECUTABLE} -DOUTPUT_FILE=${INTERMEDIATE_VERSION_BUT} -DOUTPUT_TYPE=halibut - -P ${CMAKE_SOURCE_DIR}/cmake/gitcommit.cmake - DEPENDS ${CMAKE_SOURCE_DIR}/cmake/gitcommit.cmake - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + -P ${CMAKE_CURRENT_SOURCE_DIR}/../cmake/gitcommit.cmake + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/../cmake/gitcommit.cmake + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/.. COMMENT "Checking current git commit") add_custom_target(cmake_version_but BYPRODUCTS ${VERSION_BUT} @@ -36,13 +46,13 @@ if(HALIBUT AND PERL_EXECUTABLE) endif() add_custom_command(OUTPUT copy.but - COMMAND ${PERL_EXECUTABLE} ${CMAKE_SOURCE_DIR}/licence.pl + COMMAND ${PERL_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/../licence.pl --copyrightdoc -o copy.but - DEPENDS ${CMAKE_SOURCE_DIR}/licence.pl ${CMAKE_SOURCE_DIR}/LICENCE) + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/../licence.pl ${CMAKE_CURRENT_SOURCE_DIR}/../LICENCE) add_custom_command(OUTPUT licence.but - COMMAND ${PERL_EXECUTABLE} ${CMAKE_SOURCE_DIR}/licence.pl + COMMAND ${PERL_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/../licence.pl --licencedoc -o licence.but - DEPENDS ${CMAKE_SOURCE_DIR}/licence.pl ${CMAKE_SOURCE_DIR}/LICENCE) + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/../licence.pl ${CMAKE_CURRENT_SOURCE_DIR}/../LICENCE) set(manual_sources ${CMAKE_CURRENT_BINARY_DIR}/copy.but @@ -93,6 +103,14 @@ if(HALIBUT AND PERL_EXECUTABLE) list(APPEND doc_outputs puttydoc.txt) endif() +macro(register_manpage title section) + list(APPEND manpage_outputs ${title}.${section}) + if(NOT CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) + # Only set this variable if there _is_ a parent scope. + set(HAVE_MANPAGE_${title}_${section} ON PARENT_SCOPE) + endif() +endmacro() + macro(manpage title section) if(HALIBUT) add_custom_command(OUTPUT ${title}.${section} @@ -101,15 +119,13 @@ macro(manpage title section) ${CMAKE_CURRENT_SOURCE_DIR}/man-${title}.but DEPENDS mancfg.but man-${title}.but) - list(APPEND manpage_outputs ${title}.${section}) - set(HAVE_MANPAGE_${title}_${section} ON PARENT_SCOPE) + register_manpage(${title} ${section}) elseif(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${title}.${section}) add_custom_command(OUTPUT ${title}.${section} COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_SOURCE_DIR}/${title}.${section} ${title}.${section} DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${title}.${section}) - list(APPEND manpage_outputs ${title}.${section}) - set(HAVE_MANPAGE_${title}_${section} ON PARENT_SCOPE) + register_manpage(${title} ${section}) endif() endmacro() @@ -124,7 +140,17 @@ manpage(pageant 1) manpage(psocks 1) manpage(psusan 1) -add_custom_target(manpages ALL - DEPENDS ${manpage_outputs}) -add_custom_target(doc - DEPENDS ${doc_outputs} manpages) +add_custom_target(manpages ALL DEPENDS ${manpage_outputs}) +add_custom_target(doc DEPENDS ${doc_outputs} manpages) + +if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) + # If we're doing a cmake from just the doc subdir, we expect the + # user to want to make all the documentation, including HTML and so + # forth. (What else would be the point?) + # + # But if we're included from the main makefile, then by default we + # only make the man pages (which are necessary for 'make install'), + # and we leave everything else to a separate 'make doc' target which + # the user can invoke if they need to. + add_custom_target(doc-default ALL DEPENDS doc) +endif() -- cgit v1.2.3 From 3de2f13b89ce1f6671488c4b6c0b23f4b825531b Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 24 Apr 2021 17:15:47 +0100 Subject: Factor out Windows utility function get_system_dir(). The code to find out the location of the c:\windows\system32 directory was already present, in load_system32_dll(). Now it's moved out into a function of its own, so it can be called in other contexts. --- windows/CMakeLists.txt | 1 + windows/platform.h | 1 + windows/utils/get_system_dir.c | 21 +++++++++++++++++++++ windows/utils/load_system32_dll.c | 10 +--------- 4 files changed, 24 insertions(+), 9 deletions(-) create mode 100644 windows/utils/get_system_dir.c diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt index 288827cb..fb2b3028 100644 --- a/windows/CMakeLists.txt +++ b/windows/CMakeLists.txt @@ -10,6 +10,7 @@ add_sources_from_current_dir(utils utils/filename.c utils/fontspec.c utils/getdlgitemtext_alloc.c + utils/get_system_dir.c utils/get_username.c utils/is_console_handle.c utils/load_system32_dll.c diff --git a/windows/platform.h b/windows/platform.h index f84f4236..5c307cb0 100644 --- a/windows/platform.h +++ b/windows/platform.h @@ -566,6 +566,7 @@ HWND event_log_window(void); extern DWORD osMajorVersion, osMinorVersion, osPlatformId; void init_winver(void); void dll_hijacking_protection(void); +const char *get_system_dir(void); HMODULE load_system32_dll(const char *libname); const char *win_strerror(int error); void restrict_process_acl(void); diff --git a/windows/utils/get_system_dir.c b/windows/utils/get_system_dir.c new file mode 100644 index 00000000..049cd7fc --- /dev/null +++ b/windows/utils/get_system_dir.c @@ -0,0 +1,21 @@ +/* + * Wrapper function around GetSystemDirectory that deals with + * allocating the output buffer, and also caches the result for future + * calls. + */ + +#include "putty.h" + +const char *get_system_dir(void) +{ + static char *sysdir = NULL; + static size_t sysdirsize = 0; + + if (!sysdir) { + size_t len; + while ((len = GetSystemDirectory(sysdir, sysdirsize)) >= sysdirsize) + sgrowarray(sysdir, sysdirsize, len); + } + + return sysdir; +} diff --git a/windows/utils/load_system32_dll.c b/windows/utils/load_system32_dll.c index e00d7c34..d227a264 100644 --- a/windows/utils/load_system32_dll.c +++ b/windows/utils/load_system32_dll.c @@ -8,18 +8,10 @@ HMODULE load_system32_dll(const char *libname) { - static char *sysdir = NULL; - static size_t sysdirsize = 0; char *fullpath; HMODULE ret; - if (!sysdir) { - size_t len; - while ((len = GetSystemDirectory(sysdir, sysdirsize)) >= sysdirsize) - sgrowarray(sysdir, sysdirsize, len); - } - - fullpath = dupcat(sysdir, "\\", libname); + fullpath = dupcat(get_system_dir(), "\\", libname); ret = LoadLibrary(fullpath); sfree(fullpath); return ret; -- cgit v1.2.3 From 7167c8c77165211dd352549735a903318c78fde9 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 8 May 2021 17:20:50 +0100 Subject: Move some parts of window.c into putty.c. This prepares the ground for a second essentially similarly-shaped program reusing most of window.c but handling its command line and startup differently. A couple of large parts of WinMain() to do with backend selection and command-line handling are now subfunctions in a separate file putty.c. Also, our custom AppUserModelId is defined in that file, so that it can vary with the client application. --- windows/CMakeLists.txt | 2 + windows/jump-list.c | 3 +- windows/platform.h | 9 ++ windows/putty.c | 127 +++++++++++++++++++++++++ windows/window.c | 246 +++++++++++++++++-------------------------------- 5 files changed, 224 insertions(+), 163 deletions(-) create mode 100644 windows/putty.c diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt index fb2b3028..6165df27 100644 --- a/windows/CMakeLists.txt +++ b/windows/CMakeLists.txt @@ -82,6 +82,7 @@ add_sources_from_current_dir(psocks nohelp.c) add_executable(putty window.c + putty.c help.c ${CMAKE_SOURCE_DIR}/be_all_s.c putty.rc) @@ -97,6 +98,7 @@ installed_program(putty) add_executable(puttytel window.c + putty.c help.c ${CMAKE_SOURCE_DIR}/be_nos_s.c ${CMAKE_SOURCE_DIR}/nogss.c diff --git a/windows/jump-list.c b/windows/jump-list.c index 358504fd..0aacee8f 100644 --- a/windows/jump-list.c +++ b/windows/jump-list.c @@ -735,7 +735,8 @@ bool set_explicit_app_user_model_id(void) if (p_SetCurrentProcessExplicitAppUserModelID) { - if (p_SetCurrentProcessExplicitAppUserModelID(L"SimonTatham.PuTTY") == S_OK) + const wchar_t *id = get_app_user_model_id(); + if (p_SetCurrentProcessExplicitAppUserModelID(id) == S_OK) { return true; } diff --git a/windows/platform.h b/windows/platform.h index 5c307cb0..b746c76d 100644 --- a/windows/platform.h +++ b/windows/platform.h @@ -705,4 +705,13 @@ void cli_main_loop(cliloop_pre_t pre, cliloop_post_t post, void *ctx); bool cliloop_null_pre(void *vctx, const HANDLE **, size_t *); bool cliloop_null_post(void *vctx, size_t); +/* Functions that parametrise window.c */ +void gui_term_process_cmdline(Conf *conf, char *cmdline); +const struct BackendVtable *backend_vt_from_conf(Conf *conf); +const wchar_t *get_app_user_model_id(void); +/* And functions in window.c that those files call back to */ +char *handle_restrict_acl_cmdline_prefix(char *cmdline); +bool handle_special_sessionname_cmdline(char *cmdline, Conf *conf); +bool handle_special_filemapping_cmdline(char *cmdline, Conf *conf); + #endif /* PUTTY_WINDOWS_PLATFORM_H */ diff --git a/windows/putty.c b/windows/putty.c new file mode 100644 index 00000000..b17ad7dc --- /dev/null +++ b/windows/putty.c @@ -0,0 +1,127 @@ +#include "putty.h" +#include "storage.h" + +void gui_term_process_cmdline(Conf *conf, char *cmdline) +{ + char *p; + bool special_launchable_argument = false; + + settings_set_default_protocol(be_default_protocol); + /* Find the appropriate default port. */ + { + const struct BackendVtable *vt = + backend_vt_from_proto(be_default_protocol); + settings_set_default_port(0); /* illegal */ + if (vt) + settings_set_default_port(vt->default_port); + } + conf_set_int(conf, CONF_logtype, LGTYP_NONE); + + do_defaults(NULL, conf); + + p = handle_restrict_acl_cmdline_prefix(cmdline); + + if (handle_special_sessionname_cmdline(p, conf)) { + if (!conf_launchable(conf) && !do_config(conf)) { + cleanup_exit(0); + } + special_launchable_argument = true; + } else if (handle_special_filemapping_cmdline(p, conf)) { + special_launchable_argument = true; + } else if (!*p) { + /* Do-nothing case for an empty command line - or rather, + * for a command line that's empty _after_ we strip off + * the &R prefix. */ + } 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+1default_port); - } - conf_set_int(conf, CONF_logtype, LGTYP_NONE); - - do_defaults(NULL, conf); - - 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, plus the &R - * prefix for -restrict-acl, all of which are used by PuTTYs - * auto-launching each other via System-menu options. - */ - while (*p && isspace(*p)) - p++; - if (*p == '&' && p[1] == 'R' && - (!p[2] || p[2] == '@' || p[2] == '&')) { - /* &R restrict-acl prefix */ - restrict_process_acl(); - p += 2; - } - - 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, conf); - if (!conf_launchable(conf) && !do_config(conf)) { - cleanup_exit(0); - } - special_launchable_argument = true; - } 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 interpret as a - * serialised Conf. - */ - HANDLE filemap; - void *cp; - unsigned cpsize; - if (sscanf(p + 1, "%p:%u", &filemap, &cpsize) == 2 && - (cp = MapViewOfFile(filemap, FILE_MAP_READ, - 0, 0, cpsize)) != NULL) { - BinarySource src[1]; - BinarySource_BARE_INIT(src, cp, cpsize); - if (!conf_deserialise(conf, src)) - modalfatalbox("Serialised configuration data was invalid"); - UnmapViewOfFile(cp); - CloseHandle(filemap); - } else if (!do_config(conf)) { - cleanup_exit(0); - } - special_launchable_argument = true; - } else if (!*p) { - /* Do-nothing case for an empty command line - or rather, - * for a command line that's empty _after_ we strip off - * the &R prefix. */ - } 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 0 && + isspace(((unsigned char *)sessionname.ptr)[sessionname.len-1])) + sessionname.len--; + + char *dup = mkstr(sessionname); + bool loaded = do_defaults(dup, conf); + sfree(dup); + + return loaded; +} + +bool handle_special_filemapping_cmdline(char *p, Conf *conf) +{ + /* + * Process the special form of command line with an initial & + * followed by the hex value of a HANDLE for a file mapping object + * and the size of the data contained in it, which we must + * interpret as a serialised Conf. + * + * If successful, the whole command line has been interpreted in + * this way, so there's nothing left to parse into other arguments. + */ + + if (*p != '&') + return false; + + HANDLE filemap; + unsigned cpsize; + if (sscanf(p + 1, "%p:%u", &filemap, &cpsize) != 2) + return false; + + void *cp = MapViewOfFile(filemap, FILE_MAP_READ, 0, 0, cpsize); + if (!cp) + return false; + + BinarySource src[1]; + BinarySource_BARE_INIT(src, cp, cpsize); + if (!conf_deserialise(conf, src)) + modalfatalbox("Serialised configuration data was invalid"); + UnmapViewOfFile(cp); + CloseHandle(filemap); + return true; +} + static void setup_clipboards(Terminal *term, Conf *conf) { assert(term->mouse_select_clipboards[0] == CLIP_LOCAL); -- cgit v1.2.3 From 27a09093e4140872bac55ff6912978ac1d5dd334 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 8 May 2021 17:23:11 +0100 Subject: Move icon declarations out of putty-common.rc2. Now they're done by putty.rc and puttytel.rc, before including putty-common.rc2. So another user of putty-common.rc2 can disagree on what icons to use. --- windows/putty-common.rc2 | 6 ------ windows/putty.rc | 4 ++++ windows/puttytel.rc | 4 ++++ 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/windows/putty-common.rc2 b/windows/putty-common.rc2 index 79b79ea1..056a2837 100644 --- a/windows/putty-common.rc2 +++ b/windows/putty-common.rc2 @@ -13,12 +13,6 @@ * from it (which happens if the extension is '.h'). */ -#include "putty-rc.h" - -IDI_MAINICON ICON "putty.ico" - -IDI_CFGICON ICON "puttycfg.ico" - /* Accelerators used: clw */ IDD_ABOUTBOX DIALOG DISCARDABLE 140, 40, 270, 136 STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU diff --git a/windows/putty.rc b/windows/putty.rc index 887342c3..a7d07711 100644 --- a/windows/putty.rc +++ b/windows/putty.rc @@ -1,8 +1,12 @@ #include "rcstuff.h" +#include "putty-rc.h" #define APPNAME "PuTTY" #define APPDESC "SSH, Telnet, Rlogin, and SUPDUP client" +IDI_MAINICON ICON "putty.ico" +IDI_CFGICON ICON "puttycfg.ico" + #include "help.rc2" #include "putty-common.rc2" diff --git a/windows/puttytel.rc b/windows/puttytel.rc index 6f2bfaab..41767a10 100644 --- a/windows/puttytel.rc +++ b/windows/puttytel.rc @@ -1,8 +1,12 @@ #include "rcstuff.h" +#include "putty-rc.h" #define APPNAME "PuTTYtel" #define APPDESC "Telnet and Rlogin client" +IDI_MAINICON ICON "putty.ico" +IDI_CFGICON ICON "puttycfg.ico" + #include "help.rc2" #include "putty-common.rc2" -- cgit v1.2.3 From cb33708f959b2e6323f02227b58945aa5297ba72 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 8 May 2021 17:22:19 +0100 Subject: Make Windows versions of the pterm icons. icons/Makefile will now rebuild them, but also, as per this code base's usual policy with Windows icons, they're committed directly in the windows subdir. --- icons/Makefile | 10 +++++++++- windows/pterm.ico | Bin 0 -> 4078 bytes windows/ptermcfg.ico | Bin 0 -> 4078 bytes 3 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 windows/pterm.ico create mode 100644 windows/ptermcfg.ico diff --git a/icons/Makefile b/icons/Makefile index 3bdba19c..71b43874 100644 --- a/icons/Makefile +++ b/icons/Makefile @@ -14,7 +14,7 @@ MONOPNGS = $(patsubst %.pam,%.png,$(MONOPAMS)) TRUEPNGS = $(patsubst %.pam,%.png,$(TRUEPAMS)) ICOS = putty.ico puttygen.ico pscp.ico pageant.ico pageants.ico puttycfg.ico \ - puttyins.ico + puttyins.ico pterm.ico ptermcfg.ico ICNS = PuTTY.icns Pterm.icns CICONS = xpmputty.c xpmpucfg.c xpmpterm.c xpmptcfg.c @@ -69,6 +69,14 @@ 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, $^) > $@ +pterm.ico: pterm-16.png pterm-32.png pterm-48.png \ + pterm-16-mono.png pterm-32-mono.png pterm-48-mono.png + ./icon.pl -4 $(filter-out %-mono.png, $^) -1 $(filter %-mono.png, $^) > $@ + +ptermcfg.ico: ptermcfg-16.png ptermcfg-32.png ptermcfg-48.png \ + ptermcfg-16-mono.png ptermcfg-32-mono.png ptermcfg-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. diff --git a/windows/pterm.ico b/windows/pterm.ico new file mode 100644 index 00000000..6909a8d2 Binary files /dev/null and b/windows/pterm.ico differ diff --git a/windows/ptermcfg.ico b/windows/ptermcfg.ico new file mode 100644 index 00000000..53bde87e Binary files /dev/null and b/windows/ptermcfg.ico differ -- cgit v1.2.3 From a55aac71e4fa6cf30f976da6727877c4cb117c96 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 8 May 2021 17:24:13 +0100 Subject: New application: a Windows version of 'pterm'! This fulfills our long-standing Mayhem-difficulty wishlist item 'win-command-prompt': this is a Windows pterm in the sense that when you run it you get a local cmd.exe running inside a PuTTY-style window. Advantages of this: you get the same free choice of fonts as PuTTY has (no restriction to a strange subset of the system's available fonts); you get the same copy-paste gestures as PuTTY (no mental gear-shifting when you have command prompts and SSH sessions open on the same desktop); you get scrollback with the PuTTY semantics (scrolling to the bottom gets you to where the action is, as opposed to the way you could accidentally find yourself 500 lines past the end of the action in a real console). 'win-command-prompt' was at Mayhem difficulty ('Probably impossible') basically on the grounds that with Windows's old APIs for accessing the contents of consoles, there was no way I could find to get this to work sensibly. What was needed to make it feasible was a major piece of re-engineering work inside Windows itself. But, of course, that's exactly what happened! In 2019, the new ConPTY API arrived, which lets you create an object that behaves like a Windows console at one end, and round the back, emits a stream of VT-style escape sequences as the screen contents evolve, and accepts a VT-style input stream in return which it will parse function and arrow keys out of in the usual way. So now it's actually _easy_ to get this to basically work. The new backend, in conpty.c, has to do a handful of magic Windows API calls to set up the pseudo-console and its feeder pipes and start a subprocess running in it, a further magic call every time the PuTTY window is resized, and detect the end of the session by watching for the subprocess terminating. But apart from that, all it has to do is pass data back and forth unmodified between those pipes and the backend's associated Seat! That said, this is new and experimental, and there will undoubtedly be issues. One that I already know about is that you can't copy and paste a word that has wrapped between lines without getting an annoying newline in the middle of it. As far as I can see this is a fundamental limitation: the ConPTY system sends the _same_ escape sequence stream for a line that wrapped as it would send for a line that had a logical \n at what would have been the wrap point. Probably the best we can do to mitigate this is to adopt a different heuristic for newline elision that's right more often than it's wrong. For the moment, that experimental-ness is indicated by the fact that Buildscr will build, sign and deliver a copy of pterm.exe for each flavour of Windows, but won't include it in the .zip file or in the installer. (In fact, that puts it in exactly the same ad-hoc category as PuTTYtel, although for completely different reasons.) --- Buildscr | 10 +- cmake/platforms/windows.cmake | 1 + windows/CMakeLists.txt | 22 +++ windows/be_conpty.c | 13 ++ windows/conpty.c | 389 ++++++++++++++++++++++++++++++++++++++++++ windows/platform.h | 4 +- windows/pterm.c | 45 +++++ windows/pterm.rc | 15 ++ 8 files changed, 493 insertions(+), 6 deletions(-) create mode 100644 windows/be_conpty.c create mode 100644 windows/conpty.c create mode 100644 windows/pterm.c create mode 100644 windows/pterm.rc diff --git a/Buildscr b/Buildscr index c143c9df..a8252c31 100644 --- a/Buildscr +++ b/Buildscr @@ -257,11 +257,11 @@ in putty/windows do mkdir deliver in putty/windows do for subdir in build32 abuild32 build64 abuild64 buildold; do mkdir deliver/$$subdir; done in putty/windows do while read x; do mv $$x deliver/$$x; mv $$x.map deliver/$$x.map; done < to-sign.txt -in putty/windows/deliver/buildold do zip -k -j putty.zip `ls *.exe | grep -v puttytel` ../../../docbuild/putty.chm -in putty/windows/deliver/build32 do zip -k -j putty.zip `ls *.exe | grep -v puttytel` ../../../docbuild/putty.chm -in putty/windows/deliver/build64 do zip -k -j putty.zip `ls *.exe | grep -v puttytel` ../../../docbuild/putty.chm -in putty/windows/deliver/abuild32 do zip -k -j putty.zip `ls *.exe | grep -v puttytel` ../../../docbuild/putty.chm -in putty/windows/deliver/abuild64 do zip -k -j putty.zip `ls *.exe | grep -v puttytel` ../../../docbuild/putty.chm +in putty/windows/deliver/buildold do zip -k -j putty.zip `ls *.exe | grep -vxE '^(puttytel|pterm).exe'` ../../../docbuild/putty.chm +in putty/windows/deliver/build32 do zip -k -j putty.zip `ls *.exe | grep -vxE '^(puttytel|pterm).exe'` ../../../docbuild/putty.chm +in putty/windows/deliver/build64 do zip -k -j putty.zip `ls *.exe | grep -vxE '^(puttytel|pterm).exe'` ../../../docbuild/putty.chm +in putty/windows/deliver/abuild32 do zip -k -j putty.zip `ls *.exe | grep -vxE '^(puttytel|pterm).exe'` ../../../docbuild/putty.chm +in putty/windows/deliver/abuild64 do zip -k -j putty.zip `ls *.exe | grep -vxE '^(puttytel|pterm).exe'` ../../../docbuild/putty.chm in docbuild/html do zip puttydoc.zip *.html # Deliver the actual PuTTY release directory into a subdir `putty'. diff --git a/cmake/platforms/windows.cmake b/cmake/platforms/windows.cmake index 931812f2..fb245003 100644 --- a/cmake/platforms/windows.cmake +++ b/cmake/platforms/windows.cmake @@ -47,6 +47,7 @@ check_symbol_exists(SetDefaultDllDirectories "windows.h" HAVE_SETDEFAULTDLLDIRECTORIES) check_symbol_exists(GetNamedPipeClientProcessId "windows.h" HAVE_GETNAMEDPIPECLIENTPROCESSID) +check_symbol_exists(CreatePseudoConsole "windows.h" HAVE_CONPTY) check_c_source_compiles(" #include diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt index 6165df27..a46c9a4b 100644 --- a/windows/CMakeLists.txt +++ b/windows/CMakeLists.txt @@ -134,6 +134,28 @@ set_target_properties(puttygen PROPERTIES LINK_FLAGS "${LFLAG_MANIFEST_NO}") installed_program(puttygen) +if(HAVE_CONPTY) + add_executable(pterm + window.c + pterm.c + help.c + conpty.c + be_conpty.c + ${CMAKE_SOURCE_DIR}/nogss.c + ${CMAKE_SOURCE_DIR}/norand.c + pterm.rc) + add_dependencies(pterm generated_licence_h) + target_link_libraries(pterm + guiterminal guimisc eventloop settings network utils + ${platform_libraries}) + set_target_properties(pterm PROPERTIES + WIN32_EXECUTABLE ON + LINK_FLAGS "${LFLAG_MANIFEST_NO}") + installed_program(pterm) +else() + message("ConPTY not available; cannot build Windows pterm") +endif() + add_executable(test_split_into_argv utils/split_into_argv.c) target_compile_definitions(test_split_into_argv PRIVATE TEST) diff --git a/windows/be_conpty.c b/windows/be_conpty.c new file mode 100644 index 00000000..d8f000b1 --- /dev/null +++ b/windows/be_conpty.c @@ -0,0 +1,13 @@ +#include +#include "putty.h" + +const char *const appname = "pterm"; + +const int be_default_protocol = -1; + +const struct BackendVtable *const backends[] = { + &conpty_backend, + NULL +}; + +const size_t n_ui_backends = 1; diff --git a/windows/conpty.c b/windows/conpty.c new file mode 100644 index 00000000..4f6f130f --- /dev/null +++ b/windows/conpty.c @@ -0,0 +1,389 @@ +/* + * Backend to run a Windows console session using ConPTY. + */ + +#include +#include +#include + +#include "putty.h" + +#include +#include + +typedef struct ConPTY ConPTY; +struct ConPTY { + HPCON pseudoconsole; + HANDLE outpipe, inpipe, hprocess; + struct handle *out, *in, *subprocess; + bool exited; + DWORD exitstatus; + Seat *seat; + LogContext *logctx; + int bufsize; + Backend backend; +}; + +static void conpty_terminate(ConPTY *conpty) +{ + if (conpty->out) { + handle_free(conpty->out); + conpty->out = NULL; + } + if (conpty->outpipe != INVALID_HANDLE_VALUE) { + CloseHandle(conpty->outpipe); + conpty->outpipe = INVALID_HANDLE_VALUE; + } + if (conpty->in) { + handle_free(conpty->in); + conpty->in = NULL; + } + if (conpty->inpipe != INVALID_HANDLE_VALUE) { + CloseHandle(conpty->inpipe); + conpty->inpipe = INVALID_HANDLE_VALUE; + } + if (conpty->subprocess) { + handle_free(conpty->subprocess); + conpty->subprocess = NULL; + conpty->hprocess = INVALID_HANDLE_VALUE; + } + if (conpty->pseudoconsole != INVALID_HANDLE_VALUE) { + ClosePseudoConsole(conpty->pseudoconsole); + conpty->pseudoconsole = INVALID_HANDLE_VALUE; + } +} + +static void conpty_process_wait_callback(void *vctx) +{ + ConPTY *conpty = (ConPTY *)vctx; + + if (!GetExitCodeProcess(conpty->hprocess, &conpty->exitstatus)) + return; + conpty->exited = true; + + /* + * We can stop waiting for the process now. + */ + if (conpty->subprocess) { + handle_free(conpty->subprocess); + conpty->subprocess = NULL; + conpty->hprocess = INVALID_HANDLE_VALUE; + } + + /* + * Once the contained process exits, close the pseudo-console as + * well. But don't close the pipes yet, since apparently + * ClosePseudoConsole can trigger a final bout of terminal output + * as things clean themselves up. + */ + if (conpty->pseudoconsole != INVALID_HANDLE_VALUE) { + ClosePseudoConsole(conpty->pseudoconsole); + conpty->pseudoconsole = INVALID_HANDLE_VALUE; + } +} + +static size_t conpty_gotdata( + struct handle *h, const void *data, size_t len, int err) +{ + ConPTY *conpty = (ConPTY *)handle_get_privdata(h); + if (err || len == 0) { + char *error_msg; + + conpty_terminate(conpty); + + seat_notify_remote_exit(conpty->seat); + + if (!err && conpty->exited) { + /* + * The clean-exit case: our subprocess terminated, we + * deleted the PseudoConsole ourself, and now we got the + * expected EOF on the pipe. + */ + return 0; + } + + if (err) + error_msg = dupprintf("Error reading from console pty: %s", + win_strerror(err)); + else + error_msg = dupprintf( + "Unexpected end of file reading from console pty"); + + logevent(conpty->logctx, error_msg); + seat_connection_fatal(conpty->seat, "%s", error_msg); + sfree(error_msg); + + return 0; + } else { + return seat_stdout(conpty->seat, data, len); + } +} + +static void conpty_sentdata(struct handle *h, size_t new_backlog, int err) +{ + ConPTY *conpty = (ConPTY *)handle_get_privdata(h); + if (err) { + const char *error_msg = "Error writing to conpty device"; + + conpty_terminate(conpty); + + seat_notify_remote_exit(conpty->seat); + + logevent(conpty->logctx, error_msg); + + seat_connection_fatal(conpty->seat, "%s", error_msg); + } else { + conpty->bufsize = new_backlog; + } +} + +static char *conpty_init(const BackendVtable *vt, Seat *seat, + Backend **backend_handle, LogContext *logctx, + Conf *conf, const char *host, int port, + char **realhost, bool nodelay, bool keepalive) +{ + ConPTY *conpty; + char *err = NULL; + + HANDLE in_r = INVALID_HANDLE_VALUE; + HANDLE in_w = INVALID_HANDLE_VALUE; + HANDLE out_r = INVALID_HANDLE_VALUE; + HANDLE out_w = INVALID_HANDLE_VALUE; + + HPCON pcon; + bool pcon_needs_cleanup = false; + + STARTUPINFOEX si; + memset(&si, 0, sizeof(si)); + + if (!CreatePipe(&in_r, &in_w, NULL, 0)) { + err = dupprintf("CreatePipe: %s", win_strerror(GetLastError())); + goto out; + } + if (!CreatePipe(&out_r, &out_w, NULL, 0)) { + err = dupprintf("CreatePipe: %s", win_strerror(GetLastError())); + goto out; + } + + COORD size; + size.X = conf_get_int(conf, CONF_width); + size.Y = conf_get_int(conf, CONF_height); + + HRESULT result = CreatePseudoConsole(size, in_r, out_w, 0, &pcon); + if (FAILED(result)) { + if (HRESULT_FACILITY(result) == FACILITY_WIN32) + err = dupprintf("CreatePseudoConsole: %s", + win_strerror(HRESULT_CODE(result))); + else + err = dupprintf("CreatePseudoConsole failed: HRESULT=0x%08x", + (unsigned)result); + goto out; + } + pcon_needs_cleanup = true; + + CloseHandle(in_r); + in_r = INVALID_HANDLE_VALUE; + CloseHandle(out_w); + out_w = INVALID_HANDLE_VALUE; + + si.StartupInfo.cb = sizeof(si); + + size_t attrsize = 0; + InitializeProcThreadAttributeList(NULL, 1, 0, &attrsize); + si.lpAttributeList = smalloc(attrsize); + if (!InitializeProcThreadAttributeList( + si.lpAttributeList, 1, 0, &attrsize)) { + err = dupprintf("InitializeProcThreadAttributeList: %s", + win_strerror(GetLastError())); + goto out; + } + if (!UpdateProcThreadAttribute( + si.lpAttributeList, + 0, PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, + pcon, sizeof(pcon), NULL, NULL)) { + err = dupprintf("UpdateProcThreadAttribute: %s", + win_strerror(GetLastError())); + goto out; + } + + PROCESS_INFORMATION pi; + memset(&pi, 0, sizeof(pi)); + + char *command; + const char *conf_cmd = conf_get_str(conf, CONF_remote_cmd); + if (*conf_cmd) { + command = dupstr(conf_cmd); + } else { + command = dupcat(get_system_dir(), "\\cmd.exe"); + } + bool created_ok = CreateProcess(NULL, command, NULL, NULL, + false, EXTENDED_STARTUPINFO_PRESENT, + NULL, NULL, &si.StartupInfo, &pi); + sfree(command); + if (!created_ok) { + err = dupprintf("CreateProcess: %s", + win_strerror(GetLastError())); + goto out; + } + + /* No local authentication phase in this protocol */ + seat_set_trust_status(seat, false); + + conpty = snew(ConPTY); + conpty->pseudoconsole = pcon; + pcon_needs_cleanup = false; + conpty->outpipe = in_w; + conpty->out = handle_output_new(in_w, conpty_sentdata, conpty, 0); + in_w = INVALID_HANDLE_VALUE; + conpty->inpipe = out_r; + conpty->in = handle_input_new(out_r, conpty_gotdata, conpty, 0); + out_r = INVALID_HANDLE_VALUE; + conpty->subprocess = handle_add_foreign_event( + pi.hProcess, conpty_process_wait_callback, conpty); + conpty->hprocess = pi.hProcess; + CloseHandle(pi.hThread); + conpty->exited = false; + conpty->exitstatus = 0; + conpty->bufsize = 0; + conpty->backend.vt = vt; + *backend_handle = &conpty->backend; + + conpty->seat = seat; + conpty->logctx = logctx; + + *realhost = dupstr(""); + + /* + * Specials are always available. + */ + seat_update_specials_menu(conpty->seat); + + out: + if (in_r != INVALID_HANDLE_VALUE) + CloseHandle(in_r); + if (in_w != INVALID_HANDLE_VALUE) + CloseHandle(in_w); + if (out_r != INVALID_HANDLE_VALUE) + CloseHandle(out_r); + if (out_w != INVALID_HANDLE_VALUE) + CloseHandle(out_w); + if (pcon_needs_cleanup) + ClosePseudoConsole(pcon); + sfree(si.lpAttributeList); + return err; +} + +static void conpty_free(Backend *be) +{ + ConPTY *conpty = container_of(be, ConPTY, backend); + + conpty_terminate(conpty); + expire_timer_context(conpty); + sfree(conpty); +} + +static void conpty_reconfig(Backend *be, Conf *conf) +{ +} + +static size_t conpty_send(Backend *be, const char *buf, size_t len) +{ + ConPTY *conpty = container_of(be, ConPTY, backend); + + if (conpty->out == NULL) + return 0; + + conpty->bufsize = handle_write(conpty->out, buf, len); + return conpty->bufsize; +} + +static size_t conpty_sendbuffer(Backend *be) +{ + ConPTY *conpty = container_of(be, ConPTY, backend); + return conpty->bufsize; +} + +static void conpty_size(Backend *be, int width, int height) +{ + ConPTY *conpty = container_of(be, ConPTY, backend); + COORD size; + size.X = width; + size.Y = height; + ResizePseudoConsole(conpty->pseudoconsole, size); +} + +static void conpty_special(Backend *be, SessionSpecialCode code, int arg) +{ +} + +static const SessionSpecial *conpty_get_specials(Backend *be) +{ + static const SessionSpecial specials[] = { + {NULL, SS_EXITMENU} + }; + return specials; +} + +static bool conpty_connected(Backend *be) +{ + return true; /* always connected */ +} + +static bool conpty_sendok(Backend *be) +{ + return true; +} + +static void conpty_unthrottle(Backend *be, size_t backlog) +{ + ConPTY *conpty = container_of(be, ConPTY, backend); + if (conpty->in) + handle_unthrottle(conpty->in, backlog); +} + +static bool conpty_ldisc(Backend *be, int option) +{ + return false; +} + +static void conpty_provide_ldisc(Backend *be, Ldisc *ldisc) +{ +} + +static int conpty_exitcode(Backend *be) +{ + ConPTY *conpty = container_of(be, ConPTY, backend); + + if (conpty->exited && + 0 <= conpty->exitstatus && + conpty->exitstatus <= INT_MAX) + return conpty->exitstatus; + else + return -1; +} + +static int conpty_cfg_info(Backend *be) +{ + return 0; +} + +const BackendVtable conpty_backend = { + .init = conpty_init, + .free = conpty_free, + .reconfig = conpty_reconfig, + .send = conpty_send, + .sendbuffer = conpty_sendbuffer, + .size = conpty_size, + .special = conpty_special, + .get_specials = conpty_get_specials, + .connected = conpty_connected, + .exitcode = conpty_exitcode, + .sendok = conpty_sendok, + .ldisc_option_state = conpty_ldisc, + .provide_ldisc = conpty_provide_ldisc, + .unthrottle = conpty_unthrottle, + .cfg_info = conpty_cfg_info, + .id = "conpty", + .displayname = "ConPTY", + .protocol = -1, +}; diff --git a/windows/platform.h b/windows/platform.h index b746c76d..a03d9d63 100644 --- a/windows/platform.h +++ b/windows/platform.h @@ -705,7 +705,9 @@ void cli_main_loop(cliloop_pre_t pre, cliloop_post_t post, void *ctx); bool cliloop_null_pre(void *vctx, const HANDLE **, size_t *); bool cliloop_null_post(void *vctx, size_t); -/* Functions that parametrise window.c */ +extern const struct BackendVtable conpty_backend; + +/* Functions that parametrise window.c between PuTTY and pterm */ void gui_term_process_cmdline(Conf *conf, char *cmdline); const struct BackendVtable *backend_vt_from_conf(Conf *conf); const wchar_t *get_app_user_model_id(void); diff --git a/windows/pterm.c b/windows/pterm.c new file mode 100644 index 00000000..57463449 --- /dev/null +++ b/windows/pterm.c @@ -0,0 +1,45 @@ +#include "putty.h" +#include "storage.h" + +void gui_term_process_cmdline(Conf *conf, char *cmdline) +{ + do_defaults(NULL, conf); + conf_set_str(conf, CONF_remote_cmd, ""); + + cmdline = handle_restrict_acl_cmdline_prefix(cmdline); + if (handle_special_sessionname_cmdline(cmdline, conf) || + handle_special_filemapping_cmdline(cmdline, conf)) + return; + + int argc; + char **argv, **argstart; + split_into_argv(cmdline, &argc, &argv, &argstart); + + for (int i = 0; i < argc; i++) { + const char *arg = argv[i]; + if (!strcmp(arg, "-e")) { + if (i+1 < argc) { + /* The command to execute is taken to be the unparsed + * version of the whole remainder of the command line. */ + conf_set_str(conf, CONF_remote_cmd, argstart[i+1]); + return; + } else { + cmdline_error("option \"%s\" requires an argument", arg); + } + } else if (arg[0] == '-') { + cmdline_error("unrecognised option \"%s\"", arg); + } else { + cmdline_error("unexpected non-option argument \"%s\"", arg); + } + } +} + +const struct BackendVtable *backend_vt_from_conf(Conf *conf) +{ + return &conpty_backend; +} + +const wchar_t *get_app_user_model_id(void) +{ + return L"SimonTatham.Pterm"; +} diff --git a/windows/pterm.rc b/windows/pterm.rc new file mode 100644 index 00000000..8bd3a043 --- /dev/null +++ b/windows/pterm.rc @@ -0,0 +1,15 @@ +#include "rcstuff.h" +#include "putty-rc.h" + +#define APPNAME "pterm" +#define APPDESC "PuTTY-style wrapper for Windows command prompts" + +IDI_MAINICON ICON "pterm.ico" +IDI_CFGICON ICON "ptermcfg.ico" + +#include "help.rc2" +#include "putty-common.rc2" + +#ifndef NO_MANIFESTS +1 RT_MANIFEST "putty.mft" +#endif /* NO_MANIFESTS */ -- cgit v1.2.3 From 571fa3388d61f3270d958503c62f09669665f6f8 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 8 May 2021 18:13:06 +0100 Subject: Make TermWin's palette_get_overrides() take a Terminal *. Less than 12 hours after 0.75 went out of the door, a user pointed out that enabling the 'Use system colours' config option causes an immediate NULL-dereference crash. The reason is because a chain of calls from term_init() ends up calling back to the Windows implementation of the palette_get_overrides() method, which responds by trying to call functions on the static variable 'term' in window.c, which won't be initialised until term_init() has returned. Simple fix: palette_get_overrides() is now given a pointer to the Terminal that it should be updating, because it can't find it out any other way. --- fuzzterm.c | 2 +- putty.h | 14 ++++++++++---- terminal.c | 2 +- unix/window.c | 2 +- windows/window.c | 4 ++-- 5 files changed, 15 insertions(+), 9 deletions(-) diff --git a/fuzzterm.c b/fuzzterm.c index 660a6be5..2e680342 100644 --- a/fuzzterm.c +++ b/fuzzterm.c @@ -90,7 +90,7 @@ static void fuzz_move(TermWin *tw, int x, int y) {} static void fuzz_set_zorder(TermWin *tw, bool top) {} static void fuzz_palette_set(TermWin *tw, unsigned start, unsigned ncolours, const rgb *colours) {} -static void fuzz_palette_get_overrides(TermWin *tw) {} +static void fuzz_palette_get_overrides(TermWin *tw, Terminal *term) {} static const TermWinVtable fuzz_termwin_vt = { .setup_draw_ctx = fuzz_setup_draw_ctx, diff --git a/putty.h b/putty.h index 1b3c7c67..a9e12684 100644 --- a/putty.h +++ b/putty.h @@ -1299,8 +1299,14 @@ struct TermWinVtable { /* Query the front end for any OS-local overrides to the default * colours stored in Conf. The front end should set any it cares - * about by calling term_palette_override. */ - void (*palette_get_overrides)(TermWin *); + * about by calling term_palette_override. + * + * The Terminal object is passed in as a parameter, because this + * can be called as a callback from term_init(). So the TermWin + * itself won't yet have been told where to find its Terminal + * object, because that doesn't happen until term_init + * returns. */ + void (*palette_get_overrides)(TermWin *, Terminal *); }; static inline bool win_setup_draw_ctx(TermWin *win) @@ -1354,8 +1360,8 @@ static inline void win_set_zorder(TermWin *win, bool top) static inline void win_palette_set( TermWin *win, unsigned start, unsigned ncolours, const rgb *colours) { win->vt->palette_set(win, start, ncolours, colours); } -static inline void win_palette_get_overrides(TermWin *win) -{ win->vt->palette_get_overrides(win); } +static inline void win_palette_get_overrides(TermWin *win, Terminal *term) +{ win->vt->palette_get_overrides(win, term); } /* * Global functions not specific to a connection instance. diff --git a/terminal.c b/terminal.c index 0673623a..4bb47352 100644 --- a/terminal.c +++ b/terminal.c @@ -1874,7 +1874,7 @@ static void palette_reset(Terminal *term, bool overrides_only) */ for (unsigned i = 0; i < OSC4_NCOLOURS; i++) term->subpalettes[SUBPAL_PLATFORM].present[i] = false; - win_palette_get_overrides(term->win); + win_palette_get_overrides(term->win, term); /* * Rebuild the composite palette. diff --git a/unix/window.c b/unix/window.c index 4da0cb13..16306023 100644 --- a/unix/window.c +++ b/unix/window.c @@ -2614,7 +2614,7 @@ static void gtkwin_palette_set(TermWin *tw, unsigned start, unsigned ncolours, } } -static void gtkwin_palette_get_overrides(TermWin *tw) +static void gtkwin_palette_get_overrides(TermWin *tw, Terminal *term) { /* GTK has no analogue of Windows's 'standard system colours', so GTK PuTTY * has no config option to override the normally configured colours from diff --git a/windows/window.c b/windows/window.c index 70a83ddc..3a9effee 100644 --- a/windows/window.c +++ b/windows/window.c @@ -257,7 +257,7 @@ static void wintw_set_maximised(TermWin *, bool maximised); static void wintw_move(TermWin *, int x, int y); static void wintw_set_zorder(TermWin *, bool top); static void wintw_palette_set(TermWin *, unsigned, unsigned, const rgb *); -static void wintw_palette_get_overrides(TermWin *); +static void wintw_palette_get_overrides(TermWin *, Terminal *); static const TermWinVtable windows_termwin_vt = { .setup_draw_ctx = wintw_setup_draw_ctx, @@ -1130,7 +1130,7 @@ static inline rgb rgb_from_colorref(COLORREF cr) return toret; } -static void wintw_palette_get_overrides(TermWin *tw) +static void wintw_palette_get_overrides(TermWin *tw, Terminal *term) { if (conf_get_bool(conf, CONF_system_colour)) { rgb rgb; -- cgit v1.2.3 From 8245510a029b3d5b6774fe931dc2725b0a687540 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 8 May 2021 20:57:18 +0100 Subject: Reinstate missing bit counts in Windows Pageant GUI. An embarrassing braino of && for || produced a boolean expression that could never evaluate true. --- windows/pageant.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/windows/pageant.c b/windows/pageant.c index 9159a5aa..e7023945 100644 --- a/windows/pageant.c +++ b/windows/pageant.c @@ -376,7 +376,7 @@ static void keylist_update_callback( ptrlen algname = get_string(src); const ssh_keyalg *alg = find_pubkey_alg_len(algname); - bool include_bit_count = (alg == &ssh_dsa && alg == &ssh_rsa); + bool include_bit_count = (alg == &ssh_dsa || alg == &ssh_rsa); int wordnumber = 0; for (const char *p = fingerprint; *p; p++) { -- cgit v1.2.3 From 0e83e72b09744e89c459c8156ac88cb07f35065e Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 9 May 2021 09:06:12 +0100 Subject: New test tool: list-accel.py. Gives a quick and easy report of which HW-accelerated crypto implementations are (a) compiled in to testcrypt, (b) actually instantiable at testcrypt run time. --- test/list-accel.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100755 test/list-accel.py diff --git a/test/list-accel.py b/test/list-accel.py new file mode 100755 index 00000000..af93d420 --- /dev/null +++ b/test/list-accel.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 + +# Simple client of the testcrypt system that reports the available +# variants of each of the crypto primitives that have hardware- +# accelerated implementations. +# +# It will report the set of primitives compiled in to testcrypt, and +# also report whether each one can be instantiated at run time. + +from testcrypt import * + +def get_implementations(alg): + return get_implementations_commasep(alg).decode("ASCII").split(",") + +def list_implementations(alg, checkfn): + print(f"Implementations of {alg}:") + for impl in get_implementations(alg): + if impl == alg: + continue + if checkfn(impl): + print(f" {impl:<32s} available") + else: + print(f" {impl:<32s} compiled in, but unavailable at run time") + +def list_cipher_implementations(alg): + list_implementations(alg, lambda impl: ssh_cipher_new(impl) is not None) + +def list_hash_implementations(alg): + list_implementations(alg, lambda impl: ssh_hash_new(impl) is not None) + +list_cipher_implementations("aes256_cbc") +list_hash_implementations("sha1") +list_hash_implementations("sha256") +list_hash_implementations("sha512") -- cgit v1.2.3 From 155d8121e6584f842fa06f5fbad75b2555f60269 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 13 May 2021 18:20:41 +0100 Subject: Fix confusion between invalid Windows HANDLE values. I was checking a HANDLE against INVALID_HANDLE_VALUE to decide whether it should be closed. But ten lines further up, I was setting it manually to NULL to suppress the close. Oops. --- windows/agent-client.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/windows/agent-client.c b/windows/agent-client.c index 47123cc9..5109b0f0 100644 --- a/windows/agent-client.c +++ b/windows/agent-client.c @@ -260,7 +260,7 @@ static agent_pending_query *named_pipe_agent_query( pq = snew(agent_pending_query); pq->handle = handle_input_new(pipehandle, named_pipe_agent_gotdata, pq, 0); - pipehandle = NULL; /* prevent it being closed below */ + pipehandle = INVALID_HANDLE_VALUE; /* prevent it being closed below */ pq->response = strbuf_new_nm(); pq->callback = callback; pq->callback_ctx = callback_ctx; -- cgit v1.2.3 From 6e69223dc262755c2cd2a3bba5c188d8fc91943a Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 13 May 2021 18:22:05 +0100 Subject: Close agent named-pipe handles when queries complete. I was cleaning up the 'struct handle', but not the underlying HANDLE. As a result, any PuTTY process that makes a request to Pageant keeps the named pipe connection open until the end of the process's lifetime. --- windows/agent-client.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/windows/agent-client.c b/windows/agent-client.c index 5109b0f0..4eb0bcfb 100644 --- a/windows/agent-client.c +++ b/windows/agent-client.c @@ -163,6 +163,7 @@ bool agent_exists(void) struct agent_pending_query { struct handle *handle; + HANDLE os_handle; strbuf *response; void (*callback)(void *, void *, int); void *callback_ctx; @@ -260,6 +261,7 @@ static agent_pending_query *named_pipe_agent_query( pq = snew(agent_pending_query); pq->handle = handle_input_new(pipehandle, named_pipe_agent_gotdata, pq, 0); + pq->os_handle = pipehandle; pipehandle = INVALID_HANDLE_VALUE; /* prevent it being closed below */ pq->response = strbuf_new_nm(); pq->callback = callback; @@ -284,6 +286,7 @@ static agent_pending_query *named_pipe_agent_query( void agent_cancel_query(agent_pending_query *pq) { handle_free(pq->handle); + CloseHandle(pq->os_handle); if (pq->response) strbuf_free(pq->response); sfree(pq); -- cgit v1.2.3 From 6791bdc9b6edcf67a5da718b50c0444ac512c935 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 13 May 2021 18:40:05 +0100 Subject: Don't #include if it doesn't exist. A FreeBSD user reports that it doesn't exist there. --- cmake/cmake.h.in | 1 + cmake/platforms/unix.cmake | 1 + unix/pty.c | 2 ++ 3 files changed, 4 insertions(+) diff --git a/cmake/cmake.h.in b/cmake/cmake.h.in index b6c07d2c..9de1386b 100644 --- a/cmake/cmake.h.in +++ b/cmake/cmake.h.in @@ -21,6 +21,7 @@ #cmakedefine01 HAVE_SYS_SYSCTL_H #cmakedefine01 HAVE_SYS_TYPES_H #cmakedefine01 HAVE_GLOB_H +#cmakedefine01 HAVE_UTMP_H #cmakedefine01 HAVE_FUTIMES #cmakedefine01 HAVE_GETADDRINFO #cmakedefine01 HAVE_POSIX_OPENPT diff --git a/cmake/platforms/unix.cmake b/cmake/platforms/unix.cmake index 156919e7..cb47caf9 100644 --- a/cmake/platforms/unix.cmake +++ b/cmake/platforms/unix.cmake @@ -18,6 +18,7 @@ check_include_file(asm/hwcap.h HAVE_ASM_HWCAP_H) check_include_file(sys/sysctl.h HAVE_SYS_SYSCTL_H) check_include_file(sys/types.h HAVE_SYS_TYPES_H) check_include_file(glob.h HAVE_GLOB_H) +check_include_file(utmp.h HAVE_UTMP_H) check_symbol_exists(futimes "sys/time.h" HAVE_FUTIMES) check_symbol_exists(getaddrinfo "sys/types.h;sys/socket.h;netdb.h" diff --git a/unix/pty.c b/unix/pty.c index 30e48e50..abafa108 100644 --- a/unix/pty.c +++ b/unix/pty.c @@ -13,7 +13,9 @@ #include #include #include +#if HAVE_UTMP_H #include +#endif #include #include #include -- cgit v1.2.3 From 0c21eb444794512ba1106d4a3bc3380861566099 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Wed, 19 May 2021 10:42:42 +0100 Subject: cmdgen: add missing null pointer check in --dump mode. A user pointed out that once we've identified the key algorithm from an apparent public-key blob, we call ssh_key_new_pub on the blob data and assume it will succeed. But there are plenty of ways it could still fail, and ssh_key_new_pub could return NULL. --- cmdgen.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cmdgen.c b/cmdgen.c index 3301b410..be40fe1d 100644 --- a/cmdgen.c +++ b/cmdgen.c @@ -1283,6 +1283,10 @@ int main(int argc, char **argv) } ssh_key *sk = ssh_key_new_pub( alg, ptrlen_from_strbuf(ssh2blob)); + if (!sk) { + fprintf(stderr, "puttygen: unable to decode public key\n"); + RETURN(1); + } kc = ssh_key_components(sk); ssh_key_free(sk); } -- cgit v1.2.3 From 1e726c94e8d53c929a568a74fd54f71148550696 Mon Sep 17 00:00:00 2001 From: Jacob Nevins Date: Sat, 15 May 2021 22:05:27 +0100 Subject: Fix changing colours in Change Settings. Since ca9cd983e1, changing colour config mid-session had no effect (until the palette was reset for some other reason). Now it does take effect immediately (provided that the palette has not been overridden by escape sequence -- this is new with ca9cd983e1). This changes the semantics of palette_reset(): the only important parameter when doing that is whether we keep escape sequence overrides -- there's no harm in re-fetching config and platform colours whether or not they've changed -- so that's what the parameter becomes (with a sense that doesn't require changing the call sites). The other part of this change is actually remembering to trigger this when the configuration is changed. --- putty.h | 2 +- terminal.c | 103 ++++++++++++++++++++++++++++++++++++------------------- windows/window.c | 4 +-- 3 files changed, 70 insertions(+), 39 deletions(-) diff --git a/putty.h b/putty.h index a9e12684..9b8b3799 100644 --- a/putty.h +++ b/putty.h @@ -1790,7 +1790,7 @@ void term_keyinputw(Terminal *, const wchar_t * widebuf, int len); void term_get_cursor_position(Terminal *term, int *x, int *y); void term_setup_window_titles(Terminal *term, const char *title_hostname); void term_notify_minimised(Terminal *term, bool minimised); -void term_notify_palette_overrides_changed(Terminal *term); +void term_notify_palette_changed(Terminal *term); void term_notify_window_pos(Terminal *term, int x, int y); void term_notify_window_size_pixels(Terminal *term, int x, int y); void term_palette_override(Terminal *term, unsigned osc4_index, rgb rgb); diff --git a/terminal.c b/terminal.c index 4bb47352..b845baab 100644 --- a/terminal.c +++ b/terminal.c @@ -1629,6 +1629,7 @@ void term_reconfig(Terminal *term, Conf *conf) * Mode, BCE, blinking text, character classes. */ bool reset_wrap, reset_decom, reset_bce, reset_tblink, reset_charclass; + bool palette_changed = false; int i; reset_wrap = (conf_get_bool(term->conf, CONF_wrap_mode) != @@ -1674,6 +1675,29 @@ void term_reconfig(Terminal *term, Conf *conf) } } + /* + * Just setting conf is sufficient to cause colour setting changes + * to appear on the next ESC]R palette reset. But we should also + * check whether any colour settings have been changed, so that + * they can be updated immediately if they haven't been overridden + * by some escape sequence. + */ + { + int i, j; + for (i = 0; i < CONF_NCOLOURS; i++) { + for (j = 0; j < 3; j++) + if (conf_get_int_int(term->conf, CONF_colours, i*3+j) != + conf_get_int_int(conf, CONF_colours, i*3+j)) + break; + if (j < 3) { + /* Actually enacting the change has to be deferred + * until the new conf is installed. */ + palette_changed = true; + break; + } + } + } + conf_free(term->conf); term->conf = conf_copy(conf); @@ -1702,6 +1726,8 @@ void term_reconfig(Terminal *term, Conf *conf) if (!conf_get_str(term->conf, CONF_printer)) { term_print_finish(term); } + if (palette_changed) + term_notify_palette_changed(term); term_schedule_tblink(term); term_schedule_cblink(term); term_copy_stuff_from_conf(term); @@ -1829,44 +1855,41 @@ static void palette_rebuild(Terminal *term) } } -static void palette_reset(Terminal *term, bool overrides_only) +/* + * Rebuild the palette from configuration and platform colours. + * If 'keep_overrides' set, any escape-sequence-specified overrides will + * remain in place. + */ +static void palette_reset(Terminal *term, bool keep_overrides) { - if (!overrides_only) { - for (unsigned i = 0; i < OSC4_NCOLOURS; i++) - term->subpalettes[SUBPAL_CONF].present[i] = true; - - /* - * Copy all the palette information out of the Conf. - */ - for (unsigned i = 0; i < CONF_NCOLOURS; i++) { - rgb *col = &term->subpalettes[SUBPAL_CONF].values[ - colour_indices_conf_to_osc4[i]]; - col->r = conf_get_int_int(term->conf, CONF_colours, i*3+0); - col->g = conf_get_int_int(term->conf, CONF_colours, i*3+1); - col->b = conf_get_int_int(term->conf, CONF_colours, i*3+2); - } + for (unsigned i = 0; i < OSC4_NCOLOURS; i++) + term->subpalettes[SUBPAL_CONF].present[i] = true; - /* - * Directly invent the rest of the xterm-256 colours. - */ - for (unsigned i = 0; i < 216; i++) { - rgb *col = &term->subpalettes[SUBPAL_CONF].values[i + 16]; - int r = i / 36, g = (i / 6) % 6, b = i % 6; - col->r = r ? r * 40 + 55 : 0; - col->g = g ? g * 40 + 55 : 0; - col->b = b ? b * 40 + 55 : 0; - } - for (unsigned i = 0; i < 24; i++) { - rgb *col = &term->subpalettes[SUBPAL_CONF].values[i + 232]; - int shade = i * 10 + 8; - col->r = col->g = col->b = shade; - } + /* + * Copy all the palette information out of the Conf. + */ + for (unsigned i = 0; i < CONF_NCOLOURS; i++) { + rgb *col = &term->subpalettes[SUBPAL_CONF].values[ + colour_indices_conf_to_osc4[i]]; + col->r = conf_get_int_int(term->conf, CONF_colours, i*3+0); + col->g = conf_get_int_int(term->conf, CONF_colours, i*3+1); + col->b = conf_get_int_int(term->conf, CONF_colours, i*3+2); + } - /* - * Get rid of all escape-sequence configuration. - */ - for (unsigned i = 0; i < OSC4_NCOLOURS; i++) - term->subpalettes[SUBPAL_SESSION].present[i] = false; + /* + * Directly invent the rest of the xterm-256 colours. + */ + for (unsigned i = 0; i < 216; i++) { + rgb *col = &term->subpalettes[SUBPAL_CONF].values[i + 16]; + int r = i / 36, g = (i / 6) % 6, b = i % 6; + col->r = r ? r * 40 + 55 : 0; + col->g = g ? g * 40 + 55 : 0; + col->b = b ? b * 40 + 55 : 0; + } + for (unsigned i = 0; i < 24; i++) { + rgb *col = &term->subpalettes[SUBPAL_CONF].values[i + 232]; + int shade = i * 10 + 8; + col->r = col->g = col->b = shade; } /* @@ -1876,6 +1899,14 @@ static void palette_reset(Terminal *term, bool overrides_only) term->subpalettes[SUBPAL_PLATFORM].present[i] = false; win_palette_get_overrides(term->win, term); + if (!keep_overrides) { + /* + * Get rid of all escape-sequence configuration. + */ + for (unsigned i = 0; i < OSC4_NCOLOURS; i++) + term->subpalettes[SUBPAL_SESSION].present[i] = false; + } + /* * Rebuild the composite palette. */ @@ -7593,7 +7624,7 @@ void term_notify_minimised(Terminal *term, bool minimised) term->minimised = minimised; } -void term_notify_palette_overrides_changed(Terminal *term) +void term_notify_palette_changed(Terminal *term) { palette_reset(term, true); } diff --git a/windows/window.c b/windows/window.c index 3a9effee..a10cb536 100644 --- a/windows/window.c +++ b/windows/window.c @@ -2310,7 +2310,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, if (conf_get_bool(conf, CONF_system_colour) != conf_get_bool(prev_conf, CONF_system_colour)) - term_notify_palette_overrides_changed(term); + term_notify_palette_changed(term); /* Pass new config data to the terminal */ term_reconfig(term, conf); @@ -3300,7 +3300,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, case WM_SYSCOLORCHANGE: if (conf_get_bool(conf, CONF_system_colour)) { /* Refresh palette from system colours. */ - term_notify_palette_overrides_changed(term); + term_notify_palette_changed(term); init_palette(); /* Force a repaint of the terminal window. */ term_invalidate(term); -- cgit v1.2.3 From 62b694affc0ce0377576bb34ed005ad0c38cd013 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 22 May 2021 11:27:19 +0100 Subject: New backend flag indicating support for CONF_ssh_nc_host. This flag is set in backends which can be used programmatically to proxy a network connection in place of running a shell session. That is true of both SSH proper, and the psusan ssh-connection protocol. Nothing yet uses this flag, but something is about to. --- putty.h | 2 ++ ssh/ssh.c | 2 ++ 2 files changed, 4 insertions(+) diff --git a/putty.h b/putty.h index 9b8b3799..a6d81b18 100644 --- a/putty.h +++ b/putty.h @@ -617,6 +617,8 @@ enum { #define BACKEND_RESIZE_FORBIDDEN 0x01 /* Backend does not allow resizing terminal */ #define BACKEND_NEEDS_TERMINAL 0x02 /* Backend must have terminal */ +#define BACKEND_SUPPORTS_NC_HOST 0x04 /* Backend can honour + CONF_ssh_nc_host */ struct Backend { const BackendVtable *vt; diff --git a/ssh/ssh.c b/ssh/ssh.c index b1499a54..868533d2 100644 --- a/ssh/ssh.c +++ b/ssh/ssh.c @@ -1221,6 +1221,7 @@ const BackendVtable ssh_backend = { .id = "ssh", .displayname = "SSH", .protocol = PROT_SSH, + .flags = BACKEND_SUPPORTS_NC_HOST, .default_port = 22, }; @@ -1245,4 +1246,5 @@ const BackendVtable sshconn_backend = { .id = "ssh-connection", .displayname = "Bare ssh-connection", .protocol = PROT_SSHCONN, + .flags = BACKEND_SUPPORTS_NC_HOST, }; -- cgit v1.2.3 From 0553aec60a0a7fdc01584db2df3e4b59925620d4 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 22 May 2021 12:47:51 +0100 Subject: New Seat method, notify_remote_disconnect. This notifies the Seat that the entire backend session has finished and closed its network connection - or rather, that it _might_ have done, and that the frontend should check backend_connected() if it wasn't planning to do so already. The existing Seat implementations haven't needed this: the GUI ones don't actually need to do anything specific when the network connection goes away, and the CLI ones deal with it by being in charge of their own event loop so that they can easily check backend_connected() at every possible opportunity in any case. But I'm about to introduce a new Seat implementation that does need to know this, and doesn't have any other way to get notified of it. --- otherbackends/raw.c | 2 ++ otherbackends/rlogin.c | 1 + otherbackends/supdup.c | 1 + otherbackends/telnet.c | 1 + pscp.c | 1 + psftp.c | 1 + putty.h | 26 ++++++++++++++++++++++++++ ssh/server.c | 1 + ssh/sesschan.c | 1 + ssh/ssh.c | 3 +++ unix/plink.c | 1 + unix/window.c | 1 + utils/nullseat.c | 1 + windows/plink.c | 1 + windows/window.c | 1 + 15 files changed, 43 insertions(+) diff --git a/otherbackends/raw.c b/otherbackends/raw.c index 0c454985..7977f386 100644 --- a/otherbackends/raw.c +++ b/otherbackends/raw.c @@ -52,6 +52,7 @@ static void raw_check_close(Raw *raw) sk_close(raw->s); raw->s = NULL; seat_notify_remote_exit(raw->seat); + seat_notify_remote_disconnect(raw->seat); } } } @@ -68,6 +69,7 @@ static void raw_closing(Plug *plug, const char *error_msg, int error_code, raw->s = NULL; raw->closed_on_socket_error = true; seat_notify_remote_exit(raw->seat); + seat_notify_remote_disconnect(raw->seat); } logevent(raw->logctx, error_msg); seat_connection_fatal(raw->seat, "%s", error_msg); diff --git a/otherbackends/rlogin.c b/otherbackends/rlogin.c index 2a3714e0..8289a508 100644 --- a/otherbackends/rlogin.c +++ b/otherbackends/rlogin.c @@ -63,6 +63,7 @@ static void rlogin_closing(Plug *plug, const char *error_msg, int error_code, if (error_msg) rlogin->closed_on_socket_error = true; seat_notify_remote_exit(rlogin->seat); + seat_notify_remote_disconnect(rlogin->seat); } if (error_msg) { /* A socket error has occurred. */ diff --git a/otherbackends/supdup.c b/otherbackends/supdup.c index f210ebe3..36dbb447 100644 --- a/otherbackends/supdup.c +++ b/otherbackends/supdup.c @@ -581,6 +581,7 @@ static void supdup_closing(Plug *plug, const char *error_msg, int error_code, if (error_msg) supdup->closed_on_socket_error = true; seat_notify_remote_exit(supdup->seat); + seat_notify_remote_disconnect(supdup->seat); } if (error_msg) { logevent(supdup->logctx, error_msg); diff --git a/otherbackends/telnet.c b/otherbackends/telnet.c index 3a60e646..f8b41ac3 100644 --- a/otherbackends/telnet.c +++ b/otherbackends/telnet.c @@ -639,6 +639,7 @@ static void telnet_closing(Plug *plug, const char *error_msg, int error_code, if (error_msg) telnet->closed_on_socket_error = true; seat_notify_remote_exit(telnet->seat); + seat_notify_remote_disconnect(telnet->seat); } if (error_msg) { logevent(telnet->logctx, error_msg); diff --git a/pscp.c b/pscp.c index 19bc3807..9accdf70 100644 --- a/pscp.c +++ b/pscp.c @@ -67,6 +67,7 @@ static const SeatVtable pscp_seat_vt = { .eof = pscp_eof, .get_userpass_input = filexfer_get_userpass_input, .notify_remote_exit = nullseat_notify_remote_exit, + .notify_remote_disconnect = nullseat_notify_remote_disconnect, .connection_fatal = console_connection_fatal, .update_specials_menu = nullseat_update_specials_menu, .get_ttymode = nullseat_get_ttymode, diff --git a/psftp.c b/psftp.c index c2791e3b..fd9b78d3 100644 --- a/psftp.c +++ b/psftp.c @@ -49,6 +49,7 @@ static const SeatVtable psftp_seat_vt = { .eof = psftp_eof, .get_userpass_input = filexfer_get_userpass_input, .notify_remote_exit = nullseat_notify_remote_exit, + .notify_remote_disconnect = nullseat_notify_remote_disconnect, .connection_fatal = console_connection_fatal, .update_specials_menu = nullseat_update_specials_menu, .get_ttymode = nullseat_get_ttymode, diff --git a/putty.h b/putty.h index a6d81b18..75dd5a86 100644 --- a/putty.h +++ b/putty.h @@ -924,6 +924,29 @@ struct SeatVtable { */ void (*notify_remote_exit)(Seat *seat); + /* + * Notify the seat that the whole connection has finished. + * (Distinct from notify_remote_exit, e.g. in the case where you + * have port forwardings still active when the main foreground + * session goes away: then you'd get notify_remote_exit when the + * foreground session dies, but notify_remote_disconnect when the + * last forwarding vanishes and the network connection actually + * closes.) + * + * This function might be called multiple times by accident; seats + * should be prepared to cope. + * + * More precisely: this function notifies the seat that + * backend_connected() might now return false where previously it + * returned true. (Note the 'might': an accidental duplicate call + * might happen when backend_connected() was already returning + * false. Or even, in weird situations, when it hadn't stopped + * returning true yet. The point is, when you get this + * notification, all it's really telling you is that it's worth + * _checking_ backend_connected, if you weren't already.) + */ + void (*notify_remote_disconnect)(Seat *seat); + /* * Notify the seat that the connection has suffered a fatal error. */ @@ -1095,6 +1118,8 @@ static inline int seat_get_userpass_input( { return seat->vt->get_userpass_input(seat, p, input); } static inline void seat_notify_remote_exit(Seat *seat) { seat->vt->notify_remote_exit(seat); } +static inline void seat_notify_remote_disconnect(Seat *seat) +{ seat->vt->notify_remote_disconnect(seat); } static inline void seat_update_specials_menu(Seat *seat) { seat->vt->update_specials_menu(seat); } static inline char *seat_get_ttymode(Seat *seat, const char *mode) @@ -1163,6 +1188,7 @@ size_t nullseat_output( bool nullseat_eof(Seat *seat); int nullseat_get_userpass_input(Seat *seat, prompts_t *p, bufchain *input); void nullseat_notify_remote_exit(Seat *seat); +void nullseat_notify_remote_disconnect(Seat *seat); void nullseat_connection_fatal(Seat *seat, const char *message); void nullseat_update_specials_menu(Seat *seat); char *nullseat_get_ttymode(Seat *seat, const char *mode); diff --git a/ssh/server.c b/ssh/server.c index e3fc86fe..642d4ce0 100644 --- a/ssh/server.c +++ b/ssh/server.c @@ -109,6 +109,7 @@ static const SeatVtable server_seat_vt = { .eof = nullseat_eof, .get_userpass_input = nullseat_get_userpass_input, .notify_remote_exit = nullseat_notify_remote_exit, + .notify_remote_disconnect = nullseat_notify_remote_disconnect, .connection_fatal = nullseat_connection_fatal, .update_specials_menu = nullseat_update_specials_menu, .get_ttymode = nullseat_get_ttymode, diff --git a/ssh/sesschan.c b/ssh/sesschan.c index 4b204b29..f1bed6f2 100644 --- a/ssh/sesschan.c +++ b/ssh/sesschan.c @@ -188,6 +188,7 @@ static const SeatVtable sesschan_seat_vt = { .eof = sesschan_seat_eof, .get_userpass_input = nullseat_get_userpass_input, .notify_remote_exit = sesschan_notify_remote_exit, + .notify_remote_disconnect = nullseat_notify_remote_disconnect, .connection_fatal = sesschan_connection_fatal, .update_specials_menu = nullseat_update_specials_menu, .get_ttymode = nullseat_get_ttymode, diff --git a/ssh/ssh.c b/ssh/ssh.c index 868533d2..efeb1f7e 100644 --- a/ssh/ssh.c +++ b/ssh/ssh.c @@ -387,6 +387,7 @@ static void ssh_bpp_output_raw_data_callback(void *vctx) if (ssh->pending_close) { sk_close(ssh->s); ssh->s = NULL; + seat_notify_remote_disconnect(ssh->seat); } } @@ -428,6 +429,7 @@ static void ssh_shutdown(Ssh *ssh) if (ssh->s) { sk_close(ssh->s); ssh->s = NULL; + seat_notify_remote_disconnect(ssh->seat); } bufchain_clear(&ssh->in_raw); @@ -788,6 +790,7 @@ static char *connect_to_host( if ((err = sk_socket_error(ssh->s)) != NULL) { ssh->s = NULL; seat_notify_remote_exit(ssh->seat); + seat_notify_remote_disconnect(ssh->seat); return dupstr(err); } } diff --git a/unix/plink.c b/unix/plink.c index 3e2a9b6b..f2310fc5 100644 --- a/unix/plink.c +++ b/unix/plink.c @@ -391,6 +391,7 @@ static const SeatVtable plink_seat_vt = { .eof = plink_eof, .get_userpass_input = plink_get_userpass_input, .notify_remote_exit = nullseat_notify_remote_exit, + .notify_remote_disconnect = nullseat_notify_remote_disconnect, .connection_fatal = console_connection_fatal, .update_specials_menu = nullseat_update_specials_menu, .get_ttymode = plink_get_ttymode, diff --git a/unix/window.c b/unix/window.c index 16306023..f787fa9b 100644 --- a/unix/window.c +++ b/unix/window.c @@ -391,6 +391,7 @@ static const SeatVtable gtk_seat_vt = { .eof = gtk_seat_eof, .get_userpass_input = gtk_seat_get_userpass_input, .notify_remote_exit = gtk_seat_notify_remote_exit, + .notify_remote_disconnect = nullseat_notify_remote_disconnect, .connection_fatal = gtk_seat_connection_fatal, .update_specials_menu = gtk_seat_update_specials_menu, .get_ttymode = gtk_seat_get_ttymode, diff --git a/utils/nullseat.c b/utils/nullseat.c index 01aaabea..9b69a07a 100644 --- a/utils/nullseat.c +++ b/utils/nullseat.c @@ -10,6 +10,7 @@ bool nullseat_eof(Seat *seat) { return true; } int nullseat_get_userpass_input( Seat *seat, prompts_t *p, bufchain *input) { return 0; } void nullseat_notify_remote_exit(Seat *seat) {} +void nullseat_notify_remote_disconnect(Seat *seat) {} void nullseat_connection_fatal(Seat *seat, const char *message) {} void nullseat_update_specials_menu(Seat *seat) {} char *nullseat_get_ttymode(Seat *seat, const char *mode) { return NULL; } diff --git a/windows/plink.c b/windows/plink.c index 3166178f..071e088d 100644 --- a/windows/plink.c +++ b/windows/plink.c @@ -85,6 +85,7 @@ static const SeatVtable plink_seat_vt = { .eof = plink_eof, .get_userpass_input = plink_get_userpass_input, .notify_remote_exit = nullseat_notify_remote_exit, + .notify_remote_disconnect = nullseat_notify_remote_disconnect, .connection_fatal = console_connection_fatal, .update_specials_menu = nullseat_update_specials_menu, .get_ttymode = nullseat_get_ttymode, diff --git a/windows/window.c b/windows/window.c index a10cb536..c644422c 100644 --- a/windows/window.c +++ b/windows/window.c @@ -332,6 +332,7 @@ static const SeatVtable win_seat_vt = { .eof = win_seat_eof, .get_userpass_input = win_seat_get_userpass_input, .notify_remote_exit = win_seat_notify_remote_exit, + .notify_remote_disconnect = nullseat_notify_remote_disconnect, .connection_fatal = win_seat_connection_fatal, .update_specials_menu = win_seat_update_specials_menu, .get_ttymode = win_seat_get_ttymode, -- cgit v1.2.3 From 0d3bb73608f1a5d1d5337e51736d48dad1436af5 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 22 May 2021 12:51:23 +0100 Subject: Initial support for in-process proxy SSH connections. This introduces a new entry to the radio-button list of proxy types, in which the 'Proxy host' box is taken to be the name of an SSH server or saved session. We make an entire subsidiary SSH connection to that host, open a direct-tcpip channel through it, and use that as the connection over which to run the primary network connection. The result is basically the same as if you used a local proxy subprocess, with a command along the lines of 'plink -batch %proxyhost -nc %host:%port'. But it's all done in-process, by having an SshProxy object implement the Socket trait to talk to the main connection, and implement Seat and LogPolicy to talk to its subsidiary SSH backend. All the refactoring in recent years has got us to the point where we can do that without both SSH instances fighting over some global variable or unique piece of infrastructure. From an end user perspective, doing SSH proxying in-process like this is a little bit easier to set up: it doesn't require you to bake the full pathname of Plink into your saved session (or to have it on the system PATH), and the SshProxy setup function automatically turns off SSH features that would be inappropriate in this context, such as additional port forwardings, or acting as a connection-sharing upstream. And it has minor advantages like getting the Event Log for the subsidiary connection interleaved in the main Event Log, as if it were stderr output from a proxy subcommand, without having to deliberately configure the subsidiary Plink into verbose mode. However, this is an initial implementation only, and it doesn't yet support the _big_ payoff for doing this in-process, which (I hope) will be the ability to handle interactive prompts from the subsidiary SSH connection via the same user interface as the primary one. For example, you might need to answer two password prompts in succession, or (the first time you use a session configured this way) confirm the host keys for both proxy and destination SSH servers. Comments in the new source file discuss some design thoughts on filling in this gap. For the moment, if the proxy SSH connection encounters any situation where an interactive prompt is needed, it will make the safe assumption, the same way 'plink -batch' would do. So it's at least no _worse_ than the existing technique of putting the proxy connection in a subprocess. --- CMakeLists.txt | 3 +- config.c | 30 ++- network.h | 6 + nosshproxy.c | 16 ++ proxy.c | 5 + putty.h | 6 +- sshproxy.c | 544 +++++++++++++++++++++++++++++++++++++++++++++++++ unix/CMakeLists.txt | 8 +- unix/config-unix.c | 2 + windows/CMakeLists.txt | 2 + windows/config.c | 2 + 11 files changed, 610 insertions(+), 14 deletions(-) create mode 100644 nosshproxy.c create mode 100644 sshproxy.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 1680223d..213f81af 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,7 +27,7 @@ add_library(settings STATIC cmdline.c settings.c) add_library(crypto STATIC - cproxy.c) + cproxy.c sshproxy.c) add_subdirectory(crypto) add_library(network STATIC @@ -109,6 +109,7 @@ add_executable(psocks psocks.c norand.c nocproxy.c + nosshproxy.c ssh/portfwd.c) target_link_libraries(psocks eventloop console network utils diff --git a/config.c b/config.c index ca808511..0932f894 100644 --- a/config.c +++ b/config.c @@ -2477,16 +2477,26 @@ void setup_config_box(struct controlbox *b, bool midsession, "Options controlling proxy usage"); s = ctrl_getset(b, "Connection/Proxy", "basics", NULL); - ctrl_radiobuttons(s, "Proxy type:", 't', 3, - HELPCTX(proxy_type), - conf_radiobutton_handler, - I(CONF_proxy_type), - "None", I(PROXY_NONE), - "SOCKS 4", I(PROXY_SOCKS4), - "SOCKS 5", I(PROXY_SOCKS5), - "HTTP", I(PROXY_HTTP), - "Telnet", I(PROXY_TELNET), - NULL); + c = ctrl_radiobuttons(s, "Proxy type:", 't', 3, + HELPCTX(proxy_type), + conf_radiobutton_handler, + I(CONF_proxy_type), + "None", I(PROXY_NONE), + "SOCKS 4", I(PROXY_SOCKS4), + "SOCKS 5", I(PROXY_SOCKS5), + "HTTP", I(PROXY_HTTP), + "Telnet", I(PROXY_TELNET), + NULL); + if (ssh_proxy_supported) { + /* Add an extra radio button to the above list. */ + c->radio.nbuttons++; + c->radio.buttons = + sresize(c->radio.buttons, c->radio.nbuttons, char *); + c->radio.buttons[c->radio.nbuttons-1] = dupstr("SSH"); + c->radio.buttondata = + sresize(c->radio.buttondata, c->radio.nbuttons, intorptr); + c->radio.buttondata[c->radio.nbuttons-1] = I(PROXY_SSH); + } ctrl_columns(s, 2, 80, 20); c = ctrl_editbox(s, "Proxy hostname", 'y', 100, HELPCTX(proxy_main), diff --git a/network.h b/network.h index 89419fb7..92662ac7 100644 --- a/network.h +++ b/network.h @@ -127,6 +127,12 @@ Socket *platform_new_connection(SockAddr *addr, const char *hostname, bool oobinline, bool nodelay, bool keepalive, Plug *plug, Conf *conf); +/* callback for SSH jump-host proxying */ +Socket *sshproxy_new_connection(SockAddr *addr, const char *hostname, + int port, bool privport, + bool oobinline, bool nodelay, bool keepalive, + Plug *plug, Conf *conf); + /* socket functions */ void sk_init(void); /* called once at program startup */ diff --git a/nosshproxy.c b/nosshproxy.c new file mode 100644 index 00000000..e47a860c --- /dev/null +++ b/nosshproxy.c @@ -0,0 +1,16 @@ +/* + * nosshproxy.c: stub implementation of sshproxy_new_connection(). + */ + +#include "putty.h" +#include "network.h" + +const bool ssh_proxy_supported = false; + +Socket *sshproxy_new_connection(SockAddr *addr, const char *hostname, + int port, bool privport, + bool oobinline, bool nodelay, bool keepalive, + Plug *plug, Conf *conf) +{ + return NULL; +} diff --git a/proxy.c b/proxy.c index d7069cb0..ed984e4e 100644 --- a/proxy.c +++ b/proxy.c @@ -405,6 +405,11 @@ Socket *new_connection(SockAddr *addr, const char *hostname, Socket *sret; int type; + if ((sret = sshproxy_new_connection(addr, hostname, port, privport, + oobinline, nodelay, keepalive, + plug, conf)) != NULL) + return sret; + if ((sret = platform_new_connection(addr, hostname, port, privport, oobinline, nodelay, keepalive, plug, conf)) != NULL) diff --git a/putty.h b/putty.h index 75dd5a86..08923ba9 100644 --- a/putty.h +++ b/putty.h @@ -474,7 +474,8 @@ enum { * Proxy types. */ PROXY_NONE, PROXY_SOCKS4, PROXY_SOCKS5, - PROXY_HTTP, PROXY_TELNET, PROXY_CMD, PROXY_FUZZ + PROXY_HTTP, PROXY_TELNET, PROXY_SSH, + PROXY_CMD, PROXY_FUZZ }; enum { @@ -620,6 +621,9 @@ enum { #define BACKEND_SUPPORTS_NC_HOST 0x04 /* Backend can honour CONF_ssh_nc_host */ +/* In (no)sshproxy.c */ +extern const bool ssh_proxy_supported; + struct Backend { const BackendVtable *vt; }; diff --git a/sshproxy.c b/sshproxy.c new file mode 100644 index 00000000..0ffa74ff --- /dev/null +++ b/sshproxy.c @@ -0,0 +1,544 @@ +/* + * sshproxy.c: implement a Socket type that talks to an entire + * subsidiary SSH connection (sometimes called a 'jump host'). + */ + +#include +#include + +#include "putty.h" +#include "ssh.h" +#include "network.h" +#include "storage.h" + +const bool ssh_proxy_supported = true; + +/* + * TODO for future work: + * + * At present, this use of SSH as a proxy is 100% noninteractive. In + * our implementations of the Seat and LogPolicy traits, every method + * that involves interactively prompting the user is implemented by + * pretending the user gave a safe default answer. So the effect is + * very much as if you'd used 'plink -batch' as a proxy subprocess - + * password prompts are cancelled and any dubious host key or crypto + * primitive is unconditionally rejected - except that it all happens + * in-process, making it mildly more convenient to set up, perhaps a + * hair faster, and you get all the Event Log data in one place. + * + * But the biggest benefit of in-process SSH proxying would be that + * the interactive prompts from the sub-SSH can be passed through to + * the end user. If your jump host and your ultimate destination host + * both require password authentication, you should be able to type + * both password in sequence into the PuTTY terminal window; if you're + * running a session of this kind for the first time, you should be + * able to confirm both host keys one after another; if you need to + * store SSH packet logs from both SSH connections, you should be able + * to respond in turn to two askappend() prompts if necessary. And in + * the current state of the code, none of that is yet implemented. + * + * To fix that, we'd have to start by arranging for this proxy + * implementation to get hold of the 'real' (outer) Seat and LogPolicy + * objects, which probably means that they'd have to be passed to + * new_connection. Then, each method in this file that receives an + * interactive prompt request would handle it by passing it on to the + * outer Seat or LogPolicy, with some kind of tweak that would allow + * the end user to see clearly that the prompt had come from the proxy + * SSH connection rather than the primary one. + * + * One problem here is that not all uses of new_connection _have_ a + * Seat or a LogPolicy available. So we'd also have to check if those + * pointers are NULL, and if so, fall back to the existing behaviour + * of behaving as if in batch mode. + */ + +typedef struct SshProxy { + char *errmsg; + Conf *conf; + LogContext *logctx; + Backend *backend; + + ProxyStderrBuf psb; + Plug *plug; + + bool frozen; + bufchain ssh_to_socket; + bool rcvd_eof_ssh_to_socket, sent_eof_ssh_to_socket; + + /* Traits implemented: we're a Socket from the point of view of + * the client connection, and a Seat from the POV of the SSH + * backend we instantiate. */ + Socket sock; + LogPolicy logpolicy; + Seat seat; +} SshProxy; + +static Plug *sshproxy_plug(Socket *s, Plug *p) +{ + SshProxy *sp = container_of(s, SshProxy, sock); + Plug *oldplug = sp->plug; + if (p) + sp->plug = p; + return oldplug; +} + +static void sshproxy_close(Socket *s) +{ + SshProxy *sp = container_of(s, SshProxy, sock); + + sfree(sp->errmsg); + conf_free(sp->conf); + if (sp->backend) + backend_free(sp->backend); + if (sp->logctx) + log_free(sp->logctx); + bufchain_clear(&sp->ssh_to_socket); + + delete_callbacks_for_context(sp); + sfree(sp); +} + +static size_t sshproxy_write(Socket *s, const void *data, size_t len) +{ + SshProxy *sp = container_of(s, SshProxy, sock); + if (!sp->backend) + return 0; + return backend_send(sp->backend, data, len); +} + +static size_t sshproxy_write_oob(Socket *s, const void *data, size_t len) +{ + /* + * oob data is treated as inband; nasty, but nothing really + * better we can do + */ + return sshproxy_write(s, data, len); +} + +static void sshproxy_write_eof(Socket *s) +{ + SshProxy *sp = container_of(s, SshProxy, sock); + if (!sp->backend) + return; + backend_special(sp->backend, SS_EOF, 0); +} + +static void try_send_ssh_to_socket(void *ctx); + +static void sshproxy_set_frozen(Socket *s, bool is_frozen) +{ + SshProxy *sp = container_of(s, SshProxy, sock); + sp->frozen = is_frozen; + if (!sp->frozen) + queue_toplevel_callback(try_send_ssh_to_socket, sp); +} + +static const char *sshproxy_socket_error(Socket *s) +{ + SshProxy *sp = container_of(s, SshProxy, sock); + return sp->errmsg; +} + +static SocketPeerInfo *sshproxy_peer_info(Socket *s) +{ + return NULL; +} + +static const SocketVtable SshProxy_sock_vt = { + .plug = sshproxy_plug, + .close = sshproxy_close, + .write = sshproxy_write, + .write_oob = sshproxy_write_oob, + .write_eof = sshproxy_write_eof, + .set_frozen = sshproxy_set_frozen, + .socket_error = sshproxy_socket_error, + .peer_info = sshproxy_peer_info, +}; + +static void sshproxy_eventlog(LogPolicy *lp, const char *event) +{ + SshProxy *sp = container_of(lp, SshProxy, logpolicy); + log_proxy_stderr(sp->plug, &sp->psb, event, strlen(event)); + log_proxy_stderr(sp->plug, &sp->psb, "\n", 1); +} + +static int sshproxy_askappend(LogPolicy *lp, Filename *filename, + void (*callback)(void *ctx, int result), + void *ctx) +{ + /* + * TODO: if we had access to the outer LogPolicy, we could pass on + * this request to the end user. (But we'd still have to have this + * code as a fallback in case there isn't a LogPolicy available.) + */ + char *msg = dupprintf("Log file \"%s\" already exists; logging cancelled", + filename_to_str(filename)); + sshproxy_eventlog(lp, msg); + sfree(msg); + return 0; +} + +static void sshproxy_logging_error(LogPolicy *lp, const char *event) +{ + /* + * TODO: if we had access to the outer LogPolicy, we could pass on + * this request to _its_ logging_error method, where it would be + * more prominent than just dumping it in the outer SSH + * connection's Event Log. (But we'd still have to have this code + * as a fallback in case there isn't a LogPolicy available.) + */ + char *msg = dupprintf("Logging error: %s", event); + sshproxy_eventlog(lp, msg); + sfree(msg); +} + +static const LogPolicyVtable SshProxy_logpolicy_vt = { + .eventlog = sshproxy_eventlog, + .askappend = sshproxy_askappend, + .logging_error = sshproxy_logging_error, + .verbose = null_lp_verbose_no, +}; + +/* + * Function called when we encounter an error during connection setup that's + * likely to be the cause of terminating the proxy SSH connection. Putting it + * in the Event Log is useful on general principles; also putting it in + * sp->errmsg meaks that it will be passed back through plug_closing when the + * proxy SSH connection actually terminates, so that the end user will see + * what went wrong in the proxy connection. + */ +static void sshproxy_error(SshProxy *sp, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + char *msg = dupvprintf(fmt, ap); + va_end(ap); + + if (!sp->errmsg) + sp->errmsg = dupstr(msg); + + sshproxy_eventlog(&sp->logpolicy, msg); + sfree(msg); +} + +static void try_send_ssh_to_socket(void *ctx) +{ + SshProxy *sp = (SshProxy *)ctx; + + if (sp->frozen) + return; + + while (bufchain_size(&sp->ssh_to_socket)) { + ptrlen pl = bufchain_prefix(&sp->ssh_to_socket); + plug_receive(sp->plug, 0, pl.ptr, pl.len); + bufchain_consume(&sp->ssh_to_socket, pl.len); + } + + if (sp->rcvd_eof_ssh_to_socket && + !sp->sent_eof_ssh_to_socket) { + sp->sent_eof_ssh_to_socket = true; + plug_closing(sp->plug, sp->errmsg, 0, 0); + } +} + +static size_t sshproxy_output(Seat *seat, bool is_stderr, + const void *data, size_t len) +{ + SshProxy *sp = container_of(seat, SshProxy, seat); + bufchain_add(&sp->ssh_to_socket, data, len); + try_send_ssh_to_socket(sp); + return bufchain_size(&sp->ssh_to_socket); +} + +static bool sshproxy_eof(Seat *seat) +{ + SshProxy *sp = container_of(seat, SshProxy, seat); + sp->rcvd_eof_ssh_to_socket = true; + try_send_ssh_to_socket(sp); + return false; +} + +static void sshproxy_notify_remote_disconnect(Seat *seat) +{ + SshProxy *sp = container_of(seat, SshProxy, seat); + if (!sp->rcvd_eof_ssh_to_socket && !backend_connected(sp->backend)) + sshproxy_eof(seat); +} + +static int sshproxy_get_userpass_input(Seat *seat, prompts_t *p, + bufchain *input) +{ + /* + * TODO: if we had access to the outer Seat, we could pass on this + * prompts_t to *its* get_userpass_input method, appropriately + * adjusted to indicate that it comes from the proxy SSH + * connection. (But we'd still have to have this code as a + * fallback in case there isn't a Seat available.) + * + * Design question: how does that 'appropriately adjusted' + * interact with the possibility of multiple calls to this + * function with the same prompts_t? Should we redo the + * modification every time? Or provide some kind of callback that + * userauth can use to do it once up front? Or something else? + * + * Also, we'll need to be sure that the outer Seat is in the + * correct trust status before passing prompts along to it. For + * SSH, you'd certainly expect that to be OK, on the basis that + * the primary SSH connection won't set the Seat to untrusted mode + * until it finishes its userauth phase, which won't happen until + * long after _we've_ finished _our_ userauth phase. But what if + * the primary connection is something like Telnet, which goes + * into untrusted mode during startup? We may find we have to do + * some more complicated piece of plumbing that lets us take some + * kind of a preliminary lease on the Seat and defer anything the + * primary backend tries to do to it. + */ + SshProxy *sp = container_of(seat, SshProxy, seat); + sshproxy_error(sp, "Unable to provide interactive authentication " + "requested by proxy SSH connection"); + return 0; +} + +static void sshproxy_connection_fatal_callback(void *vctx) +{ + SshProxy *sp = (SshProxy *)vctx; + plug_closing(sp->plug, sp->errmsg, 0, true); +} + +static void sshproxy_connection_fatal(Seat *seat, const char *message) +{ + SshProxy *sp = container_of(seat, SshProxy, seat); + if (!sp->errmsg) { + sp->errmsg = dupprintf( + "fatal error in proxy SSH connection: %s", message); + queue_toplevel_callback(sshproxy_connection_fatal_callback, sp); + } +} + +static int sshproxy_verify_ssh_host_key( + Seat *seat, const char *host, int port, const char *keytype, + char *keystr, const char *keydisp, char **key_fingerprints, + void (*callback)(void *ctx, int result), void *ctx) +{ + SshProxy *sp = container_of(seat, SshProxy, seat); + + /* + * TODO: if we had access to the outer Seat, we could pass on this + * request to *its* verify_ssh_host_key method, appropriately + * adjusted to indicate that it comes from the proxy SSH + * connection. (But we'd still have to have this code as a + * fallback in case there isn't a Seat available.) + * + * Instead, we have to behave as if we're in batch mode: directly + * verify the host key against the cache, and if that fails, take + * the safe option in the absence of interactive confirmation, and + * abort the connection. + */ + int hkstatus = verify_host_key(host, port, keytype, keystr); + FingerprintType fptype = ssh2_pick_default_fingerprint(key_fingerprints); + + switch (hkstatus) { + case 0: /* host key matched */ + return 1; + + case 1: /* host key not in cache at all */ + sshproxy_error(sp, "Host key not in cache for %s:%d (fingerprint %s). " + "Abandoning proxy SSH connection.", host, port, + key_fingerprints[fptype]); + return 0; + + case 2: + sshproxy_error(sp, "HOST KEY DOES NOT MATCH CACHE for %s:%d " + "(fingerprint %s). Abandoning proxy SSH connection.", + host, port, key_fingerprints[fptype]); + return 0; + + default: + unreachable("bad return value from verify_host_key"); + } +} + +static int sshproxy_confirm_weak_crypto_primitive( + Seat *seat, const char *algtype, const char *algname, + void (*callback)(void *ctx, int result), void *ctx) +{ + SshProxy *sp = container_of(seat, SshProxy, seat); + + /* + * TODO: if we had access to the outer Seat, we could pass on this + * request to *its* confirm_weak_crypto_primitive method, + * appropriately adjusted to indicate that it comes from the proxy + * SSH connection. (But we'd still have to have this code as a + * fallback in case there isn't a Seat available.) + */ + sshproxy_error(sp, "First %s supported by server is %s, below warning " + "threshold. Abandoning proxy SSH connection.", + algtype, algname); + return 0; +} + +static int sshproxy_confirm_weak_cached_hostkey( + Seat *seat, const char *algname, const char *betteralgs, + void (*callback)(void *ctx, int result), void *ctx) +{ + SshProxy *sp = container_of(seat, SshProxy, seat); + + /* + * TODO: if we had access to the outer Seat, we could pass on this + * request to *its* confirm_weak_cached_hostkey method, + * appropriately adjusted to indicate that it comes from the proxy + * SSH connection. (But we'd still have to have this code as a + * fallback in case there isn't a Seat available.) + */ + sshproxy_error(sp, "First host key type stored for server is %s, below " + "warning threshold. Abandoning proxy SSH connection.", + algname); + return 0; +} + +static bool sshproxy_set_trust_status(Seat *seat, bool trusted) +{ + /* + * This is called by the proxy SSH connection, to set our Seat + * into a given trust status. We can safely do nothing here and + * return true to claim we did something (effectively eliminating + * the spoofing defences completely, by suppressing the 'press + * Return to begin session' prompt and not providing anything in + * place of it), on the basis that session I/O from the proxy SSH + * connection is never passed directly on to the end user, so a + * malicious proxy SSH server wouldn't be able to spoof our human + * in any case. + */ + return true; +} + +static const SeatVtable SshProxy_seat_vt = { + .output = sshproxy_output, + .eof = sshproxy_eof, + .get_userpass_input = sshproxy_get_userpass_input, + .notify_remote_exit = nullseat_notify_remote_exit, + .notify_remote_disconnect = sshproxy_notify_remote_disconnect, + .connection_fatal = sshproxy_connection_fatal, + .update_specials_menu = nullseat_update_specials_menu, + .get_ttymode = nullseat_get_ttymode, + .set_busy_status = nullseat_set_busy_status, + .verify_ssh_host_key = sshproxy_verify_ssh_host_key, + .confirm_weak_crypto_primitive = sshproxy_confirm_weak_crypto_primitive, + .confirm_weak_cached_hostkey = sshproxy_confirm_weak_cached_hostkey, + .is_utf8 = nullseat_is_never_utf8, + .echoedit_update = nullseat_echoedit_update, + .get_x_display = nullseat_get_x_display, + .get_windowid = nullseat_get_windowid, + .get_window_pixel_size = nullseat_get_window_pixel_size, + .stripctrl_new = nullseat_stripctrl_new, + .set_trust_status = sshproxy_set_trust_status, + .verbose = nullseat_verbose_no, + .interactive = nullseat_interactive_no, + .get_cursor_position = nullseat_get_cursor_position, + +}; + +Socket *sshproxy_new_connection(SockAddr *addr, const char *hostname, + int port, bool privport, + bool oobinline, bool nodelay, bool keepalive, + Plug *plug, Conf *clientconf) +{ + SshProxy *sp = snew(SshProxy); + memset(sp, 0, sizeof(*sp)); + + sp->sock.vt = &SshProxy_sock_vt; + sp->logpolicy.vt = &SshProxy_logpolicy_vt; + sp->seat.vt = &SshProxy_seat_vt; + sp->plug = plug; + psb_init(&sp->psb); + bufchain_init(&sp->ssh_to_socket); + + sp->conf = conf_new(); + /* Try to treat proxy_hostname as the title of a saved session. If + * that fails, set up a default Conf of our own treating it as a + * hostname. */ + const char *proxy_hostname = conf_get_str(clientconf, CONF_proxy_host); + if (do_defaults(proxy_hostname, sp->conf)) { + if (!conf_launchable(sp->conf)) { + sp->errmsg = dupprintf("saved session '%s' is not launchable", + proxy_hostname); + return &sp->sock; + } + } else { + do_defaults(NULL, sp->conf); + /* In hostname mode, we default to PROT_SSH. This is more useful than + * the obvious approach of defaulting to the protocol defined in + * Default Settings, because only SSH (ok, and bare ssh-connection) + * can be used for this kind of proxy. */ + conf_set_int(sp->conf, CONF_protocol, PROT_SSH); + conf_set_str(sp->conf, CONF_host, proxy_hostname); + conf_set_int(sp->conf, CONF_port, + conf_get_int(clientconf, CONF_proxy_port)); + } + const char *proxy_username = conf_get_str(clientconf, CONF_proxy_username); + if (*proxy_username) + conf_set_str(sp->conf, CONF_username, proxy_username); + + const struct BackendVtable *backvt = backend_vt_from_proto( + conf_get_int(sp->conf, CONF_protocol)); + + /* + * We don't actually need an _SSH_ session specifically: it's also + * OK to use PROT_SSHCONN, because really, the criterion is + * whether setting CONF_ssh_nc_host will do anything useful. So + * our check is for whether the backend sets the flag promising + * that it does. + */ + if (!(backvt->flags & BACKEND_SUPPORTS_NC_HOST)) { + sp->errmsg = dupprintf("saved session '%s' is not an SSH session", + proxy_hostname); + return &sp->sock; + } + + /* + * Turn off SSH features we definitely don't want. It would be + * awkward and counterintuitive to have the proxy SSH connection + * become a connection-sharing upstream (but it's fine to have it + * be a downstream, if that's configured). And we don't want to + * open X forwardings, agent forwardings or (other) port + * forwardings as a side effect of this one operation. + */ + conf_set_bool(sp->conf, CONF_ssh_connection_sharing_upstream, false); + conf_set_bool(sp->conf, CONF_x11_forward, false); + conf_set_bool(sp->conf, CONF_agentfwd, false); + for (const char *subkey; + (subkey = conf_get_str_nthstrkey(sp->conf, CONF_portfwd, 0)) != NULL;) + conf_del_str_str(sp->conf, CONF_portfwd, subkey); + + /* + * We'll only be running one channel through this connection + * (since we've just turned off all the other things we might have + * done with it), so we can configure it as simple. + */ + conf_set_bool(sp->conf, CONF_ssh_simple, true); + + /* + * Configure the main channel of this SSH session to be a + * direct-tcpip connection to the destination host/port. + */ + conf_set_str(sp->conf, CONF_ssh_nc_host, hostname); + conf_set_int(sp->conf, CONF_ssh_nc_port, port); + + sp->logctx = log_init(&sp->logpolicy, sp->conf); + + char *error, *realhost; + error = backend_init(backvt, &sp->seat, &sp->backend, sp->logctx, sp->conf, + conf_get_str(sp->conf, CONF_host), + conf_get_int(sp->conf, CONF_port), + &realhost, nodelay, + conf_get_bool(sp->conf, CONF_tcp_keepalives)); + if (error) { + sp->errmsg = dupprintf("unable to open SSH proxy connection: %s", + error); + return &sp->sock; + } + + sfree(realhost); + + return &sp->sock; +} diff --git a/unix/CMakeLists.txt b/unix/CMakeLists.txt index f724bbe7..7ce5e669 100644 --- a/unix/CMakeLists.txt +++ b/unix/CMakeLists.txt @@ -137,7 +137,8 @@ if(GTK_FOUND) askpass.c x11.c noise.c - ${CMAKE_SOURCE_DIR}/ssh/x11fwd.c) + ${CMAKE_SOURCE_DIR}/ssh/x11fwd.c + ${CMAKE_SOURCE_DIR}/nosshproxy.c) target_link_libraries(pageant eventloop console agent settings network crypto utils ${GTK_LIBRARIES}) @@ -148,6 +149,7 @@ if(GTK_FOUND) main-gtk-simple.c ${CMAKE_SOURCE_DIR}/be_none.c ${CMAKE_SOURCE_DIR}/nogss.c + ${CMAKE_SOURCE_DIR}/nosshproxy.c pty.c) target_link_libraries(pterm guiterminal eventloop settings charset utils @@ -160,6 +162,7 @@ if(GTK_FOUND) ${CMAKE_SOURCE_DIR}/nocmdline.c ${CMAKE_SOURCE_DIR}/be_none.c ${CMAKE_SOURCE_DIR}/nogss.c + ${CMAKE_SOURCE_DIR}/nosshproxy.c pty.c) target_link_libraries(ptermapp guiterminal eventloop settings charset utils @@ -193,7 +196,8 @@ if(GTK_FOUND) ${CMAKE_SOURCE_DIR}/be_nos_s.c ${CMAKE_SOURCE_DIR}/nogss.c ${CMAKE_SOURCE_DIR}/norand.c - ${CMAKE_SOURCE_DIR}/nocproxy.c) + ${CMAKE_SOURCE_DIR}/nocproxy.c + ${CMAKE_SOURCE_DIR}/nosshproxy.c) target_link_libraries(puttytel guiterminal eventloop otherbackends settings network charset utils ${GTK_LIBRARIES} ${X11_LIBRARIES}) diff --git a/unix/config-unix.c b/unix/config-unix.c index 8397a0ac..7efff05d 100644 --- a/unix/config-unix.c +++ b/unix/config-unix.c @@ -51,6 +51,8 @@ void unix_setup_config_box(struct controlbox *b, bool midsession, int protocol) c->radio.buttondata = sresize(c->radio.buttondata, c->radio.nbuttons, intorptr); c->radio.buttondata[c->radio.nbuttons-1] = I(PROXY_CMD); + if (c->radio.ncolumns < 4) + c->radio.ncolumns = 4; break; } } diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt index a46c9a4b..1daf926e 100644 --- a/windows/CMakeLists.txt +++ b/windows/CMakeLists.txt @@ -104,6 +104,7 @@ add_executable(puttytel ${CMAKE_SOURCE_DIR}/nogss.c ${CMAKE_SOURCE_DIR}/norand.c ${CMAKE_SOURCE_DIR}/nocproxy.c + ${CMAKE_SOURCE_DIR}/nosshproxy.c puttytel.rc) add_dependencies(puttytel generated_licence_h) target_link_libraries(puttytel @@ -143,6 +144,7 @@ if(HAVE_CONPTY) be_conpty.c ${CMAKE_SOURCE_DIR}/nogss.c ${CMAKE_SOURCE_DIR}/norand.c + ${CMAKE_SOURCE_DIR}/nosshproxy.c pterm.rc) add_dependencies(pterm generated_licence_h) target_link_libraries(pterm diff --git a/windows/config.c b/windows/config.c index fab3240f..a32944a6 100644 --- a/windows/config.c +++ b/windows/config.c @@ -374,6 +374,8 @@ void win_setup_config_box(struct controlbox *b, HWND *hwndp, bool has_help, c->radio.buttondata = sresize(c->radio.buttondata, c->radio.nbuttons, intorptr); c->radio.buttondata[c->radio.nbuttons-1] = I(PROXY_CMD); + if (c->radio.ncolumns < 4) + c->radio.ncolumns = 4; break; } } -- cgit v1.2.3 From d008d235f3841139daca39efee25cd5423ce31b8 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 23 May 2021 08:59:13 +0100 Subject: Fix tight loop on reading truncated key files. In commit 9cc586e605e3db1 I changed the low-level key-file reading routines like read_header and read_body so that they read from a BinarySource via get_byte(), rather than from a FILE * via fgetc. But I forgot that the two functions don't signal end-of-file the same way, so testing the return value of get_byte() against EOF is pointless and will never match, and conversely, real EOF won't be spotted unless you also examine the error indicator in the BinarySource. As a result, a key file that ends without a trailing newline will cause a tight loop in one of those low-level read routines. --- sshpubk.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sshpubk.c b/sshpubk.c index f7a9bb33..e7197de3 100644 --- a/sshpubk.c +++ b/sshpubk.c @@ -480,7 +480,7 @@ static bool read_header(BinarySource *src, char *header) while (1) { c = get_byte(src); - if (c == '\n' || c == '\r' || c == EOF) + if (c == '\n' || c == '\r' || get_err(src)) return false; /* failure */ if (c == ':') { c = get_byte(src); @@ -503,10 +503,10 @@ static char *read_body(BinarySource *src) while (1) { int c = get_byte(src); - if (c == '\r' || c == '\n' || c == EOF) { - if (c != EOF) { + if (c == '\r' || c == '\n' || get_err(src)) { + if (!get_err(src)) { c = get_byte(src); - if (c != '\r' && c != '\n') + if (c != '\r' && c != '\n' && !get_err(src)) src->pos--; } return strbuf_to_str(buf); -- cgit v1.2.3 From 415927e9b0ad6aee6b9a0ba184fc420c51e05b94 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 24 May 2021 13:52:51 +0100 Subject: Make SSH proxying conditional on CONF_proxy_type! In commit 0d3bb73608f1a5d, I introduced the new SSH / jump-host proxy type, which should be invoked by proxy.c when CONF_proxy_type is set to PROXY_SSH. In fact, I left out the check, so it's invoked by proxy.c _unconditionally_, after the check to see whether proxying is required at all. So any saved session configured with any other proxy type (other than PROXY_NONE) would be treated as PROXY_SSH by mistake. How embarrassing. I did remember at one point that I needed to fix this, but it fell out of my head before I pushed! --- proxy.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/proxy.c b/proxy.c index ed984e4e..1a737cae 100644 --- a/proxy.c +++ b/proxy.c @@ -395,7 +395,9 @@ Socket *new_connection(SockAddr *addr, const char *hostname, bool oobinline, bool nodelay, bool keepalive, Plug *plug, Conf *conf) { - if (conf_get_int(conf, CONF_proxy_type) != PROXY_NONE && + int type = conf_get_int(conf, CONF_proxy_type); + + if (type != PROXY_NONE && proxy_for_destination(addr, hostname, port, conf)) { ProxySocket *ret; @@ -403,9 +405,9 @@ Socket *new_connection(SockAddr *addr, const char *hostname, char *proxy_canonical_name; const char *proxy_type; Socket *sret; - int type; - if ((sret = sshproxy_new_connection(addr, hostname, port, privport, + if (type == PROXY_SSH && + (sret = sshproxy_new_connection(addr, hostname, port, privport, oobinline, nodelay, keepalive, plug, conf)) != NULL) return sret; @@ -435,7 +437,6 @@ Socket *new_connection(SockAddr *addr, const char *hostname, ret->state = PROXY_STATE_NEW; ret->negotiate = NULL; - type = conf_get_int(conf, CONF_proxy_type); if (type == PROXY_HTTP) { ret->negotiate = proxy_http_negotiate; proxy_type = "HTTP"; -- cgit v1.2.3 From 02aca17f360bbd8409c761af3713b11936f3af78 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 24 May 2021 13:57:39 +0100 Subject: Reorder proxy-type enum for backwards compatibility. Commit 0d3bb73608f1a5d inserted PROXY_SSH just before PROXY_CMD, so that it got the numerical value that PROXY_CMD had before. But that meant that any saved session configured as PROXY_CMD by an older build of PuTTY would be regarded as PROXY_SSH by the new version - even after the previous commit fixed the unconditional use of SSH proxying regardless of the type setting. Now PROXY_CMD has its old value back, and PROXY_SSH is inserted at the _end_ of the enum. (Or rather, just before the extra-weird PROXY_FUZZ, which is allowed to have an unstable numerical value because it's never stored in a saved session at all.) This is all rather unsatisfactory, and makes me wish I'd got round to reworking the saved data format to use keywords in place of integers. --- putty.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/putty.h b/putty.h index 08923ba9..04f91879 100644 --- a/putty.h +++ b/putty.h @@ -474,8 +474,8 @@ enum { * Proxy types. */ PROXY_NONE, PROXY_SOCKS4, PROXY_SOCKS5, - PROXY_HTTP, PROXY_TELNET, PROXY_SSH, - PROXY_CMD, PROXY_FUZZ + PROXY_HTTP, PROXY_TELNET, PROXY_CMD, PROXY_SSH, + PROXY_FUZZ }; enum { -- cgit v1.2.3 From 17c57e1078c8fabd5417175247b38ce7b1643ca4 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 24 May 2021 13:06:10 +0100 Subject: Reorganise Windows HANDLE management. Before commit 6e69223dc262755, Pageant would stop working after a certain number of PuTTYs were active at the same time. (At most about 60, but maybe fewer - see below.) This was because of two separate bugs. The easy one, fixed in 6e69223dc262755 itself, was that PuTTY left each named-pipe connection to Pageant open for the rest of its lifetime. So the real problem was that Pageant had too many active connections at once. (And since a given PuTTY might make multiple connections during userauth - one to list keys, and maybe another to actually make a signature - that was why the number of _PuTTYs_ might vary.) It was clearly a bug that PuTTY was leaving connections to Pageant needlessly open. But it was _also_ a bug that Pageant couldn't handle more than about 60 at once. In this commit, I fix that secondary bug. The cause of the bug is that the WaitForMultipleObjects function family in the Windows API have a limit on the number of HANDLE objects they can select between. The limit is MAXIMUM_WAIT_OBJECTS, defined to be 64. And handle-io.c was using a separate event object for each I/O subthread to communicate back to the main thread, so as soon as all those event objects (plus a handful of other HANDLEs) added up to more than 64, we'd start passing an overlarge handle array to WaitForMultipleObjects, and it would start not doing what we wanted. To fix this, I've reorganised handle-io.c so that all its subthreads share just _one_ event object to signal readiness back to the main thread. There's now a linked list of 'struct handle' objects that are ready to be processed, protected by a CRITICAL_SECTION. Each subthread signals readiness by adding itself to the linked list, and setting the event object to indicate that the list is now non-empty. When the main thread receives the event, it iterates over the whole list processing all the ready handles. (Each 'struct handle' still has a separate event object for the main thread to use to communicate _to_ the subthread. That's OK, because no thread is ever waiting on all those events at once: each subthread only waits on its own.) The previous HT_FOREIGN system didn't really fit into this framework. So I've moved it out into its own system. There's now a handle-wait.c which deals with the relatively simple job of managing a list of handles that need to be waited for, each with a callback function; that's what communicates a list of HANDLEs to event loops, and receives the notification when the event loop notices that one of them has done something. And handle-io.c is now just one client of handle-wait.c, providing a single HANDLE to the event loop, and dealing internally with everything that needs to be done when that handle fires. The new top-level handle-wait.c system *still* can't deal with more than MAXIMUM_WAIT_OBJECTS. At the moment, I'm reasonably convinced it doesn't need to: the only kind of HANDLE that any of our tools could previously have needed to wait on more than one of was the one in handle-io.c that I've just removed. But I've left some assertions and a TODO comment in there just in case we need to change that in future. --- windows/CMakeLists.txt | 10 +- windows/cliloop.c | 22 ++-- windows/conpty.c | 9 +- windows/handle-io.c | 240 ++++++++++++++++++-------------------------- windows/handle-wait.c | 144 ++++++++++++++++++++++++++ windows/named-pipe-server.c | 9 +- windows/pageant.c | 17 ++-- windows/platform.h | 21 +++- windows/window.c | 15 ++- 9 files changed, 299 insertions(+), 188 deletions(-) create mode 100644 windows/handle-wait.c diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt index 1daf926e..dc97e675 100644 --- a/windows/CMakeLists.txt +++ b/windows/CMakeLists.txt @@ -32,7 +32,7 @@ if(NOT HAVE_STRTOUMAX) add_sources_from_current_dir(utils utils/strtoumax.c) endif() add_sources_from_current_dir(eventloop - cliloop.c handle-io.c) + cliloop.c handle-wait.c) add_sources_from_current_dir(console select-cli.c nohelp.c console.c) add_sources_from_current_dir(settings @@ -53,6 +53,14 @@ add_sources_from_current_dir(guiterminal dialog.c controls.c config.c printing.c jump-list.c sizetip.c) add_dependencies(guiterminal generated_licence_h) # dialog.c uses licence.h +# This object awkwardly needs to live in the network library as well +# as the eventloop library, in case it didn't get pulled in from the +# latter before handle-socket.c needed it. +add_library(handle-io OBJECT + handle-io.c) +target_sources(eventloop PRIVATE $) +target_sources(network PRIVATE $) + add_library(guimisc STATIC select-gui.c) diff --git a/windows/cliloop.c b/windows/cliloop.c index 26a4d3aa..eced54ca 100644 --- a/windows/cliloop.c +++ b/windows/cliloop.c @@ -8,8 +8,6 @@ void cli_main_loop(cliloop_pre_t pre, cliloop_post_t post, void *ctx) now = GETTICKCOUNT(); while (true) { - int nhandles; - HANDLE *handles; DWORD n; DWORD ticks; @@ -34,25 +32,25 @@ void cli_main_loop(cliloop_pre_t pre, cliloop_post_t post, void *ctx) * get WAIT_TIMEOUT */ } - handles = handle_get_events(&nhandles); + HandleWaitList *hwl = get_handle_wait_list(); size_t winselcli_index = -(size_t)1; - size_t extra_base = nhandles; + size_t extra_base = hwl->nhandles; if (winselcli_event != INVALID_HANDLE_VALUE) { + assert(extra_base < MAXIMUM_WAIT_OBJECTS); winselcli_index = extra_base++; - handles = sresize(handles, extra_base, HANDLE); - handles[winselcli_index] = winselcli_event; + hwl->handles[winselcli_index] = winselcli_event; } size_t total_handles = extra_base + n_extra_handles; - handles = sresize(handles, total_handles, HANDLE); + assert(total_handles < MAXIMUM_WAIT_OBJECTS); for (size_t i = 0; i < n_extra_handles; i++) - handles[extra_base + i] = extra_handles[i]; + hwl->handles[extra_base + i] = extra_handles[i]; - n = WaitForMultipleObjects(total_handles, handles, false, ticks); + n = WaitForMultipleObjects(total_handles, hwl->handles, false, ticks); size_t extra_handle_index = n_extra_handles; - if ((unsigned)(n - WAIT_OBJECT_0) < (unsigned)nhandles) { - handle_got_event(handles[n - WAIT_OBJECT_0]); + if ((unsigned)(n - WAIT_OBJECT_0) < (unsigned)hwl->nhandles) { + handle_wait_activate(hwl, n - WAIT_OBJECT_0); } else if (winselcli_event != INVALID_HANDLE_VALUE && n == WAIT_OBJECT_0 + winselcli_index) { WSANETWORKEVENTS things; @@ -122,7 +120,7 @@ void cli_main_loop(cliloop_pre_t pre, cliloop_post_t post, void *ctx) now = GETTICKCOUNT(); } - sfree(handles); + handle_wait_list_free(hwl); if (!post(ctx, extra_handle_index)) break; diff --git a/windows/conpty.c b/windows/conpty.c index 4f6f130f..843d6725 100644 --- a/windows/conpty.c +++ b/windows/conpty.c @@ -15,7 +15,8 @@ typedef struct ConPTY ConPTY; struct ConPTY { HPCON pseudoconsole; HANDLE outpipe, inpipe, hprocess; - struct handle *out, *in, *subprocess; + struct handle *out, *in; + HandleWait *subprocess; bool exited; DWORD exitstatus; Seat *seat; @@ -43,7 +44,7 @@ static void conpty_terminate(ConPTY *conpty) conpty->inpipe = INVALID_HANDLE_VALUE; } if (conpty->subprocess) { - handle_free(conpty->subprocess); + delete_handle_wait(conpty->subprocess); conpty->subprocess = NULL; conpty->hprocess = INVALID_HANDLE_VALUE; } @@ -65,7 +66,7 @@ static void conpty_process_wait_callback(void *vctx) * We can stop waiting for the process now. */ if (conpty->subprocess) { - handle_free(conpty->subprocess); + delete_handle_wait(conpty->subprocess); conpty->subprocess = NULL; conpty->hprocess = INVALID_HANDLE_VALUE; } @@ -238,7 +239,7 @@ static char *conpty_init(const BackendVtable *vt, Seat *seat, conpty->inpipe = out_r; conpty->in = handle_input_new(out_r, conpty_gotdata, conpty, 0); out_r = INVALID_HANDLE_VALUE; - conpty->subprocess = handle_add_foreign_event( + conpty->subprocess = add_handle_wait( pi.hProcess, conpty_process_wait_callback, conpty); conpty->hprocess = pi.hProcess; CloseHandle(pi.hThread); diff --git a/windows/handle-io.c b/windows/handle-io.c index 085f30a0..ecb48577 100644 --- a/windows/handle-io.c +++ b/windows/handle-io.c @@ -37,6 +37,12 @@ * Generic definitions. */ +typedef struct handle_list_node handle_list_node; +struct handle_list_node { + handle_list_node *next, *prev; +}; +static void add_to_ready_list(handle_list_node *node); + /* * Maximum amount of backlog we will allow to build up on an input * handle before we stop reading from it. @@ -56,7 +62,7 @@ struct handle_generic { * thread. */ HANDLE h; /* the handle itself */ - HANDLE ev_to_main; /* event used to signal main thread */ + handle_list_node ready_node; /* for linking on to the ready list */ HANDLE ev_from_main; /* event used to signal back to us */ bool moribund; /* are we going to kill this soon? */ bool done; /* request subthread to terminate */ @@ -65,7 +71,7 @@ struct handle_generic { void *privdata; /* for client to remember who they are */ }; -typedef enum { HT_INPUT, HT_OUTPUT, HT_FOREIGN } HandleType; +typedef enum { HT_INPUT, HT_OUTPUT } HandleType; /* ---------------------------------------------------------------------- * Input threads. @@ -79,7 +85,7 @@ 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_list_node ready_node; /* for linking on to the ready list */ HANDLE ev_from_main; /* event used to signal back to us */ bool moribund; /* are we going to kill this soon? */ bool done; /* request subthread to terminate */ @@ -93,7 +99,7 @@ struct handle_input { int flags; /* - * Data set by the input thread before signalling ev_to_main, + * Data set by the input thread before marking the handle ready, * and read by the main thread after receiving that signal. */ char buffer[4096]; /* the data read from the handle */ @@ -176,7 +182,7 @@ static DWORD WINAPI handle_input_threadfunc(void *param) */ finished = (ctx->len == 0); - SetEvent(ctx->ev_to_main); + add_to_ready_list(&ctx->ready_node); if (finished) break; @@ -189,7 +195,7 @@ static DWORD WINAPI handle_input_threadfunc(void *param) * not touch ctx at all, because the main thread might * have freed it. */ - SetEvent(ctx->ev_to_main); + add_to_ready_list(&ctx->ready_node); break; } } @@ -240,7 +246,7 @@ 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_list_node ready_node; /* for linking on to the ready list */ HANDLE ev_from_main; /* event used to signal back to us */ bool moribund; /* are we going to kill this soon? */ bool done; /* request subthread to terminate */ @@ -261,8 +267,8 @@ struct handle_output { 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. + * Data set by the input thread before marking this handle as + * ready, and read by the main thread after receiving that signal. */ DWORD lenwritten; /* how much data we actually wrote */ int writeerr; /* return value from WriteFile */ @@ -303,7 +309,7 @@ static DWORD WINAPI handle_output_threadfunc(void *param) * not touch ctx at all, because the main thread might * have freed it. */ - SetEvent(ctx->ev_to_main); + add_to_ready_list(&ctx->ready_node); break; } if (povl) { @@ -326,7 +332,7 @@ static DWORD WINAPI handle_output_threadfunc(void *param) ctx->writeerr = 0; } - SetEvent(ctx->ev_to_main); + add_to_ready_list(&ctx->ready_node); if (!writeret) { /* * The write operation has suffered an error. Telling that @@ -361,33 +367,6 @@ static void handle_try_output(struct handle_output *ctx) } } -/* ---------------------------------------------------------------------- - * 'Foreign events'. These are handle structures which just contain a - * single event object passed to us by another module such as - * winnps.c, so that they can make use of our handle_get_events / - * handle_got_event mechanism for communicating with application main - * loops. - */ -struct handle_foreign { - /* - * Copy of the handle_generic structure. - */ - HANDLE h; /* the handle itself */ - HANDLE ev_to_main; /* event used to signal main thread */ - HANDLE ev_from_main; /* event used to signal back to us */ - bool moribund; /* are we going to kill this soon? */ - bool done; /* request subthread to terminate */ - bool defunct; /* has the subthread already gone? */ - bool busy; /* operation currently in progress? */ - void *privdata; /* for client to remember who they are */ - - /* - * Our own data, just consisting of knowledge of who to call back. - */ - void (*callback)(void *); - void *ctx; -}; - /* ---------------------------------------------------------------------- * Unified code handling both input and output threads. */ @@ -398,36 +377,91 @@ struct handle { struct handle_generic g; struct handle_input i; struct handle_output o; - struct handle_foreign f; } u; }; -static tree234 *handles_by_evtomain; +/* + * Linked list storing the current list of handles ready to have + * something done to them by the main thread. + */ +static handle_list_node ready_head[1]; +static CRITICAL_SECTION ready_critsec[1]; -static int handle_cmp_evtomain(void *av, void *bv) +/* + * Event object used by all subthreads to signal that they've just put + * something on the ready list, i.e. that the ready list is non-empty. + */ +static HANDLE ready_event = INVALID_HANDLE_VALUE; + +static void add_to_ready_list(handle_list_node *node) { - struct handle *a = (struct handle *)av; - struct handle *b = (struct handle *)bv; + /* + * Called from subthreads, when their handle has done something + * that they need the main thread to respond to. We append the + * given list node to the end of the ready list, and set + * ready_event to signal to the main thread that the ready list is + * now non-empty. + */ + EnterCriticalSection(ready_critsec); + node->next = ready_head; + node->prev = ready_head->prev; + node->next->prev = node->prev->next = node; + SetEvent(ready_event); + LeaveCriticalSection(ready_critsec); +} - if ((uintptr_t)a->u.g.ev_to_main < (uintptr_t)b->u.g.ev_to_main) - return -1; - else if ((uintptr_t)a->u.g.ev_to_main > (uintptr_t)b->u.g.ev_to_main) - return +1; - else - return 0; +static void remove_from_ready_list(handle_list_node *node) +{ + /* + * Called from the main thread, just before destroying a 'struct + * handle' completely: as a precaution, we make absolutely sure + * it's not linked on the ready list, just in case somehow it + * still was. + */ + EnterCriticalSection(ready_critsec); + node->next->prev = node->prev; + node->prev->next = node->next; + node->next = node->prev = node; + LeaveCriticalSection(ready_critsec); } -static int handle_find_evtomain(void *av, void *bv) +static void handle_ready(struct handle *h); /* process one handle (below) */ + +static void handle_ready_callback(void *vctx) { - HANDLE *a = (HANDLE *)av; - struct handle *b = (struct handle *)bv; + /* + * Called when the main thread detects ready_event, indicating + * that at least one handle is on the ready list. We empty the + * whole list and process the handles one by one. + * + * It's possible that other handles may be destroyed, and hence + * taken _off_ the ready list, during this processing. That + * shouldn't cause a deadlock, because according to the API docs, + * it's safe to call EnterCriticalSection twice in the same thread + * - the second call will return immediately because that thread + * already owns the critsec. (And then it takes two calls to + * LeaveCriticalSection to release it again, which is just what we + * want here.) + */ + EnterCriticalSection(ready_critsec); + while (ready_head->next != ready_head) { + handle_list_node *node = ready_head->next; + node->prev->next = node->next; + node->next->prev = node->prev; + node->next = node->prev = node; + handle_ready(container_of(node, struct handle, u.g.ready_node)); + } + LeaveCriticalSection(ready_critsec); +} - if ((uintptr_t)*a < (uintptr_t)b->u.g.ev_to_main) - return -1; - else if ((uintptr_t)*a > (uintptr_t)b->u.g.ev_to_main) - return +1; - else - return 0; +static inline void ensure_ready_event_setup(void) +{ + if (ready_event == INVALID_HANDLE_VALUE) { + ready_head->prev = ready_head->next = ready_head; + InitializeCriticalSection(ready_critsec); + ready_event = CreateEvent(NULL, false, false, NULL); + add_handle_wait(ready_event, handle_ready_callback, NULL); + } } struct handle *handle_input_new(HANDLE handle, handle_inputfn_t gotdata, @@ -438,7 +472,6 @@ struct handle *handle_input_new(HANDLE handle, handle_inputfn_t gotdata, h->type = HT_INPUT; h->u.i.h = handle; - h->u.i.ev_to_main = CreateEvent(NULL, false, false, NULL); h->u.i.ev_from_main = CreateEvent(NULL, false, false, NULL); h->u.i.gotdata = gotdata; h->u.i.defunct = false; @@ -447,10 +480,7 @@ struct handle *handle_input_new(HANDLE handle, handle_inputfn_t gotdata, 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); - + ensure_ready_event_setup(); CreateThread(NULL, 0, handle_input_threadfunc, &h->u.i, 0, &in_threadid); h->u.i.busy = true; @@ -466,7 +496,6 @@ struct handle *handle_output_new(HANDLE handle, handle_outputfn_t sentdata, h->type = HT_OUTPUT; h->u.o.h = handle; - h->u.o.ev_to_main = CreateEvent(NULL, false, false, NULL); h->u.o.ev_from_main = CreateEvent(NULL, false, false, NULL); h->u.o.busy = false; h->u.o.defunct = false; @@ -478,40 +507,13 @@ struct handle *handle_output_new(HANDLE handle, handle_outputfn_t sentdata, 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); - + ensure_ready_event_setup(); CreateThread(NULL, 0, handle_output_threadfunc, &h->u.o, 0, &out_threadid); return h; } -struct handle *handle_add_foreign_event(HANDLE event, - void (*callback)(void *), void *ctx) -{ - struct handle *h = snew(struct handle); - - h->type = HT_FOREIGN; - h->u.f.h = INVALID_HANDLE_VALUE; - h->u.f.ev_to_main = event; - h->u.f.ev_from_main = INVALID_HANDLE_VALUE; - h->u.f.defunct = true; /* we have no thread in the first place */ - h->u.f.moribund = false; - h->u.f.done = false; - h->u.f.privdata = NULL; - h->u.f.callback = callback; - h->u.f.ctx = ctx; - h->u.f.busy = true; - - if (!handles_by_evtomain) - handles_by_evtomain = newtree234(handle_cmp_evtomain); - add234(handles_by_evtomain, h); - - return h; -} - size_t handle_write(struct handle *h, const void *data, size_t len) { assert(h->type == HT_OUTPUT); @@ -537,46 +539,19 @@ void handle_write_eof(struct handle *h) } } -HANDLE *handle_get_events(int *nevents) -{ - HANDLE *ret; - struct handle *h; - int i; - size_t 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) { - sgrowarray(ret, size, n); - ret[n++] = h->u.g.ev_to_main; - } - } - } - - *nevents = n; - return ret; -} - static void handle_destroy(struct handle *h) { if (h->type == HT_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); + remove_from_ready_list(&h->u.g.ready_node); sfree(h); } void handle_free(struct handle *h) { assert(h && !h->u.g.moribund); - if (h->u.g.busy && h->type != HT_FOREIGN) { + if (h->u.g.busy) { /* * If the handle is currently busy, we cannot immediately free * it, because its subthread is in the middle of something. @@ -608,24 +583,8 @@ void handle_free(struct handle *h) } } -void handle_got_event(HANDLE event) +static void handle_ready(struct handle *h) { - 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 one which we have either already @@ -689,11 +648,6 @@ void handle_got_event(HANDLE event) handle_try_output(&h->u.o); } break; - - case HT_FOREIGN: - /* Just call the callback. */ - h->u.f.callback(h->u.f.ctx); - break; } } diff --git a/windows/handle-wait.c b/windows/handle-wait.c new file mode 100644 index 00000000..47479aa0 --- /dev/null +++ b/windows/handle-wait.c @@ -0,0 +1,144 @@ +/* + * handle-wait.c: Manage a collection of HANDLEs to wait for (in a + * WaitFor{Single,Multiple}Objects sense), each with a callback to be + * called when it's activated. Tracks the list, and provides an API to + * event loops that let them get a list of things to wait for and a + * way to call back to here when one of them does something. + */ + +/* + * TODO: currently this system can't cope with more than + * MAXIMUM_WAIT_OBJECTS (= 64) handles at a time. It enforces that by + * assertion, so we'll at least find out if that assumption is ever + * violated. + * + * It should be OK for the moment. As of 2021-05-24, the only uses of + * this system are by the ConPTY backend (just once, to watch for its + * subprocess terminating); by Pageant (for the event that the + * WM_COPYDATA subthread uses to signal the main thread); and by + * named-pipe-server.c (once per named-pipe server, of which there is + * one in Pageant and one in connection-sharing upstreams). So the + * total number of handles has a pretty small upper bound. + * + * But sooner or later, I'm sure we'll find a reason why we really + * need to watch a squillion handles at once. When that happens, I + * can't see any alternative to setting up some kind of tree of + * subthreads in this module, each one condensing 64 of our handles + * into one, by doing its own WaitForMultipleObjects and setting an + * event object to indicate that one of them did something. It'll be + * horribly ugly. + */ + +#include "putty.h" + +struct HandleWait { + HANDLE handle; + handle_wait_callback_fn_t callback; + void *callback_ctx; + + int index; /* sort key for tree234 */ +}; + +struct HandleWaitListInner { + HandleWait *hws[MAXIMUM_WAIT_OBJECTS]; + HANDLE handles[MAXIMUM_WAIT_OBJECTS]; + + struct HandleWaitList hwl; +}; + +static int handlewait_cmp(void *av, void *bv) +{ + HandleWait *a = (HandleWait *)av, *b = (HandleWait *)bv; + if (a->index < b->index) + return -1; + if (a->index > b->index) + return +1; + return 0; +} + +static tree234 *handlewaits_tree_real; + +static inline tree234 *ensure_handlewaits_tree_exists(void) +{ + if (!handlewaits_tree_real) + handlewaits_tree_real = newtree234(handlewait_cmp); + return handlewaits_tree_real; +} + +static int allocate_index(void) +{ + tree234 *t = ensure_handlewaits_tree_exists(); + search234_state st[1]; + + search234_start(st, t); + while (st->element) { + HandleWait *hw = (HandleWait *)st->element; + if (st->index < hw->index) { + /* There are unused index slots to the left of this element */ + search234_step(st, -1); + } else { + assert(st->index == hw->index); + search234_step(st, +1); + } + } + + return st->index; +} + +HandleWait *add_handle_wait(HANDLE h, handle_wait_callback_fn_t callback, + void *callback_ctx) +{ + HandleWait *hw = snew(HandleWait); + hw->handle = h; + hw->callback = callback; + hw->callback_ctx = callback_ctx; + + tree234 *t = ensure_handlewaits_tree_exists(); + hw->index = allocate_index(); + HandleWait *added = add234(t, hw); + assert(added == hw); + + return hw; +} + +void delete_handle_wait(HandleWait *hw) +{ + tree234 *t = ensure_handlewaits_tree_exists(); + HandleWait *deleted = del234(t, hw); + assert(deleted == hw); + sfree(hw); +} + +HandleWaitList *get_handle_wait_list(void) +{ + tree234 *t = ensure_handlewaits_tree_exists(); + struct HandleWaitListInner *hwli = snew(struct HandleWaitListInner); + size_t n = 0; + HandleWait *hw; + for (int i = 0; (hw = index234(t, i)) != NULL; i++) { + assert(n < MAXIMUM_WAIT_OBJECTS); + hwli->hws[n] = hw; + hwli->hwl.handles[n] = hw->handle; + n++; + } + hwli->hwl.nhandles = n; + return &hwli->hwl; +} + +void handle_wait_activate(HandleWaitList *hwl, int index) +{ + struct HandleWaitListInner *hwli = + container_of(hwl, struct HandleWaitListInner, hwl); + tree234 *t = ensure_handlewaits_tree_exists(); + assert(0 <= index); + assert(index < hwli->hwl.nhandles); + HandleWait *hw = hwli->hws[index]; + hw->callback(hw->callback_ctx); +} + +void handle_wait_list_free(HandleWaitList *hwl) +{ + struct HandleWaitListInner *hwli = + container_of(hwl, struct HandleWaitListInner, hwl); + sfree(hwli); +} diff --git a/windows/named-pipe-server.c b/windows/named-pipe-server.c index cdda500d..a272cdad 100644 --- a/windows/named-pipe-server.c +++ b/windows/named-pipe-server.c @@ -22,7 +22,7 @@ typedef struct NamedPipeServerSocket { /* The current named pipe object + attempt to connect to it */ HANDLE pipehandle; OVERLAPPED connect_ovl; - struct handle *callback_handle; /* winhandl.c's reference */ + HandleWait *callback_handle; /* handle-wait.c's reference */ /* PuTTY Socket machinery */ Plug *plug; @@ -45,7 +45,7 @@ static void sk_namedpipeserver_close(Socket *s) NamedPipeServerSocket *ps = container_of(s, NamedPipeServerSocket, sock); if (ps->callback_handle) - handle_free(ps->callback_handle); + delete_handle_wait(ps->callback_handle); CloseHandle(ps->pipehandle); CloseHandle(ps->connect_ovl.hEvent); sfree(ps->error); @@ -226,9 +226,8 @@ Socket *new_named_pipe_listener(const char *pipename, Plug *plug) memset(&ret->connect_ovl, 0, sizeof(ret->connect_ovl)); ret->connect_ovl.hEvent = CreateEvent(NULL, true, false, NULL); - ret->callback_handle = - handle_add_foreign_event(ret->connect_ovl.hEvent, - named_pipe_connect_callback, ret); + ret->callback_handle = add_handle_wait( + ret->connect_ovl.hEvent, named_pipe_connect_callback, ret); named_pipe_accept_loop(ret, false); cleanup: diff --git a/windows/pageant.c b/windows/pageant.c index e7023945..c82c18bc 100644 --- a/windows/pageant.c +++ b/windows/pageant.c @@ -1633,7 +1633,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) wmct.ev_reply_ready = CreateEvent(NULL, false, false, NULL); CreateThread(NULL, 0, wm_copydata_threadfunc, &inst, 0, &wm_copydata_threadid); - handle_add_foreign_event(wmct.ev_msg_ready, wm_copydata_got_msg, NULL); + add_handle_wait(wmct.ev_msg_ready, wm_copydata_got_msg, NULL); if (show_keylist_on_startup) create_keylist_window(); @@ -1642,19 +1642,16 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) * Main message loop. */ while (true) { - HANDLE *handles; - int nhandles, n; + int n; - handles = handle_get_events(&nhandles); + HandleWaitList *hwl = get_handle_wait_list(); - n = MsgWaitForMultipleObjects(nhandles, handles, false, + n = MsgWaitForMultipleObjects(hwl->nhandles, hwl->handles, false, INFINITE, QS_ALLINPUT); - if ((unsigned)(n - WAIT_OBJECT_0) < (unsigned)nhandles) { - handle_got_event(handles[n - WAIT_OBJECT_0]); - sfree(handles); - } else - sfree(handles); + if ((unsigned)(n - WAIT_OBJECT_0) < (unsigned)hwl->nhandles) + handle_wait_activate(hwl, n - WAIT_OBJECT_0); + handle_wait_list_free(hwl); while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) { if (msg.message == WM_QUIT) diff --git a/windows/platform.h b/windows/platform.h index a03d9d63..f549896d 100644 --- a/windows/platform.h +++ b/windows/platform.h @@ -621,14 +621,10 @@ struct handle *handle_output_new(HANDLE handle, handle_outputfn_t sentdata, void *privdata, int flags); size_t handle_write(struct handle *h, const void *data, size_t len); void handle_write_eof(struct handle *h); -HANDLE *handle_get_events(int *nevents); void handle_free(struct handle *h); -void handle_got_event(HANDLE event); void handle_unthrottle(struct handle *h, size_t backlog); size_t handle_backlog(struct handle *h); void *handle_get_privdata(struct handle *h); -struct handle *handle_add_foreign_event(HANDLE event, - void (*callback)(void *), void *ctx); /* Analogue of stdio_sink in marshal.h, for a Windows handle */ struct handle_sink { struct handle *h; @@ -636,6 +632,23 @@ struct handle_sink { }; void handle_sink_init(handle_sink *sink, struct handle *h); +/* + * Exports from handle-wait.c. + */ +typedef struct HandleWait HandleWait; +typedef void (*handle_wait_callback_fn_t)(void *); +HandleWait *add_handle_wait(HANDLE h, handle_wait_callback_fn_t callback, + void *callback_ctx); +void delete_handle_wait(HandleWait *hw); + +typedef struct HandleWaitList { + HANDLE handles[MAXIMUM_WAIT_OBJECTS]; + int nhandles; +} HandleWaitList; +HandleWaitList *get_handle_wait_list(void); +void handle_wait_activate(HandleWaitList *hwl, int index); +void handle_wait_list_free(HandleWaitList *hwl); + /* * Exports from winpgntc.c. */ diff --git a/windows/window.c b/windows/window.c index c644422c..36284927 100644 --- a/windows/window.c +++ b/windows/window.c @@ -721,8 +721,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) UpdateWindow(wgs.term_hwnd); while (1) { - HANDLE *handles; - int nhandles, n; + int n; DWORD timeout; if (toplevel_callback_pending() || @@ -751,16 +750,14 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) term_set_focus(term, GetForegroundWindow() == wgs.term_hwnd); } - handles = handle_get_events(&nhandles); + HandleWaitList *hwl = get_handle_wait_list(); - n = MsgWaitForMultipleObjects(nhandles, handles, false, + n = MsgWaitForMultipleObjects(hwl->nhandles, hwl->handles, false, timeout, QS_ALLINPUT); - if ((unsigned)(n - WAIT_OBJECT_0) < (unsigned)nhandles) { - handle_got_event(handles[n - WAIT_OBJECT_0]); - sfree(handles); - } else - sfree(handles); + if ((unsigned)(n - WAIT_OBJECT_0) < (unsigned)hwl->nhandles) + handle_wait_activate(hwl, n - WAIT_OBJECT_0); + handle_wait_list_free(hwl); while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) { if (msg.message == WM_QUIT) -- cgit v1.2.3 From 9851d37ccbded022ef542f5e65c47cb560a6b6da Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 24 May 2021 15:04:35 +0100 Subject: Add test script for simultaneous agent connections. This script makes 128 connections to your SSH agent at once, and then sends requests down them in random order to check that the agent is correctly selecting between all its incoming sockets / named pipes / whatever. 128 is bigger than MAXIMUM_WAIT_OBJECTS, so a successful run of this script inside a Windows PuTTY agent-forwarding to a Pageant indicates that both the PuTTY and the Pageant are managing to handle >64 I/O subthreads without overloading their event loop. --- test/agentmulti.py | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ test/ssh.py | 1 + 2 files changed, 57 insertions(+) create mode 100755 test/agentmulti.py diff --git a/test/agentmulti.py b/test/agentmulti.py new file mode 100755 index 00000000..019bf2b6 --- /dev/null +++ b/test/agentmulti.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python3 + +import argparse +import os +import random +import socket +import sys + +from ssh import * + +def make_connections(n): + connections = [] + + for _ in range(n): + s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + s.connect(os.environ["SSH_AUTH_SOCK"]) + connections.append(s) + + return connections + +def use_connection(s, idstring): + print("Trying {}...".format(idstring), end="") + sys.stdout.flush() + + s.send(ssh_string(ssh_byte(SSH2_AGENTC_EXTENSION) + ssh_string( + b"nonexistent-agent-extension@putty.projects.tartarus.org"))) + length = ssh_decode_uint32(s.recv(4)) + assert length < AGENT_MAX_MSGLEN + msg = s.recv(length) + msgtype = msg[0] + msgstring = ( + "SSH_AGENT_EXTENSION_FAILURE" if msgtype == SSH_AGENT_EXTENSION_FAILURE + else "SSH_AGENT_FAILURE" if msgtype == SSH_AGENT_FAILURE + else "type {:d}".format(msgtype)) + print("got", msgstring, "with {:d}-byte payload".format(len(msg)-1)) + +def randomly_use_connections(connections, iterations): + for _ in range(iterations): + index = random.randrange(0, len(connections)) + s = connections[index] + use_connection(connections[index], "#{:d}".format(index)) + +def main(): + parser = argparse.ArgumentParser( + description='Test handling of multiple agent connections.') + parser.add_argument("--nsockets", type=int, default=128, + help="Number of simultaneous connections to make.") + parser.add_argument("--ntries", type=int, default=1024, + help="Number of messages to send in total.") + args = parser.parse_args() + + connections = make_connections(args.nsockets) + randomly_use_connections(connections, args.ntries) + +if __name__ == '__main__': + main() diff --git a/test/ssh.py b/test/ssh.py index 90eccb7e..c4f2531f 100644 --- a/test/ssh.py +++ b/test/ssh.py @@ -88,6 +88,7 @@ SSH1_AGENTC_REMOVE_RSA_IDENTITY = 8 SSH1_AGENTC_REMOVE_ALL_RSA_IDENTITIES = 9 SSH_AGENT_FAILURE = 5 SSH_AGENT_SUCCESS = 6 +SSH_AGENT_EXTENSION_FAILURE = 28 SSH2_AGENTC_REQUEST_IDENTITIES = 11 SSH2_AGENT_IDENTITIES_ANSWER = 12 SSH2_AGENTC_SIGN_REQUEST = 13 -- cgit v1.2.3 From 9f4bd6c55294ff9bece74f34158c434ed2f20c58 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 27 May 2021 09:47:33 +0100 Subject: Fix an unused variable. Enthusiastic copy-paste: in commit 17c57e1078c8fab I added the same precautionary call to ensure_handlewaits_tree_exists() everywhere, even in functions that didn't actually need to use the tree. --- windows/handle-wait.c | 1 - 1 file changed, 1 deletion(-) diff --git a/windows/handle-wait.c b/windows/handle-wait.c index 47479aa0..9e4e522d 100644 --- a/windows/handle-wait.c +++ b/windows/handle-wait.c @@ -129,7 +129,6 @@ void handle_wait_activate(HandleWaitList *hwl, int index) { struct HandleWaitListInner *hwli = container_of(hwl, struct HandleWaitListInner, hwl); - tree234 *t = ensure_handlewaits_tree_exists(); assert(0 <= index); assert(index < hwli->hwl.nhandles); HandleWait *hw = hwli->hws[index]; -- cgit v1.2.3 From 47c2bc38d1c8da3620db7ad6e08d40c989026082 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 12 Jun 2021 10:08:58 +0100 Subject: New script contrib/proveprime.py. This generates primality certificates for numbers, in the form of Python / testcrypt code that calls Pockle methods. It factors p-1 by calling out to the 'yafu' utility, which is a moderately sophisticated integer factoring tool (including ECC and quadratic sieve methods) that runs as a standalone command-line program. Also added a Pockle test generated as output from this script, which verifies the primality of the three NIST curves' moduli and their generators' orders. I already had Pockle certificates for the moduli and orders used in EdDSA, so this completes the set, and it does it without me having had to do a lot of manual work. --- contrib/proveprime.py | 162 ++++++++++++++++++++++++++++++++++++++++++++++++++ test/cryptsuite.py | 43 ++++++++++++++ 2 files changed, 205 insertions(+) create mode 100755 contrib/proveprime.py diff --git a/contrib/proveprime.py b/contrib/proveprime.py new file mode 100755 index 00000000..655e68ea --- /dev/null +++ b/contrib/proveprime.py @@ -0,0 +1,162 @@ +#!/usr/bin/env python3 + +import argparse +import functools +import math +import os +import re +import subprocess +import sys +import itertools + +def gen_names(): + for i in itertools.count(): + name = "p{:d}".format(i) + if name not in nameset: + yield name +nameset=set() +names = gen_names() + +class YafuError(Exception): + pass + +verbose = False +def diag(*args): + if verbose: + print(*args, file=sys.stderr) + +factorcache = set() +factorcachefile = None +def cache_factor(f): + if f not in factorcache: + factorcache.add(f) + if factorcachefile is not None: + factorcachefile.write("{:d}\n".format(f)) + factorcachefile.flush() + +yafu = None +yafu_pattern = re.compile(rb"^P\d+ = (\d+)$") +def call_yafu(n): + n_orig = n + diag("starting yafu", n_orig) + p = subprocess.Popen([yafu, "-v", "-v"], stdin=subprocess.PIPE, + stdout=subprocess.PIPE) + p.stdin.write("{:d}\n".format(n).encode("ASCII")) + p.stdin.close() + factors = [] + for line in iter(p.stdout.readline, b''): + line = line.rstrip(b"\r\n") + diag("yafu output:", line.decode()) + m = yafu_pattern.match(line) + if m is not None: + f = int(m.group(1)) + if n % f != 0: + raise YafuError("bad yafu factor {:d}".format(f)) + factors.append(f) + if f >> 64: + cache_factor(f) + n //= f + p.wait() + diag("done yafu", n_orig) + return factors, n + +def factorise(n): + allfactors = [] + for f in factorcache: + if n % f == 0: + n //= f + allfactors.append(f) + while n > 1: + factors, n = call_yafu(n) + allfactors.extend(factors) + return sorted(allfactors) + +def product(ns): + return functools.reduce(lambda a,b: a*b, ns, 1) + +smallprimes = set() +commands = {} + +def proveprime(p, name=None): + if p >> 32 == 0: + smallprimes.add(p) + return "{:d}".format(p) + + if name is None: + name = next(names) + print("{} = {:d}".format(name, p)) + + fs = factorise(p-1) + fs.reverse() + prod = product(fs) + qs = [] + for q in fs: + newprod = prod // q + if newprod * newprod * newprod > p: + prod = newprod + else: + qs.append(q) + assert prod == product(qs) + assert prod * prod * prod > p + qset = set(qs) + qnamedict = {q: proveprime(q) for q in qset} + qnames = [qnamedict[q] for q in qs] + for w in itertools.count(2): + assert pow(w, p-1, p) == 1, "{}={:d} is not prime!".format(name, p) + diag("trying witness", w, "for", p) + for q in qset: + wpower = pow(w, (p-1) // q, p) - 1 + if math.gcd(wpower, p) != 1: + break + else: + diag("found witness", w, "for", p) + break + commands[p]= (name, w, qnames) + return name + +def main(): + parser = argparse.ArgumentParser(description='') + parser.add_argument("prime", nargs="+", + help="Number to prove prime. Can be prefixed by a " + "variable name and '=', e.g. 'x=9999999967'.") + parser.add_argument("--cryptsuite", action="store_true", + help="Generate abbreviated Pockle calls suitable " + "for the tests in cryptsuite.py.") + parser.add_argument("--yafu", default="yafu", + help="yafu binary to help with factoring.") + parser.add_argument("-v", "--verbose", action="store_true", + help="Write diagnostics to standard error.") + parser.add_argument("--cache", help="Cache of useful factors of things.") + args = parser.parse_args() + + global verbose, yafu + verbose = args.verbose + yafu = args.yafu + + if args.cache is not None: + with open(args.cache, "r") as fh: + for line in iter(fh.readline, ""): + factorcache.add(int(line.rstrip("\r\n"))) + global factorcachefile + factorcachefile = open(args.cache, "a") + + for ps in args.prime: + name, value = (ps.split("=", 1) if "=" in ps + else (None, ps)) + proveprime(int(value, 0), name) + + print("po = pockle_new()") + if len(smallprimes) > 0: + if args.cryptsuite: + print("add_small(po, {})".format( + ", ".join("{:d}".format(q) for q in sorted(smallprimes)))) + else: + for q in sorted(smallprimes): + print("pockle_add_small_prime(po, {:d})".format(q)) + for p, (name, w, qnames) in sorted(commands.items()): + print("{cmd}(po, {name}, [{qs}], {w:d})".format( + cmd = "add" if args.cryptsuite else "pockle_add_prime", + name=name, w=w, qs=", ".join(qnames))) + +if __name__ == '__main__': + main() diff --git a/test/cryptsuite.py b/test/cryptsuite.py index 9ed0c3f5..c4df264f 100755 --- a/test/cryptsuite.py +++ b/test/cryptsuite.py @@ -1079,6 +1079,49 @@ class keygen(MyTestBase): add(po, p6, [p3,p4], 2) add(po, p, [p2,p5,p6], 2) + # Combined certificate for the moduli and generator orders of + # the three NIST curves, generated by contrib/proveprime.py + # (with some cosmetic tidying) + p256 = 2**256 - 2**224 + 2**192 + 2**96 - 1 + p384 = 2**384 - 2**128 - 2**96 + 2**32 - 1 + p521 = 2**521 - 1 + order256 = p256 - 0x4319055358e8617b0c46353d039cdaae + order384 = p384 - 0x389cb27e0bc8d21fa7e5f24cb74f58851313e696333ad68c + t = 0x5ae79787c40d069948033feb708f65a2fc44a36477663b851449048e16ec79bf6 + order521 = p521 - t + p0 = order384 // 12895580879789762060783039592702 + p1 = 1059392654943455286185473617842338478315215895509773412096307 + p2 = 55942463741690639 + p3 = 37344768852931 + p4 = order521 // 1898873518475180724503002533770555108536 + p5 = p4 // 994165722 + p6 = 144471089338257942164514676806340723 + p7 = p384 // 2054993070433694 + p8 = 1357291859799823621 + po = pockle_new() + add_small(po, 2, 3, 5, 11, 17, 19, 31, 41, 53, 67, 71, 109, 131, 149, + 157, 257, 521, 641, 1613, 2731, 3407, 6317, 8191, 8389, + 14461, 17449, 38189, 38557, 42641, 51481, 61681, 65537, + 133279, 248431, 312289, 409891, 490463, 858001, 6700417, + 187019741) + add(po, p3, [149, 11, 5, 3, 2], 3) + add(po, p2, [p3], 2) + add(po, p8, [6317, 67, 2, 2], 2) + add(po, p6, [133279, 14461, 109, 3], 7) + add(po, p1, [p2, 248431], 2) + add(po, order256, [187019741, 38189, 17449, 3407, 131, 71, 2, 2, 2, 2], + 7) + add(po, p256, [6700417, 490463, 65537, 641, 257, 17, 5, 5, 3, 2], 6) + add(po, p0, [p1], 2) + add(po, p7, [p8, 312289, 38557, 8389, 11, 2], 3) + add(po, p5, [p6, 19], 2) + add(po, order384, [p0], 2) + add(po, p384, [p7], 2) + add(po, p4, [p5], 2) + add(po, order521, [p4], 2) + add(po, p521, [858001, 409891, 61681, 51481, 42641, 8191, 2731, 1613, + 521, 157, 131, 53, 41, 31, 17, 11, 5, 5, 3, 2], 3) + def testPockleNegative(self): def add_small(po, p): self.assertEqual(pockle_add_small_prime(po, p), 'POCKLE_OK') -- cgit v1.2.3 From 5677da64813d38aa1feeb44bca5d098b54f53240 Mon Sep 17 00:00:00 2001 From: Jacob Nevins Date: Sun, 13 Jun 2021 00:18:42 +0100 Subject: Fix palette escape sequences sometimes not working. If a batch of palette changes were seen in between window updates, only the last one would take immediate effect. --- terminal.c | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/terminal.c b/terminal.c index b845baab..f8f9417c 100644 --- a/terminal.c +++ b/terminal.c @@ -1815,6 +1815,16 @@ static void palette_rebuild(Terminal *term) { unsigned min_changed = OSC4_NCOLOURS, max_changed = 0; + if (term->win_palette_pending) { + /* Possibly extend existing range. */ + min_changed = term->win_palette_pending_min; + max_changed = term->win_palette_pending_limit - 1; + } else { + /* Start with empty range. */ + min_changed = OSC4_NCOLOURS; + max_changed = 0; + } + for (unsigned i = 0; i < OSC4_NCOLOURS; i++) { rgb new_value; bool found = false; @@ -1842,10 +1852,14 @@ static void palette_rebuild(Terminal *term) if (min_changed <= max_changed) { /* - * At least one colour changed, so schedule a redraw event to - * pass the result back to the TermWin. This also requires - * invalidating the rest of the window, because usually all - * the text will need redrawing in the new colours. + * At least one colour changed (or we had an update scheduled + * already). Schedule a redraw event to pass the result back + * to the TermWin. This also requires invalidating the rest + * of the window, because usually all the text will need + * redrawing in the new colours. + * (If there was an update pending and this palette rebuild + * didn't actually change anything, we'll harmlessly reinforce + * the existing update request.) */ term->win_palette_pending = true; term->win_palette_pending_min = min_changed; -- cgit v1.2.3 From 6d05e20a0e9f81eaefd157c7fd13b1bb2c96ceed Mon Sep 17 00:00:00 2001 From: Jacob Nevins Date: Sun, 13 Jun 2021 00:25:18 +0100 Subject: Remove a futile term_schedule_update(). term_invalidate() already implies it. (The extra call was harmless.) --- terminal.c | 1 - 1 file changed, 1 deletion(-) diff --git a/terminal.c b/terminal.c index f8f9417c..1a038cf7 100644 --- a/terminal.c +++ b/terminal.c @@ -1865,7 +1865,6 @@ static void palette_rebuild(Terminal *term) term->win_palette_pending_min = min_changed; term->win_palette_pending_limit = max_changed + 1; term_invalidate(term); - term_schedule_update(term); } } -- cgit v1.2.3 From 5f5c710cf3704737e24ffceb2c918e412a2a674f Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 19 Jun 2021 15:39:15 +0100 Subject: New option to reject 'trivial' success of userauth. Suggested by Manfred Kaiser, who also wrote most of this patch (although outlying parts, like documentation and SSH-1 support, are by me). This is a second line of defence against the kind of spoofing attacks in which a malicious or compromised SSH server rushes the client through the userauth phase of SSH without actually requiring any auth inputs (passwords or signatures or whatever), and then at the start of the connection phase it presents something like a spoof prompt, intended to be taken for part of userauth by the user but in fact with some more sinister purpose. Our existing line of defence against this is the trust sigil system, and as far as I know, that's still working. This option allows a bit of extra defence in depth: if you don't expect your SSH server to trivially accept authentication in the first place, then enabling this option will cause PuTTY to disconnect if it unexpectedly does so, without the user having to spot the presence or absence of a fiddly little sigil anywhere. Several types of authentication count as 'trivial'. The obvious one is the SSH-2 "none" method, which clients always try first so that the failure message will tell them what else they can try, and which a server can instead accept in order to authenticate you unconditionally. But there are two other ways to do it that we know of: one is to run keyboard-interactive authentication and send an empty INFO_REQUEST packet containing no actual prompts for the user, and another even weirder one is to send USERAUTH_SUCCESS in response to the user's preliminary *offer* of a public key (instead of sending the usual PK_OK to request an actual signature from the key). This new option detects all of those, by clearing the 'is_trivial_auth' flag only when we send some kind of substantive authentication response (be it a password, a k-i prompt response, a signature, or a GSSAPI token). So even if there's a further path through the userauth maze we haven't spotted, that somehow avoids sending anything substantive, this strategy should still pick it up. --- cmdline.c | 8 ++++++++ config.c | 4 ++++ doc/config.but | 43 +++++++++++++++++++++++++++++++++++++++++++ pscp.c | 2 ++ psftp.c | 2 ++ putty.h | 1 + settings.c | 2 ++ ssh/login1.c | 14 +++++++++++++- ssh/ppl.h | 2 +- ssh/ssh.c | 4 +++- ssh/userauth2-client.c | 20 ++++++++++++++++---- unix/plink.c | 2 ++ windows/help.h | 1 + windows/plink.c | 2 ++ 14 files changed, 100 insertions(+), 7 deletions(-) diff --git a/cmdline.c b/cmdline.c index 7ea5cacc..62d65e19 100644 --- a/cmdline.c +++ b/cmdline.c @@ -598,6 +598,14 @@ int cmdline_process_param(const char *p, char *value, SAVEABLE(0); conf_set_bool(conf, CONF_tryagent, false); } + + if (!strcmp(p, "-no-trivial-auth")) { + RETURN(1); + UNAVAILABLE_IN(TOOLTYPE_NONNETWORK); + SAVEABLE(0); + conf_set_bool(conf, CONF_ssh_no_trivial_userauth, true); + } + if (!strcmp(p, "-share")) { RETURN(1); UNAVAILABLE_IN(TOOLTYPE_NONNETWORK); diff --git a/config.c b/config.c index 0932f894..efb65aa3 100644 --- a/config.c +++ b/config.c @@ -2782,6 +2782,10 @@ void setup_config_box(struct controlbox *b, bool midsession, HELPCTX(ssh_auth_bypass), conf_checkbox_handler, I(CONF_ssh_no_userauth)); + ctrl_checkbox(s, "Disconnect if authentication succeeds trivially", + 'n', HELPCTX(ssh_no_trivial_userauth), + conf_checkbox_handler, + I(CONF_ssh_no_trivial_userauth)); s = ctrl_getset(b, "Connection/SSH/Auth", "methods", "Authentication methods"); diff --git a/doc/config.but b/doc/config.but index a00ae476..77313282 100644 --- a/doc/config.but +++ b/doc/config.but @@ -2623,6 +2623,49 @@ interact with them.) This option only affects SSH-2 connections. SSH-1 connections always require an authentication step. +\S{config-ssh-notrivialauth} \q{Disconnect if authentication succeeds +trivially} + +This option causes PuTTY to abandon an SSH session and disconnect from +the server, if the server accepted authentication without ever having +asked for any kind of password or signature or token. + +This might be used as a security measure. There are some forms of +attack against an SSH client user which work by terminating the SSH +authentication stage early, and then doing something in the main part +of the SSH session which \e{looks} like part of the authentication, +but isn't really. + +For example, instead of demanding a signature from your public key, +for which PuTTY would ask for your key's passphrase, a compromised or +malicious server might allow you to log in with no signature or +password at all, and then print a message that \e{imitates} PuTTY's +request for your passphrase, in the hope that you would type it in. +(In fact, the passphrase for your public key should not be sent to any +server.) + +PuTTY's main defence against attacks of this type is the \q{trust +sigil} system: messages in the PuTTY window that are truly originated +by PuTTY itself are shown next to a small copy of the PuTTY icon, +which the server cannot fake when it tries to imitate the same message +using terminal output. + +However, if you think you might be at risk of this kind of thing +anyway (if you don't watch closely for the trust sigils, or if you +think you're at extra risk of one of your servers being malicious), +then you could enable this option as an extra defence. Then, if the +server tries any of these attacks involving letting you through the +authentication stage, PuTTY will disconnect from the server before it +can send a follow-up fake prompt or other type of attack. + +On the other hand, some servers \e{legitimately} let you through the +SSH authentication phase trivially, either because they are genuinely +public, or because the important authentication step happens during +the terminal session. (An example might be an SSH server that connects +you directly to the terminal login prompt of a legacy mainframe.) So +enabling this option might cause some kinds of session to stop +working. It's up to you. + \S{config-ssh-tryagent} \q{Attempt authentication using Pageant} If this option is enabled, then PuTTY will look for Pageant (the SSH diff --git a/pscp.c b/pscp.c index 9accdf70..82085962 100644 --- a/pscp.c +++ b/pscp.c @@ -2204,6 +2204,8 @@ static void usage(void) printf(" -i key private key file for user authentication\n"); printf(" -noagent disable use of Pageant\n"); printf(" -agent enable use of Pageant\n"); + printf(" -no-trivial-auth\n"); + printf(" disconnect if SSH authentication succeeds trivially\n"); printf(" -hostkey keyid\n"); printf(" manually specify a host key (may be repeated)\n"); printf(" -batch disable all interactive prompts\n"); diff --git a/psftp.c b/psftp.c index fd9b78d3..8eaa6fc5 100644 --- a/psftp.c +++ b/psftp.c @@ -2539,6 +2539,8 @@ static void usage(void) printf(" -i key private key file for user authentication\n"); printf(" -noagent disable use of Pageant\n"); printf(" -agent enable use of Pageant\n"); + printf(" -no-trivial-auth\n"); + printf(" disconnect if SSH authentication succeeds trivially\n"); printf(" -hostkey keyid\n"); printf(" manually specify a host key (may be repeated)\n"); printf(" -batch disable all interactive prompts\n"); diff --git a/putty.h b/putty.h index 04f91879..d67b4e75 100644 --- a/putty.h +++ b/putty.h @@ -1460,6 +1460,7 @@ NORETURN void cleanup_exit(int); X(INT, NONE, sshprot) \ X(BOOL, NONE, ssh2_des_cbc) /* "des-cbc" unrecommended SSH-2 cipher */ \ X(BOOL, NONE, ssh_no_userauth) /* bypass "ssh-userauth" (SSH-2 only) */ \ + X(BOOL, NONE, ssh_no_trivial_userauth) /* disable trivial types of auth */ \ X(BOOL, NONE, ssh_show_banner) /* show USERAUTH_BANNERs (SSH-2 only) */ \ X(BOOL, NONE, try_tis_auth) \ X(BOOL, NONE, try_ki_auth) \ diff --git a/settings.c b/settings.c index 3f8f2e92..43412ef9 100644 --- a/settings.c +++ b/settings.c @@ -609,6 +609,7 @@ void save_open_settings(settings_w *sesskey, Conf *conf) #endif write_setting_s(sesskey, "RekeyBytes", conf_get_str(conf, CONF_ssh_rekey_data)); write_setting_b(sesskey, "SshNoAuth", conf_get_bool(conf, CONF_ssh_no_userauth)); + write_setting_b(sesskey, "SshNoTrivialAuth", conf_get_bool(conf, CONF_ssh_no_trivial_userauth)); write_setting_b(sesskey, "SshBanner", conf_get_bool(conf, CONF_ssh_show_banner)); write_setting_b(sesskey, "AuthTIS", conf_get_bool(conf, CONF_try_tis_auth)); write_setting_b(sesskey, "AuthKI", conf_get_bool(conf, CONF_try_ki_auth)); @@ -1025,6 +1026,7 @@ void load_open_settings(settings_r *sesskey, Conf *conf) gpps(sesskey, "LogHost", "", conf, CONF_loghost); gppb(sesskey, "SSH2DES", false, conf, CONF_ssh2_des_cbc); gppb(sesskey, "SshNoAuth", false, conf, CONF_ssh_no_userauth); + gppb(sesskey, "SshNoTrivialAuth", false, conf, CONF_ssh_no_trivial_userauth); gppb(sesskey, "SshBanner", true, conf, CONF_ssh_show_banner); gppb(sesskey, "AuthTIS", false, conf, CONF_try_tis_auth); gppb(sesskey, "AuthKI", true, conf, CONF_try_ki_auth); diff --git a/ssh/login1.c b/ssh/login1.c index 716e248d..e0230d81 100644 --- a/ssh/login1.c +++ b/ssh/login1.c @@ -27,7 +27,7 @@ struct ssh1_login_state { char *savedhost; int savedport; - bool try_agent_auth; + bool try_agent_auth, is_trivial_auth; int remote_protoflags; int local_protoflags; @@ -105,6 +105,8 @@ PacketProtocolLayer *ssh1_login_new( s->savedhost = dupstr(host); s->savedport = port; s->successor_layer = successor_layer; + s->is_trivial_auth = true; + return &s->ppl; } @@ -645,6 +647,7 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl) s->ppl.bpp, SSH1_CMSG_AUTH_RSA_RESPONSE); put_data(pkt, ret + 5, 16); pq_push(s->ppl.out_pq, pkt); + s->is_trivial_auth = false; crMaybeWaitUntilV( (pktin = ssh1_login_pop(s)) != NULL); @@ -814,6 +817,7 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl) s->ppl.bpp, SSH1_CMSG_AUTH_RSA_RESPONSE); put_data(pkt, buffer, 16); pq_push(s->ppl.out_pq, pkt); + s->is_trivial_auth = false; mp_free(challenge); mp_free(response); @@ -1105,6 +1109,7 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl) put_stringz(pkt, prompt_get_result_ref(s->cur_prompt->prompts[0])); pq_push(s->ppl.out_pq, pkt); } + s->is_trivial_auth = false; ppl_logevent("Sent password"); free_prompts(s->cur_prompt); s->cur_prompt = NULL; @@ -1121,6 +1126,13 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl) } } + if (conf_get_bool(s->conf, CONF_ssh_no_trivial_userauth) && + s->is_trivial_auth) { + ssh_proto_error(s->ppl.ssh, "Authentication was trivial! " + "Abandoning session as specified in configuration."); + return; + } + ppl_logevent("Authentication successful"); if (conf_get_bool(s->conf, CONF_compression)) { diff --git a/ssh/ppl.h b/ssh/ppl.h index 363b0e6d..cd3e4694 100644 --- a/ssh/ppl.h +++ b/ssh/ppl.h @@ -116,7 +116,7 @@ PacketProtocolLayer *ssh2_transport_new( PacketProtocolLayer *ssh2_userauth_new( PacketProtocolLayer *successor_layer, const char *hostname, const char *fullhostname, - Filename *keyfile, bool show_banner, bool tryagent, + Filename *keyfile, bool show_banner, bool tryagent, bool notrivialauth, const char *default_username, bool change_username, bool try_ki_auth, bool try_gssapi_auth, bool try_gssapi_kex_auth, diff --git a/ssh/ssh.c b/ssh/ssh.c index efeb1f7e..e8724777 100644 --- a/ssh/ssh.c +++ b/ssh/ssh.c @@ -254,7 +254,9 @@ static void ssh_got_ssh_version(struct ssh_version_receiver *rcv, connection_layer, ssh->savedhost, ssh->fullhostname, conf_get_filename(ssh->conf, CONF_keyfile), conf_get_bool(ssh->conf, CONF_ssh_show_banner), - conf_get_bool(ssh->conf, CONF_tryagent), username, + conf_get_bool(ssh->conf, CONF_tryagent), + conf_get_bool(ssh->conf, CONF_ssh_no_trivial_userauth), + username, conf_get_bool(ssh->conf, CONF_change_username), conf_get_bool(ssh->conf, CONF_try_ki_auth), #ifndef NO_GSSAPI diff --git a/ssh/userauth2-client.c b/ssh/userauth2-client.c index a8f5b2c5..ddce251a 100644 --- a/ssh/userauth2-client.c +++ b/ssh/userauth2-client.c @@ -28,7 +28,7 @@ struct ssh2_userauth_state { PacketProtocolLayer *transport_layer, *successor_layer; Filename *keyfile; - bool show_banner, tryagent, change_username; + bool show_banner, tryagent, notrivialauth, change_username; char *hostname, *fullhostname; char *default_username; bool try_ki_auth, try_gssapi_auth, try_gssapi_kex_auth, gssapi_fwd; @@ -82,6 +82,7 @@ struct ssh2_userauth_state { int len; PktOut *pktout; bool want_user_input; + bool is_trivial_auth; agent_pending_query *auth_agent_query; bufchain banner; @@ -134,7 +135,7 @@ static const PacketProtocolLayerVtable ssh2_userauth_vtable = { PacketProtocolLayer *ssh2_userauth_new( PacketProtocolLayer *successor_layer, const char *hostname, const char *fullhostname, - Filename *keyfile, bool show_banner, bool tryagent, + Filename *keyfile, bool show_banner, bool tryagent, bool notrivialauth, const char *default_username, bool change_username, bool try_ki_auth, bool try_gssapi_auth, bool try_gssapi_kex_auth, bool gssapi_fwd, struct ssh_connection_shared_gss_state *shgss) @@ -149,6 +150,7 @@ PacketProtocolLayer *ssh2_userauth_new( s->keyfile = filename_copy(keyfile); s->show_banner = show_banner; s->tryagent = tryagent; + s->notrivialauth = notrivialauth; s->default_username = dupstr(default_username); s->change_username = change_username; s->try_ki_auth = try_ki_auth; @@ -157,6 +159,7 @@ PacketProtocolLayer *ssh2_userauth_new( s->gssapi_fwd = gssapi_fwd; s->shgss = shgss; s->last_methods_string = strbuf_new(); + s->is_trivial_auth = true; bufchain_init(&s->banner); bufchain_sink_init(&s->banner_bs, &s->banner); @@ -818,6 +821,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) sigblob); pq_push(s->ppl.out_pq, s->pktout); s->type = AUTH_TYPE_PUBLICKEY; + s->is_trivial_auth = false; } else { ppl_logevent("Pageant refused signing request"); ppl_printf("Pageant failed to " @@ -1038,6 +1042,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) ssh_key_free(key->key); sfree(key->comment); sfree(key); + s->is_trivial_auth = false; } #ifndef NO_GSSAPI @@ -1169,6 +1174,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) * no longer says CONTINUE_NEEDED */ if (s->gss_sndtok.length != 0) { + s->is_trivial_auth = false; s->pktout = ssh_bpp_new_pktout( s->ppl.bpp, SSH2_MSG_USERAUTH_GSSAPI_TOKEN); @@ -1288,7 +1294,6 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) * Loop while the server continues to send INFO_REQUESTs. */ while (pktin->type == SSH2_MSG_USERAUTH_INFO_REQUEST) { - ptrlen name, inst; strbuf *sb; @@ -1308,6 +1313,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) */ s->num_prompts = get_uint32(pktin); for (uint32_t i = 0; i < s->num_prompts; i++) { + s->is_trivial_auth = false; ptrlen prompt = get_string(pktin); bool echo = get_bool(pktin); @@ -1472,7 +1478,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) pq_push_front(s->ppl.in_pq, pktin); } else if (s->can_passwd) { - + s->is_trivial_auth = false; /* * Plain old password authentication. */ @@ -1731,6 +1737,12 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) } userauth_success: + if (s->notrivialauth && s->is_trivial_auth) { + ssh_proto_error(s->ppl.ssh, "Authentication was trivial! " + "Abandoning session as specified in configuration."); + return; + } + /* * We've just received USERAUTH_SUCCESS, and we haven't sent * any packets since. Signal the transport layer to consider diff --git a/unix/plink.c b/unix/plink.c index f2310fc5..606bfc69 100644 --- a/unix/plink.c +++ b/unix/plink.c @@ -528,6 +528,8 @@ static void usage(void) printf(" -i key private key file for user authentication\n"); printf(" -noagent disable use of Pageant\n"); printf(" -agent enable use of Pageant\n"); + printf(" -no-trivial-auth\n"); + printf(" disconnect if SSH authentication succeeds trivially\n"); printf(" -noshare disable use of connection sharing\n"); printf(" -share enable use of connection sharing\n"); printf(" -hostkey keyid\n"); diff --git a/windows/help.h b/windows/help.h index 5b11af3c..a1e24c18 100644 --- a/windows/help.h +++ b/windows/help.h @@ -111,6 +111,7 @@ #define WINHELP_CTX_ssh_kex_repeat "config-ssh-kex-rekey" #define WINHELP_CTX_ssh_kex_manual_hostkeys "config-ssh-kex-manual-hostkeys" #define WINHELP_CTX_ssh_auth_bypass "config-ssh-noauth" +#define WINHELP_CTX_ssh_no_trivial_userauth "config-ssh-notrivialauth" #define WINHELP_CTX_ssh_auth_banner "config-ssh-banner" #define WINHELP_CTX_ssh_auth_privkey "config-ssh-privkey" #define WINHELP_CTX_ssh_auth_agentfwd "config-ssh-agentfwd" diff --git a/windows/plink.c b/windows/plink.c index 071e088d..70bf1567 100644 --- a/windows/plink.c +++ b/windows/plink.c @@ -150,6 +150,8 @@ static void usage(void) printf(" -i key private key file for user authentication\n"); printf(" -noagent disable use of Pageant\n"); printf(" -agent enable use of Pageant\n"); + printf(" -no-trivial-auth\n"); + printf(" disconnect if SSH authentication succeeds trivially\n"); printf(" -noshare disable use of connection sharing\n"); printf(" -share enable use of connection sharing\n"); printf(" -hostkey keyid\n"); -- cgit v1.2.3 From ff941299cfc427f8ba2939d1950d8f954e3e3602 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 19 Jun 2021 15:41:18 +0100 Subject: Uppity: add stunt options for trivial authentication. This allows the 'no trivial auth' option introduced by the previous commit to be tested. Uppity has grown three new options to make it accept "none" authentication, keyboard-interactive involving no prompts, and the perverse sending of USERAUTH_SUCCESS after a signatureless public-key offer. The first of those options also enables the analogue in SSH-1; the other two have no SSH-1 analogues in the first place. (SSH-1 public key authentication has a challenge-response structure that doesn't contain any way to terminate the exchange early with success. And the TIS and CryptoCard methods, which are its closest analogue of k-i, have a fixed number of prompts, which is not 0.) --- ssh/login1-server.c | 5 ++-- ssh/server.h | 3 ++ ssh/userauth2-server.c | 6 ++-- unix/uppity.c | 74 +++++++++++++++++++++++++++++++++++--------------- 4 files changed, 62 insertions(+), 26 deletions(-) diff --git a/ssh/login1-server.c b/ssh/login1-server.c index 040342da..14f5edd6 100644 --- a/ssh/login1-server.c +++ b/ssh/login1-server.c @@ -39,7 +39,6 @@ struct ssh1_login_server_state { unsigned ap_methods, current_method; unsigned char auth_rsa_expected_response[16]; RSAKey *authkey; - bool auth_successful; PacketProtocolLayer ppl; }; @@ -267,7 +266,9 @@ static void ssh1_login_server_process_queue(PacketProtocolLayer *ppl) s->username.ptr = s->username_str = mkstr(s->username); ppl_logevent("Received username '%.*s'", PTRLEN_PRINTF(s->username)); - s->auth_successful = auth_none(s->authpolicy, s->username); + if (auth_none(s->authpolicy, s->username)) + goto auth_success; + while (1) { /* Signal failed authentication */ pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_SMSG_FAILURE); diff --git a/ssh/server.h b/ssh/server.h index 5cc393df..7a4f9e78 100644 --- a/ssh/server.h +++ b/ssh/server.h @@ -21,6 +21,9 @@ struct SshServerConfig { bool stunt_pretend_to_accept_any_pubkey; bool stunt_open_unconditional_agent_socket; + bool stunt_allow_none_auth; + bool stunt_allow_trivial_ki_auth; + bool stunt_return_success_to_pubkey_offer; }; Plug *ssh_server_plug( diff --git a/ssh/userauth2-server.c b/ssh/userauth2-server.c index da1e79c6..bfe258ce 100644 --- a/ssh/userauth2-server.c +++ b/ssh/userauth2-server.c @@ -209,7 +209,8 @@ static void ssh2_userauth_server_process_queue(PacketProtocolLayer *ppl) if (!(s->methods & s->this_method)) goto failure; - has_signature = get_bool(pktin); + has_signature = get_bool(pktin) || + s->ssc->stunt_return_success_to_pubkey_offer; algorithm = get_string(pktin); blob = get_string(pktin); @@ -251,7 +252,8 @@ static void ssh2_userauth_server_process_queue(PacketProtocolLayer *ppl) signature = get_string(pktin); success = ssh_key_verify(key, signature, - ptrlen_from_strbuf(sigdata)); + ptrlen_from_strbuf(sigdata)) || + s->ssc->stunt_return_success_to_pubkey_offer; ssh_key_free(key); strbuf_free(sigdata); diff --git a/unix/uppity.c b/unix/uppity.c index 1a35451b..4189c7e0 100644 --- a/unix/uppity.c +++ b/unix/uppity.c @@ -110,6 +110,8 @@ void make_unix_sftp_filehandle_key(void *data, size_t size) static bool verbose; +struct server_config; + struct AuthPolicyShared { struct AuthPolicy_ssh1_pubkey *ssh1keys; struct AuthPolicy_ssh2_pubkey *ssh2keys; @@ -124,6 +126,24 @@ struct server_instance { unsigned id; AuthPolicy ap; LogPolicy logpolicy; + struct server_config *cfg; +}; + +struct server_config { + Conf *conf; + const SshServerConfig *ssc; + + ssh_key **hostkeys; + int nhostkeys; + + RSAKey *hostkey1; + + struct AuthPolicyShared *ap_shared; + + unsigned next_id; + + Socket *listening_socket; + Plug listening_plug; }; static void log_to_stderr(unsigned id, const char *msg) @@ -175,11 +195,21 @@ struct AuthPolicy_ssh2_pubkey { unsigned auth_methods(AuthPolicy *ap) { - return (AUTHMETHOD_PUBLICKEY | AUTHMETHOD_PASSWORD | AUTHMETHOD_KBDINT | - AUTHMETHOD_TIS | AUTHMETHOD_CRYPTOCARD); + struct server_instance *inst = container_of( + ap, struct server_instance, ap); + unsigned methods = (AUTHMETHOD_PUBLICKEY | AUTHMETHOD_PASSWORD | + AUTHMETHOD_KBDINT | AUTHMETHOD_TIS | + AUTHMETHOD_CRYPTOCARD); + if (inst->cfg->ssc->stunt_allow_none_auth) + methods |= AUTHMETHOD_NONE; + return methods; } bool auth_none(AuthPolicy *ap, ptrlen username) { + struct server_instance *inst = container_of( + ap, struct server_instance, ap); + if (inst->cfg->ssc->stunt_allow_none_auth) + return true; return false; } int auth_password(AuthPolicy *ap, ptrlen username, ptrlen password, @@ -249,13 +279,21 @@ AuthKbdInt *auth_kbdint_prompts(AuthPolicy *ap, ptrlen username) aki->prompts[1].prompt = dupstr("Silent prompt: "); aki->prompts[1].echo = false; return aki; - case 1: + case 1: { + struct server_instance *inst = container_of( + ap, struct server_instance, ap); aki = snew(AuthKbdInt); - aki->title = dupstr("Zero-prompt step"); - aki->instruction = dupstr("Shouldn't see any prompts this time"); + if (inst->cfg->ssc->stunt_allow_trivial_ki_auth) { + aki->title = dupstr(""); + aki->instruction = dupstr(""); + } else { + aki->title = dupstr("Zero-prompt step"); + aki->instruction = dupstr("Shouldn't see any prompts this time"); + } aki->nprompts = 0; aki->prompts = NULL; return aki; + } default: ap->kbdint_state = 0; return NULL; @@ -416,23 +454,6 @@ static bool longoptnoarg(const char *arg, const char *expected) return false; } -struct server_config { - Conf *conf; - const SshServerConfig *ssc; - - ssh_key **hostkeys; - int nhostkeys; - - RSAKey *hostkey1; - - struct AuthPolicyShared *ap_shared; - - unsigned next_id; - - Socket *listening_socket; - Plug listening_plug; -}; - static Plug *server_conn_plug( struct server_config *cfg, struct server_instance **inst_out) { @@ -442,7 +463,10 @@ static Plug *server_conn_plug( inst->id = cfg->next_id++; inst->ap.shared = cfg->ap_shared; + if (cfg->ssc->stunt_allow_trivial_ki_auth) + inst->ap.kbdint_state = 1; inst->logpolicy.vt = &server_logpolicy_vt; + inst->cfg = cfg; if (inst_out) *inst_out = inst; @@ -785,6 +809,12 @@ int main(int argc, char **argv) ssc.stunt_pretend_to_accept_any_pubkey = true; } else if (!strcmp(arg, "--open-unconditional-agent-socket")) { ssc.stunt_open_unconditional_agent_socket = true; + } else if (!strcmp(arg, "--allow-none-auth")) { + ssc.stunt_allow_none_auth = true; + } else if (!strcmp(arg, "--allow-trivial-ki-auth")) { + ssc.stunt_allow_trivial_ki_auth = true; + } else if (!strcmp(arg, "--return-success-to-pubkey-offer")) { + ssc.stunt_return_success_to_pubkey_offer = true; } else { fprintf(stderr, "%s: unrecognised option '%s'\n", appname, arg); exit(1); -- cgit v1.2.3 From 6246ff3f0a08c3d551f59c07813fee4640a67926 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 27 Jun 2021 13:52:48 +0100 Subject: New Seat callback, seat_sent(). This is used to notify the Seat that some data has been cleared from the backend's outgoing data buffer. In other words, it notifies the Seat that it might be worth calling backend_sendbuffer() again. We've never needed this before, because until now, Seats have always been the 'main program' part of the application, meaning they were also in control of the event loop. So they've been able to call backend_sendbuffer() proactively, every time they go round the event loop, instead of having to wait for a callback. But now, the SSH proxy is the first example of a Seat without privileged access to the event loop, so it has no way to find out that the backend's sendbuffer has got smaller. And without that, it can't pass that notification on to plug_sent, to unblock in turn whatever the proxied connection might have been waiting to send. In fact, before this commit, sshproxy.c never called plug_sent at all. As a result, large data uploads over an SSH jump host would hang forever as soon as the outgoing buffer filled up for the first time: the main backend (to which sshproxy.c was acting as a Socket) would carefully stop filling up the buffer, and then never receive the call to plug_sent that would cause it to start again. The new callback is ignored everywhere except in sshproxy.c. It might be a good idea to remove backend_sendbuffer() entirely and convert all previous uses of it into non-empty implementations of this callback, so that we've only got one system; but for the moment, I haven't done that. --- otherbackends/raw.c | 1 + otherbackends/rlogin.c | 1 + otherbackends/supdup.c | 1 + otherbackends/telnet.c | 1 + pscp.c | 1 + psftp.c | 1 + putty.h | 14 ++++++++++++++ ssh.h | 1 + ssh/bpp-bare.c | 2 ++ ssh/bpp1.c | 2 ++ ssh/bpp2.c | 2 ++ ssh/connection2.c | 1 + ssh/server.c | 5 +++++ ssh/sesschan.c | 1 + ssh/ssh.c | 6 ++++++ ssh/transport2.c | 2 ++ sshproxy.c | 7 +++++++ unix/plink.c | 1 + unix/serial.c | 1 + unix/window.c | 1 + utils/nullseat.c | 1 + windows/plink.c | 1 + windows/serial.c | 1 + windows/window.c | 1 + 24 files changed, 56 insertions(+) diff --git a/otherbackends/raw.c b/otherbackends/raw.c index 7977f386..9c803594 100644 --- a/otherbackends/raw.c +++ b/otherbackends/raw.c @@ -104,6 +104,7 @@ static void raw_sent(Plug *plug, size_t bufsize) { Raw *raw = container_of(plug, Raw, plug); raw->bufsize = bufsize; + seat_sent(raw->seat, raw->bufsize); } static const PlugVtable Raw_plugvt = { diff --git a/otherbackends/rlogin.c b/otherbackends/rlogin.c index 8289a508..30ad0526 100644 --- a/otherbackends/rlogin.c +++ b/otherbackends/rlogin.c @@ -116,6 +116,7 @@ static void rlogin_sent(Plug *plug, size_t bufsize) { Rlogin *rlogin = container_of(plug, Rlogin, plug); rlogin->bufsize = bufsize; + seat_sent(rlogin->seat, rlogin->bufsize); } static void rlogin_startup(Rlogin *rlogin, const char *ruser) diff --git a/otherbackends/supdup.c b/otherbackends/supdup.c index 36dbb447..e7eff6ee 100644 --- a/otherbackends/supdup.c +++ b/otherbackends/supdup.c @@ -600,6 +600,7 @@ static void supdup_sent(Plug *plug, size_t bufsize) { Supdup *supdup = container_of(plug, Supdup, plug); supdup->bufsize = bufsize; + seat_sent(supdup->seat, supdup->bufsize); } static void supdup_send_36bits(Supdup *supdup, unsigned long long thirtysix) diff --git a/otherbackends/telnet.c b/otherbackends/telnet.c index f8b41ac3..4b784036 100644 --- a/otherbackends/telnet.c +++ b/otherbackends/telnet.c @@ -662,6 +662,7 @@ static void telnet_sent(Plug *plug, size_t bufsize) { Telnet *telnet = container_of(plug, Telnet, plug); telnet->bufsize = bufsize; + seat_sent(telnet->seat, telnet->bufsize); } static const PlugVtable Telnet_plugvt = { diff --git a/pscp.c b/pscp.c index 82085962..f8a6965c 100644 --- a/pscp.c +++ b/pscp.c @@ -65,6 +65,7 @@ static bool pscp_eof(Seat *); static const SeatVtable pscp_seat_vt = { .output = pscp_output, .eof = pscp_eof, + .sent = nullseat_sent, .get_userpass_input = filexfer_get_userpass_input, .notify_remote_exit = nullseat_notify_remote_exit, .notify_remote_disconnect = nullseat_notify_remote_disconnect, diff --git a/psftp.c b/psftp.c index 8eaa6fc5..1fff45ef 100644 --- a/psftp.c +++ b/psftp.c @@ -47,6 +47,7 @@ static bool psftp_eof(Seat *); static const SeatVtable psftp_seat_vt = { .output = psftp_output, .eof = psftp_eof, + .sent = nullseat_sent, .get_userpass_input = filexfer_get_userpass_input, .notify_remote_exit = nullseat_notify_remote_exit, .notify_remote_disconnect = nullseat_notify_remote_disconnect, diff --git a/putty.h b/putty.h index d67b4e75..254e370b 100644 --- a/putty.h +++ b/putty.h @@ -886,6 +886,17 @@ struct SeatVtable { */ bool (*eof)(Seat *seat); + /* + * Called by the back end to notify that the output backlog has + * changed size. A front end in control of the event loop won't + * necessarily need this (they can just keep checking it via + * backend_sendbuffer at every opportunity), but one buried in the + * depths of something else (like an SSH proxy) will need to be + * proactively notified that the amount of buffered data has + * become smaller. + */ + void (*sent)(Seat *seat, size_t new_sendbuffer); + /* * Try to get answers from a set of interactive login prompts. The * prompts are provided in 'p'; the bufchain 'input' holds the @@ -1117,6 +1128,8 @@ static inline size_t seat_output( { return seat->vt->output(seat, err, data, len); } static inline bool seat_eof(Seat *seat) { return seat->vt->eof(seat); } +static inline void seat_sent(Seat *seat, size_t bufsize) +{ seat->vt->sent(seat, bufsize); } static inline int seat_get_userpass_input( Seat *seat, prompts_t *p, bufchain *input) { return seat->vt->get_userpass_input(seat, p, input); } @@ -1190,6 +1203,7 @@ static inline size_t seat_stderr_pl(Seat *seat, ptrlen data) size_t nullseat_output( Seat *seat, bool is_stderr, const void *data, size_t len); bool nullseat_eof(Seat *seat); +void nullseat_sent(Seat *seat, size_t bufsize); int nullseat_get_userpass_input(Seat *seat, prompts_t *p, bufchain *input); void nullseat_notify_remote_exit(Seat *seat); void nullseat_notify_remote_disconnect(Seat *seat); diff --git a/ssh.h b/ssh.h index 61fc6d14..23bed390 100644 --- a/ssh.h +++ b/ssh.h @@ -402,6 +402,7 @@ bool ssh_is_bare(Ssh *ssh); /* Communications back to ssh.c from the BPP */ void ssh_conn_processed_data(Ssh *ssh); +void ssh_sendbuffer_changed(Ssh *ssh); void ssh_check_frozen(Ssh *ssh); /* Functions to abort the connection, for various reasons. */ diff --git a/ssh/bpp-bare.c b/ssh/bpp-bare.c index 3546d160..f1a889aa 100644 --- a/ssh/bpp-bare.c +++ b/ssh/bpp-bare.c @@ -201,4 +201,6 @@ static void ssh2_bare_bpp_handle_output(BinaryPacketProtocol *bpp) ssh2_bare_bpp_format_packet(s, pkt); ssh_free_pktout(pkt); } + + ssh_sendbuffer_changed(bpp->ssh); } diff --git a/ssh/bpp1.c b/ssh/bpp1.c index f84d787b..b82932f7 100644 --- a/ssh/bpp1.c +++ b/ssh/bpp1.c @@ -376,6 +376,8 @@ static void ssh1_bpp_handle_output(BinaryPacketProtocol *bpp) break; } } + + ssh_sendbuffer_changed(bpp->ssh); } static void ssh1_bpp_queue_disconnect(BinaryPacketProtocol *bpp, diff --git a/ssh/bpp2.c b/ssh/bpp2.c index f29b962f..dc98e27c 100644 --- a/ssh/bpp2.c +++ b/ssh/bpp2.c @@ -979,4 +979,6 @@ static void ssh2_bpp_handle_output(BinaryPacketProtocol *bpp) ssh2_bpp_enable_pending_compression(s); } } + + ssh_sendbuffer_changed(bpp->ssh); } diff --git a/ssh/connection2.c b/ssh/connection2.c index 48436848..2e7102db 100644 --- a/ssh/connection2.c +++ b/ssh/connection2.c @@ -1157,6 +1157,7 @@ static size_t ssh2_try_send(struct ssh2_channel *c) if (!bufsize && c->pending_eof) ssh2_channel_try_eof(c); + ssh_sendbuffer_changed(s->ppl.ssh); return bufsize; } diff --git a/ssh/server.c b/ssh/server.c index 642d4ce0..6abbf332 100644 --- a/ssh/server.c +++ b/ssh/server.c @@ -107,6 +107,7 @@ static int server_confirm_weak_cached_hostkey( static const SeatVtable server_seat_vt = { .output = nullseat_output, .eof = nullseat_eof, + .sent = nullseat_sent, .get_userpass_input = nullseat_get_userpass_input, .notify_remote_exit = nullseat_notify_remote_exit, .notify_remote_disconnect = nullseat_notify_remote_disconnect, @@ -187,6 +188,10 @@ LogContext *ssh_get_logctx(Ssh *ssh) return srv->logctx; } +void ssh_sendbuffer_changed(Ssh *ssh) +{ +} + void ssh_throttle_conn(Ssh *ssh, int adjust) { server *srv = container_of(ssh, server, ssh); diff --git a/ssh/sesschan.c b/ssh/sesschan.c index f1bed6f2..f16faad2 100644 --- a/ssh/sesschan.c +++ b/ssh/sesschan.c @@ -186,6 +186,7 @@ static bool sesschan_get_window_pixel_size(Seat *seat, int *w, int *h); static const SeatVtable sesschan_seat_vt = { .output = sesschan_seat_output, .eof = sesschan_seat_eof, + .sent = nullseat_sent, .get_userpass_input = nullseat_get_userpass_input, .notify_remote_exit = sesschan_notify_remote_exit, .notify_remote_disconnect = nullseat_notify_remote_disconnect, diff --git a/ssh/ssh.c b/ssh/ssh.c index e8724777..096c8b01 100644 --- a/ssh/ssh.c +++ b/ssh/ssh.c @@ -640,6 +640,7 @@ static void ssh_sent(Plug *plug, size_t bufsize) if (bufsize < SSH_MAX_BACKLOG) { ssh_throttle_all(ssh, false, bufsize); queue_idempotent_callback(&ssh->ic_out_raw); + ssh_sendbuffer_changed(ssh); } } @@ -1042,6 +1043,11 @@ static size_t ssh_sendbuffer(Backend *be) return backlog; } +void ssh_sendbuffer_changed(Ssh *ssh) +{ + seat_sent(ssh->seat, ssh_sendbuffer(&ssh->backend)); +} + /* * Called to set the size of the window from SSH's POV. */ diff --git a/ssh/transport2.c b/ssh/transport2.c index fcd2667a..2966f2d7 100644 --- a/ssh/transport2.c +++ b/ssh/transport2.c @@ -1484,6 +1484,7 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl) * layer's outgoing queue on to our own. */ pq_concatenate(s->ppl.out_pq, s->ppl.out_pq, &s->pq_out_higher); + ssh_sendbuffer_changed(s->ppl.ssh); /* * Expect SSH2_MSG_NEWKEYS from server. @@ -1620,6 +1621,7 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl) /* Pass through outgoing packets from the higher layer. */ pq_concatenate(s->ppl.out_pq, s->ppl.out_pq, &s->pq_out_higher); + ssh_sendbuffer_changed(s->ppl.ssh); /* Wait for either a KEXINIT, or something setting * s->rekey_class. This call to ssh2_transport_pop also has diff --git a/sshproxy.c b/sshproxy.c index 0ffa74ff..f36a2a9b 100644 --- a/sshproxy.c +++ b/sshproxy.c @@ -258,6 +258,12 @@ static bool sshproxy_eof(Seat *seat) return false; } +static void sshproxy_sent(Seat *seat, size_t new_bufsize) +{ + SshProxy *sp = container_of(seat, SshProxy, seat); + plug_sent(sp->plug, new_bufsize); +} + static void sshproxy_notify_remote_disconnect(Seat *seat) { SshProxy *sp = container_of(seat, SshProxy, seat); @@ -415,6 +421,7 @@ static bool sshproxy_set_trust_status(Seat *seat, bool trusted) static const SeatVtable SshProxy_seat_vt = { .output = sshproxy_output, .eof = sshproxy_eof, + .sent = sshproxy_sent, .get_userpass_input = sshproxy_get_userpass_input, .notify_remote_exit = nullseat_notify_remote_exit, .notify_remote_disconnect = sshproxy_notify_remote_disconnect, diff --git a/unix/plink.c b/unix/plink.c index 606bfc69..7663b976 100644 --- a/unix/plink.c +++ b/unix/plink.c @@ -389,6 +389,7 @@ static bool plink_seat_interactive(Seat *seat) static const SeatVtable plink_seat_vt = { .output = plink_output, .eof = plink_eof, + .sent = nullseat_sent, .get_userpass_input = plink_get_userpass_input, .notify_remote_exit = nullseat_notify_remote_exit, .notify_remote_disconnect = nullseat_notify_remote_disconnect, diff --git a/unix/serial.c b/unix/serial.c index d4a1e0ba..d17e4cdd 100644 --- a/unix/serial.c +++ b/unix/serial.c @@ -454,6 +454,7 @@ static void serial_try_write(Serial *serial) bufchain_consume(&serial->output_data, ret); } + seat_sent(serial->seat, bufchain_size(&serial->output_data)); serial_uxsel_setup(serial); } diff --git a/unix/window.c b/unix/window.c index f787fa9b..8582777b 100644 --- a/unix/window.c +++ b/unix/window.c @@ -389,6 +389,7 @@ static bool gtk_seat_get_cursor_position(Seat *seat, int *x, int *y); static const SeatVtable gtk_seat_vt = { .output = gtk_seat_output, .eof = gtk_seat_eof, + .sent = nullseat_sent, .get_userpass_input = gtk_seat_get_userpass_input, .notify_remote_exit = gtk_seat_notify_remote_exit, .notify_remote_disconnect = nullseat_notify_remote_disconnect, diff --git a/utils/nullseat.c b/utils/nullseat.c index 9b69a07a..0c773af0 100644 --- a/utils/nullseat.c +++ b/utils/nullseat.c @@ -7,6 +7,7 @@ size_t nullseat_output( Seat *seat, bool is_stderr, const void *data, size_t len) { return 0; } bool nullseat_eof(Seat *seat) { return true; } +void nullseat_sent(Seat *seat, size_t bufsize) {} int nullseat_get_userpass_input( Seat *seat, prompts_t *p, bufchain *input) { return 0; } void nullseat_notify_remote_exit(Seat *seat) {} diff --git a/windows/plink.c b/windows/plink.c index 70bf1567..ac68ce7d 100644 --- a/windows/plink.c +++ b/windows/plink.c @@ -83,6 +83,7 @@ static bool plink_seat_interactive(Seat *seat) static const SeatVtable plink_seat_vt = { .output = plink_output, .eof = plink_eof, + .sent = nullseat_sent, .get_userpass_input = plink_get_userpass_input, .notify_remote_exit = nullseat_notify_remote_exit, .notify_remote_disconnect = nullseat_notify_remote_disconnect, diff --git a/windows/serial.c b/windows/serial.c index 7f4bcf2e..3d5ea8e5 100644 --- a/windows/serial.c +++ b/windows/serial.c @@ -88,6 +88,7 @@ static void serial_sentdata(struct handle *h, size_t new_backlog, int err) seat_connection_fatal(serial->seat, "%s", error_msg); } else { serial->bufsize = new_backlog; + seat_sent(serial->seat, serial->bufsize); } } diff --git a/windows/window.c b/windows/window.c index 36284927..6ab8e16b 100644 --- a/windows/window.c +++ b/windows/window.c @@ -330,6 +330,7 @@ static bool win_seat_get_window_pixel_size(Seat *seat, int *x, int *y); static const SeatVtable win_seat_vt = { .output = win_seat_output, .eof = win_seat_eof, + .sent = nullseat_sent, .get_userpass_input = win_seat_get_userpass_input, .notify_remote_exit = win_seat_notify_remote_exit, .notify_remote_disconnect = nullseat_notify_remote_disconnect, -- cgit v1.2.3 From 2029aa55c22631bd1c46df0c7ce817770d38d203 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 1 Jul 2021 18:25:56 +0100 Subject: Restore missing screen updates from scrollbar buttons. In commit f69cf86a61a8109, I added a call to term_update that happens when we receive WM_VSCROLL / SB_THUMBPOSITION in the subsidiary message loop that Windows creates during the handling of WM_SYSCOMMAND / SC_VSCROLL. The effect was that interactive dragging of the scrollbar now redraws the window at every step, whereas previously it didn't. A user just pointed out that if you click on one of the scrollbar end buttons and hold it down until it begins emulating key repeat, the same bug occurs: the window isn't redrawn until you release the mouse button and the subsidiary message loop ends. This commit extends the previous fix to cover all of the WM_VSCROLL subtypes, instead of just SB_THUMBPOSITION and SB_THUMBTRACK. Redraws while holding down those scrollbar buttons now work again. --- windows/window.c | 93 +++++++++++++++++++++++++++----------------------------- 1 file changed, 45 insertions(+), 48 deletions(-) diff --git a/windows/window.c b/windows/window.c index 6ab8e16b..acfa3777 100644 --- a/windows/window.c +++ b/windows/window.c @@ -3075,57 +3075,54 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, if (GetScrollInfo(hwnd, SB_VERT, &si) == 0) si.nTrackPos = HIWORD(wParam); term_scroll(term, 1, si.nTrackPos); - - if (in_scrollbar_loop) { - /* - * Allow window updates to happen during interactive - * scroll. - * - * When the user takes hold of our window's scrollbar - * and wobbles it interactively back and forth, the - * first thing that happens is that this window - * procedure receives WM_SYSCOMMAND / SC_VSCROLL. [1] - * The default handler for that window message starts - * a subsidiary message loop, which continues to run - * until the user lets go of the scrollbar again. All - * WM_VSCROLL / SB_THUMBTRACK messages are generated - * by the handlers within that subsidiary message - * loop. - * - * So, during that time, _our_ message loop is not - * running, which means toplevel callbacks and timers - * and so forth are not happening, which means that - * when we redraw the window and set a timer to clear - * the cooldown flag 20ms later, that timer never - * fires, and we aren't able to keep redrawing the - * window. - * - * The 'obvious' answer would be to seize that - * SYSCOMMAND ourselves and inhibit the default - * handler, so that our message loop carries on - * running. But that would mean we'd have to - * reimplement the whole of the scrollbar handler! - * - * So instead we apply a bodge: set a static variable - * that indicates that we're _in_ that sub-loop, and - * if so, decide it's OK to manually call - * term_update() proper, bypassing the timer and - * cooldown and rate-limiting systems completely, - * whenever we see an SB_THUMBTRACK. This shouldn't - * cause a rate overload, because we're only doing it - * once per UI event! - * - * [1] Actually, there's an extra oddity where - * SC_HSCROLL and SC_VSCROLL have their documented - * values the wrong way round. Many people on the - * Internet have noticed this, e.g. - * https://stackoverflow.com/q/55528397 - */ - term_update(term); - } break; } } + + if (in_scrollbar_loop) { + /* + * Allow window updates to happen during interactive + * scroll. + * + * When the user takes hold of our window's scrollbar and + * wobbles it interactively back and forth, or presses on + * one of the arrow buttons at the ends, the first thing + * that happens is that this window procedure receives + * WM_SYSCOMMAND / SC_VSCROLL. [1] The default handler for + * that window message starts a subsidiary message loop, + * which continues to run until the user lets go of the + * scrollbar again. All WM_VSCROLL / SB_THUMBTRACK + * messages are generated by the handlers within that + * subsidiary message loop. + * + * So, during that time, _our_ message loop is not + * running, which means toplevel callbacks and timers and + * so forth are not happening, which means that when we + * redraw the window and set a timer to clear the cooldown + * flag 20ms later, that timer never fires, and we aren't + * able to keep redrawing the window. + * + * The 'obvious' answer would be to seize that SYSCOMMAND + * ourselves and inhibit the default handler, so that our + * message loop carries on running. But that would mean + * we'd have to reimplement the whole of the scrollbar + * handler! + * + * So instead we apply a bodge: set a static variable that + * indicates that we're _in_ that sub-loop, and if so, + * decide it's OK to manually call term_update() proper, + * bypassing the timer and cooldown and rate-limiting + * systems completely, whenever we see an SB_THUMBTRACK. + * This shouldn't cause a rate overload, because we're + * only doing it once per UI event! + * + * [1] Actually, there's an extra oddity where SC_HSCROLL + * and SC_VSCROLL have their documented values the wrong + * way round. Many people on the Internet have noticed + * this, e.g. https://stackoverflow.com/q/55528397 + */ + term_update(term); + } break; case WM_PALETTECHANGED: if ((HWND) wParam != hwnd && pal != NULL) { -- cgit v1.2.3 From c714dfc936285fb588d047bb71b27cd396af6490 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 1 Jul 2021 18:30:00 +0100 Subject: Close all thread handles returned from CreateThread. If you don't, they are permanently leaked. A user points out that this is particularly bad in Pageant, with the new named-pipe-based IPC, since it will spawn an input and output I/O thread per named pipe connection, leading to two handles being leaked every time. --- windows/handle-io.c | 12 ++++++++---- windows/pageant.c | 6 ++++-- windows/puttygen.c | 6 ++++-- windows/window.c | 6 ++++-- 4 files changed, 20 insertions(+), 10 deletions(-) diff --git a/windows/handle-io.c b/windows/handle-io.c index ecb48577..873eaf30 100644 --- a/windows/handle-io.c +++ b/windows/handle-io.c @@ -481,8 +481,10 @@ struct handle *handle_input_new(HANDLE handle, handle_inputfn_t gotdata, h->u.i.flags = flags; ensure_ready_event_setup(); - CreateThread(NULL, 0, handle_input_threadfunc, - &h->u.i, 0, &in_threadid); + HANDLE hThread = CreateThread(NULL, 0, handle_input_threadfunc, + &h->u.i, 0, &in_threadid); + if (hThread) + CloseHandle(hThread); /* we don't need the thread handle */ h->u.i.busy = true; return h; @@ -508,8 +510,10 @@ struct handle *handle_output_new(HANDLE handle, handle_outputfn_t sentdata, h->u.o.flags = flags; ensure_ready_event_setup(); - CreateThread(NULL, 0, handle_output_threadfunc, - &h->u.o, 0, &out_threadid); + HANDLE hThread = CreateThread(NULL, 0, handle_output_threadfunc, + &h->u.o, 0, &out_threadid); + if (hThread) + CloseHandle(hThread); /* we don't need the thread handle */ return h; } diff --git a/windows/pageant.c b/windows/pageant.c index c82c18bc..c1bc55cc 100644 --- a/windows/pageant.c +++ b/windows/pageant.c @@ -1631,8 +1631,10 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) DWORD wm_copydata_threadid; wmct.ev_msg_ready = CreateEvent(NULL, false, false, NULL); wmct.ev_reply_ready = CreateEvent(NULL, false, false, NULL); - CreateThread(NULL, 0, wm_copydata_threadfunc, - &inst, 0, &wm_copydata_threadid); + HANDLE hThread = CreateThread(NULL, 0, wm_copydata_threadfunc, + &inst, 0, &wm_copydata_threadid); + if (hThread) + CloseHandle(hThread); /* we don't need the thread handle */ add_handle_wait(wmct.ev_msg_ready, wm_copydata_got_msg, NULL); if (show_keylist_on_startup) diff --git a/windows/puttygen.c b/windows/puttygen.c index cf98b6fe..7bc8f494 100644 --- a/windows/puttygen.c +++ b/windows/puttygen.c @@ -1160,13 +1160,15 @@ static void start_generating_key(HWND hwnd, struct MainDlgState *state) params->key = &state->key; params->dsakey = &state->dsakey; - if (!CreateThread(NULL, 0, generate_key_thread, - params, 0, &threadid)) { + HANDLE hThread = CreateThread(NULL, 0, generate_key_thread, + params, 0, &threadid); + if (!hThread) { MessageBox(hwnd, "Out of thread resources", "Key generation error", MB_OK | MB_ICONERROR); sfree(params); } else { + CloseHandle(hThread); /* we don't need the thread handle */ state->generation_thread_exists = true; } } diff --git a/windows/window.c b/windows/window.c index acfa3777..b00b910c 100644 --- a/windows/window.c +++ b/windows/window.c @@ -5355,8 +5355,10 @@ static void wintw_clip_request_paste(TermWin *tw, int clipboard) * that tells us it's OK to paste. */ DWORD in_threadid; /* required for Win9x */ - CreateThread(NULL, 0, clipboard_read_threadfunc, - wgs.term_hwnd, 0, &in_threadid); + HANDLE hThread = CreateThread(NULL, 0, clipboard_read_threadfunc, + wgs.term_hwnd, 0, &in_threadid); + if (hThread) + CloseHandle(hThread); /* we don't need the thread handle */ } /* -- cgit v1.2.3 From 058e390ab5d40e5322b2d082c0e15b0dbde2c1d5 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 1 Jul 2021 18:59:44 +0100 Subject: Avoid crash in MIT Kerberos for Windows on session restart. A user reports that if you have MIT KfW loaded, and your PuTTY session terminates without the PuTTY process exiting, and you select 'Restart Session' from the menu, then a crash occurs inside the Kerberos library itself. Scuttlebutt on the Internet suggested this might be to do with unloading and then reloading the DLL within the process lifetime, which indeed we were doing. Now we avoid doing that for the KfW library in particular, by keeping a tree234 of module handles marked 'never unload this'. This is a workaround at best, but it seems to stop the problem happening in my own tests. --- windows/gss.c | 45 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/windows/gss.c b/windows/gss.c index 0b47d9a7..ebe5698d 100644 --- a/windows/gss.c +++ b/windows/gss.c @@ -95,6 +95,28 @@ const char *gsslogmsg = NULL; static void ssh_sspi_bind_fns(struct ssh_gss_library *lib); +static tree234 *libraries_to_never_unload; +static int library_to_never_unload_cmp(void *av, void *bv) +{ + uintptr_t a = (uintptr_t)av, b = (uintptr_t)bv; + return a < b ? -1 : a > b ? +1 : 0; +} +static void ensure_library_tree_exists(void) +{ + if (!libraries_to_never_unload) + libraries_to_never_unload = newtree234(library_to_never_unload_cmp); +} +static bool library_is_in_never_unload_tree(HMODULE module) +{ + ensure_library_tree_exists(); + return find234(libraries_to_never_unload, module, NULL); +} +static void add_library_to_never_unload_tree(HMODULE module) +{ + ensure_library_tree_exists(); + add234(libraries_to_never_unload, module); +} + struct ssh_gss_liblist *ssh_gss_setup(Conf *conf) { HMODULE module; @@ -145,6 +167,23 @@ struct ssh_gss_liblist *ssh_gss_setup(Conf *conf) LOAD_LIBRARY_SEARCH_SYSTEM32 | LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | LOAD_LIBRARY_SEARCH_USER_DIRS); + + /* + * The MIT Kerberos DLL suffers an internal segfault + * for some reason if you unload and reload one within + * the same process. So, make sure that after we load + * this library, we never free it. + * + * Or rather: after we've loaded it once, if any + * _further_ load returns the same module handle, we + * immediately free it again (to prevent the Windows + * API's internal reference count growing without + * bound). But on the other hand we never free it in + * ssh_gss_cleanup. + */ + if (library_is_in_never_unload_tree(module)) + FreeLibrary(module); + add_library_to_never_unload_tree(module); } sfree(buffer); } @@ -280,7 +319,11 @@ void ssh_gss_cleanup(struct ssh_gss_liblist *list) * another SSH instance still using it. */ for (i = 0; i < list->nlibraries; i++) { - FreeLibrary((HMODULE)list->libraries[i].handle); + if (list->libraries[i].id != 0) { + HMODULE module = (HMODULE)list->libraries[i].handle; + if (!library_is_in_never_unload_tree(module)) + FreeLibrary(module); + } 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. */ -- cgit v1.2.3 From 413398af85b27cd83134f5618bd82f81758f9603 Mon Sep 17 00:00:00 2001 From: Jacob Nevins Date: Fri, 9 Jul 2021 23:55:15 +0100 Subject: Document -no-trivial-auth more thoroughly. --- doc/man-plink.but | 9 +++++++++ doc/man-pscp.but | 9 +++++++++ doc/man-psftp.but | 9 +++++++++ doc/man-putty.but | 9 +++++++++ doc/plink.but | 2 ++ doc/pscp.but | 2 ++ doc/using.but | 9 +++++++++ 7 files changed, 49 insertions(+) diff --git a/doc/man-plink.but b/doc/man-plink.but index 33386227..89dadb09 100644 --- a/doc/man-plink.but +++ b/doc/man-plink.but @@ -203,6 +203,15 @@ which of the agent's keys to use. } \dd Allow use of an authentication agent. (This option is only necessary to override a setting in a saved session.) +\dt \cw{\-no\-trivial\-auth} + +\dd Disconnect from any SSH server which accepts authentication without +ever having asked for any kind of password or signature or token. (You +might want to enable this for a server you always expect to challenge +you, for instance to ensure ensure you don't accidentally type your key +file's passphrase into a compromised server spoofing Plink's passphrase +prompt.) + \dt \cw{\-noshare} \dd Don't test and try to share an existing connection, always make diff --git a/doc/man-pscp.but b/doc/man-pscp.but index b62e8cc2..8011483d 100644 --- a/doc/man-pscp.but +++ b/doc/man-pscp.but @@ -155,6 +155,15 @@ which of the agent's keys to use. } \dd Allow use of an authentication agent. (This option is only necessary to override a setting in a saved session.) +\dt \cw{\-no\-trivial\-auth} + +\dd Disconnect from any SSH server which accepts authentication without +ever having asked for any kind of password or signature or token. (You +might want to enable this for a server you always expect to challenge +you, for instance to ensure ensure you don't accidentally type your key +file's passphrase into a compromised server spoofing PSCP's passphrase +prompt.) + \dt \cw{\-hostkey} \e{key} \dd Specify an acceptable host public key. This option may be specified diff --git a/doc/man-psftp.but b/doc/man-psftp.but index 19f820e3..0c47aa0e 100644 --- a/doc/man-psftp.but +++ b/doc/man-psftp.but @@ -143,6 +143,15 @@ which of the agent's keys to use. } \dd Allow use of an authentication agent. (This option is only necessary to override a setting in a saved session.) +\dt \cw{\-no\-trivial\-auth} + +\dd Disconnect from any SSH server which accepts authentication without +ever having asked for any kind of password or signature or token. (You +might want to enable this for a server you always expect to challenge +you, for instance to ensure ensure you don't accidentally type your key +file's passphrase into a compromised server spoofing PSFTP's passphrase +prompt.) + \dt \cw{\-hostkey} \e{key} \dd Specify an acceptable host public key. This option may be specified diff --git a/doc/man-putty.but b/doc/man-putty.but index a1656d6c..3214a180 100644 --- a/doc/man-putty.but +++ b/doc/man-putty.but @@ -287,6 +287,15 @@ which of the agent's keys to use. } \dd Allow use of an authentication agent. (This option is only necessary to override a setting in a saved session.) +\dt \cw{\-no\-trivial\-auth} + +\dd Disconnect from any SSH server which accepts authentication without +ever having asked for any kind of password or signature or token. (You +might want to enable this for a server you always expect to challenge +you, for instance to ensure ensure you don't accidentally type your key +file's passphrase into a compromised server spoofing PuTTY's passphrase +prompt.) + \dt \cw{\-hostkey} \e{key} \dd Specify an acceptable host public key. This option may be specified diff --git a/doc/plink.but b/doc/plink.but index fcfb5f68..361b59c2 100644 --- a/doc/plink.but +++ b/doc/plink.but @@ -77,6 +77,8 @@ use Plink: \c -i key private key file for user authentication \c -noagent disable use of Pageant \c -agent enable use of Pageant +\c -no-trivial-auth +\c disconnect if SSH authentication succeeds trivially \c -noshare disable use of connection sharing \c -share enable use of connection sharing \c -hostkey keyid diff --git a/doc/pscp.but b/doc/pscp.but index 9d8daccd..2ee35ced 100644 --- a/doc/pscp.but +++ b/doc/pscp.but @@ -62,6 +62,8 @@ use PSCP: \c -i key private key file for user authentication \c -noagent disable use of Pageant \c -agent enable use of Pageant +\c -no-trivial-auth +\c disconnect if SSH authentication succeeds trivially \c -hostkey keyid \c manually specify a host key (may be repeated) \c -batch disable all interactive prompts diff --git a/doc/using.but b/doc/using.but index b583dc8c..02a67808 100644 --- a/doc/using.but +++ b/doc/using.but @@ -1014,6 +1014,15 @@ 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-no-trivial-auth} \i\c{-no-trivial-auth}: disconnect +if SSH authentication succeeds trivially + +This option causes PuTTY to abandon an SSH session if the server +accepts authentication without ever having asked for any kind of +password or signature or token. + +See \k{config-ssh-notrivialauth} for why you might want this. + \S2{using-cmdline-loghost} \i\c{-loghost}: specify a \i{logical host name} -- cgit v1.2.3 From 640e46a11254f553c9835b4e9a6f60578c6adc95 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 10 Jul 2021 10:31:07 +0100 Subject: Fix grammar nit. Ensure 'ensure ensure' doesn't make it into the release documentation :-) --- doc/man-plink.but | 4 ++-- doc/man-pscp.but | 5 ++--- doc/man-psftp.but | 4 ++-- doc/man-putty.but | 4 ++-- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/doc/man-plink.but b/doc/man-plink.but index 89dadb09..26e65f71 100644 --- a/doc/man-plink.but +++ b/doc/man-plink.but @@ -208,8 +208,8 @@ to override a setting in a saved session.) \dd Disconnect from any SSH server which accepts authentication without ever having asked for any kind of password or signature or token. (You might want to enable this for a server you always expect to challenge -you, for instance to ensure ensure you don't accidentally type your key -file's passphrase into a compromised server spoofing Plink's passphrase +you, for instance to ensure you don't accidentally type your key file's +passphrase into a compromised server spoofing Plink's passphrase prompt.) \dt \cw{\-noshare} diff --git a/doc/man-pscp.but b/doc/man-pscp.but index 8011483d..60ce4f5e 100644 --- a/doc/man-pscp.but +++ b/doc/man-pscp.but @@ -160,9 +160,8 @@ to override a setting in a saved session.) \dd Disconnect from any SSH server which accepts authentication without ever having asked for any kind of password or signature or token. (You might want to enable this for a server you always expect to challenge -you, for instance to ensure ensure you don't accidentally type your key -file's passphrase into a compromised server spoofing PSCP's passphrase -prompt.) +you, for instance to ensure you don't accidentally type your key file's +passphrase into a compromised server spoofing PSCP's passphrase prompt.) \dt \cw{\-hostkey} \e{key} diff --git a/doc/man-psftp.but b/doc/man-psftp.but index 0c47aa0e..52617291 100644 --- a/doc/man-psftp.but +++ b/doc/man-psftp.but @@ -148,8 +148,8 @@ to override a setting in a saved session.) \dd Disconnect from any SSH server which accepts authentication without ever having asked for any kind of password or signature or token. (You might want to enable this for a server you always expect to challenge -you, for instance to ensure ensure you don't accidentally type your key -file's passphrase into a compromised server spoofing PSFTP's passphrase +you, for instance to ensure you don't accidentally type your key file's +passphrase into a compromised server spoofing PSFTP's passphrase prompt.) \dt \cw{\-hostkey} \e{key} diff --git a/doc/man-putty.but b/doc/man-putty.but index 3214a180..858ec0b0 100644 --- a/doc/man-putty.but +++ b/doc/man-putty.but @@ -292,8 +292,8 @@ to override a setting in a saved session.) \dd Disconnect from any SSH server which accepts authentication without ever having asked for any kind of password or signature or token. (You might want to enable this for a server you always expect to challenge -you, for instance to ensure ensure you don't accidentally type your key -file's passphrase into a compromised server spoofing PuTTY's passphrase +you, for instance to ensure you don't accidentally type your key file's +passphrase into a compromised server spoofing PuTTY's passphrase prompt.) \dt \cw{\-hostkey} \e{key} -- cgit v1.2.3 From 7a25599d8452c29bf32a5cd7de5968acb15732cd Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 15 Jul 2021 20:02:55 +0100 Subject: Fix terminal redraw slowdown in presence of true colour. When do_paint breaks up a line of terminal text into contiguous runs of characters to treat the same, one of the criteria it uses is, 'Does this character even need redrawing? (or is it already displayed correctly from the previous redraw?)' When we encounter a character that matches its previous value, we end the previous run of characters, so that we can skip the one we've just encountered. That check was not taking account of the 'truecolour' field of the termchar it was checking. So it would sometimes falsely believe the character to be equivalent to its previously drawn value, even when in fact it was not, and hence insert a run break, anticipating that the previous character needed drawing and the current one did not. This didn't cause a _wrong_ redraw, because there's a separate loop further on which re-checks whether to actually draw things, which didn't make the same error. So the character that loop #1 thought didn't need a redraw, loop #2 knew _did_ need a redraw, and hence, everything did get redrawn. But by the time loop #2 is running, it's too late to change the run boundaries. So everything does get redrawn, but in much smaller chunks than it could have been. The net effect was that if the screen was filled with text displayed in true colour, and you changed it to the _same_ text in a different colour, then the whole terminal would be redrawn in one-character increments instead of the usual behaviour of folding together runs that can be drawn in one go. Thanks to Bradley Smith for debugging this very confusing issue! --- terminal.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/terminal.c b/terminal.c index 1a038cf7..663e0f02 100644 --- a/terminal.c +++ b/terminal.c @@ -6043,7 +6043,9 @@ static void do_paint(Terminal *term) if (!term->ucsdata->dbcs_screenfont && !dirty_line) { if (term->disptext[i]->chars[j].chr == tchar && - (term->disptext[i]->chars[j].attr &~ DATTR_MASK) == tattr) + (term->disptext[i]->chars[j].attr &~ DATTR_MASK)==tattr && + truecolour_equal( + term->disptext[i]->chars[j].truecolour, tc)) break_run = true; else if (!dirty_run && ccount == 1) break_run = true; -- cgit v1.2.3 From 9983ff53d5a3d59d58030be0cd46a30eb5da5abc Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 7 Aug 2021 16:59:05 +0100 Subject: psusan manpage: add a PATH to the UML example. Ian Jackson observes that if PATH is not set in the environment, current versions of bash will pick a default one that has "." as the last directory, which is generally considered a terrible idea: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=991959 Work around this by specifying a more sensible default in our example script, per Ian's suggestion in https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=991960 --- doc/man-psusan.but | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/man-psusan.but b/doc/man-psusan.but index fa986e88..0ecb5ca9 100644 --- a/doc/man-psusan.but +++ b/doc/man-psusan.but @@ -191,6 +191,9 @@ And the setup script \cw{uml-psusan.sh} might look like this: \c # Choose what shell you want to run inside psusan \e iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii \c export SHELL=/bin/bash +\c # Set up a default path +\e iiiiiiiiiiiiiiiiiiiiiii +\c export PATH=/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin \c # And now run psusan over the serial port \e iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii \c exec /home/simon/src/putty/misc/psusan -- cgit v1.2.3 From dfb252d161e9dc7861c19da0dce9e565de321efd Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 14 Aug 2021 08:02:27 +0100 Subject: GPG key rollover. Following the same pattern as the previous one (commit 6c924ba86247), except that this time, I don't have to _set up_ the pattern in the front-end code of presenting the current and previous key details - just change over the actual string literals in putty.h. But the rest is the same: new keys at the top of pgpkeys.but, old ones relegated to the historical appendix, key ids in sign.sh switched over. --- doc/pgpkeys.but | 46 ++++++++++++++++++++++++++++++++++------------ putty.h | 12 ++++++------ sign.sh | 4 ++-- 3 files changed, 42 insertions(+), 20 deletions(-) diff --git a/doc/pgpkeys.but b/doc/pgpkeys.but index 8fab6153..7dc62f89 100644 --- a/doc/pgpkeys.but +++ b/doc/pgpkeys.but @@ -56,25 +56,25 @@ The current issue of those keys are available for download from the PuTTY website, and are also available on PGP keyservers using the key IDs listed below. -\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/master-2018.asc}{\s{Master Key} (2018)} +\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/master-2021.asc}{\s{Master Key} (2021)} -\dd RSA, 4096-bit. Key ID: \cw{76BC7FE4EBFD2D9E}. Fingerprint: -\cw{24E1\_B1C5\_75EA\_3C9F\_F752\_\_A922\_76BC\_7FE4\_EBFD\_2D9E} +\dd RSA, 3072-bit. Key ID: \cw{DD4355EAAC1119DE}. Fingerprint: +\cw{A872\_D42F\_1660\_890F\_0E05\_223E\_DD43\_55EA\_AC11\_19DE} -\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/release-2018.asc}{\s{Release Key} (2018)} +\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/release-2021.asc}{\s{Release Key} (2021)} -\dd RSA, 3072-bit. Key ID: \cw{6289A25F4AE8DA82}. Fingerprint: -\cw{E273\_94AC\_A3F9\_D904\_9522\_\_E054\_6289\_A25F\_4AE8\_DA82} +\dd RSA, 3072-bit. Key ID: \cw{E4F83EA2AA4915EC}. Fingerprint: +\cw{2CF6\_134B\_D3F7\_7A65\_88EB\_D668\_E4F8\_3EA2\_AA49\_15EC} -\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/snapshot-2018.asc}{\s{Snapshot Key} (2018)} +\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/snapshot-2021.asc}{\s{Snapshot Key} (2021)} -\dd RSA, 3072-bit. Key ID: \cw{38BA7229B7588FD1}. Fingerprint: -\cw{C92B\_52E9\_9AB6\_1DDA\_33DB\_\_2B7A\_38BA\_7229\_B758\_8FD1} +\dd RSA, 3072-bit. Key ID: \cw{B43979F89F446CFD}. Fingerprint: +\cw{1FD3\_BCAC\_E532\_FBE0\_6A8C\_09E2\_B439\_79F8\_9F44\_6CFD} -\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/contact-2018.asc}{\s{Secure Contact Key} (2018)} +\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/contact-2021.asc}{\s{Secure Contact Key} (2021)} -\dd RSA, 3072-bit. Key ID: \cw{657D487977F95C98}. Fingerprint: -\cw{A680\_0082\_2998\_6E46\_22CA\_\_0E43\_657D\_4879\_77F9\_5C98} +\dd RSA, 3072-bit. Key ID: \cw{012C59D4211BD62A}. Fingerprint: +\cw{E30F\_1354\_2A04\_BE0E\_56F0\_5801\_012C\_59D4\_211B\_D62A} \H{pgpkeys-security} Security details @@ -169,6 +169,28 @@ generated keys. The details of all previous keys are given here. +\s{Keys generated in the 2018 rollover} + +\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/master-2018.asc}{\s{Master Key} (2018)} + +\dd RSA, 4096-bit. Key ID: \cw{76BC7FE4EBFD2D9E}. Fingerprint: +\cw{24E1\_B1C5\_75EA\_3C9F\_F752\_\_A922\_76BC\_7FE4\_EBFD\_2D9E} + +\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/release-2018.asc}{\s{Release Key} (2018)} + +\dd RSA, 3072-bit. Key ID: \cw{6289A25F4AE8DA82}. Fingerprint: +\cw{E273\_94AC\_A3F9\_D904\_9522\_\_E054\_6289\_A25F\_4AE8\_DA82} + +\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/snapshot-2018.asc}{\s{Snapshot Key} (2018)} + +\dd RSA, 3072-bit. Key ID: \cw{38BA7229B7588FD1}. Fingerprint: +\cw{C92B\_52E9\_9AB6\_1DDA\_33DB\_\_2B7A\_38BA\_7229\_B758\_8FD1} + +\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/contact-2018.asc}{\s{Secure Contact Key} (2018)} + +\dd RSA, 3072-bit. Key ID: \cw{657D487977F95C98}. Fingerprint: +\cw{A680\_0082\_2998\_6E46\_22CA\_\_0E43\_657D\_4879\_77F9\_5C98} + \s{Key generated in 2016} (when we first introduced the Secure Contact Key) \dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/contact-2016.asc}{\s{Secure Contact Key} (2016)} diff --git a/putty.h b/putty.h index 254e370b..326e9cf9 100644 --- a/putty.h +++ b/putty.h @@ -21,14 +21,14 @@ * Fingerprints of the current and previous PGP master keys, to * establish a trust path between an executable and other files. */ -#define PGP_MASTER_KEY_YEAR "2018" -#define PGP_MASTER_KEY_DETAILS "RSA, 4096-bit" -#define PGP_MASTER_KEY_FP \ - "24E1 B1C5 75EA 3C9F F752 A922 76BC 7FE4 EBFD 2D9E" -#define PGP_PREV_MASTER_KEY_YEAR "2015" +#define PGP_MASTER_KEY_YEAR "2021" +#define PGP_MASTER_KEY_DETAILS "RSA, 3072-bit" +#define PGP_MASTER_KEY_FP \ + "A872 D42F 1660 890F 0E05 223E DD43 55EA AC11 19DE" +#define PGP_PREV_MASTER_KEY_YEAR "2018" #define PGP_PREV_MASTER_KEY_DETAILS "RSA, 4096-bit" #define PGP_PREV_MASTER_KEY_FP \ - "440D E3B5 B7A1 CA85 B3CC 1718 AB58 5DC6 0467 6F7C" + "24E1 B1C5 75EA 3C9F F752 A922 76BC 7FE4 EBFD 2D9E" /* * Definitions of three separate indexing schemes for colour palette diff --git a/sign.sh b/sign.sh index f3300322..b40c2d47 100755 --- a/sign.sh +++ b/sign.sh @@ -9,14 +9,14 @@ set -e -keyname=38BA7229B7588FD1 +keyname=B43979F89F446CFD preliminary=false while :; do case "$1" in -r) shift - keyname=6289A25F4AE8DA82 + keyname=E4F83EA2AA4915EC ;; -p) shift -- cgit v1.2.3 From c62b7229c1928f875c77108db50ab356c7f59001 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 14 Aug 2021 10:56:20 +0100 Subject: Bug workaround to delay sending our SSH greeting. Ian Jackson recently tried to use the recipe in the psusan manpage for talking to UML, and found that the connection was not successfully set up, because at some point during startup, UML read the SSH greeting (ok, the bare-ssh-connection greeting) from its input fd and threw it away. So by the time psusan was run by the guest init process, the greeting wasn't there to be read. Ian's report: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=991958 I was also able to reproduce this locally, which makes me wonder why I _didn't_ notice it when I originally wrote that part of the psusan man page. It worked for me before, honest! But now it doesn't. Anyway. The ssh verstring module already has a mode switch to decide whether we ought to send our greeting before or after waiting for the other side's greeting (because that decision varies between client and server, and between SSH-1 and SSH-2). So it's easy to implement an override that forces it to 'wait for the server greeting first'. I've added this as yet another bug workaround flag. But unlike all the others, it can't be autodetected from the server's version string, because, of course, we have to act on it _before_ seeing the server's greeting and version string! So it's a manual-only flag. However, I've mentioned it in the UML section of the psusan man page, since that's the place where I _know_ people are likely to need to use this flag. --- config.c | 38 ++++++++++++++++++++++++++++++++++++++ doc/config.but | 28 +++++++++++++++++++++++++++- doc/man-psusan.but | 16 ++++++++++++---- putty.h | 1 + settings.c | 2 ++ ssh/verstring.c | 8 ++++++++ windows/help.h | 1 + 7 files changed, 89 insertions(+), 5 deletions(-) diff --git a/config.c b/config.c index efb65aa3..4c5da469 100644 --- a/config.c +++ b/config.c @@ -735,6 +735,37 @@ static void sshbug_handler(union control *ctrl, dlgparam *dlg, } } +static void sshbug_handler_manual_only(union control *ctrl, dlgparam *dlg, + void *data, int event) +{ + /* + * This is just like sshbug_handler, except that there's no 'Auto' + * option. Used for bug workaround flags that can't be + * autodetected, and have to be manually enabled if they're to be + * used at all. + */ + Conf *conf = (Conf *)data; + if (event == EVENT_REFRESH) { + int oldconf = conf_get_int(conf, ctrl->listbox.context.i); + dlg_update_start(ctrl, dlg); + dlg_listbox_clear(ctrl, dlg); + dlg_listbox_addwithid(ctrl, dlg, "Off", FORCE_OFF); + dlg_listbox_addwithid(ctrl, dlg, "On", FORCE_ON); + switch (oldconf) { + case FORCE_OFF: dlg_listbox_select(ctrl, dlg, 0); break; + case FORCE_ON: dlg_listbox_select(ctrl, dlg, 1); break; + } + dlg_update_done(ctrl, dlg); + } else if (event == EVENT_SELCHANGE) { + int i = dlg_listbox_index(ctrl, dlg); + if (i < 0) + i = FORCE_OFF; + else + i = dlg_listbox_getid(ctrl, dlg, i); + conf_set_int(conf, ctrl->listbox.context.i, i); + } +} + struct sessionsaver_data { union control *editbox, *listbox, *loadbutton, *savebutton, *delbutton; union control *okbutton, *cancelbutton; @@ -3049,6 +3080,13 @@ void setup_config_box(struct controlbox *b, bool midsession, HELPCTX(ssh_bugs_maxpkt2), sshbug_handler, I(CONF_sshbug_maxpkt2)); + s = ctrl_getset(b, "Connection/SSH/Bugs", "manual", + "Manually enabled workarounds"); + ctrl_droplist(s, "Discards data sent before its greeting", 'd', 20, + HELPCTX(ssh_bugs_dropstart), + sshbug_handler_manual_only, + I(CONF_sshbug_dropstart)); + ctrl_settitle(b, "Connection/SSH/More bugs", "Further workarounds for SSH server bugs"); diff --git a/doc/config.but b/doc/config.but index 77313282..904a04a9 100644 --- a/doc/config.but +++ b/doc/config.but @@ -3206,7 +3206,10 @@ three states: \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. +to try to guess whether or not the server has the bug. (This option is +not available for bugs that \e{cannot} be detected from the server +version, e.g. because they must be acted on before the server version +is known.) \S{config-ssh-bug-ignore2} \q{Chokes on SSH-2 \i{ignore message}s} @@ -3299,6 +3302,29 @@ send an over-sized packet. If this bug is enabled when talking to a correct server, the session will work correctly, but download performance will be less than it could be. +\S{config-ssh-bug-dropstart} \q{Discards data sent before its greeting} + +Just occasionally, an SSH connection can be established over some +channel that will accidentally discard outgoing data very early in the +connection. + +This is not typically seen as a bug in an actual SSH server, but it +can sometimes occur in situations involving a complicated proxy +process. An example is +\W{https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=991958}{Debian +bug #991958}, in which a connection going over the console of a User +Mode Linux kernel can lose outgoing data before the kernel has fully +booted. + +You can work around this problem by manually enabling this bug flag, +which will cause PuTTY to wait to send its initial SSH greeting until +after it sees the greeting from the server. + +Note that this bug flag can never be automatically detected, since +auto-detection relies on the version string in the server's greeting, +and PuTTY has to decide whether to expect this bug \e{before} it sees +the server's greeting. So this is a manual workaround only. + \S{config-ssh-bug-sig} \q{Requires padding on SSH-2 \i{RSA} \i{signatures}} Versions below 3.3 of \i{OpenSSH} require SSH-2 RSA signatures to be diff --git a/doc/man-psusan.but b/doc/man-psusan.but index 0ecb5ca9..4d7638dd 100644 --- a/doc/man-psusan.but +++ b/doc/man-psusan.but @@ -199,10 +199,18 @@ And the setup script \cw{uml-psusan.sh} might look like this: \c exec /home/simon/src/putty/misc/psusan Now set up a PuTTY saved session as in the Docker example above, using -that \cw{linux} command as the local proxy command, and you'll have a -PuTTY session that starts up a clean UML instance when you run it, and -(if you enabled connection sharing) further instances of the same -session will connect to the same instance again. +that \cw{linux} command as the local proxy command. You may also find +that you have to enable the bug workaround that indicates that the +server \q{Discards data sent before its greeting}, because otherwise +PuTTY's outgoing protocol greeting can be accidentally lost during UML +startup. (See +\W{https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=991958}{Debian +bug #991958}.) + +Once you've done that, you'll have a PuTTY session that starts up a +clean UML instance when you run it, and (if you enabled connection +sharing) further instances of the same session will connect to the +same instance again. \S2{psusan-manpage-examples-wsl} Windows Subsystem for Linux diff --git a/putty.h b/putty.h index 326e9cf9..2b544e54 100644 --- a/putty.h +++ b/putty.h @@ -1647,6 +1647,7 @@ NORETURN void cleanup_exit(int); X(INT, NONE, sshbug_oldgex2) \ X(INT, NONE, sshbug_winadj) \ X(INT, NONE, sshbug_chanreq) \ + X(INT, NONE, sshbug_dropstart) \ /* \ * ssh_simple means that we promise never to open any channel \ * other than the main one, which means it can safely use a very \ diff --git a/settings.c b/settings.c index 43412ef9..c0f181c5 100644 --- a/settings.c +++ b/settings.c @@ -769,6 +769,7 @@ void save_open_settings(settings_w *sesskey, Conf *conf) write_setting_i(sesskey, "BugOldGex2", 2-conf_get_int(conf, CONF_sshbug_oldgex2)); write_setting_i(sesskey, "BugWinadj", 2-conf_get_int(conf, CONF_sshbug_winadj)); write_setting_i(sesskey, "BugChanReq", 2-conf_get_int(conf, CONF_sshbug_chanreq)); + write_setting_i(sesskey, "BugDropStart", 2-conf_get_int(conf, CONF_sshbug_dropstart)); write_setting_b(sesskey, "StampUtmp", conf_get_bool(conf, CONF_stamp_utmp)); write_setting_b(sesskey, "LoginShell", conf_get_bool(conf, CONF_login_shell)); write_setting_b(sesskey, "ScrollbarOnLeft", conf_get_bool(conf, CONF_scrollbar_on_left)); @@ -1244,6 +1245,7 @@ void load_open_settings(settings_r *sesskey, Conf *conf) i = gppi_raw(sesskey, "BugOldGex2", 0); conf_set_int(conf, CONF_sshbug_oldgex2, 2-i); i = gppi_raw(sesskey, "BugWinadj", 0); conf_set_int(conf, CONF_sshbug_winadj, 2-i); i = gppi_raw(sesskey, "BugChanReq", 0); conf_set_int(conf, CONF_sshbug_chanreq, 2-i); + i = gppi_raw(sesskey, "BugDropStart", 0); conf_set_int(conf, CONF_sshbug_dropstart, 2-i); conf_set_bool(conf, CONF_ssh_simple, false); gppb(sesskey, "StampUtmp", true, conf, CONF_stamp_utmp); gppb(sesskey, "LoginShell", true, conf, CONF_login_shell); diff --git a/ssh/verstring.c b/ssh/verstring.c index 2de2ac6c..90814bc1 100644 --- a/ssh/verstring.c +++ b/ssh/verstring.c @@ -104,6 +104,14 @@ BinaryPacketProtocol *ssh_verstring_new( */ s->send_early = server_mode || !ssh_version_includes_v1(protoversion); + /* + * Override: we don't send our version string early if the server + * has a bug that will make it discard it. See for example + * https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=991958 + */ + if (conf_get_int(s->conf, CONF_sshbug_dropstart) == FORCE_ON) + s->send_early = false; + s->bpp.vt = &ssh_verstring_vtable; ssh_bpp_common_setup(&s->bpp); return &s->bpp; diff --git a/windows/help.h b/windows/help.h index a1e24c18..c240f133 100644 --- a/windows/help.h +++ b/windows/help.h @@ -162,6 +162,7 @@ #define WINHELP_CTX_ssh_bugs_winadj "config-ssh-bug-winadj" #define WINHELP_CTX_ssh_bugs_chanreq "config-ssh-bug-chanreq" #define WINHELP_CTX_ssh_bugs_oldgex2 "config-ssh-bug-oldgex2" +#define WINHELP_CTX_ssh_bugs_dropstart "config-ssh-bug-dropstart" #define WINHELP_CTX_serial_line "config-serial-line" #define WINHELP_CTX_serial_speed "config-serial-speed" #define WINHELP_CTX_serial_databits "config-serial-databits" -- cgit v1.2.3 From 2cb38da6e92bb9e7032f3ff1fdc6e89ced0c459b Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 16 Aug 2021 22:22:55 +0100 Subject: psusan manpage: suggest setsid in UML example. When UML terminates, it kills its entire process group. The way PuTTY invokes proxy processes, they are part of its process group. So if UML is used directly as the proxy process, it will commit patricide on termination. Wrapping it in 'setsid' is overkill (it doesn't need to be part of a separate _session_, only a separate pgrp), but it's good enough to work around this problem, and give PuTTY the opportunity to shut down cleanly when the UML it's talking to vanishes. --- doc/man-psusan.but | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/doc/man-psusan.but b/doc/man-psusan.but index 4d7638dd..7877b3c8 100644 --- a/doc/man-psusan.but +++ b/doc/man-psusan.but @@ -198,12 +198,22 @@ And the setup script \cw{uml-psusan.sh} might look like this: \e iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii \c exec /home/simon/src/putty/misc/psusan -Now set up a PuTTY saved session as in the Docker example above, using -that \cw{linux} command as the local proxy command. You may also find -that you have to enable the bug workaround that indicates that the -server \q{Discards data sent before its greeting}, because otherwise -PuTTY's outgoing protocol greeting can be accidentally lost during UML -startup. (See +Now set up a PuTTY saved session as in the Docker example above. +Basically you'll want to use the above \cw{linux} command as the local +proxy command. However, it's worth wrapping it in \c{setsid}(\e{1}), +because when UML terminates, it kills its entire process group. So +it's better that PuTTY should not be part of that group, and should +have the opportunity to shut down cleanly by itself. So probably you +end up setting the proxy command to be something more like: + +\c setsid linux mem=512M rootfstype=hostfs rootflags=/ rw \ +\c con=fd:2,fd:2 ssl0=fd:0,fd:1 init=/some/path/to/uml-psusan.sh +\e iiiiiiiiiiiiiiiiiiiiiiiiiii + +You may also find that you have to enable the bug workaround that +indicates that the server \q{Discards data sent before its greeting}, +because otherwise PuTTY's outgoing protocol greeting can be +accidentally lost during UML startup. (See \W{https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=991958}{Debian bug #991958}.) -- cgit v1.2.3 From 22fab7837660fe2cf6ee08b10bd6696cc2653024 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 22 Aug 2021 12:19:01 +0100 Subject: Tidy up formatting of manpage cross-references. In most Halibut man pages I write, I have a standard convention of referring to another man page by wrapping the page name in \cw and the section number in \e, leaving the parentheses un-marked-up. Apparently I forgot in this particular collection. --- doc/man-pageant.but | 8 ++++---- doc/man-plink.but | 6 +++--- doc/man-pscp.but | 6 +++--- doc/man-psftp.but | 6 +++--- doc/man-psocks.but | 8 ++++---- doc/man-psusan.but | 2 +- doc/man-pterm.but | 2 +- doc/man-putty.but | 2 +- doc/man-puttytel.but | 2 +- 9 files changed, 21 insertions(+), 21 deletions(-) diff --git a/doc/man-pageant.but b/doc/man-pageant.but index 358f3a08..3f407a47 100644 --- a/doc/man-pageant.but +++ b/doc/man-pageant.but @@ -41,7 +41,7 @@ extract their public half. The agent protocol used by \c{pageant} is compatible with the PuTTY tools and also with other implementations such as OpenSSH's SSH client -and \e{ssh-agent(1)}. Some \c{pageant} features are implemented with +and \cw{ssh-agent}(\e{1}). Some \c{pageant} features are implemented with protocol extensions, so will only work if \c{pageant} is on both ends. To run \c{pageant} as an agent, you must provide an option to tell it @@ -317,15 +317,15 @@ by the SSH agent protocol. \dt \cw{--askpass} \e{prompt} -\dd With this option, \c{pageant} acts as an \e{ssh-askpass(1)} +\dd With this option, \c{pageant} acts as an \cw{ssh-askpass}(\e{1}) replacement, rather than performing any SSH agent functionality. This may be useful if you prefer Pageant's GUI prompt style, which minimises information leakage about your passphrase length in its -visual feedback, compared to other \e{ssh-askpass(1)} implementations. +visual feedback, compared to other \cw{ssh-askpass}(\e{1}) implementations. \lcont{ -\c{pageant --askpass} implements the standard \e{ssh-askpass(1)} +\c{pageant --askpass} implements the standard \cw{ssh-askpass}(\e{1}) interface: it can be passed a prompt to display (as a single argument) and, if successful, prints the passphrase on standard output and returns a zero exit status. Typically you would use the environment diff --git a/doc/man-plink.but b/doc/man-plink.but index 26e65f71..ea555abf 100644 --- a/doc/man-plink.but +++ b/doc/man-plink.but @@ -59,9 +59,9 @@ to aid in verifying new files released by the PuTTY team. \dt \cw{-ssh-connection} \dd Force use of the \q{bare \cw{ssh-connection}} protocol. This is -only likely to be useful when connecting to a \e{psusan(1)} server, -most likely with an absolute path to a Unix-domain socket in place -of \e{host}. +only likely to be useful when connecting to a \cw{psusan}(\e{1}) +server, most likely with an absolute path to a Unix-domain socket in +place of \e{host}. \dt \cw{\-proxycmd} \e{command} diff --git a/doc/man-pscp.but b/doc/man-pscp.but index 60ce4f5e..402b9eef 100644 --- a/doc/man-pscp.but +++ b/doc/man-pscp.but @@ -118,9 +118,9 @@ commands such as \q{\c{w}}). \dt \cw{-ssh-connection} \dd Force use of the \q{bare \cw{ssh-connection}} protocol. This is -only likely to be useful when connecting to a \e{psusan(1)} server, -most likely with an absolute path to a Unix-domain socket in place -of \e{host}. +only likely to be useful when connecting to a \cw{psusan}(\e{1}) +server, most likely with an absolute path to a Unix-domain socket in +place of \e{host}. \dt \cw{-ssh} diff --git a/doc/man-psftp.but b/doc/man-psftp.but index 52617291..4e92e48b 100644 --- a/doc/man-psftp.but +++ b/doc/man-psftp.but @@ -106,9 +106,9 @@ commands such as \q{\c{w}}). \dt \cw{-ssh-connection} \dd Force use of the \q{bare \cw{ssh-connection}} protocol. This is -only likely to be useful when connecting to a \e{psusan(1)} server, -most likely with an absolute path to a Unix-domain socket in place -of \e{host}. +only likely to be useful when connecting to a \cw{psusan}(\e{1}) +server, most likely with an absolute path to a Unix-domain socket in +place of \e{host}. \dt \cw{-ssh} diff --git a/doc/man-psocks.but b/doc/man-psocks.but index a9792e44..eb075a6e 100644 --- a/doc/man-psocks.but +++ b/doc/man-psocks.but @@ -18,8 +18,8 @@ IPv4 and IPv6 connections. It does not support requiring authentication of its clients. \cw{psocks} can be used together with an SSH client such as -\cw{putty(1)} to implement a reverse dynamic SSH tunnel. It can also -be used for network protocol debugging, as it can record all the +\cw{putty}(\e{1}) to implement a reverse dynamic SSH tunnel. It can +also be used for network protocol debugging, as it can record all the traffic passing through it in various ways. By default, \cw{psocks} listens to connections from localhost only, @@ -84,8 +84,8 @@ have the connection's traffic piped into it, similar to \cw{-f}. \S{psocks-manpage-examples} EXAMPLES -In combination with the \e{plink(1)} SSH client, to set up a reverse -dynamic SSH tunnel, in which the remote listening port 1080 on +In combination with the \cw{plink}(\e{1}) SSH client, to set up a +reverse dynamic SSH tunnel, in which the remote listening port 1080 on remote host \cw{myhost} acts as a SOCKS server giving access to your local network: diff --git a/doc/man-psusan.but b/doc/man-psusan.but index 7877b3c8..a9c8baab 100644 --- a/doc/man-psusan.but +++ b/doc/man-psusan.but @@ -200,7 +200,7 @@ And the setup script \cw{uml-psusan.sh} might look like this: Now set up a PuTTY saved session as in the Docker example above. Basically you'll want to use the above \cw{linux} command as the local -proxy command. However, it's worth wrapping it in \c{setsid}(\e{1}), +proxy command. However, it's worth wrapping it in \cw{setsid}(\e{1}), because when UML terminates, it kills its entire process group. So it's better that PuTTY should not be part of that group, and should have the opportunity to shut down cleanly by itself. So probably you diff --git a/doc/man-pterm.but b/doc/man-pterm.but index fec97f11..d3d1d96a 100644 --- a/doc/man-pterm.but +++ b/doc/man-pterm.but @@ -76,7 +76,7 @@ will be ignored unless the \cw{BoldAsColour} resource is set to 0 or 2. \dt \cw{\-geometry} \e{geometry} \dd Specify the size of the terminal, in rows and columns of text. See -\e{X(7)} for more information on the syntax of geometry +\cw{X}(\e{7}) for more information on the syntax of geometry specifications. \dt \cw{\-sl} \e{lines} diff --git a/doc/man-putty.but b/doc/man-putty.but index 858ec0b0..a85b4505 100644 --- a/doc/man-putty.but +++ b/doc/man-putty.but @@ -55,7 +55,7 @@ will be ignored unless the \cw{BoldAsColour} resource is set to 0 or 2. \dt \cw{\-geometry} \e{geometry} \dd Specify the size of the terminal, in rows and columns of text. -See \e{X(7)} for more information on the syntax of geometry +See \cw{X}(\e{7}) for more information on the syntax of geometry specifications. \dt \cw{\-sl} \e{lines} diff --git a/doc/man-puttytel.but b/doc/man-puttytel.but index bf852ddb..075eeea7 100644 --- a/doc/man-puttytel.but +++ b/doc/man-puttytel.but @@ -56,7 +56,7 @@ will be ignored unless the \cw{BoldAsColour} resource is set to 0 or 2. \dt \cw{\-geometry} \e{geometry} \dd Specify the size of the terminal, in rows and columns of text. See -\e{X(7)} for more information on the syntax of geometry +\cw{X}(\e{7}) for more information on the syntax of geometry specifications. \dt \cw{\-sl} \e{lines} -- cgit v1.2.3 From 59409d0947ec6d0dc11b4bda8296f68ff088f0f3 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 27 Aug 2021 17:43:40 +0100 Subject: Make mp_unsafe_mod_integer not be unsafe. I've moved it from mpunsafe.c into the main mpint.c, and renamed it mp_mod_known_integer, because now it manages to avoid leaking information about the mp_int you give it. It can still potentially leak information about the small _modulus_ integer - hence the word 'known' in the new function name. This won't be a problem in any existing use of the function, because it's used during prime generation to check divisibility by all the small primes, and optionally also check for residue 1 mod the RSA public exponent. But all those values are well known and not secret. This removes one source of side-channel leakage from prime generation. --- crypto/mpint.c | 81 +++++++++++++++++++++++++++++++++++++++++++++++++ keygen/mpunsafe.c | 10 ------ keygen/mpunsafe.h | 7 ----- keygen/primecandidate.c | 6 ++-- mpint.h | 6 ++++ 5 files changed, 90 insertions(+), 20 deletions(-) diff --git a/crypto/mpint.c b/crypto/mpint.c index fca7530f..f015bd09 100644 --- a/crypto/mpint.c +++ b/crypto/mpint.c @@ -2263,6 +2263,87 @@ mp_int *mp_mod(mp_int *n, mp_int *d) return r; } +uint32_t mp_mod_known_integer(mp_int *x, uint32_t m) +{ + uint64_t reciprocal = ((uint64_t)1 << 48) / m; + uint64_t accumulator = 0; + for (size_t i = mp_max_bytes(x); i-- > 0 ;) { + accumulator = 0x100 * accumulator + mp_get_byte(x, i); + /* + * Let A be the value in 'accumulator' at this point, and let + * R be the value it will have after we subtract quot*m below. + * + * Lemma 1: if A < 2^48, then R < 2m. + * + * Proof: + * + * By construction, we have 2^48/m - 1 < reciprocal <= 2^48/m. + * Multiplying that by the accumulator gives + * + * A/m * 2^48 - A < unshifted_quot <= A/m * 2^48 + * i.e. 0 <= (A/m * 2^48) - unshifted_quot < A + * i.e. 0 <= A/m - unshifted_quot/2^48 < A/2^48 + * + * So when we shift this quotient right by 48 bits, i.e. take + * the floor of (unshifted_quot/2^48), the value we take the + * floor of is at most A/2^48 less than the true rational + * value A/m that we _wanted_ to take the floor of. + * + * Provided A < 2^48, this is less than 1. So the quotient + * 'quot' that we've just produced is either the true quotient + * floor(A/m), or one less than it. Hence, the output value R + * is less than 2m. [] + * + * Lemma 2: if A < 2^16 m, then the multiplication of + * accumulator*reciprocal does not overflow. + * + * Proof: as above, we have reciprocal <= 2^48/m. Multiplying + * by A gives unshifted_quot <= 2^48 * A / m < 2^48 * 2^16 = + * 2^64. [] + */ + uint64_t unshifted_quot = accumulator * reciprocal; + uint64_t quot = unshifted_quot >> 48; + accumulator -= quot * m; + } + + /* + * Theorem 1: accumulator < 2m at the end of every iteration of + * this loop. + * + * Proof: induction on the above loop. + * + * Base case: at the start of the first loop iteration, the + * accumulator is 0, which is certainly < 2m. + * + * Inductive step: in each loop iteration, we take a value at most + * 2m-1, multiply it by 2^8, and add another byte less than 2^8 to + * generate the input value A to the reduction process above. So + * we have A < 2m * 2^8 - 1. We know m < 2^32 (because it was + * passed in as a uint32_t), so A < 2^41, which is enough to allow + * us to apply Lemma 1, showing that the value of 'accumulator' at + * the end of the loop is still < 2m. [] + * + * Corollary: we need at most one final subtraction of m to + * produce the canonical residue of x mod m, i.e. in the range + * [0,m). + * + * Theorem 2: no multiplication in the inner loop overflows. + * + * Proof: in Theorem 1 we established A < 2m * 2^8 - 1 in every + * iteration. That is less than m * 2^16, so Lemma 2 applies. + * + * The other multiplication, of quot * m, cannot overflow because + * quot is at most A/m, so quot*m <= A < 2^64. [] + */ + + uint32_t result = accumulator; + uint32_t reduced = result - m; + uint32_t select = -(reduced >> 31); + result = reduced ^ ((result ^ reduced) & select); + assert(result < m); + return result; +} + mp_int *mp_nthroot(mp_int *y, unsigned n, mp_int *remainder_out) { /* diff --git a/keygen/mpunsafe.c b/keygen/mpunsafe.c index f33532c4..6265d40f 100644 --- a/keygen/mpunsafe.c +++ b/keygen/mpunsafe.c @@ -45,13 +45,3 @@ mp_int *mp_unsafe_copy(mp_int *x) mp_copy_into(copy, x); return copy; } - -uint32_t mp_unsafe_mod_integer(mp_int *x, uint32_t modulus) -{ - uint64_t accumulator = 0; - for (size_t i = mp_max_bytes(x); i-- > 0 ;) { - accumulator = 0x100 * accumulator + mp_get_byte(x, i); - accumulator %= modulus; - } - return accumulator; -} diff --git a/keygen/mpunsafe.h b/keygen/mpunsafe.h index 0b6ba3bd..07215372 100644 --- a/keygen/mpunsafe.h +++ b/keygen/mpunsafe.h @@ -36,11 +36,4 @@ mp_int *mp_unsafe_shrink(mp_int *m); mp_int *mp_unsafe_copy(mp_int *m); -/* - * Compute the residue of x mod m. This is implemented in the most - * obvious way using the C % operator, which won't be constant-time on - * many C implementations. - */ -uint32_t mp_unsafe_mod_integer(mp_int *x, uint32_t m); - #endif /* PUTTY_MPINT_UNSAFE_H */ diff --git a/keygen/primecandidate.c b/keygen/primecandidate.c index cf55919e..02c0259d 100644 --- a/keygen/primecandidate.c +++ b/keygen/primecandidate.c @@ -341,8 +341,8 @@ void pcs_ready(PrimeCandidateSource *s) int64_t mod = s->avoids[i].mod, res = s->avoids[i].res; if (mod != last_mod) { last_mod = mod; - addend_m = mp_unsafe_mod_integer(s->addend, mod); - factor_m = mp_unsafe_mod_integer(s->factor, mod); + addend_m = mp_mod_known_integer(s->addend, mod); + factor_m = mp_mod_known_integer(s->factor, mod); } if (factor_m == 0) { @@ -385,7 +385,7 @@ mp_int *pcs_generate(PrimeCandidateSource *s) if (mod != last_mod) { last_mod = mod; - x_res = mp_unsafe_mod_integer(x, mod); + x_res = mp_mod_known_integer(x, mod); } if (x_res == avoid_res) { diff --git a/mpint.h b/mpint.h index 5611a007..51322aa6 100644 --- a/mpint.h +++ b/mpint.h @@ -257,6 +257,12 @@ void mp_divmod_into(mp_int *n, mp_int *d, mp_int *q, mp_int *r); mp_int *mp_div(mp_int *n, mp_int *d); mp_int *mp_mod(mp_int *x, mp_int *modulus); +/* + * Compute the residue of x mod m, where m is a small integer. x is + * kept secret, but m is not. + */ +uint32_t mp_mod_known_integer(mp_int *x, uint32_t m); + /* * Integer nth root. mp_nthroot returns the largest integer x such * that x^n <= y, and if 'remainder' is non-NULL then it fills it with -- cgit v1.2.3 From 23431f8ff454aef29f25627bae2bc08c6dd69af2 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 27 Aug 2021 17:43:40 +0100 Subject: Add some tests of Miller-Rabin to cryptsuite. I'm about to rewrite the Miller-Rabin testing code, so let's start by introducing a test suite that the old version passes, and then I can make sure the new one does too. --- keygen/millerrabin.c | 29 +++++++++++++++++------ sshkeygen.h | 8 +++++++ test/cryptsuite.py | 67 ++++++++++++++++++++++++++++++++++++++++++++++++++++ test/testcrypt.py | 2 +- testcrypt.c | 12 ++++++++++ testcrypt.h | 2 ++ 6 files changed, 112 insertions(+), 8 deletions(-) diff --git a/keygen/millerrabin.c b/keygen/millerrabin.c index 3358bc51..19ca1bd3 100644 --- a/keygen/millerrabin.c +++ b/keygen/millerrabin.c @@ -135,17 +135,19 @@ void miller_rabin_free(MillerRabin *mr) sfree(mr); } -struct mr_result { - bool passed; - bool potential_primitive_root; -}; - -static struct mr_result miller_rabin_test_inner(MillerRabin *mr, mp_int *w) +/* + * The main internal function that implements a single M-R test. + * + * Expects the witness integer to be in Montgomery representation. + * (Since in live use witnesses are invented at random, this imposes + * no extra cost on the callers, and saves effort in here.) + */ +static struct mr_result miller_rabin_test_inner(MillerRabin *mr, mp_int *mw) { /* * Compute w^q mod p. */ - mp_int *wqp = monty_pow(mr->mc, w, mr->q); + mp_int *wqp = monty_pow(mr->mc, mw, mr->q); /* * See if this is 1, or if it is -1, or if it becomes -1 @@ -175,6 +177,19 @@ static struct mr_result miller_rabin_test_inner(MillerRabin *mr, mp_int *w) return result; } +/* + * Wrapper on miller_rabin_test_inner for the convenience of + * testcrypt. Expects the witness integer to be literal, so we + * monty_import it before running the real test. + */ +struct mr_result miller_rabin_test(MillerRabin *mr, mp_int *w) +{ + mp_int *mw = monty_import(mr->mc, w); + struct mr_result result = miller_rabin_test_inner(mr, mw); + mp_free(mw); + return result; +} + bool miller_rabin_test_random(MillerRabin *mr) { mp_int *mw = mp_random_in_range(mr->two, mr->pm1); diff --git a/sshkeygen.h b/sshkeygen.h index dc0024b9..fae6fa83 100644 --- a/sshkeygen.h +++ b/sshkeygen.h @@ -94,6 +94,14 @@ typedef struct MillerRabin MillerRabin; MillerRabin *miller_rabin_new(mp_int *p); void miller_rabin_free(MillerRabin *mr); +/* Perform a single Miller-Rabin test, using a specified witness value. + * Used in the test suite. */ +struct mr_result { + bool passed; + bool potential_primitive_root; +}; +struct mr_result miller_rabin_test(MillerRabin *mr, mp_int *w); + /* Perform a single Miller-Rabin test, using a random witness value. */ bool miller_rabin_test_random(MillerRabin *mr); diff --git a/test/cryptsuite.py b/test/cryptsuite.py index c4df264f..2993dbd4 100755 --- a/test/cryptsuite.py +++ b/test/cryptsuite.py @@ -1188,6 +1188,73 @@ class keygen(MyTestBase): self.assertEqual(pockle_add_prime(po, 1, [2], 1), 'POCKLE_PRIME_SMALLER_THAN_2') + def testMillerRabin(self): + # A prime congruent to 3 mod 4, so M-R can only do one + # iteration: either a^{(p-1)/2} == +1, or -1. Either counts as + # a pass; the latter also means the number is potentially a + # primitive root. + n = 0xe76e6aaa42b5d7423aa4da5613eb21c3 + mr = miller_rabin_new(n) + self.assertEqual(miller_rabin_test(mr, 2), "passed+ppr") + self.assertEqual(miller_rabin_test(mr, 4), "passed") + + # The 'potential primitive root' test only means that M-R + # didn't _rule out_ the number being a primitive root, by + # finding that any of the powers _it tested_ less than n-1 + # came out to be 1. In this case, 2 really is a primitive + # root, but since 13 | n-1, the 13th powers mod n form a + # multiplicative subgroup. So 2^13 is not a primitive root, + # and yet, M-R can't tell the difference, because it only + # tried the exponent (n-1)/2, not the actual counterexample + # (n-1)/13. + self.assertEqual(miller_rabin_test(mr, 2**13), "passed+ppr") + + # A prime congruent to 1 mod a reasonably large power of 2, so + # M-R has lots of scope to have different things happen. 3 is + # a primitive root, so we expect that 3, 3^2, 3^4, ..., 3^256 + # should all pass for different reasons, with only the first + # of them returning passed+ppr. + n = 0xb1b65ebe489ff0ab4597bb67c3d22d01 + mr = miller_rabin_new(n) + w = 3 + self.assertEqual(miller_rabin_test(mr, w), "passed+ppr") + for i in range(1, 10): + w = w * w % n + self.assertEqual(miller_rabin_test(mr, w), "passed") + + # A prime with an _absurdly_ large power-of-2 factor in its + # multiplicative group. + n = 0x600000000000000000000000000000000000000000000001 + mr = miller_rabin_new(n) + w = 10 + self.assertEqual(miller_rabin_test(mr, w), "passed+ppr") + for i in range(1, 200): + w = w * w % n + self.assertEqual(miller_rabin_test(mr, w), "passed") + + # A blatantly composite number. But we still expect to see a + # pass if we give the witness 1 (which will give a maximal + # trailing string of 1s), or -1 (which will give -1 when + # raised to the maximal odd factor of n-1, or indeed any other + # odd power). + n = 0x1010101010101010101010101010101 + mr = miller_rabin_new(n) + self.assertEqual(miller_rabin_test(mr, 1), "passed") + self.assertEqual(miller_rabin_test(mr, n-1), "passed") + self.assertEqual(miller_rabin_test(mr, 2), "failed") + + # A Carmichael number, as a proper test that M-R detects + # things the Fermat test would not. + # + # (Its prime factorisation is 26823115100268314289505807 * + # 53646230200536628579011613 * 80469345300804942868517419, + # which is enough to re-check its Carmichaelness.) + n = 0xffffffffffffffffcf8032f3e044b4a8b1b1bf0b526538eae953d90f44d65511 + mr = miller_rabin_new(n) + self.assertEqual(miller_rabin_test(mr, 16), "passed") + assert(pow(2, n-1, n) == 1) # Fermat test would pass, but ... + self.assertEqual(miller_rabin_test(mr, 2), "failed") # ... this fails + class crypt(MyTestBase): def testSSH1Fingerprint(self): # Example key and reference fingerprint value generated by diff --git a/test/testcrypt.py b/test/testcrypt.py index 686302c8..66611ed3 100644 --- a/test/testcrypt.py +++ b/test/testcrypt.py @@ -235,7 +235,7 @@ def make_retval(rettype, word, unpack_strings): elif rettype == "boolean": assert word == b"true" or word == b"false" return word == b"true" - elif rettype == "pocklestatus": + elif rettype in {"pocklestatus", "mr_result"}: return word.decode("ASCII") raise TypeError("Can't deal with return value {!r} of type {!r}" .format(word, rettype)) diff --git a/testcrypt.c b/testcrypt.c index c5c66b4b..1b875c2b 100644 --- a/testcrypt.c +++ b/testcrypt.c @@ -94,6 +94,7 @@ uint64_t prng_reseed_time_ms(void) X(pcs, PrimeCandidateSource *, pcs_free(v)) \ X(pgc, PrimeGenerationContext *, primegen_free_context(v)) \ X(pockle, Pockle *, pockle_free(v)) \ + X(millerrabin, MillerRabin *, miller_rabin_free(v)) \ /* end of list */ typedef struct Value Value; @@ -707,6 +708,16 @@ static void return_pocklestatus(strbuf *out, PockleStatus status) } } +static void return_mr_result(strbuf *out, struct mr_result result) +{ + if (!result.passed) + strbuf_catf(out, "failed\n"); + else if (!result.potential_primitive_root) + strbuf_catf(out, "passed\n"); + else + strbuf_catf(out, "passed+ppr\n"); +} + static void return_val_string_asciz_const(strbuf *out, const char *s) { strbuf *sb = strbuf_new(); @@ -1370,6 +1381,7 @@ typedef key_components *TD_keycomponents; typedef const PrimeGenerationPolicy *TD_primegenpolicy; typedef struct mpint_list TD_mpint_list; typedef PockleStatus TD_pocklestatus; +typedef struct mr_result TD_mr_result; typedef Argon2Flavour TD_argon2flavour; typedef FingerprintType TD_fptype; diff --git a/testcrypt.h b/testcrypt.h index 2e6e993b..7e0ef3cf 100644 --- a/testcrypt.h +++ b/testcrypt.h @@ -297,6 +297,8 @@ FUNC2(void, pockle_release, val_pockle, uint) FUNC2(pocklestatus, pockle_add_small_prime, val_pockle, val_mpint) FUNC4(pocklestatus, pockle_add_prime, val_pockle, val_mpint, mpint_list, val_mpint) FUNC2(val_string, pockle_mpu, val_pockle, val_mpint) +FUNC1(val_millerrabin, miller_rabin_new, val_mpint) +FUNC2(mr_result, miller_rabin_test, val_millerrabin, val_mpint) /* * Miscellaneous. -- cgit v1.2.3 From 6520574e584351f6c3af1fabcb6951db8ea98066 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 27 Aug 2021 17:46:25 +0100 Subject: Side-channel-safe rewrite of the Miller-Rabin test. Thanks to Mark Wooding for explaining the method of doing this. At first glance it seemed _obviously_ impossible to run an algorithm that needs an iteration per factor of 2 in p-1, without a timing leak giving away the number of factors of 2 in p-1. But it's not, because you can do the M-R checks interleaved with each step of your whole modular exponentiation, and they're cheap enough that you can do them in _every_ step, even the ones where the exponent is too small for M-R to be interested in yet, and then do bitwise masking to exclude the spurious results from the final output. --- keygen/millerrabin.c | 131 +++++++++++++++++++++++++++++++++++++-------------- sshkeygen.h | 4 +- test/cryptsuite.py | 19 ++++++++ 3 files changed, 116 insertions(+), 38 deletions(-) diff --git a/keygen/millerrabin.c b/keygen/millerrabin.c index 19ca1bd3..24ee6193 100644 --- a/keygen/millerrabin.c +++ b/keygen/millerrabin.c @@ -95,10 +95,8 @@ struct MillerRabin { MontyContext *mc; - size_t k; - mp_int *q; - - mp_int *two, *pm1, *m_pm1; + mp_int *pm1, *m_pm1; + mp_int *lowbit, *two; }; MillerRabin *miller_rabin_new(mp_int *p) @@ -108,15 +106,18 @@ MillerRabin *miller_rabin_new(mp_int *p) assert(mp_hs_integer(p, 2)); assert(mp_get_bit(p, 0) == 1); - mr->k = 1; - while (!mp_get_bit(p, mr->k)) - mr->k++; - mr->q = mp_rshift_safe(p, mr->k); + mr->pm1 = mp_copy(p); + mp_sub_integer_into(mr->pm1, mr->pm1, 1); - mr->two = mp_from_integer(2); + /* + * Standard bit-twiddling trick for isolating the lowest set bit + * of a number: x & (-x) + */ + mr->lowbit = mp_new(mp_max_bits(mr->pm1)); + mp_sub_into(mr->lowbit, mr->lowbit, mr->pm1); + mp_and_into(mr->lowbit, mr->lowbit, mr->pm1); - mr->pm1 = mp_unsafe_copy(p); - mp_sub_integer_into(mr->pm1, mr->pm1, 1); + mr->two = mp_from_integer(2); mr->mc = monty_new(p); mr->m_pm1 = monty_import(mr->mc, mr->pm1); @@ -126,10 +127,10 @@ MillerRabin *miller_rabin_new(mp_int *p) void miller_rabin_free(MillerRabin *mr) { - mp_free(mr->q); - mp_free(mr->two); mp_free(mr->pm1); mp_free(mr->m_pm1); + mp_free(mr->lowbit); + mp_free(mr->two); monty_free(mr->mc); smemclr(mr, sizeof(*mr)); sfree(mr); @@ -144,35 +145,93 @@ void miller_rabin_free(MillerRabin *mr) */ static struct mr_result miller_rabin_test_inner(MillerRabin *mr, mp_int *mw) { - /* - * Compute w^q mod p. - */ - mp_int *wqp = monty_pow(mr->mc, mw, mr->q); + mp_int *acc = mp_copy(monty_identity(mr->mc)); + mp_int *spare = mp_new(mp_max_bits(mr->pm1)); + size_t bit = mp_max_bits(mr->pm1); /* - * See if this is 1, or if it is -1, or if it becomes -1 - * when squared at most k-1 times. + * The obvious approach to Miller-Rabin would be to start by + * calling monty_pow to raise w to the power q, and then square it + * k times ourselves. But that introduces a timing leak that gives + * away the value of k, i.e., how many factors of 2 there are in + * p-1. + * + * Instead, we don't call monty_pow at all. We do a modular + * exponentiation ourselves to compute w^((p-1)/2), using the + * technique that works from the top bit of the exponent + * downwards. That is, in each iteration we compute + * w^floor(exponent/2^i) for i one less than the previous + * iteration, by squaring the value we previously had and then + * optionally multiplying in w if the next exponent bit is 1. + * + * At the end of that process, once i <= k, the division + * (exponent/2^i) yields an integer, so the values we're computing + * are not just w^(floor of that), but w^(exactly that). In other + * words, the last k intermediate values of this modexp are + * precisely the values M-R wants to check against +1 or -1. + * + * So we interleave those checks with the modexp loop itself, and + * to avoid a timing leak, we check _every_ intermediate result + * against (the Montgomery representations of) both +1 and -1. And + * then we do bitwise masking to arrange that only the sensible + * ones of those checks find their way into our final answer. */ + + unsigned active = 0; + struct mr_result result; - result.passed = false; - result.potential_primitive_root = false; - - if (mp_cmp_eq(wqp, monty_identity(mr->mc))) { - result.passed = true; - } else { - for (size_t i = 0; i < mr->k; i++) { - if (mp_cmp_eq(wqp, mr->m_pm1)) { - result.passed = true; - result.potential_primitive_root = (i == mr->k - 1); - break; - } - if (i == mr->k - 1) - break; - monty_mul_into(mr->mc, wqp, wqp, wqp); - } + result.passed = result.potential_primitive_root = 0; + + while (bit-- > 1) { + /* + * In this iteration, we're computing w^(2e) or w^(2e+1), + * where we have w^e from the previous iteration. So we square + * the value we had already, and then optionally multiply in + * another copy of w depending on the next bit of the exponent. + */ + monty_mul_into(mr->mc, acc, acc, acc); + monty_mul_into(mr->mc, spare, acc, mw); + mp_select_into(acc, acc, spare, mp_get_bit(mr->pm1, bit)); + + /* + * mr->lowbit is a number with only one bit set, corresponding + * to the lowest set bit in p-1. So when that's the bit of the + * exponent we've just processed, we'll detect it by setting + * first_iter to true. That's our indication that we're now + * generating intermediate results useful to M-R, so we also + * set 'active', which stays set from then on. + */ + unsigned first_iter = mp_get_bit(mr->lowbit, bit); + active |= first_iter; + + /* + * Check the intermediate result against both +1 and -1. + */ + unsigned is_plus_1 = mp_cmp_eq(acc, monty_identity(mr->mc)); + unsigned is_minus_1 = mp_cmp_eq(acc, mr->m_pm1); + + /* + * M-R must report success iff either: the first of the useful + * intermediate results (which is w^q) is 1, or _any_ of them + * (from w^q all the way up to w^((p-1)/2)) is -1. + * + * So we want to pass the test if is_plus_1 is set on the + * first iteration, or if is_minus_1 is set on any iteration. + */ + result.passed |= (first_iter & is_plus_1); + result.passed |= (active & is_minus_1); + + /* + * In the final iteration, is_minus_1 is also used to set the + * 'potential primitive root' flag, because we haven't found + * any exponent smaller than p-1 for which w^(that) == 1. + */ + if (bit == 1) + result.potential_primitive_root = is_minus_1; } - mp_free(wqp); + mp_free(acc); + mp_free(spare); return result; } diff --git a/sshkeygen.h b/sshkeygen.h index fae6fa83..60b2e836 100644 --- a/sshkeygen.h +++ b/sshkeygen.h @@ -97,8 +97,8 @@ void miller_rabin_free(MillerRabin *mr); /* Perform a single Miller-Rabin test, using a specified witness value. * Used in the test suite. */ struct mr_result { - bool passed; - bool potential_primitive_root; + unsigned passed; + unsigned potential_primitive_root; }; struct mr_result miller_rabin_test(MillerRabin *mr, mp_int *w); diff --git a/test/cryptsuite.py b/test/cryptsuite.py index 2993dbd4..2ab95481 100755 --- a/test/cryptsuite.py +++ b/test/cryptsuite.py @@ -1255,6 +1255,25 @@ class keygen(MyTestBase): assert(pow(2, n-1, n) == 1) # Fermat test would pass, but ... self.assertEqual(miller_rabin_test(mr, 2), "failed") # ... this fails + # A white-box test for the side-channel-safe M-R + # implementation, which has to check a^e against +-1 for every + # exponent e of the form floor((n-1) / power of 2), so as to + # avoid giving away exactly how many of the trailing values of + # that sequence are significant to the test. + # + # When the power of 2 is large enough that the division was + # not exact, the results of these comparisons are _not_ + # significant to the test, and we're required to ignore them! + # + # This pair of values has the property that none of the values + # legitimately computed by M-R is either +1 _or_ -1, but if + # you shift n-1 right by one too many bits (losing the lowest + # set bit of 0x6d00 to get 0x36), then _that_ power of the + # witness integer is -1. This should not cause a spurious pass. + n = 0x6d01 + mr = miller_rabin_new(n) + self.assertEqual(miller_rabin_test(mr, 0x251), "failed") + class crypt(MyTestBase): def testSSH1Fingerprint(self): # Example key and reference fingerprint value generated by -- cgit v1.2.3 From 3bb12dff3b4e4e691fd413a6de642b339f99a072 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 27 Aug 2021 17:46:25 +0100 Subject: Make pcs_set_oneshot even more one-shot. Previously, it would generate a prime candidate, test it, and abort if that candidate failed to be prime. Now, it's even willing to fail _before_ generating a prime candidate, if the first attempt to even do that is unsuccessful. This doesn't affect the existing use case of pcs_set_oneshot, which is during generation of a safe prime (as implemented by test/primegen.py --safe), where you want to make a PrimeCandidateSource that can only return 2p+1 for your existing prime p, and then abort if that fails the next step of testing. In that situation, the PrimeCandidateSource will never fail to generate its first output anyway. But these changed semantics will become useful in another use I'm about to find for one-shot mode. --- keygen/primecandidate.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/keygen/primecandidate.c b/keygen/primecandidate.c index 02c0259d..fca2b297 100644 --- a/keygen/primecandidate.c +++ b/keygen/primecandidate.c @@ -396,6 +396,8 @@ mp_int *pcs_generate(PrimeCandidateSource *s) if (!ok) { mp_free(x); + if (s->one_shot) + return NULL; continue; /* try a new x */ } -- cgit v1.2.3 From 1c78d18acb20c5f4b47af7e9daec2bbc74826dc7 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 27 Aug 2021 17:46:25 +0100 Subject: sclog: wrap memmove. I had a testsc run fail because of alignment-dependent control flow divergence in a glibc function with 'memmove' in the name, which appears to have been an accident of different memory allocation between two runs of the test in question. sclog was already giving special handling to memset for the same reason, so it's no trouble to add memmove to the same list of functions that are treated as an opaque primitive for logging purposes. --- test/sclog/sclog.c | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/test/sclog/sclog.c b/test/sclog/sclog.c index 2d2adbf4..f12a0280 100644 --- a/test/sclog/sclog.c +++ b/test/sclog/sclog.c @@ -269,6 +269,38 @@ static void wrap_memset_pre(void *wrapctx, void **user_data) } } +/* + * Similarly to the above, wrap some versions of memmove. + */ +static void wrap_memmove_pre(void *wrapctx, void **user_data) +{ + uint was_already_paused = logging_paused++; + + if (outfile == INVALID_FILE || was_already_paused) + return; + + const void *daddr = drwrap_get_arg(wrapctx, 0); + const void *saddr = drwrap_get_arg(wrapctx, 1); + size_t size = (size_t)drwrap_get_arg(wrapctx, 2); + + + struct allocation *alloc; + + dr_fprintf(outfile, "memmove %"PRIuMAX" ", (uintmax_t)size); + if (!(alloc = find_allocation(daddr))) { + dr_fprintf(outfile, "to %"PRIxMAX" ", (uintmax_t)daddr); + } else { + dr_fprintf(outfile, "to allocations[%"PRIuPTR"] + %"PRIxMAX" ", + alloc->index, (uintmax_t)(daddr - alloc->start)); + } + if (!(alloc = find_allocation(saddr))) { + dr_fprintf(outfile, "from %"PRIxMAX"\n", (uintmax_t)saddr); + } else { + dr_fprintf(outfile, "from allocations[%"PRIuPTR"] + %"PRIxMAX"\n", + alloc->index, (uintmax_t)(saddr - alloc->start)); + } +} + /* * Common post-wrapper function for memset and free, whose entire * function is to unpause the logging. @@ -565,6 +597,7 @@ static void load_module( TRY_WRAP("realloc", wrap_realloc_pre, wrap_alloc_post); TRY_WRAP("free", wrap_free_pre, unpause_post); TRY_WRAP("memset", wrap_memset_pre, unpause_post); + TRY_WRAP("memmove", wrap_memmove_pre, unpause_post); /* * More strangely named versions of standard C library @@ -585,6 +618,8 @@ static void load_module( TRY_WRAP("__GI___libc_free", wrap_free_pre, unpause_post); TRY_WRAP("__memset_sse2_unaligned", wrap_memset_pre, unpause_post); TRY_WRAP("__memset_sse2", wrap_memset_pre, unpause_post); + TRY_WRAP("__memmove_avx_unaligned_erms", wrap_memmove_pre, + unpause_post); TRY_WRAP("cfree", wrap_free_pre, unpause_post); } } -- cgit v1.2.3 From d8fda3b6da78c5a62c77fe5d75b9a033ef94e882 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 27 Aug 2021 17:46:25 +0100 Subject: testsc: add side-channel test of probabilistic prime gen. Now that I've removed side-channel leakage from both prime candidate generation (via mp_unsafe_mod_integer) and Miller-Rabin, the probabilistic prime generation system in this code base is now able to get through testsc without it detecting any source of cache or timing side channels. So you should be able to generate an RSA key (in which the primes themselves must be secret) in a more hostile environment than you could previously be confident of. This is a bit counterintuitive, because _obviously_ random prime generation takes a variable amount of time, because it has to keep retrying until an attempt succeeds! But that's OK as long as the attempts are completely independent, because then any timing or cache information leaked by a _failed_ attempt will only tell an attacker about the numbers used in the failed attempt, and those numbers have been thrown away, so it doesn't matter who knows them. It's only important that the _successful_ attempt, from generating the random candidate through to completing its verification as (probably) prime, should be side-channel clean, because that's the attempt whose data is actually going to be turned into a private key that needs to be kept secret. (In particular, this means you have to avoid the old-fashioned strategy of generating successive prime candidates by incrementing a starting value until you find something not divisible by any small prime, because the number of iterations of that method would be a timing leak. Happily, we stopped doing that last year, in commit 08a3547bc54051e: now every candidate integer is generated independently, and if one fails the initial checks, we throw it away and start completely from scratch with a fresh random value.) So the test harness works by repeatedly running the prime generator in one-shot mode until an attempt succeeds, and then resetting the random-number stream to where it was just before the successful attempt. Then we generate the same prime number again, this time with the sclog mechanism turned on - and then, we compare it against the version we previously generated with the same random numbers, to make sure they're the same. This checks that the attempts really _are_ independent, in the sense that the prime generator is a pure function of its random input stream, and doesn't depend on state left over from previous attempts. --- testsc.c | 91 +++++++++++++++++++++++++++++++++++++++++++++++++++++ unix/CMakeLists.txt | 2 +- 2 files changed, 92 insertions(+), 1 deletion(-) diff --git a/testsc.c b/testsc.c index a3e8b5d0..3ac88233 100644 --- a/testsc.c +++ b/testsc.c @@ -77,6 +77,7 @@ #include "defs.h" #include "putty.h" #include "ssh.h" +#include "sshkeygen.h" #include "misc.h" #include "mpint.h" #include "crypto/ecc.h" @@ -130,6 +131,31 @@ void random_read(void *vbuf, size_t size) } } +struct random_state { + const char *seedstr; + uint64_t counter; + size_t limit; + uint8_t buf[MAX_HASH_LEN]; +}; + +static struct random_state random_get_state(void) +{ + struct random_state st; + st.seedstr = random_seedstr; + st.counter = random_counter; + st.limit = random_buf_limit; + memcpy(st.buf, random_buf, sizeof(st.buf)); + return st; +} + +static void random_set_state(struct random_state st) +{ + random_seedstr = st.seedstr; + random_counter = st.counter; + random_buf_limit = st.limit; + memcpy(random_buf, st.buf, sizeof(random_buf)); +} + /* * Macro that defines a function, and also a volatile function pointer * pointing to it. Callers indirect through the function pointer @@ -177,6 +203,22 @@ void log_end(void) log_to_file(NULL); sfree(last_filename); } +void log_discard(void) +{ + /* + * Discard the most recently generated log file, and rewind the + * index so that its name will be reused by the next attempt. + * + * Used in tests of prime generation, in which only the + * _successful_ attempts need to be side-channel safe: it doesn't + * matter if a failed attempt leaks its data through early + * termination of a checking loop, because all the data it leaked + * will be thrown away anyway. + */ + char *prev_filename = log_filename(test_basename, --test_index); + remove(prev_filename); + sfree(prev_filename); +} static bool test_skipped = false; @@ -364,6 +406,7 @@ VOLATILE_WRAPPED_DEFN(static, size_t, looplimit, (size_t x)) MACS(MAC_TESTLIST, X) \ HASHES(HASH_TESTLIST, X) \ X(argon2) \ + X(primegen_probabilistic) \ /* end of list */ static void test_mp_get_nbits(void) @@ -1476,6 +1519,54 @@ static void test_argon2(void) strbuf_free(outdata); } +static void test_primegen(const PrimeGenerationPolicy *policy) +{ + static ProgressReceiver null_progress = { .vt = &null_progress_vt }; + + PrimeGenerationContext *pgc = primegen_new_context(policy); + + init_smallprimes(); + mp_int *pcopy = mp_new(128); + + for (size_t i = 0; i < looplimit(2); i++) { + while (true) { + struct random_state st = random_get_state(); + + PrimeCandidateSource *pcs = pcs_new(128); + pcs_set_oneshot(pcs); + pcs_ready(pcs); + mp_int *p = primegen_generate(pgc, pcs, &null_progress); + + if (p) { + mp_copy_into(pcopy, p); + sfree(p); + + random_set_state(st); + + log_start(); + PrimeCandidateSource *pcs = pcs_new(128); + pcs_set_oneshot(pcs); + pcs_ready(pcs); + mp_int *q = primegen_generate(pgc, pcs, &null_progress); + log_end(); + + assert(q); + assert(mp_cmp_eq(pcopy, q)); + mp_free(q); + break; + } + } + } + + mp_free(pcopy); + primegen_free_context(pgc); +} + +static void test_primegen_probabilistic(void) +{ + test_primegen(&primegen_probabilistic); +} + static const struct test tests[] = { #define STRUCT_TEST(X) { #X, test_##X }, TESTLIST(STRUCT_TEST) diff --git a/unix/CMakeLists.txt b/unix/CMakeLists.txt index 7ce5e669..37ea4337 100644 --- a/unix/CMakeLists.txt +++ b/unix/CMakeLists.txt @@ -99,7 +99,7 @@ target_link_libraries(cgtest add_executable(testsc ${CMAKE_SOURCE_DIR}/testsc.c) -target_link_libraries(testsc crypto utils) +target_link_libraries(testsc keygen crypto utils) add_executable(testzlib ${CMAKE_SOURCE_DIR}/testzlib.c -- cgit v1.2.3 From e0f9c42b0fc2ccfcfa99e0024549c47d4b5de96e Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 28 Aug 2021 10:17:16 +0100 Subject: primegen.py: add a --probabilistic option. Not sure how I hadn't needed that before! Obviously, if I have a test program that can exercise all the prime generation systems, it should include _all_ of them. --- test/primegen.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/primegen.py b/test/primegen.py index 6964099d..45b340f4 100755 --- a/test/primegen.py +++ b/test/primegen.py @@ -18,6 +18,8 @@ def main(): dest='policy', const='provable_fast') parser.add_argument("--complex", action='store_const', dest='policy', const='provable_maurer_complex') + parser.add_argument("--probabilistic", action='store_const', + dest='policy', const='probabilistic') parser.add_argument("-q", "--quiet", action='store_true') parser.add_argument("-b", "--binary", action='store_const', dest='fmt', const='{:b}') -- cgit v1.2.3 From 5bb869dd229c111ff6c0b3aecc70e4c162e60f9f Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 28 Aug 2021 10:25:09 +0100 Subject: Add references for the Diffie-Hellman fixed primes. I ran across their defining RFCs recently and noticed that each one provides an explicit mathematical expression for the prime (since each one is derived from the expansion of pi, with framing FFs and a correction term to make it actually prime). Those expressions can be re-evaluated trivially by spigot, so it seems reasonable to add those spigot commands in comments. This also means the comments contain citations for these primes in actual standards, including both the hex digits and the mathematical expressions. --- crypto/diffie-hellman.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crypto/diffie-hellman.c b/crypto/diffie-hellman.c index 634ef297..0461a430 100644 --- a/crypto/diffie-hellman.c +++ b/crypto/diffie-hellman.c @@ -19,12 +19,18 @@ struct dh_extra { static void dh_group1_construct(dh_ctx *ctx) { + /* Command to recompute, from the expression in RFC 2412 section E.2: +spigot -B16 '2^1024 - 2^960 - 1 + 2^64 * ( floor(2^894 pi) + 129093 )' + */ ctx->p = MP_LITERAL(0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF); ctx->g = mp_from_integer(2); } static void dh_group14_construct(dh_ctx *ctx) { + /* Command to recompute, from the expression in RFC 3526 section 3: +spigot -B16 '2^2048 - 2^1984 - 1 + 2^64 * ( floor(2^1918 pi) + 124476 )' + */ ctx->p = MP_LITERAL(0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF); ctx->g = mp_from_integer(2); } -- cgit v1.2.3 From 76688f9a0b398e14189bdad787bc21c0faf7a0bd Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Tue, 7 Sep 2021 13:38:14 +0100 Subject: Docs: insert missing 'inline' in a code example. In the section about our ad-hoc trait idioms, I described a code sample as containing a set of 'static inline' wrapper functions, which indeed it should have done - but I forgot to put the 'inline' keyword in the code sample itself. --- doc/udp.but | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/udp.but b/doc/udp.but index 22a8ceec..bb17219f 100644 --- a/doc/udp.but +++ b/doc/udp.but @@ -505,13 +505,13 @@ call sites. Instead, what we generally do in this code base is to write a set of \cw{static inline} wrapper functions in the same header file that defined the \cw{MyAbstraction} structure types, like this: -\c static MyAbstraction *myabs_new(const MyAbstractionVtable *vt) +\c static inline MyAbstraction *myabs_new(const MyAbstractionVtable *vt) \c { return vt->new(vt); } -\c static void myabs_free(MyAbstraction *myabs) +\c static inline void myabs_free(MyAbstraction *myabs) \c { myabs->vt->free(myabs); } -\c static void myimpl_modify(MyAbstraction *myabs, unsigned param) +\c static inline void myimpl_modify(MyAbstraction *myabs, unsigned param) \c { myabs->vt->modify(myabs, param); } -\c static unsigned myimpl_query(MyAbstraction *myabs, unsigned param) +\c static inline unsigned myimpl_query(MyAbstraction *myabs, unsigned param) \c { return myabs->vt->query(myabs, param); } And now call sites can use those reasonably clean-looking wrapper -- cgit v1.2.3 From 5c09c1c47ebde9dd9de149244408a356b791ba5c Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Tue, 7 Sep 2021 13:46:37 +0100 Subject: testsc: remove log_discard(). It was introduced in error by commit d8fda3b6da78c5a: I had originally intended to do test runs of prime generation by means of running every attempt with logging enabled, and after each failed attempt, deleting the log file and restarting it. But that was _far_ too slow, so I abandoned that approach, and switched to the alternative method of searching for a prime with logging turned off, and then repeating just the final successful attempt under logging conditions. log_discard() was the 'delete the log file' function intended for use in the first of those strategies. It wasn't actually used in the end, so I need not have committed it - and worse, the comment inside it claiming it _is_ used in prime generation is needlessly confusing! --- testsc.c | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/testsc.c b/testsc.c index 3ac88233..c9029662 100644 --- a/testsc.c +++ b/testsc.c @@ -203,22 +203,6 @@ void log_end(void) log_to_file(NULL); sfree(last_filename); } -void log_discard(void) -{ - /* - * Discard the most recently generated log file, and rewind the - * index so that its name will be reused by the next attempt. - * - * Used in tests of prime generation, in which only the - * _successful_ attempts need to be side-channel safe: it doesn't - * matter if a failed attempt leaks its data through early - * termination of a checking loop, because all the data it leaked - * will be thrown away anyway. - */ - char *prev_filename = log_filename(test_basename, --test_index); - remove(prev_filename); - sfree(prev_filename); -} static bool test_skipped = false; -- cgit v1.2.3 From bff0c590e5e857d3ce06d454f260d4263e10cc05 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 10 Sep 2021 10:38:30 +0100 Subject: Unix platform_make_x11_server: fix sense of error check. Analogous to the bug I just fixed in xtruss: in the loop that tries to find a reasonable port number for an X display, the sense of the (horrible) strcmp distinguishing EADDRINUSE from other socket errors was backwards. --- unix/x11.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/unix/x11.c b/unix/x11.c index 7a0c2218..f30dcfbf 100644 --- a/unix/x11.c +++ b/unix/x11.c @@ -88,7 +88,20 @@ int platform_make_x11_server(Plug *plug, const char *progname, int mindisp, sk_close(sockets[nsockets]); } - if (!strcmp(err, strerror(EADDRINUSE))) /* yuck! */ + /* + * If we weren't able to bind to this port because it's in use + * by another program, go round this loop and try again. But + * for any other reason, give up completely and return failure + * to our caller. + * + * sk_socket_error currently has no machine-readable component + * (it would need a cross-platform abstraction of the socket + * error types we care about, plus translation from each OS + * error enumeration into that). So we use the disgusting + * approach of a string compare between the error string and + * the one EADDRINUSE would have given :-( + */ + if (strcmp(err, strerror(EADDRINUSE))) goto out; } -- cgit v1.2.3 From 80f5105dad377522f86abf89d6362bfa4b3dc763 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 12 Sep 2021 09:52:46 +0100 Subject: sshproxy: keep addr and free it on close. The caller of new_connection has relinquished ownership of the SockAddr it passes in. So the receiver of that SockAddr must remember to free it, or else we leak memory. (Additionally, this means SshProxy will be able to remember the address during its run, e.g. to use in calls to its Plug. But that's not implemented yet.) --- sshproxy.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sshproxy.c b/sshproxy.c index f36a2a9b..18a1b9da 100644 --- a/sshproxy.c +++ b/sshproxy.c @@ -65,6 +65,8 @@ typedef struct SshProxy { bufchain ssh_to_socket; bool rcvd_eof_ssh_to_socket, sent_eof_ssh_to_socket; + SockAddr *addr; + /* Traits implemented: we're a Socket from the point of view of * the client connection, and a Seat from the POV of the SSH * backend we instantiate. */ @@ -86,6 +88,7 @@ static void sshproxy_close(Socket *s) { SshProxy *sp = container_of(s, SshProxy, sock); + sk_addr_free(sp->addr); sfree(sp->errmsg); conf_free(sp->conf); if (sp->backend) @@ -460,6 +463,8 @@ Socket *sshproxy_new_connection(SockAddr *addr, const char *hostname, psb_init(&sp->psb); bufchain_init(&sp->ssh_to_socket); + sp->addr = addr; + sp->conf = conf_new(); /* Try to treat proxy_hostname as the title of a saved session. If * that fails, set up a default Conf of our own treating it as a -- cgit v1.2.3 From c06c9c730fbe4033b038e36cb14a8382dd8f32b5 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 12 Sep 2021 09:52:46 +0100 Subject: Fill in missing implementation of pty_sendbuffer. Going through all the backends' send() and sendbuffer() routines, I noticed that the Unix pty backend is the only one where the return value from send() doesn't match what sendbuffer() would tell you, apparently because sendbuffer() was a stub implementation that I never got round to filling in properly. But pty masters _can_ back up, and if they do, we should return the appropriate data. --- unix/pty.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/unix/pty.c b/unix/pty.c index abafa108..8321f982 100644 --- a/unix/pty.c +++ b/unix/pty.c @@ -1414,8 +1414,8 @@ static void pty_close(Pty *pty) */ static size_t pty_sendbuffer(Backend *be) { - /* Pty *pty = container_of(be, Pty, backend); */ - return 0; + Pty *pty = container_of(be, Pty, backend); + return bufchain_size(&pty->output_data); } /* -- cgit v1.2.3 From 82177956daeb425a0df25866c2345647bb6c5b28 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 12 Sep 2021 09:52:46 +0100 Subject: Divide seat_set_trust_status into query and update. This complicates the API in one sense (more separate functions), but in another sense, simplifies it (each function does something simpler). When I start putting one Seat in front of another during SSH proxying, the latter will be more important - in particular, it means you can find out _whether_ a seat can support changing trust status without having to actually attempt a destructive modification. --- pscp.c | 3 ++- psftp.c | 3 ++- putty.h | 22 ++++++++++++++++------ ssh/connection1-client.c | 3 ++- ssh/connection2-client.c | 3 ++- ssh/server.c | 1 + ssh/sesschan.c | 1 + ssh/userauth2-client.c | 3 ++- sshproxy.c | 25 +++++++++++++++---------- unix/console.c | 15 ++++++++++++--- unix/plink.c | 1 + unix/window.c | 10 ++++++++-- utils/nullseat.c | 5 +++-- windows/console.c | 15 ++++++++++++--- windows/plink.c | 1 + windows/window.c | 10 ++++++++-- 16 files changed, 88 insertions(+), 33 deletions(-) diff --git a/pscp.c b/pscp.c index f8a6965c..a200484f 100644 --- a/pscp.c +++ b/pscp.c @@ -82,7 +82,8 @@ static const SeatVtable pscp_seat_vt = { .get_windowid = nullseat_get_windowid, .get_window_pixel_size = nullseat_get_window_pixel_size, .stripctrl_new = console_stripctrl_new, - .set_trust_status = nullseat_set_trust_status_vacuously, + .set_trust_status = nullseat_set_trust_status, + .can_set_trust_status = nullseat_can_set_trust_status_yes, .verbose = cmdline_seat_verbose, .interactive = nullseat_interactive_no, .get_cursor_position = nullseat_get_cursor_position, diff --git a/psftp.c b/psftp.c index 1fff45ef..ea52b23e 100644 --- a/psftp.c +++ b/psftp.c @@ -64,7 +64,8 @@ static const SeatVtable psftp_seat_vt = { .get_windowid = nullseat_get_windowid, .get_window_pixel_size = nullseat_get_window_pixel_size, .stripctrl_new = console_stripctrl_new, - .set_trust_status = nullseat_set_trust_status_vacuously, + .set_trust_status = nullseat_set_trust_status, + .can_set_trust_status = nullseat_can_set_trust_status_yes, .verbose = cmdline_seat_verbose, .interactive = nullseat_interactive_yes, .get_cursor_position = nullseat_get_cursor_position, diff --git a/putty.h b/putty.h index 2b544e54..382c010d 100644 --- a/putty.h +++ b/putty.h @@ -1097,13 +1097,19 @@ struct SeatVtable { * (and hence, can be trusted if it's asking you for secrets such * as your passphrase); false means output is coming from the * server. + */ + void (*set_trust_status)(Seat *seat, bool trusted); + + /* + * Query whether this Seat can do anything user-visible in + * response to set_trust_status. * * Returns true if the seat has a way to indicate this * distinction. Returns false if not, in which case the backend * should use a fallback defence against spoofing of PuTTY's local * prompts by malicious servers. */ - bool (*set_trust_status)(Seat *seat, bool trusted); + bool (*can_set_trust_status)(Seat *seat); /* * Ask the seat whether it would like verbose messages. @@ -1169,8 +1175,10 @@ static inline bool seat_get_window_pixel_size(Seat *seat, int *w, int *h) static inline StripCtrlChars *seat_stripctrl_new( Seat *seat, BinarySink *bs, SeatInteractionContext sic) { return seat->vt->stripctrl_new(seat, bs, sic); } -static inline bool seat_set_trust_status(Seat *seat, bool trusted) -{ return seat->vt->set_trust_status(seat, trusted); } +static inline void seat_set_trust_status(Seat *seat, bool trusted) +{ seat->vt->set_trust_status(seat, trusted); } +static inline bool seat_can_set_trust_status(Seat *seat) +{ return seat->vt->can_set_trust_status(seat); } static inline bool seat_verbose(Seat *seat) { return seat->vt->verbose(seat); } static inline bool seat_interactive(Seat *seat) @@ -1229,8 +1237,9 @@ bool nullseat_get_windowid(Seat *seat, long *id_out); bool nullseat_get_window_pixel_size(Seat *seat, int *width, int *height); StripCtrlChars *nullseat_stripctrl_new( Seat *seat, BinarySink *bs_out, SeatInteractionContext sic); -bool nullseat_set_trust_status(Seat *seat, bool trusted); -bool nullseat_set_trust_status_vacuously(Seat *seat, bool trusted); +void nullseat_set_trust_status(Seat *seat, bool trusted); +bool nullseat_can_set_trust_status_yes(Seat *seat); +bool nullseat_can_set_trust_status_no(Seat *seat); bool nullseat_verbose_no(Seat *seat); bool nullseat_verbose_yes(Seat *seat); bool nullseat_interactive_no(Seat *seat); @@ -1255,7 +1264,8 @@ int console_confirm_weak_cached_hostkey( void (*callback)(void *ctx, int result), void *ctx); StripCtrlChars *console_stripctrl_new( Seat *seat, BinarySink *bs_out, SeatInteractionContext sic); -bool console_set_trust_status(Seat *seat, bool trusted); +void console_set_trust_status(Seat *seat, bool trusted); +bool console_can_set_trust_status(Seat *seat); /* * Other centralised seat functions. diff --git a/ssh/connection1-client.c b/ssh/connection1-client.c index f32fefcd..7429b892 100644 --- a/ssh/connection1-client.c +++ b/ssh/connection1-client.c @@ -543,5 +543,6 @@ SshChannel *ssh1_serverside_agent_open(ConnectionLayer *cl, Channel *chan) bool ssh1_connection_need_antispoof_prompt(struct ssh1_connection_state *s) { - return !seat_set_trust_status(s->ppl.seat, false); + seat_set_trust_status(s->ppl.seat, false); + return !seat_can_set_trust_status(s->ppl.seat); } diff --git a/ssh/connection2-client.c b/ssh/connection2-client.c index b07e1eb2..88338500 100644 --- a/ssh/connection2-client.c +++ b/ssh/connection2-client.c @@ -500,6 +500,7 @@ void ssh2channel_send_terminal_size_change(SshChannel *sc, int w, int h) bool ssh2_connection_need_antispoof_prompt(struct ssh2_connection_state *s) { - bool success = seat_set_trust_status(s->ppl.seat, false); + seat_set_trust_status(s->ppl.seat, false); + bool success = seat_can_set_trust_status(s->ppl.seat); return (!success && !ssh_is_bare(s->ppl.ssh)); } diff --git a/ssh/server.c b/ssh/server.c index 6abbf332..e6d7f605 100644 --- a/ssh/server.c +++ b/ssh/server.c @@ -125,6 +125,7 @@ static const SeatVtable server_seat_vt = { .get_window_pixel_size = nullseat_get_window_pixel_size, .stripctrl_new = nullseat_stripctrl_new, .set_trust_status = nullseat_set_trust_status, + .can_set_trust_status = nullseat_can_set_trust_status_no, .verbose = nullseat_verbose_no, .interactive = nullseat_interactive_no, .get_cursor_position = nullseat_get_cursor_position, diff --git a/ssh/sesschan.c b/ssh/sesschan.c index f16faad2..7a2062a8 100644 --- a/ssh/sesschan.c +++ b/ssh/sesschan.c @@ -204,6 +204,7 @@ static const SeatVtable sesschan_seat_vt = { .get_window_pixel_size = sesschan_get_window_pixel_size, .stripctrl_new = nullseat_stripctrl_new, .set_trust_status = nullseat_set_trust_status, + .can_set_trust_status = nullseat_can_set_trust_status_no, .verbose = nullseat_verbose_no, .interactive = nullseat_interactive_no, .get_cursor_position = nullseat_get_cursor_position, diff --git a/ssh/userauth2-client.c b/ssh/userauth2-client.c index ddce251a..0fa1df06 100644 --- a/ssh/userauth2-client.c +++ b/ssh/userauth2-client.c @@ -1950,7 +1950,8 @@ static void ssh2_userauth_antispoof_msg( struct ssh2_userauth_state *s, const char *msg) { strbuf *sb = strbuf_new(); - if (seat_set_trust_status(s->ppl.seat, true)) { + seat_set_trust_status(s->ppl.seat, true); + if (seat_can_set_trust_status(s->ppl.seat)) { /* * If the seat can directly indicate that this message is * generated by the client, then we can just use the message diff --git a/sshproxy.c b/sshproxy.c index 18a1b9da..955012a8 100644 --- a/sshproxy.c +++ b/sshproxy.c @@ -405,20 +405,24 @@ static int sshproxy_confirm_weak_cached_hostkey( return 0; } -static bool sshproxy_set_trust_status(Seat *seat, bool trusted) +static void sshproxy_set_trust_status(Seat *seat, bool trusted) { /* * This is called by the proxy SSH connection, to set our Seat - * into a given trust status. We can safely do nothing here and - * return true to claim we did something (effectively eliminating - * the spoofing defences completely, by suppressing the 'press - * Return to begin session' prompt and not providing anything in - * place of it), on the basis that session I/O from the proxy SSH - * connection is never passed directly on to the end user, so a - * malicious proxy SSH server wouldn't be able to spoof our human - * in any case. + * into a given trust status. We can safely do nothing here, and + * have can_set_trust_status return true to claim we did something + * (effectively eliminating the spoofing defences completely, by + * suppressing the 'press Return to begin session' prompt and not + * providing anything in place of it), on the basis that session + * I/O from the proxy SSH connection is never passed directly on + * to the end user, so a malicious proxy SSH server wouldn't be + * able to spoof our human in any case. */ - return true; +} + +static bool sshproxy_can_set_trust_status(Seat *seat) +{ + return true; /* see comment above */ } static const SeatVtable SshProxy_seat_vt = { @@ -442,6 +446,7 @@ static const SeatVtable SshProxy_seat_vt = { .get_window_pixel_size = nullseat_get_window_pixel_size, .stripctrl_new = nullseat_stripctrl_new, .set_trust_status = sshproxy_set_trust_status, + .can_set_trust_status = sshproxy_can_set_trust_status, .verbose = nullseat_verbose_no, .interactive = nullseat_interactive_no, .get_cursor_position = nullseat_get_cursor_position, diff --git a/unix/console.c b/unix/console.c index 90e73a98..ffe777fd 100644 --- a/unix/console.c +++ b/unix/console.c @@ -321,7 +321,16 @@ int console_askappend(LogPolicy *lp, Filename *filename, } bool console_antispoof_prompt = true; -bool console_set_trust_status(Seat *seat, bool trusted) + +void console_set_trust_status(Seat *seat, bool trusted) +{ + /* Do nothing in response to a change of trust status, because + * there's nothing we can do in a console environment. However, + * the query function below will make a fiddly decision about + * whether to tell the backend to enable fallback handling. */ +} + +bool console_can_set_trust_status(Seat *seat) { if (console_batch_mode || !is_interactive() || !console_antispoof_prompt) { /* @@ -334,8 +343,8 @@ bool console_set_trust_status(Seat *seat, bool trusted) * prompt, the user couldn't respond to it via the terminal * anyway. * - * We also vacuously return success if the user has purposely - * disabled the antispoof prompt. + * We also return true without enabling any defences if the + * user has purposely disabled the antispoof prompt. */ return true; } diff --git a/unix/plink.c b/unix/plink.c index 7663b976..a1f640a1 100644 --- a/unix/plink.c +++ b/unix/plink.c @@ -407,6 +407,7 @@ static const SeatVtable plink_seat_vt = { .get_window_pixel_size = nullseat_get_window_pixel_size, .stripctrl_new = console_stripctrl_new, .set_trust_status = console_set_trust_status, + .can_set_trust_status = console_can_set_trust_status, .verbose = cmdline_seat_verbose, .interactive = plink_seat_interactive, .get_cursor_position = nullseat_get_cursor_position, diff --git a/unix/window.c b/unix/window.c index 8582777b..e5b541c8 100644 --- a/unix/window.c +++ b/unix/window.c @@ -383,7 +383,8 @@ static const char *gtk_seat_get_x_display(Seat *seat); #ifndef NOT_X_WINDOWS static bool gtk_seat_get_windowid(Seat *seat, long *id); #endif -static bool gtk_seat_set_trust_status(Seat *seat, bool trusted); +static void gtk_seat_set_trust_status(Seat *seat, bool trusted); +static bool gtk_seat_can_set_trust_status(Seat *seat); static bool gtk_seat_get_cursor_position(Seat *seat, int *x, int *y); static const SeatVtable gtk_seat_vt = { @@ -411,6 +412,7 @@ static const SeatVtable gtk_seat_vt = { .get_window_pixel_size = gtk_seat_get_window_pixel_size, .stripctrl_new = gtk_seat_stripctrl_new, .set_trust_status = gtk_seat_set_trust_status, + .can_set_trust_status = gtk_seat_can_set_trust_status, .verbose = nullseat_verbose_yes, .interactive = nullseat_interactive_yes, .get_cursor_position = gtk_seat_get_cursor_position, @@ -5415,10 +5417,14 @@ void new_session_window(Conf *conf, const char *geometry_string) ldisc_echoedit_update(inst->ldisc); /* cause ldisc to notice changes */ } -static bool gtk_seat_set_trust_status(Seat *seat, bool trusted) +static void gtk_seat_set_trust_status(Seat *seat, bool trusted) { GtkFrontend *inst = container_of(seat, GtkFrontend, seat); term_set_trust_status(inst->term, trusted); +} + +static bool gtk_seat_can_set_trust_status(Seat *seat) +{ return true; } diff --git a/utils/nullseat.c b/utils/nullseat.c index 0c773af0..5e601669 100644 --- a/utils/nullseat.c +++ b/utils/nullseat.c @@ -35,8 +35,9 @@ bool nullseat_get_window_pixel_size( Seat *seat, int *width, int *height) { return false; } StripCtrlChars *nullseat_stripctrl_new( Seat *seat, BinarySink *bs_out, SeatInteractionContext sic) {return NULL;} -bool nullseat_set_trust_status(Seat *seat, bool tr) { return false; } -bool nullseat_set_trust_status_vacuously(Seat *seat, bool tr) { return true; } +void nullseat_set_trust_status(Seat *seat, bool trusted) {} +bool nullseat_can_set_trust_status_yes(Seat *seat) { return true; } +bool nullseat_can_set_trust_status_no(Seat *seat) { return false; } bool nullseat_verbose_no(Seat *seat) { return false; } bool nullseat_verbose_yes(Seat *seat) { return true; } bool nullseat_interactive_no(Seat *seat) { return false; } diff --git a/windows/console.c b/windows/console.c index 414167b4..9bdfb6f4 100644 --- a/windows/console.c +++ b/windows/console.c @@ -187,7 +187,16 @@ bool is_interactive(void) } bool console_antispoof_prompt = true; -bool console_set_trust_status(Seat *seat, bool trusted) + +void console_set_trust_status(Seat *seat, bool trusted) +{ + /* Do nothing in response to a change of trust status, because + * there's nothing we can do in a console environment. However, + * the query function below will make a fiddly decision about + * whether to tell the backend to enable fallback handling. */ +} + +bool console_can_set_trust_status(Seat *seat) { if (console_batch_mode || !is_interactive() || !console_antispoof_prompt) { /* @@ -200,8 +209,8 @@ bool console_set_trust_status(Seat *seat, bool trusted) * prompt, the user couldn't respond to it via the terminal * anyway. * - * We also vacuously return success if the user has purposely - * disabled the antispoof prompt. + * We also return true without enabling any defences if the + * user has purposely disabled the antispoof prompt. */ return true; } diff --git a/windows/plink.c b/windows/plink.c index ac68ce7d..2c68fb63 100644 --- a/windows/plink.c +++ b/windows/plink.c @@ -101,6 +101,7 @@ static const SeatVtable plink_seat_vt = { .get_window_pixel_size = nullseat_get_window_pixel_size, .stripctrl_new = console_stripctrl_new, .set_trust_status = console_set_trust_status, + .can_set_trust_status = console_can_set_trust_status, .verbose = cmdline_seat_verbose, .interactive = plink_seat_interactive, .get_cursor_position = nullseat_get_cursor_position, diff --git a/windows/window.c b/windows/window.c index b00b910c..9a273d83 100644 --- a/windows/window.c +++ b/windows/window.c @@ -323,7 +323,8 @@ static void win_seat_notify_remote_exit(Seat *seat); static void win_seat_connection_fatal(Seat *seat, const char *msg); static void win_seat_update_specials_menu(Seat *seat); static void win_seat_set_busy_status(Seat *seat, BusyStatus status); -static bool win_seat_set_trust_status(Seat *seat, bool trusted); +static void win_seat_set_trust_status(Seat *seat, bool trusted); +static bool win_seat_can_set_trust_status(Seat *seat); static bool win_seat_get_cursor_position(Seat *seat, int *x, int *y); static bool win_seat_get_window_pixel_size(Seat *seat, int *x, int *y); @@ -348,6 +349,7 @@ static const SeatVtable win_seat_vt = { .get_window_pixel_size = win_seat_get_window_pixel_size, .stripctrl_new = win_seat_stripctrl_new, .set_trust_status = win_seat_set_trust_status, + .can_set_trust_status = win_seat_can_set_trust_status, .verbose = nullseat_verbose_yes, .interactive = nullseat_interactive_yes, .get_cursor_position = win_seat_get_cursor_position, @@ -5753,9 +5755,13 @@ static int win_seat_get_userpass_input( return ret; } -static bool win_seat_set_trust_status(Seat *seat, bool trusted) +static void win_seat_set_trust_status(Seat *seat, bool trusted) { term_set_trust_status(term, trusted); +} + +static bool win_seat_can_set_trust_status(Seat *seat) +{ return true; } -- cgit v1.2.3 From c3366435765afcfcef30cff8b65da4dcf36c2bd7 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 12 Sep 2021 09:52:46 +0100 Subject: Separate backend_send from backend_sendbuffer. On a similar theme of separating the query operation from the attempted change, backend_send() now no longer has the side effect of returning the current size of the send buffer. Instead, you have to call backend_sendbuffer() every time you want to know that. --- otherbackends/raw.c | 6 ++---- otherbackends/rlogin.c | 6 ++---- otherbackends/supdup.c | 5 ++--- otherbackends/telnet.c | 6 ++---- otherbackends/testback.c | 21 ++++++++++++++------- pscp.c | 3 ++- putty.h | 9 ++++----- ssh/sesschan.c | 3 ++- ssh/ssh.c | 6 ++---- sshproxy.c | 3 ++- unix/pty.c | 6 ++---- unix/serial.c | 6 ++---- windows/conpty.c | 5 ++--- windows/plink.c | 3 ++- windows/serial.c | 5 ++--- 15 files changed, 44 insertions(+), 49 deletions(-) diff --git a/otherbackends/raw.c b/otherbackends/raw.c index 9c803594..2f099ce3 100644 --- a/otherbackends/raw.c +++ b/otherbackends/raw.c @@ -207,16 +207,14 @@ static void raw_reconfig(Backend *be, Conf *conf) /* * Called to send data down the raw connection. */ -static size_t raw_send(Backend *be, const char *buf, size_t len) +static void raw_send(Backend *be, const char *buf, size_t len) { Raw *raw = container_of(be, Raw, backend); if (raw->s == NULL) - return 0; + return; raw->bufsize = sk_write(raw->s, buf, len); - - return raw->bufsize; } /* diff --git a/otherbackends/rlogin.c b/otherbackends/rlogin.c index 30ad0526..d0da322a 100644 --- a/otherbackends/rlogin.c +++ b/otherbackends/rlogin.c @@ -269,13 +269,13 @@ static void rlogin_reconfig(Backend *be, Conf *conf) /* * Called to send data down the rlogin connection. */ -static size_t rlogin_send(Backend *be, const char *buf, size_t len) +static void rlogin_send(Backend *be, const char *buf, size_t len) { Rlogin *rlogin = container_of(be, Rlogin, backend); bufchain bc; if (rlogin->s == NULL) - return 0; + return; bufchain_init(&bc); bufchain_add(&bc, buf, len); @@ -305,8 +305,6 @@ static size_t rlogin_send(Backend *be, const char *buf, size_t len) } bufchain_clear(&bc); - - return rlogin->bufsize; } /* diff --git a/otherbackends/supdup.c b/otherbackends/supdup.c index e7eff6ee..7111c9a6 100644 --- a/otherbackends/supdup.c +++ b/otherbackends/supdup.c @@ -797,14 +797,14 @@ static void supdup_reconfig(Backend *be, Conf *conf) /* * Called to send data down the Supdup connection. */ -static size_t supdup_send(Backend *be, const char *buf, size_t len) +static void supdup_send(Backend *be, const char *buf, size_t len) { Supdup *supdup = container_of(be, Supdup, backend); char c; int i; if (supdup->s == NULL) - return 0; + return; for (i = 0; i < len; i++) { if (buf[i] == 034) @@ -814,7 +814,6 @@ static size_t supdup_send(Backend *be, const char *buf, size_t len) supdup->bufsize = sk_write(supdup->s, &c, 1); } } - return supdup->bufsize; } /* diff --git a/otherbackends/telnet.c b/otherbackends/telnet.c index 4b784036..15afd82e 100644 --- a/otherbackends/telnet.c +++ b/otherbackends/telnet.c @@ -813,7 +813,7 @@ static void telnet_reconfig(Backend *be, Conf *conf) /* * Called to send data down the Telnet connection. */ -static size_t telnet_send(Backend *be, const char *buf, size_t len) +static void telnet_send(Backend *be, const char *buf, size_t len) { Telnet *telnet = container_of(be, Telnet, backend); unsigned char *p, *end; @@ -824,7 +824,7 @@ static size_t telnet_send(Backend *be, const char *buf, size_t len) #endif if (telnet->s == NULL) - return 0; + return; p = (unsigned char *)buf; end = (unsigned char *)(buf + len); @@ -841,8 +841,6 @@ static size_t telnet_send(Backend *be, const char *buf, size_t len) p++; } } - - return telnet->bufsize; } /* diff --git a/otherbackends/testback.c b/otherbackends/testback.c index 8a158439..4ff6fa16 100644 --- a/otherbackends/testback.c +++ b/otherbackends/testback.c @@ -39,9 +39,10 @@ static char *loop_init(const BackendVtable *, Seat *, Backend **, LogContext *, static void null_free(Backend *); static void loop_free(Backend *); static void null_reconfig(Backend *, Conf *); -static size_t null_send(Backend *, const char *, size_t); -static size_t loop_send(Backend *, const char *, size_t); +static void null_send(Backend *, const char *, size_t); +static void loop_send(Backend *, const char *, size_t); static size_t null_sendbuffer(Backend *); +static size_t loop_sendbuffer(Backend *); static void null_size(Backend *, int, int); static void null_special(Backend *, SessionSpecialCode, int); static const SessionSpecial *null_get_specials(Backend *); @@ -80,7 +81,7 @@ const BackendVtable loop_backend = { .free = loop_free, .reconfig = null_reconfig, .send = loop_send, - .sendbuffer = null_sendbuffer, + .sendbuffer = loop_sendbuffer, .size = null_size, .special = null_special, .get_specials = null_get_specials, @@ -100,6 +101,7 @@ const BackendVtable loop_backend = { struct loop_state { Seat *seat; Backend backend; + size_t sendbuffer; }; static char *null_init(const BackendVtable *vt, Seat *seat, @@ -143,15 +145,14 @@ static void null_reconfig(Backend *be, Conf *conf) { } -static size_t null_send(Backend *be, const char *buf, size_t len) { +static void null_send(Backend *be, const char *buf, size_t len) { - return 0; } -static size_t loop_send(Backend *be, const char *buf, size_t len) { +static void loop_send(Backend *be, const char *buf, size_t len) { struct loop_state *st = container_of(be, struct loop_state, backend); - return seat_output(st->seat, 0, buf, len); + st->sendbuffer = seat_output(st->seat, 0, buf, len); } static size_t null_sendbuffer(Backend *be) { @@ -159,6 +160,12 @@ static size_t null_sendbuffer(Backend *be) { return 0; } +static size_t loop_sendbuffer(Backend *be) { + struct loop_state *st = container_of(be, struct loop_state, backend); + + return st->sendbuffer; +} + static void null_size(Backend *be, int width, int height) { } diff --git a/pscp.c b/pscp.c index a200484f..d759fb4e 100644 --- a/pscp.c +++ b/pscp.c @@ -851,7 +851,8 @@ int scp_send_filedata(char *data, int len) scp_sftp_fileoffset += len; return 0; } else { - int bufsize = backend_send(backend, data, len); + backend_send(backend, data, len); + int bufsize = backend_sendbuffer(backend); /* * If the network transfer is backing up - that is, the diff --git a/putty.h b/putty.h index 382c010d..4492d515 100644 --- a/putty.h +++ b/putty.h @@ -636,9 +636,8 @@ struct BackendVtable { void (*free) (Backend *be); /* Pass in a replacement configuration. */ void (*reconfig) (Backend *be, Conf *conf); - /* send() returns the current amount of buffered data. */ - size_t (*send) (Backend *be, const char *buf, size_t len); - /* sendbuffer() does the same thing but without attempting a send */ + void (*send) (Backend *be, const char *buf, size_t len); + /* sendbuffer() returns the current amount of buffered data */ size_t (*sendbuffer) (Backend *be); void (*size) (Backend *be, int width, int height); void (*special) (Backend *be, SessionSpecialCode code, int arg); @@ -687,8 +686,8 @@ static inline void backend_free(Backend *be) { be->vt->free(be); } static inline void backend_reconfig(Backend *be, Conf *conf) { be->vt->reconfig(be, conf); } -static inline size_t backend_send(Backend *be, const char *buf, size_t len) -{ return be->vt->send(be, buf, len); } +static inline void backend_send(Backend *be, const char *buf, size_t len) +{ be->vt->send(be, buf, len); } static inline size_t backend_sendbuffer(Backend *be) { return be->vt->sendbuffer(be); } static inline void backend_size(Backend *be, int width, int height) diff --git a/ssh/sesschan.c b/ssh/sesschan.c index 7a2062a8..e1496023 100644 --- a/ssh/sesschan.c +++ b/ssh/sesschan.c @@ -270,7 +270,8 @@ static size_t sesschan_send(Channel *chan, bool is_stderr, if (!sess->backend || sess->ignoring_input) return 0; - return backend_send(sess->backend, data, length); + backend_send(sess->backend, data, length); + return backend_sendbuffer(sess->backend); } static void sesschan_send_eof(Channel *chan) diff --git a/ssh/ssh.c b/ssh/ssh.c index 096c8b01..4ad10169 100644 --- a/ssh/ssh.c +++ b/ssh/ssh.c @@ -1003,18 +1003,16 @@ static void ssh_reconfig(Backend *be, Conf *conf) /* * Called to send data down the SSH connection. */ -static size_t ssh_send(Backend *be, const char *buf, size_t len) +static void ssh_send(Backend *be, const char *buf, size_t len) { Ssh *ssh = container_of(be, Ssh, backend); if (ssh == NULL || ssh->s == NULL) - return 0; + return; bufchain_add(&ssh->user_input, buf, len); if (ssh->base_layer) ssh_ppl_got_user_input(ssh->base_layer); - - return backend_sendbuffer(&ssh->backend); } /* diff --git a/sshproxy.c b/sshproxy.c index 955012a8..9c6708ab 100644 --- a/sshproxy.c +++ b/sshproxy.c @@ -106,7 +106,8 @@ static size_t sshproxy_write(Socket *s, const void *data, size_t len) SshProxy *sp = container_of(s, SshProxy, sock); if (!sp->backend) return 0; - return backend_send(sp->backend, data, len); + backend_send(sp->backend, data, len); + return backend_sendbuffer(sp->backend); } static size_t sshproxy_write_oob(Socket *s, const void *data, size_t len) diff --git a/unix/pty.c b/unix/pty.c index 8321f982..e74ebd4b 100644 --- a/unix/pty.c +++ b/unix/pty.c @@ -1367,17 +1367,15 @@ static void pty_try_write(Pty *pty) /* * Called to send data down the pty. */ -static size_t pty_send(Backend *be, const char *buf, size_t len) +static void pty_send(Backend *be, const char *buf, size_t len) { Pty *pty = container_of(be, Pty, backend); if (pty->master_i < 0 || pty->pending_eof) - return 0; /* ignore all writes if fd closed */ + return; /* 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) diff --git a/unix/serial.c b/unix/serial.c index d17e4cdd..905c1b2d 100644 --- a/unix/serial.c +++ b/unix/serial.c @@ -461,17 +461,15 @@ static void serial_try_write(Serial *serial) /* * Called to send data down the serial connection. */ -static size_t serial_send(Backend *be, const char *buf, size_t len) +static void serial_send(Backend *be, const char *buf, size_t len) { Serial *serial = container_of(be, Serial, backend); if (serial->fd < 0) - return 0; + return; bufchain_add(&serial->output_data, buf, len); serial_try_write(serial); - - return bufchain_size(&serial->output_data); } /* diff --git a/windows/conpty.c b/windows/conpty.c index 843d6725..f2882316 100644 --- a/windows/conpty.c +++ b/windows/conpty.c @@ -287,15 +287,14 @@ static void conpty_reconfig(Backend *be, Conf *conf) { } -static size_t conpty_send(Backend *be, const char *buf, size_t len) +static void conpty_send(Backend *be, const char *buf, size_t len) { ConPTY *conpty = container_of(be, ConPTY, backend); if (conpty->out == NULL) - return 0; + return; conpty->bufsize = handle_write(conpty->out, buf, len); - return conpty->bufsize; } static size_t conpty_sendbuffer(Backend *be) diff --git a/windows/plink.c b/windows/plink.c index 2c68fb63..5bb001be 100644 --- a/windows/plink.c +++ b/windows/plink.c @@ -204,7 +204,8 @@ size_t stdin_gotdata(struct handle *h, const void *data, size_t len, int err) noise_ultralight(NOISE_SOURCE_IOLEN, len); if (backend_connected(backend)) { if (len > 0) { - return backend_send(backend, data, len); + backend_send(backend, data, len); + return backend_sendbuffer(backend); } else { backend_special(backend, SS_EOF, 0); return 0; diff --git a/windows/serial.c b/windows/serial.c index 3d5ea8e5..470c979f 100644 --- a/windows/serial.c +++ b/windows/serial.c @@ -306,15 +306,14 @@ static void serial_reconfig(Backend *be, Conf *conf) /* * Called to send data down the serial connection. */ -static size_t serial_send(Backend *be, const char *buf, size_t len) +static void serial_send(Backend *be, const char *buf, size_t len) { Serial *serial = container_of(be, Serial, backend); if (serial->out == NULL) - return 0; + return; serial->bufsize = handle_write(serial->out, buf, len); - return serial->bufsize; } /* -- cgit v1.2.3 From 346a7548e2ba2739f0cc5a2c9ec092186735fa57 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 12 Sep 2021 11:48:42 +0100 Subject: New Seat method, notify_session_started(). This is called by the backend to notify the Seat that the connection has progressed to the point where the main session channel (i.e. the thing that would typically correspond to the client's stdin/stdout) has been successfully set up. The only Seat that implements this method nontrivially is the one in SshProxy, which uses it as an indication that the proxied connection to the remote host has succeeded, and sends the PLUGLOG_CONNECT_SUCCESS notification to its own Plug. Hence, the only backends that need to implement it at the moment are the two SSH-shaped backends (SSH proper and bare-connection / psusan). For other backends, it's not always obvious what 'main session channel' would even mean, or whether it means anything very useful; so I've also introduced a backend flag indicating whether the backend is expecting to call that method at all, so as not to have to spend pointless effort on defining an arbitrary meaning for it in other contexts. So a lot of this patch is just introducing the new method and putting its trivial do-nothing implementation into all the existing Seat methods. The interesting parts happen in ssh/mainchan.c (which actually calls it), and sshproxy.c (which does something useful in response). --- pscp.c | 1 + psftp.c | 1 + putty.h | 16 ++++++++++++++++ ssh/mainchan.c | 1 + ssh/server.c | 1 + ssh/sesschan.c | 1 + ssh/ssh.c | 4 ++-- sshproxy.c | 17 +++++++++++++++++ unix/plink.c | 1 + unix/window.c | 1 + utils/nullseat.c | 1 + windows/plink.c | 1 + windows/window.c | 1 + 13 files changed, 45 insertions(+), 2 deletions(-) diff --git a/pscp.c b/pscp.c index d759fb4e..ec4dfa8a 100644 --- a/pscp.c +++ b/pscp.c @@ -67,6 +67,7 @@ static const SeatVtable pscp_seat_vt = { .eof = pscp_eof, .sent = nullseat_sent, .get_userpass_input = filexfer_get_userpass_input, + .notify_session_started = nullseat_notify_session_started, .notify_remote_exit = nullseat_notify_remote_exit, .notify_remote_disconnect = nullseat_notify_remote_disconnect, .connection_fatal = console_connection_fatal, diff --git a/psftp.c b/psftp.c index ea52b23e..16beee56 100644 --- a/psftp.c +++ b/psftp.c @@ -49,6 +49,7 @@ static const SeatVtable psftp_seat_vt = { .eof = psftp_eof, .sent = nullseat_sent, .get_userpass_input = filexfer_get_userpass_input, + .notify_session_started = nullseat_notify_session_started, .notify_remote_exit = nullseat_notify_remote_exit, .notify_remote_disconnect = nullseat_notify_remote_disconnect, .connection_fatal = console_connection_fatal, diff --git a/putty.h b/putty.h index 4492d515..d622574d 100644 --- a/putty.h +++ b/putty.h @@ -620,6 +620,8 @@ enum { #define BACKEND_NEEDS_TERMINAL 0x02 /* Backend must have terminal */ #define BACKEND_SUPPORTS_NC_HOST 0x04 /* Backend can honour CONF_ssh_nc_host */ +#define BACKEND_NOTIFIES_SESSION_START 0x08 /* Backend will call + seat_notify_session_started */ /* In (no)sshproxy.c */ extern const bool ssh_proxy_supported; @@ -932,6 +934,17 @@ struct SeatVtable { */ int (*get_userpass_input)(Seat *seat, prompts_t *p, bufchain *input); + /* + * Notify the seat that the main session channel has been + * successfully set up. + * + * This is only used as part of the SSH proxying system, so it's + * not necessary to implement it in all backends. A backend must + * call this if it advertises the BACKEND_NOTIFIES_SESSION_START + * flag, and otherwise, doesn't have to. + */ + void (*notify_session_started)(Seat *seat); + /* * Notify the seat that the process running at the other end of * the connection has finished. @@ -1138,6 +1151,8 @@ static inline void seat_sent(Seat *seat, size_t bufsize) static inline int seat_get_userpass_input( Seat *seat, prompts_t *p, bufchain *input) { return seat->vt->get_userpass_input(seat, p, input); } +static inline void seat_notify_session_started(Seat *seat) +{ seat->vt->notify_session_started(seat); } static inline void seat_notify_remote_exit(Seat *seat) { seat->vt->notify_remote_exit(seat); } static inline void seat_notify_remote_disconnect(Seat *seat) @@ -1212,6 +1227,7 @@ size_t nullseat_output( bool nullseat_eof(Seat *seat); void nullseat_sent(Seat *seat, size_t bufsize); int nullseat_get_userpass_input(Seat *seat, prompts_t *p, bufchain *input); +void nullseat_notify_session_started(Seat *seat); void nullseat_notify_remote_exit(Seat *seat); void nullseat_notify_remote_disconnect(Seat *seat); void nullseat_connection_fatal(Seat *seat, const char *message); diff --git a/ssh/mainchan.c b/ssh/mainchan.c index 70033a7a..2e690547 100644 --- a/ssh/mainchan.c +++ b/ssh/mainchan.c @@ -129,6 +129,7 @@ static void mainchan_open_confirmation(Channel *chan) seat_update_specials_menu(mc->ppl->seat); ppl_logevent("Opened main channel"); + seat_notify_session_started(mc->ppl->seat); if (mc->is_simple) sshfwd_hint_channel_is_simple(mc->sc); diff --git a/ssh/server.c b/ssh/server.c index e6d7f605..1517522b 100644 --- a/ssh/server.c +++ b/ssh/server.c @@ -109,6 +109,7 @@ static const SeatVtable server_seat_vt = { .eof = nullseat_eof, .sent = nullseat_sent, .get_userpass_input = nullseat_get_userpass_input, + .notify_session_started = nullseat_notify_session_started, .notify_remote_exit = nullseat_notify_remote_exit, .notify_remote_disconnect = nullseat_notify_remote_disconnect, .connection_fatal = nullseat_connection_fatal, diff --git a/ssh/sesschan.c b/ssh/sesschan.c index e1496023..527b7603 100644 --- a/ssh/sesschan.c +++ b/ssh/sesschan.c @@ -188,6 +188,7 @@ static const SeatVtable sesschan_seat_vt = { .eof = sesschan_seat_eof, .sent = nullseat_sent, .get_userpass_input = nullseat_get_userpass_input, + .notify_session_started = nullseat_notify_session_started, .notify_remote_exit = sesschan_notify_remote_exit, .notify_remote_disconnect = nullseat_notify_remote_disconnect, .connection_fatal = sesschan_connection_fatal, diff --git a/ssh/ssh.c b/ssh/ssh.c index 4ad10169..aee04db9 100644 --- a/ssh/ssh.c +++ b/ssh/ssh.c @@ -1230,7 +1230,7 @@ const BackendVtable ssh_backend = { .id = "ssh", .displayname = "SSH", .protocol = PROT_SSH, - .flags = BACKEND_SUPPORTS_NC_HOST, + .flags = BACKEND_SUPPORTS_NC_HOST | BACKEND_NOTIFIES_SESSION_START, .default_port = 22, }; @@ -1255,5 +1255,5 @@ const BackendVtable sshconn_backend = { .id = "ssh-connection", .displayname = "Bare ssh-connection", .protocol = PROT_SSHCONN, - .flags = BACKEND_SUPPORTS_NC_HOST, + .flags = BACKEND_SUPPORTS_NC_HOST | BACKEND_NOTIFIES_SESSION_START, }; diff --git a/sshproxy.c b/sshproxy.c index 9c6708ab..feb9b077 100644 --- a/sshproxy.c +++ b/sshproxy.c @@ -66,6 +66,7 @@ typedef struct SshProxy { bool rcvd_eof_ssh_to_socket, sent_eof_ssh_to_socket; SockAddr *addr; + int port; /* Traits implemented: we're a Socket from the point of view of * the client connection, and a Seat from the POV of the SSH @@ -245,6 +246,12 @@ static void try_send_ssh_to_socket(void *ctx) } } +static void sshproxy_notify_session_started(Seat *seat) +{ + SshProxy *sp = container_of(seat, SshProxy, seat); + plug_log(sp->plug, PLUGLOG_CONNECT_SUCCESS, sp->addr, sp->port, NULL, 0); +} + static size_t sshproxy_output(Seat *seat, bool is_stderr, const void *data, size_t len) { @@ -431,6 +438,7 @@ static const SeatVtable SshProxy_seat_vt = { .eof = sshproxy_eof, .sent = sshproxy_sent, .get_userpass_input = sshproxy_get_userpass_input, + .notify_session_started = sshproxy_notify_session_started, .notify_remote_exit = nullseat_notify_remote_exit, .notify_remote_disconnect = sshproxy_notify_remote_disconnect, .connection_fatal = sshproxy_connection_fatal, @@ -470,6 +478,7 @@ Socket *sshproxy_new_connection(SockAddr *addr, const char *hostname, bufchain_init(&sp->ssh_to_socket); sp->addr = addr; + sp->port = port; sp->conf = conf_new(); /* Try to treat proxy_hostname as the title of a saved session. If @@ -513,6 +522,14 @@ Socket *sshproxy_new_connection(SockAddr *addr, const char *hostname, return &sp->sock; } + /* + * We also expect that the backend will announce a willingness to + * notify us that the session has started. Any backend providing + * NC_HOST should also provide this. + */ + assert(backvt->flags & BACKEND_NOTIFIES_SESSION_START && + "Backend provides NC_HOST without SESSION_START!"); + /* * Turn off SSH features we definitely don't want. It would be * awkward and counterintuitive to have the proxy SSH connection diff --git a/unix/plink.c b/unix/plink.c index a1f640a1..f8acd5ec 100644 --- a/unix/plink.c +++ b/unix/plink.c @@ -391,6 +391,7 @@ static const SeatVtable plink_seat_vt = { .eof = plink_eof, .sent = nullseat_sent, .get_userpass_input = plink_get_userpass_input, + .notify_session_started = nullseat_notify_session_started, .notify_remote_exit = nullseat_notify_remote_exit, .notify_remote_disconnect = nullseat_notify_remote_disconnect, .connection_fatal = console_connection_fatal, diff --git a/unix/window.c b/unix/window.c index e5b541c8..cb6e831d 100644 --- a/unix/window.c +++ b/unix/window.c @@ -392,6 +392,7 @@ static const SeatVtable gtk_seat_vt = { .eof = gtk_seat_eof, .sent = nullseat_sent, .get_userpass_input = gtk_seat_get_userpass_input, + .notify_session_started = nullseat_notify_session_started, .notify_remote_exit = gtk_seat_notify_remote_exit, .notify_remote_disconnect = nullseat_notify_remote_disconnect, .connection_fatal = gtk_seat_connection_fatal, diff --git a/utils/nullseat.c b/utils/nullseat.c index 5e601669..907d9176 100644 --- a/utils/nullseat.c +++ b/utils/nullseat.c @@ -10,6 +10,7 @@ bool nullseat_eof(Seat *seat) { return true; } void nullseat_sent(Seat *seat, size_t bufsize) {} int nullseat_get_userpass_input( Seat *seat, prompts_t *p, bufchain *input) { return 0; } +void nullseat_notify_session_started(Seat *seat) {} void nullseat_notify_remote_exit(Seat *seat) {} void nullseat_notify_remote_disconnect(Seat *seat) {} void nullseat_connection_fatal(Seat *seat, const char *message) {} diff --git a/windows/plink.c b/windows/plink.c index 5bb001be..1587cbd3 100644 --- a/windows/plink.c +++ b/windows/plink.c @@ -85,6 +85,7 @@ static const SeatVtable plink_seat_vt = { .eof = plink_eof, .sent = nullseat_sent, .get_userpass_input = plink_get_userpass_input, + .notify_session_started = nullseat_notify_session_started, .notify_remote_exit = nullseat_notify_remote_exit, .notify_remote_disconnect = nullseat_notify_remote_disconnect, .connection_fatal = console_connection_fatal, diff --git a/windows/window.c b/windows/window.c index 9a273d83..6c288600 100644 --- a/windows/window.c +++ b/windows/window.c @@ -333,6 +333,7 @@ static const SeatVtable win_seat_vt = { .eof = win_seat_eof, .sent = nullseat_sent, .get_userpass_input = win_seat_get_userpass_input, + .notify_session_started = nullseat_notify_session_started, .notify_remote_exit = win_seat_notify_remote_exit, .notify_remote_disconnect = nullseat_notify_remote_disconnect, .connection_fatal = win_seat_connection_fatal, -- cgit v1.2.3 From 8df3ad6316ccbd8600ede97b72a02daea3543fe7 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 12 Sep 2021 15:10:35 +0100 Subject: testback.c: remove emacs magic comment. It looks as if Ben put that in when he originally wrote the file, since it refers to a C file style called 'simon', which isn't what I call my preferred style in _my_ Emacs preferences, but might plausibly be what he called it in his. It causes an annoying error every time I load the source file into Emacs, because it refers to a nonexistent style name. And surely it will do the same to almost all other Emacs users. Get rid of it. --- otherbackends/testback.c | 8 -------- 1 file changed, 8 deletions(-) diff --git a/otherbackends/testback.c b/otherbackends/testback.c index 4ff6fa16..5f319722 100644 --- a/otherbackends/testback.c +++ b/otherbackends/testback.c @@ -211,11 +211,3 @@ static int null_cfg_info(Backend *be) { return 0; } - - -/* - * Emacs magic: - * Local Variables: - * c-file-style: "simon" - * End: - */ -- cgit v1.2.3 From 18cac59b433a45520dee290c683035b1fffd0d24 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 13 Sep 2021 11:57:21 +0100 Subject: split_into_argv.c: tidy up large comment. I just happened to notice that just below my huge comment explaining the two command-line splitting policies, there's a smaller one that refers to it as '(see large comment below)'. It's not below - it's above! That was because the older parts of that comment had previously been inside split_into_argv(), until I moved the explanation further up the file to the top level. Another consequence of that was that the older section of the comment was wrapped to a strangely narrow line width, because it had previously been indented further right. Folded the two comments together, and rewrapped the narrow paragraphs. --- windows/utils/split_into_argv.c | 75 ++++++++++++++++++++++------------------- 1 file changed, 40 insertions(+), 35 deletions(-) diff --git a/windows/utils/split_into_argv.c b/windows/utils/split_into_argv.c index b8ced4c4..c9f9e184 100644 --- a/windows/utils/split_into_argv.c +++ b/windows/utils/split_into_argv.c @@ -51,6 +51,9 @@ * or more backslashes precedes two or more double quotes, starting * inside a double-quoted string. * + * Modern Visual Studio (as of 2021) + * --------------------------------- + * * I investigated this in an ordinary CLI program, using the * toolchain's crt0 to split a command line of the form * @@ -87,6 +90,9 @@ * either opens or closes a quoted string, and if it closes one, it * generates a literal " as a side effect. * + * Older Visual Studio + * ------------------- + * * But here's the corresponding table from the older Visual Studio 7: * * backslashes @@ -107,48 +113,47 @@ * 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 * - * 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. + * 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: + * 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. + * - 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: + * - 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. + * * _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. - */ - -/* - * We select between two behaviours depending on the version of Visual - * Studio (see large comment below). I don't know exactly when the bug - * fix happened, but I know that VS7 had the odd mod-3 behaviour. + * 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. + * + * I don't know exactly when the bug fix happened, but I know that VS7 + * had the odd mod-3 behaviour. So the #if below will ensure that + * modern (2015 onwards) versions of VS use the new more sensible + * behaviour, and VS7 uses the old one. Things in between may be + * wrong; if anyone cares, patches to change the cutoff version in + * this #if are welcome. */ #if _MSC_VER < 1400 #define MOD3 1 -- cgit v1.2.3 From 6b1154cc5b90181b7bd6c493bbb01d356b993077 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 13 Sep 2021 10:14:33 +0100 Subject: Remove ldisc.h. It only had to be a header file because ldisc.c and ldiscucs.c had to share the structure definition. But ldiscucs.c vanished a couple of years ago in commit 71e42b04a57479f, so it's now fine to make the Ldisc structure definition local to ldisc.c itself, the way it should be. --- ldisc.c | 17 ++++++++++++++++- ldisc.h | 27 --------------------------- 2 files changed, 16 insertions(+), 28 deletions(-) delete mode 100644 ldisc.h diff --git a/ldisc.c b/ldisc.c index f097c040..e5e1449d 100644 --- a/ldisc.c +++ b/ldisc.c @@ -11,7 +11,22 @@ #include "putty.h" #include "terminal.h" -#include "ldisc.h" + +struct Ldisc_tag { + Terminal *term; + Backend *backend; + Seat *seat; + + /* + * Values cached out of conf. + */ + bool telnet_keyboard, telnet_newline; + int protocol, localecho, localedit; + + char *buf; + size_t buflen, bufsiz; + bool quotenext; +}; #define ECHOING (ldisc->localecho == FORCE_ON || \ (ldisc->localecho == AUTO && \ diff --git a/ldisc.h b/ldisc.h deleted file mode 100644 index 770b4b05..00000000 --- a/ldisc.h +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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 - -struct Ldisc_tag { - Terminal *term; - Backend *backend; - Seat *seat; - - /* - * Values cached out of conf. - */ - bool telnet_keyboard, telnet_newline; - int protocol, localecho, localedit; - - char *buf; - size_t buflen, bufsiz; - bool quotenext; -}; - -#endif /* PUTTY_LDISC_H */ -- cgit v1.2.3 From 0b099b6a6fb5725a1cb7cda013697bf7193cc81d Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 13 Sep 2021 12:00:01 +0100 Subject: Non-SSH network backends: handle PLUGLOG_CONNECT_SUCCESS. All four of the other network-protocol backends (Raw, Telnet, rlogin and SUPDUP) now have a 'socket_connected' flag, which starts off false and is set to true when (if) their Socket signals that the connection attempt has succeeded. This field is used to tell backend_socket_log whether the session has started yet (hence, whether it should still be logging messages from the proxy). This replaces various ad-hoc answers to that question in each backend, which were the best I could do when sockets didn't notify connection success. Now they do, we can do it properly. Also, the new flag controls the answer to each backend's sendok() method, which makes them all satisfy a new policy rule: no backend shall return true from sendok() while its network connection attempt is still ongoing. (Rationale: the network connection attempt may in future involve a proxy implementation interacting with the user via the terminal, and it can't do that if the backend is already consuming all the terminal input.) --- otherbackends/raw.c | 16 ++++++++-------- otherbackends/rlogin.c | 10 +++++++--- otherbackends/supdup.c | 9 +++++++-- otherbackends/telnet.c | 13 +++++++------ putty.h | 9 ++++++++- 5 files changed, 37 insertions(+), 20 deletions(-) diff --git a/otherbackends/raw.c b/otherbackends/raw.c index 2f099ce3..9f1655d8 100644 --- a/otherbackends/raw.c +++ b/otherbackends/raw.c @@ -17,7 +17,7 @@ struct Raw { size_t bufsize; Seat *seat; LogContext *logctx; - bool sent_console_eof, sent_socket_eof, session_started; + bool sent_console_eof, sent_socket_eof, socket_connected; Conf *conf; @@ -37,8 +37,10 @@ static void raw_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, const char *error_msg, int error_code) { Raw *raw = container_of(plug, Raw, plug); - backend_socket_log(raw->seat, raw->logctx, type, addr, port, - error_msg, error_code, raw->conf, raw->session_started); + backend_socket_log(raw->seat, raw->logctx, type, addr, port, error_msg, + error_code, raw->conf, raw->socket_connected); + if (type == PLUGLOG_CONNECT_SUCCESS) + raw->socket_connected = true; } static void raw_check_close(Raw *raw) @@ -95,9 +97,6 @@ static void raw_receive(Plug *plug, int urgent, const char *data, size_t len) { Raw *raw = container_of(plug, Raw, plug); c_write(raw, data, len); - /* We count 'session start', for proxy logging purposes, as being - * when data is received from the network and printed. */ - raw->session_started = true; } static void raw_sent(Plug *plug, size_t bufsize) @@ -144,7 +143,7 @@ static char *raw_init(const BackendVtable *vt, Seat *seat, *backend_handle = &raw->backend; raw->sent_console_eof = raw->sent_socket_eof = false; raw->bufsize = 0; - raw->session_started = false; + raw->socket_connected = false; raw->conf = conf_copy(conf); raw->seat = seat; @@ -267,7 +266,8 @@ static bool raw_connected(Backend *be) static bool raw_sendok(Backend *be) { - return true; + Raw *raw = container_of(be, Raw, backend); + return raw->socket_connected; } static void raw_unthrottle(Backend *be, size_t backlog) diff --git a/otherbackends/rlogin.c b/otherbackends/rlogin.c index d0da322a..555d91fb 100644 --- a/otherbackends/rlogin.c +++ b/otherbackends/rlogin.c @@ -16,6 +16,7 @@ struct Rlogin { Socket *s; bool closed_on_socket_error; int bufsize; + bool socket_connected; bool firstbyte; bool cansize; int term_width, term_height; @@ -43,7 +44,9 @@ static void rlogin_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, Rlogin *rlogin = container_of(plug, Rlogin, plug); backend_socket_log(rlogin->seat, rlogin->logctx, type, addr, port, error_msg, error_code, - rlogin->conf, !rlogin->firstbyte); + rlogin->conf, rlogin->socket_connected); + if (type == PLUGLOG_CONNECT_SUCCESS) + rlogin->socket_connected = true; } static void rlogin_closing(Plug *plug, const char *error_msg, int error_code, @@ -176,6 +179,7 @@ static char *rlogin_init(const BackendVtable *vt, Seat *seat, rlogin->logctx = logctx; rlogin->term_width = conf_get_int(conf, CONF_width); rlogin->term_height = conf_get_int(conf, CONF_height); + rlogin->socket_connected = false; rlogin->firstbyte = true; rlogin->cansize = false; rlogin->prompt = NULL; @@ -364,8 +368,8 @@ static bool rlogin_connected(Backend *be) static bool rlogin_sendok(Backend *be) { - /* Rlogin *rlogin = container_of(be, Rlogin, backend); */ - return true; + Rlogin *rlogin = container_of(be, Rlogin, backend); + return rlogin->socket_connected; } static void rlogin_unthrottle(Backend *be, size_t backlog) diff --git a/otherbackends/supdup.c b/otherbackends/supdup.c index 7111c9a6..69e931c5 100644 --- a/otherbackends/supdup.c +++ b/otherbackends/supdup.c @@ -66,6 +66,7 @@ typedef struct supdup_tag Supdup; struct supdup_tag { Socket *s; + bool socket_connected; bool closed_on_socket_error; Seat *seat; @@ -561,7 +562,9 @@ static void supdup_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, Supdup *supdup = container_of(plug, Supdup, plug); backend_socket_log(supdup->seat, supdup->logctx, type, addr, port, error_msg, error_code, - supdup->conf, supdup->state != CONNECTING); + supdup->conf, supdup->socket_connected); + if (type == PLUGLOG_CONNECT_SUCCESS) + supdup->socket_connected = true; } static void supdup_closing(Plug *plug, const char *error_msg, int error_code, @@ -662,6 +665,7 @@ static char *supdup_init(const BackendVtable *x, Seat *seat, supdup->logctx = logctx; supdup->conf = conf_copy(conf); supdup->s = NULL; + supdup->socket_connected = false; supdup->closed_on_socket_error = false; supdup->seat = seat; supdup->term_width = conf_get_int(supdup->conf, CONF_width); @@ -861,7 +865,8 @@ static bool supdup_connected(Backend *be) static bool supdup_sendok(Backend *be) { - return 1; + Supdup *supdup = container_of(be, Supdup, backend); + return supdup->socket_connected; } static void supdup_unthrottle(Backend *be, size_t backlog) diff --git a/otherbackends/telnet.c b/otherbackends/telnet.c index 15afd82e..90e5026e 100644 --- a/otherbackends/telnet.c +++ b/otherbackends/telnet.c @@ -171,6 +171,7 @@ static const struct Opt *const opts[] = { typedef struct Telnet Telnet; struct Telnet { Socket *s; + bool socket_connected; bool closed_on_socket_error; Seat *seat; @@ -186,7 +187,6 @@ struct Telnet { bool in_synch; int sb_opt; strbuf *sb_buf; - bool session_started; enum { TOP_LEVEL, SEENIAC, SEENWILL, SEENWONT, SEENDO, SEENDONT, @@ -619,7 +619,9 @@ static void telnet_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, Telnet *telnet = container_of(plug, Telnet, plug); backend_socket_log(telnet->seat, telnet->logctx, type, addr, port, error_msg, error_code, telnet->conf, - telnet->session_started); + telnet->socket_connected); + if (type == PLUGLOG_CONNECT_SUCCESS) + telnet->socket_connected = true; } static void telnet_closing(Plug *plug, const char *error_msg, int error_code, @@ -654,7 +656,6 @@ static void telnet_receive( Telnet *telnet = container_of(plug, Telnet, plug); if (urgent) telnet->in_synch = true; - telnet->session_started = true; do_telnet_read(telnet, data, len); } @@ -699,6 +700,7 @@ static char *telnet_init(const BackendVtable *vt, Seat *seat, telnet->backend.vt = vt; telnet->conf = conf_copy(conf); telnet->s = NULL; + telnet->socket_connected = false; telnet->closed_on_socket_error = false; telnet->echoing = true; telnet->editing = true; @@ -711,7 +713,6 @@ static char *telnet_init(const BackendVtable *vt, Seat *seat, telnet->state = TOP_LEVEL; telnet->ldisc = NULL; telnet->pinger = NULL; - telnet->session_started = true; *backend_handle = &telnet->backend; /* @@ -1001,8 +1002,8 @@ static bool telnet_connected(Backend *be) static bool telnet_sendok(Backend *be) { - /* Telnet *telnet = container_of(be, Telnet, backend); */ - return true; + Telnet *telnet = container_of(be, Telnet, backend); + return telnet->socket_connected; } static void telnet_unthrottle(Backend *be, size_t backlog) diff --git a/putty.h b/putty.h index d622574d..2042d43a 100644 --- a/putty.h +++ b/putty.h @@ -648,7 +648,14 @@ struct BackendVtable { int (*exitcode) (Backend *be); /* If back->sendok() returns false, the backend doesn't currently * want input data, so the frontend should avoid acquiring any if - * possible (passing back-pressure on to its sender). */ + * possible (passing back-pressure on to its sender). + * + * Policy rule: no backend shall return true from sendok() while + * its network connection attempt is still ongoing. This ensures + * that if making the network connection involves a proxy type + * which wants to interact with the user via the terminal, the + * proxy implementation and the backend itself won't fight over + * who gets the terminal input. */ bool (*sendok) (Backend *be); bool (*ldisc_option_state) (Backend *be, int); void (*provide_ldisc) (Backend *be, Ldisc *ldisc); -- cgit v1.2.3 From 64f192093a0eaa94066042936ce637259fd6d047 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 13 Sep 2021 13:13:07 +0100 Subject: Telnet: remove tabs from Event Log entries. While re-testing the other backends, I noticed that they work really badly in the GTK event log, which apparently interprets tabs as meaning 'next table column', and assumes that any line not containing a tab must be entirely in the leftmost column. So all the Telnet negotiations are miles off to the right, beyond the longest other line in the entire log. Replaced with a bit more verbiage. --- otherbackends/telnet.c | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/otherbackends/telnet.c b/otherbackends/telnet.c index 90e5026e..ec65d400 100644 --- a/otherbackends/telnet.c +++ b/otherbackends/telnet.c @@ -218,7 +218,7 @@ static void log_option(Telnet *telnet, const char *sender, int cmd, int option) * trigraph - a double question mark followed by > maps to a * closing brace character! */ - logeventf(telnet->logctx, "%s:\t%s %s", sender, + logeventf(telnet->logctx, "%s negotiation: %s %s", sender, (cmd == WILL ? "WILL" : cmd == WONT ? "WONT" : cmd == DO ? "DO" : cmd == DONT ? "DONT" : ""), telopt(option)); @@ -374,11 +374,13 @@ static void process_subneg(Telnet *telnet) b[n] = IAC; b[n + 1] = SE; telnet->bufsize = sk_write(telnet->s, b, n + 2); - logevent(telnet->logctx, "server:\tSB TSPEED SEND"); - logeventf(telnet->logctx, "client:\tSB TSPEED IS %s", termspeed); + logevent(telnet->logctx, "server subnegotiation: SB TSPEED SEND"); + logeventf(telnet->logctx, + "client subnegotiation: SB TSPEED IS %s", termspeed); sfree(b); } else - logevent(telnet->logctx, "server:\tSB TSPEED "); + logevent(telnet->logctx, + "server subnegotiation: SB TSPEED "); break; case TELOPT_TTYPE: if (telnet->sb_buf->len == 1 && telnet->sb_buf->u[0] == TELQUAL_SEND) { @@ -396,11 +398,14 @@ static void process_subneg(Telnet *telnet) b[n + 5] = SE; telnet->bufsize = sk_write(telnet->s, b, n + 6); b[n + 4] = 0; - logevent(telnet->logctx, "server:\tSB TTYPE SEND"); - logeventf(telnet->logctx, "client:\tSB TTYPE IS %s", b + 4); + logevent(telnet->logctx, + "server subnegotiation: SB TTYPE SEND"); + logeventf(telnet->logctx, + "client subnegotiation: SB TTYPE IS %s", b + 4); sfree(b); } else - logevent(telnet->logctx, "server:\tSB TTYPE \r\n"); + logevent(telnet->logctx, + "server subnegotiation: SB TTYPE \r\n"); break; case TELOPT_OLD_ENVIRON: case TELOPT_NEW_ENVIRON: @@ -408,7 +413,7 @@ static void process_subneg(Telnet *telnet) q = p + telnet->sb_buf->len; if (p < q && *p == TELQUAL_SEND) { p++; - logeventf(telnet->logctx, "server:\tSB %s SEND", + logeventf(telnet->logctx, "server subnegotiation: SB %s SEND", telopt(telnet->sb_opt)); if (telnet->sb_opt == TELOPT_OLD_ENVIRON) { if (conf_get_bool(telnet->conf, CONF_rfc_environ)) { @@ -482,20 +487,21 @@ static void process_subneg(Telnet *telnet) b[n++] = SE; telnet->bufsize = sk_write(telnet->s, b, n); if (n == 6) { - logeventf(telnet->logctx, "client:\tSB %s IS ", + logeventf(telnet->logctx, + "client subnegotiation: SB %s IS ", telopt(telnet->sb_opt)); } else { - logeventf(telnet->logctx, "client:\tSB %s IS:", + logeventf(telnet->logctx, "client subnegotiation: SB %s IS:", telopt(telnet->sb_opt)); for (eval = conf_get_str_strs(telnet->conf, CONF_environmt, NULL, &ekey); eval != NULL; eval = conf_get_str_strs(telnet->conf, CONF_environmt, ekey, &ekey)) { - logeventf(telnet->logctx, "\t%s=%s", ekey, eval); + logeventf(telnet->logctx, " %s=%s", ekey, eval); } if (user) - logeventf(telnet->logctx, "\tUSER=%s", user); + logeventf(telnet->logctx, " USER=%s", user); } sfree(b); sfree(user); @@ -882,7 +888,7 @@ static void telnet_size(Backend *be, int width, int height) b[n++] = IAC; b[n++] = SE; telnet->bufsize = sk_write(telnet->s, b, n); - logeventf(telnet->logctx, "client:\tSB NAWS %d,%d", + logeventf(telnet->logctx, "client subnegotiation: SB NAWS %d,%d", telnet->term_width, telnet->term_height); } -- cgit v1.2.3 From 6defb2b3a0a124bf69d0d31478e42a106614fd87 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 13 Sep 2021 14:18:12 +0100 Subject: fd-socket: fix use after free on socket close. The call to plug_closing very likely destroys the FdSocket entirely, so we shouldn't wait until after that to clean up its input fd via lots of dereferences. --- unix/fd-socket.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/unix/fd-socket.c b/unix/fd-socket.c index 7697d995..6c468e6b 100644 --- a/unix/fd-socket.c +++ b/unix/fd-socket.c @@ -260,15 +260,16 @@ static void fdsocket_select_result_input(int fd, int event) if (retd > 0) { plug_receive(fds->plug, 0, buf, retd); } else { + del234(fdsocket_by_infd, fds); + uxsel_del(fds->infd); + close(fds->infd); + fds->infd = -1; + if (retd < 0) { plug_closing(fds->plug, strerror(errno), errno, 0); } else { plug_closing(fds->plug, NULL, 0, 0); } - del234(fdsocket_by_infd, fds); - uxsel_del(fds->infd); - close(fds->infd); - fds->infd = -1; } } -- cgit v1.2.3 From 8f5e9a4f8dc796aee8789847c2a946d1065a7f39 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 13 Sep 2021 14:28:47 +0100 Subject: Send PLUGLOG_CONNECT_SUCCESS in proxied socket types. Now the non-SSH backends critically depend on it, it's important not to forget to send it, for any socket type that's going to be used for any of those backends. But ProxySocket, and the Unix and Windows 'socket' types wrapping pipes to local subprocesses, were not doing so. Some of these socket types don't have a SockAddr available to represent the destination host. (Sometimes the concept isn't even meaningful). Therefore, I've also expanded the semantics of PLUGLOG_CONNECT_SUCCESS so that the addr parameter is allowed to be NULL, and invented a noncommittal fallback version of the log message in that situation. --- be_misc.c | 5 ++++- network.h | 7 +++++-- proxy.c | 2 ++ unix/fd-socket.c | 8 ++++++++ windows/handle-socket.c | 8 ++++++++ 5 files changed, 27 insertions(+), 3 deletions(-) diff --git a/be_misc.c b/be_misc.c index 7f50a643..de15a34e 100644 --- a/be_misc.c +++ b/be_misc.c @@ -29,7 +29,10 @@ void backend_socket_log(Seat *seat, LogContext *logctx, msg = dupprintf("Failed to connect to %s: %s", addrbuf, error_msg); break; case PLUGLOG_CONNECT_SUCCESS: - sk_getaddr(addr, addrbuf, lenof(addrbuf)); + if (addr) + sk_getaddr(addr, addrbuf, lenof(addrbuf)); + else /* fallback if address unavailable */ + sprintf(addrbuf, "remote host"); msg = dupprintf("Connected to %s", addrbuf); break; case PLUGLOG_PROXY_MSG: { diff --git a/network.h b/network.h index 92662ac7..05b71279 100644 --- a/network.h +++ b/network.h @@ -67,8 +67,11 @@ struct PlugVtable { * addresses to fall back to. When it _is_ fatal, the closing() * function will be called. * - * - PLUGLOG_CONNECT_SUCCESS means we have succeeded in - * connecting to address `addr'. + * - PLUGLOG_CONNECT_SUCCESS means we have succeeded in making a + * connection. `addr' gives the address we connected to, if + * available. (But sometimes, in cases of complicated proxy + * setups, it might not be available, so receivers of this log + * event should be prepared to deal with addr==NULL.) * * - PLUGLOG_PROXY_MSG means that error_msg contains a line of * logging information from whatever the connection is being diff --git a/proxy.c b/proxy.c index 1a737cae..4fa8f16f 100644 --- a/proxy.c +++ b/proxy.c @@ -28,6 +28,8 @@ void proxy_activate (ProxySocket *p) p->state = PROXY_STATE_ACTIVE; + plug_log(p->plug, PLUGLOG_CONNECT_SUCCESS, NULL, 0, NULL, 0); + /* we want to ignore new receive events until we have sent * all of our buffered receive data. */ diff --git a/unix/fd-socket.c b/unix/fd-socket.c index 6c468e6b..afd37957 100644 --- a/unix/fd-socket.c +++ b/unix/fd-socket.c @@ -315,6 +315,12 @@ static const SocketVtable FdSocket_sockvt = { .peer_info = NULL, }; +static void fdsocket_connect_success_callback(void *ctx) +{ + FdSocket *fds = (FdSocket *)ctx; + plug_log(fds->plug, PLUGLOG_CONNECT_SUCCESS, NULL, 0, NULL, 0); +} + Socket *make_fd_socket(int infd, int outfd, int inerrfd, Plug *plug) { FdSocket *fds; @@ -354,5 +360,7 @@ Socket *make_fd_socket(int infd, int outfd, int inerrfd, Plug *plug) uxsel_set(fds->inerrfd, SELECT_R, fdsocket_select_result_input_error); } + queue_toplevel_callback(fdsocket_connect_success_callback, fds); + return &fds->sock; } diff --git a/windows/handle-socket.c b/windows/handle-socket.c index 93bb500a..77b9067c 100644 --- a/windows/handle-socket.c +++ b/windows/handle-socket.c @@ -314,6 +314,12 @@ static const SocketVtable HandleSocket_sockvt = { .peer_info = sk_handle_peer_info, }; +static void sk_handle_connect_success_callback(void *ctx) +{ + HandleSocket *hs = (HandleSocket *)ctx; + plug_log(hs->plug, PLUGLOG_CONNECT_SUCCESS, NULL, 0, NULL, 0); +} + Socket *make_handle_socket(HANDLE send_H, HANDLE recv_H, HANDLE stderr_H, Plug *plug, bool overlapped) { @@ -339,5 +345,7 @@ Socket *make_handle_socket(HANDLE send_H, HANDLE recv_H, HANDLE stderr_H, hs->defer_close = hs->deferred_close = false; + queue_toplevel_callback(sk_handle_connect_success_callback, hs); + return &hs->sock; } -- cgit v1.2.3 From a4b8ff911b4abfa1c2247c91d1507687459bab68 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 13 Sep 2021 14:34:46 +0100 Subject: FdSocket, HandleSocket: store a notional peer address. In the case where these socket types are constructed because of a local proxy command, we do actually have a SockAddr representing the logical host we were trying to make a connection to. So we might as well store it in the socket implementation, and then we can include it in the PLUGLOG_CONNECT_SUCCESS call to make the log message more informative. --- unix/fd-socket.c | 13 +++++++++++-- unix/local-proxy.c | 5 +---- unix/platform.h | 3 ++- unix/psusan.c | 2 +- unix/uppity.c | 2 +- windows/handle-socket.c | 12 ++++++++++-- windows/local-proxy.c | 5 +---- windows/named-pipe-client.c | 3 ++- windows/named-pipe-server.c | 2 +- windows/platform.h | 3 ++- 10 files changed, 32 insertions(+), 18 deletions(-) diff --git a/unix/fd-socket.c b/unix/fd-socket.c index afd37957..d352d8e4 100644 --- a/unix/fd-socket.c +++ b/unix/fd-socket.c @@ -23,6 +23,8 @@ typedef struct FdSocket { int pending_error; + SockAddr *addr; + int port; Plug *plug; Socket sock; @@ -134,6 +136,9 @@ static void fdsocket_close(Socket *s) bufchain_clear(&fds->pending_input_data); bufchain_clear(&fds->pending_output_data); + if (fds->addr) + sk_addr_free(fds->addr); + delete_callbacks_for_context(fds); sfree(fds); @@ -318,15 +323,19 @@ static const SocketVtable FdSocket_sockvt = { static void fdsocket_connect_success_callback(void *ctx) { FdSocket *fds = (FdSocket *)ctx; - plug_log(fds->plug, PLUGLOG_CONNECT_SUCCESS, NULL, 0, NULL, 0); + plug_log(fds->plug, PLUGLOG_CONNECT_SUCCESS, fds->addr, fds->port, + NULL, 0); } -Socket *make_fd_socket(int infd, int outfd, int inerrfd, Plug *plug) +Socket *make_fd_socket(int infd, int outfd, int inerrfd, + SockAddr *addr, int port, Plug *plug) { FdSocket *fds; fds = snew(FdSocket); fds->sock.vt = &FdSocket_sockvt; + fds->addr = addr; + fds->port = port; fds->plug = plug; fds->outgoingeof = EOF_NO; fds->pending_error = 0; diff --git a/unix/local-proxy.c b/unix/local-proxy.c index 0a637bd9..c8663928 100644 --- a/unix/local-proxy.c +++ b/unix/local-proxy.c @@ -98,8 +98,5 @@ Socket *platform_new_connection(SockAddr *addr, const char *hostname, inerrfd = -1; } - /* We are responsible for this and don't need it any more */ - sk_addr_free(addr); - - return make_fd_socket(infd, outfd, inerrfd, plug); + return make_fd_socket(infd, outfd, inerrfd, addr, port, plug); } diff --git a/unix/platform.h b/unix/platform.h index 6d88d84b..97762f3f 100644 --- a/unix/platform.h +++ b/unix/platform.h @@ -380,7 +380,8 @@ bool so_peercred(int fd, int *pid, int *uid, int *gid); /* * uxfdsock.c. */ -Socket *make_fd_socket(int infd, int outfd, int inerrfd, Plug *plug); +Socket *make_fd_socket(int infd, int outfd, int inerrfd, + SockAddr *addr, int port, Plug *plug); /* * Default font setting, which can vary depending on NOT_X_WINDOWS. diff --git a/unix/psusan.c b/unix/psusan.c index a9312c40..2017685b 100644 --- a/unix/psusan.c +++ b/unix/psusan.c @@ -411,7 +411,7 @@ int main(int argc, char **argv) } else { struct server_instance *inst; Plug *plug = server_conn_plug(&scfg, &inst); - ssh_server_start(plug, make_fd_socket(0, 1, -1, plug)); + ssh_server_start(plug, make_fd_socket(0, 1, -1, NULL, 0, plug)); log_to_stderr(inst->id, "running directly on stdio"); } diff --git a/unix/uppity.c b/unix/uppity.c index 4189c7e0..8d731492 100644 --- a/unix/uppity.c +++ b/unix/uppity.c @@ -869,7 +869,7 @@ int main(int argc, char **argv) } else { struct server_instance *inst; Plug *plug = server_conn_plug(&scfg, &inst); - ssh_server_start(plug, make_fd_socket(0, 1, -1, plug)); + ssh_server_start(plug, make_fd_socket(0, 1, -1, NULL, 0, plug)); log_to_stderr(inst->id, "speaking SSH on stdio"); } diff --git a/windows/handle-socket.c b/windows/handle-socket.c index 77b9067c..dbc4e0de 100644 --- a/windows/handle-socket.c +++ b/windows/handle-socket.c @@ -40,6 +40,8 @@ typedef struct HandleSocket { char *error; + SockAddr *addr; + int port; Plug *plug; Socket sock; @@ -128,6 +130,9 @@ static void sk_handle_close(Socket *s) CloseHandle(hs->recv_H); bufchain_clear(&hs->inputdata); + if (hs->addr) + sk_addr_free(hs->addr); + delete_callbacks_for_context(hs); sfree(hs); @@ -317,17 +322,20 @@ static const SocketVtable HandleSocket_sockvt = { static void sk_handle_connect_success_callback(void *ctx) { HandleSocket *hs = (HandleSocket *)ctx; - plug_log(hs->plug, PLUGLOG_CONNECT_SUCCESS, NULL, 0, NULL, 0); + plug_log(hs->plug, PLUGLOG_CONNECT_SUCCESS, hs->addr, hs->port, NULL, 0); } Socket *make_handle_socket(HANDLE send_H, HANDLE recv_H, HANDLE stderr_H, - Plug *plug, bool overlapped) + SockAddr *addr, int port, Plug *plug, + bool overlapped) { HandleSocket *hs; int flags = (overlapped ? HANDLE_FLAG_OVERLAPPED : 0); hs = snew(HandleSocket); hs->sock.vt = &HandleSocket_sockvt; + hs->addr = addr; + hs->port = port; hs->plug = plug; hs->error = NULL; hs->frozen = UNFROZEN; diff --git a/windows/local-proxy.c b/windows/local-proxy.c index 94e31fcb..ddbdb97c 100644 --- a/windows/local-proxy.c +++ b/windows/local-proxy.c @@ -30,9 +30,6 @@ Socket *platform_new_connection(SockAddr *addr, const char *hostname, cmd = format_telnet_command(addr, port, conf); - /* We are responsible for this and don't need it any more */ - sk_addr_free(addr); - { char *msg = dupprintf("Starting local proxy command: %s", cmd); plug_log(plug, PLUGLOG_PROXY_MSG, NULL, 0, msg, 0); @@ -103,5 +100,5 @@ Socket *platform_new_connection(SockAddr *addr, const char *hostname, CloseHandle(cmd_err_to_us); return make_handle_socket(us_to_cmd, us_from_cmd, us_from_cmd_err, - plug, false); + addr, port, plug, false); } diff --git a/windows/named-pipe-client.c b/windows/named-pipe-client.c index bd43998a..b827dd0d 100644 --- a/windows/named-pipe-client.c +++ b/windows/named-pipe-client.c @@ -90,5 +90,6 @@ Socket *new_named_pipe_client(const char *pipename, Plug *plug) if (pipehandle == INVALID_HANDLE_VALUE) return new_error_socket_consume_string(plug, err); else - return make_handle_socket(pipehandle, pipehandle, NULL, plug, true); + return make_handle_socket(pipehandle, pipehandle, NULL, NULL, 0, + plug, true); } diff --git a/windows/named-pipe-server.c b/windows/named-pipe-server.c index a272cdad..a9271c16 100644 --- a/windows/named-pipe-server.c +++ b/windows/named-pipe-server.c @@ -112,7 +112,7 @@ static Socket *named_pipe_accept(accept_ctx_t ctx, Plug *plug) { HANDLE conn = (HANDLE)ctx.p; - return make_handle_socket(conn, conn, NULL, plug, true); + return make_handle_socket(conn, conn, NULL, NULL, 0, plug, true); } static void named_pipe_accept_loop(NamedPipeServerSocket *ps, diff --git a/windows/platform.h b/windows/platform.h index f549896d..ba07ea1a 100644 --- a/windows/platform.h +++ b/windows/platform.h @@ -341,7 +341,8 @@ extern HANDLE winselcli_event; * Network-subsystem-related functions provided in other Windows modules. */ Socket *make_handle_socket(HANDLE send_H, HANDLE recv_H, HANDLE stderr_H, - Plug *plug, bool overlapped); /* winhsock */ + SockAddr *addr, int port, Plug *plug, + bool overlapped); /* winhsock */ Socket *new_named_pipe_client(const char *pipename, Plug *plug); /* winnpc */ Socket *new_named_pipe_listener(const char *pipename, Plug *plug); /* winnps */ -- cgit v1.2.3 From a08f953bd6c1a3d19e125c7958243b64a0136823 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 13 Sep 2021 17:17:20 +0100 Subject: sshproxy: share the caller's LogPolicy. Now new_connection() takes an optional LogPolicy * argument, and passes it on to the SshProxy setup. This means that SshProxy's implementation of the LogPolicy trait can answer queries like askappend() and logging_error() by passing them on to the same LogPolicy used by the main backend. Not all callers of new_connection have a LogPolicy, so we still have to fall back to the previous conservative default behaviour if SshProxy doesn't have a LogPolicy it can ask. The main backend implementations didn't _quite_ have access to a LogPolicy already, but they do have a LogContext, which has a LogPolicy vtable pointer inside it; so I've added a query function log_get_policy() which allows them to extract that pointer to pass to new_connection. This is the first step of fixing the non-interactivity limitations of SshProxy. But it's also the easiest step: the next ones will be more involved. --- logging.c | 5 ++++ network.h | 20 +++++++++---- noproxy.c | 2 +- nosshproxy.c | 3 +- otherbackends/raw.c | 3 +- otherbackends/rlogin.c | 3 +- otherbackends/supdup.c | 3 +- otherbackends/telnet.c | 3 +- proxy.c | 4 +-- putty.h | 1 + ssh/portfwd.c | 3 +- ssh/ssh.c | 3 +- ssh/x11fwd.c | 2 +- sshproxy.c | 81 ++++++++++++++++++++++++++++++-------------------- unix/pageant.c | 3 +- unix/sharing.c | 2 +- 16 files changed, 90 insertions(+), 51 deletions(-) diff --git a/logging.c b/logging.c index 31cbccfb..e065f1a4 100644 --- a/logging.c +++ b/logging.c @@ -81,6 +81,11 @@ void logflush(LogContext *ctx) fflush(ctx->lgfp); } +LogPolicy *log_get_policy(LogContext *ctx) +{ + return ctx->lp; +} + static void logfopen_callback(void *vctx, int mode) { LogContext *ctx = (LogContext *)vctx; diff --git a/network.h b/network.h index 05b71279..067775e4 100644 --- a/network.h +++ b/network.h @@ -110,13 +110,22 @@ struct PlugVtable { */ }; -/* proxy indirection layer */ -/* NB, control of 'addr' is passed via new_connection, which takes - * responsibility for freeing it */ +/* Proxy indirection layer. + * + * Calling new_connection transfers ownership of 'addr': the proxy + * layer is now responsible for freeing it, and the caller shouldn't + * assume it exists any more. + * + * You can optionally pass a LogPolicy to this function, which will be + * passed on in turn to proxy types that can use one (e.g. SSH jump + * host proxy). If you don't have one, all proxy types are required to + * be able to manage without (and will just degrade their logging + * control). + */ Socket *new_connection(SockAddr *addr, const char *hostname, int port, bool privport, bool oobinline, bool nodelay, bool keepalive, - Plug *plug, Conf *conf); + Plug *plug, Conf *conf, LogPolicy *lp); Socket *new_listener(const char *srcaddr, int port, Plug *plug, bool local_host_only, Conf *conf, int addressfamily); SockAddr *name_lookup(const char *host, int port, char **canonicalname, @@ -134,7 +143,8 @@ Socket *platform_new_connection(SockAddr *addr, const char *hostname, Socket *sshproxy_new_connection(SockAddr *addr, const char *hostname, int port, bool privport, bool oobinline, bool nodelay, bool keepalive, - Plug *plug, Conf *conf); + Plug *plug, Conf *conf, + LogPolicy *clientlp); /* socket functions */ diff --git a/noproxy.c b/noproxy.c index 1d372932..6b5832ab 100644 --- a/noproxy.c +++ b/noproxy.c @@ -20,7 +20,7 @@ SockAddr *name_lookup(const char *host, int port, char **canonicalname, Socket *new_connection(SockAddr *addr, const char *hostname, int port, bool privport, bool oobinline, bool nodelay, bool keepalive, - Plug *plug, Conf *conf) + Plug *plug, Conf *conf, LogPolicy *lp) { return sk_new(addr, port, privport, oobinline, nodelay, keepalive, plug); } diff --git a/nosshproxy.c b/nosshproxy.c index e47a860c..cce33231 100644 --- a/nosshproxy.c +++ b/nosshproxy.c @@ -10,7 +10,8 @@ const bool ssh_proxy_supported = false; Socket *sshproxy_new_connection(SockAddr *addr, const char *hostname, int port, bool privport, bool oobinline, bool nodelay, bool keepalive, - Plug *plug, Conf *conf) + Plug *plug, Conf *conf, + LogPolicy *clientlp) { return NULL; } diff --git a/otherbackends/raw.c b/otherbackends/raw.c index 9f1655d8..638e2a50 100644 --- a/otherbackends/raw.c +++ b/otherbackends/raw.c @@ -167,7 +167,8 @@ static char *raw_init(const BackendVtable *vt, Seat *seat, * Open socket. */ raw->s = new_connection(addr, *realhost, port, false, true, nodelay, - keepalive, &raw->plug, conf); + keepalive, &raw->plug, conf, + log_get_policy(logctx)); if ((err = sk_socket_error(raw->s)) != NULL) return dupstr(err); diff --git a/otherbackends/rlogin.c b/otherbackends/rlogin.c index 555d91fb..3748e536 100644 --- a/otherbackends/rlogin.c +++ b/otherbackends/rlogin.c @@ -204,7 +204,8 @@ static char *rlogin_init(const BackendVtable *vt, Seat *seat, * Open socket. */ rlogin->s = new_connection(addr, *realhost, port, true, false, - nodelay, keepalive, &rlogin->plug, conf); + nodelay, keepalive, &rlogin->plug, conf, + log_get_policy(logctx)); if ((err = sk_socket_error(rlogin->s)) != NULL) return dupstr(err); diff --git a/otherbackends/supdup.c b/otherbackends/supdup.c index 69e931c5..cfa1b2ad 100644 --- a/otherbackends/supdup.c +++ b/otherbackends/supdup.c @@ -712,7 +712,8 @@ static char *supdup_init(const BackendVtable *x, Seat *seat, * Open socket. */ supdup->s = new_connection(addr, *realhost, port, false, true, - nodelay, keepalive, &supdup->plug, supdup->conf); + nodelay, keepalive, &supdup->plug, supdup->conf, + log_get_policy(logctx)); if ((err = sk_socket_error(supdup->s)) != NULL) return dupstr(err); diff --git a/otherbackends/telnet.c b/otherbackends/telnet.c index ec65d400..b006305d 100644 --- a/otherbackends/telnet.c +++ b/otherbackends/telnet.c @@ -739,7 +739,8 @@ static char *telnet_init(const BackendVtable *vt, Seat *seat, * Open socket. */ telnet->s = new_connection(addr, *realhost, port, false, true, nodelay, - keepalive, &telnet->plug, telnet->conf); + keepalive, &telnet->plug, telnet->conf, + log_get_policy(logctx)); if ((err = sk_socket_error(telnet->s)) != NULL) return dupstr(err); diff --git a/proxy.c b/proxy.c index 4fa8f16f..770f3d02 100644 --- a/proxy.c +++ b/proxy.c @@ -395,7 +395,7 @@ static const PlugVtable ProxySocket_plugvt = { Socket *new_connection(SockAddr *addr, const char *hostname, int port, bool privport, bool oobinline, bool nodelay, bool keepalive, - Plug *plug, Conf *conf) + Plug *plug, Conf *conf, LogPolicy *lp) { int type = conf_get_int(conf, CONF_proxy_type); @@ -411,7 +411,7 @@ Socket *new_connection(SockAddr *addr, const char *hostname, if (type == PROXY_SSH && (sret = sshproxy_new_connection(addr, hostname, port, privport, oobinline, nodelay, keepalive, - plug, conf)) != NULL) + plug, conf, lp)) != NULL) return sret; if ((sret = platform_new_connection(addr, hostname, port, privport, diff --git a/putty.h b/putty.h index 2042d43a..8538bd52 100644 --- a/putty.h +++ b/putty.h @@ -1962,6 +1962,7 @@ void logfopen(LogContext *logctx); void logfclose(LogContext *logctx); void logtraffic(LogContext *logctx, unsigned char c, int logmode); void logflush(LogContext *logctx); +LogPolicy *log_get_policy(LogContext *logctx); void logevent(LogContext *logctx, const char *event); void logeventf(LogContext *logctx, const char *fmt, ...) PRINTF_LIKE(2, 3); void logeventvf(LogContext *logctx, const char *fmt, va_list ap); diff --git a/ssh/portfwd.c b/ssh/portfwd.c index f96fe31a..a67268a4 100644 --- a/ssh/portfwd.c +++ b/ssh/portfwd.c @@ -1160,7 +1160,8 @@ char *portfwdmgr_connect(PortFwdManager *mgr, Channel **chan_ret, pf->socks_state = SOCKS_NONE; pf->s = new_connection(addr, dummy_realhost, port, - false, true, false, false, &pf->plug, mgr->conf); + false, true, false, false, &pf->plug, mgr->conf, + NULL); sfree(dummy_realhost); if ((err = sk_socket_error(pf->s)) != NULL) { char *err_ret = dupstr(err); diff --git a/ssh/ssh.c b/ssh/ssh.c index aee04db9..8b7dd92c 100644 --- a/ssh/ssh.c +++ b/ssh/ssh.c @@ -789,7 +789,8 @@ static char *connect_to_host( ssh->s = new_connection(addr, *realhost, port, false, true, nodelay, keepalive, - &ssh->plug, ssh->conf); + &ssh->plug, ssh->conf, + log_get_policy(ssh->logctx)); if ((err = sk_socket_error(ssh->s)) != NULL) { ssh->s = NULL; seat_notify_remote_exit(ssh->seat); diff --git a/ssh/x11fwd.c b/ssh/x11fwd.c index 49dca95a..eb4d4584 100644 --- a/ssh/x11fwd.c +++ b/ssh/x11fwd.c @@ -564,7 +564,7 @@ static size_t x11_send( xconn->s = new_connection(sk_addr_dup(xconn->disp->addr), xconn->disp->realhost, xconn->disp->port, false, true, false, false, &xconn->plug, - sshfwd_get_conf(xconn->c)); + sshfwd_get_conf(xconn->c), NULL); if ((err = sk_socket_error(xconn->s)) != NULL) { char *err_message = dupprintf("unable to connect to" " forwarded X server: %s", err); diff --git a/sshproxy.c b/sshproxy.c index feb9b077..46ef9d6c 100644 --- a/sshproxy.c +++ b/sshproxy.c @@ -16,15 +16,15 @@ const bool ssh_proxy_supported = true; /* * TODO for future work: * - * At present, this use of SSH as a proxy is 100% noninteractive. In - * our implementations of the Seat and LogPolicy traits, every method - * that involves interactively prompting the user is implemented by - * pretending the user gave a safe default answer. So the effect is - * very much as if you'd used 'plink -batch' as a proxy subprocess - - * password prompts are cancelled and any dubious host key or crypto - * primitive is unconditionally rejected - except that it all happens - * in-process, making it mildly more convenient to set up, perhaps a - * hair faster, and you get all the Event Log data in one place. + * At present, this use of SSH as a proxy is mostly noninteractive. In + * our implementations of the Seat trait, every method that involves + * interactively prompting the user is implemented by pretending the + * user gave a safe default answer. So the effect is very much as if + * you'd used 'plink -batch' as a proxy subprocess - password prompts + * are cancelled and any dubious host key or crypto primitive is + * unconditionally rejected - except that it all happens in-process, + * making it mildly more convenient to set up, perhaps a hair faster, + * and you get all the Event Log data in one place. * * But the biggest benefit of in-process SSH proxying would be that * the interactive prompts from the sub-SSH can be passed through to @@ -32,24 +32,21 @@ const bool ssh_proxy_supported = true; * both require password authentication, you should be able to type * both password in sequence into the PuTTY terminal window; if you're * running a session of this kind for the first time, you should be - * able to confirm both host keys one after another; if you need to - * store SSH packet logs from both SSH connections, you should be able - * to respond in turn to two askappend() prompts if necessary. And in - * the current state of the code, none of that is yet implemented. + * able to confirm both host keys one after another. In the current + * state of the code, none of that is yet implemented: we're borrowing + * the client LogPolicy for things like askappend(), but not the Seat, + * which is where all the really important stuff lives. * * To fix that, we'd have to start by arranging for this proxy - * implementation to get hold of the 'real' (outer) Seat and LogPolicy - * objects, which probably means that they'd have to be passed to - * new_connection. Then, each method in this file that receives an + * implementation to get hold of the 'real' (outer) Seat object, which + * means passing it to new_connection as we're already doing with + * LogPolicy. Then, each method in this file that receives an * interactive prompt request would handle it by passing it on to the - * outer Seat or LogPolicy, with some kind of tweak that would allow + * client Seat if present, with some kind of tweak that would allow * the end user to see clearly that the prompt had come from the proxy - * SSH connection rather than the primary one. - * - * One problem here is that not all uses of new_connection _have_ a - * Seat or a LogPolicy available. So we'd also have to check if those - * pointers are NULL, and if so, fall back to the existing behaviour - * of behaving as if in batch mode. + * SSH connection rather than the primary one. If no client Seat was + * present, we'd have no choice but to fall back to the existing + * behaviour of behaving as if in batch mode. */ typedef struct SshProxy { @@ -57,6 +54,7 @@ typedef struct SshProxy { Conf *conf; LogContext *logctx; Backend *backend; + LogPolicy *clientlp; ProxyStderrBuf psb; Plug *plug; @@ -171,10 +169,17 @@ static int sshproxy_askappend(LogPolicy *lp, Filename *filename, void (*callback)(void *ctx, int result), void *ctx) { + SshProxy *sp = container_of(lp, SshProxy, logpolicy); + + /* + * If we have access to the outer LogPolicy, pass on this request + * to the end user. + */ + if (sp->clientlp) + return lp_askappend(sp->clientlp, filename, callback, ctx); + /* - * TODO: if we had access to the outer LogPolicy, we could pass on - * this request to the end user. (But we'd still have to have this - * code as a fallback in case there isn't a LogPolicy available.) + * Otherwise, fall back to the safe noninteractive assumption. */ char *msg = dupprintf("Log file \"%s\" already exists; logging cancelled", filename_to_str(filename)); @@ -185,12 +190,20 @@ static int sshproxy_askappend(LogPolicy *lp, Filename *filename, static void sshproxy_logging_error(LogPolicy *lp, const char *event) { + SshProxy *sp = container_of(lp, SshProxy, logpolicy); + /* - * TODO: if we had access to the outer LogPolicy, we could pass on - * this request to _its_ logging_error method, where it would be - * more prominent than just dumping it in the outer SSH - * connection's Event Log. (But we'd still have to have this code - * as a fallback in case there isn't a LogPolicy available.) + * If we have access to the outer LogPolicy, pass on this request + * to it. + */ + if (sp->clientlp) { + lp_logging_error(sp->clientlp, event); + return; + } + + /* + * Otherwise, the best we can do is to put it in the outer SSH + * connection's Event Log. */ char *msg = dupprintf("Logging error: %s", event); sshproxy_eventlog(lp, msg); @@ -459,13 +472,13 @@ static const SeatVtable SshProxy_seat_vt = { .verbose = nullseat_verbose_no, .interactive = nullseat_interactive_no, .get_cursor_position = nullseat_get_cursor_position, - }; Socket *sshproxy_new_connection(SockAddr *addr, const char *hostname, int port, bool privport, bool oobinline, bool nodelay, bool keepalive, - Plug *plug, Conf *clientconf) + Plug *plug, Conf *clientconf, + LogPolicy *clientlp) { SshProxy *sp = snew(SshProxy); memset(sp, 0, sizeof(*sp)); @@ -575,5 +588,7 @@ Socket *sshproxy_new_connection(SockAddr *addr, const char *hostname, sfree(realhost); + sp->clientlp = clientlp; + return &sp->sock; } diff --git a/unix/pageant.c b/unix/pageant.c index f204ea9d..b68b829d 100644 --- a/unix/pageant.c +++ b/unix/pageant.c @@ -1189,7 +1189,8 @@ void run_agent(FILE *logfp, const char *symlink_path) conn->plug.vt = &X11Connection_plugvt; s = new_connection(sk_addr_dup(disp->addr), disp->realhost, disp->port, - false, true, false, false, &conn->plug, conf); + false, true, false, false, &conn->plug, conf, + NULL); if ((err = sk_socket_error(s)) != NULL) { fprintf(stderr, "pageant: unable to connect to X server: %s", err); exit(1); diff --git a/unix/sharing.c b/unix/sharing.c index f1ef2019..268b8df1 100644 --- a/unix/sharing.c +++ b/unix/sharing.c @@ -297,7 +297,7 @@ int platform_ssh_share(const char *pi_name, Conf *conf, if (can_downstream) { retsock = new_connection(unix_sock_addr(sockname), "", 0, false, true, false, false, - downplug, conf); + downplug, conf, NULL); if (sk_socket_error(retsock) == NULL) { sfree(*logtext); *logtext = sockname; -- cgit v1.2.3 From 6d272ee007aab95f5bedc690371ecfcdda4fd079 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 13 Sep 2021 17:17:20 +0100 Subject: Allow new_connection to take an optional Seat. (NFC) This is working towards allowing the subsidiary SSH connection in an SshProxy to share the main user-facing Seat, so as to be able to pass through interactive prompts. This is more difficult than the similar change with LogPolicy, because Seats are stateful. In particular, the trust-sigil status will need to be controlled by the SshProxy until it's ready to pass over control to the main SSH (or whatever) connection. To make this work, I've introduced a thing called a TempSeat, which is (yet) another Seat implementation. When a backend hands its Seat to new_connection(), it does it in a way that allows new_connection() to borrow it completely, and replace it in the main backend structure with a TempSeat, which acts as a temporary placeholder. If the main backend tries to do things like changing trust status or sending output, the TempSeat will buffer them; later on, when the connection is established, TempSeat will replay the changes into the real Seat. So, in each backend, I've made the following changes: - pass &foo->seat to new_connection, which may overwrite it with a TempSeat. - if it has done so (which we can tell via the is_tempseat() query function), then we have to free the TempSeat and reinstate our main Seat. The signal that we can do so is the PLUGLOG_CONNECT_SUCCESS notification, which indicates that SshProxy has finished all its connection setup work. - we also have to remember to free the TempSeat if our backend is disposed of without that having happened (e.g. because the connection _doesn't_ succeed). - in backends which have no local auth phase to worry about, ensure we don't call seat_set_trust_status on the main Seat _before_ it gets potentially replaced with a TempSeat. Moved some calls of seat_set_trust_status to just after new_connection(), so that now the initial trust status setup will go into the TempSeat (if appropriate) and be buffered until that seat is relinquished. In all other uses of new_connection, where we don't have a Seat available at all, we just pass NULL. This is NFC, because neither new_connection() nor any of its delegates will _actually_ do this replacement yet. We're just setting up the framework to enable it to do so in the next commit. --- network.h | 19 ++- noproxy.c | 2 +- otherbackends/raw.c | 19 ++- otherbackends/rlogin.c | 13 +- otherbackends/supdup.c | 13 +- otherbackends/telnet.c | 19 ++- putty.h | 25 ++++ ssh/portfwd.c | 2 +- ssh/ssh.c | 14 +- ssh/x11fwd.c | 2 +- unix/pageant.c | 2 +- unix/sharing.c | 2 +- utils/CMakeLists.txt | 1 + utils/tempseat.c | 353 +++++++++++++++++++++++++++++++++++++++++++++++++ 14 files changed, 465 insertions(+), 21 deletions(-) create mode 100644 utils/tempseat.c diff --git a/network.h b/network.h index 067775e4..1839072a 100644 --- a/network.h +++ b/network.h @@ -121,11 +121,28 @@ struct PlugVtable { * host proxy). If you don't have one, all proxy types are required to * be able to manage without (and will just degrade their logging * control). + * + * If calling this from a backend with a Seat, you can also give it a + * pointer to your 'Seat *'. In that situation, it might replace the + * 'Seat *' with a temporary seat of its own, and give the real Seat + * to the proxy system so that it can ask for passwords (and, in the + * case of SSH proxying, other prompts like host key checks). If that + * happens, then the resulting 'temp seat' is the backend's property, + * and it will have to remember to free it when cleaning up, or after + * flushing it back into the real seat when the network connection + * attempt completes. + * + * You can free your TempSeat and resume using the real Seat when one + * of two things happens: either your Plug's closing() method is + * called (indicating failure to connect), or its log() method is + * called with PLUGLOG_CONNECT_SUCCESS. In the latter case, you'll + * probably want to flush the TempSeat's contents into the real Seat, + * of course. */ Socket *new_connection(SockAddr *addr, const char *hostname, int port, bool privport, bool oobinline, bool nodelay, bool keepalive, - Plug *plug, Conf *conf, LogPolicy *lp); + Plug *plug, Conf *conf, LogPolicy *lp, Seat **seat); Socket *new_listener(const char *srcaddr, int port, Plug *plug, bool local_host_only, Conf *conf, int addressfamily); SockAddr *name_lookup(const char *host, int port, char **canonicalname, diff --git a/noproxy.c b/noproxy.c index 6b5832ab..82347d51 100644 --- a/noproxy.c +++ b/noproxy.c @@ -20,7 +20,7 @@ SockAddr *name_lookup(const char *host, int port, char **canonicalname, Socket *new_connection(SockAddr *addr, const char *hostname, int port, bool privport, bool oobinline, bool nodelay, bool keepalive, - Plug *plug, Conf *conf, LogPolicy *lp) + Plug *plug, Conf *conf, LogPolicy *lp, Seat **seat) { return sk_new(addr, port, privport, oobinline, nodelay, keepalive, plug); } diff --git a/otherbackends/raw.c b/otherbackends/raw.c index 638e2a50..87a136ba 100644 --- a/otherbackends/raw.c +++ b/otherbackends/raw.c @@ -39,8 +39,15 @@ static void raw_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, Raw *raw = container_of(plug, Raw, plug); backend_socket_log(raw->seat, raw->logctx, type, addr, port, error_msg, error_code, raw->conf, raw->socket_connected); - if (type == PLUGLOG_CONNECT_SUCCESS) + if (type == PLUGLOG_CONNECT_SUCCESS) { raw->socket_connected = true; + if (is_tempseat(raw->seat)) { + Seat *ts = raw->seat; + tempseat_flush(ts); + raw->seat = tempseat_get_real(ts); + tempseat_free(ts); + } + } } static void raw_check_close(Raw *raw) @@ -132,9 +139,6 @@ static char *raw_init(const BackendVtable *vt, Seat *seat, int addressfamily; char *loghost; - /* No local authentication phase in this protocol */ - seat_set_trust_status(seat, false); - raw = snew(Raw); raw->plug.vt = &Raw_plugvt; raw->backend.vt = vt; @@ -168,10 +172,13 @@ static char *raw_init(const BackendVtable *vt, Seat *seat, */ raw->s = new_connection(addr, *realhost, port, false, true, nodelay, keepalive, &raw->plug, conf, - log_get_policy(logctx)); + log_get_policy(logctx), &raw->seat); if ((err = sk_socket_error(raw->s)) != NULL) return dupstr(err); + /* No local authentication phase in this protocol */ + seat_set_trust_status(raw->seat, false); + loghost = conf_get_str(conf, CONF_loghost); if (*loghost) { char *colon; @@ -191,6 +198,8 @@ static void raw_free(Backend *be) { Raw *raw = container_of(be, Raw, backend); + if (is_tempseat(raw->seat)) + tempseat_free(raw->seat); if (raw->s) sk_close(raw->s); conf_free(raw->conf); diff --git a/otherbackends/rlogin.c b/otherbackends/rlogin.c index 3748e536..ecdb2c36 100644 --- a/otherbackends/rlogin.c +++ b/otherbackends/rlogin.c @@ -45,8 +45,15 @@ static void rlogin_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, backend_socket_log(rlogin->seat, rlogin->logctx, type, addr, port, error_msg, error_code, rlogin->conf, rlogin->socket_connected); - if (type == PLUGLOG_CONNECT_SUCCESS) + if (type == PLUGLOG_CONNECT_SUCCESS) { rlogin->socket_connected = true; + if (is_tempseat(rlogin->seat)) { + Seat *ts = rlogin->seat; + tempseat_flush(ts); + rlogin->seat = tempseat_get_real(ts); + tempseat_free(ts); + } + } } static void rlogin_closing(Plug *plug, const char *error_msg, int error_code, @@ -205,7 +212,7 @@ static char *rlogin_init(const BackendVtable *vt, Seat *seat, */ rlogin->s = new_connection(addr, *realhost, port, true, false, nodelay, keepalive, &rlogin->plug, conf, - log_get_policy(logctx)); + log_get_policy(logctx), &rlogin->seat); if ((err = sk_socket_error(rlogin->s)) != NULL) return dupstr(err); @@ -256,6 +263,8 @@ static void rlogin_free(Backend *be) { Rlogin *rlogin = container_of(be, Rlogin, backend); + if (is_tempseat(rlogin->seat)) + tempseat_free(rlogin->seat); if (rlogin->prompt) free_prompts(rlogin->prompt); if (rlogin->s) diff --git a/otherbackends/supdup.c b/otherbackends/supdup.c index cfa1b2ad..ae7e67f7 100644 --- a/otherbackends/supdup.c +++ b/otherbackends/supdup.c @@ -563,8 +563,15 @@ static void supdup_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, backend_socket_log(supdup->seat, supdup->logctx, type, addr, port, error_msg, error_code, supdup->conf, supdup->socket_connected); - if (type == PLUGLOG_CONNECT_SUCCESS) + if (type == PLUGLOG_CONNECT_SUCCESS) { supdup->socket_connected = true; + if (is_tempseat(supdup->seat)) { + Seat *ts = supdup->seat; + tempseat_flush(ts); + supdup->seat = tempseat_get_real(ts); + tempseat_free(ts); + } + } } static void supdup_closing(Plug *plug, const char *error_msg, int error_code, @@ -713,7 +720,7 @@ static char *supdup_init(const BackendVtable *x, Seat *seat, */ supdup->s = new_connection(addr, *realhost, port, false, true, nodelay, keepalive, &supdup->plug, supdup->conf, - log_get_policy(logctx)); + log_get_policy(logctx), &supdup->seat); if ((err = sk_socket_error(supdup->s)) != NULL) return dupstr(err); @@ -783,6 +790,8 @@ static void supdup_free(Backend *be) { Supdup *supdup = container_of(be, Supdup, backend); + if (is_tempseat(supdup->seat)) + tempseat_free(supdup->seat); if (supdup->s) sk_close(supdup->s); if (supdup->pinger) diff --git a/otherbackends/telnet.c b/otherbackends/telnet.c index b006305d..540d384c 100644 --- a/otherbackends/telnet.c +++ b/otherbackends/telnet.c @@ -626,8 +626,15 @@ static void telnet_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, backend_socket_log(telnet->seat, telnet->logctx, type, addr, port, error_msg, error_code, telnet->conf, telnet->socket_connected); - if (type == PLUGLOG_CONNECT_SUCCESS) + if (type == PLUGLOG_CONNECT_SUCCESS) { telnet->socket_connected = true; + if (is_tempseat(telnet->seat)) { + Seat *ts = telnet->seat; + tempseat_flush(ts); + telnet->seat = tempseat_get_real(ts); + tempseat_free(ts); + } + } } static void telnet_closing(Plug *plug, const char *error_msg, int error_code, @@ -698,9 +705,6 @@ static char *telnet_init(const BackendVtable *vt, Seat *seat, char *loghost; int addressfamily; - /* No local authentication phase in this protocol */ - seat_set_trust_status(seat, false); - telnet = snew(Telnet); telnet->plug.vt = &Telnet_plugvt; telnet->backend.vt = vt; @@ -740,10 +744,13 @@ static char *telnet_init(const BackendVtable *vt, Seat *seat, */ telnet->s = new_connection(addr, *realhost, port, false, true, nodelay, keepalive, &telnet->plug, telnet->conf, - log_get_policy(logctx)); + log_get_policy(logctx), &telnet->seat); if ((err = sk_socket_error(telnet->s)) != NULL) return dupstr(err); + /* No local authentication phase in this protocol */ + seat_set_trust_status(telnet->seat, false); + telnet->pinger = pinger_new(telnet->conf, &telnet->backend); /* @@ -797,6 +804,8 @@ static void telnet_free(Backend *be) { Telnet *telnet = container_of(be, Telnet, backend); + if (is_tempseat(telnet->seat)) + tempseat_free(telnet->seat); strbuf_free(telnet->sb_buf); if (telnet->s) sk_close(telnet->s); diff --git a/putty.h b/putty.h index 8538bd52..8b419a32 100644 --- a/putty.h +++ b/putty.h @@ -1295,6 +1295,31 @@ bool console_can_set_trust_status(Seat *seat); int filexfer_get_userpass_input(Seat *seat, prompts_t *p, bufchain *input); bool cmdline_seat_verbose(Seat *seat); +/* + * TempSeat: a seat implementation that can be given to a backend + * temporarily while network proxy setup is using the real seat. + * Buffers output and trust-status changes until the real seat is + * available again. + */ + +/* Called by the proxy code to make a TempSeat. */ +Seat *tempseat_new(Seat *real); + +/* Query functions to tell if a Seat _is_ temporary, and if so, to + * return the underlying real Seat. */ +bool is_tempseat(Seat *seat); +Seat *tempseat_get_real(Seat *seat); + +/* Called by the backend once the proxy connection has finished + * setting up (or failed), to pass on any buffered stuff to the real + * seat. */ +void tempseat_flush(Seat *ts); + +/* Frees a TempSeat, without flushing anything it has buffered. (Call + * this after tempseat_flush, or alternatively, when you were going to + * abandon the whole connection anyway.) */ +void tempseat_free(Seat *ts); + typedef struct rgb { uint8_t r, g, b; } rgb; diff --git a/ssh/portfwd.c b/ssh/portfwd.c index a67268a4..268935eb 100644 --- a/ssh/portfwd.c +++ b/ssh/portfwd.c @@ -1161,7 +1161,7 @@ char *portfwdmgr_connect(PortFwdManager *mgr, Channel **chan_ret, pf->s = new_connection(addr, dummy_realhost, port, false, true, false, false, &pf->plug, mgr->conf, - NULL); + NULL, NULL); sfree(dummy_realhost); if ((err = sk_socket_error(pf->s)) != NULL) { char *err_ret = dupstr(err); diff --git a/ssh/ssh.c b/ssh/ssh.c index 8b7dd92c..98b7a68a 100644 --- a/ssh/ssh.c +++ b/ssh/ssh.c @@ -598,6 +598,15 @@ static void ssh_socket_log(Plug *plug, PlugLogType type, SockAddr *addr, backend_socket_log(ssh->seat, ssh->logctx, type, addr, port, error_msg, error_code, ssh->conf, ssh->session_started); + + if (type == PLUGLOG_CONNECT_SUCCESS) { + if (is_tempseat(ssh->seat)) { + Seat *ts = ssh->seat; + tempseat_flush(ts); + ssh->seat = tempseat_get_real(ts); + tempseat_free(ts); + } + } } static void ssh_closing(Plug *plug, const char *error_msg, int error_code, @@ -790,7 +799,7 @@ static char *connect_to_host( ssh->s = new_connection(addr, *realhost, port, false, true, nodelay, keepalive, &ssh->plug, ssh->conf, - log_get_policy(ssh->logctx)); + log_get_policy(ssh->logctx), &ssh->seat); if ((err = sk_socket_error(ssh->s)) != NULL) { ssh->s = NULL; seat_notify_remote_exit(ssh->seat); @@ -955,6 +964,9 @@ static void ssh_free(Backend *be) ssh_shutdown(ssh); + if (is_tempseat(ssh->seat)) + tempseat_free(ssh->seat); + conf_free(ssh->conf); if (ssh->connshare) sharestate_free(ssh->connshare); diff --git a/ssh/x11fwd.c b/ssh/x11fwd.c index eb4d4584..60a9f1aa 100644 --- a/ssh/x11fwd.c +++ b/ssh/x11fwd.c @@ -564,7 +564,7 @@ static size_t x11_send( xconn->s = new_connection(sk_addr_dup(xconn->disp->addr), xconn->disp->realhost, xconn->disp->port, false, true, false, false, &xconn->plug, - sshfwd_get_conf(xconn->c), NULL); + sshfwd_get_conf(xconn->c), NULL, NULL); if ((err = sk_socket_error(xconn->s)) != NULL) { char *err_message = dupprintf("unable to connect to" " forwarded X server: %s", err); diff --git a/unix/pageant.c b/unix/pageant.c index b68b829d..7402ac60 100644 --- a/unix/pageant.c +++ b/unix/pageant.c @@ -1190,7 +1190,7 @@ void run_agent(FILE *logfp, const char *symlink_path) s = new_connection(sk_addr_dup(disp->addr), disp->realhost, disp->port, false, true, false, false, &conn->plug, conf, - NULL); + NULL, NULL); if ((err = sk_socket_error(s)) != NULL) { fprintf(stderr, "pageant: unable to connect to X server: %s", err); exit(1); diff --git a/unix/sharing.c b/unix/sharing.c index 268b8df1..58038ab9 100644 --- a/unix/sharing.c +++ b/unix/sharing.c @@ -297,7 +297,7 @@ int platform_ssh_share(const char *pi_name, Conf *conf, if (can_downstream) { retsock = new_connection(unix_sock_addr(sockname), "", 0, false, true, false, false, - downplug, conf, NULL); + downplug, conf, NULL, NULL); if (sk_socket_error(retsock) == NULL) { sfree(*logtext); *logtext = sockname; diff --git a/utils/CMakeLists.txt b/utils/CMakeLists.txt index 260f9920..4b10442b 100644 --- a/utils/CMakeLists.txt +++ b/utils/CMakeLists.txt @@ -42,6 +42,7 @@ add_sources_from_current_dir(utils strbuf.c string_length_for_printf.c stripctrl.c + tempseat.c tree234.c validate_manual_hostkey.c version.c diff --git a/utils/tempseat.c b/utils/tempseat.c new file mode 100644 index 00000000..22cf642c --- /dev/null +++ b/utils/tempseat.c @@ -0,0 +1,353 @@ +/* + * Implementation of the Seat trait that buffers output and other + * events until it can give them back to a real Seat. + * + * This is used by the SSH proxying code, which temporarily takes over + * the real user-facing Seat so that it can issue host key warnings, + * password prompts etc for the proxy SSH connection. While it's got + * the real Seat, it gives the primary connection's backend one of + * these temporary Seats in the interim, so that if the backend wants + * to send some kind of initial output, or start by reconfiguring the + * trust status, or what have you, then it can do that without having + * to keep careful track of the fact that its Seat is out on loan. + */ + +#include "putty.h" + +typedef struct TempSeat TempSeat; +struct TempSeat { + Seat *realseat; + bufchain outputs[2]; /* stdout, stderr */ + bool seen_session_started; + bool seen_remote_exit; + bool seen_remote_disconnect; + bool seen_update_specials_menu; + bool seen_echoedit_update, echoing, editing; + bool seen_trust_status, trusted; + + Seat seat; +}; + +/* ---------------------------------------------------------------------- + * Methods we can usefully buffer, and pass their results on to the + * real Seat in tempseat_flush(). + */ + +static size_t tempseat_output(Seat *seat, bool is_stderr, const void *data, + size_t len) +{ + TempSeat *ts = container_of(seat, TempSeat, seat); + bufchain_add(&ts->outputs[is_stderr], data, len); + return bufchain_size(&ts->outputs[0]) + bufchain_size(&ts->outputs[1]); +} + +static void tempseat_notify_session_started(Seat *seat) +{ + TempSeat *ts = container_of(seat, TempSeat, seat); + ts->seen_session_started = true; +} + +static void tempseat_notify_remote_exit(Seat *seat) +{ + TempSeat *ts = container_of(seat, TempSeat, seat); + ts->seen_remote_exit = true; +} + +static void tempseat_notify_remote_disconnect(Seat *seat) +{ + TempSeat *ts = container_of(seat, TempSeat, seat); + ts->seen_remote_disconnect = true; +} + +static void tempseat_update_specials_menu(Seat *seat) +{ + TempSeat *ts = container_of(seat, TempSeat, seat); + ts->seen_update_specials_menu = true; +} + +static void tempseat_echoedit_update(Seat *seat, bool echoing, bool editing) +{ + TempSeat *ts = container_of(seat, TempSeat, seat); + ts->seen_echoedit_update = true; + ts->echoing = echoing; + ts->editing = editing; +} + +static void tempseat_set_trust_status(Seat *seat, bool trusted) +{ + TempSeat *ts = container_of(seat, TempSeat, seat); + ts->seen_trust_status = true; + ts->trusted = trusted; +} + +/* ---------------------------------------------------------------------- + * Methods we can safely pass straight on to the real Seat, usually + * (but not in every case) because they're read-only queries. + */ + +static char *tempseat_get_ttymode(Seat *seat, const char *mode) +{ + TempSeat *ts = container_of(seat, TempSeat, seat); + return seat_get_ttymode(ts->realseat, mode); +} + +static void tempseat_set_busy_status(Seat *seat, BusyStatus status) +{ + TempSeat *ts = container_of(seat, TempSeat, seat); + /* + * set_busy_status is generally called when something is about to + * do some single-threaded, event-loop blocking computation. This + * _shouldn't_ happen in a backend while it's waiting for a + * network connection to be made, but if for some reason it were + * to, there's no reason we can't just pass this straight to the + * real seat, because we expect that it will mark itself busy, + * compute, and mark itself unbusy, all between yields to the + * event loop that might give whatever else is using the real Seat + * an opportunity to do anything. + */ + seat_set_busy_status(ts->realseat, status); +} + +static bool tempseat_is_utf8(Seat *seat) +{ + TempSeat *ts = container_of(seat, TempSeat, seat); + return seat_is_utf8(ts->realseat); +} + +static const char *tempseat_get_x_display(Seat *seat) +{ + TempSeat *ts = container_of(seat, TempSeat, seat); + return seat_get_x_display(ts->realseat); +} + +static bool tempseat_get_windowid(Seat *seat, long *id_out) +{ + TempSeat *ts = container_of(seat, TempSeat, seat); + return seat_get_windowid(ts->realseat, id_out); +} + +static bool tempseat_get_window_pixel_size(Seat *seat, int *width, int *height) +{ + TempSeat *ts = container_of(seat, TempSeat, seat); + return seat_get_window_pixel_size(ts->realseat, width, height); +} + +static StripCtrlChars *tempseat_stripctrl_new( + Seat *seat, BinarySink *bs_out, SeatInteractionContext sic) +{ + TempSeat *ts = container_of(seat, TempSeat, seat); + return seat_stripctrl_new(ts->realseat, bs_out, sic); +} + +static bool tempseat_verbose(Seat *seat) +{ + TempSeat *ts = container_of(seat, TempSeat, seat); + return seat_verbose(ts->realseat); +} + +static bool tempseat_interactive(Seat *seat) +{ + TempSeat *ts = container_of(seat, TempSeat, seat); + return seat_interactive(ts->realseat); +} + +static bool tempseat_get_cursor_position(Seat *seat, int *x, int *y) +{ + TempSeat *ts = container_of(seat, TempSeat, seat); + return seat_get_cursor_position(ts->realseat, x, y); +} + +static bool tempseat_can_set_trust_status(Seat *seat) +{ + TempSeat *ts = container_of(seat, TempSeat, seat); + return seat_can_set_trust_status(ts->realseat); +} + +/* ---------------------------------------------------------------------- + * Methods that should never be called on a TempSeat, so we can put an + * unreachable() in them. + * + * A backend in possession of a TempSeat ought to be sitting and + * patiently waiting for a network connection attempt to either + * succeed or fail. And it should be aware of the possibility that the + * proxy setup code to which it has lent the real Seat might need to + * present interactive prompts - that's the whole point of lending out + * the Seat in the first place - so it absolutely shouldn't get any + * ideas about issuing some kind of prompt of its own while it waits + * for the network connection. + */ + +static int tempseat_get_userpass_input(Seat *seat, prompts_t *p, + bufchain *input) +{ + /* + * Interactive prompts of this nature are a thing that a backend + * MUST NOT do while not in possession of the real Seat, because + * the whole point of temporarily lending the real Seat to + * something else is that so it can have a clear field to do + * interactive stuff of its own while making a network connection. + */ + unreachable("get_userpass_input should never be called on TempSeat"); +} + +static int tempseat_verify_ssh_host_key( + Seat *seat, const char *host, int port, const char *keytype, + char *keystr, const char *keydisp, char **key_fingerprints, + void (*callback)(void *ctx, int result), void *ctx) +{ + unreachable("verify_ssh_host_key should never be called on TempSeat"); +} + +static int tempseat_confirm_weak_crypto_primitive( + Seat *seat, const char *algtype, const char *algname, + void (*callback)(void *ctx, int result), void *ctx) +{ + unreachable("confirm_weak_crypto_primitive " + "should never be called on TempSeat"); +} + +static int tempseat_confirm_weak_cached_hostkey( + Seat *seat, const char *algname, const char *betteralgs, + void (*callback)(void *ctx, int result), void *ctx) +{ + unreachable("confirm_weak_cached_hostkey " + "should never be called on TempSeat"); +} + +static void tempseat_connection_fatal(Seat *seat, const char *message) +{ + /* + * Fatal errors are another thing a backend should not have any + * reason to encounter while waiting to hear back about its + * network connection setup. + * + * Also, if a backend _did_ call this, it would be hellish to + * unpick all the error handling. Just passing on the fatal error + * to the real Seat wouldn't be good enough: what about freeing + * all the various things that are confusingly holding pointers to + * each other? Better to leave this as an assertion-failure level + * issue, so that if it does ever happen by accident, we'll know + * it's a bug. + */ + unreachable("connection_fatal should never be called on TempSeat"); +} + +static bool tempseat_eof(Seat *seat) +{ + /* + * EOF is _very nearly_ something that we could buffer, and pass + * on to the real Seat at flush time. The only difficulty is that + * sometimes the front end wants to respond to an incoming EOF by + * instructing the back end to send an outgoing one, which it does + * by returning a bool from its eof method. + * + * So we'd have to arrange that tempseat_flush caught that return + * value and passed it on to the calling backend. And then every + * backend would have to deal with tempseat_flush maybe returning + * it an 'actually, please start closing down now' indication, + * which could only happen _in theory_, if it had for some reason + * called seat_eof on the TempSeat. + * + * But in fact, we don't expect back ends to call seat_eof on the + * TempSeat in the first place, so all of that effort would be a + * total waste. Hence, we'll put EOF in the category of things we + * expect backends never to do while the real Seat is out on loan. + */ + unreachable("eof should never be called on TempSeat"); +} + +/* ---------------------------------------------------------------------- + * Done with the TempSeat methods. Here's the vtable definition and + * the main setup/teardown code. + */ + +static const struct SeatVtable tempseat_vt = { + .output = tempseat_output, + .eof = tempseat_eof, + .sent = nullseat_sent, + .get_userpass_input = tempseat_get_userpass_input, + .notify_session_started = tempseat_notify_session_started, + .notify_remote_exit = tempseat_notify_remote_exit, + .notify_remote_disconnect = tempseat_notify_remote_disconnect, + .connection_fatal = tempseat_connection_fatal, + .update_specials_menu = tempseat_update_specials_menu, + .get_ttymode = tempseat_get_ttymode, + .set_busy_status = tempseat_set_busy_status, + .verify_ssh_host_key = tempseat_verify_ssh_host_key, + .confirm_weak_crypto_primitive = tempseat_confirm_weak_crypto_primitive, + .confirm_weak_cached_hostkey = tempseat_confirm_weak_cached_hostkey, + .is_utf8 = tempseat_is_utf8, + .echoedit_update = tempseat_echoedit_update, + .get_x_display = tempseat_get_x_display, + .get_windowid = tempseat_get_windowid, + .get_window_pixel_size = tempseat_get_window_pixel_size, + .stripctrl_new = tempseat_stripctrl_new, + .set_trust_status = tempseat_set_trust_status, + .can_set_trust_status = tempseat_can_set_trust_status, + .verbose = tempseat_verbose, + .interactive = tempseat_interactive, + .get_cursor_position = tempseat_get_cursor_position, +}; + +Seat *tempseat_new(Seat *realseat) +{ + TempSeat *ts = snew(TempSeat); + memset(ts, 0, sizeof(*ts)); + ts->seat.vt = &tempseat_vt; + + ts->realseat = realseat; + for (unsigned i = 0; i < 2; i++) + bufchain_init(&ts->outputs[i]); + + return &ts->seat; +} + +bool is_tempseat(Seat *seat) +{ + return seat->vt == &tempseat_vt; +} + +Seat *tempseat_get_real(Seat *seat) +{ + assert(seat->vt == &tempseat_vt); + TempSeat *ts = container_of(seat, TempSeat, seat); + return ts->realseat; +} + +void tempseat_free(Seat *seat) +{ + assert(seat->vt == &tempseat_vt); + TempSeat *ts = container_of(seat, TempSeat, seat); + for (unsigned i = 0; i < 2; i++) + bufchain_clear(&ts->outputs[i]); + sfree(ts); +} + +void tempseat_flush(Seat *seat) +{ + assert(seat->vt == &tempseat_vt); + TempSeat *ts = container_of(seat, TempSeat, seat); + + /* Empty the stdout/stderr bufchains into the real seat */ + for (unsigned i = 0; i < 2; i++) { + while (bufchain_size(&ts->outputs[i])) { + ptrlen pl = bufchain_prefix(&ts->outputs[i]); + seat_output(ts->realseat, i, pl.ptr, pl.len); + bufchain_consume(&ts->outputs[i], pl.len); + } + } + + /* Pass on any other kinds of event we've buffered */ + if (ts->seen_session_started) + seat_notify_session_started(ts->realseat); + if (ts->seen_remote_exit) + seat_notify_remote_exit(ts->realseat); + if (ts->seen_remote_disconnect) + seat_notify_remote_disconnect(ts->realseat); + if (ts->seen_update_specials_menu) + seat_update_specials_menu(ts->realseat); + if (ts->seen_echoedit_update) + seat_echoedit_update(ts->realseat, ts->echoing, ts->editing); + if (ts->seen_trust_status) + seat_set_trust_status(ts->realseat, ts->trusted); +} -- cgit v1.2.3 From b1d01cd3c7a499aee2e50e3b7991ff67fb998851 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 13 Sep 2021 16:30:59 +0100 Subject: sshproxy: borrow a Seat for host key and crypto dialogs. This puts the previous commit's framework to practical use. Now the main new_connection() passes its Seat ** through to the SshProxy setup function, which (if the stars align) will actually use it: stash it, return a TempSeat wrapper on it for the main backend to use in the interim, and pass through the GUI dialog prompts for host key confirmation and weak-crypto warnings. This is unfinished at the UI end: those dialog prompts will now need to be much clearer about which SSH server they're talking to (since now there could be two involved), and I haven't made that change yet. I haven't attempted to deal with get_userpass_input yet, though. That's much harder, and I'm still working on it. --- network.h | 2 +- nosshproxy.c | 2 +- proxy.c | 4 +- sshproxy.c | 117 ++++++++++++++++++++++++++++++++++------------------------- 4 files changed, 71 insertions(+), 54 deletions(-) diff --git a/network.h b/network.h index 1839072a..107e72c8 100644 --- a/network.h +++ b/network.h @@ -161,7 +161,7 @@ Socket *sshproxy_new_connection(SockAddr *addr, const char *hostname, int port, bool privport, bool oobinline, bool nodelay, bool keepalive, Plug *plug, Conf *conf, - LogPolicy *clientlp); + LogPolicy *clientlp, Seat **clientseat); /* socket functions */ diff --git a/nosshproxy.c b/nosshproxy.c index cce33231..5f2bbdca 100644 --- a/nosshproxy.c +++ b/nosshproxy.c @@ -11,7 +11,7 @@ Socket *sshproxy_new_connection(SockAddr *addr, const char *hostname, int port, bool privport, bool oobinline, bool nodelay, bool keepalive, Plug *plug, Conf *conf, - LogPolicy *clientlp) + LogPolicy *clientlp, Seat **clientseat) { return NULL; } diff --git a/proxy.c b/proxy.c index 770f3d02..97a4b1df 100644 --- a/proxy.c +++ b/proxy.c @@ -395,7 +395,7 @@ static const PlugVtable ProxySocket_plugvt = { Socket *new_connection(SockAddr *addr, const char *hostname, int port, bool privport, bool oobinline, bool nodelay, bool keepalive, - Plug *plug, Conf *conf, LogPolicy *lp) + Plug *plug, Conf *conf, LogPolicy *lp, Seat **seat) { int type = conf_get_int(conf, CONF_proxy_type); @@ -411,7 +411,7 @@ Socket *new_connection(SockAddr *addr, const char *hostname, if (type == PROXY_SSH && (sret = sshproxy_new_connection(addr, hostname, port, privport, oobinline, nodelay, keepalive, - plug, conf, lp)) != NULL) + plug, conf, lp, seat)) != NULL) return sret; if ((sret = platform_new_connection(addr, hostname, port, privport, diff --git a/sshproxy.c b/sshproxy.c index 46ef9d6c..433585ad 100644 --- a/sshproxy.c +++ b/sshproxy.c @@ -16,37 +16,17 @@ const bool ssh_proxy_supported = true; /* * TODO for future work: * - * At present, this use of SSH as a proxy is mostly noninteractive. In - * our implementations of the Seat trait, every method that involves - * interactively prompting the user is implemented by pretending the - * user gave a safe default answer. So the effect is very much as if - * you'd used 'plink -batch' as a proxy subprocess - password prompts - * are cancelled and any dubious host key or crypto primitive is - * unconditionally rejected - except that it all happens in-process, - * making it mildly more convenient to set up, perhaps a hair faster, - * and you get all the Event Log data in one place. + * At present, this use of SSH as a proxy is not fully interactive. + * We're borrowing the main backend's LogPolicy for queries like + * askappend(), and we're borrowing the main backend's Seat for host + * key prompts and weak-crypto warnings, but one thing we still don't + * have is a functioning implementation of seat_get_userpass_input + * that can display the proxy SSH connection's password prompts (or + * similar) in the terminal window before handing the terminal back to + * the main connection. * - * But the biggest benefit of in-process SSH proxying would be that - * the interactive prompts from the sub-SSH can be passed through to - * the end user. If your jump host and your ultimate destination host - * both require password authentication, you should be able to type - * both password in sequence into the PuTTY terminal window; if you're - * running a session of this kind for the first time, you should be - * able to confirm both host keys one after another. In the current - * state of the code, none of that is yet implemented: we're borrowing - * the client LogPolicy for things like askappend(), but not the Seat, - * which is where all the really important stuff lives. - * - * To fix that, we'd have to start by arranging for this proxy - * implementation to get hold of the 'real' (outer) Seat object, which - * means passing it to new_connection as we're already doing with - * LogPolicy. Then, each method in this file that receives an - * interactive prompt request would handle it by passing it on to the - * client Seat if present, with some kind of tweak that would allow - * the end user to see clearly that the prompt had come from the proxy - * SSH connection rather than the primary one. If no client Seat was - * present, we'd have no choice but to fall back to the existing - * behaviour of behaving as if in batch mode. + * Also, the host key and weak-crypto prompts need adjusting so that + * it's clear to the user which SSH connection they come from. */ typedef struct SshProxy { @@ -55,6 +35,7 @@ typedef struct SshProxy { LogContext *logctx; Backend *backend; LogPolicy *clientlp; + Seat *clientseat; ProxyStderrBuf psb; Plug *plug; @@ -352,16 +333,20 @@ static int sshproxy_verify_ssh_host_key( { SshProxy *sp = container_of(seat, SshProxy, seat); + if (sp->clientseat) { + /* + * If we have access to the outer Seat, pass this prompt + * request on to it. FIXME: appropriately adjusted + */ + return seat_verify_ssh_host_key( + sp->clientseat, host, port, keytype, keystr, keydisp, + key_fingerprints, callback, ctx); + } + /* - * TODO: if we had access to the outer Seat, we could pass on this - * request to *its* verify_ssh_host_key method, appropriately - * adjusted to indicate that it comes from the proxy SSH - * connection. (But we'd still have to have this code as a - * fallback in case there isn't a Seat available.) - * - * Instead, we have to behave as if we're in batch mode: directly - * verify the host key against the cache, and if that fails, take - * the safe option in the absence of interactive confirmation, and + * Otherwise, behave as if we're in batch mode: directly verify + * the host key against the cache, and if that fails, take the + * safe option in the absence of interactive confirmation, and * abort the connection. */ int hkstatus = verify_host_key(host, port, keytype, keystr); @@ -394,12 +379,18 @@ static int sshproxy_confirm_weak_crypto_primitive( { SshProxy *sp = container_of(seat, SshProxy, seat); + if (sp->clientseat) { + /* + * If we have access to the outer Seat, pass this prompt + * request on to it. FIXME: appropriately adjusted + */ + return seat_confirm_weak_crypto_primitive( + sp->clientseat, algtype, algname, callback, ctx); + } + /* - * TODO: if we had access to the outer Seat, we could pass on this - * request to *its* confirm_weak_crypto_primitive method, - * appropriately adjusted to indicate that it comes from the proxy - * SSH connection. (But we'd still have to have this code as a - * fallback in case there isn't a Seat available.) + * Otherwise, behave as if we're in batch mode: take the safest + * option. */ sshproxy_error(sp, "First %s supported by server is %s, below warning " "threshold. Abandoning proxy SSH connection.", @@ -413,12 +404,18 @@ static int sshproxy_confirm_weak_cached_hostkey( { SshProxy *sp = container_of(seat, SshProxy, seat); + if (sp->clientseat) { + /* + * If we have access to the outer Seat, pass this prompt + * request on to it. FIXME: appropriately adjusted + */ + return seat_confirm_weak_cached_hostkey( + sp->clientseat, algname, betteralgs, callback, ctx); + } + /* - * TODO: if we had access to the outer Seat, we could pass on this - * request to *its* confirm_weak_cached_hostkey method, - * appropriately adjusted to indicate that it comes from the proxy - * SSH connection. (But we'd still have to have this code as a - * fallback in case there isn't a Seat available.) + * Otherwise, behave as if we're in batch mode: take the safest + * option. */ sshproxy_error(sp, "First host key type stored for server is %s, below " "warning threshold. Abandoning proxy SSH connection.", @@ -478,7 +475,7 @@ Socket *sshproxy_new_connection(SockAddr *addr, const char *hostname, int port, bool privport, bool oobinline, bool nodelay, bool keepalive, Plug *plug, Conf *clientconf, - LogPolicy *clientlp) + LogPolicy *clientlp, Seat **clientseat) { SshProxy *sp = snew(SshProxy); memset(sp, 0, sizeof(*sp)); @@ -588,7 +585,27 @@ Socket *sshproxy_new_connection(SockAddr *addr, const char *hostname, sfree(realhost); + /* + * If we've been given useful bits and pieces for interacting with + * the end user, squirrel them away now. + */ sp->clientlp = clientlp; + if (clientseat && (backvt->flags & BACKEND_NOTIFIES_SESSION_START)) { + /* + * We can only keep the client's Seat if our own backend will + * tell us when to give it back. (SSH-based backends _should_ + * do that, but we check the flag here anyway.) + * + * Also, check if the client already has a TempSeat, and if + * so, don't wrap it with another one. + */ + if (is_tempseat(*clientseat)) { + sp->clientseat = tempseat_get_real(*clientseat); + } else { + sp->clientseat = *clientseat; + *clientseat = tempseat_new(sp->clientseat); + } + } return &sp->sock; } -- cgit v1.2.3 From 612b293c1e29c62ced3a1884fad57e98fb560642 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Tue, 14 Sep 2021 08:20:42 +0100 Subject: rlogin: defer username prompt until connection is made. This gives more sensible handling of failed network connections: if the connection _doesn't_ get made, it's pointless to ask the user for a login username anyway, and worse still, you'd probably end up giving the connection-failure error message while the user was in the middle of answering the username prompt. But more importantly, when proxy systems start being able to present their own interactive prompts, it's vital that no backend should already be presenting a prompt while in the process of setting up a network connection. --- otherbackends/rlogin.c | 66 ++++++++++++++++++++++++++++---------------------- 1 file changed, 37 insertions(+), 29 deletions(-) diff --git a/otherbackends/rlogin.c b/otherbackends/rlogin.c index ecdb2c36..871a4f19 100644 --- a/otherbackends/rlogin.c +++ b/otherbackends/rlogin.c @@ -32,6 +32,8 @@ struct Rlogin { Backend backend; }; +static void rlogin_startup(Rlogin *rlogin, const char *ruser); + static void c_write(Rlogin *rlogin, const void *buf, size_t len) { size_t backlog = seat_stdout(rlogin->seat, buf, len); @@ -53,6 +55,41 @@ static void rlogin_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, rlogin->seat = tempseat_get_real(ts); tempseat_free(ts); } + + char *ruser = get_remote_username(rlogin->conf); + if (ruser) { + /* + * If we already know the remote username, call + * rlogin_startup, which will send the initial protocol + * greeting including local username, remote username, + * terminal type and terminal speed. + */ + /* Next terminal output will come from server */ + seat_set_trust_status(rlogin->seat, false); + rlogin_startup(rlogin, ruser); + sfree(ruser); + } else { + /* + * Otherwise, set up a prompts_t asking for the local + * username. If it completes synchronously, call + * rlogin_startup as above; otherwise, wait until it does. + */ + rlogin->prompt = new_prompts(); + rlogin->prompt->to_server = true; + rlogin->prompt->from_server = false; + rlogin->prompt->name = dupstr("Rlogin login name"); + add_prompt(rlogin->prompt, dupstr("rlogin username: "), true); + + int ret = seat_get_userpass_input(rlogin->seat, rlogin->prompt, + NULL); + if (ret >= 0) { + /* Next terminal output will come from server */ + seat_set_trust_status(rlogin->seat, false); + rlogin_startup(rlogin, prompt_get_result_ref( + rlogin->prompt->prompts[0])); + } + } + } } @@ -173,7 +210,6 @@ static char *rlogin_init(const BackendVtable *vt, Seat *seat, SockAddr *addr; const char *err; Rlogin *rlogin; - char *ruser; int addressfamily; char *loghost; @@ -228,34 +264,6 @@ static char *rlogin_init(const BackendVtable *vt, Seat *seat, *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 ((ruser = get_remote_username(conf)) != NULL) { - /* Next terminal output will come from server */ - seat_set_trust_status(rlogin->seat, false); - rlogin_startup(rlogin, ruser); - sfree(ruser); - } else { - int ret; - - rlogin->prompt = new_prompts(); - rlogin->prompt->to_server = true; - rlogin->prompt->from_server = false; - rlogin->prompt->name = dupstr("Rlogin login name"); - add_prompt(rlogin->prompt, dupstr("rlogin username: "), true); - ret = seat_get_userpass_input(rlogin->seat, rlogin->prompt, NULL); - if (ret >= 0) { - /* Next terminal output will come from server */ - seat_set_trust_status(rlogin->seat, false); - rlogin_startup(rlogin, prompt_get_result_ref( - rlogin->prompt->prompts[0])); - } - } - return NULL; } -- cgit v1.2.3 From 8e35e6eeae986f1c5575790e444533c3c1269035 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Tue, 14 Sep 2021 08:49:08 +0100 Subject: Rlogin: handle user abort at the username prompt. While re-testing this backend I realised that we were completely ignoring the actual return status from seat_get_userpass_input, once it stops being -1 ("prompt still in progress"). So if the user hits ^C or ^D at the prompt, e.g. after realising they've started PuTTY in the wrong mode by mistake, then we press ahead anyway and send a nonsense username. Now we interpret that as a request to close the connection. --- otherbackends/rlogin.c | 43 ++++++++++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/otherbackends/rlogin.c b/otherbackends/rlogin.c index 871a4f19..51e6ed05 100644 --- a/otherbackends/rlogin.c +++ b/otherbackends/rlogin.c @@ -32,7 +32,8 @@ struct Rlogin { Backend backend; }; -static void rlogin_startup(Rlogin *rlogin, const char *ruser); +static void rlogin_startup(Rlogin *rlogin, int prompt_result, + const char *ruser); static void c_write(Rlogin *rlogin, const void *buf, size_t len) { @@ -66,7 +67,7 @@ static void rlogin_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, */ /* Next terminal output will come from server */ seat_set_trust_status(rlogin->seat, false); - rlogin_startup(rlogin, ruser); + rlogin_startup(rlogin, 1, ruser); sfree(ruser); } else { /* @@ -85,7 +86,7 @@ static void rlogin_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, if (ret >= 0) { /* Next terminal output will come from server */ seat_set_trust_status(rlogin->seat, false); - rlogin_startup(rlogin, prompt_get_result_ref( + rlogin_startup(rlogin, ret, prompt_get_result_ref( rlogin->prompt->prompts[0])); } } @@ -166,23 +167,31 @@ static void rlogin_sent(Plug *plug, size_t bufsize) seat_sent(rlogin->seat, rlogin->bufsize); } -static void rlogin_startup(Rlogin *rlogin, const char *ruser) +static void rlogin_startup(Rlogin *rlogin, int prompt_result, + const char *ruser) { char z = 0; char *p; - sk_write(rlogin->s, &z, 1); - p = conf_get_str(rlogin->conf, CONF_localusername); - sk_write(rlogin->s, p, strlen(p)); - sk_write(rlogin->s, &z, 1); - sk_write(rlogin->s, ruser, strlen(ruser)); - sk_write(rlogin->s, &z, 1); - p = conf_get_str(rlogin->conf, CONF_termtype); - sk_write(rlogin->s, p, strlen(p)); - sk_write(rlogin->s, "/", 1); - p = conf_get_str(rlogin->conf, CONF_termspeed); - sk_write(rlogin->s, p, strspn(p, "0123456789")); - rlogin->bufsize = sk_write(rlogin->s, &z, 1); + if (prompt_result == 0) { + /* User aborted at the username prompt. */ + sk_close(rlogin->s); + rlogin->s = NULL; + seat_notify_remote_exit(rlogin->seat); + } else { + sk_write(rlogin->s, &z, 1); + p = conf_get_str(rlogin->conf, CONF_localusername); + sk_write(rlogin->s, p, strlen(p)); + sk_write(rlogin->s, &z, 1); + sk_write(rlogin->s, ruser, strlen(ruser)); + sk_write(rlogin->s, &z, 1); + p = conf_get_str(rlogin->conf, CONF_termtype); + sk_write(rlogin->s, p, strlen(p)); + sk_write(rlogin->s, "/", 1); + p = conf_get_str(rlogin->conf, CONF_termspeed); + sk_write(rlogin->s, p, strspn(p, "0123456789")); + rlogin->bufsize = sk_write(rlogin->s, &z, 1); + } rlogin->prompt = NULL; } @@ -311,7 +320,7 @@ static void rlogin_send(Backend *be, const char *buf, size_t len) if (ret >= 0) { /* Next terminal output will come from server */ seat_set_trust_status(rlogin->seat, false); - rlogin_startup(rlogin, prompt_get_result_ref( + rlogin_startup(rlogin, ret, prompt_get_result_ref( rlogin->prompt->prompts[0])); /* that nulls out rlogin->prompt, so then we'll start sending * data down the wire in the obvious way */ -- cgit v1.2.3 From 2fd2f4715d55b5009620b6c03de2e939712cc249 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Tue, 14 Sep 2021 11:16:49 +0100 Subject: Squash shift warnings in ssh2_bpp_check_unimplemented. I did a horrible thing with a list macro which builds up a 256-bit bitmap of known SSH-2 message types at compile time, by means of evaluating a conditional expression per known message type and per bitmap word which boils down to (in pseudocode) (shift count in range ? 1 << shift count : 0) I think this is perfectly valid C. If the shift count is out of range, then the use of the << operator in the true branch of the ?: would have undefined behaviour if it were executed - but that's OK, because in that situation, the safe false branch is executed instead. But when the whole thing is a compile-time evaluated constant expression, the compiler can prove statically that the << in the true branch is an out-of-range shift, and at least some compilers will warn about it verbosely. The same compiler *could* also prove statically that that branch isn't taken, and use that to suppress the warning - but at least clang does not. The solution is the same one I used in shift_right_by_one_word and shift_left_by_one_word in mpint.c: inside the true branch, nest a second conditional expression which coerces the shift count to always be in range, by setting it to 0 if it's not. This doesn't affect the output, because the only cases in which the output of the true branch is altered by this transformation are the ones in which the true branch wasn't taken anyway. So this change should make no difference to the output of this macro construction, but it suppresses about 350 pointless warnings from clang. --- ssh/common.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ssh/common.c b/ssh/common.c index 96e07cf1..e421d90e 100644 --- a/ssh/common.c +++ b/ssh/common.c @@ -786,8 +786,10 @@ void ssh2_bpp_queue_disconnect(BinaryPacketProtocol *bpp, pq_push(&bpp->out_pq, pkt); } -#define BITMAP_UNIVERSAL(y, name, value) \ - | (value >= y && value < y+32 ? 1UL << (value-y) : 0) +#define BITMAP_UNIVERSAL(y, name, value) \ + | (value >= y && value < y+32 \ + ? 1UL << (value >= y && value < y+32 ? (value-y) : 0) \ + : 0) #define BITMAP_CONDITIONAL(y, name, value, ctx) \ BITMAP_UNIVERSAL(y, name, value) #define SSH2_BITMAP_WORD(y) \ -- cgit v1.2.3 From 9f0e7d291558989cc44f98cf8603d5cf2787ce3a Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Tue, 14 Sep 2021 10:13:28 +0100 Subject: Backends: notify ldisc when sendok becomes true. (NFC) I've introduced a function ldisc_notify_sendok(), which backends should call on their ldisc (if they have one) when anything changes that might cause backend_sendok() to start returning true. At the moment, the function does nothing. But in future, I'm going to make ldisc start buffering typed-ahead input data not yet sent to the backend, and then the effect of this function will be to trigger flushing all that data into the backend. Backends only have to call this function if sendok was previously false: backends requiring no network connection stage (like pty and serial) can safely return true from sendok, and in that case, they don't also have to immediately call this function. --- ldisc.c | 4 ++++ otherbackends/raw.c | 6 +++++- otherbackends/rlogin.c | 6 +++++- otherbackends/supdup.c | 5 +++++ otherbackends/telnet.c | 2 ++ pscp.c | 1 + psftp.c | 1 + putty.h | 1 + ssh.h | 1 + ssh/connection1.c | 2 ++ ssh/connection2.c | 2 ++ ssh/server.c | 2 ++ ssh/ssh.c | 8 ++++++++ 13 files changed, 39 insertions(+), 2 deletions(-) diff --git a/ldisc.c b/ldisc.c index e5e1449d..8c985f06 100644 --- a/ldisc.c +++ b/ldisc.c @@ -139,6 +139,10 @@ void ldisc_echoedit_update(Ldisc *ldisc) seat_echoedit_update(ldisc->seat, ECHOING, EDITING); } +void ldisc_check_sendok(Ldisc *ldisc) +{ +} + void ldisc_send(Ldisc *ldisc, const void *vbuf, int len, bool interactive) { const char *buf = (const char *)vbuf; diff --git a/otherbackends/raw.c b/otherbackends/raw.c index 87a136ba..97b7be09 100644 --- a/otherbackends/raw.c +++ b/otherbackends/raw.c @@ -17,6 +17,7 @@ struct Raw { size_t bufsize; Seat *seat; LogContext *logctx; + Ldisc *ldisc; bool sent_console_eof, sent_socket_eof, socket_connected; Conf *conf; @@ -41,6 +42,8 @@ static void raw_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, error_code, raw->conf, raw->socket_connected); if (type == PLUGLOG_CONNECT_SUCCESS) { raw->socket_connected = true; + if (raw->ldisc) + ldisc_check_sendok(raw->ldisc); if (is_tempseat(raw->seat)) { Seat *ts = raw->seat; tempseat_flush(ts); @@ -295,7 +298,8 @@ static bool raw_ldisc(Backend *be, int option) static void raw_provide_ldisc(Backend *be, Ldisc *ldisc) { - /* This is a stub. */ + Raw *raw = container_of(be, Raw, backend); + raw->ldisc = ldisc; } static int raw_exitcode(Backend *be) diff --git a/otherbackends/rlogin.c b/otherbackends/rlogin.c index 51e6ed05..11deb89c 100644 --- a/otherbackends/rlogin.c +++ b/otherbackends/rlogin.c @@ -22,6 +22,7 @@ struct Rlogin { int term_width, term_height; Seat *seat; LogContext *logctx; + Ldisc *ldisc; Conf *conf; @@ -194,6 +195,8 @@ static void rlogin_startup(Rlogin *rlogin, int prompt_result, } rlogin->prompt = NULL; + if (rlogin->ldisc) + ldisc_check_sendok(rlogin->ldisc); } static const PlugVtable Rlogin_plugvt = { @@ -413,7 +416,8 @@ static bool rlogin_ldisc(Backend *be, int option) static void rlogin_provide_ldisc(Backend *be, Ldisc *ldisc) { - /* This is a stub. */ + Rlogin *rlogin = container_of(be, Rlogin, backend); + rlogin->ldisc = ldisc; } static int rlogin_exitcode(Backend *be) diff --git a/otherbackends/supdup.c b/otherbackends/supdup.c index ae7e67f7..a3418fe1 100644 --- a/otherbackends/supdup.c +++ b/otherbackends/supdup.c @@ -71,6 +71,7 @@ struct supdup_tag Seat *seat; LogContext *logctx; + Ldisc *ldisc; int term_width, term_height; long long ttyopt; @@ -565,6 +566,8 @@ static void supdup_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, supdup->conf, supdup->socket_connected); if (type == PLUGLOG_CONNECT_SUCCESS) { supdup->socket_connected = true; + if (supdup->ldisc) + ldisc_check_sendok(supdup->ldisc); if (is_tempseat(supdup->seat)) { Seat *ts = supdup->seat; tempseat_flush(ts); @@ -893,6 +896,8 @@ static bool supdup_ldisc(Backend *be, int option) static void supdup_provide_ldisc(Backend *be, Ldisc *ldisc) { + Supdup *supdup = container_of(be, Supdup, backend); + supdup->ldisc = ldisc; } static int supdup_exitcode(Backend *be) diff --git a/otherbackends/telnet.c b/otherbackends/telnet.c index 540d384c..98c202c6 100644 --- a/otherbackends/telnet.c +++ b/otherbackends/telnet.c @@ -628,6 +628,8 @@ static void telnet_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, telnet->socket_connected); if (type == PLUGLOG_CONNECT_SUCCESS) { telnet->socket_connected = true; + if (telnet->ldisc) + ldisc_check_sendok(telnet->ldisc); if (is_tempseat(telnet->seat)) { Seat *ts = telnet->seat; tempseat_flush(ts); diff --git a/pscp.c b/pscp.c index ec4dfa8a..7717d8d6 100644 --- a/pscp.c +++ b/pscp.c @@ -58,6 +58,7 @@ const char *const appname = "PSCP"; #define MAX_SCP_BUFSIZE 16384 void ldisc_echoedit_update(Ldisc *ldisc) { } +void ldisc_check_sendok(Ldisc *ldisc) { } static size_t pscp_output(Seat *, bool is_stderr, const void *, size_t); static bool pscp_eof(Seat *); diff --git a/psftp.c b/psftp.c index 16beee56..a09837c7 100644 --- a/psftp.c +++ b/psftp.c @@ -2448,6 +2448,7 @@ int do_sftp(int mode, int modeflags, char *batchfile) static bool verbose = false; void ldisc_echoedit_update(Ldisc *ldisc) { } +void ldisc_check_sendok(Ldisc *ldisc) { } /* * Receive a block of data from the SSH link. Block until all data diff --git a/putty.h b/putty.h index 8b419a32..fcfff4b6 100644 --- a/putty.h +++ b/putty.h @@ -2055,6 +2055,7 @@ void ldisc_configure(Ldisc *, Conf *); void ldisc_free(Ldisc *); void ldisc_send(Ldisc *, const void *buf, int len, bool interactive); void ldisc_echoedit_update(Ldisc *); +void ldisc_check_sendok(Ldisc *); /* * Exports from sshrand.c. diff --git a/ssh.h b/ssh.h index 23bed390..34a8909d 100644 --- a/ssh.h +++ b/ssh.h @@ -397,6 +397,7 @@ LogContext *ssh_get_logctx(Ssh *ssh); void ssh_throttle_conn(Ssh *ssh, int adjust); void ssh_got_exitcode(Ssh *ssh, int status); void ssh_ldisc_update(Ssh *ssh); +void ssh_check_sendok(Ssh *ssh); void ssh_got_fallback_cmd(Ssh *ssh); bool ssh_is_bare(Ssh *ssh); diff --git a/ssh/connection1.c b/ssh/connection1.c index 7b1765f6..071e0139 100644 --- a/ssh/connection1.c +++ b/ssh/connection1.c @@ -775,6 +775,8 @@ static void ssh1_set_wants_user_input(ConnectionLayer *cl, bool wanted) s->want_user_input = wanted; s->finished_setup = true; + if (wanted) + ssh_check_sendok(s->ppl.ssh); } static bool ssh1_connection_want_user_input(PacketProtocolLayer *ppl) diff --git a/ssh/connection2.c b/ssh/connection2.c index 2e7102db..86e26f4b 100644 --- a/ssh/connection2.c +++ b/ssh/connection2.c @@ -1709,6 +1709,8 @@ static void ssh2_set_wants_user_input(ConnectionLayer *cl, bool wanted) container_of(cl, struct ssh2_connection_state, cl); s->want_user_input = wanted; + if (wanted) + ssh_check_sendok(s->ppl.ssh); } static bool ssh2_connection_want_user_input(PacketProtocolLayer *ppl) diff --git a/ssh/server.c b/ssh/server.c index 1517522b..a3c400c0 100644 --- a/ssh/server.c +++ b/ssh/server.c @@ -244,6 +244,8 @@ Conf *make_ssh_server_conf(void) return conf; } +void ssh_check_sendok(Ssh *ssh) {} + static const PlugVtable ssh_server_plugvt = { .log = server_socket_log, .closing = server_closing, diff --git a/ssh/ssh.c b/ssh/ssh.c index 98b7a68a..10c52001 100644 --- a/ssh/ssh.c +++ b/ssh/ssh.c @@ -1156,6 +1156,14 @@ static bool ssh_sendok(Backend *be) return ssh->base_layer && ssh_ppl_want_user_input(ssh->base_layer); } +void ssh_check_sendok(Ssh *ssh) +{ + /* Called when the connection layer might have caused ssh_sendok + * to start returning true */ + if (ssh->ldisc) + ldisc_check_sendok(ssh->ldisc); +} + void ssh_ldisc_update(Ssh *ssh) { /* Called when the connection layer wants to propagate an update -- cgit v1.2.3 From cd8a7181fddf42e14c44f024de71732bc2a6c715 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Tue, 14 Sep 2021 11:57:21 +0100 Subject: Complete rework of terminal userpass input system. The system for handling seat_get_userpass_input has always been structured differently between GUI PuTTY and CLI tools like Plink. In the CLI tools, password input is read directly from the OS terminal/console device by console_get_userpass_input; this means that you need to ensure the same terminal input data _hasn't_ already been consumed by the main event loop and sent on to the backend. This is achieved by the backend_sendok() method, which tells the event loop when the backend has finished issuing password prompts, and hence, when it's safe to start passing standard input to backend_send(). But in the GUI tools, input generated by the terminal window has always been sent straight to backend_send(), regardless of whether backend_sendok() says it wants it. So the terminal-based implementation of username and password prompts has to work by consuming input data that had _already_ been passed to the backend - hence, any backend that needs to do that must keep its input on a bufchain, and pass that bufchain to seat_get_userpass_input. It's awkward that these two totally different systems coexist in the first place. And now that SSH proxying needs to present interactive prompts of its own, it's clear which one should win: the CLI style is the Right Thing. So this change reworks the GUI side of the mechanism to be more similar: terminal data now goes into a queue in the Ldisc, and is not sent on to the backend until the backend says it's ready for it via backend_sendok(). So terminal-based userpass prompts can now consume data directly from that queue during the connection setup stage. As a result, the 'bufchain *' parameter has vanished from all the userpass_input functions (both the official implementations of the Seat trait method, and term_get_userpass_input() to which some of those implementations delegate). The only function that actually used that bufchain, namely term_get_userpass_input(), now instead reads from the ldisc's input queue via a couple of new Ldisc functions. (Not _trivial_ functions, since input buffered by Ldisc can be a mixture of raw bytes and session specials like SS_EOL! The input queue inside Ldisc is a bufchain containing a fiddly binary encoding that can represent an arbitrary interleaving of those things.) This greatly simplifies the calls to seat_get_userpass_input in backends, which now don't have to mess about with passing their own user_input bufchain around, or toggling their want_user_input flag back and forth to request data to put on to that bufchain. But the flip side is that now there has to be some _other_ method for notifying the terminal when there's more input to be consumed during an interactive prompt, and for notifying the backend when prompt input has finished so that it can proceed to the next stage of the protocol. This is done by a pair of extra callbacks: when more data is put on to Ldisc's input queue, it triggers a call to term_get_userpass_input, and when term_get_userpass_input finishes, it calls a callback function provided in the prompts_t. Therefore, any use of a prompts_t which *might* be asynchronous must fill in the latter callback when setting up the prompts_t. In SSH, the callback is centralised into a common PPL helper function, which reinvokes the same PPL's process_queue coroutine; in rlogin we have to set it up ourselves. I'm sorry for this large and sprawling patch: I tried fairly hard to break it up into individually comprehensible sub-patches, but I just couldn't tease out any part of it that would stand sensibly alone. --- fuzzterm.c | 5 + ldisc.c | 250 +++++++++++++++++++++++++++++++++++++++++++++---- noterm.c | 5 + otherbackends/rlogin.c | 69 ++++++-------- putty.h | 60 +++++++++--- ssh/common.c | 13 +++ ssh/connection1.c | 18 +--- ssh/connection2.c | 18 +--- ssh/login1.c | 56 +++-------- ssh/ppl.h | 5 + ssh/userauth2-client.c | 92 +++++------------- sshproxy.c | 3 +- terminal.c | 54 +++++++++-- unix/plink.c | 2 +- unix/sftp.c | 2 +- unix/window.c | 5 +- utils/nullseat.c | 3 +- utils/prompts.c | 2 + utils/tempseat.c | 3 +- windows/plink.c | 2 +- windows/sftp.c | 2 +- windows/window.c | 8 +- 22 files changed, 445 insertions(+), 232 deletions(-) diff --git a/fuzzterm.c b/fuzzterm.c index 2e680342..b1efeac7 100644 --- a/fuzzterm.c +++ b/fuzzterm.c @@ -119,6 +119,11 @@ static const TermWinVtable fuzz_termwin_vt = { void ldisc_send(Ldisc *ldisc, const void *buf, int len, bool interactive) {} void ldisc_echoedit_update(Ldisc *ldisc) {} +bool ldisc_has_input_buffered(Ldisc *ldisc) { return false; } +LdiscInputToken ldisc_get_input_token(Ldisc *ldisc) +{ unreachable("This fake ldisc never has any buffered input"); } +void ldisc_enable_prompt_callback(Ldisc *ldisc, prompts_t *p) +{ unreachable("This fake ldisc should never be used for user/pass prompts"); } void modalfatalbox(const char *fmt, ...) { exit(0); } void nonfatal(const char *fmt, ...) { } diff --git a/ldisc.c b/ldisc.c index 8c985f06..b549c14a 100644 --- a/ldisc.c +++ b/ldisc.c @@ -17,6 +17,46 @@ struct Ldisc_tag { Backend *backend; Seat *seat; + /* + * When the backend is not reporting true from sendok(), terminal + * input that comes here is stored in this bufchain instead. When + * the backend later decides it wants session input, we empty the + * queue in ldisc_check_sendok_callback(), passing its contents on + * to the backend. Before then, we also provide data from this + * queue to term_get_userpass_input() via ldisc_get_input_token(), + * to be interpreted as user responses to username and password + * prompts during authentication. + * + * Unfortunately, the data stored in this queue is not all of the + * same type: our output to the backend consists of both raw bytes + * sent to backend_send(), and also session specials such as + * SS_EOL and SS_EC. So we have to encode our queued data in a way + * that can represent both. + * + * The encoding is private to this source file, so we can change + * it if necessary and only have to worry about the encode and + * decode functions here. Currently, it is: + * + * - Bytes other than 0xFF are stored literally. + * - The byte 0xFF itself is stored as 0xFF 0xFF. + * - A session special (code, arg) is stored as 0xFF, followed by + * a big-endian 4-byte integer containing code, followed by + * another big-endian 4-byte integer containing arg. + * + * (This representation relies on session special codes being at + * most 0xFEFFFFFF when represented in 32 bits, so that the first + * byte of the 'code' integer can't be confused with the 0xFF + * followup byte indicating a literal 0xFF, But since session + * special codes are defined by an enum counting up from zero, and + * there are only a couple of dozen of them, that shouldn't be a + * problem! Even so, just in case, an assertion checks that at + * encode time.) + */ + bufchain input_queue; + + IdempotentCallback input_queue_callback; + prompts_t *prompts; + /* * Values cached out of conf. */ @@ -87,6 +127,8 @@ static void bsb(Ldisc *ldisc, int n) c_write(ldisc, "\010 \010", 3); } +static void ldisc_input_queue_callback(void *ctx); + #define CTRL(x) (x^'@') #define KCTRL(x) ((x^'@') | 0x100) @@ -103,6 +145,14 @@ Ldisc *ldisc_create(Conf *conf, Terminal *term, Backend *backend, Seat *seat) ldisc->term = term; ldisc->seat = seat; + bufchain_init(&ldisc->input_queue); + + ldisc->prompts = NULL; + ldisc->input_queue_callback.fn = ldisc_input_queue_callback; + ldisc->input_queue_callback.ctx = ldisc; + ldisc->input_queue_callback.queued = false; + bufchain_set_callback(&ldisc->input_queue, &ldisc->input_queue_callback); + ldisc_configure(ldisc, conf); /* Link ourselves into the backend and the terminal */ @@ -125,12 +175,14 @@ void ldisc_configure(Ldisc *ldisc, Conf *conf) void ldisc_free(Ldisc *ldisc) { + bufchain_clear(&ldisc->input_queue); if (ldisc->term) ldisc->term->ldisc = NULL; if (ldisc->backend) backend_provide_ldisc(ldisc->backend, NULL); if (ldisc->buf) sfree(ldisc->buf); + delete_callbacks_for_context(ldisc); sfree(ldisc); } @@ -139,8 +191,169 @@ void ldisc_echoedit_update(Ldisc *ldisc) seat_echoedit_update(ldisc->seat, ECHOING, EDITING); } +void ldisc_enable_prompt_callback(Ldisc *ldisc, prompts_t *prompts) +{ + /* + * Called by the terminal to indicate that there's a prompts_t + * currently in flight, or to indicate that one has just finished + * (by passing NULL). When ldisc->prompts is not null, we notify + * the terminal whenever new data arrives in our input queue, so + * that it can continue the interactive prompting process. + */ + ldisc->prompts = prompts; +} + +static void ldisc_input_queue_callback(void *ctx) +{ + /* + * Toplevel callback that is triggered whenever the input queue + * lengthens. If we're currently processing an interactive prompt, + * we call back the Terminal to tell it to do some more stuff with + * that prompt based on the new input. + */ + Ldisc *ldisc = (Ldisc *)ctx; + if (ldisc->term && ldisc->prompts) { + /* + * The integer return value from this call is discarded, + * because we have no channel to pass it on to the backend + * that originally wanted it. But that's OK, because if the + * return value is >= 0 (that is, the prompts are either + * completely filled in, or aborted by the user), then the + * terminal will notify the callback in the prompts_t, and + * when that calls term_get_userpass_input again, it will + * return the same answer again. + */ + term_get_userpass_input(ldisc->term, ldisc->prompts); + } +} + +static void ldisc_to_backend_raw( + Ldisc *ldisc, const void *vbuf, size_t len) +{ + if (backend_sendok(ldisc->backend)) { + backend_send(ldisc->backend, vbuf, len); + } else { + const char *buf = (const char *)vbuf; + while (len > 0) { + /* + * Encode raw data in input_queue, by storing large chunks + * as long as they don't include 0xFF, and pausing every + * time they do to escape it. + */ + const char *ff = memchr(buf, '\xFF', len); + size_t this_len = ff ? ff - buf : len; + if (this_len > 0) { + bufchain_add(&ldisc->input_queue, buf, len); + } else { + bufchain_add(&ldisc->input_queue, "\xFF\xFF", 2); + this_len = 1; + } + buf += this_len; + len -= this_len; + } + } +} + +static void ldisc_to_backend_special( + Ldisc *ldisc, SessionSpecialCode code, int arg) +{ + if (backend_sendok(ldisc->backend)) { + backend_special(ldisc->backend, code, arg); + } else { + /* + * Encode a session special in input_queue. + */ + unsigned char data[9]; + data[0] = 0xFF; + PUT_32BIT_MSB_FIRST(data+1, code); + PUT_32BIT_MSB_FIRST(data+5, arg); + assert(data[1] != 0xFF && + "SessionSpecialCode encoding collides with FF FF escape"); + bufchain_add(&ldisc->input_queue, data, 9); + } +} + +bool ldisc_has_input_buffered(Ldisc *ldisc) +{ + return bufchain_size(&ldisc->input_queue) > 0; +} + +LdiscInputToken ldisc_get_input_token(Ldisc *ldisc) +{ + assert(bufchain_size(&ldisc->input_queue) > 0 && + "You're not supposed to call this unless there is buffered input!"); + + LdiscInputToken tok; + + char c; + bufchain_fetch_consume(&ldisc->input_queue, &c, 1); + if (c != '\xFF') { + /* A literal non-FF byte */ + tok.is_special = false; + tok.chr = c; + return tok; + } else { + char data[8]; + + /* See if the byte after the FF is also FF, indicating a literal FF */ + bufchain_fetch_consume(&ldisc->input_queue, data, 1); + if (data[0] == '\xFF') { + tok.is_special = false; + tok.chr = '\xFF'; + return tok; + } + + /* If not, get the rest of an 8-byte chunk and decode a special */ + bufchain_fetch_consume(&ldisc->input_queue, data+1, 7); + tok.is_special = true; + tok.code = GET_32BIT_MSB_FIRST(data); + tok.arg = toint(GET_32BIT_MSB_FIRST(data+4)); + return tok; + } +} + +static void ldisc_check_sendok_callback(void *ctx) +{ + Ldisc *ldisc = (Ldisc *)ctx; + + if (!(ldisc->backend && backend_sendok(ldisc->backend))) + return; + + /* + * Flush the ldisc input queue into the backend, which is now + * willing to receive the data. + */ + while (bufchain_size(&ldisc->input_queue) > 0) { + /* + * Process either a chunk of non-special data, or an FF + * escape, depending on whether the first thing we see is an + * FF byte. + */ + ptrlen data = bufchain_prefix(&ldisc->input_queue); + const char *ff = memchr(data.ptr, '\xFF', data.len); + if (ff != data.ptr) { + /* Send a maximal block of data not containing any + * difficult bytes. */ + if (ff) + data.len = ff - (const char *)data.ptr; + backend_send(ldisc->backend, data.ptr, data.len); + bufchain_consume(&ldisc->input_queue, data.len); + } else { + /* Decode either a special or an escaped FF byte. The + * easiest way to do this is to reuse the decoding code + * already in ldisc_get_input_token. */ + LdiscInputToken tok = ldisc_get_input_token(ldisc); + if (tok.is_special) + backend_special(ldisc->backend, tok.code, tok.arg); + else + backend_send(ldisc->backend, &tok.chr, 1); + } + } +} + void ldisc_check_sendok(Ldisc *ldisc) { + queue_toplevel_callback(ldisc_check_sendok_callback, ldisc); } void ldisc_send(Ldisc *ldisc, const void *vbuf, int len, bool interactive) @@ -225,7 +438,7 @@ void ldisc_send(Ldisc *ldisc, const void *vbuf, int len, bool interactive) bsb(ldisc, plen(ldisc, ldisc->buf[ldisc->buflen - 1])); ldisc->buflen--; } - backend_special(ldisc->backend, SS_EL, 0); + ldisc_to_backend_special(ldisc, SS_EL, 0); /* * We don't send IP, SUSP or ABORT if the user has * configured telnet specials off! This breaks @@ -234,11 +447,11 @@ void ldisc_send(Ldisc *ldisc, const void *vbuf, int len, bool interactive) if (!ldisc->telnet_keyboard) goto default_case; if (c == CTRL('C')) - backend_special(ldisc->backend, SS_IP, 0); + ldisc_to_backend_special(ldisc, SS_IP, 0); if (c == CTRL('Z')) - backend_special(ldisc->backend, SS_SUSP, 0); + ldisc_to_backend_special(ldisc, SS_SUSP, 0); if (c == CTRL('\\')) - backend_special(ldisc->backend, SS_ABORT, 0); + ldisc_to_backend_special(ldisc, SS_ABORT, 0); break; case CTRL('R'): /* redraw line */ if (ECHOING) { @@ -253,9 +466,9 @@ void ldisc_send(Ldisc *ldisc, const void *vbuf, int len, bool interactive) break; case CTRL('D'): /* logout or send */ if (ldisc->buflen == 0) { - backend_special(ldisc->backend, SS_EOF, 0); + ldisc_to_backend_special(ldisc, SS_EOF, 0); } else { - backend_send(ldisc->backend, ldisc->buf, ldisc->buflen); + ldisc_to_backend_raw(ldisc, ldisc->buf, ldisc->buflen); ldisc->buflen = 0; } break; @@ -291,14 +504,13 @@ void ldisc_send(Ldisc *ldisc, const void *vbuf, int len, bool interactive) /* FALLTHROUGH */ case KCTRL('M'): /* send with newline */ if (ldisc->buflen > 0) - backend_send(ldisc->backend, - ldisc->buf, ldisc->buflen); + ldisc_to_backend_raw(ldisc, ldisc->buf, ldisc->buflen); if (ldisc->protocol == PROT_RAW) - backend_send(ldisc->backend, "\r\n", 2); + ldisc_to_backend_raw(ldisc, "\r\n", 2); else if (ldisc->protocol == PROT_TELNET && ldisc->telnet_newline) - backend_special(ldisc->backend, SS_EOL, 0); + ldisc_to_backend_special(ldisc, SS_EOL, 0); else - backend_send(ldisc->backend, "\r", 1); + ldisc_to_backend_raw(ldisc, "\r", 1); if (ECHOING) c_write(ldisc, "\r\n", 2); ldisc->buflen = 0; @@ -317,7 +529,7 @@ void ldisc_send(Ldisc *ldisc, const void *vbuf, int len, bool interactive) } } else { if (ldisc->buflen != 0) { - backend_send(ldisc->backend, ldisc->buf, ldisc->buflen); + ldisc_to_backend_raw(ldisc, ldisc->buf, ldisc->buflen); while (ldisc->buflen > 0) { bsb(ldisc, plen(ldisc, ldisc->buf[ldisc->buflen - 1])); ldisc->buflen--; @@ -330,33 +542,33 @@ void ldisc_send(Ldisc *ldisc, const void *vbuf, int len, bool interactive) switch (buf[0]) { case CTRL('M'): if (ldisc->protocol == PROT_TELNET && ldisc->telnet_newline) - backend_special(ldisc->backend, SS_EOL, 0); + ldisc_to_backend_special(ldisc, SS_EOL, 0); else - backend_send(ldisc->backend, "\r", 1); + ldisc_to_backend_raw(ldisc, "\r", 1); break; case CTRL('?'): case CTRL('H'): if (ldisc->telnet_keyboard) { - backend_special(ldisc->backend, SS_EC, 0); + ldisc_to_backend_special(ldisc, SS_EC, 0); break; } case CTRL('C'): if (ldisc->telnet_keyboard) { - backend_special(ldisc->backend, SS_IP, 0); + ldisc_to_backend_special(ldisc, SS_IP, 0); break; } case CTRL('Z'): if (ldisc->telnet_keyboard) { - backend_special(ldisc->backend, SS_SUSP, 0); + ldisc_to_backend_special(ldisc, SS_SUSP, 0); break; } default: - backend_send(ldisc->backend, buf, len); + ldisc_to_backend_raw(ldisc, buf, len); break; } } else - backend_send(ldisc->backend, buf, len); + ldisc_to_backend_raw(ldisc, buf, len); } } } diff --git a/noterm.c b/noterm.c index 4ca99fa2..b5329aec 100644 --- a/noterm.c +++ b/noterm.c @@ -9,3 +9,8 @@ void term_nopaste(Terminal *term) { } + +int term_get_userpass_input(Terminal *term, prompts_t *p) +{ + return 0; +} diff --git a/otherbackends/rlogin.c b/otherbackends/rlogin.c index 11deb89c..09f1ab1d 100644 --- a/otherbackends/rlogin.c +++ b/otherbackends/rlogin.c @@ -35,6 +35,7 @@ struct Rlogin { static void rlogin_startup(Rlogin *rlogin, int prompt_result, const char *ruser); +static void rlogin_try_username_prompt(void *ctx); static void c_write(Rlogin *rlogin, const void *buf, size_t len) { @@ -80,18 +81,11 @@ static void rlogin_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, rlogin->prompt->to_server = true; rlogin->prompt->from_server = false; rlogin->prompt->name = dupstr("Rlogin login name"); + rlogin->prompt->callback = rlogin_try_username_prompt; + rlogin->prompt->callback_ctx = rlogin; add_prompt(rlogin->prompt, dupstr("rlogin username: "), true); - - int ret = seat_get_userpass_input(rlogin->seat, rlogin->prompt, - NULL); - if (ret >= 0) { - /* Next terminal output will come from server */ - seat_set_trust_status(rlogin->seat, false); - rlogin_startup(rlogin, ret, prompt_get_result_ref( - rlogin->prompt->prompts[0])); - } + rlogin_try_username_prompt(rlogin); } - } } @@ -300,45 +294,36 @@ static void rlogin_reconfig(Backend *be, Conf *conf) { } +static void rlogin_try_username_prompt(void *ctx) +{ + Rlogin *rlogin = (Rlogin *)ctx; + + int ret = seat_get_userpass_input(rlogin->seat, rlogin->prompt); + if (ret < 0) + return; + + /* Next terminal output will come from server */ + seat_set_trust_status(rlogin->seat, false); + + /* Send the rlogin setup protocol data, and then we're ready to + * start receiving normal input to send down the wire, which + * rlogin_startup will signal to rlogin_sendok by nulling out + * rlogin->prompt. */ + rlogin_startup( + rlogin, ret, prompt_get_result_ref(rlogin->prompt->prompts[0])); +} + /* * Called to send data down the rlogin connection. */ static void rlogin_send(Backend *be, const char *buf, size_t len) { Rlogin *rlogin = container_of(be, Rlogin, backend); - bufchain bc; if (rlogin->s == NULL) return; - bufchain_init(&bc); - bufchain_add(&bc, buf, len); - - if (rlogin->prompt) { - /* - * We're still prompting for a username, and aren't talking - * directly to the network connection yet. - */ - int ret = seat_get_userpass_input(rlogin->seat, rlogin->prompt, &bc); - if (ret >= 0) { - /* Next terminal output will come from server */ - seat_set_trust_status(rlogin->seat, false); - rlogin_startup(rlogin, ret, prompt_get_result_ref( - rlogin->prompt->prompts[0])); - /* that nulls out rlogin->prompt, so then we'll start sending - * data down the wire in the obvious way */ - } - } - - if (!rlogin->prompt) { - while (bufchain_size(&bc) > 0) { - ptrlen data = bufchain_prefix(&bc); - rlogin->bufsize = sk_write(rlogin->s, data.ptr, data.len); - bufchain_consume(&bc, len); - } - } - - bufchain_clear(&bc); + rlogin->bufsize = sk_write(rlogin->s, buf, len); } /* @@ -398,8 +383,12 @@ static bool rlogin_connected(Backend *be) static bool rlogin_sendok(Backend *be) { + /* + * We only want to receive input data if the socket is connected + * and we're not still at the username prompt stage. + */ Rlogin *rlogin = container_of(be, Rlogin, backend); - return rlogin->socket_connected; + return rlogin->socket_connected && !rlogin->prompt; } static void rlogin_unthrottle(Backend *be, size_t backlog) diff --git a/putty.h b/putty.h index fcfff4b6..417579e4 100644 --- a/putty.h +++ b/putty.h @@ -741,6 +741,11 @@ extern const int be_default_protocol; */ extern const char *const appname; +/* + * Used by callback.c; declared up here so that prompts_t can use it + */ +typedef void (*toplevel_callback_fn_t)(void *ctx); + /* * Mechanism for getting text strings such as usernames and passwords * from the front-end. @@ -790,6 +795,17 @@ typedef struct { prompt_t **prompts; void *data; /* slot for housekeeping data, managed by * seat_get_userpass_input(); initially NULL */ + int idata; /* another slot private to the implementation */ + + /* + * Callback you can fill in to be notified when all the prompts' + * responses are available. After you receive this notification, a + * further call to the get_userpass_input function will return the + * final state of the prompts system, which is guaranteed not to + * be negative for 'still ongoing'. + */ + toplevel_callback_fn_t callback; + void *callback_ctx; } prompts_t; prompts_t *new_prompts(void); void add_prompt(prompts_t *p, char *promptstr, bool echo); @@ -907,13 +923,7 @@ struct SeatVtable { /* * Try to get answers from a set of interactive login prompts. The - * prompts are provided in 'p'; the bufchain 'input' holds the - * data currently outstanding in the session's normal standard- - * input channel. Seats may implement this function by consuming - * data from 'input' (e.g. password prompts in GUI PuTTY, - * displayed in the same terminal as the subsequent session), or - * by doing something entirely different (e.g. directly - * interacting with standard I/O, or putting up a dialog box). + * prompts are provided in 'p'. * * A positive return value means that all prompts have had answers * filled in. A zero return means that the user performed a @@ -939,7 +949,7 @@ struct SeatVtable { * ever do want to move password prompts into a dialog box, I'll * want a backend method for sending that notification.) */ - int (*get_userpass_input)(Seat *seat, prompts_t *p, bufchain *input); + int (*get_userpass_input)(Seat *seat, prompts_t *p); /* * Notify the seat that the main session channel has been @@ -1155,9 +1165,8 @@ static inline bool seat_eof(Seat *seat) { return seat->vt->eof(seat); } static inline void seat_sent(Seat *seat, size_t bufsize) { seat->vt->sent(seat, bufsize); } -static inline int seat_get_userpass_input( - Seat *seat, prompts_t *p, bufchain *input) -{ return seat->vt->get_userpass_input(seat, p, input); } +static inline int seat_get_userpass_input(Seat *seat, prompts_t *p) +{ return seat->vt->get_userpass_input(seat, p); } static inline void seat_notify_session_started(Seat *seat) { seat->vt->notify_session_started(seat); } static inline void seat_notify_remote_exit(Seat *seat) @@ -1233,7 +1242,7 @@ size_t nullseat_output( Seat *seat, bool is_stderr, const void *data, size_t len); bool nullseat_eof(Seat *seat); void nullseat_sent(Seat *seat, size_t bufsize); -int nullseat_get_userpass_input(Seat *seat, prompts_t *p, bufchain *input); +int nullseat_get_userpass_input(Seat *seat, prompts_t *p); void nullseat_notify_session_started(Seat *seat); void nullseat_notify_remote_exit(Seat *seat); void nullseat_notify_remote_disconnect(Seat *seat); @@ -1292,7 +1301,7 @@ bool console_can_set_trust_status(Seat *seat); /* * Other centralised seat functions. */ -int filexfer_get_userpass_input(Seat *seat, prompts_t *p, bufchain *input); +int filexfer_get_userpass_input(Seat *seat, prompts_t *p); bool cmdline_seat_verbose(Seat *seat); /* @@ -1888,7 +1897,7 @@ void term_provide_backend(Terminal *term, Backend *backend); void term_provide_logctx(Terminal *term, LogContext *logctx); void term_set_focus(Terminal *term, bool has_focus); char *term_get_ttymode(Terminal *term, const char *mode); -int term_get_userpass_input(Terminal *term, prompts_t *p, bufchain *input); +int term_get_userpass_input(Terminal *term, prompts_t *p); void term_set_trust_status(Terminal *term, bool trusted); void term_keyinput(Terminal *, int codepage, const void *buf, int len); void term_keyinputw(Terminal *, const wchar_t * widebuf, int len); @@ -2055,6 +2064,28 @@ void ldisc_configure(Ldisc *, Conf *); void ldisc_free(Ldisc *); void ldisc_send(Ldisc *, const void *buf, int len, bool interactive); void ldisc_echoedit_update(Ldisc *); +typedef struct LdiscInputToken { + /* + * Structure that encodes any single item of data that Ldisc can + * buffer: either a single character of raw data, or a session + * special. + */ + bool is_special; + union { + struct { + /* if is_special == false */ + char chr; + }; + struct { + /* if is_special == true */ + SessionSpecialCode code; + int arg; + }; + }; +} LdiscInputToken; +bool ldisc_has_input_buffered(Ldisc *); +LdiscInputToken ldisc_get_input_token(Ldisc *); /* asserts there is input */ +void ldisc_enable_prompt_callback(Ldisc *, prompts_t *); void ldisc_check_sendok(Ldisc *); /* @@ -2472,7 +2503,6 @@ unsigned long timing_last_clock(void); * loop, as in PSFTP, for example - if a callback has run then perhaps * it might have done whatever the loop's caller was waiting for. */ -typedef void (*toplevel_callback_fn_t)(void *ctx); void queue_toplevel_callback(toplevel_callback_fn_t fn, void *ctx); bool run_toplevel_callbacks(void); bool toplevel_callback_pending(void); diff --git a/ssh/common.c b/ssh/common.c index e421d90e..b6391729 100644 --- a/ssh/common.c +++ b/ssh/common.c @@ -737,6 +737,19 @@ size_t ssh_ppl_default_queued_data_size(PacketProtocolLayer *ppl) return ppl->out_pq->pqb.total_size; } +static void ssh_ppl_prompts_callback(void *ctx) +{ + ssh_ppl_process_queue((PacketProtocolLayer *)ctx); +} + +prompts_t *ssh_ppl_new_prompts(PacketProtocolLayer *ppl) +{ + prompts_t *p = new_prompts(); + p->callback = ssh_ppl_prompts_callback; + p->callback_ctx = ppl; + return p; +} + /* ---------------------------------------------------------------------- * Common helper functions for clients and implementations of * BinaryPacketProtocol. diff --git a/ssh/connection1.c b/ssh/connection1.c index 071e0139..bff09f51 100644 --- a/ssh/connection1.c +++ b/ssh/connection1.c @@ -370,7 +370,7 @@ static void ssh1_connection_process_queue(PacketProtocolLayer *ppl) * connection-sharing downstream). */ if (ssh1_connection_need_antispoof_prompt(s)) { - s->antispoof_prompt = new_prompts(); + s->antispoof_prompt = ssh_ppl_new_prompts(&s->ppl); s->antispoof_prompt->to_server = true; s->antispoof_prompt->from_server = false; s->antispoof_prompt->name = dupstr("Authentication successful"); @@ -378,19 +378,11 @@ static void ssh1_connection_process_queue(PacketProtocolLayer *ppl) s->antispoof_prompt, dupstr("Access granted. Press Return to begin session. "), false); s->antispoof_ret = seat_get_userpass_input( - s->ppl.seat, s->antispoof_prompt, NULL); - while (1) { - while (s->antispoof_ret < 0 && - bufchain_size(s->ppl.user_input) > 0) - s->antispoof_ret = seat_get_userpass_input( - s->ppl.seat, s->antispoof_prompt, s->ppl.user_input); - - if (s->antispoof_ret >= 0) - break; - - s->want_user_input = true; + s->ppl.seat, s->antispoof_prompt); + while (s->antispoof_ret < 0) { crReturnV; - s->want_user_input = false; + s->antispoof_ret = seat_get_userpass_input( + s->ppl.seat, s->antispoof_prompt); } free_prompts(s->antispoof_prompt); s->antispoof_prompt = NULL; diff --git a/ssh/connection2.c b/ssh/connection2.c index 86e26f4b..df542f63 100644 --- a/ssh/connection2.c +++ b/ssh/connection2.c @@ -982,7 +982,7 @@ static void ssh2_connection_process_queue(PacketProtocolLayer *ppl) * connection-sharing downstream). */ if (ssh2_connection_need_antispoof_prompt(s)) { - s->antispoof_prompt = new_prompts(); + s->antispoof_prompt = ssh_ppl_new_prompts(&s->ppl); s->antispoof_prompt->to_server = true; s->antispoof_prompt->from_server = false; s->antispoof_prompt->name = dupstr("Authentication successful"); @@ -990,19 +990,11 @@ static void ssh2_connection_process_queue(PacketProtocolLayer *ppl) s->antispoof_prompt, dupstr("Access granted. Press Return to begin session. "), false); s->antispoof_ret = seat_get_userpass_input( - s->ppl.seat, s->antispoof_prompt, NULL); - while (1) { - while (s->antispoof_ret < 0 && - bufchain_size(s->ppl.user_input) > 0) - s->antispoof_ret = seat_get_userpass_input( - s->ppl.seat, s->antispoof_prompt, s->ppl.user_input); - - if (s->antispoof_ret >= 0) - break; - - s->want_user_input = true; + s->ppl.seat, s->antispoof_prompt); + while (s->antispoof_ret < 0) { crReturnV; - s->want_user_input = false; + s->antispoof_ret = seat_get_userpass_input( + s->ppl.seat, s->antispoof_prompt); } free_prompts(s->antispoof_prompt); s->antispoof_prompt = NULL; diff --git a/ssh/login1.c b/ssh/login1.c index e0230d81..a700e02a 100644 --- a/ssh/login1.c +++ b/ssh/login1.c @@ -404,25 +404,16 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl) ppl_logevent("Successfully started encryption"); if ((s->username = get_remote_username(s->conf)) == NULL) { - s->cur_prompt = new_prompts(); + s->cur_prompt = ssh_ppl_new_prompts(&s->ppl); s->cur_prompt->to_server = true; s->cur_prompt->from_server = false; s->cur_prompt->name = dupstr("SSH login name"); add_prompt(s->cur_prompt, dupstr("login as: "), true); - s->userpass_ret = seat_get_userpass_input( - s->ppl.seat, s->cur_prompt, NULL); - while (1) { - while (s->userpass_ret < 0 && - bufchain_size(s->ppl.user_input) > 0) - s->userpass_ret = seat_get_userpass_input( - s->ppl.seat, s->cur_prompt, s->ppl.user_input); - - if (s->userpass_ret >= 0) - break; - - s->want_user_input = true; + s->userpass_ret = seat_get_userpass_input(s->ppl.seat, s->cur_prompt); + while (s->userpass_ret < 0) { crReturnV; - s->want_user_input = false; + s->userpass_ret = seat_get_userpass_input( + s->ppl.seat, s->cur_prompt); } if (!s->userpass_ret) { /* @@ -707,7 +698,7 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl) ppl_printf("No passphrase required.\r\n"); passphrase = NULL; } else { - s->cur_prompt = new_prompts(); + s->cur_prompt = ssh_ppl_new_prompts(&s->ppl); s->cur_prompt->to_server = false; s->cur_prompt->from_server = false; s->cur_prompt->name = dupstr("SSH key passphrase"); @@ -715,19 +706,11 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl) dupprintf("Passphrase for key \"%s\": ", s->publickey_comment), false); s->userpass_ret = seat_get_userpass_input( - s->ppl.seat, s->cur_prompt, NULL); - while (1) { - while (s->userpass_ret < 0 && - bufchain_size(s->ppl.user_input) > 0) - s->userpass_ret = seat_get_userpass_input( - s->ppl.seat, s->cur_prompt, s->ppl.user_input); - - if (s->userpass_ret >= 0) - break; - - s->want_user_input = true; + s->ppl.seat, s->cur_prompt); + while (s->userpass_ret < 0) { crReturnV; - s->want_user_input = false; + s->userpass_ret = seat_get_userpass_input( + s->ppl.seat, s->cur_prompt); } if (!s->userpass_ret) { /* Failed to get a passphrase. Terminate. */ @@ -846,7 +829,7 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl) /* * Otherwise, try various forms of password-like authentication. */ - s->cur_prompt = new_prompts(); + s->cur_prompt = ssh_ppl_new_prompts(&s->ppl); if (conf_get_bool(s->conf, CONF_try_tis_auth) && (s->supported_auths_mask & (1 << SSH1_AUTH_TIS)) && @@ -977,20 +960,11 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl) * or CryptoCard exchange if we're doing TIS or CryptoCard * authentication. */ - s->userpass_ret = seat_get_userpass_input( - s->ppl.seat, s->cur_prompt, NULL); - while (1) { - while (s->userpass_ret < 0 && - bufchain_size(s->ppl.user_input) > 0) - s->userpass_ret = seat_get_userpass_input( - s->ppl.seat, s->cur_prompt, s->ppl.user_input); - - if (s->userpass_ret >= 0) - break; - - s->want_user_input = true; + s->userpass_ret = seat_get_userpass_input(s->ppl.seat, s->cur_prompt); + while (s->userpass_ret < 0) { crReturnV; - s->want_user_input = false; + s->userpass_ret = seat_get_userpass_input( + s->ppl.seat, s->cur_prompt); } if (!s->userpass_ret) { /* diff --git a/ssh/ppl.h b/ssh/ppl.h index cd3e4694..5ba1e9d3 100644 --- a/ssh/ppl.h +++ b/ssh/ppl.h @@ -154,6 +154,11 @@ void ssh2_transport_notify_auth_done(PacketProtocolLayer *ssh2_transport_ptr); * be handled by ssh2connection. */ bool ssh2_common_filter_queue(PacketProtocolLayer *ppl); +/* Method for making a prompts_t in such a way that it will install a + * callback that causes this PPL's process_queue method to be called + * when asynchronous prompt input completes. */ +prompts_t *ssh_ppl_new_prompts(PacketProtocolLayer *ppl); + /* Methods for ssh1login to pass protocol flags to ssh1connection */ void ssh1_connection_set_protoflags( PacketProtocolLayer *ppl, int local, int remote); diff --git a/ssh/userauth2-client.c b/ssh/userauth2-client.c index 0fa1df06..51872ebf 100644 --- a/ssh/userauth2-client.c +++ b/ssh/userauth2-client.c @@ -442,25 +442,17 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) * it again. */ } else if ((s->username = s->default_username) == NULL) { - s->cur_prompt = new_prompts(); + s->cur_prompt = ssh_ppl_new_prompts(&s->ppl); s->cur_prompt->to_server = true; s->cur_prompt->from_server = false; s->cur_prompt->name = dupstr("SSH login name"); add_prompt(s->cur_prompt, dupstr("login as: "), true); s->userpass_ret = seat_get_userpass_input( - s->ppl.seat, s->cur_prompt, NULL); - while (1) { - while (s->userpass_ret < 0 && - bufchain_size(s->ppl.user_input) > 0) - s->userpass_ret = seat_get_userpass_input( - s->ppl.seat, s->cur_prompt, s->ppl.user_input); - - if (s->userpass_ret >= 0) - break; - - s->want_user_input = true; + s->ppl.seat, s->cur_prompt); + while (s->userpass_ret < 0) { crReturnV; - s->want_user_input = false; + s->userpass_ret = seat_get_userpass_input( + s->ppl.seat, s->cur_prompt); } if (!s->userpass_ret) { /* @@ -913,7 +905,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) /* * Get a passphrase from the user. */ - s->cur_prompt = new_prompts(); + s->cur_prompt = ssh_ppl_new_prompts(&s->ppl); s->cur_prompt->to_server = false; s->cur_prompt->from_server = false; s->cur_prompt->name = dupstr("SSH key passphrase"); @@ -922,20 +914,11 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) s->publickey_comment), false); s->userpass_ret = seat_get_userpass_input( - s->ppl.seat, s->cur_prompt, NULL); - while (1) { - while (s->userpass_ret < 0 && - bufchain_size(s->ppl.user_input) > 0) - s->userpass_ret = seat_get_userpass_input( - s->ppl.seat, s->cur_prompt, - s->ppl.user_input); - - if (s->userpass_ret >= 0) - break; - - s->want_user_input = true; + s->ppl.seat, s->cur_prompt); + while (s->userpass_ret < 0) { crReturnV; - s->want_user_input = false; + s->userpass_ret = seat_get_userpass_input( + s->ppl.seat, s->cur_prompt); } if (!s->userpass_ret) { /* Failed to get a passphrase. Terminate. */ @@ -1304,7 +1287,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) name = get_string(pktin); inst = get_string(pktin); get_string(pktin); /* skip language tag */ - s->cur_prompt = new_prompts(); + s->cur_prompt = ssh_ppl_new_prompts(&s->ppl); s->cur_prompt->to_server = true; s->cur_prompt->from_server = true; @@ -1408,19 +1391,11 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) * user's response(s). */ s->userpass_ret = seat_get_userpass_input( - s->ppl.seat, s->cur_prompt, NULL); - while (1) { - while (s->userpass_ret < 0 && - bufchain_size(s->ppl.user_input) > 0) - s->userpass_ret = seat_get_userpass_input( - s->ppl.seat, s->cur_prompt, s->ppl.user_input); - - if (s->userpass_ret >= 0) - break; - - s->want_user_input = true; + s->ppl.seat, s->cur_prompt); + while (s->userpass_ret < 0) { crReturnV; - s->want_user_input = false; + s->userpass_ret = seat_get_userpass_input( + s->ppl.seat, s->cur_prompt); } if (!s->userpass_ret) { /* @@ -1486,7 +1461,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) s->ppl.bpp->pls->actx = SSH2_PKTCTX_PASSWORD; - s->cur_prompt = new_prompts(); + s->cur_prompt = ssh_ppl_new_prompts(&s->ppl); s->cur_prompt->to_server = true; s->cur_prompt->from_server = false; s->cur_prompt->name = dupstr("SSH password"); @@ -1495,19 +1470,11 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) false); s->userpass_ret = seat_get_userpass_input( - s->ppl.seat, s->cur_prompt, NULL); - while (1) { - while (s->userpass_ret < 0 && - bufchain_size(s->ppl.user_input) > 0) - s->userpass_ret = seat_get_userpass_input( - s->ppl.seat, s->cur_prompt, s->ppl.user_input); - - if (s->userpass_ret >= 0) - break; - - s->want_user_input = true; + s->ppl.seat, s->cur_prompt); + while (s->userpass_ret < 0) { crReturnV; - s->want_user_input = false; + s->userpass_ret = seat_get_userpass_input( + s->ppl.seat, s->cur_prompt); } if (!s->userpass_ret) { /* @@ -1581,7 +1548,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) prompt = get_string(pktin); - s->cur_prompt = new_prompts(); + s->cur_prompt = ssh_ppl_new_prompts(&s->ppl); s->cur_prompt->to_server = true; s->cur_prompt->from_server = false; s->cur_prompt->name = dupstr("New SSH password"); @@ -1613,20 +1580,11 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) */ while (!got_new) { s->userpass_ret = seat_get_userpass_input( - s->ppl.seat, s->cur_prompt, NULL); - while (1) { - while (s->userpass_ret < 0 && - bufchain_size(s->ppl.user_input) > 0) - s->userpass_ret = seat_get_userpass_input( - s->ppl.seat, s->cur_prompt, - s->ppl.user_input); - - if (s->userpass_ret >= 0) - break; - - s->want_user_input = true; + s->ppl.seat, s->cur_prompt); + while (s->userpass_ret < 0) { crReturnV; - s->want_user_input = false; + s->userpass_ret = seat_get_userpass_input( + s->ppl.seat, s->cur_prompt); } if (!s->userpass_ret) { /* diff --git a/sshproxy.c b/sshproxy.c index 433585ad..3d9665eb 100644 --- a/sshproxy.c +++ b/sshproxy.c @@ -276,8 +276,7 @@ static void sshproxy_notify_remote_disconnect(Seat *seat) sshproxy_eof(seat); } -static int sshproxy_get_userpass_input(Seat *seat, prompts_t *p, - bufchain *input) +static int sshproxy_get_userpass_input(Seat *seat, prompts_t *p) { /* * TODO: if we had access to the outer Seat, we could pass on this diff --git a/terminal.c b/terminal.c index 663e0f02..e94e3634 100644 --- a/terminal.c +++ b/terminal.c @@ -7519,18 +7519,45 @@ static inline void term_write(Terminal *term, ptrlen data) term_data(term, false, data.ptr, data.len); } +/* + * Signal that a prompts_t is done. This involves sending a + * notification to the caller, and also turning off our own callback + * that listens for more data arriving in the ldisc's input queue. + */ +static inline int signal_prompts_t(Terminal *term, prompts_t *p, int result) +{ + assert(p->callback && "Asynchronous userpass input requires a callback"); + queue_toplevel_callback(p->callback, p->callback_ctx); + ldisc_enable_prompt_callback(term->ldisc, NULL); + p->idata = result; + return result; +} + /* * Process some terminal data in the course of username/password * input. */ -int term_get_userpass_input(Terminal *term, prompts_t *p, bufchain *input) +int term_get_userpass_input(Terminal *term, prompts_t *p) { + if (!term->ldisc) { + /* Can't handle interactive prompts without an ldisc */ + return signal_prompts_t(term, p, 0); + } + + if (p->idata >= 0) { + /* We've already finished these prompts, so return the same + * result again */ + return p->idata; + } + 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); + p->idata = -1; s->curr_prompt = 0; s->done_prompt = false; /* We only print the `name' caption if we have to... */ @@ -7569,12 +7596,26 @@ int term_get_userpass_input(Terminal *term, prompts_t *p, bufchain *input) /* Breaking out here ensures that the prompt is printed even * if we're now waiting for user data. */ - if (!input || !bufchain_size(input)) break; + if (!ldisc_has_input_buffered(term->ldisc)) + break; /* FIXME: should we be using local-line-editing code instead? */ - while (!finished_prompt && bufchain_size(input) > 0) { + while (!finished_prompt && ldisc_has_input_buffered(term->ldisc)) { + LdiscInputToken tok = ldisc_get_input_token(term->ldisc); + char c; - bufchain_fetch_consume(input, &c, 1); + if (tok.is_special) { + switch (tok.code) { + case SS_EOL: c = 13; break; + case SS_EC: c = 8; break; + case SS_IP: c = 3; break; + case SS_EOF: c = 3; break; + default: continue; + } + } else { + c = tok.chr; + } + switch (c) { case 10: case 13: @@ -7606,7 +7647,7 @@ int term_get_userpass_input(Terminal *term, prompts_t *p, bufchain *input) term_write(term, PTRLEN_LITERAL("\r\n")); sfree(s); p->data = NULL; - return 0; /* user abort */ + return signal_prompts_t(term, p, 0); /* user abort */ default: /* * This simplistic check for printability is disabled @@ -7626,11 +7667,12 @@ int term_get_userpass_input(Terminal *term, prompts_t *p, bufchain *input) } if (s->curr_prompt < p->n_prompts) { + ldisc_enable_prompt_callback(term->ldisc, p); return -1; /* more data required */ } else { sfree(s); p->data = NULL; - return +1; /* all done */ + return signal_prompts_t(term, p, +1); /* all done */ } } diff --git a/unix/plink.c b/unix/plink.c index f8acd5ec..46f2da85 100644 --- a/unix/plink.c +++ b/unix/plink.c @@ -370,7 +370,7 @@ static bool plink_eof(Seat *seat) return false; /* do not respond to incoming EOF with outgoing */ } -static int plink_get_userpass_input(Seat *seat, prompts_t *p, bufchain *input) +static int plink_get_userpass_input(Seat *seat, prompts_t *p) { int ret; ret = cmdline_get_passwd_input(p); diff --git a/unix/sftp.c b/unix/sftp.c index 89a81c92..331cbc70 100644 --- a/unix/sftp.c +++ b/unix/sftp.c @@ -63,7 +63,7 @@ Filename *platform_default_filename(const char *name) return filename_from_str(""); } -int filexfer_get_userpass_input(Seat *seat, prompts_t *p, bufchain *input) +int filexfer_get_userpass_input(Seat *seat, prompts_t *p) { int ret; ret = cmdline_get_passwd_input(p); diff --git a/unix/window.c b/unix/window.c index cb6e831d..e1e36fa5 100644 --- a/unix/window.c +++ b/unix/window.c @@ -331,14 +331,13 @@ static bool gtk_seat_eof(Seat *seat) return true; /* do respond to incoming EOF with outgoing */ } -static int gtk_seat_get_userpass_input(Seat *seat, prompts_t *p, - bufchain *input) +static int gtk_seat_get_userpass_input(Seat *seat, prompts_t *p) { GtkFrontend *inst = container_of(seat, GtkFrontend, seat); int ret; ret = cmdline_get_passwd_input(p); if (ret == -1) - ret = term_get_userpass_input(inst->term, p, input); + ret = term_get_userpass_input(inst->term, p); return ret; } diff --git a/utils/nullseat.c b/utils/nullseat.c index 907d9176..564225e5 100644 --- a/utils/nullseat.c +++ b/utils/nullseat.c @@ -8,8 +8,7 @@ size_t nullseat_output( Seat *seat, bool is_stderr, const void *data, size_t len) { return 0; } bool nullseat_eof(Seat *seat) { return true; } void nullseat_sent(Seat *seat, size_t bufsize) {} -int nullseat_get_userpass_input( - Seat *seat, prompts_t *p, bufchain *input) { return 0; } +int nullseat_get_userpass_input(Seat *seat, prompts_t *p) { return 0; } void nullseat_notify_session_started(Seat *seat) {} void nullseat_notify_remote_exit(Seat *seat) {} void nullseat_notify_remote_disconnect(Seat *seat) {} diff --git a/utils/prompts.c b/utils/prompts.c index f37bde59..8784130d 100644 --- a/utils/prompts.c +++ b/utils/prompts.c @@ -14,6 +14,8 @@ prompts_t *new_prompts(void) p->to_server = true; /* to be on the safe side */ p->name = p->instruction = NULL; p->name_reqd = p->instr_reqd = false; + p->callback = NULL; + p->callback_ctx = NULL; return p; } diff --git a/utils/tempseat.c b/utils/tempseat.c index 22cf642c..aa3fbe5a 100644 --- a/utils/tempseat.c +++ b/utils/tempseat.c @@ -177,8 +177,7 @@ static bool tempseat_can_set_trust_status(Seat *seat) * for the network connection. */ -static int tempseat_get_userpass_input(Seat *seat, prompts_t *p, - bufchain *input) +static int tempseat_get_userpass_input(Seat *seat, prompts_t *p) { /* * Interactive prompts of this nature are a thing that a backend diff --git a/windows/plink.c b/windows/plink.c index 1587cbd3..bbed994d 100644 --- a/windows/plink.c +++ b/windows/plink.c @@ -64,7 +64,7 @@ static bool plink_eof(Seat *seat) return false; /* do not respond to incoming EOF with outgoing */ } -static int plink_get_userpass_input(Seat *seat, prompts_t *p, bufchain *input) +static int plink_get_userpass_input(Seat *seat, prompts_t *p) { int ret; ret = cmdline_get_passwd_input(p); diff --git a/windows/sftp.c b/windows/sftp.c index e316f8f8..b2e90faa 100644 --- a/windows/sftp.c +++ b/windows/sftp.c @@ -12,7 +12,7 @@ #include "ssh.h" #include "security-api.h" -int filexfer_get_userpass_input(Seat *seat, prompts_t *p, bufchain *input) +int filexfer_get_userpass_input(Seat *seat, prompts_t *p) { int ret; ret = cmdline_get_passwd_input(p); diff --git a/windows/window.c b/windows/window.c index 6c288600..5aed396c 100644 --- a/windows/window.c +++ b/windows/window.c @@ -317,8 +317,7 @@ static StripCtrlChars *win_seat_stripctrl_new( static size_t win_seat_output( Seat *seat, bool is_stderr, const void *, size_t); static bool win_seat_eof(Seat *seat); -static int win_seat_get_userpass_input( - Seat *seat, prompts_t *p, bufchain *input); +static int win_seat_get_userpass_input(Seat *seat, prompts_t *p); static void win_seat_notify_remote_exit(Seat *seat); static void win_seat_connection_fatal(Seat *seat, const char *msg); static void win_seat_update_specials_menu(Seat *seat); @@ -5746,13 +5745,12 @@ static bool win_seat_eof(Seat *seat) return true; /* do respond to incoming EOF with outgoing */ } -static int win_seat_get_userpass_input( - Seat *seat, prompts_t *p, bufchain *input) +static int win_seat_get_userpass_input(Seat *seat, prompts_t *p) { int ret; ret = cmdline_get_passwd_input(p); if (ret == -1) - ret = term_get_userpass_input(term, p, input); + ret = term_get_userpass_input(term, p); return ret; } -- cgit v1.2.3 From d1374c5890c945dc50cbeedca502b8d4100845d6 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Tue, 14 Sep 2021 12:14:37 +0100 Subject: SshProxy: pass through userpass input prompts. This is the big payoff from the huge refactoring in the previous commit: now it's possible for proxy implementations to present their own interactive prompts via the seat_get_userpass_input system, because the input data that those prompts will need to consume is now always somewhere sensible (and hasn't, for example, already been put on to the main backend's input queue where the proxy can't get at it). Like the GUI dialog prompts, this isn't yet fully polished, because the login and password prompts are very unclear about which SSH server they're talking about. But at least you now _can_ log in manually with a username and password to two SSH servers in succession (if you know which server(s) you're expecting to see prompts from), and that was the really hard part. --- sshproxy.c | 65 ++++++++++++++++++++++++++++++-------------------------------- 1 file changed, 31 insertions(+), 34 deletions(-) diff --git a/sshproxy.c b/sshproxy.c index 3d9665eb..96667212 100644 --- a/sshproxy.c +++ b/sshproxy.c @@ -16,17 +16,25 @@ const bool ssh_proxy_supported = true; /* * TODO for future work: * - * At present, this use of SSH as a proxy is not fully interactive. - * We're borrowing the main backend's LogPolicy for queries like - * askappend(), and we're borrowing the main backend's Seat for host - * key prompts and weak-crypto warnings, but one thing we still don't - * have is a functioning implementation of seat_get_userpass_input - * that can display the proxy SSH connection's password prompts (or - * similar) in the terminal window before handing the terminal back to - * the main connection. + * All the interactive prompts we present to the main Seat - the host + * key and weak-crypto dialog boxes, and all prompts presented via the + * userpass_input system - need adjusting so that it's clear to the + * user _which_ SSH connection they come from. At the moment, you just + * get shown a host key fingerprint or a cryptic "login as:" prompt, + * and you have to guess which server you're currently supposed to be + * interpreting it relative to. * - * Also, the host key and weak-crypto prompts need adjusting so that - * it's clear to the user which SSH connection they come from. + * If the user manually aborts the attempt to make the proxy SSH + * connection (e.g. by hitting ^C at a userpass prompt, or refusing to + * accept the proxy server's host key), then an assertion failure + * occurs, because the main backend receives an indication of + * connection failure that causes it to want to call + * seat_connection_fatal("Remote side unexpectedly closed network + * connection"), which fails an assertion in tempseat.c because that + * method of TempSeat expects never to be called. To fix this, I think + * we need to distinguish 'connection attempt unexpectedly failed, in + * a way the user needs to be told about' from 'connection attempt was + * aborted by deliberate user action, so the user already knows'. */ typedef struct SshProxy { @@ -278,32 +286,21 @@ static void sshproxy_notify_remote_disconnect(Seat *seat) static int sshproxy_get_userpass_input(Seat *seat, prompts_t *p) { + SshProxy *sp = container_of(seat, SshProxy, seat); + + if (sp->clientseat) { + /* + * If we have access to the outer Seat, pass this prompt + * request on to it. FIXME: appropriately adjusted + */ + return seat_get_userpass_input(sp->clientseat, p); + } + /* - * TODO: if we had access to the outer Seat, we could pass on this - * prompts_t to *its* get_userpass_input method, appropriately - * adjusted to indicate that it comes from the proxy SSH - * connection. (But we'd still have to have this code as a - * fallback in case there isn't a Seat available.) - * - * Design question: how does that 'appropriately adjusted' - * interact with the possibility of multiple calls to this - * function with the same prompts_t? Should we redo the - * modification every time? Or provide some kind of callback that - * userauth can use to do it once up front? Or something else? - * - * Also, we'll need to be sure that the outer Seat is in the - * correct trust status before passing prompts along to it. For - * SSH, you'd certainly expect that to be OK, on the basis that - * the primary SSH connection won't set the Seat to untrusted mode - * until it finishes its userauth phase, which won't happen until - * long after _we've_ finished _our_ userauth phase. But what if - * the primary connection is something like Telnet, which goes - * into untrusted mode during startup? We may find we have to do - * some more complicated piece of plumbing that lets us take some - * kind of a preliminary lease on the Seat and defer anything the - * primary backend tries to do to it. + * Otherwise, behave as if noninteractive (like plink -batch): + * reject all attempts to present a prompt to the user, and log in + * the Event Log to say why not. */ - SshProxy *sp = container_of(seat, SshProxy, seat); sshproxy_error(sp, "Unable to provide interactive authentication " "requested by proxy SSH connection"); return 0; -- cgit v1.2.3 From 3037258808c21db72f7d94c934bbc6ee70bfa821 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Tue, 14 Sep 2021 14:00:05 +0100 Subject: Localise user_input to SSH connection layers. Now that the SSH backend's user_input bufchain is no longer needed for handling userpass input, it doesn't have to be awkwardly shared between all the packet protocol layers any more. So we can turn the want_user_input and got_user_input methods of PacketProtocolLayer into methods of ConnectionLayer, and then only the two connection layers have to bother implementing them, or store a pointer to the bufchain they read from. --- ssh.h | 14 ++++++++++++-- ssh/common.c | 1 - ssh/connection1.c | 26 ++++++++++++++------------ ssh/connection1.h | 1 + ssh/connection2.c | 27 +++++++++++++++------------ ssh/connection2.h | 1 + ssh/login1-server.c | 5 ----- ssh/login1.c | 20 -------------------- ssh/mainchan.c | 2 +- ssh/ppl.h | 15 +++------------ ssh/server.c | 10 ++++++---- ssh/ssh.c | 14 +++++++------- ssh/transport2.c | 22 ---------------------- ssh/userauth2-client.c | 20 -------------------- 14 files changed, 60 insertions(+), 118 deletions(-) diff --git a/ssh.h b/ssh.h index 34a8909d..d4729d5f 100644 --- a/ssh.h +++ b/ssh.h @@ -299,9 +299,15 @@ struct ConnectionLayerVtable { * subsequent channel-opens). */ void (*enable_x_fwd)(ConnectionLayer *cl); - /* Communicate to the connection layer whether the main session - * channel currently wants user input. */ + /* Communicate / query whether the main session channel currently + * wants user input. The set function is called by mainchan; the + * query function is called by the top-level ssh.c. */ void (*set_wants_user_input)(ConnectionLayer *cl, bool wanted); + bool (*get_wants_user_input)(ConnectionLayer *cl); + + /* Notify the connection layer that more data has been added to + * the user input queue. */ + void (*got_user_input)(ConnectionLayer *cl); }; struct ConnectionLayer { @@ -371,6 +377,10 @@ static inline void ssh_enable_x_fwd(ConnectionLayer *cl) { cl->vt->enable_x_fwd(cl); } static inline void ssh_set_wants_user_input(ConnectionLayer *cl, bool wanted) { cl->vt->set_wants_user_input(cl, wanted); } +static inline bool ssh_get_wants_user_input(ConnectionLayer *cl) +{ return cl->vt->get_wants_user_input(cl); } +static inline void ssh_got_user_input(ConnectionLayer *cl) +{ cl->vt->got_user_input(cl); } /* Exports from portfwd.c */ PortFwdManager *portfwdmgr_new(ConnectionLayer *cl); diff --git a/ssh/common.c b/ssh/common.c index b6391729..c67a2dfb 100644 --- a/ssh/common.c +++ b/ssh/common.c @@ -682,7 +682,6 @@ void ssh_ppl_replace(PacketProtocolLayer *old, PacketProtocolLayer *new) new->bpp = old->bpp; ssh_ppl_setup_queues(new, old->in_pq, old->out_pq); new->selfptr = old->selfptr; - new->user_input = old->user_input; new->seat = old->seat; new->ssh = old->ssh; diff --git a/ssh/connection1.c b/ssh/connection1.c index bff09f51..e0b4aac6 100644 --- a/ssh/connection1.c +++ b/ssh/connection1.c @@ -31,8 +31,6 @@ static void ssh1_connection_free(PacketProtocolLayer *); static void ssh1_connection_process_queue(PacketProtocolLayer *); static void ssh1_connection_special_cmd(PacketProtocolLayer *ppl, SessionSpecialCode code, int arg); -static bool ssh1_connection_want_user_input(PacketProtocolLayer *ppl); -static void ssh1_connection_got_user_input(PacketProtocolLayer *ppl); static void ssh1_connection_reconfigure(PacketProtocolLayer *ppl, Conf *conf); static const PacketProtocolLayerVtable ssh1_connection_vtable = { @@ -40,8 +38,6 @@ static const PacketProtocolLayerVtable ssh1_connection_vtable = { .process_queue = ssh1_connection_process_queue, .get_specials = ssh1_common_get_specials, .special_cmd = ssh1_connection_special_cmd, - .want_user_input = ssh1_connection_want_user_input, - .got_user_input = ssh1_connection_got_user_input, .reconfigure = ssh1_connection_reconfigure, .queued_data_size = ssh_ppl_default_queued_data_size, .name = NULL, /* no layer names in SSH-1 */ @@ -63,6 +59,8 @@ static bool ssh1_ldisc_option(ConnectionLayer *cl, int option); static void ssh1_set_ldisc_option(ConnectionLayer *cl, int option, bool value); static void ssh1_enable_x_fwd(ConnectionLayer *cl); static void ssh1_set_wants_user_input(ConnectionLayer *cl, bool wanted); +static bool ssh1_get_wants_user_input(ConnectionLayer *cl); +static void ssh1_got_user_input(ConnectionLayer *cl); static const ConnectionLayerVtable ssh1_connlayer_vtable = { .rportfwd_alloc = ssh1_rportfwd_alloc, @@ -81,6 +79,8 @@ static const ConnectionLayerVtable ssh1_connlayer_vtable = { .set_ldisc_option = ssh1_set_ldisc_option, .enable_x_fwd = ssh1_enable_x_fwd, .set_wants_user_input = ssh1_set_wants_user_input, + .get_wants_user_input = ssh1_get_wants_user_input, + .got_user_input = ssh1_got_user_input, /* other methods are NULL */ }; @@ -138,7 +138,7 @@ void ssh1_channel_free(struct ssh1_channel *c) } PacketProtocolLayer *ssh1_connection_new( - Ssh *ssh, Conf *conf, ConnectionLayer **cl_out) + Ssh *ssh, Conf *conf, bufchain *user_input, ConnectionLayer **cl_out) { struct ssh1_connection_state *s = snew(struct ssh1_connection_state); memset(s, 0, sizeof(*s)); @@ -150,6 +150,8 @@ PacketProtocolLayer *ssh1_connection_new( s->x11authtree = newtree234(x11_authcmp); + s->user_input = user_input; + /* Need to get the log context for s->cl now, because we won't be * helpfully notified when a copy is written into s->ppl by our * owner. */ @@ -771,28 +773,28 @@ static void ssh1_set_wants_user_input(ConnectionLayer *cl, bool wanted) ssh_check_sendok(s->ppl.ssh); } -static bool ssh1_connection_want_user_input(PacketProtocolLayer *ppl) +static bool ssh1_get_wants_user_input(ConnectionLayer *cl) { struct ssh1_connection_state *s = - container_of(ppl, struct ssh1_connection_state, ppl); + container_of(cl, struct ssh1_connection_state, cl); return s->want_user_input; } -static void ssh1_connection_got_user_input(PacketProtocolLayer *ppl) +static void ssh1_got_user_input(ConnectionLayer *cl) { struct ssh1_connection_state *s = - container_of(ppl, struct ssh1_connection_state, ppl); + container_of(cl, struct ssh1_connection_state, cl); - while (s->mainchan && bufchain_size(s->ppl.user_input) > 0) { + while (s->mainchan && bufchain_size(s->user_input) > 0) { /* * Add user input to the main channel's buffer. */ - ptrlen data = bufchain_prefix(s->ppl.user_input); + ptrlen data = bufchain_prefix(s->user_input); if (data.len > 512) data.len = 512; sshfwd_write(&s->mainchan_sc, data.ptr, data.len); - bufchain_consume(s->ppl.user_input, data.len); + bufchain_consume(s->user_input, data.len); } } diff --git a/ssh/connection1.h b/ssh/connection1.h index 44370787..7d136d60 100644 --- a/ssh/connection1.h +++ b/ssh/connection1.h @@ -25,6 +25,7 @@ struct ssh1_connection_state { bool want_user_input; bool session_terminated; int term_width, term_height, term_width_orig, term_height_orig; + bufchain *user_input; bool X11_fwd_enabled; struct X11Display *x11disp; diff --git a/ssh/connection2.c b/ssh/connection2.c index df542f63..a6bc553b 100644 --- a/ssh/connection2.c +++ b/ssh/connection2.c @@ -18,8 +18,6 @@ static bool ssh2_connection_get_specials( PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx); static void ssh2_connection_special_cmd(PacketProtocolLayer *ppl, SessionSpecialCode code, int arg); -static bool ssh2_connection_want_user_input(PacketProtocolLayer *ppl); -static void ssh2_connection_got_user_input(PacketProtocolLayer *ppl); static void ssh2_connection_reconfigure(PacketProtocolLayer *ppl, Conf *conf); static const PacketProtocolLayerVtable ssh2_connection_vtable = { @@ -27,8 +25,6 @@ static const PacketProtocolLayerVtable ssh2_connection_vtable = { .process_queue = ssh2_connection_process_queue, .get_specials = ssh2_connection_get_specials, .special_cmd = ssh2_connection_special_cmd, - .want_user_input = ssh2_connection_want_user_input, - .got_user_input = ssh2_connection_got_user_input, .reconfigure = ssh2_connection_reconfigure, .queued_data_size = ssh_ppl_default_queued_data_size, .name = "ssh-connection", @@ -63,6 +59,8 @@ static bool ssh2_ldisc_option(ConnectionLayer *cl, int option); static void ssh2_set_ldisc_option(ConnectionLayer *cl, int option, bool value); static void ssh2_enable_x_fwd(ConnectionLayer *cl); static void ssh2_set_wants_user_input(ConnectionLayer *cl, bool wanted); +static bool ssh2_get_wants_user_input(ConnectionLayer *cl); +static void ssh2_got_user_input(ConnectionLayer *cl); static const ConnectionLayerVtable ssh2_connlayer_vtable = { .rportfwd_alloc = ssh2_rportfwd_alloc, @@ -88,6 +86,8 @@ static const ConnectionLayerVtable ssh2_connlayer_vtable = { .set_ldisc_option = ssh2_set_ldisc_option, .enable_x_fwd = ssh2_enable_x_fwd, .set_wants_user_input = ssh2_set_wants_user_input, + .get_wants_user_input = ssh2_get_wants_user_input, + .got_user_input = ssh2_got_user_input, }; static char *ssh2_channel_open_failure_error_text(PktIn *pktin) @@ -239,7 +239,8 @@ static void ssh2_channel_free(struct ssh2_channel *c) PacketProtocolLayer *ssh2_connection_new( Ssh *ssh, ssh_sharing_state *connshare, bool is_simple, - Conf *conf, const char *peer_verstring, ConnectionLayer **cl_out) + Conf *conf, const char *peer_verstring, bufchain *user_input, + ConnectionLayer **cl_out) { struct ssh2_connection_state *s = snew(struct ssh2_connection_state); memset(s, 0, sizeof(*s)); @@ -264,6 +265,8 @@ PacketProtocolLayer *ssh2_connection_new( s->x11authtree = newtree234(x11_authcmp); + s->user_input = user_input; + /* Need to get the log context for s->cl now, because we won't be * helpfully notified when a copy is written into s->ppl by our * owner. */ @@ -1705,25 +1708,25 @@ static void ssh2_set_wants_user_input(ConnectionLayer *cl, bool wanted) ssh_check_sendok(s->ppl.ssh); } -static bool ssh2_connection_want_user_input(PacketProtocolLayer *ppl) +static bool ssh2_get_wants_user_input(ConnectionLayer *cl) { struct ssh2_connection_state *s = - container_of(ppl, struct ssh2_connection_state, ppl); + container_of(cl, struct ssh2_connection_state, cl); return s->want_user_input; } -static void ssh2_connection_got_user_input(PacketProtocolLayer *ppl) +static void ssh2_got_user_input(ConnectionLayer *cl) { struct ssh2_connection_state *s = - container_of(ppl, struct ssh2_connection_state, ppl); + container_of(cl, struct ssh2_connection_state, cl); - while (s->mainchan && bufchain_size(s->ppl.user_input) > 0) { + while (s->mainchan && bufchain_size(s->user_input) > 0) { /* * Add user input to the main channel's buffer. */ - ptrlen data = bufchain_prefix(s->ppl.user_input); + ptrlen data = bufchain_prefix(s->user_input); sshfwd_write(s->mainchan_sc, data.ptr, data.len); - bufchain_consume(s->ppl.user_input, data.len); + bufchain_consume(s->user_input, data.len); } } diff --git a/ssh/connection2.h b/ssh/connection2.h index d3bb240a..7971632c 100644 --- a/ssh/connection2.h +++ b/ssh/connection2.h @@ -16,6 +16,7 @@ struct ssh2_connection_state { int session_attempt, session_status; int term_width, term_height; bool want_user_input; + bufchain *user_input; bool ssh_is_simple; bool persistent; diff --git a/ssh/login1-server.c b/ssh/login1-server.c index 14f5edd6..30ff9026 100644 --- a/ssh/login1-server.c +++ b/ssh/login1-server.c @@ -51,9 +51,6 @@ static bool ssh1_login_server_get_specials( void *ctx) { return false; } static void ssh1_login_server_special_cmd(PacketProtocolLayer *ppl, SessionSpecialCode code, int arg) {} -static bool ssh1_login_server_want_user_input( - PacketProtocolLayer *ppl) { return false; } -static void ssh1_login_server_got_user_input(PacketProtocolLayer *ppl) {} static void ssh1_login_server_reconfigure( PacketProtocolLayer *ppl, Conf *conf) {} @@ -62,8 +59,6 @@ static const PacketProtocolLayerVtable ssh1_login_server_vtable = { .process_queue = ssh1_login_server_process_queue, .get_specials = ssh1_login_server_get_specials, .special_cmd = ssh1_login_server_special_cmd, - .want_user_input = ssh1_login_server_want_user_input, - .got_user_input = ssh1_login_server_got_user_input, .reconfigure = ssh1_login_server_reconfigure, .queued_data_size = ssh_ppl_default_queued_data_size, .name = NULL, /* no layer names in SSH-1 */ diff --git a/ssh/login1.c b/ssh/login1.c index a700e02a..1e2d7d3e 100644 --- a/ssh/login1.c +++ b/ssh/login1.c @@ -61,7 +61,6 @@ struct ssh1_login_state { int dlgret; Filename *keyfile; RSAKey servkey, hostkey; - bool want_user_input; StripCtrlChars *tis_scc; bool tis_scc_initialised; @@ -74,8 +73,6 @@ static void ssh1_login_process_queue(PacketProtocolLayer *); static void ssh1_login_dialog_callback(void *, int); static void ssh1_login_special_cmd(PacketProtocolLayer *ppl, SessionSpecialCode code, int arg); -static bool ssh1_login_want_user_input(PacketProtocolLayer *ppl); -static void ssh1_login_got_user_input(PacketProtocolLayer *ppl); static void ssh1_login_reconfigure(PacketProtocolLayer *ppl, Conf *conf); static const PacketProtocolLayerVtable ssh1_login_vtable = { @@ -83,8 +80,6 @@ static const PacketProtocolLayerVtable ssh1_login_vtable = { .process_queue = ssh1_login_process_queue, .get_specials = ssh1_common_get_specials, .special_cmd = ssh1_login_special_cmd, - .want_user_input = ssh1_login_want_user_input, - .got_user_input = ssh1_login_got_user_input, .reconfigure = ssh1_login_reconfigure, .queued_data_size = ssh_ppl_default_queued_data_size, .name = NULL, /* no layer names in SSH-1 */ @@ -1205,21 +1200,6 @@ static void ssh1_login_special_cmd(PacketProtocolLayer *ppl, } } -static bool ssh1_login_want_user_input(PacketProtocolLayer *ppl) -{ - struct ssh1_login_state *s = - container_of(ppl, struct ssh1_login_state, ppl); - return s->want_user_input; -} - -static void ssh1_login_got_user_input(PacketProtocolLayer *ppl) -{ - struct ssh1_login_state *s = - container_of(ppl, struct ssh1_login_state, ppl); - if (s->want_user_input) - queue_idempotent_callback(&s->ppl.ic_process_queue); -} - static void ssh1_login_reconfigure(PacketProtocolLayer *ppl, Conf *conf) { struct ssh1_login_state *s = diff --git a/ssh/mainchan.c b/ssh/mainchan.c index 2e690547..04993620 100644 --- a/ssh/mainchan.c +++ b/ssh/mainchan.c @@ -322,7 +322,7 @@ static void mainchan_ready(mainchan *mc) mc->ready = true; ssh_set_wants_user_input(mc->cl, true); - ssh_ppl_got_user_input(mc->ppl); /* in case any is already queued */ + ssh_got_user_input(mc->cl); /* in case any is already queued */ /* If an EOF arrived before we were ready, handle it now. */ if (mc->eof_pending) { diff --git a/ssh/ppl.h b/ssh/ppl.h index 5ba1e9d3..66f46038 100644 --- a/ssh/ppl.h +++ b/ssh/ppl.h @@ -17,8 +17,6 @@ struct PacketProtocolLayerVtable { PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx); void (*special_cmd)( PacketProtocolLayer *ppl, SessionSpecialCode code, int arg); - bool (*want_user_input)(PacketProtocolLayer *ppl); - void (*got_user_input)(PacketProtocolLayer *ppl); void (*reconfigure)(PacketProtocolLayer *ppl, Conf *conf); size_t (*queued_data_size)(PacketProtocolLayer *ppl); @@ -48,10 +46,6 @@ struct PacketProtocolLayer { * pointer and then freeing itself. */ PacketProtocolLayer **selfptr; - /* Bufchain of keyboard input from the user, for login prompts and - * similar. */ - bufchain *user_input; - /* Logging and error-reporting facilities. */ LogContext *logctx; Seat *seat; /* for dialog boxes, session output etc */ @@ -69,10 +63,6 @@ static inline bool ssh_ppl_get_specials( static inline void ssh_ppl_special_cmd( PacketProtocolLayer *ppl, SessionSpecialCode code, int arg) { ppl->vt->special_cmd(ppl, code, arg); } -static inline bool ssh_ppl_want_user_input(PacketProtocolLayer *ppl) -{ return ppl->vt->want_user_input(ppl); } -static inline void ssh_ppl_got_user_input(PacketProtocolLayer *ppl) -{ ppl->vt->got_user_input(ppl); } static inline void ssh_ppl_reconfigure(PacketProtocolLayer *ppl, Conf *conf) { ppl->vt->reconfigure(ppl, conf); } static inline size_t ssh_ppl_queued_data_size(PacketProtocolLayer *ppl) @@ -103,7 +93,7 @@ PacketProtocolLayer *ssh1_login_new( Conf *conf, const char *host, int port, PacketProtocolLayer *successor_layer); PacketProtocolLayer *ssh1_connection_new( - Ssh *ssh, Conf *conf, ConnectionLayer **cl_out); + Ssh *ssh, Conf *conf, bufchain *user_input, ConnectionLayer **cl_out); struct DataTransferStats; struct ssh_connection_shared_gss_state; @@ -123,7 +113,8 @@ PacketProtocolLayer *ssh2_userauth_new( bool gssapi_fwd, struct ssh_connection_shared_gss_state *shgss); PacketProtocolLayer *ssh2_connection_new( Ssh *ssh, ssh_sharing_state *connshare, bool is_simple, - Conf *conf, const char *peer_verstring, ConnectionLayer **cl_out); + Conf *conf, const char *peer_verstring, bufchain *user_input, + ConnectionLayer **cl_out); /* Can't put this in the userauth constructor without having a * dependency loop at setup time (transport and userauth can't _both_ diff --git a/ssh/server.c b/ssh/server.c index a3c400c0..4a3676ec 100644 --- a/ssh/server.c +++ b/ssh/server.c @@ -372,7 +372,6 @@ static void server_connect_bpp(server *srv) static void server_connect_ppl(server *srv, PacketProtocolLayer *ppl) { ppl->bpp = srv->bpp; - ppl->user_input = &srv->dummy_user_input; ppl->logctx = srv->logctx; ppl->ssh = &srv->ssh; ppl->seat = &srv->seat; @@ -516,7 +515,8 @@ static void server_got_ssh_version(struct ssh_version_receiver *rcv, connection_layer = ssh2_connection_new( &srv->ssh, NULL, false, srv->conf, - ssh_verstring_get_local(old_bpp), &srv->cl); + ssh_verstring_get_local(old_bpp), &srv->dummy_user_input, + &srv->cl); ssh2connection_server_configure(connection_layer, srv->sftpserver_vt, srv->ssc); server_connect_ppl(srv, connection_layer); @@ -530,7 +530,8 @@ static void server_got_ssh_version(struct ssh_version_receiver *rcv, connection_layer = ssh2_connection_new( &srv->ssh, NULL, false, srv->conf, - ssh_verstring_get_local(old_bpp), &srv->cl); + ssh_verstring_get_local(old_bpp), &srv->dummy_user_input, + &srv->cl); ssh2connection_server_configure(connection_layer, srv->sftpserver_vt, srv->ssc); server_connect_ppl(srv, connection_layer); @@ -566,7 +567,8 @@ static void server_got_ssh_version(struct ssh_version_receiver *rcv, srv->bpp = ssh1_bpp_new(srv->logctx); server_connect_bpp(srv); - connection_layer = ssh1_connection_new(&srv->ssh, srv->conf, &srv->cl); + connection_layer = ssh1_connection_new( + &srv->ssh, srv->conf, &srv->dummy_user_input, &srv->cl); ssh1connection_server_configure(connection_layer, srv->ssc); server_connect_ppl(srv, connection_layer); diff --git a/ssh/ssh.c b/ssh/ssh.c index 10c52001..4e7f8c06 100644 --- a/ssh/ssh.c +++ b/ssh/ssh.c @@ -165,7 +165,6 @@ static void ssh_connect_bpp(Ssh *ssh) static void ssh_connect_ppl(Ssh *ssh, PacketProtocolLayer *ppl) { ppl->bpp = ssh->bpp; - ppl->user_input = &ssh->user_input; ppl->seat = ssh->seat; ppl->ssh = ssh; ppl->logctx = ssh->logctx; @@ -241,7 +240,7 @@ static void ssh_got_ssh_version(struct ssh_version_receiver *rcv, connection_layer = ssh2_connection_new( ssh, ssh->connshare, is_simple, ssh->conf, - ssh_verstring_get_remote(old_bpp), &ssh->cl); + ssh_verstring_get_remote(old_bpp), &ssh->user_input, &ssh->cl); ssh_connect_ppl(ssh, connection_layer); if (conf_get_bool(ssh->conf, CONF_ssh_no_userauth)) { @@ -299,7 +298,8 @@ static void ssh_got_ssh_version(struct ssh_version_receiver *rcv, ssh->bpp = ssh1_bpp_new(ssh->logctx); ssh_connect_bpp(ssh); - connection_layer = ssh1_connection_new(ssh, ssh->conf, &ssh->cl); + connection_layer = ssh1_connection_new( + ssh, ssh->conf, &ssh->user_input, &ssh->cl); ssh_connect_ppl(ssh, connection_layer); ssh->base_layer = ssh1_login_new( @@ -314,7 +314,7 @@ static void ssh_got_ssh_version(struct ssh_version_receiver *rcv, connection_layer = ssh2_connection_new( ssh, ssh->connshare, false, ssh->conf, - ssh_verstring_get_remote(old_bpp), &ssh->cl); + ssh_verstring_get_remote(old_bpp), &ssh->user_input, &ssh->cl); ssh_connect_ppl(ssh, connection_layer); ssh->base_layer = connection_layer; } @@ -1024,8 +1024,8 @@ static void ssh_send(Backend *be, const char *buf, size_t len) return; bufchain_add(&ssh->user_input, buf, len); - if (ssh->base_layer) - ssh_ppl_got_user_input(ssh->base_layer); + if (ssh->cl) + ssh_got_user_input(ssh->cl); } /* @@ -1153,7 +1153,7 @@ static bool ssh_connected(Backend *be) static bool ssh_sendok(Backend *be) { Ssh *ssh = container_of(be, Ssh, backend); - return ssh->base_layer && ssh_ppl_want_user_input(ssh->base_layer); + return ssh->cl && ssh_get_wants_user_input(ssh->cl); } void ssh_check_sendok(Ssh *ssh) diff --git a/ssh/transport2.c b/ssh/transport2.c index 2966f2d7..9c49d55c 100644 --- a/ssh/transport2.c +++ b/ssh/transport2.c @@ -73,8 +73,6 @@ static bool ssh2_transport_get_specials( PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx); static void ssh2_transport_special_cmd(PacketProtocolLayer *ppl, SessionSpecialCode code, int arg); -static bool ssh2_transport_want_user_input(PacketProtocolLayer *ppl); -static void ssh2_transport_got_user_input(PacketProtocolLayer *ppl); static void ssh2_transport_reconfigure(PacketProtocolLayer *ppl, Conf *conf); static size_t ssh2_transport_queued_data_size(PacketProtocolLayer *ppl); @@ -87,8 +85,6 @@ static const PacketProtocolLayerVtable ssh2_transport_vtable = { .process_queue = ssh2_transport_process_queue, .get_specials = ssh2_transport_get_specials, .special_cmd = ssh2_transport_special_cmd, - .want_user_input = ssh2_transport_want_user_input, - .got_user_input = ssh2_transport_got_user_input, .reconfigure = ssh2_transport_reconfigure, .queued_data_size = ssh2_transport_queued_data_size, .name = NULL, /* no protocol name for this layer */ @@ -2132,24 +2128,6 @@ static void ssh2_transport_reconfigure(PacketProtocolLayer *ppl, Conf *conf) ssh_ppl_reconfigure(s->higher_layer, conf); } -static bool ssh2_transport_want_user_input(PacketProtocolLayer *ppl) -{ - struct ssh2_transport_state *s = - container_of(ppl, struct ssh2_transport_state, ppl); - - /* Just delegate this to the higher layer */ - return ssh_ppl_want_user_input(s->higher_layer); -} - -static void ssh2_transport_got_user_input(PacketProtocolLayer *ppl) -{ - struct ssh2_transport_state *s = - container_of(ppl, struct ssh2_transport_state, ppl); - - /* Just delegate this to the higher layer */ - ssh_ppl_got_user_input(s->higher_layer); -} - static int weak_algorithm_compare(void *av, void *bv) { uintptr_t a = (uintptr_t)av, b = (uintptr_t)bv; diff --git a/ssh/userauth2-client.c b/ssh/userauth2-client.c index 51872ebf..d4f546b5 100644 --- a/ssh/userauth2-client.c +++ b/ssh/userauth2-client.c @@ -81,7 +81,6 @@ struct ssh2_userauth_state { unsigned signflags; int len; PktOut *pktout; - bool want_user_input; bool is_trivial_auth; agent_pending_query *auth_agent_query; @@ -103,8 +102,6 @@ static bool ssh2_userauth_get_specials( PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx); static void ssh2_userauth_special_cmd(PacketProtocolLayer *ppl, SessionSpecialCode code, int arg); -static bool ssh2_userauth_want_user_input(PacketProtocolLayer *ppl); -static void ssh2_userauth_got_user_input(PacketProtocolLayer *ppl); static void ssh2_userauth_reconfigure(PacketProtocolLayer *ppl, Conf *conf); static void ssh2_userauth_agent_query(struct ssh2_userauth_state *, strbuf *); @@ -125,8 +122,6 @@ static const PacketProtocolLayerVtable ssh2_userauth_vtable = { .process_queue = ssh2_userauth_process_queue, .get_specials = ssh2_userauth_get_specials, .special_cmd = ssh2_userauth_special_cmd, - .want_user_input = ssh2_userauth_want_user_input, - .got_user_input = ssh2_userauth_got_user_input, .reconfigure = ssh2_userauth_reconfigure, .queued_data_size = ssh_ppl_default_queued_data_size, .name = "ssh-userauth", @@ -1882,21 +1877,6 @@ static void ssh2_userauth_special_cmd(PacketProtocolLayer *ppl, /* No specials provided by this layer. */ } -static bool ssh2_userauth_want_user_input(PacketProtocolLayer *ppl) -{ - struct ssh2_userauth_state *s = - container_of(ppl, struct ssh2_userauth_state, ppl); - return s->want_user_input; -} - -static void ssh2_userauth_got_user_input(PacketProtocolLayer *ppl) -{ - struct ssh2_userauth_state *s = - container_of(ppl, struct ssh2_userauth_state, ppl); - if (s->want_user_input) - queue_idempotent_callback(&s->ppl.ic_process_queue); -} - static void ssh2_userauth_reconfigure(PacketProtocolLayer *ppl, Conf *conf) { struct ssh2_userauth_state *s = -- cgit v1.2.3 From 42f3a2f6d572aaa72a9e70f103138f7b192dd432 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Tue, 14 Sep 2021 14:30:44 +0100 Subject: Add missing initialisation of prompts_t idata. Apparently in all my test runs on Linux it happened to start off negative. But as soon as I tested on Windows, that initialised the memory to something unhelpful. --- utils/prompts.c | 1 + 1 file changed, 1 insertion(+) diff --git a/utils/prompts.c b/utils/prompts.c index 8784130d..583316f6 100644 --- a/utils/prompts.c +++ b/utils/prompts.c @@ -11,6 +11,7 @@ prompts_t *new_prompts(void) p->prompts = NULL; p->n_prompts = p->prompts_size = 0; p->data = NULL; + p->idata = -1; p->to_server = true; /* to be on the safe side */ p->name = p->instruction = NULL; p->name_reqd = p->instr_reqd = false; -- cgit v1.2.3 From 99b4229abf01ea9b9ef916985e6139feed0c0bf2 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Wed, 15 Sep 2021 13:48:30 +0100 Subject: Make all Plugs have a log function, even if no-op. Commit 8f5e9a4f8dc796a introduced a segfault into both Windows Pageant and Windows connection sharing upstreams when they receive an incoming named-pipe connection. This occurs because the PlugVtables for those incoming connections had a null pointer in the 'log' field, because hitherto, only sockets involved with an outgoing connection expected to receive plug_log() notifications. But I added such a notification in make_handle_socket, forgetting that that function is used for both outgoing and incoming named-pipe connections (among other things). So now a Plug implementation that expects to be set up by the plug_accepting() method on a listener may still receive PLUGLOG_CONNECT_SUCCESS. I could fix that by adding a parameter to make_handle_socket telling it whether to send a notification, but that seems like more faff than is really needed. Simpler to make a rule that says _all_ Socket types must implement the log() method, even if only with a no-op function. We already have a no-op implementation of log(), in nullplug.c. So I've exposed that outside its module (in the same style as all the nullseat functions and so on), to make it really easy to add log() methods to PlugVtables that don't need one. --- network.h | 21 +++++++++++++++++++++ nullplug.c | 15 +++++++-------- pageant.c | 2 ++ ssh/sharing.c | 2 ++ 4 files changed, 32 insertions(+), 8 deletions(-) diff --git a/network.h b/network.h index 107e72c8..85c5a6d9 100644 --- a/network.h +++ b/network.h @@ -78,6 +78,13 @@ struct PlugVtable { * proxied through. This will typically be a wodge of * standard-error output from a local proxy command, so the * receiver should probably prefix it to indicate this. + * + * Note that sometimes log messages may be sent even to Socket + * types that don't involve making an outgoing connection, e.g. + * because the same core implementation (such as Windows handle + * sockets) is shared between listening and connecting sockets. So + * all Plugs must implement this method, even if only to ignore + * the logged events. */ void (*closing) (Plug *p, const char *error_msg, int error_code, bool calling_back); @@ -325,6 +332,20 @@ Socket *new_error_socket_consume_string(Plug *plug, char *errmsg); */ extern Plug *const nullplug; +/* + * Some trivial no-op plug functions, also in nullplug.c; exposed here + * so that other Plug implementations can use them too. + * + * In particular, nullplug_log is useful to Plugs that don't need to + * worry about logging. + */ +void nullplug_log(Plug *plug, PlugLogType type, SockAddr *addr, + int port, const char *err_msg, int err_code); +void nullplug_closing(Plug *plug, const char *error_msg, int error_code, + bool calling_back); +void nullplug_receive(Plug *plug, int urgent, const char *data, size_t len); +void nullplug_sent(Plug *plug, size_t bufsize); + /* ---------------------------------------------------------------------- * Functions defined outside the network code, which have to be * declared in this header file rather than the main putty.h because diff --git a/nullplug.c b/nullplug.c index 953f0348..380e4b4f 100644 --- a/nullplug.c +++ b/nullplug.c @@ -7,27 +7,26 @@ #include "putty.h" -static void nullplug_socket_log(Plug *plug, PlugLogType type, SockAddr *addr, - int port, const char *err_msg, int err_code) +void nullplug_log(Plug *plug, PlugLogType type, SockAddr *addr, + int port, const char *err_msg, int err_code) { } -static void nullplug_closing(Plug *plug, const char *error_msg, int error_code, - bool calling_back) +void nullplug_closing(Plug *plug, const char *error_msg, int error_code, + bool calling_back) { } -static void nullplug_receive( - Plug *plug, int urgent, const char *data, size_t len) +void nullplug_receive(Plug *plug, int urgent, const char *data, size_t len) { } -static void nullplug_sent(Plug *plug, size_t bufsize) +void nullplug_sent(Plug *plug, size_t bufsize) { } static const PlugVtable nullplug_plugvt = { - .log = nullplug_socket_log, + .log = nullplug_log, .closing = nullplug_closing, .receive = nullplug_receive, .sent = nullplug_sent, diff --git a/pageant.c b/pageant.c index 8ca9310b..fb0f86e1 100644 --- a/pageant.c +++ b/pageant.c @@ -1624,6 +1624,7 @@ static const PlugVtable pageant_connection_plugvt = { .closing = pageant_conn_closing, .receive = pageant_conn_receive, .sent = pageant_conn_sent, + .log = nullplug_log, }; static int pageant_listen_accepting(Plug *plug, @@ -1672,6 +1673,7 @@ static int pageant_listen_accepting(Plug *plug, static const PlugVtable pageant_listener_plugvt = { .closing = pageant_listen_closing, .accepting = pageant_listen_accepting, + .log = nullplug_log, }; struct pageant_listen_state *pageant_listener_new( diff --git a/ssh/sharing.c b/ssh/sharing.c index b5da8657..920218de 100644 --- a/ssh/sharing.c +++ b/ssh/sharing.c @@ -1905,6 +1905,7 @@ static const PlugVtable ssh_sharing_conn_plugvt = { .closing = share_closing, .receive = share_receive, .sent = share_sent, + .log = nullplug_log, }; static int share_listen_accepting(Plug *plug, @@ -2046,6 +2047,7 @@ bool ssh_share_test_for_upstream(const char *host, int port, Conf *conf) static const PlugVtable ssh_sharing_listen_plugvt = { .closing = share_listen_closing, .accepting = share_listen_accepting, + .log = nullplug_log, }; void ssh_connshare_provide_connlayer(ssh_sharing_state *sharestate, -- cgit v1.2.3 From 7a0223435314084f51e9521781b0b64464967c6d Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 16 Sep 2021 09:27:25 +0100 Subject: userauth2: add a missing free_prompts(). If a userauth layer is destroyed while userpass input is still ongoing, ssh2_userauth_free forgot to free the active prompts_t, leaking memory. But adding the missing free_prompts call to ssh2_userauth_free results in a double-free, because another thing I forgot was to null out that pointer field everywhere _else_ it's freed. Fixed that too. --- ssh/userauth2-client.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/ssh/userauth2-client.c b/ssh/userauth2-client.c index d4f546b5..f1e2f319 100644 --- a/ssh/userauth2-client.c +++ b/ssh/userauth2-client.c @@ -193,6 +193,8 @@ static void ssh2_userauth_free(PacketProtocolLayer *ppl) sfree(s->locally_allocated_username); sfree(s->hostname); sfree(s->fullhostname); + if (s->cur_prompt) + free_prompts(s->cur_prompt); sfree(s->publickey_comment); sfree(s->publickey_algorithm); if (s->publickey_blob) @@ -455,6 +457,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) * Terminate. */ free_prompts(s->cur_prompt); + s->cur_prompt = NULL; ssh_user_close(s->ppl.ssh, "No username provided"); return; } @@ -462,6 +465,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) s->username = s->locally_allocated_username = prompt_get_result(s->cur_prompt->prompts[0]); free_prompts(s->cur_prompt); + s->cur_prompt = NULL; } else { if (seat_verbose(s->ppl.seat) || seat_interactive(s->ppl.seat)) ppl_printf("Using username \"%s\".\r\n", s->username); @@ -918,6 +922,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) if (!s->userpass_ret) { /* Failed to get a passphrase. Terminate. */ free_prompts(s->cur_prompt); + s->cur_prompt = NULL; ssh_bpp_queue_disconnect( s->ppl.bpp, "Unable to authenticate", SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER); @@ -928,6 +933,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) passphrase = prompt_get_result(s->cur_prompt->prompts[0]); free_prompts(s->cur_prompt); + s->cur_prompt = NULL; } else { passphrase = NULL; /* no passphrase needed */ } @@ -1397,6 +1403,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) * Failed to get responses. Terminate. */ free_prompts(s->cur_prompt); + s->cur_prompt = NULL; ssh_bpp_queue_disconnect( s->ppl.bpp, "Unable to authenticate", SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER); @@ -1424,6 +1431,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) * when we return to the top of this while loop. */ free_prompts(s->cur_prompt); + s->cur_prompt = NULL; /* * Get the next packet in case it's another @@ -1476,6 +1484,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) * Failed to get responses. Terminate. */ free_prompts(s->cur_prompt); + s->cur_prompt = NULL; ssh_bpp_queue_disconnect( s->ppl.bpp, "Unable to authenticate", SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER); @@ -1489,6 +1498,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) */ s->password = prompt_get_result(s->cur_prompt->prompts[0]); free_prompts(s->cur_prompt); + s->cur_prompt = NULL; /* * Send the password packet. @@ -1587,6 +1597,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) */ /* burn the evidence */ free_prompts(s->cur_prompt); + s->cur_prompt = NULL; smemclr(s->password, strlen(s->password)); sfree(s->password); ssh_bpp_queue_disconnect( @@ -1638,6 +1649,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) put_stringz(s->pktout, prompt_get_result_ref( s->cur_prompt->prompts[1])); free_prompts(s->cur_prompt); + s->cur_prompt = NULL; s->pktout->minlen = 256; pq_push(s->ppl.out_pq, s->pktout); ppl_logevent("Sent new password"); -- cgit v1.2.3 From 65270b56f050975ea255a6556cfde2dd38546308 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 16 Sep 2021 09:41:03 +0100 Subject: free_prompts: deal with a reference from an Ldisc. In a GUI app, when interactive userpass input begins, the Ldisc acquires a reference to a prompts_t. If something bad happens to the SSH connection (e.g. unexpected server-side closure), then all the SSH layers will be destroyed, including freeing that prompts_t. So the Ldisc will have a stale reference to it, which it might potentially use. To fix that, I've arranged a back-pointer so that prompts_t itself can find the Ldisc's reference to it, and NULL it out on free. So now, whichever of a prompts_t and an Ldisc is freed first, the link between them should be cleanly broken. (I'm not 100% sure this is absolutely necessary, in the sense of whether a sequence of events can _actually_ happen that causes a stale pointer dereference. But I don't want to take the chance!) --- ldisc.c | 4 ++++ putty.h | 12 ++++++++++-- utils/prompts.c | 7 +++++++ 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/ldisc.c b/ldisc.c index b549c14a..f0e59658 100644 --- a/ldisc.c +++ b/ldisc.c @@ -182,6 +182,8 @@ void ldisc_free(Ldisc *ldisc) backend_provide_ldisc(ldisc->backend, NULL); if (ldisc->buf) sfree(ldisc->buf); + if (ldisc->prompts && ldisc->prompts->ldisc_ptr_to_us == &ldisc->prompts) + ldisc->prompts->ldisc_ptr_to_us = NULL; delete_callbacks_for_context(ldisc); sfree(ldisc); } @@ -201,6 +203,8 @@ void ldisc_enable_prompt_callback(Ldisc *ldisc, prompts_t *prompts) * that it can continue the interactive prompting process. */ ldisc->prompts = prompts; + if (prompts) + ldisc->prompts->ldisc_ptr_to_us = &ldisc->prompts; } static void ldisc_input_queue_callback(void *ctx) diff --git a/putty.h b/putty.h index 417579e4..907f3ff0 100644 --- a/putty.h +++ b/putty.h @@ -767,7 +767,8 @@ typedef struct { bool echo; strbuf *result; } prompt_t; -typedef struct { +typedef struct prompts_t prompts_t; +struct prompts_t { /* * Indicates whether the information entered is to be used locally * (for instance a key passphrase prompt), or is destined for the wire. @@ -806,7 +807,14 @@ typedef struct { */ toplevel_callback_fn_t callback; void *callback_ctx; -} prompts_t; + + /* + * When this prompts_t is known to an Ldisc, we might need to + * break the connection if things get freed in an emergency. So + * this is a pointer to the Ldisc's pointer to us. + */ + prompts_t **ldisc_ptr_to_us; +}; prompts_t *new_prompts(void); void add_prompt(prompts_t *p, char *promptstr, bool echo); void prompt_set_result(prompt_t *pr, const char *newstr); diff --git a/utils/prompts.c b/utils/prompts.c index 583316f6..e26ef905 100644 --- a/utils/prompts.c +++ b/utils/prompts.c @@ -17,6 +17,7 @@ prompts_t *new_prompts(void) p->name_reqd = p->instr_reqd = false; p->callback = NULL; p->callback_ctx = NULL; + p->ldisc_ptr_to_us = NULL; return p; } @@ -49,6 +50,12 @@ char *prompt_get_result(prompt_t *pr) void free_prompts(prompts_t *p) { size_t i; + + /* If an Ldisc currently knows about us, tell it to forget us, so + * it won't dereference a stale pointer later. */ + if (p->ldisc_ptr_to_us) + *p->ldisc_ptr_to_us = NULL; + for (i=0; i < p->n_prompts; i++) { prompt_t *pr = p->prompts[i]; strbuf_free(pr->result); -- cgit v1.2.3 From e5b6aba63a84b28e7dcece026db47c9ccdb47060 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Wed, 15 Sep 2021 06:00:02 +0100 Subject: unix/console.c: add a missing postmsg(). When abandoning a connection due to a host key mismatch in batch mode, we'd forget to restore the termios settings. --- unix/console.c | 1 + 1 file changed, 1 insertion(+) diff --git a/unix/console.c b/unix/console.c index ffe777fd..e4e372fc 100644 --- a/unix/console.c +++ b/unix/console.c @@ -138,6 +138,7 @@ int console_verify_ssh_host_key( fprintf(stderr, common_fmt, keytype, fingerprints[fptype_default]); if (console_batch_mode) { fputs(console_abandoned_msg, stderr); + postmsg(&cf); return 0; } -- cgit v1.2.3 From f317f8e67e2286863ef81aabe7073283a2307255 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Wed, 15 Sep 2021 06:00:38 +0100 Subject: Centralise host key message formatting. The format _strings_ were previously centralised into the platform- independent console.c, as const char arrays. Now the actual formatting operation is centralised as well, by means of console.c providing a function that takes all the necessary parameters and returns a formatted piece of text for the console. Mostly this is so that I can add extra parameters to the message with some confidence: changing a format string in one file and two fprintf statements in other files to match seems like the kind of situation you wish you hadn't got into in the first place :-) --- console.c | 34 +++++++++++++++++++++------------- console.h | 5 +++-- unix/console.c | 17 ++++++++++------- windows/console.c | 15 +++++++++------ 4 files changed, 43 insertions(+), 28 deletions(-) diff --git a/console.c b/console.c index 7155b9f0..ce70c104 100644 --- a/console.c +++ b/console.c @@ -9,11 +9,15 @@ #include "misc.h" #include "console.h" -const char hk_absentmsg_common_fmt[] = - "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"; +char *hk_absentmsg_common(const char *keytype, const char *fingerprint) +{ + return dupprintf( + "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", keytype, fingerprint); +} + const char hk_absentmsg_interactive_intro[] = "If you trust this host, enter \"y\" to add the key to\n" "PuTTY's cache and carry on connecting.\n" @@ -25,14 +29,18 @@ const char hk_absentmsg_interactive_prompt[] = "Store key in cache? (y/n, Return cancels connection, " "i for more info) "; -const char hk_wrongmsg_common_fmt[] = - "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"; +char *hk_wrongmsg_common(const char *keytype, const char *fingerprint) +{ + return dupprintf( + "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", keytype, fingerprint); +} + const char hk_wrongmsg_interactive_intro[] = "If you were expecting this change and trust the new key,\n" "enter \"y\" to update PuTTY's cache and continue connecting.\n" diff --git a/console.h b/console.h index a8b22466..d28acbdc 100644 --- a/console.h +++ b/console.h @@ -2,10 +2,11 @@ * Common pieces between the platform console frontend modules. */ -extern const char hk_absentmsg_common_fmt[]; +char *hk_absentmsg_common(const char *keytype, const char *fingerprint); extern const char hk_absentmsg_interactive_intro[]; extern const char hk_absentmsg_interactive_prompt[]; -extern const char hk_wrongmsg_common_fmt[]; + +char *hk_wrongmsg_common(const char *keytype, const char *fingerprint); extern const char hk_wrongmsg_interactive_intro[]; extern const char hk_wrongmsg_interactive_prompt[]; diff --git a/unix/console.c b/unix/console.c index e4e372fc..6b6379ed 100644 --- a/unix/console.c +++ b/unix/console.c @@ -111,7 +111,8 @@ int console_verify_ssh_host_key( char line[32]; struct termios cf; - const char *common_fmt, *intro, *prompt; + char *common; + const char *intro, *prompt; /* * Verify the key. @@ -121,21 +122,23 @@ int console_verify_ssh_host_key( if (ret == 0) /* success - key matched OK */ return 1; - premsg(&cf); + FingerprintType fptype_default = + ssh2_pick_default_fingerprint(fingerprints); + if (ret == 2) { /* key was different */ - common_fmt = hk_wrongmsg_common_fmt; + common = hk_wrongmsg_common(keytype, fingerprints[fptype_default]); intro = hk_wrongmsg_interactive_intro; prompt = hk_wrongmsg_interactive_prompt; } else { /* key was absent */ - common_fmt = hk_absentmsg_common_fmt; + common = hk_absentmsg_common(keytype, fingerprints[fptype_default]); intro = hk_absentmsg_interactive_intro; prompt = hk_absentmsg_interactive_prompt; } - FingerprintType fptype_default = - ssh2_pick_default_fingerprint(fingerprints); + premsg(&cf); + fputs(common, stderr); + sfree(common); - fprintf(stderr, common_fmt, keytype, fingerprints[fptype_default]); if (console_batch_mode) { fputs(console_abandoned_msg, stderr); postmsg(&cf); diff --git a/windows/console.c b/windows/console.c index 9bdfb6f4..21d33cd2 100644 --- a/windows/console.c +++ b/windows/console.c @@ -40,7 +40,8 @@ int console_verify_ssh_host_key( int ret; HANDLE hin; DWORD savemode, i; - const char *common_fmt, *intro, *prompt; + char *common; + const char *intro, *prompt; char line[32]; @@ -52,20 +53,22 @@ int console_verify_ssh_host_key( if (ret == 0) /* success - key matched OK */ return 1; + FingerprintType fptype_default = + ssh2_pick_default_fingerprint(fingerprints); + if (ret == 2) { /* key was different */ - common_fmt = hk_wrongmsg_common_fmt; + common = hk_wrongmsg_common(keytype, fingerprints[fptype_default]); intro = hk_wrongmsg_interactive_intro; prompt = hk_wrongmsg_interactive_prompt; } else { /* key was absent */ - common_fmt = hk_absentmsg_common_fmt; + common = hk_absentmsg_common(keytype, fingerprints[fptype_default]); intro = hk_absentmsg_interactive_intro; prompt = hk_absentmsg_interactive_prompt; } - FingerprintType fptype_default = - ssh2_pick_default_fingerprint(fingerprints); + fputs(common, stderr); + sfree(common); - fprintf(stderr, common_fmt, keytype, fingerprints[fptype_default]); if (console_batch_mode) { fputs(console_abandoned_msg, stderr); return 0; -- cgit v1.2.3 From d1dc1e927c20d8278c311fc0694bdc8661c17dbd Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Wed, 15 Sep 2021 14:41:00 +0100 Subject: Mention the host name in host-key prompts. Now that it's possible for a single invocation of PuTTY to connect to multiple SSH servers (jump host followed by ultimate destination host), it's rather unhelpful for host key prompts to just say "the server". To check an unknown host key, users will need to know _which_ host it's purporting to be the key for. Another possibility is to put a message in the terminal window indicating which server we're currently in the SSH setup phase for. That will certainly be what we have to end up doing for userpass prompts that appear _in_ the terminal window. But that by itself is still unhelpful for host key prompts in a separate dialog, because the user would have to check both windows to get all the information they need. Easier if the host key dialog itself tells you everything you need to know to answer the question: is _this_ key the one you expect for _that_ host? --- console.c | 24 ++++++++++------- console.h | 6 +++-- unix/console.c | 6 +++-- unix/dialog.c | 26 ++++++++++-------- windows/console.c | 6 +++-- windows/dialog.c | 9 +++++++ windows/putty-common.rc2 | 68 +++++++++++++++++++++++++----------------------- windows/putty-rc.h | 5 ++-- 8 files changed, 89 insertions(+), 61 deletions(-) diff --git a/console.c b/console.c index ce70c104..465fdfa7 100644 --- a/console.c +++ b/console.c @@ -9,13 +9,16 @@ #include "misc.h" #include "console.h" -char *hk_absentmsg_common(const char *keytype, const char *fingerprint) +char *hk_absentmsg_common(const char *host, int port, + const char *keytype, const char *fingerprint) { return dupprintf( - "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 host key is not cached for this server:\n" + " %s (port %d)\n" + "You have no guarantee that the server is the computer\n" + "you think it is.\n" "The server's %s key fingerprint is:\n" - "%s\n", keytype, fingerprint); + " %s\n", host, port, keytype, fingerprint); } const char hk_absentmsg_interactive_intro[] = @@ -29,16 +32,19 @@ const char hk_absentmsg_interactive_prompt[] = "Store key in cache? (y/n, Return cancels connection, " "i for more info) "; -char *hk_wrongmsg_common(const char *keytype, const char *fingerprint) +char *hk_wrongmsg_common(const char *host, int port, + const char *keytype, const char *fingerprint) { return dupprintf( "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" + "The host key does not match the one PuTTY has cached\n" + "for this server:\n" + " %s (port %d)\n" + "This means that either the server administrator has\n" + "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", keytype, fingerprint); + " %s\n", host, port, keytype, fingerprint); } const char hk_wrongmsg_interactive_intro[] = diff --git a/console.h b/console.h index d28acbdc..f6222e6a 100644 --- a/console.h +++ b/console.h @@ -2,11 +2,13 @@ * Common pieces between the platform console frontend modules. */ -char *hk_absentmsg_common(const char *keytype, const char *fingerprint); +char *hk_absentmsg_common(const char *host, int port, + const char *keytype, const char *fingerprint); extern const char hk_absentmsg_interactive_intro[]; extern const char hk_absentmsg_interactive_prompt[]; -char *hk_wrongmsg_common(const char *keytype, const char *fingerprint); +char *hk_wrongmsg_common(const char *host, int port, + const char *keytype, const char *fingerprint); extern const char hk_wrongmsg_interactive_intro[]; extern const char hk_wrongmsg_interactive_prompt[]; diff --git a/unix/console.c b/unix/console.c index 6b6379ed..8a7cd8f9 100644 --- a/unix/console.c +++ b/unix/console.c @@ -126,11 +126,13 @@ int console_verify_ssh_host_key( ssh2_pick_default_fingerprint(fingerprints); if (ret == 2) { /* key was different */ - common = hk_wrongmsg_common(keytype, fingerprints[fptype_default]); + common = hk_wrongmsg_common(host, port, keytype, + fingerprints[fptype_default]); intro = hk_wrongmsg_interactive_intro; prompt = hk_wrongmsg_interactive_prompt; } else { /* key was absent */ - common = hk_absentmsg_common(keytype, fingerprints[fptype_default]); + common = hk_absentmsg_common(host, port, keytype, + fingerprints[fptype_default]); intro = hk_absentmsg_interactive_intro; prompt = hk_absentmsg_interactive_prompt; } diff --git a/unix/dialog.c b/unix/dialog.c index f6a98a55..42cff97f 100644 --- a/unix/dialog.c +++ b/unix/dialog.c @@ -3545,10 +3545,12 @@ int gtk_seat_verify_ssh_host_key( 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" + "The host key is not cached for this server:\n\n" + "%s (port %d)\n\n" + "You have no guarantee that the server is the computer " + "you think it is.\n" + "The server's %s key fingerprint is:\n\n" + "%s\n\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 " @@ -3557,12 +3559,14 @@ int gtk_seat_verify_ssh_host_key( "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 " + "The host key does not match the one PuTTY has cached " + "for this server:\n\n" + "%s (port %d)\n\n" + "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" + "The new %s key fingerprint is:\n\n" + "%s\n\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 " @@ -3595,8 +3599,8 @@ int gtk_seat_verify_ssh_host_key( FingerprintType fptype_default = ssh2_pick_default_fingerprint(fingerprints); - text = dupprintf((ret == 2 ? wrongtxt : absenttxt), keytype, - fingerprints[fptype_default]); + text = dupprintf((ret == 2 ? wrongtxt : absenttxt), host, port, + keytype, fingerprints[fptype_default]); result_ctx = snew(struct verify_ssh_host_key_dialog_ctx); result_ctx->callback = callback; diff --git a/windows/console.c b/windows/console.c index 21d33cd2..75d55916 100644 --- a/windows/console.c +++ b/windows/console.c @@ -57,11 +57,13 @@ int console_verify_ssh_host_key( ssh2_pick_default_fingerprint(fingerprints); if (ret == 2) { /* key was different */ - common = hk_wrongmsg_common(keytype, fingerprints[fptype_default]); + common = hk_wrongmsg_common(host, port, keytype, + fingerprints[fptype_default]); intro = hk_wrongmsg_interactive_intro; prompt = hk_wrongmsg_interactive_prompt; } else { /* key was absent */ - common = hk_absentmsg_common(keytype, fingerprints[fptype_default]); + common = hk_absentmsg_common(host, port, keytype, + fingerprints[fptype_default]); intro = hk_absentmsg_interactive_intro; prompt = hk_absentmsg_interactive_prompt; } diff --git a/windows/dialog.c b/windows/dialog.c index c1ac0599..4778528b 100644 --- a/windows/dialog.c +++ b/windows/dialog.c @@ -825,6 +825,8 @@ void showabout(HWND hwnd) struct hostkey_dialog_ctx { const char *const *keywords; const char *const *values; + const char *host; + int port; FingerprintType fptype_default; char **fingerprints; const char *keydisp; @@ -901,6 +903,11 @@ static INT_PTR CALLBACK HostKeyDialogProc(HWND hwnd, UINT msg, } strbuf_free(sb); + char *hostport = dupprintf("%s (port %d)", ctx->host, ctx->port); + SetDlgItemText(hwnd, IDC_HK_HOST, hostport); + sfree(hostport); + MakeDlgItemBorderless(hwnd, IDC_HK_HOST); + SetDlgItemText(hwnd, IDC_HK_FINGERPRINT, ctx->fingerprints[ctx->fptype_default]); MakeDlgItemBorderless(hwnd, IDC_HK_FINGERPRINT); @@ -1002,6 +1009,8 @@ int win_seat_verify_ssh_host_key( ctx->iconid = (ret == 2 ? IDI_WARNING : IDI_QUESTION); ctx->helpctx = (ret == 2 ? WINHELP_CTX_errors_hostkey_changed : WINHELP_CTX_errors_hostkey_absent); + ctx->host = host; + ctx->port = port; int dlgid = (ret == 2 ? IDD_HK_WRONG : IDD_HK_ABSENT); int mbret = DialogBoxParam( hinst, MAKEINTRESOURCE(dlgid), wgs->term_hwnd, diff --git a/windows/putty-common.rc2 b/windows/putty-common.rc2 index 056a2837..f8df971f 100644 --- a/windows/putty-common.rc2 +++ b/windows/putty-common.rc2 @@ -57,60 +57,62 @@ BEGIN END /* No accelerators used */ -IDD_HK_ABSENT DIALOG DISCARDABLE 50, 50, 340, 148 +IDD_HK_ABSENT DIALOG DISCARDABLE 50, 50, 340, 160 STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "PuTTY Security Alert" FONT 8, "MS Shell Dlg" BEGIN - LTEXT "The server's host key is not cached in the registry. You have no", 100, 40, 20, 300, 8 - LTEXT "guarantee that the server is the computer you think it is.", 101, 40, 28, 300, 8 - LTEXT "The server's {KEYTYPE} key fingerprint is:", 102, 40, 40, 300, 8 - LTEXT "If you trust this host, press ""Accept"" to add the key to {APPNAME}'s", 103, 40, 60, 300, 8 - LTEXT "cache and carry on connecting.", 104, 40, 68, 300, 8 - LTEXT "If you want to carry on connecting just once, without adding the key", 105, 40, 80, 300, 8 - LTEXT "to the cache, press ""Connect Once"".", 106, 40, 88, 300, 8 - LTEXT "If you do not trust this host, press ""Cancel"" to abandon the connection.", 107, 40, 100, 300, 8 + LTEXT "The host key is not cached for this server:", 100, 40, 20, 300, 8 + LTEXT "You have no guarantee that the server is the computer you think it is.", 101, 40, 40, 300, 8 + LTEXT "The server's {KEYTYPE} key fingerprint is:", 102, 40, 52, 300, 8 + LTEXT "If you trust this host, press ""Accept"" to add the key to {APPNAME}'s", 103, 40, 72, 300, 8 + LTEXT "cache and carry on connecting.", 104, 40, 80, 300, 8 + LTEXT "If you want to carry on connecting just once, without adding the key", 105, 40, 92, 300, 8 + LTEXT "to the cache, press ""Connect Once"".", 106, 40, 100, 300, 8 + LTEXT "If you do not trust this host, press ""Cancel"" to abandon the connection.", 107, 40, 112, 300, 8 ICON "", IDC_HK_ICON, 10, 18, 0, 0 - PUSHBUTTON "Cancel", IDCANCEL, 288, 128, 40, 14 - PUSHBUTTON "Accept", IDC_HK_ACCEPT, 168, 128, 40, 14 - PUSHBUTTON "Connect Once", IDC_HK_ONCE, 216, 128, 64, 14 - PUSHBUTTON "More info...", IDC_HK_MOREINFO, 60, 128, 64, 14 - PUSHBUTTON "Help", IDHELP, 12, 128, 40, 14 + PUSHBUTTON "Cancel", IDCANCEL, 288, 140, 40, 14 + PUSHBUTTON "Accept", IDC_HK_ACCEPT, 168, 140, 40, 14 + PUSHBUTTON "Connect Once", IDC_HK_ONCE, 216, 140, 64, 14 + PUSHBUTTON "More info...", IDC_HK_MOREINFO, 60, 140, 64, 14 + PUSHBUTTON "Help", IDHELP, 12, 140, 40, 14 - EDITTEXT IDC_HK_FINGERPRINT, 40, 48, 300, 12, ES_READONLY | ES_LEFT, 0 + EDITTEXT IDC_HK_HOST, 40, 28, 300, 12, ES_READONLY | ES_LEFT, 0 + EDITTEXT IDC_HK_FINGERPRINT, 40, 60, 300, 12, ES_READONLY | ES_LEFT, 0 END /* No accelerators used */ -IDD_HK_WRONG DIALOG DISCARDABLE 50, 50, 340, 188 +IDD_HK_WRONG DIALOG DISCARDABLE 50, 50, 340, 200 STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "PuTTY Security Alert" FONT 8, "MS Shell Dlg" BEGIN LTEXT "WARNING - POTENTIAL SECURITY BREACH!", IDC_HK_TITLE, 40, 20, 300, 12 - LTEXT "The server's host key does not match the one {APPNAME} has cached in", 100, 40, 36, 300, 8 - LTEXT "the registry. This means that either the server administrator has", 101, 40, 44, 300, 8 - LTEXT "changed the host key, or you have actually connected to another", 102, 40, 52, 300, 8 - LTEXT "computer pretending to be the server.", 103, 40, 60, 300, 8 - LTEXT "The new {KEYTYPE} key fingerprint is:", 104, 40, 72, 300, 8 - LTEXT "If you were expecting this change and trust the new key, press", 105, 40, 92, 300, 8 - LTEXT """Accept"" to update {APPNAME}'s cache and continue connecting.", 106, 40, 100, 300, 8 - LTEXT "If you want to carry on connecting but without updating the cache,", 107, 40, 112, 300, 8 - LTEXT "press ""Connect Once"".", 108, 40, 120, 300, 8 - LTEXT "If you want to abandon the connection completely, press ""Cancel"".", 109, 40, 132, 300, 8 - LTEXT "Pressing ""Cancel"" is the ONLY guaranteed safe choice.", 110, 40, 140, 300, 8 + LTEXT "The host key does not match the one {APPNAME} has cached for this server:", 100, 40, 36, 300, 8 + LTEXT "This means that either the server administrator has changed the", 101, 40, 56, 300, 8 + LTEXT "host key, or you have actually connected to another computer", 102, 40, 64, 300, 8 + LTEXT "pretending to be the server.", 103, 40, 72, 300, 8 + LTEXT "The new {KEYTYPE} key fingerprint is:", 104, 40, 84, 300, 8 + LTEXT "If you were expecting this change and trust the new key, press", 105, 40, 104, 300, 8 + LTEXT """Accept"" to update {APPNAME}'s cache and continue connecting.", 106, 40, 112, 300, 8 + LTEXT "If you want to carry on connecting but without updating the cache,", 107, 40, 124, 300, 8 + LTEXT "press ""Connect Once"".", 108, 40, 132, 300, 8 + LTEXT "If you want to abandon the connection completely, press ""Cancel"".", 109, 40, 144, 300, 8 + LTEXT "Pressing ""Cancel"" is the ONLY guaranteed safe choice.", 110, 40, 152, 300, 8 ICON "", IDC_HK_ICON, 10, 16, 0, 0 - PUSHBUTTON "Cancel", IDCANCEL, 288, 168, 40, 14 - PUSHBUTTON "Accept", IDC_HK_ACCEPT, 168, 168, 40, 14 - PUSHBUTTON "Connect Once", IDC_HK_ONCE, 216, 168, 64, 14 - PUSHBUTTON "More info...", IDC_HK_MOREINFO, 60, 168, 64, 14 - PUSHBUTTON "Help", IDHELP, 12, 168, 40, 14 + PUSHBUTTON "Cancel", IDCANCEL, 288, 180, 40, 14 + PUSHBUTTON "Accept", IDC_HK_ACCEPT, 168, 180, 40, 14 + PUSHBUTTON "Connect Once", IDC_HK_ONCE, 216, 180, 64, 14 + PUSHBUTTON "More info...", IDC_HK_MOREINFO, 60, 180, 64, 14 + PUSHBUTTON "Help", IDHELP, 12, 180, 40, 14 - EDITTEXT IDC_HK_FINGERPRINT, 40, 80, 300, 12, ES_READONLY | ES_LEFT, 0 + EDITTEXT IDC_HK_HOST, 40, 44, 300, 12, ES_READONLY | ES_LEFT, 0 + EDITTEXT IDC_HK_FINGERPRINT, 40, 92, 300, 12, ES_READONLY | ES_LEFT, 0 END /* Accelerators used: clw */ diff --git a/windows/putty-rc.h b/windows/putty-rc.h index cdfae94a..003609fd 100644 --- a/windows/putty-rc.h +++ b/windows/putty-rc.h @@ -36,8 +36,9 @@ #define IDC_HK_TITLE 99 #define IDC_HK_ACCEPT 1001 #define IDC_HK_ONCE 1000 -#define IDC_HK_FINGERPRINT 1002 -#define IDC_HK_MOREINFO 1003 +#define IDC_HK_HOST 1002 +#define IDC_HK_FINGERPRINT 1003 +#define IDC_HK_MOREINFO 1004 #define IDC_HKI_SHA256 1000 #define IDC_HKI_MD5 1001 -- cgit v1.2.3 From a45ae8179770b07524feacca5994eb791356b3fd Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 16 Sep 2021 14:50:59 +0100 Subject: Remove 'is_stderr' parameter from term_data. It wasn't actually used for anything, and removing it now will save me deciding what to do with it in the next commit. --- fuzzterm.c | 2 +- putty.h | 2 +- terminal.c | 4 ++-- unix/window.c | 2 +- windows/window.c | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/fuzzterm.c b/fuzzterm.c index b1efeac7..a85df35a 100644 --- a/fuzzterm.c +++ b/fuzzterm.c @@ -34,7 +34,7 @@ int main(int argc, char **argv) #endif while (!feof(stdin)) { len = fread(blk, 1, sizeof(blk), stdin); - term_data(term, false, blk, len); + term_data(term, blk, len); } term_update(term); return 0; diff --git a/putty.h b/putty.h index 907f3ff0..dba26335 100644 --- a/putty.h +++ b/putty.h @@ -1900,7 +1900,7 @@ void term_reconfig(Terminal *, Conf *); void term_request_copy(Terminal *, const int *clipboards, int n_clipboards); void term_request_paste(Terminal *, int clipboard); void term_seen_key_event(Terminal *); -size_t term_data(Terminal *, bool is_stderr, const void *data, size_t len); +size_t term_data(Terminal *, const void *data, size_t len); void term_provide_backend(Terminal *term, Backend *backend); void term_provide_logctx(Terminal *term, LogContext *logctx); void term_set_focus(Terminal *term, bool has_focus); diff --git a/terminal.c b/terminal.c index e94e3634..ddeb1e94 100644 --- a/terminal.c +++ b/terminal.c @@ -7453,7 +7453,7 @@ static void term_added_data(Terminal *term) } } -size_t term_data(Terminal *term, bool is_stderr, const void *data, size_t len) +size_t term_data(Terminal *term, const void *data, size_t len) { bufchain_add(&term->inbuf, data, len); term_added_data(term); @@ -7516,7 +7516,7 @@ struct term_userpass_state { /* Tiny wrapper to make it easier to write lots of little strings */ static inline void term_write(Terminal *term, ptrlen data) { - term_data(term, false, data.ptr, data.len); + term_data(term, data.ptr, data.len); } /* diff --git a/unix/window.c b/unix/window.c index e1e36fa5..b3f98827 100644 --- a/unix/window.c +++ b/unix/window.c @@ -322,7 +322,7 @@ static size_t gtk_seat_output(Seat *seat, bool is_stderr, const void *data, size_t len) { GtkFrontend *inst = container_of(seat, GtkFrontend, seat); - return term_data(inst->term, is_stderr, data, len); + return term_data(inst->term, data, len); } static bool gtk_seat_eof(Seat *seat) diff --git a/windows/window.c b/windows/window.c index 5aed396c..ec398c0f 100644 --- a/windows/window.c +++ b/windows/window.c @@ -5737,7 +5737,7 @@ static void flip_full_screen() static size_t win_seat_output(Seat *seat, bool is_stderr, const void *data, size_t len) { - return term_data(term, is_stderr, data, len); + return term_data(term, data, len); } static bool win_seat_eof(Seat *seat) -- cgit v1.2.3 From ac47e550c6f6be4a5caa8eb5ac759b44b13399d0 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 16 Sep 2021 14:46:49 +0100 Subject: seat_output: add an output type for SSH banners. (NFC) The jump host system ought really to be treating SSH authentication banners as a distinct thing from the standard-error session output, so that the former can be presented to the user in the same way as the auth banner for the main session. This change converts the 'bool is_stderr' parameter of seat_output() into an enumerated type with three values. For the moment, stderr and banners are treated the same, but the plan is for that to change. --- pscp.c | 11 ++++++----- psftp.c | 11 ++++++----- putty.h | 35 +++++++++++++++++++++++------------ ssh/sesschan.c | 11 ++++++++--- ssh/userauth2-client.c | 6 +++--- sshproxy.c | 2 +- unix/plink.c | 3 ++- unix/window.c | 2 +- utils/nullseat.c | 2 +- utils/tempseat.c | 23 +++++++++++++++-------- windows/plink.c | 3 ++- windows/window.c | 4 ++-- 12 files changed, 70 insertions(+), 43 deletions(-) diff --git a/pscp.c b/pscp.c index 7717d8d6..07546dd4 100644 --- a/pscp.c +++ b/pscp.c @@ -60,7 +60,7 @@ const char *const appname = "PSCP"; void ldisc_echoedit_update(Ldisc *ldisc) { } void ldisc_check_sendok(Ldisc *ldisc) { } -static size_t pscp_output(Seat *, bool is_stderr, const void *, size_t); +static size_t pscp_output(Seat *, SeatOutputType type, const void *, size_t); static bool pscp_eof(Seat *); static const SeatVtable pscp_seat_vt = { @@ -146,13 +146,14 @@ static PRINTF_LIKE(2, 3) void tell_user(FILE *stream, const char *fmt, ...) static bufchain received_data; static BinarySink *stderr_bs; static size_t pscp_output( - Seat *seat, bool is_stderr, const void *data, size_t len) + Seat *seat, SeatOutputType type, const void *data, size_t len) { /* - * stderr data is just spouted to local stderr (optionally via a - * sanitiser) and otherwise ignored. + * Non-stdout data (both stderr and SSH auth banners) is just + * spouted to local stderr (optionally via a sanitiser) and + * otherwise ignored. */ - if (is_stderr) { + if (type != SEAT_OUTPUT_STDOUT) { put_data(stderr_bs, data, len); return 0; } diff --git a/psftp.c b/psftp.c index a09837c7..993a3e35 100644 --- a/psftp.c +++ b/psftp.c @@ -41,7 +41,7 @@ static bool sent_eof = false; * Seat vtable. */ -static size_t psftp_output(Seat *, bool is_stderr, const void *, size_t); +static size_t psftp_output(Seat *, SeatOutputType type, const void *, size_t); static bool psftp_eof(Seat *); static const SeatVtable psftp_seat_vt = { @@ -2461,13 +2461,14 @@ void ldisc_check_sendok(Ldisc *ldisc) { } static bufchain received_data; static BinarySink *stderr_bs; static size_t psftp_output( - Seat *seat, bool is_stderr, const void *data, size_t len) + Seat *seat, SeatOutputType type, const void *data, size_t len) { /* - * stderr data is just spouted to local stderr (optionally via a - * sanitiser) and otherwise ignored. + * Non-stdout data (both stderr and SSH auth banners) is just + * spouted to local stderr (optionally via a sanitiser) and + * otherwise ignored. */ - if (is_stderr) { + if (type != SEAT_OUTPUT_STDOUT) { put_data(stderr_bs, data, len); return 0; } diff --git a/putty.h b/putty.h index dba26335..5a765771 100644 --- a/putty.h +++ b/putty.h @@ -889,6 +889,10 @@ typedef enum SeatInteractionContext { SIC_BANNER, SIC_KI_PROMPTS } SeatInteractionContext; +typedef enum SeatOutputType { + SEAT_OUTPUT_STDOUT, SEAT_OUTPUT_STDERR, SEAT_OUTPUT_AUTH_BANNER +} SeatOutputType; + /* * Data type 'Seat', which is an API intended to contain essentially * everything that a back end might need to talk to its client for: @@ -901,14 +905,17 @@ struct Seat { }; struct SeatVtable { /* - * Provide output from the remote session. 'is_stderr' indicates - * that the output should be sent to a separate error message - * channel, if the seat has one. But combining both channels into - * one is OK too; that's what terminal-window based seats do. + * Provide output from the remote session. 'type' indicates the + * type of the output (stdout, stderr or SSH auth banner), which + * can be used to split the output into separate message channels, + * if the seat wants to handle them differently. But combining the + * channels into one is OK too; that's what terminal-window based + * seats do. * * The return value is the current size of the output backlog. */ - size_t (*output)(Seat *seat, bool is_stderr, const void *data, size_t len); + size_t (*output)(Seat *seat, SeatOutputType type, + const void *data, size_t len); /* * Called when the back end wants to indicate that EOF has arrived @@ -1167,8 +1174,8 @@ struct SeatVtable { }; static inline size_t seat_output( - Seat *seat, bool err, const void *data, size_t len) -{ return seat->vt->output(seat, err, data, len); } + Seat *seat, SeatOutputType type, const void *data, size_t len) +{ return seat->vt->output(seat, type, data, len); } static inline bool seat_eof(Seat *seat) { return seat->vt->eof(seat); } static inline void seat_sent(Seat *seat, size_t bufsize) @@ -1231,13 +1238,17 @@ void seat_connection_fatal(Seat *seat, const char *fmt, ...) PRINTF_LIKE(2, 3); /* Handy aliases for seat_output which set is_stderr to a fixed value. */ static inline size_t seat_stdout(Seat *seat, const void *data, size_t len) -{ return seat_output(seat, false, data, len); } +{ return seat_output(seat, SEAT_OUTPUT_STDOUT, data, len); } static inline size_t seat_stdout_pl(Seat *seat, ptrlen data) -{ return seat_output(seat, false, data.ptr, data.len); } +{ return seat_output(seat, SEAT_OUTPUT_STDOUT, data.ptr, data.len); } static inline size_t seat_stderr(Seat *seat, const void *data, size_t len) -{ return seat_output(seat, true, data, len); } +{ return seat_output(seat, SEAT_OUTPUT_STDERR, data, len); } static inline size_t seat_stderr_pl(Seat *seat, ptrlen data) -{ return seat_output(seat, true, data.ptr, data.len); } +{ return seat_output(seat, SEAT_OUTPUT_STDERR, data.ptr, data.len); } +static inline size_t seat_banner(Seat *seat, const void *data, size_t len) +{ return seat_output(seat, SEAT_OUTPUT_AUTH_BANNER, data, len); } +static inline size_t seat_banner_pl(Seat *seat, ptrlen data) +{ return seat_output(seat, SEAT_OUTPUT_AUTH_BANNER, data.ptr, data.len); } /* * Stub methods for seat implementations that want to use the obvious @@ -1247,7 +1258,7 @@ static inline size_t seat_stderr_pl(Seat *seat, ptrlen data) * plausibly want to return either fixed answer 'no' or 'yes'. */ size_t nullseat_output( - Seat *seat, bool is_stderr, const void *data, size_t len); + Seat *seat, SeatOutputType type, const void *data, size_t len); bool nullseat_eof(Seat *seat); void nullseat_sent(Seat *seat, size_t bufsize); int nullseat_get_userpass_input(Seat *seat, prompts_t *p); diff --git a/ssh/sesschan.c b/ssh/sesschan.c index 527b7603..1ef55dde 100644 --- a/ssh/sesschan.c +++ b/ssh/sesschan.c @@ -177,7 +177,7 @@ static const LogPolicyVtable sesschan_logpolicy_vt = { }; static size_t sesschan_seat_output( - Seat *, bool is_stderr, const void *, size_t); + Seat *, SeatOutputType type, const void *, size_t); static bool sesschan_seat_eof(Seat *); static void sesschan_notify_remote_exit(Seat *seat); static void sesschan_connection_fatal(Seat *seat, const char *message); @@ -612,10 +612,15 @@ bool sesschan_change_window_size( } static size_t sesschan_seat_output( - Seat *seat, bool is_stderr, const void *data, size_t len) + Seat *seat, SeatOutputType type, const void *data, size_t len) { sesschan *sess = container_of(seat, sesschan, seat); - return sshfwd_write_ext(sess->c, is_stderr, data, len); + + /* We don't expect anything but stdout and stderr to come here, + * because the pty backend doesn't generate auth banners */ + assert(type != SEAT_OUTPUT_AUTH_BANNER); + + return sshfwd_write_ext(sess->c, type == SEAT_OUTPUT_STDERR, data, len); } static void sesschan_check_close_callback(void *vctx) diff --git a/ssh/userauth2-client.c b/ssh/userauth2-client.c index f1e2f319..4c908048 100644 --- a/ssh/userauth2-client.c +++ b/ssh/userauth2-client.c @@ -530,7 +530,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) bool mid_line = false; while (bufchain_size(&s->banner) > 0) { ptrlen data = bufchain_prefix(&s->banner); - seat_stderr_pl(s->ppl.seat, data); + seat_banner_pl(s->ppl.seat, data); mid_line = (((const char *)data.ptr)[data.len-1] != '\n'); bufchain_consume(&s->banner, data.len); @@ -538,7 +538,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) bufchain_clear(&s->banner); if (mid_line) - seat_stderr_pl(s->ppl.seat, PTRLEN_LITERAL("\r\n")); + seat_banner_pl(s->ppl.seat, PTRLEN_LITERAL("\r\n")); if (s->banner_scc) { seat_set_trust_status(s->ppl.seat, true); @@ -1919,6 +1919,6 @@ static void ssh2_userauth_antispoof_msg( put_byte(sb, '-'); } put_datapl(sb, PTRLEN_LITERAL("\r\n")); - seat_stderr_pl(s->ppl.seat, ptrlen_from_strbuf(sb)); + seat_banner_pl(s->ppl.seat, ptrlen_from_strbuf(sb)); strbuf_free(sb); } diff --git a/sshproxy.c b/sshproxy.c index 96667212..c5fed894 100644 --- a/sshproxy.c +++ b/sshproxy.c @@ -254,7 +254,7 @@ static void sshproxy_notify_session_started(Seat *seat) plug_log(sp->plug, PLUGLOG_CONNECT_SUCCESS, sp->addr, sp->port, NULL, 0); } -static size_t sshproxy_output(Seat *seat, bool is_stderr, +static size_t sshproxy_output(Seat *seat, SeatOutputType type, const void *data, size_t len) { SshProxy *sp = container_of(seat, SshProxy, seat); diff --git a/unix/plink.c b/unix/plink.c index 46f2da85..74e772b4 100644 --- a/unix/plink.c +++ b/unix/plink.c @@ -352,8 +352,9 @@ size_t try_output(bool is_stderr) } static size_t plink_output( - Seat *seat, bool is_stderr, const void *data, size_t len) + Seat *seat, SeatOutputType type, const void *data, size_t len) { + bool is_stderr = type != SEAT_OUTPUT_STDOUT; assert(is_stderr || outgoingeof == EOF_NO); BinarySink *bs = is_stderr ? stderr_bs : stdout_bs; diff --git a/unix/window.c b/unix/window.c index b3f98827..69c35eb3 100644 --- a/unix/window.c +++ b/unix/window.c @@ -318,7 +318,7 @@ static char *gtk_seat_get_ttymode(Seat *seat, const char *mode) return term_get_ttymode(inst->term, mode); } -static size_t gtk_seat_output(Seat *seat, bool is_stderr, +static size_t gtk_seat_output(Seat *seat, SeatOutputType type, const void *data, size_t len) { GtkFrontend *inst = container_of(seat, GtkFrontend, seat); diff --git a/utils/nullseat.c b/utils/nullseat.c index 564225e5..43c478e0 100644 --- a/utils/nullseat.c +++ b/utils/nullseat.c @@ -5,7 +5,7 @@ #include "putty.h" size_t nullseat_output( - Seat *seat, bool is_stderr, const void *data, size_t len) { return 0; } + Seat *seat, SeatOutputType type, const void *data, size_t len) {return 0;} bool nullseat_eof(Seat *seat) { return true; } void nullseat_sent(Seat *seat, size_t bufsize) {} int nullseat_get_userpass_input(Seat *seat, prompts_t *p) { return 0; } diff --git a/utils/tempseat.c b/utils/tempseat.c index aa3fbe5a..56006f61 100644 --- a/utils/tempseat.c +++ b/utils/tempseat.c @@ -17,7 +17,7 @@ typedef struct TempSeat TempSeat; struct TempSeat { Seat *realseat; - bufchain outputs[2]; /* stdout, stderr */ + bufchain outputs[3]; /* stdout, stderr, auth banner (just in case) */ bool seen_session_started; bool seen_remote_exit; bool seen_remote_disconnect; @@ -33,12 +33,19 @@ struct TempSeat { * real Seat in tempseat_flush(). */ -static size_t tempseat_output(Seat *seat, bool is_stderr, const void *data, - size_t len) +static size_t tempseat_output(Seat *seat, SeatOutputType type, + const void *data, size_t len) { TempSeat *ts = container_of(seat, TempSeat, seat); - bufchain_add(&ts->outputs[is_stderr], data, len); - return bufchain_size(&ts->outputs[0]) + bufchain_size(&ts->outputs[1]); + + size_t index = (size_t)type; + assert(index < lenof(ts->outputs)); + bufchain_add(&ts->outputs[index], data, len); + + size_t total_size = 0; + for (size_t i = 0; i < lenof(ts->outputs); i++) + total_size += bufchain_size(&ts->outputs[i]); + return total_size; } static void tempseat_notify_session_started(Seat *seat) @@ -295,7 +302,7 @@ Seat *tempseat_new(Seat *realseat) ts->seat.vt = &tempseat_vt; ts->realseat = realseat; - for (unsigned i = 0; i < 2; i++) + for (size_t i = 0; i < lenof(ts->outputs); i++) bufchain_init(&ts->outputs[i]); return &ts->seat; @@ -327,8 +334,8 @@ void tempseat_flush(Seat *seat) assert(seat->vt == &tempseat_vt); TempSeat *ts = container_of(seat, TempSeat, seat); - /* Empty the stdout/stderr bufchains into the real seat */ - for (unsigned i = 0; i < 2; i++) { + /* Empty the output bufchains into the real seat */ + for (size_t i = 0; i < lenof(ts->outputs); i++) { while (bufchain_size(&ts->outputs[i])) { ptrlen pl = bufchain_prefix(&ts->outputs[i]); seat_output(ts->realseat, i, pl.ptr, pl.len); diff --git a/windows/plink.c b/windows/plink.c index bbed994d..1c1872dc 100644 --- a/windows/plink.c +++ b/windows/plink.c @@ -50,8 +50,9 @@ static void plink_echoedit_update(Seat *seat, bool echo, bool edit) } static size_t plink_output( - Seat *seat, bool is_stderr, const void *data, size_t len) + Seat *seat, SeatOutputType type, const void *data, size_t len) { + bool is_stderr = type != SEAT_OUTPUT_STDOUT; BinarySink *bs = is_stderr ? stderr_bs : stdout_bs; put_data(bs, data, len); diff --git a/windows/window.c b/windows/window.c index ec398c0f..8fbbbd27 100644 --- a/windows/window.c +++ b/windows/window.c @@ -315,7 +315,7 @@ static StripCtrlChars *win_seat_stripctrl_new( } static size_t win_seat_output( - Seat *seat, bool is_stderr, const void *, size_t); + Seat *seat, SeatOutputType type, const void *, size_t); static bool win_seat_eof(Seat *seat); static int win_seat_get_userpass_input(Seat *seat, prompts_t *p); static void win_seat_notify_remote_exit(Seat *seat); @@ -5734,7 +5734,7 @@ static void flip_full_screen() } } -static size_t win_seat_output(Seat *seat, bool is_stderr, +static size_t win_seat_output(Seat *seat, SeatOutputType type, const void *data, size_t len) { return term_data(term, data, len); -- cgit v1.2.3 From 71cb9ca487f495c112af6c496f091ccf2349cc00 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 16 Sep 2021 15:03:42 +0100 Subject: TempSeat: fix output interleaving. Working on the previous commit, I suddenly realised I'd made a mistake in the design of TempSeat: you can't buffer standard output and standard error separately and then replay them one after another, because the interleaving of the two kinds of output might also be significant. (Especially if the consuming Seat doesn't separate them.) Now TempSeat has a single bufchain for all the data, paralleled by a linked list describing each contiguous chunk of it consisting of a single output type. So we can replay the data with both the correct separation _and_ the correct order. --- utils/tempseat.c | 82 +++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 64 insertions(+), 18 deletions(-) diff --git a/utils/tempseat.c b/utils/tempseat.c index 56006f61..0046fe0d 100644 --- a/utils/tempseat.c +++ b/utils/tempseat.c @@ -14,10 +14,28 @@ #include "putty.h" +struct output_chunk { + struct output_chunk *next; + SeatOutputType type; + size_t size; +}; + typedef struct TempSeat TempSeat; struct TempSeat { Seat *realseat; - bufchain outputs[3]; /* stdout, stderr, auth banner (just in case) */ + + /* + * Single bufchain to hold all the buffered output, regardless of + * its type. + */ + bufchain output; + + /* + * List of pieces of that bufchain that are intended for one or + * another output destination + */ + struct output_chunk *outchunk_head, *outchunk_tail; + bool seen_session_started; bool seen_remote_exit; bool seen_remote_disconnect; @@ -38,14 +56,24 @@ static size_t tempseat_output(Seat *seat, SeatOutputType type, { TempSeat *ts = container_of(seat, TempSeat, seat); - size_t index = (size_t)type; - assert(index < lenof(ts->outputs)); - bufchain_add(&ts->outputs[index], data, len); + bufchain_add(&ts->output, data, len); - size_t total_size = 0; - for (size_t i = 0; i < lenof(ts->outputs); i++) - total_size += bufchain_size(&ts->outputs[i]); - return total_size; + if (!(ts->outchunk_tail && ts->outchunk_tail->type == type)) { + struct output_chunk *new_chunk = snew(struct output_chunk); + + new_chunk->type = type; + new_chunk->size = 0; + + new_chunk->next = NULL; + if (ts->outchunk_tail) + ts->outchunk_tail->next = new_chunk; + else + ts->outchunk_head = new_chunk; + ts->outchunk_tail = new_chunk; + } + ts->outchunk_tail->type += len; + + return bufchain_size(&ts->output); } static void tempseat_notify_session_started(Seat *seat) @@ -302,8 +330,8 @@ Seat *tempseat_new(Seat *realseat) ts->seat.vt = &tempseat_vt; ts->realseat = realseat; - for (size_t i = 0; i < lenof(ts->outputs); i++) - bufchain_init(&ts->outputs[i]); + bufchain_init(&ts->output); + ts->outchunk_head = ts->outchunk_tail = NULL; return &ts->seat; } @@ -324,8 +352,12 @@ void tempseat_free(Seat *seat) { assert(seat->vt == &tempseat_vt); TempSeat *ts = container_of(seat, TempSeat, seat); - for (unsigned i = 0; i < 2; i++) - bufchain_clear(&ts->outputs[i]); + bufchain_clear(&ts->output); + while (ts->outchunk_head) { + struct output_chunk *chunk = ts->outchunk_head; + ts->outchunk_head = chunk->next; + sfree(chunk); + } sfree(ts); } @@ -334,15 +366,29 @@ void tempseat_flush(Seat *seat) assert(seat->vt == &tempseat_vt); TempSeat *ts = container_of(seat, TempSeat, seat); - /* Empty the output bufchains into the real seat */ - for (size_t i = 0; i < lenof(ts->outputs); i++) { - while (bufchain_size(&ts->outputs[i])) { - ptrlen pl = bufchain_prefix(&ts->outputs[i]); - seat_output(ts->realseat, i, pl.ptr, pl.len); - bufchain_consume(&ts->outputs[i], pl.len); + /* Empty the output bufchains into the real seat, taking care to + * preserve both separation and interleaving */ + while (bufchain_size(&ts->output)) { + ptrlen pl = bufchain_prefix(&ts->output); + + assert(ts->outchunk_head); + struct output_chunk *chunk = ts->outchunk_head; + + if (pl.len > chunk->size) + pl.len = chunk->size; + + seat_output(ts->realseat, chunk->type, pl.ptr, pl.len); + bufchain_consume(&ts->output, pl.len); + chunk->size -= pl.len; + if (chunk->size == 0) { + ts->outchunk_head = chunk->next; + sfree(chunk); } } + /* That should have exactly emptied the output chunk list too */ + assert(!ts->outchunk_head); + /* Pass on any other kinds of event we've buffered */ if (ts->seen_session_started) seat_notify_session_started(ts->realseat); -- cgit v1.2.3 From d32d49c2e01ce374bc3f7f289972b3d46c1204c8 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 16 Sep 2021 17:18:49 +0100 Subject: SshProxy: pass some more functions to the client seat. Now that we're actually using it for messages, we also need to pass its interactivity setting through to the subsidiary SSH backend, because otherwise that won't know whether to display particular messages. Same goes for constructing a StripCtrl for SSH auth banners (which wants to be done the same way between primary and proxy SSH connections), and so on. --- sshproxy.c | 45 ++++++++++++++++++++++++++++++--------------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/sshproxy.c b/sshproxy.c index c5fed894..d1448219 100644 --- a/sshproxy.c +++ b/sshproxy.c @@ -419,24 +419,39 @@ static int sshproxy_confirm_weak_cached_hostkey( return 0; } +static StripCtrlChars *sshproxy_stripctrl_new( + Seat *seat, BinarySink *bs_out, SeatInteractionContext sic) +{ + SshProxy *sp = container_of(seat, SshProxy, seat); + if (sp->clientseat) + return seat_stripctrl_new(sp->clientseat, bs_out, sic); + else + return NULL; +} + static void sshproxy_set_trust_status(Seat *seat, bool trusted) { - /* - * This is called by the proxy SSH connection, to set our Seat - * into a given trust status. We can safely do nothing here, and - * have can_set_trust_status return true to claim we did something - * (effectively eliminating the spoofing defences completely, by - * suppressing the 'press Return to begin session' prompt and not - * providing anything in place of it), on the basis that session - * I/O from the proxy SSH connection is never passed directly on - * to the end user, so a malicious proxy SSH server wouldn't be - * able to spoof our human in any case. - */ + SshProxy *sp = container_of(seat, SshProxy, seat); + if (sp->clientseat) + seat_set_trust_status(sp->clientseat, trusted); } static bool sshproxy_can_set_trust_status(Seat *seat) { - return true; /* see comment above */ + SshProxy *sp = container_of(seat, SshProxy, seat); + return sp->clientseat && seat_can_set_trust_status(sp->clientseat); +} + +static bool sshproxy_verbose(Seat *seat) +{ + SshProxy *sp = container_of(seat, SshProxy, seat); + return sp->clientseat && seat_verbose(sp->clientseat); +} + +static bool sshproxy_interactive(Seat *seat) +{ + SshProxy *sp = container_of(seat, SshProxy, seat); + return sp->clientseat && seat_interactive(sp->clientseat); } static const SeatVtable SshProxy_seat_vt = { @@ -459,11 +474,11 @@ static const SeatVtable SshProxy_seat_vt = { .get_x_display = nullseat_get_x_display, .get_windowid = nullseat_get_windowid, .get_window_pixel_size = nullseat_get_window_pixel_size, - .stripctrl_new = nullseat_stripctrl_new, + .stripctrl_new = sshproxy_stripctrl_new, .set_trust_status = sshproxy_set_trust_status, .can_set_trust_status = sshproxy_can_set_trust_status, - .verbose = nullseat_verbose_no, - .interactive = nullseat_interactive_no, + .verbose = sshproxy_verbose, + .interactive = sshproxy_interactive, .get_cursor_position = nullseat_get_cursor_position, }; -- cgit v1.2.3 From 5ca0a7563671c3a088617d6f120a45078a01fe17 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 16 Sep 2021 17:20:16 +0100 Subject: SshProxy: display auth banners from proxy connections. Now the banners (plus their surrounding antispoof prompts) have their own SeatOutputType, it's easy to distinguish them in sshproxy_output and do the right thing with them. --- sshproxy.c | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/sshproxy.c b/sshproxy.c index d1448219..20815629 100644 --- a/sshproxy.c +++ b/sshproxy.c @@ -258,9 +258,21 @@ static size_t sshproxy_output(Seat *seat, SeatOutputType type, const void *data, size_t len) { SshProxy *sp = container_of(seat, SshProxy, seat); - bufchain_add(&sp->ssh_to_socket, data, len); - try_send_ssh_to_socket(sp); - return bufchain_size(&sp->ssh_to_socket); + if (type == SEAT_OUTPUT_AUTH_BANNER) { + if (sp->clientseat) { + /* + * If we have access to the outer Seat, pass the SSH login + * banner on to it. + */ + return seat_output(sp->clientseat, type, data, len); + } else { + return 0; + } + } else { + bufchain_add(&sp->ssh_to_socket, data, len); + try_send_ssh_to_socket(sp); + return bufchain_size(&sp->ssh_to_socket); + } } static bool sshproxy_eof(Seat *seat) -- cgit v1.2.3 From adf6b698e4867fd229e956eff277a53b4021738e Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 16 Sep 2021 17:21:03 +0100 Subject: SshProxy: reset trust status after setup completes. The next backend that tries to use the connection we're now proxying is going to expect the seat's trust status to be in trusted mode, because that's how things normally start up when a backend is initialised. So we should set it back to that state before handing on to that backend. --- sshproxy.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sshproxy.c b/sshproxy.c index 20815629..aea19cf6 100644 --- a/sshproxy.c +++ b/sshproxy.c @@ -251,6 +251,10 @@ static void try_send_ssh_to_socket(void *ctx) static void sshproxy_notify_session_started(Seat *seat) { SshProxy *sp = container_of(seat, SshProxy, seat); + + if (sp->clientseat) + seat_set_trust_status(sp->clientseat, true); + plug_log(sp->plug, PLUGLOG_CONNECT_SUCCESS, sp->addr, sp->port, NULL, 0); } -- cgit v1.2.3 From fb663d4761c221fed090ff98d891a9883e76cbb3 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 16 Sep 2021 11:43:02 +0100 Subject: Promote ssh2_userauth_antispoof_msg into utils. It doesn't actually do anything specific to the userauth layer; it's just a helper function that deals with the mechanics of printing an unspoofable message on various kinds of front end, and the only parameters it needs are a Seat and a message. Currently, it's used for 'here is the start/end of the server banner' only. But it's also got all the right functionality to be used for the (still missing) messages about which proxy SSH server the next set of login prompts are going to refer to. --- putty.h | 4 ++++ ssh/userauth2-client.c | 47 ++++++++++------------------------------------- utils/CMakeLists.txt | 1 + utils/antispoof.c | 28 ++++++++++++++++++++++++++++ 4 files changed, 43 insertions(+), 37 deletions(-) create mode 100644 utils/antispoof.c diff --git a/putty.h b/putty.h index 5a765771..caca09f3 100644 --- a/putty.h +++ b/putty.h @@ -1250,6 +1250,10 @@ static inline size_t seat_banner(Seat *seat, const void *data, size_t len) static inline size_t seat_banner_pl(Seat *seat, ptrlen data) { return seat_output(seat, SEAT_OUTPUT_AUTH_BANNER, data.ptr, data.len); } +/* In the utils subdir: print a message to the Seat which can't be + * spoofed by server-supplied auth-time output such as SSH banners */ +void seat_antispoof_msg(Seat *seat, const char *msg); + /* * Stub methods for seat implementations that want to use the obvious * null handling for a given method. diff --git a/ssh/userauth2-client.c b/ssh/userauth2-client.c index 4c908048..e82abe8a 100644 --- a/ssh/userauth2-client.c +++ b/ssh/userauth2-client.c @@ -114,8 +114,6 @@ static void ssh2_userauth_add_session_id( static PktOut *ssh2_userauth_gss_packet( struct ssh2_userauth_state *s, const char *authtype); #endif -static void ssh2_userauth_antispoof_msg( - struct ssh2_userauth_state *s, const char *msg); static const PacketProtocolLayerVtable ssh2_userauth_vtable = { .free = ssh2_userauth_free, @@ -522,8 +520,9 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) if (bufchain_size(&s->banner) && (seat_verbose(s->ppl.seat) || seat_interactive(s->ppl.seat))) { if (s->banner_scc) { - ssh2_userauth_antispoof_msg( - s, "Pre-authentication banner message from server:"); + seat_antispoof_msg( + s->ppl.seat, + "Pre-authentication banner message from server:"); seat_set_trust_status(s->ppl.seat, false); } @@ -542,8 +541,8 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) if (s->banner_scc) { seat_set_trust_status(s->ppl.seat, true); - ssh2_userauth_antispoof_msg( - s, "End of banner message from server"); + seat_antispoof_msg(s->ppl.seat, + "End of banner message from server"); } } @@ -1343,8 +1342,8 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) */ if (!s->ki_printed_header && s->ki_scc && (s->num_prompts || name.len || inst.len)) { - ssh2_userauth_antispoof_msg( - s, "Keyboard-interactive authentication " + seat_antispoof_msg( + s->ppl.seat, "Keyboard-interactive authentication " "prompts from server:"); s->ki_printed_header = true; seat_set_trust_status(s->ppl.seat, false); @@ -1446,8 +1445,9 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) */ if (s->ki_printed_header) { seat_set_trust_status(s->ppl.seat, true); - ssh2_userauth_antispoof_msg( - s, "End of keyboard-interactive prompts from server"); + seat_antispoof_msg( + s->ppl.seat, + "End of keyboard-interactive prompts from server"); } /* @@ -1895,30 +1895,3 @@ static void ssh2_userauth_reconfigure(PacketProtocolLayer *ppl, Conf *conf) container_of(ppl, struct ssh2_userauth_state, ppl); ssh_ppl_reconfigure(s->successor_layer, conf); } - -static void ssh2_userauth_antispoof_msg( - struct ssh2_userauth_state *s, const char *msg) -{ - strbuf *sb = strbuf_new(); - seat_set_trust_status(s->ppl.seat, true); - if (seat_can_set_trust_status(s->ppl.seat)) { - /* - * If the seat can directly indicate that this message is - * generated by the client, then we can just use the message - * unmodified as an unspoofable header. - */ - put_datapl(sb, ptrlen_from_asciz(msg)); - } else { - /* - * Otherwise, add enough padding around it that the server - * wouldn't be able to mimic it within our line-length - * constraint. - */ - strbuf_catf(sb, "-- %s ", msg); - while (sb->len < 78) - put_byte(sb, '-'); - } - put_datapl(sb, PTRLEN_LITERAL("\r\n")); - seat_banner_pl(s->ppl.seat, ptrlen_from_strbuf(sb)); - strbuf_free(sb); -} diff --git a/utils/CMakeLists.txt b/utils/CMakeLists.txt index 4b10442b..a209eb58 100644 --- a/utils/CMakeLists.txt +++ b/utils/CMakeLists.txt @@ -1,4 +1,5 @@ add_sources_from_current_dir(utils + antispoof.c base64_decode_atom.c base64_encode_atom.c bufchain.c diff --git a/utils/antispoof.c b/utils/antispoof.c new file mode 100644 index 00000000..d8599455 --- /dev/null +++ b/utils/antispoof.c @@ -0,0 +1,28 @@ +#include "putty.h" +#include "misc.h" + +void seat_antispoof_msg(Seat *seat, const char *msg) +{ + strbuf *sb = strbuf_new(); + seat_set_trust_status(seat, true); + if (seat_can_set_trust_status(seat)) { + /* + * If the seat can directly indicate that this message is + * generated by the client, then we can just use the message + * unmodified as an unspoofable header. + */ + put_datapl(sb, ptrlen_from_asciz(msg)); + } else { + /* + * Otherwise, add enough padding around it that the server + * wouldn't be able to mimic it within our line-length + * constraint. + */ + strbuf_catf(sb, "-- %s ", msg); + while (sb->len < 78) + put_byte(sb, '-'); + } + put_datapl(sb, PTRLEN_LITERAL("\r\n")); + seat_banner_pl(seat, ptrlen_from_strbuf(sb)); + strbuf_free(sb); +} -- cgit v1.2.3 From d489c64f48d50a17e688edc9fee4973790f8aca7 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 17 Sep 2021 16:10:29 +0100 Subject: Uppity: allow running multiple independent servers. I've moved all the results of the command-line config options into a small struct instead of having them be local variables of main(). We maintain an array of those structs; most command-line options modify the last element in the array; and we respond to the new special option '--and' by appending a fresh struct to the end of the array and initialising it to default values. So now, if I want two or three SSH servers running on different ports with separately configured host keys, banners, etc, I can do that with a single command line along the lines of: ./uppity --listen 2222 --hostkey this.ppk --bannertext "this" \ --and --listen 2223 --hostkey that.ppk --bannertext "that" There's a single number space of connections used in log messages, and each new connection reports which of the servers it connects to. This is only a marginally useful feature: there's not much it does that couldn't have been done just as well by running multiple Uppitys each in their own process. But when I do want several servers at once (which I've been using recently to test the jump-host system), it's quite nice to have them all producing a single combined stream of log data and all conveniently killable with a single ^C. --- unix/uppity.c | 263 ++++++++++++++++++++++++++++++++++------------------------ 1 file changed, 155 insertions(+), 108 deletions(-) diff --git a/unix/uppity.c b/unix/uppity.c index 8d731492..4f11b0ec 100644 --- a/unix/uppity.c +++ b/unix/uppity.c @@ -130,6 +130,8 @@ struct server_instance { }; struct server_config { + unsigned config_id; + Conf *conf; const SshServerConfig *ssc; @@ -140,16 +142,16 @@ struct server_config { struct AuthPolicyShared *ap_shared; - unsigned next_id; - Socket *listening_socket; Plug listening_plug; }; +static unsigned next_id = 0; + static void log_to_stderr(unsigned id, const char *msg) { if (id != (unsigned)-1) - fprintf(stderr, "#%u: ", id); + fprintf(stderr, "conn#%u: ", id); fputs(msg, stderr); fputc('\n', stderr); fflush(stderr); @@ -348,7 +350,7 @@ static void show_help(FILE *fp) { safety_warning(fp); fputs("\n" - "usage: uppity [options]\n" + "usage: uppity [options] [--and ...]\n" "options: --listen [PORT|PATH] listen to a port on localhost, or Unix socket\n" " --listen-once (with --listen) stop after one " "connection\n" @@ -385,6 +387,7 @@ static void show_help(FILE *fp) " --sshlog FILE write SSH packet log to FILE\n" " --sshrawlog FILE write SSH packets + raw data log" " to FILE\n" + " --and run a separate server on another port\n" "also: uppity --help show this text\n" " uppity --version show version information\n" "\n", fp); @@ -461,7 +464,7 @@ static Plug *server_conn_plug( memset(inst, 0, sizeof(*inst)); - inst->id = cfg->next_id++; + inst->id = next_id++; inst->ap.shared = cfg->ap_shared; if (cfg->ssc->stunt_allow_trivial_ki_auth) inst->ap.kbdint_state = 1; @@ -504,7 +507,7 @@ static int server_accepting(Plug *p, accept_fn_t constructor, accept_ctx_t ctx) cfg->listening_socket = NULL; } - unsigned old_next_id = cfg->next_id; + unsigned old_next_id = next_id; Plug *plug = server_conn_plug(cfg, &inst); s = constructor(ctx, plug); @@ -514,15 +517,17 @@ static int server_accepting(Plug *p, accept_fn_t constructor, accept_ctx_t ctx) SocketPeerInfo *pi = sk_peer_info(s); if (pi->addressfamily != ADDRTYPE_LOCAL && !sk_peer_trusted(s)) { - fprintf(stderr, "rejected connection from %s (untrustworthy peer)\n", - pi->log_text); + fprintf(stderr, "rejected connection to serv#%u " + "from %s (untrustworthy peer)\n", + cfg->config_id, pi->log_text); sk_free_peer_info(pi); sk_close(s); - cfg->next_id = old_next_id; + next_id = old_next_id; return 1; } - char *msg = dupprintf("new connection from %s", pi->log_text); + char *msg = dupprintf("new connection to serv#%u from %s", + cfg->config_id, pi->log_text); log_to_stderr(inst->id, msg); sfree(msg); sk_free_peer_info(pi); @@ -538,29 +543,87 @@ static const PlugVtable server_plugvt = { .accepting = server_accepting, }; -int main(int argc, char **argv) +struct cmdline_instance { + int listen_port; + const char *listen_socket; + ssh_key **hostkeys; + size_t nhostkeys, hostkeysize; + RSAKey *hostkey1; + struct AuthPolicyShared aps; + SshServerConfig ssc; + Conf *conf; +}; + +static void init_cmdline_instance(struct cmdline_instance *ci) { - int listen_port = -1; - const char *listen_socket = NULL; + ci->listen_port = -1; + ci->listen_socket = NULL; - ssh_key **hostkeys = NULL; - size_t nhostkeys = 0, hostkeysize = 0; - RSAKey *hostkey1 = NULL; + ci->hostkeys = NULL; + ci->nhostkeys = ci->hostkeysize = 0; + ci->hostkey1 = NULL; - struct AuthPolicyShared aps; - SshServerConfig ssc; + ci->conf = make_ssh_server_conf(); - Conf *conf = make_ssh_server_conf(); + ci->aps.ssh1keys = NULL; + ci->aps.ssh2keys = NULL; - aps.ssh1keys = NULL; - aps.ssh2keys = NULL; + memset(&ci->ssc, 0, sizeof(ci->ssc)); - memset(&ssc, 0, sizeof(ssc)); + ci->ssc.application_name = "Uppity"; + ci->ssc.session_starting_dir = getenv("HOME"); + ci->ssc.ssh1_cipher_mask = SSH1_SUPPORTED_CIPHER_MASK; + ci->ssc.ssh1_allow_compression = true; +} - ssc.application_name = "Uppity"; - ssc.session_starting_dir = getenv("HOME"); - ssc.ssh1_cipher_mask = SSH1_SUPPORTED_CIPHER_MASK; - ssc.ssh1_allow_compression = true; +static void cmdline_instance_start(struct cmdline_instance *ci) +{ + static unsigned next_server_config_id = 0; + + struct server_config *scfg = snew(struct server_config); + scfg->config_id = next_server_config_id++; + scfg->conf = ci->conf; + scfg->ssc = &ci->ssc; + scfg->hostkeys = ci->hostkeys; + scfg->nhostkeys = ci->nhostkeys; + scfg->hostkey1 = ci->hostkey1; + scfg->ap_shared = &ci->aps; + + if (ci->listen_port >= 0 || ci->listen_socket) { + listening = true; + scfg->listening_plug.vt = &server_plugvt; + char *msg; + if (ci->listen_port >= 0) { + scfg->listening_socket = sk_newlistener( + NULL, ci->listen_port, &scfg->listening_plug, true, + ADDRTYPE_UNSPEC); + msg = dupprintf("serv#%u: listening on port %d", + scfg->config_id, ci->listen_port); + } else { + SockAddr *addr = unix_sock_addr(ci->listen_socket); + scfg->listening_socket = new_unix_listener( + addr, &scfg->listening_plug); + msg = dupprintf("serv#%u: listening on Unix socket %s", + scfg->config_id, ci->listen_socket); + } + + log_to_stderr(-1, msg); + sfree(msg); + } else { + struct server_instance *inst; + Plug *plug = server_conn_plug(scfg, &inst); + ssh_server_start(plug, make_fd_socket(0, 1, -1, NULL, 0, plug)); + log_to_stderr(inst->id, "speaking SSH on stdio"); + } +} + +int main(int argc, char **argv) +{ + size_t ninstances = 0, instancessize = 8; + struct cmdline_instance *instances = snewn( + instancessize, struct cmdline_instance); + struct cmdline_instance *ci = &instances[ninstances++]; + init_cmdline_instance(ci); if (argc <= 1) { /* @@ -584,13 +647,17 @@ int main(int argc, char **argv) show_version_and_exit(); } else if (longoptnoarg(arg, "--verbose") || !strcmp(arg, "-v")) { verbose = true; + } else if (longoptnoarg(arg, "--and")) { + sgrowarray(instances, instancessize, ninstances); + ci = &instances[ninstances++]; + init_cmdline_instance(ci); } else if (longoptarg(arg, "--listen", &val, &argc, &argv)) { if (val[0] == '/') { - listen_port = -1; - listen_socket = val; + ci->listen_port = -1; + ci->listen_socket = val; } else { - listen_port = atoi(val); - listen_socket = NULL; + ci->listen_port = atoi(val); + ci->listen_socket = NULL; } } else if (!strcmp(arg, "--listen-once")) { listen_once = true; @@ -622,24 +689,24 @@ int main(int argc, char **argv) sfree(uk->comment); sfree(uk); - for (int i = 0; i < nhostkeys; i++) - if (ssh_key_alg(hostkeys[i]) == ssh_key_alg(key)) { + for (int i = 0; i < ci->nhostkeys; i++) + if (ssh_key_alg(ci->hostkeys[i]) == ssh_key_alg(key)) { fprintf(stderr, "%s: host key '%s' duplicates key " "type %s\n", appname, val, ssh_key_alg(key)->ssh_id); exit(1); } - sgrowarray(hostkeys, hostkeysize, nhostkeys); - hostkeys[nhostkeys++] = key; + sgrowarray(ci->hostkeys, ci->hostkeysize, ci->nhostkeys); + ci->hostkeys[ci->nhostkeys++] = key; } else if (keytype == SSH_KEYTYPE_SSH1) { - if (hostkey1) { + if (ci->hostkey1) { fprintf(stderr, "%s: host key '%s' is a redundant " "SSH-1 host key\n", appname, val); exit(1); } - hostkey1 = snew(RSAKey); - if (!rsa1_load_f(keyfile, hostkey1, NULL, &error)) { + ci->hostkey1 = snew(RSAKey); + if (!rsa1_load_f(keyfile, ci->hostkey1, NULL, &error)) { fprintf(stderr, "%s: unable to load host key '%s': " "%s\n", appname, val, error); exit(1); @@ -665,19 +732,19 @@ int main(int argc, char **argv) exit(1); } - if (ssc.rsa_kex_key) { - freersakey(ssc.rsa_kex_key); + if (ci->ssc.rsa_kex_key) { + freersakey(ci->ssc.rsa_kex_key); } else { - ssc.rsa_kex_key = snew(RSAKey); + ci->ssc.rsa_kex_key = snew(RSAKey); } - if (!rsa1_load_f(keyfile, ssc.rsa_kex_key, NULL, &error)) { + if (!rsa1_load_f(keyfile, ci->ssc.rsa_kex_key, NULL, &error)) { fprintf(stderr, "%s: unable to load RSA kex key '%s': " "%s\n", appname, val, error); exit(1); } - ssc.rsa_kex_key->sshk.vt = &ssh_rsa; + ci->ssc.rsa_kex_key->sshk.vt = &ssh_rsa; } else if (longoptarg(arg, "--userkey", &val, &argc, &argv)) { Filename *keyfile; int keytype; @@ -704,8 +771,8 @@ int main(int argc, char **argv) memcpy(blob, sb->u, sb->len); node->public_blob = make_ptrlen(blob, sb->len); - node->next = aps.ssh2keys; - aps.ssh2keys = node; + node->next = ci->aps.ssh2keys; + ci->aps.ssh2keys = node; strbuf_free(sb); } else if (keytype == SSH_KEYTYPE_SSH1_PUBLIC) { @@ -724,8 +791,8 @@ int main(int argc, char **argv) BinarySource_BARE_INIT(src, sb->u, sb->len); get_rsa_ssh1_pub(src, &node->key, RSA_SSH1_EXPONENT_FIRST); - node->next = aps.ssh1keys; - aps.ssh1keys = node; + node->next = ci->aps.ssh1keys; + ci->aps.ssh1keys = node; strbuf_free(sb); } else { @@ -747,27 +814,27 @@ int main(int argc, char **argv) exit(1); } fclose(fp); - ssc.banner = ptrlen_from_strbuf(sb); + ci->ssc.banner = ptrlen_from_strbuf(sb); } else if (longoptarg(arg, "--bannertext", &val, &argc, &argv)) { - ssc.banner = ptrlen_from_asciz(val); + ci->ssc.banner = ptrlen_from_asciz(val); } else if (longoptarg(arg, "--sessiondir", &val, &argc, &argv)) { - ssc.session_starting_dir = val; + ci->ssc.session_starting_dir = val; } else if (longoptarg(arg, "--kexinit-kex", &val, &argc, &argv)) { - ssc.kex_override[KEXLIST_KEX] = ptrlen_from_asciz(val); + ci->ssc.kex_override[KEXLIST_KEX] = ptrlen_from_asciz(val); } else if (longoptarg(arg, "--kexinit-hostkey", &val, &argc, &argv)) { - ssc.kex_override[KEXLIST_HOSTKEY] = ptrlen_from_asciz(val); + ci->ssc.kex_override[KEXLIST_HOSTKEY] = ptrlen_from_asciz(val); } else if (longoptarg(arg, "--kexinit-cscipher", &val, &argc, &argv)) { - ssc.kex_override[KEXLIST_CSCIPHER] = ptrlen_from_asciz(val); + ci->ssc.kex_override[KEXLIST_CSCIPHER] = ptrlen_from_asciz(val); } else if (longoptarg(arg, "--kexinit-csmac", &val, &argc, &argv)) { - ssc.kex_override[KEXLIST_CSMAC] = ptrlen_from_asciz(val); + ci->ssc.kex_override[KEXLIST_CSMAC] = ptrlen_from_asciz(val); } else if (longoptarg(arg, "--kexinit-cscomp", &val, &argc, &argv)) { - ssc.kex_override[KEXLIST_CSCOMP] = ptrlen_from_asciz(val); + ci->ssc.kex_override[KEXLIST_CSCOMP] = ptrlen_from_asciz(val); } else if (longoptarg(arg, "--kexinit-sccipher", &val, &argc, &argv)) { - ssc.kex_override[KEXLIST_SCCIPHER] = ptrlen_from_asciz(val); + ci->ssc.kex_override[KEXLIST_SCCIPHER] = ptrlen_from_asciz(val); } else if (longoptarg(arg, "--kexinit-scmac", &val, &argc, &argv)) { - ssc.kex_override[KEXLIST_SCMAC] = ptrlen_from_asciz(val); + ci->ssc.kex_override[KEXLIST_SCMAC] = ptrlen_from_asciz(val); } else if (longoptarg(arg, "--kexinit-sccomp", &val, &argc, &argv)) { - ssc.kex_override[KEXLIST_SCCOMP] = ptrlen_from_asciz(val); + ci->ssc.kex_override[KEXLIST_SCCOMP] = ptrlen_from_asciz(val); } else if (longoptarg(arg, "--ssh1-ciphers", &val, &argc, &argv)) { ptrlen list = ptrlen_from_asciz(val); ptrlen word; @@ -786,45 +853,58 @@ int main(int argc, char **argv) appname, PTRLEN_PRINTF(word)); exit(1); } - ssc.ssh1_cipher_mask = mask; + ci->ssc.ssh1_cipher_mask = mask; } else if (longoptnoarg(arg, "--ssh1-no-compression")) { - ssc.ssh1_allow_compression = false; + ci->ssc.ssh1_allow_compression = false; } else if (longoptnoarg(arg, "--exitsignum")) { - ssc.exit_signal_numeric = true; + ci->ssc.exit_signal_numeric = true; } else if (longoptarg(arg, "--sshlog", &val, &argc, &argv) || longoptarg(arg, "-sshlog", &val, &argc, &argv)) { Filename *logfile = filename_from_str(val); - conf_set_filename(conf, CONF_logfilename, logfile); + conf_set_filename(ci->conf, CONF_logfilename, logfile); filename_free(logfile); - conf_set_int(conf, CONF_logtype, LGTYP_PACKETS); - conf_set_int(conf, CONF_logxfovr, LGXF_OVR); + conf_set_int(ci->conf, CONF_logtype, LGTYP_PACKETS); + conf_set_int(ci->conf, CONF_logxfovr, LGXF_OVR); } else if (longoptarg(arg, "--sshrawlog", &val, &argc, &argv) || longoptarg(arg, "-sshrawlog", &val, &argc, &argv)) { Filename *logfile = filename_from_str(val); - conf_set_filename(conf, CONF_logfilename, logfile); + conf_set_filename(ci->conf, CONF_logfilename, logfile); filename_free(logfile); - conf_set_int(conf, CONF_logtype, LGTYP_SSHRAW); - conf_set_int(conf, CONF_logxfovr, LGXF_OVR); + conf_set_int(ci->conf, CONF_logtype, LGTYP_SSHRAW); + conf_set_int(ci->conf, CONF_logxfovr, LGXF_OVR); } else if (!strcmp(arg, "--pretend-to-accept-any-pubkey")) { - ssc.stunt_pretend_to_accept_any_pubkey = true; + ci->ssc.stunt_pretend_to_accept_any_pubkey = true; } else if (!strcmp(arg, "--open-unconditional-agent-socket")) { - ssc.stunt_open_unconditional_agent_socket = true; + ci->ssc.stunt_open_unconditional_agent_socket = true; } else if (!strcmp(arg, "--allow-none-auth")) { - ssc.stunt_allow_none_auth = true; + ci->ssc.stunt_allow_none_auth = true; } else if (!strcmp(arg, "--allow-trivial-ki-auth")) { - ssc.stunt_allow_trivial_ki_auth = true; + ci->ssc.stunt_allow_trivial_ki_auth = true; } else if (!strcmp(arg, "--return-success-to-pubkey-offer")) { - ssc.stunt_return_success_to_pubkey_offer = true; + ci->ssc.stunt_return_success_to_pubkey_offer = true; } else { fprintf(stderr, "%s: unrecognised option '%s'\n", appname, arg); exit(1); } } - if (nhostkeys == 0 && !hostkey1) { - fprintf(stderr, "%s: specify at least one host key\n", appname); + if (ninstances > 1 && listen_once) { + fprintf(stderr, "%s: cannot listen once only with multiple server " + "instances\n", appname); exit(1); } + for (size_t i = 0; i < ninstances; i++) { + ci = &instances[i]; + if (ci->nhostkeys == 0 && !ci->hostkey1) { + fprintf(stderr, "%s: specify at least one host key\n", appname); + exit(1); + } + if (ninstances > 1 && !(ci->listen_port >= 0 || ci->listen_socket)) { + fprintf(stderr, "%s: cannot talk to stdio with multiple server " + "instances\n", appname); + exit(1); + } + } random_ref(); @@ -837,41 +917,8 @@ int main(int argc, char **argv) sk_init(); uxsel_init(); - struct server_config scfg; - scfg.conf = conf; - scfg.ssc = &ssc; - scfg.hostkeys = hostkeys; - scfg.nhostkeys = nhostkeys; - scfg.hostkey1 = hostkey1; - scfg.ap_shared = &aps; - scfg.next_id = 0; - - if (listen_port >= 0 || listen_socket) { - listening = true; - scfg.listening_plug.vt = &server_plugvt; - char *msg; - if (listen_port >= 0) { - scfg.listening_socket = sk_newlistener( - NULL, listen_port, &scfg.listening_plug, true, - ADDRTYPE_UNSPEC); - msg = dupprintf("%s: listening on port %d", - appname, listen_port); - } else { - SockAddr *addr = unix_sock_addr(listen_socket); - scfg.listening_socket = new_unix_listener( - addr, &scfg.listening_plug); - msg = dupprintf("%s: listening on Unix socket %s", - appname, listen_socket); - } - - log_to_stderr(-1, msg); - sfree(msg); - } else { - struct server_instance *inst; - Plug *plug = server_conn_plug(&scfg, &inst); - ssh_server_start(plug, make_fd_socket(0, 1, -1, NULL, 0, plug)); - log_to_stderr(inst->id, "speaking SSH on stdio"); - } + for (size_t i = 0; i < ninstances; i++) + cmdline_instance_start(&instances[i]); cli_main_loop(cliloop_no_pw_setup, cliloop_no_pw_check, cliloop_always_continue, NULL); -- cgit v1.2.3 From 44ee7b9e765f7af80d94610c82dbad46c5932ca0 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Tue, 28 Sep 2021 18:04:15 +0100 Subject: Add -pwfile option, a more secure version of -pw. Similarly to cmdgen's passphrase options, this replaces the password on the command line with a filename to read the password out of, which means it can't show up in 'ps' or the Windows task manager. --- cmdline.c | 26 ++++++++++++++++++++++++++ doc/index.but | 1 + doc/man-plink.but | 7 ++++++- doc/man-pscp.but | 7 ++++++- doc/man-psftp.but | 7 ++++++- doc/using.but | 24 +++++++++++++++--------- pscp.c | 2 +- psftp.c | 2 +- unix/plink.c | 2 +- windows/plink.c | 2 +- 10 files changed, 64 insertions(+), 16 deletions(-) diff --git a/cmdline.c b/cmdline.c index 62d65e19..18781469 100644 --- a/cmdline.c +++ b/cmdline.c @@ -584,6 +584,32 @@ int cmdline_process_param(const char *p, char *value, } } + if (!strcmp(p, "-pwfile")) { + 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 (conf_get_int(conf, CONF_protocol) != PROT_SSH) + cmdline_error("the -pwfile option can only be used with the " + "SSH protocol"); + else { + Filename *fn = filename_from_str(value); + FILE *fp = f_open(fn, "r", false); + if (!fp) { + cmdline_error("unable to open password file '%s'", value); + } else { + cmdline_password = chomp(fgetline(fp)); + if (!cmdline_password) { + cmdline_error("unable to read a password from file '%s'", + value); + } + fclose(fp); + } + filename_free(fn); + } + } + if (!strcmp(p, "-agent") || !strcmp(p, "-pagent") || !strcmp(p, "-pageant")) { RETURN(1); diff --git a/doc/index.but b/doc/index.but index f7e7145a..0629f81b 100644 --- a/doc/index.but +++ b/doc/index.but @@ -245,6 +245,7 @@ saved sessions from \IM{-m} \c{-m} command-line option \IM{-P-upper} \c{-P} command-line option \IM{-pw} \c{-pw} command-line option +\IM{-pwfile} \c{-pwfile} 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 diff --git a/doc/man-plink.but b/doc/man-plink.but index ea555abf..2a3b36c7 100644 --- a/doc/man-plink.but +++ b/doc/man-plink.but @@ -114,11 +114,16 @@ sequences. These options override Plink's default behaviour to enable or disabling such filtering on the standard error and standard output channels. +\dt \cw{-pwfile} \e{filename} + +\dd Open the specified file, and use the first line of text read from +it as the remote password. + \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}}). +commands such as \q{\c{ps}} or \q{\c{w}}). Use \cw{-pwfile} instead. \dt \cw{\-L} \cw{[}\e{srcaddr}\cw{:]}\e{srcport}\cw{:}\e{desthost}\cw{:}\e{destport} diff --git a/doc/man-pscp.but b/doc/man-pscp.but index 402b9eef..544d3a40 100644 --- a/doc/man-pscp.but +++ b/doc/man-pscp.but @@ -101,11 +101,16 @@ channel from the server, to prevent remote processes sending confusing escape sequences. This option forces the standard error channel to not be filtered. +\dt \cw{-pwfile} \e{filename} + +\dd Open the specified file, and use the first line of text read from +it as the remote password. + \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}}). +commands such as \q{\c{ps}} or \q{\c{w}}). Use \cw{-pwfile} instead. \dt \cw{-1} diff --git a/doc/man-psftp.but b/doc/man-psftp.but index 4e92e48b..e0b48602 100644 --- a/doc/man-psftp.but +++ b/doc/man-psftp.but @@ -89,11 +89,16 @@ channel from the server, to prevent remote processes sending confusing escape sequences. This option forces the standard error channel to not be filtered. +\dt \cw{-pwfile} \e{filename} + +\dd Open the specified file, and use the first line of text read from +it as the remote password. + \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}}). +commands such as \q{\c{ps}} or \q{\c{w}}). Use \cw{-pwfile} instead. \dt \cw{-1} diff --git a/doc/using.but b/doc/using.but index 02a67808..8811b5fa 100644 --- a/doc/using.but +++ b/doc/using.but @@ -838,17 +838,23 @@ 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} +\S2{using-cmdline-pw} \i\c{-pwfile} and \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, Rlogin, and -SUPDUP, these protocols do not support automated password -authentication. +on the command line. + +The \c{-pwfile} option takes a file name as an argument. The first +line of text in that file will be used as your password. + +The \c{-pw} option takes the password itself as an argument. This is +\s{NOT SECURE} if anybody else uses the same computer, because the +whole command line (including the password) is likely to show up if +another user lists the running processes. \c{-pw} is retained for +backwards compatibility only; you should use \c{-pwfile} instead. + +Note that these options only work when you are using the SSH protocol. +Due to fundamental limitations of Telnet, Rlogin, and SUPDUP, 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 diff --git a/pscp.c b/pscp.c index 07546dd4..4e5d7cbf 100644 --- a/pscp.c +++ b/pscp.c @@ -2201,7 +2201,7 @@ static void usage(void) 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(" -pwfile file login with password read from specified file\n"); printf(" -1 -2 force use of particular SSH protocol version\n"); printf(" -ssh -ssh-connection\n"); printf(" force use of particular SSH protocol variant\n"); diff --git a/psftp.c b/psftp.c index 993a3e35..4dc60dc1 100644 --- a/psftp.c +++ b/psftp.c @@ -2535,7 +2535,7 @@ static void usage(void) 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(" -pwfile file login with password read from specified file\n"); printf(" -1 -2 force use of particular SSH protocol version\n"); printf(" -ssh -ssh-connection\n"); printf(" force use of particular SSH protocol variant\n"); diff --git a/unix/plink.c b/unix/plink.c index 74e772b4..69667a3d 100644 --- a/unix/plink.c +++ b/unix/plink.c @@ -516,7 +516,7 @@ static void usage(void) printf(" -sercfg configuration-string (e.g. 19200,8,n,1,X)\n"); printf(" Specify the serial configuration (serial only)\n"); printf("The following options only apply to SSH connections:\n"); - printf(" -pw passw login with specified password\n"); + printf(" -pwfile file login with password read from specified file\n"); printf(" -D [listen-IP:]listen-port\n"); printf(" Dynamic SOCKS-based port forwarding\n"); printf(" -L [listen-IP:]listen-port:host:port\n"); diff --git a/windows/plink.c b/windows/plink.c index 1c1872dc..8d3a75cf 100644 --- a/windows/plink.c +++ b/windows/plink.c @@ -138,7 +138,7 @@ static void usage(void) printf(" -sercfg configuration-string (e.g. 19200,8,n,1,X)\n"); printf(" Specify the serial configuration (serial only)\n"); printf("The following options only apply to SSH connections:\n"); - printf(" -pw passw login with specified password\n"); + printf(" -pwfile file login with password read from specified file\n"); printf(" -D [listen-IP:]listen-port\n"); printf(" Dynamic SOCKS-based port forwarding\n"); printf(" -L [listen-IP:]listen-port:host:port\n"); -- cgit v1.2.3 From a73aaf945728a15159a274193c6505a04111d2f8 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Tue, 28 Sep 2021 18:08:58 +0100 Subject: Uppity: add command-line options to configure auth methods. Now you can turn various authentication methods on and off, so that the server won't even offer (say) k-i or publickey at all. This subsumes the previous -allow-none-auth option; there's now a general -{allow,deny}-auth=foo option schema, so -allow-auth=none is the new spelling of -allow-none-auth. The former spelling is kept for backwards compatibility, just in case. --- ssh/server.h | 1 - unix/uppity.c | 55 +++++++++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 47 insertions(+), 9 deletions(-) diff --git a/ssh/server.h b/ssh/server.h index 7a4f9e78..e93fd299 100644 --- a/ssh/server.h +++ b/ssh/server.h @@ -21,7 +21,6 @@ struct SshServerConfig { bool stunt_pretend_to_accept_any_pubkey; bool stunt_open_unconditional_agent_socket; - bool stunt_allow_none_auth; bool stunt_allow_trivial_ki_auth; bool stunt_return_success_to_pubkey_offer; }; diff --git a/unix/uppity.c b/unix/uppity.c index 4f11b0ec..5d0a1b1c 100644 --- a/unix/uppity.c +++ b/unix/uppity.c @@ -140,6 +140,8 @@ struct server_config { RSAKey *hostkey1; + unsigned auth_methods; + struct AuthPolicyShared *ap_shared; Socket *listening_socket; @@ -199,18 +201,13 @@ unsigned auth_methods(AuthPolicy *ap) { struct server_instance *inst = container_of( ap, struct server_instance, ap); - unsigned methods = (AUTHMETHOD_PUBLICKEY | AUTHMETHOD_PASSWORD | - AUTHMETHOD_KBDINT | AUTHMETHOD_TIS | - AUTHMETHOD_CRYPTOCARD); - if (inst->cfg->ssc->stunt_allow_none_auth) - methods |= AUTHMETHOD_NONE; - return methods; + return inst->cfg->auth_methods; } bool auth_none(AuthPolicy *ap, ptrlen username) { struct server_instance *inst = container_of( ap, struct server_instance, ap); - if (inst->cfg->ssc->stunt_allow_none_auth) + if (inst->cfg->auth_methods & AUTHMETHOD_NONE) return true; return false; } @@ -543,12 +540,32 @@ static const PlugVtable server_plugvt = { .accepting = server_accepting, }; +static unsigned auth_method_from_name(const char *name) +{ + if (!strcmp(name, "none")) + return AUTHMETHOD_NONE; + if (!strcmp(name, "tis")) + return AUTHMETHOD_TIS; + if (!strcmp(name, "cryptocard") || !strcmp(name, "ccard")) + return AUTHMETHOD_CRYPTOCARD; + if (!strcmp(name, "keyboard-interactive") || !strcmp(name, "k-i") || + !strcmp(name, "kbdint") || !strcmp(name, "ki")) + return AUTHMETHOD_KBDINT; + if (!strcmp(name, "publickey") || !strcmp(name, "pubkey") || + !strcmp(name, "pk")) + return AUTHMETHOD_PUBLICKEY; + if (!strcmp(name, "password") || !strcmp(name, "pw")) + return AUTHMETHOD_PASSWORD; + return 0; +} + struct cmdline_instance { int listen_port; const char *listen_socket; ssh_key **hostkeys; size_t nhostkeys, hostkeysize; RSAKey *hostkey1; + unsigned auth_methods; struct AuthPolicyShared aps; SshServerConfig ssc; Conf *conf; @@ -574,6 +591,10 @@ static void init_cmdline_instance(struct cmdline_instance *ci) ci->ssc.session_starting_dir = getenv("HOME"); ci->ssc.ssh1_cipher_mask = SSH1_SUPPORTED_CIPHER_MASK; ci->ssc.ssh1_allow_compression = true; + + ci->auth_methods = (AUTHMETHOD_PUBLICKEY | AUTHMETHOD_PASSWORD | + AUTHMETHOD_KBDINT | AUTHMETHOD_TIS | + AUTHMETHOD_CRYPTOCARD); } static void cmdline_instance_start(struct cmdline_instance *ci) @@ -588,6 +609,7 @@ static void cmdline_instance_start(struct cmdline_instance *ci) scfg->nhostkeys = ci->nhostkeys; scfg->hostkey1 = ci->hostkey1; scfg->ap_shared = &ci->aps; + scfg->auth_methods = ci->auth_methods; if (ci->listen_port >= 0 || ci->listen_socket) { listening = true; @@ -835,6 +857,22 @@ int main(int argc, char **argv) ci->ssc.kex_override[KEXLIST_SCMAC] = ptrlen_from_asciz(val); } else if (longoptarg(arg, "--kexinit-sccomp", &val, &argc, &argv)) { ci->ssc.kex_override[KEXLIST_SCCOMP] = ptrlen_from_asciz(val); + } else if (longoptarg(arg, "--allow-auth", &val, &argc, &argv)) { + unsigned method = auth_method_from_name(val); + if (!method) { + fprintf(stderr, "%s: unrecognised auth method '%s'\n", + appname, val); + exit(1); + } + ci->auth_methods |= method; + } else if (longoptarg(arg, "--deny-auth", &val, &argc, &argv)) { + unsigned method = auth_method_from_name(val); + if (!method) { + fprintf(stderr, "%s: unrecognised auth method '%s'\n", + appname, val); + exit(1); + } + ci->auth_methods &= ~method; } else if (longoptarg(arg, "--ssh1-ciphers", &val, &argc, &argv)) { ptrlen list = ptrlen_from_asciz(val); ptrlen word; @@ -877,7 +915,8 @@ int main(int argc, char **argv) } else if (!strcmp(arg, "--open-unconditional-agent-socket")) { ci->ssc.stunt_open_unconditional_agent_socket = true; } else if (!strcmp(arg, "--allow-none-auth")) { - ci->ssc.stunt_allow_none_auth = true; + /* backwards-compatibility synonym for --allow-auth=none */ + ci->auth_methods |= AUTHMETHOD_NONE; } else if (!strcmp(arg, "--allow-trivial-ki-auth")) { ci->ssc.stunt_allow_trivial_ki_auth = true; } else if (!strcmp(arg, "--return-success-to-pubkey-offer")) { -- cgit v1.2.3 From 1541974564a65b6edbf09447e8cf54355fe10805 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 30 Sep 2021 18:33:16 +0100 Subject: Windows dputs: use WriteFile to avoid stdio buffering. Trying to debug a problem involving threads just now, it turned out that the version of the diagnostics going to my debug.log was getting data in a different order from the version going to the debug console. Now I open and write to debug_fp by going directly to the Win32 API instead of via a buffering userland stdio, and that seems to have solved the problem. --- windows/utils/dputs.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/windows/utils/dputs.c b/windows/utils/dputs.c index 15b0e4db..f582509a 100644 --- a/windows/utils/dputs.c +++ b/windows/utils/dputs.c @@ -11,7 +11,7 @@ #include "putty.h" #include "utils/utils.h" -static FILE *debug_fp = NULL; +static HANDLE debug_fp = INVALID_HANDLE_VALUE; static HANDLE debug_hdl = INVALID_HANDLE_VALUE; static int debug_got_console = 0; @@ -25,13 +25,15 @@ void dputs(const char *buf) debug_hdl = GetStdHandle(STD_OUTPUT_HANDLE); } } - if (!debug_fp) { - debug_fp = fopen("debug.log", "w"); + if (debug_fp == INVALID_HANDLE_VALUE) { + debug_fp = CreateFile("debug.log", GENERIC_WRITE, FILE_SHARE_READ, + NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); } + if (debug_fp != INVALID_HANDLE_VALUE) { + WriteFile(debug_fp, buf, strlen(buf), &dw, NULL); + } if (debug_hdl != INVALID_HANDLE_VALUE) { WriteFile(debug_hdl, buf, strlen(buf), &dw, NULL); } - fputs(buf, debug_fp); - fflush(debug_fp); } -- cgit v1.2.3 From dde659004083b575547f2740aaa56fdf64523770 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 30 Sep 2021 19:16:20 +0100 Subject: handle_write_eof: delegate CloseHandle back to the client. When a writable HANDLE is managed by the handle-io.c system, you ask to send EOF on the handle by calling handle_write_eof. That waits until all buffered data has been written, and then sends an EOF event by simply closing the handle. That is, of course, the only way to send an EOF signal on a handle at all. And yet, it's a bug, because the handle_output system does not take ownership of the handle you give it: the client of handle_output retains ownership, keeps its own copy of the handle, and will expect to close it itself. In most cases, the extra close will harmlessly fail, and return ERROR_INVALID_HANDLE (which the caller didn't notice anyway). But if you're unlucky, in conditions of frantic handle opening and closing (e.g. with a lot of separate named-pipe-style agent forwarding connections being constantly set up and torn down), the handle value might have been reused between the two closes, so that the second CloseHandle closes an unrelated handle belonging to some other part of the program. We can't fix this by giving handle_output permanent ownership of the handle, because it really _is_ necessary for copies of it to survive elsewhere: in particular, for a bidirectional file such as a serial port or named pipe, the reading side also needs a copy of the same handle! And yet, we can't replace the handle_write_eof call in the client with a direct CloseHandle, because that won't wait until buffered output has been drained. The solution is that the client still calls handle_write_eof to register that it _wants_ an EOF sent; the handle_output system will wait until it's ready, but then, instead of calling CloseHandle, it will ask its _client_ to close the handle, by calling the provided 'sentdata' callback with the new 'close' flag set to true. And then the client can not only close the handle, but do whatever else it needs to do to record that that has been done. --- windows/conpty.c | 3 ++- windows/handle-io.c | 8 +++++--- windows/handle-socket.c | 16 +++++++++++++--- windows/platform.h | 2 +- windows/plink.c | 8 +++++++- windows/serial.c | 3 ++- 6 files changed, 30 insertions(+), 10 deletions(-) diff --git a/windows/conpty.c b/windows/conpty.c index f2882316..322af7bf 100644 --- a/windows/conpty.c +++ b/windows/conpty.c @@ -120,7 +120,8 @@ static size_t conpty_gotdata( } } -static void conpty_sentdata(struct handle *h, size_t new_backlog, int err) +static void conpty_sentdata(struct handle *h, size_t new_backlog, int err, + bool close) { ConPTY *conpty = (ConPTY *)handle_get_privdata(h); if (err) { diff --git a/windows/handle-io.c b/windows/handle-io.c index 873eaf30..e012c8ed 100644 --- a/windows/handle-io.c +++ b/windows/handle-io.c @@ -284,6 +284,7 @@ struct handle_output { * drops. */ handle_outputfn_t sentdata; + struct handle *sentdata_param; }; static DWORD WINAPI handle_output_threadfunc(void *param) @@ -361,7 +362,7 @@ static void handle_try_output(struct handle_output *ctx) ctx->busy = true; } else if (!ctx->busy && bufchain_size(&ctx->queued_data) == 0 && ctx->outgoingeof == EOF_PENDING) { - CloseHandle(ctx->h); + ctx->sentdata(ctx->sentdata_param, 0, 0, true); ctx->h = INVALID_HANDLE_VALUE; ctx->outgoingeof = EOF_SENT; } @@ -507,6 +508,7 @@ struct handle *handle_output_new(HANDLE handle, handle_outputfn_t sentdata, bufchain_init(&h->u.o.queued_data); h->u.o.outgoingeof = EOF_NO; h->u.o.sentdata = sentdata; + h->u.o.sentdata_param = h; h->u.o.flags = flags; ensure_ready_event_setup(); @@ -644,11 +646,11 @@ static void handle_ready(struct handle *h) * thread is terminating by now). */ h->u.o.defunct = true; - h->u.o.sentdata(h, 0, h->u.o.writeerr); + h->u.o.sentdata(h, 0, h->u.o.writeerr, false); } else { bufchain_consume(&h->u.o.queued_data, h->u.o.lenwritten); noise_ultralight(NOISE_SOURCE_IOLEN, h->u.o.lenwritten); - h->u.o.sentdata(h, bufchain_size(&h->u.o.queued_data), 0); + h->u.o.sentdata(h, bufchain_size(&h->u.o.queued_data), 0, false); handle_try_output(&h->u.o); } break; diff --git a/windows/handle-socket.c b/windows/handle-socket.c index dbc4e0de..a024807e 100644 --- a/windows/handle-socket.c +++ b/windows/handle-socket.c @@ -93,10 +93,19 @@ static size_t handle_stderr( return 0; } -static void handle_sentdata(struct handle *h, size_t new_backlog, int err) +static void handle_sentdata(struct handle *h, size_t new_backlog, int err, + bool close) { HandleSocket *hs = (HandleSocket *)handle_get_privdata(h); + if (close) { + if (hs->send_H != INVALID_HANDLE_VALUE) + CloseHandle(hs->send_H); + if (hs->recv_H != INVALID_HANDLE_VALUE && hs->recv_H != hs->send_H) + CloseHandle(hs->recv_H); + hs->send_H = hs->recv_H = INVALID_HANDLE_VALUE; + } + if (err) { plug_closing(hs->plug, win_strerror(err), err, 0); return; @@ -125,8 +134,9 @@ static void sk_handle_close(Socket *s) handle_free(hs->send_h); handle_free(hs->recv_h); - CloseHandle(hs->send_H); - if (hs->recv_H != hs->send_H) + if (hs->send_H != INVALID_HANDLE_VALUE) + CloseHandle(hs->send_H); + if (hs->recv_H != INVALID_HANDLE_VALUE && hs->recv_H != hs->send_H) CloseHandle(hs->recv_H); bufchain_clear(&hs->inputdata); diff --git a/windows/platform.h b/windows/platform.h index ba07ea1a..65dacdc1 100644 --- a/windows/platform.h +++ b/windows/platform.h @@ -615,7 +615,7 @@ struct handle; typedef size_t (*handle_inputfn_t)( struct handle *h, const void *data, size_t len, int err); typedef void (*handle_outputfn_t)( - struct handle *h, size_t new_backlog, int err); + struct handle *h, size_t new_backlog, int err, bool close); 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, diff --git a/windows/plink.c b/windows/plink.c index 8d3a75cf..503ae30b 100644 --- a/windows/plink.c +++ b/windows/plink.c @@ -216,8 +216,14 @@ size_t stdin_gotdata(struct handle *h, const void *data, size_t len, int err) return 0; } -void stdouterr_sent(struct handle *h, size_t new_backlog, int err) +void stdouterr_sent(struct handle *h, size_t new_backlog, int err, bool close) { + if (close) { + CloseHandle(outhandle); + CloseHandle(errhandle); + outhandle = errhandle = INVALID_HANDLE_VALUE; + } + if (err) { char buf[4096]; FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, err, 0, diff --git a/windows/serial.c b/windows/serial.c index 470c979f..d966145c 100644 --- a/windows/serial.c +++ b/windows/serial.c @@ -73,7 +73,8 @@ static size_t serial_gotdata( } } -static void serial_sentdata(struct handle *h, size_t new_backlog, int err) +static void serial_sentdata(struct handle *h, size_t new_backlog, int err, + bool close) { Serial *serial = (Serial *)handle_get_privdata(h); if (err) { -- cgit v1.2.3 From 6dfe941a7309a5af2088061ab0a745555f31b879 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 30 Sep 2021 18:31:39 +0100 Subject: Windows Pageant: fix hang due to queued callbacks. In the Windows Pageant message loop, we were alternating between MsgWaitForMultipleObjects with timeout=INFINITE, and run_toplevel_callbacks. But run_toplevel_callbacks doesn't loop until the callback queue is empty: it just runs at least one of the currently queued callbacks, so that some progress was made. So if two or more callbacks were queued, we'd leave the rest in the queue and go back into MsgWaitForMultipleObjects, which could hang indefinitely. (A very silly workaround was available: move the mouse over the Pageant systray icon! Every mouse event would terminate the wait and let you get one more iteration of run_toplevel_callbacks.) Now we do the same thing as in other main loops: if any further callbacks are pending, then we still run MsgWaitForMultipleObjects, but we do it with timeout=0 instead of timeout=INFINITE, so that we won't go back to a _blocking_ sleep until all callbacks have been serviced. --- windows/pageant.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/windows/pageant.c b/windows/pageant.c index c1bc55cc..0e25cc5d 100644 --- a/windows/pageant.c +++ b/windows/pageant.c @@ -1648,8 +1648,9 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) HandleWaitList *hwl = get_handle_wait_list(); + DWORD timeout = toplevel_callback_pending() ? 0 : INFINITE; n = MsgWaitForMultipleObjects(hwl->nhandles, hwl->handles, false, - INFINITE, QS_ALLINPUT); + timeout, QS_ALLINPUT); if ((unsigned)(n - WAIT_OBJECT_0) < (unsigned)hwl->nhandles) handle_wait_activate(hwl, n - WAIT_OBJECT_0); -- cgit v1.2.3 From e7dd2421cfaf6dd75b316c011e4b10843d2007a6 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 30 Sep 2021 18:56:26 +0100 Subject: Pageant: actually link requests on to their queues. When I create any class that implements PageantAsyncOp, I had intended to link its pao.cr list node on to the list in the appropriate PageantClientInfo, so that if that PageantClientInfo was destroyed prematurely (e.g. an agent connection was abruptly closed), we could destroy all the pending requests containing a pointer to it. I did this linking by setting the linked-list fields in pao.cr to point to the appropriate places in the existing list - but I didn't modify the pointer fields _in_ the existing list to point to the new operation at all. So nothing ever actually got linked on to any of those queues! --- pageant.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pageant.c b/pageant.c index fb0f86e1..89256b53 100644 --- a/pageant.c +++ b/pageant.c @@ -836,6 +836,7 @@ static PageantAsyncOp *pageant_make_op( so->pao.info = pc->info; so->pao.cr.prev = pc->info->head.prev; so->pao.cr.next = &pc->info->head; + so->pao.cr.prev->next = so->pao.cr.next->prev = &so->pao.cr; so->pao.reqid = reqid; so->pk = pk; so->pkr.prev = so->pkr.next = NULL; @@ -1353,6 +1354,7 @@ static PageantAsyncOp *pageant_make_op( io->pao.info = pc->info; io->pao.cr.prev = pc->info->head.prev; io->pao.cr.next = &pc->info->head; + io->pao.cr.prev->next = io->pao.cr.next->prev = &io->pao.cr; io->pao.reqid = reqid; io->response = sb; io->crLine = 0; -- cgit v1.2.3 From 0377c689f24a704814ae180264c3cf075548032c Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 10 Oct 2021 14:30:37 +0100 Subject: Start a 'terminal' source subdirectory. This contains terminal.c, bidi.c (formerly minibidi.c), and terminal.h. I'm about to make a couple more bidi-related source files, so it seems worth starting by making a place to put them that won't be cluttering up the top level. --- CMakeLists.txt | 5 +- minibidi.c | 2025 -------------- putty.h | 2 +- terminal.c | 7699 --------------------------------------------------- terminal.h | 529 ---- terminal/bidi.c | 2025 ++++++++++++++ terminal/terminal.c | 7699 +++++++++++++++++++++++++++++++++++++++++++++++++++ terminal/terminal.h | 529 ++++ 8 files changed, 10258 insertions(+), 10255 deletions(-) delete mode 100644 minibidi.c delete mode 100644 terminal.c delete mode 100644 terminal.h create mode 100644 terminal/bidi.c create mode 100644 terminal/terminal.c create mode 100644 terminal/terminal.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 213f81af..18c76aa4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,6 +9,8 @@ add_subdirectory(doc) add_compile_definitions(HAVE_CMAKE_H) +include_directories(terminal) + add_library(utils STATIC ${GENERATED_COMMIT_C}) add_dependencies(utils cmake_commit_c) @@ -41,7 +43,8 @@ add_library(agent STATIC sshpubk.c pageant.c aqsync.c) add_library(guiterminal STATIC - terminal.c ldisc.c minibidi.c config.c dialog.c + terminal/terminal.c terminal/bidi.c + ldisc.c config.c dialog.c $) add_library(noterminal STATIC diff --git a/minibidi.c b/minibidi.c deleted file mode 100644 index 05d15b3d..00000000 --- a/minibidi.c +++ /dev/null @@ -1,2025 +0,0 @@ -/************************************************************************ - * - * ------------ - * Description: - * ------------ - * This is an implementation of Unicode's Bidirectional Algorithm - * (known as UAX #9). - * - * http://www.unicode.org/reports/tr9/ - * - * Author: Ahmad Khalifa - * - * (www.arabeyes.org - under MIT license) - * - ************************************************************************/ - -/* - * TODO: - * ===== - * - Explicit marks need to be handled (they are not 100% now) - * - Ligatures - */ - -#include /* definition of wchar_t*/ - -#include "putty.h" -#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 ) - -/* function declarations */ -static void flipThisRun( - bidi_char *from, unsigned char *level, int max, int count); -static int findIndexOfRun( - unsigned char *level, int start, int count, int tlevel); -static unsigned char getType(int ch); -static unsigned char setOverrideBits( - unsigned char level, unsigned char override); -static int getPreviousLevel(unsigned char *level, int from); -static void doMirror(unsigned int *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, doesn't 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) - -static 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 - */ -static 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 j; k--, j++) { - temp = from[k]; - from[k] = from[j]; - from[j] = temp; - } - } -} - -/* - * Finds the index of a run with level equals tlevel - */ -static int findIndexOfRun( - unsigned char *level , int start, int count, int tlevel) -{ - int i; - for (i=start; 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.) - */ -bool 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< 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; - bool ligFlag = false; - - for (i=0; i 0) switch (line[i-1].wc) { - case 0x622: - ligFlag = true; - if ((tempShape == SL) || (tempShape == SD) || (tempShape == SC)) - to[i].wc = 0xFEF6; - else - to[i].wc = 0xFEF5; - break; - case 0x623: - ligFlag = true; - if ((tempShape == SL) || (tempShape == SD) || (tempShape == SC)) - to[i].wc = 0xFEF8; - else - to[i].wc = 0xFEF7; - break; - case 0x625: - ligFlag = true; - if ((tempShape == SL) || (tempShape == SD) || (tempShape == SC)) - to[i].wc = 0xFEFA; - else - to[i].wc = 0xFEF9; - break; - case 0x627: - ligFlag = true; - 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 = false; - 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; - bool yes, bover; - - /* Check the presence of R or AL types as optimization */ - yes = false; - for (i=0; i= 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 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-1 && 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= 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= 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; i0 && (getType(line[j].wc) == WS)) { - j--; - } - if (j < (count-1)) { - 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 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. - */ -static void doMirror(unsigned int *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 -#include - -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", (unsigned)chr, typetoname[type].name); - } - - return 0; -} - -#endif diff --git a/putty.h b/putty.h index caca09f3..334b588b 100644 --- a/putty.h +++ b/putty.h @@ -2352,7 +2352,7 @@ void setup_config_box(struct controlbox *b, bool midsession, int protocol, int protcfginfo); /* - * Exports from minibidi.c. + * Exports from bidi.c. */ #define BIDI_CHAR_INDEX_NONE ((unsigned short)-1) typedef struct bidi_char { diff --git a/terminal.c b/terminal.c deleted file mode 100644 index ddeb1e94..00000000 --- a/terminal.c +++ /dev/null @@ -1,7699 +0,0 @@ -/* - * Terminal emulator. - */ - -#include -#include -#include -#include -#include - -#include -#include -#include "putty.h" -#include "terminal.h" - -#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 ) - -static const char *const EMPTY_WINDOW_TITLE = ""; - -static const char sco2ansicolour[] = { 0, 4, 2, 6, 1, 5, 3, 7 }; - -#define sel_nl_sz (sizeof(sel_nl)/sizeof(wchar_t)) -static 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 check_line_size(Terminal *, termline *); -static void do_paint(Terminal *); -static void erase_lots(Terminal *, bool, bool, bool); -static int find_last_nonempty_line(Terminal *, tree234 *); -static void swap_screen(Terminal *, int, bool, bool); -static void update_sbar(Terminal *); -static void deselect(Terminal *); -static void term_print_finish(Terminal *); -static void scroll(Terminal *, int, int, int, bool); -static void parse_optionalrgb(optionalrgb *out, unsigned *values); -static void term_added_data(Terminal *term); -static void term_update_raw_mouse_mode(Terminal *term); - -static termline *newtermline(Terminal *term, int cols, bool 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->trusted = false; - line->temporary = false; - line->cc_free = 0; - - return line; -} - -static void freetermline(termline *line) -{ - if (line) { - sfree(line->chars); - sfree(line); - } -} - -static void unlineptr(termline *line) -{ - if (line->temporary) - freetermline(line); -} - -const int colour_indices_conf_to_oscp[CONF_NCOLOURS] = { - #define COLOUR_ENTRY(id,name) OSCP_COLOUR_##id, - CONF_COLOUR_LIST(COLOUR_ENTRY) - #undef COLOUR_ENTRY -}; - -const int colour_indices_conf_to_osc4[CONF_NCOLOURS] = { - #define COLOUR_ENTRY(id,name) OSC4_COLOUR_##id, - CONF_COLOUR_LIST(COLOUR_ENTRY) - #undef COLOUR_ENTRY -}; - -const int colour_indices_oscp_to_osc4[OSCP_NCOLOURS] = { - #define COLOUR_ENTRY(id) OSC4_COLOUR_##id, - OSCP_COLOUR_LIST(COLOUR_ENTRY) - #undef COLOUR_ENTRY -}; - -#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 - -static void clear_cc(termline *line, int col); - -/* - * 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); - - /* - * Don't add combining characters at all to U+FFFD REPLACEMENT - * CHARACTER. (Partly it's a slightly incoherent idea in the first - * place; mostly, U+FFFD is what we generate if a cell already has - * too many ccs, in which case we want it to be a fixed point when - * further ccs are added.) - */ - if (line->chars[col].chr == 0xFFFD) - return; - - /* - * Walk the cc list of the cell in question to find its current - * end point. - */ - size_t ncc = 0; - int origcol = col; - while (line->chars[col].cc_next) { - col += line->chars[col].cc_next; - if (++ncc >= CC_LIMIT) { - /* - * There are already too many combining characters in this - * character cell. Change strategy: throw out the entire - * chain and replace the main character with U+FFFD. - * - * (Rationale: extrapolating from UTR #36 section 3.6.2 - * suggests the principle that it's better to substitute - * U+FFFD than to _ignore_ input completely. Also, if the - * user copies and pastes an overcombined character cell, - * this way it will clearly indicate that we haven't - * reproduced the writer's original intentions, instead of - * looking as if it was the _writer's_ fault that the 33rd - * cc is missing.) - * - * Per the code above, this will also prevent any further - * ccs from being added to this cell. - */ - clear_cc(line, origcol); - line->chars[origcol].chr = 0xFFFD; - return; - } - } - - /* - * Extend the cols array if the free list is empty. - */ - if (!line->cc_free) { - int n = line->size; - - size_t tmpsize = line->size; - sgrowarray(line->chars, tmpsize, tmpsize); - assert(tmpsize <= INT_MAX); - line->size = tmpsize; - - 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++; - } - } - - /* - * `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 bool termchars_equal_override(termchar *a, termchar *b, - unsigned long bchr, unsigned long battr) -{ - /* FULL-TERMCHAR */ - if (!truecolour_equal(a->truecolour, b->truecolour)) - return false; - 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 bool 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.) - */ -static void makerle(strbuf *b, termline *ldata, - void (*makeliteral)(strbuf *b, termchar *c, - unsigned long *state)) -{ - int hdrpos, hdrsize, n, prevlen, prevpos, thislen, thispos; - bool prev2; - termchar *c = ldata->chars; - unsigned long state = 0, oldstate; - - n = ldata->cols; - - hdrpos = b->len; - hdrsize = 0; - put_byte(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->u + prevpos, b->u + 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; - strbuf_shrink_to(b, prevpos+prevlen); - } else { - memmove(b->u + prevpos+1, b->u + prevpos, prevlen); - runpos = prevpos; - strbuf_shrink_to(b, prevpos+prevlen+1); - /* - * Terminate the previous run of ordinary - * literals. - */ - assert(hdrsize >= 1 && hdrsize <= 128); - b->u[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; - bool match = tmplen == thislen && - !memcmp(b->u + runpos+1, b->u + tmppos, tmplen); - strbuf_shrink_to(b, tmppos); - if (!match) { - state = oldstate; - break; /* run over */ - } - n--, c++, runlen++; - } - - assert(runlen >= 2 && runlen <= 129); - b->u[runpos] = runlen + 0x80 - 2; - - hdrpos = b->len; - hdrsize = 0; - put_byte(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->u[hdrpos] = hdrsize - 1; - hdrpos = b->len; - hdrsize = 0; - put_byte(b, 0); - prevlen = prevpos = 0; - prev2 = false; - } - } - - /* - * Clean up. - */ - if (hdrsize > 0) { - assert(hdrsize <= 128); - b->u[hdrpos] = hdrsize - 1; - } else { - strbuf_shrink_to(b, hdrpos); - } -} -static void makeliteral_chr(strbuf *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) { - put_byte(b, (unsigned char)(c->chr & 0x7F)); - } else if (c->chr < 0x4000) { - put_byte(b, (unsigned char)(((c->chr >> 8) & 0x3F) | 0x80)); - put_byte(b, (unsigned char)(c->chr & 0xFF)); - } else if (c->chr < 0x200000) { - put_byte(b, (unsigned char)(((c->chr >> 16) & 0x1F) | 0xC0)); - put_uint16(b, c->chr & 0xFFFF); - } else if (c->chr < 0x10000000) { - put_byte(b, (unsigned char)(((c->chr >> 24) & 0x0F) | 0xE0)); - put_byte(b, (unsigned char)((c->chr >> 16) & 0xFF)); - put_uint16(b, c->chr & 0xFFFF); - } else { - put_byte(b, 0xF0); - put_uint32(b, c->chr); - } - *state = c->chr & ~0xFF; -} -static void makeliteral_attr(strbuf *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) { - put_byte(b, (unsigned char)((attr >> 8) & 0xFF)); - put_byte(b, (unsigned char)(attr & 0xFF)); - } else { - put_byte(b, (unsigned char)(((attr >> 24) & 0x7F) | 0x80)); - put_byte(b, (unsigned char)((attr >> 16) & 0xFF)); - put_byte(b, (unsigned char)((attr >> 8) & 0xFF)); - put_byte(b, (unsigned char)(attr & 0xFF)); - } -} -static void makeliteral_truecolour(strbuf *b, termchar *c, unsigned long *state) -{ - /* - * Put the used parts of the colour info into the buffer. - */ - put_byte(b, ((c->truecolour.fg.enabled ? 1 : 0) | - (c->truecolour.bg.enabled ? 2 : 0))); - if (c->truecolour.fg.enabled) { - put_byte(b, c->truecolour.fg.r); - put_byte(b, c->truecolour.fg.g); - put_byte(b, c->truecolour.fg.b); - } - if (c->truecolour.bg.enabled) { - put_byte(b, c->truecolour.bg.r); - put_byte(b, c->truecolour.bg.g); - put_byte(b, c->truecolour.bg.b); - } -} -static void makeliteral_cc(strbuf *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); -} - -typedef struct compressed_scrollback_line { - size_t len; -} compressed_scrollback_line; - -static termline *decompressline(compressed_scrollback_line *line); - -static compressed_scrollback_line *compressline(termline *ldata) -{ - strbuf *b = strbuf_new(); - - /* Leave space for the header structure */ - strbuf_append(b, sizeof(compressed_scrollback_line)); - - /* - * 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) { - put_byte(b, (unsigned char)((n & 0x7F) | 0x80)); - n >>= 7; - } - put_byte(b, (unsigned char)(n)); - } - - /* - * Next store the lattrs; same principle. We add one extra bit to - * this to indicate the trust state of the line. - */ - { - int n = ldata->lattr | (ldata->trusted ? 0x10000 : 0); - while (n >= 128) { - put_byte(b, (unsigned char)((n & 0x7F) | 0x80)); - n >>= 7; - } - put_byte(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_truecolour); - makerle(b, ldata, makeliteral_cc); - - size_t linelen = b->len - sizeof(compressed_scrollback_line); - compressed_scrollback_line *line = - (compressed_scrollback_line *)strbuf_to_str(b); - line->len = linelen; - - /* - * 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 - { - 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(line); - 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 - - freetermline(dcl); - } -#endif -#endif /* TERM_CC_DIAGS */ - - return line; -} - -static void readrle(BinarySource *bs, termline *ldata, - void (*readliteral)(BinarySource *bs, termchar *c, - termline *ldata, unsigned long *state)) -{ - int n = 0; - unsigned long state = 0; - - while (n < ldata->cols) { - int hdr = get_byte(bs); - - if (hdr >= 0x80) { - /* A run. */ - - size_t pos = bs->pos, count = hdr + 2 - 0x80; - while (count--) { - assert(n < ldata->cols); - bs->pos = pos; - readliteral(bs, ldata->chars + n, ldata, &state); - n++; - } - } else { - /* Just a sequence of consecutive literals. */ - - int count = hdr + 1; - while (count--) { - assert(n < ldata->cols); - readliteral(bs, ldata->chars + n, ldata, &state); - n++; - } - } - } - - assert(n == ldata->cols); -} -static void readliteral_chr(BinarySource *bs, 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_byte(bs); - if (byte < 0x80) { - c->chr = byte | *state; - } else if (byte < 0xC0) { - c->chr = (byte &~ 0xC0) << 8; - c->chr |= get_byte(bs); - } else if (byte < 0xE0) { - c->chr = (byte &~ 0xE0) << 16; - c->chr |= get_uint16(bs); - } else if (byte < 0xF0) { - c->chr = (byte &~ 0xF0) << 24; - c->chr |= get_byte(bs) << 16; - c->chr |= get_uint16(bs); - } else { - assert(byte == 0xF0); - c->chr = get_uint32(bs); - } - *state = c->chr & ~0xFF; -} -static void readliteral_attr(BinarySource *bs, termchar *c, termline *ldata, - unsigned long *state) -{ - unsigned val, attr, colourbits; - - val = get_uint16(bs); - - if (val >= 0x8000) { - val &= ~0x8000; - val <<= 16; - val |= get_uint16(bs); - } - - 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_truecolour( - BinarySource *bs, termchar *c, termline *ldata, unsigned long *state) -{ - int flags = get_byte(bs); - - if (flags & 1) { - c->truecolour.fg.enabled = true; - c->truecolour.fg.r = get_byte(bs); - c->truecolour.fg.g = get_byte(bs); - c->truecolour.fg.b = get_byte(bs); - } else { - c->truecolour.fg = optionalrgb_none; - } - - if (flags & 2) { - c->truecolour.bg.enabled = true; - c->truecolour.bg.r = get_byte(bs); - c->truecolour.bg.g = get_byte(bs); - c->truecolour.bg.b = get_byte(bs); - } else { - c->truecolour.bg = optionalrgb_none; - } -} -static void readliteral_cc(BinarySource *bs, 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(bs, &n, ldata, &zstate); - if (!n.chr) - break; - add_cc(ldata, x, n.chr); - } -} - -static termline *decompressline(compressed_scrollback_line *line) -{ - int ncols, byte, shift; - BinarySource bs[1]; - termline *ldata; - - BinarySource_BARE_INIT(bs, line+1, line->len); - - /* - * First read in the column count. - */ - ncols = shift = 0; - do { - byte = get_byte(bs); - 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. - */ - int lattr = shift = 0; - do { - byte = get_byte(bs); - lattr |= (byte & 0x7F) << shift; - shift += 7; - } while (byte & 0x80); - ldata->lattr = lattr & 0xFFFF; - ldata->trusted = (lattr & 0x10000) != 0; - - /* - * Now we read in each of the RLE streams in turn. - */ - readrle(bs, ldata, readliteral_chr); - readrle(bs, ldata, readliteral_attr); - readrle(bs, ldata, readliteral_truecolour); - readrle(bs, ldata, readliteral_cc); - - /* And we always expect that we ended up exactly at the end of the - * compressed data. */ - assert(!get_err(bs)); - assert(get_avail(bs) == 0); - - 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->erase_to_scrollback && - term->alt_which && term->alt_screen) { - sblines += term->alt_sblines; - } - return sblines; -} - -static void null_line_error(Terminal *term, int y, int lineno, - tree234 *whichtree, int treeindex, - const char *varname) -{ - modalfatalbox("%s==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" - "commitid=%s\n\n" - "Please contact " - "and pass on the above information.", - varname, 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, commitid); -} - -/* - * 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->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) { - compressed_scrollback_line *cline = index234(whichtree, treeindex); - if (!cline) - null_line_error(term, y, lineno, whichtree, treeindex, "cline"); - line = decompressline(cline); - } else { - line = index234(whichtree, treeindex); - } - - /* We assume that we don't screw up and retrieve something out of range. */ - if (line == NULL) - null_line_error(term, y, lineno, whichtree, treeindex, "line"); - assert(line != NULL); - - /* - * Here we resize lines to _at least_ the right length, but we - * don't truncate them. Truncation is done as a side effect of - * modifying the line. - * - * The point of this policy is to try to arrange that resizing the - * terminal window repeatedly - e.g. successive steps in an X11 - * opaque window-resize drag, or resizing as a side effect of - * retiling by tiling WMs such as xmonad - does not throw away - * data gratuitously. Specifically, we want a sequence of resize - * operations with no terminal output between them to have the - * same effect as a single resize to the ultimate terminal size, - * and also (for the case in which xmonad narrows a window that's - * scrolling things) we want scrolling up new text at the bottom - * of a narrowed window to avoid truncating lines further up when - * the window is re-widened. - */ - if (term->cols > line->cols) - resizeline(term, line, term->cols); - - return line; -} - -#define lineptr(x) (lineptr)(term,x,__LINE__,0) -#define scrlineptr(x) (lineptr)(term,x,__LINE__,1) - -/* - * Coerce a termline to the terminal's current width. Unlike the - * optional resize in lineptr() above, this is potentially destructive - * of text, since it can shrink as well as grow the line. - * - * We call this whenever a termline is actually going to be modified. - * Helpfully, putting a single call to this function in check_boundary - * deals with _nearly_ all such cases, leaving only a few things like - * bulk erase and ESC#8 to handle separately. - */ -static void check_line_size(Terminal *term, termline *line) -{ - if (term->cols != line->cols) /* trivial optimisation */ - resizeline(term, line, term->cols); -} - -static void term_schedule_tblink(Terminal *term); -static void term_schedule_cblink(Terminal *term); -static void term_update_callback(void *ctx); - -static void term_timer(void *ctx, unsigned long now) -{ - Terminal *term = (Terminal *)ctx; - - if (term->tblink_pending && now == term->next_tblink) { - term->tblinker = !term->tblinker; - term->tblink_pending = false; - term_schedule_tblink(term); - term->window_update_pending = true; - } - - if (term->cblink_pending && now == term->next_cblink) { - term->cblinker = !term->cblinker; - term->cblink_pending = false; - term_schedule_cblink(term); - term->window_update_pending = true; - } - - if (term->in_vbell && now == term->vbell_end) { - term->in_vbell = false; - term->window_update_pending = true; - } - - if (term->window_update_cooldown && - now == term->window_update_cooldown_end) { - term->window_update_cooldown = false; - } - - if (term->window_update_pending) - term_update_callback(term); -} - -static void term_update_callback(void *ctx) -{ - Terminal *term = (Terminal *)ctx; - if (!term->window_update_pending) - return; - if (!term->window_update_cooldown) { - term_update(term); - term->window_update_cooldown = true; - term->window_update_cooldown_end = schedule_timer( - UPDATE_DELAY, term_timer, term); - } -} - -static void term_schedule_update(Terminal *term) -{ - if (!term->window_update_pending) { - term->window_update_pending = true; - queue_toplevel_callback(term_update_callback, 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 = true; /* reset when not in use */ - term->tblink_pending = false; - } -} - -/* - * Likewise with cursor blinks. - */ -static void term_schedule_cblink(Terminal *term) -{ - if (term->blink_cur && term->has_focus) { - if (!term->cblink_pending) - term->next_cblink = schedule_timer(CBLINK_DELAY, term_timer, term); - term->cblink_pending = true; - } else { - term->cblinker = true; /* 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 = true; - term->cblink_pending = false; - term_schedule_cblink(term); -} - -/* - * Call to begin a visual bell. - */ -static void term_schedule_vbell(Terminal *term, bool 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, bool 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 = conf_get_bool(term->conf, CONF_dec_om); - term->alt_ins = false; - term->insert = false; - term->alt_wnext = false; - term->wrapnext = false; - term->save_wnext = false; - term->alt_save_wnext = false; - term->alt_wrap = term->wrap = conf_get_bool(term->conf, CONF_wrap_mode); - term->alt_cset = term->cset = term->save_cset = term->alt_save_cset = 0; - term->alt_utf = false; - term->utf = false; - term->save_utf = false; - term->alt_save_utf = false; - term->utf8.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 = false; - term->in_vbell = false; - term->cursor_on = true; - term->big_cursor = false; - term->default_attr = term->save_attr = - term->alt_save_attr = term->curr_attr = ATTR_DEFAULT; - term->curr_truecolour.fg = term->curr_truecolour.bg = optionalrgb_none; - term->save_truecolour = term->alt_save_truecolour = term->curr_truecolour; - term->app_cursor_keys = conf_get_bool(term->conf, CONF_app_cursor); - term->app_keypad_keys = conf_get_bool(term->conf, CONF_app_keypad); - term->use_bce = conf_get_bool(term->conf, CONF_bce); - term->blink_is_real = conf_get_bool(term->conf, CONF_blinktext); - term->erase_char = term->basic_erase_char; - term->alt_which = 0; - term_print_finish(term); - term->xterm_mouse = 0; - term->xterm_extended_mouse = false; - term->urxvt_extended_mouse = false; - win_set_raw_mouse_mode(term->win, false); - term->win_pointer_shape_pending = true; - term->win_pointer_shape_raw = false; - term->bracketed_paste = false; - term->srm_echo = false; - { - int i; - for (i = 0; i < 256; i++) - term->wordness[i] = conf_get_int_int(term->conf, CONF_wordness, i); - } - if (term->screen) { - swap_screen(term, 1, false, false); - erase_lots(term, false, true, true); - swap_screen(term, 0, false, false); - 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); - term_schedule_update(term); -} - -/* - * Force a screen update. - */ -void term_update(Terminal *term) -{ - term->window_update_pending = false; - - if (term->win_move_pending) { - win_move(term->win, term->win_move_pending_x, - term->win_move_pending_y); - term->win_move_pending = false; - } - if (term->win_resize_pending) { - win_request_resize(term->win, term->win_resize_pending_w, - term->win_resize_pending_h); - term->win_resize_pending = false; - } - if (term->win_zorder_pending) { - win_set_zorder(term->win, term->win_zorder_top); - term->win_zorder_pending = false; - } - if (term->win_minimise_pending) { - win_set_minimised(term->win, term->win_minimise_enable); - term->win_minimise_pending = false; - } - if (term->win_maximise_pending) { - win_set_maximised(term->win, term->win_maximise_enable); - term->win_maximise_pending = false; - } - if (term->win_title_pending) { - win_set_title(term->win, term->window_title); - term->win_title_pending = false; - } - if (term->win_icon_title_pending) { - win_set_icon_title(term->win, term->icon_title); - term->win_icon_title_pending = false; - } - if (term->win_pointer_shape_pending) { - win_set_raw_mouse_mode_pointer(term->win, term->win_pointer_shape_raw); - term->win_pointer_shape_pending = false; - } - if (term->win_refresh_pending) { - win_refresh(term->win); - term->win_refresh_pending = false; - } - if (term->win_palette_pending) { - unsigned start = term->win_palette_pending_min; - unsigned ncolours = term->win_palette_pending_limit - start; - win_palette_set(term->win, start, ncolours, term->palette + start); - term->win_palette_pending = false; - } - - if (win_setup_draw_ctx(term->win)) { - bool need_sbar_update = term->seen_disp_event || - term->win_scrollbar_update_pending; - term->win_scrollbar_update_pending = false; - if (term->seen_disp_event && term->scroll_on_disp) { - term->disptop = 0; /* return to main screen */ - term->seen_disp_event = false; - need_sbar_update = true; - } - - if (need_sbar_update) - update_sbar(term); - do_paint(term); - win_set_cursor_pos( - term->win, term->curs.x, term->curs.y - term->disptop); - win_free_draw_ctx(term->win); - } -} - -/* - * 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->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, bool clear) -{ - power_on(term, clear); - if (term->ldisc) /* cause ldisc to notice changes */ - ldisc_echoedit_update(term->ldisc); - 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)); - term->erase_char.truecolour.bg = term->curr_truecolour.bg; - } -} - -/* - * We copy a bunch of stuff out of the Conf structure into local - * fields in the Terminal structure, to avoid the repeated tree234 - * lookups which would be involved in fetching them from the former - * every time. - */ -void term_copy_stuff_from_conf(Terminal *term) -{ - term->ansi_colour = conf_get_bool(term->conf, CONF_ansi_colour); - term->no_arabicshaping = conf_get_bool(term->conf, CONF_no_arabicshaping); - term->beep = conf_get_int(term->conf, CONF_beep); - term->bellovl = conf_get_bool(term->conf, CONF_bellovl); - term->bellovl_n = conf_get_int(term->conf, CONF_bellovl_n); - term->bellovl_s = conf_get_int(term->conf, CONF_bellovl_s); - term->bellovl_t = conf_get_int(term->conf, CONF_bellovl_t); - term->no_bidi = conf_get_bool(term->conf, CONF_no_bidi); - term->bksp_is_delete = conf_get_bool(term->conf, CONF_bksp_is_delete); - term->blink_cur = conf_get_bool(term->conf, CONF_blink_cur); - term->blinktext = conf_get_bool(term->conf, CONF_blinktext); - term->cjk_ambig_wide = conf_get_bool(term->conf, CONF_cjk_ambig_wide); - term->conf_height = conf_get_int(term->conf, CONF_height); - term->conf_width = conf_get_int(term->conf, CONF_width); - term->crhaslf = conf_get_bool(term->conf, CONF_crhaslf); - term->erase_to_scrollback = conf_get_bool(term->conf, CONF_erase_to_scrollback); - term->funky_type = conf_get_int(term->conf, CONF_funky_type); - term->lfhascr = conf_get_bool(term->conf, CONF_lfhascr); - term->logflush = conf_get_bool(term->conf, CONF_logflush); - term->logtype = conf_get_int(term->conf, CONF_logtype); - term->mouse_override = conf_get_bool(term->conf, CONF_mouse_override); - term->nethack_keypad = conf_get_bool(term->conf, CONF_nethack_keypad); - term->no_alt_screen = conf_get_bool(term->conf, CONF_no_alt_screen); - term->no_applic_c = conf_get_bool(term->conf, CONF_no_applic_c); - term->no_applic_k = conf_get_bool(term->conf, CONF_no_applic_k); - term->no_dbackspace = conf_get_bool(term->conf, CONF_no_dbackspace); - term->no_mouse_rep = conf_get_bool(term->conf, CONF_no_mouse_rep); - term->no_remote_charset = conf_get_bool(term->conf, CONF_no_remote_charset); - term->no_remote_resize = conf_get_bool(term->conf, CONF_no_remote_resize); - term->no_remote_wintitle = conf_get_bool(term->conf, CONF_no_remote_wintitle); - term->no_remote_clearscroll = conf_get_bool(term->conf, CONF_no_remote_clearscroll); - term->rawcnp = conf_get_bool(term->conf, CONF_rawcnp); - term->utf8linedraw = conf_get_bool(term->conf, CONF_utf8linedraw); - term->rect_select = conf_get_bool(term->conf, CONF_rect_select); - term->remote_qtitle_action = conf_get_int(term->conf, CONF_remote_qtitle_action); - term->rxvt_homeend = conf_get_bool(term->conf, CONF_rxvt_homeend); - term->scroll_on_disp = conf_get_bool(term->conf, CONF_scroll_on_disp); - term->scroll_on_key = conf_get_bool(term->conf, CONF_scroll_on_key); - term->xterm_mouse_forbidden = conf_get_bool(term->conf, CONF_no_mouse_rep); - term->xterm_256_colour = conf_get_bool(term->conf, CONF_xterm_256_colour); - term->true_colour = conf_get_bool(term->conf, CONF_true_colour); - - /* - * Parse the control-character escapes in the configured - * answerback string. - */ - { - char *answerback = conf_get_str(term->conf, CONF_answerback); - int maxlen = strlen(answerback); - - term->answerback = snewn(maxlen, char); - term->answerbacklen = 0; - - while (*answerback) { - char *n; - char c = ctrlparse(answerback, &n); - if (n) { - term->answerback[term->answerbacklen++] = c; - answerback = n; - } else { - term->answerback[term->answerbacklen++] = *answerback++; - } - } - } -} - -void term_pre_reconfig(Terminal *term, Conf *conf) -{ - - /* - * Copy the current window title into the stored previous - * configuration, so that doing nothing to the window title field - * in the config box doesn't reset the title to its startup state. - */ - conf_set_str(conf, CONF_wintitle, term->window_title); -} - -/* - * 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, Conf *conf) -{ - /* - * Before adopting the new config, check all those terminal - * settings which control power-on defaults; and if they've - * changed, we will modify the current state as well as the - * default one. The full list is: Auto wrap mode, DEC Origin - * Mode, BCE, blinking text, character classes. - */ - bool reset_wrap, reset_decom, reset_bce, reset_tblink, reset_charclass; - bool palette_changed = false; - int i; - - reset_wrap = (conf_get_bool(term->conf, CONF_wrap_mode) != - conf_get_bool(conf, CONF_wrap_mode)); - reset_decom = (conf_get_bool(term->conf, CONF_dec_om) != - conf_get_bool(conf, CONF_dec_om)); - reset_bce = (conf_get_bool(term->conf, CONF_bce) != - conf_get_bool(conf, CONF_bce)); - reset_tblink = (conf_get_bool(term->conf, CONF_blinktext) != - conf_get_bool(conf, CONF_blinktext)); - reset_charclass = false; - for (i = 0; i < 256; i++) - if (conf_get_int_int(term->conf, CONF_wordness, i) != - conf_get_int_int(conf, CONF_wordness, i)) - reset_charclass = true; - - /* - * If the bidi or shaping settings have changed, flush the bidi - * cache completely. - */ - if (conf_get_bool(term->conf, CONF_no_arabicshaping) != - conf_get_bool(conf, CONF_no_arabicshaping) || - conf_get_bool(term->conf, CONF_no_bidi) != - conf_get_bool(conf, CONF_no_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; - } - } - - { - const char *old_title = conf_get_str(term->conf, CONF_wintitle); - const char *new_title = conf_get_str(conf, CONF_wintitle); - if (strcmp(old_title, new_title)) { - sfree(term->window_title); - term->window_title = dupstr(new_title); - term->win_title_pending = true; - term_schedule_update(term); - } - } - - /* - * Just setting conf is sufficient to cause colour setting changes - * to appear on the next ESC]R palette reset. But we should also - * check whether any colour settings have been changed, so that - * they can be updated immediately if they haven't been overridden - * by some escape sequence. - */ - { - int i, j; - for (i = 0; i < CONF_NCOLOURS; i++) { - for (j = 0; j < 3; j++) - if (conf_get_int_int(term->conf, CONF_colours, i*3+j) != - conf_get_int_int(conf, CONF_colours, i*3+j)) - break; - if (j < 3) { - /* Actually enacting the change has to be deferred - * until the new conf is installed. */ - palette_changed = true; - break; - } - } - } - - conf_free(term->conf); - term->conf = conf_copy(conf); - - if (reset_wrap) - term->alt_wrap = term->wrap = conf_get_bool(term->conf, CONF_wrap_mode); - if (reset_decom) - term->alt_om = term->dec_om = conf_get_bool(term->conf, CONF_dec_om); - if (reset_bce) { - term->use_bce = conf_get_bool(term->conf, CONF_bce); - set_erase_char(term); - } - if (reset_tblink) { - term->blink_is_real = conf_get_bool(term->conf, CONF_blinktext); - } - if (reset_charclass) - for (i = 0; i < 256; i++) - term->wordness[i] = conf_get_int_int(term->conf, CONF_wordness, i); - - if (conf_get_bool(term->conf, CONF_no_alt_screen)) - swap_screen(term, 0, false, false); - if (conf_get_bool(term->conf, CONF_no_remote_charset)) { - term->cset_attr[0] = term->cset_attr[1] = CSET_ASCII; - term->sco_acs = term->alt_sco_acs = 0; - term->utf = false; - } - if (!conf_get_str(term->conf, CONF_printer)) { - term_print_finish(term); - } - if (palette_changed) - term_notify_palette_changed(term); - term_schedule_tblink(term); - term_schedule_cblink(term); - term_copy_stuff_from_conf(term); - term_update_raw_mouse_mode(term); -} - -/* - * Clear the scrollback. - */ -void term_clrsb(Terminal *term) -{ - unsigned char *line; - int i; - - /* - * Scroll forward to the current screen, if we were back in the - * scrollback somewhere until now. - */ - term->disptop = 0; - - /* - * Clear the actual scrollback. - */ - while ((line = delpos234(term->scrollback, 0)) != NULL) { - sfree(line); /* this is compressed data, not a termline */ - } - - /* - * When clearing the scrollback, we also truncate any termlines on - * the current screen which have remembered data from a previous - * larger window size. Rationale: clearing the scrollback is - * sometimes done to protect privacy, so the user intention is - * specifically that we should not retain evidence of what - * previously happened in the terminal, and that ought to include - * evidence to the right as well as evidence above. - */ - for (i = 0; i < term->rows; i++) - check_line_size(term, scrlineptr(i)); - - /* - * That operation has invalidated the selection, if it overlapped - * the scrollback at all. - */ - if (term->selstate != NO_SELECTION && term->selstart.y < 0) - deselect(term); - - /* - * There are now no lines of real scrollback which can be pulled - * back into the screen by a resize, and no lines of the alternate - * screen which should be displayed as if part of the scrollback. - */ - term->tempsblines = 0; - term->alt_sblines = 0; - - /* - * The scrollbar will need updating to reflect the new state of - * the world. - */ - term->win_scrollbar_update_pending = true; - term_schedule_update(term); -} - -const optionalrgb optionalrgb_none = {0, 0, 0, 0}; - -void term_setup_window_titles(Terminal *term, const char *title_hostname) -{ - const char *conf_title = conf_get_str(term->conf, CONF_wintitle); - sfree(term->window_title); - sfree(term->icon_title); - if (*conf_title) { - term->window_title = dupstr(conf_title); - term->icon_title = dupstr(conf_title); - } else { - if (title_hostname && *title_hostname) - term->window_title = dupcat(title_hostname, " - ", appname); - else - term->window_title = dupstr(appname); - term->icon_title = dupstr(term->window_title); - } - term->win_title_pending = true; - term->win_icon_title_pending = true; -} - -static void palette_rebuild(Terminal *term) -{ - unsigned min_changed = OSC4_NCOLOURS, max_changed = 0; - - if (term->win_palette_pending) { - /* Possibly extend existing range. */ - min_changed = term->win_palette_pending_min; - max_changed = term->win_palette_pending_limit - 1; - } else { - /* Start with empty range. */ - min_changed = OSC4_NCOLOURS; - max_changed = 0; - } - - for (unsigned i = 0; i < OSC4_NCOLOURS; i++) { - rgb new_value; - bool found = false; - - for (unsigned j = lenof(term->subpalettes); j-- > 0 ;) { - if (term->subpalettes[j].present[i]) { - new_value = term->subpalettes[j].values[i]; - found = true; - break; - } - } - - assert(found); /* we expect SUBPAL_CONF to always be set */ - - if (new_value.r != term->palette[i].r || - new_value.g != term->palette[i].g || - new_value.b != term->palette[i].b) { - term->palette[i] = new_value; - if (min_changed > i) - min_changed = i; - if (max_changed < i) - max_changed = i; - } - } - - if (min_changed <= max_changed) { - /* - * At least one colour changed (or we had an update scheduled - * already). Schedule a redraw event to pass the result back - * to the TermWin. This also requires invalidating the rest - * of the window, because usually all the text will need - * redrawing in the new colours. - * (If there was an update pending and this palette rebuild - * didn't actually change anything, we'll harmlessly reinforce - * the existing update request.) - */ - term->win_palette_pending = true; - term->win_palette_pending_min = min_changed; - term->win_palette_pending_limit = max_changed + 1; - term_invalidate(term); - } -} - -/* - * Rebuild the palette from configuration and platform colours. - * If 'keep_overrides' set, any escape-sequence-specified overrides will - * remain in place. - */ -static void palette_reset(Terminal *term, bool keep_overrides) -{ - for (unsigned i = 0; i < OSC4_NCOLOURS; i++) - term->subpalettes[SUBPAL_CONF].present[i] = true; - - /* - * Copy all the palette information out of the Conf. - */ - for (unsigned i = 0; i < CONF_NCOLOURS; i++) { - rgb *col = &term->subpalettes[SUBPAL_CONF].values[ - colour_indices_conf_to_osc4[i]]; - col->r = conf_get_int_int(term->conf, CONF_colours, i*3+0); - col->g = conf_get_int_int(term->conf, CONF_colours, i*3+1); - col->b = conf_get_int_int(term->conf, CONF_colours, i*3+2); - } - - /* - * Directly invent the rest of the xterm-256 colours. - */ - for (unsigned i = 0; i < 216; i++) { - rgb *col = &term->subpalettes[SUBPAL_CONF].values[i + 16]; - int r = i / 36, g = (i / 6) % 6, b = i % 6; - col->r = r ? r * 40 + 55 : 0; - col->g = g ? g * 40 + 55 : 0; - col->b = b ? b * 40 + 55 : 0; - } - for (unsigned i = 0; i < 24; i++) { - rgb *col = &term->subpalettes[SUBPAL_CONF].values[i + 232]; - int shade = i * 10 + 8; - col->r = col->g = col->b = shade; - } - - /* - * Re-fetch any OS-local overrides. - */ - for (unsigned i = 0; i < OSC4_NCOLOURS; i++) - term->subpalettes[SUBPAL_PLATFORM].present[i] = false; - win_palette_get_overrides(term->win, term); - - if (!keep_overrides) { - /* - * Get rid of all escape-sequence configuration. - */ - for (unsigned i = 0; i < OSC4_NCOLOURS; i++) - term->subpalettes[SUBPAL_SESSION].present[i] = false; - } - - /* - * Rebuild the composite palette. - */ - palette_rebuild(term); -} - -void term_palette_override(Terminal *term, unsigned osc4_index, rgb rgb) -{ - /* - * We never expect to be called except as re-entry from our own - * call to win_palette_get_overrides above, so we need not mess - * about calling palette_rebuild. - */ - term->subpalettes[SUBPAL_PLATFORM].present[osc4_index] = true; - term->subpalettes[SUBPAL_PLATFORM].values[osc4_index] = rgb; -} - -/* - * Initialise the terminal. - */ -Terminal *term_init(Conf *myconf, struct unicode_data *ucsdata, TermWin *win) -{ - Terminal *term; - - /* - * Allocate a new Terminal structure and initialise the fields - * that need it. - */ - term = snew(Terminal); - term->win = win; - term->ucsdata = ucsdata; - term->conf = conf_copy(myconf); - term->logctx = NULL; - term->compatibility_level = TM_PUTTY; - strcpy(term->id_string, "\033[?6c"); - term->cblink_pending = term->tblink_pending = false; - term->paste_buffer = NULL; - term->paste_len = 0; - 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 = 0; - term->reset_132 = false; - term->cblinker = false; - term->tblinker = false; - term->has_focus = true; - term->repeat_off = false; - term->termstate = TOPLEVEL; - term->selstate = NO_SELECTION; - term->curstype = 0; - - term_copy_stuff_from_conf(term); - - term->screen = term->alt_screen = term->scrollback = NULL; - term->tempsblines = 0; - term->alt_sblines = 0; - term->disptop = 0; - 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; - term->nbeeps = 0; - term->lastbeep = false; - term->beep_overloaded = false; - term->attr_mask = 0xffffffff; - term->backend = 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->window_update_cooldown = 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->basic_erase_char.truecolour.fg = optionalrgb_none; - term->basic_erase_char.truecolour.bg = optionalrgb_none; - term->erase_char = term->basic_erase_char; - - term->last_selected_text = NULL; - term->last_selected_attr = NULL; - term->last_selected_tc = NULL; - term->last_selected_len = 0; - /* TermWin implementations will typically extend these with - * clipboard ids they know about */ - term->mouse_select_clipboards[0] = CLIP_LOCAL; - term->n_mouse_select_clipboards = 1; - term->mouse_paste_clipboard = CLIP_NULL; - - term->last_graphic_char = 0; - - term->trusted = true; - - term->bracketed_paste_active = false; - - term->window_title = dupstr(""); - term->icon_title = dupstr(""); - term->minimised = false; - term->winpos_x = term->winpos_y = 0; - term->winpixsize_x = term->winpixsize_y = 0; - - term->win_move_pending = false; - term->win_resize_pending = false; - term->win_zorder_pending = false; - term->win_minimise_pending = false; - term->win_maximise_pending = false; - term->win_title_pending = false; - term->win_icon_title_pending = false; - term->win_pointer_shape_pending = false; - term->win_refresh_pending = false; - term->win_scrollbar_update_pending = false; - term->win_palette_pending = false; - - palette_reset(term, false); - - 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) - freetermline(line); - freetree234(term->screen); - while ((line = delpos234(term->alt_screen, 0)) != NULL) - freetermline(line); - freetree234(term->alt_screen); - if (term->disptext) { - for (i = 0; i < term->rows; i++) - freetermline(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); - sfree(term->answerback); - - for (i = 0; i < term->bidi_cache_size; i++) { - sfree(term->pre_bidi_cache[i].chars); - sfree(term->post_bidi_cache[i].chars); - sfree(term->post_bidi_cache[i].forward); - sfree(term->post_bidi_cache[i].backward); - } - sfree(term->pre_bidi_cache); - sfree(term->post_bidi_cache); - - sfree(term->tabs); - - expire_timer_context(term); - delete_callbacks_for_context(term); - - conf_free(term->conf); - - sfree(term->window_title); - sfree(term->icon_title); - - sfree(term); -} - -void term_set_trust_status(Terminal *term, bool trusted) -{ - term->trusted = trusted; -} - -void term_get_cursor_position(Terminal *term, int *x, int *y) -{ - *x = term->curs.x; - *y = term->curs.y; -} - -/* - * 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) { - compressed_scrollback_line *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); - 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 = newtermline(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 */ - line = delpos234(term->screen, term->rows - 1); - freetermline(line); - } else { - /* push top row to scrollback */ - line = delpos234(term->screen, 0); - addpos234(term->scrollback, compressline(line), sblen++); - freetermline(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] = newtermline(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++) - freetermline(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 = newtermline(term, newcols, true); - addpos234(newalt, line, i); - } - if (term->alt_screen) { - while (NULL != (line = delpos234(term->alt_screen, 0))) - freetermline(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 = false; - term->alt_wnext = false; - - term->rows = newrows; - term->cols = newcols; - term->savelines = newsavelines; - - swap_screen(term, save_alt_which, false, false); - - term->win_scrollbar_update_pending = true; - term_schedule_update(term); - if (term->backend) - backend_size(term->backend, term->cols, term->rows); -} - -/* - * Hand a backend to the terminal, so it can be notified of resizes. - */ -void term_provide_backend(Terminal *term, Backend *backend) -{ - term->backend = backend; - if (term->backend && term->cols > 0 && term->rows > 0) - backend_size(term->backend, 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, - bool reset, bool keep_cur_pos) -{ - int t; - bool bt; - pos tp; - truecolour ttc; - tree234 *ttr; - - if (!which) - reset = false; /* do no weird resetting if which==0 */ - - if (which != term->alt_which) { - if (term->erase_to_scrollback && term->alt_screen && - term->alt_which && term->disptop < 0) { - /* - * We're swapping away from the alternate screen, so some - * lines are about to vanish from the virtual scrollback. - * Adjust disptop by that much, so that (if we're not - * resetting the scrollback anyway on a display event) the - * current scroll position still ends up pointing at the - * same text. - */ - term->disptop += term->alt_sblines; - if (term->disptop > 0) - term->disptop = 0; - } - - term->alt_which = which; - - ttr = term->alt_screen; - term->alt_screen = term->screen; - term->screen = ttr; - term->alt_sblines = ( - term->alt_screen ? - find_last_nonempty_line(term, term->alt_screen) + 1 : 0); - 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; - bt = term->dec_om; - if (!reset) term->dec_om = term->alt_om; - term->alt_om = bt; - bt = term->wrap; - if (!reset) term->wrap = term->alt_wrap; - term->alt_wrap = bt; - bt = term->wrapnext; - if (!reset) term->wrapnext = term->alt_wnext; - term->alt_wnext = bt; - bt = term->insert; - if (!reset) term->insert = term->alt_ins; - term->alt_ins = bt; - t = term->cset; - if (!reset) term->cset = term->alt_cset; - term->alt_cset = t; - bt = term->utf; - if (!reset) term->utf = term->alt_utf; - term->alt_utf = bt; - t = term->sco_acs; - if (!reset) term->sco_acs = term->alt_sco_acs; - term->alt_sco_acs = t; - - tp = term->savecurs; - if (!reset) - term->savecurs = term->alt_savecurs; - term->alt_savecurs = tp; - t = term->save_cset; - if (!reset) - term->save_cset = term->alt_save_cset; - term->alt_save_cset = t; - t = term->save_csattr; - if (!reset) - term->save_csattr = term->alt_save_csattr; - term->alt_save_csattr = t; - t = term->save_attr; - if (!reset) - term->save_attr = term->alt_save_attr; - term->alt_save_attr = t; - ttc = term->save_truecolour; - if (!reset) - term->save_truecolour = term->alt_save_truecolour; - term->alt_save_truecolour = ttc; - bt = term->save_utf; - if (!reset) - term->save_utf = term->alt_save_utf; - term->alt_save_utf = bt; - bt = term->save_wnext; - if (!reset) - term->save_wnext = term->alt_save_wnext; - term->alt_save_wnext = bt; - t = term->save_sco_acs; - if (!reset) - term->save_sco_acs = term->alt_save_sco_acs; - term->alt_save_sco_acs = t; - - if (term->erase_to_scrollback && term->alt_screen && - term->alt_which && term->disptop < 0) { - /* - * Inverse of the adjustment at the top of this function. - * This time, we're swapping _to_ the alternate screen, so - * some lines are about to _appear_ in the virtual - * scrollback, and we adjust disptop in the other - * direction. - * - * Both these adjustments depend on the value stored in - * term->alt_sblines while the alt screen is selected, - * which is why we had to do one _before_ switching away - * from it and the other _after_ switching to it. - */ - term->disptop -= term->alt_sblines; - int limit = -sblines(term); - if (term->disptop < limit) - term->disptop = limit; - } - } - - 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); - win_set_scrollbar(term->win, 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); -} - -static void clear_line(Terminal *term, termline *line) -{ - resizeline(term, line, term->cols); - for (int i = 0; i < term->cols; i++) - copy_termchar(line, i, &term->erase_char); - line->lattr = LATTR_NORM; -} - -static void check_trust_status(Terminal *term, termline *line) -{ - if (line->trusted != term->trusted) { - /* - * If we're displaying trusted output on a previously - * untrusted line, or vice versa, we need to switch the - * 'trusted' attribute on this terminal line, and also clear - * all its previous contents. - */ - clear_line(term, line); - line->trusted = term->trusted; - } -} - -/* - * 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, bool sb) -{ - termline *line; - int seltop, scrollwinsize; - - if (topline != 0 || term->alt_which != 0) - sb = false; - - scrollwinsize = botline - topline + 1; - - if (lines < 0) { - lines = -lines; - if (lines > scrollwinsize) - lines = scrollwinsize; - while (lines-- > 0) { - line = delpos234(term->screen, botline); - resizeline(term, line, term->cols); - clear_line(term, line); - 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; - } - } - } - } else { - if (lines > scrollwinsize) - lines = scrollwinsize; - while (lines-- > 0) { - line = delpos234(term->screen, topline); -#ifdef TERM_CC_DIAGS - cc_check(line); -#endif - if (sb && term->savelines > 0) { - 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); - clear_line(term, line); - check_trust_status(term, line); - 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; - } - } - } - } - } -} - -/* - * 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, bool save) -{ - if (save) { - term->savecurs = term->curs; - term->save_attr = term->curr_attr; - term->save_truecolour = term->curr_truecolour; - 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->curr_truecolour = term->save_truecolour; - 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); - check_trust_status(term, ldata); - check_line_size(term, ldata); - 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, - bool line_only, bool from_begin, bool to_end) -{ - pos start, end; - bool erase_lattr; - bool erasing_lines_from_top = false; - - 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; - } - - /* This is the endpoint of the clearing operation that is not - * either the start or end of the line / screen. */ - pos boundary = term->curs; - - if (!from_begin) { - /* - * If we're erasing from the current char to the end of - * line/screen, then we take account of wrapnext, so as to - * maintain the invariant that writing a printing character - * followed by ESC[K should not overwrite the character you - * _just wrote_. That is, when wrapnext says the cursor is - * 'logically' at the very rightmost edge of the screen - * instead of just before the last printing char, ESC[K should - * do nothing at all, and ESC[J should clear the next line but - * leave this one unchanged. - * - * This adjusted position will also be the position we use for - * check_boundary (i.e. the thing we ensure isn't in the - * middle of a double-width printing char). - */ - if (term->wrapnext) - incpos(boundary); - - start = boundary; - } - if (!to_end) { - /* - * If we're erasing from the start of (at least) the line _to_ - * the current position, then that is taken to mean 'inclusive - * of the cell under the cursor', which means we don't - * consider wrapnext at all: whether it's set or not, we still - * clear the cell under the cursor. - * - * Again, that incremented boundary position is where we - * should be careful of a straddling wide character. - */ - incpos(boundary); - end = boundary; - } - if (!from_begin || !to_end) - check_boundary(term, boundary.x, boundary.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 = true; - - if (term->erase_to_scrollback && erasing_lines_from_top) { - /* If it's a whole number of lines, starting at the top, and - * we're fully erasing them, erase by scrolling and keep the - * lines in the scrollback. */ - int scrolllines = end.y; - if (end.y == term->rows) { - /* 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); - check_trust_status(term, ldata); - while (poslt(start, end)) { - check_line_size(term, ldata); - 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); - check_trust_status(term, ldata); - } - } - } - - /* 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 explicitly 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 eol; - 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; - - /* - * We must de-highlight the selection if it overlaps any part of - * the region affected by this operation, i.e. the region from the - * current cursor position to end-of-line, _unless_ the entirety - * of the selection is going to be moved to the left or right by - * this operation but otherwise unchanged, in which case we can - * simply move the highlight with the text. - */ - eol.y = term->curs.y; - eol.x = term->cols; - if (poslt(term->curs, term->selend) && poslt(term->selstart, eol)) { - pos okstart = term->curs; - pos okend = eol; - if (dir > 0) { - /* Insertion: n characters at EOL will be splatted. */ - okend.x -= n; - } else { - /* Deletion: n characters at cursor position will be splatted. */ - okstart.x += n; - } - if (posle(okstart, term->selstart) && posle(term->selend, okend)) { - /* Selection is contained entirely in the interval - * [okstart,okend), so we need only adjust the selection - * bounds. */ - term->selstart.x += dir * n; - term->selend.x += dir * n; - assert(term->selstart.x >= term->curs.x); - assert(term->selstart.x < term->cols); - assert(term->selend.x > term->curs.x); - assert(term->selend.x <= term->cols); - } else { - /* Selection is not wholly contained in that interval, so - * we must unhighlight it. */ - deselect(term); - } - } - - 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); - check_trust_status(term, ldata); - 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); - } -} - -static void term_update_raw_mouse_mode(Terminal *term) -{ - bool want_raw = (term->xterm_mouse != 0 && !term->xterm_mouse_forbidden); - win_set_raw_mouse_mode(term->win, want_raw); - term->win_pointer_shape_pending = true; - term->win_pointer_shape_raw = want_raw; - term_schedule_update(term); -} - -/* - * 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, bool state) -{ - if (query == 1) { - 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->blinktext; - } - term_schedule_tblink(term); - break; - case 3: /* DECCOLM: 80/132 columns */ - deselect(term); - if (!term->no_remote_resize) { - term->win_resize_pending = true; - term->win_resize_pending_w = state ? 132 : 80; - term->win_resize_pending_h = term->rows; - term_schedule_update(term); - } - 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 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->no_alt_screen ? 0 : state, false, false); - if (term->scroll_on_disp) - term->disptop = 0; - break; - case 1000: /* xterm mouse 1 (normal) */ - term->xterm_mouse = state ? 1 : 0; - term_update_raw_mouse_mode(term); - break; - case 1002: /* xterm mouse 2 (inc. button drags) */ - term->xterm_mouse = state ? 2 : 0; - term_update_raw_mouse_mode(term); - break; - case 1006: /* xterm extended mouse */ - term->xterm_extended_mouse = state; - break; - case 1015: /* urxvt extended mouse */ - term->urxvt_extended_mouse = state; - break; - case 1047: /* alternate screen */ - compatibility(OTHER); - deselect(term); - swap_screen(term, term->no_alt_screen ? 0 : state, true, true); - if (term->scroll_on_disp) - term->disptop = 0; - break; - case 1048: /* save/restore cursor */ - if (!term->no_alt_screen) - save_cursor(term, state); - if (!state) seen_disp_event(term); - break; - case 1049: /* cursor & alternate screen */ - if (state && !term->no_alt_screen) - save_cursor(term, state); - if (!state) seen_disp_event(term); - compatibility(OTHER); - deselect(term); - swap_screen(term, term->no_alt_screen ? 0 : state, true, false); - if (!state && !term->no_alt_screen) - save_cursor(term, state); - if (term->scroll_on_disp) - term->disptop = 0; - break; - case 2004: /* xterm bracketed paste */ - term->bracketed_paste = state ? true : false; - break; - } - } else if (query == 0) { - switch (mode) { - case 4: /* IRM: set insert mode */ - compatibility(VT102); - term->insert = state; - break; - case 12: /* SRM: set echo mode */ - term->srm_echo = !state; - 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->no_remote_wintitle) { - sfree(term->icon_title); - term->icon_title = dupstr(term->osc_string); - term->win_icon_title_pending = true; - term_schedule_update(term); - } - if (term->esc_args[0] == 1) - break; - /* fall through: parameter 0 means set both */ - case 2: - case 21: - if (!term->no_remote_wintitle) { - sfree(term->window_title); - term->window_title = dupstr(term->osc_string); - term->win_title_pending = true; - term_schedule_update(term); - } - break; - case 4: - if (term->ldisc && !strcmp(term->osc_string, "?")) { - unsigned index = term->esc_args[1]; - if (index < OSC4_NCOLOURS) { - rgb colour = term->palette[index]; - char *reply_buf = dupprintf( - "\033]4;%u;rgb:%04x/%04x/%04x\007", index, - (unsigned)colour.r * 0x0101, - (unsigned)colour.g * 0x0101, - (unsigned)colour.b * 0x0101); - ldisc_send(term->ldisc, reply_buf, strlen(reply_buf), - false); - sfree(reply_buf); - } - } - break; - } - } -} - -/* - * ANSI printing routines. - */ -static void term_print_setup(Terminal *term, char *printer) -{ - bufchain_clear(&term->printer_buf); - term->print_job = printer_start_job(printer); -} -static void term_print_flush(Terminal *term) -{ - size_t size; - while ((size = bufchain_size(&term->printer_buf)) > 5) { - ptrlen data = bufchain_prefix(&term->printer_buf); - if (data.len > size-5) - data.len = size-5; - printer_job_data(term->print_job, data.ptr, data.len); - bufchain_consume(&term->printer_buf, data.len); - } -} -static void term_print_finish(Terminal *term) -{ - size_t 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) { - ptrlen data = bufchain_prefix(&term->printer_buf); - c = *(char *)data.ptr; - 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; -} - -static void term_display_graphic_char(Terminal *term, unsigned long c) -{ - termline *cline = scrlineptr(term->curs.y); - int width = 0; - if (DIRECT_CHAR(c)) - width = 1; - if (!width) - width = term_char_width(term, 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); - - check_trust_status(term, cline); - - int linecols = term->cols; - if (cline->trusted) - linecols -= TRUST_SIGIL_WIDTH; - - /* - * Preliminary check: if the terminal is only one character cell - * wide, then we cannot display any double-width character at all. - * Substitute single-width REPLACEMENT CHARACTER instead. - */ - if (width == 2 && linecols < 2) { - width = 1; - c = 0xFFFD; - } - - 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 >= linecols-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; - cline->chars[term->curs.x].truecolour = - term->curr_truecolour; - - 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; - cline->chars[term->curs.x].truecolour = - term->curr_truecolour; - - 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; - cline->chars[term->curs.x].truecolour = - term->curr_truecolour; - - 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); - } - return; - default: - return; - } - term->curs.x++; - if (term->curs.x >= linecols) { - term->curs.x = linecols - 1; - 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); -} - -static strbuf *term_input_data_from_unicode( - Terminal *term, const wchar_t *widebuf, int len) -{ - strbuf *buf = strbuf_new(); - - if (in_utf(term)) { - /* - * Translate input wide characters into UTF-8 to go in the - * terminal's input data queue. - */ - for (int i = 0; i < len; i++) { - unsigned long ch = widebuf[i]; - - if (IS_SURROGATE(ch)) { -#ifdef PLATFORM_IS_UTF16 - if (i+1 < len) { - unsigned long ch2 = widebuf[i+1]; - if (IS_SURROGATE_PAIR(ch, ch2)) { - ch = FROM_SURROGATES(ch, ch2); - i++; - } - } else -#endif - { - /* Unrecognised UTF-16 sequence */ - ch = '.'; - } - } - - char utf8_chr[6]; - put_data(buf, utf8_chr, encode_utf8(utf8_chr, ch)); - } - } else { - /* - * Call to the character-set subsystem to translate into - * whatever charset the terminal is currently configured in. - * - * Since the terminal doesn't currently support any multibyte - * character set other than UTF-8, we can assume here that - * there will be at most one output byte per input wchar_t. - * (But also we must allow space for the trailing NUL that - * wc_to_mb will write.) - */ - char *bufptr = strbuf_append(buf, len + 1); - int rv; - rv = wc_to_mb(term->ucsdata->line_codepage, 0, widebuf, len, - bufptr, len + 1, NULL, term->ucsdata); - strbuf_shrink_to(buf, rv < 0 ? 0 : rv); - } - - return buf; -} - -static strbuf *term_input_data_from_charset( - Terminal *term, int codepage, const char *str, int len) -{ - strbuf *buf; - - if (codepage < 0) { - buf = strbuf_new(); - put_data(buf, str, len); - } else { - int widesize = len * 2; /* allow for UTF-16 surrogates */ - wchar_t *widebuf = snewn(widesize, wchar_t); - int widelen = mb_to_wc(codepage, 0, str, len, widebuf, widesize); - buf = term_input_data_from_unicode(term, widebuf, widelen); - sfree(widebuf); - } - - return buf; -} - -static inline void term_bracketed_paste_start(Terminal *term) -{ - ptrlen seq = PTRLEN_LITERAL("\033[200~"); - if (term->ldisc) - ldisc_send(term->ldisc, seq.ptr, seq.len, false); - term->bracketed_paste_active = true; -} - -static inline void term_bracketed_paste_stop(Terminal *term) -{ - if (!term->bracketed_paste_active) - return; - - ptrlen seq = PTRLEN_LITERAL("\033[201~"); - if (term->ldisc) - ldisc_send(term->ldisc, seq.ptr, seq.len, false); - term->bracketed_paste_active = false; -} - -static inline void term_keyinput_internal( - Terminal *term, const void *buf, int len, bool interactive) -{ - if (term->srm_echo) { - /* - * Implement the terminal-level local echo behaviour that - * ECMA-48 specifies when terminal mode 12 is configured off - * (ESC[12l). In this mode, data input to the terminal via the - * keyboard is also added to the output buffer. But this - * doesn't apply to escape sequences generated as session - * input _within_ the terminal, e.g. in response to terminal - * query sequences, or the bracketing sequences of bracketed - * paste mode. Those will be sent directly via - * ldisc_send(term->ldisc, ...) and won't go through this - * function. - */ - - /* Mimic the special case of negative length in ldisc_send */ - int true_len = len >= 0 ? len : strlen(buf); - - bufchain_add(&term->inbuf, buf, true_len); - term_added_data(term); - } - if (interactive) - term_bracketed_paste_stop(term); - if (term->ldisc) - ldisc_send(term->ldisc, buf, len, interactive); - term_seen_key_event(term); -} - -unsigned long term_translate( - Terminal *term, struct term_utf8_decode *utf8, unsigned char c) -{ - if (in_utf(term)) { - switch (utf8->state) { - case 0: - if (c < 0x80) { - /* UTF-8 must be stateless so we ignore iso2022. */ - if (term->ucsdata->unitab_ctrl[c] != 0xFF) { - return term->ucsdata->unitab_ctrl[c]; - } else if ((term->utf8linedraw) && - (term->cset_attr[term->cset] == CSET_LINEDRW)) { - /* Linedraw characters are explicitly enabled */ - return c | CSET_LINEDRW; - } else { - return c | CSET_ASCII; - } - } else if ((c & 0xe0) == 0xc0) { - utf8->size = utf8->state = 1; - utf8->chr = (c & 0x1f); - } else if ((c & 0xf0) == 0xe0) { - utf8->size = utf8->state = 2; - utf8->chr = (c & 0x0f); - } else if ((c & 0xf8) == 0xf0) { - utf8->size = utf8->state = 3; - utf8->chr = (c & 0x07); - } else if ((c & 0xfc) == 0xf8) { - utf8->size = utf8->state = 4; - utf8->chr = (c & 0x03); - } else if ((c & 0xfe) == 0xfc) { - utf8->size = utf8->state = 5; - utf8->chr = (c & 0x01); - } else { - return UCSINVALID; - } - return UCSINCOMPLETE; - case 1: - case 2: - case 3: - case 4: - case 5: - if ((c & 0xC0) != 0x80) { - utf8->state = 0; - return UCSTRUNCATED; /* caller will then give us the - * same byte again */ - } - utf8->chr = (utf8->chr << 6) | (c & 0x3f); - if (--utf8->state) - return UCSINCOMPLETE; - - unsigned long t = utf8->chr; - - /* Is somebody trying to be evil! */ - if (t < 0x80 || - (t < 0x800 && utf8->size >= 2) || - (t < 0x10000 && utf8->size >= 3) || - (t < 0x200000 && utf8->size >= 4) || - (t < 0x4000000 && utf8->size >= 5)) - return UCSINVALID; - - /* Unicode line separator and paragraph separator are CR-LF */ - if (t == 0x2028 || t == 0x2029) - return 0x85; - - /* High controls are probably a Baaad idea too. */ - if (t < 0xA0) - return 0xFFFD; - - /* The UTF-16 surrogates are not nice either. */ - /* The standard give the option of decoding these: - * I don't want to! */ - if (t >= 0xD800 && t < 0xE000) - return UCSINVALID; - - /* ISO 10646 characters now limited to UTF-16 range. */ - if (t > 0x10FFFF) - return UCSINVALID; - - /* This is currently a TagPhobic application.. */ - if (t >= 0xE0000 && t <= 0xE007F) - return UCSINCOMPLETE; - - /* U+FEFF is best seen as a null. */ - if (t == 0xFEFF) - return UCSINCOMPLETE; - /* But U+FFFE is an error. */ - if (t == 0xFFFE || t == 0xFFFF) - return UCSINVALID; - - return t; - } - } else if (term->sco_acs && - (c!='\033' && c!='\012' && c!='\015' && c!='\b')) { - /* Are we in the nasty ACS mode? Note: no sco in utf mode. */ - if (term->sco_acs == 2) - c |= 0x80; - - return 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) - return term->ucsdata->unitab_ctrl[c]; - else - return c | CSET_LINEDRW; - break; - - case CSET_GBCHR: - /* If UK-ASCII, make the '#' a LineDraw Pound */ - if (c == '#') - return '}' | CSET_LINEDRW; - /* fall through */ - - case CSET_ASCII: - if (term->ucsdata->unitab_ctrl[c] != 0xFF) - return term->ucsdata->unitab_ctrl[c]; - else - return c | CSET_ASCII; - break; - case CSET_SCOACS: - if (c >= ' ') - return c | CSET_SCOACS; - break; - } - } - return c; -} - -/* - * 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; - size_t 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) { - ptrlen data = bufchain_prefix(&term->inbuf); - if (data.len > sizeof(localbuf)) - data.len = sizeof(localbuf); - memcpy(localbuf, data.ptr, data.len); - bufchain_consume(&term->inbuf, data.len); - nchars = data.len; - chars = localbuf; - assert(chars != NULL); - assert(nchars > 0); - } - c = *chars++; - nchars--; - - /* - * Optionally log the session traffic to a file. Useful for - * debugging and possibly also useful for actual logging. - */ - if (term->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; - } - } - - /* Do character-set translation. */ - if (term->termstate == TOPLEVEL) { - unsigned long t = term_translate(term, &term->utf8, c); - switch (t) { - case UCSINCOMPLETE: - continue; /* didn't complete a multibyte char */ - case UCSTRUNCATED: - unget = c; - /* fall through */ - case UCSINVALID: - c = UCSERR; - break; - default: - c = t; - 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 = 0; - 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->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) { - strbuf *buf = term_input_data_from_charset( - term, DEFAULT_CODEPAGE, - term->answerback, term->answerbacklen); - ldisc_send(term->ldisc, buf->s, buf->len, false); - strbuf_free(buf); - } - 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->bellovl_t) { - struct beeptime *tmp = term->beephead; - term->beephead = tmp->next; - sfree(tmp); - if (!term->beephead) - term->beeptail = NULL; - term->nbeeps--; - } - - if (term->bellovl && term->beep_overloaded && - ticks - term->lastbeep >= (unsigned)term->bellovl_s) { - /* - * If we're currently overloaded and the - * last beep was more than s seconds ago, - * leave overload mode. - */ - term->beep_overloaded = false; - } else if (term->bellovl && !term->beep_overloaded && - term->nbeeps >= term->bellovl_n) { - /* - * Now, if we have n or more beeps - * remaining in the queue, go into overload - * mode. - */ - term->beep_overloaded = true; - } - term->lastbeep = ticks; - - /* - * Perform an actual beep if we're not overloaded. - */ - if (!term->bellovl || !term->beep_overloaded) { - win_bell(term->win, term->beep); - - if (term->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)) - /* 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 = 0; - } - break; - case '\015': /* CR: Carriage return */ - term->curs.x = 0; - term->wrapnext = false; - seen_disp_event(term); - - if (term->crhaslf) { - if (term->curs.y == term->marg_b) - scroll(term, term->marg_t, term->marg_b, 1, true); - else if (term->curs.y < term->rows - 1) - term->curs.y++; - } - if (term->logctx) - logtraffic(term->logctx, (unsigned char) c, LGTYP_ASCII); - break; - case '\014': /* FF: Form feed */ - if (has_compat(SCOANSI)) { - move(term, 0, 0, 0); - erase_lots(term, false, false, true); - if (term->scroll_on_disp) - 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->lfhascr) - term->curs.x = 0; - term->wrapnext = false; - seen_disp_event(term); - 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 */ - term_display_graphic_char(term, c); - term->last_graphic_char = c; - 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 = 0; - 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; - term->esc_nargs = 1; - 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), false); - break; - case 'c': /* RIS: restore power-on settings */ - compatibility(VT100); - power_on(term, true); - if (term->ldisc) /* cause ldisc to notice changes */ - ldisc_echoedit_update(term->ldisc); - if (term->reset_132) { - if (!term->no_remote_resize) { - term->win_resize_pending = true; - term->win_resize_pending_w = 80; - term->win_resize_pending_h = term->rows; - term_schedule_update(term); - } - term->reset_132 = false; - } - if (term->scroll_on_disp) - 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); - check_line_size(term, ldata); - for (j = 0; j < term->cols; j++) { - copy_termchar(ldata, j, - &term->basic_erase_char); - ldata->chars[j].chr = 'E'; - } - ldata->lattr = LATTR_NORM; - } - if (term->scroll_on_disp) - 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; - termline *ldata; - - 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; - } - ldata = scrlineptr(term->curs.y); - check_line_size(term, ldata); - check_trust_status(term, ldata); - ldata->lattr = nlattr; - break; - } - /* GZD4: G0 designate 94-set */ - case ANSI('A', '('): - compatibility(VT100); - if (!term->no_remote_charset) - term->cset_attr[0] = CSET_GBCHR; - break; - case ANSI('B', '('): - compatibility(VT100); - if (!term->no_remote_charset) - term->cset_attr[0] = CSET_ASCII; - break; - case ANSI('0', '('): - compatibility(VT100); - if (!term->no_remote_charset) - term->cset_attr[0] = CSET_LINEDRW; - break; - case ANSI('U', '('): - compatibility(OTHER); - if (!term->no_remote_charset) - term->cset_attr[0] = CSET_SCOACS; - break; - /* G1D4: G1-designate 94-set */ - case ANSI('A', ')'): - compatibility(VT100); - if (!term->no_remote_charset) - term->cset_attr[1] = CSET_GBCHR; - break; - case ANSI('B', ')'): - compatibility(VT100); - if (!term->no_remote_charset) - term->cset_attr[1] = CSET_ASCII; - break; - case ANSI('0', ')'): - compatibility(VT100); - if (!term->no_remote_charset) - term->cset_attr[1] = CSET_LINEDRW; - break; - case ANSI('U', ')'): - compatibility(OTHER); - if (!term->no_remote_charset) - term->cset_attr[1] = CSET_SCOACS; - break; - /* DOCS: Designate other coding system */ - case ANSI('8', '%'): /* Old Linux code */ - case ANSI('G', '%'): - compatibility(OTHER); - if (!term->no_remote_charset) - term->utf = true; - break; - case ANSI('@', '%'): - compatibility(OTHER); - if (!term->no_remote_charset) - term->utf = false; - 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; - if (term->esc_args[term->esc_nargs - 1] <= - UINT_MAX / 10 && - term->esc_args[term->esc_nargs - 1] * 10 <= - UINT_MAX - c - '0') - term->esc_args[term->esc_nargs - 1] = - 10 * term->esc_args[term->esc_nargs - 1] + - c - '0'; - else - term->esc_args[term->esc_nargs - 1] = UINT_MAX; - } - 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 = 1; - else - term->esc_query = c; - term->termstate = SEEN_CSI; - } else -#define CLAMP(arg, lim) ((arg) = ((arg) > (lim)) ? (lim) : (arg)) - switch (ANSI(c, term->esc_query)) { - case 'A': /* CUU: move up N lines */ - CLAMP(term->esc_args[0], term->rows); - 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 */ - CLAMP(term->esc_args[0], term->rows); - move(term, term->curs.x, - term->curs.y + def(term->esc_args[0], 1), 1); - seen_disp_event(term); - break; - case 'b': /* REP: repeat previous grap */ - CLAMP(term->esc_args[0], term->rows * term->cols); - if (term->last_graphic_char) { - unsigned i; - for (i = 0; i < term->esc_args[0]; i++) - term_display_graphic_char( - term, term->last_graphic_char); - } - 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, - false); - break; - case 'a': /* HPR: move right N cols */ - compatibility(ANSI); - /* FALLTHROUGH */ - case 'C': /* CUF: Cursor right */ - CLAMP(term->esc_args[0], term->cols); - 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 */ - CLAMP(term->esc_args[0], term->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); - CLAMP(term->esc_args[0], term->rows); - 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); - CLAMP(term->esc_args[0], term->rows); - 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); - CLAMP(term->esc_args[0], term->cols); - 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); - CLAMP(term->esc_args[0], term->rows); - 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; - CLAMP(term->esc_args[0], term->rows); - CLAMP(term->esc_args[1], term->cols); - 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. */ - if (!term->no_remote_clearscroll) - term_clrsb(term); - } else { - i++; - if (i > 3) - i = 0; - erase_lots(term, false, !!(i & 2), !!(i & 1)); - } - if (term->scroll_on_disp) - 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); - CLAMP(term->esc_args[0], term->rows); - 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); - CLAMP(term->esc_args[0], term->rows); - 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); - CLAMP(term->esc_args[0], term->cols); - insch(term, def(term->esc_args[0], 1)); - seen_disp_event(term); - break; - case 'P': /* DCH: delete chars */ - compatibility(VT102); - CLAMP(term->esc_args[0], term->cols); - 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), false); - 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), - false); - } else if (term->esc_args[0] == 5) { - ldisc_send(term->ldisc, "\033[0n", 4, false); - } - } - break; - case 'h': /* SM: toggle modes to high */ - case ANSI_QUE('h'): - compatibility(VT100); - for (int 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); - char *printer; - if (term->esc_nargs != 1) break; - if (term->esc_args[0] == 5 && - (printer = conf_get_str(term->conf, - CONF_printer))[0]) { - term->printing = true; - term->only_printing = !term->esc_query; - term->print_state = 0; - term_print_setup(term, printer); - } 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); - for (int 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; - CLAMP(term->esc_args[0], term->rows); - CLAMP(term->esc_args[1], term->rows); - 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. - */ - for (int i = 0; i < term->esc_nargs; i++) - switch (def(term->esc_args[i], 0)) { - case 0: /* restore defaults */ - term->curr_attr = term->default_attr; - term->curr_truecolour = - term->basic_erase_char.truecolour; - break; - case 1: /* enable bold */ - compatibility(VT100AVO); - term->curr_attr |= ATTR_BOLD; - break; - case 2: /* enable dim */ - compatibility(OTHER); - term->curr_attr |= ATTR_DIM; - 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 9: /* enable strikethrough */ - term->curr_attr |= ATTR_STRIKE; - break; - case 10: /* SCO acs off */ - compatibility(SCOANSI); - if (term->no_remote_charset) break; - term->sco_acs = 0; break; - case 11: /* SCO acs on */ - compatibility(SCOANSI); - if (term->no_remote_charset) break; - term->sco_acs = 1; break; - case 12: /* SCO acs on, |0x80 */ - compatibility(SCOANSI); - if (term->no_remote_charset) break; - term->sco_acs = 2; break; - case 22: /* disable bold and dim */ - compatibility2(OTHER, VT220); - term->curr_attr &= ~(ATTR_BOLD | ATTR_DIM); - 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 29: /* disable strikethrough */ - term->curr_attr &= ~ATTR_STRIKE; - break; - case 30: - case 31: - case 32: - case 33: - case 34: - case 35: - case 36: - case 37: - /* foreground */ - term->curr_truecolour.fg.enabled = false; - term->curr_attr &= ~ATTR_FGMASK; - term->curr_attr |= - (term->esc_args[i] - 30)<curr_truecolour.fg.enabled = false; - term->curr_attr &= ~ATTR_FGMASK; - term->curr_attr |= - ((term->esc_args[i] - 90 + 8) - << ATTR_FGSHIFT); - break; - case 39: /* default-foreground */ - term->curr_truecolour.fg.enabled = false; - 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_truecolour.bg.enabled = false; - term->curr_attr &= ~ATTR_BGMASK; - term->curr_attr |= - (term->esc_args[i] - 40)<curr_truecolour.bg.enabled = false; - term->curr_attr &= ~ATTR_BGMASK; - term->curr_attr |= - ((term->esc_args[i] - 100 + 8) - << ATTR_BGSHIFT); - break; - case 49: /* default-background */ - term->curr_truecolour.bg.enabled = false; - term->curr_attr &= ~ATTR_BGMASK; - term->curr_attr |= ATTR_DEFBG; - break; - - /* - * 256-colour and true-colour - * sequences. A 256-colour - * foreground is selected by a - * sequence of 3 arguments in the - * form 38;5;n, where n is in the - * range 0-255. A true-colour RGB - * triple is selected by 5 args of - * the form 38;2;r;g;b. Replacing - * the initial 38 with 48 in both - * cases selects the same colour - * as the background. - */ - case 38: - 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); - term->curr_truecolour.fg = - optionalrgb_none; - i += 2; - } - if (i + 4 < term->esc_nargs && - term->esc_args[i + 1] == 2) { - parse_optionalrgb( - &term->curr_truecolour.fg, - term->esc_args + (i+2)); - i += 4; - } - break; - case 48: - 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); - term->curr_truecolour.bg = - optionalrgb_none; - i += 2; - } - if (i + 4 < term->esc_nargs && - term->esc_args[i+1] == 2) { - parse_optionalrgb( - &term->curr_truecolour.bg, - term->esc_args + (i+2)); - i += 4; - } - 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->no_remote_resize) { - term->win_resize_pending = true; - term->win_resize_pending_w = term->cols; - term->win_resize_pending_h = - def(term->esc_args[0], 24); - term_schedule_update(term); - } - 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 len; - char buf[80]; - const char *p; - case 1: - term->win_minimise_pending = true; - term->win_minimise_enable = false; - term_schedule_update(term); - break; - case 2: - term->win_minimise_pending = true; - term->win_minimise_enable = true; - term_schedule_update(term); - break; - case 3: - if (term->esc_nargs >= 3) { - if (!term->no_remote_resize) { - term->win_move_pending = true; - term->win_move_pending_x = - def(term->esc_args[1], 0); - term->win_move_pending_y = - def(term->esc_args[2], 0); - term_schedule_update(term); - } - } - 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 */ - term->win_zorder_pending = true; - term->win_zorder_top = true; - term_schedule_update(term); - break; - case 6: - /* move to bottom */ - term->win_zorder_pending = true; - term->win_zorder_top = false; - term_schedule_update(term); - break; - case 7: - term->win_refresh_pending = true; - term_schedule_update(term); - break; - case 8: - if (term->esc_nargs >= 3 && - !term->no_remote_resize) { - term->win_resize_pending = true; - term->win_resize_pending_w = - def(term->esc_args[2], - term->conf_width); - term->win_resize_pending_h = - def(term->esc_args[1], - term->conf_height); - term_schedule_update(term); - } - break; - case 9: - if (term->esc_nargs >= 2) { - term->win_maximise_pending = true; - term->win_maximise_enable = - term->esc_args[1]; - term_schedule_update(term); - } - break; - case 11: - if (term->ldisc) - ldisc_send(term->ldisc, term->minimised ? - "\033[2t" : "\033[1t", 4, - false); - break; - case 13: - if (term->ldisc) { - len = sprintf(buf, "\033[3;%u;%ut", - term->winpos_x, - term->winpos_y); - ldisc_send(term->ldisc, buf, len, false); - } - break; - case 14: - if (term->ldisc) { - len = sprintf(buf, "\033[4;%u;%ut", - term->winpixsize_y, - term->winpixsize_x); - ldisc_send(term->ldisc, buf, len, false); - } - break; - case 18: - if (term->ldisc) { - len = sprintf(buf, "\033[8;%d;%dt", - term->rows, term->cols); - ldisc_send(term->ldisc, buf, len, false); - } - 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->remote_qtitle_action != TITLE_NONE) { - if(term->remote_qtitle_action == TITLE_REAL) - p = term->icon_title; - else - p = EMPTY_WINDOW_TITLE; - len = strlen(p); - ldisc_send(term->ldisc, "\033]L", 3, - false); - ldisc_send(term->ldisc, p, len, false); - ldisc_send(term->ldisc, "\033\\", 2, - false); - } - break; - case 21: - if (term->ldisc && - term->remote_qtitle_action != TITLE_NONE) { - if(term->remote_qtitle_action == TITLE_REAL) - p = term->window_title; - else - p = EMPTY_WINDOW_TITLE; - len = strlen(p); - ldisc_send(term->ldisc, "\033]l", 3, - false); - ldisc_send(term->ldisc, p, len, false); - ldisc_send(term->ldisc, "\033\\", 2, - false); - } - break; - } - } - break; - case 'S': /* SU: Scroll up */ - CLAMP(term->esc_args[0], term->rows); - 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 */ - CLAMP(term->esc_args[0], term->rows); - 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->no_remote_resize) { - term->win_resize_pending = true; - term->win_resize_pending_w = term->cols; - term->win_resize_pending_h = - def(term->esc_args[0], term->conf_height); - term_schedule_update(term); - } - 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->no_remote_resize) { - term->win_resize_pending = true; - term->win_resize_pending_w = - def(term->esc_args[0], term->conf_width); - term->win_resize_pending_h = term->rows; - term_schedule_update(term); - } - 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); - CLAMP(term->esc_args[0], term->cols); - int n = def(term->esc_args[0], 1); - pos cursplus; - int p = term->curs.x; - termline *cline = scrlineptr(term->curs.y); - - check_trust_status(term, cline); - 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, false); - } - } - break; - case 'Z': { /* CBT */ - compatibility(OTHER); - CLAMP(term->esc_args[0], term->cols); - 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] < 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->curr_truecolour.fg = optionalrgb_none; - 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] < 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->curr_truecolour.bg = optionalrgb_none; - 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, "%u", 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->no_remote_resize) { - term->win_resize_pending = true; - term->win_resize_pending_w = - term->reset_132 ? 132 : 80; - term->win_resize_pending_h = 24; - term_schedule_update(term); - } - } -#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, false); - 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': - if (term->esc_args[term->esc_nargs-1] <= UINT_MAX / 10 && - term->esc_args[term->esc_nargs-1] * 10 <= UINT_MAX - c - '0') - term->esc_args[term->esc_nargs-1] = - 10 * term->esc_args[term->esc_nargs-1] + c - '0'; - else - term->esc_args[term->esc_nargs-1] = UINT_MAX; - break; - default: - /* - * _Most_ other characters here terminate the - * immediate parsing of the OSC sequence and go - * into OSC_STRING state, but we deal with a - * couple of exceptions first. - */ - if (c == 'L' && term->esc_args[0] == 2) { - /* - * Grotty hack to support xterm and DECterm title - * sequences concurrently. - */ - term->esc_args[0] = 1; - } else if (c == ';' && term->esc_nargs == 1 && - term->esc_args[0] == 4) { - /* - * xterm's OSC 4 sequence to query the current - * RGB value of a colour takes a second - * numeric argument which is easiest to parse - * using the existing system rather than in - * do_osc. - */ - term->esc_args[term->esc_nargs++] = 0; - } else { - 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) { - unsigned oscp_index = term->osc_string[0]; - assert(oscp_index < OSCP_NCOLOURS); - unsigned osc4_index = - colour_indices_oscp_to_osc4[oscp_index]; - - rgb *value = &term->subpalettes[SUBPAL_SESSION].values[ - osc4_index]; - value->r = term->osc_string[1] * 16 + term->osc_string[2]; - value->g = term->osc_string[3] * 16 + term->osc_string[4]; - value->b = term->osc_string[5] * 16 + term->osc_string[6]; - term->subpalettes[SUBPAL_SESSION].present[ - osc4_index] = true; - - palette_rebuild(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': - if (term->esc_args[0] <= UINT_MAX / 10 && - term->esc_args[0] * 10 <= UINT_MAX - c - '0') - term->esc_args[0] = 10 * term->esc_args[0] + c - '0'; - else - term->esc_args[0] = UINT_MAX; - 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); - if (term->scroll_on_disp) - 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, false); - 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->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); - if (term->scroll_on_disp) - 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); - if (term->scroll_on_disp) - 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 = true; - break; - case 'w': /* Autowrap off */ - /* compatibility(ATARI) */ - term->wrap = false; - break; - - case 'R': - /* compatibility(OTHER) */ - term->vt52_bold = false; - term->curr_attr = ATTR_DEFAULT; - term->curr_truecolour.fg = optionalrgb_none; - term->curr_truecolour.bg = optionalrgb_none; - 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->logflush && term->logctx) - logflush(term->logctx); -} - -/* - * Small subroutine to parse three consecutive escape-sequence - * arguments representing a true-colour RGB triple into an - * optionalrgb. - */ -static void parse_optionalrgb(optionalrgb *out, unsigned *values) -{ - out->enabled = true; - out->r = values[0] < 256 ? values[0] : 0; - out->g = values[1] < 256 ? values[1] : 0; - out->b = values[2] < 256 ? values[2] : 0; -} - -/* - * 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 bool term_bidi_cache_hit(Terminal *term, int line, - termchar *lbefore, int width, bool trusted) -{ - 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 */ - - if (term->pre_bidi_cache[line].trusted != trusted) - return false; /* line has wrong trust state */ - - 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, bool trusted) -{ - size_t i, j; - - if (!term->pre_bidi_cache || term->bidi_cache_size <= line) { - j = term->bidi_cache_size; - sgrowarray(term->pre_bidi_cache, term->bidi_cache_size, line); - 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].trusted = false; - term->post_bidi_cache[j].trusted = false; - 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].trusted = trusted; - term->pre_bidi_cache[line].chars = snewn(size, termchar); - term->post_bidi_cache[line].width = width; - term->post_bidi_cache[line].trusted = trusted; - 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 = j = 0; j < width; j += wcTo[i].nchars, i++) { - int p = wcTo[i].index; - - if (p != BIDI_CHAR_INDEX_NONE) { - assert(0 <= p && p < width); - - for (int x = 0; x < wcTo[i].nchars; x++) { - term->post_bidi_cache[line].backward[j+x] = p+x; - term->post_bidi_cache[line].forward[p+x] = j+x; - } - } - } -} - -/* - * 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->no_bidi || !term->no_arabicshaping || - (ldata->trusted && term->cols > TRUST_SIGIL_WIDTH)) { - - if (!term_bidi_cache_hit(term, scr_y, ldata->chars, term->cols, - ldata->trusted)) { - - 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; itcols ; it++) - { - unsigned long uc = (ldata->chars[it].chr); - - switch (uc & CSET_MASK) { - case CSET_LINEDRW: - if (!term->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 = - (unsigned int)uc; - term->wcFrom[it].index = it; - term->wcFrom[it].nchars = 1; - } - - if (ldata->trusted && term->cols > TRUST_SIGIL_WIDTH) { - memmove( - term->wcFrom + TRUST_SIGIL_WIDTH, term->wcFrom, - (term->cols - TRUST_SIGIL_WIDTH) * sizeof(*term->wcFrom)); - for (it = 0; it < TRUST_SIGIL_WIDTH; it++) { - term->wcFrom[it].origwc = term->wcFrom[it].wc = - (it == 0 ? TRUST_SIGIL_CHAR : - it == 1 ? UCSWIDE : ' '); - term->wcFrom[it].index = BIDI_CHAR_INDEX_NONE; - term->wcFrom[it].nchars = 1; - } - } - - int nbc = 0; - for (it = 0; it < term->cols; it++) { - term->wcFrom[nbc] = term->wcFrom[it]; - if (it+1 < term->cols && term->wcFrom[it+1].wc == UCSWIDE) { - term->wcFrom[nbc].nchars++; - it++; - } - nbc++; - } - - if(!term->no_bidi) - do_bidi(term->wcFrom, nbc); - - if(!term->no_arabicshaping) { - do_shape(term->wcFrom, term->wcTo, nbc); - } else { - /* If we're not calling do_shape, we must copy the - * data into wcTo anyway, unchanged */ - memcpy(term->wcTo, term->wcFrom, nbc * sizeof(*term->wcTo)); - } - - 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); - - int opos = 0; - for (it=0; itwcTo[it].index; - for (int j = 0; j < term->wcTo[it].nchars; j++) { - if (ipos != BIDI_CHAR_INDEX_NONE) { - term->ltemp[opos] = ldata->chars[ipos]; - if (term->ltemp[opos].cc_next) - term->ltemp[opos].cc_next -= opos - ipos; - - if (j > 0) - term->ltemp[opos].chr = UCSWIDE; - else if (term->wcTo[it].origwc != term->wcTo[it].wc) - term->ltemp[opos].chr = term->wcTo[it].wc; - } else { - term->ltemp[opos] = term->basic_erase_char; - term->ltemp[opos].chr = - j > 0 ? UCSWIDE : term->wcTo[it].origwc; - } - opos++; - } - } - assert(opos == term->cols); - term_bidi_cache_store(term, scr_y, ldata->chars, - term->ltemp, term->wcTo, - term->cols, ldata->size, ldata->trusted); - - lchars = term->ltemp; - } else { - lchars = term->post_bidi_cache[scr_y].chars; - } - } else { - lchars = NULL; - } - - return lchars; -} - -static void do_paint_draw(Terminal *term, termline *ldata, int x, int y, - wchar_t *ch, int ccount, - unsigned long attr, truecolour tc) -{ - if (ch[0] == TRUST_SIGIL_CHAR) { - assert(ldata->trusted); - assert(ccount == 1); - assert(attr & ATTR_WIDE); - wchar_t tch[2]; - tch[0] = tch[1] = L' '; - win_draw_text(term->win, x, y, tch, 2, term->basic_erase_char.attr, - ldata->lattr, term->basic_erase_char.truecolour); - win_draw_trust_sigil(term->win, x, y); - } else { - win_draw_text(term->win, x, y, ch, ccount, attr, ldata->lattr, tc); - if (attr & (TATTR_ACTCURS | TATTR_PASCURS)) - win_draw_cursor(term->win, x, y, ch, ccount, - attr, ldata->lattr, tc); - } -} - -/* - * Given a context, update the window. - */ -static void do_paint(Terminal *term) -{ - int i, j, our_curs_y, our_curs_x; - int rv, cursor; - pos scrpos; - wchar_t *ch; - size_t chlen; - 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, - * blinkpc, blink_is_real, tblinker, - * curs.y, curs.x, cblinker, blink_cur, cursor_on, has_focus, wrapnext - */ - - /* Has the cursor position or type changed ? */ - if (term->cursor_on) { - if (term->has_focus) { - if (term->cblinker || !term->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; - - /* The normal screen data */ - for (i = 0; i < term->rows; i++) { - termline *ldata; - termchar *lchars; - bool dirty_line, dirty_run, selected; - unsigned long attr = 0, cset = 0; - int start = 0; - int ccount = 0; - bool last_run_dirty = false; - int laststart; - bool dirtyrect; - int *backward; - truecolour tc; - - 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->ansi_colour) - tattr = (tattr & ~(ATTR_FGMASK | ATTR_BGMASK)) | - ATTR_DEFFG | ATTR_DEFBG; - - if (!term->xterm_256_colour) { - int colour; - colour = (tattr & ATTR_FGMASK) >> ATTR_FGSHIFT; - if (colour >= 16 && colour < 256) - tattr = (tattr &~ ATTR_FGMASK) | ATTR_DEFFG; - colour = (tattr & ATTR_BGMASK) >> ATTR_BGSHIFT; - if (colour >= 16 && colour < 256) - tattr = (tattr &~ ATTR_BGMASK) | ATTR_DEFBG; - } - - if (term->true_colour) { - tc = d->truecolour; - } else { - tc.fg = tc.bg = optionalrgb_none; - } - - 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) && - posPle_left(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 && - win_char_width(term->win, 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; - newline[j].truecolour = tc; - /* 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; - - tc = term->erase_char.truecolour; - for (j = 0; j < term->cols; j++) { - unsigned long tattr, tchar; - bool 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; - - if (!truecolour_equal(newline[j].truecolour, tc)) - break_run = true; - -#ifdef USES_VTLINE_HACK - /* Special hack for VT100 Linedraw glyphs */ - if ((tchar >= 0x23BA && tchar <= 0x23BD) || - (j > 0 && (newline[j-1].chr >= 0x23BA && - newline[j-1].chr <= 0x23BD))) - break_run = true; -#endif - - /* - * Separate out sequences of characters that have the - * same CSET, if that CSET is a magic one. - */ - 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; - - /* - * Break on both sides of a trust sigil. - */ - if (d->chr == TRUST_SIGIL_CHAR || - (j >= 2 && d[-1].chr == UCSWIDE && - d[-2].chr == TRUST_SIGIL_CHAR)) - 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 && - truecolour_equal( - term->disptext[i]->chars[j].truecolour, tc)) - break_run = true; - else if (!dirty_run && ccount == 1) - break_run = true; - } - - if (break_run) { - if ((dirty_run || last_run_dirty) && ccount > 0) - do_paint_draw(term, ldata, start, i, ch, ccount, attr, tc); - start = j; - ccount = 0; - attr = tattr; - tc = newline[j].truecolour; - 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; - } - - sgrowarrayn(ch, chlen, ccount, 2); - -#ifdef PLATFORM_IS_UTF16 - if (tchar > 0x10000 && tchar < 0x110000) { - ch[ccount++] = (wchar_t) HIGH_SURROGATE_OF(tchar); - ch[ccount++] = (wchar_t) LOW_SURROGATE_OF(tchar); - } else -#endif /* PLATFORM_IS_UTF16 */ - ch[ccount++] = (wchar_t) tchar; - - if (d->cc_next) { - termchar *dd = d; - - 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; - } - - sgrowarrayn(ch, chlen, ccount, 2); - -#ifdef PLATFORM_IS_UTF16 - if (schar > 0x10000 && schar < 0x110000) { - ch[ccount++] = (wchar_t) HIGH_SURROGATE_OF(schar); - ch[ccount++] = (wchar_t) LOW_SURROGATE_OF(schar); - } else -#endif /* PLATFORM_IS_UTF16 */ - ch[ccount++] = (wchar_t) schar; - } - - attr |= TATTR_COMBINING; - } - - if (do_copy) { - copy_termchar(term->disptext[i], j, d); - term->disptext[i]->chars[j].chr = tchar; - term->disptext[i]->chars[j].attr = tattr; - term->disptext[i]->chars[j].truecolour = tc; - 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_paint_draw(term, ldata, start, i, ch, ccount, attr, tc); - - 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, - int left, int top, int right, int bottom, bool 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); - } 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); - - 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; - term->win_scrollbar_update_pending = true; - term_schedule_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 { - size_t bufsize; /* amount of allocated space in textbuf/attrbuf */ - size_t 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 */ - truecolour *tcbuf; /* buffer for copied colours */ - truecolour *tcptr; /* = tcbuf + bufpos */ -} clip_workbuf; - -static void clip_addchar(clip_workbuf *b, wchar_t chr, int attr, truecolour tc) -{ - if (b->bufpos >= b->bufsize) { - sgrowarray(b->textbuf, b->bufsize, b->bufpos); - b->textptr = b->textbuf + b->bufpos; - b->attrbuf = sresize(b->attrbuf, b->bufsize, int); - b->attrptr = b->attrbuf + b->bufpos; - b->tcbuf = sresize(b->tcbuf, b->bufsize, truecolour); - b->tcptr = b->tcbuf + b->bufpos; - } - *b->textptr++ = chr; - *b->attrptr++ = attr; - *b->tcptr++ = tc; - b->bufpos++; -} - -static void clipme(Terminal *term, pos top, pos bottom, bool rect, bool desel, - const int *clipboards, int n_clipboards) -{ - clip_workbuf buf; - int old_top_x; - int attr; - truecolour tc; - - buf.bufsize = 5120; - buf.bufpos = 0; - buf.textptr = buf.textbuf = snewn(buf.bufsize, wchar_t); - buf.attrptr = buf.attrbuf = snewn(buf.bufsize, int); - buf.tcptr = buf.tcbuf = snewn(buf.bufsize, truecolour); - - old_top_x = top.x; /* needed for rect==1 */ - - while (poslt(top, bottom)) { - bool 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->trusted) { - /* A wrapped line with a trust sigil on it terminates - * a few characters earlier. */ - nlpos.x = (nlpos.x < TRUST_SIGIL_WIDTH ? 0 : - nlpos.x - TRUST_SIGIL_WIDTH); - } - 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, "", (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; - tc = ldata->chars[x].truecolour; - - switch (uc & CSET_MASK) { - case CSET_LINEDRW: - if (!term->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, tc); - - 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, term->basic_erase_char.truecolour); - } - top.y++; - top.x = rect ? old_top_x : 0; - - unlineptr(ldata); - } -#if SELECTION_NUL_TERMINATED - clip_addchar(&buf, 0, 0, term->basic_erase_char.truecolour); -#endif - /* Finally, transfer all that to the clipboard(s). */ - { - int i; - bool clip_local = false; - for (i = 0; i < n_clipboards; i++) { - if (clipboards[i] == CLIP_LOCAL) { - clip_local = true; - } else if (clipboards[i] != CLIP_NULL) { - win_clip_write( - term->win, clipboards[i], buf.textbuf, buf.attrbuf, - buf.tcbuf, buf.bufpos, desel); - } - } - if (clip_local) { - sfree(term->last_selected_text); - sfree(term->last_selected_attr); - sfree(term->last_selected_tc); - term->last_selected_text = buf.textbuf; - term->last_selected_attr = buf.attrbuf; - term->last_selected_tc = buf.tcbuf; - term->last_selected_len = buf.bufpos; - } else { - sfree(buf.textbuf); - sfree(buf.attrbuf); - sfree(buf.tcbuf); - } - } -} - -void term_copyall(Terminal *term, const int *clipboards, int n_clipboards) -{ - 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, false, true, clipboards, n_clipboards); -} - -static void paste_from_clip_local(void *vterm) -{ - Terminal *term = (Terminal *)vterm; - term_do_paste(term, term->last_selected_text, term->last_selected_len); -} - -void term_request_copy(Terminal *term, const int *clipboards, int n_clipboards) -{ - int i; - for (i = 0; i < n_clipboards; i++) { - assert(clipboards[i] != CLIP_LOCAL); - if (clipboards[i] != CLIP_NULL) { - win_clip_write(term->win, clipboards[i], - term->last_selected_text, term->last_selected_attr, - term->last_selected_tc, term->last_selected_len, - false); - } - } -} - -void term_request_paste(Terminal *term, int clipboard) -{ - switch (clipboard) { - case CLIP_NULL: - /* Do nothing: CLIP_NULL never has data in it. */ - break; - case CLIP_LOCAL: - queue_toplevel_callback(paste_from_clip_local, term); - break; - default: - win_clip_request_paste(term->win, clipboard); - break; - } -} - -/* - * 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; -} - -static int line_cols(Terminal *term, termline *ldata) -{ - int cols = term->cols; - if (ldata->trusted) { - cols -= TRUST_SIGIL_WIDTH; - } - if (ldata->lattr & LATTR_WRAPPED2) - cols--; - if (cols < 0) - cols = 0; - return cols; -} - -/* - * 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 + line_cols(term, ldata); - 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 = line_cols(term, ldata); - if (p.x < maxcols-1) { - if (wordtype(term, UCSGET(ldata->chars, p.x+1)) == wvalue) - p.x++; - else - break; - } else { - if (p.y+1 < term->rows && - (ldata->lattr & LATTR_WRAPPED)) { - termline *ldata2; - ldata2 = lineptr(p.y+1); - if (wordtype(term, UCSGET(ldata2->chars, 0)) - == wvalue) { - p.x = 0; - 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 = line_cols(term, ldata2); - 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); - } -} - -static void term_paste_callback(void *vterm) -{ - Terminal *term = (Terminal *)vterm; - - if (term->paste_len == 0) - return; - - while (term->paste_pos < term->paste_len) { - int n = 0; - while (n + term->paste_pos < term->paste_len) { - if (term->paste_buffer[term->paste_pos + n++] == '\015') - break; - } - if (term->ldisc) { - strbuf *buf = term_input_data_from_unicode( - term, term->paste_buffer + term->paste_pos, n); - term_keyinput_internal(term, buf->s, buf->len, false); - strbuf_free(buf); - } - term->paste_pos += n; - - if (term->paste_pos < term->paste_len) { - queue_toplevel_callback(term_paste_callback, term); - return; - } - } - term_bracketed_paste_stop(term); - sfree(term->paste_buffer); - term->paste_buffer = NULL; - term->paste_len = 0; -} - -/* - * Specialist string compare function. Returns true if the buffer of - * alen wide characters starting at a has as a prefix the buffer of - * blen characters starting at b. - */ -static bool wstartswith(const wchar_t *a, size_t alen, - const wchar_t *b, size_t blen) -{ - return alen >= blen && !wcsncmp(a, b, blen); -} - -void term_do_paste(Terminal *term, const wchar_t *data, int len) -{ - const wchar_t *p; - bool paste_controls = conf_get_bool(term->conf, CONF_paste_controls); - - /* - * Pasting data into the terminal counts as a keyboard event (for - * purposes of the 'Reset scrollback on keypress' config option), - * unless the paste is zero-length. - */ - if (len == 0) - return; - term_seen_key_event(term); - - if (term->paste_buffer) - sfree(term->paste_buffer); - term->paste_pos = term->paste_len = 0; - term->paste_buffer = snewn(len + 12, wchar_t); - - if (term->bracketed_paste) - term_bracketed_paste_start(term); - - p = data; - while (p < data + len) { - wchar_t wc = *p++; - - if (wc == sel_nl[0] && - wstartswith(p-1, data+len-(p-1), sel_nl, sel_nl_sz)) { - /* - * This is the (platform-dependent) sequence that the host - * OS uses to represent newlines in clipboard data. - * Normalise it to a press of CR. - */ - p += sel_nl_sz - 1; - wc = '\015'; - } - - if ((wc & ~(wint_t)0x9F) == 0) { - /* - * This is a control code, either in the range 0x00-0x1F - * or 0x80-0x9F. We reject all of these in pastecontrols - * mode, except for a small set of permitted ones. - */ - if (!paste_controls) { - /* In line with xterm 292, accepted control chars are: - * CR, LF, tab, backspace. (And DEL, i.e. 0x7F, but - * that's permitted by virtue of not matching the bit - * mask that got us into this if statement, so we - * don't have to permit it here. */ - static const unsigned mask = - (1<<13) | (1<<10) | (1<<9) | (1<<8); - - if (wc > 15 || !((mask >> wc) & 1)) - continue; - } - - if (wc == '\033' && term->bracketed_paste && - wstartswith(p-1, data+len-(p-1), L"\033[201~", 6)) { - /* - * Also, in bracketed-paste mode, reject the ESC - * character that begins the end-of-paste sequence. - */ - continue; - } - } - - term->paste_buffer[term->paste_len++] = wc; - } - - /* Assume a small paste will be OK in one go. */ - if (term->paste_len < 256) { - if (term->ldisc) { - strbuf *buf = term_input_data_from_unicode( - term, term->paste_buffer, term->paste_len); - term_keyinput_internal(term, buf->s, buf->len, false); - strbuf_free(buf); - } - if (term->paste_buffer) - sfree(term->paste_buffer); - term_bracketed_paste_stop(term); - term->paste_buffer = NULL; - term->paste_pos = term->paste_len = 0; - } - - queue_toplevel_callback(term_paste_callback, term); -} - -void term_mouse(Terminal *term, Mouse_Button braw, Mouse_Button bcooked, - Mouse_Action a, int x, int y, bool shift, bool ctrl, bool alt) -{ - pos selpoint; - termline *ldata; - bool raw_mouse = (term->xterm_mouse && - !term->no_mouse_rep && - !(term->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 && !raw_mouse && term->seltype != RECTANGULAR) { - /* - * When we're using the mouse for normal raster-based - * selection, dragging off the left edge of a terminal row - * is treated the same as the right-hand end of the - * previous row, in that it's considered to identify a - * point _before_ the first character on row y. - * - * But if the mouse action is going to be used for - * anything else - rectangular selection, or xterm mouse - * tracking - then we disable this special treatment. - */ - 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; - bool wheel; - char abuf[32]; - int len = 0; - - if (term->ldisc) { - - switch (braw) { - case MBT_LEFT: - encstate = 0x00; /* left button down */ - wheel = false; - break; - case MBT_MIDDLE: - encstate = 0x01; - wheel = false; - break; - case MBT_RIGHT: - encstate = 0x02; - wheel = false; - break; - case MBT_WHEEL_UP: - encstate = 0x40; - wheel = true; - break; - case MBT_WHEEL_DOWN: - encstate = 0x41; - wheel = true; - break; - default: - return; - } - if (wheel) { - /* For mouse wheel buttons, we only ever expect to see - * MA_CLICK actions, and we don't try to keep track of - * the buttons being 'pressed' (since without matching - * click/release pairs that's pointless). */ - if (a != MA_CLICK) - return; - } else switch (a) { - case MA_DRAG: - if (term->xterm_mouse == 1) - return; - encstate += 0x20; - break; - case MA_RELEASE: - /* If multiple extensions are enabled, the xterm 1006 is used, so it's okay to check for only that */ - if (!term->xterm_extended_mouse) - encstate = 0x03; - term->mouse_is_down = 0; - break; - case MA_CLICK: - if (term->mouse_is_down == braw) - return; - term->mouse_is_down = braw; - break; - default: - return; - } - if (shift) - encstate += 0x04; - if (ctrl) - encstate += 0x10; - r = y + 1; - c = x + 1; - - /* Check the extensions in decreasing order of preference. Encoding the release event above assumes that 1006 comes first. */ - if (term->xterm_extended_mouse) { - len = sprintf(abuf, "\033[<%d;%d;%d%c", encstate, c, r, a == MA_RELEASE ? 'm' : 'M'); - } else if (term->urxvt_extended_mouse) { - len = sprintf(abuf, "\033[%d;%d;%dM", encstate + 32, c, r); - } else if (c <= 223 && r <= 223) { - len = sprintf(abuf, "\033[M%c%c%c", encstate + 32, c + 32, r + 32); - } - if (len > 0) - ldisc_send(term->ldisc, abuf, len, false); - } - return; - } - - /* - * Set the selection type (rectangular or normal) at the start - * of a selection attempt, from the state of Alt. - */ - if (!alt ^ !term->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 (a == MA_DRAG && - (term->selstate == NO_SELECTION || term->selstate == SELECTED)) { - /* - * This can happen if a front end has passed us a MA_DRAG - * without a prior MA_CLICK. OS X GTK does so, for - * example, if the initial button press was eaten by the - * WM when it activated the window in the first place. The - * nicest thing to do in this situation is to ignore - * further drags, and wait for the user to click in the - * window again properly if they want to select. - */ - return; - } - 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->mouse_select_clipboards, - term->n_mouse_select_clipboards); - 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 - )) { - term_request_paste(term, term->mouse_paste_clipboard); - } - - /* - * Since terminal output is suppressed during drag-selects, we - * should make sure to write any pending output if one has just - * finished. - */ - if (term->selstate != DRAGGING) - term_out(term); - term_schedule_update(term); -} - -int format_arrow_key(char *buf, Terminal *term, int xkey, bool ctrl) -{ - char *p = buf; - - if (term->vt52_mode) - p += sprintf(p, "\x1B%c", xkey); - else { - bool app_flg = (term->app_cursor_keys && !term->no_applic_c); -#if 0 - /* - * RDB: VT100 & VT102 manuals both state the app cursor - * keys only work if the app keypad is on. - * - * 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(p, "\x1BO%c", xkey); - else - p += sprintf(p, "\x1B[%c", xkey); - } - - return p - buf; -} - -int format_function_key(char *buf, Terminal *term, int key_number, - bool shift, bool ctrl) -{ - char *p = buf; - - static const int key_number_to_tilde_code[] = { - -1, /* no such key as F0 */ - 11, 12, 13, 14, 15, /*gap*/ 17, 18, 19, 20, 21, /*gap*/ - 23, 24, 25, 26, /*gap*/ 28, 29, /*gap*/ 31, 32, 33, 34, - }; - - assert(key_number > 0); - assert(key_number < lenof(key_number_to_tilde_code)); - - int index = (shift && key_number <= 10) ? key_number + 10 : key_number; - int code = key_number_to_tilde_code[index]; - - if (term->funky_type == FUNKY_SCO) { - /* SCO function keys */ - static const char sco_codes[] = - "MNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz@[\\]^_`{"; - index = (key_number >= 1 && key_number <= 12) ? key_number - 1 : 0; - if (shift) index += 12; - if (ctrl) index += 24; - p += sprintf(p, "\x1B[%c", sco_codes[index]); - } else if ((term->vt52_mode || term->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(p, "\x1B%c", code + 'P' - 11 - offt); - else - p += sprintf(p, "\x1BO%c", code + 'P' - 11 - offt); - } else if (term->funky_type == FUNKY_LINUX && code >= 11 && code <= 15) { - p += sprintf(p, "\x1B[[%c", code + 'A' - 11); - } else if (term->funky_type == FUNKY_XTERM && code >= 11 && code <= 14) { - if (term->vt52_mode) - p += sprintf(p, "\x1B%c", code + 'P' - 11); - else - p += sprintf(p, "\x1BO%c", code + 'P' - 11); - } else { - p += sprintf(p, "\x1B[%d~", code); - } - - return p - buf; -} - -int format_small_keypad_key(char *buf, Terminal *term, SmallKeypadKey key) -{ - char *p = buf; - - int code; - switch (key) { - case SKK_HOME: code = 1; break; - case SKK_INSERT: code = 2; break; - case SKK_DELETE: code = 3; break; - case SKK_END: code = 4; break; - case SKK_PGUP: code = 5; break; - case SKK_PGDN: code = 6; break; - default: unreachable("bad small keypad key enum value"); - } - - /* Reorder edit keys to physical order */ - if (term->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(p, "\x1B%c", " HLMEIG"[code]); - } else if (term->funky_type == FUNKY_SCO) { - static const char codes[] = "HL.FIG"; - if (code == 3) { - *p++ = '\x7F'; - } else { - p += sprintf(p, "\x1B[%c", codes[code-1]); - } - } else if ((code == 1 || code == 4) && term->rxvt_homeend) { - p += sprintf(p, code == 1 ? "\x1B[H" : "\x1BOw"); - } else { - p += sprintf(p, "\x1B[%d~", code); - } - - return p - buf; -} - -int format_numeric_keypad_key(char *buf, Terminal *term, char key, - bool shift, bool ctrl) -{ - char *p = buf; - bool app_keypad = (term->app_keypad_keys && !term->no_applic_k); - - if (term->nethack_keypad && (key >= '1' && key <= '9')) { - static const char nh_base[] = "bjnh.lyku"; - char c = nh_base[key - '1']; - if (ctrl && c != '.') - c &= 0x1F; - else if (shift && c != '.') - c += 'A'-'a'; - *p++ = c; - } else { - int xkey = 0; - - if (term->funky_type == FUNKY_VT400 || - (term->funky_type <= FUNKY_LINUX && app_keypad)) { - switch (key) { - case 'G': xkey = 'P'; break; - case '/': xkey = 'Q'; break; - case '*': xkey = 'R'; break; - case '-': xkey = 'S'; break; - } - } - - if (app_keypad) { - switch (key) { - 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; - case '\r': xkey = 'M'; break; - - case '+': - /* - * 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... - */ - if (term->funky_type == FUNKY_XTERM) - xkey = shift ? 'l' : 'k'; - else - xkey = shift ? 'm' : 'l'; - break; - - case '/': - if (term->funky_type == FUNKY_XTERM) - xkey = 'o'; - break; - case '*': - if (term->funky_type == FUNKY_XTERM) - xkey = 'j'; - break; - case '-': - if (term->funky_type == FUNKY_XTERM) - xkey = 'm'; - break; - } - } - - if (xkey) { - if (term->vt52_mode) { - if (xkey >= 'P' && xkey <= 'S') - p += sprintf(p, "\x1B%c", xkey); - else - p += sprintf(p, "\x1B?%c", xkey); - } else - p += sprintf(p, "\x1BO%c", xkey); - } - } - - return p - buf; -} - -void term_keyinputw(Terminal *term, const wchar_t *widebuf, int len) -{ - strbuf *buf = term_input_data_from_unicode(term, widebuf, len); - if (buf->len) - term_keyinput_internal(term, buf->s, buf->len, true); - strbuf_free(buf); -} - -void term_keyinput(Terminal *term, int codepage, const void *str, int len) -{ - if (codepage < 0 || codepage == term->ucsdata->line_codepage) { - /* - * This text needs no translation, either because it's already - * in the right character set, or because we got the special - * codepage value -1 from our caller which means 'this data - * should be charset-agnostic, just send it raw' (for really - * simple things like control characters). - */ - term_keyinput_internal(term, str, len, true); - } else { - strbuf *buf = term_input_data_from_charset(term, codepage, str, len); - if (buf->len) - term_keyinput_internal(term, buf->s, buf->len, true); - strbuf_free(buf); - } -} - -void term_nopaste(Terminal *term) -{ - if (term->paste_len == 0) - return; - sfree(term->paste_buffer); - term_bracketed_paste_stop(term); - 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_lost_clipboard_ownership(Terminal *term, int clipboard) -{ - if (!(term->n_mouse_select_clipboards > 1 && - clipboard == term->mouse_select_clipboards[1])) - return; - - deselect(term); - term_update(term); - - /* - * Since terminal output is suppressed during drag-selects, we - * should make sure to write any pending output if one has just - * finished. - */ - if (term->selstate != DRAGGING) - term_out(term); -} - -static void term_added_data(Terminal *term) -{ - 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; - } -} - -size_t term_data(Terminal *term, const void *data, size_t len) -{ - bufchain_add(&term->inbuf, data, len); - term_added_data(term); - - /* - * 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; -} - -void term_provide_logctx(Terminal *term, LogContext *logctx) -{ - term->logctx = logctx; -} - -void term_set_focus(Terminal *term, bool 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) -{ - const char *val = NULL; - if (strcmp(mode, "ERASE") == 0) { - val = term->bksp_is_delete ? "^?" : "^H"; - } else if (strcmp(mode, "IUTF8") == 0) { - val = (term->ucsdata->line_codepage == CP_UTF8) ? "yes" : "no"; - } - /* FIXME: perhaps we should set ONLCR based on lfhascr as well? */ - /* FIXME: or ECHO and friends based on local echo state? */ - return dupstr(val); -} - -struct term_userpass_state { - size_t curr_prompt; - bool done_prompt; /* printed out prompt yet? */ -}; - -/* Tiny wrapper to make it easier to write lots of little strings */ -static inline void term_write(Terminal *term, ptrlen data) -{ - term_data(term, data.ptr, data.len); -} - -/* - * Signal that a prompts_t is done. This involves sending a - * notification to the caller, and also turning off our own callback - * that listens for more data arriving in the ldisc's input queue. - */ -static inline int signal_prompts_t(Terminal *term, prompts_t *p, int result) -{ - assert(p->callback && "Asynchronous userpass input requires a callback"); - queue_toplevel_callback(p->callback, p->callback_ctx); - ldisc_enable_prompt_callback(term->ldisc, NULL); - p->idata = result; - return result; -} - -/* - * Process some terminal data in the course of username/password - * input. - */ -int term_get_userpass_input(Terminal *term, prompts_t *p) -{ - if (!term->ldisc) { - /* Can't handle interactive prompts without an ldisc */ - return signal_prompts_t(term, p, 0); - } - - if (p->idata >= 0) { - /* We've already finished these prompts, so return the same - * result again */ - return p->idata; - } - - 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); - p->idata = -1; - s->curr_prompt = 0; - s->done_prompt = false; - /* We only print the `name' caption if we have to... */ - if (p->name_reqd && p->name) { - ptrlen plname = ptrlen_from_asciz(p->name); - term_write(term, plname); - if (!ptrlen_endswith(plname, PTRLEN_LITERAL("\n"), NULL)) - term_write(term, PTRLEN_LITERAL("\r\n")); - } - /* ...but we always print any `instruction'. */ - if (p->instruction) { - ptrlen plinst = ptrlen_from_asciz(p->instruction); - term_write(term, plinst); - if (!ptrlen_endswith(plinst, PTRLEN_LITERAL("\n"), NULL)) - term_write(term, PTRLEN_LITERAL("\r\n")); - } - /* - * Zero all the results, in case we abort half-way through. - */ - { - int i; - for (i = 0; i < (int)p->n_prompts; i++) - prompt_set_result(p->prompts[i], ""); - } - } - - while (s->curr_prompt < p->n_prompts) { - - prompt_t *pr = p->prompts[s->curr_prompt]; - bool finished_prompt = false; - - if (!s->done_prompt) { - term_write(term, ptrlen_from_asciz(pr->prompt)); - s->done_prompt = true; - } - - /* Breaking out here ensures that the prompt is printed even - * if we're now waiting for user data. */ - if (!ldisc_has_input_buffered(term->ldisc)) - break; - - /* FIXME: should we be using local-line-editing code instead? */ - while (!finished_prompt && ldisc_has_input_buffered(term->ldisc)) { - LdiscInputToken tok = ldisc_get_input_token(term->ldisc); - - char c; - if (tok.is_special) { - switch (tok.code) { - case SS_EOL: c = 13; break; - case SS_EC: c = 8; break; - case SS_IP: c = 3; break; - case SS_EOF: c = 3; break; - default: continue; - } - } else { - c = tok.chr; - } - - switch (c) { - case 10: - case 13: - term_write(term, PTRLEN_LITERAL("\r\n")); - /* go to next prompt, if any */ - s->curr_prompt++; - s->done_prompt = false; - finished_prompt = true; /* break out */ - break; - case 8: - case 127: - if (pr->result->len > 0) { - if (pr->echo) - term_write(term, PTRLEN_LITERAL("\b \b")); - strbuf_shrink_by(pr->result, 1); - } - break; - case 21: - case 27: - while (pr->result->len > 0) { - if (pr->echo) - term_write(term, PTRLEN_LITERAL("\b \b")); - strbuf_shrink_by(pr->result, 1); - } - break; - case 3: - case 4: - /* Immediate abort. */ - term_write(term, PTRLEN_LITERAL("\r\n")); - sfree(s); - p->data = NULL; - return signal_prompts_t(term, p, 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)) { - put_byte(pr->result, c); - if (pr->echo) - term_write(term, make_ptrlen(&c, 1)); - } - break; - } - } - - } - - if (s->curr_prompt < p->n_prompts) { - ldisc_enable_prompt_callback(term->ldisc, p); - return -1; /* more data required */ - } else { - sfree(s); - p->data = NULL; - return signal_prompts_t(term, p, +1); /* all done */ - } -} - -void term_notify_minimised(Terminal *term, bool minimised) -{ - term->minimised = minimised; -} - -void term_notify_palette_changed(Terminal *term) -{ - palette_reset(term, true); -} - -void term_notify_window_pos(Terminal *term, int x, int y) -{ - term->winpos_x = x; - term->winpos_y = y; -} - -void term_notify_window_size_pixels(Terminal *term, int x, int y) -{ - term->winpixsize_x = x; - term->winpixsize_y = y; -} diff --git a/terminal.h b/terminal.h deleted file mode 100644 index c463f7a8..00000000 --- a/terminal.h +++ /dev/null @@ -1,529 +0,0 @@ -/* - * 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; -}; - -#define TRUST_SIGIL_WIDTH 3 -#define TRUST_SIGIL_CHAR 0xDFFE - -typedef struct { - int y, x; -} pos; - -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; - truecolour truecolour; - - /* - * 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) */ - bool temporary; /* true if decompressed from scrollback */ - int cc_free; /* offset to first cc in free list */ - struct termchar *chars; - bool trusted; -}; - -struct bidi_cache_entry { - int width; - bool trusted; - struct termchar *chars; - int *forward, *backward; /* the permutations of line positions */ -}; - -struct term_utf8_decode { - int state; /* Is there a pending UTF-8 character */ - int chr; /* and what is it so far? */ - int size; /* The size of the UTF character. */ -}; - -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; - bool beep_overloaded; - long lastbeep; - -#define TTYPE termchar -#define TSIZE (sizeof(TTYPE)) - - int default_attr, curr_attr, save_attr; - truecolour curr_truecolour, save_truecolour; - 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 */ - bool dec_om; /* DEC origin mode flag */ - bool wrap, wrapnext; /* wrap flags */ - bool insert; /* insert-mode flag */ - int cset; /* 0 or 1: which char set */ - int save_cset, save_csattr; /* saved with cursor position */ - bool save_utf, save_wnext; /* saved with cursor position */ - bool rvideo; /* global reverse video flag */ - unsigned long rvbell_startpoint; /* for ESC[?5hESC[?5l vbell */ - bool cursor_on; /* cursor enabled flag */ - bool reset_132; /* Flag ESC c resets to 80 cols */ - bool use_bce; /* Use Background coloured erase */ - bool cblinker; /* When blinking is the cursor on ? */ - bool tblinker; /* When the blinking text is on */ - bool blink_is_real; /* Actually blink blinking text */ - int sco_acs, save_sco_acs; /* CSI 10,11,12m -> OEM charset */ - bool vt52_bold; /* Force bold on non-bold colours */ - bool utf; /* Are we in toggleable UTF-8 mode? */ - term_utf8_decode utf8; /* If so, here's our decoding state */ - bool 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; - truecolour alt_save_truecolour; - int alt_save_cset, alt_save_csattr; - bool alt_save_utf; - bool alt_save_wnext; - int alt_save_sco_acs; - - int rows, cols, savelines; - bool has_focus; - bool in_vbell; - long vbell_end; - bool app_cursor_keys, app_keypad_keys, vt52_mode; - bool repeat_off, srm_echo, cr_lf_return; - bool seen_disp_event; - bool big_cursor; - - bool xterm_mouse_forbidden; - int xterm_mouse; /* send mouse messages to host */ - bool xterm_extended_mouse; - bool urxvt_extended_mouse; - int mouse_is_down; /* used while tracking mouse buttons */ - - bool bracketed_paste, bracketed_paste_active; - - int cset_attr[2]; - -/* - * Saved settings on the alternate screen. - */ - int alt_x, alt_y; - bool alt_wnext, alt_ins; - bool alt_om, alt_wrap; - int alt_cset, alt_sco_acs; - bool 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) ) - unsigned esc_args[ARGS_MAX]; - int esc_nargs; - int esc_query; -#define ANSI(x,y) ((x)+((y)*256)) -#define ANSI_QUE(x) ANSI(x,1) - -#define OSC_STR_MAX 2048 - int osc_strlen; - char osc_string[OSC_STR_MAX + 1]; - bool 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; - - Backend *backend; - - Ldisc *ldisc; - - TermWin *win; - - LogContext *logctx; - - struct unicode_data *ucsdata; - - unsigned long last_graphic_char; - - /* - * We maintain a full copy of a Conf here, not merely a pointer - * to it. That way, when we're passed a new one for - * reconfiguration, we can check the differences and adjust the - * _current_ setting of (e.g.) auto wrap mode rather than only - * the default. - */ - Conf *conf; - - /* - * GUI implementations of seat_output call 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 - * seat_output 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. - */ - bool in_term_out; - - /* - * We don't permit window updates too close together, to avoid CPU - * churn pointlessly redrawing the window faster than the user can - * read. So after an update, we set window_update_cooldown = true - * and schedule a timer to reset it to false. In between those - * times, window updates are not performed, and instead we set - * window_update_pending = true, which will remind us to perform - * the deferred redraw when the cooldown period ends and - * window_update_cooldown is reset to false. - */ - bool window_update_pending, window_update_cooldown; - long window_update_cooldown_end; - - /* - * Track pending blinks and tblinks. - */ - bool 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; - size_t bidi_cache_size; - - /* - * Current trust state, used to annotate every line of the - * terminal that a graphic character is output to. - */ - bool trusted; - - /* - * We copy a bunch of stuff out of the Conf structure into local - * fields in the Terminal structure, to avoid the repeated - * tree234 lookups which would be involved in fetching them from - * the former every time. - */ - bool ansi_colour; - char *answerback; - int answerbacklen; - bool no_arabicshaping; - int beep; - bool bellovl; - int bellovl_n; - int bellovl_s; - int bellovl_t; - bool no_bidi; - bool bksp_is_delete; - bool blink_cur; - bool blinktext; - bool cjk_ambig_wide; - int conf_height; - int conf_width; - bool crhaslf; - bool erase_to_scrollback; - int funky_type; - bool lfhascr; - bool logflush; - int logtype; - bool mouse_override; - bool nethack_keypad; - bool no_alt_screen; - bool no_applic_c; - bool no_applic_k; - bool no_dbackspace; - bool no_mouse_rep; - bool no_remote_charset; - bool no_remote_resize; - bool no_remote_wintitle; - bool no_remote_clearscroll; - bool rawcnp; - bool utf8linedraw; - bool rect_select; - int remote_qtitle_action; - bool rxvt_homeend; - bool scroll_on_disp; - bool scroll_on_key; - bool xterm_256_colour; - bool true_colour; - - wchar_t *last_selected_text; - int *last_selected_attr; - truecolour *last_selected_tc; - size_t last_selected_len; - int mouse_select_clipboards[N_CLIPBOARDS]; - int n_mouse_select_clipboards; - int mouse_paste_clipboard; - - char *window_title, *icon_title; - bool minimised; - - /* Multi-layered colour palette. The colours from Conf (plus the - * default xterm-256 ones that don't have Conf ids at all) have - * lowest priority, followed by platform overrides if any, - * followed by escape-sequence overrides during the session. */ - struct term_subpalette { - rgb values[OSC4_NCOLOURS]; - bool present[OSC4_NCOLOURS]; - } subpalettes[3]; -#define SUBPAL_CONF 0 -#define SUBPAL_PLATFORM 1 -#define SUBPAL_SESSION 2 - - /* The composite palette that we make out of the above */ - rgb palette[OSC4_NCOLOURS]; - - unsigned winpos_x, winpos_y, winpixsize_x, winpixsize_y; - - /* - * Assorted 'pending' flags for ancillary window changes performed - * in term_update. Generally, to trigger one of these operations, - * you set the pending flag and/or the parameters here, then call - * term_schedule_update. - */ - bool win_move_pending; - int win_move_pending_x, win_move_pending_y; - bool win_resize_pending; - int win_resize_pending_w, win_resize_pending_h; - bool win_zorder_pending; - bool win_zorder_top; - bool win_minimise_pending; - bool win_minimise_enable; - bool win_maximise_pending; - bool win_maximise_enable; - bool win_title_pending, win_icon_title_pending; - bool win_pointer_shape_pending; - bool win_pointer_shape_raw; - bool win_refresh_pending; - bool win_scrollbar_update_pending; - bool win_palette_pending; - unsigned win_palette_pending_min, win_palette_pending_limit; -}; - -static inline bool in_utf(Terminal *term) -{ - return term->utf || term->ucsdata->line_codepage == CP_UTF8; -} - -unsigned long term_translate( - Terminal *term, term_utf8_decode *utf8, unsigned char c); -static inline int term_char_width(Terminal *term, unsigned int c) -{ - return term->cjk_ambig_wide ? mk_wcwidth_cjk(c) : mk_wcwidth(c); -} - -/* - * UCSINCOMPLETE is returned from term_translate if it's successfully - * absorbed a byte but not emitted a complete character yet. - * UCSTRUNCATED indicates a truncated multibyte sequence (so the - * caller emits an error character and then calls term_translate again - * with the same input byte). UCSINVALID indicates some other invalid - * multibyte sequence, such as an overlong synonym, or a standalone - * continuation byte, or a completely illegal thing like 0xFE. These - * values are not stored in the terminal data structures at all. - */ -#define UCSINCOMPLETE 0x8000003FU /* '?' */ -#define UCSTRUNCATED 0x80000021U /* '!' */ -#define UCSINVALID 0x8000002AU /* '*' */ - -/* - * Maximum number of combining characters we're willing to store in a - * character cell. Our linked-list data representation permits an - * unlimited number of these in principle, but if we allowed that in - * practice then it would be an easy DoS to just squirt a squillion - * identical combining characters to someone's terminal and cause - * their PuTTY or pterm to consume lots of memory and CPU pointlessly. - * - * The precise figure of 32 is more or less arbitrary, but one point - * supporting it is UAX #15's comment that 30 combining characters is - * "significantly beyond what is required for any linguistic or - * technical usage". - */ -#define CC_LIMIT 32 - -/* ---------------------------------------------------------------------- - * Helper functions for dealing with the small 'pos' structure. - */ - -static inline bool poslt(pos p1, pos p2) -{ - if (p1.y != p2.y) - return p1.y < p2.y; - return p1.x < p2.x; -} - -static inline bool posle(pos p1, pos p2) -{ - if (p1.y != p2.y) - return p1.y < p2.y; - return p1.x <= p2.x; -} - -static inline bool poseq(pos p1, pos p2) -{ - return p1.y == p2.y && p1.x == p2.x; -} - -static inline int posdiff_fn(pos p1, pos p2, int cols) -{ - return (p1.y - p2.y) * (cols+1) + (p1.x - p2.x); -} - -/* Convenience wrapper on posdiff_fn which uses the 'Terminal *term' - * that more or less every function in terminal.c will have in scope. - * For safety's sake I include a TYPECHECK that ensures it really is a - * structure pointer of the right type. */ -#define GET_TERM_COLS TYPECHECK(term == (Terminal *)0, term->cols) -#define posdiff(p1,p2) posdiff_fn(p1, p2, GET_TERM_COLS) - -/* Product-order comparisons for rectangular block selection. */ - -static inline bool posPle(pos p1, pos p2) -{ - return p1.y <= p2.y && p1.x <= p2.x; -} - -static inline bool posPle_left(pos p1, pos p2) -{ - /* - * This function is used for checking whether a given character - * cell of the terminal ought to be highlighted as part of the - * selection, by comparing with term->selend. term->selend stores - * the location one space to the right of the last highlighted - * character. So we want to highlight the characters that are - * less-or-equal (in the product order) to the character just left - * of p2. - * - * (Setting up term->selend that way was the easiest way to get - * rectangular selection working at all, in a code base that had - * done lexicographic selection the way I happened to have done - * it.) - */ - return p1.y <= p2.y && p1.x < p2.x; -} - -static inline bool incpos_fn(pos *p, int cols) -{ - if (p->x == cols) { - p->x = 0; - p->y++; - return true; - } - p->x++; - return false; -} - -static inline bool decpos_fn(pos *p, int cols) -{ - if (p->x == 0) { - p->x = cols; - p->y--; - return true; - } - p->x--; - return false; -} - -/* Convenience wrappers on incpos and decpos which use term->cols - * (similarly to posdiff above), and also (for mild convenience and - * mostly historical inertia) let you leave off the & at every call - * site. */ -#define incpos(p) incpos_fn(&(p), GET_TERM_COLS) -#define decpos(p) decpos_fn(&(p), GET_TERM_COLS) - -#endif diff --git a/terminal/bidi.c b/terminal/bidi.c new file mode 100644 index 00000000..05d15b3d --- /dev/null +++ b/terminal/bidi.c @@ -0,0 +1,2025 @@ +/************************************************************************ + * + * ------------ + * Description: + * ------------ + * This is an implementation of Unicode's Bidirectional Algorithm + * (known as UAX #9). + * + * http://www.unicode.org/reports/tr9/ + * + * Author: Ahmad Khalifa + * + * (www.arabeyes.org - under MIT license) + * + ************************************************************************/ + +/* + * TODO: + * ===== + * - Explicit marks need to be handled (they are not 100% now) + * - Ligatures + */ + +#include /* definition of wchar_t*/ + +#include "putty.h" +#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 ) + +/* function declarations */ +static void flipThisRun( + bidi_char *from, unsigned char *level, int max, int count); +static int findIndexOfRun( + unsigned char *level, int start, int count, int tlevel); +static unsigned char getType(int ch); +static unsigned char setOverrideBits( + unsigned char level, unsigned char override); +static int getPreviousLevel(unsigned char *level, int from); +static void doMirror(unsigned int *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, doesn't 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) + +static 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 + */ +static 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 j; k--, j++) { + temp = from[k]; + from[k] = from[j]; + from[j] = temp; + } + } +} + +/* + * Finds the index of a run with level equals tlevel + */ +static int findIndexOfRun( + unsigned char *level , int start, int count, int tlevel) +{ + int i; + for (i=start; 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.) + */ +bool 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< 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; + bool ligFlag = false; + + for (i=0; i 0) switch (line[i-1].wc) { + case 0x622: + ligFlag = true; + if ((tempShape == SL) || (tempShape == SD) || (tempShape == SC)) + to[i].wc = 0xFEF6; + else + to[i].wc = 0xFEF5; + break; + case 0x623: + ligFlag = true; + if ((tempShape == SL) || (tempShape == SD) || (tempShape == SC)) + to[i].wc = 0xFEF8; + else + to[i].wc = 0xFEF7; + break; + case 0x625: + ligFlag = true; + if ((tempShape == SL) || (tempShape == SD) || (tempShape == SC)) + to[i].wc = 0xFEFA; + else + to[i].wc = 0xFEF9; + break; + case 0x627: + ligFlag = true; + 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 = false; + 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; + bool yes, bover; + + /* Check the presence of R or AL types as optimization */ + yes = false; + for (i=0; i= 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 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-1 && 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= 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= 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; i0 && (getType(line[j].wc) == WS)) { + j--; + } + if (j < (count-1)) { + 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 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. + */ +static void doMirror(unsigned int *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 +#include + +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", (unsigned)chr, typetoname[type].name); + } + + return 0; +} + +#endif diff --git a/terminal/terminal.c b/terminal/terminal.c new file mode 100644 index 00000000..ddeb1e94 --- /dev/null +++ b/terminal/terminal.c @@ -0,0 +1,7699 @@ +/* + * Terminal emulator. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include "putty.h" +#include "terminal.h" + +#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 ) + +static const char *const EMPTY_WINDOW_TITLE = ""; + +static const char sco2ansicolour[] = { 0, 4, 2, 6, 1, 5, 3, 7 }; + +#define sel_nl_sz (sizeof(sel_nl)/sizeof(wchar_t)) +static 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 check_line_size(Terminal *, termline *); +static void do_paint(Terminal *); +static void erase_lots(Terminal *, bool, bool, bool); +static int find_last_nonempty_line(Terminal *, tree234 *); +static void swap_screen(Terminal *, int, bool, bool); +static void update_sbar(Terminal *); +static void deselect(Terminal *); +static void term_print_finish(Terminal *); +static void scroll(Terminal *, int, int, int, bool); +static void parse_optionalrgb(optionalrgb *out, unsigned *values); +static void term_added_data(Terminal *term); +static void term_update_raw_mouse_mode(Terminal *term); + +static termline *newtermline(Terminal *term, int cols, bool 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->trusted = false; + line->temporary = false; + line->cc_free = 0; + + return line; +} + +static void freetermline(termline *line) +{ + if (line) { + sfree(line->chars); + sfree(line); + } +} + +static void unlineptr(termline *line) +{ + if (line->temporary) + freetermline(line); +} + +const int colour_indices_conf_to_oscp[CONF_NCOLOURS] = { + #define COLOUR_ENTRY(id,name) OSCP_COLOUR_##id, + CONF_COLOUR_LIST(COLOUR_ENTRY) + #undef COLOUR_ENTRY +}; + +const int colour_indices_conf_to_osc4[CONF_NCOLOURS] = { + #define COLOUR_ENTRY(id,name) OSC4_COLOUR_##id, + CONF_COLOUR_LIST(COLOUR_ENTRY) + #undef COLOUR_ENTRY +}; + +const int colour_indices_oscp_to_osc4[OSCP_NCOLOURS] = { + #define COLOUR_ENTRY(id) OSC4_COLOUR_##id, + OSCP_COLOUR_LIST(COLOUR_ENTRY) + #undef COLOUR_ENTRY +}; + +#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 + +static void clear_cc(termline *line, int col); + +/* + * 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); + + /* + * Don't add combining characters at all to U+FFFD REPLACEMENT + * CHARACTER. (Partly it's a slightly incoherent idea in the first + * place; mostly, U+FFFD is what we generate if a cell already has + * too many ccs, in which case we want it to be a fixed point when + * further ccs are added.) + */ + if (line->chars[col].chr == 0xFFFD) + return; + + /* + * Walk the cc list of the cell in question to find its current + * end point. + */ + size_t ncc = 0; + int origcol = col; + while (line->chars[col].cc_next) { + col += line->chars[col].cc_next; + if (++ncc >= CC_LIMIT) { + /* + * There are already too many combining characters in this + * character cell. Change strategy: throw out the entire + * chain and replace the main character with U+FFFD. + * + * (Rationale: extrapolating from UTR #36 section 3.6.2 + * suggests the principle that it's better to substitute + * U+FFFD than to _ignore_ input completely. Also, if the + * user copies and pastes an overcombined character cell, + * this way it will clearly indicate that we haven't + * reproduced the writer's original intentions, instead of + * looking as if it was the _writer's_ fault that the 33rd + * cc is missing.) + * + * Per the code above, this will also prevent any further + * ccs from being added to this cell. + */ + clear_cc(line, origcol); + line->chars[origcol].chr = 0xFFFD; + return; + } + } + + /* + * Extend the cols array if the free list is empty. + */ + if (!line->cc_free) { + int n = line->size; + + size_t tmpsize = line->size; + sgrowarray(line->chars, tmpsize, tmpsize); + assert(tmpsize <= INT_MAX); + line->size = tmpsize; + + 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++; + } + } + + /* + * `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 bool termchars_equal_override(termchar *a, termchar *b, + unsigned long bchr, unsigned long battr) +{ + /* FULL-TERMCHAR */ + if (!truecolour_equal(a->truecolour, b->truecolour)) + return false; + 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 bool 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.) + */ +static void makerle(strbuf *b, termline *ldata, + void (*makeliteral)(strbuf *b, termchar *c, + unsigned long *state)) +{ + int hdrpos, hdrsize, n, prevlen, prevpos, thislen, thispos; + bool prev2; + termchar *c = ldata->chars; + unsigned long state = 0, oldstate; + + n = ldata->cols; + + hdrpos = b->len; + hdrsize = 0; + put_byte(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->u + prevpos, b->u + 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; + strbuf_shrink_to(b, prevpos+prevlen); + } else { + memmove(b->u + prevpos+1, b->u + prevpos, prevlen); + runpos = prevpos; + strbuf_shrink_to(b, prevpos+prevlen+1); + /* + * Terminate the previous run of ordinary + * literals. + */ + assert(hdrsize >= 1 && hdrsize <= 128); + b->u[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; + bool match = tmplen == thislen && + !memcmp(b->u + runpos+1, b->u + tmppos, tmplen); + strbuf_shrink_to(b, tmppos); + if (!match) { + state = oldstate; + break; /* run over */ + } + n--, c++, runlen++; + } + + assert(runlen >= 2 && runlen <= 129); + b->u[runpos] = runlen + 0x80 - 2; + + hdrpos = b->len; + hdrsize = 0; + put_byte(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->u[hdrpos] = hdrsize - 1; + hdrpos = b->len; + hdrsize = 0; + put_byte(b, 0); + prevlen = prevpos = 0; + prev2 = false; + } + } + + /* + * Clean up. + */ + if (hdrsize > 0) { + assert(hdrsize <= 128); + b->u[hdrpos] = hdrsize - 1; + } else { + strbuf_shrink_to(b, hdrpos); + } +} +static void makeliteral_chr(strbuf *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) { + put_byte(b, (unsigned char)(c->chr & 0x7F)); + } else if (c->chr < 0x4000) { + put_byte(b, (unsigned char)(((c->chr >> 8) & 0x3F) | 0x80)); + put_byte(b, (unsigned char)(c->chr & 0xFF)); + } else if (c->chr < 0x200000) { + put_byte(b, (unsigned char)(((c->chr >> 16) & 0x1F) | 0xC0)); + put_uint16(b, c->chr & 0xFFFF); + } else if (c->chr < 0x10000000) { + put_byte(b, (unsigned char)(((c->chr >> 24) & 0x0F) | 0xE0)); + put_byte(b, (unsigned char)((c->chr >> 16) & 0xFF)); + put_uint16(b, c->chr & 0xFFFF); + } else { + put_byte(b, 0xF0); + put_uint32(b, c->chr); + } + *state = c->chr & ~0xFF; +} +static void makeliteral_attr(strbuf *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) { + put_byte(b, (unsigned char)((attr >> 8) & 0xFF)); + put_byte(b, (unsigned char)(attr & 0xFF)); + } else { + put_byte(b, (unsigned char)(((attr >> 24) & 0x7F) | 0x80)); + put_byte(b, (unsigned char)((attr >> 16) & 0xFF)); + put_byte(b, (unsigned char)((attr >> 8) & 0xFF)); + put_byte(b, (unsigned char)(attr & 0xFF)); + } +} +static void makeliteral_truecolour(strbuf *b, termchar *c, unsigned long *state) +{ + /* + * Put the used parts of the colour info into the buffer. + */ + put_byte(b, ((c->truecolour.fg.enabled ? 1 : 0) | + (c->truecolour.bg.enabled ? 2 : 0))); + if (c->truecolour.fg.enabled) { + put_byte(b, c->truecolour.fg.r); + put_byte(b, c->truecolour.fg.g); + put_byte(b, c->truecolour.fg.b); + } + if (c->truecolour.bg.enabled) { + put_byte(b, c->truecolour.bg.r); + put_byte(b, c->truecolour.bg.g); + put_byte(b, c->truecolour.bg.b); + } +} +static void makeliteral_cc(strbuf *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); +} + +typedef struct compressed_scrollback_line { + size_t len; +} compressed_scrollback_line; + +static termline *decompressline(compressed_scrollback_line *line); + +static compressed_scrollback_line *compressline(termline *ldata) +{ + strbuf *b = strbuf_new(); + + /* Leave space for the header structure */ + strbuf_append(b, sizeof(compressed_scrollback_line)); + + /* + * 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) { + put_byte(b, (unsigned char)((n & 0x7F) | 0x80)); + n >>= 7; + } + put_byte(b, (unsigned char)(n)); + } + + /* + * Next store the lattrs; same principle. We add one extra bit to + * this to indicate the trust state of the line. + */ + { + int n = ldata->lattr | (ldata->trusted ? 0x10000 : 0); + while (n >= 128) { + put_byte(b, (unsigned char)((n & 0x7F) | 0x80)); + n >>= 7; + } + put_byte(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_truecolour); + makerle(b, ldata, makeliteral_cc); + + size_t linelen = b->len - sizeof(compressed_scrollback_line); + compressed_scrollback_line *line = + (compressed_scrollback_line *)strbuf_to_str(b); + line->len = linelen; + + /* + * 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 + { + 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(line); + 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 + + freetermline(dcl); + } +#endif +#endif /* TERM_CC_DIAGS */ + + return line; +} + +static void readrle(BinarySource *bs, termline *ldata, + void (*readliteral)(BinarySource *bs, termchar *c, + termline *ldata, unsigned long *state)) +{ + int n = 0; + unsigned long state = 0; + + while (n < ldata->cols) { + int hdr = get_byte(bs); + + if (hdr >= 0x80) { + /* A run. */ + + size_t pos = bs->pos, count = hdr + 2 - 0x80; + while (count--) { + assert(n < ldata->cols); + bs->pos = pos; + readliteral(bs, ldata->chars + n, ldata, &state); + n++; + } + } else { + /* Just a sequence of consecutive literals. */ + + int count = hdr + 1; + while (count--) { + assert(n < ldata->cols); + readliteral(bs, ldata->chars + n, ldata, &state); + n++; + } + } + } + + assert(n == ldata->cols); +} +static void readliteral_chr(BinarySource *bs, 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_byte(bs); + if (byte < 0x80) { + c->chr = byte | *state; + } else if (byte < 0xC0) { + c->chr = (byte &~ 0xC0) << 8; + c->chr |= get_byte(bs); + } else if (byte < 0xE0) { + c->chr = (byte &~ 0xE0) << 16; + c->chr |= get_uint16(bs); + } else if (byte < 0xF0) { + c->chr = (byte &~ 0xF0) << 24; + c->chr |= get_byte(bs) << 16; + c->chr |= get_uint16(bs); + } else { + assert(byte == 0xF0); + c->chr = get_uint32(bs); + } + *state = c->chr & ~0xFF; +} +static void readliteral_attr(BinarySource *bs, termchar *c, termline *ldata, + unsigned long *state) +{ + unsigned val, attr, colourbits; + + val = get_uint16(bs); + + if (val >= 0x8000) { + val &= ~0x8000; + val <<= 16; + val |= get_uint16(bs); + } + + 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_truecolour( + BinarySource *bs, termchar *c, termline *ldata, unsigned long *state) +{ + int flags = get_byte(bs); + + if (flags & 1) { + c->truecolour.fg.enabled = true; + c->truecolour.fg.r = get_byte(bs); + c->truecolour.fg.g = get_byte(bs); + c->truecolour.fg.b = get_byte(bs); + } else { + c->truecolour.fg = optionalrgb_none; + } + + if (flags & 2) { + c->truecolour.bg.enabled = true; + c->truecolour.bg.r = get_byte(bs); + c->truecolour.bg.g = get_byte(bs); + c->truecolour.bg.b = get_byte(bs); + } else { + c->truecolour.bg = optionalrgb_none; + } +} +static void readliteral_cc(BinarySource *bs, 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(bs, &n, ldata, &zstate); + if (!n.chr) + break; + add_cc(ldata, x, n.chr); + } +} + +static termline *decompressline(compressed_scrollback_line *line) +{ + int ncols, byte, shift; + BinarySource bs[1]; + termline *ldata; + + BinarySource_BARE_INIT(bs, line+1, line->len); + + /* + * First read in the column count. + */ + ncols = shift = 0; + do { + byte = get_byte(bs); + 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. + */ + int lattr = shift = 0; + do { + byte = get_byte(bs); + lattr |= (byte & 0x7F) << shift; + shift += 7; + } while (byte & 0x80); + ldata->lattr = lattr & 0xFFFF; + ldata->trusted = (lattr & 0x10000) != 0; + + /* + * Now we read in each of the RLE streams in turn. + */ + readrle(bs, ldata, readliteral_chr); + readrle(bs, ldata, readliteral_attr); + readrle(bs, ldata, readliteral_truecolour); + readrle(bs, ldata, readliteral_cc); + + /* And we always expect that we ended up exactly at the end of the + * compressed data. */ + assert(!get_err(bs)); + assert(get_avail(bs) == 0); + + 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->erase_to_scrollback && + term->alt_which && term->alt_screen) { + sblines += term->alt_sblines; + } + return sblines; +} + +static void null_line_error(Terminal *term, int y, int lineno, + tree234 *whichtree, int treeindex, + const char *varname) +{ + modalfatalbox("%s==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" + "commitid=%s\n\n" + "Please contact " + "and pass on the above information.", + varname, 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, commitid); +} + +/* + * 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->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) { + compressed_scrollback_line *cline = index234(whichtree, treeindex); + if (!cline) + null_line_error(term, y, lineno, whichtree, treeindex, "cline"); + line = decompressline(cline); + } else { + line = index234(whichtree, treeindex); + } + + /* We assume that we don't screw up and retrieve something out of range. */ + if (line == NULL) + null_line_error(term, y, lineno, whichtree, treeindex, "line"); + assert(line != NULL); + + /* + * Here we resize lines to _at least_ the right length, but we + * don't truncate them. Truncation is done as a side effect of + * modifying the line. + * + * The point of this policy is to try to arrange that resizing the + * terminal window repeatedly - e.g. successive steps in an X11 + * opaque window-resize drag, or resizing as a side effect of + * retiling by tiling WMs such as xmonad - does not throw away + * data gratuitously. Specifically, we want a sequence of resize + * operations with no terminal output between them to have the + * same effect as a single resize to the ultimate terminal size, + * and also (for the case in which xmonad narrows a window that's + * scrolling things) we want scrolling up new text at the bottom + * of a narrowed window to avoid truncating lines further up when + * the window is re-widened. + */ + if (term->cols > line->cols) + resizeline(term, line, term->cols); + + return line; +} + +#define lineptr(x) (lineptr)(term,x,__LINE__,0) +#define scrlineptr(x) (lineptr)(term,x,__LINE__,1) + +/* + * Coerce a termline to the terminal's current width. Unlike the + * optional resize in lineptr() above, this is potentially destructive + * of text, since it can shrink as well as grow the line. + * + * We call this whenever a termline is actually going to be modified. + * Helpfully, putting a single call to this function in check_boundary + * deals with _nearly_ all such cases, leaving only a few things like + * bulk erase and ESC#8 to handle separately. + */ +static void check_line_size(Terminal *term, termline *line) +{ + if (term->cols != line->cols) /* trivial optimisation */ + resizeline(term, line, term->cols); +} + +static void term_schedule_tblink(Terminal *term); +static void term_schedule_cblink(Terminal *term); +static void term_update_callback(void *ctx); + +static void term_timer(void *ctx, unsigned long now) +{ + Terminal *term = (Terminal *)ctx; + + if (term->tblink_pending && now == term->next_tblink) { + term->tblinker = !term->tblinker; + term->tblink_pending = false; + term_schedule_tblink(term); + term->window_update_pending = true; + } + + if (term->cblink_pending && now == term->next_cblink) { + term->cblinker = !term->cblinker; + term->cblink_pending = false; + term_schedule_cblink(term); + term->window_update_pending = true; + } + + if (term->in_vbell && now == term->vbell_end) { + term->in_vbell = false; + term->window_update_pending = true; + } + + if (term->window_update_cooldown && + now == term->window_update_cooldown_end) { + term->window_update_cooldown = false; + } + + if (term->window_update_pending) + term_update_callback(term); +} + +static void term_update_callback(void *ctx) +{ + Terminal *term = (Terminal *)ctx; + if (!term->window_update_pending) + return; + if (!term->window_update_cooldown) { + term_update(term); + term->window_update_cooldown = true; + term->window_update_cooldown_end = schedule_timer( + UPDATE_DELAY, term_timer, term); + } +} + +static void term_schedule_update(Terminal *term) +{ + if (!term->window_update_pending) { + term->window_update_pending = true; + queue_toplevel_callback(term_update_callback, 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 = true; /* reset when not in use */ + term->tblink_pending = false; + } +} + +/* + * Likewise with cursor blinks. + */ +static void term_schedule_cblink(Terminal *term) +{ + if (term->blink_cur && term->has_focus) { + if (!term->cblink_pending) + term->next_cblink = schedule_timer(CBLINK_DELAY, term_timer, term); + term->cblink_pending = true; + } else { + term->cblinker = true; /* 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 = true; + term->cblink_pending = false; + term_schedule_cblink(term); +} + +/* + * Call to begin a visual bell. + */ +static void term_schedule_vbell(Terminal *term, bool 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, bool 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 = conf_get_bool(term->conf, CONF_dec_om); + term->alt_ins = false; + term->insert = false; + term->alt_wnext = false; + term->wrapnext = false; + term->save_wnext = false; + term->alt_save_wnext = false; + term->alt_wrap = term->wrap = conf_get_bool(term->conf, CONF_wrap_mode); + term->alt_cset = term->cset = term->save_cset = term->alt_save_cset = 0; + term->alt_utf = false; + term->utf = false; + term->save_utf = false; + term->alt_save_utf = false; + term->utf8.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 = false; + term->in_vbell = false; + term->cursor_on = true; + term->big_cursor = false; + term->default_attr = term->save_attr = + term->alt_save_attr = term->curr_attr = ATTR_DEFAULT; + term->curr_truecolour.fg = term->curr_truecolour.bg = optionalrgb_none; + term->save_truecolour = term->alt_save_truecolour = term->curr_truecolour; + term->app_cursor_keys = conf_get_bool(term->conf, CONF_app_cursor); + term->app_keypad_keys = conf_get_bool(term->conf, CONF_app_keypad); + term->use_bce = conf_get_bool(term->conf, CONF_bce); + term->blink_is_real = conf_get_bool(term->conf, CONF_blinktext); + term->erase_char = term->basic_erase_char; + term->alt_which = 0; + term_print_finish(term); + term->xterm_mouse = 0; + term->xterm_extended_mouse = false; + term->urxvt_extended_mouse = false; + win_set_raw_mouse_mode(term->win, false); + term->win_pointer_shape_pending = true; + term->win_pointer_shape_raw = false; + term->bracketed_paste = false; + term->srm_echo = false; + { + int i; + for (i = 0; i < 256; i++) + term->wordness[i] = conf_get_int_int(term->conf, CONF_wordness, i); + } + if (term->screen) { + swap_screen(term, 1, false, false); + erase_lots(term, false, true, true); + swap_screen(term, 0, false, false); + 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); + term_schedule_update(term); +} + +/* + * Force a screen update. + */ +void term_update(Terminal *term) +{ + term->window_update_pending = false; + + if (term->win_move_pending) { + win_move(term->win, term->win_move_pending_x, + term->win_move_pending_y); + term->win_move_pending = false; + } + if (term->win_resize_pending) { + win_request_resize(term->win, term->win_resize_pending_w, + term->win_resize_pending_h); + term->win_resize_pending = false; + } + if (term->win_zorder_pending) { + win_set_zorder(term->win, term->win_zorder_top); + term->win_zorder_pending = false; + } + if (term->win_minimise_pending) { + win_set_minimised(term->win, term->win_minimise_enable); + term->win_minimise_pending = false; + } + if (term->win_maximise_pending) { + win_set_maximised(term->win, term->win_maximise_enable); + term->win_maximise_pending = false; + } + if (term->win_title_pending) { + win_set_title(term->win, term->window_title); + term->win_title_pending = false; + } + if (term->win_icon_title_pending) { + win_set_icon_title(term->win, term->icon_title); + term->win_icon_title_pending = false; + } + if (term->win_pointer_shape_pending) { + win_set_raw_mouse_mode_pointer(term->win, term->win_pointer_shape_raw); + term->win_pointer_shape_pending = false; + } + if (term->win_refresh_pending) { + win_refresh(term->win); + term->win_refresh_pending = false; + } + if (term->win_palette_pending) { + unsigned start = term->win_palette_pending_min; + unsigned ncolours = term->win_palette_pending_limit - start; + win_palette_set(term->win, start, ncolours, term->palette + start); + term->win_palette_pending = false; + } + + if (win_setup_draw_ctx(term->win)) { + bool need_sbar_update = term->seen_disp_event || + term->win_scrollbar_update_pending; + term->win_scrollbar_update_pending = false; + if (term->seen_disp_event && term->scroll_on_disp) { + term->disptop = 0; /* return to main screen */ + term->seen_disp_event = false; + need_sbar_update = true; + } + + if (need_sbar_update) + update_sbar(term); + do_paint(term); + win_set_cursor_pos( + term->win, term->curs.x, term->curs.y - term->disptop); + win_free_draw_ctx(term->win); + } +} + +/* + * 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->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, bool clear) +{ + power_on(term, clear); + if (term->ldisc) /* cause ldisc to notice changes */ + ldisc_echoedit_update(term->ldisc); + 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)); + term->erase_char.truecolour.bg = term->curr_truecolour.bg; + } +} + +/* + * We copy a bunch of stuff out of the Conf structure into local + * fields in the Terminal structure, to avoid the repeated tree234 + * lookups which would be involved in fetching them from the former + * every time. + */ +void term_copy_stuff_from_conf(Terminal *term) +{ + term->ansi_colour = conf_get_bool(term->conf, CONF_ansi_colour); + term->no_arabicshaping = conf_get_bool(term->conf, CONF_no_arabicshaping); + term->beep = conf_get_int(term->conf, CONF_beep); + term->bellovl = conf_get_bool(term->conf, CONF_bellovl); + term->bellovl_n = conf_get_int(term->conf, CONF_bellovl_n); + term->bellovl_s = conf_get_int(term->conf, CONF_bellovl_s); + term->bellovl_t = conf_get_int(term->conf, CONF_bellovl_t); + term->no_bidi = conf_get_bool(term->conf, CONF_no_bidi); + term->bksp_is_delete = conf_get_bool(term->conf, CONF_bksp_is_delete); + term->blink_cur = conf_get_bool(term->conf, CONF_blink_cur); + term->blinktext = conf_get_bool(term->conf, CONF_blinktext); + term->cjk_ambig_wide = conf_get_bool(term->conf, CONF_cjk_ambig_wide); + term->conf_height = conf_get_int(term->conf, CONF_height); + term->conf_width = conf_get_int(term->conf, CONF_width); + term->crhaslf = conf_get_bool(term->conf, CONF_crhaslf); + term->erase_to_scrollback = conf_get_bool(term->conf, CONF_erase_to_scrollback); + term->funky_type = conf_get_int(term->conf, CONF_funky_type); + term->lfhascr = conf_get_bool(term->conf, CONF_lfhascr); + term->logflush = conf_get_bool(term->conf, CONF_logflush); + term->logtype = conf_get_int(term->conf, CONF_logtype); + term->mouse_override = conf_get_bool(term->conf, CONF_mouse_override); + term->nethack_keypad = conf_get_bool(term->conf, CONF_nethack_keypad); + term->no_alt_screen = conf_get_bool(term->conf, CONF_no_alt_screen); + term->no_applic_c = conf_get_bool(term->conf, CONF_no_applic_c); + term->no_applic_k = conf_get_bool(term->conf, CONF_no_applic_k); + term->no_dbackspace = conf_get_bool(term->conf, CONF_no_dbackspace); + term->no_mouse_rep = conf_get_bool(term->conf, CONF_no_mouse_rep); + term->no_remote_charset = conf_get_bool(term->conf, CONF_no_remote_charset); + term->no_remote_resize = conf_get_bool(term->conf, CONF_no_remote_resize); + term->no_remote_wintitle = conf_get_bool(term->conf, CONF_no_remote_wintitle); + term->no_remote_clearscroll = conf_get_bool(term->conf, CONF_no_remote_clearscroll); + term->rawcnp = conf_get_bool(term->conf, CONF_rawcnp); + term->utf8linedraw = conf_get_bool(term->conf, CONF_utf8linedraw); + term->rect_select = conf_get_bool(term->conf, CONF_rect_select); + term->remote_qtitle_action = conf_get_int(term->conf, CONF_remote_qtitle_action); + term->rxvt_homeend = conf_get_bool(term->conf, CONF_rxvt_homeend); + term->scroll_on_disp = conf_get_bool(term->conf, CONF_scroll_on_disp); + term->scroll_on_key = conf_get_bool(term->conf, CONF_scroll_on_key); + term->xterm_mouse_forbidden = conf_get_bool(term->conf, CONF_no_mouse_rep); + term->xterm_256_colour = conf_get_bool(term->conf, CONF_xterm_256_colour); + term->true_colour = conf_get_bool(term->conf, CONF_true_colour); + + /* + * Parse the control-character escapes in the configured + * answerback string. + */ + { + char *answerback = conf_get_str(term->conf, CONF_answerback); + int maxlen = strlen(answerback); + + term->answerback = snewn(maxlen, char); + term->answerbacklen = 0; + + while (*answerback) { + char *n; + char c = ctrlparse(answerback, &n); + if (n) { + term->answerback[term->answerbacklen++] = c; + answerback = n; + } else { + term->answerback[term->answerbacklen++] = *answerback++; + } + } + } +} + +void term_pre_reconfig(Terminal *term, Conf *conf) +{ + + /* + * Copy the current window title into the stored previous + * configuration, so that doing nothing to the window title field + * in the config box doesn't reset the title to its startup state. + */ + conf_set_str(conf, CONF_wintitle, term->window_title); +} + +/* + * 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, Conf *conf) +{ + /* + * Before adopting the new config, check all those terminal + * settings which control power-on defaults; and if they've + * changed, we will modify the current state as well as the + * default one. The full list is: Auto wrap mode, DEC Origin + * Mode, BCE, blinking text, character classes. + */ + bool reset_wrap, reset_decom, reset_bce, reset_tblink, reset_charclass; + bool palette_changed = false; + int i; + + reset_wrap = (conf_get_bool(term->conf, CONF_wrap_mode) != + conf_get_bool(conf, CONF_wrap_mode)); + reset_decom = (conf_get_bool(term->conf, CONF_dec_om) != + conf_get_bool(conf, CONF_dec_om)); + reset_bce = (conf_get_bool(term->conf, CONF_bce) != + conf_get_bool(conf, CONF_bce)); + reset_tblink = (conf_get_bool(term->conf, CONF_blinktext) != + conf_get_bool(conf, CONF_blinktext)); + reset_charclass = false; + for (i = 0; i < 256; i++) + if (conf_get_int_int(term->conf, CONF_wordness, i) != + conf_get_int_int(conf, CONF_wordness, i)) + reset_charclass = true; + + /* + * If the bidi or shaping settings have changed, flush the bidi + * cache completely. + */ + if (conf_get_bool(term->conf, CONF_no_arabicshaping) != + conf_get_bool(conf, CONF_no_arabicshaping) || + conf_get_bool(term->conf, CONF_no_bidi) != + conf_get_bool(conf, CONF_no_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; + } + } + + { + const char *old_title = conf_get_str(term->conf, CONF_wintitle); + const char *new_title = conf_get_str(conf, CONF_wintitle); + if (strcmp(old_title, new_title)) { + sfree(term->window_title); + term->window_title = dupstr(new_title); + term->win_title_pending = true; + term_schedule_update(term); + } + } + + /* + * Just setting conf is sufficient to cause colour setting changes + * to appear on the next ESC]R palette reset. But we should also + * check whether any colour settings have been changed, so that + * they can be updated immediately if they haven't been overridden + * by some escape sequence. + */ + { + int i, j; + for (i = 0; i < CONF_NCOLOURS; i++) { + for (j = 0; j < 3; j++) + if (conf_get_int_int(term->conf, CONF_colours, i*3+j) != + conf_get_int_int(conf, CONF_colours, i*3+j)) + break; + if (j < 3) { + /* Actually enacting the change has to be deferred + * until the new conf is installed. */ + palette_changed = true; + break; + } + } + } + + conf_free(term->conf); + term->conf = conf_copy(conf); + + if (reset_wrap) + term->alt_wrap = term->wrap = conf_get_bool(term->conf, CONF_wrap_mode); + if (reset_decom) + term->alt_om = term->dec_om = conf_get_bool(term->conf, CONF_dec_om); + if (reset_bce) { + term->use_bce = conf_get_bool(term->conf, CONF_bce); + set_erase_char(term); + } + if (reset_tblink) { + term->blink_is_real = conf_get_bool(term->conf, CONF_blinktext); + } + if (reset_charclass) + for (i = 0; i < 256; i++) + term->wordness[i] = conf_get_int_int(term->conf, CONF_wordness, i); + + if (conf_get_bool(term->conf, CONF_no_alt_screen)) + swap_screen(term, 0, false, false); + if (conf_get_bool(term->conf, CONF_no_remote_charset)) { + term->cset_attr[0] = term->cset_attr[1] = CSET_ASCII; + term->sco_acs = term->alt_sco_acs = 0; + term->utf = false; + } + if (!conf_get_str(term->conf, CONF_printer)) { + term_print_finish(term); + } + if (palette_changed) + term_notify_palette_changed(term); + term_schedule_tblink(term); + term_schedule_cblink(term); + term_copy_stuff_from_conf(term); + term_update_raw_mouse_mode(term); +} + +/* + * Clear the scrollback. + */ +void term_clrsb(Terminal *term) +{ + unsigned char *line; + int i; + + /* + * Scroll forward to the current screen, if we were back in the + * scrollback somewhere until now. + */ + term->disptop = 0; + + /* + * Clear the actual scrollback. + */ + while ((line = delpos234(term->scrollback, 0)) != NULL) { + sfree(line); /* this is compressed data, not a termline */ + } + + /* + * When clearing the scrollback, we also truncate any termlines on + * the current screen which have remembered data from a previous + * larger window size. Rationale: clearing the scrollback is + * sometimes done to protect privacy, so the user intention is + * specifically that we should not retain evidence of what + * previously happened in the terminal, and that ought to include + * evidence to the right as well as evidence above. + */ + for (i = 0; i < term->rows; i++) + check_line_size(term, scrlineptr(i)); + + /* + * That operation has invalidated the selection, if it overlapped + * the scrollback at all. + */ + if (term->selstate != NO_SELECTION && term->selstart.y < 0) + deselect(term); + + /* + * There are now no lines of real scrollback which can be pulled + * back into the screen by a resize, and no lines of the alternate + * screen which should be displayed as if part of the scrollback. + */ + term->tempsblines = 0; + term->alt_sblines = 0; + + /* + * The scrollbar will need updating to reflect the new state of + * the world. + */ + term->win_scrollbar_update_pending = true; + term_schedule_update(term); +} + +const optionalrgb optionalrgb_none = {0, 0, 0, 0}; + +void term_setup_window_titles(Terminal *term, const char *title_hostname) +{ + const char *conf_title = conf_get_str(term->conf, CONF_wintitle); + sfree(term->window_title); + sfree(term->icon_title); + if (*conf_title) { + term->window_title = dupstr(conf_title); + term->icon_title = dupstr(conf_title); + } else { + if (title_hostname && *title_hostname) + term->window_title = dupcat(title_hostname, " - ", appname); + else + term->window_title = dupstr(appname); + term->icon_title = dupstr(term->window_title); + } + term->win_title_pending = true; + term->win_icon_title_pending = true; +} + +static void palette_rebuild(Terminal *term) +{ + unsigned min_changed = OSC4_NCOLOURS, max_changed = 0; + + if (term->win_palette_pending) { + /* Possibly extend existing range. */ + min_changed = term->win_palette_pending_min; + max_changed = term->win_palette_pending_limit - 1; + } else { + /* Start with empty range. */ + min_changed = OSC4_NCOLOURS; + max_changed = 0; + } + + for (unsigned i = 0; i < OSC4_NCOLOURS; i++) { + rgb new_value; + bool found = false; + + for (unsigned j = lenof(term->subpalettes); j-- > 0 ;) { + if (term->subpalettes[j].present[i]) { + new_value = term->subpalettes[j].values[i]; + found = true; + break; + } + } + + assert(found); /* we expect SUBPAL_CONF to always be set */ + + if (new_value.r != term->palette[i].r || + new_value.g != term->palette[i].g || + new_value.b != term->palette[i].b) { + term->palette[i] = new_value; + if (min_changed > i) + min_changed = i; + if (max_changed < i) + max_changed = i; + } + } + + if (min_changed <= max_changed) { + /* + * At least one colour changed (or we had an update scheduled + * already). Schedule a redraw event to pass the result back + * to the TermWin. This also requires invalidating the rest + * of the window, because usually all the text will need + * redrawing in the new colours. + * (If there was an update pending and this palette rebuild + * didn't actually change anything, we'll harmlessly reinforce + * the existing update request.) + */ + term->win_palette_pending = true; + term->win_palette_pending_min = min_changed; + term->win_palette_pending_limit = max_changed + 1; + term_invalidate(term); + } +} + +/* + * Rebuild the palette from configuration and platform colours. + * If 'keep_overrides' set, any escape-sequence-specified overrides will + * remain in place. + */ +static void palette_reset(Terminal *term, bool keep_overrides) +{ + for (unsigned i = 0; i < OSC4_NCOLOURS; i++) + term->subpalettes[SUBPAL_CONF].present[i] = true; + + /* + * Copy all the palette information out of the Conf. + */ + for (unsigned i = 0; i < CONF_NCOLOURS; i++) { + rgb *col = &term->subpalettes[SUBPAL_CONF].values[ + colour_indices_conf_to_osc4[i]]; + col->r = conf_get_int_int(term->conf, CONF_colours, i*3+0); + col->g = conf_get_int_int(term->conf, CONF_colours, i*3+1); + col->b = conf_get_int_int(term->conf, CONF_colours, i*3+2); + } + + /* + * Directly invent the rest of the xterm-256 colours. + */ + for (unsigned i = 0; i < 216; i++) { + rgb *col = &term->subpalettes[SUBPAL_CONF].values[i + 16]; + int r = i / 36, g = (i / 6) % 6, b = i % 6; + col->r = r ? r * 40 + 55 : 0; + col->g = g ? g * 40 + 55 : 0; + col->b = b ? b * 40 + 55 : 0; + } + for (unsigned i = 0; i < 24; i++) { + rgb *col = &term->subpalettes[SUBPAL_CONF].values[i + 232]; + int shade = i * 10 + 8; + col->r = col->g = col->b = shade; + } + + /* + * Re-fetch any OS-local overrides. + */ + for (unsigned i = 0; i < OSC4_NCOLOURS; i++) + term->subpalettes[SUBPAL_PLATFORM].present[i] = false; + win_palette_get_overrides(term->win, term); + + if (!keep_overrides) { + /* + * Get rid of all escape-sequence configuration. + */ + for (unsigned i = 0; i < OSC4_NCOLOURS; i++) + term->subpalettes[SUBPAL_SESSION].present[i] = false; + } + + /* + * Rebuild the composite palette. + */ + palette_rebuild(term); +} + +void term_palette_override(Terminal *term, unsigned osc4_index, rgb rgb) +{ + /* + * We never expect to be called except as re-entry from our own + * call to win_palette_get_overrides above, so we need not mess + * about calling palette_rebuild. + */ + term->subpalettes[SUBPAL_PLATFORM].present[osc4_index] = true; + term->subpalettes[SUBPAL_PLATFORM].values[osc4_index] = rgb; +} + +/* + * Initialise the terminal. + */ +Terminal *term_init(Conf *myconf, struct unicode_data *ucsdata, TermWin *win) +{ + Terminal *term; + + /* + * Allocate a new Terminal structure and initialise the fields + * that need it. + */ + term = snew(Terminal); + term->win = win; + term->ucsdata = ucsdata; + term->conf = conf_copy(myconf); + term->logctx = NULL; + term->compatibility_level = TM_PUTTY; + strcpy(term->id_string, "\033[?6c"); + term->cblink_pending = term->tblink_pending = false; + term->paste_buffer = NULL; + term->paste_len = 0; + 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 = 0; + term->reset_132 = false; + term->cblinker = false; + term->tblinker = false; + term->has_focus = true; + term->repeat_off = false; + term->termstate = TOPLEVEL; + term->selstate = NO_SELECTION; + term->curstype = 0; + + term_copy_stuff_from_conf(term); + + term->screen = term->alt_screen = term->scrollback = NULL; + term->tempsblines = 0; + term->alt_sblines = 0; + term->disptop = 0; + 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; + term->nbeeps = 0; + term->lastbeep = false; + term->beep_overloaded = false; + term->attr_mask = 0xffffffff; + term->backend = 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->window_update_cooldown = 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->basic_erase_char.truecolour.fg = optionalrgb_none; + term->basic_erase_char.truecolour.bg = optionalrgb_none; + term->erase_char = term->basic_erase_char; + + term->last_selected_text = NULL; + term->last_selected_attr = NULL; + term->last_selected_tc = NULL; + term->last_selected_len = 0; + /* TermWin implementations will typically extend these with + * clipboard ids they know about */ + term->mouse_select_clipboards[0] = CLIP_LOCAL; + term->n_mouse_select_clipboards = 1; + term->mouse_paste_clipboard = CLIP_NULL; + + term->last_graphic_char = 0; + + term->trusted = true; + + term->bracketed_paste_active = false; + + term->window_title = dupstr(""); + term->icon_title = dupstr(""); + term->minimised = false; + term->winpos_x = term->winpos_y = 0; + term->winpixsize_x = term->winpixsize_y = 0; + + term->win_move_pending = false; + term->win_resize_pending = false; + term->win_zorder_pending = false; + term->win_minimise_pending = false; + term->win_maximise_pending = false; + term->win_title_pending = false; + term->win_icon_title_pending = false; + term->win_pointer_shape_pending = false; + term->win_refresh_pending = false; + term->win_scrollbar_update_pending = false; + term->win_palette_pending = false; + + palette_reset(term, false); + + 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) + freetermline(line); + freetree234(term->screen); + while ((line = delpos234(term->alt_screen, 0)) != NULL) + freetermline(line); + freetree234(term->alt_screen); + if (term->disptext) { + for (i = 0; i < term->rows; i++) + freetermline(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); + sfree(term->answerback); + + for (i = 0; i < term->bidi_cache_size; i++) { + sfree(term->pre_bidi_cache[i].chars); + sfree(term->post_bidi_cache[i].chars); + sfree(term->post_bidi_cache[i].forward); + sfree(term->post_bidi_cache[i].backward); + } + sfree(term->pre_bidi_cache); + sfree(term->post_bidi_cache); + + sfree(term->tabs); + + expire_timer_context(term); + delete_callbacks_for_context(term); + + conf_free(term->conf); + + sfree(term->window_title); + sfree(term->icon_title); + + sfree(term); +} + +void term_set_trust_status(Terminal *term, bool trusted) +{ + term->trusted = trusted; +} + +void term_get_cursor_position(Terminal *term, int *x, int *y) +{ + *x = term->curs.x; + *y = term->curs.y; +} + +/* + * 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) { + compressed_scrollback_line *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); + 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 = newtermline(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 */ + line = delpos234(term->screen, term->rows - 1); + freetermline(line); + } else { + /* push top row to scrollback */ + line = delpos234(term->screen, 0); + addpos234(term->scrollback, compressline(line), sblen++); + freetermline(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] = newtermline(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++) + freetermline(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 = newtermline(term, newcols, true); + addpos234(newalt, line, i); + } + if (term->alt_screen) { + while (NULL != (line = delpos234(term->alt_screen, 0))) + freetermline(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 = false; + term->alt_wnext = false; + + term->rows = newrows; + term->cols = newcols; + term->savelines = newsavelines; + + swap_screen(term, save_alt_which, false, false); + + term->win_scrollbar_update_pending = true; + term_schedule_update(term); + if (term->backend) + backend_size(term->backend, term->cols, term->rows); +} + +/* + * Hand a backend to the terminal, so it can be notified of resizes. + */ +void term_provide_backend(Terminal *term, Backend *backend) +{ + term->backend = backend; + if (term->backend && term->cols > 0 && term->rows > 0) + backend_size(term->backend, 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, + bool reset, bool keep_cur_pos) +{ + int t; + bool bt; + pos tp; + truecolour ttc; + tree234 *ttr; + + if (!which) + reset = false; /* do no weird resetting if which==0 */ + + if (which != term->alt_which) { + if (term->erase_to_scrollback && term->alt_screen && + term->alt_which && term->disptop < 0) { + /* + * We're swapping away from the alternate screen, so some + * lines are about to vanish from the virtual scrollback. + * Adjust disptop by that much, so that (if we're not + * resetting the scrollback anyway on a display event) the + * current scroll position still ends up pointing at the + * same text. + */ + term->disptop += term->alt_sblines; + if (term->disptop > 0) + term->disptop = 0; + } + + term->alt_which = which; + + ttr = term->alt_screen; + term->alt_screen = term->screen; + term->screen = ttr; + term->alt_sblines = ( + term->alt_screen ? + find_last_nonempty_line(term, term->alt_screen) + 1 : 0); + 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; + bt = term->dec_om; + if (!reset) term->dec_om = term->alt_om; + term->alt_om = bt; + bt = term->wrap; + if (!reset) term->wrap = term->alt_wrap; + term->alt_wrap = bt; + bt = term->wrapnext; + if (!reset) term->wrapnext = term->alt_wnext; + term->alt_wnext = bt; + bt = term->insert; + if (!reset) term->insert = term->alt_ins; + term->alt_ins = bt; + t = term->cset; + if (!reset) term->cset = term->alt_cset; + term->alt_cset = t; + bt = term->utf; + if (!reset) term->utf = term->alt_utf; + term->alt_utf = bt; + t = term->sco_acs; + if (!reset) term->sco_acs = term->alt_sco_acs; + term->alt_sco_acs = t; + + tp = term->savecurs; + if (!reset) + term->savecurs = term->alt_savecurs; + term->alt_savecurs = tp; + t = term->save_cset; + if (!reset) + term->save_cset = term->alt_save_cset; + term->alt_save_cset = t; + t = term->save_csattr; + if (!reset) + term->save_csattr = term->alt_save_csattr; + term->alt_save_csattr = t; + t = term->save_attr; + if (!reset) + term->save_attr = term->alt_save_attr; + term->alt_save_attr = t; + ttc = term->save_truecolour; + if (!reset) + term->save_truecolour = term->alt_save_truecolour; + term->alt_save_truecolour = ttc; + bt = term->save_utf; + if (!reset) + term->save_utf = term->alt_save_utf; + term->alt_save_utf = bt; + bt = term->save_wnext; + if (!reset) + term->save_wnext = term->alt_save_wnext; + term->alt_save_wnext = bt; + t = term->save_sco_acs; + if (!reset) + term->save_sco_acs = term->alt_save_sco_acs; + term->alt_save_sco_acs = t; + + if (term->erase_to_scrollback && term->alt_screen && + term->alt_which && term->disptop < 0) { + /* + * Inverse of the adjustment at the top of this function. + * This time, we're swapping _to_ the alternate screen, so + * some lines are about to _appear_ in the virtual + * scrollback, and we adjust disptop in the other + * direction. + * + * Both these adjustments depend on the value stored in + * term->alt_sblines while the alt screen is selected, + * which is why we had to do one _before_ switching away + * from it and the other _after_ switching to it. + */ + term->disptop -= term->alt_sblines; + int limit = -sblines(term); + if (term->disptop < limit) + term->disptop = limit; + } + } + + 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); + win_set_scrollbar(term->win, 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); +} + +static void clear_line(Terminal *term, termline *line) +{ + resizeline(term, line, term->cols); + for (int i = 0; i < term->cols; i++) + copy_termchar(line, i, &term->erase_char); + line->lattr = LATTR_NORM; +} + +static void check_trust_status(Terminal *term, termline *line) +{ + if (line->trusted != term->trusted) { + /* + * If we're displaying trusted output on a previously + * untrusted line, or vice versa, we need to switch the + * 'trusted' attribute on this terminal line, and also clear + * all its previous contents. + */ + clear_line(term, line); + line->trusted = term->trusted; + } +} + +/* + * 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, bool sb) +{ + termline *line; + int seltop, scrollwinsize; + + if (topline != 0 || term->alt_which != 0) + sb = false; + + scrollwinsize = botline - topline + 1; + + if (lines < 0) { + lines = -lines; + if (lines > scrollwinsize) + lines = scrollwinsize; + while (lines-- > 0) { + line = delpos234(term->screen, botline); + resizeline(term, line, term->cols); + clear_line(term, line); + 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; + } + } + } + } else { + if (lines > scrollwinsize) + lines = scrollwinsize; + while (lines-- > 0) { + line = delpos234(term->screen, topline); +#ifdef TERM_CC_DIAGS + cc_check(line); +#endif + if (sb && term->savelines > 0) { + 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); + clear_line(term, line); + check_trust_status(term, line); + 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; + } + } + } + } + } +} + +/* + * 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, bool save) +{ + if (save) { + term->savecurs = term->curs; + term->save_attr = term->curr_attr; + term->save_truecolour = term->curr_truecolour; + 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->curr_truecolour = term->save_truecolour; + 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); + check_trust_status(term, ldata); + check_line_size(term, ldata); + 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, + bool line_only, bool from_begin, bool to_end) +{ + pos start, end; + bool erase_lattr; + bool erasing_lines_from_top = false; + + 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; + } + + /* This is the endpoint of the clearing operation that is not + * either the start or end of the line / screen. */ + pos boundary = term->curs; + + if (!from_begin) { + /* + * If we're erasing from the current char to the end of + * line/screen, then we take account of wrapnext, so as to + * maintain the invariant that writing a printing character + * followed by ESC[K should not overwrite the character you + * _just wrote_. That is, when wrapnext says the cursor is + * 'logically' at the very rightmost edge of the screen + * instead of just before the last printing char, ESC[K should + * do nothing at all, and ESC[J should clear the next line but + * leave this one unchanged. + * + * This adjusted position will also be the position we use for + * check_boundary (i.e. the thing we ensure isn't in the + * middle of a double-width printing char). + */ + if (term->wrapnext) + incpos(boundary); + + start = boundary; + } + if (!to_end) { + /* + * If we're erasing from the start of (at least) the line _to_ + * the current position, then that is taken to mean 'inclusive + * of the cell under the cursor', which means we don't + * consider wrapnext at all: whether it's set or not, we still + * clear the cell under the cursor. + * + * Again, that incremented boundary position is where we + * should be careful of a straddling wide character. + */ + incpos(boundary); + end = boundary; + } + if (!from_begin || !to_end) + check_boundary(term, boundary.x, boundary.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 = true; + + if (term->erase_to_scrollback && erasing_lines_from_top) { + /* If it's a whole number of lines, starting at the top, and + * we're fully erasing them, erase by scrolling and keep the + * lines in the scrollback. */ + int scrolllines = end.y; + if (end.y == term->rows) { + /* 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); + check_trust_status(term, ldata); + while (poslt(start, end)) { + check_line_size(term, ldata); + 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); + check_trust_status(term, ldata); + } + } + } + + /* 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 explicitly 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 eol; + 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; + + /* + * We must de-highlight the selection if it overlaps any part of + * the region affected by this operation, i.e. the region from the + * current cursor position to end-of-line, _unless_ the entirety + * of the selection is going to be moved to the left or right by + * this operation but otherwise unchanged, in which case we can + * simply move the highlight with the text. + */ + eol.y = term->curs.y; + eol.x = term->cols; + if (poslt(term->curs, term->selend) && poslt(term->selstart, eol)) { + pos okstart = term->curs; + pos okend = eol; + if (dir > 0) { + /* Insertion: n characters at EOL will be splatted. */ + okend.x -= n; + } else { + /* Deletion: n characters at cursor position will be splatted. */ + okstart.x += n; + } + if (posle(okstart, term->selstart) && posle(term->selend, okend)) { + /* Selection is contained entirely in the interval + * [okstart,okend), so we need only adjust the selection + * bounds. */ + term->selstart.x += dir * n; + term->selend.x += dir * n; + assert(term->selstart.x >= term->curs.x); + assert(term->selstart.x < term->cols); + assert(term->selend.x > term->curs.x); + assert(term->selend.x <= term->cols); + } else { + /* Selection is not wholly contained in that interval, so + * we must unhighlight it. */ + deselect(term); + } + } + + 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); + check_trust_status(term, ldata); + 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); + } +} + +static void term_update_raw_mouse_mode(Terminal *term) +{ + bool want_raw = (term->xterm_mouse != 0 && !term->xterm_mouse_forbidden); + win_set_raw_mouse_mode(term->win, want_raw); + term->win_pointer_shape_pending = true; + term->win_pointer_shape_raw = want_raw; + term_schedule_update(term); +} + +/* + * 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, bool state) +{ + if (query == 1) { + 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->blinktext; + } + term_schedule_tblink(term); + break; + case 3: /* DECCOLM: 80/132 columns */ + deselect(term); + if (!term->no_remote_resize) { + term->win_resize_pending = true; + term->win_resize_pending_w = state ? 132 : 80; + term->win_resize_pending_h = term->rows; + term_schedule_update(term); + } + 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 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->no_alt_screen ? 0 : state, false, false); + if (term->scroll_on_disp) + term->disptop = 0; + break; + case 1000: /* xterm mouse 1 (normal) */ + term->xterm_mouse = state ? 1 : 0; + term_update_raw_mouse_mode(term); + break; + case 1002: /* xterm mouse 2 (inc. button drags) */ + term->xterm_mouse = state ? 2 : 0; + term_update_raw_mouse_mode(term); + break; + case 1006: /* xterm extended mouse */ + term->xterm_extended_mouse = state; + break; + case 1015: /* urxvt extended mouse */ + term->urxvt_extended_mouse = state; + break; + case 1047: /* alternate screen */ + compatibility(OTHER); + deselect(term); + swap_screen(term, term->no_alt_screen ? 0 : state, true, true); + if (term->scroll_on_disp) + term->disptop = 0; + break; + case 1048: /* save/restore cursor */ + if (!term->no_alt_screen) + save_cursor(term, state); + if (!state) seen_disp_event(term); + break; + case 1049: /* cursor & alternate screen */ + if (state && !term->no_alt_screen) + save_cursor(term, state); + if (!state) seen_disp_event(term); + compatibility(OTHER); + deselect(term); + swap_screen(term, term->no_alt_screen ? 0 : state, true, false); + if (!state && !term->no_alt_screen) + save_cursor(term, state); + if (term->scroll_on_disp) + term->disptop = 0; + break; + case 2004: /* xterm bracketed paste */ + term->bracketed_paste = state ? true : false; + break; + } + } else if (query == 0) { + switch (mode) { + case 4: /* IRM: set insert mode */ + compatibility(VT102); + term->insert = state; + break; + case 12: /* SRM: set echo mode */ + term->srm_echo = !state; + 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->no_remote_wintitle) { + sfree(term->icon_title); + term->icon_title = dupstr(term->osc_string); + term->win_icon_title_pending = true; + term_schedule_update(term); + } + if (term->esc_args[0] == 1) + break; + /* fall through: parameter 0 means set both */ + case 2: + case 21: + if (!term->no_remote_wintitle) { + sfree(term->window_title); + term->window_title = dupstr(term->osc_string); + term->win_title_pending = true; + term_schedule_update(term); + } + break; + case 4: + if (term->ldisc && !strcmp(term->osc_string, "?")) { + unsigned index = term->esc_args[1]; + if (index < OSC4_NCOLOURS) { + rgb colour = term->palette[index]; + char *reply_buf = dupprintf( + "\033]4;%u;rgb:%04x/%04x/%04x\007", index, + (unsigned)colour.r * 0x0101, + (unsigned)colour.g * 0x0101, + (unsigned)colour.b * 0x0101); + ldisc_send(term->ldisc, reply_buf, strlen(reply_buf), + false); + sfree(reply_buf); + } + } + break; + } + } +} + +/* + * ANSI printing routines. + */ +static void term_print_setup(Terminal *term, char *printer) +{ + bufchain_clear(&term->printer_buf); + term->print_job = printer_start_job(printer); +} +static void term_print_flush(Terminal *term) +{ + size_t size; + while ((size = bufchain_size(&term->printer_buf)) > 5) { + ptrlen data = bufchain_prefix(&term->printer_buf); + if (data.len > size-5) + data.len = size-5; + printer_job_data(term->print_job, data.ptr, data.len); + bufchain_consume(&term->printer_buf, data.len); + } +} +static void term_print_finish(Terminal *term) +{ + size_t 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) { + ptrlen data = bufchain_prefix(&term->printer_buf); + c = *(char *)data.ptr; + 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; +} + +static void term_display_graphic_char(Terminal *term, unsigned long c) +{ + termline *cline = scrlineptr(term->curs.y); + int width = 0; + if (DIRECT_CHAR(c)) + width = 1; + if (!width) + width = term_char_width(term, 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); + + check_trust_status(term, cline); + + int linecols = term->cols; + if (cline->trusted) + linecols -= TRUST_SIGIL_WIDTH; + + /* + * Preliminary check: if the terminal is only one character cell + * wide, then we cannot display any double-width character at all. + * Substitute single-width REPLACEMENT CHARACTER instead. + */ + if (width == 2 && linecols < 2) { + width = 1; + c = 0xFFFD; + } + + 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 >= linecols-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; + cline->chars[term->curs.x].truecolour = + term->curr_truecolour; + + 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; + cline->chars[term->curs.x].truecolour = + term->curr_truecolour; + + 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; + cline->chars[term->curs.x].truecolour = + term->curr_truecolour; + + 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); + } + return; + default: + return; + } + term->curs.x++; + if (term->curs.x >= linecols) { + term->curs.x = linecols - 1; + 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); +} + +static strbuf *term_input_data_from_unicode( + Terminal *term, const wchar_t *widebuf, int len) +{ + strbuf *buf = strbuf_new(); + + if (in_utf(term)) { + /* + * Translate input wide characters into UTF-8 to go in the + * terminal's input data queue. + */ + for (int i = 0; i < len; i++) { + unsigned long ch = widebuf[i]; + + if (IS_SURROGATE(ch)) { +#ifdef PLATFORM_IS_UTF16 + if (i+1 < len) { + unsigned long ch2 = widebuf[i+1]; + if (IS_SURROGATE_PAIR(ch, ch2)) { + ch = FROM_SURROGATES(ch, ch2); + i++; + } + } else +#endif + { + /* Unrecognised UTF-16 sequence */ + ch = '.'; + } + } + + char utf8_chr[6]; + put_data(buf, utf8_chr, encode_utf8(utf8_chr, ch)); + } + } else { + /* + * Call to the character-set subsystem to translate into + * whatever charset the terminal is currently configured in. + * + * Since the terminal doesn't currently support any multibyte + * character set other than UTF-8, we can assume here that + * there will be at most one output byte per input wchar_t. + * (But also we must allow space for the trailing NUL that + * wc_to_mb will write.) + */ + char *bufptr = strbuf_append(buf, len + 1); + int rv; + rv = wc_to_mb(term->ucsdata->line_codepage, 0, widebuf, len, + bufptr, len + 1, NULL, term->ucsdata); + strbuf_shrink_to(buf, rv < 0 ? 0 : rv); + } + + return buf; +} + +static strbuf *term_input_data_from_charset( + Terminal *term, int codepage, const char *str, int len) +{ + strbuf *buf; + + if (codepage < 0) { + buf = strbuf_new(); + put_data(buf, str, len); + } else { + int widesize = len * 2; /* allow for UTF-16 surrogates */ + wchar_t *widebuf = snewn(widesize, wchar_t); + int widelen = mb_to_wc(codepage, 0, str, len, widebuf, widesize); + buf = term_input_data_from_unicode(term, widebuf, widelen); + sfree(widebuf); + } + + return buf; +} + +static inline void term_bracketed_paste_start(Terminal *term) +{ + ptrlen seq = PTRLEN_LITERAL("\033[200~"); + if (term->ldisc) + ldisc_send(term->ldisc, seq.ptr, seq.len, false); + term->bracketed_paste_active = true; +} + +static inline void term_bracketed_paste_stop(Terminal *term) +{ + if (!term->bracketed_paste_active) + return; + + ptrlen seq = PTRLEN_LITERAL("\033[201~"); + if (term->ldisc) + ldisc_send(term->ldisc, seq.ptr, seq.len, false); + term->bracketed_paste_active = false; +} + +static inline void term_keyinput_internal( + Terminal *term, const void *buf, int len, bool interactive) +{ + if (term->srm_echo) { + /* + * Implement the terminal-level local echo behaviour that + * ECMA-48 specifies when terminal mode 12 is configured off + * (ESC[12l). In this mode, data input to the terminal via the + * keyboard is also added to the output buffer. But this + * doesn't apply to escape sequences generated as session + * input _within_ the terminal, e.g. in response to terminal + * query sequences, or the bracketing sequences of bracketed + * paste mode. Those will be sent directly via + * ldisc_send(term->ldisc, ...) and won't go through this + * function. + */ + + /* Mimic the special case of negative length in ldisc_send */ + int true_len = len >= 0 ? len : strlen(buf); + + bufchain_add(&term->inbuf, buf, true_len); + term_added_data(term); + } + if (interactive) + term_bracketed_paste_stop(term); + if (term->ldisc) + ldisc_send(term->ldisc, buf, len, interactive); + term_seen_key_event(term); +} + +unsigned long term_translate( + Terminal *term, struct term_utf8_decode *utf8, unsigned char c) +{ + if (in_utf(term)) { + switch (utf8->state) { + case 0: + if (c < 0x80) { + /* UTF-8 must be stateless so we ignore iso2022. */ + if (term->ucsdata->unitab_ctrl[c] != 0xFF) { + return term->ucsdata->unitab_ctrl[c]; + } else if ((term->utf8linedraw) && + (term->cset_attr[term->cset] == CSET_LINEDRW)) { + /* Linedraw characters are explicitly enabled */ + return c | CSET_LINEDRW; + } else { + return c | CSET_ASCII; + } + } else if ((c & 0xe0) == 0xc0) { + utf8->size = utf8->state = 1; + utf8->chr = (c & 0x1f); + } else if ((c & 0xf0) == 0xe0) { + utf8->size = utf8->state = 2; + utf8->chr = (c & 0x0f); + } else if ((c & 0xf8) == 0xf0) { + utf8->size = utf8->state = 3; + utf8->chr = (c & 0x07); + } else if ((c & 0xfc) == 0xf8) { + utf8->size = utf8->state = 4; + utf8->chr = (c & 0x03); + } else if ((c & 0xfe) == 0xfc) { + utf8->size = utf8->state = 5; + utf8->chr = (c & 0x01); + } else { + return UCSINVALID; + } + return UCSINCOMPLETE; + case 1: + case 2: + case 3: + case 4: + case 5: + if ((c & 0xC0) != 0x80) { + utf8->state = 0; + return UCSTRUNCATED; /* caller will then give us the + * same byte again */ + } + utf8->chr = (utf8->chr << 6) | (c & 0x3f); + if (--utf8->state) + return UCSINCOMPLETE; + + unsigned long t = utf8->chr; + + /* Is somebody trying to be evil! */ + if (t < 0x80 || + (t < 0x800 && utf8->size >= 2) || + (t < 0x10000 && utf8->size >= 3) || + (t < 0x200000 && utf8->size >= 4) || + (t < 0x4000000 && utf8->size >= 5)) + return UCSINVALID; + + /* Unicode line separator and paragraph separator are CR-LF */ + if (t == 0x2028 || t == 0x2029) + return 0x85; + + /* High controls are probably a Baaad idea too. */ + if (t < 0xA0) + return 0xFFFD; + + /* The UTF-16 surrogates are not nice either. */ + /* The standard give the option of decoding these: + * I don't want to! */ + if (t >= 0xD800 && t < 0xE000) + return UCSINVALID; + + /* ISO 10646 characters now limited to UTF-16 range. */ + if (t > 0x10FFFF) + return UCSINVALID; + + /* This is currently a TagPhobic application.. */ + if (t >= 0xE0000 && t <= 0xE007F) + return UCSINCOMPLETE; + + /* U+FEFF is best seen as a null. */ + if (t == 0xFEFF) + return UCSINCOMPLETE; + /* But U+FFFE is an error. */ + if (t == 0xFFFE || t == 0xFFFF) + return UCSINVALID; + + return t; + } + } else if (term->sco_acs && + (c!='\033' && c!='\012' && c!='\015' && c!='\b')) { + /* Are we in the nasty ACS mode? Note: no sco in utf mode. */ + if (term->sco_acs == 2) + c |= 0x80; + + return 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) + return term->ucsdata->unitab_ctrl[c]; + else + return c | CSET_LINEDRW; + break; + + case CSET_GBCHR: + /* If UK-ASCII, make the '#' a LineDraw Pound */ + if (c == '#') + return '}' | CSET_LINEDRW; + /* fall through */ + + case CSET_ASCII: + if (term->ucsdata->unitab_ctrl[c] != 0xFF) + return term->ucsdata->unitab_ctrl[c]; + else + return c | CSET_ASCII; + break; + case CSET_SCOACS: + if (c >= ' ') + return c | CSET_SCOACS; + break; + } + } + return c; +} + +/* + * 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; + size_t 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) { + ptrlen data = bufchain_prefix(&term->inbuf); + if (data.len > sizeof(localbuf)) + data.len = sizeof(localbuf); + memcpy(localbuf, data.ptr, data.len); + bufchain_consume(&term->inbuf, data.len); + nchars = data.len; + chars = localbuf; + assert(chars != NULL); + assert(nchars > 0); + } + c = *chars++; + nchars--; + + /* + * Optionally log the session traffic to a file. Useful for + * debugging and possibly also useful for actual logging. + */ + if (term->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; + } + } + + /* Do character-set translation. */ + if (term->termstate == TOPLEVEL) { + unsigned long t = term_translate(term, &term->utf8, c); + switch (t) { + case UCSINCOMPLETE: + continue; /* didn't complete a multibyte char */ + case UCSTRUNCATED: + unget = c; + /* fall through */ + case UCSINVALID: + c = UCSERR; + break; + default: + c = t; + 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 = 0; + 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->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) { + strbuf *buf = term_input_data_from_charset( + term, DEFAULT_CODEPAGE, + term->answerback, term->answerbacklen); + ldisc_send(term->ldisc, buf->s, buf->len, false); + strbuf_free(buf); + } + 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->bellovl_t) { + struct beeptime *tmp = term->beephead; + term->beephead = tmp->next; + sfree(tmp); + if (!term->beephead) + term->beeptail = NULL; + term->nbeeps--; + } + + if (term->bellovl && term->beep_overloaded && + ticks - term->lastbeep >= (unsigned)term->bellovl_s) { + /* + * If we're currently overloaded and the + * last beep was more than s seconds ago, + * leave overload mode. + */ + term->beep_overloaded = false; + } else if (term->bellovl && !term->beep_overloaded && + term->nbeeps >= term->bellovl_n) { + /* + * Now, if we have n or more beeps + * remaining in the queue, go into overload + * mode. + */ + term->beep_overloaded = true; + } + term->lastbeep = ticks; + + /* + * Perform an actual beep if we're not overloaded. + */ + if (!term->bellovl || !term->beep_overloaded) { + win_bell(term->win, term->beep); + + if (term->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)) + /* 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 = 0; + } + break; + case '\015': /* CR: Carriage return */ + term->curs.x = 0; + term->wrapnext = false; + seen_disp_event(term); + + if (term->crhaslf) { + if (term->curs.y == term->marg_b) + scroll(term, term->marg_t, term->marg_b, 1, true); + else if (term->curs.y < term->rows - 1) + term->curs.y++; + } + if (term->logctx) + logtraffic(term->logctx, (unsigned char) c, LGTYP_ASCII); + break; + case '\014': /* FF: Form feed */ + if (has_compat(SCOANSI)) { + move(term, 0, 0, 0); + erase_lots(term, false, false, true); + if (term->scroll_on_disp) + 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->lfhascr) + term->curs.x = 0; + term->wrapnext = false; + seen_disp_event(term); + 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 */ + term_display_graphic_char(term, c); + term->last_graphic_char = c; + 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 = 0; + 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; + term->esc_nargs = 1; + 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), false); + break; + case 'c': /* RIS: restore power-on settings */ + compatibility(VT100); + power_on(term, true); + if (term->ldisc) /* cause ldisc to notice changes */ + ldisc_echoedit_update(term->ldisc); + if (term->reset_132) { + if (!term->no_remote_resize) { + term->win_resize_pending = true; + term->win_resize_pending_w = 80; + term->win_resize_pending_h = term->rows; + term_schedule_update(term); + } + term->reset_132 = false; + } + if (term->scroll_on_disp) + 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); + check_line_size(term, ldata); + for (j = 0; j < term->cols; j++) { + copy_termchar(ldata, j, + &term->basic_erase_char); + ldata->chars[j].chr = 'E'; + } + ldata->lattr = LATTR_NORM; + } + if (term->scroll_on_disp) + 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; + termline *ldata; + + 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; + } + ldata = scrlineptr(term->curs.y); + check_line_size(term, ldata); + check_trust_status(term, ldata); + ldata->lattr = nlattr; + break; + } + /* GZD4: G0 designate 94-set */ + case ANSI('A', '('): + compatibility(VT100); + if (!term->no_remote_charset) + term->cset_attr[0] = CSET_GBCHR; + break; + case ANSI('B', '('): + compatibility(VT100); + if (!term->no_remote_charset) + term->cset_attr[0] = CSET_ASCII; + break; + case ANSI('0', '('): + compatibility(VT100); + if (!term->no_remote_charset) + term->cset_attr[0] = CSET_LINEDRW; + break; + case ANSI('U', '('): + compatibility(OTHER); + if (!term->no_remote_charset) + term->cset_attr[0] = CSET_SCOACS; + break; + /* G1D4: G1-designate 94-set */ + case ANSI('A', ')'): + compatibility(VT100); + if (!term->no_remote_charset) + term->cset_attr[1] = CSET_GBCHR; + break; + case ANSI('B', ')'): + compatibility(VT100); + if (!term->no_remote_charset) + term->cset_attr[1] = CSET_ASCII; + break; + case ANSI('0', ')'): + compatibility(VT100); + if (!term->no_remote_charset) + term->cset_attr[1] = CSET_LINEDRW; + break; + case ANSI('U', ')'): + compatibility(OTHER); + if (!term->no_remote_charset) + term->cset_attr[1] = CSET_SCOACS; + break; + /* DOCS: Designate other coding system */ + case ANSI('8', '%'): /* Old Linux code */ + case ANSI('G', '%'): + compatibility(OTHER); + if (!term->no_remote_charset) + term->utf = true; + break; + case ANSI('@', '%'): + compatibility(OTHER); + if (!term->no_remote_charset) + term->utf = false; + 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; + if (term->esc_args[term->esc_nargs - 1] <= + UINT_MAX / 10 && + term->esc_args[term->esc_nargs - 1] * 10 <= + UINT_MAX - c - '0') + term->esc_args[term->esc_nargs - 1] = + 10 * term->esc_args[term->esc_nargs - 1] + + c - '0'; + else + term->esc_args[term->esc_nargs - 1] = UINT_MAX; + } + 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 = 1; + else + term->esc_query = c; + term->termstate = SEEN_CSI; + } else +#define CLAMP(arg, lim) ((arg) = ((arg) > (lim)) ? (lim) : (arg)) + switch (ANSI(c, term->esc_query)) { + case 'A': /* CUU: move up N lines */ + CLAMP(term->esc_args[0], term->rows); + 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 */ + CLAMP(term->esc_args[0], term->rows); + move(term, term->curs.x, + term->curs.y + def(term->esc_args[0], 1), 1); + seen_disp_event(term); + break; + case 'b': /* REP: repeat previous grap */ + CLAMP(term->esc_args[0], term->rows * term->cols); + if (term->last_graphic_char) { + unsigned i; + for (i = 0; i < term->esc_args[0]; i++) + term_display_graphic_char( + term, term->last_graphic_char); + } + 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, + false); + break; + case 'a': /* HPR: move right N cols */ + compatibility(ANSI); + /* FALLTHROUGH */ + case 'C': /* CUF: Cursor right */ + CLAMP(term->esc_args[0], term->cols); + 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 */ + CLAMP(term->esc_args[0], term->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); + CLAMP(term->esc_args[0], term->rows); + 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); + CLAMP(term->esc_args[0], term->rows); + 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); + CLAMP(term->esc_args[0], term->cols); + 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); + CLAMP(term->esc_args[0], term->rows); + 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; + CLAMP(term->esc_args[0], term->rows); + CLAMP(term->esc_args[1], term->cols); + 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. */ + if (!term->no_remote_clearscroll) + term_clrsb(term); + } else { + i++; + if (i > 3) + i = 0; + erase_lots(term, false, !!(i & 2), !!(i & 1)); + } + if (term->scroll_on_disp) + 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); + CLAMP(term->esc_args[0], term->rows); + 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); + CLAMP(term->esc_args[0], term->rows); + 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); + CLAMP(term->esc_args[0], term->cols); + insch(term, def(term->esc_args[0], 1)); + seen_disp_event(term); + break; + case 'P': /* DCH: delete chars */ + compatibility(VT102); + CLAMP(term->esc_args[0], term->cols); + 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), false); + 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), + false); + } else if (term->esc_args[0] == 5) { + ldisc_send(term->ldisc, "\033[0n", 4, false); + } + } + break; + case 'h': /* SM: toggle modes to high */ + case ANSI_QUE('h'): + compatibility(VT100); + for (int 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); + char *printer; + if (term->esc_nargs != 1) break; + if (term->esc_args[0] == 5 && + (printer = conf_get_str(term->conf, + CONF_printer))[0]) { + term->printing = true; + term->only_printing = !term->esc_query; + term->print_state = 0; + term_print_setup(term, printer); + } 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); + for (int 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; + CLAMP(term->esc_args[0], term->rows); + CLAMP(term->esc_args[1], term->rows); + 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. + */ + for (int i = 0; i < term->esc_nargs; i++) + switch (def(term->esc_args[i], 0)) { + case 0: /* restore defaults */ + term->curr_attr = term->default_attr; + term->curr_truecolour = + term->basic_erase_char.truecolour; + break; + case 1: /* enable bold */ + compatibility(VT100AVO); + term->curr_attr |= ATTR_BOLD; + break; + case 2: /* enable dim */ + compatibility(OTHER); + term->curr_attr |= ATTR_DIM; + 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 9: /* enable strikethrough */ + term->curr_attr |= ATTR_STRIKE; + break; + case 10: /* SCO acs off */ + compatibility(SCOANSI); + if (term->no_remote_charset) break; + term->sco_acs = 0; break; + case 11: /* SCO acs on */ + compatibility(SCOANSI); + if (term->no_remote_charset) break; + term->sco_acs = 1; break; + case 12: /* SCO acs on, |0x80 */ + compatibility(SCOANSI); + if (term->no_remote_charset) break; + term->sco_acs = 2; break; + case 22: /* disable bold and dim */ + compatibility2(OTHER, VT220); + term->curr_attr &= ~(ATTR_BOLD | ATTR_DIM); + 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 29: /* disable strikethrough */ + term->curr_attr &= ~ATTR_STRIKE; + break; + case 30: + case 31: + case 32: + case 33: + case 34: + case 35: + case 36: + case 37: + /* foreground */ + term->curr_truecolour.fg.enabled = false; + term->curr_attr &= ~ATTR_FGMASK; + term->curr_attr |= + (term->esc_args[i] - 30)<curr_truecolour.fg.enabled = false; + term->curr_attr &= ~ATTR_FGMASK; + term->curr_attr |= + ((term->esc_args[i] - 90 + 8) + << ATTR_FGSHIFT); + break; + case 39: /* default-foreground */ + term->curr_truecolour.fg.enabled = false; + 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_truecolour.bg.enabled = false; + term->curr_attr &= ~ATTR_BGMASK; + term->curr_attr |= + (term->esc_args[i] - 40)<curr_truecolour.bg.enabled = false; + term->curr_attr &= ~ATTR_BGMASK; + term->curr_attr |= + ((term->esc_args[i] - 100 + 8) + << ATTR_BGSHIFT); + break; + case 49: /* default-background */ + term->curr_truecolour.bg.enabled = false; + term->curr_attr &= ~ATTR_BGMASK; + term->curr_attr |= ATTR_DEFBG; + break; + + /* + * 256-colour and true-colour + * sequences. A 256-colour + * foreground is selected by a + * sequence of 3 arguments in the + * form 38;5;n, where n is in the + * range 0-255. A true-colour RGB + * triple is selected by 5 args of + * the form 38;2;r;g;b. Replacing + * the initial 38 with 48 in both + * cases selects the same colour + * as the background. + */ + case 38: + 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); + term->curr_truecolour.fg = + optionalrgb_none; + i += 2; + } + if (i + 4 < term->esc_nargs && + term->esc_args[i + 1] == 2) { + parse_optionalrgb( + &term->curr_truecolour.fg, + term->esc_args + (i+2)); + i += 4; + } + break; + case 48: + 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); + term->curr_truecolour.bg = + optionalrgb_none; + i += 2; + } + if (i + 4 < term->esc_nargs && + term->esc_args[i+1] == 2) { + parse_optionalrgb( + &term->curr_truecolour.bg, + term->esc_args + (i+2)); + i += 4; + } + 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->no_remote_resize) { + term->win_resize_pending = true; + term->win_resize_pending_w = term->cols; + term->win_resize_pending_h = + def(term->esc_args[0], 24); + term_schedule_update(term); + } + 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 len; + char buf[80]; + const char *p; + case 1: + term->win_minimise_pending = true; + term->win_minimise_enable = false; + term_schedule_update(term); + break; + case 2: + term->win_minimise_pending = true; + term->win_minimise_enable = true; + term_schedule_update(term); + break; + case 3: + if (term->esc_nargs >= 3) { + if (!term->no_remote_resize) { + term->win_move_pending = true; + term->win_move_pending_x = + def(term->esc_args[1], 0); + term->win_move_pending_y = + def(term->esc_args[2], 0); + term_schedule_update(term); + } + } + 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 */ + term->win_zorder_pending = true; + term->win_zorder_top = true; + term_schedule_update(term); + break; + case 6: + /* move to bottom */ + term->win_zorder_pending = true; + term->win_zorder_top = false; + term_schedule_update(term); + break; + case 7: + term->win_refresh_pending = true; + term_schedule_update(term); + break; + case 8: + if (term->esc_nargs >= 3 && + !term->no_remote_resize) { + term->win_resize_pending = true; + term->win_resize_pending_w = + def(term->esc_args[2], + term->conf_width); + term->win_resize_pending_h = + def(term->esc_args[1], + term->conf_height); + term_schedule_update(term); + } + break; + case 9: + if (term->esc_nargs >= 2) { + term->win_maximise_pending = true; + term->win_maximise_enable = + term->esc_args[1]; + term_schedule_update(term); + } + break; + case 11: + if (term->ldisc) + ldisc_send(term->ldisc, term->minimised ? + "\033[2t" : "\033[1t", 4, + false); + break; + case 13: + if (term->ldisc) { + len = sprintf(buf, "\033[3;%u;%ut", + term->winpos_x, + term->winpos_y); + ldisc_send(term->ldisc, buf, len, false); + } + break; + case 14: + if (term->ldisc) { + len = sprintf(buf, "\033[4;%u;%ut", + term->winpixsize_y, + term->winpixsize_x); + ldisc_send(term->ldisc, buf, len, false); + } + break; + case 18: + if (term->ldisc) { + len = sprintf(buf, "\033[8;%d;%dt", + term->rows, term->cols); + ldisc_send(term->ldisc, buf, len, false); + } + 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->remote_qtitle_action != TITLE_NONE) { + if(term->remote_qtitle_action == TITLE_REAL) + p = term->icon_title; + else + p = EMPTY_WINDOW_TITLE; + len = strlen(p); + ldisc_send(term->ldisc, "\033]L", 3, + false); + ldisc_send(term->ldisc, p, len, false); + ldisc_send(term->ldisc, "\033\\", 2, + false); + } + break; + case 21: + if (term->ldisc && + term->remote_qtitle_action != TITLE_NONE) { + if(term->remote_qtitle_action == TITLE_REAL) + p = term->window_title; + else + p = EMPTY_WINDOW_TITLE; + len = strlen(p); + ldisc_send(term->ldisc, "\033]l", 3, + false); + ldisc_send(term->ldisc, p, len, false); + ldisc_send(term->ldisc, "\033\\", 2, + false); + } + break; + } + } + break; + case 'S': /* SU: Scroll up */ + CLAMP(term->esc_args[0], term->rows); + 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 */ + CLAMP(term->esc_args[0], term->rows); + 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->no_remote_resize) { + term->win_resize_pending = true; + term->win_resize_pending_w = term->cols; + term->win_resize_pending_h = + def(term->esc_args[0], term->conf_height); + term_schedule_update(term); + } + 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->no_remote_resize) { + term->win_resize_pending = true; + term->win_resize_pending_w = + def(term->esc_args[0], term->conf_width); + term->win_resize_pending_h = term->rows; + term_schedule_update(term); + } + 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); + CLAMP(term->esc_args[0], term->cols); + int n = def(term->esc_args[0], 1); + pos cursplus; + int p = term->curs.x; + termline *cline = scrlineptr(term->curs.y); + + check_trust_status(term, cline); + 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, false); + } + } + break; + case 'Z': { /* CBT */ + compatibility(OTHER); + CLAMP(term->esc_args[0], term->cols); + 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] < 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->curr_truecolour.fg = optionalrgb_none; + 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] < 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->curr_truecolour.bg = optionalrgb_none; + 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, "%u", 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->no_remote_resize) { + term->win_resize_pending = true; + term->win_resize_pending_w = + term->reset_132 ? 132 : 80; + term->win_resize_pending_h = 24; + term_schedule_update(term); + } + } +#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, false); + 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': + if (term->esc_args[term->esc_nargs-1] <= UINT_MAX / 10 && + term->esc_args[term->esc_nargs-1] * 10 <= UINT_MAX - c - '0') + term->esc_args[term->esc_nargs-1] = + 10 * term->esc_args[term->esc_nargs-1] + c - '0'; + else + term->esc_args[term->esc_nargs-1] = UINT_MAX; + break; + default: + /* + * _Most_ other characters here terminate the + * immediate parsing of the OSC sequence and go + * into OSC_STRING state, but we deal with a + * couple of exceptions first. + */ + if (c == 'L' && term->esc_args[0] == 2) { + /* + * Grotty hack to support xterm and DECterm title + * sequences concurrently. + */ + term->esc_args[0] = 1; + } else if (c == ';' && term->esc_nargs == 1 && + term->esc_args[0] == 4) { + /* + * xterm's OSC 4 sequence to query the current + * RGB value of a colour takes a second + * numeric argument which is easiest to parse + * using the existing system rather than in + * do_osc. + */ + term->esc_args[term->esc_nargs++] = 0; + } else { + 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) { + unsigned oscp_index = term->osc_string[0]; + assert(oscp_index < OSCP_NCOLOURS); + unsigned osc4_index = + colour_indices_oscp_to_osc4[oscp_index]; + + rgb *value = &term->subpalettes[SUBPAL_SESSION].values[ + osc4_index]; + value->r = term->osc_string[1] * 16 + term->osc_string[2]; + value->g = term->osc_string[3] * 16 + term->osc_string[4]; + value->b = term->osc_string[5] * 16 + term->osc_string[6]; + term->subpalettes[SUBPAL_SESSION].present[ + osc4_index] = true; + + palette_rebuild(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': + if (term->esc_args[0] <= UINT_MAX / 10 && + term->esc_args[0] * 10 <= UINT_MAX - c - '0') + term->esc_args[0] = 10 * term->esc_args[0] + c - '0'; + else + term->esc_args[0] = UINT_MAX; + 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); + if (term->scroll_on_disp) + 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, false); + 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->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); + if (term->scroll_on_disp) + 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); + if (term->scroll_on_disp) + 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 = true; + break; + case 'w': /* Autowrap off */ + /* compatibility(ATARI) */ + term->wrap = false; + break; + + case 'R': + /* compatibility(OTHER) */ + term->vt52_bold = false; + term->curr_attr = ATTR_DEFAULT; + term->curr_truecolour.fg = optionalrgb_none; + term->curr_truecolour.bg = optionalrgb_none; + 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->logflush && term->logctx) + logflush(term->logctx); +} + +/* + * Small subroutine to parse three consecutive escape-sequence + * arguments representing a true-colour RGB triple into an + * optionalrgb. + */ +static void parse_optionalrgb(optionalrgb *out, unsigned *values) +{ + out->enabled = true; + out->r = values[0] < 256 ? values[0] : 0; + out->g = values[1] < 256 ? values[1] : 0; + out->b = values[2] < 256 ? values[2] : 0; +} + +/* + * 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 bool term_bidi_cache_hit(Terminal *term, int line, + termchar *lbefore, int width, bool trusted) +{ + 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 */ + + if (term->pre_bidi_cache[line].trusted != trusted) + return false; /* line has wrong trust state */ + + 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, bool trusted) +{ + size_t i, j; + + if (!term->pre_bidi_cache || term->bidi_cache_size <= line) { + j = term->bidi_cache_size; + sgrowarray(term->pre_bidi_cache, term->bidi_cache_size, line); + 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].trusted = false; + term->post_bidi_cache[j].trusted = false; + 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].trusted = trusted; + term->pre_bidi_cache[line].chars = snewn(size, termchar); + term->post_bidi_cache[line].width = width; + term->post_bidi_cache[line].trusted = trusted; + 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 = j = 0; j < width; j += wcTo[i].nchars, i++) { + int p = wcTo[i].index; + + if (p != BIDI_CHAR_INDEX_NONE) { + assert(0 <= p && p < width); + + for (int x = 0; x < wcTo[i].nchars; x++) { + term->post_bidi_cache[line].backward[j+x] = p+x; + term->post_bidi_cache[line].forward[p+x] = j+x; + } + } + } +} + +/* + * 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->no_bidi || !term->no_arabicshaping || + (ldata->trusted && term->cols > TRUST_SIGIL_WIDTH)) { + + if (!term_bidi_cache_hit(term, scr_y, ldata->chars, term->cols, + ldata->trusted)) { + + 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; itcols ; it++) + { + unsigned long uc = (ldata->chars[it].chr); + + switch (uc & CSET_MASK) { + case CSET_LINEDRW: + if (!term->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 = + (unsigned int)uc; + term->wcFrom[it].index = it; + term->wcFrom[it].nchars = 1; + } + + if (ldata->trusted && term->cols > TRUST_SIGIL_WIDTH) { + memmove( + term->wcFrom + TRUST_SIGIL_WIDTH, term->wcFrom, + (term->cols - TRUST_SIGIL_WIDTH) * sizeof(*term->wcFrom)); + for (it = 0; it < TRUST_SIGIL_WIDTH; it++) { + term->wcFrom[it].origwc = term->wcFrom[it].wc = + (it == 0 ? TRUST_SIGIL_CHAR : + it == 1 ? UCSWIDE : ' '); + term->wcFrom[it].index = BIDI_CHAR_INDEX_NONE; + term->wcFrom[it].nchars = 1; + } + } + + int nbc = 0; + for (it = 0; it < term->cols; it++) { + term->wcFrom[nbc] = term->wcFrom[it]; + if (it+1 < term->cols && term->wcFrom[it+1].wc == UCSWIDE) { + term->wcFrom[nbc].nchars++; + it++; + } + nbc++; + } + + if(!term->no_bidi) + do_bidi(term->wcFrom, nbc); + + if(!term->no_arabicshaping) { + do_shape(term->wcFrom, term->wcTo, nbc); + } else { + /* If we're not calling do_shape, we must copy the + * data into wcTo anyway, unchanged */ + memcpy(term->wcTo, term->wcFrom, nbc * sizeof(*term->wcTo)); + } + + 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); + + int opos = 0; + for (it=0; itwcTo[it].index; + for (int j = 0; j < term->wcTo[it].nchars; j++) { + if (ipos != BIDI_CHAR_INDEX_NONE) { + term->ltemp[opos] = ldata->chars[ipos]; + if (term->ltemp[opos].cc_next) + term->ltemp[opos].cc_next -= opos - ipos; + + if (j > 0) + term->ltemp[opos].chr = UCSWIDE; + else if (term->wcTo[it].origwc != term->wcTo[it].wc) + term->ltemp[opos].chr = term->wcTo[it].wc; + } else { + term->ltemp[opos] = term->basic_erase_char; + term->ltemp[opos].chr = + j > 0 ? UCSWIDE : term->wcTo[it].origwc; + } + opos++; + } + } + assert(opos == term->cols); + term_bidi_cache_store(term, scr_y, ldata->chars, + term->ltemp, term->wcTo, + term->cols, ldata->size, ldata->trusted); + + lchars = term->ltemp; + } else { + lchars = term->post_bidi_cache[scr_y].chars; + } + } else { + lchars = NULL; + } + + return lchars; +} + +static void do_paint_draw(Terminal *term, termline *ldata, int x, int y, + wchar_t *ch, int ccount, + unsigned long attr, truecolour tc) +{ + if (ch[0] == TRUST_SIGIL_CHAR) { + assert(ldata->trusted); + assert(ccount == 1); + assert(attr & ATTR_WIDE); + wchar_t tch[2]; + tch[0] = tch[1] = L' '; + win_draw_text(term->win, x, y, tch, 2, term->basic_erase_char.attr, + ldata->lattr, term->basic_erase_char.truecolour); + win_draw_trust_sigil(term->win, x, y); + } else { + win_draw_text(term->win, x, y, ch, ccount, attr, ldata->lattr, tc); + if (attr & (TATTR_ACTCURS | TATTR_PASCURS)) + win_draw_cursor(term->win, x, y, ch, ccount, + attr, ldata->lattr, tc); + } +} + +/* + * Given a context, update the window. + */ +static void do_paint(Terminal *term) +{ + int i, j, our_curs_y, our_curs_x; + int rv, cursor; + pos scrpos; + wchar_t *ch; + size_t chlen; + 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, + * blinkpc, blink_is_real, tblinker, + * curs.y, curs.x, cblinker, blink_cur, cursor_on, has_focus, wrapnext + */ + + /* Has the cursor position or type changed ? */ + if (term->cursor_on) { + if (term->has_focus) { + if (term->cblinker || !term->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; + + /* The normal screen data */ + for (i = 0; i < term->rows; i++) { + termline *ldata; + termchar *lchars; + bool dirty_line, dirty_run, selected; + unsigned long attr = 0, cset = 0; + int start = 0; + int ccount = 0; + bool last_run_dirty = false; + int laststart; + bool dirtyrect; + int *backward; + truecolour tc; + + 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->ansi_colour) + tattr = (tattr & ~(ATTR_FGMASK | ATTR_BGMASK)) | + ATTR_DEFFG | ATTR_DEFBG; + + if (!term->xterm_256_colour) { + int colour; + colour = (tattr & ATTR_FGMASK) >> ATTR_FGSHIFT; + if (colour >= 16 && colour < 256) + tattr = (tattr &~ ATTR_FGMASK) | ATTR_DEFFG; + colour = (tattr & ATTR_BGMASK) >> ATTR_BGSHIFT; + if (colour >= 16 && colour < 256) + tattr = (tattr &~ ATTR_BGMASK) | ATTR_DEFBG; + } + + if (term->true_colour) { + tc = d->truecolour; + } else { + tc.fg = tc.bg = optionalrgb_none; + } + + 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) && + posPle_left(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 && + win_char_width(term->win, 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; + newline[j].truecolour = tc; + /* 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; + + tc = term->erase_char.truecolour; + for (j = 0; j < term->cols; j++) { + unsigned long tattr, tchar; + bool 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; + + if (!truecolour_equal(newline[j].truecolour, tc)) + break_run = true; + +#ifdef USES_VTLINE_HACK + /* Special hack for VT100 Linedraw glyphs */ + if ((tchar >= 0x23BA && tchar <= 0x23BD) || + (j > 0 && (newline[j-1].chr >= 0x23BA && + newline[j-1].chr <= 0x23BD))) + break_run = true; +#endif + + /* + * Separate out sequences of characters that have the + * same CSET, if that CSET is a magic one. + */ + 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; + + /* + * Break on both sides of a trust sigil. + */ + if (d->chr == TRUST_SIGIL_CHAR || + (j >= 2 && d[-1].chr == UCSWIDE && + d[-2].chr == TRUST_SIGIL_CHAR)) + 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 && + truecolour_equal( + term->disptext[i]->chars[j].truecolour, tc)) + break_run = true; + else if (!dirty_run && ccount == 1) + break_run = true; + } + + if (break_run) { + if ((dirty_run || last_run_dirty) && ccount > 0) + do_paint_draw(term, ldata, start, i, ch, ccount, attr, tc); + start = j; + ccount = 0; + attr = tattr; + tc = newline[j].truecolour; + 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; + } + + sgrowarrayn(ch, chlen, ccount, 2); + +#ifdef PLATFORM_IS_UTF16 + if (tchar > 0x10000 && tchar < 0x110000) { + ch[ccount++] = (wchar_t) HIGH_SURROGATE_OF(tchar); + ch[ccount++] = (wchar_t) LOW_SURROGATE_OF(tchar); + } else +#endif /* PLATFORM_IS_UTF16 */ + ch[ccount++] = (wchar_t) tchar; + + if (d->cc_next) { + termchar *dd = d; + + 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; + } + + sgrowarrayn(ch, chlen, ccount, 2); + +#ifdef PLATFORM_IS_UTF16 + if (schar > 0x10000 && schar < 0x110000) { + ch[ccount++] = (wchar_t) HIGH_SURROGATE_OF(schar); + ch[ccount++] = (wchar_t) LOW_SURROGATE_OF(schar); + } else +#endif /* PLATFORM_IS_UTF16 */ + ch[ccount++] = (wchar_t) schar; + } + + attr |= TATTR_COMBINING; + } + + if (do_copy) { + copy_termchar(term->disptext[i], j, d); + term->disptext[i]->chars[j].chr = tchar; + term->disptext[i]->chars[j].attr = tattr; + term->disptext[i]->chars[j].truecolour = tc; + 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_paint_draw(term, ldata, start, i, ch, ccount, attr, tc); + + 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, + int left, int top, int right, int bottom, bool 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); + } 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); + + 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; + term->win_scrollbar_update_pending = true; + term_schedule_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 { + size_t bufsize; /* amount of allocated space in textbuf/attrbuf */ + size_t 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 */ + truecolour *tcbuf; /* buffer for copied colours */ + truecolour *tcptr; /* = tcbuf + bufpos */ +} clip_workbuf; + +static void clip_addchar(clip_workbuf *b, wchar_t chr, int attr, truecolour tc) +{ + if (b->bufpos >= b->bufsize) { + sgrowarray(b->textbuf, b->bufsize, b->bufpos); + b->textptr = b->textbuf + b->bufpos; + b->attrbuf = sresize(b->attrbuf, b->bufsize, int); + b->attrptr = b->attrbuf + b->bufpos; + b->tcbuf = sresize(b->tcbuf, b->bufsize, truecolour); + b->tcptr = b->tcbuf + b->bufpos; + } + *b->textptr++ = chr; + *b->attrptr++ = attr; + *b->tcptr++ = tc; + b->bufpos++; +} + +static void clipme(Terminal *term, pos top, pos bottom, bool rect, bool desel, + const int *clipboards, int n_clipboards) +{ + clip_workbuf buf; + int old_top_x; + int attr; + truecolour tc; + + buf.bufsize = 5120; + buf.bufpos = 0; + buf.textptr = buf.textbuf = snewn(buf.bufsize, wchar_t); + buf.attrptr = buf.attrbuf = snewn(buf.bufsize, int); + buf.tcptr = buf.tcbuf = snewn(buf.bufsize, truecolour); + + old_top_x = top.x; /* needed for rect==1 */ + + while (poslt(top, bottom)) { + bool 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->trusted) { + /* A wrapped line with a trust sigil on it terminates + * a few characters earlier. */ + nlpos.x = (nlpos.x < TRUST_SIGIL_WIDTH ? 0 : + nlpos.x - TRUST_SIGIL_WIDTH); + } + 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, "", (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; + tc = ldata->chars[x].truecolour; + + switch (uc & CSET_MASK) { + case CSET_LINEDRW: + if (!term->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, tc); + + 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, term->basic_erase_char.truecolour); + } + top.y++; + top.x = rect ? old_top_x : 0; + + unlineptr(ldata); + } +#if SELECTION_NUL_TERMINATED + clip_addchar(&buf, 0, 0, term->basic_erase_char.truecolour); +#endif + /* Finally, transfer all that to the clipboard(s). */ + { + int i; + bool clip_local = false; + for (i = 0; i < n_clipboards; i++) { + if (clipboards[i] == CLIP_LOCAL) { + clip_local = true; + } else if (clipboards[i] != CLIP_NULL) { + win_clip_write( + term->win, clipboards[i], buf.textbuf, buf.attrbuf, + buf.tcbuf, buf.bufpos, desel); + } + } + if (clip_local) { + sfree(term->last_selected_text); + sfree(term->last_selected_attr); + sfree(term->last_selected_tc); + term->last_selected_text = buf.textbuf; + term->last_selected_attr = buf.attrbuf; + term->last_selected_tc = buf.tcbuf; + term->last_selected_len = buf.bufpos; + } else { + sfree(buf.textbuf); + sfree(buf.attrbuf); + sfree(buf.tcbuf); + } + } +} + +void term_copyall(Terminal *term, const int *clipboards, int n_clipboards) +{ + 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, false, true, clipboards, n_clipboards); +} + +static void paste_from_clip_local(void *vterm) +{ + Terminal *term = (Terminal *)vterm; + term_do_paste(term, term->last_selected_text, term->last_selected_len); +} + +void term_request_copy(Terminal *term, const int *clipboards, int n_clipboards) +{ + int i; + for (i = 0; i < n_clipboards; i++) { + assert(clipboards[i] != CLIP_LOCAL); + if (clipboards[i] != CLIP_NULL) { + win_clip_write(term->win, clipboards[i], + term->last_selected_text, term->last_selected_attr, + term->last_selected_tc, term->last_selected_len, + false); + } + } +} + +void term_request_paste(Terminal *term, int clipboard) +{ + switch (clipboard) { + case CLIP_NULL: + /* Do nothing: CLIP_NULL never has data in it. */ + break; + case CLIP_LOCAL: + queue_toplevel_callback(paste_from_clip_local, term); + break; + default: + win_clip_request_paste(term->win, clipboard); + break; + } +} + +/* + * 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; +} + +static int line_cols(Terminal *term, termline *ldata) +{ + int cols = term->cols; + if (ldata->trusted) { + cols -= TRUST_SIGIL_WIDTH; + } + if (ldata->lattr & LATTR_WRAPPED2) + cols--; + if (cols < 0) + cols = 0; + return cols; +} + +/* + * 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 + line_cols(term, ldata); + 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 = line_cols(term, ldata); + if (p.x < maxcols-1) { + if (wordtype(term, UCSGET(ldata->chars, p.x+1)) == wvalue) + p.x++; + else + break; + } else { + if (p.y+1 < term->rows && + (ldata->lattr & LATTR_WRAPPED)) { + termline *ldata2; + ldata2 = lineptr(p.y+1); + if (wordtype(term, UCSGET(ldata2->chars, 0)) + == wvalue) { + p.x = 0; + 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 = line_cols(term, ldata2); + 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); + } +} + +static void term_paste_callback(void *vterm) +{ + Terminal *term = (Terminal *)vterm; + + if (term->paste_len == 0) + return; + + while (term->paste_pos < term->paste_len) { + int n = 0; + while (n + term->paste_pos < term->paste_len) { + if (term->paste_buffer[term->paste_pos + n++] == '\015') + break; + } + if (term->ldisc) { + strbuf *buf = term_input_data_from_unicode( + term, term->paste_buffer + term->paste_pos, n); + term_keyinput_internal(term, buf->s, buf->len, false); + strbuf_free(buf); + } + term->paste_pos += n; + + if (term->paste_pos < term->paste_len) { + queue_toplevel_callback(term_paste_callback, term); + return; + } + } + term_bracketed_paste_stop(term); + sfree(term->paste_buffer); + term->paste_buffer = NULL; + term->paste_len = 0; +} + +/* + * Specialist string compare function. Returns true if the buffer of + * alen wide characters starting at a has as a prefix the buffer of + * blen characters starting at b. + */ +static bool wstartswith(const wchar_t *a, size_t alen, + const wchar_t *b, size_t blen) +{ + return alen >= blen && !wcsncmp(a, b, blen); +} + +void term_do_paste(Terminal *term, const wchar_t *data, int len) +{ + const wchar_t *p; + bool paste_controls = conf_get_bool(term->conf, CONF_paste_controls); + + /* + * Pasting data into the terminal counts as a keyboard event (for + * purposes of the 'Reset scrollback on keypress' config option), + * unless the paste is zero-length. + */ + if (len == 0) + return; + term_seen_key_event(term); + + if (term->paste_buffer) + sfree(term->paste_buffer); + term->paste_pos = term->paste_len = 0; + term->paste_buffer = snewn(len + 12, wchar_t); + + if (term->bracketed_paste) + term_bracketed_paste_start(term); + + p = data; + while (p < data + len) { + wchar_t wc = *p++; + + if (wc == sel_nl[0] && + wstartswith(p-1, data+len-(p-1), sel_nl, sel_nl_sz)) { + /* + * This is the (platform-dependent) sequence that the host + * OS uses to represent newlines in clipboard data. + * Normalise it to a press of CR. + */ + p += sel_nl_sz - 1; + wc = '\015'; + } + + if ((wc & ~(wint_t)0x9F) == 0) { + /* + * This is a control code, either in the range 0x00-0x1F + * or 0x80-0x9F. We reject all of these in pastecontrols + * mode, except for a small set of permitted ones. + */ + if (!paste_controls) { + /* In line with xterm 292, accepted control chars are: + * CR, LF, tab, backspace. (And DEL, i.e. 0x7F, but + * that's permitted by virtue of not matching the bit + * mask that got us into this if statement, so we + * don't have to permit it here. */ + static const unsigned mask = + (1<<13) | (1<<10) | (1<<9) | (1<<8); + + if (wc > 15 || !((mask >> wc) & 1)) + continue; + } + + if (wc == '\033' && term->bracketed_paste && + wstartswith(p-1, data+len-(p-1), L"\033[201~", 6)) { + /* + * Also, in bracketed-paste mode, reject the ESC + * character that begins the end-of-paste sequence. + */ + continue; + } + } + + term->paste_buffer[term->paste_len++] = wc; + } + + /* Assume a small paste will be OK in one go. */ + if (term->paste_len < 256) { + if (term->ldisc) { + strbuf *buf = term_input_data_from_unicode( + term, term->paste_buffer, term->paste_len); + term_keyinput_internal(term, buf->s, buf->len, false); + strbuf_free(buf); + } + if (term->paste_buffer) + sfree(term->paste_buffer); + term_bracketed_paste_stop(term); + term->paste_buffer = NULL; + term->paste_pos = term->paste_len = 0; + } + + queue_toplevel_callback(term_paste_callback, term); +} + +void term_mouse(Terminal *term, Mouse_Button braw, Mouse_Button bcooked, + Mouse_Action a, int x, int y, bool shift, bool ctrl, bool alt) +{ + pos selpoint; + termline *ldata; + bool raw_mouse = (term->xterm_mouse && + !term->no_mouse_rep && + !(term->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 && !raw_mouse && term->seltype != RECTANGULAR) { + /* + * When we're using the mouse for normal raster-based + * selection, dragging off the left edge of a terminal row + * is treated the same as the right-hand end of the + * previous row, in that it's considered to identify a + * point _before_ the first character on row y. + * + * But if the mouse action is going to be used for + * anything else - rectangular selection, or xterm mouse + * tracking - then we disable this special treatment. + */ + 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; + bool wheel; + char abuf[32]; + int len = 0; + + if (term->ldisc) { + + switch (braw) { + case MBT_LEFT: + encstate = 0x00; /* left button down */ + wheel = false; + break; + case MBT_MIDDLE: + encstate = 0x01; + wheel = false; + break; + case MBT_RIGHT: + encstate = 0x02; + wheel = false; + break; + case MBT_WHEEL_UP: + encstate = 0x40; + wheel = true; + break; + case MBT_WHEEL_DOWN: + encstate = 0x41; + wheel = true; + break; + default: + return; + } + if (wheel) { + /* For mouse wheel buttons, we only ever expect to see + * MA_CLICK actions, and we don't try to keep track of + * the buttons being 'pressed' (since without matching + * click/release pairs that's pointless). */ + if (a != MA_CLICK) + return; + } else switch (a) { + case MA_DRAG: + if (term->xterm_mouse == 1) + return; + encstate += 0x20; + break; + case MA_RELEASE: + /* If multiple extensions are enabled, the xterm 1006 is used, so it's okay to check for only that */ + if (!term->xterm_extended_mouse) + encstate = 0x03; + term->mouse_is_down = 0; + break; + case MA_CLICK: + if (term->mouse_is_down == braw) + return; + term->mouse_is_down = braw; + break; + default: + return; + } + if (shift) + encstate += 0x04; + if (ctrl) + encstate += 0x10; + r = y + 1; + c = x + 1; + + /* Check the extensions in decreasing order of preference. Encoding the release event above assumes that 1006 comes first. */ + if (term->xterm_extended_mouse) { + len = sprintf(abuf, "\033[<%d;%d;%d%c", encstate, c, r, a == MA_RELEASE ? 'm' : 'M'); + } else if (term->urxvt_extended_mouse) { + len = sprintf(abuf, "\033[%d;%d;%dM", encstate + 32, c, r); + } else if (c <= 223 && r <= 223) { + len = sprintf(abuf, "\033[M%c%c%c", encstate + 32, c + 32, r + 32); + } + if (len > 0) + ldisc_send(term->ldisc, abuf, len, false); + } + return; + } + + /* + * Set the selection type (rectangular or normal) at the start + * of a selection attempt, from the state of Alt. + */ + if (!alt ^ !term->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 (a == MA_DRAG && + (term->selstate == NO_SELECTION || term->selstate == SELECTED)) { + /* + * This can happen if a front end has passed us a MA_DRAG + * without a prior MA_CLICK. OS X GTK does so, for + * example, if the initial button press was eaten by the + * WM when it activated the window in the first place. The + * nicest thing to do in this situation is to ignore + * further drags, and wait for the user to click in the + * window again properly if they want to select. + */ + return; + } + 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->mouse_select_clipboards, + term->n_mouse_select_clipboards); + 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 + )) { + term_request_paste(term, term->mouse_paste_clipboard); + } + + /* + * Since terminal output is suppressed during drag-selects, we + * should make sure to write any pending output if one has just + * finished. + */ + if (term->selstate != DRAGGING) + term_out(term); + term_schedule_update(term); +} + +int format_arrow_key(char *buf, Terminal *term, int xkey, bool ctrl) +{ + char *p = buf; + + if (term->vt52_mode) + p += sprintf(p, "\x1B%c", xkey); + else { + bool app_flg = (term->app_cursor_keys && !term->no_applic_c); +#if 0 + /* + * RDB: VT100 & VT102 manuals both state the app cursor + * keys only work if the app keypad is on. + * + * 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(p, "\x1BO%c", xkey); + else + p += sprintf(p, "\x1B[%c", xkey); + } + + return p - buf; +} + +int format_function_key(char *buf, Terminal *term, int key_number, + bool shift, bool ctrl) +{ + char *p = buf; + + static const int key_number_to_tilde_code[] = { + -1, /* no such key as F0 */ + 11, 12, 13, 14, 15, /*gap*/ 17, 18, 19, 20, 21, /*gap*/ + 23, 24, 25, 26, /*gap*/ 28, 29, /*gap*/ 31, 32, 33, 34, + }; + + assert(key_number > 0); + assert(key_number < lenof(key_number_to_tilde_code)); + + int index = (shift && key_number <= 10) ? key_number + 10 : key_number; + int code = key_number_to_tilde_code[index]; + + if (term->funky_type == FUNKY_SCO) { + /* SCO function keys */ + static const char sco_codes[] = + "MNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz@[\\]^_`{"; + index = (key_number >= 1 && key_number <= 12) ? key_number - 1 : 0; + if (shift) index += 12; + if (ctrl) index += 24; + p += sprintf(p, "\x1B[%c", sco_codes[index]); + } else if ((term->vt52_mode || term->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(p, "\x1B%c", code + 'P' - 11 - offt); + else + p += sprintf(p, "\x1BO%c", code + 'P' - 11 - offt); + } else if (term->funky_type == FUNKY_LINUX && code >= 11 && code <= 15) { + p += sprintf(p, "\x1B[[%c", code + 'A' - 11); + } else if (term->funky_type == FUNKY_XTERM && code >= 11 && code <= 14) { + if (term->vt52_mode) + p += sprintf(p, "\x1B%c", code + 'P' - 11); + else + p += sprintf(p, "\x1BO%c", code + 'P' - 11); + } else { + p += sprintf(p, "\x1B[%d~", code); + } + + return p - buf; +} + +int format_small_keypad_key(char *buf, Terminal *term, SmallKeypadKey key) +{ + char *p = buf; + + int code; + switch (key) { + case SKK_HOME: code = 1; break; + case SKK_INSERT: code = 2; break; + case SKK_DELETE: code = 3; break; + case SKK_END: code = 4; break; + case SKK_PGUP: code = 5; break; + case SKK_PGDN: code = 6; break; + default: unreachable("bad small keypad key enum value"); + } + + /* Reorder edit keys to physical order */ + if (term->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(p, "\x1B%c", " HLMEIG"[code]); + } else if (term->funky_type == FUNKY_SCO) { + static const char codes[] = "HL.FIG"; + if (code == 3) { + *p++ = '\x7F'; + } else { + p += sprintf(p, "\x1B[%c", codes[code-1]); + } + } else if ((code == 1 || code == 4) && term->rxvt_homeend) { + p += sprintf(p, code == 1 ? "\x1B[H" : "\x1BOw"); + } else { + p += sprintf(p, "\x1B[%d~", code); + } + + return p - buf; +} + +int format_numeric_keypad_key(char *buf, Terminal *term, char key, + bool shift, bool ctrl) +{ + char *p = buf; + bool app_keypad = (term->app_keypad_keys && !term->no_applic_k); + + if (term->nethack_keypad && (key >= '1' && key <= '9')) { + static const char nh_base[] = "bjnh.lyku"; + char c = nh_base[key - '1']; + if (ctrl && c != '.') + c &= 0x1F; + else if (shift && c != '.') + c += 'A'-'a'; + *p++ = c; + } else { + int xkey = 0; + + if (term->funky_type == FUNKY_VT400 || + (term->funky_type <= FUNKY_LINUX && app_keypad)) { + switch (key) { + case 'G': xkey = 'P'; break; + case '/': xkey = 'Q'; break; + case '*': xkey = 'R'; break; + case '-': xkey = 'S'; break; + } + } + + if (app_keypad) { + switch (key) { + 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; + case '\r': xkey = 'M'; break; + + case '+': + /* + * 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... + */ + if (term->funky_type == FUNKY_XTERM) + xkey = shift ? 'l' : 'k'; + else + xkey = shift ? 'm' : 'l'; + break; + + case '/': + if (term->funky_type == FUNKY_XTERM) + xkey = 'o'; + break; + case '*': + if (term->funky_type == FUNKY_XTERM) + xkey = 'j'; + break; + case '-': + if (term->funky_type == FUNKY_XTERM) + xkey = 'm'; + break; + } + } + + if (xkey) { + if (term->vt52_mode) { + if (xkey >= 'P' && xkey <= 'S') + p += sprintf(p, "\x1B%c", xkey); + else + p += sprintf(p, "\x1B?%c", xkey); + } else + p += sprintf(p, "\x1BO%c", xkey); + } + } + + return p - buf; +} + +void term_keyinputw(Terminal *term, const wchar_t *widebuf, int len) +{ + strbuf *buf = term_input_data_from_unicode(term, widebuf, len); + if (buf->len) + term_keyinput_internal(term, buf->s, buf->len, true); + strbuf_free(buf); +} + +void term_keyinput(Terminal *term, int codepage, const void *str, int len) +{ + if (codepage < 0 || codepage == term->ucsdata->line_codepage) { + /* + * This text needs no translation, either because it's already + * in the right character set, or because we got the special + * codepage value -1 from our caller which means 'this data + * should be charset-agnostic, just send it raw' (for really + * simple things like control characters). + */ + term_keyinput_internal(term, str, len, true); + } else { + strbuf *buf = term_input_data_from_charset(term, codepage, str, len); + if (buf->len) + term_keyinput_internal(term, buf->s, buf->len, true); + strbuf_free(buf); + } +} + +void term_nopaste(Terminal *term) +{ + if (term->paste_len == 0) + return; + sfree(term->paste_buffer); + term_bracketed_paste_stop(term); + 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_lost_clipboard_ownership(Terminal *term, int clipboard) +{ + if (!(term->n_mouse_select_clipboards > 1 && + clipboard == term->mouse_select_clipboards[1])) + return; + + deselect(term); + term_update(term); + + /* + * Since terminal output is suppressed during drag-selects, we + * should make sure to write any pending output if one has just + * finished. + */ + if (term->selstate != DRAGGING) + term_out(term); +} + +static void term_added_data(Terminal *term) +{ + 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; + } +} + +size_t term_data(Terminal *term, const void *data, size_t len) +{ + bufchain_add(&term->inbuf, data, len); + term_added_data(term); + + /* + * 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; +} + +void term_provide_logctx(Terminal *term, LogContext *logctx) +{ + term->logctx = logctx; +} + +void term_set_focus(Terminal *term, bool 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) +{ + const char *val = NULL; + if (strcmp(mode, "ERASE") == 0) { + val = term->bksp_is_delete ? "^?" : "^H"; + } else if (strcmp(mode, "IUTF8") == 0) { + val = (term->ucsdata->line_codepage == CP_UTF8) ? "yes" : "no"; + } + /* FIXME: perhaps we should set ONLCR based on lfhascr as well? */ + /* FIXME: or ECHO and friends based on local echo state? */ + return dupstr(val); +} + +struct term_userpass_state { + size_t curr_prompt; + bool done_prompt; /* printed out prompt yet? */ +}; + +/* Tiny wrapper to make it easier to write lots of little strings */ +static inline void term_write(Terminal *term, ptrlen data) +{ + term_data(term, data.ptr, data.len); +} + +/* + * Signal that a prompts_t is done. This involves sending a + * notification to the caller, and also turning off our own callback + * that listens for more data arriving in the ldisc's input queue. + */ +static inline int signal_prompts_t(Terminal *term, prompts_t *p, int result) +{ + assert(p->callback && "Asynchronous userpass input requires a callback"); + queue_toplevel_callback(p->callback, p->callback_ctx); + ldisc_enable_prompt_callback(term->ldisc, NULL); + p->idata = result; + return result; +} + +/* + * Process some terminal data in the course of username/password + * input. + */ +int term_get_userpass_input(Terminal *term, prompts_t *p) +{ + if (!term->ldisc) { + /* Can't handle interactive prompts without an ldisc */ + return signal_prompts_t(term, p, 0); + } + + if (p->idata >= 0) { + /* We've already finished these prompts, so return the same + * result again */ + return p->idata; + } + + 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); + p->idata = -1; + s->curr_prompt = 0; + s->done_prompt = false; + /* We only print the `name' caption if we have to... */ + if (p->name_reqd && p->name) { + ptrlen plname = ptrlen_from_asciz(p->name); + term_write(term, plname); + if (!ptrlen_endswith(plname, PTRLEN_LITERAL("\n"), NULL)) + term_write(term, PTRLEN_LITERAL("\r\n")); + } + /* ...but we always print any `instruction'. */ + if (p->instruction) { + ptrlen plinst = ptrlen_from_asciz(p->instruction); + term_write(term, plinst); + if (!ptrlen_endswith(plinst, PTRLEN_LITERAL("\n"), NULL)) + term_write(term, PTRLEN_LITERAL("\r\n")); + } + /* + * Zero all the results, in case we abort half-way through. + */ + { + int i; + for (i = 0; i < (int)p->n_prompts; i++) + prompt_set_result(p->prompts[i], ""); + } + } + + while (s->curr_prompt < p->n_prompts) { + + prompt_t *pr = p->prompts[s->curr_prompt]; + bool finished_prompt = false; + + if (!s->done_prompt) { + term_write(term, ptrlen_from_asciz(pr->prompt)); + s->done_prompt = true; + } + + /* Breaking out here ensures that the prompt is printed even + * if we're now waiting for user data. */ + if (!ldisc_has_input_buffered(term->ldisc)) + break; + + /* FIXME: should we be using local-line-editing code instead? */ + while (!finished_prompt && ldisc_has_input_buffered(term->ldisc)) { + LdiscInputToken tok = ldisc_get_input_token(term->ldisc); + + char c; + if (tok.is_special) { + switch (tok.code) { + case SS_EOL: c = 13; break; + case SS_EC: c = 8; break; + case SS_IP: c = 3; break; + case SS_EOF: c = 3; break; + default: continue; + } + } else { + c = tok.chr; + } + + switch (c) { + case 10: + case 13: + term_write(term, PTRLEN_LITERAL("\r\n")); + /* go to next prompt, if any */ + s->curr_prompt++; + s->done_prompt = false; + finished_prompt = true; /* break out */ + break; + case 8: + case 127: + if (pr->result->len > 0) { + if (pr->echo) + term_write(term, PTRLEN_LITERAL("\b \b")); + strbuf_shrink_by(pr->result, 1); + } + break; + case 21: + case 27: + while (pr->result->len > 0) { + if (pr->echo) + term_write(term, PTRLEN_LITERAL("\b \b")); + strbuf_shrink_by(pr->result, 1); + } + break; + case 3: + case 4: + /* Immediate abort. */ + term_write(term, PTRLEN_LITERAL("\r\n")); + sfree(s); + p->data = NULL; + return signal_prompts_t(term, p, 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)) { + put_byte(pr->result, c); + if (pr->echo) + term_write(term, make_ptrlen(&c, 1)); + } + break; + } + } + + } + + if (s->curr_prompt < p->n_prompts) { + ldisc_enable_prompt_callback(term->ldisc, p); + return -1; /* more data required */ + } else { + sfree(s); + p->data = NULL; + return signal_prompts_t(term, p, +1); /* all done */ + } +} + +void term_notify_minimised(Terminal *term, bool minimised) +{ + term->minimised = minimised; +} + +void term_notify_palette_changed(Terminal *term) +{ + palette_reset(term, true); +} + +void term_notify_window_pos(Terminal *term, int x, int y) +{ + term->winpos_x = x; + term->winpos_y = y; +} + +void term_notify_window_size_pixels(Terminal *term, int x, int y) +{ + term->winpixsize_x = x; + term->winpixsize_y = y; +} diff --git a/terminal/terminal.h b/terminal/terminal.h new file mode 100644 index 00000000..c463f7a8 --- /dev/null +++ b/terminal/terminal.h @@ -0,0 +1,529 @@ +/* + * 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; +}; + +#define TRUST_SIGIL_WIDTH 3 +#define TRUST_SIGIL_CHAR 0xDFFE + +typedef struct { + int y, x; +} pos; + +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; + truecolour truecolour; + + /* + * 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) */ + bool temporary; /* true if decompressed from scrollback */ + int cc_free; /* offset to first cc in free list */ + struct termchar *chars; + bool trusted; +}; + +struct bidi_cache_entry { + int width; + bool trusted; + struct termchar *chars; + int *forward, *backward; /* the permutations of line positions */ +}; + +struct term_utf8_decode { + int state; /* Is there a pending UTF-8 character */ + int chr; /* and what is it so far? */ + int size; /* The size of the UTF character. */ +}; + +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; + bool beep_overloaded; + long lastbeep; + +#define TTYPE termchar +#define TSIZE (sizeof(TTYPE)) + + int default_attr, curr_attr, save_attr; + truecolour curr_truecolour, save_truecolour; + 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 */ + bool dec_om; /* DEC origin mode flag */ + bool wrap, wrapnext; /* wrap flags */ + bool insert; /* insert-mode flag */ + int cset; /* 0 or 1: which char set */ + int save_cset, save_csattr; /* saved with cursor position */ + bool save_utf, save_wnext; /* saved with cursor position */ + bool rvideo; /* global reverse video flag */ + unsigned long rvbell_startpoint; /* for ESC[?5hESC[?5l vbell */ + bool cursor_on; /* cursor enabled flag */ + bool reset_132; /* Flag ESC c resets to 80 cols */ + bool use_bce; /* Use Background coloured erase */ + bool cblinker; /* When blinking is the cursor on ? */ + bool tblinker; /* When the blinking text is on */ + bool blink_is_real; /* Actually blink blinking text */ + int sco_acs, save_sco_acs; /* CSI 10,11,12m -> OEM charset */ + bool vt52_bold; /* Force bold on non-bold colours */ + bool utf; /* Are we in toggleable UTF-8 mode? */ + term_utf8_decode utf8; /* If so, here's our decoding state */ + bool 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; + truecolour alt_save_truecolour; + int alt_save_cset, alt_save_csattr; + bool alt_save_utf; + bool alt_save_wnext; + int alt_save_sco_acs; + + int rows, cols, savelines; + bool has_focus; + bool in_vbell; + long vbell_end; + bool app_cursor_keys, app_keypad_keys, vt52_mode; + bool repeat_off, srm_echo, cr_lf_return; + bool seen_disp_event; + bool big_cursor; + + bool xterm_mouse_forbidden; + int xterm_mouse; /* send mouse messages to host */ + bool xterm_extended_mouse; + bool urxvt_extended_mouse; + int mouse_is_down; /* used while tracking mouse buttons */ + + bool bracketed_paste, bracketed_paste_active; + + int cset_attr[2]; + +/* + * Saved settings on the alternate screen. + */ + int alt_x, alt_y; + bool alt_wnext, alt_ins; + bool alt_om, alt_wrap; + int alt_cset, alt_sco_acs; + bool 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) ) + unsigned esc_args[ARGS_MAX]; + int esc_nargs; + int esc_query; +#define ANSI(x,y) ((x)+((y)*256)) +#define ANSI_QUE(x) ANSI(x,1) + +#define OSC_STR_MAX 2048 + int osc_strlen; + char osc_string[OSC_STR_MAX + 1]; + bool 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; + + Backend *backend; + + Ldisc *ldisc; + + TermWin *win; + + LogContext *logctx; + + struct unicode_data *ucsdata; + + unsigned long last_graphic_char; + + /* + * We maintain a full copy of a Conf here, not merely a pointer + * to it. That way, when we're passed a new one for + * reconfiguration, we can check the differences and adjust the + * _current_ setting of (e.g.) auto wrap mode rather than only + * the default. + */ + Conf *conf; + + /* + * GUI implementations of seat_output call 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 + * seat_output 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. + */ + bool in_term_out; + + /* + * We don't permit window updates too close together, to avoid CPU + * churn pointlessly redrawing the window faster than the user can + * read. So after an update, we set window_update_cooldown = true + * and schedule a timer to reset it to false. In between those + * times, window updates are not performed, and instead we set + * window_update_pending = true, which will remind us to perform + * the deferred redraw when the cooldown period ends and + * window_update_cooldown is reset to false. + */ + bool window_update_pending, window_update_cooldown; + long window_update_cooldown_end; + + /* + * Track pending blinks and tblinks. + */ + bool 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; + size_t bidi_cache_size; + + /* + * Current trust state, used to annotate every line of the + * terminal that a graphic character is output to. + */ + bool trusted; + + /* + * We copy a bunch of stuff out of the Conf structure into local + * fields in the Terminal structure, to avoid the repeated + * tree234 lookups which would be involved in fetching them from + * the former every time. + */ + bool ansi_colour; + char *answerback; + int answerbacklen; + bool no_arabicshaping; + int beep; + bool bellovl; + int bellovl_n; + int bellovl_s; + int bellovl_t; + bool no_bidi; + bool bksp_is_delete; + bool blink_cur; + bool blinktext; + bool cjk_ambig_wide; + int conf_height; + int conf_width; + bool crhaslf; + bool erase_to_scrollback; + int funky_type; + bool lfhascr; + bool logflush; + int logtype; + bool mouse_override; + bool nethack_keypad; + bool no_alt_screen; + bool no_applic_c; + bool no_applic_k; + bool no_dbackspace; + bool no_mouse_rep; + bool no_remote_charset; + bool no_remote_resize; + bool no_remote_wintitle; + bool no_remote_clearscroll; + bool rawcnp; + bool utf8linedraw; + bool rect_select; + int remote_qtitle_action; + bool rxvt_homeend; + bool scroll_on_disp; + bool scroll_on_key; + bool xterm_256_colour; + bool true_colour; + + wchar_t *last_selected_text; + int *last_selected_attr; + truecolour *last_selected_tc; + size_t last_selected_len; + int mouse_select_clipboards[N_CLIPBOARDS]; + int n_mouse_select_clipboards; + int mouse_paste_clipboard; + + char *window_title, *icon_title; + bool minimised; + + /* Multi-layered colour palette. The colours from Conf (plus the + * default xterm-256 ones that don't have Conf ids at all) have + * lowest priority, followed by platform overrides if any, + * followed by escape-sequence overrides during the session. */ + struct term_subpalette { + rgb values[OSC4_NCOLOURS]; + bool present[OSC4_NCOLOURS]; + } subpalettes[3]; +#define SUBPAL_CONF 0 +#define SUBPAL_PLATFORM 1 +#define SUBPAL_SESSION 2 + + /* The composite palette that we make out of the above */ + rgb palette[OSC4_NCOLOURS]; + + unsigned winpos_x, winpos_y, winpixsize_x, winpixsize_y; + + /* + * Assorted 'pending' flags for ancillary window changes performed + * in term_update. Generally, to trigger one of these operations, + * you set the pending flag and/or the parameters here, then call + * term_schedule_update. + */ + bool win_move_pending; + int win_move_pending_x, win_move_pending_y; + bool win_resize_pending; + int win_resize_pending_w, win_resize_pending_h; + bool win_zorder_pending; + bool win_zorder_top; + bool win_minimise_pending; + bool win_minimise_enable; + bool win_maximise_pending; + bool win_maximise_enable; + bool win_title_pending, win_icon_title_pending; + bool win_pointer_shape_pending; + bool win_pointer_shape_raw; + bool win_refresh_pending; + bool win_scrollbar_update_pending; + bool win_palette_pending; + unsigned win_palette_pending_min, win_palette_pending_limit; +}; + +static inline bool in_utf(Terminal *term) +{ + return term->utf || term->ucsdata->line_codepage == CP_UTF8; +} + +unsigned long term_translate( + Terminal *term, term_utf8_decode *utf8, unsigned char c); +static inline int term_char_width(Terminal *term, unsigned int c) +{ + return term->cjk_ambig_wide ? mk_wcwidth_cjk(c) : mk_wcwidth(c); +} + +/* + * UCSINCOMPLETE is returned from term_translate if it's successfully + * absorbed a byte but not emitted a complete character yet. + * UCSTRUNCATED indicates a truncated multibyte sequence (so the + * caller emits an error character and then calls term_translate again + * with the same input byte). UCSINVALID indicates some other invalid + * multibyte sequence, such as an overlong synonym, or a standalone + * continuation byte, or a completely illegal thing like 0xFE. These + * values are not stored in the terminal data structures at all. + */ +#define UCSINCOMPLETE 0x8000003FU /* '?' */ +#define UCSTRUNCATED 0x80000021U /* '!' */ +#define UCSINVALID 0x8000002AU /* '*' */ + +/* + * Maximum number of combining characters we're willing to store in a + * character cell. Our linked-list data representation permits an + * unlimited number of these in principle, but if we allowed that in + * practice then it would be an easy DoS to just squirt a squillion + * identical combining characters to someone's terminal and cause + * their PuTTY or pterm to consume lots of memory and CPU pointlessly. + * + * The precise figure of 32 is more or less arbitrary, but one point + * supporting it is UAX #15's comment that 30 combining characters is + * "significantly beyond what is required for any linguistic or + * technical usage". + */ +#define CC_LIMIT 32 + +/* ---------------------------------------------------------------------- + * Helper functions for dealing with the small 'pos' structure. + */ + +static inline bool poslt(pos p1, pos p2) +{ + if (p1.y != p2.y) + return p1.y < p2.y; + return p1.x < p2.x; +} + +static inline bool posle(pos p1, pos p2) +{ + if (p1.y != p2.y) + return p1.y < p2.y; + return p1.x <= p2.x; +} + +static inline bool poseq(pos p1, pos p2) +{ + return p1.y == p2.y && p1.x == p2.x; +} + +static inline int posdiff_fn(pos p1, pos p2, int cols) +{ + return (p1.y - p2.y) * (cols+1) + (p1.x - p2.x); +} + +/* Convenience wrapper on posdiff_fn which uses the 'Terminal *term' + * that more or less every function in terminal.c will have in scope. + * For safety's sake I include a TYPECHECK that ensures it really is a + * structure pointer of the right type. */ +#define GET_TERM_COLS TYPECHECK(term == (Terminal *)0, term->cols) +#define posdiff(p1,p2) posdiff_fn(p1, p2, GET_TERM_COLS) + +/* Product-order comparisons for rectangular block selection. */ + +static inline bool posPle(pos p1, pos p2) +{ + return p1.y <= p2.y && p1.x <= p2.x; +} + +static inline bool posPle_left(pos p1, pos p2) +{ + /* + * This function is used for checking whether a given character + * cell of the terminal ought to be highlighted as part of the + * selection, by comparing with term->selend. term->selend stores + * the location one space to the right of the last highlighted + * character. So we want to highlight the characters that are + * less-or-equal (in the product order) to the character just left + * of p2. + * + * (Setting up term->selend that way was the easiest way to get + * rectangular selection working at all, in a code base that had + * done lexicographic selection the way I happened to have done + * it.) + */ + return p1.y <= p2.y && p1.x < p2.x; +} + +static inline bool incpos_fn(pos *p, int cols) +{ + if (p->x == cols) { + p->x = 0; + p->y++; + return true; + } + p->x++; + return false; +} + +static inline bool decpos_fn(pos *p, int cols) +{ + if (p->x == 0) { + p->x = cols; + p->y--; + return true; + } + p->x--; + return false; +} + +/* Convenience wrappers on incpos and decpos which use term->cols + * (similarly to posdiff above), and also (for mild convenience and + * mostly historical inertia) let you leave off the & at every call + * site. */ +#define incpos(p) incpos_fn(&(p), GET_TERM_COLS) +#define decpos(p) decpos_fn(&(p), GET_TERM_COLS) + +#endif -- cgit v1.2.3 From d7548d044923a991914486432439612f0ddb982a Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 10 Oct 2021 14:31:04 +0100 Subject: Move bidi gettype main() into its own file. That's what I've usually been doing with any main()s I find under ifdef; there's no reason this should be an exception. If we're keeping it in the code at all, we should ensure it carries on compiling. I've also created a new header file bidi.h, containing pieces of the bidi definitions shared between bidi.c and the new source file. --- CMakeLists.txt | 4 ++ terminal/bidi.c | 122 +++++------------------------------------------- terminal/bidi.h | 60 ++++++++++++++++++++++++ terminal/bidi_gettype.c | 53 +++++++++++++++++++++ 4 files changed, 128 insertions(+), 111 deletions(-) create mode 100644 terminal/bidi.h create mode 100644 terminal/bidi_gettype.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 18c76aa4..debd6e1d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -82,6 +82,10 @@ add_executable(test_wildcard target_compile_definitions(test_wildcard PRIVATE TEST) target_link_libraries(test_wildcard utils ${platform_libraries}) +add_executable(bidi_gettype + terminal/bidi_gettype.c) +target_link_libraries(bidi_gettype guiterminal utils ${platform_libraries}) + add_executable(plink ${platform}/plink.c be_all_s.c) diff --git a/terminal/bidi.c b/terminal/bidi.c index 05d15b3d..4c55b86e 100644 --- a/terminal/bidi.c +++ b/terminal/bidi.c @@ -25,74 +25,18 @@ #include "putty.h" #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 ) +#include "bidi.h" /* function declarations */ static void flipThisRun( bidi_char *from, unsigned char *level, int max, int count); static int findIndexOfRun( unsigned char *level, int start, int count, int tlevel); -static unsigned char getType(int ch); static unsigned char setOverrideBits( unsigned char level, unsigned char override); static int getPreviousLevel(unsigned char *level, int from); static void doMirror(unsigned int *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, doesn't 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; @@ -354,7 +298,7 @@ perl -ne 'split ";"; $num = hex $_[0]; $type = $_[4];' \ UnicodeData.txt */ -static unsigned char getType(int ch) +unsigned char bidi_getType(int ch) { static const struct { int first, last, type; @@ -1036,7 +980,7 @@ bool is_rtl(int c) */ const int mask = (1<0 && (getType(line[j].wc) == WS)) { + while (j>0 && (bidi_getType(line[j].wc) == WS)) { j--; } if (j < (count-1)) { @@ -1564,14 +1508,14 @@ int do_bidi(bidi_char *line, int count) levels[j] = paragraphLevel; } for (i=0; i=i ; j--) { levels[j] = paragraphLevel; } @@ -1979,47 +1923,3 @@ static void doMirror(unsigned int *ch) } } } - -#ifdef TEST_GETTYPE - -#include -#include - -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", (unsigned)chr, typetoname[type].name); - } - - return 0; -} - -#endif diff --git a/terminal/bidi.h b/terminal/bidi.h new file mode 100644 index 00000000..eca80c21 --- /dev/null +++ b/terminal/bidi.h @@ -0,0 +1,60 @@ +/* + * Header file shared between bidi.c and its tests. Not used by + * anything outside the bidi subsystem. + */ + +#ifndef PUTTY_BIDI_H +#define PUTTY_BIDI_H + +#define LMASK 0x3F /* Embedding Level mask */ +#define OMASK 0xC0 /* Override mask */ +#define OISL 0x80 /* Override is L */ +#define OISR 0x40 /* Override is R */ + +/* 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 ) + +/* Function declarations used outside bidi.c */ +unsigned char bidi_getType(int 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, doesn't 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) */ +}; + +#endif /* PUTTY_BIDI_H */ diff --git a/terminal/bidi_gettype.c b/terminal/bidi_gettype.c new file mode 100644 index 00000000..a3b765ae --- /dev/null +++ b/terminal/bidi_gettype.c @@ -0,0 +1,53 @@ +/* + * Standalone test program that exposes the minibidi getType function. + */ + +#include +#include + +#include "putty.h" +#include "misc.h" +#include "bidi.h" + +void out_of_memory(void) +{ + fprintf(stderr, "out of memory!\n"); + exit(2); +} + +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 = bidi_getType(chr); + assert(typetoname[type].type == type); + printf("U+%04x: %s\n", (unsigned)chr, typetoname[type].name); + } + + return 0; +} -- cgit v1.2.3 From 804f32765fd909018ec22b27d5e3d7bffe802d72 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 10 Oct 2021 14:32:03 +0100 Subject: Make bidi type enums into list macros. This makes it easier to create the matching array of type names in bidi_gettype.c, and eliminates the need for an assertion to check the array matched the enum. And I'm about to need to add more types, so let's start by making that trivially easy. --- terminal/bidi.h | 63 ++++++++++++++++++++++++++----------------------- terminal/bidi_gettype.c | 30 ++++------------------- 2 files changed, 39 insertions(+), 54 deletions(-) diff --git a/terminal/bidi.h b/terminal/bidi.h index eca80c21..53ffbcd3 100644 --- a/terminal/bidi.h +++ b/terminal/bidi.h @@ -25,36 +25,41 @@ shapetypes[(xh)-SHAPE_FIRST].type : SU) /*))*/ /* Function declarations used outside bidi.c */ unsigned char bidi_getType(int ch); -/* character types */ -enum { - L, - LRE, - LRO, - R, - AL, - RLE, - RLO, - PDF, - EN, - ES, - ET, - AN, - CS, - NSM, - BN, - B, - S, - WS, - ON -}; +/* Bidi character types */ +#define BIDI_CHAR_TYPE_LIST(X) \ + X(L) \ + X(LRE) \ + X(LRO) \ + X(R) \ + X(AL) \ + X(RLE) \ + X(RLO) \ + X(PDF) \ + X(EN) \ + X(ES) \ + X(ET) \ + X(AN) \ + X(CS) \ + X(NSM) \ + X(BN) \ + X(B) \ + X(S) \ + X(WS) \ + X(ON) \ + /* end of list */ /* Shaping Types */ -enum { - SL, /* Left-Joining, doesn't 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) */ -}; +#define SHAPING_CHAR_TYPE_LIST(X) \ + X(SL) /* Left-Joining, doesn't exist in U+0600 - U+06FF */ \ + X(SR) /* Right-Joining, ie has Isolated, Final */ \ + X(SD) /* Dual-Joining, ie has Isolated, Final, Initial, Medial */ \ + X(SU) /* Non-Joining */ \ + X(SC) /* Join-Causing, like U+0640 (TATWEEL) */ \ + /* end of list */ + +#define ENUM_DECL(name) name, +typedef enum { BIDI_CHAR_TYPE_LIST(ENUM_DECL) N_BIDI_TYPES } BidiType; +typedef enum { SHAPING_CHAR_TYPE_LIST(ENUM_DECL) N_SHAPING_TYPES } ShapingType; +#undef ENUM_DECL #endif /* PUTTY_BIDI_H */ diff --git a/terminal/bidi_gettype.c b/terminal/bidi_gettype.c index a3b765ae..f3f5338e 100644 --- a/terminal/bidi_gettype.c +++ b/terminal/bidi_gettype.c @@ -15,38 +15,18 @@ void out_of_memory(void) exit(2); } +#define TYPETONAME(X) #X, +static const char *const typenames[] = { BIDI_CHAR_TYPE_LIST(TYPETONAME) }; +#undef TYPETONAME + 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 = bidi_getType(chr); - assert(typetoname[type].type == type); - printf("U+%04x: %s\n", (unsigned)chr, typetoname[type].name); + printf("U+%04x: %s\n", (unsigned)chr, typenames[type]); } return 0; -- cgit v1.2.3 From 3a3b264e9dc801a39cb9fba8aad65b9c71cc5908 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 10 Oct 2021 14:32:33 +0100 Subject: wcwidth.c: reflow existing lookup table. With one entry per line, it now takes up more vertical space, but it will be easier to see changes when I update it for a later Unicode version. --- utils/wcwidth.c | 188 ++++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 141 insertions(+), 47 deletions(-) diff --git a/utils/wcwidth.c b/utils/wcwidth.c index 6468fedd..37dacbc2 100644 --- a/utils/wcwidth.c +++ b/utils/wcwidth.c @@ -126,53 +126,147 @@ int mk_wcwidth(unsigned int ucs) /* sorted list of non-overlapping intervals of non-spacing characters */ /* generated by "uniset +cat=Me +cat=Mn +cat=Cf -00AD +1160-11FF +200B c" */ static const struct interval combining[] = { - { 0x0300, 0x036F }, { 0x0483, 0x0486 }, { 0x0488, 0x0489 }, - { 0x0591, 0x05BD }, { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 }, - { 0x05C4, 0x05C5 }, { 0x05C7, 0x05C7 }, { 0x0600, 0x0603 }, - { 0x0610, 0x0615 }, { 0x064B, 0x065E }, { 0x0670, 0x0670 }, - { 0x06D6, 0x06E4 }, { 0x06E7, 0x06E8 }, { 0x06EA, 0x06ED }, - { 0x070F, 0x070F }, { 0x0711, 0x0711 }, { 0x0730, 0x074A }, - { 0x07A6, 0x07B0 }, { 0x07EB, 0x07F3 }, { 0x0901, 0x0902 }, - { 0x093C, 0x093C }, { 0x0941, 0x0948 }, { 0x094D, 0x094D }, - { 0x0951, 0x0954 }, { 0x0962, 0x0963 }, { 0x0981, 0x0981 }, - { 0x09BC, 0x09BC }, { 0x09C1, 0x09C4 }, { 0x09CD, 0x09CD }, - { 0x09E2, 0x09E3 }, { 0x0A01, 0x0A02 }, { 0x0A3C, 0x0A3C }, - { 0x0A41, 0x0A42 }, { 0x0A47, 0x0A48 }, { 0x0A4B, 0x0A4D }, - { 0x0A70, 0x0A71 }, { 0x0A81, 0x0A82 }, { 0x0ABC, 0x0ABC }, - { 0x0AC1, 0x0AC5 }, { 0x0AC7, 0x0AC8 }, { 0x0ACD, 0x0ACD }, - { 0x0AE2, 0x0AE3 }, { 0x0B01, 0x0B01 }, { 0x0B3C, 0x0B3C }, - { 0x0B3F, 0x0B3F }, { 0x0B41, 0x0B43 }, { 0x0B4D, 0x0B4D }, - { 0x0B56, 0x0B56 }, { 0x0B82, 0x0B82 }, { 0x0BC0, 0x0BC0 }, - { 0x0BCD, 0x0BCD }, { 0x0C3E, 0x0C40 }, { 0x0C46, 0x0C48 }, - { 0x0C4A, 0x0C4D }, { 0x0C55, 0x0C56 }, { 0x0CBC, 0x0CBC }, - { 0x0CBF, 0x0CBF }, { 0x0CC6, 0x0CC6 }, { 0x0CCC, 0x0CCD }, - { 0x0CE2, 0x0CE3 }, { 0x0D41, 0x0D43 }, { 0x0D4D, 0x0D4D }, - { 0x0DCA, 0x0DCA }, { 0x0DD2, 0x0DD4 }, { 0x0DD6, 0x0DD6 }, - { 0x0E31, 0x0E31 }, { 0x0E34, 0x0E3A }, { 0x0E47, 0x0E4E }, - { 0x0EB1, 0x0EB1 }, { 0x0EB4, 0x0EB9 }, { 0x0EBB, 0x0EBC }, - { 0x0EC8, 0x0ECD }, { 0x0F18, 0x0F19 }, { 0x0F35, 0x0F35 }, - { 0x0F37, 0x0F37 }, { 0x0F39, 0x0F39 }, { 0x0F71, 0x0F7E }, - { 0x0F80, 0x0F84 }, { 0x0F86, 0x0F87 }, { 0x0F90, 0x0F97 }, - { 0x0F99, 0x0FBC }, { 0x0FC6, 0x0FC6 }, { 0x102D, 0x1030 }, - { 0x1032, 0x1032 }, { 0x1036, 0x1037 }, { 0x1039, 0x1039 }, - { 0x1058, 0x1059 }, { 0x1160, 0x11FF }, { 0x135F, 0x135F }, - { 0x1712, 0x1714 }, { 0x1732, 0x1734 }, { 0x1752, 0x1753 }, - { 0x1772, 0x1773 }, { 0x17B4, 0x17B5 }, { 0x17B7, 0x17BD }, - { 0x17C6, 0x17C6 }, { 0x17C9, 0x17D3 }, { 0x17DD, 0x17DD }, - { 0x180B, 0x180D }, { 0x18A9, 0x18A9 }, { 0x1920, 0x1922 }, - { 0x1927, 0x1928 }, { 0x1932, 0x1932 }, { 0x1939, 0x193B }, - { 0x1A17, 0x1A18 }, { 0x1B00, 0x1B03 }, { 0x1B34, 0x1B34 }, - { 0x1B36, 0x1B3A }, { 0x1B3C, 0x1B3C }, { 0x1B42, 0x1B42 }, - { 0x1B6B, 0x1B73 }, { 0x1DC0, 0x1DCA }, { 0x1DFE, 0x1DFF }, - { 0x200B, 0x200F }, { 0x202A, 0x202E }, { 0x2060, 0x2063 }, - { 0x206A, 0x206F }, { 0x20D0, 0x20EF }, { 0x302A, 0x302F }, - { 0x3099, 0x309A }, { 0xA806, 0xA806 }, { 0xA80B, 0xA80B }, - { 0xA825, 0xA826 }, { 0xFB1E, 0xFB1E }, { 0xFE00, 0xFE0F }, - { 0xFE20, 0xFE23 }, { 0xFEFF, 0xFEFF }, { 0xFFF9, 0xFFFB }, - { 0x10A01, 0x10A03 }, { 0x10A05, 0x10A06 }, { 0x10A0C, 0x10A0F }, - { 0x10A38, 0x10A3A }, { 0x10A3F, 0x10A3F }, { 0x1D167, 0x1D169 }, - { 0x1D173, 0x1D182 }, { 0x1D185, 0x1D18B }, { 0x1D1AA, 0x1D1AD }, - { 0x1D242, 0x1D244 }, { 0xE0001, 0xE0001 }, { 0xE0020, 0xE007F }, + { 0x0300, 0x036F }, + { 0x0483, 0x0486 }, + { 0x0488, 0x0489 }, + { 0x0591, 0x05BD }, + { 0x05BF, 0x05BF }, + { 0x05C1, 0x05C2 }, + { 0x05C4, 0x05C5 }, + { 0x05C7, 0x05C7 }, + { 0x0600, 0x0603 }, + { 0x0610, 0x0615 }, + { 0x064B, 0x065E }, + { 0x0670, 0x0670 }, + { 0x06D6, 0x06E4 }, + { 0x06E7, 0x06E8 }, + { 0x06EA, 0x06ED }, + { 0x070F, 0x070F }, + { 0x0711, 0x0711 }, + { 0x0730, 0x074A }, + { 0x07A6, 0x07B0 }, + { 0x07EB, 0x07F3 }, + { 0x0901, 0x0902 }, + { 0x093C, 0x093C }, + { 0x0941, 0x0948 }, + { 0x094D, 0x094D }, + { 0x0951, 0x0954 }, + { 0x0962, 0x0963 }, + { 0x0981, 0x0981 }, + { 0x09BC, 0x09BC }, + { 0x09C1, 0x09C4 }, + { 0x09CD, 0x09CD }, + { 0x09E2, 0x09E3 }, + { 0x0A01, 0x0A02 }, + { 0x0A3C, 0x0A3C }, + { 0x0A41, 0x0A42 }, + { 0x0A47, 0x0A48 }, + { 0x0A4B, 0x0A4D }, + { 0x0A70, 0x0A71 }, + { 0x0A81, 0x0A82 }, + { 0x0ABC, 0x0ABC }, + { 0x0AC1, 0x0AC5 }, + { 0x0AC7, 0x0AC8 }, + { 0x0ACD, 0x0ACD }, + { 0x0AE2, 0x0AE3 }, + { 0x0B01, 0x0B01 }, + { 0x0B3C, 0x0B3C }, + { 0x0B3F, 0x0B3F }, + { 0x0B41, 0x0B43 }, + { 0x0B4D, 0x0B4D }, + { 0x0B56, 0x0B56 }, + { 0x0B82, 0x0B82 }, + { 0x0BC0, 0x0BC0 }, + { 0x0BCD, 0x0BCD }, + { 0x0C3E, 0x0C40 }, + { 0x0C46, 0x0C48 }, + { 0x0C4A, 0x0C4D }, + { 0x0C55, 0x0C56 }, + { 0x0CBC, 0x0CBC }, + { 0x0CBF, 0x0CBF }, + { 0x0CC6, 0x0CC6 }, + { 0x0CCC, 0x0CCD }, + { 0x0CE2, 0x0CE3 }, + { 0x0D41, 0x0D43 }, + { 0x0D4D, 0x0D4D }, + { 0x0DCA, 0x0DCA }, + { 0x0DD2, 0x0DD4 }, + { 0x0DD6, 0x0DD6 }, + { 0x0E31, 0x0E31 }, + { 0x0E34, 0x0E3A }, + { 0x0E47, 0x0E4E }, + { 0x0EB1, 0x0EB1 }, + { 0x0EB4, 0x0EB9 }, + { 0x0EBB, 0x0EBC }, + { 0x0EC8, 0x0ECD }, + { 0x0F18, 0x0F19 }, + { 0x0F35, 0x0F35 }, + { 0x0F37, 0x0F37 }, + { 0x0F39, 0x0F39 }, + { 0x0F71, 0x0F7E }, + { 0x0F80, 0x0F84 }, + { 0x0F86, 0x0F87 }, + { 0x0F90, 0x0F97 }, + { 0x0F99, 0x0FBC }, + { 0x0FC6, 0x0FC6 }, + { 0x102D, 0x1030 }, + { 0x1032, 0x1032 }, + { 0x1036, 0x1037 }, + { 0x1039, 0x1039 }, + { 0x1058, 0x1059 }, + { 0x1160, 0x11FF }, + { 0x135F, 0x135F }, + { 0x1712, 0x1714 }, + { 0x1732, 0x1734 }, + { 0x1752, 0x1753 }, + { 0x1772, 0x1773 }, + { 0x17B4, 0x17B5 }, + { 0x17B7, 0x17BD }, + { 0x17C6, 0x17C6 }, + { 0x17C9, 0x17D3 }, + { 0x17DD, 0x17DD }, + { 0x180B, 0x180D }, + { 0x18A9, 0x18A9 }, + { 0x1920, 0x1922 }, + { 0x1927, 0x1928 }, + { 0x1932, 0x1932 }, + { 0x1939, 0x193B }, + { 0x1A17, 0x1A18 }, + { 0x1B00, 0x1B03 }, + { 0x1B34, 0x1B34 }, + { 0x1B36, 0x1B3A }, + { 0x1B3C, 0x1B3C }, + { 0x1B42, 0x1B42 }, + { 0x1B6B, 0x1B73 }, + { 0x1DC0, 0x1DCA }, + { 0x1DFE, 0x1DFF }, + { 0x200B, 0x200F }, + { 0x202A, 0x202E }, + { 0x2060, 0x2063 }, + { 0x206A, 0x206F }, + { 0x20D0, 0x20EF }, + { 0x302A, 0x302F }, + { 0x3099, 0x309A }, + { 0xA806, 0xA806 }, + { 0xA80B, 0xA80B }, + { 0xA825, 0xA826 }, + { 0xFB1E, 0xFB1E }, + { 0xFE00, 0xFE0F }, + { 0xFE20, 0xFE23 }, + { 0xFEFF, 0xFEFF }, + { 0xFFF9, 0xFFFB }, + { 0x10A01, 0x10A03 }, + { 0x10A05, 0x10A06 }, + { 0x10A0C, 0x10A0F }, + { 0x10A38, 0x10A3A }, + { 0x10A3F, 0x10A3F }, + { 0x1D167, 0x1D169 }, + { 0x1D173, 0x1D182 }, + { 0x1D185, 0x1D18B }, + { 0x1D1AA, 0x1D1AD }, + { 0x1D242, 0x1D244 }, + { 0xE0001, 0xE0001 }, + { 0xE0020, 0xE007F }, { 0xE0100, 0xE01EF } }; -- cgit v1.2.3 From 53e84b893323d2cae3a789bfa6560a5e1a92ccdd Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 10 Oct 2021 14:33:21 +0100 Subject: wcwidth.c: update to Unicode 14.0.0. I wasn't able to find the 'uniset' program mentioned in the comment that generated one of the tables, or at least I wasn't confident that I'd found the right thing of that name. So I rewrote the semantics of that command line in my own Perl and have included that in the revised version of the comment. --- utils/wcwidth.c | 315 +++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 275 insertions(+), 40 deletions(-) diff --git a/utils/wcwidth.c b/utils/wcwidth.c index 37dacbc2..7705d984 100644 --- a/utils/wcwidth.c +++ b/utils/wcwidth.c @@ -124,21 +124,46 @@ static bool bisearch(unsigned int ucs, const struct interval *table, int max) { int mk_wcwidth(unsigned int ucs) { /* sorted list of non-overlapping intervals of non-spacing characters */ - /* generated by "uniset +cat=Me +cat=Mn +cat=Cf -00AD +1160-11FF +200B c" */ + /* generated by the following Perl + * from the Unicode 14.0.0 data files available at: + * https://www.unicode.org/Public/14.0.0/ucd/ + +open DATA, "<", "UnicodeData.txt" || die "$!"; +while () { + @fields = split /;/; + $chr = hex $fields[0]; + $cat = $fields[2]; + $include = ($cat eq "Me" || $cat eq "Mn" || $cat eq "Cf"); + $include = 0 if ($chr == 0x00AD); + $include = 1 if (0x1160 <= $chr && $chr <= 0x11FF); + $include = 1 if ($chr == 0x200B); + $chrs{$chr} = $include; +} +close DATA; +for ($chr = 0; $chr < 0x110000; $chr++) { + if ($chrs{$chr}) { + $start = $chr; + $chr++ while $chrs{$chr}; + printf " { 0x%04X, 0x%04X },\n", $start, $chr-1; + } +} + + */ static const struct interval combining[] = { { 0x0300, 0x036F }, - { 0x0483, 0x0486 }, - { 0x0488, 0x0489 }, + { 0x0483, 0x0489 }, { 0x0591, 0x05BD }, { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 }, { 0x05C4, 0x05C5 }, { 0x05C7, 0x05C7 }, - { 0x0600, 0x0603 }, - { 0x0610, 0x0615 }, - { 0x064B, 0x065E }, + { 0x0600, 0x0605 }, + { 0x0610, 0x061A }, + { 0x061C, 0x061C }, + { 0x064B, 0x065F }, { 0x0670, 0x0670 }, - { 0x06D6, 0x06E4 }, + { 0x06D6, 0x06DD }, + { 0x06DF, 0x06E4 }, { 0x06E7, 0x06E8 }, { 0x06EA, 0x06ED }, { 0x070F, 0x070F }, @@ -146,49 +171,72 @@ int mk_wcwidth(unsigned int ucs) { 0x0730, 0x074A }, { 0x07A6, 0x07B0 }, { 0x07EB, 0x07F3 }, - { 0x0901, 0x0902 }, + { 0x07FD, 0x07FD }, + { 0x0816, 0x0819 }, + { 0x081B, 0x0823 }, + { 0x0825, 0x0827 }, + { 0x0829, 0x082D }, + { 0x0859, 0x085B }, + { 0x0890, 0x0891 }, + { 0x0898, 0x089F }, + { 0x08CA, 0x0902 }, + { 0x093A, 0x093A }, { 0x093C, 0x093C }, { 0x0941, 0x0948 }, { 0x094D, 0x094D }, - { 0x0951, 0x0954 }, + { 0x0951, 0x0957 }, { 0x0962, 0x0963 }, { 0x0981, 0x0981 }, { 0x09BC, 0x09BC }, { 0x09C1, 0x09C4 }, { 0x09CD, 0x09CD }, { 0x09E2, 0x09E3 }, + { 0x09FE, 0x09FE }, { 0x0A01, 0x0A02 }, { 0x0A3C, 0x0A3C }, { 0x0A41, 0x0A42 }, { 0x0A47, 0x0A48 }, { 0x0A4B, 0x0A4D }, + { 0x0A51, 0x0A51 }, { 0x0A70, 0x0A71 }, + { 0x0A75, 0x0A75 }, { 0x0A81, 0x0A82 }, { 0x0ABC, 0x0ABC }, { 0x0AC1, 0x0AC5 }, { 0x0AC7, 0x0AC8 }, { 0x0ACD, 0x0ACD }, { 0x0AE2, 0x0AE3 }, + { 0x0AFA, 0x0AFF }, { 0x0B01, 0x0B01 }, { 0x0B3C, 0x0B3C }, { 0x0B3F, 0x0B3F }, - { 0x0B41, 0x0B43 }, + { 0x0B41, 0x0B44 }, { 0x0B4D, 0x0B4D }, - { 0x0B56, 0x0B56 }, + { 0x0B55, 0x0B56 }, + { 0x0B62, 0x0B63 }, { 0x0B82, 0x0B82 }, { 0x0BC0, 0x0BC0 }, { 0x0BCD, 0x0BCD }, + { 0x0C00, 0x0C00 }, + { 0x0C04, 0x0C04 }, + { 0x0C3C, 0x0C3C }, { 0x0C3E, 0x0C40 }, { 0x0C46, 0x0C48 }, { 0x0C4A, 0x0C4D }, { 0x0C55, 0x0C56 }, + { 0x0C62, 0x0C63 }, + { 0x0C81, 0x0C81 }, { 0x0CBC, 0x0CBC }, { 0x0CBF, 0x0CBF }, { 0x0CC6, 0x0CC6 }, { 0x0CCC, 0x0CCD }, { 0x0CE2, 0x0CE3 }, - { 0x0D41, 0x0D43 }, + { 0x0D00, 0x0D01 }, + { 0x0D3B, 0x0D3C }, + { 0x0D41, 0x0D44 }, { 0x0D4D, 0x0D4D }, + { 0x0D62, 0x0D63 }, + { 0x0D81, 0x0D81 }, { 0x0DCA, 0x0DCA }, { 0x0DD2, 0x0DD4 }, { 0x0DD6, 0x0DD6 }, @@ -196,8 +244,7 @@ int mk_wcwidth(unsigned int ucs) { 0x0E34, 0x0E3A }, { 0x0E47, 0x0E4E }, { 0x0EB1, 0x0EB1 }, - { 0x0EB4, 0x0EB9 }, - { 0x0EBB, 0x0EBC }, + { 0x0EB4, 0x0EBC }, { 0x0EC8, 0x0ECD }, { 0x0F18, 0x0F19 }, { 0x0F35, 0x0F35 }, @@ -206,18 +253,24 @@ int mk_wcwidth(unsigned int ucs) { 0x0F71, 0x0F7E }, { 0x0F80, 0x0F84 }, { 0x0F86, 0x0F87 }, - { 0x0F90, 0x0F97 }, + { 0x0F8D, 0x0F97 }, { 0x0F99, 0x0FBC }, { 0x0FC6, 0x0FC6 }, { 0x102D, 0x1030 }, - { 0x1032, 0x1032 }, - { 0x1036, 0x1037 }, - { 0x1039, 0x1039 }, + { 0x1032, 0x1037 }, + { 0x1039, 0x103A }, + { 0x103D, 0x103E }, { 0x1058, 0x1059 }, + { 0x105E, 0x1060 }, + { 0x1071, 0x1074 }, + { 0x1082, 0x1082 }, + { 0x1085, 0x1086 }, + { 0x108D, 0x108D }, + { 0x109D, 0x109D }, { 0x1160, 0x11FF }, - { 0x135F, 0x135F }, + { 0x135D, 0x135F }, { 0x1712, 0x1714 }, - { 0x1732, 0x1734 }, + { 0x1732, 0x1733 }, { 0x1752, 0x1753 }, { 0x1772, 0x1773 }, { 0x17B4, 0x17B5 }, @@ -225,55 +278,232 @@ int mk_wcwidth(unsigned int ucs) { 0x17C6, 0x17C6 }, { 0x17C9, 0x17D3 }, { 0x17DD, 0x17DD }, - { 0x180B, 0x180D }, + { 0x180B, 0x180F }, + { 0x1885, 0x1886 }, { 0x18A9, 0x18A9 }, { 0x1920, 0x1922 }, { 0x1927, 0x1928 }, { 0x1932, 0x1932 }, { 0x1939, 0x193B }, { 0x1A17, 0x1A18 }, + { 0x1A1B, 0x1A1B }, + { 0x1A56, 0x1A56 }, + { 0x1A58, 0x1A5E }, + { 0x1A60, 0x1A60 }, + { 0x1A62, 0x1A62 }, + { 0x1A65, 0x1A6C }, + { 0x1A73, 0x1A7C }, + { 0x1A7F, 0x1A7F }, + { 0x1AB0, 0x1ACE }, { 0x1B00, 0x1B03 }, { 0x1B34, 0x1B34 }, { 0x1B36, 0x1B3A }, { 0x1B3C, 0x1B3C }, { 0x1B42, 0x1B42 }, { 0x1B6B, 0x1B73 }, - { 0x1DC0, 0x1DCA }, - { 0x1DFE, 0x1DFF }, + { 0x1B80, 0x1B81 }, + { 0x1BA2, 0x1BA5 }, + { 0x1BA8, 0x1BA9 }, + { 0x1BAB, 0x1BAD }, + { 0x1BE6, 0x1BE6 }, + { 0x1BE8, 0x1BE9 }, + { 0x1BED, 0x1BED }, + { 0x1BEF, 0x1BF1 }, + { 0x1C2C, 0x1C33 }, + { 0x1C36, 0x1C37 }, + { 0x1CD0, 0x1CD2 }, + { 0x1CD4, 0x1CE0 }, + { 0x1CE2, 0x1CE8 }, + { 0x1CED, 0x1CED }, + { 0x1CF4, 0x1CF4 }, + { 0x1CF8, 0x1CF9 }, + { 0x1DC0, 0x1DFF }, { 0x200B, 0x200F }, { 0x202A, 0x202E }, - { 0x2060, 0x2063 }, - { 0x206A, 0x206F }, - { 0x20D0, 0x20EF }, - { 0x302A, 0x302F }, + { 0x2060, 0x2064 }, + { 0x2066, 0x206F }, + { 0x20D0, 0x20F0 }, + { 0x2CEF, 0x2CF1 }, + { 0x2D7F, 0x2D7F }, + { 0x2DE0, 0x2DFF }, + { 0x302A, 0x302D }, { 0x3099, 0x309A }, + { 0xA66F, 0xA672 }, + { 0xA674, 0xA67D }, + { 0xA69E, 0xA69F }, + { 0xA6F0, 0xA6F1 }, + { 0xA802, 0xA802 }, { 0xA806, 0xA806 }, { 0xA80B, 0xA80B }, { 0xA825, 0xA826 }, + { 0xA82C, 0xA82C }, + { 0xA8C4, 0xA8C5 }, + { 0xA8E0, 0xA8F1 }, + { 0xA8FF, 0xA8FF }, + { 0xA926, 0xA92D }, + { 0xA947, 0xA951 }, + { 0xA980, 0xA982 }, + { 0xA9B3, 0xA9B3 }, + { 0xA9B6, 0xA9B9 }, + { 0xA9BC, 0xA9BD }, + { 0xA9E5, 0xA9E5 }, + { 0xAA29, 0xAA2E }, + { 0xAA31, 0xAA32 }, + { 0xAA35, 0xAA36 }, + { 0xAA43, 0xAA43 }, + { 0xAA4C, 0xAA4C }, + { 0xAA7C, 0xAA7C }, + { 0xAAB0, 0xAAB0 }, + { 0xAAB2, 0xAAB4 }, + { 0xAAB7, 0xAAB8 }, + { 0xAABE, 0xAABF }, + { 0xAAC1, 0xAAC1 }, + { 0xAAEC, 0xAAED }, + { 0xAAF6, 0xAAF6 }, + { 0xABE5, 0xABE5 }, + { 0xABE8, 0xABE8 }, + { 0xABED, 0xABED }, { 0xFB1E, 0xFB1E }, { 0xFE00, 0xFE0F }, - { 0xFE20, 0xFE23 }, + { 0xFE20, 0xFE2F }, { 0xFEFF, 0xFEFF }, { 0xFFF9, 0xFFFB }, + { 0x101FD, 0x101FD }, + { 0x102E0, 0x102E0 }, + { 0x10376, 0x1037A }, { 0x10A01, 0x10A03 }, { 0x10A05, 0x10A06 }, { 0x10A0C, 0x10A0F }, { 0x10A38, 0x10A3A }, { 0x10A3F, 0x10A3F }, + { 0x10AE5, 0x10AE6 }, + { 0x10D24, 0x10D27 }, + { 0x10EAB, 0x10EAC }, + { 0x10F46, 0x10F50 }, + { 0x10F82, 0x10F85 }, + { 0x11001, 0x11001 }, + { 0x11038, 0x11046 }, + { 0x11070, 0x11070 }, + { 0x11073, 0x11074 }, + { 0x1107F, 0x11081 }, + { 0x110B3, 0x110B6 }, + { 0x110B9, 0x110BA }, + { 0x110BD, 0x110BD }, + { 0x110C2, 0x110C2 }, + { 0x110CD, 0x110CD }, + { 0x11100, 0x11102 }, + { 0x11127, 0x1112B }, + { 0x1112D, 0x11134 }, + { 0x11173, 0x11173 }, + { 0x11180, 0x11181 }, + { 0x111B6, 0x111BE }, + { 0x111C9, 0x111CC }, + { 0x111CF, 0x111CF }, + { 0x1122F, 0x11231 }, + { 0x11234, 0x11234 }, + { 0x11236, 0x11237 }, + { 0x1123E, 0x1123E }, + { 0x112DF, 0x112DF }, + { 0x112E3, 0x112EA }, + { 0x11300, 0x11301 }, + { 0x1133B, 0x1133C }, + { 0x11340, 0x11340 }, + { 0x11366, 0x1136C }, + { 0x11370, 0x11374 }, + { 0x11438, 0x1143F }, + { 0x11442, 0x11444 }, + { 0x11446, 0x11446 }, + { 0x1145E, 0x1145E }, + { 0x114B3, 0x114B8 }, + { 0x114BA, 0x114BA }, + { 0x114BF, 0x114C0 }, + { 0x114C2, 0x114C3 }, + { 0x115B2, 0x115B5 }, + { 0x115BC, 0x115BD }, + { 0x115BF, 0x115C0 }, + { 0x115DC, 0x115DD }, + { 0x11633, 0x1163A }, + { 0x1163D, 0x1163D }, + { 0x1163F, 0x11640 }, + { 0x116AB, 0x116AB }, + { 0x116AD, 0x116AD }, + { 0x116B0, 0x116B5 }, + { 0x116B7, 0x116B7 }, + { 0x1171D, 0x1171F }, + { 0x11722, 0x11725 }, + { 0x11727, 0x1172B }, + { 0x1182F, 0x11837 }, + { 0x11839, 0x1183A }, + { 0x1193B, 0x1193C }, + { 0x1193E, 0x1193E }, + { 0x11943, 0x11943 }, + { 0x119D4, 0x119D7 }, + { 0x119DA, 0x119DB }, + { 0x119E0, 0x119E0 }, + { 0x11A01, 0x11A0A }, + { 0x11A33, 0x11A38 }, + { 0x11A3B, 0x11A3E }, + { 0x11A47, 0x11A47 }, + { 0x11A51, 0x11A56 }, + { 0x11A59, 0x11A5B }, + { 0x11A8A, 0x11A96 }, + { 0x11A98, 0x11A99 }, + { 0x11C30, 0x11C36 }, + { 0x11C38, 0x11C3D }, + { 0x11C3F, 0x11C3F }, + { 0x11C92, 0x11CA7 }, + { 0x11CAA, 0x11CB0 }, + { 0x11CB2, 0x11CB3 }, + { 0x11CB5, 0x11CB6 }, + { 0x11D31, 0x11D36 }, + { 0x11D3A, 0x11D3A }, + { 0x11D3C, 0x11D3D }, + { 0x11D3F, 0x11D45 }, + { 0x11D47, 0x11D47 }, + { 0x11D90, 0x11D91 }, + { 0x11D95, 0x11D95 }, + { 0x11D97, 0x11D97 }, + { 0x11EF3, 0x11EF4 }, + { 0x13430, 0x13438 }, + { 0x16AF0, 0x16AF4 }, + { 0x16B30, 0x16B36 }, + { 0x16F4F, 0x16F4F }, + { 0x16F8F, 0x16F92 }, + { 0x16FE4, 0x16FE4 }, + { 0x1BC9D, 0x1BC9E }, + { 0x1BCA0, 0x1BCA3 }, + { 0x1CF00, 0x1CF2D }, + { 0x1CF30, 0x1CF46 }, { 0x1D167, 0x1D169 }, { 0x1D173, 0x1D182 }, { 0x1D185, 0x1D18B }, { 0x1D1AA, 0x1D1AD }, { 0x1D242, 0x1D244 }, + { 0x1DA00, 0x1DA36 }, + { 0x1DA3B, 0x1DA6C }, + { 0x1DA75, 0x1DA75 }, + { 0x1DA84, 0x1DA84 }, + { 0x1DA9B, 0x1DA9F }, + { 0x1DAA1, 0x1DAAF }, + { 0x1E000, 0x1E006 }, + { 0x1E008, 0x1E018 }, + { 0x1E01B, 0x1E021 }, + { 0x1E023, 0x1E024 }, + { 0x1E026, 0x1E02A }, + { 0x1E130, 0x1E136 }, + { 0x1E2AE, 0x1E2AE }, + { 0x1E2EC, 0x1E2EF }, + { 0x1E8D0, 0x1E8D6 }, + { 0x1E944, 0x1E94A }, { 0xE0001, 0xE0001 }, { 0xE0020, 0xE007F }, - { 0xE0100, 0xE01EF } + { 0xE0100, 0xE01EF }, }; /* A sorted list of intervals of double-width characters generated by: * https://raw.githubusercontent.com/GNOME/glib/37d4c2941bd0326b8b6e6bb22c81bd424fcc040b/glib/gen-unicode-tables.pl - * from the Unicode 9.0.0 data files available at: - * https://www.unicode.org/Public/13.0.0/ucd/ + * from the Unicode 14.0.0 data files available at: + * https://www.unicode.org/Public/14.0.0/ucd/ */ static const struct interval wide[] = { {0x1100, 0x115F}, @@ -340,7 +570,10 @@ int mk_wcwidth(unsigned int ucs) {0x17000, 0x187F7}, {0x18800, 0x18CD5}, {0x18D00, 0x18D08}, - {0x1B000, 0x1B11E}, + {0x1AFF0, 0x1AFF3}, + {0x1AFF5, 0x1AFFB}, + {0x1AFFD, 0x1AFFE}, + {0x1B000, 0x1B122}, {0x1B150, 0x1B152}, {0x1B164, 0x1B167}, {0x1B170, 0x1B2FB}, @@ -375,21 +608,23 @@ int mk_wcwidth(unsigned int ucs) {0x1F6CC, 0x1F6CC}, {0x1F6D0, 0x1F6D2}, {0x1F6D5, 0x1F6D7}, + {0x1F6DD, 0x1F6DF}, {0x1F6EB, 0x1F6EC}, {0x1F6F4, 0x1F6FC}, {0x1F7E0, 0x1F7EB}, + {0x1F7F0, 0x1F7F0}, {0x1F90C, 0x1F93A}, {0x1F93C, 0x1F945}, - {0x1F947, 0x1F978}, - {0x1F97A, 0x1F9CB}, - {0x1F9CD, 0x1F9FF}, + {0x1F947, 0x1F9FF}, {0x1FA70, 0x1FA74}, - {0x1FA78, 0x1FA7A}, + {0x1FA78, 0x1FA7C}, {0x1FA80, 0x1FA86}, - {0x1FA90, 0x1FAA8}, - {0x1FAB0, 0x1FAB6}, - {0x1FAC0, 0x1FAC2}, - {0x1FAD0, 0x1FAD6}, + {0x1FA90, 0x1FAAC}, + {0x1FAB0, 0x1FABA}, + {0x1FAC0, 0x1FAC5}, + {0x1FAD0, 0x1FAD9}, + {0x1FAE0, 0x1FAE7}, + {0x1FAF0, 0x1FAF6}, {0x20000, 0x2FFFD}, {0x30000, 0x3FFFD}, }; -- cgit v1.2.3 From caa16deb1cca045e88065b86ac39826fcbee84fb Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 10 Oct 2021 14:40:51 +0100 Subject: bidi.c: update the API. The input length field is now a size_t rather than an int, on general principles. The return value is now void (we weren't using the previous return value at all). And we now require the client to have previously allocated a BidiContext, which will allow allocated storage to be reused between runs, saving a lot of churn on malloc. (However, the current BidiContext doesn't contain anything interesting. I could have moved the existing mallocs into it, but there's no point, since I'm about to rewrite the whole thing anyway.) --- defs.h | 2 ++ putty.h | 4 +++- terminal/bidi.c | 22 +++++++++++++++++++--- terminal/terminal.c | 6 +++++- terminal/terminal.h | 2 ++ 5 files changed, 31 insertions(+), 5 deletions(-) diff --git a/defs.h b/defs.h index b8205c10..e5d3fefe 100644 --- a/defs.h +++ b/defs.h @@ -170,6 +170,8 @@ typedef struct SessionSpecial SessionSpecial; typedef struct StripCtrlChars StripCtrlChars; +typedef struct BidiContext BidiContext; + /* * A small structure wrapping up a (pointer, length) pair so that it * can be conveniently passed to or from a function. diff --git a/putty.h b/putty.h index 334b588b..6bff90bb 100644 --- a/putty.h +++ b/putty.h @@ -2359,7 +2359,9 @@ typedef struct bidi_char { unsigned int origwc, wc; unsigned short index, nchars; } bidi_char; -int do_bidi(bidi_char *line, int count); +BidiContext *bidi_new_context(void); +void bidi_free_context(BidiContext *ctx); +void do_bidi(BidiContext *ctx, bidi_char *line, size_t count); int do_shape(bidi_char *line, bidi_char *to, int count); bool is_rtl(int c); diff --git a/terminal/bidi.c b/terminal/bidi.c index 4c55b86e..15ed23d0 100644 --- a/terminal/bidi.c +++ b/terminal/bidi.c @@ -1116,6 +1116,22 @@ int do_shape(bidi_char *line, bidi_char *to, int count) return 1; } +struct BidiContext { + int dummy; +}; + +BidiContext *bidi_new_context(void) +{ + BidiContext *ctx = snew(BidiContext); + memset(ctx, 0, sizeof(BidiContext)); + return ctx; +} + +void bidi_free_context(BidiContext *ctx) +{ + sfree(ctx); +} + /* * The Main Bidi Function, and the only function that should * be used by the outside world. @@ -1124,7 +1140,7 @@ int do_shape(bidi_char *line, bidi_char *to, int count) * the Bidirectional algorithm to. */ -int do_bidi(bidi_char *line, int count) +void do_bidi(BidiContext *ctx, bidi_char *line, size_t count) { unsigned char* types; unsigned char* levels; @@ -1145,7 +1161,7 @@ int do_bidi(bidi_char *line, int count) } } if (!yes) - return L; + return; /* Initialize types, levels */ types = snewn(count, unsigned char); @@ -1565,7 +1581,7 @@ int do_bidi(bidi_char *line, int count) */ sfree(types); sfree(levels); - return R; + return; } diff --git a/terminal/terminal.c b/terminal/terminal.c index ddeb1e94..dcdbfc6a 100644 --- a/terminal/terminal.c +++ b/terminal/terminal.c @@ -2048,6 +2048,8 @@ Terminal *term_init(Conf *myconf, struct unicode_data *ucsdata, TermWin *win) term->win_scrollbar_update_pending = false; term->win_palette_pending = false; + term->bidi_ctx = bidi_new_context(); + palette_reset(term, false); return term; @@ -2107,6 +2109,8 @@ void term_free(Terminal *term) sfree(term->window_title); sfree(term->icon_title); + bidi_free_context(term->bidi_ctx); + sfree(term); } @@ -5662,7 +5666,7 @@ static termchar *term_bidi_line(Terminal *term, struct termline *ldata, } if(!term->no_bidi) - do_bidi(term->wcFrom, nbc); + do_bidi(term->bidi_ctx, term->wcFrom, nbc); if(!term->no_arabicshaping) { do_shape(term->wcFrom, term->wcTo, nbc); diff --git a/terminal/terminal.h b/terminal/terminal.h index c463f7a8..75d08de7 100644 --- a/terminal/terminal.h +++ b/terminal/terminal.h @@ -353,6 +353,8 @@ struct terminal_tag { char *window_title, *icon_title; bool minimised; + BidiContext *bidi_ctx; + /* Multi-layered colour palette. The colours from Conf (plus the * default xterm-256 ones that don't have Conf ids at all) have * lowest priority, followed by platform overrides if any, -- cgit v1.2.3 From b8be01adca7f9b70d04cbd967628136398a7abaa Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 10 Oct 2021 14:51:17 +0100 Subject: Complete rewrite of the bidi algorithm. A user reported that PuTTY's existing bidi algorithm will generate misordered text in cases like this (assuming UTF-8): echo -e '12 A \xD7\x90\xD7\x91 B' The hex codes in the middle are the Hebrew letters aleph and beth. Appearing in the middle of a line whose primary direction is left-to-right, those two letters should appear in the opposite order, but not cause the rest of the line to move around. That is, you expect the displayed text in this situation to be 12 A B But in fact, the digits '12' were erroneously reversed, so you would actually see '21 A B'. I tried to debug the existing bidi algorithm, but it was very hard, because the Unicode bidi spec has been extensively changed since Arabeyes contributed that code, and I couldn't even reliably work out which version of the spec the code was intended to implement. I found some problems, notably that the resolution phase was running once on the whole line instead of separately on runs of characters at the same level, and also that the 'sor' and 'eor' values were being wrongly computed. But I had no way to test any fix to ensure it hadn't introduced another bug somewhere else. Unicode provides a set of conformance tests in the UCD. That was just what I wanted - but they're too up-to-date to run against the old algorithm and expect to pass! So, paradoxically, it seemed to me that the _easiest_ way to fix this bidi bug would be to bring absolutely everything up to date. But the revised bidi algorithm is significantly more complicated, so I also didn't think it would be sensible to try to gradually evolve the existing code into it. Instead, I've done a complete rewrite of my own. The new code implements the full UAX#9 rev 44 algorithm, including in particular support for the new 'directional isolate' control characters, and also special handling for matched pairs of brackets in the text (see rule N0 in the spec). I've managed to get it to pass the entire UCD conformance test suite, so I'm reasonably confident it's right, or at the very least a lot closer to right than the old algorithm was. So the upshot is: the test case shown at the top of this file now passes, but also, other detailed bidi handling might have changed, certainly some cases involving brackets, but perhaps also other things that were either bugs in the old algorithm or updates to the standard. --- terminal/bidi.c | 3673 ++++++++++++++++++++++++++++++++++++++++--------------- terminal/bidi.h | 69 ++ 2 files changed, 2735 insertions(+), 1007 deletions(-) diff --git a/terminal/bidi.c b/terminal/bidi.c index 15ed23d0..ea05e2bd 100644 --- a/terminal/bidi.c +++ b/terminal/bidi.c @@ -1,42 +1,132 @@ -/************************************************************************ +/* + * Implementation of the Unicode bidirectional and Arabic shaping + * algorithms for PuTTY. * - * ------------ - * Description: - * ------------ - * This is an implementation of Unicode's Bidirectional Algorithm - * (known as UAX #9). + * Original version written and kindly contributed to this code base + * by Arabeyes. The bidi part was almost completely rewritten in 2021 + * by Simon Tatham to bring it up to date, but the shaping part is + * still the one by the original authors. * - * http://www.unicode.org/reports/tr9/ + * Implementation notes: * - * Author: Ahmad Khalifa + * Algorithm version + * ----------------- * - * (www.arabeyes.org - under MIT license) + * This algorithm is up to date with Unicode Standard Annex #9 + * revision 44: * - ************************************************************************/ - -/* - * TODO: - * ===== - * - Explicit marks need to be handled (they are not 100% now) - * - Ligatures + * https://www.unicode.org/reports/tr9/tr9-44.html + * + * and passes the full conformance test suite in Unicode 14.0.0. + * + * Paragraph and line handling + * --------------------------- + * + * The full Unicode bidi algorithm expects to receive text containing + * multiple paragraphs, together with a decision about how those + * paragraphs are broken up into lines. It calculates embedding levels + * a whole paragraph at a time without considering the line breaks, + * but then the final reordering of the text for display is done to + * each _line_ independently based on the levels computed for the text + * in that line. + * + * This algorithm omits all of that, because it's intended for use as + * a display-time transformation of a text terminal, which doesn't + * preserve enough semantic information to decide what's a paragraph + * break and what is not. So a piece of input text provided to this + * algorithm is always expected to consist of exactly one paragraph + * *and* exactly one line. + * + * Embeddings, overrides and isolates + * ---------------------------------- + * + * This implementation has full support for all the Unicode special + * control characters that modify bidi behaviour, such as + * + * U+202A LEFT-TO-RIGHT EMBEDDING + * U+202B RIGHT-TO-LEFT EMBEDDING + * U+202D LEFT-TO-RIGHT OVERRIDE + * U+202E RIGHT-TO-LEFT OVERRIDE + * U+202C POP DIRECTIONAL FORMATTING + * U+2068 FIRST STRONG ISOLATE + * U+2066 LEFT-TO-RIGHT ISOLATE + * U+2067 RIGHT-TO-LEFT ISOLATE + * U+2069 POP DIRECTIONAL ISOLATE + * + * However, at present, the terminal emulator that is a client of this + * code has no way to pass those in (because they're dropped during + * escape sequence processing and don't get stored in the terminal + * state). Nonetheless, the code is all here, so if the terminal + * emulator becomes able to record those characters at some later + * point, we'll be all set to take account of them during bidi. + * + * But the _main_ purpose of supporting the full bidi algorithm is + * simply that that's the easiest way to be sure it's correct, because + * if you support the whole thing, you can run the full conformance + * test suite. (And I don't 100% believe that restricting to the + * subset of _tests_ valid with a reduced character set will test the + * full set of _functionality_ relevant to the reduced set.) + * + * Retained formatting characters + * ------------------------------ + * + * The standard bidi algorithm, in step X9, deletes assorted + * formatting characters from the text: all the embedding and override + * section initiator characters, the Pop Directional Formatting + * character that closes one of those sections again, and any + * character labelled as Boundary Neutral. So the characters it + * returns are not a _full_ reordering of the input; some input + * characters vanish completely. + * + * This would be fine, if it were not for the fact that - as far as I + * can see - _exactly one_ Unicode code point in the discarded + * category has a wcwidth() of more than 1, namely U+00AD SOFT HYPHEN + * which is a printing character for terminal purposes but has a bidi + * class of BN. + * + * Therefore, we must implement a modified version of the algorithm, + * as described in section 5.2 of TR9, which retains those formatting + * characters so that a client can find out where they ended up in the + * reordering. + * + * Section 5.2 describes a set of modifications to the algorithm that + * are _intended_ to achieve this without changing the rest of the + * behaviour: that is, if you take the output of the modified + * algorithm and delete all the characters that the standard algorithm + * would have removed, you should end up with the remaining characters + * in the same order that the standard algorithm would have delivered. + * However, section 5.2 admits the possibility of error, and says "in + * case of any deviation the explicit algorithm is the normative + * statement for conformance". And indeed, in one or two places I + * found I had to make my own tweaks to the section 5.2 description in + * order to get the whole test suite to pass, because I think the 5.2 + * modifications if taken literally don't quite achieve that. My + * justification is that sentence of 5.2: in case of doubt, the right + * thing is to make the code behave the same as the official + * algorithm. + * + * It's possible that there might still be some undiscovered + * discrepancies between the behaviour of the standard and modified + * algorithms. So, just in case, I've kept in this code the ability to + * implement the _standard_ algorithm too! If you compile with + * -DREMOVE_FORMATTING_CHARS, this code should go back to implementing + * the literal UAX#9 bidi algorithm - so you can run your suspect + * input through both versions, making it much easier to figure out + * why they differ, and in which of the many stages of the algorithm + * the difference was introduced. + * + * However, beware that when compiling in this mode, the do_bidi + * interface to the terminal will stop working, and just abort() when + * called! The only useful thing you can do with this mode is to run + * the companion program bidi_test.c. */ -#include /* definition of wchar_t*/ +#include /* definition of wchar_t */ #include "putty.h" #include "misc.h" #include "bidi.h" -/* function declarations */ -static void flipThisRun( - bidi_char *from, unsigned char *level, int max, int count); -static int findIndexOfRun( - unsigned char *level, int start, int count, int tlevel); -static unsigned char setOverrideBits( - unsigned char level, unsigned char override); -static int getPreviousLevel(unsigned char *level, int from); -static void doMirror(unsigned int *ch); - typedef struct { char type; wchar_t form_b; @@ -228,66 +318,18 @@ static const shape_node shapetypes[] = { /* 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 - */ -static 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 j; k--, j++) { - temp = from[k]; - from[k] = from[j]; - from[j] = temp; - } - } -} - -/* - * Finds the index of a run with level equals tlevel - */ -static int findIndexOfRun( - unsigned char *level , int start, int count, int tlevel) -{ - int i; - for (i=start; i>) { + chomp; s{\s}{}g; s{#.*$}{}; next unless /./; + @_ = split /;/, $_; + $src = hex $_[0]; $dst = hex $_[1]; + $m{$src}=$dst; $m{$dst}=$src; + } + for $src (sort {$a <=> $b} keys %m) { + printf " {0x%04x, 0x%04x},\n", $src, $m{$src}; + } +' BidiMirroring.txt + + * + * FIXME: there are also glyphs which the text rendering engine is + * supposed to display left-right reflected, since no mirrored glyph + * exists in Unicode itself to indicate the reflected form. Those are + * listed in comments in BidiMirroring.txt. Many of them are + * mathematical, e.g. the square root sign, or set difference + * operator, or integral sign. No API currently exists here to + * communicate the need for that reflected display back to the client. + */ +static unsigned mirror_glyph(unsigned int ch) +{ + static const struct { + unsigned src, dst; + } mirror_pairs[] = { + {0x0028, 0x0029}, + {0x0029, 0x0028}, + {0x003c, 0x003e}, + {0x003e, 0x003c}, + {0x005b, 0x005d}, + {0x005d, 0x005b}, + {0x007b, 0x007d}, + {0x007d, 0x007b}, + {0x00ab, 0x00bb}, + {0x00bb, 0x00ab}, + {0x0f3a, 0x0f3b}, + {0x0f3b, 0x0f3a}, + {0x0f3c, 0x0f3d}, + {0x0f3d, 0x0f3c}, + {0x169b, 0x169c}, + {0x169c, 0x169b}, + {0x2039, 0x203a}, + {0x203a, 0x2039}, + {0x2045, 0x2046}, + {0x2046, 0x2045}, + {0x207d, 0x207e}, + {0x207e, 0x207d}, + {0x208d, 0x208e}, + {0x208e, 0x208d}, + {0x2208, 0x220b}, + {0x2209, 0x220c}, + {0x220a, 0x220d}, + {0x220b, 0x2208}, + {0x220c, 0x2209}, + {0x220d, 0x220a}, + {0x2215, 0x29f5}, + {0x221f, 0x2bfe}, + {0x2220, 0x29a3}, + {0x2221, 0x299b}, + {0x2222, 0x29a0}, + {0x2224, 0x2aee}, + {0x223c, 0x223d}, + {0x223d, 0x223c}, + {0x2243, 0x22cd}, + {0x2245, 0x224c}, + {0x224c, 0x2245}, + {0x2252, 0x2253}, + {0x2253, 0x2252}, + {0x2254, 0x2255}, + {0x2255, 0x2254}, + {0x2264, 0x2265}, + {0x2265, 0x2264}, + {0x2266, 0x2267}, + {0x2267, 0x2266}, + {0x2268, 0x2269}, + {0x2269, 0x2268}, + {0x226a, 0x226b}, + {0x226b, 0x226a}, + {0x226e, 0x226f}, + {0x226f, 0x226e}, + {0x2270, 0x2271}, + {0x2271, 0x2270}, + {0x2272, 0x2273}, + {0x2273, 0x2272}, + {0x2274, 0x2275}, + {0x2275, 0x2274}, + {0x2276, 0x2277}, + {0x2277, 0x2276}, + {0x2278, 0x2279}, + {0x2279, 0x2278}, + {0x227a, 0x227b}, + {0x227b, 0x227a}, + {0x227c, 0x227d}, + {0x227d, 0x227c}, + {0x227e, 0x227f}, + {0x227f, 0x227e}, + {0x2280, 0x2281}, + {0x2281, 0x2280}, + {0x2282, 0x2283}, + {0x2283, 0x2282}, + {0x2284, 0x2285}, + {0x2285, 0x2284}, + {0x2286, 0x2287}, + {0x2287, 0x2286}, + {0x2288, 0x2289}, + {0x2289, 0x2288}, + {0x228a, 0x228b}, + {0x228b, 0x228a}, + {0x228f, 0x2290}, + {0x2290, 0x228f}, + {0x2291, 0x2292}, + {0x2292, 0x2291}, + {0x2298, 0x29b8}, + {0x22a2, 0x22a3}, + {0x22a3, 0x22a2}, + {0x22a6, 0x2ade}, + {0x22a8, 0x2ae4}, + {0x22a9, 0x2ae3}, + {0x22ab, 0x2ae5}, + {0x22b0, 0x22b1}, + {0x22b1, 0x22b0}, + {0x22b2, 0x22b3}, + {0x22b3, 0x22b2}, + {0x22b4, 0x22b5}, + {0x22b5, 0x22b4}, + {0x22b6, 0x22b7}, + {0x22b7, 0x22b6}, + {0x22b8, 0x27dc}, + {0x22c9, 0x22ca}, + {0x22ca, 0x22c9}, + {0x22cb, 0x22cc}, + {0x22cc, 0x22cb}, + {0x22cd, 0x2243}, + {0x22d0, 0x22d1}, + {0x22d1, 0x22d0}, + {0x22d6, 0x22d7}, + {0x22d7, 0x22d6}, + {0x22d8, 0x22d9}, + {0x22d9, 0x22d8}, + {0x22da, 0x22db}, + {0x22db, 0x22da}, + {0x22dc, 0x22dd}, + {0x22dd, 0x22dc}, + {0x22de, 0x22df}, + {0x22df, 0x22de}, + {0x22e0, 0x22e1}, + {0x22e1, 0x22e0}, + {0x22e2, 0x22e3}, + {0x22e3, 0x22e2}, + {0x22e4, 0x22e5}, + {0x22e5, 0x22e4}, + {0x22e6, 0x22e7}, + {0x22e7, 0x22e6}, + {0x22e8, 0x22e9}, + {0x22e9, 0x22e8}, + {0x22ea, 0x22eb}, + {0x22eb, 0x22ea}, + {0x22ec, 0x22ed}, + {0x22ed, 0x22ec}, + {0x22f0, 0x22f1}, + {0x22f1, 0x22f0}, + {0x22f2, 0x22fa}, + {0x22f3, 0x22fb}, + {0x22f4, 0x22fc}, + {0x22f6, 0x22fd}, + {0x22f7, 0x22fe}, + {0x22fa, 0x22f2}, + {0x22fb, 0x22f3}, + {0x22fc, 0x22f4}, + {0x22fd, 0x22f6}, + {0x22fe, 0x22f7}, + {0x2308, 0x2309}, + {0x2309, 0x2308}, + {0x230a, 0x230b}, + {0x230b, 0x230a}, + {0x2329, 0x232a}, + {0x232a, 0x2329}, + {0x2768, 0x2769}, + {0x2769, 0x2768}, + {0x276a, 0x276b}, + {0x276b, 0x276a}, + {0x276c, 0x276d}, + {0x276d, 0x276c}, + {0x276e, 0x276f}, + {0x276f, 0x276e}, + {0x2770, 0x2771}, + {0x2771, 0x2770}, + {0x2772, 0x2773}, + {0x2773, 0x2772}, + {0x2774, 0x2775}, + {0x2775, 0x2774}, + {0x27c3, 0x27c4}, + {0x27c4, 0x27c3}, + {0x27c5, 0x27c6}, + {0x27c6, 0x27c5}, + {0x27c8, 0x27c9}, + {0x27c9, 0x27c8}, + {0x27cb, 0x27cd}, + {0x27cd, 0x27cb}, + {0x27d5, 0x27d6}, + {0x27d6, 0x27d5}, + {0x27dc, 0x22b8}, + {0x27dd, 0x27de}, + {0x27de, 0x27dd}, + {0x27e2, 0x27e3}, + {0x27e3, 0x27e2}, + {0x27e4, 0x27e5}, + {0x27e5, 0x27e4}, + {0x27e6, 0x27e7}, + {0x27e7, 0x27e6}, + {0x27e8, 0x27e9}, + {0x27e9, 0x27e8}, + {0x27ea, 0x27eb}, + {0x27eb, 0x27ea}, + {0x27ec, 0x27ed}, + {0x27ed, 0x27ec}, + {0x27ee, 0x27ef}, + {0x27ef, 0x27ee}, + {0x2983, 0x2984}, + {0x2984, 0x2983}, + {0x2985, 0x2986}, + {0x2986, 0x2985}, + {0x2987, 0x2988}, + {0x2988, 0x2987}, + {0x2989, 0x298a}, + {0x298a, 0x2989}, + {0x298b, 0x298c}, + {0x298c, 0x298b}, + {0x298d, 0x2990}, + {0x298e, 0x298f}, + {0x298f, 0x298e}, + {0x2990, 0x298d}, + {0x2991, 0x2992}, + {0x2992, 0x2991}, + {0x2993, 0x2994}, + {0x2994, 0x2993}, + {0x2995, 0x2996}, + {0x2996, 0x2995}, + {0x2997, 0x2998}, + {0x2998, 0x2997}, + {0x299b, 0x2221}, + {0x29a0, 0x2222}, + {0x29a3, 0x2220}, + {0x29a4, 0x29a5}, + {0x29a5, 0x29a4}, + {0x29a8, 0x29a9}, + {0x29a9, 0x29a8}, + {0x29aa, 0x29ab}, + {0x29ab, 0x29aa}, + {0x29ac, 0x29ad}, + {0x29ad, 0x29ac}, + {0x29ae, 0x29af}, + {0x29af, 0x29ae}, + {0x29b8, 0x2298}, + {0x29c0, 0x29c1}, + {0x29c1, 0x29c0}, + {0x29c4, 0x29c5}, + {0x29c5, 0x29c4}, + {0x29cf, 0x29d0}, + {0x29d0, 0x29cf}, + {0x29d1, 0x29d2}, + {0x29d2, 0x29d1}, + {0x29d4, 0x29d5}, + {0x29d5, 0x29d4}, + {0x29d8, 0x29d9}, + {0x29d9, 0x29d8}, + {0x29da, 0x29db}, + {0x29db, 0x29da}, + {0x29e8, 0x29e9}, + {0x29e9, 0x29e8}, + {0x29f5, 0x2215}, + {0x29f8, 0x29f9}, + {0x29f9, 0x29f8}, + {0x29fc, 0x29fd}, + {0x29fd, 0x29fc}, + {0x2a2b, 0x2a2c}, + {0x2a2c, 0x2a2b}, + {0x2a2d, 0x2a2e}, + {0x2a2e, 0x2a2d}, + {0x2a34, 0x2a35}, + {0x2a35, 0x2a34}, + {0x2a3c, 0x2a3d}, + {0x2a3d, 0x2a3c}, + {0x2a64, 0x2a65}, + {0x2a65, 0x2a64}, + {0x2a79, 0x2a7a}, + {0x2a7a, 0x2a79}, + {0x2a7b, 0x2a7c}, + {0x2a7c, 0x2a7b}, + {0x2a7d, 0x2a7e}, + {0x2a7e, 0x2a7d}, + {0x2a7f, 0x2a80}, + {0x2a80, 0x2a7f}, + {0x2a81, 0x2a82}, + {0x2a82, 0x2a81}, + {0x2a83, 0x2a84}, + {0x2a84, 0x2a83}, + {0x2a85, 0x2a86}, + {0x2a86, 0x2a85}, + {0x2a87, 0x2a88}, + {0x2a88, 0x2a87}, + {0x2a89, 0x2a8a}, + {0x2a8a, 0x2a89}, + {0x2a8b, 0x2a8c}, + {0x2a8c, 0x2a8b}, + {0x2a8d, 0x2a8e}, + {0x2a8e, 0x2a8d}, + {0x2a8f, 0x2a90}, + {0x2a90, 0x2a8f}, + {0x2a91, 0x2a92}, + {0x2a92, 0x2a91}, + {0x2a93, 0x2a94}, + {0x2a94, 0x2a93}, + {0x2a95, 0x2a96}, + {0x2a96, 0x2a95}, + {0x2a97, 0x2a98}, + {0x2a98, 0x2a97}, + {0x2a99, 0x2a9a}, + {0x2a9a, 0x2a99}, + {0x2a9b, 0x2a9c}, + {0x2a9c, 0x2a9b}, + {0x2a9d, 0x2a9e}, + {0x2a9e, 0x2a9d}, + {0x2a9f, 0x2aa0}, + {0x2aa0, 0x2a9f}, + {0x2aa1, 0x2aa2}, + {0x2aa2, 0x2aa1}, + {0x2aa6, 0x2aa7}, + {0x2aa7, 0x2aa6}, + {0x2aa8, 0x2aa9}, + {0x2aa9, 0x2aa8}, + {0x2aaa, 0x2aab}, + {0x2aab, 0x2aaa}, + {0x2aac, 0x2aad}, + {0x2aad, 0x2aac}, + {0x2aaf, 0x2ab0}, + {0x2ab0, 0x2aaf}, + {0x2ab1, 0x2ab2}, + {0x2ab2, 0x2ab1}, + {0x2ab3, 0x2ab4}, + {0x2ab4, 0x2ab3}, + {0x2ab5, 0x2ab6}, + {0x2ab6, 0x2ab5}, + {0x2ab7, 0x2ab8}, + {0x2ab8, 0x2ab7}, + {0x2ab9, 0x2aba}, + {0x2aba, 0x2ab9}, + {0x2abb, 0x2abc}, + {0x2abc, 0x2abb}, + {0x2abd, 0x2abe}, + {0x2abe, 0x2abd}, + {0x2abf, 0x2ac0}, + {0x2ac0, 0x2abf}, + {0x2ac1, 0x2ac2}, + {0x2ac2, 0x2ac1}, + {0x2ac3, 0x2ac4}, + {0x2ac4, 0x2ac3}, + {0x2ac5, 0x2ac6}, + {0x2ac6, 0x2ac5}, + {0x2ac7, 0x2ac8}, + {0x2ac8, 0x2ac7}, + {0x2ac9, 0x2aca}, + {0x2aca, 0x2ac9}, + {0x2acb, 0x2acc}, + {0x2acc, 0x2acb}, + {0x2acd, 0x2ace}, + {0x2ace, 0x2acd}, + {0x2acf, 0x2ad0}, + {0x2ad0, 0x2acf}, + {0x2ad1, 0x2ad2}, + {0x2ad2, 0x2ad1}, + {0x2ad3, 0x2ad4}, + {0x2ad4, 0x2ad3}, + {0x2ad5, 0x2ad6}, + {0x2ad6, 0x2ad5}, + {0x2ade, 0x22a6}, + {0x2ae3, 0x22a9}, + {0x2ae4, 0x22a8}, + {0x2ae5, 0x22ab}, + {0x2aec, 0x2aed}, + {0x2aed, 0x2aec}, + {0x2aee, 0x2224}, + {0x2af7, 0x2af8}, + {0x2af8, 0x2af7}, + {0x2af9, 0x2afa}, + {0x2afa, 0x2af9}, + {0x2bfe, 0x221f}, + {0x2e02, 0x2e03}, + {0x2e03, 0x2e02}, + {0x2e04, 0x2e05}, + {0x2e05, 0x2e04}, + {0x2e09, 0x2e0a}, + {0x2e0a, 0x2e09}, + {0x2e0c, 0x2e0d}, + {0x2e0d, 0x2e0c}, + {0x2e1c, 0x2e1d}, + {0x2e1d, 0x2e1c}, + {0x2e20, 0x2e21}, + {0x2e21, 0x2e20}, + {0x2e22, 0x2e23}, + {0x2e23, 0x2e22}, + {0x2e24, 0x2e25}, + {0x2e25, 0x2e24}, + {0x2e26, 0x2e27}, + {0x2e27, 0x2e26}, + {0x2e28, 0x2e29}, + {0x2e29, 0x2e28}, + {0x2e55, 0x2e56}, + {0x2e56, 0x2e55}, + {0x2e57, 0x2e58}, + {0x2e58, 0x2e57}, + {0x2e59, 0x2e5a}, + {0x2e5a, 0x2e59}, + {0x2e5b, 0x2e5c}, + {0x2e5c, 0x2e5b}, + {0x3008, 0x3009}, + {0x3009, 0x3008}, + {0x300a, 0x300b}, + {0x300b, 0x300a}, + {0x300c, 0x300d}, + {0x300d, 0x300c}, + {0x300e, 0x300f}, + {0x300f, 0x300e}, + {0x3010, 0x3011}, + {0x3011, 0x3010}, + {0x3014, 0x3015}, + {0x3015, 0x3014}, + {0x3016, 0x3017}, + {0x3017, 0x3016}, + {0x3018, 0x3019}, + {0x3019, 0x3018}, + {0x301a, 0x301b}, + {0x301b, 0x301a}, + {0xfe59, 0xfe5a}, + {0xfe5a, 0xfe59}, + {0xfe5b, 0xfe5c}, + {0xfe5c, 0xfe5b}, + {0xfe5d, 0xfe5e}, + {0xfe5e, 0xfe5d}, + {0xfe64, 0xfe65}, + {0xfe65, 0xfe64}, + {0xff08, 0xff09}, + {0xff09, 0xff08}, + {0xff1c, 0xff1e}, + {0xff1e, 0xff1c}, + {0xff3b, 0xff3d}, + {0xff3d, 0xff3b}, + {0xff5b, 0xff5d}, + {0xff5d, 0xff5b}, + {0xff5f, 0xff60}, + {0xff60, 0xff5f}, + {0xff62, 0xff63}, + {0xff63, 0xff62}, + }; + + int i, j, k; + + i = -1; + j = lenof(mirror_pairs); + + while (j - i > 1) { + k = (i + j) / 2; + if (ch < mirror_pairs[k].src) + j = k; + else if (ch > mirror_pairs[k].src) + i = k; + else + return mirror_pairs[k].dst; + } + + return ch; +} + +/* + * Identify the bracket characters treated specially by bidi rule + * BD19, and return their paired character(s). + * + * The data table in this function is constructed from the Unicode + * Character Database version 14.0.0, downloadable from unicode.org at + * the URL + * + * https://www.unicode.org/Public/14.0.0/ucd/ + * + * by the following fragment of Perl: + +perl -e ' + open BIDIBRACKETS, "<", $ARGV[0] or die; + while () { + chomp; s{\s}{}g; s{#.*$}{}; next unless /./; + @_ = split /;/, $_; + $src = hex $_[0]; $dst = hex $_[1]; $kind = $_[2]; + $m{$src}=[$kind, $dst]; + } + open UNICODEDATA, "<", $ARGV[1] or die; + while () { + chomp; @_ = split /;/, $_; + $src = hex $_[0]; next unless defined $m{$src}; + if ($_[5] =~ /^[0-9a-f]+$/i) { + $equiv = hex $_[5]; + $e{$src} = $equiv; + $e{$equiv} = $src; + } + } + for $src (sort {$a <=> $b} keys %m) { + ($kind, $dst) = @{$m{$src}}; + $equiv = 0 + $e{$dst}; + printf " {0x%04x, {0x%04x, 0x%04x, %s}},\n", $src, $dst, $equiv, + $kind eq "c" ? "BT_CLOSE" : "BT_OPEN"; + } +' BidiBrackets.txt UnicodeData.txt + + */ +typedef enum { BT_NONE, BT_OPEN, BT_CLOSE } BracketType; +typedef struct BracketTypeData { + unsigned partner, equiv_partner; + BracketType type; +} BracketTypeData; +static BracketTypeData bracket_type(unsigned int ch) +{ + static const struct { + unsigned src; + BracketTypeData payload; + } bracket_pairs[] = { + {0x0028, {0x0029, 0x0000, BT_OPEN}}, + {0x0029, {0x0028, 0x0000, BT_CLOSE}}, + {0x005b, {0x005d, 0x0000, BT_OPEN}}, + {0x005d, {0x005b, 0x0000, BT_CLOSE}}, + {0x007b, {0x007d, 0x0000, BT_OPEN}}, + {0x007d, {0x007b, 0x0000, BT_CLOSE}}, + {0x0f3a, {0x0f3b, 0x0000, BT_OPEN}}, + {0x0f3b, {0x0f3a, 0x0000, BT_CLOSE}}, + {0x0f3c, {0x0f3d, 0x0000, BT_OPEN}}, + {0x0f3d, {0x0f3c, 0x0000, BT_CLOSE}}, + {0x169b, {0x169c, 0x0000, BT_OPEN}}, + {0x169c, {0x169b, 0x0000, BT_CLOSE}}, + {0x2045, {0x2046, 0x0000, BT_OPEN}}, + {0x2046, {0x2045, 0x0000, BT_CLOSE}}, + {0x207d, {0x207e, 0x0000, BT_OPEN}}, + {0x207e, {0x207d, 0x0000, BT_CLOSE}}, + {0x208d, {0x208e, 0x0000, BT_OPEN}}, + {0x208e, {0x208d, 0x0000, BT_CLOSE}}, + {0x2308, {0x2309, 0x0000, BT_OPEN}}, + {0x2309, {0x2308, 0x0000, BT_CLOSE}}, + {0x230a, {0x230b, 0x0000, BT_OPEN}}, + {0x230b, {0x230a, 0x0000, BT_CLOSE}}, + {0x2329, {0x232a, 0x3009, BT_OPEN}}, + {0x232a, {0x2329, 0x3008, BT_CLOSE}}, + {0x2768, {0x2769, 0x0000, BT_OPEN}}, + {0x2769, {0x2768, 0x0000, BT_CLOSE}}, + {0x276a, {0x276b, 0x0000, BT_OPEN}}, + {0x276b, {0x276a, 0x0000, BT_CLOSE}}, + {0x276c, {0x276d, 0x0000, BT_OPEN}}, + {0x276d, {0x276c, 0x0000, BT_CLOSE}}, + {0x276e, {0x276f, 0x0000, BT_OPEN}}, + {0x276f, {0x276e, 0x0000, BT_CLOSE}}, + {0x2770, {0x2771, 0x0000, BT_OPEN}}, + {0x2771, {0x2770, 0x0000, BT_CLOSE}}, + {0x2772, {0x2773, 0x0000, BT_OPEN}}, + {0x2773, {0x2772, 0x0000, BT_CLOSE}}, + {0x2774, {0x2775, 0x0000, BT_OPEN}}, + {0x2775, {0x2774, 0x0000, BT_CLOSE}}, + {0x27c5, {0x27c6, 0x0000, BT_OPEN}}, + {0x27c6, {0x27c5, 0x0000, BT_CLOSE}}, + {0x27e6, {0x27e7, 0x0000, BT_OPEN}}, + {0x27e7, {0x27e6, 0x0000, BT_CLOSE}}, + {0x27e8, {0x27e9, 0x0000, BT_OPEN}}, + {0x27e9, {0x27e8, 0x0000, BT_CLOSE}}, + {0x27ea, {0x27eb, 0x0000, BT_OPEN}}, + {0x27eb, {0x27ea, 0x0000, BT_CLOSE}}, + {0x27ec, {0x27ed, 0x0000, BT_OPEN}}, + {0x27ed, {0x27ec, 0x0000, BT_CLOSE}}, + {0x27ee, {0x27ef, 0x0000, BT_OPEN}}, + {0x27ef, {0x27ee, 0x0000, BT_CLOSE}}, + {0x2983, {0x2984, 0x0000, BT_OPEN}}, + {0x2984, {0x2983, 0x0000, BT_CLOSE}}, + {0x2985, {0x2986, 0x0000, BT_OPEN}}, + {0x2986, {0x2985, 0x0000, BT_CLOSE}}, + {0x2987, {0x2988, 0x0000, BT_OPEN}}, + {0x2988, {0x2987, 0x0000, BT_CLOSE}}, + {0x2989, {0x298a, 0x0000, BT_OPEN}}, + {0x298a, {0x2989, 0x0000, BT_CLOSE}}, + {0x298b, {0x298c, 0x0000, BT_OPEN}}, + {0x298c, {0x298b, 0x0000, BT_CLOSE}}, + {0x298d, {0x2990, 0x0000, BT_OPEN}}, + {0x298e, {0x298f, 0x0000, BT_CLOSE}}, + {0x298f, {0x298e, 0x0000, BT_OPEN}}, + {0x2990, {0x298d, 0x0000, BT_CLOSE}}, + {0x2991, {0x2992, 0x0000, BT_OPEN}}, + {0x2992, {0x2991, 0x0000, BT_CLOSE}}, + {0x2993, {0x2994, 0x0000, BT_OPEN}}, + {0x2994, {0x2993, 0x0000, BT_CLOSE}}, + {0x2995, {0x2996, 0x0000, BT_OPEN}}, + {0x2996, {0x2995, 0x0000, BT_CLOSE}}, + {0x2997, {0x2998, 0x0000, BT_OPEN}}, + {0x2998, {0x2997, 0x0000, BT_CLOSE}}, + {0x29d8, {0x29d9, 0x0000, BT_OPEN}}, + {0x29d9, {0x29d8, 0x0000, BT_CLOSE}}, + {0x29da, {0x29db, 0x0000, BT_OPEN}}, + {0x29db, {0x29da, 0x0000, BT_CLOSE}}, + {0x29fc, {0x29fd, 0x0000, BT_OPEN}}, + {0x29fd, {0x29fc, 0x0000, BT_CLOSE}}, + {0x2e22, {0x2e23, 0x0000, BT_OPEN}}, + {0x2e23, {0x2e22, 0x0000, BT_CLOSE}}, + {0x2e24, {0x2e25, 0x0000, BT_OPEN}}, + {0x2e25, {0x2e24, 0x0000, BT_CLOSE}}, + {0x2e26, {0x2e27, 0x0000, BT_OPEN}}, + {0x2e27, {0x2e26, 0x0000, BT_CLOSE}}, + {0x2e28, {0x2e29, 0x0000, BT_OPEN}}, + {0x2e29, {0x2e28, 0x0000, BT_CLOSE}}, + {0x2e55, {0x2e56, 0x0000, BT_OPEN}}, + {0x2e56, {0x2e55, 0x0000, BT_CLOSE}}, + {0x2e57, {0x2e58, 0x0000, BT_OPEN}}, + {0x2e58, {0x2e57, 0x0000, BT_CLOSE}}, + {0x2e59, {0x2e5a, 0x0000, BT_OPEN}}, + {0x2e5a, {0x2e59, 0x0000, BT_CLOSE}}, + {0x2e5b, {0x2e5c, 0x0000, BT_OPEN}}, + {0x2e5c, {0x2e5b, 0x0000, BT_CLOSE}}, + {0x3008, {0x3009, 0x232a, BT_OPEN}}, + {0x3009, {0x3008, 0x2329, BT_CLOSE}}, + {0x300a, {0x300b, 0x0000, BT_OPEN}}, + {0x300b, {0x300a, 0x0000, BT_CLOSE}}, + {0x300c, {0x300d, 0x0000, BT_OPEN}}, + {0x300d, {0x300c, 0x0000, BT_CLOSE}}, + {0x300e, {0x300f, 0x0000, BT_OPEN}}, + {0x300f, {0x300e, 0x0000, BT_CLOSE}}, + {0x3010, {0x3011, 0x0000, BT_OPEN}}, + {0x3011, {0x3010, 0x0000, BT_CLOSE}}, + {0x3014, {0x3015, 0x0000, BT_OPEN}}, + {0x3015, {0x3014, 0x0000, BT_CLOSE}}, + {0x3016, {0x3017, 0x0000, BT_OPEN}}, + {0x3017, {0x3016, 0x0000, BT_CLOSE}}, + {0x3018, {0x3019, 0x0000, BT_OPEN}}, + {0x3019, {0x3018, 0x0000, BT_CLOSE}}, + {0x301a, {0x301b, 0x0000, BT_OPEN}}, + {0x301b, {0x301a, 0x0000, BT_CLOSE}}, + {0xfe59, {0xfe5a, 0x0000, BT_OPEN}}, + {0xfe5a, {0xfe59, 0x0000, BT_CLOSE}}, + {0xfe5b, {0xfe5c, 0x0000, BT_OPEN}}, + {0xfe5c, {0xfe5b, 0x0000, BT_CLOSE}}, + {0xfe5d, {0xfe5e, 0x0000, BT_OPEN}}, + {0xfe5e, {0xfe5d, 0x0000, BT_CLOSE}}, + {0xff08, {0xff09, 0x0000, BT_OPEN}}, + {0xff09, {0xff08, 0x0000, BT_CLOSE}}, + {0xff3b, {0xff3d, 0x0000, BT_OPEN}}, + {0xff3d, {0xff3b, 0x0000, BT_CLOSE}}, + {0xff5b, {0xff5d, 0x0000, BT_OPEN}}, + {0xff5d, {0xff5b, 0x0000, BT_CLOSE}}, + {0xff5f, {0xff60, 0x0000, BT_OPEN}}, + {0xff60, {0xff5f, 0x0000, BT_CLOSE}}, + {0xff62, {0xff63, 0x0000, BT_OPEN}}, + {0xff63, {0xff62, 0x0000, BT_CLOSE}}, + }; + + int i, j, k; + + i = -1; + j = lenof(bracket_pairs); + + while (j - i > 1) { + k = (i + j) / 2; + if (ch < bracket_pairs[k].src) { + j = k; + } else if (ch > bracket_pairs[k].src) { + i = k; + } else { + return bracket_pairs[k].payload; + } + } + + static const BracketTypeData null = { 0, 0, BT_NONE }; + return null; +} + /* * Function exported to front ends to allow them to identify * bidi-active characters (in case, for example, the platform's @@ -970,56 +2375,7 @@ unsigned char bidi_getType(int ch) */ bool 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< 0) { - unsigned char current = level[--from]; - - while (from >= 0 && level[from] == current) - from--; - - if (from >= 0) - return level[from]; - - return -1; - } else - return -1; + return typeIsBidiActive(bidi_getType(c)); } /* The Main shaping function, and the only one to be used @@ -1116,8 +2472,68 @@ int do_shape(bidi_char *line, bidi_char *to, int count) return 1; } +typedef enum { DO_NEUTRAL, DO_LTR, DO_RTL } DirectionalOverride; + +typedef struct DSStackEntry { + /* + * An entry in the directional status stack (rule section X). + */ + unsigned char level; + bool isolate; + DirectionalOverride override; +} DSStackEntry; + +typedef struct BracketStackEntry { + /* + * An entry in the bracket-pair-tracking stack (rule BD16). + */ + unsigned ch; + size_t c; +} BracketStackEntry; + +typedef struct IsolatingRunSequence { + size_t start, end; + BidiType sos, eos, embeddingDirection; +} IsolatingRunSequence; + +#define MAX_DEPTH 125 /* specified in the standard */ + struct BidiContext { - int dummy; + /* + * Storage space preserved between runs, all allocated to the same + * length (internal_array_sizes). + */ + size_t internal_array_sizes; + BidiType *types, *origTypes; + unsigned char *levels; + size_t *irsindices, *bracketpos; + bool *irsdone; + + /* + * Separately allocated with its own size field + */ + IsolatingRunSequence *irslist; + size_t irslistsize; + + /* + * Rewritten to point to the input to the currently active run of + * the bidi algorithm + */ + bidi_char *text; + size_t textlen; + + /* + * State within a run of the algorithm + */ + BidiType paragraphOverride; + DSStackEntry dsstack[MAX_DEPTH + 2]; + size_t ds_sp; + size_t overflowIsolateCount, overflowEmbeddingCount, validIsolateCount; + unsigned char paragraphLevel; + size_t *irs; + size_t irslen; + BidiType sos, eos, embeddingDirection; + BracketStackEntry bstack[63]; /* constant size specified in rule BD16 */ }; BidiContext *bidi_new_context(void) @@ -1129,813 +2545,1056 @@ BidiContext *bidi_new_context(void) void bidi_free_context(BidiContext *ctx) { + sfree(ctx->types); + sfree(ctx->origTypes); + sfree(ctx->levels); + sfree(ctx->irsindices); + sfree(ctx->irsdone); + sfree(ctx->bracketpos); + sfree(ctx->irslist); sfree(ctx); } -/* - * 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. - */ - -void do_bidi(BidiContext *ctx, bidi_char *line, size_t count) +static void ensure_arrays(BidiContext *ctx, size_t textlen) { - unsigned char* types; - unsigned char* levels; - unsigned char paragraphLevel; - unsigned char currentEmbedding; - unsigned char currentOverride; - unsigned char tempType; - int i, j; - bool yes, bover; - - /* Check the presence of R or AL types as optimization */ - yes = false; - for (i=0; iinternal_array_sizes) return; + ctx->internal_array_sizes = textlen; + ctx->types = sresize(ctx->types, ctx->internal_array_sizes, BidiType); + ctx->origTypes = sresize(ctx->origTypes, ctx->internal_array_sizes, + BidiType); + ctx->levels = sresize(ctx->levels, ctx->internal_array_sizes, + unsigned char); + ctx->irsindices = sresize(ctx->irsindices, ctx->internal_array_sizes, + size_t); + ctx->irsdone = sresize(ctx->irsdone, ctx->internal_array_sizes, bool); + ctx->bracketpos = sresize(ctx->bracketpos, ctx->internal_array_sizes, + size_t); +} - /* Initialize types, levels */ - types = snewn(count, unsigned char); - levels = snewn(count, unsigned char); +static void setup_types(BidiContext *ctx) +{ + for (size_t i = 0; i < ctx->textlen; i++) + ctx->types[i] = ctx->origTypes[i] = bidi_getType(ctx->text[i].wc); +} - /* 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. +static bool text_needs_bidi(BidiContext *ctx) +{ + /* + * Initial optimisation: check for any bidi-active character at + * all in an input line. If there aren't any, we can skip the + * whole algorithm. + * + * Also include the paragraph override in this check! */ + for (size_t i = 0; i < ctx->textlen; i++) + if (typeIsBidiActive(ctx->types[i])) + return true; + return typeIsBidiActive(ctx->paragraphOverride); +} - /* 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; iparagraphOverride == L) + ctx->paragraphLevel = 0; + else if (ctx->paragraphOverride == R) + ctx->paragraphLevel = 1; + else + ctx->paragraphLevel = rule_p2_p3(ctx->types, ctx->textlen); +} + +static inline unsigned char nextOddLevel(unsigned char x) { return (x+1)|1; } +static inline unsigned char nextEvenLevel(unsigned char x) { return (x|1)+1; } + +static inline void push(BidiContext *ctx, unsigned char level, + DirectionalOverride override, bool isolate) +{ + ctx->ds_sp++; + assert(ctx->ds_sp < lenof(ctx->dsstack)); + ctx->dsstack[ctx->ds_sp].level = level; + ctx->dsstack[ctx->ds_sp].override = override; + ctx->dsstack[ctx->ds_sp].isolate = isolate; +} + +static inline void pop(BidiContext *ctx) +{ + assert(ctx->ds_sp > 0); + ctx->ds_sp--; +} + +static void process_explicit_embeddings(BidiContext *ctx) +{ + /* + * Rule X1 initialisation. */ - bover = false; - for (i=0; ids_sp = (size_t)-1; + push(ctx, ctx->paragraphLevel, DO_NEUTRAL, false); + ctx->overflowIsolateCount = 0; + ctx->overflowEmbeddingCount = 0; + ctx->validIsolateCount = 0; + + #define stk (&ctx->dsstack[ctx->ds_sp]) + + for (size_t i = 0; i < ctx->textlen; i++) { + BidiType t = ctx->types[i]; + switch (t) { + case RLE: case LRE: case RLO: case LRO: { + /* Rules X2-X5 */ + unsigned char newLevel; + DirectionalOverride override; + +#ifndef REMOVE_FORMATTING_CHARS + ctx->levels[i] = stk->level; +#endif + + switch (t) { + case RLE: /* rule X2 */ + newLevel = nextOddLevel(stk->level); + override = DO_NEUTRAL; + break; + case LRE: /* rule X3 */ + newLevel = nextEvenLevel(stk->level); + override = DO_NEUTRAL; + break; + case RLO: /* rule X4 */ + newLevel = nextOddLevel(stk->level); + override = DO_RTL; + break; + case LRO: /* rule X5 */ + newLevel = nextEvenLevel(stk->level); + override = DO_LTR; + break; + default: + unreachable("how did this get past the outer switch?"); + } - case LRE: - currentEmbedding = levels[i] = leastGreaterEven(currentEmbedding); - levels[i] = setOverrideBits(levels[i], currentOverride); - currentOverride = ON; + if (newLevel <= MAX_DEPTH && + ctx->overflowIsolateCount == 0 && + ctx->overflowEmbeddingCount == 0) { + /* Embedding code is valid. Push a stack entry. */ + push(ctx, newLevel, override, false); + } else { + /* Embedding code is an overflow one. */ + if (ctx->overflowIsolateCount == 0) + ctx->overflowEmbeddingCount++; + } break; + } + + case RLI: case LRI: case FSI: { + /* Rules X5a, X5b, X5c */ - case RLO: - currentEmbedding = levels[i] = leastGreaterOdd(currentEmbedding); - tempType = currentOverride = R; - bover = true; + if (t == FSI) { + /* Rule X5c: decide whether this should be treated + * like RLI or LRI */ + size_t pdi = find_matching_pdi(ctx->types, i, ctx->textlen); + unsigned char level = rule_p2_p3(ctx->types + (i + 1), + pdi - (i + 1)); + t = (level == 1 ? RLI : LRI); + } + + ctx->levels[i] = stk->level; + if (stk->override != DO_NEUTRAL) + ctx->types[i] = (stk->override == DO_LTR ? L : + stk->override == DO_RTL ? R : t); + + unsigned char newLevel = (t == RLI ? nextOddLevel(stk->level) : + nextEvenLevel(stk->level)); + + if (newLevel <= MAX_DEPTH && + ctx->overflowIsolateCount == 0 && + ctx->overflowEmbeddingCount == 0) { + /* Isolate code is valid. Push a stack entry. */ + push(ctx, newLevel, DO_NEUTRAL, true); + ctx->validIsolateCount++; + } else { + /* Isolate code is an overflow one. */ + ctx->overflowIsolateCount++; + } break; + } - case LRO: - currentEmbedding = levels[i] = leastGreaterEven(currentEmbedding); - tempType = currentOverride = L; - bover = true; + case PDI: { + /* Rule X6a */ + if (ctx->overflowIsolateCount > 0) { + ctx->overflowIsolateCount--; + } else if (ctx->validIsolateCount == 0) { + /* Do nothing: spurious isolate-pop */ + } else { + /* Valid isolate-pop. We expect that the stack must + * therefore contain at least one isolate==true entry, + * so pop everything up to and including it. */ + ctx->overflowEmbeddingCount = 0; + while (!stk->isolate) + pop(ctx); + pop(ctx); + ctx->validIsolateCount--; + } + ctx->levels[i] = stk->level; + if (stk->override != DO_NEUTRAL) + ctx->types[i] = (stk->override == DO_LTR ? L : R); break; + } case PDF: { - int prevlevel = getPreviousLevel(levels, i); - - if (prevlevel == -1) { - currentEmbedding = paragraphLevel; - currentOverride = ON; + /* Rule X7 */ + if (ctx->overflowIsolateCount > 0) { + /* Do nothing if we've overflowed on isolates */ + } else if (ctx->overflowEmbeddingCount > 0) { + ctx->overflowEmbeddingCount--; + } else if (ctx->ds_sp > 0 && !stk->isolate) { + pop(ctx); } else { - currentOverride = currentEmbedding & OMASK; - currentEmbedding = currentEmbedding & ~OMASK; + /* Do nothing: spurious embedding-pop */ } - levels[i] = currentEmbedding; + +#ifndef REMOVE_FORMATTING_CHARS + ctx->levels[i] = stk->level; +#endif break; } - /* Whitespace is treated as neutral for now */ - case WS: - case S: - levels[i] = currentEmbedding; - tempType = ON; - if (currentOverride != ON) - tempType = currentOverride; + case B: { + /* Rule X8: if an explicit paragraph separator appears in + * this text at all then it does not participate in any of + * the above, and just gets assigned the paragraph level. + * + * PS, it had better be right at the end of the text, + * because we have not implemented rule P1 in this code. */ + assert(i == ctx->textlen - 1); + ctx->levels[i] = ctx->paragraphLevel; break; + } - default: - levels[i] = currentEmbedding; - if (currentOverride != ON) - tempType = currentOverride; + case BN: { + /* + * The section 5.2 adjustment to rule X6 says that we + * apply it to BN just like any other class. But I think + * this can't possibly give the same results as the + * unmodified algorithm. + * + * Proof: adding RLO BN or LRO BN at the end of a + * paragraph should not change the output of the standard + * algorithm, because the override doesn't affect the BN + * in rule X6, and then rule X9 removes both. But with the + * modified rule X6, the BN is changed into R or L, and + * then rule X9 doesn't remove it, and then you've added a + * strong type that will set eos for the level run just + * before the override. And whatever the standard + * algorithm set eos to, _one_ of these override sequences + * will disagree with it. + * + * So I think we just set the BN's level, and don't change + * its type. + */ + ctx->levels[i] = stk->level; break; + } + default: { + /* Rule X6. */ + ctx->levels[i] = stk->level; + if (stk->override != DO_NEUTRAL) + ctx->types[i] = (stk->override == DO_LTR ? L : R); + break; + } } - types[i] = tempType; } - /* this clears out all overrides, so we can use levels safely... */ - /* checks bover first */ - if (bover) - for (i=0; itextlen; i++) { + BidiType t = ctx->types[i]; + if (typeIsRemovedDuringProcessing(t)) { + ctx->types[i] = BN; + + /* + * My own adjustment to the section 5.2 mods: a sequence + * of contiguous BN generated by this setup should never + * be at different levels from each other. + * + * An example where this goes wrong is if you open two + * LREs in sequence, then close them again: + * + * ... LRE LRE PDF PDF ... + * + * The initial level assignment gives level 0 to the outer + * LRE/PDF pair, and level 2 to the inner one. The + * standard algorithm would remove all four, so this + * doesn't matter, and you end up with no break in the + * surrounding level run. But if you just rewrite the + * types of all those characters to BN and leave the + * levels in that state, then the modified algorithm will + * leave the middle two BN at level 2, dividing what + * should have been a long level run at level 0 into two + * separate ones. + */ + if (i > 0 && ctx->types[i-1] == BN) + ctx->levels[i] = ctx->levels[i-1]; } } - - /* 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. +#else + /* + * Rule X9, original version: completely remove embedding + * start/end characters and also boundary neutrals. */ - if (types[0] == NSM) - types[0] = paragraphLevel; - - for (i=1; itextlen; i++) { + BidiType t = ctx->types[i]; + if (!typeIsRemovedDuringProcessing(t)) { + ctx->text[outpos] = ctx->text[i]; + ctx->levels[outpos] = ctx->levels[i]; + ctx->types[outpos] = ctx->types[i]; + ctx->origTypes[outpos] = ctx->origTypes[i]; + outpos++; + } } + ctx->textlen = outpos; +#endif +} + +typedef void (*irs_fn_t)(BidiContext *ctx); - /* 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. +static void find_isolating_run_sequences(BidiContext *ctx, irs_fn_t process) +{ + /* + * Rule X10 / BD13. Now that we've assigned an embedding level to + * each character in the text, we have to divide the text into + * subsequences on which to do the next stage of processing. + * + * In earlier issues of the bidi algorithm, these subsequences + * were contiguous in the original text, and each one was a 'level + * run': a maximal contiguous subsequence of characters all at the + * same embedding level. + * + * But now we have isolates, and the point of an (isolate + * initiator ... PDI) sequence is that the whole sequence should + * be treated like a single BN for the purposes of formatting + * everything outside it. As a result, we now have to recombine + * our level runs into longer sequences, on the principle that if + * a level run ends with an isolate initiator, then we bring it + * together with whatever later level run starts with the matching + * PDI. + * + * These subsequences are no longer contiguous (the whole point is + * that between the isolate initiator and the PDI is some other + * text that we've skipped over). They're called 'isolating run + * sequences'. */ - for (i=0; i= 0) { - if (types[j] == AL) { - types[i] = AN; - break; - } else if (types[j] == R || types[j] == L) { + + memset(ctx->irsdone, 0, ctx->textlen); + size_t i = 0; + size_t n_irs = 0; + size_t indexpos = 0; + while (i < ctx->textlen) { + if (ctx->irsdone[i]) { + i++; + continue; + } + + /* + * Found a character not already processed. Start a new + * sequence here. + */ + sgrowarray(ctx->irslist, ctx->irslistsize, n_irs); + IsolatingRunSequence *irs = &ctx->irslist[n_irs++]; + irs->start = indexpos; + size_t j = i; + size_t irslevel = ctx->levels[i]; + while (j < ctx->textlen) { + /* + * We expect that all level runs in this sequence will be + * at the same level as each other, by construction of how + * we set up the levels from the isolates in the first + * place. + */ + assert(ctx->levels[j] == irslevel); + + do { + ctx->irsdone[j] = true; + ctx->irsindices[indexpos++] = j++; + } while (j < ctx->textlen && ctx->levels[j] == irslevel); + if (!typeIsIsolateInitiator(ctx->types[j-1])) + break; /* this IRS is ended */ + j = find_matching_pdi(ctx->types, j-1, ctx->textlen); + } + irs->end = indexpos; + + /* + * Determine the start-of-sequence and end-of-sequence types + * for this sequence. + * + * These depend on the embedding levels of surrounding text. + * But processing each run can change those levels. That's why + * we have to use a two-pass strategy here, first identifying + * all the isolating run sequences using the input level data, + * and not processing any of them until we know where they all + * are. + */ + size_t p; + unsigned char level_inside, level_outside, level_max; + + p = i; + level_inside = ctx->levels[p]; + level_outside = ctx->paragraphLevel; + while (p > 0) { + p--; + if (ctx->types[p] != BN) { + level_outside = ctx->levels[p]; + break; + } + } + level_max = max(level_inside, level_outside); + irs->sos = (level_max % 2 ? R : L); + + p = ctx->irsindices[irs->end - 1]; + level_inside = ctx->levels[p]; + level_outside = ctx->paragraphLevel; + if (typeIsIsolateInitiator(ctx->types[p])) { + /* Special case: if an isolating run sequence ends in an + * unmatched isolate initiator, then level_outside is + * taken to be the paragraph embedding level and the + * loop below is skipped. */ + } else { + while (p+1 < ctx->textlen) { + p++; + if (ctx->types[p] != BN) { + level_outside = ctx->levels[p]; break; } - j--; } } + level_max = max(level_inside, level_outside); + irs->eos = (level_max % 2 ? R : L); + + irs->embeddingDirection = (irslevel % 2 ? R : L); + + /* + * Now we've listed in ctx->irsindices[] the index of every + * character that's part of this isolating run sequence, and + * recorded an entry in irslist containing the interval of + * indices relevant to this IRS, plus its assorted metadata. + * We've also marked those locations in the input text as done + * in ctx->irsdone, so that we'll skip over them when the + * outer iteration reaches them later. + */ + } + + for (size_t k = 0; k < n_irs; k++) { + IsolatingRunSequence *irs = &ctx->irslist[k]; + ctx->irs = ctx->irsindices + irs->start; + ctx->irslen = irs->end - irs->start; + ctx->sos = irs->sos; + ctx->eos = irs->eos; + ctx->embeddingDirection = irs->embeddingDirection; + process(ctx); + } + + /* Reset irslen to 0 when we've finished. This means any other + * functions that absentmindedly try to use irslen at all will end + * up doing nothing at all, which should be easier to detect and + * debug than if they run on subtly the wrong subset of the + * text. */ + ctx->irslen = 0; +} + +static void remove_nsm(BidiContext *ctx) +{ + /* Rule W1: NSM gains the type of the previous character, or sos + * at the start of the run, with the exception that isolation + * boundaries turn into ON. */ + BidiType prevType = ctx->sos; + for (size_t c = 0; c < ctx->irslen; c++) { + size_t i = ctx->irs[c]; + BidiType t = ctx->types[i]; + if (t == NSM) { + ctx->types[i] = prevType; + } else if (typeIsIsolateInitiatorOrPDI(t)) { + prevType = ON; +#ifndef REMOVE_FORMATTING_CHARS + } else if (t == BN) { + /* section 5.2 adjustment: these don't affect prevType */ +#endif + } else { + prevType = t; + } + } +} + +static void change_en_to_an(BidiContext *ctx) +{ + /* Rule W2: EN becomes AN if the previous strong type is AL. (The + * spec says that the 'previous strong type' is counted as sos at + * the start of the run, although it hardly matters, since sos + * can't be AL.) */ + BidiType prevStrongType = ctx->sos; + for (size_t c = 0; c < ctx->irslen; c++) { + size_t i = ctx->irs[c]; + BidiType t = ctx->types[i]; + if (t == EN && prevStrongType == AL) { + ctx->types[i] = AN; + } else if (typeIsStrong(t)) { + prevStrongType = t; + } + } +} + +static void change_al_to_r(BidiContext *ctx) +{ + /* Rule W3: AL becomes R unconditionally. (The only difference + * between the two types was their effect on nearby numbers, which + * was dealt with in rule W2, so now we're done with the + * distinction.) */ + for (size_t c = 0; c < ctx->irslen; c++) { + size_t i = ctx->irs[c]; + if (ctx->types[i] == AL) + ctx->types[i] = R; } +} - /* Rule (W3) - * W3. Change all ALs to R. +static void eliminate_separators_between_numbers(BidiContext *ctx) +{ + /* Rule W4: a single numeric separator between two numbers of the + * same type compatible with that separator takes the type of the + * number. ES is a separator type compatible only with EN; CS is a + * separator type compatible with either EN or AN. * - * Optimization: on Rule Xn, we might set a flag on AL type - * to prevent this loop in L R lines only... - */ - for (i=0; iirslen; c++) { + size_t i = ctx->irs[c]; + BidiType t = ctx->types[i]; + +#ifndef REMOVE_FORMATTING_CHARS + if (t == BN) + continue; +#endif + + i0 = i1; i1 = i2; i2 = i; + t0 = t1; t1 = t2; t2 = t; + if (t0 == t2 && ((t1 == ES && t0 == EN) || + (t1 == CS && (t0 == EN || t0 == AN)))) { + ctx->types[i1] = t0; + } } +} - /* 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; +static void eliminate_et_next_to_en(BidiContext *ctx) +{ + /* Rule W5: a sequence of ET adjacent to an EN take the type EN. + * This is easiest to implement with one loop in each direction. + * + * Section 5.2 adjustment: include BN with ET. (We don't need to + * #ifdef that out, because in the standard algorithm, we won't + * have any BN left in any case.) */ + + bool modifying = false; + + for (size_t c = 0; c < ctx->irslen; c++) { + size_t i = ctx->irs[c]; + BidiType t = ctx->types[i]; + if (t == EN) { + modifying = true; + } else if (modifying && typeIsETOrBN(t)) { + ctx->types[i] = EN; + } else { + modifying = false; } } - /* Rule (W5) - * W5. A sequence of European terminators adjacent to European numbers - * changes to all European numbers. + for (size_t c = ctx->irslen; c-- > 0 ;) { + size_t i = ctx->irs[c]; + BidiType t = ctx->types[i]; + if (t == EN) { + modifying = true; + } else if (modifying && typeIsETOrBN(t)) { + ctx->types[i] = EN; + } else { + modifying = false; + } + } +} + +static void eliminate_separators_and_terminators(BidiContext *ctx) +{ + /* Rule W6: all separators and terminators change to ON. + * + * (The spec is not quite clear on which bidi types are included + * in this; one assumes ES, ET and CS, but what about S? I _think_ + * the answer is that this is a rule in the W section, so it's + * implicitly supposed to only apply to types designated as weakly + * directional, so not S.) */ + +#ifndef REMOVE_FORMATTING_CHARS + /* + * Section 5.2 adjustment: this also applies to any BN adjacent on + * either side to one of these types, which is easiest to + * implement with a separate double-loop converting those to an + * arbitrary one of the affected types, say CS. * - * Optimization: lots here... else ifs need rearrangement + * This double loop can be completely skipped in the standard + * algorithm. */ - for (i=0; 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-1 && types[j] == ET) { - j++; - } - if (types[j] == EN) - types[i] = EN; - } + bool modifying = false; + + for (size_t c = 0; c < ctx->irslen; c++) { + size_t i = ctx->irs[c]; + BidiType t = ctx->types[i]; + if (typeIsWeakSeparatorOrTerminator(t)) { + modifying = true; + } else if (modifying && t == BN) { + ctx->types[i] = CS; + } else { + modifying = false; } } - /* Rule (W6) - * W6. Otherwise, separators and terminators change to Other Neutral: - */ - for (i=0; iirslen; c-- > 0 ;) { + size_t i = ctx->irs[c]; + BidiType t = ctx->types[i]; + if (typeIsWeakSeparatorOrTerminator(t)) { + modifying = true; + } else if (modifying && t == BN) { + ctx->types[i] = CS; + } else { + modifying = false; } } +#endif + + /* Now the main part of rule W6 */ + for (size_t c = 0; c < ctx->irslen; c++) { + size_t i = ctx->irs[c]; + BidiType t = ctx->types[i]; + if (typeIsWeakSeparatorOrTerminator(t)) + ctx->types[i] = ON; + } +} + +static void change_en_to_l(BidiContext *ctx) +{ + /* Rule W7: EN becomes L if the previous strong type (or sos) is L. */ + BidiType prevStrongType = ctx->sos; + for (size_t c = 0; c < ctx->irslen; c++) { + size_t i = ctx->irs[c]; + BidiType t = ctx->types[i]; + if (t == EN && prevStrongType == L) { + ctx->types[i] = L; + } else if (typeIsStrong(t)) { + prevStrongType = t; + } + } +} - /* 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. +typedef void (*bracket_pair_fn)(BidiContext *ctx, size_t copen, size_t cclose); + +static void find_bracket_pairs(BidiContext *ctx, bracket_pair_fn process) +{ + const size_t NO_BRACKET = ~(size_t)0; + + /* + * Rule BD16. */ - for (i=0; i= 0) { - if (types[j] == L) { - types[i] = L; - break; - } else if (types[j] == R || types[j] == AL) { + size_t sp = 0; + for (size_t c = 0; c < ctx->irslen; c++) + ctx->bracketpos[c] = NO_BRACKET; + + for (size_t c = 0; c < ctx->irslen; c++) { + size_t i = ctx->irs[c]; + unsigned wc = ctx->text[i].wc; + BracketTypeData bt = bracket_type(wc); + if (bt.type == BT_OPEN) { + if (sp >= lenof(ctx->bstack)) { + /* + * Stack overflow. The spec says we simply give up at + * this point. + */ + goto found_all_pairs; + } + + ctx->bstack[sp].ch = wc; + ctx->bstack[sp].c = c; + sp++; + } else if (bt.type == BT_CLOSE) { + size_t new_sp = sp; + + /* + * Search up the stack for an entry containing a matching + * open bracket. If we find it, pop that entry and + * everything deeper, and record a matching pair. If we + * reach the bottom of the stack without finding anything, + * leave sp where it started. + */ + while (new_sp-- > 0) { + if (ctx->bstack[new_sp].ch == bt.partner || + ctx->bstack[new_sp].ch == bt.equiv_partner) { + /* Found a stack element matching this one */ + size_t cstart = ctx->bstack[new_sp].c; + ctx->bracketpos[cstart] = c; + sp = new_sp; 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; + found_all_pairs: + for (size_t c = 0; c < ctx->irslen; c++) { + if (ctx->bracketpos[c] != NO_BRACKET) { + process(ctx, c, ctx->bracketpos[c]); + } } - 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 (iirs[c]; + BidiType t = ctx->types[i]; + if (typeIsStrongOrNumber(t)) { + t = t == L ? L : R; /* numbers count as R */ + if (t == ctx->embeddingDirection) { + /* Found something inside the brackets matching the + * current level, so (a) is violated. */ + return ctx->embeddingDirection; + } else { + foundOppositeTypeInside = true; } } } - 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; + + if (!foundOppositeTypeInside) { + /* No strong types at all inside the brackets, so return ON to + * indicate that we're not messing with their type at all. */ + return ON; } - /* Rule (N2) - * N2. Any remaining neutrals take the embedding direction. - */ - for (i=0; i 0 ;) { + size_t i = ctx->irs[c]; + BidiType t = ctx->types[i]; + if (typeIsStrongOrNumber(t)) { + t = t == L ? L : R; /* numbers count as R */ + return t; } } - /* 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; isos; +} + +static void reset_bracket_type(BidiContext *ctx, size_t c, BidiType t) +{ + /* Final bullet point of rule N0: when we change the type of a + * bracket, the same change applies to any contiguous sequence of + * characters after it whose _original_ bidi type was NSM. */ + do { + ctx->types[ctx->irs[c++]] = t; + +#ifndef REMOVE_FORMATTING_CHARS + while (c < ctx->irslen && ctx->origTypes[ctx->irs[c]] == BN) { + /* Section 5.2 adjustment: skip past BN in the process. */ + c++; } - } +#endif + } while (c < ctx->irslen && ctx->origTypes[ctx->irs[c]] == NSM); +} - /* 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; itypes[ctx->irs[copen]]) && + typeIsNeutral(ctx->types[ctx->irs[cclose]])) { + BidiType t = get_bracket_type(ctx, copen, cclose); + if (t != ON) { + reset_bracket_type(ctx, copen, t); + reset_bracket_type(ctx, cclose, t); } } +} - /* 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. +static void remove_ni(BidiContext *ctx) +{ + /* + * Rules N1 and N2 together: neutral or isolate characters take + * the direction of the surrounding strong text if the nearest + * strong characters on each side match, and otherwise, they take + * the embedding direction. */ - j=count-1; - while (j>0 && (bidi_getType(line[j].wc) == WS)) { - j--; - } - if (j < (count-1)) { - for (j++; j=i ; j--) { - levels[j] = paragraphLevel; + const size_t NO_INDEX = ~(size_t)0; + BidiType prevStrongType = ctx->sos; + size_t c_ni_start = NO_INDEX; + for (size_t c = 0; c <= ctx->irslen; c++) { + BidiType t; + + if (c < ctx->irslen) { + size_t i = ctx->irs[c]; + t = ctx->types[i]; + } else { + /* One extra loop iteration, using eos to resolve the + * final sequence of NI if any */ + t = ctx->eos; + } + + if (typeIsStrongOrNumber(t)) { + t = t == L ? L : R; /* numbers count as R */ + if (c_ni_start != NO_INDEX) { + /* There are some NI we have to fix up */ + BidiType ni_type = (t == prevStrongType ? t : + ctx->embeddingDirection); + for (size_t c2 = c_ni_start; c2 < c; c2++) { + size_t i2 = ctx->irs[c2]; + BidiType t2 = ctx->types[i2]; + if (typeIsNeutralOrIsolate(t2)) + ctx->types[i2] = ni_type; } } - } else if (tempType == B || tempType == S) { - levels[i] = paragraphLevel; + prevStrongType = t; + c_ni_start = NO_INDEX; + } else if (typeIsNeutralOrIsolate(t) && c_ni_start == NO_INDEX) { + c_ni_start = c; } } +} + +static void resolve_implicit_levels(BidiContext *ctx) +{ + /* Rules I1 and I2 */ + for (size_t c = 0; c < ctx->irslen; c++) { + size_t i = ctx->irs[c]; + unsigned char level = ctx->levels[i]; + BidiType t = ctx->types[i]; + if (level % 2 == 0) { + /* Rule I1 */ + if (t == R) + ctx->levels[i] += 1; + else if (t == AN || t == EN) + ctx->levels[i] += 2; + } else { + /* Rule I2 */ + if (t == L || t == AN || t == EN) + ctx->levels[i] += 1; + } + } +} - /* 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. +static void process_isolating_run_sequence(BidiContext *ctx) +{ + /* Section W: resolve weak types */ + remove_nsm(ctx); + change_en_to_an(ctx); + change_al_to_r(ctx); + eliminate_separators_between_numbers(ctx); + eliminate_et_next_to_en(ctx); + eliminate_separators_and_terminators(ctx); + change_en_to_l(ctx); + + /* Section N: resolve neutral types (and isolates) */ + find_bracket_pairs(ctx, resolve_brackets); + remove_ni(ctx); + + /* Section I: resolve implicit levels */ + resolve_implicit_levels(ctx); +} + +static void reset_whitespace_and_separators(BidiContext *ctx) +{ + /* + * Rule L1: segment and paragraph separators, plus whitespace + * preceding them, all reset to the paragraph embedding level. + * This also applies to whitespace at the very end. + * + * This is done using the original types, not the versions that + * the rest of this algorithm has been merrily mutating. */ - /* Note: this is implemented before L2 for efficiency */ - for (i=0; itextlen; i-- > 0 ;) { + BidiType t = ctx->origTypes[i]; + if (typeIsSegmentOrParaSeparator(t)) { + ctx->levels[i] = ctx->paragraphLevel; + modifying = true; + } else if (modifying) { + if (typeIsWhitespaceOrIsolate(t)) { + ctx->levels[i] = ctx->paragraphLevel; + } else if (!typeIsRemovedDuringProcessing(t)) { + modifying = false; + } + } + } + +#ifndef REMOVE_FORMATTING_CHARS + /* + * Section 5.2 adjustment: types removed by rule X9 take the level + * of the character to their left. */ - /* 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++; + for (size_t i = 0; i < ctx->textlen; i++) { + BidiType t = ctx->origTypes[i]; + if (typeIsRemovedDuringProcessing(t)) { + /* Section 5.2 adjustment */ + ctx->levels[i] = (i > 0 ? ctx->levels[i-1] : ctx->paragraphLevel); + } } - /* 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--; +#endif /* ! REMOVE_FORMATTING_CHARS */ +} + +static void reverse(BidiContext *ctx, size_t start, size_t end) +{ + for (size_t i = start, j = end; i < j; i++, j--) { + bidi_char tmp = ctx->text[i]; + ctx->text[i] = ctx->text[j]; + ctx->text[j] = tmp; } +} - /* 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. +static void mirror_glyphs(BidiContext *ctx) +{ + /* + * Rule L3: any character with a mirror-image pair at an odd + * embedding level is replaced by its mirror image. + * + * This is specified in the standard as happening _after_ rule L2 + * (the actual reordering of the text). But it's much easier to + * implement it before, while our levels[] array still matches up + * to the text order. */ - sfree(types); - sfree(levels); - return; + for (size_t i = 0; i < ctx->textlen; i++) { + if (ctx->levels[i] % 2) + ctx->text[i].wc = mirror_glyph(ctx->text[i].wc); + } } +static void reverse_sequences(BidiContext *ctx) +{ + /* + * Rule L2: every maximal contiguous sequence of characters at a + * given level or higher is reversed. + */ + unsigned level = 0; + for (size_t i = 0; i < ctx->textlen; i++) + level = max(level, ctx->levels[i]); + + for (; level >= 1; level--) { + for (size_t i = 0; i < ctx->textlen; i++) { + if (ctx->levels[i] >= level) { + size_t start = i; + while (i+1 < ctx->textlen && ctx->levels[i+1] >= level) + i++; + reverse(ctx, start, i); + } + } + } +} /* - * Bad, Horrible function - * takes a pointer to a character that is checked for - * having a mirror glyph. + * The Main Bidi Function, and the only function that should be used + * by the outside world. + * + * text: a buffer of size textlen containing text to apply the + * Bidirectional algorithm to. */ -static void doMirror(unsigned int *ch) +void do_bidi_new(BidiContext *ctx, bidi_char *text, size_t textlen) { - 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; - } - } + ensure_arrays(ctx, textlen); + ctx->text = text; + ctx->textlen = textlen; + setup_types(ctx); + + /* Quick initial test: see if we need to bother with any work at all */ + if (!text_needs_bidi(ctx)) + return; + + set_paragraph_level(ctx); + process_explicit_embeddings(ctx); + remove_embedding_characters(ctx); + find_isolating_run_sequences(ctx, process_isolating_run_sequence); + + /* If this implementation distinguished paragraphs from lines, + * then this would be the point where we repeat the remainder of + * the algorithm once for each line in the paragraph. */ + + reset_whitespace_and_separators(ctx); + mirror_glyphs(ctx); + reverse_sequences(ctx); +} + +void do_bidi(BidiContext *ctx, bidi_char *text, size_t textlen) +{ +#ifdef REMOVE_FORMATTING_CHARACTERS + abort(); /* can't use the standard algorithm in a live terminal */ +#else + assert(textlen >= 0); + do_bidi_new(ctx, text, textlen); +#endif } diff --git a/terminal/bidi.h b/terminal/bidi.h index 53ffbcd3..dd488e1f 100644 --- a/terminal/bidi.h +++ b/terminal/bidi.h @@ -30,11 +30,15 @@ unsigned char bidi_getType(int ch); X(L) \ X(LRE) \ X(LRO) \ + X(LRI) \ X(R) \ X(AL) \ X(RLE) \ X(RLO) \ + X(RLI) \ X(PDF) \ + X(PDI) \ + X(FSI) \ X(EN) \ X(ES) \ X(ET) \ @@ -62,4 +66,69 @@ typedef enum { BIDI_CHAR_TYPE_LIST(ENUM_DECL) N_BIDI_TYPES } BidiType; typedef enum { SHAPING_CHAR_TYPE_LIST(ENUM_DECL) N_SHAPING_TYPES } ShapingType; #undef ENUM_DECL +static inline bool typeIsStrong(BidiType t) +{ + return ((1< Date: Sun, 10 Oct 2021 14:52:17 +0100 Subject: Test rig for the new bidi algorithm. This standalone CLI program runs the UCD bidi tests in the form provided in Unicode 14.0.0. You can run it by just saying bidi_test --class BidiTest.txt --char BidiCharacterTest.txt assuming those two UCD files are in the current directory. --- CMakeLists.txt | 4 + terminal/bidi.c | 8 ++ terminal/bidi.h | 13 ++ terminal/bidi_test.c | 365 +++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 390 insertions(+) create mode 100644 terminal/bidi_test.c diff --git a/CMakeLists.txt b/CMakeLists.txt index debd6e1d..299f04c4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -86,6 +86,10 @@ add_executable(bidi_gettype terminal/bidi_gettype.c) target_link_libraries(bidi_gettype guiterminal utils ${platform_libraries}) +add_executable(bidi_test + terminal/bidi_test.c) +target_link_libraries(bidi_test guiterminal utils ${platform_libraries}) + add_executable(plink ${platform}/plink.c be_all_s.c) diff --git a/terminal/bidi.c b/terminal/bidi.c index ea05e2bd..0e72b49d 100644 --- a/terminal/bidi.c +++ b/terminal/bidi.c @@ -3589,6 +3589,14 @@ void do_bidi_new(BidiContext *ctx, bidi_char *text, size_t textlen) reverse_sequences(ctx); } +size_t do_bidi_test(BidiContext *ctx, bidi_char *text, size_t textlen, + int override) +{ + ctx->paragraphOverride = (override > 0 ? L : override < 0 ? R : ON); + do_bidi_new(ctx, text, textlen); + return ctx->textlen; +} + void do_bidi(BidiContext *ctx, bidi_char *text, size_t textlen) { #ifdef REMOVE_FORMATTING_CHARACTERS diff --git a/terminal/bidi.h b/terminal/bidi.h index dd488e1f..90d68e5b 100644 --- a/terminal/bidi.h +++ b/terminal/bidi.h @@ -131,4 +131,17 @@ static inline bool typeIsETOrBN(BidiType t) return ((1< + +#include "putty.h" +#include "misc.h" +#include "bidi.h" + +static int pass = 0, fail = 0; + +static BidiContext *ctx; + +static const char *extract_word(char **ptr) +{ + char *p = *ptr; + while (*p && isspace((unsigned char)*p)) p++; + + char *start = p; + while (*p && !isspace((unsigned char)*p)) p++; + + if (*p) { + *p++ = '\0'; + while (*p && isspace((unsigned char)*p)) p++; + } + + *ptr = p; + return start; +} + +#define TYPETONAME(X) #X, +static const char *const typenames[] = { BIDI_CHAR_TYPE_LIST(TYPETONAME) }; +#undef TYPETONAME + +static void run_test(const char *filename, unsigned lineno, + bidi_char *bcs, size_t bcs_len, + const unsigned *order, size_t order_len, + int override) +{ + size_t bcs_orig_len = bcs_len; + bidi_char *bcs_orig = snewn(bcs_orig_len, bidi_char); + memcpy(bcs_orig, bcs, bcs_orig_len * sizeof(bidi_char)); + + bcs_len = do_bidi_test(ctx, bcs, bcs_len, override); + + /* + * TR9 revision 44 rule X9 says we remove explicit embedding + * controls and BN characters. So the test cases don't list them + * in the expected outputs. Do the same to our own output - unless + * we're testing the standard version of the algorithm, in which + * case, we expect the output to be exactly as the test cases say. + */ + unsigned *our_order = snewn(bcs_len, unsigned); + size_t our_order_len = 0; + for (size_t i = 0; i < bcs_len; i++) { + BidiType t = bidi_getType(bcs[i].wc); +#ifndef REMOVE_FORMATTING_CHARS + if (typeIsRemovedDuringProcessing(t)) + continue; +#endif + our_order[our_order_len++] = bcs[i].index; + } + + bool ok = false; + if (our_order_len == order_len) { + ok = true; + for (size_t i = 0; i < our_order_len; i++) + if (our_order[i] != order[i]) + ok = false; + } + if (ok) { + pass++; + } else { + fail++; + printf("%s:%u: failed order\n", filename, lineno); + printf(" input chars:"); + for (size_t i = 0; i < bcs_orig_len; i++) + printf(" %04x", bcs_orig[i].wc); + printf("\n"); + printf(" classes: "); + for (size_t i = 0; i < bcs_orig_len; i++) + printf(" %-4s", typenames[bidi_getType(bcs_orig[i].wc)]); + printf("\n"); + printf(" para level = %s\n", + override > 0 ? "LTR" : override < 0 ? "RTL" : "auto"); + printf(" expected:"); + for (size_t i = 0; i < order_len; i++) + printf(" %u", order[i]); + printf("\n"); + printf(" got: "); + for (size_t i = 0; i < our_order_len; i++) + printf(" %u", our_order[i]); + printf("\n"); + } + + /* Put the original data back so we can re-test with another override */ + memcpy(bcs, bcs_orig, bcs_orig_len * sizeof(bidi_char)); + + sfree(bcs_orig); + sfree(our_order); +} + +static void class_test(const char *filename, FILE *fp) +{ + unsigned lineno = 0; + size_t bcs_size = 0, bcs_len = 0; + bidi_char *bcs = NULL; + size_t order_size = 0, order_len = 0; + unsigned *order = NULL; + + /* Preliminary: find a representative character of every bidi + * type. Prefer positive-width ones if available. */ + unsigned representatives[N_BIDI_TYPES]; + for (size_t i = 0; i < N_BIDI_TYPES; i++) + representatives[i] = 0; + for (unsigned uc = 1; uc < 0x110000; uc++) { + unsigned type = bidi_getType(uc); + if (!representatives[type] || + (mk_wcwidth(representatives[type]) <= 0 && mk_wcwidth(uc) > 0)) + representatives[type] = uc; + } + + while (true) { + lineno++; + char *line = chomp(fgetline(fp)); + if (!line) + break; + + /* Skip blank lines and comments */ + if (!line[0] || line[0] == '#') { + sfree(line); + continue; + } + + /* Parse @Reorder lines, which tell us the expected output + * order for all following test cases (until superseded) */ + if (strstartswith(line, "@Reorder:")) { + char *p = line; + extract_word(&p); /* eat the "@Reorder:" header itself */ + order_len = 0; + while (1) { + const char *word = extract_word(&p); + if (!*word) + break; + sgrowarray(order, order_size, order_len); + order[order_len++] = strtoul(word, NULL, 0); + } + + sfree(line); + continue; + } + + /* Skip @Levels lines, which we don't (yet?) do anything with */ + if (strstartswith(line, "@Levels:")) { + sfree(line); + continue; + } + + /* Everything remaining should be an actual test */ + char *semicolon = strchr(line, ';'); + if (!semicolon) { + printf("%s:%u: bad test line': no bitmap\n", filename, lineno); + sfree(line); + continue; + } + *semicolon++ = '\0'; + unsigned bitmask = strtoul(semicolon, NULL, 0); + char *p = line; + bcs_len = 0; + bool test_ok = true; + while (1) { + const char *word = extract_word(&p); + if (!*word) + break; + unsigned type; + for (type = 0; type < N_BIDI_TYPES; type++) + if (!strcmp(word, typenames[type])) + break; + if (type == N_BIDI_TYPES) { + printf("%s:%u: bad test line: bad bidi type '%s'\n", + filename, lineno, word); + test_ok = false; + break; + } + sgrowarray(bcs, bcs_size, bcs_len); + bcs[bcs_len].wc = representatives[type]; + bcs[bcs_len].origwc = bcs[bcs_len].wc; + bcs[bcs_len].index = bcs_len; + bcs[bcs_len].nchars = 1; + bcs_len++; + } + + if (!test_ok) { + sfree(line); + continue; + } + + if (bitmask & 1) + run_test(filename, lineno, bcs, bcs_len, order, order_len, 0); + if (bitmask & 2) + run_test(filename, lineno, bcs, bcs_len, order, order_len, +1); + if (bitmask & 4) + run_test(filename, lineno, bcs, bcs_len, order, order_len, -1); + + sfree(line); + } + + sfree(bcs); + sfree(order); +} + +static void char_test(const char *filename, FILE *fp) +{ + unsigned lineno = 0; + size_t bcs_size = 0, bcs_len = 0; + bidi_char *bcs = NULL; + size_t order_size = 0, order_len = 0; + unsigned *order = NULL; + + while (true) { + lineno++; + char *line = chomp(fgetline(fp)); + if (!line) + break; + + /* Skip blank lines and comments */ + if (!line[0] || line[0] == '#') { + sfree(line); + continue; + } + + /* Break each test line up into its main fields */ + ptrlen input_pl, para_dir_pl, para_level_pl, levels_pl, order_pl; + { + ptrlen pl = ptrlen_from_asciz(line); + input_pl = ptrlen_get_word(&pl, ";"); + para_dir_pl = ptrlen_get_word(&pl, ";"); + para_level_pl = ptrlen_get_word(&pl, ";"); + levels_pl = ptrlen_get_word(&pl, ";"); + order_pl = ptrlen_get_word(&pl, ";"); + } + + int override; + { + char *para_dir_str = mkstr(para_dir_pl); + unsigned para_dir = strtoul(para_dir_str, NULL, 0); + sfree(para_dir_str); + + override = (para_dir == 0 ? +1 : para_dir == 1 ? -1 : 0); + } + + /* Break up the input into Unicode characters */ + bcs_len = 0; + { + ptrlen pl = input_pl; + while (pl.len) { + ptrlen chr = ptrlen_get_word(&pl, " "); + char *chrstr = mkstr(chr); + sgrowarray(bcs, bcs_size, bcs_len); + bcs[bcs_len].wc = strtoul(chrstr, NULL, 16); + bcs[bcs_len].origwc = bcs[bcs_len].wc; + bcs[bcs_len].index = bcs_len; + bcs[bcs_len].nchars = 1; + bcs_len++; + sfree(chrstr); + } + } + + /* Ditto the expected output order */ + order_len = 0; + { + ptrlen pl = order_pl; + while (pl.len) { + ptrlen chr = ptrlen_get_word(&pl, " "); + char *chrstr = mkstr(chr); + sgrowarray(order, order_size, order_len); + order[order_len++] = strtoul(chrstr, NULL, 0); + sfree(chrstr); + } + } + + run_test(filename, lineno, bcs, bcs_len, order, order_len, override); + sfree(line); + } + + sfree(bcs); + sfree(order); +} + +void out_of_memory(void) +{ + fprintf(stderr, "out of memory!\n"); + exit(2); +} + +static void usage(FILE *fp) +{ + fprintf(fp, "\ +usage: bidi_test ( ( --class | --char ) infile... )...\n\ +e.g.: bidi_test --class BidiTest.txt --char BidiCharacterTest.txt\n\ +also: --help display this text\n\ +"); +} + +int main(int argc, char **argv) +{ + void (*testfn)(const char *, FILE *) = NULL; + bool doing_opts = true; + const char *filename = NULL; + bool done_something = false; + + ctx = bidi_new_context(); + + while (--argc > 0) { + const char *arg = *++argv; + if (doing_opts && arg[0] == '-' && arg[1]) { + if (!strcmp(arg, "--")) { + doing_opts = false; + } else if (!strcmp(arg, "--class")) { + testfn = class_test; + } else if (!strcmp(arg, "--char")) { + testfn = char_test; + } else if (!strcmp(arg, "--help")) { + usage(stdout); + return 0; + } else { + fprintf(stderr, "unrecognised option '%s'\n", arg); + return 1; + } + } else { + const char *filename = arg; + + if (!strcmp(filename, "-")) { + testfn("", stdin); + } else { + FILE *fp = fopen(filename, "r"); + if (!fp) { + fprintf(stderr, "unable to open '%s'\n", filename); + return 1; + } + testfn(filename, fp); + fclose(fp); + } + done_something = true; + } + } + + if (!done_something) { + usage(stderr); + return 1; + } + + if (!filename) + filename = "-"; + + printf("pass %d fail %d total %d\n", pass, fail, pass + fail); + + bidi_free_context(ctx); + return fail != 0; +} -- cgit v1.2.3 From 54930cf784163294ce1211b9d7c3c91b372e13ef Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 10 Oct 2021 22:55:41 +0100 Subject: bidi.c: correct comments. I accidentally deleted the original author's name in my rewrite, which was unnecessarily unfriendly given that some of their code is still here. Also I made a thinko in my explanation of the U+00AD problem. --- terminal/bidi.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/terminal/bidi.c b/terminal/bidi.c index 0e72b49d..c7cb7b43 100644 --- a/terminal/bidi.c +++ b/terminal/bidi.c @@ -3,9 +3,9 @@ * algorithms for PuTTY. * * Original version written and kindly contributed to this code base - * by Arabeyes. The bidi part was almost completely rewritten in 2021 - * by Simon Tatham to bring it up to date, but the shaping part is - * still the one by the original authors. + * by Ahmad Khalifa of Arabeyes. The bidi part was almost completely + * rewritten in 2021 by Simon Tatham to bring it up to date, but the + * shaping part is still the one by the original authors. * * Implementation notes: * @@ -80,7 +80,7 @@ * * This would be fine, if it were not for the fact that - as far as I * can see - _exactly one_ Unicode code point in the discarded - * category has a wcwidth() of more than 1, namely U+00AD SOFT HYPHEN + * category has a wcwidth() of more than 0, namely U+00AD SOFT HYPHEN * which is a printing character for terminal purposes but has a bidi * class of BN. * -- cgit v1.2.3 From e744071a033e223ebdc8bb8e3bbab53ee649e3a3 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 16 Oct 2021 11:54:43 +0100 Subject: Remove some unused variables. clang warned about these in the recent bidi work. --- terminal/bidi.c | 4 ++-- terminal/bidi_test.c | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/terminal/bidi.c b/terminal/bidi.c index c7cb7b43..c8085ca4 100644 --- a/terminal/bidi.c +++ b/terminal/bidi.c @@ -3111,7 +3111,7 @@ static void eliminate_separators_between_numbers(BidiContext *ctx) * Section 5.2 adjustment: intervening BNs do not break this, so * instead of simply looking at types[irs[c-1]] and types[irs[c+1]], * we must track the last three indices we saw that were not BN. */ - size_t i0 = 0, i1 = 0, i2 = 0; + size_t i1 = 0, i2 = 0; BidiType t0 = ON, t1 = ON, t2 = ON; for (size_t c = 0; c < ctx->irslen; c++) { size_t i = ctx->irs[c]; @@ -3122,7 +3122,7 @@ static void eliminate_separators_between_numbers(BidiContext *ctx) continue; #endif - i0 = i1; i1 = i2; i2 = i; + i1 = i2; i2 = i; t0 = t1; t1 = t2; t2 = t; if (t0 == t2 && ((t1 == ES && t0 == EN) || (t1 == CS && (t0 == EN || t0 == AN)))) { diff --git a/terminal/bidi_test.c b/terminal/bidi_test.c index b4546af1..0b63029f 100644 --- a/terminal/bidi_test.c +++ b/terminal/bidi_test.c @@ -235,13 +235,13 @@ static void char_test(const char *filename, FILE *fp) } /* Break each test line up into its main fields */ - ptrlen input_pl, para_dir_pl, para_level_pl, levels_pl, order_pl; + ptrlen input_pl, para_dir_pl, order_pl; { ptrlen pl = ptrlen_from_asciz(line); input_pl = ptrlen_get_word(&pl, ";"); para_dir_pl = ptrlen_get_word(&pl, ";"); - para_level_pl = ptrlen_get_word(&pl, ";"); - levels_pl = ptrlen_get_word(&pl, ";"); + ptrlen_get_word(&pl, ";"); /* paragraph level, which we ignore */ + ptrlen_get_word(&pl, ";"); /* embedding levels, which we ignore */ order_pl = ptrlen_get_word(&pl, ";"); } -- cgit v1.2.3 From 4f41bc04ab27953cce112070b796b52a8d0de52d Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 16 Oct 2021 11:47:06 +0100 Subject: Charset-aware handling of C1 ST in OSC sequences. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the terminal is in UTF-8 mode, we accumulate UTF-8 text normally in the OSC string buffer - but the byte 0x9C is interpreted as the C1 control character String Terminator, which terminates the OSC sequence. That's not really what you want in UTF-8 mode, because 0x9C is also a perfectly normal UTF-8 continuation character. For example, you'd expect this to set the window title to "FÜNF": echo -ne '\033]0;FÜNF\007' but in fact, by the sheer chance that Ü is encoded with an 0x9C byte, you get a window title consisting of "F" followed by an illegal- encoding marker, and the OSC sequence is terminated abruptly so that the trailing 'NF' is printed normally to the terminal and then the BEL generates a beep. Now, in UTF-8 mode, we only support the C1 control for ST if it appears in the form of the proper UTF-8 encoding of U+009C. So that example now 'works', at least in the sense that the terminal considers the OSC sequence to terminate where the sender expected it to terminate. Another case where we interpret 0x9C inappropriately as ST is if the terminal is in a single-byte character set in which that character is a printing one. In CP437, for example, you can't set a window title containing a pound sign, because its encoding is 0x9C. This commit by itself doesn't make those window titles _work_, in the sense of coming out looking right. They just mean that the OSC sequence is not terminated at the wrong place. The actual title rendering will be fixed in the next commit. --- terminal/terminal.c | 96 +++++++++++++++++++++++++++++++++++++++++++---------- terminal/terminal.h | 2 +- 2 files changed, 79 insertions(+), 19 deletions(-) diff --git a/terminal/terminal.c b/terminal/terminal.c index dcdbfc6a..c087096f 100644 --- a/terminal/terminal.c +++ b/terminal/terminal.c @@ -5131,31 +5131,91 @@ static void term_out(Terminal *term) 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. + * OSC sequences can be terminated or aborted in + * various ways. * - * -- RDB + * The official way to terminate an OSC, per written + * standards, is the String Terminator, SC. That can + * appear in a 7-bit two-character form ESC \, or as + * an 8-bit C1 control 0x9C. + * + * We only accept 0x9C in circumstances where it + * doesn't interfere with our main character set + * processing: so in ISO 8859-1, for example, the byte + * 0x9C is interpreted as ST, but in CP437 it's + * interpreted as an ordinary printing character (as + * it happens, the pound sign), because you might + * perfectly well want to put it in the window title + * like any other printing character. + * + * In particular, in UTF-8 mode, 0x9C is a perfectly + * valid continuation byte for an ordinary printing + * character, so we don't accept the C1 control form + * of ST unless it appears as a full UTF-8 character + * in its own right, i.e. bytes 0xC2 0x9C. + * + * BEL is also treated as a clean termination of OSC, + * which I believe was a behaviour introduced by + * xterm. + * + * To prevent run-on storage of OSC data forever if + * emission of a control sequence is interrupted, we + * also treat various control characters as illegal, + * so that they abort the OSC without processing it + * and return to TOPLEVEL state. These are CR, LF, and + * any ESC that is *not* followed by \. */ + if (c == '\012' || c == '\015') { + /* CR or LF aborts */ 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. - */ + break; + } + + if (c == '\033') { + /* ESC goes into a state where we wait to see if + * the next character is \ */ + term->termstate = OSC_MAYBE_ST; + break; + } + + if (c == '\007' || (c == 0x9C && !in_utf(term) && + term->ucsdata->unitab_ctrl[c] != 0xFF)) { + /* BEL, or the C1 ST appearing as a one-byte + * encoding, cleanly terminates the OSC right here */ do_osc(term); term->termstate = TOPLEVEL; - } else if (c == '\033') - term->termstate = OSC_MAYBE_ST; - else if (term->osc_strlen < OSC_STR_MAX) + break; + } + + if (c == 0xC2 && in_utf(term)) { + /* 0xC2 is the UTF-8 character that might + * introduce the encoding of C1 ST */ + term->termstate = OSC_MAYBE_ST_UTF8; + break; + } + + /* Anything else gets added to the string */ + if (term->osc_strlen < OSC_STR_MAX) + term->osc_string[term->osc_strlen++] = (char)c; + break; + case OSC_MAYBE_ST_UTF8: + /* In UTF-8 mode, we've seen C2, so are we now seeing + * 9C? */ + if (c == 0x9C) { + /* Yes, so cleanly terminate the OSC */ + do_osc(term); + term->termstate = TOPLEVEL; + break; + } + /* No, so append the pending C2 byte to the OSC string + * followed by the current character, and go back to + * OSC string accumulation */ + if (term->osc_strlen < OSC_STR_MAX) + term->osc_string[term->osc_strlen++] = 0xC2; + if (term->osc_strlen < OSC_STR_MAX) term->osc_string[term->osc_strlen++] = (char)c; + term->termstate = OSC_STRING; break; case SEEN_OSC_P: { int max = (term->osc_strlen == 0 ? 21 : 15); diff --git a/terminal/terminal.h b/terminal/terminal.h index 75d08de7..c0524d05 100644 --- a/terminal/terminal.h +++ b/terminal/terminal.h @@ -200,7 +200,7 @@ struct terminal_tag { DO_CTRLS, SEEN_OSC_P, - OSC_STRING, OSC_MAYBE_ST, + OSC_STRING, OSC_MAYBE_ST, OSC_MAYBE_ST_UTF8, VT52_ESC, VT52_Y1, VT52_Y2, -- cgit v1.2.3 From c35d8b832801d926a16ad29c416b969654051ef0 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 16 Oct 2021 13:20:44 +0100 Subject: win_set_[icon_]title: send a codepage along with the string. While fixing the previous commit I noticed that window titles don't actually _work_ properly if you change the terminal character set, because the text accumulated in the OSC string buffer is sent to the TermWin as raw bytes, with no indication of what character set it should interpret them as. You might get lucky if you happened to choose the right charset (in particular, UTF-8 is a common default), but if you change the charset half way through a run, then there's certainly no way the frontend will know to interpret two window titles sent before and after the change in two different charsets. So, now win_set_title() and win_set_icon_title() both include a codepage parameter along with the byte string, and it's up to them to translate the provided window title from that encoding to whatever the local window system expects to receive. On Windows, that's wide-string Unicode, so we can just use the existing dup_mb_to_wc utility function. But in GTK, it's UTF-8, so I had to write an extra utility function to encode a wide string as UTF-8. --- fuzzterm.c | 4 ++-- misc.h | 5 +++++ putty.h | 14 ++++++++------ terminal/terminal.c | 11 +++++++++-- terminal/terminal.h | 1 + unix/window.c | 20 ++++++++++++++++---- utils/CMakeLists.txt | 1 + utils/dup_mb_to_wc.c | 28 ++++++++++++++++++++++++++++ utils/encode_wide_string_as_utf8.c | 25 +++++++++++++++++++++++++ windows/window.c | 31 ++++++++++++++++--------------- 10 files changed, 111 insertions(+), 29 deletions(-) create mode 100644 utils/dup_mb_to_wc.c create mode 100644 utils/encode_wide_string_as_utf8.c diff --git a/fuzzterm.c b/fuzzterm.c index a85df35a..a1902ede 100644 --- a/fuzzterm.c +++ b/fuzzterm.c @@ -82,8 +82,8 @@ static void fuzz_clip_write( static void fuzz_clip_request_paste(TermWin *tw, int clipboard) {} static void fuzz_refresh(TermWin *tw) {} static void fuzz_request_resize(TermWin *tw, int w, int h) {} -static void fuzz_set_title(TermWin *tw, const char *title) {} -static void fuzz_set_icon_title(TermWin *tw, const char *icontitle) {} +static void fuzz_set_title(TermWin *tw, const char *title, int codepage) {} +static void fuzz_set_icon_title(TermWin *tw, const char *icontitle, int cp) {} static void fuzz_set_minimised(TermWin *tw, bool minimised) {} static void fuzz_set_maximised(TermWin *tw, bool maximised) {} static void fuzz_move(TermWin *tw, int x, int y) {} diff --git a/misc.h b/misc.h index 04fe3e68..5c8f982e 100644 --- a/misc.h +++ b/misc.h @@ -215,6 +215,11 @@ bool smemeq(const void *av, const void *bv, size_t len); * been removed. */ size_t encode_utf8(void *output, unsigned long ch); +/* Encode a wide-character string into UTF-8. Tolerates surrogates if + * sizeof(wchar_t) == 2, assuming that in that case the wide string is + * encoded in UTF-16. */ +char *encode_wide_string_as_utf8(const wchar_t *wstr); + /* Write a string out in C string-literal format. */ void write_c_string_literal(FILE *fp, ptrlen str); diff --git a/putty.h b/putty.h index 6bff90bb..550b7ee5 100644 --- a/putty.h +++ b/putty.h @@ -1414,8 +1414,9 @@ struct TermWinVtable { void (*request_resize)(TermWin *, int w, int h); - void (*set_title)(TermWin *, const char *title); - void (*set_icon_title)(TermWin *, const char *icontitle); + void (*set_title)(TermWin *, const char *title, int codepage); + void (*set_icon_title)(TermWin *, const char *icontitle, int codepage); + /* set_minimised and set_maximised are assumed to set two * independent settings, rather than a single three-way * {min,normal,max} switch. The idea is that when you un-minimise @@ -1480,10 +1481,11 @@ static inline void win_refresh(TermWin *win) { win->vt->refresh(win); } static inline void win_request_resize(TermWin *win, int w, int h) { win->vt->request_resize(win, w, h); } -static inline void win_set_title(TermWin *win, const char *title) -{ win->vt->set_title(win, title); } -static inline void win_set_icon_title(TermWin *win, const char *icontitle) -{ win->vt->set_icon_title(win, icontitle); } +static inline void win_set_title(TermWin *win, const char *title, int codepage) +{ win->vt->set_title(win, title, codepage); } +static inline void win_set_icon_title(TermWin *win, const char *icontitle, + int codepage) +{ win->vt->set_icon_title(win, icontitle, codepage); } static inline void win_set_minimised(TermWin *win, bool minimised) { win->vt->set_minimised(win, minimised); } static inline void win_set_maximised(TermWin *win, bool maximised) diff --git a/terminal/terminal.c b/terminal/terminal.c index c087096f..e44f9f69 100644 --- a/terminal/terminal.c +++ b/terminal/terminal.c @@ -1433,11 +1433,13 @@ void term_update(Terminal *term) term->win_maximise_pending = false; } if (term->win_title_pending) { - win_set_title(term->win, term->window_title); + win_set_title(term->win, term->window_title, + term->wintitle_codepage); term->win_title_pending = false; } if (term->win_icon_title_pending) { - win_set_icon_title(term->win, term->icon_title); + win_set_icon_title(term->win, term->icon_title, + term->icontitle_codepage); term->win_icon_title_pending = false; } if (term->win_pointer_shape_pending) { @@ -1670,6 +1672,7 @@ void term_reconfig(Terminal *term, Conf *conf) if (strcmp(old_title, new_title)) { sfree(term->window_title); term->window_title = dupstr(new_title); + term->wintitle_codepage = DEFAULT_CODEPAGE; term->win_title_pending = true; term_schedule_update(term); } @@ -1807,6 +1810,7 @@ void term_setup_window_titles(Terminal *term, const char *title_hostname) term->window_title = dupstr(appname); term->icon_title = dupstr(term->window_title); } + term->wintitle_codepage = term->icontitle_codepage = DEFAULT_CODEPAGE; term->win_title_pending = true; term->win_icon_title_pending = true; } @@ -2032,6 +2036,7 @@ Terminal *term_init(Conf *myconf, struct unicode_data *ucsdata, TermWin *win) term->window_title = dupstr(""); term->icon_title = dupstr(""); + term->wintitle_codepage = term->icontitle_codepage = DEFAULT_CODEPAGE; term->minimised = false; term->winpos_x = term->winpos_y = 0; term->winpixsize_x = term->winpixsize_y = 0; @@ -3117,6 +3122,7 @@ static void do_osc(Terminal *term) if (!term->no_remote_wintitle) { sfree(term->icon_title); term->icon_title = dupstr(term->osc_string); + term->icontitle_codepage = term->ucsdata->line_codepage; term->win_icon_title_pending = true; term_schedule_update(term); } @@ -3128,6 +3134,7 @@ static void do_osc(Terminal *term) if (!term->no_remote_wintitle) { sfree(term->window_title); term->window_title = dupstr(term->osc_string); + term->wintitle_codepage = term->ucsdata->line_codepage; term->win_title_pending = true; term_schedule_update(term); } diff --git a/terminal/terminal.h b/terminal/terminal.h index c0524d05..3af752e7 100644 --- a/terminal/terminal.h +++ b/terminal/terminal.h @@ -351,6 +351,7 @@ struct terminal_tag { int mouse_paste_clipboard; char *window_title, *icon_title; + int wintitle_codepage, icontitle_codepage; bool minimised; BidiContext *bidi_ctx; diff --git a/unix/window.c b/unix/window.c index 69c35eb3..beaa2776 100644 --- a/unix/window.c +++ b/unix/window.c @@ -3248,19 +3248,31 @@ static void set_window_titles(GtkFrontend *inst) inst->icontitle); } -static void gtkwin_set_title(TermWin *tw, const char *title) +static void gtkwin_set_title(TermWin *tw, const char *title, int codepage) { GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); sfree(inst->wintitle); - inst->wintitle = dupstr(title); + if (codepage != CP_UTF8) { + wchar_t *title_w = dup_mb_to_wc(codepage, 0, title); + inst->wintitle = encode_wide_string_as_utf8(title_w); + sfree(title_w); + } else { + inst->wintitle = dupstr(title); + } set_window_titles(inst); } -static void gtkwin_set_icon_title(TermWin *tw, const char *title) +static void gtkwin_set_icon_title(TermWin *tw, const char *title, int codepage) { GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); sfree(inst->icontitle); - inst->icontitle = dupstr(title); + if (codepage != CP_UTF8) { + wchar_t *title_w = dup_mb_to_wc(codepage, 0, title); + inst->icontitle = encode_wide_string_as_utf8(title_w); + sfree(title_w); + } else { + inst->icontitle = dupstr(title); + } set_window_titles(inst); } diff --git a/utils/CMakeLists.txt b/utils/CMakeLists.txt index a209eb58..6376c0c9 100644 --- a/utils/CMakeLists.txt +++ b/utils/CMakeLists.txt @@ -15,6 +15,7 @@ add_sources_from_current_dir(utils dupprintf.c dupstr.c encode_utf8.c + encode_wide_string_as_utf8.c fgetline.c host_strchr.c host_strchr_internal.c diff --git a/utils/dup_mb_to_wc.c b/utils/dup_mb_to_wc.c new file mode 100644 index 00000000..7785f9b6 --- /dev/null +++ b/utils/dup_mb_to_wc.c @@ -0,0 +1,28 @@ +/* + * Centralised Unicode-related helper functions, separate from misc.c + * so that they can be omitted from tools that aren't including + * Unicode handling. + */ + +#include "putty.h" +#include "misc.h" + +wchar_t *dup_mb_to_wc_c(int codepage, int flags, const char *string, int len) +{ + int mult; + for (mult = 1 ;; mult++) { + wchar_t *ret = snewn(mult*len + 2, wchar_t); + int outlen; + outlen = mb_to_wc(codepage, flags, string, len, ret, mult*len + 1); + if (outlen < mult*len+1) { + ret[outlen] = L'\0'; + return ret; + } + sfree(ret); + } +} + +wchar_t *dup_mb_to_wc(int codepage, int flags, const char *string) +{ + return dup_mb_to_wc_c(codepage, flags, string, strlen(string)); +} diff --git a/utils/encode_wide_string_as_utf8.c b/utils/encode_wide_string_as_utf8.c new file mode 100644 index 00000000..870903d5 --- /dev/null +++ b/utils/encode_wide_string_as_utf8.c @@ -0,0 +1,25 @@ +/* + * Encode a string of wchar_t as UTF-8. + */ + +#include "putty.h" +#include "misc.h" + +char *encode_wide_string_as_utf8(const wchar_t *ws) +{ + strbuf *sb = strbuf_new(); + while (*ws) { + unsigned long ch = *ws++; + if (sizeof(wchar_t) == 2 && IS_HIGH_SURROGATE(ch) && + IS_LOW_SURROGATE(*ws)) { + ch = FROM_SURROGATES(ch, *ws); + ws++; + } else if (IS_SURROGATE(ch)) { + ch = 0xfffd; /* illegal UTF-16 -> REPLACEMENT CHARACTER */ + } + char utf8[6]; + size_t size = encode_utf8(utf8, ch); + put_data(sb, utf8, size); + } + return strbuf_to_str(sb); +} diff --git a/windows/window.c b/windows/window.c index 8fbbbd27..aba1c000 100644 --- a/windows/window.c +++ b/windows/window.c @@ -219,7 +219,7 @@ static bool pointer_indicates_raw_mouse = false; static BusyStatus busy_status = BUSY_NOT; -static char *window_name, *icon_name; +static wchar_t *window_name, *icon_name; static int compose_state = 0; @@ -250,8 +250,9 @@ static void wintw_clip_write( static void wintw_clip_request_paste(TermWin *, int clipboard); static void wintw_refresh(TermWin *); static void wintw_request_resize(TermWin *, int w, int h); -static void wintw_set_title(TermWin *, const char *title); -static void wintw_set_icon_title(TermWin *, const char *icontitle); +static void wintw_set_title(TermWin *, const char *title, int codepage); +static void wintw_set_icon_title(TermWin *, const char *icontitle, + int codepage); static void wintw_set_minimised(TermWin *, bool minimised); static void wintw_set_maximised(TermWin *, bool maximised); static void wintw_move(TermWin *, int x, int y); @@ -416,8 +417,8 @@ static void close_session(void *ignored_context) session_closed = true; newtitle = dupprintf("%s (inactive)", appname); - win_set_icon_title(wintw, newtitle); - win_set_title(wintw, newtitle); + win_set_icon_title(wintw, newtitle, DEFAULT_CODEPAGE); + win_set_title(wintw, newtitle, DEFAULT_CODEPAGE); sfree(newtitle); if (ldisc) { @@ -2955,11 +2956,11 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, term, r.right - r.left, r.bottom - r.top); } if (wParam == SIZE_MINIMIZED) - SetWindowText(hwnd, - conf_get_bool(conf, CONF_win_name_always) ? - window_name : icon_name); + SetWindowTextW(hwnd, + conf_get_bool(conf, CONF_win_name_always) ? + window_name : icon_name); if (wParam == SIZE_RESTORED || wParam == SIZE_MAXIMIZED) - SetWindowText(hwnd, window_name); + SetWindowTextW(hwnd, window_name); if (wParam == SIZE_RESTORED) { processed_resize = false; clear_full_screen(); @@ -4707,20 +4708,20 @@ static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam, return -1; } -static void wintw_set_title(TermWin *tw, const char *title) +static void wintw_set_title(TermWin *tw, const char *title, int codepage) { sfree(window_name); - window_name = dupstr(title); + window_name = dup_mb_to_wc(codepage, 0, title); if (conf_get_bool(conf, CONF_win_name_always) || !IsIconic(wgs.term_hwnd)) - SetWindowText(wgs.term_hwnd, title); + SetWindowTextW(wgs.term_hwnd, window_name); } -static void wintw_set_icon_title(TermWin *tw, const char *title) +static void wintw_set_icon_title(TermWin *tw, const char *title, int codepage) { sfree(icon_name); - icon_name = dupstr(title); + icon_name = dup_mb_to_wc(codepage, 0, title); if (!conf_get_bool(conf, CONF_win_name_always) && IsIconic(wgs.term_hwnd)) - SetWindowText(wgs.term_hwnd, title); + SetWindowTextW(wgs.term_hwnd, icon_name); } static void wintw_set_scrollbar(TermWin *tw, int total, int start, int page) -- cgit v1.2.3 From 22911ccdcc37c7955bfc1c45a88d3762d1206da7 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 18 Oct 2021 20:00:25 +0100 Subject: New config option for shifted arrow key handling. This commit introduces a new config option for how to handle shifted arrow keys. In the default mode (SHARROW_APPLICATION), we do what we've always done: Ctrl flips the arrow keys between sending their most usual escape sequences (ESC [ A ... ESC [ D) and sending the 'application cursor keys' sequences (ESC O A ... ESC O D). Whichever of those modes is currently configured, Ctrl+arrow sends the other one. In the new mode (SHARROW_BITMAP), application cursor key mode is unaffected by any shift keys, but the default sequences acquire two numeric arguments. The first argument is 1 (reflecting the fact that a shifted arrow key still notionally moves just 1 character cell); the second is the bitmap (1 for Shift) + (2 for Alt) + (4 for Ctrl), offset by 1. (Except that if _none_ of those modifiers is pressed, both numeric arguments are simply omitted.) The new bitmap mode is what current xterm generates, and also what Windows ConPTY seems to expect. If you start an ordinary Command Prompt and launch into WSL, those are the sequences it will generate for shifted arrow keys; conversely, if you run a Command Prompt within a ConPTY, then these sequences for Ctrl+arrow will have the effect you expect in cmd.exe command-line editing (going backward or forward a word). For that reason, I enable this mode unconditionally when launching Windows pterm. --- config.c | 6 ++++++ doc/config.but | 18 ++++++++++++++++++ doc/index.but | 3 +++ putty.h | 10 +++++++++- settings.c | 3 +++ terminal/terminal.c | 30 ++++++++++++++++++++++++++---- terminal/terminal.h | 2 +- unix/window.c | 4 +++- windows/help.h | 1 + windows/pterm.c | 2 ++ windows/window.c | 3 ++- 11 files changed, 74 insertions(+), 8 deletions(-) diff --git a/config.c b/config.c index 4c5da469..dd39862d 100644 --- a/config.c +++ b/config.c @@ -2016,6 +2016,12 @@ void setup_config_box(struct controlbox *b, bool midsession, I(CONF_funky_type), "ESC[n~", I(0), "Linux", I(1), "Xterm R6", I(2), "VT400", I(3), "VT100+", I(4), "SCO", I(5), NULL); + ctrl_radiobuttons(s, "Shift/Ctrl/Alt with the arrow keys", 'w', 2, + HELPCTX(keyboard_sharrow), + conf_radiobutton_handler, + I(CONF_sharrow_type), + "Ctrl toggles app mode", I(SHARROW_APPLICATION), + "xterm-style bitmap", I(SHARROW_BITMAP), NULL); s = ctrl_getset(b, "Terminal/Keyboard", "appkeypad", "Application keypad settings:"); diff --git a/doc/config.but b/doc/config.but index 904a04a9..a6a14ce9 100644 --- a/doc/config.but +++ b/doc/config.but @@ -599,6 +599,24 @@ to \c{ESC [v}, and with shift and control together they generate If you don't know what any of this means, you probably don't need to fiddle with it. +\S{config-sharrow} Changing the action of the \i{shifted arrow keys} + +This option affects the arrow keys, if you press one with any of the +modifier keys Shift, Ctrl or Alt held down. + +\b In the default mode, labelled \c{Ctrl toggles app mode}, the Ctrl +key toggles between the default arrow-key sequnces like \c{ESC [A} and +\c{ESC [B}, and the sequences Digital's terminals generate in +\q{application cursor keys} mode, i.e. \c{ESC O A} and so on. Shift +and Alt have no effect. + +\b In the \q{xterm-style bitmap} mode, Shift, Ctrl and Alt all +generate different sequences, with a number indicating which set of +modifiers is active. + +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 Application Cursor Keys mode is a way for the server to change the diff --git a/doc/index.but b/doc/index.but index 0629f81b..1e04acc1 100644 --- a/doc/index.but +++ b/doc/index.but @@ -931,3 +931,6 @@ saved sessions from \IM{system tray} system tray, Windows \IM{system tray} notification area, Windows (aka system tray) \IM{system tray} taskbar notification area, Windows (aka system tray) + +\IM{shifted arrow keys} arrow keys, shifted +\IM{shifted arrow keys} shifted arrow keys diff --git a/putty.h b/putty.h index 550b7ee5..77301bf8 100644 --- a/putty.h +++ b/putty.h @@ -533,6 +533,12 @@ enum { FUNKY_SCO }; +enum { + /* Shifted arrow key types (CONF_sharrow_type) */ + SHARROW_APPLICATION, /* Ctrl flips between ESC O A and ESC [ A */ + SHARROW_BITMAP /* ESC [ 1 ; n A, where n = 1 + bitmap of CAS */ +}; + enum { FQ_DEFAULT, FQ_ANTIALIASED, FQ_NONANTIALIASED, FQ_CLEARTYPE }; @@ -1606,6 +1612,7 @@ NORETURN void cleanup_exit(int); X(BOOL, NONE, bksp_is_delete) \ X(BOOL, NONE, rxvt_homeend) \ X(INT, NONE, funky_type) /* FUNKY_XTERM, FUNKY_LINUX, ... */ \ + X(INT, NONE, sharrow_type) /* SHARROW_APPLICATION, SHARROW_BITMAP, ... */ \ X(BOOL, NONE, no_applic_c) /* totally disable app cursor keys */ \ X(BOOL, NONE, no_applic_k) /* totally disable app keypad */ \ X(BOOL, NONE, no_mouse_rep) /* totally disable mouse reporting */ \ @@ -1937,7 +1944,8 @@ void term_palette_override(Terminal *term, unsigned osc4_index, rgb rgb); typedef enum SmallKeypadKey { SKK_HOME, SKK_END, SKK_INSERT, SKK_DELETE, SKK_PGUP, SKK_PGDN, } SmallKeypadKey; -int format_arrow_key(char *buf, Terminal *term, int xkey, bool ctrl); +int format_arrow_key(char *buf, Terminal *term, int xkey, + bool shift, bool ctrl, bool alt); int format_function_key(char *buf, Terminal *term, int key_number, bool shift, bool ctrl); int format_small_keypad_key(char *buf, Terminal *term, SmallKeypadKey key); diff --git a/settings.c b/settings.c index c0f181c5..ae0aebe0 100644 --- a/settings.c +++ b/settings.c @@ -630,6 +630,7 @@ void save_open_settings(settings_w *sesskey, Conf *conf) write_setting_b(sesskey, "BackspaceIsDelete", conf_get_bool(conf, CONF_bksp_is_delete)); write_setting_b(sesskey, "RXVTHomeEnd", conf_get_bool(conf, CONF_rxvt_homeend)); write_setting_i(sesskey, "LinuxFunctionKeys", conf_get_int(conf, CONF_funky_type)); + write_setting_i(sesskey, "ShiftedArrowKeys", conf_get_int(conf, CONF_sharrow_type)); write_setting_b(sesskey, "NoApplicationKeys", conf_get_bool(conf, CONF_no_applic_k)); write_setting_b(sesskey, "NoApplicationCursors", conf_get_bool(conf, CONF_no_applic_c)); write_setting_b(sesskey, "NoMouseReporting", conf_get_bool(conf, CONF_no_mouse_rep)); @@ -1046,6 +1047,8 @@ void load_open_settings(settings_r *sesskey, Conf *conf) gppb(sesskey, "BackspaceIsDelete", true, conf, CONF_bksp_is_delete); gppb(sesskey, "RXVTHomeEnd", false, conf, CONF_rxvt_homeend); gppi(sesskey, "LinuxFunctionKeys", 0, conf, CONF_funky_type); + gppi(sesskey, "ShiftedArrowKeys", SHARROW_APPLICATION, conf, + CONF_sharrow_type); gppb(sesskey, "NoApplicationKeys", false, conf, CONF_no_applic_k); gppb(sesskey, "NoApplicationCursors", false, conf, CONF_no_applic_c); gppb(sesskey, "NoMouseReporting", false, conf, CONF_no_mouse_rep); diff --git a/terminal/terminal.c b/terminal/terminal.c index e44f9f69..da255550 100644 --- a/terminal/terminal.c +++ b/terminal/terminal.c @@ -1555,6 +1555,7 @@ void term_copy_stuff_from_conf(Terminal *term) term->crhaslf = conf_get_bool(term->conf, CONF_crhaslf); term->erase_to_scrollback = conf_get_bool(term->conf, CONF_erase_to_scrollback); term->funky_type = conf_get_int(term->conf, CONF_funky_type); + term->sharrow_type = conf_get_int(term->conf, CONF_sharrow_type); term->lfhascr = conf_get_bool(term->conf, CONF_lfhascr); term->logflush = conf_get_bool(term->conf, CONF_logflush); term->logtype = conf_get_int(term->conf, CONF_logtype); @@ -7240,7 +7241,16 @@ void term_mouse(Terminal *term, Mouse_Button braw, Mouse_Button bcooked, term_schedule_update(term); } -int format_arrow_key(char *buf, Terminal *term, int xkey, bool ctrl) +static int shift_bitmap(bool shift, bool ctrl, bool alt) +{ + int bitmap = (shift ? 1 : 0) + (alt ? 2 : 0) + (ctrl ? 4 : 0); + if (bitmap) + bitmap++; + return bitmap; +} + +int format_arrow_key(char *buf, Terminal *term, int xkey, + bool shift, bool ctrl, bool alt) { char *p = buf; @@ -7263,12 +7273,24 @@ int format_arrow_key(char *buf, Terminal *term, int xkey, bool ctrl) if (!term->app_keypad_keys) app_flg = 0; #endif - /* Useful mapping of Ctrl-arrows */ - if (ctrl) - app_flg = !app_flg; + + int bitmap = 0; + + /* Adjustment based on Shift, Ctrl and/or Alt */ + switch (term->sharrow_type) { + case SHARROW_APPLICATION: + if (ctrl) + app_flg = !app_flg; + break; + case SHARROW_BITMAP: + bitmap = shift_bitmap(shift, ctrl, alt); + break; + } if (app_flg) p += sprintf(p, "\x1BO%c", xkey); + else if (bitmap) + p += sprintf(p, "\x1B[1;%d%c", bitmap, xkey); else p += sprintf(p, "\x1B[%c", xkey); } diff --git a/terminal/terminal.h b/terminal/terminal.h index 3af752e7..2eaaffcc 100644 --- a/terminal/terminal.h +++ b/terminal/terminal.h @@ -317,7 +317,7 @@ struct terminal_tag { int conf_width; bool crhaslf; bool erase_to_scrollback; - int funky_type; + int funky_type, sharrow_type; bool lfhascr; bool logflush; int logtype; diff --git a/unix/window.c b/unix/window.c index beaa2776..9f5948f4 100644 --- a/unix/window.c +++ b/unix/window.c @@ -1876,7 +1876,9 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) xkey = 'G'; goto arrow_key; arrow_key: end = 1 + format_arrow_key(output+1, inst->term, xkey, - event->state & GDK_CONTROL_MASK); + event->state & GDK_SHIFT_MASK, + event->state & GDK_CONTROL_MASK, + event->state & GDK_META_MASK); #ifdef KEY_EVENT_DIAGNOSTICS debug(" - arrow key"); #endif diff --git a/windows/help.h b/windows/help.h index c240f133..0ab0e050 100644 --- a/windows/help.h +++ b/windows/help.h @@ -26,6 +26,7 @@ #define WINHELP_CTX_keyboard_backspace "config-backspace" #define WINHELP_CTX_keyboard_homeend "config-homeend" #define WINHELP_CTX_keyboard_funkeys "config-funkeys" +#define WINHELP_CTX_keyboard_sharrow "config-sharrow" #define WINHELP_CTX_keyboard_appkeypad "config-appkeypad" #define WINHELP_CTX_keyboard_appcursor "config-appcursor" #define WINHELP_CTX_keyboard_nethack "config-nethack" diff --git a/windows/pterm.c b/windows/pterm.c index 57463449..2cdef30c 100644 --- a/windows/pterm.c +++ b/windows/pterm.c @@ -32,6 +32,8 @@ void gui_term_process_cmdline(Conf *conf, char *cmdline) cmdline_error("unexpected non-option argument \"%s\"", arg); } } + + conf_set_int(conf, CONF_sharrow_type, SHARROW_BITMAP); } const struct BackendVtable *backend_vt_from_conf(Conf *conf) diff --git a/windows/window.c b/windows/window.c index aba1c000..3b00ad96 100644 --- a/windows/window.c +++ b/windows/window.c @@ -4536,7 +4536,8 @@ static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam, case VK_LEFT: xkey = 'D'; goto arrow_key; case VK_CLEAR: xkey = 'G'; goto arrow_key; /* close enough */ arrow_key: - p += format_arrow_key((char *)p, term, xkey, shift_state & 2); + p += format_arrow_key((char *)p, term, xkey, shift_state & 1, + shift_state & 2, left_alt); return p - output; case VK_RETURN: -- cgit v1.2.3 From 6c24cb5c9fbeb34de7544b24d65bf21ad87a7d86 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Wed, 20 Oct 2021 17:25:55 +0100 Subject: Fix paste error in comment. For some reason, in my comment explaining which Visual Studio compile warnings I'd suppressed and why, one of the warning numbers in the comment totally failed to match the one in the suppression option! I probably pasted it from some other warning in that compile, which I fixed rather than suppressing. --- cmake/platforms/windows.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/platforms/windows.cmake b/cmake/platforms/windows.cmake index fb245003..ef3f7825 100644 --- a/cmake/platforms/windows.cmake +++ b/cmake/platforms/windows.cmake @@ -105,7 +105,7 @@ if(CMAKE_C_COMPILER_ID MATCHES "MSVC") # comes up a lot, and generally my spot checks make it look as if # it's OK. # - # - 4235: applying unary '-' to an unsigned type. We do that all + # - 4146: applying unary '-' to an unsigned type. We do that all # the time in deliberate bit-twiddling code like mpint.c or # crypto implementations. # -- cgit v1.2.3 From a40b581fc1df99bba6bfba42c777122ce9bc8f6b Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 23 Oct 2021 10:52:11 +0100 Subject: Fix Alt handling in the new shifted-arrow-key support. As well as affecting the bitmap field in the escape sequence, it was _also_ having its otherwise standard effect of prefixing Esc to the whole sequence. It shouldn't do both. --- putty.h | 2 +- terminal/terminal.c | 8 +++++--- unix/window.c | 8 +++++++- windows/window.c | 7 ++++++- 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/putty.h b/putty.h index 77301bf8..1719e8db 100644 --- a/putty.h +++ b/putty.h @@ -1945,7 +1945,7 @@ typedef enum SmallKeypadKey { SKK_HOME, SKK_END, SKK_INSERT, SKK_DELETE, SKK_PGUP, SKK_PGDN, } SmallKeypadKey; int format_arrow_key(char *buf, Terminal *term, int xkey, - bool shift, bool ctrl, bool alt); + bool shift, bool ctrl, bool alt, bool *consumed_alt); int format_function_key(char *buf, Terminal *term, int key_number, bool shift, bool ctrl); int format_small_keypad_key(char *buf, Terminal *term, SmallKeypadKey key); diff --git a/terminal/terminal.c b/terminal/terminal.c index da255550..dc12755c 100644 --- a/terminal/terminal.c +++ b/terminal/terminal.c @@ -7241,16 +7241,18 @@ void term_mouse(Terminal *term, Mouse_Button braw, Mouse_Button bcooked, term_schedule_update(term); } -static int shift_bitmap(bool shift, bool ctrl, bool alt) +static int shift_bitmap(bool shift, bool ctrl, bool alt, bool *consumed_alt) { int bitmap = (shift ? 1 : 0) + (alt ? 2 : 0) + (ctrl ? 4 : 0); if (bitmap) bitmap++; + if (alt && consumed_alt) + *consumed_alt = true; return bitmap; } int format_arrow_key(char *buf, Terminal *term, int xkey, - bool shift, bool ctrl, bool alt) + bool shift, bool ctrl, bool alt, bool *consumed_alt) { char *p = buf; @@ -7283,7 +7285,7 @@ int format_arrow_key(char *buf, Terminal *term, int xkey, app_flg = !app_flg; break; case SHARROW_BITMAP: - bitmap = shift_bitmap(shift, ctrl, alt); + bitmap = shift_bitmap(shift, ctrl, alt, consumed_alt); break; } diff --git a/unix/window.c b/unix/window.c index 9f5948f4..73ff5760 100644 --- a/unix/window.c +++ b/unix/window.c @@ -1808,6 +1808,8 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) switch (event->keyval) { int fkey_number; + bool consumed_meta_key; + case GDK_KEY_F1: fkey_number = 1; goto numbered_function_key; case GDK_KEY_F2: fkey_number = 2; goto numbered_function_key; case GDK_KEY_F3: fkey_number = 3; goto numbered_function_key; @@ -1875,10 +1877,14 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) case GDK_KEY_Begin: case GDK_KEY_KP_Begin: xkey = 'G'; goto arrow_key; arrow_key: + consumed_meta_key = false; end = 1 + format_arrow_key(output+1, inst->term, xkey, event->state & GDK_SHIFT_MASK, event->state & GDK_CONTROL_MASK, - event->state & GDK_META_MASK); + event->state & inst->meta_mod_mask, + &consumed_meta_key); + if (consumed_meta_key) + start = 1; /* supersedes the usual prefixing of Esc */ #ifdef KEY_EVENT_DIAGNOSTICS debug(" - arrow key"); #endif diff --git a/windows/window.c b/windows/window.c index 3b00ad96..a5bad374 100644 --- a/windows/window.c +++ b/windows/window.c @@ -4429,6 +4429,8 @@ static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam, } switch (wParam) { + bool consumed_alt; + case VK_NUMPAD0: keypad_key = '0'; goto numeric_keypad; case VK_NUMPAD1: keypad_key = '1'; goto numeric_keypad; case VK_NUMPAD2: keypad_key = '2'; goto numeric_keypad; @@ -4536,8 +4538,11 @@ static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam, case VK_LEFT: xkey = 'D'; goto arrow_key; case VK_CLEAR: xkey = 'G'; goto arrow_key; /* close enough */ arrow_key: + consumed_alt = false; p += format_arrow_key((char *)p, term, xkey, shift_state & 1, - shift_state & 2, left_alt); + shift_state & 2, left_alt, &consumed_alt); + if (consumed_alt) + left_alt = false; /* supersedes the usual prefixing of Esc */ return p - output; case VK_RETURN: -- cgit v1.2.3 From b13f3d079b66f25d26179185c65d40b348b4b570 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 23 Oct 2021 11:04:53 +0100 Subject: New function-key mode similar to modern xterm. This is the same as the previous FUNKY_XTERM mode if you don't press any modifier keys, but now Shift or Ctrl or Alt with function keys adds an extra bitmap parameter. The bitmaps are the same as the ones used by the new SHARROW_BITMAP arrow key mode. --- config.c | 12 +++++++++--- doc/config.but | 9 +++++++++ putty.h | 5 +++-- terminal/terminal.c | 34 ++++++++++++++++++++++++++++------ unix/window.c | 7 ++++++- windows/window.c | 6 +++++- 6 files changed, 60 insertions(+), 13 deletions(-) diff --git a/config.c b/config.c index dd39862d..e7597831 100644 --- a/config.c +++ b/config.c @@ -2010,12 +2010,18 @@ void setup_config_box(struct controlbox *b, bool midsession, conf_radiobutton_bool_handler, I(CONF_rxvt_homeend), "Standard", I(false), "rxvt", I(true), NULL); - ctrl_radiobuttons(s, "The Function keys and keypad", 'f', 3, + ctrl_radiobuttons(s, "The Function keys and keypad", 'f', 4, HELPCTX(keyboard_funkeys), conf_radiobutton_handler, I(CONF_funky_type), - "ESC[n~", I(0), "Linux", I(1), "Xterm R6", I(2), - "VT400", I(3), "VT100+", I(4), "SCO", I(5), NULL); + "ESC[n~", I(FUNKY_TILDE), + "Linux", I(FUNKY_LINUX), + "Xterm R6", I(FUNKY_XTERM), + "VT400", I(FUNKY_VT400), + "VT100+", I(FUNKY_VT100P), + "SCO", I(FUNKY_SCO), + "Xterm 216+", I(FUNKY_XTERM_216), + NULL); ctrl_radiobuttons(s, "Shift/Ctrl/Alt with the arrow keys", 'w', 2, HELPCTX(keyboard_sharrow), conf_radiobutton_handler, diff --git a/doc/config.but b/doc/config.but index a6a14ce9..df935388 100644 --- a/doc/config.but +++ b/doc/config.but @@ -596,6 +596,15 @@ 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 [\{}. +\b In \I{xterm}Xterm 216 mode, the unshifted function keys behave the +same as Xterm R6 mode. But pressing a function key together with Shift +or Alt or Ctrl generates a different sequence containing an extra +numeric parameter of the form (1 for Shift) + (2 for Alt) + (4 for +Ctrl) + 1. For F1-F4, the basic sequences like \c{ESC OP} become +\cw{ESC [1;}\e{bitmap}\cw{P} and similar; for F5 and above, +\cw{ESC[}\e{index}\cw{~} becomes +\cw{ESC[}\e{index}\cw{;}\e{bitmap}\cw{~}. + If you don't know what any of this means, you probably don't need to fiddle with it. diff --git a/putty.h b/putty.h index 1719e8db..ff679752 100644 --- a/putty.h +++ b/putty.h @@ -530,7 +530,8 @@ enum { FUNKY_XTERM, FUNKY_VT400, FUNKY_VT100P, - FUNKY_SCO + FUNKY_SCO, + FUNKY_XTERM_216 }; enum { @@ -1947,7 +1948,7 @@ typedef enum SmallKeypadKey { int format_arrow_key(char *buf, Terminal *term, int xkey, bool shift, bool ctrl, bool alt, bool *consumed_alt); int format_function_key(char *buf, Terminal *term, int key_number, - bool shift, bool ctrl); + bool shift, bool ctrl, bool alt, bool *consumed_alt); int format_small_keypad_key(char *buf, Terminal *term, SmallKeypadKey key); int format_numeric_keypad_key(char *buf, Terminal *term, char key, bool shift, bool ctrl); diff --git a/terminal/terminal.c b/terminal/terminal.c index dc12755c..424683dc 100644 --- a/terminal/terminal.c +++ b/terminal/terminal.c @@ -7301,7 +7301,7 @@ int format_arrow_key(char *buf, Terminal *term, int xkey, } int format_function_key(char *buf, Terminal *term, int key_number, - bool shift, bool ctrl) + bool shift, bool ctrl, bool alt, bool *consumed_alt) { char *p = buf; @@ -7314,7 +7314,14 @@ int format_function_key(char *buf, Terminal *term, int key_number, assert(key_number > 0); assert(key_number < lenof(key_number_to_tilde_code)); - int index = (shift && key_number <= 10) ? key_number + 10 : key_number; + int index = key_number; + if (term->funky_type != FUNKY_XTERM_216) { + if (shift && index <= 10) { + shift = false; + index += 10; + } + } + int code = key_number_to_tilde_code[index]; if (term->funky_type == FUNKY_SCO) { @@ -7338,13 +7345,28 @@ int format_function_key(char *buf, Terminal *term, int key_number, p += sprintf(p, "\x1BO%c", code + 'P' - 11 - offt); } else if (term->funky_type == FUNKY_LINUX && code >= 11 && code <= 15) { p += sprintf(p, "\x1B[[%c", code + 'A' - 11); - } else if (term->funky_type == FUNKY_XTERM && code >= 11 && code <= 14) { + } else if ((term->funky_type == FUNKY_XTERM || + term->funky_type == FUNKY_XTERM_216) && + code >= 11 && code <= 14) { if (term->vt52_mode) p += sprintf(p, "\x1B%c", code + 'P' - 11); - else - p += sprintf(p, "\x1BO%c", code + 'P' - 11); + else { + int bitmap = 0; + if (term->funky_type == FUNKY_XTERM_216) + bitmap = shift_bitmap(shift, ctrl, alt, consumed_alt); + if (bitmap) + p += sprintf(p, "\x1B[1;%d%c", bitmap, code + 'P' - 11); + else + p += sprintf(p, "\x1BO%c", code + 'P' - 11); + } } else { - p += sprintf(p, "\x1B[%d~", code); + int bitmap = 0; + if (term->funky_type == FUNKY_XTERM_216) + bitmap = shift_bitmap(shift, ctrl, alt, consumed_alt); + if (bitmap) + p += sprintf(p, "\x1B[%d;%d~", code, bitmap); + else + p += sprintf(p, "\x1B[%d~", code); } return p - buf; diff --git a/unix/window.c b/unix/window.c index 73ff5760..8740bc50 100644 --- a/unix/window.c +++ b/unix/window.c @@ -1831,9 +1831,14 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) case GDK_KEY_F19: fkey_number = 19; goto numbered_function_key; case GDK_KEY_F20: fkey_number = 20; goto numbered_function_key; numbered_function_key: + consumed_meta_key = false; end = 1 + format_function_key(output+1, inst->term, fkey_number, event->state & GDK_SHIFT_MASK, - event->state & GDK_CONTROL_MASK); + event->state & GDK_CONTROL_MASK, + event->state & inst->meta_mod_mask, + &consumed_meta_key); + if (consumed_meta_key) + start = 1; /* supersedes the usual prefixing of Esc */ #ifdef KEY_EVENT_DIAGNOSTICS debug(" - function key F%d", fkey_number); #endif diff --git a/windows/window.c b/windows/window.c index a5bad374..2f7cb274 100644 --- a/windows/window.c +++ b/windows/window.c @@ -4512,8 +4512,12 @@ static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam, case VK_F19: fkey_number = 19; goto numbered_function_key; case VK_F20: fkey_number = 20; goto numbered_function_key; numbered_function_key: + consumed_alt = false; p += format_function_key((char *)p, term, fkey_number, - shift_state & 1, shift_state & 2); + shift_state & 1, shift_state & 2, + left_alt, &consumed_alt); + if (consumed_alt) + left_alt = false; /* supersedes the usual prefixing of Esc */ return p - output; SmallKeypadKey sk_key; -- cgit v1.2.3 From d42f1fe96d4a42f94954cf665ba12e6f1317fc62 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 23 Oct 2021 17:54:21 +0100 Subject: Remove 'calling_back' parameter from plug_closing. It was totally unused. No implementation of the 'closing' method in a Plug vtable was checking it for any reason at all, except for ProxySocket which captured it from its client in order to pass on to its server (which, perhaps after further iterations of ProxySocket, would have ended up ignoring it similarly). And every caller of plug_closing set it to 0 (aka false), except for the one in sshproxy.c which passed true (but it would have made no difference to anyone). The comment in network.h refers to a FIXME comment which was in try_send() when that code was written (see winnet.c in commit 7b0e08270058390). That FIXME is long gone, replaced by a use of a toplevel callback. So I think the aim must have been to avoid re-entrancy when sk_write called try_send which encountered a socket error and called back to plug_closing - but that's long since fixed by other means now. --- cproxy.c | 10 ++++----- network.h | 10 ++++----- nocproxy.c | 4 ++-- nullplug.c | 3 +-- otherbackends/raw.c | 3 +-- otherbackends/rlogin.c | 3 +-- otherbackends/supdup.c | 3 +-- otherbackends/telnet.c | 3 +-- pageant.c | 4 ++-- proxy.c | 56 ++++++++++++++++++++++--------------------------- proxy.h | 1 - psocks.c | 4 ++-- ssh/portfwd.c | 6 ++---- ssh/server.c | 3 +-- ssh/sesschan.c | 3 +-- ssh/sharing.c | 5 ++--- ssh/ssh.c | 3 +-- ssh/x11fwd.c | 3 +-- sshproxy.c | 4 ++-- unix/fd-socket.c | 7 +++---- unix/network.c | 10 ++++----- unix/pageant.c | 3 +-- unix/psusan.c | 3 +-- unix/uppity.c | 3 +-- windows/handle-socket.c | 6 +++--- windows/network.c | 14 ++++++------- 26 files changed, 76 insertions(+), 101 deletions(-) diff --git a/cproxy.c b/cproxy.c index e1d788b6..d187ffec 100644 --- a/cproxy.c +++ b/cproxy.c @@ -67,13 +67,13 @@ int proxy_socks5_handlechap (ProxySocket *p) if (data[0] != 0x01) { plug_closing(p->plug, "Proxy error: SOCKS proxy wants" " a different CHAP version", - PROXY_ERROR_GENERAL, 0); + PROXY_ERROR_GENERAL); return 1; } if (data[1] == 0x00) { plug_closing(p->plug, "Proxy error: SOCKS proxy won't" " negotiate CHAP with us", - PROXY_ERROR_GENERAL, 0); + PROXY_ERROR_GENERAL); return 1; } p->chap_num_attributes = data[1]; @@ -105,7 +105,7 @@ int proxy_socks5_handlechap (ProxySocket *p) else { plug_closing(p->plug, "Proxy error: SOCKS proxy" " refused CHAP authentication", - PROXY_ERROR_GENERAL, 0); + PROXY_ERROR_GENERAL); return 1; } break; @@ -125,7 +125,7 @@ int proxy_socks5_handlechap (ProxySocket *p) plug_closing(p->plug, "Proxy error: Server chose " "CHAP of other than HMAC-MD5 but we " "didn't offer it!", - PROXY_ERROR_GENERAL, 0); + PROXY_ERROR_GENERAL); return 1; } break; @@ -174,6 +174,6 @@ int proxy_socks5_selectchap(ProxySocket *p) } else plug_closing(p->plug, "Proxy error: Server chose " "CHAP authentication but we didn't offer it!", - PROXY_ERROR_GENERAL, 0); + PROXY_ERROR_GENERAL); return 1; } diff --git a/network.h b/network.h index 85c5a6d9..ff88923b 100644 --- a/network.h +++ b/network.h @@ -87,7 +87,7 @@ struct PlugVtable { * the logged events. */ void (*closing) - (Plug *p, const char *error_msg, int error_code, bool calling_back); + (Plug *p, const char *error_msg, int error_code); /* 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()) */ @@ -214,9 +214,8 @@ static inline void sk_write_eof(Socket *s) static inline void plug_log( Plug *p, int type, SockAddr *addr, int port, const char *msg, int code) { p->vt->log(p, type, addr, port, msg, code); } -static inline void plug_closing( - Plug *p, const char *msg, int code, bool calling_back) -{ p->vt->closing(p, msg, code, calling_back); } +static inline void plug_closing(Plug *p, const char *msg, int code) +{ p->vt->closing(p, msg, code); } static inline void plug_receive(Plug *p, int urg, const char *data, size_t len) { p->vt->receive(p, urg, data, len); } static inline void plug_sent (Plug *p, size_t bufsize) @@ -341,8 +340,7 @@ extern Plug *const nullplug; */ void nullplug_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, const char *err_msg, int err_code); -void nullplug_closing(Plug *plug, const char *error_msg, int error_code, - bool calling_back); +void nullplug_closing(Plug *plug, const char *error_msg, int error_code); void nullplug_receive(Plug *plug, int urgent, const char *data, size_t len); void nullplug_sent(Plug *plug, size_t bufsize); diff --git a/nocproxy.c b/nocproxy.c index f93214fa..cdc28a39 100644 --- a/nocproxy.c +++ b/nocproxy.c @@ -22,7 +22,7 @@ int proxy_socks5_handlechap (ProxySocket *p) plug_closing(p->plug, "Proxy error: Trying to handle a SOCKS5 CHAP request" " in telnet-only build", - PROXY_ERROR_GENERAL, 0); + PROXY_ERROR_GENERAL); return 1; } @@ -30,6 +30,6 @@ int proxy_socks5_selectchap(ProxySocket *p) { plug_closing(p->plug, "Proxy error: Trying to handle a SOCKS5 CHAP request" " in telnet-only build", - PROXY_ERROR_GENERAL, 0); + PROXY_ERROR_GENERAL); return 1; } diff --git a/nullplug.c b/nullplug.c index 380e4b4f..ad65f5ea 100644 --- a/nullplug.c +++ b/nullplug.c @@ -12,8 +12,7 @@ void nullplug_log(Plug *plug, PlugLogType type, SockAddr *addr, { } -void nullplug_closing(Plug *plug, const char *error_msg, int error_code, - bool calling_back) +void nullplug_closing(Plug *plug, const char *error_msg, int error_code) { } diff --git a/otherbackends/raw.c b/otherbackends/raw.c index 97b7be09..491a3df9 100644 --- a/otherbackends/raw.c +++ b/otherbackends/raw.c @@ -69,8 +69,7 @@ static void raw_check_close(Raw *raw) } } -static void raw_closing(Plug *plug, const char *error_msg, int error_code, - bool calling_back) +static void raw_closing(Plug *plug, const char *error_msg, int error_code) { Raw *raw = container_of(plug, Raw, plug); diff --git a/otherbackends/rlogin.c b/otherbackends/rlogin.c index 09f1ab1d..9951ce18 100644 --- a/otherbackends/rlogin.c +++ b/otherbackends/rlogin.c @@ -89,8 +89,7 @@ static void rlogin_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, } } -static void rlogin_closing(Plug *plug, const char *error_msg, int error_code, - bool calling_back) +static void rlogin_closing(Plug *plug, const char *error_msg, int error_code) { Rlogin *rlogin = container_of(plug, Rlogin, plug); diff --git a/otherbackends/supdup.c b/otherbackends/supdup.c index a3418fe1..f015e054 100644 --- a/otherbackends/supdup.c +++ b/otherbackends/supdup.c @@ -577,8 +577,7 @@ static void supdup_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, } } -static void supdup_closing(Plug *plug, const char *error_msg, int error_code, - bool calling_back) +static void supdup_closing(Plug *plug, const char *error_msg, int error_code) { Supdup *supdup = container_of(plug, Supdup, plug); diff --git a/otherbackends/telnet.c b/otherbackends/telnet.c index 98c202c6..281c39ac 100644 --- a/otherbackends/telnet.c +++ b/otherbackends/telnet.c @@ -639,8 +639,7 @@ static void telnet_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, } } -static void telnet_closing(Plug *plug, const char *error_msg, int error_code, - bool calling_back) +static void telnet_closing(Plug *plug, const char *error_msg, int error_code) { Telnet *telnet = container_of(plug, Telnet, plug); diff --git a/pageant.c b/pageant.c index 89256b53..bb658cfe 100644 --- a/pageant.c +++ b/pageant.c @@ -1464,7 +1464,7 @@ struct pageant_conn_state { }; static void pageant_conn_closing(Plug *plug, const char *error_msg, - int error_code, bool calling_back) + int error_code) { struct pageant_conn_state *pc = container_of( plug, struct pageant_conn_state, plug); @@ -1611,7 +1611,7 @@ struct pageant_listen_state { }; static void pageant_listen_closing(Plug *plug, const char *error_msg, - int error_code, bool calling_back) + int error_code) { struct pageant_listen_state *pl = container_of( plug, struct pageant_listen_state, plug); diff --git a/proxy.c b/proxy.c index 97a4b1df..08d5afba 100644 --- a/proxy.c +++ b/proxy.c @@ -181,18 +181,16 @@ static void plug_proxy_log(Plug *plug, PlugLogType type, SockAddr *addr, plug_log(ps->plug, type, addr, port, error_msg, error_code); } -static void plug_proxy_closing (Plug *p, const char *error_msg, - int error_code, bool calling_back) +static void plug_proxy_closing(Plug *p, const char *error_msg, int error_code) { ProxySocket *ps = container_of(p, ProxySocket, plugimpl); if (ps->state != PROXY_STATE_ACTIVE) { ps->closing_error_msg = error_msg; ps->closing_error_code = error_code; - ps->closing_calling_back = calling_back; ps->negotiate(ps, PROXY_CHANGE_CLOSING); } else { - plug_closing(ps->plug, error_msg, error_code, calling_back); + plug_closing(ps->plug, error_msg, error_code); } } @@ -617,8 +615,7 @@ int proxy_http_negotiate (ProxySocket *p, int change) * a socket close, then some error must have occurred. we'll * just pass those errors up to the backend. */ - plug_closing(p->plug, p->closing_error_msg, p->closing_error_code, - p->closing_calling_back); + plug_closing(p->plug, p->closing_error_msg, p->closing_error_code); return 0; /* ignored */ } @@ -675,7 +672,7 @@ int proxy_http_negotiate (ProxySocket *p, int change) 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); + PROXY_ERROR_GENERAL); sfree(data); return 1; } @@ -690,7 +687,7 @@ int proxy_http_negotiate (ProxySocket *p, int change) (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); + plug_closing(p->plug, buf, PROXY_ERROR_GENERAL); sfree(buf); sfree(data); return 1; @@ -741,7 +738,7 @@ int proxy_http_negotiate (ProxySocket *p, int change) } plug_closing(p->plug, "Proxy error: unexpected proxy error", - PROXY_ERROR_UNEXPECTED, 0); + PROXY_ERROR_UNEXPECTED); return 1; } @@ -807,8 +804,7 @@ int proxy_socks4_negotiate (ProxySocket *p, int change) * a socket close, then some error must have occurred. we'll * just pass those errors up to the backend. */ - plug_closing(p->plug, p->closing_error_msg, p->closing_error_code, - p->closing_calling_back); + plug_closing(p->plug, p->closing_error_msg, p->closing_error_code); return 0; /* ignored */ } @@ -860,7 +856,7 @@ int proxy_socks4_negotiate (ProxySocket *p, int change) if (data[0] != 0) { plug_closing(p->plug, "Proxy error: SOCKS proxy responded with " "unexpected reply code version", - PROXY_ERROR_GENERAL, 0); + PROXY_ERROR_GENERAL); return 1; } @@ -869,16 +865,16 @@ int proxy_socks4_negotiate (ProxySocket *p, int change) switch (data[1]) { case 92: plug_closing(p->plug, "Proxy error: SOCKS server wanted IDENTD on client", - PROXY_ERROR_GENERAL, 0); + PROXY_ERROR_GENERAL); break; case 93: plug_closing(p->plug, "Proxy error: Username and IDENTD on client don't agree", - PROXY_ERROR_GENERAL, 0); + PROXY_ERROR_GENERAL); break; case 91: default: plug_closing(p->plug, "Proxy error: Error while communicating with proxy", - PROXY_ERROR_GENERAL, 0); + PROXY_ERROR_GENERAL); break; } @@ -895,7 +891,7 @@ int proxy_socks4_negotiate (ProxySocket *p, int change) } plug_closing(p->plug, "Proxy error: unexpected proxy error", - PROXY_ERROR_UNEXPECTED, 0); + PROXY_ERROR_UNEXPECTED); return 1; } @@ -951,8 +947,7 @@ int proxy_socks5_negotiate (ProxySocket *p, int change) * a socket close, then some error must have occurred. we'll * just pass those errors up to the backend. */ - plug_closing(p->plug, p->closing_error_msg, p->closing_error_code, - p->closing_calling_back); + plug_closing(p->plug, p->closing_error_msg, p->closing_error_code); return 0; /* ignored */ } @@ -1002,7 +997,7 @@ int proxy_socks5_negotiate (ProxySocket *p, int change) if (data[0] != 5) { plug_closing(p->plug, "Proxy error: SOCKS proxy returned unexpected version", - PROXY_ERROR_GENERAL, 0); + PROXY_ERROR_GENERAL); return 1; } @@ -1012,7 +1007,7 @@ int proxy_socks5_negotiate (ProxySocket *p, int change) 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); + PROXY_ERROR_GENERAL); return 1; } bufchain_consume(&p->pending_input_data, 2); @@ -1037,7 +1032,7 @@ int proxy_socks5_negotiate (ProxySocket *p, int change) if (data[0] != 1) { plug_closing(p->plug, "Proxy error: SOCKS password " "subnegotiation contained wrong version number", - PROXY_ERROR_GENERAL, 0); + PROXY_ERROR_GENERAL); return 1; } @@ -1045,7 +1040,7 @@ int proxy_socks5_negotiate (ProxySocket *p, int change) plug_closing(p->plug, "Proxy error: SOCKS proxy refused" " password authentication", - PROXY_ERROR_GENERAL, 0); + PROXY_ERROR_GENERAL); return 1; } @@ -1148,7 +1143,7 @@ int proxy_socks5_negotiate (ProxySocket *p, int change) if (data[0] != 5) { plug_closing(p->plug, "Proxy error: SOCKS proxy returned wrong version number", - PROXY_ERROR_GENERAL, 0); + PROXY_ERROR_GENERAL); return 1; } @@ -1171,7 +1166,7 @@ int proxy_socks5_negotiate (ProxySocket *p, int change) data[1]); break; } - plug_closing(p->plug, buf, PROXY_ERROR_GENERAL, 0); + plug_closing(p->plug, buf, PROXY_ERROR_GENERAL); return 1; } @@ -1187,7 +1182,7 @@ int proxy_socks5_negotiate (ProxySocket *p, int change) default: plug_closing(p->plug, "Proxy error: SOCKS proxy returned " "unrecognised address format", - PROXY_ERROR_GENERAL, 0); + PROXY_ERROR_GENERAL); return 1; } if (bufchain_size(&p->pending_input_data) < len) @@ -1202,7 +1197,7 @@ int proxy_socks5_negotiate (ProxySocket *p, int change) if (p->state == 4) { /* TODO: Handle GSSAPI authentication */ plug_closing(p->plug, "Proxy error: We don't support GSSAPI authentication", - PROXY_ERROR_GENERAL, 0); + PROXY_ERROR_GENERAL); return 1; } @@ -1231,7 +1226,7 @@ int proxy_socks5_negotiate (ProxySocket *p, int change) plug_closing(p->plug, "Proxy error: Server chose " "username/password authentication but we " "didn't offer it!", - PROXY_ERROR_GENERAL, 0); + PROXY_ERROR_GENERAL); return 1; } @@ -1244,7 +1239,7 @@ int proxy_socks5_negotiate (ProxySocket *p, int change) } plug_closing(p->plug, "Proxy error: Unexpected proxy error", - PROXY_ERROR_UNEXPECTED, 0); + PROXY_ERROR_UNEXPECTED); return 1; } @@ -1479,8 +1474,7 @@ int proxy_telnet_negotiate (ProxySocket *p, int change) * a socket close, then some error must have occurred. we'll * just pass those errors up to the backend. */ - plug_closing(p->plug, p->closing_error_msg, p->closing_error_code, - p->closing_calling_back); + plug_closing(p->plug, p->closing_error_msg, p->closing_error_code); return 0; /* ignored */ } @@ -1516,6 +1510,6 @@ int proxy_telnet_negotiate (ProxySocket *p, int change) } plug_closing(p->plug, "Proxy error: Unexpected proxy error", - PROXY_ERROR_UNEXPECTED, 0); + PROXY_ERROR_UNEXPECTED); return 1; } diff --git a/proxy.h b/proxy.h index f11e1e3d..9dca87d1 100644 --- a/proxy.h +++ b/proxy.h @@ -63,7 +63,6 @@ struct ProxySocket { /* closing */ const char *closing_error_msg; int closing_error_code; - bool closing_calling_back; /* receive */ bool receive_urgent; diff --git a/psocks.c b/psocks.c index 75dc85d9..50bdf714 100644 --- a/psocks.c +++ b/psocks.c @@ -95,7 +95,7 @@ static const SshChannelVtable psocks_scvt = { static void psocks_plug_log(Plug *p, PlugLogType type, SockAddr *addr, int port, const char *error_msg, int error_code); static void psocks_plug_closing(Plug *p, const char *error_msg, - int error_code, bool calling_back); + int error_code); static void psocks_plug_receive(Plug *p, int urgent, const char *data, size_t len); static void psocks_plug_sent(Plug *p, size_t bufsize); @@ -355,7 +355,7 @@ static void psocks_plug_log(Plug *plug, PlugLogType type, SockAddr *addr, } static void psocks_plug_closing(Plug *plug, const char *error_msg, - int error_code, bool calling_back) + int error_code) { psocks_connection *conn = container_of(plug, psocks_connection, plug); if (conn->connecting) { diff --git a/ssh/portfwd.c b/ssh/portfwd.c index 268935eb..9dd35ea4 100644 --- a/ssh/portfwd.c +++ b/ssh/portfwd.c @@ -109,8 +109,7 @@ static void pfl_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, static void pfd_close(struct PortForwarding *pf); -static void pfd_closing(Plug *plug, const char *error_msg, int error_code, - bool calling_back) +static void pfd_closing(Plug *plug, const char *error_msg, int error_code) { struct PortForwarding *pf = container_of(plug, struct PortForwarding, plug); @@ -142,8 +141,7 @@ static void pfd_closing(Plug *plug, const char *error_msg, int error_code, static void pfl_terminate(struct PortListener *pl); -static void pfl_closing(Plug *plug, const char *error_msg, int error_code, - bool calling_back) +static void pfl_closing(Plug *plug, const char *error_msg, int error_code) { struct PortListener *pl = (struct PortListener *) plug; pfl_terminate(pl); diff --git a/ssh/server.c b/ssh/server.c index 4a3676ec..404ba77d 100644 --- a/ssh/server.c +++ b/ssh/server.c @@ -139,8 +139,7 @@ static void server_socket_log(Plug *plug, PlugLogType type, SockAddr *addr, /* FIXME */ } -static void server_closing(Plug *plug, const char *error_msg, int error_code, - bool calling_back) +static void server_closing(Plug *plug, const char *error_msg, int error_code) { server *srv = container_of(plug, server, plug); if (error_msg) { diff --git a/ssh/sesschan.c b/ssh/sesschan.c index 1ef55dde..ff4a008f 100644 --- a/ssh/sesschan.c +++ b/ssh/sesschan.c @@ -366,8 +366,7 @@ bool sesschan_run_subsystem(Channel *chan, ptrlen subsys) static void fwd_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, const char *error_msg, int error_code) { /* don't expect any weirdnesses from a listening socket */ } -static void fwd_closing(Plug *plug, const char *error_msg, int error_code, - bool calling_back) +static void fwd_closing(Plug *plug, const char *error_msg, int error_code) { /* not here, either */ } static int xfwd_accepting(Plug *p, accept_fn_t constructor, accept_ctx_t ctx) diff --git a/ssh/sharing.c b/ssh/sharing.c index 920218de..92e2a786 100644 --- a/ssh/sharing.c +++ b/ssh/sharing.c @@ -937,8 +937,7 @@ static void share_disconnect(struct ssh_sharing_connstate *cs, share_begin_cleanup(cs); } -static void share_closing(Plug *plug, const char *error_msg, int error_code, - bool calling_back) +static void share_closing(Plug *plug, const char *error_msg, int error_code) { struct ssh_sharing_connstate *cs = container_of( plug, struct ssh_sharing_connstate, plug); @@ -1846,7 +1845,7 @@ static void share_sent(Plug *plug, size_t bufsize) } static void share_listen_closing(Plug *plug, const char *error_msg, - int error_code, bool calling_back) + int error_code) { ssh_sharing_state *sharestate = container_of(plug, ssh_sharing_state, plug); diff --git a/ssh/ssh.c b/ssh/ssh.c index 4e7f8c06..b2d75b09 100644 --- a/ssh/ssh.c +++ b/ssh/ssh.c @@ -609,8 +609,7 @@ static void ssh_socket_log(Plug *plug, PlugLogType type, SockAddr *addr, } } -static void ssh_closing(Plug *plug, const char *error_msg, int error_code, - bool calling_back) +static void ssh_closing(Plug *plug, const char *error_msg, int error_code) { Ssh *ssh = container_of(plug, Ssh, plug); if (error_msg) { diff --git a/ssh/x11fwd.c b/ssh/x11fwd.c index 60a9f1aa..6886dace 100644 --- a/ssh/x11fwd.c +++ b/ssh/x11fwd.c @@ -267,8 +267,7 @@ static void x11_log(Plug *p, PlugLogType type, SockAddr *addr, int port, static void x11_send_init_error(struct X11Connection *conn, const char *err_message); -static void x11_closing(Plug *plug, const char *error_msg, int error_code, - bool calling_back) +static void x11_closing(Plug *plug, const char *error_msg, int error_code) { struct X11Connection *xconn = container_of( plug, struct X11Connection, plug); diff --git a/sshproxy.c b/sshproxy.c index aea19cf6..31657645 100644 --- a/sshproxy.c +++ b/sshproxy.c @@ -244,7 +244,7 @@ static void try_send_ssh_to_socket(void *ctx) if (sp->rcvd_eof_ssh_to_socket && !sp->sent_eof_ssh_to_socket) { sp->sent_eof_ssh_to_socket = true; - plug_closing(sp->plug, sp->errmsg, 0, 0); + plug_closing(sp->plug, sp->errmsg, 0); } } @@ -325,7 +325,7 @@ static int sshproxy_get_userpass_input(Seat *seat, prompts_t *p) static void sshproxy_connection_fatal_callback(void *vctx) { SshProxy *sp = (SshProxy *)vctx; - plug_closing(sp->plug, sp->errmsg, 0, true); + plug_closing(sp->plug, sp->errmsg, 0); } static void sshproxy_connection_fatal(Seat *seat, const char *message) diff --git a/unix/fd-socket.c b/unix/fd-socket.c index d352d8e4..48d7dc8b 100644 --- a/unix/fd-socket.c +++ b/unix/fd-socket.c @@ -158,8 +158,7 @@ static void fdsocket_error_callback(void *vs) /* * An error has occurred on this socket. Pass it to the plug. */ - plug_closing(fds->plug, strerror(fds->pending_error), - fds->pending_error, 0); + plug_closing(fds->plug, strerror(fds->pending_error), fds->pending_error); } static int fdsocket_try_send(FdSocket *fds) @@ -271,9 +270,9 @@ static void fdsocket_select_result_input(int fd, int event) fds->infd = -1; if (retd < 0) { - plug_closing(fds->plug, strerror(errno), errno, 0); + plug_closing(fds->plug, strerror(errno), errno); } else { - plug_closing(fds->plug, NULL, 0, 0); + plug_closing(fds->plug, NULL, 0); } } } diff --git a/unix/network.c b/unix/network.c index bd6ebb1d..d2d426ed 100644 --- a/unix/network.c +++ b/unix/network.c @@ -1101,7 +1101,7 @@ static void socket_error_callback(void *vs) /* * An error has occurred on this socket. Pass it to the plug. */ - plug_closing(s->plug, strerror(s->pending_error), s->pending_error, 0); + plug_closing(s->plug, strerror(s->pending_error), s->pending_error); } /* @@ -1298,7 +1298,7 @@ static void net_select_result(int fd, int event) if (ret <= 0) { plug_closing(s->plug, ret == 0 ? "Internal networking trouble" : - strerror(errno), errno, 0); + strerror(errno), errno); } else { /* * Receiving actual data on a socket means we can @@ -1384,11 +1384,11 @@ static void net_select_result(int fd, int event) } } if (ret < 0) { - plug_closing(s->plug, strerror(errno), errno, 0); + plug_closing(s->plug, strerror(errno), errno); } else if (0 == ret) { s->incomingeof = true; /* stop trying to read now */ uxsel_tell(s); - plug_closing(s->plug, NULL, 0, 0); + plug_closing(s->plug, NULL, 0); } else { /* * Receiving actual data on a socket means we can @@ -1438,7 +1438,7 @@ static void net_select_result(int fd, int event) err = try_connect(s); } if (err) { - plug_closing(s->plug, strerror(err), err, 0); + plug_closing(s->plug, strerror(err), err); return; /* socket is now presumably defunct */ } if (!s->connected) diff --git a/unix/pageant.c b/unix/pageant.c index 7402ac60..a0b2e9e2 100644 --- a/unix/pageant.c +++ b/unix/pageant.c @@ -249,8 +249,7 @@ static void x11_log(Plug *p, PlugLogType type, SockAddr *addr, int port, const char *error_msg, int error_code) {} static void x11_receive(Plug *plug, int urgent, const char *data, size_t len) {} static void x11_sent(Plug *plug, size_t bufsize) {} -static void x11_closing(Plug *plug, const char *error_msg, int error_code, - bool calling_back) +static void x11_closing(Plug *plug, const char *error_msg, int error_code) { time_to_die = true; } diff --git a/unix/psusan.c b/unix/psusan.c index 2017685b..8fcef33a 100644 --- a/unix/psusan.c +++ b/unix/psusan.c @@ -273,8 +273,7 @@ static void server_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, log_to_stderr(-1, error_msg); } -static void server_closing(Plug *plug, const char *error_msg, int error_code, - bool calling_back) +static void server_closing(Plug *plug, const char *error_msg, int error_code) { log_to_stderr(-1, error_msg); } diff --git a/unix/uppity.c b/unix/uppity.c index 5d0a1b1c..0701ddab 100644 --- a/unix/uppity.c +++ b/unix/uppity.c @@ -482,8 +482,7 @@ static void server_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, log_to_stderr((unsigned)-1, error_msg); } -static void server_closing(Plug *plug, const char *error_msg, int error_code, - bool calling_back) +static void server_closing(Plug *plug, const char *error_msg, int error_code) { log_to_stderr((unsigned)-1, error_msg); } diff --git a/windows/handle-socket.c b/windows/handle-socket.c index a024807e..066d2373 100644 --- a/windows/handle-socket.c +++ b/windows/handle-socket.c @@ -53,10 +53,10 @@ static size_t handle_gotdata( HandleSocket *hs = (HandleSocket *)handle_get_privdata(h); if (err) { - plug_closing(hs->plug, "Read error from handle", 0, 0); + plug_closing(hs->plug, "Read error from handle", 0); return 0; } else if (len == 0) { - plug_closing(hs->plug, NULL, 0, 0); + plug_closing(hs->plug, NULL, 0); return 0; } else { assert(hs->frozen != FROZEN && hs->frozen != THAWING); @@ -107,7 +107,7 @@ static void handle_sentdata(struct handle *h, size_t new_backlog, int err, } if (err) { - plug_closing(hs->plug, win_strerror(err), err, 0); + plug_closing(hs->plug, win_strerror(err), err); return; } diff --git a/windows/network.c b/windows/network.c index 3b4da3cc..17c80cb4 100644 --- a/windows/network.c +++ b/windows/network.c @@ -1353,7 +1353,7 @@ static void socket_error_callback(void *vs) * 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); + s->pending_error); } /* @@ -1528,7 +1528,7 @@ void select_result(WPARAM wParam, LPARAM lParam) } } if (err != 0) - plug_closing(s->plug, winsock_error_string(err), err, 0); + plug_closing(s->plug, winsock_error_string(err), err); return; } @@ -1590,9 +1590,9 @@ void select_result(WPARAM wParam, LPARAM lParam) } } if (ret < 0) { - plug_closing(s->plug, winsock_error_string(err), err, 0); + plug_closing(s->plug, winsock_error_string(err), err); } else if (0 == ret) { - plug_closing(s->plug, NULL, 0, 0); + plug_closing(s->plug, NULL, 0); } else { plug_receive(s->plug, atmark ? 0 : 1, buf, ret); } @@ -1608,7 +1608,7 @@ void select_result(WPARAM wParam, LPARAM lParam) noise_ultralight(NOISE_SOURCE_IOLEN, ret); if (ret <= 0) { int err = p_WSAGetLastError(); - plug_closing(s->plug, winsock_error_string(err), err, 0); + plug_closing(s->plug, winsock_error_string(err), err); } else { plug_receive(s->plug, 2, buf, ret); } @@ -1631,12 +1631,12 @@ void select_result(WPARAM wParam, LPARAM lParam) err = p_WSAGetLastError(); if (err == WSAEWOULDBLOCK) break; - plug_closing(s->plug, winsock_error_string(err), err, 0); + plug_closing(s->plug, winsock_error_string(err), err); } else { if (ret) plug_receive(s->plug, 0, buf, ret); else - plug_closing(s->plug, NULL, 0, 0); + plug_closing(s->plug, NULL, 0); } } while (ret > 0); return; -- cgit v1.2.3 From efb658941160735e298c8b771eab021dd81612c3 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 23 Oct 2021 18:09:25 +0100 Subject: Tidy up the comments in PlugVtable. It always confused me that each comment was _after_ the function prototype it described, instead of before, which is my usual idiom. Reordered everything, and added a blank line between each (comment,function) pair to make it clear what goes with what. While I'm at it, rewrote some of the comments for clarity and whole sentences. --- network.h | 48 +++++++++++++++++++++++++++++++----------------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/network.h b/network.h index ff88923b..099895bc 100644 --- a/network.h +++ b/network.h @@ -52,8 +52,6 @@ typedef enum PlugLogType { } PlugLogType; struct PlugVtable { - void (*log)(Plug *p, PlugLogType 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. @@ -86,13 +84,26 @@ struct PlugVtable { * all Plugs must implement this method, even if only to ignore * the logged events. */ - void (*closing) - (Plug *p, const char *error_msg, int error_code); - /* 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()) */ - void (*receive) (Plug *p, int urgent, const char *data, size_t len); + void (*log)(Plug *p, PlugLogType type, SockAddr *addr, int port, + const char *error_msg, int error_code); + /* + * Notifies the Plug that the socket is closing. + * + * For a normal non-error close, error_msg is NULL. If the socket + * has encountered an error, error_msg will contain a string + * (ownership not transferred), and error_code will contain the OS + * error code, if available. + * + * OS error codes will vary between platforms, of course, but + * platform.h should define any that we need to distinguish here, + * in particular BROKEN_PIPE_ERROR_CODE. + */ + void (*closing)(Plug *p, const char *error_msg, int error_code); + + /* + * Provides incoming socket data to the Plug. Three cases: + * * - urgent==0. `data' points to `len' bytes of perfectly * ordinary data. * @@ -102,19 +113,22 @@ struct PlugVtable { * - urgent==2. `data' points to `len' bytes of data, * the first of which was the one at the Urgent mark. */ - void (*sent) (Plug *p, size_t bufsize); + void (*receive) (Plug *p, int urgent, const char *data, size_t len); + /* - * 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. + * 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, accept_fn_t constructor, accept_ctx_t ctx); + void (*sent) (Plug *p, size_t bufsize); + /* - * `accepting' is called only on listener-type sockets, and is - * passed a constructor function+context that will create a fresh - * Socket describing the connection. It returns nonzero if it - * doesn't want the connection for some reason, or 0 on success. + * Only called on listener-type sockets, and is passed a + * constructor function+context that will create a fresh Socket + * describing the connection. It returns nonzero if it doesn't + * want the connection for some reason, or 0 on success. */ + int (*accepting)(Plug *p, accept_fn_t constructor, accept_ctx_t ctx); }; /* Proxy indirection layer. -- cgit v1.2.3 From 5374444879057ad6f013e57716e49dfd3a10ed6e Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 23 Oct 2021 18:26:34 +0100 Subject: Lowercase version of BackendVtable's displayname. The current 'displayname' field is designed for presenting in the config UI, so it starts with a capital letter even when it's not a proper noun. If I want to name the backend in the middle of a sentence, I'll need a version that starts with lowercase where appropriate. The old field is renamed displayname_tc, to avoid ambiguity. --- config.c | 4 ++-- otherbackends/raw.c | 3 ++- otherbackends/rlogin.c | 3 ++- otherbackends/supdup.c | 3 ++- otherbackends/telnet.c | 3 ++- otherbackends/testback.c | 6 ++++-- putty.h | 7 ++++--- ssh/ssh.c | 6 ++++-- unix/plink.c | 4 ++-- unix/pty.c | 3 ++- unix/serial.c | 3 ++- windows/conpty.c | 3 ++- windows/plink.c | 4 ++-- windows/serial.c | 3 ++- 14 files changed, 34 insertions(+), 21 deletions(-) diff --git a/config.c b/config.c index e7597831..8114a586 100644 --- a/config.c +++ b/config.c @@ -312,7 +312,7 @@ static void config_protocols_handler(union control *ctrl, dlgparam *dlg, for (size_t i = n_ui_backends; i < PROTOCOL_LIMIT && backends[i]; i++) { dlg_listbox_addwithid(ctrl, dlg, - backends[i]->displayname, + backends[i]->displayname_tc, backends[i]->protocol); if (backends[i]->protocol == curproto) curentry = i - n_ui_backends; @@ -1793,7 +1793,7 @@ void setup_config_box(struct controlbox *b, bool midsession, for (size_t i = 0; i < n_ui_backends; i++) { assert(backends[i]); c->radio.buttons[c->radio.nbuttons] = - dupstr(backends[i]->displayname); + dupstr(backends[i]->displayname_tc); c->radio.shortcuts[c->radio.nbuttons] = (backends[i]->protocol == PROT_SSH ? 's' : backends[i]->protocol == PROT_SERIAL ? 'r' : diff --git a/otherbackends/raw.c b/otherbackends/raw.c index 491a3df9..d6693244 100644 --- a/otherbackends/raw.c +++ b/otherbackends/raw.c @@ -338,7 +338,8 @@ const BackendVtable raw_backend = { .unthrottle = raw_unthrottle, .cfg_info = raw_cfg_info, .id = "raw", - .displayname = "Raw", + .displayname_tc = "Raw", + .displayname_lc = "raw", .protocol = PROT_RAW, .default_port = 0, }; diff --git a/otherbackends/rlogin.c b/otherbackends/rlogin.c index 9951ce18..44a3f625 100644 --- a/otherbackends/rlogin.c +++ b/otherbackends/rlogin.c @@ -445,7 +445,8 @@ const BackendVtable rlogin_backend = { .unthrottle = rlogin_unthrottle, .cfg_info = rlogin_cfg_info, .id = "rlogin", - .displayname = "Rlogin", + .displayname_tc = "Rlogin", + .displayname_lc = "Rlogin", /* proper name, so capitalise it anyway */ .protocol = PROT_RLOGIN, .default_port = 513, }; diff --git a/otherbackends/supdup.c b/otherbackends/supdup.c index f015e054..2ec8e0f9 100644 --- a/otherbackends/supdup.c +++ b/otherbackends/supdup.c @@ -936,7 +936,8 @@ const BackendVtable supdup_backend = { .unthrottle = supdup_unthrottle, .cfg_info = supdup_cfg_info, .id = "supdup", - .displayname = "SUPDUP", + .displayname_tc = "SUPDUP", + .displayname_lc = "SUPDUP", /* proper name, so capitalise it anyway */ .protocol = PROT_SUPDUP, .default_port = 0137, .flags = BACKEND_RESIZE_FORBIDDEN | BACKEND_NEEDS_TERMINAL, diff --git a/otherbackends/telnet.c b/otherbackends/telnet.c index 281c39ac..048bb2d6 100644 --- a/otherbackends/telnet.c +++ b/otherbackends/telnet.c @@ -1082,7 +1082,8 @@ const BackendVtable telnet_backend = { .unthrottle = telnet_unthrottle, .cfg_info = telnet_cfg_info, .id = "telnet", - .displayname = "Telnet", + .displayname_tc = "Telnet", + .displayname_lc = "Telnet", /* proper name, so capitalise it anyway */ .protocol = PROT_TELNET, .default_port = 23, }; diff --git a/otherbackends/testback.c b/otherbackends/testback.c index 5f319722..dbe282ec 100644 --- a/otherbackends/testback.c +++ b/otherbackends/testback.c @@ -71,7 +71,8 @@ const BackendVtable null_backend = { .unthrottle = null_unthrottle, .cfg_info = null_cfg_info, .id = "null", - .displayname = "null", + .displayname_tc = "Null", + .displayname_lc = "null", .protocol = -1, .default_port = 0, }; @@ -93,7 +94,8 @@ const BackendVtable loop_backend = { .unthrottle = null_unthrottle, .cfg_info = null_cfg_info, .id = "loop", - .displayname = "loop", + .displayname_tc = "Loop", + .displayname_lc = "loop", .protocol = -1, .default_port = 0, }; diff --git a/putty.h b/putty.h index ff679752..a2136a05 100644 --- a/putty.h +++ b/putty.h @@ -681,9 +681,10 @@ struct BackendVtable { char *(*close_warn_text)(Backend *be); /* 'id' is a machine-readable name for the backend, used in - * saved-session storage. 'displayname' is a human-readable name - * for error messages. */ - const char *id, *displayname; + * saved-session storage. 'displayname_tc' and 'displayname_lc' + * are human-readable names, one in title-case for config boxes, + * and one in lower-case for use in mid-sentence. */ + const char *id, *displayname_tc, *displayname_lc; int protocol; int default_port; diff --git a/ssh/ssh.c b/ssh/ssh.c index b2d75b09..103f5795 100644 --- a/ssh/ssh.c +++ b/ssh/ssh.c @@ -1248,7 +1248,8 @@ const BackendVtable ssh_backend = { .test_for_upstream = ssh_test_for_upstream, .close_warn_text = ssh_close_warn_text, .id = "ssh", - .displayname = "SSH", + .displayname_tc = "SSH", + .displayname_lc = "SSH", /* proper name, so capitalise it anyway */ .protocol = PROT_SSH, .flags = BACKEND_SUPPORTS_NC_HOST | BACKEND_NOTIFIES_SESSION_START, .default_port = 22, @@ -1273,7 +1274,8 @@ const BackendVtable sshconn_backend = { .test_for_upstream = ssh_test_for_upstream, .close_warn_text = ssh_close_warn_text, .id = "ssh-connection", - .displayname = "Bare ssh-connection", + .displayname_tc = "Bare ssh-connection", + .displayname_lc = "bare ssh-connection", .protocol = PROT_SSHCONN, .flags = BACKEND_SUPPORTS_NC_HOST | BACKEND_NOTIFIES_SESSION_START, }; diff --git a/unix/plink.c b/unix/plink.c index 69667a3d..643fc563 100644 --- a/unix/plink.c +++ b/unix/plink.c @@ -835,7 +835,7 @@ int main(int argc, char **argv) if (backvt->flags & BACKEND_NEEDS_TERMINAL) { fprintf(stderr, "Plink doesn't support %s, which needs terminal emulation\n", - backvt->displayname); + backvt->displayname_lc); return 1; } @@ -912,7 +912,7 @@ int main(int argc, char **argv) if (just_test_share_exists) { if (!backvt->test_for_upstream) { fprintf(stderr, "Connection sharing not supported for this " - "connection type (%s)'\n", backvt->displayname); + "connection type (%s)'\n", backvt->displayname_lc); return 1; } if (backvt->test_for_upstream(conf_get_str(conf, CONF_host), diff --git a/unix/pty.c b/unix/pty.c index e74ebd4b..46382c06 100644 --- a/unix/pty.c +++ b/unix/pty.c @@ -1597,6 +1597,7 @@ const BackendVtable pty_backend = { .unthrottle = pty_unthrottle, .cfg_info = pty_cfg_info, .id = "pty", - .displayname = "pty", + .displayname_tc = "pty", + .displayname_lc = "pty", .protocol = -1, }; diff --git a/unix/serial.c b/unix/serial.c index 905c1b2d..6c8f9b9e 100644 --- a/unix/serial.c +++ b/unix/serial.c @@ -583,7 +583,8 @@ const BackendVtable serial_backend = { .unthrottle = serial_unthrottle, .cfg_info = serial_cfg_info, .id = "serial", - .displayname = "Serial", + .displayname_tc = "Serial", + .displayname_lc = "serial", .protocol = PROT_SERIAL, .serial_parity_mask = ((1 << SER_PAR_NONE) | (1 << SER_PAR_ODD) | diff --git a/windows/conpty.c b/windows/conpty.c index 322af7bf..83cb9635 100644 --- a/windows/conpty.c +++ b/windows/conpty.c @@ -385,6 +385,7 @@ const BackendVtable conpty_backend = { .unthrottle = conpty_unthrottle, .cfg_info = conpty_cfg_info, .id = "conpty", - .displayname = "ConPTY", + .displayname_tc = "ConPTY", + .displayname_lc = "ConPTY", /* proper name, so capitalise it anyway */ .protocol = -1, }; diff --git a/windows/plink.c b/windows/plink.c index 503ae30b..b49266d3 100644 --- a/windows/plink.c +++ b/windows/plink.c @@ -415,7 +415,7 @@ int main(int argc, char **argv) if (vt->flags & BACKEND_NEEDS_TERMINAL) { fprintf(stderr, "Plink doesn't support %s, which needs terminal emulation\n", - vt->displayname); + vt->displayname_lc); return 1; } @@ -441,7 +441,7 @@ int main(int argc, char **argv) if (just_test_share_exists) { if (!vt->test_for_upstream) { fprintf(stderr, "Connection sharing not supported for this " - "connection type (%s)'\n", vt->displayname); + "connection type (%s)'\n", vt->displayname_lc); return 1; } if (vt->test_for_upstream(conf_get_str(conf, CONF_host), diff --git a/windows/serial.c b/windows/serial.c index d966145c..82146a40 100644 --- a/windows/serial.c +++ b/windows/serial.c @@ -452,7 +452,8 @@ const BackendVtable serial_backend = { .unthrottle = serial_unthrottle, .cfg_info = serial_cfg_info, .id = "serial", - .displayname = "Serial", + .displayname_tc = "Serial", + .displayname_lc = "serial", .protocol = PROT_SERIAL, .serial_parity_mask = ((1 << SER_PAR_NONE) | (1 << SER_PAR_ODD) | -- cgit v1.2.3 From f1746d69b172f8ab196ed52ec1941f374130eb57 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 24 Oct 2021 09:18:12 +0100 Subject: Add 'description' methods for Backend and Plug. These will typically be implemented by objects that are both a Backend *and* a Plug, and the two methods will deliver the same results to any caller, regardless of which facet of the object is known to that caller. Their purpose is to deliver a user-oriented natural-language description of what network connection the object is handling, so that it can appear in diagnostic messages. The messages I specifically have in mind are going to appear in cases where proxies require interactive authentication: when PuTTY prompts interactively for a password, it will need to explain which *thing* it's asking for the password for, and these descriptions are what it will use to describe the thing in question. Each backend is allowed to compose these messages however it thinks best. In all cases at present, the description string is constructed by the new centralised default_description() function, which takes a host name and port number and combines them with the backend's display name. But the SSH backend does things a bit differently, because it uses the _logical_ host name (the one that goes with the SSH host key) rather than the physical destination of the network connection. That seems more appropriate when the question it's really helping the user to answer is "What host am I supposed to be entering the password for?" In this commit, no clients of the new methods are introduced. I have a draft implementation of actually using it for the purpose I describe above, but it needs polishing. --- network.h | 22 ++++++++++++++++++++++ otherbackends/raw.c | 17 +++++++++++++++++ otherbackends/rlogin.c | 17 +++++++++++++++++ otherbackends/supdup.c | 17 +++++++++++++++++ otherbackends/telnet.c | 17 +++++++++++++++++ putty.h | 27 +++++++++++++++++++++++++++ ssh/ssh.c | 31 +++++++++++++++++++++++++------ utils/CMakeLists.txt | 1 + utils/default_description.c | 22 ++++++++++++++++++++++ 9 files changed, 165 insertions(+), 6 deletions(-) create mode 100644 utils/default_description.c diff --git a/network.h b/network.h index 099895bc..0b600f63 100644 --- a/network.h +++ b/network.h @@ -129,6 +129,26 @@ struct PlugVtable { * want the connection for some reason, or 0 on success. */ int (*accepting)(Plug *p, accept_fn_t constructor, accept_ctx_t ctx); + + /* + * Returns a user-facing description of the nature of the network + * connection being made. Used in interactive proxy authentication + * to announce which connection attempt is now in control of the + * Seat. + * + * The idea is not just to be written in natural language, but to + * connect with the user's idea of _why_ they think some + * connection is being made. For example, instead of saying 'TCP + * connection to 123.45.67.89 port 22', you might say 'SSH + * connection to [logical host name for SSH host key purposes]'. + * + * This function pointer may be NULL, or may exist but return + * NULL, in which case no user-facing description is available. + * + * If a non-NULL string is returned, it must be freed by the + * caller. + */ + char *(*description)(Plug *p); }; /* Proxy indirection layer. @@ -236,6 +256,8 @@ static inline void plug_sent (Plug *p, size_t bufsize) { p->vt->sent(p, bufsize); } static inline int plug_accepting(Plug *p, accept_fn_t cons, accept_ctx_t ctx) { return p->vt->accepting(p, cons, ctx); } +static inline char *plug_description(Plug *p) +{ return p->vt->description ? p->vt->description(p) : NULL; } /* * Special error values are returned from sk_namelookup and sk_new diff --git a/otherbackends/raw.c b/otherbackends/raw.c index d6693244..57130dc6 100644 --- a/otherbackends/raw.c +++ b/otherbackends/raw.c @@ -19,6 +19,7 @@ struct Raw { LogContext *logctx; Ldisc *ldisc; bool sent_console_eof, sent_socket_eof, socket_connected; + char *description; Conf *conf; @@ -115,11 +116,24 @@ static void raw_sent(Plug *plug, size_t bufsize) seat_sent(raw->seat, raw->bufsize); } +static char *raw_plug_description(Plug *plug) +{ + Raw *raw = container_of(plug, Raw, plug); + return dupstr(raw->description); +} + +static char *raw_backend_description(Backend *backend) +{ + Raw *raw = container_of(backend, Raw, backend); + return dupstr(raw->description); +} + static const PlugVtable Raw_plugvt = { .log = raw_log, .closing = raw_closing, .receive = raw_receive, .sent = raw_sent, + .description = raw_plug_description, }; /* @@ -151,6 +165,7 @@ static char *raw_init(const BackendVtable *vt, Seat *seat, raw->bufsize = 0; raw->socket_connected = false; raw->conf = conf_copy(conf); + raw->description = default_description(vt, host, port); raw->seat = seat; raw->logctx = logctx; @@ -205,6 +220,7 @@ static void raw_free(Backend *be) if (raw->s) sk_close(raw->s); conf_free(raw->conf); + sfree(raw->description); sfree(raw); } @@ -337,6 +353,7 @@ const BackendVtable raw_backend = { .provide_ldisc = raw_provide_ldisc, .unthrottle = raw_unthrottle, .cfg_info = raw_cfg_info, + .description = raw_backend_description, .id = "raw", .displayname_tc = "Raw", .displayname_lc = "raw", diff --git a/otherbackends/rlogin.c b/otherbackends/rlogin.c index 44a3f625..4e3abffe 100644 --- a/otherbackends/rlogin.c +++ b/otherbackends/rlogin.c @@ -23,6 +23,7 @@ struct Rlogin { Seat *seat; LogContext *logctx; Ldisc *ldisc; + char *description; Conf *conf; @@ -192,11 +193,24 @@ static void rlogin_startup(Rlogin *rlogin, int prompt_result, ldisc_check_sendok(rlogin->ldisc); } +static char *rlogin_plug_description(Plug *plug) +{ + Rlogin *rlogin = container_of(plug, Rlogin, plug); + return dupstr(rlogin->description); +} + +static char *rlogin_backend_description(Backend *backend) +{ + Rlogin *rlogin = container_of(backend, Rlogin, backend); + return dupstr(rlogin->description); +} + static const PlugVtable Rlogin_plugvt = { .log = rlogin_log, .closing = rlogin_closing, .receive = rlogin_receive, .sent = rlogin_sent, + .description = rlogin_plug_description, }; /* @@ -232,6 +246,7 @@ static char *rlogin_init(const BackendVtable *vt, Seat *seat, rlogin->cansize = false; rlogin->prompt = NULL; rlogin->conf = conf_copy(conf); + rlogin->description = default_description(vt, host, port); *backend_handle = &rlogin->backend; addressfamily = conf_get_int(conf, CONF_addressfamily); @@ -283,6 +298,7 @@ static void rlogin_free(Backend *be) if (rlogin->s) sk_close(rlogin->s); conf_free(rlogin->conf); + sfree(rlogin->description); sfree(rlogin); } @@ -444,6 +460,7 @@ const BackendVtable rlogin_backend = { .provide_ldisc = rlogin_provide_ldisc, .unthrottle = rlogin_unthrottle, .cfg_info = rlogin_cfg_info, + .description = rlogin_backend_description, .id = "rlogin", .displayname_tc = "Rlogin", .displayname_lc = "Rlogin", /* proper name, so capitalise it anyway */ diff --git a/otherbackends/supdup.c b/otherbackends/supdup.c index 2ec8e0f9..278e689e 100644 --- a/otherbackends/supdup.c +++ b/otherbackends/supdup.c @@ -73,6 +73,7 @@ struct supdup_tag LogContext *logctx; Ldisc *ldisc; int term_width, term_height; + char *description; long long ttyopt; long tcmxv; @@ -641,6 +642,18 @@ static void supdup_send_config(Supdup *supdup) supdup_send_36bits(supdup, TTYROL); // scroll amount } +static char *supdup_plug_description(Plug *plug) +{ + Supdup *supdup = container_of(plug, Supdup, plug); + return dupstr(supdup->description); +} + +static char *supdup_backend_description(Backend *backend) +{ + Supdup *supdup = container_of(backend, Supdup, backend); + return dupstr(supdup->description); +} + /* * Called to set up the Supdup connection. * @@ -660,6 +673,7 @@ static char *supdup_init(const BackendVtable *x, Seat *seat, .closing = supdup_closing, .receive = supdup_receive, .sent = supdup_sent, + .description = supdup_plug_description, }; SockAddr *addr; const char *err; @@ -681,6 +695,7 @@ static char *supdup_init(const BackendVtable *x, Seat *seat, supdup->term_height = conf_get_int(supdup->conf, CONF_height); supdup->pinger = NULL; supdup->sent_location = false; + supdup->description = default_description(supdup->backend.vt, host, port); *backend_handle = &supdup->backend; switch (conf_get_int(supdup->conf, CONF_supdup_ascii_set)) { @@ -799,6 +814,7 @@ static void supdup_free(Backend *be) if (supdup->pinger) pinger_free(supdup->pinger); conf_free(supdup->conf); + sfree(supdup->description); sfree(supdup); } @@ -935,6 +951,7 @@ const BackendVtable supdup_backend = { .provide_ldisc = supdup_provide_ldisc, .unthrottle = supdup_unthrottle, .cfg_info = supdup_cfg_info, + .description = supdup_backend_description, .id = "supdup", .displayname_tc = "SUPDUP", .displayname_lc = "SUPDUP", /* proper name, so capitalise it anyway */ diff --git a/otherbackends/telnet.c b/otherbackends/telnet.c index 048bb2d6..0011cf0d 100644 --- a/otherbackends/telnet.c +++ b/otherbackends/telnet.c @@ -178,6 +178,7 @@ struct Telnet { LogContext *logctx; Ldisc *ldisc; int term_width, term_height; + char *description; int opt_states[NUM_OPTS]; @@ -680,11 +681,24 @@ static void telnet_sent(Plug *plug, size_t bufsize) seat_sent(telnet->seat, telnet->bufsize); } +static char *telnet_plug_description(Plug *plug) +{ + Telnet *telnet = container_of(plug, Telnet, plug); + return dupstr(telnet->description); +} + +static char *telnet_backend_description(Backend *backend) +{ + Telnet *telnet = container_of(backend, Telnet, backend); + return dupstr(telnet->description); +} + static const PlugVtable Telnet_plugvt = { .log = telnet_log, .closing = telnet_closing, .receive = telnet_receive, .sent = telnet_sent, + .description = telnet_plug_description, }; /* @@ -724,6 +738,7 @@ static char *telnet_init(const BackendVtable *vt, Seat *seat, telnet->state = TOP_LEVEL; telnet->ldisc = NULL; telnet->pinger = NULL; + telnet->description = default_description(vt, host, port); *backend_handle = &telnet->backend; /* @@ -813,6 +828,7 @@ static void telnet_free(Backend *be) if (telnet->pinger) pinger_free(telnet->pinger); conf_free(telnet->conf); + sfree(telnet->description); sfree(telnet); } /* @@ -1081,6 +1097,7 @@ const BackendVtable telnet_backend = { .provide_ldisc = telnet_provide_ldisc, .unthrottle = telnet_unthrottle, .cfg_info = telnet_cfg_info, + .description = telnet_backend_description, .id = "telnet", .displayname_tc = "Telnet", .displayname_lc = "Telnet", /* proper name, so capitalise it anyway */ diff --git a/putty.h b/putty.h index a2136a05..82ee83a8 100644 --- a/putty.h +++ b/putty.h @@ -680,6 +680,28 @@ struct BackendVtable { * connections that would be lost if this one were terminated. */ char *(*close_warn_text)(Backend *be); + /* + * Returns a user-facing description of the nature of the network + * connection being made. Used in interactive proxy authentication + * to announce which connection attempt is now in control of the + * Seat. + * + * The idea is not just to be written in natural language, but to + * connect with the user's idea of _why_ they think some + * connection is being made. For example, instead of saying 'TCP + * connection to 123.45.67.89 port 22', you might say 'SSH + * connection to [logical host name for SSH host key purposes]'. + * + * This function pointer may be NULL, or may exist but return + * NULL, in which case no user-facing description is available. + * (Backends which are never proxied, such as pty and ConPTY, need + * not bother to fill this in.) + * + * If a non-NULL string is returned, it must be freed by the + * caller. + */ + char *(*description)(Backend *be); + /* 'id' is a machine-readable name for the backend, used in * saved-session storage. 'displayname_tc' and 'displayname_lc' * are human-readable names, one in title-case for config boxes, @@ -728,6 +750,11 @@ static inline void backend_unthrottle(Backend *be, size_t bufsize) { be->vt->unthrottle(be, bufsize); } static inline int backend_cfg_info(Backend *be) { return be->vt->cfg_info(be); } +static inline char *backend_description(Backend *be) +{ return be->vt->description ? be->vt->description(be) : NULL; } + +char *default_description(const BackendVtable *backvt, + const char *host, int port); extern const struct BackendVtable *const backends[]; /* diff --git a/ssh/ssh.c b/ssh/ssh.c index 103f5795..c9ce2b0b 100644 --- a/ssh/ssh.c +++ b/ssh/ssh.c @@ -57,6 +57,7 @@ struct Ssh { char *savedhost; int savedport; char *fullhostname; + char *description; bool fallback_cmd; int exitcode; @@ -717,11 +718,24 @@ static char *ssh_close_warn_text(Backend *be) return msg; } +static char *ssh_plug_description(Plug *plug) +{ + Ssh *ssh = container_of(plug, Ssh, plug); + return dupstr(ssh->description); +} + +static char *ssh_backend_description(Backend *backend) +{ + Ssh *ssh = container_of(backend, Ssh, backend); + return dupstr(ssh->description); +} + static const PlugVtable Ssh_plugvt = { .log = ssh_socket_log, .closing = ssh_closing, .receive = ssh_receive, .sent = ssh_sent, + .description = ssh_plug_description, }; /* @@ -731,17 +745,13 @@ static const PlugVtable Ssh_plugvt = { * freed by the caller. */ static char *connect_to_host( - Ssh *ssh, const char *host, int port, char **realhost, + Ssh *ssh, const char *host, int port, char *loghost, char **realhost, bool nodelay, bool keepalive) { SockAddr *addr; const char *err; - char *loghost; int addressfamily, sshprot; - ssh_hostport_setup(host, port, ssh->conf, - &ssh->savedhost, &ssh->savedport, &loghost); - ssh->plug.vt = &Ssh_plugvt; /* @@ -938,11 +948,17 @@ static char *ssh_init(const BackendVtable *vt, Seat *seat, ssh->cl_dummy.vt = &dummy_connlayer_vtable; ssh->cl_dummy.logctx = ssh->logctx = logctx; + char *loghost; + + ssh_hostport_setup(host, port, ssh->conf, + &ssh->savedhost, &ssh->savedport, &loghost); + ssh->description = default_description(vt, ssh->savedhost, ssh->savedport); + random_ref(); /* do this now - may be needed by sharing setup code */ ssh->need_random_unref = true; char *conn_err = connect_to_host( - ssh, host, port, realhost, nodelay, keepalive); + ssh, host, port, loghost, realhost, nodelay, keepalive); if (conn_err) { /* Call random_unref now instead of waiting until the caller * frees this useless Ssh object, in case the caller is @@ -985,6 +1001,7 @@ static void ssh_free(Backend *be) #endif sfree(ssh->deferred_abort_message); + sfree(ssh->description); delete_callbacks_for_context(ssh); /* likely to catch ic_out_raw */ @@ -1247,6 +1264,7 @@ const BackendVtable ssh_backend = { .cfg_info = ssh_cfg_info, .test_for_upstream = ssh_test_for_upstream, .close_warn_text = ssh_close_warn_text, + .description = ssh_backend_description, .id = "ssh", .displayname_tc = "SSH", .displayname_lc = "SSH", /* proper name, so capitalise it anyway */ @@ -1273,6 +1291,7 @@ const BackendVtable sshconn_backend = { .cfg_info = ssh_cfg_info, .test_for_upstream = ssh_test_for_upstream, .close_warn_text = ssh_close_warn_text, + .description = ssh_backend_description, .id = "ssh-connection", .displayname_tc = "Bare ssh-connection", .displayname_lc = "bare ssh-connection", diff --git a/utils/CMakeLists.txt b/utils/CMakeLists.txt index 6376c0c9..97557eb6 100644 --- a/utils/CMakeLists.txt +++ b/utils/CMakeLists.txt @@ -11,6 +11,7 @@ add_sources_from_current_dir(utils conf_launchable.c ctrlparse.c debug.c + default_description.c dupcat.c dupprintf.c dupstr.c diff --git a/utils/default_description.c b/utils/default_description.c new file mode 100644 index 00000000..e0695ee6 --- /dev/null +++ b/utils/default_description.c @@ -0,0 +1,22 @@ +/* + * Construct a description string for a backend to use as + * backend_description(), or a plug as plug_description(). + * + * For some backends this will be overridden: e.g. SSH prefers to + * think in terms of _logical_ host names (i.e. the one associated + * with the host key) rather than the physical details of where you're + * connecting to. But this default is good for simpler backends. + */ + +#include "putty.h" + +char *default_description(const BackendVtable *backvt, + const char *host, int port) +{ + const char *be_name = backvt->displayname_lc; + + if (backvt->default_port && port == backvt->default_port) + return dupprintf("%s connection to %s", be_name, host); + else + return dupprintf("%s connection to %s port %d", be_name, host, port); +} -- cgit v1.2.3 From efa89573ae2b737146c4e3d8348d1ecfc5433685 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 25 Oct 2021 18:12:17 +0100 Subject: Reorganise host key checking and confirmation. Previously, checking the host key against the persistent cache managed by the storage.h API was done as part of the seat_verify_ssh_host_key method, i.e. separately by each Seat. Now that check is done by verify_ssh_host_key(), which is a new function in ssh/common.c that centralises all the parts of host key checking that don't need an interactive prompt. It subsumes the previous verify_ssh_manual_host_key() that checked against the Conf, and it does the check against the storage API that each Seat was previously doing separately. If it can't confirm or definitively reject the host key by itself, _then_ it calls out to the Seat, once an interactive prompt is definitely needed. The main point of doing this is so that when SshProxy forwards a Seat call from the proxy SSH connection to the primary Seat, it won't print an announcement of which connection is involved unless it's actually going to do something interactive. (Not that we're printing those announcements _yet_ anyway, but this is a piece of groundwork that works towards doing so.) But while I'm at it, I've also taken the opportunity to clean things up a bit by renaming functions sensibly. Previously we had three very similarly named functions verify_ssh_manual_host_key(), SeatVtable's 'verify_ssh_host_key' method, and verify_host_key() in storage.h. Now the Seat method is called 'confirm' rather than 'verify' (since its job is now always to print an interactive prompt, so it looks more like the other confirm_foo methods), and the storage.h function is called check_stored_host_key(), which goes better with store_host_key and avoids having too many functions with similar names. And the 'manual' function is subsumed into the new centralised code, so there's now just *one* host key function with 'verify' in the name. Several functions are reindented in this commit. Best viewed with whitespace changes ignored. --- pscp.c | 2 +- psftp.c | 2 +- psocks.c | 4 +- putty.h | 47 ++++++++++++++-------- ssh.h | 5 ++- ssh/common.c | 116 +++++++++++++++++++++++++++++++++++------------------ ssh/kex2-client.c | 46 +++++++++------------ ssh/login1.c | 47 ++++++++-------------- ssh/server.c | 2 +- ssh/sesschan.c | 2 +- sshproxy.c | 44 +++++--------------- storage.h | 4 +- unix/console.c | 16 ++------ unix/dialog.c | 37 +++++++---------- unix/platform.h | 4 +- unix/plink.c | 2 +- unix/storage.c | 8 ++-- unix/window.c | 2 +- utils/nullseat.c | 4 +- utils/tempseat.c | 8 ++-- windows/console.c | 15 ++----- windows/dialog.c | 71 ++++++++++++++------------------ windows/platform.h | 4 +- windows/plink.c | 2 +- windows/storage.c | 8 ++-- windows/window.c | 2 +- 26 files changed, 239 insertions(+), 265 deletions(-) diff --git a/pscp.c b/pscp.c index 4e5d7cbf..0b091085 100644 --- a/pscp.c +++ b/pscp.c @@ -75,7 +75,7 @@ static const SeatVtable pscp_seat_vt = { .update_specials_menu = nullseat_update_specials_menu, .get_ttymode = nullseat_get_ttymode, .set_busy_status = nullseat_set_busy_status, - .verify_ssh_host_key = console_verify_ssh_host_key, + .confirm_ssh_host_key = console_confirm_ssh_host_key, .confirm_weak_crypto_primitive = console_confirm_weak_crypto_primitive, .confirm_weak_cached_hostkey = console_confirm_weak_cached_hostkey, .is_utf8 = nullseat_is_never_utf8, diff --git a/psftp.c b/psftp.c index 4dc60dc1..e00ebed1 100644 --- a/psftp.c +++ b/psftp.c @@ -56,7 +56,7 @@ static const SeatVtable psftp_seat_vt = { .update_specials_menu = nullseat_update_specials_menu, .get_ttymode = nullseat_get_ttymode, .set_busy_status = nullseat_set_busy_status, - .verify_ssh_host_key = console_verify_ssh_host_key, + .confirm_ssh_host_key = console_confirm_ssh_host_key, .confirm_weak_crypto_primitive = console_confirm_weak_crypto_primitive, .confirm_weak_cached_hostkey = console_confirm_weak_cached_hostkey, .is_utf8 = nullseat_is_never_utf8, diff --git a/psocks.c b/psocks.c index 50bdf714..f3c8efa0 100644 --- a/psocks.c +++ b/psocks.c @@ -523,8 +523,8 @@ void psocks_start(psocks_state *ps) * Some stubs that are needed to link against PuTTY modules. */ -int verify_host_key(const char *hostname, int port, - const char *keytype, const char *key) +int check_stored_host_key(const char *hostname, int port, + const char *keytype, const char *key) { unreachable("host keys not handled in this tool"); } diff --git a/putty.h b/putty.h index 82ee83a8..1d55f91e 100644 --- a/putty.h +++ b/putty.h @@ -1081,31 +1081,43 @@ struct SeatVtable { /* * Ask the seat whether a given SSH host key should be accepted. - * This may return immediately after checking saved configuration - * or command-line options, or it may have to present a prompt to - * the user and return asynchronously later. + * This is called after we've already checked it by any means we + * can do ourselves, such as checking against host key + * fingerprints in the Conf or the host key cache on disk: once we + * call this function, we've already decided there's nothing for + * it but to prompt the user. + * + * 'mismatch' reports the result of checking the host key cache: + * it is true if the server has presented a host key different + * from the one we expected, and false if we had no expectation in + * the first place. + * + * This call may prompt the user synchronously and not return + * until the answer is available, or it may present the prompt and + * return immediately, giving the answer later via the provided + * callback. * * Return values: * - * - +1 means `key was OK' (either already known or the user just - * approved it) `so continue with the connection' + * - +1 means `user approved the key, so continue with the + * connection' * - * - 0 means `key was not OK, abandon the connection' + * - 0 means `user rejected the key, 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)( + int (*confirm_ssh_host_key)( Seat *seat, const char *host, int port, const char *keytype, char *keystr, const char *keydisp, char **key_fingerprints, - void (*callback)(void *ctx, int result), void *ctx); + bool mismatch, void (*callback)(void *ctx, int result), void *ctx); /* * Check with the seat whether it's OK to use a cryptographic * primitive from below the 'warn below this line' threshold in * the input Conf. Return values are the same as - * verify_ssh_host_key above. + * confirm_ssh_host_key above. */ int (*confirm_weak_crypto_primitive)( Seat *seat, const char *algtype, const char *algname, @@ -1229,11 +1241,12 @@ static inline char *seat_get_ttymode(Seat *seat, const char *mode) { return seat->vt->get_ttymode(seat, mode); } static inline void seat_set_busy_status(Seat *seat, BusyStatus status) { seat->vt->set_busy_status(seat, status); } -static inline int seat_verify_ssh_host_key( +static inline int seat_confirm_ssh_host_key( Seat *seat, const char *h, int p, const char *ktyp, char *kstr, - const char *kdsp, char **fps, void (*cb)(void *ctx, int result), void *ctx) -{ return seat->vt->verify_ssh_host_key(seat, h, p, ktyp, kstr, kdsp, fps, - cb, ctx); } + const char *kdsp, char **fps, bool mis, + void (*cb)(void *ctx, int result), void *ctx) +{ return seat->vt->confirm_ssh_host_key(seat, h, p, ktyp, kstr, kdsp, fps, + mis, cb, ctx); } static inline int seat_confirm_weak_crypto_primitive( Seat *seat, const char *atyp, const char *aname, void (*cb)(void *ctx, int result), void *ctx) @@ -1308,9 +1321,9 @@ void nullseat_connection_fatal(Seat *seat, const char *message); void nullseat_update_specials_menu(Seat *seat); char *nullseat_get_ttymode(Seat *seat, const char *mode); void nullseat_set_busy_status(Seat *seat, BusyStatus status); -int nullseat_verify_ssh_host_key( +int nullseat_confirm_ssh_host_key( Seat *seat, const char *host, int port, const char *keytype, - char *keystr, const char *keydisp, char **key_fingerprints, + char *keystr, const char *keydisp, char **key_fingerprints, bool mismatch, void (*callback)(void *ctx, int result), void *ctx); int nullseat_confirm_weak_crypto_primitive( Seat *seat, const char *algtype, const char *algname, @@ -1341,9 +1354,9 @@ bool nullseat_get_cursor_position(Seat *seat, int *x, int *y); */ void console_connection_fatal(Seat *seat, const char *message); -int console_verify_ssh_host_key( +int console_confirm_ssh_host_key( Seat *seat, const char *host, int port, const char *keytype, - char *keystr, const char *keydisp, char **key_fingerprints, + char *keystr, const char *keydisp, char **key_fingerprints, bool mismatch, void (*callback)(void *ctx, int result), void *ctx); int console_confirm_weak_crypto_primitive( Seat *seat, const char *algtype, const char *algname, diff --git a/ssh.h b/ssh.h index d4729d5f..e633da20 100644 --- a/ssh.h +++ b/ssh.h @@ -1707,7 +1707,10 @@ unsigned alloc_channel_id_general(tree234 *channels, size_t localid_offset); void add_to_commasep(strbuf *buf, const char *data); bool get_commasep_word(ptrlen *list, ptrlen *word); -int verify_ssh_manual_host_key(Conf *conf, char **fingerprints, ssh_key *key); +int verify_ssh_host_key( + Seat *seat, Conf *conf, const char *host, int port, ssh_key *key, + const char *keytype, char *keystr, const char *keydisp, + char **fingerprints, void (*callback)(void *ctx, int result), void *ctx); typedef struct ssh_transient_hostkey_cache ssh_transient_hostkey_cache; ssh_transient_hostkey_cache *ssh_transient_hostkey_cache_new(void); diff --git a/ssh/common.c b/ssh/common.c index c67a2dfb..ee09910a 100644 --- a/ssh/common.c +++ b/ssh/common.c @@ -9,6 +9,7 @@ #include "putty.h" #include "mpint.h" #include "ssh.h" +#include "storage.h" #include "bpp.h" #include "ppl.h" #include "channel.h" @@ -837,58 +838,93 @@ bool ssh2_bpp_check_unimplemented(BinaryPacketProtocol *bpp, PktIn *pktin) #undef SSH1_BITMAP_WORD /* ---------------------------------------------------------------------- - * Function to check a host key against any manually configured in Conf. + * Centralised component of SSH host key verification. + * + * verify_ssh_host_key is called from both the SSH-1 and SSH-2 + * transport layers, and does the initial work of checking whether the + * host key is already known. If so, it returns success on its own + * account; otherwise, it calls out to the Seat to give an interactive + * prompt (the nature of which varies depending on the Seat itself). + * + * Return values are 0 for 'abort connection', 1 for 'ok, carry on', + * and negative for 'answer not received yet, wait for a callback'. */ -int verify_ssh_manual_host_key(Conf *conf, char **fingerprints, ssh_key *key) +int verify_ssh_host_key( + Seat *seat, Conf *conf, const char *host, int port, ssh_key *key, + const char *keytype, char *keystr, const char *keydisp, + char **fingerprints, void (*callback)(void *ctx, int result), void *ctx) { - if (!conf_get_str_nthstrkey(conf, CONF_ssh_manual_hostkeys, 0)) - return -1; /* no manual keys configured */ + /* + * First, check if the Conf includes a manual specification of the + * expected host key. If so, that completely supersedes everything + * else, including the normal host key cache _and_ including + * manual overrides: we return success or failure immediately, + * entirely based on whether the key matches the Conf. + */ + if (conf_get_str_nthstrkey(conf, CONF_ssh_manual_hostkeys, 0)) { + if (fingerprints) { + for (size_t i = 0; i < SSH_N_FPTYPES; i++) { + /* + * Each fingerprint string we've been given will have + * things like 'ssh-rsa 2048' at the front of it. Strip + * those off and narrow down to just the hash at the end + * of the string. + */ + const char *fingerprint = fingerprints[i]; + if (!fingerprint) + continue; + const char *p = strrchr(fingerprint, ' '); + fingerprint = p ? p+1 : fingerprint; + if (conf_get_str_str_opt(conf, CONF_ssh_manual_hostkeys, + fingerprint)) + return 1; /* success */ + } + } - if (fingerprints) { - for (size_t i = 0; i < SSH_N_FPTYPES; i++) { + if (key) { /* - * Each fingerprint string we've been given will have - * things like 'ssh-rsa 2048' at the front of it. Strip - * those off and narrow down to just the hash at the end - * of the string. + * Construct the base64-encoded public key blob and see if + * that's listed. */ - const char *fingerprint = fingerprints[i]; - if (!fingerprint) - continue; - const char *p = strrchr(fingerprint, ' '); - fingerprint = p ? p+1 : fingerprint; + strbuf *binblob; + char *base64blob; + int atoms, i; + binblob = strbuf_new(); + ssh_key_public_blob(key, BinarySink_UPCAST(binblob)); + atoms = (binblob->len + 2) / 3; + base64blob = snewn(atoms * 4 + 1, char); + for (i = 0; i < atoms; i++) + base64_encode_atom(binblob->u + 3*i, + binblob->len - 3*i, base64blob + 4*i); + base64blob[atoms * 4] = '\0'; + strbuf_free(binblob); if (conf_get_str_str_opt(conf, CONF_ssh_manual_hostkeys, - fingerprint)) + base64blob)) { + sfree(base64blob); return 1; /* success */ - } - } - - if (key) { - /* - * Construct the base64-encoded public key blob and see if - * that's listed. - */ - strbuf *binblob; - char *base64blob; - int atoms, i; - binblob = strbuf_new(); - ssh_key_public_blob(key, BinarySink_UPCAST(binblob)); - atoms = (binblob->len + 2) / 3; - base64blob = snewn(atoms * 4 + 1, char); - for (i = 0; i < atoms; i++) - base64_encode_atom(binblob->u + 3*i, - binblob->len - 3*i, base64blob + 4*i); - base64blob[atoms * 4] = '\0'; - strbuf_free(binblob); - if (conf_get_str_str_opt(conf, CONF_ssh_manual_hostkeys, base64blob)) { + } sfree(base64blob); - return 1; /* success */ } - sfree(base64blob); + + return 0; } - return 0; + /* + * Next, check the host key cache. + */ + int storage_status = check_stored_host_key(host, port, keytype, keystr); + if (storage_status == 0) /* matching key was found in the cache */ + return 1; /* success */ + + /* + * The key is either missing from the cache, or does not match. + * Either way, fall back to an interactive prompt from the Seat. + */ + bool mismatch = (storage_status != 1); + return seat_confirm_ssh_host_key( + seat, host, port, keytype, keystr, keydisp, fingerprints, mismatch, + callback, ctx); } /* ---------------------------------------------------------------------- diff --git a/ssh/kex2-client.c b/ssh/kex2-client.c index 4bbd8765..2435534c 100644 --- a/ssh/kex2-client.c +++ b/ssh/kex2-client.c @@ -843,39 +843,33 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) * Authenticate remote host: verify host key. (We've already * checked the signature of the exchange hash.) */ - char **fingerprints = ssh2_all_fingerprints(s->hkey); - FingerprintType fptype_default = - ssh2_pick_default_fingerprint(fingerprints); - ppl_logevent("Host key fingerprint is:"); - ppl_logevent("%s", fingerprints[fptype_default]); - /* First check against manually configured host keys. */ - s->dlgret = verify_ssh_manual_host_key( - s->conf, fingerprints, s->hkey); - if (s->dlgret == 0) { /* did not match */ - ssh2_free_all_fingerprints(fingerprints); - ssh_sw_abort(s->ppl.ssh, "Host key did not appear in manually " - "configured list"); - *aborted = true; - return; - } else if (s->dlgret < 0) { /* none configured; use standard handling */ + { ssh2_userkey uk = { .key = s->hkey, .comment = NULL }; char *keydisp = ssh2_pubkey_openssh_str(&uk); - s->dlgret = seat_verify_ssh_host_key( - s->ppl.seat, s->savedhost, s->savedport, + char **fingerprints = ssh2_all_fingerprints(s->hkey); + + FingerprintType fptype_default = + ssh2_pick_default_fingerprint(fingerprints); + ppl_logevent("Host key fingerprint is:"); + ppl_logevent("%s", fingerprints[fptype_default]); + + s->dlgret = verify_ssh_host_key( + s->ppl.seat, s->conf, s->savedhost, s->savedport, s->hkey, ssh_key_cache_id(s->hkey), s->keystr, keydisp, fingerprints, ssh2_transport_dialog_callback, s); - sfree(keydisp); + ssh2_free_all_fingerprints(fingerprints); + sfree(keydisp); + } #ifdef FUZZING - s->dlgret = 1; + s->dlgret = 1; #endif - crMaybeWaitUntilV(s->dlgret >= 0); - if (s->dlgret == 0) { - ssh_user_close(s->ppl.ssh, - "User aborted at host key verification"); - *aborted = true; - return; - } + crMaybeWaitUntilV(s->dlgret >= 0); + if (s->dlgret == 0) { + ssh_user_close(s->ppl.ssh, + "User aborted at host key verification"); + *aborted = true; + return; } /* diff --git a/ssh/login1.c b/ssh/login1.c index 1e2d7d3e..001b3d8e 100644 --- a/ssh/login1.c +++ b/ssh/login1.c @@ -238,42 +238,29 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl) * Verify the host key. */ { - /* - * First format the key into a string. - */ char *keystr = rsastr_fmt(&s->hostkey); + char *keydisp = ssh1_pubkey_str(&s->hostkey); char **fingerprints = rsa_ssh1_fake_all_fingerprints(&s->hostkey); - /* First check against manually configured host keys. */ - s->dlgret = verify_ssh_manual_host_key(s->conf, fingerprints, NULL); - if (s->dlgret == 0) { /* did not match */ - ssh2_free_all_fingerprints(fingerprints); - sfree(keystr); - ssh_proto_error(s->ppl.ssh, "Host key did not appear in manually " - "configured list"); - return; - } else if (s->dlgret < 0) { /* none configured; use standard handling */ - char *keydisp = ssh1_pubkey_str(&s->hostkey); - s->dlgret = seat_verify_ssh_host_key( - s->ppl.seat, s->savedhost, s->savedport, "rsa", keystr, - keydisp, fingerprints, ssh1_login_dialog_callback, s); - sfree(keydisp); - ssh2_free_all_fingerprints(fingerprints); - sfree(keystr); + s->dlgret = verify_ssh_host_key( + s->ppl.seat, s->conf, s->savedhost, s->savedport, NULL, + "rsa", keystr, keydisp, fingerprints, + ssh1_login_dialog_callback, s); + + ssh2_free_all_fingerprints(fingerprints); + sfree(keydisp); + sfree(keystr); + } + #ifdef FUZZING - s->dlgret = 1; + s->dlgret = 1; #endif - crMaybeWaitUntilV(s->dlgret >= 0); + crMaybeWaitUntilV(s->dlgret >= 0); - if (s->dlgret == 0) { - ssh_user_close(s->ppl.ssh, - "User aborted at host key verification"); - return; - } - } else { - ssh2_free_all_fingerprints(fingerprints); - sfree(keystr); - } + if (s->dlgret == 0) { + ssh_user_close(s->ppl.ssh, + "User aborted at host key verification"); + return; } for (i = 0; i < 32; i++) { diff --git a/ssh/server.c b/ssh/server.c index 404ba77d..63a68f62 100644 --- a/ssh/server.c +++ b/ssh/server.c @@ -116,7 +116,7 @@ static const SeatVtable server_seat_vt = { .update_specials_menu = nullseat_update_specials_menu, .get_ttymode = nullseat_get_ttymode, .set_busy_status = nullseat_set_busy_status, - .verify_ssh_host_key = nullseat_verify_ssh_host_key, + .confirm_ssh_host_key = nullseat_confirm_ssh_host_key, .confirm_weak_crypto_primitive = server_confirm_weak_crypto_primitive, .confirm_weak_cached_hostkey = server_confirm_weak_cached_hostkey, .is_utf8 = nullseat_is_never_utf8, diff --git a/ssh/sesschan.c b/ssh/sesschan.c index ff4a008f..6951edc4 100644 --- a/ssh/sesschan.c +++ b/ssh/sesschan.c @@ -195,7 +195,7 @@ static const SeatVtable sesschan_seat_vt = { .update_specials_menu = nullseat_update_specials_menu, .get_ttymode = nullseat_get_ttymode, .set_busy_status = nullseat_set_busy_status, - .verify_ssh_host_key = nullseat_verify_ssh_host_key, + .confirm_ssh_host_key = nullseat_confirm_ssh_host_key, .confirm_weak_crypto_primitive = nullseat_confirm_weak_crypto_primitive, .confirm_weak_cached_hostkey = nullseat_confirm_weak_cached_hostkey, .is_utf8 = nullseat_is_never_utf8, diff --git a/sshproxy.c b/sshproxy.c index 31657645..ad274d57 100644 --- a/sshproxy.c +++ b/sshproxy.c @@ -338,10 +338,10 @@ static void sshproxy_connection_fatal(Seat *seat, const char *message) } } -static int sshproxy_verify_ssh_host_key( - Seat *seat, const char *host, int port, const char *keytype, - char *keystr, const char *keydisp, char **key_fingerprints, - void (*callback)(void *ctx, int result), void *ctx) +static int sshproxy_confirm_ssh_host_key( + Seat *seat, const char *host, int port, const char *keytype, + char *keystr, const char *keydisp, char **key_fingerprints, bool mismatch, + void (*callback)(void *ctx, int result), void *ctx) { SshProxy *sp = container_of(seat, SshProxy, seat); @@ -350,39 +350,17 @@ static int sshproxy_verify_ssh_host_key( * If we have access to the outer Seat, pass this prompt * request on to it. FIXME: appropriately adjusted */ - return seat_verify_ssh_host_key( + return seat_confirm_ssh_host_key( sp->clientseat, host, port, keytype, keystr, keydisp, - key_fingerprints, callback, ctx); + key_fingerprints, mismatch, callback, ctx); } /* - * Otherwise, behave as if we're in batch mode: directly verify - * the host key against the cache, and if that fails, take the - * safe option in the absence of interactive confirmation, and - * abort the connection. + * Otherwise, behave as if we're in batch mode, i.e. take the safe + * option in the absence of interactive confirmation, i.e. abort + * the connection. */ - int hkstatus = verify_host_key(host, port, keytype, keystr); - FingerprintType fptype = ssh2_pick_default_fingerprint(key_fingerprints); - - switch (hkstatus) { - case 0: /* host key matched */ - return 1; - - case 1: /* host key not in cache at all */ - sshproxy_error(sp, "Host key not in cache for %s:%d (fingerprint %s). " - "Abandoning proxy SSH connection.", host, port, - key_fingerprints[fptype]); - return 0; - - case 2: - sshproxy_error(sp, "HOST KEY DOES NOT MATCH CACHE for %s:%d " - "(fingerprint %s). Abandoning proxy SSH connection.", - host, port, key_fingerprints[fptype]); - return 0; - - default: - unreachable("bad return value from verify_host_key"); - } + return 0; } static int sshproxy_confirm_weak_crypto_primitive( @@ -482,7 +460,7 @@ static const SeatVtable SshProxy_seat_vt = { .update_specials_menu = nullseat_update_specials_menu, .get_ttymode = nullseat_get_ttymode, .set_busy_status = nullseat_set_busy_status, - .verify_ssh_host_key = sshproxy_verify_ssh_host_key, + .confirm_ssh_host_key = sshproxy_confirm_ssh_host_key, .confirm_weak_crypto_primitive = sshproxy_confirm_weak_crypto_primitive, .confirm_weak_cached_hostkey = sshproxy_confirm_weak_cached_hostkey, .is_utf8 = nullseat_is_never_utf8, diff --git a/storage.h b/storage.h index 6464b69d..3e03181a 100644 --- a/storage.h +++ b/storage.h @@ -81,8 +81,8 @@ void enum_settings_finish(settings_e *handle); * 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); +int check_stored_host_key(const char *hostname, int port, + const char *keytype, const char *key); /* * Write a host key into the database, overwriting any previous diff --git a/unix/console.c b/unix/console.c index 8a7cd8f9..da795614 100644 --- a/unix/console.c +++ b/unix/console.c @@ -102,30 +102,20 @@ static int block_and_read(int fd, void *buf, size_t len) return ret; } -int console_verify_ssh_host_key( +int console_confirm_ssh_host_key( Seat *seat, const char *host, int port, const char *keytype, - char *keystr, const char *keydisp, char **fingerprints, + char *keystr, const char *keydisp, char **fingerprints, bool mismatch, void (*callback)(void *ctx, int result), void *ctx) { - int ret; - char line[32]; struct termios cf; char *common; const char *intro, *prompt; - /* - * Verify the key. - */ - ret = verify_host_key(host, port, keytype, keystr); - - if (ret == 0) /* success - key matched OK */ - return 1; - FingerprintType fptype_default = ssh2_pick_default_fingerprint(fingerprints); - if (ret == 2) { /* key was different */ + if (mismatch) { /* key was different */ common = hk_wrongmsg_common(host, port, keytype, fingerprints[fptype_default]); intro = hk_wrongmsg_interactive_intro; diff --git a/unix/dialog.c b/unix/dialog.c index 42cff97f..743be31d 100644 --- a/unix/dialog.c +++ b/unix/dialog.c @@ -3448,7 +3448,7 @@ GtkWidget *create_message_box( NULL /* action_postproc */, NULL /* postproc_ctx */); } -struct verify_ssh_host_key_dialog_ctx { +struct confirm_ssh_host_key_dialog_ctx { char *host; int port; char *keytype; @@ -3462,10 +3462,10 @@ struct verify_ssh_host_key_dialog_ctx { GtkWidget *more_info_dialog; }; -static void verify_ssh_host_key_result_callback(void *vctx, int result) +static void confirm_ssh_host_key_result_callback(void *vctx, int result) { - struct verify_ssh_host_key_dialog_ctx *ctx = - (struct verify_ssh_host_key_dialog_ctx *)vctx; + struct confirm_ssh_host_key_dialog_ctx *ctx = + (struct confirm_ssh_host_key_dialog_ctx *)vctx; if (result >= 0) { int logical_result; @@ -3518,16 +3518,16 @@ static GtkWidget *add_more_info_button(GtkWidget *w, void *vctx) static void more_info_closed(void *vctx, int result) { - struct verify_ssh_host_key_dialog_ctx *ctx = - (struct verify_ssh_host_key_dialog_ctx *)vctx; + struct confirm_ssh_host_key_dialog_ctx *ctx = + (struct confirm_ssh_host_key_dialog_ctx *)vctx; ctx->more_info_dialog = NULL; } static void more_info_button_clicked(GtkButton *button, gpointer vctx) { - struct verify_ssh_host_key_dialog_ctx *ctx = - (struct verify_ssh_host_key_dialog_ctx *)vctx; + struct confirm_ssh_host_key_dialog_ctx *ctx = + (struct confirm_ssh_host_key_dialog_ctx *)vctx; if (ctx->more_info_dialog) return; @@ -3539,9 +3539,9 @@ static void more_info_button_clicked(GtkButton *button, gpointer vctx) &buttons_ok, more_info_closed, ctx); } -int gtk_seat_verify_ssh_host_key( +int gtk_seat_confirm_ssh_host_key( Seat *seat, const char *host, int port, const char *keytype, - char *keystr, const char *keydisp, char **fingerprints, + char *keystr, const char *keydisp, char **fingerprints, bool mismatch, void (*callback)(void *ctx, int result), void *ctx) { static const char absenttxt[] = @@ -3584,25 +3584,16 @@ int gtk_seat_verify_ssh_host_key( }; char *text; - int ret; - struct verify_ssh_host_key_dialog_ctx *result_ctx; + struct confirm_ssh_host_key_dialog_ctx *result_ctx; GtkWidget *mainwin, *msgbox; - /* - * Verify the key. - */ - ret = verify_host_key(host, port, keytype, keystr); - - if (ret == 0) /* success - key matched OK */ - return 1; - FingerprintType fptype_default = ssh2_pick_default_fingerprint(fingerprints); - text = dupprintf((ret == 2 ? wrongtxt : absenttxt), host, port, + text = dupprintf((mismatch ? wrongtxt : absenttxt), host, port, keytype, fingerprints[fptype_default]); - result_ctx = snew(struct verify_ssh_host_key_dialog_ctx); + result_ctx = snew(struct confirm_ssh_host_key_dialog_ctx); result_ctx->callback = callback; result_ctx->callback_ctx = ctx; result_ctx->host = dupstr(host); @@ -3616,7 +3607,7 @@ int gtk_seat_verify_ssh_host_key( msgbox = create_message_box_general( mainwin, "PuTTY Security Alert", text, string_width(fingerprints[fptype_default]), true, - &buttons_hostkey, verify_ssh_host_key_result_callback, result_ctx, + &buttons_hostkey, confirm_ssh_host_key_result_callback, result_ctx, add_more_info_button, &more_info_button); result_ctx->main_dialog = msgbox; diff --git a/unix/platform.h b/unix/platform.h index 97762f3f..b4f5a008 100644 --- a/unix/platform.h +++ b/unix/platform.h @@ -217,9 +217,9 @@ void showeventlog(eventlog_stuff *estuff, void *parentwin); void logevent_dlg(eventlog_stuff *estuff, const char *string); int gtkdlg_askappend(Seat *seat, Filename *filename, void (*callback)(void *ctx, int result), void *ctx); -int gtk_seat_verify_ssh_host_key( +int gtk_seat_confirm_ssh_host_key( Seat *seat, const char *host, int port, const char *keytype, - char *keystr, const char *keydisp, char **fingerprints, + char *keystr, const char *keydisp, char **fingerprints, bool mismatch, void (*callback)(void *ctx, int result), void *ctx); int gtk_seat_confirm_weak_crypto_primitive( Seat *seat, const char *algtype, const char *algname, diff --git a/unix/plink.c b/unix/plink.c index 643fc563..524e4545 100644 --- a/unix/plink.c +++ b/unix/plink.c @@ -399,7 +399,7 @@ static const SeatVtable plink_seat_vt = { .update_specials_menu = nullseat_update_specials_menu, .get_ttymode = plink_get_ttymode, .set_busy_status = nullseat_set_busy_status, - .verify_ssh_host_key = console_verify_ssh_host_key, + .confirm_ssh_host_key = console_confirm_ssh_host_key, .confirm_weak_crypto_primitive = console_confirm_weak_crypto_primitive, .confirm_weak_cached_hostkey = console_confirm_weak_cached_hostkey, .is_utf8 = nullseat_is_never_utf8, diff --git a/unix/storage.c b/unix/storage.c index 9db713d1..16bb63e0 100644 --- a/unix/storage.c +++ b/unix/storage.c @@ -595,8 +595,8 @@ void enum_settings_finish(settings_e *handle) * * rsa@22:foovax.example.org 0x23,0x293487364395345345....2343 */ -int verify_host_key(const char *hostname, int port, - const char *keytype, const char *key) +int check_stored_host_key(const char *hostname, int port, + const char *keytype, const char *key) { FILE *fp; char *filename; @@ -668,10 +668,10 @@ bool have_ssh_host_key(const char *hostname, int port, const char *keytype) { /* - * If we have a host key, verify_host_key will return 0 or 2. + * If we have a host key, check_stored_host_key will return 0 or 2. * If we don't have one, it'll return 1. */ - return verify_host_key(hostname, port, keytype, "") != 1; + return check_stored_host_key(hostname, port, keytype, "") != 1; } void store_host_key(const char *hostname, int port, diff --git a/unix/window.c b/unix/window.c index 8740bc50..c3ddb515 100644 --- a/unix/window.c +++ b/unix/window.c @@ -398,7 +398,7 @@ static const SeatVtable gtk_seat_vt = { .update_specials_menu = gtk_seat_update_specials_menu, .get_ttymode = gtk_seat_get_ttymode, .set_busy_status = gtk_seat_set_busy_status, - .verify_ssh_host_key = gtk_seat_verify_ssh_host_key, + .confirm_ssh_host_key = gtk_seat_confirm_ssh_host_key, .confirm_weak_crypto_primitive = gtk_seat_confirm_weak_crypto_primitive, .confirm_weak_cached_hostkey = gtk_seat_confirm_weak_cached_hostkey, .is_utf8 = gtk_seat_is_utf8, diff --git a/utils/nullseat.c b/utils/nullseat.c index 43c478e0..19fad59d 100644 --- a/utils/nullseat.c +++ b/utils/nullseat.c @@ -16,9 +16,9 @@ void nullseat_connection_fatal(Seat *seat, const char *message) {} void nullseat_update_specials_menu(Seat *seat) {} char *nullseat_get_ttymode(Seat *seat, const char *mode) { return NULL; } void nullseat_set_busy_status(Seat *seat, BusyStatus status) {} -int nullseat_verify_ssh_host_key( +int nullseat_confirm_ssh_host_key( Seat *seat, const char *host, int port, const char *keytype, - char *keystr, const char *keydisp, char **key_fingerprints, + char *keystr, const char *keydisp, char **key_fingerprints, bool mismatch, void (*callback)(void *ctx, int result), void *ctx) { return 0; } int nullseat_confirm_weak_crypto_primitive( Seat *seat, const char *algtype, const char *algname, diff --git a/utils/tempseat.c b/utils/tempseat.c index 0046fe0d..f877dd1d 100644 --- a/utils/tempseat.c +++ b/utils/tempseat.c @@ -224,12 +224,12 @@ static int tempseat_get_userpass_input(Seat *seat, prompts_t *p) unreachable("get_userpass_input should never be called on TempSeat"); } -static int tempseat_verify_ssh_host_key( +static int tempseat_confirm_ssh_host_key( Seat *seat, const char *host, int port, const char *keytype, - char *keystr, const char *keydisp, char **key_fingerprints, + char *keystr, const char *keydisp, char **key_fingerprints, bool mismatch, void (*callback)(void *ctx, int result), void *ctx) { - unreachable("verify_ssh_host_key should never be called on TempSeat"); + unreachable("confirm_ssh_host_key should never be called on TempSeat"); } static int tempseat_confirm_weak_crypto_primitive( @@ -307,7 +307,7 @@ static const struct SeatVtable tempseat_vt = { .update_specials_menu = tempseat_update_specials_menu, .get_ttymode = tempseat_get_ttymode, .set_busy_status = tempseat_set_busy_status, - .verify_ssh_host_key = tempseat_verify_ssh_host_key, + .confirm_ssh_host_key = tempseat_confirm_ssh_host_key, .confirm_weak_crypto_primitive = tempseat_confirm_weak_crypto_primitive, .confirm_weak_cached_hostkey = tempseat_confirm_weak_cached_hostkey, .is_utf8 = tempseat_is_utf8, diff --git a/windows/console.c b/windows/console.c index 75d55916..abd31270 100644 --- a/windows/console.c +++ b/windows/console.c @@ -32,12 +32,11 @@ void console_print_error_msg(const char *prefix, const char *msg) fflush(stderr); } -int console_verify_ssh_host_key( +int console_confirm_ssh_host_key( Seat *seat, const char *host, int port, const char *keytype, - char *keystr, const char *keydisp, char **fingerprints, + char *keystr, const char *keydisp, char **fingerprints, bool mismatch, void (*callback)(void *ctx, int result), void *ctx) { - int ret; HANDLE hin; DWORD savemode, i; char *common; @@ -45,18 +44,10 @@ int console_verify_ssh_host_key( 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; - FingerprintType fptype_default = ssh2_pick_default_fingerprint(fingerprints); - if (ret == 2) { /* key was different */ + if (mismatch) { /* key was different */ common = hk_wrongmsg_common(host, port, keytype, fingerprints[fptype_default]); intro = hk_wrongmsg_interactive_intro; diff --git a/windows/dialog.c b/windows/dialog.c index 4778528b..a5af31ed 100644 --- a/windows/dialog.c +++ b/windows/dialog.c @@ -976,52 +976,43 @@ static INT_PTR CALLBACK HostKeyDialogProc(HWND hwnd, UINT msg, return 0; } -int win_seat_verify_ssh_host_key( +int win_seat_confirm_ssh_host_key( Seat *seat, const char *host, int port, const char *keytype, - char *keystr, const char *keydisp, char **fingerprints, - void (*callback)(void *ctx, int result), void *ctx) + char *keystr, const char *keydisp, char **fingerprints, bool mismatch, + void (*callback)(void *ctx, int result), void *vctx) { - int ret; - WinGuiSeat *wgs = container_of(seat, WinGuiSeat, seat); - /* - * Verify the key against the registry. - */ - ret = verify_host_key(host, port, keytype, keystr); - - if (ret == 0) /* success - key matched OK */ + static const char *const keywords[] = + { "{KEYTYPE}", "{APPNAME}", NULL }; + + const char *values[2]; + values[0] = keytype; + values[1] = appname; + + struct hostkey_dialog_ctx ctx[1]; + ctx->keywords = keywords; + ctx->values = values; + ctx->fingerprints = fingerprints; + ctx->fptype_default = ssh2_pick_default_fingerprint(fingerprints); + ctx->keydisp = keydisp; + ctx->iconid = (mismatch ? IDI_WARNING : IDI_QUESTION); + ctx->helpctx = (mismatch ? WINHELP_CTX_errors_hostkey_changed : + WINHELP_CTX_errors_hostkey_absent); + ctx->host = host; + ctx->port = port; + int dlgid = (mismatch ? IDD_HK_WRONG : IDD_HK_ABSENT); + int mbret = DialogBoxParam( + hinst, MAKEINTRESOURCE(dlgid), wgs->term_hwnd, + HostKeyDialogProc, (LPARAM)ctx); + assert(mbret==IDC_HK_ACCEPT || mbret==IDC_HK_ONCE || mbret==IDCANCEL); + if (mbret == IDC_HK_ACCEPT) { + store_host_key(host, port, keytype, keystr); + return 1; + } else if (mbret == IDC_HK_ONCE) { return 1; - else { - static const char *const keywords[] = - { "{KEYTYPE}", "{APPNAME}", NULL }; - - const char *values[2]; - values[0] = keytype; - values[1] = appname; - - struct hostkey_dialog_ctx ctx[1]; - ctx->keywords = keywords; - ctx->values = values; - ctx->fingerprints = fingerprints; - ctx->fptype_default = ssh2_pick_default_fingerprint(fingerprints); - ctx->keydisp = keydisp; - ctx->iconid = (ret == 2 ? IDI_WARNING : IDI_QUESTION); - ctx->helpctx = (ret == 2 ? WINHELP_CTX_errors_hostkey_changed : - WINHELP_CTX_errors_hostkey_absent); - ctx->host = host; - ctx->port = port; - int dlgid = (ret == 2 ? IDD_HK_WRONG : IDD_HK_ABSENT); - int mbret = DialogBoxParam( - hinst, MAKEINTRESOURCE(dlgid), wgs->term_hwnd, - HostKeyDialogProc, (LPARAM)ctx); - assert(mbret==IDC_HK_ACCEPT || mbret==IDC_HK_ONCE || mbret==IDCANCEL); - if (mbret == IDC_HK_ACCEPT) { - store_host_key(host, port, keytype, keystr); - return 1; - } else if (mbret == IDC_HK_ONCE) - return 1; } + return 0; /* abandon the connection */ } diff --git a/windows/platform.h b/windows/platform.h index 65dacdc1..cf76566b 100644 --- a/windows/platform.h +++ b/windows/platform.h @@ -220,9 +220,9 @@ int has_embedded_chm(void); /* 1 = yes, 0 = no, -1 = N/A */ * GUI seat methods in windlg.c, so that the vtable definition in * window.c can refer to them. */ -int win_seat_verify_ssh_host_key( +int win_seat_confirm_ssh_host_key( Seat *seat, const char *host, int port, const char *keytype, - char *keystr, const char *keydisp, char **key_fingerprints, + char *keystr, const char *keydisp, char **key_fingerprints, bool mismatch, void (*callback)(void *ctx, int result), void *ctx); int win_seat_confirm_weak_crypto_primitive( Seat *seat, const char *algtype, const char *algname, diff --git a/windows/plink.c b/windows/plink.c index b49266d3..7506a13f 100644 --- a/windows/plink.c +++ b/windows/plink.c @@ -93,7 +93,7 @@ static const SeatVtable plink_seat_vt = { .update_specials_menu = nullseat_update_specials_menu, .get_ttymode = nullseat_get_ttymode, .set_busy_status = nullseat_set_busy_status, - .verify_ssh_host_key = console_verify_ssh_host_key, + .confirm_ssh_host_key = console_confirm_ssh_host_key, .confirm_weak_crypto_primitive = console_confirm_weak_crypto_primitive, .confirm_weak_cached_hostkey = console_confirm_weak_cached_hostkey, .is_utf8 = nullseat_is_never_utf8, diff --git a/windows/storage.c b/windows/storage.c index 09e5c028..2bb08953 100644 --- a/windows/storage.c +++ b/windows/storage.c @@ -322,8 +322,8 @@ static void hostkey_regname(strbuf *sb, const char *hostname, escape_registry_key(hostname, sb); } -int verify_host_key(const char *hostname, int port, - const char *keytype, const char *key) +int check_stored_host_key(const char *hostname, int port, + const char *keytype, const char *key) { char *otherstr; strbuf *regname; @@ -437,10 +437,10 @@ bool have_ssh_host_key(const char *hostname, int port, const char *keytype) { /* - * If we have a host key, verify_host_key will return 0 or 2. + * If we have a host key, check_stored_host_key will return 0 or 2. * If we don't have one, it'll return 1. */ - return verify_host_key(hostname, port, keytype, "") != 1; + return check_stored_host_key(hostname, port, keytype, "") != 1; } void store_host_key(const char *hostname, int port, diff --git a/windows/window.c b/windows/window.c index 2f7cb274..4c67009b 100644 --- a/windows/window.c +++ b/windows/window.c @@ -340,7 +340,7 @@ static const SeatVtable win_seat_vt = { .update_specials_menu = win_seat_update_specials_menu, .get_ttymode = win_seat_get_ttymode, .set_busy_status = win_seat_set_busy_status, - .verify_ssh_host_key = win_seat_verify_ssh_host_key, + .confirm_ssh_host_key = win_seat_confirm_ssh_host_key, .confirm_weak_crypto_primitive = win_seat_confirm_weak_crypto_primitive, .confirm_weak_cached_hostkey = win_seat_confirm_weak_cached_hostkey, .is_utf8 = win_seat_is_utf8, -- cgit v1.2.3 From e24444dba81ef960b02bd7aa666ca5c6d46f03eb Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 25 Oct 2021 18:12:21 +0100 Subject: Fix manual host key validation. When the user tries to add a string to the CONF_ssh_manual_hostkeys list box, we call a validation function which is supposed to look along the string for either a valid-looking SSH key fingerprint, or a base64 public key blob, and after it finds it, move that key alone to the start of the input string and delete all the surrounding cruft. SHA-256 key fingerprints were being detected all right, but not moved to the start of the string sensibly - we just returned true without rewriting anything. (Probably inadequate testing when I added SHA-256 fairly recently.) And the code that moved a full public-key blob to the front of the string triggered an ASan error on the grounds that it used strcpy with the source and destination overlapping. I actually hadn't known that was supposed to be a bad thing these days! But it's easily fixed by making it a memmove instead. --- utils/validate_manual_hostkey.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/utils/validate_manual_hostkey.c b/utils/validate_manual_hostkey.c index 386e1039..7c5d1b88 100644 --- a/utils/validate_manual_hostkey.c +++ b/utils/validate_manual_hostkey.c @@ -38,8 +38,11 @@ bool validate_manual_hostkey(char *key) if (strstartswith(q, "SHA256:")) { /* Test for a valid SHA256 key fingerprint. */ r = q + 7; - if (strlen(r) == 43 && r[strspn(r, BASE64_CHARS_NOEQ)] == 0) + if (strspn(r, BASE64_CHARS_NOEQ) == 43) { + memmove(key, q, 50); /* 7-char prefix + 43-char base64 */ + key[50] = '\0'; return true; + } } r = q; @@ -106,7 +109,9 @@ bool validate_manual_hostkey(char *key) if (strlen(q) < minlen) goto not_ssh2_blob; /* sorry */ - strcpy(key, q); + size_t base64_len = strspn(q, BASE64_CHARS_ALL); + memmove(key, q, base64_len); + key[base64_len] = '\0'; return true; } not_ssh2_blob:; -- cgit v1.2.3 From 5eee8ca648c8453ff5308f7a009277eccfa80d16 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 29 Oct 2021 18:08:18 +0100 Subject: Compatibility with older versions of cmake. After this change, the cmake setup now works even on Debian stretch (oldoldstable), which runs cmake 3.7. In order to support a version that early I had to: - write a fallback implementation of 'add_compile_definitions' for older cmakes, which is easy, because add_compile_definitions(FOO) is basically just add_compile_options(-DFOO) - stop using list(TRANSFORM) and string(JOIN), of which I had one case each, and they were easily replaced with simple foreach loops - stop putting OBJECT libraries in the target_link_libraries command for executable targets, in favour of adding $ to the main sources list for the same target. That matches what I do with library targets, so it's probably more sensible anyway. I tried going back by another Debian release and getting this cmake setup to work on jessie, but that runs CMake 3.0.1, and in _that_ version of cmake the target_sources command is missing, and I didn't find any alternative way to add extra sources to a target after having first declared it. Reorganising to cope with _that_ omission would be too much upheaval without a very good reason. --- CHECKLST.txt | 3 +++ CMakeLists.txt | 2 +- cmake/setup.cmake | 14 ++++++++++++-- crypto/CMakeLists.txt | 4 +++- doc/CMakeLists.txt | 2 +- unix/CMakeLists.txt | 12 ++++++------ 6 files changed, 26 insertions(+), 11 deletions(-) diff --git a/CHECKLST.txt b/CHECKLST.txt index c3a5365a..82fc9d6a 100644 --- a/CHECKLST.txt +++ b/CHECKLST.txt @@ -37,6 +37,9 @@ Things to do during the branch-stabilisation period: particular, any headline features for the release should get a workout with memory checking enabled! + - Test the CMake build scripts with the oldest CMake they claim to + support, on both Unix and Windows. + Making a release candidate build -------------------------------- diff --git a/CMakeLists.txt b/CMakeLists.txt index 299f04c4..16a0bc96 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.12) +cmake_minimum_required(VERSION 3.7) project(putty LANGUAGES C) include(cmake/setup.cmake) diff --git a/cmake/setup.cmake b/cmake/setup.cmake index c0965c49..6612de97 100644 --- a/cmake/setup.cmake +++ b/cmake/setup.cmake @@ -55,9 +55,19 @@ add_custom_target(cmake_commit_c DEPENDS check_git_commit ${INTERMEDIATE_COMMIT_C} COMMENT "Updating cmake_commit.c") +if(CMAKE_VERSION VERSION_LESS 3.12) + function(add_compile_definitions) + foreach(i ${ARGN}) + add_compile_options(-D${i}) + endforeach() + endfunction() +endif() + function(add_sources_from_current_dir target) - set(sources ${ARGN}) - list(TRANSFORM sources PREPEND ${CMAKE_CURRENT_SOURCE_DIR}/) + set(sources) + foreach(i ${ARGN}) + set(sources ${sources} ${CMAKE_CURRENT_SOURCE_DIR}/${i}) + endforeach() target_sources(${target} PRIVATE ${sources}) endfunction() diff --git a/crypto/CMakeLists.txt b/crypto/CMakeLists.txt index 7d11f444..c6420361 100644 --- a/crypto/CMakeLists.txt +++ b/crypto/CMakeLists.txt @@ -54,7 +54,9 @@ function(test_compile_with_flags outvar) endif() # See if we can compile the provided test program. - string(JOIN " " CMAKE_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS} ${flags}) + foreach(i ${flags}) + set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} ${i}") + endforeach() check_c_source_compiles("${OPT_TEST_SOURCE}" "${outvar}") if(${outvar} AND OPT_ADD_SOURCES_IF_SUCCESSFUL) diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index eb273758..cc90fcdc 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.12) +cmake_minimum_required(VERSION 3.7) project(putty-documentation LANGUAGES) # This build script can be run standalone, or included as a diff --git a/unix/CMakeLists.txt b/unix/CMakeLists.txt index 37ea4337..a2332473 100644 --- a/unix/CMakeLists.txt +++ b/unix/CMakeLists.txt @@ -87,15 +87,15 @@ add_library(puttygen-common OBJECT ${CMAKE_SOURCE_DIR}/sshrand.c) add_executable(puttygen - ${CMAKE_SOURCE_DIR}/cmdgen.c) -target_link_libraries(puttygen - puttygen-common keygen console crypto utils) + ${CMAKE_SOURCE_DIR}/cmdgen.c + $) +target_link_libraries(puttygen keygen console crypto utils) installed_program(puttygen) add_executable(cgtest - ${CMAKE_SOURCE_DIR}/cgtest.c) -target_link_libraries(cgtest - puttygen-common keygen console crypto utils) + ${CMAKE_SOURCE_DIR}/cgtest.c + $) +target_link_libraries(cgtest keygen console crypto utils) add_executable(testsc ${CMAKE_SOURCE_DIR}/testsc.c) -- cgit v1.2.3 From 27f00038e1e22b9aba6cc287471ff2dda41fc8d4 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 30 Oct 2021 15:32:34 +0100 Subject: Fix trust-sigil handling when scrolling the terminal. Previously, when we scrolled the terminal, the newly exposed line at the bottom would be immediately allocated a trust status corresponding to the current state of the terminal. So if you're in trusted mode and you print a newline, then the line scrolled on at the bottom immediately gets a trust sigil, whether you subsequently print anything on it or not. Up until now, that hasn't mattered, because we always _do_ print something on it. But if you don't - if you send \r\n\r\n to deliberately leave a blank line - then it turns out that's not what we want after all, because if the screen _doesn't_ scroll, the passed-over line remains completely blank, whereas if it does scroll the blank line gets a trust sigil, which is inconsistent. Now, terminal lines newly exposed by a scroll have untrusted status, just the same as terminal lines that were present in the initial blank screen. They only become trusted if you actually print at least one character on them (whereupon check_trust_status will re-clear them just in case). And this is now independent of whether the terminal has scrolled or not. --- terminal/terminal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terminal/terminal.c b/terminal/terminal.c index 424683dc..b132b4e0 100644 --- a/terminal/terminal.c +++ b/terminal/terminal.c @@ -2618,7 +2618,7 @@ static void scroll(Terminal *term, int topline, int botline, } resizeline(term, line, term->cols); clear_line(term, line); - check_trust_status(term, line); + line->trusted = false; addpos234(term->screen, line, botline); /* -- cgit v1.2.3 From 76dc28552c401a4cd77d2bdd8b2a756c52334c06 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 30 Oct 2021 14:51:24 +0100 Subject: Add memsets after allocation of all Backend implementors. Now every struct that implements the Backend trait is completely cleared before we start initialising any of its fields. This will mean I can add new fields that default to 0 or NULL, without having to mess around initialising them explicitly everywhere. --- otherbackends/raw.c | 1 + otherbackends/rlogin.c | 1 + otherbackends/supdup.c | 1 + otherbackends/telnet.c | 1 + unix/pty.c | 1 + unix/serial.c | 1 + windows/conpty.c | 1 + windows/serial.c | 1 + 8 files changed, 8 insertions(+) diff --git a/otherbackends/raw.c b/otherbackends/raw.c index 57130dc6..569d2c62 100644 --- a/otherbackends/raw.c +++ b/otherbackends/raw.c @@ -156,6 +156,7 @@ static char *raw_init(const BackendVtable *vt, Seat *seat, char *loghost; raw = snew(Raw); + memset(raw, 0, sizeof(Raw)); raw->plug.vt = &Raw_plugvt; raw->backend.vt = vt; raw->s = NULL; diff --git a/otherbackends/rlogin.c b/otherbackends/rlogin.c index 4e3abffe..b9b21786 100644 --- a/otherbackends/rlogin.c +++ b/otherbackends/rlogin.c @@ -233,6 +233,7 @@ static char *rlogin_init(const BackendVtable *vt, Seat *seat, char *loghost; rlogin = snew(Rlogin); + memset(rlogin, 0, sizeof(Rlogin)); rlogin->plug.vt = &Rlogin_plugvt; rlogin->backend.vt = vt; rlogin->s = NULL; diff --git a/otherbackends/supdup.c b/otherbackends/supdup.c index 278e689e..3bde828e 100644 --- a/otherbackends/supdup.c +++ b/otherbackends/supdup.c @@ -683,6 +683,7 @@ static char *supdup_init(const BackendVtable *x, Seat *seat, const char *utf8 = "\033%G"; supdup = snew(struct supdup_tag); + memset(supdup, 0, sizeof(Supdup)); supdup->plug.vt = &fn_table; supdup->backend.vt = &supdup_backend; supdup->logctx = logctx; diff --git a/otherbackends/telnet.c b/otherbackends/telnet.c index 0011cf0d..819cbcb0 100644 --- a/otherbackends/telnet.c +++ b/otherbackends/telnet.c @@ -721,6 +721,7 @@ static char *telnet_init(const BackendVtable *vt, Seat *seat, int addressfamily; telnet = snew(Telnet); + memset(telnet, 0, sizeof(Telnet)); telnet->plug.vt = &Telnet_plugvt; telnet->backend.vt = vt; telnet->conf = conf_copy(conf); diff --git a/unix/pty.c b/unix/pty.c index 46382c06..e379e6cf 100644 --- a/unix/pty.c +++ b/unix/pty.c @@ -414,6 +414,7 @@ static void pty_open_master(Pty *pty) static Pty *new_pty_struct(void) { Pty *pty = snew(Pty); + memset(pty, 0, sizeof(Pty)); pty->conf = NULL; pty->pending_eof = false; bufchain_init(&pty->output_data); diff --git a/unix/serial.c b/unix/serial.c index 6c8f9b9e..1e20dd20 100644 --- a/unix/serial.c +++ b/unix/serial.c @@ -294,6 +294,7 @@ static char *serial_init(const BackendVtable *vt, Seat *seat, seat_set_trust_status(seat, false); serial = snew(Serial); + memset(serial, 0, sizeof(Serial)); serial->backend.vt = vt; *backend_handle = &serial->backend; diff --git a/windows/conpty.c b/windows/conpty.c index 83cb9635..fd5ef246 100644 --- a/windows/conpty.c +++ b/windows/conpty.c @@ -232,6 +232,7 @@ static char *conpty_init(const BackendVtable *vt, Seat *seat, seat_set_trust_status(seat, false); conpty = snew(ConPTY); + memset(conpty, 0, sizeof(ConPTY)); conpty->pseudoconsole = pcon; pcon_needs_cleanup = false; conpty->outpipe = in_w; diff --git a/windows/serial.c b/windows/serial.c index 82146a40..14a89822 100644 --- a/windows/serial.c +++ b/windows/serial.c @@ -212,6 +212,7 @@ static char *serial_init(const BackendVtable *vt, Seat *seat, seat_set_trust_status(seat, false); serial = snew(Serial); + memset(serial, 0, sizeof(Serial)); serial->port = INVALID_HANDLE_VALUE; serial->out = serial->in = NULL; serial->bufsize = 0; -- cgit v1.2.3 From 971c70e6031f3fcfd043c59a75ed886b1b1df905 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 30 Oct 2021 11:02:28 +0100 Subject: Move proxy-related source files into a subdirectory. There are quite a few of them already, and I'm about to make another one, so let's start with a bit of tidying up. The CMake build organisation is unchanged: I haven't put the proxy object files into a separate library, just moved the locations of the source files. (Organising proxying as a library would be tricky anyway, because of the various overrides for tools that want to avoid cryptography.) --- CMakeLists.txt | 8 +- cproxy.c | 179 ----- nocproxy.c | 35 - noproxy.c | 32 - nosshproxy.c | 17 - pproxy.c | 17 - proxy.c | 1515 ------------------------------------------- proxy.h | 110 ---- proxy/cproxy.c | 179 +++++ proxy/nocproxy.c | 35 + proxy/noproxy.c | 32 + proxy/nosshproxy.c | 17 + proxy/pproxy.c | 17 + proxy/proxy.c | 1515 +++++++++++++++++++++++++++++++++++++++++++ proxy/proxy.h | 110 ++++ proxy/sshproxy.c | 616 ++++++++++++++++++ sshproxy.c | 616 ------------------ unix/CMakeLists.txt | 10 +- unix/local-proxy.c | 2 +- unix/sharing.c | 2 +- windows/CMakeLists.txt | 6 +- windows/local-proxy.c | 2 +- windows/named-pipe-client.c | 2 +- windows/named-pipe-server.c | 2 +- windows/sharing.c | 2 +- 25 files changed, 2539 insertions(+), 2539 deletions(-) delete mode 100644 cproxy.c delete mode 100644 nocproxy.c delete mode 100644 noproxy.c delete mode 100644 nosshproxy.c delete mode 100644 pproxy.c delete mode 100644 proxy.c delete mode 100644 proxy.h create mode 100644 proxy/cproxy.c create mode 100644 proxy/nocproxy.c create mode 100644 proxy/noproxy.c create mode 100644 proxy/nosshproxy.c create mode 100644 proxy/pproxy.c create mode 100644 proxy/proxy.c create mode 100644 proxy/proxy.h create mode 100644 proxy/sshproxy.c delete mode 100644 sshproxy.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 16a0bc96..15670d69 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,11 +29,11 @@ add_library(settings STATIC cmdline.c settings.c) add_library(crypto STATIC - cproxy.c sshproxy.c) + proxy/cproxy.c proxy/sshproxy.c) add_subdirectory(crypto) add_library(network STATIC - be_misc.c nullplug.c errsock.c proxy.c logging.c x11disp.c) + be_misc.c nullplug.c errsock.c proxy/proxy.c logging.c x11disp.c) add_library(keygen STATIC import.c) @@ -119,8 +119,8 @@ add_executable(psocks ${platform}/psocks.c psocks.c norand.c - nocproxy.c - nosshproxy.c + proxy/nocproxy.c + proxy/nosshproxy.c ssh/portfwd.c) target_link_libraries(psocks eventloop console network utils diff --git a/cproxy.c b/cproxy.c deleted file mode 100644 index d187ffec..00000000 --- a/cproxy.c +++ /dev/null @@ -1,179 +0,0 @@ -/* - * 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 -#include -#include - -#include "putty.h" -#include "ssh.h" /* For MD5 support */ -#include "network.h" -#include "proxy.h" -#include "marshal.h" - -static void hmacmd5_chap(const unsigned char *challenge, int challen, - const char *passwd, unsigned char *response) -{ - mac_simple(&ssh_hmac_md5, ptrlen_from_asciz(passwd), - make_ptrlen(challenge, challen), response); -} - -void proxy_socks5_offerencryptedauth(BinarySink *bs) -{ - put_byte(bs, 0x03); /* CHAP */ -} - -int proxy_socks5_handlechap (ProxySocket *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); - return 1; - } - if (data[1] == 0x00) { - plug_closing(p->plug, "Proxy error: SOCKS proxy won't" - " negotiate CHAP with us", - PROXY_ERROR_GENERAL); - 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); - 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, - conf_get_str(p->conf, CONF_proxy_password), - &outbuf[4]); - sk_write(p->sub_socket, 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); - 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(ProxySocket *p) -{ - char *username = conf_get_str(p->conf, CONF_proxy_username); - char *password = conf_get_str(p->conf, CONF_proxy_password); - if (username[0] || password[0]) { - char chapbuf[514]; - int ulen; - chapbuf[0] = '\x01'; /* Version */ - chapbuf[1] = '\x02'; /* Number of attributes sent */ - chapbuf[2] = '\x11'; /* First attribute - algorithms list */ - chapbuf[3] = '\x01'; /* Only one CHAP algorithm */ - chapbuf[4] = '\x85'; /* ...and it's HMAC-MD5, the core one */ - chapbuf[5] = '\x02'; /* Second attribute - username */ - - ulen = strlen(username); - if (ulen > 255) ulen = 255; - if (ulen < 1) ulen = 1; - - chapbuf[6] = ulen; - memcpy(chapbuf+7, username, ulen); - - sk_write(p->sub_socket, chapbuf, ulen + 7); - p->chap_num_attributes = 0; - p->chap_num_attributes_processed = 0; - p->chap_current_attribute = -1; - 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); - return 1; -} diff --git a/nocproxy.c b/nocproxy.c deleted file mode 100644 index cdc28a39..00000000 --- a/nocproxy.c +++ /dev/null @@ -1,35 +0,0 @@ -/* - * 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 -#include -#include - -#include "putty.h" -#include "network.h" -#include "proxy.h" - -void proxy_socks5_offerencryptedauth(BinarySink *bs) -{ - /* For telnet, don't add any new encrypted authentication routines */ -} - -int proxy_socks5_handlechap (ProxySocket *p) -{ - - plug_closing(p->plug, "Proxy error: Trying to handle a SOCKS5 CHAP request" - " in telnet-only build", - PROXY_ERROR_GENERAL); - return 1; -} - -int proxy_socks5_selectchap(ProxySocket *p) -{ - plug_closing(p->plug, "Proxy error: Trying to handle a SOCKS5 CHAP request" - " in telnet-only build", - PROXY_ERROR_GENERAL); - return 1; -} diff --git a/noproxy.c b/noproxy.c deleted file mode 100644 index 82347d51..00000000 --- a/noproxy.c +++ /dev/null @@ -1,32 +0,0 @@ -/* - * noproxy.c: an alternative to proxy.c, for use by auxiliary programs - * that need to make network connections but don't want to include all - * the full-on support for endless network proxies (and its - * configuration requirements). Implements the primary APIs of - * proxy.c, but maps them straight to the underlying network layer. - */ - -#include "putty.h" -#include "network.h" -#include "proxy.h" - -SockAddr *name_lookup(const char *host, int port, char **canonicalname, - Conf *conf, int addressfamily, LogContext *logctx, - const char *reason) -{ - return sk_namelookup(host, canonicalname, addressfamily); -} - -Socket *new_connection(SockAddr *addr, const char *hostname, - int port, bool privport, - bool oobinline, bool nodelay, bool keepalive, - Plug *plug, Conf *conf, LogPolicy *lp, Seat **seat) -{ - return sk_new(addr, port, privport, oobinline, nodelay, keepalive, plug); -} - -Socket *new_listener(const char *srcaddr, int port, Plug *plug, - bool local_host_only, Conf *conf, int addressfamily) -{ - return sk_newlistener(srcaddr, port, plug, local_host_only, addressfamily); -} diff --git a/nosshproxy.c b/nosshproxy.c deleted file mode 100644 index 5f2bbdca..00000000 --- a/nosshproxy.c +++ /dev/null @@ -1,17 +0,0 @@ -/* - * nosshproxy.c: stub implementation of sshproxy_new_connection(). - */ - -#include "putty.h" -#include "network.h" - -const bool ssh_proxy_supported = false; - -Socket *sshproxy_new_connection(SockAddr *addr, const char *hostname, - int port, bool privport, - bool oobinline, bool nodelay, bool keepalive, - Plug *plug, Conf *conf, - LogPolicy *clientlp, Seat **clientseat) -{ - return NULL; -} diff --git a/pproxy.c b/pproxy.c deleted file mode 100644 index 4b08606e..00000000 --- a/pproxy.c +++ /dev/null @@ -1,17 +0,0 @@ -/* - * 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, const char *hostname, - int port, int privport, - int oobinline, int nodelay, int keepalive, - Plug *plug, Conf *conf) -{ - return NULL; -} diff --git a/proxy.c b/proxy.c deleted file mode 100644 index 08d5afba..00000000 --- a/proxy.c +++ /dev/null @@ -1,1515 +0,0 @@ -/* - * Network proxy abstraction in PuTTY - * - * A proxy layer, if necessary, wedges itself between the network - * code and the higher level backend. - */ - -#include -#include -#include - -#include "putty.h" -#include "network.h" -#include "proxy.h" - -#define do_proxy_dns(conf) \ - (conf_get_int(conf, CONF_proxy_dns) == FORCE_ON || \ - (conf_get_int(conf, CONF_proxy_dns) == AUTO && \ - conf_get_int(conf, CONF_proxy_type) != PROXY_SOCKS4)) - -/* - * Call this when proxy negotiation is complete, so that this - * socket can begin working normally. - */ -void proxy_activate (ProxySocket *p) -{ - size_t output_before, output_after; - - p->state = PROXY_STATE_ACTIVE; - - plug_log(p->plug, PLUGLOG_CONNECT_SUCCESS, NULL, 0, NULL, 0); - - /* we want to ignore new receive events until we have sent - * all of our buffered receive data. - */ - sk_set_frozen(p->sub_socket, true); - - /* 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) { - ptrlen data = bufchain_prefix(&p->pending_oob_output_data); - output_after += sk_write_oob(p->sub_socket, data.ptr, data.len); - bufchain_consume(&p->pending_oob_output_data, data.len); - } - - /* send buffered normal writes */ - while (bufchain_size(&p->pending_output_data) > 0) { - ptrlen data = bufchain_prefix(&p->pending_output_data); - output_after += sk_write(p->sub_socket, data.ptr, data.len); - bufchain_consume(&p->pending_output_data, 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 have a pending EOF to send, send it */ - if (p->pending_eof) sk_write_eof(p->sub_socket); - - /* if the backend wanted the socket unfrozen, try to unfreeze. - * our set_frozen handler will flush buffered receive data before - * unfreezing the actual underlying socket. - */ - if (!p->freeze) - sk_set_frozen(&p->sock, false); -} - -/* basic proxy socket functions */ - -static Plug *sk_proxy_plug (Socket *s, Plug *p) -{ - ProxySocket *ps = container_of(s, ProxySocket, sock); - Plug *ret = ps->plug; - if (p) - ps->plug = p; - return ret; -} - -static void sk_proxy_close (Socket *s) -{ - ProxySocket *ps = container_of(s, ProxySocket, sock); - - sk_close(ps->sub_socket); - sk_addr_free(ps->remote_addr); - sfree(ps); -} - -static size_t sk_proxy_write (Socket *s, const void *data, size_t len) -{ - ProxySocket *ps = container_of(s, ProxySocket, sock); - - 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 size_t sk_proxy_write_oob (Socket *s, const void *data, size_t len) -{ - ProxySocket *ps = container_of(s, ProxySocket, sock); - - 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_write_eof (Socket *s) -{ - ProxySocket *ps = container_of(s, ProxySocket, sock); - - if (ps->state != PROXY_STATE_ACTIVE) { - ps->pending_eof = true; - return; - } - sk_write_eof(ps->sub_socket); -} - -static void sk_proxy_set_frozen (Socket *s, bool is_frozen) -{ - ProxySocket *ps = container_of(s, ProxySocket, sock); - - 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) { - char databuf[512]; - ptrlen data = bufchain_prefix(&ps->pending_input_data); - if (data.len > lenof(databuf)) - data.len = lenof(databuf); - memcpy(databuf, data.ptr, data.len); - bufchain_consume(&ps->pending_input_data, data.len); - plug_receive(ps->plug, 0, databuf, data.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) -{ - ProxySocket *ps = container_of(s, ProxySocket, sock); - 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, PlugLogType type, SockAddr *addr, - int port, const char *error_msg, int error_code) -{ - ProxySocket *ps = container_of(plug, ProxySocket, plugimpl); - - plug_log(ps->plug, type, addr, port, error_msg, error_code); -} - -static void plug_proxy_closing(Plug *p, const char *error_msg, int error_code) -{ - ProxySocket *ps = container_of(p, ProxySocket, plugimpl); - - if (ps->state != PROXY_STATE_ACTIVE) { - ps->closing_error_msg = error_msg; - ps->closing_error_code = error_code; - ps->negotiate(ps, PROXY_CHANGE_CLOSING); - } else { - plug_closing(ps->plug, error_msg, error_code); - } -} - -static void plug_proxy_receive( - Plug *p, int urgent, const char *data, size_t len) -{ - ProxySocket *ps = container_of(p, ProxySocket, plugimpl); - - 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 != 0); - ps->receive_data = data; - ps->receive_len = len; - ps->negotiate(ps, PROXY_CHANGE_RECEIVE); - } else { - plug_receive(ps->plug, urgent, data, len); - } -} - -static void plug_proxy_sent (Plug *p, size_t bufsize) -{ - ProxySocket *ps = container_of(p, ProxySocket, plugimpl); - - if (ps->state != PROXY_STATE_ACTIVE) { - ps->negotiate(ps, PROXY_CHANGE_SENT); - return; - } - plug_sent(ps->plug, bufsize); -} - -static int plug_proxy_accepting(Plug *p, - accept_fn_t constructor, accept_ctx_t ctx) -{ - ProxySocket *ps = container_of(p, ProxySocket, plugimpl); - - if (ps->state != PROXY_STATE_ACTIVE) { - ps->accepting_constructor = constructor; - ps->accepting_ctx = ctx; - return ps->negotiate(ps, PROXY_CHANGE_ACCEPTING); - } - return plug_accepting(ps->plug, constructor, ctx); -} - -/* - * This function can accept a NULL pointer as `addr', in which case - * it will only check the host name. - */ -static bool proxy_for_destination(SockAddr *addr, const char *hostname, - int port, Conf *conf) -{ - int s = 0, e = 0; - char hostip[64]; - int hostip_len, hostname_len; - const char *exclude_list; - - /* - * Special local connections such as Unix-domain sockets - * unconditionally cannot be proxied, even in proxy-localhost - * mode. There just isn't any way to ask any known proxy type for - * them. - */ - if (addr && sk_address_is_special_local(addr)) - return false; /* do not proxy */ - - /* - * Check the host name and IP against the hard-coded - * representations of `localhost'. - */ - if (!conf_get_bool(conf, CONF_even_proxy_localhost) && - (sk_hostname_is_local(hostname) || - (addr && sk_address_is_local(addr)))) - return false; /* 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 = conf_get_str(conf, CONF_proxy_exclude_list); - - /* now parse the exclude list, and see if either our IP - * or hostname matches anything in it. - */ - - 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) { - /* IP/hostname range excluded. do not use proxy. */ - return false; - } - } 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) { - /* IP/hostname range excluded. do not use proxy. */ - return false; - } - } 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 false; /* IP/hostname excluded. do not use proxy. */ - if (strnicmp(hostname, exclude_list + s, e - s) == 0) - return false; /* 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 true; -} - -static char *dns_log_msg(const char *host, int addressfamily, - const char *reason) -{ - return dupprintf("Looking up host \"%s\"%s for %s", host, - (addressfamily == ADDRTYPE_IPV4 ? " (IPv4)" : - addressfamily == ADDRTYPE_IPV6 ? " (IPv6)" : - ""), reason); -} - -SockAddr *name_lookup(const char *host, int port, char **canonicalname, - Conf *conf, int addressfamily, LogContext *logctx, - const char *reason) -{ - if (conf_get_int(conf, CONF_proxy_type) != PROXY_NONE && - do_proxy_dns(conf) && - proxy_for_destination(NULL, host, port, conf)) { - - if (logctx) - logeventf(logctx, "Leaving host lookup to proxy of \"%s\"" - " (for %s)", host, reason); - - *canonicalname = dupstr(host); - return sk_nonamelookup(host); - } else { - if (logctx) - logevent_and_free( - logctx, dns_log_msg(host, addressfamily, reason)); - - return sk_namelookup(host, canonicalname, addressfamily); - } -} - -static const SocketVtable ProxySocket_sockvt = { - .plug = sk_proxy_plug, - .close = sk_proxy_close, - .write = sk_proxy_write, - .write_oob = sk_proxy_write_oob, - .write_eof = sk_proxy_write_eof, - .set_frozen = sk_proxy_set_frozen, - .socket_error = sk_proxy_socket_error, - .peer_info = NULL, -}; - -static const PlugVtable ProxySocket_plugvt = { - .log = plug_proxy_log, - .closing = plug_proxy_closing, - .receive = plug_proxy_receive, - .sent = plug_proxy_sent, - .accepting = plug_proxy_accepting -}; - -Socket *new_connection(SockAddr *addr, const char *hostname, - int port, bool privport, - bool oobinline, bool nodelay, bool keepalive, - Plug *plug, Conf *conf, LogPolicy *lp, Seat **seat) -{ - int type = conf_get_int(conf, CONF_proxy_type); - - if (type != PROXY_NONE && - proxy_for_destination(addr, hostname, port, conf)) - { - ProxySocket *ret; - SockAddr *proxy_addr; - char *proxy_canonical_name; - const char *proxy_type; - Socket *sret; - - if (type == PROXY_SSH && - (sret = sshproxy_new_connection(addr, hostname, port, privport, - oobinline, nodelay, keepalive, - plug, conf, lp, seat)) != NULL) - return sret; - - if ((sret = platform_new_connection(addr, hostname, port, privport, - oobinline, nodelay, keepalive, - plug, conf)) != NULL) - return sret; - - ret = snew(ProxySocket); - ret->sock.vt = &ProxySocket_sockvt; - ret->plugimpl.vt = &ProxySocket_plugvt; - ret->conf = conf_copy(conf); - ret->plug = plug; - ret->remote_addr = addr; /* will need to be freed on close */ - ret->remote_port = port; - - ret->error = NULL; - ret->pending_eof = false; - ret->freeze = false; - - 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 (type == PROXY_HTTP) { - ret->negotiate = proxy_http_negotiate; - proxy_type = "HTTP"; - } else if (type == PROXY_SOCKS4) { - ret->negotiate = proxy_socks4_negotiate; - proxy_type = "SOCKS 4"; - } else if (type == PROXY_SOCKS5) { - ret->negotiate = proxy_socks5_negotiate; - proxy_type = "SOCKS 5"; - } else if (type == PROXY_TELNET) { - ret->negotiate = proxy_telnet_negotiate; - proxy_type = "Telnet"; - } else { - ret->error = "Proxy error: Unknown proxy method"; - return &ret->sock; - } - - { - char *logmsg = dupprintf("Will use %s proxy at %s:%d to connect" - " to %s:%d", proxy_type, - conf_get_str(conf, CONF_proxy_host), - conf_get_int(conf, CONF_proxy_port), - hostname, port); - plug_log(plug, PLUGLOG_PROXY_MSG, NULL, 0, logmsg, 0); - sfree(logmsg); - } - - { - char *logmsg = dns_log_msg(conf_get_str(conf, CONF_proxy_host), - conf_get_int(conf, CONF_addressfamily), - "proxy"); - plug_log(plug, PLUGLOG_PROXY_MSG, NULL, 0, logmsg, 0); - sfree(logmsg); - } - - /* look-up proxy */ - proxy_addr = sk_namelookup(conf_get_str(conf, CONF_proxy_host), - &proxy_canonical_name, - conf_get_int(conf, CONF_addressfamily)); - if (sk_addr_error(proxy_addr) != NULL) { - ret->error = "Proxy error: Unable to resolve proxy host name"; - sk_addr_free(proxy_addr); - return &ret->sock; - } - sfree(proxy_canonical_name); - - { - char addrbuf[256], *logmsg; - sk_getaddr(proxy_addr, addrbuf, lenof(addrbuf)); - logmsg = dupprintf("Connecting to %s proxy at %s port %d", - proxy_type, addrbuf, - conf_get_int(conf, CONF_proxy_port)); - plug_log(plug, PLUGLOG_PROXY_MSG, NULL, 0, logmsg, 0); - sfree(logmsg); - } - - /* create the actual socket we will be using, - * connected to our proxy server and port. - */ - ret->sub_socket = sk_new(proxy_addr, - conf_get_int(conf, CONF_proxy_port), - privport, oobinline, - nodelay, keepalive, &ret->plugimpl); - if (sk_socket_error(ret->sub_socket) != NULL) - return &ret->sock; - - /* start the proxy negotiation process... */ - sk_set_frozen(ret->sub_socket, false); - ret->negotiate(ret, PROXY_CHANGE_NEW); - - return &ret->sock; - } - - /* no proxy, so just return the direct socket */ - return sk_new(addr, port, privport, oobinline, nodelay, keepalive, plug); -} - -Socket *new_listener(const char *srcaddr, int port, Plug *plug, - bool local_host_only, Conf *conf, 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 bool get_line_end(char *data, size_t len, size_t *out) -{ - size_t 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) { - *out = off; - return true; - } - - /* 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') { - *out = off; - return true; - } - - /* the line does continue, so we have to keep going - * until we see an the header's "real" end of line. - */ - off++; - } - - off++; - } - - return false; -} - -int proxy_http_negotiate (ProxySocket *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]; - char *username, *password; - - sk_getaddr(p->remote_addr, dest, lenof(dest)); - - buf = dupprintf("CONNECT %s:%i HTTP/1.1\r\nHost: %s:%i\r\n", - dest, p->remote_port, dest, p->remote_port); - sk_write(p->sub_socket, buf, strlen(buf)); - sfree(buf); - - username = conf_get_str(p->conf, CONF_proxy_username); - password = conf_get_str(p->conf, CONF_proxy_password); - if (username[0] || password[0]) { - char *buf, *buf2; - int i, j, len; - buf = dupprintf("%s:%s", username, password); - len = strlen(buf); - buf2 = snewn(len * 4 / 3 + 100, char); - sprintf(buf2, "Proxy-Authorization: Basic "); - for (i = 0, j = strlen(buf2); i < len; i += 3, j += 4) - base64_encode_atom((unsigned char *)(buf+i), - (len-i > 3 ? 3 : len-i), buf2+j); - strcpy(buf2+j, "\r\n"); - sk_write(p->sub_socket, buf2, strlen(buf2)); - sfree(buf); - sfree(buf2); - } - - sk_write(p->sub_socket, "\r\n", 2); - - p->state = 1; - 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. - */ - plug_closing(p->plug, p->closing_error_msg, p->closing_error_code); - return 0; /* ignored */ - } - - 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_constructor, p->accepting_ctx); - } - - if (change == PROXY_CHANGE_RECEIVE) { - /* we have received data from the underlying socket, which - * we'll need to parse, process, and respond to appropriately. - */ - - char *data, *datap; - size_t len, 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'; - - if (!get_line_end(data, len, &eol)) { - 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); - 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); - 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); - - if (!get_line_end(datap, len, &eol)) { - sfree(data); - return 1; - } - while (eol > 2) { - bufchain_consume(&p->pending_input_data, eol); - datap += eol; - len -= eol; - if (!get_line_end(datap, len, &eol)) - eol = 0; /* terminate the loop */ - } - - 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); - return 1; -} - -/* ---------------------------------------------------------------------- - * SOCKS proxy type. - */ - -/* SOCKS version 4 */ -int proxy_socks4_negotiate (ProxySocket *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) - */ - - strbuf *command = strbuf_new(); - char hostname[512]; - bool write_hostname = false; - - put_byte(command, 4); /* SOCKS version 4 */ - put_byte(command, 1); /* CONNECT command */ - put_uint16(command, p->remote_port); - - switch (sk_addrtype(p->remote_addr)) { - case ADDRTYPE_IPV4: { - char addr[4]; - sk_addrcopy(p->remote_addr, addr); - put_data(command, addr, 4); - break; - } - case ADDRTYPE_NAME: - sk_getaddr(p->remote_addr, hostname, lenof(hostname)); - put_uint32(command, 1); - write_hostname = true; - break; - case ADDRTYPE_IPV6: - p->error = "Proxy error: SOCKS version 4 does not support IPv6"; - strbuf_free(command); - return 1; - } - - put_asciz(command, conf_get_str(p->conf, CONF_proxy_username)); - if (write_hostname) - put_asciz(command, hostname); - sk_write(p->sub_socket, command->s, command->len); - strbuf_free(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. - */ - plug_closing(p->plug, p->closing_error_msg, p->closing_error_code); - return 0; /* ignored */ - } - - 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_constructor, p->accepting_ctx); - } - - if (change == PROXY_CHANGE_RECEIVE) { - /* we have received data from the underlying socket, which - * we'll need to parse, process, and respond to appropriately. - */ - - 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); - 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); - break; - case 93: - plug_closing(p->plug, "Proxy error: Username and IDENTD on client don't agree", - PROXY_ERROR_GENERAL); - break; - case 91: - default: - plug_closing(p->plug, "Proxy error: Error while communicating with proxy", - PROXY_ERROR_GENERAL); - 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); - return 1; -} - -/* SOCKS version 5 */ -int proxy_socks5_negotiate (ProxySocket *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 - */ - - strbuf *command; - char *username, *password; - int method_count_offset, methods_start; - - command = strbuf_new(); - put_byte(command, 5); /* SOCKS version 5 */ - username = conf_get_str(p->conf, CONF_proxy_username); - password = conf_get_str(p->conf, CONF_proxy_password); - - method_count_offset = command->len; - put_byte(command, 0); - methods_start = command->len; - - put_byte(command, 0x00); /* no authentication */ - - if (username[0] || password[0]) { - proxy_socks5_offerencryptedauth(BinarySink_UPCAST(command)); - put_byte(command, 0x02); /* username/password */ - } - - command->u[method_count_offset] = command->len - methods_start; - - sk_write(p->sub_socket, command->s, command->len); - strbuf_free(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. - */ - plug_closing(p->plug, p->closing_error_msg, p->closing_error_code); - return 0; /* ignored */ - } - - 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_constructor, p->accepting_ctx); - } - - if (change == PROXY_CHANGE_RECEIVE) { - /* we have received data from the underlying socket, which - * we'll need to parse, process, and respond to appropriately. - */ - - 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); - 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); - 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); - return 1; - } - - if (data[1] != 0) { - - plug_closing(p->plug, "Proxy error: SOCKS proxy refused" - " password authentication", - PROXY_ERROR_GENERAL); - 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] - */ - - strbuf *command = strbuf_new(); - put_byte(command, 5); /* SOCKS version 5 */ - put_byte(command, 1); /* CONNECT command */ - put_byte(command, 0x00); /* reserved byte */ - - switch (sk_addrtype(p->remote_addr)) { - case ADDRTYPE_IPV4: - put_byte(command, 1); /* IPv4 */ - sk_addrcopy(p->remote_addr, strbuf_append(command, 4)); - break; - case ADDRTYPE_IPV6: - put_byte(command, 4); /* IPv6 */ - sk_addrcopy(p->remote_addr, strbuf_append(command, 16)); - break; - case ADDRTYPE_NAME: { - char hostname[512]; - put_byte(command, 3); /* domain name */ - sk_getaddr(p->remote_addr, hostname, lenof(hostname)); - if (!put_pstring(command, hostname)) { - p->error = "Proxy error: SOCKS 5 cannot " - "support host names longer than 255 chars"; - strbuf_free(command); - return 1; - } - break; - } - } - - put_uint16(command, p->remote_port); - - sk_write(p->sub_socket, command->s, command->len); - - strbuf_free(command); - - 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); - 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); - - 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 += 1+(unsigned char)data[4]; break; /* domain name */ - default: - plug_closing(p->plug, "Proxy error: SOCKS proxy returned " - "unrecognised address format", - PROXY_ERROR_GENERAL); - 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); - return 1; - } - - if (p->state == 5) { - const char *username = conf_get_str(p->conf, CONF_proxy_username); - const char *password = conf_get_str(p->conf, CONF_proxy_password); - if (username[0] || password[0]) { - strbuf *auth = strbuf_new_nm(); - put_byte(auth, 1); /* version number of subnegotiation */ - if (!put_pstring(auth, username)) { - p->error = "Proxy error: SOCKS 5 authentication cannot " - "support usernames longer than 255 chars"; - strbuf_free(auth); - return 1; - } - if (!put_pstring(auth, password)) { - p->error = "Proxy error: SOCKS 5 authentication cannot " - "support passwords longer than 255 chars"; - strbuf_free(auth); - return 1; - } - sk_write(p->sub_socket, auth->s, auth->len); - strbuf_free(auth); - p->state = 7; - } else - plug_closing(p->plug, "Proxy error: Server chose " - "username/password authentication but we " - "didn't offer it!", - PROXY_ERROR_GENERAL); - 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); - 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, Conf *conf) -{ - char *fmt = conf_get_str(conf, CONF_proxy_telnet_command); - int so = 0, eo = 0; - strbuf *buf = strbuf_new(); - - /* we need to escape \\, \%, \r, \n, \t, \x??, \0???, - * %%, %host, %port, %user, and %pass - */ - - while (fmt[eo] != 0) { - - /* scan forward until we hit end-of-line, - * or an escape character (\ or %) */ - while (fmt[eo] != 0 && fmt[eo] != '%' && fmt[eo] != '\\') - eo++; - - /* if we hit eol, break out of our escaping loop */ - if (fmt[eo] == 0) break; - - /* if there was any unescaped text before the escape - * character, send that now */ - if (eo != so) - put_data(buf, fmt + so, eo - so); - - so = eo++; - - /* if the escape character was the last character of - * the line, we'll just stop and send it. */ - if (fmt[eo] == 0) break; - - if (fmt[so] == '\\') { - - /* we recognize \\, \%, \r, \n, \t, \x??. - * anything else, we just send unescaped (including the \). - */ - - switch (fmt[eo]) { - - case '\\': - put_byte(buf, '\\'); - eo++; - break; - - case '%': - put_byte(buf, '%'); - eo++; - break; - - case 'r': - put_byte(buf, '\r'); - eo++; - break; - - case 'n': - put_byte(buf, '\n'); - eo++; - break; - - case 't': - put_byte(buf, '\t'); - eo++; - break; - - case 'x': - case 'X': { - /* escaped hexadecimal value (ie. \xff) */ - unsigned char v = 0; - int i = 0; - - for (;;) { - eo++; - if (fmt[eo] >= '0' && fmt[eo] <= '9') - v += fmt[eo] - '0'; - else if (fmt[eo] >= 'a' && fmt[eo] <= 'f') - v += fmt[eo] - 'a' + 10; - else if (fmt[eo] >= 'A' && fmt[eo] <= 'F') - v += fmt[eo] - 'A' + 10; - else { - /* non hex character, so we abort and just - * send the whole thing unescaped (including \x) - */ - put_byte(buf, '\\'); - eo = so + 1; - break; - } - - /* we only extract two hex characters */ - if (i == 1) { - put_byte(buf, v); - eo++; - break; - } - - i++; - v <<= 4; - } - break; - } - - default: - put_data(buf, fmt + so, 2); - eo++; - break; - } - } else { - - /* % escape. we recognize %%, %host, %port, %user, %pass. - * %proxyhost, %proxyport. Anything else we just send - * unescaped (including the %). - */ - - if (fmt[eo] == '%') { - put_byte(buf, '%'); - eo++; - } - else if (strnicmp(fmt + eo, "host", 4) == 0) { - char dest[512]; - sk_getaddr(addr, dest, lenof(dest)); - put_data(buf, dest, strlen(dest)); - eo += 4; - } - else if (strnicmp(fmt + eo, "port", 4) == 0) { - strbuf_catf(buf, "%d", port); - eo += 4; - } - else if (strnicmp(fmt + eo, "user", 4) == 0) { - const char *username = conf_get_str(conf, CONF_proxy_username); - put_data(buf, username, strlen(username)); - eo += 4; - } - else if (strnicmp(fmt + eo, "pass", 4) == 0) { - const char *password = conf_get_str(conf, CONF_proxy_password); - put_data(buf, password, strlen(password)); - eo += 4; - } - else if (strnicmp(fmt + eo, "proxyhost", 9) == 0) { - const char *host = conf_get_str(conf, CONF_proxy_host); - put_data(buf, host, strlen(host)); - eo += 9; - } - else if (strnicmp(fmt + eo, "proxyport", 9) == 0) { - int port = conf_get_int(conf, CONF_proxy_port); - strbuf_catf(buf, "%d", port); - 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. - */ - put_byte(buf, '%'); - } - } - - /* 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) { - put_data(buf, fmt + so, eo - so); - } - - return strbuf_to_str(buf); -} - -int proxy_telnet_negotiate (ProxySocket *p, int change) -{ - if (p->state == PROXY_CHANGE_NEW) { - char *formatted_cmd; - - formatted_cmd = format_telnet_command(p->remote_addr, p->remote_port, - p->conf); - - { - /* - * Re-escape control chars in the command, for logging. - */ - char *reescaped = snewn(4*strlen(formatted_cmd) + 1, char); - const char *in; - char *out; - char *logmsg; - - for (in = formatted_cmd, out = reescaped; *in; in++) { - if (*in == '\n') { - *out++ = '\\'; *out++ = 'n'; - } else if (*in == '\r') { - *out++ = '\\'; *out++ = 'r'; - } else if (*in == '\t') { - *out++ = '\\'; *out++ = 't'; - } else if (*in == '\\') { - *out++ = '\\'; *out++ = '\\'; - } else if ((unsigned)(((unsigned char)*in) - 0x20) < - (0x7F-0x20)) { - *out++ = *in; - } else { - out += sprintf(out, "\\x%02X", (unsigned)*in & 0xFF); - } - } - *out = '\0'; - - logmsg = dupprintf("Sending Telnet proxy command: %s", reescaped); - plug_log(p->plug, PLUGLOG_PROXY_MSG, NULL, 0, logmsg, 0); - sfree(logmsg); - sfree(reescaped); - } - - 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. - */ - plug_closing(p->plug, p->closing_error_msg, p->closing_error_code); - return 0; /* ignored */ - } - - 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_constructor, p->accepting_ctx); - } - - if (change == PROXY_CHANGE_RECEIVE) { - /* we have received data from the underlying socket, which - * we'll need to parse, process, and respond to appropriately. - */ - - /* 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); - return 1; -} diff --git a/proxy.h b/proxy.h deleted file mode 100644 index 9dca87d1..00000000 --- a/proxy.h +++ /dev/null @@ -1,110 +0,0 @@ -/* - * 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 ProxySocket ProxySocket; - -struct ProxySocket { - const char *error; - - Socket *sub_socket; - Plug *plug; - SockAddr *remote_addr; - int remote_port; - - bufchain pending_output_data; - bufchain pending_oob_output_data; - bufchain pending_input_data; - bool pending_eof; - -#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. - */ - bool 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) (ProxySocket * /* 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; - - /* receive */ - bool receive_urgent; - const char *receive_data; - int receive_len; - - /* accepting */ - accept_fn_t accepting_constructor; - accept_ctx_t accepting_ctx; - - /* configuration, used to look up proxy settings */ - Conf *conf; - - /* CHAP transient data */ - int chap_num_attributes; - int chap_num_attributes_processed; - int chap_current_attribute; - int chap_current_datalen; - - Socket sock; - Plug plugimpl; -}; - -extern void proxy_activate (ProxySocket *); - -extern int proxy_http_negotiate (ProxySocket *, int); -extern int proxy_telnet_negotiate (ProxySocket *, int); -extern int proxy_socks4_negotiate (ProxySocket *, int); -extern int proxy_socks5_negotiate (ProxySocket *, int); - -/* - * This may be reused by local-command proxies on individual - * platforms. - */ -char *format_telnet_command(SockAddr *addr, int port, Conf *conf); - -/* - * These are implemented in cproxy.c or nocproxy.c, depending on - * whether encrypted proxy authentication is available. - */ -extern void proxy_socks5_offerencryptedauth(BinarySink *); -extern int proxy_socks5_handlechap (ProxySocket *); -extern int proxy_socks5_selectchap(ProxySocket *); - -#endif diff --git a/proxy/cproxy.c b/proxy/cproxy.c new file mode 100644 index 00000000..d187ffec --- /dev/null +++ b/proxy/cproxy.c @@ -0,0 +1,179 @@ +/* + * 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 +#include +#include + +#include "putty.h" +#include "ssh.h" /* For MD5 support */ +#include "network.h" +#include "proxy.h" +#include "marshal.h" + +static void hmacmd5_chap(const unsigned char *challenge, int challen, + const char *passwd, unsigned char *response) +{ + mac_simple(&ssh_hmac_md5, ptrlen_from_asciz(passwd), + make_ptrlen(challenge, challen), response); +} + +void proxy_socks5_offerencryptedauth(BinarySink *bs) +{ + put_byte(bs, 0x03); /* CHAP */ +} + +int proxy_socks5_handlechap (ProxySocket *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); + return 1; + } + if (data[1] == 0x00) { + plug_closing(p->plug, "Proxy error: SOCKS proxy won't" + " negotiate CHAP with us", + PROXY_ERROR_GENERAL); + 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); + 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, + conf_get_str(p->conf, CONF_proxy_password), + &outbuf[4]); + sk_write(p->sub_socket, 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); + 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(ProxySocket *p) +{ + char *username = conf_get_str(p->conf, CONF_proxy_username); + char *password = conf_get_str(p->conf, CONF_proxy_password); + if (username[0] || password[0]) { + char chapbuf[514]; + int ulen; + chapbuf[0] = '\x01'; /* Version */ + chapbuf[1] = '\x02'; /* Number of attributes sent */ + chapbuf[2] = '\x11'; /* First attribute - algorithms list */ + chapbuf[3] = '\x01'; /* Only one CHAP algorithm */ + chapbuf[4] = '\x85'; /* ...and it's HMAC-MD5, the core one */ + chapbuf[5] = '\x02'; /* Second attribute - username */ + + ulen = strlen(username); + if (ulen > 255) ulen = 255; + if (ulen < 1) ulen = 1; + + chapbuf[6] = ulen; + memcpy(chapbuf+7, username, ulen); + + sk_write(p->sub_socket, chapbuf, ulen + 7); + p->chap_num_attributes = 0; + p->chap_num_attributes_processed = 0; + p->chap_current_attribute = -1; + 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); + return 1; +} diff --git a/proxy/nocproxy.c b/proxy/nocproxy.c new file mode 100644 index 00000000..cdc28a39 --- /dev/null +++ b/proxy/nocproxy.c @@ -0,0 +1,35 @@ +/* + * 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 +#include +#include + +#include "putty.h" +#include "network.h" +#include "proxy.h" + +void proxy_socks5_offerencryptedauth(BinarySink *bs) +{ + /* For telnet, don't add any new encrypted authentication routines */ +} + +int proxy_socks5_handlechap (ProxySocket *p) +{ + + plug_closing(p->plug, "Proxy error: Trying to handle a SOCKS5 CHAP request" + " in telnet-only build", + PROXY_ERROR_GENERAL); + return 1; +} + +int proxy_socks5_selectchap(ProxySocket *p) +{ + plug_closing(p->plug, "Proxy error: Trying to handle a SOCKS5 CHAP request" + " in telnet-only build", + PROXY_ERROR_GENERAL); + return 1; +} diff --git a/proxy/noproxy.c b/proxy/noproxy.c new file mode 100644 index 00000000..82347d51 --- /dev/null +++ b/proxy/noproxy.c @@ -0,0 +1,32 @@ +/* + * noproxy.c: an alternative to proxy.c, for use by auxiliary programs + * that need to make network connections but don't want to include all + * the full-on support for endless network proxies (and its + * configuration requirements). Implements the primary APIs of + * proxy.c, but maps them straight to the underlying network layer. + */ + +#include "putty.h" +#include "network.h" +#include "proxy.h" + +SockAddr *name_lookup(const char *host, int port, char **canonicalname, + Conf *conf, int addressfamily, LogContext *logctx, + const char *reason) +{ + return sk_namelookup(host, canonicalname, addressfamily); +} + +Socket *new_connection(SockAddr *addr, const char *hostname, + int port, bool privport, + bool oobinline, bool nodelay, bool keepalive, + Plug *plug, Conf *conf, LogPolicy *lp, Seat **seat) +{ + return sk_new(addr, port, privport, oobinline, nodelay, keepalive, plug); +} + +Socket *new_listener(const char *srcaddr, int port, Plug *plug, + bool local_host_only, Conf *conf, int addressfamily) +{ + return sk_newlistener(srcaddr, port, plug, local_host_only, addressfamily); +} diff --git a/proxy/nosshproxy.c b/proxy/nosshproxy.c new file mode 100644 index 00000000..5f2bbdca --- /dev/null +++ b/proxy/nosshproxy.c @@ -0,0 +1,17 @@ +/* + * nosshproxy.c: stub implementation of sshproxy_new_connection(). + */ + +#include "putty.h" +#include "network.h" + +const bool ssh_proxy_supported = false; + +Socket *sshproxy_new_connection(SockAddr *addr, const char *hostname, + int port, bool privport, + bool oobinline, bool nodelay, bool keepalive, + Plug *plug, Conf *conf, + LogPolicy *clientlp, Seat **clientseat) +{ + return NULL; +} diff --git a/proxy/pproxy.c b/proxy/pproxy.c new file mode 100644 index 00000000..4b08606e --- /dev/null +++ b/proxy/pproxy.c @@ -0,0 +1,17 @@ +/* + * 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, const char *hostname, + int port, int privport, + int oobinline, int nodelay, int keepalive, + Plug *plug, Conf *conf) +{ + return NULL; +} diff --git a/proxy/proxy.c b/proxy/proxy.c new file mode 100644 index 00000000..08d5afba --- /dev/null +++ b/proxy/proxy.c @@ -0,0 +1,1515 @@ +/* + * Network proxy abstraction in PuTTY + * + * A proxy layer, if necessary, wedges itself between the network + * code and the higher level backend. + */ + +#include +#include +#include + +#include "putty.h" +#include "network.h" +#include "proxy.h" + +#define do_proxy_dns(conf) \ + (conf_get_int(conf, CONF_proxy_dns) == FORCE_ON || \ + (conf_get_int(conf, CONF_proxy_dns) == AUTO && \ + conf_get_int(conf, CONF_proxy_type) != PROXY_SOCKS4)) + +/* + * Call this when proxy negotiation is complete, so that this + * socket can begin working normally. + */ +void proxy_activate (ProxySocket *p) +{ + size_t output_before, output_after; + + p->state = PROXY_STATE_ACTIVE; + + plug_log(p->plug, PLUGLOG_CONNECT_SUCCESS, NULL, 0, NULL, 0); + + /* we want to ignore new receive events until we have sent + * all of our buffered receive data. + */ + sk_set_frozen(p->sub_socket, true); + + /* 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) { + ptrlen data = bufchain_prefix(&p->pending_oob_output_data); + output_after += sk_write_oob(p->sub_socket, data.ptr, data.len); + bufchain_consume(&p->pending_oob_output_data, data.len); + } + + /* send buffered normal writes */ + while (bufchain_size(&p->pending_output_data) > 0) { + ptrlen data = bufchain_prefix(&p->pending_output_data); + output_after += sk_write(p->sub_socket, data.ptr, data.len); + bufchain_consume(&p->pending_output_data, 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 have a pending EOF to send, send it */ + if (p->pending_eof) sk_write_eof(p->sub_socket); + + /* if the backend wanted the socket unfrozen, try to unfreeze. + * our set_frozen handler will flush buffered receive data before + * unfreezing the actual underlying socket. + */ + if (!p->freeze) + sk_set_frozen(&p->sock, false); +} + +/* basic proxy socket functions */ + +static Plug *sk_proxy_plug (Socket *s, Plug *p) +{ + ProxySocket *ps = container_of(s, ProxySocket, sock); + Plug *ret = ps->plug; + if (p) + ps->plug = p; + return ret; +} + +static void sk_proxy_close (Socket *s) +{ + ProxySocket *ps = container_of(s, ProxySocket, sock); + + sk_close(ps->sub_socket); + sk_addr_free(ps->remote_addr); + sfree(ps); +} + +static size_t sk_proxy_write (Socket *s, const void *data, size_t len) +{ + ProxySocket *ps = container_of(s, ProxySocket, sock); + + 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 size_t sk_proxy_write_oob (Socket *s, const void *data, size_t len) +{ + ProxySocket *ps = container_of(s, ProxySocket, sock); + + 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_write_eof (Socket *s) +{ + ProxySocket *ps = container_of(s, ProxySocket, sock); + + if (ps->state != PROXY_STATE_ACTIVE) { + ps->pending_eof = true; + return; + } + sk_write_eof(ps->sub_socket); +} + +static void sk_proxy_set_frozen (Socket *s, bool is_frozen) +{ + ProxySocket *ps = container_of(s, ProxySocket, sock); + + 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) { + char databuf[512]; + ptrlen data = bufchain_prefix(&ps->pending_input_data); + if (data.len > lenof(databuf)) + data.len = lenof(databuf); + memcpy(databuf, data.ptr, data.len); + bufchain_consume(&ps->pending_input_data, data.len); + plug_receive(ps->plug, 0, databuf, data.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) +{ + ProxySocket *ps = container_of(s, ProxySocket, sock); + 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, PlugLogType type, SockAddr *addr, + int port, const char *error_msg, int error_code) +{ + ProxySocket *ps = container_of(plug, ProxySocket, plugimpl); + + plug_log(ps->plug, type, addr, port, error_msg, error_code); +} + +static void plug_proxy_closing(Plug *p, const char *error_msg, int error_code) +{ + ProxySocket *ps = container_of(p, ProxySocket, plugimpl); + + if (ps->state != PROXY_STATE_ACTIVE) { + ps->closing_error_msg = error_msg; + ps->closing_error_code = error_code; + ps->negotiate(ps, PROXY_CHANGE_CLOSING); + } else { + plug_closing(ps->plug, error_msg, error_code); + } +} + +static void plug_proxy_receive( + Plug *p, int urgent, const char *data, size_t len) +{ + ProxySocket *ps = container_of(p, ProxySocket, plugimpl); + + 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 != 0); + ps->receive_data = data; + ps->receive_len = len; + ps->negotiate(ps, PROXY_CHANGE_RECEIVE); + } else { + plug_receive(ps->plug, urgent, data, len); + } +} + +static void plug_proxy_sent (Plug *p, size_t bufsize) +{ + ProxySocket *ps = container_of(p, ProxySocket, plugimpl); + + if (ps->state != PROXY_STATE_ACTIVE) { + ps->negotiate(ps, PROXY_CHANGE_SENT); + return; + } + plug_sent(ps->plug, bufsize); +} + +static int plug_proxy_accepting(Plug *p, + accept_fn_t constructor, accept_ctx_t ctx) +{ + ProxySocket *ps = container_of(p, ProxySocket, plugimpl); + + if (ps->state != PROXY_STATE_ACTIVE) { + ps->accepting_constructor = constructor; + ps->accepting_ctx = ctx; + return ps->negotiate(ps, PROXY_CHANGE_ACCEPTING); + } + return plug_accepting(ps->plug, constructor, ctx); +} + +/* + * This function can accept a NULL pointer as `addr', in which case + * it will only check the host name. + */ +static bool proxy_for_destination(SockAddr *addr, const char *hostname, + int port, Conf *conf) +{ + int s = 0, e = 0; + char hostip[64]; + int hostip_len, hostname_len; + const char *exclude_list; + + /* + * Special local connections such as Unix-domain sockets + * unconditionally cannot be proxied, even in proxy-localhost + * mode. There just isn't any way to ask any known proxy type for + * them. + */ + if (addr && sk_address_is_special_local(addr)) + return false; /* do not proxy */ + + /* + * Check the host name and IP against the hard-coded + * representations of `localhost'. + */ + if (!conf_get_bool(conf, CONF_even_proxy_localhost) && + (sk_hostname_is_local(hostname) || + (addr && sk_address_is_local(addr)))) + return false; /* 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 = conf_get_str(conf, CONF_proxy_exclude_list); + + /* now parse the exclude list, and see if either our IP + * or hostname matches anything in it. + */ + + 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) { + /* IP/hostname range excluded. do not use proxy. */ + return false; + } + } 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) { + /* IP/hostname range excluded. do not use proxy. */ + return false; + } + } 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 false; /* IP/hostname excluded. do not use proxy. */ + if (strnicmp(hostname, exclude_list + s, e - s) == 0) + return false; /* 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 true; +} + +static char *dns_log_msg(const char *host, int addressfamily, + const char *reason) +{ + return dupprintf("Looking up host \"%s\"%s for %s", host, + (addressfamily == ADDRTYPE_IPV4 ? " (IPv4)" : + addressfamily == ADDRTYPE_IPV6 ? " (IPv6)" : + ""), reason); +} + +SockAddr *name_lookup(const char *host, int port, char **canonicalname, + Conf *conf, int addressfamily, LogContext *logctx, + const char *reason) +{ + if (conf_get_int(conf, CONF_proxy_type) != PROXY_NONE && + do_proxy_dns(conf) && + proxy_for_destination(NULL, host, port, conf)) { + + if (logctx) + logeventf(logctx, "Leaving host lookup to proxy of \"%s\"" + " (for %s)", host, reason); + + *canonicalname = dupstr(host); + return sk_nonamelookup(host); + } else { + if (logctx) + logevent_and_free( + logctx, dns_log_msg(host, addressfamily, reason)); + + return sk_namelookup(host, canonicalname, addressfamily); + } +} + +static const SocketVtable ProxySocket_sockvt = { + .plug = sk_proxy_plug, + .close = sk_proxy_close, + .write = sk_proxy_write, + .write_oob = sk_proxy_write_oob, + .write_eof = sk_proxy_write_eof, + .set_frozen = sk_proxy_set_frozen, + .socket_error = sk_proxy_socket_error, + .peer_info = NULL, +}; + +static const PlugVtable ProxySocket_plugvt = { + .log = plug_proxy_log, + .closing = plug_proxy_closing, + .receive = plug_proxy_receive, + .sent = plug_proxy_sent, + .accepting = plug_proxy_accepting +}; + +Socket *new_connection(SockAddr *addr, const char *hostname, + int port, bool privport, + bool oobinline, bool nodelay, bool keepalive, + Plug *plug, Conf *conf, LogPolicy *lp, Seat **seat) +{ + int type = conf_get_int(conf, CONF_proxy_type); + + if (type != PROXY_NONE && + proxy_for_destination(addr, hostname, port, conf)) + { + ProxySocket *ret; + SockAddr *proxy_addr; + char *proxy_canonical_name; + const char *proxy_type; + Socket *sret; + + if (type == PROXY_SSH && + (sret = sshproxy_new_connection(addr, hostname, port, privport, + oobinline, nodelay, keepalive, + plug, conf, lp, seat)) != NULL) + return sret; + + if ((sret = platform_new_connection(addr, hostname, port, privport, + oobinline, nodelay, keepalive, + plug, conf)) != NULL) + return sret; + + ret = snew(ProxySocket); + ret->sock.vt = &ProxySocket_sockvt; + ret->plugimpl.vt = &ProxySocket_plugvt; + ret->conf = conf_copy(conf); + ret->plug = plug; + ret->remote_addr = addr; /* will need to be freed on close */ + ret->remote_port = port; + + ret->error = NULL; + ret->pending_eof = false; + ret->freeze = false; + + 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 (type == PROXY_HTTP) { + ret->negotiate = proxy_http_negotiate; + proxy_type = "HTTP"; + } else if (type == PROXY_SOCKS4) { + ret->negotiate = proxy_socks4_negotiate; + proxy_type = "SOCKS 4"; + } else if (type == PROXY_SOCKS5) { + ret->negotiate = proxy_socks5_negotiate; + proxy_type = "SOCKS 5"; + } else if (type == PROXY_TELNET) { + ret->negotiate = proxy_telnet_negotiate; + proxy_type = "Telnet"; + } else { + ret->error = "Proxy error: Unknown proxy method"; + return &ret->sock; + } + + { + char *logmsg = dupprintf("Will use %s proxy at %s:%d to connect" + " to %s:%d", proxy_type, + conf_get_str(conf, CONF_proxy_host), + conf_get_int(conf, CONF_proxy_port), + hostname, port); + plug_log(plug, PLUGLOG_PROXY_MSG, NULL, 0, logmsg, 0); + sfree(logmsg); + } + + { + char *logmsg = dns_log_msg(conf_get_str(conf, CONF_proxy_host), + conf_get_int(conf, CONF_addressfamily), + "proxy"); + plug_log(plug, PLUGLOG_PROXY_MSG, NULL, 0, logmsg, 0); + sfree(logmsg); + } + + /* look-up proxy */ + proxy_addr = sk_namelookup(conf_get_str(conf, CONF_proxy_host), + &proxy_canonical_name, + conf_get_int(conf, CONF_addressfamily)); + if (sk_addr_error(proxy_addr) != NULL) { + ret->error = "Proxy error: Unable to resolve proxy host name"; + sk_addr_free(proxy_addr); + return &ret->sock; + } + sfree(proxy_canonical_name); + + { + char addrbuf[256], *logmsg; + sk_getaddr(proxy_addr, addrbuf, lenof(addrbuf)); + logmsg = dupprintf("Connecting to %s proxy at %s port %d", + proxy_type, addrbuf, + conf_get_int(conf, CONF_proxy_port)); + plug_log(plug, PLUGLOG_PROXY_MSG, NULL, 0, logmsg, 0); + sfree(logmsg); + } + + /* create the actual socket we will be using, + * connected to our proxy server and port. + */ + ret->sub_socket = sk_new(proxy_addr, + conf_get_int(conf, CONF_proxy_port), + privport, oobinline, + nodelay, keepalive, &ret->plugimpl); + if (sk_socket_error(ret->sub_socket) != NULL) + return &ret->sock; + + /* start the proxy negotiation process... */ + sk_set_frozen(ret->sub_socket, false); + ret->negotiate(ret, PROXY_CHANGE_NEW); + + return &ret->sock; + } + + /* no proxy, so just return the direct socket */ + return sk_new(addr, port, privport, oobinline, nodelay, keepalive, plug); +} + +Socket *new_listener(const char *srcaddr, int port, Plug *plug, + bool local_host_only, Conf *conf, 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 bool get_line_end(char *data, size_t len, size_t *out) +{ + size_t 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) { + *out = off; + return true; + } + + /* 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') { + *out = off; + return true; + } + + /* the line does continue, so we have to keep going + * until we see an the header's "real" end of line. + */ + off++; + } + + off++; + } + + return false; +} + +int proxy_http_negotiate (ProxySocket *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]; + char *username, *password; + + sk_getaddr(p->remote_addr, dest, lenof(dest)); + + buf = dupprintf("CONNECT %s:%i HTTP/1.1\r\nHost: %s:%i\r\n", + dest, p->remote_port, dest, p->remote_port); + sk_write(p->sub_socket, buf, strlen(buf)); + sfree(buf); + + username = conf_get_str(p->conf, CONF_proxy_username); + password = conf_get_str(p->conf, CONF_proxy_password); + if (username[0] || password[0]) { + char *buf, *buf2; + int i, j, len; + buf = dupprintf("%s:%s", username, password); + len = strlen(buf); + buf2 = snewn(len * 4 / 3 + 100, char); + sprintf(buf2, "Proxy-Authorization: Basic "); + for (i = 0, j = strlen(buf2); i < len; i += 3, j += 4) + base64_encode_atom((unsigned char *)(buf+i), + (len-i > 3 ? 3 : len-i), buf2+j); + strcpy(buf2+j, "\r\n"); + sk_write(p->sub_socket, buf2, strlen(buf2)); + sfree(buf); + sfree(buf2); + } + + sk_write(p->sub_socket, "\r\n", 2); + + p->state = 1; + 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. + */ + plug_closing(p->plug, p->closing_error_msg, p->closing_error_code); + return 0; /* ignored */ + } + + 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_constructor, p->accepting_ctx); + } + + if (change == PROXY_CHANGE_RECEIVE) { + /* we have received data from the underlying socket, which + * we'll need to parse, process, and respond to appropriately. + */ + + char *data, *datap; + size_t len, 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'; + + if (!get_line_end(data, len, &eol)) { + 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); + 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); + 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); + + if (!get_line_end(datap, len, &eol)) { + sfree(data); + return 1; + } + while (eol > 2) { + bufchain_consume(&p->pending_input_data, eol); + datap += eol; + len -= eol; + if (!get_line_end(datap, len, &eol)) + eol = 0; /* terminate the loop */ + } + + 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); + return 1; +} + +/* ---------------------------------------------------------------------- + * SOCKS proxy type. + */ + +/* SOCKS version 4 */ +int proxy_socks4_negotiate (ProxySocket *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) + */ + + strbuf *command = strbuf_new(); + char hostname[512]; + bool write_hostname = false; + + put_byte(command, 4); /* SOCKS version 4 */ + put_byte(command, 1); /* CONNECT command */ + put_uint16(command, p->remote_port); + + switch (sk_addrtype(p->remote_addr)) { + case ADDRTYPE_IPV4: { + char addr[4]; + sk_addrcopy(p->remote_addr, addr); + put_data(command, addr, 4); + break; + } + case ADDRTYPE_NAME: + sk_getaddr(p->remote_addr, hostname, lenof(hostname)); + put_uint32(command, 1); + write_hostname = true; + break; + case ADDRTYPE_IPV6: + p->error = "Proxy error: SOCKS version 4 does not support IPv6"; + strbuf_free(command); + return 1; + } + + put_asciz(command, conf_get_str(p->conf, CONF_proxy_username)); + if (write_hostname) + put_asciz(command, hostname); + sk_write(p->sub_socket, command->s, command->len); + strbuf_free(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. + */ + plug_closing(p->plug, p->closing_error_msg, p->closing_error_code); + return 0; /* ignored */ + } + + 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_constructor, p->accepting_ctx); + } + + if (change == PROXY_CHANGE_RECEIVE) { + /* we have received data from the underlying socket, which + * we'll need to parse, process, and respond to appropriately. + */ + + 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); + 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); + break; + case 93: + plug_closing(p->plug, "Proxy error: Username and IDENTD on client don't agree", + PROXY_ERROR_GENERAL); + break; + case 91: + default: + plug_closing(p->plug, "Proxy error: Error while communicating with proxy", + PROXY_ERROR_GENERAL); + 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); + return 1; +} + +/* SOCKS version 5 */ +int proxy_socks5_negotiate (ProxySocket *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 + */ + + strbuf *command; + char *username, *password; + int method_count_offset, methods_start; + + command = strbuf_new(); + put_byte(command, 5); /* SOCKS version 5 */ + username = conf_get_str(p->conf, CONF_proxy_username); + password = conf_get_str(p->conf, CONF_proxy_password); + + method_count_offset = command->len; + put_byte(command, 0); + methods_start = command->len; + + put_byte(command, 0x00); /* no authentication */ + + if (username[0] || password[0]) { + proxy_socks5_offerencryptedauth(BinarySink_UPCAST(command)); + put_byte(command, 0x02); /* username/password */ + } + + command->u[method_count_offset] = command->len - methods_start; + + sk_write(p->sub_socket, command->s, command->len); + strbuf_free(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. + */ + plug_closing(p->plug, p->closing_error_msg, p->closing_error_code); + return 0; /* ignored */ + } + + 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_constructor, p->accepting_ctx); + } + + if (change == PROXY_CHANGE_RECEIVE) { + /* we have received data from the underlying socket, which + * we'll need to parse, process, and respond to appropriately. + */ + + 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); + 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); + 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); + return 1; + } + + if (data[1] != 0) { + + plug_closing(p->plug, "Proxy error: SOCKS proxy refused" + " password authentication", + PROXY_ERROR_GENERAL); + 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] + */ + + strbuf *command = strbuf_new(); + put_byte(command, 5); /* SOCKS version 5 */ + put_byte(command, 1); /* CONNECT command */ + put_byte(command, 0x00); /* reserved byte */ + + switch (sk_addrtype(p->remote_addr)) { + case ADDRTYPE_IPV4: + put_byte(command, 1); /* IPv4 */ + sk_addrcopy(p->remote_addr, strbuf_append(command, 4)); + break; + case ADDRTYPE_IPV6: + put_byte(command, 4); /* IPv6 */ + sk_addrcopy(p->remote_addr, strbuf_append(command, 16)); + break; + case ADDRTYPE_NAME: { + char hostname[512]; + put_byte(command, 3); /* domain name */ + sk_getaddr(p->remote_addr, hostname, lenof(hostname)); + if (!put_pstring(command, hostname)) { + p->error = "Proxy error: SOCKS 5 cannot " + "support host names longer than 255 chars"; + strbuf_free(command); + return 1; + } + break; + } + } + + put_uint16(command, p->remote_port); + + sk_write(p->sub_socket, command->s, command->len); + + strbuf_free(command); + + 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); + 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); + + 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 += 1+(unsigned char)data[4]; break; /* domain name */ + default: + plug_closing(p->plug, "Proxy error: SOCKS proxy returned " + "unrecognised address format", + PROXY_ERROR_GENERAL); + 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); + return 1; + } + + if (p->state == 5) { + const char *username = conf_get_str(p->conf, CONF_proxy_username); + const char *password = conf_get_str(p->conf, CONF_proxy_password); + if (username[0] || password[0]) { + strbuf *auth = strbuf_new_nm(); + put_byte(auth, 1); /* version number of subnegotiation */ + if (!put_pstring(auth, username)) { + p->error = "Proxy error: SOCKS 5 authentication cannot " + "support usernames longer than 255 chars"; + strbuf_free(auth); + return 1; + } + if (!put_pstring(auth, password)) { + p->error = "Proxy error: SOCKS 5 authentication cannot " + "support passwords longer than 255 chars"; + strbuf_free(auth); + return 1; + } + sk_write(p->sub_socket, auth->s, auth->len); + strbuf_free(auth); + p->state = 7; + } else + plug_closing(p->plug, "Proxy error: Server chose " + "username/password authentication but we " + "didn't offer it!", + PROXY_ERROR_GENERAL); + 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); + 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, Conf *conf) +{ + char *fmt = conf_get_str(conf, CONF_proxy_telnet_command); + int so = 0, eo = 0; + strbuf *buf = strbuf_new(); + + /* we need to escape \\, \%, \r, \n, \t, \x??, \0???, + * %%, %host, %port, %user, and %pass + */ + + while (fmt[eo] != 0) { + + /* scan forward until we hit end-of-line, + * or an escape character (\ or %) */ + while (fmt[eo] != 0 && fmt[eo] != '%' && fmt[eo] != '\\') + eo++; + + /* if we hit eol, break out of our escaping loop */ + if (fmt[eo] == 0) break; + + /* if there was any unescaped text before the escape + * character, send that now */ + if (eo != so) + put_data(buf, fmt + so, eo - so); + + so = eo++; + + /* if the escape character was the last character of + * the line, we'll just stop and send it. */ + if (fmt[eo] == 0) break; + + if (fmt[so] == '\\') { + + /* we recognize \\, \%, \r, \n, \t, \x??. + * anything else, we just send unescaped (including the \). + */ + + switch (fmt[eo]) { + + case '\\': + put_byte(buf, '\\'); + eo++; + break; + + case '%': + put_byte(buf, '%'); + eo++; + break; + + case 'r': + put_byte(buf, '\r'); + eo++; + break; + + case 'n': + put_byte(buf, '\n'); + eo++; + break; + + case 't': + put_byte(buf, '\t'); + eo++; + break; + + case 'x': + case 'X': { + /* escaped hexadecimal value (ie. \xff) */ + unsigned char v = 0; + int i = 0; + + for (;;) { + eo++; + if (fmt[eo] >= '0' && fmt[eo] <= '9') + v += fmt[eo] - '0'; + else if (fmt[eo] >= 'a' && fmt[eo] <= 'f') + v += fmt[eo] - 'a' + 10; + else if (fmt[eo] >= 'A' && fmt[eo] <= 'F') + v += fmt[eo] - 'A' + 10; + else { + /* non hex character, so we abort and just + * send the whole thing unescaped (including \x) + */ + put_byte(buf, '\\'); + eo = so + 1; + break; + } + + /* we only extract two hex characters */ + if (i == 1) { + put_byte(buf, v); + eo++; + break; + } + + i++; + v <<= 4; + } + break; + } + + default: + put_data(buf, fmt + so, 2); + eo++; + break; + } + } else { + + /* % escape. we recognize %%, %host, %port, %user, %pass. + * %proxyhost, %proxyport. Anything else we just send + * unescaped (including the %). + */ + + if (fmt[eo] == '%') { + put_byte(buf, '%'); + eo++; + } + else if (strnicmp(fmt + eo, "host", 4) == 0) { + char dest[512]; + sk_getaddr(addr, dest, lenof(dest)); + put_data(buf, dest, strlen(dest)); + eo += 4; + } + else if (strnicmp(fmt + eo, "port", 4) == 0) { + strbuf_catf(buf, "%d", port); + eo += 4; + } + else if (strnicmp(fmt + eo, "user", 4) == 0) { + const char *username = conf_get_str(conf, CONF_proxy_username); + put_data(buf, username, strlen(username)); + eo += 4; + } + else if (strnicmp(fmt + eo, "pass", 4) == 0) { + const char *password = conf_get_str(conf, CONF_proxy_password); + put_data(buf, password, strlen(password)); + eo += 4; + } + else if (strnicmp(fmt + eo, "proxyhost", 9) == 0) { + const char *host = conf_get_str(conf, CONF_proxy_host); + put_data(buf, host, strlen(host)); + eo += 9; + } + else if (strnicmp(fmt + eo, "proxyport", 9) == 0) { + int port = conf_get_int(conf, CONF_proxy_port); + strbuf_catf(buf, "%d", port); + 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. + */ + put_byte(buf, '%'); + } + } + + /* 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) { + put_data(buf, fmt + so, eo - so); + } + + return strbuf_to_str(buf); +} + +int proxy_telnet_negotiate (ProxySocket *p, int change) +{ + if (p->state == PROXY_CHANGE_NEW) { + char *formatted_cmd; + + formatted_cmd = format_telnet_command(p->remote_addr, p->remote_port, + p->conf); + + { + /* + * Re-escape control chars in the command, for logging. + */ + char *reescaped = snewn(4*strlen(formatted_cmd) + 1, char); + const char *in; + char *out; + char *logmsg; + + for (in = formatted_cmd, out = reescaped; *in; in++) { + if (*in == '\n') { + *out++ = '\\'; *out++ = 'n'; + } else if (*in == '\r') { + *out++ = '\\'; *out++ = 'r'; + } else if (*in == '\t') { + *out++ = '\\'; *out++ = 't'; + } else if (*in == '\\') { + *out++ = '\\'; *out++ = '\\'; + } else if ((unsigned)(((unsigned char)*in) - 0x20) < + (0x7F-0x20)) { + *out++ = *in; + } else { + out += sprintf(out, "\\x%02X", (unsigned)*in & 0xFF); + } + } + *out = '\0'; + + logmsg = dupprintf("Sending Telnet proxy command: %s", reescaped); + plug_log(p->plug, PLUGLOG_PROXY_MSG, NULL, 0, logmsg, 0); + sfree(logmsg); + sfree(reescaped); + } + + 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. + */ + plug_closing(p->plug, p->closing_error_msg, p->closing_error_code); + return 0; /* ignored */ + } + + 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_constructor, p->accepting_ctx); + } + + if (change == PROXY_CHANGE_RECEIVE) { + /* we have received data from the underlying socket, which + * we'll need to parse, process, and respond to appropriately. + */ + + /* 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); + return 1; +} diff --git a/proxy/proxy.h b/proxy/proxy.h new file mode 100644 index 00000000..9dca87d1 --- /dev/null +++ b/proxy/proxy.h @@ -0,0 +1,110 @@ +/* + * 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 ProxySocket ProxySocket; + +struct ProxySocket { + const char *error; + + Socket *sub_socket; + Plug *plug; + SockAddr *remote_addr; + int remote_port; + + bufchain pending_output_data; + bufchain pending_oob_output_data; + bufchain pending_input_data; + bool pending_eof; + +#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. + */ + bool 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) (ProxySocket * /* 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; + + /* receive */ + bool receive_urgent; + const char *receive_data; + int receive_len; + + /* accepting */ + accept_fn_t accepting_constructor; + accept_ctx_t accepting_ctx; + + /* configuration, used to look up proxy settings */ + Conf *conf; + + /* CHAP transient data */ + int chap_num_attributes; + int chap_num_attributes_processed; + int chap_current_attribute; + int chap_current_datalen; + + Socket sock; + Plug plugimpl; +}; + +extern void proxy_activate (ProxySocket *); + +extern int proxy_http_negotiate (ProxySocket *, int); +extern int proxy_telnet_negotiate (ProxySocket *, int); +extern int proxy_socks4_negotiate (ProxySocket *, int); +extern int proxy_socks5_negotiate (ProxySocket *, int); + +/* + * This may be reused by local-command proxies on individual + * platforms. + */ +char *format_telnet_command(SockAddr *addr, int port, Conf *conf); + +/* + * These are implemented in cproxy.c or nocproxy.c, depending on + * whether encrypted proxy authentication is available. + */ +extern void proxy_socks5_offerencryptedauth(BinarySink *); +extern int proxy_socks5_handlechap (ProxySocket *); +extern int proxy_socks5_selectchap(ProxySocket *); + +#endif diff --git a/proxy/sshproxy.c b/proxy/sshproxy.c new file mode 100644 index 00000000..ad274d57 --- /dev/null +++ b/proxy/sshproxy.c @@ -0,0 +1,616 @@ +/* + * sshproxy.c: implement a Socket type that talks to an entire + * subsidiary SSH connection (sometimes called a 'jump host'). + */ + +#include +#include + +#include "putty.h" +#include "ssh.h" +#include "network.h" +#include "storage.h" + +const bool ssh_proxy_supported = true; + +/* + * TODO for future work: + * + * All the interactive prompts we present to the main Seat - the host + * key and weak-crypto dialog boxes, and all prompts presented via the + * userpass_input system - need adjusting so that it's clear to the + * user _which_ SSH connection they come from. At the moment, you just + * get shown a host key fingerprint or a cryptic "login as:" prompt, + * and you have to guess which server you're currently supposed to be + * interpreting it relative to. + * + * If the user manually aborts the attempt to make the proxy SSH + * connection (e.g. by hitting ^C at a userpass prompt, or refusing to + * accept the proxy server's host key), then an assertion failure + * occurs, because the main backend receives an indication of + * connection failure that causes it to want to call + * seat_connection_fatal("Remote side unexpectedly closed network + * connection"), which fails an assertion in tempseat.c because that + * method of TempSeat expects never to be called. To fix this, I think + * we need to distinguish 'connection attempt unexpectedly failed, in + * a way the user needs to be told about' from 'connection attempt was + * aborted by deliberate user action, so the user already knows'. + */ + +typedef struct SshProxy { + char *errmsg; + Conf *conf; + LogContext *logctx; + Backend *backend; + LogPolicy *clientlp; + Seat *clientseat; + + ProxyStderrBuf psb; + Plug *plug; + + bool frozen; + bufchain ssh_to_socket; + bool rcvd_eof_ssh_to_socket, sent_eof_ssh_to_socket; + + SockAddr *addr; + int port; + + /* Traits implemented: we're a Socket from the point of view of + * the client connection, and a Seat from the POV of the SSH + * backend we instantiate. */ + Socket sock; + LogPolicy logpolicy; + Seat seat; +} SshProxy; + +static Plug *sshproxy_plug(Socket *s, Plug *p) +{ + SshProxy *sp = container_of(s, SshProxy, sock); + Plug *oldplug = sp->plug; + if (p) + sp->plug = p; + return oldplug; +} + +static void sshproxy_close(Socket *s) +{ + SshProxy *sp = container_of(s, SshProxy, sock); + + sk_addr_free(sp->addr); + sfree(sp->errmsg); + conf_free(sp->conf); + if (sp->backend) + backend_free(sp->backend); + if (sp->logctx) + log_free(sp->logctx); + bufchain_clear(&sp->ssh_to_socket); + + delete_callbacks_for_context(sp); + sfree(sp); +} + +static size_t sshproxy_write(Socket *s, const void *data, size_t len) +{ + SshProxy *sp = container_of(s, SshProxy, sock); + if (!sp->backend) + return 0; + backend_send(sp->backend, data, len); + return backend_sendbuffer(sp->backend); +} + +static size_t sshproxy_write_oob(Socket *s, const void *data, size_t len) +{ + /* + * oob data is treated as inband; nasty, but nothing really + * better we can do + */ + return sshproxy_write(s, data, len); +} + +static void sshproxy_write_eof(Socket *s) +{ + SshProxy *sp = container_of(s, SshProxy, sock); + if (!sp->backend) + return; + backend_special(sp->backend, SS_EOF, 0); +} + +static void try_send_ssh_to_socket(void *ctx); + +static void sshproxy_set_frozen(Socket *s, bool is_frozen) +{ + SshProxy *sp = container_of(s, SshProxy, sock); + sp->frozen = is_frozen; + if (!sp->frozen) + queue_toplevel_callback(try_send_ssh_to_socket, sp); +} + +static const char *sshproxy_socket_error(Socket *s) +{ + SshProxy *sp = container_of(s, SshProxy, sock); + return sp->errmsg; +} + +static SocketPeerInfo *sshproxy_peer_info(Socket *s) +{ + return NULL; +} + +static const SocketVtable SshProxy_sock_vt = { + .plug = sshproxy_plug, + .close = sshproxy_close, + .write = sshproxy_write, + .write_oob = sshproxy_write_oob, + .write_eof = sshproxy_write_eof, + .set_frozen = sshproxy_set_frozen, + .socket_error = sshproxy_socket_error, + .peer_info = sshproxy_peer_info, +}; + +static void sshproxy_eventlog(LogPolicy *lp, const char *event) +{ + SshProxy *sp = container_of(lp, SshProxy, logpolicy); + log_proxy_stderr(sp->plug, &sp->psb, event, strlen(event)); + log_proxy_stderr(sp->plug, &sp->psb, "\n", 1); +} + +static int sshproxy_askappend(LogPolicy *lp, Filename *filename, + void (*callback)(void *ctx, int result), + void *ctx) +{ + SshProxy *sp = container_of(lp, SshProxy, logpolicy); + + /* + * If we have access to the outer LogPolicy, pass on this request + * to the end user. + */ + if (sp->clientlp) + return lp_askappend(sp->clientlp, filename, callback, ctx); + + /* + * Otherwise, fall back to the safe noninteractive assumption. + */ + char *msg = dupprintf("Log file \"%s\" already exists; logging cancelled", + filename_to_str(filename)); + sshproxy_eventlog(lp, msg); + sfree(msg); + return 0; +} + +static void sshproxy_logging_error(LogPolicy *lp, const char *event) +{ + SshProxy *sp = container_of(lp, SshProxy, logpolicy); + + /* + * If we have access to the outer LogPolicy, pass on this request + * to it. + */ + if (sp->clientlp) { + lp_logging_error(sp->clientlp, event); + return; + } + + /* + * Otherwise, the best we can do is to put it in the outer SSH + * connection's Event Log. + */ + char *msg = dupprintf("Logging error: %s", event); + sshproxy_eventlog(lp, msg); + sfree(msg); +} + +static const LogPolicyVtable SshProxy_logpolicy_vt = { + .eventlog = sshproxy_eventlog, + .askappend = sshproxy_askappend, + .logging_error = sshproxy_logging_error, + .verbose = null_lp_verbose_no, +}; + +/* + * Function called when we encounter an error during connection setup that's + * likely to be the cause of terminating the proxy SSH connection. Putting it + * in the Event Log is useful on general principles; also putting it in + * sp->errmsg meaks that it will be passed back through plug_closing when the + * proxy SSH connection actually terminates, so that the end user will see + * what went wrong in the proxy connection. + */ +static void sshproxy_error(SshProxy *sp, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + char *msg = dupvprintf(fmt, ap); + va_end(ap); + + if (!sp->errmsg) + sp->errmsg = dupstr(msg); + + sshproxy_eventlog(&sp->logpolicy, msg); + sfree(msg); +} + +static void try_send_ssh_to_socket(void *ctx) +{ + SshProxy *sp = (SshProxy *)ctx; + + if (sp->frozen) + return; + + while (bufchain_size(&sp->ssh_to_socket)) { + ptrlen pl = bufchain_prefix(&sp->ssh_to_socket); + plug_receive(sp->plug, 0, pl.ptr, pl.len); + bufchain_consume(&sp->ssh_to_socket, pl.len); + } + + if (sp->rcvd_eof_ssh_to_socket && + !sp->sent_eof_ssh_to_socket) { + sp->sent_eof_ssh_to_socket = true; + plug_closing(sp->plug, sp->errmsg, 0); + } +} + +static void sshproxy_notify_session_started(Seat *seat) +{ + SshProxy *sp = container_of(seat, SshProxy, seat); + + if (sp->clientseat) + seat_set_trust_status(sp->clientseat, true); + + plug_log(sp->plug, PLUGLOG_CONNECT_SUCCESS, sp->addr, sp->port, NULL, 0); +} + +static size_t sshproxy_output(Seat *seat, SeatOutputType type, + const void *data, size_t len) +{ + SshProxy *sp = container_of(seat, SshProxy, seat); + if (type == SEAT_OUTPUT_AUTH_BANNER) { + if (sp->clientseat) { + /* + * If we have access to the outer Seat, pass the SSH login + * banner on to it. + */ + return seat_output(sp->clientseat, type, data, len); + } else { + return 0; + } + } else { + bufchain_add(&sp->ssh_to_socket, data, len); + try_send_ssh_to_socket(sp); + return bufchain_size(&sp->ssh_to_socket); + } +} + +static bool sshproxy_eof(Seat *seat) +{ + SshProxy *sp = container_of(seat, SshProxy, seat); + sp->rcvd_eof_ssh_to_socket = true; + try_send_ssh_to_socket(sp); + return false; +} + +static void sshproxy_sent(Seat *seat, size_t new_bufsize) +{ + SshProxy *sp = container_of(seat, SshProxy, seat); + plug_sent(sp->plug, new_bufsize); +} + +static void sshproxy_notify_remote_disconnect(Seat *seat) +{ + SshProxy *sp = container_of(seat, SshProxy, seat); + if (!sp->rcvd_eof_ssh_to_socket && !backend_connected(sp->backend)) + sshproxy_eof(seat); +} + +static int sshproxy_get_userpass_input(Seat *seat, prompts_t *p) +{ + SshProxy *sp = container_of(seat, SshProxy, seat); + + if (sp->clientseat) { + /* + * If we have access to the outer Seat, pass this prompt + * request on to it. FIXME: appropriately adjusted + */ + return seat_get_userpass_input(sp->clientseat, p); + } + + /* + * Otherwise, behave as if noninteractive (like plink -batch): + * reject all attempts to present a prompt to the user, and log in + * the Event Log to say why not. + */ + sshproxy_error(sp, "Unable to provide interactive authentication " + "requested by proxy SSH connection"); + return 0; +} + +static void sshproxy_connection_fatal_callback(void *vctx) +{ + SshProxy *sp = (SshProxy *)vctx; + plug_closing(sp->plug, sp->errmsg, 0); +} + +static void sshproxy_connection_fatal(Seat *seat, const char *message) +{ + SshProxy *sp = container_of(seat, SshProxy, seat); + if (!sp->errmsg) { + sp->errmsg = dupprintf( + "fatal error in proxy SSH connection: %s", message); + queue_toplevel_callback(sshproxy_connection_fatal_callback, sp); + } +} + +static int sshproxy_confirm_ssh_host_key( + Seat *seat, const char *host, int port, const char *keytype, + char *keystr, const char *keydisp, char **key_fingerprints, bool mismatch, + void (*callback)(void *ctx, int result), void *ctx) +{ + SshProxy *sp = container_of(seat, SshProxy, seat); + + if (sp->clientseat) { + /* + * If we have access to the outer Seat, pass this prompt + * request on to it. FIXME: appropriately adjusted + */ + return seat_confirm_ssh_host_key( + sp->clientseat, host, port, keytype, keystr, keydisp, + key_fingerprints, mismatch, callback, ctx); + } + + /* + * Otherwise, behave as if we're in batch mode, i.e. take the safe + * option in the absence of interactive confirmation, i.e. abort + * the connection. + */ + return 0; +} + +static int sshproxy_confirm_weak_crypto_primitive( + Seat *seat, const char *algtype, const char *algname, + void (*callback)(void *ctx, int result), void *ctx) +{ + SshProxy *sp = container_of(seat, SshProxy, seat); + + if (sp->clientseat) { + /* + * If we have access to the outer Seat, pass this prompt + * request on to it. FIXME: appropriately adjusted + */ + return seat_confirm_weak_crypto_primitive( + sp->clientseat, algtype, algname, callback, ctx); + } + + /* + * Otherwise, behave as if we're in batch mode: take the safest + * option. + */ + sshproxy_error(sp, "First %s supported by server is %s, below warning " + "threshold. Abandoning proxy SSH connection.", + algtype, algname); + return 0; +} + +static int sshproxy_confirm_weak_cached_hostkey( + Seat *seat, const char *algname, const char *betteralgs, + void (*callback)(void *ctx, int result), void *ctx) +{ + SshProxy *sp = container_of(seat, SshProxy, seat); + + if (sp->clientseat) { + /* + * If we have access to the outer Seat, pass this prompt + * request on to it. FIXME: appropriately adjusted + */ + return seat_confirm_weak_cached_hostkey( + sp->clientseat, algname, betteralgs, callback, ctx); + } + + /* + * Otherwise, behave as if we're in batch mode: take the safest + * option. + */ + sshproxy_error(sp, "First host key type stored for server is %s, below " + "warning threshold. Abandoning proxy SSH connection.", + algname); + return 0; +} + +static StripCtrlChars *sshproxy_stripctrl_new( + Seat *seat, BinarySink *bs_out, SeatInteractionContext sic) +{ + SshProxy *sp = container_of(seat, SshProxy, seat); + if (sp->clientseat) + return seat_stripctrl_new(sp->clientseat, bs_out, sic); + else + return NULL; +} + +static void sshproxy_set_trust_status(Seat *seat, bool trusted) +{ + SshProxy *sp = container_of(seat, SshProxy, seat); + if (sp->clientseat) + seat_set_trust_status(sp->clientseat, trusted); +} + +static bool sshproxy_can_set_trust_status(Seat *seat) +{ + SshProxy *sp = container_of(seat, SshProxy, seat); + return sp->clientseat && seat_can_set_trust_status(sp->clientseat); +} + +static bool sshproxy_verbose(Seat *seat) +{ + SshProxy *sp = container_of(seat, SshProxy, seat); + return sp->clientseat && seat_verbose(sp->clientseat); +} + +static bool sshproxy_interactive(Seat *seat) +{ + SshProxy *sp = container_of(seat, SshProxy, seat); + return sp->clientseat && seat_interactive(sp->clientseat); +} + +static const SeatVtable SshProxy_seat_vt = { + .output = sshproxy_output, + .eof = sshproxy_eof, + .sent = sshproxy_sent, + .get_userpass_input = sshproxy_get_userpass_input, + .notify_session_started = sshproxy_notify_session_started, + .notify_remote_exit = nullseat_notify_remote_exit, + .notify_remote_disconnect = sshproxy_notify_remote_disconnect, + .connection_fatal = sshproxy_connection_fatal, + .update_specials_menu = nullseat_update_specials_menu, + .get_ttymode = nullseat_get_ttymode, + .set_busy_status = nullseat_set_busy_status, + .confirm_ssh_host_key = sshproxy_confirm_ssh_host_key, + .confirm_weak_crypto_primitive = sshproxy_confirm_weak_crypto_primitive, + .confirm_weak_cached_hostkey = sshproxy_confirm_weak_cached_hostkey, + .is_utf8 = nullseat_is_never_utf8, + .echoedit_update = nullseat_echoedit_update, + .get_x_display = nullseat_get_x_display, + .get_windowid = nullseat_get_windowid, + .get_window_pixel_size = nullseat_get_window_pixel_size, + .stripctrl_new = sshproxy_stripctrl_new, + .set_trust_status = sshproxy_set_trust_status, + .can_set_trust_status = sshproxy_can_set_trust_status, + .verbose = sshproxy_verbose, + .interactive = sshproxy_interactive, + .get_cursor_position = nullseat_get_cursor_position, +}; + +Socket *sshproxy_new_connection(SockAddr *addr, const char *hostname, + int port, bool privport, + bool oobinline, bool nodelay, bool keepalive, + Plug *plug, Conf *clientconf, + LogPolicy *clientlp, Seat **clientseat) +{ + SshProxy *sp = snew(SshProxy); + memset(sp, 0, sizeof(*sp)); + + sp->sock.vt = &SshProxy_sock_vt; + sp->logpolicy.vt = &SshProxy_logpolicy_vt; + sp->seat.vt = &SshProxy_seat_vt; + sp->plug = plug; + psb_init(&sp->psb); + bufchain_init(&sp->ssh_to_socket); + + sp->addr = addr; + sp->port = port; + + sp->conf = conf_new(); + /* Try to treat proxy_hostname as the title of a saved session. If + * that fails, set up a default Conf of our own treating it as a + * hostname. */ + const char *proxy_hostname = conf_get_str(clientconf, CONF_proxy_host); + if (do_defaults(proxy_hostname, sp->conf)) { + if (!conf_launchable(sp->conf)) { + sp->errmsg = dupprintf("saved session '%s' is not launchable", + proxy_hostname); + return &sp->sock; + } + } else { + do_defaults(NULL, sp->conf); + /* In hostname mode, we default to PROT_SSH. This is more useful than + * the obvious approach of defaulting to the protocol defined in + * Default Settings, because only SSH (ok, and bare ssh-connection) + * can be used for this kind of proxy. */ + conf_set_int(sp->conf, CONF_protocol, PROT_SSH); + conf_set_str(sp->conf, CONF_host, proxy_hostname); + conf_set_int(sp->conf, CONF_port, + conf_get_int(clientconf, CONF_proxy_port)); + } + const char *proxy_username = conf_get_str(clientconf, CONF_proxy_username); + if (*proxy_username) + conf_set_str(sp->conf, CONF_username, proxy_username); + + const struct BackendVtable *backvt = backend_vt_from_proto( + conf_get_int(sp->conf, CONF_protocol)); + + /* + * We don't actually need an _SSH_ session specifically: it's also + * OK to use PROT_SSHCONN, because really, the criterion is + * whether setting CONF_ssh_nc_host will do anything useful. So + * our check is for whether the backend sets the flag promising + * that it does. + */ + if (!(backvt->flags & BACKEND_SUPPORTS_NC_HOST)) { + sp->errmsg = dupprintf("saved session '%s' is not an SSH session", + proxy_hostname); + return &sp->sock; + } + + /* + * We also expect that the backend will announce a willingness to + * notify us that the session has started. Any backend providing + * NC_HOST should also provide this. + */ + assert(backvt->flags & BACKEND_NOTIFIES_SESSION_START && + "Backend provides NC_HOST without SESSION_START!"); + + /* + * Turn off SSH features we definitely don't want. It would be + * awkward and counterintuitive to have the proxy SSH connection + * become a connection-sharing upstream (but it's fine to have it + * be a downstream, if that's configured). And we don't want to + * open X forwardings, agent forwardings or (other) port + * forwardings as a side effect of this one operation. + */ + conf_set_bool(sp->conf, CONF_ssh_connection_sharing_upstream, false); + conf_set_bool(sp->conf, CONF_x11_forward, false); + conf_set_bool(sp->conf, CONF_agentfwd, false); + for (const char *subkey; + (subkey = conf_get_str_nthstrkey(sp->conf, CONF_portfwd, 0)) != NULL;) + conf_del_str_str(sp->conf, CONF_portfwd, subkey); + + /* + * We'll only be running one channel through this connection + * (since we've just turned off all the other things we might have + * done with it), so we can configure it as simple. + */ + conf_set_bool(sp->conf, CONF_ssh_simple, true); + + /* + * Configure the main channel of this SSH session to be a + * direct-tcpip connection to the destination host/port. + */ + conf_set_str(sp->conf, CONF_ssh_nc_host, hostname); + conf_set_int(sp->conf, CONF_ssh_nc_port, port); + + sp->logctx = log_init(&sp->logpolicy, sp->conf); + + char *error, *realhost; + error = backend_init(backvt, &sp->seat, &sp->backend, sp->logctx, sp->conf, + conf_get_str(sp->conf, CONF_host), + conf_get_int(sp->conf, CONF_port), + &realhost, nodelay, + conf_get_bool(sp->conf, CONF_tcp_keepalives)); + if (error) { + sp->errmsg = dupprintf("unable to open SSH proxy connection: %s", + error); + return &sp->sock; + } + + sfree(realhost); + + /* + * If we've been given useful bits and pieces for interacting with + * the end user, squirrel them away now. + */ + sp->clientlp = clientlp; + if (clientseat && (backvt->flags & BACKEND_NOTIFIES_SESSION_START)) { + /* + * We can only keep the client's Seat if our own backend will + * tell us when to give it back. (SSH-based backends _should_ + * do that, but we check the flag here anyway.) + * + * Also, check if the client already has a TempSeat, and if + * so, don't wrap it with another one. + */ + if (is_tempseat(*clientseat)) { + sp->clientseat = tempseat_get_real(*clientseat); + } else { + sp->clientseat = *clientseat; + *clientseat = tempseat_new(sp->clientseat); + } + } + + return &sp->sock; +} diff --git a/sshproxy.c b/sshproxy.c deleted file mode 100644 index ad274d57..00000000 --- a/sshproxy.c +++ /dev/null @@ -1,616 +0,0 @@ -/* - * sshproxy.c: implement a Socket type that talks to an entire - * subsidiary SSH connection (sometimes called a 'jump host'). - */ - -#include -#include - -#include "putty.h" -#include "ssh.h" -#include "network.h" -#include "storage.h" - -const bool ssh_proxy_supported = true; - -/* - * TODO for future work: - * - * All the interactive prompts we present to the main Seat - the host - * key and weak-crypto dialog boxes, and all prompts presented via the - * userpass_input system - need adjusting so that it's clear to the - * user _which_ SSH connection they come from. At the moment, you just - * get shown a host key fingerprint or a cryptic "login as:" prompt, - * and you have to guess which server you're currently supposed to be - * interpreting it relative to. - * - * If the user manually aborts the attempt to make the proxy SSH - * connection (e.g. by hitting ^C at a userpass prompt, or refusing to - * accept the proxy server's host key), then an assertion failure - * occurs, because the main backend receives an indication of - * connection failure that causes it to want to call - * seat_connection_fatal("Remote side unexpectedly closed network - * connection"), which fails an assertion in tempseat.c because that - * method of TempSeat expects never to be called. To fix this, I think - * we need to distinguish 'connection attempt unexpectedly failed, in - * a way the user needs to be told about' from 'connection attempt was - * aborted by deliberate user action, so the user already knows'. - */ - -typedef struct SshProxy { - char *errmsg; - Conf *conf; - LogContext *logctx; - Backend *backend; - LogPolicy *clientlp; - Seat *clientseat; - - ProxyStderrBuf psb; - Plug *plug; - - bool frozen; - bufchain ssh_to_socket; - bool rcvd_eof_ssh_to_socket, sent_eof_ssh_to_socket; - - SockAddr *addr; - int port; - - /* Traits implemented: we're a Socket from the point of view of - * the client connection, and a Seat from the POV of the SSH - * backend we instantiate. */ - Socket sock; - LogPolicy logpolicy; - Seat seat; -} SshProxy; - -static Plug *sshproxy_plug(Socket *s, Plug *p) -{ - SshProxy *sp = container_of(s, SshProxy, sock); - Plug *oldplug = sp->plug; - if (p) - sp->plug = p; - return oldplug; -} - -static void sshproxy_close(Socket *s) -{ - SshProxy *sp = container_of(s, SshProxy, sock); - - sk_addr_free(sp->addr); - sfree(sp->errmsg); - conf_free(sp->conf); - if (sp->backend) - backend_free(sp->backend); - if (sp->logctx) - log_free(sp->logctx); - bufchain_clear(&sp->ssh_to_socket); - - delete_callbacks_for_context(sp); - sfree(sp); -} - -static size_t sshproxy_write(Socket *s, const void *data, size_t len) -{ - SshProxy *sp = container_of(s, SshProxy, sock); - if (!sp->backend) - return 0; - backend_send(sp->backend, data, len); - return backend_sendbuffer(sp->backend); -} - -static size_t sshproxy_write_oob(Socket *s, const void *data, size_t len) -{ - /* - * oob data is treated as inband; nasty, but nothing really - * better we can do - */ - return sshproxy_write(s, data, len); -} - -static void sshproxy_write_eof(Socket *s) -{ - SshProxy *sp = container_of(s, SshProxy, sock); - if (!sp->backend) - return; - backend_special(sp->backend, SS_EOF, 0); -} - -static void try_send_ssh_to_socket(void *ctx); - -static void sshproxy_set_frozen(Socket *s, bool is_frozen) -{ - SshProxy *sp = container_of(s, SshProxy, sock); - sp->frozen = is_frozen; - if (!sp->frozen) - queue_toplevel_callback(try_send_ssh_to_socket, sp); -} - -static const char *sshproxy_socket_error(Socket *s) -{ - SshProxy *sp = container_of(s, SshProxy, sock); - return sp->errmsg; -} - -static SocketPeerInfo *sshproxy_peer_info(Socket *s) -{ - return NULL; -} - -static const SocketVtable SshProxy_sock_vt = { - .plug = sshproxy_plug, - .close = sshproxy_close, - .write = sshproxy_write, - .write_oob = sshproxy_write_oob, - .write_eof = sshproxy_write_eof, - .set_frozen = sshproxy_set_frozen, - .socket_error = sshproxy_socket_error, - .peer_info = sshproxy_peer_info, -}; - -static void sshproxy_eventlog(LogPolicy *lp, const char *event) -{ - SshProxy *sp = container_of(lp, SshProxy, logpolicy); - log_proxy_stderr(sp->plug, &sp->psb, event, strlen(event)); - log_proxy_stderr(sp->plug, &sp->psb, "\n", 1); -} - -static int sshproxy_askappend(LogPolicy *lp, Filename *filename, - void (*callback)(void *ctx, int result), - void *ctx) -{ - SshProxy *sp = container_of(lp, SshProxy, logpolicy); - - /* - * If we have access to the outer LogPolicy, pass on this request - * to the end user. - */ - if (sp->clientlp) - return lp_askappend(sp->clientlp, filename, callback, ctx); - - /* - * Otherwise, fall back to the safe noninteractive assumption. - */ - char *msg = dupprintf("Log file \"%s\" already exists; logging cancelled", - filename_to_str(filename)); - sshproxy_eventlog(lp, msg); - sfree(msg); - return 0; -} - -static void sshproxy_logging_error(LogPolicy *lp, const char *event) -{ - SshProxy *sp = container_of(lp, SshProxy, logpolicy); - - /* - * If we have access to the outer LogPolicy, pass on this request - * to it. - */ - if (sp->clientlp) { - lp_logging_error(sp->clientlp, event); - return; - } - - /* - * Otherwise, the best we can do is to put it in the outer SSH - * connection's Event Log. - */ - char *msg = dupprintf("Logging error: %s", event); - sshproxy_eventlog(lp, msg); - sfree(msg); -} - -static const LogPolicyVtable SshProxy_logpolicy_vt = { - .eventlog = sshproxy_eventlog, - .askappend = sshproxy_askappend, - .logging_error = sshproxy_logging_error, - .verbose = null_lp_verbose_no, -}; - -/* - * Function called when we encounter an error during connection setup that's - * likely to be the cause of terminating the proxy SSH connection. Putting it - * in the Event Log is useful on general principles; also putting it in - * sp->errmsg meaks that it will be passed back through plug_closing when the - * proxy SSH connection actually terminates, so that the end user will see - * what went wrong in the proxy connection. - */ -static void sshproxy_error(SshProxy *sp, const char *fmt, ...) -{ - va_list ap; - va_start(ap, fmt); - char *msg = dupvprintf(fmt, ap); - va_end(ap); - - if (!sp->errmsg) - sp->errmsg = dupstr(msg); - - sshproxy_eventlog(&sp->logpolicy, msg); - sfree(msg); -} - -static void try_send_ssh_to_socket(void *ctx) -{ - SshProxy *sp = (SshProxy *)ctx; - - if (sp->frozen) - return; - - while (bufchain_size(&sp->ssh_to_socket)) { - ptrlen pl = bufchain_prefix(&sp->ssh_to_socket); - plug_receive(sp->plug, 0, pl.ptr, pl.len); - bufchain_consume(&sp->ssh_to_socket, pl.len); - } - - if (sp->rcvd_eof_ssh_to_socket && - !sp->sent_eof_ssh_to_socket) { - sp->sent_eof_ssh_to_socket = true; - plug_closing(sp->plug, sp->errmsg, 0); - } -} - -static void sshproxy_notify_session_started(Seat *seat) -{ - SshProxy *sp = container_of(seat, SshProxy, seat); - - if (sp->clientseat) - seat_set_trust_status(sp->clientseat, true); - - plug_log(sp->plug, PLUGLOG_CONNECT_SUCCESS, sp->addr, sp->port, NULL, 0); -} - -static size_t sshproxy_output(Seat *seat, SeatOutputType type, - const void *data, size_t len) -{ - SshProxy *sp = container_of(seat, SshProxy, seat); - if (type == SEAT_OUTPUT_AUTH_BANNER) { - if (sp->clientseat) { - /* - * If we have access to the outer Seat, pass the SSH login - * banner on to it. - */ - return seat_output(sp->clientseat, type, data, len); - } else { - return 0; - } - } else { - bufchain_add(&sp->ssh_to_socket, data, len); - try_send_ssh_to_socket(sp); - return bufchain_size(&sp->ssh_to_socket); - } -} - -static bool sshproxy_eof(Seat *seat) -{ - SshProxy *sp = container_of(seat, SshProxy, seat); - sp->rcvd_eof_ssh_to_socket = true; - try_send_ssh_to_socket(sp); - return false; -} - -static void sshproxy_sent(Seat *seat, size_t new_bufsize) -{ - SshProxy *sp = container_of(seat, SshProxy, seat); - plug_sent(sp->plug, new_bufsize); -} - -static void sshproxy_notify_remote_disconnect(Seat *seat) -{ - SshProxy *sp = container_of(seat, SshProxy, seat); - if (!sp->rcvd_eof_ssh_to_socket && !backend_connected(sp->backend)) - sshproxy_eof(seat); -} - -static int sshproxy_get_userpass_input(Seat *seat, prompts_t *p) -{ - SshProxy *sp = container_of(seat, SshProxy, seat); - - if (sp->clientseat) { - /* - * If we have access to the outer Seat, pass this prompt - * request on to it. FIXME: appropriately adjusted - */ - return seat_get_userpass_input(sp->clientseat, p); - } - - /* - * Otherwise, behave as if noninteractive (like plink -batch): - * reject all attempts to present a prompt to the user, and log in - * the Event Log to say why not. - */ - sshproxy_error(sp, "Unable to provide interactive authentication " - "requested by proxy SSH connection"); - return 0; -} - -static void sshproxy_connection_fatal_callback(void *vctx) -{ - SshProxy *sp = (SshProxy *)vctx; - plug_closing(sp->plug, sp->errmsg, 0); -} - -static void sshproxy_connection_fatal(Seat *seat, const char *message) -{ - SshProxy *sp = container_of(seat, SshProxy, seat); - if (!sp->errmsg) { - sp->errmsg = dupprintf( - "fatal error in proxy SSH connection: %s", message); - queue_toplevel_callback(sshproxy_connection_fatal_callback, sp); - } -} - -static int sshproxy_confirm_ssh_host_key( - Seat *seat, const char *host, int port, const char *keytype, - char *keystr, const char *keydisp, char **key_fingerprints, bool mismatch, - void (*callback)(void *ctx, int result), void *ctx) -{ - SshProxy *sp = container_of(seat, SshProxy, seat); - - if (sp->clientseat) { - /* - * If we have access to the outer Seat, pass this prompt - * request on to it. FIXME: appropriately adjusted - */ - return seat_confirm_ssh_host_key( - sp->clientseat, host, port, keytype, keystr, keydisp, - key_fingerprints, mismatch, callback, ctx); - } - - /* - * Otherwise, behave as if we're in batch mode, i.e. take the safe - * option in the absence of interactive confirmation, i.e. abort - * the connection. - */ - return 0; -} - -static int sshproxy_confirm_weak_crypto_primitive( - Seat *seat, const char *algtype, const char *algname, - void (*callback)(void *ctx, int result), void *ctx) -{ - SshProxy *sp = container_of(seat, SshProxy, seat); - - if (sp->clientseat) { - /* - * If we have access to the outer Seat, pass this prompt - * request on to it. FIXME: appropriately adjusted - */ - return seat_confirm_weak_crypto_primitive( - sp->clientseat, algtype, algname, callback, ctx); - } - - /* - * Otherwise, behave as if we're in batch mode: take the safest - * option. - */ - sshproxy_error(sp, "First %s supported by server is %s, below warning " - "threshold. Abandoning proxy SSH connection.", - algtype, algname); - return 0; -} - -static int sshproxy_confirm_weak_cached_hostkey( - Seat *seat, const char *algname, const char *betteralgs, - void (*callback)(void *ctx, int result), void *ctx) -{ - SshProxy *sp = container_of(seat, SshProxy, seat); - - if (sp->clientseat) { - /* - * If we have access to the outer Seat, pass this prompt - * request on to it. FIXME: appropriately adjusted - */ - return seat_confirm_weak_cached_hostkey( - sp->clientseat, algname, betteralgs, callback, ctx); - } - - /* - * Otherwise, behave as if we're in batch mode: take the safest - * option. - */ - sshproxy_error(sp, "First host key type stored for server is %s, below " - "warning threshold. Abandoning proxy SSH connection.", - algname); - return 0; -} - -static StripCtrlChars *sshproxy_stripctrl_new( - Seat *seat, BinarySink *bs_out, SeatInteractionContext sic) -{ - SshProxy *sp = container_of(seat, SshProxy, seat); - if (sp->clientseat) - return seat_stripctrl_new(sp->clientseat, bs_out, sic); - else - return NULL; -} - -static void sshproxy_set_trust_status(Seat *seat, bool trusted) -{ - SshProxy *sp = container_of(seat, SshProxy, seat); - if (sp->clientseat) - seat_set_trust_status(sp->clientseat, trusted); -} - -static bool sshproxy_can_set_trust_status(Seat *seat) -{ - SshProxy *sp = container_of(seat, SshProxy, seat); - return sp->clientseat && seat_can_set_trust_status(sp->clientseat); -} - -static bool sshproxy_verbose(Seat *seat) -{ - SshProxy *sp = container_of(seat, SshProxy, seat); - return sp->clientseat && seat_verbose(sp->clientseat); -} - -static bool sshproxy_interactive(Seat *seat) -{ - SshProxy *sp = container_of(seat, SshProxy, seat); - return sp->clientseat && seat_interactive(sp->clientseat); -} - -static const SeatVtable SshProxy_seat_vt = { - .output = sshproxy_output, - .eof = sshproxy_eof, - .sent = sshproxy_sent, - .get_userpass_input = sshproxy_get_userpass_input, - .notify_session_started = sshproxy_notify_session_started, - .notify_remote_exit = nullseat_notify_remote_exit, - .notify_remote_disconnect = sshproxy_notify_remote_disconnect, - .connection_fatal = sshproxy_connection_fatal, - .update_specials_menu = nullseat_update_specials_menu, - .get_ttymode = nullseat_get_ttymode, - .set_busy_status = nullseat_set_busy_status, - .confirm_ssh_host_key = sshproxy_confirm_ssh_host_key, - .confirm_weak_crypto_primitive = sshproxy_confirm_weak_crypto_primitive, - .confirm_weak_cached_hostkey = sshproxy_confirm_weak_cached_hostkey, - .is_utf8 = nullseat_is_never_utf8, - .echoedit_update = nullseat_echoedit_update, - .get_x_display = nullseat_get_x_display, - .get_windowid = nullseat_get_windowid, - .get_window_pixel_size = nullseat_get_window_pixel_size, - .stripctrl_new = sshproxy_stripctrl_new, - .set_trust_status = sshproxy_set_trust_status, - .can_set_trust_status = sshproxy_can_set_trust_status, - .verbose = sshproxy_verbose, - .interactive = sshproxy_interactive, - .get_cursor_position = nullseat_get_cursor_position, -}; - -Socket *sshproxy_new_connection(SockAddr *addr, const char *hostname, - int port, bool privport, - bool oobinline, bool nodelay, bool keepalive, - Plug *plug, Conf *clientconf, - LogPolicy *clientlp, Seat **clientseat) -{ - SshProxy *sp = snew(SshProxy); - memset(sp, 0, sizeof(*sp)); - - sp->sock.vt = &SshProxy_sock_vt; - sp->logpolicy.vt = &SshProxy_logpolicy_vt; - sp->seat.vt = &SshProxy_seat_vt; - sp->plug = plug; - psb_init(&sp->psb); - bufchain_init(&sp->ssh_to_socket); - - sp->addr = addr; - sp->port = port; - - sp->conf = conf_new(); - /* Try to treat proxy_hostname as the title of a saved session. If - * that fails, set up a default Conf of our own treating it as a - * hostname. */ - const char *proxy_hostname = conf_get_str(clientconf, CONF_proxy_host); - if (do_defaults(proxy_hostname, sp->conf)) { - if (!conf_launchable(sp->conf)) { - sp->errmsg = dupprintf("saved session '%s' is not launchable", - proxy_hostname); - return &sp->sock; - } - } else { - do_defaults(NULL, sp->conf); - /* In hostname mode, we default to PROT_SSH. This is more useful than - * the obvious approach of defaulting to the protocol defined in - * Default Settings, because only SSH (ok, and bare ssh-connection) - * can be used for this kind of proxy. */ - conf_set_int(sp->conf, CONF_protocol, PROT_SSH); - conf_set_str(sp->conf, CONF_host, proxy_hostname); - conf_set_int(sp->conf, CONF_port, - conf_get_int(clientconf, CONF_proxy_port)); - } - const char *proxy_username = conf_get_str(clientconf, CONF_proxy_username); - if (*proxy_username) - conf_set_str(sp->conf, CONF_username, proxy_username); - - const struct BackendVtable *backvt = backend_vt_from_proto( - conf_get_int(sp->conf, CONF_protocol)); - - /* - * We don't actually need an _SSH_ session specifically: it's also - * OK to use PROT_SSHCONN, because really, the criterion is - * whether setting CONF_ssh_nc_host will do anything useful. So - * our check is for whether the backend sets the flag promising - * that it does. - */ - if (!(backvt->flags & BACKEND_SUPPORTS_NC_HOST)) { - sp->errmsg = dupprintf("saved session '%s' is not an SSH session", - proxy_hostname); - return &sp->sock; - } - - /* - * We also expect that the backend will announce a willingness to - * notify us that the session has started. Any backend providing - * NC_HOST should also provide this. - */ - assert(backvt->flags & BACKEND_NOTIFIES_SESSION_START && - "Backend provides NC_HOST without SESSION_START!"); - - /* - * Turn off SSH features we definitely don't want. It would be - * awkward and counterintuitive to have the proxy SSH connection - * become a connection-sharing upstream (but it's fine to have it - * be a downstream, if that's configured). And we don't want to - * open X forwardings, agent forwardings or (other) port - * forwardings as a side effect of this one operation. - */ - conf_set_bool(sp->conf, CONF_ssh_connection_sharing_upstream, false); - conf_set_bool(sp->conf, CONF_x11_forward, false); - conf_set_bool(sp->conf, CONF_agentfwd, false); - for (const char *subkey; - (subkey = conf_get_str_nthstrkey(sp->conf, CONF_portfwd, 0)) != NULL;) - conf_del_str_str(sp->conf, CONF_portfwd, subkey); - - /* - * We'll only be running one channel through this connection - * (since we've just turned off all the other things we might have - * done with it), so we can configure it as simple. - */ - conf_set_bool(sp->conf, CONF_ssh_simple, true); - - /* - * Configure the main channel of this SSH session to be a - * direct-tcpip connection to the destination host/port. - */ - conf_set_str(sp->conf, CONF_ssh_nc_host, hostname); - conf_set_int(sp->conf, CONF_ssh_nc_port, port); - - sp->logctx = log_init(&sp->logpolicy, sp->conf); - - char *error, *realhost; - error = backend_init(backvt, &sp->seat, &sp->backend, sp->logctx, sp->conf, - conf_get_str(sp->conf, CONF_host), - conf_get_int(sp->conf, CONF_port), - &realhost, nodelay, - conf_get_bool(sp->conf, CONF_tcp_keepalives)); - if (error) { - sp->errmsg = dupprintf("unable to open SSH proxy connection: %s", - error); - return &sp->sock; - } - - sfree(realhost); - - /* - * If we've been given useful bits and pieces for interacting with - * the end user, squirrel them away now. - */ - sp->clientlp = clientlp; - if (clientseat && (backvt->flags & BACKEND_NOTIFIES_SESSION_START)) { - /* - * We can only keep the client's Seat if our own backend will - * tell us when to give it back. (SSH-based backends _should_ - * do that, but we check the flag here anyway.) - * - * Also, check if the client already has a TempSeat, and if - * so, don't wrap it with another one. - */ - if (is_tempseat(*clientseat)) { - sp->clientseat = tempseat_get_real(*clientseat); - } else { - sp->clientseat = *clientseat; - *clientseat = tempseat_new(sp->clientseat); - } - } - - return &sp->sock; -} diff --git a/unix/CMakeLists.txt b/unix/CMakeLists.txt index a2332473..825ac35f 100644 --- a/unix/CMakeLists.txt +++ b/unix/CMakeLists.txt @@ -138,7 +138,7 @@ if(GTK_FOUND) x11.c noise.c ${CMAKE_SOURCE_DIR}/ssh/x11fwd.c - ${CMAKE_SOURCE_DIR}/nosshproxy.c) + ${CMAKE_SOURCE_DIR}/proxy/nosshproxy.c) target_link_libraries(pageant eventloop console agent settings network crypto utils ${GTK_LIBRARIES}) @@ -149,7 +149,7 @@ if(GTK_FOUND) main-gtk-simple.c ${CMAKE_SOURCE_DIR}/be_none.c ${CMAKE_SOURCE_DIR}/nogss.c - ${CMAKE_SOURCE_DIR}/nosshproxy.c + ${CMAKE_SOURCE_DIR}/proxy/nosshproxy.c pty.c) target_link_libraries(pterm guiterminal eventloop settings charset utils @@ -162,7 +162,7 @@ if(GTK_FOUND) ${CMAKE_SOURCE_DIR}/nocmdline.c ${CMAKE_SOURCE_DIR}/be_none.c ${CMAKE_SOURCE_DIR}/nogss.c - ${CMAKE_SOURCE_DIR}/nosshproxy.c + ${CMAKE_SOURCE_DIR}/proxy/nosshproxy.c pty.c) target_link_libraries(ptermapp guiterminal eventloop settings charset utils @@ -196,8 +196,8 @@ if(GTK_FOUND) ${CMAKE_SOURCE_DIR}/be_nos_s.c ${CMAKE_SOURCE_DIR}/nogss.c ${CMAKE_SOURCE_DIR}/norand.c - ${CMAKE_SOURCE_DIR}/nocproxy.c - ${CMAKE_SOURCE_DIR}/nosshproxy.c) + ${CMAKE_SOURCE_DIR}/proxy/nocproxy.c + ${CMAKE_SOURCE_DIR}/proxy/nosshproxy.c) target_link_libraries(puttytel guiterminal eventloop otherbackends settings network charset utils ${GTK_LIBRARIES} ${X11_LIBRARIES}) diff --git a/unix/local-proxy.c b/unix/local-proxy.c index c8663928..583bb75a 100644 --- a/unix/local-proxy.c +++ b/unix/local-proxy.c @@ -12,7 +12,7 @@ #include "tree234.h" #include "putty.h" #include "network.h" -#include "proxy.h" +#include "proxy/proxy.h" Socket *platform_new_connection(SockAddr *addr, const char *hostname, int port, bool privport, diff --git a/unix/sharing.c b/unix/sharing.c index 58038ab9..66628723 100644 --- a/unix/sharing.c +++ b/unix/sharing.c @@ -16,7 +16,7 @@ #include "tree234.h" #include "putty.h" #include "network.h" -#include "proxy.h" +#include "proxy/proxy.h" #include "ssh.h" #define CONNSHARE_SOCKETDIR_PREFIX "/tmp/putty-connshare" diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt index dc97e675..c432c6e5 100644 --- a/windows/CMakeLists.txt +++ b/windows/CMakeLists.txt @@ -111,8 +111,8 @@ add_executable(puttytel ${CMAKE_SOURCE_DIR}/be_nos_s.c ${CMAKE_SOURCE_DIR}/nogss.c ${CMAKE_SOURCE_DIR}/norand.c - ${CMAKE_SOURCE_DIR}/nocproxy.c - ${CMAKE_SOURCE_DIR}/nosshproxy.c + ${CMAKE_SOURCE_DIR}/proxy/nocproxy.c + ${CMAKE_SOURCE_DIR}/proxy/nosshproxy.c puttytel.rc) add_dependencies(puttytel generated_licence_h) target_link_libraries(puttytel @@ -152,7 +152,7 @@ if(HAVE_CONPTY) be_conpty.c ${CMAKE_SOURCE_DIR}/nogss.c ${CMAKE_SOURCE_DIR}/norand.c - ${CMAKE_SOURCE_DIR}/nosshproxy.c + ${CMAKE_SOURCE_DIR}/proxy/nosshproxy.c pterm.rc) add_dependencies(pterm generated_licence_h) target_link_libraries(pterm diff --git a/windows/local-proxy.c b/windows/local-proxy.c index ddbdb97c..9672ddbe 100644 --- a/windows/local-proxy.c +++ b/windows/local-proxy.c @@ -10,7 +10,7 @@ #include "tree234.h" #include "putty.h" #include "network.h" -#include "proxy.h" +#include "proxy/proxy.h" Socket *platform_new_connection(SockAddr *addr, const char *hostname, int port, bool privport, diff --git a/windows/named-pipe-client.c b/windows/named-pipe-client.c index b827dd0d..e6ec5312 100644 --- a/windows/named-pipe-client.c +++ b/windows/named-pipe-client.c @@ -8,7 +8,7 @@ #include "tree234.h" #include "putty.h" #include "network.h" -#include "proxy.h" +#include "proxy/proxy.h" #include "ssh.h" #include "security-api.h" diff --git a/windows/named-pipe-server.c b/windows/named-pipe-server.c index a9271c16..d6d8d1c8 100644 --- a/windows/named-pipe-server.c +++ b/windows/named-pipe-server.c @@ -8,7 +8,7 @@ #include "tree234.h" #include "putty.h" #include "network.h" -#include "proxy.h" +#include "proxy/proxy.h" #include "ssh.h" #include "security-api.h" diff --git a/windows/sharing.c b/windows/sharing.c index 6ded0716..02eeb087 100644 --- a/windows/sharing.c +++ b/windows/sharing.c @@ -8,7 +8,7 @@ #include "tree234.h" #include "putty.h" #include "network.h" -#include "proxy.h" +#include "proxy/proxy.h" #include "ssh.h" #include "cryptoapi.h" -- cgit v1.2.3 From 74a0be9c5647ed033b7ef7b58d074a3510bcc071 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 30 Oct 2021 17:06:00 +0100 Subject: Split seat_banner from seat_output. Previously, SSH authentication banners were displayed by calling the ordinary seat_output function, and passing it a special value in the SeatOutputType enumeration indicating an auth banner. The awkwardness of this was already showing a little in SshProxy's implementation of seat_output, where it had to check for that special value and do totally different things for SEAT_OUTPUT_AUTH_BANNER and everything else. Further work in that area is going to make it more and more awkward if I keep the two output systems unified. So let's split them up. Now, Seat has separate output() and banner() methods, which each implementation can override differently if it wants to. All the 'end user' Seat implementations use the centralised implementation function nullseat_banner_to_stderr(), which turns banner text straight back into SEAT_OUTPUT_STDERR and passes it on to seat_output. So I didn't have to tediously implement a boring version of this function in GTK, Windows GUI, consoles, file transfer etc. --- proxy/sshproxy.c | 29 ++++++++++++++++------------- pscp.c | 1 + psftp.c | 1 + putty.h | 29 ++++++++++++++++++++--------- ssh/server.c | 1 + ssh/sesschan.c | 6 +----- unix/plink.c | 1 + unix/window.c | 1 + utils/nullseat.c | 3 +++ utils/tempseat.c | 6 ++++++ windows/plink.c | 1 + windows/window.c | 1 + 12 files changed, 53 insertions(+), 27 deletions(-) diff --git a/proxy/sshproxy.c b/proxy/sshproxy.c index ad274d57..d113cb04 100644 --- a/proxy/sshproxy.c +++ b/proxy/sshproxy.c @@ -262,20 +262,22 @@ static size_t sshproxy_output(Seat *seat, SeatOutputType type, const void *data, size_t len) { SshProxy *sp = container_of(seat, SshProxy, seat); - if (type == SEAT_OUTPUT_AUTH_BANNER) { - if (sp->clientseat) { - /* - * If we have access to the outer Seat, pass the SSH login - * banner on to it. - */ - return seat_output(sp->clientseat, type, data, len); - } else { - return 0; - } + bufchain_add(&sp->ssh_to_socket, data, len); + try_send_ssh_to_socket(sp); + return bufchain_size(&sp->ssh_to_socket); +} + +static size_t sshproxy_banner(Seat *seat, const void *data, size_t len) +{ + SshProxy *sp = container_of(seat, SshProxy, seat); + if (sp->clientseat) { + /* + * If we have access to the outer Seat, pass the SSH login + * banner on to it. + */ + return seat_banner(sp->clientseat, data, len); } else { - bufchain_add(&sp->ssh_to_socket, data, len); - try_send_ssh_to_socket(sp); - return bufchain_size(&sp->ssh_to_socket); + return 0; } } @@ -452,6 +454,7 @@ static const SeatVtable SshProxy_seat_vt = { .output = sshproxy_output, .eof = sshproxy_eof, .sent = sshproxy_sent, + .banner = sshproxy_banner, .get_userpass_input = sshproxy_get_userpass_input, .notify_session_started = sshproxy_notify_session_started, .notify_remote_exit = nullseat_notify_remote_exit, diff --git a/pscp.c b/pscp.c index 0b091085..13e89812 100644 --- a/pscp.c +++ b/pscp.c @@ -67,6 +67,7 @@ static const SeatVtable pscp_seat_vt = { .output = pscp_output, .eof = pscp_eof, .sent = nullseat_sent, + .banner = nullseat_banner_to_stderr, .get_userpass_input = filexfer_get_userpass_input, .notify_session_started = nullseat_notify_session_started, .notify_remote_exit = nullseat_notify_remote_exit, diff --git a/psftp.c b/psftp.c index e00ebed1..14483569 100644 --- a/psftp.c +++ b/psftp.c @@ -48,6 +48,7 @@ static const SeatVtable psftp_seat_vt = { .output = psftp_output, .eof = psftp_eof, .sent = nullseat_sent, + .banner = nullseat_banner_to_stderr, .get_userpass_input = filexfer_get_userpass_input, .notify_session_started = nullseat_notify_session_started, .notify_remote_exit = nullseat_notify_remote_exit, diff --git a/putty.h b/putty.h index 1d55f91e..4e1f9dde 100644 --- a/putty.h +++ b/putty.h @@ -925,7 +925,7 @@ typedef enum SeatInteractionContext { } SeatInteractionContext; typedef enum SeatOutputType { - SEAT_OUTPUT_STDOUT, SEAT_OUTPUT_STDERR, SEAT_OUTPUT_AUTH_BANNER + SEAT_OUTPUT_STDOUT, SEAT_OUTPUT_STDERR } SeatOutputType; /* @@ -941,11 +941,10 @@ struct Seat { struct SeatVtable { /* * Provide output from the remote session. 'type' indicates the - * type of the output (stdout, stderr or SSH auth banner), which - * can be used to split the output into separate message channels, - * if the seat wants to handle them differently. But combining the - * channels into one is OK too; that's what terminal-window based - * seats do. + * type of the output (stdout or stderr), which can be used to + * split the output into separate message channels, if the seat + * wants to handle them differently. But combining the channels + * into one is OK too; that's what terminal-window based seats do. * * The return value is the current size of the output backlog. */ @@ -971,6 +970,14 @@ struct SeatVtable { */ void (*sent)(Seat *seat, size_t new_sendbuffer); + /* + * Provide authentication-banner output from the session setup. + * End-user Seats can treat this as very similar to 'output', but + * intermediate Seats in complex proxying situations will want to + * implement this and 'output' differently. + */ + size_t (*banner)(Seat *seat, const void *data, size_t len); + /* * Try to get answers from a set of interactive login prompts. The * prompts are provided in 'p'. @@ -1227,6 +1234,8 @@ static inline bool seat_eof(Seat *seat) { return seat->vt->eof(seat); } static inline void seat_sent(Seat *seat, size_t bufsize) { seat->vt->sent(seat, bufsize); } +static inline size_t seat_banner(Seat *seat, const void *data, size_t len) +{ return seat->vt->banner(seat, data, len); } static inline int seat_get_userpass_input(Seat *seat, prompts_t *p) { return seat->vt->get_userpass_input(seat, p); } static inline void seat_notify_session_started(Seat *seat) @@ -1293,10 +1302,10 @@ static inline size_t seat_stderr(Seat *seat, const void *data, size_t len) { return seat_output(seat, SEAT_OUTPUT_STDERR, data, len); } static inline size_t seat_stderr_pl(Seat *seat, ptrlen data) { return seat_output(seat, SEAT_OUTPUT_STDERR, data.ptr, data.len); } -static inline size_t seat_banner(Seat *seat, const void *data, size_t len) -{ return seat_output(seat, SEAT_OUTPUT_AUTH_BANNER, data, len); } + +/* Alternative API for seat_banner taking a ptrlen */ static inline size_t seat_banner_pl(Seat *seat, ptrlen data) -{ return seat_output(seat, SEAT_OUTPUT_AUTH_BANNER, data.ptr, data.len); } +{ return seat->vt->banner(seat, data.ptr, data.len); } /* In the utils subdir: print a message to the Seat which can't be * spoofed by server-supplied auth-time output such as SSH banners */ @@ -1313,6 +1322,8 @@ size_t nullseat_output( Seat *seat, SeatOutputType type, const void *data, size_t len); bool nullseat_eof(Seat *seat); void nullseat_sent(Seat *seat, size_t bufsize); +size_t nullseat_banner(Seat *seat, const void *data, size_t len); +size_t nullseat_banner_to_stderr(Seat *seat, const void *data, size_t len); int nullseat_get_userpass_input(Seat *seat, prompts_t *p); void nullseat_notify_session_started(Seat *seat); void nullseat_notify_remote_exit(Seat *seat); diff --git a/ssh/server.c b/ssh/server.c index 63a68f62..724abaf4 100644 --- a/ssh/server.c +++ b/ssh/server.c @@ -108,6 +108,7 @@ static const SeatVtable server_seat_vt = { .output = nullseat_output, .eof = nullseat_eof, .sent = nullseat_sent, + .banner = nullseat_banner, .get_userpass_input = nullseat_get_userpass_input, .notify_session_started = nullseat_notify_session_started, .notify_remote_exit = nullseat_notify_remote_exit, diff --git a/ssh/sesschan.c b/ssh/sesschan.c index 6951edc4..56ee37e1 100644 --- a/ssh/sesschan.c +++ b/ssh/sesschan.c @@ -187,6 +187,7 @@ static const SeatVtable sesschan_seat_vt = { .output = sesschan_seat_output, .eof = sesschan_seat_eof, .sent = nullseat_sent, + .banner = nullseat_banner, .get_userpass_input = nullseat_get_userpass_input, .notify_session_started = nullseat_notify_session_started, .notify_remote_exit = sesschan_notify_remote_exit, @@ -614,11 +615,6 @@ static size_t sesschan_seat_output( Seat *seat, SeatOutputType type, const void *data, size_t len) { sesschan *sess = container_of(seat, sesschan, seat); - - /* We don't expect anything but stdout and stderr to come here, - * because the pty backend doesn't generate auth banners */ - assert(type != SEAT_OUTPUT_AUTH_BANNER); - return sshfwd_write_ext(sess->c, type == SEAT_OUTPUT_STDERR, data, len); } diff --git a/unix/plink.c b/unix/plink.c index 524e4545..dee0cbec 100644 --- a/unix/plink.c +++ b/unix/plink.c @@ -391,6 +391,7 @@ static const SeatVtable plink_seat_vt = { .output = plink_output, .eof = plink_eof, .sent = nullseat_sent, + .banner = nullseat_banner_to_stderr, .get_userpass_input = plink_get_userpass_input, .notify_session_started = nullseat_notify_session_started, .notify_remote_exit = nullseat_notify_remote_exit, diff --git a/unix/window.c b/unix/window.c index c3ddb515..c5894ffa 100644 --- a/unix/window.c +++ b/unix/window.c @@ -390,6 +390,7 @@ static const SeatVtable gtk_seat_vt = { .output = gtk_seat_output, .eof = gtk_seat_eof, .sent = nullseat_sent, + .banner = nullseat_banner_to_stderr, .get_userpass_input = gtk_seat_get_userpass_input, .notify_session_started = nullseat_notify_session_started, .notify_remote_exit = gtk_seat_notify_remote_exit, diff --git a/utils/nullseat.c b/utils/nullseat.c index 19fad59d..a53f563e 100644 --- a/utils/nullseat.c +++ b/utils/nullseat.c @@ -8,6 +8,9 @@ size_t nullseat_output( Seat *seat, SeatOutputType type, const void *data, size_t len) {return 0;} bool nullseat_eof(Seat *seat) { return true; } void nullseat_sent(Seat *seat, size_t bufsize) {} +size_t nullseat_banner(Seat *seat, const void *data, size_t len) {return 0;} +size_t nullseat_banner_to_stderr(Seat *seat, const void *data, size_t len) +{ return seat_output(seat, SEAT_OUTPUT_STDERR, data, len); } int nullseat_get_userpass_input(Seat *seat, prompts_t *p) { return 0; } void nullseat_notify_session_started(Seat *seat) {} void nullseat_notify_remote_exit(Seat *seat) {} diff --git a/utils/tempseat.c b/utils/tempseat.c index f877dd1d..eac10f8d 100644 --- a/utils/tempseat.c +++ b/utils/tempseat.c @@ -224,6 +224,11 @@ static int tempseat_get_userpass_input(Seat *seat, prompts_t *p) unreachable("get_userpass_input should never be called on TempSeat"); } +static size_t tempseat_banner(Seat *seat, const void *data, size_t len) +{ + unreachable("banner should never be called on TempSeat"); +} + static int tempseat_confirm_ssh_host_key( Seat *seat, const char *host, int port, const char *keytype, char *keystr, const char *keydisp, char **key_fingerprints, bool mismatch, @@ -299,6 +304,7 @@ static const struct SeatVtable tempseat_vt = { .output = tempseat_output, .eof = tempseat_eof, .sent = nullseat_sent, + .banner = tempseat_banner, .get_userpass_input = tempseat_get_userpass_input, .notify_session_started = tempseat_notify_session_started, .notify_remote_exit = tempseat_notify_remote_exit, diff --git a/windows/plink.c b/windows/plink.c index 7506a13f..ef982be1 100644 --- a/windows/plink.c +++ b/windows/plink.c @@ -85,6 +85,7 @@ static const SeatVtable plink_seat_vt = { .output = plink_output, .eof = plink_eof, .sent = nullseat_sent, + .banner = nullseat_banner_to_stderr, .get_userpass_input = plink_get_userpass_input, .notify_session_started = nullseat_notify_session_started, .notify_remote_exit = nullseat_notify_remote_exit, diff --git a/windows/window.c b/windows/window.c index 4c67009b..1aa83743 100644 --- a/windows/window.c +++ b/windows/window.c @@ -332,6 +332,7 @@ static const SeatVtable win_seat_vt = { .output = win_seat_output, .eof = win_seat_eof, .sent = nullseat_sent, + .banner = nullseat_banner_to_stderr, .get_userpass_input = win_seat_get_userpass_input, .notify_session_started = nullseat_notify_session_started, .notify_remote_exit = win_seat_notify_remote_exit, -- cgit v1.2.3 From 44db74ec513d36b95d28bb718f85e594b18c49de Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 30 Oct 2021 17:16:08 +0100 Subject: Introduce a new 'Interactor' trait. This trait will be implemented by anything that wants to display interactive prompts or notifications to the user in the course of setting up a network connection, _or_ anything that wants to make a network connection whose proxy setup might in turn need to do that. To begin with, that means every Backend that makes network connections at all must be an Interactor, because any of those network connections might be proxied via an SSH jump host which might need to interact with the user. I'll fill in the contents of this trait over the next few commits, to keep the patches comprehensible. For the moment, I've just introduced the trait, set up implementations of it in the five network backends, and given it a single 'description' method. The previous 'description' methods of Backend and Plug are now removed, and their work is done by the new Interactor method instead. (I changed my mind since last week about where that should best live.) This isn't too much of an upheaval, fortunately, because I hadn't got round yet to committing anything that used those methods! --- defs.h | 2 ++ network.h | 22 --------------- otherbackends/raw.c | 27 +++++++++--------- otherbackends/rlogin.c | 27 +++++++++--------- otherbackends/supdup.c | 17 ++++++------ otherbackends/telnet.c | 27 +++++++++--------- putty.h | 75 ++++++++++++++++++++++++++++++++------------------ ssh/ssh.c | 28 +++++++++---------- 8 files changed, 110 insertions(+), 115 deletions(-) diff --git a/defs.h b/defs.h index e5d3fefe..172a30b7 100644 --- a/defs.h +++ b/defs.h @@ -101,6 +101,8 @@ typedef struct SocketPeerInfo SocketPeerInfo; typedef struct Backend Backend; typedef struct BackendVtable BackendVtable; +typedef struct Interactor Interactor; +typedef struct InteractorVtable InteractorVtable; typedef struct Ldisc_tag Ldisc; typedef struct LogContext LogContext; diff --git a/network.h b/network.h index 0b600f63..099895bc 100644 --- a/network.h +++ b/network.h @@ -129,26 +129,6 @@ struct PlugVtable { * want the connection for some reason, or 0 on success. */ int (*accepting)(Plug *p, accept_fn_t constructor, accept_ctx_t ctx); - - /* - * Returns a user-facing description of the nature of the network - * connection being made. Used in interactive proxy authentication - * to announce which connection attempt is now in control of the - * Seat. - * - * The idea is not just to be written in natural language, but to - * connect with the user's idea of _why_ they think some - * connection is being made. For example, instead of saying 'TCP - * connection to 123.45.67.89 port 22', you might say 'SSH - * connection to [logical host name for SSH host key purposes]'. - * - * This function pointer may be NULL, or may exist but return - * NULL, in which case no user-facing description is available. - * - * If a non-NULL string is returned, it must be freed by the - * caller. - */ - char *(*description)(Plug *p); }; /* Proxy indirection layer. @@ -256,8 +236,6 @@ static inline void plug_sent (Plug *p, size_t bufsize) { p->vt->sent(p, bufsize); } static inline int plug_accepting(Plug *p, accept_fn_t cons, accept_ctx_t ctx) { return p->vt->accepting(p, cons, ctx); } -static inline char *plug_description(Plug *p) -{ return p->vt->description ? p->vt->description(p) : NULL; } /* * Special error values are returned from sk_namelookup and sk_new diff --git a/otherbackends/raw.c b/otherbackends/raw.c index 569d2c62..9a391d94 100644 --- a/otherbackends/raw.c +++ b/otherbackends/raw.c @@ -25,6 +25,7 @@ struct Raw { Plug plug; Backend backend; + Interactor interactor; }; static void raw_size(Backend *be, int width, int height); @@ -116,24 +117,21 @@ static void raw_sent(Plug *plug, size_t bufsize) seat_sent(raw->seat, raw->bufsize); } -static char *raw_plug_description(Plug *plug) -{ - Raw *raw = container_of(plug, Raw, plug); - return dupstr(raw->description); -} - -static char *raw_backend_description(Backend *backend) -{ - Raw *raw = container_of(backend, Raw, backend); - return dupstr(raw->description); -} - static const PlugVtable Raw_plugvt = { .log = raw_log, .closing = raw_closing, .receive = raw_receive, .sent = raw_sent, - .description = raw_plug_description, +}; + +static char *raw_description(Interactor *itr) +{ + Raw *raw = container_of(itr, Raw, interactor); + return dupstr(raw->description); +} + +static const InteractorVtable Raw_interactorvt = { + .description = raw_description, }; /* @@ -159,6 +157,8 @@ static char *raw_init(const BackendVtable *vt, Seat *seat, memset(raw, 0, sizeof(Raw)); raw->plug.vt = &Raw_plugvt; raw->backend.vt = vt; + raw->interactor.vt = &Raw_interactorvt; + raw->backend.interactor = &raw->interactor; raw->s = NULL; raw->closed_on_socket_error = false; *backend_handle = &raw->backend; @@ -354,7 +354,6 @@ const BackendVtable raw_backend = { .provide_ldisc = raw_provide_ldisc, .unthrottle = raw_unthrottle, .cfg_info = raw_cfg_info, - .description = raw_backend_description, .id = "raw", .displayname_tc = "Raw", .displayname_lc = "raw", diff --git a/otherbackends/rlogin.c b/otherbackends/rlogin.c index b9b21786..3ab3c070 100644 --- a/otherbackends/rlogin.c +++ b/otherbackends/rlogin.c @@ -32,6 +32,7 @@ struct Rlogin { Plug plug; Backend backend; + Interactor interactor; }; static void rlogin_startup(Rlogin *rlogin, int prompt_result, @@ -193,24 +194,21 @@ static void rlogin_startup(Rlogin *rlogin, int prompt_result, ldisc_check_sendok(rlogin->ldisc); } -static char *rlogin_plug_description(Plug *plug) -{ - Rlogin *rlogin = container_of(plug, Rlogin, plug); - return dupstr(rlogin->description); -} - -static char *rlogin_backend_description(Backend *backend) -{ - Rlogin *rlogin = container_of(backend, Rlogin, backend); - return dupstr(rlogin->description); -} - static const PlugVtable Rlogin_plugvt = { .log = rlogin_log, .closing = rlogin_closing, .receive = rlogin_receive, .sent = rlogin_sent, - .description = rlogin_plug_description, +}; + +static char *rlogin_description(Interactor *itr) +{ + Rlogin *rlogin = container_of(itr, Rlogin, interactor); + return dupstr(rlogin->description); +} + +static const InteractorVtable Rlogin_interactorvt = { + .description = rlogin_description, }; /* @@ -236,6 +234,8 @@ static char *rlogin_init(const BackendVtable *vt, Seat *seat, memset(rlogin, 0, sizeof(Rlogin)); rlogin->plug.vt = &Rlogin_plugvt; rlogin->backend.vt = vt; + rlogin->interactor.vt = &Rlogin_interactorvt; + rlogin->backend.interactor = &rlogin->interactor; rlogin->s = NULL; rlogin->closed_on_socket_error = false; rlogin->seat = seat; @@ -461,7 +461,6 @@ const BackendVtable rlogin_backend = { .provide_ldisc = rlogin_provide_ldisc, .unthrottle = rlogin_unthrottle, .cfg_info = rlogin_cfg_info, - .description = rlogin_backend_description, .id = "rlogin", .displayname_tc = "Rlogin", .displayname_lc = "Rlogin", /* proper name, so capitalise it anyway */ diff --git a/otherbackends/supdup.c b/otherbackends/supdup.c index 3bde828e..3588e9f3 100644 --- a/otherbackends/supdup.c +++ b/otherbackends/supdup.c @@ -107,6 +107,7 @@ struct supdup_tag Plug plug; Backend backend; + Interactor interactor; }; #define SUPDUP_MAX_BACKLOG 4096 @@ -642,17 +643,15 @@ static void supdup_send_config(Supdup *supdup) supdup_send_36bits(supdup, TTYROL); // scroll amount } -static char *supdup_plug_description(Plug *plug) +static char *supdup_description(Interactor *itr) { - Supdup *supdup = container_of(plug, Supdup, plug); + Supdup *supdup = container_of(itr, Supdup, interactor); return dupstr(supdup->description); } -static char *supdup_backend_description(Backend *backend) -{ - Supdup *supdup = container_of(backend, Supdup, backend); - return dupstr(supdup->description); -} +static const InteractorVtable Supdup_interactorvt = { + .description = supdup_description, +}; /* * Called to set up the Supdup connection. @@ -673,7 +672,6 @@ static char *supdup_init(const BackendVtable *x, Seat *seat, .closing = supdup_closing, .receive = supdup_receive, .sent = supdup_sent, - .description = supdup_plug_description, }; SockAddr *addr; const char *err; @@ -686,6 +684,8 @@ static char *supdup_init(const BackendVtable *x, Seat *seat, memset(supdup, 0, sizeof(Supdup)); supdup->plug.vt = &fn_table; supdup->backend.vt = &supdup_backend; + supdup->interactor.vt = &Supdup_interactorvt; + supdup->backend.interactor = &supdup->interactor; supdup->logctx = logctx; supdup->conf = conf_copy(conf); supdup->s = NULL; @@ -952,7 +952,6 @@ const BackendVtable supdup_backend = { .provide_ldisc = supdup_provide_ldisc, .unthrottle = supdup_unthrottle, .cfg_info = supdup_cfg_info, - .description = supdup_backend_description, .id = "supdup", .displayname_tc = "SUPDUP", .displayname_lc = "SUPDUP", /* proper name, so capitalise it anyway */ diff --git a/otherbackends/telnet.c b/otherbackends/telnet.c index 819cbcb0..bbf539c9 100644 --- a/otherbackends/telnet.c +++ b/otherbackends/telnet.c @@ -200,6 +200,7 @@ struct Telnet { Plug plug; Backend backend; + Interactor interactor; }; #define TELNET_MAX_BACKLOG 4096 @@ -681,24 +682,21 @@ static void telnet_sent(Plug *plug, size_t bufsize) seat_sent(telnet->seat, telnet->bufsize); } -static char *telnet_plug_description(Plug *plug) -{ - Telnet *telnet = container_of(plug, Telnet, plug); - return dupstr(telnet->description); -} - -static char *telnet_backend_description(Backend *backend) -{ - Telnet *telnet = container_of(backend, Telnet, backend); - return dupstr(telnet->description); -} - static const PlugVtable Telnet_plugvt = { .log = telnet_log, .closing = telnet_closing, .receive = telnet_receive, .sent = telnet_sent, - .description = telnet_plug_description, +}; + +static char *telnet_description(Interactor *itr) +{ + Telnet *telnet = container_of(itr, Telnet, interactor); + return dupstr(telnet->description); +} + +static const InteractorVtable Telnet_interactorvt = { + .description = telnet_description, }; /* @@ -724,6 +722,8 @@ static char *telnet_init(const BackendVtable *vt, Seat *seat, memset(telnet, 0, sizeof(Telnet)); telnet->plug.vt = &Telnet_plugvt; telnet->backend.vt = vt; + telnet->interactor.vt = &Telnet_interactorvt; + telnet->backend.interactor = &telnet->interactor; telnet->conf = conf_copy(conf); telnet->s = NULL; telnet->socket_connected = false; @@ -1098,7 +1098,6 @@ const BackendVtable telnet_backend = { .provide_ldisc = telnet_provide_ldisc, .unthrottle = telnet_unthrottle, .cfg_info = telnet_cfg_info, - .description = telnet_backend_description, .id = "telnet", .displayname_tc = "Telnet", .displayname_lc = "Telnet", /* proper name, so capitalise it anyway */ diff --git a/putty.h b/putty.h index 4e1f9dde..ffbfa000 100644 --- a/putty.h +++ b/putty.h @@ -633,8 +633,56 @@ enum { /* In (no)sshproxy.c */ extern const bool ssh_proxy_supported; +/* + * The Interactor trait is implemented by anything that is capable of + * presenting interactive prompts or questions to the user during + * network connection setup. Every Backend that ever needs to do this + * is an Interactor, but also, while a Backend is making its initial + * network connection, it may go via network proxy code which is also + * an Interactor and can ask questions of its own. + */ +struct Interactor { + const InteractorVtable *vt; +}; + +struct InteractorVtable { + /* + * Returns a user-facing description of the nature of the network + * connection being made. Used in interactive proxy authentication + * to announce which connection attempt is now in control of the + * Seat. + * + * The idea is not just to be written in natural language, but to + * connect with the user's idea of _why_ they think some + * connection is being made. For example, instead of saying 'TCP + * connection to 123.45.67.89 port 22', you might say 'SSH + * connection to [logical host name for SSH host key purposes]'. + * + * The returned string must be freed by the caller. + */ + char *(*description)(Interactor *itr); +}; + +static inline char *interactor_description(Interactor *itr) +{ return itr->vt->description(itr); } + +/* Interactors that are Backends will find this helper function useful + * in constructing their description strings */ +char *default_description(const BackendVtable *backvt, + const char *host, int port); + +/* + * The Backend trait is the top-level one that governs each of the + * user-facing main modes that PuTTY can use to talk to some + * destination: SSH, Telnet, serial port, pty, etc. + */ + struct Backend { const BackendVtable *vt; + + /* Many Backends are also Interactors. If this one is, a pointer + * to its Interactor trait lives here. */ + Interactor *interactor; }; struct BackendVtable { char *(*init) (const BackendVtable *vt, Seat *seat, @@ -680,28 +728,6 @@ struct BackendVtable { * connections that would be lost if this one were terminated. */ char *(*close_warn_text)(Backend *be); - /* - * Returns a user-facing description of the nature of the network - * connection being made. Used in interactive proxy authentication - * to announce which connection attempt is now in control of the - * Seat. - * - * The idea is not just to be written in natural language, but to - * connect with the user's idea of _why_ they think some - * connection is being made. For example, instead of saying 'TCP - * connection to 123.45.67.89 port 22', you might say 'SSH - * connection to [logical host name for SSH host key purposes]'. - * - * This function pointer may be NULL, or may exist but return - * NULL, in which case no user-facing description is available. - * (Backends which are never proxied, such as pty and ConPTY, need - * not bother to fill this in.) - * - * If a non-NULL string is returned, it must be freed by the - * caller. - */ - char *(*description)(Backend *be); - /* 'id' is a machine-readable name for the backend, used in * saved-session storage. 'displayname_tc' and 'displayname_lc' * are human-readable names, one in title-case for config boxes, @@ -750,11 +776,6 @@ static inline void backend_unthrottle(Backend *be, size_t bufsize) { be->vt->unthrottle(be, bufsize); } static inline int backend_cfg_info(Backend *be) { return be->vt->cfg_info(be); } -static inline char *backend_description(Backend *be) -{ return be->vt->description ? be->vt->description(be) : NULL; } - -char *default_description(const BackendVtable *backvt, - const char *host, int port); extern const struct BackendVtable *const backends[]; /* diff --git a/ssh/ssh.c b/ssh/ssh.c index c9ce2b0b..e240c0c7 100644 --- a/ssh/ssh.c +++ b/ssh/ssh.c @@ -39,6 +39,7 @@ struct Ssh { Plug plug; Backend backend; + Interactor interactor; Ldisc *ldisc; LogContext *logctx; @@ -718,24 +719,21 @@ static char *ssh_close_warn_text(Backend *be) return msg; } -static char *ssh_plug_description(Plug *plug) -{ - Ssh *ssh = container_of(plug, Ssh, plug); - return dupstr(ssh->description); -} - -static char *ssh_backend_description(Backend *backend) -{ - Ssh *ssh = container_of(backend, Ssh, backend); - return dupstr(ssh->description); -} - static const PlugVtable Ssh_plugvt = { .log = ssh_socket_log, .closing = ssh_closing, .receive = ssh_receive, .sent = ssh_sent, - .description = ssh_plug_description, +}; + +static char *ssh_description(Interactor *itr) +{ + Ssh *ssh = container_of(itr, Ssh, interactor); + return dupstr(ssh->description); +} + +static const InteractorVtable Ssh_interactorvt = { + .description = ssh_description, }; /* @@ -940,6 +938,8 @@ static char *ssh_init(const BackendVtable *vt, Seat *seat, ssh->term_height = conf_get_int(ssh->conf, CONF_height); ssh->backend.vt = vt; + ssh->interactor.vt = &Ssh_interactorvt; + ssh->backend.interactor = &ssh->interactor; *backend_handle = &ssh->backend; ssh->bare_connection = (vt->protocol == PROT_SSHCONN); @@ -1264,7 +1264,6 @@ const BackendVtable ssh_backend = { .cfg_info = ssh_cfg_info, .test_for_upstream = ssh_test_for_upstream, .close_warn_text = ssh_close_warn_text, - .description = ssh_backend_description, .id = "ssh", .displayname_tc = "SSH", .displayname_lc = "SSH", /* proper name, so capitalise it anyway */ @@ -1291,7 +1290,6 @@ const BackendVtable sshconn_backend = { .cfg_info = ssh_cfg_info, .test_for_upstream = ssh_test_for_upstream, .close_warn_text = ssh_close_warn_text, - .description = ssh_backend_description, .id = "ssh-connection", .displayname_tc = "Bare ssh-connection", .displayname_lc = "bare ssh-connection", -- cgit v1.2.3 From aac5e096fa0579c754776167c1486bdb693c065d Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 30 Oct 2021 17:34:53 +0100 Subject: Add Interactor methods to get/set LogPolicy and Seat. Nothing uses this yet, but the next commit will. --- otherbackends/raw.c | 21 +++++++++++++++++++++ otherbackends/rlogin.c | 21 +++++++++++++++++++++ otherbackends/supdup.c | 21 +++++++++++++++++++++ otherbackends/telnet.c | 21 +++++++++++++++++++++ putty.h | 22 ++++++++++++++++++++++ ssh/ssh.c | 21 +++++++++++++++++++++ 6 files changed, 127 insertions(+) diff --git a/otherbackends/raw.c b/otherbackends/raw.c index 9a391d94..7e80e42b 100644 --- a/otherbackends/raw.c +++ b/otherbackends/raw.c @@ -130,8 +130,29 @@ static char *raw_description(Interactor *itr) return dupstr(raw->description); } +static LogPolicy *raw_logpolicy(Interactor *itr) +{ + Raw *raw = container_of(itr, Raw, interactor); + return log_get_policy(raw->logctx); +} + +static Seat *raw_get_seat(Interactor *itr) +{ + Raw *raw = container_of(itr, Raw, interactor); + return raw->seat; +} + +static void raw_set_seat(Interactor *itr, Seat *seat) +{ + Raw *raw = container_of(itr, Raw, interactor); + raw->seat = seat; +} + static const InteractorVtable Raw_interactorvt = { .description = raw_description, + .logpolicy = raw_logpolicy, + .get_seat = raw_get_seat, + .set_seat = raw_set_seat, }; /* diff --git a/otherbackends/rlogin.c b/otherbackends/rlogin.c index 3ab3c070..6da67070 100644 --- a/otherbackends/rlogin.c +++ b/otherbackends/rlogin.c @@ -207,8 +207,29 @@ static char *rlogin_description(Interactor *itr) return dupstr(rlogin->description); } +static LogPolicy *rlogin_logpolicy(Interactor *itr) +{ + Rlogin *rlogin = container_of(itr, Rlogin, interactor); + return log_get_policy(rlogin->logctx); +} + +static Seat *rlogin_get_seat(Interactor *itr) +{ + Rlogin *rlogin = container_of(itr, Rlogin, interactor); + return rlogin->seat; +} + +static void rlogin_set_seat(Interactor *itr, Seat *seat) +{ + Rlogin *rlogin = container_of(itr, Rlogin, interactor); + rlogin->seat = seat; +} + static const InteractorVtable Rlogin_interactorvt = { .description = rlogin_description, + .logpolicy = rlogin_logpolicy, + .get_seat = rlogin_get_seat, + .set_seat = rlogin_set_seat, }; /* diff --git a/otherbackends/supdup.c b/otherbackends/supdup.c index 3588e9f3..f680c521 100644 --- a/otherbackends/supdup.c +++ b/otherbackends/supdup.c @@ -649,8 +649,29 @@ static char *supdup_description(Interactor *itr) return dupstr(supdup->description); } +static LogPolicy *supdup_logpolicy(Interactor *itr) +{ + Supdup *supdup = container_of(itr, Supdup, interactor); + return log_get_policy(supdup->logctx); +} + +static Seat *supdup_get_seat(Interactor *itr) +{ + Supdup *supdup = container_of(itr, Supdup, interactor); + return supdup->seat; +} + +static void supdup_set_seat(Interactor *itr, Seat *seat) +{ + Supdup *supdup = container_of(itr, Supdup, interactor); + supdup->seat = seat; +} + static const InteractorVtable Supdup_interactorvt = { .description = supdup_description, + .logpolicy = supdup_logpolicy, + .get_seat = supdup_get_seat, + .set_seat = supdup_set_seat, }; /* diff --git a/otherbackends/telnet.c b/otherbackends/telnet.c index bbf539c9..fecee06c 100644 --- a/otherbackends/telnet.c +++ b/otherbackends/telnet.c @@ -695,8 +695,29 @@ static char *telnet_description(Interactor *itr) return dupstr(telnet->description); } +static LogPolicy *telnet_logpolicy(Interactor *itr) +{ + Telnet *telnet = container_of(itr, Telnet, interactor); + return log_get_policy(telnet->logctx); +} + +static Seat *telnet_get_seat(Interactor *itr) +{ + Telnet *telnet = container_of(itr, Telnet, interactor); + return telnet->seat; +} + +static void telnet_set_seat(Interactor *itr, Seat *seat) +{ + Telnet *telnet = container_of(itr, Telnet, interactor); + telnet->seat = seat; +} + static const InteractorVtable Telnet_interactorvt = { .description = telnet_description, + .logpolicy = telnet_logpolicy, + .get_seat = telnet_get_seat, + .set_seat = telnet_set_seat, }; /* diff --git a/putty.h b/putty.h index ffbfa000..7887535f 100644 --- a/putty.h +++ b/putty.h @@ -661,10 +661,32 @@ struct InteractorVtable { * The returned string must be freed by the caller. */ char *(*description)(Interactor *itr); + + /* + * Returns the LogPolicy associated with this Interactor. (A + * Backend can derive this from its logging context; a proxy + * Interactor inherits it from the Interactor for the parent + * network connection.) + */ + LogPolicy *(*logpolicy)(Interactor *itr); + + /* + * Gets and sets the Seat that this Interactor talks to. When a + * Seat is borrowed and replaced with a TempSeat, this will be the + * mechanism by which that replacement happens. + */ + Seat *(*get_seat)(Interactor *itr); + void (*set_seat)(Interactor *itr, Seat *seat); }; static inline char *interactor_description(Interactor *itr) { return itr->vt->description(itr); } +static inline LogPolicy *interactor_logpolicy(Interactor *itr) +{ return itr->vt->logpolicy(itr); } +static inline Seat *interactor_get_seat(Interactor *itr) +{ return itr->vt->get_seat(itr); } +static inline void interactor_set_seat(Interactor *itr, Seat *seat) +{ itr->vt->set_seat(itr, seat); } /* Interactors that are Backends will find this helper function useful * in constructing their description strings */ diff --git a/ssh/ssh.c b/ssh/ssh.c index e240c0c7..adc55391 100644 --- a/ssh/ssh.c +++ b/ssh/ssh.c @@ -732,8 +732,29 @@ static char *ssh_description(Interactor *itr) return dupstr(ssh->description); } +static LogPolicy *ssh_logpolicy(Interactor *itr) +{ + Ssh *ssh = container_of(itr, Ssh, interactor); + return log_get_policy(ssh->logctx); +} + +static Seat *ssh_get_seat(Interactor *itr) +{ + Ssh *ssh = container_of(itr, Ssh, interactor); + return ssh->seat; +} + +static void ssh_set_seat(Interactor *itr, Seat *seat) +{ + Ssh *ssh = container_of(itr, Ssh, interactor); + ssh->seat = seat; +} + static const InteractorVtable Ssh_interactorvt = { .description = ssh_description, + .logpolicy = ssh_logpolicy, + .get_seat = ssh_get_seat, + .set_seat = ssh_set_seat, }; /* -- cgit v1.2.3 From 89a390bdeb5b624e17789518efb843e3a88b5417 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 30 Oct 2021 17:36:52 +0100 Subject: Pass an Interactor to new_connection(). Thanks to the previous commit, this new parameter can replace two of the existing ones: instead of passing a LogPolicy and a Seat, we now pass just an Interactor, from which any proxy implementation can extract the LogPolicy and the Seat anyway if they need it. --- network.h | 24 +++++++++--------------- otherbackends/raw.c | 3 +-- otherbackends/rlogin.c | 2 +- otherbackends/supdup.c | 2 +- otherbackends/telnet.c | 2 +- proxy/noproxy.c | 2 +- proxy/nosshproxy.c | 3 +-- proxy/proxy.c | 4 ++-- proxy/sshproxy.c | 39 +++++++++++++++++++++------------------ ssh/portfwd.c | 2 +- ssh/ssh.c | 3 +-- ssh/x11fwd.c | 2 +- unix/pageant.c | 2 +- unix/sharing.c | 2 +- 14 files changed, 43 insertions(+), 49 deletions(-) diff --git a/network.h b/network.h index 099895bc..068e6cc6 100644 --- a/network.h +++ b/network.h @@ -137,19 +137,14 @@ struct PlugVtable { * layer is now responsible for freeing it, and the caller shouldn't * assume it exists any more. * - * You can optionally pass a LogPolicy to this function, which will be - * passed on in turn to proxy types that can use one (e.g. SSH jump - * host proxy). If you don't have one, all proxy types are required to - * be able to manage without (and will just degrade their logging - * control). - * * If calling this from a backend with a Seat, you can also give it a - * pointer to your 'Seat *'. In that situation, it might replace the - * 'Seat *' with a temporary seat of its own, and give the real Seat - * to the proxy system so that it can ask for passwords (and, in the - * case of SSH proxying, other prompts like host key checks). If that - * happens, then the resulting 'temp seat' is the backend's property, - * and it will have to remember to free it when cleaning up, or after + * pointer to the backend's Interactor trait. In that situation, it + * might replace the backend's seat with a temporary seat of its own, + * and give the real Seat to an Interactor somewhere in the proxy + * system so that it can ask for passwords (and, in the case of SSH + * proxying, other prompts like host key checks). If that happens, + * then the resulting 'temp seat' is the backend's property, and it + * will have to remember to free it when cleaning up, or after * flushing it back into the real seat when the network connection * attempt completes. * @@ -163,7 +158,7 @@ struct PlugVtable { Socket *new_connection(SockAddr *addr, const char *hostname, int port, bool privport, bool oobinline, bool nodelay, bool keepalive, - Plug *plug, Conf *conf, LogPolicy *lp, Seat **seat); + Plug *plug, Conf *conf, Interactor *interactor); Socket *new_listener(const char *srcaddr, int port, Plug *plug, bool local_host_only, Conf *conf, int addressfamily); SockAddr *name_lookup(const char *host, int port, char **canonicalname, @@ -181,8 +176,7 @@ Socket *platform_new_connection(SockAddr *addr, const char *hostname, Socket *sshproxy_new_connection(SockAddr *addr, const char *hostname, int port, bool privport, bool oobinline, bool nodelay, bool keepalive, - Plug *plug, Conf *conf, - LogPolicy *clientlp, Seat **clientseat); + Plug *plug, Conf *conf, Interactor *itr); /* socket functions */ diff --git a/otherbackends/raw.c b/otherbackends/raw.c index 7e80e42b..a5b3f427 100644 --- a/otherbackends/raw.c +++ b/otherbackends/raw.c @@ -210,8 +210,7 @@ static char *raw_init(const BackendVtable *vt, Seat *seat, * Open socket. */ raw->s = new_connection(addr, *realhost, port, false, true, nodelay, - keepalive, &raw->plug, conf, - log_get_policy(logctx), &raw->seat); + keepalive, &raw->plug, conf, &raw->interactor); if ((err = sk_socket_error(raw->s)) != NULL) return dupstr(err); diff --git a/otherbackends/rlogin.c b/otherbackends/rlogin.c index 6da67070..035295c9 100644 --- a/otherbackends/rlogin.c +++ b/otherbackends/rlogin.c @@ -290,7 +290,7 @@ static char *rlogin_init(const BackendVtable *vt, Seat *seat, */ rlogin->s = new_connection(addr, *realhost, port, true, false, nodelay, keepalive, &rlogin->plug, conf, - log_get_policy(logctx), &rlogin->seat); + &rlogin->interactor); if ((err = sk_socket_error(rlogin->s)) != NULL) return dupstr(err); diff --git a/otherbackends/supdup.c b/otherbackends/supdup.c index f680c521..a46bfd9a 100644 --- a/otherbackends/supdup.c +++ b/otherbackends/supdup.c @@ -759,7 +759,7 @@ static char *supdup_init(const BackendVtable *x, Seat *seat, */ supdup->s = new_connection(addr, *realhost, port, false, true, nodelay, keepalive, &supdup->plug, supdup->conf, - log_get_policy(logctx), &supdup->seat); + &supdup->interactor); if ((err = sk_socket_error(supdup->s)) != NULL) return dupstr(err); diff --git a/otherbackends/telnet.c b/otherbackends/telnet.c index fecee06c..df23f2f2 100644 --- a/otherbackends/telnet.c +++ b/otherbackends/telnet.c @@ -782,7 +782,7 @@ static char *telnet_init(const BackendVtable *vt, Seat *seat, */ telnet->s = new_connection(addr, *realhost, port, false, true, nodelay, keepalive, &telnet->plug, telnet->conf, - log_get_policy(logctx), &telnet->seat); + &telnet->interactor); if ((err = sk_socket_error(telnet->s)) != NULL) return dupstr(err); diff --git a/proxy/noproxy.c b/proxy/noproxy.c index 82347d51..248688e0 100644 --- a/proxy/noproxy.c +++ b/proxy/noproxy.c @@ -20,7 +20,7 @@ SockAddr *name_lookup(const char *host, int port, char **canonicalname, Socket *new_connection(SockAddr *addr, const char *hostname, int port, bool privport, bool oobinline, bool nodelay, bool keepalive, - Plug *plug, Conf *conf, LogPolicy *lp, Seat **seat) + Plug *plug, Conf *conf, Interactor *itr) { return sk_new(addr, port, privport, oobinline, nodelay, keepalive, plug); } diff --git a/proxy/nosshproxy.c b/proxy/nosshproxy.c index 5f2bbdca..1160b8de 100644 --- a/proxy/nosshproxy.c +++ b/proxy/nosshproxy.c @@ -10,8 +10,7 @@ const bool ssh_proxy_supported = false; Socket *sshproxy_new_connection(SockAddr *addr, const char *hostname, int port, bool privport, bool oobinline, bool nodelay, bool keepalive, - Plug *plug, Conf *conf, - LogPolicy *clientlp, Seat **clientseat) + Plug *plug, Conf *conf, Interactor *itr) { return NULL; } diff --git a/proxy/proxy.c b/proxy/proxy.c index 08d5afba..69b32866 100644 --- a/proxy/proxy.c +++ b/proxy/proxy.c @@ -393,7 +393,7 @@ static const PlugVtable ProxySocket_plugvt = { Socket *new_connection(SockAddr *addr, const char *hostname, int port, bool privport, bool oobinline, bool nodelay, bool keepalive, - Plug *plug, Conf *conf, LogPolicy *lp, Seat **seat) + Plug *plug, Conf *conf, Interactor *itr) { int type = conf_get_int(conf, CONF_proxy_type); @@ -409,7 +409,7 @@ Socket *new_connection(SockAddr *addr, const char *hostname, if (type == PROXY_SSH && (sret = sshproxy_new_connection(addr, hostname, port, privport, oobinline, nodelay, keepalive, - plug, conf, lp, seat)) != NULL) + plug, conf, itr)) != NULL) return sret; if ((sret = platform_new_connection(addr, hostname, port, privport, diff --git a/proxy/sshproxy.c b/proxy/sshproxy.c index d113cb04..8d8a0e90 100644 --- a/proxy/sshproxy.c +++ b/proxy/sshproxy.c @@ -483,7 +483,7 @@ Socket *sshproxy_new_connection(SockAddr *addr, const char *hostname, int port, bool privport, bool oobinline, bool nodelay, bool keepalive, Plug *plug, Conf *clientconf, - LogPolicy *clientlp, Seat **clientseat) + Interactor *clientitr) { SshProxy *sp = snew(SshProxy); memset(sp, 0, sizeof(*sp)); @@ -594,24 +594,27 @@ Socket *sshproxy_new_connection(SockAddr *addr, const char *hostname, sfree(realhost); /* - * If we've been given useful bits and pieces for interacting with - * the end user, squirrel them away now. + * If we've been given an Interactor by the caller, squirrel away + * things it's holding. */ - sp->clientlp = clientlp; - if (clientseat && (backvt->flags & BACKEND_NOTIFIES_SESSION_START)) { - /* - * We can only keep the client's Seat if our own backend will - * tell us when to give it back. (SSH-based backends _should_ - * do that, but we check the flag here anyway.) - * - * Also, check if the client already has a TempSeat, and if - * so, don't wrap it with another one. - */ - if (is_tempseat(*clientseat)) { - sp->clientseat = tempseat_get_real(*clientseat); - } else { - sp->clientseat = *clientseat; - *clientseat = tempseat_new(sp->clientseat); + if (clientitr) { + sp->clientlp = interactor_logpolicy(clientitr); + if (backvt->flags & BACKEND_NOTIFIES_SESSION_START) { + /* + * We can only keep the client's Seat if our own backend will + * tell us when to give it back. (SSH-based backends _should_ + * do that, but we check the flag here anyway.) + * + * Also, check if the client already has a TempSeat, and if + * so, don't wrap it with another one. + */ + Seat *clientseat = interactor_get_seat(clientitr); + if (is_tempseat(clientseat)) { + sp->clientseat = tempseat_get_real(clientseat); + } else { + sp->clientseat = clientseat; + interactor_set_seat(clientitr, tempseat_new(sp->clientseat)); + } } } diff --git a/ssh/portfwd.c b/ssh/portfwd.c index 9dd35ea4..204f0632 100644 --- a/ssh/portfwd.c +++ b/ssh/portfwd.c @@ -1159,7 +1159,7 @@ char *portfwdmgr_connect(PortFwdManager *mgr, Channel **chan_ret, pf->s = new_connection(addr, dummy_realhost, port, false, true, false, false, &pf->plug, mgr->conf, - NULL, NULL); + NULL); sfree(dummy_realhost); if ((err = sk_socket_error(pf->s)) != NULL) { char *err_ret = dupstr(err); diff --git a/ssh/ssh.c b/ssh/ssh.c index adc55391..1def173e 100644 --- a/ssh/ssh.c +++ b/ssh/ssh.c @@ -826,8 +826,7 @@ static char *connect_to_host( ssh->s = new_connection(addr, *realhost, port, false, true, nodelay, keepalive, - &ssh->plug, ssh->conf, - log_get_policy(ssh->logctx), &ssh->seat); + &ssh->plug, ssh->conf, &ssh->interactor); if ((err = sk_socket_error(ssh->s)) != NULL) { ssh->s = NULL; seat_notify_remote_exit(ssh->seat); diff --git a/ssh/x11fwd.c b/ssh/x11fwd.c index 6886dace..1d2fe512 100644 --- a/ssh/x11fwd.c +++ b/ssh/x11fwd.c @@ -563,7 +563,7 @@ static size_t x11_send( xconn->s = new_connection(sk_addr_dup(xconn->disp->addr), xconn->disp->realhost, xconn->disp->port, false, true, false, false, &xconn->plug, - sshfwd_get_conf(xconn->c), NULL, NULL); + sshfwd_get_conf(xconn->c), NULL); if ((err = sk_socket_error(xconn->s)) != NULL) { char *err_message = dupprintf("unable to connect to" " forwarded X server: %s", err); diff --git a/unix/pageant.c b/unix/pageant.c index a0b2e9e2..2fa3eeaa 100644 --- a/unix/pageant.c +++ b/unix/pageant.c @@ -1189,7 +1189,7 @@ void run_agent(FILE *logfp, const char *symlink_path) s = new_connection(sk_addr_dup(disp->addr), disp->realhost, disp->port, false, true, false, false, &conn->plug, conf, - NULL, NULL); + NULL); if ((err = sk_socket_error(s)) != NULL) { fprintf(stderr, "pageant: unable to connect to X server: %s", err); exit(1); diff --git a/unix/sharing.c b/unix/sharing.c index 66628723..8db2d71e 100644 --- a/unix/sharing.c +++ b/unix/sharing.c @@ -297,7 +297,7 @@ int platform_ssh_share(const char *pi_name, Conf *conf, if (can_downstream) { retsock = new_connection(unix_sock_addr(sockname), "", 0, false, true, false, false, - downplug, conf, NULL, NULL); + downplug, conf, NULL); if (sk_socket_error(retsock) == NULL) { sfree(*logtext); *logtext = sockname; -- cgit v1.2.3 From f00c72cc2a92ce2c49e6951a05860ddca551e1cd Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 30 Oct 2021 18:05:36 +0100 Subject: Framework for announcing which Interactor is talking. All this Interactor business has been gradually working towards being able to inform the user _which_ network connection is currently presenting them with a password prompt (or whatever), in situations where more than one of them might be, such as an SSH connection being used as a proxy for another SSH connection when neither one has one-touch login configured. At some point, we have to arrange that any attempt to do a user interaction during connection setup - be it a password prompt, a host key confirmation dialog, or just displaying an SSH login banner - makes it clear which host it's come from. That's going to mean calling some kind of announcement function before doing any of those things. But there are several of those functions in the Seat API, and calls to them are scattered far and wide across the SSH backend. (And not even just there - the Rlogin backend also uses seat_get_userpass_input). How can we possibly make sure we don't forget a vital call site on some obscure little-tested code path, and leave the user confused in just that one case which nobody might notice for years? Today I thought of a trick to solve that problem. We can use the C type system to enforce it for us! The plan is: we invent a new struct type which contains nothing but a 'Seat *'. Then, for every Seat method which does a thing that ought to be clearly identified as relating to a particular Interactor, we adjust the API for that function to take the new struct type where it previously took a plain 'Seat *'. Or rather - doing less violence to the existing code - we only need to adjust the API of the dispatch functions inline in putty.h. How does that help? Because the way you _get_ one of these struct-wrapped Seat pointers is by calling interactor_announce() on your Interactor, which will in turn call interactor_get_seat(), and wrap the returned pointer into one of these structs. The effect is that whenever the SSH (or Rlogin) code wants to call one of those particular Seat methods, it _has_ to call interactor_announce() just beforehand, which (once I finish all of this) will make sure the user is aware of who is presenting the prompt or banner or whatever. And you can't forget to call it, because if you don't call it, then you just don't have a struct of the right type to give to the Seat method you wanted to call! (Of course, there's nothing stopping code from _deliberately_ taking a Seat * it already has and wrapping it into the new struct. In fact SshProxy has to do that, in order to forward these requests up the chain of Seats. But the point is that you can't do it _by accident_, just by forgetting to make a vital function call - when you do that, you _know_ you're doing it on purpose.) No functional change: the new interactor_announce() function exists, and the type-system trick ensures it's called in all the right places, but it doesn't actually _do_ anything yet. --- CMakeLists.txt | 3 ++- defs.h | 1 + otherbackends/rlogin.c | 3 ++- proxy/interactor.c | 18 +++++++++++++++++ proxy/sshproxy.c | 17 +++++++++++----- putty.h | 54 ++++++++++++++++++++++++++++++++++++-------------- ssh.h | 4 ++-- ssh/common.c | 6 +++--- ssh/connection1.c | 4 ++-- ssh/connection2.c | 4 ++-- ssh/kex2-client.c | 4 ++-- ssh/login1.c | 18 +++++++++-------- ssh/ppl.h | 4 ++++ ssh/server.c | 1 + ssh/ssh.c | 1 + ssh/transport2.c | 4 ++-- ssh/userauth2-client.c | 35 ++++++++++++++++---------------- utils/antispoof.c | 8 ++++---- 18 files changed, 125 insertions(+), 64 deletions(-) create mode 100644 proxy/interactor.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 15670d69..5ccd8570 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,7 +33,8 @@ add_library(crypto STATIC add_subdirectory(crypto) add_library(network STATIC - be_misc.c nullplug.c errsock.c proxy/proxy.c logging.c x11disp.c) + be_misc.c nullplug.c errsock.c logging.c x11disp.c + proxy/proxy.c proxy/interactor.c) add_library(keygen STATIC import.c) diff --git a/defs.h b/defs.h index 172a30b7..437bef97 100644 --- a/defs.h +++ b/defs.h @@ -103,6 +103,7 @@ typedef struct Backend Backend; typedef struct BackendVtable BackendVtable; typedef struct Interactor Interactor; typedef struct InteractorVtable InteractorVtable; +typedef struct InteractionReadySeat InteractionReadySeat; typedef struct Ldisc_tag Ldisc; typedef struct LogContext LogContext; diff --git a/otherbackends/rlogin.c b/otherbackends/rlogin.c index 035295c9..099f115b 100644 --- a/otherbackends/rlogin.c +++ b/otherbackends/rlogin.c @@ -335,7 +335,8 @@ static void rlogin_try_username_prompt(void *ctx) { Rlogin *rlogin = (Rlogin *)ctx; - int ret = seat_get_userpass_input(rlogin->seat, rlogin->prompt); + int ret = seat_get_userpass_input( + interactor_announce(&rlogin->interactor), rlogin->prompt); if (ret < 0) return; diff --git a/proxy/interactor.c b/proxy/interactor.c new file mode 100644 index 00000000..8006ae64 --- /dev/null +++ b/proxy/interactor.c @@ -0,0 +1,18 @@ +/* + * Centralised functions for the Interactor trait. + */ + +#include "putty.h" + +InteractionReadySeat interactor_announce(Interactor *itr) +{ + Seat *seat = interactor_get_seat(itr); + + /* TODO: print an announcement of this Interactor's identity, when + * appropriate */ + + InteractionReadySeat iseat; + iseat.seat = seat; + + return iseat; +} diff --git a/proxy/sshproxy.c b/proxy/sshproxy.c index 8d8a0e90..55de65e6 100644 --- a/proxy/sshproxy.c +++ b/proxy/sshproxy.c @@ -267,6 +267,13 @@ static size_t sshproxy_output(Seat *seat, SeatOutputType type, return bufchain_size(&sp->ssh_to_socket); } +static inline InteractionReadySeat wrap(Seat *seat) +{ + InteractionReadySeat iseat; + iseat.seat = seat; + return iseat; +} + static size_t sshproxy_banner(Seat *seat, const void *data, size_t len) { SshProxy *sp = container_of(seat, SshProxy, seat); @@ -275,7 +282,7 @@ static size_t sshproxy_banner(Seat *seat, const void *data, size_t len) * If we have access to the outer Seat, pass the SSH login * banner on to it. */ - return seat_banner(sp->clientseat, data, len); + return seat_banner(wrap(sp->clientseat), data, len); } else { return 0; } @@ -311,7 +318,7 @@ static int sshproxy_get_userpass_input(Seat *seat, prompts_t *p) * If we have access to the outer Seat, pass this prompt * request on to it. FIXME: appropriately adjusted */ - return seat_get_userpass_input(sp->clientseat, p); + return seat_get_userpass_input(wrap(sp->clientseat), p); } /* @@ -353,7 +360,7 @@ static int sshproxy_confirm_ssh_host_key( * request on to it. FIXME: appropriately adjusted */ return seat_confirm_ssh_host_key( - sp->clientseat, host, port, keytype, keystr, keydisp, + wrap(sp->clientseat), host, port, keytype, keystr, keydisp, key_fingerprints, mismatch, callback, ctx); } @@ -377,7 +384,7 @@ static int sshproxy_confirm_weak_crypto_primitive( * request on to it. FIXME: appropriately adjusted */ return seat_confirm_weak_crypto_primitive( - sp->clientseat, algtype, algname, callback, ctx); + wrap(sp->clientseat), algtype, algname, callback, ctx); } /* @@ -402,7 +409,7 @@ static int sshproxy_confirm_weak_cached_hostkey( * request on to it. FIXME: appropriately adjusted */ return seat_confirm_weak_cached_hostkey( - sp->clientseat, algname, betteralgs, callback, ctx); + wrap(sp->clientseat), algname, betteralgs, callback, ctx); } /* diff --git a/putty.h b/putty.h index 7887535f..5c7a01f0 100644 --- a/putty.h +++ b/putty.h @@ -633,6 +633,24 @@ enum { /* In (no)sshproxy.c */ extern const bool ssh_proxy_supported; +/* + * This structure type wraps a Seat pointer, in a way that has no + * purpose except to be a different type. + * + * The Seat wrapper functions that present interactive prompts all + * expect one of these in place of their ordinary Seat pointer. You + * get one by calling interactor_announce (defined below), which will + * print a message (if not already done) identifying the Interactor + * that originated the prompt. + * + * This arranges that the C type system itself will check that no call + * to any of those Seat methods has omitted the mandatory call to + * interactor_announce beforehand. + */ +struct InteractionReadySeat { + Seat *seat; +}; + /* * The Interactor trait is implemented by anything that is capable of * presenting interactive prompts or questions to the user during @@ -688,6 +706,8 @@ static inline Seat *interactor_get_seat(Interactor *itr) static inline void interactor_set_seat(Interactor *itr, Seat *seat) { itr->vt->set_seat(itr, seat); } +InteractionReadySeat interactor_announce(Interactor *itr); + /* Interactors that are Backends will find this helper function useful * in constructing their description strings */ char *default_description(const BackendVtable *backvt, @@ -1277,10 +1297,12 @@ static inline bool seat_eof(Seat *seat) { return seat->vt->eof(seat); } static inline void seat_sent(Seat *seat, size_t bufsize) { seat->vt->sent(seat, bufsize); } -static inline size_t seat_banner(Seat *seat, const void *data, size_t len) -{ return seat->vt->banner(seat, data, len); } -static inline int seat_get_userpass_input(Seat *seat, prompts_t *p) -{ return seat->vt->get_userpass_input(seat, p); } +static inline size_t seat_banner( + InteractionReadySeat iseat, const void *data, size_t len) +{ return iseat.seat->vt->banner(iseat.seat, data, len); } +static inline int seat_get_userpass_input(InteractionReadySeat iseat, + prompts_t *p) +{ return iseat.seat->vt->get_userpass_input(iseat.seat, p); } static inline void seat_notify_session_started(Seat *seat) { seat->vt->notify_session_started(seat); } static inline void seat_notify_remote_exit(Seat *seat) @@ -1294,19 +1316,21 @@ static inline char *seat_get_ttymode(Seat *seat, const char *mode) static inline void seat_set_busy_status(Seat *seat, BusyStatus status) { seat->vt->set_busy_status(seat, status); } static inline int seat_confirm_ssh_host_key( - Seat *seat, const char *h, int p, const char *ktyp, char *kstr, - const char *kdsp, char **fps, bool mis, + InteractionReadySeat iseat, const char *h, int p, const char *ktyp, + char *kstr, const char *kdsp, char **fps, bool mis, void (*cb)(void *ctx, int result), void *ctx) -{ return seat->vt->confirm_ssh_host_key(seat, h, p, ktyp, kstr, kdsp, fps, - mis, cb, ctx); } +{ return iseat.seat->vt->confirm_ssh_host_key( + iseat.seat, h, p, ktyp, kstr, kdsp, fps, mis, cb, ctx); } static inline int seat_confirm_weak_crypto_primitive( - Seat *seat, const char *atyp, const char *aname, + InteractionReadySeat iseat, const char *atyp, const char *aname, void (*cb)(void *ctx, int result), void *ctx) -{ return seat->vt->confirm_weak_crypto_primitive(seat, atyp, aname, cb, ctx); } +{ return iseat.seat->vt->confirm_weak_crypto_primitive( + iseat.seat, atyp, aname, cb, ctx); } static inline int seat_confirm_weak_cached_hostkey( - Seat *seat, const char *aname, const char *better, + InteractionReadySeat iseat, const char *aname, const char *better, void (*cb)(void *ctx, int result), void *ctx) -{ return seat->vt->confirm_weak_cached_hostkey(seat, aname, better, cb, ctx); } +{ return iseat.seat->vt->confirm_weak_cached_hostkey( + iseat.seat, aname, better, cb, ctx); } static inline bool seat_is_utf8(Seat *seat) { return seat->vt->is_utf8(seat); } static inline void seat_echoedit_update(Seat *seat, bool ec, bool ed) @@ -1347,12 +1371,12 @@ static inline size_t seat_stderr_pl(Seat *seat, ptrlen data) { return seat_output(seat, SEAT_OUTPUT_STDERR, data.ptr, data.len); } /* Alternative API for seat_banner taking a ptrlen */ -static inline size_t seat_banner_pl(Seat *seat, ptrlen data) -{ return seat->vt->banner(seat, data.ptr, data.len); } +static inline size_t seat_banner_pl(InteractionReadySeat iseat, ptrlen data) +{ return iseat.seat->vt->banner(iseat.seat, data.ptr, data.len); } /* In the utils subdir: print a message to the Seat which can't be * spoofed by server-supplied auth-time output such as SSH banners */ -void seat_antispoof_msg(Seat *seat, const char *msg); +void seat_antispoof_msg(InteractionReadySeat iseat, const char *msg); /* * Stub methods for seat implementations that want to use the obvious diff --git a/ssh.h b/ssh.h index e633da20..c32ff686 100644 --- a/ssh.h +++ b/ssh.h @@ -1708,8 +1708,8 @@ void add_to_commasep(strbuf *buf, const char *data); bool get_commasep_word(ptrlen *list, ptrlen *word); int verify_ssh_host_key( - Seat *seat, Conf *conf, const char *host, int port, ssh_key *key, - const char *keytype, char *keystr, const char *keydisp, + InteractionReadySeat iseat, Conf *conf, const char *host, int port, + ssh_key *key, const char *keytype, char *keystr, const char *keydisp, char **fingerprints, void (*callback)(void *ctx, int result), void *ctx); typedef struct ssh_transient_hostkey_cache ssh_transient_hostkey_cache; diff --git a/ssh/common.c b/ssh/common.c index ee09910a..c7d98061 100644 --- a/ssh/common.c +++ b/ssh/common.c @@ -851,8 +851,8 @@ bool ssh2_bpp_check_unimplemented(BinaryPacketProtocol *bpp, PktIn *pktin) */ int verify_ssh_host_key( - Seat *seat, Conf *conf, const char *host, int port, ssh_key *key, - const char *keytype, char *keystr, const char *keydisp, + InteractionReadySeat iseat, Conf *conf, const char *host, int port, + ssh_key *key, const char *keytype, char *keystr, const char *keydisp, char **fingerprints, void (*callback)(void *ctx, int result), void *ctx) { /* @@ -923,7 +923,7 @@ int verify_ssh_host_key( */ bool mismatch = (storage_status != 1); return seat_confirm_ssh_host_key( - seat, host, port, keytype, keystr, keydisp, fingerprints, mismatch, + iseat, host, port, keytype, keystr, keydisp, fingerprints, mismatch, callback, ctx); } diff --git a/ssh/connection1.c b/ssh/connection1.c index e0b4aac6..47ffd3c7 100644 --- a/ssh/connection1.c +++ b/ssh/connection1.c @@ -380,11 +380,11 @@ static void ssh1_connection_process_queue(PacketProtocolLayer *ppl) s->antispoof_prompt, dupstr("Access granted. Press Return to begin session. "), false); s->antispoof_ret = seat_get_userpass_input( - s->ppl.seat, s->antispoof_prompt); + ppl_get_iseat(&s->ppl), s->antispoof_prompt); while (s->antispoof_ret < 0) { crReturnV; s->antispoof_ret = seat_get_userpass_input( - s->ppl.seat, s->antispoof_prompt); + ppl_get_iseat(&s->ppl), s->antispoof_prompt); } free_prompts(s->antispoof_prompt); s->antispoof_prompt = NULL; diff --git a/ssh/connection2.c b/ssh/connection2.c index a6bc553b..86bd315a 100644 --- a/ssh/connection2.c +++ b/ssh/connection2.c @@ -993,11 +993,11 @@ static void ssh2_connection_process_queue(PacketProtocolLayer *ppl) s->antispoof_prompt, dupstr("Access granted. Press Return to begin session. "), false); s->antispoof_ret = seat_get_userpass_input( - s->ppl.seat, s->antispoof_prompt); + ppl_get_iseat(&s->ppl), s->antispoof_prompt); while (s->antispoof_ret < 0) { crReturnV; s->antispoof_ret = seat_get_userpass_input( - s->ppl.seat, s->antispoof_prompt); + ppl_get_iseat(&s->ppl), s->antispoof_prompt); } free_prompts(s->antispoof_prompt); s->antispoof_prompt = NULL; diff --git a/ssh/kex2-client.c b/ssh/kex2-client.c index 2435534c..8a3a290a 100644 --- a/ssh/kex2-client.c +++ b/ssh/kex2-client.c @@ -854,8 +854,8 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) ppl_logevent("%s", fingerprints[fptype_default]); s->dlgret = verify_ssh_host_key( - s->ppl.seat, s->conf, s->savedhost, s->savedport, s->hkey, - ssh_key_cache_id(s->hkey), s->keystr, keydisp, + ppl_get_iseat(&s->ppl), s->conf, s->savedhost, s->savedport, + s->hkey, ssh_key_cache_id(s->hkey), s->keystr, keydisp, fingerprints, ssh2_transport_dialog_callback, s); ssh2_free_all_fingerprints(fingerprints); diff --git a/ssh/login1.c b/ssh/login1.c index 001b3d8e..62c7b866 100644 --- a/ssh/login1.c +++ b/ssh/login1.c @@ -243,7 +243,7 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl) char **fingerprints = rsa_ssh1_fake_all_fingerprints(&s->hostkey); s->dlgret = verify_ssh_host_key( - s->ppl.seat, s->conf, s->savedhost, s->savedport, NULL, + ppl_get_iseat(&s->ppl), s->conf, s->savedhost, s->savedport, NULL, "rsa", keystr, keydisp, fingerprints, ssh1_login_dialog_callback, s); @@ -325,7 +325,7 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl) /* Warn about chosen cipher if necessary. */ if (warn) { s->dlgret = seat_confirm_weak_crypto_primitive( - s->ppl.seat, "cipher", cipher_string, + ppl_get_iseat(&s->ppl), "cipher", cipher_string, ssh1_login_dialog_callback, s); crMaybeWaitUntilV(s->dlgret >= 0); if (s->dlgret == 0) { @@ -391,11 +391,12 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl) s->cur_prompt->from_server = false; s->cur_prompt->name = dupstr("SSH login name"); add_prompt(s->cur_prompt, dupstr("login as: "), true); - s->userpass_ret = seat_get_userpass_input(s->ppl.seat, s->cur_prompt); + s->userpass_ret = seat_get_userpass_input( + ppl_get_iseat(&s->ppl), s->cur_prompt); while (s->userpass_ret < 0) { crReturnV; s->userpass_ret = seat_get_userpass_input( - s->ppl.seat, s->cur_prompt); + ppl_get_iseat(&s->ppl), s->cur_prompt); } if (!s->userpass_ret) { /* @@ -688,11 +689,11 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl) dupprintf("Passphrase for key \"%s\": ", s->publickey_comment), false); s->userpass_ret = seat_get_userpass_input( - s->ppl.seat, s->cur_prompt); + ppl_get_iseat(&s->ppl), s->cur_prompt); while (s->userpass_ret < 0) { crReturnV; s->userpass_ret = seat_get_userpass_input( - s->ppl.seat, s->cur_prompt); + ppl_get_iseat(&s->ppl), s->cur_prompt); } if (!s->userpass_ret) { /* Failed to get a passphrase. Terminate. */ @@ -942,11 +943,12 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl) * or CryptoCard exchange if we're doing TIS or CryptoCard * authentication. */ - s->userpass_ret = seat_get_userpass_input(s->ppl.seat, s->cur_prompt); + s->userpass_ret = seat_get_userpass_input( + ppl_get_iseat(&s->ppl), s->cur_prompt); while (s->userpass_ret < 0) { crReturnV; s->userpass_ret = seat_get_userpass_input( - s->ppl.seat, s->cur_prompt); + ppl_get_iseat(&s->ppl), s->cur_prompt); } if (!s->userpass_ret) { /* diff --git a/ssh/ppl.h b/ssh/ppl.h index 66f46038..c3625166 100644 --- a/ssh/ppl.h +++ b/ssh/ppl.h @@ -49,6 +49,7 @@ struct PacketProtocolLayer { /* Logging and error-reporting facilities. */ LogContext *logctx; Seat *seat; /* for dialog boxes, session output etc */ + Interactor *interactor; /* for ppl_get_iseat */ Ssh *ssh; /* for session termination + assorted connection-layer ops */ /* Known bugs in the remote implementation. */ @@ -68,6 +69,9 @@ static inline void ssh_ppl_reconfigure(PacketProtocolLayer *ppl, Conf *conf) static inline size_t ssh_ppl_queued_data_size(PacketProtocolLayer *ppl) { return ppl->vt->queued_data_size(ppl); } +static inline InteractionReadySeat ppl_get_iseat(PacketProtocolLayer *ppl) +{ return interactor_announce(ppl->interactor); } + /* ssh_ppl_free is more than just a macro wrapper on the vtable; it * does centralised parts of the freeing too. */ void ssh_ppl_free(PacketProtocolLayer *ppl); diff --git a/ssh/server.c b/ssh/server.c index 724abaf4..888ff87d 100644 --- a/ssh/server.c +++ b/ssh/server.c @@ -375,6 +375,7 @@ static void server_connect_ppl(server *srv, PacketProtocolLayer *ppl) ppl->logctx = srv->logctx; ppl->ssh = &srv->ssh; ppl->seat = &srv->seat; + ppl->interactor = NULL; ppl->remote_bugs = srv->remote_bugs; } diff --git a/ssh/ssh.c b/ssh/ssh.c index 1def173e..8a70b665 100644 --- a/ssh/ssh.c +++ b/ssh/ssh.c @@ -168,6 +168,7 @@ static void ssh_connect_ppl(Ssh *ssh, PacketProtocolLayer *ppl) { ppl->bpp = ssh->bpp; ppl->seat = ssh->seat; + ppl->interactor = &ssh->interactor; ppl->ssh = ssh; ppl->logctx = ssh->logctx; ppl->remote_bugs = ssh->remote_bugs; diff --git a/ssh/transport2.c b/ssh/transport2.c index 9c49d55c..f0975431 100644 --- a/ssh/transport2.c +++ b/ssh/transport2.c @@ -1313,7 +1313,7 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl) /* Use the special warning prompt that lets us provide * a list of better algorithms */ s->dlgret = seat_confirm_weak_cached_hostkey( - s->ppl.seat, s->hostkey_alg->ssh_id, betteralgs, + ppl_get_iseat(&s->ppl), s->hostkey_alg->ssh_id, betteralgs, ssh2_transport_dialog_callback, s); sfree(betteralgs); } else { @@ -2148,7 +2148,7 @@ static int ssh2_transport_confirm_weak_crypto_primitive( add234(s->weak_algorithms_consented_to, (void *)alg); return seat_confirm_weak_crypto_primitive( - s->ppl.seat, type, name, ssh2_transport_dialog_callback, s); + ppl_get_iseat(&s->ppl), type, name, ssh2_transport_dialog_callback, s); } static size_t ssh2_transport_queued_data_size(PacketProtocolLayer *ppl) diff --git a/ssh/userauth2-client.c b/ssh/userauth2-client.c index e82abe8a..b6ed5704 100644 --- a/ssh/userauth2-client.c +++ b/ssh/userauth2-client.c @@ -443,11 +443,11 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) s->cur_prompt->name = dupstr("SSH login name"); add_prompt(s->cur_prompt, dupstr("login as: "), true); s->userpass_ret = seat_get_userpass_input( - s->ppl.seat, s->cur_prompt); + ppl_get_iseat(&s->ppl), s->cur_prompt); while (s->userpass_ret < 0) { crReturnV; s->userpass_ret = seat_get_userpass_input( - s->ppl.seat, s->cur_prompt); + ppl_get_iseat(&s->ppl), s->cur_prompt); } if (!s->userpass_ret) { /* @@ -521,7 +521,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) (seat_verbose(s->ppl.seat) || seat_interactive(s->ppl.seat))) { if (s->banner_scc) { seat_antispoof_msg( - s->ppl.seat, + ppl_get_iseat(&s->ppl), "Pre-authentication banner message from server:"); seat_set_trust_status(s->ppl.seat, false); } @@ -529,7 +529,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) bool mid_line = false; while (bufchain_size(&s->banner) > 0) { ptrlen data = bufchain_prefix(&s->banner); - seat_banner_pl(s->ppl.seat, data); + seat_banner_pl(ppl_get_iseat(&s->ppl), data); mid_line = (((const char *)data.ptr)[data.len-1] != '\n'); bufchain_consume(&s->banner, data.len); @@ -537,11 +537,12 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) bufchain_clear(&s->banner); if (mid_line) - seat_banner_pl(s->ppl.seat, PTRLEN_LITERAL("\r\n")); + seat_banner_pl(ppl_get_iseat(&s->ppl), + PTRLEN_LITERAL("\r\n")); if (s->banner_scc) { seat_set_trust_status(s->ppl.seat, true); - seat_antispoof_msg(s->ppl.seat, + seat_antispoof_msg(ppl_get_iseat(&s->ppl), "End of banner message from server"); } } @@ -912,11 +913,11 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) s->publickey_comment), false); s->userpass_ret = seat_get_userpass_input( - s->ppl.seat, s->cur_prompt); + ppl_get_iseat(&s->ppl), s->cur_prompt); while (s->userpass_ret < 0) { crReturnV; s->userpass_ret = seat_get_userpass_input( - s->ppl.seat, s->cur_prompt); + ppl_get_iseat(&s->ppl), s->cur_prompt); } if (!s->userpass_ret) { /* Failed to get a passphrase. Terminate. */ @@ -1343,8 +1344,8 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) if (!s->ki_printed_header && s->ki_scc && (s->num_prompts || name.len || inst.len)) { seat_antispoof_msg( - s->ppl.seat, "Keyboard-interactive authentication " - "prompts from server:"); + ppl_get_iseat(&s->ppl), "Keyboard-interactive " + "authentication prompts from server:"); s->ki_printed_header = true; seat_set_trust_status(s->ppl.seat, false); } @@ -1391,11 +1392,11 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) * user's response(s). */ s->userpass_ret = seat_get_userpass_input( - s->ppl.seat, s->cur_prompt); + ppl_get_iseat(&s->ppl), s->cur_prompt); while (s->userpass_ret < 0) { crReturnV; s->userpass_ret = seat_get_userpass_input( - s->ppl.seat, s->cur_prompt); + ppl_get_iseat(&s->ppl), s->cur_prompt); } if (!s->userpass_ret) { /* @@ -1446,7 +1447,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) if (s->ki_printed_header) { seat_set_trust_status(s->ppl.seat, true); seat_antispoof_msg( - s->ppl.seat, + ppl_get_iseat(&s->ppl), "End of keyboard-interactive prompts from server"); } @@ -1473,11 +1474,11 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) false); s->userpass_ret = seat_get_userpass_input( - s->ppl.seat, s->cur_prompt); + ppl_get_iseat(&s->ppl), s->cur_prompt); while (s->userpass_ret < 0) { crReturnV; s->userpass_ret = seat_get_userpass_input( - s->ppl.seat, s->cur_prompt); + ppl_get_iseat(&s->ppl), s->cur_prompt); } if (!s->userpass_ret) { /* @@ -1585,11 +1586,11 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) */ while (!got_new) { s->userpass_ret = seat_get_userpass_input( - s->ppl.seat, s->cur_prompt); + ppl_get_iseat(&s->ppl), s->cur_prompt); while (s->userpass_ret < 0) { crReturnV; s->userpass_ret = seat_get_userpass_input( - s->ppl.seat, s->cur_prompt); + ppl_get_iseat(&s->ppl), s->cur_prompt); } if (!s->userpass_ret) { /* diff --git a/utils/antispoof.c b/utils/antispoof.c index d8599455..3a633189 100644 --- a/utils/antispoof.c +++ b/utils/antispoof.c @@ -1,11 +1,11 @@ #include "putty.h" #include "misc.h" -void seat_antispoof_msg(Seat *seat, const char *msg) +void seat_antispoof_msg(InteractionReadySeat iseat, const char *msg) { strbuf *sb = strbuf_new(); - seat_set_trust_status(seat, true); - if (seat_can_set_trust_status(seat)) { + seat_set_trust_status(iseat.seat, true); + if (seat_can_set_trust_status(iseat.seat)) { /* * If the seat can directly indicate that this message is * generated by the client, then we can just use the message @@ -23,6 +23,6 @@ void seat_antispoof_msg(Seat *seat, const char *msg) put_byte(sb, '-'); } put_datapl(sb, PTRLEN_LITERAL("\r\n")); - seat_banner_pl(seat, ptrlen_from_strbuf(sb)); + seat_banner_pl(iseat, ptrlen_from_strbuf(sb)); strbuf_free(sb); } -- cgit v1.2.3 From 746059443373b8a4cd73d85f67fe473a2c56667f Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 30 Oct 2021 17:45:38 +0100 Subject: Move TempSeat creation/destruction into Interactor. Previously, SshProxy dealt with creating a TempSeat to wrap the one it was borrowing from its client, and then each client in turn dealt with detecting when it had had its seat borrowed and finishing up with the TempSeat. The latter involved a lot of code duplication; the former didn't involve code duplication _yet_ (since SshProxy was the only thing doing this job), but would have once we started wanting to do interactive password prompting for other types of network proxy. Now all of that functionality is centralised into two new Interactor helper functions: interactor_borrow_seat and interactor_return_seat. --- otherbackends/raw.c | 6 ------ otherbackends/rlogin.c | 6 ------ otherbackends/supdup.c | 6 ------ otherbackends/telnet.c | 6 ------ proxy/interactor.c | 37 +++++++++++++++++++++++++++++++++++++ proxy/sshproxy.c | 34 ++++++++++++++-------------------- putty.h | 8 +++++--- ssh/ssh.c | 9 --------- 8 files changed, 56 insertions(+), 56 deletions(-) diff --git a/otherbackends/raw.c b/otherbackends/raw.c index a5b3f427..80ce8fe1 100644 --- a/otherbackends/raw.c +++ b/otherbackends/raw.c @@ -46,12 +46,6 @@ static void raw_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, raw->socket_connected = true; if (raw->ldisc) ldisc_check_sendok(raw->ldisc); - if (is_tempseat(raw->seat)) { - Seat *ts = raw->seat; - tempseat_flush(ts); - raw->seat = tempseat_get_real(ts); - tempseat_free(ts); - } } } diff --git a/otherbackends/rlogin.c b/otherbackends/rlogin.c index 099f115b..e626eff4 100644 --- a/otherbackends/rlogin.c +++ b/otherbackends/rlogin.c @@ -54,12 +54,6 @@ static void rlogin_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, rlogin->conf, rlogin->socket_connected); if (type == PLUGLOG_CONNECT_SUCCESS) { rlogin->socket_connected = true; - if (is_tempseat(rlogin->seat)) { - Seat *ts = rlogin->seat; - tempseat_flush(ts); - rlogin->seat = tempseat_get_real(ts); - tempseat_free(ts); - } char *ruser = get_remote_username(rlogin->conf); if (ruser) { diff --git a/otherbackends/supdup.c b/otherbackends/supdup.c index a46bfd9a..c1867086 100644 --- a/otherbackends/supdup.c +++ b/otherbackends/supdup.c @@ -570,12 +570,6 @@ static void supdup_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, supdup->socket_connected = true; if (supdup->ldisc) ldisc_check_sendok(supdup->ldisc); - if (is_tempseat(supdup->seat)) { - Seat *ts = supdup->seat; - tempseat_flush(ts); - supdup->seat = tempseat_get_real(ts); - tempseat_free(ts); - } } } diff --git a/otherbackends/telnet.c b/otherbackends/telnet.c index df23f2f2..66f7308e 100644 --- a/otherbackends/telnet.c +++ b/otherbackends/telnet.c @@ -632,12 +632,6 @@ static void telnet_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, telnet->socket_connected = true; if (telnet->ldisc) ldisc_check_sendok(telnet->ldisc); - if (is_tempseat(telnet->seat)) { - Seat *ts = telnet->seat; - tempseat_flush(ts); - telnet->seat = tempseat_get_real(ts); - tempseat_free(ts); - } } } diff --git a/proxy/interactor.c b/proxy/interactor.c index 8006ae64..ccd3e42e 100644 --- a/proxy/interactor.c +++ b/proxy/interactor.c @@ -4,6 +4,43 @@ #include "putty.h" +Seat *interactor_borrow_seat(Interactor *itr) +{ + Seat *clientseat = interactor_get_seat(itr); + if (!clientseat) + return NULL; + + /* If the client has already had its Seat borrowed, then look + * through the existing TempSeat to find the underlying one. */ + if (is_tempseat(clientseat)) + return tempseat_get_real(clientseat); + + /* Otherwise, make a new TempSeat and give that to the client. */ + Seat *tempseat = tempseat_new(clientseat); + interactor_set_seat(itr, tempseat); + return clientseat; +} + +void interactor_return_seat(Interactor *itr) +{ + Seat *tempseat = interactor_get_seat(itr); + if (!is_tempseat(tempseat)) + return; /* no-op */ + + tempseat_flush(tempseat); + Seat *realseat = tempseat_get_real(tempseat); + interactor_set_seat(itr, realseat); + tempseat_free(tempseat); + + /* + * We're about to hand this seat back to the parent Interactor to + * do its own thing with. It will typically expect to start in the + * same state as if the seat had never been borrowed, i.e. in the + * starting trust state. + */ + seat_set_trust_status(realseat, true); +} + InteractionReadySeat interactor_announce(Interactor *itr) { Seat *seat = interactor_get_seat(itr); diff --git a/proxy/sshproxy.c b/proxy/sshproxy.c index 55de65e6..5ab81e61 100644 --- a/proxy/sshproxy.c +++ b/proxy/sshproxy.c @@ -44,6 +44,7 @@ typedef struct SshProxy { Backend *backend; LogPolicy *clientlp; Seat *clientseat; + Interactor *clientitr; ProxyStderrBuf psb; Plug *plug; @@ -253,7 +254,7 @@ static void sshproxy_notify_session_started(Seat *seat) SshProxy *sp = container_of(seat, SshProxy, seat); if (sp->clientseat) - seat_set_trust_status(sp->clientseat, true); + interactor_return_seat(sp->clientitr); plug_log(sp->plug, PLUGLOG_CONNECT_SUCCESS, sp->addr, sp->port, NULL, 0); } @@ -601,28 +602,21 @@ Socket *sshproxy_new_connection(SockAddr *addr, const char *hostname, sfree(realhost); /* - * If we've been given an Interactor by the caller, squirrel away - * things it's holding. + * If we've been given an Interactor by the caller, set ourselves + * up to work with it. */ if (clientitr) { + sp->clientitr = clientitr; + sp->clientlp = interactor_logpolicy(clientitr); - if (backvt->flags & BACKEND_NOTIFIES_SESSION_START) { - /* - * We can only keep the client's Seat if our own backend will - * tell us when to give it back. (SSH-based backends _should_ - * do that, but we check the flag here anyway.) - * - * Also, check if the client already has a TempSeat, and if - * so, don't wrap it with another one. - */ - Seat *clientseat = interactor_get_seat(clientitr); - if (is_tempseat(clientseat)) { - sp->clientseat = tempseat_get_real(clientseat); - } else { - sp->clientseat = clientseat; - interactor_set_seat(clientitr, tempseat_new(sp->clientseat)); - } - } + + /* + * We can only borrow the client's Seat if our own backend + * will tell us when to give it back. (SSH-based backends + * _should_ do that, but we check the flag here anyway.) + */ + if (backvt->flags & BACKEND_NOTIFIES_SESSION_START) + sp->clientseat = interactor_borrow_seat(clientitr); } return &sp->sock; diff --git a/putty.h b/putty.h index 5c7a01f0..edfd1171 100644 --- a/putty.h +++ b/putty.h @@ -706,6 +706,8 @@ static inline Seat *interactor_get_seat(Interactor *itr) static inline void interactor_set_seat(Interactor *itr, Seat *seat) { itr->vt->set_seat(itr, seat); } +Seat *interactor_borrow_seat(Interactor *itr); +void interactor_return_seat(Interactor *itr); InteractionReadySeat interactor_announce(Interactor *itr); /* Interactors that are Backends will find this helper function useful @@ -1468,9 +1470,9 @@ Seat *tempseat_new(Seat *real); bool is_tempseat(Seat *seat); Seat *tempseat_get_real(Seat *seat); -/* Called by the backend once the proxy connection has finished - * setting up (or failed), to pass on any buffered stuff to the real - * seat. */ +/* Called by interactor_return_seat once the proxy connection has + * finished setting up (or failed), to pass on any buffered stuff to + * the real seat. */ void tempseat_flush(Seat *ts); /* Frees a TempSeat, without flushing anything it has buffered. (Call diff --git a/ssh/ssh.c b/ssh/ssh.c index 8a70b665..ae7c1f6f 100644 --- a/ssh/ssh.c +++ b/ssh/ssh.c @@ -601,15 +601,6 @@ static void ssh_socket_log(Plug *plug, PlugLogType type, SockAddr *addr, backend_socket_log(ssh->seat, ssh->logctx, type, addr, port, error_msg, error_code, ssh->conf, ssh->session_started); - - if (type == PLUGLOG_CONNECT_SUCCESS) { - if (is_tempseat(ssh->seat)) { - Seat *ts = ssh->seat; - tempseat_flush(ts); - ssh->seat = tempseat_get_real(ts); - tempseat_free(ts); - } - } } static void ssh_closing(Plug *plug, const char *error_msg, int error_code) -- cgit v1.2.3 From 215b9d17759ccbde068f5af617f12ce45bf3f9fc Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 30 Oct 2021 18:08:02 +0100 Subject: Actually print announcements of Interactors' identity. Finally, the payoff from all of this refactoring: now, when a proxy prompts interactively during connection setup, you get a message in advance telling you which Interactor is originating the following messages. To achieve this, I've arranged to link Interactors together into a list, so that any Interactor created by a proxy has a 'parent' pointer pointing to the Interactor its client passed to new_connection(). This allows interactor_announce() to follow the links back up the chain and count them, so that it knows whether it's a primary connection, or a proxy, or a proxy-for-a-proxy, or more generally an nth-order proxy, and can include that in its announcement. And secondly, once interactor_announce() reaches the top of the chain, it can use that as a storage location agreed on by all Interactors in the whole setup, to tell each other which one of them was the last to do anything interactive. Then, whenever there's a change of Interactor, a message can be printed to indicate it to the user; and when the same Interactor does multiple things in succession, you don't get a slew of pointless messages in between them all. --- proxy/interactor.c | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++--- proxy/sshproxy.c | 9 +-------- putty.h | 15 +++++++++++++++ 3 files changed, 69 insertions(+), 11 deletions(-) diff --git a/proxy/interactor.c b/proxy/interactor.c index ccd3e42e..49359c1c 100644 --- a/proxy/interactor.c +++ b/proxy/interactor.c @@ -32,6 +32,13 @@ void interactor_return_seat(Interactor *itr) interactor_set_seat(itr, realseat); tempseat_free(tempseat); + /* + * If we have a parent Interactor, and anyone has ever called + * interactor_announce, then all Interactors from now on will + * announce themselves even if they have nothing to say. + */ + interactor_announce(itr); + /* * We're about to hand this seat back to the parent Interactor to * do its own thing with. It will typically expect to start in the @@ -44,12 +51,55 @@ void interactor_return_seat(Interactor *itr) InteractionReadySeat interactor_announce(Interactor *itr) { Seat *seat = interactor_get_seat(itr); - - /* TODO: print an announcement of this Interactor's identity, when - * appropriate */ + assert(!is_tempseat(seat) && + "Shouldn't call announce when someone else is using our seat"); InteractionReadySeat iseat; iseat.seat = seat; + /* + * Find the Interactor at the top of the chain, so that all the + * Interactors in a stack can share that one's last-to-talk field. + * Also, count how far we had to go to get to it, to put in the + * message. + */ + Interactor *itr_top = itr; + unsigned level = 0; + while (itr_top->parent) { + itr_top = itr_top->parent; + level++; + } + + /* + * Generally, we should announce ourself if the previous + * Interactor that said anything was not us. That includes if + * there was no previous Interactor to talk (i.e. if we're the + * first to say anything) - *except* that the primary Interactor + * doesn't need to announce itself, if no proxy has intervened + * before it. + */ + bool need_announcement = (itr_top->last_to_talk != itr); + if (!itr->parent && !itr_top->last_to_talk) + need_announcement = false; + + if (need_announcement) { + const char *prefix = ""; + if (itr_top->last_to_talk != NULL) + prefix = "\r\n"; + + char *desc = interactor_description(itr); + char *adjective = (level == 0 ? dupstr("primary") : + level == 1 ? dupstr("proxy") : + dupprintf("proxy^%u", level)); + char *msg = dupprintf("%sMaking %s %s", prefix, adjective, desc); + sfree(adjective); + sfree(desc); + + seat_antispoof_msg(iseat, msg); + sfree(msg); + + itr_top->last_to_talk = itr; + } + return iseat; } diff --git a/proxy/sshproxy.c b/proxy/sshproxy.c index 5ab81e61..ff830931 100644 --- a/proxy/sshproxy.c +++ b/proxy/sshproxy.c @@ -16,14 +16,6 @@ const bool ssh_proxy_supported = true; /* * TODO for future work: * - * All the interactive prompts we present to the main Seat - the host - * key and weak-crypto dialog boxes, and all prompts presented via the - * userpass_input system - need adjusting so that it's clear to the - * user _which_ SSH connection they come from. At the moment, you just - * get shown a host key fingerprint or a cryptic "login as:" prompt, - * and you have to guess which server you're currently supposed to be - * interpreting it relative to. - * * If the user manually aborts the attempt to make the proxy SSH * connection (e.g. by hitting ^C at a userpass prompt, or refusing to * accept the proxy server's host key), then an assertion failure @@ -607,6 +599,7 @@ Socket *sshproxy_new_connection(SockAddr *addr, const char *hostname, */ if (clientitr) { sp->clientitr = clientitr; + interactor_set_child(sp->clientitr, sp->backend->interactor); sp->clientlp = interactor_logpolicy(clientitr); diff --git a/putty.h b/putty.h index edfd1171..0047156f 100644 --- a/putty.h +++ b/putty.h @@ -661,6 +661,19 @@ struct InteractionReadySeat { */ struct Interactor { const InteractorVtable *vt; + + /* The parent Interactor that we are a proxy for, if any. */ + Interactor *parent; + + /* + * If we're the top-level Interactor (parent==NULL), then this + * field records the last Interactor that actually did anything + * interactive, so that we know when to announce a changeover + * between levels of proxying. + * + * If parent != NULL, this field is not used. + */ + Interactor *last_to_talk; }; struct InteractorVtable { @@ -706,6 +719,8 @@ static inline Seat *interactor_get_seat(Interactor *itr) static inline void interactor_set_seat(Interactor *itr, Seat *seat) { itr->vt->set_seat(itr, seat); } +static inline void interactor_set_child(Interactor *parent, Interactor *child) +{ child->parent = parent; } Seat *interactor_borrow_seat(Interactor *itr); void interactor_return_seat(Interactor *itr); InteractionReadySeat interactor_announce(Interactor *itr); -- cgit v1.2.3 From aca339d189b2665f7f1f100a71896f4eb4593081 Mon Sep 17 00:00:00 2001 From: Jacob Nevins Date: Thu, 4 Nov 2021 15:13:33 +0000 Subject: Make BugDropStart default to FORCE_OFF, not AUTO. Since it's a manually-enabled bug compatibility mode, AUTO isn't one of the available UI options. This was causing Windows PuTTY to display a blank entry in the drop-down for "Discards data sent before its greeting". (It is possible that this unhelpful default has escaped into saved sessions of snapshot users, which would have the same effect, but since the actual using code can cope with it, I've not done anything to clean that up.) --- settings.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings.c b/settings.c index ae0aebe0..89986703 100644 --- a/settings.c +++ b/settings.c @@ -1248,7 +1248,7 @@ void load_open_settings(settings_r *sesskey, Conf *conf) i = gppi_raw(sesskey, "BugOldGex2", 0); conf_set_int(conf, CONF_sshbug_oldgex2, 2-i); i = gppi_raw(sesskey, "BugWinadj", 0); conf_set_int(conf, CONF_sshbug_winadj, 2-i); i = gppi_raw(sesskey, "BugChanReq", 0); conf_set_int(conf, CONF_sshbug_chanreq, 2-i); - i = gppi_raw(sesskey, "BugDropStart", 0); conf_set_int(conf, CONF_sshbug_dropstart, 2-i); + i = gppi_raw(sesskey, "BugDropStart", 1); conf_set_int(conf, CONF_sshbug_dropstart, 2-i); conf_set_bool(conf, CONF_ssh_simple, false); gppb(sesskey, "StampUtmp", true, conf, CONF_stamp_utmp); gppb(sesskey, "LoginShell", true, conf, CONF_login_shell); -- cgit v1.2.3 From 1811f51b947c894a137a7abf3e8f1e3909fda30b Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 6 Nov 2021 11:31:40 +0000 Subject: sshproxy: improve a couple of comments. Removed the FIXMEs in various Seat passthrough functions that were there to remind me to say which SSH connection they referred to: that is now done by the interactor_announce mechanism, as of commit 215b9d17759ccbd. (Though not by modifying the actual passthrough functions, as it turned out, which is how I didn't find and remove the FIXMEs when I did all that.) Also, added a comment in wrap() explaining *why* it's allowed to (in fact, must) cheat by making an InteractionReadySeat without going through interactor_announce(). --- proxy/sshproxy.c | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/proxy/sshproxy.c b/proxy/sshproxy.c index ff830931..f8d52996 100644 --- a/proxy/sshproxy.c +++ b/proxy/sshproxy.c @@ -262,6 +262,20 @@ static size_t sshproxy_output(Seat *seat, SeatOutputType type, static inline InteractionReadySeat wrap(Seat *seat) { + /* + * When we receive interaction requests from the proxy and want to + * pass them on to our client Seat, we have to present them to the + * latter in the form of an InteractionReadySeat. This forwarding + * scenario is the one case where we _mustn't_ get an + * InteractionReadySeat by calling interactor_announce(), because + * the point is that we're _not_ the originating Interactor, we're + * just forwarding the request from the real one, which has + * already announced itself. + * + * So, just here in the code, it really is the right thing to make + * an InteractionReadySeat out of a plain Seat * without an + * announcement. + */ InteractionReadySeat iseat; iseat.seat = seat; return iseat; @@ -309,7 +323,7 @@ static int sshproxy_get_userpass_input(Seat *seat, prompts_t *p) if (sp->clientseat) { /* * If we have access to the outer Seat, pass this prompt - * request on to it. FIXME: appropriately adjusted + * request on to it. */ return seat_get_userpass_input(wrap(sp->clientseat), p); } @@ -350,7 +364,7 @@ static int sshproxy_confirm_ssh_host_key( if (sp->clientseat) { /* * If we have access to the outer Seat, pass this prompt - * request on to it. FIXME: appropriately adjusted + * request on to it. */ return seat_confirm_ssh_host_key( wrap(sp->clientseat), host, port, keytype, keystr, keydisp, @@ -374,7 +388,7 @@ static int sshproxy_confirm_weak_crypto_primitive( if (sp->clientseat) { /* * If we have access to the outer Seat, pass this prompt - * request on to it. FIXME: appropriately adjusted + * request on to it. */ return seat_confirm_weak_crypto_primitive( wrap(sp->clientseat), algtype, algname, callback, ctx); @@ -399,7 +413,7 @@ static int sshproxy_confirm_weak_cached_hostkey( if (sp->clientseat) { /* * If we have access to the outer Seat, pass this prompt - * request on to it. FIXME: appropriately adjusted + * request on to it. */ return seat_confirm_weak_cached_hostkey( wrap(sp->clientseat), algname, betteralgs, callback, ctx); -- cgit v1.2.3 From 1fd27e649a281a1e67aea1bc875165b2816cde4a Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 6 Nov 2021 11:32:51 +0000 Subject: Remove unnecessary interactor_announce() calls. In interactor_return_seat, I wrote a comment saying that we should call interactor_announce when handing over to the next Interactor in the chain, *if* any Interactor had already made any kind of announcement. But, having written that comment, I didn't actually *implement* the 'if' clause, and called interactor_announce unconditionally! Now fixed. --- proxy/interactor.c | 38 +++++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/proxy/interactor.c b/proxy/interactor.c index 49359c1c..d2227ae3 100644 --- a/proxy/interactor.c +++ b/proxy/interactor.c @@ -21,6 +21,26 @@ Seat *interactor_borrow_seat(Interactor *itr) return clientseat; } +static Interactor *interactor_toplevel(Interactor *itr, unsigned *level_out) +{ + /* + * Find the Interactor at the top of the chain, so that all the + * Interactors in a stack can share that one's last-to-talk field. + * Also, count how far we had to go to get to it, to put in the + * message. + */ + Interactor *itr_top = itr; + unsigned level = 0; + while (itr_top->parent) { + itr_top = itr_top->parent; + level++; + } + + if (level_out) + *level_out = level; + return itr_top; +} + void interactor_return_seat(Interactor *itr) { Seat *tempseat = interactor_get_seat(itr); @@ -37,7 +57,9 @@ void interactor_return_seat(Interactor *itr) * interactor_announce, then all Interactors from now on will * announce themselves even if they have nothing to say. */ - interactor_announce(itr); + Interactor *itr_top = interactor_toplevel(itr, NULL); + if (itr_top->last_to_talk) + interactor_announce(itr); /* * We're about to hand this seat back to the parent Interactor to @@ -57,18 +79,8 @@ InteractionReadySeat interactor_announce(Interactor *itr) InteractionReadySeat iseat; iseat.seat = seat; - /* - * Find the Interactor at the top of the chain, so that all the - * Interactors in a stack can share that one's last-to-talk field. - * Also, count how far we had to go to get to it, to put in the - * message. - */ - Interactor *itr_top = itr; - unsigned level = 0; - while (itr_top->parent) { - itr_top = itr_top->parent; - level++; - } + unsigned level; + Interactor *itr_top = interactor_toplevel(itr, &level); /* * Generally, we should announce ourself if the previous -- cgit v1.2.3 From 5fdce31ecafc63e78afec2e2bb5155d40edc6566 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 6 Nov 2021 11:48:33 +0000 Subject: sshproxy: fix handling of connection closure. Now we always respond to backend disconnection or connection_fatal by calling plug_closing. And we always do it in a toplevel callback, so that when the Plug responds by calling our Socket close method (which frees us), nothing re-entrant happens. Also, the handling of notify_remote_disconnect is brought into line with the spec in putty.h, which says it can be sent redundantly (when already disconnected) or spuriously (when not even disconnected at all), so the toplevel callback queued by that method will check first. After this change, failures during connection_setup are now handled _mostly_ sensibly: if the proxy connection fails, then the main connection gets enough information to pass a sensible connection_fatal on to the real front end. This also fixes the assertion failure mentioned in the TODO comment, replacing it with a reasonably sensible connection_fatal() - although I still think that in that situation it might be better not to have a dialog box at all. --- proxy/sshproxy.c | 45 +++++++++++++++++++++++++++++++++------------ 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/proxy/sshproxy.c b/proxy/sshproxy.c index f8d52996..c3785bd0 100644 --- a/proxy/sshproxy.c +++ b/proxy/sshproxy.c @@ -18,15 +18,11 @@ const bool ssh_proxy_supported = true; * * If the user manually aborts the attempt to make the proxy SSH * connection (e.g. by hitting ^C at a userpass prompt, or refusing to - * accept the proxy server's host key), then an assertion failure - * occurs, because the main backend receives an indication of - * connection failure that causes it to want to call - * seat_connection_fatal("Remote side unexpectedly closed network - * connection"), which fails an assertion in tempseat.c because that - * method of TempSeat expects never to be called. To fix this, I think - * we need to distinguish 'connection attempt unexpectedly failed, in - * a way the user needs to be told about' from 'connection attempt was - * aborted by deliberate user action, so the user already knows'. + * accept the proxy server's host key), then I think it would be nicer + * if we didn't give a connection_fatal error box. If I've aborted the + * connection deliberately, I don't need to be told it happened, and + * I'd rather not have the UI annoyance of clicking away an extra + * error dialog. */ typedef struct SshProxy { @@ -44,6 +40,7 @@ typedef struct SshProxy { bool frozen; bufchain ssh_to_socket; bool rcvd_eof_ssh_to_socket, sent_eof_ssh_to_socket; + bool conn_established; SockAddr *addr; int port; @@ -247,6 +244,7 @@ static void sshproxy_notify_session_started(Seat *seat) if (sp->clientseat) interactor_return_seat(sp->clientitr); + sp->conn_established = true; plug_log(sp->plug, PLUGLOG_CONNECT_SUCCESS, sp->addr, sp->port, NULL, 0); } @@ -309,11 +307,34 @@ static void sshproxy_sent(Seat *seat, size_t new_bufsize) plug_sent(sp->plug, new_bufsize); } +static void sshproxy_send_close(SshProxy *sp) +{ + if (sp->clientseat) + interactor_return_seat(sp->clientitr); + + if (!sp->conn_established) + plug_log(sp->plug, PLUGLOG_CONNECT_FAILED, sp->addr, sp->port, + sp->errmsg, 0); + + plug_closing(sp->plug, sp->errmsg, 0); +} + +static void sshproxy_notify_remote_disconnect_callback(void *vctx) +{ + SshProxy *sp = (SshProxy *)vctx; + + /* notify_remote_disconnect can be called redundantly, so first + * check if the backend really has become disconnected */ + if (backend_connected(sp->backend)) + return; + + sshproxy_send_close(sp); +} + static void sshproxy_notify_remote_disconnect(Seat *seat) { SshProxy *sp = container_of(seat, SshProxy, seat); - if (!sp->rcvd_eof_ssh_to_socket && !backend_connected(sp->backend)) - sshproxy_eof(seat); + queue_toplevel_callback(sshproxy_notify_remote_disconnect_callback, sp); } static int sshproxy_get_userpass_input(Seat *seat, prompts_t *p) @@ -341,7 +362,7 @@ static int sshproxy_get_userpass_input(Seat *seat, prompts_t *p) static void sshproxy_connection_fatal_callback(void *vctx) { SshProxy *sp = (SshProxy *)vctx; - plug_closing(sp->plug, sp->errmsg, 0); + sshproxy_send_close(sp); } static void sshproxy_connection_fatal(Seat *seat, const char *message) -- cgit v1.2.3 From 364e1aa3f39b4c0dd9a04e487fe5f188d4f2f59b Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 6 Nov 2021 13:25:42 +0000 Subject: Convenience wrappers on plug_closing(). Having a single plug_closing() function covering various kinds of closure is reasonably convenient from the point of view of Plug implementations, but it's annoying for callers, who all have to fill in pointless NULL and 0 parameters in the cases where they're not used. Added some inline helper functions in network.h alongside the main plug_closing() dispatch wrappers, so that each kind of connection closure can present a separate API for the Socket side of the interface, without complicating the vtable for the Plug side. Also, added OS-specific extra helpers in the Unix and Windows directories, which centralise the job of taking an OS error code (of whatever kind) and translating it into its error message. In passing, this removes the horrible ad-hoc made-up error codes in proxy.h, which is OK, because nothing checked for them anyway, and also I'm about to do an API change to plug_closing proper that removes the need for them. --- network.h | 4 +++ proxy/cproxy.c | 27 +++++++---------- proxy/nocproxy.c | 13 ++++----- proxy/proxy.c | 78 ++++++++++++++++++++++--------------------------- proxy/proxy.h | 3 -- proxy/sshproxy.c | 7 +++-- unix/fd-socket.c | 6 ++-- unix/network.c | 21 ++++++++----- unix/platform.h | 3 ++ windows/handle-socket.c | 6 ++-- windows/network.c | 25 +++++++++++----- windows/platform.h | 4 +++ 12 files changed, 103 insertions(+), 94 deletions(-) diff --git a/network.h b/network.h index 068e6cc6..851e1738 100644 --- a/network.h +++ b/network.h @@ -224,6 +224,10 @@ static inline void plug_log( { p->vt->log(p, type, addr, port, msg, code); } static inline void plug_closing(Plug *p, const char *msg, int code) { p->vt->closing(p, msg, code); } +static inline void plug_closing_normal(Plug *p) +{ p->vt->closing(p, NULL, 0); } +static inline void plug_closing_error(Plug *p, const char *msg) +{ p->vt->closing(p, msg, 0); } static inline void plug_receive(Plug *p, int urg, const char *data, size_t len) { p->vt->receive(p, urg, data, len); } static inline void plug_sent (Plug *p, size_t bufsize) diff --git a/proxy/cproxy.c b/proxy/cproxy.c index d187ffec..e58c344b 100644 --- a/proxy/cproxy.c +++ b/proxy/cproxy.c @@ -65,15 +65,13 @@ int proxy_socks5_handlechap (ProxySocket *p) * number of attributes */ if (data[0] != 0x01) { - plug_closing(p->plug, "Proxy error: SOCKS proxy wants" - " a different CHAP version", - PROXY_ERROR_GENERAL); + plug_closing_error(p->plug, "Proxy error: SOCKS proxy wants " + "a different CHAP version"); return 1; } if (data[1] == 0x00) { - plug_closing(p->plug, "Proxy error: SOCKS proxy won't" - " negotiate CHAP with us", - PROXY_ERROR_GENERAL); + plug_closing_error(p->plug, "Proxy error: SOCKS proxy won't " + "negotiate CHAP with us"); return 1; } p->chap_num_attributes = data[1]; @@ -103,9 +101,8 @@ int proxy_socks5_handlechap (ProxySocket *p) if (data[0] == 0x00) p->state = 2; else { - plug_closing(p->plug, "Proxy error: SOCKS proxy" - " refused CHAP authentication", - PROXY_ERROR_GENERAL); + plug_closing_error(p->plug, "Proxy error: SOCKS proxy " + "refused CHAP authentication"); return 1; } break; @@ -122,10 +119,9 @@ int proxy_socks5_handlechap (ProxySocket *p) 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); + plug_closing_error(p->plug, "Proxy error: Server chose " + "CHAP of other than HMAC-MD5 but we " + "didn't offer it!"); return 1; } break; @@ -172,8 +168,7 @@ int proxy_socks5_selectchap(ProxySocket *p) p->state = 8; } else - plug_closing(p->plug, "Proxy error: Server chose " - "CHAP authentication but we didn't offer it!", - PROXY_ERROR_GENERAL); + plug_closing_error(p->plug, "Proxy error: Server chose " + "CHAP authentication but we didn't offer it!"); return 1; } diff --git a/proxy/nocproxy.c b/proxy/nocproxy.c index cdc28a39..ed9c3ffd 100644 --- a/proxy/nocproxy.c +++ b/proxy/nocproxy.c @@ -17,19 +17,16 @@ void proxy_socks5_offerencryptedauth(BinarySink *bs) /* For telnet, don't add any new encrypted authentication routines */ } -int proxy_socks5_handlechap (ProxySocket *p) +int proxy_socks5_handlechap(ProxySocket *p) { - - plug_closing(p->plug, "Proxy error: Trying to handle a SOCKS5 CHAP request" - " in telnet-only build", - PROXY_ERROR_GENERAL); + plug_closing_error(p->plug, "Proxy error: Trying to handle a " + "SOCKS5 CHAP request in telnet-only build"); return 1; } int proxy_socks5_selectchap(ProxySocket *p) { - plug_closing(p->plug, "Proxy error: Trying to handle a SOCKS5 CHAP request" - " in telnet-only build", - PROXY_ERROR_GENERAL); + plug_closing_error(p->plug, "Proxy error: Trying to handle a " + "SOCKS5 CHAP request in telnet-only build"); return 1; } diff --git a/proxy/proxy.c b/proxy/proxy.c index 69b32866..d4963db4 100644 --- a/proxy/proxy.c +++ b/proxy/proxy.c @@ -671,8 +671,8 @@ int proxy_http_negotiate (ProxySocket *p, int change) /* 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); + plug_closing_error(p->plug, "Proxy error: " + "HTTP response was absent"); sfree(data); return 1; } @@ -687,7 +687,7 @@ int proxy_http_negotiate (ProxySocket *p, int change) (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); + plug_closing_error(p->plug, buf); sfree(buf); sfree(data); return 1; @@ -737,8 +737,7 @@ int proxy_http_negotiate (ProxySocket *p, int change) } } - plug_closing(p->plug, "Proxy error: unexpected proxy error", - PROXY_ERROR_UNEXPECTED); + plug_closing_error(p->plug, "Proxy error: unexpected proxy error"); return 1; } @@ -854,9 +853,9 @@ int proxy_socks4_negotiate (ProxySocket *p, int change) 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); + plug_closing_error(p->plug, "Proxy error: SOCKS proxy " + "responded with unexpected " + "reply code version"); return 1; } @@ -864,17 +863,17 @@ int proxy_socks4_negotiate (ProxySocket *p, int change) switch (data[1]) { case 92: - plug_closing(p->plug, "Proxy error: SOCKS server wanted IDENTD on client", - PROXY_ERROR_GENERAL); + plug_closing_error(p->plug, "Proxy error: SOCKS server " + "wanted IDENTD on client"); break; case 93: - plug_closing(p->plug, "Proxy error: Username and IDENTD on client don't agree", - PROXY_ERROR_GENERAL); + plug_closing_error(p->plug, "Proxy error: Username and " + "IDENTD on client don't agree"); break; case 91: default: - plug_closing(p->plug, "Proxy error: Error while communicating with proxy", - PROXY_ERROR_GENERAL); + plug_closing_error(p->plug, "Proxy error: Error while " + "communicating with proxy"); break; } @@ -890,8 +889,7 @@ int proxy_socks4_negotiate (ProxySocket *p, int change) } } - plug_closing(p->plug, "Proxy error: unexpected proxy error", - PROXY_ERROR_UNEXPECTED); + plug_closing_error(p->plug, "Proxy error: unexpected proxy error"); return 1; } @@ -996,8 +994,8 @@ int proxy_socks5_negotiate (ProxySocket *p, int change) 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); + plug_closing_error(p->plug, "Proxy error: SOCKS proxy " + "returned unexpected version"); return 1; } @@ -1006,8 +1004,8 @@ int proxy_socks5_negotiate (ProxySocket *p, int change) 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); + plug_closing_error(p->plug, "Proxy error: SOCKS proxy did not " + "accept our authentication"); return 1; } bufchain_consume(&p->pending_input_data, 2); @@ -1030,17 +1028,15 @@ int proxy_socks5_negotiate (ProxySocket *p, int change) 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); + plug_closing_error(p->plug, "Proxy error: SOCKS password " + "subnegotiation contained wrong version " + "number"); return 1; } if (data[1] != 0) { - - plug_closing(p->plug, "Proxy error: SOCKS proxy refused" - " password authentication", - PROXY_ERROR_GENERAL); + plug_closing_error(p->plug, "Proxy error: SOCKS proxy refused " + "password authentication"); return 1; } @@ -1142,8 +1138,8 @@ int proxy_socks5_negotiate (ProxySocket *p, int change) 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); + plug_closing_error(p->plug, "Proxy error: SOCKS proxy " + "returned wrong version number"); return 1; } @@ -1166,7 +1162,7 @@ int proxy_socks5_negotiate (ProxySocket *p, int change) data[1]); break; } - plug_closing(p->plug, buf, PROXY_ERROR_GENERAL); + plug_closing_error(p->plug, buf); return 1; } @@ -1180,9 +1176,8 @@ int proxy_socks5_negotiate (ProxySocket *p, int change) case 4: len += 16; break;/* IPv6 address */ case 3: len += 1+(unsigned char)data[4]; break; /* domain name */ default: - plug_closing(p->plug, "Proxy error: SOCKS proxy returned " - "unrecognised address format", - PROXY_ERROR_GENERAL); + plug_closing_error(p->plug, "Proxy error: SOCKS proxy " + "returned unrecognised address format"); return 1; } if (bufchain_size(&p->pending_input_data) < len) @@ -1196,8 +1191,8 @@ int proxy_socks5_negotiate (ProxySocket *p, int change) if (p->state == 4) { /* TODO: Handle GSSAPI authentication */ - plug_closing(p->plug, "Proxy error: We don't support GSSAPI authentication", - PROXY_ERROR_GENERAL); + plug_closing_error(p->plug, "Proxy error: We don't support " + "GSSAPI authentication"); return 1; } @@ -1223,10 +1218,9 @@ int proxy_socks5_negotiate (ProxySocket *p, int change) strbuf_free(auth); p->state = 7; } else - plug_closing(p->plug, "Proxy error: Server chose " - "username/password authentication but we " - "didn't offer it!", - PROXY_ERROR_GENERAL); + plug_closing_error(p->plug, "Proxy error: Server chose " + "username/password authentication " + "but we didn't offer it!"); return 1; } @@ -1238,8 +1232,7 @@ int proxy_socks5_negotiate (ProxySocket *p, int change) } - plug_closing(p->plug, "Proxy error: Unexpected proxy error", - PROXY_ERROR_UNEXPECTED); + plug_closing_error(p->plug, "Proxy error: Unexpected proxy error"); return 1; } @@ -1509,7 +1502,6 @@ int proxy_telnet_negotiate (ProxySocket *p, int change) return 1; } - plug_closing(p->plug, "Proxy error: Unexpected proxy error", - PROXY_ERROR_UNEXPECTED); + plug_closing_error(p->plug, "Proxy error: Unexpected proxy error"); return 1; } diff --git a/proxy/proxy.h b/proxy/proxy.h index 9dca87d1..3ec3261c 100644 --- a/proxy/proxy.h +++ b/proxy/proxy.h @@ -10,9 +10,6 @@ #ifndef PUTTY_PROXY_H #define PUTTY_PROXY_H -#define PROXY_ERROR_GENERAL 8000 -#define PROXY_ERROR_UNEXPECTED 8001 - typedef struct ProxySocket ProxySocket; struct ProxySocket { diff --git a/proxy/sshproxy.c b/proxy/sshproxy.c index c3785bd0..fd9afc4e 100644 --- a/proxy/sshproxy.c +++ b/proxy/sshproxy.c @@ -234,7 +234,7 @@ static void try_send_ssh_to_socket(void *ctx) if (sp->rcvd_eof_ssh_to_socket && !sp->sent_eof_ssh_to_socket) { sp->sent_eof_ssh_to_socket = true; - plug_closing(sp->plug, sp->errmsg, 0); + plug_closing_normal(sp->plug); } } @@ -316,7 +316,10 @@ static void sshproxy_send_close(SshProxy *sp) plug_log(sp->plug, PLUGLOG_CONNECT_FAILED, sp->addr, sp->port, sp->errmsg, 0); - plug_closing(sp->plug, sp->errmsg, 0); + if (sp->errmsg) + plug_closing_error(sp->plug, sp->errmsg); + else + plug_closing_normal(sp->plug); } static void sshproxy_notify_remote_disconnect_callback(void *vctx) diff --git a/unix/fd-socket.c b/unix/fd-socket.c index 48d7dc8b..013c5361 100644 --- a/unix/fd-socket.c +++ b/unix/fd-socket.c @@ -158,7 +158,7 @@ static void fdsocket_error_callback(void *vs) /* * An error has occurred on this socket. Pass it to the plug. */ - plug_closing(fds->plug, strerror(fds->pending_error), fds->pending_error); + plug_closing_errno(fds->plug, fds->pending_error); } static int fdsocket_try_send(FdSocket *fds) @@ -270,9 +270,9 @@ static void fdsocket_select_result_input(int fd, int event) fds->infd = -1; if (retd < 0) { - plug_closing(fds->plug, strerror(errno), errno); + plug_closing_errno(fds->plug, errno); } else { - plug_closing(fds->plug, NULL, 0); + plug_closing_normal(fds->plug); } } } diff --git a/unix/network.c b/unix/network.c index d2d426ed..32cc3c9e 100644 --- a/unix/network.c +++ b/unix/network.c @@ -1084,6 +1084,11 @@ void *sk_getxdmdata(Socket *sock, int *lenp) return buf; } +void plug_closing_errno(Plug *plug, int error) +{ + plug_closing(plug, strerror(error), error); +} + /* * Deal with socket errors detected in try_send(). */ @@ -1101,7 +1106,7 @@ static void socket_error_callback(void *vs) /* * An error has occurred on this socket. Pass it to the plug. */ - plug_closing(s->plug, strerror(s->pending_error), s->pending_error); + plug_closing_errno(s->plug, s->pending_error); } /* @@ -1295,10 +1300,10 @@ static void net_select_result(int fd, int event) */ ret = recv(s->s, buf, sizeof(buf), MSG_OOB); noise_ultralight(NOISE_SOURCE_IOLEN, ret); - if (ret <= 0) { - plug_closing(s->plug, - ret == 0 ? "Internal networking trouble" : - strerror(errno), errno); + if (ret == 0) { + plug_closing_error(s->plug, "Internal networking trouble"); + } else if (ret < 0) { + plug_closing_errno(s->plug, errno); } else { /* * Receiving actual data on a socket means we can @@ -1384,11 +1389,11 @@ static void net_select_result(int fd, int event) } } if (ret < 0) { - plug_closing(s->plug, strerror(errno), errno); + plug_closing_errno(s->plug, errno); } else if (0 == ret) { s->incomingeof = true; /* stop trying to read now */ uxsel_tell(s); - plug_closing(s->plug, NULL, 0); + plug_closing_normal(s->plug); } else { /* * Receiving actual data on a socket means we can @@ -1438,7 +1443,7 @@ static void net_select_result(int fd, int event) err = try_connect(s); } if (err) { - plug_closing(s->plug, strerror(err), err); + plug_closing_errno(s->plug, err); return; /* socket is now presumably defunct */ } if (!s->connected) diff --git a/unix/platform.h b/unix/platform.h index b4f5a008..79b171ca 100644 --- a/unix/platform.h +++ b/unix/platform.h @@ -458,4 +458,7 @@ bool cliloop_no_pw_setup(void *ctx, pollwrapper *pw); void cliloop_no_pw_check(void *ctx, pollwrapper *pw); bool cliloop_always_continue(void *ctx, bool, bool); +/* network.c: network error reporting helper taking an OS error code */ +void plug_closing_errno(Plug *plug, int error); + #endif /* PUTTY_UNIX_PLATFORM_H */ diff --git a/windows/handle-socket.c b/windows/handle-socket.c index 066d2373..32479d64 100644 --- a/windows/handle-socket.c +++ b/windows/handle-socket.c @@ -53,10 +53,10 @@ static size_t handle_gotdata( HandleSocket *hs = (HandleSocket *)handle_get_privdata(h); if (err) { - plug_closing(hs->plug, "Read error from handle", 0); + plug_closing_error(hs->plug, "Read error from handle"); return 0; } else if (len == 0) { - plug_closing(hs->plug, NULL, 0); + plug_closing_normal(hs->plug); return 0; } else { assert(hs->frozen != FROZEN && hs->frozen != THAWING); @@ -107,7 +107,7 @@ static void handle_sentdata(struct handle *h, size_t new_backlog, int err, } if (err) { - plug_closing(hs->plug, win_strerror(err), err); + plug_closing_system_error(hs->plug, err); return; } diff --git a/windows/network.c b/windows/network.c index 17c80cb4..b5b5ab0e 100644 --- a/windows/network.c +++ b/windows/network.c @@ -1335,6 +1335,16 @@ static void sk_net_close(Socket *sock) sfree(s); } +void plug_closing_system_error(Plug *plug, DWORD error) +{ + plug_closing(plug, win_strerror(error), error); +} + +void plug_closing_winsock_error(Plug *plug, DWORD error) +{ + plug_closing(plug, winsock_error_string(error), error); +} + /* * Deal with socket errors detected in try_send(). */ @@ -1352,8 +1362,7 @@ static void socket_error_callback(void *vs) /* * 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); + plug_closing_winsock_error(s->plug, s->pending_error); } /* @@ -1528,7 +1537,7 @@ void select_result(WPARAM wParam, LPARAM lParam) } } if (err != 0) - plug_closing(s->plug, winsock_error_string(err), err); + plug_closing_winsock_error(s->plug, err); return; } @@ -1590,9 +1599,9 @@ void select_result(WPARAM wParam, LPARAM lParam) } } if (ret < 0) { - plug_closing(s->plug, winsock_error_string(err), err); + plug_closing_winsock_error(s->plug, err); } else if (0 == ret) { - plug_closing(s->plug, NULL, 0); + plug_closing_normal(s->plug); } else { plug_receive(s->plug, atmark ? 0 : 1, buf, ret); } @@ -1608,7 +1617,7 @@ void select_result(WPARAM wParam, LPARAM lParam) noise_ultralight(NOISE_SOURCE_IOLEN, ret); if (ret <= 0) { int err = p_WSAGetLastError(); - plug_closing(s->plug, winsock_error_string(err), err); + plug_closing_winsock_error(s->plug, err); } else { plug_receive(s->plug, 2, buf, ret); } @@ -1631,12 +1640,12 @@ void select_result(WPARAM wParam, LPARAM lParam) err = p_WSAGetLastError(); if (err == WSAEWOULDBLOCK) break; - plug_closing(s->plug, winsock_error_string(err), err); + plug_closing_winsock_error(s->plug, err); } else { if (ret) plug_receive(s->plug, 0, buf, ret); else - plug_closing(s->plug, NULL, 0); + plug_closing_normal(s->plug); } } while (ret > 0); return; diff --git a/windows/platform.h b/windows/platform.h index cf76566b..0a1a6ef7 100644 --- a/windows/platform.h +++ b/windows/platform.h @@ -730,4 +730,8 @@ char *handle_restrict_acl_cmdline_prefix(char *cmdline); bool handle_special_sessionname_cmdline(char *cmdline, Conf *conf); bool handle_special_filemapping_cmdline(char *cmdline, Conf *conf); +/* network.c: network error reporting helpers taking OS error code */ +void plug_closing_system_error(Plug *plug, DWORD error); +void plug_closing_winsock_error(Plug *plug, DWORD error); + #endif /* PUTTY_WINDOWS_PLATFORM_H */ -- cgit v1.2.3 From 0fe41294e62546db74a631928c535e50315cf87c Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 6 Nov 2021 13:28:32 +0000 Subject: New API for plug_closing() with a custom type enum. Passing an operating-system-specific error code to plug_closing(), such as errno or GetLastError(), was always a bit weird, given that it generally had to be handled by cross-platform receiving code in backends. I had the platform.h implementations #define any error values that the cross-platform code would have to handle specially, but that's still not a great system, because it also doesn't leave freedom to invent error representations of my own that don't correspond to any OS code. (For example, the ones I just removed from proxy.h.) So now, the OS error code is gone from the plug_closing API, and in its place is a custom enumeration of closure types: normal, error, and the special case BROKEN_PIPE which is the only OS error code we have so far needed to handle specially. (All others just mean 'abandon the connection and print the textual message'.) Having already centralised the handling of OS error codes in the previous commit, we've now got a convenient place to add any further type codes for errors needing special handling: each of Unix plug_closing_errno(), Windows plug_closing_system_error(), and Windows plug_closing_winsock_error() can easily grow extra special cases if need be, and each one will only have to live in one place. --- network.h | 42 ++++++++++++++++++++++++++++-------------- nullplug.c | 2 +- otherbackends/raw.c | 4 ++-- otherbackends/rlogin.c | 8 +++++--- otherbackends/supdup.c | 5 +++-- otherbackends/telnet.c | 5 +++-- pageant.c | 12 ++++++------ proxy/proxy.c | 15 ++++++++------- proxy/proxy.h | 2 +- psocks.c | 7 +++---- ssh/portfwd.c | 6 +++--- ssh/server.c | 5 +++-- ssh/sesschan.c | 2 +- ssh/sharing.c | 38 ++++++++++++++++++-------------------- ssh/ssh.c | 4 ++-- ssh/x11fwd.c | 4 ++-- unix/network.c | 5 ++++- unix/pageant.c | 2 +- unix/psusan.c | 6 ++++-- unix/uppity.c | 6 ++++-- windows/network.c | 7 +++++-- windows/platform.h | 2 -- 22 files changed, 107 insertions(+), 82 deletions(-) diff --git a/network.h b/network.h index 851e1738..9b63d39f 100644 --- a/network.h +++ b/network.h @@ -51,6 +51,12 @@ typedef enum PlugLogType { PLUGLOG_PROXY_MSG, } PlugLogType; +typedef enum PlugCloseType { + PLUGCLOSE_NORMAL, + PLUGCLOSE_ERROR, + PLUGCLOSE_BROKEN_PIPE, +} PlugCloseType; + struct PlugVtable { /* * Passes the client progress reports on the process of setting @@ -88,18 +94,26 @@ struct PlugVtable { const char *error_msg, int error_code); /* - * Notifies the Plug that the socket is closing. + * Notifies the Plug that the socket is closing, and something + * about why. + * + * - PLUGCLOSE_NORMAL means an ordinary non-error closure. In + * this case, error_msg should be ignored (and hopefully + * callers will have passed NULL). * - * For a normal non-error close, error_msg is NULL. If the socket - * has encountered an error, error_msg will contain a string - * (ownership not transferred), and error_code will contain the OS - * error code, if available. + * - PLUGCLOSE_ERROR indicates that an OS error occurred, and + * 'error_msg' contains a string describing it, for use in + * diagnostics. (Ownership of the string is not transferred.) + * This error class covers anything other than the special + * case below: * - * OS error codes will vary between platforms, of course, but - * platform.h should define any that we need to distinguish here, - * in particular BROKEN_PIPE_ERROR_CODE. + * - PLUGCLOSE_BROKEN_PIPE behaves like PLUGCLOSE_ERROR (in + * particular, there's still an error message provided), but + * distinguishes the particular error condition signalled by + * EPIPE / ERROR_BROKEN_PIPE, which ssh/sharing.c needs to + * recognise and handle specially in one situation. */ - void (*closing)(Plug *p, const char *error_msg, int error_code); + void (*closing)(Plug *p, PlugCloseType type, const char *error_msg); /* * Provides incoming socket data to the Plug. Three cases: @@ -222,12 +236,12 @@ static inline void sk_write_eof(Socket *s) static inline void plug_log( Plug *p, int type, SockAddr *addr, int port, const char *msg, int code) { p->vt->log(p, type, addr, port, msg, code); } -static inline void plug_closing(Plug *p, const char *msg, int code) -{ p->vt->closing(p, msg, code); } +static inline void plug_closing(Plug *p, PlugCloseType type, const char *msg) +{ p->vt->closing(p, type, msg); } static inline void plug_closing_normal(Plug *p) -{ p->vt->closing(p, NULL, 0); } +{ p->vt->closing(p, PLUGCLOSE_NORMAL, NULL); } static inline void plug_closing_error(Plug *p, const char *msg) -{ p->vt->closing(p, msg, 0); } +{ p->vt->closing(p, PLUGCLOSE_ERROR, msg); } static inline void plug_receive(Plug *p, int urg, const char *data, size_t len) { p->vt->receive(p, urg, data, len); } static inline void plug_sent (Plug *p, size_t bufsize) @@ -352,7 +366,7 @@ extern Plug *const nullplug; */ void nullplug_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, const char *err_msg, int err_code); -void nullplug_closing(Plug *plug, const char *error_msg, int error_code); +void nullplug_closing(Plug *plug, PlugCloseType type, const char *error_msg); void nullplug_receive(Plug *plug, int urgent, const char *data, size_t len); void nullplug_sent(Plug *plug, size_t bufsize); diff --git a/nullplug.c b/nullplug.c index ad65f5ea..d583d156 100644 --- a/nullplug.c +++ b/nullplug.c @@ -12,7 +12,7 @@ void nullplug_log(Plug *plug, PlugLogType type, SockAddr *addr, { } -void nullplug_closing(Plug *plug, const char *error_msg, int error_code) +void nullplug_closing(Plug *plug, PlugCloseType type, const char *error_msg) { } diff --git a/otherbackends/raw.c b/otherbackends/raw.c index 80ce8fe1..59a6611d 100644 --- a/otherbackends/raw.c +++ b/otherbackends/raw.c @@ -65,11 +65,11 @@ static void raw_check_close(Raw *raw) } } -static void raw_closing(Plug *plug, const char *error_msg, int error_code) +static void raw_closing(Plug *plug, PlugCloseType type, const char *error_msg) { Raw *raw = container_of(plug, Raw, plug); - if (error_msg) { + if (type != PLUGCLOSE_NORMAL) { /* A socket error has occurred. */ if (raw->s) { sk_close(raw->s); diff --git a/otherbackends/rlogin.c b/otherbackends/rlogin.c index e626eff4..341ecae7 100644 --- a/otherbackends/rlogin.c +++ b/otherbackends/rlogin.c @@ -85,7 +85,8 @@ static void rlogin_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, } } -static void rlogin_closing(Plug *plug, const char *error_msg, int error_code) +static void rlogin_closing(Plug *plug, PlugCloseType type, + const char *error_msg) { Rlogin *rlogin = container_of(plug, Rlogin, plug); @@ -103,11 +104,12 @@ static void rlogin_closing(Plug *plug, const char *error_msg, int error_code) seat_notify_remote_exit(rlogin->seat); seat_notify_remote_disconnect(rlogin->seat); } - if (error_msg) { + if (type != PLUGCLOSE_NORMAL) { /* A socket error has occurred. */ logevent(rlogin->logctx, error_msg); seat_connection_fatal(rlogin->seat, "%s", error_msg); - } /* Otherwise, the remote side closed the connection normally. */ + } + /* Otherwise, the remote side closed the connection normally. */ } static void rlogin_receive( diff --git a/otherbackends/supdup.c b/otherbackends/supdup.c index c1867086..697c3e84 100644 --- a/otherbackends/supdup.c +++ b/otherbackends/supdup.c @@ -573,7 +573,8 @@ static void supdup_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, } } -static void supdup_closing(Plug *plug, const char *error_msg, int error_code) +static void supdup_closing(Plug *plug, PlugCloseType type, + const char *error_msg) { Supdup *supdup = container_of(plug, Supdup, plug); @@ -591,7 +592,7 @@ static void supdup_closing(Plug *plug, const char *error_msg, int error_code) seat_notify_remote_exit(supdup->seat); seat_notify_remote_disconnect(supdup->seat); } - if (error_msg) { + if (type != PLUGCLOSE_NORMAL) { logevent(supdup->logctx, error_msg); seat_connection_fatal(supdup->seat, "%s", error_msg); } diff --git a/otherbackends/telnet.c b/otherbackends/telnet.c index 66f7308e..80a7cc24 100644 --- a/otherbackends/telnet.c +++ b/otherbackends/telnet.c @@ -635,7 +635,8 @@ static void telnet_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, } } -static void telnet_closing(Plug *plug, const char *error_msg, int error_code) +static void telnet_closing(Plug *plug, PlugCloseType type, + const char *error_msg) { Telnet *telnet = container_of(plug, Telnet, plug); @@ -653,7 +654,7 @@ static void telnet_closing(Plug *plug, const char *error_msg, int error_code) seat_notify_remote_exit(telnet->seat); seat_notify_remote_disconnect(telnet->seat); } - if (error_msg) { + if (type != PLUGCLOSE_NORMAL) { logevent(telnet->logctx, error_msg); seat_connection_fatal(telnet->seat, "%s", error_msg); } diff --git a/pageant.c b/pageant.c index bb658cfe..f64026d8 100644 --- a/pageant.c +++ b/pageant.c @@ -1463,12 +1463,12 @@ struct pageant_conn_state { Plug plug; }; -static void pageant_conn_closing(Plug *plug, const char *error_msg, - int error_code) +static void pageant_conn_closing(Plug *plug, PlugCloseType type, + const char *error_msg) { struct pageant_conn_state *pc = container_of( plug, struct pageant_conn_state, plug); - if (error_msg) + if (type != PLUGCLOSE_NORMAL) pageant_listener_client_log(pc->plc, "c#%"SIZEu": error: %s", pc->conn_index, error_msg); else @@ -1610,12 +1610,12 @@ struct pageant_listen_state { Plug plug; }; -static void pageant_listen_closing(Plug *plug, const char *error_msg, - int error_code) +static void pageant_listen_closing(Plug *plug, PlugCloseType type, + const char *error_msg) { struct pageant_listen_state *pl = container_of( plug, struct pageant_listen_state, plug); - if (error_msg) + if (type != PLUGCLOSE_NORMAL) pageant_listener_client_log(pl->plc, "listening socket: error: %s", error_msg); sk_close(pl->listensock); diff --git a/proxy/proxy.c b/proxy/proxy.c index d4963db4..d6041754 100644 --- a/proxy/proxy.c +++ b/proxy/proxy.c @@ -181,16 +181,17 @@ static void plug_proxy_log(Plug *plug, PlugLogType type, SockAddr *addr, plug_log(ps->plug, type, addr, port, error_msg, error_code); } -static void plug_proxy_closing(Plug *p, const char *error_msg, int error_code) +static void plug_proxy_closing(Plug *p, PlugCloseType type, + const char *error_msg) { ProxySocket *ps = container_of(p, ProxySocket, plugimpl); if (ps->state != PROXY_STATE_ACTIVE) { + ps->closing_type = type; ps->closing_error_msg = error_msg; - ps->closing_error_code = error_code; ps->negotiate(ps, PROXY_CHANGE_CLOSING); } else { - plug_closing(ps->plug, error_msg, error_code); + plug_closing(ps->plug, type, error_msg); } } @@ -615,7 +616,7 @@ int proxy_http_negotiate (ProxySocket *p, int change) * a socket close, then some error must have occurred. we'll * just pass those errors up to the backend. */ - plug_closing(p->plug, p->closing_error_msg, p->closing_error_code); + plug_closing(p->plug, p->closing_type, p->closing_error_msg); return 0; /* ignored */ } @@ -803,7 +804,7 @@ int proxy_socks4_negotiate (ProxySocket *p, int change) * a socket close, then some error must have occurred. we'll * just pass those errors up to the backend. */ - plug_closing(p->plug, p->closing_error_msg, p->closing_error_code); + plug_closing(p->plug, p->closing_type, p->closing_error_msg); return 0; /* ignored */ } @@ -945,7 +946,7 @@ int proxy_socks5_negotiate (ProxySocket *p, int change) * a socket close, then some error must have occurred. we'll * just pass those errors up to the backend. */ - plug_closing(p->plug, p->closing_error_msg, p->closing_error_code); + plug_closing(p->plug, p->closing_type, p->closing_error_msg); return 0; /* ignored */ } @@ -1467,7 +1468,7 @@ int proxy_telnet_negotiate (ProxySocket *p, int change) * a socket close, then some error must have occurred. we'll * just pass those errors up to the backend. */ - plug_closing(p->plug, p->closing_error_msg, p->closing_error_code); + plug_closing(p->plug, p->closing_type, p->closing_error_msg); return 0; /* ignored */ } diff --git a/proxy/proxy.h b/proxy/proxy.h index 3ec3261c..06692b70 100644 --- a/proxy/proxy.h +++ b/proxy/proxy.h @@ -58,8 +58,8 @@ struct ProxySocket { */ /* closing */ + PlugCloseType closing_type; const char *closing_error_msg; - int closing_error_code; /* receive */ bool receive_urgent; diff --git a/psocks.c b/psocks.c index f3c8efa0..64c46953 100644 --- a/psocks.c +++ b/psocks.c @@ -94,8 +94,7 @@ static const SshChannelVtable psocks_scvt = { static void psocks_plug_log(Plug *p, PlugLogType type, SockAddr *addr, int port, const char *error_msg, int error_code); -static void psocks_plug_closing(Plug *p, const char *error_msg, - int error_code); +static void psocks_plug_closing(Plug *p, PlugCloseType, const char *error_msg); static void psocks_plug_receive(Plug *p, int urgent, const char *data, size_t len); static void psocks_plug_sent(Plug *p, size_t bufsize); @@ -354,8 +353,8 @@ static void psocks_plug_log(Plug *plug, PlugLogType type, SockAddr *addr, }; } -static void psocks_plug_closing(Plug *plug, const char *error_msg, - int error_code) +static void psocks_plug_closing(Plug *plug, PlugCloseType type, + const char *error_msg) { psocks_connection *conn = container_of(plug, psocks_connection, plug); if (conn->connecting) { diff --git a/ssh/portfwd.c b/ssh/portfwd.c index 204f0632..2afa9507 100644 --- a/ssh/portfwd.c +++ b/ssh/portfwd.c @@ -109,12 +109,12 @@ static void pfl_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, static void pfd_close(struct PortForwarding *pf); -static void pfd_closing(Plug *plug, const char *error_msg, int error_code) +static void pfd_closing(Plug *plug, PlugCloseType type, const char *error_msg) { struct PortForwarding *pf = container_of(plug, struct PortForwarding, plug); - if (error_msg) { + if (type != PLUGCLOSE_NORMAL) { /* * Socket error. Slam the connection instantly shut. */ @@ -141,7 +141,7 @@ static void pfd_closing(Plug *plug, const char *error_msg, int error_code) static void pfl_terminate(struct PortListener *pl); -static void pfl_closing(Plug *plug, const char *error_msg, int error_code) +static void pfl_closing(Plug *plug, PlugCloseType type, const char *error_msg) { struct PortListener *pl = (struct PortListener *) plug; pfl_terminate(pl); diff --git a/ssh/server.c b/ssh/server.c index 888ff87d..99237616 100644 --- a/ssh/server.c +++ b/ssh/server.c @@ -140,10 +140,11 @@ static void server_socket_log(Plug *plug, PlugLogType type, SockAddr *addr, /* FIXME */ } -static void server_closing(Plug *plug, const char *error_msg, int error_code) +static void server_closing(Plug *plug, PlugCloseType type, + const char *error_msg) { server *srv = container_of(plug, server, plug); - if (error_msg) { + if (type != PLUGCLOSE_NORMAL) { ssh_remote_error(&srv->ssh, "%s", error_msg); } else if (srv->bpp) { srv->bpp->input_eof = true; diff --git a/ssh/sesschan.c b/ssh/sesschan.c index 56ee37e1..5379b308 100644 --- a/ssh/sesschan.c +++ b/ssh/sesschan.c @@ -367,7 +367,7 @@ bool sesschan_run_subsystem(Channel *chan, ptrlen subsys) static void fwd_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, const char *error_msg, int error_code) { /* don't expect any weirdnesses from a listening socket */ } -static void fwd_closing(Plug *plug, const char *error_msg, int error_code) +static void fwd_closing(Plug *plug, PlugCloseType type, const char *error_msg) { /* not here, either */ } static int xfwd_accepting(Plug *p, accept_fn_t constructor, accept_ctx_t ctx) diff --git a/ssh/sharing.c b/ssh/sharing.c index 92e2a786..dfa0ca22 100644 --- a/ssh/sharing.c +++ b/ssh/sharing.c @@ -937,27 +937,25 @@ static void share_disconnect(struct ssh_sharing_connstate *cs, share_begin_cleanup(cs); } -static void share_closing(Plug *plug, const char *error_msg, int error_code) +static void share_closing(Plug *plug, PlugCloseType type, + const char *error_msg) { struct ssh_sharing_connstate *cs = container_of( plug, struct ssh_sharing_connstate, plug); - if (error_msg) { -#ifdef BROKEN_PIPE_ERROR_CODE - /* - * Most of the time, we log what went wrong when a downstream - * disappears with a socket error. One exception, though, is - * receiving EPIPE when we haven't received a protocol version - * string from the downstream, because that can happen as a result - * of plink -shareexists (opening the connection and instantly - * closing it again without bothering to read our version string). - * So that one case is not treated as a log-worthy error. - */ - if (error_code == BROKEN_PIPE_ERROR_CODE && !cs->got_verstring) - /* do nothing */; - else -#endif - log_downstream(cs, "Socket error: %s", error_msg); + /* + * Most of the time, we log what went wrong when a downstream + * disappears with a socket error. One exception, though, is + * receiving EPIPE when we haven't received a protocol version + * string from the downstream, because that can happen as a result + * of plink -shareexists (opening the connection and instantly + * closing it again without bothering to read our version string). + * So that one case is not treated as a log-worthy error. + */ + if (type == PLUGCLOSE_BROKEN_PIPE && !cs->got_verstring) { + /* do nothing */; + } else if (type != PLUGCLOSE_NORMAL) { + log_downstream(cs, "Socket error: %s", error_msg); } share_begin_cleanup(cs); } @@ -1844,12 +1842,12 @@ static void share_sent(Plug *plug, size_t bufsize) */ } -static void share_listen_closing(Plug *plug, const char *error_msg, - int error_code) +static void share_listen_closing(Plug *plug, PlugCloseType type, + const char *error_msg) { ssh_sharing_state *sharestate = container_of(plug, ssh_sharing_state, plug); - if (error_msg) + if (type != PLUGCLOSE_NORMAL) log_general(sharestate, "listening socket: %s", error_msg); sk_close(sharestate->listensock); sharestate->listensock = NULL; diff --git a/ssh/ssh.c b/ssh/ssh.c index ae7c1f6f..831afd72 100644 --- a/ssh/ssh.c +++ b/ssh/ssh.c @@ -603,10 +603,10 @@ static void ssh_socket_log(Plug *plug, PlugLogType type, SockAddr *addr, ssh->session_started); } -static void ssh_closing(Plug *plug, const char *error_msg, int error_code) +static void ssh_closing(Plug *plug, PlugCloseType type, const char *error_msg) { Ssh *ssh = container_of(plug, Ssh, plug); - if (error_msg) { + if (type != PLUGCLOSE_NORMAL) { ssh_remote_error(ssh, "%s", error_msg); } else if (ssh->bpp) { ssh->bpp->input_eof = true; diff --git a/ssh/x11fwd.c b/ssh/x11fwd.c index 1d2fe512..4a2073e6 100644 --- a/ssh/x11fwd.c +++ b/ssh/x11fwd.c @@ -267,12 +267,12 @@ static void x11_log(Plug *p, PlugLogType type, SockAddr *addr, int port, static void x11_send_init_error(struct X11Connection *conn, const char *err_message); -static void x11_closing(Plug *plug, const char *error_msg, int error_code) +static void x11_closing(Plug *plug, PlugCloseType type, const char *error_msg) { struct X11Connection *xconn = container_of( plug, struct X11Connection, plug); - if (error_msg) { + if (type != PLUGCLOSE_NORMAL) { /* * Socket error. If we're still at the connection setup stage, * construct an X11 error packet passing on the problem. diff --git a/unix/network.c b/unix/network.c index 32cc3c9e..b8f923e9 100644 --- a/unix/network.c +++ b/unix/network.c @@ -1086,7 +1086,10 @@ void *sk_getxdmdata(Socket *sock, int *lenp) void plug_closing_errno(Plug *plug, int error) { - plug_closing(plug, strerror(error), error); + PlugCloseType type = PLUGCLOSE_ERROR; + if (error == EPIPE) + type = PLUGCLOSE_BROKEN_PIPE; + plug_closing(plug, type, strerror(error)); } /* diff --git a/unix/pageant.c b/unix/pageant.c index 2fa3eeaa..f2f75eac 100644 --- a/unix/pageant.c +++ b/unix/pageant.c @@ -249,7 +249,7 @@ static void x11_log(Plug *p, PlugLogType type, SockAddr *addr, int port, const char *error_msg, int error_code) {} static void x11_receive(Plug *plug, int urgent, const char *data, size_t len) {} static void x11_sent(Plug *plug, size_t bufsize) {} -static void x11_closing(Plug *plug, const char *error_msg, int error_code) +static void x11_closing(Plug *plug, PlugCloseType type, const char *error_msg) { time_to_die = true; } diff --git a/unix/psusan.c b/unix/psusan.c index 8fcef33a..6560adf2 100644 --- a/unix/psusan.c +++ b/unix/psusan.c @@ -273,9 +273,11 @@ static void server_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, log_to_stderr(-1, error_msg); } -static void server_closing(Plug *plug, const char *error_msg, int error_code) +static void server_closing(Plug *plug, PlugCloseType type, + const char *error_msg) { - log_to_stderr(-1, error_msg); + if (type != PLUGCLOSE_NORMAL) + log_to_stderr(-1, error_msg); } static int server_accepting(Plug *p, accept_fn_t constructor, accept_ctx_t ctx) diff --git a/unix/uppity.c b/unix/uppity.c index 0701ddab..7f74fed6 100644 --- a/unix/uppity.c +++ b/unix/uppity.c @@ -482,9 +482,11 @@ static void server_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, log_to_stderr((unsigned)-1, error_msg); } -static void server_closing(Plug *plug, const char *error_msg, int error_code) +static void server_closing(Plug *plug, PlugCloseType type, + const char *error_msg) { - log_to_stderr((unsigned)-1, error_msg); + if (type != PLUGCLOSE_NORMAL) + log_to_stderr((unsigned)-1, error_msg); } static int server_accepting(Plug *p, accept_fn_t constructor, accept_ctx_t ctx) diff --git a/windows/network.c b/windows/network.c index b5b5ab0e..a1a74b4c 100644 --- a/windows/network.c +++ b/windows/network.c @@ -1337,12 +1337,15 @@ static void sk_net_close(Socket *sock) void plug_closing_system_error(Plug *plug, DWORD error) { - plug_closing(plug, win_strerror(error), error); + PlugCloseType type = PLUGCLOSE_ERROR; + if (error == ERROR_BROKEN_PIPE) + type = PLUGCLOSE_BROKEN_PIPE; + plug_closing(plug, type, win_strerror(error)); } void plug_closing_winsock_error(Plug *plug, DWORD error) { - plug_closing(plug, winsock_error_string(error), error); + plug_closing(plug, PLUGCLOSE_ERROR, winsock_error_string(error)); } /* diff --git a/windows/platform.h b/windows/platform.h index 0a1a6ef7..cfd47281 100644 --- a/windows/platform.h +++ b/windows/platform.h @@ -121,8 +121,6 @@ static inline uintmax_t strtoumax(const char *nptr, char **endptr, int base) #define strnicmp strncasecmp #endif -#define BROKEN_PIPE_ERROR_CODE ERROR_BROKEN_PIPE /* used in ssh/sharing.c */ - /* * Dynamically linked functions. These come in two flavours: * -- cgit v1.2.3 From 2ae338b407ab88edeb850a17065abfb5a8a95276 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 6 Nov 2021 13:31:09 +0000 Subject: New plug_closing error type for 'user abort'. This is generated when setup of a network connection is cancelled by deliberate user action, namely, pressing ^C or ^D or the like at a get_userpass_input prompt presented during proxy setup. It's handled just like normal socket setup errors, except that it omits the call to seat_connection_fatal, on the grounds that in this one case of connection-setup failure, the user doesn't need to be _informed_ that the connection failed - they already know, because they failed it themself on purpose. --- network.h | 13 +++++++++++++ otherbackends/raw.c | 3 ++- otherbackends/rlogin.c | 3 ++- otherbackends/supdup.c | 3 ++- otherbackends/telnet.c | 3 ++- proxy/sshproxy.c | 14 ++------------ ssh/ssh.c | 4 +++- 7 files changed, 26 insertions(+), 17 deletions(-) diff --git a/network.h b/network.h index 9b63d39f..cd8d1a7b 100644 --- a/network.h +++ b/network.h @@ -55,6 +55,7 @@ typedef enum PlugCloseType { PLUGCLOSE_NORMAL, PLUGCLOSE_ERROR, PLUGCLOSE_BROKEN_PIPE, + PLUGCLOSE_USER_ABORT, } PlugCloseType; struct PlugVtable { @@ -112,6 +113,16 @@ struct PlugVtable { * distinguishes the particular error condition signalled by * EPIPE / ERROR_BROKEN_PIPE, which ssh/sharing.c needs to * recognise and handle specially in one situation. + * + * - PLUGCLOSE_USER_ABORT means that the close has happened as a + * result of some kind of deliberate user action (e.g. hitting + * ^C at a password prompt presented by a proxy socket setup + * phase). This can be used to suppress interactive error + * messages sent to the user (such as dialog boxes), on the + * grounds that the user already knows. However, 'error_msg' + * will still contain some appropriate text, so that + * non-interactive error reporting (e.g. event logs) can still + * record why the connection terminated. */ void (*closing)(Plug *p, PlugCloseType type, const char *error_msg); @@ -242,6 +253,8 @@ static inline void plug_closing_normal(Plug *p) { p->vt->closing(p, PLUGCLOSE_NORMAL, NULL); } static inline void plug_closing_error(Plug *p, const char *msg) { p->vt->closing(p, PLUGCLOSE_ERROR, msg); } +static inline void plug_closing_user_abort(Plug *p) +{ p->vt->closing(p, PLUGCLOSE_USER_ABORT, "User aborted connection setup"); } static inline void plug_receive(Plug *p, int urg, const char *data, size_t len) { p->vt->receive(p, urg, data, len); } static inline void plug_sent (Plug *p, size_t bufsize) diff --git a/otherbackends/raw.c b/otherbackends/raw.c index 59a6611d..c9a32e05 100644 --- a/otherbackends/raw.c +++ b/otherbackends/raw.c @@ -79,7 +79,8 @@ static void raw_closing(Plug *plug, PlugCloseType type, const char *error_msg) seat_notify_remote_disconnect(raw->seat); } logevent(raw->logctx, error_msg); - seat_connection_fatal(raw->seat, "%s", error_msg); + if (type != PLUGCLOSE_USER_ABORT) + seat_connection_fatal(raw->seat, "%s", error_msg); } else { /* Otherwise, the remote side closed the connection normally. */ if (!raw->sent_console_eof && seat_eof(raw->seat)) { diff --git a/otherbackends/rlogin.c b/otherbackends/rlogin.c index 341ecae7..59a833a0 100644 --- a/otherbackends/rlogin.c +++ b/otherbackends/rlogin.c @@ -107,7 +107,8 @@ static void rlogin_closing(Plug *plug, PlugCloseType type, if (type != PLUGCLOSE_NORMAL) { /* A socket error has occurred. */ logevent(rlogin->logctx, error_msg); - seat_connection_fatal(rlogin->seat, "%s", error_msg); + if (type != PLUGCLOSE_USER_ABORT) + seat_connection_fatal(rlogin->seat, "%s", error_msg); } /* Otherwise, the remote side closed the connection normally. */ } diff --git a/otherbackends/supdup.c b/otherbackends/supdup.c index 697c3e84..5c9494d5 100644 --- a/otherbackends/supdup.c +++ b/otherbackends/supdup.c @@ -594,7 +594,8 @@ static void supdup_closing(Plug *plug, PlugCloseType type, } if (type != PLUGCLOSE_NORMAL) { logevent(supdup->logctx, error_msg); - seat_connection_fatal(supdup->seat, "%s", error_msg); + if (type != PLUGCLOSE_USER_ABORT) + seat_connection_fatal(supdup->seat, "%s", error_msg); } /* Otherwise, the remote side closed the connection normally. */ } diff --git a/otherbackends/telnet.c b/otherbackends/telnet.c index 80a7cc24..1c0f5d68 100644 --- a/otherbackends/telnet.c +++ b/otherbackends/telnet.c @@ -656,7 +656,8 @@ static void telnet_closing(Plug *plug, PlugCloseType type, } if (type != PLUGCLOSE_NORMAL) { logevent(telnet->logctx, error_msg); - seat_connection_fatal(telnet->seat, "%s", error_msg); + if (type != PLUGCLOSE_USER_ABORT) + seat_connection_fatal(telnet->seat, "%s", error_msg); } /* Otherwise, the remote side closed the connection normally. */ } diff --git a/proxy/sshproxy.c b/proxy/sshproxy.c index fd9afc4e..a26ccb04 100644 --- a/proxy/sshproxy.c +++ b/proxy/sshproxy.c @@ -13,18 +13,6 @@ const bool ssh_proxy_supported = true; -/* - * TODO for future work: - * - * If the user manually aborts the attempt to make the proxy SSH - * connection (e.g. by hitting ^C at a userpass prompt, or refusing to - * accept the proxy server's host key), then I think it would be nicer - * if we didn't give a connection_fatal error box. If I've aborted the - * connection deliberately, I don't need to be told it happened, and - * I'd rather not have the UI annoyance of clicking away an extra - * error dialog. - */ - typedef struct SshProxy { char *errmsg; Conf *conf; @@ -318,6 +306,8 @@ static void sshproxy_send_close(SshProxy *sp) if (sp->errmsg) plug_closing_error(sp->plug, sp->errmsg); + else if (!sp->conn_established && backend_exitcode(sp->backend) == 0) + plug_closing_user_abort(sp->plug); else plug_closing_normal(sp->plug); } diff --git a/ssh/ssh.c b/ssh/ssh.c index 831afd72..6ba1c577 100644 --- a/ssh/ssh.c +++ b/ssh/ssh.c @@ -606,7 +606,9 @@ static void ssh_socket_log(Plug *plug, PlugLogType type, SockAddr *addr, static void ssh_closing(Plug *plug, PlugCloseType type, const char *error_msg) { Ssh *ssh = container_of(plug, Ssh, plug); - if (type != PLUGCLOSE_NORMAL) { + if (type == PLUGCLOSE_USER_ABORT) { + ssh_user_close(ssh, "%s", error_msg); + } else if (type != PLUGCLOSE_NORMAL) { ssh_remote_error(ssh, "%s", error_msg); } else if (ssh->bpp) { ssh->bpp->input_eof = true; -- cgit v1.2.3 From 028714d02ab8e181b93ea2096fcfc12fd1133ea7 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 6 Nov 2021 14:01:18 +0000 Subject: Fix Plink's handling of interactor_announce() blank lines. I'd forgotten that the text-only branch of seat_antispoof_msg() constructs a string from its input in the expectation that it's a one-line message. So it was a mistake to put a \n at the start of the string in interactor_announce() to get a blank line first. Now interactor_announce() makes an extra call to seat_antispoof_msg to show its blank line, and seat_antispoof_msg itself handles the blank-line case specially. --- proxy/interactor.c | 2 +- utils/antispoof.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/proxy/interactor.c b/proxy/interactor.c index d2227ae3..958e5a98 100644 --- a/proxy/interactor.c +++ b/proxy/interactor.c @@ -97,7 +97,7 @@ InteractionReadySeat interactor_announce(Interactor *itr) if (need_announcement) { const char *prefix = ""; if (itr_top->last_to_talk != NULL) - prefix = "\r\n"; + seat_antispoof_msg(iseat, ""); /* leave a separating blank line */ char *desc = interactor_description(itr); char *adjective = (level == 0 ? dupstr("primary") : diff --git a/utils/antispoof.c b/utils/antispoof.c index 3a633189..60769af4 100644 --- a/utils/antispoof.c +++ b/utils/antispoof.c @@ -12,7 +12,7 @@ void seat_antispoof_msg(InteractionReadySeat iseat, const char *msg) * unmodified as an unspoofable header. */ put_datapl(sb, ptrlen_from_asciz(msg)); - } else { + } else if (*msg) { /* * Otherwise, add enough padding around it that the server * wouldn't be able to mimic it within our line-length -- cgit v1.2.3 From 7eb7d5e2e9117132940f6bc865a23aa3cc1b60ff Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 6 Nov 2021 14:33:03 +0000 Subject: New Seat query, has_mixed_input_stream(). (TL;DR: to suppress redundant 'Press Return to begin session' prompts in between hops of a jump-host configuration, in Plink.) This new query method directly asks the Seat the question: is the same stream of input used to provide responses to interactive login prompts, and the session input provided after login concludes? It's used to suppress the last-ditch anti-spoofing defence in Plink of interactively asking 'Access granted. Press Return to begin session', on the basis that any such spoofing attack works by confusing the user about what's a legit login prompt before the session begins and what's sent by the server after the main session begins - so if those two things take input from different places, the user can't be confused. This doesn't change the existing behaviour of Plink, which was already suppressing the antispoof prompt in cases where its standard input was redirected from something other than a terminal. But previously it was doing it within the can_set_trust_status() seat query, and I've now moved it out into a separate query function. The reason why these need to be separate is for SshProxy, which needs to give an unusual combination of answers when run inside Plink. For can_set_trust_status(), it needs to return whatever the parent Seat returns, so that all the login prompts for a string of proxy connections in session will be antispoofed the same way. But you only want that final 'Access granted' prompt to happen _once_, after all the proxy connection setup phases are done, because up until then you're still in the safe hands of PuTTY itself presenting an unbroken sequence of legit login prompts (even if they come from a succession of different servers). Hence, SshProxy unconditionally returns 'no' to the query of whether it has a single mixed input stream, because indeed, it never does - for purposes of session input it behaves like an always-redirected Plink, no matter what kind of real Seat it ends up sending its pre-session login prompts to. --- proxy/sshproxy.c | 1 + pscp.c | 1 + psftp.c | 1 + putty.h | 17 +++++++++++++++++ ssh/connection1-client.c | 6 +++++- ssh/connection2-client.c | 9 +++++++-- ssh/server.c | 1 + ssh/sesschan.c | 1 + unix/console.c | 27 ++++++++++++++++++--------- unix/plink.c | 1 + unix/window.c | 1 + utils/nullseat.c | 2 ++ utils/tempseat.c | 7 +++++++ windows/console.c | 27 ++++++++++++++++++--------- windows/plink.c | 1 + windows/window.c | 1 + 16 files changed, 83 insertions(+), 21 deletions(-) diff --git a/proxy/sshproxy.c b/proxy/sshproxy.c index a26ccb04..49d1ea98 100644 --- a/proxy/sshproxy.c +++ b/proxy/sshproxy.c @@ -502,6 +502,7 @@ static const SeatVtable SshProxy_seat_vt = { .stripctrl_new = sshproxy_stripctrl_new, .set_trust_status = sshproxy_set_trust_status, .can_set_trust_status = sshproxy_can_set_trust_status, + .has_mixed_input_stream = nullseat_has_mixed_input_stream_no, .verbose = sshproxy_verbose, .interactive = sshproxy_interactive, .get_cursor_position = nullseat_get_cursor_position, diff --git a/pscp.c b/pscp.c index 13e89812..837e89d9 100644 --- a/pscp.c +++ b/pscp.c @@ -87,6 +87,7 @@ static const SeatVtable pscp_seat_vt = { .stripctrl_new = console_stripctrl_new, .set_trust_status = nullseat_set_trust_status, .can_set_trust_status = nullseat_can_set_trust_status_yes, + .has_mixed_input_stream = nullseat_has_mixed_input_stream_no, .verbose = cmdline_seat_verbose, .interactive = nullseat_interactive_no, .get_cursor_position = nullseat_get_cursor_position, diff --git a/psftp.c b/psftp.c index 14483569..d0317c2e 100644 --- a/psftp.c +++ b/psftp.c @@ -68,6 +68,7 @@ static const SeatVtable psftp_seat_vt = { .stripctrl_new = console_stripctrl_new, .set_trust_status = nullseat_set_trust_status, .can_set_trust_status = nullseat_can_set_trust_status_yes, + .has_mixed_input_stream = nullseat_has_mixed_input_stream_no, .verbose = cmdline_seat_verbose, .interactive = nullseat_interactive_yes, .get_cursor_position = nullseat_get_cursor_position, diff --git a/putty.h b/putty.h index 0047156f..232bbe30 100644 --- a/putty.h +++ b/putty.h @@ -1289,6 +1289,18 @@ struct SeatVtable { */ bool (*can_set_trust_status)(Seat *seat); + /* + * Query whether this Seat's interactive prompt responses and its + * session input come from the same place. + * + * If false, this is used to suppress the final 'Press Return to + * begin session' anti-spoofing prompt in Plink. For example, + * Plink itself sets this flag if its standard input is redirected + * (and therefore not coming from the same place as the console + * it's sending its prompts to). + */ + bool (*has_mixed_input_stream)(Seat *seat); + /* * Ask the seat whether it would like verbose messages. */ @@ -1365,6 +1377,8 @@ static inline void seat_set_trust_status(Seat *seat, bool trusted) { seat->vt->set_trust_status(seat, trusted); } static inline bool seat_can_set_trust_status(Seat *seat) { return seat->vt->can_set_trust_status(seat); } +static inline bool seat_has_mixed_input_stream(Seat *seat) +{ return seat->vt->has_mixed_input_stream(seat); } static inline bool seat_verbose(Seat *seat) { return seat->vt->verbose(seat); } static inline bool seat_interactive(Seat *seat) @@ -1437,6 +1451,8 @@ StripCtrlChars *nullseat_stripctrl_new( void nullseat_set_trust_status(Seat *seat, bool trusted); bool nullseat_can_set_trust_status_yes(Seat *seat); bool nullseat_can_set_trust_status_no(Seat *seat); +bool nullseat_has_mixed_input_stream_yes(Seat *seat); +bool nullseat_has_mixed_input_stream_no(Seat *seat); bool nullseat_verbose_no(Seat *seat); bool nullseat_verbose_yes(Seat *seat); bool nullseat_interactive_no(Seat *seat); @@ -1463,6 +1479,7 @@ StripCtrlChars *console_stripctrl_new( Seat *seat, BinarySink *bs_out, SeatInteractionContext sic); void console_set_trust_status(Seat *seat, bool trusted); bool console_can_set_trust_status(Seat *seat); +bool console_has_mixed_input_stream(Seat *seat); /* * Other centralised seat functions. diff --git a/ssh/connection1-client.c b/ssh/connection1-client.c index 7429b892..41bf9716 100644 --- a/ssh/connection1-client.c +++ b/ssh/connection1-client.c @@ -544,5 +544,9 @@ SshChannel *ssh1_serverside_agent_open(ConnectionLayer *cl, Channel *chan) bool ssh1_connection_need_antispoof_prompt(struct ssh1_connection_state *s) { seat_set_trust_status(s->ppl.seat, false); - return !seat_can_set_trust_status(s->ppl.seat); + if (!seat_has_mixed_input_stream(s->ppl.seat)) + return false; + if (seat_can_set_trust_status(s->ppl.seat)) + return false; + return true; } diff --git a/ssh/connection2-client.c b/ssh/connection2-client.c index 88338500..f17d1e21 100644 --- a/ssh/connection2-client.c +++ b/ssh/connection2-client.c @@ -501,6 +501,11 @@ void ssh2channel_send_terminal_size_change(SshChannel *sc, int w, int h) bool ssh2_connection_need_antispoof_prompt(struct ssh2_connection_state *s) { seat_set_trust_status(s->ppl.seat, false); - bool success = seat_can_set_trust_status(s->ppl.seat); - return (!success && !ssh_is_bare(s->ppl.ssh)); + if (!seat_has_mixed_input_stream(s->ppl.seat)) + return false; + if (seat_can_set_trust_status(s->ppl.seat)) + return false; + if (ssh_is_bare(s->ppl.ssh)) + return false; + return true; } diff --git a/ssh/server.c b/ssh/server.c index 99237616..347aa14b 100644 --- a/ssh/server.c +++ b/ssh/server.c @@ -128,6 +128,7 @@ static const SeatVtable server_seat_vt = { .stripctrl_new = nullseat_stripctrl_new, .set_trust_status = nullseat_set_trust_status, .can_set_trust_status = nullseat_can_set_trust_status_no, + .has_mixed_input_stream = nullseat_has_mixed_input_stream_no, .verbose = nullseat_verbose_no, .interactive = nullseat_interactive_no, .get_cursor_position = nullseat_get_cursor_position, diff --git a/ssh/sesschan.c b/ssh/sesschan.c index 5379b308..15b8bb4e 100644 --- a/ssh/sesschan.c +++ b/ssh/sesschan.c @@ -207,6 +207,7 @@ static const SeatVtable sesschan_seat_vt = { .stripctrl_new = nullseat_stripctrl_new, .set_trust_status = nullseat_set_trust_status, .can_set_trust_status = nullseat_can_set_trust_status_no, + .has_mixed_input_stream = nullseat_has_mixed_input_stream_no, .verbose = nullseat_verbose_no, .interactive = nullseat_interactive_no, .get_cursor_position = nullseat_get_cursor_position, diff --git a/unix/console.c b/unix/console.c index da795614..4131abdc 100644 --- a/unix/console.c +++ b/unix/console.c @@ -328,19 +328,11 @@ void console_set_trust_status(Seat *seat, bool trusted) bool console_can_set_trust_status(Seat *seat) { - if (console_batch_mode || !is_interactive() || !console_antispoof_prompt) { + if (console_batch_mode) { /* * In batch mode, we don't need to worry about the server * mimicking our interactive authentication, because the user * already knows not to expect any. - * - * If standard input isn't connected to a terminal, likewise, - * because even if the server did send a spoof authentication - * prompt, the user couldn't respond to it via the terminal - * anyway. - * - * We also return true without enabling any defences if the - * user has purposely disabled the antispoof prompt. */ return true; } @@ -348,6 +340,23 @@ bool console_can_set_trust_status(Seat *seat) return false; } +bool console_has_mixed_input_stream(Seat *seat) +{ + if (!is_interactive() || !console_antispoof_prompt) { + /* + * If standard input isn't connected to a terminal, then even + * if the server did send a spoof authentication prompt, the + * user couldn't respond to it via the terminal anyway. + * + * We also pretend this is true if the user has purposely + * disabled the antispoof prompt. + */ + return false; + } + + return true; +} + /* * Warn about the obsolescent key file format. * diff --git a/unix/plink.c b/unix/plink.c index dee0cbec..446bef21 100644 --- a/unix/plink.c +++ b/unix/plink.c @@ -411,6 +411,7 @@ static const SeatVtable plink_seat_vt = { .stripctrl_new = console_stripctrl_new, .set_trust_status = console_set_trust_status, .can_set_trust_status = console_can_set_trust_status, + .has_mixed_input_stream = console_has_mixed_input_stream, .verbose = cmdline_seat_verbose, .interactive = plink_seat_interactive, .get_cursor_position = nullseat_get_cursor_position, diff --git a/unix/window.c b/unix/window.c index c5894ffa..c9d635ab 100644 --- a/unix/window.c +++ b/unix/window.c @@ -414,6 +414,7 @@ static const SeatVtable gtk_seat_vt = { .stripctrl_new = gtk_seat_stripctrl_new, .set_trust_status = gtk_seat_set_trust_status, .can_set_trust_status = gtk_seat_can_set_trust_status, + .has_mixed_input_stream = nullseat_has_mixed_input_stream_yes, .verbose = nullseat_verbose_yes, .interactive = nullseat_interactive_yes, .get_cursor_position = gtk_seat_get_cursor_position, diff --git a/utils/nullseat.c b/utils/nullseat.c index a53f563e..43a338fb 100644 --- a/utils/nullseat.c +++ b/utils/nullseat.c @@ -41,6 +41,8 @@ StripCtrlChars *nullseat_stripctrl_new( void nullseat_set_trust_status(Seat *seat, bool trusted) {} bool nullseat_can_set_trust_status_yes(Seat *seat) { return true; } bool nullseat_can_set_trust_status_no(Seat *seat) { return false; } +bool nullseat_has_mixed_input_stream_yes(Seat *seat) { return true; } +bool nullseat_has_mixed_input_stream_no(Seat *seat) { return false; } bool nullseat_verbose_no(Seat *seat) { return false; } bool nullseat_verbose_yes(Seat *seat) { return true; } bool nullseat_interactive_no(Seat *seat) { return false; } diff --git a/utils/tempseat.c b/utils/tempseat.c index eac10f8d..e1956eaa 100644 --- a/utils/tempseat.c +++ b/utils/tempseat.c @@ -198,6 +198,12 @@ static bool tempseat_can_set_trust_status(Seat *seat) return seat_can_set_trust_status(ts->realseat); } +static bool tempseat_has_mixed_input_stream(Seat *seat) +{ + TempSeat *ts = container_of(seat, TempSeat, seat); + return seat_has_mixed_input_stream(ts->realseat); +} + /* ---------------------------------------------------------------------- * Methods that should never be called on a TempSeat, so we can put an * unreachable() in them. @@ -324,6 +330,7 @@ static const struct SeatVtable tempseat_vt = { .stripctrl_new = tempseat_stripctrl_new, .set_trust_status = tempseat_set_trust_status, .can_set_trust_status = tempseat_can_set_trust_status, + .has_mixed_input_stream = tempseat_has_mixed_input_stream, .verbose = tempseat_verbose, .interactive = tempseat_interactive, .get_cursor_position = tempseat_get_cursor_position, diff --git a/windows/console.c b/windows/console.c index abd31270..9fa9d7f0 100644 --- a/windows/console.c +++ b/windows/console.c @@ -194,19 +194,11 @@ void console_set_trust_status(Seat *seat, bool trusted) bool console_can_set_trust_status(Seat *seat) { - if (console_batch_mode || !is_interactive() || !console_antispoof_prompt) { + if (console_batch_mode) { /* * In batch mode, we don't need to worry about the server * mimicking our interactive authentication, because the user * already knows not to expect any. - * - * If standard input isn't connected to a terminal, likewise, - * because even if the server did send a spoof authentication - * prompt, the user couldn't respond to it via the terminal - * anyway. - * - * We also return true without enabling any defences if the - * user has purposely disabled the antispoof prompt. */ return true; } @@ -214,6 +206,23 @@ bool console_can_set_trust_status(Seat *seat) return false; } +bool console_has_mixed_input_stream(Seat *seat) +{ + if (!is_interactive() || !console_antispoof_prompt) { + /* + * If standard input isn't connected to a terminal, then even + * if the server did send a spoof authentication prompt, the + * user couldn't respond to it via the terminal anyway. + * + * We also pretend this is true if the user has purposely + * disabled the antispoof prompt. + */ + return false; + } + + return true; +} + /* * 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). diff --git a/windows/plink.c b/windows/plink.c index ef982be1..8dc44c9e 100644 --- a/windows/plink.c +++ b/windows/plink.c @@ -105,6 +105,7 @@ static const SeatVtable plink_seat_vt = { .stripctrl_new = console_stripctrl_new, .set_trust_status = console_set_trust_status, .can_set_trust_status = console_can_set_trust_status, + .has_mixed_input_stream = console_has_mixed_input_stream, .verbose = cmdline_seat_verbose, .interactive = plink_seat_interactive, .get_cursor_position = nullseat_get_cursor_position, diff --git a/windows/window.c b/windows/window.c index 1aa83743..8ab371bb 100644 --- a/windows/window.c +++ b/windows/window.c @@ -352,6 +352,7 @@ static const SeatVtable win_seat_vt = { .stripctrl_new = win_seat_stripctrl_new, .set_trust_status = win_seat_set_trust_status, .can_set_trust_status = win_seat_can_set_trust_status, + .has_mixed_input_stream = nullseat_has_mixed_input_stream_yes, .verbose = nullseat_verbose_yes, .interactive = nullseat_interactive_yes, .get_cursor_position = win_seat_get_cursor_position, -- cgit v1.2.3 From 4e93a2c1b89289f35894a61c158a29a69b033b17 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 7 Nov 2021 06:37:30 +0000 Subject: Fix obvious braino in tempseat_output. I'd added a data length to the 'type' field of output_chunk instead of the 'size' field. Caused an assertion failure when I tried a simple SSH proxy operation on Windows just now, because the output_chunks and the output bufchain didn't match up, and no wonder. The mystery is how it hasn't been consistently failing like that since September! --- utils/tempseat.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/tempseat.c b/utils/tempseat.c index e1956eaa..5854a5dc 100644 --- a/utils/tempseat.c +++ b/utils/tempseat.c @@ -71,7 +71,7 @@ static size_t tempseat_output(Seat *seat, SeatOutputType type, ts->outchunk_head = new_chunk; ts->outchunk_tail = new_chunk; } - ts->outchunk_tail->type += len; + ts->outchunk_tail->size += len; return bufchain_size(&ts->output); } -- cgit v1.2.3 From efee4e0eae8cc15fdb8614c022e67b9568d8a0cb Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 19 Nov 2021 10:21:40 +0000 Subject: Add some more bufchain_try_* functions. We already had bufchain_try_fetch_consume; now we also have bufchain_try_fetch (for when you want to wait until that much data is available but then not commit to removing it), and bufchain_try_consume (so you can conveniently ignore a certain amount of incoming data). --- misc.h | 2 ++ utils/bufchain.c | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/misc.h b/misc.h index 5c8f982e..84643936 100644 --- a/misc.h +++ b/misc.h @@ -123,6 +123,8 @@ ptrlen bufchain_prefix(bufchain *ch); void bufchain_consume(bufchain *ch, size_t len); void bufchain_fetch(bufchain *ch, void *data, size_t len); void bufchain_fetch_consume(bufchain *ch, void *data, size_t len); +bool bufchain_try_consume(bufchain *ch, size_t len); +bool bufchain_try_fetch(bufchain *ch, void *data, size_t len); bool bufchain_try_fetch_consume(bufchain *ch, void *data, size_t len); size_t bufchain_fetch_consume_up_to(bufchain *ch, void *data, size_t len); void bufchain_set_callback_inner( diff --git a/utils/bufchain.c b/utils/bufchain.c index 60ebc2be..9b02c65f 100644 --- a/utils/bufchain.c +++ b/utils/bufchain.c @@ -153,6 +153,26 @@ void bufchain_fetch_consume(bufchain *ch, void *data, size_t len) bufchain_consume(ch, len); } +bool bufchain_try_fetch(bufchain *ch, void *data, size_t len) +{ + if (ch->buffersize >= len) { + bufchain_fetch(ch, data, len); + return true; + } else { + return false; + } +} + +bool bufchain_try_consume(bufchain *ch, size_t len) +{ + if (ch->buffersize >= len) { + bufchain_consume(ch, len); + return true; + } else { + return false; + } +} + bool bufchain_try_fetch_consume(bufchain *ch, void *data, size_t len) { if (ch->buffersize >= len) { -- cgit v1.2.3 From be8d3974ff7ccf3864d134722ff5b9ba611c1ed4 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 19 Nov 2021 10:23:32 +0000 Subject: Generalise strbuf_catf() into put_fmt(). marshal.h now provides a macro put_fmt() which allows you to write arbitrary printf-formatted data to an arbitrary BinarySink. We already had this facility for strbufs in particular, in the form of strbuf_catf(). That was able to take advantage of knowing the inner structure of a strbuf to minimise memory allocation (it would snprintf directly into the strbuf's existing buffer if possible). For a general black-box BinarySink we can't do that, so instead we dupvprintf into a temporary buffer. For consistency, I've removed strbuf_catf, and converted all uses of it into the new put_fmt - and I've also added an extra vtable method in the BinarySink API, so that put_fmt can still use strbuf_catf's more efficient memory management when talking to a strbuf, and fall back to the simpler strategy when that's not available. --- crypto/ecc-ssh.c | 4 +- crypto/hmac.c | 12 +++--- crypto/rsa.c | 6 +-- keygen/pockle.c | 24 +++++------ marshal.h | 11 +++++ misc.h | 2 - otherbackends/supdup.c | 26 +++++------ proxy/proxy.c | 4 +- ssh/scpserver.c | 10 ++--- sshpubk.c | 44 +++++++++---------- testcrypt.c | 46 ++++++++++---------- unix/dialog.c | 14 +++--- unix/network.c | 8 ++-- unix/procnet.c | 4 +- unix/storage.c | 2 +- unix/unifont.c | 12 +++--- utils/antispoof.c | 2 +- utils/buildinfo.c | 95 ++++++++++++++++++++--------------------- utils/marshal.c | 20 +++++++++ utils/strbuf.c | 26 +++++------ windows/pageant.c | 8 ++-- windows/storage.c | 2 +- windows/utils/split_into_argv.c | 2 +- windows/window.c | 26 +++++------ 24 files changed, 217 insertions(+), 193 deletions(-) diff --git a/crypto/ecc-ssh.c b/crypto/ecc-ssh.c index d985866d..893a5877 100644 --- a/crypto/ecc-ssh.c +++ b/crypto/ecc-ssh.c @@ -684,11 +684,11 @@ static char *ecc_cache_str_shared( strbuf *sb = strbuf_new(); if (curve_name) - strbuf_catf(sb, "%s,", curve_name); + put_fmt(sb, "%s,", curve_name); char *hx = mp_get_hex(x); char *hy = mp_get_hex(y); - strbuf_catf(sb, "0x%s,0x%s", hx, hy); + put_fmt(sb, "0x%s,0x%s", hx, hy); sfree(hx); sfree(hy); diff --git a/crypto/hmac.c b/crypto/hmac.c index 8f18eb28..f04d74b5 100644 --- a/crypto/hmac.c +++ b/crypto/hmac.c @@ -42,20 +42,20 @@ static ssh2_mac *hmac_new(const ssh2_macalg *alg, ssh_cipher *cipher) ctx->digest = snewn(ctx->hashalg->hlen, uint8_t); ctx->text_name = strbuf_new(); - strbuf_catf(ctx->text_name, "HMAC-%s%s", - ctx->hashalg->text_basename, extra->suffix); + put_fmt(ctx->text_name, "HMAC-%s%s", + ctx->hashalg->text_basename, extra->suffix); if (extra->annotation || ctx->hashalg->annotation) { - strbuf_catf(ctx->text_name, " ("); + put_fmt(ctx->text_name, " ("); const char *sep = ""; if (extra->annotation) { - strbuf_catf(ctx->text_name, "%s%s", sep, extra->annotation); + put_fmt(ctx->text_name, "%s%s", sep, extra->annotation); sep = ", "; } if (ctx->hashalg->annotation) { - strbuf_catf(ctx->text_name, "%s%s", sep, ctx->hashalg->annotation); + put_fmt(ctx->text_name, "%s%s", sep, ctx->hashalg->annotation); sep = ", "; } - strbuf_catf(ctx->text_name, ")"); + put_fmt(ctx->text_name, ")"); } ctx->mac.vt = alg; diff --git a/crypto/rsa.c b/crypto/rsa.c index e3dcbdda..ef832868 100644 --- a/crypto/rsa.c +++ b/crypto/rsa.c @@ -311,11 +311,11 @@ char *rsa_ssh1_fingerprint(RSAKey *key) ssh_hash_final(hash, digest); out = strbuf_new(); - strbuf_catf(out, "%"SIZEu" ", mp_get_nbits(key->modulus)); + put_fmt(out, "%"SIZEu" ", mp_get_nbits(key->modulus)); for (i = 0; i < 16; i++) - strbuf_catf(out, "%s%02x", i ? ":" : "", digest[i]); + put_fmt(out, "%s%02x", i ? ":" : "", digest[i]); if (key->comment) - strbuf_catf(out, " %s", key->comment); + put_fmt(out, " %s", key->comment); return strbuf_to_str(out); } diff --git a/keygen/pockle.c b/keygen/pockle.c index 60017e33..2a072f18 100644 --- a/keygen/pockle.c +++ b/keygen/pockle.c @@ -410,10 +410,10 @@ strbuf *pockle_mpu(Pockle *pockle, mp_int *p) memset(needed, 0, pockle->nlist * sizeof(bool)); needed[pr->index] = true; - strbuf_catf(sb, "[MPU - Primality Certificate]\nVersion 1.0\nBase 10\n\n" - "Proof for:\nN "); + put_fmt(sb, "[MPU - Primality Certificate]\nVersion 1.0\nBase 10\n\n" + "Proof for:\nN "); mp_write_decimal(sb, p); - strbuf_catf(sb, "\n"); + put_fmt(sb, "\n"); for (size_t index = pockle->nlist; index-- > 0 ;) { if (!needed[index]) @@ -421,27 +421,27 @@ strbuf *pockle_mpu(Pockle *pockle, mp_int *p) pr = pockle->list[index]; if (mp_get_nbits(pr->prime) <= 64) { - strbuf_catf(sb, "\nType Small\nN "); + put_fmt(sb, "\nType Small\nN "); mp_write_decimal(sb, pr->prime); - strbuf_catf(sb, "\n"); + put_fmt(sb, "\n"); } else { assert(pr->witness); - strbuf_catf(sb, "\nType BLS5\nN "); + put_fmt(sb, "\nType BLS5\nN "); mp_write_decimal(sb, pr->prime); - strbuf_catf(sb, "\n"); + put_fmt(sb, "\n"); for (size_t i = 0; i < pr->nfactors; i++) { - strbuf_catf(sb, "Q[%"SIZEu"] ", i+1); + put_fmt(sb, "Q[%"SIZEu"] ", i+1); mp_write_decimal(sb, pr->factors[i]->prime); assert(pr->factors[i]->index < index); needed[pr->factors[i]->index] = true; - strbuf_catf(sb, "\n"); + put_fmt(sb, "\n"); } for (size_t i = 0; i < pr->nfactors + 1; i++) { - strbuf_catf(sb, "A[%"SIZEu"] ", i); + put_fmt(sb, "A[%"SIZEu"] ", i); mp_write_decimal(sb, pr->witness); - strbuf_catf(sb, "\n"); + put_fmt(sb, "\n"); } - strbuf_catf(sb, "----\n"); + put_fmt(sb, "----\n"); } } sfree(needed); diff --git a/marshal.h b/marshal.h index 108d8aeb..bcd2b38d 100644 --- a/marshal.h +++ b/marshal.h @@ -4,6 +4,7 @@ #include "defs.h" #include +#include /* * A sort of 'abstract base class' or 'interface' or 'trait' which is @@ -12,6 +13,7 @@ */ struct BinarySink { void (*write)(BinarySink *sink, const void *data, size_t len); + void (*writefmtv)(BinarySink *sink, const char *fmt, va_list ap); BinarySink *binarysink_; }; @@ -25,6 +27,7 @@ struct BinarySink { #define BinarySink_IMPLEMENTATION BinarySink binarysink_[1] #define BinarySink_INIT(obj, writefn) \ ((obj)->binarysink_->write = (writefn), \ + (obj)->binarysink_->writefmtv = NULL, \ (obj)->binarysink_->binarysink_ = (obj)->binarysink_) /* @@ -139,6 +142,12 @@ struct BinarySink { #define put_datapl(bs, pl) \ BinarySink_put_datapl(BinarySink_UPCAST(bs), pl) +/* Emit printf-formatted data, with no terminator. */ +#define put_fmt(bs, ...) \ + BinarySink_put_fmt(BinarySink_UPCAST(bs), __VA_ARGS__) +#define put_fmtv(bs, fmt, ap) \ + BinarySink_put_fmtv(BinarySink_UPCAST(bs), fmt, ap) + /* * The underlying real C functions that implement most of those * macros. Generally you won't want to call these directly, because @@ -166,6 +175,8 @@ void BinarySink_put_asciz(BinarySink *, const char *str); bool BinarySink_put_pstring(BinarySink *, const char *str); void BinarySink_put_mp_ssh1(BinarySink *bs, mp_int *x); void BinarySink_put_mp_ssh2(BinarySink *bs, mp_int *x); +void BinarySink_put_fmt(BinarySink *, const char *fmt, ...) PRINTF_LIKE(2, 3); +void BinarySink_put_fmtv(BinarySink *, const char *fmt, va_list ap); /* ---------------------------------------------------------------------- */ diff --git a/misc.h b/misc.h index 84643936..0cb55b65 100644 --- a/misc.h +++ b/misc.h @@ -56,8 +56,6 @@ void *strbuf_append(strbuf *buf, size_t len); void strbuf_shrink_to(strbuf *buf, size_t new_len); void strbuf_shrink_by(strbuf *buf, size_t amount_to_remove); char *strbuf_to_str(strbuf *buf); /* does free buf, but you must free result */ -void strbuf_catf(strbuf *buf, const char *fmt, ...) PRINTF_LIKE(2, 3); -void strbuf_catfv(strbuf *buf, const char *fmt, va_list ap); static inline void strbuf_clear(strbuf *buf) { strbuf_shrink_to(buf, 0); } bool strbuf_chomp(strbuf *buf, char char_to_remove); diff --git a/otherbackends/supdup.c b/otherbackends/supdup.c index 5c9494d5..a272b256 100644 --- a/otherbackends/supdup.c +++ b/otherbackends/supdup.c @@ -289,7 +289,7 @@ static void do_argsdone(Supdup *supdup, strbuf *outbuf, int c) */ // We only care about the new position. - strbuf_catf(outbuf, "\033[%d;%dH", supdup->td_args[2]+1, supdup->td_args[3]+1); + put_fmt(outbuf, "\033[%d;%dH", supdup->td_args[2]+1, supdup->td_args[3]+1); break; case TDMV0: @@ -298,7 +298,7 @@ static void do_argsdone(Supdup *supdup, strbuf *outbuf, int c) General cursor position code. Followed by two bytes; the new vertical and horizontal positions. */ - strbuf_catf(outbuf, "\033[%d;%dH", supdup->td_args[0]+1, supdup->td_args[1]+1); + put_fmt(outbuf, "\033[%d;%dH", supdup->td_args[0]+1, supdup->td_args[1]+1); break; case TDEOF: @@ -312,7 +312,7 @@ static void do_argsdone(Supdup *supdup, strbuf *outbuf, int c) lines lower on the screen than the cursor. The cursor does not move. */ - strbuf_catf(outbuf, "\033[J"); + put_fmt(outbuf, "\033[J"); break; case TDEOL: @@ -321,7 +321,7 @@ static void do_argsdone(Supdup *supdup, strbuf *outbuf, int c) position the cursor is at and all positions to the right on the same line. The cursor does not move. */ - strbuf_catf(outbuf, "\033[K"); + put_fmt(outbuf, "\033[K"); break; case TDDLF: @@ -329,7 +329,7 @@ static void do_argsdone(Supdup *supdup, strbuf *outbuf, int c) Clear the character position the cursor is on. The cursor does not move. */ - strbuf_catf(outbuf, "\033[X"); + put_fmt(outbuf, "\033[X"); break; case TDCRL: @@ -339,7 +339,7 @@ static void do_argsdone(Supdup *supdup, strbuf *outbuf, int c) that line. If the cursor is at the bottom line, scroll up. */ - strbuf_catf(outbuf, "\015\012"); + put_fmt(outbuf, "\015\012"); break; case TDNOP: @@ -383,7 +383,7 @@ static void do_argsdone(Supdup *supdup, strbuf *outbuf, int c) line. */ - strbuf_catf(outbuf, "\033[C"); + put_fmt(outbuf, "\033[C"); break; case TDCLR: @@ -391,7 +391,7 @@ static void do_argsdone(Supdup *supdup, strbuf *outbuf, int c) Erase the screen. Home the cursor to the top left hand corner of the screen. */ - strbuf_catf(outbuf, "\033[2J\033[H"); + put_fmt(outbuf, "\033[2J\033[H"); break; case TDBEL: @@ -399,7 +399,7 @@ static void do_argsdone(Supdup *supdup, strbuf *outbuf, int c) Generate an audio tone, bell, whatever. */ - strbuf_catf(outbuf, "\007"); + put_fmt(outbuf, "\007"); break; case TDILP: @@ -410,7 +410,7 @@ static void do_argsdone(Supdup *supdup, strbuf *outbuf, int c) on and all lines below it move down; lines moved off the bottom of the screen are lost. */ - strbuf_catf(outbuf, "\033[%dL", supdup->td_args[0]); + put_fmt(outbuf, "\033[%dL", supdup->td_args[0]); break; case TDDLP: @@ -421,7 +421,7 @@ static void do_argsdone(Supdup *supdup, strbuf *outbuf, int c) Newly- created lines at the bottom of the screen are blank. */ - strbuf_catf(outbuf, "\033[%dM", supdup->td_args[0]); + put_fmt(outbuf, "\033[%dM", supdup->td_args[0]); break; case TDICP: @@ -432,7 +432,7 @@ static void do_argsdone(Supdup *supdup, strbuf *outbuf, int c) current line move to the right; characters moved off the end of the line are lost. */ - strbuf_catf(outbuf, "\033[%d@", supdup->td_args[0]); + put_fmt(outbuf, "\033[%d@", supdup->td_args[0]); break; case TDDCP: @@ -442,7 +442,7 @@ static void do_argsdone(Supdup *supdup, strbuf *outbuf, int c) the one the cursor is on. Newly-created characters at the end of the line are blank. */ - strbuf_catf(outbuf, "\033[%dP", supdup->td_args[0]); + put_fmt(outbuf, "\033[%dP", supdup->td_args[0]); break; case TDBOW: diff --git a/proxy/proxy.c b/proxy/proxy.c index d6041754..c3a882e6 100644 --- a/proxy/proxy.c +++ b/proxy/proxy.c @@ -1369,7 +1369,7 @@ char *format_telnet_command(SockAddr *addr, int port, Conf *conf) eo += 4; } else if (strnicmp(fmt + eo, "port", 4) == 0) { - strbuf_catf(buf, "%d", port); + put_fmt(buf, "%d", port); eo += 4; } else if (strnicmp(fmt + eo, "user", 4) == 0) { @@ -1389,7 +1389,7 @@ char *format_telnet_command(SockAddr *addr, int port, Conf *conf) } else if (strnicmp(fmt + eo, "proxyport", 9) == 0) { int port = conf_get_int(conf, CONF_proxy_port); - strbuf_catf(buf, "%d", port); + put_fmt(buf, "%d", port); eo += 9; } else { diff --git a/ssh/scpserver.c b/ssh/scpserver.c index 97b20814..15633ecb 100644 --- a/ssh/scpserver.c +++ b/ssh/scpserver.c @@ -536,7 +536,7 @@ static void scp_source_send_E(ScpSource *scp) assert(scp->n_pending_commands == 0); scp->pending_commands[scp->n_pending_commands++] = cmd = strbuf_new(); - strbuf_catf(cmd, "E\012"); + put_fmt(cmd, "E\012"); } static void scp_source_send_CD( @@ -550,7 +550,7 @@ static void scp_source_send_CD( if (scp->send_file_times && (attrs.flags & SSH_FILEXFER_ATTR_ACMODTIME)) { scp->pending_commands[scp->n_pending_commands++] = cmd = strbuf_new(); /* Our SFTP-based filesystem API doesn't support microsecond times */ - strbuf_catf(cmd, "T%lu 0 %lu 0\012", attrs.mtime, attrs.atime); + put_fmt(cmd, "T%lu 0 %lu 0\012", attrs.mtime, attrs.atime); } const char *slash; @@ -559,9 +559,9 @@ static void scp_source_send_CD( slash+1, name.len - (slash+1 - (const char *)name.ptr)); scp->pending_commands[scp->n_pending_commands++] = cmd = strbuf_new(); - strbuf_catf(cmd, "%c%04o %"PRIu64" %.*s\012", cmdchar, - (unsigned)(attrs.permissions & 07777), - size, PTRLEN_PRINTF(name)); + put_fmt(cmd, "%c%04o %"PRIu64" %.*s\012", cmdchar, + (unsigned)(attrs.permissions & 07777), + size, PTRLEN_PRINTF(name)); if (cmdchar == 'C') { /* We'll also wait for an ack before sending the file data, diff --git a/sshpubk.c b/sshpubk.c index e7197de3..003213ca 100644 --- a/sshpubk.c +++ b/sshpubk.c @@ -1550,33 +1550,33 @@ strbuf *ppk_save_sb(ssh2_userkey *key, const char *passphrase, } strbuf *out = strbuf_new_nm(); - strbuf_catf(out, "PuTTY-User-Key-File-%u: %s\n", - params.fmt_version, ssh_key_ssh_id(key->key)); - strbuf_catf(out, "Encryption: %s\n", cipherstr); - strbuf_catf(out, "Comment: %s\n", key->comment); - strbuf_catf(out, "Public-Lines: %d\n", base64_lines(pub_blob->len)); + put_fmt(out, "PuTTY-User-Key-File-%u: %s\n", + params.fmt_version, ssh_key_ssh_id(key->key)); + put_fmt(out, "Encryption: %s\n", cipherstr); + put_fmt(out, "Comment: %s\n", key->comment); + put_fmt(out, "Public-Lines: %d\n", base64_lines(pub_blob->len)); base64_encode_s(BinarySink_UPCAST(out), pub_blob->u, pub_blob->len, 64); if (params.fmt_version == 3 && ciphertype->keylen != 0) { - strbuf_catf(out, "Key-Derivation: %s\n", - params.argon2_flavour == Argon2d ? "Argon2d" : - params.argon2_flavour == Argon2i ? "Argon2i" : "Argon2id"); - strbuf_catf(out, "Argon2-Memory: %"PRIu32"\n", params.argon2_mem); + put_fmt(out, "Key-Derivation: %s\n", + params.argon2_flavour == Argon2d ? "Argon2d" : + params.argon2_flavour == Argon2i ? "Argon2i" : "Argon2id"); + put_fmt(out, "Argon2-Memory: %"PRIu32"\n", params.argon2_mem); assert(!params.argon2_passes_auto); - strbuf_catf(out, "Argon2-Passes: %"PRIu32"\n", params.argon2_passes); - strbuf_catf(out, "Argon2-Parallelism: %"PRIu32"\n", - params.argon2_parallelism); - strbuf_catf(out, "Argon2-Salt: "); + put_fmt(out, "Argon2-Passes: %"PRIu32"\n", params.argon2_passes); + put_fmt(out, "Argon2-Parallelism: %"PRIu32"\n", + params.argon2_parallelism); + put_fmt(out, "Argon2-Salt: "); for (size_t i = 0; i < passphrase_salt->len; i++) - strbuf_catf(out, "%02x", passphrase_salt->u[i]); - strbuf_catf(out, "\n"); + put_fmt(out, "%02x", passphrase_salt->u[i]); + put_fmt(out, "\n"); } - strbuf_catf(out, "Private-Lines: %d\n", base64_lines(priv_encrypted_len)); + put_fmt(out, "Private-Lines: %d\n", base64_lines(priv_encrypted_len)); base64_encode_s(BinarySink_UPCAST(out), priv_blob_encrypted, priv_encrypted_len, 64); - strbuf_catf(out, "Private-MAC: "); + put_fmt(out, "Private-MAC: "); for (i = 0; i < macalg->len; i++) - strbuf_catf(out, "%02x", priv_mac[i]); - strbuf_catf(out, "\n"); + put_fmt(out, "%02x", priv_mac[i]); + put_fmt(out, "\n"); strbuf_free(cipher_mac_keys_blob); strbuf_free(passphrase_salt); @@ -1740,7 +1740,7 @@ static void ssh2_fingerprint_blob_md5(ptrlen blob, strbuf *sb) hash_simple(&ssh_md5, blob, digest); for (unsigned i = 0; i < 16; i++) - strbuf_catf(sb, "%02x%s", digest[i], i==15 ? "" : ":"); + put_fmt(sb, "%02x%s", digest[i], i==15 ? "" : ":"); } static void ssh2_fingerprint_blob_sha256(ptrlen blob, strbuf *sb) @@ -1778,9 +1778,9 @@ char *ssh2_fingerprint_blob(ptrlen blob, FingerprintType fptype) const ssh_keyalg *alg = find_pubkey_alg_len(algname); if (alg) { int bits = ssh_key_public_bits(alg, blob); - strbuf_catf(sb, "%.*s %d ", PTRLEN_PRINTF(algname), bits); + put_fmt(sb, "%.*s %d ", PTRLEN_PRINTF(algname), bits); } else { - strbuf_catf(sb, "%.*s ", PTRLEN_PRINTF(algname)); + put_fmt(sb, "%.*s ", PTRLEN_PRINTF(algname)); } } diff --git a/testcrypt.c b/testcrypt.c index 1b875c2b..2376fa04 100644 --- a/testcrypt.c +++ b/testcrypt.c @@ -592,7 +592,7 @@ static struct mpint_list get_mpint_list(BinarySource *in) static void finaliser_return_uint(strbuf *out, void *ctx) { unsigned *uval = (unsigned *)ctx; - strbuf_catf(out, "%u\n", *uval); + put_fmt(out, "%u\n", *uval); sfree(uval); } @@ -620,7 +620,7 @@ static void finaliser_return_opt_string_asciz(strbuf *out, void *ctx) char *val = *valp; sfree(valp); if (!val) - strbuf_catf(out, "NULL\n"); + put_fmt(out, "NULL\n"); else return_val_string_asciz(out, val); } @@ -639,7 +639,7 @@ static void finaliser_return_opt_string_asciz_const(strbuf *out, void *ctx) const char *val = *valp; sfree(valp); if (!val) - strbuf_catf(out, "NULL\n"); + put_fmt(out, "NULL\n"); else return_val_string_asciz_const(out, val); } @@ -676,29 +676,29 @@ GET_CONSUMED_FN(pcs) static void return_int(strbuf *out, intmax_t u) { - strbuf_catf(out, "%"PRIdMAX"\n", u); + put_fmt(out, "%"PRIdMAX"\n", u); } static void return_uint(strbuf *out, uintmax_t u) { - strbuf_catf(out, "0x%"PRIXMAX"\n", u); + put_fmt(out, "0x%"PRIXMAX"\n", u); } static void return_boolean(strbuf *out, bool b) { - strbuf_catf(out, "%s\n", b ? "true" : "false"); + put_fmt(out, "%s\n", b ? "true" : "false"); } static void return_pocklestatus(strbuf *out, PockleStatus status) { switch (status) { default: - strbuf_catf(out, "POCKLE_BAD_STATUS_VALUE\n"); + put_fmt(out, "POCKLE_BAD_STATUS_VALUE\n"); break; #define STATUS_CASE(id) \ case id: \ - strbuf_catf(out, "%s\n", #id); \ + put_fmt(out, "%s\n", #id); \ break; POCKLE_STATUSES(STATUS_CASE); @@ -711,11 +711,11 @@ static void return_pocklestatus(strbuf *out, PockleStatus status) static void return_mr_result(strbuf *out, struct mr_result result) { if (!result.passed) - strbuf_catf(out, "failed\n"); + put_fmt(out, "failed\n"); else if (!result.potential_primitive_root) - strbuf_catf(out, "passed\n"); + put_fmt(out, "passed\n"); else - strbuf_catf(out, "passed+ppr\n"); + put_fmt(out, "passed+ppr\n"); } static void return_val_string_asciz_const(strbuf *out, const char *s) @@ -735,7 +735,7 @@ static void return_val_string_asciz(strbuf *out, char *s) static void return_opt_##type_name(strbuf *out, c_type ptr) \ { \ if (!ptr) \ - strbuf_catf(out, "NULL\n"); \ + put_fmt(out, "NULL\n"); \ else \ return_##type_name(out, ptr); \ } @@ -750,7 +750,7 @@ NULLABLE_RETURN_WRAPPER(val_mpint, mp_int *) static void handle_hello(BinarySource *in, strbuf *out) { - strbuf_catf(out, "hello, world\n"); + put_fmt(out, "hello, world\n"); } static void rsa_free(RSAKey *rsa) @@ -803,7 +803,7 @@ static void handle_getstring(BinarySource *in, strbuf *out) if (c > ' ' && c < 0x7F && c != '%') { put_byte(out, c); } else { - strbuf_catf(out, "%%%02X", 0xFFU & (unsigned)c); + put_fmt(out, "%%%02X", 0xFFU & (unsigned)c); } } put_byte(out, '\n'); @@ -822,7 +822,7 @@ static void handle_mp_dump(BinarySource *in, strbuf *out) { mp_int *mp = get_val_mpint(in); for (size_t i = mp_max_bytes(mp); i-- > 0 ;) - strbuf_catf(out, "%02X", mp_get_byte(mp, i)); + put_fmt(out, "%02X", mp_get_byte(mp, i)); put_byte(out, '\n'); } @@ -1320,26 +1320,26 @@ strbuf *get_implementations_commasep(ptrlen alg) put_datapl(out, alg); if (ptrlen_startswith(alg, PTRLEN_LITERAL("aes"), NULL)) { - strbuf_catf(out, ",%.*s_sw", PTRLEN_PRINTF(alg)); + put_fmt(out, ",%.*s_sw", PTRLEN_PRINTF(alg)); #if HAVE_AES_NI - strbuf_catf(out, ",%.*s_ni", PTRLEN_PRINTF(alg)); + put_fmt(out, ",%.*s_ni", PTRLEN_PRINTF(alg)); #endif #if HAVE_NEON_CRYPTO - strbuf_catf(out, ",%.*s_neon", PTRLEN_PRINTF(alg)); + put_fmt(out, ",%.*s_neon", PTRLEN_PRINTF(alg)); #endif } else if (ptrlen_startswith(alg, PTRLEN_LITERAL("sha256"), NULL) || ptrlen_startswith(alg, PTRLEN_LITERAL("sha1"), NULL)) { - strbuf_catf(out, ",%.*s_sw", PTRLEN_PRINTF(alg)); + put_fmt(out, ",%.*s_sw", PTRLEN_PRINTF(alg)); #if HAVE_SHA_NI - strbuf_catf(out, ",%.*s_ni", PTRLEN_PRINTF(alg)); + put_fmt(out, ",%.*s_ni", PTRLEN_PRINTF(alg)); #endif #if HAVE_NEON_CRYPTO - strbuf_catf(out, ",%.*s_neon", PTRLEN_PRINTF(alg)); + put_fmt(out, ",%.*s_neon", PTRLEN_PRINTF(alg)); #endif } else if (ptrlen_startswith(alg, PTRLEN_LITERAL("sha512"), NULL)) { - strbuf_catf(out, ",%.*s_sw", PTRLEN_PRINTF(alg)); + put_fmt(out, ",%.*s_sw", PTRLEN_PRINTF(alg)); #if HAVE_NEON_SHA512 - strbuf_catf(out, ",%.*s_neon", PTRLEN_PRINTF(alg)); + put_fmt(out, ",%.*s_neon", PTRLEN_PRINTF(alg)); #endif } diff --git a/unix/dialog.c b/unix/dialog.c index 743be31d..9ba1e164 100644 --- a/unix/dialog.c +++ b/unix/dialog.c @@ -3615,12 +3615,12 @@ int gtk_seat_confirm_ssh_host_key( strbuf *sb = strbuf_new(); if (fingerprints[SSH_FPTYPE_SHA256]) - strbuf_catf(sb, "SHA256 fingerprint: %s\n", - fingerprints[SSH_FPTYPE_SHA256]); + put_fmt(sb, "SHA256 fingerprint: %s\n", + fingerprints[SSH_FPTYPE_SHA256]); if (fingerprints[SSH_FPTYPE_MD5]) - strbuf_catf(sb, "MD5 fingerprint: %s\n", - fingerprints[SSH_FPTYPE_MD5]); - strbuf_catf(sb, "Full text of host's public key:"); + put_fmt(sb, "MD5 fingerprint: %s\n", + fingerprints[SSH_FPTYPE_MD5]); + put_fmt(sb, "Full text of host's public key:"); /* We have to manually wrap the public key, or else the GtkLabel * will resize itself to accommodate the longest word, which will * lead to a hilariously wide message box. */ @@ -3943,12 +3943,12 @@ static void eventlog_list_handler(union control *ctrl, dlgparam *dp, strbuf_clear(es->seldata); for (i = 0; i < es->ninitial; i++) { if (dlg_listbox_issel(ctrl, dp, i)) - strbuf_catf(es->seldata, "%s\n", es->events_initial[i]); + put_fmt(es->seldata, "%s\n", es->events_initial[i]); } for (i = 0; i < es->ncircular; i++) { if (dlg_listbox_issel(ctrl, dp, es->ninitial + i)) { int j = (es->circular_first + i) % LOGEVENT_CIRCULAR_MAX; - strbuf_catf(es->seldata, "%s\n", es->events_circular[j]); + put_fmt(es->seldata, "%s\n", es->events_circular[j]); } } diff --git a/unix/network.c b/unix/network.c index b8f923e9..fe0bc70b 100644 --- a/unix/network.c +++ b/unix/network.c @@ -232,9 +232,9 @@ SockAddr *sk_namelookup(const char *host, char **canonicalname, int address_fami ret->superfamily = IP; if (ret->ais->ai_canonname != NULL) - strbuf_catf(realhost, "%s", ret->ais->ai_canonname); + put_fmt(realhost, "%s", ret->ais->ai_canonname); else - strbuf_catf(realhost, "%s", host); + put_fmt(realhost, "%s", host); #else if ((a = inet_addr(host)) == (unsigned long)(in_addr_t)(-1)) { /* @@ -258,7 +258,7 @@ SockAddr *sk_namelookup(const char *host, char **canonicalname, int address_fami } /* This way we are always sure the h->h_name is valid :) */ strbuf_clear(realhost); - strbuf_catf(realhost, "%s", h->h_name); + put_fmt(realhost, "%s", h->h_name); for (n = 0; h->h_addr_list[n]; n++); ret->addresses = snewn(n, unsigned long); ret->naddresses = n; @@ -273,7 +273,7 @@ SockAddr *sk_namelookup(const char *host, char **canonicalname, int address_fami */ ret->superfamily = IP; strbuf_clear(realhost); - strbuf_catf(realhost, "%s", host); + put_fmt(realhost, "%s", host); ret->addresses = snew(unsigned long); ret->naddresses = 1; ret->addresses[0] = ntohl(a); diff --git a/unix/procnet.c b/unix/procnet.c index 43b1f055..09373956 100644 --- a/unix/procnet.c +++ b/unix/procnet.c @@ -170,8 +170,8 @@ static char *format_sockaddr(const void *addr, int family) const uint32_t *addrwords = (const uint32_t *)a->sin6_addr.s6_addr; for (int i = 0; i < 4; i++) - strbuf_catf(sb, "%08X", addrwords[i]); - strbuf_catf(sb, ":%04X", ntohs(a->sin6_port)); + put_fmt(sb, "%08X", addrwords[i]); + put_fmt(sb, ":%04X", ntohs(a->sin6_port)); return strbuf_to_str(sb); } else { diff --git a/unix/storage.c b/unix/storage.c index 16bb63e0..7a9585f9 100644 --- a/unix/storage.c +++ b/unix/storage.c @@ -173,7 +173,7 @@ static char *make_filename(int index, const char *subname) if (index == INDEX_SESSION) { strbuf *sb = strbuf_new(); tmp = make_filename(INDEX_SESSIONDIR, NULL); - strbuf_catf(sb, "%s/", tmp); + put_fmt(sb, "%s/", tmp); sfree(tmp); make_session_filename(subname, sb); return strbuf_to_str(sb); diff --git a/unix/unifont.c b/unix/unifont.c index d50a7810..62445db2 100644 --- a/unix/unifont.c +++ b/unix/unifont.c @@ -1904,19 +1904,19 @@ static void pangofont_enum_fonts(GtkWidget *widget, fontsel_add_entry callback, /* Weight: normal, then lighter, then bolder */ if (weight <= PANGO_WEIGHT_NORMAL) weight = PANGO_WEIGHT_NORMAL - weight; - strbuf_catf(buf, "%4d", weight); + put_fmt(buf, "%4d", weight); - strbuf_catf(buf, " %2d", - pango_font_description_get_style(desc)); + put_fmt(buf, " %2d", + pango_font_description_get_style(desc)); int stretch = pango_font_description_get_stretch(desc); /* Stretch: closer to normal sorts earlier */ stretch = 2 * abs(PANGO_STRETCH_NORMAL - stretch) + (stretch < PANGO_STRETCH_NORMAL); - strbuf_catf(buf, " %2d", stretch); + put_fmt(buf, " %2d", stretch); - strbuf_catf(buf, " %2d", - pango_font_description_get_variant(desc)); + put_fmt(buf, " %2d", + pango_font_description_get_variant(desc)); stylekey = strbuf_to_str(buf); } diff --git a/utils/antispoof.c b/utils/antispoof.c index 60769af4..b3a04ef3 100644 --- a/utils/antispoof.c +++ b/utils/antispoof.c @@ -18,7 +18,7 @@ void seat_antispoof_msg(InteractionReadySeat iseat, const char *msg) * wouldn't be able to mimic it within our line-length * constraint. */ - strbuf_catf(sb, "-- %s ", msg); + put_fmt(sb, "-- %s ", msg); while (sb->len < 78) put_byte(sb, '-'); } diff --git a/utils/buildinfo.c b/utils/buildinfo.c index 2c40d6c2..3e42dec9 100644 --- a/utils/buildinfo.c +++ b/utils/buildinfo.c @@ -10,26 +10,25 @@ char *buildinfo(const char *newline) { strbuf *buf = strbuf_new(); - strbuf_catf(buf, "Build platform: %d-bit %s", - (int)(CHAR_BIT * sizeof(void *)), - BUILDINFO_PLATFORM); + put_fmt(buf, "Build platform: %d-bit %s", + (int)(CHAR_BIT * sizeof(void *)), BUILDINFO_PLATFORM); #ifdef __clang_version__ #define FOUND_COMPILER - strbuf_catf(buf, "%sCompiler: clang %s", newline, __clang_version__); + put_fmt(buf, "%sCompiler: clang %s", newline, __clang_version__); #elif defined __GNUC__ && defined __VERSION__ #define FOUND_COMPILER - strbuf_catf(buf, "%sCompiler: gcc %s", newline, __VERSION__); + put_fmt(buf, "%sCompiler: gcc %s", newline, __VERSION__); #endif #if defined _MSC_VER #ifndef FOUND_COMPILER #define FOUND_COMPILER - strbuf_catf(buf, "%sCompiler: ", newline); + put_fmt(buf, "%sCompiler: ", newline); #else - strbuf_catf(buf, ", emulating "); + put_fmt(buf, ", emulating "); #endif - strbuf_catf(buf, "Visual Studio"); + put_fmt(buf, "Visual Studio"); #if 0 /* @@ -51,69 +50,69 @@ char *buildinfo(const char *newline) * 19.28.29500.* and going up. Hence, 19 28 29500 is what we * compare _MSC_FULL_VER against above. */ - strbuf_catf(buf, " 2019 (16.9)"); + put_fmt(buf, " 2019 (16.9)"); #elif _MSC_VER == 1928 - strbuf_catf(buf, " 2019 (16.8)"); + put_fmt(buf, " 2019 (16.8)"); #elif _MSC_VER == 1927 - strbuf_catf(buf, " 2019 (16.7)"); + put_fmt(buf, " 2019 (16.7)"); #elif _MSC_VER == 1926 - strbuf_catf(buf, " 2019 (16.6)"); + put_fmt(buf, " 2019 (16.6)"); #elif _MSC_VER == 1925 - strbuf_catf(buf, " 2019 (16.5)"); + put_fmt(buf, " 2019 (16.5)"); #elif _MSC_VER == 1924 - strbuf_catf(buf, " 2019 (16.4)"); + put_fmt(buf, " 2019 (16.4)"); #elif _MSC_VER == 1923 - strbuf_catf(buf, " 2019 (16.3)"); + put_fmt(buf, " 2019 (16.3)"); #elif _MSC_VER == 1922 - strbuf_catf(buf, " 2019 (16.2)"); + put_fmt(buf, " 2019 (16.2)"); #elif _MSC_VER == 1921 - strbuf_catf(buf, " 2019 (16.1)"); + put_fmt(buf, " 2019 (16.1)"); #elif _MSC_VER == 1920 - strbuf_catf(buf, " 2019 (16.0)"); + put_fmt(buf, " 2019 (16.0)"); #elif _MSC_VER == 1916 - strbuf_catf(buf, " 2017 version 15.9"); + put_fmt(buf, " 2017 version 15.9"); #elif _MSC_VER == 1915 - strbuf_catf(buf, " 2017 version 15.8"); + put_fmt(buf, " 2017 version 15.8"); #elif _MSC_VER == 1914 - strbuf_catf(buf, " 2017 version 15.7"); + put_fmt(buf, " 2017 version 15.7"); #elif _MSC_VER == 1913 - strbuf_catf(buf, " 2017 version 15.6"); + put_fmt(buf, " 2017 version 15.6"); #elif _MSC_VER == 1912 - strbuf_catf(buf, " 2017 version 15.5"); + put_fmt(buf, " 2017 version 15.5"); #elif _MSC_VER == 1911 - strbuf_catf(buf, " 2017 version 15.3"); + put_fmt(buf, " 2017 version 15.3"); #elif _MSC_VER == 1910 - strbuf_catf(buf, " 2017 RTW (15.0)"); + put_fmt(buf, " 2017 RTW (15.0)"); #elif _MSC_VER == 1900 - strbuf_catf(buf, " 2015 (14.0)"); + put_fmt(buf, " 2015 (14.0)"); #elif _MSC_VER == 1800 - strbuf_catf(buf, " 2013 (12.0)"); + put_fmt(buf, " 2013 (12.0)"); #elif _MSC_VER == 1700 - strbuf_catf(buf, " 2012 (11.0)"); + put_fmt(buf, " 2012 (11.0)"); #elif _MSC_VER == 1600 - strbuf_catf(buf, " 2010 (10.0)"); + put_fmt(buf, " 2010 (10.0)"); #elif _MSC_VER == 1500 - strbuf_catf(buf, " 2008 (9.0)"); + put_fmt(buf, " 2008 (9.0)"); #elif _MSC_VER == 1400 - strbuf_catf(buf, " 2005 (8.0)"); + put_fmt(buf, " 2005 (8.0)"); #elif _MSC_VER == 1310 - strbuf_catf(buf, " .NET 2003 (7.1)"); + put_fmt(buf, " .NET 2003 (7.1)"); #elif _MSC_VER == 1300 - strbuf_catf(buf, " .NET 2002 (7.0)"); + put_fmt(buf, " .NET 2002 (7.0)"); #elif _MSC_VER == 1200 - strbuf_catf(buf, " 6.0"); + put_fmt(buf, " 6.0"); #else - strbuf_catf(buf, ", unrecognised version"); + put_fmt(buf, ", unrecognised version"); #endif - strbuf_catf(buf, ", _MSC_VER=%d", (int)_MSC_VER); + put_fmt(buf, ", _MSC_VER=%d", (int)_MSC_VER); #endif #ifdef BUILDINFO_GTK { char *gtk_buildinfo = buildinfo_gtk_version(); if (gtk_buildinfo) { - strbuf_catf(buf, "%sCompiled against GTK version %s", - newline, gtk_buildinfo); + put_fmt(buf, "%sCompiled against GTK version %s", + newline, gtk_buildinfo); sfree(gtk_buildinfo); } } @@ -122,34 +121,34 @@ char *buildinfo(const char *newline) { int echm = has_embedded_chm(); if (echm >= 0) - strbuf_catf(buf, "%sEmbedded HTML Help file: %s", newline, - echm ? "yes" : "no"); + put_fmt(buf, "%sEmbedded HTML Help file: %s", newline, + echm ? "yes" : "no"); } #endif #if defined _WINDOWS && defined MINEFIELD - strbuf_catf(buf, "%sBuild option: MINEFIELD", newline); + put_fmt(buf, "%sBuild option: MINEFIELD", newline); #endif #ifdef NO_IPV6 - strbuf_catf(buf, "%sBuild option: NO_IPV6", newline); + put_fmt(buf, "%sBuild option: NO_IPV6", newline); #endif #ifdef NO_GSSAPI - strbuf_catf(buf, "%sBuild option: NO_GSSAPI", newline); + put_fmt(buf, "%sBuild option: NO_GSSAPI", newline); #endif #ifdef STATIC_GSSAPI - strbuf_catf(buf, "%sBuild option: STATIC_GSSAPI", newline); + put_fmt(buf, "%sBuild option: STATIC_GSSAPI", newline); #endif #ifdef UNPROTECT - strbuf_catf(buf, "%sBuild option: UNPROTECT", newline); + put_fmt(buf, "%sBuild option: UNPROTECT", newline); #endif #ifdef FUZZING - strbuf_catf(buf, "%sBuild option: FUZZING", newline); + put_fmt(buf, "%sBuild option: FUZZING", newline); #endif #ifdef DEBUG - strbuf_catf(buf, "%sBuild option: DEBUG", newline); + put_fmt(buf, "%sBuild option: DEBUG", newline); #endif - strbuf_catf(buf, "%sSource commit: %s", newline, commitid); + put_fmt(buf, "%sSource commit: %s", newline, commitid); return strbuf_to_str(buf); } diff --git a/utils/marshal.c b/utils/marshal.c index ff9bb851..84d1391f 100644 --- a/utils/marshal.c +++ b/utils/marshal.c @@ -1,4 +1,5 @@ #include +#include #include #include @@ -99,6 +100,25 @@ bool BinarySink_put_pstring(BinarySink *bs, const char *str) return true; } +void BinarySink_put_fmtv(BinarySink *bs, const char *fmt, va_list ap) +{ + if (bs->writefmtv) { + bs->writefmtv(bs, fmt, ap); + } else { + char *str = dupvprintf(fmt, ap); + bs->write(bs, str, strlen(str)); + burnstr(str); + } +} + +void BinarySink_put_fmt(BinarySink *bs, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + BinarySink_put_fmtv(bs, fmt, ap); + va_end(ap); +} + /* ---------------------------------------------------------------------- */ static bool BinarySource_data_avail(BinarySource *src, size_t wanted) diff --git a/utils/strbuf.c b/utils/strbuf.c index 8358a413..636467a4 100644 --- a/utils/strbuf.c +++ b/utils/strbuf.c @@ -60,10 +60,21 @@ static void strbuf_BinarySink_write( memcpy(strbuf_append(buf_o, len), data, len); } +static void strbuf_BinarySink_writefmtv( + BinarySink *bs, const char *fmt, va_list ap) +{ + strbuf *buf_o = BinarySink_DOWNCAST(bs, strbuf); + struct strbuf_impl *buf = container_of(buf_o, struct strbuf_impl, visible); + STRBUF_SET_PTR(buf, dupvprintf_inner(buf->visible.s, buf->visible.len, + &buf->size, fmt, ap)); + buf->visible.len += strlen(buf->visible.s + buf->visible.len); +} + static strbuf *strbuf_new_general(bool nm) { struct strbuf_impl *buf = snew(struct strbuf_impl); BinarySink_INIT(&buf->visible, strbuf_BinarySink_write); + buf->visible.binarysink_->writefmtv = strbuf_BinarySink_writefmtv; buf->visible.len = 0; buf->size = 512; buf->nm = nm; @@ -89,21 +100,6 @@ char *strbuf_to_str(strbuf *buf_o) sfree(buf); return ret; } -void strbuf_catfv(strbuf *buf_o, const char *fmt, va_list ap) -{ - struct strbuf_impl *buf = container_of(buf_o, struct strbuf_impl, visible); - STRBUF_SET_PTR(buf, dupvprintf_inner(buf->visible.s, buf->visible.len, - &buf->size, fmt, ap)); - buf->visible.len += strlen(buf->visible.s + buf->visible.len); -} -void strbuf_catf(strbuf *buf_o, const char *fmt, ...) -{ - va_list ap; - va_start(ap, fmt); - strbuf_catfv(buf_o, fmt, ap); - va_end(ap); -} - strbuf *strbuf_new_for_agent_query(void) { strbuf *buf = strbuf_new(); diff --git a/windows/pageant.c b/windows/pageant.c index 0e25cc5d..6fbbf883 100644 --- a/windows/pageant.c +++ b/windows/pageant.c @@ -335,7 +335,7 @@ static void keylist_update_callback( switch (key->ssh_version) { case 1: { - strbuf_catf(listentry, "ssh1\t%s\t%s", fingerprint, comment); + put_fmt(listentry, "ssh1\t%s\t%s", fingerprint, comment); /* * Replace the space in the fingerprint (between bit count and @@ -390,15 +390,15 @@ static void keylist_update_callback( put_byte(listentry, c); } - strbuf_catf(listentry, "\t%s", comment); + put_fmt(listentry, "\t%s", comment); break; } } if (ext_flags & LIST_EXTENDED_FLAG_HAS_NO_CLEARTEXT_KEY) { - strbuf_catf(listentry, "\t(encrypted)"); + put_fmt(listentry, "\t(encrypted)"); } else if (ext_flags & LIST_EXTENDED_FLAG_HAS_ENCRYPTED_KEY_FILE) { - strbuf_catf(listentry, "\t(re-encryptable)"); + put_fmt(listentry, "\t(re-encryptable)"); /* At least one key can be re-encrypted */ ctx->enable_reencrypt_controls = true; diff --git a/windows/storage.c b/windows/storage.c index 2bb08953..6f05cdc7 100644 --- a/windows/storage.c +++ b/windows/storage.c @@ -318,7 +318,7 @@ void enum_settings_finish(settings_e *e) static void hostkey_regname(strbuf *sb, const char *hostname, int port, const char *keytype) { - strbuf_catf(sb, "%s@%d:", keytype, port); + put_fmt(sb, "%s@%d:", keytype, port); escape_registry_key(hostname, sb); } diff --git a/windows/utils/split_into_argv.c b/windows/utils/split_into_argv.c index c9f9e184..c42c7a0b 100644 --- a/windows/utils/split_into_argv.c +++ b/windows/utils/split_into_argv.c @@ -514,7 +514,7 @@ int main(int argc, char **argv) strbuf *cmdline = strbuf_new(); char *p; - strbuf_catf(cmdline, "%s -splat ", argv[0]); + put_fmt(cmdline, "%s -splat ", argv[0]); printf(" {\""); size_t args_start = cmdline->len; for (p = argv[2]; *p; p++) { diff --git a/windows/window.c b/windows/window.c index 8ab371bb..851dafb5 100644 --- a/windows/window.c +++ b/windows/window.c @@ -4946,7 +4946,7 @@ static void wintw_clip_write( get_unitab(CP_ACP, unitab, 0); - strbuf_catf( + put_fmt( rtf, "{\\rtf1\\ansi\\deff0{\\fonttbl\\f0\\fmodern %s;}\\f0\\fs%d", font->name, font->height*2); @@ -5034,16 +5034,16 @@ static void wintw_clip_write( for (i = 0; i < OSC4_NCOLOURS; i++) { if (palette[i] != 0) { const PALETTEENTRY *pe = &logpal->palPalEntry[i]; - strbuf_catf(rtf, "\\red%d\\green%d\\blue%d;", - pe->peRed, pe->peGreen, pe->peBlue); + put_fmt(rtf, "\\red%d\\green%d\\blue%d;", + pe->peRed, pe->peGreen, pe->peBlue); } } if (rgbtree) { rgbindex *rgbp; for (i = 0; (rgbp = index234(rgbtree, i)) != NULL; i++) - strbuf_catf(rtf, "\\red%d\\green%d\\blue%d;", - GetRValue(rgbp->ref), GetGValue(rgbp->ref), - GetBValue(rgbp->ref)); + put_fmt(rtf, "\\red%d\\green%d\\blue%d;", + GetRValue(rgbp->ref), GetGValue(rgbp->ref), + GetBValue(rgbp->ref)); } put_datapl(rtf, PTRLEN_LITERAL("}")); } @@ -5162,13 +5162,13 @@ static void wintw_clip_write( lastfgcolour = fgcolour; lastfg = fg; if (fg == -1) { - strbuf_catf(rtf, "\\cf%d ", - (fgcolour >= 0) ? palette[fgcolour] : 0); + put_fmt(rtf, "\\cf%d ", + (fgcolour >= 0) ? palette[fgcolour] : 0); } else { rgbindex rgb, *rgbp; rgb.ref = fg; if ((rgbp = find234(rgbtree, &rgb, NULL)) != NULL) - strbuf_catf(rtf, "\\cf%d ", rgbp->index); + put_fmt(rtf, "\\cf%d ", rgbp->index); } } @@ -5176,13 +5176,13 @@ static void wintw_clip_write( lastbgcolour = bgcolour; lastbg = bg; if (bg == -1) - strbuf_catf(rtf, "\\highlight%d ", - (bgcolour >= 0) ? palette[bgcolour] : 0); + put_fmt(rtf, "\\highlight%d ", + (bgcolour >= 0) ? palette[bgcolour] : 0); else { rgbindex rgb, *rgbp; rgb.ref = bg; if ((rgbp = find234(rgbtree, &rgb, NULL)) != NULL) - strbuf_catf(rtf, "\\highlight%d ", rgbp->index); + put_fmt(rtf, "\\highlight%d ", rgbp->index); } } @@ -5243,7 +5243,7 @@ static void wintw_clip_write( } else if (tdata[tindex+i] == 0x0D || tdata[tindex+i] == 0x0A) { put_datapl(rtf, PTRLEN_LITERAL("\\par\r\n")); } else if (tdata[tindex+i] > 0x7E || tdata[tindex+i] < 0x20) { - strbuf_catf(rtf, "\\'%02x", tdata[tindex+i]); + put_fmt(rtf, "\\'%02x", tdata[tindex+i]); } else { put_byte(rtf, tdata[tindex+i]); } -- cgit v1.2.3 From cc6d3591adeb08bc03f579df168ca3f8efd5fe64 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 19 Nov 2021 14:33:23 +0000 Subject: Marshalling macros put_dataz and put_datalit. When I wanted to append an ordinary C string to a BinarySink, without any prefix length field or suffix terminator, I was using the idiom put_datapl(bs, ptrlen_from_asciz(string)); but I've finally decided that's too cumbersome, and it deserves a shorter name. put_dataz(bs, string) now does the same thing - in fact it's a macro expanding to exactly the above. While I'm at it, I've also added put_datalit(), which is the same except that it expects a C string literal (and will enforce that at compile time, via PTRLEN_LITERAL which it calls in turn). You can use that where possible to avoid the run-time cost of the strlen. --- marshal.h | 4 ++++ unix/plink.c | 2 +- unix/storage.c | 4 ++-- utils/antispoof.c | 2 +- utils/prompts.c | 2 +- windows/dialog.c | 2 +- windows/plink.c | 2 +- 7 files changed, 11 insertions(+), 7 deletions(-) diff --git a/marshal.h b/marshal.h index bcd2b38d..20094289 100644 --- a/marshal.h +++ b/marshal.h @@ -141,6 +141,10 @@ struct BinarySink { BinarySink_put_data(BinarySink_UPCAST(bs), val, len) #define put_datapl(bs, pl) \ BinarySink_put_datapl(BinarySink_UPCAST(bs), pl) +#define put_dataz(bs, val) \ + BinarySink_put_datapl(BinarySink_UPCAST(bs), ptrlen_from_asciz(val)) +#define put_datalit(bs, val) \ + BinarySink_put_datapl(BinarySink_UPCAST(bs), PTRLEN_LITERAL(val)) /* Emit printf-formatted data, with no terminator. */ #define put_fmt(bs, ...) \ diff --git a/unix/plink.c b/unix/plink.c index 446bef21..385fe993 100644 --- a/unix/plink.c +++ b/unix/plink.c @@ -774,7 +774,7 @@ int main(int argc, char **argv) while (argc > 0) { if (cmdbuf->len > 0) put_byte(cmdbuf, ' '); /* add space separator */ - put_datapl(cmdbuf, ptrlen_from_asciz(p)); + put_dataz(cmdbuf, p); if (--argc > 0) p = *++argv; } diff --git a/unix/storage.c b/unix/storage.c index 7a9585f9..9132eb70 100644 --- a/unix/storage.c +++ b/unix/storage.c @@ -557,7 +557,7 @@ bool enum_settings_next(settings_e *handle, strbuf *out) fullpath = strbuf_new(); char *sessiondir = make_filename(INDEX_SESSIONDIR, NULL); - put_datapl(fullpath, ptrlen_from_asciz(sessiondir)); + put_dataz(fullpath, sessiondir); sfree(sessiondir); put_byte(fullpath, '/'); @@ -565,7 +565,7 @@ bool enum_settings_next(settings_e *handle, strbuf *out) while ( (de = readdir(handle->dp)) != NULL ) { strbuf_shrink_to(fullpath, baselen); - put_datapl(fullpath, ptrlen_from_asciz(de->d_name)); + put_dataz(fullpath, de->d_name); if (stat(fullpath->s, &st) < 0 || !S_ISREG(st.st_mode)) continue; /* try another one */ diff --git a/utils/antispoof.c b/utils/antispoof.c index b3a04ef3..6435944f 100644 --- a/utils/antispoof.c +++ b/utils/antispoof.c @@ -11,7 +11,7 @@ void seat_antispoof_msg(InteractionReadySeat iseat, const char *msg) * generated by the client, then we can just use the message * unmodified as an unspoofable header. */ - put_datapl(sb, ptrlen_from_asciz(msg)); + put_dataz(sb, msg); } else if (*msg) { /* * Otherwise, add enough padding around it that the server diff --git a/utils/prompts.c b/utils/prompts.c index e26ef905..e01dd01c 100644 --- a/utils/prompts.c +++ b/utils/prompts.c @@ -34,7 +34,7 @@ void add_prompt(prompts_t *p, char *promptstr, bool echo) void prompt_set_result(prompt_t *pr, const char *newstr) { strbuf_clear(pr->result); - put_datapl(pr->result, ptrlen_from_asciz(newstr)); + put_dataz(pr->result, newstr); } const char *prompt_get_result_ref(prompt_t *pr) diff --git a/windows/dialog.c b/windows/dialog.c index a5af31ed..b790f0f4 100644 --- a/windows/dialog.c +++ b/windows/dialog.c @@ -889,7 +889,7 @@ static INT_PTR CALLBACK HostKeyDialogProc(HWND hwnd, UINT msg, for (size_t i = 0; ctx->keywords[i]; i++) { if (strstartswith(p, ctx->keywords[i])) { p += strlen(ctx->keywords[i]); - put_datapl(sb, ptrlen_from_asciz(ctx->values[i])); + put_dataz(sb, ctx->values[i]); goto matched; } } diff --git a/windows/plink.c b/windows/plink.c index 8dc44c9e..037387e9 100644 --- a/windows/plink.c +++ b/windows/plink.c @@ -366,7 +366,7 @@ int main(int argc, char **argv) while (argc > 0) { if (cmdbuf->len > 0) put_byte(cmdbuf, ' '); /* add space separator */ - put_datapl(cmdbuf, ptrlen_from_asciz(p)); + put_dataz(cmdbuf, p); if (--argc > 0) p = *++argv; } -- cgit v1.2.3 From 30148eee6a5f6cdaa27a54e5376587c6a4693fd4 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 19 Nov 2021 13:13:23 +0000 Subject: marshal.[ch]: remove redundant declaration. Spotted this in passing while I was adding new functions in the same area. That 'struct strbuf;' must have been there since before I introduced defs.h to predeclare all the structure tag names and their typedefs. But marshal.h includes defs.h itself, so it has no reason to worry about the possibility that the typedef 'strbuf' might not already exist. --- marshal.h | 3 +-- utils/marshal.c | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/marshal.h b/marshal.h index 20094289..515f0e53 100644 --- a/marshal.h +++ b/marshal.h @@ -173,8 +173,7 @@ void BinarySink_put_uint64(BinarySink *, uint64_t); void BinarySink_put_string(BinarySink *, const void *data, size_t len); void BinarySink_put_stringpl(BinarySink *, ptrlen); void BinarySink_put_stringz(BinarySink *, const char *str); -struct strbuf; -void BinarySink_put_stringsb(BinarySink *, struct strbuf *); +void BinarySink_put_stringsb(BinarySink *, strbuf *); void BinarySink_put_asciz(BinarySink *, const char *str); bool BinarySink_put_pstring(BinarySink *, const char *str); void BinarySink_put_mp_ssh1(BinarySink *bs, mp_int *x); diff --git a/utils/marshal.c b/utils/marshal.c index 84d1391f..534ecf50 100644 --- a/utils/marshal.c +++ b/utils/marshal.c @@ -79,7 +79,7 @@ void BinarySink_put_stringz(BinarySink *bs, const char *str) BinarySink_put_string(bs, str, strlen(str)); } -void BinarySink_put_stringsb(BinarySink *bs, struct strbuf *buf) +void BinarySink_put_stringsb(BinarySink *bs, strbuf *buf) { BinarySink_put_string(bs, buf->s, buf->len); strbuf_free(buf); -- cgit v1.2.3 From 05b5aa0bb982bf6b4a36ef8585e415ebfa04ea0e Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 19 Nov 2021 10:14:43 +0000 Subject: proxy.c: name the 'ProxySocket' pointer consistently. It's variously 'ps' and 'p' in functions that already receive one, and it's 'ret' in the main function that initially constructs one. Let's call it 'ps' consistently, so that the code idioms are the same everywhere. --- proxy/cproxy.c | 90 +++++++-------- proxy/proxy.c | 346 ++++++++++++++++++++++++++++----------------------------- 2 files changed, 218 insertions(+), 218 deletions(-) diff --git a/proxy/cproxy.c b/proxy/cproxy.c index e58c344b..1ae4cf9a 100644 --- a/proxy/cproxy.c +++ b/proxy/cproxy.c @@ -27,7 +27,7 @@ void proxy_socks5_offerencryptedauth(BinarySink *bs) put_byte(bs, 0x03); /* CHAP */ } -int proxy_socks5_handlechap (ProxySocket *p) +int proxy_socks5_handlechap (ProxySocket *ps) { /* CHAP authentication reply format: @@ -41,67 +41,67 @@ int proxy_socks5_handlechap (ProxySocket *p) 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) { + while(ps->chap_num_attributes == 0 || + ps->chap_num_attributes_processed < ps->chap_num_attributes) { + if (ps->chap_num_attributes == 0 || + ps->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) + if (bufchain_size(&ps->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); + bufchain_fetch(&ps->pending_input_data, data, 2); + bufchain_consume(&ps->pending_input_data, 2); } - if (p->chap_num_attributes == 0) { + if (ps->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_error(p->plug, "Proxy error: SOCKS proxy wants " + plug_closing_error(ps->plug, "Proxy error: SOCKS proxy wants " "a different CHAP version"); return 1; } if (data[1] == 0x00) { - plug_closing_error(p->plug, "Proxy error: SOCKS proxy won't " + plug_closing_error(ps->plug, "Proxy error: SOCKS proxy won't " "negotiate CHAP with us"); return 1; } - p->chap_num_attributes = data[1]; + ps->chap_num_attributes = data[1]; } else { - if (p->chap_current_attribute == -1) { + if (ps->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]; + ps->chap_current_attribute = data[0]; + ps->chap_current_datalen = data[1]; } - if (bufchain_size(&p->pending_input_data) < - p->chap_current_datalen) + if (bufchain_size(&ps->pending_input_data) < + ps->chap_current_datalen) return 1; /* not got everything yet */ /* get the response */ - bufchain_fetch(&p->pending_input_data, data, - p->chap_current_datalen); + bufchain_fetch(&ps->pending_input_data, data, + ps->chap_current_datalen); - bufchain_consume(&p->pending_input_data, - p->chap_current_datalen); + bufchain_consume(&ps->pending_input_data, + ps->chap_current_datalen); - switch (p->chap_current_attribute) { + switch (ps->chap_current_attribute) { case 0x00: /* Successful authentication */ if (data[0] == 0x00) - p->state = 2; + ps->state = 2; else { - plug_closing_error(p->plug, "Proxy error: SOCKS proxy " + plug_closing_error(ps->plug, "Proxy error: SOCKS proxy " "refused CHAP authentication"); return 1; } @@ -111,38 +111,38 @@ int proxy_socks5_handlechap (ProxySocket *p) outbuf[1] = 0x01; /* One attribute */ outbuf[2] = 0x04; /* Response */ outbuf[3] = 0x10; /* Length */ - hmacmd5_chap(data, p->chap_current_datalen, - conf_get_str(p->conf, CONF_proxy_password), + hmacmd5_chap(data, ps->chap_current_datalen, + conf_get_str(ps->conf, CONF_proxy_password), &outbuf[4]); - sk_write(p->sub_socket, outbuf, 20); + sk_write(ps->sub_socket, outbuf, 20); break; case 0x11: /* Chose a protocol */ if (data[0] != 0x85) { - plug_closing_error(p->plug, "Proxy error: Server chose " + plug_closing_error(ps->plug, "Proxy error: Server chose " "CHAP of other than HMAC-MD5 but we " "didn't offer it!"); return 1; } break; } - p->chap_current_attribute = -1; - p->chap_num_attributes_processed++; + ps->chap_current_attribute = -1; + ps->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; + if (ps->state == 8 && + ps->chap_num_attributes_processed >= ps->chap_num_attributes) { + ps->chap_num_attributes = 0; + ps->chap_num_attributes_processed = 0; + ps->chap_current_datalen = 0; } } return 0; } -int proxy_socks5_selectchap(ProxySocket *p) +int proxy_socks5_selectchap(ProxySocket *ps) { - char *username = conf_get_str(p->conf, CONF_proxy_username); - char *password = conf_get_str(p->conf, CONF_proxy_password); + char *username = conf_get_str(ps->conf, CONF_proxy_username); + char *password = conf_get_str(ps->conf, CONF_proxy_password); if (username[0] || password[0]) { char chapbuf[514]; int ulen; @@ -160,15 +160,15 @@ int proxy_socks5_selectchap(ProxySocket *p) chapbuf[6] = ulen; memcpy(chapbuf+7, username, ulen); - sk_write(p->sub_socket, chapbuf, ulen + 7); - p->chap_num_attributes = 0; - p->chap_num_attributes_processed = 0; - p->chap_current_attribute = -1; - p->chap_current_datalen = 0; + sk_write(ps->sub_socket, chapbuf, ulen + 7); + ps->chap_num_attributes = 0; + ps->chap_num_attributes_processed = 0; + ps->chap_current_attribute = -1; + ps->chap_current_datalen = 0; - p->state = 8; + ps->state = 8; } else - plug_closing_error(p->plug, "Proxy error: Server chose " + plug_closing_error(ps->plug, "Proxy error: Server chose " "CHAP authentication but we didn't offer it!"); return 1; } diff --git a/proxy/proxy.c b/proxy/proxy.c index c3a882e6..28e64253 100644 --- a/proxy/proxy.c +++ b/proxy/proxy.c @@ -22,52 +22,52 @@ * Call this when proxy negotiation is complete, so that this * socket can begin working normally. */ -void proxy_activate (ProxySocket *p) +void proxy_activate(ProxySocket *ps) { size_t output_before, output_after; - p->state = PROXY_STATE_ACTIVE; + ps->state = PROXY_STATE_ACTIVE; - plug_log(p->plug, PLUGLOG_CONNECT_SUCCESS, NULL, 0, NULL, 0); + plug_log(ps->plug, PLUGLOG_CONNECT_SUCCESS, NULL, 0, NULL, 0); /* we want to ignore new receive events until we have sent * all of our buffered receive data. */ - sk_set_frozen(p->sub_socket, true); + sk_set_frozen(ps->sub_socket, true); /* how many bytes of output have we buffered? */ - output_before = bufchain_size(&p->pending_oob_output_data) + - bufchain_size(&p->pending_output_data); + output_before = bufchain_size(&ps->pending_oob_output_data) + + bufchain_size(&ps->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) { - ptrlen data = bufchain_prefix(&p->pending_oob_output_data); - output_after += sk_write_oob(p->sub_socket, data.ptr, data.len); - bufchain_consume(&p->pending_oob_output_data, data.len); + while (bufchain_size(&ps->pending_oob_output_data) > 0) { + ptrlen data = bufchain_prefix(&ps->pending_oob_output_data); + output_after += sk_write_oob(ps->sub_socket, data.ptr, data.len); + bufchain_consume(&ps->pending_oob_output_data, data.len); } /* send buffered normal writes */ - while (bufchain_size(&p->pending_output_data) > 0) { - ptrlen data = bufchain_prefix(&p->pending_output_data); - output_after += sk_write(p->sub_socket, data.ptr, data.len); - bufchain_consume(&p->pending_output_data, data.len); + while (bufchain_size(&ps->pending_output_data) > 0) { + ptrlen data = bufchain_prefix(&ps->pending_output_data); + output_after += sk_write(ps->sub_socket, data.ptr, data.len); + bufchain_consume(&ps->pending_output_data, 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); + plug_sent(ps->plug, output_after); /* if we have a pending EOF to send, send it */ - if (p->pending_eof) sk_write_eof(p->sub_socket); + if (ps->pending_eof) sk_write_eof(ps->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(&p->sock, false); + if (!ps->freeze) + sk_set_frozen(&ps->sock, false); } /* basic proxy socket functions */ @@ -401,7 +401,7 @@ Socket *new_connection(SockAddr *addr, const char *hostname, if (type != PROXY_NONE && proxy_for_destination(addr, hostname, port, conf)) { - ProxySocket *ret; + ProxySocket *ps; SockAddr *proxy_addr; char *proxy_canonical_name; const char *proxy_type; @@ -418,41 +418,41 @@ Socket *new_connection(SockAddr *addr, const char *hostname, plug, conf)) != NULL) return sret; - ret = snew(ProxySocket); - ret->sock.vt = &ProxySocket_sockvt; - ret->plugimpl.vt = &ProxySocket_plugvt; - ret->conf = conf_copy(conf); - ret->plug = plug; - ret->remote_addr = addr; /* will need to be freed on close */ - ret->remote_port = port; + ps = snew(ProxySocket); + ps->sock.vt = &ProxySocket_sockvt; + ps->plugimpl.vt = &ProxySocket_plugvt; + ps->conf = conf_copy(conf); + ps->plug = plug; + ps->remote_addr = addr; /* will need to be freed on close */ + ps->remote_port = port; - ret->error = NULL; - ret->pending_eof = false; - ret->freeze = false; + ps->error = NULL; + ps->pending_eof = false; + ps->freeze = false; - bufchain_init(&ret->pending_input_data); - bufchain_init(&ret->pending_output_data); - bufchain_init(&ret->pending_oob_output_data); + bufchain_init(&ps->pending_input_data); + bufchain_init(&ps->pending_output_data); + bufchain_init(&ps->pending_oob_output_data); - ret->sub_socket = NULL; - ret->state = PROXY_STATE_NEW; - ret->negotiate = NULL; + ps->sub_socket = NULL; + ps->state = PROXY_STATE_NEW; + ps->negotiate = NULL; if (type == PROXY_HTTP) { - ret->negotiate = proxy_http_negotiate; + ps->negotiate = proxy_http_negotiate; proxy_type = "HTTP"; } else if (type == PROXY_SOCKS4) { - ret->negotiate = proxy_socks4_negotiate; + ps->negotiate = proxy_socks4_negotiate; proxy_type = "SOCKS 4"; } else if (type == PROXY_SOCKS5) { - ret->negotiate = proxy_socks5_negotiate; + ps->negotiate = proxy_socks5_negotiate; proxy_type = "SOCKS 5"; } else if (type == PROXY_TELNET) { - ret->negotiate = proxy_telnet_negotiate; + ps->negotiate = proxy_telnet_negotiate; proxy_type = "Telnet"; } else { - ret->error = "Proxy error: Unknown proxy method"; - return &ret->sock; + ps->error = "Proxy error: Unknown proxy method"; + return &ps->sock; } { @@ -478,9 +478,9 @@ Socket *new_connection(SockAddr *addr, const char *hostname, &proxy_canonical_name, conf_get_int(conf, CONF_addressfamily)); if (sk_addr_error(proxy_addr) != NULL) { - ret->error = "Proxy error: Unable to resolve proxy host name"; + ps->error = "Proxy error: Unable to resolve proxy host name"; sk_addr_free(proxy_addr); - return &ret->sock; + return &ps->sock; } sfree(proxy_canonical_name); @@ -497,18 +497,18 @@ Socket *new_connection(SockAddr *addr, const char *hostname, /* create the actual socket we will be using, * connected to our proxy server and port. */ - ret->sub_socket = sk_new(proxy_addr, - conf_get_int(conf, CONF_proxy_port), - privport, oobinline, - nodelay, keepalive, &ret->plugimpl); - if (sk_socket_error(ret->sub_socket) != NULL) - return &ret->sock; + ps->sub_socket = sk_new(proxy_addr, + conf_get_int(conf, CONF_proxy_port), + privport, oobinline, + nodelay, keepalive, &ps->plugimpl); + if (sk_socket_error(ps->sub_socket) != NULL) + return &ps->sock; /* start the proxy negotiation process... */ - sk_set_frozen(ret->sub_socket, false); - ret->negotiate(ret, PROXY_CHANGE_NEW); + sk_set_frozen(ps->sub_socket, false); + ps->negotiate(ps, PROXY_CHANGE_NEW); - return &ret->sock; + return &ps->sock; } /* no proxy, so just return the direct socket */ @@ -567,9 +567,9 @@ static bool get_line_end(char *data, size_t len, size_t *out) return false; } -int proxy_http_negotiate (ProxySocket *p, int change) +int proxy_http_negotiate (ProxySocket *ps, int change) { - if (p->state == PROXY_STATE_NEW) { + if (ps->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 @@ -578,15 +578,15 @@ int proxy_http_negotiate (ProxySocket *p, int change) char *buf, dest[512]; char *username, *password; - sk_getaddr(p->remote_addr, dest, lenof(dest)); + sk_getaddr(ps->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)); + dest, ps->remote_port, dest, ps->remote_port); + sk_write(ps->sub_socket, buf, strlen(buf)); sfree(buf); - username = conf_get_str(p->conf, CONF_proxy_username); - password = conf_get_str(p->conf, CONF_proxy_password); + username = conf_get_str(ps->conf, CONF_proxy_username); + password = conf_get_str(ps->conf, CONF_proxy_password); if (username[0] || password[0]) { char *buf, *buf2; int i, j, len; @@ -598,14 +598,14 @@ int proxy_http_negotiate (ProxySocket *p, int change) 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(ps->sub_socket, buf2, strlen(buf2)); sfree(buf); sfree(buf2); } - sk_write(p->sub_socket, "\r\n", 2); + sk_write(ps->sub_socket, "\r\n", 2); - p->state = 1; + ps->state = 1; return 0; } @@ -616,7 +616,7 @@ int proxy_http_negotiate (ProxySocket *p, int change) * a socket close, then some error must have occurred. we'll * just pass those errors up to the backend. */ - plug_closing(p->plug, p->closing_type, p->closing_error_msg); + plug_closing(ps->plug, ps->closing_type, ps->closing_error_msg); return 0; /* ignored */ } @@ -635,8 +635,8 @@ int proxy_http_negotiate (ProxySocket *p, int change) * what should we do? close the socket with an appropriate * error message? */ - return plug_accepting(p->plug, - p->accepting_constructor, p->accepting_ctx); + return plug_accepting(ps->plug, + ps->accepting_constructor, ps->accepting_ctx); } if (change == PROXY_CHANGE_RECEIVE) { @@ -647,15 +647,15 @@ int proxy_http_negotiate (ProxySocket *p, int change) char *data, *datap; size_t len, eol; - if (p->state == 1) { + if (ps->state == 1) { int min_ver, maj_ver, status; /* get the status line */ - len = bufchain_size(&p->pending_input_data); + len = bufchain_size(&ps->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); + bufchain_fetch(&ps->pending_input_data, data, len); /* * We must NUL-terminate this data, because Windows * sscanf appears to require a NUL at the end of the @@ -672,14 +672,14 @@ int proxy_http_negotiate (ProxySocket *p, int change) /* 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_error(p->plug, "Proxy error: " + plug_closing_error(ps->plug, "Proxy error: " "HTTP response was absent"); sfree(data); return 1; } /* remove the status line from the input buffer. */ - bufchain_consume(&p->pending_input_data, eol); + bufchain_consume(&ps->pending_input_data, eol); if (data[status] != '2') { /* error */ char *buf; @@ -688,7 +688,7 @@ int proxy_http_negotiate (ProxySocket *p, int change) (data[eol-1] == '\r' || data[eol-1] == '\n')) data[--eol] = '\0'; buf = dupprintf("Proxy error: %s", data+status); - plug_closing_error(p->plug, buf); + plug_closing_error(ps->plug, buf); sfree(buf); sfree(data); return 1; @@ -696,27 +696,27 @@ int proxy_http_negotiate (ProxySocket *p, int change) sfree(data); - p->state = 2; + ps->state = 2; } - if (p->state == 2) { + if (ps->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); + len = bufchain_size(&ps->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); + bufchain_fetch(&ps->pending_input_data, data, len); if (!get_line_end(datap, len, &eol)) { sfree(data); return 1; } while (eol > 2) { - bufchain_consume(&p->pending_input_data, eol); + bufchain_consume(&ps->pending_input_data, eol); datap += eol; len -= eol; if (!get_line_end(datap, len, &eol)) @@ -725,8 +725,8 @@ int proxy_http_negotiate (ProxySocket *p, int change) if (eol == 2) { /* we're done */ - bufchain_consume(&p->pending_input_data, 2); - proxy_activate(p); + bufchain_consume(&ps->pending_input_data, 2); + proxy_activate(ps); /* proxy activate will have dealt with * whatever is left of the buffer */ sfree(data); @@ -738,7 +738,7 @@ int proxy_http_negotiate (ProxySocket *p, int change) } } - plug_closing_error(p->plug, "Proxy error: unexpected proxy error"); + plug_closing_error(ps->plug, "Proxy error: unexpected proxy error"); return 1; } @@ -747,9 +747,9 @@ int proxy_http_negotiate (ProxySocket *p, int change) */ /* SOCKS version 4 */ -int proxy_socks4_negotiate (ProxySocket *p, int change) +int proxy_socks4_negotiate (ProxySocket *ps, int change) { - if (p->state == PROXY_CHANGE_NEW) { + if (ps->state == PROXY_CHANGE_NEW) { /* request format: * version number (1 byte) = 4 @@ -767,33 +767,33 @@ int proxy_socks4_negotiate (ProxySocket *p, int change) put_byte(command, 4); /* SOCKS version 4 */ put_byte(command, 1); /* CONNECT command */ - put_uint16(command, p->remote_port); + put_uint16(command, ps->remote_port); - switch (sk_addrtype(p->remote_addr)) { + switch (sk_addrtype(ps->remote_addr)) { case ADDRTYPE_IPV4: { char addr[4]; - sk_addrcopy(p->remote_addr, addr); + sk_addrcopy(ps->remote_addr, addr); put_data(command, addr, 4); break; } case ADDRTYPE_NAME: - sk_getaddr(p->remote_addr, hostname, lenof(hostname)); + sk_getaddr(ps->remote_addr, hostname, lenof(hostname)); put_uint32(command, 1); write_hostname = true; break; case ADDRTYPE_IPV6: - p->error = "Proxy error: SOCKS version 4 does not support IPv6"; + ps->error = "Proxy error: SOCKS version 4 does not support IPv6"; strbuf_free(command); return 1; } - put_asciz(command, conf_get_str(p->conf, CONF_proxy_username)); + put_asciz(command, conf_get_str(ps->conf, CONF_proxy_username)); if (write_hostname) put_asciz(command, hostname); - sk_write(p->sub_socket, command->s, command->len); + sk_write(ps->sub_socket, command->s, command->len); strbuf_free(command); - p->state = 1; + ps->state = 1; return 0; } @@ -804,7 +804,7 @@ int proxy_socks4_negotiate (ProxySocket *p, int change) * a socket close, then some error must have occurred. we'll * just pass those errors up to the backend. */ - plug_closing(p->plug, p->closing_type, p->closing_error_msg); + plug_closing(ps->plug, ps->closing_type, ps->closing_error_msg); return 0; /* ignored */ } @@ -823,8 +823,8 @@ int proxy_socks4_negotiate (ProxySocket *p, int change) * what should we do? close the socket with an appropriate * error message? */ - return plug_accepting(p->plug, - p->accepting_constructor, p->accepting_ctx); + return plug_accepting(ps->plug, + ps->accepting_constructor, ps->accepting_ctx); } if (change == PROXY_CHANGE_RECEIVE) { @@ -832,7 +832,7 @@ int proxy_socks4_negotiate (ProxySocket *p, int change) * we'll need to parse, process, and respond to appropriately. */ - if (p->state == 1) { + if (ps->state == 1) { /* response format: * version number (1 byte) = 4 * reply code (1 byte) @@ -847,14 +847,14 @@ int proxy_socks4_negotiate (ProxySocket *p, int change) char data[8]; - if (bufchain_size(&p->pending_input_data) < 8) + if (bufchain_size(&ps->pending_input_data) < 8) return 1; /* not got anything yet */ /* get the response */ - bufchain_fetch(&p->pending_input_data, data, 8); + bufchain_fetch(&ps->pending_input_data, data, 8); if (data[0] != 0) { - plug_closing_error(p->plug, "Proxy error: SOCKS proxy " + plug_closing_error(ps->plug, "Proxy error: SOCKS proxy " "responded with unexpected " "reply code version"); return 1; @@ -864,40 +864,40 @@ int proxy_socks4_negotiate (ProxySocket *p, int change) switch (data[1]) { case 92: - plug_closing_error(p->plug, "Proxy error: SOCKS server " + plug_closing_error(ps->plug, "Proxy error: SOCKS server " "wanted IDENTD on client"); break; case 93: - plug_closing_error(p->plug, "Proxy error: Username and " + plug_closing_error(ps->plug, "Proxy error: Username and " "IDENTD on client don't agree"); break; case 91: default: - plug_closing_error(p->plug, "Proxy error: Error while " + plug_closing_error(ps->plug, "Proxy error: Error while " "communicating with proxy"); break; } return 1; } - bufchain_consume(&p->pending_input_data, 8); + bufchain_consume(&ps->pending_input_data, 8); /* we're done */ - proxy_activate(p); + proxy_activate(ps); /* proxy activate will have dealt with * whatever is left of the buffer */ return 1; } } - plug_closing_error(p->plug, "Proxy error: unexpected proxy error"); + plug_closing_error(ps->plug, "Proxy error: unexpected proxy error"); return 1; } /* SOCKS version 5 */ -int proxy_socks5_negotiate (ProxySocket *p, int change) +int proxy_socks5_negotiate (ProxySocket *ps, int change) { - if (p->state == PROXY_CHANGE_NEW) { + if (ps->state == PROXY_CHANGE_NEW) { /* initial command: * version number (1 byte) = 5 @@ -916,8 +916,8 @@ int proxy_socks5_negotiate (ProxySocket *p, int change) command = strbuf_new(); put_byte(command, 5); /* SOCKS version 5 */ - username = conf_get_str(p->conf, CONF_proxy_username); - password = conf_get_str(p->conf, CONF_proxy_password); + username = conf_get_str(ps->conf, CONF_proxy_username); + password = conf_get_str(ps->conf, CONF_proxy_password); method_count_offset = command->len; put_byte(command, 0); @@ -932,10 +932,10 @@ int proxy_socks5_negotiate (ProxySocket *p, int change) command->u[method_count_offset] = command->len - methods_start; - sk_write(p->sub_socket, command->s, command->len); + sk_write(ps->sub_socket, command->s, command->len); strbuf_free(command); - p->state = 1; + ps->state = 1; return 0; } @@ -946,7 +946,7 @@ int proxy_socks5_negotiate (ProxySocket *p, int change) * a socket close, then some error must have occurred. we'll * just pass those errors up to the backend. */ - plug_closing(p->plug, p->closing_type, p->closing_error_msg); + plug_closing(ps->plug, ps->closing_type, ps->closing_error_msg); return 0; /* ignored */ } @@ -965,8 +965,8 @@ int proxy_socks5_negotiate (ProxySocket *p, int change) * what should we do? close the socket with an appropriate * error message? */ - return plug_accepting(p->plug, - p->accepting_constructor, p->accepting_ctx); + return plug_accepting(ps->plug, + ps->accepting_constructor, ps->accepting_ctx); } if (change == PROXY_CHANGE_RECEIVE) { @@ -974,7 +974,7 @@ int proxy_socks5_negotiate (ProxySocket *p, int change) * we'll need to parse, process, and respond to appropriately. */ - if (p->state == 1) { + if (ps->state == 1) { /* initial response: * version number (1 byte) = 5 @@ -988,31 +988,31 @@ int proxy_socks5_negotiate (ProxySocket *p, int change) */ char data[2]; - if (bufchain_size(&p->pending_input_data) < 2) + if (bufchain_size(&ps->pending_input_data) < 2) return 1; /* not got anything yet */ /* get the response */ - bufchain_fetch(&p->pending_input_data, data, 2); + bufchain_fetch(&ps->pending_input_data, data, 2); if (data[0] != 5) { - plug_closing_error(p->plug, "Proxy error: SOCKS proxy " + plug_closing_error(ps->plug, "Proxy error: SOCKS proxy " "returned unexpected version"); 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 */ + if (data[1] == 0x00) ps->state = 2; /* no authentication needed */ + else if (data[1] == 0x01) ps->state = 4; /* GSSAPI authentication */ + else if (data[1] == 0x02) ps->state = 5; /* username/password authentication */ + else if (data[1] == 0x03) ps->state = 6; /* CHAP authentication */ else { - plug_closing_error(p->plug, "Proxy error: SOCKS proxy did not " + plug_closing_error(ps->plug, "Proxy error: SOCKS proxy did not " "accept our authentication"); return 1; } - bufchain_consume(&p->pending_input_data, 2); + bufchain_consume(&ps->pending_input_data, 2); } - if (p->state == 7) { + if (ps->state == 7) { /* password authentication reply format: * version number (1 bytes) = 1 @@ -1022,36 +1022,36 @@ int proxy_socks5_negotiate (ProxySocket *p, int change) */ char data[2]; - if (bufchain_size(&p->pending_input_data) < 2) + if (bufchain_size(&ps->pending_input_data) < 2) return 1; /* not got anything yet */ /* get the response */ - bufchain_fetch(&p->pending_input_data, data, 2); + bufchain_fetch(&ps->pending_input_data, data, 2); if (data[0] != 1) { - plug_closing_error(p->plug, "Proxy error: SOCKS password " + plug_closing_error(ps->plug, "Proxy error: SOCKS password " "subnegotiation contained wrong version " "number"); return 1; } if (data[1] != 0) { - plug_closing_error(p->plug, "Proxy error: SOCKS proxy refused " + plug_closing_error(ps->plug, "Proxy error: SOCKS proxy refused " "password authentication"); return 1; } - bufchain_consume(&p->pending_input_data, 2); - p->state = 2; /* now proceed as authenticated */ + bufchain_consume(&ps->pending_input_data, 2); + ps->state = 2; /* now proceed as authenticated */ } - if (p->state == 8) { + if (ps->state == 8) { int ret; - ret = proxy_socks5_handlechap(p); + ret = proxy_socks5_handlechap(ps); if (ret) return ret; } - if (p->state == 2) { + if (ps->state == 2) { /* request format: * version number (1 byte) = 5 @@ -1073,21 +1073,21 @@ int proxy_socks5_negotiate (ProxySocket *p, int change) put_byte(command, 1); /* CONNECT command */ put_byte(command, 0x00); /* reserved byte */ - switch (sk_addrtype(p->remote_addr)) { + switch (sk_addrtype(ps->remote_addr)) { case ADDRTYPE_IPV4: put_byte(command, 1); /* IPv4 */ - sk_addrcopy(p->remote_addr, strbuf_append(command, 4)); + sk_addrcopy(ps->remote_addr, strbuf_append(command, 4)); break; case ADDRTYPE_IPV6: put_byte(command, 4); /* IPv6 */ - sk_addrcopy(p->remote_addr, strbuf_append(command, 16)); + sk_addrcopy(ps->remote_addr, strbuf_append(command, 16)); break; case ADDRTYPE_NAME: { char hostname[512]; put_byte(command, 3); /* domain name */ - sk_getaddr(p->remote_addr, hostname, lenof(hostname)); + sk_getaddr(ps->remote_addr, hostname, lenof(hostname)); if (!put_pstring(command, hostname)) { - p->error = "Proxy error: SOCKS 5 cannot " + ps->error = "Proxy error: SOCKS 5 cannot " "support host names longer than 255 chars"; strbuf_free(command); return 1; @@ -1096,17 +1096,17 @@ int proxy_socks5_negotiate (ProxySocket *p, int change) } } - put_uint16(command, p->remote_port); + put_uint16(command, ps->remote_port); - sk_write(p->sub_socket, command->s, command->len); + sk_write(ps->sub_socket, command->s, command->len); strbuf_free(command); - p->state = 3; + ps->state = 3; return 1; } - if (p->state == 3) { + if (ps->state == 3) { /* reply format: * version number (1 bytes) = 5 @@ -1132,14 +1132,14 @@ int proxy_socks5_negotiate (ProxySocket *p, int change) int len; /* First 5 bytes of packet are enough to tell its length. */ - if (bufchain_size(&p->pending_input_data) < 5) + if (bufchain_size(&ps->pending_input_data) < 5) return 1; /* not got anything yet */ /* get the response */ - bufchain_fetch(&p->pending_input_data, data, 5); + bufchain_fetch(&ps->pending_input_data, data, 5); if (data[0] != 5) { - plug_closing_error(p->plug, "Proxy error: SOCKS proxy " + plug_closing_error(ps->plug, "Proxy error: SOCKS proxy " "returned wrong version number"); return 1; } @@ -1163,7 +1163,7 @@ int proxy_socks5_negotiate (ProxySocket *p, int change) data[1]); break; } - plug_closing_error(p->plug, buf); + plug_closing_error(ps->plug, buf); return 1; } @@ -1177,63 +1177,63 @@ int proxy_socks5_negotiate (ProxySocket *p, int change) case 4: len += 16; break;/* IPv6 address */ case 3: len += 1+(unsigned char)data[4]; break; /* domain name */ default: - plug_closing_error(p->plug, "Proxy error: SOCKS proxy " + plug_closing_error(ps->plug, "Proxy error: SOCKS proxy " "returned unrecognised address format"); return 1; } - if (bufchain_size(&p->pending_input_data) < len) + if (bufchain_size(&ps->pending_input_data) < len) return 1; /* not got whole reply yet */ - bufchain_consume(&p->pending_input_data, len); + bufchain_consume(&ps->pending_input_data, len); /* we're done */ - proxy_activate(p); + proxy_activate(ps); return 1; } - if (p->state == 4) { + if (ps->state == 4) { /* TODO: Handle GSSAPI authentication */ - plug_closing_error(p->plug, "Proxy error: We don't support " + plug_closing_error(ps->plug, "Proxy error: We don't support " "GSSAPI authentication"); return 1; } - if (p->state == 5) { - const char *username = conf_get_str(p->conf, CONF_proxy_username); - const char *password = conf_get_str(p->conf, CONF_proxy_password); + if (ps->state == 5) { + const char *username = conf_get_str(ps->conf, CONF_proxy_username); + const char *password = conf_get_str(ps->conf, CONF_proxy_password); if (username[0] || password[0]) { strbuf *auth = strbuf_new_nm(); put_byte(auth, 1); /* version number of subnegotiation */ if (!put_pstring(auth, username)) { - p->error = "Proxy error: SOCKS 5 authentication cannot " + ps->error = "Proxy error: SOCKS 5 authentication cannot " "support usernames longer than 255 chars"; strbuf_free(auth); return 1; } if (!put_pstring(auth, password)) { - p->error = "Proxy error: SOCKS 5 authentication cannot " + ps->error = "Proxy error: SOCKS 5 authentication cannot " "support passwords longer than 255 chars"; strbuf_free(auth); return 1; } - sk_write(p->sub_socket, auth->s, auth->len); + sk_write(ps->sub_socket, auth->s, auth->len); strbuf_free(auth); - p->state = 7; + ps->state = 7; } else - plug_closing_error(p->plug, "Proxy error: Server chose " + plug_closing_error(ps->plug, "Proxy error: Server chose " "username/password authentication " "but we didn't offer it!"); return 1; } - if (p->state == 6) { + if (ps->state == 6) { int ret; - ret = proxy_socks5_selectchap(p); + ret = proxy_socks5_selectchap(ps); if (ret) return ret; } } - plug_closing_error(p->plug, "Proxy error: Unexpected proxy error"); + plug_closing_error(ps->plug, "Proxy error: Unexpected proxy error"); return 1; } @@ -1413,13 +1413,13 @@ char *format_telnet_command(SockAddr *addr, int port, Conf *conf) return strbuf_to_str(buf); } -int proxy_telnet_negotiate (ProxySocket *p, int change) +int proxy_telnet_negotiate (ProxySocket *ps, int change) { - if (p->state == PROXY_CHANGE_NEW) { + if (ps->state == PROXY_CHANGE_NEW) { char *formatted_cmd; - formatted_cmd = format_telnet_command(p->remote_addr, p->remote_port, - p->conf); + formatted_cmd = format_telnet_command(ps->remote_addr, ps->remote_port, + ps->conf); { /* @@ -1449,15 +1449,15 @@ int proxy_telnet_negotiate (ProxySocket *p, int change) *out = '\0'; logmsg = dupprintf("Sending Telnet proxy command: %s", reescaped); - plug_log(p->plug, PLUGLOG_PROXY_MSG, NULL, 0, logmsg, 0); + plug_log(ps->plug, PLUGLOG_PROXY_MSG, NULL, 0, logmsg, 0); sfree(logmsg); sfree(reescaped); } - sk_write(p->sub_socket, formatted_cmd, strlen(formatted_cmd)); + sk_write(ps->sub_socket, formatted_cmd, strlen(formatted_cmd)); sfree(formatted_cmd); - p->state = 1; + ps->state = 1; return 0; } @@ -1468,7 +1468,7 @@ int proxy_telnet_negotiate (ProxySocket *p, int change) * a socket close, then some error must have occurred. we'll * just pass those errors up to the backend. */ - plug_closing(p->plug, p->closing_type, p->closing_error_msg); + plug_closing(ps->plug, ps->closing_type, ps->closing_error_msg); return 0; /* ignored */ } @@ -1487,8 +1487,8 @@ int proxy_telnet_negotiate (ProxySocket *p, int change) * what should we do? close the socket with an appropriate * error message? */ - return plug_accepting(p->plug, - p->accepting_constructor, p->accepting_ctx); + return plug_accepting(ps->plug, + ps->accepting_constructor, ps->accepting_ctx); } if (change == PROXY_CHANGE_RECEIVE) { @@ -1497,12 +1497,12 @@ int proxy_telnet_negotiate (ProxySocket *p, int change) */ /* we're done */ - proxy_activate(p); + proxy_activate(ps); /* proxy activate will have dealt with * whatever is left of the buffer */ return 1; } - plug_closing_error(p->plug, "Proxy error: Unexpected proxy error"); + plug_closing_error(ps->plug, "Proxy error: Unexpected proxy error"); return 1; } -- cgit v1.2.3 From 1bf93289c933e5f59f35d0c65a296750b4596ae5 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 19 Nov 2021 10:33:57 +0000 Subject: Pull out SOCKS protocol constants into a header. This seemed like a worthwhile cleanup to do while I was working on this code anyway. Now all the magic numbers are defined in a header file by macro names indicating their meaning, and used by both the SOCKS client code in the proxy subdirectory and the SOCKS server code in portfwd.c. --- proxy/cproxy.c | 25 ++++++++++---------- proxy/proxy.c | 75 +++++++++++++++++++++++++++++----------------------------- proxy/socks.h | 72 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ ssh/portfwd.c | 68 ++++++++++++++++++++++++++++------------------------ 4 files changed, 160 insertions(+), 80 deletions(-) create mode 100644 proxy/socks.h diff --git a/proxy/cproxy.c b/proxy/cproxy.c index 1ae4cf9a..14c67357 100644 --- a/proxy/cproxy.c +++ b/proxy/cproxy.c @@ -13,6 +13,7 @@ #include "ssh.h" /* For MD5 support */ #include "network.h" #include "proxy.h" +#include "socks.h" #include "marshal.h" static void hmacmd5_chap(const unsigned char *challenge, int challen, @@ -24,7 +25,7 @@ static void hmacmd5_chap(const unsigned char *challenge, int challen, void proxy_socks5_offerencryptedauth(BinarySink *bs) { - put_byte(bs, 0x03); /* CHAP */ + put_byte(bs, SOCKS5_AUTH_CHAP); } int proxy_socks5_handlechap (ProxySocket *ps) @@ -64,7 +65,7 @@ int proxy_socks5_handlechap (ProxySocket *ps) * with the server, where we negotiate version and * number of attributes */ - if (data[0] != 0x01) { + if (data[0] != SOCKS5_AUTH_CHAP_VERSION) { plug_closing_error(ps->plug, "Proxy error: SOCKS proxy wants " "a different CHAP version"); return 1; @@ -96,7 +97,7 @@ int proxy_socks5_handlechap (ProxySocket *ps) ps->chap_current_datalen); switch (ps->chap_current_attribute) { - case 0x00: + case SOCKS5_AUTH_CHAP_ATTR_STATUS: /* Successful authentication */ if (data[0] == 0x00) ps->state = 2; @@ -106,19 +107,19 @@ int proxy_socks5_handlechap (ProxySocket *ps) return 1; } break; - case 0x03: - outbuf[0] = 0x01; /* Version */ + case SOCKS5_AUTH_CHAP_ATTR_CHALLENGE: + outbuf[0] = SOCKS5_AUTH_CHAP_VERSION; outbuf[1] = 0x01; /* One attribute */ - outbuf[2] = 0x04; /* Response */ + outbuf[2] = SOCKS5_AUTH_CHAP_ATTR_RESPONSE; outbuf[3] = 0x10; /* Length */ hmacmd5_chap(data, ps->chap_current_datalen, conf_get_str(ps->conf, CONF_proxy_password), &outbuf[4]); sk_write(ps->sub_socket, outbuf, 20); break; - case 0x11: + case SOCKS5_AUTH_CHAP_ATTR_ALGLIST: /* Chose a protocol */ - if (data[0] != 0x85) { + if (data[0] != SOCKS5_AUTH_CHAP_ALG_HMACMD5) { plug_closing_error(ps->plug, "Proxy error: Server chose " "CHAP of other than HMAC-MD5 but we " "didn't offer it!"); @@ -146,12 +147,12 @@ int proxy_socks5_selectchap(ProxySocket *ps) if (username[0] || password[0]) { char chapbuf[514]; int ulen; - chapbuf[0] = '\x01'; /* Version */ + chapbuf[0] = SOCKS5_AUTH_CHAP_VERSION; chapbuf[1] = '\x02'; /* Number of attributes sent */ - chapbuf[2] = '\x11'; /* First attribute - algorithms list */ + chapbuf[2] = SOCKS5_AUTH_CHAP_ATTR_ALGLIST; chapbuf[3] = '\x01'; /* Only one CHAP algorithm */ - chapbuf[4] = '\x85'; /* ...and it's HMAC-MD5, the core one */ - chapbuf[5] = '\x02'; /* Second attribute - username */ + chapbuf[4] = SOCKS5_AUTH_CHAP_ALG_HMACMD5; + chapbuf[5] = SOCKS5_AUTH_CHAP_ATTR_USERNAME; ulen = strlen(username); if (ulen > 255) ulen = 255; diff --git a/proxy/proxy.c b/proxy/proxy.c index 28e64253..dbf5c035 100644 --- a/proxy/proxy.c +++ b/proxy/proxy.c @@ -12,6 +12,7 @@ #include "putty.h" #include "network.h" #include "proxy.h" +#include "socks.h" #define do_proxy_dns(conf) \ (conf_get_int(conf, CONF_proxy_dns) == FORCE_ON || \ @@ -765,8 +766,8 @@ int proxy_socks4_negotiate (ProxySocket *ps, int change) char hostname[512]; bool write_hostname = false; - put_byte(command, 4); /* SOCKS version 4 */ - put_byte(command, 1); /* CONNECT command */ + put_byte(command, SOCKS4_REQUEST_VERSION); + put_byte(command, SOCKS_CMD_CONNECT); put_uint16(command, ps->remote_port); switch (sk_addrtype(ps->remote_addr)) { @@ -778,7 +779,7 @@ int proxy_socks4_negotiate (ProxySocket *ps, int change) } case ADDRTYPE_NAME: sk_getaddr(ps->remote_addr, hostname, lenof(hostname)); - put_uint32(command, 1); + put_uint32(command, SOCKS4A_NAME_FOLLOWS_BASE); write_hostname = true; break; case ADDRTYPE_IPV6: @@ -834,7 +835,7 @@ int proxy_socks4_negotiate (ProxySocket *ps, int change) if (ps->state == 1) { /* response format: - * version number (1 byte) = 4 + * version number (1 byte) = 0 * reply code (1 byte) * 90 = request granted * 91 = request rejected or failed @@ -853,25 +854,25 @@ int proxy_socks4_negotiate (ProxySocket *ps, int change) /* get the response */ bufchain_fetch(&ps->pending_input_data, data, 8); - if (data[0] != 0) { + if (data[0] != SOCKS4_REPLY_VERSION) { plug_closing_error(ps->plug, "Proxy error: SOCKS proxy " "responded with unexpected " "reply code version"); return 1; } - if (data[1] != 90) { + if (data[1] != SOCKS4_RESP_SUCCESS) { switch (data[1]) { - case 92: + case SOCKS4_RESP_WANT_IDENTD: plug_closing_error(ps->plug, "Proxy error: SOCKS server " "wanted IDENTD on client"); break; - case 93: + case SOCKS4_RESP_IDENTD_MISMATCH: plug_closing_error(ps->plug, "Proxy error: Username and " "IDENTD on client don't agree"); break; - case 91: + case SOCKS4_RESP_FAILURE: default: plug_closing_error(ps->plug, "Proxy error: Error while " "communicating with proxy"); @@ -915,7 +916,7 @@ int proxy_socks5_negotiate (ProxySocket *ps, int change) int method_count_offset, methods_start; command = strbuf_new(); - put_byte(command, 5); /* SOCKS version 5 */ + put_byte(command, SOCKS5_REQUEST_VERSION); username = conf_get_str(ps->conf, CONF_proxy_username); password = conf_get_str(ps->conf, CONF_proxy_password); @@ -923,11 +924,11 @@ int proxy_socks5_negotiate (ProxySocket *ps, int change) put_byte(command, 0); methods_start = command->len; - put_byte(command, 0x00); /* no authentication */ + put_byte(command, SOCKS5_AUTH_NONE); if (username[0] || password[0]) { proxy_socks5_offerencryptedauth(BinarySink_UPCAST(command)); - put_byte(command, 0x02); /* username/password */ + put_byte(command, SOCKS5_AUTH_PASSWORD); } command->u[method_count_offset] = command->len - methods_start; @@ -994,16 +995,16 @@ int proxy_socks5_negotiate (ProxySocket *ps, int change) /* get the response */ bufchain_fetch(&ps->pending_input_data, data, 2); - if (data[0] != 5) { + if (data[0] != SOCKS5_REPLY_VERSION) { plug_closing_error(ps->plug, "Proxy error: SOCKS proxy " "returned unexpected version"); return 1; } - if (data[1] == 0x00) ps->state = 2; /* no authentication needed */ - else if (data[1] == 0x01) ps->state = 4; /* GSSAPI authentication */ - else if (data[1] == 0x02) ps->state = 5; /* username/password authentication */ - else if (data[1] == 0x03) ps->state = 6; /* CHAP authentication */ + if (data[1] == SOCKS5_AUTH_NONE) ps->state = 2; + else if (data[1] == SOCKS5_AUTH_GSSAPI) ps->state = 4; + else if (data[1] == SOCKS5_AUTH_PASSWORD) ps->state = 5; + else if (data[1] == SOCKS5_AUTH_CHAP) ps->state = 6; else { plug_closing_error(ps->plug, "Proxy error: SOCKS proxy did not " "accept our authentication"); @@ -1028,7 +1029,7 @@ int proxy_socks5_negotiate (ProxySocket *ps, int change) /* get the response */ bufchain_fetch(&ps->pending_input_data, data, 2); - if (data[0] != 1) { + if (data[0] != SOCKS5_AUTH_PASSWORD_VERSION) { plug_closing_error(ps->plug, "Proxy error: SOCKS password " "subnegotiation contained wrong version " "number"); @@ -1069,22 +1070,22 @@ int proxy_socks5_negotiate (ProxySocket *ps, int change) */ strbuf *command = strbuf_new(); - put_byte(command, 5); /* SOCKS version 5 */ - put_byte(command, 1); /* CONNECT command */ + put_byte(command, SOCKS5_REQUEST_VERSION); + put_byte(command, SOCKS_CMD_CONNECT); put_byte(command, 0x00); /* reserved byte */ switch (sk_addrtype(ps->remote_addr)) { case ADDRTYPE_IPV4: - put_byte(command, 1); /* IPv4 */ + put_byte(command, SOCKS5_ADDR_IPV4); sk_addrcopy(ps->remote_addr, strbuf_append(command, 4)); break; case ADDRTYPE_IPV6: - put_byte(command, 4); /* IPv6 */ + put_byte(command, SOCKS5_ADDR_IPV6); sk_addrcopy(ps->remote_addr, strbuf_append(command, 16)); break; case ADDRTYPE_NAME: { char hostname[512]; - put_byte(command, 3); /* domain name */ + put_byte(command, SOCKS5_ADDR_HOSTNAME); sk_getaddr(ps->remote_addr, hostname, lenof(hostname)); if (!put_pstring(command, hostname)) { ps->error = "Proxy error: SOCKS 5 cannot " @@ -1138,26 +1139,26 @@ int proxy_socks5_negotiate (ProxySocket *ps, int change) /* get the response */ bufchain_fetch(&ps->pending_input_data, data, 5); - if (data[0] != 5) { + if (data[0] != SOCKS5_REPLY_VERSION) { plug_closing_error(ps->plug, "Proxy error: SOCKS proxy " "returned wrong version number"); return 1; } - if (data[1] != 0) { + if (data[1] != SOCKS5_RESP_SUCCESS) { 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; + case SOCKS5_RESP_FAILURE: strcat(buf, "General SOCKS server failure"); break; + case SOCKS5_RESP_CONNECTION_NOT_ALLOWED_BY_RULESET: strcat(buf, "Connection not allowed by ruleset"); break; + case SOCKS5_RESP_NETWORK_UNREACHABLE: strcat(buf, "Network unreachable"); break; + case SOCKS5_RESP_HOST_UNREACHABLE: strcat(buf, "Host unreachable"); break; + case SOCKS5_RESP_CONNECTION_REFUSED: strcat(buf, "Connection refused"); break; + case SOCKS5_RESP_TTL_EXPIRED: strcat(buf, "TTL expired"); break; + case SOCKS5_RESP_COMMAND_NOT_SUPPORTED: strcat(buf, "Command not supported"); break; + case SOCKS5_RESP_ADDRTYPE_NOT_SUPPORTED: strcat(buf, "Address type not supported"); break; default: sprintf(buf+strlen(buf), "Unrecognised SOCKS error code %d", data[1]); @@ -1173,9 +1174,9 @@ int proxy_socks5_negotiate (ProxySocket *ps, int change) */ 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 += 1+(unsigned char)data[4]; break; /* domain name */ + case SOCKS5_ADDR_IPV4: len += 4; break; /* IPv4 address */ + case SOCKS5_ADDR_IPV6: len += 16; break;/* IPv6 address */ + case SOCKS5_ADDR_HOSTNAME: len += 1+(unsigned char)data[4]; break; /* domain name */ default: plug_closing_error(ps->plug, "Proxy error: SOCKS proxy " "returned unrecognised address format"); @@ -1202,7 +1203,7 @@ int proxy_socks5_negotiate (ProxySocket *ps, int change) const char *password = conf_get_str(ps->conf, CONF_proxy_password); if (username[0] || password[0]) { strbuf *auth = strbuf_new_nm(); - put_byte(auth, 1); /* version number of subnegotiation */ + put_byte(auth, SOCKS5_AUTH_PASSWORD_VERSION); if (!put_pstring(auth, username)) { ps->error = "Proxy error: SOCKS 5 authentication cannot " "support usernames longer than 255 chars"; diff --git a/proxy/socks.h b/proxy/socks.h new file mode 100644 index 00000000..3e86ae23 --- /dev/null +++ b/proxy/socks.h @@ -0,0 +1,72 @@ +/* + * Constants used in the SOCKS protocols. + */ + +/* Command codes common to both versions */ +#define SOCKS_CMD_CONNECT 1 +#define SOCKS_CMD_BIND 2 + +/* SOCKS 4 definitions */ + +#define SOCKS4_REQUEST_VERSION 4 +#define SOCKS4_REPLY_VERSION 0 + +#define SOCKS4_RESP_SUCCESS 90 +#define SOCKS4_RESP_FAILURE 91 +#define SOCKS4_RESP_WANT_IDENTD 92 +#define SOCKS4_RESP_IDENTD_MISMATCH 93 + +/* + * Special nonsense IP address range, used as a signal to indicate + * that an ASCIZ hostname follows the user id field. + * + * Strictly speaking, the use of this extension indicates that we're + * speaking SOCKS 4A rather than vanilla SOCKS 4, although we don't + * bother to draw the distinction. + */ +#define SOCKS4A_NAME_FOLLOWS_BASE 0x00000001 /* inclusive */ +#define SOCKS4A_NAME_FOLLOWS_LIMIT 0x00000100 /* exclusive */ + +/* SOCKS 5 definitions */ + +#define SOCKS5_REQUEST_VERSION 5 +#define SOCKS5_REPLY_VERSION 5 + +/* Extra command codes extending the SOCKS_CMD_* list above */ +#define SOCKS5_CMD_UDP_ASSOCIATE 3 + +#define SOCKS5_AUTH_NONE 0 +#define SOCKS5_AUTH_GSSAPI 1 +#define SOCKS5_AUTH_PASSWORD 2 +#define SOCKS5_AUTH_CHAP 3 +#define SOCKS5_AUTH_REJECTED 0xFF /* used in reply to indicate 'no + * acceptable method offered' */ + +#define SOCKS5_AUTH_PASSWORD_VERSION 1 + +#define SOCKS5_AUTH_CHAP_VERSION 1 + +#define SOCKS5_AUTH_CHAP_ATTR_STATUS 0x00 +#define SOCKS5_AUTH_CHAP_ATTR_INFO 0x01 +#define SOCKS5_AUTH_CHAP_ATTR_USERNAME 0x02 +#define SOCKS5_AUTH_CHAP_ATTR_CHALLENGE 0x03 +#define SOCKS5_AUTH_CHAP_ATTR_RESPONSE 0x04 +#define SOCKS5_AUTH_CHAP_ATTR_CHARSET 0x05 +#define SOCKS5_AUTH_CHAP_ATTR_IDENTIFIER 0x10 +#define SOCKS5_AUTH_CHAP_ATTR_ALGLIST 0x11 + +#define SOCKS5_AUTH_CHAP_ALG_HMACMD5 0x85 + +#define SOCKS5_ADDR_IPV4 1 +#define SOCKS5_ADDR_IPV6 4 +#define SOCKS5_ADDR_HOSTNAME 3 + +#define SOCKS5_RESP_SUCCESS 0 +#define SOCKS5_RESP_FAILURE 1 +#define SOCKS5_RESP_CONNECTION_NOT_ALLOWED_BY_RULESET 2 +#define SOCKS5_RESP_NETWORK_UNREACHABLE 3 +#define SOCKS5_RESP_HOST_UNREACHABLE 4 +#define SOCKS5_RESP_CONNECTION_REFUSED 5 +#define SOCKS5_RESP_TTL_EXPIRED 6 +#define SOCKS5_RESP_COMMAND_NOT_SUPPORTED 7 +#define SOCKS5_RESP_ADDRTYPE_NOT_SUPPORTED 8 diff --git a/ssh/portfwd.c b/ssh/portfwd.c index 2afa9507..11544564 100644 --- a/ssh/portfwd.c +++ b/ssh/portfwd.c @@ -9,6 +9,7 @@ #include "putty.h" #include "ssh.h" #include "channel.h" +#include "proxy/socks.h" /* * Enumeration of values that live in the 'socks_state' field of @@ -216,10 +217,10 @@ static void pfd_receive(Plug *plug, int urgent, const char *data, size_t len) * _must_ have by now) to find out which SOCKS major * version we're speaking. */ switch (pf->socksbuf->u[0]) { - case 4: + case SOCKS4_REQUEST_VERSION: pf->socks_state = SOCKS_4; break; - case 5: + case SOCKS5_REQUEST_VERSION: pf->socks_state = SOCKS_5_INITIAL; break; default: @@ -250,13 +251,15 @@ static void pfd_receive(Plug *plug, int urgent, const char *data, size_t len) if (get_err(src) == BSE_OUT_OF_DATA) return; - if (socks_version == 4 && message_type == 1) { + if (socks_version == SOCKS4_REQUEST_VERSION && + message_type == SOCKS_CMD_CONNECT) { /* CONNECT message */ bool name_based = false; port = get_uint16(src); ipv4 = get_uint32(src); - if (ipv4 > 0x00000000 && ipv4 < 0x00000100) { + if (ipv4 >= SOCKS4A_NAME_FOLLOWS_BASE && + ipv4 < SOCKS4A_NAME_FOLLOWS_LIMIT) { /* * Addresses in this range indicate the SOCKS 4A * extension to specify a hostname, which comes @@ -280,8 +283,8 @@ static void pfd_receive(Plug *plug, int urgent, const char *data, size_t len) } output = strbuf_new(); - put_byte(output, 0); /* reply version */ - put_byte(output, 90); /* SOCKS 4 'request granted' */ + put_byte(output, SOCKS4_REPLY_VERSION); + put_byte(output, SOCKS4_RESP_SUCCESS); put_uint16(output, 0); /* null port field */ put_uint32(output, 0); /* null address field */ sk_write(pf->s, output->u, output->len); @@ -294,8 +297,8 @@ static void pfd_receive(Plug *plug, int urgent, const char *data, size_t len) socks4_reject: output = strbuf_new(); - put_byte(output, 0); /* reply version */ - put_byte(output, 91); /* SOCKS 4 'request rejected' */ + put_byte(output, SOCKS4_REPLY_VERSION); + put_byte(output, SOCKS4_RESP_FAILURE); put_uint16(output, 0); /* null port field */ put_uint32(output, 0); /* null address field */ sk_write(pf->s, output->u, output->len); @@ -308,29 +311,31 @@ static void pfd_receive(Plug *plug, int urgent, const char *data, size_t len) socks_version = get_byte(src); methods = get_pstring(src); - method = 0xFF; /* means 'no usable method found' */ - { - int i; - for (i = 0; i < methods.len; i++) { - if (((const unsigned char *)methods.ptr)[i] == 0 ) { - method = 0; /* no auth */ - break; - } + method = SOCKS5_AUTH_REJECTED; + + /* Search the method list for AUTH_NONE, which is the + * only one this client code can speak */ + for (size_t i = 0; i < methods.len; i++) { + unsigned char this_method = + ((const unsigned char *)methods.ptr)[i]; + if (this_method == SOCKS5_AUTH_NONE) { + method = this_method; + break; } } if (get_err(src) == BSE_OUT_OF_DATA) return; if (get_err(src)) - method = 0xFF; + method = SOCKS5_AUTH_REJECTED; output = strbuf_new(); - put_byte(output, 5); /* SOCKS version */ - put_byte(output, method); /* selected auth method */ + put_byte(output, SOCKS5_REPLY_VERSION); + put_byte(output, method); sk_write(pf->s, output->u, output->len); strbuf_free(output); - if (method == 0xFF) { + if (method == SOCKS5_AUTH_REJECTED) { pfd_close(pf); return; } @@ -345,48 +350,49 @@ static void pfd_receive(Plug *plug, int urgent, const char *data, size_t len) message_type = get_byte(src); reserved_byte = get_byte(src); - if (socks_version == 5 && message_type == 1 && + if (socks_version == SOCKS5_REQUEST_VERSION && + message_type == SOCKS_CMD_CONNECT && reserved_byte == 0) { - reply_code = 0; /* success */ + reply_code = SOCKS5_RESP_SUCCESS; switch (get_byte(src)) { - case 1: /* IPv4 */ + case SOCKS5_ADDR_IPV4: pf->hostname = ipv4_to_string(get_uint32(src)); break; - case 4: /* IPv6 */ + case SOCKS5_ADDR_IPV6: pf->hostname = ipv6_to_string(get_data(src, 16)); break; - case 3: /* unresolved domain name */ + case SOCKS5_ADDR_HOSTNAME: pf->hostname = mkstr(get_pstring(src)); break; default: pf->hostname = NULL; - reply_code = 8; /* address type not supported */ + reply_code = SOCKS5_RESP_ADDRTYPE_NOT_SUPPORTED; break; } pf->port = get_uint16(src); } else { - reply_code = 7; /* command not supported */ + reply_code = SOCKS5_RESP_COMMAND_NOT_SUPPORTED; } if (get_err(src) == BSE_OUT_OF_DATA) return; if (get_err(src)) - reply_code = 1; /* general server failure */ + reply_code = SOCKS5_RESP_FAILURE; output = strbuf_new(); - put_byte(output, 5); /* SOCKS version */ + put_byte(output, SOCKS5_REPLY_VERSION); put_byte(output, reply_code); put_byte(output, 0); /* reserved */ - put_byte(output, 1); /* IPv4 address follows */ + put_byte(output, SOCKS5_ADDR_IPV4); /* IPv4 address follows */ put_uint32(output, 0); /* bound IPv4 address (unused) */ put_uint16(output, 0); /* bound port number (unused) */ sk_write(pf->s, output->u, output->len); strbuf_free(output); - if (reply_code != 0) { + if (reply_code != SOCKS5_RESP_SUCCESS) { pfd_close(pf); return; } -- cgit v1.2.3 From 23c64fa00e1189be6e251d075e7f3a8cd68795b5 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 19 Nov 2021 10:42:20 +0000 Subject: Remove PROXY_CHANGE_{SENT,CLOSING,ACCEPTING}. These were just boilerplate in all the proxy negotiation functions: every negotiator had to contain a handler for each of these events, and they all handled them in exactly the same way. Remove them and centralise the handling in the shared code. A long time ago, some of these event codes were added with purpose in mind. PROXY_CHANGE_CLOSING was there to anticipate the possibility that you might need to make multiple TCP connections to the proxy server (e.g. retrying with different authentication) before successfully getting a connection you could use to talk to the ultimate destination. And PROXY_CHANGE_ACCEPTING was there so that we could use the listening side of SOCKS (where you ask the proxy to open a listening socket on your behalf). But neither of them has ever been used, and now that the code has evolved, I think probably if we do ever need to do either of those things then they'll want to be done differently. --- proxy/proxy.c | 142 ++-------------------------------------------------------- proxy/proxy.h | 11 ----- 2 files changed, 4 insertions(+), 149 deletions(-) diff --git a/proxy/proxy.c b/proxy/proxy.c index dbf5c035..b233bba5 100644 --- a/proxy/proxy.c +++ b/proxy/proxy.c @@ -187,13 +187,7 @@ static void plug_proxy_closing(Plug *p, PlugCloseType type, { ProxySocket *ps = container_of(p, ProxySocket, plugimpl); - if (ps->state != PROXY_STATE_ACTIVE) { - ps->closing_type = type; - ps->closing_error_msg = error_msg; - ps->negotiate(ps, PROXY_CHANGE_CLOSING); - } else { - plug_closing(ps->plug, type, error_msg); - } + plug_closing(ps->plug, type, error_msg); } static void plug_proxy_receive( @@ -220,24 +214,16 @@ static void plug_proxy_sent (Plug *p, size_t bufsize) { ProxySocket *ps = container_of(p, ProxySocket, plugimpl); - if (ps->state != PROXY_STATE_ACTIVE) { - ps->negotiate(ps, PROXY_CHANGE_SENT); + if (ps->state != PROXY_STATE_ACTIVE) return; - } + plug_sent(ps->plug, bufsize); } static int plug_proxy_accepting(Plug *p, accept_fn_t constructor, accept_ctx_t ctx) { - ProxySocket *ps = container_of(p, ProxySocket, plugimpl); - - if (ps->state != PROXY_STATE_ACTIVE) { - ps->accepting_constructor = constructor; - ps->accepting_ctx = ctx; - return ps->negotiate(ps, PROXY_CHANGE_ACCEPTING); - } - return plug_accepting(ps->plug, constructor, ctx); + unreachable("ProxySockets never create listening Sockets"); } /* @@ -610,36 +596,6 @@ int proxy_http_negotiate (ProxySocket *ps, int change) 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. - */ - plug_closing(ps->plug, ps->closing_type, ps->closing_error_msg); - return 0; /* ignored */ - } - - 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(ps->plug, - ps->accepting_constructor, ps->accepting_ctx); - } - if (change == PROXY_CHANGE_RECEIVE) { /* we have received data from the underlying socket, which * we'll need to parse, process, and respond to appropriately. @@ -798,36 +754,6 @@ int proxy_socks4_negotiate (ProxySocket *ps, int change) 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. - */ - plug_closing(ps->plug, ps->closing_type, ps->closing_error_msg); - return 0; /* ignored */ - } - - 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(ps->plug, - ps->accepting_constructor, ps->accepting_ctx); - } - if (change == PROXY_CHANGE_RECEIVE) { /* we have received data from the underlying socket, which * we'll need to parse, process, and respond to appropriately. @@ -940,36 +866,6 @@ int proxy_socks5_negotiate (ProxySocket *ps, int change) 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. - */ - plug_closing(ps->plug, ps->closing_type, ps->closing_error_msg); - return 0; /* ignored */ - } - - 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(ps->plug, - ps->accepting_constructor, ps->accepting_ctx); - } - if (change == PROXY_CHANGE_RECEIVE) { /* we have received data from the underlying socket, which * we'll need to parse, process, and respond to appropriately. @@ -1462,36 +1358,6 @@ int proxy_telnet_negotiate (ProxySocket *ps, int change) 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. - */ - plug_closing(ps->plug, ps->closing_type, ps->closing_error_msg); - return 0; /* ignored */ - } - - 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(ps->plug, - ps->accepting_constructor, ps->accepting_ctx); - } - if (change == PROXY_CHANGE_RECEIVE) { /* we have received data from the underlying socket, which * we'll need to parse, process, and respond to appropriately. diff --git a/proxy/proxy.h b/proxy/proxy.h index 06692b70..62aa144b 100644 --- a/proxy/proxy.h +++ b/proxy/proxy.h @@ -39,10 +39,7 @@ struct ProxySocket { */ #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 @@ -57,19 +54,11 @@ struct ProxySocket { * (for use by proxy's negotiate function) */ - /* closing */ - PlugCloseType closing_type; - const char *closing_error_msg; - /* receive */ bool receive_urgent; const char *receive_data; int receive_len; - /* accepting */ - accept_fn_t accepting_constructor; - accept_ctx_t accepting_ctx; - /* configuration, used to look up proxy settings */ Conf *conf; -- cgit v1.2.3 From b7bf2aec745a0b7c07c79ab48b28e20f0621206f Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 19 Nov 2021 10:26:41 +0000 Subject: Reorganise proxy system into coroutines. Previously, the proxy negotiation functions were written as explicit state machines, with ps->state being manually set to a sequence of positive integer values which would be tested by if statements in the next call to the same negotiation function. That's not how this code base likes to do things! We have a coroutine system to allow those state machines to be implicit rather than explicit, so that we can use ordinary control flow statements like while loops. Reorganised each proxy negotiation function into a coroutine-based system like that. While I'm at it, I've also moved each proxy negotiator out into its own source file, to make proxy.c less overcrowded and monolithic. And _that_ gave me the opportunity to define each negotiator as an implementation of a trait rather than as a single function - which means now each one can define its own local variables and have its own cleanup function, instead of all of them having to share the variables inside the main ProxySocket struct. In the new coroutine system, negotiators don't have to worry about the mechanics of actually sending data down the underlying Socket any more. The negotiator coroutine just appends to a bufchain (via a provided bufchain_sink), and after every call to the coroutine, central code in proxy.c transfers the data to the Socket itself. This avoids a lot of intermediate allocations within the negotiators, which previously kept having to make temporary strbufs or arrays in order to have something to point an sk_write() at; now they can just put formatted data directly into the output bufchain via the marshal.h interface. In this version of the code, I've also moved most of the SOCKS5 CHAP implementation from cproxy.c into socks5.c, so that it can sit in the same coroutine as the rest of the proxy negotiation control flow. That's because calling a sub-coroutine (co-subroutine?) is awkward to set up (though it is _possible_ - we do SSH-2 kex that way), and there's no real need to bother in this case, since the only thing that really needs to go in cproxy.c is the actual cryptography plus a flag to tell socks5.c whether to offer CHAP authentication in the first place. --- CMakeLists.txt | 7 +- proxy/cproxy.c | 161 +--------- proxy/http.c | 158 +++++++++ proxy/nocproxy.c | 18 +- proxy/proxy.c | 956 ++++--------------------------------------------------- proxy/proxy.h | 82 ++--- proxy/socks4.c | 136 ++++++++ proxy/socks5.c | 434 +++++++++++++++++++++++++ proxy/telnet.c | 246 ++++++++++++++ 9 files changed, 1091 insertions(+), 1107 deletions(-) create mode 100644 proxy/http.c create mode 100644 proxy/socks4.c create mode 100644 proxy/socks5.c create mode 100644 proxy/telnet.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 5ccd8570..4b60c9d2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,7 +34,12 @@ add_subdirectory(crypto) add_library(network STATIC be_misc.c nullplug.c errsock.c logging.c x11disp.c - proxy/proxy.c proxy/interactor.c) + proxy/proxy.c + proxy/http.c + proxy/socks4.c + proxy/socks5.c + proxy/telnet.c + proxy/interactor.c) add_library(keygen STATIC import.c) diff --git a/proxy/cproxy.c b/proxy/cproxy.c index 14c67357..fab5f63c 100644 --- a/proxy/cproxy.c +++ b/proxy/cproxy.c @@ -13,163 +13,14 @@ #include "ssh.h" /* For MD5 support */ #include "network.h" #include "proxy.h" -#include "socks.h" #include "marshal.h" -static void hmacmd5_chap(const unsigned char *challenge, int challen, - const char *passwd, unsigned char *response) -{ - mac_simple(&ssh_hmac_md5, ptrlen_from_asciz(passwd), - make_ptrlen(challenge, challen), response); -} - -void proxy_socks5_offerencryptedauth(BinarySink *bs) -{ - put_byte(bs, SOCKS5_AUTH_CHAP); -} - -int proxy_socks5_handlechap (ProxySocket *ps) -{ - - /* 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(ps->chap_num_attributes == 0 || - ps->chap_num_attributes_processed < ps->chap_num_attributes) { - if (ps->chap_num_attributes == 0 || - ps->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(&ps->pending_input_data) < 2) - return 1; /* not got anything yet */ +const bool socks5_chap_available = true; - /* get the response */ - bufchain_fetch(&ps->pending_input_data, data, 2); - bufchain_consume(&ps->pending_input_data, 2); - } - - if (ps->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] != SOCKS5_AUTH_CHAP_VERSION) { - plug_closing_error(ps->plug, "Proxy error: SOCKS proxy wants " - "a different CHAP version"); - return 1; - } - if (data[1] == 0x00) { - plug_closing_error(ps->plug, "Proxy error: SOCKS proxy won't " - "negotiate CHAP with us"); - return 1; - } - ps->chap_num_attributes = data[1]; - } else { - if (ps->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. - */ - ps->chap_current_attribute = data[0]; - ps->chap_current_datalen = data[1]; - } - if (bufchain_size(&ps->pending_input_data) < - ps->chap_current_datalen) - return 1; /* not got everything yet */ - - /* get the response */ - bufchain_fetch(&ps->pending_input_data, data, - ps->chap_current_datalen); - - bufchain_consume(&ps->pending_input_data, - ps->chap_current_datalen); - - switch (ps->chap_current_attribute) { - case SOCKS5_AUTH_CHAP_ATTR_STATUS: - /* Successful authentication */ - if (data[0] == 0x00) - ps->state = 2; - else { - plug_closing_error(ps->plug, "Proxy error: SOCKS proxy " - "refused CHAP authentication"); - return 1; - } - break; - case SOCKS5_AUTH_CHAP_ATTR_CHALLENGE: - outbuf[0] = SOCKS5_AUTH_CHAP_VERSION; - outbuf[1] = 0x01; /* One attribute */ - outbuf[2] = SOCKS5_AUTH_CHAP_ATTR_RESPONSE; - outbuf[3] = 0x10; /* Length */ - hmacmd5_chap(data, ps->chap_current_datalen, - conf_get_str(ps->conf, CONF_proxy_password), - &outbuf[4]); - sk_write(ps->sub_socket, outbuf, 20); - break; - case SOCKS5_AUTH_CHAP_ATTR_ALGLIST: - /* Chose a protocol */ - if (data[0] != SOCKS5_AUTH_CHAP_ALG_HMACMD5) { - plug_closing_error(ps->plug, "Proxy error: Server chose " - "CHAP of other than HMAC-MD5 but we " - "didn't offer it!"); - return 1; - } - break; - } - ps->chap_current_attribute = -1; - ps->chap_num_attributes_processed++; - } - if (ps->state == 8 && - ps->chap_num_attributes_processed >= ps->chap_num_attributes) { - ps->chap_num_attributes = 0; - ps->chap_num_attributes_processed = 0; - ps->chap_current_datalen = 0; - } - } - return 0; -} - -int proxy_socks5_selectchap(ProxySocket *ps) +strbuf *chap_response(ptrlen challenge, ptrlen password) { - char *username = conf_get_str(ps->conf, CONF_proxy_username); - char *password = conf_get_str(ps->conf, CONF_proxy_password); - if (username[0] || password[0]) { - char chapbuf[514]; - int ulen; - chapbuf[0] = SOCKS5_AUTH_CHAP_VERSION; - chapbuf[1] = '\x02'; /* Number of attributes sent */ - chapbuf[2] = SOCKS5_AUTH_CHAP_ATTR_ALGLIST; - chapbuf[3] = '\x01'; /* Only one CHAP algorithm */ - chapbuf[4] = SOCKS5_AUTH_CHAP_ALG_HMACMD5; - chapbuf[5] = SOCKS5_AUTH_CHAP_ATTR_USERNAME; - - ulen = strlen(username); - if (ulen > 255) ulen = 255; - if (ulen < 1) ulen = 1; - - chapbuf[6] = ulen; - memcpy(chapbuf+7, username, ulen); - - sk_write(ps->sub_socket, chapbuf, ulen + 7); - ps->chap_num_attributes = 0; - ps->chap_num_attributes_processed = 0; - ps->chap_current_attribute = -1; - ps->chap_current_datalen = 0; - - ps->state = 8; - } else - plug_closing_error(ps->plug, "Proxy error: Server chose " - "CHAP authentication but we didn't offer it!"); - return 1; + strbuf *sb = strbuf_new_nm(); + const ssh2_macalg *alg = &ssh_hmac_md5; + mac_simple(alg, password, challenge, strbuf_append(sb, alg->len)); + return sb; } diff --git a/proxy/http.c b/proxy/http.c new file mode 100644 index 00000000..d67f1be9 --- /dev/null +++ b/proxy/http.c @@ -0,0 +1,158 @@ +/* + * HTTP CONNECT proxy negotiation. + */ + +#include "putty.h" +#include "network.h" +#include "proxy.h" +#include "sshcr.h" + +static bool read_line(bufchain *input, strbuf *output, bool is_header) +{ + char c; + + while (bufchain_try_fetch(input, &c, 1)) { + if (is_header && output->len > 0 && + output->s[output->len - 1] == '\n') { + /* + * A newline terminates the header, provided we're sure it + * is _not_ followed by a space or a tab. + */ + if (c != ' ' && c != '\t') + goto done; /* we have a complete header line */ + } else { + put_byte(output, c); + bufchain_consume(input, 1); + + if (!is_header && output->len > 0 && + output->s[output->len - 1] == '\n') { + /* If we're looking for just a line, not an HTTP + * header, then any newline terminates it. */ + goto done; + } + } + } + + return false; + + done: + strbuf_chomp(output, '\n'); + strbuf_chomp(output, '\r'); + return true; +} + +typedef struct HttpProxyNegotiator { + int crLine; + strbuf *line; + ProxyNegotiator pn; +} HttpProxyNegotiator; + +static ProxyNegotiator *proxy_http_new(const ProxyNegotiatorVT *vt) +{ + HttpProxyNegotiator *s = snew(HttpProxyNegotiator); + s->pn.vt = vt; + s->crLine = 0; + s->line = strbuf_new(); + return &s->pn; +} + +static void proxy_http_free(ProxyNegotiator *pn) +{ + HttpProxyNegotiator *s = container_of(pn, HttpProxyNegotiator, pn); + strbuf_free(s->line); + sfree(s); +} + +static void proxy_http_process_queue(ProxyNegotiator *pn) +{ + HttpProxyNegotiator *s = container_of(pn, HttpProxyNegotiator, pn); + + crBegin(s->crLine); + + /* + * Standard prefix for the HTTP CONNECT request. + */ + { + char dest[512]; + sk_getaddr(pn->ps->remote_addr, dest, lenof(dest)); + put_fmt(pn->output, + "CONNECT %s:%d HTTP/1.1\r\n" + "Host: %s:%d\r\n", + dest, pn->ps->remote_port, dest, pn->ps->remote_port); + } + + /* + * Optionally send an HTTP Basic auth header with the username and + * password. + */ + { + const char *username = conf_get_str(pn->ps->conf, CONF_proxy_username); + const char *password = conf_get_str(pn->ps->conf, CONF_proxy_password); + if (username[0] || password[0]) { + put_datalit(pn->output, "Proxy-Authorization: Basic "); + + char *base64_input = dupcat(username, ":", password); + char base64_output[4]; + for (size_t i = 0, e = strlen(base64_input); i < e; i += 3) { + base64_encode_atom((const unsigned char *)base64_input + i, + e-i > 3 ? 3 : e-i, base64_output); + put_data(pn->output, base64_output, 4); + } + burnstr(base64_input); + smemclr(base64_output, sizeof(base64_output)); + put_datalit(pn->output, "\r\n"); + } + } + + /* + * Blank line to terminate the HTTP request. + */ + put_datalit(pn->output, "\r\n"); + crReturnV; + + /* + * Read and parse the HTTP status line, and check if it's a 2xx + * for success. + */ + strbuf_clear(s->line); + crMaybeWaitUntilV(read_line(pn->input, s->line, false)); + { + int maj_ver, min_ver, status_pos = -1; + sscanf(s->line->s, "HTTP/%d.%d %n", &maj_ver, &min_ver, &status_pos); + + /* If status_pos is still -1 then the sscanf didn't get right + * to the end of the string */ + if (status_pos == -1) { + pn->error = dupstr("HTTP response was absent or malformed"); + crStopV; + } + + if (s->line->s[status_pos] != '2') { + pn->error = dupprintf("HTTP response %s", s->line->s + status_pos); + crStopV; + } + } + + /* + * Read and skip the rest of the HTTP response headers, terminated + * by a blank line. + */ + do { + strbuf_clear(s->line); + crMaybeWaitUntilV(read_line(pn->input, s->line, true)); + } while (s->line->len > 0); + + /* + * Success! Hand over to the main connection. + */ + pn->done = true; + + crFinishV; +} + +const struct ProxyNegotiatorVT http_proxy_negotiator_vt = { + .new = proxy_http_new, + .free = proxy_http_free, + .process_queue = proxy_http_process_queue, + .type = "HTTP", +}; diff --git a/proxy/nocproxy.c b/proxy/nocproxy.c index ed9c3ffd..85beec36 100644 --- a/proxy/nocproxy.c +++ b/proxy/nocproxy.c @@ -12,21 +12,9 @@ #include "network.h" #include "proxy.h" -void proxy_socks5_offerencryptedauth(BinarySink *bs) -{ - /* For telnet, don't add any new encrypted authentication routines */ -} - -int proxy_socks5_handlechap(ProxySocket *p) -{ - plug_closing_error(p->plug, "Proxy error: Trying to handle a " - "SOCKS5 CHAP request in telnet-only build"); - return 1; -} +const bool socks5_chap_available = false; -int proxy_socks5_selectchap(ProxySocket *p) +strbuf *chap_response(ptrlen challenge, ptrlen password) { - plug_closing_error(p->plug, "Proxy error: Trying to handle a " - "SOCKS5 CHAP request in telnet-only build"); - return 1; + unreachable("CHAP is not built into this binary"); } diff --git a/proxy/proxy.c b/proxy/proxy.c index b233bba5..249b39a4 100644 --- a/proxy/proxy.c +++ b/proxy/proxy.c @@ -12,7 +12,6 @@ #include "putty.h" #include "network.h" #include "proxy.h" -#include "socks.h" #define do_proxy_dns(conf) \ (conf_get_int(conf, CONF_proxy_dns) == FORCE_ON || \ @@ -27,7 +26,9 @@ void proxy_activate(ProxySocket *ps) { size_t output_before, output_after; - ps->state = PROXY_STATE_ACTIVE; + assert(ps->pn); + proxy_negotiator_free(ps->pn); + ps->pn = NULL; plug_log(ps->plug, PLUGLOG_CONNECT_SUCCESS, NULL, 0, NULL, 0); @@ -88,6 +89,9 @@ static void sk_proxy_close (Socket *s) sk_close(ps->sub_socket); sk_addr_free(ps->remote_addr); + if (ps->pn) + proxy_negotiator_free(ps->pn); + bufchain_clear(&ps->output_from_negotiator); sfree(ps); } @@ -95,7 +99,7 @@ static size_t sk_proxy_write (Socket *s, const void *data, size_t len) { ProxySocket *ps = container_of(s, ProxySocket, sock); - if (ps->state != PROXY_STATE_ACTIVE) { + if (ps->pn) { bufchain_add(&ps->pending_output_data, data, len); return bufchain_size(&ps->pending_output_data); } @@ -106,7 +110,7 @@ static size_t sk_proxy_write_oob (Socket *s, const void *data, size_t len) { ProxySocket *ps = container_of(s, ProxySocket, sock); - if (ps->state != PROXY_STATE_ACTIVE) { + if (ps->pn) { bufchain_clear(&ps->pending_output_data); bufchain_clear(&ps->pending_oob_output_data); bufchain_add(&ps->pending_oob_output_data, data, len); @@ -119,7 +123,7 @@ static void sk_proxy_write_eof (Socket *s) { ProxySocket *ps = container_of(s, ProxySocket, sock); - if (ps->state != PROXY_STATE_ACTIVE) { + if (ps->pn) { ps->pending_eof = true; return; } @@ -130,7 +134,7 @@ static void sk_proxy_set_frozen (Socket *s, bool is_frozen) { ProxySocket *ps = container_of(s, ProxySocket, sock); - if (ps->state != PROXY_STATE_ACTIVE) { + if (ps->pn) { ps->freeze = is_frozen; return; } @@ -190,21 +194,40 @@ static void plug_proxy_closing(Plug *p, PlugCloseType type, plug_closing(ps->plug, type, error_msg); } +static void proxy_negotiate(ProxySocket *ps) +{ + assert(ps->pn); + proxy_negotiator_process_queue(ps->pn); + if (ps->pn->done) { + proxy_activate(ps); + } else if (ps->pn->error) { + char *err = dupprintf("Proxy error: %s", ps->pn->error); + sfree(ps->pn->error); + proxy_negotiator_free(ps->pn); + ps->pn = NULL; + plug_closing_error(ps->plug, err); + sfree(err); + } else { + while (bufchain_size(&ps->output_from_negotiator)) { + ptrlen data = bufchain_prefix(&ps->output_from_negotiator); + sk_write(ps->sub_socket, data.ptr, data.len); + bufchain_consume(&ps->output_from_negotiator, data.len); + } + } +} + static void plug_proxy_receive( Plug *p, int urgent, const char *data, size_t len) { ProxySocket *ps = container_of(p, ProxySocket, plugimpl); - if (ps->state != PROXY_STATE_ACTIVE) { + if (ps->pn) { /* 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 != 0); - ps->receive_data = data; - ps->receive_len = len; - ps->negotiate(ps, PROXY_CHANGE_RECEIVE); + proxy_negotiate(ps); } else { plug_receive(ps->plug, urgent, data, len); } @@ -214,9 +237,8 @@ static void plug_proxy_sent (Plug *p, size_t bufsize) { ProxySocket *ps = container_of(p, ProxySocket, plugimpl); - if (ps->state != PROXY_STATE_ACTIVE) + if (ps->pn) return; - plug_sent(ps->plug, bufsize); } @@ -391,7 +413,6 @@ Socket *new_connection(SockAddr *addr, const char *hostname, ProxySocket *ps; SockAddr *proxy_addr; char *proxy_canonical_name; - const char *proxy_type; Socket *sret; if (type == PROXY_SSH && @@ -420,31 +441,38 @@ Socket *new_connection(SockAddr *addr, const char *hostname, bufchain_init(&ps->pending_input_data); bufchain_init(&ps->pending_output_data); bufchain_init(&ps->pending_oob_output_data); + bufchain_init(&ps->output_from_negotiator); ps->sub_socket = NULL; - ps->state = PROXY_STATE_NEW; - ps->negotiate = NULL; - - if (type == PROXY_HTTP) { - ps->negotiate = proxy_http_negotiate; - proxy_type = "HTTP"; - } else if (type == PROXY_SOCKS4) { - ps->negotiate = proxy_socks4_negotiate; - proxy_type = "SOCKS 4"; - } else if (type == PROXY_SOCKS5) { - ps->negotiate = proxy_socks5_negotiate; - proxy_type = "SOCKS 5"; - } else if (type == PROXY_TELNET) { - ps->negotiate = proxy_telnet_negotiate; - proxy_type = "Telnet"; - } else { + + const ProxyNegotiatorVT *vt; + switch (type) { + case PROXY_HTTP: + vt = &http_proxy_negotiator_vt; + break; + case PROXY_SOCKS4: + vt = &socks4_proxy_negotiator_vt; + break; + case PROXY_SOCKS5: + vt = &socks5_proxy_negotiator_vt; + break; + case PROXY_TELNET: + vt = &telnet_proxy_negotiator_vt; + break; + default: ps->error = "Proxy error: Unknown proxy method"; return &ps->sock; } + ps->pn = proxy_negotiator_new(vt); + ps->pn->ps = ps; + ps->pn->done = false; + ps->pn->error = NULL; + ps->pn->input = &ps->pending_input_data; + bufchain_sink_init(ps->pn->output, &ps->output_from_negotiator); { char *logmsg = dupprintf("Will use %s proxy at %s:%d to connect" - " to %s:%d", proxy_type, + " to %s:%d", vt->type, conf_get_str(conf, CONF_proxy_host), conf_get_int(conf, CONF_proxy_port), hostname, port); @@ -475,7 +503,7 @@ Socket *new_connection(SockAddr *addr, const char *hostname, char addrbuf[256], *logmsg; sk_getaddr(proxy_addr, addrbuf, lenof(addrbuf)); logmsg = dupprintf("Connecting to %s proxy at %s port %d", - proxy_type, addrbuf, + vt->type, addrbuf, conf_get_int(conf, CONF_proxy_port)); plug_log(plug, PLUGLOG_PROXY_MSG, NULL, 0, logmsg, 0); sfree(logmsg); @@ -493,7 +521,7 @@ Socket *new_connection(SockAddr *addr, const char *hostname, /* start the proxy negotiation process... */ sk_set_frozen(ps->sub_socket, false); - ps->negotiate(ps, PROXY_CHANGE_NEW); + proxy_negotiate(ps); return &ps->sock; } @@ -511,865 +539,3 @@ Socket *new_listener(const char *srcaddr, int port, Plug *plug, return sk_newlistener(srcaddr, port, plug, local_host_only, addressfamily); } - -/* ---------------------------------------------------------------------- - * HTTP CONNECT proxy type. - */ - -static bool get_line_end(char *data, size_t len, size_t *out) -{ - size_t 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) { - *out = off; - return true; - } - - /* 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') { - *out = off; - return true; - } - - /* the line does continue, so we have to keep going - * until we see an the header's "real" end of line. - */ - off++; - } - - off++; - } - - return false; -} - -int proxy_http_negotiate (ProxySocket *ps, int change) -{ - if (ps->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]; - char *username, *password; - - sk_getaddr(ps->remote_addr, dest, lenof(dest)); - - buf = dupprintf("CONNECT %s:%i HTTP/1.1\r\nHost: %s:%i\r\n", - dest, ps->remote_port, dest, ps->remote_port); - sk_write(ps->sub_socket, buf, strlen(buf)); - sfree(buf); - - username = conf_get_str(ps->conf, CONF_proxy_username); - password = conf_get_str(ps->conf, CONF_proxy_password); - if (username[0] || password[0]) { - char *buf, *buf2; - int i, j, len; - buf = dupprintf("%s:%s", username, password); - len = strlen(buf); - buf2 = snewn(len * 4 / 3 + 100, char); - sprintf(buf2, "Proxy-Authorization: Basic "); - for (i = 0, j = strlen(buf2); i < len; i += 3, j += 4) - base64_encode_atom((unsigned char *)(buf+i), - (len-i > 3 ? 3 : len-i), buf2+j); - strcpy(buf2+j, "\r\n"); - sk_write(ps->sub_socket, buf2, strlen(buf2)); - sfree(buf); - sfree(buf2); - } - - sk_write(ps->sub_socket, "\r\n", 2); - - ps->state = 1; - return 0; - } - - 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; - size_t len, eol; - - if (ps->state == 1) { - - int min_ver, maj_ver, status; - - /* get the status line */ - len = bufchain_size(&ps->pending_input_data); - assert(len > 0); /* or we wouldn't be here */ - data = snewn(len+1, char); - bufchain_fetch(&ps->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'; - - if (!get_line_end(data, len, &eol)) { - 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_error(ps->plug, "Proxy error: " - "HTTP response was absent"); - sfree(data); - return 1; - } - - /* remove the status line from the input buffer. */ - bufchain_consume(&ps->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_error(ps->plug, buf); - sfree(buf); - sfree(data); - return 1; - } - - sfree(data); - - ps->state = 2; - } - - if (ps->state == 2) { - - /* get headers. we're done when we get a - * header of length 2, (ie. just "\r\n") - */ - - len = bufchain_size(&ps->pending_input_data); - assert(len > 0); /* or we wouldn't be here */ - data = snewn(len, char); - datap = data; - bufchain_fetch(&ps->pending_input_data, data, len); - - if (!get_line_end(datap, len, &eol)) { - sfree(data); - return 1; - } - while (eol > 2) { - bufchain_consume(&ps->pending_input_data, eol); - datap += eol; - len -= eol; - if (!get_line_end(datap, len, &eol)) - eol = 0; /* terminate the loop */ - } - - if (eol == 2) { - /* we're done */ - bufchain_consume(&ps->pending_input_data, 2); - proxy_activate(ps); - /* proxy activate will have dealt with - * whatever is left of the buffer */ - sfree(data); - return 1; - } - - sfree(data); - return 1; - } - } - - plug_closing_error(ps->plug, "Proxy error: unexpected proxy error"); - return 1; -} - -/* ---------------------------------------------------------------------- - * SOCKS proxy type. - */ - -/* SOCKS version 4 */ -int proxy_socks4_negotiate (ProxySocket *ps, int change) -{ - if (ps->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) - */ - - strbuf *command = strbuf_new(); - char hostname[512]; - bool write_hostname = false; - - put_byte(command, SOCKS4_REQUEST_VERSION); - put_byte(command, SOCKS_CMD_CONNECT); - put_uint16(command, ps->remote_port); - - switch (sk_addrtype(ps->remote_addr)) { - case ADDRTYPE_IPV4: { - char addr[4]; - sk_addrcopy(ps->remote_addr, addr); - put_data(command, addr, 4); - break; - } - case ADDRTYPE_NAME: - sk_getaddr(ps->remote_addr, hostname, lenof(hostname)); - put_uint32(command, SOCKS4A_NAME_FOLLOWS_BASE); - write_hostname = true; - break; - case ADDRTYPE_IPV6: - ps->error = "Proxy error: SOCKS version 4 does not support IPv6"; - strbuf_free(command); - return 1; - } - - put_asciz(command, conf_get_str(ps->conf, CONF_proxy_username)); - if (write_hostname) - put_asciz(command, hostname); - sk_write(ps->sub_socket, command->s, command->len); - strbuf_free(command); - - ps->state = 1; - return 0; - } - - 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 (ps->state == 1) { - /* response format: - * version number (1 byte) = 0 - * 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(&ps->pending_input_data) < 8) - return 1; /* not got anything yet */ - - /* get the response */ - bufchain_fetch(&ps->pending_input_data, data, 8); - - if (data[0] != SOCKS4_REPLY_VERSION) { - plug_closing_error(ps->plug, "Proxy error: SOCKS proxy " - "responded with unexpected " - "reply code version"); - return 1; - } - - if (data[1] != SOCKS4_RESP_SUCCESS) { - - switch (data[1]) { - case SOCKS4_RESP_WANT_IDENTD: - plug_closing_error(ps->plug, "Proxy error: SOCKS server " - "wanted IDENTD on client"); - break; - case SOCKS4_RESP_IDENTD_MISMATCH: - plug_closing_error(ps->plug, "Proxy error: Username and " - "IDENTD on client don't agree"); - break; - case SOCKS4_RESP_FAILURE: - default: - plug_closing_error(ps->plug, "Proxy error: Error while " - "communicating with proxy"); - break; - } - - return 1; - } - bufchain_consume(&ps->pending_input_data, 8); - - /* we're done */ - proxy_activate(ps); - /* proxy activate will have dealt with - * whatever is left of the buffer */ - return 1; - } - } - - plug_closing_error(ps->plug, "Proxy error: unexpected proxy error"); - return 1; -} - -/* SOCKS version 5 */ -int proxy_socks5_negotiate (ProxySocket *ps, int change) -{ - if (ps->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 - */ - - strbuf *command; - char *username, *password; - int method_count_offset, methods_start; - - command = strbuf_new(); - put_byte(command, SOCKS5_REQUEST_VERSION); - username = conf_get_str(ps->conf, CONF_proxy_username); - password = conf_get_str(ps->conf, CONF_proxy_password); - - method_count_offset = command->len; - put_byte(command, 0); - methods_start = command->len; - - put_byte(command, SOCKS5_AUTH_NONE); - - if (username[0] || password[0]) { - proxy_socks5_offerencryptedauth(BinarySink_UPCAST(command)); - put_byte(command, SOCKS5_AUTH_PASSWORD); - } - - command->u[method_count_offset] = command->len - methods_start; - - sk_write(ps->sub_socket, command->s, command->len); - strbuf_free(command); - - ps->state = 1; - return 0; - } - - 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 (ps->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(&ps->pending_input_data) < 2) - return 1; /* not got anything yet */ - - /* get the response */ - bufchain_fetch(&ps->pending_input_data, data, 2); - - if (data[0] != SOCKS5_REPLY_VERSION) { - plug_closing_error(ps->plug, "Proxy error: SOCKS proxy " - "returned unexpected version"); - return 1; - } - - if (data[1] == SOCKS5_AUTH_NONE) ps->state = 2; - else if (data[1] == SOCKS5_AUTH_GSSAPI) ps->state = 4; - else if (data[1] == SOCKS5_AUTH_PASSWORD) ps->state = 5; - else if (data[1] == SOCKS5_AUTH_CHAP) ps->state = 6; - else { - plug_closing_error(ps->plug, "Proxy error: SOCKS proxy did not " - "accept our authentication"); - return 1; - } - bufchain_consume(&ps->pending_input_data, 2); - } - - if (ps->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(&ps->pending_input_data) < 2) - return 1; /* not got anything yet */ - - /* get the response */ - bufchain_fetch(&ps->pending_input_data, data, 2); - - if (data[0] != SOCKS5_AUTH_PASSWORD_VERSION) { - plug_closing_error(ps->plug, "Proxy error: SOCKS password " - "subnegotiation contained wrong version " - "number"); - return 1; - } - - if (data[1] != 0) { - plug_closing_error(ps->plug, "Proxy error: SOCKS proxy refused " - "password authentication"); - return 1; - } - - bufchain_consume(&ps->pending_input_data, 2); - ps->state = 2; /* now proceed as authenticated */ - } - - if (ps->state == 8) { - int ret; - ret = proxy_socks5_handlechap(ps); - if (ret) return ret; - } - - if (ps->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] - */ - - strbuf *command = strbuf_new(); - put_byte(command, SOCKS5_REQUEST_VERSION); - put_byte(command, SOCKS_CMD_CONNECT); - put_byte(command, 0x00); /* reserved byte */ - - switch (sk_addrtype(ps->remote_addr)) { - case ADDRTYPE_IPV4: - put_byte(command, SOCKS5_ADDR_IPV4); - sk_addrcopy(ps->remote_addr, strbuf_append(command, 4)); - break; - case ADDRTYPE_IPV6: - put_byte(command, SOCKS5_ADDR_IPV6); - sk_addrcopy(ps->remote_addr, strbuf_append(command, 16)); - break; - case ADDRTYPE_NAME: { - char hostname[512]; - put_byte(command, SOCKS5_ADDR_HOSTNAME); - sk_getaddr(ps->remote_addr, hostname, lenof(hostname)); - if (!put_pstring(command, hostname)) { - ps->error = "Proxy error: SOCKS 5 cannot " - "support host names longer than 255 chars"; - strbuf_free(command); - return 1; - } - break; - } - } - - put_uint16(command, ps->remote_port); - - sk_write(ps->sub_socket, command->s, command->len); - - strbuf_free(command); - - ps->state = 3; - return 1; - } - - if (ps->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(&ps->pending_input_data) < 5) - return 1; /* not got anything yet */ - - /* get the response */ - bufchain_fetch(&ps->pending_input_data, data, 5); - - if (data[0] != SOCKS5_REPLY_VERSION) { - plug_closing_error(ps->plug, "Proxy error: SOCKS proxy " - "returned wrong version number"); - return 1; - } - - if (data[1] != SOCKS5_RESP_SUCCESS) { - char buf[256]; - - strcpy(buf, "Proxy error: "); - - switch (data[1]) { - case SOCKS5_RESP_FAILURE: strcat(buf, "General SOCKS server failure"); break; - case SOCKS5_RESP_CONNECTION_NOT_ALLOWED_BY_RULESET: strcat(buf, "Connection not allowed by ruleset"); break; - case SOCKS5_RESP_NETWORK_UNREACHABLE: strcat(buf, "Network unreachable"); break; - case SOCKS5_RESP_HOST_UNREACHABLE: strcat(buf, "Host unreachable"); break; - case SOCKS5_RESP_CONNECTION_REFUSED: strcat(buf, "Connection refused"); break; - case SOCKS5_RESP_TTL_EXPIRED: strcat(buf, "TTL expired"); break; - case SOCKS5_RESP_COMMAND_NOT_SUPPORTED: strcat(buf, "Command not supported"); break; - case SOCKS5_RESP_ADDRTYPE_NOT_SUPPORTED: strcat(buf, "Address type not supported"); break; - default: sprintf(buf+strlen(buf), - "Unrecognised SOCKS error code %d", - data[1]); - break; - } - plug_closing_error(ps->plug, buf); - - return 1; - } - - /* - * Eat the rest of the reply packet. - */ - len = 6; /* first 4 bytes, last 2 */ - switch (data[3]) { - case SOCKS5_ADDR_IPV4: len += 4; break; /* IPv4 address */ - case SOCKS5_ADDR_IPV6: len += 16; break;/* IPv6 address */ - case SOCKS5_ADDR_HOSTNAME: len += 1+(unsigned char)data[4]; break; /* domain name */ - default: - plug_closing_error(ps->plug, "Proxy error: SOCKS proxy " - "returned unrecognised address format"); - return 1; - } - if (bufchain_size(&ps->pending_input_data) < len) - return 1; /* not got whole reply yet */ - bufchain_consume(&ps->pending_input_data, len); - - /* we're done */ - proxy_activate(ps); - return 1; - } - - if (ps->state == 4) { - /* TODO: Handle GSSAPI authentication */ - plug_closing_error(ps->plug, "Proxy error: We don't support " - "GSSAPI authentication"); - return 1; - } - - if (ps->state == 5) { - const char *username = conf_get_str(ps->conf, CONF_proxy_username); - const char *password = conf_get_str(ps->conf, CONF_proxy_password); - if (username[0] || password[0]) { - strbuf *auth = strbuf_new_nm(); - put_byte(auth, SOCKS5_AUTH_PASSWORD_VERSION); - if (!put_pstring(auth, username)) { - ps->error = "Proxy error: SOCKS 5 authentication cannot " - "support usernames longer than 255 chars"; - strbuf_free(auth); - return 1; - } - if (!put_pstring(auth, password)) { - ps->error = "Proxy error: SOCKS 5 authentication cannot " - "support passwords longer than 255 chars"; - strbuf_free(auth); - return 1; - } - sk_write(ps->sub_socket, auth->s, auth->len); - strbuf_free(auth); - ps->state = 7; - } else - plug_closing_error(ps->plug, "Proxy error: Server chose " - "username/password authentication " - "but we didn't offer it!"); - return 1; - } - - if (ps->state == 6) { - int ret; - ret = proxy_socks5_selectchap(ps); - if (ret) return ret; - } - - } - - plug_closing_error(ps->plug, "Proxy error: Unexpected proxy error"); - 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, Conf *conf) -{ - char *fmt = conf_get_str(conf, CONF_proxy_telnet_command); - int so = 0, eo = 0; - strbuf *buf = strbuf_new(); - - /* we need to escape \\, \%, \r, \n, \t, \x??, \0???, - * %%, %host, %port, %user, and %pass - */ - - while (fmt[eo] != 0) { - - /* scan forward until we hit end-of-line, - * or an escape character (\ or %) */ - while (fmt[eo] != 0 && fmt[eo] != '%' && fmt[eo] != '\\') - eo++; - - /* if we hit eol, break out of our escaping loop */ - if (fmt[eo] == 0) break; - - /* if there was any unescaped text before the escape - * character, send that now */ - if (eo != so) - put_data(buf, fmt + so, eo - so); - - so = eo++; - - /* if the escape character was the last character of - * the line, we'll just stop and send it. */ - if (fmt[eo] == 0) break; - - if (fmt[so] == '\\') { - - /* we recognize \\, \%, \r, \n, \t, \x??. - * anything else, we just send unescaped (including the \). - */ - - switch (fmt[eo]) { - - case '\\': - put_byte(buf, '\\'); - eo++; - break; - - case '%': - put_byte(buf, '%'); - eo++; - break; - - case 'r': - put_byte(buf, '\r'); - eo++; - break; - - case 'n': - put_byte(buf, '\n'); - eo++; - break; - - case 't': - put_byte(buf, '\t'); - eo++; - break; - - case 'x': - case 'X': { - /* escaped hexadecimal value (ie. \xff) */ - unsigned char v = 0; - int i = 0; - - for (;;) { - eo++; - if (fmt[eo] >= '0' && fmt[eo] <= '9') - v += fmt[eo] - '0'; - else if (fmt[eo] >= 'a' && fmt[eo] <= 'f') - v += fmt[eo] - 'a' + 10; - else if (fmt[eo] >= 'A' && fmt[eo] <= 'F') - v += fmt[eo] - 'A' + 10; - else { - /* non hex character, so we abort and just - * send the whole thing unescaped (including \x) - */ - put_byte(buf, '\\'); - eo = so + 1; - break; - } - - /* we only extract two hex characters */ - if (i == 1) { - put_byte(buf, v); - eo++; - break; - } - - i++; - v <<= 4; - } - break; - } - - default: - put_data(buf, fmt + so, 2); - eo++; - break; - } - } else { - - /* % escape. we recognize %%, %host, %port, %user, %pass. - * %proxyhost, %proxyport. Anything else we just send - * unescaped (including the %). - */ - - if (fmt[eo] == '%') { - put_byte(buf, '%'); - eo++; - } - else if (strnicmp(fmt + eo, "host", 4) == 0) { - char dest[512]; - sk_getaddr(addr, dest, lenof(dest)); - put_data(buf, dest, strlen(dest)); - eo += 4; - } - else if (strnicmp(fmt + eo, "port", 4) == 0) { - put_fmt(buf, "%d", port); - eo += 4; - } - else if (strnicmp(fmt + eo, "user", 4) == 0) { - const char *username = conf_get_str(conf, CONF_proxy_username); - put_data(buf, username, strlen(username)); - eo += 4; - } - else if (strnicmp(fmt + eo, "pass", 4) == 0) { - const char *password = conf_get_str(conf, CONF_proxy_password); - put_data(buf, password, strlen(password)); - eo += 4; - } - else if (strnicmp(fmt + eo, "proxyhost", 9) == 0) { - const char *host = conf_get_str(conf, CONF_proxy_host); - put_data(buf, host, strlen(host)); - eo += 9; - } - else if (strnicmp(fmt + eo, "proxyport", 9) == 0) { - int port = conf_get_int(conf, CONF_proxy_port); - put_fmt(buf, "%d", port); - 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. - */ - put_byte(buf, '%'); - } - } - - /* 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) { - put_data(buf, fmt + so, eo - so); - } - - return strbuf_to_str(buf); -} - -int proxy_telnet_negotiate (ProxySocket *ps, int change) -{ - if (ps->state == PROXY_CHANGE_NEW) { - char *formatted_cmd; - - formatted_cmd = format_telnet_command(ps->remote_addr, ps->remote_port, - ps->conf); - - { - /* - * Re-escape control chars in the command, for logging. - */ - char *reescaped = snewn(4*strlen(formatted_cmd) + 1, char); - const char *in; - char *out; - char *logmsg; - - for (in = formatted_cmd, out = reescaped; *in; in++) { - if (*in == '\n') { - *out++ = '\\'; *out++ = 'n'; - } else if (*in == '\r') { - *out++ = '\\'; *out++ = 'r'; - } else if (*in == '\t') { - *out++ = '\\'; *out++ = 't'; - } else if (*in == '\\') { - *out++ = '\\'; *out++ = '\\'; - } else if ((unsigned)(((unsigned char)*in) - 0x20) < - (0x7F-0x20)) { - *out++ = *in; - } else { - out += sprintf(out, "\\x%02X", (unsigned)*in & 0xFF); - } - } - *out = '\0'; - - logmsg = dupprintf("Sending Telnet proxy command: %s", reescaped); - plug_log(ps->plug, PLUGLOG_PROXY_MSG, NULL, 0, logmsg, 0); - sfree(logmsg); - sfree(reescaped); - } - - sk_write(ps->sub_socket, formatted_cmd, strlen(formatted_cmd)); - sfree(formatted_cmd); - - ps->state = 1; - return 0; - } - - 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(ps); - /* proxy activate will have dealt with - * whatever is left of the buffer */ - return 1; - } - - plug_closing_error(ps->plug, "Proxy error: Unexpected proxy error"); - return 1; -} diff --git a/proxy/proxy.h b/proxy/proxy.h index 62aa144b..26ff20ef 100644 --- a/proxy/proxy.h +++ b/proxy/proxy.h @@ -11,6 +11,8 @@ #define PUTTY_PROXY_H typedef struct ProxySocket ProxySocket; +typedef struct ProxyNegotiator ProxyNegotiator; +typedef struct ProxyNegotiatorVT ProxyNegotiatorVT; struct ProxySocket { const char *error; @@ -25,59 +27,58 @@ struct ProxySocket { bufchain pending_input_data; bool pending_eof; -#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. - */ bool 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_RECEIVE 2 + ProxyNegotiator *pn; /* non-NULL if still negotiating */ + bufchain output_from_negotiator; - /* 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. - */ + /* configuration, used to look up proxy settings */ + Conf *conf; - int (*negotiate) (ProxySocket * /* this */, int /* change type */); + Socket sock; + Plug plugimpl; +}; - /* current arguments of plug handlers - * (for use by proxy's negotiate function) - */ +struct ProxyNegotiator { + const ProxyNegotiatorVT *vt; - /* receive */ - bool receive_urgent; - const char *receive_data; - int receive_len; + /* Standard fields for any ProxyNegotiator. new() and free() don't + * have to set these up; that's done centrally, to save duplication. */ + ProxySocket *ps; + bufchain *input; + bufchain_sink output[1]; - /* configuration, used to look up proxy settings */ - Conf *conf; + /* Set to report success during proxy negotiation. */ + bool done; - /* CHAP transient data */ - int chap_num_attributes; - int chap_num_attributes_processed; - int chap_current_attribute; - int chap_current_datalen; + /* Set to report an error during proxy negotiation. The main + * ProxySocket will free it, and will then guarantee never to call + * process_queue again. */ + char *error; +}; - Socket sock; - Plug plugimpl; +struct ProxyNegotiatorVT { + ProxyNegotiator *(*new)(const ProxyNegotiatorVT *); + void (*process_queue)(ProxyNegotiator *); + void (*free)(ProxyNegotiator *); + const char *type; }; -extern void proxy_activate (ProxySocket *); +static inline ProxyNegotiator *proxy_negotiator_new( + const ProxyNegotiatorVT *vt) +{ return vt->new(vt); } +static inline void proxy_negotiator_process_queue(ProxyNegotiator *pn) +{ pn->vt->process_queue(pn); } +static inline void proxy_negotiator_free(ProxyNegotiator *pn) +{ pn->vt->free(pn); } -extern int proxy_http_negotiate (ProxySocket *, int); -extern int proxy_telnet_negotiate (ProxySocket *, int); -extern int proxy_socks4_negotiate (ProxySocket *, int); -extern int proxy_socks5_negotiate (ProxySocket *, int); +extern const ProxyNegotiatorVT http_proxy_negotiator_vt; +extern const ProxyNegotiatorVT socks4_proxy_negotiator_vt; +extern const ProxyNegotiatorVT socks5_proxy_negotiator_vt; +extern const ProxyNegotiatorVT telnet_proxy_negotiator_vt; /* * This may be reused by local-command proxies on individual @@ -89,8 +90,7 @@ char *format_telnet_command(SockAddr *addr, int port, Conf *conf); * These are implemented in cproxy.c or nocproxy.c, depending on * whether encrypted proxy authentication is available. */ -extern void proxy_socks5_offerencryptedauth(BinarySink *); -extern int proxy_socks5_handlechap (ProxySocket *); -extern int proxy_socks5_selectchap(ProxySocket *); +extern const bool socks5_chap_available; +strbuf *chap_response(ptrlen challenge, ptrlen password); #endif diff --git a/proxy/socks4.c b/proxy/socks4.c new file mode 100644 index 00000000..ac85ec05 --- /dev/null +++ b/proxy/socks4.c @@ -0,0 +1,136 @@ +/* + * SOCKS 4 proxy negotiation. + */ + +#include "putty.h" +#include "network.h" +#include "proxy.h" +#include "socks.h" +#include "sshcr.h" + +typedef struct Socks4ProxyNegotiator { + int crLine; + ProxyNegotiator pn; +} Socks4ProxyNegotiator; + +static ProxyNegotiator *proxy_socks4_new(const ProxyNegotiatorVT *vt) +{ + Socks4ProxyNegotiator *s = snew(Socks4ProxyNegotiator); + s->pn.vt = vt; + s->crLine = 0; + return &s->pn; +} + +static void proxy_socks4_free(ProxyNegotiator *pn) +{ + Socks4ProxyNegotiator *s = container_of(pn, Socks4ProxyNegotiator, pn); + sfree(s); +} + +static void proxy_socks4_process_queue(ProxyNegotiator *pn) +{ + Socks4ProxyNegotiator *s = container_of(pn, Socks4ProxyNegotiator, pn); + + crBegin(s->crLine); + + { + char hostname[512]; + bool write_hostname = false; + + /* + * SOCKS 4 request packet: + * + * byte version + * byte command + * uint16 destination port number + * uint32 destination IPv4 address (or something in the + * SOCKS4A_NAME_FOLLOWS range) + * asciz username + * asciz destination hostname (if we sent SOCKS4A_NAME_FOLLOWS_*) + */ + + put_byte(pn->output, SOCKS4_REQUEST_VERSION); + put_byte(pn->output, SOCKS_CMD_CONNECT); + put_uint16(pn->output, pn->ps->remote_port); + + switch (sk_addrtype(pn->ps->remote_addr)) { + case ADDRTYPE_IPV4: { + char addr[4]; + sk_addrcopy(pn->ps->remote_addr, addr); + put_data(pn->output, addr, 4); + break; + } + case ADDRTYPE_NAME: + put_uint32(pn->output, SOCKS4A_NAME_FOLLOWS_BASE); + sk_getaddr(pn->ps->remote_addr, hostname, lenof(hostname)); + write_hostname = true; + break; + case ADDRTYPE_IPV6: + pn->error = dupstr("SOCKS version 4 does not support IPv6"); + crStopV; + } + + put_asciz(pn->output, conf_get_str(pn->ps->conf, CONF_proxy_username)); + + if (write_hostname) + put_asciz(pn->output, hostname); + } + + crReturnV; + + { + unsigned char data[8]; + crMaybeWaitUntilV(bufchain_try_fetch_consume(pn->input, data, 8)); + + /* + * SOCKS 4 response packet: + * + * byte version + * byte status + * uint16 port number + * uint32 IPv4 address + * + * We don't need to worry about the port and destination address. + */ + + if (data[0] != SOCKS4_REPLY_VERSION) { + pn->error = dupprintf("SOCKS proxy response contained reply " + "version number %d (expected 0)", + (int)data[0]); + crStopV; + } + + switch (data[1]) { + case SOCKS4_RESP_SUCCESS: + pn->done = true; + break; + + case SOCKS4_RESP_FAILURE: + pn->error = dupstr("SOCKS server reported failure to connect"); + break; + + case SOCKS4_RESP_WANT_IDENTD: + pn->error = dupstr("SOCKS server wanted IDENTD on client"); + break; + + case SOCKS4_RESP_IDENTD_MISMATCH: + pn->error = dupstr("Username and IDENTD on client don't agree"); + break; + + default: + pn->error = dupprintf("SOCKS server sent unrecognised error " + "code %d", (int)data[1]); + break; + } + crStopV; + } + + crFinishV; +} + +const struct ProxyNegotiatorVT socks4_proxy_negotiator_vt = { + .new = proxy_socks4_new, + .free = proxy_socks4_free, + .process_queue = proxy_socks4_process_queue, + .type = "SOCKS 4", +}; diff --git a/proxy/socks5.c b/proxy/socks5.c new file mode 100644 index 00000000..407e98ea --- /dev/null +++ b/proxy/socks5.c @@ -0,0 +1,434 @@ +/* + * SOCKS 5 proxy negotiation. + */ + +#include "putty.h" +#include "network.h" +#include "proxy.h" +#include "socks.h" +#include "sshcr.h" + +static inline const char *socks5_auth_name(unsigned char m) +{ + switch (m) { + case SOCKS5_AUTH_NONE: return "none"; + case SOCKS5_AUTH_GSSAPI: return "GSSAPI"; + case SOCKS5_AUTH_PASSWORD: return "password"; + case SOCKS5_AUTH_CHAP: return "CHAP"; + default: return "unknown"; + } +} + +static inline const char *socks5_response_text(unsigned char m) +{ + switch (m) { + case SOCKS5_RESP_SUCCESS: return "success"; + case SOCKS5_RESP_FAILURE: return "unspecified failure"; + case SOCKS5_RESP_CONNECTION_NOT_ALLOWED_BY_RULESET: + return "connection not allowed by ruleset"; + case SOCKS5_RESP_NETWORK_UNREACHABLE: return "network unreachable"; + case SOCKS5_RESP_HOST_UNREACHABLE: return "host unreachable"; + case SOCKS5_RESP_CONNECTION_REFUSED: return "connection refused"; + case SOCKS5_RESP_TTL_EXPIRED: return "TTL expired"; + case SOCKS5_RESP_COMMAND_NOT_SUPPORTED: return "command not supported"; + case SOCKS5_RESP_ADDRTYPE_NOT_SUPPORTED: + return "address type not supported"; + default: return "unknown"; + } +} + +typedef struct Socks5ProxyNegotiator { + int crLine; + strbuf *auth_methods_offered; + unsigned char auth_method; + unsigned n_chap_attrs; + unsigned chap_attr, chap_attr_len; + unsigned char chap_buf[256]; + int response_addr_length; + ProxyNegotiator pn; +} Socks5ProxyNegotiator; + +static ProxyNegotiator *proxy_socks5_new(const ProxyNegotiatorVT *vt) +{ + Socks5ProxyNegotiator *s = snew(Socks5ProxyNegotiator); + memset(s, 0, sizeof(*s)); + s->pn.vt = vt; + s->auth_methods_offered = strbuf_new(); + return &s->pn; +} + +static void proxy_socks5_free(ProxyNegotiator *pn) +{ + Socks5ProxyNegotiator *s = container_of(pn, Socks5ProxyNegotiator, pn); + strbuf_free(s->auth_methods_offered); + smemclr(s, sizeof(s)); + sfree(s); +} + +static void proxy_socks5_process_queue(ProxyNegotiator *pn) +{ + Socks5ProxyNegotiator *s = container_of(pn, Socks5ProxyNegotiator, pn); + + crBegin(s->crLine); + + /* + * SOCKS 5 initial client packet: + * + * byte version + * byte number of available auth methods + * byte[] that many bytes indicating auth types + */ + + put_byte(pn->output, SOCKS5_REQUEST_VERSION); + + strbuf_clear(s->auth_methods_offered); + + put_byte(s->auth_methods_offered, SOCKS5_AUTH_NONE); + + { + const char *username = conf_get_str(pn->ps->conf, CONF_proxy_username); + const char *password = conf_get_str(pn->ps->conf, CONF_proxy_password); + if (username[0] || password[0]) { + if (socks5_chap_available) + put_byte(s->auth_methods_offered, SOCKS5_AUTH_CHAP); + + put_byte(s->auth_methods_offered, SOCKS5_AUTH_PASSWORD); + } + } + + put_byte(pn->output, s->auth_methods_offered->len); + put_datapl(pn->output, ptrlen_from_strbuf(s->auth_methods_offered)); + + crReturnV; + + /* + * SOCKS 5 initial server packet: + * + * byte version + * byte selected auth method, or SOCKS5_AUTH_REJECTED + */ + { + unsigned char data[2]; + crMaybeWaitUntilV(bufchain_try_fetch_consume(pn->input, data, 2)); + + if (data[0] != SOCKS5_REPLY_VERSION) { + pn->error = dupprintf("SOCKS proxy returned unexpected " + "reply version %d (expected %d)", + (int)data[0], SOCKS5_REPLY_VERSION); + crStopV; + } + + if (data[1] == SOCKS5_AUTH_REJECTED) { + pn->error = dupstr("SOCKS server rejected every authentication " + "method we offered"); + crStopV; + } + + { + bool found = false; + for (size_t i = 0; i < s->auth_methods_offered->len; i++) + if (s->auth_methods_offered->u[i] == data[1]) { + found = true; + break; + } + + if (!found) { + pn->error = dupprintf("SOCKS server asked for auth method %d " + "(%s), which we did not offer", + (int)data[1], socks5_auth_name(data[1])); + crStopV; + } + } + + s->auth_method = data[1]; + } + + /* + * Process each auth method. Note we can't do this using the + * natural idiom of a switch statement, because there are + * crReturns inside some cases. + */ + if (s->auth_method == SOCKS5_AUTH_NONE) { + /* 'none' auth needs no further negotiation, so skip ahead + * to actually making the connection */ + goto authenticated; + } + + if (s->auth_method == SOCKS5_AUTH_PASSWORD) { + /* + * SOCKS 5 password auth packet: + * + * byte version + * pstring username + * pstring password + */ + const char *username = conf_get_str( + pn->ps->conf, CONF_proxy_username); + const char *password = conf_get_str( + pn->ps->conf, CONF_proxy_password); + put_byte(pn->output, SOCKS5_AUTH_PASSWORD_VERSION); + if (!put_pstring(pn->output, username)) { + pn->error = dupstr("SOCKS 5 authentication cannot support " + "usernames longer than 255 chars"); + crStopV; + } + if (!put_pstring(pn->output, password)) { + pn->error = dupstr("SOCKS 5 authentication cannot support " + "passwords longer than 255 chars"); + crStopV; + } + + /* + * SOCKS 5 password reply packet: + * + * byte version + * byte 0 for success, >0 for failure + */ + unsigned char data[2]; + crMaybeWaitUntilV(bufchain_try_fetch_consume(pn->input, data, 2)); + + if (data[0] != SOCKS5_AUTH_PASSWORD_VERSION) { + pn->error = dupprintf( + "SOCKS 5 password reply had version number %d (expected " + "%d)", (int)data[0], SOCKS5_AUTH_PASSWORD_VERSION); + crStopV; + } + + if (data[1] != 0) { + pn->error = dupstr("SOCKS 5 server rejected our password"); + crStopV; + } + } else if (s->auth_method == SOCKS5_AUTH_CHAP) { + assert(socks5_chap_available); + + /* + * All CHAP packets, in both directions, have the same + * overall format: + * + * byte version + * byte number of attributes + * + * and then for each attribute: + * + * byte attribute type + * byte length + * byte[] that many bytes of payload + * + * In the initial outgoing packet we send two attributes: + * the list of supported algorithm names, and the + * username. + * + * (It's possible that we ought to delay sending the + * username until the second packet, in case the proxy + * sent back an attribute indicating which character set + * it would like us to use.) + */ + put_byte(pn->output, SOCKS5_AUTH_CHAP_VERSION); + put_byte(pn->output, 2); /* number of attributes */ + + put_byte(pn->output, SOCKS5_AUTH_CHAP_ATTR_ALGLIST); + put_byte(pn->output, 1); /* string length */ + put_byte(pn->output, SOCKS5_AUTH_CHAP_ALG_HMACMD5); + + /* Second attribute: username */ + { + const char *username = conf_get_str( + pn->ps->conf, CONF_proxy_username); + put_byte(pn->output, SOCKS5_AUTH_CHAP_ATTR_USERNAME); + if (!put_pstring(pn->output, username)) { + pn->error = dupstr( + "SOCKS 5 CHAP authentication cannot support " + "usernames longer than 255 chars"); + crStopV; + } + } + + while (true) { + /* + * Process a CHAP response packet, which has the same + * overall format as the outgoing packet shown above. + */ + unsigned char data[2]; + crMaybeWaitUntilV(bufchain_try_fetch_consume( + pn->input, data, 2)); + if (data[0] != SOCKS5_AUTH_CHAP_VERSION) { + pn->error = dupprintf( + "SOCKS 5 CHAP reply had version number %d (expected " + "%d)", (int)data[0], SOCKS5_AUTH_CHAP_VERSION); + crStopV; + } + + s->n_chap_attrs = data[1]; + if (s->n_chap_attrs == 0) { + /* + * If we receive a CHAP packet containing no + * attributes, then we have nothing we didn't have + * before, and can't make further progress. + */ + pn->error = dupprintf( + "SOCKS 5 CHAP reply sent no attributes"); + crStopV; + } + while (s->n_chap_attrs-- > 0) { + unsigned char data[2]; + crMaybeWaitUntilV(bufchain_try_fetch_consume( + pn->input, data, 2)); + s->chap_attr = data[0]; + s->chap_attr_len = data[1]; + crMaybeWaitUntilV(bufchain_try_fetch_consume( + pn->input, s->chap_buf, s->chap_attr_len)); + + if (s->chap_attr == SOCKS5_AUTH_CHAP_ATTR_STATUS) { + if (s->chap_attr_len == 1 && s->chap_buf[0] == 0) { + /* Status 0 means success: we are authenticated! */ + goto authenticated; + } else { + pn->error = dupstr( + "SOCKS 5 CHAP authentication failed"); + crStopV; + } + } else if (s->chap_attr==SOCKS5_AUTH_CHAP_ATTR_CHALLENGE) { + /* The CHAP challenge string. Send the response */ + const char *password = conf_get_str( + pn->ps->conf, CONF_proxy_password); + strbuf *response = chap_response( + make_ptrlen(s->chap_buf, s->chap_attr_len), + ptrlen_from_asciz(password)); + + put_byte(pn->output, SOCKS5_AUTH_CHAP_VERSION); + put_byte(pn->output, 1); /* number of attributes */ + put_byte(pn->output, SOCKS5_AUTH_CHAP_ATTR_RESPONSE); + put_byte(pn->output, response->len); + put_datapl(pn->output, ptrlen_from_strbuf(response)); + + strbuf_free(response); + } else { + /* ignore all other attributes */ + } + } + } + } else { + unreachable("bad auth method in SOCKS 5 negotiation"); + } + + authenticated: + + /* + * SOCKS 5 connection command: + * + * byte version + * byte command + * byte reserved (send as zero) + * byte address type + * byte[] address, with variable size (see below) + * uint16 port + */ + put_byte(pn->output, SOCKS5_REPLY_VERSION); + put_byte(pn->output, SOCKS_CMD_CONNECT); + put_byte(pn->output, 0); /* reserved byte */ + + switch (sk_addrtype(pn->ps->remote_addr)) { + case ADDRTYPE_IPV4: { + /* IPv4: address is 4 raw bytes */ + put_byte(pn->output, SOCKS5_ADDR_IPV4); + char buf[4]; + sk_addrcopy(pn->ps->remote_addr, buf); + put_data(pn->output, buf, sizeof(buf)); + break; + } + case ADDRTYPE_IPV6: { + /* IPv6: address is 16 raw bytes */ + put_byte(pn->output, SOCKS5_ADDR_IPV6); + char buf[16]; + sk_addrcopy(pn->ps->remote_addr, buf); + put_data(pn->output, buf, sizeof(buf)); + break; + } + case ADDRTYPE_NAME: { + /* Hostname: address is a pstring (Pascal-style string, + * unterminated but with a one-byte prefix length) */ + put_byte(pn->output, SOCKS5_ADDR_HOSTNAME); + char hostname[512]; + sk_getaddr(pn->ps->remote_addr, hostname, lenof(hostname)); + if (!put_pstring(pn->output, hostname)) { + pn->error = dupstr( + "SOCKS 5 cannot support host names longer than 255 chars"); + crStopV; + } + break; + } + default: + unreachable("Unexpected addrtype in SOCKS 5 proxy"); + } + + put_uint16(pn->output, pn->ps->remote_port); + crReturnV; + + /* + * SOCKS 5 connection response: + * + * byte version + * byte status + * byte reserved + * byte address type + * byte[] address bound to (same formats as in connection request) + * uint16 port + * + * We read the first four bytes and then decide what to do next. + */ + { + unsigned char data[4]; + crMaybeWaitUntilV(bufchain_try_fetch_consume(pn->input, data, 4)); + + if (data[0] != SOCKS5_REPLY_VERSION) { + pn->error = dupprintf("SOCKS proxy returned unexpected " + "reply version %d (expected %d)", + (int)data[0], SOCKS5_REPLY_VERSION); + crStopV; + } + + if (data[1] != SOCKS5_RESP_SUCCESS) { + pn->error = dupprintf("SOCKS proxy failed to connect, error %d " + "(%s)", (int)data[1], + socks5_response_text(data[1])); + crStopV; + } + + /* + * Process each address type to find out the size of the rest + * of the packet. Note we can't do this using the natural + * idiom of a switch statement, because there are crReturns + * inside some cases. + */ + if (data[3] == SOCKS5_ADDR_IPV4) { + s->response_addr_length = 4; + } else if (data[3] == SOCKS5_ADDR_IPV6) { + s->response_addr_length = 16; + } else if (data[3] == SOCKS5_ADDR_HOSTNAME) { + /* Read the hostname length byte to find out how much to read */ + unsigned char len; + crMaybeWaitUntilV(bufchain_try_fetch_consume(pn->input, &len, 1)); + s->response_addr_length = len; + break; + } else { + pn->error = dupprintf("SOCKS proxy response included unknown " + "address type %d", (int)data[3]); + crStopV; + } + + /* Read and ignore the address and port fields */ + crMaybeWaitUntilV(bufchain_try_consume( + pn->input, s->response_addr_length + 2)); + } + + /* And we're done! */ + pn->done = true; + crFinishV; +} + +const struct ProxyNegotiatorVT socks5_proxy_negotiator_vt = { + .new = proxy_socks5_new, + .free = proxy_socks5_free, + .process_queue = proxy_socks5_process_queue, + .type = "SOCKS 5", +}; diff --git a/proxy/telnet.c b/proxy/telnet.c new file mode 100644 index 00000000..91e6060c --- /dev/null +++ b/proxy/telnet.c @@ -0,0 +1,246 @@ +/* + * "Telnet" proxy negotiation. + * + * (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.) + */ + +#include "putty.h" +#include "network.h" +#include "proxy.h" + +char *format_telnet_command(SockAddr *addr, int port, Conf *conf) +{ + char *fmt = conf_get_str(conf, CONF_proxy_telnet_command); + int so = 0, eo = 0; + strbuf *buf = strbuf_new(); + + /* we need to escape \\, \%, \r, \n, \t, \x??, \0???, + * %%, %host, %port, %user, and %pass + */ + + while (fmt[eo] != 0) { + + /* scan forward until we hit end-of-line, + * or an escape character (\ or %) */ + while (fmt[eo] != 0 && fmt[eo] != '%' && fmt[eo] != '\\') + eo++; + + /* if we hit eol, break out of our escaping loop */ + if (fmt[eo] == 0) break; + + /* if there was any unescaped text before the escape + * character, send that now */ + if (eo != so) + put_data(buf, fmt + so, eo - so); + + so = eo++; + + /* if the escape character was the last character of + * the line, we'll just stop and send it. */ + if (fmt[eo] == 0) break; + + if (fmt[so] == '\\') { + + /* we recognize \\, \%, \r, \n, \t, \x??. + * anything else, we just send unescaped (including the \). + */ + + switch (fmt[eo]) { + + case '\\': + put_byte(buf, '\\'); + eo++; + break; + + case '%': + put_byte(buf, '%'); + eo++; + break; + + case 'r': + put_byte(buf, '\r'); + eo++; + break; + + case 'n': + put_byte(buf, '\n'); + eo++; + break; + + case 't': + put_byte(buf, '\t'); + eo++; + break; + + case 'x': + case 'X': { + /* escaped hexadecimal value (ie. \xff) */ + unsigned char v = 0; + int i = 0; + + for (;;) { + eo++; + if (fmt[eo] >= '0' && fmt[eo] <= '9') + v += fmt[eo] - '0'; + else if (fmt[eo] >= 'a' && fmt[eo] <= 'f') + v += fmt[eo] - 'a' + 10; + else if (fmt[eo] >= 'A' && fmt[eo] <= 'F') + v += fmt[eo] - 'A' + 10; + else { + /* non hex character, so we abort and just + * send the whole thing unescaped (including \x) + */ + put_byte(buf, '\\'); + eo = so + 1; + break; + } + + /* we only extract two hex characters */ + if (i == 1) { + put_byte(buf, v); + eo++; + break; + } + + i++; + v <<= 4; + } + break; + } + + default: + put_data(buf, fmt + so, 2); + eo++; + break; + } + } else { + + /* % escape. we recognize %%, %host, %port, %user, %pass. + * %proxyhost, %proxyport. Anything else we just send + * unescaped (including the %). + */ + + if (fmt[eo] == '%') { + put_byte(buf, '%'); + eo++; + } + else if (strnicmp(fmt + eo, "host", 4) == 0) { + char dest[512]; + sk_getaddr(addr, dest, lenof(dest)); + put_data(buf, dest, strlen(dest)); + eo += 4; + } + else if (strnicmp(fmt + eo, "port", 4) == 0) { + put_fmt(buf, "%d", port); + eo += 4; + } + else if (strnicmp(fmt + eo, "user", 4) == 0) { + const char *username = conf_get_str(conf, CONF_proxy_username); + put_data(buf, username, strlen(username)); + eo += 4; + } + else if (strnicmp(fmt + eo, "pass", 4) == 0) { + const char *password = conf_get_str(conf, CONF_proxy_password); + put_data(buf, password, strlen(password)); + eo += 4; + } + else if (strnicmp(fmt + eo, "proxyhost", 9) == 0) { + const char *host = conf_get_str(conf, CONF_proxy_host); + put_data(buf, host, strlen(host)); + eo += 9; + } + else if (strnicmp(fmt + eo, "proxyport", 9) == 0) { + int port = conf_get_int(conf, CONF_proxy_port); + put_fmt(buf, "%d", port); + 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. + */ + put_byte(buf, '%'); + } + } + + /* 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) { + put_data(buf, fmt + so, eo - so); + } + + return strbuf_to_str(buf); +} + +typedef struct TelnetProxyNegotiator { + ProxyNegotiator pn; +} TelnetProxyNegotiator; + +static ProxyNegotiator *proxy_telnet_new(const ProxyNegotiatorVT *vt) +{ + TelnetProxyNegotiator *s = snew(TelnetProxyNegotiator); + s->pn.vt = vt; + return &s->pn; +} + +static void proxy_telnet_free(ProxyNegotiator *pn) +{ + TelnetProxyNegotiator *s = container_of(pn, TelnetProxyNegotiator, pn); + sfree(s); +} + +static void proxy_telnet_process_queue(ProxyNegotiator *pn) +{ + // TelnetProxyNegotiator *s = container_of(pn, TelnetProxyNegotiator, pn); + + char *formatted_cmd = format_telnet_command( + pn->ps->remote_addr, pn->ps->remote_port, pn->ps->conf); + + /* + * Re-escape control chars in the command, for logging. + */ + strbuf *logmsg = strbuf_new(); + const char *in; + + put_datapl(logmsg, PTRLEN_LITERAL("Sending Telnet proxy command: ")); + + for (in = formatted_cmd; *in; in++) { + if (*in == '\n') { + put_datapl(logmsg, PTRLEN_LITERAL("\\n")); + } else if (*in == '\r') { + put_datapl(logmsg, PTRLEN_LITERAL("\\r")); + } else if (*in == '\t') { + put_datapl(logmsg, PTRLEN_LITERAL("\\t")); + } else if (*in == '\\') { + put_datapl(logmsg, PTRLEN_LITERAL("\\\\")); + } else if (0x20 <= *in && *in < 0x7F) { + put_byte(logmsg, *in); + } else { + put_fmt(logmsg, "\\x%02X", (unsigned)*in & 0xFF); + } + } + + plug_log(pn->ps->plug, PLUGLOG_PROXY_MSG, NULL, 0, logmsg->s, 0); + strbuf_free(logmsg); + + put_dataz(pn->output, formatted_cmd); + sfree(formatted_cmd); + + /* + * Unconditionally report success. + */ + pn->done = true; +} + +const struct ProxyNegotiatorVT telnet_proxy_negotiator_vt = { + .new = proxy_telnet_new, + .free = proxy_telnet_free, + .process_queue = proxy_telnet_process_queue, + .type = "Telnet", +}; -- cgit v1.2.3 From 02aa5610dd16bbf688e0eb626e744bde061093a4 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 19 Nov 2021 11:05:14 +0000 Subject: Make ProxySocket an Interactor. This lays all the groundwork for ProxyNegotiators to be able to issue username and password prompts: ProxySocket now implements the Interactor trait, it will borrow and return a Seat if one is available, and it will present an Interactor of its own to the ProxyNegotiator which can use it (via interactor_announce as usual) to get a Seat to send prompts to. Also, proxy.c provides a centralised system for making a prompts_t with an appropriate callback in it, and dealing with the results of that callback. No actual ProxyNegotiator implementation uses it yet, though. --- proxy/proxy.c | 90 ++++++++++++++++++++++++++++++++++++++++++++++++++++++----- proxy/proxy.h | 16 +++++++++++ 2 files changed, 99 insertions(+), 7 deletions(-) diff --git a/proxy/proxy.c b/proxy/proxy.c index 249b39a4..efff853b 100644 --- a/proxy/proxy.c +++ b/proxy/proxy.c @@ -18,6 +18,19 @@ (conf_get_int(conf, CONF_proxy_dns) == AUTO && \ conf_get_int(conf, CONF_proxy_type) != PROXY_SOCKS4)) +static void proxy_negotiator_cleanup(ProxySocket *ps) +{ + if (ps->pn) { + proxy_negotiator_free(ps->pn); + ps->pn = NULL; + } + if (ps->clientseat) { + interactor_return_seat(ps->clientitr); + ps->clientitr = NULL; + ps->clientseat = NULL; + } +} + /* * Call this when proxy negotiation is complete, so that this * socket can begin working normally. @@ -26,9 +39,7 @@ void proxy_activate(ProxySocket *ps) { size_t output_before, output_after; - assert(ps->pn); - proxy_negotiator_free(ps->pn); - ps->pn = NULL; + proxy_negotiator_cleanup(ps); plug_log(ps->plug, PLUGLOG_CONNECT_SUCCESS, NULL, 0, NULL, 0); @@ -89,8 +100,7 @@ static void sk_proxy_close (Socket *s) sk_close(ps->sub_socket); sk_addr_free(ps->remote_addr); - if (ps->pn) - proxy_negotiator_free(ps->pn); + proxy_negotiator_cleanup(ps); bufchain_clear(&ps->output_from_negotiator); sfree(ps); } @@ -191,6 +201,7 @@ static void plug_proxy_closing(Plug *p, PlugCloseType type, { ProxySocket *ps = container_of(p, ProxySocket, plugimpl); + proxy_negotiator_cleanup(ps); plug_closing(ps->plug, type, error_msg); } @@ -203,10 +214,12 @@ static void proxy_negotiate(ProxySocket *ps) } else if (ps->pn->error) { char *err = dupprintf("Proxy error: %s", ps->pn->error); sfree(ps->pn->error); - proxy_negotiator_free(ps->pn); - ps->pn = NULL; + proxy_negotiator_cleanup(ps); plug_closing_error(ps->plug, err); sfree(err); + } else if (ps->pn->aborted) { + proxy_negotiator_cleanup(ps); + plug_closing_user_abort(ps->plug); } else { while (bufchain_size(&ps->output_from_negotiator)) { ptrlen data = bufchain_prefix(&ps->output_from_negotiator); @@ -400,6 +413,53 @@ static const PlugVtable ProxySocket_plugvt = { .accepting = plug_proxy_accepting }; +static char *proxy_description(Interactor *itr) +{ + ProxySocket *ps = container_of(itr, ProxySocket, interactor); + assert(ps->pn); + return dupprintf("%s connection to %s port %d", ps->pn->vt->type, + conf_get_str(ps->conf, CONF_proxy_host), + conf_get_int(ps->conf, CONF_proxy_port)); +} + +static LogPolicy *proxy_logpolicy(Interactor *itr) +{ + ProxySocket *ps = container_of(itr, ProxySocket, interactor); + return ps->clientlp; +} + +static Seat *proxy_get_seat(Interactor *itr) +{ + ProxySocket *ps = container_of(itr, ProxySocket, interactor); + return ps->clientseat; +} + +static void proxy_set_seat(Interactor *itr, Seat *seat) +{ + ProxySocket *ps = container_of(itr, ProxySocket, interactor); + ps->clientseat = seat; +} + +static const InteractorVtable ProxySocket_interactorvt = { + .description = proxy_description, + .logpolicy = proxy_logpolicy, + .get_seat = proxy_get_seat, + .set_seat = proxy_set_seat, +}; + +static void proxy_prompts_callback(void *ctx) +{ + proxy_negotiate((ProxySocket *)ctx); +} + +prompts_t *proxy_new_prompts(ProxySocket *ps) +{ + prompts_t *prs = new_prompts(); + prs->callback = proxy_prompts_callback; + prs->callback_ctx = ps; + return prs; +} + Socket *new_connection(SockAddr *addr, const char *hostname, int port, bool privport, bool oobinline, bool nodelay, bool keepalive, @@ -429,6 +489,7 @@ Socket *new_connection(SockAddr *addr, const char *hostname, ps = snew(ProxySocket); ps->sock.vt = &ProxySocket_sockvt; ps->plugimpl.vt = &ProxySocket_plugvt; + ps->interactor.vt = &ProxySocket_interactorvt; ps->conf = conf_copy(conf); ps->plug = plug; ps->remote_addr = addr; /* will need to be freed on close */ @@ -445,6 +506,17 @@ Socket *new_connection(SockAddr *addr, const char *hostname, ps->sub_socket = NULL; + /* + * If we've been given an Interactor by the caller, set ourselves + * up to work with it. + */ + if (itr) { + ps->clientitr = itr; + interactor_set_child(ps->clientitr, &ps->interactor); + ps->clientlp = interactor_logpolicy(ps->clientitr); + ps->clientseat = interactor_borrow_seat(ps->clientitr); + } + const ProxyNegotiatorVT *vt; switch (type) { case PROXY_HTTP: @@ -467,7 +539,11 @@ Socket *new_connection(SockAddr *addr, const char *hostname, ps->pn->ps = ps; ps->pn->done = false; ps->pn->error = NULL; + ps->pn->aborted = false; ps->pn->input = &ps->pending_input_data; + /* Provide an Interactor to the negotiator if and only if we + * are usefully able to ask interactive questions of the user */ + ps->pn->itr = ps->clientseat ? &ps->interactor : NULL; bufchain_sink_init(ps->pn->output, &ps->output_from_negotiator); { diff --git a/proxy/proxy.h b/proxy/proxy.h index 26ff20ef..f099541d 100644 --- a/proxy/proxy.h +++ b/proxy/proxy.h @@ -38,8 +38,14 @@ struct ProxySocket { /* configuration, used to look up proxy settings */ Conf *conf; + /* for interaction with the Seat */ + Interactor *clientitr; + LogPolicy *clientlp; + Seat *clientseat; + Socket sock; Plug plugimpl; + Interactor interactor; }; struct ProxyNegotiator { @@ -50,6 +56,7 @@ struct ProxyNegotiator { ProxySocket *ps; bufchain *input; bufchain_sink output[1]; + Interactor *itr; /* NULL if we are not able to interact with the user */ /* Set to report success during proxy negotiation. */ bool done; @@ -58,6 +65,9 @@ struct ProxyNegotiator { * ProxySocket will free it, and will then guarantee never to call * process_queue again. */ char *error; + + /* Set to report user abort during proxy negotiation. */ + bool aborted; }; struct ProxyNegotiatorVT { @@ -80,6 +90,12 @@ extern const ProxyNegotiatorVT socks4_proxy_negotiator_vt; extern const ProxyNegotiatorVT socks5_proxy_negotiator_vt; extern const ProxyNegotiatorVT telnet_proxy_negotiator_vt; +/* + * Centralised function to allow ProxyNegotiators to get hold of a + * prompts_t. + */ +prompts_t *proxy_new_prompts(ProxySocket *ps); + /* * This may be reused by local-command proxies on individual * platforms. -- cgit v1.2.3 From 6354dba6316e02add9d359e1bd1b12c93492a5b7 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 19 Nov 2021 14:33:47 +0000 Subject: Support interactive password prompts in SOCKS 5. This is the first of the ProxyNegotiator implementations to use the new interaction system. The other two both need more work than just inserting a prompt and using the result. --- proxy/socks5.c | 116 ++++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 90 insertions(+), 26 deletions(-) diff --git a/proxy/socks5.c b/proxy/socks5.c index 407e98ea..eb858b37 100644 --- a/proxy/socks5.c +++ b/proxy/socks5.c @@ -44,6 +44,9 @@ typedef struct Socks5ProxyNegotiator { unsigned n_chap_attrs; unsigned chap_attr, chap_attr_len; unsigned char chap_buf[256]; + strbuf *username, *password; + prompts_t *prompts; + int username_prompt_index, password_prompt_index; int response_addr_length; ProxyNegotiator pn; } Socks5ProxyNegotiator; @@ -54,6 +57,8 @@ static ProxyNegotiator *proxy_socks5_new(const ProxyNegotiatorVT *vt) memset(s, 0, sizeof(*s)); s->pn.vt = vt; s->auth_methods_offered = strbuf_new(); + s->username = strbuf_new(); + s->password = strbuf_new_nm(); return &s->pn; } @@ -61,6 +66,10 @@ static void proxy_socks5_free(ProxyNegotiator *pn) { Socks5ProxyNegotiator *s = container_of(pn, Socks5ProxyNegotiator, pn); strbuf_free(s->auth_methods_offered); + strbuf_free(s->username); + strbuf_free(s->password); + if (s->prompts) + free_prompts(s->prompts); smemclr(s, sizeof(s)); sfree(s); } @@ -83,17 +92,25 @@ static void proxy_socks5_process_queue(ProxyNegotiator *pn) strbuf_clear(s->auth_methods_offered); + /* + * We have two basic kinds of authentication to offer: none at + * all, and password-based systems (whether the password is sent + * in cleartext or proved via CHAP). + * + * We always offer 'none' as an option. We offer 'password' if we + * either have a username and password already from the Conf, or + * we have a Seat available to ask for them interactively. If + * neither, we don't offer those options in the first place. + */ put_byte(s->auth_methods_offered, SOCKS5_AUTH_NONE); - { - const char *username = conf_get_str(pn->ps->conf, CONF_proxy_username); - const char *password = conf_get_str(pn->ps->conf, CONF_proxy_password); - if (username[0] || password[0]) { - if (socks5_chap_available) - put_byte(s->auth_methods_offered, SOCKS5_AUTH_CHAP); + put_dataz(s->username, conf_get_str(pn->ps->conf, CONF_proxy_username)); + put_dataz(s->password, conf_get_str(pn->ps->conf, CONF_proxy_password)); + if (pn->itr || (s->username->len && s->password->len)) { + if (socks5_chap_available) + put_byte(s->auth_methods_offered, SOCKS5_AUTH_CHAP); - put_byte(s->auth_methods_offered, SOCKS5_AUTH_PASSWORD); - } + put_byte(s->auth_methods_offered, SOCKS5_AUTH_PASSWORD); } put_byte(pn->output, s->auth_methods_offered->len); @@ -144,16 +161,71 @@ static void proxy_socks5_process_queue(ProxyNegotiator *pn) } /* - * Process each auth method. Note we can't do this using the - * natural idiom of a switch statement, because there are - * crReturns inside some cases. + * The 'none' auth option requires no further negotiation. If that + * was the one we selected, go straight to making the connection. */ - if (s->auth_method == SOCKS5_AUTH_NONE) { - /* 'none' auth needs no further negotiation, so skip ahead - * to actually making the connection */ + if (s->auth_method == SOCKS5_AUTH_NONE) goto authenticated; + + /* + * Otherwise, we're going to need a username and password, so this + * is the moment to stop and ask for one if we don't already have + * them. + */ + if (pn->itr && (!s->username->len || !s->password->len)) { + s->prompts = proxy_new_prompts(pn->ps); + s->prompts->to_server = true; + s->prompts->from_server = false; + s->prompts->name = dupstr("SOCKS proxy authentication"); + if (!s->username->len) { + s->username_prompt_index = s->prompts->n_prompts; + add_prompt(s->prompts, dupstr("Proxy username: "), true); + } else { + s->username_prompt_index = -1; + } + if (!s->password->len) { + s->password_prompt_index = s->prompts->n_prompts; + add_prompt(s->prompts, dupstr("Proxy password: "), false); + } else { + s->password_prompt_index = -1; + } + + while (true) { + int prompt_result = seat_get_userpass_input( + interactor_announce(pn->itr), s->prompts); + if (prompt_result > 0) { + break; + } else if (prompt_result == 0) { + pn->aborted = true; + crStopV; + } + crReturnV; + } + + if (s->username_prompt_index != -1) { + strbuf_clear(s->username); + put_dataz(s->username, + prompt_get_result_ref( + s->prompts->prompts[s->username_prompt_index])); + } + + if (s->password_prompt_index != -1) { + strbuf_clear(s->password); + put_dataz(s->password, + prompt_get_result_ref( + s->prompts->prompts[s->password_prompt_index])); + } + + free_prompts(s->prompts); + s->prompts = NULL; } + /* + * Now process the different auth methods that will use that + * username and password. Note we can't do this using the natural + * idiom of a switch statement, because there are crReturns inside + * some cases. + */ if (s->auth_method == SOCKS5_AUTH_PASSWORD) { /* * SOCKS 5 password auth packet: @@ -162,17 +234,13 @@ static void proxy_socks5_process_queue(ProxyNegotiator *pn) * pstring username * pstring password */ - const char *username = conf_get_str( - pn->ps->conf, CONF_proxy_username); - const char *password = conf_get_str( - pn->ps->conf, CONF_proxy_password); put_byte(pn->output, SOCKS5_AUTH_PASSWORD_VERSION); - if (!put_pstring(pn->output, username)) { + if (!put_pstring(pn->output, s->username->s)) { pn->error = dupstr("SOCKS 5 authentication cannot support " "usernames longer than 255 chars"); crStopV; } - if (!put_pstring(pn->output, password)) { + if (!put_pstring(pn->output, s->password->s)) { pn->error = dupstr("SOCKS 5 authentication cannot support " "passwords longer than 255 chars"); crStopV; @@ -232,10 +300,8 @@ static void proxy_socks5_process_queue(ProxyNegotiator *pn) /* Second attribute: username */ { - const char *username = conf_get_str( - pn->ps->conf, CONF_proxy_username); put_byte(pn->output, SOCKS5_AUTH_CHAP_ATTR_USERNAME); - if (!put_pstring(pn->output, username)) { + if (!put_pstring(pn->output, s->username->s)) { pn->error = dupstr( "SOCKS 5 CHAP authentication cannot support " "usernames longer than 255 chars"); @@ -289,11 +355,9 @@ static void proxy_socks5_process_queue(ProxyNegotiator *pn) } } else if (s->chap_attr==SOCKS5_AUTH_CHAP_ATTR_CHALLENGE) { /* The CHAP challenge string. Send the response */ - const char *password = conf_get_str( - pn->ps->conf, CONF_proxy_password); strbuf *response = chap_response( make_ptrlen(s->chap_buf, s->chap_attr_len), - ptrlen_from_asciz(password)); + ptrlen_from_strbuf(s->password)); put_byte(pn->output, SOCKS5_AUTH_CHAP_VERSION); put_byte(pn->output, 1); /* number of attributes */ -- cgit v1.2.3 From d8ecba71b7b4186ab68bd1b5a9622a03d94a3d7b Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 19 Nov 2021 15:23:34 +0000 Subject: get_userpass_input: remove an obsolete FIXME. I just spotted this in passing, and it's out of date! The prompts_t system no longer works by consuming user input that had already been handed to a backend, and it now has a callback that it can use to proactively notify a backend (or other Interactor) that its prompts have been answered. So if we did ever want to use a separate GUI dialog box for prompts (as the comment suggested), then we do now have the means. --- putty.h | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/putty.h b/putty.h index 232bbe30..0753d7cf 100644 --- a/putty.h +++ b/putty.h @@ -1075,16 +1075,6 @@ struct SeatVtable { * remember your private key passphrase then perhaps you'd rather * fall back to password auth rather than aborting the whole * session.) - * - * (Also FIXME: currently, backends' only response to the 'try - * again later' is to try again when more input data becomes - * available, because they assume that a seat is returning that - * value because it's consuming keyboard input. But a seat that - * handled this function by putting up a dialog box might want to - * put it up non-modally, and therefore would want to proactively - * notify the backend to retry once the dialog went away. So if I - * ever do want to move password prompts into a dialog box, I'll - * want a backend method for sending that notification.) */ int (*get_userpass_input)(Seat *seat, prompts_t *p); -- cgit v1.2.3 From 445bcd7030c1c8a90528aaf3e269e8e7e8e1b8a3 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 19 Nov 2021 16:36:29 +0000 Subject: Send ProxyNegotiator output even when pn->done is set. This fixes the Telnet proxy, which was the only one of the proxy types I forgot to test when I pushed the previous patch series, and therefore, naturally, the one I left a bug in: if a ProxyNegotiator returns both some output to be transmitted _and_ the 'done' flag, we were forgetting to do anything with the former. So the proxy command was being carefully constructed by TelnetProxyNegotiator, and then promptly dropped on the floor by the owning ProxySocket. --- proxy/proxy.c | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/proxy/proxy.c b/proxy/proxy.c index efff853b..979f1468 100644 --- a/proxy/proxy.c +++ b/proxy/proxy.c @@ -209,24 +209,27 @@ static void proxy_negotiate(ProxySocket *ps) { assert(ps->pn); proxy_negotiator_process_queue(ps->pn); - if (ps->pn->done) { - proxy_activate(ps); - } else if (ps->pn->error) { + + if (ps->pn->error) { char *err = dupprintf("Proxy error: %s", ps->pn->error); sfree(ps->pn->error); proxy_negotiator_cleanup(ps); plug_closing_error(ps->plug, err); sfree(err); + return; } else if (ps->pn->aborted) { proxy_negotiator_cleanup(ps); plug_closing_user_abort(ps->plug); - } else { - while (bufchain_size(&ps->output_from_negotiator)) { - ptrlen data = bufchain_prefix(&ps->output_from_negotiator); - sk_write(ps->sub_socket, data.ptr, data.len); - bufchain_consume(&ps->output_from_negotiator, data.len); - } + return; + } + + while (bufchain_size(&ps->output_from_negotiator)) { + ptrlen data = bufchain_prefix(&ps->output_from_negotiator); + sk_write(ps->sub_socket, data.ptr, data.len); + bufchain_consume(&ps->output_from_negotiator, data.len); } + if (ps->pn->done) + proxy_activate(ps); } static void plug_proxy_receive( -- cgit v1.2.3 From a864f7bb57c18b80646ac95791e960c2810f5212 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 19 Nov 2021 16:03:22 +0000 Subject: Support interactive password prompts in Telnet proxy. The Telnet proxy system is not a proper network protocol - we have no reliable way to receive communication from the proxy telling us whether a password is even required. However, we _do_ know (a) whether the keywords '%user' or '%pass' appeared in the format string stored in the Conf, and (b) whether we actually had a username or a password to substitute into them. So that's how we know whether to ask for a username or a password: if the format string asks for them and the Conf doesn't provide them, we prompt for them at startup. This involved turning TelnetProxyNegotiator into a coroutine (matching all the other proxy types, but previously, it was the only one simple enough not to need to be one), so that it can wait until a response arrives to that prompt. (And also, as it turned out, so that it can wait until setup is finished before even presenting the prompt!) It also involves having format_telnet_command grow an extra output parameter, in the form of 'unsigned *flags', with which it can communicate back to the caller that a username or password was wanted but not found. The other clients of that function (the local proxy implementations) don't use those flags, but if necessary, they could. --- proxy/proxy.h | 5 +- proxy/telnet.c | 188 ++++++++++++++++++++++++++++++++++++++++++-------- unix/local-proxy.c | 4 +- windows/local-proxy.c | 2 +- 4 files changed, 167 insertions(+), 32 deletions(-) diff --git a/proxy/proxy.h b/proxy/proxy.h index f099541d..9268fcb4 100644 --- a/proxy/proxy.h +++ b/proxy/proxy.h @@ -100,7 +100,10 @@ prompts_t *proxy_new_prompts(ProxySocket *ps); * This may be reused by local-command proxies on individual * platforms. */ -char *format_telnet_command(SockAddr *addr, int port, Conf *conf); +#define TELNET_CMD_MISSING_USERNAME 0x0001 +#define TELNET_CMD_MISSING_PASSWORD 0x0002 +char *format_telnet_command(SockAddr *addr, int port, Conf *conf, + unsigned *flags_out); /* * These are implemented in cproxy.c or nocproxy.c, depending on diff --git a/proxy/telnet.c b/proxy/telnet.c index 91e6060c..ebba3948 100644 --- a/proxy/telnet.c +++ b/proxy/telnet.c @@ -10,12 +10,15 @@ #include "putty.h" #include "network.h" #include "proxy.h" +#include "sshcr.h" -char *format_telnet_command(SockAddr *addr, int port, Conf *conf) +char *format_telnet_command(SockAddr *addr, int port, Conf *conf, + unsigned *flags_out) { char *fmt = conf_get_str(conf, CONF_proxy_telnet_command); int so = 0, eo = 0; strbuf *buf = strbuf_new(); + unsigned flags = 0; /* we need to escape \\, \%, \r, \n, \t, \x??, \0???, * %%, %host, %port, %user, and %pass @@ -141,11 +144,15 @@ char *format_telnet_command(SockAddr *addr, int port, Conf *conf) const char *username = conf_get_str(conf, CONF_proxy_username); put_data(buf, username, strlen(username)); eo += 4; + if (!*username) + flags |= TELNET_CMD_MISSING_USERNAME; } else if (strnicmp(fmt + eo, "pass", 4) == 0) { const char *password = conf_get_str(conf, CONF_proxy_password); put_data(buf, password, strlen(password)); eo += 4; + if (!*password) + flags |= TELNET_CMD_MISSING_PASSWORD; } else if (strnicmp(fmt + eo, "proxyhost", 9) == 0) { const char *host = conf_get_str(conf, CONF_proxy_host); @@ -175,16 +182,24 @@ char *format_telnet_command(SockAddr *addr, int port, Conf *conf) put_data(buf, fmt + so, eo - so); } + if (flags_out) + *flags_out = flags; return strbuf_to_str(buf); } typedef struct TelnetProxyNegotiator { + int crLine; + Conf *conf; + char *formatted_cmd; + prompts_t *prompts; + int username_prompt_index, password_prompt_index; ProxyNegotiator pn; } TelnetProxyNegotiator; static ProxyNegotiator *proxy_telnet_new(const ProxyNegotiatorVT *vt) { TelnetProxyNegotiator *s = snew(TelnetProxyNegotiator); + memset(s, 0, sizeof(*s)); s->pn.vt = vt; return &s->pn; } @@ -192,50 +207,167 @@ static ProxyNegotiator *proxy_telnet_new(const ProxyNegotiatorVT *vt) static void proxy_telnet_free(ProxyNegotiator *pn) { TelnetProxyNegotiator *s = container_of(pn, TelnetProxyNegotiator, pn); + if (s->conf) + conf_free(s->conf); + if (s->prompts) + free_prompts(s->prompts); + burnstr(s->formatted_cmd); + delete_callbacks_for_context(s); sfree(s); } +static void proxy_telnet_process_queue_callback(void *vctx) +{ + TelnetProxyNegotiator *s = (TelnetProxyNegotiator *)vctx; + proxy_negotiator_process_queue(&s->pn); +} + static void proxy_telnet_process_queue(ProxyNegotiator *pn) { - // TelnetProxyNegotiator *s = container_of(pn, TelnetProxyNegotiator, pn); + TelnetProxyNegotiator *s = container_of(pn, TelnetProxyNegotiator, pn); - char *formatted_cmd = format_telnet_command( - pn->ps->remote_addr, pn->ps->remote_port, pn->ps->conf); + crBegin(s->crLine); + + s->conf = conf_copy(pn->ps->conf); /* - * Re-escape control chars in the command, for logging. + * Make an initial attempt to figure out the command we want, and + * see if it tried to include a username or password that we don't + * have. */ - strbuf *logmsg = strbuf_new(); - const char *in; - - put_datapl(logmsg, PTRLEN_LITERAL("Sending Telnet proxy command: ")); - - for (in = formatted_cmd; *in; in++) { - if (*in == '\n') { - put_datapl(logmsg, PTRLEN_LITERAL("\\n")); - } else if (*in == '\r') { - put_datapl(logmsg, PTRLEN_LITERAL("\\r")); - } else if (*in == '\t') { - put_datapl(logmsg, PTRLEN_LITERAL("\\t")); - } else if (*in == '\\') { - put_datapl(logmsg, PTRLEN_LITERAL("\\\\")); - } else if (0x20 <= *in && *in < 0x7F) { - put_byte(logmsg, *in); - } else { - put_fmt(logmsg, "\\x%02X", (unsigned)*in & 0xFF); + { + unsigned flags; + s->formatted_cmd = format_telnet_command( + pn->ps->remote_addr, pn->ps->remote_port, s->conf, &flags); + + if (pn->itr && (flags & (TELNET_CMD_MISSING_USERNAME | + TELNET_CMD_MISSING_PASSWORD))) { + burnstr(s->formatted_cmd); + s->formatted_cmd = NULL; + + /* + * We're missing at least one of the two parts, and we + * have an Interactor we can use to prompt for them, so + * try it. + */ + s->prompts = proxy_new_prompts(pn->ps); + s->prompts->to_server = true; + s->prompts->from_server = false; + s->prompts->name = dupstr("Telnet proxy authentication"); + if (flags & TELNET_CMD_MISSING_USERNAME) { + s->username_prompt_index = s->prompts->n_prompts; + add_prompt(s->prompts, dupstr("Proxy username: "), true); + } else { + s->username_prompt_index = -1; + } + if (flags & TELNET_CMD_MISSING_PASSWORD) { + s->password_prompt_index = s->prompts->n_prompts; + add_prompt(s->prompts, dupstr("Proxy password: "), false); + } else { + s->password_prompt_index = -1; + } + + /* + * This prompt is presented extremely early in PuTTY's + * setup. (Very promptly, you might say.) + * + * In particular, we can get here through a chain of + * synchronous calls from backend_init, which means (in + * GUI PuTTY) that the terminal we'll be sending this + * prompt to may not have its Ldisc set up yet (due to + * cyclic dependencies among all the things that have to + * be initialised). + * + * So we'll start by having ourself called back via a + * toplevel callback, to make sure we don't call + * seat_get_userpass_input until we've returned from + * backend_init and the frontend has finished getting + * everything ready. + */ + queue_toplevel_callback(proxy_telnet_process_queue_callback, s); + crReturnV; + + while (true) { + int prompt_result = seat_get_userpass_input( + interactor_announce(pn->itr), s->prompts); + if (prompt_result > 0) { + break; + } else if (prompt_result == 0) { + pn->aborted = true; + crStopV; + } + crReturnV; + } + + if (s->username_prompt_index != -1) { + conf_set_str( + s->conf, CONF_proxy_username, + prompt_get_result_ref( + s->prompts->prompts[s->username_prompt_index])); + } + + if (s->password_prompt_index != -1) { + conf_set_str( + s->conf, CONF_proxy_password, + prompt_get_result_ref( + s->prompts->prompts[s->password_prompt_index])); + } + + free_prompts(s->prompts); + s->prompts = NULL; } + + /* + * Now format the command a second time, with the results of + * those prompts written into s->conf. + */ + s->formatted_cmd = format_telnet_command( + pn->ps->remote_addr, pn->ps->remote_port, s->conf, NULL); } - plug_log(pn->ps->plug, PLUGLOG_PROXY_MSG, NULL, 0, logmsg->s, 0); - strbuf_free(logmsg); + /* + * Re-escape control chars in the command, for logging. + */ + { + strbuf *logmsg = strbuf_new(); + const char *in; + + put_datapl(logmsg, PTRLEN_LITERAL("Sending Telnet proxy command: ")); + + for (in = s->formatted_cmd; *in; in++) { + if (*in == '\n') { + put_datapl(logmsg, PTRLEN_LITERAL("\\n")); + } else if (*in == '\r') { + put_datapl(logmsg, PTRLEN_LITERAL("\\r")); + } else if (*in == '\t') { + put_datapl(logmsg, PTRLEN_LITERAL("\\t")); + } else if (*in == '\\') { + put_datapl(logmsg, PTRLEN_LITERAL("\\\\")); + } else if (0x20 <= *in && *in < 0x7F) { + put_byte(logmsg, *in); + } else { + put_fmt(logmsg, "\\x%02X", (unsigned)*in & 0xFF); + } + } + + plug_log(pn->ps->plug, PLUGLOG_PROXY_MSG, NULL, 0, logmsg->s, 0); + strbuf_free(logmsg); + } - put_dataz(pn->output, formatted_cmd); - sfree(formatted_cmd); + /* + * Actually send the command. + */ + put_dataz(pn->output, s->formatted_cmd); /* - * Unconditionally report success. + * Unconditionally report success. We don't hang around waiting + * for error messages from the proxy, because this proxy type is + * so ad-hoc that we wouldn't know how to even recognise an error + * message if we saw one, let alone what to do about it. */ pn->done = true; + + crFinishV; } const struct ProxyNegotiatorVT telnet_proxy_negotiator_vt = { diff --git a/unix/local-proxy.c b/unix/local-proxy.c index 583bb75a..f4c98ced 100644 --- a/unix/local-proxy.c +++ b/unix/local-proxy.c @@ -29,7 +29,7 @@ Socket *platform_new_connection(SockAddr *addr, const char *hostname, return NULL; if (proxytype == PROXY_CMD) { - cmd = format_telnet_command(addr, port, conf); + cmd = format_telnet_command(addr, port, conf, NULL); { char *logmsg = dupprintf("Starting local proxy command: %s", cmd); @@ -79,7 +79,7 @@ Socket *platform_new_connection(SockAddr *addr, const char *hostname, infd = from_cmd_pipe[0]; inerrfd = cmd_err_pipe[0]; } else { - cmd = format_telnet_command(addr, port, conf); + cmd = format_telnet_command(addr, port, conf, NULL); outfd = open("/dev/null", O_WRONLY); if (outfd == -1) { sfree(cmd); diff --git a/windows/local-proxy.c b/windows/local-proxy.c index 9672ddbe..19ceb726 100644 --- a/windows/local-proxy.c +++ b/windows/local-proxy.c @@ -28,7 +28,7 @@ Socket *platform_new_connection(SockAddr *addr, const char *hostname, if (conf_get_int(conf, CONF_proxy_type) != PROXY_CMD) return NULL; - cmd = format_telnet_command(addr, port, conf); + cmd = format_telnet_command(addr, port, conf, NULL); { char *msg = dupprintf("Starting local proxy command: %s", cmd); -- cgit v1.2.3 From dbaaa9d1dd0c1c24bfd138572812cfdd8218f078 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 19 Nov 2021 15:59:27 +0000 Subject: Telnet proxy: censor the password in the log file. Probably should have done this a long time ago: when we write the formatted command into the log file, we now base it on a version in which CONF_proxy_password has been reset to "*password*", to avoid writing the actual password (if any) into log files. --- proxy/telnet.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/proxy/telnet.c b/proxy/telnet.c index ebba3948..e0a54450 100644 --- a/proxy/telnet.c +++ b/proxy/telnet.c @@ -326,15 +326,21 @@ static void proxy_telnet_process_queue(ProxyNegotiator *pn) } /* - * Re-escape control chars in the command, for logging. + * Log the command, with some changes. Firstly, we regenerate it + * with the password masked; secondly, we escape control + * characters so that the log message is printable. */ + conf_set_str(s->conf, CONF_proxy_password, "*password*"); { + char *censored_cmd = format_telnet_command( + pn->ps->remote_addr, pn->ps->remote_port, s->conf, NULL); + strbuf *logmsg = strbuf_new(); const char *in; put_datapl(logmsg, PTRLEN_LITERAL("Sending Telnet proxy command: ")); - for (in = s->formatted_cmd; *in; in++) { + for (in = censored_cmd; *in; in++) { if (*in == '\n') { put_datapl(logmsg, PTRLEN_LITERAL("\\n")); } else if (*in == '\r') { @@ -352,6 +358,7 @@ static void proxy_telnet_process_queue(ProxyNegotiator *pn) plug_log(pn->ps->plug, PLUGLOG_PROXY_MSG, NULL, 0, logmsg->s, 0); strbuf_free(logmsg); + sfree(censored_cmd); } /* -- cgit v1.2.3 From 9a0b1fa3f6b8d850b7a603dca081ded31de2ad2f Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 19 Nov 2021 18:25:04 +0000 Subject: Support interactive password prompts in HTTP proxy. In HTTP proxying, we can (and do) send the username and password immediately in the form of HTTP Basic, if we have them in the Conf. But if they get rejected, or if we never sent them in the first place and the server won't let us in without auth, then we get back an HTTP 407 response with a full set of headers and an error-document. Assuming the HTTP connection doesn't close after that (which in sensible HTTP/1.1 proxies it won't), this gives us the opportunity to respond by sending a second CONNECT request, containing a fresh username and password we just requested interactively from the user. --- proxy/http.c | 352 ++++++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 288 insertions(+), 64 deletions(-) diff --git a/proxy/http.c b/proxy/http.c index d67f1be9..8cd043d4 100644 --- a/proxy/http.c +++ b/proxy/http.c @@ -43,26 +43,112 @@ static bool read_line(bufchain *input, strbuf *output, bool is_header) typedef struct HttpProxyNegotiator { int crLine; - strbuf *line; + strbuf *response, *header, *token; + int http_status_pos; + size_t header_pos; + strbuf *username, *password; + int http_status; + bool connection_close; + prompts_t *prompts; + int username_prompt_index, password_prompt_index; + size_t content_length; ProxyNegotiator pn; } HttpProxyNegotiator; static ProxyNegotiator *proxy_http_new(const ProxyNegotiatorVT *vt) { HttpProxyNegotiator *s = snew(HttpProxyNegotiator); + memset(s, 0, sizeof(*s)); s->pn.vt = vt; - s->crLine = 0; - s->line = strbuf_new(); + s->response = strbuf_new(); + s->header = strbuf_new(); + s->token = strbuf_new(); + s->username = strbuf_new(); + s->password = strbuf_new_nm(); return &s->pn; } static void proxy_http_free(ProxyNegotiator *pn) { HttpProxyNegotiator *s = container_of(pn, HttpProxyNegotiator, pn); - strbuf_free(s->line); + strbuf_free(s->response); + strbuf_free(s->header); + strbuf_free(s->token); + strbuf_free(s->username); + strbuf_free(s->password); + if (s->prompts) + free_prompts(s->prompts); sfree(s); } +#define HTTP_HEADER_LIST(X) \ + X(HDR_CONNECTION, "Connection") \ + X(HDR_CONTENT_LENGTH, "Content-Length") \ + X(HDR_PROXY_AUTHENTICATE, "Proxy-Authenticate") \ + /* end of list */ + +typedef enum HttpHeader { + #define ENUM_DEF(id, string) id, + HTTP_HEADER_LIST(ENUM_DEF) + #undef ENUM_DEF + HDR_UNKNOWN +} HttpHeader; + +static inline bool is_whitespace(char c) +{ + return (c == ' ' || c == '\t' || c == '\n'); +} + +static inline bool is_separator(char c) +{ + return (c == '(' || c == ')' || c == '<' || c == '>' || c == '@' || + c == ',' || c == ';' || c == ':' || c == '\\' || c == '"' || + c == '/' || c == '[' || c == ']' || c == '?' || c == '=' || + c == '{' || c == '}'); +} + +#define HTTP_SEPARATORS + +static bool get_token(HttpProxyNegotiator *s) +{ + size_t pos = s->header_pos; + + while (pos < s->header->len && is_whitespace(s->header->s[pos])) + pos++; + + if (pos == s->header->len) + return false; /* end of string */ + + if (is_separator(s->header->s[pos])) + return false; + + strbuf_clear(s->token); + while (pos < s->header->len && + !is_whitespace(s->header->s[pos]) && + !is_separator(s->header->s[pos])) + put_byte(s->token, s->header->s[pos++]); + + s->header_pos = pos; + return true; +} + +static bool get_separator(HttpProxyNegotiator *s, char sep) +{ + size_t pos = s->header_pos; + + while (pos < s->header->len && is_whitespace(s->header->s[pos])) + pos++; + + if (pos == s->header->len) + return false; /* end of string */ + + if (s->header->s[pos] != sep) + return false; + + s->header_pos = ++pos; + return true; +} + static void proxy_http_process_queue(ProxyNegotiator *pn) { HttpProxyNegotiator *s = container_of(pn, HttpProxyNegotiator, pn); @@ -70,78 +156,216 @@ static void proxy_http_process_queue(ProxyNegotiator *pn) crBegin(s->crLine); /* - * Standard prefix for the HTTP CONNECT request. + * Initialise our username and password strbufs from the Conf. */ - { - char dest[512]; - sk_getaddr(pn->ps->remote_addr, dest, lenof(dest)); - put_fmt(pn->output, - "CONNECT %s:%d HTTP/1.1\r\n" - "Host: %s:%d\r\n", - dest, pn->ps->remote_port, dest, pn->ps->remote_port); - } + put_dataz(s->username, conf_get_str(pn->ps->conf, CONF_proxy_username)); + put_dataz(s->password, conf_get_str(pn->ps->conf, CONF_proxy_password)); - /* - * Optionally send an HTTP Basic auth header with the username and - * password. - */ - { - const char *username = conf_get_str(pn->ps->conf, CONF_proxy_username); - const char *password = conf_get_str(pn->ps->conf, CONF_proxy_password); - if (username[0] || password[0]) { - put_datalit(pn->output, "Proxy-Authorization: Basic "); - - char *base64_input = dupcat(username, ":", password); - char base64_output[4]; - for (size_t i = 0, e = strlen(base64_input); i < e; i += 3) { - base64_encode_atom((const unsigned char *)base64_input + i, - e-i > 3 ? 3 : e-i, base64_output); - put_data(pn->output, base64_output, 4); + while (true) { + /* + * Standard prefix for the HTTP CONNECT request. + */ + { + char dest[512]; + sk_getaddr(pn->ps->remote_addr, dest, lenof(dest)); + put_fmt(pn->output, + "CONNECT %s:%d HTTP/1.1\r\n" + "Host: %s:%d\r\n", + dest, pn->ps->remote_port, dest, pn->ps->remote_port); + } + + /* + * Optionally send an HTTP Basic auth header with the username and + * password. + */ + { + if (s->username->len || s->password->len) { + put_datalit(pn->output, "Proxy-Authorization: Basic "); + + strbuf *base64_input = strbuf_new_nm(); + put_datapl(base64_input, ptrlen_from_strbuf(s->username)); + put_byte(base64_input, ':'); + put_datapl(base64_input, ptrlen_from_strbuf(s->password)); + + char base64_output[4]; + for (size_t i = 0, e = base64_input->len; i < e; i += 3) { + base64_encode_atom(base64_input->u + i, + e-i > 3 ? 3 : e-i, base64_output); + put_data(pn->output, base64_output, 4); + } + strbuf_free(base64_input); + smemclr(base64_output, sizeof(base64_output)); + put_datalit(pn->output, "\r\n"); } - burnstr(base64_input); - smemclr(base64_output, sizeof(base64_output)); - put_datalit(pn->output, "\r\n"); } - } - /* - * Blank line to terminate the HTTP request. - */ - put_datalit(pn->output, "\r\n"); - crReturnV; + /* + * Blank line to terminate the HTTP request. + */ + put_datalit(pn->output, "\r\n"); + crReturnV; - /* - * Read and parse the HTTP status line, and check if it's a 2xx - * for success. - */ - strbuf_clear(s->line); - crMaybeWaitUntilV(read_line(pn->input, s->line, false)); - { - int maj_ver, min_ver, status_pos = -1; - sscanf(s->line->s, "HTTP/%d.%d %n", &maj_ver, &min_ver, &status_pos); - - /* If status_pos is still -1 then the sscanf didn't get right - * to the end of the string */ - if (status_pos == -1) { - pn->error = dupstr("HTTP response was absent or malformed"); - crStopV; + s->content_length = 0; + s->connection_close = false; + + /* + * Read and parse the HTTP status line, and check if it's a 2xx + * for success. + */ + strbuf_clear(s->response); + crMaybeWaitUntilV(read_line(pn->input, s->response, false)); + { + int maj_ver, min_ver, n_scanned; + n_scanned = sscanf( + s->response->s, "HTTP/%d.%d %n%d", + &maj_ver, &min_ver, &s->http_status_pos, &s->http_status); + + if (n_scanned < 3) { + pn->error = dupstr("HTTP response was absent or malformed"); + crStopV; + } + + if (maj_ver < 1 && (maj_ver == 1 && min_ver < 1)) { + /* Before HTTP/1.1, connections close by default */ + s->connection_close = true; + } } - if (s->line->s[status_pos] != '2') { - pn->error = dupprintf("HTTP response %s", s->line->s + status_pos); + /* + * Read the HTTP response header section. + */ + do { + strbuf_clear(s->header); + crMaybeWaitUntilV(read_line(pn->input, s->header, true)); + s->header_pos = 0; + + if (!get_token(s)) { + /* Possibly we ought to panic if we see an HTTP header + * we can't make any sense of at all? But whatever, + * ignore it and hope the next one makes more sense */ + continue; + } + + /* Parse the header name */ + HttpHeader hdr = HDR_UNKNOWN; + { + #define CHECK_HEADER(id, string) \ + if (!stricmp(s->token->s, string)) hdr = id; + HTTP_HEADER_LIST(CHECK_HEADER); + #undef CHECK_HEADER + } + + if (!get_separator(s, ':')) + continue; + + if (hdr == HDR_CONTENT_LENGTH) { + if (!get_token(s)) + continue; + s->content_length = strtoumax(s->token->s, NULL, 10); + } else if (hdr == HDR_CONNECTION) { + if (!get_token(s)) + continue; + if (!stricmp(s->token->s, "close")) + s->connection_close = true; + else if (!stricmp(s->token->s, "keep-alive")) + s->connection_close = false; + } else if (hdr == HDR_PROXY_AUTHENTICATE) { + if (!get_token(s)) + continue; + + if (!stricmp(s->token->s, "Basic")) { + /* fine, we know how to do Basic auth */ + } else { + pn->error = dupprintf("HTTP proxy asked for unsupported " + "authentication type '%s'", + s->token->s); + crStopV; + } + } + } while (s->header->len > 0); + + /* Read and ignore the entire response document */ + crMaybeWaitUntilV(bufchain_try_consume( + pn->input, s->content_length)); + + if (200 <= s->http_status && s->http_status < 300) { + /* Any 2xx HTTP response means we're done */ + goto authenticated; + } else if (s->http_status == 407) { + /* 407 is Proxy Authentication Required, which we may be + * able to do something about. */ + if (s->connection_close) { + pn->error = dupprintf("HTTP proxy closed connection after " + "asking for authentication"); + crStopV; + } + + /* Either we never had a password in the first place, or + * the one we already presented was rejected. We can only + * proceed from here if we have a way to ask the user + * questions. */ + if (!pn->itr) { + pn->error = dupprintf("HTTP proxy requested authentication " + "which we do not have"); + crStopV; + } + + /* + * Send some prompts to the user. We'll assume the + * password is always required (since it's just been + * rejected, even if we did send one before), and we'll + * prompt for the username only if we don't have one from + * the Conf. + */ + s->prompts = proxy_new_prompts(pn->ps); + s->prompts->to_server = true; + s->prompts->from_server = false; + s->prompts->name = dupstr("HTTP proxy authentication"); + if (!s->username->len) { + s->username_prompt_index = s->prompts->n_prompts; + add_prompt(s->prompts, dupstr("Proxy username: "), true); + } else { + s->username_prompt_index = -1; + } + + s->password_prompt_index = s->prompts->n_prompts; + add_prompt(s->prompts, dupstr("Proxy password: "), false); + + while (true) { + int prompt_result = seat_get_userpass_input( + interactor_announce(pn->itr), s->prompts); + if (prompt_result > 0) { + break; + } else if (prompt_result == 0) { + pn->aborted = true; + crStopV; + } + crReturnV; + } + + if (s->username_prompt_index != -1) { + strbuf_clear(s->username); + put_dataz(s->username, + prompt_get_result_ref( + s->prompts->prompts[s->username_prompt_index])); + } + + strbuf_clear(s->password); + put_dataz(s->password, + prompt_get_result_ref( + s->prompts->prompts[s->password_prompt_index])); + + free_prompts(s->prompts); + s->prompts = NULL; + } else { + /* Any other HTTP response is treated as permanent failure */ + pn->error = dupprintf("HTTP response %s", + s->response->s + s->http_status_pos); crStopV; } } - /* - * Read and skip the rest of the HTTP response headers, terminated - * by a blank line. - */ - do { - strbuf_clear(s->line); - crMaybeWaitUntilV(read_line(pn->input, s->line, true)); - } while (s->line->len > 0); - + authenticated: /* * Success! Hand over to the main connection. */ -- cgit v1.2.3 From c9e10b316a5d63058cef48cf0bef17582d0a3f4a Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 20 Nov 2021 10:47:11 +0000 Subject: HTTP proxy: don't eagerly send a Basic auth header. Now, we always try an initial CONNECT request with no auth at all, and wait for the proxy to reject it before sending a second try with auth. That way, we can wait to see what _kind_ of authentication the proxy requests, which will enable us to support something more secure than Basic, such as HTTP Digest. (I mean, it would _work_ to try Basic in request #1 and then retrying with Digest in #2 when the proxy asks for it. But if the aim of using Digest is to avoid sending the password in cleartext, it defeats the entire purpose to have sent it in cleartext anyway by the time you realise the server is prepared to do something better!) --- proxy/http.c | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/proxy/http.c b/proxy/http.c index 8cd043d4..4f184dbe 100644 --- a/proxy/http.c +++ b/proxy/http.c @@ -49,6 +49,7 @@ typedef struct HttpProxyNegotiator { strbuf *username, *password; int http_status; bool connection_close; + bool tried_no_auth, try_auth_from_conf; prompts_t *prompts; int username_prompt_index, password_prompt_index; size_t content_length; @@ -160,6 +161,8 @@ static void proxy_http_process_queue(ProxyNegotiator *pn) */ put_dataz(s->username, conf_get_str(pn->ps->conf, CONF_proxy_username)); put_dataz(s->password, conf_get_str(pn->ps->conf, CONF_proxy_password)); + if (s->username->len || s->password->len) + s->try_auth_from_conf = true; while (true) { /* @@ -175,10 +178,12 @@ static void proxy_http_process_queue(ProxyNegotiator *pn) } /* - * Optionally send an HTTP Basic auth header with the username and - * password. + * Optionally send an HTTP Basic auth header with the username + * and password. We do this only after we've first tried no + * authentication at all (even if we have a password to start + * with). */ - { + if (s->tried_no_auth) { if (s->username->len || s->password->len) { put_datalit(pn->output, "Proxy-Authorization: Basic "); @@ -197,6 +202,8 @@ static void proxy_http_process_queue(ProxyNegotiator *pn) smemclr(base64_output, sizeof(base64_output)); put_datalit(pn->output, "\r\n"); } + } else { + s->tried_no_auth = true; } /* @@ -300,6 +307,13 @@ static void proxy_http_process_queue(ProxyNegotiator *pn) crStopV; } + /* If we have auth details from the Conf and haven't tried + * them yet, that's our first step. */ + if (s->try_auth_from_conf) { + s->try_auth_from_conf = false; + continue; + } + /* Either we never had a password in the first place, or * the one we already presented was rejected. We can only * proceed from here if we have a way to ask the user -- cgit v1.2.3 From 52ee636b092c199afd1df9442bb334b216ea106b Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 20 Nov 2021 14:28:36 +0000 Subject: Extend testcrypt macros up to 12-ary functions. I'm about to add a monster new function that takes 12 arguments. --- testcrypt.c | 83 ++++++++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 74 insertions(+), 9 deletions(-) diff --git a/testcrypt.c b/testcrypt.c index 2376fa04..24ec2a5b 100644 --- a/testcrypt.c +++ b/testcrypt.c @@ -1488,8 +1488,67 @@ typedef FingerprintType TD_fptype; arg6, arg7, arg8, arg9)); \ } +#define FUNC10(rettype, function, type1, type2, type3, type4, type5, \ + type6, type7, type8, type9, type10) \ + static void handle_##function(BinarySource *in, strbuf *out) { \ + TD_##type1 arg1 = get_##type1(in); \ + TD_##type2 arg2 = get_##type2(in); \ + TD_##type3 arg3 = get_##type3(in); \ + TD_##type4 arg4 = get_##type4(in); \ + TD_##type5 arg5 = get_##type5(in); \ + TD_##type6 arg6 = get_##type6(in); \ + TD_##type7 arg7 = get_##type7(in); \ + TD_##type8 arg8 = get_##type8(in); \ + TD_##type9 arg9 = get_##type9(in); \ + TD_##type10 arg10 = get_##type10(in); \ + return_##rettype(out, function(arg1, arg2, arg3, arg4, arg5, \ + arg6, arg7, arg8, arg9, arg10)); \ + } + +#define FUNC11(rettype, function, type1, type2, type3, type4, type5, \ + type6, type7, type8, type9, type10, type11) \ + static void handle_##function(BinarySource *in, strbuf *out) { \ + TD_##type1 arg1 = get_##type1(in); \ + TD_##type2 arg2 = get_##type2(in); \ + TD_##type3 arg3 = get_##type3(in); \ + TD_##type4 arg4 = get_##type4(in); \ + TD_##type5 arg5 = get_##type5(in); \ + TD_##type6 arg6 = get_##type6(in); \ + TD_##type7 arg7 = get_##type7(in); \ + TD_##type8 arg8 = get_##type8(in); \ + TD_##type9 arg9 = get_##type9(in); \ + TD_##type10 arg10 = get_##type10(in); \ + TD_##type11 arg11 = get_##type11(in); \ + return_##rettype(out, function(arg1, arg2, arg3, arg4, arg5, \ + arg6, arg7, arg8, arg9, arg10, \ + arg11)); \ + } + +#define FUNC12(rettype, function, type1, type2, type3, type4, type5, \ + type6, type7, type8, type9, type10, type11, type12) \ + static void handle_##function(BinarySource *in, strbuf *out) { \ + TD_##type1 arg1 = get_##type1(in); \ + TD_##type2 arg2 = get_##type2(in); \ + TD_##type3 arg3 = get_##type3(in); \ + TD_##type4 arg4 = get_##type4(in); \ + TD_##type5 arg5 = get_##type5(in); \ + TD_##type6 arg6 = get_##type6(in); \ + TD_##type7 arg7 = get_##type7(in); \ + TD_##type8 arg8 = get_##type8(in); \ + TD_##type9 arg9 = get_##type9(in); \ + TD_##type10 arg10 = get_##type10(in); \ + TD_##type11 arg11 = get_##type11(in); \ + TD_##type12 arg12 = get_##type12(in); \ + return_##rettype(out, function(arg1, arg2, arg3, arg4, arg5, \ + arg6, arg7, arg8, arg9, arg10, \ + arg11, arg12)); \ + } + #include "testcrypt.h" +#undef FUNC12 +#undef FUNC11 +#undef FUNC10 #undef FUNC9 #undef FUNC8 #undef FUNC7 @@ -1522,18 +1581,24 @@ static void process_line(BinarySource *in, strbuf *out) #undef DISPATCH_COMMAND #define FUNC0(ret,fn) DISPATCH_INTERNAL(#fn,handle_##fn); -#define FUNC1(ret,fn,x) DISPATCH_INTERNAL(#fn,handle_##fn); -#define FUNC2(ret,fn,x,y) DISPATCH_INTERNAL(#fn,handle_##fn); -#define FUNC3(ret,fn,x,y,z) DISPATCH_INTERNAL(#fn,handle_##fn); -#define FUNC4(ret,fn,x,y,z,v) DISPATCH_INTERNAL(#fn,handle_##fn); -#define FUNC5(ret,fn,x,y,z,v,w) DISPATCH_INTERNAL(#fn,handle_##fn); -#define FUNC6(ret,fn,x,y,z,v,w,u) DISPATCH_INTERNAL(#fn,handle_##fn); -#define FUNC7(ret,fn,x,y,z,v,w,u,t) DISPATCH_INTERNAL(#fn,handle_##fn); -#define FUNC8(ret,fn,x,y,z,v,w,u,t,s) DISPATCH_INTERNAL(#fn,handle_##fn); -#define FUNC9(ret,fn,x,y,z,v,w,u,t,s,r) DISPATCH_INTERNAL(#fn,handle_##fn); +#define FUNC1(ret,fn,...) DISPATCH_INTERNAL(#fn,handle_##fn); +#define FUNC2(ret,fn,...) DISPATCH_INTERNAL(#fn,handle_##fn); +#define FUNC3(ret,fn,...) DISPATCH_INTERNAL(#fn,handle_##fn); +#define FUNC4(ret,fn,...) DISPATCH_INTERNAL(#fn,handle_##fn); +#define FUNC5(ret,fn,...) DISPATCH_INTERNAL(#fn,handle_##fn); +#define FUNC6(ret,fn,...) DISPATCH_INTERNAL(#fn,handle_##fn); +#define FUNC7(ret,fn,...) DISPATCH_INTERNAL(#fn,handle_##fn); +#define FUNC8(ret,fn,...) DISPATCH_INTERNAL(#fn,handle_##fn); +#define FUNC9(ret,fn,...) DISPATCH_INTERNAL(#fn,handle_##fn); +#define FUNC10(ret,fn,...) DISPATCH_INTERNAL(#fn,handle_##fn); +#define FUNC11(ret,fn,...) DISPATCH_INTERNAL(#fn,handle_##fn); +#define FUNC12(ret,fn,...) DISPATCH_INTERNAL(#fn,handle_##fn); #include "testcrypt.h" +#undef FUNC12 +#undef FUNC11 +#undef FUNC10 #undef FUNC9 #undef FUNC8 #undef FUNC7 -- cgit v1.2.3 From 3c21fa54c5927c17b5abb248ec1ce5801fded9bf Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 20 Nov 2021 14:56:32 +0000 Subject: HTTP proxy: implement Digest authentication. In http.c, this drops in reasonably neatly alongside the existing support for Basic, now that we're waiting for an initial 407 response from the proxy to tell us which auth mechanism it would prefer to use. The rest of this patch is mostly contriving to add testcrypt support for the function in cproxy.c that generates the complicated output header to go in the HTTP request: you need about a dozen assorted parameters, the actual response hash has two more hashes in its preimage, and there's even an option to hash the username as well if necessary. Much more complicated than CHAP (which is just plain HMAC-MD5), so it needs testing! Happily, RFC 7616 comes with some reasonably useful test cases, and I've managed to transcribe them directly into cryptsuite.py and demonstrate that my response-generator agrees with them. End-to-end testing of the whole system was done against Squid 4.13 (specifically, the squid package in Debian bullseye, version 4.13-10). --- proxy/cproxy.c | 156 +++++++++++++++++++++++++++++++ proxy/cproxy.h | 25 +++++ proxy/http.c | 268 ++++++++++++++++++++++++++++++++++++++++++++++------- proxy/nocproxy.c | 11 +++ proxy/proxy.h | 7 +- test/cryptsuite.py | 63 +++++++++++++ test/testcrypt.py | 2 +- testcrypt.c | 21 +++++ testcrypt.h | 1 + 9 files changed, 513 insertions(+), 41 deletions(-) create mode 100644 proxy/cproxy.h diff --git a/proxy/cproxy.c b/proxy/cproxy.c index fab5f63c..29eafdfc 100644 --- a/proxy/cproxy.c +++ b/proxy/cproxy.c @@ -16,6 +16,7 @@ #include "marshal.h" const bool socks5_chap_available = true; +const bool http_digest_available = true; strbuf *chap_response(ptrlen challenge, ptrlen password) { @@ -24,3 +25,158 @@ strbuf *chap_response(ptrlen challenge, ptrlen password) mac_simple(alg, password, challenge, strbuf_append(sb, alg->len)); return sb; } + +void BinarySink_put_hex_data(BinarySink *bs, const void *vptr, size_t len) +{ + const unsigned char *p = (const unsigned char *)vptr; + const char *hexdigits = "0123456789abcdef"; + while (len-- > 0) { + unsigned c = *p++; + put_byte(bs, hexdigits[0xF & (c >> 4)]); + put_byte(bs, hexdigits[0xF & (c )]); + } +} + +#define put_hex_data(bs, p, len) \ + BinarySink_put_hex_data(BinarySink_UPCAST(bs), p, len) + +const char *const httphashnames[] = { + #define DECL_ARRAY(id, str, alg, bits) str, + HTTP_DIGEST_HASHES(DECL_ARRAY) + #undef DECL_ARRAY +}; + +static const ssh_hashalg *const httphashalgs[] = { + #define DECL_ARRAY(id, str, alg, bits) alg, + HTTP_DIGEST_HASHES(DECL_ARRAY) + #undef DECL_ARRAY +}; +static const size_t httphashlengths[] = { + #define DECL_ARRAY(id, str, alg, bits) bits/8, + HTTP_DIGEST_HASHES(DECL_ARRAY) + #undef DECL_ARRAY +}; + +void http_digest_response(BinarySink *bs, ptrlen username, ptrlen password, + ptrlen realm, ptrlen method, ptrlen uri, ptrlen qop, + ptrlen nonce, ptrlen opaque, uint32_t nonce_count, + HttpDigestHash hash, bool hash_username) +{ + unsigned char a1hash[MAX_HASH_LEN]; + unsigned char a2hash[MAX_HASH_LEN]; + unsigned char rsphash[MAX_HASH_LEN]; + const ssh_hashalg *alg = httphashalgs[hash]; + size_t hashlen = httphashlengths[hash]; + + unsigned char ncbuf[4]; + PUT_32BIT_MSB_FIRST(ncbuf, nonce_count); + + unsigned char client_nonce_raw[33]; + random_read(client_nonce_raw, lenof(client_nonce_raw)); + char client_nonce_base64[lenof(client_nonce_raw) / 3 * 4]; + for (unsigned i = 0; i < lenof(client_nonce_raw)/3; i++) + base64_encode_atom(client_nonce_raw + 3*i, 3, + client_nonce_base64 + 4*i); + + /* + * RFC 7616 section 3.4.2: the hash "A1" is a hash of + * username:realm:password (in the absence of hash names like + * "MD5-sess" which as far as I know don't sensibly apply to + * proxies and HTTP CONNECT). + */ + ssh_hash *h = ssh_hash_new(alg); + put_datapl(h, username); + put_byte(h, ':'); + put_datapl(h, realm); + put_byte(h, ':'); + put_datapl(h, password); + ssh_hash_digest_nondestructive(h, a1hash); + + /* + * RFC 7616 section 3.4.3: the hash "A2" is a hash of method:uri + * (in the absence of more interesting quality-of-protection + * schemes than plain "auth" - e.g. "auth-int" hashes the entire + * document as well - which again I don't think make sense in the + * context of proxies and CONNECT). + */ + ssh_hash_reset(h); + put_datapl(h, method); + put_byte(h, ':'); + put_datapl(h, uri); + ssh_hash_digest_nondestructive(h, a2hash); + + /* + * RFC 7616 section 3.4.1: the overall output hash in the + * "response" parameter of the authorization header is a hash of + * A1:nonce:nonce-count:client-nonce:qop:A2, where A1 and A2 are + * the hashes computed above. + */ + ssh_hash_reset(h); + put_hex_data(h, a1hash, hashlen); + put_byte(h, ':'); + put_datapl(h, nonce); + put_byte(h, ':'); + put_hex_data(h, ncbuf, 4); + put_byte(h, ':'); + put_data(h, client_nonce_base64, lenof(client_nonce_base64)); + put_byte(h, ':'); + put_datapl(h, qop); + put_byte(h, ':'); + put_hex_data(h, a2hash, hashlen); + ssh_hash_final(h, rsphash); + + /* + * Now construct the output header (everything after the initial + * "Proxy-Authorization: Digest ") and write it to the provided + * BinarySink. + */ + put_datalit(bs, "username=\""); + if (hash_username) { + /* + * RFC 7616 section 3.4.4: if we're hashing the username, we + * actually hash username:realm (like a truncated version of + * A1 above). + */ + ssh_hash *h = ssh_hash_new(alg); + put_datapl(h, username); + put_byte(h, ':'); + put_datapl(h, realm); + ssh_hash_final(h, a1hash); + put_hex_data(bs, a1hash, hashlen); + } else { + put_datapl(bs, username); + } + put_datalit(bs, "\", realm=\""); + put_datapl(bs, realm); + put_datalit(bs, "\", uri=\""); + put_datapl(bs, uri); + put_datalit(bs, "\", algorithm="); + put_dataz(bs, httphashnames[hash]); + put_datalit(bs, ", nonce=\""); + put_datapl(bs, nonce); + put_datalit(bs, "\", nc="); + put_hex_data(bs, ncbuf, 4); + put_datalit(bs, ", cnonce=\""); + put_data(bs, client_nonce_base64, lenof(client_nonce_base64)); + put_datalit(bs, "\", qop="); + put_datapl(bs, qop); + put_datalit(bs, ", response=\""); + put_hex_data(bs, rsphash, hashlen); + put_datalit(bs, "\""); + + if (opaque.ptr) { + put_datalit(bs, ", opaque=\""); + put_datapl(bs, opaque); + put_datalit(bs, "\""); + } + + if (hash_username) { + put_datalit(bs, ", userhash=true"); + } + + smemclr(a1hash, lenof(a1hash)); + smemclr(a2hash, lenof(a2hash)); + smemclr(rsphash, lenof(rsphash)); + smemclr(client_nonce_raw, lenof(client_nonce_raw)); + smemclr(client_nonce_base64, lenof(client_nonce_base64)); +} diff --git a/proxy/cproxy.h b/proxy/cproxy.h new file mode 100644 index 00000000..c3e7ba15 --- /dev/null +++ b/proxy/cproxy.h @@ -0,0 +1,25 @@ +/* + * Header for the interaction between proxy.c and cproxy.c. Separated + * from proxy.h proper so that testcrypt can include it conveniently. + */ + +extern const bool socks5_chap_available; +strbuf *chap_response(ptrlen challenge, ptrlen password); +extern const bool http_digest_available; + +#define HTTP_DIGEST_HASHES(X) \ + X(HTTP_DIGEST_MD5, "MD5", &ssh_md5, 128) \ + X(HTTP_DIGEST_SHA256, "SHA-256", &ssh_sha256, 256) \ + X(HTTP_DIGEST_SHA512_256, "SHA-512-256", &ssh_sha512, 256) \ + /* end of list */ +typedef enum HttpDigestHash { + #define DECL_ENUM(id, str, alg, bits) id, + HTTP_DIGEST_HASHES(DECL_ENUM) + #undef DECL_ENUM + N_HTTP_DIGEST_HASHES +} HttpDigestHash; +extern const char *const httphashnames[]; +void http_digest_response(BinarySink *bs, ptrlen username, ptrlen password, + ptrlen realm, ptrlen method, ptrlen uri, ptrlen qop, + ptrlen nonce, ptrlen opaque, uint32_t nonce_count, + HttpDigestHash hash, bool hash_username); diff --git a/proxy/http.c b/proxy/http.c index 4f184dbe..5176027c 100644 --- a/proxy/http.c +++ b/proxy/http.c @@ -41,6 +41,8 @@ static bool read_line(bufchain *input, strbuf *output, bool is_header) return true; } +typedef enum HttpAuthType { AUTH_NONE, AUTH_BASIC, AUTH_DIGEST } HttpAuthType; + typedef struct HttpProxyNegotiator { int crLine; strbuf *response, *header, *token; @@ -49,7 +51,14 @@ typedef struct HttpProxyNegotiator { strbuf *username, *password; int http_status; bool connection_close; - bool tried_no_auth, try_auth_from_conf; + HttpAuthType next_auth_type; + bool try_auth_from_conf; + strbuf *realm, *nonce, *opaque, *uri; + bool got_opaque; + uint32_t nonce_count; + bool digest_nonce_was_stale; + HttpDigestHash digest_hash; + bool hash_username; prompts_t *prompts; int username_prompt_index, password_prompt_index; size_t content_length; @@ -66,6 +75,17 @@ static ProxyNegotiator *proxy_http_new(const ProxyNegotiatorVT *vt) s->token = strbuf_new(); s->username = strbuf_new(); s->password = strbuf_new_nm(); + s->realm = strbuf_new(); + s->nonce = strbuf_new(); + s->opaque = strbuf_new(); + s->uri = strbuf_new(); + s->nonce_count = 0; + /* + * Always start with a CONNECT request containing no auth. If the + * proxy rejects that, it will tell us what kind of auth it would + * prefer. + */ + s->next_auth_type = AUTH_NONE; return &s->pn; } @@ -77,6 +97,10 @@ static void proxy_http_free(ProxyNegotiator *pn) strbuf_free(s->token); strbuf_free(s->username); strbuf_free(s->password); + strbuf_free(s->realm); + strbuf_free(s->nonce); + strbuf_free(s->opaque); + strbuf_free(s->uri); if (s->prompts) free_prompts(s->prompts); sfree(s); @@ -110,6 +134,21 @@ static inline bool is_separator(char c) #define HTTP_SEPARATORS +static bool get_end_of_header(HttpProxyNegotiator *s) +{ + size_t pos = s->header_pos; + + while (pos < s->header->len && is_whitespace(s->header->s[pos])) + pos++; + + if (pos == s->header->len) { + s->header_pos = pos; + return true; + } + + return false; +} + static bool get_token(HttpProxyNegotiator *s) { size_t pos = s->header_pos; @@ -150,6 +189,39 @@ static bool get_separator(HttpProxyNegotiator *s, char sep) return true; } +static bool get_quoted_string(HttpProxyNegotiator *s) +{ + size_t pos = s->header_pos; + + while (pos < s->header->len && is_whitespace(s->header->s[pos])) + pos++; + + if (pos == s->header->len) + return false; /* end of string */ + + if (s->header->s[pos] != '"') + return false; + pos++; + + strbuf_clear(s->token); + while (pos < s->header->len && s->header->s[pos] != '"') { + if (s->header->s[pos] == '\\') { + /* Backslash makes the next char literal, even if it's " or \ */ + pos++; + if (pos == s->header->len) + return false; /* unexpected end of string */ + } + put_byte(s->token, s->header->s[pos++]); + } + + if (pos == s->header->len) + return false; /* no closing quote */ + pos++; + + s->header_pos = pos; + return true; +} + static void proxy_http_process_queue(ProxyNegotiator *pn) { HttpProxyNegotiator *s = container_of(pn, HttpProxyNegotiator, pn); @@ -164,46 +236,60 @@ static void proxy_http_process_queue(ProxyNegotiator *pn) if (s->username->len || s->password->len) s->try_auth_from_conf = true; + /* + * Set up the host:port string we're trying to connect to, also + * used as the URI string in HTTP Digest auth. + */ + { + char dest[512]; + sk_getaddr(pn->ps->remote_addr, dest, lenof(dest)); + put_fmt(s->uri, "%s:%d", dest, pn->ps->remote_port); + } + while (true) { /* * Standard prefix for the HTTP CONNECT request. */ - { - char dest[512]; - sk_getaddr(pn->ps->remote_addr, dest, lenof(dest)); - put_fmt(pn->output, - "CONNECT %s:%d HTTP/1.1\r\n" - "Host: %s:%d\r\n", - dest, pn->ps->remote_port, dest, pn->ps->remote_port); - } + put_fmt(pn->output, + "CONNECT %s HTTP/1.1\r\n" + "Host: %s\r\n", s->uri->s, s->uri->s); /* - * Optionally send an HTTP Basic auth header with the username - * and password. We do this only after we've first tried no - * authentication at all (even if we have a password to start - * with). + * Add an auth header, if we're planning to this time round. */ - if (s->tried_no_auth) { - if (s->username->len || s->password->len) { - put_datalit(pn->output, "Proxy-Authorization: Basic "); - - strbuf *base64_input = strbuf_new_nm(); - put_datapl(base64_input, ptrlen_from_strbuf(s->username)); - put_byte(base64_input, ':'); - put_datapl(base64_input, ptrlen_from_strbuf(s->password)); - - char base64_output[4]; - for (size_t i = 0, e = base64_input->len; i < e; i += 3) { - base64_encode_atom(base64_input->u + i, - e-i > 3 ? 3 : e-i, base64_output); - put_data(pn->output, base64_output, 4); - } - strbuf_free(base64_input); - smemclr(base64_output, sizeof(base64_output)); - put_datalit(pn->output, "\r\n"); + if (s->next_auth_type == AUTH_BASIC) { + put_datalit(pn->output, "Proxy-Authorization: Basic "); + + strbuf *base64_input = strbuf_new_nm(); + put_datapl(base64_input, ptrlen_from_strbuf(s->username)); + put_byte(base64_input, ':'); + put_datapl(base64_input, ptrlen_from_strbuf(s->password)); + + char base64_output[4]; + for (size_t i = 0, e = base64_input->len; i < e; i += 3) { + base64_encode_atom(base64_input->u + i, + e-i > 3 ? 3 : e-i, base64_output); + put_data(pn->output, base64_output, 4); } - } else { - s->tried_no_auth = true; + strbuf_free(base64_input); + smemclr(base64_output, sizeof(base64_output)); + put_datalit(pn->output, "\r\n"); + } else if (s->next_auth_type == AUTH_DIGEST) { + put_datalit(pn->output, "Proxy-Authorization: Digest "); + http_digest_response(BinarySink_UPCAST(pn->output), + ptrlen_from_strbuf(s->username), + ptrlen_from_strbuf(s->password), + ptrlen_from_strbuf(s->realm), + PTRLEN_LITERAL("CONNECT"), + ptrlen_from_strbuf(s->uri), + PTRLEN_LITERAL("auth"), + ptrlen_from_strbuf(s->nonce), + (s->got_opaque ? + ptrlen_from_strbuf(s->opaque) : + make_ptrlen(NULL, 0)), + ++s->nonce_count, s->digest_hash, + s->hash_username); + put_datalit(pn->output, "\r\n"); } /* @@ -281,7 +367,114 @@ static void proxy_http_process_queue(ProxyNegotiator *pn) continue; if (!stricmp(s->token->s, "Basic")) { - /* fine, we know how to do Basic auth */ + s->next_auth_type = AUTH_BASIC; + } else if (!stricmp(s->token->s, "Digest")) { + if (!http_digest_available) { + pn->error = dupprintf( + "HTTP proxy requested Digest authentication " + "which we do not support"); + crStopV; + } + + /* Parse the rest of the Digest header */ + s->digest_nonce_was_stale = false; + s->digest_hash = HTTP_DIGEST_MD5; + strbuf_clear(s->realm); + strbuf_clear(s->nonce); + strbuf_clear(s->opaque); + s->got_opaque = false; + s->hash_username = false; + + while (true) { + if (!get_token(s)) + goto bad_digest; + + if (!stricmp(s->token->s, "realm")) { + if (!get_separator(s, '=') || + !get_quoted_string(s)) + goto bad_digest; + put_datapl(s->realm, ptrlen_from_strbuf(s->token)); + } else if (!stricmp(s->token->s, "nonce")) { + if (!get_separator(s, '=') || + !get_quoted_string(s)) + goto bad_digest; + + /* If we have a fresh nonce, reset the + * nonce count. Otherwise, keep incrementing it. */ + if (!ptrlen_eq_ptrlen( + ptrlen_from_strbuf(s->token), + ptrlen_from_strbuf(s->nonce))) + s->nonce_count = 0; + + put_datapl(s->nonce, ptrlen_from_strbuf(s->token)); + } else if (!stricmp(s->token->s, "opaque")) { + if (!get_separator(s, '=') || + !get_quoted_string(s)) + goto bad_digest; + put_datapl(s->opaque, + ptrlen_from_strbuf(s->token)); + s->got_opaque = true; + } else if (!stricmp(s->token->s, "stale")) { + if (!get_separator(s, '=') || + !get_token(s)) + goto bad_digest; + s->digest_nonce_was_stale = !stricmp( + s->token->s, "true"); + } else if (!stricmp(s->token->s, "userhash")) { + if (!get_separator(s, '=') || + !get_token(s)) + goto bad_digest; + s->hash_username = !stricmp(s->token->s, "true"); + } else if (!stricmp(s->token->s, "algorithm")) { + if (!get_separator(s, '=') || + !get_token(s)) + goto bad_digest; + bool found = false; + + for (size_t i = 0; i < N_HTTP_DIGEST_HASHES; i++) { + if (!stricmp(s->token->s, httphashnames[i])) { + s->digest_hash = i; + found = true; + break; + } + } + + if (!found) { + pn->error = dupprintf( + "HTTP proxy requested Digest hash " + "algorithm '%s' which we do not support", + s->token->s); + crStopV; + } + } else if (!stricmp(s->token->s, "qop")) { + if (!get_separator(s, '=') || + !get_quoted_string(s)) + goto bad_digest; + if (stricmp(s->token->s, "auth")) { + pn->error = dupprintf( + "HTTP proxy requested Digest quality-of-" + "protection type '%s' which we do not " + "support", s->token->s); + crStopV; + } + } else { + /* Ignore any other auth-param */ + if (!get_separator(s, '=') || + (!get_quoted_string(s) && !get_token(s))) + goto bad_digest; + } + + if (get_end_of_header(s)) + break; + if (!get_separator(s, ',')) + goto bad_digest; + } + s->next_auth_type = AUTH_DIGEST; + continue; + bad_digest: + pn->error = dupprintf("HTTP proxy sent Digest auth " + "request we could not parse"); + crStopV; } else { pn->error = dupprintf("HTTP proxy asked for unsupported " "authentication type '%s'", @@ -314,6 +507,13 @@ static void proxy_http_process_queue(ProxyNegotiator *pn) continue; } + /* If the server sent us stale="true" in a Digest auth + * header, that means we _don't_ need to request a new + * password yet; just try again with the existing details + * and the fresh nonce it sent us. */ + if (s->digest_nonce_was_stale) + continue; + /* Either we never had a password in the first place, or * the one we already presented was rejected. We can only * proceed from here if we have a way to ask the user diff --git a/proxy/nocproxy.c b/proxy/nocproxy.c index 85beec36..38d416d5 100644 --- a/proxy/nocproxy.c +++ b/proxy/nocproxy.c @@ -13,8 +13,19 @@ #include "proxy.h" const bool socks5_chap_available = false; +const bool http_digest_available = false; strbuf *chap_response(ptrlen challenge, ptrlen password) { unreachable("CHAP is not built into this binary"); } + +const char *const httphashnames[] = { NULL }; /* dummy to prevent link error */ + +void http_digest_response(BinarySink *bs, ptrlen username, ptrlen password, + ptrlen realm, ptrlen method, ptrlen uri, ptrlen qop, + ptrlen nonce, ptrlen opaque, uint32_t nonce_count, + HttpDigestHash hash, bool hash_username) +{ + unreachable("HTTP DIGEST is not built into this binary"); +} diff --git a/proxy/proxy.h b/proxy/proxy.h index 9268fcb4..f44a0c55 100644 --- a/proxy/proxy.h +++ b/proxy/proxy.h @@ -105,11 +105,6 @@ prompts_t *proxy_new_prompts(ProxySocket *ps); char *format_telnet_command(SockAddr *addr, int port, Conf *conf, unsigned *flags_out); -/* - * These are implemented in cproxy.c or nocproxy.c, depending on - * whether encrypted proxy authentication is available. - */ -extern const bool socks5_chap_available; -strbuf *chap_response(ptrlen challenge, ptrlen password); +#include "cproxy.h" #endif diff --git a/test/cryptsuite.py b/test/cryptsuite.py index 2ab95481..29bc464e 100755 --- a/test/cryptsuite.py +++ b/test/cryptsuite.py @@ -3171,6 +3171,69 @@ class standard_test_vectors(MyTestBase): self.assertEqual(crc32_rfc1662(vec[:-4]), expected) self.assertEqual(crc32_rfc1662(vec), 0x2144DF1C) + def testHttpDigest(self): + # RFC 7616 section 3.9.1 + params = ["Mufasa", "Circle of Life", "http-auth@example.org", + "GET", "/dir/index.html", "auth", + "7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v", + "FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS", 1, + "MD5", False] + cnonce = base64.decodebytes( + b'f2/wE4q74E6zIJEtWaHKaf5wv/H5QzzpXusqGemxURZJ') + with queued_specific_random_data(cnonce): + self.assertEqual(http_digest_response(*params), + b'username="Mufasa", ' + b'realm="http-auth@example.org", ' + b'uri="/dir/index.html", ' + b'algorithm=MD5, ' + b'nonce="7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v", ' + b'nc=00000001, ' + b'cnonce="f2/wE4q74E6zIJEtWaHKaf5wv/H5QzzpXusqGemxURZJ", ' + b'qop=auth, ' + b'response="8ca523f5e9506fed4657c9700eebdbec", ' + b'opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS"') + + # And again with all the same details except the hash + params[9] = "SHA-256" + with queued_specific_random_data(cnonce): + self.assertEqual(http_digest_response(*params), + b'username="Mufasa", ' + b'realm="http-auth@example.org", ' + b'uri="/dir/index.html", ' + b'algorithm=SHA-256, ' + b'nonce="7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v", ' + b'nc=00000001, ' + b'cnonce="f2/wE4q74E6zIJEtWaHKaf5wv/H5QzzpXusqGemxURZJ", ' + b'qop=auth, ' + b'response="753927fa0e85d155564e2e272a28d1802ca10daf4496794697cf8db5856cb6c1", ' + b'opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS"') + + # RFC 7616 section 3.9.2, using SHA-512-256 (demonstrating + # that they think it's just a 256-bit truncation of SHA-512, + # and not the version defined in FIPS 180-4 which also uses + # a different initial hash state), and username hashing. + params = ["J\u00E4s\u00F8n Doe".encode("UTF-8"), + "Secret, or not?", "api@example.org", + "GET", "/doe.json", "auth", + "5TsQWLVdgBdmrQ0XsxbDODV+57QdFR34I9HAbC/RVvkK", + "HRPCssKJSGjCrkzDg8OhwpzCiGPChXYjwrI2QmXDnsOS", 1, + "SHA-512-256", True] + cnonce = base64.decodebytes( + b'NTg6RKcb9boFIAS3KrFK9BGeh+iDa/sm6jUMp2wds69v') + with queued_specific_random_data(cnonce): + self.assertEqual(http_digest_response(*params), + b'username="488869477bf257147b804c45308cd62ac4e25eb717b12b298c79e62dcea254ec", ' + b'realm="api@example.org", ' + b'uri="/doe.json", ' + b'algorithm=SHA-512-256, ' + b'nonce="5TsQWLVdgBdmrQ0XsxbDODV+57QdFR34I9HAbC/RVvkK", ' + b'nc=00000001, ' + b'cnonce="NTg6RKcb9boFIAS3KrFK9BGeh+iDa/sm6jUMp2wds69v", ' + b'qop=auth, ' + b'response="ae66e67d6b427bd3f120414a82e4acff38e8ecd9101d6c861229025f607a79dd", ' + b'opaque="HRPCssKJSGjCrkzDg8OhwpzCiGPChXYjwrI2QmXDnsOS", ' + b'userhash=true') + if __name__ == "__main__": # Run the tests, suppressing automatic sys.exit and collecting the # unittest.TestProgram instance returned by unittest.main instead. diff --git a/test/testcrypt.py b/test/testcrypt.py index 66611ed3..28a00897 100644 --- a/test/testcrypt.py +++ b/test/testcrypt.py @@ -178,7 +178,7 @@ def make_argword(arg, argtype, fnname, argindex, to_preserve): if typename in { "hashalg", "macalg", "keyalg", "cipheralg", "dh_group", "ecdh_alg", "rsaorder", "primegenpolicy", - "argon2flavour", "fptype"}: + "argon2flavour", "fptype", "httpdigesthash"}: arg = coerce_to_bytes(arg) if isinstance(arg, bytes) and b" " not in arg: return arg diff --git a/testcrypt.c b/testcrypt.c index 24ec2a5b..3bdaa518 100644 --- a/testcrypt.c +++ b/testcrypt.c @@ -35,6 +35,7 @@ #include "misc.h" #include "mpint.h" #include "crypto/ecc.h" +#include "proxy/cproxy.h" static NORETURN PRINTF_LIKE(1, 2) void fatal_error(const char *p, ...) { @@ -463,6 +464,25 @@ static FingerprintType get_fptype(BinarySource *in) fatal_error("fingerprint type '%.*s': not found", PTRLEN_PRINTF(name)); } +static HttpDigestHash get_httpdigesthash(BinarySource *in) +{ + static const struct { + const char *key; + HttpDigestHash value; + } hashes[] = { + #define DECL_ARRAY(id, str, alg, bits) {str, id}, + HTTP_DIGEST_HASHES(DECL_ARRAY) + #undef DECL_ARRAY + }; + + ptrlen name = get_word(in); + for (size_t i = 0; i < lenof(hashes); i++) + if (ptrlen_eq_string(name, hashes[i].key)) + return hashes[i].value; + + fatal_error("httpdigesthash '%.*s': not found", PTRLEN_PRINTF(name)); +} + static uintmax_t get_uint(BinarySource *in) { ptrlen word = get_word(in); @@ -1384,6 +1404,7 @@ typedef PockleStatus TD_pocklestatus; typedef struct mr_result TD_mr_result; typedef Argon2Flavour TD_argon2flavour; typedef FingerprintType TD_fptype; +typedef HttpDigestHash TD_httpdigesthash; #define FUNC0(rettype, function) \ static void handle_##function(BinarySource *in, strbuf *out) { \ diff --git a/testcrypt.h b/testcrypt.h index 7e0ef3cf..d6ac0a2a 100644 --- a/testcrypt.h +++ b/testcrypt.h @@ -318,6 +318,7 @@ FUNC1(uint, crc32_ssh1, val_string_ptrlen) FUNC2(uint, crc32_update, uint, val_string_ptrlen) FUNC2(boolean, crcda_detect, val_string_ptrlen, val_string_ptrlen) FUNC1(val_string, get_implementations_commasep, val_string_ptrlen) +FUNC12(void, http_digest_response, out_val_string_binarysink, val_string_ptrlen, val_string_ptrlen, val_string_ptrlen, val_string_ptrlen, val_string_ptrlen, val_string_ptrlen, val_string_ptrlen, val_string_ptrlen, uint, httpdigesthash, boolean) /* * These functions aren't part of PuTTY's own API, but are additions -- cgit v1.2.3 From 60377a09b47668a84a569dfcdb6a6a002791b39f Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 21 Nov 2021 09:36:22 +0000 Subject: Actually test multiple SHA-512 implementations. Spotted in passing: the cryptsuite test functions iterate 'hashname' through all the available implementations of SHA-512 (or SHA-384), but then, in each iteration, ignore that loop variable completely and always test the default algorithm. So on a platform where more than one implementation is available, we were only actually testing one of them. Oops! --- test/cryptsuite.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/test/cryptsuite.py b/test/cryptsuite.py index 29bc464e..a501bc27 100755 --- a/test/cryptsuite.py +++ b/test/cryptsuite.py @@ -2753,30 +2753,30 @@ class standard_test_vectors(MyTestBase): # Test cases from RFC 6234 section 8.5, omitting the ones # whose input is not a multiple of 8 bits - self.assertEqualBin(hash_str('sha384', "abc"), unhex( + self.assertEqualBin(hash_str(hashname, "abc"), unhex( 'cb00753f45a35e8bb5a03d699ac65007272c32ab0eded163' '1a8b605a43ff5bed8086072ba1e7cc2358baeca134c825a7')) - self.assertEqualBin(hash_str('sha384', + self.assertEqualBin(hash_str(hashname, "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmn" "hijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu"), unhex('09330c33f71147e83d192fc782cd1b4753111b173b3b05d2' '2fa08086e3b0f712fcc7c71a557e2db966c3e9fa91746039')) - self.assertEqualBin(hash_str_iter('sha384', + self.assertEqualBin(hash_str_iter(hashname, ("a" * 1000 for _ in range(1000))), unhex( '9d0e1809716474cb086e834e310a4a1ced149e9c00f24852' '7972cec5704c2a5b07b8b3dc38ecc4ebae97ddd87f3d8985')) - self.assertEqualBin(hash_str('sha384', + self.assertEqualBin(hash_str(hashname, "01234567012345670123456701234567" * 20), unhex( '2fc64a4f500ddb6828f6a3430b8dd72a368eb7f3a8322a70' 'bc84275b9c0b3ab00d27a5cc3c2d224aa6b61a0d79fb4596')) - self.assertEqualBin(hash_str('sha384', b"\xB9"), unhex( + self.assertEqualBin(hash_str(hashname, b"\xB9"), unhex( 'bc8089a19007c0b14195f4ecc74094fec64f01f90929282c' '2fb392881578208ad466828b1c6c283d2722cf0ad1ab6938')) - self.assertEqualBin(hash_str('sha384', + self.assertEqualBin(hash_str(hashname, unhex("a41c497779c0375ff10a7f4e08591739")), unhex( 'c9a68443a005812256b8ec76b00516f0dbb74fab26d66591' '3f194b6ffb0e91ea9967566b58109cbc675cc208e4c823f7')) - self.assertEqualBin(hash_str('sha384', unhex( + self.assertEqualBin(hash_str(hashname, unhex( "399669e28f6b9c6dbcbb6912ec10ffcf74790349b7dc8fbe4a8e7b3b5621" "db0f3e7dc87f823264bbe40d1811c9ea2061e1c84ad10a23fac1727e7202" "fc3f5042e6bf58cba8a2746e1f64f9b9ea352c711507053cf4e5339d5286" @@ -2795,36 +2795,36 @@ class standard_test_vectors(MyTestBase): # Test cases from RFC 6234 section 8.5, omitting the ones # whose input is not a multiple of 8 bits - self.assertEqualBin(hash_str('sha512', "abc"), unhex( + self.assertEqualBin(hash_str(hashname, "abc"), unhex( 'ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55' 'd39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94f' 'a54ca49f')) - self.assertEqualBin(hash_str('sha512', + self.assertEqualBin(hash_str(hashname, "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmn" "hijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu"), unhex('8e959b75dae313da8cf4f72814fc143f8f7779c6eb9f7fa17299' 'aeadb6889018501d289e4900f7e4331b99dec4b5433ac7d329eeb6dd26' '545e96e55b874be909')) - self.assertEqualBin(hash_str_iter('sha512', + self.assertEqualBin(hash_str_iter(hashname, ("a" * 1000 for _ in range(1000))), unhex( 'e718483d0ce769644e2e42c7bc15b4638e1f98b13b2044285632a803afa9' '73ebde0ff244877ea60a4cb0432ce577c31beb009c5c2c49aa2e4eadb217' 'ad8cc09b')) - self.assertEqualBin(hash_str('sha512', + self.assertEqualBin(hash_str(hashname, "01234567012345670123456701234567" * 20), unhex( '89d05ba632c699c31231ded4ffc127d5a894dad412c0e024db872d1abd2b' 'a8141a0f85072a9be1e2aa04cf33c765cb510813a39cd5a84c4acaa64d3f' '3fb7bae9')) - self.assertEqualBin(hash_str('sha512', b"\xD0"), unhex( + self.assertEqualBin(hash_str(hashname, b"\xD0"), unhex( '9992202938e882e73e20f6b69e68a0a7149090423d93c81bab3f21678d4a' 'ceeee50e4e8cafada4c85a54ea8306826c4ad6e74cece9631bfa8a549b4a' 'b3fbba15')) - self.assertEqualBin(hash_str('sha512', + self.assertEqualBin(hash_str(hashname, unhex("8d4e3c0e3889191491816e9d98bff0a0")), unhex( 'cb0b67a4b8712cd73c9aabc0b199e9269b20844afb75acbdd1c153c98289' '24c3ddedaafe669c5fdd0bc66f630f6773988213eb1b16f517ad0de4b2f0' 'c95c90f8')) - self.assertEqualBin(hash_str('sha512', unhex( + self.assertEqualBin(hash_str(hashname, unhex( "a55f20c411aad132807a502d65824e31a2305432aa3d06d3e282a8d84e0d" "e1de6974bf495469fc7f338f8054d58c26c49360c3e87af56523acf6d89d" "03e56ff2f868002bc3e431edc44df2f0223d4bb3b243586e1a7d92493669" -- cgit v1.2.3 From 1847ab282d384a069dc5afa5aedbdca148897cca Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 21 Nov 2021 11:46:00 +0000 Subject: testcrypt: fix param name in ssh_cipher_setiv_wrapper. Spotted in passing that a cut-and-paste error made the parameter name 'key' rather than 'iv'. Harmless, but wrong. --- testcrypt.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/testcrypt.c b/testcrypt.c index 3bdaa518..456cd831 100644 --- a/testcrypt.c +++ b/testcrypt.c @@ -908,12 +908,12 @@ strbuf *ssh_hash_final_wrapper(ssh_hash *h) #undef ssh_hash_final #define ssh_hash_final ssh_hash_final_wrapper -void ssh_cipher_setiv_wrapper(ssh_cipher *c, ptrlen key) +void ssh_cipher_setiv_wrapper(ssh_cipher *c, ptrlen iv) { - if (key.len != ssh_cipher_alg(c)->blksize) + if (iv.len != ssh_cipher_alg(c)->blksize) fatal_error("ssh_cipher_setiv: needs exactly %d bytes", ssh_cipher_alg(c)->blksize); - ssh_cipher_setiv(c, key.ptr); + ssh_cipher_setiv(c, iv.ptr); } #undef ssh_cipher_setiv #define ssh_cipher_setiv ssh_cipher_setiv_wrapper -- cgit v1.2.3 From 3743859f971439db33968cc19e1e69866440c1a3 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 21 Nov 2021 10:27:30 +0000 Subject: Rewrite the testcrypt.c macro system. Yesterday's commit 52ee636b092c199 which further extended the huge pile of arity-specific annoying wrapper macros pushed me over the edge and inspired me to give some harder thought to finding a way to handle all arities at once. And this time I found one! The new technique changes the syntax of the function specifications in testcrypt.h. In particular, they now have to specify a _name_ for each parameter as well as a type, because the macros generating the C marshalling wrappers will need a structure field for each parameter and cpp isn't flexible enough to generate names for those fields automatically. Rather than tediously name them arg1, arg2 etc, I've reused the names of the parameters from the prototypes or definitions of the underlying real functions (via a one-off auto-extraction process starting from the output of 'clang -Xclang -dump-ast' plus some manual polishing), which means testcrypt.h is now a bit more self-documenting. The testcrypt.py end of the mechanism is rewritten to eat the new format. Since it's got more complicated syntax and nested parens and things, I've written something a bit like a separated lexer/parser system in place of the previous crude regex matcher, which should enforce that the whole header file really does conform to the restricted syntax it has to fit into. The new system uses a lot less code in testcrypt.c, but I've made up for that by also writing a long comment explaining how it works, which was another thing the previous system lacked! Similarly, the new testcrypt.h has some long-overdue instructions at the top. --- test/testcrypt.py | 150 +++++++++++---- testcrypt.c | 419 +++++++++++++++++++++-------------------- testcrypt.h | 542 ++++++++++++++++++++++++++++++------------------------ 3 files changed, 642 insertions(+), 469 deletions(-) diff --git a/test/testcrypt.py b/test/testcrypt.py index 28a00897..170ad3ce 100644 --- a/test/testcrypt.py +++ b/test/testcrypt.py @@ -3,6 +3,7 @@ import os import numbers import subprocess import re +import string import struct from binascii import hexlify @@ -269,10 +270,95 @@ class Function(object): return retvals[0] return tuple(retvals) -def _setup(scope): - header_file = os.path.join(putty_srcdir, "testcrypt.h") +def _lex_testcrypt_header(header): + pat = re.compile( + # Skip any combination of whitespace and comments + '(?:{})*'.format('|'.join(( + '[ \t\n]', # whitespace + '/\\*(?:.|\n)*?\\*/', # C90-style /* ... */ comment, ended eagerly + '//[^\n]*\n', # C99-style comment to end-of-line + ))) + + # And then match a token + '({})'.format('|'.join(( + # Punctuation + '\(', + '\)', + ',', + # Identifier + '[A-Za-z_][A-Za-z0-9_]*', + # End of string + '$', + ))) + ) + + pos = 0 + end = len(header) + while pos < end: + m = pat.match(header, pos) + assert m is not None, ( + "Failed to lex testcrypt.h at byte position {:d}".format(pos)) + + pos = m.end() + tok = m.group(1) + if len(tok) == 0: + assert pos == end, ( + "Empty token should only be returned at end of string") + yield tok, m.start(1) + +def _parse_testcrypt_header(tokens): + def is_id(tok): + return tok[0] in string.ascii_letters+"_" + def expect(what, why, eof_ok=False): + tok, pos = next(tokens) + if tok == '' and eof_ok: + return None + if hasattr(what, '__call__'): + description = lambda: "" + ok = what(tok) + elif isinstance(what, set): + description = lambda: " or ".join("'"+x+"' " for x in sorted(what)) + ok = tok in what + else: + description = lambda: "'"+what+"' " + ok = tok == what + if not ok: + sys.exit("testcrypt.h:{:d}: expected {}{}".format( + pos, description(), why)) + return tok - linere = re.compile(r'^FUNC\d+\((.*)\)$') + while True: + tok = expect("FUNC", "at start of function specification", eof_ok=True) + if tok is None: + break + + expect("(", "after FUNC") + rettype = expect(is_id, "return type") + expect(",", "after return type") + funcname = expect(is_id, "function name") + expect(",", "after function name") + expect("(", "to begin argument list") + args = [] + firstargkind = expect({"ARG", "VOID"}, "at start of argument list") + if firstargkind == "VOID": + expect(")", "after VOID") + else: + while True: + # Every time we come back to the top of this loop, we've + # just seen 'ARG' + expect("(", "after ARG") + argtype = expect(is_id, "argument type") + expect(",", "after argument type") + argname = expect(is_id, "argument name") + args.append((argtype, argname)) + expect(")", "at end of ARG") + punct = expect({",", ")"}, "after argument") + if punct == ")": + break + expect("ARG", "to begin next argument") + expect(")", "at end of FUNC") + yield funcname, rettype, args + +def _setup(scope): valprefix = "val_" outprefix = "out_" optprefix = "opt_" @@ -288,36 +374,34 @@ def _setup(scope): arg = arg[:arg.index("_", len(valprefix))] return arg - with open(header_file) as f: - for line in iter(f.readline, ""): - line = line.rstrip("\r\n").replace(" ", "") - m = linere.match(line) - if m is not None: - words = m.group(1).split(",") - function = words[1] - rettypes = [] - argtypes = [] - argsconsumed = [] - if words[0] != "void": - rettypes.append(trim_argtype(words[0])) - for arg in words[2:]: - if arg.startswith(outprefix): - rettypes.append(trim_argtype(arg[len(outprefix):])) - else: - consumed = False - if arg.startswith(consprefix): - arg = arg[len(consprefix):] - consumed = True - arg = trim_argtype(arg) - argtypes.append((arg, consumed)) - func = Function(function, rettypes, argtypes) - scope[function] = func - if len(argtypes) > 0: - t = argtypes[0][0] - if (t in method_prefixes and - function.startswith(method_prefixes[t])): - methodname = function[len(method_prefixes[t]):] - method_lists[t].append((methodname, func)) + with open(os.path.join(putty_srcdir, "testcrypt.h")) as f: + header = f.read() + tokens = _lex_testcrypt_header(header) + for function, rettype, arglist in _parse_testcrypt_header(tokens): + rettypes = [] + if rettype != "void": + rettypes.append(trim_argtype(rettype)) + + argtypes = [] + argsconsumed = [] + for arg, argname in arglist: + if arg.startswith(outprefix): + rettypes.append(trim_argtype(arg[len(outprefix):])) + else: + consumed = False + if arg.startswith(consprefix): + arg = arg[len(consprefix):] + consumed = True + arg = trim_argtype(arg) + argtypes.append((arg, consumed)) + func = Function(function, rettypes, argtypes) + scope[function] = func + if len(argtypes) > 0: + t = argtypes[0][0] + if (t in method_prefixes and + function.startswith(method_prefixes[t])): + methodname = function[len(method_prefixes[t]):] + method_lists[t].append((methodname, func)) _setup(globals()) del _setup diff --git a/testcrypt.c b/testcrypt.c index 456cd831..bf49af25 100644 --- a/testcrypt.c +++ b/testcrypt.c @@ -1406,180 +1406,226 @@ typedef Argon2Flavour TD_argon2flavour; typedef FingerprintType TD_fptype; typedef HttpDigestHash TD_httpdigesthash; -#define FUNC0(rettype, function) \ - static void handle_##function(BinarySource *in, strbuf *out) { \ - return_##rettype(out, function()); \ - } - -#define FUNC1(rettype, function, type1) \ - static void handle_##function(BinarySource *in, strbuf *out) { \ - TD_##type1 arg1 = get_##type1(in); \ - return_##rettype(out, function(arg1)); \ - } - -#define FUNC2(rettype, function, type1, type2) \ - static void handle_##function(BinarySource *in, strbuf *out) { \ - TD_##type1 arg1 = get_##type1(in); \ - TD_##type2 arg2 = get_##type2(in); \ - return_##rettype(out, function(arg1, arg2)); \ - } - -#define FUNC3(rettype, function, type1, type2, type3) \ - static void handle_##function(BinarySource *in, strbuf *out) { \ - TD_##type1 arg1 = get_##type1(in); \ - TD_##type2 arg2 = get_##type2(in); \ - TD_##type3 arg3 = get_##type3(in); \ - return_##rettype(out, function(arg1, arg2, arg3)); \ - } - -#define FUNC4(rettype, function, type1, type2, type3, type4) \ - static void handle_##function(BinarySource *in, strbuf *out) { \ - TD_##type1 arg1 = get_##type1(in); \ - TD_##type2 arg2 = get_##type2(in); \ - TD_##type3 arg3 = get_##type3(in); \ - TD_##type4 arg4 = get_##type4(in); \ - return_##rettype(out, function(arg1, arg2, arg3, arg4)); \ - } - -#define FUNC5(rettype, function, type1, type2, type3, type4, type5) \ - static void handle_##function(BinarySource *in, strbuf *out) { \ - TD_##type1 arg1 = get_##type1(in); \ - TD_##type2 arg2 = get_##type2(in); \ - TD_##type3 arg3 = get_##type3(in); \ - TD_##type4 arg4 = get_##type4(in); \ - TD_##type5 arg5 = get_##type5(in); \ - return_##rettype(out, function(arg1, arg2, arg3, arg4, arg5)); \ - } - -#define FUNC6(rettype, function, type1, type2, type3, type4, type5, \ - type6) \ - static void handle_##function(BinarySource *in, strbuf *out) { \ - TD_##type1 arg1 = get_##type1(in); \ - TD_##type2 arg2 = get_##type2(in); \ - TD_##type3 arg3 = get_##type3(in); \ - TD_##type4 arg4 = get_##type4(in); \ - TD_##type5 arg5 = get_##type5(in); \ - TD_##type6 arg6 = get_##type6(in); \ - return_##rettype(out, function(arg1, arg2, arg3, arg4, arg5, \ - arg6)); \ - } - -#define FUNC7(rettype, function, type1, type2, type3, type4, type5, \ - type6, type7) \ - static void handle_##function(BinarySource *in, strbuf *out) { \ - TD_##type1 arg1 = get_##type1(in); \ - TD_##type2 arg2 = get_##type2(in); \ - TD_##type3 arg3 = get_##type3(in); \ - TD_##type4 arg4 = get_##type4(in); \ - TD_##type5 arg5 = get_##type5(in); \ - TD_##type6 arg6 = get_##type6(in); \ - TD_##type7 arg7 = get_##type7(in); \ - return_##rettype(out, function(arg1, arg2, arg3, arg4, arg5, \ - arg6, arg7)); \ - } - -#define FUNC8(rettype, function, type1, type2, type3, type4, type5, \ - type6, type7, type8) \ - static void handle_##function(BinarySource *in, strbuf *out) { \ - TD_##type1 arg1 = get_##type1(in); \ - TD_##type2 arg2 = get_##type2(in); \ - TD_##type3 arg3 = get_##type3(in); \ - TD_##type4 arg4 = get_##type4(in); \ - TD_##type5 arg5 = get_##type5(in); \ - TD_##type6 arg6 = get_##type6(in); \ - TD_##type7 arg7 = get_##type7(in); \ - TD_##type8 arg8 = get_##type8(in); \ - return_##rettype(out, function(arg1, arg2, arg3, arg4, arg5, \ - arg6, arg7, arg8)); \ - } - -#define FUNC9(rettype, function, type1, type2, type3, type4, type5, \ - type6, type7, type8, type9) \ - static void handle_##function(BinarySource *in, strbuf *out) { \ - TD_##type1 arg1 = get_##type1(in); \ - TD_##type2 arg2 = get_##type2(in); \ - TD_##type3 arg3 = get_##type3(in); \ - TD_##type4 arg4 = get_##type4(in); \ - TD_##type5 arg5 = get_##type5(in); \ - TD_##type6 arg6 = get_##type6(in); \ - TD_##type7 arg7 = get_##type7(in); \ - TD_##type8 arg8 = get_##type8(in); \ - TD_##type9 arg9 = get_##type9(in); \ - return_##rettype(out, function(arg1, arg2, arg3, arg4, arg5, \ - arg6, arg7, arg8, arg9)); \ - } +/* + * HERE BE DRAGONS: the horrible C preprocessor business that reads + * testcrypt.h and generates a marshalling wrapper for each exported + * function. + * + * In an ideal world, we would start from a specification like this in + * testcrypt.h + * + * FUNC(val_foo, example, (ARG(val_bar, bar), ARG(uint, n))) + * + * and generate a wrapper function looking like this: + * + * static void handle_example(BinarySource *in, strbuf *out) { + * TD_val_bar bar = get_val_bar(in); + * TD_uint n = get_uint(in); + * return_val_foo(out, example(bar, n)); + * } + * + * which would read the marshalled form of each function argument in + * turn from the input BinarySource via the get_() function + * family defined in this file; assign each argument to a local + * variable; call the underlying C function with all those arguments; + * and then call a function of the return_() family to marshal + * the output value into the output strbuf to be sent to standard + * output. + * + * With a more general macro processor such as m4, or custom code in + * Perl or Python, or a helper program like llvm-tblgen, we could just + * do that directly, reading function specifications from testcrypt.h + * and writing out exactly the above. But we don't have a fully + * general macro processor (since everything in that category + * introduces an extra build dependency that's awkward on plain + * Windows, or requires compiling and running a helper program which + * is awkward in a cross-compile). We only have cpp. And in cpp, a + * macro can't expand one of its arguments differently in two parts of + * its own expansion. So we have to be more clever. + * + * In place of the above code, I instead generate three successive + * declarations for each function. In simplified form they would look + * like this: + * + * typedef struct ARGS_example { + * TD_val_bar bar; + * TD_uint n; + * } ARGS_example; + * + * static inline ARGS_example get_args_example(BinarySource *in) { + * ARGS_example args; + * args.bar = get_val_bar(in); + * args.n = get_uint(in); + * return args; + * } + * + * static void handle_example(BinarySource *in, strbuf *out) { + * ARGS_example args = get_args_example(in); + * return_val_foo(out, example(args.bar, args.n)); + * } + * + * Each of these mentions the arguments and their types just _once_, + * so each one can be generated by a single expansion of the FUNC(...) + * specification in testcrypt.h, with FUNC and ARG and VOID defined to + * appropriate values. + * + * Or ... *nearly*. In fact, I left out several details there, but + * it's a good starting point to understand the full version. + * + * To begin with, several of the variable names shown above are + * actually named with an ugly leading underscore, to minimise the + * chance of them colliding with real parameter names. (You could + * easily imagine 'out' being the name of a parameter to one of the + * wrapped functions.) Also, we memset the whole structure to zero at + * the start of get_args_example() to avoid compiler warnings about + * uninitialised stuff, and insert a precautionary '(void)args;' in + * handle_example to avoid a similar warning about _unused_ stuff. + * + * The big problem is the commas that have to appear between arguments + * in the final call to the actual C function. Those can't be + * generated by expanding the ARG macro itself, or you'd get one too + * many - either a leading comma or a trailing comma. Trailing commas + * are legal in a Python function call, but unfortunately C is not yet + * so enlightened. (C permits a trailing comma in a struct or array + * initialiser, and is coming round to it in enums, but hasn't yet + * seen the light about function calls or function prototypes.) + * + * So the commas must appear _between_ ARG(...) specifiers. And that + * means they unavoidably appear in _every_ expansion of FUNC() (or + * rather, every expansion that uses the argument list at all). + * Therefore, we need to ensure they're harmless in the other two + * functions as well. + * + * In the get_args_example() function above, there's no real problem. + * The list of assignments can perfectly well be separated by commas + * instead of semicolons, so that it becomes a single expression- + * statement instead of a sequence of them; the comma operator still + * defines a sequence point, so it's fine. + * + * But what about the structure definition of ARGS_example? + * + * To get round that, we fill the structure with pointless extra + * cruft, in the form of an extra 'int' field before and after each + * actually useful argument field. So the real structure definition + * ends up looking more like this: + * + * typedef struct ARGS_example { + * int _predummy_bar; + * TD_val_bar bar; + * int _postdummy_bar, _predummy_n; + * TD_uint n; + * int _postdummy_n; + * } ARGS_example; + * + * Those extra 'int' fields are ignored completely at run time. They + * might cause a runtime space cost if the struct doesn't get + * completely optimised away when get_args_example is inlined into + * handle_example, but even if so, that's OK, this is a test program + * whose memory usage isn't critical. The real point is that, in + * between each pair of real arguments, there's a declaration + * containing *two* int variables, and in between them is the vital + * comma that we need! + * + * So in that pass through testcrypt.h, the ARG(type, name) macro has + * to expand to the weird piece of text + * + * _predummy_name; // terminating the previous int declaration + * TD_type name; // declaring the thing we actually wanted + * int _postdummy_name // new declaration ready to see a comma + * + * so that a comma-separated list of pieces of expansion like that + * will fall into just the right form to be the core of the above + * expanded structure definition. Then we just need to put in the + * 'int' after the open brace, and the ';' before the closing brace, + * and we've got everything we need to make it all syntactically legal. + * + * Other points of note: + * + * Why the extra pair of parens around the whole argument list? You'd + * like to think that FUNC could be a variadic macro, and just use + * __VA_ARGS__ to expand all the arguments wherever they're needed. + * But unfortunately there's a portability consideration: some of the + * 'functions' wrapped by this system are actually macros in turn, and + * if you use __VA_ARGS__ to expand multiple arguments from one macro + * into the argument list of another macro, compilers disagree on what + * happens: Visual Studio in particular will turn __VA_ARGS__ into + * just one argument instead of multiple ones. That is, if you do this: + * + * #define DESTINATION_MACRO(a, b) ... stuff using a and b ... + * #define WRAPPER(...) DESTINATION_MACRO(__VA_ARGS__) + * WRAPPER(1, 2) + * + * then most compilers will behave as if you'd called + * DESTINATION_MACRO with 'a' expanding to 1 and 'b' expanding to 2. + * But Visual Studio will consider that you called it with 'a' + * expanding to the whole of __VA_ARGS__ - that is, the token sequence + * '1 , 2' - and will expand 'b' to nothing at all! + * + * So we have a constraint that if ARGS is going to be turned into the + * argument list to the actual called function - as it is in the final + * handle_example() expansion shown above - then the commas can't come + * from a variadic expansion of __VA_ARGS__. Hence, FUNC can't _be_ a + * variadic macro. Instead, we wrap all the arguments in an extra pair + * of parens, and generate the final call not by saying function(args) + * but by saying just 'function args', since 'args' contains the + * parens already. + * + * In get_args_example(), that's still fine, because our giant + * comma-separated expression containing multiple assignment + * subexpressions can legally be wrapped in parentheses as well. But + * what do you do in the structure definition? + * + * Answer: _there_ we use a variadic macro to strip off the outer + * parens, by expanding to just __VA_ARGS__. That's OK even in Visual + * Studio, because in this particular context, __VA_ARGS__ is ending + * up in a structure definition and definitely _not_ in the argument + * list of another macro. + * + * Finally, what if a wrapped function has _no_ arguments? Two out of + * three uses of the argument list here need some kind of special case + * for that. That's why you have to write 'VOID' explicitly in an + * empty argument list in testcrypt.h: we make VOID expand to whatever + * is needed to avoid a syntax error in that special case. + */ -#define FUNC10(rettype, function, type1, type2, type3, type4, type5, \ - type6, type7, type8, type9, type10) \ - static void handle_##function(BinarySource *in, strbuf *out) { \ - TD_##type1 arg1 = get_##type1(in); \ - TD_##type2 arg2 = get_##type2(in); \ - TD_##type3 arg3 = get_##type3(in); \ - TD_##type4 arg4 = get_##type4(in); \ - TD_##type5 arg5 = get_##type5(in); \ - TD_##type6 arg6 = get_##type6(in); \ - TD_##type7 arg7 = get_##type7(in); \ - TD_##type8 arg8 = get_##type8(in); \ - TD_##type9 arg9 = get_##type9(in); \ - TD_##type10 arg10 = get_##type10(in); \ - return_##rettype(out, function(arg1, arg2, arg3, arg4, arg5, \ - arg6, arg7, arg8, arg9, arg10)); \ - } +#define DEPARENTHESISE(...) __VA_ARGS__ -#define FUNC11(rettype, function, type1, type2, type3, type4, type5, \ - type6, type7, type8, type9, type10, type11) \ - static void handle_##function(BinarySource *in, strbuf *out) { \ - TD_##type1 arg1 = get_##type1(in); \ - TD_##type2 arg2 = get_##type2(in); \ - TD_##type3 arg3 = get_##type3(in); \ - TD_##type4 arg4 = get_##type4(in); \ - TD_##type5 arg5 = get_##type5(in); \ - TD_##type6 arg6 = get_##type6(in); \ - TD_##type7 arg7 = get_##type7(in); \ - TD_##type8 arg8 = get_##type8(in); \ - TD_##type9 arg9 = get_##type9(in); \ - TD_##type10 arg10 = get_##type10(in); \ - TD_##type11 arg11 = get_##type11(in); \ - return_##rettype(out, function(arg1, arg2, arg3, arg4, arg5, \ - arg6, arg7, arg8, arg9, arg10, \ - arg11)); \ +#define ARG(type, arg) _predummy_##arg; TD_##type arg; int _postdummy_##arg +#define VOID _voiddummy +#define FUNC(outtype, fname, args) \ + typedef struct ARGS_##fname { \ + int DEPARENTHESISE args; \ + } ARGS_##fname; +#include "testcrypt.h" +#undef FUNC +#undef ARG +#undef VOID + +#define ARG(type, arg) _args.arg = get_##type(_in) +#define VOID ((void)0) +#define FUNC(outtype, fname, args) \ + static inline ARGS_##fname get_args_##fname(BinarySource *_in) { \ + ARGS_##fname _args; \ + memset(&_args, 0, sizeof(_args)); \ + args; \ + return _args; \ } - -#define FUNC12(rettype, function, type1, type2, type3, type4, type5, \ - type6, type7, type8, type9, type10, type11, type12) \ - static void handle_##function(BinarySource *in, strbuf *out) { \ - TD_##type1 arg1 = get_##type1(in); \ - TD_##type2 arg2 = get_##type2(in); \ - TD_##type3 arg3 = get_##type3(in); \ - TD_##type4 arg4 = get_##type4(in); \ - TD_##type5 arg5 = get_##type5(in); \ - TD_##type6 arg6 = get_##type6(in); \ - TD_##type7 arg7 = get_##type7(in); \ - TD_##type8 arg8 = get_##type8(in); \ - TD_##type9 arg9 = get_##type9(in); \ - TD_##type10 arg10 = get_##type10(in); \ - TD_##type11 arg11 = get_##type11(in); \ - TD_##type12 arg12 = get_##type12(in); \ - return_##rettype(out, function(arg1, arg2, arg3, arg4, arg5, \ - arg6, arg7, arg8, arg9, arg10, \ - arg11, arg12)); \ +#include "testcrypt.h" +#undef FUNC +#undef ARG +#undef VOID + +#define ARG(type, arg) _args.arg +#define VOID +#define FUNC(outtype, fname, args) \ + static void handle_##fname(BinarySource *_in, strbuf *_out) { \ + ARGS_##fname _args = get_args_##fname(_in); \ + (void)_args; /* suppress warning if no actual arguments */ \ + return_##outtype(_out, fname args); \ } - #include "testcrypt.h" - -#undef FUNC12 -#undef FUNC11 -#undef FUNC10 -#undef FUNC9 -#undef FUNC8 -#undef FUNC7 -#undef FUNC6 -#undef FUNC5 -#undef FUNC4 -#undef FUNC3 -#undef FUNC2 -#undef FUNC1 -#undef FUNC0 +#undef FUNC +#undef ARG static void process_line(BinarySource *in, strbuf *out) { @@ -1601,35 +1647,14 @@ static void process_line(BinarySource *in, strbuf *out) DISPATCH_COMMAND(mp_dump); #undef DISPATCH_COMMAND -#define FUNC0(ret,fn) DISPATCH_INTERNAL(#fn,handle_##fn); -#define FUNC1(ret,fn,...) DISPATCH_INTERNAL(#fn,handle_##fn); -#define FUNC2(ret,fn,...) DISPATCH_INTERNAL(#fn,handle_##fn); -#define FUNC3(ret,fn,...) DISPATCH_INTERNAL(#fn,handle_##fn); -#define FUNC4(ret,fn,...) DISPATCH_INTERNAL(#fn,handle_##fn); -#define FUNC5(ret,fn,...) DISPATCH_INTERNAL(#fn,handle_##fn); -#define FUNC6(ret,fn,...) DISPATCH_INTERNAL(#fn,handle_##fn); -#define FUNC7(ret,fn,...) DISPATCH_INTERNAL(#fn,handle_##fn); -#define FUNC8(ret,fn,...) DISPATCH_INTERNAL(#fn,handle_##fn); -#define FUNC9(ret,fn,...) DISPATCH_INTERNAL(#fn,handle_##fn); -#define FUNC10(ret,fn,...) DISPATCH_INTERNAL(#fn,handle_##fn); -#define FUNC11(ret,fn,...) DISPATCH_INTERNAL(#fn,handle_##fn); -#define FUNC12(ret,fn,...) DISPATCH_INTERNAL(#fn,handle_##fn); - +#define FUNC(outtype, fname, args) DISPATCH_INTERNAL(#fname,handle_##fname); +#define ARG1(type, arg) +#define ARGN(type, arg) +#define VOID #include "testcrypt.h" - -#undef FUNC12 -#undef FUNC11 -#undef FUNC10 -#undef FUNC9 -#undef FUNC8 -#undef FUNC7 -#undef FUNC6 -#undef FUNC5 -#undef FUNC4 -#undef FUNC3 -#undef FUNC2 -#undef FUNC1 -#undef FUNC0 +#undef FUNC +#undef ARG +#undef VOID #undef DISPATCH_INTERNAL diff --git a/testcrypt.h b/testcrypt.h index d6ac0a2a..0e8f8186 100644 --- a/testcrypt.h +++ b/testcrypt.h @@ -1,126 +1,190 @@ +/* + * List of functions exported by the 'testcrypt' system to provide a + * Python API for running unit tests and auxiliary programs. + * + * Each function definition in this file has the form + * + * FUNC(return-type, function-name, (arguments)) + * + * where 'arguments' in turn is either VOID, or a comma-separated list + * of argument specifications of the form + * + * ARG(argument-type, argument-name) + * + * Type names are always single identifiers, and they have some + * standard prefixes: + * + * 'val_' means that the type refers to something dynamically + * allocated, so that it has a persistent identity, needs to be freed + * when finished with (though this is done automatically by the + * testcrypt.py system via Python's reference counting), and may also + * be mutable. The argument type in C will be a pointer; in Python the + * corresponding argument will be an instance of a 'Value' object + * defined in testcrypt.py. + * + * 'opt_val_' is a modification of 'val_' to indicate that the pointer + * may be NULL. In Python this is translated by accepting (or + * returning) None as an alternative to a Value. + * + * 'out_' on an argument type indicates an additional output + * parameter. The argument type in C has an extra layer of + * indirection, e.g. an 'out_val_mpint' is an 'mpint **' instead of an + * 'mpint *', identifying a pointer variable where the returned + * pointer value will be written. In the Python API, these arguments + * do not appear in the argument list of the Python function; instead + * they cause the return value to become a tuple, with additional + * types appended. For example, a declaration like + * + * FUNC(val_foo, example, (ARG(out_val_bar, bar), ARG(val_baz, baz))) + * + * would identify a function in C with the following prototype, which + * returns a 'foo *' directly and a 'bar *' by writing it through the + * provided 'bar **' pointer argument: + * + * foo *example(bar **extra_output, baz *input); + * + * and in Python this would become a function taking one argument of + * type 'baz' and returning a tuple of the form (foo, bar). + * + * 'out_' and 'opt_' can go together, if a function returns a second + * output value but it may in some cases be NULL. + * + * 'consumed_' on an argument type indicates that the C function + * receiving that argument frees it as a side effect. + * + * Any argument type which does not start 'val_' is plain old data + * with no dynamic allocation requirements. Ordinary C integers are + * sometimes handled this way (e.g. 'uint'). Other plain-data types + * are represented in Python as a string that must be one of a + * recognised set of keywords; in C these variously translate into + * enumeration types (e.g. argon2flavour, rsaorder) or pointers to + * const vtables of one kind or another (e.g. keyalg, hashalg, + * primegenpolicy). + */ + /* * mpint.h functions. */ -FUNC1(val_mpint, mp_new, uint) -FUNC1(void, mp_clear, val_mpint) -FUNC1(val_mpint, mp_from_bytes_le, val_string_ptrlen) -FUNC1(val_mpint, mp_from_bytes_be, val_string_ptrlen) -FUNC1(val_mpint, mp_from_integer, uint) -FUNC1(val_mpint, mp_from_decimal_pl, val_string_ptrlen) -FUNC1(val_mpint, mp_from_decimal, val_string_asciz) -FUNC1(val_mpint, mp_from_hex_pl, val_string_ptrlen) -FUNC1(val_mpint, mp_from_hex, val_string_asciz) -FUNC1(val_mpint, mp_copy, val_mpint) -FUNC1(val_mpint, mp_power_2, uint) -FUNC2(uint, mp_get_byte, val_mpint, uint) -FUNC2(uint, mp_get_bit, val_mpint, uint) -FUNC3(void, mp_set_bit, val_mpint, uint, uint) -FUNC1(uint, mp_max_bytes, val_mpint) -FUNC1(uint, mp_max_bits, val_mpint) -FUNC1(uint, mp_get_nbits, val_mpint) -FUNC1(val_string_asciz, mp_get_decimal, val_mpint) -FUNC1(val_string_asciz, mp_get_hex, val_mpint) -FUNC1(val_string_asciz, mp_get_hex_uppercase, val_mpint) -FUNC2(uint, mp_cmp_hs, val_mpint, val_mpint) -FUNC2(uint, mp_cmp_eq, val_mpint, val_mpint) -FUNC2(uint, mp_hs_integer, val_mpint, uint) -FUNC2(uint, mp_eq_integer, val_mpint, uint) -FUNC3(void, mp_min_into, val_mpint, val_mpint, val_mpint) -FUNC3(void, mp_max_into, val_mpint, val_mpint, val_mpint) -FUNC2(val_mpint, mp_min, val_mpint, val_mpint) -FUNC2(val_mpint, mp_max, val_mpint, val_mpint) -FUNC2(void, mp_copy_into, val_mpint, val_mpint) -FUNC4(void, mp_select_into, val_mpint, val_mpint, val_mpint, uint) -FUNC3(void, mp_add_into, val_mpint, val_mpint, val_mpint) -FUNC3(void, mp_sub_into, val_mpint, val_mpint, val_mpint) -FUNC3(void, mp_mul_into, val_mpint, val_mpint, val_mpint) -FUNC2(val_mpint, mp_add, val_mpint, val_mpint) -FUNC2(val_mpint, mp_sub, val_mpint, val_mpint) -FUNC2(val_mpint, mp_mul, val_mpint, val_mpint) -FUNC3(void, mp_and_into, val_mpint, val_mpint, val_mpint) -FUNC3(void, mp_or_into, val_mpint, val_mpint, val_mpint) -FUNC3(void, mp_xor_into, val_mpint, val_mpint, val_mpint) -FUNC3(void, mp_bic_into, val_mpint, val_mpint, val_mpint) -FUNC2(void, mp_copy_integer_into, val_mpint, uint) -FUNC3(void, mp_add_integer_into, val_mpint, val_mpint, uint) -FUNC3(void, mp_sub_integer_into, val_mpint, val_mpint, uint) -FUNC3(void, mp_mul_integer_into, val_mpint, val_mpint, uint) -FUNC4(void, mp_cond_add_into, val_mpint, val_mpint, val_mpint, uint) -FUNC4(void, mp_cond_sub_into, val_mpint, val_mpint, val_mpint, uint) -FUNC3(void, mp_cond_swap, val_mpint, val_mpint, uint) -FUNC2(void, mp_cond_clear, val_mpint, uint) -FUNC4(void, mp_divmod_into, val_mpint, val_mpint, opt_val_mpint, opt_val_mpint) -FUNC2(val_mpint, mp_div, val_mpint, val_mpint) -FUNC2(val_mpint, mp_mod, val_mpint, val_mpint) -FUNC3(val_mpint, mp_nthroot, val_mpint, uint, opt_val_mpint) -FUNC2(void, mp_reduce_mod_2to, val_mpint, uint) -FUNC2(val_mpint, mp_invert_mod_2to, val_mpint, uint) -FUNC2(val_mpint, mp_invert, val_mpint, val_mpint) -FUNC5(void, mp_gcd_into, val_mpint, val_mpint, opt_val_mpint, opt_val_mpint, opt_val_mpint) -FUNC2(val_mpint, mp_gcd, val_mpint, val_mpint) -FUNC2(uint, mp_coprime, val_mpint, val_mpint) -FUNC2(val_modsqrt, modsqrt_new, val_mpint, val_mpint) +FUNC(val_mpint, mp_new, (ARG(uint, maxbits))) +FUNC(void, mp_clear, (ARG(val_mpint, x))) +FUNC(val_mpint, mp_from_bytes_le, (ARG(val_string_ptrlen, bytes))) +FUNC(val_mpint, mp_from_bytes_be, (ARG(val_string_ptrlen, bytes))) +FUNC(val_mpint, mp_from_integer, (ARG(uint, n))) +FUNC(val_mpint, mp_from_decimal_pl, (ARG(val_string_ptrlen, decimal))) +FUNC(val_mpint, mp_from_decimal, (ARG(val_string_asciz, decimal))) +FUNC(val_mpint, mp_from_hex_pl, (ARG(val_string_ptrlen, hex))) +FUNC(val_mpint, mp_from_hex, (ARG(val_string_asciz, hex))) +FUNC(val_mpint, mp_copy, (ARG(val_mpint, x))) +FUNC(val_mpint, mp_power_2, (ARG(uint, power))) +FUNC(uint, mp_get_byte, (ARG(val_mpint, x), ARG(uint, byte))) +FUNC(uint, mp_get_bit, (ARG(val_mpint, x), ARG(uint, bit))) +FUNC(void, mp_set_bit, (ARG(val_mpint, x), ARG(uint, bit), ARG(uint, val))) +FUNC(uint, mp_max_bytes, (ARG(val_mpint, x))) +FUNC(uint, mp_max_bits, (ARG(val_mpint, x))) +FUNC(uint, mp_get_nbits, (ARG(val_mpint, x))) +FUNC(val_string_asciz, mp_get_decimal, (ARG(val_mpint, x))) +FUNC(val_string_asciz, mp_get_hex, (ARG(val_mpint, x))) +FUNC(val_string_asciz, mp_get_hex_uppercase, (ARG(val_mpint, x))) +FUNC(uint, mp_cmp_hs, (ARG(val_mpint, a), ARG(val_mpint, b))) +FUNC(uint, mp_cmp_eq, (ARG(val_mpint, a), ARG(val_mpint, b))) +FUNC(uint, mp_hs_integer, (ARG(val_mpint, x), ARG(uint, n))) +FUNC(uint, mp_eq_integer, (ARG(val_mpint, x), ARG(uint, n))) +FUNC(void, mp_min_into, (ARG(val_mpint, r), ARG(val_mpint, x), ARG(val_mpint, y))) +FUNC(void, mp_max_into, (ARG(val_mpint, r), ARG(val_mpint, x), ARG(val_mpint, y))) +FUNC(val_mpint, mp_min, (ARG(val_mpint, x), ARG(val_mpint, y))) +FUNC(val_mpint, mp_max, (ARG(val_mpint, x), ARG(val_mpint, y))) +FUNC(void, mp_copy_into, (ARG(val_mpint, dest), ARG(val_mpint, src))) +FUNC(void, mp_select_into, (ARG(val_mpint, dest), ARG(val_mpint, src0), ARG(val_mpint, src1), ARG(uint, choose_src1))) +FUNC(void, mp_add_into, (ARG(val_mpint, r), ARG(val_mpint, a), ARG(val_mpint, b))) +FUNC(void, mp_sub_into, (ARG(val_mpint, r), ARG(val_mpint, a), ARG(val_mpint, b))) +FUNC(void, mp_mul_into, (ARG(val_mpint, r), ARG(val_mpint, a), ARG(val_mpint, b))) +FUNC(val_mpint, mp_add, (ARG(val_mpint, x), ARG(val_mpint, y))) +FUNC(val_mpint, mp_sub, (ARG(val_mpint, x), ARG(val_mpint, y))) +FUNC(val_mpint, mp_mul, (ARG(val_mpint, x), ARG(val_mpint, y))) +FUNC(void, mp_and_into, (ARG(val_mpint, r), ARG(val_mpint, a), ARG(val_mpint, b))) +FUNC(void, mp_or_into, (ARG(val_mpint, r), ARG(val_mpint, a), ARG(val_mpint, b))) +FUNC(void, mp_xor_into, (ARG(val_mpint, r), ARG(val_mpint, a), ARG(val_mpint, b))) +FUNC(void, mp_bic_into, (ARG(val_mpint, r), ARG(val_mpint, a), ARG(val_mpint, b))) +FUNC(void, mp_copy_integer_into, (ARG(val_mpint, dest), ARG(uint, n))) +FUNC(void, mp_add_integer_into, (ARG(val_mpint, r), ARG(val_mpint, a), ARG(uint, n))) +FUNC(void, mp_sub_integer_into, (ARG(val_mpint, r), ARG(val_mpint, a), ARG(uint, n))) +FUNC(void, mp_mul_integer_into, (ARG(val_mpint, r), ARG(val_mpint, a), ARG(uint, n))) +FUNC(void, mp_cond_add_into, (ARG(val_mpint, r), ARG(val_mpint, a), ARG(val_mpint, b), ARG(uint, yes))) +FUNC(void, mp_cond_sub_into, (ARG(val_mpint, r), ARG(val_mpint, a), ARG(val_mpint, b), ARG(uint, yes))) +FUNC(void, mp_cond_swap, (ARG(val_mpint, x0), ARG(val_mpint, x1), ARG(uint, swap))) +FUNC(void, mp_cond_clear, (ARG(val_mpint, x), ARG(uint, clear))) +FUNC(void, mp_divmod_into, (ARG(val_mpint, n), ARG(val_mpint, d), ARG(opt_val_mpint, q), ARG(opt_val_mpint, r))) +FUNC(val_mpint, mp_div, (ARG(val_mpint, n), ARG(val_mpint, d))) +FUNC(val_mpint, mp_mod, (ARG(val_mpint, x), ARG(val_mpint, modulus))) +FUNC(val_mpint, mp_nthroot, (ARG(val_mpint, y), ARG(uint, n), ARG(opt_val_mpint, remainder))) +FUNC(void, mp_reduce_mod_2to, (ARG(val_mpint, x), ARG(uint, p))) +FUNC(val_mpint, mp_invert_mod_2to, (ARG(val_mpint, x), ARG(uint, p))) +FUNC(val_mpint, mp_invert, (ARG(val_mpint, x), ARG(val_mpint, modulus))) +FUNC(void, mp_gcd_into, (ARG(val_mpint, a), ARG(val_mpint, b), ARG(opt_val_mpint, gcd_out), ARG(opt_val_mpint, A_out), ARG(opt_val_mpint, B_out))) +FUNC(val_mpint, mp_gcd, (ARG(val_mpint, a), ARG(val_mpint, b))) +FUNC(uint, mp_coprime, (ARG(val_mpint, a), ARG(val_mpint, b))) +FUNC(val_modsqrt, modsqrt_new, (ARG(val_mpint, p), ARG(val_mpint, any_nonsquare_mod_p))) /* The modsqrt functions' 'success' pointer becomes a second return value */ -FUNC3(val_mpint, mp_modsqrt, val_modsqrt, val_mpint, out_uint) -FUNC1(val_monty, monty_new, val_mpint) -FUNC1(val_mpint, monty_modulus, val_monty) -FUNC1(val_mpint, monty_identity, val_monty) -FUNC3(void, monty_import_into, val_monty, val_mpint, val_mpint) -FUNC2(val_mpint, monty_import, val_monty, val_mpint) -FUNC3(void, monty_export_into, val_monty, val_mpint, val_mpint) -FUNC2(val_mpint, monty_export, val_monty, val_mpint) -FUNC4(void, monty_mul_into, val_monty, val_mpint, val_mpint, val_mpint) -FUNC3(val_mpint, monty_add, val_monty, val_mpint, val_mpint) -FUNC3(val_mpint, monty_sub, val_monty, val_mpint, val_mpint) -FUNC3(val_mpint, monty_mul, val_monty, val_mpint, val_mpint) -FUNC3(val_mpint, monty_pow, val_monty, val_mpint, val_mpint) -FUNC2(val_mpint, monty_invert, val_monty, val_mpint) -FUNC3(val_mpint, monty_modsqrt, val_modsqrt, val_mpint, out_uint) -FUNC3(val_mpint, mp_modpow, val_mpint, val_mpint, val_mpint) -FUNC3(val_mpint, mp_modmul, val_mpint, val_mpint, val_mpint) -FUNC3(val_mpint, mp_modadd, val_mpint, val_mpint, val_mpint) -FUNC3(val_mpint, mp_modsub, val_mpint, val_mpint, val_mpint) -FUNC3(void, mp_lshift_safe_into, val_mpint, val_mpint, uint) -FUNC3(void, mp_rshift_safe_into, val_mpint, val_mpint, uint) -FUNC2(val_mpint, mp_rshift_safe, val_mpint, uint) -FUNC3(void, mp_lshift_fixed_into, val_mpint, val_mpint, uint) -FUNC3(void, mp_rshift_fixed_into, val_mpint, val_mpint, uint) -FUNC2(val_mpint, mp_rshift_fixed, val_mpint, uint) -FUNC1(val_mpint, mp_random_bits, uint) -FUNC2(val_mpint, mp_random_in_range, val_mpint, val_mpint) +FUNC(val_mpint, mp_modsqrt, (ARG(val_modsqrt, sc), ARG(val_mpint, x), ARG(out_uint, success))) +FUNC(val_monty, monty_new, (ARG(val_mpint, modulus))) +FUNC(val_mpint, monty_modulus, (ARG(val_monty, mc))) +FUNC(val_mpint, monty_identity, (ARG(val_monty, mc))) +FUNC(void, monty_import_into, (ARG(val_monty, mc), ARG(val_mpint, r), ARG(val_mpint, x))) +FUNC(val_mpint, monty_import, (ARG(val_monty, mc), ARG(val_mpint, x))) +FUNC(void, monty_export_into, (ARG(val_monty, mc), ARG(val_mpint, r), ARG(val_mpint, x))) +FUNC(val_mpint, monty_export, (ARG(val_monty, mc), ARG(val_mpint, x))) +FUNC(void, monty_mul_into, (ARG(val_monty, mc), ARG(val_mpint, r), ARG(val_mpint, x), ARG(val_mpint, y))) +FUNC(val_mpint, monty_add, (ARG(val_monty, mc), ARG(val_mpint, x), ARG(val_mpint, y))) +FUNC(val_mpint, monty_sub, (ARG(val_monty, mc), ARG(val_mpint, x), ARG(val_mpint, y))) +FUNC(val_mpint, monty_mul, (ARG(val_monty, mc), ARG(val_mpint, x), ARG(val_mpint, y))) +FUNC(val_mpint, monty_pow, (ARG(val_monty, mc), ARG(val_mpint, base), ARG(val_mpint, exponent))) +FUNC(val_mpint, monty_invert, (ARG(val_monty, mc), ARG(val_mpint, x))) +FUNC(val_mpint, monty_modsqrt, (ARG(val_modsqrt, sc), ARG(val_mpint, mx), ARG(out_uint, success))) +FUNC(val_mpint, mp_modpow, (ARG(val_mpint, base), ARG(val_mpint, exponent), ARG(val_mpint, modulus))) +FUNC(val_mpint, mp_modmul, (ARG(val_mpint, x), ARG(val_mpint, y), ARG(val_mpint, modulus))) +FUNC(val_mpint, mp_modadd, (ARG(val_mpint, x), ARG(val_mpint, y), ARG(val_mpint, modulus))) +FUNC(val_mpint, mp_modsub, (ARG(val_mpint, x), ARG(val_mpint, y), ARG(val_mpint, modulus))) +FUNC(void, mp_lshift_safe_into, (ARG(val_mpint, r), ARG(val_mpint, x), ARG(uint, shift))) +FUNC(void, mp_rshift_safe_into, (ARG(val_mpint, r), ARG(val_mpint, x), ARG(uint, shift))) +FUNC(val_mpint, mp_rshift_safe, (ARG(val_mpint, x), ARG(uint, shift))) +FUNC(void, mp_lshift_fixed_into, (ARG(val_mpint, r), ARG(val_mpint, a), ARG(uint, shift))) +FUNC(void, mp_rshift_fixed_into, (ARG(val_mpint, r), ARG(val_mpint, x), ARG(uint, shift))) +FUNC(val_mpint, mp_rshift_fixed, (ARG(val_mpint, x), ARG(uint, shift))) +FUNC(val_mpint, mp_random_bits, (ARG(uint, bits))) +FUNC(val_mpint, mp_random_in_range, (ARG(val_mpint, lo), ARG(val_mpint, hi))) /* * ecc.h functions. */ -FUNC4(val_wcurve, ecc_weierstrass_curve, val_mpint, val_mpint, val_mpint, opt_val_mpint) -FUNC1(val_wpoint, ecc_weierstrass_point_new_identity, val_wcurve) -FUNC3(val_wpoint, ecc_weierstrass_point_new, val_wcurve, val_mpint, val_mpint) -FUNC3(val_wpoint, ecc_weierstrass_point_new_from_x, val_wcurve, val_mpint, uint) -FUNC1(val_wpoint, ecc_weierstrass_point_copy, val_wpoint) -FUNC1(uint, ecc_weierstrass_point_valid, val_wpoint) -FUNC2(val_wpoint, ecc_weierstrass_add_general, val_wpoint, val_wpoint) -FUNC2(val_wpoint, ecc_weierstrass_add, val_wpoint, val_wpoint) -FUNC1(val_wpoint, ecc_weierstrass_double, val_wpoint) -FUNC2(val_wpoint, ecc_weierstrass_multiply, val_wpoint, val_mpint) -FUNC1(uint, ecc_weierstrass_is_identity, val_wpoint) +FUNC(val_wcurve, ecc_weierstrass_curve, (ARG(val_mpint, p), ARG(val_mpint, a), ARG(val_mpint, b), ARG(opt_val_mpint, nonsquare_mod_p))) +FUNC(val_wpoint, ecc_weierstrass_point_new_identity, (ARG(val_wcurve, curve))) +FUNC(val_wpoint, ecc_weierstrass_point_new, (ARG(val_wcurve, curve), ARG(val_mpint, x), ARG(val_mpint, y))) +FUNC(val_wpoint, ecc_weierstrass_point_new_from_x, (ARG(val_wcurve, curve), ARG(val_mpint, x), ARG(uint, desired_y_parity))) +FUNC(val_wpoint, ecc_weierstrass_point_copy, (ARG(val_wpoint, wc))) +FUNC(uint, ecc_weierstrass_point_valid, (ARG(val_wpoint, P))) +FUNC(val_wpoint, ecc_weierstrass_add_general, (ARG(val_wpoint, P), ARG(val_wpoint, Q))) +FUNC(val_wpoint, ecc_weierstrass_add, (ARG(val_wpoint, P), ARG(val_wpoint, Q))) +FUNC(val_wpoint, ecc_weierstrass_double, (ARG(val_wpoint, P))) +FUNC(val_wpoint, ecc_weierstrass_multiply, (ARG(val_wpoint, B), ARG(val_mpint, n))) +FUNC(uint, ecc_weierstrass_is_identity, (ARG(val_wpoint, wp))) /* The output pointers in get_affine all become extra output values */ -FUNC3(void, ecc_weierstrass_get_affine, val_wpoint, out_val_mpint, out_val_mpint) -FUNC3(val_mcurve, ecc_montgomery_curve, val_mpint, val_mpint, val_mpint) -FUNC2(val_mpoint, ecc_montgomery_point_new, val_mcurve, val_mpint) -FUNC1(val_mpoint, ecc_montgomery_point_copy, val_mpoint) -FUNC3(val_mpoint, ecc_montgomery_diff_add, val_mpoint, val_mpoint, val_mpoint) -FUNC1(val_mpoint, ecc_montgomery_double, val_mpoint) -FUNC2(val_mpoint, ecc_montgomery_multiply, val_mpoint, val_mpint) -FUNC2(void, ecc_montgomery_get_affine, val_mpoint, out_val_mpint) -FUNC1(boolean, ecc_montgomery_is_identity, val_mpoint) -FUNC4(val_ecurve, ecc_edwards_curve, val_mpint, val_mpint, val_mpint, opt_val_mpint) -FUNC3(val_epoint, ecc_edwards_point_new, val_ecurve, val_mpint, val_mpint) -FUNC3(val_epoint, ecc_edwards_point_new_from_y, val_ecurve, val_mpint, uint) -FUNC1(val_epoint, ecc_edwards_point_copy, val_epoint) -FUNC2(val_epoint, ecc_edwards_add, val_epoint, val_epoint) -FUNC2(val_epoint, ecc_edwards_multiply, val_epoint, val_mpint) -FUNC2(uint, ecc_edwards_eq, val_epoint, val_epoint) -FUNC3(void, ecc_edwards_get_affine, val_epoint, out_val_mpint, out_val_mpint) +FUNC(void, ecc_weierstrass_get_affine, (ARG(val_wpoint, wp), ARG(out_val_mpint, x), ARG(out_val_mpint, y))) +FUNC(val_mcurve, ecc_montgomery_curve, (ARG(val_mpint, p), ARG(val_mpint, a), ARG(val_mpint, b))) +FUNC(val_mpoint, ecc_montgomery_point_new, (ARG(val_mcurve, mc), ARG(val_mpint, x))) +FUNC(val_mpoint, ecc_montgomery_point_copy, (ARG(val_mpoint, orig))) +FUNC(val_mpoint, ecc_montgomery_diff_add, (ARG(val_mpoint, P), ARG(val_mpoint, Q), ARG(val_mpoint, PminusQ))) +FUNC(val_mpoint, ecc_montgomery_double, (ARG(val_mpoint, P))) +FUNC(val_mpoint, ecc_montgomery_multiply, (ARG(val_mpoint, B), ARG(val_mpint, n))) +FUNC(void, ecc_montgomery_get_affine, (ARG(val_mpoint, mp), ARG(out_val_mpint, x))) +FUNC(boolean, ecc_montgomery_is_identity, (ARG(val_mpoint, mp))) +FUNC(val_ecurve, ecc_edwards_curve, (ARG(val_mpint, p), ARG(val_mpint, d), ARG(val_mpint, a), ARG(opt_val_mpint, nonsquare_mod_p))) +FUNC(val_epoint, ecc_edwards_point_new, (ARG(val_ecurve, curve), ARG(val_mpint, x), ARG(val_mpint, y))) +FUNC(val_epoint, ecc_edwards_point_new_from_y, (ARG(val_ecurve, curve), ARG(val_mpint, y), ARG(uint, desired_x_parity))) +FUNC(val_epoint, ecc_edwards_point_copy, (ARG(val_epoint, ec))) +FUNC(val_epoint, ecc_edwards_add, (ARG(val_epoint, P), ARG(val_epoint, Q))) +FUNC(val_epoint, ecc_edwards_multiply, (ARG(val_epoint, B), ARG(val_mpint, n))) +FUNC(uint, ecc_edwards_eq, (ARG(val_epoint, P), ARG(val_epoint, Q))) +FUNC(void, ecc_edwards_get_affine, (ARG(val_epoint, wp), ARG(out_val_mpint, x), ARG(out_val_mpint, y))) /* * The ssh_hash abstraction. Note the 'consumed', indicating that @@ -129,26 +193,26 @@ FUNC3(void, ecc_edwards_get_affine, val_epoint, out_val_mpint, out_val_mpint) * ssh_hash_update is an invention of testcrypt, handled in the real C * API by the hash object also functioning as a BinarySink. */ -FUNC1(opt_val_hash, ssh_hash_new, hashalg) -FUNC1(void, ssh_hash_reset, val_hash) -FUNC1(val_hash, ssh_hash_copy, val_hash) -FUNC1(val_string, ssh_hash_digest, val_hash) -FUNC1(val_string, ssh_hash_final, consumed_val_hash) -FUNC2(void, ssh_hash_update, val_hash, val_string_ptrlen) +FUNC(opt_val_hash, ssh_hash_new, (ARG(hashalg, alg))) +FUNC(void, ssh_hash_reset, (ARG(val_hash, h))) +FUNC(val_hash, ssh_hash_copy, (ARG(val_hash, orig))) +FUNC(val_string, ssh_hash_digest, (ARG(val_hash, h))) +FUNC(val_string, ssh_hash_final, (ARG(consumed_val_hash, h))) +FUNC(void, ssh_hash_update, (ARG(val_hash, h), ARG(val_string_ptrlen, pl))) -FUNC1(opt_val_hash, blake2b_new_general, uint) +FUNC(opt_val_hash, blake2b_new_general, (ARG(uint, hashlen))) /* * The ssh2_mac abstraction. Note the optional ssh_cipher parameter * to ssh2_mac_new. Also, again, I've invented an ssh2_mac_update so * you can put data into the MAC. */ -FUNC2(val_mac, ssh2_mac_new, macalg, opt_val_cipher) -FUNC2(void, ssh2_mac_setkey, val_mac, val_string_ptrlen) -FUNC1(void, ssh2_mac_start, val_mac) -FUNC2(void, ssh2_mac_update, val_mac, val_string_ptrlen) -FUNC1(val_string, ssh2_mac_genresult, val_mac) -FUNC1(val_string_asciz_const, ssh2_mac_text_name, val_mac) +FUNC(val_mac, ssh2_mac_new, (ARG(macalg, alg), ARG(opt_val_cipher, cipher))) +FUNC(void, ssh2_mac_setkey, (ARG(val_mac, m), ARG(val_string_ptrlen, key))) +FUNC(void, ssh2_mac_start, (ARG(val_mac, m))) +FUNC(void, ssh2_mac_update, (ARG(val_mac, m), ARG(val_string_ptrlen, pl))) +FUNC(val_string, ssh2_mac_genresult, (ARG(val_mac, m))) +FUNC(val_string_asciz_const, ssh2_mac_text_name, (ARG(val_mac, m))) /* * The ssh_key abstraction. All the uses of BinarySink and @@ -157,174 +221,174 @@ FUNC1(val_string_asciz_const, ssh2_mac_text_name, val_mac) * all the functions that output key and signature blobs do it by * returning a string. */ -FUNC2(val_key, ssh_key_new_pub, keyalg, val_string_ptrlen) -FUNC3(opt_val_key, ssh_key_new_priv, keyalg, val_string_ptrlen, val_string_ptrlen) -FUNC2(opt_val_key, ssh_key_new_priv_openssh, keyalg, val_string_binarysource) -FUNC2(opt_val_string_asciz, ssh_key_invalid, val_key, uint) -FUNC4(void, ssh_key_sign, val_key, val_string_ptrlen, uint, out_val_string_binarysink) -FUNC3(boolean, ssh_key_verify, val_key, val_string_ptrlen, val_string_ptrlen) -FUNC2(void, ssh_key_public_blob, val_key, out_val_string_binarysink) -FUNC2(void, ssh_key_private_blob, val_key, out_val_string_binarysink) -FUNC2(void, ssh_key_openssh_blob, val_key, out_val_string_binarysink) -FUNC1(val_string_asciz, ssh_key_cache_str, val_key) -FUNC1(val_keycomponents, ssh_key_components, val_key) -FUNC2(uint, ssh_key_public_bits, keyalg, val_string_ptrlen) +FUNC(val_key, ssh_key_new_pub, (ARG(keyalg, self), ARG(val_string_ptrlen, pub))) +FUNC(opt_val_key, ssh_key_new_priv, (ARG(keyalg, self), ARG(val_string_ptrlen, pub), ARG(val_string_ptrlen, priv))) +FUNC(opt_val_key, ssh_key_new_priv_openssh, (ARG(keyalg, self), ARG(val_string_binarysource, src))) +FUNC(opt_val_string_asciz, ssh_key_invalid, (ARG(val_key, key), ARG(uint, flags))) +FUNC(void, ssh_key_sign, (ARG(val_key, key), ARG(val_string_ptrlen, data), ARG(uint, flags), ARG(out_val_string_binarysink, bs))) +FUNC(boolean, ssh_key_verify, (ARG(val_key, key), ARG(val_string_ptrlen, sig), ARG(val_string_ptrlen, data))) +FUNC(void, ssh_key_public_blob, (ARG(val_key, key), ARG(out_val_string_binarysink, bs))) +FUNC(void, ssh_key_private_blob, (ARG(val_key, key), ARG(out_val_string_binarysink, bs))) +FUNC(void, ssh_key_openssh_blob, (ARG(val_key, key), ARG(out_val_string_binarysink, bs))) +FUNC(val_string_asciz, ssh_key_cache_str, (ARG(val_key, key))) +FUNC(val_keycomponents, ssh_key_components, (ARG(val_key, key))) +FUNC(uint, ssh_key_public_bits, (ARG(keyalg, self), ARG(val_string_ptrlen, blob))) /* * Accessors to retrieve the innards of a 'key_components'. */ -FUNC1(uint, key_components_count, val_keycomponents) -FUNC2(opt_val_string_asciz_const, key_components_nth_name, val_keycomponents, uint) -FUNC2(opt_val_string_asciz_const, key_components_nth_str, val_keycomponents, uint) -FUNC2(opt_val_mpint, key_components_nth_mp, val_keycomponents, uint) +FUNC(uint, key_components_count, (ARG(val_keycomponents, kc))) +FUNC(opt_val_string_asciz_const, key_components_nth_name, (ARG(val_keycomponents, kc), ARG(uint, n))) +FUNC(opt_val_string_asciz_const, key_components_nth_str, (ARG(val_keycomponents, kc), ARG(uint, n))) +FUNC(opt_val_mpint, key_components_nth_mp, (ARG(val_keycomponents, kc), ARG(uint, n))) /* * The ssh_cipher abstraction. The in-place encrypt and decrypt * functions are wrapped to replace them with versions that take one * string and return a separate string. */ -FUNC1(opt_val_cipher, ssh_cipher_new, cipheralg) -FUNC2(void, ssh_cipher_setiv, val_cipher, val_string_ptrlen) -FUNC2(void, ssh_cipher_setkey, val_cipher, val_string_ptrlen) -FUNC2(val_string, ssh_cipher_encrypt, val_cipher, val_string_ptrlen) -FUNC2(val_string, ssh_cipher_decrypt, val_cipher, val_string_ptrlen) -FUNC3(val_string, ssh_cipher_encrypt_length, val_cipher, val_string_ptrlen, uint) -FUNC3(val_string, ssh_cipher_decrypt_length, val_cipher, val_string_ptrlen, uint) +FUNC(opt_val_cipher, ssh_cipher_new, (ARG(cipheralg, alg))) +FUNC(void, ssh_cipher_setiv, (ARG(val_cipher, c), ARG(val_string_ptrlen, iv))) +FUNC(void, ssh_cipher_setkey, (ARG(val_cipher, c), ARG(val_string_ptrlen, key))) +FUNC(val_string, ssh_cipher_encrypt, (ARG(val_cipher, c), ARG(val_string_ptrlen, blk))) +FUNC(val_string, ssh_cipher_decrypt, (ARG(val_cipher, c), ARG(val_string_ptrlen, blk))) +FUNC(val_string, ssh_cipher_encrypt_length, (ARG(val_cipher, c), ARG(val_string_ptrlen, blk), ARG(uint, seq))) +FUNC(val_string, ssh_cipher_decrypt_length, (ARG(val_cipher, c), ARG(val_string_ptrlen, blk), ARG(uint, seq))) /* * Integer Diffie-Hellman. */ -FUNC1(val_dh, dh_setup_group, dh_group) -FUNC2(val_dh, dh_setup_gex, val_mpint, val_mpint) -FUNC1(uint, dh_modulus_bit_size, val_dh) -FUNC2(val_mpint, dh_create_e, val_dh, uint) -FUNC2(boolean, dh_validate_f, val_dh, val_mpint) -FUNC2(val_mpint, dh_find_K, val_dh, val_mpint) +FUNC(val_dh, dh_setup_group, (ARG(dh_group, kex))) +FUNC(val_dh, dh_setup_gex, (ARG(val_mpint, pval), ARG(val_mpint, gval))) +FUNC(uint, dh_modulus_bit_size, (ARG(val_dh, ctx))) +FUNC(val_mpint, dh_create_e, (ARG(val_dh, ctx), ARG(uint, nbits))) +FUNC(boolean, dh_validate_f, (ARG(val_dh, ctx), ARG(val_mpint, f))) +FUNC(val_mpint, dh_find_K, (ARG(val_dh, ctx), ARG(val_mpint, f))) /* * Elliptic-curve Diffie-Hellman. */ -FUNC1(val_ecdh, ssh_ecdhkex_newkey, ecdh_alg) -FUNC2(void, ssh_ecdhkex_getpublic, val_ecdh, out_val_string_binarysink) -FUNC2(opt_val_mpint, ssh_ecdhkex_getkey, val_ecdh, val_string_ptrlen) +FUNC(val_ecdh, ssh_ecdhkex_newkey, (ARG(ecdh_alg, kex))) +FUNC(void, ssh_ecdhkex_getpublic, (ARG(val_ecdh, key), ARG(out_val_string_binarysink, bs))) +FUNC(opt_val_mpint, ssh_ecdhkex_getkey, (ARG(val_ecdh, key), ARG(val_string_ptrlen, remoteKey))) /* * RSA key exchange, and also the BinarySource get function * get_ssh1_rsa_priv_agent, which is a convenient way to make an * RSAKey for RSA kex testing purposes. */ -FUNC1(val_rsakex, ssh_rsakex_newkey, val_string_ptrlen) -FUNC1(uint, ssh_rsakex_klen, val_rsakex) -FUNC3(val_string, ssh_rsakex_encrypt, val_rsakex, hashalg, val_string_ptrlen) -FUNC3(opt_val_mpint, ssh_rsakex_decrypt, val_rsakex, hashalg, val_string_ptrlen) -FUNC1(val_rsakex, get_rsa_ssh1_priv_agent, val_string_binarysource) +FUNC(val_rsakex, ssh_rsakex_newkey, (ARG(val_string_ptrlen, data))) +FUNC(uint, ssh_rsakex_klen, (ARG(val_rsakex, key))) +FUNC(val_string, ssh_rsakex_encrypt, (ARG(val_rsakex, key), ARG(hashalg, h), ARG(val_string_ptrlen, plaintext))) +FUNC(opt_val_mpint, ssh_rsakex_decrypt, (ARG(val_rsakex, key), ARG(hashalg, h), ARG(val_string_ptrlen, ciphertext))) +FUNC(val_rsakex, get_rsa_ssh1_priv_agent, (ARG(val_string_binarysource, src))) /* * Bare RSA keys as used in SSH-1. The construction API functions * write into an existing RSAKey object, so I've invented an 'rsa_new' * function to make one in the first place. */ -FUNC0(val_rsa, rsa_new) -FUNC3(void, get_rsa_ssh1_pub, val_string_binarysource, val_rsa, rsaorder) -FUNC2(void, get_rsa_ssh1_priv, val_string_binarysource, val_rsa) -FUNC2(opt_val_string, rsa_ssh1_encrypt, val_string_ptrlen, val_rsa) -FUNC2(val_mpint, rsa_ssh1_decrypt, val_mpint, val_rsa) -FUNC2(val_string, rsa_ssh1_decrypt_pkcs1, val_mpint, val_rsa) -FUNC1(val_string_asciz, rsastr_fmt, val_rsa) -FUNC1(val_string_asciz, rsa_ssh1_fingerprint, val_rsa) -FUNC3(void, rsa_ssh1_public_blob, out_val_string_binarysink, val_rsa, rsaorder) -FUNC1(int, rsa_ssh1_public_blob_len, val_string_ptrlen) -FUNC2(void, rsa_ssh1_private_blob_agent, out_val_string_binarysink, val_rsa) +FUNC(val_rsa, rsa_new, (VOID)) +FUNC(void, get_rsa_ssh1_pub, (ARG(val_string_binarysource, src), ARG(val_rsa, key), ARG(rsaorder, order))) +FUNC(void, get_rsa_ssh1_priv, (ARG(val_string_binarysource, src), ARG(val_rsa, key))) +FUNC(opt_val_string, rsa_ssh1_encrypt, (ARG(val_string_ptrlen, data), ARG(val_rsa, key))) +FUNC(val_mpint, rsa_ssh1_decrypt, (ARG(val_mpint, input), ARG(val_rsa, key))) +FUNC(val_string, rsa_ssh1_decrypt_pkcs1, (ARG(val_mpint, input), ARG(val_rsa, key))) +FUNC(val_string_asciz, rsastr_fmt, (ARG(val_rsa, key))) +FUNC(val_string_asciz, rsa_ssh1_fingerprint, (ARG(val_rsa, key))) +FUNC(void, rsa_ssh1_public_blob, (ARG(out_val_string_binarysink, bs), ARG(val_rsa, key), ARG(rsaorder, order))) +FUNC(int, rsa_ssh1_public_blob_len, (ARG(val_string_ptrlen, data))) +FUNC(void, rsa_ssh1_private_blob_agent, (ARG(out_val_string_binarysink, bs), ARG(val_rsa, key))) /* * The PRNG type. Similarly to hashes and MACs, I've invented an extra * function prng_seed_update for putting seed data into the PRNG's * exposed BinarySink. */ -FUNC1(val_prng, prng_new, hashalg) -FUNC1(void, prng_seed_begin, val_prng) -FUNC2(void, prng_seed_update, val_prng, val_string_ptrlen) -FUNC1(void, prng_seed_finish, val_prng) -FUNC2(val_string, prng_read, val_prng, uint) -FUNC3(void, prng_add_entropy, val_prng, uint, val_string_ptrlen) +FUNC(val_prng, prng_new, (ARG(hashalg, hashalg))) +FUNC(void, prng_seed_begin, (ARG(val_prng, p))) +FUNC(void, prng_seed_update, (ARG(val_prng, pr), ARG(val_string_ptrlen, data))) +FUNC(void, prng_seed_finish, (ARG(val_prng, p))) +FUNC(val_string, prng_read, (ARG(val_prng, p), ARG(uint, size))) +FUNC(void, prng_add_entropy, (ARG(val_prng, p), ARG(uint, source_id), ARG(val_string_ptrlen, data))) /* * Key load/save functions, or rather, the BinarySource / strbuf API * that sits just inside the file I/O versions. */ -FUNC2(boolean, ppk_encrypted_s, val_string_binarysource, out_opt_val_string_asciz) -FUNC2(boolean, rsa1_encrypted_s, val_string_binarysource, out_opt_val_string_asciz) -FUNC5(boolean, ppk_loadpub_s, val_string_binarysource, out_opt_val_string_asciz, out_val_string_binarysink, out_opt_val_string_asciz, out_opt_val_string_asciz_const) -FUNC4(int, rsa1_loadpub_s, val_string_binarysource, out_val_string_binarysink, out_opt_val_string_asciz, out_opt_val_string_asciz_const) -FUNC4(opt_val_key, ppk_load_s, val_string_binarysource, out_opt_val_string_asciz, opt_val_string_asciz, out_opt_val_string_asciz_const) -FUNC5(int, rsa1_load_s, val_string_binarysource, val_rsa, out_opt_val_string_asciz, opt_val_string_asciz, out_opt_val_string_asciz_const) -FUNC8(val_string, ppk_save_sb, val_key, opt_val_string_asciz, opt_val_string_asciz, uint, argon2flavour, uint, uint, uint) -FUNC3(val_string, rsa1_save_sb, val_rsa, opt_val_string_asciz, opt_val_string_asciz) +FUNC(boolean, ppk_encrypted_s, (ARG(val_string_binarysource, src), ARG(out_opt_val_string_asciz, comment))) +FUNC(boolean, rsa1_encrypted_s, (ARG(val_string_binarysource, src), ARG(out_opt_val_string_asciz, comment))) +FUNC(boolean, ppk_loadpub_s, (ARG(val_string_binarysource, src), ARG(out_opt_val_string_asciz, algorithm), ARG(out_val_string_binarysink, bs), ARG(out_opt_val_string_asciz, commentptr), ARG(out_opt_val_string_asciz_const, errorstr))) +FUNC(int, rsa1_loadpub_s, (ARG(val_string_binarysource, src), ARG(out_val_string_binarysink, bs), ARG(out_opt_val_string_asciz, commentptr), ARG(out_opt_val_string_asciz_const, errorstr))) +FUNC(opt_val_key, ppk_load_s, (ARG(val_string_binarysource, src), ARG(out_opt_val_string_asciz, comment), ARG(opt_val_string_asciz, passphrase), ARG(out_opt_val_string_asciz_const, errorstr))) +FUNC(int, rsa1_load_s, (ARG(val_string_binarysource, src), ARG(val_rsa, key), ARG(out_opt_val_string_asciz, comment), ARG(opt_val_string_asciz, passphrase), ARG(out_opt_val_string_asciz_const, errorstr))) +FUNC(val_string, ppk_save_sb, (ARG(val_key, key), ARG(opt_val_string_asciz, comment), ARG(opt_val_string_asciz, passphrase), ARG(uint, fmt_version), ARG(argon2flavour, flavour), ARG(uint, mem), ARG(uint, passes), ARG(uint, parallel))) +FUNC(val_string, rsa1_save_sb, (ARG(val_rsa, key), ARG(opt_val_string_asciz, comment), ARG(opt_val_string_asciz, passphrase))) -FUNC2(val_string_asciz, ssh2_fingerprint_blob, val_string_ptrlen, fptype) +FUNC(val_string_asciz, ssh2_fingerprint_blob, (ARG(val_string_ptrlen, blob), ARG(fptype, fptype))) /* * Password hashing. */ -FUNC9(val_string, argon2, argon2flavour, uint, uint, uint, uint, val_string_ptrlen, val_string_ptrlen, val_string_ptrlen, val_string_ptrlen) -FUNC2(val_string, argon2_long_hash, uint, val_string_ptrlen) +FUNC(val_string, argon2, (ARG(argon2flavour, flavour), ARG(uint, mem), ARG(uint, passes), ARG(uint, parallel), ARG(uint, taglen), ARG(val_string_ptrlen, P), ARG(val_string_ptrlen, S), ARG(val_string_ptrlen, K), ARG(val_string_ptrlen, X))) +FUNC(val_string, argon2_long_hash, (ARG(uint, length), ARG(val_string_ptrlen, data))) /* * Key generation functions. */ -FUNC3(val_key, rsa_generate, uint, boolean, val_pgc) -FUNC2(val_key, dsa_generate, uint, val_pgc) -FUNC1(opt_val_key, ecdsa_generate, uint) -FUNC1(opt_val_key, eddsa_generate, uint) -FUNC3(val_rsa, rsa1_generate, uint, boolean, val_pgc) -FUNC1(val_pgc, primegen_new_context, primegenpolicy) -FUNC2(opt_val_mpint, primegen_generate, val_pgc, consumed_val_pcs) -FUNC2(val_string, primegen_mpu_certificate, val_pgc, val_mpint) -FUNC1(val_pcs, pcs_new, uint) -FUNC3(val_pcs, pcs_new_with_firstbits, uint, uint, uint) -FUNC3(void, pcs_require_residue, val_pcs, val_mpint, val_mpint) -FUNC2(void, pcs_require_residue_1, val_pcs, val_mpint) -FUNC2(void, pcs_require_residue_1_mod_prime, val_pcs, val_mpint) -FUNC3(void, pcs_avoid_residue_small, val_pcs, uint, uint) -FUNC1(void, pcs_try_sophie_germain, val_pcs) -FUNC1(void, pcs_set_oneshot, val_pcs) -FUNC1(void, pcs_ready, val_pcs) -FUNC4(void, pcs_inspect, val_pcs, out_val_mpint, out_val_mpint, out_val_mpint) -FUNC1(val_mpint, pcs_generate, val_pcs) -FUNC0(val_pockle, pockle_new) -FUNC1(uint, pockle_mark, val_pockle) -FUNC2(void, pockle_release, val_pockle, uint) -FUNC2(pocklestatus, pockle_add_small_prime, val_pockle, val_mpint) -FUNC4(pocklestatus, pockle_add_prime, val_pockle, val_mpint, mpint_list, val_mpint) -FUNC2(val_string, pockle_mpu, val_pockle, val_mpint) -FUNC1(val_millerrabin, miller_rabin_new, val_mpint) -FUNC2(mr_result, miller_rabin_test, val_millerrabin, val_mpint) +FUNC(val_key, rsa_generate, (ARG(uint, bits), ARG(boolean, strong), ARG(val_pgc, pgc))) +FUNC(val_key, dsa_generate, (ARG(uint, bits), ARG(val_pgc, pgc))) +FUNC(opt_val_key, ecdsa_generate, (ARG(uint, bits))) +FUNC(opt_val_key, eddsa_generate, (ARG(uint, bits))) +FUNC(val_rsa, rsa1_generate, (ARG(uint, bits), ARG(boolean, strong), ARG(val_pgc, pgc))) +FUNC(val_pgc, primegen_new_context, (ARG(primegenpolicy, policy))) +FUNC(opt_val_mpint, primegen_generate, (ARG(val_pgc, ctx), ARG(consumed_val_pcs, pcs))) +FUNC(val_string, primegen_mpu_certificate, (ARG(val_pgc, ctx), ARG(val_mpint, p))) +FUNC(val_pcs, pcs_new, (ARG(uint, bits))) +FUNC(val_pcs, pcs_new_with_firstbits, (ARG(uint, bits), ARG(uint, first), ARG(uint, nfirst))) +FUNC(void, pcs_require_residue, (ARG(val_pcs, s), ARG(val_mpint, mod), ARG(val_mpint, res))) +FUNC(void, pcs_require_residue_1, (ARG(val_pcs, s), ARG(val_mpint, mod))) +FUNC(void, pcs_require_residue_1_mod_prime, (ARG(val_pcs, s), ARG(val_mpint, mod))) +FUNC(void, pcs_avoid_residue_small, (ARG(val_pcs, s), ARG(uint, mod), ARG(uint, res))) +FUNC(void, pcs_try_sophie_germain, (ARG(val_pcs, s))) +FUNC(void, pcs_set_oneshot, (ARG(val_pcs, s))) +FUNC(void, pcs_ready, (ARG(val_pcs, s))) +FUNC(void, pcs_inspect, (ARG(val_pcs, pcs), ARG(out_val_mpint, limit_out), ARG(out_val_mpint, factor_out), ARG(out_val_mpint, addend_out))) +FUNC(val_mpint, pcs_generate, (ARG(val_pcs, s))) +FUNC(val_pockle, pockle_new, (VOID)) +FUNC(uint, pockle_mark, (ARG(val_pockle, pockle))) +FUNC(void, pockle_release, (ARG(val_pockle, pockle), ARG(uint, mark))) +FUNC(pocklestatus, pockle_add_small_prime, (ARG(val_pockle, pockle), ARG(val_mpint, p))) +FUNC(pocklestatus, pockle_add_prime, (ARG(val_pockle, pockle), ARG(val_mpint, p), ARG(mpint_list, factors), ARG(val_mpint, witness))) +FUNC(val_string, pockle_mpu, (ARG(val_pockle, pockle), ARG(val_mpint, p))) +FUNC(val_millerrabin, miller_rabin_new, (ARG(val_mpint, p))) +FUNC(mr_result, miller_rabin_test, (ARG(val_millerrabin, mr), ARG(val_mpint, w))) /* * Miscellaneous. */ -FUNC2(val_wpoint, ecdsa_public, val_mpint, keyalg) -FUNC2(val_epoint, eddsa_public, val_mpint, keyalg) -FUNC2(val_string, des_encrypt_xdmauth, val_string_ptrlen, val_string_ptrlen) -FUNC2(val_string, des_decrypt_xdmauth, val_string_ptrlen, val_string_ptrlen) -FUNC2(val_string, des3_encrypt_pubkey, val_string_ptrlen, val_string_ptrlen) -FUNC2(val_string, des3_decrypt_pubkey, val_string_ptrlen, val_string_ptrlen) -FUNC3(val_string, des3_encrypt_pubkey_ossh, val_string_ptrlen, val_string_ptrlen, val_string_ptrlen) -FUNC3(val_string, des3_decrypt_pubkey_ossh, val_string_ptrlen, val_string_ptrlen, val_string_ptrlen) -FUNC3(val_string, aes256_encrypt_pubkey, val_string_ptrlen, val_string_ptrlen, val_string_ptrlen) -FUNC3(val_string, aes256_decrypt_pubkey, val_string_ptrlen, val_string_ptrlen, val_string_ptrlen) -FUNC1(uint, crc32_rfc1662, val_string_ptrlen) -FUNC1(uint, crc32_ssh1, val_string_ptrlen) -FUNC2(uint, crc32_update, uint, val_string_ptrlen) -FUNC2(boolean, crcda_detect, val_string_ptrlen, val_string_ptrlen) -FUNC1(val_string, get_implementations_commasep, val_string_ptrlen) -FUNC12(void, http_digest_response, out_val_string_binarysink, val_string_ptrlen, val_string_ptrlen, val_string_ptrlen, val_string_ptrlen, val_string_ptrlen, val_string_ptrlen, val_string_ptrlen, val_string_ptrlen, uint, httpdigesthash, boolean) +FUNC(val_wpoint, ecdsa_public, (ARG(val_mpint, private_key), ARG(keyalg, alg))) +FUNC(val_epoint, eddsa_public, (ARG(val_mpint, private_key), ARG(keyalg, alg))) +FUNC(val_string, des_encrypt_xdmauth, (ARG(val_string_ptrlen, key), ARG(val_string_ptrlen, blk))) +FUNC(val_string, des_decrypt_xdmauth, (ARG(val_string_ptrlen, key), ARG(val_string_ptrlen, blk))) +FUNC(val_string, des3_encrypt_pubkey, (ARG(val_string_ptrlen, key), ARG(val_string_ptrlen, blk))) +FUNC(val_string, des3_decrypt_pubkey, (ARG(val_string_ptrlen, key), ARG(val_string_ptrlen, blk))) +FUNC(val_string, des3_encrypt_pubkey_ossh, (ARG(val_string_ptrlen, key), ARG(val_string_ptrlen, iv), ARG(val_string_ptrlen, blk))) +FUNC(val_string, des3_decrypt_pubkey_ossh, (ARG(val_string_ptrlen, key), ARG(val_string_ptrlen, iv), ARG(val_string_ptrlen, blk))) +FUNC(val_string, aes256_encrypt_pubkey, (ARG(val_string_ptrlen, key), ARG(val_string_ptrlen, iv), ARG(val_string_ptrlen, blk))) +FUNC(val_string, aes256_decrypt_pubkey, (ARG(val_string_ptrlen, key), ARG(val_string_ptrlen, iv), ARG(val_string_ptrlen, blk))) +FUNC(uint, crc32_rfc1662, (ARG(val_string_ptrlen, data))) +FUNC(uint, crc32_ssh1, (ARG(val_string_ptrlen, data))) +FUNC(uint, crc32_update, (ARG(uint, crc_input), ARG(val_string_ptrlen, data))) +FUNC(boolean, crcda_detect, (ARG(val_string_ptrlen, packet), ARG(val_string_ptrlen, iv))) +FUNC(val_string, get_implementations_commasep, (ARG(val_string_ptrlen, alg))) +FUNC(void, http_digest_response, (ARG(out_val_string_binarysink, bs), ARG(val_string_ptrlen, username), ARG(val_string_ptrlen, password), ARG(val_string_ptrlen, realm), ARG(val_string_ptrlen, method), ARG(val_string_ptrlen, uri), ARG(val_string_ptrlen, qop), ARG(val_string_ptrlen, nonce), ARG(val_string_ptrlen, opaque), ARG(uint, nonce_count), ARG(httpdigesthash, hash), ARG(boolean, hash_username))) /* * These functions aren't part of PuTTY's own API, but are additions * by testcrypt itself for administrative purposes. */ -FUNC1(void, random_queue, val_string_ptrlen) -FUNC0(uint, random_queue_len) -FUNC2(void, random_make_prng, hashalg, val_string_ptrlen) -FUNC0(void, random_clear) +FUNC(void, random_queue, (ARG(val_string_ptrlen, pl))) +FUNC(uint, random_queue_len, (VOID)) +FUNC(void, random_make_prng, (ARG(hashalg, hashalg), ARG(val_string_ptrlen, seed))) +FUNC(void, random_clear, (VOID)) -- cgit v1.2.3 From 3153f3ef39c0be57b4d16f442b3f1a5e6b4fa186 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 21 Nov 2021 13:03:34 +0000 Subject: testcrypt.h: invent FUNC_WRAPPED. FUNC_WRAPPED is an alternative keyword to FUNC which you can use to introduce a function specification in testcrypt.h, indicating that the function is _not_ the one of the same name used in the main PuTTY code, but instead a wrapper on it in testcrypt.c whose API was reworked to be more friendly to translation into Python. There are a lot of those wrappers already, and previously they passed without comment in testcrypt.h, and were put into service by #defining over the top of each name before expanding the marshalling functions. Now, all those #defines are gone, because the use of FUNC_WRAPPED in testcrypt.h is enough to clue in the marshalling wrapper to be generated with a call to foo_wrapper() instead of foo(). Mostly the purpose of this is to make testcrypt.h a bit more self-documenting: if you see FUNC_WRAPPED, you know not to be confused by the Python and C function definitions totally failing to match. --- test/testcrypt.py | 3 ++- testcrypt.c | 71 +++++++++++++--------------------------------------- testcrypt.h | 74 ++++++++++++++++++++++++++++++------------------------- 3 files changed, 59 insertions(+), 89 deletions(-) diff --git a/test/testcrypt.py b/test/testcrypt.py index 170ad3ce..0d37e57a 100644 --- a/test/testcrypt.py +++ b/test/testcrypt.py @@ -327,7 +327,8 @@ def _parse_testcrypt_header(tokens): return tok while True: - tok = expect("FUNC", "at start of function specification", eof_ok=True) + tok = expect({"FUNC", "FUNC_WRAPPED"}, + "at start of function specification", eof_ok=True) if tok is None: break diff --git a/testcrypt.c b/testcrypt.c index bf49af25..09f742a5 100644 --- a/testcrypt.c +++ b/testcrypt.c @@ -880,13 +880,11 @@ mp_int *monty_identity_wrapper(MontyContext *mc) { return mp_copy(monty_identity(mc)); } -#define monty_identity monty_identity_wrapper mp_int *monty_modulus_wrapper(MontyContext *mc) { return mp_copy(monty_modulus(mc)); } -#define monty_modulus monty_modulus_wrapper strbuf *ssh_hash_digest_wrapper(ssh_hash *h) { @@ -895,8 +893,6 @@ strbuf *ssh_hash_digest_wrapper(ssh_hash *h) ssh_hash_digest(h, p); return sb; } -#undef ssh_hash_digest -#define ssh_hash_digest ssh_hash_digest_wrapper strbuf *ssh_hash_final_wrapper(ssh_hash *h) { @@ -905,8 +901,6 @@ strbuf *ssh_hash_final_wrapper(ssh_hash *h) ssh_hash_final(h, p); return sb; } -#undef ssh_hash_final -#define ssh_hash_final ssh_hash_final_wrapper void ssh_cipher_setiv_wrapper(ssh_cipher *c, ptrlen iv) { @@ -915,8 +909,6 @@ void ssh_cipher_setiv_wrapper(ssh_cipher *c, ptrlen iv) ssh_cipher_alg(c)->blksize); ssh_cipher_setiv(c, iv.ptr); } -#undef ssh_cipher_setiv -#define ssh_cipher_setiv ssh_cipher_setiv_wrapper void ssh_cipher_setkey_wrapper(ssh_cipher *c, ptrlen key) { @@ -925,8 +917,6 @@ void ssh_cipher_setkey_wrapper(ssh_cipher *c, ptrlen key) ssh_cipher_alg(c)->padded_keybytes); ssh_cipher_setkey(c, key.ptr); } -#undef ssh_cipher_setkey -#define ssh_cipher_setkey ssh_cipher_setkey_wrapper strbuf *ssh_cipher_encrypt_wrapper(ssh_cipher *c, ptrlen input) { @@ -938,8 +928,6 @@ strbuf *ssh_cipher_encrypt_wrapper(ssh_cipher *c, ptrlen input) ssh_cipher_encrypt(c, sb->u, sb->len); return sb; } -#undef ssh_cipher_encrypt -#define ssh_cipher_encrypt ssh_cipher_encrypt_wrapper strbuf *ssh_cipher_decrypt_wrapper(ssh_cipher *c, ptrlen input) { @@ -951,8 +939,6 @@ strbuf *ssh_cipher_decrypt_wrapper(ssh_cipher *c, ptrlen input) ssh_cipher_decrypt(c, sb->u, sb->len); return sb; } -#undef ssh_cipher_decrypt -#define ssh_cipher_decrypt ssh_cipher_decrypt_wrapper strbuf *ssh_cipher_encrypt_length_wrapper(ssh_cipher *c, ptrlen input, unsigned long seq) @@ -964,8 +950,6 @@ strbuf *ssh_cipher_encrypt_length_wrapper(ssh_cipher *c, ptrlen input, ssh_cipher_encrypt_length(c, sb->u, sb->len, seq); return sb; } -#undef ssh_cipher_encrypt_length -#define ssh_cipher_encrypt_length ssh_cipher_encrypt_length_wrapper strbuf *ssh_cipher_decrypt_length_wrapper(ssh_cipher *c, ptrlen input, unsigned long seq) @@ -977,8 +961,6 @@ strbuf *ssh_cipher_decrypt_length_wrapper(ssh_cipher *c, ptrlen input, ssh_cipher_decrypt_length(c, sb->u, sb->len, seq); return sb; } -#undef ssh_cipher_decrypt_length -#define ssh_cipher_decrypt_length ssh_cipher_decrypt_length_wrapper strbuf *ssh2_mac_genresult_wrapper(ssh2_mac *m) { @@ -987,14 +969,11 @@ strbuf *ssh2_mac_genresult_wrapper(ssh2_mac *m) ssh2_mac_genresult(m, u); return sb; } -#undef ssh2_mac_genresult -#define ssh2_mac_genresult ssh2_mac_genresult_wrapper bool dh_validate_f_wrapper(dh_ctx *dh, mp_int *f) { return dh_validate_f(dh, f) == NULL; } -#define dh_validate_f dh_validate_f_wrapper void ssh_hash_update(ssh_hash *h, ptrlen pl) { @@ -1026,7 +1005,6 @@ strbuf *rsa_ssh1_encrypt_wrapper(ptrlen input, RSAKey *key) } return sb; } -#define rsa_ssh1_encrypt rsa_ssh1_encrypt_wrapper strbuf *rsa_ssh1_decrypt_pkcs1_wrapper(mp_int *input, RSAKey *key) { @@ -1036,7 +1014,6 @@ strbuf *rsa_ssh1_decrypt_pkcs1_wrapper(mp_int *input, RSAKey *key) strbuf_clear(sb); return sb; } -#define rsa_ssh1_decrypt_pkcs1 rsa_ssh1_decrypt_pkcs1_wrapper strbuf *des_encrypt_xdmauth_wrapper(ptrlen key, ptrlen data) { @@ -1049,7 +1026,6 @@ strbuf *des_encrypt_xdmauth_wrapper(ptrlen key, ptrlen data) des_encrypt_xdmauth(key.ptr, sb->u, sb->len); return sb; } -#define des_encrypt_xdmauth des_encrypt_xdmauth_wrapper strbuf *des_decrypt_xdmauth_wrapper(ptrlen key, ptrlen data) { @@ -1062,7 +1038,6 @@ strbuf *des_decrypt_xdmauth_wrapper(ptrlen key, ptrlen data) des_decrypt_xdmauth(key.ptr, sb->u, sb->len); return sb; } -#define des_decrypt_xdmauth des_decrypt_xdmauth_wrapper strbuf *des3_encrypt_pubkey_wrapper(ptrlen key, ptrlen data) { @@ -1075,7 +1050,6 @@ strbuf *des3_encrypt_pubkey_wrapper(ptrlen key, ptrlen data) des3_encrypt_pubkey(key.ptr, sb->u, sb->len); return sb; } -#define des3_encrypt_pubkey des3_encrypt_pubkey_wrapper strbuf *des3_decrypt_pubkey_wrapper(ptrlen key, ptrlen data) { @@ -1088,7 +1062,6 @@ strbuf *des3_decrypt_pubkey_wrapper(ptrlen key, ptrlen data) des3_decrypt_pubkey(key.ptr, sb->u, sb->len); return sb; } -#define des3_decrypt_pubkey des3_decrypt_pubkey_wrapper strbuf *des3_encrypt_pubkey_ossh_wrapper(ptrlen key, ptrlen iv, ptrlen data) { @@ -1103,7 +1076,6 @@ strbuf *des3_encrypt_pubkey_ossh_wrapper(ptrlen key, ptrlen iv, ptrlen data) des3_encrypt_pubkey_ossh(key.ptr, iv.ptr, sb->u, sb->len); return sb; } -#define des3_encrypt_pubkey_ossh des3_encrypt_pubkey_ossh_wrapper strbuf *des3_decrypt_pubkey_ossh_wrapper(ptrlen key, ptrlen iv, ptrlen data) { @@ -1118,7 +1090,6 @@ strbuf *des3_decrypt_pubkey_ossh_wrapper(ptrlen key, ptrlen iv, ptrlen data) des3_decrypt_pubkey_ossh(key.ptr, iv.ptr, sb->u, sb->len); return sb; } -#define des3_decrypt_pubkey_ossh des3_decrypt_pubkey_ossh_wrapper strbuf *aes256_encrypt_pubkey_wrapper(ptrlen key, ptrlen iv, ptrlen data) { @@ -1133,7 +1104,6 @@ strbuf *aes256_encrypt_pubkey_wrapper(ptrlen key, ptrlen iv, ptrlen data) aes256_encrypt_pubkey(key.ptr, iv.ptr, sb->u, sb->len); return sb; } -#define aes256_encrypt_pubkey aes256_encrypt_pubkey_wrapper strbuf *aes256_decrypt_pubkey_wrapper(ptrlen key, ptrlen iv, ptrlen data) { @@ -1148,7 +1118,6 @@ strbuf *aes256_decrypt_pubkey_wrapper(ptrlen key, ptrlen iv, ptrlen data) aes256_decrypt_pubkey(key.ptr, iv.ptr, sb->u, sb->len); return sb; } -#define aes256_decrypt_pubkey aes256_decrypt_pubkey_wrapper strbuf *prng_read_wrapper(prng *pr, size_t size) { @@ -1156,7 +1125,6 @@ strbuf *prng_read_wrapper(prng *pr, size_t size) prng_read(pr, strbuf_append(sb, size), size); return sb; } -#define prng_read prng_read_wrapper void prng_seed_update(prng *pr, ptrlen data) { @@ -1192,7 +1160,6 @@ ssh_key *ppk_load_s_wrapper(BinarySource *src, char **comment, sfree(uk); return toret; } -#define ppk_load_s ppk_load_s_wrapper int rsa1_load_s_wrapper(BinarySource *src, RSAKey *rsa, char **comment, const char *passphrase, const char **errorstr) @@ -1202,7 +1169,6 @@ int rsa1_load_s_wrapper(BinarySource *src, RSAKey *rsa, char **comment, rsa->comment = NULL; return toret; } -#define rsa1_load_s rsa1_load_s_wrapper strbuf *ppk_save_sb_wrapper( ssh_key *key, const char *comment, const char *passphrase, @@ -1229,7 +1195,6 @@ strbuf *ppk_save_sb_wrapper( sfree(uk.comment); return toret; } -#define ppk_save_sb ppk_save_sb_wrapper strbuf *rsa1_save_sb_wrapper(RSAKey *key, const char *comment, const char *passphrase) @@ -1240,7 +1205,6 @@ strbuf *rsa1_save_sb_wrapper(RSAKey *key, const char *comment, key->comment = NULL; return toret; } -#define rsa1_save_sb rsa1_save_sb_wrapper #define return_void(out, expression) (expression) @@ -1251,7 +1215,6 @@ mp_int *primegen_generate_wrapper( { return primegen_generate(ctx, pcs, &null_progress); } -#define primegen_generate primegen_generate_wrapper RSAKey *rsa1_generate(int bits, bool strong, PrimeGenerationContext *pgc) { @@ -1266,7 +1229,6 @@ ssh_key *rsa_generate_wrapper(int bits, bool strong, { return &rsa1_generate(bits, strong, pgc)->sshk; } -#define rsa_generate rsa_generate_wrapper ssh_key *dsa_generate_wrapper(int bits, PrimeGenerationContext *pgc) { @@ -1274,7 +1236,6 @@ ssh_key *dsa_generate_wrapper(int bits, PrimeGenerationContext *pgc) dsa_generate(dsakey, bits, pgc, &null_progress); return &dsakey->sshk; } -#define dsa_generate dsa_generate_wrapper ssh_key *ecdsa_generate_wrapper(int bits) { @@ -1285,7 +1246,6 @@ ssh_key *ecdsa_generate_wrapper(int bits) } return &ek->sshk; } -#define ecdsa_generate ecdsa_generate_wrapper ssh_key *eddsa_generate_wrapper(int bits) { @@ -1296,7 +1256,6 @@ ssh_key *eddsa_generate_wrapper(int bits) } return &ek->sshk; } -#define eddsa_generate eddsa_generate_wrapper size_t key_components_count(key_components *kc) { return kc->ncomponents; } const char *key_components_nth_name(key_components *kc, size_t n) @@ -1322,7 +1281,6 @@ PockleStatus pockle_add_prime_wrapper(Pockle *pockle, mp_int *p, { return pockle_add_prime(pockle, p, mpl.integers, mpl.n, witness); } -#define pockle_add_prime pockle_add_prime_wrapper strbuf *argon2_wrapper(Argon2Flavour flavour, uint32_t mem, uint32_t passes, uint32_t parallel, uint32_t taglen, @@ -1332,7 +1290,6 @@ strbuf *argon2_wrapper(Argon2Flavour flavour, uint32_t mem, uint32_t passes, argon2(flavour, mem, passes, parallel, taglen, P, S, K, X, out); return out; } -#define argon2 argon2_wrapper strbuf *get_implementations_commasep(ptrlen alg) { @@ -1590,20 +1547,25 @@ typedef HttpDigestHash TD_httpdigesthash; #define DEPARENTHESISE(...) __VA_ARGS__ +#define FUNC(outtype, fname, args) \ + FUNC_INNER(outtype, fname, fname, args) +#define FUNC_WRAPPED(outtype, fname, args) \ + FUNC_INNER(outtype, fname, fname##_wrapper, args) + #define ARG(type, arg) _predummy_##arg; TD_##type arg; int _postdummy_##arg #define VOID _voiddummy -#define FUNC(outtype, fname, args) \ - typedef struct ARGS_##fname { \ - int DEPARENTHESISE args; \ +#define FUNC_INNER(outtype, fname, realname, args) \ + typedef struct ARGS_##fname { \ + int DEPARENTHESISE args; \ } ARGS_##fname; #include "testcrypt.h" -#undef FUNC +#undef FUNC_INNER #undef ARG #undef VOID #define ARG(type, arg) _args.arg = get_##type(_in) #define VOID ((void)0) -#define FUNC(outtype, fname, args) \ +#define FUNC_INNER(outtype, fname, realname, args) \ static inline ARGS_##fname get_args_##fname(BinarySource *_in) { \ ARGS_##fname _args; \ memset(&_args, 0, sizeof(_args)); \ @@ -1611,20 +1573,20 @@ typedef HttpDigestHash TD_httpdigesthash; return _args; \ } #include "testcrypt.h" -#undef FUNC +#undef FUNC_INNER #undef ARG #undef VOID #define ARG(type, arg) _args.arg #define VOID -#define FUNC(outtype, fname, args) \ +#define FUNC_INNER(outtype, fname, realname, args) \ static void handle_##fname(BinarySource *_in, strbuf *_out) { \ ARGS_##fname _args = get_args_##fname(_in); \ (void)_args; /* suppress warning if no actual arguments */ \ - return_##outtype(_out, fname args); \ + return_##outtype(_out, realname args); \ } #include "testcrypt.h" -#undef FUNC +#undef FUNC_INNER #undef ARG static void process_line(BinarySource *in, strbuf *out) @@ -1647,12 +1609,13 @@ static void process_line(BinarySource *in, strbuf *out) DISPATCH_COMMAND(mp_dump); #undef DISPATCH_COMMAND -#define FUNC(outtype, fname, args) DISPATCH_INTERNAL(#fname,handle_##fname); +#define FUNC_INNER(outtype, fname, realname, args) \ + DISPATCH_INTERNAL(#fname,handle_##fname); #define ARG1(type, arg) #define ARGN(type, arg) #define VOID #include "testcrypt.h" -#undef FUNC +#undef FUNC_INNER #undef ARG #undef VOID diff --git a/testcrypt.h b/testcrypt.h index 0e8f8186..094be610 100644 --- a/testcrypt.h +++ b/testcrypt.h @@ -60,6 +60,12 @@ * enumeration types (e.g. argon2flavour, rsaorder) or pointers to * const vtables of one kind or another (e.g. keyalg, hashalg, * primegenpolicy). + * + * If a function definition begins with FUNC_WRAPPED rather than FUNC, + * it means that the underlying C function has a suffix "_wrapper", + * e.g. ssh_cipher_setiv_wrapper(). Those wrappers are defined in + * testcrypt.c itself, and change the API or semantics in a way that + * makes the function more Python-friendly. */ /* @@ -127,8 +133,8 @@ FUNC(val_modsqrt, modsqrt_new, (ARG(val_mpint, p), ARG(val_mpint, any_nonsquare_ /* The modsqrt functions' 'success' pointer becomes a second return value */ FUNC(val_mpint, mp_modsqrt, (ARG(val_modsqrt, sc), ARG(val_mpint, x), ARG(out_uint, success))) FUNC(val_monty, monty_new, (ARG(val_mpint, modulus))) -FUNC(val_mpint, monty_modulus, (ARG(val_monty, mc))) -FUNC(val_mpint, monty_identity, (ARG(val_monty, mc))) +FUNC_WRAPPED(val_mpint, monty_modulus, (ARG(val_monty, mc))) +FUNC_WRAPPED(val_mpint, monty_identity, (ARG(val_monty, mc))) FUNC(void, monty_import_into, (ARG(val_monty, mc), ARG(val_mpint, r), ARG(val_mpint, x))) FUNC(val_mpint, monty_import, (ARG(val_monty, mc), ARG(val_mpint, x))) FUNC(void, monty_export_into, (ARG(val_monty, mc), ARG(val_mpint, r), ARG(val_mpint, x))) @@ -196,8 +202,8 @@ FUNC(void, ecc_edwards_get_affine, (ARG(val_epoint, wp), ARG(out_val_mpint, x), FUNC(opt_val_hash, ssh_hash_new, (ARG(hashalg, alg))) FUNC(void, ssh_hash_reset, (ARG(val_hash, h))) FUNC(val_hash, ssh_hash_copy, (ARG(val_hash, orig))) -FUNC(val_string, ssh_hash_digest, (ARG(val_hash, h))) -FUNC(val_string, ssh_hash_final, (ARG(consumed_val_hash, h))) +FUNC_WRAPPED(val_string, ssh_hash_digest, (ARG(val_hash, h))) +FUNC_WRAPPED(val_string, ssh_hash_final, (ARG(consumed_val_hash, h))) FUNC(void, ssh_hash_update, (ARG(val_hash, h), ARG(val_string_ptrlen, pl))) FUNC(opt_val_hash, blake2b_new_general, (ARG(uint, hashlen))) @@ -211,7 +217,7 @@ FUNC(val_mac, ssh2_mac_new, (ARG(macalg, alg), ARG(opt_val_cipher, cipher))) FUNC(void, ssh2_mac_setkey, (ARG(val_mac, m), ARG(val_string_ptrlen, key))) FUNC(void, ssh2_mac_start, (ARG(val_mac, m))) FUNC(void, ssh2_mac_update, (ARG(val_mac, m), ARG(val_string_ptrlen, pl))) -FUNC(val_string, ssh2_mac_genresult, (ARG(val_mac, m))) +FUNC_WRAPPED(val_string, ssh2_mac_genresult, (ARG(val_mac, m))) FUNC(val_string_asciz_const, ssh2_mac_text_name, (ARG(val_mac, m))) /* @@ -248,12 +254,12 @@ FUNC(opt_val_mpint, key_components_nth_mp, (ARG(val_keycomponents, kc), ARG(uint * string and return a separate string. */ FUNC(opt_val_cipher, ssh_cipher_new, (ARG(cipheralg, alg))) -FUNC(void, ssh_cipher_setiv, (ARG(val_cipher, c), ARG(val_string_ptrlen, iv))) -FUNC(void, ssh_cipher_setkey, (ARG(val_cipher, c), ARG(val_string_ptrlen, key))) -FUNC(val_string, ssh_cipher_encrypt, (ARG(val_cipher, c), ARG(val_string_ptrlen, blk))) -FUNC(val_string, ssh_cipher_decrypt, (ARG(val_cipher, c), ARG(val_string_ptrlen, blk))) -FUNC(val_string, ssh_cipher_encrypt_length, (ARG(val_cipher, c), ARG(val_string_ptrlen, blk), ARG(uint, seq))) -FUNC(val_string, ssh_cipher_decrypt_length, (ARG(val_cipher, c), ARG(val_string_ptrlen, blk), ARG(uint, seq))) +FUNC_WRAPPED(void, ssh_cipher_setiv, (ARG(val_cipher, c), ARG(val_string_ptrlen, iv))) +FUNC_WRAPPED(void, ssh_cipher_setkey, (ARG(val_cipher, c), ARG(val_string_ptrlen, key))) +FUNC_WRAPPED(val_string, ssh_cipher_encrypt, (ARG(val_cipher, c), ARG(val_string_ptrlen, blk))) +FUNC_WRAPPED(val_string, ssh_cipher_decrypt, (ARG(val_cipher, c), ARG(val_string_ptrlen, blk))) +FUNC_WRAPPED(val_string, ssh_cipher_encrypt_length, (ARG(val_cipher, c), ARG(val_string_ptrlen, blk), ARG(uint, seq))) +FUNC_WRAPPED(val_string, ssh_cipher_decrypt_length, (ARG(val_cipher, c), ARG(val_string_ptrlen, blk), ARG(uint, seq))) /* * Integer Diffie-Hellman. @@ -262,7 +268,7 @@ FUNC(val_dh, dh_setup_group, (ARG(dh_group, kex))) FUNC(val_dh, dh_setup_gex, (ARG(val_mpint, pval), ARG(val_mpint, gval))) FUNC(uint, dh_modulus_bit_size, (ARG(val_dh, ctx))) FUNC(val_mpint, dh_create_e, (ARG(val_dh, ctx), ARG(uint, nbits))) -FUNC(boolean, dh_validate_f, (ARG(val_dh, ctx), ARG(val_mpint, f))) +FUNC_WRAPPED(boolean, dh_validate_f, (ARG(val_dh, ctx), ARG(val_mpint, f))) FUNC(val_mpint, dh_find_K, (ARG(val_dh, ctx), ARG(val_mpint, f))) /* @@ -291,9 +297,9 @@ FUNC(val_rsakex, get_rsa_ssh1_priv_agent, (ARG(val_string_binarysource, src))) FUNC(val_rsa, rsa_new, (VOID)) FUNC(void, get_rsa_ssh1_pub, (ARG(val_string_binarysource, src), ARG(val_rsa, key), ARG(rsaorder, order))) FUNC(void, get_rsa_ssh1_priv, (ARG(val_string_binarysource, src), ARG(val_rsa, key))) -FUNC(opt_val_string, rsa_ssh1_encrypt, (ARG(val_string_ptrlen, data), ARG(val_rsa, key))) +FUNC_WRAPPED(opt_val_string, rsa_ssh1_encrypt, (ARG(val_string_ptrlen, data), ARG(val_rsa, key))) FUNC(val_mpint, rsa_ssh1_decrypt, (ARG(val_mpint, input), ARG(val_rsa, key))) -FUNC(val_string, rsa_ssh1_decrypt_pkcs1, (ARG(val_mpint, input), ARG(val_rsa, key))) +FUNC_WRAPPED(val_string, rsa_ssh1_decrypt_pkcs1, (ARG(val_mpint, input), ARG(val_rsa, key))) FUNC(val_string_asciz, rsastr_fmt, (ARG(val_rsa, key))) FUNC(val_string_asciz, rsa_ssh1_fingerprint, (ARG(val_rsa, key))) FUNC(void, rsa_ssh1_public_blob, (ARG(out_val_string_binarysink, bs), ARG(val_rsa, key), ARG(rsaorder, order))) @@ -309,7 +315,7 @@ FUNC(val_prng, prng_new, (ARG(hashalg, hashalg))) FUNC(void, prng_seed_begin, (ARG(val_prng, p))) FUNC(void, prng_seed_update, (ARG(val_prng, pr), ARG(val_string_ptrlen, data))) FUNC(void, prng_seed_finish, (ARG(val_prng, p))) -FUNC(val_string, prng_read, (ARG(val_prng, p), ARG(uint, size))) +FUNC_WRAPPED(val_string, prng_read, (ARG(val_prng, p), ARG(uint, size))) FUNC(void, prng_add_entropy, (ARG(val_prng, p), ARG(uint, source_id), ARG(val_string_ptrlen, data))) /* @@ -320,29 +326,29 @@ FUNC(boolean, ppk_encrypted_s, (ARG(val_string_binarysource, src), ARG(out_opt_v FUNC(boolean, rsa1_encrypted_s, (ARG(val_string_binarysource, src), ARG(out_opt_val_string_asciz, comment))) FUNC(boolean, ppk_loadpub_s, (ARG(val_string_binarysource, src), ARG(out_opt_val_string_asciz, algorithm), ARG(out_val_string_binarysink, bs), ARG(out_opt_val_string_asciz, commentptr), ARG(out_opt_val_string_asciz_const, errorstr))) FUNC(int, rsa1_loadpub_s, (ARG(val_string_binarysource, src), ARG(out_val_string_binarysink, bs), ARG(out_opt_val_string_asciz, commentptr), ARG(out_opt_val_string_asciz_const, errorstr))) -FUNC(opt_val_key, ppk_load_s, (ARG(val_string_binarysource, src), ARG(out_opt_val_string_asciz, comment), ARG(opt_val_string_asciz, passphrase), ARG(out_opt_val_string_asciz_const, errorstr))) -FUNC(int, rsa1_load_s, (ARG(val_string_binarysource, src), ARG(val_rsa, key), ARG(out_opt_val_string_asciz, comment), ARG(opt_val_string_asciz, passphrase), ARG(out_opt_val_string_asciz_const, errorstr))) -FUNC(val_string, ppk_save_sb, (ARG(val_key, key), ARG(opt_val_string_asciz, comment), ARG(opt_val_string_asciz, passphrase), ARG(uint, fmt_version), ARG(argon2flavour, flavour), ARG(uint, mem), ARG(uint, passes), ARG(uint, parallel))) -FUNC(val_string, rsa1_save_sb, (ARG(val_rsa, key), ARG(opt_val_string_asciz, comment), ARG(opt_val_string_asciz, passphrase))) +FUNC_WRAPPED(opt_val_key, ppk_load_s, (ARG(val_string_binarysource, src), ARG(out_opt_val_string_asciz, comment), ARG(opt_val_string_asciz, passphrase), ARG(out_opt_val_string_asciz_const, errorstr))) +FUNC_WRAPPED(int, rsa1_load_s, (ARG(val_string_binarysource, src), ARG(val_rsa, key), ARG(out_opt_val_string_asciz, comment), ARG(opt_val_string_asciz, passphrase), ARG(out_opt_val_string_asciz_const, errorstr))) +FUNC_WRAPPED(val_string, ppk_save_sb, (ARG(val_key, key), ARG(opt_val_string_asciz, comment), ARG(opt_val_string_asciz, passphrase), ARG(uint, fmt_version), ARG(argon2flavour, flavour), ARG(uint, mem), ARG(uint, passes), ARG(uint, parallel))) +FUNC_WRAPPED(val_string, rsa1_save_sb, (ARG(val_rsa, key), ARG(opt_val_string_asciz, comment), ARG(opt_val_string_asciz, passphrase))) FUNC(val_string_asciz, ssh2_fingerprint_blob, (ARG(val_string_ptrlen, blob), ARG(fptype, fptype))) /* * Password hashing. */ -FUNC(val_string, argon2, (ARG(argon2flavour, flavour), ARG(uint, mem), ARG(uint, passes), ARG(uint, parallel), ARG(uint, taglen), ARG(val_string_ptrlen, P), ARG(val_string_ptrlen, S), ARG(val_string_ptrlen, K), ARG(val_string_ptrlen, X))) +FUNC_WRAPPED(val_string, argon2, (ARG(argon2flavour, flavour), ARG(uint, mem), ARG(uint, passes), ARG(uint, parallel), ARG(uint, taglen), ARG(val_string_ptrlen, P), ARG(val_string_ptrlen, S), ARG(val_string_ptrlen, K), ARG(val_string_ptrlen, X))) FUNC(val_string, argon2_long_hash, (ARG(uint, length), ARG(val_string_ptrlen, data))) /* * Key generation functions. */ -FUNC(val_key, rsa_generate, (ARG(uint, bits), ARG(boolean, strong), ARG(val_pgc, pgc))) -FUNC(val_key, dsa_generate, (ARG(uint, bits), ARG(val_pgc, pgc))) -FUNC(opt_val_key, ecdsa_generate, (ARG(uint, bits))) -FUNC(opt_val_key, eddsa_generate, (ARG(uint, bits))) +FUNC_WRAPPED(val_key, rsa_generate, (ARG(uint, bits), ARG(boolean, strong), ARG(val_pgc, pgc))) +FUNC_WRAPPED(val_key, dsa_generate, (ARG(uint, bits), ARG(val_pgc, pgc))) +FUNC_WRAPPED(opt_val_key, ecdsa_generate, (ARG(uint, bits))) +FUNC_WRAPPED(opt_val_key, eddsa_generate, (ARG(uint, bits))) FUNC(val_rsa, rsa1_generate, (ARG(uint, bits), ARG(boolean, strong), ARG(val_pgc, pgc))) FUNC(val_pgc, primegen_new_context, (ARG(primegenpolicy, policy))) -FUNC(opt_val_mpint, primegen_generate, (ARG(val_pgc, ctx), ARG(consumed_val_pcs, pcs))) +FUNC_WRAPPED(opt_val_mpint, primegen_generate, (ARG(val_pgc, ctx), ARG(consumed_val_pcs, pcs))) FUNC(val_string, primegen_mpu_certificate, (ARG(val_pgc, ctx), ARG(val_mpint, p))) FUNC(val_pcs, pcs_new, (ARG(uint, bits))) FUNC(val_pcs, pcs_new_with_firstbits, (ARG(uint, bits), ARG(uint, first), ARG(uint, nfirst))) @@ -359,7 +365,7 @@ FUNC(val_pockle, pockle_new, (VOID)) FUNC(uint, pockle_mark, (ARG(val_pockle, pockle))) FUNC(void, pockle_release, (ARG(val_pockle, pockle), ARG(uint, mark))) FUNC(pocklestatus, pockle_add_small_prime, (ARG(val_pockle, pockle), ARG(val_mpint, p))) -FUNC(pocklestatus, pockle_add_prime, (ARG(val_pockle, pockle), ARG(val_mpint, p), ARG(mpint_list, factors), ARG(val_mpint, witness))) +FUNC_WRAPPED(pocklestatus, pockle_add_prime, (ARG(val_pockle, pockle), ARG(val_mpint, p), ARG(mpint_list, factors), ARG(val_mpint, witness))) FUNC(val_string, pockle_mpu, (ARG(val_pockle, pockle), ARG(val_mpint, p))) FUNC(val_millerrabin, miller_rabin_new, (ARG(val_mpint, p))) FUNC(mr_result, miller_rabin_test, (ARG(val_millerrabin, mr), ARG(val_mpint, w))) @@ -369,14 +375,14 @@ FUNC(mr_result, miller_rabin_test, (ARG(val_millerrabin, mr), ARG(val_mpint, w)) */ FUNC(val_wpoint, ecdsa_public, (ARG(val_mpint, private_key), ARG(keyalg, alg))) FUNC(val_epoint, eddsa_public, (ARG(val_mpint, private_key), ARG(keyalg, alg))) -FUNC(val_string, des_encrypt_xdmauth, (ARG(val_string_ptrlen, key), ARG(val_string_ptrlen, blk))) -FUNC(val_string, des_decrypt_xdmauth, (ARG(val_string_ptrlen, key), ARG(val_string_ptrlen, blk))) -FUNC(val_string, des3_encrypt_pubkey, (ARG(val_string_ptrlen, key), ARG(val_string_ptrlen, blk))) -FUNC(val_string, des3_decrypt_pubkey, (ARG(val_string_ptrlen, key), ARG(val_string_ptrlen, blk))) -FUNC(val_string, des3_encrypt_pubkey_ossh, (ARG(val_string_ptrlen, key), ARG(val_string_ptrlen, iv), ARG(val_string_ptrlen, blk))) -FUNC(val_string, des3_decrypt_pubkey_ossh, (ARG(val_string_ptrlen, key), ARG(val_string_ptrlen, iv), ARG(val_string_ptrlen, blk))) -FUNC(val_string, aes256_encrypt_pubkey, (ARG(val_string_ptrlen, key), ARG(val_string_ptrlen, iv), ARG(val_string_ptrlen, blk))) -FUNC(val_string, aes256_decrypt_pubkey, (ARG(val_string_ptrlen, key), ARG(val_string_ptrlen, iv), ARG(val_string_ptrlen, blk))) +FUNC_WRAPPED(val_string, des_encrypt_xdmauth, (ARG(val_string_ptrlen, key), ARG(val_string_ptrlen, blk))) +FUNC_WRAPPED(val_string, des_decrypt_xdmauth, (ARG(val_string_ptrlen, key), ARG(val_string_ptrlen, blk))) +FUNC_WRAPPED(val_string, des3_encrypt_pubkey, (ARG(val_string_ptrlen, key), ARG(val_string_ptrlen, blk))) +FUNC_WRAPPED(val_string, des3_decrypt_pubkey, (ARG(val_string_ptrlen, key), ARG(val_string_ptrlen, blk))) +FUNC_WRAPPED(val_string, des3_encrypt_pubkey_ossh, (ARG(val_string_ptrlen, key), ARG(val_string_ptrlen, iv), ARG(val_string_ptrlen, blk))) +FUNC_WRAPPED(val_string, des3_decrypt_pubkey_ossh, (ARG(val_string_ptrlen, key), ARG(val_string_ptrlen, iv), ARG(val_string_ptrlen, blk))) +FUNC_WRAPPED(val_string, aes256_encrypt_pubkey, (ARG(val_string_ptrlen, key), ARG(val_string_ptrlen, iv), ARG(val_string_ptrlen, blk))) +FUNC_WRAPPED(val_string, aes256_decrypt_pubkey, (ARG(val_string_ptrlen, key), ARG(val_string_ptrlen, iv), ARG(val_string_ptrlen, blk))) FUNC(uint, crc32_rfc1662, (ARG(val_string_ptrlen, data))) FUNC(uint, crc32_ssh1, (ARG(val_string_ptrlen, data))) FUNC(uint, crc32_update, (ARG(uint, crc_input), ARG(val_string_ptrlen, data))) -- cgit v1.2.3 From aaaf11d7fb9d8fa3a3992d35000b5428c6bcce71 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 21 Nov 2021 13:21:01 +0000 Subject: testcrypt.py: use parameter names in diagnostics. Making a virtue of the necessity of adding parameter names to testcrypt.h a couple of commits ago, we can now use those names to improve diagnostics, so that if you use the wrong type in a Python function call the error message will tell you the name as well as the index of the offending argument. Also, the repr() text for the function itself will now print a full prototype (albeit in a nasty hybrid of C, Python and testcrypt.h syntax) which shows all the parameter names. That should be handy when trying to remember the order of arguments at the REPL prompt. --- test/testcrypt.py | 37 ++++++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/test/testcrypt.py b/test/testcrypt.py index 0d37e57a..d21f19c7 100644 --- a/test/testcrypt.py +++ b/test/testcrypt.py @@ -149,7 +149,7 @@ def marshal_string(val): else "%{:02x}".format(b) for b in val) -def make_argword(arg, argtype, fnname, argindex, to_preserve): +def make_argword(arg, argtype, fnname, argindex, argname, to_preserve): typename, consumed = argtype if typename.startswith("opt_"): if arg is None: @@ -166,8 +166,8 @@ def make_argword(arg, argtype, fnname, argindex, to_preserve): if isinstance(arg, Value): if arg._typename != typename: raise TypeError( - "{}() argument {:d} should be {} ({} given)".format( - fnname, argindex, typename, arg._typename)) + "{}() argument #{:d} ({}) should be {} ({} given)".format( + fnname, argindex, argname, typename, arg._typename)) ident = arg._ident if consumed: arg._consumed() @@ -185,14 +185,14 @@ def make_argword(arg, argtype, fnname, argindex, to_preserve): return arg if typename == "mpint_list": sublist = [make_argword(len(arg), ("uint", False), - fnname, argindex, to_preserve)] + fnname, argindex, argname, to_preserve)] for val in arg: sublist.append(make_argword(val, ("val_mpint", False), - fnname, argindex, to_preserve)) + fnname, argindex, argname, to_preserve)) return b" ".join(coerce_to_bytes(sub) for sub in sublist) raise TypeError( - "Can't convert {}() argument {:d} to {} (value was {!r})".format( - fnname, argindex, typename, arg)) + "Can't convert {}() argument #{:d} ({}) to {} (value was {!r})".format( + fnname, argindex, argname, typename, arg)) def unpack_string(identifier): retwords = childprocess.funcall("getstring", [identifier]) @@ -247,12 +247,20 @@ def make_retvals(rettypes, retwords, unpack_strings=True): for rettype, word in zip(rettypes, retwords)] class Function(object): - def __init__(self, fnname, rettypes, argtypes): + def __init__(self, fnname, rettypes, retnames, argtypes, argnames): self.fnname = fnname self.rettypes = rettypes + self.retnames = retnames self.argtypes = argtypes + self.argnames = argnames def __repr__(self): - return "".format(self.fnname) + return " ({})>".format( + self.fnname, + ", ".join(("consumed " if c else "")+t+" "+n + for (t,c),n in zip(self.argtypes, self.argnames)), + ", ".join((t+" "+n if n is not None else t) + for t,n in zip(self.rettypes, self.retnames)), + ) def __call__(self, *args): if len(args) != len(self.argtypes): raise TypeError( @@ -261,7 +269,8 @@ class Function(object): to_preserve = [] retwords = childprocess.funcall( self.fnname, [make_argword(args[i], self.argtypes[i], - self.fnname, i, to_preserve) + self.fnname, i, self.argnames[i], + to_preserve) for i in range(len(args))]) retvals = make_retvals(self.rettypes, retwords) if len(retvals) == 0: @@ -380,14 +389,18 @@ def _setup(scope): tokens = _lex_testcrypt_header(header) for function, rettype, arglist in _parse_testcrypt_header(tokens): rettypes = [] + retnames = [] if rettype != "void": rettypes.append(trim_argtype(rettype)) + retnames.append(None) argtypes = [] + argnames = [] argsconsumed = [] for arg, argname in arglist: if arg.startswith(outprefix): rettypes.append(trim_argtype(arg[len(outprefix):])) + retnames.append(argname) else: consumed = False if arg.startswith(consprefix): @@ -395,7 +408,9 @@ def _setup(scope): consumed = True arg = trim_argtype(arg) argtypes.append((arg, consumed)) - func = Function(function, rettypes, argtypes) + argnames.append(argname) + func = Function(function, rettypes, retnames, + argtypes, argnames) scope[function] = func if len(argtypes) > 0: t = argtypes[0][0] -- cgit v1.2.3 From 1cf04cf01d1255c39776f9f23f0712deb7f96b5c Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 21 Nov 2021 15:01:58 +0000 Subject: Run testcrypt.h through clang-format. Now that testcrypt.py parses it using a C-like lexer, we don't have the constraint any more that each function definition has to live on exactly one source line for the sake of the previous line-by-line parser. So we can put whitespace wherever we like, and reformat for legibility! I've done the initial pass of this using clang-format, which will optimise for (a) everything fitting in 80 columns, and (b) individual ARG(...) specifications generally not being broken across lines. Those seem like a good combination to me. --- testcrypt.h | 389 ++++++++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 274 insertions(+), 115 deletions(-) diff --git a/testcrypt.h b/testcrypt.h index 094be610..4b843d8f 100644 --- a/testcrypt.h +++ b/testcrypt.h @@ -95,66 +95,107 @@ FUNC(uint, mp_cmp_hs, (ARG(val_mpint, a), ARG(val_mpint, b))) FUNC(uint, mp_cmp_eq, (ARG(val_mpint, a), ARG(val_mpint, b))) FUNC(uint, mp_hs_integer, (ARG(val_mpint, x), ARG(uint, n))) FUNC(uint, mp_eq_integer, (ARG(val_mpint, x), ARG(uint, n))) -FUNC(void, mp_min_into, (ARG(val_mpint, r), ARG(val_mpint, x), ARG(val_mpint, y))) -FUNC(void, mp_max_into, (ARG(val_mpint, r), ARG(val_mpint, x), ARG(val_mpint, y))) +FUNC(void, mp_min_into, + (ARG(val_mpint, r), ARG(val_mpint, x), ARG(val_mpint, y))) +FUNC(void, mp_max_into, + (ARG(val_mpint, r), ARG(val_mpint, x), ARG(val_mpint, y))) FUNC(val_mpint, mp_min, (ARG(val_mpint, x), ARG(val_mpint, y))) FUNC(val_mpint, mp_max, (ARG(val_mpint, x), ARG(val_mpint, y))) FUNC(void, mp_copy_into, (ARG(val_mpint, dest), ARG(val_mpint, src))) -FUNC(void, mp_select_into, (ARG(val_mpint, dest), ARG(val_mpint, src0), ARG(val_mpint, src1), ARG(uint, choose_src1))) -FUNC(void, mp_add_into, (ARG(val_mpint, r), ARG(val_mpint, a), ARG(val_mpint, b))) -FUNC(void, mp_sub_into, (ARG(val_mpint, r), ARG(val_mpint, a), ARG(val_mpint, b))) -FUNC(void, mp_mul_into, (ARG(val_mpint, r), ARG(val_mpint, a), ARG(val_mpint, b))) +FUNC(void, mp_select_into, + (ARG(val_mpint, dest), ARG(val_mpint, src0), ARG(val_mpint, src1), + ARG(uint, choose_src1))) +FUNC(void, mp_add_into, + (ARG(val_mpint, r), ARG(val_mpint, a), ARG(val_mpint, b))) +FUNC(void, mp_sub_into, + (ARG(val_mpint, r), ARG(val_mpint, a), ARG(val_mpint, b))) +FUNC(void, mp_mul_into, + (ARG(val_mpint, r), ARG(val_mpint, a), ARG(val_mpint, b))) FUNC(val_mpint, mp_add, (ARG(val_mpint, x), ARG(val_mpint, y))) FUNC(val_mpint, mp_sub, (ARG(val_mpint, x), ARG(val_mpint, y))) FUNC(val_mpint, mp_mul, (ARG(val_mpint, x), ARG(val_mpint, y))) -FUNC(void, mp_and_into, (ARG(val_mpint, r), ARG(val_mpint, a), ARG(val_mpint, b))) -FUNC(void, mp_or_into, (ARG(val_mpint, r), ARG(val_mpint, a), ARG(val_mpint, b))) -FUNC(void, mp_xor_into, (ARG(val_mpint, r), ARG(val_mpint, a), ARG(val_mpint, b))) -FUNC(void, mp_bic_into, (ARG(val_mpint, r), ARG(val_mpint, a), ARG(val_mpint, b))) +FUNC(void, mp_and_into, + (ARG(val_mpint, r), ARG(val_mpint, a), ARG(val_mpint, b))) +FUNC(void, mp_or_into, + (ARG(val_mpint, r), ARG(val_mpint, a), ARG(val_mpint, b))) +FUNC(void, mp_xor_into, + (ARG(val_mpint, r), ARG(val_mpint, a), ARG(val_mpint, b))) +FUNC(void, mp_bic_into, + (ARG(val_mpint, r), ARG(val_mpint, a), ARG(val_mpint, b))) FUNC(void, mp_copy_integer_into, (ARG(val_mpint, dest), ARG(uint, n))) -FUNC(void, mp_add_integer_into, (ARG(val_mpint, r), ARG(val_mpint, a), ARG(uint, n))) -FUNC(void, mp_sub_integer_into, (ARG(val_mpint, r), ARG(val_mpint, a), ARG(uint, n))) -FUNC(void, mp_mul_integer_into, (ARG(val_mpint, r), ARG(val_mpint, a), ARG(uint, n))) -FUNC(void, mp_cond_add_into, (ARG(val_mpint, r), ARG(val_mpint, a), ARG(val_mpint, b), ARG(uint, yes))) -FUNC(void, mp_cond_sub_into, (ARG(val_mpint, r), ARG(val_mpint, a), ARG(val_mpint, b), ARG(uint, yes))) -FUNC(void, mp_cond_swap, (ARG(val_mpint, x0), ARG(val_mpint, x1), ARG(uint, swap))) +FUNC(void, mp_add_integer_into, + (ARG(val_mpint, r), ARG(val_mpint, a), ARG(uint, n))) +FUNC(void, mp_sub_integer_into, + (ARG(val_mpint, r), ARG(val_mpint, a), ARG(uint, n))) +FUNC(void, mp_mul_integer_into, + (ARG(val_mpint, r), ARG(val_mpint, a), ARG(uint, n))) +FUNC(void, mp_cond_add_into, + (ARG(val_mpint, r), ARG(val_mpint, a), ARG(val_mpint, b), ARG(uint, yes))) +FUNC(void, mp_cond_sub_into, + (ARG(val_mpint, r), ARG(val_mpint, a), ARG(val_mpint, b), ARG(uint, yes))) +FUNC(void, mp_cond_swap, + (ARG(val_mpint, x0), ARG(val_mpint, x1), ARG(uint, swap))) FUNC(void, mp_cond_clear, (ARG(val_mpint, x), ARG(uint, clear))) -FUNC(void, mp_divmod_into, (ARG(val_mpint, n), ARG(val_mpint, d), ARG(opt_val_mpint, q), ARG(opt_val_mpint, r))) +FUNC(void, mp_divmod_into, + (ARG(val_mpint, n), ARG(val_mpint, d), ARG(opt_val_mpint, q), + ARG(opt_val_mpint, r))) FUNC(val_mpint, mp_div, (ARG(val_mpint, n), ARG(val_mpint, d))) FUNC(val_mpint, mp_mod, (ARG(val_mpint, x), ARG(val_mpint, modulus))) -FUNC(val_mpint, mp_nthroot, (ARG(val_mpint, y), ARG(uint, n), ARG(opt_val_mpint, remainder))) +FUNC(val_mpint, mp_nthroot, + (ARG(val_mpint, y), ARG(uint, n), ARG(opt_val_mpint, remainder))) FUNC(void, mp_reduce_mod_2to, (ARG(val_mpint, x), ARG(uint, p))) FUNC(val_mpint, mp_invert_mod_2to, (ARG(val_mpint, x), ARG(uint, p))) FUNC(val_mpint, mp_invert, (ARG(val_mpint, x), ARG(val_mpint, modulus))) -FUNC(void, mp_gcd_into, (ARG(val_mpint, a), ARG(val_mpint, b), ARG(opt_val_mpint, gcd_out), ARG(opt_val_mpint, A_out), ARG(opt_val_mpint, B_out))) +FUNC(void, mp_gcd_into, + (ARG(val_mpint, a), ARG(val_mpint, b), ARG(opt_val_mpint, gcd_out), + ARG(opt_val_mpint, A_out), ARG(opt_val_mpint, B_out))) FUNC(val_mpint, mp_gcd, (ARG(val_mpint, a), ARG(val_mpint, b))) FUNC(uint, mp_coprime, (ARG(val_mpint, a), ARG(val_mpint, b))) -FUNC(val_modsqrt, modsqrt_new, (ARG(val_mpint, p), ARG(val_mpint, any_nonsquare_mod_p))) +FUNC(val_modsqrt, modsqrt_new, + (ARG(val_mpint, p), ARG(val_mpint, any_nonsquare_mod_p))) /* The modsqrt functions' 'success' pointer becomes a second return value */ -FUNC(val_mpint, mp_modsqrt, (ARG(val_modsqrt, sc), ARG(val_mpint, x), ARG(out_uint, success))) +FUNC(val_mpint, mp_modsqrt, + (ARG(val_modsqrt, sc), ARG(val_mpint, x), ARG(out_uint, success))) FUNC(val_monty, monty_new, (ARG(val_mpint, modulus))) FUNC_WRAPPED(val_mpint, monty_modulus, (ARG(val_monty, mc))) FUNC_WRAPPED(val_mpint, monty_identity, (ARG(val_monty, mc))) -FUNC(void, monty_import_into, (ARG(val_monty, mc), ARG(val_mpint, r), ARG(val_mpint, x))) +FUNC(void, monty_import_into, + (ARG(val_monty, mc), ARG(val_mpint, r), ARG(val_mpint, x))) FUNC(val_mpint, monty_import, (ARG(val_monty, mc), ARG(val_mpint, x))) -FUNC(void, monty_export_into, (ARG(val_monty, mc), ARG(val_mpint, r), ARG(val_mpint, x))) +FUNC(void, monty_export_into, + (ARG(val_monty, mc), ARG(val_mpint, r), ARG(val_mpint, x))) FUNC(val_mpint, monty_export, (ARG(val_monty, mc), ARG(val_mpint, x))) -FUNC(void, monty_mul_into, (ARG(val_monty, mc), ARG(val_mpint, r), ARG(val_mpint, x), ARG(val_mpint, y))) -FUNC(val_mpint, monty_add, (ARG(val_monty, mc), ARG(val_mpint, x), ARG(val_mpint, y))) -FUNC(val_mpint, monty_sub, (ARG(val_monty, mc), ARG(val_mpint, x), ARG(val_mpint, y))) -FUNC(val_mpint, monty_mul, (ARG(val_monty, mc), ARG(val_mpint, x), ARG(val_mpint, y))) -FUNC(val_mpint, monty_pow, (ARG(val_monty, mc), ARG(val_mpint, base), ARG(val_mpint, exponent))) +FUNC(void, monty_mul_into, + (ARG(val_monty, mc), ARG(val_mpint, r), ARG(val_mpint, x), + ARG(val_mpint, y))) +FUNC(val_mpint, monty_add, + (ARG(val_monty, mc), ARG(val_mpint, x), ARG(val_mpint, y))) +FUNC(val_mpint, monty_sub, + (ARG(val_monty, mc), ARG(val_mpint, x), ARG(val_mpint, y))) +FUNC(val_mpint, monty_mul, + (ARG(val_monty, mc), ARG(val_mpint, x), ARG(val_mpint, y))) +FUNC(val_mpint, monty_pow, + (ARG(val_monty, mc), ARG(val_mpint, base), ARG(val_mpint, exponent))) FUNC(val_mpint, monty_invert, (ARG(val_monty, mc), ARG(val_mpint, x))) -FUNC(val_mpint, monty_modsqrt, (ARG(val_modsqrt, sc), ARG(val_mpint, mx), ARG(out_uint, success))) -FUNC(val_mpint, mp_modpow, (ARG(val_mpint, base), ARG(val_mpint, exponent), ARG(val_mpint, modulus))) -FUNC(val_mpint, mp_modmul, (ARG(val_mpint, x), ARG(val_mpint, y), ARG(val_mpint, modulus))) -FUNC(val_mpint, mp_modadd, (ARG(val_mpint, x), ARG(val_mpint, y), ARG(val_mpint, modulus))) -FUNC(val_mpint, mp_modsub, (ARG(val_mpint, x), ARG(val_mpint, y), ARG(val_mpint, modulus))) -FUNC(void, mp_lshift_safe_into, (ARG(val_mpint, r), ARG(val_mpint, x), ARG(uint, shift))) -FUNC(void, mp_rshift_safe_into, (ARG(val_mpint, r), ARG(val_mpint, x), ARG(uint, shift))) +FUNC(val_mpint, monty_modsqrt, + (ARG(val_modsqrt, sc), ARG(val_mpint, mx), ARG(out_uint, success))) +FUNC(val_mpint, mp_modpow, + (ARG(val_mpint, base), ARG(val_mpint, exponent), ARG(val_mpint, modulus))) +FUNC(val_mpint, mp_modmul, + (ARG(val_mpint, x), ARG(val_mpint, y), ARG(val_mpint, modulus))) +FUNC(val_mpint, mp_modadd, + (ARG(val_mpint, x), ARG(val_mpint, y), ARG(val_mpint, modulus))) +FUNC(val_mpint, mp_modsub, + (ARG(val_mpint, x), ARG(val_mpint, y), ARG(val_mpint, modulus))) +FUNC(void, mp_lshift_safe_into, + (ARG(val_mpint, r), ARG(val_mpint, x), ARG(uint, shift))) +FUNC(void, mp_rshift_safe_into, + (ARG(val_mpint, r), ARG(val_mpint, x), ARG(uint, shift))) FUNC(val_mpint, mp_rshift_safe, (ARG(val_mpint, x), ARG(uint, shift))) -FUNC(void, mp_lshift_fixed_into, (ARG(val_mpint, r), ARG(val_mpint, a), ARG(uint, shift))) -FUNC(void, mp_rshift_fixed_into, (ARG(val_mpint, r), ARG(val_mpint, x), ARG(uint, shift))) +FUNC(void, mp_lshift_fixed_into, + (ARG(val_mpint, r), ARG(val_mpint, a), ARG(uint, shift))) +FUNC(void, mp_rshift_fixed_into, + (ARG(val_mpint, r), ARG(val_mpint, x), ARG(uint, shift))) FUNC(val_mpint, mp_rshift_fixed, (ARG(val_mpint, x), ARG(uint, shift))) FUNC(val_mpint, mp_random_bits, (ARG(uint, bits))) FUNC(val_mpint, mp_random_in_range, (ARG(val_mpint, lo), ARG(val_mpint, hi))) @@ -162,35 +203,52 @@ FUNC(val_mpint, mp_random_in_range, (ARG(val_mpint, lo), ARG(val_mpint, hi))) /* * ecc.h functions. */ -FUNC(val_wcurve, ecc_weierstrass_curve, (ARG(val_mpint, p), ARG(val_mpint, a), ARG(val_mpint, b), ARG(opt_val_mpint, nonsquare_mod_p))) +FUNC(val_wcurve, ecc_weierstrass_curve, + (ARG(val_mpint, p), ARG(val_mpint, a), ARG(val_mpint, b), + ARG(opt_val_mpint, nonsquare_mod_p))) FUNC(val_wpoint, ecc_weierstrass_point_new_identity, (ARG(val_wcurve, curve))) -FUNC(val_wpoint, ecc_weierstrass_point_new, (ARG(val_wcurve, curve), ARG(val_mpint, x), ARG(val_mpint, y))) -FUNC(val_wpoint, ecc_weierstrass_point_new_from_x, (ARG(val_wcurve, curve), ARG(val_mpint, x), ARG(uint, desired_y_parity))) +FUNC(val_wpoint, ecc_weierstrass_point_new, + (ARG(val_wcurve, curve), ARG(val_mpint, x), ARG(val_mpint, y))) +FUNC(val_wpoint, ecc_weierstrass_point_new_from_x, + (ARG(val_wcurve, curve), ARG(val_mpint, x), ARG(uint, desired_y_parity))) FUNC(val_wpoint, ecc_weierstrass_point_copy, (ARG(val_wpoint, wc))) FUNC(uint, ecc_weierstrass_point_valid, (ARG(val_wpoint, P))) -FUNC(val_wpoint, ecc_weierstrass_add_general, (ARG(val_wpoint, P), ARG(val_wpoint, Q))) +FUNC(val_wpoint, ecc_weierstrass_add_general, + (ARG(val_wpoint, P), ARG(val_wpoint, Q))) FUNC(val_wpoint, ecc_weierstrass_add, (ARG(val_wpoint, P), ARG(val_wpoint, Q))) FUNC(val_wpoint, ecc_weierstrass_double, (ARG(val_wpoint, P))) -FUNC(val_wpoint, ecc_weierstrass_multiply, (ARG(val_wpoint, B), ARG(val_mpint, n))) +FUNC(val_wpoint, ecc_weierstrass_multiply, + (ARG(val_wpoint, B), ARG(val_mpint, n))) FUNC(uint, ecc_weierstrass_is_identity, (ARG(val_wpoint, wp))) /* The output pointers in get_affine all become extra output values */ -FUNC(void, ecc_weierstrass_get_affine, (ARG(val_wpoint, wp), ARG(out_val_mpint, x), ARG(out_val_mpint, y))) -FUNC(val_mcurve, ecc_montgomery_curve, (ARG(val_mpint, p), ARG(val_mpint, a), ARG(val_mpint, b))) -FUNC(val_mpoint, ecc_montgomery_point_new, (ARG(val_mcurve, mc), ARG(val_mpint, x))) +FUNC(void, ecc_weierstrass_get_affine, + (ARG(val_wpoint, wp), ARG(out_val_mpint, x), ARG(out_val_mpint, y))) +FUNC(val_mcurve, ecc_montgomery_curve, + (ARG(val_mpint, p), ARG(val_mpint, a), ARG(val_mpint, b))) +FUNC(val_mpoint, ecc_montgomery_point_new, + (ARG(val_mcurve, mc), ARG(val_mpint, x))) FUNC(val_mpoint, ecc_montgomery_point_copy, (ARG(val_mpoint, orig))) -FUNC(val_mpoint, ecc_montgomery_diff_add, (ARG(val_mpoint, P), ARG(val_mpoint, Q), ARG(val_mpoint, PminusQ))) +FUNC(val_mpoint, ecc_montgomery_diff_add, + (ARG(val_mpoint, P), ARG(val_mpoint, Q), ARG(val_mpoint, PminusQ))) FUNC(val_mpoint, ecc_montgomery_double, (ARG(val_mpoint, P))) -FUNC(val_mpoint, ecc_montgomery_multiply, (ARG(val_mpoint, B), ARG(val_mpint, n))) -FUNC(void, ecc_montgomery_get_affine, (ARG(val_mpoint, mp), ARG(out_val_mpint, x))) +FUNC(val_mpoint, ecc_montgomery_multiply, + (ARG(val_mpoint, B), ARG(val_mpint, n))) +FUNC(void, ecc_montgomery_get_affine, + (ARG(val_mpoint, mp), ARG(out_val_mpint, x))) FUNC(boolean, ecc_montgomery_is_identity, (ARG(val_mpoint, mp))) -FUNC(val_ecurve, ecc_edwards_curve, (ARG(val_mpint, p), ARG(val_mpint, d), ARG(val_mpint, a), ARG(opt_val_mpint, nonsquare_mod_p))) -FUNC(val_epoint, ecc_edwards_point_new, (ARG(val_ecurve, curve), ARG(val_mpint, x), ARG(val_mpint, y))) -FUNC(val_epoint, ecc_edwards_point_new_from_y, (ARG(val_ecurve, curve), ARG(val_mpint, y), ARG(uint, desired_x_parity))) +FUNC(val_ecurve, ecc_edwards_curve, + (ARG(val_mpint, p), ARG(val_mpint, d), ARG(val_mpint, a), + ARG(opt_val_mpint, nonsquare_mod_p))) +FUNC(val_epoint, ecc_edwards_point_new, + (ARG(val_ecurve, curve), ARG(val_mpint, x), ARG(val_mpint, y))) +FUNC(val_epoint, ecc_edwards_point_new_from_y, + (ARG(val_ecurve, curve), ARG(val_mpint, y), ARG(uint, desired_x_parity))) FUNC(val_epoint, ecc_edwards_point_copy, (ARG(val_epoint, ec))) FUNC(val_epoint, ecc_edwards_add, (ARG(val_epoint, P), ARG(val_epoint, Q))) FUNC(val_epoint, ecc_edwards_multiply, (ARG(val_epoint, B), ARG(val_mpint, n))) FUNC(uint, ecc_edwards_eq, (ARG(val_epoint, P), ARG(val_epoint, Q))) -FUNC(void, ecc_edwards_get_affine, (ARG(val_epoint, wp), ARG(out_val_mpint, x), ARG(out_val_mpint, y))) +FUNC(void, ecc_edwards_get_affine, + (ARG(val_epoint, wp), ARG(out_val_mpint, x), ARG(out_val_mpint, y))) /* * The ssh_hash abstraction. Note the 'consumed', indicating that @@ -228,25 +286,40 @@ FUNC(val_string_asciz_const, ssh2_mac_text_name, (ARG(val_mac, m))) * returning a string. */ FUNC(val_key, ssh_key_new_pub, (ARG(keyalg, self), ARG(val_string_ptrlen, pub))) -FUNC(opt_val_key, ssh_key_new_priv, (ARG(keyalg, self), ARG(val_string_ptrlen, pub), ARG(val_string_ptrlen, priv))) -FUNC(opt_val_key, ssh_key_new_priv_openssh, (ARG(keyalg, self), ARG(val_string_binarysource, src))) -FUNC(opt_val_string_asciz, ssh_key_invalid, (ARG(val_key, key), ARG(uint, flags))) -FUNC(void, ssh_key_sign, (ARG(val_key, key), ARG(val_string_ptrlen, data), ARG(uint, flags), ARG(out_val_string_binarysink, bs))) -FUNC(boolean, ssh_key_verify, (ARG(val_key, key), ARG(val_string_ptrlen, sig), ARG(val_string_ptrlen, data))) -FUNC(void, ssh_key_public_blob, (ARG(val_key, key), ARG(out_val_string_binarysink, bs))) -FUNC(void, ssh_key_private_blob, (ARG(val_key, key), ARG(out_val_string_binarysink, bs))) -FUNC(void, ssh_key_openssh_blob, (ARG(val_key, key), ARG(out_val_string_binarysink, bs))) +FUNC(opt_val_key, ssh_key_new_priv, + (ARG(keyalg, self), ARG(val_string_ptrlen, pub), + ARG(val_string_ptrlen, priv))) +FUNC(opt_val_key, ssh_key_new_priv_openssh, + (ARG(keyalg, self), ARG(val_string_binarysource, src))) +FUNC(opt_val_string_asciz, ssh_key_invalid, + (ARG(val_key, key), ARG(uint, flags))) +FUNC(void, ssh_key_sign, + (ARG(val_key, key), ARG(val_string_ptrlen, data), ARG(uint, flags), + ARG(out_val_string_binarysink, bs))) +FUNC(boolean, ssh_key_verify, + (ARG(val_key, key), ARG(val_string_ptrlen, sig), + ARG(val_string_ptrlen, data))) +FUNC(void, ssh_key_public_blob, + (ARG(val_key, key), ARG(out_val_string_binarysink, bs))) +FUNC(void, ssh_key_private_blob, + (ARG(val_key, key), ARG(out_val_string_binarysink, bs))) +FUNC(void, ssh_key_openssh_blob, + (ARG(val_key, key), ARG(out_val_string_binarysink, bs))) FUNC(val_string_asciz, ssh_key_cache_str, (ARG(val_key, key))) FUNC(val_keycomponents, ssh_key_components, (ARG(val_key, key))) -FUNC(uint, ssh_key_public_bits, (ARG(keyalg, self), ARG(val_string_ptrlen, blob))) +FUNC(uint, ssh_key_public_bits, + (ARG(keyalg, self), ARG(val_string_ptrlen, blob))) /* * Accessors to retrieve the innards of a 'key_components'. */ FUNC(uint, key_components_count, (ARG(val_keycomponents, kc))) -FUNC(opt_val_string_asciz_const, key_components_nth_name, (ARG(val_keycomponents, kc), ARG(uint, n))) -FUNC(opt_val_string_asciz_const, key_components_nth_str, (ARG(val_keycomponents, kc), ARG(uint, n))) -FUNC(opt_val_mpint, key_components_nth_mp, (ARG(val_keycomponents, kc), ARG(uint, n))) +FUNC(opt_val_string_asciz_const, key_components_nth_name, + (ARG(val_keycomponents, kc), ARG(uint, n))) +FUNC(opt_val_string_asciz_const, key_components_nth_str, + (ARG(val_keycomponents, kc), ARG(uint, n))) +FUNC(opt_val_mpint, key_components_nth_mp, + (ARG(val_keycomponents, kc), ARG(uint, n))) /* * The ssh_cipher abstraction. The in-place encrypt and decrypt @@ -254,12 +327,18 @@ FUNC(opt_val_mpint, key_components_nth_mp, (ARG(val_keycomponents, kc), ARG(uint * string and return a separate string. */ FUNC(opt_val_cipher, ssh_cipher_new, (ARG(cipheralg, alg))) -FUNC_WRAPPED(void, ssh_cipher_setiv, (ARG(val_cipher, c), ARG(val_string_ptrlen, iv))) -FUNC_WRAPPED(void, ssh_cipher_setkey, (ARG(val_cipher, c), ARG(val_string_ptrlen, key))) -FUNC_WRAPPED(val_string, ssh_cipher_encrypt, (ARG(val_cipher, c), ARG(val_string_ptrlen, blk))) -FUNC_WRAPPED(val_string, ssh_cipher_decrypt, (ARG(val_cipher, c), ARG(val_string_ptrlen, blk))) -FUNC_WRAPPED(val_string, ssh_cipher_encrypt_length, (ARG(val_cipher, c), ARG(val_string_ptrlen, blk), ARG(uint, seq))) -FUNC_WRAPPED(val_string, ssh_cipher_decrypt_length, (ARG(val_cipher, c), ARG(val_string_ptrlen, blk), ARG(uint, seq))) +FUNC_WRAPPED(void, ssh_cipher_setiv, + (ARG(val_cipher, c), ARG(val_string_ptrlen, iv))) +FUNC_WRAPPED(void, ssh_cipher_setkey, + (ARG(val_cipher, c), ARG(val_string_ptrlen, key))) +FUNC_WRAPPED(val_string, ssh_cipher_encrypt, + (ARG(val_cipher, c), ARG(val_string_ptrlen, blk))) +FUNC_WRAPPED(val_string, ssh_cipher_decrypt, + (ARG(val_cipher, c), ARG(val_string_ptrlen, blk))) +FUNC_WRAPPED(val_string, ssh_cipher_encrypt_length, + (ARG(val_cipher, c), ARG(val_string_ptrlen, blk), ARG(uint, seq))) +FUNC_WRAPPED(val_string, ssh_cipher_decrypt_length, + (ARG(val_cipher, c), ARG(val_string_ptrlen, blk), ARG(uint, seq))) /* * Integer Diffie-Hellman. @@ -275,8 +354,10 @@ FUNC(val_mpint, dh_find_K, (ARG(val_dh, ctx), ARG(val_mpint, f))) * Elliptic-curve Diffie-Hellman. */ FUNC(val_ecdh, ssh_ecdhkex_newkey, (ARG(ecdh_alg, kex))) -FUNC(void, ssh_ecdhkex_getpublic, (ARG(val_ecdh, key), ARG(out_val_string_binarysink, bs))) -FUNC(opt_val_mpint, ssh_ecdhkex_getkey, (ARG(val_ecdh, key), ARG(val_string_ptrlen, remoteKey))) +FUNC(void, ssh_ecdhkex_getpublic, + (ARG(val_ecdh, key), ARG(out_val_string_binarysink, bs))) +FUNC(opt_val_mpint, ssh_ecdhkex_getkey, + (ARG(val_ecdh, key), ARG(val_string_ptrlen, remoteKey))) /* * RSA key exchange, and also the BinarySource get function @@ -285,8 +366,11 @@ FUNC(opt_val_mpint, ssh_ecdhkex_getkey, (ARG(val_ecdh, key), ARG(val_string_ptrl */ FUNC(val_rsakex, ssh_rsakex_newkey, (ARG(val_string_ptrlen, data))) FUNC(uint, ssh_rsakex_klen, (ARG(val_rsakex, key))) -FUNC(val_string, ssh_rsakex_encrypt, (ARG(val_rsakex, key), ARG(hashalg, h), ARG(val_string_ptrlen, plaintext))) -FUNC(opt_val_mpint, ssh_rsakex_decrypt, (ARG(val_rsakex, key), ARG(hashalg, h), ARG(val_string_ptrlen, ciphertext))) +FUNC(val_string, ssh_rsakex_encrypt, + (ARG(val_rsakex, key), ARG(hashalg, h), ARG(val_string_ptrlen, plaintext))) +FUNC(opt_val_mpint, ssh_rsakex_decrypt, + (ARG(val_rsakex, key), ARG(hashalg, h), + ARG(val_string_ptrlen, ciphertext))) FUNC(val_rsakex, get_rsa_ssh1_priv_agent, (ARG(val_string_binarysource, src))) /* @@ -295,16 +379,24 @@ FUNC(val_rsakex, get_rsa_ssh1_priv_agent, (ARG(val_string_binarysource, src))) * function to make one in the first place. */ FUNC(val_rsa, rsa_new, (VOID)) -FUNC(void, get_rsa_ssh1_pub, (ARG(val_string_binarysource, src), ARG(val_rsa, key), ARG(rsaorder, order))) -FUNC(void, get_rsa_ssh1_priv, (ARG(val_string_binarysource, src), ARG(val_rsa, key))) -FUNC_WRAPPED(opt_val_string, rsa_ssh1_encrypt, (ARG(val_string_ptrlen, data), ARG(val_rsa, key))) +FUNC(void, get_rsa_ssh1_pub, + (ARG(val_string_binarysource, src), ARG(val_rsa, key), + ARG(rsaorder, order))) +FUNC(void, get_rsa_ssh1_priv, + (ARG(val_string_binarysource, src), ARG(val_rsa, key))) +FUNC_WRAPPED(opt_val_string, rsa_ssh1_encrypt, + (ARG(val_string_ptrlen, data), ARG(val_rsa, key))) FUNC(val_mpint, rsa_ssh1_decrypt, (ARG(val_mpint, input), ARG(val_rsa, key))) -FUNC_WRAPPED(val_string, rsa_ssh1_decrypt_pkcs1, (ARG(val_mpint, input), ARG(val_rsa, key))) +FUNC_WRAPPED(val_string, rsa_ssh1_decrypt_pkcs1, + (ARG(val_mpint, input), ARG(val_rsa, key))) FUNC(val_string_asciz, rsastr_fmt, (ARG(val_rsa, key))) FUNC(val_string_asciz, rsa_ssh1_fingerprint, (ARG(val_rsa, key))) -FUNC(void, rsa_ssh1_public_blob, (ARG(out_val_string_binarysink, bs), ARG(val_rsa, key), ARG(rsaorder, order))) +FUNC(void, rsa_ssh1_public_blob, + (ARG(out_val_string_binarysink, bs), ARG(val_rsa, key), + ARG(rsaorder, order))) FUNC(int, rsa_ssh1_public_blob_len, (ARG(val_string_ptrlen, data))) -FUNC(void, rsa_ssh1_private_blob_agent, (ARG(out_val_string_binarysink, bs), ARG(val_rsa, key))) +FUNC(void, rsa_ssh1_private_blob_agent, + (ARG(out_val_string_binarysink, bs), ARG(val_rsa, key))) /* * The PRNG type. Similarly to hashes and MACs, I've invented an extra @@ -316,79 +408,145 @@ FUNC(void, prng_seed_begin, (ARG(val_prng, p))) FUNC(void, prng_seed_update, (ARG(val_prng, pr), ARG(val_string_ptrlen, data))) FUNC(void, prng_seed_finish, (ARG(val_prng, p))) FUNC_WRAPPED(val_string, prng_read, (ARG(val_prng, p), ARG(uint, size))) -FUNC(void, prng_add_entropy, (ARG(val_prng, p), ARG(uint, source_id), ARG(val_string_ptrlen, data))) +FUNC(void, prng_add_entropy, + (ARG(val_prng, p), ARG(uint, source_id), ARG(val_string_ptrlen, data))) /* * Key load/save functions, or rather, the BinarySource / strbuf API * that sits just inside the file I/O versions. */ -FUNC(boolean, ppk_encrypted_s, (ARG(val_string_binarysource, src), ARG(out_opt_val_string_asciz, comment))) -FUNC(boolean, rsa1_encrypted_s, (ARG(val_string_binarysource, src), ARG(out_opt_val_string_asciz, comment))) -FUNC(boolean, ppk_loadpub_s, (ARG(val_string_binarysource, src), ARG(out_opt_val_string_asciz, algorithm), ARG(out_val_string_binarysink, bs), ARG(out_opt_val_string_asciz, commentptr), ARG(out_opt_val_string_asciz_const, errorstr))) -FUNC(int, rsa1_loadpub_s, (ARG(val_string_binarysource, src), ARG(out_val_string_binarysink, bs), ARG(out_opt_val_string_asciz, commentptr), ARG(out_opt_val_string_asciz_const, errorstr))) -FUNC_WRAPPED(opt_val_key, ppk_load_s, (ARG(val_string_binarysource, src), ARG(out_opt_val_string_asciz, comment), ARG(opt_val_string_asciz, passphrase), ARG(out_opt_val_string_asciz_const, errorstr))) -FUNC_WRAPPED(int, rsa1_load_s, (ARG(val_string_binarysource, src), ARG(val_rsa, key), ARG(out_opt_val_string_asciz, comment), ARG(opt_val_string_asciz, passphrase), ARG(out_opt_val_string_asciz_const, errorstr))) -FUNC_WRAPPED(val_string, ppk_save_sb, (ARG(val_key, key), ARG(opt_val_string_asciz, comment), ARG(opt_val_string_asciz, passphrase), ARG(uint, fmt_version), ARG(argon2flavour, flavour), ARG(uint, mem), ARG(uint, passes), ARG(uint, parallel))) -FUNC_WRAPPED(val_string, rsa1_save_sb, (ARG(val_rsa, key), ARG(opt_val_string_asciz, comment), ARG(opt_val_string_asciz, passphrase))) +FUNC(boolean, ppk_encrypted_s, + (ARG(val_string_binarysource, src), + ARG(out_opt_val_string_asciz, comment))) +FUNC(boolean, rsa1_encrypted_s, + (ARG(val_string_binarysource, src), + ARG(out_opt_val_string_asciz, comment))) +FUNC(boolean, ppk_loadpub_s, + (ARG(val_string_binarysource, src), + ARG(out_opt_val_string_asciz, algorithm), + ARG(out_val_string_binarysink, bs), + ARG(out_opt_val_string_asciz, commentptr), + ARG(out_opt_val_string_asciz_const, errorstr))) +FUNC(int, rsa1_loadpub_s, + (ARG(val_string_binarysource, src), ARG(out_val_string_binarysink, bs), + ARG(out_opt_val_string_asciz, commentptr), + ARG(out_opt_val_string_asciz_const, errorstr))) +FUNC_WRAPPED(opt_val_key, ppk_load_s, + (ARG(val_string_binarysource, src), + ARG(out_opt_val_string_asciz, comment), + ARG(opt_val_string_asciz, passphrase), + ARG(out_opt_val_string_asciz_const, errorstr))) +FUNC_WRAPPED(int, rsa1_load_s, + (ARG(val_string_binarysource, src), ARG(val_rsa, key), + ARG(out_opt_val_string_asciz, comment), + ARG(opt_val_string_asciz, passphrase), + ARG(out_opt_val_string_asciz_const, errorstr))) +FUNC_WRAPPED(val_string, ppk_save_sb, + (ARG(val_key, key), ARG(opt_val_string_asciz, comment), + ARG(opt_val_string_asciz, passphrase), ARG(uint, fmt_version), + ARG(argon2flavour, flavour), ARG(uint, mem), ARG(uint, passes), + ARG(uint, parallel))) +FUNC_WRAPPED(val_string, rsa1_save_sb, + (ARG(val_rsa, key), ARG(opt_val_string_asciz, comment), + ARG(opt_val_string_asciz, passphrase))) -FUNC(val_string_asciz, ssh2_fingerprint_blob, (ARG(val_string_ptrlen, blob), ARG(fptype, fptype))) +FUNC(val_string_asciz, ssh2_fingerprint_blob, + (ARG(val_string_ptrlen, blob), ARG(fptype, fptype))) /* * Password hashing. */ -FUNC_WRAPPED(val_string, argon2, (ARG(argon2flavour, flavour), ARG(uint, mem), ARG(uint, passes), ARG(uint, parallel), ARG(uint, taglen), ARG(val_string_ptrlen, P), ARG(val_string_ptrlen, S), ARG(val_string_ptrlen, K), ARG(val_string_ptrlen, X))) -FUNC(val_string, argon2_long_hash, (ARG(uint, length), ARG(val_string_ptrlen, data))) +FUNC_WRAPPED(val_string, argon2, + (ARG(argon2flavour, flavour), ARG(uint, mem), ARG(uint, passes), + ARG(uint, parallel), ARG(uint, taglen), ARG(val_string_ptrlen, P), + ARG(val_string_ptrlen, S), ARG(val_string_ptrlen, K), + ARG(val_string_ptrlen, X))) +FUNC(val_string, argon2_long_hash, + (ARG(uint, length), ARG(val_string_ptrlen, data))) /* * Key generation functions. */ -FUNC_WRAPPED(val_key, rsa_generate, (ARG(uint, bits), ARG(boolean, strong), ARG(val_pgc, pgc))) +FUNC_WRAPPED(val_key, rsa_generate, + (ARG(uint, bits), ARG(boolean, strong), ARG(val_pgc, pgc))) FUNC_WRAPPED(val_key, dsa_generate, (ARG(uint, bits), ARG(val_pgc, pgc))) FUNC_WRAPPED(opt_val_key, ecdsa_generate, (ARG(uint, bits))) FUNC_WRAPPED(opt_val_key, eddsa_generate, (ARG(uint, bits))) -FUNC(val_rsa, rsa1_generate, (ARG(uint, bits), ARG(boolean, strong), ARG(val_pgc, pgc))) +FUNC(val_rsa, rsa1_generate, + (ARG(uint, bits), ARG(boolean, strong), ARG(val_pgc, pgc))) FUNC(val_pgc, primegen_new_context, (ARG(primegenpolicy, policy))) -FUNC_WRAPPED(opt_val_mpint, primegen_generate, (ARG(val_pgc, ctx), ARG(consumed_val_pcs, pcs))) -FUNC(val_string, primegen_mpu_certificate, (ARG(val_pgc, ctx), ARG(val_mpint, p))) +FUNC_WRAPPED(opt_val_mpint, primegen_generate, + (ARG(val_pgc, ctx), ARG(consumed_val_pcs, pcs))) +FUNC(val_string, primegen_mpu_certificate, + (ARG(val_pgc, ctx), ARG(val_mpint, p))) FUNC(val_pcs, pcs_new, (ARG(uint, bits))) -FUNC(val_pcs, pcs_new_with_firstbits, (ARG(uint, bits), ARG(uint, first), ARG(uint, nfirst))) -FUNC(void, pcs_require_residue, (ARG(val_pcs, s), ARG(val_mpint, mod), ARG(val_mpint, res))) +FUNC(val_pcs, pcs_new_with_firstbits, + (ARG(uint, bits), ARG(uint, first), ARG(uint, nfirst))) +FUNC(void, pcs_require_residue, + (ARG(val_pcs, s), ARG(val_mpint, mod), ARG(val_mpint, res))) FUNC(void, pcs_require_residue_1, (ARG(val_pcs, s), ARG(val_mpint, mod))) -FUNC(void, pcs_require_residue_1_mod_prime, (ARG(val_pcs, s), ARG(val_mpint, mod))) -FUNC(void, pcs_avoid_residue_small, (ARG(val_pcs, s), ARG(uint, mod), ARG(uint, res))) +FUNC(void, pcs_require_residue_1_mod_prime, + (ARG(val_pcs, s), ARG(val_mpint, mod))) +FUNC(void, pcs_avoid_residue_small, + (ARG(val_pcs, s), ARG(uint, mod), ARG(uint, res))) FUNC(void, pcs_try_sophie_germain, (ARG(val_pcs, s))) FUNC(void, pcs_set_oneshot, (ARG(val_pcs, s))) FUNC(void, pcs_ready, (ARG(val_pcs, s))) -FUNC(void, pcs_inspect, (ARG(val_pcs, pcs), ARG(out_val_mpint, limit_out), ARG(out_val_mpint, factor_out), ARG(out_val_mpint, addend_out))) +FUNC(void, pcs_inspect, + (ARG(val_pcs, pcs), ARG(out_val_mpint, limit_out), + ARG(out_val_mpint, factor_out), ARG(out_val_mpint, addend_out))) FUNC(val_mpint, pcs_generate, (ARG(val_pcs, s))) FUNC(val_pockle, pockle_new, (VOID)) FUNC(uint, pockle_mark, (ARG(val_pockle, pockle))) FUNC(void, pockle_release, (ARG(val_pockle, pockle), ARG(uint, mark))) -FUNC(pocklestatus, pockle_add_small_prime, (ARG(val_pockle, pockle), ARG(val_mpint, p))) -FUNC_WRAPPED(pocklestatus, pockle_add_prime, (ARG(val_pockle, pockle), ARG(val_mpint, p), ARG(mpint_list, factors), ARG(val_mpint, witness))) +FUNC(pocklestatus, pockle_add_small_prime, + (ARG(val_pockle, pockle), ARG(val_mpint, p))) +FUNC_WRAPPED(pocklestatus, pockle_add_prime, + (ARG(val_pockle, pockle), ARG(val_mpint, p), + ARG(mpint_list, factors), ARG(val_mpint, witness))) FUNC(val_string, pockle_mpu, (ARG(val_pockle, pockle), ARG(val_mpint, p))) FUNC(val_millerrabin, miller_rabin_new, (ARG(val_mpint, p))) -FUNC(mr_result, miller_rabin_test, (ARG(val_millerrabin, mr), ARG(val_mpint, w))) +FUNC(mr_result, miller_rabin_test, + (ARG(val_millerrabin, mr), ARG(val_mpint, w))) /* * Miscellaneous. */ FUNC(val_wpoint, ecdsa_public, (ARG(val_mpint, private_key), ARG(keyalg, alg))) FUNC(val_epoint, eddsa_public, (ARG(val_mpint, private_key), ARG(keyalg, alg))) -FUNC_WRAPPED(val_string, des_encrypt_xdmauth, (ARG(val_string_ptrlen, key), ARG(val_string_ptrlen, blk))) -FUNC_WRAPPED(val_string, des_decrypt_xdmauth, (ARG(val_string_ptrlen, key), ARG(val_string_ptrlen, blk))) -FUNC_WRAPPED(val_string, des3_encrypt_pubkey, (ARG(val_string_ptrlen, key), ARG(val_string_ptrlen, blk))) -FUNC_WRAPPED(val_string, des3_decrypt_pubkey, (ARG(val_string_ptrlen, key), ARG(val_string_ptrlen, blk))) -FUNC_WRAPPED(val_string, des3_encrypt_pubkey_ossh, (ARG(val_string_ptrlen, key), ARG(val_string_ptrlen, iv), ARG(val_string_ptrlen, blk))) -FUNC_WRAPPED(val_string, des3_decrypt_pubkey_ossh, (ARG(val_string_ptrlen, key), ARG(val_string_ptrlen, iv), ARG(val_string_ptrlen, blk))) -FUNC_WRAPPED(val_string, aes256_encrypt_pubkey, (ARG(val_string_ptrlen, key), ARG(val_string_ptrlen, iv), ARG(val_string_ptrlen, blk))) -FUNC_WRAPPED(val_string, aes256_decrypt_pubkey, (ARG(val_string_ptrlen, key), ARG(val_string_ptrlen, iv), ARG(val_string_ptrlen, blk))) +FUNC_WRAPPED(val_string, des_encrypt_xdmauth, + (ARG(val_string_ptrlen, key), ARG(val_string_ptrlen, blk))) +FUNC_WRAPPED(val_string, des_decrypt_xdmauth, + (ARG(val_string_ptrlen, key), ARG(val_string_ptrlen, blk))) +FUNC_WRAPPED(val_string, des3_encrypt_pubkey, + (ARG(val_string_ptrlen, key), ARG(val_string_ptrlen, blk))) +FUNC_WRAPPED(val_string, des3_decrypt_pubkey, + (ARG(val_string_ptrlen, key), ARG(val_string_ptrlen, blk))) +FUNC_WRAPPED(val_string, des3_encrypt_pubkey_ossh, + (ARG(val_string_ptrlen, key), ARG(val_string_ptrlen, iv), + ARG(val_string_ptrlen, blk))) +FUNC_WRAPPED(val_string, des3_decrypt_pubkey_ossh, + (ARG(val_string_ptrlen, key), ARG(val_string_ptrlen, iv), + ARG(val_string_ptrlen, blk))) +FUNC_WRAPPED(val_string, aes256_encrypt_pubkey, + (ARG(val_string_ptrlen, key), ARG(val_string_ptrlen, iv), + ARG(val_string_ptrlen, blk))) +FUNC_WRAPPED(val_string, aes256_decrypt_pubkey, + (ARG(val_string_ptrlen, key), ARG(val_string_ptrlen, iv), + ARG(val_string_ptrlen, blk))) FUNC(uint, crc32_rfc1662, (ARG(val_string_ptrlen, data))) FUNC(uint, crc32_ssh1, (ARG(val_string_ptrlen, data))) FUNC(uint, crc32_update, (ARG(uint, crc_input), ARG(val_string_ptrlen, data))) -FUNC(boolean, crcda_detect, (ARG(val_string_ptrlen, packet), ARG(val_string_ptrlen, iv))) +FUNC(boolean, crcda_detect, + (ARG(val_string_ptrlen, packet), ARG(val_string_ptrlen, iv))) FUNC(val_string, get_implementations_commasep, (ARG(val_string_ptrlen, alg))) -FUNC(void, http_digest_response, (ARG(out_val_string_binarysink, bs), ARG(val_string_ptrlen, username), ARG(val_string_ptrlen, password), ARG(val_string_ptrlen, realm), ARG(val_string_ptrlen, method), ARG(val_string_ptrlen, uri), ARG(val_string_ptrlen, qop), ARG(val_string_ptrlen, nonce), ARG(val_string_ptrlen, opaque), ARG(uint, nonce_count), ARG(httpdigesthash, hash), ARG(boolean, hash_username))) +FUNC(void, http_digest_response, + (ARG(out_val_string_binarysink, bs), ARG(val_string_ptrlen, username), + ARG(val_string_ptrlen, password), ARG(val_string_ptrlen, realm), + ARG(val_string_ptrlen, method), ARG(val_string_ptrlen, uri), + ARG(val_string_ptrlen, qop), ARG(val_string_ptrlen, nonce), + ARG(val_string_ptrlen, opaque), ARG(uint, nonce_count), + ARG(httpdigesthash, hash), ARG(boolean, hash_username))) /* * These functions aren't part of PuTTY's own API, but are additions @@ -396,5 +554,6 @@ FUNC(void, http_digest_response, (ARG(out_val_string_binarysink, bs), ARG(val_st */ FUNC(void, random_queue, (ARG(val_string_ptrlen, pl))) FUNC(uint, random_queue_len, (VOID)) -FUNC(void, random_make_prng, (ARG(hashalg, hashalg), ARG(val_string_ptrlen, seed))) +FUNC(void, random_make_prng, + (ARG(hashalg, hashalg), ARG(val_string_ptrlen, seed))) FUNC(void, random_clear, (VOID)) -- cgit v1.2.3 From 42120dd1c5184191ba90d127073ef99d28d61b53 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 21 Nov 2021 22:13:02 +0000 Subject: testcrypt: adjust some function parameter names. Now those names appear in help files, I thought it was worth giving them a read-through and spotting any really obviously confusing or wrong ones. Quite a few make more sense in the original context of C than in the derived Python (e.g. 'BinarySink *bs' as a place to write output to makes sense, but the output 'val_string bs' is less helpful). A couple were so confusing that I also corrected them in the original C, notably the misuse of 'wc' for the elliptic curve point input to ecc_weierstrass_point_copy. ('wc' in that section of the code is normally a parameter describing a whole curve.) --- crypto/ecc.h | 4 +- testcrypt.h | 131 ++++++++++++++++++++++++++++++----------------------------- 2 files changed, 69 insertions(+), 66 deletions(-) diff --git a/crypto/ecc.h b/crypto/ecc.h index 96eebdf0..a6115329 100644 --- a/crypto/ecc.h +++ b/crypto/ecc.h @@ -63,7 +63,7 @@ WeierstrassPoint *ecc_weierstrass_point_new_from_x( /* Memory management: copy and free points. */ void ecc_weierstrass_point_copy_into( WeierstrassPoint *dest, WeierstrassPoint *src); -WeierstrassPoint *ecc_weierstrass_point_copy(WeierstrassPoint *wc); +WeierstrassPoint *ecc_weierstrass_point_copy(WeierstrassPoint *orig); void ecc_weierstrass_point_free(WeierstrassPoint *point); /* Check whether a point is actually on the curve. */ @@ -223,7 +223,7 @@ EdwardsPoint *ecc_edwards_point_new_from_y( /* Copy and free points. */ void ecc_edwards_point_copy_into(EdwardsPoint *dest, EdwardsPoint *src); -EdwardsPoint *ecc_edwards_point_copy(EdwardsPoint *ec); +EdwardsPoint *ecc_edwards_point_copy(EdwardsPoint *orig); void ecc_edwards_point_free(EdwardsPoint *point); /* diff --git a/testcrypt.h b/testcrypt.h index 4b843d8f..0da41826 100644 --- a/testcrypt.h +++ b/testcrypt.h @@ -96,9 +96,9 @@ FUNC(uint, mp_cmp_eq, (ARG(val_mpint, a), ARG(val_mpint, b))) FUNC(uint, mp_hs_integer, (ARG(val_mpint, x), ARG(uint, n))) FUNC(uint, mp_eq_integer, (ARG(val_mpint, x), ARG(uint, n))) FUNC(void, mp_min_into, - (ARG(val_mpint, r), ARG(val_mpint, x), ARG(val_mpint, y))) + (ARG(val_mpint, dest), ARG(val_mpint, x), ARG(val_mpint, y))) FUNC(void, mp_max_into, - (ARG(val_mpint, r), ARG(val_mpint, x), ARG(val_mpint, y))) + (ARG(val_mpint, dest), ARG(val_mpint, x), ARG(val_mpint, y))) FUNC(val_mpint, mp_min, (ARG(val_mpint, x), ARG(val_mpint, y))) FUNC(val_mpint, mp_max, (ARG(val_mpint, x), ARG(val_mpint, y))) FUNC(void, mp_copy_into, (ARG(val_mpint, dest), ARG(val_mpint, src))) @@ -106,33 +106,35 @@ FUNC(void, mp_select_into, (ARG(val_mpint, dest), ARG(val_mpint, src0), ARG(val_mpint, src1), ARG(uint, choose_src1))) FUNC(void, mp_add_into, - (ARG(val_mpint, r), ARG(val_mpint, a), ARG(val_mpint, b))) + (ARG(val_mpint, dest), ARG(val_mpint, a), ARG(val_mpint, b))) FUNC(void, mp_sub_into, - (ARG(val_mpint, r), ARG(val_mpint, a), ARG(val_mpint, b))) + (ARG(val_mpint, dest), ARG(val_mpint, a), ARG(val_mpint, b))) FUNC(void, mp_mul_into, - (ARG(val_mpint, r), ARG(val_mpint, a), ARG(val_mpint, b))) + (ARG(val_mpint, dest), ARG(val_mpint, a), ARG(val_mpint, b))) FUNC(val_mpint, mp_add, (ARG(val_mpint, x), ARG(val_mpint, y))) FUNC(val_mpint, mp_sub, (ARG(val_mpint, x), ARG(val_mpint, y))) FUNC(val_mpint, mp_mul, (ARG(val_mpint, x), ARG(val_mpint, y))) FUNC(void, mp_and_into, - (ARG(val_mpint, r), ARG(val_mpint, a), ARG(val_mpint, b))) + (ARG(val_mpint, dest), ARG(val_mpint, a), ARG(val_mpint, b))) FUNC(void, mp_or_into, - (ARG(val_mpint, r), ARG(val_mpint, a), ARG(val_mpint, b))) + (ARG(val_mpint, dest), ARG(val_mpint, a), ARG(val_mpint, b))) FUNC(void, mp_xor_into, - (ARG(val_mpint, r), ARG(val_mpint, a), ARG(val_mpint, b))) + (ARG(val_mpint, dest), ARG(val_mpint, a), ARG(val_mpint, b))) FUNC(void, mp_bic_into, - (ARG(val_mpint, r), ARG(val_mpint, a), ARG(val_mpint, b))) + (ARG(val_mpint, dest), ARG(val_mpint, a), ARG(val_mpint, b))) FUNC(void, mp_copy_integer_into, (ARG(val_mpint, dest), ARG(uint, n))) FUNC(void, mp_add_integer_into, - (ARG(val_mpint, r), ARG(val_mpint, a), ARG(uint, n))) + (ARG(val_mpint, dest), ARG(val_mpint, a), ARG(uint, n))) FUNC(void, mp_sub_integer_into, - (ARG(val_mpint, r), ARG(val_mpint, a), ARG(uint, n))) + (ARG(val_mpint, dest), ARG(val_mpint, a), ARG(uint, n))) FUNC(void, mp_mul_integer_into, - (ARG(val_mpint, r), ARG(val_mpint, a), ARG(uint, n))) + (ARG(val_mpint, dest), ARG(val_mpint, a), ARG(uint, n))) FUNC(void, mp_cond_add_into, - (ARG(val_mpint, r), ARG(val_mpint, a), ARG(val_mpint, b), ARG(uint, yes))) + (ARG(val_mpint, dest), ARG(val_mpint, a), ARG(val_mpint, b), + ARG(uint, yes))) FUNC(void, mp_cond_sub_into, - (ARG(val_mpint, r), ARG(val_mpint, a), ARG(val_mpint, b), ARG(uint, yes))) + (ARG(val_mpint, dest), ARG(val_mpint, a), ARG(val_mpint, b), + ARG(uint, yes))) FUNC(void, mp_cond_swap, (ARG(val_mpint, x0), ARG(val_mpint, x1), ARG(uint, swap))) FUNC(void, mp_cond_clear, (ARG(val_mpint, x), ARG(uint, clear))) @@ -160,13 +162,13 @@ FUNC(val_monty, monty_new, (ARG(val_mpint, modulus))) FUNC_WRAPPED(val_mpint, monty_modulus, (ARG(val_monty, mc))) FUNC_WRAPPED(val_mpint, monty_identity, (ARG(val_monty, mc))) FUNC(void, monty_import_into, - (ARG(val_monty, mc), ARG(val_mpint, r), ARG(val_mpint, x))) + (ARG(val_monty, mc), ARG(val_mpint, dest), ARG(val_mpint, x))) FUNC(val_mpint, monty_import, (ARG(val_monty, mc), ARG(val_mpint, x))) FUNC(void, monty_export_into, - (ARG(val_monty, mc), ARG(val_mpint, r), ARG(val_mpint, x))) + (ARG(val_monty, mc), ARG(val_mpint, dest), ARG(val_mpint, x))) FUNC(val_mpint, monty_export, (ARG(val_monty, mc), ARG(val_mpint, x))) FUNC(void, monty_mul_into, - (ARG(val_monty, mc), ARG(val_mpint, r), ARG(val_mpint, x), + (ARG(val_monty, mc), ARG(val_mpint, dest), ARG(val_mpint, x), ARG(val_mpint, y))) FUNC(val_mpint, monty_add, (ARG(val_monty, mc), ARG(val_mpint, x), ARG(val_mpint, y))) @@ -188,14 +190,14 @@ FUNC(val_mpint, mp_modadd, FUNC(val_mpint, mp_modsub, (ARG(val_mpint, x), ARG(val_mpint, y), ARG(val_mpint, modulus))) FUNC(void, mp_lshift_safe_into, - (ARG(val_mpint, r), ARG(val_mpint, x), ARG(uint, shift))) + (ARG(val_mpint, dest), ARG(val_mpint, x), ARG(uint, shift))) FUNC(void, mp_rshift_safe_into, - (ARG(val_mpint, r), ARG(val_mpint, x), ARG(uint, shift))) + (ARG(val_mpint, dest), ARG(val_mpint, x), ARG(uint, shift))) FUNC(val_mpint, mp_rshift_safe, (ARG(val_mpint, x), ARG(uint, shift))) FUNC(void, mp_lshift_fixed_into, - (ARG(val_mpint, r), ARG(val_mpint, a), ARG(uint, shift))) + (ARG(val_mpint, dest), ARG(val_mpint, x), ARG(uint, shift))) FUNC(void, mp_rshift_fixed_into, - (ARG(val_mpint, r), ARG(val_mpint, x), ARG(uint, shift))) + (ARG(val_mpint, dest), ARG(val_mpint, x), ARG(uint, shift))) FUNC(val_mpint, mp_rshift_fixed, (ARG(val_mpint, x), ARG(uint, shift))) FUNC(val_mpint, mp_random_bits, (ARG(uint, bits))) FUNC(val_mpint, mp_random_in_range, (ARG(val_mpint, lo), ARG(val_mpint, hi))) @@ -211,7 +213,7 @@ FUNC(val_wpoint, ecc_weierstrass_point_new, (ARG(val_wcurve, curve), ARG(val_mpint, x), ARG(val_mpint, y))) FUNC(val_wpoint, ecc_weierstrass_point_new_from_x, (ARG(val_wcurve, curve), ARG(val_mpint, x), ARG(uint, desired_y_parity))) -FUNC(val_wpoint, ecc_weierstrass_point_copy, (ARG(val_wpoint, wc))) +FUNC(val_wpoint, ecc_weierstrass_point_copy, (ARG(val_wpoint, orig))) FUNC(uint, ecc_weierstrass_point_valid, (ARG(val_wpoint, P))) FUNC(val_wpoint, ecc_weierstrass_add_general, (ARG(val_wpoint, P), ARG(val_wpoint, Q))) @@ -219,14 +221,14 @@ FUNC(val_wpoint, ecc_weierstrass_add, (ARG(val_wpoint, P), ARG(val_wpoint, Q))) FUNC(val_wpoint, ecc_weierstrass_double, (ARG(val_wpoint, P))) FUNC(val_wpoint, ecc_weierstrass_multiply, (ARG(val_wpoint, B), ARG(val_mpint, n))) -FUNC(uint, ecc_weierstrass_is_identity, (ARG(val_wpoint, wp))) +FUNC(uint, ecc_weierstrass_is_identity, (ARG(val_wpoint, P))) /* The output pointers in get_affine all become extra output values */ FUNC(void, ecc_weierstrass_get_affine, - (ARG(val_wpoint, wp), ARG(out_val_mpint, x), ARG(out_val_mpint, y))) + (ARG(val_wpoint, P), ARG(out_val_mpint, x), ARG(out_val_mpint, y))) FUNC(val_mcurve, ecc_montgomery_curve, (ARG(val_mpint, p), ARG(val_mpint, a), ARG(val_mpint, b))) FUNC(val_mpoint, ecc_montgomery_point_new, - (ARG(val_mcurve, mc), ARG(val_mpint, x))) + (ARG(val_mcurve, curve), ARG(val_mpint, x))) FUNC(val_mpoint, ecc_montgomery_point_copy, (ARG(val_mpoint, orig))) FUNC(val_mpoint, ecc_montgomery_diff_add, (ARG(val_mpoint, P), ARG(val_mpoint, Q), ARG(val_mpoint, PminusQ))) @@ -234,8 +236,8 @@ FUNC(val_mpoint, ecc_montgomery_double, (ARG(val_mpoint, P))) FUNC(val_mpoint, ecc_montgomery_multiply, (ARG(val_mpoint, B), ARG(val_mpint, n))) FUNC(void, ecc_montgomery_get_affine, - (ARG(val_mpoint, mp), ARG(out_val_mpint, x))) -FUNC(boolean, ecc_montgomery_is_identity, (ARG(val_mpoint, mp))) + (ARG(val_mpoint, P), ARG(out_val_mpint, x))) +FUNC(boolean, ecc_montgomery_is_identity, (ARG(val_mpoint, P))) FUNC(val_ecurve, ecc_edwards_curve, (ARG(val_mpint, p), ARG(val_mpint, d), ARG(val_mpint, a), ARG(opt_val_mpint, nonsquare_mod_p))) @@ -243,12 +245,12 @@ FUNC(val_epoint, ecc_edwards_point_new, (ARG(val_ecurve, curve), ARG(val_mpint, x), ARG(val_mpint, y))) FUNC(val_epoint, ecc_edwards_point_new_from_y, (ARG(val_ecurve, curve), ARG(val_mpint, y), ARG(uint, desired_x_parity))) -FUNC(val_epoint, ecc_edwards_point_copy, (ARG(val_epoint, ec))) +FUNC(val_epoint, ecc_edwards_point_copy, (ARG(val_epoint, orig))) FUNC(val_epoint, ecc_edwards_add, (ARG(val_epoint, P), ARG(val_epoint, Q))) FUNC(val_epoint, ecc_edwards_multiply, (ARG(val_epoint, B), ARG(val_mpint, n))) FUNC(uint, ecc_edwards_eq, (ARG(val_epoint, P), ARG(val_epoint, Q))) FUNC(void, ecc_edwards_get_affine, - (ARG(val_epoint, wp), ARG(out_val_mpint, x), ARG(out_val_mpint, y))) + (ARG(val_epoint, P), ARG(out_val_mpint, x), ARG(out_val_mpint, y))) /* * The ssh_hash abstraction. Note the 'consumed', indicating that @@ -262,7 +264,7 @@ FUNC(void, ssh_hash_reset, (ARG(val_hash, h))) FUNC(val_hash, ssh_hash_copy, (ARG(val_hash, orig))) FUNC_WRAPPED(val_string, ssh_hash_digest, (ARG(val_hash, h))) FUNC_WRAPPED(val_string, ssh_hash_final, (ARG(consumed_val_hash, h))) -FUNC(void, ssh_hash_update, (ARG(val_hash, h), ARG(val_string_ptrlen, pl))) +FUNC(void, ssh_hash_update, (ARG(val_hash, h), ARG(val_string_ptrlen, data))) FUNC(opt_val_hash, blake2b_new_general, (ARG(uint, hashlen))) @@ -274,7 +276,7 @@ FUNC(opt_val_hash, blake2b_new_general, (ARG(uint, hashlen))) FUNC(val_mac, ssh2_mac_new, (ARG(macalg, alg), ARG(opt_val_cipher, cipher))) FUNC(void, ssh2_mac_setkey, (ARG(val_mac, m), ARG(val_string_ptrlen, key))) FUNC(void, ssh2_mac_start, (ARG(val_mac, m))) -FUNC(void, ssh2_mac_update, (ARG(val_mac, m), ARG(val_string_ptrlen, pl))) +FUNC(void, ssh2_mac_update, (ARG(val_mac, m), ARG(val_string_ptrlen, data))) FUNC_WRAPPED(val_string, ssh2_mac_genresult, (ARG(val_mac, m))) FUNC(val_string_asciz_const, ssh2_mac_text_name, (ARG(val_mac, m))) @@ -285,26 +287,26 @@ FUNC(val_string_asciz_const, ssh2_mac_text_name, (ARG(val_mac, m))) * all the functions that output key and signature blobs do it by * returning a string. */ -FUNC(val_key, ssh_key_new_pub, (ARG(keyalg, self), ARG(val_string_ptrlen, pub))) +FUNC(val_key, ssh_key_new_pub, (ARG(keyalg, alg), ARG(val_string_ptrlen, pub))) FUNC(opt_val_key, ssh_key_new_priv, - (ARG(keyalg, self), ARG(val_string_ptrlen, pub), + (ARG(keyalg, alg), ARG(val_string_ptrlen, pub), ARG(val_string_ptrlen, priv))) FUNC(opt_val_key, ssh_key_new_priv_openssh, - (ARG(keyalg, self), ARG(val_string_binarysource, src))) + (ARG(keyalg, alg), ARG(val_string_binarysource, src))) FUNC(opt_val_string_asciz, ssh_key_invalid, (ARG(val_key, key), ARG(uint, flags))) FUNC(void, ssh_key_sign, (ARG(val_key, key), ARG(val_string_ptrlen, data), ARG(uint, flags), - ARG(out_val_string_binarysink, bs))) + ARG(out_val_string_binarysink, sig))) FUNC(boolean, ssh_key_verify, (ARG(val_key, key), ARG(val_string_ptrlen, sig), ARG(val_string_ptrlen, data))) FUNC(void, ssh_key_public_blob, - (ARG(val_key, key), ARG(out_val_string_binarysink, bs))) + (ARG(val_key, key), ARG(out_val_string_binarysink, blob))) FUNC(void, ssh_key_private_blob, - (ARG(val_key, key), ARG(out_val_string_binarysink, bs))) + (ARG(val_key, key), ARG(out_val_string_binarysink, blob))) FUNC(void, ssh_key_openssh_blob, - (ARG(val_key, key), ARG(out_val_string_binarysink, bs))) + (ARG(val_key, key), ARG(out_val_string_binarysink, blob))) FUNC(val_string_asciz, ssh_key_cache_str, (ARG(val_key, key))) FUNC(val_keycomponents, ssh_key_components, (ARG(val_key, key))) FUNC(uint, ssh_key_public_bits, @@ -343,8 +345,8 @@ FUNC_WRAPPED(val_string, ssh_cipher_decrypt_length, /* * Integer Diffie-Hellman. */ -FUNC(val_dh, dh_setup_group, (ARG(dh_group, kex))) -FUNC(val_dh, dh_setup_gex, (ARG(val_mpint, pval), ARG(val_mpint, gval))) +FUNC(val_dh, dh_setup_group, (ARG(dh_group, group))) +FUNC(val_dh, dh_setup_gex, (ARG(val_mpint, p), ARG(val_mpint, g))) FUNC(uint, dh_modulus_bit_size, (ARG(val_dh, ctx))) FUNC(val_mpint, dh_create_e, (ARG(val_dh, ctx), ARG(uint, nbits))) FUNC_WRAPPED(boolean, dh_validate_f, (ARG(val_dh, ctx), ARG(val_mpint, f))) @@ -353,11 +355,11 @@ FUNC(val_mpint, dh_find_K, (ARG(val_dh, ctx), ARG(val_mpint, f))) /* * Elliptic-curve Diffie-Hellman. */ -FUNC(val_ecdh, ssh_ecdhkex_newkey, (ARG(ecdh_alg, kex))) +FUNC(val_ecdh, ssh_ecdhkex_newkey, (ARG(ecdh_alg, alg))) FUNC(void, ssh_ecdhkex_getpublic, - (ARG(val_ecdh, key), ARG(out_val_string_binarysink, bs))) + (ARG(val_ecdh, key), ARG(out_val_string_binarysink, pub))) FUNC(opt_val_mpint, ssh_ecdhkex_getkey, - (ARG(val_ecdh, key), ARG(val_string_ptrlen, remoteKey))) + (ARG(val_ecdh, key), ARG(val_string_ptrlen, pub))) /* * RSA key exchange, and also the BinarySource get function @@ -392,11 +394,11 @@ FUNC_WRAPPED(val_string, rsa_ssh1_decrypt_pkcs1, FUNC(val_string_asciz, rsastr_fmt, (ARG(val_rsa, key))) FUNC(val_string_asciz, rsa_ssh1_fingerprint, (ARG(val_rsa, key))) FUNC(void, rsa_ssh1_public_blob, - (ARG(out_val_string_binarysink, bs), ARG(val_rsa, key), + (ARG(out_val_string_binarysink, blob), ARG(val_rsa, key), ARG(rsaorder, order))) FUNC(int, rsa_ssh1_public_blob_len, (ARG(val_string_ptrlen, data))) FUNC(void, rsa_ssh1_private_blob_agent, - (ARG(out_val_string_binarysink, bs), ARG(val_rsa, key))) + (ARG(out_val_string_binarysink, blob), ARG(val_rsa, key))) /* * The PRNG type. Similarly to hashes and MACs, I've invented an extra @@ -404,12 +406,12 @@ FUNC(void, rsa_ssh1_private_blob_agent, * exposed BinarySink. */ FUNC(val_prng, prng_new, (ARG(hashalg, hashalg))) -FUNC(void, prng_seed_begin, (ARG(val_prng, p))) +FUNC(void, prng_seed_begin, (ARG(val_prng, pr))) FUNC(void, prng_seed_update, (ARG(val_prng, pr), ARG(val_string_ptrlen, data))) -FUNC(void, prng_seed_finish, (ARG(val_prng, p))) -FUNC_WRAPPED(val_string, prng_read, (ARG(val_prng, p), ARG(uint, size))) +FUNC(void, prng_seed_finish, (ARG(val_prng, pr))) +FUNC_WRAPPED(val_string, prng_read, (ARG(val_prng, pr), ARG(uint, size))) FUNC(void, prng_add_entropy, - (ARG(val_prng, p), ARG(uint, source_id), ARG(val_string_ptrlen, data))) + (ARG(val_prng, pr), ARG(uint, source_id), ARG(val_string_ptrlen, data))) /* * Key load/save functions, or rather, the BinarySource / strbuf API @@ -424,23 +426,23 @@ FUNC(boolean, rsa1_encrypted_s, FUNC(boolean, ppk_loadpub_s, (ARG(val_string_binarysource, src), ARG(out_opt_val_string_asciz, algorithm), - ARG(out_val_string_binarysink, bs), - ARG(out_opt_val_string_asciz, commentptr), - ARG(out_opt_val_string_asciz_const, errorstr))) + ARG(out_val_string_binarysink, blob), + ARG(out_opt_val_string_asciz, comment), + ARG(out_opt_val_string_asciz_const, error))) FUNC(int, rsa1_loadpub_s, - (ARG(val_string_binarysource, src), ARG(out_val_string_binarysink, bs), - ARG(out_opt_val_string_asciz, commentptr), - ARG(out_opt_val_string_asciz_const, errorstr))) + (ARG(val_string_binarysource, src), ARG(out_val_string_binarysink, blob), + ARG(out_opt_val_string_asciz, comment), + ARG(out_opt_val_string_asciz_const, error))) FUNC_WRAPPED(opt_val_key, ppk_load_s, (ARG(val_string_binarysource, src), ARG(out_opt_val_string_asciz, comment), ARG(opt_val_string_asciz, passphrase), - ARG(out_opt_val_string_asciz_const, errorstr))) + ARG(out_opt_val_string_asciz_const, error))) FUNC_WRAPPED(int, rsa1_load_s, (ARG(val_string_binarysource, src), ARG(val_rsa, key), ARG(out_opt_val_string_asciz, comment), ARG(opt_val_string_asciz, passphrase), - ARG(out_opt_val_string_asciz_const, errorstr))) + ARG(out_opt_val_string_asciz_const, error))) FUNC_WRAPPED(val_string, ppk_save_sb, (ARG(val_key, key), ARG(opt_val_string_asciz, comment), ARG(opt_val_string_asciz, passphrase), ARG(uint, fmt_version), @@ -541,18 +543,19 @@ FUNC(boolean, crcda_detect, (ARG(val_string_ptrlen, packet), ARG(val_string_ptrlen, iv))) FUNC(val_string, get_implementations_commasep, (ARG(val_string_ptrlen, alg))) FUNC(void, http_digest_response, - (ARG(out_val_string_binarysink, bs), ARG(val_string_ptrlen, username), - ARG(val_string_ptrlen, password), ARG(val_string_ptrlen, realm), - ARG(val_string_ptrlen, method), ARG(val_string_ptrlen, uri), - ARG(val_string_ptrlen, qop), ARG(val_string_ptrlen, nonce), - ARG(val_string_ptrlen, opaque), ARG(uint, nonce_count), - ARG(httpdigesthash, hash), ARG(boolean, hash_username))) + (ARG(out_val_string_binarysink, response), + ARG(val_string_ptrlen, username), ARG(val_string_ptrlen, password), + ARG(val_string_ptrlen, realm), ARG(val_string_ptrlen, method), + ARG(val_string_ptrlen, uri), ARG(val_string_ptrlen, qop), + ARG(val_string_ptrlen, nonce), ARG(val_string_ptrlen, opaque), + ARG(uint, nonce_count), ARG(httpdigesthash, hash), + ARG(boolean, hash_username))) /* * These functions aren't part of PuTTY's own API, but are additions * by testcrypt itself for administrative purposes. */ -FUNC(void, random_queue, (ARG(val_string_ptrlen, pl))) +FUNC(void, random_queue, (ARG(val_string_ptrlen, data))) FUNC(uint, random_queue_len, (VOID)) FUNC(void, random_make_prng, (ARG(hashalg, hashalg), ARG(val_string_ptrlen, seed))) -- cgit v1.2.3 From a434b13050196846bf3afb8347e9b7f130d9f643 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 22 Nov 2021 18:31:05 +0000 Subject: Pass diffiehellman ssh_kex objects to testcrypt. This slightly simplifies the lookup function get_dh_group(), but mostly, the point is to make it more similar to the other lookup functions, because I'm planning to have those autogenerated. --- crypto/diffie-hellman.c | 6 +++--- ssh.h | 3 +++ testcrypt.c | 8 ++++---- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/crypto/diffie-hellman.c b/crypto/diffie-hellman.c index 0461a430..a1ed7cb4 100644 --- a/crypto/diffie-hellman.c +++ b/crypto/diffie-hellman.c @@ -39,7 +39,7 @@ static const struct dh_extra extra_group1 = { false, dh_group1_construct, }; -static const ssh_kex ssh_diffiehellman_group1_sha1 = { +const ssh_kex ssh_diffiehellman_group1_sha1 = { "diffie-hellman-group1-sha1", "group1", KEXTYPE_DH, &ssh_sha1, &extra_group1, }; @@ -54,12 +54,12 @@ static const struct dh_extra extra_group14 = { false, dh_group14_construct, }; -static const ssh_kex ssh_diffiehellman_group14_sha256 = { +const ssh_kex ssh_diffiehellman_group14_sha256 = { "diffie-hellman-group14-sha256", "group14", KEXTYPE_DH, &ssh_sha256, &extra_group14, }; -static const ssh_kex ssh_diffiehellman_group14_sha1 = { +const ssh_kex ssh_diffiehellman_group14_sha1 = { "diffie-hellman-group14-sha1", "group14", KEXTYPE_DH, &ssh_sha1, &extra_group14, }; diff --git a/ssh.h b/ssh.h index c32ff686..7049f2af 100644 --- a/ssh.h +++ b/ssh.h @@ -1023,6 +1023,9 @@ extern const ssh_hashalg ssh_blake2b; extern const ssh_kexes ssh_diffiehellman_group1; extern const ssh_kexes ssh_diffiehellman_group14; extern const ssh_kexes ssh_diffiehellman_gex; +extern const ssh_kex ssh_diffiehellman_group1_sha1; +extern const ssh_kex ssh_diffiehellman_group14_sha256; +extern const ssh_kex ssh_diffiehellman_group14_sha1; extern const ssh_kexes ssh_gssk5_sha1_kex; extern const ssh_kexes ssh_rsa_kex; extern const ssh_kex ssh_ec_kex_curve25519; diff --git a/testcrypt.c b/testcrypt.c index 09f742a5..2fc2ffbf 100644 --- a/testcrypt.c +++ b/testcrypt.c @@ -347,16 +347,16 @@ static const ssh_kex *get_dh_group(BinarySource *in) { static const struct { const char *key; - const ssh_kexes *value; + const ssh_kex *value; } algs[] = { - {"group1", &ssh_diffiehellman_group1}, - {"group14", &ssh_diffiehellman_group14}, + {"group1", &ssh_diffiehellman_group1_sha1}, + {"group14", &ssh_diffiehellman_group14_sha256}, }; ptrlen name = get_word(in); for (size_t i = 0; i < lenof(algs); i++) if (ptrlen_eq_string(name, algs[i].key)) - return algs[i].value->list[0]; + return algs[i].value; fatal_error("dh_group '%.*s': not found", PTRLEN_PRINTF(name)); } -- cgit v1.2.3 From beff1cb600b8b47547fe39bdcbbb61c7ab19557c Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 22 Nov 2021 18:35:08 +0000 Subject: testcrypt: move TD_foo declarations up the file. A completely trivial change which I only put in its own commit so that the next one will have a less confusing-looking diff. --- testcrypt.c | 56 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/testcrypt.c b/testcrypt.c index 2fc2ffbf..c6d8fc97 100644 --- a/testcrypt.c +++ b/testcrypt.c @@ -200,6 +200,34 @@ static ptrlen get_word(BinarySource *in) return toret; } +typedef uintmax_t TD_uint; +typedef bool TD_boolean; +typedef ptrlen TD_val_string_ptrlen; +typedef char *TD_val_string_asciz; +typedef BinarySource *TD_val_string_binarysource; +typedef unsigned *TD_out_uint; +typedef BinarySink *TD_out_val_string_binarysink; +typedef const char *TD_opt_val_string_asciz; +typedef char **TD_out_val_string_asciz; +typedef char **TD_out_opt_val_string_asciz; +typedef const char **TD_out_opt_val_string_asciz_const; +typedef ssh_hash *TD_consumed_val_hash; +typedef const ssh_hashalg *TD_hashalg; +typedef const ssh2_macalg *TD_macalg; +typedef const ssh_keyalg *TD_keyalg; +typedef const ssh_cipheralg *TD_cipheralg; +typedef const ssh_kex *TD_dh_group; +typedef const ssh_kex *TD_ecdh_alg; +typedef RsaSsh1Order TD_rsaorder; +typedef key_components *TD_keycomponents; +typedef const PrimeGenerationPolicy *TD_primegenpolicy; +typedef struct mpint_list TD_mpint_list; +typedef PockleStatus TD_pocklestatus; +typedef struct mr_result TD_mr_result; +typedef Argon2Flavour TD_argon2flavour; +typedef FingerprintType TD_fptype; +typedef HttpDigestHash TD_httpdigesthash; + static const ssh_hashalg *get_hashalg(BinarySource *in) { static const struct { @@ -1335,34 +1363,6 @@ OPTIONAL_PTR_FUNC(cipher) OPTIONAL_PTR_FUNC(mpint) OPTIONAL_PTR_FUNC(string) -typedef uintmax_t TD_uint; -typedef bool TD_boolean; -typedef ptrlen TD_val_string_ptrlen; -typedef char *TD_val_string_asciz; -typedef BinarySource *TD_val_string_binarysource; -typedef unsigned *TD_out_uint; -typedef BinarySink *TD_out_val_string_binarysink; -typedef const char *TD_opt_val_string_asciz; -typedef char **TD_out_val_string_asciz; -typedef char **TD_out_opt_val_string_asciz; -typedef const char **TD_out_opt_val_string_asciz_const; -typedef ssh_hash *TD_consumed_val_hash; -typedef const ssh_hashalg *TD_hashalg; -typedef const ssh2_macalg *TD_macalg; -typedef const ssh_keyalg *TD_keyalg; -typedef const ssh_cipheralg *TD_cipheralg; -typedef const ssh_kex *TD_dh_group; -typedef const ssh_kex *TD_ecdh_alg; -typedef RsaSsh1Order TD_rsaorder; -typedef key_components *TD_keycomponents; -typedef const PrimeGenerationPolicy *TD_primegenpolicy; -typedef struct mpint_list TD_mpint_list; -typedef PockleStatus TD_pocklestatus; -typedef struct mr_result TD_mr_result; -typedef Argon2Flavour TD_argon2flavour; -typedef FingerprintType TD_fptype; -typedef HttpDigestHash TD_httpdigesthash; - /* * HERE BE DRAGONS: the horrible C preprocessor business that reads * testcrypt.h and generates a marshalling wrapper for each exported -- cgit v1.2.3 From d4fedfebdc5fea7c3ee876b95bc9453954f5e4bd Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 22 Nov 2021 18:38:36 +0000 Subject: testcrypt: move enumerated types out into another header. Now all the lookup functions like get_hashalg() and get_primegenpolicy() are autogenerated by macro expansion from a header a little like the existing testcrypt.h. But the macros are much simpler this time. This doesn't really change anything, _except_ that now I'll be able to reinclude the same header to do other things with the list of enumerated types. --- testcrypt-enum.h | 144 +++++++++++++++++++++++++ testcrypt.c | 313 ++++++------------------------------------------------- 2 files changed, 175 insertions(+), 282 deletions(-) create mode 100644 testcrypt-enum.h diff --git a/testcrypt-enum.h b/testcrypt-enum.h new file mode 100644 index 00000000..cec8d836 --- /dev/null +++ b/testcrypt-enum.h @@ -0,0 +1,144 @@ +BEGIN_ENUM_TYPE(hashalg) + ENUM_VALUE("md5", &ssh_md5) + ENUM_VALUE("sha1", &ssh_sha1) + ENUM_VALUE("sha1_sw", &ssh_sha1_sw) + ENUM_VALUE("sha256", &ssh_sha256) + ENUM_VALUE("sha384", &ssh_sha384) + ENUM_VALUE("sha512", &ssh_sha512) + ENUM_VALUE("sha256_sw", &ssh_sha256_sw) + ENUM_VALUE("sha384_sw", &ssh_sha384_sw) + ENUM_VALUE("sha512_sw", &ssh_sha512_sw) +#if HAVE_SHA_NI + ENUM_VALUE("sha1_ni", &ssh_sha1_ni) + ENUM_VALUE("sha256_ni", &ssh_sha256_ni) +#endif +#if HAVE_NEON_CRYPTO + ENUM_VALUE("sha1_neon", &ssh_sha1_neon) + ENUM_VALUE("sha256_neon", &ssh_sha256_neon) +#endif +#if HAVE_NEON_SHA512 + ENUM_VALUE("sha384_neon", &ssh_sha384_neon) + ENUM_VALUE("sha512_neon", &ssh_sha512_neon) +#endif + ENUM_VALUE("sha3_224", &ssh_sha3_224) + ENUM_VALUE("sha3_256", &ssh_sha3_256) + ENUM_VALUE("sha3_384", &ssh_sha3_384) + ENUM_VALUE("sha3_512", &ssh_sha3_512) + ENUM_VALUE("shake256_114bytes", &ssh_shake256_114bytes) + ENUM_VALUE("blake2b", &ssh_blake2b) +END_ENUM_TYPE(hashalg) + +BEGIN_ENUM_TYPE(macalg) + ENUM_VALUE("hmac_md5", &ssh_hmac_md5) + ENUM_VALUE("hmac_sha1", &ssh_hmac_sha1) + ENUM_VALUE("hmac_sha1_buggy", &ssh_hmac_sha1_buggy) + ENUM_VALUE("hmac_sha1_96", &ssh_hmac_sha1_96) + ENUM_VALUE("hmac_sha1_96_buggy", &ssh_hmac_sha1_96_buggy) + ENUM_VALUE("hmac_sha256", &ssh_hmac_sha256) + ENUM_VALUE("poly1305", &ssh2_poly1305) +END_ENUM_TYPE(macalg) + +BEGIN_ENUM_TYPE(keyalg) + ENUM_VALUE("dsa", &ssh_dsa) + ENUM_VALUE("rsa", &ssh_rsa) + ENUM_VALUE("ed25519", &ssh_ecdsa_ed25519) + ENUM_VALUE("ed448", &ssh_ecdsa_ed448) + ENUM_VALUE("p256", &ssh_ecdsa_nistp256) + ENUM_VALUE("p384", &ssh_ecdsa_nistp384) + ENUM_VALUE("p521", &ssh_ecdsa_nistp521) +END_ENUM_TYPE(keyalg) + +BEGIN_ENUM_TYPE(cipheralg) + ENUM_VALUE("3des_ctr", &ssh_3des_ssh2_ctr) + ENUM_VALUE("3des_ssh2", &ssh_3des_ssh2) + ENUM_VALUE("3des_ssh1", &ssh_3des_ssh1) + ENUM_VALUE("des_cbc", &ssh_des) + ENUM_VALUE("aes256_ctr", &ssh_aes256_sdctr) + ENUM_VALUE("aes256_cbc", &ssh_aes256_cbc) + ENUM_VALUE("aes192_ctr", &ssh_aes192_sdctr) + ENUM_VALUE("aes192_cbc", &ssh_aes192_cbc) + ENUM_VALUE("aes128_ctr", &ssh_aes128_sdctr) + ENUM_VALUE("aes128_cbc", &ssh_aes128_cbc) + ENUM_VALUE("aes256_ctr_sw", &ssh_aes256_sdctr_sw) + ENUM_VALUE("aes256_cbc_sw", &ssh_aes256_cbc_sw) + ENUM_VALUE("aes192_ctr_sw", &ssh_aes192_sdctr_sw) + ENUM_VALUE("aes192_cbc_sw", &ssh_aes192_cbc_sw) + ENUM_VALUE("aes128_ctr_sw", &ssh_aes128_sdctr_sw) + ENUM_VALUE("aes128_cbc_sw", &ssh_aes128_cbc_sw) +#if HAVE_AES_NI + ENUM_VALUE("aes256_ctr_ni", &ssh_aes256_sdctr_ni) + ENUM_VALUE("aes256_cbc_ni", &ssh_aes256_cbc_ni) + ENUM_VALUE("aes192_ctr_ni", &ssh_aes192_sdctr_ni) + ENUM_VALUE("aes192_cbc_ni", &ssh_aes192_cbc_ni) + ENUM_VALUE("aes128_ctr_ni", &ssh_aes128_sdctr_ni) + ENUM_VALUE("aes128_cbc_ni", &ssh_aes128_cbc_ni) +#endif +#if HAVE_NEON_CRYPTO + ENUM_VALUE("aes256_ctr_neon", &ssh_aes256_sdctr_neon) + ENUM_VALUE("aes256_cbc_neon", &ssh_aes256_cbc_neon) + ENUM_VALUE("aes192_ctr_neon", &ssh_aes192_sdctr_neon) + ENUM_VALUE("aes192_cbc_neon", &ssh_aes192_cbc_neon) + ENUM_VALUE("aes128_ctr_neon", &ssh_aes128_sdctr_neon) + ENUM_VALUE("aes128_cbc_neon", &ssh_aes128_cbc_neon) +#endif + ENUM_VALUE("blowfish_ctr", &ssh_blowfish_ssh2_ctr) + ENUM_VALUE("blowfish_ssh2", &ssh_blowfish_ssh2) + ENUM_VALUE("blowfish_ssh1", &ssh_blowfish_ssh1) + ENUM_VALUE("arcfour256", &ssh_arcfour256_ssh2) + ENUM_VALUE("arcfour128", &ssh_arcfour128_ssh2) + ENUM_VALUE("chacha20_poly1305", &ssh2_chacha20_poly1305) +END_ENUM_TYPE(cipheralg) + +BEGIN_ENUM_TYPE(dh_group) + ENUM_VALUE("group1", &ssh_diffiehellman_group1_sha1) + ENUM_VALUE("group14", &ssh_diffiehellman_group14_sha256) +END_ENUM_TYPE(dh_group) + +BEGIN_ENUM_TYPE(ecdh_alg) + ENUM_VALUE("curve25519", &ssh_ec_kex_curve25519) + ENUM_VALUE("curve448", &ssh_ec_kex_curve448) + ENUM_VALUE("nistp256", &ssh_ec_kex_nistp256) + ENUM_VALUE("nistp384", &ssh_ec_kex_nistp384) + ENUM_VALUE("nistp521", &ssh_ec_kex_nistp521) +END_ENUM_TYPE(ecdh_alg) + +BEGIN_ENUM_TYPE(rsaorder) + ENUM_VALUE("exponent_first", RSA_SSH1_EXPONENT_FIRST) + ENUM_VALUE("modulus_first", RSA_SSH1_MODULUS_FIRST) +END_ENUM_TYPE(rsaorder) + +BEGIN_ENUM_TYPE(primegenpolicy) + ENUM_VALUE("probabilistic", &primegen_probabilistic) + ENUM_VALUE("provable_fast", &primegen_provable_fast) + ENUM_VALUE("provable_maurer_simple", &primegen_provable_maurer_simple) + ENUM_VALUE("provable_maurer_complex", &primegen_provable_maurer_complex) +END_ENUM_TYPE(primegenpolicy) + +BEGIN_ENUM_TYPE(argon2flavour) + ENUM_VALUE("d", Argon2d) + ENUM_VALUE("i", Argon2i) + ENUM_VALUE("id", Argon2id) + /* I expect to forget which spelling I chose, so let's support many */ + ENUM_VALUE("argon2d", Argon2d) + ENUM_VALUE("argon2i", Argon2i) + ENUM_VALUE("argon2id", Argon2id) + ENUM_VALUE("Argon2d", Argon2d) + ENUM_VALUE("Argon2i", Argon2i) + ENUM_VALUE("Argon2id", Argon2id) +END_ENUM_TYPE(argon2flavour) + +BEGIN_ENUM_TYPE(fptype) + ENUM_VALUE("md5", SSH_FPTYPE_MD5) + ENUM_VALUE("sha256", SSH_FPTYPE_SHA256) +END_ENUM_TYPE(fptype) + +/* + * cproxy.h already has a list macro mapping protocol-specified + * strings to the list of HTTP Digest hash functions. Rather than + * invent a separate one for testcrypt, reuse the existing names. + */ +BEGIN_ENUM_TYPE(httpdigesthash) + #define DECL_ARRAY(id, str, alg, bits) ENUM_VALUE(str, id) + HTTP_DIGEST_HASHES(DECL_ARRAY) + #undef DECL_ARRAY +END_ENUM_TYPE(httpdigesthash) diff --git a/testcrypt.c b/testcrypt.c index c6d8fc97..f6cb8517 100644 --- a/testcrypt.c +++ b/testcrypt.c @@ -228,288 +228,37 @@ typedef Argon2Flavour TD_argon2flavour; typedef FingerprintType TD_fptype; typedef HttpDigestHash TD_httpdigesthash; -static const ssh_hashalg *get_hashalg(BinarySource *in) -{ - static const struct { - const char *key; - const ssh_hashalg *value; - } algs[] = { - {"md5", &ssh_md5}, - {"sha1", &ssh_sha1}, - {"sha1_sw", &ssh_sha1_sw}, - {"sha256", &ssh_sha256}, - {"sha384", &ssh_sha384}, - {"sha512", &ssh_sha512}, - {"sha256_sw", &ssh_sha256_sw}, - {"sha384_sw", &ssh_sha384_sw}, - {"sha512_sw", &ssh_sha512_sw}, -#if HAVE_SHA_NI - {"sha1_ni", &ssh_sha1_ni}, - {"sha256_ni", &ssh_sha256_ni}, -#endif -#if HAVE_NEON_CRYPTO - {"sha1_neon", &ssh_sha1_neon}, - {"sha256_neon", &ssh_sha256_neon}, -#endif -#if HAVE_NEON_SHA512 - {"sha384_neon", &ssh_sha384_neon}, - {"sha512_neon", &ssh_sha512_neon}, -#endif - {"sha3_224", &ssh_sha3_224}, - {"sha3_256", &ssh_sha3_256}, - {"sha3_384", &ssh_sha3_384}, - {"sha3_512", &ssh_sha3_512}, - {"shake256_114bytes", &ssh_shake256_114bytes}, - {"blake2b", &ssh_blake2b}, - }; - - ptrlen name = get_word(in); - for (size_t i = 0; i < lenof(algs); i++) - if (ptrlen_eq_string(name, algs[i].key)) - return algs[i].value; - - fatal_error("hashalg '%.*s': not found", PTRLEN_PRINTF(name)); -} - -static const ssh2_macalg *get_macalg(BinarySource *in) -{ - static const struct { - const char *key; - const ssh2_macalg *value; - } algs[] = { - {"hmac_md5", &ssh_hmac_md5}, - {"hmac_sha1", &ssh_hmac_sha1}, - {"hmac_sha1_buggy", &ssh_hmac_sha1_buggy}, - {"hmac_sha1_96", &ssh_hmac_sha1_96}, - {"hmac_sha1_96_buggy", &ssh_hmac_sha1_96_buggy}, - {"hmac_sha256", &ssh_hmac_sha256}, - {"poly1305", &ssh2_poly1305}, - }; - - ptrlen name = get_word(in); - for (size_t i = 0; i < lenof(algs); i++) - if (ptrlen_eq_string(name, algs[i].key)) - return algs[i].value; - - fatal_error("macalg '%.*s': not found", PTRLEN_PRINTF(name)); -} - -static const ssh_keyalg *get_keyalg(BinarySource *in) -{ - static const struct { - const char *key; - const ssh_keyalg *value; - } algs[] = { - {"dsa", &ssh_dsa}, - {"rsa", &ssh_rsa}, - {"ed25519", &ssh_ecdsa_ed25519}, - {"ed448", &ssh_ecdsa_ed448}, - {"p256", &ssh_ecdsa_nistp256}, - {"p384", &ssh_ecdsa_nistp384}, - {"p521", &ssh_ecdsa_nistp521}, - }; - - ptrlen name = get_word(in); - for (size_t i = 0; i < lenof(algs); i++) - if (ptrlen_eq_string(name, algs[i].key)) - return algs[i].value; - - fatal_error("keyalg '%.*s': not found", PTRLEN_PRINTF(name)); -} - -static const ssh_cipheralg *get_cipheralg(BinarySource *in) -{ - static const struct { - const char *key; - const ssh_cipheralg *value; - } algs[] = { - {"3des_ctr", &ssh_3des_ssh2_ctr}, - {"3des_ssh2", &ssh_3des_ssh2}, - {"3des_ssh1", &ssh_3des_ssh1}, - {"des_cbc", &ssh_des}, - {"aes256_ctr", &ssh_aes256_sdctr}, - {"aes256_cbc", &ssh_aes256_cbc}, - {"aes192_ctr", &ssh_aes192_sdctr}, - {"aes192_cbc", &ssh_aes192_cbc}, - {"aes128_ctr", &ssh_aes128_sdctr}, - {"aes128_cbc", &ssh_aes128_cbc}, - {"aes256_ctr_sw", &ssh_aes256_sdctr_sw}, - {"aes256_cbc_sw", &ssh_aes256_cbc_sw}, - {"aes192_ctr_sw", &ssh_aes192_sdctr_sw}, - {"aes192_cbc_sw", &ssh_aes192_cbc_sw}, - {"aes128_ctr_sw", &ssh_aes128_sdctr_sw}, - {"aes128_cbc_sw", &ssh_aes128_cbc_sw}, -#if HAVE_AES_NI - {"aes256_ctr_ni", &ssh_aes256_sdctr_ni}, - {"aes256_cbc_ni", &ssh_aes256_cbc_ni}, - {"aes192_ctr_ni", &ssh_aes192_sdctr_ni}, - {"aes192_cbc_ni", &ssh_aes192_cbc_ni}, - {"aes128_ctr_ni", &ssh_aes128_sdctr_ni}, - {"aes128_cbc_ni", &ssh_aes128_cbc_ni}, -#endif -#if HAVE_NEON_CRYPTO - {"aes256_ctr_neon", &ssh_aes256_sdctr_neon}, - {"aes256_cbc_neon", &ssh_aes256_cbc_neon}, - {"aes192_ctr_neon", &ssh_aes192_sdctr_neon}, - {"aes192_cbc_neon", &ssh_aes192_cbc_neon}, - {"aes128_ctr_neon", &ssh_aes128_sdctr_neon}, - {"aes128_cbc_neon", &ssh_aes128_cbc_neon}, -#endif - {"blowfish_ctr", &ssh_blowfish_ssh2_ctr}, - {"blowfish_ssh2", &ssh_blowfish_ssh2}, - {"blowfish_ssh1", &ssh_blowfish_ssh1}, - {"arcfour256", &ssh_arcfour256_ssh2}, - {"arcfour128", &ssh_arcfour128_ssh2}, - {"chacha20_poly1305", &ssh2_chacha20_poly1305}, - }; - - ptrlen name = get_word(in); - for (size_t i = 0; i < lenof(algs); i++) - if (ptrlen_eq_string(name, algs[i].key)) - return algs[i].value; - - fatal_error("cipheralg '%.*s': not found", PTRLEN_PRINTF(name)); -} - -static const ssh_kex *get_dh_group(BinarySource *in) -{ - static const struct { - const char *key; - const ssh_kex *value; - } algs[] = { - {"group1", &ssh_diffiehellman_group1_sha1}, - {"group14", &ssh_diffiehellman_group14_sha256}, - }; - - ptrlen name = get_word(in); - for (size_t i = 0; i < lenof(algs); i++) - if (ptrlen_eq_string(name, algs[i].key)) - return algs[i].value; - - fatal_error("dh_group '%.*s': not found", PTRLEN_PRINTF(name)); -} - -static const ssh_kex *get_ecdh_alg(BinarySource *in) -{ - static const struct { - const char *key; - const ssh_kex *value; - } algs[] = { - {"curve25519", &ssh_ec_kex_curve25519}, - {"curve448", &ssh_ec_kex_curve448}, - {"nistp256", &ssh_ec_kex_nistp256}, - {"nistp384", &ssh_ec_kex_nistp384}, - {"nistp521", &ssh_ec_kex_nistp521}, - }; - - ptrlen name = get_word(in); - for (size_t i = 0; i < lenof(algs); i++) - if (ptrlen_eq_string(name, algs[i].key)) - return algs[i].value; - - fatal_error("ecdh_alg '%.*s': not found", PTRLEN_PRINTF(name)); -} - -static RsaSsh1Order get_rsaorder(BinarySource *in) -{ - static const struct { - const char *key; - RsaSsh1Order value; - } orders[] = { - {"exponent_first", RSA_SSH1_EXPONENT_FIRST}, - {"modulus_first", RSA_SSH1_MODULUS_FIRST}, - }; - - ptrlen name = get_word(in); - for (size_t i = 0; i < lenof(orders); i++) - if (ptrlen_eq_string(name, orders[i].key)) - return orders[i].value; - - fatal_error("rsaorder '%.*s': not found", PTRLEN_PRINTF(name)); -} - -static const PrimeGenerationPolicy *get_primegenpolicy(BinarySource *in) -{ - static const struct { - const char *key; - const PrimeGenerationPolicy *value; - } algs[] = { - {"probabilistic", &primegen_probabilistic}, - {"provable_fast", &primegen_provable_fast}, - {"provable_maurer_simple", &primegen_provable_maurer_simple}, - {"provable_maurer_complex", &primegen_provable_maurer_complex}, - }; - - ptrlen name = get_word(in); - for (size_t i = 0; i < lenof(algs); i++) - if (ptrlen_eq_string(name, algs[i].key)) - return algs[i].value; - - fatal_error("primegenpolicy '%.*s': not found", PTRLEN_PRINTF(name)); -} - -static Argon2Flavour get_argon2flavour(BinarySource *in) -{ - static const struct { - const char *key; - Argon2Flavour value; - } algs[] = { - {"d", Argon2d}, - {"i", Argon2i}, - {"id", Argon2id}, - /* I expect to forget which spelling I chose, so let's support many */ - {"argon2d", Argon2d}, - {"argon2i", Argon2i}, - {"argon2id", Argon2id}, - {"Argon2d", Argon2d}, - {"Argon2i", Argon2i}, - {"Argon2id", Argon2id}, - }; - - ptrlen name = get_word(in); - for (size_t i = 0; i < lenof(algs); i++) - if (ptrlen_eq_string(name, algs[i].key)) - return algs[i].value; - - fatal_error("Argon2 flavour '%.*s': not found", PTRLEN_PRINTF(name)); -} - -static FingerprintType get_fptype(BinarySource *in) -{ - static const struct { - const char *key; - FingerprintType value; - } ids[] = { - {"md5", SSH_FPTYPE_MD5}, - {"sha256", SSH_FPTYPE_SHA256}, - }; - - ptrlen name = get_word(in); - for (size_t i = 0; i < lenof(ids); i++) - if (ptrlen_eq_string(name, ids[i].key)) - return ids[i].value; - - fatal_error("fingerprint type '%.*s': not found", PTRLEN_PRINTF(name)); -} - -static HttpDigestHash get_httpdigesthash(BinarySource *in) -{ - static const struct { - const char *key; - HttpDigestHash value; - } hashes[] = { - #define DECL_ARRAY(id, str, alg, bits) {str, id}, - HTTP_DIGEST_HASHES(DECL_ARRAY) - #undef DECL_ARRAY - }; - - ptrlen name = get_word(in); - for (size_t i = 0; i < lenof(hashes); i++) - if (ptrlen_eq_string(name, hashes[i].key)) - return hashes[i].value; - - fatal_error("httpdigesthash '%.*s': not found", PTRLEN_PRINTF(name)); -} +#define BEGIN_ENUM_TYPE(name) \ + static bool enum_translate_##name(ptrlen valname, TD_##name *out) { \ + static const struct { \ + const char *key; \ + TD_##name value; \ + } mapping[] = { +#define ENUM_VALUE(name, value) {name, value}, +#define END_ENUM_TYPE(name) \ + }; \ + for (size_t i = 0; i < lenof(mapping); i++) \ + if (ptrlen_eq_string(valname, mapping[i].key)) { \ + if (out) \ + *out = mapping[i].value; \ + return true; \ + } \ + return false; \ + } \ + \ + static TD_##name get_##name(BinarySource *in) { \ + ptrlen valname = get_word(in); \ + TD_##name out; \ + if (enum_translate_##name(valname, &out)) \ + return out; \ + else \ + fatal_error("%s '%.*s': not found", \ + #name, PTRLEN_PRINTF(valname)); \ + } +#include "testcrypt-enum.h" +#undef BEGIN_ENUM_TYPE +#undef ENUM_VALUE +#undef END_ENUM_TYPE static uintmax_t get_uint(BinarySource *in) { -- cgit v1.2.3 From 9ceb2c49ae79c40e7836589cf3f04385043e45e6 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 22 Nov 2021 18:39:45 +0000 Subject: testcrypt: introduce and use 'checkenum' protocol query. This allows the Python side of testcrypt to check in advance if a given string is a valid element of an enumeration, and if not, cleanly throw a Python-level exception without terminating the testcrypt subprocess. Should be useful in both manual use (when I'm trying something out by hand and make a typo or misremember a spelling), and automated use (if I make the same kind of error in cryptsuite.py then the exception dump will make more sense). In order to do this, the new handle_checkenum() function has to recognise all the enumerated types by name and match them up to their lookup functions - which is just the kind of thing that can now be done easily be reincluding testcrypt-enum.h with different #defines. --- test/testcrypt.py | 10 +++++++++- testcrypt.c | 20 ++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/test/testcrypt.py b/test/testcrypt.py index d21f19c7..b71f851d 100644 --- a/test/testcrypt.py +++ b/test/testcrypt.py @@ -103,6 +103,8 @@ method_prefixes = { } method_lists = {t: [] for t in method_prefixes} +checked_enum_values = {} + class Value(object): def __init__(self, typename, ident): self._typename = typename @@ -182,7 +184,13 @@ def make_argword(arg, argtype, fnname, argindex, argname, to_preserve): "argon2flavour", "fptype", "httpdigesthash"}: arg = coerce_to_bytes(arg) if isinstance(arg, bytes) and b" " not in arg: - return arg + dictkey = (typename, arg) + if dictkey not in checked_enum_values: + retwords = childprocess.funcall("checkenum", [typename, arg]) + assert len(retwords) == 1 + checked_enum_values[dictkey] = (retwords[0] == b"ok") + if checked_enum_values[dictkey]: + return arg if typename == "mpint_list": sublist = [make_argword(len(arg), ("uint", False), fnname, argindex, argname, to_preserve)] diff --git a/testcrypt.c b/testcrypt.c index f6cb8517..68e2d11d 100644 --- a/testcrypt.c +++ b/testcrypt.c @@ -623,6 +623,25 @@ static void handle_mp_dump(BinarySource *in, strbuf *out) put_byte(out, '\n'); } +static void handle_checkenum(BinarySource *in, strbuf *out) +{ + ptrlen type = get_word(in); + ptrlen value = get_word(in); + bool ok = false; + + #define BEGIN_ENUM_TYPE(name) \ + if (ptrlen_eq_string(type, #name)) \ + ok = enum_translate_##name(value, NULL); + #define ENUM_VALUE(name, value) + #define END_ENUM_TYPE(name) + #include "testcrypt-enum.h" + #undef BEGIN_ENUM_TYPE + #undef ENUM_VALUE + #undef END_ENUM_TYPE + + put_dataz(out, ok ? "ok\n" : "bad\n"); +} + static void random_queue(ptrlen pl) { bufchain_add(&random_data_queue, pl.ptr, pl.len); @@ -1356,6 +1375,7 @@ static void process_line(BinarySource *in, strbuf *out) DISPATCH_COMMAND(getstring); DISPATCH_COMMAND(mp_literal); DISPATCH_COMMAND(mp_dump); + DISPATCH_COMMAND(checkenum); #undef DISPATCH_COMMAND #define FUNC_INNER(outtype, fname, realname, args) \ -- cgit v1.2.3 From 67b11add593153244f4ec910352c4a65fb3d63c9 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 22 Nov 2021 18:50:12 +0000 Subject: Move some tests into the test subdirectory. Now testcrypt has _two_ header files, that's more files than I want at the top level, so I decided to move it. It has a good claim to live in either 'test' or 'crypto', but in the end I decided it wasn't quite specific enough to crypto (it already also tests things in keygen and proxy), and also, the Python half of the mechanism already lives in 'test', so it can live alongside that. Having done that, it seemed silly to leave testsc and testzlib at the top level: those have 'test' in the names as well, so they can go in the test subdir as well. While I'm renaming, also renamed testcrypt.h to testcrypt-func.h to distinguish it from the new testcrypt-enum.h. --- CMakeLists.txt | 2 +- test/testcrypt-enum.h | 144 ++++ test/testcrypt-func.h | 562 ++++++++++++++++ test/testcrypt.c | 1485 +++++++++++++++++++++++++++++++++++++++++ test/testcrypt.py | 6 +- test/testsc.c | 1753 +++++++++++++++++++++++++++++++++++++++++++++++++ test/testzlib.c | 114 ++++ testcrypt-enum.h | 144 ---- testcrypt.c | 1485 ----------------------------------------- testcrypt.h | 562 ---------------- testsc.c | 1753 ------------------------------------------------- testzlib.c | 114 ---- unix/CMakeLists.txt | 4 +- 13 files changed, 4064 insertions(+), 4064 deletions(-) create mode 100644 test/testcrypt-enum.h create mode 100644 test/testcrypt-func.h create mode 100644 test/testcrypt.c create mode 100644 test/testsc.c create mode 100644 test/testzlib.c delete mode 100644 testcrypt-enum.h delete mode 100644 testcrypt.c delete mode 100644 testcrypt.h delete mode 100644 testsc.c delete mode 100644 testzlib.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 4b60c9d2..c2e69e3a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -69,7 +69,7 @@ add_library(otherbackends STATIC add_subdirectory(otherbackends) add_executable(testcrypt - testcrypt.c sshpubk.c ssh/crc-attack-detector.c) + test/testcrypt.c sshpubk.c ssh/crc-attack-detector.c) target_link_libraries(testcrypt keygen crypto utils ${platform_libraries}) diff --git a/test/testcrypt-enum.h b/test/testcrypt-enum.h new file mode 100644 index 00000000..cec8d836 --- /dev/null +++ b/test/testcrypt-enum.h @@ -0,0 +1,144 @@ +BEGIN_ENUM_TYPE(hashalg) + ENUM_VALUE("md5", &ssh_md5) + ENUM_VALUE("sha1", &ssh_sha1) + ENUM_VALUE("sha1_sw", &ssh_sha1_sw) + ENUM_VALUE("sha256", &ssh_sha256) + ENUM_VALUE("sha384", &ssh_sha384) + ENUM_VALUE("sha512", &ssh_sha512) + ENUM_VALUE("sha256_sw", &ssh_sha256_sw) + ENUM_VALUE("sha384_sw", &ssh_sha384_sw) + ENUM_VALUE("sha512_sw", &ssh_sha512_sw) +#if HAVE_SHA_NI + ENUM_VALUE("sha1_ni", &ssh_sha1_ni) + ENUM_VALUE("sha256_ni", &ssh_sha256_ni) +#endif +#if HAVE_NEON_CRYPTO + ENUM_VALUE("sha1_neon", &ssh_sha1_neon) + ENUM_VALUE("sha256_neon", &ssh_sha256_neon) +#endif +#if HAVE_NEON_SHA512 + ENUM_VALUE("sha384_neon", &ssh_sha384_neon) + ENUM_VALUE("sha512_neon", &ssh_sha512_neon) +#endif + ENUM_VALUE("sha3_224", &ssh_sha3_224) + ENUM_VALUE("sha3_256", &ssh_sha3_256) + ENUM_VALUE("sha3_384", &ssh_sha3_384) + ENUM_VALUE("sha3_512", &ssh_sha3_512) + ENUM_VALUE("shake256_114bytes", &ssh_shake256_114bytes) + ENUM_VALUE("blake2b", &ssh_blake2b) +END_ENUM_TYPE(hashalg) + +BEGIN_ENUM_TYPE(macalg) + ENUM_VALUE("hmac_md5", &ssh_hmac_md5) + ENUM_VALUE("hmac_sha1", &ssh_hmac_sha1) + ENUM_VALUE("hmac_sha1_buggy", &ssh_hmac_sha1_buggy) + ENUM_VALUE("hmac_sha1_96", &ssh_hmac_sha1_96) + ENUM_VALUE("hmac_sha1_96_buggy", &ssh_hmac_sha1_96_buggy) + ENUM_VALUE("hmac_sha256", &ssh_hmac_sha256) + ENUM_VALUE("poly1305", &ssh2_poly1305) +END_ENUM_TYPE(macalg) + +BEGIN_ENUM_TYPE(keyalg) + ENUM_VALUE("dsa", &ssh_dsa) + ENUM_VALUE("rsa", &ssh_rsa) + ENUM_VALUE("ed25519", &ssh_ecdsa_ed25519) + ENUM_VALUE("ed448", &ssh_ecdsa_ed448) + ENUM_VALUE("p256", &ssh_ecdsa_nistp256) + ENUM_VALUE("p384", &ssh_ecdsa_nistp384) + ENUM_VALUE("p521", &ssh_ecdsa_nistp521) +END_ENUM_TYPE(keyalg) + +BEGIN_ENUM_TYPE(cipheralg) + ENUM_VALUE("3des_ctr", &ssh_3des_ssh2_ctr) + ENUM_VALUE("3des_ssh2", &ssh_3des_ssh2) + ENUM_VALUE("3des_ssh1", &ssh_3des_ssh1) + ENUM_VALUE("des_cbc", &ssh_des) + ENUM_VALUE("aes256_ctr", &ssh_aes256_sdctr) + ENUM_VALUE("aes256_cbc", &ssh_aes256_cbc) + ENUM_VALUE("aes192_ctr", &ssh_aes192_sdctr) + ENUM_VALUE("aes192_cbc", &ssh_aes192_cbc) + ENUM_VALUE("aes128_ctr", &ssh_aes128_sdctr) + ENUM_VALUE("aes128_cbc", &ssh_aes128_cbc) + ENUM_VALUE("aes256_ctr_sw", &ssh_aes256_sdctr_sw) + ENUM_VALUE("aes256_cbc_sw", &ssh_aes256_cbc_sw) + ENUM_VALUE("aes192_ctr_sw", &ssh_aes192_sdctr_sw) + ENUM_VALUE("aes192_cbc_sw", &ssh_aes192_cbc_sw) + ENUM_VALUE("aes128_ctr_sw", &ssh_aes128_sdctr_sw) + ENUM_VALUE("aes128_cbc_sw", &ssh_aes128_cbc_sw) +#if HAVE_AES_NI + ENUM_VALUE("aes256_ctr_ni", &ssh_aes256_sdctr_ni) + ENUM_VALUE("aes256_cbc_ni", &ssh_aes256_cbc_ni) + ENUM_VALUE("aes192_ctr_ni", &ssh_aes192_sdctr_ni) + ENUM_VALUE("aes192_cbc_ni", &ssh_aes192_cbc_ni) + ENUM_VALUE("aes128_ctr_ni", &ssh_aes128_sdctr_ni) + ENUM_VALUE("aes128_cbc_ni", &ssh_aes128_cbc_ni) +#endif +#if HAVE_NEON_CRYPTO + ENUM_VALUE("aes256_ctr_neon", &ssh_aes256_sdctr_neon) + ENUM_VALUE("aes256_cbc_neon", &ssh_aes256_cbc_neon) + ENUM_VALUE("aes192_ctr_neon", &ssh_aes192_sdctr_neon) + ENUM_VALUE("aes192_cbc_neon", &ssh_aes192_cbc_neon) + ENUM_VALUE("aes128_ctr_neon", &ssh_aes128_sdctr_neon) + ENUM_VALUE("aes128_cbc_neon", &ssh_aes128_cbc_neon) +#endif + ENUM_VALUE("blowfish_ctr", &ssh_blowfish_ssh2_ctr) + ENUM_VALUE("blowfish_ssh2", &ssh_blowfish_ssh2) + ENUM_VALUE("blowfish_ssh1", &ssh_blowfish_ssh1) + ENUM_VALUE("arcfour256", &ssh_arcfour256_ssh2) + ENUM_VALUE("arcfour128", &ssh_arcfour128_ssh2) + ENUM_VALUE("chacha20_poly1305", &ssh2_chacha20_poly1305) +END_ENUM_TYPE(cipheralg) + +BEGIN_ENUM_TYPE(dh_group) + ENUM_VALUE("group1", &ssh_diffiehellman_group1_sha1) + ENUM_VALUE("group14", &ssh_diffiehellman_group14_sha256) +END_ENUM_TYPE(dh_group) + +BEGIN_ENUM_TYPE(ecdh_alg) + ENUM_VALUE("curve25519", &ssh_ec_kex_curve25519) + ENUM_VALUE("curve448", &ssh_ec_kex_curve448) + ENUM_VALUE("nistp256", &ssh_ec_kex_nistp256) + ENUM_VALUE("nistp384", &ssh_ec_kex_nistp384) + ENUM_VALUE("nistp521", &ssh_ec_kex_nistp521) +END_ENUM_TYPE(ecdh_alg) + +BEGIN_ENUM_TYPE(rsaorder) + ENUM_VALUE("exponent_first", RSA_SSH1_EXPONENT_FIRST) + ENUM_VALUE("modulus_first", RSA_SSH1_MODULUS_FIRST) +END_ENUM_TYPE(rsaorder) + +BEGIN_ENUM_TYPE(primegenpolicy) + ENUM_VALUE("probabilistic", &primegen_probabilistic) + ENUM_VALUE("provable_fast", &primegen_provable_fast) + ENUM_VALUE("provable_maurer_simple", &primegen_provable_maurer_simple) + ENUM_VALUE("provable_maurer_complex", &primegen_provable_maurer_complex) +END_ENUM_TYPE(primegenpolicy) + +BEGIN_ENUM_TYPE(argon2flavour) + ENUM_VALUE("d", Argon2d) + ENUM_VALUE("i", Argon2i) + ENUM_VALUE("id", Argon2id) + /* I expect to forget which spelling I chose, so let's support many */ + ENUM_VALUE("argon2d", Argon2d) + ENUM_VALUE("argon2i", Argon2i) + ENUM_VALUE("argon2id", Argon2id) + ENUM_VALUE("Argon2d", Argon2d) + ENUM_VALUE("Argon2i", Argon2i) + ENUM_VALUE("Argon2id", Argon2id) +END_ENUM_TYPE(argon2flavour) + +BEGIN_ENUM_TYPE(fptype) + ENUM_VALUE("md5", SSH_FPTYPE_MD5) + ENUM_VALUE("sha256", SSH_FPTYPE_SHA256) +END_ENUM_TYPE(fptype) + +/* + * cproxy.h already has a list macro mapping protocol-specified + * strings to the list of HTTP Digest hash functions. Rather than + * invent a separate one for testcrypt, reuse the existing names. + */ +BEGIN_ENUM_TYPE(httpdigesthash) + #define DECL_ARRAY(id, str, alg, bits) ENUM_VALUE(str, id) + HTTP_DIGEST_HASHES(DECL_ARRAY) + #undef DECL_ARRAY +END_ENUM_TYPE(httpdigesthash) diff --git a/test/testcrypt-func.h b/test/testcrypt-func.h new file mode 100644 index 00000000..0da41826 --- /dev/null +++ b/test/testcrypt-func.h @@ -0,0 +1,562 @@ +/* + * List of functions exported by the 'testcrypt' system to provide a + * Python API for running unit tests and auxiliary programs. + * + * Each function definition in this file has the form + * + * FUNC(return-type, function-name, (arguments)) + * + * where 'arguments' in turn is either VOID, or a comma-separated list + * of argument specifications of the form + * + * ARG(argument-type, argument-name) + * + * Type names are always single identifiers, and they have some + * standard prefixes: + * + * 'val_' means that the type refers to something dynamically + * allocated, so that it has a persistent identity, needs to be freed + * when finished with (though this is done automatically by the + * testcrypt.py system via Python's reference counting), and may also + * be mutable. The argument type in C will be a pointer; in Python the + * corresponding argument will be an instance of a 'Value' object + * defined in testcrypt.py. + * + * 'opt_val_' is a modification of 'val_' to indicate that the pointer + * may be NULL. In Python this is translated by accepting (or + * returning) None as an alternative to a Value. + * + * 'out_' on an argument type indicates an additional output + * parameter. The argument type in C has an extra layer of + * indirection, e.g. an 'out_val_mpint' is an 'mpint **' instead of an + * 'mpint *', identifying a pointer variable where the returned + * pointer value will be written. In the Python API, these arguments + * do not appear in the argument list of the Python function; instead + * they cause the return value to become a tuple, with additional + * types appended. For example, a declaration like + * + * FUNC(val_foo, example, (ARG(out_val_bar, bar), ARG(val_baz, baz))) + * + * would identify a function in C with the following prototype, which + * returns a 'foo *' directly and a 'bar *' by writing it through the + * provided 'bar **' pointer argument: + * + * foo *example(bar **extra_output, baz *input); + * + * and in Python this would become a function taking one argument of + * type 'baz' and returning a tuple of the form (foo, bar). + * + * 'out_' and 'opt_' can go together, if a function returns a second + * output value but it may in some cases be NULL. + * + * 'consumed_' on an argument type indicates that the C function + * receiving that argument frees it as a side effect. + * + * Any argument type which does not start 'val_' is plain old data + * with no dynamic allocation requirements. Ordinary C integers are + * sometimes handled this way (e.g. 'uint'). Other plain-data types + * are represented in Python as a string that must be one of a + * recognised set of keywords; in C these variously translate into + * enumeration types (e.g. argon2flavour, rsaorder) or pointers to + * const vtables of one kind or another (e.g. keyalg, hashalg, + * primegenpolicy). + * + * If a function definition begins with FUNC_WRAPPED rather than FUNC, + * it means that the underlying C function has a suffix "_wrapper", + * e.g. ssh_cipher_setiv_wrapper(). Those wrappers are defined in + * testcrypt.c itself, and change the API or semantics in a way that + * makes the function more Python-friendly. + */ + +/* + * mpint.h functions. + */ +FUNC(val_mpint, mp_new, (ARG(uint, maxbits))) +FUNC(void, mp_clear, (ARG(val_mpint, x))) +FUNC(val_mpint, mp_from_bytes_le, (ARG(val_string_ptrlen, bytes))) +FUNC(val_mpint, mp_from_bytes_be, (ARG(val_string_ptrlen, bytes))) +FUNC(val_mpint, mp_from_integer, (ARG(uint, n))) +FUNC(val_mpint, mp_from_decimal_pl, (ARG(val_string_ptrlen, decimal))) +FUNC(val_mpint, mp_from_decimal, (ARG(val_string_asciz, decimal))) +FUNC(val_mpint, mp_from_hex_pl, (ARG(val_string_ptrlen, hex))) +FUNC(val_mpint, mp_from_hex, (ARG(val_string_asciz, hex))) +FUNC(val_mpint, mp_copy, (ARG(val_mpint, x))) +FUNC(val_mpint, mp_power_2, (ARG(uint, power))) +FUNC(uint, mp_get_byte, (ARG(val_mpint, x), ARG(uint, byte))) +FUNC(uint, mp_get_bit, (ARG(val_mpint, x), ARG(uint, bit))) +FUNC(void, mp_set_bit, (ARG(val_mpint, x), ARG(uint, bit), ARG(uint, val))) +FUNC(uint, mp_max_bytes, (ARG(val_mpint, x))) +FUNC(uint, mp_max_bits, (ARG(val_mpint, x))) +FUNC(uint, mp_get_nbits, (ARG(val_mpint, x))) +FUNC(val_string_asciz, mp_get_decimal, (ARG(val_mpint, x))) +FUNC(val_string_asciz, mp_get_hex, (ARG(val_mpint, x))) +FUNC(val_string_asciz, mp_get_hex_uppercase, (ARG(val_mpint, x))) +FUNC(uint, mp_cmp_hs, (ARG(val_mpint, a), ARG(val_mpint, b))) +FUNC(uint, mp_cmp_eq, (ARG(val_mpint, a), ARG(val_mpint, b))) +FUNC(uint, mp_hs_integer, (ARG(val_mpint, x), ARG(uint, n))) +FUNC(uint, mp_eq_integer, (ARG(val_mpint, x), ARG(uint, n))) +FUNC(void, mp_min_into, + (ARG(val_mpint, dest), ARG(val_mpint, x), ARG(val_mpint, y))) +FUNC(void, mp_max_into, + (ARG(val_mpint, dest), ARG(val_mpint, x), ARG(val_mpint, y))) +FUNC(val_mpint, mp_min, (ARG(val_mpint, x), ARG(val_mpint, y))) +FUNC(val_mpint, mp_max, (ARG(val_mpint, x), ARG(val_mpint, y))) +FUNC(void, mp_copy_into, (ARG(val_mpint, dest), ARG(val_mpint, src))) +FUNC(void, mp_select_into, + (ARG(val_mpint, dest), ARG(val_mpint, src0), ARG(val_mpint, src1), + ARG(uint, choose_src1))) +FUNC(void, mp_add_into, + (ARG(val_mpint, dest), ARG(val_mpint, a), ARG(val_mpint, b))) +FUNC(void, mp_sub_into, + (ARG(val_mpint, dest), ARG(val_mpint, a), ARG(val_mpint, b))) +FUNC(void, mp_mul_into, + (ARG(val_mpint, dest), ARG(val_mpint, a), ARG(val_mpint, b))) +FUNC(val_mpint, mp_add, (ARG(val_mpint, x), ARG(val_mpint, y))) +FUNC(val_mpint, mp_sub, (ARG(val_mpint, x), ARG(val_mpint, y))) +FUNC(val_mpint, mp_mul, (ARG(val_mpint, x), ARG(val_mpint, y))) +FUNC(void, mp_and_into, + (ARG(val_mpint, dest), ARG(val_mpint, a), ARG(val_mpint, b))) +FUNC(void, mp_or_into, + (ARG(val_mpint, dest), ARG(val_mpint, a), ARG(val_mpint, b))) +FUNC(void, mp_xor_into, + (ARG(val_mpint, dest), ARG(val_mpint, a), ARG(val_mpint, b))) +FUNC(void, mp_bic_into, + (ARG(val_mpint, dest), ARG(val_mpint, a), ARG(val_mpint, b))) +FUNC(void, mp_copy_integer_into, (ARG(val_mpint, dest), ARG(uint, n))) +FUNC(void, mp_add_integer_into, + (ARG(val_mpint, dest), ARG(val_mpint, a), ARG(uint, n))) +FUNC(void, mp_sub_integer_into, + (ARG(val_mpint, dest), ARG(val_mpint, a), ARG(uint, n))) +FUNC(void, mp_mul_integer_into, + (ARG(val_mpint, dest), ARG(val_mpint, a), ARG(uint, n))) +FUNC(void, mp_cond_add_into, + (ARG(val_mpint, dest), ARG(val_mpint, a), ARG(val_mpint, b), + ARG(uint, yes))) +FUNC(void, mp_cond_sub_into, + (ARG(val_mpint, dest), ARG(val_mpint, a), ARG(val_mpint, b), + ARG(uint, yes))) +FUNC(void, mp_cond_swap, + (ARG(val_mpint, x0), ARG(val_mpint, x1), ARG(uint, swap))) +FUNC(void, mp_cond_clear, (ARG(val_mpint, x), ARG(uint, clear))) +FUNC(void, mp_divmod_into, + (ARG(val_mpint, n), ARG(val_mpint, d), ARG(opt_val_mpint, q), + ARG(opt_val_mpint, r))) +FUNC(val_mpint, mp_div, (ARG(val_mpint, n), ARG(val_mpint, d))) +FUNC(val_mpint, mp_mod, (ARG(val_mpint, x), ARG(val_mpint, modulus))) +FUNC(val_mpint, mp_nthroot, + (ARG(val_mpint, y), ARG(uint, n), ARG(opt_val_mpint, remainder))) +FUNC(void, mp_reduce_mod_2to, (ARG(val_mpint, x), ARG(uint, p))) +FUNC(val_mpint, mp_invert_mod_2to, (ARG(val_mpint, x), ARG(uint, p))) +FUNC(val_mpint, mp_invert, (ARG(val_mpint, x), ARG(val_mpint, modulus))) +FUNC(void, mp_gcd_into, + (ARG(val_mpint, a), ARG(val_mpint, b), ARG(opt_val_mpint, gcd_out), + ARG(opt_val_mpint, A_out), ARG(opt_val_mpint, B_out))) +FUNC(val_mpint, mp_gcd, (ARG(val_mpint, a), ARG(val_mpint, b))) +FUNC(uint, mp_coprime, (ARG(val_mpint, a), ARG(val_mpint, b))) +FUNC(val_modsqrt, modsqrt_new, + (ARG(val_mpint, p), ARG(val_mpint, any_nonsquare_mod_p))) +/* The modsqrt functions' 'success' pointer becomes a second return value */ +FUNC(val_mpint, mp_modsqrt, + (ARG(val_modsqrt, sc), ARG(val_mpint, x), ARG(out_uint, success))) +FUNC(val_monty, monty_new, (ARG(val_mpint, modulus))) +FUNC_WRAPPED(val_mpint, monty_modulus, (ARG(val_monty, mc))) +FUNC_WRAPPED(val_mpint, monty_identity, (ARG(val_monty, mc))) +FUNC(void, monty_import_into, + (ARG(val_monty, mc), ARG(val_mpint, dest), ARG(val_mpint, x))) +FUNC(val_mpint, monty_import, (ARG(val_monty, mc), ARG(val_mpint, x))) +FUNC(void, monty_export_into, + (ARG(val_monty, mc), ARG(val_mpint, dest), ARG(val_mpint, x))) +FUNC(val_mpint, monty_export, (ARG(val_monty, mc), ARG(val_mpint, x))) +FUNC(void, monty_mul_into, + (ARG(val_monty, mc), ARG(val_mpint, dest), ARG(val_mpint, x), + ARG(val_mpint, y))) +FUNC(val_mpint, monty_add, + (ARG(val_monty, mc), ARG(val_mpint, x), ARG(val_mpint, y))) +FUNC(val_mpint, monty_sub, + (ARG(val_monty, mc), ARG(val_mpint, x), ARG(val_mpint, y))) +FUNC(val_mpint, monty_mul, + (ARG(val_monty, mc), ARG(val_mpint, x), ARG(val_mpint, y))) +FUNC(val_mpint, monty_pow, + (ARG(val_monty, mc), ARG(val_mpint, base), ARG(val_mpint, exponent))) +FUNC(val_mpint, monty_invert, (ARG(val_monty, mc), ARG(val_mpint, x))) +FUNC(val_mpint, monty_modsqrt, + (ARG(val_modsqrt, sc), ARG(val_mpint, mx), ARG(out_uint, success))) +FUNC(val_mpint, mp_modpow, + (ARG(val_mpint, base), ARG(val_mpint, exponent), ARG(val_mpint, modulus))) +FUNC(val_mpint, mp_modmul, + (ARG(val_mpint, x), ARG(val_mpint, y), ARG(val_mpint, modulus))) +FUNC(val_mpint, mp_modadd, + (ARG(val_mpint, x), ARG(val_mpint, y), ARG(val_mpint, modulus))) +FUNC(val_mpint, mp_modsub, + (ARG(val_mpint, x), ARG(val_mpint, y), ARG(val_mpint, modulus))) +FUNC(void, mp_lshift_safe_into, + (ARG(val_mpint, dest), ARG(val_mpint, x), ARG(uint, shift))) +FUNC(void, mp_rshift_safe_into, + (ARG(val_mpint, dest), ARG(val_mpint, x), ARG(uint, shift))) +FUNC(val_mpint, mp_rshift_safe, (ARG(val_mpint, x), ARG(uint, shift))) +FUNC(void, mp_lshift_fixed_into, + (ARG(val_mpint, dest), ARG(val_mpint, x), ARG(uint, shift))) +FUNC(void, mp_rshift_fixed_into, + (ARG(val_mpint, dest), ARG(val_mpint, x), ARG(uint, shift))) +FUNC(val_mpint, mp_rshift_fixed, (ARG(val_mpint, x), ARG(uint, shift))) +FUNC(val_mpint, mp_random_bits, (ARG(uint, bits))) +FUNC(val_mpint, mp_random_in_range, (ARG(val_mpint, lo), ARG(val_mpint, hi))) + +/* + * ecc.h functions. + */ +FUNC(val_wcurve, ecc_weierstrass_curve, + (ARG(val_mpint, p), ARG(val_mpint, a), ARG(val_mpint, b), + ARG(opt_val_mpint, nonsquare_mod_p))) +FUNC(val_wpoint, ecc_weierstrass_point_new_identity, (ARG(val_wcurve, curve))) +FUNC(val_wpoint, ecc_weierstrass_point_new, + (ARG(val_wcurve, curve), ARG(val_mpint, x), ARG(val_mpint, y))) +FUNC(val_wpoint, ecc_weierstrass_point_new_from_x, + (ARG(val_wcurve, curve), ARG(val_mpint, x), ARG(uint, desired_y_parity))) +FUNC(val_wpoint, ecc_weierstrass_point_copy, (ARG(val_wpoint, orig))) +FUNC(uint, ecc_weierstrass_point_valid, (ARG(val_wpoint, P))) +FUNC(val_wpoint, ecc_weierstrass_add_general, + (ARG(val_wpoint, P), ARG(val_wpoint, Q))) +FUNC(val_wpoint, ecc_weierstrass_add, (ARG(val_wpoint, P), ARG(val_wpoint, Q))) +FUNC(val_wpoint, ecc_weierstrass_double, (ARG(val_wpoint, P))) +FUNC(val_wpoint, ecc_weierstrass_multiply, + (ARG(val_wpoint, B), ARG(val_mpint, n))) +FUNC(uint, ecc_weierstrass_is_identity, (ARG(val_wpoint, P))) +/* The output pointers in get_affine all become extra output values */ +FUNC(void, ecc_weierstrass_get_affine, + (ARG(val_wpoint, P), ARG(out_val_mpint, x), ARG(out_val_mpint, y))) +FUNC(val_mcurve, ecc_montgomery_curve, + (ARG(val_mpint, p), ARG(val_mpint, a), ARG(val_mpint, b))) +FUNC(val_mpoint, ecc_montgomery_point_new, + (ARG(val_mcurve, curve), ARG(val_mpint, x))) +FUNC(val_mpoint, ecc_montgomery_point_copy, (ARG(val_mpoint, orig))) +FUNC(val_mpoint, ecc_montgomery_diff_add, + (ARG(val_mpoint, P), ARG(val_mpoint, Q), ARG(val_mpoint, PminusQ))) +FUNC(val_mpoint, ecc_montgomery_double, (ARG(val_mpoint, P))) +FUNC(val_mpoint, ecc_montgomery_multiply, + (ARG(val_mpoint, B), ARG(val_mpint, n))) +FUNC(void, ecc_montgomery_get_affine, + (ARG(val_mpoint, P), ARG(out_val_mpint, x))) +FUNC(boolean, ecc_montgomery_is_identity, (ARG(val_mpoint, P))) +FUNC(val_ecurve, ecc_edwards_curve, + (ARG(val_mpint, p), ARG(val_mpint, d), ARG(val_mpint, a), + ARG(opt_val_mpint, nonsquare_mod_p))) +FUNC(val_epoint, ecc_edwards_point_new, + (ARG(val_ecurve, curve), ARG(val_mpint, x), ARG(val_mpint, y))) +FUNC(val_epoint, ecc_edwards_point_new_from_y, + (ARG(val_ecurve, curve), ARG(val_mpint, y), ARG(uint, desired_x_parity))) +FUNC(val_epoint, ecc_edwards_point_copy, (ARG(val_epoint, orig))) +FUNC(val_epoint, ecc_edwards_add, (ARG(val_epoint, P), ARG(val_epoint, Q))) +FUNC(val_epoint, ecc_edwards_multiply, (ARG(val_epoint, B), ARG(val_mpint, n))) +FUNC(uint, ecc_edwards_eq, (ARG(val_epoint, P), ARG(val_epoint, Q))) +FUNC(void, ecc_edwards_get_affine, + (ARG(val_epoint, P), ARG(out_val_mpint, x), ARG(out_val_mpint, y))) + +/* + * The ssh_hash abstraction. Note the 'consumed', indicating that + * ssh_hash_final puts its input ssh_hash beyond use. + * + * ssh_hash_update is an invention of testcrypt, handled in the real C + * API by the hash object also functioning as a BinarySink. + */ +FUNC(opt_val_hash, ssh_hash_new, (ARG(hashalg, alg))) +FUNC(void, ssh_hash_reset, (ARG(val_hash, h))) +FUNC(val_hash, ssh_hash_copy, (ARG(val_hash, orig))) +FUNC_WRAPPED(val_string, ssh_hash_digest, (ARG(val_hash, h))) +FUNC_WRAPPED(val_string, ssh_hash_final, (ARG(consumed_val_hash, h))) +FUNC(void, ssh_hash_update, (ARG(val_hash, h), ARG(val_string_ptrlen, data))) + +FUNC(opt_val_hash, blake2b_new_general, (ARG(uint, hashlen))) + +/* + * The ssh2_mac abstraction. Note the optional ssh_cipher parameter + * to ssh2_mac_new. Also, again, I've invented an ssh2_mac_update so + * you can put data into the MAC. + */ +FUNC(val_mac, ssh2_mac_new, (ARG(macalg, alg), ARG(opt_val_cipher, cipher))) +FUNC(void, ssh2_mac_setkey, (ARG(val_mac, m), ARG(val_string_ptrlen, key))) +FUNC(void, ssh2_mac_start, (ARG(val_mac, m))) +FUNC(void, ssh2_mac_update, (ARG(val_mac, m), ARG(val_string_ptrlen, data))) +FUNC_WRAPPED(val_string, ssh2_mac_genresult, (ARG(val_mac, m))) +FUNC(val_string_asciz_const, ssh2_mac_text_name, (ARG(val_mac, m))) + +/* + * The ssh_key abstraction. All the uses of BinarySink and + * BinarySource in parameters are replaced with ordinary strings for + * the testing API: new_priv_openssh just takes a string input, and + * all the functions that output key and signature blobs do it by + * returning a string. + */ +FUNC(val_key, ssh_key_new_pub, (ARG(keyalg, alg), ARG(val_string_ptrlen, pub))) +FUNC(opt_val_key, ssh_key_new_priv, + (ARG(keyalg, alg), ARG(val_string_ptrlen, pub), + ARG(val_string_ptrlen, priv))) +FUNC(opt_val_key, ssh_key_new_priv_openssh, + (ARG(keyalg, alg), ARG(val_string_binarysource, src))) +FUNC(opt_val_string_asciz, ssh_key_invalid, + (ARG(val_key, key), ARG(uint, flags))) +FUNC(void, ssh_key_sign, + (ARG(val_key, key), ARG(val_string_ptrlen, data), ARG(uint, flags), + ARG(out_val_string_binarysink, sig))) +FUNC(boolean, ssh_key_verify, + (ARG(val_key, key), ARG(val_string_ptrlen, sig), + ARG(val_string_ptrlen, data))) +FUNC(void, ssh_key_public_blob, + (ARG(val_key, key), ARG(out_val_string_binarysink, blob))) +FUNC(void, ssh_key_private_blob, + (ARG(val_key, key), ARG(out_val_string_binarysink, blob))) +FUNC(void, ssh_key_openssh_blob, + (ARG(val_key, key), ARG(out_val_string_binarysink, blob))) +FUNC(val_string_asciz, ssh_key_cache_str, (ARG(val_key, key))) +FUNC(val_keycomponents, ssh_key_components, (ARG(val_key, key))) +FUNC(uint, ssh_key_public_bits, + (ARG(keyalg, self), ARG(val_string_ptrlen, blob))) + +/* + * Accessors to retrieve the innards of a 'key_components'. + */ +FUNC(uint, key_components_count, (ARG(val_keycomponents, kc))) +FUNC(opt_val_string_asciz_const, key_components_nth_name, + (ARG(val_keycomponents, kc), ARG(uint, n))) +FUNC(opt_val_string_asciz_const, key_components_nth_str, + (ARG(val_keycomponents, kc), ARG(uint, n))) +FUNC(opt_val_mpint, key_components_nth_mp, + (ARG(val_keycomponents, kc), ARG(uint, n))) + +/* + * The ssh_cipher abstraction. The in-place encrypt and decrypt + * functions are wrapped to replace them with versions that take one + * string and return a separate string. + */ +FUNC(opt_val_cipher, ssh_cipher_new, (ARG(cipheralg, alg))) +FUNC_WRAPPED(void, ssh_cipher_setiv, + (ARG(val_cipher, c), ARG(val_string_ptrlen, iv))) +FUNC_WRAPPED(void, ssh_cipher_setkey, + (ARG(val_cipher, c), ARG(val_string_ptrlen, key))) +FUNC_WRAPPED(val_string, ssh_cipher_encrypt, + (ARG(val_cipher, c), ARG(val_string_ptrlen, blk))) +FUNC_WRAPPED(val_string, ssh_cipher_decrypt, + (ARG(val_cipher, c), ARG(val_string_ptrlen, blk))) +FUNC_WRAPPED(val_string, ssh_cipher_encrypt_length, + (ARG(val_cipher, c), ARG(val_string_ptrlen, blk), ARG(uint, seq))) +FUNC_WRAPPED(val_string, ssh_cipher_decrypt_length, + (ARG(val_cipher, c), ARG(val_string_ptrlen, blk), ARG(uint, seq))) + +/* + * Integer Diffie-Hellman. + */ +FUNC(val_dh, dh_setup_group, (ARG(dh_group, group))) +FUNC(val_dh, dh_setup_gex, (ARG(val_mpint, p), ARG(val_mpint, g))) +FUNC(uint, dh_modulus_bit_size, (ARG(val_dh, ctx))) +FUNC(val_mpint, dh_create_e, (ARG(val_dh, ctx), ARG(uint, nbits))) +FUNC_WRAPPED(boolean, dh_validate_f, (ARG(val_dh, ctx), ARG(val_mpint, f))) +FUNC(val_mpint, dh_find_K, (ARG(val_dh, ctx), ARG(val_mpint, f))) + +/* + * Elliptic-curve Diffie-Hellman. + */ +FUNC(val_ecdh, ssh_ecdhkex_newkey, (ARG(ecdh_alg, alg))) +FUNC(void, ssh_ecdhkex_getpublic, + (ARG(val_ecdh, key), ARG(out_val_string_binarysink, pub))) +FUNC(opt_val_mpint, ssh_ecdhkex_getkey, + (ARG(val_ecdh, key), ARG(val_string_ptrlen, pub))) + +/* + * RSA key exchange, and also the BinarySource get function + * get_ssh1_rsa_priv_agent, which is a convenient way to make an + * RSAKey for RSA kex testing purposes. + */ +FUNC(val_rsakex, ssh_rsakex_newkey, (ARG(val_string_ptrlen, data))) +FUNC(uint, ssh_rsakex_klen, (ARG(val_rsakex, key))) +FUNC(val_string, ssh_rsakex_encrypt, + (ARG(val_rsakex, key), ARG(hashalg, h), ARG(val_string_ptrlen, plaintext))) +FUNC(opt_val_mpint, ssh_rsakex_decrypt, + (ARG(val_rsakex, key), ARG(hashalg, h), + ARG(val_string_ptrlen, ciphertext))) +FUNC(val_rsakex, get_rsa_ssh1_priv_agent, (ARG(val_string_binarysource, src))) + +/* + * Bare RSA keys as used in SSH-1. The construction API functions + * write into an existing RSAKey object, so I've invented an 'rsa_new' + * function to make one in the first place. + */ +FUNC(val_rsa, rsa_new, (VOID)) +FUNC(void, get_rsa_ssh1_pub, + (ARG(val_string_binarysource, src), ARG(val_rsa, key), + ARG(rsaorder, order))) +FUNC(void, get_rsa_ssh1_priv, + (ARG(val_string_binarysource, src), ARG(val_rsa, key))) +FUNC_WRAPPED(opt_val_string, rsa_ssh1_encrypt, + (ARG(val_string_ptrlen, data), ARG(val_rsa, key))) +FUNC(val_mpint, rsa_ssh1_decrypt, (ARG(val_mpint, input), ARG(val_rsa, key))) +FUNC_WRAPPED(val_string, rsa_ssh1_decrypt_pkcs1, + (ARG(val_mpint, input), ARG(val_rsa, key))) +FUNC(val_string_asciz, rsastr_fmt, (ARG(val_rsa, key))) +FUNC(val_string_asciz, rsa_ssh1_fingerprint, (ARG(val_rsa, key))) +FUNC(void, rsa_ssh1_public_blob, + (ARG(out_val_string_binarysink, blob), ARG(val_rsa, key), + ARG(rsaorder, order))) +FUNC(int, rsa_ssh1_public_blob_len, (ARG(val_string_ptrlen, data))) +FUNC(void, rsa_ssh1_private_blob_agent, + (ARG(out_val_string_binarysink, blob), ARG(val_rsa, key))) + +/* + * The PRNG type. Similarly to hashes and MACs, I've invented an extra + * function prng_seed_update for putting seed data into the PRNG's + * exposed BinarySink. + */ +FUNC(val_prng, prng_new, (ARG(hashalg, hashalg))) +FUNC(void, prng_seed_begin, (ARG(val_prng, pr))) +FUNC(void, prng_seed_update, (ARG(val_prng, pr), ARG(val_string_ptrlen, data))) +FUNC(void, prng_seed_finish, (ARG(val_prng, pr))) +FUNC_WRAPPED(val_string, prng_read, (ARG(val_prng, pr), ARG(uint, size))) +FUNC(void, prng_add_entropy, + (ARG(val_prng, pr), ARG(uint, source_id), ARG(val_string_ptrlen, data))) + +/* + * Key load/save functions, or rather, the BinarySource / strbuf API + * that sits just inside the file I/O versions. + */ +FUNC(boolean, ppk_encrypted_s, + (ARG(val_string_binarysource, src), + ARG(out_opt_val_string_asciz, comment))) +FUNC(boolean, rsa1_encrypted_s, + (ARG(val_string_binarysource, src), + ARG(out_opt_val_string_asciz, comment))) +FUNC(boolean, ppk_loadpub_s, + (ARG(val_string_binarysource, src), + ARG(out_opt_val_string_asciz, algorithm), + ARG(out_val_string_binarysink, blob), + ARG(out_opt_val_string_asciz, comment), + ARG(out_opt_val_string_asciz_const, error))) +FUNC(int, rsa1_loadpub_s, + (ARG(val_string_binarysource, src), ARG(out_val_string_binarysink, blob), + ARG(out_opt_val_string_asciz, comment), + ARG(out_opt_val_string_asciz_const, error))) +FUNC_WRAPPED(opt_val_key, ppk_load_s, + (ARG(val_string_binarysource, src), + ARG(out_opt_val_string_asciz, comment), + ARG(opt_val_string_asciz, passphrase), + ARG(out_opt_val_string_asciz_const, error))) +FUNC_WRAPPED(int, rsa1_load_s, + (ARG(val_string_binarysource, src), ARG(val_rsa, key), + ARG(out_opt_val_string_asciz, comment), + ARG(opt_val_string_asciz, passphrase), + ARG(out_opt_val_string_asciz_const, error))) +FUNC_WRAPPED(val_string, ppk_save_sb, + (ARG(val_key, key), ARG(opt_val_string_asciz, comment), + ARG(opt_val_string_asciz, passphrase), ARG(uint, fmt_version), + ARG(argon2flavour, flavour), ARG(uint, mem), ARG(uint, passes), + ARG(uint, parallel))) +FUNC_WRAPPED(val_string, rsa1_save_sb, + (ARG(val_rsa, key), ARG(opt_val_string_asciz, comment), + ARG(opt_val_string_asciz, passphrase))) + +FUNC(val_string_asciz, ssh2_fingerprint_blob, + (ARG(val_string_ptrlen, blob), ARG(fptype, fptype))) + +/* + * Password hashing. + */ +FUNC_WRAPPED(val_string, argon2, + (ARG(argon2flavour, flavour), ARG(uint, mem), ARG(uint, passes), + ARG(uint, parallel), ARG(uint, taglen), ARG(val_string_ptrlen, P), + ARG(val_string_ptrlen, S), ARG(val_string_ptrlen, K), + ARG(val_string_ptrlen, X))) +FUNC(val_string, argon2_long_hash, + (ARG(uint, length), ARG(val_string_ptrlen, data))) + +/* + * Key generation functions. + */ +FUNC_WRAPPED(val_key, rsa_generate, + (ARG(uint, bits), ARG(boolean, strong), ARG(val_pgc, pgc))) +FUNC_WRAPPED(val_key, dsa_generate, (ARG(uint, bits), ARG(val_pgc, pgc))) +FUNC_WRAPPED(opt_val_key, ecdsa_generate, (ARG(uint, bits))) +FUNC_WRAPPED(opt_val_key, eddsa_generate, (ARG(uint, bits))) +FUNC(val_rsa, rsa1_generate, + (ARG(uint, bits), ARG(boolean, strong), ARG(val_pgc, pgc))) +FUNC(val_pgc, primegen_new_context, (ARG(primegenpolicy, policy))) +FUNC_WRAPPED(opt_val_mpint, primegen_generate, + (ARG(val_pgc, ctx), ARG(consumed_val_pcs, pcs))) +FUNC(val_string, primegen_mpu_certificate, + (ARG(val_pgc, ctx), ARG(val_mpint, p))) +FUNC(val_pcs, pcs_new, (ARG(uint, bits))) +FUNC(val_pcs, pcs_new_with_firstbits, + (ARG(uint, bits), ARG(uint, first), ARG(uint, nfirst))) +FUNC(void, pcs_require_residue, + (ARG(val_pcs, s), ARG(val_mpint, mod), ARG(val_mpint, res))) +FUNC(void, pcs_require_residue_1, (ARG(val_pcs, s), ARG(val_mpint, mod))) +FUNC(void, pcs_require_residue_1_mod_prime, + (ARG(val_pcs, s), ARG(val_mpint, mod))) +FUNC(void, pcs_avoid_residue_small, + (ARG(val_pcs, s), ARG(uint, mod), ARG(uint, res))) +FUNC(void, pcs_try_sophie_germain, (ARG(val_pcs, s))) +FUNC(void, pcs_set_oneshot, (ARG(val_pcs, s))) +FUNC(void, pcs_ready, (ARG(val_pcs, s))) +FUNC(void, pcs_inspect, + (ARG(val_pcs, pcs), ARG(out_val_mpint, limit_out), + ARG(out_val_mpint, factor_out), ARG(out_val_mpint, addend_out))) +FUNC(val_mpint, pcs_generate, (ARG(val_pcs, s))) +FUNC(val_pockle, pockle_new, (VOID)) +FUNC(uint, pockle_mark, (ARG(val_pockle, pockle))) +FUNC(void, pockle_release, (ARG(val_pockle, pockle), ARG(uint, mark))) +FUNC(pocklestatus, pockle_add_small_prime, + (ARG(val_pockle, pockle), ARG(val_mpint, p))) +FUNC_WRAPPED(pocklestatus, pockle_add_prime, + (ARG(val_pockle, pockle), ARG(val_mpint, p), + ARG(mpint_list, factors), ARG(val_mpint, witness))) +FUNC(val_string, pockle_mpu, (ARG(val_pockle, pockle), ARG(val_mpint, p))) +FUNC(val_millerrabin, miller_rabin_new, (ARG(val_mpint, p))) +FUNC(mr_result, miller_rabin_test, + (ARG(val_millerrabin, mr), ARG(val_mpint, w))) + +/* + * Miscellaneous. + */ +FUNC(val_wpoint, ecdsa_public, (ARG(val_mpint, private_key), ARG(keyalg, alg))) +FUNC(val_epoint, eddsa_public, (ARG(val_mpint, private_key), ARG(keyalg, alg))) +FUNC_WRAPPED(val_string, des_encrypt_xdmauth, + (ARG(val_string_ptrlen, key), ARG(val_string_ptrlen, blk))) +FUNC_WRAPPED(val_string, des_decrypt_xdmauth, + (ARG(val_string_ptrlen, key), ARG(val_string_ptrlen, blk))) +FUNC_WRAPPED(val_string, des3_encrypt_pubkey, + (ARG(val_string_ptrlen, key), ARG(val_string_ptrlen, blk))) +FUNC_WRAPPED(val_string, des3_decrypt_pubkey, + (ARG(val_string_ptrlen, key), ARG(val_string_ptrlen, blk))) +FUNC_WRAPPED(val_string, des3_encrypt_pubkey_ossh, + (ARG(val_string_ptrlen, key), ARG(val_string_ptrlen, iv), + ARG(val_string_ptrlen, blk))) +FUNC_WRAPPED(val_string, des3_decrypt_pubkey_ossh, + (ARG(val_string_ptrlen, key), ARG(val_string_ptrlen, iv), + ARG(val_string_ptrlen, blk))) +FUNC_WRAPPED(val_string, aes256_encrypt_pubkey, + (ARG(val_string_ptrlen, key), ARG(val_string_ptrlen, iv), + ARG(val_string_ptrlen, blk))) +FUNC_WRAPPED(val_string, aes256_decrypt_pubkey, + (ARG(val_string_ptrlen, key), ARG(val_string_ptrlen, iv), + ARG(val_string_ptrlen, blk))) +FUNC(uint, crc32_rfc1662, (ARG(val_string_ptrlen, data))) +FUNC(uint, crc32_ssh1, (ARG(val_string_ptrlen, data))) +FUNC(uint, crc32_update, (ARG(uint, crc_input), ARG(val_string_ptrlen, data))) +FUNC(boolean, crcda_detect, + (ARG(val_string_ptrlen, packet), ARG(val_string_ptrlen, iv))) +FUNC(val_string, get_implementations_commasep, (ARG(val_string_ptrlen, alg))) +FUNC(void, http_digest_response, + (ARG(out_val_string_binarysink, response), + ARG(val_string_ptrlen, username), ARG(val_string_ptrlen, password), + ARG(val_string_ptrlen, realm), ARG(val_string_ptrlen, method), + ARG(val_string_ptrlen, uri), ARG(val_string_ptrlen, qop), + ARG(val_string_ptrlen, nonce), ARG(val_string_ptrlen, opaque), + ARG(uint, nonce_count), ARG(httpdigesthash, hash), + ARG(boolean, hash_username))) + +/* + * These functions aren't part of PuTTY's own API, but are additions + * by testcrypt itself for administrative purposes. + */ +FUNC(void, random_queue, (ARG(val_string_ptrlen, data))) +FUNC(uint, random_queue_len, (VOID)) +FUNC(void, random_make_prng, + (ARG(hashalg, hashalg), ARG(val_string_ptrlen, seed))) +FUNC(void, random_clear, (VOID)) diff --git a/test/testcrypt.c b/test/testcrypt.c new file mode 100644 index 00000000..e8593431 --- /dev/null +++ b/test/testcrypt.c @@ -0,0 +1,1485 @@ +/* + * testcrypt: a standalone test program that provides direct access to + * PuTTY's cryptography and mp_int code. + */ + +/* + * This program speaks a line-oriented protocol on standard input and + * standard output. It's a half-duplex protocol: it expects to read + * one line of command, and then produce a fixed amount of output + * (namely a line containing a decimal integer, followed by that many + * lines each containing one return value). + * + * The protocol is human-readable enough to make it debuggable, but + * verbose enough that you probably wouldn't want to speak it by hand + * at any great length. The Python program test/testcrypt.py wraps it + * to give a more useful user-facing API, by invoking this binary as a + * subprocess. + * + * (I decided that was a better idea than making this program an + * actual Python module, partly because you can rewrap the same binary + * in another scripting language if you prefer, but mostly because + * it's easy to attach a debugger to testcrypt or to run it under + * sanitisers or valgrind or what have you.) + */ + +#include +#include +#include +#include +#include + +#include "defs.h" +#include "ssh.h" +#include "sshkeygen.h" +#include "misc.h" +#include "mpint.h" +#include "crypto/ecc.h" +#include "proxy/cproxy.h" + +static NORETURN PRINTF_LIKE(1, 2) void fatal_error(const char *p, ...) +{ + va_list ap; + fprintf(stderr, "testcrypt: "); + va_start(ap, p); + vfprintf(stderr, p, ap); + va_end(ap); + fputc('\n', stderr); + exit(1); +} + +void out_of_memory(void) { fatal_error("out of memory"); } + +static bool old_keyfile_warning_given; +void old_keyfile_warning(void) { old_keyfile_warning_given = true; } + +static bufchain random_data_queue; +static prng *test_prng; +void random_read(void *buf, size_t size) +{ + if (test_prng) { + prng_read(test_prng, buf, size); + } else { + if (!bufchain_try_fetch_consume(&random_data_queue, buf, size)) + fatal_error("No random data in queue"); + } +} + +uint64_t prng_reseed_time_ms(void) +{ + static uint64_t previous_time = 0; + return previous_time += 200; +} + +#define VALUE_TYPES(X) \ + X(string, strbuf *, strbuf_free(v)) \ + X(mpint, mp_int *, mp_free(v)) \ + X(modsqrt, ModsqrtContext *, modsqrt_free(v)) \ + X(monty, MontyContext *, monty_free(v)) \ + X(wcurve, WeierstrassCurve *, ecc_weierstrass_curve_free(v)) \ + X(wpoint, WeierstrassPoint *, ecc_weierstrass_point_free(v)) \ + X(mcurve, MontgomeryCurve *, ecc_montgomery_curve_free(v)) \ + X(mpoint, MontgomeryPoint *, ecc_montgomery_point_free(v)) \ + X(ecurve, EdwardsCurve *, ecc_edwards_curve_free(v)) \ + X(epoint, EdwardsPoint *, ecc_edwards_point_free(v)) \ + X(hash, ssh_hash *, ssh_hash_free(v)) \ + X(key, ssh_key *, ssh_key_free(v)) \ + X(cipher, ssh_cipher *, ssh_cipher_free(v)) \ + X(mac, ssh2_mac *, ssh2_mac_free(v)) \ + X(dh, dh_ctx *, dh_cleanup(v)) \ + X(ecdh, ecdh_key *, ssh_ecdhkex_freekey(v)) \ + X(rsakex, RSAKey *, ssh_rsakex_freekey(v)) \ + X(rsa, RSAKey *, rsa_free(v)) \ + X(prng, prng *, prng_free(v)) \ + X(keycomponents, key_components *, key_components_free(v)) \ + X(pcs, PrimeCandidateSource *, pcs_free(v)) \ + X(pgc, PrimeGenerationContext *, primegen_free_context(v)) \ + X(pockle, Pockle *, pockle_free(v)) \ + X(millerrabin, MillerRabin *, miller_rabin_free(v)) \ + /* end of list */ + +typedef struct Value Value; + +enum ValueType { +#define VALTYPE_ENUM(n,t,f) VT_##n, + VALUE_TYPES(VALTYPE_ENUM) +#undef VALTYPE_ENUM +}; + +typedef enum ValueType ValueType; + +static const char *const type_names[] = { +#define VALTYPE_NAME(n,t,f) #n, + VALUE_TYPES(VALTYPE_NAME) +#undef VALTYPE_NAME +}; + +#define VALTYPE_TYPEDEF(n,t,f) \ + typedef t TD_val_##n; \ + typedef t *TD_out_val_##n; +VALUE_TYPES(VALTYPE_TYPEDEF) +#undef VALTYPE_TYPEDEF + +struct Value { + /* + * Protocol identifier assigned to this value when it was created. + * Lives in the same malloced block as this Value object itself. + */ + ptrlen id; + + /* + * Type of the value. + */ + ValueType type; + + /* + * Union of all the things it could hold. + */ + union { +#define VALTYPE_UNION(n,t,f) t vu_##n; + VALUE_TYPES(VALTYPE_UNION) +#undef VALTYPE_UNION + + char *bare_string; + }; +}; + +static int valuecmp(void *av, void *bv) +{ + Value *a = (Value *)av, *b = (Value *)bv; + return ptrlen_strcmp(a->id, b->id); +} + +static int valuefind(void *av, void *bv) +{ + ptrlen *a = (ptrlen *)av; + Value *b = (Value *)bv; + return ptrlen_strcmp(*a, b->id); +} + +static tree234 *values; + +static Value *value_new(ValueType vt) +{ + static uint64_t next_index = 0; + + char *name = dupprintf("%s%"PRIu64, type_names[vt], next_index++); + size_t namelen = strlen(name); + + Value *val = snew_plus(Value, namelen+1); + memcpy(snew_plus_get_aux(val), name, namelen+1); + val->id.ptr = snew_plus_get_aux(val); + val->id.len = namelen; + val->type = vt; + + Value *added = add234(values, val); + assert(added == val); + + sfree(name); + + return val; +} + +#define VALTYPE_RETURNFN(n,t,f) \ + void return_val_##n(strbuf *out, t v) { \ + Value *val = value_new(VT_##n); \ + val->vu_##n = v; \ + put_datapl(out, val->id); \ + put_byte(out, '\n'); \ + } +VALUE_TYPES(VALTYPE_RETURNFN) +#undef VALTYPE_RETURNFN + +static ptrlen get_word(BinarySource *in) +{ + ptrlen toret; + toret.ptr = get_ptr(in); + toret.len = 0; + while (get_avail(in) && get_byte(in) != ' ') + toret.len++; + return toret; +} + +typedef uintmax_t TD_uint; +typedef bool TD_boolean; +typedef ptrlen TD_val_string_ptrlen; +typedef char *TD_val_string_asciz; +typedef BinarySource *TD_val_string_binarysource; +typedef unsigned *TD_out_uint; +typedef BinarySink *TD_out_val_string_binarysink; +typedef const char *TD_opt_val_string_asciz; +typedef char **TD_out_val_string_asciz; +typedef char **TD_out_opt_val_string_asciz; +typedef const char **TD_out_opt_val_string_asciz_const; +typedef ssh_hash *TD_consumed_val_hash; +typedef const ssh_hashalg *TD_hashalg; +typedef const ssh2_macalg *TD_macalg; +typedef const ssh_keyalg *TD_keyalg; +typedef const ssh_cipheralg *TD_cipheralg; +typedef const ssh_kex *TD_dh_group; +typedef const ssh_kex *TD_ecdh_alg; +typedef RsaSsh1Order TD_rsaorder; +typedef key_components *TD_keycomponents; +typedef const PrimeGenerationPolicy *TD_primegenpolicy; +typedef struct mpint_list TD_mpint_list; +typedef PockleStatus TD_pocklestatus; +typedef struct mr_result TD_mr_result; +typedef Argon2Flavour TD_argon2flavour; +typedef FingerprintType TD_fptype; +typedef HttpDigestHash TD_httpdigesthash; + +#define BEGIN_ENUM_TYPE(name) \ + static bool enum_translate_##name(ptrlen valname, TD_##name *out) { \ + static const struct { \ + const char *key; \ + TD_##name value; \ + } mapping[] = { +#define ENUM_VALUE(name, value) {name, value}, +#define END_ENUM_TYPE(name) \ + }; \ + for (size_t i = 0; i < lenof(mapping); i++) \ + if (ptrlen_eq_string(valname, mapping[i].key)) { \ + if (out) \ + *out = mapping[i].value; \ + return true; \ + } \ + return false; \ + } \ + \ + static TD_##name get_##name(BinarySource *in) { \ + ptrlen valname = get_word(in); \ + TD_##name out; \ + if (enum_translate_##name(valname, &out)) \ + return out; \ + else \ + fatal_error("%s '%.*s': not found", \ + #name, PTRLEN_PRINTF(valname)); \ + } +#include "testcrypt-enum.h" +#undef BEGIN_ENUM_TYPE +#undef ENUM_VALUE +#undef END_ENUM_TYPE + +static uintmax_t get_uint(BinarySource *in) +{ + ptrlen word = get_word(in); + char *string = mkstr(word); + uintmax_t toret = strtoumax(string, NULL, 0); + sfree(string); + return toret; +} + +static bool get_boolean(BinarySource *in) +{ + return ptrlen_eq_string(get_word(in), "true"); +} + +static Value *lookup_value(ptrlen word) +{ + Value *val = find234(values, &word, valuefind); + if (!val) + fatal_error("id '%.*s': not found", PTRLEN_PRINTF(word)); + return val; +} + +static Value *get_value(BinarySource *in) +{ + return lookup_value(get_word(in)); +} + +typedef void (*finaliser_fn_t)(strbuf *out, void *ctx); +struct finaliser { + finaliser_fn_t fn; + void *ctx; +}; + +static struct finaliser *finalisers; +static size_t nfinalisers, finalisersize; + +static void add_finaliser(finaliser_fn_t fn, void *ctx) +{ + sgrowarray(finalisers, finalisersize, nfinalisers); + finalisers[nfinalisers].fn = fn; + finalisers[nfinalisers].ctx = ctx; + nfinalisers++; +} + +static void run_finalisers(strbuf *out) +{ + for (size_t i = 0; i < nfinalisers; i++) + finalisers[i].fn(out, finalisers[i].ctx); + nfinalisers = 0; +} + +static void finaliser_return_value(strbuf *out, void *ctx) +{ + Value *val = (Value *)ctx; + put_datapl(out, val->id); + put_byte(out, '\n'); +} + +static void finaliser_sfree(strbuf *out, void *ctx) +{ + sfree(ctx); +} + +#define VALTYPE_GETFN(n,t,f) \ + static Value *unwrap_value_##n(Value *val) { \ + ValueType expected = VT_##n; \ + if (expected != val->type) \ + fatal_error("id '%.*s': expected %s, got %s", \ + PTRLEN_PRINTF(val->id), \ + type_names[expected], type_names[val->type]); \ + return val; \ + } \ + static Value *get_value_##n(BinarySource *in) { \ + return unwrap_value_##n(get_value(in)); \ + } \ + static t get_val_##n(BinarySource *in) { \ + return get_value_##n(in)->vu_##n; \ + } +VALUE_TYPES(VALTYPE_GETFN) +#undef VALTYPE_GETFN + +static ptrlen get_val_string_ptrlen(BinarySource *in) +{ + return ptrlen_from_strbuf(get_val_string(in)); +} + +static char *get_val_string_asciz(BinarySource *in) +{ + return get_val_string(in)->s; +} + +static strbuf *get_opt_val_string(BinarySource *in); + +static char *get_opt_val_string_asciz(BinarySource *in) +{ + strbuf *sb = get_opt_val_string(in); + return sb ? sb->s : NULL; +} + +static mp_int **get_out_val_mpint(BinarySource *in) +{ + Value *val = value_new(VT_mpint); + add_finaliser(finaliser_return_value, val); + return &val->vu_mpint; +} + +struct mpint_list { + size_t n; + mp_int **integers; +}; + +static struct mpint_list get_mpint_list(BinarySource *in) +{ + size_t n = get_uint(in); + + struct mpint_list mpl; + mpl.n = n; + + mpl.integers = snewn(n, mp_int *); + for (size_t i = 0; i < n; i++) + mpl.integers[i] = get_val_mpint(in); + + add_finaliser(finaliser_sfree, mpl.integers); + return mpl; +} + +static void finaliser_return_uint(strbuf *out, void *ctx) +{ + unsigned *uval = (unsigned *)ctx; + put_fmt(out, "%u\n", *uval); + sfree(uval); +} + +static unsigned *get_out_uint(BinarySource *in) +{ + unsigned *uval = snew(unsigned); + add_finaliser(finaliser_return_uint, uval); + return uval; +} + +static BinarySink *get_out_val_string_binarysink(BinarySource *in) +{ + Value *val = value_new(VT_string); + val->vu_string = strbuf_new(); + add_finaliser(finaliser_return_value, val); + return BinarySink_UPCAST(val->vu_string); +} + +static void return_val_string_asciz_const(strbuf *out, const char *s); +static void return_val_string_asciz(strbuf *out, char *s); + +static void finaliser_return_opt_string_asciz(strbuf *out, void *ctx) +{ + char **valp = (char **)ctx; + char *val = *valp; + sfree(valp); + if (!val) + put_fmt(out, "NULL\n"); + else + return_val_string_asciz(out, val); +} + +static char **get_out_opt_val_string_asciz(BinarySource *in) +{ + char **valp = snew(char *); + *valp = NULL; + add_finaliser(finaliser_return_opt_string_asciz, valp); + return valp; +} + +static void finaliser_return_opt_string_asciz_const(strbuf *out, void *ctx) +{ + const char **valp = (const char **)ctx; + const char *val = *valp; + sfree(valp); + if (!val) + put_fmt(out, "NULL\n"); + else + return_val_string_asciz_const(out, val); +} + +static const char **get_out_opt_val_string_asciz_const(BinarySource *in) +{ + const char **valp = snew(const char *); + *valp = NULL; + add_finaliser(finaliser_return_opt_string_asciz_const, valp); + return valp; +} + +static BinarySource *get_val_string_binarysource(BinarySource *in) +{ + strbuf *sb = get_val_string(in); + BinarySource *src = snew(BinarySource); + BinarySource_BARE_INIT(src, sb->u, sb->len); + add_finaliser(finaliser_sfree, src); + return src; +} + +#define GET_CONSUMED_FN(type) \ + typedef TD_val_##type TD_consumed_val_##type; \ + static TD_val_##type get_consumed_val_##type(BinarySource *in) \ + { \ + Value *val = get_value_##type(in); \ + TD_val_##type toret = val->vu_##type; \ + del234(values, val); \ + sfree(val); \ + return toret; \ + } +GET_CONSUMED_FN(hash) +GET_CONSUMED_FN(pcs) + +static void return_int(strbuf *out, intmax_t u) +{ + put_fmt(out, "%"PRIdMAX"\n", u); +} + +static void return_uint(strbuf *out, uintmax_t u) +{ + put_fmt(out, "0x%"PRIXMAX"\n", u); +} + +static void return_boolean(strbuf *out, bool b) +{ + put_fmt(out, "%s\n", b ? "true" : "false"); +} + +static void return_pocklestatus(strbuf *out, PockleStatus status) +{ + switch (status) { + default: + put_fmt(out, "POCKLE_BAD_STATUS_VALUE\n"); + break; + +#define STATUS_CASE(id) \ + case id: \ + put_fmt(out, "%s\n", #id); \ + break; + + POCKLE_STATUSES(STATUS_CASE); + +#undef STATUS_CASE + + } +} + +static void return_mr_result(strbuf *out, struct mr_result result) +{ + if (!result.passed) + put_fmt(out, "failed\n"); + else if (!result.potential_primitive_root) + put_fmt(out, "passed\n"); + else + put_fmt(out, "passed+ppr\n"); +} + +static void return_val_string_asciz_const(strbuf *out, const char *s) +{ + strbuf *sb = strbuf_new(); + put_data(sb, s, strlen(s)); + return_val_string(out, sb); +} + +static void return_val_string_asciz(strbuf *out, char *s) +{ + return_val_string_asciz_const(out, s); + sfree(s); +} + +#define NULLABLE_RETURN_WRAPPER(type_name, c_type) \ + static void return_opt_##type_name(strbuf *out, c_type ptr) \ + { \ + if (!ptr) \ + put_fmt(out, "NULL\n"); \ + else \ + return_##type_name(out, ptr); \ + } + +NULLABLE_RETURN_WRAPPER(val_string, strbuf *) +NULLABLE_RETURN_WRAPPER(val_string_asciz, char *) +NULLABLE_RETURN_WRAPPER(val_string_asciz_const, const char *) +NULLABLE_RETURN_WRAPPER(val_cipher, ssh_cipher *) +NULLABLE_RETURN_WRAPPER(val_hash, ssh_hash *) +NULLABLE_RETURN_WRAPPER(val_key, ssh_key *) +NULLABLE_RETURN_WRAPPER(val_mpint, mp_int *) + +static void handle_hello(BinarySource *in, strbuf *out) +{ + put_fmt(out, "hello, world\n"); +} + +static void rsa_free(RSAKey *rsa) +{ + freersakey(rsa); + sfree(rsa); +} + +static void free_value(Value *val) +{ + switch (val->type) { +#define VALTYPE_FREE(n,t,f) case VT_##n: { t v = val->vu_##n; (f); break; } + VALUE_TYPES(VALTYPE_FREE) +#undef VALTYPE_FREE + } + sfree(val); +} + +static void handle_free(BinarySource *in, strbuf *out) +{ + Value *val = get_value(in); + del234(values, val); + free_value(val); +} + +static void handle_newstring(BinarySource *in, strbuf *out) +{ + strbuf *sb = strbuf_new(); + while (get_avail(in)) { + char c = get_byte(in); + if (c == '%') { + char hex[3]; + hex[0] = get_byte(in); + if (hex[0] != '%') { + hex[1] = get_byte(in); + hex[2] = '\0'; + c = strtoul(hex, NULL, 16); + } + } + put_byte(sb, c); + } + return_val_string(out, sb); +} + +static void handle_getstring(BinarySource *in, strbuf *out) +{ + strbuf *sb = get_val_string(in); + for (size_t i = 0; i < sb->len; i++) { + char c = sb->s[i]; + if (c > ' ' && c < 0x7F && c != '%') { + put_byte(out, c); + } else { + put_fmt(out, "%%%02X", 0xFFU & (unsigned)c); + } + } + put_byte(out, '\n'); +} + +static void handle_mp_literal(BinarySource *in, strbuf *out) +{ + ptrlen pl = get_word(in); + char *str = mkstr(pl); + mp_int *mp = mp__from_string_literal(str); + sfree(str); + return_val_mpint(out, mp); +} + +static void handle_mp_dump(BinarySource *in, strbuf *out) +{ + mp_int *mp = get_val_mpint(in); + for (size_t i = mp_max_bytes(mp); i-- > 0 ;) + put_fmt(out, "%02X", mp_get_byte(mp, i)); + put_byte(out, '\n'); +} + +static void handle_checkenum(BinarySource *in, strbuf *out) +{ + ptrlen type = get_word(in); + ptrlen value = get_word(in); + bool ok = false; + + #define BEGIN_ENUM_TYPE(name) \ + if (ptrlen_eq_string(type, #name)) \ + ok = enum_translate_##name(value, NULL); + #define ENUM_VALUE(name, value) + #define END_ENUM_TYPE(name) + #include "testcrypt-enum.h" + #undef BEGIN_ENUM_TYPE + #undef ENUM_VALUE + #undef END_ENUM_TYPE + + put_dataz(out, ok ? "ok\n" : "bad\n"); +} + +static void random_queue(ptrlen pl) +{ + bufchain_add(&random_data_queue, pl.ptr, pl.len); +} + +static size_t random_queue_len(void) +{ + return bufchain_size(&random_data_queue); +} + +static void random_clear(void) +{ + if (test_prng) { + prng_free(test_prng); + test_prng = NULL; + } + + bufchain_clear(&random_data_queue); +} + +static void random_make_prng(const ssh_hashalg *hashalg, ptrlen seed) +{ + random_clear(); + + test_prng = prng_new(hashalg); + prng_seed_begin(test_prng); + put_datapl(test_prng, seed); + prng_seed_finish(test_prng); +} + +mp_int *monty_identity_wrapper(MontyContext *mc) +{ + return mp_copy(monty_identity(mc)); +} + +mp_int *monty_modulus_wrapper(MontyContext *mc) +{ + return mp_copy(monty_modulus(mc)); +} + +strbuf *ssh_hash_digest_wrapper(ssh_hash *h) +{ + strbuf *sb = strbuf_new(); + void *p = strbuf_append(sb, ssh_hash_alg(h)->hlen); + ssh_hash_digest(h, p); + return sb; +} + +strbuf *ssh_hash_final_wrapper(ssh_hash *h) +{ + strbuf *sb = strbuf_new(); + void *p = strbuf_append(sb, ssh_hash_alg(h)->hlen); + ssh_hash_final(h, p); + return sb; +} + +void ssh_cipher_setiv_wrapper(ssh_cipher *c, ptrlen iv) +{ + if (iv.len != ssh_cipher_alg(c)->blksize) + fatal_error("ssh_cipher_setiv: needs exactly %d bytes", + ssh_cipher_alg(c)->blksize); + ssh_cipher_setiv(c, iv.ptr); +} + +void ssh_cipher_setkey_wrapper(ssh_cipher *c, ptrlen key) +{ + if (key.len != ssh_cipher_alg(c)->padded_keybytes) + fatal_error("ssh_cipher_setkey: needs exactly %d bytes", + ssh_cipher_alg(c)->padded_keybytes); + ssh_cipher_setkey(c, key.ptr); +} + +strbuf *ssh_cipher_encrypt_wrapper(ssh_cipher *c, ptrlen input) +{ + if (input.len % ssh_cipher_alg(c)->blksize) + fatal_error("ssh_cipher_encrypt: needs a multiple of %d bytes", + ssh_cipher_alg(c)->blksize); + strbuf *sb = strbuf_new(); + put_datapl(sb, input); + ssh_cipher_encrypt(c, sb->u, sb->len); + return sb; +} + +strbuf *ssh_cipher_decrypt_wrapper(ssh_cipher *c, ptrlen input) +{ + if (input.len % ssh_cipher_alg(c)->blksize) + fatal_error("ssh_cipher_decrypt: needs a multiple of %d bytes", + ssh_cipher_alg(c)->blksize); + strbuf *sb = strbuf_new(); + put_datapl(sb, input); + ssh_cipher_decrypt(c, sb->u, sb->len); + return sb; +} + +strbuf *ssh_cipher_encrypt_length_wrapper(ssh_cipher *c, ptrlen input, + unsigned long seq) +{ + if (input.len != 4) + fatal_error("ssh_cipher_encrypt_length: needs exactly 4 bytes"); + strbuf *sb = strbuf_new(); + put_datapl(sb, input); + ssh_cipher_encrypt_length(c, sb->u, sb->len, seq); + return sb; +} + +strbuf *ssh_cipher_decrypt_length_wrapper(ssh_cipher *c, ptrlen input, + unsigned long seq) +{ + if (input.len % ssh_cipher_alg(c)->blksize) + fatal_error("ssh_cipher_decrypt_length: needs exactly 4 bytes"); + strbuf *sb = strbuf_new(); + put_datapl(sb, input); + ssh_cipher_decrypt_length(c, sb->u, sb->len, seq); + return sb; +} + +strbuf *ssh2_mac_genresult_wrapper(ssh2_mac *m) +{ + strbuf *sb = strbuf_new(); + void *u = strbuf_append(sb, ssh2_mac_alg(m)->len); + ssh2_mac_genresult(m, u); + return sb; +} + +bool dh_validate_f_wrapper(dh_ctx *dh, mp_int *f) +{ + return dh_validate_f(dh, f) == NULL; +} + +void ssh_hash_update(ssh_hash *h, ptrlen pl) +{ + put_datapl(h, pl); +} + +void ssh2_mac_update(ssh2_mac *m, ptrlen pl) +{ + put_datapl(m, pl); +} + +static RSAKey *rsa_new(void) +{ + RSAKey *rsa = snew(RSAKey); + memset(rsa, 0, sizeof(RSAKey)); + return rsa; +} + +strbuf *rsa_ssh1_encrypt_wrapper(ptrlen input, RSAKey *key) +{ + /* Fold the boolean return value in C into the string return value + * for this purpose, by returning NULL on failure */ + strbuf *sb = strbuf_new(); + put_datapl(sb, input); + put_padding(sb, key->bytes - input.len, 0); + if (!rsa_ssh1_encrypt(sb->u, input.len, key)) { + strbuf_free(sb); + return NULL; + } + return sb; +} + +strbuf *rsa_ssh1_decrypt_pkcs1_wrapper(mp_int *input, RSAKey *key) +{ + /* Again, return "" on failure */ + strbuf *sb = strbuf_new(); + if (!rsa_ssh1_decrypt_pkcs1(input, key, sb)) + strbuf_clear(sb); + return sb; +} + +strbuf *des_encrypt_xdmauth_wrapper(ptrlen key, ptrlen data) +{ + if (key.len != 7) + fatal_error("des_encrypt_xdmauth: key must be 7 bytes long"); + if (data.len % 8 != 0) + fatal_error("des_encrypt_xdmauth: data must be a multiple of 8 bytes"); + strbuf *sb = strbuf_new(); + put_datapl(sb, data); + des_encrypt_xdmauth(key.ptr, sb->u, sb->len); + return sb; +} + +strbuf *des_decrypt_xdmauth_wrapper(ptrlen key, ptrlen data) +{ + if (key.len != 7) + fatal_error("des_decrypt_xdmauth: key must be 7 bytes long"); + if (data.len % 8 != 0) + fatal_error("des_decrypt_xdmauth: data must be a multiple of 8 bytes"); + strbuf *sb = strbuf_new(); + put_datapl(sb, data); + des_decrypt_xdmauth(key.ptr, sb->u, sb->len); + return sb; +} + +strbuf *des3_encrypt_pubkey_wrapper(ptrlen key, ptrlen data) +{ + if (key.len != 16) + fatal_error("des3_encrypt_pubkey: key must be 16 bytes long"); + if (data.len % 8 != 0) + fatal_error("des3_encrypt_pubkey: data must be a multiple of 8 bytes"); + strbuf *sb = strbuf_new(); + put_datapl(sb, data); + des3_encrypt_pubkey(key.ptr, sb->u, sb->len); + return sb; +} + +strbuf *des3_decrypt_pubkey_wrapper(ptrlen key, ptrlen data) +{ + if (key.len != 16) + fatal_error("des3_decrypt_pubkey: key must be 16 bytes long"); + if (data.len % 8 != 0) + fatal_error("des3_decrypt_pubkey: data must be a multiple of 8 bytes"); + strbuf *sb = strbuf_new(); + put_datapl(sb, data); + des3_decrypt_pubkey(key.ptr, sb->u, sb->len); + return sb; +} + +strbuf *des3_encrypt_pubkey_ossh_wrapper(ptrlen key, ptrlen iv, ptrlen data) +{ + if (key.len != 24) + fatal_error("des3_encrypt_pubkey_ossh: key must be 24 bytes long"); + if (iv.len != 8) + fatal_error("des3_encrypt_pubkey_ossh: iv must be 8 bytes long"); + if (data.len % 8 != 0) + fatal_error("des3_encrypt_pubkey_ossh: data must be a multiple of 8 bytes"); + strbuf *sb = strbuf_new(); + put_datapl(sb, data); + des3_encrypt_pubkey_ossh(key.ptr, iv.ptr, sb->u, sb->len); + return sb; +} + +strbuf *des3_decrypt_pubkey_ossh_wrapper(ptrlen key, ptrlen iv, ptrlen data) +{ + if (key.len != 24) + fatal_error("des3_decrypt_pubkey_ossh: key must be 24 bytes long"); + if (iv.len != 8) + fatal_error("des3_encrypt_pubkey_ossh: iv must be 8 bytes long"); + if (data.len % 8 != 0) + fatal_error("des3_decrypt_pubkey_ossh: data must be a multiple of 8 bytes"); + strbuf *sb = strbuf_new(); + put_datapl(sb, data); + des3_decrypt_pubkey_ossh(key.ptr, iv.ptr, sb->u, sb->len); + return sb; +} + +strbuf *aes256_encrypt_pubkey_wrapper(ptrlen key, ptrlen iv, ptrlen data) +{ + if (key.len != 32) + fatal_error("aes256_encrypt_pubkey: key must be 32 bytes long"); + if (iv.len != 16) + fatal_error("aes256_encrypt_pubkey: iv must be 16 bytes long"); + if (data.len % 16 != 0) + fatal_error("aes256_encrypt_pubkey: data must be a multiple of 16 bytes"); + strbuf *sb = strbuf_new(); + put_datapl(sb, data); + aes256_encrypt_pubkey(key.ptr, iv.ptr, sb->u, sb->len); + return sb; +} + +strbuf *aes256_decrypt_pubkey_wrapper(ptrlen key, ptrlen iv, ptrlen data) +{ + if (key.len != 32) + fatal_error("aes256_decrypt_pubkey: key must be 32 bytes long"); + if (iv.len != 16) + fatal_error("aes256_encrypt_pubkey: iv must be 16 bytes long"); + if (data.len % 16 != 0) + fatal_error("aes256_decrypt_pubkey: data must be a multiple of 16 bytes"); + strbuf *sb = strbuf_new(); + put_datapl(sb, data); + aes256_decrypt_pubkey(key.ptr, iv.ptr, sb->u, sb->len); + return sb; +} + +strbuf *prng_read_wrapper(prng *pr, size_t size) +{ + strbuf *sb = strbuf_new(); + prng_read(pr, strbuf_append(sb, size), size); + return sb; +} + +void prng_seed_update(prng *pr, ptrlen data) +{ + put_datapl(pr, data); +} + +bool crcda_detect(ptrlen packet, ptrlen iv) +{ + if (iv.len != 0 && iv.len != 8) + fatal_error("crcda_detect: iv must be empty or 8 bytes long"); + if (packet.len % 8 != 0) + fatal_error("crcda_detect: packet must be a multiple of 8 bytes"); + struct crcda_ctx *ctx = crcda_make_context(); + bool toret = detect_attack(ctx, packet.ptr, packet.len, + iv.len ? iv.ptr : NULL); + crcda_free_context(ctx); + return toret; +} + +ssh_key *ppk_load_s_wrapper(BinarySource *src, char **comment, + const char *passphrase, const char **errorstr) +{ + ssh2_userkey *uk = ppk_load_s(src, passphrase, errorstr); + if (uk == SSH2_WRONG_PASSPHRASE) { + /* Fudge this special return value */ + *errorstr = "SSH2_WRONG_PASSPHRASE"; + return NULL; + } + if (uk == NULL) + return NULL; + ssh_key *toret = uk->key; + *comment = uk->comment; + sfree(uk); + return toret; +} + +int rsa1_load_s_wrapper(BinarySource *src, RSAKey *rsa, char **comment, + const char *passphrase, const char **errorstr) +{ + int toret = rsa1_load_s(src, rsa, passphrase, errorstr); + *comment = rsa->comment; + rsa->comment = NULL; + return toret; +} + +strbuf *ppk_save_sb_wrapper( + ssh_key *key, const char *comment, const char *passphrase, + unsigned fmt_version, Argon2Flavour flavour, + uint32_t mem, uint32_t passes, uint32_t parallel) +{ + /* + * For repeatable testing purposes, we never want a timing-dependent + * choice of password hashing parameters, so this is easy. + */ + ppk_save_parameters save_params; + memset(&save_params, 0, sizeof(save_params)); + save_params.fmt_version = fmt_version; + save_params.argon2_flavour = flavour; + save_params.argon2_mem = mem; + save_params.argon2_passes_auto = false; + save_params.argon2_passes = passes; + save_params.argon2_parallelism = parallel; + + ssh2_userkey uk; + uk.key = key; + uk.comment = dupstr(comment); + strbuf *toret = ppk_save_sb(&uk, passphrase, &save_params); + sfree(uk.comment); + return toret; +} + +strbuf *rsa1_save_sb_wrapper(RSAKey *key, const char *comment, + const char *passphrase) +{ + key->comment = dupstr(comment); + strbuf *toret = rsa1_save_sb(key, passphrase); + sfree(key->comment); + key->comment = NULL; + return toret; +} + +#define return_void(out, expression) (expression) + +static ProgressReceiver null_progress = { .vt = &null_progress_vt }; + +mp_int *primegen_generate_wrapper( + PrimeGenerationContext *ctx, PrimeCandidateSource *pcs) +{ + return primegen_generate(ctx, pcs, &null_progress); +} + +RSAKey *rsa1_generate(int bits, bool strong, PrimeGenerationContext *pgc) +{ + RSAKey *rsakey = snew(RSAKey); + rsa_generate(rsakey, bits, strong, pgc, &null_progress); + rsakey->comment = NULL; + return rsakey; +} + +ssh_key *rsa_generate_wrapper(int bits, bool strong, + PrimeGenerationContext *pgc) +{ + return &rsa1_generate(bits, strong, pgc)->sshk; +} + +ssh_key *dsa_generate_wrapper(int bits, PrimeGenerationContext *pgc) +{ + struct dsa_key *dsakey = snew(struct dsa_key); + dsa_generate(dsakey, bits, pgc, &null_progress); + return &dsakey->sshk; +} + +ssh_key *ecdsa_generate_wrapper(int bits) +{ + struct ecdsa_key *ek = snew(struct ecdsa_key); + if (!ecdsa_generate(ek, bits)) { + sfree(ek); + return NULL; + } + return &ek->sshk; +} + +ssh_key *eddsa_generate_wrapper(int bits) +{ + struct eddsa_key *ek = snew(struct eddsa_key); + if (!eddsa_generate(ek, bits)) { + sfree(ek); + return NULL; + } + return &ek->sshk; +} + +size_t key_components_count(key_components *kc) { return kc->ncomponents; } +const char *key_components_nth_name(key_components *kc, size_t n) +{ + return (n >= kc->ncomponents ? NULL : + kc->components[n].name); +} +const char *key_components_nth_str(key_components *kc, size_t n) +{ + return (n >= kc->ncomponents ? NULL : + kc->components[n].is_mp_int ? NULL : + kc->components[n].text); +} +mp_int *key_components_nth_mp(key_components *kc, size_t n) +{ + return (n >= kc->ncomponents ? NULL : + !kc->components[n].is_mp_int ? NULL : + mp_copy(kc->components[n].mp)); +} + +PockleStatus pockle_add_prime_wrapper(Pockle *pockle, mp_int *p, + struct mpint_list mpl, mp_int *witness) +{ + return pockle_add_prime(pockle, p, mpl.integers, mpl.n, witness); +} + +strbuf *argon2_wrapper(Argon2Flavour flavour, uint32_t mem, uint32_t passes, + uint32_t parallel, uint32_t taglen, + ptrlen P, ptrlen S, ptrlen K, ptrlen X) +{ + strbuf *out = strbuf_new(); + argon2(flavour, mem, passes, parallel, taglen, P, S, K, X, out); + return out; +} + +strbuf *get_implementations_commasep(ptrlen alg) +{ + strbuf *out = strbuf_new(); + put_datapl(out, alg); + + if (ptrlen_startswith(alg, PTRLEN_LITERAL("aes"), NULL)) { + put_fmt(out, ",%.*s_sw", PTRLEN_PRINTF(alg)); +#if HAVE_AES_NI + put_fmt(out, ",%.*s_ni", PTRLEN_PRINTF(alg)); +#endif +#if HAVE_NEON_CRYPTO + put_fmt(out, ",%.*s_neon", PTRLEN_PRINTF(alg)); +#endif + } else if (ptrlen_startswith(alg, PTRLEN_LITERAL("sha256"), NULL) || + ptrlen_startswith(alg, PTRLEN_LITERAL("sha1"), NULL)) { + put_fmt(out, ",%.*s_sw", PTRLEN_PRINTF(alg)); +#if HAVE_SHA_NI + put_fmt(out, ",%.*s_ni", PTRLEN_PRINTF(alg)); +#endif +#if HAVE_NEON_CRYPTO + put_fmt(out, ",%.*s_neon", PTRLEN_PRINTF(alg)); +#endif + } else if (ptrlen_startswith(alg, PTRLEN_LITERAL("sha512"), NULL)) { + put_fmt(out, ",%.*s_sw", PTRLEN_PRINTF(alg)); +#if HAVE_NEON_SHA512 + put_fmt(out, ",%.*s_neon", PTRLEN_PRINTF(alg)); +#endif + } + + return out; +} + +#define OPTIONAL_PTR_FUNC(type) \ + typedef TD_val_##type TD_opt_val_##type; \ + static TD_opt_val_##type get_opt_val_##type(BinarySource *in) { \ + ptrlen word = get_word(in); \ + if (ptrlen_eq_string(word, "NULL")) \ + return NULL; \ + return unwrap_value_##type(lookup_value(word))->vu_##type; \ + } +OPTIONAL_PTR_FUNC(cipher) +OPTIONAL_PTR_FUNC(mpint) +OPTIONAL_PTR_FUNC(string) + +/* + * HERE BE DRAGONS: the horrible C preprocessor business that reads + * testcrypt-func.h and generates a marshalling wrapper for each + * exported function. + * + * In an ideal world, we would start from a specification like this in + * testcrypt-func.h + * + * FUNC(val_foo, example, (ARG(val_bar, bar), ARG(uint, n))) + * + * and generate a wrapper function looking like this: + * + * static void handle_example(BinarySource *in, strbuf *out) { + * TD_val_bar bar = get_val_bar(in); + * TD_uint n = get_uint(in); + * return_val_foo(out, example(bar, n)); + * } + * + * which would read the marshalled form of each function argument in + * turn from the input BinarySource via the get_() function + * family defined in this file; assign each argument to a local + * variable; call the underlying C function with all those arguments; + * and then call a function of the return_() family to marshal + * the output value into the output strbuf to be sent to standard + * output. + * + * With a more general macro processor such as m4, or custom code in + * Perl or Python, or a helper program like llvm-tblgen, we could just + * do that directly, reading function specifications from + * testcrypt-func.h and writing out exactly the above. But we don't + * have a fully general macro processor (since everything in that + * category introduces an extra build dependency that's awkward on + * plain Windows, or requires compiling and running a helper program + * which is awkward in a cross-compile). We only have cpp. And in cpp, + * a macro can't expand one of its arguments differently in two parts + * of its own expansion. So we have to be more clever. + * + * In place of the above code, I instead generate three successive + * declarations for each function. In simplified form they would look + * like this: + * + * typedef struct ARGS_example { + * TD_val_bar bar; + * TD_uint n; + * } ARGS_example; + * + * static inline ARGS_example get_args_example(BinarySource *in) { + * ARGS_example args; + * args.bar = get_val_bar(in); + * args.n = get_uint(in); + * return args; + * } + * + * static void handle_example(BinarySource *in, strbuf *out) { + * ARGS_example args = get_args_example(in); + * return_val_foo(out, example(args.bar, args.n)); + * } + * + * Each of these mentions the arguments and their types just _once_, + * so each one can be generated by a single expansion of the FUNC(...) + * specification in testcrypt-func.h, with FUNC and ARG and VOID + * defined to appropriate values. + * + * Or ... *nearly*. In fact, I left out several details there, but + * it's a good starting point to understand the full version. + * + * To begin with, several of the variable names shown above are + * actually named with an ugly leading underscore, to minimise the + * chance of them colliding with real parameter names. (You could + * easily imagine 'out' being the name of a parameter to one of the + * wrapped functions.) Also, we memset the whole structure to zero at + * the start of get_args_example() to avoid compiler warnings about + * uninitialised stuff, and insert a precautionary '(void)args;' in + * handle_example to avoid a similar warning about _unused_ stuff. + * + * The big problem is the commas that have to appear between arguments + * in the final call to the actual C function. Those can't be + * generated by expanding the ARG macro itself, or you'd get one too + * many - either a leading comma or a trailing comma. Trailing commas + * are legal in a Python function call, but unfortunately C is not yet + * so enlightened. (C permits a trailing comma in a struct or array + * initialiser, and is coming round to it in enums, but hasn't yet + * seen the light about function calls or function prototypes.) + * + * So the commas must appear _between_ ARG(...) specifiers. And that + * means they unavoidably appear in _every_ expansion of FUNC() (or + * rather, every expansion that uses the argument list at all). + * Therefore, we need to ensure they're harmless in the other two + * functions as well. + * + * In the get_args_example() function above, there's no real problem. + * The list of assignments can perfectly well be separated by commas + * instead of semicolons, so that it becomes a single expression- + * statement instead of a sequence of them; the comma operator still + * defines a sequence point, so it's fine. + * + * But what about the structure definition of ARGS_example? + * + * To get round that, we fill the structure with pointless extra + * cruft, in the form of an extra 'int' field before and after each + * actually useful argument field. So the real structure definition + * ends up looking more like this: + * + * typedef struct ARGS_example { + * int _predummy_bar; + * TD_val_bar bar; + * int _postdummy_bar, _predummy_n; + * TD_uint n; + * int _postdummy_n; + * } ARGS_example; + * + * Those extra 'int' fields are ignored completely at run time. They + * might cause a runtime space cost if the struct doesn't get + * completely optimised away when get_args_example is inlined into + * handle_example, but even if so, that's OK, this is a test program + * whose memory usage isn't critical. The real point is that, in + * between each pair of real arguments, there's a declaration + * containing *two* int variables, and in between them is the vital + * comma that we need! + * + * So in that pass through testcrypt-func.h, the ARG(type, name) macro + * has to expand to the weird piece of text + * + * _predummy_name; // terminating the previous int declaration + * TD_type name; // declaring the thing we actually wanted + * int _postdummy_name // new declaration ready to see a comma + * + * so that a comma-separated list of pieces of expansion like that + * will fall into just the right form to be the core of the above + * expanded structure definition. Then we just need to put in the + * 'int' after the open brace, and the ';' before the closing brace, + * and we've got everything we need to make it all syntactically legal. + * + * Other points of note: + * + * Why the extra pair of parens around the whole argument list? You'd + * like to think that FUNC could be a variadic macro, and just use + * __VA_ARGS__ to expand all the arguments wherever they're needed. + * But unfortunately there's a portability consideration: some of the + * 'functions' wrapped by this system are actually macros in turn, and + * if you use __VA_ARGS__ to expand multiple arguments from one macro + * into the argument list of another macro, compilers disagree on what + * happens: Visual Studio in particular will turn __VA_ARGS__ into + * just one argument instead of multiple ones. That is, if you do this: + * + * #define DESTINATION_MACRO(a, b) ... stuff using a and b ... + * #define WRAPPER(...) DESTINATION_MACRO(__VA_ARGS__) + * WRAPPER(1, 2) + * + * then most compilers will behave as if you'd called + * DESTINATION_MACRO with 'a' expanding to 1 and 'b' expanding to 2. + * But Visual Studio will consider that you called it with 'a' + * expanding to the whole of __VA_ARGS__ - that is, the token sequence + * '1 , 2' - and will expand 'b' to nothing at all! + * + * So we have a constraint that if ARGS is going to be turned into the + * argument list to the actual called function - as it is in the final + * handle_example() expansion shown above - then the commas can't come + * from a variadic expansion of __VA_ARGS__. Hence, FUNC can't _be_ a + * variadic macro. Instead, we wrap all the arguments in an extra pair + * of parens, and generate the final call not by saying function(args) + * but by saying just 'function args', since 'args' contains the + * parens already. + * + * In get_args_example(), that's still fine, because our giant + * comma-separated expression containing multiple assignment + * subexpressions can legally be wrapped in parentheses as well. But + * what do you do in the structure definition? + * + * Answer: _there_ we use a variadic macro to strip off the outer + * parens, by expanding to just __VA_ARGS__. That's OK even in Visual + * Studio, because in this particular context, __VA_ARGS__ is ending + * up in a structure definition and definitely _not_ in the argument + * list of another macro. + * + * Finally, what if a wrapped function has _no_ arguments? Two out of + * three uses of the argument list here need some kind of special case + * for that. That's why you have to write 'VOID' explicitly in an + * empty argument list in testcrypt-func.h: we make VOID expand to + * whatever is needed to avoid a syntax error in that special case. + */ + +#define DEPARENTHESISE(...) __VA_ARGS__ + +#define FUNC(outtype, fname, args) \ + FUNC_INNER(outtype, fname, fname, args) +#define FUNC_WRAPPED(outtype, fname, args) \ + FUNC_INNER(outtype, fname, fname##_wrapper, args) + +#define ARG(type, arg) _predummy_##arg; TD_##type arg; int _postdummy_##arg +#define VOID _voiddummy +#define FUNC_INNER(outtype, fname, realname, args) \ + typedef struct ARGS_##fname { \ + int DEPARENTHESISE args; \ + } ARGS_##fname; +#include "testcrypt-func.h" +#undef FUNC_INNER +#undef ARG +#undef VOID + +#define ARG(type, arg) _args.arg = get_##type(_in) +#define VOID ((void)0) +#define FUNC_INNER(outtype, fname, realname, args) \ + static inline ARGS_##fname get_args_##fname(BinarySource *_in) { \ + ARGS_##fname _args; \ + memset(&_args, 0, sizeof(_args)); \ + args; \ + return _args; \ + } +#include "testcrypt-func.h" +#undef FUNC_INNER +#undef ARG +#undef VOID + +#define ARG(type, arg) _args.arg +#define VOID +#define FUNC_INNER(outtype, fname, realname, args) \ + static void handle_##fname(BinarySource *_in, strbuf *_out) { \ + ARGS_##fname _args = get_args_##fname(_in); \ + (void)_args; /* suppress warning if no actual arguments */ \ + return_##outtype(_out, realname args); \ + } +#include "testcrypt-func.h" +#undef FUNC_INNER +#undef ARG + +static void process_line(BinarySource *in, strbuf *out) +{ + ptrlen id = get_word(in); + +#define DISPATCH_INTERNAL(cmdname, handler) do { \ + if (ptrlen_eq_string(id, cmdname)) { \ + handler(in, out); \ + return; \ + } \ + } while (0) + +#define DISPATCH_COMMAND(cmd) DISPATCH_INTERNAL(#cmd, handle_##cmd) + DISPATCH_COMMAND(hello); + DISPATCH_COMMAND(free); + DISPATCH_COMMAND(newstring); + DISPATCH_COMMAND(getstring); + DISPATCH_COMMAND(mp_literal); + DISPATCH_COMMAND(mp_dump); + DISPATCH_COMMAND(checkenum); +#undef DISPATCH_COMMAND + +#define FUNC_INNER(outtype, fname, realname, args) \ + DISPATCH_INTERNAL(#fname,handle_##fname); +#define ARG1(type, arg) +#define ARGN(type, arg) +#define VOID +#include "testcrypt-func.h" +#undef FUNC_INNER +#undef ARG +#undef VOID + +#undef DISPATCH_INTERNAL + + fatal_error("command '%.*s': unrecognised", PTRLEN_PRINTF(id)); +} + +static void free_all_values(void) +{ + for (Value *val; (val = delpos234(values, 0)) != NULL ;) + free_value(val); + freetree234(values); +} + +void dputs(const char *buf) +{ + fputs(buf, stderr); +} + +int main(int argc, char **argv) +{ + const char *infile = NULL, *outfile = NULL; + bool doing_opts = true; + + while (--argc > 0) { + char *p = *++argv; + + if (p[0] == '-' && doing_opts) { + if (!strcmp(p, "-o")) { + if (--argc <= 0) { + fprintf(stderr, "'-o' expects a filename\n"); + return 1; + } + outfile = *++argv; + } else if (!strcmp(p, "--")) { + doing_opts = false; + } else if (!strcmp(p, "--help")) { + printf("usage: testcrypt [INFILE] [-o OUTFILE]\n"); + printf(" also: testcrypt --help display this text\n"); + return 0; + } else { + fprintf(stderr, "unknown command line option '%s'\n", p); + return 1; + } + } else if (!infile) { + infile = p; + } else { + fprintf(stderr, "can only handle one input file name\n"); + return 1; + } + } + + FILE *infp = stdin; + if (infile) { + infp = fopen(infile, "r"); + if (!infp) { + fprintf(stderr, "%s: open: %s\n", infile, strerror(errno)); + return 1; + } + } + + FILE *outfp = stdout; + if (outfile) { + outfp = fopen(outfile, "w"); + if (!outfp) { + fprintf(stderr, "%s: open: %s\n", outfile, strerror(errno)); + return 1; + } + } + + values = newtree234(valuecmp); + + atexit(free_all_values); + + for (char *line; (line = chomp(fgetline(infp))) != NULL ;) { + BinarySource src[1]; + BinarySource_BARE_INIT(src, line, strlen(line)); + strbuf *sb = strbuf_new(); + process_line(src, sb); + run_finalisers(sb); + size_t lines = 0; + for (size_t i = 0; i < sb->len; i++) + if (sb->s[i] == '\n') + lines++; + fprintf(outfp, "%"SIZEu"\n%s", lines, sb->s); + fflush(outfp); + strbuf_free(sb); + sfree(line); + } + + if (infp != stdin) + fclose(infp); + if (outfp != stdin) + fclose(outfp); + + return 0; +} diff --git a/test/testcrypt.py b/test/testcrypt.py index b71f851d..18a741d5 100644 --- a/test/testcrypt.py +++ b/test/testcrypt.py @@ -313,7 +313,7 @@ def _lex_testcrypt_header(header): while pos < end: m = pat.match(header, pos) assert m is not None, ( - "Failed to lex testcrypt.h at byte position {:d}".format(pos)) + "Failed to lex testcrypt-func.h at byte position {:d}".format(pos)) pos = m.end() tok = m.group(1) @@ -339,7 +339,7 @@ def _parse_testcrypt_header(tokens): description = lambda: "'"+what+"' " ok = tok == what if not ok: - sys.exit("testcrypt.h:{:d}: expected {}{}".format( + sys.exit("testcrypt-func.h:{:d}: expected {}{}".format( pos, description(), why)) return tok @@ -392,7 +392,7 @@ def _setup(scope): arg = arg[:arg.index("_", len(valprefix))] return arg - with open(os.path.join(putty_srcdir, "testcrypt.h")) as f: + with open(os.path.join(putty_srcdir, "test", "testcrypt-func.h")) as f: header = f.read() tokens = _lex_testcrypt_header(header) for function, rettype, arglist in _parse_testcrypt_header(tokens): diff --git a/test/testsc.c b/test/testsc.c new file mode 100644 index 00000000..c9029662 --- /dev/null +++ b/test/testsc.c @@ -0,0 +1,1753 @@ +/* + * testsc: run PuTTY's crypto primitives under instrumentation that + * checks for cache and timing side channels. + * + * The idea is: cryptographic code should avoid leaking secret data + * through timing information, or through traces of its activity left + * in the caches. + * + * (This property is sometimes called 'constant-time', although really + * that's a misnomer. It would be impossible to avoid the execution + * time varying for any number of reasons outside the code's control, + * such as the prior contents of caches and branch predictors, + * temperature-based CPU throttling, system load, etc. And in any case + * you don't _need_ the execution time to be literally constant: you + * just need it to be independent of your secrets. It can vary as much + * as it likes based on anything else.) + * + * To avoid this, you need to ensure that various aspects of the + * code's behaviour do not depend on the secret data. The control + * flow, for a start - no conditional branches based on secrets - and + * also the memory access pattern (no using secret data as an index + * into a lookup table). A couple of other kinds of CPU instruction + * also can't be trusted to run in constant time: we check for + * register-controlled shifts and hardware divisions. (But, again, + * it's perfectly fine to _use_ those instructions in the course of + * crypto code. You just can't use a secret as any time-affecting + * operand.) + * + * This test program works by running the same crypto primitive + * multiple times, with different secret input data. The relevant + * details of each run is logged to a file via the DynamoRIO-based + * instrumentation system living in the subdirectory test/sclog. Then + * we check over all the files and ensure they're identical. + * + * This program itself (testsc) is built by the ordinary PuTTY + * makefiles. But run by itself, it will do nothing useful: it needs + * to be run under DynamoRIO, with the sclog instrumentation library. + * + * Here's an example of how I built it: + * + * Download the DynamoRIO source. I did this by cloning + * https://github.com/DynamoRIO/dynamorio.git, and at the time of + * writing this, 259c182a75ce80112bcad329c97ada8d56ba854d was the head + * commit. + * + * In the DynamoRIO checkout: + * + * mkdir build + * cd build + * cmake -G Ninja .. + * ninja + * + * Now set the shell variable DRBUILD to be the location of the build + * directory you did that in. (Or not, if you prefer, but the example + * build commands below will assume that that's where the DynamoRIO + * libraries, headers and runtime can be found.) + * + * Then, in test/sclog: + * + * cmake -G Ninja -DCMAKE_PREFIX_PATH=$DRBUILD/cmake . + * ninja + * + * Finally, to run the actual test, set SCTMP to some temp directory + * you don't mind filling with large temp files (several GB at a + * time), and in the main PuTTY source directory (assuming that's + * where testsc has been built): + * + * $DRBUILD/bin64/drrun -c test/sclog/libsclog.so -- ./testsc -O $SCTMP + */ + +#include +#include +#include +#include +#include + +#include "defs.h" +#include "putty.h" +#include "ssh.h" +#include "sshkeygen.h" +#include "misc.h" +#include "mpint.h" +#include "crypto/ecc.h" + +static NORETURN PRINTF_LIKE(1, 2) void fatal_error(const char *p, ...) +{ + va_list ap; + fprintf(stderr, "testsc: "); + va_start(ap, p); + vfprintf(stderr, p, ap); + va_end(ap); + fputc('\n', stderr); + exit(1); +} + +void out_of_memory(void) { fatal_error("out of memory"); } + +/* + * A simple deterministic PRNG, without any of the Fortuna + * complexities, for generating test inputs in a way that's repeatable + * between runs of the program, even if only a subset of test cases is + * run. + */ +static uint64_t random_counter = 0; +static const char *random_seedstr = NULL; +static uint8_t random_buf[MAX_HASH_LEN]; +static size_t random_buf_limit = 0; +static ssh_hash *random_hash; + +static void random_seed(const char *seedstr) +{ + random_seedstr = seedstr; + random_counter = 0; + random_buf_limit = 0; +} + +void random_read(void *vbuf, size_t size) +{ + assert(random_seedstr); + uint8_t *buf = (uint8_t *)vbuf; + while (size-- > 0) { + if (random_buf_limit == 0) { + ssh_hash_reset(random_hash); + put_asciz(random_hash, random_seedstr); + put_uint64(random_hash, random_counter); + random_counter++; + random_buf_limit = ssh_hash_alg(random_hash)->hlen; + ssh_hash_digest(random_hash, random_buf); + } + *buf++ = random_buf[random_buf_limit--]; + } +} + +struct random_state { + const char *seedstr; + uint64_t counter; + size_t limit; + uint8_t buf[MAX_HASH_LEN]; +}; + +static struct random_state random_get_state(void) +{ + struct random_state st; + st.seedstr = random_seedstr; + st.counter = random_counter; + st.limit = random_buf_limit; + memcpy(st.buf, random_buf, sizeof(st.buf)); + return st; +} + +static void random_set_state(struct random_state st) +{ + random_seedstr = st.seedstr; + random_counter = st.counter; + random_buf_limit = st.limit; + memcpy(random_buf, st.buf, sizeof(random_buf)); +} + +/* + * Macro that defines a function, and also a volatile function pointer + * pointing to it. Callers indirect through the function pointer + * instead of directly calling the function, to ensure that the + * compiler doesn't try to get clever by eliminating the call + * completely, or inlining it. + * + * This is used to mark functions that DynamoRIO will look for to + * intercept, and also to inhibit inlining and unrolling where they'd + * cause a failure of experimental control in the main test. + */ +#define VOLATILE_WRAPPED_DEFN(qualifier, rettype, fn, params) \ + qualifier rettype fn##_real params; \ + qualifier rettype (*volatile fn) params = fn##_real; \ + qualifier rettype fn##_real params + +VOLATILE_WRAPPED_DEFN(, void, log_to_file, (const char *filename)) +{ + /* + * This function is intercepted by the DynamoRIO side of the + * mechanism. We use it to send instructions to the DR wrapper, + * namely, 'please start logging to this file' or 'please stop + * logging' (if filename == NULL). But we don't have to actually + * do anything in _this_ program - all the functionality is in the + * DR wrapper. + */ +} + +static const char *outdir = NULL; +char *log_filename(const char *basename, size_t index) +{ + return dupprintf("%s/%s.%04"SIZEu, outdir, basename, index); +} + +static char *last_filename; +static const char *test_basename; +static size_t test_index = 0; +void log_start(void) +{ + last_filename = log_filename(test_basename, test_index++); + log_to_file(last_filename); +} +void log_end(void) +{ + log_to_file(NULL); + sfree(last_filename); +} + +static bool test_skipped = false; + +VOLATILE_WRAPPED_DEFN(, intptr_t, dry_run, (void)) +{ + /* + * This is another function intercepted by DynamoRIO. In this + * case, DR overrides this function to return 0 rather than 1, so + * we can use it as a check for whether we're running under + * instrumentation, or whether this is just a dry run which goes + * through the motions but doesn't expect to find any log files + * created. + */ + return 1; +} + +static void mp_random_bits_into(mp_int *r, size_t bits) +{ + mp_int *x = mp_random_bits(bits); + mp_copy_into(r, x); + mp_free(x); +} + +static void mp_random_fill(mp_int *r) +{ + mp_random_bits_into(r, mp_max_bits(r)); +} + +VOLATILE_WRAPPED_DEFN(static, size_t, looplimit, (size_t x)) +{ + /* + * looplimit() is the identity function on size_t, but the + * compiler isn't allowed to rely on it being that. I use it to + * make loops in the test functions look less attractive to + * compilers' unrolling heuristics. + */ + return x; +} + +#if HAVE_AES_NI +#define CIPHERS_AES_NI(X, Y) \ + X(Y, ssh_aes256_sdctr_ni) \ + X(Y, ssh_aes256_cbc_ni) \ + X(Y, ssh_aes192_sdctr_ni) \ + X(Y, ssh_aes192_cbc_ni) \ + X(Y, ssh_aes128_sdctr_ni) \ + X(Y, ssh_aes128_cbc_ni) \ + /* end of list */ +#else +#define CIPHERS_AES_NI(X, Y) +#endif +#if HAVE_NEON_CRYPTO +#define CIPHERS_AES_NEON(X, Y) \ + X(Y, ssh_aes256_sdctr_neon) \ + X(Y, ssh_aes256_cbc_neon) \ + X(Y, ssh_aes192_sdctr_neon) \ + X(Y, ssh_aes192_cbc_neon) \ + X(Y, ssh_aes128_sdctr_neon) \ + X(Y, ssh_aes128_cbc_neon) \ + /* end of list */ +#else +#define CIPHERS_AES_NEON(X, Y) +#endif + +/* Ciphers that we expect to pass this test. Blowfish and Arcfour are + * intentionally omitted, because we already know they don't. */ +#define CIPHERS(X, Y) \ + X(Y, ssh_3des_ssh1) \ + X(Y, ssh_3des_ssh2_ctr) \ + X(Y, ssh_3des_ssh2) \ + X(Y, ssh_des) \ + X(Y, ssh_des_sshcom_ssh2) \ + X(Y, ssh_aes256_sdctr) \ + X(Y, ssh_aes256_cbc) \ + X(Y, ssh_aes192_sdctr) \ + X(Y, ssh_aes192_cbc) \ + X(Y, ssh_aes128_sdctr) \ + X(Y, ssh_aes128_cbc) \ + X(Y, ssh_aes256_sdctr_sw) \ + X(Y, ssh_aes256_cbc_sw) \ + X(Y, ssh_aes192_sdctr_sw) \ + X(Y, ssh_aes192_cbc_sw) \ + X(Y, ssh_aes128_sdctr_sw) \ + X(Y, ssh_aes128_cbc_sw) \ + CIPHERS_AES_NI(X, Y) \ + CIPHERS_AES_NEON(X, Y) \ + X(Y, ssh2_chacha20_poly1305) \ + /* end of list */ + +#define CIPHER_TESTLIST(X, name) X(cipher_ ## name) + +#define MACS(X, Y) \ + X(Y, ssh_hmac_md5) \ + X(Y, ssh_hmac_sha1) \ + X(Y, ssh_hmac_sha1_buggy) \ + X(Y, ssh_hmac_sha1_96) \ + X(Y, ssh_hmac_sha1_96_buggy) \ + X(Y, ssh_hmac_sha256) \ + /* end of list */ + +#define MAC_TESTLIST(X, name) X(mac_ ## name) + +#if HAVE_SHA_NI +#define HASH_SHA_NI(X, Y) X(Y, ssh_sha256_ni) X(Y, ssh_sha1_ni) +#else +#define HASH_SHA_NI(X, Y) +#endif +#if HAVE_NEON_CRYPTO +#define HASH_SHA_NEON(X, Y) X(Y, ssh_sha256_neon) X(Y, ssh_sha1_neon) +#else +#define HASH_SHA_NEON(X, Y) +#endif +#if HAVE_NEON_SHA512 +#define HASH_SHA512_NEON(X, Y) X(Y, ssh_sha384_neon) X(Y, ssh_sha512_neon) +#else +#define HASH_SHA512_NEON(X, Y) +#endif + +#define HASHES(X, Y) \ + X(Y, ssh_md5) \ + X(Y, ssh_sha1) \ + X(Y, ssh_sha1_sw) \ + X(Y, ssh_sha256) \ + X(Y, ssh_sha256_sw) \ + X(Y, ssh_sha384) \ + X(Y, ssh_sha512) \ + X(Y, ssh_sha384_sw) \ + X(Y, ssh_sha512_sw) \ + HASH_SHA_NI(X, Y) \ + HASH_SHA_NEON(X, Y) \ + HASH_SHA512_NEON(X, Y) \ + X(Y, ssh_sha3_224) \ + X(Y, ssh_sha3_256) \ + X(Y, ssh_sha3_384) \ + X(Y, ssh_sha3_512) \ + X(Y, ssh_shake256_114bytes) \ + X(Y, ssh_blake2b) \ + /* end of list */ + +#define HASH_TESTLIST(X, name) X(hash_ ## name) + +#define TESTLIST(X) \ + X(mp_get_nbits) \ + X(mp_from_decimal) \ + X(mp_from_hex) \ + X(mp_get_decimal) \ + X(mp_get_hex) \ + X(mp_cmp_hs) \ + X(mp_cmp_eq) \ + X(mp_min) \ + X(mp_max) \ + X(mp_select_into) \ + X(mp_cond_swap) \ + X(mp_cond_clear) \ + X(mp_add) \ + X(mp_sub) \ + X(mp_mul) \ + X(mp_rshift_safe) \ + X(mp_divmod) \ + X(mp_nthroot) \ + X(mp_modadd) \ + X(mp_modsub) \ + X(mp_modmul) \ + X(mp_modpow) \ + X(mp_invert_mod_2to) \ + X(mp_invert) \ + X(mp_modsqrt) \ + X(ecc_weierstrass_add) \ + X(ecc_weierstrass_double) \ + X(ecc_weierstrass_add_general) \ + X(ecc_weierstrass_multiply) \ + X(ecc_weierstrass_is_identity) \ + X(ecc_weierstrass_get_affine) \ + X(ecc_weierstrass_decompress) \ + X(ecc_montgomery_diff_add) \ + X(ecc_montgomery_double) \ + X(ecc_montgomery_multiply) \ + X(ecc_montgomery_get_affine) \ + X(ecc_edwards_add) \ + X(ecc_edwards_multiply) \ + X(ecc_edwards_eq) \ + X(ecc_edwards_get_affine) \ + X(ecc_edwards_decompress) \ + CIPHERS(CIPHER_TESTLIST, X) \ + MACS(MAC_TESTLIST, X) \ + HASHES(HASH_TESTLIST, X) \ + X(argon2) \ + X(primegen_probabilistic) \ + /* end of list */ + +static void test_mp_get_nbits(void) +{ + mp_int *z = mp_new(512); + static const size_t bitposns[] = { + 0, 1, 5, 16, 23, 32, 67, 123, 234, 511 + }; + mp_int *prev = mp_from_integer(0); + for (size_t i = 0; i < looplimit(lenof(bitposns)); i++) { + mp_int *x = mp_power_2(bitposns[i]); + mp_add_into(z, x, prev); + mp_free(prev); + prev = x; + log_start(); + mp_get_nbits(z); + log_end(); + } + mp_free(prev); + mp_free(z); +} + +static void test_mp_from_decimal(void) +{ + char dec[64]; + static const size_t starts[] = { 0, 1, 5, 16, 23, 32, 63, 64 }; + for (size_t i = 0; i < looplimit(lenof(starts)); i++) { + memset(dec, '0', lenof(dec)); + for (size_t j = starts[i]; j < lenof(dec); j++) { + uint8_t r[4]; + random_read(r, 4); + dec[j] = '0' + GET_32BIT_MSB_FIRST(r) % 10; + } + log_start(); + mp_int *x = mp_from_decimal_pl(make_ptrlen(dec, lenof(dec))); + log_end(); + mp_free(x); + } +} + +static void test_mp_from_hex(void) +{ + char hex[64]; + static const size_t starts[] = { 0, 1, 5, 16, 23, 32, 63, 64 }; + static const char digits[] = "0123456789abcdefABCDEF"; + for (size_t i = 0; i < looplimit(lenof(starts)); i++) { + memset(hex, '0', lenof(hex)); + for (size_t j = starts[i]; j < lenof(hex); j++) { + uint8_t r[4]; + random_read(r, 4); + hex[j] = digits[GET_32BIT_MSB_FIRST(r) % lenof(digits)]; + } + log_start(); + mp_int *x = mp_from_hex_pl(make_ptrlen(hex, lenof(hex))); + log_end(); + mp_free(x); + } +} + +static void test_mp_string_format(char *(*mp_format)(mp_int *x)) +{ + mp_int *z = mp_new(512); + static const size_t bitposns[] = { + 0, 1, 5, 16, 23, 32, 67, 123, 234, 511 + }; + for (size_t i = 0; i < looplimit(lenof(bitposns)); i++) { + mp_random_bits_into(z, bitposns[i]); + log_start(); + char *formatted = mp_format(z); + log_end(); + sfree(formatted); + } + mp_free(z); +} + +static void test_mp_get_decimal(void) +{ + test_mp_string_format(mp_get_decimal); +} + +static void test_mp_get_hex(void) +{ + test_mp_string_format(mp_get_hex); +} + +static void test_mp_cmp(unsigned (*mp_cmp)(mp_int *a, mp_int *b)) +{ + mp_int *a = mp_new(512), *b = mp_new(512); + static const size_t bitposns[] = { + 0, 1, 5, 16, 23, 32, 67, 123, 234, 511 + }; + for (size_t i = 0; i < looplimit(lenof(bitposns)); i++) { + mp_random_fill(b); + mp_int *x = mp_random_bits(bitposns[i]); + mp_xor_into(a, b, x); + mp_free(x); + log_start(); + mp_cmp(a, b); + log_end(); + } + mp_free(a); + mp_free(b); +} + +static void test_mp_cmp_hs(void) +{ + test_mp_cmp(mp_cmp_hs); +} + +static void test_mp_cmp_eq(void) +{ + test_mp_cmp(mp_cmp_eq); +} + +static void test_mp_minmax( + void (*mp_minmax_into)(mp_int *r, mp_int *x, mp_int *y)) +{ + mp_int *a = mp_new(256), *b = mp_new(256); + for (size_t i = 0; i < looplimit(10); i++) { + uint8_t lens[2]; + random_read(lens, 2); + mp_int *x = mp_random_bits(lens[0]); + mp_copy_into(a, x); + mp_free(x); + mp_int *y = mp_random_bits(lens[1]); + mp_copy_into(a, y); + mp_free(y); + log_start(); + mp_minmax_into(a, a, b); + log_end(); + } + mp_free(a); + mp_free(b); +} + +static void test_mp_max(void) +{ + test_mp_minmax(mp_max_into); +} + +static void test_mp_min(void) +{ + test_mp_minmax(mp_min_into); +} + +static void test_mp_select_into(void) +{ + mp_int *a = mp_random_bits(256); + mp_int *b = mp_random_bits(512); + mp_int *r = mp_new(384); + for (size_t i = 0; i < looplimit(16); i++) { + log_start(); + mp_select_into(r, a, b, i & 1); + log_end(); + } + mp_free(a); + mp_free(b); + mp_free(r); +} + +static void test_mp_cond_swap(void) +{ + mp_int *a = mp_random_bits(512); + mp_int *b = mp_random_bits(512); + for (size_t i = 0; i < looplimit(16); i++) { + log_start(); + mp_cond_swap(a, b, i & 1); + log_end(); + } + mp_free(a); + mp_free(b); +} + +static void test_mp_cond_clear(void) +{ + mp_int *a = mp_random_bits(512); + mp_int *x = mp_copy(a); + for (size_t i = 0; i < looplimit(16); i++) { + mp_copy_into(x, a); + log_start(); + mp_cond_clear(a, i & 1); + log_end(); + } + mp_free(a); + mp_free(x); +} + +static void test_mp_arithmetic(mp_int *(*mp_arith)(mp_int *x, mp_int *y)) +{ + mp_int *a = mp_new(256), *b = mp_new(512); + for (size_t i = 0; i < looplimit(16); i++) { + mp_random_fill(a); + mp_random_fill(b); + log_start(); + mp_int *r = mp_arith(a, b); + log_end(); + mp_free(r); + } + mp_free(a); + mp_free(b); +} + +static void test_mp_add(void) +{ + test_mp_arithmetic(mp_add); +} + +static void test_mp_sub(void) +{ + test_mp_arithmetic(mp_sub); +} + +static void test_mp_mul(void) +{ + test_mp_arithmetic(mp_mul); +} + +static void test_mp_invert(void) +{ + test_mp_arithmetic(mp_invert); +} + +static void test_mp_rshift_safe(void) +{ + mp_int *x = mp_random_bits(256); + + for (size_t i = 0; i < looplimit(mp_max_bits(x)+1); i++) { + log_start(); + mp_int *r = mp_rshift_safe(x, i); + log_end(); + mp_free(r); + } + + mp_free(x); +} + +static void test_mp_divmod(void) +{ + mp_int *n = mp_new(256), *d = mp_new(256); + mp_int *q = mp_new(256), *r = mp_new(256); + + for (size_t i = 0; i < looplimit(32); i++) { + uint8_t sizes[2]; + random_read(sizes, 2); + mp_random_bits_into(n, sizes[0]); + mp_random_bits_into(d, sizes[1]); + log_start(); + mp_divmod_into(n, d, q, r); + log_end(); + } + + mp_free(n); + mp_free(d); + mp_free(q); + mp_free(r); +} + +static void test_mp_nthroot(void) +{ + mp_int *x = mp_new(256), *remainder = mp_new(256); + + for (size_t i = 0; i < looplimit(32); i++) { + uint8_t sizes[1]; + random_read(sizes, 1); + mp_random_bits_into(x, sizes[0]); + log_start(); + mp_free(mp_nthroot(x, 3, remainder)); + log_end(); + } + + mp_free(x); + mp_free(remainder); +} + +static void test_mp_modarith( + mp_int *(*mp_modarith)(mp_int *x, mp_int *y, mp_int *modulus)) +{ + mp_int *base = mp_new(256); + mp_int *exponent = mp_new(256); + mp_int *modulus = mp_new(256); + + for (size_t i = 0; i < looplimit(8); i++) { + mp_random_fill(base); + mp_random_fill(exponent); + mp_random_fill(modulus); + mp_set_bit(modulus, 0, 1); /* we only support odd moduli */ + + log_start(); + mp_int *out = mp_modarith(base, exponent, modulus); + log_end(); + + mp_free(out); + } + + mp_free(base); + mp_free(exponent); + mp_free(modulus); +} + +static void test_mp_modadd(void) +{ + test_mp_modarith(mp_modadd); +} + +static void test_mp_modsub(void) +{ + test_mp_modarith(mp_modsub); +} + +static void test_mp_modmul(void) +{ + test_mp_modarith(mp_modmul); +} + +static void test_mp_modpow(void) +{ + test_mp_modarith(mp_modpow); +} + +static void test_mp_invert_mod_2to(void) +{ + mp_int *x = mp_new(512); + + for (size_t i = 0; i < looplimit(32); i++) { + mp_random_fill(x); + mp_set_bit(x, 0, 1); /* input should be odd */ + + log_start(); + mp_int *out = mp_invert_mod_2to(x, 511); + log_end(); + + mp_free(out); + } + + mp_free(x); +} + +static void test_mp_modsqrt(void) +{ + /* The prime isn't secret in this function (and in any case + * finding a non-square on the fly would be prohibitively + * annoying), so I hardcode a fixed one, selected to have a lot of + * factors of two in p-1 so as to exercise lots of choices in the + * algorithm. */ + mp_int *p = + MP_LITERAL(0xb56a517b206a88c73cfa9ec6f704c7030d18212cace82401); + mp_int *nonsquare = MP_LITERAL(0x5); + size_t bits = mp_max_bits(p); + ModsqrtContext *sc = modsqrt_new(p, nonsquare); + mp_free(p); + mp_free(nonsquare); + + mp_int *x = mp_new(bits); + unsigned success; + + /* Do one initial call to cause the lazily initialised sub-context + * to be set up. This will take a while, but it can't be helped. */ + mp_int *unwanted = mp_modsqrt(sc, x, &success); + mp_free(unwanted); + + for (size_t i = 0; i < looplimit(8); i++) { + mp_random_bits_into(x, bits - 1); + log_start(); + mp_int *out = mp_modsqrt(sc, x, &success); + log_end(); + mp_free(out); + } + + mp_free(x); + modsqrt_free(sc); +} + +static WeierstrassCurve *wcurve(void) +{ + mp_int *p = MP_LITERAL(0xc19337603dc856acf31e01375a696fdf5451); + mp_int *a = MP_LITERAL(0x864946f50eecca4cde7abad4865e34be8f67); + mp_int *b = MP_LITERAL(0x6a5bf56db3a03ba91cfbf3241916c90feeca); + mp_int *nonsquare = mp_from_integer(3); + WeierstrassCurve *wc = ecc_weierstrass_curve(p, a, b, nonsquare); + mp_free(p); + mp_free(a); + mp_free(b); + mp_free(nonsquare); + return wc; +} + +static WeierstrassPoint *wpoint(WeierstrassCurve *wc, size_t index) +{ + mp_int *x = NULL, *y = NULL; + WeierstrassPoint *wp; + switch (index) { + case 0: + break; + case 1: + x = MP_LITERAL(0x12345); + y = MP_LITERAL(0x3c2c799a365b53d003ef37dab65860bf80ae); + break; + case 2: + x = MP_LITERAL(0x4e1c77e3c00f7c3b15869e6a4e5f86b3ee53); + y = MP_LITERAL(0x5bde01693130591400b5c9d257d8325a44a5); + break; + case 3: + x = MP_LITERAL(0xb5f0e722b2f0f7e729f55ba9f15511e3b399); + y = MP_LITERAL(0x033d636b855c931cfe679f0b18db164a0d64); + break; + case 4: + x = MP_LITERAL(0xb5f0e722b2f0f7e729f55ba9f15511e3b399); + y = MP_LITERAL(0xbe55d3f4b86bc38ff4b6622c418e599546ed); + break; + default: + unreachable("only 5 example Weierstrass points defined"); + } + if (x && y) { + wp = ecc_weierstrass_point_new(wc, x, y); + } else { + wp = ecc_weierstrass_point_new_identity(wc); + } + if (x) + mp_free(x); + if (y) + mp_free(y); + return wp; +} + +static void test_ecc_weierstrass_add(void) +{ + WeierstrassCurve *wc = wcurve(); + WeierstrassPoint *a = ecc_weierstrass_point_new_identity(wc); + WeierstrassPoint *b = ecc_weierstrass_point_new_identity(wc); + for (size_t i = 0; i < looplimit(5); i++) { + for (size_t j = 0; j < looplimit(5); j++) { + if (i == 0 || j == 0 || i == j || + (i==3 && j==4) || (i==4 && j==3)) + continue; /* difficult cases */ + + WeierstrassPoint *A = wpoint(wc, i), *B = wpoint(wc, j); + ecc_weierstrass_point_copy_into(a, A); + ecc_weierstrass_point_copy_into(b, B); + ecc_weierstrass_point_free(A); + ecc_weierstrass_point_free(B); + + log_start(); + WeierstrassPoint *r = ecc_weierstrass_add(a, b); + log_end(); + ecc_weierstrass_point_free(r); + } + } + ecc_weierstrass_point_free(a); + ecc_weierstrass_point_free(b); + ecc_weierstrass_curve_free(wc); +} + +static void test_ecc_weierstrass_double(void) +{ + WeierstrassCurve *wc = wcurve(); + WeierstrassPoint *a = ecc_weierstrass_point_new_identity(wc); + for (size_t i = 0; i < looplimit(5); i++) { + WeierstrassPoint *A = wpoint(wc, i); + ecc_weierstrass_point_copy_into(a, A); + ecc_weierstrass_point_free(A); + + log_start(); + WeierstrassPoint *r = ecc_weierstrass_double(a); + log_end(); + ecc_weierstrass_point_free(r); + } + ecc_weierstrass_point_free(a); + ecc_weierstrass_curve_free(wc); +} + +static void test_ecc_weierstrass_add_general(void) +{ + WeierstrassCurve *wc = wcurve(); + WeierstrassPoint *a = ecc_weierstrass_point_new_identity(wc); + WeierstrassPoint *b = ecc_weierstrass_point_new_identity(wc); + for (size_t i = 0; i < looplimit(5); i++) { + for (size_t j = 0; j < looplimit(5); j++) { + WeierstrassPoint *A = wpoint(wc, i), *B = wpoint(wc, j); + ecc_weierstrass_point_copy_into(a, A); + ecc_weierstrass_point_copy_into(b, B); + ecc_weierstrass_point_free(A); + ecc_weierstrass_point_free(B); + + log_start(); + WeierstrassPoint *r = ecc_weierstrass_add_general(a, b); + log_end(); + ecc_weierstrass_point_free(r); + } + } + ecc_weierstrass_point_free(a); + ecc_weierstrass_point_free(b); + ecc_weierstrass_curve_free(wc); +} + +static void test_ecc_weierstrass_multiply(void) +{ + WeierstrassCurve *wc = wcurve(); + WeierstrassPoint *a = ecc_weierstrass_point_new_identity(wc); + mp_int *exponent = mp_new(56); + for (size_t i = 1; i < looplimit(5); i++) { + WeierstrassPoint *A = wpoint(wc, i); + ecc_weierstrass_point_copy_into(a, A); + ecc_weierstrass_point_free(A); + mp_random_fill(exponent); + + log_start(); + WeierstrassPoint *r = ecc_weierstrass_multiply(a, exponent); + log_end(); + + ecc_weierstrass_point_free(r); + } + ecc_weierstrass_point_free(a); + ecc_weierstrass_curve_free(wc); + mp_free(exponent); +} + +static void test_ecc_weierstrass_is_identity(void) +{ + WeierstrassCurve *wc = wcurve(); + WeierstrassPoint *a = ecc_weierstrass_point_new_identity(wc); + for (size_t i = 1; i < looplimit(5); i++) { + WeierstrassPoint *A = wpoint(wc, i); + ecc_weierstrass_point_copy_into(a, A); + ecc_weierstrass_point_free(A); + + log_start(); + ecc_weierstrass_is_identity(a); + log_end(); + } + ecc_weierstrass_point_free(a); + ecc_weierstrass_curve_free(wc); +} + +static void test_ecc_weierstrass_get_affine(void) +{ + WeierstrassCurve *wc = wcurve(); + WeierstrassPoint *r = ecc_weierstrass_point_new_identity(wc); + for (size_t i = 0; i < looplimit(4); i++) { + WeierstrassPoint *A = wpoint(wc, i), *B = wpoint(wc, i+1); + WeierstrassPoint *R = ecc_weierstrass_add_general(A, B); + ecc_weierstrass_point_copy_into(r, R); + ecc_weierstrass_point_free(A); + ecc_weierstrass_point_free(B); + ecc_weierstrass_point_free(R); + + log_start(); + mp_int *x, *y; + ecc_weierstrass_get_affine(r, &x, &y); + log_end(); + mp_free(x); + mp_free(y); + } + ecc_weierstrass_point_free(r); + ecc_weierstrass_curve_free(wc); +} + +static void test_ecc_weierstrass_decompress(void) +{ + WeierstrassCurve *wc = wcurve(); + + /* As in the mp_modsqrt test, prime the lazy initialisation of the + * ModsqrtContext */ + mp_int *x = mp_new(144); + WeierstrassPoint *a = ecc_weierstrass_point_new_from_x(wc, x, 0); + if (a) /* don't care whether this one succeeded */ + ecc_weierstrass_point_free(a); + + for (size_t p = 0; p < looplimit(2); p++) { + for (size_t i = 1; i < looplimit(5); i++) { + WeierstrassPoint *A = wpoint(wc, i); + mp_int *X; + ecc_weierstrass_get_affine(A, &X, NULL); + mp_copy_into(x, X); + mp_free(X); + ecc_weierstrass_point_free(A); + + log_start(); + WeierstrassPoint *a = ecc_weierstrass_point_new_from_x(wc, x, p); + log_end(); + + ecc_weierstrass_point_free(a); + } + } + mp_free(x); + ecc_weierstrass_curve_free(wc); +} + +static MontgomeryCurve *mcurve(void) +{ + mp_int *p = MP_LITERAL(0xde978eb1db35236a5792e9f0c04d86000659); + mp_int *a = MP_LITERAL(0x799b62a612b1b30e1c23cea6d67b2e33c51a); + mp_int *b = MP_LITERAL(0x944bf9042b56821a8c9e0b49b636c2502b2b); + MontgomeryCurve *mc = ecc_montgomery_curve(p, a, b); + mp_free(p); + mp_free(a); + mp_free(b); + return mc; +} + +static MontgomeryPoint *mpoint(MontgomeryCurve *wc, size_t index) +{ + mp_int *x = NULL; + MontgomeryPoint *mp; + switch (index) { + case 0: + x = MP_LITERAL(31415); + break; + case 1: + x = MP_LITERAL(0x4d352c654c06eecfe19104118857b38398e8); + break; + case 2: + x = MP_LITERAL(0x03fca2a73983bc3434caae3134599cd69cce); + break; + case 3: + x = MP_LITERAL(0xa0fd735ce9b3406498b5f035ee655bda4e15); + break; + case 4: + x = MP_LITERAL(0x7c7f46a00cc286dbe47db39b6d8f5efd920e); + break; + case 5: + x = MP_LITERAL(0x07a6dc30d3b320448e6f8999be417e6b7c6b); + break; + case 6: + x = MP_LITERAL(0x7832da5fc16dfbd358170b2b96896cd3cd06); + break; + default: + unreachable("only 7 example Weierstrass points defined"); + } + mp = ecc_montgomery_point_new(wc, x); + mp_free(x); + return mp; +} + +static void test_ecc_montgomery_diff_add(void) +{ + MontgomeryCurve *wc = mcurve(); + MontgomeryPoint *a = NULL, *b = NULL, *c = NULL; + for (size_t i = 0; i < looplimit(5); i++) { + MontgomeryPoint *A = mpoint(wc, i); + MontgomeryPoint *B = mpoint(wc, i); + MontgomeryPoint *C = mpoint(wc, i); + if (!a) { + a = A; + b = B; + c = C; + } else { + ecc_montgomery_point_copy_into(a, A); + ecc_montgomery_point_copy_into(b, B); + ecc_montgomery_point_copy_into(c, C); + ecc_montgomery_point_free(A); + ecc_montgomery_point_free(B); + ecc_montgomery_point_free(C); + } + + log_start(); + MontgomeryPoint *r = ecc_montgomery_diff_add(b, c, a); + log_end(); + + ecc_montgomery_point_free(r); + } + ecc_montgomery_point_free(a); + ecc_montgomery_point_free(b); + ecc_montgomery_point_free(c); + ecc_montgomery_curve_free(wc); +} + +static void test_ecc_montgomery_double(void) +{ + MontgomeryCurve *wc = mcurve(); + MontgomeryPoint *a = NULL; + for (size_t i = 0; i < looplimit(7); i++) { + MontgomeryPoint *A = mpoint(wc, i); + if (!a) { + a = A; + } else { + ecc_montgomery_point_copy_into(a, A); + ecc_montgomery_point_free(A); + } + + log_start(); + MontgomeryPoint *r = ecc_montgomery_double(a); + log_end(); + + ecc_montgomery_point_free(r); + } + ecc_montgomery_point_free(a); + ecc_montgomery_curve_free(wc); +} + +static void test_ecc_montgomery_multiply(void) +{ + MontgomeryCurve *wc = mcurve(); + MontgomeryPoint *a = NULL; + mp_int *exponent = mp_new(56); + for (size_t i = 0; i < looplimit(7); i++) { + MontgomeryPoint *A = mpoint(wc, i); + if (!a) { + a = A; + } else { + ecc_montgomery_point_copy_into(a, A); + ecc_montgomery_point_free(A); + } + mp_random_fill(exponent); + + log_start(); + MontgomeryPoint *r = ecc_montgomery_multiply(a, exponent); + log_end(); + + ecc_montgomery_point_free(r); + } + ecc_montgomery_point_free(a); + ecc_montgomery_curve_free(wc); + mp_free(exponent); +} + +static void test_ecc_montgomery_get_affine(void) +{ + MontgomeryCurve *wc = mcurve(); + MontgomeryPoint *r = NULL; + for (size_t i = 0; i < looplimit(5); i++) { + MontgomeryPoint *A = mpoint(wc, i); + MontgomeryPoint *B = mpoint(wc, i); + MontgomeryPoint *C = mpoint(wc, i); + MontgomeryPoint *R = ecc_montgomery_diff_add(B, C, A); + ecc_montgomery_point_free(A); + ecc_montgomery_point_free(B); + ecc_montgomery_point_free(C); + if (!r) { + r = R; + } else { + ecc_montgomery_point_copy_into(r, R); + ecc_montgomery_point_free(R); + } + + log_start(); + mp_int *x; + ecc_montgomery_get_affine(r, &x); + log_end(); + + mp_free(x); + } + ecc_montgomery_point_free(r); + ecc_montgomery_curve_free(wc); +} + +static EdwardsCurve *ecurve(void) +{ + mp_int *p = MP_LITERAL(0xfce2dac1704095de0b5c48876c45063cd475); + mp_int *d = MP_LITERAL(0xbd4f77401c3b14ae1742a7d1d367adac8f3e); + mp_int *a = MP_LITERAL(0x51d0845da3fa871aaac4341adea53b861919); + mp_int *nonsquare = mp_from_integer(2); + EdwardsCurve *ec = ecc_edwards_curve(p, d, a, nonsquare); + mp_free(p); + mp_free(d); + mp_free(a); + mp_free(nonsquare); + return ec; +} + +static EdwardsPoint *epoint(EdwardsCurve *wc, size_t index) +{ + mp_int *x, *y; + EdwardsPoint *ep; + switch (index) { + case 0: + x = MP_LITERAL(0x0); + y = MP_LITERAL(0x1); + break; + case 1: + x = MP_LITERAL(0x3d8aef0294a67c1c7e8e185d987716250d7c); + y = MP_LITERAL(0x27184); + break; + case 2: + x = MP_LITERAL(0xf44ed5b8a6debfd3ab24b7874cd2589fd672); + y = MP_LITERAL(0xd635d8d15d367881c8a3af472c8fe487bf40); + break; + case 3: + x = MP_LITERAL(0xde114ecc8b944684415ef81126a07269cd30); + y = MP_LITERAL(0xbe0fd45ff67ebba047ed0ec5a85d22e688a1); + break; + case 4: + x = MP_LITERAL(0x76bd2f90898d271b492c9c20dd7bbfe39fe5); + y = MP_LITERAL(0xbf1c82698b4a5a12c1057631c1ebdc216ae2); + break; + default: + unreachable("only 5 example Edwards points defined"); + } + ep = ecc_edwards_point_new(wc, x, y); + mp_free(x); + mp_free(y); + return ep; +} + +static void test_ecc_edwards_add(void) +{ + EdwardsCurve *ec = ecurve(); + EdwardsPoint *a = NULL, *b = NULL; + for (size_t i = 0; i < looplimit(5); i++) { + for (size_t j = 0; j < looplimit(5); j++) { + EdwardsPoint *A = epoint(ec, i), *B = epoint(ec, j); + if (!a) { + a = A; + b = B; + } else { + ecc_edwards_point_copy_into(a, A); + ecc_edwards_point_copy_into(b, B); + ecc_edwards_point_free(A); + ecc_edwards_point_free(B); + } + + log_start(); + EdwardsPoint *r = ecc_edwards_add(a, b); + log_end(); + + ecc_edwards_point_free(r); + } + } + ecc_edwards_point_free(a); + ecc_edwards_point_free(b); + ecc_edwards_curve_free(ec); +} + +static void test_ecc_edwards_multiply(void) +{ + EdwardsCurve *ec = ecurve(); + EdwardsPoint *a = NULL; + mp_int *exponent = mp_new(56); + for (size_t i = 1; i < looplimit(5); i++) { + EdwardsPoint *A = epoint(ec, i); + if (!a) { + a = A; + } else { + ecc_edwards_point_copy_into(a, A); + ecc_edwards_point_free(A); + } + mp_random_fill(exponent); + + log_start(); + EdwardsPoint *r = ecc_edwards_multiply(a, exponent); + log_end(); + + ecc_edwards_point_free(r); + } + ecc_edwards_point_free(a); + ecc_edwards_curve_free(ec); + mp_free(exponent); +} + +static void test_ecc_edwards_eq(void) +{ + EdwardsCurve *ec = ecurve(); + EdwardsPoint *a = NULL, *b = NULL; + for (size_t i = 0; i < looplimit(5); i++) { + for (size_t j = 0; j < looplimit(5); j++) { + EdwardsPoint *A = epoint(ec, i), *B = epoint(ec, j); + if (!a) { + a = A; + b = B; + } else { + ecc_edwards_point_copy_into(a, A); + ecc_edwards_point_copy_into(b, B); + ecc_edwards_point_free(A); + ecc_edwards_point_free(B); + } + + log_start(); + ecc_edwards_eq(a, b); + log_end(); + } + } + ecc_edwards_point_free(a); + ecc_edwards_point_free(b); + ecc_edwards_curve_free(ec); +} + +static void test_ecc_edwards_get_affine(void) +{ + EdwardsCurve *ec = ecurve(); + EdwardsPoint *r = NULL; + for (size_t i = 0; i < looplimit(4); i++) { + EdwardsPoint *A = epoint(ec, i), *B = epoint(ec, i+1); + EdwardsPoint *R = ecc_edwards_add(A, B); + ecc_edwards_point_free(A); + ecc_edwards_point_free(B); + if (!r) { + r = R; + } else { + ecc_edwards_point_copy_into(r, R); + ecc_edwards_point_free(R); + } + + log_start(); + mp_int *x, *y; + ecc_edwards_get_affine(r, &x, &y); + log_end(); + + mp_free(x); + mp_free(y); + } + ecc_edwards_point_free(r); + ecc_edwards_curve_free(ec); +} + +static void test_ecc_edwards_decompress(void) +{ + EdwardsCurve *ec = ecurve(); + + /* As in the mp_modsqrt test, prime the lazy initialisation of the + * ModsqrtContext */ + mp_int *y = mp_new(144); + EdwardsPoint *a = ecc_edwards_point_new_from_y(ec, y, 0); + if (a) /* don't care whether this one succeeded */ + ecc_edwards_point_free(a); + + for (size_t p = 0; p < looplimit(2); p++) { + for (size_t i = 0; i < looplimit(5); i++) { + EdwardsPoint *A = epoint(ec, i); + mp_int *Y; + ecc_edwards_get_affine(A, NULL, &Y); + mp_copy_into(y, Y); + mp_free(Y); + ecc_edwards_point_free(A); + + log_start(); + EdwardsPoint *a = ecc_edwards_point_new_from_y(ec, y, p); + log_end(); + + ecc_edwards_point_free(a); + } + } + mp_free(y); + ecc_edwards_curve_free(ec); +} + +static void test_cipher(const ssh_cipheralg *calg) +{ + ssh_cipher *c = ssh_cipher_new(calg); + if (!c) { + test_skipped = true; + return; + } + const ssh2_macalg *malg = calg->required_mac; + ssh2_mac *m = NULL; + if (malg) { + m = ssh2_mac_new(malg, c); + if (!m) { + ssh_cipher_free(c); + test_skipped = true; + return; + } + } + + uint8_t *ckey = snewn(calg->padded_keybytes, uint8_t); + uint8_t *civ = snewn(calg->blksize, uint8_t); + uint8_t *mkey = malg ? snewn(malg->keylen, uint8_t) : NULL; + size_t datalen = calg->blksize * 8; + size_t maclen = malg ? malg->len : 0; + uint8_t *data = snewn(datalen + maclen, uint8_t); + size_t lenlen = 4; + uint8_t *lendata = snewn(lenlen, uint8_t); + + for (size_t i = 0; i < looplimit(16); i++) { + random_read(ckey, calg->padded_keybytes); + if (malg) + random_read(mkey, malg->keylen); + random_read(data, datalen); + random_read(lendata, lenlen); + if (i == 0) { + /* Ensure one of our test IVs will cause SDCTR wraparound */ + memset(civ, 0xFF, calg->blksize); + } else { + random_read(civ, calg->blksize); + } + uint8_t seqbuf[4]; + random_read(seqbuf, 4); + uint32_t seq = GET_32BIT_MSB_FIRST(seqbuf); + + log_start(); + ssh_cipher_setkey(c, ckey); + ssh_cipher_setiv(c, civ); + if (m) + ssh2_mac_setkey(m, make_ptrlen(mkey, malg->keylen)); + if (calg->flags & SSH_CIPHER_SEPARATE_LENGTH) + ssh_cipher_encrypt_length(c, data, datalen, seq); + ssh_cipher_encrypt(c, data, datalen); + if (m) { + ssh2_mac_generate(m, data, datalen, seq); + ssh2_mac_verify(m, data, datalen, seq); + } + if (calg->flags & SSH_CIPHER_SEPARATE_LENGTH) + ssh_cipher_decrypt_length(c, data, datalen, seq); + ssh_cipher_decrypt(c, data, datalen); + log_end(); + } + + sfree(ckey); + sfree(civ); + sfree(mkey); + sfree(data); + sfree(lendata); + if (m) + ssh2_mac_free(m); + ssh_cipher_free(c); +} + +#define CIPHER_TESTFN(Y_unused, cipher) \ + static void test_cipher_##cipher(void) { test_cipher(&cipher); } +CIPHERS(CIPHER_TESTFN, Y_unused) + +static void test_mac(const ssh2_macalg *malg) +{ + ssh2_mac *m = ssh2_mac_new(malg, NULL); + if (!m) { + test_skipped = true; + return; + } + + uint8_t *mkey = snewn(malg->keylen, uint8_t); + size_t datalen = 256; + size_t maclen = malg->len; + uint8_t *data = snewn(datalen + maclen, uint8_t); + + for (size_t i = 0; i < looplimit(16); i++) { + random_read(mkey, malg->keylen); + random_read(data, datalen); + uint8_t seqbuf[4]; + random_read(seqbuf, 4); + uint32_t seq = GET_32BIT_MSB_FIRST(seqbuf); + + log_start(); + ssh2_mac_setkey(m, make_ptrlen(mkey, malg->keylen)); + ssh2_mac_generate(m, data, datalen, seq); + ssh2_mac_verify(m, data, datalen, seq); + log_end(); + } + + sfree(mkey); + sfree(data); + ssh2_mac_free(m); +} + +#define MAC_TESTFN(Y_unused, mac) \ + static void test_mac_##mac(void) { test_mac(&mac); } +MACS(MAC_TESTFN, Y_unused) + +static void test_hash(const ssh_hashalg *halg) +{ + ssh_hash *h = ssh_hash_new(halg); + if (!h) { + test_skipped = true; + return; + } + + size_t datalen = 256; + uint8_t *data = snewn(datalen, uint8_t); + uint8_t *hash = snewn(halg->hlen, uint8_t); + + for (size_t i = 0; i < looplimit(16); i++) { + random_read(data, datalen); + + log_start(); + put_data(h, data, datalen); + ssh_hash_final(h, hash); + log_end(); + + h = ssh_hash_new(halg); + } + + sfree(data); + sfree(hash); + ssh_hash_free(h); +} + +#define HASH_TESTFN(Y_unused, hash) \ + static void test_hash_##hash(void) { test_hash(&hash); } +HASHES(HASH_TESTFN, Y_unused) + +struct test { + const char *testname; + void (*testfn)(void); +}; + +static void test_argon2(void) +{ + /* + * We can only expect the Argon2i variant to pass this stringent + * test for no data-dependency, because the other two variants of + * Argon2 have _deliberate_ data-dependency. + */ + size_t inlen = 48+16+24+8; + uint8_t *indata = snewn(inlen, uint8_t); + ptrlen password = make_ptrlen(indata, 48); + ptrlen salt = make_ptrlen(indata+48, 16); + ptrlen secret = make_ptrlen(indata+48+16, 24); + ptrlen assoc = make_ptrlen(indata+48+16+24, 8); + + strbuf *outdata = strbuf_new(); + strbuf_append(outdata, 256); + + for (size_t i = 0; i < looplimit(16); i++) { + strbuf_clear(outdata); + random_read(indata, inlen); + + log_start(); + argon2(Argon2i, 32, 2, 2, 144, password, salt, secret, assoc, outdata); + log_end(); + } + + sfree(indata); + strbuf_free(outdata); +} + +static void test_primegen(const PrimeGenerationPolicy *policy) +{ + static ProgressReceiver null_progress = { .vt = &null_progress_vt }; + + PrimeGenerationContext *pgc = primegen_new_context(policy); + + init_smallprimes(); + mp_int *pcopy = mp_new(128); + + for (size_t i = 0; i < looplimit(2); i++) { + while (true) { + struct random_state st = random_get_state(); + + PrimeCandidateSource *pcs = pcs_new(128); + pcs_set_oneshot(pcs); + pcs_ready(pcs); + mp_int *p = primegen_generate(pgc, pcs, &null_progress); + + if (p) { + mp_copy_into(pcopy, p); + sfree(p); + + random_set_state(st); + + log_start(); + PrimeCandidateSource *pcs = pcs_new(128); + pcs_set_oneshot(pcs); + pcs_ready(pcs); + mp_int *q = primegen_generate(pgc, pcs, &null_progress); + log_end(); + + assert(q); + assert(mp_cmp_eq(pcopy, q)); + mp_free(q); + break; + } + } + } + + mp_free(pcopy); + primegen_free_context(pgc); +} + +static void test_primegen_probabilistic(void) +{ + test_primegen(&primegen_probabilistic); +} + +static const struct test tests[] = { +#define STRUCT_TEST(X) { #X, test_##X }, +TESTLIST(STRUCT_TEST) +#undef STRUCT_TEST +}; + +void dputs(const char *buf) +{ + fputs(buf, stderr); +} + +int main(int argc, char **argv) +{ + bool doing_opts = true; + const char *pname = argv[0]; + uint8_t tests_to_run[lenof(tests)]; + bool keep_outfiles = false; + bool test_names_given = false; + + memset(tests_to_run, 1, sizeof(tests_to_run)); + random_hash = ssh_hash_new(&ssh_sha256); + + while (--argc > 0) { + char *p = *++argv; + + if (p[0] == '-' && doing_opts) { + if (!strcmp(p, "-O")) { + if (--argc <= 0) { + fprintf(stderr, "'-O' expects a directory name\n"); + return 1; + } + outdir = *++argv; + } else if (!strcmp(p, "-k") || !strcmp(p, "--keep")) { + keep_outfiles = true; + } else if (!strcmp(p, "--")) { + doing_opts = false; + } else if (!strcmp(p, "--help")) { + printf(" usage: drrun -c test/sclog/libsclog.so -- " + "%s -O \n", pname); + printf("options: -O " + "put log files in the specified directory\n"); + printf(" -k, --keep " + "do not delete log files for tests that passed\n"); + printf(" also: --help " + "display this text\n"); + return 0; + } else { + fprintf(stderr, "unknown command line option '%s'\n", p); + return 1; + } + } else { + if (!test_names_given) { + test_names_given = true; + memset(tests_to_run, 0, sizeof(tests_to_run)); + } + bool found_one = false; + for (size_t i = 0; i < lenof(tests); i++) { + if (wc_match(p, tests[i].testname)) { + tests_to_run[i] = 1; + found_one = true; + } + } + if (!found_one) { + fprintf(stderr, "no test name matched '%s'\n", p); + return 1; + } + } + } + + bool is_dry_run = dry_run(); + + if (is_dry_run) { + printf("Dry run (DynamoRIO instrumentation not detected)\n"); + } else { + /* Print the address of main() in this run. The idea is that + * if this image is compiled to be position-independent, then + * PC values in the logs won't match the ones you get if you + * disassemble the binary, so it'll be harder to match up the + * log messages to the code. But if you know the address of a + * fixed (and not inlined) function in both worlds, you can + * find out the offset between them. */ + printf("Live run, main = %p\n", (void *)main); + + if (!outdir) { + fprintf(stderr, "expected -O option\n"); + return 1; + } + printf("Will write log files to %s\n", outdir); + } + + size_t nrun = 0, npass = 0; + + for (size_t i = 0; i < lenof(tests); i++) { + bool keep_these_outfiles = true; + + if (!tests_to_run[i]) + continue; + const struct test *test = &tests[i]; + printf("Running test %s ... ", test->testname); + fflush(stdout); + + test_skipped = false; + random_seed(test->testname); + test_basename = test->testname; + test_index = 0; + + test->testfn(); + + if (test_skipped) { + /* Used for e.g. tests of hardware-accelerated crypto when + * the hardware acceleration isn't available */ + printf("skipped\n"); + continue; + } + + nrun++; + + if (is_dry_run) { + printf("dry run done\n"); + continue; /* test files won't exist anyway */ + } + + if (test_index < 2) { + printf("FAIL: test did not generate multiple output files\n"); + goto test_done; + } + + char *firstfile = log_filename(test_basename, 0); + FILE *firstfp = fopen(firstfile, "rb"); + if (!firstfp) { + printf("ERR: %s: open: %s\n", firstfile, strerror(errno)); + goto test_done; + } + for (size_t i = 1; i < test_index; i++) { + char *nextfile = log_filename(test_basename, i); + FILE *nextfp = fopen(nextfile, "rb"); + if (!nextfp) { + printf("ERR: %s: open: %s\n", nextfile, strerror(errno)); + goto test_done; + } + + rewind(firstfp); + char buf1[4096], bufn[4096]; + bool compare_ok = false; + while (true) { + size_t r1 = fread(buf1, 1, sizeof(buf1), firstfp); + size_t rn = fread(bufn, 1, sizeof(bufn), nextfp); + if (r1 != rn) { + printf("FAIL: %s %s: different lengths\n", + firstfile, nextfile); + break; + } + if (r1 == 0) { + if (feof(firstfp) && feof(nextfp)) { + compare_ok = true; + } else { + printf("FAIL: %s %s: error at end of file\n", + firstfile, nextfile); + } + break; + } + if (memcmp(buf1, bufn, r1) != 0) { + printf("FAIL: %s %s: different content\n", + firstfile, nextfile); + break; + } + } + fclose(nextfp); + sfree(nextfile); + if (!compare_ok) { + goto test_done; + } + } + fclose(firstfp); + sfree(firstfile); + + printf("pass\n"); + npass++; + keep_these_outfiles = keep_outfiles; + + test_done: + if (!keep_these_outfiles) { + for (size_t i = 0; i < test_index; i++) { + char *file = log_filename(test_basename, i); + remove(file); + sfree(file); + } + } + } + + ssh_hash_free(random_hash); + + if (npass == nrun) { + printf("All tests passed\n"); + return 0; + } else { + printf("%"SIZEu" tests failed\n", nrun - npass); + return 1; + } +} diff --git a/test/testzlib.c b/test/testzlib.c new file mode 100644 index 00000000..0ef4ef19 --- /dev/null +++ b/test/testzlib.c @@ -0,0 +1,114 @@ +/* + * Main program to compile ssh/zlib.c into a zlib decoding tool. + * + * This is potentially a handy tool in its own right for picking apart + * Zip files or PDFs or PNGs, because it accepts the bare Deflate + * format and the zlib wrapper format, unlike 'zcat' which accepts + * only the gzip wrapper format. + * + * It's also useful as a means for a fuzzer to get reasonably direct + * access to PuTTY's zlib decompressor. + */ + +#include +#include +#include +#include + +#include "defs.h" +#include "ssh.h" + +void out_of_memory(void) +{ + fprintf(stderr, "Out of memory!\n"); + exit(1); +} + +void dputs(const char *buf) +{ + fputs(buf, stderr); +} + +int main(int argc, char **argv) +{ + unsigned char buf[16], *outbuf; + int ret, outlen; + ssh_decompressor *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 if (!strcmp(p, "--help")) { + printf("usage: testzlib decode zlib (RFC1950) data" + " from standard input\n"); + printf(" testzlib -d decode Deflate (RFC1951) data" + " from standard input\n"); + printf(" testzlib --help display this text\n"); + return 0; + } 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 = ssh_decompressor_new(&ssh_zlib); + + if (noheader) { + /* + * Provide missing zlib header if -d was specified. + */ + static const unsigned char ersatz_zlib_header[] = { 0x78, 0x9C }; + ssh_decompressor_decompress( + handle, ersatz_zlib_header, sizeof(ersatz_zlib_header), + &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; + ssh_decompressor_decompress(handle, buf, ret, &outbuf, &outlen); + if (outbuf) { + if (outlen) + fwrite(outbuf, 1, outlen, stdout); + sfree(outbuf); + } else { + fprintf(stderr, "decoding error\n"); + fclose(fp); + return 1; + } + } + + ssh_decompressor_free(handle); + + if (filename) + fclose(fp); + + return 0; +} diff --git a/testcrypt-enum.h b/testcrypt-enum.h deleted file mode 100644 index cec8d836..00000000 --- a/testcrypt-enum.h +++ /dev/null @@ -1,144 +0,0 @@ -BEGIN_ENUM_TYPE(hashalg) - ENUM_VALUE("md5", &ssh_md5) - ENUM_VALUE("sha1", &ssh_sha1) - ENUM_VALUE("sha1_sw", &ssh_sha1_sw) - ENUM_VALUE("sha256", &ssh_sha256) - ENUM_VALUE("sha384", &ssh_sha384) - ENUM_VALUE("sha512", &ssh_sha512) - ENUM_VALUE("sha256_sw", &ssh_sha256_sw) - ENUM_VALUE("sha384_sw", &ssh_sha384_sw) - ENUM_VALUE("sha512_sw", &ssh_sha512_sw) -#if HAVE_SHA_NI - ENUM_VALUE("sha1_ni", &ssh_sha1_ni) - ENUM_VALUE("sha256_ni", &ssh_sha256_ni) -#endif -#if HAVE_NEON_CRYPTO - ENUM_VALUE("sha1_neon", &ssh_sha1_neon) - ENUM_VALUE("sha256_neon", &ssh_sha256_neon) -#endif -#if HAVE_NEON_SHA512 - ENUM_VALUE("sha384_neon", &ssh_sha384_neon) - ENUM_VALUE("sha512_neon", &ssh_sha512_neon) -#endif - ENUM_VALUE("sha3_224", &ssh_sha3_224) - ENUM_VALUE("sha3_256", &ssh_sha3_256) - ENUM_VALUE("sha3_384", &ssh_sha3_384) - ENUM_VALUE("sha3_512", &ssh_sha3_512) - ENUM_VALUE("shake256_114bytes", &ssh_shake256_114bytes) - ENUM_VALUE("blake2b", &ssh_blake2b) -END_ENUM_TYPE(hashalg) - -BEGIN_ENUM_TYPE(macalg) - ENUM_VALUE("hmac_md5", &ssh_hmac_md5) - ENUM_VALUE("hmac_sha1", &ssh_hmac_sha1) - ENUM_VALUE("hmac_sha1_buggy", &ssh_hmac_sha1_buggy) - ENUM_VALUE("hmac_sha1_96", &ssh_hmac_sha1_96) - ENUM_VALUE("hmac_sha1_96_buggy", &ssh_hmac_sha1_96_buggy) - ENUM_VALUE("hmac_sha256", &ssh_hmac_sha256) - ENUM_VALUE("poly1305", &ssh2_poly1305) -END_ENUM_TYPE(macalg) - -BEGIN_ENUM_TYPE(keyalg) - ENUM_VALUE("dsa", &ssh_dsa) - ENUM_VALUE("rsa", &ssh_rsa) - ENUM_VALUE("ed25519", &ssh_ecdsa_ed25519) - ENUM_VALUE("ed448", &ssh_ecdsa_ed448) - ENUM_VALUE("p256", &ssh_ecdsa_nistp256) - ENUM_VALUE("p384", &ssh_ecdsa_nistp384) - ENUM_VALUE("p521", &ssh_ecdsa_nistp521) -END_ENUM_TYPE(keyalg) - -BEGIN_ENUM_TYPE(cipheralg) - ENUM_VALUE("3des_ctr", &ssh_3des_ssh2_ctr) - ENUM_VALUE("3des_ssh2", &ssh_3des_ssh2) - ENUM_VALUE("3des_ssh1", &ssh_3des_ssh1) - ENUM_VALUE("des_cbc", &ssh_des) - ENUM_VALUE("aes256_ctr", &ssh_aes256_sdctr) - ENUM_VALUE("aes256_cbc", &ssh_aes256_cbc) - ENUM_VALUE("aes192_ctr", &ssh_aes192_sdctr) - ENUM_VALUE("aes192_cbc", &ssh_aes192_cbc) - ENUM_VALUE("aes128_ctr", &ssh_aes128_sdctr) - ENUM_VALUE("aes128_cbc", &ssh_aes128_cbc) - ENUM_VALUE("aes256_ctr_sw", &ssh_aes256_sdctr_sw) - ENUM_VALUE("aes256_cbc_sw", &ssh_aes256_cbc_sw) - ENUM_VALUE("aes192_ctr_sw", &ssh_aes192_sdctr_sw) - ENUM_VALUE("aes192_cbc_sw", &ssh_aes192_cbc_sw) - ENUM_VALUE("aes128_ctr_sw", &ssh_aes128_sdctr_sw) - ENUM_VALUE("aes128_cbc_sw", &ssh_aes128_cbc_sw) -#if HAVE_AES_NI - ENUM_VALUE("aes256_ctr_ni", &ssh_aes256_sdctr_ni) - ENUM_VALUE("aes256_cbc_ni", &ssh_aes256_cbc_ni) - ENUM_VALUE("aes192_ctr_ni", &ssh_aes192_sdctr_ni) - ENUM_VALUE("aes192_cbc_ni", &ssh_aes192_cbc_ni) - ENUM_VALUE("aes128_ctr_ni", &ssh_aes128_sdctr_ni) - ENUM_VALUE("aes128_cbc_ni", &ssh_aes128_cbc_ni) -#endif -#if HAVE_NEON_CRYPTO - ENUM_VALUE("aes256_ctr_neon", &ssh_aes256_sdctr_neon) - ENUM_VALUE("aes256_cbc_neon", &ssh_aes256_cbc_neon) - ENUM_VALUE("aes192_ctr_neon", &ssh_aes192_sdctr_neon) - ENUM_VALUE("aes192_cbc_neon", &ssh_aes192_cbc_neon) - ENUM_VALUE("aes128_ctr_neon", &ssh_aes128_sdctr_neon) - ENUM_VALUE("aes128_cbc_neon", &ssh_aes128_cbc_neon) -#endif - ENUM_VALUE("blowfish_ctr", &ssh_blowfish_ssh2_ctr) - ENUM_VALUE("blowfish_ssh2", &ssh_blowfish_ssh2) - ENUM_VALUE("blowfish_ssh1", &ssh_blowfish_ssh1) - ENUM_VALUE("arcfour256", &ssh_arcfour256_ssh2) - ENUM_VALUE("arcfour128", &ssh_arcfour128_ssh2) - ENUM_VALUE("chacha20_poly1305", &ssh2_chacha20_poly1305) -END_ENUM_TYPE(cipheralg) - -BEGIN_ENUM_TYPE(dh_group) - ENUM_VALUE("group1", &ssh_diffiehellman_group1_sha1) - ENUM_VALUE("group14", &ssh_diffiehellman_group14_sha256) -END_ENUM_TYPE(dh_group) - -BEGIN_ENUM_TYPE(ecdh_alg) - ENUM_VALUE("curve25519", &ssh_ec_kex_curve25519) - ENUM_VALUE("curve448", &ssh_ec_kex_curve448) - ENUM_VALUE("nistp256", &ssh_ec_kex_nistp256) - ENUM_VALUE("nistp384", &ssh_ec_kex_nistp384) - ENUM_VALUE("nistp521", &ssh_ec_kex_nistp521) -END_ENUM_TYPE(ecdh_alg) - -BEGIN_ENUM_TYPE(rsaorder) - ENUM_VALUE("exponent_first", RSA_SSH1_EXPONENT_FIRST) - ENUM_VALUE("modulus_first", RSA_SSH1_MODULUS_FIRST) -END_ENUM_TYPE(rsaorder) - -BEGIN_ENUM_TYPE(primegenpolicy) - ENUM_VALUE("probabilistic", &primegen_probabilistic) - ENUM_VALUE("provable_fast", &primegen_provable_fast) - ENUM_VALUE("provable_maurer_simple", &primegen_provable_maurer_simple) - ENUM_VALUE("provable_maurer_complex", &primegen_provable_maurer_complex) -END_ENUM_TYPE(primegenpolicy) - -BEGIN_ENUM_TYPE(argon2flavour) - ENUM_VALUE("d", Argon2d) - ENUM_VALUE("i", Argon2i) - ENUM_VALUE("id", Argon2id) - /* I expect to forget which spelling I chose, so let's support many */ - ENUM_VALUE("argon2d", Argon2d) - ENUM_VALUE("argon2i", Argon2i) - ENUM_VALUE("argon2id", Argon2id) - ENUM_VALUE("Argon2d", Argon2d) - ENUM_VALUE("Argon2i", Argon2i) - ENUM_VALUE("Argon2id", Argon2id) -END_ENUM_TYPE(argon2flavour) - -BEGIN_ENUM_TYPE(fptype) - ENUM_VALUE("md5", SSH_FPTYPE_MD5) - ENUM_VALUE("sha256", SSH_FPTYPE_SHA256) -END_ENUM_TYPE(fptype) - -/* - * cproxy.h already has a list macro mapping protocol-specified - * strings to the list of HTTP Digest hash functions. Rather than - * invent a separate one for testcrypt, reuse the existing names. - */ -BEGIN_ENUM_TYPE(httpdigesthash) - #define DECL_ARRAY(id, str, alg, bits) ENUM_VALUE(str, id) - HTTP_DIGEST_HASHES(DECL_ARRAY) - #undef DECL_ARRAY -END_ENUM_TYPE(httpdigesthash) diff --git a/testcrypt.c b/testcrypt.c deleted file mode 100644 index 68e2d11d..00000000 --- a/testcrypt.c +++ /dev/null @@ -1,1485 +0,0 @@ -/* - * testcrypt: a standalone test program that provides direct access to - * PuTTY's cryptography and mp_int code. - */ - -/* - * This program speaks a line-oriented protocol on standard input and - * standard output. It's a half-duplex protocol: it expects to read - * one line of command, and then produce a fixed amount of output - * (namely a line containing a decimal integer, followed by that many - * lines each containing one return value). - * - * The protocol is human-readable enough to make it debuggable, but - * verbose enough that you probably wouldn't want to speak it by hand - * at any great length. The Python program test/testcrypt.py wraps it - * to give a more useful user-facing API, by invoking this binary as a - * subprocess. - * - * (I decided that was a better idea than making this program an - * actual Python module, partly because you can rewrap the same binary - * in another scripting language if you prefer, but mostly because - * it's easy to attach a debugger to testcrypt or to run it under - * sanitisers or valgrind or what have you.) - */ - -#include -#include -#include -#include -#include - -#include "defs.h" -#include "ssh.h" -#include "sshkeygen.h" -#include "misc.h" -#include "mpint.h" -#include "crypto/ecc.h" -#include "proxy/cproxy.h" - -static NORETURN PRINTF_LIKE(1, 2) void fatal_error(const char *p, ...) -{ - va_list ap; - fprintf(stderr, "testcrypt: "); - va_start(ap, p); - vfprintf(stderr, p, ap); - va_end(ap); - fputc('\n', stderr); - exit(1); -} - -void out_of_memory(void) { fatal_error("out of memory"); } - -static bool old_keyfile_warning_given; -void old_keyfile_warning(void) { old_keyfile_warning_given = true; } - -static bufchain random_data_queue; -static prng *test_prng; -void random_read(void *buf, size_t size) -{ - if (test_prng) { - prng_read(test_prng, buf, size); - } else { - if (!bufchain_try_fetch_consume(&random_data_queue, buf, size)) - fatal_error("No random data in queue"); - } -} - -uint64_t prng_reseed_time_ms(void) -{ - static uint64_t previous_time = 0; - return previous_time += 200; -} - -#define VALUE_TYPES(X) \ - X(string, strbuf *, strbuf_free(v)) \ - X(mpint, mp_int *, mp_free(v)) \ - X(modsqrt, ModsqrtContext *, modsqrt_free(v)) \ - X(monty, MontyContext *, monty_free(v)) \ - X(wcurve, WeierstrassCurve *, ecc_weierstrass_curve_free(v)) \ - X(wpoint, WeierstrassPoint *, ecc_weierstrass_point_free(v)) \ - X(mcurve, MontgomeryCurve *, ecc_montgomery_curve_free(v)) \ - X(mpoint, MontgomeryPoint *, ecc_montgomery_point_free(v)) \ - X(ecurve, EdwardsCurve *, ecc_edwards_curve_free(v)) \ - X(epoint, EdwardsPoint *, ecc_edwards_point_free(v)) \ - X(hash, ssh_hash *, ssh_hash_free(v)) \ - X(key, ssh_key *, ssh_key_free(v)) \ - X(cipher, ssh_cipher *, ssh_cipher_free(v)) \ - X(mac, ssh2_mac *, ssh2_mac_free(v)) \ - X(dh, dh_ctx *, dh_cleanup(v)) \ - X(ecdh, ecdh_key *, ssh_ecdhkex_freekey(v)) \ - X(rsakex, RSAKey *, ssh_rsakex_freekey(v)) \ - X(rsa, RSAKey *, rsa_free(v)) \ - X(prng, prng *, prng_free(v)) \ - X(keycomponents, key_components *, key_components_free(v)) \ - X(pcs, PrimeCandidateSource *, pcs_free(v)) \ - X(pgc, PrimeGenerationContext *, primegen_free_context(v)) \ - X(pockle, Pockle *, pockle_free(v)) \ - X(millerrabin, MillerRabin *, miller_rabin_free(v)) \ - /* end of list */ - -typedef struct Value Value; - -enum ValueType { -#define VALTYPE_ENUM(n,t,f) VT_##n, - VALUE_TYPES(VALTYPE_ENUM) -#undef VALTYPE_ENUM -}; - -typedef enum ValueType ValueType; - -static const char *const type_names[] = { -#define VALTYPE_NAME(n,t,f) #n, - VALUE_TYPES(VALTYPE_NAME) -#undef VALTYPE_NAME -}; - -#define VALTYPE_TYPEDEF(n,t,f) \ - typedef t TD_val_##n; \ - typedef t *TD_out_val_##n; -VALUE_TYPES(VALTYPE_TYPEDEF) -#undef VALTYPE_TYPEDEF - -struct Value { - /* - * Protocol identifier assigned to this value when it was created. - * Lives in the same malloced block as this Value object itself. - */ - ptrlen id; - - /* - * Type of the value. - */ - ValueType type; - - /* - * Union of all the things it could hold. - */ - union { -#define VALTYPE_UNION(n,t,f) t vu_##n; - VALUE_TYPES(VALTYPE_UNION) -#undef VALTYPE_UNION - - char *bare_string; - }; -}; - -static int valuecmp(void *av, void *bv) -{ - Value *a = (Value *)av, *b = (Value *)bv; - return ptrlen_strcmp(a->id, b->id); -} - -static int valuefind(void *av, void *bv) -{ - ptrlen *a = (ptrlen *)av; - Value *b = (Value *)bv; - return ptrlen_strcmp(*a, b->id); -} - -static tree234 *values; - -static Value *value_new(ValueType vt) -{ - static uint64_t next_index = 0; - - char *name = dupprintf("%s%"PRIu64, type_names[vt], next_index++); - size_t namelen = strlen(name); - - Value *val = snew_plus(Value, namelen+1); - memcpy(snew_plus_get_aux(val), name, namelen+1); - val->id.ptr = snew_plus_get_aux(val); - val->id.len = namelen; - val->type = vt; - - Value *added = add234(values, val); - assert(added == val); - - sfree(name); - - return val; -} - -#define VALTYPE_RETURNFN(n,t,f) \ - void return_val_##n(strbuf *out, t v) { \ - Value *val = value_new(VT_##n); \ - val->vu_##n = v; \ - put_datapl(out, val->id); \ - put_byte(out, '\n'); \ - } -VALUE_TYPES(VALTYPE_RETURNFN) -#undef VALTYPE_RETURNFN - -static ptrlen get_word(BinarySource *in) -{ - ptrlen toret; - toret.ptr = get_ptr(in); - toret.len = 0; - while (get_avail(in) && get_byte(in) != ' ') - toret.len++; - return toret; -} - -typedef uintmax_t TD_uint; -typedef bool TD_boolean; -typedef ptrlen TD_val_string_ptrlen; -typedef char *TD_val_string_asciz; -typedef BinarySource *TD_val_string_binarysource; -typedef unsigned *TD_out_uint; -typedef BinarySink *TD_out_val_string_binarysink; -typedef const char *TD_opt_val_string_asciz; -typedef char **TD_out_val_string_asciz; -typedef char **TD_out_opt_val_string_asciz; -typedef const char **TD_out_opt_val_string_asciz_const; -typedef ssh_hash *TD_consumed_val_hash; -typedef const ssh_hashalg *TD_hashalg; -typedef const ssh2_macalg *TD_macalg; -typedef const ssh_keyalg *TD_keyalg; -typedef const ssh_cipheralg *TD_cipheralg; -typedef const ssh_kex *TD_dh_group; -typedef const ssh_kex *TD_ecdh_alg; -typedef RsaSsh1Order TD_rsaorder; -typedef key_components *TD_keycomponents; -typedef const PrimeGenerationPolicy *TD_primegenpolicy; -typedef struct mpint_list TD_mpint_list; -typedef PockleStatus TD_pocklestatus; -typedef struct mr_result TD_mr_result; -typedef Argon2Flavour TD_argon2flavour; -typedef FingerprintType TD_fptype; -typedef HttpDigestHash TD_httpdigesthash; - -#define BEGIN_ENUM_TYPE(name) \ - static bool enum_translate_##name(ptrlen valname, TD_##name *out) { \ - static const struct { \ - const char *key; \ - TD_##name value; \ - } mapping[] = { -#define ENUM_VALUE(name, value) {name, value}, -#define END_ENUM_TYPE(name) \ - }; \ - for (size_t i = 0; i < lenof(mapping); i++) \ - if (ptrlen_eq_string(valname, mapping[i].key)) { \ - if (out) \ - *out = mapping[i].value; \ - return true; \ - } \ - return false; \ - } \ - \ - static TD_##name get_##name(BinarySource *in) { \ - ptrlen valname = get_word(in); \ - TD_##name out; \ - if (enum_translate_##name(valname, &out)) \ - return out; \ - else \ - fatal_error("%s '%.*s': not found", \ - #name, PTRLEN_PRINTF(valname)); \ - } -#include "testcrypt-enum.h" -#undef BEGIN_ENUM_TYPE -#undef ENUM_VALUE -#undef END_ENUM_TYPE - -static uintmax_t get_uint(BinarySource *in) -{ - ptrlen word = get_word(in); - char *string = mkstr(word); - uintmax_t toret = strtoumax(string, NULL, 0); - sfree(string); - return toret; -} - -static bool get_boolean(BinarySource *in) -{ - return ptrlen_eq_string(get_word(in), "true"); -} - -static Value *lookup_value(ptrlen word) -{ - Value *val = find234(values, &word, valuefind); - if (!val) - fatal_error("id '%.*s': not found", PTRLEN_PRINTF(word)); - return val; -} - -static Value *get_value(BinarySource *in) -{ - return lookup_value(get_word(in)); -} - -typedef void (*finaliser_fn_t)(strbuf *out, void *ctx); -struct finaliser { - finaliser_fn_t fn; - void *ctx; -}; - -static struct finaliser *finalisers; -static size_t nfinalisers, finalisersize; - -static void add_finaliser(finaliser_fn_t fn, void *ctx) -{ - sgrowarray(finalisers, finalisersize, nfinalisers); - finalisers[nfinalisers].fn = fn; - finalisers[nfinalisers].ctx = ctx; - nfinalisers++; -} - -static void run_finalisers(strbuf *out) -{ - for (size_t i = 0; i < nfinalisers; i++) - finalisers[i].fn(out, finalisers[i].ctx); - nfinalisers = 0; -} - -static void finaliser_return_value(strbuf *out, void *ctx) -{ - Value *val = (Value *)ctx; - put_datapl(out, val->id); - put_byte(out, '\n'); -} - -static void finaliser_sfree(strbuf *out, void *ctx) -{ - sfree(ctx); -} - -#define VALTYPE_GETFN(n,t,f) \ - static Value *unwrap_value_##n(Value *val) { \ - ValueType expected = VT_##n; \ - if (expected != val->type) \ - fatal_error("id '%.*s': expected %s, got %s", \ - PTRLEN_PRINTF(val->id), \ - type_names[expected], type_names[val->type]); \ - return val; \ - } \ - static Value *get_value_##n(BinarySource *in) { \ - return unwrap_value_##n(get_value(in)); \ - } \ - static t get_val_##n(BinarySource *in) { \ - return get_value_##n(in)->vu_##n; \ - } -VALUE_TYPES(VALTYPE_GETFN) -#undef VALTYPE_GETFN - -static ptrlen get_val_string_ptrlen(BinarySource *in) -{ - return ptrlen_from_strbuf(get_val_string(in)); -} - -static char *get_val_string_asciz(BinarySource *in) -{ - return get_val_string(in)->s; -} - -static strbuf *get_opt_val_string(BinarySource *in); - -static char *get_opt_val_string_asciz(BinarySource *in) -{ - strbuf *sb = get_opt_val_string(in); - return sb ? sb->s : NULL; -} - -static mp_int **get_out_val_mpint(BinarySource *in) -{ - Value *val = value_new(VT_mpint); - add_finaliser(finaliser_return_value, val); - return &val->vu_mpint; -} - -struct mpint_list { - size_t n; - mp_int **integers; -}; - -static struct mpint_list get_mpint_list(BinarySource *in) -{ - size_t n = get_uint(in); - - struct mpint_list mpl; - mpl.n = n; - - mpl.integers = snewn(n, mp_int *); - for (size_t i = 0; i < n; i++) - mpl.integers[i] = get_val_mpint(in); - - add_finaliser(finaliser_sfree, mpl.integers); - return mpl; -} - -static void finaliser_return_uint(strbuf *out, void *ctx) -{ - unsigned *uval = (unsigned *)ctx; - put_fmt(out, "%u\n", *uval); - sfree(uval); -} - -static unsigned *get_out_uint(BinarySource *in) -{ - unsigned *uval = snew(unsigned); - add_finaliser(finaliser_return_uint, uval); - return uval; -} - -static BinarySink *get_out_val_string_binarysink(BinarySource *in) -{ - Value *val = value_new(VT_string); - val->vu_string = strbuf_new(); - add_finaliser(finaliser_return_value, val); - return BinarySink_UPCAST(val->vu_string); -} - -static void return_val_string_asciz_const(strbuf *out, const char *s); -static void return_val_string_asciz(strbuf *out, char *s); - -static void finaliser_return_opt_string_asciz(strbuf *out, void *ctx) -{ - char **valp = (char **)ctx; - char *val = *valp; - sfree(valp); - if (!val) - put_fmt(out, "NULL\n"); - else - return_val_string_asciz(out, val); -} - -static char **get_out_opt_val_string_asciz(BinarySource *in) -{ - char **valp = snew(char *); - *valp = NULL; - add_finaliser(finaliser_return_opt_string_asciz, valp); - return valp; -} - -static void finaliser_return_opt_string_asciz_const(strbuf *out, void *ctx) -{ - const char **valp = (const char **)ctx; - const char *val = *valp; - sfree(valp); - if (!val) - put_fmt(out, "NULL\n"); - else - return_val_string_asciz_const(out, val); -} - -static const char **get_out_opt_val_string_asciz_const(BinarySource *in) -{ - const char **valp = snew(const char *); - *valp = NULL; - add_finaliser(finaliser_return_opt_string_asciz_const, valp); - return valp; -} - -static BinarySource *get_val_string_binarysource(BinarySource *in) -{ - strbuf *sb = get_val_string(in); - BinarySource *src = snew(BinarySource); - BinarySource_BARE_INIT(src, sb->u, sb->len); - add_finaliser(finaliser_sfree, src); - return src; -} - -#define GET_CONSUMED_FN(type) \ - typedef TD_val_##type TD_consumed_val_##type; \ - static TD_val_##type get_consumed_val_##type(BinarySource *in) \ - { \ - Value *val = get_value_##type(in); \ - TD_val_##type toret = val->vu_##type; \ - del234(values, val); \ - sfree(val); \ - return toret; \ - } -GET_CONSUMED_FN(hash) -GET_CONSUMED_FN(pcs) - -static void return_int(strbuf *out, intmax_t u) -{ - put_fmt(out, "%"PRIdMAX"\n", u); -} - -static void return_uint(strbuf *out, uintmax_t u) -{ - put_fmt(out, "0x%"PRIXMAX"\n", u); -} - -static void return_boolean(strbuf *out, bool b) -{ - put_fmt(out, "%s\n", b ? "true" : "false"); -} - -static void return_pocklestatus(strbuf *out, PockleStatus status) -{ - switch (status) { - default: - put_fmt(out, "POCKLE_BAD_STATUS_VALUE\n"); - break; - -#define STATUS_CASE(id) \ - case id: \ - put_fmt(out, "%s\n", #id); \ - break; - - POCKLE_STATUSES(STATUS_CASE); - -#undef STATUS_CASE - - } -} - -static void return_mr_result(strbuf *out, struct mr_result result) -{ - if (!result.passed) - put_fmt(out, "failed\n"); - else if (!result.potential_primitive_root) - put_fmt(out, "passed\n"); - else - put_fmt(out, "passed+ppr\n"); -} - -static void return_val_string_asciz_const(strbuf *out, const char *s) -{ - strbuf *sb = strbuf_new(); - put_data(sb, s, strlen(s)); - return_val_string(out, sb); -} - -static void return_val_string_asciz(strbuf *out, char *s) -{ - return_val_string_asciz_const(out, s); - sfree(s); -} - -#define NULLABLE_RETURN_WRAPPER(type_name, c_type) \ - static void return_opt_##type_name(strbuf *out, c_type ptr) \ - { \ - if (!ptr) \ - put_fmt(out, "NULL\n"); \ - else \ - return_##type_name(out, ptr); \ - } - -NULLABLE_RETURN_WRAPPER(val_string, strbuf *) -NULLABLE_RETURN_WRAPPER(val_string_asciz, char *) -NULLABLE_RETURN_WRAPPER(val_string_asciz_const, const char *) -NULLABLE_RETURN_WRAPPER(val_cipher, ssh_cipher *) -NULLABLE_RETURN_WRAPPER(val_hash, ssh_hash *) -NULLABLE_RETURN_WRAPPER(val_key, ssh_key *) -NULLABLE_RETURN_WRAPPER(val_mpint, mp_int *) - -static void handle_hello(BinarySource *in, strbuf *out) -{ - put_fmt(out, "hello, world\n"); -} - -static void rsa_free(RSAKey *rsa) -{ - freersakey(rsa); - sfree(rsa); -} - -static void free_value(Value *val) -{ - switch (val->type) { -#define VALTYPE_FREE(n,t,f) case VT_##n: { t v = val->vu_##n; (f); break; } - VALUE_TYPES(VALTYPE_FREE) -#undef VALTYPE_FREE - } - sfree(val); -} - -static void handle_free(BinarySource *in, strbuf *out) -{ - Value *val = get_value(in); - del234(values, val); - free_value(val); -} - -static void handle_newstring(BinarySource *in, strbuf *out) -{ - strbuf *sb = strbuf_new(); - while (get_avail(in)) { - char c = get_byte(in); - if (c == '%') { - char hex[3]; - hex[0] = get_byte(in); - if (hex[0] != '%') { - hex[1] = get_byte(in); - hex[2] = '\0'; - c = strtoul(hex, NULL, 16); - } - } - put_byte(sb, c); - } - return_val_string(out, sb); -} - -static void handle_getstring(BinarySource *in, strbuf *out) -{ - strbuf *sb = get_val_string(in); - for (size_t i = 0; i < sb->len; i++) { - char c = sb->s[i]; - if (c > ' ' && c < 0x7F && c != '%') { - put_byte(out, c); - } else { - put_fmt(out, "%%%02X", 0xFFU & (unsigned)c); - } - } - put_byte(out, '\n'); -} - -static void handle_mp_literal(BinarySource *in, strbuf *out) -{ - ptrlen pl = get_word(in); - char *str = mkstr(pl); - mp_int *mp = mp__from_string_literal(str); - sfree(str); - return_val_mpint(out, mp); -} - -static void handle_mp_dump(BinarySource *in, strbuf *out) -{ - mp_int *mp = get_val_mpint(in); - for (size_t i = mp_max_bytes(mp); i-- > 0 ;) - put_fmt(out, "%02X", mp_get_byte(mp, i)); - put_byte(out, '\n'); -} - -static void handle_checkenum(BinarySource *in, strbuf *out) -{ - ptrlen type = get_word(in); - ptrlen value = get_word(in); - bool ok = false; - - #define BEGIN_ENUM_TYPE(name) \ - if (ptrlen_eq_string(type, #name)) \ - ok = enum_translate_##name(value, NULL); - #define ENUM_VALUE(name, value) - #define END_ENUM_TYPE(name) - #include "testcrypt-enum.h" - #undef BEGIN_ENUM_TYPE - #undef ENUM_VALUE - #undef END_ENUM_TYPE - - put_dataz(out, ok ? "ok\n" : "bad\n"); -} - -static void random_queue(ptrlen pl) -{ - bufchain_add(&random_data_queue, pl.ptr, pl.len); -} - -static size_t random_queue_len(void) -{ - return bufchain_size(&random_data_queue); -} - -static void random_clear(void) -{ - if (test_prng) { - prng_free(test_prng); - test_prng = NULL; - } - - bufchain_clear(&random_data_queue); -} - -static void random_make_prng(const ssh_hashalg *hashalg, ptrlen seed) -{ - random_clear(); - - test_prng = prng_new(hashalg); - prng_seed_begin(test_prng); - put_datapl(test_prng, seed); - prng_seed_finish(test_prng); -} - -mp_int *monty_identity_wrapper(MontyContext *mc) -{ - return mp_copy(monty_identity(mc)); -} - -mp_int *monty_modulus_wrapper(MontyContext *mc) -{ - return mp_copy(monty_modulus(mc)); -} - -strbuf *ssh_hash_digest_wrapper(ssh_hash *h) -{ - strbuf *sb = strbuf_new(); - void *p = strbuf_append(sb, ssh_hash_alg(h)->hlen); - ssh_hash_digest(h, p); - return sb; -} - -strbuf *ssh_hash_final_wrapper(ssh_hash *h) -{ - strbuf *sb = strbuf_new(); - void *p = strbuf_append(sb, ssh_hash_alg(h)->hlen); - ssh_hash_final(h, p); - return sb; -} - -void ssh_cipher_setiv_wrapper(ssh_cipher *c, ptrlen iv) -{ - if (iv.len != ssh_cipher_alg(c)->blksize) - fatal_error("ssh_cipher_setiv: needs exactly %d bytes", - ssh_cipher_alg(c)->blksize); - ssh_cipher_setiv(c, iv.ptr); -} - -void ssh_cipher_setkey_wrapper(ssh_cipher *c, ptrlen key) -{ - if (key.len != ssh_cipher_alg(c)->padded_keybytes) - fatal_error("ssh_cipher_setkey: needs exactly %d bytes", - ssh_cipher_alg(c)->padded_keybytes); - ssh_cipher_setkey(c, key.ptr); -} - -strbuf *ssh_cipher_encrypt_wrapper(ssh_cipher *c, ptrlen input) -{ - if (input.len % ssh_cipher_alg(c)->blksize) - fatal_error("ssh_cipher_encrypt: needs a multiple of %d bytes", - ssh_cipher_alg(c)->blksize); - strbuf *sb = strbuf_new(); - put_datapl(sb, input); - ssh_cipher_encrypt(c, sb->u, sb->len); - return sb; -} - -strbuf *ssh_cipher_decrypt_wrapper(ssh_cipher *c, ptrlen input) -{ - if (input.len % ssh_cipher_alg(c)->blksize) - fatal_error("ssh_cipher_decrypt: needs a multiple of %d bytes", - ssh_cipher_alg(c)->blksize); - strbuf *sb = strbuf_new(); - put_datapl(sb, input); - ssh_cipher_decrypt(c, sb->u, sb->len); - return sb; -} - -strbuf *ssh_cipher_encrypt_length_wrapper(ssh_cipher *c, ptrlen input, - unsigned long seq) -{ - if (input.len != 4) - fatal_error("ssh_cipher_encrypt_length: needs exactly 4 bytes"); - strbuf *sb = strbuf_new(); - put_datapl(sb, input); - ssh_cipher_encrypt_length(c, sb->u, sb->len, seq); - return sb; -} - -strbuf *ssh_cipher_decrypt_length_wrapper(ssh_cipher *c, ptrlen input, - unsigned long seq) -{ - if (input.len % ssh_cipher_alg(c)->blksize) - fatal_error("ssh_cipher_decrypt_length: needs exactly 4 bytes"); - strbuf *sb = strbuf_new(); - put_datapl(sb, input); - ssh_cipher_decrypt_length(c, sb->u, sb->len, seq); - return sb; -} - -strbuf *ssh2_mac_genresult_wrapper(ssh2_mac *m) -{ - strbuf *sb = strbuf_new(); - void *u = strbuf_append(sb, ssh2_mac_alg(m)->len); - ssh2_mac_genresult(m, u); - return sb; -} - -bool dh_validate_f_wrapper(dh_ctx *dh, mp_int *f) -{ - return dh_validate_f(dh, f) == NULL; -} - -void ssh_hash_update(ssh_hash *h, ptrlen pl) -{ - put_datapl(h, pl); -} - -void ssh2_mac_update(ssh2_mac *m, ptrlen pl) -{ - put_datapl(m, pl); -} - -static RSAKey *rsa_new(void) -{ - RSAKey *rsa = snew(RSAKey); - memset(rsa, 0, sizeof(RSAKey)); - return rsa; -} - -strbuf *rsa_ssh1_encrypt_wrapper(ptrlen input, RSAKey *key) -{ - /* Fold the boolean return value in C into the string return value - * for this purpose, by returning NULL on failure */ - strbuf *sb = strbuf_new(); - put_datapl(sb, input); - put_padding(sb, key->bytes - input.len, 0); - if (!rsa_ssh1_encrypt(sb->u, input.len, key)) { - strbuf_free(sb); - return NULL; - } - return sb; -} - -strbuf *rsa_ssh1_decrypt_pkcs1_wrapper(mp_int *input, RSAKey *key) -{ - /* Again, return "" on failure */ - strbuf *sb = strbuf_new(); - if (!rsa_ssh1_decrypt_pkcs1(input, key, sb)) - strbuf_clear(sb); - return sb; -} - -strbuf *des_encrypt_xdmauth_wrapper(ptrlen key, ptrlen data) -{ - if (key.len != 7) - fatal_error("des_encrypt_xdmauth: key must be 7 bytes long"); - if (data.len % 8 != 0) - fatal_error("des_encrypt_xdmauth: data must be a multiple of 8 bytes"); - strbuf *sb = strbuf_new(); - put_datapl(sb, data); - des_encrypt_xdmauth(key.ptr, sb->u, sb->len); - return sb; -} - -strbuf *des_decrypt_xdmauth_wrapper(ptrlen key, ptrlen data) -{ - if (key.len != 7) - fatal_error("des_decrypt_xdmauth: key must be 7 bytes long"); - if (data.len % 8 != 0) - fatal_error("des_decrypt_xdmauth: data must be a multiple of 8 bytes"); - strbuf *sb = strbuf_new(); - put_datapl(sb, data); - des_decrypt_xdmauth(key.ptr, sb->u, sb->len); - return sb; -} - -strbuf *des3_encrypt_pubkey_wrapper(ptrlen key, ptrlen data) -{ - if (key.len != 16) - fatal_error("des3_encrypt_pubkey: key must be 16 bytes long"); - if (data.len % 8 != 0) - fatal_error("des3_encrypt_pubkey: data must be a multiple of 8 bytes"); - strbuf *sb = strbuf_new(); - put_datapl(sb, data); - des3_encrypt_pubkey(key.ptr, sb->u, sb->len); - return sb; -} - -strbuf *des3_decrypt_pubkey_wrapper(ptrlen key, ptrlen data) -{ - if (key.len != 16) - fatal_error("des3_decrypt_pubkey: key must be 16 bytes long"); - if (data.len % 8 != 0) - fatal_error("des3_decrypt_pubkey: data must be a multiple of 8 bytes"); - strbuf *sb = strbuf_new(); - put_datapl(sb, data); - des3_decrypt_pubkey(key.ptr, sb->u, sb->len); - return sb; -} - -strbuf *des3_encrypt_pubkey_ossh_wrapper(ptrlen key, ptrlen iv, ptrlen data) -{ - if (key.len != 24) - fatal_error("des3_encrypt_pubkey_ossh: key must be 24 bytes long"); - if (iv.len != 8) - fatal_error("des3_encrypt_pubkey_ossh: iv must be 8 bytes long"); - if (data.len % 8 != 0) - fatal_error("des3_encrypt_pubkey_ossh: data must be a multiple of 8 bytes"); - strbuf *sb = strbuf_new(); - put_datapl(sb, data); - des3_encrypt_pubkey_ossh(key.ptr, iv.ptr, sb->u, sb->len); - return sb; -} - -strbuf *des3_decrypt_pubkey_ossh_wrapper(ptrlen key, ptrlen iv, ptrlen data) -{ - if (key.len != 24) - fatal_error("des3_decrypt_pubkey_ossh: key must be 24 bytes long"); - if (iv.len != 8) - fatal_error("des3_encrypt_pubkey_ossh: iv must be 8 bytes long"); - if (data.len % 8 != 0) - fatal_error("des3_decrypt_pubkey_ossh: data must be a multiple of 8 bytes"); - strbuf *sb = strbuf_new(); - put_datapl(sb, data); - des3_decrypt_pubkey_ossh(key.ptr, iv.ptr, sb->u, sb->len); - return sb; -} - -strbuf *aes256_encrypt_pubkey_wrapper(ptrlen key, ptrlen iv, ptrlen data) -{ - if (key.len != 32) - fatal_error("aes256_encrypt_pubkey: key must be 32 bytes long"); - if (iv.len != 16) - fatal_error("aes256_encrypt_pubkey: iv must be 16 bytes long"); - if (data.len % 16 != 0) - fatal_error("aes256_encrypt_pubkey: data must be a multiple of 16 bytes"); - strbuf *sb = strbuf_new(); - put_datapl(sb, data); - aes256_encrypt_pubkey(key.ptr, iv.ptr, sb->u, sb->len); - return sb; -} - -strbuf *aes256_decrypt_pubkey_wrapper(ptrlen key, ptrlen iv, ptrlen data) -{ - if (key.len != 32) - fatal_error("aes256_decrypt_pubkey: key must be 32 bytes long"); - if (iv.len != 16) - fatal_error("aes256_encrypt_pubkey: iv must be 16 bytes long"); - if (data.len % 16 != 0) - fatal_error("aes256_decrypt_pubkey: data must be a multiple of 16 bytes"); - strbuf *sb = strbuf_new(); - put_datapl(sb, data); - aes256_decrypt_pubkey(key.ptr, iv.ptr, sb->u, sb->len); - return sb; -} - -strbuf *prng_read_wrapper(prng *pr, size_t size) -{ - strbuf *sb = strbuf_new(); - prng_read(pr, strbuf_append(sb, size), size); - return sb; -} - -void prng_seed_update(prng *pr, ptrlen data) -{ - put_datapl(pr, data); -} - -bool crcda_detect(ptrlen packet, ptrlen iv) -{ - if (iv.len != 0 && iv.len != 8) - fatal_error("crcda_detect: iv must be empty or 8 bytes long"); - if (packet.len % 8 != 0) - fatal_error("crcda_detect: packet must be a multiple of 8 bytes"); - struct crcda_ctx *ctx = crcda_make_context(); - bool toret = detect_attack(ctx, packet.ptr, packet.len, - iv.len ? iv.ptr : NULL); - crcda_free_context(ctx); - return toret; -} - -ssh_key *ppk_load_s_wrapper(BinarySource *src, char **comment, - const char *passphrase, const char **errorstr) -{ - ssh2_userkey *uk = ppk_load_s(src, passphrase, errorstr); - if (uk == SSH2_WRONG_PASSPHRASE) { - /* Fudge this special return value */ - *errorstr = "SSH2_WRONG_PASSPHRASE"; - return NULL; - } - if (uk == NULL) - return NULL; - ssh_key *toret = uk->key; - *comment = uk->comment; - sfree(uk); - return toret; -} - -int rsa1_load_s_wrapper(BinarySource *src, RSAKey *rsa, char **comment, - const char *passphrase, const char **errorstr) -{ - int toret = rsa1_load_s(src, rsa, passphrase, errorstr); - *comment = rsa->comment; - rsa->comment = NULL; - return toret; -} - -strbuf *ppk_save_sb_wrapper( - ssh_key *key, const char *comment, const char *passphrase, - unsigned fmt_version, Argon2Flavour flavour, - uint32_t mem, uint32_t passes, uint32_t parallel) -{ - /* - * For repeatable testing purposes, we never want a timing-dependent - * choice of password hashing parameters, so this is easy. - */ - ppk_save_parameters save_params; - memset(&save_params, 0, sizeof(save_params)); - save_params.fmt_version = fmt_version; - save_params.argon2_flavour = flavour; - save_params.argon2_mem = mem; - save_params.argon2_passes_auto = false; - save_params.argon2_passes = passes; - save_params.argon2_parallelism = parallel; - - ssh2_userkey uk; - uk.key = key; - uk.comment = dupstr(comment); - strbuf *toret = ppk_save_sb(&uk, passphrase, &save_params); - sfree(uk.comment); - return toret; -} - -strbuf *rsa1_save_sb_wrapper(RSAKey *key, const char *comment, - const char *passphrase) -{ - key->comment = dupstr(comment); - strbuf *toret = rsa1_save_sb(key, passphrase); - sfree(key->comment); - key->comment = NULL; - return toret; -} - -#define return_void(out, expression) (expression) - -static ProgressReceiver null_progress = { .vt = &null_progress_vt }; - -mp_int *primegen_generate_wrapper( - PrimeGenerationContext *ctx, PrimeCandidateSource *pcs) -{ - return primegen_generate(ctx, pcs, &null_progress); -} - -RSAKey *rsa1_generate(int bits, bool strong, PrimeGenerationContext *pgc) -{ - RSAKey *rsakey = snew(RSAKey); - rsa_generate(rsakey, bits, strong, pgc, &null_progress); - rsakey->comment = NULL; - return rsakey; -} - -ssh_key *rsa_generate_wrapper(int bits, bool strong, - PrimeGenerationContext *pgc) -{ - return &rsa1_generate(bits, strong, pgc)->sshk; -} - -ssh_key *dsa_generate_wrapper(int bits, PrimeGenerationContext *pgc) -{ - struct dsa_key *dsakey = snew(struct dsa_key); - dsa_generate(dsakey, bits, pgc, &null_progress); - return &dsakey->sshk; -} - -ssh_key *ecdsa_generate_wrapper(int bits) -{ - struct ecdsa_key *ek = snew(struct ecdsa_key); - if (!ecdsa_generate(ek, bits)) { - sfree(ek); - return NULL; - } - return &ek->sshk; -} - -ssh_key *eddsa_generate_wrapper(int bits) -{ - struct eddsa_key *ek = snew(struct eddsa_key); - if (!eddsa_generate(ek, bits)) { - sfree(ek); - return NULL; - } - return &ek->sshk; -} - -size_t key_components_count(key_components *kc) { return kc->ncomponents; } -const char *key_components_nth_name(key_components *kc, size_t n) -{ - return (n >= kc->ncomponents ? NULL : - kc->components[n].name); -} -const char *key_components_nth_str(key_components *kc, size_t n) -{ - return (n >= kc->ncomponents ? NULL : - kc->components[n].is_mp_int ? NULL : - kc->components[n].text); -} -mp_int *key_components_nth_mp(key_components *kc, size_t n) -{ - return (n >= kc->ncomponents ? NULL : - !kc->components[n].is_mp_int ? NULL : - mp_copy(kc->components[n].mp)); -} - -PockleStatus pockle_add_prime_wrapper(Pockle *pockle, mp_int *p, - struct mpint_list mpl, mp_int *witness) -{ - return pockle_add_prime(pockle, p, mpl.integers, mpl.n, witness); -} - -strbuf *argon2_wrapper(Argon2Flavour flavour, uint32_t mem, uint32_t passes, - uint32_t parallel, uint32_t taglen, - ptrlen P, ptrlen S, ptrlen K, ptrlen X) -{ - strbuf *out = strbuf_new(); - argon2(flavour, mem, passes, parallel, taglen, P, S, K, X, out); - return out; -} - -strbuf *get_implementations_commasep(ptrlen alg) -{ - strbuf *out = strbuf_new(); - put_datapl(out, alg); - - if (ptrlen_startswith(alg, PTRLEN_LITERAL("aes"), NULL)) { - put_fmt(out, ",%.*s_sw", PTRLEN_PRINTF(alg)); -#if HAVE_AES_NI - put_fmt(out, ",%.*s_ni", PTRLEN_PRINTF(alg)); -#endif -#if HAVE_NEON_CRYPTO - put_fmt(out, ",%.*s_neon", PTRLEN_PRINTF(alg)); -#endif - } else if (ptrlen_startswith(alg, PTRLEN_LITERAL("sha256"), NULL) || - ptrlen_startswith(alg, PTRLEN_LITERAL("sha1"), NULL)) { - put_fmt(out, ",%.*s_sw", PTRLEN_PRINTF(alg)); -#if HAVE_SHA_NI - put_fmt(out, ",%.*s_ni", PTRLEN_PRINTF(alg)); -#endif -#if HAVE_NEON_CRYPTO - put_fmt(out, ",%.*s_neon", PTRLEN_PRINTF(alg)); -#endif - } else if (ptrlen_startswith(alg, PTRLEN_LITERAL("sha512"), NULL)) { - put_fmt(out, ",%.*s_sw", PTRLEN_PRINTF(alg)); -#if HAVE_NEON_SHA512 - put_fmt(out, ",%.*s_neon", PTRLEN_PRINTF(alg)); -#endif - } - - return out; -} - -#define OPTIONAL_PTR_FUNC(type) \ - typedef TD_val_##type TD_opt_val_##type; \ - static TD_opt_val_##type get_opt_val_##type(BinarySource *in) { \ - ptrlen word = get_word(in); \ - if (ptrlen_eq_string(word, "NULL")) \ - return NULL; \ - return unwrap_value_##type(lookup_value(word))->vu_##type; \ - } -OPTIONAL_PTR_FUNC(cipher) -OPTIONAL_PTR_FUNC(mpint) -OPTIONAL_PTR_FUNC(string) - -/* - * HERE BE DRAGONS: the horrible C preprocessor business that reads - * testcrypt.h and generates a marshalling wrapper for each exported - * function. - * - * In an ideal world, we would start from a specification like this in - * testcrypt.h - * - * FUNC(val_foo, example, (ARG(val_bar, bar), ARG(uint, n))) - * - * and generate a wrapper function looking like this: - * - * static void handle_example(BinarySource *in, strbuf *out) { - * TD_val_bar bar = get_val_bar(in); - * TD_uint n = get_uint(in); - * return_val_foo(out, example(bar, n)); - * } - * - * which would read the marshalled form of each function argument in - * turn from the input BinarySource via the get_() function - * family defined in this file; assign each argument to a local - * variable; call the underlying C function with all those arguments; - * and then call a function of the return_() family to marshal - * the output value into the output strbuf to be sent to standard - * output. - * - * With a more general macro processor such as m4, or custom code in - * Perl or Python, or a helper program like llvm-tblgen, we could just - * do that directly, reading function specifications from testcrypt.h - * and writing out exactly the above. But we don't have a fully - * general macro processor (since everything in that category - * introduces an extra build dependency that's awkward on plain - * Windows, or requires compiling and running a helper program which - * is awkward in a cross-compile). We only have cpp. And in cpp, a - * macro can't expand one of its arguments differently in two parts of - * its own expansion. So we have to be more clever. - * - * In place of the above code, I instead generate three successive - * declarations for each function. In simplified form they would look - * like this: - * - * typedef struct ARGS_example { - * TD_val_bar bar; - * TD_uint n; - * } ARGS_example; - * - * static inline ARGS_example get_args_example(BinarySource *in) { - * ARGS_example args; - * args.bar = get_val_bar(in); - * args.n = get_uint(in); - * return args; - * } - * - * static void handle_example(BinarySource *in, strbuf *out) { - * ARGS_example args = get_args_example(in); - * return_val_foo(out, example(args.bar, args.n)); - * } - * - * Each of these mentions the arguments and their types just _once_, - * so each one can be generated by a single expansion of the FUNC(...) - * specification in testcrypt.h, with FUNC and ARG and VOID defined to - * appropriate values. - * - * Or ... *nearly*. In fact, I left out several details there, but - * it's a good starting point to understand the full version. - * - * To begin with, several of the variable names shown above are - * actually named with an ugly leading underscore, to minimise the - * chance of them colliding with real parameter names. (You could - * easily imagine 'out' being the name of a parameter to one of the - * wrapped functions.) Also, we memset the whole structure to zero at - * the start of get_args_example() to avoid compiler warnings about - * uninitialised stuff, and insert a precautionary '(void)args;' in - * handle_example to avoid a similar warning about _unused_ stuff. - * - * The big problem is the commas that have to appear between arguments - * in the final call to the actual C function. Those can't be - * generated by expanding the ARG macro itself, or you'd get one too - * many - either a leading comma or a trailing comma. Trailing commas - * are legal in a Python function call, but unfortunately C is not yet - * so enlightened. (C permits a trailing comma in a struct or array - * initialiser, and is coming round to it in enums, but hasn't yet - * seen the light about function calls or function prototypes.) - * - * So the commas must appear _between_ ARG(...) specifiers. And that - * means they unavoidably appear in _every_ expansion of FUNC() (or - * rather, every expansion that uses the argument list at all). - * Therefore, we need to ensure they're harmless in the other two - * functions as well. - * - * In the get_args_example() function above, there's no real problem. - * The list of assignments can perfectly well be separated by commas - * instead of semicolons, so that it becomes a single expression- - * statement instead of a sequence of them; the comma operator still - * defines a sequence point, so it's fine. - * - * But what about the structure definition of ARGS_example? - * - * To get round that, we fill the structure with pointless extra - * cruft, in the form of an extra 'int' field before and after each - * actually useful argument field. So the real structure definition - * ends up looking more like this: - * - * typedef struct ARGS_example { - * int _predummy_bar; - * TD_val_bar bar; - * int _postdummy_bar, _predummy_n; - * TD_uint n; - * int _postdummy_n; - * } ARGS_example; - * - * Those extra 'int' fields are ignored completely at run time. They - * might cause a runtime space cost if the struct doesn't get - * completely optimised away when get_args_example is inlined into - * handle_example, but even if so, that's OK, this is a test program - * whose memory usage isn't critical. The real point is that, in - * between each pair of real arguments, there's a declaration - * containing *two* int variables, and in between them is the vital - * comma that we need! - * - * So in that pass through testcrypt.h, the ARG(type, name) macro has - * to expand to the weird piece of text - * - * _predummy_name; // terminating the previous int declaration - * TD_type name; // declaring the thing we actually wanted - * int _postdummy_name // new declaration ready to see a comma - * - * so that a comma-separated list of pieces of expansion like that - * will fall into just the right form to be the core of the above - * expanded structure definition. Then we just need to put in the - * 'int' after the open brace, and the ';' before the closing brace, - * and we've got everything we need to make it all syntactically legal. - * - * Other points of note: - * - * Why the extra pair of parens around the whole argument list? You'd - * like to think that FUNC could be a variadic macro, and just use - * __VA_ARGS__ to expand all the arguments wherever they're needed. - * But unfortunately there's a portability consideration: some of the - * 'functions' wrapped by this system are actually macros in turn, and - * if you use __VA_ARGS__ to expand multiple arguments from one macro - * into the argument list of another macro, compilers disagree on what - * happens: Visual Studio in particular will turn __VA_ARGS__ into - * just one argument instead of multiple ones. That is, if you do this: - * - * #define DESTINATION_MACRO(a, b) ... stuff using a and b ... - * #define WRAPPER(...) DESTINATION_MACRO(__VA_ARGS__) - * WRAPPER(1, 2) - * - * then most compilers will behave as if you'd called - * DESTINATION_MACRO with 'a' expanding to 1 and 'b' expanding to 2. - * But Visual Studio will consider that you called it with 'a' - * expanding to the whole of __VA_ARGS__ - that is, the token sequence - * '1 , 2' - and will expand 'b' to nothing at all! - * - * So we have a constraint that if ARGS is going to be turned into the - * argument list to the actual called function - as it is in the final - * handle_example() expansion shown above - then the commas can't come - * from a variadic expansion of __VA_ARGS__. Hence, FUNC can't _be_ a - * variadic macro. Instead, we wrap all the arguments in an extra pair - * of parens, and generate the final call not by saying function(args) - * but by saying just 'function args', since 'args' contains the - * parens already. - * - * In get_args_example(), that's still fine, because our giant - * comma-separated expression containing multiple assignment - * subexpressions can legally be wrapped in parentheses as well. But - * what do you do in the structure definition? - * - * Answer: _there_ we use a variadic macro to strip off the outer - * parens, by expanding to just __VA_ARGS__. That's OK even in Visual - * Studio, because in this particular context, __VA_ARGS__ is ending - * up in a structure definition and definitely _not_ in the argument - * list of another macro. - * - * Finally, what if a wrapped function has _no_ arguments? Two out of - * three uses of the argument list here need some kind of special case - * for that. That's why you have to write 'VOID' explicitly in an - * empty argument list in testcrypt.h: we make VOID expand to whatever - * is needed to avoid a syntax error in that special case. - */ - -#define DEPARENTHESISE(...) __VA_ARGS__ - -#define FUNC(outtype, fname, args) \ - FUNC_INNER(outtype, fname, fname, args) -#define FUNC_WRAPPED(outtype, fname, args) \ - FUNC_INNER(outtype, fname, fname##_wrapper, args) - -#define ARG(type, arg) _predummy_##arg; TD_##type arg; int _postdummy_##arg -#define VOID _voiddummy -#define FUNC_INNER(outtype, fname, realname, args) \ - typedef struct ARGS_##fname { \ - int DEPARENTHESISE args; \ - } ARGS_##fname; -#include "testcrypt.h" -#undef FUNC_INNER -#undef ARG -#undef VOID - -#define ARG(type, arg) _args.arg = get_##type(_in) -#define VOID ((void)0) -#define FUNC_INNER(outtype, fname, realname, args) \ - static inline ARGS_##fname get_args_##fname(BinarySource *_in) { \ - ARGS_##fname _args; \ - memset(&_args, 0, sizeof(_args)); \ - args; \ - return _args; \ - } -#include "testcrypt.h" -#undef FUNC_INNER -#undef ARG -#undef VOID - -#define ARG(type, arg) _args.arg -#define VOID -#define FUNC_INNER(outtype, fname, realname, args) \ - static void handle_##fname(BinarySource *_in, strbuf *_out) { \ - ARGS_##fname _args = get_args_##fname(_in); \ - (void)_args; /* suppress warning if no actual arguments */ \ - return_##outtype(_out, realname args); \ - } -#include "testcrypt.h" -#undef FUNC_INNER -#undef ARG - -static void process_line(BinarySource *in, strbuf *out) -{ - ptrlen id = get_word(in); - -#define DISPATCH_INTERNAL(cmdname, handler) do { \ - if (ptrlen_eq_string(id, cmdname)) { \ - handler(in, out); \ - return; \ - } \ - } while (0) - -#define DISPATCH_COMMAND(cmd) DISPATCH_INTERNAL(#cmd, handle_##cmd) - DISPATCH_COMMAND(hello); - DISPATCH_COMMAND(free); - DISPATCH_COMMAND(newstring); - DISPATCH_COMMAND(getstring); - DISPATCH_COMMAND(mp_literal); - DISPATCH_COMMAND(mp_dump); - DISPATCH_COMMAND(checkenum); -#undef DISPATCH_COMMAND - -#define FUNC_INNER(outtype, fname, realname, args) \ - DISPATCH_INTERNAL(#fname,handle_##fname); -#define ARG1(type, arg) -#define ARGN(type, arg) -#define VOID -#include "testcrypt.h" -#undef FUNC_INNER -#undef ARG -#undef VOID - -#undef DISPATCH_INTERNAL - - fatal_error("command '%.*s': unrecognised", PTRLEN_PRINTF(id)); -} - -static void free_all_values(void) -{ - for (Value *val; (val = delpos234(values, 0)) != NULL ;) - free_value(val); - freetree234(values); -} - -void dputs(const char *buf) -{ - fputs(buf, stderr); -} - -int main(int argc, char **argv) -{ - const char *infile = NULL, *outfile = NULL; - bool doing_opts = true; - - while (--argc > 0) { - char *p = *++argv; - - if (p[0] == '-' && doing_opts) { - if (!strcmp(p, "-o")) { - if (--argc <= 0) { - fprintf(stderr, "'-o' expects a filename\n"); - return 1; - } - outfile = *++argv; - } else if (!strcmp(p, "--")) { - doing_opts = false; - } else if (!strcmp(p, "--help")) { - printf("usage: testcrypt [INFILE] [-o OUTFILE]\n"); - printf(" also: testcrypt --help display this text\n"); - return 0; - } else { - fprintf(stderr, "unknown command line option '%s'\n", p); - return 1; - } - } else if (!infile) { - infile = p; - } else { - fprintf(stderr, "can only handle one input file name\n"); - return 1; - } - } - - FILE *infp = stdin; - if (infile) { - infp = fopen(infile, "r"); - if (!infp) { - fprintf(stderr, "%s: open: %s\n", infile, strerror(errno)); - return 1; - } - } - - FILE *outfp = stdout; - if (outfile) { - outfp = fopen(outfile, "w"); - if (!outfp) { - fprintf(stderr, "%s: open: %s\n", outfile, strerror(errno)); - return 1; - } - } - - values = newtree234(valuecmp); - - atexit(free_all_values); - - for (char *line; (line = chomp(fgetline(infp))) != NULL ;) { - BinarySource src[1]; - BinarySource_BARE_INIT(src, line, strlen(line)); - strbuf *sb = strbuf_new(); - process_line(src, sb); - run_finalisers(sb); - size_t lines = 0; - for (size_t i = 0; i < sb->len; i++) - if (sb->s[i] == '\n') - lines++; - fprintf(outfp, "%"SIZEu"\n%s", lines, sb->s); - fflush(outfp); - strbuf_free(sb); - sfree(line); - } - - if (infp != stdin) - fclose(infp); - if (outfp != stdin) - fclose(outfp); - - return 0; -} diff --git a/testcrypt.h b/testcrypt.h deleted file mode 100644 index 0da41826..00000000 --- a/testcrypt.h +++ /dev/null @@ -1,562 +0,0 @@ -/* - * List of functions exported by the 'testcrypt' system to provide a - * Python API for running unit tests and auxiliary programs. - * - * Each function definition in this file has the form - * - * FUNC(return-type, function-name, (arguments)) - * - * where 'arguments' in turn is either VOID, or a comma-separated list - * of argument specifications of the form - * - * ARG(argument-type, argument-name) - * - * Type names are always single identifiers, and they have some - * standard prefixes: - * - * 'val_' means that the type refers to something dynamically - * allocated, so that it has a persistent identity, needs to be freed - * when finished with (though this is done automatically by the - * testcrypt.py system via Python's reference counting), and may also - * be mutable. The argument type in C will be a pointer; in Python the - * corresponding argument will be an instance of a 'Value' object - * defined in testcrypt.py. - * - * 'opt_val_' is a modification of 'val_' to indicate that the pointer - * may be NULL. In Python this is translated by accepting (or - * returning) None as an alternative to a Value. - * - * 'out_' on an argument type indicates an additional output - * parameter. The argument type in C has an extra layer of - * indirection, e.g. an 'out_val_mpint' is an 'mpint **' instead of an - * 'mpint *', identifying a pointer variable where the returned - * pointer value will be written. In the Python API, these arguments - * do not appear in the argument list of the Python function; instead - * they cause the return value to become a tuple, with additional - * types appended. For example, a declaration like - * - * FUNC(val_foo, example, (ARG(out_val_bar, bar), ARG(val_baz, baz))) - * - * would identify a function in C with the following prototype, which - * returns a 'foo *' directly and a 'bar *' by writing it through the - * provided 'bar **' pointer argument: - * - * foo *example(bar **extra_output, baz *input); - * - * and in Python this would become a function taking one argument of - * type 'baz' and returning a tuple of the form (foo, bar). - * - * 'out_' and 'opt_' can go together, if a function returns a second - * output value but it may in some cases be NULL. - * - * 'consumed_' on an argument type indicates that the C function - * receiving that argument frees it as a side effect. - * - * Any argument type which does not start 'val_' is plain old data - * with no dynamic allocation requirements. Ordinary C integers are - * sometimes handled this way (e.g. 'uint'). Other plain-data types - * are represented in Python as a string that must be one of a - * recognised set of keywords; in C these variously translate into - * enumeration types (e.g. argon2flavour, rsaorder) or pointers to - * const vtables of one kind or another (e.g. keyalg, hashalg, - * primegenpolicy). - * - * If a function definition begins with FUNC_WRAPPED rather than FUNC, - * it means that the underlying C function has a suffix "_wrapper", - * e.g. ssh_cipher_setiv_wrapper(). Those wrappers are defined in - * testcrypt.c itself, and change the API or semantics in a way that - * makes the function more Python-friendly. - */ - -/* - * mpint.h functions. - */ -FUNC(val_mpint, mp_new, (ARG(uint, maxbits))) -FUNC(void, mp_clear, (ARG(val_mpint, x))) -FUNC(val_mpint, mp_from_bytes_le, (ARG(val_string_ptrlen, bytes))) -FUNC(val_mpint, mp_from_bytes_be, (ARG(val_string_ptrlen, bytes))) -FUNC(val_mpint, mp_from_integer, (ARG(uint, n))) -FUNC(val_mpint, mp_from_decimal_pl, (ARG(val_string_ptrlen, decimal))) -FUNC(val_mpint, mp_from_decimal, (ARG(val_string_asciz, decimal))) -FUNC(val_mpint, mp_from_hex_pl, (ARG(val_string_ptrlen, hex))) -FUNC(val_mpint, mp_from_hex, (ARG(val_string_asciz, hex))) -FUNC(val_mpint, mp_copy, (ARG(val_mpint, x))) -FUNC(val_mpint, mp_power_2, (ARG(uint, power))) -FUNC(uint, mp_get_byte, (ARG(val_mpint, x), ARG(uint, byte))) -FUNC(uint, mp_get_bit, (ARG(val_mpint, x), ARG(uint, bit))) -FUNC(void, mp_set_bit, (ARG(val_mpint, x), ARG(uint, bit), ARG(uint, val))) -FUNC(uint, mp_max_bytes, (ARG(val_mpint, x))) -FUNC(uint, mp_max_bits, (ARG(val_mpint, x))) -FUNC(uint, mp_get_nbits, (ARG(val_mpint, x))) -FUNC(val_string_asciz, mp_get_decimal, (ARG(val_mpint, x))) -FUNC(val_string_asciz, mp_get_hex, (ARG(val_mpint, x))) -FUNC(val_string_asciz, mp_get_hex_uppercase, (ARG(val_mpint, x))) -FUNC(uint, mp_cmp_hs, (ARG(val_mpint, a), ARG(val_mpint, b))) -FUNC(uint, mp_cmp_eq, (ARG(val_mpint, a), ARG(val_mpint, b))) -FUNC(uint, mp_hs_integer, (ARG(val_mpint, x), ARG(uint, n))) -FUNC(uint, mp_eq_integer, (ARG(val_mpint, x), ARG(uint, n))) -FUNC(void, mp_min_into, - (ARG(val_mpint, dest), ARG(val_mpint, x), ARG(val_mpint, y))) -FUNC(void, mp_max_into, - (ARG(val_mpint, dest), ARG(val_mpint, x), ARG(val_mpint, y))) -FUNC(val_mpint, mp_min, (ARG(val_mpint, x), ARG(val_mpint, y))) -FUNC(val_mpint, mp_max, (ARG(val_mpint, x), ARG(val_mpint, y))) -FUNC(void, mp_copy_into, (ARG(val_mpint, dest), ARG(val_mpint, src))) -FUNC(void, mp_select_into, - (ARG(val_mpint, dest), ARG(val_mpint, src0), ARG(val_mpint, src1), - ARG(uint, choose_src1))) -FUNC(void, mp_add_into, - (ARG(val_mpint, dest), ARG(val_mpint, a), ARG(val_mpint, b))) -FUNC(void, mp_sub_into, - (ARG(val_mpint, dest), ARG(val_mpint, a), ARG(val_mpint, b))) -FUNC(void, mp_mul_into, - (ARG(val_mpint, dest), ARG(val_mpint, a), ARG(val_mpint, b))) -FUNC(val_mpint, mp_add, (ARG(val_mpint, x), ARG(val_mpint, y))) -FUNC(val_mpint, mp_sub, (ARG(val_mpint, x), ARG(val_mpint, y))) -FUNC(val_mpint, mp_mul, (ARG(val_mpint, x), ARG(val_mpint, y))) -FUNC(void, mp_and_into, - (ARG(val_mpint, dest), ARG(val_mpint, a), ARG(val_mpint, b))) -FUNC(void, mp_or_into, - (ARG(val_mpint, dest), ARG(val_mpint, a), ARG(val_mpint, b))) -FUNC(void, mp_xor_into, - (ARG(val_mpint, dest), ARG(val_mpint, a), ARG(val_mpint, b))) -FUNC(void, mp_bic_into, - (ARG(val_mpint, dest), ARG(val_mpint, a), ARG(val_mpint, b))) -FUNC(void, mp_copy_integer_into, (ARG(val_mpint, dest), ARG(uint, n))) -FUNC(void, mp_add_integer_into, - (ARG(val_mpint, dest), ARG(val_mpint, a), ARG(uint, n))) -FUNC(void, mp_sub_integer_into, - (ARG(val_mpint, dest), ARG(val_mpint, a), ARG(uint, n))) -FUNC(void, mp_mul_integer_into, - (ARG(val_mpint, dest), ARG(val_mpint, a), ARG(uint, n))) -FUNC(void, mp_cond_add_into, - (ARG(val_mpint, dest), ARG(val_mpint, a), ARG(val_mpint, b), - ARG(uint, yes))) -FUNC(void, mp_cond_sub_into, - (ARG(val_mpint, dest), ARG(val_mpint, a), ARG(val_mpint, b), - ARG(uint, yes))) -FUNC(void, mp_cond_swap, - (ARG(val_mpint, x0), ARG(val_mpint, x1), ARG(uint, swap))) -FUNC(void, mp_cond_clear, (ARG(val_mpint, x), ARG(uint, clear))) -FUNC(void, mp_divmod_into, - (ARG(val_mpint, n), ARG(val_mpint, d), ARG(opt_val_mpint, q), - ARG(opt_val_mpint, r))) -FUNC(val_mpint, mp_div, (ARG(val_mpint, n), ARG(val_mpint, d))) -FUNC(val_mpint, mp_mod, (ARG(val_mpint, x), ARG(val_mpint, modulus))) -FUNC(val_mpint, mp_nthroot, - (ARG(val_mpint, y), ARG(uint, n), ARG(opt_val_mpint, remainder))) -FUNC(void, mp_reduce_mod_2to, (ARG(val_mpint, x), ARG(uint, p))) -FUNC(val_mpint, mp_invert_mod_2to, (ARG(val_mpint, x), ARG(uint, p))) -FUNC(val_mpint, mp_invert, (ARG(val_mpint, x), ARG(val_mpint, modulus))) -FUNC(void, mp_gcd_into, - (ARG(val_mpint, a), ARG(val_mpint, b), ARG(opt_val_mpint, gcd_out), - ARG(opt_val_mpint, A_out), ARG(opt_val_mpint, B_out))) -FUNC(val_mpint, mp_gcd, (ARG(val_mpint, a), ARG(val_mpint, b))) -FUNC(uint, mp_coprime, (ARG(val_mpint, a), ARG(val_mpint, b))) -FUNC(val_modsqrt, modsqrt_new, - (ARG(val_mpint, p), ARG(val_mpint, any_nonsquare_mod_p))) -/* The modsqrt functions' 'success' pointer becomes a second return value */ -FUNC(val_mpint, mp_modsqrt, - (ARG(val_modsqrt, sc), ARG(val_mpint, x), ARG(out_uint, success))) -FUNC(val_monty, monty_new, (ARG(val_mpint, modulus))) -FUNC_WRAPPED(val_mpint, monty_modulus, (ARG(val_monty, mc))) -FUNC_WRAPPED(val_mpint, monty_identity, (ARG(val_monty, mc))) -FUNC(void, monty_import_into, - (ARG(val_monty, mc), ARG(val_mpint, dest), ARG(val_mpint, x))) -FUNC(val_mpint, monty_import, (ARG(val_monty, mc), ARG(val_mpint, x))) -FUNC(void, monty_export_into, - (ARG(val_monty, mc), ARG(val_mpint, dest), ARG(val_mpint, x))) -FUNC(val_mpint, monty_export, (ARG(val_monty, mc), ARG(val_mpint, x))) -FUNC(void, monty_mul_into, - (ARG(val_monty, mc), ARG(val_mpint, dest), ARG(val_mpint, x), - ARG(val_mpint, y))) -FUNC(val_mpint, monty_add, - (ARG(val_monty, mc), ARG(val_mpint, x), ARG(val_mpint, y))) -FUNC(val_mpint, monty_sub, - (ARG(val_monty, mc), ARG(val_mpint, x), ARG(val_mpint, y))) -FUNC(val_mpint, monty_mul, - (ARG(val_monty, mc), ARG(val_mpint, x), ARG(val_mpint, y))) -FUNC(val_mpint, monty_pow, - (ARG(val_monty, mc), ARG(val_mpint, base), ARG(val_mpint, exponent))) -FUNC(val_mpint, monty_invert, (ARG(val_monty, mc), ARG(val_mpint, x))) -FUNC(val_mpint, monty_modsqrt, - (ARG(val_modsqrt, sc), ARG(val_mpint, mx), ARG(out_uint, success))) -FUNC(val_mpint, mp_modpow, - (ARG(val_mpint, base), ARG(val_mpint, exponent), ARG(val_mpint, modulus))) -FUNC(val_mpint, mp_modmul, - (ARG(val_mpint, x), ARG(val_mpint, y), ARG(val_mpint, modulus))) -FUNC(val_mpint, mp_modadd, - (ARG(val_mpint, x), ARG(val_mpint, y), ARG(val_mpint, modulus))) -FUNC(val_mpint, mp_modsub, - (ARG(val_mpint, x), ARG(val_mpint, y), ARG(val_mpint, modulus))) -FUNC(void, mp_lshift_safe_into, - (ARG(val_mpint, dest), ARG(val_mpint, x), ARG(uint, shift))) -FUNC(void, mp_rshift_safe_into, - (ARG(val_mpint, dest), ARG(val_mpint, x), ARG(uint, shift))) -FUNC(val_mpint, mp_rshift_safe, (ARG(val_mpint, x), ARG(uint, shift))) -FUNC(void, mp_lshift_fixed_into, - (ARG(val_mpint, dest), ARG(val_mpint, x), ARG(uint, shift))) -FUNC(void, mp_rshift_fixed_into, - (ARG(val_mpint, dest), ARG(val_mpint, x), ARG(uint, shift))) -FUNC(val_mpint, mp_rshift_fixed, (ARG(val_mpint, x), ARG(uint, shift))) -FUNC(val_mpint, mp_random_bits, (ARG(uint, bits))) -FUNC(val_mpint, mp_random_in_range, (ARG(val_mpint, lo), ARG(val_mpint, hi))) - -/* - * ecc.h functions. - */ -FUNC(val_wcurve, ecc_weierstrass_curve, - (ARG(val_mpint, p), ARG(val_mpint, a), ARG(val_mpint, b), - ARG(opt_val_mpint, nonsquare_mod_p))) -FUNC(val_wpoint, ecc_weierstrass_point_new_identity, (ARG(val_wcurve, curve))) -FUNC(val_wpoint, ecc_weierstrass_point_new, - (ARG(val_wcurve, curve), ARG(val_mpint, x), ARG(val_mpint, y))) -FUNC(val_wpoint, ecc_weierstrass_point_new_from_x, - (ARG(val_wcurve, curve), ARG(val_mpint, x), ARG(uint, desired_y_parity))) -FUNC(val_wpoint, ecc_weierstrass_point_copy, (ARG(val_wpoint, orig))) -FUNC(uint, ecc_weierstrass_point_valid, (ARG(val_wpoint, P))) -FUNC(val_wpoint, ecc_weierstrass_add_general, - (ARG(val_wpoint, P), ARG(val_wpoint, Q))) -FUNC(val_wpoint, ecc_weierstrass_add, (ARG(val_wpoint, P), ARG(val_wpoint, Q))) -FUNC(val_wpoint, ecc_weierstrass_double, (ARG(val_wpoint, P))) -FUNC(val_wpoint, ecc_weierstrass_multiply, - (ARG(val_wpoint, B), ARG(val_mpint, n))) -FUNC(uint, ecc_weierstrass_is_identity, (ARG(val_wpoint, P))) -/* The output pointers in get_affine all become extra output values */ -FUNC(void, ecc_weierstrass_get_affine, - (ARG(val_wpoint, P), ARG(out_val_mpint, x), ARG(out_val_mpint, y))) -FUNC(val_mcurve, ecc_montgomery_curve, - (ARG(val_mpint, p), ARG(val_mpint, a), ARG(val_mpint, b))) -FUNC(val_mpoint, ecc_montgomery_point_new, - (ARG(val_mcurve, curve), ARG(val_mpint, x))) -FUNC(val_mpoint, ecc_montgomery_point_copy, (ARG(val_mpoint, orig))) -FUNC(val_mpoint, ecc_montgomery_diff_add, - (ARG(val_mpoint, P), ARG(val_mpoint, Q), ARG(val_mpoint, PminusQ))) -FUNC(val_mpoint, ecc_montgomery_double, (ARG(val_mpoint, P))) -FUNC(val_mpoint, ecc_montgomery_multiply, - (ARG(val_mpoint, B), ARG(val_mpint, n))) -FUNC(void, ecc_montgomery_get_affine, - (ARG(val_mpoint, P), ARG(out_val_mpint, x))) -FUNC(boolean, ecc_montgomery_is_identity, (ARG(val_mpoint, P))) -FUNC(val_ecurve, ecc_edwards_curve, - (ARG(val_mpint, p), ARG(val_mpint, d), ARG(val_mpint, a), - ARG(opt_val_mpint, nonsquare_mod_p))) -FUNC(val_epoint, ecc_edwards_point_new, - (ARG(val_ecurve, curve), ARG(val_mpint, x), ARG(val_mpint, y))) -FUNC(val_epoint, ecc_edwards_point_new_from_y, - (ARG(val_ecurve, curve), ARG(val_mpint, y), ARG(uint, desired_x_parity))) -FUNC(val_epoint, ecc_edwards_point_copy, (ARG(val_epoint, orig))) -FUNC(val_epoint, ecc_edwards_add, (ARG(val_epoint, P), ARG(val_epoint, Q))) -FUNC(val_epoint, ecc_edwards_multiply, (ARG(val_epoint, B), ARG(val_mpint, n))) -FUNC(uint, ecc_edwards_eq, (ARG(val_epoint, P), ARG(val_epoint, Q))) -FUNC(void, ecc_edwards_get_affine, - (ARG(val_epoint, P), ARG(out_val_mpint, x), ARG(out_val_mpint, y))) - -/* - * The ssh_hash abstraction. Note the 'consumed', indicating that - * ssh_hash_final puts its input ssh_hash beyond use. - * - * ssh_hash_update is an invention of testcrypt, handled in the real C - * API by the hash object also functioning as a BinarySink. - */ -FUNC(opt_val_hash, ssh_hash_new, (ARG(hashalg, alg))) -FUNC(void, ssh_hash_reset, (ARG(val_hash, h))) -FUNC(val_hash, ssh_hash_copy, (ARG(val_hash, orig))) -FUNC_WRAPPED(val_string, ssh_hash_digest, (ARG(val_hash, h))) -FUNC_WRAPPED(val_string, ssh_hash_final, (ARG(consumed_val_hash, h))) -FUNC(void, ssh_hash_update, (ARG(val_hash, h), ARG(val_string_ptrlen, data))) - -FUNC(opt_val_hash, blake2b_new_general, (ARG(uint, hashlen))) - -/* - * The ssh2_mac abstraction. Note the optional ssh_cipher parameter - * to ssh2_mac_new. Also, again, I've invented an ssh2_mac_update so - * you can put data into the MAC. - */ -FUNC(val_mac, ssh2_mac_new, (ARG(macalg, alg), ARG(opt_val_cipher, cipher))) -FUNC(void, ssh2_mac_setkey, (ARG(val_mac, m), ARG(val_string_ptrlen, key))) -FUNC(void, ssh2_mac_start, (ARG(val_mac, m))) -FUNC(void, ssh2_mac_update, (ARG(val_mac, m), ARG(val_string_ptrlen, data))) -FUNC_WRAPPED(val_string, ssh2_mac_genresult, (ARG(val_mac, m))) -FUNC(val_string_asciz_const, ssh2_mac_text_name, (ARG(val_mac, m))) - -/* - * The ssh_key abstraction. All the uses of BinarySink and - * BinarySource in parameters are replaced with ordinary strings for - * the testing API: new_priv_openssh just takes a string input, and - * all the functions that output key and signature blobs do it by - * returning a string. - */ -FUNC(val_key, ssh_key_new_pub, (ARG(keyalg, alg), ARG(val_string_ptrlen, pub))) -FUNC(opt_val_key, ssh_key_new_priv, - (ARG(keyalg, alg), ARG(val_string_ptrlen, pub), - ARG(val_string_ptrlen, priv))) -FUNC(opt_val_key, ssh_key_new_priv_openssh, - (ARG(keyalg, alg), ARG(val_string_binarysource, src))) -FUNC(opt_val_string_asciz, ssh_key_invalid, - (ARG(val_key, key), ARG(uint, flags))) -FUNC(void, ssh_key_sign, - (ARG(val_key, key), ARG(val_string_ptrlen, data), ARG(uint, flags), - ARG(out_val_string_binarysink, sig))) -FUNC(boolean, ssh_key_verify, - (ARG(val_key, key), ARG(val_string_ptrlen, sig), - ARG(val_string_ptrlen, data))) -FUNC(void, ssh_key_public_blob, - (ARG(val_key, key), ARG(out_val_string_binarysink, blob))) -FUNC(void, ssh_key_private_blob, - (ARG(val_key, key), ARG(out_val_string_binarysink, blob))) -FUNC(void, ssh_key_openssh_blob, - (ARG(val_key, key), ARG(out_val_string_binarysink, blob))) -FUNC(val_string_asciz, ssh_key_cache_str, (ARG(val_key, key))) -FUNC(val_keycomponents, ssh_key_components, (ARG(val_key, key))) -FUNC(uint, ssh_key_public_bits, - (ARG(keyalg, self), ARG(val_string_ptrlen, blob))) - -/* - * Accessors to retrieve the innards of a 'key_components'. - */ -FUNC(uint, key_components_count, (ARG(val_keycomponents, kc))) -FUNC(opt_val_string_asciz_const, key_components_nth_name, - (ARG(val_keycomponents, kc), ARG(uint, n))) -FUNC(opt_val_string_asciz_const, key_components_nth_str, - (ARG(val_keycomponents, kc), ARG(uint, n))) -FUNC(opt_val_mpint, key_components_nth_mp, - (ARG(val_keycomponents, kc), ARG(uint, n))) - -/* - * The ssh_cipher abstraction. The in-place encrypt and decrypt - * functions are wrapped to replace them with versions that take one - * string and return a separate string. - */ -FUNC(opt_val_cipher, ssh_cipher_new, (ARG(cipheralg, alg))) -FUNC_WRAPPED(void, ssh_cipher_setiv, - (ARG(val_cipher, c), ARG(val_string_ptrlen, iv))) -FUNC_WRAPPED(void, ssh_cipher_setkey, - (ARG(val_cipher, c), ARG(val_string_ptrlen, key))) -FUNC_WRAPPED(val_string, ssh_cipher_encrypt, - (ARG(val_cipher, c), ARG(val_string_ptrlen, blk))) -FUNC_WRAPPED(val_string, ssh_cipher_decrypt, - (ARG(val_cipher, c), ARG(val_string_ptrlen, blk))) -FUNC_WRAPPED(val_string, ssh_cipher_encrypt_length, - (ARG(val_cipher, c), ARG(val_string_ptrlen, blk), ARG(uint, seq))) -FUNC_WRAPPED(val_string, ssh_cipher_decrypt_length, - (ARG(val_cipher, c), ARG(val_string_ptrlen, blk), ARG(uint, seq))) - -/* - * Integer Diffie-Hellman. - */ -FUNC(val_dh, dh_setup_group, (ARG(dh_group, group))) -FUNC(val_dh, dh_setup_gex, (ARG(val_mpint, p), ARG(val_mpint, g))) -FUNC(uint, dh_modulus_bit_size, (ARG(val_dh, ctx))) -FUNC(val_mpint, dh_create_e, (ARG(val_dh, ctx), ARG(uint, nbits))) -FUNC_WRAPPED(boolean, dh_validate_f, (ARG(val_dh, ctx), ARG(val_mpint, f))) -FUNC(val_mpint, dh_find_K, (ARG(val_dh, ctx), ARG(val_mpint, f))) - -/* - * Elliptic-curve Diffie-Hellman. - */ -FUNC(val_ecdh, ssh_ecdhkex_newkey, (ARG(ecdh_alg, alg))) -FUNC(void, ssh_ecdhkex_getpublic, - (ARG(val_ecdh, key), ARG(out_val_string_binarysink, pub))) -FUNC(opt_val_mpint, ssh_ecdhkex_getkey, - (ARG(val_ecdh, key), ARG(val_string_ptrlen, pub))) - -/* - * RSA key exchange, and also the BinarySource get function - * get_ssh1_rsa_priv_agent, which is a convenient way to make an - * RSAKey for RSA kex testing purposes. - */ -FUNC(val_rsakex, ssh_rsakex_newkey, (ARG(val_string_ptrlen, data))) -FUNC(uint, ssh_rsakex_klen, (ARG(val_rsakex, key))) -FUNC(val_string, ssh_rsakex_encrypt, - (ARG(val_rsakex, key), ARG(hashalg, h), ARG(val_string_ptrlen, plaintext))) -FUNC(opt_val_mpint, ssh_rsakex_decrypt, - (ARG(val_rsakex, key), ARG(hashalg, h), - ARG(val_string_ptrlen, ciphertext))) -FUNC(val_rsakex, get_rsa_ssh1_priv_agent, (ARG(val_string_binarysource, src))) - -/* - * Bare RSA keys as used in SSH-1. The construction API functions - * write into an existing RSAKey object, so I've invented an 'rsa_new' - * function to make one in the first place. - */ -FUNC(val_rsa, rsa_new, (VOID)) -FUNC(void, get_rsa_ssh1_pub, - (ARG(val_string_binarysource, src), ARG(val_rsa, key), - ARG(rsaorder, order))) -FUNC(void, get_rsa_ssh1_priv, - (ARG(val_string_binarysource, src), ARG(val_rsa, key))) -FUNC_WRAPPED(opt_val_string, rsa_ssh1_encrypt, - (ARG(val_string_ptrlen, data), ARG(val_rsa, key))) -FUNC(val_mpint, rsa_ssh1_decrypt, (ARG(val_mpint, input), ARG(val_rsa, key))) -FUNC_WRAPPED(val_string, rsa_ssh1_decrypt_pkcs1, - (ARG(val_mpint, input), ARG(val_rsa, key))) -FUNC(val_string_asciz, rsastr_fmt, (ARG(val_rsa, key))) -FUNC(val_string_asciz, rsa_ssh1_fingerprint, (ARG(val_rsa, key))) -FUNC(void, rsa_ssh1_public_blob, - (ARG(out_val_string_binarysink, blob), ARG(val_rsa, key), - ARG(rsaorder, order))) -FUNC(int, rsa_ssh1_public_blob_len, (ARG(val_string_ptrlen, data))) -FUNC(void, rsa_ssh1_private_blob_agent, - (ARG(out_val_string_binarysink, blob), ARG(val_rsa, key))) - -/* - * The PRNG type. Similarly to hashes and MACs, I've invented an extra - * function prng_seed_update for putting seed data into the PRNG's - * exposed BinarySink. - */ -FUNC(val_prng, prng_new, (ARG(hashalg, hashalg))) -FUNC(void, prng_seed_begin, (ARG(val_prng, pr))) -FUNC(void, prng_seed_update, (ARG(val_prng, pr), ARG(val_string_ptrlen, data))) -FUNC(void, prng_seed_finish, (ARG(val_prng, pr))) -FUNC_WRAPPED(val_string, prng_read, (ARG(val_prng, pr), ARG(uint, size))) -FUNC(void, prng_add_entropy, - (ARG(val_prng, pr), ARG(uint, source_id), ARG(val_string_ptrlen, data))) - -/* - * Key load/save functions, or rather, the BinarySource / strbuf API - * that sits just inside the file I/O versions. - */ -FUNC(boolean, ppk_encrypted_s, - (ARG(val_string_binarysource, src), - ARG(out_opt_val_string_asciz, comment))) -FUNC(boolean, rsa1_encrypted_s, - (ARG(val_string_binarysource, src), - ARG(out_opt_val_string_asciz, comment))) -FUNC(boolean, ppk_loadpub_s, - (ARG(val_string_binarysource, src), - ARG(out_opt_val_string_asciz, algorithm), - ARG(out_val_string_binarysink, blob), - ARG(out_opt_val_string_asciz, comment), - ARG(out_opt_val_string_asciz_const, error))) -FUNC(int, rsa1_loadpub_s, - (ARG(val_string_binarysource, src), ARG(out_val_string_binarysink, blob), - ARG(out_opt_val_string_asciz, comment), - ARG(out_opt_val_string_asciz_const, error))) -FUNC_WRAPPED(opt_val_key, ppk_load_s, - (ARG(val_string_binarysource, src), - ARG(out_opt_val_string_asciz, comment), - ARG(opt_val_string_asciz, passphrase), - ARG(out_opt_val_string_asciz_const, error))) -FUNC_WRAPPED(int, rsa1_load_s, - (ARG(val_string_binarysource, src), ARG(val_rsa, key), - ARG(out_opt_val_string_asciz, comment), - ARG(opt_val_string_asciz, passphrase), - ARG(out_opt_val_string_asciz_const, error))) -FUNC_WRAPPED(val_string, ppk_save_sb, - (ARG(val_key, key), ARG(opt_val_string_asciz, comment), - ARG(opt_val_string_asciz, passphrase), ARG(uint, fmt_version), - ARG(argon2flavour, flavour), ARG(uint, mem), ARG(uint, passes), - ARG(uint, parallel))) -FUNC_WRAPPED(val_string, rsa1_save_sb, - (ARG(val_rsa, key), ARG(opt_val_string_asciz, comment), - ARG(opt_val_string_asciz, passphrase))) - -FUNC(val_string_asciz, ssh2_fingerprint_blob, - (ARG(val_string_ptrlen, blob), ARG(fptype, fptype))) - -/* - * Password hashing. - */ -FUNC_WRAPPED(val_string, argon2, - (ARG(argon2flavour, flavour), ARG(uint, mem), ARG(uint, passes), - ARG(uint, parallel), ARG(uint, taglen), ARG(val_string_ptrlen, P), - ARG(val_string_ptrlen, S), ARG(val_string_ptrlen, K), - ARG(val_string_ptrlen, X))) -FUNC(val_string, argon2_long_hash, - (ARG(uint, length), ARG(val_string_ptrlen, data))) - -/* - * Key generation functions. - */ -FUNC_WRAPPED(val_key, rsa_generate, - (ARG(uint, bits), ARG(boolean, strong), ARG(val_pgc, pgc))) -FUNC_WRAPPED(val_key, dsa_generate, (ARG(uint, bits), ARG(val_pgc, pgc))) -FUNC_WRAPPED(opt_val_key, ecdsa_generate, (ARG(uint, bits))) -FUNC_WRAPPED(opt_val_key, eddsa_generate, (ARG(uint, bits))) -FUNC(val_rsa, rsa1_generate, - (ARG(uint, bits), ARG(boolean, strong), ARG(val_pgc, pgc))) -FUNC(val_pgc, primegen_new_context, (ARG(primegenpolicy, policy))) -FUNC_WRAPPED(opt_val_mpint, primegen_generate, - (ARG(val_pgc, ctx), ARG(consumed_val_pcs, pcs))) -FUNC(val_string, primegen_mpu_certificate, - (ARG(val_pgc, ctx), ARG(val_mpint, p))) -FUNC(val_pcs, pcs_new, (ARG(uint, bits))) -FUNC(val_pcs, pcs_new_with_firstbits, - (ARG(uint, bits), ARG(uint, first), ARG(uint, nfirst))) -FUNC(void, pcs_require_residue, - (ARG(val_pcs, s), ARG(val_mpint, mod), ARG(val_mpint, res))) -FUNC(void, pcs_require_residue_1, (ARG(val_pcs, s), ARG(val_mpint, mod))) -FUNC(void, pcs_require_residue_1_mod_prime, - (ARG(val_pcs, s), ARG(val_mpint, mod))) -FUNC(void, pcs_avoid_residue_small, - (ARG(val_pcs, s), ARG(uint, mod), ARG(uint, res))) -FUNC(void, pcs_try_sophie_germain, (ARG(val_pcs, s))) -FUNC(void, pcs_set_oneshot, (ARG(val_pcs, s))) -FUNC(void, pcs_ready, (ARG(val_pcs, s))) -FUNC(void, pcs_inspect, - (ARG(val_pcs, pcs), ARG(out_val_mpint, limit_out), - ARG(out_val_mpint, factor_out), ARG(out_val_mpint, addend_out))) -FUNC(val_mpint, pcs_generate, (ARG(val_pcs, s))) -FUNC(val_pockle, pockle_new, (VOID)) -FUNC(uint, pockle_mark, (ARG(val_pockle, pockle))) -FUNC(void, pockle_release, (ARG(val_pockle, pockle), ARG(uint, mark))) -FUNC(pocklestatus, pockle_add_small_prime, - (ARG(val_pockle, pockle), ARG(val_mpint, p))) -FUNC_WRAPPED(pocklestatus, pockle_add_prime, - (ARG(val_pockle, pockle), ARG(val_mpint, p), - ARG(mpint_list, factors), ARG(val_mpint, witness))) -FUNC(val_string, pockle_mpu, (ARG(val_pockle, pockle), ARG(val_mpint, p))) -FUNC(val_millerrabin, miller_rabin_new, (ARG(val_mpint, p))) -FUNC(mr_result, miller_rabin_test, - (ARG(val_millerrabin, mr), ARG(val_mpint, w))) - -/* - * Miscellaneous. - */ -FUNC(val_wpoint, ecdsa_public, (ARG(val_mpint, private_key), ARG(keyalg, alg))) -FUNC(val_epoint, eddsa_public, (ARG(val_mpint, private_key), ARG(keyalg, alg))) -FUNC_WRAPPED(val_string, des_encrypt_xdmauth, - (ARG(val_string_ptrlen, key), ARG(val_string_ptrlen, blk))) -FUNC_WRAPPED(val_string, des_decrypt_xdmauth, - (ARG(val_string_ptrlen, key), ARG(val_string_ptrlen, blk))) -FUNC_WRAPPED(val_string, des3_encrypt_pubkey, - (ARG(val_string_ptrlen, key), ARG(val_string_ptrlen, blk))) -FUNC_WRAPPED(val_string, des3_decrypt_pubkey, - (ARG(val_string_ptrlen, key), ARG(val_string_ptrlen, blk))) -FUNC_WRAPPED(val_string, des3_encrypt_pubkey_ossh, - (ARG(val_string_ptrlen, key), ARG(val_string_ptrlen, iv), - ARG(val_string_ptrlen, blk))) -FUNC_WRAPPED(val_string, des3_decrypt_pubkey_ossh, - (ARG(val_string_ptrlen, key), ARG(val_string_ptrlen, iv), - ARG(val_string_ptrlen, blk))) -FUNC_WRAPPED(val_string, aes256_encrypt_pubkey, - (ARG(val_string_ptrlen, key), ARG(val_string_ptrlen, iv), - ARG(val_string_ptrlen, blk))) -FUNC_WRAPPED(val_string, aes256_decrypt_pubkey, - (ARG(val_string_ptrlen, key), ARG(val_string_ptrlen, iv), - ARG(val_string_ptrlen, blk))) -FUNC(uint, crc32_rfc1662, (ARG(val_string_ptrlen, data))) -FUNC(uint, crc32_ssh1, (ARG(val_string_ptrlen, data))) -FUNC(uint, crc32_update, (ARG(uint, crc_input), ARG(val_string_ptrlen, data))) -FUNC(boolean, crcda_detect, - (ARG(val_string_ptrlen, packet), ARG(val_string_ptrlen, iv))) -FUNC(val_string, get_implementations_commasep, (ARG(val_string_ptrlen, alg))) -FUNC(void, http_digest_response, - (ARG(out_val_string_binarysink, response), - ARG(val_string_ptrlen, username), ARG(val_string_ptrlen, password), - ARG(val_string_ptrlen, realm), ARG(val_string_ptrlen, method), - ARG(val_string_ptrlen, uri), ARG(val_string_ptrlen, qop), - ARG(val_string_ptrlen, nonce), ARG(val_string_ptrlen, opaque), - ARG(uint, nonce_count), ARG(httpdigesthash, hash), - ARG(boolean, hash_username))) - -/* - * These functions aren't part of PuTTY's own API, but are additions - * by testcrypt itself for administrative purposes. - */ -FUNC(void, random_queue, (ARG(val_string_ptrlen, data))) -FUNC(uint, random_queue_len, (VOID)) -FUNC(void, random_make_prng, - (ARG(hashalg, hashalg), ARG(val_string_ptrlen, seed))) -FUNC(void, random_clear, (VOID)) diff --git a/testsc.c b/testsc.c deleted file mode 100644 index c9029662..00000000 --- a/testsc.c +++ /dev/null @@ -1,1753 +0,0 @@ -/* - * testsc: run PuTTY's crypto primitives under instrumentation that - * checks for cache and timing side channels. - * - * The idea is: cryptographic code should avoid leaking secret data - * through timing information, or through traces of its activity left - * in the caches. - * - * (This property is sometimes called 'constant-time', although really - * that's a misnomer. It would be impossible to avoid the execution - * time varying for any number of reasons outside the code's control, - * such as the prior contents of caches and branch predictors, - * temperature-based CPU throttling, system load, etc. And in any case - * you don't _need_ the execution time to be literally constant: you - * just need it to be independent of your secrets. It can vary as much - * as it likes based on anything else.) - * - * To avoid this, you need to ensure that various aspects of the - * code's behaviour do not depend on the secret data. The control - * flow, for a start - no conditional branches based on secrets - and - * also the memory access pattern (no using secret data as an index - * into a lookup table). A couple of other kinds of CPU instruction - * also can't be trusted to run in constant time: we check for - * register-controlled shifts and hardware divisions. (But, again, - * it's perfectly fine to _use_ those instructions in the course of - * crypto code. You just can't use a secret as any time-affecting - * operand.) - * - * This test program works by running the same crypto primitive - * multiple times, with different secret input data. The relevant - * details of each run is logged to a file via the DynamoRIO-based - * instrumentation system living in the subdirectory test/sclog. Then - * we check over all the files and ensure they're identical. - * - * This program itself (testsc) is built by the ordinary PuTTY - * makefiles. But run by itself, it will do nothing useful: it needs - * to be run under DynamoRIO, with the sclog instrumentation library. - * - * Here's an example of how I built it: - * - * Download the DynamoRIO source. I did this by cloning - * https://github.com/DynamoRIO/dynamorio.git, and at the time of - * writing this, 259c182a75ce80112bcad329c97ada8d56ba854d was the head - * commit. - * - * In the DynamoRIO checkout: - * - * mkdir build - * cd build - * cmake -G Ninja .. - * ninja - * - * Now set the shell variable DRBUILD to be the location of the build - * directory you did that in. (Or not, if you prefer, but the example - * build commands below will assume that that's where the DynamoRIO - * libraries, headers and runtime can be found.) - * - * Then, in test/sclog: - * - * cmake -G Ninja -DCMAKE_PREFIX_PATH=$DRBUILD/cmake . - * ninja - * - * Finally, to run the actual test, set SCTMP to some temp directory - * you don't mind filling with large temp files (several GB at a - * time), and in the main PuTTY source directory (assuming that's - * where testsc has been built): - * - * $DRBUILD/bin64/drrun -c test/sclog/libsclog.so -- ./testsc -O $SCTMP - */ - -#include -#include -#include -#include -#include - -#include "defs.h" -#include "putty.h" -#include "ssh.h" -#include "sshkeygen.h" -#include "misc.h" -#include "mpint.h" -#include "crypto/ecc.h" - -static NORETURN PRINTF_LIKE(1, 2) void fatal_error(const char *p, ...) -{ - va_list ap; - fprintf(stderr, "testsc: "); - va_start(ap, p); - vfprintf(stderr, p, ap); - va_end(ap); - fputc('\n', stderr); - exit(1); -} - -void out_of_memory(void) { fatal_error("out of memory"); } - -/* - * A simple deterministic PRNG, without any of the Fortuna - * complexities, for generating test inputs in a way that's repeatable - * between runs of the program, even if only a subset of test cases is - * run. - */ -static uint64_t random_counter = 0; -static const char *random_seedstr = NULL; -static uint8_t random_buf[MAX_HASH_LEN]; -static size_t random_buf_limit = 0; -static ssh_hash *random_hash; - -static void random_seed(const char *seedstr) -{ - random_seedstr = seedstr; - random_counter = 0; - random_buf_limit = 0; -} - -void random_read(void *vbuf, size_t size) -{ - assert(random_seedstr); - uint8_t *buf = (uint8_t *)vbuf; - while (size-- > 0) { - if (random_buf_limit == 0) { - ssh_hash_reset(random_hash); - put_asciz(random_hash, random_seedstr); - put_uint64(random_hash, random_counter); - random_counter++; - random_buf_limit = ssh_hash_alg(random_hash)->hlen; - ssh_hash_digest(random_hash, random_buf); - } - *buf++ = random_buf[random_buf_limit--]; - } -} - -struct random_state { - const char *seedstr; - uint64_t counter; - size_t limit; - uint8_t buf[MAX_HASH_LEN]; -}; - -static struct random_state random_get_state(void) -{ - struct random_state st; - st.seedstr = random_seedstr; - st.counter = random_counter; - st.limit = random_buf_limit; - memcpy(st.buf, random_buf, sizeof(st.buf)); - return st; -} - -static void random_set_state(struct random_state st) -{ - random_seedstr = st.seedstr; - random_counter = st.counter; - random_buf_limit = st.limit; - memcpy(random_buf, st.buf, sizeof(random_buf)); -} - -/* - * Macro that defines a function, and also a volatile function pointer - * pointing to it. Callers indirect through the function pointer - * instead of directly calling the function, to ensure that the - * compiler doesn't try to get clever by eliminating the call - * completely, or inlining it. - * - * This is used to mark functions that DynamoRIO will look for to - * intercept, and also to inhibit inlining and unrolling where they'd - * cause a failure of experimental control in the main test. - */ -#define VOLATILE_WRAPPED_DEFN(qualifier, rettype, fn, params) \ - qualifier rettype fn##_real params; \ - qualifier rettype (*volatile fn) params = fn##_real; \ - qualifier rettype fn##_real params - -VOLATILE_WRAPPED_DEFN(, void, log_to_file, (const char *filename)) -{ - /* - * This function is intercepted by the DynamoRIO side of the - * mechanism. We use it to send instructions to the DR wrapper, - * namely, 'please start logging to this file' or 'please stop - * logging' (if filename == NULL). But we don't have to actually - * do anything in _this_ program - all the functionality is in the - * DR wrapper. - */ -} - -static const char *outdir = NULL; -char *log_filename(const char *basename, size_t index) -{ - return dupprintf("%s/%s.%04"SIZEu, outdir, basename, index); -} - -static char *last_filename; -static const char *test_basename; -static size_t test_index = 0; -void log_start(void) -{ - last_filename = log_filename(test_basename, test_index++); - log_to_file(last_filename); -} -void log_end(void) -{ - log_to_file(NULL); - sfree(last_filename); -} - -static bool test_skipped = false; - -VOLATILE_WRAPPED_DEFN(, intptr_t, dry_run, (void)) -{ - /* - * This is another function intercepted by DynamoRIO. In this - * case, DR overrides this function to return 0 rather than 1, so - * we can use it as a check for whether we're running under - * instrumentation, or whether this is just a dry run which goes - * through the motions but doesn't expect to find any log files - * created. - */ - return 1; -} - -static void mp_random_bits_into(mp_int *r, size_t bits) -{ - mp_int *x = mp_random_bits(bits); - mp_copy_into(r, x); - mp_free(x); -} - -static void mp_random_fill(mp_int *r) -{ - mp_random_bits_into(r, mp_max_bits(r)); -} - -VOLATILE_WRAPPED_DEFN(static, size_t, looplimit, (size_t x)) -{ - /* - * looplimit() is the identity function on size_t, but the - * compiler isn't allowed to rely on it being that. I use it to - * make loops in the test functions look less attractive to - * compilers' unrolling heuristics. - */ - return x; -} - -#if HAVE_AES_NI -#define CIPHERS_AES_NI(X, Y) \ - X(Y, ssh_aes256_sdctr_ni) \ - X(Y, ssh_aes256_cbc_ni) \ - X(Y, ssh_aes192_sdctr_ni) \ - X(Y, ssh_aes192_cbc_ni) \ - X(Y, ssh_aes128_sdctr_ni) \ - X(Y, ssh_aes128_cbc_ni) \ - /* end of list */ -#else -#define CIPHERS_AES_NI(X, Y) -#endif -#if HAVE_NEON_CRYPTO -#define CIPHERS_AES_NEON(X, Y) \ - X(Y, ssh_aes256_sdctr_neon) \ - X(Y, ssh_aes256_cbc_neon) \ - X(Y, ssh_aes192_sdctr_neon) \ - X(Y, ssh_aes192_cbc_neon) \ - X(Y, ssh_aes128_sdctr_neon) \ - X(Y, ssh_aes128_cbc_neon) \ - /* end of list */ -#else -#define CIPHERS_AES_NEON(X, Y) -#endif - -/* Ciphers that we expect to pass this test. Blowfish and Arcfour are - * intentionally omitted, because we already know they don't. */ -#define CIPHERS(X, Y) \ - X(Y, ssh_3des_ssh1) \ - X(Y, ssh_3des_ssh2_ctr) \ - X(Y, ssh_3des_ssh2) \ - X(Y, ssh_des) \ - X(Y, ssh_des_sshcom_ssh2) \ - X(Y, ssh_aes256_sdctr) \ - X(Y, ssh_aes256_cbc) \ - X(Y, ssh_aes192_sdctr) \ - X(Y, ssh_aes192_cbc) \ - X(Y, ssh_aes128_sdctr) \ - X(Y, ssh_aes128_cbc) \ - X(Y, ssh_aes256_sdctr_sw) \ - X(Y, ssh_aes256_cbc_sw) \ - X(Y, ssh_aes192_sdctr_sw) \ - X(Y, ssh_aes192_cbc_sw) \ - X(Y, ssh_aes128_sdctr_sw) \ - X(Y, ssh_aes128_cbc_sw) \ - CIPHERS_AES_NI(X, Y) \ - CIPHERS_AES_NEON(X, Y) \ - X(Y, ssh2_chacha20_poly1305) \ - /* end of list */ - -#define CIPHER_TESTLIST(X, name) X(cipher_ ## name) - -#define MACS(X, Y) \ - X(Y, ssh_hmac_md5) \ - X(Y, ssh_hmac_sha1) \ - X(Y, ssh_hmac_sha1_buggy) \ - X(Y, ssh_hmac_sha1_96) \ - X(Y, ssh_hmac_sha1_96_buggy) \ - X(Y, ssh_hmac_sha256) \ - /* end of list */ - -#define MAC_TESTLIST(X, name) X(mac_ ## name) - -#if HAVE_SHA_NI -#define HASH_SHA_NI(X, Y) X(Y, ssh_sha256_ni) X(Y, ssh_sha1_ni) -#else -#define HASH_SHA_NI(X, Y) -#endif -#if HAVE_NEON_CRYPTO -#define HASH_SHA_NEON(X, Y) X(Y, ssh_sha256_neon) X(Y, ssh_sha1_neon) -#else -#define HASH_SHA_NEON(X, Y) -#endif -#if HAVE_NEON_SHA512 -#define HASH_SHA512_NEON(X, Y) X(Y, ssh_sha384_neon) X(Y, ssh_sha512_neon) -#else -#define HASH_SHA512_NEON(X, Y) -#endif - -#define HASHES(X, Y) \ - X(Y, ssh_md5) \ - X(Y, ssh_sha1) \ - X(Y, ssh_sha1_sw) \ - X(Y, ssh_sha256) \ - X(Y, ssh_sha256_sw) \ - X(Y, ssh_sha384) \ - X(Y, ssh_sha512) \ - X(Y, ssh_sha384_sw) \ - X(Y, ssh_sha512_sw) \ - HASH_SHA_NI(X, Y) \ - HASH_SHA_NEON(X, Y) \ - HASH_SHA512_NEON(X, Y) \ - X(Y, ssh_sha3_224) \ - X(Y, ssh_sha3_256) \ - X(Y, ssh_sha3_384) \ - X(Y, ssh_sha3_512) \ - X(Y, ssh_shake256_114bytes) \ - X(Y, ssh_blake2b) \ - /* end of list */ - -#define HASH_TESTLIST(X, name) X(hash_ ## name) - -#define TESTLIST(X) \ - X(mp_get_nbits) \ - X(mp_from_decimal) \ - X(mp_from_hex) \ - X(mp_get_decimal) \ - X(mp_get_hex) \ - X(mp_cmp_hs) \ - X(mp_cmp_eq) \ - X(mp_min) \ - X(mp_max) \ - X(mp_select_into) \ - X(mp_cond_swap) \ - X(mp_cond_clear) \ - X(mp_add) \ - X(mp_sub) \ - X(mp_mul) \ - X(mp_rshift_safe) \ - X(mp_divmod) \ - X(mp_nthroot) \ - X(mp_modadd) \ - X(mp_modsub) \ - X(mp_modmul) \ - X(mp_modpow) \ - X(mp_invert_mod_2to) \ - X(mp_invert) \ - X(mp_modsqrt) \ - X(ecc_weierstrass_add) \ - X(ecc_weierstrass_double) \ - X(ecc_weierstrass_add_general) \ - X(ecc_weierstrass_multiply) \ - X(ecc_weierstrass_is_identity) \ - X(ecc_weierstrass_get_affine) \ - X(ecc_weierstrass_decompress) \ - X(ecc_montgomery_diff_add) \ - X(ecc_montgomery_double) \ - X(ecc_montgomery_multiply) \ - X(ecc_montgomery_get_affine) \ - X(ecc_edwards_add) \ - X(ecc_edwards_multiply) \ - X(ecc_edwards_eq) \ - X(ecc_edwards_get_affine) \ - X(ecc_edwards_decompress) \ - CIPHERS(CIPHER_TESTLIST, X) \ - MACS(MAC_TESTLIST, X) \ - HASHES(HASH_TESTLIST, X) \ - X(argon2) \ - X(primegen_probabilistic) \ - /* end of list */ - -static void test_mp_get_nbits(void) -{ - mp_int *z = mp_new(512); - static const size_t bitposns[] = { - 0, 1, 5, 16, 23, 32, 67, 123, 234, 511 - }; - mp_int *prev = mp_from_integer(0); - for (size_t i = 0; i < looplimit(lenof(bitposns)); i++) { - mp_int *x = mp_power_2(bitposns[i]); - mp_add_into(z, x, prev); - mp_free(prev); - prev = x; - log_start(); - mp_get_nbits(z); - log_end(); - } - mp_free(prev); - mp_free(z); -} - -static void test_mp_from_decimal(void) -{ - char dec[64]; - static const size_t starts[] = { 0, 1, 5, 16, 23, 32, 63, 64 }; - for (size_t i = 0; i < looplimit(lenof(starts)); i++) { - memset(dec, '0', lenof(dec)); - for (size_t j = starts[i]; j < lenof(dec); j++) { - uint8_t r[4]; - random_read(r, 4); - dec[j] = '0' + GET_32BIT_MSB_FIRST(r) % 10; - } - log_start(); - mp_int *x = mp_from_decimal_pl(make_ptrlen(dec, lenof(dec))); - log_end(); - mp_free(x); - } -} - -static void test_mp_from_hex(void) -{ - char hex[64]; - static const size_t starts[] = { 0, 1, 5, 16, 23, 32, 63, 64 }; - static const char digits[] = "0123456789abcdefABCDEF"; - for (size_t i = 0; i < looplimit(lenof(starts)); i++) { - memset(hex, '0', lenof(hex)); - for (size_t j = starts[i]; j < lenof(hex); j++) { - uint8_t r[4]; - random_read(r, 4); - hex[j] = digits[GET_32BIT_MSB_FIRST(r) % lenof(digits)]; - } - log_start(); - mp_int *x = mp_from_hex_pl(make_ptrlen(hex, lenof(hex))); - log_end(); - mp_free(x); - } -} - -static void test_mp_string_format(char *(*mp_format)(mp_int *x)) -{ - mp_int *z = mp_new(512); - static const size_t bitposns[] = { - 0, 1, 5, 16, 23, 32, 67, 123, 234, 511 - }; - for (size_t i = 0; i < looplimit(lenof(bitposns)); i++) { - mp_random_bits_into(z, bitposns[i]); - log_start(); - char *formatted = mp_format(z); - log_end(); - sfree(formatted); - } - mp_free(z); -} - -static void test_mp_get_decimal(void) -{ - test_mp_string_format(mp_get_decimal); -} - -static void test_mp_get_hex(void) -{ - test_mp_string_format(mp_get_hex); -} - -static void test_mp_cmp(unsigned (*mp_cmp)(mp_int *a, mp_int *b)) -{ - mp_int *a = mp_new(512), *b = mp_new(512); - static const size_t bitposns[] = { - 0, 1, 5, 16, 23, 32, 67, 123, 234, 511 - }; - for (size_t i = 0; i < looplimit(lenof(bitposns)); i++) { - mp_random_fill(b); - mp_int *x = mp_random_bits(bitposns[i]); - mp_xor_into(a, b, x); - mp_free(x); - log_start(); - mp_cmp(a, b); - log_end(); - } - mp_free(a); - mp_free(b); -} - -static void test_mp_cmp_hs(void) -{ - test_mp_cmp(mp_cmp_hs); -} - -static void test_mp_cmp_eq(void) -{ - test_mp_cmp(mp_cmp_eq); -} - -static void test_mp_minmax( - void (*mp_minmax_into)(mp_int *r, mp_int *x, mp_int *y)) -{ - mp_int *a = mp_new(256), *b = mp_new(256); - for (size_t i = 0; i < looplimit(10); i++) { - uint8_t lens[2]; - random_read(lens, 2); - mp_int *x = mp_random_bits(lens[0]); - mp_copy_into(a, x); - mp_free(x); - mp_int *y = mp_random_bits(lens[1]); - mp_copy_into(a, y); - mp_free(y); - log_start(); - mp_minmax_into(a, a, b); - log_end(); - } - mp_free(a); - mp_free(b); -} - -static void test_mp_max(void) -{ - test_mp_minmax(mp_max_into); -} - -static void test_mp_min(void) -{ - test_mp_minmax(mp_min_into); -} - -static void test_mp_select_into(void) -{ - mp_int *a = mp_random_bits(256); - mp_int *b = mp_random_bits(512); - mp_int *r = mp_new(384); - for (size_t i = 0; i < looplimit(16); i++) { - log_start(); - mp_select_into(r, a, b, i & 1); - log_end(); - } - mp_free(a); - mp_free(b); - mp_free(r); -} - -static void test_mp_cond_swap(void) -{ - mp_int *a = mp_random_bits(512); - mp_int *b = mp_random_bits(512); - for (size_t i = 0; i < looplimit(16); i++) { - log_start(); - mp_cond_swap(a, b, i & 1); - log_end(); - } - mp_free(a); - mp_free(b); -} - -static void test_mp_cond_clear(void) -{ - mp_int *a = mp_random_bits(512); - mp_int *x = mp_copy(a); - for (size_t i = 0; i < looplimit(16); i++) { - mp_copy_into(x, a); - log_start(); - mp_cond_clear(a, i & 1); - log_end(); - } - mp_free(a); - mp_free(x); -} - -static void test_mp_arithmetic(mp_int *(*mp_arith)(mp_int *x, mp_int *y)) -{ - mp_int *a = mp_new(256), *b = mp_new(512); - for (size_t i = 0; i < looplimit(16); i++) { - mp_random_fill(a); - mp_random_fill(b); - log_start(); - mp_int *r = mp_arith(a, b); - log_end(); - mp_free(r); - } - mp_free(a); - mp_free(b); -} - -static void test_mp_add(void) -{ - test_mp_arithmetic(mp_add); -} - -static void test_mp_sub(void) -{ - test_mp_arithmetic(mp_sub); -} - -static void test_mp_mul(void) -{ - test_mp_arithmetic(mp_mul); -} - -static void test_mp_invert(void) -{ - test_mp_arithmetic(mp_invert); -} - -static void test_mp_rshift_safe(void) -{ - mp_int *x = mp_random_bits(256); - - for (size_t i = 0; i < looplimit(mp_max_bits(x)+1); i++) { - log_start(); - mp_int *r = mp_rshift_safe(x, i); - log_end(); - mp_free(r); - } - - mp_free(x); -} - -static void test_mp_divmod(void) -{ - mp_int *n = mp_new(256), *d = mp_new(256); - mp_int *q = mp_new(256), *r = mp_new(256); - - for (size_t i = 0; i < looplimit(32); i++) { - uint8_t sizes[2]; - random_read(sizes, 2); - mp_random_bits_into(n, sizes[0]); - mp_random_bits_into(d, sizes[1]); - log_start(); - mp_divmod_into(n, d, q, r); - log_end(); - } - - mp_free(n); - mp_free(d); - mp_free(q); - mp_free(r); -} - -static void test_mp_nthroot(void) -{ - mp_int *x = mp_new(256), *remainder = mp_new(256); - - for (size_t i = 0; i < looplimit(32); i++) { - uint8_t sizes[1]; - random_read(sizes, 1); - mp_random_bits_into(x, sizes[0]); - log_start(); - mp_free(mp_nthroot(x, 3, remainder)); - log_end(); - } - - mp_free(x); - mp_free(remainder); -} - -static void test_mp_modarith( - mp_int *(*mp_modarith)(mp_int *x, mp_int *y, mp_int *modulus)) -{ - mp_int *base = mp_new(256); - mp_int *exponent = mp_new(256); - mp_int *modulus = mp_new(256); - - for (size_t i = 0; i < looplimit(8); i++) { - mp_random_fill(base); - mp_random_fill(exponent); - mp_random_fill(modulus); - mp_set_bit(modulus, 0, 1); /* we only support odd moduli */ - - log_start(); - mp_int *out = mp_modarith(base, exponent, modulus); - log_end(); - - mp_free(out); - } - - mp_free(base); - mp_free(exponent); - mp_free(modulus); -} - -static void test_mp_modadd(void) -{ - test_mp_modarith(mp_modadd); -} - -static void test_mp_modsub(void) -{ - test_mp_modarith(mp_modsub); -} - -static void test_mp_modmul(void) -{ - test_mp_modarith(mp_modmul); -} - -static void test_mp_modpow(void) -{ - test_mp_modarith(mp_modpow); -} - -static void test_mp_invert_mod_2to(void) -{ - mp_int *x = mp_new(512); - - for (size_t i = 0; i < looplimit(32); i++) { - mp_random_fill(x); - mp_set_bit(x, 0, 1); /* input should be odd */ - - log_start(); - mp_int *out = mp_invert_mod_2to(x, 511); - log_end(); - - mp_free(out); - } - - mp_free(x); -} - -static void test_mp_modsqrt(void) -{ - /* The prime isn't secret in this function (and in any case - * finding a non-square on the fly would be prohibitively - * annoying), so I hardcode a fixed one, selected to have a lot of - * factors of two in p-1 so as to exercise lots of choices in the - * algorithm. */ - mp_int *p = - MP_LITERAL(0xb56a517b206a88c73cfa9ec6f704c7030d18212cace82401); - mp_int *nonsquare = MP_LITERAL(0x5); - size_t bits = mp_max_bits(p); - ModsqrtContext *sc = modsqrt_new(p, nonsquare); - mp_free(p); - mp_free(nonsquare); - - mp_int *x = mp_new(bits); - unsigned success; - - /* Do one initial call to cause the lazily initialised sub-context - * to be set up. This will take a while, but it can't be helped. */ - mp_int *unwanted = mp_modsqrt(sc, x, &success); - mp_free(unwanted); - - for (size_t i = 0; i < looplimit(8); i++) { - mp_random_bits_into(x, bits - 1); - log_start(); - mp_int *out = mp_modsqrt(sc, x, &success); - log_end(); - mp_free(out); - } - - mp_free(x); - modsqrt_free(sc); -} - -static WeierstrassCurve *wcurve(void) -{ - mp_int *p = MP_LITERAL(0xc19337603dc856acf31e01375a696fdf5451); - mp_int *a = MP_LITERAL(0x864946f50eecca4cde7abad4865e34be8f67); - mp_int *b = MP_LITERAL(0x6a5bf56db3a03ba91cfbf3241916c90feeca); - mp_int *nonsquare = mp_from_integer(3); - WeierstrassCurve *wc = ecc_weierstrass_curve(p, a, b, nonsquare); - mp_free(p); - mp_free(a); - mp_free(b); - mp_free(nonsquare); - return wc; -} - -static WeierstrassPoint *wpoint(WeierstrassCurve *wc, size_t index) -{ - mp_int *x = NULL, *y = NULL; - WeierstrassPoint *wp; - switch (index) { - case 0: - break; - case 1: - x = MP_LITERAL(0x12345); - y = MP_LITERAL(0x3c2c799a365b53d003ef37dab65860bf80ae); - break; - case 2: - x = MP_LITERAL(0x4e1c77e3c00f7c3b15869e6a4e5f86b3ee53); - y = MP_LITERAL(0x5bde01693130591400b5c9d257d8325a44a5); - break; - case 3: - x = MP_LITERAL(0xb5f0e722b2f0f7e729f55ba9f15511e3b399); - y = MP_LITERAL(0x033d636b855c931cfe679f0b18db164a0d64); - break; - case 4: - x = MP_LITERAL(0xb5f0e722b2f0f7e729f55ba9f15511e3b399); - y = MP_LITERAL(0xbe55d3f4b86bc38ff4b6622c418e599546ed); - break; - default: - unreachable("only 5 example Weierstrass points defined"); - } - if (x && y) { - wp = ecc_weierstrass_point_new(wc, x, y); - } else { - wp = ecc_weierstrass_point_new_identity(wc); - } - if (x) - mp_free(x); - if (y) - mp_free(y); - return wp; -} - -static void test_ecc_weierstrass_add(void) -{ - WeierstrassCurve *wc = wcurve(); - WeierstrassPoint *a = ecc_weierstrass_point_new_identity(wc); - WeierstrassPoint *b = ecc_weierstrass_point_new_identity(wc); - for (size_t i = 0; i < looplimit(5); i++) { - for (size_t j = 0; j < looplimit(5); j++) { - if (i == 0 || j == 0 || i == j || - (i==3 && j==4) || (i==4 && j==3)) - continue; /* difficult cases */ - - WeierstrassPoint *A = wpoint(wc, i), *B = wpoint(wc, j); - ecc_weierstrass_point_copy_into(a, A); - ecc_weierstrass_point_copy_into(b, B); - ecc_weierstrass_point_free(A); - ecc_weierstrass_point_free(B); - - log_start(); - WeierstrassPoint *r = ecc_weierstrass_add(a, b); - log_end(); - ecc_weierstrass_point_free(r); - } - } - ecc_weierstrass_point_free(a); - ecc_weierstrass_point_free(b); - ecc_weierstrass_curve_free(wc); -} - -static void test_ecc_weierstrass_double(void) -{ - WeierstrassCurve *wc = wcurve(); - WeierstrassPoint *a = ecc_weierstrass_point_new_identity(wc); - for (size_t i = 0; i < looplimit(5); i++) { - WeierstrassPoint *A = wpoint(wc, i); - ecc_weierstrass_point_copy_into(a, A); - ecc_weierstrass_point_free(A); - - log_start(); - WeierstrassPoint *r = ecc_weierstrass_double(a); - log_end(); - ecc_weierstrass_point_free(r); - } - ecc_weierstrass_point_free(a); - ecc_weierstrass_curve_free(wc); -} - -static void test_ecc_weierstrass_add_general(void) -{ - WeierstrassCurve *wc = wcurve(); - WeierstrassPoint *a = ecc_weierstrass_point_new_identity(wc); - WeierstrassPoint *b = ecc_weierstrass_point_new_identity(wc); - for (size_t i = 0; i < looplimit(5); i++) { - for (size_t j = 0; j < looplimit(5); j++) { - WeierstrassPoint *A = wpoint(wc, i), *B = wpoint(wc, j); - ecc_weierstrass_point_copy_into(a, A); - ecc_weierstrass_point_copy_into(b, B); - ecc_weierstrass_point_free(A); - ecc_weierstrass_point_free(B); - - log_start(); - WeierstrassPoint *r = ecc_weierstrass_add_general(a, b); - log_end(); - ecc_weierstrass_point_free(r); - } - } - ecc_weierstrass_point_free(a); - ecc_weierstrass_point_free(b); - ecc_weierstrass_curve_free(wc); -} - -static void test_ecc_weierstrass_multiply(void) -{ - WeierstrassCurve *wc = wcurve(); - WeierstrassPoint *a = ecc_weierstrass_point_new_identity(wc); - mp_int *exponent = mp_new(56); - for (size_t i = 1; i < looplimit(5); i++) { - WeierstrassPoint *A = wpoint(wc, i); - ecc_weierstrass_point_copy_into(a, A); - ecc_weierstrass_point_free(A); - mp_random_fill(exponent); - - log_start(); - WeierstrassPoint *r = ecc_weierstrass_multiply(a, exponent); - log_end(); - - ecc_weierstrass_point_free(r); - } - ecc_weierstrass_point_free(a); - ecc_weierstrass_curve_free(wc); - mp_free(exponent); -} - -static void test_ecc_weierstrass_is_identity(void) -{ - WeierstrassCurve *wc = wcurve(); - WeierstrassPoint *a = ecc_weierstrass_point_new_identity(wc); - for (size_t i = 1; i < looplimit(5); i++) { - WeierstrassPoint *A = wpoint(wc, i); - ecc_weierstrass_point_copy_into(a, A); - ecc_weierstrass_point_free(A); - - log_start(); - ecc_weierstrass_is_identity(a); - log_end(); - } - ecc_weierstrass_point_free(a); - ecc_weierstrass_curve_free(wc); -} - -static void test_ecc_weierstrass_get_affine(void) -{ - WeierstrassCurve *wc = wcurve(); - WeierstrassPoint *r = ecc_weierstrass_point_new_identity(wc); - for (size_t i = 0; i < looplimit(4); i++) { - WeierstrassPoint *A = wpoint(wc, i), *B = wpoint(wc, i+1); - WeierstrassPoint *R = ecc_weierstrass_add_general(A, B); - ecc_weierstrass_point_copy_into(r, R); - ecc_weierstrass_point_free(A); - ecc_weierstrass_point_free(B); - ecc_weierstrass_point_free(R); - - log_start(); - mp_int *x, *y; - ecc_weierstrass_get_affine(r, &x, &y); - log_end(); - mp_free(x); - mp_free(y); - } - ecc_weierstrass_point_free(r); - ecc_weierstrass_curve_free(wc); -} - -static void test_ecc_weierstrass_decompress(void) -{ - WeierstrassCurve *wc = wcurve(); - - /* As in the mp_modsqrt test, prime the lazy initialisation of the - * ModsqrtContext */ - mp_int *x = mp_new(144); - WeierstrassPoint *a = ecc_weierstrass_point_new_from_x(wc, x, 0); - if (a) /* don't care whether this one succeeded */ - ecc_weierstrass_point_free(a); - - for (size_t p = 0; p < looplimit(2); p++) { - for (size_t i = 1; i < looplimit(5); i++) { - WeierstrassPoint *A = wpoint(wc, i); - mp_int *X; - ecc_weierstrass_get_affine(A, &X, NULL); - mp_copy_into(x, X); - mp_free(X); - ecc_weierstrass_point_free(A); - - log_start(); - WeierstrassPoint *a = ecc_weierstrass_point_new_from_x(wc, x, p); - log_end(); - - ecc_weierstrass_point_free(a); - } - } - mp_free(x); - ecc_weierstrass_curve_free(wc); -} - -static MontgomeryCurve *mcurve(void) -{ - mp_int *p = MP_LITERAL(0xde978eb1db35236a5792e9f0c04d86000659); - mp_int *a = MP_LITERAL(0x799b62a612b1b30e1c23cea6d67b2e33c51a); - mp_int *b = MP_LITERAL(0x944bf9042b56821a8c9e0b49b636c2502b2b); - MontgomeryCurve *mc = ecc_montgomery_curve(p, a, b); - mp_free(p); - mp_free(a); - mp_free(b); - return mc; -} - -static MontgomeryPoint *mpoint(MontgomeryCurve *wc, size_t index) -{ - mp_int *x = NULL; - MontgomeryPoint *mp; - switch (index) { - case 0: - x = MP_LITERAL(31415); - break; - case 1: - x = MP_LITERAL(0x4d352c654c06eecfe19104118857b38398e8); - break; - case 2: - x = MP_LITERAL(0x03fca2a73983bc3434caae3134599cd69cce); - break; - case 3: - x = MP_LITERAL(0xa0fd735ce9b3406498b5f035ee655bda4e15); - break; - case 4: - x = MP_LITERAL(0x7c7f46a00cc286dbe47db39b6d8f5efd920e); - break; - case 5: - x = MP_LITERAL(0x07a6dc30d3b320448e6f8999be417e6b7c6b); - break; - case 6: - x = MP_LITERAL(0x7832da5fc16dfbd358170b2b96896cd3cd06); - break; - default: - unreachable("only 7 example Weierstrass points defined"); - } - mp = ecc_montgomery_point_new(wc, x); - mp_free(x); - return mp; -} - -static void test_ecc_montgomery_diff_add(void) -{ - MontgomeryCurve *wc = mcurve(); - MontgomeryPoint *a = NULL, *b = NULL, *c = NULL; - for (size_t i = 0; i < looplimit(5); i++) { - MontgomeryPoint *A = mpoint(wc, i); - MontgomeryPoint *B = mpoint(wc, i); - MontgomeryPoint *C = mpoint(wc, i); - if (!a) { - a = A; - b = B; - c = C; - } else { - ecc_montgomery_point_copy_into(a, A); - ecc_montgomery_point_copy_into(b, B); - ecc_montgomery_point_copy_into(c, C); - ecc_montgomery_point_free(A); - ecc_montgomery_point_free(B); - ecc_montgomery_point_free(C); - } - - log_start(); - MontgomeryPoint *r = ecc_montgomery_diff_add(b, c, a); - log_end(); - - ecc_montgomery_point_free(r); - } - ecc_montgomery_point_free(a); - ecc_montgomery_point_free(b); - ecc_montgomery_point_free(c); - ecc_montgomery_curve_free(wc); -} - -static void test_ecc_montgomery_double(void) -{ - MontgomeryCurve *wc = mcurve(); - MontgomeryPoint *a = NULL; - for (size_t i = 0; i < looplimit(7); i++) { - MontgomeryPoint *A = mpoint(wc, i); - if (!a) { - a = A; - } else { - ecc_montgomery_point_copy_into(a, A); - ecc_montgomery_point_free(A); - } - - log_start(); - MontgomeryPoint *r = ecc_montgomery_double(a); - log_end(); - - ecc_montgomery_point_free(r); - } - ecc_montgomery_point_free(a); - ecc_montgomery_curve_free(wc); -} - -static void test_ecc_montgomery_multiply(void) -{ - MontgomeryCurve *wc = mcurve(); - MontgomeryPoint *a = NULL; - mp_int *exponent = mp_new(56); - for (size_t i = 0; i < looplimit(7); i++) { - MontgomeryPoint *A = mpoint(wc, i); - if (!a) { - a = A; - } else { - ecc_montgomery_point_copy_into(a, A); - ecc_montgomery_point_free(A); - } - mp_random_fill(exponent); - - log_start(); - MontgomeryPoint *r = ecc_montgomery_multiply(a, exponent); - log_end(); - - ecc_montgomery_point_free(r); - } - ecc_montgomery_point_free(a); - ecc_montgomery_curve_free(wc); - mp_free(exponent); -} - -static void test_ecc_montgomery_get_affine(void) -{ - MontgomeryCurve *wc = mcurve(); - MontgomeryPoint *r = NULL; - for (size_t i = 0; i < looplimit(5); i++) { - MontgomeryPoint *A = mpoint(wc, i); - MontgomeryPoint *B = mpoint(wc, i); - MontgomeryPoint *C = mpoint(wc, i); - MontgomeryPoint *R = ecc_montgomery_diff_add(B, C, A); - ecc_montgomery_point_free(A); - ecc_montgomery_point_free(B); - ecc_montgomery_point_free(C); - if (!r) { - r = R; - } else { - ecc_montgomery_point_copy_into(r, R); - ecc_montgomery_point_free(R); - } - - log_start(); - mp_int *x; - ecc_montgomery_get_affine(r, &x); - log_end(); - - mp_free(x); - } - ecc_montgomery_point_free(r); - ecc_montgomery_curve_free(wc); -} - -static EdwardsCurve *ecurve(void) -{ - mp_int *p = MP_LITERAL(0xfce2dac1704095de0b5c48876c45063cd475); - mp_int *d = MP_LITERAL(0xbd4f77401c3b14ae1742a7d1d367adac8f3e); - mp_int *a = MP_LITERAL(0x51d0845da3fa871aaac4341adea53b861919); - mp_int *nonsquare = mp_from_integer(2); - EdwardsCurve *ec = ecc_edwards_curve(p, d, a, nonsquare); - mp_free(p); - mp_free(d); - mp_free(a); - mp_free(nonsquare); - return ec; -} - -static EdwardsPoint *epoint(EdwardsCurve *wc, size_t index) -{ - mp_int *x, *y; - EdwardsPoint *ep; - switch (index) { - case 0: - x = MP_LITERAL(0x0); - y = MP_LITERAL(0x1); - break; - case 1: - x = MP_LITERAL(0x3d8aef0294a67c1c7e8e185d987716250d7c); - y = MP_LITERAL(0x27184); - break; - case 2: - x = MP_LITERAL(0xf44ed5b8a6debfd3ab24b7874cd2589fd672); - y = MP_LITERAL(0xd635d8d15d367881c8a3af472c8fe487bf40); - break; - case 3: - x = MP_LITERAL(0xde114ecc8b944684415ef81126a07269cd30); - y = MP_LITERAL(0xbe0fd45ff67ebba047ed0ec5a85d22e688a1); - break; - case 4: - x = MP_LITERAL(0x76bd2f90898d271b492c9c20dd7bbfe39fe5); - y = MP_LITERAL(0xbf1c82698b4a5a12c1057631c1ebdc216ae2); - break; - default: - unreachable("only 5 example Edwards points defined"); - } - ep = ecc_edwards_point_new(wc, x, y); - mp_free(x); - mp_free(y); - return ep; -} - -static void test_ecc_edwards_add(void) -{ - EdwardsCurve *ec = ecurve(); - EdwardsPoint *a = NULL, *b = NULL; - for (size_t i = 0; i < looplimit(5); i++) { - for (size_t j = 0; j < looplimit(5); j++) { - EdwardsPoint *A = epoint(ec, i), *B = epoint(ec, j); - if (!a) { - a = A; - b = B; - } else { - ecc_edwards_point_copy_into(a, A); - ecc_edwards_point_copy_into(b, B); - ecc_edwards_point_free(A); - ecc_edwards_point_free(B); - } - - log_start(); - EdwardsPoint *r = ecc_edwards_add(a, b); - log_end(); - - ecc_edwards_point_free(r); - } - } - ecc_edwards_point_free(a); - ecc_edwards_point_free(b); - ecc_edwards_curve_free(ec); -} - -static void test_ecc_edwards_multiply(void) -{ - EdwardsCurve *ec = ecurve(); - EdwardsPoint *a = NULL; - mp_int *exponent = mp_new(56); - for (size_t i = 1; i < looplimit(5); i++) { - EdwardsPoint *A = epoint(ec, i); - if (!a) { - a = A; - } else { - ecc_edwards_point_copy_into(a, A); - ecc_edwards_point_free(A); - } - mp_random_fill(exponent); - - log_start(); - EdwardsPoint *r = ecc_edwards_multiply(a, exponent); - log_end(); - - ecc_edwards_point_free(r); - } - ecc_edwards_point_free(a); - ecc_edwards_curve_free(ec); - mp_free(exponent); -} - -static void test_ecc_edwards_eq(void) -{ - EdwardsCurve *ec = ecurve(); - EdwardsPoint *a = NULL, *b = NULL; - for (size_t i = 0; i < looplimit(5); i++) { - for (size_t j = 0; j < looplimit(5); j++) { - EdwardsPoint *A = epoint(ec, i), *B = epoint(ec, j); - if (!a) { - a = A; - b = B; - } else { - ecc_edwards_point_copy_into(a, A); - ecc_edwards_point_copy_into(b, B); - ecc_edwards_point_free(A); - ecc_edwards_point_free(B); - } - - log_start(); - ecc_edwards_eq(a, b); - log_end(); - } - } - ecc_edwards_point_free(a); - ecc_edwards_point_free(b); - ecc_edwards_curve_free(ec); -} - -static void test_ecc_edwards_get_affine(void) -{ - EdwardsCurve *ec = ecurve(); - EdwardsPoint *r = NULL; - for (size_t i = 0; i < looplimit(4); i++) { - EdwardsPoint *A = epoint(ec, i), *B = epoint(ec, i+1); - EdwardsPoint *R = ecc_edwards_add(A, B); - ecc_edwards_point_free(A); - ecc_edwards_point_free(B); - if (!r) { - r = R; - } else { - ecc_edwards_point_copy_into(r, R); - ecc_edwards_point_free(R); - } - - log_start(); - mp_int *x, *y; - ecc_edwards_get_affine(r, &x, &y); - log_end(); - - mp_free(x); - mp_free(y); - } - ecc_edwards_point_free(r); - ecc_edwards_curve_free(ec); -} - -static void test_ecc_edwards_decompress(void) -{ - EdwardsCurve *ec = ecurve(); - - /* As in the mp_modsqrt test, prime the lazy initialisation of the - * ModsqrtContext */ - mp_int *y = mp_new(144); - EdwardsPoint *a = ecc_edwards_point_new_from_y(ec, y, 0); - if (a) /* don't care whether this one succeeded */ - ecc_edwards_point_free(a); - - for (size_t p = 0; p < looplimit(2); p++) { - for (size_t i = 0; i < looplimit(5); i++) { - EdwardsPoint *A = epoint(ec, i); - mp_int *Y; - ecc_edwards_get_affine(A, NULL, &Y); - mp_copy_into(y, Y); - mp_free(Y); - ecc_edwards_point_free(A); - - log_start(); - EdwardsPoint *a = ecc_edwards_point_new_from_y(ec, y, p); - log_end(); - - ecc_edwards_point_free(a); - } - } - mp_free(y); - ecc_edwards_curve_free(ec); -} - -static void test_cipher(const ssh_cipheralg *calg) -{ - ssh_cipher *c = ssh_cipher_new(calg); - if (!c) { - test_skipped = true; - return; - } - const ssh2_macalg *malg = calg->required_mac; - ssh2_mac *m = NULL; - if (malg) { - m = ssh2_mac_new(malg, c); - if (!m) { - ssh_cipher_free(c); - test_skipped = true; - return; - } - } - - uint8_t *ckey = snewn(calg->padded_keybytes, uint8_t); - uint8_t *civ = snewn(calg->blksize, uint8_t); - uint8_t *mkey = malg ? snewn(malg->keylen, uint8_t) : NULL; - size_t datalen = calg->blksize * 8; - size_t maclen = malg ? malg->len : 0; - uint8_t *data = snewn(datalen + maclen, uint8_t); - size_t lenlen = 4; - uint8_t *lendata = snewn(lenlen, uint8_t); - - for (size_t i = 0; i < looplimit(16); i++) { - random_read(ckey, calg->padded_keybytes); - if (malg) - random_read(mkey, malg->keylen); - random_read(data, datalen); - random_read(lendata, lenlen); - if (i == 0) { - /* Ensure one of our test IVs will cause SDCTR wraparound */ - memset(civ, 0xFF, calg->blksize); - } else { - random_read(civ, calg->blksize); - } - uint8_t seqbuf[4]; - random_read(seqbuf, 4); - uint32_t seq = GET_32BIT_MSB_FIRST(seqbuf); - - log_start(); - ssh_cipher_setkey(c, ckey); - ssh_cipher_setiv(c, civ); - if (m) - ssh2_mac_setkey(m, make_ptrlen(mkey, malg->keylen)); - if (calg->flags & SSH_CIPHER_SEPARATE_LENGTH) - ssh_cipher_encrypt_length(c, data, datalen, seq); - ssh_cipher_encrypt(c, data, datalen); - if (m) { - ssh2_mac_generate(m, data, datalen, seq); - ssh2_mac_verify(m, data, datalen, seq); - } - if (calg->flags & SSH_CIPHER_SEPARATE_LENGTH) - ssh_cipher_decrypt_length(c, data, datalen, seq); - ssh_cipher_decrypt(c, data, datalen); - log_end(); - } - - sfree(ckey); - sfree(civ); - sfree(mkey); - sfree(data); - sfree(lendata); - if (m) - ssh2_mac_free(m); - ssh_cipher_free(c); -} - -#define CIPHER_TESTFN(Y_unused, cipher) \ - static void test_cipher_##cipher(void) { test_cipher(&cipher); } -CIPHERS(CIPHER_TESTFN, Y_unused) - -static void test_mac(const ssh2_macalg *malg) -{ - ssh2_mac *m = ssh2_mac_new(malg, NULL); - if (!m) { - test_skipped = true; - return; - } - - uint8_t *mkey = snewn(malg->keylen, uint8_t); - size_t datalen = 256; - size_t maclen = malg->len; - uint8_t *data = snewn(datalen + maclen, uint8_t); - - for (size_t i = 0; i < looplimit(16); i++) { - random_read(mkey, malg->keylen); - random_read(data, datalen); - uint8_t seqbuf[4]; - random_read(seqbuf, 4); - uint32_t seq = GET_32BIT_MSB_FIRST(seqbuf); - - log_start(); - ssh2_mac_setkey(m, make_ptrlen(mkey, malg->keylen)); - ssh2_mac_generate(m, data, datalen, seq); - ssh2_mac_verify(m, data, datalen, seq); - log_end(); - } - - sfree(mkey); - sfree(data); - ssh2_mac_free(m); -} - -#define MAC_TESTFN(Y_unused, mac) \ - static void test_mac_##mac(void) { test_mac(&mac); } -MACS(MAC_TESTFN, Y_unused) - -static void test_hash(const ssh_hashalg *halg) -{ - ssh_hash *h = ssh_hash_new(halg); - if (!h) { - test_skipped = true; - return; - } - - size_t datalen = 256; - uint8_t *data = snewn(datalen, uint8_t); - uint8_t *hash = snewn(halg->hlen, uint8_t); - - for (size_t i = 0; i < looplimit(16); i++) { - random_read(data, datalen); - - log_start(); - put_data(h, data, datalen); - ssh_hash_final(h, hash); - log_end(); - - h = ssh_hash_new(halg); - } - - sfree(data); - sfree(hash); - ssh_hash_free(h); -} - -#define HASH_TESTFN(Y_unused, hash) \ - static void test_hash_##hash(void) { test_hash(&hash); } -HASHES(HASH_TESTFN, Y_unused) - -struct test { - const char *testname; - void (*testfn)(void); -}; - -static void test_argon2(void) -{ - /* - * We can only expect the Argon2i variant to pass this stringent - * test for no data-dependency, because the other two variants of - * Argon2 have _deliberate_ data-dependency. - */ - size_t inlen = 48+16+24+8; - uint8_t *indata = snewn(inlen, uint8_t); - ptrlen password = make_ptrlen(indata, 48); - ptrlen salt = make_ptrlen(indata+48, 16); - ptrlen secret = make_ptrlen(indata+48+16, 24); - ptrlen assoc = make_ptrlen(indata+48+16+24, 8); - - strbuf *outdata = strbuf_new(); - strbuf_append(outdata, 256); - - for (size_t i = 0; i < looplimit(16); i++) { - strbuf_clear(outdata); - random_read(indata, inlen); - - log_start(); - argon2(Argon2i, 32, 2, 2, 144, password, salt, secret, assoc, outdata); - log_end(); - } - - sfree(indata); - strbuf_free(outdata); -} - -static void test_primegen(const PrimeGenerationPolicy *policy) -{ - static ProgressReceiver null_progress = { .vt = &null_progress_vt }; - - PrimeGenerationContext *pgc = primegen_new_context(policy); - - init_smallprimes(); - mp_int *pcopy = mp_new(128); - - for (size_t i = 0; i < looplimit(2); i++) { - while (true) { - struct random_state st = random_get_state(); - - PrimeCandidateSource *pcs = pcs_new(128); - pcs_set_oneshot(pcs); - pcs_ready(pcs); - mp_int *p = primegen_generate(pgc, pcs, &null_progress); - - if (p) { - mp_copy_into(pcopy, p); - sfree(p); - - random_set_state(st); - - log_start(); - PrimeCandidateSource *pcs = pcs_new(128); - pcs_set_oneshot(pcs); - pcs_ready(pcs); - mp_int *q = primegen_generate(pgc, pcs, &null_progress); - log_end(); - - assert(q); - assert(mp_cmp_eq(pcopy, q)); - mp_free(q); - break; - } - } - } - - mp_free(pcopy); - primegen_free_context(pgc); -} - -static void test_primegen_probabilistic(void) -{ - test_primegen(&primegen_probabilistic); -} - -static const struct test tests[] = { -#define STRUCT_TEST(X) { #X, test_##X }, -TESTLIST(STRUCT_TEST) -#undef STRUCT_TEST -}; - -void dputs(const char *buf) -{ - fputs(buf, stderr); -} - -int main(int argc, char **argv) -{ - bool doing_opts = true; - const char *pname = argv[0]; - uint8_t tests_to_run[lenof(tests)]; - bool keep_outfiles = false; - bool test_names_given = false; - - memset(tests_to_run, 1, sizeof(tests_to_run)); - random_hash = ssh_hash_new(&ssh_sha256); - - while (--argc > 0) { - char *p = *++argv; - - if (p[0] == '-' && doing_opts) { - if (!strcmp(p, "-O")) { - if (--argc <= 0) { - fprintf(stderr, "'-O' expects a directory name\n"); - return 1; - } - outdir = *++argv; - } else if (!strcmp(p, "-k") || !strcmp(p, "--keep")) { - keep_outfiles = true; - } else if (!strcmp(p, "--")) { - doing_opts = false; - } else if (!strcmp(p, "--help")) { - printf(" usage: drrun -c test/sclog/libsclog.so -- " - "%s -O \n", pname); - printf("options: -O " - "put log files in the specified directory\n"); - printf(" -k, --keep " - "do not delete log files for tests that passed\n"); - printf(" also: --help " - "display this text\n"); - return 0; - } else { - fprintf(stderr, "unknown command line option '%s'\n", p); - return 1; - } - } else { - if (!test_names_given) { - test_names_given = true; - memset(tests_to_run, 0, sizeof(tests_to_run)); - } - bool found_one = false; - for (size_t i = 0; i < lenof(tests); i++) { - if (wc_match(p, tests[i].testname)) { - tests_to_run[i] = 1; - found_one = true; - } - } - if (!found_one) { - fprintf(stderr, "no test name matched '%s'\n", p); - return 1; - } - } - } - - bool is_dry_run = dry_run(); - - if (is_dry_run) { - printf("Dry run (DynamoRIO instrumentation not detected)\n"); - } else { - /* Print the address of main() in this run. The idea is that - * if this image is compiled to be position-independent, then - * PC values in the logs won't match the ones you get if you - * disassemble the binary, so it'll be harder to match up the - * log messages to the code. But if you know the address of a - * fixed (and not inlined) function in both worlds, you can - * find out the offset between them. */ - printf("Live run, main = %p\n", (void *)main); - - if (!outdir) { - fprintf(stderr, "expected -O option\n"); - return 1; - } - printf("Will write log files to %s\n", outdir); - } - - size_t nrun = 0, npass = 0; - - for (size_t i = 0; i < lenof(tests); i++) { - bool keep_these_outfiles = true; - - if (!tests_to_run[i]) - continue; - const struct test *test = &tests[i]; - printf("Running test %s ... ", test->testname); - fflush(stdout); - - test_skipped = false; - random_seed(test->testname); - test_basename = test->testname; - test_index = 0; - - test->testfn(); - - if (test_skipped) { - /* Used for e.g. tests of hardware-accelerated crypto when - * the hardware acceleration isn't available */ - printf("skipped\n"); - continue; - } - - nrun++; - - if (is_dry_run) { - printf("dry run done\n"); - continue; /* test files won't exist anyway */ - } - - if (test_index < 2) { - printf("FAIL: test did not generate multiple output files\n"); - goto test_done; - } - - char *firstfile = log_filename(test_basename, 0); - FILE *firstfp = fopen(firstfile, "rb"); - if (!firstfp) { - printf("ERR: %s: open: %s\n", firstfile, strerror(errno)); - goto test_done; - } - for (size_t i = 1; i < test_index; i++) { - char *nextfile = log_filename(test_basename, i); - FILE *nextfp = fopen(nextfile, "rb"); - if (!nextfp) { - printf("ERR: %s: open: %s\n", nextfile, strerror(errno)); - goto test_done; - } - - rewind(firstfp); - char buf1[4096], bufn[4096]; - bool compare_ok = false; - while (true) { - size_t r1 = fread(buf1, 1, sizeof(buf1), firstfp); - size_t rn = fread(bufn, 1, sizeof(bufn), nextfp); - if (r1 != rn) { - printf("FAIL: %s %s: different lengths\n", - firstfile, nextfile); - break; - } - if (r1 == 0) { - if (feof(firstfp) && feof(nextfp)) { - compare_ok = true; - } else { - printf("FAIL: %s %s: error at end of file\n", - firstfile, nextfile); - } - break; - } - if (memcmp(buf1, bufn, r1) != 0) { - printf("FAIL: %s %s: different content\n", - firstfile, nextfile); - break; - } - } - fclose(nextfp); - sfree(nextfile); - if (!compare_ok) { - goto test_done; - } - } - fclose(firstfp); - sfree(firstfile); - - printf("pass\n"); - npass++; - keep_these_outfiles = keep_outfiles; - - test_done: - if (!keep_these_outfiles) { - for (size_t i = 0; i < test_index; i++) { - char *file = log_filename(test_basename, i); - remove(file); - sfree(file); - } - } - } - - ssh_hash_free(random_hash); - - if (npass == nrun) { - printf("All tests passed\n"); - return 0; - } else { - printf("%"SIZEu" tests failed\n", nrun - npass); - return 1; - } -} diff --git a/testzlib.c b/testzlib.c deleted file mode 100644 index 0ef4ef19..00000000 --- a/testzlib.c +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Main program to compile ssh/zlib.c into a zlib decoding tool. - * - * This is potentially a handy tool in its own right for picking apart - * Zip files or PDFs or PNGs, because it accepts the bare Deflate - * format and the zlib wrapper format, unlike 'zcat' which accepts - * only the gzip wrapper format. - * - * It's also useful as a means for a fuzzer to get reasonably direct - * access to PuTTY's zlib decompressor. - */ - -#include -#include -#include -#include - -#include "defs.h" -#include "ssh.h" - -void out_of_memory(void) -{ - fprintf(stderr, "Out of memory!\n"); - exit(1); -} - -void dputs(const char *buf) -{ - fputs(buf, stderr); -} - -int main(int argc, char **argv) -{ - unsigned char buf[16], *outbuf; - int ret, outlen; - ssh_decompressor *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 if (!strcmp(p, "--help")) { - printf("usage: testzlib decode zlib (RFC1950) data" - " from standard input\n"); - printf(" testzlib -d decode Deflate (RFC1951) data" - " from standard input\n"); - printf(" testzlib --help display this text\n"); - return 0; - } 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 = ssh_decompressor_new(&ssh_zlib); - - if (noheader) { - /* - * Provide missing zlib header if -d was specified. - */ - static const unsigned char ersatz_zlib_header[] = { 0x78, 0x9C }; - ssh_decompressor_decompress( - handle, ersatz_zlib_header, sizeof(ersatz_zlib_header), - &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; - ssh_decompressor_decompress(handle, buf, ret, &outbuf, &outlen); - if (outbuf) { - if (outlen) - fwrite(outbuf, 1, outlen, stdout); - sfree(outbuf); - } else { - fprintf(stderr, "decoding error\n"); - fclose(fp); - return 1; - } - } - - ssh_decompressor_free(handle); - - if (filename) - fclose(fp); - - return 0; -} diff --git a/unix/CMakeLists.txt b/unix/CMakeLists.txt index 825ac35f..cff15de0 100644 --- a/unix/CMakeLists.txt +++ b/unix/CMakeLists.txt @@ -98,11 +98,11 @@ add_executable(cgtest target_link_libraries(cgtest keygen console crypto utils) add_executable(testsc - ${CMAKE_SOURCE_DIR}/testsc.c) + ${CMAKE_SOURCE_DIR}/test/testsc.c) target_link_libraries(testsc keygen crypto utils) add_executable(testzlib - ${CMAKE_SOURCE_DIR}/testzlib.c + ${CMAKE_SOURCE_DIR}/test/testzlib.c ${CMAKE_SOURCE_DIR}/ssh/zlib.c) target_link_libraries(testzlib utils) -- cgit v1.2.3 From d13547d5044fa50df0f713e39743c97838d14ea2 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Tue, 23 Nov 2021 18:52:15 +0000 Subject: Move some more files into subdirectories. While I'm in the mood for cleaning up the top-level directory here: all the 'nostuff.c' files have moved into a new 'stubs' directory, and I broke up be_misc.c into smaller modules that can live in 'utils'. --- CMakeLists.txt | 6 +- be_misc.c | 157 --------------------------------------------- network.h | 3 - nocmdline.c | 37 ----------- nogss.c | 11 ---- noprint.c | 38 ----------- norand.c | 22 ------- noterm.c | 16 ----- notiming.c | 21 ------ nullplug.c | 40 ------------ stubs/nocmdline.c | 37 +++++++++++ stubs/nogss.c | 11 ++++ stubs/noprint.c | 38 +++++++++++ stubs/norand.c | 22 +++++++ stubs/noterm.c | 16 +++++ stubs/notiming.c | 21 ++++++ stubs/nullplug.c | 40 ++++++++++++ unix/CMakeLists.txt | 23 ++++--- utils/CMakeLists.txt | 2 + utils/backend_socket_log.c | 62 ++++++++++++++++++ utils/log_proxy_stderr.c | 96 +++++++++++++++++++++++++++ windows/CMakeLists.txt | 12 ++-- 22 files changed, 365 insertions(+), 366 deletions(-) delete mode 100644 be_misc.c delete mode 100644 nocmdline.c delete mode 100644 nogss.c delete mode 100644 noprint.c delete mode 100644 norand.c delete mode 100644 noterm.c delete mode 100644 notiming.c delete mode 100644 nullplug.c create mode 100644 stubs/nocmdline.c create mode 100644 stubs/nogss.c create mode 100644 stubs/noprint.c create mode 100644 stubs/norand.c create mode 100644 stubs/noterm.c create mode 100644 stubs/notiming.c create mode 100644 stubs/nullplug.c create mode 100644 utils/backend_socket_log.c create mode 100644 utils/log_proxy_stderr.c diff --git a/CMakeLists.txt b/CMakeLists.txt index c2e69e3a..a3c74d41 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,7 +33,7 @@ add_library(crypto STATIC add_subdirectory(crypto) add_library(network STATIC - be_misc.c nullplug.c errsock.c logging.c x11disp.c + stubs/nullplug.c errsock.c logging.c x11disp.c proxy/proxy.c proxy/http.c proxy/socks4.c @@ -54,7 +54,7 @@ add_library(guiterminal STATIC $) add_library(noterminal STATIC - noterm.c ldisc.c) + stubs/noterm.c ldisc.c) add_library(all-backends OBJECT pinger.c) @@ -124,7 +124,7 @@ installed_program(psftp) add_executable(psocks ${platform}/psocks.c psocks.c - norand.c + stubs/norand.c proxy/nocproxy.c proxy/nosshproxy.c ssh/portfwd.c) diff --git a/be_misc.c b/be_misc.c deleted file mode 100644 index de15a34e..00000000 --- a/be_misc.c +++ /dev/null @@ -1,157 +0,0 @@ -/* - * be_misc.c: helper functions shared between main network backends. - */ - -#include -#include - -#include "putty.h" -#include "network.h" - -void backend_socket_log(Seat *seat, LogContext *logctx, - PlugLogType type, SockAddr *addr, int port, - const char *error_msg, int error_code, Conf *conf, - bool session_started) -{ - char addrbuf[256], *msg; - - switch (type) { - case PLUGLOG_CONNECT_TRYING: - sk_getaddr(addr, addrbuf, lenof(addrbuf)); - if (sk_addr_needs_port(addr)) { - msg = dupprintf("Connecting to %s port %d", addrbuf, port); - } else { - msg = dupprintf("Connecting to %s", addrbuf); - } - break; - case PLUGLOG_CONNECT_FAILED: - sk_getaddr(addr, addrbuf, lenof(addrbuf)); - msg = dupprintf("Failed to connect to %s: %s", addrbuf, error_msg); - break; - case PLUGLOG_CONNECT_SUCCESS: - if (addr) - sk_getaddr(addr, addrbuf, lenof(addrbuf)); - else /* fallback if address unavailable */ - sprintf(addrbuf, "remote host"); - msg = dupprintf("Connected to %s", addrbuf); - break; - case PLUGLOG_PROXY_MSG: { - /* Proxy-related log messages have their own identifying - * prefix already, put on by our caller. */ - int len, log_to_term; - - /* Suffix \r\n temporarily, so we can log to the terminal. */ - msg = dupprintf("%s\r\n", error_msg); - len = strlen(msg); - assert(len >= 2); - - log_to_term = conf_get_int(conf, CONF_proxy_log_to_term); - if (log_to_term == AUTO) - log_to_term = session_started ? FORCE_OFF : FORCE_ON; - if (log_to_term == FORCE_ON) - seat_stderr(seat, msg, len); - - msg[len-2] = '\0'; /* remove the \r\n again */ - break; - } - default: - msg = NULL; /* shouldn't happen, but placate optimiser */ - break; - } - - if (msg) { - logevent(logctx, msg); - sfree(msg); - } -} - -void psb_init(ProxyStderrBuf *psb) -{ - psb->size = 0; -} - -void log_proxy_stderr(Plug *plug, ProxyStderrBuf *psb, - const void *vdata, size_t len) -{ - const char *data = (const char *)vdata; - - /* - * This helper function allows us to collect the data written to a - * local proxy command's standard error in whatever size chunks we - * happen to get from its pipe, and whenever we have a complete - * line, we pass it to plug_log. - * - * (We also do this when the buffer in psb fills up, to avoid just - * allocating more and more memory forever, and also to keep Event - * Log lines reasonably bounded in size.) - * - * Prerequisites: a plug to log to, and a ProxyStderrBuf stored - * somewhere to collect any not-yet-output partial line. - */ - - while (len > 0) { - /* - * Copy as much data into psb->buf as will fit. - */ - assert(psb->size < lenof(psb->buf)); - size_t to_consume = lenof(psb->buf) - psb->size; - if (to_consume > len) - to_consume = len; - memcpy(psb->buf + psb->size, data, to_consume); - data += to_consume; - len -= to_consume; - psb->size += to_consume; - - /* - * Output any full lines in psb->buf. - */ - size_t pos = 0; - while (pos < psb->size) { - char *nlpos = memchr(psb->buf + pos, '\n', psb->size - pos); - if (!nlpos) - break; - - /* - * Found a newline in the buffer, so we can output a line. - */ - size_t endpos = nlpos - psb->buf; - while (endpos > pos && (psb->buf[endpos-1] == '\n' || - psb->buf[endpos-1] == '\r')) - endpos--; - char *msg = dupprintf( - "proxy: %.*s", (int)(endpos - pos), psb->buf + pos); - plug_log(plug, PLUGLOG_PROXY_MSG, NULL, 0, msg, 0); - sfree(msg); - - pos = nlpos - psb->buf + 1; - assert(pos <= psb->size); - } - - /* - * If the buffer is completely full and we didn't output - * anything, then output the whole thing, flagging it as a - * truncated line. - */ - if (pos == 0 && psb->size == lenof(psb->buf)) { - char *msg = dupprintf( - "proxy (partial line): %.*s", (int)psb->size, psb->buf); - plug_log(plug, PLUGLOG_PROXY_MSG, NULL, 0, msg, 0); - sfree(msg); - - pos = psb->size = 0; - } - - /* - * Now move any remaining data up to the front of the buffer. - */ - size_t newsize = psb->size - pos; - if (newsize) - memmove(psb->buf, psb->buf + pos, newsize); - psb->size = newsize; - - /* - * And loop round again if there's more data to be read from - * our input. - */ - } -} diff --git a/network.h b/network.h index cd8d1a7b..d144d952 100644 --- a/network.h +++ b/network.h @@ -389,9 +389,6 @@ void nullplug_sent(Plug *plug, size_t bufsize); * they use types defined here. */ -/* - * Exports from be_misc.c. - */ void backend_socket_log(Seat *seat, LogContext *logctx, PlugLogType type, SockAddr *addr, int port, const char *error_msg, int error_code, Conf *conf, diff --git a/nocmdline.c b/nocmdline.c deleted file mode 100644 index e4c6d08f..00000000 --- a/nocmdline.c +++ /dev/null @@ -1,37 +0,0 @@ -/* - * nocmdline.c - stubs in applications which don't do the - * standard(ish) PuTTY tools' command-line parsing - */ - -#include -#include -#include -#include "putty.h" - -/* - * Stub version of the function in cmdline.c which provides the - * password to SSH authentication by remembering it having been passed - * as a command-line option. If we're not doing normal command-line - * handling, then there is no such option, so that function always - * returns failure. - */ -int cmdline_get_passwd_input(prompts_t *p) -{ - return -1; -} - -/* - * The main cmdline_process_param function is normally called from - * applications' main(). An application linking against this stub - * module shouldn't have a main() that calls it in the first place :-) - * but it is just occasionally called by other supporting functions, - * such as one in uxputty.c which sometimes handles a non-option - * argument by making up equivalent options and passing them back to - * this function. So we have to provide a link-time stub of this - * function, but it had better not end up being called at run time. - */ -int cmdline_process_param(const char *p, char *value, - int need_save, Conf *conf) -{ - unreachable("cmdline_process_param should never be called"); -} diff --git a/nogss.c b/nogss.c deleted file mode 100644 index 844a1323..00000000 --- a/nogss.c +++ /dev/null @@ -1,11 +0,0 @@ -/* - * 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 } }; diff --git a/noprint.c b/noprint.c deleted file mode 100644 index 941da68c..00000000 --- a/noprint.c +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Stub implementation of the printing interface for PuTTY, for the - * benefit of non-printing terminal applications. - */ - -#include -#include -#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, const void *data, size_t 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) -{ -} diff --git a/norand.c b/norand.c deleted file mode 100644 index 2ad9f661..00000000 --- a/norand.c +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Stub implementations of RNG functions for applications without an RNG. - */ - -#include "putty.h" - -void random_read(void *out, size_t size) -{ - unreachable("Random numbers are not available in this application"); -} - -void random_save_seed(void) -{ -} - -void random_destroy_seed(void) -{ -} - -void noise_ultralight(NoiseSourceId id, unsigned long data) -{ -} diff --git a/noterm.c b/noterm.c deleted file mode 100644 index b5329aec..00000000 --- a/noterm.c +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Stubs of functions in terminal.c, for use in programs that don't - * have a terminal. - */ - -#include "putty.h" -#include "terminal.h" - -void term_nopaste(Terminal *term) -{ -} - -int term_get_userpass_input(Terminal *term, prompts_t *p) -{ - return 0; -} diff --git a/notiming.c b/notiming.c deleted file mode 100644 index 3feb5cdf..00000000 --- a/notiming.c +++ /dev/null @@ -1,21 +0,0 @@ -/* - * 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" - -unsigned long schedule_timer(int ticks, timer_fn_t fn, void *ctx) -{ - return 0; -} - -void expire_timer_context(void *ctx) -{ -} diff --git a/nullplug.c b/nullplug.c deleted file mode 100644 index d583d156..00000000 --- a/nullplug.c +++ /dev/null @@ -1,40 +0,0 @@ -/* - * nullplug.c: provide a null implementation of the Plug vtable which - * ignores all calls. Occasionally useful in cases where we want to - * make a network connection just to see if it works, but not do - * anything with it afterwards except close it again. - */ - -#include "putty.h" - -void nullplug_log(Plug *plug, PlugLogType type, SockAddr *addr, - int port, const char *err_msg, int err_code) -{ -} - -void nullplug_closing(Plug *plug, PlugCloseType type, const char *error_msg) -{ -} - -void nullplug_receive(Plug *plug, int urgent, const char *data, size_t len) -{ -} - -void nullplug_sent(Plug *plug, size_t bufsize) -{ -} - -static const PlugVtable nullplug_plugvt = { - .log = nullplug_log, - .closing = nullplug_closing, - .receive = nullplug_receive, - .sent = nullplug_sent, -}; - -static Plug nullplug_plug = { &nullplug_plugvt }; - -/* - * There's a singleton instance of nullplug, because it's not - * interesting enough to worry about making more than one of them. - */ -Plug *const nullplug = &nullplug_plug; diff --git a/stubs/nocmdline.c b/stubs/nocmdline.c new file mode 100644 index 00000000..e4c6d08f --- /dev/null +++ b/stubs/nocmdline.c @@ -0,0 +1,37 @@ +/* + * nocmdline.c - stubs in applications which don't do the + * standard(ish) PuTTY tools' command-line parsing + */ + +#include +#include +#include +#include "putty.h" + +/* + * Stub version of the function in cmdline.c which provides the + * password to SSH authentication by remembering it having been passed + * as a command-line option. If we're not doing normal command-line + * handling, then there is no such option, so that function always + * returns failure. + */ +int cmdline_get_passwd_input(prompts_t *p) +{ + return -1; +} + +/* + * The main cmdline_process_param function is normally called from + * applications' main(). An application linking against this stub + * module shouldn't have a main() that calls it in the first place :-) + * but it is just occasionally called by other supporting functions, + * such as one in uxputty.c which sometimes handles a non-option + * argument by making up equivalent options and passing them back to + * this function. So we have to provide a link-time stub of this + * function, but it had better not end up being called at run time. + */ +int cmdline_process_param(const char *p, char *value, + int need_save, Conf *conf) +{ + unreachable("cmdline_process_param should never be called"); +} diff --git a/stubs/nogss.c b/stubs/nogss.c new file mode 100644 index 00000000..844a1323 --- /dev/null +++ b/stubs/nogss.c @@ -0,0 +1,11 @@ +/* + * 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 } }; diff --git a/stubs/noprint.c b/stubs/noprint.c new file mode 100644 index 00000000..941da68c --- /dev/null +++ b/stubs/noprint.c @@ -0,0 +1,38 @@ +/* + * Stub implementation of the printing interface for PuTTY, for the + * benefit of non-printing terminal applications. + */ + +#include +#include +#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, const void *data, size_t 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) +{ +} diff --git a/stubs/norand.c b/stubs/norand.c new file mode 100644 index 00000000..2ad9f661 --- /dev/null +++ b/stubs/norand.c @@ -0,0 +1,22 @@ +/* + * Stub implementations of RNG functions for applications without an RNG. + */ + +#include "putty.h" + +void random_read(void *out, size_t size) +{ + unreachable("Random numbers are not available in this application"); +} + +void random_save_seed(void) +{ +} + +void random_destroy_seed(void) +{ +} + +void noise_ultralight(NoiseSourceId id, unsigned long data) +{ +} diff --git a/stubs/noterm.c b/stubs/noterm.c new file mode 100644 index 00000000..b5329aec --- /dev/null +++ b/stubs/noterm.c @@ -0,0 +1,16 @@ +/* + * Stubs of functions in terminal.c, for use in programs that don't + * have a terminal. + */ + +#include "putty.h" +#include "terminal.h" + +void term_nopaste(Terminal *term) +{ +} + +int term_get_userpass_input(Terminal *term, prompts_t *p) +{ + return 0; +} diff --git a/stubs/notiming.c b/stubs/notiming.c new file mode 100644 index 00000000..3feb5cdf --- /dev/null +++ b/stubs/notiming.c @@ -0,0 +1,21 @@ +/* + * 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" + +unsigned long schedule_timer(int ticks, timer_fn_t fn, void *ctx) +{ + return 0; +} + +void expire_timer_context(void *ctx) +{ +} diff --git a/stubs/nullplug.c b/stubs/nullplug.c new file mode 100644 index 00000000..d583d156 --- /dev/null +++ b/stubs/nullplug.c @@ -0,0 +1,40 @@ +/* + * nullplug.c: provide a null implementation of the Plug vtable which + * ignores all calls. Occasionally useful in cases where we want to + * make a network connection just to see if it works, but not do + * anything with it afterwards except close it again. + */ + +#include "putty.h" + +void nullplug_log(Plug *plug, PlugLogType type, SockAddr *addr, + int port, const char *err_msg, int err_code) +{ +} + +void nullplug_closing(Plug *plug, PlugCloseType type, const char *error_msg) +{ +} + +void nullplug_receive(Plug *plug, int urgent, const char *data, size_t len) +{ +} + +void nullplug_sent(Plug *plug, size_t bufsize) +{ +} + +static const PlugVtable nullplug_plugvt = { + .log = nullplug_log, + .closing = nullplug_closing, + .receive = nullplug_receive, + .sent = nullplug_sent, +}; + +static Plug nullplug_plug = { &nullplug_plugvt }; + +/* + * There's a singleton instance of nullplug, because it's not + * interesting enough to worry about making more than one of them. + */ +Plug *const nullplug = &nullplug_plug; diff --git a/unix/CMakeLists.txt b/unix/CMakeLists.txt index cff15de0..1030f486 100644 --- a/unix/CMakeLists.txt +++ b/unix/CMakeLists.txt @@ -51,7 +51,7 @@ add_executable(fuzzterm ${CMAKE_SOURCE_DIR}/fuzzterm.c ${CMAKE_SOURCE_DIR}/be_none.c ${CMAKE_SOURCE_DIR}/logging.c - ${CMAKE_SOURCE_DIR}/noprint.c + ${CMAKE_SOURCE_DIR}/stubs/noprint.c unicode.c no-gtk.c) add_dependencies(fuzzterm generated_licence_h) @@ -69,7 +69,7 @@ add_sources_from_current_dir(psocks no-gtk.c) add_executable(psusan psusan.c ${CMAKE_SOURCE_DIR}/be_none.c - ${CMAKE_SOURCE_DIR}/nogss.c + ${CMAKE_SOURCE_DIR}/stubs/nogss.c ${CMAKE_SOURCE_DIR}/ssh/scpserver.c no-gtk.c pty.c) @@ -78,7 +78,7 @@ target_link_libraries(psusan installed_program(psusan) add_library(puttygen-common OBJECT - ${CMAKE_SOURCE_DIR}/notiming.c + ${CMAKE_SOURCE_DIR}/stubs/notiming.c keygen-noise.c no-gtk.c noise.c @@ -112,7 +112,7 @@ add_executable(uppity ${CMAKE_SOURCE_DIR}/ssh/scpserver.c no-gtk.c pty.c - ${CMAKE_SOURCE_DIR}/nogss.c) + ${CMAKE_SOURCE_DIR}/stubs/nogss.c) target_link_libraries(uppity eventloop sshserver keygen settings network crypto utils) @@ -131,9 +131,8 @@ if(GTK_FOUND) add_executable(pageant pageant.c - ${CMAKE_SOURCE_DIR}/be_misc.c ${CMAKE_SOURCE_DIR}/be_none.c - ${CMAKE_SOURCE_DIR}/nogss.c + ${CMAKE_SOURCE_DIR}/stubs/nogss.c askpass.c x11.c noise.c @@ -148,7 +147,7 @@ if(GTK_FOUND) pterm.c main-gtk-simple.c ${CMAKE_SOURCE_DIR}/be_none.c - ${CMAKE_SOURCE_DIR}/nogss.c + ${CMAKE_SOURCE_DIR}/stubs/nogss.c ${CMAKE_SOURCE_DIR}/proxy/nosshproxy.c pty.c) target_link_libraries(pterm @@ -159,9 +158,9 @@ if(GTK_FOUND) add_executable(ptermapp pterm.c main-gtk-application.c - ${CMAKE_SOURCE_DIR}/nocmdline.c + ${CMAKE_SOURCE_DIR}/stubs/nocmdline.c ${CMAKE_SOURCE_DIR}/be_none.c - ${CMAKE_SOURCE_DIR}/nogss.c + ${CMAKE_SOURCE_DIR}/stubs/nogss.c ${CMAKE_SOURCE_DIR}/proxy/nosshproxy.c pty.c) target_link_libraries(ptermapp @@ -183,7 +182,7 @@ if(GTK_FOUND) add_executable(puttyapp putty.c main-gtk-application.c - ${CMAKE_SOURCE_DIR}/nocmdline.c + ${CMAKE_SOURCE_DIR}/stubs/nocmdline.c ${CMAKE_SOURCE_DIR}/be_all_s.c) target_link_libraries(puttyapp guiterminal eventloop sshclient otherbackends settings @@ -194,8 +193,8 @@ if(GTK_FOUND) putty.c main-gtk-simple.c ${CMAKE_SOURCE_DIR}/be_nos_s.c - ${CMAKE_SOURCE_DIR}/nogss.c - ${CMAKE_SOURCE_DIR}/norand.c + ${CMAKE_SOURCE_DIR}/stubs/nogss.c + ${CMAKE_SOURCE_DIR}/stubs/norand.c ${CMAKE_SOURCE_DIR}/proxy/nocproxy.c ${CMAKE_SOURCE_DIR}/proxy/nosshproxy.c) target_link_libraries(puttytel diff --git a/utils/CMakeLists.txt b/utils/CMakeLists.txt index 97557eb6..73e120db 100644 --- a/utils/CMakeLists.txt +++ b/utils/CMakeLists.txt @@ -1,5 +1,6 @@ add_sources_from_current_dir(utils antispoof.c + backend_socket_log.c base64_decode_atom.c base64_encode_atom.c bufchain.c @@ -23,6 +24,7 @@ add_sources_from_current_dir(utils host_strcspn.c host_strduptrim.c host_strrchr.c + log_proxy_stderr.c marshal.c memory.c memxor.c diff --git a/utils/backend_socket_log.c b/utils/backend_socket_log.c new file mode 100644 index 00000000..783cca31 --- /dev/null +++ b/utils/backend_socket_log.c @@ -0,0 +1,62 @@ +#include +#include + +#include "putty.h" +#include "network.h" + +void backend_socket_log(Seat *seat, LogContext *logctx, + PlugLogType type, SockAddr *addr, int port, + const char *error_msg, int error_code, Conf *conf, + bool session_started) +{ + char addrbuf[256], *msg; + + switch (type) { + case PLUGLOG_CONNECT_TRYING: + sk_getaddr(addr, addrbuf, lenof(addrbuf)); + if (sk_addr_needs_port(addr)) { + msg = dupprintf("Connecting to %s port %d", addrbuf, port); + } else { + msg = dupprintf("Connecting to %s", addrbuf); + } + break; + case PLUGLOG_CONNECT_FAILED: + sk_getaddr(addr, addrbuf, lenof(addrbuf)); + msg = dupprintf("Failed to connect to %s: %s", addrbuf, error_msg); + break; + case PLUGLOG_CONNECT_SUCCESS: + if (addr) + sk_getaddr(addr, addrbuf, lenof(addrbuf)); + else /* fallback if address unavailable */ + sprintf(addrbuf, "remote host"); + msg = dupprintf("Connected to %s", addrbuf); + break; + case PLUGLOG_PROXY_MSG: { + /* Proxy-related log messages have their own identifying + * prefix already, put on by our caller. */ + int len, log_to_term; + + /* Suffix \r\n temporarily, so we can log to the terminal. */ + msg = dupprintf("%s\r\n", error_msg); + len = strlen(msg); + assert(len >= 2); + + log_to_term = conf_get_int(conf, CONF_proxy_log_to_term); + if (log_to_term == AUTO) + log_to_term = session_started ? FORCE_OFF : FORCE_ON; + if (log_to_term == FORCE_ON) + seat_stderr(seat, msg, len); + + msg[len-2] = '\0'; /* remove the \r\n again */ + break; + } + default: + msg = NULL; /* shouldn't happen, but placate optimiser */ + break; + } + + if (msg) { + logevent(logctx, msg); + sfree(msg); + } +} diff --git a/utils/log_proxy_stderr.c b/utils/log_proxy_stderr.c new file mode 100644 index 00000000..90025e08 --- /dev/null +++ b/utils/log_proxy_stderr.c @@ -0,0 +1,96 @@ +#include +#include + +#include "putty.h" +#include "network.h" + +void psb_init(ProxyStderrBuf *psb) +{ + psb->size = 0; +} + +void log_proxy_stderr(Plug *plug, ProxyStderrBuf *psb, + const void *vdata, size_t len) +{ + const char *data = (const char *)vdata; + + /* + * This helper function allows us to collect the data written to a + * local proxy command's standard error in whatever size chunks we + * happen to get from its pipe, and whenever we have a complete + * line, we pass it to plug_log. + * + * (We also do this when the buffer in psb fills up, to avoid just + * allocating more and more memory forever, and also to keep Event + * Log lines reasonably bounded in size.) + * + * Prerequisites: a plug to log to, and a ProxyStderrBuf stored + * somewhere to collect any not-yet-output partial line. + */ + + while (len > 0) { + /* + * Copy as much data into psb->buf as will fit. + */ + assert(psb->size < lenof(psb->buf)); + size_t to_consume = lenof(psb->buf) - psb->size; + if (to_consume > len) + to_consume = len; + memcpy(psb->buf + psb->size, data, to_consume); + data += to_consume; + len -= to_consume; + psb->size += to_consume; + + /* + * Output any full lines in psb->buf. + */ + size_t pos = 0; + while (pos < psb->size) { + char *nlpos = memchr(psb->buf + pos, '\n', psb->size - pos); + if (!nlpos) + break; + + /* + * Found a newline in the buffer, so we can output a line. + */ + size_t endpos = nlpos - psb->buf; + while (endpos > pos && (psb->buf[endpos-1] == '\n' || + psb->buf[endpos-1] == '\r')) + endpos--; + char *msg = dupprintf( + "proxy: %.*s", (int)(endpos - pos), psb->buf + pos); + plug_log(plug, PLUGLOG_PROXY_MSG, NULL, 0, msg, 0); + sfree(msg); + + pos = nlpos - psb->buf + 1; + assert(pos <= psb->size); + } + + /* + * If the buffer is completely full and we didn't output + * anything, then output the whole thing, flagging it as a + * truncated line. + */ + if (pos == 0 && psb->size == lenof(psb->buf)) { + char *msg = dupprintf( + "proxy (partial line): %.*s", (int)psb->size, psb->buf); + plug_log(plug, PLUGLOG_PROXY_MSG, NULL, 0, msg, 0); + sfree(msg); + + pos = psb->size = 0; + } + + /* + * Now move any remaining data up to the front of the buffer. + */ + size_t newsize = psb->size - pos; + if (newsize) + memmove(psb->buf, psb->buf + pos, newsize); + psb->size = newsize; + + /* + * And loop round again if there's more data to be read from + * our input. + */ + } +} diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt index c432c6e5..10769ee1 100644 --- a/windows/CMakeLists.txt +++ b/windows/CMakeLists.txt @@ -109,8 +109,8 @@ add_executable(puttytel putty.c help.c ${CMAKE_SOURCE_DIR}/be_nos_s.c - ${CMAKE_SOURCE_DIR}/nogss.c - ${CMAKE_SOURCE_DIR}/norand.c + ${CMAKE_SOURCE_DIR}/stubs/nogss.c + ${CMAKE_SOURCE_DIR}/stubs/norand.c ${CMAKE_SOURCE_DIR}/proxy/nocproxy.c ${CMAKE_SOURCE_DIR}/proxy/nosshproxy.c puttytel.rc) @@ -125,7 +125,7 @@ installed_program(puttytel) add_executable(puttygen puttygen.c - ${CMAKE_SOURCE_DIR}/notiming.c + ${CMAKE_SOURCE_DIR}/stubs/notiming.c noise.c no-jump-list.c storage.c @@ -150,9 +150,9 @@ if(HAVE_CONPTY) help.c conpty.c be_conpty.c - ${CMAKE_SOURCE_DIR}/nogss.c - ${CMAKE_SOURCE_DIR}/norand.c - ${CMAKE_SOURCE_DIR}/proxy/nosshproxy.c + ${CMAKE_SOURCE_DIR}/stubs/nogss.c + ${CMAKE_SOURCE_DIR}/stubs/norand.c + ${CMAKE_SOURCE_DIR}/proxy/stubs/nosshproxy.c pterm.rc) add_dependencies(pterm generated_licence_h) target_link_libraries(pterm -- cgit v1.2.3 From 3260e429a13efdbc8e371206f26c2761b365ab13 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Wed, 24 Nov 2021 19:02:40 +0000 Subject: Move STR() and CAT() into defs.h. I'm actually quite surprised there was only _one_ copy of each of these standard macros in the code base, given my general habit of casually redefining them anywhere I need them! But each one was in a silly place. Moved them up to the top level where they're available globally. --- defs.h | 24 ++++++++++++++++++++++++ unix/gtk-common.c | 2 -- windows/platform.h | 2 -- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/defs.h b/defs.h index 437bef97..c30ac5cf 100644 --- a/defs.h +++ b/defs.h @@ -210,4 +210,28 @@ typedef struct PacketProtocolLayer PacketProtocolLayer; #define NORETURN #endif +/* + * Standard macro definitions. STR() behaves like the preprocessor + * stringification # operator, and CAT() behaves like the token paste + * ## operator, except that each one macro-expands its argument(s) + * first, unlike the raw version. E.g. + * + * #__LINE__ -> "__LINE__" + * STR(__LINE__) -> "1234" (or whatever) + * + * and similarly, + * + * foo ## __LINE__ -> foo__LINE__ + * CAT(foo, __LINE__) -> foo1234 (or whatever) + * + * The expansion is achieved by having each macro pass its arguments + * to a secondary inner macro, because parameter lists of a macro call + * get expanded before the called macro is invoked. So STR(__LINE__) + * -> STR_INNER(1234) -> #1234 -> "1234", and similarly for CAT. + */ +#define STR_INNER(x) #x +#define STR(x) STR_INNER(x) +#define CAT_INNER(x,y) x ## y +#define CAT(x,y) CAT_INNER(x,y) + #endif /* PUTTY_DEFS_H */ diff --git a/unix/gtk-common.c b/unix/gtk-common.c index af39a83c..da653253 100644 --- a/unix/gtk-common.c +++ b/unix/gtk-common.c @@ -44,8 +44,6 @@ #include #endif -#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) diff --git a/windows/platform.h b/windows/platform.h index cfd47281..78ea1e59 100644 --- a/windows/platform.h +++ b/windows/platform.h @@ -144,8 +144,6 @@ static inline uintmax_t strtoumax(const char *nptr, char **endptr, int base) /* If you DECL_WINDOWS_FUNCTION as extern in a header file, use this to * define the function pointer in a source file */ #define DEF_WINDOWS_FUNCTION(name) t_##name p_##name -#define STR1(x) #x -#define STR(x) STR1(x) #define GET_WINDOWS_FUNCTION_PP(module, name) \ TYPECHECK((t_##name)NULL == name, \ (p_##name = module ? \ -- cgit v1.2.3 From 53f7da8ce74e998e9987fddbbd73e6910064e87d Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 26 Nov 2021 17:58:55 +0000 Subject: Merge be_*.c into one ifdef-controlled module. This commit replaces all those fiddly little linking modules (be_all.c, be_none.c, be_ssh.c etc) with a single source file controlled by ifdefs, and introduces a function be_list() in setup.cmake that makes it easy to compile a version of it appropriate to each application. This is a net reduction in code according to 'git diff --stat', even though I've introduced more comments. It also gets rid of another pile of annoying little source files in the top-level directory that didn't deserve to take up so much room in 'ls'. More concretely, doing this has some maintenance advantages. Centralisation means less to maintain (e.g. n_ui_backends is worked out once in a way that makes sense everywhere), and also, 'appname' can now be reliably set per program. Previously, some programs got the wrong appname due to sharing the same linking module (e.g. Plink had appname="PuTTY"), which was a latent bug that would have manifested if I'd wanted to reuse the same string in another context. One thing I've changed in this rework is that Windows pterm no longer has the ConPTY backend in its backends[]: it now has an empty one. The special be_conpty.c module shouldn't really have been there in the first place: it was used in the very earliest uncommitted drafts of the ConPTY work, where I was using another method of selecting that backend, but now that Windows pterm has a dedicated backend_vt_from_conf() that refers to conpty_backend by name, it has no need to live in backends[] at all, just as it doesn't have to in Unix pterm. --- CMakeLists.txt | 17 ++++--- be_all.c | 31 ------------- be_all_s.c | 32 -------------- be_list.c | 118 +++++++++++++++++++++++++++++++++++++++++++++++++ be_none.c | 15 ------- be_nos_s.c | 22 --------- be_nossh.c | 21 --------- be_ssh.c | 18 -------- cmake/setup.cmake | 14 ++++++ fuzzterm.c | 1 - pscp.c | 2 - psftp.c | 2 - unix/CMakeLists.txt | 22 ++++----- unix/pageant.c | 2 - unix/psusan.c | 2 - unix/pterm.c | 1 - unix/uppity.c | 2 - windows/CMakeLists.txt | 8 ++-- windows/be_conpty.c | 13 ------ 19 files changed, 158 insertions(+), 185 deletions(-) delete mode 100644 be_all.c delete mode 100644 be_all_s.c create mode 100644 be_list.c delete mode 100644 be_none.c delete mode 100644 be_nos_s.c delete mode 100644 be_nossh.c delete mode 100644 be_ssh.c delete mode 100644 windows/be_conpty.c diff --git a/CMakeLists.txt b/CMakeLists.txt index a3c74d41..6acd9181 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -97,8 +97,13 @@ add_executable(bidi_test target_link_libraries(bidi_test guiterminal utils ${platform_libraries}) add_executable(plink - ${platform}/plink.c - be_all_s.c) + ${platform}/plink.c) +# Note: if we ever port Plink to a platform where we can't implement a +# serial backend, this be_list command will need to become platform- +# dependent, so that it only sets the SERIAL option on platforms where +# that backend exists. For the moment, though, we have serial port +# backends for both our platforms, so we can do this unconditionally. +be_list(plink Plink SSH SERIAL OTHERBACKENDS) target_link_libraries(plink eventloop noterminal console sshclient otherbackends settings network crypto utils @@ -106,16 +111,16 @@ target_link_libraries(plink installed_program(plink) add_executable(pscp - pscp.c - be_ssh.c) + pscp.c) +be_list(pscp PSCP SSH) target_link_libraries(pscp sftpclient eventloop console sshclient settings network crypto utils ${platform_libraries}) installed_program(pscp) add_executable(psftp - psftp.c - be_ssh.c) + psftp.c) +be_list(psftp PSFTP SSH) target_link_libraries(psftp sftpclient eventloop console sshclient settings network crypto utils ${platform_libraries}) diff --git a/be_all.c b/be_all.c deleted file mode 100644 index 668541b8..00000000 --- a/be_all.c +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Linking module for PuTTY proper: list the available backends - * including ssh. - */ - -#include -#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"; - -const int be_default_protocol = PROT_SSH; - -const struct BackendVtable *const backends[] = { - &ssh_backend, - &telnet_backend, - &rlogin_backend, - &supdup_backend, - &raw_backend, - &sshconn_backend, - NULL -}; - -const size_t n_ui_backends = 1; diff --git a/be_all_s.c b/be_all_s.c deleted file mode 100644 index 4e502930..00000000 --- a/be_all_s.c +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Linking module for PuTTY proper: list the available backends - * including ssh, plus the serial backend. - */ - -#include -#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"; - -const int be_default_protocol = PROT_SSH; - -const struct BackendVtable *const backends[] = { - &ssh_backend, - &serial_backend, - &telnet_backend, - &rlogin_backend, - &supdup_backend, - &raw_backend, - &sshconn_backend, - NULL -}; - -const size_t n_ui_backends = 2; diff --git a/be_list.c b/be_list.c new file mode 100644 index 00000000..09437a69 --- /dev/null +++ b/be_list.c @@ -0,0 +1,118 @@ +/* + * Source file that is rebuilt per application, and provides the list + * of backends, the default protocol, and the application name. + * + * This file expects the build system to provide some per-application + * definitions on the compiler command line. So you don't just add it + * directly to the sources list for an application. Instead you call + * the be_list() function defined in setup.cmake, e.g. + * + * be_list(target-name AppName [SSH] [SERIAL] [OTHERBACKENDS]) + * + * This translates into the following command-line macro definitions + * used by the code below: + * + * - APPNAME should be defined to the name of the program, in + * user-facing capitalisation (e.g. PuTTY rather than putty). + * Unquoted: it's easier to stringify it in the preprocessor than + * to persuade cmake to put the right quotes on the command line on + * all build platforms. + * + * - The following macros should each be defined to 1 if a given set + * of backends should be added to the backends[] list, or 0 if they + * should not be: + * + * * SSH: the two SSH backends (SSH proper, and bare-ssh-connection) + * + * * SERIAL: the serial port backend + * + * * OTHERBACKENDS: the non-cryptographic network protocol backends + * (Telnet, Rlogin, SUPDUP, Raw) + */ + +#include +#include "putty.h" + +const char *const appname = STR(APPNAME); + +/* + * Define the default protocol for the application. This is always a + * network backend (serial ports come second behind network, in every + * case). Applications that don't have either (such as pterm) don't + * need this variable anyway, so just set it to -1. + */ +#if SSH +const int be_default_protocol = PROT_SSH; +#elif OTHERBACKENDS +const int be_default_protocol = PROT_TELNET; +#else +const int be_default_protocol = -1; +#endif + +/* + * List all the configured backends, in the order they should appear + * in the config box. + */ +const struct BackendVtable *const backends[] = { + /* + * Start with the most-preferred network-remote-login protocol. + * That's SSH if present, otherwise Telnet if present. + */ +#if SSH + &ssh_backend, +#elif OTHERBACKENDS + &telnet_backend, /* Telnet at the top if SSH is absent */ +#endif + + /* + * Second on the list is the serial-port backend, if available. + */ +#if SERIAL + &serial_backend, +#endif + + /* + * After that come the remaining network protocols: Telnet if it + * hasn't already appeared above, and Rlogin, SUPDUP and Raw. + */ +#if OTHERBACKENDS && SSH + &telnet_backend, /* only if SSH displaced it at the top */ +#endif +#if OTHERBACKENDS + &rlogin_backend, + &supdup_backend, + &raw_backend, +#endif + + /* + * Bare ssh-connection / PSUSAN is a niche protocol and goes well + * down the list. + */ +#if SSH + &sshconn_backend, +#endif + + /* + * Done. Null pointer to mark the end of the list. + */ + NULL +}; + +/* + * Number of backends at the start of the above list that should have + * radio buttons in the config UI. + * + * The rule is: the most-preferred network backend, and Serial, each + * get a radio button if present. + * + * The rest will be relegated to a dropdown list. + */ +const size_t n_ui_backends = + 0 +#if SSH || OTHERBACKENDS + + 1 +#endif +#if SERIAL + + 1 +#endif + ; diff --git a/be_none.c b/be_none.c deleted file mode 100644 index 588bb1d4..00000000 --- a/be_none.c +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Linking module for programs that do not support selection of backend - * (such as pterm). - */ - -#include -#include "putty.h" - -const int be_default_protocol = -1; - -const struct BackendVtable *const backends[] = { - NULL -}; - -const size_t n_ui_backends = 0; diff --git a/be_nos_s.c b/be_nos_s.c deleted file mode 100644 index 097433aa..00000000 --- a/be_nos_s.c +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Linking module for PuTTYtel: list the available backends not - * including ssh. - */ - -#include -#include "putty.h" - -const int be_default_protocol = PROT_TELNET; - -const char *const appname = "PuTTYtel"; - -const struct BackendVtable *const backends[] = { - &telnet_backend, - &serial_backend, - &rlogin_backend, - &supdup_backend, - &raw_backend, - NULL -}; - -const size_t n_ui_backends = 2; diff --git a/be_nossh.c b/be_nossh.c deleted file mode 100644 index 1c94f3ae..00000000 --- a/be_nossh.c +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Linking module for PuTTYtel: list the available backends not - * including ssh. - */ - -#include -#include "putty.h" - -const int be_default_protocol = PROT_TELNET; - -const char *const appname = "PuTTYtel"; - -const struct BackendVtable *const backends[] = { - &telnet_backend, - &rlogin_backend, - &supdup_backend, - &raw_backend, - NULL -}; - -const size_t n_ui_backends = 1; diff --git a/be_ssh.c b/be_ssh.c deleted file mode 100644 index 81be62d8..00000000 --- a/be_ssh.c +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Linking module for programs that are restricted to only using - * SSH-type protocols (pscp and psftp). These still have a choice of - * two actual backends, because they can also speak PROT_SSHCONN. - */ - -#include -#include "putty.h" - -const int be_default_protocol = PROT_SSH; - -const struct BackendVtable *const backends[] = { - &ssh_backend, - &sshconn_backend, - NULL -}; - -const size_t n_ui_backends = 0; /* not used in programs with a config UI */ diff --git a/cmake/setup.cmake b/cmake/setup.cmake index 6612de97..26079f31 100644 --- a/cmake/setup.cmake +++ b/cmake/setup.cmake @@ -78,6 +78,20 @@ else() set(platform unix) endif() +function(be_list TARGET NAME) + cmake_parse_arguments(OPT "SSH;SERIAL;OTHERBACKENDS" "" "" "${ARGN}") + add_library(${TARGET}-be-list OBJECT ${CMAKE_SOURCE_DIR}/be_list.c) + foreach(setting SSH SERIAL OTHERBACKENDS) + if(OPT_${setting}) + target_compile_definitions(${TARGET}-be-list PRIVATE ${setting}=1) + else() + target_compile_definitions(${TARGET}-be-list PRIVATE ${setting}=0) + endif() + endforeach() + target_compile_definitions(${TARGET}-be-list PRIVATE APPNAME=${NAME}) + target_sources(${TARGET} PRIVATE $) +endfunction() + include(cmake/platforms/${platform}.cmake) include_directories( diff --git a/fuzzterm.c b/fuzzterm.c index a1902ede..f1477054 100644 --- a/fuzzterm.c +++ b/fuzzterm.c @@ -171,7 +171,6 @@ bool dlg_coloursel_results(union control *ctrl, dlgparam *dp, void dlg_refresh(union control *ctrl, dlgparam *dp) { } bool dlg_is_visible(union control *ctrl, dlgparam *dp) { return false; } -const char *const appname = "FuZZterm"; const int ngsslibs = 0; const char *const gsslibnames[0] = { }; const struct keyvalwhere gsslibkeywords[0] = { }; diff --git a/pscp.c b/pscp.c index 837e89d9..192341c3 100644 --- a/pscp.c +++ b/pscp.c @@ -49,8 +49,6 @@ static void source(const char *src); static void rsource(const char *src); static void sink(const char *targ, const 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. diff --git a/psftp.c b/psftp.c index d0317c2e..bddc18a2 100644 --- a/psftp.c +++ b/psftp.c @@ -14,8 +14,6 @@ #include "ssh.h" #include "ssh/sftp.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 diff --git a/unix/CMakeLists.txt b/unix/CMakeLists.txt index 1030f486..51d03f0f 100644 --- a/unix/CMakeLists.txt +++ b/unix/CMakeLists.txt @@ -49,11 +49,11 @@ add_sources_from_current_dir(agent add_executable(fuzzterm ${CMAKE_SOURCE_DIR}/fuzzterm.c - ${CMAKE_SOURCE_DIR}/be_none.c ${CMAKE_SOURCE_DIR}/logging.c ${CMAKE_SOURCE_DIR}/stubs/noprint.c unicode.c no-gtk.c) +be_list(fuzzterm FuZZterm) add_dependencies(fuzzterm generated_licence_h) target_link_libraries(fuzzterm guiterminal eventloop charset settings utils) @@ -68,11 +68,11 @@ add_sources_from_current_dir(psocks no-gtk.c) add_executable(psusan psusan.c - ${CMAKE_SOURCE_DIR}/be_none.c ${CMAKE_SOURCE_DIR}/stubs/nogss.c ${CMAKE_SOURCE_DIR}/ssh/scpserver.c no-gtk.c pty.c) +be_list(psusan psusan) target_link_libraries(psusan eventloop sshserver keygen settings network crypto utils) installed_program(psusan) @@ -108,11 +108,11 @@ target_link_libraries(testzlib utils) add_executable(uppity uppity.c - ${CMAKE_SOURCE_DIR}/be_none.c ${CMAKE_SOURCE_DIR}/ssh/scpserver.c no-gtk.c pty.c ${CMAKE_SOURCE_DIR}/stubs/nogss.c) +be_list(uppity Uppity) target_link_libraries(uppity eventloop sshserver keygen settings network crypto utils) @@ -131,13 +131,13 @@ if(GTK_FOUND) add_executable(pageant pageant.c - ${CMAKE_SOURCE_DIR}/be_none.c ${CMAKE_SOURCE_DIR}/stubs/nogss.c askpass.c x11.c noise.c ${CMAKE_SOURCE_DIR}/ssh/x11fwd.c ${CMAKE_SOURCE_DIR}/proxy/nosshproxy.c) + be_list(pageant Pageant) target_link_libraries(pageant eventloop console agent settings network crypto utils ${GTK_LIBRARIES}) @@ -146,10 +146,10 @@ if(GTK_FOUND) add_executable(pterm pterm.c main-gtk-simple.c - ${CMAKE_SOURCE_DIR}/be_none.c ${CMAKE_SOURCE_DIR}/stubs/nogss.c ${CMAKE_SOURCE_DIR}/proxy/nosshproxy.c pty.c) + be_list(pterm pterm) target_link_libraries(pterm guiterminal eventloop settings charset utils ${GTK_LIBRARIES} ${X11_LIBRARIES}) @@ -159,18 +159,18 @@ if(GTK_FOUND) pterm.c main-gtk-application.c ${CMAKE_SOURCE_DIR}/stubs/nocmdline.c - ${CMAKE_SOURCE_DIR}/be_none.c ${CMAKE_SOURCE_DIR}/stubs/nogss.c ${CMAKE_SOURCE_DIR}/proxy/nosshproxy.c pty.c) + be_list(ptermapp pterm) target_link_libraries(ptermapp guiterminal eventloop settings charset utils ${GTK_LIBRARIES} ${X11_LIBRARIES}) add_executable(putty putty.c - main-gtk-simple.c - ${CMAKE_SOURCE_DIR}/be_all_s.c) + main-gtk-simple.c) + be_list(putty PuTTY SSH SERIAL OTHERBACKENDS) target_link_libraries(putty guiterminal eventloop sshclient otherbackends settings network crypto charset utils @@ -182,8 +182,8 @@ if(GTK_FOUND) add_executable(puttyapp putty.c main-gtk-application.c - ${CMAKE_SOURCE_DIR}/stubs/nocmdline.c - ${CMAKE_SOURCE_DIR}/be_all_s.c) + ${CMAKE_SOURCE_DIR}/stubs/nocmdline.c) + be_list(puttyapp PuTTY SSH SERIAL OTHERBACKENDS) target_link_libraries(puttyapp guiterminal eventloop sshclient otherbackends settings network crypto charset utils @@ -192,11 +192,11 @@ if(GTK_FOUND) add_executable(puttytel putty.c main-gtk-simple.c - ${CMAKE_SOURCE_DIR}/be_nos_s.c ${CMAKE_SOURCE_DIR}/stubs/nogss.c ${CMAKE_SOURCE_DIR}/stubs/norand.c ${CMAKE_SOURCE_DIR}/proxy/nocproxy.c ${CMAKE_SOURCE_DIR}/proxy/nosshproxy.c) + be_list(puttytel PuTTYtel SERIAL OTHERBACKENDS) target_link_libraries(puttytel guiterminal eventloop otherbackends settings network charset utils ${GTK_LIBRARIES} ${X11_LIBRARIES}) diff --git a/unix/pageant.c b/unix/pageant.c index f2f75eac..f8a766db 100644 --- a/unix/pageant.c +++ b/unix/pageant.c @@ -235,8 +235,6 @@ void keylist_update(void) #define PAGEANT_DIR_PREFIX "/tmp/pageant" -const char *const appname = "Pageant"; - static bool time_to_die = false; /* diff --git a/unix/psusan.c b/unix/psusan.c index 6560adf2..1d5816d5 100644 --- a/unix/psusan.c +++ b/unix/psusan.c @@ -41,8 +41,6 @@ #include "ssh.h" #include "ssh/server.h" -const char *const appname = "psusan"; - void modalfatalbox(const char *p, ...) { va_list ap; diff --git a/unix/pterm.c b/unix/pterm.c index b18e3442..649e7b83 100644 --- a/unix/pterm.c +++ b/unix/pterm.c @@ -7,7 +7,6 @@ #include "putty.h" -const char *const appname = "pterm"; const bool use_event_log = false; /* pterm doesn't need it */ const bool new_session = false, saved_sessions = false; /* or these */ const bool dup_check_launchable = false; /* no need to check host name diff --git a/unix/uppity.c b/unix/uppity.c index 7f74fed6..b5c30075 100644 --- a/unix/uppity.c +++ b/unix/uppity.c @@ -43,8 +43,6 @@ #include "ssh.h" #include "ssh/server.h" -const char *const appname = "uppity"; - void modalfatalbox(const char *p, ...) { va_list ap; diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt index 10769ee1..7338f4c2 100644 --- a/windows/CMakeLists.txt +++ b/windows/CMakeLists.txt @@ -92,8 +92,8 @@ add_executable(putty window.c putty.c help.c - ${CMAKE_SOURCE_DIR}/be_all_s.c putty.rc) +be_list(putty PuTTY SSH SERIAL OTHERBACKENDS) add_dependencies(putty generated_licence_h) target_link_libraries(putty guiterminal guimisc eventloop sshclient otherbackends settings network crypto @@ -108,12 +108,12 @@ add_executable(puttytel window.c putty.c help.c - ${CMAKE_SOURCE_DIR}/be_nos_s.c ${CMAKE_SOURCE_DIR}/stubs/nogss.c ${CMAKE_SOURCE_DIR}/stubs/norand.c ${CMAKE_SOURCE_DIR}/proxy/nocproxy.c ${CMAKE_SOURCE_DIR}/proxy/nosshproxy.c puttytel.rc) +be_list(puttytel PuTTYtel SERIAL OTHERBACKENDS) add_dependencies(puttytel generated_licence_h) target_link_libraries(puttytel guiterminal guimisc eventloop otherbackends settings network utils @@ -149,11 +149,11 @@ if(HAVE_CONPTY) pterm.c help.c conpty.c - be_conpty.c ${CMAKE_SOURCE_DIR}/stubs/nogss.c ${CMAKE_SOURCE_DIR}/stubs/norand.c - ${CMAKE_SOURCE_DIR}/proxy/stubs/nosshproxy.c + ${CMAKE_SOURCE_DIR}/proxy/nosshproxy.c pterm.rc) + be_list(pterm pterm) add_dependencies(pterm generated_licence_h) target_link_libraries(pterm guiterminal guimisc eventloop settings network utils diff --git a/windows/be_conpty.c b/windows/be_conpty.c deleted file mode 100644 index d8f000b1..00000000 --- a/windows/be_conpty.c +++ /dev/null @@ -1,13 +0,0 @@ -#include -#include "putty.h" - -const char *const appname = "pterm"; - -const int be_default_protocol = -1; - -const struct BackendVtable *const backends[] = { - &conpty_backend, - NULL -}; - -const size_t n_ui_backends = 1; -- cgit v1.2.3 From 44055cd36ef0ee7cf9b03d3ff6ec39e51bfa92b6 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 27 Nov 2021 11:41:00 +0000 Subject: Withdraw support for SHA-512-256 in HTTP Digest. I was dubious about it to begin with, when I found that RFC 7616's example seemed to be treating it as a 256-bit truncation of SHA-512, and not the thing FIPS 180-4 section 6.7 specifies as "SHA-512/256" (which also changes the initial hash state). Having failed to get a clarifying response from the RFC authors, I had the idea this morning of testing other HTTP clients to see what _they_ thought that hash function meant, and then at least I could go with an existing in-practice consensus. There is no in-practice consensus. Firefox doesn't support that algorithm at all (but they do support SHA-256); wget doesn't support anything that RFC 7616 added to the original RFC 2617. But the prize for weirdness goes to curl, which does accept the name "SHA-512-256" and ... treats it as an alias for SHA-256! So I think the situation among real clients is too confusing to even try to work with, and I'm going to stop adding to it. PuTTY will follow Firefox's policy: if a proxy server asks for SHA-256 digests we'll happily provide them, but if they ask for SHA-512-256 we'll refuse on the grounds that it's not clear enough what it means. --- proxy/cproxy.c | 12 ++++++-- proxy/cproxy.h | 79 ++++++++++++++++++++++++++++++++++++++++++++++++--- proxy/http.c | 17 +++++++++-- proxy/nocproxy.c | 4 ++- test/cryptsuite.py | 8 ++++++ test/testcrypt-enum.h | 2 +- 6 files changed, 111 insertions(+), 11 deletions(-) diff --git a/proxy/cproxy.c b/proxy/cproxy.c index 29eafdfc..38e6eef9 100644 --- a/proxy/cproxy.c +++ b/proxy/cproxy.c @@ -41,18 +41,24 @@ void BinarySink_put_hex_data(BinarySink *bs, const void *vptr, size_t len) BinarySink_put_hex_data(BinarySink_UPCAST(bs), p, len) const char *const httphashnames[] = { - #define DECL_ARRAY(id, str, alg, bits) str, + #define DECL_ARRAY(id, str, alg, bits, accepted) str, + HTTP_DIGEST_HASHES(DECL_ARRAY) + #undef DECL_ARRAY +}; + +const bool httphashaccepted[] = { + #define DECL_ARRAY(id, str, alg, bits, accepted) accepted, HTTP_DIGEST_HASHES(DECL_ARRAY) #undef DECL_ARRAY }; static const ssh_hashalg *const httphashalgs[] = { - #define DECL_ARRAY(id, str, alg, bits) alg, + #define DECL_ARRAY(id, str, alg, bits, accepted) alg, HTTP_DIGEST_HASHES(DECL_ARRAY) #undef DECL_ARRAY }; static const size_t httphashlengths[] = { - #define DECL_ARRAY(id, str, alg, bits) bits/8, + #define DECL_ARRAY(id, str, alg, bits, accepted) bits/8, HTTP_DIGEST_HASHES(DECL_ARRAY) #undef DECL_ARRAY }; diff --git a/proxy/cproxy.h b/proxy/cproxy.h index c3e7ba15..4179895e 100644 --- a/proxy/cproxy.h +++ b/proxy/cproxy.h @@ -7,18 +7,89 @@ extern const bool socks5_chap_available; strbuf *chap_response(ptrlen challenge, ptrlen password); extern const bool http_digest_available; +/* + * List macro for the various hash functions defined for HTTP Digest. + * + * Of these, MD5 is the original one; SHA-256 is unambiguous; but + * SHA-512-256 seems to be controversial. + * + * RFC 7616 doesn't provide a normative reference, or any text + * explaining what they mean by it. They apparently expect you to + * already know. The problem with that is that there are two plausible + * things they _might_ have meant: + * + * 1. Ordinary SHA-512, truncated to 256 bits by discarding the + * second half of the hash output, per FIPS 180-4 section 7 (which + * says that in general it's OK to truncate hash functions like + * that if you need to). FIPS 180-4 assigns no particular specific + * spelling to this kind of truncated hash. + * + * 2. The same except that the initial state of the SHA-512 algorithm + * is reset to a different 512-bit vector to ensure that it's a + * distinguishable hash function in its own right, per FIPS 180-4 + * section 6.7 (which in turn refers to section 5.3.6.2 for the + * actual initial values). FIPS 180-4 spells this "SHA-512/256". + * + * The text of RFC 7616 is totally silent as to which of these they + * meant. Their spelling is inconsistent: the protocol identifier is + * "SHA-512-256", but in some places in the RFC they say + * "SHA-512/256", matching FIPS's spelling for the hash in option 2 + * above. On the other hand, the example authentication exchange in + * section 3.9.2 of the RFC contains hashes that are consistent with + * option 1 above (a truncation of plain SHA-512). + * + * Erratum 4897, https://www.rfc-editor.org/errata/eid4897, points out + * this ambiguity, and suggests correcting the example exchange to be + * consistent with option 2. However, as of 2021-11-27, that erratum + * is shown on the RFC Editor website in state "Reported", with no + * response (positive _or_ negative) from the RFC authors or anyone + * else. (And it was reported in 2016, so it's not as if they haven't + * had time.) + * + * So, which hash should we implement? Perhaps there's a consensus + * among existing implementations in the wild? + * + * I rigged up an HTTP server to present a SHA-512-256 Digest auth + * request, and tried various HTTP clients against it. The only HTTP + * client I found that accepts 'algorithm="SHA-512-256"' and sends + * back an auth attempt quoting the same hash is curl - and curl, + * bizarrely, seems to treat "SHA-512-256" as _neither_ of the above + * options, but as simply an alias for SHA-256! + * + * Therefore, I think the only safe answer is to refuse to support + * that hash at all: it's too confusing. + * + * However, I keep it in the list of hashes here, so that we can check + * the test case from RFC 7616, because that test case is also the + * only test of username hashing. So we reject it in proxy/http.c, but + * accept it in the internal function http_digest_response(), and + * treat it as option 1 (truncated SHA-512). + * + * Therefore, the parameters to each invocation of X in the following + * list macro are: + * + * - internal enum id for the hash + * - protocol identifier string + * - algorithm to use for computing it (as a const ssh_hashalg *) + * - length to truncate the output to + * - whether we accept it in http.c or not. + */ #define HTTP_DIGEST_HASHES(X) \ - X(HTTP_DIGEST_MD5, "MD5", &ssh_md5, 128) \ - X(HTTP_DIGEST_SHA256, "SHA-256", &ssh_sha256, 256) \ - X(HTTP_DIGEST_SHA512_256, "SHA-512-256", &ssh_sha512, 256) \ + X(HTTP_DIGEST_MD5, "MD5", &ssh_md5, 128, true) \ + X(HTTP_DIGEST_SHA256, "SHA-256", &ssh_sha256, 256, true) \ + X(HTTP_DIGEST_SHA512_256, "SHA-512-256", &ssh_sha512, 256, false) \ /* end of list */ + typedef enum HttpDigestHash { - #define DECL_ENUM(id, str, alg, bits) id, + #define DECL_ENUM(id, str, alg, bits, accepted) id, HTTP_DIGEST_HASHES(DECL_ENUM) #undef DECL_ENUM N_HTTP_DIGEST_HASHES } HttpDigestHash; + extern const char *const httphashnames[]; +extern const bool httphashaccepted[]; + void http_digest_response(BinarySink *bs, ptrlen username, ptrlen password, ptrlen realm, ptrlen method, ptrlen uri, ptrlen qop, ptrlen nonce, ptrlen opaque, uint32_t nonce_count, diff --git a/proxy/http.c b/proxy/http.c index 5176027c..bcedacc7 100644 --- a/proxy/http.c +++ b/proxy/http.c @@ -430,22 +430,35 @@ static void proxy_http_process_queue(ProxyNegotiator *pn) !get_token(s)) goto bad_digest; bool found = false; + size_t i; - for (size_t i = 0; i < N_HTTP_DIGEST_HASHES; i++) { + for (i = 0; i < N_HTTP_DIGEST_HASHES; i++) { if (!stricmp(s->token->s, httphashnames[i])) { - s->digest_hash = i; found = true; break; } } if (!found) { + /* We don't even recognise the name */ + pn->error = dupprintf( + "HTTP proxy requested Digest hash " + "algorithm '%s' which we do not recognise", + s->token->s); + crStopV; + } + + if (!httphashaccepted[i]) { + /* We do recognise the name but we + * don't like it (see comment in cproxy.h) */ pn->error = dupprintf( "HTTP proxy requested Digest hash " "algorithm '%s' which we do not support", s->token->s); crStopV; } + + s->digest_hash = i; } else if (!stricmp(s->token->s, "qop")) { if (!get_separator(s, '=') || !get_quoted_string(s)) diff --git a/proxy/nocproxy.c b/proxy/nocproxy.c index 38d416d5..89341489 100644 --- a/proxy/nocproxy.c +++ b/proxy/nocproxy.c @@ -20,7 +20,9 @@ strbuf *chap_response(ptrlen challenge, ptrlen password) unreachable("CHAP is not built into this binary"); } -const char *const httphashnames[] = { NULL }; /* dummy to prevent link error */ +/* dummy arrays to prevent link error */ +const char *const httphashnames[] = { NULL }; +const bool httphashaccepted[] = { false }; void http_digest_response(BinarySink *bs, ptrlen username, ptrlen password, ptrlen realm, ptrlen method, ptrlen uri, ptrlen qop, diff --git a/test/cryptsuite.py b/test/cryptsuite.py index a501bc27..e331bcf6 100755 --- a/test/cryptsuite.py +++ b/test/cryptsuite.py @@ -3212,6 +3212,14 @@ class standard_test_vectors(MyTestBase): # that they think it's just a 256-bit truncation of SHA-512, # and not the version defined in FIPS 180-4 which also uses # a different initial hash state), and username hashing. + # + # We don't actually support SHA-512-256 in the top-level proxy + # client code (see the comment in proxy/cproxy.h). However, + # this internal http_digest_response function still provides + # it, simply so that we can run this test case from the RFC, + # because it's the only provided test case for username + # hashing, and this confirms that we've got the preimage right + # for the username hash. params = ["J\u00E4s\u00F8n Doe".encode("UTF-8"), "Secret, or not?", "api@example.org", "GET", "/doe.json", "auth", diff --git a/test/testcrypt-enum.h b/test/testcrypt-enum.h index cec8d836..cf8f00c5 100644 --- a/test/testcrypt-enum.h +++ b/test/testcrypt-enum.h @@ -138,7 +138,7 @@ END_ENUM_TYPE(fptype) * invent a separate one for testcrypt, reuse the existing names. */ BEGIN_ENUM_TYPE(httpdigesthash) - #define DECL_ARRAY(id, str, alg, bits) ENUM_VALUE(str, id) + #define DECL_ARRAY(id, str, alg, bits, accepted) ENUM_VALUE(str, id) HTTP_DIGEST_HASHES(DECL_ARRAY) #undef DECL_ARRAY END_ENUM_TYPE(httpdigesthash) -- cgit v1.2.3 From cbc723bf9daa9990981aca047c65420379effeb7 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 28 Nov 2021 09:39:49 +0000 Subject: testcrypt-funcs.h: remove extra parens round argument lists. They were there to work around that annoying feature of VS's preprocessor when it expands __VA_ARGS__ into the argument list of another macro. But I've just thought of a workaround that I can apply in testcrypt.c itself, so that those parens don't have to appear in every function definition in the header file. The trick is, instead of writing destination_macro(__VA_ARGS__) you instead write JUXTAPOSE(destination_macro, (__VA_ARGS__)) where JUXTAPOSE is defined to be a macro that simply expands its two arguments next to each other: #define JUXTAPOSE(first, second) first second This works because the arguments to JUXTAPOSE get macro-expanded _before_ passing them to JUXTAPOSE itself - the same reason that the standard tricks with STR_INNER and CAT_INNER work (as seen in defs.h here). So this defuses the magic behaviour of commas expanded from __VA_ARGS__, and causes the destination macro to get all its arguments in the expected places again. --- test/testcrypt-func.h | 780 ++++++++++++++++++++++++-------------------------- test/testcrypt.c | 103 +++---- test/testcrypt.py | 2 - 3 files changed, 422 insertions(+), 463 deletions(-) diff --git a/test/testcrypt-func.h b/test/testcrypt-func.h index 0da41826..9158e800 100644 --- a/test/testcrypt-func.h +++ b/test/testcrypt-func.h @@ -4,13 +4,18 @@ * * Each function definition in this file has the form * - * FUNC(return-type, function-name, (arguments)) + * FUNC(return-type, function-name, ...) * - * where 'arguments' in turn is either VOID, or a comma-separated list - * of argument specifications of the form + * where '...' in turn a variadic list of argument specifications of + * the form * * ARG(argument-type, argument-name) * + * An empty argument list must be marked by including a + * pseudo-argument VOID: + * + * FUNC(return-type, function-name, VOID) + * * Type names are always single identifiers, and they have some * standard prefixes: * @@ -35,7 +40,7 @@ * they cause the return value to become a tuple, with additional * types appended. For example, a declaration like * - * FUNC(val_foo, example, (ARG(out_val_bar, bar), ARG(val_baz, baz))) + * FUNC(val_foo, example, ARG(out_val_bar, bar), ARG(val_baz, baz)) * * would identify a function in C with the following prototype, which * returns a 'foo *' directly and a 'bar *' by writing it through the @@ -71,186 +76,177 @@ /* * mpint.h functions. */ -FUNC(val_mpint, mp_new, (ARG(uint, maxbits))) -FUNC(void, mp_clear, (ARG(val_mpint, x))) -FUNC(val_mpint, mp_from_bytes_le, (ARG(val_string_ptrlen, bytes))) -FUNC(val_mpint, mp_from_bytes_be, (ARG(val_string_ptrlen, bytes))) -FUNC(val_mpint, mp_from_integer, (ARG(uint, n))) -FUNC(val_mpint, mp_from_decimal_pl, (ARG(val_string_ptrlen, decimal))) -FUNC(val_mpint, mp_from_decimal, (ARG(val_string_asciz, decimal))) -FUNC(val_mpint, mp_from_hex_pl, (ARG(val_string_ptrlen, hex))) -FUNC(val_mpint, mp_from_hex, (ARG(val_string_asciz, hex))) -FUNC(val_mpint, mp_copy, (ARG(val_mpint, x))) -FUNC(val_mpint, mp_power_2, (ARG(uint, power))) -FUNC(uint, mp_get_byte, (ARG(val_mpint, x), ARG(uint, byte))) -FUNC(uint, mp_get_bit, (ARG(val_mpint, x), ARG(uint, bit))) -FUNC(void, mp_set_bit, (ARG(val_mpint, x), ARG(uint, bit), ARG(uint, val))) -FUNC(uint, mp_max_bytes, (ARG(val_mpint, x))) -FUNC(uint, mp_max_bits, (ARG(val_mpint, x))) -FUNC(uint, mp_get_nbits, (ARG(val_mpint, x))) -FUNC(val_string_asciz, mp_get_decimal, (ARG(val_mpint, x))) -FUNC(val_string_asciz, mp_get_hex, (ARG(val_mpint, x))) -FUNC(val_string_asciz, mp_get_hex_uppercase, (ARG(val_mpint, x))) -FUNC(uint, mp_cmp_hs, (ARG(val_mpint, a), ARG(val_mpint, b))) -FUNC(uint, mp_cmp_eq, (ARG(val_mpint, a), ARG(val_mpint, b))) -FUNC(uint, mp_hs_integer, (ARG(val_mpint, x), ARG(uint, n))) -FUNC(uint, mp_eq_integer, (ARG(val_mpint, x), ARG(uint, n))) -FUNC(void, mp_min_into, - (ARG(val_mpint, dest), ARG(val_mpint, x), ARG(val_mpint, y))) -FUNC(void, mp_max_into, - (ARG(val_mpint, dest), ARG(val_mpint, x), ARG(val_mpint, y))) -FUNC(val_mpint, mp_min, (ARG(val_mpint, x), ARG(val_mpint, y))) -FUNC(val_mpint, mp_max, (ARG(val_mpint, x), ARG(val_mpint, y))) -FUNC(void, mp_copy_into, (ARG(val_mpint, dest), ARG(val_mpint, src))) -FUNC(void, mp_select_into, - (ARG(val_mpint, dest), ARG(val_mpint, src0), ARG(val_mpint, src1), - ARG(uint, choose_src1))) -FUNC(void, mp_add_into, - (ARG(val_mpint, dest), ARG(val_mpint, a), ARG(val_mpint, b))) -FUNC(void, mp_sub_into, - (ARG(val_mpint, dest), ARG(val_mpint, a), ARG(val_mpint, b))) -FUNC(void, mp_mul_into, - (ARG(val_mpint, dest), ARG(val_mpint, a), ARG(val_mpint, b))) -FUNC(val_mpint, mp_add, (ARG(val_mpint, x), ARG(val_mpint, y))) -FUNC(val_mpint, mp_sub, (ARG(val_mpint, x), ARG(val_mpint, y))) -FUNC(val_mpint, mp_mul, (ARG(val_mpint, x), ARG(val_mpint, y))) -FUNC(void, mp_and_into, - (ARG(val_mpint, dest), ARG(val_mpint, a), ARG(val_mpint, b))) -FUNC(void, mp_or_into, - (ARG(val_mpint, dest), ARG(val_mpint, a), ARG(val_mpint, b))) -FUNC(void, mp_xor_into, - (ARG(val_mpint, dest), ARG(val_mpint, a), ARG(val_mpint, b))) -FUNC(void, mp_bic_into, - (ARG(val_mpint, dest), ARG(val_mpint, a), ARG(val_mpint, b))) -FUNC(void, mp_copy_integer_into, (ARG(val_mpint, dest), ARG(uint, n))) -FUNC(void, mp_add_integer_into, - (ARG(val_mpint, dest), ARG(val_mpint, a), ARG(uint, n))) -FUNC(void, mp_sub_integer_into, - (ARG(val_mpint, dest), ARG(val_mpint, a), ARG(uint, n))) -FUNC(void, mp_mul_integer_into, - (ARG(val_mpint, dest), ARG(val_mpint, a), ARG(uint, n))) -FUNC(void, mp_cond_add_into, - (ARG(val_mpint, dest), ARG(val_mpint, a), ARG(val_mpint, b), - ARG(uint, yes))) -FUNC(void, mp_cond_sub_into, - (ARG(val_mpint, dest), ARG(val_mpint, a), ARG(val_mpint, b), - ARG(uint, yes))) -FUNC(void, mp_cond_swap, - (ARG(val_mpint, x0), ARG(val_mpint, x1), ARG(uint, swap))) -FUNC(void, mp_cond_clear, (ARG(val_mpint, x), ARG(uint, clear))) -FUNC(void, mp_divmod_into, - (ARG(val_mpint, n), ARG(val_mpint, d), ARG(opt_val_mpint, q), - ARG(opt_val_mpint, r))) -FUNC(val_mpint, mp_div, (ARG(val_mpint, n), ARG(val_mpint, d))) -FUNC(val_mpint, mp_mod, (ARG(val_mpint, x), ARG(val_mpint, modulus))) -FUNC(val_mpint, mp_nthroot, - (ARG(val_mpint, y), ARG(uint, n), ARG(opt_val_mpint, remainder))) -FUNC(void, mp_reduce_mod_2to, (ARG(val_mpint, x), ARG(uint, p))) -FUNC(val_mpint, mp_invert_mod_2to, (ARG(val_mpint, x), ARG(uint, p))) -FUNC(val_mpint, mp_invert, (ARG(val_mpint, x), ARG(val_mpint, modulus))) -FUNC(void, mp_gcd_into, - (ARG(val_mpint, a), ARG(val_mpint, b), ARG(opt_val_mpint, gcd_out), - ARG(opt_val_mpint, A_out), ARG(opt_val_mpint, B_out))) -FUNC(val_mpint, mp_gcd, (ARG(val_mpint, a), ARG(val_mpint, b))) -FUNC(uint, mp_coprime, (ARG(val_mpint, a), ARG(val_mpint, b))) -FUNC(val_modsqrt, modsqrt_new, - (ARG(val_mpint, p), ARG(val_mpint, any_nonsquare_mod_p))) +FUNC(val_mpint, mp_new, ARG(uint, maxbits)) +FUNC(void, mp_clear, ARG(val_mpint, x)) +FUNC(val_mpint, mp_from_bytes_le, ARG(val_string_ptrlen, bytes)) +FUNC(val_mpint, mp_from_bytes_be, ARG(val_string_ptrlen, bytes)) +FUNC(val_mpint, mp_from_integer, ARG(uint, n)) +FUNC(val_mpint, mp_from_decimal_pl, ARG(val_string_ptrlen, decimal)) +FUNC(val_mpint, mp_from_decimal, ARG(val_string_asciz, decimal)) +FUNC(val_mpint, mp_from_hex_pl, ARG(val_string_ptrlen, hex)) +FUNC(val_mpint, mp_from_hex, ARG(val_string_asciz, hex)) +FUNC(val_mpint, mp_copy, ARG(val_mpint, x)) +FUNC(val_mpint, mp_power_2, ARG(uint, power)) +FUNC(uint, mp_get_byte, ARG(val_mpint, x), ARG(uint, byte)) +FUNC(uint, mp_get_bit, ARG(val_mpint, x), ARG(uint, bit)) +FUNC(void, mp_set_bit, ARG(val_mpint, x), ARG(uint, bit), ARG(uint, val)) +FUNC(uint, mp_max_bytes, ARG(val_mpint, x)) +FUNC(uint, mp_max_bits, ARG(val_mpint, x)) +FUNC(uint, mp_get_nbits, ARG(val_mpint, x)) +FUNC(val_string_asciz, mp_get_decimal, ARG(val_mpint, x)) +FUNC(val_string_asciz, mp_get_hex, ARG(val_mpint, x)) +FUNC(val_string_asciz, mp_get_hex_uppercase, ARG(val_mpint, x)) +FUNC(uint, mp_cmp_hs, ARG(val_mpint, a), ARG(val_mpint, b)) +FUNC(uint, mp_cmp_eq, ARG(val_mpint, a), ARG(val_mpint, b)) +FUNC(uint, mp_hs_integer, ARG(val_mpint, x), ARG(uint, n)) +FUNC(uint, mp_eq_integer, ARG(val_mpint, x), ARG(uint, n)) +FUNC(void, mp_min_into, ARG(val_mpint, dest), ARG(val_mpint, x), + ARG(val_mpint, y)) +FUNC(void, mp_max_into, ARG(val_mpint, dest), ARG(val_mpint, x), + ARG(val_mpint, y)) +FUNC(val_mpint, mp_min, ARG(val_mpint, x), ARG(val_mpint, y)) +FUNC(val_mpint, mp_max, ARG(val_mpint, x), ARG(val_mpint, y)) +FUNC(void, mp_copy_into, ARG(val_mpint, dest), ARG(val_mpint, src)) +FUNC(void, mp_select_into, ARG(val_mpint, dest), ARG(val_mpint, src0), + ARG(val_mpint, src1), ARG(uint, choose_src1)) +FUNC(void, mp_add_into, ARG(val_mpint, dest), ARG(val_mpint, a), + ARG(val_mpint, b)) +FUNC(void, mp_sub_into, ARG(val_mpint, dest), ARG(val_mpint, a), + ARG(val_mpint, b)) +FUNC(void, mp_mul_into, ARG(val_mpint, dest), ARG(val_mpint, a), + ARG(val_mpint, b)) +FUNC(val_mpint, mp_add, ARG(val_mpint, x), ARG(val_mpint, y)) +FUNC(val_mpint, mp_sub, ARG(val_mpint, x), ARG(val_mpint, y)) +FUNC(val_mpint, mp_mul, ARG(val_mpint, x), ARG(val_mpint, y)) +FUNC(void, mp_and_into, ARG(val_mpint, dest), ARG(val_mpint, a), + ARG(val_mpint, b)) +FUNC(void, mp_or_into, ARG(val_mpint, dest), ARG(val_mpint, a), + ARG(val_mpint, b)) +FUNC(void, mp_xor_into, ARG(val_mpint, dest), ARG(val_mpint, a), + ARG(val_mpint, b)) +FUNC(void, mp_bic_into, ARG(val_mpint, dest), ARG(val_mpint, a), + ARG(val_mpint, b)) +FUNC(void, mp_copy_integer_into, ARG(val_mpint, dest), ARG(uint, n)) +FUNC(void, mp_add_integer_into, ARG(val_mpint, dest), ARG(val_mpint, a), + ARG(uint, n)) +FUNC(void, mp_sub_integer_into, ARG(val_mpint, dest), ARG(val_mpint, a), + ARG(uint, n)) +FUNC(void, mp_mul_integer_into, ARG(val_mpint, dest), ARG(val_mpint, a), + ARG(uint, n)) +FUNC(void, mp_cond_add_into, ARG(val_mpint, dest), ARG(val_mpint, a), + ARG(val_mpint, b), ARG(uint, yes)) +FUNC(void, mp_cond_sub_into, ARG(val_mpint, dest), ARG(val_mpint, a), + ARG(val_mpint, b), ARG(uint, yes)) +FUNC(void, mp_cond_swap, ARG(val_mpint, x0), ARG(val_mpint, x1), + ARG(uint, swap)) +FUNC(void, mp_cond_clear, ARG(val_mpint, x), ARG(uint, clear)) +FUNC(void, mp_divmod_into, ARG(val_mpint, n), ARG(val_mpint, d), + ARG(opt_val_mpint, q), ARG(opt_val_mpint, r)) +FUNC(val_mpint, mp_div, ARG(val_mpint, n), ARG(val_mpint, d)) +FUNC(val_mpint, mp_mod, ARG(val_mpint, x), ARG(val_mpint, modulus)) +FUNC(val_mpint, mp_nthroot, ARG(val_mpint, y), ARG(uint, n), + ARG(opt_val_mpint, remainder)) +FUNC(void, mp_reduce_mod_2to, ARG(val_mpint, x), ARG(uint, p)) +FUNC(val_mpint, mp_invert_mod_2to, ARG(val_mpint, x), ARG(uint, p)) +FUNC(val_mpint, mp_invert, ARG(val_mpint, x), ARG(val_mpint, modulus)) +FUNC(void, mp_gcd_into, ARG(val_mpint, a), ARG(val_mpint, b), + ARG(opt_val_mpint, gcd_out), ARG(opt_val_mpint, A_out), + ARG(opt_val_mpint, B_out)) +FUNC(val_mpint, mp_gcd, ARG(val_mpint, a), ARG(val_mpint, b)) +FUNC(uint, mp_coprime, ARG(val_mpint, a), ARG(val_mpint, b)) +FUNC(val_modsqrt, modsqrt_new, ARG(val_mpint, p), + ARG(val_mpint, any_nonsquare_mod_p)) /* The modsqrt functions' 'success' pointer becomes a second return value */ -FUNC(val_mpint, mp_modsqrt, - (ARG(val_modsqrt, sc), ARG(val_mpint, x), ARG(out_uint, success))) -FUNC(val_monty, monty_new, (ARG(val_mpint, modulus))) -FUNC_WRAPPED(val_mpint, monty_modulus, (ARG(val_monty, mc))) -FUNC_WRAPPED(val_mpint, monty_identity, (ARG(val_monty, mc))) -FUNC(void, monty_import_into, - (ARG(val_monty, mc), ARG(val_mpint, dest), ARG(val_mpint, x))) -FUNC(val_mpint, monty_import, (ARG(val_monty, mc), ARG(val_mpint, x))) -FUNC(void, monty_export_into, - (ARG(val_monty, mc), ARG(val_mpint, dest), ARG(val_mpint, x))) -FUNC(val_mpint, monty_export, (ARG(val_monty, mc), ARG(val_mpint, x))) -FUNC(void, monty_mul_into, - (ARG(val_monty, mc), ARG(val_mpint, dest), ARG(val_mpint, x), - ARG(val_mpint, y))) -FUNC(val_mpint, monty_add, - (ARG(val_monty, mc), ARG(val_mpint, x), ARG(val_mpint, y))) -FUNC(val_mpint, monty_sub, - (ARG(val_monty, mc), ARG(val_mpint, x), ARG(val_mpint, y))) -FUNC(val_mpint, monty_mul, - (ARG(val_monty, mc), ARG(val_mpint, x), ARG(val_mpint, y))) -FUNC(val_mpint, monty_pow, - (ARG(val_monty, mc), ARG(val_mpint, base), ARG(val_mpint, exponent))) -FUNC(val_mpint, monty_invert, (ARG(val_monty, mc), ARG(val_mpint, x))) -FUNC(val_mpint, monty_modsqrt, - (ARG(val_modsqrt, sc), ARG(val_mpint, mx), ARG(out_uint, success))) -FUNC(val_mpint, mp_modpow, - (ARG(val_mpint, base), ARG(val_mpint, exponent), ARG(val_mpint, modulus))) -FUNC(val_mpint, mp_modmul, - (ARG(val_mpint, x), ARG(val_mpint, y), ARG(val_mpint, modulus))) -FUNC(val_mpint, mp_modadd, - (ARG(val_mpint, x), ARG(val_mpint, y), ARG(val_mpint, modulus))) -FUNC(val_mpint, mp_modsub, - (ARG(val_mpint, x), ARG(val_mpint, y), ARG(val_mpint, modulus))) -FUNC(void, mp_lshift_safe_into, - (ARG(val_mpint, dest), ARG(val_mpint, x), ARG(uint, shift))) -FUNC(void, mp_rshift_safe_into, - (ARG(val_mpint, dest), ARG(val_mpint, x), ARG(uint, shift))) -FUNC(val_mpint, mp_rshift_safe, (ARG(val_mpint, x), ARG(uint, shift))) -FUNC(void, mp_lshift_fixed_into, - (ARG(val_mpint, dest), ARG(val_mpint, x), ARG(uint, shift))) -FUNC(void, mp_rshift_fixed_into, - (ARG(val_mpint, dest), ARG(val_mpint, x), ARG(uint, shift))) -FUNC(val_mpint, mp_rshift_fixed, (ARG(val_mpint, x), ARG(uint, shift))) -FUNC(val_mpint, mp_random_bits, (ARG(uint, bits))) -FUNC(val_mpint, mp_random_in_range, (ARG(val_mpint, lo), ARG(val_mpint, hi))) +FUNC(val_mpint, mp_modsqrt, ARG(val_modsqrt, sc), ARG(val_mpint, x), + ARG(out_uint, success)) +FUNC(val_monty, monty_new, ARG(val_mpint, modulus)) +FUNC_WRAPPED(val_mpint, monty_modulus, ARG(val_monty, mc)) +FUNC_WRAPPED(val_mpint, monty_identity, ARG(val_monty, mc)) +FUNC(void, monty_import_into, ARG(val_monty, mc), ARG(val_mpint, dest), + ARG(val_mpint, x)) +FUNC(val_mpint, monty_import, ARG(val_monty, mc), ARG(val_mpint, x)) +FUNC(void, monty_export_into, ARG(val_monty, mc), ARG(val_mpint, dest), + ARG(val_mpint, x)) +FUNC(val_mpint, monty_export, ARG(val_monty, mc), ARG(val_mpint, x)) +FUNC(void, monty_mul_into, ARG(val_monty, mc), ARG(val_mpint, dest), + ARG(val_mpint, x), ARG(val_mpint, y)) +FUNC(val_mpint, monty_add, ARG(val_monty, mc), ARG(val_mpint, x), + ARG(val_mpint, y)) +FUNC(val_mpint, monty_sub, ARG(val_monty, mc), ARG(val_mpint, x), + ARG(val_mpint, y)) +FUNC(val_mpint, monty_mul, ARG(val_monty, mc), ARG(val_mpint, x), + ARG(val_mpint, y)) +FUNC(val_mpint, monty_pow, ARG(val_monty, mc), ARG(val_mpint, base), + ARG(val_mpint, exponent)) +FUNC(val_mpint, monty_invert, ARG(val_monty, mc), ARG(val_mpint, x)) +FUNC(val_mpint, monty_modsqrt, ARG(val_modsqrt, sc), ARG(val_mpint, mx), + ARG(out_uint, success)) +FUNC(val_mpint, mp_modpow, ARG(val_mpint, base), ARG(val_mpint, exponent), + ARG(val_mpint, modulus)) +FUNC(val_mpint, mp_modmul, ARG(val_mpint, x), ARG(val_mpint, y), + ARG(val_mpint, modulus)) +FUNC(val_mpint, mp_modadd, ARG(val_mpint, x), ARG(val_mpint, y), + ARG(val_mpint, modulus)) +FUNC(val_mpint, mp_modsub, ARG(val_mpint, x), ARG(val_mpint, y), + ARG(val_mpint, modulus)) +FUNC(void, mp_lshift_safe_into, ARG(val_mpint, dest), ARG(val_mpint, x), + ARG(uint, shift)) +FUNC(void, mp_rshift_safe_into, ARG(val_mpint, dest), ARG(val_mpint, x), + ARG(uint, shift)) +FUNC(val_mpint, mp_rshift_safe, ARG(val_mpint, x), ARG(uint, shift)) +FUNC(void, mp_lshift_fixed_into, ARG(val_mpint, dest), ARG(val_mpint, x), + ARG(uint, shift)) +FUNC(void, mp_rshift_fixed_into, ARG(val_mpint, dest), ARG(val_mpint, x), + ARG(uint, shift)) +FUNC(val_mpint, mp_rshift_fixed, ARG(val_mpint, x), ARG(uint, shift)) +FUNC(val_mpint, mp_random_bits, ARG(uint, bits)) +FUNC(val_mpint, mp_random_in_range, ARG(val_mpint, lo), ARG(val_mpint, hi)) /* * ecc.h functions. */ -FUNC(val_wcurve, ecc_weierstrass_curve, - (ARG(val_mpint, p), ARG(val_mpint, a), ARG(val_mpint, b), - ARG(opt_val_mpint, nonsquare_mod_p))) -FUNC(val_wpoint, ecc_weierstrass_point_new_identity, (ARG(val_wcurve, curve))) -FUNC(val_wpoint, ecc_weierstrass_point_new, - (ARG(val_wcurve, curve), ARG(val_mpint, x), ARG(val_mpint, y))) -FUNC(val_wpoint, ecc_weierstrass_point_new_from_x, - (ARG(val_wcurve, curve), ARG(val_mpint, x), ARG(uint, desired_y_parity))) -FUNC(val_wpoint, ecc_weierstrass_point_copy, (ARG(val_wpoint, orig))) -FUNC(uint, ecc_weierstrass_point_valid, (ARG(val_wpoint, P))) -FUNC(val_wpoint, ecc_weierstrass_add_general, - (ARG(val_wpoint, P), ARG(val_wpoint, Q))) -FUNC(val_wpoint, ecc_weierstrass_add, (ARG(val_wpoint, P), ARG(val_wpoint, Q))) -FUNC(val_wpoint, ecc_weierstrass_double, (ARG(val_wpoint, P))) -FUNC(val_wpoint, ecc_weierstrass_multiply, - (ARG(val_wpoint, B), ARG(val_mpint, n))) -FUNC(uint, ecc_weierstrass_is_identity, (ARG(val_wpoint, P))) +FUNC(val_wcurve, ecc_weierstrass_curve, ARG(val_mpint, p), ARG(val_mpint, a), + ARG(val_mpint, b), ARG(opt_val_mpint, nonsquare_mod_p)) +FUNC(val_wpoint, ecc_weierstrass_point_new_identity, ARG(val_wcurve, curve)) +FUNC(val_wpoint, ecc_weierstrass_point_new, ARG(val_wcurve, curve), + ARG(val_mpint, x), ARG(val_mpint, y)) +FUNC(val_wpoint, ecc_weierstrass_point_new_from_x, ARG(val_wcurve, curve), + ARG(val_mpint, x), ARG(uint, desired_y_parity)) +FUNC(val_wpoint, ecc_weierstrass_point_copy, ARG(val_wpoint, orig)) +FUNC(uint, ecc_weierstrass_point_valid, ARG(val_wpoint, P)) +FUNC(val_wpoint, ecc_weierstrass_add_general, ARG(val_wpoint, P), + ARG(val_wpoint, Q)) +FUNC(val_wpoint, ecc_weierstrass_add, ARG(val_wpoint, P), ARG(val_wpoint, Q)) +FUNC(val_wpoint, ecc_weierstrass_double, ARG(val_wpoint, P)) +FUNC(val_wpoint, ecc_weierstrass_multiply, ARG(val_wpoint, B), + ARG(val_mpint, n)) +FUNC(uint, ecc_weierstrass_is_identity, ARG(val_wpoint, P)) /* The output pointers in get_affine all become extra output values */ -FUNC(void, ecc_weierstrass_get_affine, - (ARG(val_wpoint, P), ARG(out_val_mpint, x), ARG(out_val_mpint, y))) -FUNC(val_mcurve, ecc_montgomery_curve, - (ARG(val_mpint, p), ARG(val_mpint, a), ARG(val_mpint, b))) -FUNC(val_mpoint, ecc_montgomery_point_new, - (ARG(val_mcurve, curve), ARG(val_mpint, x))) -FUNC(val_mpoint, ecc_montgomery_point_copy, (ARG(val_mpoint, orig))) -FUNC(val_mpoint, ecc_montgomery_diff_add, - (ARG(val_mpoint, P), ARG(val_mpoint, Q), ARG(val_mpoint, PminusQ))) -FUNC(val_mpoint, ecc_montgomery_double, (ARG(val_mpoint, P))) -FUNC(val_mpoint, ecc_montgomery_multiply, - (ARG(val_mpoint, B), ARG(val_mpint, n))) -FUNC(void, ecc_montgomery_get_affine, - (ARG(val_mpoint, P), ARG(out_val_mpint, x))) -FUNC(boolean, ecc_montgomery_is_identity, (ARG(val_mpoint, P))) -FUNC(val_ecurve, ecc_edwards_curve, - (ARG(val_mpint, p), ARG(val_mpint, d), ARG(val_mpint, a), - ARG(opt_val_mpint, nonsquare_mod_p))) -FUNC(val_epoint, ecc_edwards_point_new, - (ARG(val_ecurve, curve), ARG(val_mpint, x), ARG(val_mpint, y))) -FUNC(val_epoint, ecc_edwards_point_new_from_y, - (ARG(val_ecurve, curve), ARG(val_mpint, y), ARG(uint, desired_x_parity))) -FUNC(val_epoint, ecc_edwards_point_copy, (ARG(val_epoint, orig))) -FUNC(val_epoint, ecc_edwards_add, (ARG(val_epoint, P), ARG(val_epoint, Q))) -FUNC(val_epoint, ecc_edwards_multiply, (ARG(val_epoint, B), ARG(val_mpint, n))) -FUNC(uint, ecc_edwards_eq, (ARG(val_epoint, P), ARG(val_epoint, Q))) -FUNC(void, ecc_edwards_get_affine, - (ARG(val_epoint, P), ARG(out_val_mpint, x), ARG(out_val_mpint, y))) +FUNC(void, ecc_weierstrass_get_affine, ARG(val_wpoint, P), + ARG(out_val_mpint, x), ARG(out_val_mpint, y)) +FUNC(val_mcurve, ecc_montgomery_curve, ARG(val_mpint, p), ARG(val_mpint, a), + ARG(val_mpint, b)) +FUNC(val_mpoint, ecc_montgomery_point_new, ARG(val_mcurve, curve), + ARG(val_mpint, x)) +FUNC(val_mpoint, ecc_montgomery_point_copy, ARG(val_mpoint, orig)) +FUNC(val_mpoint, ecc_montgomery_diff_add, ARG(val_mpoint, P), + ARG(val_mpoint, Q), ARG(val_mpoint, PminusQ)) +FUNC(val_mpoint, ecc_montgomery_double, ARG(val_mpoint, P)) +FUNC(val_mpoint, ecc_montgomery_multiply, ARG(val_mpoint, B), ARG(val_mpint, n)) +FUNC(void, ecc_montgomery_get_affine, ARG(val_mpoint, P), ARG(out_val_mpint, x)) +FUNC(boolean, ecc_montgomery_is_identity, ARG(val_mpoint, P)) +FUNC(val_ecurve, ecc_edwards_curve, ARG(val_mpint, p), ARG(val_mpint, d), + ARG(val_mpint, a), ARG(opt_val_mpint, nonsquare_mod_p)) +FUNC(val_epoint, ecc_edwards_point_new, ARG(val_ecurve, curve), + ARG(val_mpint, x), ARG(val_mpint, y)) +FUNC(val_epoint, ecc_edwards_point_new_from_y, ARG(val_ecurve, curve), + ARG(val_mpint, y), ARG(uint, desired_x_parity)) +FUNC(val_epoint, ecc_edwards_point_copy, ARG(val_epoint, orig)) +FUNC(val_epoint, ecc_edwards_add, ARG(val_epoint, P), ARG(val_epoint, Q)) +FUNC(val_epoint, ecc_edwards_multiply, ARG(val_epoint, B), ARG(val_mpint, n)) +FUNC(uint, ecc_edwards_eq, ARG(val_epoint, P), ARG(val_epoint, Q)) +FUNC(void, ecc_edwards_get_affine, ARG(val_epoint, P), ARG(out_val_mpint, x), + ARG(out_val_mpint, y)) /* * The ssh_hash abstraction. Note the 'consumed', indicating that @@ -259,26 +255,26 @@ FUNC(void, ecc_edwards_get_affine, * ssh_hash_update is an invention of testcrypt, handled in the real C * API by the hash object also functioning as a BinarySink. */ -FUNC(opt_val_hash, ssh_hash_new, (ARG(hashalg, alg))) -FUNC(void, ssh_hash_reset, (ARG(val_hash, h))) -FUNC(val_hash, ssh_hash_copy, (ARG(val_hash, orig))) -FUNC_WRAPPED(val_string, ssh_hash_digest, (ARG(val_hash, h))) -FUNC_WRAPPED(val_string, ssh_hash_final, (ARG(consumed_val_hash, h))) -FUNC(void, ssh_hash_update, (ARG(val_hash, h), ARG(val_string_ptrlen, data))) +FUNC(opt_val_hash, ssh_hash_new, ARG(hashalg, alg)) +FUNC(void, ssh_hash_reset, ARG(val_hash, h)) +FUNC(val_hash, ssh_hash_copy, ARG(val_hash, orig)) +FUNC_WRAPPED(val_string, ssh_hash_digest, ARG(val_hash, h)) +FUNC_WRAPPED(val_string, ssh_hash_final, ARG(consumed_val_hash, h)) +FUNC(void, ssh_hash_update, ARG(val_hash, h), ARG(val_string_ptrlen, data)) -FUNC(opt_val_hash, blake2b_new_general, (ARG(uint, hashlen))) +FUNC(opt_val_hash, blake2b_new_general, ARG(uint, hashlen)) /* * The ssh2_mac abstraction. Note the optional ssh_cipher parameter * to ssh2_mac_new. Also, again, I've invented an ssh2_mac_update so * you can put data into the MAC. */ -FUNC(val_mac, ssh2_mac_new, (ARG(macalg, alg), ARG(opt_val_cipher, cipher))) -FUNC(void, ssh2_mac_setkey, (ARG(val_mac, m), ARG(val_string_ptrlen, key))) -FUNC(void, ssh2_mac_start, (ARG(val_mac, m))) -FUNC(void, ssh2_mac_update, (ARG(val_mac, m), ARG(val_string_ptrlen, data))) -FUNC_WRAPPED(val_string, ssh2_mac_genresult, (ARG(val_mac, m))) -FUNC(val_string_asciz_const, ssh2_mac_text_name, (ARG(val_mac, m))) +FUNC(val_mac, ssh2_mac_new, ARG(macalg, alg), ARG(opt_val_cipher, cipher)) +FUNC(void, ssh2_mac_setkey, ARG(val_mac, m), ARG(val_string_ptrlen, key)) +FUNC(void, ssh2_mac_start, ARG(val_mac, m)) +FUNC(void, ssh2_mac_update, ARG(val_mac, m), ARG(val_string_ptrlen, data)) +FUNC_WRAPPED(val_string, ssh2_mac_genresult, ARG(val_mac, m)) +FUNC(val_string_asciz_const, ssh2_mac_text_name, ARG(val_mac, m)) /* * The ssh_key abstraction. All the uses of BinarySink and @@ -287,276 +283,254 @@ FUNC(val_string_asciz_const, ssh2_mac_text_name, (ARG(val_mac, m))) * all the functions that output key and signature blobs do it by * returning a string. */ -FUNC(val_key, ssh_key_new_pub, (ARG(keyalg, alg), ARG(val_string_ptrlen, pub))) -FUNC(opt_val_key, ssh_key_new_priv, - (ARG(keyalg, alg), ARG(val_string_ptrlen, pub), - ARG(val_string_ptrlen, priv))) -FUNC(opt_val_key, ssh_key_new_priv_openssh, - (ARG(keyalg, alg), ARG(val_string_binarysource, src))) -FUNC(opt_val_string_asciz, ssh_key_invalid, - (ARG(val_key, key), ARG(uint, flags))) -FUNC(void, ssh_key_sign, - (ARG(val_key, key), ARG(val_string_ptrlen, data), ARG(uint, flags), - ARG(out_val_string_binarysink, sig))) -FUNC(boolean, ssh_key_verify, - (ARG(val_key, key), ARG(val_string_ptrlen, sig), - ARG(val_string_ptrlen, data))) -FUNC(void, ssh_key_public_blob, - (ARG(val_key, key), ARG(out_val_string_binarysink, blob))) -FUNC(void, ssh_key_private_blob, - (ARG(val_key, key), ARG(out_val_string_binarysink, blob))) -FUNC(void, ssh_key_openssh_blob, - (ARG(val_key, key), ARG(out_val_string_binarysink, blob))) -FUNC(val_string_asciz, ssh_key_cache_str, (ARG(val_key, key))) -FUNC(val_keycomponents, ssh_key_components, (ARG(val_key, key))) -FUNC(uint, ssh_key_public_bits, - (ARG(keyalg, self), ARG(val_string_ptrlen, blob))) +FUNC(val_key, ssh_key_new_pub, ARG(keyalg, alg), ARG(val_string_ptrlen, pub)) +FUNC(opt_val_key, ssh_key_new_priv, ARG(keyalg, alg), + ARG(val_string_ptrlen, pub), ARG(val_string_ptrlen, priv)) +FUNC(opt_val_key, ssh_key_new_priv_openssh, ARG(keyalg, alg), + ARG(val_string_binarysource, src)) +FUNC(opt_val_string_asciz, ssh_key_invalid, ARG(val_key, key), ARG(uint, flags)) +FUNC(void, ssh_key_sign, ARG(val_key, key), ARG(val_string_ptrlen, data), + ARG(uint, flags), ARG(out_val_string_binarysink, sig)) +FUNC(boolean, ssh_key_verify, ARG(val_key, key), ARG(val_string_ptrlen, sig), + ARG(val_string_ptrlen, data)) +FUNC(void, ssh_key_public_blob, ARG(val_key, key), + ARG(out_val_string_binarysink, blob)) +FUNC(void, ssh_key_private_blob, ARG(val_key, key), + ARG(out_val_string_binarysink, blob)) +FUNC(void, ssh_key_openssh_blob, ARG(val_key, key), + ARG(out_val_string_binarysink, blob)) +FUNC(val_string_asciz, ssh_key_cache_str, ARG(val_key, key)) +FUNC(val_keycomponents, ssh_key_components, ARG(val_key, key)) +FUNC(uint, ssh_key_public_bits, ARG(keyalg, self), ARG(val_string_ptrlen, blob)) /* * Accessors to retrieve the innards of a 'key_components'. */ -FUNC(uint, key_components_count, (ARG(val_keycomponents, kc))) +FUNC(uint, key_components_count, ARG(val_keycomponents, kc)) FUNC(opt_val_string_asciz_const, key_components_nth_name, - (ARG(val_keycomponents, kc), ARG(uint, n))) + ARG(val_keycomponents, kc), ARG(uint, n)) FUNC(opt_val_string_asciz_const, key_components_nth_str, - (ARG(val_keycomponents, kc), ARG(uint, n))) -FUNC(opt_val_mpint, key_components_nth_mp, - (ARG(val_keycomponents, kc), ARG(uint, n))) + ARG(val_keycomponents, kc), ARG(uint, n)) +FUNC(opt_val_mpint, key_components_nth_mp, ARG(val_keycomponents, kc), + ARG(uint, n)) /* * The ssh_cipher abstraction. The in-place encrypt and decrypt * functions are wrapped to replace them with versions that take one * string and return a separate string. */ -FUNC(opt_val_cipher, ssh_cipher_new, (ARG(cipheralg, alg))) -FUNC_WRAPPED(void, ssh_cipher_setiv, - (ARG(val_cipher, c), ARG(val_string_ptrlen, iv))) -FUNC_WRAPPED(void, ssh_cipher_setkey, - (ARG(val_cipher, c), ARG(val_string_ptrlen, key))) -FUNC_WRAPPED(val_string, ssh_cipher_encrypt, - (ARG(val_cipher, c), ARG(val_string_ptrlen, blk))) -FUNC_WRAPPED(val_string, ssh_cipher_decrypt, - (ARG(val_cipher, c), ARG(val_string_ptrlen, blk))) -FUNC_WRAPPED(val_string, ssh_cipher_encrypt_length, - (ARG(val_cipher, c), ARG(val_string_ptrlen, blk), ARG(uint, seq))) -FUNC_WRAPPED(val_string, ssh_cipher_decrypt_length, - (ARG(val_cipher, c), ARG(val_string_ptrlen, blk), ARG(uint, seq))) +FUNC(opt_val_cipher, ssh_cipher_new, ARG(cipheralg, alg)) +FUNC_WRAPPED(void, ssh_cipher_setiv, ARG(val_cipher, c), + ARG(val_string_ptrlen, iv)) +FUNC_WRAPPED(void, ssh_cipher_setkey, ARG(val_cipher, c), + ARG(val_string_ptrlen, key)) +FUNC_WRAPPED(val_string, ssh_cipher_encrypt, ARG(val_cipher, c), + ARG(val_string_ptrlen, blk)) +FUNC_WRAPPED(val_string, ssh_cipher_decrypt, ARG(val_cipher, c), + ARG(val_string_ptrlen, blk)) +FUNC_WRAPPED(val_string, ssh_cipher_encrypt_length, ARG(val_cipher, c), + ARG(val_string_ptrlen, blk), ARG(uint, seq)) +FUNC_WRAPPED(val_string, ssh_cipher_decrypt_length, ARG(val_cipher, c), + ARG(val_string_ptrlen, blk), ARG(uint, seq)) /* * Integer Diffie-Hellman. */ -FUNC(val_dh, dh_setup_group, (ARG(dh_group, group))) -FUNC(val_dh, dh_setup_gex, (ARG(val_mpint, p), ARG(val_mpint, g))) -FUNC(uint, dh_modulus_bit_size, (ARG(val_dh, ctx))) -FUNC(val_mpint, dh_create_e, (ARG(val_dh, ctx), ARG(uint, nbits))) -FUNC_WRAPPED(boolean, dh_validate_f, (ARG(val_dh, ctx), ARG(val_mpint, f))) -FUNC(val_mpint, dh_find_K, (ARG(val_dh, ctx), ARG(val_mpint, f))) +FUNC(val_dh, dh_setup_group, ARG(dh_group, group)) +FUNC(val_dh, dh_setup_gex, ARG(val_mpint, p), ARG(val_mpint, g)) +FUNC(uint, dh_modulus_bit_size, ARG(val_dh, ctx)) +FUNC(val_mpint, dh_create_e, ARG(val_dh, ctx), ARG(uint, nbits)) +FUNC_WRAPPED(boolean, dh_validate_f, ARG(val_dh, ctx), ARG(val_mpint, f)) +FUNC(val_mpint, dh_find_K, ARG(val_dh, ctx), ARG(val_mpint, f)) /* * Elliptic-curve Diffie-Hellman. */ -FUNC(val_ecdh, ssh_ecdhkex_newkey, (ARG(ecdh_alg, alg))) -FUNC(void, ssh_ecdhkex_getpublic, - (ARG(val_ecdh, key), ARG(out_val_string_binarysink, pub))) -FUNC(opt_val_mpint, ssh_ecdhkex_getkey, - (ARG(val_ecdh, key), ARG(val_string_ptrlen, pub))) +FUNC(val_ecdh, ssh_ecdhkex_newkey, ARG(ecdh_alg, alg)) +FUNC(void, ssh_ecdhkex_getpublic, ARG(val_ecdh, key), + ARG(out_val_string_binarysink, pub)) +FUNC(opt_val_mpint, ssh_ecdhkex_getkey, ARG(val_ecdh, key), + ARG(val_string_ptrlen, pub)) /* * RSA key exchange, and also the BinarySource get function * get_ssh1_rsa_priv_agent, which is a convenient way to make an * RSAKey for RSA kex testing purposes. */ -FUNC(val_rsakex, ssh_rsakex_newkey, (ARG(val_string_ptrlen, data))) -FUNC(uint, ssh_rsakex_klen, (ARG(val_rsakex, key))) -FUNC(val_string, ssh_rsakex_encrypt, - (ARG(val_rsakex, key), ARG(hashalg, h), ARG(val_string_ptrlen, plaintext))) -FUNC(opt_val_mpint, ssh_rsakex_decrypt, - (ARG(val_rsakex, key), ARG(hashalg, h), - ARG(val_string_ptrlen, ciphertext))) -FUNC(val_rsakex, get_rsa_ssh1_priv_agent, (ARG(val_string_binarysource, src))) +FUNC(val_rsakex, ssh_rsakex_newkey, ARG(val_string_ptrlen, data)) +FUNC(uint, ssh_rsakex_klen, ARG(val_rsakex, key)) +FUNC(val_string, ssh_rsakex_encrypt, ARG(val_rsakex, key), ARG(hashalg, h), + ARG(val_string_ptrlen, plaintext)) +FUNC(opt_val_mpint, ssh_rsakex_decrypt, ARG(val_rsakex, key), ARG(hashalg, h), + ARG(val_string_ptrlen, ciphertext)) +FUNC(val_rsakex, get_rsa_ssh1_priv_agent, ARG(val_string_binarysource, src)) /* * Bare RSA keys as used in SSH-1. The construction API functions * write into an existing RSAKey object, so I've invented an 'rsa_new' * function to make one in the first place. */ -FUNC(val_rsa, rsa_new, (VOID)) -FUNC(void, get_rsa_ssh1_pub, - (ARG(val_string_binarysource, src), ARG(val_rsa, key), - ARG(rsaorder, order))) -FUNC(void, get_rsa_ssh1_priv, - (ARG(val_string_binarysource, src), ARG(val_rsa, key))) -FUNC_WRAPPED(opt_val_string, rsa_ssh1_encrypt, - (ARG(val_string_ptrlen, data), ARG(val_rsa, key))) -FUNC(val_mpint, rsa_ssh1_decrypt, (ARG(val_mpint, input), ARG(val_rsa, key))) -FUNC_WRAPPED(val_string, rsa_ssh1_decrypt_pkcs1, - (ARG(val_mpint, input), ARG(val_rsa, key))) -FUNC(val_string_asciz, rsastr_fmt, (ARG(val_rsa, key))) -FUNC(val_string_asciz, rsa_ssh1_fingerprint, (ARG(val_rsa, key))) -FUNC(void, rsa_ssh1_public_blob, - (ARG(out_val_string_binarysink, blob), ARG(val_rsa, key), - ARG(rsaorder, order))) -FUNC(int, rsa_ssh1_public_blob_len, (ARG(val_string_ptrlen, data))) -FUNC(void, rsa_ssh1_private_blob_agent, - (ARG(out_val_string_binarysink, blob), ARG(val_rsa, key))) +FUNC(val_rsa, rsa_new, VOID) +FUNC(void, get_rsa_ssh1_pub, ARG(val_string_binarysource, src), + ARG(val_rsa, key), ARG(rsaorder, order)) +FUNC(void, get_rsa_ssh1_priv, ARG(val_string_binarysource, src), + ARG(val_rsa, key)) +FUNC_WRAPPED(opt_val_string, rsa_ssh1_encrypt, ARG(val_string_ptrlen, data), + ARG(val_rsa, key)) +FUNC(val_mpint, rsa_ssh1_decrypt, ARG(val_mpint, input), ARG(val_rsa, key)) +FUNC_WRAPPED(val_string, rsa_ssh1_decrypt_pkcs1, ARG(val_mpint, input), + ARG(val_rsa, key)) +FUNC(val_string_asciz, rsastr_fmt, ARG(val_rsa, key)) +FUNC(val_string_asciz, rsa_ssh1_fingerprint, ARG(val_rsa, key)) +FUNC(void, rsa_ssh1_public_blob, ARG(out_val_string_binarysink, blob), + ARG(val_rsa, key), ARG(rsaorder, order)) +FUNC(int, rsa_ssh1_public_blob_len, ARG(val_string_ptrlen, data)) +FUNC(void, rsa_ssh1_private_blob_agent, ARG(out_val_string_binarysink, blob), + ARG(val_rsa, key)) /* * The PRNG type. Similarly to hashes and MACs, I've invented an extra * function prng_seed_update for putting seed data into the PRNG's * exposed BinarySink. */ -FUNC(val_prng, prng_new, (ARG(hashalg, hashalg))) -FUNC(void, prng_seed_begin, (ARG(val_prng, pr))) -FUNC(void, prng_seed_update, (ARG(val_prng, pr), ARG(val_string_ptrlen, data))) -FUNC(void, prng_seed_finish, (ARG(val_prng, pr))) -FUNC_WRAPPED(val_string, prng_read, (ARG(val_prng, pr), ARG(uint, size))) -FUNC(void, prng_add_entropy, - (ARG(val_prng, pr), ARG(uint, source_id), ARG(val_string_ptrlen, data))) +FUNC(val_prng, prng_new, ARG(hashalg, hashalg)) +FUNC(void, prng_seed_begin, ARG(val_prng, pr)) +FUNC(void, prng_seed_update, ARG(val_prng, pr), ARG(val_string_ptrlen, data)) +FUNC(void, prng_seed_finish, ARG(val_prng, pr)) +FUNC_WRAPPED(val_string, prng_read, ARG(val_prng, pr), ARG(uint, size)) +FUNC(void, prng_add_entropy, ARG(val_prng, pr), ARG(uint, source_id), + ARG(val_string_ptrlen, data)) /* * Key load/save functions, or rather, the BinarySource / strbuf API * that sits just inside the file I/O versions. */ -FUNC(boolean, ppk_encrypted_s, - (ARG(val_string_binarysource, src), - ARG(out_opt_val_string_asciz, comment))) -FUNC(boolean, rsa1_encrypted_s, - (ARG(val_string_binarysource, src), - ARG(out_opt_val_string_asciz, comment))) -FUNC(boolean, ppk_loadpub_s, - (ARG(val_string_binarysource, src), - ARG(out_opt_val_string_asciz, algorithm), - ARG(out_val_string_binarysink, blob), - ARG(out_opt_val_string_asciz, comment), - ARG(out_opt_val_string_asciz_const, error))) -FUNC(int, rsa1_loadpub_s, - (ARG(val_string_binarysource, src), ARG(out_val_string_binarysink, blob), - ARG(out_opt_val_string_asciz, comment), - ARG(out_opt_val_string_asciz_const, error))) -FUNC_WRAPPED(opt_val_key, ppk_load_s, - (ARG(val_string_binarysource, src), - ARG(out_opt_val_string_asciz, comment), - ARG(opt_val_string_asciz, passphrase), - ARG(out_opt_val_string_asciz_const, error))) -FUNC_WRAPPED(int, rsa1_load_s, - (ARG(val_string_binarysource, src), ARG(val_rsa, key), - ARG(out_opt_val_string_asciz, comment), - ARG(opt_val_string_asciz, passphrase), - ARG(out_opt_val_string_asciz_const, error))) -FUNC_WRAPPED(val_string, ppk_save_sb, - (ARG(val_key, key), ARG(opt_val_string_asciz, comment), - ARG(opt_val_string_asciz, passphrase), ARG(uint, fmt_version), - ARG(argon2flavour, flavour), ARG(uint, mem), ARG(uint, passes), - ARG(uint, parallel))) -FUNC_WRAPPED(val_string, rsa1_save_sb, - (ARG(val_rsa, key), ARG(opt_val_string_asciz, comment), - ARG(opt_val_string_asciz, passphrase))) +FUNC(boolean, ppk_encrypted_s, ARG(val_string_binarysource, src), + ARG(out_opt_val_string_asciz, comment)) +FUNC(boolean, rsa1_encrypted_s, ARG(val_string_binarysource, src), + ARG(out_opt_val_string_asciz, comment)) +FUNC(boolean, ppk_loadpub_s, ARG(val_string_binarysource, src), + ARG(out_opt_val_string_asciz, algorithm), + ARG(out_val_string_binarysink, blob), + ARG(out_opt_val_string_asciz, comment), + ARG(out_opt_val_string_asciz_const, error)) +FUNC(int, rsa1_loadpub_s, ARG(val_string_binarysource, src), + ARG(out_val_string_binarysink, blob), + ARG(out_opt_val_string_asciz, comment), + ARG(out_opt_val_string_asciz_const, error)) +FUNC_WRAPPED(opt_val_key, ppk_load_s, ARG(val_string_binarysource, src), + ARG(out_opt_val_string_asciz, comment), + ARG(opt_val_string_asciz, passphrase), + ARG(out_opt_val_string_asciz_const, error)) +FUNC_WRAPPED(int, rsa1_load_s, ARG(val_string_binarysource, src), + ARG(val_rsa, key), ARG(out_opt_val_string_asciz, comment), + ARG(opt_val_string_asciz, passphrase), + ARG(out_opt_val_string_asciz_const, error)) +FUNC_WRAPPED(val_string, ppk_save_sb, ARG(val_key, key), + ARG(opt_val_string_asciz, comment), + ARG(opt_val_string_asciz, passphrase), ARG(uint, fmt_version), + ARG(argon2flavour, flavour), ARG(uint, mem), ARG(uint, passes), + ARG(uint, parallel)) +FUNC_WRAPPED(val_string, rsa1_save_sb, ARG(val_rsa, key), + ARG(opt_val_string_asciz, comment), + ARG(opt_val_string_asciz, passphrase)) -FUNC(val_string_asciz, ssh2_fingerprint_blob, - (ARG(val_string_ptrlen, blob), ARG(fptype, fptype))) +FUNC(val_string_asciz, ssh2_fingerprint_blob, ARG(val_string_ptrlen, blob), + ARG(fptype, fptype)) /* * Password hashing. */ -FUNC_WRAPPED(val_string, argon2, - (ARG(argon2flavour, flavour), ARG(uint, mem), ARG(uint, passes), - ARG(uint, parallel), ARG(uint, taglen), ARG(val_string_ptrlen, P), - ARG(val_string_ptrlen, S), ARG(val_string_ptrlen, K), - ARG(val_string_ptrlen, X))) -FUNC(val_string, argon2_long_hash, - (ARG(uint, length), ARG(val_string_ptrlen, data))) +FUNC_WRAPPED(val_string, argon2, ARG(argon2flavour, flavour), ARG(uint, mem), + ARG(uint, passes), ARG(uint, parallel), ARG(uint, taglen), + ARG(val_string_ptrlen, P), ARG(val_string_ptrlen, S), + ARG(val_string_ptrlen, K), ARG(val_string_ptrlen, X)) +FUNC(val_string, argon2_long_hash, ARG(uint, length), + ARG(val_string_ptrlen, data)) /* * Key generation functions. */ -FUNC_WRAPPED(val_key, rsa_generate, - (ARG(uint, bits), ARG(boolean, strong), ARG(val_pgc, pgc))) -FUNC_WRAPPED(val_key, dsa_generate, (ARG(uint, bits), ARG(val_pgc, pgc))) -FUNC_WRAPPED(opt_val_key, ecdsa_generate, (ARG(uint, bits))) -FUNC_WRAPPED(opt_val_key, eddsa_generate, (ARG(uint, bits))) -FUNC(val_rsa, rsa1_generate, - (ARG(uint, bits), ARG(boolean, strong), ARG(val_pgc, pgc))) -FUNC(val_pgc, primegen_new_context, (ARG(primegenpolicy, policy))) -FUNC_WRAPPED(opt_val_mpint, primegen_generate, - (ARG(val_pgc, ctx), ARG(consumed_val_pcs, pcs))) -FUNC(val_string, primegen_mpu_certificate, - (ARG(val_pgc, ctx), ARG(val_mpint, p))) -FUNC(val_pcs, pcs_new, (ARG(uint, bits))) -FUNC(val_pcs, pcs_new_with_firstbits, - (ARG(uint, bits), ARG(uint, first), ARG(uint, nfirst))) -FUNC(void, pcs_require_residue, - (ARG(val_pcs, s), ARG(val_mpint, mod), ARG(val_mpint, res))) -FUNC(void, pcs_require_residue_1, (ARG(val_pcs, s), ARG(val_mpint, mod))) -FUNC(void, pcs_require_residue_1_mod_prime, - (ARG(val_pcs, s), ARG(val_mpint, mod))) -FUNC(void, pcs_avoid_residue_small, - (ARG(val_pcs, s), ARG(uint, mod), ARG(uint, res))) -FUNC(void, pcs_try_sophie_germain, (ARG(val_pcs, s))) -FUNC(void, pcs_set_oneshot, (ARG(val_pcs, s))) -FUNC(void, pcs_ready, (ARG(val_pcs, s))) -FUNC(void, pcs_inspect, - (ARG(val_pcs, pcs), ARG(out_val_mpint, limit_out), - ARG(out_val_mpint, factor_out), ARG(out_val_mpint, addend_out))) -FUNC(val_mpint, pcs_generate, (ARG(val_pcs, s))) -FUNC(val_pockle, pockle_new, (VOID)) -FUNC(uint, pockle_mark, (ARG(val_pockle, pockle))) -FUNC(void, pockle_release, (ARG(val_pockle, pockle), ARG(uint, mark))) -FUNC(pocklestatus, pockle_add_small_prime, - (ARG(val_pockle, pockle), ARG(val_mpint, p))) -FUNC_WRAPPED(pocklestatus, pockle_add_prime, - (ARG(val_pockle, pockle), ARG(val_mpint, p), - ARG(mpint_list, factors), ARG(val_mpint, witness))) -FUNC(val_string, pockle_mpu, (ARG(val_pockle, pockle), ARG(val_mpint, p))) -FUNC(val_millerrabin, miller_rabin_new, (ARG(val_mpint, p))) -FUNC(mr_result, miller_rabin_test, - (ARG(val_millerrabin, mr), ARG(val_mpint, w))) +FUNC_WRAPPED(val_key, rsa_generate, ARG(uint, bits), ARG(boolean, strong), + ARG(val_pgc, pgc)) +FUNC_WRAPPED(val_key, dsa_generate, ARG(uint, bits), ARG(val_pgc, pgc)) +FUNC_WRAPPED(opt_val_key, ecdsa_generate, ARG(uint, bits)) +FUNC_WRAPPED(opt_val_key, eddsa_generate, ARG(uint, bits)) +FUNC(val_rsa, rsa1_generate, ARG(uint, bits), ARG(boolean, strong), + ARG(val_pgc, pgc)) +FUNC(val_pgc, primegen_new_context, ARG(primegenpolicy, policy)) +FUNC_WRAPPED(opt_val_mpint, primegen_generate, ARG(val_pgc, ctx), + ARG(consumed_val_pcs, pcs)) +FUNC(val_string, primegen_mpu_certificate, ARG(val_pgc, ctx), ARG(val_mpint, p)) +FUNC(val_pcs, pcs_new, ARG(uint, bits)) +FUNC(val_pcs, pcs_new_with_firstbits, ARG(uint, bits), ARG(uint, first), + ARG(uint, nfirst)) +FUNC(void, pcs_require_residue, ARG(val_pcs, s), ARG(val_mpint, mod), + ARG(val_mpint, res)) +FUNC(void, pcs_require_residue_1, ARG(val_pcs, s), ARG(val_mpint, mod)) +FUNC(void, pcs_require_residue_1_mod_prime, ARG(val_pcs, s), + ARG(val_mpint, mod)) +FUNC(void, pcs_avoid_residue_small, ARG(val_pcs, s), ARG(uint, mod), + ARG(uint, res)) +FUNC(void, pcs_try_sophie_germain, ARG(val_pcs, s)) +FUNC(void, pcs_set_oneshot, ARG(val_pcs, s)) +FUNC(void, pcs_ready, ARG(val_pcs, s)) +FUNC(void, pcs_inspect, ARG(val_pcs, pcs), ARG(out_val_mpint, limit_out), + ARG(out_val_mpint, factor_out), ARG(out_val_mpint, addend_out)) +FUNC(val_mpint, pcs_generate, ARG(val_pcs, s)) +FUNC(val_pockle, pockle_new, VOID) +FUNC(uint, pockle_mark, ARG(val_pockle, pockle)) +FUNC(void, pockle_release, ARG(val_pockle, pockle), ARG(uint, mark)) +FUNC(pocklestatus, pockle_add_small_prime, ARG(val_pockle, pockle), + ARG(val_mpint, p)) +FUNC_WRAPPED(pocklestatus, pockle_add_prime, ARG(val_pockle, pockle), + ARG(val_mpint, p), ARG(mpint_list, factors), + ARG(val_mpint, witness)) +FUNC(val_string, pockle_mpu, ARG(val_pockle, pockle), ARG(val_mpint, p)) +FUNC(val_millerrabin, miller_rabin_new, ARG(val_mpint, p)) +FUNC(mr_result, miller_rabin_test, ARG(val_millerrabin, mr), ARG(val_mpint, w)) /* * Miscellaneous. */ -FUNC(val_wpoint, ecdsa_public, (ARG(val_mpint, private_key), ARG(keyalg, alg))) -FUNC(val_epoint, eddsa_public, (ARG(val_mpint, private_key), ARG(keyalg, alg))) -FUNC_WRAPPED(val_string, des_encrypt_xdmauth, - (ARG(val_string_ptrlen, key), ARG(val_string_ptrlen, blk))) -FUNC_WRAPPED(val_string, des_decrypt_xdmauth, - (ARG(val_string_ptrlen, key), ARG(val_string_ptrlen, blk))) -FUNC_WRAPPED(val_string, des3_encrypt_pubkey, - (ARG(val_string_ptrlen, key), ARG(val_string_ptrlen, blk))) -FUNC_WRAPPED(val_string, des3_decrypt_pubkey, - (ARG(val_string_ptrlen, key), ARG(val_string_ptrlen, blk))) -FUNC_WRAPPED(val_string, des3_encrypt_pubkey_ossh, - (ARG(val_string_ptrlen, key), ARG(val_string_ptrlen, iv), - ARG(val_string_ptrlen, blk))) -FUNC_WRAPPED(val_string, des3_decrypt_pubkey_ossh, - (ARG(val_string_ptrlen, key), ARG(val_string_ptrlen, iv), - ARG(val_string_ptrlen, blk))) -FUNC_WRAPPED(val_string, aes256_encrypt_pubkey, - (ARG(val_string_ptrlen, key), ARG(val_string_ptrlen, iv), - ARG(val_string_ptrlen, blk))) -FUNC_WRAPPED(val_string, aes256_decrypt_pubkey, - (ARG(val_string_ptrlen, key), ARG(val_string_ptrlen, iv), - ARG(val_string_ptrlen, blk))) -FUNC(uint, crc32_rfc1662, (ARG(val_string_ptrlen, data))) -FUNC(uint, crc32_ssh1, (ARG(val_string_ptrlen, data))) -FUNC(uint, crc32_update, (ARG(uint, crc_input), ARG(val_string_ptrlen, data))) -FUNC(boolean, crcda_detect, - (ARG(val_string_ptrlen, packet), ARG(val_string_ptrlen, iv))) -FUNC(val_string, get_implementations_commasep, (ARG(val_string_ptrlen, alg))) -FUNC(void, http_digest_response, - (ARG(out_val_string_binarysink, response), - ARG(val_string_ptrlen, username), ARG(val_string_ptrlen, password), - ARG(val_string_ptrlen, realm), ARG(val_string_ptrlen, method), - ARG(val_string_ptrlen, uri), ARG(val_string_ptrlen, qop), - ARG(val_string_ptrlen, nonce), ARG(val_string_ptrlen, opaque), - ARG(uint, nonce_count), ARG(httpdigesthash, hash), - ARG(boolean, hash_username))) +FUNC(val_wpoint, ecdsa_public, ARG(val_mpint, private_key), ARG(keyalg, alg)) +FUNC(val_epoint, eddsa_public, ARG(val_mpint, private_key), ARG(keyalg, alg)) +FUNC_WRAPPED(val_string, des_encrypt_xdmauth, ARG(val_string_ptrlen, key), + ARG(val_string_ptrlen, blk)) +FUNC_WRAPPED(val_string, des_decrypt_xdmauth, ARG(val_string_ptrlen, key), + ARG(val_string_ptrlen, blk)) +FUNC_WRAPPED(val_string, des3_encrypt_pubkey, ARG(val_string_ptrlen, key), + ARG(val_string_ptrlen, blk)) +FUNC_WRAPPED(val_string, des3_decrypt_pubkey, ARG(val_string_ptrlen, key), + ARG(val_string_ptrlen, blk)) +FUNC_WRAPPED(val_string, des3_encrypt_pubkey_ossh, ARG(val_string_ptrlen, key), + ARG(val_string_ptrlen, iv), ARG(val_string_ptrlen, blk)) +FUNC_WRAPPED(val_string, des3_decrypt_pubkey_ossh, ARG(val_string_ptrlen, key), + ARG(val_string_ptrlen, iv), ARG(val_string_ptrlen, blk)) +FUNC_WRAPPED(val_string, aes256_encrypt_pubkey, ARG(val_string_ptrlen, key), + ARG(val_string_ptrlen, iv), ARG(val_string_ptrlen, blk)) +FUNC_WRAPPED(val_string, aes256_decrypt_pubkey, ARG(val_string_ptrlen, key), + ARG(val_string_ptrlen, iv), ARG(val_string_ptrlen, blk)) +FUNC(uint, crc32_rfc1662, ARG(val_string_ptrlen, data)) +FUNC(uint, crc32_ssh1, ARG(val_string_ptrlen, data)) +FUNC(uint, crc32_update, ARG(uint, crc_input), ARG(val_string_ptrlen, data)) +FUNC(boolean, crcda_detect, ARG(val_string_ptrlen, packet), + ARG(val_string_ptrlen, iv)) +FUNC(val_string, get_implementations_commasep, ARG(val_string_ptrlen, alg)) +FUNC(void, http_digest_response, ARG(out_val_string_binarysink, response), + ARG(val_string_ptrlen, username), ARG(val_string_ptrlen, password), + ARG(val_string_ptrlen, realm), ARG(val_string_ptrlen, method), + ARG(val_string_ptrlen, uri), ARG(val_string_ptrlen, qop), + ARG(val_string_ptrlen, nonce), ARG(val_string_ptrlen, opaque), + ARG(uint, nonce_count), ARG(httpdigesthash, hash), + ARG(boolean, hash_username)) /* * These functions aren't part of PuTTY's own API, but are additions * by testcrypt itself for administrative purposes. */ -FUNC(void, random_queue, (ARG(val_string_ptrlen, data))) -FUNC(uint, random_queue_len, (VOID)) -FUNC(void, random_make_prng, - (ARG(hashalg, hashalg), ARG(val_string_ptrlen, seed))) -FUNC(void, random_clear, (VOID)) +FUNC(void, random_queue, ARG(val_string_ptrlen, data)) +FUNC(uint, random_queue_len, VOID) +FUNC(void, random_make_prng, ARG(hashalg, hashalg), + ARG(val_string_ptrlen, seed)) +FUNC(void, random_clear, VOID) diff --git a/test/testcrypt.c b/test/testcrypt.c index e8593431..5936a248 100644 --- a/test/testcrypt.c +++ b/test/testcrypt.c @@ -1139,7 +1139,7 @@ OPTIONAL_PTR_FUNC(string) * In an ideal world, we would start from a specification like this in * testcrypt-func.h * - * FUNC(val_foo, example, (ARG(val_bar, bar), ARG(uint, n))) + * FUNC(val_foo, example, ARG(val_bar, bar), ARG(uint, n)) * * and generate a wrapper function looking like this: * @@ -1217,9 +1217,9 @@ OPTIONAL_PTR_FUNC(string) * * So the commas must appear _between_ ARG(...) specifiers. And that * means they unavoidably appear in _every_ expansion of FUNC() (or - * rather, every expansion that uses the argument list at all). - * Therefore, we need to ensure they're harmless in the other two - * functions as well. + * rather, every expansion that uses the variadic argument list at + * all). Therefore, we need to ensure they're harmless in the other + * two functions as well. * * In the get_args_example() function above, there's no real problem. * The list of assignments can perfectly well be separated by commas @@ -1264,48 +1264,6 @@ OPTIONAL_PTR_FUNC(string) * 'int' after the open brace, and the ';' before the closing brace, * and we've got everything we need to make it all syntactically legal. * - * Other points of note: - * - * Why the extra pair of parens around the whole argument list? You'd - * like to think that FUNC could be a variadic macro, and just use - * __VA_ARGS__ to expand all the arguments wherever they're needed. - * But unfortunately there's a portability consideration: some of the - * 'functions' wrapped by this system are actually macros in turn, and - * if you use __VA_ARGS__ to expand multiple arguments from one macro - * into the argument list of another macro, compilers disagree on what - * happens: Visual Studio in particular will turn __VA_ARGS__ into - * just one argument instead of multiple ones. That is, if you do this: - * - * #define DESTINATION_MACRO(a, b) ... stuff using a and b ... - * #define WRAPPER(...) DESTINATION_MACRO(__VA_ARGS__) - * WRAPPER(1, 2) - * - * then most compilers will behave as if you'd called - * DESTINATION_MACRO with 'a' expanding to 1 and 'b' expanding to 2. - * But Visual Studio will consider that you called it with 'a' - * expanding to the whole of __VA_ARGS__ - that is, the token sequence - * '1 , 2' - and will expand 'b' to nothing at all! - * - * So we have a constraint that if ARGS is going to be turned into the - * argument list to the actual called function - as it is in the final - * handle_example() expansion shown above - then the commas can't come - * from a variadic expansion of __VA_ARGS__. Hence, FUNC can't _be_ a - * variadic macro. Instead, we wrap all the arguments in an extra pair - * of parens, and generate the final call not by saying function(args) - * but by saying just 'function args', since 'args' contains the - * parens already. - * - * In get_args_example(), that's still fine, because our giant - * comma-separated expression containing multiple assignment - * subexpressions can legally be wrapped in parentheses as well. But - * what do you do in the structure definition? - * - * Answer: _there_ we use a variadic macro to strip off the outer - * parens, by expanding to just __VA_ARGS__. That's OK even in Visual - * Studio, because in this particular context, __VA_ARGS__ is ending - * up in a structure definition and definitely _not_ in the argument - * list of another macro. - * * Finally, what if a wrapped function has _no_ arguments? Two out of * three uses of the argument list here need some kind of special case * for that. That's why you have to write 'VOID' explicitly in an @@ -1313,18 +1271,47 @@ OPTIONAL_PTR_FUNC(string) * whatever is needed to avoid a syntax error in that special case. */ -#define DEPARENTHESISE(...) __VA_ARGS__ +/* + * Workarounds for an awkwardness in Visual Studio's preprocessor, + * which disagrees with everyone else about what happens if you expand + * __VA_ARGS__ into the argument list of another macro. gcc and clang + * will treat the commas expanding from __VA_ARGS__ as argument + * separators, whereas VS will make them all part of a single argument + * to the secondary macro. We want the former behaviour, so we use + * the following workaround to enforce it. + * + * Each of these JUXTAPOSE macros simply places its arguments side by + * side. But the arguments are macro-expanded before JUXTAPOSE is + * called at all, so we can do this: + * + * JUXTAPOSE(macroname, (__VA_ARGS__)) + * -> JUXTAPOSE(macroname, (foo, bar, baz)) + * -> macroname (foo, bar, baz) + * + * and this preliminary expansion causes the commas to be treated + * normally by the time VS gets round to expanding the inner macro. + * + * We need two differently named JUXTAPOSE macros, because we have to + * do this trick twice: once to turn FUNC and FUNC_WRAPPED in + * testcrypt-funcs.h into the underlying common FUNC_INNER, and again + * to expand the final function call. And you can't expand a macro + * inside text expanded from the _same_ macro, so we have to do the + * outer and inner instances of this trick using macros of different + * names. + */ +#define JUXTAPOSE1(first, second) first second +#define JUXTAPOSE2(first, second) first second -#define FUNC(outtype, fname, args) \ - FUNC_INNER(outtype, fname, fname, args) -#define FUNC_WRAPPED(outtype, fname, args) \ - FUNC_INNER(outtype, fname, fname##_wrapper, args) +#define FUNC(outtype, fname, ...) \ + JUXTAPOSE1(FUNC_INNER, (outtype, fname, fname, __VA_ARGS__)) +#define FUNC_WRAPPED(outtype, fname, ...) \ + JUXTAPOSE1(FUNC_INNER, (outtype, fname, fname##_wrapper, __VA_ARGS__)) #define ARG(type, arg) _predummy_##arg; TD_##type arg; int _postdummy_##arg #define VOID _voiddummy -#define FUNC_INNER(outtype, fname, realname, args) \ +#define FUNC_INNER(outtype, fname, realname, ...) \ typedef struct ARGS_##fname { \ - int DEPARENTHESISE args; \ + int __VA_ARGS__; \ } ARGS_##fname; #include "testcrypt-func.h" #undef FUNC_INNER @@ -1333,11 +1320,11 @@ OPTIONAL_PTR_FUNC(string) #define ARG(type, arg) _args.arg = get_##type(_in) #define VOID ((void)0) -#define FUNC_INNER(outtype, fname, realname, args) \ +#define FUNC_INNER(outtype, fname, realname, ...) \ static inline ARGS_##fname get_args_##fname(BinarySource *_in) { \ ARGS_##fname _args; \ memset(&_args, 0, sizeof(_args)); \ - args; \ + __VA_ARGS__; \ return _args; \ } #include "testcrypt-func.h" @@ -1347,11 +1334,11 @@ OPTIONAL_PTR_FUNC(string) #define ARG(type, arg) _args.arg #define VOID -#define FUNC_INNER(outtype, fname, realname, args) \ +#define FUNC_INNER(outtype, fname, realname, ...) \ static void handle_##fname(BinarySource *_in, strbuf *_out) { \ ARGS_##fname _args = get_args_##fname(_in); \ (void)_args; /* suppress warning if no actual arguments */ \ - return_##outtype(_out, realname args); \ + return_##outtype(_out, JUXTAPOSE2(realname, (__VA_ARGS__))); \ } #include "testcrypt-func.h" #undef FUNC_INNER @@ -1378,7 +1365,7 @@ static void process_line(BinarySource *in, strbuf *out) DISPATCH_COMMAND(checkenum); #undef DISPATCH_COMMAND -#define FUNC_INNER(outtype, fname, realname, args) \ +#define FUNC_INNER(outtype, fname, realname, ...) \ DISPATCH_INTERNAL(#fname,handle_##fname); #define ARG1(type, arg) #define ARGN(type, arg) diff --git a/test/testcrypt.py b/test/testcrypt.py index 18a741d5..61196446 100644 --- a/test/testcrypt.py +++ b/test/testcrypt.py @@ -354,7 +354,6 @@ def _parse_testcrypt_header(tokens): expect(",", "after return type") funcname = expect(is_id, "function name") expect(",", "after function name") - expect("(", "to begin argument list") args = [] firstargkind = expect({"ARG", "VOID"}, "at start of argument list") if firstargkind == "VOID": @@ -373,7 +372,6 @@ def _parse_testcrypt_header(tokens): if punct == ")": break expect("ARG", "to begin next argument") - expect(")", "at end of FUNC") yield funcname, rettype, args def _setup(scope): -- cgit v1.2.3 From e800e5310cb382bd00dfdf37500ce3f1cbc3d253 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 28 Nov 2021 11:15:46 +0000 Subject: Move fuzzterm.c into the test subdirectory. It's unquestionably a test program, and I'm generally clearing those out of the top level. I only missed it in the last clearout because I was looking for things with 'test' in the name. --- fuzzterm.c | 216 ---------------------------------------------------- test/fuzzterm.c | 216 ++++++++++++++++++++++++++++++++++++++++++++++++++++ unix/CMakeLists.txt | 2 +- 3 files changed, 217 insertions(+), 217 deletions(-) delete mode 100644 fuzzterm.c create mode 100644 test/fuzzterm.c diff --git a/fuzzterm.c b/fuzzterm.c deleted file mode 100644 index f1477054..00000000 --- a/fuzzterm.c +++ /dev/null @@ -1,216 +0,0 @@ -#include -#include -#include - -#include "putty.h" -#include "dialog.h" -#include "terminal.h" - -static const TermWinVtable fuzz_termwin_vt; - -int main(int argc, char **argv) -{ - char blk[512]; - size_t len; - Terminal *term; - Conf *conf; - struct unicode_data ucsdata; - TermWin termwin; - - termwin.vt = &fuzz_termwin_vt; - - conf = conf_new(); - do_defaults(NULL, conf); - init_ucs(&ucsdata, conf_get_str(conf, CONF_line_codepage), - conf_get_bool(conf, CONF_utf8_override), - CS_NONE, conf_get_int(conf, CONF_vtmode)); - - term = term_init(conf, &ucsdata, &termwin); - term_size(term, 24, 80, 10000); - term->ldisc = NULL; - /* Tell american fuzzy lop that this is a good place to fork. */ -#ifdef __AFL_HAVE_MANUAL_CONTROL - __AFL_INIT(); -#endif - while (!feof(stdin)) { - len = fread(blk, 1, sizeof(blk), stdin); - term_data(term, blk, len); - } - term_update(term); - return 0; -} - -/* functions required by terminal.c */ -static bool fuzz_setup_draw_ctx(TermWin *tw) { return true; } -static void fuzz_draw_text( - TermWin *tw, int x, int y, wchar_t *text, int len, - unsigned long attr, int lattr, truecolour tc) -{ - int i; - - printf("TEXT[attr=%08lx,lattr=%02x]@(%d,%d):", attr, lattr, x, y); - for (i = 0; i < len; i++) { - printf(" %x", (unsigned)text[i]); - } - printf("\n"); -} -static void fuzz_draw_cursor( - TermWin *tw, int x, int y, wchar_t *text, int len, - unsigned long attr, int lattr, truecolour tc) -{ - int i; - - printf("CURS[attr=%08lx,lattr=%02x]@(%d,%d):", attr, lattr, x, y); - for (i = 0; i < len; i++) { - printf(" %x", (unsigned)text[i]); - } - printf("\n"); -} -static void fuzz_draw_trust_sigil(TermWin *tw, int x, int y) -{ - printf("TRUST@(%d,%d)\n", x, y); -} -static int fuzz_char_width(TermWin *tw, int uc) { return 1; } -static void fuzz_free_draw_ctx(TermWin *tw) {} -static void fuzz_set_cursor_pos(TermWin *tw, int x, int y) {} -static void fuzz_set_raw_mouse_mode(TermWin *tw, bool enable) {} -static void fuzz_set_scrollbar(TermWin *tw, int total, int start, int page) {} -static void fuzz_bell(TermWin *tw, int mode) {} -static void fuzz_clip_write( - TermWin *tw, int clipboard, wchar_t *text, int *attrs, - truecolour *colours, int len, bool must_deselect) {} -static void fuzz_clip_request_paste(TermWin *tw, int clipboard) {} -static void fuzz_refresh(TermWin *tw) {} -static void fuzz_request_resize(TermWin *tw, int w, int h) {} -static void fuzz_set_title(TermWin *tw, const char *title, int codepage) {} -static void fuzz_set_icon_title(TermWin *tw, const char *icontitle, int cp) {} -static void fuzz_set_minimised(TermWin *tw, bool minimised) {} -static void fuzz_set_maximised(TermWin *tw, bool maximised) {} -static void fuzz_move(TermWin *tw, int x, int y) {} -static void fuzz_set_zorder(TermWin *tw, bool top) {} -static void fuzz_palette_set(TermWin *tw, unsigned start, unsigned ncolours, - const rgb *colours) {} -static void fuzz_palette_get_overrides(TermWin *tw, Terminal *term) {} - -static const TermWinVtable fuzz_termwin_vt = { - .setup_draw_ctx = fuzz_setup_draw_ctx, - .draw_text = fuzz_draw_text, - .draw_cursor = fuzz_draw_cursor, - .draw_trust_sigil = fuzz_draw_trust_sigil, - .char_width = fuzz_char_width, - .free_draw_ctx = fuzz_free_draw_ctx, - .set_cursor_pos = fuzz_set_cursor_pos, - .set_raw_mouse_mode = fuzz_set_raw_mouse_mode, - .set_scrollbar = fuzz_set_scrollbar, - .bell = fuzz_bell, - .clip_write = fuzz_clip_write, - .clip_request_paste = fuzz_clip_request_paste, - .refresh = fuzz_refresh, - .request_resize = fuzz_request_resize, - .set_title = fuzz_set_title, - .set_icon_title = fuzz_set_icon_title, - .set_minimised = fuzz_set_minimised, - .set_maximised = fuzz_set_maximised, - .move = fuzz_move, - .set_zorder = fuzz_set_zorder, - .palette_set = fuzz_palette_set, - .palette_get_overrides = fuzz_palette_get_overrides, -}; - -void ldisc_send(Ldisc *ldisc, const void *buf, int len, bool interactive) {} -void ldisc_echoedit_update(Ldisc *ldisc) {} -bool ldisc_has_input_buffered(Ldisc *ldisc) { return false; } -LdiscInputToken ldisc_get_input_token(Ldisc *ldisc) -{ unreachable("This fake ldisc never has any buffered input"); } -void ldisc_enable_prompt_callback(Ldisc *ldisc, prompts_t *p) -{ unreachable("This fake ldisc should never be used for user/pass prompts"); } -void modalfatalbox(const char *fmt, ...) { exit(0); } -void nonfatal(const char *fmt, ...) { } - -/* needed by timing.c */ -void timer_change_notify(unsigned long next) { } - -/* needed by config.c and sercfg.c */ - -void dlg_radiobutton_set(union control *ctrl, dlgparam *dp, int whichbutton) { } -int dlg_radiobutton_get(union control *ctrl, dlgparam *dp) { return 0; } -void dlg_checkbox_set(union control *ctrl, dlgparam *dp, bool checked) { } -bool dlg_checkbox_get(union control *ctrl, dlgparam *dp) { return false; } -void dlg_editbox_set(union control *ctrl, dlgparam *dp, char const *text) { } -char *dlg_editbox_get(union control *ctrl, dlgparam *dp) -{ return dupstr("moo"); } -void dlg_listbox_clear(union control *ctrl, dlgparam *dp) { } -void dlg_listbox_del(union control *ctrl, dlgparam *dp, int index) { } -void dlg_listbox_add(union control *ctrl, dlgparam *dp, char const *text) { } -void dlg_listbox_addwithid(union control *ctrl, dlgparam *dp, - char const *text, int id) { } -int dlg_listbox_getid(union control *ctrl, dlgparam *dp, int index) -{ return 0; } -int dlg_listbox_index(union control *ctrl, dlgparam *dp) { return -1; } -bool dlg_listbox_issel(union control *ctrl, dlgparam *dp, int index) -{ return false; } -void dlg_listbox_select(union control *ctrl, dlgparam *dp, int index) { } -void dlg_text_set(union control *ctrl, dlgparam *dp, char const *text) { } -void dlg_filesel_set(union control *ctrl, dlgparam *dp, Filename *fn) { } -Filename *dlg_filesel_get(union control *ctrl, dlgparam *dp) { return NULL; } -void dlg_fontsel_set(union control *ctrl, dlgparam *dp, FontSpec *fn) { } -FontSpec *dlg_fontsel_get(union control *ctrl, dlgparam *dp) { return NULL; } -void dlg_update_start(union control *ctrl, dlgparam *dp) { } -void dlg_update_done(union control *ctrl, dlgparam *dp) { } -void dlg_set_focus(union control *ctrl, dlgparam *dp) { } -void dlg_label_change(union control *ctrl, dlgparam *dp, char const *text) { } -union control *dlg_last_focused(union control *ctrl, dlgparam *dp) -{ return NULL; } -void dlg_beep(dlgparam *dp) { } -void dlg_error_msg(dlgparam *dp, const char *msg) { } -void dlg_end(dlgparam *dp, int value) { } -void dlg_coloursel_start(union control *ctrl, dlgparam *dp, - int r, int g, int b) { } -bool dlg_coloursel_results(union control *ctrl, dlgparam *dp, - int *r, int *g, int *b) { return false; } -void dlg_refresh(union control *ctrl, dlgparam *dp) { } -bool dlg_is_visible(union control *ctrl, dlgparam *dp) { return false; } - -const int ngsslibs = 0; -const char *const gsslibnames[0] = { }; -const struct keyvalwhere gsslibkeywords[0] = { }; - -/* - * Default settings that are specific to Unix plink. - */ -char *platform_default_s(const char *name) -{ - if (!strcmp(name, "TermType")) - return dupstr(getenv("TERM")); - if (!strcmp(name, "SerialLine")) - return dupstr("/dev/ttyS0"); - return NULL; -} - -bool platform_default_b(const char *name, bool def) -{ - return def; -} - -int platform_default_i(const char *name, int def) -{ - return def; -} - -FontSpec *platform_default_fontspec(const char *name) -{ - return fontspec_new(""); -} - -Filename *platform_default_filename(const char *name) -{ - if (!strcmp(name, "LogFileName")) - return filename_from_str("putty.log"); - else - return filename_from_str(""); -} - -char *x_get_default(const char *key) -{ - return NULL; /* this is a stub */ -} diff --git a/test/fuzzterm.c b/test/fuzzterm.c new file mode 100644 index 00000000..f1477054 --- /dev/null +++ b/test/fuzzterm.c @@ -0,0 +1,216 @@ +#include +#include +#include + +#include "putty.h" +#include "dialog.h" +#include "terminal.h" + +static const TermWinVtable fuzz_termwin_vt; + +int main(int argc, char **argv) +{ + char blk[512]; + size_t len; + Terminal *term; + Conf *conf; + struct unicode_data ucsdata; + TermWin termwin; + + termwin.vt = &fuzz_termwin_vt; + + conf = conf_new(); + do_defaults(NULL, conf); + init_ucs(&ucsdata, conf_get_str(conf, CONF_line_codepage), + conf_get_bool(conf, CONF_utf8_override), + CS_NONE, conf_get_int(conf, CONF_vtmode)); + + term = term_init(conf, &ucsdata, &termwin); + term_size(term, 24, 80, 10000); + term->ldisc = NULL; + /* Tell american fuzzy lop that this is a good place to fork. */ +#ifdef __AFL_HAVE_MANUAL_CONTROL + __AFL_INIT(); +#endif + while (!feof(stdin)) { + len = fread(blk, 1, sizeof(blk), stdin); + term_data(term, blk, len); + } + term_update(term); + return 0; +} + +/* functions required by terminal.c */ +static bool fuzz_setup_draw_ctx(TermWin *tw) { return true; } +static void fuzz_draw_text( + TermWin *tw, int x, int y, wchar_t *text, int len, + unsigned long attr, int lattr, truecolour tc) +{ + int i; + + printf("TEXT[attr=%08lx,lattr=%02x]@(%d,%d):", attr, lattr, x, y); + for (i = 0; i < len; i++) { + printf(" %x", (unsigned)text[i]); + } + printf("\n"); +} +static void fuzz_draw_cursor( + TermWin *tw, int x, int y, wchar_t *text, int len, + unsigned long attr, int lattr, truecolour tc) +{ + int i; + + printf("CURS[attr=%08lx,lattr=%02x]@(%d,%d):", attr, lattr, x, y); + for (i = 0; i < len; i++) { + printf(" %x", (unsigned)text[i]); + } + printf("\n"); +} +static void fuzz_draw_trust_sigil(TermWin *tw, int x, int y) +{ + printf("TRUST@(%d,%d)\n", x, y); +} +static int fuzz_char_width(TermWin *tw, int uc) { return 1; } +static void fuzz_free_draw_ctx(TermWin *tw) {} +static void fuzz_set_cursor_pos(TermWin *tw, int x, int y) {} +static void fuzz_set_raw_mouse_mode(TermWin *tw, bool enable) {} +static void fuzz_set_scrollbar(TermWin *tw, int total, int start, int page) {} +static void fuzz_bell(TermWin *tw, int mode) {} +static void fuzz_clip_write( + TermWin *tw, int clipboard, wchar_t *text, int *attrs, + truecolour *colours, int len, bool must_deselect) {} +static void fuzz_clip_request_paste(TermWin *tw, int clipboard) {} +static void fuzz_refresh(TermWin *tw) {} +static void fuzz_request_resize(TermWin *tw, int w, int h) {} +static void fuzz_set_title(TermWin *tw, const char *title, int codepage) {} +static void fuzz_set_icon_title(TermWin *tw, const char *icontitle, int cp) {} +static void fuzz_set_minimised(TermWin *tw, bool minimised) {} +static void fuzz_set_maximised(TermWin *tw, bool maximised) {} +static void fuzz_move(TermWin *tw, int x, int y) {} +static void fuzz_set_zorder(TermWin *tw, bool top) {} +static void fuzz_palette_set(TermWin *tw, unsigned start, unsigned ncolours, + const rgb *colours) {} +static void fuzz_palette_get_overrides(TermWin *tw, Terminal *term) {} + +static const TermWinVtable fuzz_termwin_vt = { + .setup_draw_ctx = fuzz_setup_draw_ctx, + .draw_text = fuzz_draw_text, + .draw_cursor = fuzz_draw_cursor, + .draw_trust_sigil = fuzz_draw_trust_sigil, + .char_width = fuzz_char_width, + .free_draw_ctx = fuzz_free_draw_ctx, + .set_cursor_pos = fuzz_set_cursor_pos, + .set_raw_mouse_mode = fuzz_set_raw_mouse_mode, + .set_scrollbar = fuzz_set_scrollbar, + .bell = fuzz_bell, + .clip_write = fuzz_clip_write, + .clip_request_paste = fuzz_clip_request_paste, + .refresh = fuzz_refresh, + .request_resize = fuzz_request_resize, + .set_title = fuzz_set_title, + .set_icon_title = fuzz_set_icon_title, + .set_minimised = fuzz_set_minimised, + .set_maximised = fuzz_set_maximised, + .move = fuzz_move, + .set_zorder = fuzz_set_zorder, + .palette_set = fuzz_palette_set, + .palette_get_overrides = fuzz_palette_get_overrides, +}; + +void ldisc_send(Ldisc *ldisc, const void *buf, int len, bool interactive) {} +void ldisc_echoedit_update(Ldisc *ldisc) {} +bool ldisc_has_input_buffered(Ldisc *ldisc) { return false; } +LdiscInputToken ldisc_get_input_token(Ldisc *ldisc) +{ unreachable("This fake ldisc never has any buffered input"); } +void ldisc_enable_prompt_callback(Ldisc *ldisc, prompts_t *p) +{ unreachable("This fake ldisc should never be used for user/pass prompts"); } +void modalfatalbox(const char *fmt, ...) { exit(0); } +void nonfatal(const char *fmt, ...) { } + +/* needed by timing.c */ +void timer_change_notify(unsigned long next) { } + +/* needed by config.c and sercfg.c */ + +void dlg_radiobutton_set(union control *ctrl, dlgparam *dp, int whichbutton) { } +int dlg_radiobutton_get(union control *ctrl, dlgparam *dp) { return 0; } +void dlg_checkbox_set(union control *ctrl, dlgparam *dp, bool checked) { } +bool dlg_checkbox_get(union control *ctrl, dlgparam *dp) { return false; } +void dlg_editbox_set(union control *ctrl, dlgparam *dp, char const *text) { } +char *dlg_editbox_get(union control *ctrl, dlgparam *dp) +{ return dupstr("moo"); } +void dlg_listbox_clear(union control *ctrl, dlgparam *dp) { } +void dlg_listbox_del(union control *ctrl, dlgparam *dp, int index) { } +void dlg_listbox_add(union control *ctrl, dlgparam *dp, char const *text) { } +void dlg_listbox_addwithid(union control *ctrl, dlgparam *dp, + char const *text, int id) { } +int dlg_listbox_getid(union control *ctrl, dlgparam *dp, int index) +{ return 0; } +int dlg_listbox_index(union control *ctrl, dlgparam *dp) { return -1; } +bool dlg_listbox_issel(union control *ctrl, dlgparam *dp, int index) +{ return false; } +void dlg_listbox_select(union control *ctrl, dlgparam *dp, int index) { } +void dlg_text_set(union control *ctrl, dlgparam *dp, char const *text) { } +void dlg_filesel_set(union control *ctrl, dlgparam *dp, Filename *fn) { } +Filename *dlg_filesel_get(union control *ctrl, dlgparam *dp) { return NULL; } +void dlg_fontsel_set(union control *ctrl, dlgparam *dp, FontSpec *fn) { } +FontSpec *dlg_fontsel_get(union control *ctrl, dlgparam *dp) { return NULL; } +void dlg_update_start(union control *ctrl, dlgparam *dp) { } +void dlg_update_done(union control *ctrl, dlgparam *dp) { } +void dlg_set_focus(union control *ctrl, dlgparam *dp) { } +void dlg_label_change(union control *ctrl, dlgparam *dp, char const *text) { } +union control *dlg_last_focused(union control *ctrl, dlgparam *dp) +{ return NULL; } +void dlg_beep(dlgparam *dp) { } +void dlg_error_msg(dlgparam *dp, const char *msg) { } +void dlg_end(dlgparam *dp, int value) { } +void dlg_coloursel_start(union control *ctrl, dlgparam *dp, + int r, int g, int b) { } +bool dlg_coloursel_results(union control *ctrl, dlgparam *dp, + int *r, int *g, int *b) { return false; } +void dlg_refresh(union control *ctrl, dlgparam *dp) { } +bool dlg_is_visible(union control *ctrl, dlgparam *dp) { return false; } + +const int ngsslibs = 0; +const char *const gsslibnames[0] = { }; +const struct keyvalwhere gsslibkeywords[0] = { }; + +/* + * Default settings that are specific to Unix plink. + */ +char *platform_default_s(const char *name) +{ + if (!strcmp(name, "TermType")) + return dupstr(getenv("TERM")); + if (!strcmp(name, "SerialLine")) + return dupstr("/dev/ttyS0"); + return NULL; +} + +bool platform_default_b(const char *name, bool def) +{ + return def; +} + +int platform_default_i(const char *name, int def) +{ + return def; +} + +FontSpec *platform_default_fontspec(const char *name) +{ + return fontspec_new(""); +} + +Filename *platform_default_filename(const char *name) +{ + if (!strcmp(name, "LogFileName")) + return filename_from_str("putty.log"); + else + return filename_from_str(""); +} + +char *x_get_default(const char *key) +{ + return NULL; /* this is a stub */ +} diff --git a/unix/CMakeLists.txt b/unix/CMakeLists.txt index 51d03f0f..0490cf5c 100644 --- a/unix/CMakeLists.txt +++ b/unix/CMakeLists.txt @@ -48,7 +48,7 @@ add_sources_from_current_dir(agent agent-client.c) add_executable(fuzzterm - ${CMAKE_SOURCE_DIR}/fuzzterm.c + ${CMAKE_SOURCE_DIR}/test/fuzzterm.c ${CMAKE_SOURCE_DIR}/logging.c ${CMAKE_SOURCE_DIR}/stubs/noprint.c unicode.c -- cgit v1.2.3 From 46fbe375bf1e019154314d13bd7b38334783240d Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 28 Nov 2021 12:01:19 +0000 Subject: Switch to a fixed-window strategy for monty_pow. Instead of the basic square-and-multiply strategy which requires a square and a multiply per exponent bit (i.e. two modular multiplications per bit in total), we instead reduce to a square per exponent bit and an extra multiply only every 5 bits, because the value we're multiplying in is derived from 5 of the exponent bits at once via a table lookup. To avoid the obvious side-channel leakage of a literal table lookup, we read the whole table every time, mp_selecting the right value into the multiplication input. This isn't as slow as it sounds when the alternative is four entire modular multiplications! In my testing, this commit speeds up large modpows by a factor of just over 1.5, and it still gets a clean pass from 'testsc'. --- crypto/mpint.c | 119 +++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 95 insertions(+), 24 deletions(-) diff --git a/crypto/mpint.c b/crypto/mpint.c index f015bd09..fba9431d 100644 --- a/crypto/mpint.c +++ b/crypto/mpint.c @@ -1539,41 +1539,112 @@ mp_int *monty_export(MontyContext *mc, mp_int *x) return toret; } -static void monty_reduce(MontyContext *mc, mp_int *x) -{ - mp_int reduced = monty_reduce_internal(mc, x, *mc->scratch); - mp_copy_into(x, &reduced); - mp_clear(mc->scratch); -} - +#define MODPOW_LOG2_WINDOW_SIZE 5 +#define MODPOW_WINDOW_SIZE (1 << MODPOW_LOG2_WINDOW_SIZE) mp_int *monty_pow(MontyContext *mc, mp_int *base, mp_int *exponent) { - /* square builds up powers of the form base^{2^i}. */ - mp_int *square = mp_copy(base); - size_t i = 0; + /* + * Modular exponentation is done from the top down, using a + * fixed-window technique. + * + * We have a table storing every power of the base from base^0 up + * to base^{w-1}, where w is a small power of 2, say 2^k. (k is + * defined above as MODPOW_LOG2_WINDOW_SIZE, and w = 2^k is + * defined as MODPOW_WINDOW_SIZE.) + * + * We break the exponent up into k-bit chunks, from the bottom up, + * that is + * + * exponent = c_0 + 2^k c_1 + 2^{2k} c_2 + ... + 2^{nk} c_n + * + * and we compute base^exponent by computing in turn + * + * base^{c_n} + * base^{2^k c_n + c_{n-1}} + * base^{2^{2k} c_n + 2^k c_{n-1} + c_{n-2}} + * ... + * + * where each line is obtained by raising the previous line to the + * power 2^k (i.e. squaring it k times) and then multiplying in + * a value base^{c_i}, which we can look up in our table. + * + * Side-channel considerations: the exponent is secret, so + * actually doing a single table lookup by using a chunk of + * exponent bits as an array index would be an obvious leak of + * secret information into the cache. So instead, in each + * iteration, we read _all_ the table entries, and do a sequence + * of mp_select operations to leave just the one we wanted in the + * variable that will go into the multiplication. In other + * contexts (like software AES) that technique is so prohibitively + * slow that it makes you choose a strategy that doesn't use table + * lookups at all (we do bitslicing in preference); but here, this + * iteration through 2^k table elements is replacing k-1 bignum + * _multiplications_ that you'd have to use instead if you did + * simple square-and-multiply, and that makes it still a win. + */ + + /* Table that holds base^0, ..., base^{w-1} */ + mp_int *table[MODPOW_WINDOW_SIZE]; + table[0] = mp_copy(monty_identity(mc)); + for (size_t i = 1; i < MODPOW_WINDOW_SIZE; i++) + table[i] = monty_mul(mc, table[i-1], base); - /* out accumulates the output value. Starts at 1 (in Montgomery - * representation) and we multiply in each base^{2^i}. */ - mp_int *out = mp_copy(mc->powers_of_r_mod_m[0]); + /* out accumulates the output value */ + mp_int *out = mp_make_sized(mc->rw); + mp_copy_into(out, monty_identity(mc)); - /* tmp holds each product we compute and reduce. */ - mp_int *tmp = mp_make_sized(mc->rw * 2); + /* table_entry will hold each value we get out of the table */ + mp_int *table_entry = mp_make_sized(mc->rw); + + /* Bit index of the chunk of bits we're working on. Start with the + * highest multiple of k strictly less than the size of our + * bignum, i.e. the highest-index chunk of bits that might + * conceivably contain any nonzero bit. */ + size_t i = (exponent->nw * BIGNUM_INT_BITS) - 1; + i -= i % MODPOW_LOG2_WINDOW_SIZE; + + bool first_iteration = true; while (true) { - mp_mul_into(tmp, out, square); - monty_reduce(mc, tmp); - mp_select_into(out, out, tmp, mp_get_bit(exponent, i)); + /* Construct the table index */ + unsigned table_index = 0; + for (size_t j = 0; j < MODPOW_LOG2_WINDOW_SIZE; j++) + table_index |= mp_get_bit(exponent, i+j) << j; + + /* Iterate through the table to do a side-channel-safe lookup, + * ending up with table_entry = table[table_index] */ + mp_copy_into(table_entry, table[0]); + for (size_t j = 1; j < MODPOW_WINDOW_SIZE; j++) { + unsigned not_this_one = + ((table_index ^ j) + MODPOW_WINDOW_SIZE - 1) + >> MODPOW_LOG2_WINDOW_SIZE; + mp_select_into(table_entry, table[j], table_entry, not_this_one); + } - if (++i >= exponent->nw * BIGNUM_INT_BITS) + if (!first_iteration) { + /* Multiply into the output */ + monty_mul_into(mc, out, out, table_entry); + } else { + /* On the first iteration, we can save one multiplication + * by just copying */ + mp_copy_into(out, table_entry); + first_iteration = false; + } + + /* If that was the bottommost chunk of bits, we're done */ + if (i == 0) break; - mp_mul_into(tmp, square, square); - monty_reduce(mc, tmp); - mp_copy_into(square, tmp); + /* Otherwise, square k times and go round again. */ + for (size_t j = 0; j < MODPOW_LOG2_WINDOW_SIZE; j++) + monty_mul_into(mc, out, out, out); + + i-= MODPOW_LOG2_WINDOW_SIZE; } - mp_free(square); - mp_free(tmp); + for (size_t i = 0; i < MODPOW_WINDOW_SIZE; i++) + mp_free(table[i]); + mp_free(table_entry); mp_clear(mc->scratch); return out; } -- cgit v1.2.3 From cd60a602f541ac44aecd207f4e055279b86d1898 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 28 Nov 2021 12:10:42 +0000 Subject: Stop using short exponents for Diffie-Hellman. I recently encountered a paper [1] which catalogues all kinds of things that can go wrong when one party in a discrete-log system invents a prime and the other party chooses an exponent. In particular, some choices of prime make it reasonable to use a short exponent to save time, but others make that strategy very bad. That paper is about the ElGamal encryption scheme used in OpenPGP, which is basically integer Diffie-Hellman with one side's key being persistent: a shared-secret integer is derived exactly as in DH, and then it's used to communicate a message integer by simply multiplying the shared secret by the message, mod p. I don't _know_ that any problem of this kind arises in the SSH usage of Diffie-Hellman: the standard integer DH groups in SSH are safe primes, and as far as I know, the usual generation of prime moduli for DH group exchange also picks safe primes. So the short exponents PuTTY has been using _should_ be OK. However, the range of imaginative other possibilities shown in that paper make me nervous, even so! So I think I'm going to retire the short exponent strategy, on general principles of overcaution. This slows down 4096-bit integer DH by about a factor of 3-4 (which would be worse if it weren't for the modpow speedup in the previous commit). I think that's OK, because, firstly, computers are a lot faster these days than when I originally chose to use short exponents, and secondly, more and more implementations are now switching to elliptic-curve DH, which is unaffected by this change (and with which we've always been using maximum-length exponents). [1] On the (in)security of ElGamal in OpenPGP. Luca De Feo, Bertram Poettering, Alessandro Sorniotti. https://eprint.iacr.org/2021/923 --- crypto/diffie-hellman.c | 19 +------------------ ssh.h | 2 +- ssh/kex2-client.c | 4 ++-- ssh/kex2-server.c | 2 +- test/testcrypt-func.h | 2 +- 5 files changed, 6 insertions(+), 23 deletions(-) diff --git a/crypto/diffie-hellman.c b/crypto/diffie-hellman.c index a1ed7cb4..914167bb 100644 --- a/crypto/diffie-hellman.c +++ b/crypto/diffie-hellman.c @@ -200,19 +200,8 @@ void dh_cleanup(dh_ctx *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. */ -mp_int *dh_create_e(dh_ctx *ctx, int nbits) +mp_int *dh_create_e(dh_ctx *ctx) { /* * Lower limit is just 2. @@ -224,12 +213,6 @@ mp_int *dh_create_e(dh_ctx *ctx, int nbits) */ mp_int *hi = mp_copy(ctx->q); mp_sub_integer_into(hi, hi, 1); - if (nbits) { - mp_int *pow2 = mp_power_2(nbits+1); - mp_min_into(pow2, pow2, hi); - mp_free(hi); - hi = pow2; - } /* * Make a random number in that range. diff --git a/ssh.h b/ssh.h index 7049f2af..77cddf99 100644 --- a/ssh.h +++ b/ssh.h @@ -1223,7 +1223,7 @@ dh_ctx *dh_setup_group(const ssh_kex *kex); dh_ctx *dh_setup_gex(mp_int *pval, mp_int *gval); int dh_modulus_bit_size(const dh_ctx *ctx); void dh_cleanup(dh_ctx *); -mp_int *dh_create_e(dh_ctx *, int nbits); +mp_int *dh_create_e(dh_ctx *); const char *dh_validate_f(dh_ctx *, mp_int *f); mp_int *dh_find_K(dh_ctx *, mp_int *f); diff --git a/ssh/kex2-client.c b/ssh/kex2-client.c index 8a3a290a..0f81ef15 100644 --- a/ssh/kex2-client.c +++ b/ssh/kex2-client.c @@ -118,7 +118,7 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) * Now generate and send e for Diffie-Hellman. */ seat_set_busy_status(s->ppl.seat, BUSY_CPU); - s->e = dh_create_e(s->dh_ctx, s->nbits * 2); + s->e = dh_create_e(s->dh_ctx); pktout = ssh_bpp_new_pktout(s->ppl.bpp, s->kex_init_value); put_mp_ssh2(pktout, s->e); pq_push(s->ppl.out_pq, pktout); @@ -322,7 +322,7 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) "exchange with hash %s", ssh_hash_alg(s->exhash)->text_name); /* Now generate e for Diffie-Hellman. */ seat_set_busy_status(s->ppl.seat, BUSY_CPU); - s->e = dh_create_e(s->dh_ctx, s->nbits * 2); + s->e = dh_create_e(s->dh_ctx); if (s->shgss->lib->gsslogmsg) ppl_logevent("%s", s->shgss->lib->gsslogmsg); diff --git a/ssh/kex2-server.c b/ssh/kex2-server.c index 56bdc3c5..3c017077 100644 --- a/ssh/kex2-server.c +++ b/ssh/kex2-server.c @@ -128,7 +128,7 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) /* * Generate e for Diffie-Hellman. */ - s->e = dh_create_e(s->dh_ctx, s->nbits * 2); + s->e = dh_create_e(s->dh_ctx); /* * Wait to receive f. diff --git a/test/testcrypt-func.h b/test/testcrypt-func.h index 9158e800..b8e53a96 100644 --- a/test/testcrypt-func.h +++ b/test/testcrypt-func.h @@ -339,7 +339,7 @@ FUNC_WRAPPED(val_string, ssh_cipher_decrypt_length, ARG(val_cipher, c), FUNC(val_dh, dh_setup_group, ARG(dh_group, group)) FUNC(val_dh, dh_setup_gex, ARG(val_mpint, p), ARG(val_mpint, g)) FUNC(uint, dh_modulus_bit_size, ARG(val_dh, ctx)) -FUNC(val_mpint, dh_create_e, ARG(val_dh, ctx), ARG(uint, nbits)) +FUNC(val_mpint, dh_create_e, ARG(val_dh, ctx)) FUNC_WRAPPED(boolean, dh_validate_f, ARG(val_dh, ctx), ARG(val_mpint, f)) FUNC(val_mpint, dh_find_K, ARG(val_dh, ctx), ARG(val_mpint, f)) -- cgit v1.2.3 From 1bbcde70ba11dd205cc9324a6e3aad169b75a6bf Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Tue, 30 Nov 2021 18:42:21 +0000 Subject: Remove a redundant #include. ptrlen.c doesn't have anything to do with SSH, so it doesn't need ssh.h. --- utils/ptrlen.c | 1 - 1 file changed, 1 deletion(-) diff --git a/utils/ptrlen.c b/utils/ptrlen.c index f2297eb5..7d4e12ec 100644 --- a/utils/ptrlen.c +++ b/utils/ptrlen.c @@ -4,7 +4,6 @@ #include "defs.h" #include "misc.h" -#include "ssh.h" bool ptrlen_eq_string(ptrlen pl, const char *str) { -- cgit v1.2.3 From 7ab9a3f36d0736de43bbd3e8bb3a132816b5055c Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Tue, 30 Nov 2021 18:48:06 +0000 Subject: Remove a redundant file in utils. At some point while setting up the utils subdirectory, I apparently only got half way through renaming miscucs.c to dup_mb_to_wc.c: I created the new copy of the file, but I didn't delete the old one, I didn't mention it in utils/CMakeLists.txt, and I didn't change the comment at the top. Now done all three, so we now have just one copy of this utility module. --- utils/CMakeLists.txt | 2 +- utils/dup_mb_to_wc.c | 7 ++++--- utils/miscucs.c | 28 ---------------------------- 3 files changed, 5 insertions(+), 32 deletions(-) delete mode 100644 utils/miscucs.c diff --git a/utils/CMakeLists.txt b/utils/CMakeLists.txt index 73e120db..86f48952 100644 --- a/utils/CMakeLists.txt +++ b/utils/CMakeLists.txt @@ -16,6 +16,7 @@ add_sources_from_current_dir(utils dupcat.c dupprintf.c dupstr.c + dup_mb_to_wc.c encode_utf8.c encode_wide_string_as_utf8.c fgetline.c @@ -28,7 +29,6 @@ add_sources_from_current_dir(utils marshal.c memory.c memxor.c - miscucs.c null_lp.c nullseat.c nullstrcmp.c diff --git a/utils/dup_mb_to_wc.c b/utils/dup_mb_to_wc.c index 7785f9b6..c3f17aba 100644 --- a/utils/dup_mb_to_wc.c +++ b/utils/dup_mb_to_wc.c @@ -1,7 +1,8 @@ /* - * Centralised Unicode-related helper functions, separate from misc.c - * so that they can be omitted from tools that aren't including - * Unicode handling. + * dup_mb_to_wc: memory-allocating wrapper on mb_to_wc. + * + * Also dup_mb_to_wc_c: same but you already know the length of the + * string. */ #include "putty.h" diff --git a/utils/miscucs.c b/utils/miscucs.c deleted file mode 100644 index 7785f9b6..00000000 --- a/utils/miscucs.c +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Centralised Unicode-related helper functions, separate from misc.c - * so that they can be omitted from tools that aren't including - * Unicode handling. - */ - -#include "putty.h" -#include "misc.h" - -wchar_t *dup_mb_to_wc_c(int codepage, int flags, const char *string, int len) -{ - int mult; - for (mult = 1 ;; mult++) { - wchar_t *ret = snewn(mult*len + 2, wchar_t); - int outlen; - outlen = mb_to_wc(codepage, flags, string, len, ret, mult*len + 1); - if (outlen < mult*len+1) { - ret[outlen] = L'\0'; - return ret; - } - sfree(ret); - } -} - -wchar_t *dup_mb_to_wc(int codepage, int flags, const char *string) -{ - return dup_mb_to_wc_c(codepage, flags, string, strlen(string)); -} -- cgit v1.2.3 From a759e303b03dee07467c042acc32a79728e1ec59 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 12 Dec 2021 10:49:31 +0000 Subject: PSCP: fix filename in 'compound pathname' error. In commit 2675f9578daf662, when I added 'stripctrl' sanitisation to many uses of filenames throughout the file transfer tools, I made a copy-and-paste error in the message 'remote host sent a compound pathname X / renaming local file to Y' in which both X and Y were the same pathname. Oops. Fixed the other one. --- pscp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pscp.c b/pscp.c index 192341c3..d18b0ecd 100644 --- a/pscp.c +++ b/pscp.c @@ -1813,7 +1813,7 @@ static void sink(const char *targ, const char *src) striptarget = stripslashes(act.name, true); if (striptarget != act.name) { with_stripctrl(sanname, act.name) { - with_stripctrl(santarg, act.name) { + with_stripctrl(santarg, striptarget) { tell_user(stderr, "warning: remote host sent a" " compound pathname '%s'", sanname); tell_user(stderr, " renaming local" -- cgit v1.2.3 From 0e630bc4f16bc6019cdb0135cd82c8832502d8ef Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 18 Dec 2021 11:43:57 +0000 Subject: Fix pre-GTK3 build failures in puttyapp / ptermapp. These alternate frontends using the GtkApplication class don't work before GTK3, because the GtkApplication class didn't exist. In the old mkfiles.pl system, the simplest way to prevent a build failure was to just compile them anyway but make them reduce to a stub main(). But now, with the new library-based code organisation, library search order issues mean that these applications won't build at all. Happily, with cmake, it's also easy to simply omit these binaries from the build completely depending on our GTK version. --- cmake/gtk.cmake | 3 +++ unix/CMakeLists.txt | 44 ++++++++++++++++++++++++-------------------- unix/main-gtk-application.c | 19 ++----------------- 3 files changed, 29 insertions(+), 37 deletions(-) diff --git a/cmake/gtk.cmake b/cmake/gtk.cmake index 145f214b..85ecb7de 100644 --- a/cmake/gtk.cmake +++ b/cmake/gtk.cmake @@ -12,6 +12,9 @@ macro(try_pkg_config_gtk VER PACKAGENAME) (PUTTY_GTK_VERSION STREQUAL ANY OR PUTTY_GTK_VERSION STREQUAL ${VER})) find_package(PkgConfig) pkg_check_modules(GTK ${PACKAGENAME}) + if(GTK_FOUND) + set(GTK_VERSION ${VER}) + endif() endif() endmacro() try_pkg_config_gtk(3 gtk+-3.0) diff --git a/unix/CMakeLists.txt b/unix/CMakeLists.txt index 0490cf5c..9bdd2522 100644 --- a/unix/CMakeLists.txt +++ b/unix/CMakeLists.txt @@ -155,17 +155,19 @@ if(GTK_FOUND) ${GTK_LIBRARIES} ${X11_LIBRARIES}) installed_program(pterm) - add_executable(ptermapp - pterm.c - main-gtk-application.c - ${CMAKE_SOURCE_DIR}/stubs/nocmdline.c - ${CMAKE_SOURCE_DIR}/stubs/nogss.c - ${CMAKE_SOURCE_DIR}/proxy/nosshproxy.c - pty.c) - be_list(ptermapp pterm) - target_link_libraries(ptermapp - guiterminal eventloop settings charset utils - ${GTK_LIBRARIES} ${X11_LIBRARIES}) + if(GTK_VERSION GREATER_EQUAL 3) + add_executable(ptermapp + pterm.c + main-gtk-application.c + ${CMAKE_SOURCE_DIR}/stubs/nocmdline.c + ${CMAKE_SOURCE_DIR}/stubs/nogss.c + ${CMAKE_SOURCE_DIR}/proxy/nosshproxy.c + pty.c) + be_list(ptermapp pterm) + target_link_libraries(ptermapp + guiterminal eventloop settings charset utils + ${GTK_LIBRARIES} ${X11_LIBRARIES}) + endif() add_executable(putty putty.c @@ -179,15 +181,17 @@ if(GTK_FOUND) PROPERTIES LINK_INTERFACE_MULTIPLICITY 2) installed_program(putty) - add_executable(puttyapp - putty.c - main-gtk-application.c - ${CMAKE_SOURCE_DIR}/stubs/nocmdline.c) - be_list(puttyapp PuTTY SSH SERIAL OTHERBACKENDS) - target_link_libraries(puttyapp - guiterminal eventloop sshclient otherbackends settings - network crypto charset utils - ${GTK_LIBRARIES} ${X11_LIBRARIES}) + if(GTK_VERSION GREATER_EQUAL 3) + add_executable(puttyapp + putty.c + main-gtk-application.c + ${CMAKE_SOURCE_DIR}/stubs/nocmdline.c) + be_list(puttyapp PuTTY SSH SERIAL OTHERBACKENDS) + target_link_libraries(puttyapp + guiterminal eventloop sshclient otherbackends settings + network crypto charset utils + ${GTK_LIBRARIES} ${X11_LIBRARIES}) + endif() add_executable(puttytel putty.c diff --git a/unix/main-gtk-application.c b/unix/main-gtk-application.c index fb924009..b6c2807b 100644 --- a/unix/main-gtk-application.c +++ b/unix/main-gtk-application.c @@ -84,21 +84,8 @@ char *x_get_default(const char *key) { return NULL; } const bool buildinfo_gtk_relevant = true; #if !GTK_CHECK_VERSION(3,0,0) -/* This front end only works in GTK 3. If that's not what we've got, - * it's easier to just turn this program into a trivial stub by ifdef - * in the source than it is to remove it in the makefile edifice. */ -int main(int argc, char **argv) -{ - fprintf(stderr, "GtkApplication frontend doesn't work pre-GTK3\n"); - return 1; -} -GtkWidget *make_gtk_toplevel_window(GtkFrontend *frontend) { return NULL; } -void launch_duplicate_session(Conf *conf) {} -void launch_new_session(void) {} -void launch_saved_session(const char *str) {} -void session_window_closed(void) {} -void window_setup_error(const char *errmsg) {} -#else /* GTK_CHECK_VERSION(3,0,0) */ +#error This front end only works in GTK 3 +#endif static void startup(GApplication *app, gpointer user_data) { @@ -336,5 +323,3 @@ int main(int argc, char **argv) return status; } - -#endif /* GTK_CHECK_VERSION(3,0,0) */ -- cgit v1.2.3 From adf8fc1ab092cf0d1c4ff4f037b6fe9eb1a409d1 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 18 Dec 2021 14:49:11 +0000 Subject: GTK: fix return type of window-state-event handler. The event should return a 'gboolean', indicating whether the event needs propagating any further. We were returning void, which meant that the default handling might be accidentally suppressed. On Wayland, this had the particularly nasty effect that window redraws would stop completely if you maximised the terminal window. (By trial and error I found that this stopped happening if I removed GDK_HINT_RESIZE_INC from the geometry hints, from which I guess that the default window-state-event handler is doing something important relating to that hint, or would have been if we hadn't accidentally suppressed it. But the bug is clearly here.) --- unix/window.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/unix/window.c b/unix/window.c index c9d635ab..da16703e 100644 --- a/unix/window.c +++ b/unix/window.c @@ -651,12 +651,13 @@ gint delete_window(GtkWidget *widget, GdkEvent *event, GtkFrontend *inst) } #if GTK_CHECK_VERSION(2,0,0) -static void window_state_event(GtkWidget *widget, GdkEventWindowState *event, - gpointer user_data) +static gboolean window_state_event( + GtkWidget *widget, GdkEventWindowState *event, gpointer user_data) { GtkFrontend *inst = (GtkFrontend *)user_data; term_notify_minimised( inst->term, event->new_window_state & GDK_WINDOW_STATE_ICONIFIED); + return false; } #endif -- cgit v1.2.3 From 8c63125f7ad61a42d17decc249384307e2edb98a Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 18 Dec 2021 14:14:16 +0000 Subject: GTK: avoid trying to resize a maximised window. This is another thing that seems harmless on X11 but causes window redraws to semipermanently stop happening on Wayland: if we try to gtk_window_resize() a window that is maximised at the time, then something mysterious goes wrong and we stop ever getting "draw" events. --- unix/window.c | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/unix/window.c b/unix/window.c index da16703e..fef7687a 100644 --- a/unix/window.c +++ b/unix/window.c @@ -2454,6 +2454,51 @@ static void gtkwin_request_resize(TermWin *tw, int w, int h) { GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); +#if GTK_CHECK_VERSION(2,0,0) + /* + * Initial check: don't even try to resize a window if it's in one + * of the states that make that impossible. This includes being + * maximised; being full-screen (if we ever implement that); or + * being in various tiled states. + * + * On X11, the effect of trying to resize the window when it can't + * be resized should be that the window manager sends us a + * synthetic ConfigureNotify event restating our existing size + * (ICCCM section 4.1.5 "Configuring the Window"). That's awkward + * to deal with, but not impossible; so for X11 alone, we might + * not bother with this check. + * + * (In any case, X11 has other reasons why a window resize might + * be rejected, which this enumeration can't be aware of in any + * case. For example, if your window manager is a tiling one, then + * all your windows are _de facto_ tiled, but not _de jure_ in a + * way that GDK will know about. So we have to handle those + * synthetic ConfigureNotifies in any case.) + * + * On Wayland, as of GTK 3.24.20, the effects are much worse: it + * looks to me as if GTK stops ever sending us "draw" events, or + * even a size_allocate, so the display locks up completely until + * you toggle the maximised state of the window by some other + * means. So it's worth checking! + */ + GdkWindow *gdkwin = gtk_widget_get_window(inst->window); + if (gdkwin) { + GdkWindowState state = gdk_window_get_state(gdkwin); + if (state & (GDK_WINDOW_STATE_MAXIMIZED | + GDK_WINDOW_STATE_FULLSCREEN | +#if GTK_CHECK_VERSION(3,0,0) + GDK_WINDOW_STATE_TILED | + GDK_WINDOW_STATE_TOP_TILED | + GDK_WINDOW_STATE_RIGHT_TILED | + GDK_WINDOW_STATE_BOTTOM_TILED | + GDK_WINDOW_STATE_LEFT_TILED | +#endif + 0)) { + return; + } + } +#endif + #if !GTK_CHECK_VERSION(3,0,0) int large_x, large_y; -- cgit v1.2.3 From 8f365e39f3c1662884361900e2c017d9f2d4875e Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 12 Dec 2021 14:39:50 +0000 Subject: Centralise drag-select check into term_out(). This tiny refactoring replaces three identical checks at call sites, not all as well commented as each other, with a check in just one place with the best of the three comments. --- terminal/terminal.c | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/terminal/terminal.c b/terminal/terminal.c index b132b4e0..19816282 100644 --- a/terminal/terminal.c +++ b/terminal/terminal.c @@ -3622,6 +3622,13 @@ static void term_out(Terminal *term) unsigned char localbuf[256], *chars; size_t nchars = 0; + /* + * 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) + return; + unget = -1; chars = NULL; /* placate compiler warnings */ @@ -7236,8 +7243,7 @@ void term_mouse(Terminal *term, Mouse_Button braw, Mouse_Button bcooked, * should make sure to write any pending output if one has just * finished. */ - if (term->selstate != DRAGGING) - term_out(term); + term_out(term); term_schedule_update(term); } @@ -7550,8 +7556,7 @@ void term_lost_clipboard_ownership(Terminal *term, int clipboard) * should make sure to write any pending output if one has just * finished. */ - if (term->selstate != DRAGGING) - term_out(term); + term_out(term); } static void term_added_data(Terminal *term) @@ -7559,13 +7564,7 @@ static void term_added_data(Terminal *term) 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_out(term); term->in_term_out = false; } } -- cgit v1.2.3 From 5a54b3bf1707781cd4a46009bc44a8aad59844a9 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 13 Dec 2021 18:49:45 +0000 Subject: Factor out term_request_resize(). This tiny refactoring makes a convenient function for setting all the 'pending' flags and triggering a callback for the next window update. This saves a bit of code, but that's not really the main point (or else I'd have done the same to all the other similar things like window moves). The point is that in a future commit I'm going to want to do an extra thing on every server-controlled window resize, and this refactoring gives me a single place to put that extra action. --- terminal/terminal.c | 81 +++++++++++++++++++++++------------------------------ 1 file changed, 35 insertions(+), 46 deletions(-) diff --git a/terminal/terminal.c b/terminal/terminal.c index 19816282..248481ee 100644 --- a/terminal/terminal.c +++ b/terminal/terminal.c @@ -2972,6 +2972,17 @@ static void term_update_raw_mouse_mode(Terminal *term) term_schedule_update(term); } +static void term_request_resize(Terminal *term, int cols, int rows) +{ + if (term->cols == cols && term->rows == rows) + return; /* don't need to do anything */ + + term->win_resize_pending = true; + term->win_resize_pending_w = cols; + term->win_resize_pending_h = rows; + term_schedule_update(term); +} + /* * Toggle terminal mode `mode' to state `state'. (`query' indicates * whether the mode is a DEC private one or a normal one.) @@ -2995,12 +3006,8 @@ static void toggle_mode(Terminal *term, int mode, int query, bool state) break; case 3: /* DECCOLM: 80/132 columns */ deselect(term); - if (!term->no_remote_resize) { - term->win_resize_pending = true; - term->win_resize_pending_w = state ? 132 : 80; - term->win_resize_pending_h = term->rows; - term_schedule_update(term); - } + if (!term->no_remote_resize) + term_request_resize(term, 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; @@ -4015,12 +4022,8 @@ static void term_out(Terminal *term) if (term->ldisc) /* cause ldisc to notice changes */ ldisc_echoedit_update(term->ldisc); if (term->reset_132) { - if (!term->no_remote_resize) { - term->win_resize_pending = true; - term->win_resize_pending_w = 80; - term->win_resize_pending_h = term->rows; - term_schedule_update(term); - } + if (!term->no_remote_resize) + term_request_resize(term, 80, term->rows); term->reset_132 = false; } if (term->scroll_on_disp) @@ -4655,13 +4658,8 @@ static void term_out(Terminal *term) && (term->esc_args[0] < 1 || term->esc_args[0] >= 24)) { compatibility(VT340TEXT); - if (!term->no_remote_resize) { - term->win_resize_pending = true; - term->win_resize_pending_w = term->cols; - term->win_resize_pending_h = - def(term->esc_args[0], 24); - term_schedule_update(term); - } + if (!term->no_remote_resize) + term_request_resize(term, term->cols, 24); deselect(term); } else if (term->esc_nargs >= 1 && term->esc_args[0] >= 1 && @@ -4719,14 +4717,12 @@ static void term_out(Terminal *term) case 8: if (term->esc_nargs >= 3 && !term->no_remote_resize) { - term->win_resize_pending = true; - term->win_resize_pending_w = + term_request_resize( + term, def(term->esc_args[2], - term->conf_width); - term->win_resize_pending_h = + term->conf_width), def(term->esc_args[1], - term->conf_height); - term_schedule_update(term); + term->conf_height)); } break; case 9: @@ -4841,13 +4837,11 @@ static void term_out(Terminal *term) */ compatibility(VT420); if (term->esc_nargs == 1 && term->esc_args[0] > 0) { - if (!term->no_remote_resize) { - term->win_resize_pending = true; - term->win_resize_pending_w = term->cols; - term->win_resize_pending_h = - def(term->esc_args[0], term->conf_height); - term_schedule_update(term); - } + if (!term->no_remote_resize) + term_request_resize( + term, + term->cols, + def(term->esc_args[0], term->conf_height)); deselect(term); } break; @@ -4859,13 +4853,11 @@ static void term_out(Terminal *term) */ compatibility(VT340TEXT); if (term->esc_nargs <= 1) { - if (!term->no_remote_resize) { - term->win_resize_pending = true; - term->win_resize_pending_w = - def(term->esc_args[0], term->conf_width); - term->win_resize_pending_h = term->rows; - term_schedule_update(term); - } + if (!term->no_remote_resize) + term_request_resize( + term, + def(term->esc_args[0], term->conf_width), + term->rows); deselect(term); } break; @@ -5070,13 +5062,10 @@ static void term_out(Terminal *term) * Well we should do a soft reset at this point ... */ if (!has_compat(VT420) && has_compat(VT100)) { - if (!term->no_remote_resize) { - term->win_resize_pending = true; - term->win_resize_pending_w = - term->reset_132 ? 132 : 80; - term->win_resize_pending_h = 24; - term_schedule_update(term); - } + if (!term->no_remote_resize) + term_request_resize(term, + term->reset_132 ? 132 : 80, + 24); } #endif break; -- cgit v1.2.3 From be0cea71304c353f66e4411e449bc85367e3b702 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 18 Dec 2021 15:07:41 +0000 Subject: Stop using a local buffer in term_out. There's no actual need to copy the data from term->inbuf into a local variable inside term_out(). We can simply store a pointer and length, and use the data _in situ_ - as long as we remember how much of it we've used, and bufchain_consume() it when the routine exits. Getting rid of that awkward and size-limited local array should marginally improve performance. But also, it opens up the possibility to suddenly suspend handling of terminal data and leave everything not yet processed in the bufchain, because now we never remove anything from the bufchain until _after_ it's been processed, so there's no need to awkwardly push the unused segment of localbuf[] back on to the front of the bufchain if we need to do that. NFC, but as usual, I have a plan to use the new capability in a followup commit. --- terminal/terminal.c | 48 +++++++++++++++++++++++++++++++----------------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/terminal/terminal.c b/terminal/terminal.c index 248481ee..4f094462 100644 --- a/terminal/terminal.c +++ b/terminal/terminal.c @@ -3626,8 +3626,8 @@ static void term_out(Terminal *term) { unsigned long c; int unget; - unsigned char localbuf[256], *chars; - size_t nchars = 0; + const unsigned char *chars; + size_t nchars_got = 0, nchars_used = 0; /* * During drag-selects, we do not process terminal input, because @@ -3639,21 +3639,36 @@ static void term_out(Terminal *term) unget = -1; chars = NULL; /* placate compiler warnings */ - while (nchars > 0 || unget != -1 || bufchain_size(&term->inbuf) > 0) { - if (unget == -1) { - if (nchars == 0) { + while (nchars_got < nchars_used || + unget != -1 || + bufchain_size(&term->inbuf) > 0) { + if (unget != -1) { + /* + * Handle a character we left in 'unget' the last time + * round this loop. This happens if a UTF-8 sequence is + * aborted early, by containing fewer continuation bytes + * than its introducer expected: the non-continuation byte + * that interrupted the sequence must now be processed + * as a fresh piece of input in its own right. + */ + c = unget; + unget = -1; + } else { + if (nchars_got == nchars_used) { + /* Delete the previous chunk from the bufchain */ + bufchain_consume(&term->inbuf, nchars_used); + nchars_used = 0; + + if (bufchain_size(&term->inbuf) == 0) + break; /* no more data */ + ptrlen data = bufchain_prefix(&term->inbuf); - if (data.len > sizeof(localbuf)) - data.len = sizeof(localbuf); - memcpy(localbuf, data.ptr, data.len); - bufchain_consume(&term->inbuf, data.len); - nchars = data.len; - chars = localbuf; + chars = data.ptr; + nchars_got = data.len; assert(chars != NULL); - assert(nchars > 0); + assert(nchars_used < nchars_got); } - c = *chars++; - nchars--; + c = chars[nchars_used++]; /* * Optionally log the session traffic to a file. Useful for @@ -3661,9 +3676,6 @@ static void term_out(Terminal *term) */ if (term->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 @@ -5535,6 +5547,8 @@ static void term_out(Terminal *term) } } + bufchain_consume(&term->inbuf, nchars_used); + term_print_flush(term); if (term->logflush && term->logctx) logflush(term->logctx); -- cgit v1.2.3 From 19b12ee56c623db2331ef44859445169bdcad9e2 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 19 Dec 2021 10:21:11 +0000 Subject: Try to ensure term_size() after win_resize_request(). When the terminal asks its TermWin for a resize, the resize operation happens asynchronously (or can do), and sooner or later, the terminal will see a term_size() telling it the resize has actually taken effect. If the resize _doesn't_ take effect for any reason - e.g. because the window is maximised, or because the X11 window manager is a tiling one which will refuse requests to change the window size anyway - then the terminal never got any explicit notification of refusal to resize. Now it should, in as many cases as I can arrange. One obvious case of this is the early exit I recently added to gtkwin_request_resize() when the window is known to be in a maximised or tiled state preventing a resize: in that situation, when our own code knows we're not even attempting the resize, we also queue a toplevel callback to tell the terminal so. The more interesting case is when the request is refused for a reason GTK _didn't_ know in advance, e.g. because the user is running an X11 tiling window manager such as i3, which generally refuses windows' resize requests. In X11, if a window manager refuses an attempt to change the window's size via ConfigureWindow, ICCCM says it should respond by sending a synthetic ConfigureNotify event restating the same size. Such no-op configure events do reach the "configure_event" handler in a GTK program, but they weren't previously getting as far as term_size(), because the call to term_size() was triggered from the GTK "size_allocate" event on the GtkDrawingArea inside the window (via drawing_area_setup()), so GTK would detect that nothing had changed. Now we queue a last-ditch toplevel callback which ensures that if the configure event doesn't also cause a size_allocate and a call to drawing_area_setup(), then we cause one of our own once the dust has settled. And drawing_area_setup(), in turn, now unconditionally calls term_size() even if the size is the same as it was last time, instead of taking an early exit. (It still does take an early exit to avoid unnecessary faffing with Cairo surfaces etc, but _after_ term_size()). This won't be 100% reliable, because it's the window manager's responsibility to send those synthetic ConfigureNotify events, and a window manager is a fallible process which could get into a stuck state. But it covers all the cases I know of that _can_ be sensibly covered - so now, when terminal.c asks the front end to resize the window, it ought to find out in a timely manner whether or not that has happened, in _almost_ all cases. --- unix/window.c | 52 +++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 39 insertions(+), 13 deletions(-) diff --git a/unix/window.c b/unix/window.c index fef7687a..2fadd393 100644 --- a/unix/window.c +++ b/unix/window.c @@ -89,6 +89,7 @@ struct GtkFrontend { gboolean sbar_visible; gboolean drawing_area_got_size, drawing_area_realised; gboolean drawing_area_setup_needed; + bool drawing_area_setup_called; GtkBox *hbox; GtkAdjustment *sbar_adjust; GtkWidget *menu, *specialsmenu, *specialsitem1, *specialsitem2, @@ -700,7 +701,6 @@ static void draw_backing_rect(GtkFrontend *inst); static void drawing_area_setup(GtkFrontend *inst, int width, int height) { int w, h, new_scale; - bool need_size = false; /* * See if the terminal size has changed. @@ -716,11 +716,7 @@ static void drawing_area_setup(GtkFrontend *inst, int width, int height) conf_set_int(inst->conf, CONF_width, inst->width); conf_set_int(inst->conf, CONF_height, inst->height); /* - * We'll need to tell terminal.c about the resize below. - */ - need_size = true; - /* - * And we must refresh the window's backing image. + * We must refresh the window's backing image. */ inst->drawing_area_setup_needed = true; } @@ -742,10 +738,23 @@ static void drawing_area_setup(GtkFrontend *inst, int width, int height) inst->drawing_area_setup_needed = true; /* - * This event might be spurious; some GTK setups have been known - * to call it when nothing at all has changed. Check if we have - * any reason to proceed. + * GTK will sometimes send us configure events when nothing about + * the window size has actually changed. In some situations this + * can happen quite often, so it's a worthwhile optimisation to + * detect that situation and avoid the expensive reinitialisation + * of the backing surface / image, and so on. + * + * However, we must still communicate to the terminal that we + * received a resize event, because sometimes a trivial resize + * event (to the same size we already were) is a signal from the + * window system that a _nontrivial_ resize we recently asked for + * has failed to happen. */ + + inst->drawing_area_setup_called = true; + if (inst->term) + term_size(inst->term, h, w, conf_get_int(inst->conf, CONF_savelines)); + if (!inst->drawing_area_setup_needed) return; @@ -776,10 +785,6 @@ static void drawing_area_setup(GtkFrontend *inst, int width, int height) draw_backing_rect(inst); - if (need_size && inst->term) { - term_size(inst->term, h, w, conf_get_int(inst->conf, CONF_savelines)); - } - if (inst->term) term_invalidate(inst->term); @@ -806,6 +811,14 @@ static void drawing_area_setup_simple(GtkFrontend *inst) drawing_area_setup(inst, alloc.width, alloc.height); } +static void drawing_area_setup_cb(void *vctx) +{ + GtkFrontend *inst = (GtkFrontend *)vctx; + + if (!inst->drawing_area_setup_called) + drawing_area_setup_simple(inst); +} + static void area_realised(GtkWidget *widget, GtkFrontend *inst) { inst->drawing_area_realised = true; @@ -844,6 +857,10 @@ static gboolean window_configured( term_notify_window_pos(inst->term, event->x, event->y); term_notify_window_size_pixels( inst->term, event->width, event->height); + if (inst->drawing_area_realised && inst->drawing_area_got_size) { + inst->drawing_area_setup_called = false; + queue_toplevel_callback(drawing_area_setup_cb, inst); + } } return false; } @@ -2450,6 +2467,14 @@ static void compute_whole_window_size(GtkFrontend *inst, int *wpix, int *hpix); #endif +static void gtkwin_deny_term_resize(void *vctx) +{ + GtkFrontend *inst = (GtkFrontend *)vctx; + if (inst->term) + term_size(inst->term, inst->term->rows, inst->term->cols, + inst->term->savelines); +} + static void gtkwin_request_resize(TermWin *tw, int w, int h) { GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); @@ -2494,6 +2519,7 @@ static void gtkwin_request_resize(TermWin *tw, int w, int h) GDK_WINDOW_STATE_LEFT_TILED | #endif 0)) { + queue_toplevel_callback(gtkwin_deny_term_resize, inst); return; } } -- cgit v1.2.3 From 420fe75552afa9490c7ca2ff08e92ec96933cfe1 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 19 Dec 2021 10:37:02 +0000 Subject: Suspend terminal output while a window resize is pending. This is the payoff from the last few commits of refactoring. It fixes the following race-condition bug in terminal application redraw: * server sends a window-resizing escape sequence * terminal requests a window resize from the front end * server sends further escape sequences to perform a redraw of some full-screen application, which assume that the window resize has occurred and the window is already its new size * terminal processes all those sequences in the context of the old window size, while the front end is still thinking * window resize completes in the front end and term_size() tells the terminal it now has its new size, but it's too late, the screen redraw has made a total mess. (Perhaps the server might even send its window resize + followup redraw all in one SSH packet, so that it's all queued in term->inbuf in one go.) As far as I can see, handling of this case has been broken more or less forever in the GTK frontend (where window resizes are inherently asynchronous due to the way X11 works, and we've never done anything to compensate for that). On Windows, where window size is changed via SetWindowPos which is synchronous, it used to work, but broke in commit d74308e90e3813a (i.e. between 0.74 and 0.75), which made all the ancillary window updates run on the same delayed-action timer as ordinary text display. So, it's time to fix it, and I think now I should be able to fix it in GTK as well as on Windows. Now, as soon as we've set the term->win_resize_pending flag (in response to a resize escape sequence), the next return to the top of the main loop in term_out will terminate output processing early, leaving any further terminal data still in the term->inbuf bufchain. Once we get a term_size() callback from the front end telling us our new size, we reset term->win_resize_pending, which unblocks output processing again, and we also queue a toplevel callback to have another try at term_out() so that it will be unblocked promptly. To implement this I've changed term->win_resize_pending from a bool into a three-state enumeration, so that we can tell the difference between 'pending' in the sense of not yet having sent our resize request to the frontend, and in the sense of waiting for the frontend to reply. That way, a window resize from the GUI user at least won't be mistaken for the response to our resize request if it arrives in the former state. (It can still be mistaken for one in the latter case, but if the user is resizing the window at the same time as the server-side application is doing critically size-dependent redrawing, I don't think there can be any reasonable expectation of nothing going wrong.) As mentioned in the previous commit, some failure modes under X11 (in particular the window manager process getting wedged in some way) can result in no response being received to a ConfigureWindow request. In that situation, it seems to me that we really _shouldn't_ sit there waiting forever - perhaps it's technically the WM's fault and not ours, but what kind of X window are you most likely to want to use to do emergency WM repair? A terminal window, of course, so it would be exceptionally unhelpful to make any terminal window stop working completely in this situation! Hence, there's a fallback timeout in terminal.c, so that if we don't receive a response in _too_ long, we'll assume one is not forthcoming, and resume processing terminal data at the old window size. The fallback timeout is set to 5 seconds, following existing practice in libXt (DEFAULT_WM_TIMEOUT). --- terminal/terminal.c | 54 +++++++++++++++++++++++++++++++++++++++++++++++++---- terminal/terminal.h | 53 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 101 insertions(+), 6 deletions(-) diff --git a/terminal/terminal.c b/terminal/terminal.c index 4f094462..3b0c68d4 100644 --- a/terminal/terminal.c +++ b/terminal/terminal.c @@ -100,6 +100,7 @@ static void scroll(Terminal *, int, int, int, bool); static void parse_optionalrgb(optionalrgb *out, unsigned *values); static void term_added_data(Terminal *term); static void term_update_raw_mouse_mode(Terminal *term); +static void term_out_cb(void *); static termline *newtermline(Terminal *term, int cols, bool bce) { @@ -1219,6 +1220,12 @@ static void term_timer(void *ctx, unsigned long now) if (term->window_update_pending) term_update_callback(term); + + if (term->win_resize_pending == WIN_RESIZE_AWAIT_REPLY && + now == term->win_resize_timeout) { + term->win_resize_pending = WIN_RESIZE_NO; + queue_toplevel_callback(term_out_cb, term); + } } static void term_update_callback(void *ctx) @@ -1415,10 +1422,12 @@ void term_update(Terminal *term) term->win_move_pending_y); term->win_move_pending = false; } - if (term->win_resize_pending) { + if (term->win_resize_pending == WIN_RESIZE_NEED_SEND) { + term->win_resize_pending = WIN_RESIZE_AWAIT_REPLY; win_request_resize(term->win, term->win_resize_pending_w, term->win_resize_pending_h); - term->win_resize_pending = false; + term->win_resize_timeout = schedule_timer( + WIN_RESIZE_TIMEOUT, term_timer, term); } if (term->win_zorder_pending) { win_set_zorder(term->win, term->win_zorder_top); @@ -2043,7 +2052,7 @@ Terminal *term_init(Conf *myconf, struct unicode_data *ucsdata, TermWin *win) term->winpixsize_x = term->winpixsize_y = 0; term->win_move_pending = false; - term->win_resize_pending = false; + term->win_resize_pending = WIN_RESIZE_NO; term->win_zorder_pending = false; term->win_minimise_pending = false; term->win_maximise_pending = false; @@ -2142,6 +2151,14 @@ void term_size(Terminal *term, int newrows, int newcols, int newsavelines) int sblen; int save_alt_which = term->alt_which; + /* If we were holding buffered terminal data because we were + * waiting for confirmation of a resize, queue a callback to start + * processing it again. */ + if (term->win_resize_pending == WIN_RESIZE_AWAIT_REPLY) { + term->win_resize_pending = WIN_RESIZE_NO; + queue_toplevel_callback(term_out_cb, term); + } + if (newrows == term->rows && newcols == term->cols && newsavelines == term->savelines) return; /* nothing to do */ @@ -2977,7 +2994,7 @@ static void term_request_resize(Terminal *term, int cols, int rows) if (term->cols == cols && term->rows == rows) return; /* don't need to do anything */ - term->win_resize_pending = true; + term->win_resize_pending = WIN_RESIZE_NEED_SEND; term->win_resize_pending_w = cols; term->win_resize_pending_h = rows; term_schedule_update(term); @@ -3654,6 +3671,29 @@ static void term_out(Terminal *term) c = unget; unget = -1; } else { + /* + * If we're waiting for a terminal resize triggered by an + * escape sequence, we defer processing the terminal + * output until we receive acknowledgment from the front + * end that the resize has happened, so that further + * output will be processed in the context of the new + * size. + * + * This test goes inside the main while-loop, so that we + * exit early if we encounter a resize escape sequence + * part way through term->inbuf. + * + * It's also in the branch of this if statement that + * doesn't deal with a character left in 'unget' by the + * previous loop iteration, because if we break out of + * this loop with an ungot character still pending, we'll + * lose it. (And in any case, if the previous thing that + * happened was a truncated UTF-8 sequence, then it won't + * have scheduled a pending resize.) + */ + if (term->win_resize_pending != WIN_RESIZE_NO) + break; + if (nchars_got == nchars_used) { /* Delete the previous chunk from the bufchain */ bufchain_consume(&term->inbuf, nchars_used); @@ -5554,6 +5594,12 @@ static void term_out(Terminal *term) logflush(term->logctx); } +/* Wrapper on term_out with the right prototype to be a toplevel callback */ +void term_out_cb(void *ctx) +{ + term_out((Terminal *)ctx); +} + /* * Small subroutine to parse three consecutive escape-sequence * arguments representing a true-colour RGB triple into an diff --git a/terminal/terminal.h b/terminal/terminal.h index 2eaaffcc..b2347f9a 100644 --- a/terminal/terminal.h +++ b/terminal/terminal.h @@ -381,8 +381,6 @@ struct terminal_tag { */ bool win_move_pending; int win_move_pending_x, win_move_pending_y; - bool win_resize_pending; - int win_resize_pending_w, win_resize_pending_h; bool win_zorder_pending; bool win_zorder_top; bool win_minimise_pending; @@ -396,6 +394,57 @@ struct terminal_tag { bool win_scrollbar_update_pending; bool win_palette_pending; unsigned win_palette_pending_min, win_palette_pending_limit; + + /* + * Unlike the rest of the above 'pending' flags, the one for + * window resizing has to be more complicated, because it's very + * likely that a server sending a window-resize escape sequence is + * going to follow it up immediately with further terminal output + * that draws a full-screen application expecting the terminal to + * be the new size. + * + * So, once we've requested a window resize from the TermWin, we + * have to stop processing terminal data until we get back the + * notification that our window really has changed size (or until + * we find out that it's not going to). + * + * Hence, window resizes go through a small state machine with two + * different kinds of 'pending'. NEED_SEND is the state where + * we've received an escape sequence asking for a new size but not + * yet sent it to the TermWin via win_request_resize; AWAIT_REPLY + * is the state where we've sent it to the TermWin and are + * expecting a call back to term_size(). + * + * So _both_ of those 'pending' states inhibit terminal output + * processing. + * + * (Hence, once we're in either state, we should never handle + * another resize sequence, so the only possible path through this + * state machine is to get all the way back to the ground state + * before doing anything else interesting.) + */ + enum { + WIN_RESIZE_NO, WIN_RESIZE_NEED_SEND, WIN_RESIZE_AWAIT_REPLY + } win_resize_pending; + int win_resize_pending_w, win_resize_pending_h; + + /* + * Not every frontend / TermWin implementation can be relied on + * 100% to reply to a resize request in a timely manner. (In X11 + * it's all asynchronous and goes via the window manager, and if + * your window manager is seriously unwell, you'd rather not have + * terminal windows start becoming unusable as a knock-on effect, + * since those are just the thing you might need to use for + * emergency WM maintenance!) So when we enter AWAIT_REPLY status, + * we also set a 5-second timer, after which we'll regretfully + * conclude that a resize is probably not going to happen after + * all. + * + * However, in non-emergency cases, the plan is that this + * shouldn't be needed, for one reason or another. + */ + long win_resize_timeout; + #define WIN_RESIZE_TIMEOUT (TICKSPERSEC*5) }; static inline bool in_utf(Terminal *term) -- cgit v1.2.3 From cfc902361633915646cd1384830d758f16aa701d Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 13 Dec 2021 06:54:49 +0000 Subject: Windows: try to send only one term_size on request_resize. Previously, the Windows implementation of win_request_resize would call term_size() to tell the terminal about its new size, _before_ calling SetWindowPos to actually change the window size. If SetWindowPos ends up not getting the exact window size it asks for, Windows notifies the application by sending back a WM_SIZE message - synchronously, by calling back to the window procedure from within SetWindowPos. So after the first over-optimistic term_size(), the WM_SIZE handler would trigger a second one, resetting the terminal again to the _actual_ size. This was more or less harmless, since current handling of terminal resizes is such that no terminal data gets too confused: any lines pushed into the scrollback by the first term_size will be pulled back out by the second one if needed (or vice versa), and since commit 271de3e4ec5682e, individual termlines are not eagerly truncated by resizing them twice. However, it's more work than we really wanted to be doing, and it seems presumptuous to send term_size() before we know it's right! So now we send term_size() after SetWindowPos returns, unless it already got sent by a re-entrant call to the WM_SIZE handler _before_ SetWindowPos returned. That way, the terminal should get exactly one term_size() call in response to win_request_resize(), whether it got the size it was expecting or not. --- windows/window.c | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/windows/window.c b/windows/window.c index 851dafb5..aead39f4 100644 --- a/windows/window.c +++ b/windows/window.c @@ -1590,6 +1590,8 @@ static void deinit_fonts(void) trust_icon = INVALID_HANDLE_VALUE; } +static bool sent_term_size; /* only live during wintw_request_resize() */ + static void wintw_request_resize(TermWin *tw, int w, int h) { const struct BackendVtable *vt; @@ -1635,7 +1637,18 @@ static void wintw_request_resize(TermWin *tw, int w, int h) } } - term_size(term, h, w, conf_get_int(conf, CONF_savelines)); + /* + * We want to send exactly one term_size() to the terminal, + * telling it what size it ended up after this operation. + * + * If we don't get the size we asked for in SetWindowPos, then + * we'll be sent a WM_SIZE message, whose handler will make that + * call, all before SetWindowPos even returns to here. + * + * But if that _didn't_ happen, we'll need to call term_size + * ourselves afterwards. + */ + sent_term_size = false; if (conf_get_int(conf, CONF_resize_action) != RESIZE_FONT && !IsZoomed(wgs.term_hwnd)) { @@ -1648,6 +1661,9 @@ static void wintw_request_resize(TermWin *tw, int w, int h) } else reset_window(0); + if (!sent_term_size) + term_size(term, h, w, conf_get_int(conf, CONF_savelines)); + InvalidateRect(wgs.term_hwnd, NULL, true); } @@ -2088,6 +2104,11 @@ static void wm_size_resize_term(LPARAM lParam, bool border) } else { term_size(term, h, w, conf_get_int(conf, CONF_savelines)); + /* If this is happening re-entrantly during the call to + * SetWindowPos in wintw_request_resize, let it know that + * we've already done a term_size() so that it doesn't have + * to. */ + sent_term_size = true; } } -- cgit v1.2.3 From bc91a39670a7a74bb231c35a8ea1b020a58f0917 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 12 Dec 2021 10:57:23 +0000 Subject: Proper buffer management between terminal and backend. The return value of term_data() is used as the return value from the GUI-terminal versions of the Seat output method, which means backends will take it to be the amount of standard-output data currently buffered, and exert back-pressure on the remote peer if it gets too big (e.g. by ceasing to extend the window in that particular SSH-2 channel). Historically, as a comment in term_data() explained, we always just returned 0 from that function, on the basis that we were processing all the terminal data through our terminal emulation code immediately, and never retained any of it in the buffer at all. If the terminal emulation code were to start running slowly, then it would slow down the _whole_ PuTTY system, due to single-threadedness, and back-pressure of a sort would be exerted on the remote by it simply failing to get round to reading from the network socket. But by the time we got back to the top level of term_data(), we'd have finished reading all the data we had, so it was still appropriate to return 0. That comment is still correct if you're thinking about the limiting factor on terminal data processing being the CPU usage in term_out(). But now that's no longer the whole story, because sometimes we leave data in term->inbuf without having processed it: during drag-selects in the terminal window, and (just introduced) while waiting for the response to a pending window resize request. For both those reasons, we _don't_ always have a buffer size of zero when we return from term_data(). So now that hole in our buffer size management is filled in: term_data() returns the true size of the remaining unprocessed terminal output, so that back-pressure will be exerted if the terminal is currently not consuming it. And when processing resumes and we start to clear our backlog, we call backend_unthrottle to let the backend know it can relax the back-pressure if necessary. --- putty.h | 7 +++++++ terminal/terminal.c | 43 +++++++++++++------------------------------ test/fuzzterm.c | 2 ++ unix/window.c | 8 ++++++++ windows/window.c | 8 ++++++++ 5 files changed, 38 insertions(+), 30 deletions(-) diff --git a/putty.h b/putty.h index 0753d7cf..c4e8d6a1 100644 --- a/putty.h +++ b/putty.h @@ -1593,6 +1593,11 @@ struct TermWinVtable { * object, because that doesn't happen until term_init * returns. */ void (*palette_get_overrides)(TermWin *, Terminal *); + + /* Notify the front end that the terminal's buffer of unprocessed + * output has reduced. (Front ends will likely pass this straight + * on to backend_unthrottle.) */ + void (*unthrottle)(TermWin *, size_t bufsize); }; static inline bool win_setup_draw_ctx(TermWin *win) @@ -1649,6 +1654,8 @@ static inline void win_palette_set( { win->vt->palette_set(win, start, ncolours, colours); } static inline void win_palette_get_overrides(TermWin *win, Terminal *term) { win->vt->palette_get_overrides(win, term); } +static inline void win_unthrottle(TermWin *win, size_t size) +{ win->vt->unthrottle(win, size); } /* * Global functions not specific to a connection instance. diff --git a/terminal/terminal.c b/terminal/terminal.c index 3b0c68d4..6efd9cfe 100644 --- a/terminal/terminal.c +++ b/terminal/terminal.c @@ -98,7 +98,7 @@ static void deselect(Terminal *); static void term_print_finish(Terminal *); static void scroll(Terminal *, int, int, int, bool); static void parse_optionalrgb(optionalrgb *out, unsigned *values); -static void term_added_data(Terminal *term); +static void term_added_data(Terminal *term, bool); static void term_update_raw_mouse_mode(Terminal *term); static void term_out_cb(void *); @@ -3491,7 +3491,7 @@ static inline void term_keyinput_internal( int true_len = len >= 0 ? len : strlen(buf); bufchain_add(&term->inbuf, buf, true_len); - term_added_data(term); + term_added_data(term, false); } if (interactive) term_bracketed_paste_stop(term); @@ -3639,7 +3639,7 @@ unsigned long term_translate( * in-memory display. There's a big state machine in here to * process escape sequences... */ -static void term_out(Terminal *term) +static void term_out(Terminal *term, bool called_from_term_data) { unsigned long c; int unget; @@ -5589,6 +5589,9 @@ static void term_out(Terminal *term) bufchain_consume(&term->inbuf, nchars_used); + if (!called_from_term_data) + win_unthrottle(term->win, bufchain_size(&term->inbuf)); + term_print_flush(term); if (term->logflush && term->logctx) logflush(term->logctx); @@ -5597,7 +5600,7 @@ static void term_out(Terminal *term) /* Wrapper on term_out with the right prototype to be a toplevel callback */ void term_out_cb(void *ctx) { - term_out((Terminal *)ctx); + term_out((Terminal *)ctx, false); } /* @@ -7292,7 +7295,7 @@ void term_mouse(Terminal *term, Mouse_Button braw, Mouse_Button bcooked, * should make sure to write any pending output if one has just * finished. */ - term_out(term); + term_out(term, false); term_schedule_update(term); } @@ -7605,15 +7608,15 @@ void term_lost_clipboard_ownership(Terminal *term, int clipboard) * should make sure to write any pending output if one has just * finished. */ - term_out(term); + term_out(term, false); } -static void term_added_data(Terminal *term) +static void term_added_data(Terminal *term, bool called_from_term_data) { if (!term->in_term_out) { term->in_term_out = true; term_reset_cblink(term); - term_out(term); + term_out(term, called_from_term_data); term->in_term_out = false; } } @@ -7621,28 +7624,8 @@ static void term_added_data(Terminal *term) size_t term_data(Terminal *term, const void *data, size_t len) { bufchain_add(&term->inbuf, data, len); - term_added_data(term); - - /* - * 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; + term_added_data(term, true); + return bufchain_size(&term->inbuf); } void term_provide_logctx(Terminal *term, LogContext *logctx) diff --git a/test/fuzzterm.c b/test/fuzzterm.c index f1477054..02de7bf6 100644 --- a/test/fuzzterm.c +++ b/test/fuzzterm.c @@ -91,6 +91,7 @@ static void fuzz_set_zorder(TermWin *tw, bool top) {} static void fuzz_palette_set(TermWin *tw, unsigned start, unsigned ncolours, const rgb *colours) {} static void fuzz_palette_get_overrides(TermWin *tw, Terminal *term) {} +static void fuzz_unthrottle(TermWin *tw, size_t size) {} static const TermWinVtable fuzz_termwin_vt = { .setup_draw_ctx = fuzz_setup_draw_ctx, @@ -115,6 +116,7 @@ static const TermWinVtable fuzz_termwin_vt = { .set_zorder = fuzz_set_zorder, .palette_set = fuzz_palette_set, .palette_get_overrides = fuzz_palette_get_overrides, + .unthrottle = fuzz_unthrottle, }; void ldisc_send(Ldisc *ldisc, const void *buf, int len, bool interactive) {} diff --git a/unix/window.c b/unix/window.c index 2fadd393..4c57b020 100644 --- a/unix/window.c +++ b/unix/window.c @@ -326,6 +326,13 @@ static size_t gtk_seat_output(Seat *seat, SeatOutputType type, return term_data(inst->term, data, len); } +static void gtkwin_unthrottle(TermWin *win, size_t bufsize) +{ + GtkFrontend *inst = container_of(win, GtkFrontend, termwin); + if (inst->backend) + backend_unthrottle(inst->backend, bufsize); +} + static bool gtk_seat_eof(Seat *seat) { /* GtkFrontend *inst = container_of(seat, GtkFrontend, seat); */ @@ -5156,6 +5163,7 @@ static const TermWinVtable gtk_termwin_vt = { .set_zorder = gtkwin_set_zorder, .palette_set = gtkwin_palette_set, .palette_get_overrides = gtkwin_palette_get_overrides, + .unthrottle = gtkwin_unthrottle, }; void new_session_window(Conf *conf, const char *geometry_string) diff --git a/windows/window.c b/windows/window.c index aead39f4..b1a884dd 100644 --- a/windows/window.c +++ b/windows/window.c @@ -259,6 +259,7 @@ static void wintw_move(TermWin *, int x, int y); static void wintw_set_zorder(TermWin *, bool top); static void wintw_palette_set(TermWin *, unsigned, unsigned, const rgb *); static void wintw_palette_get_overrides(TermWin *, Terminal *); +static void wintw_unthrottle(TermWin *win, size_t bufsize); static const TermWinVtable windows_termwin_vt = { .setup_draw_ctx = wintw_setup_draw_ctx, @@ -284,6 +285,7 @@ static const TermWinVtable windows_termwin_vt = { .set_zorder = wintw_set_zorder, .palette_set = wintw_palette_set, .palette_get_overrides = wintw_palette_get_overrides, + .unthrottle = wintw_unthrottle, }; static TermWin wintw[1]; @@ -5774,6 +5776,12 @@ static size_t win_seat_output(Seat *seat, SeatOutputType type, return term_data(term, data, len); } +static void wintw_unthrottle(TermWin *win, size_t bufsize) +{ + if (backend) + backend_unthrottle(backend, bufsize); +} + static bool win_seat_eof(Seat *seat) { return true; /* do respond to incoming EOF with outgoing */ -- cgit v1.2.3 From 4721571b8bcfc2c0266d0f20dcd5ddc7b0e58889 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 19 Dec 2021 13:13:37 +0000 Subject: GTK: run toplevel callbacks when an fd is active. Normally, the GTK code runs toplevel callbacks from a GTK 'idle function'. But those mean what they say: they are considered low-priority, to be run _only_ when the system is idle - so they can fail to run at all in conditions of a steady stream of higher-priority things, e.g. something is throwing data at the application so fast that every main-loop iteration finds a readable fd. And that's not good, because _we_ don't think our callbacks are low-priority: they do a lot of really important work like redrawing the window. So if they never get round to happening, PuTTY or pterm can appear to lock up. Simple solution to that one: whenever we process a select notification on any fd, we _also_ call run_toplevel_callbacks(). Then our callbacks are bound to happen reasonably regularly. --- unix/gtk-common.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/unix/gtk-common.c b/unix/gtk-common.c index da653253..4fcc0335 100644 --- a/unix/gtk-common.c +++ b/unix/gtk-common.c @@ -86,6 +86,8 @@ gboolean fd_input_func(GIOChannel *source, GIOCondition condition, if (condition & G_IO_OUT) select_result(sourcefd, SELECT_W); + run_toplevel_callbacks(); + return true; } #else @@ -97,6 +99,8 @@ void fd_input_func(gpointer data, gint sourcefd, GdkInputCondition condition) select_result(sourcefd, SELECT_R); if (condition & GDK_INPUT_WRITE) select_result(sourcefd, SELECT_W); + + run_toplevel_callbacks(); } #endif -- cgit v1.2.3 From f780a45c571f7623766300731b243e485393bf15 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 19 Dec 2021 11:15:50 +0000 Subject: Proper backlog handling in Unix pty backend. If the Seat that the pty backend is talking to starts to back up, then we ought to temporarily stop reading from the pty device, to pass that back-pressure on to whatever's running in the terminal. Previously, this didn't matter because a Seat running a GUI terminal never backed up anyway. But now it does, so we should support it all the way through the system. --- unix/pty.c | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/unix/pty.c b/unix/pty.c index e379e6cf..0747b584 100644 --- a/unix/pty.c +++ b/unix/pty.c @@ -73,6 +73,7 @@ struct Pty { int master_i, master_o, master_e; Seat *seat; + size_t output_backlog; char name[FILENAME_MAX]; pid_t child_pid; int term_width, term_height; @@ -83,6 +84,8 @@ struct Pty { Backend backend; }; +#define PTY_MAX_BACKLOG 32768 + /* * We store all the (active) PtyFd structures in a tree sorted by fd, * so that when we get an uxsel notification we know which backend @@ -589,6 +592,7 @@ void pty_pre_init(void) } static void pty_try_wait(void); +static void pty_uxsel_setup(Pty *pty); static void pty_real_select_result(Pty *pty, int fd, int event, int status) { @@ -673,7 +677,9 @@ static void pty_real_select_result(Pty *pty, int fd, int event, int status) perror("read pty master"); exit(1); } else if (ret > 0) { - seat_output(pty->seat, !is_stdout, buf, ret); + pty->output_backlog = seat_output( + pty->seat, !is_stdout, buf, ret); + pty_uxsel_setup(pty); } } else if (event == SELECT_W) { /* @@ -775,8 +781,10 @@ static void pty_uxsel_setup_fd(Pty *pty, int fd) if (fd < 0) return; - /* read from standard output and standard error pipes */ - if (pty->master_o == fd || pty->master_e == fd) + /* read from standard output and standard error pipes, assuming + * we're not too backlogged */ + if ((pty->master_o == fd || pty->master_e == fd) && + pty->output_backlog < PTY_MAX_BACKLOG) rwx |= SELECT_R; /* write to standard input pipe if we have any data */ if (pty->master_i == fd && bufchain_size(&pty->output_data)) @@ -1514,8 +1522,9 @@ static bool pty_sendok(Backend *be) static void pty_unthrottle(Backend *be, size_t backlog) { - /* Pty *pty = container_of(be, Pty, backend); */ - /* do nothing */ + Pty *pty = container_of(be, Pty, backend); + pty->output_backlog = backlog; + pty_uxsel_setup(pty); } static bool pty_ldisc(Backend *be, int option) -- cgit v1.2.3 From 18a3a999f6671763755b270713f7e43d6e0fa9fc Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 20 Dec 2021 10:18:38 +0000 Subject: GTK: fix calculation of fixed window size for SUPDUP. The window size set in the geometry hints when the backend has the BACKEND_RESIZE_FORBIDDEN flag was computed in a simplistic way that forgot to take account of window furniture like scrollbars and menu bars. Now it's computed based on the rest of the geometry hints, which are more accurate. --- unix/window.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/unix/window.c b/unix/window.c index 4c57b020..f2edd356 100644 --- a/unix/window.c +++ b/unix/window.c @@ -4500,8 +4500,8 @@ void set_geom_hints(GtkFrontend *inst) if (vt && vt->flags & BACKEND_RESIZE_FORBIDDEN) { /* Window resizing forbidden. Set both minimum and maximum * dimensions to be the initial size. */ - geom.min_width = inst->width*inst->font_width + 2*inst->window_border; - geom.min_height = inst->height*inst->font_height + 2*inst->window_border; + geom.min_width = geom.base_width + geom.width_inc * inst->width; + geom.min_height = geom.base_height + geom.height_inc * inst->height; geom.max_width = geom.min_width; geom.max_height = geom.min_height; flags |= GDK_HINT_MAX_SIZE; -- cgit v1.2.3 From 99aac9c4f4f9b9153284b499668b3ba1300c9e67 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 20 Dec 2021 11:06:57 +0000 Subject: GTK: stop using geometry hints when not on X11. While re-testing on Wayland after all this churn of the window resizing code, I discovered that the window constantly came out a few pixels too small, losing a character cell in width and height. This stopped happening once I experimentally stopped setting geometry hints. Source-diving in GTK, it turns out that this is because the GDK Wayland backend is applying the geometry hints to the size of the window including 'margins', which are a very large extra space around a window beyond even the visible 'non-client-area' furniture like the title bar. And I have no idea how you find out the size of those margins, so I can't allow for them in the geometry hints. I also noticed that gtk_window_set_geometry_hints is removed in GTK 4, which suggests that GTK upstream are probably not interested in fiddling with them until they work more usefully (even if they would agree with me that this is a bug in the first place, which I have no idea). A simpler workaround is to avoid setting geometry hints at all on any GDK backend other than X11. So, that's what this commit does. On Wayland (or other backends), the window can now be resized a pixel at a time, and if its size doesn't work out to a whole number of character cells, then you just get some dead space at the edges. Not especially nice, but better than the alternatives I can see. One other job the geometry hints were doing was to forbid resizing if the backend sets the BACKEND_RESIZE_FORBIDDEN flag (which SUPDUP does). That's now done at window creation time, via gtk_window_set_resizable. --- unix/window.c | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/unix/window.c b/unix/window.c index f2edd356..dba32c27 100644 --- a/unix/window.c +++ b/unix/window.c @@ -4488,6 +4488,35 @@ static void compute_geom_hints(GtkFrontend *inst, GdkGeometry *geom) void set_geom_hints(GtkFrontend *inst) { + /* + * 2021-12-20: I've found that on Ubuntu 20.04 Wayland (using GTK + * 3.24.20), setting geometry hints causes the window size to come + * out wrong. As far as I can tell, that's because the GDK Wayland + * backend internally considers windows to be a lot larger than + * their obvious display size (*even* considering visible window + * furniture like title bars), with an extra margin on every side + * to account for surrounding effects like shadows. And the + * geometry hints like base size and resize increment are applied + * to that larger size rather than the more obvious 'client area' + * size. So when we ask for a window of exactly the size we want, + * it gets modified by GDK based on the geometry hints, but + * applying this extra margin, which causes the size to be a + * little bit too small. + * + * I don't know how you can sensibly find out the size of that + * margin. If I did, I could account for it in the geometry hints. + * But I also see that gtk_window_set_geometry_hints is removed in + * GTK 4, which suggests that probably doing a lot of hard work to + * fix this is not the way forward. + * + * So instead, I simply avoid setting geometry hints at all on any + * GDK backend other than X11, and hopefully that's a workaround. + */ +#if GTK_CHECK_VERSION(3,0,0) + if (!GDK_IS_X11_DISPLAY(gdk_display_get_default())) + return; +#endif + const struct BackendVtable *vt; GdkGeometry geom; gint flags = GDK_HINT_MIN_SIZE | GDK_HINT_BASE_SIZE | GDK_HINT_RESIZE_INC; @@ -5272,6 +5301,14 @@ void new_session_window(Conf *conf, const char *geometry_string) } } +#if GTK_CHECK_VERSION(2,0,0) + { + const BackendVtable *vt = select_backend(inst->conf); + if (vt && vt->flags & BACKEND_RESIZE_FORBIDDEN) + gtk_window_set_resizable(GTK_WINDOW(inst->window), false); + } +#endif + inst->width = conf_get_int(inst->conf, CONF_width); inst->height = conf_get_int(inst->conf, CONF_height); cache_conf_values(inst); -- cgit v1.2.3 From ce1774282c35c02867200607497fd300f46649cd Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Tue, 21 Dec 2021 09:35:13 +0000 Subject: HTTP proxy: correctly handle multiple auth headers. This is a piece I forgot in the initial implementation of HTTP Digest: an HTTP server can send _more than one_ authentication request header (WWW-Authenticate for normal servers, Proxy-Authenticate for proxies), and if it does, they're supposed to be treated as alternatives to each other, so that the client chooses one to reply to. I suppose that technically we were 'complying' with that spec already, in that HttpProxyNegotiator would have read each new header and overwritten all the fields set by the previous one, so we'd always have gone with the last header presented by the server. But that seems inelegant: better to choose the one we actually like best. So now we do that. All the details of an auth header are moved out of the main HttpProxyNegotiator struct into a sub-struct we can have multiple copies of. Each new header is parsed into a fresh struct of that kind, and then we can compare it with the previous one and decide which we prefer. The preference order, naturally, is 'more secure is better': Digest beats Basic, and between two Digest headers, SHA-256 beats MD5. (And anything beats a header we can't make sense of at all.) Another side effect of this change is that a 407 response which contains _no_ Proxy-Authenticate headers will trigger an error message saying so, instead of just going with whatever happened to be left in the relevant variables from the previous attempt. --- proxy/cproxy.h | 3 + proxy/http.c | 398 +++++++++++++++++++++++++++++++++++---------------------- 2 files changed, 250 insertions(+), 151 deletions(-) diff --git a/proxy/cproxy.h b/proxy/cproxy.h index 4179895e..34058dd8 100644 --- a/proxy/cproxy.h +++ b/proxy/cproxy.h @@ -73,6 +73,9 @@ extern const bool http_digest_available; * - algorithm to use for computing it (as a const ssh_hashalg *) * - length to truncate the output to * - whether we accept it in http.c or not. + * + * Finally, the ordering of the accepted hashes is our preference + * order among them if the server offers a choice. */ #define HTTP_DIGEST_HASHES(X) \ X(HTTP_DIGEST_MD5, "MD5", &ssh_md5, 128, true) \ diff --git a/proxy/http.c b/proxy/http.c index bcedacc7..e8c66400 100644 --- a/proxy/http.c +++ b/proxy/http.c @@ -41,7 +41,22 @@ static bool read_line(bufchain *input, strbuf *output, bool is_header) return true; } -typedef enum HttpAuthType { AUTH_NONE, AUTH_BASIC, AUTH_DIGEST } HttpAuthType; +/* Types of HTTP authentication, in preference order. */ +typedef enum HttpAuthType { + AUTH_ERROR, /* if an HttpAuthDetails was never satisfactorily filled in */ + AUTH_NONE, /* if no auth header is seen, assume no auth required */ + AUTH_BASIC, /* username + password sent in clear (only keyless base64) */ + AUTH_DIGEST, /* cryptographic hash, most preferred if available */ +} HttpAuthType; + +typedef struct HttpAuthDetails { + HttpAuthType auth_type; + bool digest_nonce_was_stale; + HttpDigestHash digest_hash; + strbuf *realm, *nonce, *opaque, *error; + bool got_opaque; + bool hash_username; +} HttpAuthDetails; typedef struct HttpProxyNegotiator { int crLine; @@ -51,20 +66,51 @@ typedef struct HttpProxyNegotiator { strbuf *username, *password; int http_status; bool connection_close; - HttpAuthType next_auth_type; + HttpAuthDetails *next_auth; bool try_auth_from_conf; - strbuf *realm, *nonce, *opaque, *uri; - bool got_opaque; + strbuf *uri; uint32_t nonce_count; - bool digest_nonce_was_stale; - HttpDigestHash digest_hash; - bool hash_username; prompts_t *prompts; int username_prompt_index, password_prompt_index; size_t content_length; ProxyNegotiator pn; } HttpProxyNegotiator; +static inline HttpAuthDetails *auth_error(HttpAuthDetails *d, + const char *fmt, ...) +{ + d->auth_type = AUTH_ERROR; + put_fmt(d->error, "Unable to parse auth header from HTTP proxy"); + if (fmt) { + va_list ap; + va_start(ap, fmt); + put_datalit(d->error, ": "); + put_fmtv(d->error, fmt, ap); + va_end(ap); + } + return d; +} + +static HttpAuthDetails *http_auth_details_new(void) +{ + HttpAuthDetails *d = snew(HttpAuthDetails); + memset(d, 0, sizeof(*d)); + d->realm = strbuf_new(); + d->nonce = strbuf_new(); + d->opaque = strbuf_new(); + d->error = strbuf_new(); + return d; +} + +static void http_auth_details_free(HttpAuthDetails *d) +{ + strbuf_free(d->realm); + strbuf_free(d->nonce); + strbuf_free(d->opaque); + strbuf_free(d->error); + sfree(d); +} + static ProxyNegotiator *proxy_http_new(const ProxyNegotiatorVT *vt) { HttpProxyNegotiator *s = snew(HttpProxyNegotiator); @@ -75,9 +121,6 @@ static ProxyNegotiator *proxy_http_new(const ProxyNegotiatorVT *vt) s->token = strbuf_new(); s->username = strbuf_new(); s->password = strbuf_new_nm(); - s->realm = strbuf_new(); - s->nonce = strbuf_new(); - s->opaque = strbuf_new(); s->uri = strbuf_new(); s->nonce_count = 0; /* @@ -85,7 +128,8 @@ static ProxyNegotiator *proxy_http_new(const ProxyNegotiatorVT *vt) * proxy rejects that, it will tell us what kind of auth it would * prefer. */ - s->next_auth_type = AUTH_NONE; + s->next_auth = http_auth_details_new(); + s->next_auth->auth_type = AUTH_NONE; return &s->pn; } @@ -97,10 +141,8 @@ static void proxy_http_free(ProxyNegotiator *pn) strbuf_free(s->token); strbuf_free(s->username); strbuf_free(s->password); - strbuf_free(s->realm); - strbuf_free(s->nonce); - strbuf_free(s->opaque); strbuf_free(s->uri); + http_auth_details_free(s->next_auth); if (s->prompts) free_prompts(s->prompts); sfree(s); @@ -222,6 +264,118 @@ static bool get_quoted_string(HttpProxyNegotiator *s) return true; } +static HttpAuthDetails *parse_http_auth_header(HttpProxyNegotiator *s) +{ + HttpAuthDetails *d = http_auth_details_new(); + + /* Default hash for HTTP Digest is MD5, if none specified explicitly */ + d->digest_hash = HTTP_DIGEST_MD5; + + if (!get_token(s)) + return auth_error(d, "parse error"); + + if (!stricmp(s->token->s, "Basic")) { + /* For Basic authentication, we don't need anything else. The + * realm string is not required for the protocol. */ + d->auth_type = AUTH_BASIC; + return d; + } + + if (!stricmp(s->token->s, "Digest")) { + /* Parse all the additional parts of the Digest header. */ + if (!http_digest_available) + return auth_error(d, "Digest authentication not supported"); + + /* Parse the rest of the Digest header */ + while (true) { + if (!get_token(s)) + return auth_error(d, "parse error in Digest header"); + + if (!stricmp(s->token->s, "realm")) { + if (!get_separator(s, '=') || + !get_quoted_string(s)) + return auth_error(d, "parse error in Digest realm field"); + put_datapl(d->realm, ptrlen_from_strbuf(s->token)); + } else if (!stricmp(s->token->s, "nonce")) { + if (!get_separator(s, '=') || + !get_quoted_string(s)) + return auth_error(d, "parse error in Digest nonce field"); + put_datapl(d->nonce, ptrlen_from_strbuf(s->token)); + } else if (!stricmp(s->token->s, "opaque")) { + if (!get_separator(s, '=') || + !get_quoted_string(s)) + return auth_error(d, "parse error in Digest opaque field"); + put_datapl(d->opaque, + ptrlen_from_strbuf(s->token)); + d->got_opaque = true; + } else if (!stricmp(s->token->s, "stale")) { + if (!get_separator(s, '=') || + !get_token(s)) + return auth_error(d, "parse error in Digest stale field"); + d->digest_nonce_was_stale = !stricmp( + s->token->s, "true"); + } else if (!stricmp(s->token->s, "userhash")) { + if (!get_separator(s, '=') || + !get_token(s)) + return auth_error(d, "parse error in Digest userhash " + "field"); + d->hash_username = !stricmp(s->token->s, "true"); + } else if (!stricmp(s->token->s, "algorithm")) { + if (!get_separator(s, '=') || + !get_token(s)) + return auth_error(d, "parse error in Digest algorithm " + "field"); + bool found = false; + size_t i; + + for (i = 0; i < N_HTTP_DIGEST_HASHES; i++) { + if (!stricmp(s->token->s, httphashnames[i])) { + found = true; + break; + } + } + + if (!found) { + /* We don't even recognise the name */ + return auth_error(d, "Digest hash algorithm '%s' not " + "recognised", s->token->s); + } + + if (!httphashaccepted[i]) { + /* We do recognise the name but we + * don't like it (see comment in cproxy.h) */ + return auth_error(d, "Digest hash algorithm '%s' not " + "supported", s->token->s); + } + + d->digest_hash = i; + } else if (!stricmp(s->token->s, "qop")) { + if (!get_separator(s, '=') || + !get_quoted_string(s)) + return auth_error(d, "parse error in Digest qop field"); + if (stricmp(s->token->s, "auth")) + return auth_error(d, "quality-of-protection type '%s' not " + "supported", s->token->s); + } else { + /* Ignore any other auth-param */ + if (!get_separator(s, '=') || + (!get_quoted_string(s) && !get_token(s))) + return auth_error(d, "parse error in Digest header"); + } + + if (get_end_of_header(s)) + break; + if (!get_separator(s, ',')) + return auth_error(d, "parse error in Digest header"); + } + d->auth_type = AUTH_DIGEST; + return d; + } + + return auth_error(d, "authentication type '%s' not supported", + s->token->s); +} + static void proxy_http_process_queue(ProxyNegotiator *pn) { HttpProxyNegotiator *s = container_of(pn, HttpProxyNegotiator, pn); @@ -257,7 +411,7 @@ static void proxy_http_process_queue(ProxyNegotiator *pn) /* * Add an auth header, if we're planning to this time round. */ - if (s->next_auth_type == AUTH_BASIC) { + if (s->next_auth->auth_type == AUTH_BASIC) { put_datalit(pn->output, "Proxy-Authorization: Basic "); strbuf *base64_input = strbuf_new_nm(); @@ -274,21 +428,28 @@ static void proxy_http_process_queue(ProxyNegotiator *pn) strbuf_free(base64_input); smemclr(base64_output, sizeof(base64_output)); put_datalit(pn->output, "\r\n"); - } else if (s->next_auth_type == AUTH_DIGEST) { + } else if (s->next_auth->auth_type == AUTH_DIGEST) { put_datalit(pn->output, "Proxy-Authorization: Digest "); + + /* If we have a fresh nonce, reset the + * nonce count. Otherwise, keep incrementing it. */ + if (!ptrlen_eq_ptrlen(ptrlen_from_strbuf(s->token), + ptrlen_from_strbuf(s->next_auth->nonce))) + s->nonce_count = 0; + http_digest_response(BinarySink_UPCAST(pn->output), ptrlen_from_strbuf(s->username), ptrlen_from_strbuf(s->password), - ptrlen_from_strbuf(s->realm), + ptrlen_from_strbuf(s->next_auth->realm), PTRLEN_LITERAL("CONNECT"), ptrlen_from_strbuf(s->uri), PTRLEN_LITERAL("auth"), - ptrlen_from_strbuf(s->nonce), - (s->got_opaque ? - ptrlen_from_strbuf(s->opaque) : + ptrlen_from_strbuf(s->next_auth->nonce), + (s->next_auth->got_opaque ? + ptrlen_from_strbuf(s->next_auth->opaque) : make_ptrlen(NULL, 0)), - ++s->nonce_count, s->digest_hash, - s->hash_username); + ++s->nonce_count, s->next_auth->digest_hash, + s->next_auth->hash_username); put_datalit(pn->output, "\r\n"); } @@ -324,6 +485,20 @@ static void proxy_http_process_queue(ProxyNegotiator *pn) } } + if (s->http_status == 407) { + /* + * If this is going to be an auth request, we expect to + * see at least one Proxy-Authorization header offering us + * auth options. Start by preloading s->next_auth with a + * fallback error message, which will be used if nothing + * better is available. + */ + http_auth_details_free(s->next_auth); + s->next_auth = http_auth_details_new(); + auth_error(s->next_auth, "no Proxy-Authorization header seen in " + "HTTP 407 Proxy Authentication Required response"); + } + /* * Read the HTTP response header section. */ @@ -363,136 +538,47 @@ static void proxy_http_process_queue(ProxyNegotiator *pn) else if (!stricmp(s->token->s, "keep-alive")) s->connection_close = false; } else if (hdr == HDR_PROXY_AUTHENTICATE) { - if (!get_token(s)) - continue; - - if (!stricmp(s->token->s, "Basic")) { - s->next_auth_type = AUTH_BASIC; - } else if (!stricmp(s->token->s, "Digest")) { - if (!http_digest_available) { - pn->error = dupprintf( - "HTTP proxy requested Digest authentication " - "which we do not support"); - crStopV; - } + HttpAuthDetails *auth = parse_http_auth_header(s); + + /* + * See if we prefer this set of auth details to the + * previous one we had (either from a previous auth + * header, or the fallback when no auth header is + * provided at all). + */ + bool change; + + if (auth->auth_type != s->next_auth->auth_type) { + /* Use the preference order implied by the enum */ + change = auth->auth_type > s->next_auth->auth_type; + } else if (auth->auth_type == AUTH_DIGEST && + auth->digest_hash != s->next_auth->digest_hash) { + /* Choose based on the hash functions */ + change = auth->digest_hash > s->next_auth->digest_hash; + } else { + /* + * If in doubt, go with the later one of the + * headers. + * + * The main reason for this is so that an error in + * interpreting an auth header will supersede the + * default error we preload saying 'no header + * found', because that would be a particularly + * bad error to report if there _was_ one. + * + * But we're in a tie-breaking situation by now, + * so there's no other reason to choose - we might + * as well apply the same policy everywhere else + * too. + */ + change = true; + } - /* Parse the rest of the Digest header */ - s->digest_nonce_was_stale = false; - s->digest_hash = HTTP_DIGEST_MD5; - strbuf_clear(s->realm); - strbuf_clear(s->nonce); - strbuf_clear(s->opaque); - s->got_opaque = false; - s->hash_username = false; - - while (true) { - if (!get_token(s)) - goto bad_digest; - - if (!stricmp(s->token->s, "realm")) { - if (!get_separator(s, '=') || - !get_quoted_string(s)) - goto bad_digest; - put_datapl(s->realm, ptrlen_from_strbuf(s->token)); - } else if (!stricmp(s->token->s, "nonce")) { - if (!get_separator(s, '=') || - !get_quoted_string(s)) - goto bad_digest; - - /* If we have a fresh nonce, reset the - * nonce count. Otherwise, keep incrementing it. */ - if (!ptrlen_eq_ptrlen( - ptrlen_from_strbuf(s->token), - ptrlen_from_strbuf(s->nonce))) - s->nonce_count = 0; - - put_datapl(s->nonce, ptrlen_from_strbuf(s->token)); - } else if (!stricmp(s->token->s, "opaque")) { - if (!get_separator(s, '=') || - !get_quoted_string(s)) - goto bad_digest; - put_datapl(s->opaque, - ptrlen_from_strbuf(s->token)); - s->got_opaque = true; - } else if (!stricmp(s->token->s, "stale")) { - if (!get_separator(s, '=') || - !get_token(s)) - goto bad_digest; - s->digest_nonce_was_stale = !stricmp( - s->token->s, "true"); - } else if (!stricmp(s->token->s, "userhash")) { - if (!get_separator(s, '=') || - !get_token(s)) - goto bad_digest; - s->hash_username = !stricmp(s->token->s, "true"); - } else if (!stricmp(s->token->s, "algorithm")) { - if (!get_separator(s, '=') || - !get_token(s)) - goto bad_digest; - bool found = false; - size_t i; - - for (i = 0; i < N_HTTP_DIGEST_HASHES; i++) { - if (!stricmp(s->token->s, httphashnames[i])) { - found = true; - break; - } - } - - if (!found) { - /* We don't even recognise the name */ - pn->error = dupprintf( - "HTTP proxy requested Digest hash " - "algorithm '%s' which we do not recognise", - s->token->s); - crStopV; - } - - if (!httphashaccepted[i]) { - /* We do recognise the name but we - * don't like it (see comment in cproxy.h) */ - pn->error = dupprintf( - "HTTP proxy requested Digest hash " - "algorithm '%s' which we do not support", - s->token->s); - crStopV; - } - - s->digest_hash = i; - } else if (!stricmp(s->token->s, "qop")) { - if (!get_separator(s, '=') || - !get_quoted_string(s)) - goto bad_digest; - if (stricmp(s->token->s, "auth")) { - pn->error = dupprintf( - "HTTP proxy requested Digest quality-of-" - "protection type '%s' which we do not " - "support", s->token->s); - crStopV; - } - } else { - /* Ignore any other auth-param */ - if (!get_separator(s, '=') || - (!get_quoted_string(s) && !get_token(s))) - goto bad_digest; - } - - if (get_end_of_header(s)) - break; - if (!get_separator(s, ',')) - goto bad_digest; - } - s->next_auth_type = AUTH_DIGEST; - continue; - bad_digest: - pn->error = dupprintf("HTTP proxy sent Digest auth " - "request we could not parse"); - crStopV; + if (change) { + http_auth_details_free(s->next_auth); + s->next_auth = auth; } else { - pn->error = dupprintf("HTTP proxy asked for unsupported " - "authentication type '%s'", - s->token->s); - crStopV; + http_auth_details_free(auth); } } } while (s->header->len > 0); @@ -513,6 +599,16 @@ static void proxy_http_process_queue(ProxyNegotiator *pn) crStopV; } + /* If the best we can do is report some kind of error from + * a Proxy-Auth header (or an error saying there wasn't + * one at all), and no successful parsing of an auth + * header superseded that, then just throw that error and + * die. */ + if (s->next_auth->auth_type == AUTH_ERROR) { + pn->error = dupstr(s->next_auth->error->s); + crStopV; + } + /* If we have auth details from the Conf and haven't tried * them yet, that's our first step. */ if (s->try_auth_from_conf) { @@ -524,7 +620,7 @@ static void proxy_http_process_queue(ProxyNegotiator *pn) * header, that means we _don't_ need to request a new * password yet; just try again with the existing details * and the fresh nonce it sent us. */ - if (s->digest_nonce_was_stale) + if (s->next_auth->digest_nonce_was_stale) continue; /* Either we never had a password in the first place, or -- cgit v1.2.3 From f91118780fc3f507355419662b5a00908da99c15 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Tue, 21 Dec 2021 09:49:58 +0000 Subject: Put all the docs formats into the tarball. Colin points out that in the migration to cmake, I accidentally stopped putting some of the pre-built docs in the tarball - only the man pages are still there. --- Buildscr | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Buildscr b/Buildscr index f828ecdb..a920b2f0 100644 --- a/Buildscr +++ b/Buildscr @@ -126,6 +126,9 @@ in . do mkdir docbuild in docbuild do cmake ../putty/doc in docbuild do make -j$(nproc) VERBOSE=1 in putty/doc do cp ../../docbuild/*.1 . +in putty/doc do cp ../../docbuild/puttydoc.txt . +in putty/doc do cp ../../docbuild/putty.chm . +in putty/doc do cp -r ../../docbuild/html . in putty do ./mksrcarc.sh in putty do ./mkunxarc.sh '$(Uxarcsuffix)' -- cgit v1.2.3 From 120723bf4011f492163a489cb530c149d09c92cf Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Tue, 21 Dec 2021 10:53:41 +0000 Subject: GTK: allow Event Log list box to grow vertically. Now, when you resize the Event Log window, the list box grows in both directions. Previously, as a side effect of the Columns-based layout, it grew only horizontally. I've arranged this by adding an extra wrinkle to the Columns layout system, which allows you to tag _exactly one_ widget in the whole container as the 'vexpand' widget. When the Columns is size-allocated taller than its minimum height, the vexpand widget is given all the extra space. This technique ports naturally across all versions of GTK (since the hard part is done in my own code). But it's limited: you can't set more than one widget to be vexpand (which saves having to figure out whether they're side by side and can expand in parallel, or vertically separated and each have to get half the available extra space, etc). And in complex layouts where the widget you really want to expand is in a sub-Columns, there's no system for recursively searching down to find it. In other words, this is a one-shot bodge for the Event Log, and it will want more work if we ever plan to extend it to list boxes in the main config dialog. --- unix/columns.c | 29 +++++++++++++++++++++++++++-- unix/columns.h | 2 ++ unix/dialog.c | 4 ++++ 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/unix/columns.c b/unix/columns.c index d1c0cb28..e53d4a06 100644 --- a/unix/columns.c +++ b/unix/columns.c @@ -358,6 +358,9 @@ static void columns_remove(GtkContainer *container, GtkWidget *widget) gtk_widget_queue_resize(GTK_WIDGET(container)); } + if (cols->vexpand == child) + cols->vexpand = NULL; + g_free(child); if (was_visible) gtk_widget_queue_resize(GTK_WIDGET(container)); @@ -555,6 +558,18 @@ void columns_taborder_last(Columns *cols, GtkWidget *widget) } } +void columns_vexpand(Columns *cols, GtkWidget *widget) +{ + g_return_if_fail(cols != NULL); + g_return_if_fail(IS_COLUMNS(cols)); + g_return_if_fail(widget != NULL); + + ColumnsChild *child = columns_find_child(cols, widget); + g_return_if_fail(child != NULL); + + cols->vexpand = child; +} + /* * Override GtkContainer's focus movement so the user can * explicitly specify the tab order. @@ -873,7 +888,14 @@ static void columns_alloc_vert(Columns *cols, gint ourheight, { ColumnsChild *child; GList *children; - gint i, ncols, colspan, *colypos, realheight, fakeheight; + gint i, ncols, colspan, *colypos, realheight, fakeheight, vexpand_extra; + + if (cols->vexpand) { + gint minheight = columns_compute_height(cols, get_height); + vexpand_extra = ourheight - minheight; + } else { + vexpand_extra = 0; + } ncols = 1; /* As in size_request, colypos is the lowest y reached in each column. */ @@ -900,7 +922,10 @@ static void columns_alloc_vert(Columns *cols, gint ourheight, if (!gtk_widget_get_visible(child->widget)) continue; - realheight = fakeheight = get_height(child); + realheight = get_height(child); + if (child == cols->vexpand) + realheight += vexpand_extra; + fakeheight = realheight; if (child->same_height_as) { gint childheight2 = get_height(child->same_height_as); if (fakeheight < childheight2) diff --git a/unix/columns.h b/unix/columns.h index a850a166..d2d58c4a 100644 --- a/unix/columns.h +++ b/unix/columns.h @@ -30,6 +30,7 @@ struct Columns_tag { /* private after here */ GList *children; /* this holds ColumnsChild structures */ GList *taborder; /* this just holds GtkWidgets */ + ColumnsChild *vexpand; gint spacing; }; @@ -57,6 +58,7 @@ void columns_add(Columns *cols, GtkWidget *child, void columns_taborder_last(Columns *cols, GtkWidget *child); void columns_force_left_align(Columns *cols, GtkWidget *child); void columns_force_same_height(Columns *cols, GtkWidget *ch1, GtkWidget *ch2); +void columns_vexpand(Columns *cols, GtkWidget *child); #ifdef __cplusplus } diff --git a/unix/dialog.c b/unix/dialog.c index 9ba1e164..84a5762f 100644 --- a/unix/dialog.c +++ b/unix/dialog.c @@ -4050,6 +4050,10 @@ void showeventlog(eventlog_stuff *es, void *parentwin) "QUITE LONG 'COS SSH LOG ENTRIES ARE WIDE"), -1); our_dialog_add_to_content_area(GTK_WINDOW(window), w1, true, true, 0); + { + struct uctrl *uc = dlg_find_byctrl(&es->dp, es->listctrl); + columns_vexpand(COLUMNS(w1), uc->toplevel); + } gtk_widget_show(w1); es->dp.data = es; -- cgit v1.2.3 From 4944b4ddd5dfb00c2b573887c6f4919c40c9391a Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Tue, 21 Dec 2021 13:25:19 +0000 Subject: Remove duplicated string-literal formatter in Telnet proxy. Now it's done using the same code as in write_c_string_literal(), by means of factoring the latter into a version that targets any old BinarySink and a convenience wrapper taking a FILE *. --- marshal.h | 5 +++++ proxy/telnet.c | 19 +------------------ utils/write_c_string_literal.c | 30 +++++++++++++++++++----------- 3 files changed, 25 insertions(+), 29 deletions(-) diff --git a/marshal.h b/marshal.h index 515f0e53..4d5b0075 100644 --- a/marshal.h +++ b/marshal.h @@ -152,6 +152,10 @@ struct BinarySink { #define put_fmtv(bs, fmt, ap) \ BinarySink_put_fmtv(BinarySink_UPCAST(bs), fmt, ap) +/* More complicated function implemented in write_c_string_literal.c */ +#define put_c_string_literal(bs, str) \ + BinarySink_put_c_string_literal(BinarySink_UPCAST(bs), str) + /* * The underlying real C functions that implement most of those * macros. Generally you won't want to call these directly, because @@ -180,6 +184,7 @@ void BinarySink_put_mp_ssh1(BinarySink *bs, mp_int *x); void BinarySink_put_mp_ssh2(BinarySink *bs, mp_int *x); void BinarySink_put_fmt(BinarySink *, const char *fmt, ...) PRINTF_LIKE(2, 3); void BinarySink_put_fmtv(BinarySink *, const char *fmt, va_list ap); +void BinarySink_put_c_string_literal(BinarySink *, ptrlen); /* ---------------------------------------------------------------------- */ diff --git a/proxy/telnet.c b/proxy/telnet.c index e0a54450..1b4a5326 100644 --- a/proxy/telnet.c +++ b/proxy/telnet.c @@ -336,25 +336,8 @@ static void proxy_telnet_process_queue(ProxyNegotiator *pn) pn->ps->remote_addr, pn->ps->remote_port, s->conf, NULL); strbuf *logmsg = strbuf_new(); - const char *in; - put_datapl(logmsg, PTRLEN_LITERAL("Sending Telnet proxy command: ")); - - for (in = censored_cmd; *in; in++) { - if (*in == '\n') { - put_datapl(logmsg, PTRLEN_LITERAL("\\n")); - } else if (*in == '\r') { - put_datapl(logmsg, PTRLEN_LITERAL("\\r")); - } else if (*in == '\t') { - put_datapl(logmsg, PTRLEN_LITERAL("\\t")); - } else if (*in == '\\') { - put_datapl(logmsg, PTRLEN_LITERAL("\\\\")); - } else if (0x20 <= *in && *in < 0x7F) { - put_byte(logmsg, *in); - } else { - put_fmt(logmsg, "\\x%02X", (unsigned)*in & 0xFF); - } - } + put_c_string_literal(logmsg, ptrlen_from_asciz(censored_cmd)); plug_log(pn->ps->plug, PLUGLOG_PROXY_MSG, NULL, 0, logmsg->s, 0); strbuf_free(logmsg); diff --git a/utils/write_c_string_literal.c b/utils/write_c_string_literal.c index 6415c287..e8b2f53b 100644 --- a/utils/write_c_string_literal.c +++ b/utils/write_c_string_literal.c @@ -1,31 +1,39 @@ /* - * Write data to a file in the form of a C string literal, with any - * non-printable-ASCII character escaped appropriately. + * Write data to a file or BinarySink in the form of a C string + * literal, with any non-printable-ASCII character escaped + * appropriately. */ #include "defs.h" #include "misc.h" -void write_c_string_literal(FILE *fp, ptrlen str) +void BinarySink_put_c_string_literal(BinarySink *bs, ptrlen str) { for (const char *p = str.ptr; p < (const char *)str.ptr + str.len; p++) { char c = *p; if (c == '\n') - fputs("\\n", fp); + put_datalit(bs, "\\n"); else if (c == '\r') - fputs("\\r", fp); + put_datalit(bs, "\\r"); else if (c == '\t') - fputs("\\t", fp); + put_datalit(bs, "\\t"); else if (c == '\b') - fputs("\\b", fp); + put_datalit(bs, "\\b"); else if (c == '\\') - fputs("\\\\", fp); + put_datalit(bs, "\\\\"); else if (c == '"') - fputs("\\\"", fp); + put_datalit(bs, "\\\""); else if (c >= 32 && c <= 126) - fputc(c, fp); + put_byte(bs, c); else - fprintf(fp, "\\%03o", (unsigned char)c); + put_fmt(bs, "\\%03o", (unsigned)c & 0xFFU); } } + +void write_c_string_literal(FILE *fp, ptrlen str) +{ + stdio_sink s; + stdio_sink_init(&s, fp); + put_c_string_literal(&s, str); +} -- cgit v1.2.3 From 48b7ef21a104e66035bd75566a8d96f0e07b4c14 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Tue, 21 Dec 2021 13:35:51 +0000 Subject: Pass an Interactor to platform_new_connection. This will mean that platform-specific proxy types will also be able to set themselves up as child Interactors and prompt the user interactively for passwords and the like. NFC: nothing uses the new parameter yet. --- network.h | 2 +- proxy/pproxy.c | 2 +- proxy/proxy.c | 2 +- unix/local-proxy.c | 2 +- windows/local-proxy.c | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/network.h b/network.h index d144d952..551d0be2 100644 --- a/network.h +++ b/network.h @@ -195,7 +195,7 @@ SockAddr *name_lookup(const char *host, int port, char **canonicalname, Socket *platform_new_connection(SockAddr *addr, const char *hostname, int port, bool privport, bool oobinline, bool nodelay, bool keepalive, - Plug *plug, Conf *conf); + Plug *plug, Conf *conf, Interactor *itr); /* callback for SSH jump-host proxying */ Socket *sshproxy_new_connection(SockAddr *addr, const char *hostname, diff --git a/proxy/pproxy.c b/proxy/pproxy.c index 4b08606e..1712ae8c 100644 --- a/proxy/pproxy.c +++ b/proxy/pproxy.c @@ -11,7 +11,7 @@ Socket *platform_new_connection(SockAddr *addr, const char *hostname, int port, int privport, int oobinline, int nodelay, int keepalive, - Plug *plug, Conf *conf) + Plug *plug, Conf *conf, Interactor *itr) { return NULL; } diff --git a/proxy/proxy.c b/proxy/proxy.c index 979f1468..b228bdb7 100644 --- a/proxy/proxy.c +++ b/proxy/proxy.c @@ -486,7 +486,7 @@ Socket *new_connection(SockAddr *addr, const char *hostname, if ((sret = platform_new_connection(addr, hostname, port, privport, oobinline, nodelay, keepalive, - plug, conf)) != NULL) + plug, conf, itr)) != NULL) return sret; ps = snew(ProxySocket); diff --git a/unix/local-proxy.c b/unix/local-proxy.c index f4c98ced..7275bd5a 100644 --- a/unix/local-proxy.c +++ b/unix/local-proxy.c @@ -17,7 +17,7 @@ Socket *platform_new_connection(SockAddr *addr, const char *hostname, int port, bool privport, bool oobinline, bool nodelay, bool keepalive, - Plug *plug, Conf *conf) + Plug *plug, Conf *conf, Interactor *itr) { char *cmd; diff --git a/windows/local-proxy.c b/windows/local-proxy.c index 19ceb726..7e4e7bed 100644 --- a/windows/local-proxy.c +++ b/windows/local-proxy.c @@ -15,7 +15,7 @@ Socket *platform_new_connection(SockAddr *addr, const char *hostname, int port, bool privport, bool oobinline, bool nodelay, bool keepalive, - Plug *plug, Conf *conf) + Plug *plug, Conf *conf, Interactor *itr) { char *cmd; HANDLE us_to_cmd, cmd_from_us; -- cgit v1.2.3 From ca70b1285d0f5ac9dc9654aaf60214d088a42def Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Wed, 22 Dec 2021 09:31:06 +0000 Subject: Allow creating FdSocket/HandleSocket before the fds/handles. Previously, a setup function returning one of these socket types (such as platform_new_connection) had to do all its setup synchronously, because if it was going to call make_fd_socket or make_handle_socket, it had to have the actual fds or HANDLEs ready-made. If some kind of asynchronous operation were needed before those fds become available, there would be no way the function could achieve it, except by becoming a whole extra permanent Socket wrapper layer. Now there is, because you can make an FdSocket when you don't yet have the fds, or a HandleSocket without the HANDLEs. Instead, you provide an instance of the new trait 'DeferredSocketOpener', which is responsible for setting in motion whatever asynchronous setup procedure it needs, and when that finishes, calling back to setup_fd_socket / setup_handle_socket to provide the missing pieces. In the meantime, the FdSocket or HandleSocket will sit there inertly, buffering any data the client might eagerly hand it via sk_write(), and waiting for its setup to finish. When it does finish, buffered data will be released. In FdSocket, this is easy enough, because we were doing our own buffering anyway - we called the uxsel system to find out when the fds were readable/writable, and then wrote to them from our own bufchain. So more or less all I had to do was make the try_send function do nothing if the setup phase wasn't finished yet. In HandleSocket, on the other hand, we're passing all our data to the underlying handle-io.c system, and making _that_ deferrable in the same way would be much more painful, because that's the place where the scary threads live. So instead I've arranged it by replacing the whole vtable, so that a deferred HandleSocket and a normal HandleSocket are effectively separate trait implementations that can share their state structure. And in fact that state struct itself now contains a big anonymous union, containing one branch to go with each vtable. Nothing yet uses this system, but the next commit will do so. --- defs.h | 2 + network.h | 27 ++++++++ unix/fd-socket.c | 66 ++++++++++++++---- unix/platform.h | 3 + windows/handle-socket.c | 180 ++++++++++++++++++++++++++++++++++++++++++------ windows/platform.h | 4 ++ 6 files changed, 245 insertions(+), 37 deletions(-) diff --git a/defs.h b/defs.h index c30ac5cf..039341fe 100644 --- a/defs.h +++ b/defs.h @@ -98,6 +98,8 @@ typedef struct SockAddr SockAddr; typedef struct Socket Socket; typedef struct Plug Plug; typedef struct SocketPeerInfo SocketPeerInfo; +typedef struct DeferredSocketOpener DeferredSocketOpener; +typedef struct DeferredSocketOpenerVtable DeferredSocketOpenerVtable; typedef struct Backend Backend; typedef struct BackendVtable BackendVtable; diff --git a/network.h b/network.h index 551d0be2..5d0112b0 100644 --- a/network.h +++ b/network.h @@ -402,4 +402,31 @@ void psb_init(ProxyStderrBuf *psb); void log_proxy_stderr( Plug *plug, ProxyStderrBuf *psb, const void *vdata, size_t len); +/* ---------------------------------------------------------------------- + * The DeferredSocketOpener trait. This is a thing that some Socket + * implementations may choose to own if they need to delay actually + * setting up the underlying connection. For example, sockets used in + * local-proxy handling (Unix FdSocket / Windows HandleSocket) might + * need to do this if they have to prompt the user interactively for + * parts of the command they'll run. + * + * Mostly, a DeferredSocketOpener implementation will keep to itself, + * arrange its own callbacks in order to do whatever setup it needs, + * and when it's ready, call back to its parent Socket via some + * implementation-specific API of its own. So the shared API here + * requires almost nothing: the only thing we need is a free function, + * so that if the owner of a Socket of this kind needs to close it + * before the deferred connection process is finished, the Socket can + * also clean up the DeferredSocketOpener dangling off it. + */ + +struct DeferredSocketOpener { + const DeferredSocketOpenerVtable *vt; +}; +struct DeferredSocketOpenerVtable { + void (*free)(DeferredSocketOpener *); +}; +static inline void deferred_socket_opener_free(DeferredSocketOpener *dso) +{ dso->vt->free(dso); } + #endif diff --git a/unix/fd-socket.c b/unix/fd-socket.c index 013c5361..85dbc7c0 100644 --- a/unix/fd-socket.c +++ b/unix/fd-socket.c @@ -14,7 +14,8 @@ #include "network.h" typedef struct FdSocket { - int outfd, infd, inerrfd; + int outfd, infd, inerrfd; /* >= 0 if socket is open */ + DeferredSocketOpener *opener; /* non-NULL if not opened yet */ bufchain pending_output_data; bufchain pending_input_data; @@ -115,6 +116,9 @@ static void fdsocket_close(Socket *s) { FdSocket *fds = container_of(s, FdSocket, sock); + if (fds->opener) + deferred_socket_opener_free(fds->opener); + if (fds->outfd >= 0) { del234(fdsocket_by_outfd, fds); uxsel_del(fds->outfd); @@ -165,6 +169,9 @@ static int fdsocket_try_send(FdSocket *fds) { int sent = 0; + if (fds->opener) + return sent; + while (bufchain_size(&fds->pending_output_data) > 0) { ssize_t ret; @@ -326,27 +333,20 @@ static void fdsocket_connect_success_callback(void *ctx) NULL, 0); } -Socket *make_fd_socket(int infd, int outfd, int inerrfd, - SockAddr *addr, int port, Plug *plug) +void setup_fd_socket(Socket *s, int infd, int outfd, int inerrfd) { - FdSocket *fds; + FdSocket *fds = container_of(s, FdSocket, sock); + assert(fds->sock.vt == &FdSocket_sockvt); - fds = snew(FdSocket); - fds->sock.vt = &FdSocket_sockvt; - fds->addr = addr; - fds->port = port; - fds->plug = plug; - fds->outgoingeof = EOF_NO; - fds->pending_error = 0; + if (fds->opener) { + deferred_socket_opener_free(fds->opener); + fds->opener = NULL; + } fds->infd = infd; fds->outfd = outfd; fds->inerrfd = inerrfd; - bufchain_init(&fds->pending_input_data); - bufchain_init(&fds->pending_output_data); - psb_init(&fds->psb); - if (fds->outfd >= 0) { if (!fdsocket_by_outfd) fdsocket_by_outfd = newtree234(fdsocket_outfd_cmp); @@ -369,6 +369,42 @@ Socket *make_fd_socket(int infd, int outfd, int inerrfd, } queue_toplevel_callback(fdsocket_connect_success_callback, fds); +} + +static FdSocket *make_fd_socket_internal(SockAddr *addr, int port, Plug *plug) +{ + FdSocket *fds; + fds = snew(FdSocket); + fds->sock.vt = &FdSocket_sockvt; + fds->addr = addr; + fds->port = port; + fds->plug = plug; + fds->outgoingeof = EOF_NO; + fds->pending_error = 0; + + fds->opener = NULL; + fds->infd = fds->outfd = fds->inerrfd = -1; + + bufchain_init(&fds->pending_input_data); + bufchain_init(&fds->pending_output_data); + psb_init(&fds->psb); + + return fds; +} + +Socket *make_fd_socket(int infd, int outfd, int inerrfd, + SockAddr *addr, int port, Plug *plug) +{ + FdSocket *fds = make_fd_socket_internal(addr, port, plug); + setup_fd_socket(&fds->sock, infd, outfd, inerrfd); + return &fds->sock; +} + +Socket *make_deferred_fd_socket(DeferredSocketOpener *opener, + SockAddr *addr, int port, Plug *plug) +{ + FdSocket *fds = make_fd_socket_internal(addr, port, plug); + fds->opener = opener; return &fds->sock; } diff --git a/unix/platform.h b/unix/platform.h index 79b171ca..86996b3d 100644 --- a/unix/platform.h +++ b/unix/platform.h @@ -382,6 +382,9 @@ bool so_peercred(int fd, int *pid, int *uid, int *gid); */ Socket *make_fd_socket(int infd, int outfd, int inerrfd, SockAddr *addr, int port, Plug *plug); +Socket *make_deferred_fd_socket(DeferredSocketOpener *opener, + SockAddr *addr, int port, Plug *plug); +void setup_fd_socket(Socket *s, int infd, int outfd, int inerrfd); /* * Default font setting, which can vary depending on NOT_X_WINDOWS. diff --git a/windows/handle-socket.c b/windows/handle-socket.c index 32479d64..8c13d686 100644 --- a/windows/handle-socket.c +++ b/windows/handle-socket.c @@ -11,32 +11,51 @@ #include "putty.h" #include "network.h" +/* + * Freezing one of these sockets is a slightly fiddly business, + * because the reads from the handle are happening in a separate + * thread as blocking system calls and so once one is in progress it + * can't sensibly be interrupted. Hence, after the user tries to + * freeze one of these sockets, it's unavoidable that we may receive + * one more load of data before we manage to get winhandl.c to stop + * reading. + */ +typedef enum HandleSocketFreezeState { + UNFROZEN, /* reading as normal */ + FREEZING, /* have been set to frozen but winhandl is still reading */ + FROZEN, /* really frozen - winhandl has been throttled */ + THAWING /* we're gradually releasing our remaining data */ +} HandleSocketFreezeState; + typedef struct HandleSocket { - HANDLE send_H, recv_H, stderr_H; - struct handle *send_h, *recv_h, *stderr_h; + union { + struct { + HANDLE send_H, recv_H, stderr_H; + struct handle *send_h, *recv_h, *stderr_h; - /* - * Freezing one of these sockets is a slightly fiddly business, - * because the reads from the handle are happening in a separate - * thread as blocking system calls and so once one is in progress - * it can't sensibly be interrupted. Hence, after the user tries - * to freeze one of these sockets, it's unavoidable that we may - * receive one more load of data before we manage to get - * winhandl.c to stop reading. - */ - enum { - UNFROZEN, /* reading as normal */ - FREEZING, /* have been set to frozen but winhandl is still reading */ - FROZEN, /* really frozen - winhandl has been throttled */ - THAWING /* we're gradually releasing our remaining data */ - } frozen; - /* We buffer data here if we receive it from winhandl while frozen. */ - bufchain inputdata; + HandleSocketFreezeState frozen; + /* We buffer data here if we receive it from winhandl + * while frozen. */ + bufchain inputdata; - /* Handle logging proxy error messages from stderr_H, if we have one. */ - ProxyStderrBuf psb; + /* Handle logging proxy error messages from stderr_H, if + * we have one */ + ProxyStderrBuf psb; - bool defer_close, deferred_close; /* in case of re-entrance */ + bool defer_close, deferred_close; /* in case of re-entrance */ + }; + struct { + DeferredSocketOpener *opener; + + /* We buffer data here if we receive it via sk_write + * before the socket is opened. */ + bufchain outputdata; + + bool output_eof_pending; + + bool start_frozen; + }; + }; char *error; @@ -348,6 +367,7 @@ Socket *make_handle_socket(HANDLE send_H, HANDLE recv_H, HANDLE stderr_H, hs->port = port; hs->plug = plug; hs->error = NULL; + hs->frozen = UNFROZEN; bufchain_init(&hs->inputdata); psb_init(&hs->psb); @@ -367,3 +387,119 @@ Socket *make_handle_socket(HANDLE send_H, HANDLE recv_H, HANDLE stderr_H, return &hs->sock; } + +static void sk_handle_deferred_close(Socket *s) +{ + HandleSocket *hs = container_of(s, HandleSocket, sock); + + deferred_socket_opener_free(hs->opener); + bufchain_clear(&hs->outputdata); + + if (hs->addr) + sk_addr_free(hs->addr); + + delete_callbacks_for_context(hs); + + sfree(hs); +} + +static size_t sk_handle_deferred_write(Socket *s, const void *data, size_t len) +{ + HandleSocket *hs = container_of(s, HandleSocket, sock); + assert(!hs->output_eof_pending); + bufchain_add(&hs->outputdata, data, len); + return bufchain_size(&hs->outputdata); +} + +static void sk_handle_deferred_write_eof(Socket *s) +{ + HandleSocket *hs = container_of(s, HandleSocket, sock); + assert(!hs->output_eof_pending); + hs->output_eof_pending = true; +} + +static void sk_handle_deferred_set_frozen(Socket *s, bool is_frozen) +{ + HandleSocket *hs = container_of(s, HandleSocket, sock); + hs->frozen = is_frozen; +} + +static SocketPeerInfo *sk_handle_deferred_peer_info(Socket *s) +{ + return NULL; +} + +static const SocketVtable HandleSocket_deferred_sockvt = { + .plug = sk_handle_plug, + .close = sk_handle_deferred_close, + .write = sk_handle_deferred_write, + .write_oob = sk_handle_deferred_write, + .write_eof = sk_handle_deferred_write_eof, + .set_frozen = sk_handle_deferred_set_frozen, + .socket_error = sk_handle_socket_error, + .peer_info = sk_handle_deferred_peer_info, +}; + +Socket *make_deferred_handle_socket(DeferredSocketOpener *opener, + SockAddr *addr, int port, Plug *plug) +{ + HandleSocket *hs = snew(HandleSocket); + hs->sock.vt = &HandleSocket_deferred_sockvt; + hs->addr = addr; + hs->port = port; + hs->plug = plug; + hs->error = NULL; + + hs->opener = opener; + bufchain_init(&hs->outputdata); + hs->output_eof_pending = false; + hs->start_frozen = false; + + return &hs->sock; +} + +void setup_handle_socket(Socket *s, HANDLE send_H, HANDLE recv_H, + HANDLE stderr_H, bool overlapped) +{ + HandleSocket *hs = container_of(s, HandleSocket, sock); + assert(hs->sock.vt == &HandleSocket_deferred_sockvt); + + int flags = (overlapped ? HANDLE_FLAG_OVERLAPPED : 0); + + struct handle *recv_h = handle_input_new( + recv_H, handle_gotdata, hs, flags); + struct handle *send_h = handle_output_new( + send_H, handle_sentdata, hs, flags); + struct handle *stderr_h = !stderr_H ? NULL : handle_input_new( + stderr_H, handle_stderr, hs, flags); + + while (bufchain_size(&hs->outputdata)) { + ptrlen data = bufchain_prefix(&hs->outputdata); + handle_write(send_h, data.ptr, data.len); + bufchain_consume(&hs->outputdata, data.len); + } + + if (hs->output_eof_pending) + handle_write_eof(send_h); + + bool start_frozen = hs->start_frozen; + + deferred_socket_opener_free(hs->opener); + bufchain_clear(&hs->outputdata); + + hs->sock.vt = &HandleSocket_sockvt; + hs->frozen = start_frozen ? FREEZING : UNFROZEN; + bufchain_init(&hs->inputdata); + psb_init(&hs->psb); + + hs->recv_H = recv_H; + hs->recv_h = recv_h; + hs->send_H = send_H; + hs->send_h = send_h; + hs->stderr_H = stderr_H; + hs->stderr_h = stderr_h; + + hs->defer_close = hs->deferred_close = false; + + queue_toplevel_callback(sk_handle_connect_success_callback, hs); +} diff --git a/windows/platform.h b/windows/platform.h index 78ea1e59..660bf590 100644 --- a/windows/platform.h +++ b/windows/platform.h @@ -339,6 +339,10 @@ extern HANDLE winselcli_event; Socket *make_handle_socket(HANDLE send_H, HANDLE recv_H, HANDLE stderr_H, SockAddr *addr, int port, Plug *plug, bool overlapped); /* winhsock */ +Socket *make_deferred_handle_socket(DeferredSocketOpener *opener, + SockAddr *addr, int port, Plug *plug); +void setup_handle_socket(Socket *s, HANDLE send_H, HANDLE recv_H, + HANDLE stderr_H, bool overlapped); Socket *new_named_pipe_client(const char *pipename, Plug *plug); /* winnpc */ Socket *new_named_pipe_listener(const char *pipename, Plug *plug); /* winnps */ -- cgit v1.2.3 From c1ddacf78f75c463035a03371d2477d30ffa92ba Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Wed, 22 Dec 2021 12:03:28 +0000 Subject: Rewrite local-proxy system to allow interactive prompts. This fills in the remaining gap in the interactive prompt rework of the proxy system in general. If you used the Telnet proxy with a command containing %user or %pass, and hadn't filled in those variables in the PuTTY config, then proxy/telnet.c would prompt you at run time to enter the proxy auth details. But the local proxy command, which uses the same format_telnet_command function, would not do that. Now it does! I've implemented this by moving the formatting of the proxy command into a new module proxy/local.c, shared between both the Unix and Windows local-proxy implementations. That module implements a DeferredSocketOpener, which constructs the proxy command (prompting first if necessary), and once it's constructed, hands it to a per-platform function platform_setup_local_proxy(). So each platform-specific proxy function, instead of starting a subprocess there and then and passing its details to make_fd_socket or make_handle_socket, now returns a _deferred_ version of one of those sockets, with the DeferredSocketOpener being the thing in proxy/local.c. When that calls back to platform_setup_local_proxy(), we actually start the subprocess and pass the resulting fds/handles to the deferred socket to un-defer it. A side effect of the rewrite is that when proxy commands are logged in the Event Log, they now get the same amenities as in the Telnet proxy type: the proxy password is sanitised out, and any difficult characters are escaped. --- CMakeLists.txt | 1 + proxy/local.c | 266 ++++++++++++++++++++++++++++++++++++++++++++++++++ proxy/proxy.h | 6 ++ unix/local-proxy.c | 122 +++++++++++------------ windows/local-proxy.c | 62 ++++++------ 5 files changed, 361 insertions(+), 96 deletions(-) create mode 100644 proxy/local.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 6acd9181..3aba5e20 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,6 +39,7 @@ add_library(network STATIC proxy/socks4.c proxy/socks5.c proxy/telnet.c + proxy/local.c proxy/interactor.c) add_library(keygen STATIC diff --git a/proxy/local.c b/proxy/local.c new file mode 100644 index 00000000..278d0b12 --- /dev/null +++ b/proxy/local.c @@ -0,0 +1,266 @@ +/* + * Implement LocalProxyOpener, a centralised system for setting up the + * command string to be run by platform-specific local-subprocess + * proxy types. + * + * The platform-specific local proxy code is expected to use this + * system by calling local_proxy_opener() from + * platform_new_connection(); then using the resulting + * DeferredSocketOpener to make a deferred version of whatever local + * socket type is used for talking to subcommands (Unix FdSocket, + * Windows HandleSocket); then passing the 'Socket *' back to us via + * local_proxy_opener_set_socket(). + * + * The LocalProxyOpener object implemented by this code will set + * itself up as an Interactor if possible, so that it can prompt for + * the proxy username and/or password if they're referred to in the + * command string but not given in the config (exactly as the Telnet + * proxy does). Once it knows the exact command it wants to run - + * whether that was done immediately or after user interaction - it + * calls back to platform_setup_local_proxy() with the full command, + * which is expected to actually start the subprocess and fill in the + * missing details in the deferred socket, freeing the + * LocalProxyOpener as a side effect. + */ + +#include "tree234.h" +#include "putty.h" +#include "network.h" +#include "sshcr.h" +#include "proxy/proxy.h" + +typedef struct LocalProxyOpener { + int crLine; + + Socket *socket; + char *formatted_cmd; + Plug *plug; + SockAddr *addr; + int port; + Conf *conf; + + Interactor *clientitr; + LogPolicy *clientlp; + Seat *clientseat; + prompts_t *prompts; + int username_prompt_index, password_prompt_index; + + Interactor interactor; + DeferredSocketOpener opener; +} LocalProxyOpener; + +static void local_proxy_opener_free(DeferredSocketOpener *opener) +{ + LocalProxyOpener *lp = container_of(opener, LocalProxyOpener, opener); + burnstr(lp->formatted_cmd); + if (lp->prompts) + free_prompts(lp->prompts); + sk_addr_free(lp->addr); + conf_free(lp->conf); + sfree(lp); +} + +static const DeferredSocketOpenerVtable LocalProxyOpener_openervt = { + .free = local_proxy_opener_free, +}; + +static char *local_proxy_opener_description(Interactor *itr) +{ + return dupstr("connection via local command"); +} + +static LogPolicy *local_proxy_opener_logpolicy(Interactor *itr) +{ + LocalProxyOpener *lp = container_of(itr, LocalProxyOpener, interactor); + return lp->clientlp; +} + +static Seat *local_proxy_opener_get_seat(Interactor *itr) +{ + LocalProxyOpener *lp = container_of(itr, LocalProxyOpener, interactor); + return lp->clientseat; +} + +static void local_proxy_opener_set_seat(Interactor *itr, Seat *seat) +{ + LocalProxyOpener *lp = container_of(itr, LocalProxyOpener, interactor); + lp->clientseat = seat; +} + +static const InteractorVtable LocalProxyOpener_interactorvt = { + .description = local_proxy_opener_description, + .logpolicy = local_proxy_opener_logpolicy, + .get_seat = local_proxy_opener_get_seat, + .set_seat = local_proxy_opener_set_seat, +}; + +static void local_proxy_opener_cleanup_interactor(LocalProxyOpener *lp) +{ + if (lp->clientseat) { + interactor_return_seat(lp->clientitr); + lp->clientitr = NULL; + lp->clientseat = NULL; + } +} + +static void local_proxy_opener_coroutine(void *vctx) +{ + LocalProxyOpener *lp = (LocalProxyOpener *)vctx; + + crBegin(lp->crLine); + + /* + * Make an initial attempt to figure out the command we want, and + * see if it tried to include a username or password that we don't + * have. + */ + { + unsigned flags; + lp->formatted_cmd = format_telnet_command( + lp->addr, lp->port, lp->conf, &flags); + + if (lp->clientseat && (flags & (TELNET_CMD_MISSING_USERNAME | + TELNET_CMD_MISSING_PASSWORD))) { + burnstr(lp->formatted_cmd); + lp->formatted_cmd = NULL; + + /* + * We're missing at least one of the two parts, and we + * have an Interactor we can use to prompt for them, so + * try it. + */ + lp->prompts = new_prompts(); + lp->prompts->callback = local_proxy_opener_coroutine; + lp->prompts->callback_ctx = lp; + lp->prompts->to_server = true; + lp->prompts->from_server = false; + lp->prompts->name = dupstr("Local proxy authentication"); + if (flags & TELNET_CMD_MISSING_USERNAME) { + lp->username_prompt_index = lp->prompts->n_prompts; + add_prompt(lp->prompts, dupstr("Proxy username: "), true); + } else { + lp->username_prompt_index = -1; + } + if (flags & TELNET_CMD_MISSING_PASSWORD) { + lp->password_prompt_index = lp->prompts->n_prompts; + add_prompt(lp->prompts, dupstr("Proxy password: "), false); + } else { + lp->password_prompt_index = -1; + } + + while (true) { + int prompt_result = seat_get_userpass_input( + interactor_announce(&lp->interactor), lp->prompts); + if (prompt_result > 0) { + break; + } else if (prompt_result == 0) { + local_proxy_opener_cleanup_interactor(lp); + plug_closing_user_abort(lp->plug); + /* That will have freed us, so we must just return + * without calling any crStop */ + return; + } + crReturnV; + } + + if (lp->username_prompt_index != -1) { + conf_set_str( + lp->conf, CONF_proxy_username, + prompt_get_result_ref( + lp->prompts->prompts[lp->username_prompt_index])); + } + + if (lp->password_prompt_index != -1) { + conf_set_str( + lp->conf, CONF_proxy_password, + prompt_get_result_ref( + lp->prompts->prompts[lp->password_prompt_index])); + } + + free_prompts(lp->prompts); + lp->prompts = NULL; + } + + /* + * Now format the command a second time, with the results of + * those prompts written into lp->conf. + */ + lp->formatted_cmd = format_telnet_command( + lp->addr, lp->port, lp->conf, NULL); + } + + /* + * Log the command, with some changes. Firstly, we regenerate it + * with the password masked; secondly, we escape control + * characters so that the log message is printable. + */ + conf_set_str(lp->conf, CONF_proxy_password, "*password*"); + { + char *censored_cmd = format_telnet_command( + lp->addr, lp->port, lp->conf, NULL); + + strbuf *logmsg = strbuf_new(); + put_datapl(logmsg, PTRLEN_LITERAL("Starting local proxy command: ")); + put_c_string_literal(logmsg, ptrlen_from_asciz(censored_cmd)); + + plug_log(lp->plug, PLUGLOG_PROXY_MSG, NULL, 0, logmsg->s, 0); + strbuf_free(logmsg); + sfree(censored_cmd); + } + + /* + * Now we're ready to actually do the platform-specific socket + * setup. + */ + char *cmd = lp->formatted_cmd; + lp->formatted_cmd = NULL; + + local_proxy_opener_cleanup_interactor(lp); + + char *error_msg = platform_setup_local_proxy(lp->socket, cmd); + burnstr(cmd); + + if (error_msg) { + plug_closing_error(lp->plug, error_msg); + sfree(error_msg); + } else { + /* If error_msg was NULL, there was no error in setup, + * which means that platform_setup_local_proxy will have + * called back to free us. So return without calling any + * crStop. */ + return; + } + + crFinishV; +} + +DeferredSocketOpener *local_proxy_opener( + SockAddr *addr, int port, Plug *plug, Conf *conf, Interactor *itr) +{ + LocalProxyOpener *lp = snew(LocalProxyOpener); + memset(lp, 0, sizeof(*lp)); + lp->plug = plug; + lp->opener.vt = &LocalProxyOpener_openervt; + lp->interactor.vt = &LocalProxyOpener_interactorvt; + lp->addr = sk_addr_dup(addr); + lp->port = port; + lp->conf = conf_copy(conf); + + if (itr) { + lp->clientitr = itr; + interactor_set_child(lp->clientitr, &lp->interactor); + lp->clientlp = interactor_logpolicy(lp->clientitr); + lp->clientseat = interactor_borrow_seat(lp->clientitr); + } + + return &lp->opener; +} + +void local_proxy_opener_set_socket(DeferredSocketOpener *opener, + Socket *socket) +{ + assert(opener->vt == &LocalProxyOpener_openervt); + LocalProxyOpener *lp = container_of(opener, LocalProxyOpener, opener); + lp->socket = socket; + queue_toplevel_callback(local_proxy_opener_coroutine, lp); +} diff --git a/proxy/proxy.h b/proxy/proxy.h index f44a0c55..aacc1cb1 100644 --- a/proxy/proxy.h +++ b/proxy/proxy.h @@ -105,6 +105,12 @@ prompts_t *proxy_new_prompts(ProxySocket *ps); char *format_telnet_command(SockAddr *addr, int port, Conf *conf, unsigned *flags_out); +DeferredSocketOpener *local_proxy_opener( + SockAddr *addr, int port, Plug *plug, Conf *conf, Interactor *itr); +void local_proxy_opener_set_socket(DeferredSocketOpener *opener, + Socket *socket); +char *platform_setup_local_proxy(Socket *socket, const char *cmd); + #include "cproxy.h" #endif diff --git a/unix/local-proxy.c b/unix/local-proxy.c index 7275bd5a..0f52931f 100644 --- a/unix/local-proxy.c +++ b/unix/local-proxy.c @@ -14,79 +14,73 @@ #include "network.h" #include "proxy/proxy.h" -Socket *platform_new_connection(SockAddr *addr, const char *hostname, - int port, bool privport, - bool oobinline, bool nodelay, bool keepalive, - Plug *plug, Conf *conf, Interactor *itr) +char *platform_setup_local_proxy(Socket *socket, const char *cmd) { - char *cmd; - - int to_cmd_pipe[2], from_cmd_pipe[2], cmd_err_pipe[2], pid, proxytype; - int infd, outfd, inerrfd; - - proxytype = conf_get_int(conf, CONF_proxy_type); - if (proxytype != PROXY_CMD && proxytype != PROXY_FUZZ) - return NULL; - - if (proxytype == PROXY_CMD) { - cmd = format_telnet_command(addr, port, conf, NULL); + /* + * Create the pipes to the proxy command, and spawn the proxy + * command process. + */ + int to_cmd_pipe[2], from_cmd_pipe[2], cmd_err_pipe[2]; + if (pipe(to_cmd_pipe) < 0 || + pipe(from_cmd_pipe) < 0 || + pipe(cmd_err_pipe) < 0) { + return dupprintf("pipe: %s", strerror(errno)); + } + cloexec(to_cmd_pipe[1]); + cloexec(from_cmd_pipe[0]); + cloexec(cmd_err_pipe[0]); - { - char *logmsg = dupprintf("Starting local proxy command: %s", cmd); - plug_log(plug, PLUGLOG_PROXY_MSG, NULL, 0, logmsg, 0); - sfree(logmsg); - } + int pid = fork(); + 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]); + dup2(cmd_err_pipe[1], 2); + noncloexec(0); + noncloexec(1); + execl("/bin/sh", "sh", "-c", cmd, (void *)NULL); + _exit(255); + } - /* - * Create the pipes to the proxy command, and spawn the proxy - * command process. - */ - if (pipe(to_cmd_pipe) < 0 || - pipe(from_cmd_pipe) < 0 || - pipe(cmd_err_pipe) < 0) { - sfree(cmd); - return new_error_socket_fmt(plug, "pipe: %s", strerror(errno)); - } - cloexec(to_cmd_pipe[1]); - cloexec(from_cmd_pipe[0]); - cloexec(cmd_err_pipe[0]); + if (pid < 0) { + return dupprintf("fork: %s", strerror(errno)); + } - pid = fork(); - 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]); - dup2(cmd_err_pipe[1], 2); - noncloexec(0); - noncloexec(1); - execl("/bin/sh", "sh", "-c", cmd, (void *)NULL); - _exit(255); - } + close(to_cmd_pipe[0]); + close(from_cmd_pipe[1]); + close(cmd_err_pipe[1]); - sfree(cmd); + setup_fd_socket(socket, from_cmd_pipe[0], to_cmd_pipe[1], cmd_err_pipe[0]); - if (pid < 0) - return new_error_socket_fmt(plug, "fork: %s", strerror(errno)); + return NULL; +} - close(to_cmd_pipe[0]); - close(from_cmd_pipe[1]); - close(cmd_err_pipe[1]); +Socket *platform_new_connection(SockAddr *addr, const char *hostname, + int port, bool privport, + bool oobinline, bool nodelay, bool keepalive, + Plug *plug, Conf *conf, Interactor *itr) +{ + switch (conf_get_int(conf, CONF_proxy_type)) { + case PROXY_CMD: { + DeferredSocketOpener *opener = local_proxy_opener( + addr, port, plug, conf, itr); + Socket *socket = make_deferred_fd_socket(opener, addr, port, plug); + local_proxy_opener_set_socket(opener, socket); + return socket; + } - outfd = to_cmd_pipe[1]; - infd = from_cmd_pipe[0]; - inerrfd = cmd_err_pipe[0]; - } else { - cmd = format_telnet_command(addr, port, conf, NULL); - outfd = open("/dev/null", O_WRONLY); + case PROXY_FUZZ: { + char *cmd = format_telnet_command(addr, port, conf, NULL); + int outfd = open("/dev/null", O_WRONLY); if (outfd == -1) { sfree(cmd); return new_error_socket_fmt( plug, "/dev/null: %s", strerror(errno)); } - infd = open(cmd, O_RDONLY); + int infd = open(cmd, O_RDONLY); if (infd == -1) { Socket *toret = new_error_socket_fmt( plug, "%s: %s", cmd, strerror(errno)); @@ -95,8 +89,10 @@ Socket *platform_new_connection(SockAddr *addr, const char *hostname, return toret; } sfree(cmd); - inerrfd = -1; - } + return make_fd_socket(infd, outfd, -1, addr, port, plug); + } - return make_fd_socket(infd, outfd, inerrfd, addr, port, plug); + default: + return NULL; + } } diff --git a/windows/local-proxy.c b/windows/local-proxy.c index 7e4e7bed..3c4922f0 100644 --- a/windows/local-proxy.c +++ b/windows/local-proxy.c @@ -12,12 +12,8 @@ #include "network.h" #include "proxy/proxy.h" -Socket *platform_new_connection(SockAddr *addr, const char *hostname, - int port, bool privport, - bool oobinline, bool nodelay, bool keepalive, - Plug *plug, Conf *conf, Interactor *itr) +char *platform_setup_local_proxy(Socket *socket, const char *cmd) { - char *cmd; HANDLE us_to_cmd, cmd_from_us; HANDLE us_from_cmd, cmd_to_us; HANDLE us_from_cmd_err, cmd_err_to_us; @@ -25,17 +21,6 @@ Socket *platform_new_connection(SockAddr *addr, const char *hostname, STARTUPINFO si; PROCESS_INFORMATION pi; - if (conf_get_int(conf, CONF_proxy_type) != PROXY_CMD) - return NULL; - - cmd = format_telnet_command(addr, port, conf, NULL); - - { - char *msg = dupprintf("Starting local proxy command: %s", cmd); - plug_log(plug, PLUGLOG_PROXY_MSG, NULL, 0, msg, 0); - sfree(msg); - } - /* * Create the pipes to the proxy command, and spawn the proxy * command process. @@ -44,30 +29,24 @@ Socket *platform_new_connection(SockAddr *addr, const char *hostname, sa.lpSecurityDescriptor = NULL; /* default */ sa.bInheritHandle = true; if (!CreatePipe(&us_from_cmd, &cmd_to_us, &sa, 0)) { - sfree(cmd); - return new_error_socket_fmt( - plug, "Unable to create pipes for proxy command: %s", - win_strerror(GetLastError())); + return dupprintf("Unable to create pipes for proxy command: %s", + win_strerror(GetLastError())); } if (!CreatePipe(&cmd_from_us, &us_to_cmd, &sa, 0)) { - sfree(cmd); CloseHandle(us_from_cmd); CloseHandle(cmd_to_us); - return new_error_socket_fmt( - plug, "Unable to create pipes for proxy command: %s", - win_strerror(GetLastError())); + return dupprintf("Unable to create pipes for proxy command: %s", + win_strerror(GetLastError())); } if (!CreatePipe(&us_from_cmd_err, &cmd_err_to_us, &sa, 0)) { - sfree(cmd); CloseHandle(us_from_cmd); CloseHandle(cmd_to_us); CloseHandle(us_to_cmd); CloseHandle(cmd_from_us); - return new_error_socket_fmt( - plug, "Unable to create pipes for proxy command: %s", - win_strerror(GetLastError())); + return dupprintf("Unable to create pipes for proxy command: %s", + win_strerror(GetLastError())); } SetHandleInformation(us_to_cmd, HANDLE_FLAG_INHERIT, 0); @@ -85,20 +64,37 @@ Socket *platform_new_connection(SockAddr *addr, const char *hostname, si.hStdInput = cmd_from_us; si.hStdOutput = cmd_to_us; si.hStdError = cmd_err_to_us; - CreateProcess(NULL, cmd, NULL, NULL, true, + char *cmd_mutable = dupstr(cmd); /* CreateProcess needs non-const char * */ + CreateProcess(NULL, cmd_mutable, NULL, NULL, true, CREATE_NO_WINDOW | NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi); + sfree(cmd_mutable); CloseHandle(pi.hProcess); CloseHandle(pi.hThread); - sfree(cmd); - CloseHandle(cmd_from_us); CloseHandle(cmd_to_us); if (cmd_err_to_us != NULL) CloseHandle(cmd_err_to_us); - return make_handle_socket(us_to_cmd, us_from_cmd, us_from_cmd_err, - addr, port, plug, false); + setup_handle_socket(socket, us_to_cmd, us_from_cmd, us_from_cmd_err, + false); + + return NULL; +} + +Socket *platform_new_connection(SockAddr *addr, const char *hostname, + int port, bool privport, + bool oobinline, bool nodelay, bool keepalive, + Plug *plug, Conf *conf, Interactor *itr) +{ + if (conf_get_int(conf, CONF_proxy_type) != PROXY_CMD) + return NULL; + + DeferredSocketOpener *opener = local_proxy_opener( + addr, port, plug, conf, itr); + Socket *socket = make_deferred_handle_socket(opener, addr, port, plug); + local_proxy_opener_set_socket(opener, socket); + return socket; } -- cgit v1.2.3 From 831accb2a90b305422742ed2bb3eab5d927cee0e Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 24 Dec 2021 09:56:30 +0000 Subject: Expose openssh_bcrypt() to testcrypt, and test it. I happened to notice in passing that this function doesn't have any tests (although it will have been at least somewhat tested by the cmdgen interop test system). This involved writing a wrapper that passes the passphrase and salt as ptrlens, and I decided it made more sense to make the same change to the original function too and adjust the call sites appropriately. I derived a test case by getting OpenSSH itself to make an encrypted key file, and then using the inputs and output from the password hash operation that decrypted it again. --- crypto/bcrypt.c | 9 ++++----- import.c | 11 +++++------ ssh.h | 3 +-- test/cryptsuite.py | 14 ++++++++++++++ test/testcrypt-func.h | 3 +++ test/testcrypt.c | 9 +++++++++ 6 files changed, 36 insertions(+), 13 deletions(-) diff --git a/crypto/bcrypt.c b/crypto/bcrypt.c index 2b29b037..a4eb384a 100644 --- a/crypto/bcrypt.c +++ b/crypto/bcrypt.c @@ -69,8 +69,7 @@ void bcrypt_genblock(int counter, smemclr(&hashed_salt, sizeof(hashed_salt)); } -void openssh_bcrypt(const char *passphrase, - const unsigned char *salt, int saltbytes, +void openssh_bcrypt(ptrlen passphrase, ptrlen salt, int rounds, unsigned char *out, int outbytes) { unsigned char hashed_passphrase[64]; @@ -80,7 +79,7 @@ void openssh_bcrypt(const char *passphrase, int modulus, residue, i, j, round; /* Hash the passphrase to get the bcrypt key material */ - hash_simple(&ssh_sha512, ptrlen_from_asciz(passphrase), hashed_passphrase); + hash_simple(&ssh_sha512, passphrase, hashed_passphrase); /* We output key bytes in a scattered fashion to meld all output * key blocks into all parts of the output. To do this, we pick a @@ -97,8 +96,8 @@ void openssh_bcrypt(const char *passphrase, * by bcrypt in the following loop */ memset(outblock, 0, sizeof(outblock)); - thissalt = salt; - thissaltbytes = saltbytes; + thissalt = salt.ptr; + thissaltbytes = salt.len; for (round = 0; round < rounds; round++) { bcrypt_genblock(round == 0 ? residue+1 : 0, hashed_passphrase, diff --git a/import.c b/import.c index ccff2c14..41c06a9a 100644 --- a/import.c +++ b/import.c @@ -1363,9 +1363,8 @@ static ssh2_userkey *openssh_new_read( memset(keybuf, 0, keysize); break; case ON_K_BCRYPT: - openssh_bcrypt(passphrase, - key->kdfopts.bcrypt.salt.ptr, - key->kdfopts.bcrypt.salt.len, + openssh_bcrypt(ptrlen_from_asciz(passphrase), + key->kdfopts.bcrypt.salt, key->kdfopts.bcrypt.rounds, keybuf, keysize); break; @@ -1583,9 +1582,9 @@ static bool openssh_new_write( unsigned char keybuf[48]; ssh_cipher *cipher; - openssh_bcrypt(passphrase, - bcrypt_salt, sizeof(bcrypt_salt), bcrypt_rounds, - keybuf, sizeof(keybuf)); + openssh_bcrypt(ptrlen_from_asciz(passphrase), + make_ptrlen(bcrypt_salt, sizeof(bcrypt_salt)), + bcrypt_rounds, keybuf, sizeof(keybuf)); cipher = ssh_cipher_new(&ssh_aes256_sdctr); ssh_cipher_setkey(cipher, keybuf); diff --git a/ssh.h b/ssh.h index 77cddf99..294afada 100644 --- a/ssh.h +++ b/ssh.h @@ -1413,8 +1413,7 @@ void aes256_decrypt_pubkey(const void *key, const void *iv, void des_encrypt_xdmauth(const void *key, void *blk, int len); void des_decrypt_xdmauth(const void *key, void *blk, int len); -void openssh_bcrypt(const char *passphrase, - const unsigned char *salt, int saltbytes, +void openssh_bcrypt(ptrlen passphrase, ptrlen salt, int rounds, unsigned char *out, int outbytes); /* diff --git a/test/cryptsuite.py b/test/cryptsuite.py index e331bcf6..4971a599 100755 --- a/test/cryptsuite.py +++ b/test/cryptsuite.py @@ -2106,6 +2106,20 @@ culpa qui officia deserunt mollit anim id est laborum. "aeae2a21201eef5e347de22c922192e8f46274b0c9d33e965155a91e7686" "9d530e")) + def testOpenSSHBcrypt(self): + # Test case created by making an OpenSSH private key file + # using their own ssh-keygen, then decrypting it successfully + # using PuTTYgen and printing the inputs and outputs to + # openssh_bcrypt in the process. So this output key is known + # to agree with OpenSSH's own answer. + + self.assertEqualBin( + openssh_bcrypt('test passphrase', + unhex('d0c3b40ace4afeaf8c0f81202ae36718'), + 16, 48), + unhex('d78ba86e7273de0e007ab0ba256646823d5c902bc44293ae' + '78547e9a7f629be928cc78ff78a75a4feb7aa6f125079c7d')) + def testRSAVerify(self): def blobs(n, e, d, p, q, iqmp): pubblob = ssh_string(b"ssh-rsa") + ssh2_mpint(e) + ssh2_mpint(n) diff --git a/test/testcrypt-func.h b/test/testcrypt-func.h index b8e53a96..2cb0b3dc 100644 --- a/test/testcrypt-func.h +++ b/test/testcrypt-func.h @@ -447,6 +447,9 @@ FUNC_WRAPPED(val_string, argon2, ARG(argon2flavour, flavour), ARG(uint, mem), ARG(val_string_ptrlen, K), ARG(val_string_ptrlen, X)) FUNC(val_string, argon2_long_hash, ARG(uint, length), ARG(val_string_ptrlen, data)) +FUNC_WRAPPED(val_string, openssh_bcrypt, ARG(val_string_ptrlen, passphrase), + ARG(val_string_ptrlen, salt), ARG(uint, rounds), + ARG(uint, outbytes)) /* * Key generation functions. diff --git a/test/testcrypt.c b/test/testcrypt.c index 5936a248..5e0b73db 100644 --- a/test/testcrypt.c +++ b/test/testcrypt.c @@ -1087,6 +1087,15 @@ strbuf *argon2_wrapper(Argon2Flavour flavour, uint32_t mem, uint32_t passes, return out; } +strbuf *openssh_bcrypt_wrapper(ptrlen passphrase, ptrlen salt, + unsigned rounds, unsigned outbytes) +{ + strbuf *out = strbuf_new(); + openssh_bcrypt(passphrase, salt, rounds, + strbuf_append(out, outbytes), outbytes); + return out; +} + strbuf *get_implementations_commasep(ptrlen alg) { strbuf *out = strbuf_new(); -- cgit v1.2.3 From 60e557575efc6857ade4985db9b70bee15bee98d Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 27 Dec 2021 11:43:31 +0000 Subject: Cosmetic fix: silly parameter name caused by copy-paste. In SSH-1 there's a function that takes a void * that it casts to the state of the login layer. The corresponding function in SSH-2 casts it to the state of a differently named layer, but I had still called the parameter 'loginv'. --- ssh/transport2.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ssh/transport2.c b/ssh/transport2.c index f0975431..3c8fb382 100644 --- a/ssh/transport2.c +++ b/ssh/transport2.c @@ -1815,9 +1815,9 @@ static bool ssh2_transport_timer_update(struct ssh2_transport_state *s, return false; } -void ssh2_transport_dialog_callback(void *loginv, int ret) +void ssh2_transport_dialog_callback(void *vctx, int ret) { - struct ssh2_transport_state *s = (struct ssh2_transport_state *)loginv; + struct ssh2_transport_state *s = (struct ssh2_transport_state *)vctx; s->dlgret = ret; ssh_ppl_process_queue(&s->ppl); } -- cgit v1.2.3 From 88d5bb2a222470f1f615d96384f3701f1406790d Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 27 Dec 2021 13:02:31 +0000 Subject: Cosmetic fix: double indentation in an if statement. Not sure how that happened, but since I've spotted it, let's fix it. --- ssh/transport2.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/ssh/transport2.c b/ssh/transport2.c index 3c8fb382..0ba937f3 100644 --- a/ssh/transport2.c +++ b/ssh/transport2.c @@ -1310,18 +1310,18 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl) } } if (betteralgs) { - /* Use the special warning prompt that lets us provide - * a list of better algorithms */ - s->dlgret = seat_confirm_weak_cached_hostkey( - ppl_get_iseat(&s->ppl), s->hostkey_alg->ssh_id, betteralgs, - ssh2_transport_dialog_callback, s); + /* Use the special warning prompt that lets us provide + * a list of better algorithms */ + s->dlgret = seat_confirm_weak_cached_hostkey( + ppl_get_iseat(&s->ppl), s->hostkey_alg->ssh_id, betteralgs, + ssh2_transport_dialog_callback, s); sfree(betteralgs); } else { - /* If none exist, use the more general 'weak crypto' - * warning prompt */ - s->dlgret = ssh2_transport_confirm_weak_crypto_primitive( - s, "host key type", s->hostkey_alg->ssh_id, - s->hostkey_alg); + /* If none exist, use the more general 'weak crypto' + * warning prompt */ + s->dlgret = ssh2_transport_confirm_weak_crypto_primitive( + s, "host key type", s->hostkey_alg->ssh_id, + s->hostkey_alg); } crMaybeWaitUntilV(s->dlgret >= 0); if (s->dlgret == 0) { -- cgit v1.2.3 From a82ab70b0bf418a7c18d07e0090bbf194f795cbe Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Tue, 28 Dec 2021 15:15:53 +0000 Subject: Fix error handling when command-line password fails. In cmdline_get_passwd_input(), there's a boolean 'tried_once' which is set the first time the function returns the password set by -pw or -pwfile. The idea, as clearly commented in the code, was that if cmdline_get_password_input asks for that password _again_, we return failure, as if the user had refused to make a second attempt. But that wasn't actually what happened, because when we set tried_once to true, we also set cmdline_password to NULL, which causes the second call to the function to behave as if no password was ever provided at all. So after the -pw password failed, we'd fall back to asking interactively. This change moves the check of cmdline_password to after the check of tried_once, restoring the originally intended behaviour: password authentication will now _only_ be done via the pre-set password, if there is one. This seems like an xkcd #1172 kind of change: now that it's been wrong for a while, _someone_ has probably found the unintended behaviour useful, and started relying on it. So it may become necessary to add an option to set the behaviour either way. But for the moment, let's try it the way I originally intended it. --- cmdline.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/cmdline.c b/cmdline.c index 18781469..d87c14d0 100644 --- a/cmdline.c +++ b/cmdline.c @@ -90,7 +90,7 @@ int cmdline_get_passwd_input(prompts_t *p) * passwords), and (currently) we only cope with a password prompt * that comes in a prompt-set on its own. */ - if (!cmdline_password || p->n_prompts != 1 || p->prompts[0]->echo) { + if (p->n_prompts != 1 || p->prompts[0]->echo) { return -1; } @@ -101,6 +101,15 @@ int cmdline_get_passwd_input(prompts_t *p) if (tried_once) return 0; + /* + * If we never had a password available in the first place, we + * can't do anything in any case. (But we delay this test until + * after tried_once, so that after we free cmdline_password below, + * we'll still remember that we _used_ to have one.) + */ + if (!cmdline_password) + return -1; + prompt_set_result(p->prompts[0], cmdline_password); smemclr(cmdline_password, strlen(cmdline_password)); sfree(cmdline_password); -- cgit v1.2.3 From a2ff884512be0cb0ef62aceee5428ba42ca64d9b Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Tue, 28 Dec 2021 17:52:00 +0000 Subject: Richer data type for interactive prompt results. All the seat functions that request an interactive prompt of some kind to the user - both the main seat_get_userpass_input and the various confirmation dialogs for things like host keys - were using a simple int return value, with the general semantics of 0 = "fail", 1 = "proceed" (and in the case of seat_get_userpass_input, answers to the prompts were provided), and -1 = "request in progress, wait for a callback". In this commit I change all those functions' return types to a new struct called SeatPromptResult, whose primary field is an enum replacing those simple integer values. The main purpose is that the enum has not three but _four_ values: the "fail" result has been split into 'user abort' and 'software abort'. The distinction is that a user abort occurs as a result of an interactive UI action, such as the user clicking 'cancel' in a dialog box or hitting ^D or ^C at a terminal password prompt - and therefore, there's no need to display an error message telling the user that the interactive operation has failed, because the user already knows, because they _did_ it. 'Software abort' is from any other cause, where PuTTY is the first to know there was a problem, and has to tell the user. We already had this 'user abort' vs 'software abort' distinction in other parts of the code - the SSH backend has separate termination functions which protocol layers can call. But we assumed that any failure from an interactive prompt request fell into the 'user abort' category, which is not true. A couple of examples: if you configure a host key fingerprint in your saved session via the SSH > Host keys pane, and the server presents a host key that doesn't match it, then verify_ssh_host_key would report that the user had aborted the connection, and feel no need to tell the user what had gone wrong! Similarly, if a password provided on the command line was not accepted, then (after I fixed the semantics of that in the previous commit) the same wrong handling would occur. So now, those Seat prompt functions too can communicate whether the user or the software originated a connection abort. And in the latter case, we also provide an error message to present to the user. Result: in those two example cases (and others), error messages should no longer go missing. Implementation note: to avoid the hassle of having the error message in a SeatPromptResult being a dynamically allocated string (and hence, every recipient of one must always check whether it's non-NULL and free it on every exit path, plus being careful about copying the struct around), I've instead arranged that the structure contains a function pointer and a couple of parameters, so that the string form of the message can be constructed on demand. That way, the only users who need to free it are the ones who actually _asked_ for it in the first place, which is a much smaller set. (This is one of the rare occasions that I regret not having C++'s extra features available in this code base - a unique_ptr or shared_ptr to a string would have been just the thing here, and the compiler would have done all the hard work for me of remembering where to insert the frees!) --- cgtest.c | 6 +- cmdgen.c | 29 ++++-- cmdline.c | 10 +- defs.h | 1 + otherbackends/rlogin.c | 22 +++-- proxy/http.c | 8 +- proxy/local.c | 12 ++- proxy/proxy.c | 10 ++ proxy/proxy.h | 5 +- proxy/socks5.c | 8 +- proxy/sshproxy.c | 25 ++--- proxy/telnet.c | 8 +- putty.h | 152 +++++++++++++++++++++-------- ssh.h | 6 +- ssh/common.c | 35 +++++-- ssh/connection1.c | 2 +- ssh/connection1.h | 2 +- ssh/connection2.c | 2 +- ssh/connection2.h | 2 +- ssh/kex2-client.c | 11 +-- ssh/login1.c | 59 ++++++----- ssh/server.c | 10 +- ssh/transport2.c | 44 ++++----- ssh/transport2.h | 4 +- ssh/userauth2-client.c | 59 ++++++----- stubs/nocmdline.c | 4 +- stubs/noterm.c | 4 +- terminal/terminal.c | 24 ++--- unix/CMakeLists.txt | 1 + unix/console.c | 55 +++++++---- unix/dialog.c | 87 +++++++++++------ unix/pageant.c | 14 ++- unix/platform.h | 14 +-- unix/plink.c | 12 +-- unix/sftp.c | 12 +-- unix/utils/make_spr_sw_abort_errno.c | 22 +++++ unix/window.c | 12 +-- utils/CMakeLists.txt | 2 + utils/make_spr_sw_abort_static.c | 21 ++++ utils/nullseat.c | 18 ++-- utils/prompts.c | 2 +- utils/spr_get_error_message.c | 13 +++ utils/tempseat.c | 14 +-- windows/CMakeLists.txt | 1 + windows/console.c | 56 ++++++----- windows/dialog.c | 26 ++--- windows/platform.h | 14 +-- windows/plink.c | 12 +-- windows/sftp.c | 12 +-- windows/utils/make_spr_sw_abort_winerror.c | 22 +++++ windows/window.c | 14 +-- 51 files changed, 648 insertions(+), 372 deletions(-) create mode 100644 unix/utils/make_spr_sw_abort_errno.c create mode 100644 utils/make_spr_sw_abort_static.c create mode 100644 utils/spr_get_error_message.c create mode 100644 windows/utils/make_spr_sw_abort_winerror.c diff --git a/cgtest.c b/cgtest.c index ba87892c..f0655b98 100644 --- a/cgtest.c +++ b/cgtest.c @@ -65,10 +65,10 @@ char *get_random_data_diagnostic(int len, const char *device) static int nprompts, promptsgot; static const char *prompts[3]; -int console_get_userpass_input_diagnostic(prompts_t *p) +SeatPromptResult console_get_userpass_input_diagnostic(prompts_t *p) { size_t i; - int ret = 1; + SeatPromptResult ret = SPR_OK; for (i = 0; i < p->n_prompts; i++) { if (promptsgot < nprompts) { prompt_set_result(p->prompts[i], prompts[promptsgot++]); @@ -77,7 +77,7 @@ int console_get_userpass_input_diagnostic(prompts_t *p) p->prompts[i]->prompt, p->prompts[i]->result->s); } else { promptsgot++; /* track number of requests anyway */ - ret = 0; + ret = SPR_SW_ABORT("preloaded prompt unavailable in cgtest"); if (cgtest_verbose) printf(" prompt \"%s\": no response preloaded\n", p->prompts[i]->prompt); diff --git a/cmdgen.c b/cmdgen.c index be40fe1d..e0006efc 100644 --- a/cmdgen.c +++ b/cmdgen.c @@ -214,6 +214,15 @@ static char *readpassphrase(const char *filename) #define DEFAULT_RSADSA_BITS 2048 +static void spr_error(SeatPromptResult spr) +{ + if (spr.kind == SPRK_SW_ABORT) { + char *err = spr_get_error_message(spr); + fprintf(stderr, "puttygen: unable to read passphrase: %s", err); + sfree(err); + } +} + /* For Unix in particular, but harmless if this main() is reused elsewhere */ const bool buildinfo_gtk_relevant = false; @@ -941,16 +950,16 @@ int main(int argc, char **argv) if (encrypted && load_encrypted) { if (!old_passphrase) { prompts_t *p = new_prompts(); - int ret; + SeatPromptResult spr; p->to_server = false; p->from_server = false; p->name = dupstr("SSH key passphrase"); add_prompt(p, dupstr("Enter passphrase to load key: "), false); - ret = console_get_userpass_input(p); - assert(ret >= 0); - if (!ret) { + spr = console_get_userpass_input(p); + assert(spr.kind != SPRK_INCOMPLETE); + if (spr_is_abort(spr)) { free_prompts(p); - perror("puttygen: unable to read passphrase"); + spr_error(spr); RETURN(1); } else { old_passphrase = prompt_get_result(p->prompts[0]); @@ -1090,18 +1099,18 @@ int main(int argc, char **argv) if (!new_passphrase && (change_passphrase || (keytype != NOKEYGEN && outtype != TEXT))) { prompts_t *p = new_prompts(); - int ret; + SeatPromptResult spr; p->to_server = false; p->from_server = false; p->name = dupstr("New SSH key passphrase"); add_prompt(p, dupstr("Enter passphrase to save key: "), false); add_prompt(p, dupstr("Re-enter passphrase to verify: "), false); - ret = console_get_userpass_input(p); - assert(ret >= 0); - if (!ret) { + spr = console_get_userpass_input(p); + assert(spr.kind != SPRK_INCOMPLETE); + if (spr_is_abort(spr)) { free_prompts(p); - perror("puttygen: unable to read new passphrase"); + spr_error(spr); RETURN(1); } else { if (strcmp(prompt_get_result_ref(p->prompts[0]), diff --git a/cmdline.c b/cmdline.c index d87c14d0..c39c8ad2 100644 --- a/cmdline.c +++ b/cmdline.c @@ -81,7 +81,7 @@ void cmdline_cleanup(void) * -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) +SeatPromptResult cmdline_get_passwd_input(prompts_t *p) { static bool tried_once = false; @@ -91,7 +91,7 @@ int cmdline_get_passwd_input(prompts_t *p) * that comes in a prompt-set on its own. */ if (p->n_prompts != 1 || p->prompts[0]->echo) { - return -1; + return SPR_INCOMPLETE; } /* @@ -99,7 +99,7 @@ int cmdline_get_passwd_input(prompts_t *p) * to try). */ if (tried_once) - return 0; + return SPR_SW_ABORT("Configured password was not accepted"); /* * If we never had a password available in the first place, we @@ -108,14 +108,14 @@ int cmdline_get_passwd_input(prompts_t *p) * we'll still remember that we _used_ to have one.) */ if (!cmdline_password) - return -1; + return SPR_INCOMPLETE; prompt_set_result(p->prompts[0], cmdline_password); smemclr(cmdline_password, strlen(cmdline_password)); sfree(cmdline_password); cmdline_password = NULL; tried_once = true; - return 1; + return SPR_OK; } static bool cmdline_check_unavailable(int flag, const char *p) diff --git a/defs.h b/defs.h index 039341fe..45bc90f3 100644 --- a/defs.h +++ b/defs.h @@ -114,6 +114,7 @@ typedef struct LogPolicyVtable LogPolicyVtable; typedef struct Seat Seat; typedef struct SeatVtable SeatVtable; +typedef struct SeatPromptResult SeatPromptResult; typedef struct TermWin TermWin; typedef struct TermWinVtable TermWinVtable; diff --git a/otherbackends/rlogin.c b/otherbackends/rlogin.c index 59a833a0..37087257 100644 --- a/otherbackends/rlogin.c +++ b/otherbackends/rlogin.c @@ -35,7 +35,7 @@ struct Rlogin { Interactor interactor; }; -static void rlogin_startup(Rlogin *rlogin, int prompt_result, +static void rlogin_startup(Rlogin *rlogin, SeatPromptResult spr, const char *ruser); static void rlogin_try_username_prompt(void *ctx); @@ -65,7 +65,7 @@ static void rlogin_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, */ /* Next terminal output will come from server */ seat_set_trust_status(rlogin->seat, false); - rlogin_startup(rlogin, 1, ruser); + rlogin_startup(rlogin, SPR_OK, ruser); sfree(ruser); } else { /* @@ -160,17 +160,25 @@ static void rlogin_sent(Plug *plug, size_t bufsize) seat_sent(rlogin->seat, rlogin->bufsize); } -static void rlogin_startup(Rlogin *rlogin, int prompt_result, +static void rlogin_startup(Rlogin *rlogin, SeatPromptResult spr, const char *ruser) { char z = 0; char *p; - if (prompt_result == 0) { + if (spr.kind == SPRK_USER_ABORT) { /* User aborted at the username prompt. */ sk_close(rlogin->s); rlogin->s = NULL; seat_notify_remote_exit(rlogin->seat); + } else if (spr.kind == SPRK_SW_ABORT) { + /* Something else went wrong at the username prompt, so we + * have to show some kind of error. */ + sk_close(rlogin->s); + rlogin->s = NULL; + char *err = spr_get_error_message(spr); + seat_connection_fatal(rlogin->seat, "%s", err); + sfree(err); } else { sk_write(rlogin->s, &z, 1); p = conf_get_str(rlogin->conf, CONF_localusername); @@ -332,9 +340,9 @@ static void rlogin_try_username_prompt(void *ctx) { Rlogin *rlogin = (Rlogin *)ctx; - int ret = seat_get_userpass_input( + SeatPromptResult spr = seat_get_userpass_input( interactor_announce(&rlogin->interactor), rlogin->prompt); - if (ret < 0) + if (spr.kind == SPRK_INCOMPLETE) return; /* Next terminal output will come from server */ @@ -345,7 +353,7 @@ static void rlogin_try_username_prompt(void *ctx) * rlogin_startup will signal to rlogin_sendok by nulling out * rlogin->prompt. */ rlogin_startup( - rlogin, ret, prompt_get_result_ref(rlogin->prompt->prompts[0])); + rlogin, spr, prompt_get_result_ref(rlogin->prompt->prompts[0])); } /* diff --git a/proxy/http.c b/proxy/http.c index e8c66400..f788e52c 100644 --- a/proxy/http.c +++ b/proxy/http.c @@ -655,12 +655,12 @@ static void proxy_http_process_queue(ProxyNegotiator *pn) add_prompt(s->prompts, dupstr("Proxy password: "), false); while (true) { - int prompt_result = seat_get_userpass_input( + SeatPromptResult spr = seat_get_userpass_input( interactor_announce(pn->itr), s->prompts); - if (prompt_result > 0) { + if (spr.kind == SPRK_OK) { break; - } else if (prompt_result == 0) { - pn->aborted = true; + } else if (spr_is_abort(spr)) { + proxy_spr_abort(pn, spr); crStopV; } crReturnV; diff --git a/proxy/local.c b/proxy/local.c index 278d0b12..3b2d130c 100644 --- a/proxy/local.c +++ b/proxy/local.c @@ -149,16 +149,22 @@ static void local_proxy_opener_coroutine(void *vctx) } while (true) { - int prompt_result = seat_get_userpass_input( + SeatPromptResult spr = seat_get_userpass_input( interactor_announce(&lp->interactor), lp->prompts); - if (prompt_result > 0) { + if (spr.kind == SPRK_OK) { break; - } else if (prompt_result == 0) { + } else if (spr.kind == SPRK_USER_ABORT) { local_proxy_opener_cleanup_interactor(lp); plug_closing_user_abort(lp->plug); /* That will have freed us, so we must just return * without calling any crStop */ return; + } else if (spr.kind == SPRK_SW_ABORT) { + local_proxy_opener_cleanup_interactor(lp); + char *err = spr_get_error_message(spr); + plug_closing_error(lp->plug, err); + sfree(err); + return; /* without crStop, as above */ } crReturnV; } diff --git a/proxy/proxy.c b/proxy/proxy.c index b228bdb7..36105097 100644 --- a/proxy/proxy.c +++ b/proxy/proxy.c @@ -463,6 +463,16 @@ prompts_t *proxy_new_prompts(ProxySocket *ps) return prs; } +void proxy_spr_abort(ProxyNegotiator *pn, SeatPromptResult spr) +{ + if (spr.kind == SPRK_SW_ABORT) { + pn->error = spr_get_error_message(spr); + } else { + assert(spr.kind == SPRK_USER_ABORT); + pn->aborted = true; + } +} + Socket *new_connection(SockAddr *addr, const char *hostname, int port, bool privport, bool oobinline, bool nodelay, bool keepalive, diff --git a/proxy/proxy.h b/proxy/proxy.h index aacc1cb1..ca4a293f 100644 --- a/proxy/proxy.h +++ b/proxy/proxy.h @@ -91,10 +91,11 @@ extern const ProxyNegotiatorVT socks5_proxy_negotiator_vt; extern const ProxyNegotiatorVT telnet_proxy_negotiator_vt; /* - * Centralised function to allow ProxyNegotiators to get hold of a - * prompts_t. + * Centralised functions to allow ProxyNegotiators to get hold of a + * prompts_t, and to deal with SeatPromptResults coming back. */ prompts_t *proxy_new_prompts(ProxySocket *ps); +void proxy_spr_abort(ProxyNegotiator *pn, SeatPromptResult spr); /* * This may be reused by local-command proxies on individual diff --git a/proxy/socks5.c b/proxy/socks5.c index eb858b37..66797440 100644 --- a/proxy/socks5.c +++ b/proxy/socks5.c @@ -191,12 +191,12 @@ static void proxy_socks5_process_queue(ProxyNegotiator *pn) } while (true) { - int prompt_result = seat_get_userpass_input( + SeatPromptResult spr = seat_get_userpass_input( interactor_announce(pn->itr), s->prompts); - if (prompt_result > 0) { + if (spr.kind == SPRK_OK) { break; - } else if (prompt_result == 0) { - pn->aborted = true; + } else if (spr_is_abort(spr)) { + proxy_spr_abort(pn, spr); crStopV; } crReturnV; diff --git a/proxy/sshproxy.c b/proxy/sshproxy.c index 49d1ea98..bc514452 100644 --- a/proxy/sshproxy.c +++ b/proxy/sshproxy.c @@ -330,7 +330,7 @@ static void sshproxy_notify_remote_disconnect(Seat *seat) queue_toplevel_callback(sshproxy_notify_remote_disconnect_callback, sp); } -static int sshproxy_get_userpass_input(Seat *seat, prompts_t *p) +static SeatPromptResult sshproxy_get_userpass_input(Seat *seat, prompts_t *p) { SshProxy *sp = container_of(seat, SshProxy, seat); @@ -349,7 +349,8 @@ static int sshproxy_get_userpass_input(Seat *seat, prompts_t *p) */ sshproxy_error(sp, "Unable to provide interactive authentication " "requested by proxy SSH connection"); - return 0; + return SPR_SW_ABORT("Noninteractive SSH proxy cannot perform " + "interactive authentication"); } static void sshproxy_connection_fatal_callback(void *vctx) @@ -368,10 +369,10 @@ static void sshproxy_connection_fatal(Seat *seat, const char *message) } } -static int sshproxy_confirm_ssh_host_key( +static SeatPromptResult sshproxy_confirm_ssh_host_key( Seat *seat, const char *host, int port, const char *keytype, char *keystr, const char *keydisp, char **key_fingerprints, bool mismatch, - void (*callback)(void *ctx, int result), void *ctx) + void (*callback)(void *ctx, SeatPromptResult result), void *ctx) { SshProxy *sp = container_of(seat, SshProxy, seat); @@ -390,12 +391,12 @@ static int sshproxy_confirm_ssh_host_key( * option in the absence of interactive confirmation, i.e. abort * the connection. */ - return 0; + return SPR_SW_ABORT("Noninteractive SSH proxy cannot confirm host key"); } -static int sshproxy_confirm_weak_crypto_primitive( +static SeatPromptResult sshproxy_confirm_weak_crypto_primitive( Seat *seat, const char *algtype, const char *algname, - void (*callback)(void *ctx, int result), void *ctx) + void (*callback)(void *ctx, SeatPromptResult result), void *ctx) { SshProxy *sp = container_of(seat, SshProxy, seat); @@ -415,12 +416,13 @@ static int sshproxy_confirm_weak_crypto_primitive( sshproxy_error(sp, "First %s supported by server is %s, below warning " "threshold. Abandoning proxy SSH connection.", algtype, algname); - return 0; + return SPR_SW_ABORT("Noninteractive SSH proxy cannot confirm " + "weak crypto primitive"); } -static int sshproxy_confirm_weak_cached_hostkey( +static SeatPromptResult sshproxy_confirm_weak_cached_hostkey( Seat *seat, const char *algname, const char *betteralgs, - void (*callback)(void *ctx, int result), void *ctx) + void (*callback)(void *ctx, SeatPromptResult result), void *ctx) { SshProxy *sp = container_of(seat, SshProxy, seat); @@ -440,7 +442,8 @@ static int sshproxy_confirm_weak_cached_hostkey( sshproxy_error(sp, "First host key type stored for server is %s, below " "warning threshold. Abandoning proxy SSH connection.", algname); - return 0; + return SPR_SW_ABORT("Noninteractive SSH proxy cannot confirm " + "weak cached host key"); } static StripCtrlChars *sshproxy_stripctrl_new( diff --git a/proxy/telnet.c b/proxy/telnet.c index 1b4a5326..242c4025 100644 --- a/proxy/telnet.c +++ b/proxy/telnet.c @@ -288,12 +288,12 @@ static void proxy_telnet_process_queue(ProxyNegotiator *pn) crReturnV; while (true) { - int prompt_result = seat_get_userpass_input( + SeatPromptResult spr = seat_get_userpass_input( interactor_announce(pn->itr), s->prompts); - if (prompt_result > 0) { + if (spr.kind == SPRK_OK) { break; - } else if (prompt_result == 0) { - pn->aborted = true; + } else if (spr_is_abort(spr)) { + proxy_spr_abort(pn, spr); crStopV; } crReturnV; diff --git a/putty.h b/putty.h index c4e8d6a1..edb5eb4c 100644 --- a/putty.h +++ b/putty.h @@ -861,6 +861,82 @@ extern const char *const appname; */ typedef void (*toplevel_callback_fn_t)(void *ctx); +/* Enum of result types in SeatPromptResult below */ +typedef enum SeatPromptResultKind { + /* Answer not yet available at all; either try again later or wait + * for a callback (depending on the request's API) */ + SPRK_INCOMPLETE, + + /* We're abandoning the connection because the user interactively + * told us to. (Hence, no need to present an error message + * telling the user we're doing that: they already know.) */ + SPRK_USER_ABORT, + + /* We're abandoning the connection for some other reason (e.g. we + * were unable to present the prompt at all, or a batch-mode + * configuration told us to give the answer no). This may + * ultimately have stemmed from some user configuration, but they + * didn't _tell us right now_ to abandon this connection, so we + * still need to inform them that we've done so. */ + SPRK_SW_ABORT, + + /* We're proceeding with the connection and have all requested + * information (if any) */ + SPRK_OK +} SeatPromptResultKind; + +/* Small struct to present the results of interactive requests from + * backend to Seat (see below) */ +struct SeatPromptResult { + SeatPromptResultKind kind; + + /* + * In the case of SPRK_SW_ABORT, the frontend provides an error + * message to present to the user. But dynamically allocating it + * up front would mean having to make sure it got freed at any + * call site where one of these structs is received (and freed + * _once_ no matter how many times the struct is copied). So + * instead we provide a function that will generate the error + * message into a BinarySink. + */ + void (*errfn)(SeatPromptResult, BinarySink *); + + /* + * And some fields the error function can use to construct the + * message (holding, e.g. an OS error code). + */ + const char *errdata_lit; /* statically allocated, e.g. a string literal */ + unsigned errdata_u; +}; + +/* Helper function to construct the simple versions of these + * structures inline */ +static inline SeatPromptResult make_spr_simple(SeatPromptResultKind kind) +{ + SeatPromptResult spr; + spr.kind = kind; + spr.errdata_lit = NULL; + return spr; +} + +/* Most common constructor function for SPRK_SW_ABORT errors */ +SeatPromptResult make_spr_sw_abort_static(const char *); + +/* Convenience macros wrapping those constructors in turn */ +#define SPR_INCOMPLETE make_spr_simple(SPRK_INCOMPLETE) +#define SPR_USER_ABORT make_spr_simple(SPRK_USER_ABORT) +#define SPR_SW_ABORT(lit) make_spr_sw_abort_static(lit) +#define SPR_OK make_spr_simple(SPRK_OK) + +/* Query function that folds both kinds of abort together */ +static inline bool spr_is_abort(SeatPromptResult spr) +{ + return spr.kind == SPRK_USER_ABORT || spr.kind == SPRK_SW_ABORT; +} + +/* Function to return a dynamically allocated copy of the error message */ +char *spr_get_error_message(SeatPromptResult spr); + /* * Mechanism for getting text strings such as usernames and passwords * from the front-end. @@ -911,7 +987,7 @@ struct prompts_t { prompt_t **prompts; void *data; /* slot for housekeeping data, managed by * seat_get_userpass_input(); initially NULL */ - int idata; /* another slot private to the implementation */ + SeatPromptResult spr; /* some implementations need to cache one of these */ /* * Callback you can fill in to be notified when all the prompts' @@ -1062,13 +1138,8 @@ struct SeatVtable { * Try to get answers from a set of interactive login prompts. The * prompts are provided in 'p'. * - * A positive return value means that all prompts have had answers - * filled in. A zero return means that the user performed a - * deliberate 'cancel' UI action. A negative return means that no - * answer can be given yet but please try again later. - * - * (FIXME: it would be nice to distinguish two classes of cancel - * action, so the user could specify 'I want to abandon this + * (FIXME: it would be nice to distinguish two classes of user- + * abort action, so the user could specify 'I want to abandon this * entire attempt to start a session' or the milder 'I want to * abandon this particular form of authentication and fall back to * a different one' - e.g. if you turn out not to be able to @@ -1076,7 +1147,7 @@ struct SeatVtable { * fall back to password auth rather than aborting the whole * session.) */ - int (*get_userpass_input)(Seat *seat, prompts_t *p); + SeatPromptResult (*get_userpass_input)(Seat *seat, prompts_t *p); /* * Notify the seat that the main session channel has been @@ -1185,10 +1256,11 @@ struct SeatVtable { * back via the provided function with a result that's either 0 * or +1'. */ - int (*confirm_ssh_host_key)( + SeatPromptResult (*confirm_ssh_host_key)( Seat *seat, const char *host, int port, const char *keytype, char *keystr, const char *keydisp, char **key_fingerprints, - bool mismatch, void (*callback)(void *ctx, int result), void *ctx); + bool mismatch, void (*callback)(void *ctx, SeatPromptResult result), + void *ctx); /* * Check with the seat whether it's OK to use a cryptographic @@ -1196,9 +1268,9 @@ struct SeatVtable { * the input Conf. Return values are the same as * confirm_ssh_host_key above. */ - int (*confirm_weak_crypto_primitive)( + SeatPromptResult (*confirm_weak_crypto_primitive)( Seat *seat, const char *algtype, const char *algname, - void (*callback)(void *ctx, int result), void *ctx); + void (*callback)(void *ctx, SeatPromptResult result), void *ctx); /* * Variant form of confirm_weak_crypto_primitive, which prints a @@ -1211,9 +1283,9 @@ struct SeatVtable { * threshold is available that we don't have cached. 'betteralgs' * lists the better algorithm(s). */ - int (*confirm_weak_cached_hostkey)( + SeatPromptResult (*confirm_weak_cached_hostkey)( Seat *seat, const char *algname, const char *betteralgs, - void (*callback)(void *ctx, int result), void *ctx); + void (*callback)(void *ctx, SeatPromptResult result), void *ctx); /* * Indicates whether the seat is expecting to interact with the @@ -1319,8 +1391,8 @@ static inline void seat_sent(Seat *seat, size_t bufsize) static inline size_t seat_banner( InteractionReadySeat iseat, const void *data, size_t len) { return iseat.seat->vt->banner(iseat.seat, data, len); } -static inline int seat_get_userpass_input(InteractionReadySeat iseat, - prompts_t *p) +static inline SeatPromptResult seat_get_userpass_input( + InteractionReadySeat iseat, prompts_t *p) { return iseat.seat->vt->get_userpass_input(iseat.seat, p); } static inline void seat_notify_session_started(Seat *seat) { seat->vt->notify_session_started(seat); } @@ -1334,20 +1406,20 @@ static inline char *seat_get_ttymode(Seat *seat, const char *mode) { return seat->vt->get_ttymode(seat, mode); } static inline void seat_set_busy_status(Seat *seat, BusyStatus status) { seat->vt->set_busy_status(seat, status); } -static inline int seat_confirm_ssh_host_key( +static inline SeatPromptResult seat_confirm_ssh_host_key( InteractionReadySeat iseat, const char *h, int p, const char *ktyp, char *kstr, const char *kdsp, char **fps, bool mis, - void (*cb)(void *ctx, int result), void *ctx) + void (*cb)(void *ctx, SeatPromptResult result), void *ctx) { return iseat.seat->vt->confirm_ssh_host_key( iseat.seat, h, p, ktyp, kstr, kdsp, fps, mis, cb, ctx); } -static inline int seat_confirm_weak_crypto_primitive( +static inline SeatPromptResult seat_confirm_weak_crypto_primitive( InteractionReadySeat iseat, const char *atyp, const char *aname, - void (*cb)(void *ctx, int result), void *ctx) + void (*cb)(void *ctx, SeatPromptResult result), void *ctx) { return iseat.seat->vt->confirm_weak_crypto_primitive( iseat.seat, atyp, aname, cb, ctx); } -static inline int seat_confirm_weak_cached_hostkey( +static inline SeatPromptResult seat_confirm_weak_cached_hostkey( InteractionReadySeat iseat, const char *aname, const char *better, - void (*cb)(void *ctx, int result), void *ctx) + void (*cb)(void *ctx, SeatPromptResult result), void *ctx) { return iseat.seat->vt->confirm_weak_cached_hostkey( iseat.seat, aname, better, cb, ctx); } static inline bool seat_is_utf8(Seat *seat) @@ -1412,7 +1484,7 @@ bool nullseat_eof(Seat *seat); void nullseat_sent(Seat *seat, size_t bufsize); size_t nullseat_banner(Seat *seat, const void *data, size_t len); size_t nullseat_banner_to_stderr(Seat *seat, const void *data, size_t len); -int nullseat_get_userpass_input(Seat *seat, prompts_t *p); +SeatPromptResult nullseat_get_userpass_input(Seat *seat, prompts_t *p); void nullseat_notify_session_started(Seat *seat); void nullseat_notify_remote_exit(Seat *seat); void nullseat_notify_remote_disconnect(Seat *seat); @@ -1420,16 +1492,16 @@ void nullseat_connection_fatal(Seat *seat, const char *message); void nullseat_update_specials_menu(Seat *seat); char *nullseat_get_ttymode(Seat *seat, const char *mode); void nullseat_set_busy_status(Seat *seat, BusyStatus status); -int nullseat_confirm_ssh_host_key( +SeatPromptResult nullseat_confirm_ssh_host_key( Seat *seat, const char *host, int port, const char *keytype, char *keystr, const char *keydisp, char **key_fingerprints, bool mismatch, - void (*callback)(void *ctx, int result), void *ctx); -int nullseat_confirm_weak_crypto_primitive( + void (*callback)(void *ctx, SeatPromptResult result), void *ctx); +SeatPromptResult nullseat_confirm_weak_crypto_primitive( Seat *seat, const char *algtype, const char *algname, - void (*callback)(void *ctx, int result), void *ctx); -int nullseat_confirm_weak_cached_hostkey( + void (*callback)(void *ctx, SeatPromptResult result), void *ctx); +SeatPromptResult nullseat_confirm_weak_cached_hostkey( Seat *seat, const char *algname, const char *betteralgs, - void (*callback)(void *ctx, int result), void *ctx); + void (*callback)(void *ctx, SeatPromptResult result), void *ctx); bool nullseat_is_never_utf8(Seat *seat); bool nullseat_is_always_utf8(Seat *seat); void nullseat_echoedit_update(Seat *seat, bool echoing, bool editing); @@ -1455,16 +1527,16 @@ bool nullseat_get_cursor_position(Seat *seat, int *x, int *y); */ void console_connection_fatal(Seat *seat, const char *message); -int console_confirm_ssh_host_key( +SeatPromptResult console_confirm_ssh_host_key( Seat *seat, const char *host, int port, const char *keytype, char *keystr, const char *keydisp, char **key_fingerprints, bool mismatch, - void (*callback)(void *ctx, int result), void *ctx); -int console_confirm_weak_crypto_primitive( + void (*callback)(void *ctx, SeatPromptResult result), void *ctx); +SeatPromptResult console_confirm_weak_crypto_primitive( Seat *seat, const char *algtype, const char *algname, - void (*callback)(void *ctx, int result), void *ctx); -int console_confirm_weak_cached_hostkey( + void (*callback)(void *ctx, SeatPromptResult result), void *ctx); +SeatPromptResult console_confirm_weak_cached_hostkey( Seat *seat, const char *algname, const char *betteralgs, - void (*callback)(void *ctx, int result), void *ctx); + void (*callback)(void *ctx, SeatPromptResult result), void *ctx); StripCtrlChars *console_stripctrl_new( Seat *seat, BinarySink *bs_out, SeatInteractionContext sic); void console_set_trust_status(Seat *seat, bool trusted); @@ -1474,7 +1546,7 @@ bool console_has_mixed_input_stream(Seat *seat); /* * Other centralised seat functions. */ -int filexfer_get_userpass_input(Seat *seat, prompts_t *p); +SeatPromptResult filexfer_get_userpass_input(Seat *seat, prompts_t *p); bool cmdline_seat_verbose(Seat *seat); /* @@ -2080,7 +2152,7 @@ void term_provide_backend(Terminal *term, Backend *backend); void term_provide_logctx(Terminal *term, LogContext *logctx); void term_set_focus(Terminal *term, bool has_focus); char *term_get_ttymode(Terminal *term, const char *mode); -int term_get_userpass_input(Terminal *term, prompts_t *p); +SeatPromptResult term_get_userpass_input(Terminal *term, prompts_t *p); void term_set_trust_status(Terminal *term, bool trusted); void term_keyinput(Terminal *, int codepage, const void *buf, int len); void term_keyinputw(Terminal *, const wchar_t * widebuf, int len); @@ -2424,7 +2496,7 @@ bool have_ssh_host_key(const char *host, int port, const char *keytype); * that aren't equivalents to things in windlg.c et al. */ extern bool console_batch_mode, console_antispoof_prompt; -int console_get_userpass_input(prompts_t *p); +SeatPromptResult console_get_userpass_input(prompts_t *p); bool is_interactive(void); void console_print_error_msg(const char *prefix, const char *msg); void console_print_error_msg_fmt_v( @@ -2457,7 +2529,7 @@ void printer_finish_job(printer_job *); int cmdline_process_param(const char *, char *, int, Conf *); void cmdline_run_saved(Conf *); void cmdline_cleanup(void); -int cmdline_get_passwd_input(prompts_t *p); +SeatPromptResult cmdline_get_passwd_input(prompts_t *p); bool cmdline_host_ok(Conf *); bool cmdline_verbose(void); bool cmdline_loaded_session(void); diff --git a/ssh.h b/ssh.h index 294afada..0c2edf33 100644 --- a/ssh.h +++ b/ssh.h @@ -423,6 +423,7 @@ void ssh_proto_error(Ssh *ssh, const char *fmt, ...) PRINTF_LIKE(2, 3); void ssh_sw_abort(Ssh *ssh, const char *fmt, ...) PRINTF_LIKE(2, 3); void ssh_sw_abort_deferred(Ssh *ssh, const char *fmt, ...) PRINTF_LIKE(2, 3); void ssh_user_close(Ssh *ssh, const char *fmt, ...) PRINTF_LIKE(2, 3); +void ssh_spr_close(Ssh *ssh, SeatPromptResult spr, const char *context); /* Bit positions in the SSH-1 cipher protocol word */ #define SSH1_CIPHER_IDEA 1 @@ -1709,10 +1710,11 @@ unsigned alloc_channel_id_general(tree234 *channels, size_t localid_offset); void add_to_commasep(strbuf *buf, const char *data); bool get_commasep_word(ptrlen *list, ptrlen *word); -int verify_ssh_host_key( +SeatPromptResult verify_ssh_host_key( InteractionReadySeat iseat, Conf *conf, const char *host, int port, ssh_key *key, const char *keytype, char *keystr, const char *keydisp, - char **fingerprints, void (*callback)(void *ctx, int result), void *ctx); + char **fingerprints, void (*callback)(void *ctx, SeatPromptResult result), + void *ctx); typedef struct ssh_transient_hostkey_cache ssh_transient_hostkey_cache; ssh_transient_hostkey_cache *ssh_transient_hostkey_cache_new(void); diff --git a/ssh/common.c b/ssh/common.c index c7d98061..0c9f51e3 100644 --- a/ssh/common.c +++ b/ssh/common.c @@ -845,15 +845,13 @@ bool ssh2_bpp_check_unimplemented(BinaryPacketProtocol *bpp, PktIn *pktin) * host key is already known. If so, it returns success on its own * account; otherwise, it calls out to the Seat to give an interactive * prompt (the nature of which varies depending on the Seat itself). - * - * Return values are 0 for 'abort connection', 1 for 'ok, carry on', - * and negative for 'answer not received yet, wait for a callback'. */ -int verify_ssh_host_key( +SeatPromptResult verify_ssh_host_key( InteractionReadySeat iseat, Conf *conf, const char *host, int port, ssh_key *key, const char *keytype, char *keystr, const char *keydisp, - char **fingerprints, void (*callback)(void *ctx, int result), void *ctx) + char **fingerprints, void (*callback)(void *ctx, SeatPromptResult result), + void *ctx) { /* * First, check if the Conf includes a manual specification of the @@ -878,7 +876,7 @@ int verify_ssh_host_key( fingerprint = p ? p+1 : fingerprint; if (conf_get_str_str_opt(conf, CONF_ssh_manual_hostkeys, fingerprint)) - return 1; /* success */ + return SPR_OK; } } @@ -902,12 +900,12 @@ int verify_ssh_host_key( if (conf_get_str_str_opt(conf, CONF_ssh_manual_hostkeys, base64blob)) { sfree(base64blob); - return 1; /* success */ + return SPR_OK; } sfree(base64blob); } - return 0; + return SPR_SW_ABORT("Host key not in manually configured list"); } /* @@ -915,7 +913,7 @@ int verify_ssh_host_key( */ int storage_status = check_stored_host_key(host, port, keytype, keystr); if (storage_status == 0) /* matching key was found in the cache */ - return 1; /* success */ + return SPR_OK; /* * The key is either missing from the cache, or does not match. @@ -994,3 +992,22 @@ void ssh1_compute_session_id( put_data(hash, cookie, 8); ssh_hash_final(hash, session_id); } + +/* ---------------------------------------------------------------------- + * Wrapper function to handle the abort-connection modes of a + * SeatPromptResult without a lot of verbiage at every call site. + * + * Can become ssh_sw_abort or ssh_user_close, depending on the kind of + * negative SeatPromptResult. + */ +void ssh_spr_close(Ssh *ssh, SeatPromptResult spr, const char *context) +{ + if (spr.kind == SPRK_USER_ABORT) { + ssh_user_close(ssh, "User aborted at %s", context); + } else { + assert(spr.kind == SPRK_SW_ABORT); + char *err = spr_get_error_message(spr); + ssh_sw_abort(ssh, "%s", err); + sfree(err); + } +} diff --git a/ssh/connection1.c b/ssh/connection1.c index 47ffd3c7..0b5485f8 100644 --- a/ssh/connection1.c +++ b/ssh/connection1.c @@ -381,7 +381,7 @@ static void ssh1_connection_process_queue(PacketProtocolLayer *ppl) dupstr("Access granted. Press Return to begin session. "), false); s->antispoof_ret = seat_get_userpass_input( ppl_get_iseat(&s->ppl), s->antispoof_prompt); - while (s->antispoof_ret < 0) { + while (s->antispoof_ret.kind == SPRK_INCOMPLETE) { crReturnV; s->antispoof_ret = seat_get_userpass_input( ppl_get_iseat(&s->ppl), s->antispoof_prompt); diff --git a/ssh/connection1.h b/ssh/connection1.h index 7d136d60..ff370131 100644 --- a/ssh/connection1.h +++ b/ssh/connection1.h @@ -50,7 +50,7 @@ struct ssh1_connection_state { bool sent_exit_status; /* also for server mode */ prompts_t *antispoof_prompt; - int antispoof_ret; + SeatPromptResult antispoof_ret; const SshServerConfig *ssc; diff --git a/ssh/connection2.c b/ssh/connection2.c index 86bd315a..fb24b7c6 100644 --- a/ssh/connection2.c +++ b/ssh/connection2.c @@ -994,7 +994,7 @@ static void ssh2_connection_process_queue(PacketProtocolLayer *ppl) dupstr("Access granted. Press Return to begin session. "), false); s->antispoof_ret = seat_get_userpass_input( ppl_get_iseat(&s->ppl), s->antispoof_prompt); - while (s->antispoof_ret < 0) { + while (s->antispoof_ret.kind == SPRK_INCOMPLETE) { crReturnV; s->antispoof_ret = seat_get_userpass_input( ppl_get_iseat(&s->ppl), s->antispoof_prompt); diff --git a/ssh/connection2.h b/ssh/connection2.h index 7971632c..54c3ebf9 100644 --- a/ssh/connection2.h +++ b/ssh/connection2.h @@ -37,7 +37,7 @@ struct ssh2_connection_state { bool portfwdmgr_configured; prompts_t *antispoof_prompt; - int antispoof_ret; + SeatPromptResult antispoof_ret; const SftpServerVtable *sftpserver_vt; const SshServerConfig *ssc; diff --git a/ssh/kex2-client.c b/ssh/kex2-client.c index 0f81ef15..9a8f75e2 100644 --- a/ssh/kex2-client.c +++ b/ssh/kex2-client.c @@ -853,7 +853,7 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) ppl_logevent("Host key fingerprint is:"); ppl_logevent("%s", fingerprints[fptype_default]); - s->dlgret = verify_ssh_host_key( + s->spr = verify_ssh_host_key( ppl_get_iseat(&s->ppl), s->conf, s->savedhost, s->savedport, s->hkey, ssh_key_cache_id(s->hkey), s->keystr, keydisp, fingerprints, ssh2_transport_dialog_callback, s); @@ -862,13 +862,12 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) sfree(keydisp); } #ifdef FUZZING - s->dlgret = 1; + s->spr = SPR_OK; #endif - crMaybeWaitUntilV(s->dlgret >= 0); - if (s->dlgret == 0) { - ssh_user_close(s->ppl.ssh, - "User aborted at host key verification"); + crMaybeWaitUntilV(s->spr.kind != SPRK_INCOMPLETE); + if (spr_is_abort(s->spr)) { *aborted = true; + ssh_spr_close(s->ppl.ssh, s->spr, "host key verification"); return; } diff --git a/ssh/login1.c b/ssh/login1.c index 62c7b866..7b345c78 100644 --- a/ssh/login1.c +++ b/ssh/login1.c @@ -47,7 +47,7 @@ struct ssh1_login_state { char *publickey_comment; bool privatekey_available, privatekey_encrypted; prompts_t *cur_prompt; - int userpass_ret; + SeatPromptResult spr; char c; int pwpkt_type; void *agent_response_to_free; @@ -58,7 +58,6 @@ struct ssh1_login_state { size_t agent_key_index, agent_key_limit; bool authed; RSAKey key; - int dlgret; Filename *keyfile; RSAKey servkey, hostkey; @@ -70,7 +69,7 @@ struct ssh1_login_state { static void ssh1_login_free(PacketProtocolLayer *); static void ssh1_login_process_queue(PacketProtocolLayer *); -static void ssh1_login_dialog_callback(void *, int); +static void ssh1_login_dialog_callback(void *, SeatPromptResult); static void ssh1_login_special_cmd(PacketProtocolLayer *ppl, SessionSpecialCode code, int arg); static void ssh1_login_reconfigure(PacketProtocolLayer *ppl, Conf *conf); @@ -242,7 +241,7 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl) char *keydisp = ssh1_pubkey_str(&s->hostkey); char **fingerprints = rsa_ssh1_fake_all_fingerprints(&s->hostkey); - s->dlgret = verify_ssh_host_key( + s->spr = verify_ssh_host_key( ppl_get_iseat(&s->ppl), s->conf, s->savedhost, s->savedport, NULL, "rsa", keystr, keydisp, fingerprints, ssh1_login_dialog_callback, s); @@ -253,13 +252,12 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl) } #ifdef FUZZING - s->dlgret = 1; + s->spr = SPR_OK; #endif - crMaybeWaitUntilV(s->dlgret >= 0); + crMaybeWaitUntilV(s->spr.kind != SPRK_INCOMPLETE); - if (s->dlgret == 0) { - ssh_user_close(s->ppl.ssh, - "User aborted at host key verification"); + if (spr_is_abort(s->spr)) { + ssh_spr_close(s->ppl.ssh, s->spr, "host key verification"); return; } @@ -324,12 +322,12 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl) /* Warn about chosen cipher if necessary. */ if (warn) { - s->dlgret = seat_confirm_weak_crypto_primitive( + s->spr = seat_confirm_weak_crypto_primitive( ppl_get_iseat(&s->ppl), "cipher", cipher_string, ssh1_login_dialog_callback, s); - crMaybeWaitUntilV(s->dlgret >= 0); - if (s->dlgret == 0) { - ssh_user_close(s->ppl.ssh, "User aborted at cipher warning"); + crMaybeWaitUntilV(s->spr.kind != SPRK_INCOMPLETE); + if (spr_is_abort(s->spr)) { + ssh_spr_close(s->ppl.ssh, s->spr, "cipher warning"); return; } } @@ -391,18 +389,18 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl) s->cur_prompt->from_server = false; s->cur_prompt->name = dupstr("SSH login name"); add_prompt(s->cur_prompt, dupstr("login as: "), true); - s->userpass_ret = seat_get_userpass_input( + s->spr = seat_get_userpass_input( ppl_get_iseat(&s->ppl), s->cur_prompt); - while (s->userpass_ret < 0) { + while (s->spr.kind == SPRK_INCOMPLETE) { crReturnV; - s->userpass_ret = seat_get_userpass_input( + s->spr = seat_get_userpass_input( ppl_get_iseat(&s->ppl), s->cur_prompt); } - if (!s->userpass_ret) { + if (spr_is_abort(s->spr)) { /* * Failed to get a username. Terminate. */ - ssh_user_close(s->ppl.ssh, "No username provided"); + ssh_spr_close(s->ppl.ssh, s->spr, "username prompt"); return; } s->username = prompt_get_result(s->cur_prompt->prompts[0]); @@ -688,17 +686,16 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl) add_prompt(s->cur_prompt, dupprintf("Passphrase for key \"%s\": ", s->publickey_comment), false); - s->userpass_ret = seat_get_userpass_input( + s->spr = seat_get_userpass_input( ppl_get_iseat(&s->ppl), s->cur_prompt); - while (s->userpass_ret < 0) { + while (s->spr.kind == SPRK_INCOMPLETE) { crReturnV; - s->userpass_ret = seat_get_userpass_input( + s->spr = seat_get_userpass_input( ppl_get_iseat(&s->ppl), s->cur_prompt); } - if (!s->userpass_ret) { + if (spr_is_abort(s->spr)) { /* Failed to get a passphrase. Terminate. */ - ssh_user_close(s->ppl.ssh, - "User aborted at passphrase prompt"); + ssh_spr_close(s->ppl.ssh, s->spr, "passphrase prompt"); return; } passphrase = prompt_get_result(s->cur_prompt->prompts[0]); @@ -943,20 +940,20 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl) * or CryptoCard exchange if we're doing TIS or CryptoCard * authentication. */ - s->userpass_ret = seat_get_userpass_input( + s->spr = seat_get_userpass_input( ppl_get_iseat(&s->ppl), s->cur_prompt); - while (s->userpass_ret < 0) { + while (s->spr.kind == SPRK_INCOMPLETE) { crReturnV; - s->userpass_ret = seat_get_userpass_input( + s->spr = seat_get_userpass_input( ppl_get_iseat(&s->ppl), s->cur_prompt); } - if (!s->userpass_ret) { + if (spr_is_abort(s->spr)) { /* * Failed to get a password (for example * because one was supplied on the command line * which has already failed to work). Terminate. */ - ssh_user_close(s->ppl.ssh, "User aborted at password prompt"); + ssh_spr_close(s->ppl.ssh, s->spr, "password prompt"); return; } @@ -1141,10 +1138,10 @@ static void ssh1_login_setup_tis_scc(struct ssh1_login_state *s) s->tis_scc_initialised = true; } -static void ssh1_login_dialog_callback(void *loginv, int ret) +static void ssh1_login_dialog_callback(void *loginv, SeatPromptResult spr) { struct ssh1_login_state *s = (struct ssh1_login_state *)loginv; - s->dlgret = ret; + s->spr = spr; ssh_ppl_process_queue(&s->ppl); } diff --git a/ssh/server.c b/ssh/server.c index 347aa14b..f69bdd83 100644 --- a/ssh/server.c +++ b/ssh/server.c @@ -97,12 +97,14 @@ void mainchan_terminal_size(mainchan *mc, int width, int height) {} /* Seat functions to ensure we don't get choosy about crypto - as the * server, it's not up to us to give user warnings */ -static int server_confirm_weak_crypto_primitive( +static SeatPromptResult server_confirm_weak_crypto_primitive( Seat *seat, const char *algtype, const char *algname, - void (*callback)(void *ctx, int result), void *ctx) { return 1; } -static int server_confirm_weak_cached_hostkey( + void (*callback)(void *ctx, SeatPromptResult result), void *ctx) +{ return SPR_OK; } +static SeatPromptResult server_confirm_weak_cached_hostkey( Seat *seat, const char *algname, const char *betteralgs, - void (*callback)(void *ctx, int result), void *ctx) { return 1; } + void (*callback)(void *ctx, SeatPromptResult result), void *ctx) +{ return SPR_OK; } static const SeatVtable server_seat_vt = { .output = nullseat_output, diff --git a/ssh/transport2.c b/ssh/transport2.c index 0ba937f3..b4261562 100644 --- a/ssh/transport2.c +++ b/ssh/transport2.c @@ -97,7 +97,7 @@ static void ssh2_transport_gss_update(struct ssh2_transport_state *s, static bool ssh2_transport_timer_update(struct ssh2_transport_state *s, unsigned long rekey_time); -static int ssh2_transport_confirm_weak_crypto_primitive( +static SeatPromptResult ssh2_transport_confirm_weak_crypto_primitive( struct ssh2_transport_state *s, const char *type, const char *name, const void *alg); @@ -1265,11 +1265,11 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl) } if (s->warn_kex) { - s->dlgret = ssh2_transport_confirm_weak_crypto_primitive( + s->spr = ssh2_transport_confirm_weak_crypto_primitive( s, "key-exchange algorithm", s->kex_alg->name, s->kex_alg); - crMaybeWaitUntilV(s->dlgret >= 0); - if (s->dlgret == 0) { - ssh_user_close(s->ppl.ssh, "User aborted at kex warning"); + crMaybeWaitUntilV(s->spr.kind != SPRK_INCOMPLETE); + if (spr_is_abort(s->spr)) { + ssh_spr_close(s->ppl.ssh, s->spr, "kex warning"); return; } } @@ -1312,42 +1312,42 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl) if (betteralgs) { /* Use the special warning prompt that lets us provide * a list of better algorithms */ - s->dlgret = seat_confirm_weak_cached_hostkey( + s->spr = seat_confirm_weak_cached_hostkey( ppl_get_iseat(&s->ppl), s->hostkey_alg->ssh_id, betteralgs, ssh2_transport_dialog_callback, s); sfree(betteralgs); } else { /* If none exist, use the more general 'weak crypto' * warning prompt */ - s->dlgret = ssh2_transport_confirm_weak_crypto_primitive( + s->spr = ssh2_transport_confirm_weak_crypto_primitive( s, "host key type", s->hostkey_alg->ssh_id, s->hostkey_alg); } - crMaybeWaitUntilV(s->dlgret >= 0); - if (s->dlgret == 0) { - ssh_user_close(s->ppl.ssh, "User aborted at host key warning"); + crMaybeWaitUntilV(s->spr.kind != SPRK_INCOMPLETE); + if (spr_is_abort(s->spr)) { + ssh_spr_close(s->ppl.ssh, s->spr, "host key warning"); return; } } if (s->warn_cscipher) { - s->dlgret = ssh2_transport_confirm_weak_crypto_primitive( + s->spr = ssh2_transport_confirm_weak_crypto_primitive( s, "client-to-server cipher", s->out.cipher->ssh2_id, s->out.cipher); - crMaybeWaitUntilV(s->dlgret >= 0); - if (s->dlgret == 0) { - ssh_user_close(s->ppl.ssh, "User aborted at cipher warning"); + crMaybeWaitUntilV(s->spr.kind != SPRK_INCOMPLETE); + if (spr_is_abort(s->spr)) { + ssh_spr_close(s->ppl.ssh, s->spr, "cipher warning"); return; } } if (s->warn_sccipher) { - s->dlgret = ssh2_transport_confirm_weak_crypto_primitive( + s->spr = ssh2_transport_confirm_weak_crypto_primitive( s, "server-to-client cipher", s->in.cipher->ssh2_id, s->in.cipher); - crMaybeWaitUntilV(s->dlgret >= 0); - if (s->dlgret == 0) { - ssh_user_close(s->ppl.ssh, "User aborted at cipher warning"); + crMaybeWaitUntilV(s->spr.kind != SPRK_INCOMPLETE); + if (spr_is_abort(s->spr)) { + ssh_spr_close(s->ppl.ssh, s->spr, "cipher warning"); return; } } @@ -1815,10 +1815,10 @@ static bool ssh2_transport_timer_update(struct ssh2_transport_state *s, return false; } -void ssh2_transport_dialog_callback(void *vctx, int ret) +void ssh2_transport_dialog_callback(void *vctx, SeatPromptResult spr) { struct ssh2_transport_state *s = (struct ssh2_transport_state *)vctx; - s->dlgret = ret; + s->spr = spr; ssh_ppl_process_queue(&s->ppl); } @@ -2139,12 +2139,12 @@ static int weak_algorithm_compare(void *av, void *bv) * tree234 s->weak_algorithms_consented_to to ensure we ask at most * once about any given crypto primitive. */ -static int ssh2_transport_confirm_weak_crypto_primitive( +static SeatPromptResult ssh2_transport_confirm_weak_crypto_primitive( struct ssh2_transport_state *s, const char *type, const char *name, const void *alg) { if (find234(s->weak_algorithms_consented_to, (void *)alg, NULL)) - return 1; + return SPR_OK; add234(s->weak_algorithms_consented_to, (void *)alg); return seat_confirm_weak_crypto_primitive( diff --git a/ssh/transport2.h b/ssh/transport2.h index 46cd67f9..5f0df80e 100644 --- a/ssh/transport2.h +++ b/ssh/transport2.h @@ -186,7 +186,7 @@ struct ssh2_transport_state { bool warned_about_no_gss_transient_hostkey; bool got_session_id; bool can_send_ext_info, post_newkeys_ext_info; - int dlgret; + SeatPromptResult spr; bool guessok; bool ignorepkt; struct kexinit_algorithm kexlists[NKEXLIST][MAXKEXLIST]; @@ -231,7 +231,7 @@ struct ssh2_transport_state { /* Helpers shared between transport and kex */ PktIn *ssh2_transport_pop(struct ssh2_transport_state *s); -void ssh2_transport_dialog_callback(void *, int); +void ssh2_transport_dialog_callback(void *, SeatPromptResult); /* Provided by transport for use in kex */ void ssh2transport_finalise_exhash(struct ssh2_transport_state *s); diff --git a/ssh/userauth2-client.c b/ssh/userauth2-client.c index b6ed5704..ba97502f 100644 --- a/ssh/userauth2-client.c +++ b/ssh/userauth2-client.c @@ -45,7 +45,7 @@ struct ssh2_userauth_state { AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET } type; bool need_pw, can_pubkey, can_passwd, can_keyb_inter; - int userpass_ret; + SeatPromptResult spr; bool tried_pubkey_config, done_agent; struct ssh_connection_shared_gss_state *shgss; #ifndef NO_GSSAPI @@ -442,21 +442,21 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) s->cur_prompt->from_server = false; s->cur_prompt->name = dupstr("SSH login name"); add_prompt(s->cur_prompt, dupstr("login as: "), true); - s->userpass_ret = seat_get_userpass_input( + s->spr = seat_get_userpass_input( ppl_get_iseat(&s->ppl), s->cur_prompt); - while (s->userpass_ret < 0) { + while (s->spr.kind == SPRK_INCOMPLETE) { crReturnV; - s->userpass_ret = seat_get_userpass_input( + s->spr = seat_get_userpass_input( ppl_get_iseat(&s->ppl), s->cur_prompt); } - if (!s->userpass_ret) { + if (spr_is_abort(s->spr)) { /* * seat_get_userpass_input() failed to get a username. * Terminate. */ free_prompts(s->cur_prompt); s->cur_prompt = NULL; - ssh_user_close(s->ppl.ssh, "No username provided"); + ssh_spr_close(s->ppl.ssh, s->spr, "username prompt"); return; } sfree(s->locally_allocated_username); /* for change_username */ @@ -912,22 +912,22 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) dupprintf("Passphrase for key \"%s\": ", s->publickey_comment), false); - s->userpass_ret = seat_get_userpass_input( + s->spr = seat_get_userpass_input( ppl_get_iseat(&s->ppl), s->cur_prompt); - while (s->userpass_ret < 0) { + while (s->spr.kind == SPRK_INCOMPLETE) { crReturnV; - s->userpass_ret = seat_get_userpass_input( + s->spr = seat_get_userpass_input( ppl_get_iseat(&s->ppl), s->cur_prompt); } - if (!s->userpass_ret) { + if (spr_is_abort(s->spr)) { /* Failed to get a passphrase. Terminate. */ free_prompts(s->cur_prompt); s->cur_prompt = NULL; ssh_bpp_queue_disconnect( s->ppl.bpp, "Unable to authenticate", SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER); - ssh_user_close(s->ppl.ssh, "User aborted at " - "passphrase prompt"); + ssh_spr_close(s->ppl.ssh, s->spr, + "passphrase prompt"); return; } passphrase = @@ -1391,14 +1391,14 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) * Our prompts_t is fully constructed now. Get the * user's response(s). */ - s->userpass_ret = seat_get_userpass_input( + s->spr = seat_get_userpass_input( ppl_get_iseat(&s->ppl), s->cur_prompt); - while (s->userpass_ret < 0) { + while (s->spr.kind == SPRK_INCOMPLETE) { crReturnV; - s->userpass_ret = seat_get_userpass_input( + s->spr = seat_get_userpass_input( ppl_get_iseat(&s->ppl), s->cur_prompt); } - if (!s->userpass_ret) { + if (spr_is_abort(s->spr)) { /* * Failed to get responses. Terminate. */ @@ -1407,8 +1407,8 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) ssh_bpp_queue_disconnect( s->ppl.bpp, "Unable to authenticate", SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER); - ssh_user_close(s->ppl.ssh, "User aborted during " - "keyboard-interactive authentication"); + ssh_spr_close(s->ppl.ssh, s->spr, "keyboard-" + "interactive authentication prompt"); return; } @@ -1473,14 +1473,14 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) s->username, s->hostname), false); - s->userpass_ret = seat_get_userpass_input( + s->spr = seat_get_userpass_input( ppl_get_iseat(&s->ppl), s->cur_prompt); - while (s->userpass_ret < 0) { + while (s->spr.kind == SPRK_INCOMPLETE) { crReturnV; - s->userpass_ret = seat_get_userpass_input( + s->spr = seat_get_userpass_input( ppl_get_iseat(&s->ppl), s->cur_prompt); } - if (!s->userpass_ret) { + if (spr_is_abort(s->spr)) { /* * Failed to get responses. Terminate. */ @@ -1489,8 +1489,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) ssh_bpp_queue_disconnect( s->ppl.bpp, "Unable to authenticate", SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER); - ssh_user_close(s->ppl.ssh, "User aborted during password " - "authentication"); + ssh_spr_close(s->ppl.ssh, s->spr, "password prompt"); return; } /* @@ -1585,14 +1584,14 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) * password twice. */ while (!got_new) { - s->userpass_ret = seat_get_userpass_input( + s->spr = seat_get_userpass_input( ppl_get_iseat(&s->ppl), s->cur_prompt); - while (s->userpass_ret < 0) { + while (s->spr.kind == SPRK_INCOMPLETE) { crReturnV; - s->userpass_ret = seat_get_userpass_input( + s->spr = seat_get_userpass_input( ppl_get_iseat(&s->ppl), s->cur_prompt); } - if (!s->userpass_ret) { + if (spr_is_abort(s->spr)) { /* * Failed to get responses. Terminate. */ @@ -1604,8 +1603,8 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) ssh_bpp_queue_disconnect( s->ppl.bpp, "Unable to authenticate", SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER); - ssh_user_close(s->ppl.ssh, "User aborted during " - "password changing"); + ssh_spr_close(s->ppl.ssh, s->spr, + "password-change prompt"); return; } diff --git a/stubs/nocmdline.c b/stubs/nocmdline.c index e4c6d08f..9efd9ef2 100644 --- a/stubs/nocmdline.c +++ b/stubs/nocmdline.c @@ -15,9 +15,9 @@ * handling, then there is no such option, so that function always * returns failure. */ -int cmdline_get_passwd_input(prompts_t *p) +SeatPromptResult cmdline_get_passwd_input(prompts_t *p) { - return -1; + return SPR_INCOMPLETE; } /* diff --git a/stubs/noterm.c b/stubs/noterm.c index b5329aec..c2e534b2 100644 --- a/stubs/noterm.c +++ b/stubs/noterm.c @@ -10,7 +10,7 @@ void term_nopaste(Terminal *term) { } -int term_get_userpass_input(Terminal *term, prompts_t *p) +SeatPromptResult term_get_userpass_input(Terminal *term, prompts_t *p) { - return 0; + return SPR_SW_ABORT("No terminal to send interactive prompts to"); } diff --git a/terminal/terminal.c b/terminal/terminal.c index 6efd9cfe..b5d01813 100644 --- a/terminal/terminal.c +++ b/terminal/terminal.c @@ -7672,30 +7672,32 @@ static inline void term_write(Terminal *term, ptrlen data) * notification to the caller, and also turning off our own callback * that listens for more data arriving in the ldisc's input queue. */ -static inline int signal_prompts_t(Terminal *term, prompts_t *p, int result) +static inline SeatPromptResult signal_prompts_t(Terminal *term, prompts_t *p, + SeatPromptResult spr) { assert(p->callback && "Asynchronous userpass input requires a callback"); queue_toplevel_callback(p->callback, p->callback_ctx); ldisc_enable_prompt_callback(term->ldisc, NULL); - p->idata = result; - return result; + p->spr = spr; + return spr; } /* * Process some terminal data in the course of username/password * input. */ -int term_get_userpass_input(Terminal *term, prompts_t *p) +SeatPromptResult term_get_userpass_input(Terminal *term, prompts_t *p) { if (!term->ldisc) { /* Can't handle interactive prompts without an ldisc */ - return signal_prompts_t(term, p, 0); + return signal_prompts_t(term, p, SPR_SW_ABORT( + "Terminal not prepared for interactive prompts")); } - if (p->idata >= 0) { + if (p->spr.kind != SPRK_INCOMPLETE) { /* We've already finished these prompts, so return the same * result again */ - return p->idata; + return p->spr; } struct term_userpass_state *s = (struct term_userpass_state *)p->data; @@ -7705,7 +7707,7 @@ int term_get_userpass_input(Terminal *term, prompts_t *p) * First call. Set some stuff up. */ p->data = s = snew(struct term_userpass_state); - p->idata = -1; + p->spr = SPR_INCOMPLETE; s->curr_prompt = 0; s->done_prompt = false; /* We only print the `name' caption if we have to... */ @@ -7795,7 +7797,7 @@ int term_get_userpass_input(Terminal *term, prompts_t *p) term_write(term, PTRLEN_LITERAL("\r\n")); sfree(s); p->data = NULL; - return signal_prompts_t(term, p, 0); /* user abort */ + return signal_prompts_t(term, p, SPR_USER_ABORT); default: /* * This simplistic check for printability is disabled @@ -7816,11 +7818,11 @@ int term_get_userpass_input(Terminal *term, prompts_t *p) if (s->curr_prompt < p->n_prompts) { ldisc_enable_prompt_callback(term->ldisc, p); - return -1; /* more data required */ + return SPR_INCOMPLETE; } else { sfree(s); p->data = NULL; - return signal_prompts_t(term, p, +1); /* all done */ + return signal_prompts_t(term, p, SPR_OK); } } diff --git a/unix/CMakeLists.txt b/unix/CMakeLists.txt index 9bdd2522..342ca14f 100644 --- a/unix/CMakeLists.txt +++ b/unix/CMakeLists.txt @@ -12,6 +12,7 @@ add_sources_from_current_dir(utils utils/keysym_to_unicode.c utils/make_dir_and_check_ours.c utils/make_dir_path.c + utils/make_spr_sw_abort_errno.c utils/nonblock.c utils/open_for_write_would_lose_data.c utils/pgp_fingerprints.c diff --git a/unix/console.c b/unix/console.c index 4131abdc..434854bd 100644 --- a/unix/console.c +++ b/unix/console.c @@ -102,10 +102,10 @@ static int block_and_read(int fd, void *buf, size_t len) return ret; } -int console_confirm_ssh_host_key( +SeatPromptResult console_confirm_ssh_host_key( Seat *seat, const char *host, int port, const char *keytype, char *keystr, const char *keydisp, char **fingerprints, bool mismatch, - void (*callback)(void *ctx, int result), void *ctx) + void (*callback)(void *ctx, SeatPromptResult result), void *ctx) { char line[32]; struct termios cf; @@ -134,7 +134,7 @@ int console_confirm_ssh_host_key( if (console_batch_mode) { fputs(console_abandoned_msg, stderr); postmsg(&cf); - return 0; + return SPR_SW_ABORT("Cannot confirm a host key in batch mode"); } fputs(intro, stderr); @@ -173,17 +173,17 @@ int console_confirm_ssh_host_key( if (line[0] == 'y' || line[0] == 'Y') store_host_key(host, port, keytype, keystr); postmsg(&cf); - return 1; + return SPR_OK; } else { fputs(console_abandoned_msg, stderr); postmsg(&cf); - return 0; + return SPR_USER_ABORT; } } -int console_confirm_weak_crypto_primitive( +SeatPromptResult console_confirm_weak_crypto_primitive( Seat *seat, const char *algtype, const char *algname, - void (*callback)(void *ctx, int result), void *ctx) + void (*callback)(void *ctx, SeatPromptResult result), void *ctx) { char line[32]; struct termios cf; @@ -194,7 +194,8 @@ int console_confirm_weak_crypto_primitive( if (console_batch_mode) { fputs(console_abandoned_msg, stderr); postmsg(&cf); - return 0; + return SPR_SW_ABORT("Cannot confirm a weak crypto primitive " + "in batch mode"); } fputs(console_continue_prompt, stderr); @@ -214,17 +215,17 @@ int console_confirm_weak_crypto_primitive( if (line[0] == 'y' || line[0] == 'Y') { postmsg(&cf); - return 1; + return SPR_OK; } else { fputs(console_abandoned_msg, stderr); postmsg(&cf); - return 0; + return SPR_USER_ABORT; } } -int console_confirm_weak_cached_hostkey( +SeatPromptResult console_confirm_weak_cached_hostkey( Seat *seat, const char *algname, const char *betteralgs, - void (*callback)(void *ctx, int result), void *ctx) + void (*callback)(void *ctx, SeatPromptResult result), void *ctx) { char line[32]; struct termios cf; @@ -235,7 +236,8 @@ int console_confirm_weak_cached_hostkey( if (console_batch_mode) { fputs(console_abandoned_msg, stderr); postmsg(&cf); - return 0; + return SPR_SW_ABORT("Cannot confirm a weak cached host key " + "in batch mode"); } fputs(console_continue_prompt, stderr); @@ -255,11 +257,11 @@ int console_confirm_weak_cached_hostkey( if (line[0] == 'y' || line[0] == 'Y') { postmsg(&cf); - return 1; + return SPR_OK; } else { fputs(console_abandoned_msg, stderr); postmsg(&cf); - return 0; + return SPR_USER_ABORT; } } @@ -443,7 +445,7 @@ static void console_write(FILE *outfp, ptrlen data) fflush(outfp); } -int console_get_userpass_input(prompts_t *p) +SeatPromptResult console_get_userpass_input(prompts_t *p) { size_t curr_prompt; FILE *outfp = NULL; @@ -459,7 +461,8 @@ int console_get_userpass_input(prompts_t *p) } if (p->n_prompts && console_batch_mode) - return 0; + return SPR_SW_ABORT("Cannot answer interactive prompts " + "in batch mode"); console_open(&outfp, &infd); @@ -498,14 +501,26 @@ int console_get_userpass_input(prompts_t *p) console_write(outfp, ptrlen_from_asciz(pr->prompt)); bool failed = false; + SeatPromptResult spr; while (1) { size_t toread = 65536; size_t prev_result_len = pr->result->len; void *ptr = strbuf_append(pr->result, toread); int ret = read(infd, ptr, toread); - if (ret <= 0) { + if (ret == 0) { + /* Regard EOF on the terminal as a deliberate user-abort */ + failed = true; + spr = SPR_USER_ABORT; + break; + } + + if (ret < 0) { + /* Any other failure to read from the terminal is treated as + * an unexpected error and reported to the user. */ failed = true; + spr = make_spr_sw_abort_errno( + "Error reading from terminal", errno); break; } @@ -521,13 +536,13 @@ int console_get_userpass_input(prompts_t *p) if (failed) { console_close(outfp, infd); - return 0; /* failure due to read error */ + return spr; } } console_close(outfp, infd); - return 1; /* success */ + return SPR_OK; } bool is_interactive(void) diff --git a/unix/dialog.c b/unix/dialog.c index 84a5762f..8f86d116 100644 --- a/unix/dialog.c +++ b/unix/dialog.c @@ -3454,7 +3454,7 @@ struct confirm_ssh_host_key_dialog_ctx { char *keytype; char *keystr; char *more_info; - void (*callback)(void *callback_ctx, int result); + void (*callback)(void *callback_ctx, SeatPromptResult result); void *callback_ctx; Seat *seat; @@ -3468,7 +3468,7 @@ static void confirm_ssh_host_key_result_callback(void *vctx, int result) (struct confirm_ssh_host_key_dialog_ctx *)vctx; if (result >= 0) { - int logical_result; + SeatPromptResult logical_result; /* * Convert the dialog-box return value (one of three @@ -3478,11 +3478,11 @@ static void confirm_ssh_host_key_result_callback(void *vctx, int result) */ if (result == 2) { store_host_key(ctx->host, ctx->port, ctx->keytype, ctx->keystr); - logical_result = 1; /* continue with connection */ + logical_result = SPR_OK; } else if (result == 1) { - logical_result = 1; /* continue with connection */ + logical_result = SPR_OK; } else { - logical_result = 0; /* do not continue with connection */ + logical_result = SPR_USER_ABORT; } ctx->callback(ctx->callback_ctx, logical_result); @@ -3539,10 +3539,10 @@ static void more_info_button_clicked(GtkButton *button, gpointer vctx) &buttons_ok, more_info_closed, ctx); } -int gtk_seat_confirm_ssh_host_key( +SeatPromptResult gtk_seat_confirm_ssh_host_key( Seat *seat, const char *host, int port, const char *keytype, char *keystr, const char *keydisp, char **fingerprints, bool mismatch, - void (*callback)(void *ctx, int result), void *ctx) + void (*callback)(void *ctx, SeatPromptResult result), void *ctx) { static const char absenttxt[] = "The host key is not cached for this server:\n\n" @@ -3641,25 +3641,28 @@ int gtk_seat_confirm_ssh_host_key( sfree(text); - return -1; /* dialog still in progress */ + return SPR_INCOMPLETE; /* dialog still in progress */ } -struct simple_prompt_result_ctx { - void (*callback)(void *callback_ctx, int result); +struct simple_prompt_result_spr_ctx { + void (*callback)(void *callback_ctx, SeatPromptResult spr); void *callback_ctx; Seat *seat; enum DialogSlot dialog_slot; }; -static void simple_prompt_result_callback(void *vctx, int result) +static void simple_prompt_result_spr_callback(void *vctx, int result) { - struct simple_prompt_result_ctx *ctx = - (struct simple_prompt_result_ctx *)vctx; + struct simple_prompt_result_spr_ctx *ctx = + (struct simple_prompt_result_spr_ctx *)vctx; unregister_dialog(ctx->seat, ctx->dialog_slot); - if (result >= 0) - ctx->callback(ctx->callback_ctx, result); + if (result == 0) + ctx->callback(ctx->callback_ctx, SPR_USER_ABORT); + else if (result > 0) + ctx->callback(ctx->callback_ctx, SPR_OK); + /* if <0, we're cleaning up for some other reason */ /* * Clean up this context structure, whether or not a result was @@ -3672,9 +3675,9 @@ static void simple_prompt_result_callback(void *vctx, int result) * Ask whether the selected algorithm is acceptable (since it was * below the configured 'warn' threshold). */ -int gtk_seat_confirm_weak_crypto_primitive( +SeatPromptResult gtk_seat_confirm_weak_crypto_primitive( Seat *seat, const char *algtype, const char *algname, - void (*callback)(void *ctx, int result), void *ctx) + void (*callback)(void *ctx, SeatPromptResult result), void *ctx) { static const char msg[] = "The first %s supported by the server is " @@ -3682,12 +3685,12 @@ int gtk_seat_confirm_weak_crypto_primitive( "Continue with connection?"; char *text; - struct simple_prompt_result_ctx *result_ctx; + struct simple_prompt_result_spr_ctx *result_ctx; GtkWidget *mainwin, *msgbox; text = dupprintf(msg, algtype, algname); - result_ctx = snew(struct simple_prompt_result_ctx); + result_ctx = snew(struct simple_prompt_result_spr_ctx); result_ctx->callback = callback; result_ctx->callback_ctx = ctx; result_ctx->seat = seat; @@ -3697,17 +3700,17 @@ int gtk_seat_confirm_weak_crypto_primitive( msgbox = create_message_box( mainwin, "PuTTY Security Alert", text, string_width("Reasonably long line of text as a width template"), - false, &buttons_yn, simple_prompt_result_callback, result_ctx); + false, &buttons_yn, simple_prompt_result_spr_callback, result_ctx); register_dialog(seat, result_ctx->dialog_slot, msgbox); sfree(text); - return -1; /* dialog still in progress */ + return SPR_INCOMPLETE; } -int gtk_seat_confirm_weak_cached_hostkey( +SeatPromptResult gtk_seat_confirm_weak_cached_hostkey( Seat *seat, const char *algname, const char *betteralgs, - void (*callback)(void *ctx, int result), void *ctx) + void (*callback)(void *ctx, SeatPromptResult result), void *ctx) { static const char msg[] = "The first host key type we have stored for this server\n" @@ -3718,12 +3721,12 @@ int gtk_seat_confirm_weak_cached_hostkey( "Continue with connection?"; char *text; - struct simple_prompt_result_ctx *result_ctx; + struct simple_prompt_result_spr_ctx *result_ctx; GtkWidget *mainwin, *msgbox; text = dupprintf(msg, algname, betteralgs); - result_ctx = snew(struct simple_prompt_result_ctx); + result_ctx = snew(struct simple_prompt_result_spr_ctx); result_ctx->callback = callback; result_ctx->callback_ctx = ctx; result_ctx->seat = seat; @@ -3734,12 +3737,12 @@ int gtk_seat_confirm_weak_cached_hostkey( mainwin, "PuTTY Security Alert", text, string_width("is ecdsa-nistp521, which is below the configured" " warning threshold."), - false, &buttons_yn, simple_prompt_result_callback, result_ctx); + false, &buttons_yn, simple_prompt_result_spr_callback, result_ctx); register_dialog(seat, result_ctx->dialog_slot, msgbox); sfree(text); - return -1; /* dialog still in progress */ + return SPR_INCOMPLETE; } void old_keyfile_warning(void) @@ -4149,6 +4152,30 @@ void logevent_dlg(eventlog_stuff *es, const char *string) } } +struct simple_prompt_result_int_ctx { + void (*callback)(void *callback_ctx, int result); + void *callback_ctx; + Seat *seat; + enum DialogSlot dialog_slot; +}; + +static void simple_prompt_result_int_callback(void *vctx, int result) +{ + struct simple_prompt_result_int_ctx *ctx = + (struct simple_prompt_result_int_ctx *)vctx; + + unregister_dialog(ctx->seat, ctx->dialog_slot); + + if (result >= 0) + ctx->callback(ctx->callback_ctx, result); + + /* + * Clean up this context structure, whether or not a result was + * ever actually delivered from the dialog box. + */ + sfree(ctx); +} + int gtkdlg_askappend(Seat *seat, Filename *filename, void (*callback)(void *ctx, int result), void *ctx) { @@ -4168,13 +4195,13 @@ int gtkdlg_askappend(Seat *seat, Filename *filename, char *message; char *mbtitle; - struct simple_prompt_result_ctx *result_ctx; + struct simple_prompt_result_int_ctx *result_ctx; GtkWidget *mainwin, *msgbox; message = dupprintf(msgtemplate, FILENAME_MAX, filename->path); mbtitle = dupprintf("%s Log to File", appname); - result_ctx = snew(struct simple_prompt_result_ctx); + result_ctx = snew(struct simple_prompt_result_int_ctx); result_ctx->callback = callback; result_ctx->callback_ctx = ctx; result_ctx->seat = seat; @@ -4184,7 +4211,7 @@ int gtkdlg_askappend(Seat *seat, Filename *filename, msgbox = create_message_box( mainwin, mbtitle, message, string_width("LINE OF TEXT SUITABLE FOR THE ASKAPPEND WIDTH"), - false, &buttons_append, simple_prompt_result_callback, result_ctx); + false, &buttons_append, simple_prompt_result_int_callback, result_ctx); register_dialog(seat, result_ctx->dialog_slot, msgbox); sfree(message); diff --git a/unix/pageant.c b/unix/pageant.c index f8a766db..df124dc6 100644 --- a/unix/pageant.c +++ b/unix/pageant.c @@ -437,19 +437,23 @@ static FingerprintType key_list_fptype = SSH_FPTYPE_DEFAULT; static char *askpass_tty(const char *prompt) { - int ret; prompts_t *p = new_prompts(); p->to_server = false; p->from_server = false; p->name = dupstr("Pageant passphrase prompt"); add_prompt(p, dupcat(prompt, ": "), false); - ret = console_get_userpass_input(p); - assert(ret >= 0); + SeatPromptResult spr = console_get_userpass_input(p); + assert(spr.kind != SPRK_INCOMPLETE); - if (!ret) { - perror("pageant: unable to read passphrase"); + if (spr.kind == SPRK_USER_ABORT) { free_prompts(p); return NULL; + } else if (spr.kind == SPRK_SW_ABORT) { + free_prompts(p); + char *err = spr_get_error_message(spr); + fprintf(stderr, "pageant: unable to read passphrase: %s", err); + sfree(err); + return NULL; } else { char *passphrase = prompt_get_result(p->prompts[0]); free_prompts(p); diff --git a/unix/platform.h b/unix/platform.h index 86996b3d..58cb4fbf 100644 --- a/unix/platform.h +++ b/unix/platform.h @@ -217,16 +217,16 @@ void showeventlog(eventlog_stuff *estuff, void *parentwin); void logevent_dlg(eventlog_stuff *estuff, const char *string); int gtkdlg_askappend(Seat *seat, Filename *filename, void (*callback)(void *ctx, int result), void *ctx); -int gtk_seat_confirm_ssh_host_key( +SeatPromptResult gtk_seat_confirm_ssh_host_key( Seat *seat, const char *host, int port, const char *keytype, char *keystr, const char *keydisp, char **fingerprints, bool mismatch, - void (*callback)(void *ctx, int result), void *ctx); -int gtk_seat_confirm_weak_crypto_primitive( + void (*callback)(void *ctx, SeatPromptResult result), void *ctx); +SeatPromptResult gtk_seat_confirm_weak_crypto_primitive( Seat *seat, const char *algtype, const char *algname, - void (*callback)(void *ctx, int result), void *ctx); -int gtk_seat_confirm_weak_cached_hostkey( + void (*callback)(void *ctx, SeatPromptResult result), void *ctx); +SeatPromptResult gtk_seat_confirm_weak_cached_hostkey( Seat *seat, const char *algname, const char *betteralgs, - void (*callback)(void *ctx, int result), void *ctx); + void (*callback)(void *ctx, SeatPromptResult result), void *ctx); #ifdef MAY_REFER_TO_GTK_IN_HEADERS struct message_box_button { const char *title; @@ -464,4 +464,6 @@ bool cliloop_always_continue(void *ctx, bool, bool); /* network.c: network error reporting helper taking an OS error code */ void plug_closing_errno(Plug *plug, int error); +SeatPromptResult make_spr_sw_abort_errno(const char *prefix, int errno_value); + #endif /* PUTTY_UNIX_PLATFORM_H */ diff --git a/unix/plink.c b/unix/plink.c index 385fe993..ca7fcad8 100644 --- a/unix/plink.c +++ b/unix/plink.c @@ -371,13 +371,13 @@ static bool plink_eof(Seat *seat) return false; /* do not respond to incoming EOF with outgoing */ } -static int plink_get_userpass_input(Seat *seat, prompts_t *p) +static SeatPromptResult plink_get_userpass_input(Seat *seat, prompts_t *p) { - int ret; - ret = cmdline_get_passwd_input(p); - if (ret == -1) - ret = console_get_userpass_input(p); - return ret; + SeatPromptResult spr; + spr = cmdline_get_passwd_input(p); + if (spr.kind == SPRK_INCOMPLETE) + spr = console_get_userpass_input(p); + return spr; } static bool plink_seat_interactive(Seat *seat) diff --git a/unix/sftp.c b/unix/sftp.c index 331cbc70..92f3f8ed 100644 --- a/unix/sftp.c +++ b/unix/sftp.c @@ -63,13 +63,13 @@ Filename *platform_default_filename(const char *name) return filename_from_str(""); } -int filexfer_get_userpass_input(Seat *seat, prompts_t *p) +SeatPromptResult filexfer_get_userpass_input(Seat *seat, prompts_t *p) { - int ret; - ret = cmdline_get_passwd_input(p); - if (ret == -1) - ret = console_get_userpass_input(p); - return ret; + SeatPromptResult spr; + spr = cmdline_get_passwd_input(p); + if (spr.kind == SPRK_INCOMPLETE) + spr = console_get_userpass_input(p); + return spr; } /* diff --git a/unix/utils/make_spr_sw_abort_errno.c b/unix/utils/make_spr_sw_abort_errno.c new file mode 100644 index 00000000..57f58c2f --- /dev/null +++ b/unix/utils/make_spr_sw_abort_errno.c @@ -0,0 +1,22 @@ +/* + * Constructor function for a SeatPromptResult of the 'software abort' + * category, whose error message includes the translation of an OS + * error code. + */ + +#include "putty.h" + +static void spr_errno_errfn(SeatPromptResult spr, BinarySink *bs) +{ + put_fmt(bs, "%s: %s", spr.errdata_lit, strerror(spr.errdata_u)); +} + +SeatPromptResult make_spr_sw_abort_errno(const char *prefix, int errno_value) +{ + SeatPromptResult spr; + spr.kind = SPRK_SW_ABORT; + spr.errfn = spr_errno_errfn; + spr.errdata_lit = prefix; + spr.errdata_u = errno_value; + return spr; +} diff --git a/unix/window.c b/unix/window.c index dba32c27..93281429 100644 --- a/unix/window.c +++ b/unix/window.c @@ -339,14 +339,14 @@ static bool gtk_seat_eof(Seat *seat) return true; /* do respond to incoming EOF with outgoing */ } -static int gtk_seat_get_userpass_input(Seat *seat, prompts_t *p) +static SeatPromptResult gtk_seat_get_userpass_input(Seat *seat, prompts_t *p) { GtkFrontend *inst = container_of(seat, GtkFrontend, seat); - int ret; - ret = cmdline_get_passwd_input(p); - if (ret == -1) - ret = term_get_userpass_input(inst->term, p); - return ret; + SeatPromptResult spr; + spr = cmdline_get_passwd_input(p); + if (spr.kind == SPRK_INCOMPLETE) + spr = term_get_userpass_input(inst->term, p); + return spr; } static bool gtk_seat_is_utf8(Seat *seat) diff --git a/utils/CMakeLists.txt b/utils/CMakeLists.txt index 86f48952..4a6a1fe8 100644 --- a/utils/CMakeLists.txt +++ b/utils/CMakeLists.txt @@ -26,6 +26,7 @@ add_sources_from_current_dir(utils host_strduptrim.c host_strrchr.c log_proxy_stderr.c + make_spr_sw_abort_static.c marshal.c memory.c memxor.c @@ -42,6 +43,7 @@ add_sources_from_current_dir(utils sk_free_peer_info.c smemclr.c smemeq.c + spr_get_error_message.c ssh2_pick_fingerprint.c sshutils.c strbuf.c diff --git a/utils/make_spr_sw_abort_static.c b/utils/make_spr_sw_abort_static.c new file mode 100644 index 00000000..f9eac59f --- /dev/null +++ b/utils/make_spr_sw_abort_static.c @@ -0,0 +1,21 @@ +/* + * Constructor function for a SeatPromptResult of the 'software abort' + * category, whose error message is in the simplest possible form of a + * static string constant. + */ + +#include "putty.h" + +static void spr_static_errfn(SeatPromptResult spr, BinarySink *bs) +{ + put_dataz(bs, spr.errdata_lit); +} + +SeatPromptResult make_spr_sw_abort_static(const char *str) +{ + SeatPromptResult spr; + spr.kind = SPRK_SW_ABORT; + spr.errfn = spr_static_errfn; + spr.errdata_lit = str; + return spr; +} diff --git a/utils/nullseat.c b/utils/nullseat.c index 43a338fb..ba09839e 100644 --- a/utils/nullseat.c +++ b/utils/nullseat.c @@ -11,7 +11,8 @@ void nullseat_sent(Seat *seat, size_t bufsize) {} size_t nullseat_banner(Seat *seat, const void *data, size_t len) {return 0;} size_t nullseat_banner_to_stderr(Seat *seat, const void *data, size_t len) { return seat_output(seat, SEAT_OUTPUT_STDERR, data, len); } -int nullseat_get_userpass_input(Seat *seat, prompts_t *p) { return 0; } +SeatPromptResult nullseat_get_userpass_input(Seat *seat, prompts_t *p) +{ return SPR_SW_ABORT("this seat can't handle interactive prompts"); } void nullseat_notify_session_started(Seat *seat) {} void nullseat_notify_remote_exit(Seat *seat) {} void nullseat_notify_remote_disconnect(Seat *seat) {} @@ -19,16 +20,19 @@ void nullseat_connection_fatal(Seat *seat, const char *message) {} void nullseat_update_specials_menu(Seat *seat) {} char *nullseat_get_ttymode(Seat *seat, const char *mode) { return NULL; } void nullseat_set_busy_status(Seat *seat, BusyStatus status) {} -int nullseat_confirm_ssh_host_key( +SeatPromptResult nullseat_confirm_ssh_host_key( Seat *seat, const char *host, int port, const char *keytype, char *keystr, const char *keydisp, char **key_fingerprints, bool mismatch, - void (*callback)(void *ctx, int result), void *ctx) { return 0; } -int nullseat_confirm_weak_crypto_primitive( + void (*callback)(void *ctx, SeatPromptResult result), void *ctx) +{ return SPR_SW_ABORT("this seat can't handle interactive prompts"); } +SeatPromptResult nullseat_confirm_weak_crypto_primitive( Seat *seat, const char *algtype, const char *algname, - void (*callback)(void *ctx, int result), void *ctx) { return 0; } -int nullseat_confirm_weak_cached_hostkey( + void (*callback)(void *ctx, SeatPromptResult result), void *ctx) +{ return SPR_SW_ABORT("this seat can't handle interactive prompts"); } +SeatPromptResult nullseat_confirm_weak_cached_hostkey( Seat *seat, const char *algname, const char *betteralgs, - void (*callback)(void *ctx, int result), void *ctx) { return 0; } + void (*callback)(void *ctx, SeatPromptResult result), void *ctx) +{ return SPR_SW_ABORT("this seat can't handle interactive prompts"); } bool nullseat_is_never_utf8(Seat *seat) { return false; } bool nullseat_is_always_utf8(Seat *seat) { return true; } void nullseat_echoedit_update(Seat *seat, bool echoing, bool editing) {} diff --git a/utils/prompts.c b/utils/prompts.c index e01dd01c..d0823334 100644 --- a/utils/prompts.c +++ b/utils/prompts.c @@ -11,7 +11,7 @@ prompts_t *new_prompts(void) p->prompts = NULL; p->n_prompts = p->prompts_size = 0; p->data = NULL; - p->idata = -1; + p->spr = SPR_INCOMPLETE; p->to_server = true; /* to be on the safe side */ p->name = p->instruction = NULL; p->name_reqd = p->instr_reqd = false; diff --git a/utils/spr_get_error_message.c b/utils/spr_get_error_message.c new file mode 100644 index 00000000..b9f81a47 --- /dev/null +++ b/utils/spr_get_error_message.c @@ -0,0 +1,13 @@ +/* + * Construct the error message from a SeatPromptResult, and return it + * in a dynamically allocated string. + */ + +#include "putty.h" + +char *spr_get_error_message(SeatPromptResult spr) +{ + strbuf *sb = strbuf_new(); + spr.errfn(spr, BinarySink_UPCAST(sb)); + return strbuf_to_str(sb); +} diff --git a/utils/tempseat.c b/utils/tempseat.c index 5854a5dc..785c00c2 100644 --- a/utils/tempseat.c +++ b/utils/tempseat.c @@ -218,7 +218,7 @@ static bool tempseat_has_mixed_input_stream(Seat *seat) * for the network connection. */ -static int tempseat_get_userpass_input(Seat *seat, prompts_t *p) +static SeatPromptResult tempseat_get_userpass_input(Seat *seat, prompts_t *p) { /* * Interactive prompts of this nature are a thing that a backend @@ -235,25 +235,25 @@ static size_t tempseat_banner(Seat *seat, const void *data, size_t len) unreachable("banner should never be called on TempSeat"); } -static int tempseat_confirm_ssh_host_key( +static SeatPromptResult tempseat_confirm_ssh_host_key( Seat *seat, const char *host, int port, const char *keytype, char *keystr, const char *keydisp, char **key_fingerprints, bool mismatch, - void (*callback)(void *ctx, int result), void *ctx) + void (*callback)(void *ctx, SeatPromptResult result), void *ctx) { unreachable("confirm_ssh_host_key should never be called on TempSeat"); } -static int tempseat_confirm_weak_crypto_primitive( +static SeatPromptResult tempseat_confirm_weak_crypto_primitive( Seat *seat, const char *algtype, const char *algname, - void (*callback)(void *ctx, int result), void *ctx) + void (*callback)(void *ctx, SeatPromptResult result), void *ctx) { unreachable("confirm_weak_crypto_primitive " "should never be called on TempSeat"); } -static int tempseat_confirm_weak_cached_hostkey( +static SeatPromptResult tempseat_confirm_weak_cached_hostkey( Seat *seat, const char *algname, const char *betteralgs, - void (*callback)(void *ctx, int result), void *ctx) + void (*callback)(void *ctx, SeatPromptResult result), void *ctx) { unreachable("confirm_weak_cached_hostkey " "should never be called on TempSeat"); diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt index 7338f4c2..0a999e51 100644 --- a/windows/CMakeLists.txt +++ b/windows/CMakeLists.txt @@ -16,6 +16,7 @@ add_sources_from_current_dir(utils utils/load_system32_dll.c utils/ltime.c utils/makedlgitemborderless.c + utils/make_spr_sw_abort_winerror.c utils/message_box.c utils/minefield.c utils/open_for_write_would_lose_data.c diff --git a/windows/console.c b/windows/console.c index 9fa9d7f0..8cb27c5a 100644 --- a/windows/console.c +++ b/windows/console.c @@ -32,10 +32,10 @@ void console_print_error_msg(const char *prefix, const char *msg) fflush(stderr); } -int console_confirm_ssh_host_key( +SeatPromptResult console_confirm_ssh_host_key( Seat *seat, const char *host, int port, const char *keytype, char *keystr, const char *keydisp, char **fingerprints, bool mismatch, - void (*callback)(void *ctx, int result), void *ctx) + void (*callback)(void *ctx, SeatPromptResult result), void *ctx) { HANDLE hin; DWORD savemode, i; @@ -64,7 +64,7 @@ int console_confirm_ssh_host_key( if (console_batch_mode) { fputs(console_abandoned_msg, stderr); - return 0; + return SPR_SW_ABORT("Cannot confirm a host key in batch mode"); } fputs(intro, stderr); @@ -102,16 +102,16 @@ int console_confirm_ssh_host_key( line[0] != 'q' && line[0] != 'Q') { if (line[0] == 'y' || line[0] == 'Y') store_host_key(host, port, keytype, keystr); - return 1; + return SPR_OK; } else { fputs(console_abandoned_msg, stderr); - return 0; + return SPR_USER_ABORT; } } -int console_confirm_weak_crypto_primitive( +SeatPromptResult console_confirm_weak_crypto_primitive( Seat *seat, const char *algtype, const char *algname, - void (*callback)(void *ctx, int result), void *ctx) + void (*callback)(void *ctx, SeatPromptResult result), void *ctx) { HANDLE hin; DWORD savemode, i; @@ -122,7 +122,8 @@ int console_confirm_weak_crypto_primitive( if (console_batch_mode) { fputs(console_abandoned_msg, stderr); - return 0; + return SPR_SW_ABORT("Cannot confirm a weak crypto primitive " + "in batch mode"); } fputs(console_continue_prompt, stderr); @@ -136,16 +137,16 @@ int console_confirm_weak_crypto_primitive( SetConsoleMode(hin, savemode); if (line[0] == 'y' || line[0] == 'Y') { - return 1; + return SPR_OK; } else { fputs(console_abandoned_msg, stderr); - return 0; + return SPR_USER_ABORT; } } -int console_confirm_weak_cached_hostkey( +SeatPromptResult console_confirm_weak_cached_hostkey( Seat *seat, const char *algname, const char *betteralgs, - void (*callback)(void *ctx, int result), void *ctx) + void (*callback)(void *ctx, SeatPromptResult result), void *ctx) { HANDLE hin; DWORD savemode, i; @@ -156,7 +157,8 @@ int console_confirm_weak_cached_hostkey( if (console_batch_mode) { fputs(console_abandoned_msg, stderr); - return 0; + return SPR_SW_ABORT("Cannot confirm a weak cached host key " + "in batch mode"); } fputs(console_continue_prompt, stderr); @@ -170,10 +172,10 @@ int console_confirm_weak_cached_hostkey( SetConsoleMode(hin, savemode); if (line[0] == 'y' || line[0] == 'Y') { - return 1; + return SPR_OK; } else { fputs(console_abandoned_msg, stderr); - return 0; + return SPR_USER_ABORT; } } @@ -343,7 +345,7 @@ static void console_write(HANDLE hout, ptrlen data) WriteFile(hout, data.ptr, data.len, &dummy, NULL); } -int console_get_userpass_input(prompts_t *p) +SeatPromptResult console_get_userpass_input(prompts_t *p) { HANDLE hin = INVALID_HANDLE_VALUE, hout = INVALID_HANDLE_VALUE; size_t curr_prompt; @@ -365,7 +367,8 @@ int console_get_userpass_input(prompts_t *p) */ if (p->n_prompts) { if (console_batch_mode) - return 0; + return SPR_SW_ABORT("Cannot answer interactive prompts " + "in batch mode"); hin = GetStdHandle(STD_INPUT_HANDLE); if (hin == INVALID_HANDLE_VALUE) { fprintf(stderr, "Cannot get standard input handle\n"); @@ -418,6 +421,7 @@ int console_get_userpass_input(prompts_t *p) console_write(hout, ptrlen_from_asciz(pr->prompt)); bool failed = false; + SeatPromptResult spr; while (1) { /* * Amount of data to try to read from the console in one @@ -440,8 +444,17 @@ int console_get_userpass_input(prompts_t *p) void *ptr = strbuf_append(pr->result, toread); DWORD ret = 0; - if (!ReadFile(hin, ptr, toread, &ret, NULL) || ret == 0) { + if (!ReadFile(hin, ptr, toread, &ret, NULL)) { + /* An OS error when reading from the console is treated as an + * unexpected error and reported to the user. */ failed = true; + spr = make_spr_sw_abort_winerror( + "Error reading from console", GetLastError()); + break; + } else if (ret == 0) { + /* Regard EOF on the terminal as a deliberate user-abort */ + failed = true; + spr = SPR_USER_ABORT; break; } @@ -457,10 +470,9 @@ int console_get_userpass_input(prompts_t *p) if (!pr->echo) console_write(hout, PTRLEN_LITERAL("\r\n")); - if (failed) { - return 0; /* failure due to read error */ - } + if (failed) + return spr; } - return 1; /* success */ + return SPR_OK; } diff --git a/windows/dialog.c b/windows/dialog.c index b790f0f4..66f26128 100644 --- a/windows/dialog.c +++ b/windows/dialog.c @@ -976,10 +976,10 @@ static INT_PTR CALLBACK HostKeyDialogProc(HWND hwnd, UINT msg, return 0; } -int win_seat_confirm_ssh_host_key( +SeatPromptResult win_seat_confirm_ssh_host_key( Seat *seat, const char *host, int port, const char *keytype, char *keystr, const char *keydisp, char **fingerprints, bool mismatch, - void (*callback)(void *ctx, int result), void *vctx) + void (*callback)(void *ctx, SeatPromptResult result), void *vctx) { WinGuiSeat *wgs = container_of(seat, WinGuiSeat, seat); @@ -1008,21 +1008,21 @@ int win_seat_confirm_ssh_host_key( assert(mbret==IDC_HK_ACCEPT || mbret==IDC_HK_ONCE || mbret==IDCANCEL); if (mbret == IDC_HK_ACCEPT) { store_host_key(host, port, keytype, keystr); - return 1; + return SPR_OK; } else if (mbret == IDC_HK_ONCE) { - return 1; + return SPR_OK; } - return 0; /* abandon the connection */ + return SPR_USER_ABORT; } /* * Ask whether the selected algorithm is acceptable (since it was * below the configured 'warn' threshold). */ -int win_seat_confirm_weak_crypto_primitive( +SeatPromptResult win_seat_confirm_weak_crypto_primitive( Seat *seat, const char *algtype, const char *algname, - void (*callback)(void *ctx, int result), void *ctx) + void (*callback)(void *ctx, SeatPromptResult result), void *ctx) { static const char mbtitle[] = "%s Security Alert"; static const char msg[] = @@ -1041,14 +1041,14 @@ int win_seat_confirm_weak_crypto_primitive( sfree(message); sfree(title); if (mbret == IDYES) - return 1; + return SPR_OK; else - return 0; + return SPR_USER_ABORT; } -int win_seat_confirm_weak_cached_hostkey( +SeatPromptResult win_seat_confirm_weak_cached_hostkey( Seat *seat, const char *algname, const char *betteralgs, - void (*callback)(void *ctx, int result), void *ctx) + void (*callback)(void *ctx, SeatPromptResult result), void *ctx) { static const char mbtitle[] = "%s Security Alert"; static const char msg[] = @@ -1069,9 +1069,9 @@ int win_seat_confirm_weak_cached_hostkey( sfree(message); sfree(title); if (mbret == IDYES) - return 1; + return SPR_OK; else - return 0; + return SPR_USER_ABORT; } /* diff --git a/windows/platform.h b/windows/platform.h index 660bf590..78ebf188 100644 --- a/windows/platform.h +++ b/windows/platform.h @@ -216,16 +216,16 @@ int has_embedded_chm(void); /* 1 = yes, 0 = no, -1 = N/A */ * GUI seat methods in windlg.c, so that the vtable definition in * window.c can refer to them. */ -int win_seat_confirm_ssh_host_key( +SeatPromptResult win_seat_confirm_ssh_host_key( Seat *seat, const char *host, int port, const char *keytype, char *keystr, const char *keydisp, char **key_fingerprints, bool mismatch, - void (*callback)(void *ctx, int result), void *ctx); -int win_seat_confirm_weak_crypto_primitive( + void (*callback)(void *ctx, SeatPromptResult result), void *ctx); +SeatPromptResult win_seat_confirm_weak_crypto_primitive( Seat *seat, const char *algtype, const char *algname, - void (*callback)(void *ctx, int result), void *ctx); -int win_seat_confirm_weak_cached_hostkey( + void (*callback)(void *ctx, SeatPromptResult result), void *ctx); +SeatPromptResult win_seat_confirm_weak_cached_hostkey( Seat *seat, const char *algname, const char *betteralgs, - void (*callback)(void *ctx, int result), void *ctx); + void (*callback)(void *ctx, SeatPromptResult result), void *ctx); /* * Windows-specific clipboard helper function shared with windlg.c, @@ -734,4 +734,6 @@ bool handle_special_filemapping_cmdline(char *cmdline, Conf *conf); void plug_closing_system_error(Plug *plug, DWORD error); void plug_closing_winsock_error(Plug *plug, DWORD error); +SeatPromptResult make_spr_sw_abort_winerror(const char *prefix, DWORD error); + #endif /* PUTTY_WINDOWS_PLATFORM_H */ diff --git a/windows/plink.c b/windows/plink.c index 037387e9..af95c2b9 100644 --- a/windows/plink.c +++ b/windows/plink.c @@ -65,13 +65,13 @@ static bool plink_eof(Seat *seat) return false; /* do not respond to incoming EOF with outgoing */ } -static int plink_get_userpass_input(Seat *seat, prompts_t *p) +static SeatPromptResult plink_get_userpass_input(Seat *seat, prompts_t *p) { - int ret; - ret = cmdline_get_passwd_input(p); - if (ret == -1) - ret = console_get_userpass_input(p); - return ret; + SeatPromptResult spr; + spr = cmdline_get_passwd_input(p); + if (spr.kind == SPRK_INCOMPLETE) + spr = console_get_userpass_input(p); + return spr; } static bool plink_seat_interactive(Seat *seat) diff --git a/windows/sftp.c b/windows/sftp.c index b2e90faa..bc16f5d1 100644 --- a/windows/sftp.c +++ b/windows/sftp.c @@ -12,13 +12,13 @@ #include "ssh.h" #include "security-api.h" -int filexfer_get_userpass_input(Seat *seat, prompts_t *p) +SeatPromptResult filexfer_get_userpass_input(Seat *seat, prompts_t *p) { - int ret; - ret = cmdline_get_passwd_input(p); - if (ret == -1) - ret = console_get_userpass_input(p); - return ret; + SeatPromptResult spr; + spr = cmdline_get_passwd_input(p); + if (spr.kind == SPRK_INCOMPLETE) + spr = console_get_userpass_input(p); + return spr; } void platform_get_x11_auth(struct X11Display *display, Conf *conf) diff --git a/windows/utils/make_spr_sw_abort_winerror.c b/windows/utils/make_spr_sw_abort_winerror.c new file mode 100644 index 00000000..b05ef61f --- /dev/null +++ b/windows/utils/make_spr_sw_abort_winerror.c @@ -0,0 +1,22 @@ +/* + * Constructor function for a SeatPromptResult of the 'software abort' + * category, whose error message includes the translation of an OS + * error code. + */ + +#include "putty.h" + +static void spr_winerror_errfn(SeatPromptResult spr, BinarySink *bs) +{ + put_fmt(bs, "%s: %s", spr.errdata_lit, win_strerror(spr.errdata_u)); +} + +SeatPromptResult make_spr_sw_abort_winerror(const char *prefix, DWORD error) +{ + SeatPromptResult spr; + spr.kind = SPRK_SW_ABORT; + spr.errfn = spr_winerror_errfn; + spr.errdata_lit = prefix; + spr.errdata_u = error; + return spr; +} diff --git a/windows/window.c b/windows/window.c index b1a884dd..4d61a709 100644 --- a/windows/window.c +++ b/windows/window.c @@ -320,7 +320,7 @@ static StripCtrlChars *win_seat_stripctrl_new( static size_t win_seat_output( Seat *seat, SeatOutputType type, const void *, size_t); static bool win_seat_eof(Seat *seat); -static int win_seat_get_userpass_input(Seat *seat, prompts_t *p); +static SeatPromptResult win_seat_get_userpass_input(Seat *seat, prompts_t *p); static void win_seat_notify_remote_exit(Seat *seat); static void win_seat_connection_fatal(Seat *seat, const char *msg); static void win_seat_update_specials_menu(Seat *seat); @@ -5787,13 +5787,13 @@ static bool win_seat_eof(Seat *seat) return true; /* do respond to incoming EOF with outgoing */ } -static int win_seat_get_userpass_input(Seat *seat, prompts_t *p) +static SeatPromptResult win_seat_get_userpass_input(Seat *seat, prompts_t *p) { - int ret; - ret = cmdline_get_passwd_input(p); - if (ret == -1) - ret = term_get_userpass_input(term, p); - return ret; + SeatPromptResult spr; + spr = cmdline_get_passwd_input(p); + if (spr.kind == SPRK_INCOMPLETE) + spr = term_get_userpass_input(term, p); + return spr; } static void win_seat_set_trust_status(Seat *seat, bool trusted) -- cgit v1.2.3 From b94bdac931bbe8f66b32595dea8daec63f286a21 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Wed, 29 Dec 2021 16:35:57 +0000 Subject: Document Cygwin as a use case for psusan. I have _no_ idea how I managed to leave this out of the list of examples when I first wrote this man page. It should have been the very first one I thought of, since Cygwin was the platform I wrote cygtermd for, and one of psusan's primary purposes was to be a productised and improved replacement for cygtermd! Oh well, better late than never. --- doc/man-psusan.but | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/doc/man-psusan.but b/doc/man-psusan.but index a9c8baab..64d3a030 100644 --- a/doc/man-psusan.but +++ b/doc/man-psusan.but @@ -252,6 +252,39 @@ ports in and out of the WSL environment (e.g. expose a WSL2 network service through the hypervisor's internal NAT), forward Pageant into it, and so on. +\S2{psusan-manpage-examples-cygwin} Cygwin + +Another Unix-like environment on Windows is Cygwin. That comes with +its own GUI terminal application, \cw{mintty} (as it happens, a +derivative of PuTTY); but if you'd prefer to use PuTTY itself to talk +to your Cygwin terminal sessions, \cw{psusan} can help. + +To do this, you'll first need to build the Unix PuTTY tools inside +Cygwin (via the usual \cw{cmake} method). Then, copy the resulting +\cw{psusan.exe} into Cygwin's \cw{/bin} directory. (It has to be +in that directory for non-Cygwin programs to run it; otherwise it +won't be able to find the Cygwin DLL at startup.) + +Then set up your PuTTY saved session like this: + +\b set the local proxy command to run \cw{psusan.exe} via its real +Windows path. You might also want to add the \cw{--sessiondir} option +so that shell sessions start up in your Cygwin home directory. For +example, you might use the command \cq{c:\\cygwin64\\bin\\psusan.exe +--sessiondir /home/simon} (changing the pathname and username to match +your setup). + +\b enter anything you like in the host name box; \cq{Cygwin} is +probably a good choice + +\b set the protocol to \q{Bare ssh-connection}, as usual. + +Port forwarding is probably not particularly useful in this case, +since Cygwin shares the same network port space as the host machine. +But turning on agent forwarding is useful, because then the Cygwin +command-line SSH client can talk to Pageant without any further +configuration. + \S2{psusan-manpage-examples-schroot} \cw{schroot} Another example of a container-like environment is the alternative -- cgit v1.2.3 From 4d1b125885e469e41c78772bb493f68f66199324 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 30 Dec 2021 11:47:58 +0000 Subject: SSH proxying: use CONF_proxy_password if available. I was just writing the documentation for the new proxy type, which caused me to realise that the thing I obviously wanted to write in the documentation was not actually true. Let's make it true, and then I can document it! --- proxy/sshproxy.c | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/proxy/sshproxy.c b/proxy/sshproxy.c index bc514452..ee2bee6e 100644 --- a/proxy/sshproxy.c +++ b/proxy/sshproxy.c @@ -22,6 +22,9 @@ typedef struct SshProxy { Seat *clientseat; Interactor *clientitr; + bool got_proxy_password, tried_proxy_password; + char *proxy_password; + ProxyStderrBuf psb; Plug *plug; @@ -61,6 +64,8 @@ static void sshproxy_close(Socket *s) backend_free(sp->backend); if (sp->logctx) log_free(sp->logctx); + if (sp->proxy_password) + burnstr(sp->proxy_password); bufchain_clear(&sp->ssh_to_socket); delete_callbacks_for_context(sp); @@ -334,6 +339,21 @@ static SeatPromptResult sshproxy_get_userpass_input(Seat *seat, prompts_t *p) { SshProxy *sp = container_of(seat, SshProxy, seat); + /* + * If we have a stored proxy_password, use that, via logic similar + * to cmdline_get_passwd_input: we only try it if we're given a + * prompts_t containing exactly one prompt, and that prompt is set + * to non-echoing. + */ + if (sp->got_proxy_password && !sp->tried_proxy_password && + p->n_prompts == 1 && !p->prompts[0]->echo) { + prompt_set_result(p->prompts[0], sp->proxy_password); + burnstr(sp->proxy_password); + sp->proxy_password = NULL; + sp->tried_proxy_password = true; + return SPR_OK; + } + if (sp->clientseat) { /* * If we have access to the outer Seat, pass this prompt @@ -556,6 +576,12 @@ Socket *sshproxy_new_connection(SockAddr *addr, const char *hostname, if (*proxy_username) conf_set_str(sp->conf, CONF_username, proxy_username); + const char *proxy_password = conf_get_str(clientconf, CONF_proxy_password); + if (*proxy_password) { + sp->proxy_password = dupstr(proxy_password); + sp->got_proxy_password = true; + } + const struct BackendVtable *backvt = backend_vt_from_proto( conf_get_int(sp->conf, CONF_protocol)); -- cgit v1.2.3 From f9d0557330f985ff23dbe5b8e014dc22cecbf13e Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 30 Dec 2021 11:49:35 +0000 Subject: Update proxy documentation. This update covers several recently added features: SSH proxying, HTTP Digest proxy auth, and interactive prompting for proxy auth in general. Also, downplayed the use of 'plink -nc' as a Local-type proxy command. It still works, but it's no longer the recommended way of tunnelling SSH over SSH, so there's no need to explain it quite so enthusiastically. --- doc/config.but | 34 ++++++++++++++++++++++++++-------- doc/index.but | 7 +++++-- doc/using.but | 22 +++++++++++++--------- 3 files changed, 44 insertions(+), 19 deletions(-) diff --git a/doc/config.but b/doc/config.but index df935388..b5ad41d0 100644 --- a/doc/config.but +++ b/doc/config.but @@ -1961,6 +1961,12 @@ 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 \q{SSH} causes PuTTY to make a secondary SSH connection +to the proxy host, and then open a port-forwarding channel to the +final destination host. The \q{Proxy hostname} field will be +interpreted as the name of a PuTTY saved session if one exists, or a +hostname if not. + \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 @@ -1972,11 +1978,6 @@ 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. - You can also enable this mode on the command line; see \k{using-cmdline-proxycmd}. } @@ -2069,18 +2070,35 @@ proxies and SOCKS 5 proxies. 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 With HTTP proxying, authentication is via \q{\i{HTTP Digest}} if +possible (again, not supported in PuTTYtel), or \q{\i{HTTP Basic}}. In +the latter case, 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 SSH proxying can use all the same forms of SSH authentication +supported by PuTTY for its main connection. If the SSH server requests +password authentication, the configured proxy password will be used, +but other authentication methods such as public keys will be tried +first, just as for a primary SSH connection. + \b You can specify a way to include a username and password in the Telnet/Local proxy command (see \k{config-proxy-command}). +If PuTTY discovers that it needs a proxy username or password and you +have not specified one in the configuration, it will prompt for it +interactively in the terminal. + +(For SSH proxying, this will also happen in the case of other +interactive SSH login prompts, such as SSH key passphrases or GSSAPI. +For the Telnet and Local proxy types, PuTTY will prompt for a username +or password if you included \c{%user} or \c{%pass} in the command +string and did not provide a corresponding configuration entry.) + \S{config-proxy-command} Specifying the Telnet or Local proxy command If you are using the \i{Telnet proxy} type, the usual command required diff --git a/doc/index.but b/doc/index.but index 1e04acc1..afd74167 100644 --- a/doc/index.but +++ b/doc/index.but @@ -608,8 +608,11 @@ saved sessions from \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{HTTP Basic} HTTP Basic authentication +\IM{HTTP Basic} \q{basic} authentication (HTTP) + +\IM{HTTP Digest} HTTP Digest authentication +\IM{HTTP Digest} \q{digest} authentication (HTTP) \IM{plaintext password} plain text password \IM{plaintext password} password, plain text diff --git a/doc/using.but b/doc/using.but index 8811b5fa..d5cf41c4 100644 --- a/doc/using.but +++ b/doc/using.but @@ -947,15 +947,19 @@ 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 can be useful if you're trying to make a connection to a target +host which you can only reach by SSH forwarding through a proxy host. +One way to do this would be to have an existing SSH connection to the +proxy host, with a port forwarding, but if you prefer to have the +connection started on demand as needed, then this approach can also +work. + +However, this does depend on the program \e{using} the proxy being +able to run a subprocess in place of making a network connection. +PuTTY itself can do this using the \q{Local} proxy type, but there's a +built-in more flexible way using the \q{SSH} proxy type. (See +\k{config-proxy-type} for a description of both.) So this feature is +probably most useful with another client program as the end user. 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 -- cgit v1.2.3 From 4ecb40a60d26a1b9f6705940cec8f49b8a4eab2d Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 3 Jan 2022 06:38:07 +0000 Subject: Fix a batch of typos in comments and docs. --- contrib/gdb.py | 2 +- crypto/argon2.c | 2 +- crypto/mpint.c | 2 +- doc/config.but | 4 ++-- ssh/sftp.h | 2 +- ssh/transport2.c | 2 +- unix/window.c | 2 +- utils/tree234.c | 2 +- windows/jump-list.c | 2 +- windows/window.c | 2 +- 10 files changed, 11 insertions(+), 11 deletions(-) diff --git a/contrib/gdb.py b/contrib/gdb.py index 34bbb0ec..4c41edcc 100644 --- a/contrib/gdb.py +++ b/contrib/gdb.py @@ -203,7 +203,7 @@ class List234(gdb.Function): Arguments are a tree234, and optionally a value type. If no value type is given, the result is a list of the raw void * pointers - stored in the tree. Othewise, each one is cast to a pointer to the + stored in the tree. Otherwise, each one is cast to a pointer to the value type and dereferenced. Due to limitations of GDB's convenience function syntax, the value diff --git a/crypto/argon2.c b/crypto/argon2.c index 25385d7e..99bcfa17 100644 --- a/crypto/argon2.c +++ b/crypto/argon2.c @@ -266,7 +266,7 @@ static void argon2_internal(uint32_t p, uint32_t T, uint32_t m, uint32_t t, * that in the initial slice on the first pass, we've already written * values into the first two columns during the initial setup above. So * 'jstart' indicates the starting index in each segment we process; it - * starts off as 2 so that we don't overwrite the inital setup, and then + * starts off as 2 so that we don't overwrite the initial setup, and then * after the first slice is done, we set it to 0, and it stays there. * * d_mode indicates whether we're being data-dependent (true) or diff --git a/crypto/mpint.c b/crypto/mpint.c index fba9431d..544a18b5 100644 --- a/crypto/mpint.c +++ b/crypto/mpint.c @@ -1544,7 +1544,7 @@ mp_int *monty_export(MontyContext *mc, mp_int *x) mp_int *monty_pow(MontyContext *mc, mp_int *base, mp_int *exponent) { /* - * Modular exponentation is done from the top down, using a + * Modular exponentiation is done from the top down, using a * fixed-window technique. * * We have a table storing every power of the base from base^0 up diff --git a/doc/config.but b/doc/config.but index b5ad41d0..16cf8560 100644 --- a/doc/config.but +++ b/doc/config.but @@ -614,7 +614,7 @@ This option affects the arrow keys, if you press one with any of the modifier keys Shift, Ctrl or Alt held down. \b In the default mode, labelled \c{Ctrl toggles app mode}, the Ctrl -key toggles between the default arrow-key sequnces like \c{ESC [A} and +key toggles between the default arrow-key sequences like \c{ESC [A} and \c{ESC [B}, and the sequences Digital's terminals generate in \q{application cursor keys} mode, i.e. \c{ESC O A} and so on. Shift and Alt have no effect. @@ -1369,7 +1369,7 @@ 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.) -(When PuTTY iself is running on Unix, it follows the X Window System +(When PuTTY itself is running on Unix, it follows the X Window System convention.) \S{config-mouseshift} \q{Shift overrides application's use of mouse} diff --git a/ssh/sftp.h b/ssh/sftp.h index 5835c16b..fb7d3267 100644 --- a/ssh/sftp.h +++ b/ssh/sftp.h @@ -327,7 +327,7 @@ struct SftpServerVtable { /* * Handle actual filesystem requests. * - * Each of these functions replies by calling an appropiate + * Each of these functions replies by calling an appropriate * sftp_reply_foo() function on the given reply packet. */ diff --git a/ssh/transport2.c b/ssh/transport2.c index b4261562..2f9b02af 100644 --- a/ssh/transport2.c +++ b/ssh/transport2.c @@ -1922,7 +1922,7 @@ static void ssh2_transport_gss_update(struct ssh2_transport_state *s, s->gss_status |= GSS_KEX_CAPABLE; /* - * When rekeying to cascade, avoding doing this too close to the + * When rekeying to cascade, avoid doing this too close to the * context expiration time, since the key exchange might fail. */ if (s->gss_ctxt_lifetime < MIN_CTXT_LIFETIME) diff --git a/unix/window.c b/unix/window.c index 93281429..5475fe9c 100644 --- a/unix/window.c +++ b/unix/window.c @@ -3194,7 +3194,7 @@ static void selection_received(GtkWidget *widget, GtkSelectionData *seldata, text = retrieve_cutbuffer(inst, &length); if (length == 0) return; - /* Xterm is rumoured to expect Latin-1, though I havn't checked the + /* Xterm is rumoured to expect Latin-1, though I haven't checked the * source, so use that as a de-facto standard. */ charset = CS_ISO8859_1; free_required = true; diff --git a/utils/tree234.c b/utils/tree234.c index f9104158..83683f13 100644 --- a/utils/tree234.c +++ b/utils/tree234.c @@ -615,7 +615,7 @@ void search234_start(search234_state *state, tree234 *t) { state->_node = t->root; state->_base = 0; /* index of first element in this node's subtree */ - state->_last = -1; /* indicate that this node is not previously visted */ + state->_last = -1; /* indicate that this node is not previously visited */ search234_step(state, 0); } void search234_step(search234_state *state, int direction) diff --git a/windows/jump-list.c b/windows/jump-list.c index 0aacee8f..39de1e00 100644 --- a/windows/jump-list.c +++ b/windows/jump-list.c @@ -12,7 +12,7 @@ * * 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 + * list persistently 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. */ diff --git a/windows/window.c b/windows/window.c index 4d61a709..f3ca5936 100644 --- a/windows/window.c +++ b/windows/window.c @@ -4178,7 +4178,7 @@ static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam, } - /* Nastyness with NUMLock - Shift-NUMLock is left alone though */ + /* Nastiness with NUMLock - Shift-NUMLock is left alone though */ if ((funky_type == FUNKY_VT400 || (funky_type <= FUNKY_LINUX && term->app_keypad_keys && !no_applic_k)) -- cgit v1.2.3 From 2a36f968e93bdb905c7f7d094d0dd76d151f2847 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 3 Jan 2022 11:15:07 +0000 Subject: Centralise definition of AGENT_COPYDATA_ID. How have I not noticed for years that it was separately defined in the client and server code?! --- windows/agent-client.c | 2 -- windows/pageant.c | 2 -- windows/platform.h | 4 ++++ 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/windows/agent-client.c b/windows/agent-client.c index 4eb0bcfb..408480df 100644 --- a/windows/agent-client.c +++ b/windows/agent-client.c @@ -12,8 +12,6 @@ #include "security-api.h" #include "cryptoapi.h" -#define AGENT_COPYDATA_ID 0x804e50ba /* random goop */ - static bool wm_copydata_agent_exists(void) { HWND hwnd; diff --git a/windows/pageant.c b/windows/pageant.c index 6fbbf883..4bf9ea3b 100644 --- a/windows/pageant.c +++ b/windows/pageant.c @@ -30,8 +30,6 @@ #define WM_SYSTRAY (WM_APP + 6) #define WM_SYSTRAY2 (WM_APP + 7) -#define AGENT_COPYDATA_ID 0x804e50ba /* random goop */ - #define APPNAME "Pageant" /* Titles and class names for invisible windows. IPCWINTITLE and diff --git a/windows/platform.h b/windows/platform.h index 78ebf188..454ae2f0 100644 --- a/windows/platform.h +++ b/windows/platform.h @@ -35,6 +35,10 @@ #define BUILDINFO_PLATFORM "Windows" #endif +/* Randomly-chosen dwData value identifying a WM_COPYDATA message as + * being a Pageant transaction */ +#define AGENT_COPYDATA_ID 0x804e50ba + struct Filename { char *path; }; -- cgit v1.2.3 From d0b609c68ad040910313dfb3b145d6299ce8dbf4 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 3 Jan 2022 11:24:29 +0000 Subject: Move agent_named_pipe_name into its own source file. It's used by server and client, so it seems silly to have it live in the client code. --- windows/CMakeLists.txt | 1 + windows/agent-client.c | 11 ----------- windows/utils/agent_named_pipe_name.c | 17 +++++++++++++++++ 3 files changed, 18 insertions(+), 11 deletions(-) create mode 100644 windows/utils/agent_named_pipe_name.c diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt index 0a999e51..54a72018 100644 --- a/windows/CMakeLists.txt +++ b/windows/CMakeLists.txt @@ -1,6 +1,7 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) add_sources_from_current_dir(utils + utils/agent_named_pipe_name.c utils/arm_arch_queries.c utils/cryptoapi.c utils/defaults.c diff --git a/windows/agent-client.c b/windows/agent-client.c index 408480df..1183bff8 100644 --- a/windows/agent-client.c +++ b/windows/agent-client.c @@ -123,17 +123,6 @@ static void wm_copydata_agent_query(strbuf *query, void **out, int *outlen) LocalFree(psd); } -char *agent_named_pipe_name(void) -{ - char *username, *suffix, *pipename; - username = get_username(); - suffix = capi_obfuscate_string("Pageant"); - pipename = dupprintf("\\\\.\\pipe\\pageant.%s.%s", username, suffix); - sfree(username); - sfree(suffix); - return pipename; -} - Socket *agent_connect(Plug *plug) { char *pipename = agent_named_pipe_name(); diff --git a/windows/utils/agent_named_pipe_name.c b/windows/utils/agent_named_pipe_name.c new file mode 100644 index 00000000..aa64b3f6 --- /dev/null +++ b/windows/utils/agent_named_pipe_name.c @@ -0,0 +1,17 @@ +/* + * Return the full pathname of the named pipe Pageant will listen on. + * Used by both the Pageant server code and client code. + */ + +#include "putty.h" +#include "cryptoapi.h" + +char *agent_named_pipe_name(void) +{ + char *username = get_username(); + char *suffix = capi_obfuscate_string("Pageant"); + char *pipename = dupprintf("\\\\.\\pipe\\pageant.%s.%s", username, suffix); + sfree(username); + sfree(suffix); + return pipename; +} -- cgit v1.2.3 From a2de0a8b7d16325dc97727d61efda0fdfc7a4396 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 3 Jan 2022 11:40:22 +0000 Subject: Windows: factor out mutex lock/unlock from sharing.c. This will let me reuse them easily in Pageant setup. --- windows/CMakeLists.txt | 1 + windows/platform.h | 3 +++ windows/sharing.c | 42 +++++---------------------------- windows/utils/interprocess_mutex.c | 48 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 58 insertions(+), 36 deletions(-) create mode 100644 windows/utils/interprocess_mutex.c diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt index 54a72018..5343e078 100644 --- a/windows/CMakeLists.txt +++ b/windows/CMakeLists.txt @@ -13,6 +13,7 @@ add_sources_from_current_dir(utils utils/getdlgitemtext_alloc.c utils/get_system_dir.c utils/get_username.c + utils/interprocess_mutex.c utils/is_console_handle.c utils/load_system32_dll.c utils/ltime.c diff --git a/windows/platform.h b/windows/platform.h index 454ae2f0..aa543982 100644 --- a/windows/platform.h +++ b/windows/platform.h @@ -740,4 +740,7 @@ void plug_closing_winsock_error(Plug *plug, DWORD error); SeatPromptResult make_spr_sw_abort_winerror(const char *prefix, DWORD error); +HANDLE lock_interprocess_mutex(const char *mutexname, char **error); +void unlock_interprocess_mutex(HANDLE mutex); + #endif /* PUTTY_WINDOWS_PLATFORM_H */ diff --git a/windows/sharing.c b/windows/sharing.c index 02eeb087..15b1da48 100644 --- a/windows/sharing.c +++ b/windows/sharing.c @@ -36,8 +36,6 @@ int platform_ssh_share(const char *pi_name, Conf *conf, char *name, *mutexname, *pipename; HANDLE mutex; Socket *retsock; - PSECURITY_DESCRIPTOR psd; - PACL acl; /* * Transform the platform-independent version of the connection @@ -57,39 +55,12 @@ int platform_ssh_share(const char *pi_name, Conf *conf, * Make a mutex name out of the connection identifier, and lock it * while we decide whether to be upstream or downstream. */ - { - SECURITY_ATTRIBUTES sa; - - mutexname = make_name(CONNSHARE_MUTEX_PREFIX, name); - if (!make_private_security_descriptor(MUTEX_ALL_ACCESS, - &psd, &acl, logtext)) { - sfree(mutexname); - sfree(name); - return SHARE_NONE; - } - - memset(&sa, 0, sizeof(sa)); - sa.nLength = sizeof(sa); - sa.lpSecurityDescriptor = psd; - sa.bInheritHandle = false; - - mutex = CreateMutex(&sa, false, mutexname); - - if (!mutex) { - *logtext = dupprintf("CreateMutex(\"%s\") failed: %s", - mutexname, win_strerror(GetLastError())); - sfree(mutexname); - sfree(name); - LocalFree(psd); - LocalFree(acl); - return SHARE_NONE; - } - + mutexname = make_name(CONNSHARE_MUTEX_PREFIX, name); + mutex = lock_interprocess_mutex(mutexname, logtext); + if (!mutex) { sfree(mutexname); - LocalFree(psd); - LocalFree(acl); - - WaitForSingleObject(mutex, INFINITE); + sfree(name); + return SHARE_NONE; } pipename = make_name(CONNSHARE_PIPE_PREFIX, name); @@ -103,8 +74,7 @@ int platform_ssh_share(const char *pi_name, Conf *conf, *logtext = pipename; *sock = retsock; sfree(name); - ReleaseMutex(mutex); - CloseHandle(mutex); + unlock_interprocess_mutex(mutex); return SHARE_DOWNSTREAM; } sfree(*ds_err); diff --git a/windows/utils/interprocess_mutex.c b/windows/utils/interprocess_mutex.c new file mode 100644 index 00000000..a22ec820 --- /dev/null +++ b/windows/utils/interprocess_mutex.c @@ -0,0 +1,48 @@ +/* + * Lock and unlock a mutex with a globally visible name. Used to + * synchronise between unrelated processes, such as two + * connection-sharing PuTTYs deciding which will be the upstream. + */ + +#include "putty.h" +#include "security-api.h" + +HANDLE lock_interprocess_mutex(const char *mutexname, char **error) +{ + PSECURITY_DESCRIPTOR psd = NULL; + PACL acl = NULL; + HANDLE mutex = NULL; + + if (!make_private_security_descriptor(MUTEX_ALL_ACCESS, + &psd, &acl, error)) + goto out; + + SECURITY_ATTRIBUTES sa; + memset(&sa, 0, sizeof(sa)); + sa.nLength = sizeof(sa); + sa.lpSecurityDescriptor = psd; + sa.bInheritHandle = false; + + mutex = CreateMutex(&sa, false, mutexname); + if (!mutex) { + *error = dupprintf("CreateMutex(\"%s\") failed: %s", + mutexname, win_strerror(GetLastError())); + goto out; + } + + WaitForSingleObject(mutex, INFINITE); + + out: + if (psd) + LocalFree(psd); + if (acl) + LocalFree(acl); + + return mutex; +} + +void unlock_interprocess_mutex(HANDLE mutex) +{ + ReleaseMutex(mutex); + CloseHandle(mutex); +} -- cgit v1.2.3 From 0ad344ca32e3df0d33883eed662f6aef6eff3a11 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 3 Jan 2022 12:17:15 +0000 Subject: Windows Pageant: make atomic client/server decision. In the previous state of the code, we first tested agent_exists() to decide whether to be the long-running Pageant server or a short-lived client; then, during the command-line parsing loop, we prompted for passphrases to add keys presented on the command line (to ourself or the server, respectively); *then* we set up the named pipe and WM_COPYDATA receiver window to actually start functioning as a server, if we decided that was our role. A consequence is that if a user started up two Pageants each with an encrypted key on the command line, there would be a race condition: each one would decide that it was _going_ to be the server, then prompt for a passphrase, and then try to set itself up as the server once the passphrase is entered. So whichever one's passphrase prompt was answered second would add its key to its own internal data structures, then fail to set up the server's named pipe, terminate with an error, and end up not having added its key to the _surviving_ server. This change reorders the setup steps so that the command-line parsing loop does not add the keys immediately; instead it merely caches the key filenames provided. Then we make the decision about whether we're the server, and set up both the named pipe and WM_COPYDATA window if we are; and finally, we go back to our list of key filenames and actually add them, either to ourself (if we're the server) or to some other Pageant (if we're a client). Moreover, the decision about whether to be the server is now wrapped in an interprocess mutex similar to the one used in connection sharing, which means that even if two or more Pageants are started up as close to simultaneously as possible, they should avoid a race condition and successfully manage to agree on exactly one of themselves to be the server. For example, a user reported that this could occur if you put shortcuts to multiple private key files in your Windows Startup folder, so that they were all launched simultaneously at startup. One slightly odd behaviour that remains: if the server Pageant has to prompt for private key passphrases at startup, then it won't actually start _servicing_ requests from other Pageants until it's finished dealing with its own prompts. As a result, if you do start up two Pageants at once each with an encrypted key file on its command line, the second one won't even manage to present its passphrase prompt until the first one's prompt is dismissed, because it will block waiting for the initial check of the key list. But it will get there in the end, so that's only a cosmetic oddity. It would be nice to arrange that Pageant GUI operations don't block unrelated agent requests (e.g. by having the GUI and the agent code run in separate threads). But that's a bigger problem, not specific to startup time - the same thing happens if you interactively load a key via Pageant's file dialog. And it would require a major reorganisation to fix that fully, because currently the GUI code depends on being able to call _internal_ Pageant query functions like pageant_count_ssh2_keys() that don't work by constructing an agent request at all. --- windows/CMakeLists.txt | 1 + windows/pageant.c | 191 +++++++++++++++++++++++++-------------- windows/platform.h | 3 +- windows/utils/agent_mutex_name.c | 14 +++ 4 files changed, 142 insertions(+), 67 deletions(-) create mode 100644 windows/utils/agent_mutex_name.c diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt index 5343e078..db2a38ea 100644 --- a/windows/CMakeLists.txt +++ b/windows/CMakeLists.txt @@ -1,6 +1,7 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) add_sources_from_current_dir(utils + utils/agent_mutex_name.c utils/agent_named_pipe_name.c utils/arm_arch_queries.c utils/cryptoapi.c diff --git a/windows/pageant.c b/windows/pageant.c index 4bf9ea3b..f4acc987 100644 --- a/windows/pageant.c +++ b/windows/pageant.c @@ -1375,11 +1375,18 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) { MSG msg; const char *command = NULL; - bool added_keys = false; bool show_keylist_on_startup = false; int argc, i; char **argv, **argstart; + typedef struct CommandLineKey { + Filename *fn; + bool add_encrypted; + } CommandLineKey; + + CommandLineKey *clkeys = NULL; + size_t nclkeys = 0, clkeysize = 0; + dll_hijacking_protection(); hinst = inst; @@ -1431,19 +1438,10 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) } /* - * Find out if Pageant is already running. - */ - already_running = agent_exists(); - - /* - * Initialise the cross-platform Pageant code. - */ - if (!already_running) { - pageant_init(); - } - - /* - * Process the command line and add keys as listed on it. + * Process the command line, handling anything that can be done + * immediately, but deferring adding keys until after we've + * started up the main agent. Details of keys to be added are + * stored in the 'clkeys' array. */ split_into_argv(cmdline, &argc, &argv, &argstart); bool doing_opts = true; @@ -1493,19 +1491,122 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) exit(1); } } else { - Filename *fn = filename_from_str(p); - win_add_keyfile(fn, add_keys_encrypted); - filename_free(fn); - added_keys = true; + sgrowarray(clkeys, clkeysize, nclkeys); + CommandLineKey *clkey = &clkeys[nclkeys++]; + clkey->fn = filename_from_str(p); + clkey->add_encrypted = add_keys_encrypted; + } + } + + /* + * Create and lock an interprocess mutex while we figure out + * whether we're going to be the Pageant server or a client. That + * way, two Pageant processes started up simultaneously will be + * able to agree on which one becomes the server without a race + * condition. + */ + HANDLE mutex; + { + char *err; + char *mutexname = agent_mutex_name(); + mutex = lock_interprocess_mutex(mutexname, &err); + sfree(mutexname); + if (!mutex) { + MessageBox(NULL, err, "Pageant Error", MB_ICONERROR | MB_OK); + return 1; + } + } + + /* + * Find out if Pageant is already running. + */ + already_running = agent_exists(); + + /* + * If it isn't, we're going to be the primary Pageant that stays + * running, so set up all the machinery to answer requests. + */ + if (!already_running) { + /* + * Initialise the cross-platform Pageant code. + */ + pageant_init(); + + /* + * Set up a named-pipe listener. + */ + Plug *pl_plug; + wpc->plc.vt = &winpgnt_vtable; + wpc->plc.suppress_logging = true; + struct pageant_listen_state *pl = + pageant_listener_new(&pl_plug, &wpc->plc); + char *pipename = agent_named_pipe_name(); + Socket *sock = new_named_pipe_listener(pipename, pl_plug); + if (sk_socket_error(sock)) { + char *err = dupprintf("Unable to open named pipe at %s " + "for SSH agent:\n%s", pipename, + sk_socket_error(sock)); + MessageBox(NULL, err, "Pageant Error", MB_ICONERROR | MB_OK); + return 1; + } + pageant_listener_got_socket(pl, sock); + sfree(pipename); + + /* + * Set up the window class for the hidden window that receives + * the WM_COPYDATA message used by the old-style Pageant IPC + * system. + */ + if (!prev) { + WNDCLASS wndclass; + + memset(&wndclass, 0, sizeof(wndclass)); + wndclass.lpfnWndProc = wm_copydata_WndProc; + wndclass.hInstance = inst; + wndclass.lpszClassName = IPCCLASSNAME; + + RegisterClass(&wndclass); } + + /* + * And launch the subthread which will open that hidden window and + * handle WM_COPYDATA messages on it. + */ + wmcpc.vt = &wmcpc_vtable; + wmcpc.suppress_logging = true; + pageant_register_client(&wmcpc); + DWORD wm_copydata_threadid; + wmct.ev_msg_ready = CreateEvent(NULL, false, false, NULL); + wmct.ev_reply_ready = CreateEvent(NULL, false, false, NULL); + HANDLE hThread = CreateThread(NULL, 0, wm_copydata_threadfunc, + &inst, 0, &wm_copydata_threadid); + if (hThread) + CloseHandle(hThread); /* we don't need the thread handle */ + add_handle_wait(wmct.ev_msg_ready, wm_copydata_got_msg, NULL); } /* - * Forget any passphrase that we retained while going over - * command line keyfiles. + * Now we're either a fully set up Pageant server, or we know one + * is running somewhere else. Either way, now it's safe to unlock + * the mutex. + */ + unlock_interprocess_mutex(mutex); + + /* + * Add any keys provided on the command line. */ + for (size_t i = 0; i < nclkeys; i++) { + CommandLineKey *clkey = &clkeys[i]; + win_add_keyfile(clkey->fn, clkey->add_encrypted); + filename_free(clkey->fn); + } + sfree(clkeys); + /* And forget any passphrases we stashed during that loop. */ pageant_forget_passphrases(); + /* + * Now our keys are present, spawn a command, if we were asked to. + */ if (command) { char *args; if (command[0] == '"') @@ -1525,7 +1626,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) * keys), complain. */ if (already_running) { - if (!command && !added_keys) { + if (!command && !nclkeys) { MessageBox(NULL, "Pageant is already running", "Pageant Error", MB_ICONERROR | MB_OK); } @@ -1533,32 +1634,8 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) } /* - * Set up a named-pipe listener. - */ - { - Plug *pl_plug; - wpc->plc.vt = &winpgnt_vtable; - wpc->plc.suppress_logging = true; - struct pageant_listen_state *pl = - pageant_listener_new(&pl_plug, &wpc->plc); - char *pipename = agent_named_pipe_name(); - Socket *sock = new_named_pipe_listener(pipename, pl_plug); - if (sk_socket_error(sock)) { - char *err = dupprintf("Unable to open named pipe at %s " - "for SSH agent:\n%s", pipename, - sk_socket_error(sock)); - MessageBox(NULL, err, "Pageant Error", MB_ICONERROR | MB_OK); - return 1; - } - pageant_listener_got_socket(pl, sock); - sfree(pipename); - } - - /* - * Set up window classes for two hidden windows: one that receives - * all the messages to do with our presence in the system tray, - * and one that receives the WM_COPYDATA message used by the - * old-style Pageant IPC system. + * Set up the window class for the hidden window that receives + * all the messages to do with our presence in the system tray. */ if (!prev) { @@ -1571,13 +1648,6 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) wndclass.lpszClassName = TRAYCLASSNAME; RegisterClass(&wndclass); - - memset(&wndclass, 0, sizeof(wndclass)); - wndclass.lpfnWndProc = wm_copydata_WndProc; - wndclass.hInstance = inst; - wndclass.lpszClassName = IPCCLASSNAME; - - RegisterClass(&wndclass); } keylist = NULL; @@ -1623,18 +1693,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) ShowWindow(traywindow, SW_HIDE); - wmcpc.vt = &wmcpc_vtable; - wmcpc.suppress_logging = true; - pageant_register_client(&wmcpc); - DWORD wm_copydata_threadid; - wmct.ev_msg_ready = CreateEvent(NULL, false, false, NULL); - wmct.ev_reply_ready = CreateEvent(NULL, false, false, NULL); - HANDLE hThread = CreateThread(NULL, 0, wm_copydata_threadfunc, - &inst, 0, &wm_copydata_threadid); - if (hThread) - CloseHandle(hThread); /* we don't need the thread handle */ - add_handle_wait(wmct.ev_msg_ready, wm_copydata_got_msg, NULL); - + /* Open the visible key list window, if we've been asked to. */ if (show_keylist_on_startup) create_keylist_window(); diff --git a/windows/platform.h b/windows/platform.h index aa543982..b453a5bd 100644 --- a/windows/platform.h +++ b/windows/platform.h @@ -655,8 +655,9 @@ void handle_wait_activate(HandleWaitList *hwl, int index); void handle_wait_list_free(HandleWaitList *hwl); /* - * Exports from winpgntc.c. + * Pageant-related pathnames. */ +char *agent_mutex_name(void); char *agent_named_pipe_name(void); /* diff --git a/windows/utils/agent_mutex_name.c b/windows/utils/agent_mutex_name.c new file mode 100644 index 00000000..949e8cfa --- /dev/null +++ b/windows/utils/agent_mutex_name.c @@ -0,0 +1,14 @@ +/* + * Return the full pathname of the global mutex that Pageant uses at + * startup to atomically decide whether to be a server or a client. + */ + +#include "putty.h" + +char *agent_mutex_name(void) +{ + char *username = get_username(); + char *mutexname = dupprintf("Local\\pageant-mutex.%s", username); + sfree(username); + return mutexname; +} -- cgit v1.2.3 From ce59d8bb4f7a1c6aff677d1431ec84a803a24091 Mon Sep 17 00:00:00 2001 From: Jacob Nevins Date: Tue, 4 Jan 2022 10:17:17 +0000 Subject: It's a new year. --- LICENCE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENCE b/LICENCE index 0e1df3c5..57ea292b 100644 --- a/LICENCE +++ b/LICENCE @@ -1,4 +1,4 @@ -PuTTY is copyright 1997-2021 Simon Tatham. +PuTTY is copyright 1997-2022 Simon Tatham. Portions copyright Robert de Bath, Joris van Rantwijk, Delian Delchev, Andreas Schultz, Jeroen Massar, Wez Furlong, Nicolas Barry, -- cgit v1.2.3 From 498c0a3abca2f3cfa5e1c663e60ea423bcd79672 Mon Sep 17 00:00:00 2001 From: Jacob Nevins Date: Thu, 6 Jan 2022 23:50:44 +0000 Subject: Fix missing parenthesis in help text. --- unix/pageant.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unix/pageant.c b/unix/pageant.c index df124dc6..d5c15d1c 100644 --- a/unix/pageant.c +++ b/unix/pageant.c @@ -206,7 +206,7 @@ static void usage(void) printf(" --public-openssh, -L print public keys in OpenSSH format\n"); printf(" -d delete key(s) from the agent\n"); printf(" -D delete all keys from the agent\n"); - printf(" -r re-encrypt keys in the agent (forget cleartext\n"); + printf(" -r re-encrypt keys in the agent (forget cleartext)\n"); printf(" -R re-encrypt all possible keys in the agent\n"); printf("Other options:\n"); printf(" -v verbose mode (in agent mode)\n"); -- cgit v1.2.3 From 9529769b6002eaaa44d293762a3cec159d628206 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 8 Jan 2022 11:20:27 +0000 Subject: Windows PuTTYgen: make state->entropy into a strbuf. This offloads the memory management into centralised code which is better at it than the ad-hoc code here. Now I don't have to predict in advance how much memory the entropy will consume, and can change that decision on the fly. --- windows/puttygen.c | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/windows/puttygen.c b/windows/puttygen.c index 7bc8f494..30e85c58 100644 --- a/windows/puttygen.c +++ b/windows/puttygen.c @@ -633,7 +633,8 @@ struct MainDlgState { bool collecting_entropy; bool generation_thread_exists; bool key_exists; - int entropy_got, entropy_required, entropy_size; + int entropy_got, entropy_required; + strbuf *entropy; int key_bits, curve_bits; bool ssh2; keytype keytype; @@ -642,7 +643,6 @@ struct MainDlgState { FingerprintType fptype; char **commentptr; /* points to key.comment or ssh2key.comment */ ssh2_userkey ssh2key; - unsigned *entropy; union { RSAKey key; struct dsa_key dsakey; @@ -1409,18 +1409,17 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, 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(); + put_uint32(state->entropy, lParam); + put_uint32(state->entropy, GetMessageTime()); + state->entropy_got += 2; SendDlgItemMessage(hwnd, IDC_PROGRESS, PBM_SETPOS, state->entropy_got, 0); if (state->entropy_got >= state->entropy_required) { /* * Seed the entropy pool */ - random_reseed( - make_ptrlen(state->entropy, state->entropy_size)); - smemclr(state->entropy, state->entropy_size); - sfree(state->entropy); + random_reseed(ptrlen_from_strbuf(state->entropy)); + strbuf_free(state->entropy); state->collecting_entropy = false; start_generating_key(hwnd, state); @@ -1638,9 +1637,7 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, state->collecting_entropy = true; state->entropy_got = 0; - state->entropy_size = (state->entropy_required * - sizeof(unsigned)); - state->entropy = snewn(state->entropy_required, unsigned); + state->entropy = strbuf_new_nm(); SendDlgItemMessage(hwnd, IDC_PROGRESS, PBM_SETRANGE, 0, MAKELPARAM(0, state->entropy_required)); -- cgit v1.2.3 From e7a695103fd51ae765ccf0460f15795adb74485e Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 8 Jan 2022 11:23:46 +0000 Subject: Windows PuTTYgen: remove state->collecting_entropy. There's no point having a separate boolean flag. All we have to do is remember to NULL out the strbuf point state->entropy when we free the strbuf (which is a good idea in any case!), and then we can use the non-NULL-ness of that pointer as the indicator that we're currently collecting entropy. --- windows/puttygen.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/windows/puttygen.c b/windows/puttygen.c index 30e85c58..d9b2eb29 100644 --- a/windows/puttygen.c +++ b/windows/puttygen.c @@ -630,7 +630,6 @@ static DWORD WINAPI generate_key_thread(void *param) } struct MainDlgState { - bool collecting_entropy; bool generation_thread_exists; bool key_exists; int entropy_got, entropy_required; @@ -1200,7 +1199,6 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, 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); @@ -1407,8 +1405,7 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, return 1; case WM_MOUSEMOVE: state = (struct MainDlgState *) GetWindowLongPtr(hwnd, GWLP_USERDATA); - if (state->collecting_entropy && - state->entropy && state->entropy_got < state->entropy_required) { + if (state->entropy && state->entropy_got < state->entropy_required) { put_uint32(state->entropy, lParam); put_uint32(state->entropy, GetMessageTime()); state->entropy_got += 2; @@ -1420,7 +1417,7 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, */ random_reseed(ptrlen_from_strbuf(state->entropy)); strbuf_free(state->entropy); - state->collecting_entropy = false; + state->entropy = NULL; start_generating_key(hwnd, state); } @@ -1634,7 +1631,6 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, ui_set_state(hwnd, state, 1); SetDlgItemText(hwnd, IDC_GENERATING, entropy_msg); state->key_exists = false; - state->collecting_entropy = true; state->entropy_got = 0; state->entropy = strbuf_new_nm(); -- cgit v1.2.3 From 5ad601ffcd5d09b5f22d998f71ce9ccdf35140f1 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 8 Jan 2022 12:09:36 +0000 Subject: Windows PuTTYgen: rate-limit entropy counter increment. Some pointing devices (e.g. gaming mice) can be set to send mouse-movement events at an extremely high sample rate like 1kHz. This apparently translates into Windows genuinely sending WM_MOUSEMOVE messages at that rate. So if you're using one of those mice, then PuTTYgen's mouse-based entropy collection system will fill its buffer almost immediately, and give you no perceptible time to actually wave the mouse around. I think that in that situation, there's also likely to be a strong correlation between the contents of successive movement events, because human-controlled mouse movements aren't fractals - the more you zoom in on a little piece of one, the more it starts to look more and more like something moving in a straight line at constant speed, because the deviations from that happen on a larger time scale than you're seeing. So this commit adds a rate limit, not on the _collection_ of the data (we'll still take all the bits we can get, thanks!), but on the rate at which we increment the _counter_ for how much entropy we think we've collected so far. That way, the user still has to spend time wiggling the mouse back and forth in a way that varies with muscle motions and introduces randomness. --- windows/puttygen.c | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/windows/puttygen.c b/windows/puttygen.c index d9b2eb29..887a34b4 100644 --- a/windows/puttygen.c +++ b/windows/puttygen.c @@ -634,6 +634,7 @@ struct MainDlgState { bool key_exists; int entropy_got, entropy_required; strbuf *entropy; + ULONG entropy_prev_msgtime; int key_bits, curve_bits; bool ssh2; keytype keytype; @@ -651,6 +652,23 @@ struct MainDlgState { HMENU filemenu, keymenu, cvtmenu; }; +/* + * Rate limit for incrementing the entropy_got counter. + * + * Some pointing devices (e.g. gaming mice) can be set to send + * mouse-movement events at an extremely high sample rate like 1kHz. + * In that situation, there's likely to be a strong correlation + * between the contents of successive movement events, so you have to + * regard the mouse movements as containing less entropy each. + * + * A reasonably simple approach to this is to continue to buffer all + * mouse data, but limit the rate at which we increment the counter + * for how much entropy we think we've collected. That way, the user + * still has to spend time wiggling the mouse back and forth in a way + * that varies with muscle motions and introduces randomness. + */ +#define ENTROPY_RATE_LIMIT 10 /* in units of GetMessageTime(), i.e. ms */ + static void hidemany(HWND hwnd, const int *ids, bool hideit) { while (*ids) { @@ -1406,9 +1424,13 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, case WM_MOUSEMOVE: state = (struct MainDlgState *) GetWindowLongPtr(hwnd, GWLP_USERDATA); if (state->entropy && state->entropy_got < state->entropy_required) { + ULONG msgtime = GetMessageTime(); put_uint32(state->entropy, lParam); - put_uint32(state->entropy, GetMessageTime()); - state->entropy_got += 2; + put_uint32(state->entropy, msgtime); + if (msgtime - state->entropy_prev_msgtime > ENTROPY_RATE_LIMIT) { + state->entropy_got += 2; + state->entropy_prev_msgtime = msgtime; + } SendDlgItemMessage(hwnd, IDC_PROGRESS, PBM_SETPOS, state->entropy_got, 0); if (state->entropy_got >= state->entropy_required) { @@ -1634,6 +1656,7 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, state->entropy_got = 0; state->entropy = strbuf_new_nm(); + state->entropy_prev_msgtime = GetMessageTime(); SendDlgItemMessage(hwnd, IDC_PROGRESS, PBM_SETRANGE, 0, MAKELPARAM(0, state->entropy_required)); -- cgit v1.2.3 From 520915c641ae14bbcdb25a257d854b24e9be700e Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 8 Jan 2022 13:48:41 +0000 Subject: Windows: make 'resize by changing font' work again. It looks as if I broke this some time around commit cfc902361633915, when I stopped proactively calling term_size() in advance of resizing the window. A side effect was that I also stopped calling it at all in the case where we're _not_ resizing the window (because changing the size of the terminal means adapting the font size to fit a different amount of stuff in the existing window). Fixed by moving all the new machinery inside the 'actually resize the window' branch of the if statement, and restoring the previous behaviour in the other branch, this time with a comment that will hopefully stop me making the same mistake again. --- windows/window.c | 39 +++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/windows/window.c b/windows/window.c index f3ca5936..c1cd0c4c 100644 --- a/windows/window.c +++ b/windows/window.c @@ -1639,32 +1639,39 @@ static void wintw_request_resize(TermWin *tw, int w, int h) } } - /* - * We want to send exactly one term_size() to the terminal, - * telling it what size it ended up after this operation. - * - * If we don't get the size we asked for in SetWindowPos, then - * we'll be sent a WM_SIZE message, whose handler will make that - * call, all before SetWindowPos even returns to here. - * - * But if that _didn't_ happen, we'll need to call term_size - * ourselves afterwards. - */ - sent_term_size = false; - if (conf_get_int(conf, CONF_resize_action) != RESIZE_FONT && !IsZoomed(wgs.term_hwnd)) { + /* + * We want to send exactly one term_size() to the terminal, + * telling it what size it ended up after this operation. + * + * If we don't get the size we asked for in SetWindowPos, then + * we'll be sent a WM_SIZE message, whose handler will make + * that call, all before SetWindowPos even returns to here. + * + * But if that _didn't_ happen, we'll need to call term_size + * ourselves afterwards. + */ + sent_term_size = false; + width = extra_width + font_width * w; height = extra_height + font_height * h; SetWindowPos(wgs.term_hwnd, NULL, 0, 0, width, height, SWP_NOACTIVATE | SWP_NOCOPYBITS | SWP_NOMOVE | SWP_NOZORDER); - } else - reset_window(0); - if (!sent_term_size) + if (!sent_term_size) + term_size(term, h, w, conf_get_int(conf, CONF_savelines)); + } else { + /* + * If we're resizing by changing the font, we must tell the + * terminal the new size immediately, so that reset_window + * will know what to do. + */ term_size(term, h, w, conf_get_int(conf, CONF_savelines)); + reset_window(0); + } InvalidateRect(wgs.term_hwnd, NULL, true); } -- cgit v1.2.3 From f0162af6a5285acce3c59548d16214477c030450 Mon Sep 17 00:00:00 2001 From: Jacob Nevins Date: Tue, 11 Jan 2022 23:57:05 +0000 Subject: doc: Index 'DSS'. --- doc/pubkey.but | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/pubkey.but b/doc/pubkey.but index f40c9526..469c9d7d 100644 --- a/doc/pubkey.but +++ b/doc/pubkey.but @@ -56,7 +56,7 @@ and convenience. See \k{pageant} for further details. There is more than one \i{public-key algorithm} available. The most common are \i{RSA} and \i{ECDSA}, but others exist, notably \i{DSA} -(otherwise known as DSS), the USA's federal Digital Signature Standard. +(otherwise known as \i{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 -- cgit v1.2.3 From 39d1515ea672cb77aea1838cc087b55d1b1b3220 Mon Sep 17 00:00:00 2001 From: Jacob Nevins Date: Tue, 11 Jan 2022 23:57:20 +0000 Subject: Note side-channel resistance of probable primes. This came in around d8fda3b6da. --- doc/pubkey.but | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/doc/pubkey.but b/doc/pubkey.but index 469c9d7d..dd481a3e 100644 --- a/doc/pubkey.but +++ b/doc/pubkey.but @@ -177,6 +177,13 @@ are prime, because it generates the output number together with a proof of its primality. This takes more effort, but it eliminates that theoretical risk in the probabilistic method. +There in one way in which PuTTYgen's proven-primes method is not +strictly better than its probable-primes method. If you use PuTTYgen +to generate RSA or DSA keys on a computer that is potentially +susceptible to timing- or cache-based \i{side-channel attacks}, such +as a shared computer, the \q{probable primes} method is designed to +resist such attacks, whereas the \q{proven primes} methods are not. + You might choose to switch from probable to proven primes if you have a local security standard that demands it, or if you don't trust the probabilistic argument for the safety of the usual method. @@ -389,8 +396,8 @@ These options only affect PPK version 3. \dt Key derivation function \dd The variant of the \i{Argon2} key derivation function to use. -You might change this if you consider your exposure to side-channel -attacks to be different to the norm. +You might change this if you consider your exposure to \i{side-channel +attacks} to be different to the norm. \dt Memory to use for passphrase hash -- cgit v1.2.3 From c78226a711714129da0caafaa8f7160463b32560 Mon Sep 17 00:00:00 2001 From: Jacob Nevins Date: Tue, 11 Jan 2022 23:10:45 +0000 Subject: udp: Coroutines are used in more than just SSH. --- doc/udp.but | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/doc/udp.but b/doc/udp.but index bb17219f..bd3000dc 100644 --- a/doc/udp.but +++ b/doc/udp.but @@ -333,12 +333,11 @@ 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-ssh-coroutines} Coroutines in the SSH code +\H{udp-ssh-coroutines} Coroutines in protocol code -Large parts of the code in the various SSH modules (in fact most of -the protocol layers) are structured using a set of macros that -implement (something close to) Donald Knuth's \q{coroutines} concept -in C. +Large parts of the code in modules implementing wire protocols +(mainly SSH) 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 @@ -349,7 +348,7 @@ 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 some appropriate structure containing the persistent state -of the coroutine \dash typically the main state structure for an SSH +of the coroutine \dash typically the main state structure for a protocol layer. See -- cgit v1.2.3 From 1ca557a29bfe5188dbc2accd1ad710d2d20a076d Mon Sep 17 00:00:00 2001 From: Jacob Nevins Date: Tue, 11 Jan 2022 23:11:18 +0000 Subject: udp: Correct name of ProxySocket type in example. (I don't think this type has ever been called 'Proxy'.) --- doc/udp.but | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/udp.but b/doc/udp.but index bd3000dc..489b329b 100644 --- a/doc/udp.but +++ b/doc/udp.but @@ -558,8 +558,8 @@ based on the offset within that structure of the field called This system is flexible enough to permit \q{multiple inheritance}, or rather, multiple \e{implementation}: having one object type implement -more than one trait. For example, the \cw{Proxy} type implements both -the \cw{Socket} trait and the \cw{Plug} trait that connects to it, +more than one trait. For example, the \cw{ProxySocket} type implements +both the \cw{Socket} trait and the \cw{Plug} trait that connects to it, because it has to act as an adapter between another instance of each of those types. -- cgit v1.2.3 From e7b9eea7860af013d7bb82c67ca3668263cae854 Mon Sep 17 00:00:00 2001 From: Jacob Nevins Date: Tue, 11 Jan 2022 23:13:37 +0000 Subject: udp: Tweak for new source structure. There are lots of subdirectories now besides 'windows' and 'unix'. --- doc/udp.but | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/udp.but b/doc/udp.but index 489b329b..636ec4fc 100644 --- a/doc/udp.but +++ b/doc/udp.but @@ -28,9 +28,9 @@ platform-generic modules. The Unix-specific modules are all in the \c{unix} 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. +All the modules in the main source directory and other +subdirectories - 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 the C semantics guaranteed by the C standard: try not to make assumptions about the precise size of -- cgit v1.2.3 From 16ead30c0feb251ec7b8fcff337ab15a5d946583 Mon Sep 17 00:00:00 2001 From: Jacob Nevins Date: Tue, 11 Jan 2022 23:44:21 +0000 Subject: Update docs for new host key prompts. The message wording changed in d1dc1e927c. --- doc/errors.but | 6 +++--- doc/gs.but | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/doc/errors.but b/doc/errors.but index 36a42d94..27e2ae32 100644 --- a/doc/errors.but +++ b/doc/errors.but @@ -10,8 +10,8 @@ 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} +\H{errors-hostkey-absent} \q{The host key is not cached for this +server} This error message occurs when PuTTY connects to a new SSH server. Every server identifies itself by means of a host key; once PuTTY @@ -35,7 +35,7 @@ See \k{gs-hostkey} for more information on host keys. \H{errors-hostkey-wrong} \q{WARNING - POTENTIAL SECURITY BREACH!} 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 +the one PuTTY has cached for this server}, means that PuTTY has connected to the SSH server before, knows what its host key \e{should} be, but has found a different one. diff --git a/doc/gs.but b/doc/gs.but index e6a84923..4eff8967 100644 --- a/doc/gs.but +++ b/doc/gs.but @@ -50,8 +50,9 @@ 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 have no -\c guarantee that the server is the computer you think it is. +\c The host key is not cached for this server: +\c ssh.example.com (port 22) +\c You have no guarantee that the server is the computer you think it is. \c The server's ssh-ed25519 key fingerprint is: \c ssh-ed25519 255 SHA256:TddlQk20DVs4LRcAsIfDN9pInKpY06D+h4kSHwWAj4w \c If you trust this host, press "Accept" to add the key to PuTTY's -- cgit v1.2.3 From 660b8047cb343fb8a59c30351d384de6569ffd1f Mon Sep 17 00:00:00 2001 From: Jacob Nevins Date: Tue, 11 Jan 2022 23:54:33 +0000 Subject: Add --allow/deny-auth to Uppity usage message. These were added in commit a73aaf9457. --- unix/uppity.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/unix/uppity.c b/unix/uppity.c index b5c30075..84d332c0 100644 --- a/unix/uppity.c +++ b/unix/uppity.c @@ -375,6 +375,10 @@ static void show_help(FILE *fp) "s->c compression types\n" " --ssh1-ciphers STR override list of SSH-1 ciphers\n" " --ssh1-no-compression forbid compression in SSH-1\n" + " --deny-auth METHOD forbid a userauth method\n" + " --allow-auth METHOD allow a userauth method\n" + " (METHOD = none/password/publickey/kbdint/tis/" + "cryptocard)\n" " --exitsignum send buggy numeric \"exit-signal\" " "message\n" " --verbose print event log messages to standard " -- cgit v1.2.3 From 7843b428ad2b61d850811b24a32e1e5d992e329d Mon Sep 17 00:00:00 2001 From: Jacob Nevins Date: Wed, 12 Jan 2022 01:15:55 +0000 Subject: docs: Correct proven-primes side channel risk. After reading Simon's wishlist write-up 'rsa-gen-side-channels'. --- doc/pubkey.but | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/doc/pubkey.but b/doc/pubkey.but index dd481a3e..e53dbf00 100644 --- a/doc/pubkey.but +++ b/doc/pubkey.but @@ -177,12 +177,14 @@ are prime, because it generates the output number together with a proof of its primality. This takes more effort, but it eliminates that theoretical risk in the probabilistic method. -There in one way in which PuTTYgen's proven-primes method is not -strictly better than its probable-primes method. If you use PuTTYgen -to generate RSA or DSA keys on a computer that is potentially +There in one way in which PuTTYgen's \q{proven primes} method is not +strictly better than its \q{probable primes} method. If you use +PuTTYgen to generate an RSA key on a computer that is potentially susceptible to timing- or cache-based \i{side-channel attacks}, such as a shared computer, the \q{probable primes} method is designed to resist such attacks, whereas the \q{proven primes} methods are not. +(This is only a concern for RSA keys; for other key types, primes +are either not secret or not involved.) You might choose to switch from probable to proven primes if you have a local security standard that demands it, or if you don't trust the -- cgit v1.2.3 From 91806dfbb7977820b85a170841108ba2973abef6 Mon Sep 17 00:00:00 2001 From: Jacob Nevins Date: Wed, 12 Jan 2022 01:16:53 +0000 Subject: A few tweaks to SSH proxy docs. --- doc/config.but | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/doc/config.but b/doc/config.but index 16cf8560..cd210ae5 100644 --- a/doc/config.but +++ b/doc/config.but @@ -1962,10 +1962,17 @@ through to an external host. Selecting \I{Telnet proxy}\q{Telnet} allows you to tell PuTTY to use this type of proxy. \b Selecting \q{SSH} causes PuTTY to make a secondary SSH connection -to the proxy host, and then open a port-forwarding channel to the -final destination host. The \q{Proxy hostname} field will be -interpreted as the name of a PuTTY saved session if one exists, or a -hostname if not. +to the proxy host (sometimes called a \q{\i{jump host}} in this +context), and then open a port-forwarding channel to the +final destination host. + +\lcont{ +The \q{Proxy hostname} field will be interpreted as the name of a +PuTTY saved session if one exists, or a hostname if not. This +allows multi-hop jump paths, if the referenced saved session is +itself configured to use an SSH proxy; and it allows combining SSH +and non-SSH proxying. +} \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 @@ -2035,9 +2042,9 @@ 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. +something it considers appropriate for each type of proxy. Most +types of proxy (HTTP, SOCK5, SSH, Telnet, and local) 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 -- cgit v1.2.3 From dc183e1649b429a98fa39fdedb42d5e5ab820f26 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 15 Jan 2022 18:27:19 +0000 Subject: Windows Pageant and PuTTYgen: spiff up option parsing. These two tools had ad-hoc command loops with similar options, and I want to extend both (in particular, in a way that introduces options with arguments). So I've started by throwing together some common code to do all the tedious bits like finding option arguments wherever they might be, throwing errors, handling "--" and so on. Should be no functional change to the existing command-line syntax, except that now all long options are recognised in both "-foo" and "--foo" form. --- windows/CMakeLists.txt | 1 + windows/pageant.c | 100 +++++++++++++++++++----------------- windows/platform.h | 13 +++++ windows/puttygen.c | 49 +++++++++++++----- windows/utils/aux_match_opt.c | 117 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 220 insertions(+), 60 deletions(-) create mode 100644 windows/utils/aux_match_opt.c diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt index db2a38ea..aad2f5af 100644 --- a/windows/CMakeLists.txt +++ b/windows/CMakeLists.txt @@ -4,6 +4,7 @@ add_sources_from_current_dir(utils utils/agent_mutex_name.c utils/agent_named_pipe_name.c utils/arm_arch_queries.c + utils/aux_match_opt.c utils/cryptoapi.c utils/defaults.c utils/dll_hijacking_protection.c diff --git a/windows/pageant.c b/windows/pageant.c index f4acc987..a249f613 100644 --- a/windows/pageant.c +++ b/windows/pageant.c @@ -1371,12 +1371,24 @@ static struct winpgnt_client wpc[1]; HINSTANCE hinst; +static NORETURN void opt_error(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + char *msg = dupvprintf(fmt, ap); + va_end(ap); + + MessageBox(NULL, msg, "Pageant command line error", MB_ICONERROR | MB_OK); + + exit(1); +} + int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) { MSG msg; const char *command = NULL; bool show_keylist_on_startup = false; - int argc, i; + int argc; char **argv, **argstart; typedef struct CommandLineKey { @@ -1444,57 +1456,49 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) * stored in the 'clkeys' array. */ split_into_argv(cmdline, &argc, &argv, &argstart); - bool doing_opts = true; bool add_keys_encrypted = false; - for (i = 0; i < argc; i++) { - char *p = argv[i]; - if (*p == '-' && doing_opts) { - if (!strcmp(p, "-pgpfp")) { - pgp_fingerprints_msgbox(NULL); - return 1; - } else if (!strcmp(p, "-restrict-acl") || - !strcmp(p, "-restrict_acl") || - !strcmp(p, "-restrictacl")) { - restrict_process_acl(); - } else if (!strcmp(p, "-restrict-putty-acl") || - !strcmp(p, "-restrict_putty_acl")) { - restrict_putty_acl = true; - } else if (!strcmp(p, "--no-decrypt") || - !strcmp(p, "-no-decrypt") || - !strcmp(p, "--no_decrypt") || - !strcmp(p, "-no_decrypt") || - !strcmp(p, "--nodecrypt") || - !strcmp(p, "-nodecrypt") || - !strcmp(p, "--encrypted") || - !strcmp(p, "-encrypted")) { - add_keys_encrypted = true; - } else if (!strcmp(p, "-keylist") || !strcmp(p, "--keylist")) { - show_keylist_on_startup = true; - } else if (!strcmp(p, "-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 if (!strcmp(p, "--")) { - doing_opts = false; - } else { - char *msg = dupprintf("unrecognised command-line option\n" - "'%s'", p); - MessageBox(NULL, msg, "Pageant command-line syntax error", - MB_ICONERROR | MB_OK); - exit(1); - } - } else { + AuxMatchOpt amo = aux_match_opt_init(argc, argv, 0, opt_error); + while (!aux_match_done(&amo)) { + char *val; + #define match_opt(...) aux_match_opt( \ + &amo, NULL, __VA_ARGS__, (const char *)NULL) + #define match_optval(...) aux_match_opt( \ + &amo, &val, __VA_ARGS__, (const char *)NULL) + + if (aux_match_arg(&amo, &val)) { + /* + * Non-option arguments are expected to be key files, and + * added to clkeys. + */ sgrowarray(clkeys, clkeysize, nclkeys); CommandLineKey *clkey = &clkeys[nclkeys++]; - clkey->fn = filename_from_str(p); + clkey->fn = filename_from_str(val); clkey->add_encrypted = add_keys_encrypted; + } else if (match_opt("-pgpfp")) { + pgp_fingerprints_msgbox(NULL); + return 1; + } else if (match_opt("-restrict-acl", "-restrict_acl", + "-restrictacl")) { + restrict_process_acl(); + } else if (match_opt("-restrict-putty-acl", "-restrict_putty_acl")) { + restrict_putty_acl = true; + } else if (match_opt("-no-decrypt", "-no_decrypt", + "-nodecrypt", "-encrypted")) { + add_keys_encrypted = true; + } else if (match_opt("-keylist")) { + show_keylist_on_startup = true; + } else if (match_opt("-c")) { + /* + * If we see `-c', then the rest of the command line + * should be treated as a command to be spawned. + */ + if (amo.index < amo.argc-1) + command = argstart[amo.index + 1]; + else + command = ""; + break; + } else { + opt_error("unrecognised option '%s'\n", amo.argv[amo.index]); } } diff --git a/windows/platform.h b/windows/platform.h index b453a5bd..8c0bfa33 100644 --- a/windows/platform.h +++ b/windows/platform.h @@ -744,4 +744,17 @@ SeatPromptResult make_spr_sw_abort_winerror(const char *prefix, DWORD error); HANDLE lock_interprocess_mutex(const char *mutexname, char **error); void unlock_interprocess_mutex(HANDLE mutex); +typedef void (*aux_opt_error_fn_t)(const char *, ...); +typedef struct AuxMatchOpt { + int index, argc; + char **argv; + bool doing_opts; + aux_opt_error_fn_t error; +} AuxMatchOpt; +AuxMatchOpt aux_match_opt_init(int argc, char **argv, int start_index, + aux_opt_error_fn_t opt_error); +bool aux_match_arg(AuxMatchOpt *amo, char **val); +bool aux_match_opt(AuxMatchOpt *amo, char **val, const char *optname, ...); +bool aux_match_done(AuxMatchOpt *amo); + #endif /* PUTTY_WINDOWS_PLATFORM_H */ diff --git a/windows/puttygen.c b/windows/puttygen.c index 887a34b4..920b7339 100644 --- a/windows/puttygen.c +++ b/windows/puttygen.c @@ -1996,9 +1996,21 @@ void cleanup_exit(int code) HINSTANCE hinst; +static NORETURN void opt_error(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + char *msg = dupvprintf(fmt, ap); + va_end(ap); + + MessageBox(NULL, msg, "PuTTYgen command line error", MB_ICONERROR | MB_OK); + + exit(1); +} + int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) { - int argc, i; + int argc; char **argv; int ret; @@ -2014,21 +2026,34 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) split_into_argv(cmdline, &argc, &argv, NULL); - for (i = 0; i < argc; i++) { - if (!strcmp(argv[i], "-pgpfp")) { + int argbits = -1; + AuxMatchOpt amo = aux_match_opt_init(argc, argv, 0, opt_error); + while (!aux_match_done(&amo)) { + char *val; + #define match_opt(...) aux_match_opt( \ + &amo, NULL, __VA_ARGS__, (const char *)NULL) + #define match_optval(...) aux_match_opt( \ + &amo, &val, __VA_ARGS__, (const char *)NULL) + + if (aux_match_arg(&amo, &val)) { + if (!cmdline_keyfile) { + /* + * Assume the first argument to be a private key file, and + * attempt to load it. + */ + cmdline_keyfile = val; + continue; + } else { + opt_error("unexpected extra argument '%s'\n", val); + } + } else if (match_opt("-pgpfp")) { pgp_fingerprints_msgbox(NULL); return 1; - } else if (!strcmp(argv[i], "-restrict-acl") || - !strcmp(argv[i], "-restrict_acl") || - !strcmp(argv[i], "-restrictacl")) { + } else if (match_opt("-restrict-acl", "-restrict_acl", + "-restrictacl")) { restrict_process_acl(); } else { - /* - * Assume the first argument to be a private key file, and - * attempt to load it. - */ - cmdline_keyfile = argv[i]; - break; + opt_error("unrecognised option '%s'\n", amo.argv[amo.index]); } } diff --git a/windows/utils/aux_match_opt.c b/windows/utils/aux_match_opt.c new file mode 100644 index 00000000..190eddac --- /dev/null +++ b/windows/utils/aux_match_opt.c @@ -0,0 +1,117 @@ +/* + * Helper function for matching command-line options in the Windows + * auxiliary tools (PuTTYgen and Pageant). + * + * Supports basically the usual kinds of option, including GNUish + * --foo long options and simple -f short options. But historically + * those tools have also supported long options with a single dash, so + * we don't go full GNU and report those as syntax errors. + */ + +#include "putty.h" + +/* + * Call this to initialise the state structure. + */ +AuxMatchOpt aux_match_opt_init(int argc, char **argv, int start_index, + aux_opt_error_fn_t opt_error) +{ + AuxMatchOpt amo[1]; + + amo->argc = argc; + amo->argv = argv; + amo->index = start_index; + amo->doing_opts = true; + amo->error = opt_error; + + return amo[0]; +} + +/* + * Call this with a NULL-terminated list of synonymous option names. + * Point 'val' at a char * to receive the option argument, if one is + * expected. Set 'val' to NULL if no argument is expected. + */ +bool aux_match_opt(AuxMatchOpt *amo, char **val, const char *optname, ...) +{ + assert(amo->index < amo->argc); + + /* Find the end of the option name */ + char *opt = amo->argv[amo->index]; + ptrlen argopt; + argopt.ptr = opt; + argopt.len = strcspn(opt, "="); + + /* Normalise GNU-style --foo long options to the -foo that this + * tool has historically used */ + ptrlen argopt2 = make_ptrlen(NULL, 0); + if (ptrlen_startswith(argopt, PTRLEN_LITERAL("--"), NULL)) + ptrlen_startswith(argopt, PTRLEN_LITERAL("-"), &argopt2); + + /* See if this option matches any of our synonyms */ + va_list ap; + va_start(ap, optname); + bool matched = false; + while (optname) { + if (ptrlen_eq_string(argopt, optname)) { + matched = true; + break; + } + if (argopt2.ptr && strlen(optname) > 2 && + ptrlen_eq_string(argopt2, optname)) { + matched = true; + break; + } + optname = va_arg(ap, const char *); + } + va_end(ap); + if (!matched) + return false; + + /* Check for a value */ + if (opt[argopt.len]) { + if (!val) + amo->error("option '%s' does not expect a value", opt); + *val = opt + argopt.len + 1; + amo->index++; + } else if (!val) { + amo->index++; + } else { + if (amo->index + 1 >= amo->argc) + amo->error("option '%s' expects a value", opt); + *val = amo->argv[amo->index + 1]; + amo->index += 2; + } + + return true; +} + +/* + * Call this to return a non-option argument in *val. + */ +bool aux_match_arg(AuxMatchOpt *amo, char **val) +{ + assert(amo->index < amo->argc); + char *opt = amo->argv[amo->index]; + + if (!amo->doing_opts || opt[0] != '-' || !strcmp(opt, "-")) { + *val = opt; + amo->index++; + return true; + } + + return false; +} + +/* + * And call this to test whether we're done. Also handles '--'. + */ +bool aux_match_done(AuxMatchOpt *amo) +{ + if (amo->index < amo->argc && !strcmp(amo->argv[amo->index], "--")) { + amo->doing_opts = false; + amo->index++; + } + + return amo->index >= amo->argc; +} -- cgit v1.2.3 From 11aa9ab8f3f426131fdeb2f37f98a94802a0a37c Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 15 Jan 2022 18:30:34 +0000 Subject: Windows PuTTYgen: support cmdgen's key-type and PPK options. This imports the following options from command-line PuTTYgen, which all correspond to controls in Windows PuTTYgen's GUI, and let you set the GUI controls to initial values of your choice: -t -b -E --primes --strong-rsa --ppk-param The idea is that if someone generates a lot of keys and has standard non-default preferences, they can make a shortcut that passes those preferences on the command line. --- doc/pubkey.but | 76 +++++++++++++++++++++++ windows/puttygen.c | 175 ++++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 242 insertions(+), 9 deletions(-) diff --git a/doc/pubkey.but b/doc/pubkey.but index e53dbf00..ed755760 100644 --- a/doc/pubkey.but +++ b/doc/pubkey.but @@ -478,6 +478,82 @@ you have generated an SSH-1 private key using OpenSSH or Hence, the export options are not available if you have generated an SSH-1 key. +\S{puttygen-cli} PuTTYgen command-line configuration + +PuTTYgen supports a set of command-line options to configure many of +the same settings you can select in the GUI. This allows you to start +it up with your own preferences ready-selected, which might be useful +if you generate a lot of keys. (For example, you could make a Windows +shortcut that runs PuTTYgen with some command line options, or a batch +file or Powershell script that you could distribute to a whole +organisation containing your local standards.) + +The options supported on the command line are: + +\dt \cw{\-t} \e{keytype} + +\dd Type of key to generate. You can select \c{rsa}, \c{dsa}, +\c{ecdsa}, \c{eddsa} or \c{rsa1}. See \k{puttygen-keytype}. + +\dt \cw{\-b} \e{bits} + +\dd Size of the key to generate, in bits. See \k{puttygen-strength}. + +\dt \cw{\-\-primes} \e{method} + +\dd Method for generating prime numbers. You can select \c{probable}, +\c{proven}, and \c{proven-even}. See \k{puttygen-primes}. + +\dt \cw{\-\-strong-rsa} + +\dd When generating an RSA key, make sure the prime factors of the key +modulus are \q{strong primes}. A strong prime is a prime number chosen +to have a particular structure that makes certain factoring algorithms +more difficult to apply, so some security standards recommend their +use. However, the most modern factoring algorithms are unaffected, so +this option is probably not worth turning on \e{unless} you have a +local standard that recommends it. + +\dt \cw{\-\-ppk-param} \e{key}\cw{=}\e{value}\cw{,}... + +\dd Allows setting all the same details of the PPK save file format +described in \k{puttygen-save-params}. + +\lcont{ + +Aspects to change are specified as a series of \e{key}\cw{=}\e{value} pairs +separated by commas. The \e{key}s are: + +\dt \cw{version} + +\dd The PPK format version: either \cw{3} or \cw{2}. + +\dt \cw{kdf} + +\dd The variant of Argon2 to use: \cw{argon2id}, \cw{argon2i}, and +\cw{argon2d}. + +\dt \cw{memory} + +\dd The amount of memory needed to decrypt the key, in Kbyte. + +\dt \cw{time} + +\dd Specifies how much time is required to attempt decrypting the key, +in milliseconds. + +\dt \cw{passes} + +\dd Alternative to \cw{time}: specifies the number of hash passes +required to attempt decrypting the key. + +\dt \cw{parallelism} + +\dd Number of parallelisable threads that can be used to decrypt the +key. + +} + \H{pubkey-gettingready} Getting ready for public key authentication Connect to your SSH server using PuTTY with the SSH protocol. When the diff --git a/windows/puttygen.c b/windows/puttygen.c index 920b7339..6cda5817 100644 --- a/windows/puttygen.c +++ b/windows/puttygen.c @@ -629,6 +629,15 @@ static DWORD WINAPI generate_key_thread(void *param) return 0; } +struct InitialParams { + int keybutton; + int primepolicybutton; + bool rsa_strong; + FingerprintType fptype; + int keybits; + int eccurve_index, edcurve_index; +}; + struct MainDlgState { bool generation_thread_exists; bool key_exists; @@ -1393,15 +1402,16 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, cp.ypos = ymax; endbox(&cp); } - ui_set_key_type(hwnd, state, IDC_KEYSSH2RSA); - ui_set_primepolicy(hwnd, state, IDC_PRIMEGEN_PROB); - ui_set_rsa_strong(hwnd, state, false); - ui_set_fptype(hwnd, state, fptype_to_idc(SSH_FPTYPE_DEFAULT)); - SetDlgItemInt(hwnd, IDC_BITS, DEFAULT_KEY_BITS, false); + struct InitialParams *params = (struct InitialParams *)lParam; + ui_set_key_type(hwnd, state, params->keybutton); + ui_set_primepolicy(hwnd, state, params->primepolicybutton); + ui_set_rsa_strong(hwnd, state, params->rsa_strong); + ui_set_fptype(hwnd, state, fptype_to_idc(params->fptype)); + SetDlgItemInt(hwnd, IDC_BITS, params->keybits, false); SendDlgItemMessage(hwnd, IDC_ECCURVE, CB_SETCURSEL, - DEFAULT_ECCURVE_INDEX, 0); + params->eccurve_index, 0); SendDlgItemMessage(hwnd, IDC_EDCURVE, CB_SETCURSEL, - DEFAULT_EDCURVE_INDEX, 0); + params->edcurve_index, 0); /* * Initially, hide the progress bar and the key display, @@ -2013,6 +2023,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) int argc; char **argv; int ret; + struct InitialParams params[1]; dll_hijacking_protection(); @@ -2024,6 +2035,16 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) */ init_help(); + params->keybutton = IDC_KEYSSH2RSA; + params->primepolicybutton = IDC_PRIMEGEN_PROB; + params->rsa_strong = false; + params->fptype = SSH_FPTYPE_DEFAULT; + params->keybits = DEFAULT_KEY_BITS; + params->eccurve_index = DEFAULT_ECCURVE_INDEX; + params->edcurve_index = DEFAULT_EDCURVE_INDEX; + + save_params = ppk_save_default_parameters; + split_into_argv(cmdline, &argc, &argv, NULL); int argbits = -1; @@ -2052,15 +2073,151 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) } else if (match_opt("-restrict-acl", "-restrict_acl", "-restrictacl")) { restrict_process_acl(); + } else if (match_optval("-t")) { + if (!strcmp(val, "rsa") || !strcmp(val, "rsa2")) { + params->keybutton = IDC_KEYSSH2RSA; + } else if (!strcmp(val, "rsa1")) { + params->keybutton = IDC_KEYSSH1; + } else if (!strcmp(val, "dsa") || !strcmp(val, "dss")) { + params->keybutton = IDC_KEYSSH2DSA; + } else if (!strcmp(val, "ecdsa")) { + params->keybutton = IDC_KEYSSH2ECDSA; + } else if (!strcmp(val, "eddsa")) { + params->keybutton = IDC_KEYSSH2EDDSA; + } else if (!strcmp(val, "ed25519")) { + params->keybutton = IDC_KEYSSH2EDDSA; + argbits = 255; + } else if (!strcmp(val, "ed448")) { + params->keybutton = IDC_KEYSSH2EDDSA; + argbits = 448; + } else { + opt_error("unknown key type '%s'\n", val); + } + } else if (match_optval("-b")) { + argbits = atoi(val); + } else if (match_optval("-E")) { + if (!strcmp(val, "md5")) + params->fptype = SSH_FPTYPE_MD5; + else if (!strcmp(val, "sha256")) + params->fptype = SSH_FPTYPE_SHA256; + else + opt_error("unknown fingerprint type '%s'\n", val); + } else if (match_optval("-primes")) { + if (!strcmp(val, "probable") || + !strcmp(val, "probabilistic")) { + params->primepolicybutton = IDC_PRIMEGEN_PROB; + } else if (!strcmp(val, "provable") || + !strcmp(val, "proven") || + !strcmp(val, "simple") || + !strcmp(val, "maurer-simple")) { + params->primepolicybutton = IDC_PRIMEGEN_MAURER_SIMPLE; + } else if (!strcmp(val, "provable-even") || + !strcmp(val, "proven-even") || + !strcmp(val, "even") || + !strcmp(val, "complex") || + !strcmp(val, "maurer-complex")) { + params->primepolicybutton = IDC_PRIMEGEN_MAURER_COMPLEX; + } else { + opt_error("unrecognised prime-generation mode '%s'\n", val); + } + } else if (match_opt("-strong-rsa")) { + params->rsa_strong = true; + } else if (match_optval("-ppk-param", "-ppk-params")) { + char *nextval; + for (; val; val = nextval) { + nextval = strchr(val, ','); + if (nextval) + *nextval++ = '\0'; + + char *optvalue = strchr(val, '='); + if (!optvalue) + opt_error("PPK parameter '%s' expected a value\n", val); + *optvalue++ = '\0'; + + /* Non-numeric options */ + if (!strcmp(val, "kdf")) { + if (!strcmp(optvalue, "Argon2id") || + !strcmp(optvalue, "argon2id")) { + save_params.argon2_flavour = Argon2id; + } else if (!strcmp(optvalue, "Argon2i") || + !strcmp(optvalue, "argon2i")) { + save_params.argon2_flavour = Argon2i; + } else if (!strcmp(optvalue, "Argon2d") || + !strcmp(optvalue, "argon2d")) { + save_params.argon2_flavour = Argon2d; + } else { + opt_error("unrecognised kdf '%s'\n", optvalue); + } + continue; + } + + char *end; + unsigned long n = strtoul(optvalue, &end, 0); + if (!*optvalue || *end) + opt_error("value '%s' for PPK parameter '%s': expected a " + "number\n", optvalue, val); + + if (!strcmp(val, "version")) { + save_params.fmt_version = n; + } else if (!strcmp(val, "memory") || + !strcmp(val, "mem")) { + save_params.argon2_mem = n; + } else if (!strcmp(val, "time")) { + save_params.argon2_passes_auto = true; + save_params.argon2_milliseconds = n; + } else if (!strcmp(val, "passes")) { + save_params.argon2_passes_auto = false; + save_params.argon2_passes = n; + } else if (!strcmp(val, "parallelism") || + !strcmp(val, "parallel")) { + save_params.argon2_parallelism = n; + } else { + opt_error("unrecognised PPK parameter '%s'\n", val); + } + } } else { opt_error("unrecognised option '%s'\n", amo.argv[amo.index]); } } - save_params = ppk_save_default_parameters; + /* Translate argbits into eccurve_index and edcurve_index */ + if (argbits > 0) { + switch (params->keybutton) { + case IDC_KEYSSH2RSA: + case IDC_KEYSSH1: + case IDC_KEYSSH2DSA: + params->keybits = argbits; + break; + case IDC_KEYSSH2ECDSA: { + bool found = false; + for (int j = 0; j < n_ec_nist_curve_lengths; j++) + if (argbits == ec_nist_curve_lengths[j]) { + params->eccurve_index = j; + found = true; + break; + } + if (!found) + opt_error("unsupported ECDSA bit length %d", argbits); + break; + } + case IDC_KEYSSH2EDDSA: { + bool found = false; + for (int j = 0; j < n_ec_ed_curve_lengths; j++) + if (argbits == ec_ed_curve_lengths[j]) { + params->edcurve_index = j; + found = true; + break; + } + if (!found) + opt_error("unsupported EDDSA bit length %d", argbits); + break; + } + } + } random_setup_special(); - ret = DialogBox(hinst, MAKEINTRESOURCE(201), NULL, MainDlgProc) != IDOK; + ret = DialogBoxParam(hinst, MAKEINTRESOURCE(201), NULL, MainDlgProc, + (LPARAM)params) != IDOK; cleanup_exit(ret); return ret; /* just in case optimiser complains */ -- cgit v1.2.3 From 8a2883933d4f664681a015341cf8760a0231a41b Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 15 Jan 2022 18:36:22 +0000 Subject: Windows Pageant: integrate with Windows OpenSSH. After a discussion with a user recently, I investigated the Windows native ssh.exe, and found it uses a Windows named pipe to talk to its ssh-agent, in exactly the same way Pageant does. So if you tell ssh.exe where to find Pageant's pipe, it can talk directly to Pageant, and then you can have just one SSH agent. The slight problem is that Pageant's pipe name is not stable. It's generated using the same system as connection-sharing pipe names, and contains a hex hash value whose preimage was fed through CryptProtectData. And the problem with _that_ is that CryptProtectData apparently reinitialises its seed between login sessions (though it's stable within a login session), which I hadn't fully realised when I reused the same pipe-name construction code. One possibility, of course, would be to change Pageant so that it uses a fixed pipe name. But after a bit of thought, I think I actually like this feature, because the Windows named pipe namespace isn't segregated into areas writable by only particular users, so anyone using that namespace on a multiuser Windows box is potentially vulnerable to someone else squatting on the name you wanted to use. Using this system makes that harder, because the squatter won't be able to predict what the name is going to be! (Unless you shut down Pageant and start it up again within one login session - but there's only so much we can do. And squatting is at most a DoS, because PuTTY's named-pipe client code checks ownership of the other end of the pipe in all cases.) So instead I've gone for a different approach. Windows Pageant now supports an extra command-line option to write out a snippet of OpenSSH config file format on startup, containing an 'IdentityAgent' directive which points at the location of its named pipe. So you can use the 'Include' directive in your main .ssh/config to include this extra snippet, and then ssh.exe invocations will be able to find wherever the current Pageant has put its pipe. --- doc/pageant.but | 22 ++++++++++++++++++++++ windows/pageant.c | 30 ++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/doc/pageant.but b/doc/pageant.but index 8abb5cdf..392c1da1 100644 --- a/doc/pageant.but +++ b/doc/pageant.but @@ -170,6 +170,28 @@ by the command, like this: \c C:\PuTTY\pageant.exe d:\main.ppk -c C:\PuTTY\putty.exe +\S{pageant-cmdline-openssh} Integrating with Windows OpenSSH + +When Pageant starts up, it can optionally write out a file containing +an OpenSSH configuration directive that tells the Windows \c{ssh.exe} +where to find Pageant. If you include this file from your Windows SSH +configuration, then \c{ssh.exe} should automatically use Pageant as +its agent, so that you can keep your keys in one place and have both +SSH clients able to use them. + +The option is \c{--openssh-config}, and you follow it with a filename. + +To refer to this file from your main OpenSSH configuration, you can +use the \cq{Include} directive. For example, you might run Pageant +like this: + +\c pageant --openssh-config C:\Users\Simon\.ssh\pageant.conf + +and then add a directive like this to your main \cq{.ssh\\config} +file: + +\c Include C:\Users\Simon\.ssh\pageant.conf + \S{pageant-cmdline-keylist} Starting with the key list visible Start Pageant with the \i\c{--keylist} option to show the main window diff --git a/windows/pageant.c b/windows/pageant.c index a249f613..896b1fea 100644 --- a/windows/pageant.c +++ b/windows/pageant.c @@ -1390,6 +1390,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) bool show_keylist_on_startup = false; int argc; char **argv, **argstart; + const char *openssh_config_file = NULL; typedef struct CommandLineKey { Filename *fn; @@ -1487,6 +1488,8 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) add_keys_encrypted = true; } else if (match_opt("-keylist")) { show_keylist_on_startup = true; + } else if (match_optval("-openssh-config", "-openssh_config")) { + openssh_config_file = val; } else if (match_opt("-c")) { /* * If we see `-c', then the rest of the command line @@ -1554,6 +1557,23 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) return 1; } pageant_listener_got_socket(pl, sock); + + /* + * If we've been asked to write out an OpenSSH config file + * pointing at the named pipe, do so. + */ + if (openssh_config_file) { + FILE *fp = fopen(openssh_config_file, "w"); + if (!fp) { + char *err = dupprintf("Unable to write OpenSSH config file " + "to %s", openssh_config_file); + MessageBox(NULL, err, "Pageant Error", MB_ICONERROR | MB_OK); + return 1; + } + fprintf(fp, "IdentityAgent %s\n", pipename); + fclose(fp); + } + sfree(pipename); /* @@ -1752,6 +1772,16 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) if (keypath) filereq_free(keypath); + if (openssh_config_file) { + /* + * Leave this file around, but empty it, so that it doesn't + * refer to a pipe we aren't listening on any more. + */ + FILE *fp = fopen(openssh_config_file, "w"); + if (fp) + fclose(fp); + } + cleanup_exit(msg.wParam); return msg.wParam; /* just in case optimiser complains */ } -- cgit v1.2.3 From fafad1b8f6b76abad80b8a290a39590896f9ac2f Mon Sep 17 00:00:00 2001 From: Jacob Nevins Date: Sun, 16 Jan 2022 12:35:24 +0000 Subject: doc: relevance of 'Host keys' panel to SSH-1. The documentation claimed that it was entirely for SSH-2, but the manually-configured host keys part is still useful with SSH-1. --- doc/config.but | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/config.but b/doc/config.but index cd210ae5..b1dc8dd6 100644 --- a/doc/config.but +++ b/doc/config.but @@ -2455,7 +2455,7 @@ protection than SSH-2 without rekeys. \H{config-ssh-hostkey} The Host Keys panel -The Host Keys panel allows you to configure options related to SSH-2 +The Host Keys panel allows you to configure options related to \i{host key management}. Host keys are used to prove the server's identity, and assure you that @@ -2463,8 +2463,8 @@ the server is not being spoofed (either by a man-in-the-middle attack or by completely replacing it on the network). See \k{gs-hostkey} for a basic introduction to host keys. -This entire panel is only relevant to SSH protocol version 2; none of -these settings affect SSH-1 at all. +Much of this panel is only relevant to SSH protocol version 2; SSH-1 +only supports one type of host key. \S{config-ssh-hostkey-order} \ii{Host key type} selection @@ -2504,7 +2504,7 @@ to that for cipher selection (see \k{config-ssh-encryption}). \S{config-ssh-prefer-known-hostkeys} Preferring known host keys -By default, PuTTY will adjust the preference order for host key +By default, PuTTY will adjust the preference order for SSH-2 host key algorithms so that any host keys it already knows are moved to the top of the list. -- cgit v1.2.3 From cadd86ac4993fa9fcd69827c083803e159f499b1 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 22 Jan 2022 14:42:03 +0000 Subject: doc/CMakeLists.txt: reorganise custom targets. Jacob reported that on Debian buster, the command sequence cmake $srcdir cmake --build . cmake --build . --target doc would fail at the third step, with the make error "No rule to make target 'doc/cmake_version.but', needed by 'doc/html/index.html'". That seems odd, because the file ${VERSION_BUT} _was_ declared as a dependency of the rule that builds doc/html/*.html, and _cmake_ knew what rule built it (namely the custom target 'cmake_version_but'). I suspect this is a bug in cmake 3.13, because the same command sequence works fine with cmake 3.20. However, it's possible to work around, by means of adding the cmake _target name_ to the dependencies for any rule that uses that file, instead of relying on it to map the output _file_ name to that target. While I'm at it, I've transformed the rules that build copy.but and licence.but in the same way, turning those too into custom targets instead of custom commands (I've found that the former are more generally reliable across a range of cmake versions), and including the target names themselves as dependencies. --- doc/CMakeLists.txt | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index cc90fcdc..709df3de 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -22,6 +22,9 @@ if(HALIBUT AND PERL_EXECUTABLE) # If this is a source archive in which a fixed version.but was # provided, use that. Otherwise, infer one from the git checkout (if # possible). + + set(manual_dependencies) # extra target names to depend on + if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/version.but) set(VERSION_BUT ${CMAKE_CURRENT_SOURCE_DIR}/version.but) else() @@ -43,16 +46,20 @@ if(HALIBUT AND PERL_EXECUTABLE) ${INTERMEDIATE_VERSION_BUT} ${VERSION_BUT} DEPENDS check_git_commit_for_doc ${INTERMEDIATE_VERSION_BUT} COMMENT "Updating cmake_version.but") + set(manual_dependencies ${manual_dependencies} cmake_version_but) endif() - add_custom_command(OUTPUT copy.but + add_custom_target(copy_but + BYPRODUCTS copy.but COMMAND ${PERL_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/../licence.pl --copyrightdoc -o copy.but DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/../licence.pl ${CMAKE_CURRENT_SOURCE_DIR}/../LICENCE) - add_custom_command(OUTPUT licence.but + add_custom_target(licence_but + BYPRODUCTS licence.but COMMAND ${PERL_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/../licence.pl --licencedoc -o licence.but DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/../licence.pl ${CMAKE_CURRENT_SOURCE_DIR}/../LICENCE) + set(manual_dependencies ${manual_dependencies} copy_but licence_but) set(manual_sources ${CMAKE_CURRENT_BINARY_DIR}/copy.but @@ -83,7 +90,7 @@ if(HALIBUT AND PERL_EXECUTABLE) add_custom_command(OUTPUT ${html_dir}/index.html COMMAND ${HALIBUT} --html ${manual_sources} WORKING_DIRECTORY ${html_dir} - DEPENDS ${manual_sources}) + DEPENDS ${manual_sources} ${manual_dependencies}) list(APPEND doc_outputs ${html_dir}/index.html) # Windows help. @@ -92,14 +99,14 @@ if(HALIBUT AND PERL_EXECUTABLE) add_custom_command(OUTPUT putty.chm COMMAND ${HALIBUT} --chm chmextra.but ${manual_sources} WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - DEPENDS ${manual_sources}) + DEPENDS ${manual_sources} ${manual_dependencies}) list(APPEND doc_outputs putty.chm) # Plain text. add_custom_command(OUTPUT puttydoc.txt COMMAND ${HALIBUT} --text ${manual_sources} WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - DEPENDS ${manual_sources}) + DEPENDS ${manual_sources} ${manual_dependencies}) list(APPEND doc_outputs puttydoc.txt) endif() -- cgit v1.2.3 From e262dab642f2f11f7b64f706bb86659dabfed68f Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 22 Jan 2022 14:52:46 +0000 Subject: udp.but: update description of handle-io system. It's been so long since Windows Plink kept its stdio subthreads in its own main source file that I'd forgotten it had ever done so! They've lived in a separate module for managing Windows HANDLE-based I/O for ages. That module has recently changed its filename, but this piece of documentation was so out of date that the old filename wasn't in there - it was still mentioning the filename _before_ that. --- doc/udp.but | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/doc/udp.but b/doc/udp.but index 636ec4fc..b98633f8 100644 --- a/doc/udp.but +++ b/doc/udp.but @@ -278,16 +278,17 @@ 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. +Also, the front ends can use multiple threads if they like. For +example, the Windows front-end code spawns subthreads to deal with +bidirectional blocking I/O on non-network streams such as Windows +pipes. However, it keeps tight control of its auxiliary threads, and +uses them only for that one purpose, as a form of \cw{select()}. +Pretty much all the code outside \cw{windows/handle-io.c} is \e{only} +ever called from the one primary thread; the others just loop round +blocking on file handles, and signal the main thread (via Windows +event objects) when some real work needs doing. This is not considered +a portability hazard because that code is already Windows-specific and +needs rewriting on other platforms. One important consequence of this: PuTTY has only one thread in which to do everything. That \q{everything} may include managing -- cgit v1.2.3 From b67c01be841b602faea9c3253a4d08952bd7fc0c Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 22 Jan 2022 15:40:28 +0000 Subject: Remove orphaned prototype of ser_setup_config_box. The serial configuration was folded back into config.c in commit 18d273fcf1eca23, but I forgot to remove the prototype in putty.h. --- putty.h | 6 ------ 1 file changed, 6 deletions(-) diff --git a/putty.h b/putty.h index edb5eb4c..7b7486a5 100644 --- a/putty.h +++ b/putty.h @@ -2399,12 +2399,6 @@ char const *conf_dest(Conf *conf); */ void prepare_session(Conf *conf); -/* - * Exports from sercfg.c. - */ -void ser_setup_config_box(struct controlbox *b, bool midsession, - int parity_mask, int flow_mask); - /* * Exports from version.c. */ -- cgit v1.2.3 From 81391e3f2367df2601ccd671f2fe91bbdf1107ae Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 22 Jan 2022 15:47:22 +0000 Subject: nocmdline.c: remove unused stub of cmdline_process_param. This was needed at the time it was introduced in commit c99338b750aed37, because uxputty.c (as was) handled its non-option arguments directly (that was how Unix PuTTY and pterm arranged to have different sets of them), and sometimes did it by converting them into option arguments and feeding them to cmdline.c, so it still needed to not fail to link when not linked against cmdline.c (for the GtkApplication based front end). But now the non-option argument handling is centralised into cmdline.c itself, with a system of flags indicating which arguments a particular tool expects. So that stub is no longer needed. --- stubs/nocmdline.c | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/stubs/nocmdline.c b/stubs/nocmdline.c index 9efd9ef2..de3cfd0d 100644 --- a/stubs/nocmdline.c +++ b/stubs/nocmdline.c @@ -19,19 +19,3 @@ SeatPromptResult cmdline_get_passwd_input(prompts_t *p) { return SPR_INCOMPLETE; } - -/* - * The main cmdline_process_param function is normally called from - * applications' main(). An application linking against this stub - * module shouldn't have a main() that calls it in the first place :-) - * but it is just occasionally called by other supporting functions, - * such as one in uxputty.c which sometimes handles a non-option - * argument by making up equivalent options and passing them back to - * this function. So we have to provide a link-time stub of this - * function, but it had better not end up being called at run time. - */ -int cmdline_process_param(const char *p, char *value, - int need_save, Conf *conf) -{ - unreachable("cmdline_process_param should never be called"); -} -- cgit v1.2.3 From 5935c682884386672d9acab5e81e15aa66573c98 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 22 Jan 2022 15:38:53 +0000 Subject: Update source file names in comments and docs. Correcting a source file name in the docs just now reminded me that I've seen a lot of outdated source file names elsewhere in the code, due to all the reorganisation since we moved to cmake. Here's a giant pass of trying to make them all accurate again. --- charset/localenc.c | 2 +- cmake/gitcommit.cmake | 4 ++-- crypto/blowfish.h | 2 +- crypto/ecc-ssh.c | 4 ++-- crypto/mpint_i.h | 2 +- doc/faq.but | 3 ++- misc.h | 17 ++++++-------- mpint.h | 4 ++-- network.h | 4 ++-- pscp.c | 2 +- putty.h | 30 +++++++++++++----------- puttymem.h | 13 ++++++----- settings.c | 6 ++--- ssh.h | 18 +++++++-------- ssh/gss.h | 2 +- ssh/server.h | 2 +- ssh/sesschan.c | 6 ++--- ssh/sharing.c | 25 ++++++++++---------- ssh/ssh.c | 2 +- sshcr.h | 1 - test/fuzzterm.c | 2 +- unix/columns.c | 2 +- unix/config-gtk.c | 2 +- unix/config-unix.c | 2 +- unix/console.c | 4 ++-- unix/dialog.c | 4 ++-- unix/fd-socket.c | 2 +- unix/gtk-common.c | 2 +- unix/keygen-noise.c | 2 +- unix/local-proxy.c | 2 +- unix/main-gtk-application.c | 5 ++-- unix/main-gtk-simple.c | 6 ++--- unix/no-gtk.c | 2 +- unix/pageant.c | 2 +- unix/platform.h | 49 ++++++++++++++++++++------------------- unix/plink.c | 2 +- unix/printing.c | 4 ++-- unix/pterm.c | 2 +- unix/putty.c | 2 +- unix/sftp.c | 2 +- unix/storage.c | 2 +- unix/unifont.h | 6 ++--- unix/utils/pgp_fingerprints.c | 2 +- unix/uxsel.c | 4 ++-- unix/window.c | 4 ++-- unix/x11.c | 2 +- unix/x11misc.h | 8 +------ windows/config.c | 2 +- windows/console.c | 2 +- windows/controls.c | 2 +- windows/dialog.c | 2 +- windows/handle-io.c | 2 +- windows/handle-socket.c | 6 ++--- windows/help.c | 2 +- windows/jump-list.c | 2 +- windows/local-proxy.c | 4 ++-- windows/named-pipe-client.c | 10 ++++---- windows/no-jump-list.c | 4 ++-- windows/nohelp.c | 2 +- windows/pageant.c | 5 ++-- windows/platform.h | 54 +++++++++++++++++++++---------------------- windows/security-api.h | 2 +- windows/select-cli.c | 2 +- windows/select-gui.c | 2 +- windows/sftp.c | 2 +- windows/storage.c | 2 +- windows/win-gui-seat.h | 4 ++-- windows/x11.c | 2 +- 68 files changed, 196 insertions(+), 196 deletions(-) diff --git a/charset/localenc.c b/charset/localenc.c index 4aa4ae66..03340d6d 100644 --- a/charset/localenc.c +++ b/charset/localenc.c @@ -1,5 +1,5 @@ /* - * local.c - translate our internal character set codes to and from + * localenc.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 diff --git a/cmake/gitcommit.cmake b/cmake/gitcommit.cmake index 37358208..adb644bc 100644 --- a/cmake/gitcommit.cmake +++ b/cmake/gitcommit.cmake @@ -1,4 +1,4 @@ -# Pure cmake script to write out cmake_commit.h +# Pure cmake script to write out cmake_commit.c and cmake_version.but set(DEFAULT_COMMIT "unavailable") set(commit "${DEFAULT_COMMIT}") @@ -40,7 +40,7 @@ endif() if(OUTPUT_TYPE STREQUAL header) file(WRITE "${OUTPUT_FILE}" "\ /* - * cmake_commit.h - string literal giving the source git commit, if known. + * cmake_commit.c - string literal giving the source git commit, if known. * * Generated by cmake/gitcommit.cmake. */ diff --git a/crypto/blowfish.h b/crypto/blowfish.h index a9efe3da..54158922 100644 --- a/crypto/blowfish.h +++ b/crypto/blowfish.h @@ -1,5 +1,5 @@ /* - * Header file shared between sshblowf.c and sshbcrypt.c. Exposes the + * Header file shared between blowfish.c and bcrypt.c. Exposes the * internal Blowfish routines needed by bcrypt. */ diff --git a/crypto/ecc-ssh.c b/crypto/ecc-ssh.c index 893a5877..d246d2b7 100644 --- a/crypto/ecc-ssh.c +++ b/crypto/ecc-ssh.c @@ -548,8 +548,8 @@ static EdwardsPoint *eddsa_decode(ptrlen encoded, const struct ec_curve *curve) mp_free(y); /* A point constructed in this way will always satisfy the curve - * equation, unless ecc.c wasn't able to construct one at all, in - * which case P is now NULL. Either way, return it. */ + * equation, unless ecc-arithmetic.c wasn't able to construct one + * at all, in which case P is now NULL. Either way, return it. */ return P; } diff --git a/crypto/mpint_i.h b/crypto/mpint_i.h index d37e75f0..fb2b367c 100644 --- a/crypto/mpint_i.h +++ b/crypto/mpint_i.h @@ -266,7 +266,7 @@ #endif /* DEFINE_BIGNUMDBLINT */ /* ---------------------------------------------------------------------- - * Data structures used inside bignum.c. + * Data structures used inside mpint.c. */ struct mp_int { diff --git a/doc/faq.but b/doc/faq.but index 474b984a..e9dddb66 100644 --- a/doc/faq.but +++ b/doc/faq.but @@ -325,7 +325,8 @@ unfinished. If any OS X and/or GTK programming experts are keen to have a finished version of this, we urge them to help out with some of the remaining -problems! See the TODO list in \c{unix/gtkapp.c} in the source code. +problems! See the TODO list in \c{unix/main-gtk-application.c} in the +source code. \S{faq-epoc}{Question} Will there be a port to EPOC? diff --git a/misc.h b/misc.h index 0cb55b65..a85b87ae 100644 --- a/misc.h +++ b/misc.h @@ -1,5 +1,6 @@ /* - * Header for misc.c. + * Header for miscellaneous helper functions, mostly defined in the + * utils subdirectory. */ #ifndef PUTTY_MISC_H @@ -33,7 +34,7 @@ void burnstr(char *string); /* * The visible part of a strbuf structure. There's a surrounding - * implementation struct in misc.c, which isn't exposed to client + * implementation struct in strbuf.c, which isn't exposed to client * code. */ struct strbuf { @@ -63,11 +64,7 @@ strbuf *strbuf_new_for_agent_query(void); void strbuf_finalise_agent_query(strbuf *buf); /* String-to-Unicode converters that auto-allocate the destination and - * work around the rather deficient interface of mb_to_wc. - * - * These actually live in miscucs.c, not misc.c (the distinction being - * that the former is only linked into tools that also have the main - * Unicode support). */ + * work around the rather deficient interface of mb_to_wc. */ wchar_t *dup_mb_to_wc_c(int codepage, int flags, const char *string, int len); wchar_t *dup_mb_to_wc(int codepage, int flags, const char *string); @@ -132,9 +129,9 @@ static inline void bufchain_set_callback(bufchain *ch, IdempotentCallback *ic) { extern void queue_idempotent_callback(struct IdempotentCallback *ic); /* Wrapper that puts in the standard queue_idempotent_callback - * function. Lives here rather than in utils.c so that standalone - * programs can use the bufchain facility without this optional - * callback feature and not need to provide a stub of + * function. Lives here rather than in bufchain.c so that + * standalone programs can use the bufchain facility without this + * optional callback feature and not need to provide a stub of * queue_idempotent_callback. */ bufchain_set_callback_inner(ch, ic, queue_idempotent_callback); } diff --git a/mpint.h b/mpint.h index 51322aa6..ae09a24f 100644 --- a/mpint.h +++ b/mpint.h @@ -428,10 +428,10 @@ mp_int *mp_rshift_fixed(mp_int *x, size_t shift); * * The _function_ definitions here will expect to be given a gen_data * function that provides random data. Normally you'd use this using - * random_read() from random.c, and the macro wrappers automate that. + * random_read() from sshrand.c, and the macro wrappers automate that. * * (This is a bit of a dodge to avoid mpint.c having a link-time - * dependency on random.c, so that programs can link against one but + * dependency on sshrand.c, so that programs can link against one but * not the other: if a client of this header uses one of these macros * then _they_ have link-time dependencies on both modules.) * diff --git a/network.h b/network.h index 5d0112b0..a855f7de 100644 --- a/network.h +++ b/network.h @@ -302,7 +302,7 @@ static inline SocketPeerInfo *sk_peer_info(Socket *s) /* * The structure returned from sk_peer_info, and a function to free - * one (in misc.c). + * one (in utils). */ struct SocketPeerInfo { int addressfamily; @@ -340,7 +340,7 @@ struct SocketPeerInfo { void sk_free_peer_info(SocketPeerInfo *pi); /* - * Simple wrapper on getservbyname(), needed by ssh.c. Returns the + * Simple wrapper on getservbyname(), needed by portfwd.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 diff --git a/pscp.c b/pscp.c index d18b0ecd..da9e9aaa 100644 --- a/pscp.c +++ b/pscp.c @@ -1,5 +1,5 @@ /* - * scp.c - Scp (Secure Copy) client for PuTTY. + * pscp.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. diff --git a/putty.h b/putty.h index 7b7486a5..606ff178 100644 --- a/putty.h +++ b/putty.h @@ -1450,7 +1450,7 @@ static inline bool seat_get_cursor_position(Seat *seat, int *x, int *y) /* Unlike the seat's actual method, the public entry point * seat_connection_fatal is a wrapper function with a printf-like API, - * defined in misc.c. */ + * defined in utils. */ void seat_connection_fatal(Seat *seat, const char *fmt, ...) PRINTF_LIKE(2, 3); /* Handy aliases for seat_output which set is_stderr to a fixed value. */ @@ -1523,7 +1523,7 @@ bool nullseat_get_cursor_position(Seat *seat, int *x, int *y); /* * Seat functions provided by the platform's console-application - * support module (wincons.c, uxcons.c). + * support module (console.c in each platform subdirectory). */ void console_connection_fatal(Seat *seat, const char *message); @@ -2049,7 +2049,7 @@ void fontspec_serialise(BinarySink *bs, FontSpec *f); FontSpec *fontspec_deserialise(BinarySource *src); /* - * Exports from noise.c. + * Exports from each platform's noise.c. */ typedef enum NoiseSourceId { NOISE_SOURCE_TIME, @@ -2075,6 +2075,10 @@ void noise_get_heavy(void (*func) (void *, int)); void noise_get_light(void (*func) (void *, int)); void noise_regular(void); void noise_ultralight(NoiseSourceId id, unsigned long data); + +/* + * Exports from sshrand.c. + */ void random_save_seed(void); void random_destroy_seed(void); @@ -2234,7 +2238,7 @@ static inline void lp_logging_error(LogPolicy *lp, const char *event) static inline bool lp_verbose(LogPolicy *lp) { return lp->vt->verbose(lp); } -/* Defined in conscli.c, used in several console command-line tools */ +/* Defined in clicons.c, used in several console command-line tools */ extern LogPolicy console_cli_logpolicy[]; int console_askappend(LogPolicy *lp, Filename *filename, @@ -2387,7 +2391,7 @@ void pinger_reconfig(Pinger *, Conf *oldconf, Conf *newconf); void pinger_free(Pinger *); /* - * Exports from misc.c. + * Exports from modules in utils. */ #include "misc.h" @@ -2400,13 +2404,13 @@ char const *conf_dest(Conf *conf); void prepare_session(Conf *conf); /* - * Exports from version.c. + * Exports from version.c and cmake_commit.c. */ extern const char ver[]; extern const char commitid[]; /* - * Exports from unicode.c. + * Exports from unicode.c in platform subdirs. */ #ifndef CP_UTF8 #define CP_UTF8 65001 @@ -2434,7 +2438,7 @@ int mk_wcwidth_cjk(unsigned int ucs); int mk_wcswidth_cjk(const unsigned int *pwcs, size_t n); /* - * Exports from pageantc.c. + * Exports from agent-client.c in platform subdirs. * * agent_query returns NULL for here's-a-response, and non-NULL for * query-in- progress. In the latter case there will be a call to @@ -2453,8 +2457,8 @@ int mk_wcswidth_cjk(const unsigned int *pwcs, size_t n); * * Passing a null pointer as callback forces agent_query to behave * synchronously, i.e. it will block if necessary, and guarantee to - * return NULL. The wrapper function agent_query_synchronous() makes - * this easier. + * return NULL. The wrapper function agent_query_synchronous() + * (defined in its own module aqsync.c) makes this easier. */ typedef struct agent_pending_query agent_pending_query; agent_pending_query *agent_query( @@ -2476,7 +2480,7 @@ int wc_match(const char *wildcard, const char *target); bool wc_unescape(char *output, const char *wildcard); /* - * Exports from frontend (windlg.c etc) + * Exports from frontend (dialog.c etc) */ void pgp_fingerprints(void); /* @@ -2486,7 +2490,7 @@ void pgp_fingerprints(void); bool have_ssh_host_key(const char *host, int port, const char *keytype); /* - * Exports from console frontends (wincons.c, uxcons.c) + * Exports from console frontends (console.c in platform subdirs) * that aren't equivalents to things in windlg.c et al. */ extern bool console_batch_mode, console_antispoof_prompt; @@ -2499,7 +2503,7 @@ void console_print_error_msg_fmt(const char *prefix, const char *fmt, ...) PRINTF_LIKE(2, 3); /* - * Exports from printing.c. + * Exports from printing.c in platform subdirs. */ typedef struct printer_enum_tag printer_enum; typedef struct printer_job_tag printer_job; diff --git a/puttymem.h b/puttymem.h index 6513d6cf..11c1f83a 100644 --- a/puttymem.h +++ b/puttymem.h @@ -107,18 +107,19 @@ void *safegrowarray(void *array, size_t *size, size_t eltsize, /* * This function is called by the innermost safemalloc/saferealloc - * functions when allocation fails. Usually it's provided by misc.c - * which ties it into an application's existing modalfatalbox() - * system, but standalone test applications can reimplement it some - * other way if they prefer. + * functions when allocation fails. Usually it's provided by an + * implementation in utils, which ties it into an application's + * existing modalfatalbox() system, but standalone test applications + * can reimplement it some other way if they prefer. */ NORETURN void out_of_memory(void); #ifdef MINEFIELD /* * Definitions for Minefield, PuTTY's own Windows-specific malloc - * debugger in the style of Electric Fence. Implemented in winmisc.c, - * and referred to by the main malloc wrappers in memory.c. + * debugger in the style of Electric Fence. Implemented in + * windows/utils/minefield.c, and referred to by the main malloc + * wrappers in memory.c. */ void *minefield_c_malloc(size_t size); void minefield_c_free(void *p); diff --git a/settings.c b/settings.c index 89986703..ff2bb6c4 100644 --- a/settings.c +++ b/settings.c @@ -49,9 +49,9 @@ static const struct keyvalwhere hknames[] = { /* * 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). + * This is currently precisely the same as the set in + * ssh/ttymode-list.h, but could in principle differ if other backends + * started to support tty modes (e.g., the pty backend). * The set of modes in in this array is currently significant for * settings migration from old versions; if they change, review the * gppmap() invocation for "TerminalModes". diff --git a/ssh.h b/ssh.h index 0c2edf33..8187b394 100644 --- a/ssh.h +++ b/ssh.h @@ -735,7 +735,7 @@ static inline const char *ssh2_mac_text_name(ssh2_mac *m) static inline const ssh2_macalg *ssh2_mac_alg(ssh2_mac *m) { return m->vt; } -/* Centralised 'methods' for ssh2_mac, defined in sshmac.c. These run +/* Centralised 'methods' for ssh2_mac, defined in mac.c. These run * the MAC in a specifically SSH-2 style, i.e. taking account of a * packet sequence number as well as the data to be authenticated. */ bool ssh2_mac_verresult(ssh2_mac *, const void *); @@ -1075,13 +1075,13 @@ extern const 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 + * that fails. This variable is the means by which pscp.c can reach * into the SSH code and find out which one it got. */ extern bool ssh_fallback_cmd(Backend *backend); /* - * The PRNG type, defined in sshprng.c. Visible data fields are + * The PRNG type, defined in prng.c. Visible data fields are * 'savesize', which suggests how many random bytes you should request * from a particular PRNG instance to write to putty.rnd, and a * BinarySink implementation which you can use to write seed data in @@ -1090,7 +1090,7 @@ extern bool ssh_fallback_cmd(Backend *backend); struct prng { size_t savesize; BinarySink_IMPLEMENTATION; - /* (also there's a surrounding implementation struct in sshprng.c) */ + /* (also there's a surrounding implementation struct in prng.c) */ }; prng *prng_new(const ssh_hashalg *hashalg); void prng_free(prng *p); @@ -1161,10 +1161,6 @@ struct X11FakeAuth { ssh_sharing_connstate *share_cs; share_channel *share_chan; }; -void *x11_make_greeting(int endian, int protomajor, int protominor, - int auth_proto, const void *auth_data, int auth_len, - const char *peer_ip, int peer_port, - int *outlen); int x11_authcmp(void *av, void *bv); /* for putting X11FakeAuth in a tree234 */ /* * x11_setup_display() parses the display variable and fills in an @@ -1194,7 +1190,7 @@ 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 */ +/* X11-related helper functions in utils */ /* * This function does the job of platform_get_x11_auth, provided * it is told where to find a normally formatted .Xauthority file: @@ -1213,6 +1209,10 @@ void x11_get_auth_from_authfile(struct X11Display *display, void x11_format_auth_for_authfile( BinarySink *bs, SockAddr *addr, int display_no, ptrlen authproto, ptrlen authdata); +void *x11_make_greeting(int endian, int protomajor, int protominor, + int auth_proto, const void *auth_data, int auth_len, + const char *peer_ip, int peer_port, + int *outlen); int x11_identify_auth_proto(ptrlen protoname); void *x11_dehexify(ptrlen hex, int *outlen); bool x11_parse_ip(const char *addr_string, unsigned long *ip); diff --git a/ssh/gss.h b/ssh/gss.h index eaef5dd9..c819d48b 100644 --- a/ssh/gss.h +++ b/ssh/gss.h @@ -32,7 +32,7 @@ typedef gss_name_t Ssh_gss_name; #define GSS_DEF_REKEY_MINS 2 /* Default minutes between GSS cache checks */ -/* Functions, provided by either wingss.c or gssc.c */ +/* Functions, provided by either {windows,unix}/gss.c or gssc.c */ struct ssh_gss_library; diff --git a/ssh/server.h b/ssh/server.h index e93fd299..c2b3647b 100644 --- a/ssh/server.h +++ b/ssh/server.h @@ -137,7 +137,7 @@ int platform_make_x11_server(Plug *plug, const char *progname, int mindisp, Conf *make_ssh_server_conf(void); -/* Provided by Unix front end programs to uxsftpserver.c */ +/* Provided by Unix front end programs to unix/sftpserver.c */ void make_unix_sftp_filehandle_key(void *data, size_t size); typedef struct agentfwd agentfwd; diff --git a/ssh/sesschan.c b/ssh/sesschan.c index 15b8bb4e..978ffb9a 100644 --- a/ssh/sesschan.c +++ b/ssh/sesschan.c @@ -230,7 +230,7 @@ Channel *sesschan_new(SshChannel *c, LogContext *logctx, sess->conf = conf_new(); load_open_settings(NULL, sess->conf); - /* Set close-on-exit = true to suppress uxpty.c's "[pterm: process + /* Set close-on-exit = true to suppress pty.c's "[pterm: process * terminated with status x]" message */ conf_set_int(sess->conf, CONF_close_on_exit, FORCE_ON); @@ -303,7 +303,7 @@ static void sesschan_start_backend(sesschan *sess, const char *cmd) * will be set as part of X or agent forwarding, and shouldn't be * confusingly set in the absence of that. * - * (DISPLAY must also be cleared, but uxpty.c will do that anyway + * (DISPLAY must also be cleared, but pty.c will do that anyway * when our get_x_display method returns NULL.) */ static const char *const env_to_unset[] = { @@ -560,7 +560,7 @@ bool sesschan_send_break(Channel *chan, unsigned length) if (sess->backend) { /* We ignore the break length. We could pass it through as the - * 'arg' parameter, and have uxpty.c collect it and pass it on + * 'arg' parameter, and have pty.c collect it and pass it on * to tcsendbreak, but since tcsendbreak in turn assigns * implementation-defined semantics to _its_ duration * parameter, this all just sounds too difficult. */ diff --git a/ssh/sharing.c b/ssh/sharing.c index dfa0ca22..7b52dc09 100644 --- a/ssh/sharing.c +++ b/ssh/sharing.c @@ -1177,7 +1177,7 @@ void share_got_pkt_from_server(ssh_sharing_connstate *cs, int type, case SSH2_MSG_REQUEST_SUCCESS: case SSH2_MSG_REQUEST_FAILURE: globreq = cs->globreq_head; - assert(globreq); /* should match the queue in ssh.c */ + assert(globreq); /* should match the queue in connection2.c */ if (globreq->type == GLOBREQ_TCPIP_FORWARD) { if (type == SSH2_MSG_REQUEST_FAILURE) { share_remove_forwarding(cs, globreq->fwd); @@ -1289,7 +1289,8 @@ void share_got_pkt_from_server(ssh_sharing_connstate *cs, int type, break; default: - unreachable("This packet type should never have come from ssh.c"); + unreachable("This packet type should never have come from " + "connection2.c"); } } @@ -1356,12 +1357,12 @@ static void share_got_pkt_from_downstream(struct ssh_sharing_connstate *cs, host = mkstr(hostpl); /* - * See if we can allocate space in ssh.c's tree of remote - * port forwardings. If we can't, it's because another - * client sharing this connection has already allocated - * the identical port forwarding, so we take it on - * ourselves to manufacture a failure packet and send it - * back to downstream. + * See if we can allocate space in the connection layer's + * tree of remote port forwardings. If we can't, it's + * because another client sharing this connection has + * already allocated the identical port forwarding, so we + * take it on ourselves to manufacture a failure packet + * and send it back to downstream. */ rpf = ssh_rportfwd_alloc( cs->parent->cl, host, port, NULL, 0, 0, NULL, NULL, cs); @@ -1430,8 +1431,8 @@ static void share_got_pkt_from_downstream(struct ssh_sharing_connstate *cs, } } else { /* - * Tell ssh.c to stop sending us channel-opens for - * this forwarding. + * Tell the connection layer to stop sending us + * channel-opens for this forwarding. */ ssh_rportfwd_remove(cs->parent->cl, fwd->rpf); @@ -1872,8 +1873,8 @@ void share_activate(ssh_sharing_state *sharestate, const char *server_verstring) { /* - * Indication from ssh.c that we are now ready to begin serving - * any downstreams that have already connected to us. + * Indication from connection layer that we are now ready to begin + * serving any downstreams that have already connected to us. */ struct ssh_sharing_connstate *cs; int i; diff --git a/ssh/ssh.c b/ssh/ssh.c index 6ba1c577..0db68c59 100644 --- a/ssh/ssh.c +++ b/ssh/ssh.c @@ -1246,7 +1246,7 @@ static int ssh_cfg_info(Backend *be) /* * 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 + * that fails. This variable is the means by which pscp.c can reach * into the SSH code and find out which one it got. */ extern bool ssh_fallback_cmd(Backend *be) diff --git a/sshcr.h b/sshcr.h index e87ce864..12bfea6c 100644 --- a/sshcr.h +++ b/sshcr.h @@ -18,7 +18,6 @@ * 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 diff --git a/test/fuzzterm.c b/test/fuzzterm.c index 02de7bf6..faea2dd7 100644 --- a/test/fuzzterm.c +++ b/test/fuzzterm.c @@ -132,7 +132,7 @@ void nonfatal(const char *fmt, ...) { } /* needed by timing.c */ void timer_change_notify(unsigned long next) { } -/* needed by config.c and sercfg.c */ +/* needed by config.c */ void dlg_radiobutton_set(union control *ctrl, dlgparam *dp, int whichbutton) { } int dlg_radiobutton_get(union control *ctrl, dlgparam *dp) { return 0; } diff --git a/unix/columns.c b/unix/columns.c index e53d4a06..a1049b47 100644 --- a/unix/columns.c +++ b/unix/columns.c @@ -1,5 +1,5 @@ /* - * gtkcols.c - implementation of the `Columns' GTK layout container. + * columns.c - implementation of the `Columns' GTK layout container. */ #include diff --git a/unix/config-gtk.c b/unix/config-gtk.c index 93c48ce6..52e6e181 100644 --- a/unix/config-gtk.c +++ b/unix/config-gtk.c @@ -1,5 +1,5 @@ /* - * gtkcfg.c - the GTK-specific parts of the PuTTY configuration + * config-gtk.c - the GTK-specific parts of the PuTTY configuration * box. */ diff --git a/unix/config-unix.c b/unix/config-unix.c index 7efff05d..ee1b953d 100644 --- a/unix/config-unix.c +++ b/unix/config-unix.c @@ -1,5 +1,5 @@ /* - * uxcfg.c - the Unix-specific parts of the PuTTY configuration + * config-unix.c - the Unix-specific parts of the PuTTY configuration * box. */ diff --git a/unix/console.c b/unix/console.c index 434854bd..cc6c9853 100644 --- a/unix/console.c +++ b/unix/console.c @@ -1,6 +1,6 @@ /* - * uxcons.c: various interactive-prompt routines shared between the - * Unix console PuTTY tools + * unix/console.c: various interactive-prompt routines shared between + * the Unix console PuTTY tools */ #include diff --git a/unix/dialog.c b/unix/dialog.c index 8f86d116..7d1514fd 100644 --- a/unix/dialog.c +++ b/unix/dialog.c @@ -1,5 +1,5 @@ /* - * gtkdlg.c - GTK implementation of the PuTTY configuration box. + * dialog.c - GTK implementation of the PuTTY configuration box. */ #include @@ -1787,7 +1787,7 @@ static void filefont_clicked(GtkButton *button, gpointer data) #else /* !GTK_CHECK_VERSION(2,0,0) */ /* - * Use the unifontsel code provided in gtkfont.c. + * Use the unifontsel code provided in unifont.c. */ unifontsel *fontsel = unifontsel_new("Select a font"); diff --git a/unix/fd-socket.c b/unix/fd-socket.c index 85dbc7c0..036979d3 100644 --- a/unix/fd-socket.c +++ b/unix/fd-socket.c @@ -1,5 +1,5 @@ /* - * uxfdsock.c: implementation of Socket that just talks to two + * fd-socket.c: implementation of Socket that just talks to two * existing input and output file descriptors. */ diff --git a/unix/gtk-common.c b/unix/gtk-common.c index 4fcc0335..9d48e882 100644 --- a/unix/gtk-common.c +++ b/unix/gtk-common.c @@ -1,5 +1,5 @@ /* - * gtkcomm.c: machinery in the GTK front end which is common to all + * gtk-common.c: machinery in the GTK front end which is common to all * programs that run a session in a terminal window, and also common * across all _sessions_ rather than specific to one session. (Timers, * uxsel etc.) diff --git a/unix/keygen-noise.c b/unix/keygen-noise.c index da5e8f05..ab7de68d 100644 --- a/unix/keygen-noise.c +++ b/unix/keygen-noise.c @@ -1,5 +1,5 @@ /* - * uxgen.c: Unix implementation of get_heavy_noise() from cmdgen.c. + * keygen-noise.c: Unix implementation of get_heavy_noise() from cmdgen.c. */ #include diff --git a/unix/local-proxy.c b/unix/local-proxy.c index 0f52931f..92f2a501 100644 --- a/unix/local-proxy.c +++ b/unix/local-proxy.c @@ -1,5 +1,5 @@ /* - * uxproxy.c: Unix implementation of platform_new_connection(), + * local-proxy.c: Unix implementation of platform_new_connection(), * supporting an OpenSSH-like proxy command. */ diff --git a/unix/main-gtk-application.c b/unix/main-gtk-application.c index b6c2807b..963c93fc 100644 --- a/unix/main-gtk-application.c +++ b/unix/main-gtk-application.c @@ -1,6 +1,7 @@ /* - * gtkapp.c: a top-level front end to GUI PuTTY and pterm, using - * GtkApplication. Suitable for OS X. Currently unfinished. + * main-gtk-application.c: a top-level front end to GUI PuTTY and + * pterm, using GtkApplication. Suitable for OS X. Currently + * unfinished. * * (You could run it on ordinary Linux GTK too, in principle, but I * don't think it would be particularly useful to do so, even once diff --git a/unix/main-gtk-simple.c b/unix/main-gtk-simple.c index 4ee43e7b..e52a18d0 100644 --- a/unix/main-gtk-simple.c +++ b/unix/main-gtk-simple.c @@ -1,7 +1,7 @@ /* - * gtkmain.c: the common main-program code between the straight-up - * Unix PuTTY and pterm, which they do not share with the - * multi-session gtkapp.c. + * main-gtk-simple.c: the common main-program code between the + * straight-up Unix PuTTY and pterm, which they do not share with the + * multi-session main-gtk-application.c. */ #define _GNU_SOURCE diff --git a/unix/no-gtk.c b/unix/no-gtk.c index c9028ebf..12565a1f 100644 --- a/unix/no-gtk.c +++ b/unix/no-gtk.c @@ -1,5 +1,5 @@ /* - * uxnogtk.c: link into non-GUI Unix programs so that they can tell + * no-gtk.c: link into non-GUI Unix programs so that they can tell * buildinfo about a lack of GTK. */ diff --git a/unix/pageant.c b/unix/pageant.c index d5c15d1c..5db797a8 100644 --- a/unix/pageant.c +++ b/unix/pageant.c @@ -90,7 +90,7 @@ static int make_pipe_to_askpass(const char *msg) cloexec(pipefds[1]); /* - * See comment in fork_and_exec_self() in gtkmain.c. + * See comment in fork_and_exec_self() in main-gtk-simple.c. */ execv("/proc/self/exe", (char **)args); execvp(progname, (char **)args); diff --git a/unix/platform.h b/unix/platform.h index 58cb4fbf..6dff7842 100644 --- a/unix/platform.h +++ b/unix/platform.h @@ -143,7 +143,7 @@ unsigned long getticks(void); #define NAMED_CLIPBOARDS #endif -/* The per-session frontend structure managed by gtkwin.c */ +/* The per-session frontend structure managed by window.c */ typedef struct GtkFrontend GtkFrontend; /* Callback when a dialog box finishes, and a no-op implementation of it */ @@ -154,7 +154,7 @@ void trivial_post_dialog_fn(void *vctx, int result); void initial_config_box(Conf *conf, post_dialog_fn_t after, void *afterctx); void new_session_window(Conf *conf, const char *geometry_string); -/* Defined in gtkmain.c */ +/* Defined in main-gtk-*.c */ void launch_duplicate_session(Conf *conf); void launch_new_session(void); void launch_saved_session(const char *str); @@ -166,10 +166,11 @@ GtkWidget *make_gtk_toplevel_window(GtkFrontend *frontend); const struct BackendVtable *select_backend(Conf *conf); -/* Defined in gtkcomm.c */ +/* Defined in gtk-common.c */ void gtkcomm_setup(void); -/* Used to pass application-menu operations from gtkapp.c to gtkwin.c */ +/* Used to pass application-menu operations from + * main-gtk-application.c to window.c */ enum MenuAction { MA_COPY, MA_PASTE, MA_COPY_ALL, MA_DUPLICATE_SESSION, MA_RESTART_SESSION, MA_CHANGE_SETTINGS, MA_CLEAR_SCROLLBACK, @@ -184,7 +185,7 @@ extern const char *const *const main_icon[]; extern const char *const *const cfg_icon[]; extern const int n_main_icon, n_cfg_icon; -/* Things gtkdlg.c needs from gtkwin.c */ +/* Things dialog.c needs from window.c */ #ifdef MAY_REFER_TO_GTK_IN_HEADERS enum DialogSlot { DIALOG_SLOT_RECONFIGURE, @@ -202,7 +203,7 @@ void set_window_icon(GtkWidget *window, const char *const *const *icon, extern GdkAtom compound_text_atom; #endif -/* Things gtkwin.c needs from gtkdlg.c */ +/* Things window.c needs from dialog.c */ #ifdef MAY_REFER_TO_GTK_IN_HEADERS GtkWidget *create_config_box(const char *title, Conf *conf, bool midsession, int protcfginfo, @@ -245,16 +246,16 @@ GtkWidget *create_message_box( post_dialog_fn_t after, void *afterctx); #endif -/* gtkwin.c needs this special function in xkeysym.c */ +/* window.c needs this special function in utils */ int keysym_to_unicode(int keysym); -/* Things uxstore.c needs from gtkwin.c */ +/* Things storage.c needs from window.c */ char *x_get_default(const char *key); -/* Things uxstore.c provides to gtkwin.c */ +/* Things storage.c provides to window.c */ void provide_xrm_string(const char *string, const char *progname); -/* Function that {gtkapp,gtkmain}.c needs from ux{pterm,putty}.c. Does +/* Function that main-gtk-*.c needs from {pterm,putty}.c. Does * early process setup that varies between applications (e.g. * pty_pre_init or sk_init), and is passed a boolean by the caller * indicating whether this is an OS X style multi-session monolithic @@ -283,12 +284,12 @@ extern const bool use_pty_argv; * OS X environment munging: this is the prefix we expect to find on * environment variable names that were changed by osxlaunch. * Extracted from the command line of the OS X pterm main binary, and - * used in uxpty.c to restore the original environment before + * used in pty.c to restore the original environment before * launching its subprocess. */ extern char *pty_osx_envrestore_prefix; -/* Things provided by uxcons.c */ +/* Things provided by console.c */ struct termios; void stderr_tty_init(void); /* call at startup if stderr might be a tty */ void premsg(struct termios *); @@ -308,12 +309,12 @@ int next_fd(int *state, int *rwx); uxsel_id *uxsel_input_add(int fd, int rwx); /* returns an id */ void uxsel_input_remove(uxsel_id *id); -/* uxcfg.c */ +/* config-unix.c */ struct controlbox; void unix_setup_config_box( struct controlbox *b, bool midsession, int protocol); -/* gtkcfg.c */ +/* config-gtk.c */ void gtk_setup_config_box( struct controlbox *b, bool midsession, void *window); @@ -335,7 +336,7 @@ void gtk_setup_config_box( void (*putty_signal(int sig, void (*func)(int)))(int); void block_signal(int sig, bool block_it); -/* uxmisc.c */ +/* utils */ void cloexec(int); void noncloexec(int); bool nonblock(int); @@ -351,7 +352,7 @@ bool init_ucs(struct unicode_data *ucsdata, char *line_codepage, bool utf8_override, int font_charset, int vtmode); /* - * Spare functions exported directly from uxnet.c. + * Spare functions exported directly from network.c. */ void *sk_getxdmdata(Socket *sock, int *lenp); int sk_net_get_fd(Socket *sock); @@ -368,17 +369,17 @@ Socket *new_unix_listener(SockAddr *listenaddr, Plug *plug); } while (0) /* - * Exports from uxser.c. + * Exports from serial.c. */ extern const struct BackendVtable serial_backend; /* - * uxpeer.c, wrapping getsockopt(SO_PEERCRED). + * peerinfo.c, wrapping getsockopt(SO_PEERCRED). */ bool so_peercred(int fd, int *pid, int *uid, int *gid); /* - * uxfdsock.c. + * fd-socket.c. */ Socket *make_fd_socket(int infd, int outfd, int inerrfd, SockAddr *addr, int port, Plug *plug); @@ -396,7 +397,7 @@ void setup_fd_socket(Socket *s, int infd, int outfd, int inerrfd); #endif /* - * uxpty.c. + * pty.c. */ void pty_pre_init(void); /* pty+utmp setup before dropping privilege */ /* Pass in the argv[] for an instance of the pty backend created by @@ -406,7 +407,7 @@ void pty_pre_init(void); /* pty+utmp setup before dropping privilege */ extern char **pty_argv; /* - * gtkask.c. + * askpass.c. */ char *gtk_askpass_main(const char *display, const char *wintitle, const char *prompt, bool *success); @@ -422,12 +423,12 @@ static inline bool sk_peer_trusted(Socket *sock) } /* - * uxsftpserver.c. + * sftpserver.c. */ extern const SftpServerVtable unix_live_sftpserver_vt; /* - * uxpoll.c. + * utils/pollwrap.c. */ typedef struct pollwrapper pollwrapper; pollwrapper *pollwrap_new(void); @@ -446,7 +447,7 @@ static inline bool pollwrap_check_fd_rwx(pollwrapper *pw, int fd, int rwx) } /* - * uxcliloop.c. + * cliloop.c. */ typedef bool (*cliloop_pw_setup_t)(void *ctx, pollwrapper *pw); typedef void (*cliloop_pw_check_t)(void *ctx, pollwrapper *pw); diff --git a/unix/plink.c b/unix/plink.c index ca7fcad8..8f53ceda 100644 --- a/unix/plink.c +++ b/unix/plink.c @@ -745,7 +745,7 @@ int main(int argc, char **argv) --argc; /* Explicitly pass "plink" in place of appname for * error reporting purposes. appname will have been - * set by be_foo.c to something more generic, probably + * set by be_list.c to something more generic, probably * "PuTTY". */ provide_xrm_string(*++argv, "plink"); } diff --git a/unix/printing.c b/unix/printing.c index 3de6d21b..416db396 100644 --- a/unix/printing.c +++ b/unix/printing.c @@ -46,8 +46,8 @@ void printer_finish_job(printer_job *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. + * these are useless stub functions, and config-unix.c will disable + * the drop-down list in the printer configurer. */ printer_enum *printer_start_enum(int *nprinters_ptr) { *nprinters_ptr = 0; diff --git a/unix/pterm.c b/unix/pterm.c index 649e7b83..87dd3e99 100644 --- a/unix/pterm.c +++ b/unix/pterm.c @@ -15,7 +15,7 @@ const bool use_pty_argv = true; const unsigned cmdline_tooltype = TOOLTYPE_NONNETWORK; -/* gtkwin.c will call this, and in pterm it's not needed */ +/* window.c will call this, and in pterm it's not needed */ void noise_ultralight(NoiseSourceId id, unsigned long data) { } const struct BackendVtable *select_backend(Conf *conf) diff --git a/unix/putty.c b/unix/putty.c index 7a808087..a96217e3 100644 --- a/unix/putty.c +++ b/unix/putty.c @@ -19,7 +19,7 @@ #include "gtkcompat.h" /* - * Stubs to avoid uxpty.c needing to be linked in. + * Stubs to avoid pty.c needing to be linked in. */ const bool use_pty_argv = false; char **pty_argv; /* never used */ diff --git a/unix/sftp.c b/unix/sftp.c index 92f3f8ed..17a83a89 100644 --- a/unix/sftp.c +++ b/unix/sftp.c @@ -1,5 +1,5 @@ /* - * uxsftp.c: the Unix-specific parts of PSFTP and PSCP. + * sftp.c: the Unix-specific parts of PSFTP and PSCP. */ #include diff --git a/unix/storage.c b/unix/storage.c index 9132eb70..c61fb526 100644 --- a/unix/storage.c +++ b/unix/storage.c @@ -1,5 +1,5 @@ /* - * uxstore.c: Unix-specific implementation of the interface defined + * storage.c: Unix-specific implementation of the interface defined * in storage.h. */ diff --git a/unix/unifont.h b/unix/unifont.h index e43a748d..c0f01121 100644 --- a/unix/unifont.h +++ b/unix/unifont.h @@ -1,5 +1,5 @@ /* - * Header file for gtkfont.c. Has to be separate from unix.h + * Header file for unifont.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). */ @@ -47,10 +47,10 @@ #endif /* - * Exports from gtkfont.c. + * Exports from unifont.c. */ typedef struct UnifontVtable UnifontVtable; /* contents internal to - * gtkfont.c */ + * unifont.c */ typedef struct unifont { const struct UnifontVtable *vt; /* diff --git a/unix/utils/pgp_fingerprints.c b/unix/utils/pgp_fingerprints.c index badedd71..9fb6bf42 100644 --- a/unix/utils/pgp_fingerprints.c +++ b/unix/utils/pgp_fingerprints.c @@ -1,7 +1,7 @@ /* * Display the fingerprints of the PGP Master Keys to the user. * - * (This is in its own file rather than in uxcons.c, because it's + * (This is in its own file rather than in console.c, because it's * appropriate even for Unix GUI apps.) */ diff --git a/unix/uxsel.c b/unix/uxsel.c index eb3abed3..18d512ac 100644 --- a/unix/uxsel.c +++ b/unix/uxsel.c @@ -2,10 +2,10 @@ * 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 + * descriptors. At one end it talks to network.c and pty.c and * anything else which might have one or more fds that need * select() or poll()-type things doing to them during an extended - * program run; at the other end it talks to pterm.c or uxplink.c or + * program run; at the other end it talks to window.c or plink.c or * anything else which might have its own means of actually doing * those select()-type things. */ diff --git a/unix/window.c b/unix/window.c index 5475fe9c..eeebfeb5 100644 --- a/unix/window.c +++ b/unix/window.c @@ -1,5 +1,5 @@ /* - * gtkwin.c: the main code that runs a PuTTY terminal emulator and + * window.c: the main code that runs a PuTTY terminal emulator and * backend in a GTK window. */ @@ -4443,7 +4443,7 @@ static void compute_geom_hints(GtkFrontend *inst, GdkGeometry *geom) #if GTK_CHECK_VERSION(3,0,0) /* - * And if we're running a gtkapp.c based program and + * And if we're running a main-gtk-application.c based program and * GtkApplicationWindow has given us a menu bar inside the window, * then we must take that into account as well. * diff --git a/unix/x11.c b/unix/x11.c index f30dcfbf..710ff849 100644 --- a/unix/x11.c +++ b/unix/x11.c @@ -1,5 +1,5 @@ /* - * ux_x11.c: fetch local auth data for X forwarding. + * x11.c: fetch local auth data for X forwarding. */ #include diff --git a/unix/x11misc.h b/unix/x11misc.h index 159d4226..5f5a2d26 100644 --- a/unix/x11misc.h +++ b/unix/x11misc.h @@ -7,14 +7,8 @@ #ifndef NOT_X_WINDOWS -/* - * x11misc.c. - */ +/* Defined in unix/utils */ void x11_ignore_error(Display *disp, unsigned char errcode); - -/* - * gtkmisc.c - */ Display *get_x11_display(void); #endif diff --git a/windows/config.c b/windows/config.c index a32944a6..9c107edd 100644 --- a/windows/config.c +++ b/windows/config.c @@ -1,5 +1,5 @@ /* - * wincfg.c - the Windows-specific parts of the PuTTY configuration + * config.c - the Windows-specific parts of the PuTTY configuration * box. */ diff --git a/windows/console.c b/windows/console.c index 8cb27c5a..81ab7d06 100644 --- a/windows/console.c +++ b/windows/console.c @@ -1,5 +1,5 @@ /* - * wincons.c - various interactive-prompt routines shared between + * console.c - various interactive-prompt routines shared between * the Windows console PuTTY tools */ diff --git a/windows/controls.c b/windows/controls.c index 59129eab..3c7896a4 100644 --- a/windows/controls.c +++ b/windows/controls.c @@ -1,5 +1,5 @@ /* - * winctrls.c: routines to self-manage the controls in a dialog + * controls.c: routines to self-manage the controls in a dialog * box. */ diff --git a/windows/dialog.c b/windows/dialog.c index 66f26128..31bf19e6 100644 --- a/windows/dialog.c +++ b/windows/dialog.c @@ -1,5 +1,5 @@ /* - * windlg.c - dialogs for PuTTY(tel), including the configuration dialog. + * dialog.c - dialogs for PuTTY(tel), including the configuration dialog. */ #include diff --git a/windows/handle-io.c b/windows/handle-io.c index e012c8ed..0f5b7e94 100644 --- a/windows/handle-io.c +++ b/windows/handle-io.c @@ -1,5 +1,5 @@ /* - * winhandl.c: Module to give Windows front ends the general + * handle-io.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. diff --git a/windows/handle-socket.c b/windows/handle-socket.c index 8c13d686..0b0e60a9 100644 --- a/windows/handle-socket.c +++ b/windows/handle-socket.c @@ -17,7 +17,7 @@ * thread as blocking system calls and so once one is in progress it * can't sensibly be interrupted. Hence, after the user tries to * freeze one of these sockets, it's unavoidable that we may receive - * one more load of data before we manage to get winhandl.c to stop + * one more load of data before we manage to get handle-io.c to stop * reading. */ typedef enum HandleSocketFreezeState { @@ -82,7 +82,7 @@ static size_t handle_gotdata( if (hs->frozen == FREEZING) { /* * If we've received data while this socket is supposed to - * be frozen (because the read winhandl.c started before + * be frozen (because the read handle-io.c started before * sk_set_frozen was called has now returned) then buffer * the data for when we unfreeze. */ @@ -248,7 +248,7 @@ static void sk_handle_set_frozen(Socket *s, bool is_frozen) case THAWING: /* * We were in the middle of emptying our bufchain, and got - * frozen again. In that case, winhandl.c is already + * frozen again. In that case, handle-io.c is already * throttled, so just return to FROZEN state. The toplevel * callback will notice and disable itself. */ diff --git a/windows/help.c b/windows/help.c index daea282a..bff2ee5c 100644 --- a/windows/help.c +++ b/windows/help.c @@ -1,5 +1,5 @@ /* - * winhelp.c: centralised functions to launch Windows HTML Help files. + * help.c: centralised functions to launch Windows HTML Help files. */ #include diff --git a/windows/jump-list.c b/windows/jump-list.c index 39de1e00..43c1e66d 100644 --- a/windows/jump-list.c +++ b/windows/jump-list.c @@ -1,5 +1,5 @@ /* - * winjump.c: support for Windows 7 jump lists. + * jump-list.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 diff --git a/windows/local-proxy.c b/windows/local-proxy.c index 3c4922f0..93baad6b 100644 --- a/windows/local-proxy.c +++ b/windows/local-proxy.c @@ -1,6 +1,6 @@ /* - * winproxy.c: Windows implementation of platform_new_connection(), - * supporting an OpenSSH-like proxy command via the winhandl.c + * local-proxy.c: Windows implementation of platform_new_connection(), + * supporting an OpenSSH-like proxy command via the handle-io.c * mechanism. */ diff --git a/windows/named-pipe-client.c b/windows/named-pipe-client.c index e6ec5312..2ab6a309 100644 --- a/windows/named-pipe-client.c +++ b/windows/named-pipe-client.c @@ -38,11 +38,11 @@ HANDLE connect_to_named_pipe(const char *pipename, char **err) } /* - * If we got ERROR_PIPE_BUSY, wait for the server to - * create a new pipe instance. (Since the server is - * expected to be winnps.c, which will do that immediately - * after a previous connection is accepted, that shouldn't - * take excessively long.) + * If we got ERROR_PIPE_BUSY, wait for the server to create a + * new pipe instance. (Since the server is expected to be + * named-pipe-server.c, which will do that immediately after a + * previous connection is accepted, that shouldn't take + * excessively long.) */ if (!WaitNamedPipe(pipename, NMPWAIT_USE_DEFAULT_WAIT)) { *err = dupprintf( diff --git a/windows/no-jump-list.c b/windows/no-jump-list.c index dd61dc69..7c76f668 100644 --- a/windows/no-jump-list.c +++ b/windows/no-jump-list.c @@ -1,6 +1,6 @@ /* - * winnojmp.c: stub jump list functions for Windows executables that - * don't update the jump list. + * no-jump-list.c: stub jump list functions for Windows executables + * that don't update the jump list. */ void add_session_to_jumplist(const char * const sessionname) {} diff --git a/windows/nohelp.c b/windows/nohelp.c index 62ddc65c..1b748776 100644 --- a/windows/nohelp.c +++ b/windows/nohelp.c @@ -1,6 +1,6 @@ /* * nohelp.c: implement the has_embedded_chm() function for - * applications that have no help file at all, so that misc.c's + * applications that have no help file at all, so that buildinfo.c's * buildinfo string knows not to talk meaninglessly about whether the * nonexistent help file is present. */ diff --git a/windows/pageant.c b/windows/pageant.c index 896b1fea..0e3868a0 100644 --- a/windows/pageant.c +++ b/windows/pageant.c @@ -187,7 +187,8 @@ static void end_passphrase_dialog(HWND hwnd, INT_PTR result) } else { /* * Destroy this passphrase dialog box before passing the - * results back to pageant.c, to avoid re-entrancy issues. + * results back to the main pageant.c, to avoid re-entrancy + * issues. * * If we successfully got a passphrase from the user, but it * was _wrong_, then pageant_passphrase_request_success will @@ -828,7 +829,7 @@ static void update_sessions(void) * communications. For backwards compatibility, and more particularly * for compatibility with derived works of PuTTY still using the old * Pageant client code, we accept it as an alternative to the one - * returned from get_user_sid() in winpgntc.c. + * returned from get_user_sid(). */ PSID get_default_sid(void) { diff --git a/windows/platform.h b/windows/platform.h index 8c0bfa33..6900a3c6 100644 --- a/windows/platform.h +++ b/windows/platform.h @@ -207,7 +207,7 @@ typedef void *Ssh_gss_name; extern HINSTANCE hinst; /* - * Help file stuff in winhelp.c. + * Help file stuff in help.c. */ void init_help(void); void shutdown_help(void); @@ -217,7 +217,7 @@ void quit_help(HWND hwnd); int has_embedded_chm(void); /* 1 = yes, 0 = no, -1 = N/A */ /* - * GUI seat methods in windlg.c, so that the vtable definition in + * GUI seat methods in dialog.c, so that the vtable definition in * window.c can refer to them. */ SeatPromptResult win_seat_confirm_ssh_host_key( @@ -232,7 +232,7 @@ SeatPromptResult win_seat_confirm_weak_cached_hostkey( void (*callback)(void *ctx, SeatPromptResult result), void *ctx); /* - * Windows-specific clipboard helper function shared with windlg.c, + * Windows-specific clipboard helper function shared with dialog.c, * which takes the data string in the system code page instead of * Unicode. */ @@ -242,7 +242,7 @@ void write_aclip(int clipboard, char *, int, bool); /* * On Windows, we send MA_2CLK as the only event marking the second - * press of a mouse button. Compare unix.h. + * press of a mouse button. Compare unix/platform.h. */ #define MULTICLICK_ONLY_EVENT 1 @@ -278,14 +278,14 @@ void write_aclip(int clipboard, char *, int, bool); "All Files (*.*)\0*\0\0\0") /* - * Exports from winnet.c. + * Exports from network.c. */ /* Report an event notification from WSA*Select */ void select_result(WPARAM, LPARAM); /* Enumerate all currently live OS-level SOCKETs */ SOCKET first_socket(int *); SOCKET next_socket(int *); -/* Ask winnet.c whether we currently want to try to write to a SOCKET */ +/* Ask network.c whether we currently want to try to write to a SOCKET */ bool socket_writable(SOCKET skt); /* Force a refresh of the SOCKET list by re-calling do_select for each one */ void socket_reselect_all(void); @@ -295,7 +295,7 @@ SockAddr *sk_namedpipe_addr(const char *pipename); const char *winsock_error_string(int error); /* - * winnet.c dynamically loads WinSock 2 or WinSock 1 depending on + * network.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. @@ -321,13 +321,13 @@ DECL_WINDOWS_FUNCTION(extern, int, select, #endif /* - * Implemented differently depending on the client of winnet.c, and - * called by winnet.c to turn on or off WSA*Select for a given socket. + * Implemented differently depending on the client of network.c, and + * called by network.c to turn on or off WSA*Select for a given socket. */ const char *do_select(SOCKET skt, bool enable); /* - * Exports from winselgui.c and winselcli.c, each of which provides an + * Exports from select-{gui,cli}.c, each of which provides an * implementation of do_select. */ void winselgui_set_hwnd(HWND hwnd); @@ -350,14 +350,14 @@ void setup_handle_socket(Socket *s, HANDLE send_H, HANDLE recv_H, Socket *new_named_pipe_client(const char *pipename, Plug *plug); /* winnpc */ Socket *new_named_pipe_listener(const char *pipename, Plug *plug); /* winnps */ -/* A lower-level function in winnpc.c, which does most of the work of - * new_named_pipe_client (including checking the ownership of what - * it's connected to), but returns a plain HANDLE instead of wrapping - * it into a Socket. */ +/* A lower-level function in named-pipe-client.c, which does most of + * the work of new_named_pipe_client (including checking the ownership + * of what it's connected to), but returns a plain HANDLE instead of + * wrapping it into a Socket. */ HANDLE connect_to_named_pipe(const char *pipename, char **err); /* - * Exports from winctrls.c. + * Exports from controls.c. */ struct ctlpos { @@ -372,7 +372,7 @@ struct ctlpos { void init_common_controls(void); /* also does some DLL-loading */ /* - * Exports from winutils.c. + * Exports from utils. */ typedef struct filereq_tag filereq; /* cwd for file requester */ bool request_file(filereq *state, OPENFILENAME *of, bool preserve, bool save); @@ -422,7 +422,7 @@ struct dlgparam { }; /* - * Exports from winctrls.c. + * Exports from controls.c. */ void ctlposinit(struct ctlpos *cp, HWND hwnd, int leftborder, int rightborder, int topborder); @@ -547,13 +547,13 @@ void dp_add_tree(struct dlgparam *dp, struct winctrls *tree); void dp_cleanup(struct dlgparam *dp); /* - * Exports from wincfg.c. + * Exports from config.c. */ void win_setup_config_box(struct controlbox *b, HWND *hwndp, bool has_help, bool midsession, int protocol); /* - * Exports from windlg.c. + * Exports from dialog.c. */ void defuse_showwindow(void); bool do_config(Conf *); @@ -566,7 +566,7 @@ void show_help(HWND hwnd); HWND event_log_window(void); /* - * Exports from winmisc.c. + * Exports from utils. */ extern DWORD osMajorVersion, osMinorVersion, osPlatformId; void init_winver(void); @@ -610,7 +610,7 @@ struct unicode_data; void init_ucs(Conf *, struct unicode_data *); /* - * Exports from winhandl.c. + * Exports from handle-io.c. */ #define HANDLE_FLAG_OVERLAPPED 1 #define HANDLE_FLAG_IGNOREEOF 2 @@ -661,12 +661,12 @@ char *agent_mutex_name(void); char *agent_named_pipe_name(void); /* - * Exports from winser.c. + * Exports from serial.c. */ extern const struct BackendVtable serial_backend; /* - * Exports from winjump.c. + * Exports from jump-list.c. */ #define JUMPLIST_SUPPORTED /* suppress #defines in putty.h */ void add_session_to_jumplist(const char * const sessionname); @@ -675,12 +675,12 @@ void clear_jumplist(void); bool set_explicit_app_user_model_id(void); /* - * Exports from winnoise.c. + * Exports from noise.c. */ bool win_read_random(void *buf, unsigned wanted); /* returns true on success */ /* - * Extra functions in winstore.c over and above the interface in + * Extra functions in storage.c over and above the interface in * storage.h. * * These functions manipulate the Registry section which mirrors the @@ -713,10 +713,10 @@ char *get_jumplist_registry_entries(void); #define CLIPUI_DEFAULT_MOUSE CLIPUI_EXPLICIT #define CLIPUI_DEFAULT_INS CLIPUI_EXPLICIT -/* In winmisc.c */ +/* In utils */ char *registry_get_string(HKEY root, const char *path, const char *leaf); -/* In wincliloop.c */ +/* In cliloop.c */ typedef bool (*cliloop_pre_t)(void *vctx, const HANDLE **extra_handles, size_t *n_extra_handles); typedef bool (*cliloop_post_t)(void *vctx, size_t extra_handle_index); diff --git a/windows/security-api.h b/windows/security-api.h index 95e9353d..175486ef 100644 --- a/windows/security-api.h +++ b/windows/security-api.h @@ -1,6 +1,6 @@ /* * security-api.h: some miscellaneous security-related helper functions, - * defined in winsecur.c, that use the advapi32 library. Also + * defined in utils/security.c, that use the advapi32 library. Also * centralises the machinery for dynamically loading that library. */ diff --git a/windows/select-cli.c b/windows/select-cli.c index f19a0bbe..9bf8411e 100644 --- a/windows/select-cli.c +++ b/windows/select-cli.c @@ -1,5 +1,5 @@ /* - * Implementation of do_select() for winnet.c to use, suitable for use + * Implementation of do_select() for network.c to use, suitable for use * when there's no GUI window to have network activity reported to. * * It uses WSAEventSelect, where available, to convert network diff --git a/windows/select-gui.c b/windows/select-gui.c index 48a15212..de3d9091 100644 --- a/windows/select-gui.c +++ b/windows/select-gui.c @@ -1,5 +1,5 @@ /* - * Implementation of do_select() for winnet.c to use, that uses + * Implementation of do_select() for network.c to use, that uses * WSAAsyncSelect to convert network activity into window messages, * for integration into a GUI event loop. */ diff --git a/windows/sftp.c b/windows/sftp.c index bc16f5d1..ad2df4fb 100644 --- a/windows/sftp.c +++ b/windows/sftp.c @@ -1,5 +1,5 @@ /* - * winsftp.c: the Windows-specific parts of PSFTP and PSCP. + * sftp.c: the Windows-specific parts of PSFTP and PSCP. */ #include /* need to put this first, for winelib builds */ diff --git a/windows/storage.c b/windows/storage.c index 6f05cdc7..077f0890 100644 --- a/windows/storage.c +++ b/windows/storage.c @@ -1,5 +1,5 @@ /* - * winstore.c: Windows-specific implementation of the interface + * storage.c: Windows-specific implementation of the interface * defined in storage.h. */ diff --git a/windows/win-gui-seat.h b/windows/win-gui-seat.h index c6b5fa96..19c5cbea 100644 --- a/windows/win-gui-seat.h +++ b/windows/win-gui-seat.h @@ -1,6 +1,6 @@ /* * Small implementation of Seat and LogPolicy shared between window.c - * and windlg.c. + * and dialog.c. */ typedef struct WinGuiSeat WinGuiSeat; @@ -11,4 +11,4 @@ struct WinGuiSeat { LogPolicy logpolicy; }; -extern const LogPolicyVtable win_gui_logpolicy_vt; /* in windlg.c */ +extern const LogPolicyVtable win_gui_logpolicy_vt; /* in dialog.c */ diff --git a/windows/x11.c b/windows/x11.c index 800d8509..98bbb627 100644 --- a/windows/x11.c +++ b/windows/x11.c @@ -1,5 +1,5 @@ /* - * winx11.c: fetch local auth data for X forwarding. + * x11.c: fetch local auth data for X forwarding. */ #include -- cgit v1.2.3 From b7ed5056e5d10090f51551da9929024b53c8f5e2 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 22 Jan 2022 15:45:00 +0000 Subject: net_service_lookup: add missing 'const'. Spotted in passing while doing the filename-correction trawl. --- network.h | 2 +- unix/network.c | 2 +- windows/network.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/network.h b/network.h index a855f7de..4c0b0332 100644 --- a/network.h +++ b/network.h @@ -346,7 +346,7 @@ void sk_free_peer_info(SocketPeerInfo *pi); * can just return 0 - this function is not required to handle * numeric port specifications. */ -int net_service_lookup(char *service); +int net_service_lookup(const char *service); /* * Look up the local hostname; return value needs freeing. diff --git a/unix/network.c b/unix/network.c index fe0bc70b..0f15796b 100644 --- a/unix/network.c +++ b/unix/network.c @@ -1599,7 +1599,7 @@ static void uxsel_tell(NetSocket *s) uxsel_set(s->s, rwx, net_select_result); } -int net_service_lookup(char *service) +int net_service_lookup(const char *service) { struct servent *se; se = getservbyname(service, NULL); diff --git a/windows/network.c b/windows/network.c index a1a74b4c..58efb766 100644 --- a/windows/network.c +++ b/windows/network.c @@ -1809,7 +1809,7 @@ bool socket_writable(SOCKET skt) return false; } -int net_service_lookup(char *service) +int net_service_lookup(const char *service) { struct servent *se; se = p_getservbyname(service, NULL); -- cgit v1.2.3 From 575318717b383c399dda3221d715ecaa699a2a64 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 22 Jan 2022 15:53:24 +0000 Subject: Remove the prohibition on // comments. Those were forbidden so that we could still compile on pre-C99 C compilers. But now we expect C99 everywhere (or at least most of it, excluding the parts that MSVC never implemented and C11 made optional), so // comments aren't forbidden any more. Most of the comments in this code base are still old-style, but that's now a matter of stylistic consistency rather than hard requirement. --- doc/udp.but | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/doc/udp.but b/doc/udp.but index b98633f8..12b3392b 100644 --- a/doc/udp.but +++ b/doc/udp.but @@ -171,9 +171,7 @@ 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.) +classes will not be accepted. 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 -- cgit v1.2.3 From f11b20156ba10c7a7cfe55d1fa8e1cd41121c95b Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 22 Jan 2022 16:45:29 +0000 Subject: Windows PuTTYgen docs: remove redundant text. When I was writing the documentation for the new command-line options, I wondered why there was an existing section for the corresponding GUI setting for each option I'd added except strong primes. Now I've found it: strong primes are discussed in the same section as prime- generation methods. So I can replace the second explanation with a cross-reference. --- doc/pubkey.but | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/doc/pubkey.but b/doc/pubkey.but index ed755760..57282455 100644 --- a/doc/pubkey.but +++ b/doc/pubkey.but @@ -507,12 +507,7 @@ The options supported on the command line are: \dt \cw{\-\-strong-rsa} \dd When generating an RSA key, make sure the prime factors of the key -modulus are \q{strong primes}. A strong prime is a prime number chosen -to have a particular structure that makes certain factoring algorithms -more difficult to apply, so some security standards recommend their -use. However, the most modern factoring algorithms are unaffected, so -this option is probably not worth turning on \e{unless} you have a -local standard that recommends it. +modulus are \q{strong primes}. See \k{puttygen-primes}. \dt \cw{\-\-ppk-param} \e{key}\cw{=}\e{value}\cw{,}... -- cgit v1.2.3 From 9d687e417727a7583ba06e83180f2d3459e14129 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Wed, 26 Jan 2022 19:59:19 +0000 Subject: Pageant docs: improve the new OpenSSH section. I tried setting this up on a different Windows machine today and had some slightly different experiences. I found that in at least some situations the command 'Include c:\...\pageant.conf' will cause OpenSSH to emit a log message saying it's trying to open the file '~/.ssh/c:\...\pageant.conf', which it then doesn't find. But 'Include pageant.conf' works, because that's interpreted relative to the .ssh directory that it's already found. (I don't know why this happened on one Windows machine and not another, since I only have a sample size of two. But an obvious guess would be a bug fix in the Windows OpenSSH port, present in the version on one of the machines I tried, and not in the other. Certainly that failure mode looks to me like 'apply Unix instead of Windows rules to decide what's an absolute pathname'.) Also, clarified that all of this only works with the version of OpenSSH that's available as a Windows optional feature, and not with the MSYS-based one that ships with Windows git. --- doc/pageant.but | 35 ++++++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/doc/pageant.but b/doc/pageant.but index 392c1da1..97d41087 100644 --- a/doc/pageant.but +++ b/doc/pageant.but @@ -172,6 +172,11 @@ by the command, like this: \S{pageant-cmdline-openssh} Integrating with Windows OpenSSH +Windows's own port of OpenSSH uses the same mechanism as Pageant to +talk to its SSH agent (Windows named pipes). This means that Windows +OpenSSH can talk directly to Pageant, if it knows where to find +Pageant's named pipe. + When Pageant starts up, it can optionally write out a file containing an OpenSSH configuration directive that tells the Windows \c{ssh.exe} where to find Pageant. If you include this file from your Windows SSH @@ -183,14 +188,34 @@ The option is \c{--openssh-config}, and you follow it with a filename. To refer to this file from your main OpenSSH configuration, you can use the \cq{Include} directive. For example, you might run Pageant -like this: +like this (with your own username substituted, of course): \c pageant --openssh-config C:\Users\Simon\.ssh\pageant.conf -and then add a directive like this to your main \cq{.ssh\\config} -file: - -\c Include C:\Users\Simon\.ssh\pageant.conf +and then add a directive like this to your main \cq{.ssh\\config} file +(assuming that lives in the same directory that you just put +\cw{pageant.conf}): + +\c Include pageant.conf + +\s{Note}: this technique only works with \e{Windows's} port of +OpenSSH, which lives at \cw{C:\\Windows\\System32\\OpenSSH\\ssh.exe} +if you have it installed. (If not, it can be installed as a Windows +optional feature, e.g., via Settings > Apps & features > Optional +features > Add a feature > OpenSSH Client.) + +There are other versions of OpenSSH for Windows, notably the one that +comes with Windows \cw{git}. Those will likely not work with the same +configuration, because they tend to depend on Unix emulation layers +like MinGW or MSys, so they won't speak Windows native pathname syntax +or understand named pipes. The above instructions will only work with +Windows's own version of OpenSSH. + +So, if you want to use Windows \cw{git} with an SSH key held in +Pageant, you'll have to set the environment variable \cw{GIT_SSH}, to +point at a different program. You could point it at +\cw{c:\\Windows\\System32\\OpenSSH\\ssh.exe} once you've done this +setup \dash but it's just as easy to point it at Plink! \S{pageant-cmdline-keylist} Starting with the key list visible -- cgit v1.2.3 From d6a83fe336230d60dcb166b9f9ffabe451404c5a Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Wed, 26 Jan 2022 20:02:15 +0000 Subject: Unix Pageant: ability to build without GTK. Unix Pageant is in a tricky position as a hybrid CLI/GUI application. It has uses even in a purely CLI environment, but it won't build without libgtk-3-dev and friends. The solution, of course - enabled by the migration to cmake - is to allow it to build without GTK, leaving out just the GTK askpass functionality. That way you can still use it in any of its CLI modes, either as a non-graphical SSH agent or as a client for an agent elsewhere. (You can still even use it in X lifetime mode, because its connection to the X server is done using PuTTY's built-in X authentication and connection setup code. It's only putting up the password prompt window that you lose in this configuration - so you're still fine as long as you don't try to add any encrypted keys.) --- unix/CMakeLists.txt | 37 +++++++++++++++++++++++-------------- unix/noaskpass.c | 19 +++++++++++++++++++ 2 files changed, 42 insertions(+), 14 deletions(-) create mode 100644 unix/noaskpass.c diff --git a/unix/CMakeLists.txt b/unix/CMakeLists.txt index 342ca14f..6bb275d9 100644 --- a/unix/CMakeLists.txt +++ b/unix/CMakeLists.txt @@ -130,20 +130,6 @@ if(GTK_FOUND) window.c unifont.c dialog.c config-gtk.c gtk-common.c config-unix.c unicode.c printing.c) add_dependencies(guiterminal generated_licence_h) # dialog.c uses licence.h - add_executable(pageant - pageant.c - ${CMAKE_SOURCE_DIR}/stubs/nogss.c - askpass.c - x11.c - noise.c - ${CMAKE_SOURCE_DIR}/ssh/x11fwd.c - ${CMAKE_SOURCE_DIR}/proxy/nosshproxy.c) - be_list(pageant Pageant) - target_link_libraries(pageant - eventloop console agent settings network crypto utils - ${GTK_LIBRARIES}) - installed_program(pageant) - add_executable(pterm pterm.c main-gtk-simple.c @@ -206,3 +192,26 @@ if(GTK_FOUND) guiterminal eventloop otherbackends settings network charset utils ${GTK_LIBRARIES} ${X11_LIBRARIES}) endif() + +# Pageant is built whether we have GTK or not; in its absence we +# degrade to a version that doesn't provide the GTK askpass. +if(GTK_FOUND) + set(pageant_conditional_sources askpass.c) + set(pageant_libs ${GTK_LIBRARIES}) +else() + set(pageant_conditional_sources noaskpass.c no-gtk.c) + set(pageant_libs) +endif() +add_executable(pageant + pageant.c + ${CMAKE_SOURCE_DIR}/stubs/nogss.c + x11.c + noise.c + ${CMAKE_SOURCE_DIR}/ssh/x11fwd.c + ${CMAKE_SOURCE_DIR}/proxy/nosshproxy.c + ${pageant_conditional_sources}) +be_list(pageant Pageant) +target_link_libraries(pageant + eventloop console agent settings network crypto utils + ${pageant_libs}) +installed_program(pageant) diff --git a/unix/noaskpass.c b/unix/noaskpass.c new file mode 100644 index 00000000..3d77b51c --- /dev/null +++ b/unix/noaskpass.c @@ -0,0 +1,19 @@ +/* + * GTK implementation of a GUI password/passphrase prompt. + */ + +#include "putty.h" + +void random_add_noise(NoiseSourceId source, const void *noise, int length) +{ + /* We have no keypress_prng here, so no need to implement this */ +} + +const bool buildinfo_gtk_relevant = false; + +char *gtk_askpass_main(const char *display, const char *wintitle, + const char *prompt, bool *success) +{ + *success = false; + return dupstr("this Pageant was built without GTK"); +} -- cgit v1.2.3 From ca62d67699e1c87611a612a107c9a9936acd48bb Mon Sep 17 00:00:00 2001 From: Jacob Nevins Date: Thu, 27 Jan 2022 15:04:37 +0000 Subject: Update usage messages embedded in docs. For changes in 44ee7b9e76. --- doc/plink.but | 2 +- doc/pscp.but | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/plink.but b/doc/plink.but index 30dcead1..b132b6eb 100644 --- a/doc/plink.but +++ b/doc/plink.but @@ -61,7 +61,7 @@ use Plink: \c -sercfg configuration-string (e.g. 19200,8,n,1,X) \c Specify the serial configuration (serial only) \c The following options only apply to SSH connections: -\c -pw passw login with specified password +\c -pwfile file login with password read from specified file \c -D [listen-IP:]listen-port \c Dynamic SOCKS-based port forwarding \c -L [listen-IP:]listen-port:host:port diff --git a/doc/pscp.but b/doc/pscp.but index e816f3e5..abf8f597 100644 --- a/doc/pscp.but +++ b/doc/pscp.but @@ -53,7 +53,7 @@ use PSCP: \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 -pwfile file login with password read from specified file \c -1 -2 force use of particular SSH protocol version \c -ssh -ssh-connection \c force use of particular SSH protocol variant -- cgit v1.2.3 From 1f6fa876e3be4eb9753319bee5d17624f8400452 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 29 Jan 2022 17:58:33 +0000 Subject: do_bidi: remove a pointless assert. When the textlen parameter became a size_t, it became unsigned, so it stopped being useful to assert() its non-negativity. Spotted by Coverity. Harmless, but ordinary compilers have been known to emit annoying warnings about that kind of thing too, so it's worth fixing just to avoid noise. --- terminal/bidi.c | 1 - 1 file changed, 1 deletion(-) diff --git a/terminal/bidi.c b/terminal/bidi.c index c8085ca4..a56570fd 100644 --- a/terminal/bidi.c +++ b/terminal/bidi.c @@ -3602,7 +3602,6 @@ void do_bidi(BidiContext *ctx, bidi_char *text, size_t textlen) #ifdef REMOVE_FORMATTING_CHARACTERS abort(); /* can't use the standard algorithm in a live terminal */ #else - assert(textlen >= 0); do_bidi_new(ctx, text, textlen); #endif } -- cgit v1.2.3 From 7582ce3cd668da1d0546271c095e9d97790fc37a Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 29 Jan 2022 18:00:13 +0000 Subject: proxy_socks5_free: fix inadequate smemclr. Thanks to Coverity for pointing out that I'd only cleared sizeof(pointer) amount of the struct, not sizeof(the whole thing). --- proxy/socks5.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxy/socks5.c b/proxy/socks5.c index 66797440..87a0bbc8 100644 --- a/proxy/socks5.c +++ b/proxy/socks5.c @@ -70,7 +70,7 @@ static void proxy_socks5_free(ProxyNegotiator *pn) strbuf_free(s->password); if (s->prompts) free_prompts(s->prompts); - smemclr(s, sizeof(s)); + smemclr(s, sizeof(*s)); sfree(s); } -- cgit v1.2.3 From af6a19e962c13cb61851b53f2d3c34b0b2a631ca Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 29 Jan 2022 18:03:33 +0000 Subject: sshproxy.c: add missing NULL check. If you try to use a saved session for SSH proxying which specifies a protocol that is not SSH or bare-SSH-connection, you get a clean error return from the proxy setup code - *provided* it's at least a protocol known to this particular build of PuTTY. If it's one so outlandish that backend_vt_from_proto returns NULL, there'd have been a crash. I don't think any such protocol currently exists, but if in the next version of PuTTY some additional protocol becomes supported, it will trip this error in the current version. Spotted by Coverity. --- proxy/sshproxy.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxy/sshproxy.c b/proxy/sshproxy.c index ee2bee6e..4f240d50 100644 --- a/proxy/sshproxy.c +++ b/proxy/sshproxy.c @@ -592,7 +592,7 @@ Socket *sshproxy_new_connection(SockAddr *addr, const char *hostname, * our check is for whether the backend sets the flag promising * that it does. */ - if (!(backvt->flags & BACKEND_SUPPORTS_NC_HOST)) { + if (!backvt || !(backvt->flags & BACKEND_SUPPORTS_NC_HOST)) { sp->errmsg = dupprintf("saved session '%s' is not an SSH session", proxy_hostname); return &sp->sock; -- cgit v1.2.3 From 6344e40e3f7524aa3479953b4e58daae349879de Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 29 Jan 2022 18:05:00 +0000 Subject: cmdline.c: free cmdline_password whenever it's reset. If you provided two -pw or -pwfile arguments on the same command line, the first password could be left in memory uncleared. Spotted by Coverity. --- cmdline.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/cmdline.c b/cmdline.c index c39c8ad2..9f37f2a0 100644 --- a/cmdline.c +++ b/cmdline.c @@ -585,6 +585,11 @@ int cmdline_process_param(const char *p, char *value, cmdline_error("the -pw option can only be used with the " "SSH protocol"); else { + if (cmdline_password) { + smemclr(cmdline_password, strlen(cmdline_password)); + sfree(cmdline_password); + } + 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 @@ -608,6 +613,11 @@ int cmdline_process_param(const char *p, char *value, if (!fp) { cmdline_error("unable to open password file '%s'", value); } else { + if (cmdline_password) { + smemclr(cmdline_password, strlen(cmdline_password)); + sfree(cmdline_password); + } + cmdline_password = chomp(fgetline(fp)); if (!cmdline_password) { cmdline_error("unable to read a password from file '%s'", -- cgit v1.2.3 From 6d77541080f463b1c4743e834e41f31169fdf60a Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 29 Jan 2022 18:11:06 +0000 Subject: bidi_test: minor memory fixes. Spotted by Coverity: if you _just_ gave a filename to bidi_test, without any previous argument that set testfn to something other than NULL, the program would crash rather than giving an error message. (It's only a test program, but test programs you only run once in a blue moon are the ones that _most_ need to explain their command-line syntax to you carefully, because you've forgotten it since last time you used them!) Also, conditionalised a memcpy on the size not being 0, because it's illegal to pass a null pointer to memcpy _even_ if size==0. (That would only happen with a test case containing a zero-length string, but whatever.) --- terminal/bidi_test.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/terminal/bidi_test.c b/terminal/bidi_test.c index 0b63029f..1acd1d68 100644 --- a/terminal/bidi_test.c +++ b/terminal/bidi_test.c @@ -44,7 +44,8 @@ static void run_test(const char *filename, unsigned lineno, { size_t bcs_orig_len = bcs_len; bidi_char *bcs_orig = snewn(bcs_orig_len, bidi_char); - memcpy(bcs_orig, bcs, bcs_orig_len * sizeof(bidi_char)); + if (bcs_orig_len) + memcpy(bcs_orig, bcs, bcs_orig_len * sizeof(bidi_char)); bcs_len = do_bidi_test(ctx, bcs, bcs_len, override); @@ -335,6 +336,12 @@ int main(int argc, char **argv) } else { const char *filename = arg; + if (!testfn) { + fprintf(stderr, "no mode argument provided before filename " + "'%s'\n", filename); + return 1; + } + if (!strcmp(filename, "-")) { testfn("", stdin); } else { -- cgit v1.2.3 From d78d14f917d11deb9418fbd96a1ee05c1315c234 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 29 Jan 2022 18:19:58 +0000 Subject: HTTP proxy: fix nonsense HTTP version check. Substitution of && for || would have caused us to accept HTTP/1.0 when we meant to reject it. Thanks Coverity! --- proxy/http.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxy/http.c b/proxy/http.c index f788e52c..081fefb6 100644 --- a/proxy/http.c +++ b/proxy/http.c @@ -479,7 +479,7 @@ static void proxy_http_process_queue(ProxyNegotiator *pn) crStopV; } - if (maj_ver < 1 && (maj_ver == 1 && min_ver < 1)) { + if (maj_ver < 1 || (maj_ver == 1 && min_ver < 1)) { /* Before HTTP/1.1, connections close by default */ s->connection_close = true; } -- cgit v1.2.3 From b7a9cdd6ee059a27821ae2e193c5881ec4d215da Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 29 Jan 2022 18:22:31 +0000 Subject: term_get_userpass_input: missing NULL check. If term_get_userpass_input is called with term->ldisc not yet set up, then we had a special-case handler that returns an error message - but it does it via the same subroutine that returns normal results, which also turns off the prompt callback in term->ldisc! Need an extra NULL check in that subroutine. Thanks Coverity. --- terminal/terminal.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/terminal/terminal.c b/terminal/terminal.c index b5d01813..3398cd59 100644 --- a/terminal/terminal.c +++ b/terminal/terminal.c @@ -7677,7 +7677,8 @@ static inline SeatPromptResult signal_prompts_t(Terminal *term, prompts_t *p, { assert(p->callback && "Asynchronous userpass input requires a callback"); queue_toplevel_callback(p->callback, p->callback_ctx); - ldisc_enable_prompt_callback(term->ldisc, NULL); + if (term->ldisc) + ldisc_enable_prompt_callback(term->ldisc, NULL); p->spr = spr; return spr; } -- cgit v1.2.3 From 397f3bd2b3b30f3226e0c3c126559667f84d060c Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 29 Jan 2022 18:34:53 +0000 Subject: Add more _MSC_VER translations. Visual Studio 2022 is out, and 2019 has added a couple more version numbers while I wasn't looking. Also, the main web page that lists the version number mappings now documents the wrinkle where you sometimes have to disambiguate via _MSC_FULL_VER (and indeed has added another such case for 16.11), so I no longer have to link to some unofficial blog post in the comment explaining that. (*Also*, if _MSC_FULL_VER is worth checking, then it's worth putting in the build info!) --- utils/buildinfo.c | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/utils/buildinfo.c b/utils/buildinfo.c index 3e42dec9..c72765d5 100644 --- a/utils/buildinfo.c +++ b/utils/buildinfo.c @@ -39,17 +39,18 @@ char *buildinfo(const char *newline) * that every real clause can start with #elif and there's no * anomalous first clause. That way the patch looks nicer when you * add extra ones. + * + * Mostly you can tell the version just from _MSC_VER, but in some + * cases, two different compiler versions have the same _MSC_VER + * value, and have to be distinguished by _MSC_FULL_VER. */ +#elif _MSC_VER == 1930 + put_fmt(buf, " 2022 (17.0)"); +#elif _MSC_VER == 1929 && _MSC_FULL_VER >= 192930100 + put_fmt(buf, " 2019 (16.11)"); +#elif _MSC_VER == 1929 + put_fmt(buf, " 2019 (16.10)"); #elif _MSC_VER == 1928 && _MSC_FULL_VER >= 192829500 - /* - * 16.9 and 16.8 have the same _MSC_VER value, and have to be - * distinguished by _MSC_FULL_VER. As of 2021-03-04 that is not - * mentioned on the above page, but see e.g. - * https://developercommunity.visualstudio.com/t/the-169-cc-compiler-still-uses-the-same-version-nu/1335194#T-N1337120 - * which says that 16.9 builds will have versions starting at - * 19.28.29500.* and going up. Hence, 19 28 29500 is what we - * compare _MSC_FULL_VER against above. - */ put_fmt(buf, " 2019 (16.9)"); #elif _MSC_VER == 1928 put_fmt(buf, " 2019 (16.8)"); @@ -105,6 +106,9 @@ char *buildinfo(const char *newline) put_fmt(buf, ", unrecognised version"); #endif put_fmt(buf, ", _MSC_VER=%d", (int)_MSC_VER); +#ifdef _MSC_FULL_VER + put_fmt(buf, ", _MSC_FULL_VER=%d", (int)_MSC_FULL_VER); +#endif #endif #ifdef BUILDINFO_GTK -- cgit v1.2.3 From 1e98710174920fcb6af277b0fb33efa362f23844 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 3 Feb 2022 17:48:21 +0000 Subject: GTK: fix font-size change when window maximised. If you maximise the terminal window and then press Ctrl-> or Ctrl-< to change the font size, then the maximised window can't change size, so what _should_ happen instead is that the terminal adjusts the number of character cells to whatever the new font size will now permit in the same size of window as before. But in fact, the terminal size wasn't changing at all, because the call to gtkwin_request_resize (called from change_font_size) detected the maximised window and went straight to gtkwin_deny_term_resize, which immediately called term_size() to tell the terminal it still had the same size as before. This commit switches gtkwin_deny_term_resize so that instead it calls drawing_area_setup_simple(), which re-runs drawing_area_setup with the same size the drawing area already had. This should work out the same in the case where we're _not_ changing the font size, but now also does the right thing when we are. --- unix/window.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/unix/window.c b/unix/window.c index eeebfeb5..9d46e96d 100644 --- a/unix/window.c +++ b/unix/window.c @@ -2477,9 +2477,7 @@ static void compute_whole_window_size(GtkFrontend *inst, static void gtkwin_deny_term_resize(void *vctx) { GtkFrontend *inst = (GtkFrontend *)vctx; - if (inst->term) - term_size(inst->term, inst->term->rows, inst->term->cols, - inst->term->savelines); + drawing_area_setup_simple(inst); } static void gtkwin_request_resize(TermWin *tw, int w, int h) -- cgit v1.2.3 From 9427f9699d1072f4598753e77681b3a8b59a48a7 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 3 Feb 2022 17:56:38 +0000 Subject: GTK: fix junk in window margin with fixed-size windows. When the window can't be resized for any reason, there will be extra space inside the drawing area that's not part of our standard width*font_width+2*window_border. We should include that in the backing surface and make sure we erase it to the background colour, otherwise it can end up containing unwanted visual junk. An example is the same case described in the previous commit: maximise the window and then start playing about with the font size. If you do this while running a full-screen application that displays text in the bottom line, it's easy to see that part of the previous display is left over and not cleared when the new font size leaves more space at the bottom than the old one. --- unix/window.c | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/unix/window.c b/unix/window.c index 9d46e96d..a8e73789 100644 --- a/unix/window.c +++ b/unix/window.c @@ -736,10 +736,8 @@ static void drawing_area_setup(GtkFrontend *inst, int width, int height) new_scale = 1; #endif - int new_backing_w = w * inst->font_width + 2*inst->window_border; - int new_backing_h = h * inst->font_height + 2*inst->window_border; - new_backing_w *= new_scale; - new_backing_h *= new_scale; + int new_backing_w = width * new_scale; + int new_backing_h = height * new_scale; if (inst->backing_w != new_backing_w || inst->backing_h != new_backing_h) inst->drawing_area_setup_needed = true; @@ -3754,16 +3752,12 @@ static void draw_stretch_after(GtkFrontend *inst, int x, int y, static void draw_backing_rect(GtkFrontend *inst) { - int w, h; - if (!win_setup_draw_ctx(&inst->termwin)) return; - w = inst->width * inst->font_width + 2*inst->window_border; - h = inst->height * inst->font_height + 2*inst->window_border; draw_set_colour(inst, 258, false); - draw_rectangle(inst, true, 0, 0, w, h); - draw_update(inst, 0, 0, w, h); + draw_rectangle(inst, true, 0, 0, inst->backing_w, inst->backing_h); + draw_update(inst, 0, 0, inst->backing_w, inst->backing_h); win_free_draw_ctx(&inst->termwin); } -- cgit v1.2.3 From dd3d0e931fd4433789326915b64d4c1bbf4cce58 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 8 Jan 2022 15:28:40 +0000 Subject: windows/network.c: reformat some mis-indented code. Not quite sure how this all ended up a whole indent level off where it should have been, but before I start modifying it, let's fix that. --- windows/network.c | 126 +++++++++++++++++++++++++++--------------------------- 1 file changed, 63 insertions(+), 63 deletions(-) diff --git a/windows/network.c b/windows/network.c index 58efb766..06d7916f 100644 --- a/windows/network.c +++ b/windows/network.c @@ -1196,78 +1196,78 @@ Socket *sk_newlistener(const char *srcaddr, int port, Plug *plug, } #ifndef NO_IPV6 - if (address_family == AF_INET6) { - memset(&a6, 0, sizeof(a6)); - a6.sin6_family = AF_INET6; - if (local_host_only) - a6.sin6_addr = in6addr_loopback; - else - a6.sin6_addr = in6addr_any; - if (srcaddr != NULL && p_getaddrinfo) { - struct addrinfo hints; - struct addrinfo *ai; - int err; - - memset(&hints, 0, sizeof(hints)); - hints.ai_family = AF_INET6; - hints.ai_flags = 0; - { - /* strip [] on IPv6 address literals */ - char *trimmed_addr = host_strduptrim(srcaddr); - err = p_getaddrinfo(trimmed_addr, NULL, &hints, &ai); - sfree(trimmed_addr); - } - if (err == 0 && ai->ai_family == AF_INET6) { - a6.sin6_addr = - ((struct sockaddr_in6 *)ai->ai_addr)->sin6_addr; - } - } - a6.sin6_port = p_htons(port); - } else -#endif - { - bool got_addr = false; - a.sin_family = AF_INET; + if (address_family == AF_INET6) { + memset(&a6, 0, sizeof(a6)); + a6.sin6_family = AF_INET6; + if (local_host_only) + a6.sin6_addr = in6addr_loopback; + else + a6.sin6_addr = in6addr_any; + if (srcaddr != NULL && p_getaddrinfo) { + struct addrinfo hints; + struct addrinfo *ai; + int err; - /* - * 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 = true; - } + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET6; + hints.ai_flags = 0; + { + /* strip [] on IPv6 address literals */ + char *trimmed_addr = host_strduptrim(srcaddr); + err = p_getaddrinfo(trimmed_addr, NULL, &hints, &ai); + sfree(trimmed_addr); + } + if (err == 0 && ai->ai_family == AF_INET6) { + a6.sin6_addr = + ((struct sockaddr_in6 *)ai->ai_addr)->sin6_addr; } + } + a6.sin6_port = p_htons(port); + } else +#endif + { + bool got_addr = false; + a.sin_family = AF_INET; - /* - * ... 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); + /* + * 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 = true; } + } - a.sin_port = p_htons((short)port); + /* + * ... 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))); + 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)); + retcode = p_bind(s, (struct sockaddr *) &a, sizeof(a)); #endif - if (retcode != SOCKET_ERROR) { - err = 0; - } else { - err = p_WSAGetLastError(); - } + if (retcode != SOCKET_ERROR) { + err = 0; + } else { + err = p_WSAGetLastError(); + } if (err) { p_closesocket(s); -- cgit v1.2.3 From f08879a55686da5e7d307f0d2f2b172e52d38857 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 8 Jan 2022 15:14:30 +0000 Subject: windows/network.c: create a 'superfamily' SockAddr field. This replaces two previous boolean fields 'resolved' and 'namedpipe', converting them into a single three-valued enum which avoids being able to represent the meaningless fourth possibility at all. Also, it provides an open-ended place to add further possibilities. The new field is very similar to the one in unix/network.c, except that the UNIX entry for AF_UNIX sockets is missing, and in its place is the NAMEDPIPE entry for storing the pathnames of Windows named pipes. --- windows/network.c | 61 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 32 insertions(+), 29 deletions(-) diff --git a/windows/network.c b/windows/network.c index 06d7916f..7e9dda28 100644 --- a/windows/network.c +++ b/windows/network.c @@ -79,12 +79,24 @@ struct NetSocket { Socket sock; }; +/* + * Top-level discriminator for SockAddr. + * + * UNRESOLVED means a host name not yet put through DNS; IP means a + * resolved IP address (or list of them); NAMEDPIPE indicates that + * this SockAddr is phony, holding a Windows named pipe pathname + * instead of any address WinSock can understand. + */ +typedef enum SuperFamily { + UNRESOLVED, + IP, + NAMEDPIPE +} SuperFamily; + struct SockAddr { int refcount; char *error; - bool resolved; - bool namedpipe; /* indicates that this SockAddr is phony, holding a Windows - * named pipe pathname instead of a network address */ + SuperFamily superfamily; #ifndef NO_IPV6 struct addrinfo *ais; /* Addresses IPv6 style. */ #endif @@ -101,11 +113,11 @@ struct SockAddr { */ #ifndef NO_IPV6 #define SOCKADDR_FAMILY(addr, step) \ - (!(addr)->resolved ? AF_UNSPEC : \ + ((addr)->superfamily != IP ? AF_UNSPEC : \ (step).ai ? (step).ai->ai_family : AF_INET) #else #define SOCKADDR_FAMILY(addr, step) \ - (!(addr)->resolved ? AF_UNSPEC : AF_INET) + ((addr)->superfamily != IP ? AF_UNSPEC : AF_INET) #endif /* @@ -446,9 +458,8 @@ SockAddr *sk_namelookup(const char *host, char **canonicalname, #ifndef NO_IPV6 ret->ais = NULL; #endif - ret->namedpipe = false; ret->addresses = NULL; - ret->resolved = false; + ret->superfamily = UNRESOLVED; ret->refcount = 1; *realhost = '\0'; @@ -471,7 +482,7 @@ SockAddr *sk_namelookup(const char *host, char **canonicalname, sfree(trimmed_host); } if (err == 0) - ret->resolved = true; + ret->superfamily = IP; } else #endif { @@ -480,12 +491,12 @@ SockAddr *sk_namelookup(const char *host, char **canonicalname, * (NOTE: we don't use gethostbyname as a fallback!) */ if ( (h = p_gethostbyname(host)) ) - ret->resolved = true; + ret->superfamily = IP; else err = p_WSAGetLastError(); } - if (!ret->resolved) { + if (ret->superfamily != IP) { ret->error = (err == WSAENETDOWN ? "Network is down" : err == WSAHOST_NOT_FOUND ? "Host does not exist" : err == WSATRY_AGAIN ? "Host not found" : @@ -536,7 +547,7 @@ SockAddr *sk_namelookup(const char *host, char **canonicalname, ret->addresses = snewn(1, unsigned long); ret->naddresses = 1; ret->addresses[0] = p_ntohl(a); - ret->resolved = true; + ret->superfamily = IP; strncpy(realhost, host, sizeof(realhost)); } realhost[lenof(realhost)-1] = '\0'; @@ -544,38 +555,30 @@ SockAddr *sk_namelookup(const char *host, char **canonicalname, return ret; } -SockAddr *sk_nonamelookup(const char *host) +static SockAddr *sk_special_addr(SuperFamily superfamily, const char *name) { SockAddr *ret = snew(SockAddr); ret->error = NULL; - ret->resolved = false; + ret->superfamily = superfamily; #ifndef NO_IPV6 ret->ais = NULL; #endif - ret->namedpipe = false; ret->addresses = NULL; ret->naddresses = 0; ret->refcount = 1; - strncpy(ret->hostname, host, lenof(ret->hostname)); + strncpy(ret->hostname, name, lenof(ret->hostname)); ret->hostname[lenof(ret->hostname)-1] = '\0'; return ret; } +SockAddr *sk_nonamelookup(const char *host) +{ + return sk_special_addr(UNRESOLVED, host); +} + SockAddr *sk_namedpipe_addr(const char *pipename) { - SockAddr *ret = snew(SockAddr); - ret->error = NULL; - ret->resolved = false; -#ifndef NO_IPV6 - ret->ais = NULL; -#endif - ret->namedpipe = true; - ret->addresses = NULL; - ret->naddresses = 0; - ret->refcount = 1; - strncpy(ret->hostname, pipename, lenof(ret->hostname)); - ret->hostname[lenof(ret->hostname)-1] = '\0'; - return ret; + return sk_special_addr(NAMEDPIPE, pipename); } static bool sk_nextaddr(SockAddr *addr, SockAddrStep *step) @@ -662,7 +665,7 @@ static SockAddr sk_extractaddr_tmp( bool sk_addr_needs_port(SockAddr *addr) { - return !addr->namedpipe; + return addr->superfamily != NAMEDPIPE; } bool sk_hostname_is_local(const char *name) -- cgit v1.2.3 From 5b5904b7cc38a063995912c950abb22ae68c600a Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 8 Jan 2022 15:28:34 +0000 Subject: windows/network.c: refactor switch in sk_newlistener. The code that diverges based on the address family is now in the form of a switch statement, rather than an unwieldy series of chained ifs. And the final call to bind() has all its arguments worked out in the previous switch, rather than computing them at the last minute with an equally unwieldy set of ?: operators that repeat the previous test. This will make it easier to add more cases, and also, to keep each case under its own ifdef without losing too much legibility. --- windows/network.c | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/windows/network.c b/windows/network.c index 7e9dda28..3d524d85 100644 --- a/windows/network.c +++ b/windows/network.c @@ -1131,6 +1131,8 @@ Socket *sk_newlistener(const char *srcaddr, int port, Plug *plug, SOCKADDR_IN6 a6; #endif SOCKADDR_IN a; + struct sockaddr *bindaddr; + unsigned bindsize; DWORD err; const char *errstr; @@ -1198,8 +1200,9 @@ Socket *sk_newlistener(const char *srcaddr, int port, Plug *plug, (const char *)&on, sizeof(on)); } + switch (address_family) { #ifndef NO_IPV6 - if (address_family == AF_INET6) { + case AF_INET6: { memset(&a6, 0, sizeof(a6)); a6.sin6_family = AF_INET6; if (local_host_only) @@ -1226,9 +1229,12 @@ Socket *sk_newlistener(const char *srcaddr, int port, Plug *plug, } } a6.sin6_port = p_htons(port); - } else + bindaddr = (struct sockaddr *)&a6; + bindsize = sizeof(a6); + break; + } #endif - { + case AF_INET: { bool got_addr = false; a.sin_family = AF_INET; @@ -1256,16 +1262,15 @@ Socket *sk_newlistener(const char *srcaddr, int port, Plug *plug, } a.sin_port = p_htons((short)port); + bindaddr = (struct sockaddr *)&a; + bindsize = sizeof(a); + break; + } + default: + unreachable("bad address family in sk_newlistener_internal"); } -#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 + + retcode = p_bind(s, bindaddr, bindsize); if (retcode != SOCKET_ERROR) { err = 0; } else { -- cgit v1.2.3 From 04cc999b50adc11a97a50e91822039531742edcb Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 27 Jan 2022 20:32:27 +0000 Subject: windows/network.c: refactor SOCKADDR_FAMILY. This macro is now an inline function, and as in the previous commit, each possible value for the main discriminator is now a case in a switch statement instead of tested in an interlocking set of ?:. --- windows/network.c | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/windows/network.c b/windows/network.c index 3d524d85..c8dfcab5 100644 --- a/windows/network.c +++ b/windows/network.c @@ -111,14 +111,19 @@ struct SockAddr { * not been done and a simple host name is held in this SockAddr * structure. */ +static inline int sockaddr_family(SockAddr *addr, SockAddrStep step) +{ + switch (addr->superfamily) { + case IP: #ifndef NO_IPV6 -#define SOCKADDR_FAMILY(addr, step) \ - ((addr)->superfamily != IP ? AF_UNSPEC : \ - (step).ai ? (step).ai->ai_family : AF_INET) -#else -#define SOCKADDR_FAMILY(addr, step) \ - ((addr)->superfamily != IP ? AF_UNSPEC : AF_INET) + if (step.ai) + return step.ai->ai_family; #endif + return AF_INET; + default: + return AF_UNSPEC; + } +} /* * Start a SockAddrStep structure to step through multiple @@ -622,7 +627,7 @@ void sk_getaddr(SockAddr *addr, char *buf, int buflen) } } else #endif - if (SOCKADDR_FAMILY(addr, step) == AF_INET) { + 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]); @@ -653,7 +658,7 @@ static SockAddr sk_extractaddr_tmp( #ifndef NO_IPV6 toret.ais = step->ai; #endif - if (SOCKADDR_FAMILY(addr, *step) == AF_INET + if (sockaddr_family(addr, *step) == AF_INET #ifndef NO_IPV6 && !toret.ais #endif @@ -713,7 +718,7 @@ bool sk_address_is_local(SockAddr *addr) SockAddrStep step; int family; START_STEP(addr, step); - family = SOCKADDR_FAMILY(addr, step); + family = sockaddr_family(addr, step); #ifndef NO_IPV6 if (family == AF_INET6) { @@ -749,7 +754,7 @@ int sk_addrtype(SockAddr *addr) SockAddrStep step; int family; START_STEP(addr, step); - family = SOCKADDR_FAMILY(addr, step); + family = sockaddr_family(addr, step); return (family == AF_INET ? ADDRTYPE_IPV4 : #ifndef NO_IPV6 @@ -763,7 +768,7 @@ void sk_addrcopy(SockAddr *addr, char *buf) SockAddrStep step; int family; START_STEP(addr, step); - family = SOCKADDR_FAMILY(addr, step); + family = sockaddr_family(addr, step); assert(family != AF_UNSPEC); #ifndef NO_IPV6 @@ -907,7 +912,7 @@ static DWORD try_connect(NetSocket *sock) /* * Open socket. */ - family = SOCKADDR_FAMILY(sock->addr, sock->step); + family = sockaddr_family(sock->addr, sock->step); /* * Remove the socket from the tree before we overwrite its -- cgit v1.2.3 From 3d15342fe8a1e6f549fd8aaaa100114c942360bb Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 27 Jan 2022 20:33:50 +0000 Subject: windows/network.c: refactor addr family in sk_newlistener. Most of the previous large sk_newlistener function is now an inner function whose address-family parameter is a platform AF_FOO constant rather than one of our own ADDRTYPE_FOO. sk_newlistener itself is a trivial wrapper on that, which just does the initial translation from the input ADDRTYPE_FOO into an AF_FOO. This will make it possible to drop in alternative wrapper functions which won't have to make up a pointless ADDRTYPE. --- windows/network.c | 39 +++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/windows/network.c b/windows/network.c index c8dfcab5..f2d3a971 100644 --- a/windows/network.c +++ b/windows/network.c @@ -1128,8 +1128,8 @@ Socket *sk_new(SockAddr *addr, int port, bool privport, bool oobinline, return &ret->sock; } -Socket *sk_newlistener(const char *srcaddr, int port, Plug *plug, - bool local_host_only, int orig_address_family) +Socket *sk_newlistener_internal(const char *srcaddr, int port, Plug *plug, + bool local_host_only, int orig_address_family) { SOCKET s; #ifndef NO_IPV6 @@ -1144,7 +1144,7 @@ Socket *sk_newlistener(const char *srcaddr, int port, Plug *plug, NetSocket *ret; int retcode; - int address_family; + int address_family = orig_address_family; /* * Create NetSocket structure. @@ -1164,16 +1164,6 @@ Socket *sk_newlistener(const char *srcaddr, int port, Plug *plug, 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, @@ -1311,9 +1301,9 @@ Socket *sk_newlistener(const char *srcaddr, int port, Plug *plug, * 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) { - Socket *other = sk_newlistener(srcaddr, port, plug, - local_host_only, ADDRTYPE_IPV6); + if (address_family == AF_INET && orig_address_family == AF_UNSPEC) { + Socket *other = sk_newlistener_internal(srcaddr, port, plug, + local_host_only, AF_INET6); if (other) { NetSocket *ns = container_of(other, NetSocket, sock); @@ -1330,6 +1320,23 @@ Socket *sk_newlistener(const char *srcaddr, int port, Plug *plug, return &ret->sock; } +Socket *sk_newlistener(const char *srcaddr, int port, Plug *plug, + bool local_host_only, int orig_address_family) +{ + /* + * Translate address_family from platform-independent constants + * into local reality. + */ + int address_family = (orig_address_family == ADDRTYPE_IPV4 ? AF_INET : +#ifndef NO_IPV6 + orig_address_family == ADDRTYPE_IPV6 ? AF_INET6 : +#endif + AF_UNSPEC); + + return sk_newlistener_internal(srcaddr, port, plug, local_host_only, + address_family); +} + static void sk_net_close(Socket *sock) { NetSocket *s = container_of(sock, NetSocket, sock); -- cgit v1.2.3 From 018236da290803e94ffd843eb53ed4d695d27a0a Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 27 Jan 2022 20:37:32 +0000 Subject: Support AF_UNIX listening sockets on Windows. Not all Windows toolchains have this yet, so we have to put the whole lot under #ifdef. --- cmake/cmake.h.in | 1 + cmake/platforms/windows.cmake | 2 ++ windows/network.c | 66 +++++++++++++++++++++++++++++++++++++------ windows/platform.h | 1 + 4 files changed, 61 insertions(+), 9 deletions(-) diff --git a/cmake/cmake.h.in b/cmake/cmake.h.in index 9de1386b..bf5f6c8a 100644 --- a/cmake/cmake.h.in +++ b/cmake/cmake.h.in @@ -8,6 +8,7 @@ #cmakedefine01 HAVE_WINRES_H #cmakedefine01 HAVE_WIN_H #cmakedefine01 HAVE_NO_STDINT_H +#cmakedefine01 HAVE_AFUNIX_H #cmakedefine01 HAVE_GCP_RESULTSW #cmakedefine01 HAVE_ADDDLLDIRECTORY #cmakedefine01 HAVE_GETNAMEDPIPECLIENTPROCESSID diff --git a/cmake/platforms/windows.cmake b/cmake/platforms/windows.cmake index ef3f7825..c2a760fa 100644 --- a/cmake/platforms/windows.cmake +++ b/cmake/platforms/windows.cmake @@ -41,6 +41,8 @@ define_negation(NO_MULTIMON HAVE_MULTIMON_H) check_include_files("windows.h;htmlhelp.h" HAVE_HTMLHELP_H) define_negation(NO_HTMLHELP HAVE_HTMLHELP_H) +check_include_files("winsock2.h;afunix.h" HAVE_AFUNIX_H) + check_symbol_exists(strtoumax "inttypes.h" HAVE_STRTOUMAX) check_symbol_exists(AddDllDirectory "windows.h" HAVE_ADDDLLDIRECTORY) check_symbol_exists(SetDefaultDllDirectories "windows.h" diff --git a/windows/network.c b/windows/network.c index f2d3a971..acc74034 100644 --- a/windows/network.c +++ b/windows/network.c @@ -20,6 +20,10 @@ #include +#if HAVE_AFUNIX_H +#include +#endif + #ifndef NO_IPV6 #ifdef __clang__ #pragma clang diagnostic push @@ -83,13 +87,17 @@ struct NetSocket { * Top-level discriminator for SockAddr. * * UNRESOLVED means a host name not yet put through DNS; IP means a - * resolved IP address (or list of them); NAMEDPIPE indicates that + * resolved IP address (or list of them); UNIX indicates the AF_UNIX + * network family (which Windows also has); NAMEDPIPE indicates that * this SockAddr is phony, holding a Windows named pipe pathname * instead of any address WinSock can understand. */ typedef enum SuperFamily { UNRESOLVED, IP, +#if HAVE_AFUNIX_H + UNIX, +#endif NAMEDPIPE } SuperFamily; @@ -107,9 +115,9 @@ struct SockAddr { /* * 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. + * AF_INET6 for IPv6; AF_UNIX for Unix-domain sockets; AF_UNSPEC + * indicates that name resolution has not been done and a simple host + * name is held in this SockAddr structure. */ static inline int sockaddr_family(SockAddr *addr, SockAddrStep step) { @@ -120,6 +128,10 @@ static inline int sockaddr_family(SockAddr *addr, SockAddrStep step) return step.ai->ai_family; #endif return AF_INET; +#if HAVE_AFUNIX_H + case UNIX: + return AF_UNIX; +#endif default: return AF_UNSPEC; } @@ -586,6 +598,13 @@ SockAddr *sk_namedpipe_addr(const char *pipename) return sk_special_addr(NAMEDPIPE, pipename); } +#if HAVE_AFUNIX_H +SockAddr *sk_unix_addr(const char *sockpath) +{ + return sk_special_addr(UNIX, sockpath); +} +#endif + static bool sk_nextaddr(SockAddr *addr, SockAddrStep *step) { #ifndef NO_IPV6 @@ -670,7 +689,11 @@ static SockAddr sk_extractaddr_tmp( bool sk_addr_needs_port(SockAddr *addr) { - return addr->superfamily != NAMEDPIPE; + return addr->superfamily != NAMEDPIPE +#if HAVE_AFUNIX_H + && addr->superfamily != UNIX +#endif + ; } bool sk_hostname_is_local(const char *name) @@ -1132,10 +1155,13 @@ Socket *sk_newlistener_internal(const char *srcaddr, int port, Plug *plug, bool local_host_only, int orig_address_family) { SOCKET s; + SOCKADDR_IN a; #ifndef NO_IPV6 SOCKADDR_IN6 a6; #endif - SOCKADDR_IN a; +#if HAVE_AFUNIX_H + SOCKADDR_UN au; +#endif struct sockaddr *bindaddr; unsigned bindsize; @@ -1189,6 +1215,9 @@ Socket *sk_newlistener_internal(const char *srcaddr, int port, Plug *plug, ret->oobinline = false; +#if HAVE_AFUNIX_H + if (address_family != AF_UNIX) +#endif { BOOL on = true; p_setsockopt(s, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, @@ -1261,6 +1290,15 @@ Socket *sk_newlistener_internal(const char *srcaddr, int port, Plug *plug, bindsize = sizeof(a); break; } +#if HAVE_AFUNIX_H + case AF_UNIX: { + au.sun_family = AF_UNIX; + strncpy(au.sun_path, srcaddr, sizeof(au.sun_path)); + bindaddr = (struct sockaddr *)&au; + bindsize = sizeof(au); + break; + } +#endif default: unreachable("bad address family in sk_newlistener_internal"); } @@ -1337,6 +1375,16 @@ Socket *sk_newlistener(const char *srcaddr, int port, Plug *plug, address_family); } +Socket *sk_newlistener_unix(const char *path, Plug *plug) +{ +#if HAVE_AFUNIX_H + return sk_newlistener_internal(path, 0, plug, false, AF_UNIX); +#else + return new_error_socket_fmt( + plug, "AF_UNIX support not compiled into this program"); +#endif +} + static void sk_net_close(Socket *sock) { NetSocket *s = container_of(sock, NetSocket, sock); @@ -1676,7 +1724,7 @@ void select_result(WPARAM wParam, LPARAM lParam) #ifdef NO_IPV6 struct sockaddr_in isa; #else - struct sockaddr_storage isa; + struct sockaddr_storage isa; // FIXME: also if Unix and no IPv6 #endif int addrlen = sizeof(isa); SOCKET t; /* socket of connection */ @@ -1732,7 +1780,7 @@ static SocketPeerInfo *sk_net_peer_info(Socket *sock) #ifdef NO_IPV6 struct sockaddr_in addr; #else - struct sockaddr_storage addr; + struct sockaddr_storage addr; // FIXME: also if Unix and no IPv6 char buf[INET6_ADDRSTRLEN]; #endif int addrlen = sizeof(addr); @@ -1851,7 +1899,7 @@ SockAddr *platform_get_x11_unix_address(const char *display, int displaynum) { SockAddr *ret = snew(SockAddr); memset(ret, 0, sizeof(SockAddr)); - ret->error = "unix sockets not supported on this platform"; + ret->error = "unix sockets for X11 not supported on this platform"; ret->refcount = 1; return ret; } diff --git a/windows/platform.h b/windows/platform.h index 6900a3c6..33891c5f 100644 --- a/windows/platform.h +++ b/windows/platform.h @@ -293,6 +293,7 @@ void socket_reselect_all(void); SockAddr *sk_namedpipe_addr(const char *pipename); /* Turn a WinSock error code into a string. */ const char *winsock_error_string(int error); +Socket *sk_newlistener_unix(const char *socketpath, Plug *plug); /* * network.c dynamically loads WinSock 2 or WinSock 1 depending on -- cgit v1.2.3 From 82971a3ebb4110d7ac9dd6a4f93aec8214085c1e Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 29 Jan 2022 16:49:44 +0000 Subject: Handle WM_NETEVENT in Windows Pageant. Apparently when I made Windows Pageant use the winselgui system, I added the call that gets WSAAsyncSelect response messages sent to Pageant's window, but I didn't add the switch case in the window procedure that actually handles those responses. I suppose I didn't notice at the time because no actual functionality used it - Pageant has never yet dealt with any real (i.e. Winsock) sockets, only with HANDLE-based named pipes, which are called 'sockets' in PuTTY's abstraction, but not by Windows. --- windows/pageant.c | 3 +++ windows/platform.h | 1 + windows/select-gui.c | 27 +++++++++++++++++++++++++++ windows/window.c | 32 ++------------------------------ 4 files changed, 33 insertions(+), 30 deletions(-) diff --git a/windows/pageant.c b/windows/pageant.c index 0e3868a0..cb0abb97 100644 --- a/windows/pageant.c +++ b/windows/pageant.c @@ -1270,6 +1270,9 @@ static LRESULT CALLBACK TrayWndProc(HWND hwnd, UINT message, } break; } + case WM_NETEVENT: + winselgui_response(wParam, lParam); + return 0; case WM_DESTROY: quit_help(hwnd); PostQuitMessage(0); diff --git a/windows/platform.h b/windows/platform.h index 33891c5f..8018ba23 100644 --- a/windows/platform.h +++ b/windows/platform.h @@ -333,6 +333,7 @@ const char *do_select(SOCKET skt, bool enable); */ void winselgui_set_hwnd(HWND hwnd); void winselgui_clear_hwnd(void); +void winselgui_response(WPARAM wParam, LPARAM lParam); void winselcli_setup(void); SOCKET winselcli_unique_socket(void); diff --git a/windows/select-gui.c b/windows/select-gui.c index de3d9091..4c37c345 100644 --- a/windows/select-gui.c +++ b/windows/select-gui.c @@ -36,3 +36,30 @@ const char *do_select(SOCKET skt, bool enable) return NULL; } + +struct wm_netevent_params { + /* Used to pass data to wm_netevent_callback */ + WPARAM wParam; + LPARAM lParam; +}; + +static void wm_netevent_callback(void *vctx) +{ + struct wm_netevent_params *params = (struct wm_netevent_params *)vctx; + select_result(params->wParam, params->lParam); + sfree(params); +} + +void winselgui_response(WPARAM wParam, LPARAM lParam) +{ + /* + * To protect against re-entrancy when Windows's recv() + * immediately triggers a new WSAAsyncSelect window message, we + * don't call select_result directly from this handler but instead + * wait until we're back out at the top level of the message loop. + */ + struct wm_netevent_params *params = snew(struct wm_netevent_params); + params->wParam = wParam; + params->lParam = lParam; + queue_toplevel_callback(wm_netevent_callback, params); +} diff --git a/windows/window.c b/windows/window.c index c1cd0c4c..ebcaa74a 100644 --- a/windows/window.c +++ b/windows/window.c @@ -151,12 +151,6 @@ static Conf *conf; static LogContext *logctx; static Terminal *term; -struct wm_netevent_params { - /* Used to pass data to wm_netevent_callback */ - WPARAM wParam; - LPARAM lParam; -}; - static void conf_cache_data(void); static int cursor_type; static int vtmode; @@ -1117,16 +1111,6 @@ void cmdline_error(const char *fmt, ...) exit(1); } -/* - * Actually do the job requested by a WM_NETEVENT - */ -static void wm_netevent_callback(void *vctx) -{ - struct wm_netevent_params *params = (struct wm_netevent_params *)vctx; - select_result(params->wParam, params->lParam); - sfree(vctx); -} - static inline rgb rgb_from_colorref(COLORREF cr) { rgb toret; @@ -2812,21 +2796,9 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, ShowCaret(hwnd); return 0; } - case WM_NETEVENT: { - /* - * To protect against re-entrancy when Windows's recv() - * immediately triggers a new WSAAsyncSelect window - * message, we don't call select_result directly from this - * handler but instead wait until we're back out at the - * top level of the message loop. - */ - struct wm_netevent_params *params = - snew(struct wm_netevent_params); - params->wParam = wParam; - params->lParam = lParam; - queue_toplevel_callback(wm_netevent_callback, params); + case WM_NETEVENT: + winselgui_response(wParam, lParam); return 0; - } case WM_SETFOCUS: term_set_focus(term, true); CreateCaret(hwnd, caretbm, font_width, font_height); -- cgit v1.2.3 From 6f8db22972398b3a5439cbece0c7a7b678bdd7e4 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 27 Jan 2022 20:37:50 +0000 Subject: Windows Pageant: option to open an AF_UNIX socket. There's now a command-line option to make Pageant open an AF_UNIX socket at a pathname of your choice. This allows it to act as an SSH agent for any client program willing to use a WinSock AF_UNIX socket. In particular, this allows WSL 1 processes to talk directly to Windows Pageant without needing any intermediate process, because the AF_UNIX sockets in the WSL 1 world interoperate with WinSock's ones. (However, not WSL 2, which isn't very surprising.) --- doc/pageant.but | 39 +++++++++++++++++ windows/pageant.c | 126 +++++++++++++++++++++++++++++++++--------------------- 2 files changed, 117 insertions(+), 48 deletions(-) diff --git a/doc/pageant.but b/doc/pageant.but index 97d41087..8c19633d 100644 --- a/doc/pageant.but +++ b/doc/pageant.but @@ -217,6 +217,45 @@ point at a different program. You could point it at \cw{c:\\Windows\\System32\\OpenSSH\\ssh.exe} once you've done this setup \dash but it's just as easy to point it at Plink! +\S{pageant-cmdline-unix} Unix-domain sockets: integrating with WSL 1 + +Pageant can listen on the WinSock implementation of \q{Unix-domain +sockets}. These interoperate with the Unix-domain sockets found in the +original Windows Subsystem for Linux (now known as WSL 1). So if you +ask Pageant to listen on one of these, then your WSL 1 processes can +talk directly to Pageant. + +To configure this, run Pageant with the option \c{--unix}, followed +with a pathname. Then, in WSL 1, set the environment variable +\cw{SSH_AUTH_SOCK} to point at the WSL translation of that pathname. + +For example, you might run + +\c pageant --unix C:\Users\Simon\.ssh\agent.sock + +and in WSL 1, set the environment variable + +\c SSH_AUTH_SOCK=/mnt/c/Users/Simon/.ssh/agent.sock + +Alternatively, you can add a line to your \cw{.ssh/config} file inside +WSL that says + +\c IdentityAgent /mnt/c/Users/Simon/.ssh/agent.sock + +although doing it like that may mean that \cw{ssh-add} commands won't +find the agent, even though \cw{ssh} itself will. + +\s{Security note}: Unix-domain sockets are protected against access by +other users by the file protections on their containing directory. So +if your Windows machine is multiuser, make sure you create the socket +inside a directory that other users can't access at all. (In fact, +that's a good idea on general principles.) + +\s{Compatibility note}: WSL 2 processes cannot talk to Pageant by this +mechanism, because WSL 2's Unix-domain sockets are managed by a +separate Linux kernel, and not by the same kernel that WinSock talks +to. + \S{pageant-cmdline-keylist} Starting with the key list visible Start Pageant with the \i\c{--keylist} option to show the main window diff --git a/windows/pageant.c b/windows/pageant.c index cb0abb97..63605043 100644 --- a/windows/pageant.c +++ b/windows/pageant.c @@ -1391,6 +1391,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) { MSG msg; const char *command = NULL; + const char *unixsocket = NULL; bool show_keylist_on_startup = false; int argc; char **argv, **argstart; @@ -1494,6 +1495,8 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) show_keylist_on_startup = true; } else if (match_optval("-openssh-config", "-openssh_config")) { openssh_config_file = val; + } else if (match_optval("-unix")) { + unixsocket = val; } else if (match_opt("-c")) { /* * If we see `-c', then the rest of the command line @@ -1538,6 +1541,31 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) * running, so set up all the machinery to answer requests. */ if (!already_running) { + /* + * Set up the window class for the hidden window that receives + * all the messages to do with our presence in the system tray. + */ + + if (!prev) { + WNDCLASS wndclass; + + memset(&wndclass, 0, sizeof(wndclass)); + wndclass.lpfnWndProc = TrayWndProc; + wndclass.hInstance = inst; + wndclass.hIcon = LoadIcon(inst, MAKEINTRESOURCE(IDI_MAINICON)); + wndclass.lpszClassName = TRAYCLASSNAME; + + RegisterClass(&wndclass); + } + + keylist = NULL; + + traywindow = CreateWindow(TRAYCLASSNAME, TRAYWINTITLE, + WS_OVERLAPPEDWINDOW | WS_VSCROLL, + CW_USEDEFAULT, CW_USEDEFAULT, + 100, 100, NULL, NULL, inst, NULL); + winselgui_set_hwnd(traywindow); + /* * Initialise the cross-platform Pageant code. */ @@ -1546,40 +1574,67 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) /* * Set up a named-pipe listener. */ - Plug *pl_plug; wpc->plc.vt = &winpgnt_vtable; wpc->plc.suppress_logging = true; - struct pageant_listen_state *pl = - pageant_listener_new(&pl_plug, &wpc->plc); - char *pipename = agent_named_pipe_name(); - Socket *sock = new_named_pipe_listener(pipename, pl_plug); - if (sk_socket_error(sock)) { - char *err = dupprintf("Unable to open named pipe at %s " - "for SSH agent:\n%s", pipename, - sk_socket_error(sock)); - MessageBox(NULL, err, "Pageant Error", MB_ICONERROR | MB_OK); - return 1; + { + Plug *pl_plug; + struct pageant_listen_state *pl = + pageant_listener_new(&pl_plug, &wpc->plc); + char *pipename = agent_named_pipe_name(); + Socket *sock = new_named_pipe_listener(pipename, pl_plug); + if (sk_socket_error(sock)) { + char *err = dupprintf("Unable to open named pipe at %s " + "for SSH agent:\n%s", pipename, + sk_socket_error(sock)); + MessageBox(NULL, err, "Pageant Error", MB_ICONERROR | MB_OK); + return 1; + } + pageant_listener_got_socket(pl, sock); + + /* + * If we've been asked to write out an OpenSSH config file + * pointing at the named pipe, do so. + */ + if (openssh_config_file) { + FILE *fp = fopen(openssh_config_file, "w"); + if (!fp) { + char *err = dupprintf("Unable to write OpenSSH config " + "file to %s", openssh_config_file); + MessageBox(NULL, err, "Pageant Error", + MB_ICONERROR | MB_OK); + return 1; + } + fprintf(fp, "IdentityAgent %s\n", pipename); + fclose(fp); + } + + sfree(pipename); } - pageant_listener_got_socket(pl, sock); /* - * If we've been asked to write out an OpenSSH config file - * pointing at the named pipe, do so. + * Set up an AF_UNIX listener too, if we were asked to. */ - if (openssh_config_file) { - FILE *fp = fopen(openssh_config_file, "w"); - if (!fp) { - char *err = dupprintf("Unable to write OpenSSH config file " - "to %s", openssh_config_file); + if (unixsocket) { + sk_init(); + + /* FIXME: diagnose any error except file-not-found. Also, + * check the file type if possible? */ + remove(unixsocket); + + Plug *pl_plug; + struct pageant_listen_state *pl = + pageant_listener_new(&pl_plug, &wpc->plc); + Socket *sock = sk_newlistener_unix(unixsocket, pl_plug); + if (sk_socket_error(sock)) { + char *err = dupprintf("Unable to open AF_UNIX socket at %s " + "for SSH agent:\n%s", unixsocket, + sk_socket_error(sock)); MessageBox(NULL, err, "Pageant Error", MB_ICONERROR | MB_OK); return 1; } - fprintf(fp, "IdentityAgent %s\n", pipename); - fclose(fp); + pageant_listener_got_socket(pl, sock); } - sfree(pipename); - /* * Set up the window class for the hidden window that receives * the WM_COPYDATA message used by the old-style Pageant IPC @@ -1661,31 +1716,6 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) return 0; } - /* - * Set up the window class for the hidden window that receives - * all the messages to do with our presence in the system tray. - */ - - if (!prev) { - WNDCLASS wndclass; - - memset(&wndclass, 0, sizeof(wndclass)); - wndclass.lpfnWndProc = TrayWndProc; - wndclass.hInstance = inst; - wndclass.hIcon = LoadIcon(inst, MAKEINTRESOURCE(IDI_MAINICON)); - wndclass.lpszClassName = TRAYCLASSNAME; - - RegisterClass(&wndclass); - } - - keylist = NULL; - - traywindow = CreateWindow(TRAYCLASSNAME, TRAYWINTITLE, - WS_OVERLAPPEDWINDOW | WS_VSCROLL, - CW_USEDEFAULT, CW_USEDEFAULT, - 100, 100, NULL, NULL, inst, NULL); - winselgui_set_hwnd(traywindow); - /* Set up a system tray icon */ AddTrayIcon(traywindow); -- cgit v1.2.3 From aa01530488e0b693172b513836fa6881d387ea8a Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 10 Feb 2022 18:51:19 +0000 Subject: Fix handling of shifted SCO function keys. A user points out that this has regressed since 0.76, probably when I reorganised the keyboard control-sequence formatting into centralised helper functions in terminal.c. The SCO function keys should behave differently when you press Shift or Ctrl or both. For example, F1 should generate ESC[M bare, ESC[Y with Shift, Esc[k with Ctrl, Esc[w with Shift+Ctrl. But in fact, Shift was having no effect, so those tests would give ESC[M twice and ESC[k twice. That was because I was setting 'shift = false' for all function key types except FUNKY_XTERM_216, after modifying the derived 'index' value. But the SCO branch of the code doesn't use 'index' (it wouldn't have the right value in any case), so the sole effect was to forget about Shift. Easily fixed by disabling that branch for FUNKY_SCO too. --- terminal/terminal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terminal/terminal.c b/terminal/terminal.c index 3398cd59..923ed03e 100644 --- a/terminal/terminal.c +++ b/terminal/terminal.c @@ -7373,7 +7373,7 @@ int format_function_key(char *buf, Terminal *term, int key_number, assert(key_number < lenof(key_number_to_tilde_code)); int index = key_number; - if (term->funky_type != FUNKY_XTERM_216) { + if (term->funky_type != FUNKY_XTERM_216 && term->funky_type != FUNKY_SCO) { if (shift && index <= 10) { shift = false; index += 10; -- cgit v1.2.3 From 445f9de1297e408b9ca08f231873e312eb2222d4 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 10 Feb 2022 18:51:19 +0000 Subject: Fix handling of shifted SCO function keys. A user points out that this has regressed since 0.76, probably when I reorganised the keyboard control-sequence formatting into centralised helper functions in terminal.c. The SCO function keys should behave differently when you press Shift or Ctrl or both. For example, F1 should generate ESC[M bare, ESC[Y with Shift, Esc[k with Ctrl, Esc[w with Shift+Ctrl. But in fact, Shift was having no effect, so those tests would give ESC[M twice and ESC[k twice. That was because I was setting 'shift = false' for all function key types except FUNKY_XTERM_216, after modifying the derived 'index' value. But the SCO branch of the code doesn't use 'index' (it wouldn't have the right value in any case), so the sole effect was to forget about Shift. Easily fixed by disabling that branch for FUNKY_SCO too. (cherry picked from commit aa01530488e0b693172b513836fa6881d387ea8a) --- terminal/terminal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terminal/terminal.c b/terminal/terminal.c index 3398cd59..923ed03e 100644 --- a/terminal/terminal.c +++ b/terminal/terminal.c @@ -7373,7 +7373,7 @@ int format_function_key(char *buf, Terminal *term, int key_number, assert(key_number < lenof(key_number_to_tilde_code)); int index = key_number; - if (term->funky_type != FUNKY_XTERM_216) { + if (term->funky_type != FUNKY_XTERM_216 && term->funky_type != FUNKY_SCO) { if (shift && index <= 10) { shift = false; index += 10; -- cgit v1.2.3 From 5c9a43f47872a3c1eed99863fefda9b13a5f9e49 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 19 Feb 2022 11:55:07 +0000 Subject: HTTP proxy: support 'Transfer-encoding: chunked'. I had a report that the Windows free-as-in-beer proxy tool 'FreeProxy' didn't work with the new HTTP proxy code, and it turns out that the first reason why not is that the error-document in its 407 response is sent via chunked transfer encoding, which is to say, instead of an up-front Content-length header, you receive a sequence of chunks each prefixed with a hex length. (In 0.76, before the rewritten proxy support, we never even noticed this because we sent Basic auth details up front in our first attempt, rather than attempting a no-auth connection first and waiting to see what kind of auth the proxy asks us for. So we'd only ever see a 407 if the auth details were refused - and since 0.76 didn't have interactive proxy auth prompts, there was nothing we could do at that point but abort immediately, without attempting to parse the rest of the 407 at all.) Now we spot the Transfer-encoding header and successfully parse chunked transfers. Happily, we don't need to worry about the further transfer-encodings such as 'gzip', because we're not actually _using_ the error document - we only have to skip over it to find the end of the HTTP response. This still doesn't make PuTTY work with FreeProxy, because there are further problems hiding behind that one, which I'll fix in following commits. --- proxy/http.c | 80 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 77 insertions(+), 3 deletions(-) diff --git a/proxy/http.c b/proxy/http.c index 081fefb6..e01329ea 100644 --- a/proxy/http.c +++ b/proxy/http.c @@ -72,7 +72,8 @@ typedef struct HttpProxyNegotiator { uint32_t nonce_count; prompts_t *prompts; int username_prompt_index, password_prompt_index; - size_t content_length; + size_t content_length, chunk_length; + bool chunked_transfer; ProxyNegotiator pn; } HttpProxyNegotiator; @@ -151,6 +152,7 @@ static void proxy_http_free(ProxyNegotiator *pn) #define HTTP_HEADER_LIST(X) \ X(HDR_CONNECTION, "Connection") \ X(HDR_CONTENT_LENGTH, "Content-Length") \ + X(HDR_TRANSFER_ENCODING, "Transfer-Encoding") \ X(HDR_PROXY_AUTHENTICATE, "Proxy-Authenticate") \ /* end of list */ @@ -460,6 +462,7 @@ static void proxy_http_process_queue(ProxyNegotiator *pn) crReturnV; s->content_length = 0; + s->chunked_transfer = false; s->connection_close = false; /* @@ -530,6 +533,23 @@ static void proxy_http_process_queue(ProxyNegotiator *pn) if (!get_token(s)) continue; s->content_length = strtoumax(s->token->s, NULL, 10); + } else if (hdr == HDR_TRANSFER_ENCODING) { + /* + * The Transfer-Encoding header value should be a + * comma-separated list of keywords including + * "chunked", "deflate" and "gzip". We parse it in the + * most superficial way, by just looking for "chunked" + * and ignoring everything else. + * + * It's OK to do that because we're not actually + * _using_ the error document - we only have to skip + * over it to find the end of the HTTP response. So we + * don't care if it's gzipped or not. + */ + while (get_token(s)) { + if (!stricmp(s->token->s, "chunked")) + s->chunked_transfer = true; + } } else if (hdr == HDR_CONNECTION) { if (!get_token(s)) continue; @@ -584,8 +604,62 @@ static void proxy_http_process_queue(ProxyNegotiator *pn) } while (s->header->len > 0); /* Read and ignore the entire response document */ - crMaybeWaitUntilV(bufchain_try_consume( - pn->input, s->content_length)); + if (!s->chunked_transfer) { + /* Simple approach: read exactly Content-Length bytes */ + crMaybeWaitUntilV(bufchain_try_consume( + pn->input, s->content_length)); + } else { + /* Chunked transfer: read a sequence of + * \r\n\r\n chunks, terminating in one with + * zero length */ + do { + /* + * Expect a chunk length + */ + s->chunk_length = 0; + while (true) { + char c; + crMaybeWaitUntilV(bufchain_try_fetch_consume( + pn->input, &c, 1)); + if (c == '\r') { + continue; + } else if (c == '\n') { + break; + } else if ('0' <= c && c <= '9') { + s->chunk_length = s->chunk_length*16 + (c-'0'); + } else if ('A' <= c && c <= 'F') { + s->chunk_length = s->chunk_length*16 + (c-'A'+10); + } else if ('a' <= c && c <= 'f') { + s->chunk_length = s->chunk_length*16 + (c-'a'+10); + } else { + pn->error = dupprintf( + "Received bad character 0x%02X in chunk length " + "during HTTP chunked transfer encoding", + (unsigned)(unsigned char)c); + crStopV; + } + } + + /* + * Expect that many bytes of chunked data + */ + crMaybeWaitUntilV(bufchain_try_consume( + pn->input, s->chunk_length)); + + /* Now expect \r\n */ + { + char buf[2]; + crMaybeWaitUntilV(bufchain_try_fetch_consume( + pn->input, buf, 2)); + if (memcmp(buf, "\r\n", 2)) { + pn->error = dupprintf( + "Missing CRLF after chunk " + "during HTTP chunked transfer encoding"); + crStopV; + } + } + } while (s->chunk_length); + } if (200 <= s->http_status && s->http_status < 300) { /* Any 2xx HTTP response means we're done */ -- cgit v1.2.3 From 099d00c4acaaa28f660d3a941eaadbaa012b5924 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 19 Feb 2022 11:56:54 +0000 Subject: HTTP proxy: accept the 'Proxy-Connection' header. FreeProxy sends this as a substitute for the standard 'Connection' header (with the same contents, i.e. 'keep-alive' or 'close' depending on whether the TCP connection is going to continue afterwards). The Internet reckons it's not standard, but it's easy to recognise as an ad-hoc synonym for 'Connection'. --- proxy/http.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/proxy/http.c b/proxy/http.c index e01329ea..761abdf6 100644 --- a/proxy/http.c +++ b/proxy/http.c @@ -154,6 +154,7 @@ static void proxy_http_free(ProxyNegotiator *pn) X(HDR_CONTENT_LENGTH, "Content-Length") \ X(HDR_TRANSFER_ENCODING, "Transfer-Encoding") \ X(HDR_PROXY_AUTHENTICATE, "Proxy-Authenticate") \ + X(HDR_PROXY_CONNECTION, "Proxy-Connection") \ /* end of list */ typedef enum HttpHeader { @@ -550,7 +551,8 @@ static void proxy_http_process_queue(ProxyNegotiator *pn) if (!stricmp(s->token->s, "chunked")) s->chunked_transfer = true; } - } else if (hdr == HDR_CONNECTION) { + } else if (hdr == HDR_CONNECTION || + hdr == HDR_PROXY_CONNECTION) { if (!get_token(s)) continue; if (!stricmp(s->token->s, "close")) -- cgit v1.2.3 From 6c754822bca7284374f15ba90392e598909f9833 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 19 Feb 2022 12:04:09 +0000 Subject: Proxy system: ability to reconnect to the proxy server. Another awkward thing that FreeProxy does is to slam the connection shut after sending its 407 response, at least in Basic auth mode. (It keeps the connection alive in digest mode, which makes sense to me, because that's a more stateful system.) It was surprisingly easy to make the proxy code able to tolerate this! I've set it up so that a ProxyNegotiator can just set its 'reconnect' flag on return from the negotiation coroutine, and the effect will be that proxy.c makes a new connection to the same proxy server before doing anything else. In particular, you can set that flag _and_ put data in the output bufchain, and there's no problem - the output data will be queued directly into the new socket. --- proxy/http.c | 6 +++--- proxy/proxy.c | 25 +++++++++++++++++++++---- proxy/proxy.h | 10 ++++++++++ 3 files changed, 34 insertions(+), 7 deletions(-) diff --git a/proxy/http.c b/proxy/http.c index 761abdf6..d4797171 100644 --- a/proxy/http.c +++ b/proxy/http.c @@ -670,9 +670,9 @@ static void proxy_http_process_queue(ProxyNegotiator *pn) /* 407 is Proxy Authentication Required, which we may be * able to do something about. */ if (s->connection_close) { - pn->error = dupprintf("HTTP proxy closed connection after " - "asking for authentication"); - crStopV; + /* If we got 407 + connection closed, reconnect before + * sending our next request. */ + pn->reconnect = true; } /* If the best we can do is report some kind of error from diff --git a/proxy/proxy.c b/proxy/proxy.c index 36105097..6bedcf9b 100644 --- a/proxy/proxy.c +++ b/proxy/proxy.c @@ -99,6 +99,7 @@ static void sk_proxy_close (Socket *s) ProxySocket *ps = container_of(s, ProxySocket, sock); sk_close(ps->sub_socket); + sk_addr_free(ps->proxy_addr); sk_addr_free(ps->remote_addr); proxy_negotiator_cleanup(ps); bufchain_clear(&ps->output_from_negotiator); @@ -223,6 +224,16 @@ static void proxy_negotiate(ProxySocket *ps) return; } + if (ps->pn->reconnect) { + sk_close(ps->sub_socket); + SockAddr *proxy_addr = sk_addr_dup(ps->proxy_addr); + ps->sub_socket = sk_new(proxy_addr, ps->proxy_port, + ps->proxy_privport, ps->proxy_oobinline, + ps->proxy_nodelay, ps->proxy_keepalive, + &ps->plugimpl); + ps->pn->reconnect = false; + } + while (bufchain_size(&ps->output_from_negotiator)) { ptrlen data = bufchain_prefix(&ps->output_from_negotiator); sk_write(ps->sub_socket, data.ptr, data.len); @@ -601,10 +612,16 @@ Socket *new_connection(SockAddr *addr, const char *hostname, /* create the actual socket we will be using, * connected to our proxy server and port. */ - ps->sub_socket = sk_new(proxy_addr, - conf_get_int(conf, CONF_proxy_port), - privport, oobinline, - nodelay, keepalive, &ps->plugimpl); + ps->proxy_addr = sk_addr_dup(proxy_addr); + ps->proxy_port = conf_get_int(conf, CONF_proxy_port); + ps->proxy_privport = privport; + ps->proxy_oobinline = oobinline; + ps->proxy_nodelay = nodelay; + ps->proxy_keepalive = keepalive; + ps->sub_socket = sk_new(proxy_addr, ps->proxy_port, + ps->proxy_privport, ps->proxy_oobinline, + ps->proxy_nodelay, ps->proxy_keepalive, + &ps->plugimpl); if (sk_socket_error(ps->sub_socket) != NULL) return &ps->sock; diff --git a/proxy/proxy.h b/proxy/proxy.h index ca4a293f..72d06d49 100644 --- a/proxy/proxy.h +++ b/proxy/proxy.h @@ -22,6 +22,11 @@ struct ProxySocket { SockAddr *remote_addr; int remote_port; + /* Parameters needed to make further connections to the proxy */ + SockAddr *proxy_addr; + int proxy_port; + bool proxy_privport, proxy_oobinline, proxy_nodelay, proxy_keepalive; + bufchain pending_output_data; bufchain pending_oob_output_data; bufchain pending_input_data; @@ -68,6 +73,11 @@ struct ProxyNegotiator { /* Set to report user abort during proxy negotiation. */ bool aborted; + + /* Set to request the centralised code to make a fresh connection + * to the proxy server, e.g. because an HTTP proxy slammed the + * connection shut after sending 407 Proxy Auth Required. */ + bool reconnect; }; struct ProxyNegotiatorVT { -- cgit v1.2.3 From f85716be45a0cd47085c978744a6276fe189175e Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 19 Feb 2022 12:06:29 +0000 Subject: HTTP proxy: accept Digest algorithm name as a quoted string. FreeProxy sends 'algorithm="MD5"' instead of 'algorithm=MD5.' I'm actually not sure whether that's legal by RFC 7616, but it's certainly no trouble to parse if we see it. With all these changes, PuTTY now _can_ successfully make connections through FreeProxy again, whether it's in Basic or Digest mode. --- proxy/http.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxy/http.c b/proxy/http.c index d4797171..0738e37d 100644 --- a/proxy/http.c +++ b/proxy/http.c @@ -325,7 +325,7 @@ static HttpAuthDetails *parse_http_auth_header(HttpProxyNegotiator *s) d->hash_username = !stricmp(s->token->s, "true"); } else if (!stricmp(s->token->s, "algorithm")) { if (!get_separator(s, '=') || - !get_token(s)) + (!get_token(s) && !get_quoted_string(s))) return auth_error(d, "parse error in Digest algorithm " "field"); bool found = false; -- cgit v1.2.3 From 0613ec9986f0862886c1391726a4c31b1d08360d Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Tue, 22 Feb 2022 18:43:53 +0000 Subject: Add a docs note about DNS performed by GSSAPI. I recently noticed a mysterious delay at connection startup while using an SSH jump host, and investigated it in case it was a bug in the new jump host code that ought to be fixed before 0.77 goes out. strace showed that at the time of the delay PuTTY was doing a DNS lookup for the destination host, which was hanging due to the authoritative DNS server in question not being reachable. But that was odd, because I'd configured it to leave DNS lookup to the proxy, anticipating exactly that problem. But on closer investigation, the _proxy_ code was doing exactly what I'd told it. The DNS lookup was coming from somewhere else: namely, an (unsuccessful) attempt to set up a GSSAPI context. The GSSAPI library had called gethostbyname, completely separately from PuTTY's own use of DNS. Simple workaround for me: turn off GSSAPI, which doesn't work for that particular SSH connection anyway, and there's no point spending 30 seconds faffing just to find that out. But also, if that puzzled me, it's worth documenting! --- doc/config.but | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/doc/config.but b/doc/config.but index b1dc8dd6..813d243d 100644 --- a/doc/config.but +++ b/doc/config.but @@ -2057,6 +2057,16 @@ 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. +If you want to avoid PuTTY making \e{any} DNS query related to your +destination host name (for example, because your local DNS resolver is +very slow to return a negative response in that situation), then as +well as setting this control to \q{Yes}, you may also need to turn off +GSSAPI authentication and GSSAPI key exchange in SSH (see +\k{config-ssh-auth-gssapi} and \k{config-ssh-gssapi-kex} +respectively). This is because GSSAPI setup also involves a DNS query +for the destination host name, and that query is performed by the +separate GSSAPI library, so PuTTY can't override or reconfigure it. + \S{config-proxy-auth} \I{proxy username}Username and \I{proxy password}password If your proxy requires \I{proxy authentication}authentication, you can -- cgit v1.2.3 From ee987ce4cdc35578207e7cd02837292f5b23ebdd Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Tue, 8 Mar 2022 18:05:48 +0000 Subject: pterm.exe: fix handling of Windows exception codes. Unlike on Unix, a Windows process's exit status is a DWORD, i.e. a 32-bit unsigned integer. And exit statuses seen in practice can range up into the high half of that space. For example, if a process dies of an 'illegal instruction' exception, then the exit status retrieved by GetExitCodeProcess will be 0xC000001D == STATUS_ILLEGAL_INSTRUCTION. If this happens to the process running inside pterm.exe, then conpty->exitstatus will be set to a value greater than INT_MAX, which will cause conpty_exitcode to return -1. Unfortunately, a -1 return from conpty_exitstatus is treated by front ends as saying that the backend process hasn't exited yet, and is still running. So pterm will sit around after its subprocess has already terminated, contrary to your 'close on exit' setting. Moreover, when cmd.exe exits, it apparently passes on to its parent process the exit status of the last subcommand it ran. So if you run a Windows pterm containing an ordinary interactive console session, and the last subprogram you happen to run inside that session dies of a fatal signal, then the same thing will happen after you type 'exit' at the command prompt. This has been happening to me intermittently ever since I created pterm.exe in the first place, and I guessed completely wrong about the cause (I feared some kind of subtle race condition in pterm's use of the process API). I've only just managed to reproduce it reliably enough to debug, and I'm relieved to find it's much simpler than that! The immediate fix, in any case, is to ensure we don't return -1 from conpty_exitcode unless the process really is still running. And don't return INT_MAX either, because that indicates 'unclean exit' in a way that triggers 'close window only on clean exit' (and even Unix pterm doesn't consider the primary subprocess dying of a signal to count as unclean). So we clip all out-of-range Windows exception codes to INT_MAX-1. In the longer term I think it would be nice to turn exit codes into a full 32-bit space, and move the special values completely out of it. That would permit actually keeping the exact exception code and passing it on to a caller who needed it. For example, if we were to write a Windows psusan (which I could imagine being occasionally useful), this way it would be able to return the unmodified full Windows exit code via the "exit-status" chanreq. But I don't think we currently have any clients needing that much detail, so that's a more intrusive cleanup for a later date. --- windows/conpty.c | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/windows/conpty.c b/windows/conpty.c index fd5ef246..ca907020 100644 --- a/windows/conpty.c +++ b/windows/conpty.c @@ -356,12 +356,26 @@ static int conpty_exitcode(Backend *be) { ConPTY *conpty = container_of(be, ConPTY, backend); - if (conpty->exited && - 0 <= conpty->exitstatus && - conpty->exitstatus <= INT_MAX) - return conpty->exitstatus; - else + if (conpty->exited) { + /* + * PuTTY's representation of exit statuses expects them to be + * non-negative 'int' values. But Windows exit statuses can + * include all those exception codes like 0xC000001D which + * convert to negative 32-bit ints. + * + * I don't think there's a great deal of use for returning + * those in full detail, right now. (Though if we ever + * connected this system up to a Windows version of psusan or + * Uppity, perhaps there might be?) + * + * So we clip them at INT_MAX-1, since INT_MAX is reserved for + * 'exit so unclean as to inhibit Close On Clean Exit'. + */ + return (0 <= conpty->exitstatus && conpty->exitstatus < INT_MAX) ? + conpty->exitstatus : INT_MAX-1; + } else { return -1; + } } static int conpty_cfg_info(Backend *be) -- cgit v1.2.3 From 269ea8aaf5d3fc2b9e577b09e217dc0ed051dd2b Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 12 Mar 2022 16:57:51 +0000 Subject: Move predeclaration of struct unicode_data into defs.h. It's just the sort of thing that ought to be in there, once, so it doesn't have to be declared in n places. --- defs.h | 2 ++ unix/platform.h | 1 - windows/platform.h | 1 - 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/defs.h b/defs.h index 45bc90f3..3c30b6b6 100644 --- a/defs.h +++ b/defs.h @@ -192,6 +192,8 @@ typedef struct logblank_t logblank_t; typedef struct BinaryPacketProtocol BinaryPacketProtocol; typedef struct PacketProtocolLayer PacketProtocolLayer; +struct unicode_data; + /* Do a compile-time type-check of 'to_check' (without evaluating it), * as a side effect of returning the value 'to_return'. Note that * although this macro double-*expands* to_return, it always diff --git a/unix/platform.h b/unix/platform.h index 6dff7842..7cdf4d11 100644 --- a/unix/platform.h +++ b/unix/platform.h @@ -347,7 +347,6 @@ char *make_dir_path(const char *path, mode_t mode); /* * Exports from unicode.c. */ -struct unicode_data; bool init_ucs(struct unicode_data *ucsdata, char *line_codepage, bool utf8_override, int font_charset, int vtmode); diff --git a/windows/platform.h b/windows/platform.h index 6900a3c6..d15e1a68 100644 --- a/windows/platform.h +++ b/windows/platform.h @@ -606,7 +606,6 @@ void EnableSizeTip(bool bEnable); /* * Exports from unicode.c. */ -struct unicode_data; void init_ucs(Conf *, struct unicode_data *); /* -- cgit v1.2.3 From 21f602be407838a53b281484afdeb068a84713b8 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 12 Mar 2022 16:01:21 +0000 Subject: Add utility function dup_wc_to_mb. This parallels dup_mb_to_wc, which already existed. I haven't needed the same thing this way round yet, but I'm about to. --- misc.h | 4 ++++ utils/CMakeLists.txt | 1 + utils/dup_wc_to_mb.c | 39 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 44 insertions(+) create mode 100644 utils/dup_wc_to_mb.c diff --git a/misc.h b/misc.h index a85b87ae..a78f8c8d 100644 --- a/misc.h +++ b/misc.h @@ -67,6 +67,10 @@ void strbuf_finalise_agent_query(strbuf *buf); * work around the rather deficient interface of mb_to_wc. */ wchar_t *dup_mb_to_wc_c(int codepage, int flags, const char *string, int len); wchar_t *dup_mb_to_wc(int codepage, int flags, const char *string); +char *dup_wc_to_mb_c(int codepage, int flags, const wchar_t *string, int len, + const char *defchr, struct unicode_data *ucsdata); +char *dup_wc_to_mb(int codepage, int flags, const wchar_t *string, + const char *defchr, struct unicode_data *ucsdata); static inline int toint(unsigned u) { diff --git a/utils/CMakeLists.txt b/utils/CMakeLists.txt index 4a6a1fe8..8048f811 100644 --- a/utils/CMakeLists.txt +++ b/utils/CMakeLists.txt @@ -17,6 +17,7 @@ add_sources_from_current_dir(utils dupprintf.c dupstr.c dup_mb_to_wc.c + dup_wc_to_mb.c encode_utf8.c encode_wide_string_as_utf8.c fgetline.c diff --git a/utils/dup_wc_to_mb.c b/utils/dup_wc_to_mb.c new file mode 100644 index 00000000..e91a8916 --- /dev/null +++ b/utils/dup_wc_to_mb.c @@ -0,0 +1,39 @@ +/* + * dup_wc_to_mb: memory-allocating wrapper on wc_to_mb. + * + * Also dup_wc_to_mb_c: same but you already know the length of the + * string. + */ + +#include + +#include "putty.h" +#include "misc.h" + +char *dup_wc_to_mb_c(int codepage, int flags, const wchar_t *string, int len, + const char *defchr, struct unicode_data *ucsdata) +{ + size_t outsize = len+1; + char *out = snewn(outsize, char); + + while (true) { + size_t outlen = wc_to_mb(codepage, flags, string, len, out, outsize, + defchr, ucsdata); + /* We can only be sure we've consumed the whole input if the + * output is not within a multibyte-character-length of the + * end of the buffer! */ + if (outlen < outsize && outsize - outlen > MB_LEN_MAX) { + out[outlen] = '\0'; + return out; + } + + sgrowarray(out, outsize, outsize); + } +} + +char *dup_wc_to_mb(int codepage, int flags, const wchar_t *string, + const char *defchr, struct unicode_data *ucsdata) +{ + return dup_wc_to_mb_c(codepage, flags, string, wcslen(string), + defchr, ucsdata); +} -- cgit v1.2.3 From b360ea6ac19b7688ab0a8b09350298874b731c4a Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 12 Mar 2022 15:53:04 +0000 Subject: Add a manual single-char UTF-8 decoder. This parallels encode_utf8 which we already had. Decoding is more fraught with perils than encoding, so I've also included a small test program. --- CMakeLists.txt | 5 ++ misc.h | 11 +++ utils/CMakeLists.txt | 2 + utils/decode_utf8.c | 178 +++++++++++++++++++++++++++++++++++++++++++ utils/decode_utf8_to_wchar.c | 20 +++++ 5 files changed, 216 insertions(+) create mode 100644 utils/decode_utf8.c create mode 100644 utils/decode_utf8_to_wchar.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 3aba5e20..0eb4cf1c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -79,6 +79,11 @@ add_executable(test_host_strfoo target_compile_definitions(test_host_strfoo PRIVATE TEST) target_link_libraries(test_host_strfoo utils ${platform_libraries}) +add_executable(test_decode_utf8 + utils/decode_utf8.c) +target_compile_definitions(test_decode_utf8 PRIVATE TEST) +target_link_libraries(test_decode_utf8 utils ${platform_libraries}) + add_executable(test_tree234 utils/tree234.c) target_compile_definitions(test_tree234 PRIVATE TEST) diff --git a/misc.h b/misc.h index a78f8c8d..dea7190b 100644 --- a/misc.h +++ b/misc.h @@ -221,6 +221,17 @@ size_t encode_utf8(void *output, unsigned long ch); * encoded in UTF-16. */ char *encode_wide_string_as_utf8(const wchar_t *wstr); +/* Decode a single UTF-8 character. Returns U+FFFD for any of the + * illegal cases. */ +unsigned long decode_utf8(const char **utf8); + +/* Decode a single UTF-8 character to an output buffer of the + * platform's wchar_t. May write a pair of surrogates if + * sizeof(wchar_t) == 2, assuming that in that case the wide string is + * encoded in UTF-16. Otherwise, writes one character. Returns the + * number written. */ +size_t decode_utf8_to_wchar(const char **utf8, wchar_t *out); + /* Write a string out in C string-literal format. */ void write_c_string_literal(FILE *fp, ptrlen str); diff --git a/utils/CMakeLists.txt b/utils/CMakeLists.txt index 8048f811..80fc20b8 100644 --- a/utils/CMakeLists.txt +++ b/utils/CMakeLists.txt @@ -12,6 +12,8 @@ add_sources_from_current_dir(utils conf_launchable.c ctrlparse.c debug.c + decode_utf8.c + decode_utf8_to_wchar.c default_description.c dupcat.c dupprintf.c diff --git a/utils/decode_utf8.c b/utils/decode_utf8.c new file mode 100644 index 00000000..c8dbec79 --- /dev/null +++ b/utils/decode_utf8.c @@ -0,0 +1,178 @@ +/* + * Decode a single UTF-8 character. + */ + +#include "putty.h" +#include "misc.h" + +unsigned long decode_utf8(const char **utf8) +{ + unsigned char c = (unsigned char)*(*utf8)++; + + /* One-byte cases. */ + if (c < 0x80) { + return c; + } else if (c < 0xC0) { + return 0xFFFD; /* spurious continuation byte */ + } + + unsigned long wc, min; + size_t ncont; + if (c < 0xE0) { + wc = c & 0x1F; ncont = 1; min = 0x80; + } else if (c < 0xF0) { + wc = c & 0x0F; ncont = 2; min = 0x800; + } else if (c < 0xF8) { + wc = c & 0x07; ncont = 3; min = 0x10000; + } else if (c < 0xFC) { + wc = c & 0x03; ncont = 4; min = 0x200000; + } else if (c < 0xFE) { + wc = c & 0x01; ncont = 5; min = 0x4000000; + } else { + return 0xFFFD; /* FE or FF illegal bytes */ + } + + while (ncont-- > 0) { + unsigned char cont = (unsigned char)**utf8; + if (!(0x80 <= cont && cont < 0xC0)) + return 0xFFFD; /* short sequence */ + (*utf8)++; + + wc = (wc << 6) | (cont & 0x3F); + } + + if (wc < min) + return 0xFFFD; /* overlong encoding */ + if (0xD800 <= wc && wc < 0xE000) + return 0xFFFD; /* UTF-8 encoding of surrogate */ + if (wc > 0x10FFFF) + return 0xFFFD; /* outside Unicode range */ + return wc; +} + +#ifdef TEST + +#include + +bool dotest(const char *file, int line, const char *input, + const unsigned long *chars, size_t nchars) +{ + const char *start = input; + const char *end = input + strlen(input) + 1; + size_t noutput = 0; + + printf("%s:%d: test start\n", file, line); + + while (input < end) { + const char *before = input; + unsigned long wc = decode_utf8(&input); + + printf("%s:%d in+%"SIZEu" out+%"SIZEu":", + file, line, (size_t)(before-start), noutput); + while (before < input) + printf(" %02x", (unsigned)(unsigned char)(*before++)); + printf(" -> U-%08lx\n", wc); + + if (noutput >= nchars) { + printf("%s:%d: FAIL: expected no further output\n", file, line); + return false; + } + + if (chars[noutput] != wc) { + printf("%s:%d: FAIL: expected U-%08lx\n", + file, line, chars[noutput]); + return false; + } + + noutput++; + } + + if (noutput < nchars) { + printf("%s:%d: FAIL: expected further output\n", file, line); + return false; + } + + printf("%s:%d: pass\n", file, line); + return true; +} + +#define DOTEST(input, ...) do { \ + static const unsigned long chars[] = { __VA_ARGS__, 0 }; \ + ntest++; \ + if (dotest(__FILE__, __LINE__, input, chars, lenof(chars))) \ + npass++; \ + } while (0) + +int main(void) +{ + int ntest = 0, npass = 0; + + DOTEST("\xCE\xBA\xE1\xBD\xB9\xCF\x83\xCE\xBC\xCE\xB5", + 0x03BA, 0x1F79, 0x03C3, 0x03BC, 0x03B5); + + /* First sequence of each length (not counting NUL, which is + * tested anyway by the string-termination handling in every test) */ + DOTEST("\xC2\x80", 0x0080); + DOTEST("\xE0\xA0\x80", 0x0800); + DOTEST("\xF0\x90\x80\x80", 0x00010000); + DOTEST("\xF8\x88\x80\x80\x80", 0xFFFD); /* would be 0x00200000 */ + DOTEST("\xFC\x84\x80\x80\x80\x80", 0xFFFD); /* would be 0x04000000 */ + + /* Last sequence of each length */ + DOTEST("\x7F", 0x007F); + DOTEST("\xDF\xBF", 0x07FF); + DOTEST("\xEF\xBF\xBF", 0xFFFF); + DOTEST("\xF7\xBF\xBF\xBF", 0xFFFD); /* would be 0x001FFFFF */ + DOTEST("\xFB\xBF\xBF\xBF\xBF", 0xFFFD); /* would be 0x03FFFFFF */ + DOTEST("\xFD\xBF\xBF\xBF\xBF\xBF", 0xFFFD); /* would be 0x7FFFFFFF */ + + /* Endpoints of the surrogate range */ + DOTEST("\xED\x9F\xBF", 0xD7FF); + DOTEST("\xED\xA0\x00", 0xFFFD); /* would be 0xD800 */ + DOTEST("\xED\xBF\xBF", 0xFFFD); /* would be 0xDFFF */ + DOTEST("\xEE\x80\x80", 0xE000); + + /* REPLACEMENT CHARACTER itself */ + DOTEST("\xEF\xBF\xBD", 0xFFFD); + + /* Endpoints of the legal Unicode range */ + DOTEST("\xF4\x8F\xBF\xBF", 0x0010FFFF); + DOTEST("\xF4\x90\x80\x80", 0xFFFD); /* would be 0x00110000 */ + + /* Spurious continuation bytes, each shown as a separate failure */ + DOTEST("\x80 \x81\x82 \xBD\xBE\xBF", + 0xFFFD, 0x0020, 0xFFFD, 0xFFFD, 0x0020, 0xFFFD, 0xFFFD, 0xFFFD); + + /* Truncated sequences, each shown as just one failure */ + DOTEST("\xC2\xE0\xA0\xF0\x90\x80\xF8\x88\x80\x80\xFC\x84\x80\x80\x80", + 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD); + DOTEST("\xC2 \xE0\xA0 \xF0\x90\x80 \xF8\x88\x80\x80 \xFC\x84\x80\x80\x80", + 0xFFFD, 0x0020, 0xFFFD, 0x0020, 0xFFFD, 0x0020, 0xFFFD, 0x0020, + 0xFFFD); + + /* Illegal bytes */ + DOTEST("\xFE\xFF", 0xFFFD, 0xFFFD); + + /* Overlong sequences */ + DOTEST("\xC1\xBF", 0xFFFD); + DOTEST("\xE0\x9F\xBF", 0xFFFD); + DOTEST("\xF0\x8F\xBF\xBF", 0xFFFD); + DOTEST("\xF8\x87\xBF\xBF\xBF", 0xFFFD); + DOTEST("\xFC\x83\xBF\xBF\xBF\xBF", 0xFFFD); + + DOTEST("\xC0\x80", 0xFFFD); + DOTEST("\xE0\x80\x80", 0xFFFD); + DOTEST("\xF0\x80\x80\x80", 0xFFFD); + DOTEST("\xF8\x80\x80\x80\x80", 0xFFFD); + DOTEST("\xFC\x80\x80\x80\x80\x80", 0xFFFD); + + printf("%d tests %d passed", ntest, npass); + if (npass < ntest) { + printf(" %d FAILED\n", ntest-npass); + return 1; + } else { + printf("\n"); + return 0; + } +} +#endif diff --git a/utils/decode_utf8_to_wchar.c b/utils/decode_utf8_to_wchar.c new file mode 100644 index 00000000..97a83218 --- /dev/null +++ b/utils/decode_utf8_to_wchar.c @@ -0,0 +1,20 @@ +/* + * Decode a single UTF-8 character to the platform's local wchar_t. + */ + +#include "putty.h" +#include "misc.h" + +size_t decode_utf8_to_wchar(const char **utf8, wchar_t *out) +{ + size_t outlen = 0; + unsigned wc = decode_utf8(utf8); + if (sizeof(wchar_t) > 2 || wc < 0x10000) { + out[outlen++] = wc; + } else { + unsigned wcoff = wc - 0x10000; + out[outlen++] = 0xD800 | (0x3FF & (wcoff >> 10)); + out[outlen++] = 0xDC00 | (0x3FF & wcoff); + } + return outlen; +} -- cgit v1.2.3 From cf41bc0c624eca01d5a710bee46f63e631b61aae Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 12 Mar 2022 18:47:27 +0000 Subject: Unix mb_to_wc: add missing bounds checks. Checking various implementations of these functions against each other, I noticed by eyeball review that some of the special cases in mb_to_wc() never check the buffer limit at all. Yikes! Fortunately, I think there's no vulnerability, because these special cases are ones that write out at most one wide char per multibyte char, and at all the call sites (including dup_mb_to_wc) we allocate that much even for the first attempt. The only exception to that is the call in key_event() in unix/window.c, which uses a fixed-size output buffer, but its input will always be the data generated by an X keystroke event. So that one can only overrun the buffer if an X key event manages to translate into more than 32 wide characters of text - and even if that does come up in some exotic edge case, it will at least not be happening under _enemy_ control. --- unix/unicode.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/unix/unicode.c b/unix/unicode.c index c1d76a42..4eaa45f4 100644 --- a/unix/unicode.c +++ b/unix/unicode.c @@ -31,6 +31,8 @@ int mb_to_wc(int codepage, int flags, const char *mbstr, int mblen, memset(&state, 0, sizeof state); while (mblen > 0) { + if (n >= wclen) + return n; size_t i = mbrtowc(wcstr+n, mbstr, (size_t)mblen, &state); if (i == (size_t)-1 || i == (size_t)-2) break; @@ -44,6 +46,8 @@ int mb_to_wc(int codepage, int flags, const char *mbstr, int mblen, int n = 0; while (mblen > 0) { + if (n >= wclen) + return n; wcstr[n] = 0xD800 | (mbstr[0] & 0xFF); n++; mbstr++; -- cgit v1.2.3 From fe00a2928c5f490be2d8f70a03af5529dfbaad7f Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 11 Mar 2022 19:11:35 +0000 Subject: Windows: diagnose failure to create the terminal window. It only fails in very unusual circumstances, but that's all the more reason to give a useful report when it does! --- windows/window.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/windows/window.c b/windows/window.c index c1cd0c4c..39a7ca34 100644 --- a/windows/window.c +++ b/windows/window.c @@ -575,6 +575,10 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) wgs.term_hwnd = CreateWindowExW( exwinmode, uappname, uappname, winmode, CW_USEDEFAULT, CW_USEDEFAULT, guess_width, guess_height, NULL, NULL, inst, NULL); + if (!wgs.term_hwnd) { + modalfatalbox("Unable to create terminal window: %s", + win_strerror(GetLastError())); + } memset(&dpi_info, 0, sizeof(struct _dpi_info)); init_dpi_info(); sfree(uappname); -- cgit v1.2.3 From 901667280ae2708df3a73618f85a03af15a8dcd6 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 12 Mar 2022 14:33:01 +0000 Subject: Windows: initialise window_name and icon_name. It turns out that they're still NULL at the first moment that a SetWindowText call tries to read one of them, because they weren't initialised at startup! Apparently SetWindowText notices that it's been passed a null pointer, and does nothing in preference to failing, but it's still not what I _meant_ to do. --- windows/window.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/windows/window.c b/windows/window.c index 39a7ca34..1bc12fd7 100644 --- a/windows/window.c +++ b/windows/window.c @@ -563,6 +563,8 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) if (vt && vt->flags & BACKEND_RESIZE_FORBIDDEN) resize_forbidden = true; wchar_t *uappname = dup_mb_to_wc(DEFAULT_CODEPAGE, 0, appname); + window_name = dup_mb_to_wc(DEFAULT_CODEPAGE, 0, appname); + icon_name = dup_mb_to_wc(DEFAULT_CODEPAGE, 0, appname); if (!conf_get_bool(conf, CONF_scrollbar)) winmode &= ~(WS_VSCROLL); if (conf_get_int(conf, CONF_resize_action) == RESIZE_DISABLED || -- cgit v1.2.3 From 5de1df1b947076f3b9d2b8253a55aa93403c4ee2 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 12 Mar 2022 17:36:54 +0000 Subject: Windows: avoid idempotent window title changes. By testing on a platform slow enough to show the flicker, I happened to notice that if your shell prompt resets the window title every time it's displayed, this was actually resulting in a call to SetWindowText every time, which caused the GUI to actually do work. There's certainly no need for that! We can at least avoid bothering if the new title is identical to the old one. --- windows/window.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/windows/window.c b/windows/window.c index 1bc12fd7..5a350b97 100644 --- a/windows/window.c +++ b/windows/window.c @@ -4758,16 +4758,26 @@ static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam, static void wintw_set_title(TermWin *tw, const char *title, int codepage) { + wchar_t *new_window_name = dup_mb_to_wc(codepage, 0, title); + if (!wcscmp(new_window_name, window_name)) { + sfree(new_window_name); + return; + } sfree(window_name); - window_name = dup_mb_to_wc(codepage, 0, title); + window_name = new_window_name; if (conf_get_bool(conf, CONF_win_name_always) || !IsIconic(wgs.term_hwnd)) SetWindowTextW(wgs.term_hwnd, window_name); } static void wintw_set_icon_title(TermWin *tw, const char *title, int codepage) { + wchar_t *new_icon_name = dup_mb_to_wc(codepage, 0, title); + if (!wcscmp(new_icon_name, icon_name)) { + sfree(new_icon_name); + return; + } sfree(icon_name); - icon_name = dup_mb_to_wc(codepage, 0, title); + icon_name = new_icon_name; if (!conf_get_bool(conf, CONF_win_name_always) && IsIconic(wgs.term_hwnd)) SetWindowTextW(wgs.term_hwnd, icon_name); } -- cgit v1.2.3 From 26dcfcbd4470eef31f61b6a617f3386947c4fad6 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 12 Mar 2022 15:21:18 +0000 Subject: Make init_winver() idempotent. This way, anyone who needs to use the version data can quickly call init_winver to make sure it's been set up, and not waste too much faff redoing the actual work. --- windows/utils/version.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/windows/utils/version.c b/windows/utils/version.c index a53c2ad3..b626710e 100644 --- a/windows/utils/version.c +++ b/windows/utils/version.c @@ -4,6 +4,11 @@ DWORD osMajorVersion, osMinorVersion, osPlatformId; void init_winver(void) { + static bool initialised = false; + if (initialised) + return; + initialised = true; + OSVERSIONINFO osVersion; static HMODULE kernel32_module; DECL_WINDOWS_FUNCTION(static, BOOL, GetVersionExA, (LPOSVERSIONINFO)); -- cgit v1.2.3 From 51f0057b673ac948de497194f3d8a6fb0c8230da Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 12 Mar 2022 17:10:11 +0000 Subject: Add a '#define LEGACY_WINDOWS'. This will be used to wrap some of the stranger workarounds we're keeping in this code base for the purposes of backwards compatibility to seriously old platforms like Win95. --- defs.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/defs.h b/defs.h index 3c30b6b6..354c208f 100644 --- a/defs.h +++ b/defs.h @@ -42,6 +42,8 @@ #define SIZEx "Ix" #define SIZEu "Iu" uintmax_t strtoumax(const char *nptr, char **endptr, int base); +/* Also, define a LEGACY_WINDOWS flag to enable other workarounds */ +#define LEGACY_WINDOWS #else #include /* Because we still support older MSVC libraries which don't recognise the -- cgit v1.2.3 From 83ff08f9db58e516f81e948e8256b50940145d30 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 11 Mar 2022 18:07:50 +0000 Subject: Remove hard dependency on GetFileAttributesEx. This fixes a load-time failure on versions of Windows too old to have that function in kernel32.dll. We use it to determine whether a file was safe to overwrite in the context of PuTTY session logging: if it's safe, we skip the 'do you want to overwrite or append?' dialog box. On earlier Windows you can use FindFirstFile to get a similar effect, so that's what we fall back to. It's not quite the same, though - if you pass a wildcard then it will succeed when you'd rather it had failed. But it's good enough to at least work in normal cases. --- windows/utils/open_for_write_would_lose_data.c | 66 ++++++++++++++++++++------ 1 file changed, 52 insertions(+), 14 deletions(-) diff --git a/windows/utils/open_for_write_would_lose_data.c b/windows/utils/open_for_write_would_lose_data.c index 3891f51e..2aef5c5a 100644 --- a/windows/utils/open_for_write_would_lose_data.c +++ b/windows/utils/open_for_write_would_lose_data.c @@ -4,20 +4,19 @@ #include "putty.h" -bool open_for_write_would_lose_data(const Filename *fn) +/* + * This is slightly fiddly because we want to be backwards-compatible + * with systems too old to have GetFileAttributesEx. The next best + * thing is FindFirstFile, which will return a different data + * structure, but one that also contains the fields we want. (But it + * will behave more unhelpfully - for this application - in the + * presence of wildcards, so we'd prefer to use GFAE if we can.) + */ + +static inline bool open_for_write_would_lose_data_impl( + DWORD dwFileAttributes, DWORD nFileSizeHigh, DWORD nFileSizeLow) { - WIN32_FILE_ATTRIBUTE_DATA attrs; - if (!GetFileAttributesEx(fn->path, GetFileExInfoStandard, &attrs)) { - /* - * Generally, if we don't identify a specific reason why we - * should return true from this function, we return false, and - * let the subsequent attempt to open the file for real give a - * more useful error message. - */ - return false; - } - if (attrs.dwFileAttributes & (FILE_ATTRIBUTE_DEVICE | - FILE_ATTRIBUTE_DIRECTORY)) { + if (dwFileAttributes & (FILE_ATTRIBUTE_DEVICE|FILE_ATTRIBUTE_DIRECTORY)) { /* * File is something other than an ordinary disk file, so * opening it for writing will not cause truncation. (It may @@ -25,7 +24,7 @@ bool open_for_write_would_lose_data(const Filename *fn) */ return false; } - if (attrs.nFileSizeHigh == 0 && attrs.nFileSizeLow == 0) { + if (nFileSizeHigh == 0 && nFileSizeLow == 0) { /* * File is zero-length (or may be a named pipe, which * dwFileAttributes can't tell apart from a regular file), so @@ -36,3 +35,42 @@ bool open_for_write_would_lose_data(const Filename *fn) } return true; } + +bool open_for_write_would_lose_data(const Filename *fn) +{ + static HMODULE kernel32_module; + DECL_WINDOWS_FUNCTION(static, BOOL, GetFileAttributesExA, + (LPCSTR, GET_FILEEX_INFO_LEVELS, LPVOID)); + + if (!kernel32_module) { + kernel32_module = load_system32_dll("kernel32.dll"); + GET_WINDOWS_FUNCTION(kernel32_module, GetFileAttributesExA); + } + + if (p_GetFileAttributesExA) { + WIN32_FILE_ATTRIBUTE_DATA attrs; + if (!p_GetFileAttributesExA(fn->path, GetFileExInfoStandard, &attrs)) { + /* + * Generally, if we don't identify a specific reason why we + * should return true from this function, we return false, and + * let the subsequent attempt to open the file for real give a + * more useful error message. + */ + return false; + } + return open_for_write_would_lose_data_impl( + attrs.dwFileAttributes, attrs.nFileSizeHigh, attrs.nFileSizeLow); + } else { + WIN32_FIND_DATA fd; + HANDLE h = FindFirstFile(fn->path, &fd); + if (h == INVALID_HANDLE_VALUE) { + /* + * As above, if we can't find the file at all, return false. + */ + return false; + } + CloseHandle(h); + return open_for_write_would_lose_data_impl( + fd.dwFileAttributes, fd.nFileSizeHigh, fd.nFileSizeLow); + } +} -- cgit v1.2.3 From 01c007121aa3baec3e02842c03171ec9a99882ae Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 11 Mar 2022 18:32:26 +0000 Subject: Remove hard dependencies on multi-monitor functions. These are more functions that don't exist on all our supported legacy versions of Windows, so we need to follow the same policy as everywhere else, by trying to acquire them at run time and having a fallback if they aren't available. --- windows/window.c | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/windows/window.c b/windows/window.c index 5a350b97..e8b99b93 100644 --- a/windows/window.c +++ b/windows/window.c @@ -197,6 +197,9 @@ bool tried_pal = false; COLORREF colorref_modifier = 0; enum MONITOR_DPI_TYPE { MDT_EFFECTIVE_DPI, MDT_ANGULAR_DPI, MDT_RAW_DPI, MDT_DEFAULT }; +DECL_WINDOWS_FUNCTION(static, BOOL, GetMonitorInfoA, (HMONITOR, LPMONITORINFO)); +DECL_WINDOWS_FUNCTION(static, HMONITOR, MonitorFromPoint, (POINT, DWORD)); +DECL_WINDOWS_FUNCTION(static, HMONITOR, MonitorFromWindow, (HWND, DWORD)); DECL_WINDOWS_FUNCTION(static, HRESULT, GetDpiForMonitor, (HMONITOR hmonitor, enum MONITOR_DPI_TYPE dpiType, UINT *dpiX, UINT *dpiY)); DECL_WINDOWS_FUNCTION(static, HRESULT, GetSystemMetricsForDpi, (int nIndex, UINT dpi)); DECL_WINDOWS_FUNCTION(static, HRESULT, AdjustWindowRectExForDpi, (LPRECT lpRect, DWORD dwStyle, BOOL bMenu, DWORD dwExStyle, UINT dpi)); @@ -1293,9 +1296,9 @@ static int get_font_width(HDC hdc, const TEXTMETRIC *tm) static void init_dpi_info(void) { if (dpi_info.cur_dpi.x == 0 || dpi_info.cur_dpi.y == 0) { - if (p_GetDpiForMonitor) { + if (p_GetDpiForMonitor && p_MonitorFromWindow) { UINT dpiX, dpiY; - HMONITOR currentMonitor = MonitorFromWindow( + HMONITOR currentMonitor = p_MonitorFromWindow( wgs.term_hwnd, MONITOR_DEFAULTTOPRIMARY); if (p_GetDpiForMonitor(currentMonitor, MDT_EFFECTIVE_DPI, &dpiX, &dpiY) == S_OK) { @@ -2618,27 +2621,26 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, GetCursorPos(&pt); #ifndef NO_MULTIMON - { + if (p_GetMonitorInfoA && p_MonitorFromPoint) { HMONITOR mon; MONITORINFO mi; - mon = MonitorFromPoint(pt, MONITOR_DEFAULTTONULL); + mon = p_MonitorFromPoint(pt, MONITOR_DEFAULTTONULL); if (mon != NULL) { mi.cbSize = sizeof(MONITORINFO); - GetMonitorInfo(mon, &mi); + p_GetMonitorInfoA(mon, &mi); if (mi.rcMonitor.left == pt.x && mi.rcMonitor.top == pt.y) { mouse_on_hotspot = true; } } - } -#else + } else +#endif if (pt.x == 0 && pt.y == 0) { mouse_on_hotspot = true; } -#endif if (is_full_screen() && press && button == MBT_LEFT && mouse_on_hotspot) { SendMessage(hwnd, WM_SYSCOMMAND, SC_MOUSEMENU, @@ -4075,6 +4077,9 @@ static void init_winfuncs(void) GET_WINDOWS_FUNCTION(user32_module, FlashWindowEx); GET_WINDOWS_FUNCTION(user32_module, ToUnicodeEx); GET_WINDOWS_FUNCTION_PP(winmm_module, PlaySound); + GET_WINDOWS_FUNCTION_NO_TYPECHECK(user32_module, GetMonitorInfoA); + GET_WINDOWS_FUNCTION_NO_TYPECHECK(user32_module, MonitorFromPoint); + GET_WINDOWS_FUNCTION_NO_TYPECHECK(user32_module, MonitorFromWindow); GET_WINDOWS_FUNCTION_NO_TYPECHECK(shcore_module, GetDpiForMonitor); GET_WINDOWS_FUNCTION_NO_TYPECHECK(user32_module, GetSystemMetricsForDpi); GET_WINDOWS_FUNCTION_NO_TYPECHECK(user32_module, AdjustWindowRectExForDpi); @@ -5685,23 +5690,24 @@ static bool is_full_screen() static bool get_fullscreen_rect(RECT * ss) { #if defined(MONITOR_DEFAULTTONEAREST) && !defined(NO_MULTIMON) + if (p_GetMonitorInfoA && p_MonitorFromWindow) { HMONITOR mon; MONITORINFO mi; - mon = MonitorFromWindow(wgs.term_hwnd, MONITOR_DEFAULTTONEAREST); + mon = p_MonitorFromWindow(wgs.term_hwnd, MONITOR_DEFAULTTONEAREST); mi.cbSize = sizeof(mi); - GetMonitorInfo(mon, &mi); + p_GetMonitorInfoA(mon, &mi); /* structure copy */ *ss = mi.rcMonitor; return true; -#else + } +#endif /* 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 } -- cgit v1.2.3 From f500d24a95d1eac73cdde701b4849bf6a936e291 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 12 Mar 2022 06:43:59 +0000 Subject: Windows: runtime switch between Unicode and ANSI windows. Turns out that PuTTY hasn't run successfully on legacy Windows since 0.66, in spite of an ongoing intention to keep it working. Among the reasons for this is that CreateWindowExW simply fails with ERROR_CALL_NOT_IMPLEMENTED: apparently Win95 and its ilk just didn't have fully-Unicode windows as an option. Fixed by resurrecting the previous code from the git history (in particular, code removed by commit 67e5ceb9a8e6bc2 was useful), and including it as a runtime alternative. One subtlety was that I found I had to name the A and W window classes differently (by appending ".ansi" to the A one): apparently they occupy the same namespace even though the names are in different character sets, so if you somehow manage to register both classes, they'll collide with each other without that tweak. --- windows/window.c | 147 +++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 116 insertions(+), 31 deletions(-) diff --git a/windows/window.c b/windows/window.c index e8b99b93..c81755f2 100644 --- a/windows/window.c +++ b/windows/window.c @@ -450,6 +450,72 @@ static void close_session(void *ignored_context) } } +/* + * Some machinery to deal with switching the window type between ANSI + * and Unicode. We prefer Unicode, but some PuTTY builds still try to + * run on machines so old that they don't support that mode. So we're + * prepared to fall back to an ANSI window if we have to. For this + * purpose, we swap out a few Windows API functions, and wrap + * SetWindowText so that if we're not in Unicode mode we first convert + * the wide string we're given. + */ +static bool unicode_window; +static BOOL (WINAPI *sw_PeekMessage)(LPMSG, HWND, UINT, UINT, UINT); +static LRESULT (WINAPI *sw_DispatchMessage)(const MSG *); +static LRESULT (WINAPI *sw_DefWindowProc)(HWND, UINT, WPARAM, LPARAM); +static void sw_SetWindowText(HWND hwnd, wchar_t *text) +{ + if (unicode_window) { + SetWindowTextW(hwnd, text); + } else { + char *mb = dup_wc_to_mb(DEFAULT_CODEPAGE, 0, text, "?", &ucsdata); + SetWindowTextA(hwnd, mb); + sfree(mb); + } +} + +static HINSTANCE hprev; + +/* + * Also, registering window classes has to be done in a fiddly way. + */ +#define SETUP_WNDCLASS(wndclass, classname) do { \ + wndclass.style = 0; \ + wndclass.lpfnWndProc = WndProc; \ + wndclass.cbClsExtra = 0; \ + wndclass.cbWndExtra = 0; \ + wndclass.hInstance = hinst; \ + wndclass.hIcon = LoadIcon(hinst, MAKEINTRESOURCE(IDI_MAINICON)); \ + wndclass.hCursor = LoadCursor(NULL, IDC_IBEAM); \ + wndclass.hbrBackground = NULL; \ + wndclass.lpszMenuName = NULL; \ + wndclass.lpszClassName = classname; \ + } while (0) +wchar_t *terminal_window_class_w(void) +{ + static wchar_t *classname = NULL; + if (!classname) + classname = dup_mb_to_wc(DEFAULT_CODEPAGE, 0, appname); + if (!hprev) { + WNDCLASSW wndclassw; + SETUP_WNDCLASS(wndclassw, classname); + RegisterClassW(&wndclassw); + } + return classname; +} +char *terminal_window_class_a(void) +{ + static char *classname = NULL; + if (!classname) + classname = dupcat(appname, ".ansi"); + if (!hprev) { + WNDCLASSA wndclassa; + SETUP_WNDCLASS(wndclassa, classname); + RegisterClassA(&wndclassa); + } + return classname; +} + const unsigned cmdline_tooltype = TOOLTYPE_HOST_ARG | TOOLTYPE_PORT_ARG | @@ -466,6 +532,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) dll_hijacking_protection(); hinst = inst; + hprev = prev; sk_init(); @@ -514,23 +581,6 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) */ gui_term_process_cmdline(conf, cmdline); - if (!prev) { - WNDCLASSW wndclass; - - 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 = dup_mb_to_wc(DEFAULT_CODEPAGE, 0, appname); - - RegisterClassW(&wndclass); - } - memset(&ucsdata, 0, sizeof(ucsdata)); conf_cache_data(); @@ -577,9 +627,38 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) exwinmode |= WS_EX_TOPMOST; if (conf_get_bool(conf, CONF_sunken_edge)) exwinmode |= WS_EX_CLIENTEDGE; + +#ifdef TEST_ANSI_WINDOW + /* For developer testing of ANSI window support, pretend + * CreateWindowExW failed */ + wgs.term_hwnd = NULL; + SetLastError(ERROR_CALL_NOT_IMPLEMENTED); +#else + unicode_window = true; + sw_PeekMessage = PeekMessageW; + sw_DispatchMessage = DispatchMessageW; + sw_DefWindowProc = DefWindowProcW; wgs.term_hwnd = CreateWindowExW( - exwinmode, uappname, uappname, winmode, CW_USEDEFAULT, - CW_USEDEFAULT, guess_width, guess_height, NULL, NULL, inst, NULL); + exwinmode, terminal_window_class_w(), uappname, + winmode, CW_USEDEFAULT, CW_USEDEFAULT, + guess_width, guess_height, NULL, NULL, inst, NULL); +#endif + +#if defined LEGACY_WINDOWS || defined TEST_ANSI_WINDOW + if (!wgs.term_hwnd && GetLastError() == ERROR_CALL_NOT_IMPLEMENTED) { + /* Fall back to an ANSI window, swapping in all the ANSI + * window message handling functions */ + unicode_window = false; + sw_PeekMessage = PeekMessageA; + sw_DispatchMessage = DispatchMessageA; + sw_DefWindowProc = DefWindowProcA; + wgs.term_hwnd = CreateWindowExA( + exwinmode, terminal_window_class_a(), appname, + winmode, CW_USEDEFAULT, CW_USEDEFAULT, + guess_width, guess_height, NULL, NULL, inst, NULL); + } +#endif + if (!wgs.term_hwnd) { modalfatalbox("Unable to create terminal window: %s", win_strerror(GetLastError())); @@ -776,13 +855,13 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) handle_wait_activate(hwl, n - WAIT_OBJECT_0); handle_wait_list_free(hwl); - while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) { + while (sw_PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { if (msg.message == WM_QUIT) goto finished; /* two-level break */ HWND logbox = event_log_window(); if (!(IsWindow(logbox) && IsDialogMessage(logbox, &msg))) - DispatchMessageW(&msg); + sw_DispatchMessage(&msg); /* * WM_NETEVENT messages seem to jump ahead of others in @@ -2202,7 +2281,8 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, * within an interactive scrollbar-drag can be handled * differently. */ in_scrollbar_loop = true; - LRESULT result = DefWindowProcW(hwnd, message, wParam, lParam); + LRESULT result = sw_DefWindowProc( + hwnd, message, wParam, lParam); in_scrollbar_loop = false; return result; } @@ -2996,11 +3076,11 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, term, r.right - r.left, r.bottom - r.top); } if (wParam == SIZE_MINIMIZED) - SetWindowTextW(hwnd, - conf_get_bool(conf, CONF_win_name_always) ? - window_name : icon_name); + sw_SetWindowText(hwnd, + conf_get_bool(conf, CONF_win_name_always) ? + window_name : icon_name); if (wParam == SIZE_RESTORED || wParam == SIZE_MAXIMIZED) - SetWindowTextW(hwnd, window_name); + sw_SetWindowText(hwnd, window_name); if (wParam == SIZE_RESTORED) { processed_resize = false; clear_full_screen(); @@ -3222,7 +3302,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, } else { len = TranslateKey(message, wParam, lParam, buf); if (len == -1) - return DefWindowProcW(hwnd, message, wParam, lParam); + return sw_DefWindowProc(hwnd, message, wParam, lParam); if (len != 0) { /* @@ -3320,7 +3400,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, * post the things to us as part of a macro manoeuvre, * we're ready to cope. */ - { + if (unicode_window) { static wchar_t pending_surrogate = 0; wchar_t c = wParam; @@ -3334,6 +3414,11 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, } else if (!IS_SURROGATE(c)) { term_keyinputw(term, &c, 1); } + } else { + char c = (unsigned char)wParam; + term_seen_key_event(term); + if (ldisc) + term_keyinput(term, CP_ACP, &c, 1); } return 0; case WM_SYSCOLORCHANGE: @@ -3409,7 +3494,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, * Any messages we don't process completely above are passed through to * DefWindowProc() for default processing. */ - return DefWindowProcW(hwnd, message, wParam, lParam); + return sw_DefWindowProc(hwnd, message, wParam, lParam); } /* @@ -4771,7 +4856,7 @@ static void wintw_set_title(TermWin *tw, const char *title, int codepage) sfree(window_name); window_name = new_window_name; if (conf_get_bool(conf, CONF_win_name_always) || !IsIconic(wgs.term_hwnd)) - SetWindowTextW(wgs.term_hwnd, window_name); + sw_SetWindowText(wgs.term_hwnd, window_name); } static void wintw_set_icon_title(TermWin *tw, const char *title, int codepage) @@ -4784,7 +4869,7 @@ static void wintw_set_icon_title(TermWin *tw, const char *title, int codepage) sfree(icon_name); icon_name = new_icon_name; if (!conf_get_bool(conf, CONF_win_name_always) && IsIconic(wgs.term_hwnd)) - SetWindowTextW(wgs.term_hwnd, icon_name); + sw_SetWindowText(wgs.term_hwnd, icon_name); } static void wintw_set_scrollbar(TermWin *tw, int total, int start, int page) -- cgit v1.2.3 From a2b376af96072d6741caeb51b5f55fd05fd37157 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 12 Mar 2022 15:02:12 +0000 Subject: Windows Pageant: turn 'has_security' into a global function. Now it can be called from places other than Pageant's WinMain(). In particular, the attempt to make a security descriptor in lock_interprocess_mutex() is gated on it. In return, however, I've tightened up the semantics. In normal PuTTY builds that aren't trying to support pre-NT systems, the function *unconditionally* returns true, on the grounds that we don't expect to target any system that doesn't support the security APIs, and if someone manages to contrive one anyway - or, more likely, if we some day introduce a bug in our loading of the security API functions - then this safety catch should make Pageant less likely to accidentally fall back to 'never mind, just run in insecure mode'. --- windows/pageant.c | 70 +++++++++++++++++--------------------- windows/platform.h | 1 + windows/utils/interprocess_mutex.c | 4 +-- windows/utils/security.c | 14 ++++++++ 4 files changed, 49 insertions(+), 40 deletions(-) diff --git a/windows/pageant.c b/windows/pageant.c index 0e3868a0..a08659e5 100644 --- a/windows/pageant.c +++ b/windows/pageant.c @@ -91,8 +91,6 @@ void modalfatalbox(const char *fmt, ...) exit(1); } -static bool has_security; - struct PassphraseProcStruct { bool modal; const char *help_topic; @@ -999,7 +997,7 @@ static char *answer_filemapping_message(const char *mapname) debug("maphandle = %p\n", maphandle); #endif - if (has_security) { + if (should_have_security()) { DWORD retd; if ((expectedsid = get_user_sid()) == NULL) { @@ -1405,14 +1403,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) hinst = inst; - /* - * Determine whether we're an NT system (should have security - * APIs) or a non-NT system (don't do security). - */ - init_winver(); - has_security = (osPlatformId == VER_PLATFORM_WIN32_NT); - - if (has_security) { + if (should_have_security()) { /* * Attempt to get the security API we need. */ @@ -1543,39 +1534,42 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) /* * Set up a named-pipe listener. */ - Plug *pl_plug; wpc->plc.vt = &winpgnt_vtable; wpc->plc.suppress_logging = true; - struct pageant_listen_state *pl = - pageant_listener_new(&pl_plug, &wpc->plc); - char *pipename = agent_named_pipe_name(); - Socket *sock = new_named_pipe_listener(pipename, pl_plug); - if (sk_socket_error(sock)) { - char *err = dupprintf("Unable to open named pipe at %s " - "for SSH agent:\n%s", pipename, - sk_socket_error(sock)); - MessageBox(NULL, err, "Pageant Error", MB_ICONERROR | MB_OK); - return 1; - } - pageant_listener_got_socket(pl, sock); - - /* - * If we've been asked to write out an OpenSSH config file - * pointing at the named pipe, do so. - */ - if (openssh_config_file) { - FILE *fp = fopen(openssh_config_file, "w"); - if (!fp) { - char *err = dupprintf("Unable to write OpenSSH config file " - "to %s", openssh_config_file); + if (should_have_security()) { + Plug *pl_plug; + struct pageant_listen_state *pl = + pageant_listener_new(&pl_plug, &wpc->plc); + char *pipename = agent_named_pipe_name(); + Socket *sock = new_named_pipe_listener(pipename, pl_plug); + if (sk_socket_error(sock)) { + char *err = dupprintf("Unable to open named pipe at %s " + "for SSH agent:\n%s", pipename, + sk_socket_error(sock)); MessageBox(NULL, err, "Pageant Error", MB_ICONERROR | MB_OK); return 1; } - fprintf(fp, "IdentityAgent %s\n", pipename); - fclose(fp); - } + pageant_listener_got_socket(pl, sock); - sfree(pipename); + /* + * If we've been asked to write out an OpenSSH config file + * pointing at the named pipe, do so. + */ + if (openssh_config_file) { + FILE *fp = fopen(openssh_config_file, "w"); + if (!fp) { + char *err = dupprintf("Unable to write OpenSSH config " + "file to %s", openssh_config_file); + MessageBox(NULL, err, "Pageant Error", + MB_ICONERROR | MB_OK); + return 1; + } + fprintf(fp, "IdentityAgent %s\n", pipename); + fclose(fp); + } + + sfree(pipename); + } /* * Set up the window class for the hidden window that receives diff --git a/windows/platform.h b/windows/platform.h index d15e1a68..5264e8f0 100644 --- a/windows/platform.h +++ b/windows/platform.h @@ -574,6 +574,7 @@ void dll_hijacking_protection(void); const char *get_system_dir(void); HMODULE load_system32_dll(const char *libname); const char *win_strerror(int error); +bool should_have_security(void); void restrict_process_acl(void); bool restricted_acl(void); void escape_registry_key(const char *in, strbuf *out); diff --git a/windows/utils/interprocess_mutex.c b/windows/utils/interprocess_mutex.c index a22ec820..8612a298 100644 --- a/windows/utils/interprocess_mutex.c +++ b/windows/utils/interprocess_mutex.c @@ -13,8 +13,8 @@ HANDLE lock_interprocess_mutex(const char *mutexname, char **error) PACL acl = NULL; HANDLE mutex = NULL; - if (!make_private_security_descriptor(MUTEX_ALL_ACCESS, - &psd, &acl, error)) + if (should_have_security() && !make_private_security_descriptor( + MUTEX_ALL_ACCESS, &psd, &acl, error)) goto out; SECURITY_ATTRIBUTES sa; diff --git a/windows/utils/security.c b/windows/utils/security.c index 2d899b83..1a4770b6 100644 --- a/windows/utils/security.c +++ b/windows/utils/security.c @@ -20,6 +20,20 @@ DEF_WINDOWS_FUNCTION(GetSecurityInfo); DEF_WINDOWS_FUNCTION(SetSecurityInfo); DEF_WINDOWS_FUNCTION(SetEntriesInAclA); +bool should_have_security(void) +{ +#ifdef LEGACY_WINDOWS + /* Legacy pre-NT platforms are not expected to have any of these APIs */ + init_winver(); + return (osPlatformId == VER_PLATFORM_WIN32_NT); +#else + /* In the up-to-date PuTTY builds which do not support those + * platforms, unconditionally return true, to minimise the risk of + * compiling out security checks. */ + return true; +#endif +} + bool got_advapi(void) { static bool attempted = false; -- cgit v1.2.3 From 3f76a86c13a315a8b657642987935d72f86707c6 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 12 Mar 2022 15:18:38 +0000 Subject: Windows Pageant: deal with PeekMessageW failing on legacy Windows. This makes Pageant run on Win95 again. Previously (after fixing the startup-time failures due to missing security APIs) it would go into an uninterruptible CPU-consuming spin in the message loop when every attempt to retrieve its messages failed because PeekMessageW doesn't work at all on the 95 series. --- windows/pageant.c | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/windows/pageant.c b/windows/pageant.c index a08659e5..5ba16801 100644 --- a/windows/pageant.c +++ b/windows/pageant.c @@ -1382,6 +1382,23 @@ static NORETURN void opt_error(const char *fmt, ...) exit(1); } +#ifdef LEGACY_WINDOWS +BOOL sw_PeekMessage(LPMSG msg, HWND hwnd, UINT min, UINT max, UINT remove) +{ + static bool unicode_unavailable = false; + if (!unicode_unavailable) { + BOOL ret = PeekMessageW(msg, hwnd, min, max, remove); + if (!ret && GetLastError() == ERROR_CALL_NOT_IMPLEMENTED) + unicode_unavailable = true; /* don't try again */ + else + return ret; + } + return PeekMessageA(msg, hwnd, min, max, remove); +} +#else +#define sw_PeekMessage PeekMessageW +#endif + int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) { MSG msg; @@ -1732,7 +1749,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) handle_wait_activate(hwl, n - WAIT_OBJECT_0); handle_wait_list_free(hwl); - while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) { + while (sw_PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { if (msg.message == WM_QUIT) goto finished; /* two-level break */ -- cgit v1.2.3 From f23a84cf7c89cc4918976178c19abb654f76a7ef Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 12 Mar 2022 16:16:01 +0000 Subject: windows/unicode.c: manually speak UTF-8. This is another fallback needed on Win95, where the Win32 API functions to convert between multibyte and wide strings exist, but they haven't heard of the UTF-8 code page. PuTTY can't really do without that these days. (In particular, if a server sends a remote window-title escape sequence while the terminal is in UTF-8 mode, then _something_ needs to translate the UTF-8 data into Unicode for Windows to reconvert into the character set used in window titles.) This is a weird enough thing to be doing that I've put it under the new #ifdef LEGACY_WINDOWS, so behaviour in the standard builds should be unchanged. --- windows/unicode.c | 85 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 81 insertions(+), 4 deletions(-) diff --git a/windows/unicode.c b/windows/unicode.c index 9ffff5e9..943c3c2d 100644 --- a/windows/unicode.c +++ b/windows/unicode.c @@ -1195,16 +1195,93 @@ int wc_to_mb(int codepage, int flags, const wchar_t *wcstr, int wclen, } return p - mbstr; } else { - int defused; - return WideCharToMultiByte(codepage, flags, wcstr, wclen, - mbstr, mblen, defchr, &defused); + int defused, ret; + ret = WideCharToMultiByte(codepage, flags, wcstr, wclen, + mbstr, mblen, defchr, &defused); + if (ret) + return ret; + +#ifdef LEGACY_WINDOWS + /* + * Fallback for legacy platforms too old to support UTF-8: if + * the codepage is UTF-8, we can do the translation ourselves. + */ + if (codepage == CP_UTF8 && mblen > 0 && wclen > 0) { + size_t remaining = mblen; + char *p = mbstr; + + while (wclen > 0) { + unsigned long wc = (wclen--, *wcstr++); + if (wclen > 0 && IS_SURROGATE_PAIR(wc, *wcstr)) { + wc = FROM_SURROGATES(wc, *wcstr); + wclen--, wcstr++; + } + + char utfbuf[6]; + size_t utflen = encode_utf8(utfbuf, wc); + if (utflen <= remaining) { + memcpy(p, utfbuf, utflen); + p += utflen; + remaining -= utflen; + } else { + return p - mbstr; + } + } + + return p - mbstr; + } +#endif + + /* No other fallbacks are available */ + return 0; } } int mb_to_wc(int codepage, int flags, const char *mbstr, int mblen, wchar_t *wcstr, int wclen) { - return MultiByteToWideChar(codepage, flags, mbstr, mblen, wcstr, wclen); + int ret = MultiByteToWideChar(codepage, flags, mbstr, mblen, wcstr, wclen); + if (ret) + return ret; + +#ifdef LEGACY_WINDOWS + /* + * Fallback for legacy platforms too old to support UTF-8: if the + * codepage is UTF-8, we can do the translation ourselves. + */ + if (codepage == CP_UTF8 && mblen > 0 && wclen > 0) { + size_t remaining = wclen; + wchar_t *p = wcstr; + + while (mblen > 0) { + char utfbuf[7]; + int thissize = mblen < 6 ? mblen : 6; + memcpy(utfbuf, mbstr, thissize); + utfbuf[thissize] = '\0'; + + const char *utfptr = utfbuf; + wchar_t wcbuf[2]; + size_t nwc = decode_utf8_to_wchar(&utfptr, wcbuf); + + for (size_t i = 0; i < nwc; i++) { + if (remaining > 0) { + remaining--; + *p++ = wcbuf[i]; + } else { + return p - wcstr; + } + } + + mbstr += (utfptr - utfbuf); + mblen -= (utfptr - utfbuf); + } + + return p - wcstr; + } +#endif + + /* No other fallbacks are available */ + return 0; } bool is_dbcs_leadbyte(int codepage, char byte) -- cgit v1.2.3 From 5d58931b51ff1223f1b2208956538cfadd702c0b Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 12 Mar 2022 20:17:30 +0000 Subject: Fix trust status when Interactor returns a seat. While testing the unrelated pile of commits just past, I accidentally started a Cygwin saved session I hadn't run in ages which used the old Telnet-based cygtermd as a local proxy command, and found that it presented the Cygwin prompt with a trust sigil. Oops! It turns out that this is because interactor_return_seat does two things that can change the real seat's trust status, and it does them in the wrong order: it defaults the status back to trusted (as if the seat was brand new, because that's how they start out), and it calls tempseat_flush which may have buffered a trust-status reset while the seat was borrowed. The former should not override the latter! --- proxy/interactor.c | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/proxy/interactor.c b/proxy/interactor.c index 958e5a98..d069d226 100644 --- a/proxy/interactor.c +++ b/proxy/interactor.c @@ -47,8 +47,18 @@ void interactor_return_seat(Interactor *itr) if (!is_tempseat(tempseat)) return; /* no-op */ - tempseat_flush(tempseat); + /* + * We're about to hand this seat back to the parent Interactor to + * do its own thing with. It will typically expect to start in the + * same state as if the seat had never been borrowed, i.e. in the + * starting trust state. + * + * However, this may be overridden by the tempseat_flush call. + */ Seat *realseat = tempseat_get_real(tempseat); + seat_set_trust_status(realseat, true); + + tempseat_flush(tempseat); interactor_set_seat(itr, realseat); tempseat_free(tempseat); @@ -60,14 +70,6 @@ void interactor_return_seat(Interactor *itr) Interactor *itr_top = interactor_toplevel(itr, NULL); if (itr_top->last_to_talk) interactor_announce(itr); - - /* - * We're about to hand this seat back to the parent Interactor to - * do its own thing with. It will typically expect to start in the - * same state as if the seat had never been borrowed, i.e. in the - * starting trust state. - */ - seat_set_trust_status(realseat, true); } InteractionReadySeat interactor_announce(Interactor *itr) -- cgit v1.2.3 From a101444d40c76557e27e9920ffd0768cd17b9940 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 18 Mar 2022 07:11:11 +0000 Subject: New script to draw the icons as SVG. This gets us scalable icons that will go to extremely large sizes without the problems that arise from scaling up the output of mkicon.py, in which outlines become too thin because the script was mostly concerned with trying to squeeze all the desired detail into _tiny_ sizes. The SVG icons are generated by mksvg.py, which is a conversion of the existing mkicon.py. So the SVG files themselves are not committed in this repo; 'make svg' in the icons subdir will generate them. (I haven't decided yet whether this state of affairs should be permanent. Perhaps _having_ generated the SVGs via a similar program to the bitmap icons, we should regard the script as a discardable booster stage and redesignate the SVGs themselves as the source format for future modifications, so that they can be edited in Inkscape or similar rather than by tinkering with Python. On the other hand, perhaps keeping the script will make it easier to keep the icon family consistent, e.g. if changing the style of one of the shared visual components.) My plan is that we should stick with the output of the previous bitmap-generating script for all the _small_ icons, up to and including 48 pixels, because it does a better job at low resolution. (That was really what it was for in the first place: you can think of it as an analogue of a scalable-font hinting system, to tune the scaling for very low res so that all the important features are still visible.) I think probably I want to switch the 128-pixel icons used in the Mac icon file over to being rendered from the SVG (though in this commit I haven't gone that far, not least because I'll also need to prepare a corresponding black and white version). I haven't done extensive research yet to decide where I think the crossover point in between is. --- icons/Makefile | 8 +- icons/mksvg.py | 938 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 945 insertions(+), 1 deletion(-) create mode 100755 icons/mksvg.py diff --git a/icons/Makefile b/icons/Makefile index 71b43874..3e3ea456 100644 --- a/icons/Makefile +++ b/icons/Makefile @@ -13,6 +13,8 @@ PNGS = $(patsubst %.pam,%.png,$(PAMS)) MONOPNGS = $(patsubst %.pam,%.png,$(MONOPAMS)) TRUEPNGS = $(patsubst %.pam,%.png,$(TRUEPAMS)) +SVGS = $(patsubst %,%.svg,$(ICONS)) + ICOS = putty.ico puttygen.ico pscp.ico pageant.ico pageants.ico puttycfg.ico \ puttyins.ico pterm.ico ptermcfg.ico ICNS = PuTTY.icns Pterm.icns @@ -20,11 +22,12 @@ CICONS = xpmputty.c xpmpucfg.c xpmpterm.c xpmptcfg.c base: icos cicons -all: pngs monopngs base icns # truepngs currently disabled by default +all: pngs monopngs base icns svgs # truepngs currently disabled by default pngs: $(PNGS) monopngs: $(MONOPNGS) truepngs: $(TRUEPNGS) +svgs: $(SVGS) icos: $(ICOS) icns: $(ICNS) @@ -46,6 +49,9 @@ $(MONOPAMS): %.pam: mkicon.py $(TRUEPAMS): %.pam: mkicon.py ./mkicon.py -T $(MODE) $(join $(subst -, ,$(subst -true,,$(basename $@))),_icon) $@ +$(SVGS): %.svg: mksvg.py + ./mksvg.py $(patsubst %.svg,%_icon,$@) -o $@ + 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, $^) > $@ diff --git a/icons/mksvg.py b/icons/mksvg.py new file mode 100755 index 00000000..f29ff25b --- /dev/null +++ b/icons/mksvg.py @@ -0,0 +1,938 @@ +#!/usr/bin/env python3 + +import argparse +import itertools +import math +import os +import sys +from fractions import Fraction + +import xml.etree.cElementTree as ET + +# Python code which draws the PuTTY icon components in SVG. + +def makegroup(*objects): + if len(objects) == 1: + return objects[0] + g = ET.Element("g") + for obj in objects: + g.append(obj) + return g + +class Container: + "Empty class for keeping things in." + pass + +class SVGthing(object): + def __init__(self): + self.fillc = "none" + self.strokec = "none" + self.strokewidth = 0 + self.strokebehind = False + self.clipobj = None + self.props = Container() + def fmt_colour(self, rgb): + return "#{0:02x}{1:02x}{2:02x}".format(*rgb) + def fill(self, colour): + self.fillc = self.fmt_colour(colour) + def stroke(self, colour, width=1, behind=False): + self.strokec = self.fmt_colour(colour) + self.strokewidth = width + self.strokebehind = behind + def clip(self, obj): + self.clipobj = obj + def styles(self, elt, styles): + elt.attrib["style"] = ";".join("{}:{}".format(k,v) + for k,v in sorted(styles.items())) + def add_clip_paths(self, container, idents, X, Y): + if self.clipobj: + self.clipobj.identifier = next(idents) + clipelt = self.clipobj.render_thing(X, Y) + clippath = ET.Element("clipPath") + clippath.attrib["id"] = self.clipobj.identifier + clippath.append(clipelt) + container.append(clippath) + return True + return False + def render(self, X, Y, with_styles=True): + elt = self.render_thing(X, Y) + if self.clipobj: + elt.attrib["clip-path"] = "url(#{})".format( + self.clipobj.identifier) + estyles = {"fill": self.fillc} + sstyles = {"stroke": self.strokec} + if self.strokewidth: + sstyles["stroke-width"] = "{:g}".format(self.strokewidth) + sstyles["stroke-linecap"] = "round" + sstyles["stroke-linejoin"] = "round" + if not self.strokebehind: + estyles.update(sstyles) + if with_styles: + self.styles(elt, estyles) + if not self.strokebehind: + return elt + selt = self.render_thing(X, Y) + if with_styles: + self.styles(selt, sstyles) + return makegroup(selt, elt) + def bbox(self): + it = self.bb_iter() + xmin, ymin = xmax, ymax = next(it) + for x, y in it: + xmin = min(x, xmin) + xmax = max(x, xmax) + ymin = min(y, ymin) + ymax = max(y, ymax) + r = self.strokewidth / 2.0 + xmin -= r + ymin -= r + xmax += r + ymax += r + if self.clipobj: + x0, y0, x1, y1 = self.clipobj.bbox() + xmin = max(x0, xmin) + xmax = min(x1, xmax) + ymin = max(y0, ymin) + ymax = min(y1, ymax) + return xmin, ymin, xmax, ymax + +class SVGpath(SVGthing): + def __init__(self, pointlists, closed=True): + super().__init__() + self.pointlists = pointlists + self.closed = closed + def bb_iter(self): + for points in self.pointlists: + for x,y,on in points: + yield x,y + def render_thing(self, X, Y): + pathcmds = [] + + for points in self.pointlists: + while not points[-1][2]: + points = points[1:] + [points[0]] + + piter = iter(points) + + if self.closed: + xp, yp, _ = points[-1] + pathcmds.extend(["M", X+xp, Y-yp]) + else: + xp, yp, on = next(piter) + assert on, "Open paths must start with an on-curve point" + pathcmds.extend(["M", X+xp, Y-yp]) + + for x, y, on in piter: + if isinstance(on, type(())): + assert on[0] == "arc" + _, rx, ry, rotation, large, sweep = on + pathcmds.extend(["a", + rx, ry, rotation, + 1 if large else 0, + 1 if sweep else 0, + x-xp, -(y-yp)]) + elif not on: + x0, y0 = x, y + x1, y1, on = next(piter) + assert not on + x, y, on = next(piter) + assert on + pathcmds.extend(["c", x0-xp, -(y0-yp), + ",", x1-xp, -(y1-yp), + ",", x-xp, -(y-yp)]) + elif x == xp: + pathcmds.extend(["v", -(y-yp)]) + elif x == xp: + pathcmds.extend(["h", x-xp]) + else: + pathcmds.extend(["l", x-xp, -(y-yp)]) + + xp, yp = x, y + + if self.closed: + pathcmds.append("z") + + path = ET.Element("path") + path.attrib["d"] = " ".join(str(cmd) for cmd in pathcmds) + return path + +class SVGrect(SVGthing): + def __init__(self, x0, y0, x1, y1): + super().__init__() + self.points = x0, y0, x1, y1 + def bb_iter(self): + x0, y0, x1, y1 = self.points + return iter([(x0,y0), (x1,y1)]) + def render_thing(self, X, Y): + x0, y0, x1, y1 = self.points + rect = ET.Element("rect") + rect.attrib["x"] = "{:g}".format(min(X+x0,X+x1)) + rect.attrib["y"] = "{:g}".format(min(Y-y0,Y-y1)) + rect.attrib["width"] = "{:g}".format(abs(x0-x1)) + rect.attrib["height"] = "{:g}".format(abs(y0-y1)) + return rect + +class SVGpoly(SVGthing): + def __init__(self, points): + super().__init__() + self.points = points + def bb_iter(self): + return iter(self.points) + def render_thing(self, X, Y): + poly = ET.Element("polygon") + poly.attrib["points"] = " ".join("{:g},{:g}".format(X+x,Y-y) + for x,y in self.points) + return poly + +class SVGgroup(object): + def __init__(self, objects, translations=[]): + translations = translations + ( + [(0,0)] * (len(objects)-len(translations))) + self.contents = list(zip(objects, translations)) + self.props = Container() + def render(self, X, Y): + return makegroup(*[obj.render(X+x, Y-y) + for obj, (x,y) in self.contents]) + def add_clip_paths(self, container, idents, X, Y): + toret = False + for obj, (x,y) in self.contents: + if obj.add_clip_paths(container, idents, X+x, Y-y): + toret = True + return toret + def bbox(self): + it = ((x,y) + obj.bbox() for obj, (x,y) in self.contents) + x, y, xmin, ymin, xmax, ymax = next(it) + xmin = x+xmin + ymin = y+ymin + xmax = x+xmax + ymax = y+ymax + for x, y, x0, y0, x1, y1 in it: + xmin = min(x+x0, xmin) + xmax = max(x+x1, xmax) + ymin = min(y+y0, ymin) + ymax = max(y+y1, ymax) + return (xmin, ymin, xmax, ymax) + +class SVGtranslate(object): + def __init__(self, obj, translation): + self.obj = obj + self.tx, self.ty = translation + def render(self, X, Y): + return self.obj.render(X+self.tx, Y+self.ty) + def add_clip_paths(self, container, idents, X, Y): + return self.obj.add_clip_paths(container, idents, X+self.tx, Y-self.ty) + def bbox(self): + xmin, ymin, xmax, ymax = self.obj.bbox() + return xmin+self.tx, ymin+self.ty, xmax+self.tx, ymax+self.ty + +# Code to actually draw pieces of icon. These don't generally worry +# about positioning within a rectangle; they just draw at a standard +# location, return some useful coordinates, and leave composition +# to other pieces of code. + +def sysbox(size): + # The system box of the computer. + + height = 3.6*size + width = 16.51*size + depth = 2*size + highlight = 1*size + + floppystart = 19*size # measured in half-pixels + floppyend = 29*size # measured in half-pixels + floppybottom = highlight + floppyrheight = 0.7 * size + floppyheight = floppyrheight + if floppyheight < 1: + floppyheight = 1 + floppytop = floppybottom + floppyheight + + background_coords = [ + (0,0), (width,0), (width+depth,depth), + (width+depth,height+depth), (depth,height+depth), (0,height)] + background = SVGpoly(background_coords) + background.fill(greypix(0.75)) + + hl_dark = SVGpoly([ + (highlight,0), (highlight,highlight), (width-highlight,highlight), + (width-highlight,height-highlight), (width+depth,height+depth), + (width+depth,depth), (width,0)]) + hl_dark.fill(greypix(0.5)) + + hl_light = SVGpoly([ + (0,highlight), (highlight,highlight), (highlight,height-highlight), + (width-highlight,height-highlight), (width+depth,height+depth), + (width+depth-highlight,height+depth), (width-highlight,height), + (0,height)]) + hl_light.fill(cW) + + floppy = SVGrect(floppystart/2.0, floppybottom, + floppyend/2.0, floppytop) + floppy.fill(cK) + + outline = SVGpoly(background_coords) + outline.stroke(cK, width=0.5) + + toret = SVGgroup([background, hl_dark, hl_light, floppy, outline]) + toret.props.sysboxheight = height + toret.props.borderthickness = 1 # FIXME + return toret + +def monitor(size): + # The computer's monitor. + + height = 9.5*size + width = 11.5*size + surround = 1*size + botsurround = 2*size + sheight = height - surround - botsurround + swidth = width - 2*surround + depth = 2*size + highlight = surround/2 + shadow = 0.5*size + + background_coords = [ + (0,0), (width,0), (width+depth,depth), + (width+depth,height+depth), (depth,height+depth), (0,height)] + background = SVGpoly(background_coords) + background.fill(greypix(0.75)) + + hl0_dark = SVGpoly([ + (0,0), (highlight,highlight), (width-highlight,highlight), + (width-highlight,height-highlight), (width+depth,height+depth), + (width+depth,depth), (width,0)]) + hl0_dark.fill(greypix(0.5)) + + hl0_light = SVGpoly([ + (0,0), (highlight,highlight), (highlight,height-highlight), + (width-highlight,height-highlight), (width,height), (0,height)]) + hl0_light.fill(greypix(1)) + + hl1_dark = SVGpoly([ + (surround-highlight,botsurround-highlight), (surround,botsurround), + (surround,height-surround), (width-surround,height-surround), + (width-surround+highlight,height-surround+highlight), + (surround-highlight,height-surround+highlight)]) + hl1_dark.fill(greypix(0.5)) + + hl1_light = SVGpoly([ + (surround-highlight,botsurround-highlight), (surround,botsurround), + (width-surround,botsurround), (width-surround,height-surround), + (width-surround+highlight,height-surround+highlight), + (width-surround+highlight,botsurround-highlight)]) + hl1_light.fill(greypix(1)) + + screen = SVGrect(surround, botsurround, width-surround, height-surround) + screen.fill(bluepix(1)) + + screenshadow = SVGpoly([ + (surround,botsurround), (surround+shadow,botsurround), + (surround+shadow,height-surround-shadow), + (width-surround,height-surround-shadow), + (width-surround,height-surround), (surround,height-surround)]) + screenshadow.fill(bluepix(0.5)) + + outline = SVGpoly(background_coords) + outline.stroke(cK, width=0.5) + + toret = SVGgroup([background, hl0_dark, hl0_light, hl1_dark, hl1_light, + screen, screenshadow, outline]) + # Give the centre of the screen (for lightning-bolt positioning purposes) + # as the centre of the _light_ area of the screen, not counting the + # shadow on the top and left. I think that looks very slightly nicer. + sbb = (surround+shadow, botsurround, width-surround, height-surround-shadow) + toret.props.screencentre = ((sbb[0]+sbb[2])/2, (sbb[1]+sbb[3])/2) + return toret + +def computer(size): + # Monitor plus sysbox. + m = monitor(size) + s = sysbox(size) + x = (2+size/(size+1))*size + y = int(s.props.sysboxheight + s.props.borderthickness) + mb = m.bbox() + sb = s.bbox() + xoff = mb[0] - sb[0] + x + yoff = mb[1] - sb[1] + y + toret = SVGgroup([s, m], [(0,0), (xoff,yoff)]) + toret.props.screencentre = (m.props.screencentre[0]+xoff, + m.props.screencentre[1]+yoff) + return toret + +def lightning(size): + # The lightning bolt motif. + + # Compute the right size of a lightning bolt to exactly connect + # the centres of the two screens in the main PuTTY icon. We'll use + # that size of bolt for all the other icons too, for consistency. + iconw = iconh = 32 * size + cbb = computer(size).bbox() + assert cbb[2]-cbb[0] <= iconw and cbb[3]-cbb[1] <= iconh + width, height = iconw-(cbb[2]-cbb[0]), iconh-(cbb[3]-cbb[1]) + + degree = math.pi/180 + + centrethickness = 2*size # top-to-bottom thickness of centre bar + innerangle = 46 * degree # slope of the inner slanting line + outerangle = 39 * degree # slope of the outer one + + innery = (height - centrethickness) / 2 + outery = (height + centrethickness) / 2 + innerx = innery / math.tan(innerangle) + outerx = outery / math.tan(outerangle) + + points = [(innerx, innery), (0,0), (outerx, outery)] + points.extend([(width-x, height-y) for x,y in points]) + + # Fill and stroke the lightning bolt. + # + # Most of the filled-and-stroked objects in these icons are filled + # first, and then stroked with width 0.5, so that the edge of the + # filled area runs down the centre line of the stroke. Put another + # way, half the stroke covers what would have been the filled + # area, and the other half covers the background. This seems like + # the normal way to fill-and-stroke a shape of a given size, and + # SVG makes it easy by allowing us to specify the polygon just + # once with both 'fill' and 'stroke' CSS properties. + # + # But if we did that in this case, then the tips of the lightning + # bolt wouldn't have lightning-colour anywhere near them, because + # the two edges are so close together in angle that the point + # where the strokes would first _not_ overlap would be miles away + # from the logical endpoint. + # + # So, for this one case, we stroke the polygon first at double the + # width, and then fill it on top of that, requiring two copies of + # it in the SVG (though my construction class here hides that + # detail). The effect is that we still get a stroke of visible + # width 0.5, but it's entirely outside the filled area of the + # polygon, so the tips of the yellow interior of the lightning + # bolt are exactly at the logical endpoints. + poly = SVGpoly(points) + poly.fill(cY) + poly.stroke(cK, width=1, behind=True) + poly.props.end1 = (0,0) + poly.props.end2 = (width,height) + return poly + +def document(size): + # The document used in the PSCP/PSFTP icon. + + width = 13*size + height = 16*size + + lineht = 0.875*size + linespc = 1.125*size + nlines = int((height-linespc)/(lineht+linespc)) + height = nlines*(lineht+linespc)+linespc # round this so it fits better + + paper = SVGrect(0, 0, width, height) + paper.fill(cW) + paper.stroke(cK, width=0.5) + + objs = [paper] + + # Now draw lines of text. + for line in range(nlines): + # Decide where this line of text begins. + if line == 0: + start = 4*size + elif line < 5*nlines/7: + start = (line * 4/5) * size + else: + start = 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 = end * size + + liney = (lineht+linespc) * (line+1) + line = SVGrect(start, liney-lineht, end, liney) + line.fill(cK) + objs.append(line) + + return SVGgroup(objs) + +def hat(size): + # The secret-agent hat in the Pageant icon. + + leftend = (0, -6*size) + rightend = (28*size, -12*size) + dx = rightend[0]-leftend[0] + dy = rightend[1]-leftend[1] + tcentre = (leftend[0] + 0.5*dx - 0.3*dy, leftend[1] + 0.5*dy + 0.3*dx) + + hatpoints = [leftend + (True,), + (7.5*size, -6*size, True), + (12*size, 0, True), + (14*size, 3*size, False), + (tcentre[0] - 0.1*dx, tcentre[1] - 0.1*dy, False), + tcentre + (True,)] + for x, y, on in list(reversed(hatpoints))[1:]: + vx, vy = x-tcentre[0], y-tcentre[1] + coeff = float(vx*dx + vy*dy) / float(dx*dx + dy*dy) + rx, ry = x - 2*coeff*dx, y - 2*coeff*dy + hatpoints.append((rx, ry, on)) + + mainhat = SVGpath([hatpoints]) + mainhat.fill(cK) + + band = SVGpoly([ + (leftend[0] - 0.1*dy, leftend[1] + 0.1*dx), + (rightend[0] - 0.1*dy, rightend[1] + 0.1*dx), + (rightend[0] - 0.15*dy, rightend[1] + 0.15*dx), + (leftend[0] - 0.15*dy, leftend[1] + 0.15*dx)]) + band.fill(cW) + band.clip(SVGpath([hatpoints])) + + outline = SVGpath([hatpoints]) + outline.stroke(cK, width=1) + + return SVGgroup([mainhat, band, outline]) + +def key(size): + # The key in the PuTTYgen icon. + + keyheadw = 9.5*size + keyheadh = 12*size + keyholed = 4*size + keyholeoff = 2*size + # Ensure keyheadh and keyshafth have the same parity. + keyshafth = (2*size - (int(keyheadh)&1)) / 2 * 2 + (int(keyheadh)&1) + keyshaftw = 18.5*size + keyheaddetail = [x*size for x in [12,11,8,10,9,8,11,12]] + + squarepix = [] + + keyheadcx = keyshaftw + keyheadw / 2.0 + keyheadcy = keyheadh / 2.0 + keyshafttop = keyheadcy + keyshafth / 2.0 + keyshaftbot = keyheadcy - keyshafth / 2.0 + + keyhead = [(0, keyshafttop, True), (keyshaftw, keyshafttop, True), + (keyshaftw, keyshaftbot, + ("arc", keyheadw/2.0, keyheadh/2.0, 0, True, True)), + (len(keyheaddetail)*size, keyshaftbot, True)] + for i, h in reversed(list(enumerate(keyheaddetail))): + keyhead.append(((i+1)*size, keyheadh-h, True)) + keyhead.append(((i)*size, keyheadh-h, True)) + + keyholecx = keyheadcx + keyholeoff + keyholecy = keyheadcy + keyholer = keyholed / 2.0 + + keyhole = [(keyholecx + keyholer, keyholecy, + ("arc", keyholer, keyholer, 0, False, False)), + (keyholecx - keyholer, keyholecy, + ("arc", keyholer, keyholer, 0, False, False))] + + outline = SVGpath([keyhead, keyhole]) + outline.fill(cy) + outline.stroke(cK, width=0.5) + return outline + +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): + # The spanner in the config box icon. + + # Coordinate definitions. + headcentre = 0.5 + 4*size + headradius = headcentre + 0.1 + headhighlight = 1.5*size + holecentre = 0.5 + 3*size + holeradius = 2*size + holehighlight = 1.5*size + shaftend = 0.5 + 25*size + shaftwidth = 2*size + shafthighlight = 1.5*size + cmax = shaftend + shaftwidth + + # The spanner head is a circle centred at headcentre*(1,1) with + # radius headradius, minus a circle at holecentre*(1,1) with + # radius holeradius, and also minus every translate of that circle + # by a negative real multiple of (1,1). + # + # The spanner handle is a diagonally oriented rectangle, of width + # shaftwidth, with the centre of the far end at shaftend*(1,1), + # and the near end terminating somewhere inside the spanner head + # (doesn't really matter exactly where). + # + # Hence, in SVG we can represent the shape using a path of + # straight lines and circular arcs. But first we need to calculate + # the points where the straight lines meet the spanner head circle. + headpt = lambda a, on=True: (headcentre+headradius*math.cos(a), + -headcentre+headradius*math.sin(a), on) + holept = lambda a, on=True: (holecentre+holeradius*math.cos(a), + -holecentre+holeradius*math.sin(a), on) + + # Now we can specify the path. + spannercoords = [[ + holept(math.pi*5/4), + holept(math.pi*1/4, ("arc", holeradius,holeradius,0, False, False)), + headpt(math.pi*3/4 - math.asin(holeradius/headradius)), + headpt(math.pi*7/4 + math.asin(shaftwidth/headradius), + ("arc", headradius,headradius,0, False, True)), + (shaftend+math.sqrt(0.5)*shaftwidth, + -shaftend+math.sqrt(0.5)*shaftwidth, True), + (shaftend-math.sqrt(0.5)*shaftwidth, + -shaftend-math.sqrt(0.5)*shaftwidth, True), + headpt(math.pi*7/4 - math.asin(shaftwidth/headradius)), + headpt(math.pi*3/4 + math.asin(holeradius/headradius), + ("arc", headradius,headradius,0, False, True)), + ]] + + base = SVGpath(spannercoords) + base.fill(cY) + + shadowthickness = 2*size + sx, sy, _ = holept(math.pi*5/4) + sx += math.sqrt(0.5) * shadowthickness/2 + sy += math.sqrt(0.5) * shadowthickness/2 + sr = holeradius - shadowthickness/2 + + shadow = SVGpath([ + [(sx, sy, sr), + holept(math.pi*1/4, ("arc", sr, sr, 0, False, False)), + headpt(math.pi*3/4 - math.asin(holeradius/headradius))], + [(shaftend-math.sqrt(0.5)*shaftwidth, + -shaftend-math.sqrt(0.5)*shaftwidth, True), + headpt(math.pi*7/4 - math.asin(shaftwidth/headradius)), + headpt(math.pi*3/4 + math.asin(holeradius/headradius), + ("arc", headradius,headradius,0, False, True))], + ], closed=False) + shadow.clip(SVGpath(spannercoords)) + shadow.stroke(cy, width=shadowthickness) + + outline = SVGpath(spannercoords) + outline.stroke(cK, width=0.5) + + return SVGgroup([base, shadow, outline]) + +def box(size, wantback): + # The back side of the cardboard box in the installer icon. + + boxwidth = 15 * size + boxheight = 12 * size + boxdepth = 4 * size + boxfrontflapheight = 5 * size + boxrightflapheight = 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 back of the box. + if wantback: + back = SVGpoly([ + (0,0), (boxwidth,0), (boxwidth+boxdepth,boxdepth), + (boxwidth+boxdepth,boxheight+boxdepth), + (boxdepth,boxheight+boxdepth), (0,boxheight)]) + back.fill(dark) + back.stroke(cK, width=0.5) + return back + + # The front face of the box. + front = SVGrect(0, 0, boxwidth, boxheight) + front.fill(med) + front.stroke(cK, width=0.5) + # The right face of the box. + right = SVGpoly([ + (boxwidth,0), (boxwidth+boxdepth,boxdepth), + (boxwidth+boxdepth,boxheight+boxdepth), (boxwidth,boxheight)]) + right.fill(dark) + right.stroke(cK, width=0.5) + frontflap = SVGpoly([ + (0,boxheight), (boxwidth,boxheight), + (boxwidth-boxfrontflapheight/2, boxheight-boxfrontflapheight), + (-boxfrontflapheight/2, boxheight-boxfrontflapheight)]) + frontflap.stroke(cK, width=0.5) + frontflap.fill(light) + rightflap = SVGpoly([ + (boxwidth,boxheight), (boxwidth+boxdepth,boxheight+boxdepth), + (boxwidth+boxdepth+boxrightflapheight, + boxheight+boxdepth-boxrightflapheight), + (boxwidth+boxrightflapheight,boxheight-boxrightflapheight)]) + rightflap.stroke(cK, width=0.5) + rightflap.fill(med) + + return SVGgroup([front, right, frontflap, rightflap]) + +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, c1bb=None, c2bb=None): + # Two unspecified objects and a lightning bolt. + + w = h = 32 * size + + bolt = lightning(size) + + objs = [c2, c1, bolt] + origins = [None] * 3 + + # Position c2 against the top right of the icon. + bb = c2bb if c2bb is not None else c2.bbox() + assert bb[2]-bb[0] <= w and bb[3]-bb[1] <= h + origins[0] = w-bb[2], h-bb[3] + # Position c1 against the bottom left of the icon. + bb = c1bb if c1bb is not None else c1.bbox() + assert bb[2]-bb[0] <= w and bb[3]-bb[1] <= h + origins[1] = 0-bb[0], 0-bb[1] + + # Place the lightning bolt so that it ends precisely at the centre + # of the monitor, in whichever of the two sub-pictures has one. + # (In the case of the PuTTY icon proper, in which _both_ + # sub-pictures are computers, it should line up correctly for both.) + origin1 = origin2 = None + if hasattr(c1.props, "screencentre"): + origin1 = ( + c1.props.screencentre[0] + origins[1][0] - bolt.props.end1[0], + c1.props.screencentre[1] + origins[1][1] - bolt.props.end1[1]) + if hasattr(c2.props, "screencentre"): + origin2 = ( + c2.props.screencentre[0] + origins[0][0] - bolt.props.end2[0], + c2.props.screencentre[1] + origins[0][1] - bolt.props.end2[1]) + if origin1 is not None and origin2 is not None: + assert math.hypot(origin1[0]-origin2[0],origin1[1]-origin2[1]<1e-5), ( + "Lightning bolt didn't line up! Off by {}*size".format( + ((origin1[0]-origin2[0])/size, + (origin1[1]-origin2[1])/size))) + origins[2] = origin1 if origin1 is not None else origin2 + assert origins[2] is not None, "Need at least one computer to line up bolt" + + toret = SVGgroup(objs, origins) + toret.props.c1pos = origins[1] + toret.props.c2pos = origins[0] + return toret + +def putty_icon(size): + return xybolt(computer(size), computer(size), size) + +def puttycfg_icon(size): + w = h = 32 * size + s = spanner(size) + b = putty_icon(size) + bb = s.bbox() + return SVGgroup([b, s], [(0,0), ((w-bb[0]-bb[2])/2, (h-bb[1]-bb[3])/2)]) + +def puttygen_icon(size): + k = key(size) + # Manually move the key around, by pretending to xybolt that its + # bounding box is offset from where it really is. + kbb = SVGtranslate(k,(2*size,5*size)).bbox() + return xybolt(computer(size), k, size, boltoffx=2, c2bb=kbb) + +def pscp_icon(size): + return xybolt(document(size), computer(size), size) + +def puttyins_icon(size): + boxfront = box(size, False) + boxback = box(size, True) + # The box back goes behind the lightning bolt. + most = xybolt(boxback, computer(size), size, c1bb=boxfront.bbox(), + boltoffx=-2, boltoffy=+1) + # 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 = most.props.c1pos + return SVGgroup([most, boxfront], [(0,0), most.props.c1pos]) + +def pterm_icon(size): + # Just a really big computer. + + w = h = 32 * size + + c = computer(size * 1.4) + + # Centre c in the output rectangle. + bb = c.bbox() + assert bb[2]-bb[0] <= w and bb[3]-bb[1] <= h + + return SVGgroup([c], [((w-bb[0]-bb[2])/2, (h-bb[1]-bb[3])/2)]) + +def ptermcfg_icon(size): + w = h = 32 * size + s = spanner(size) + b = pterm_icon(size) + bb = s.bbox() + return SVGgroup([b, s], [(0,0), ((w-bb[0]-bb[2])/2, (h-bb[1]-bb[3])/2)]) + +def pageant_icon(size): + # A biggish computer, in a hat. + + w = h = 32 * size + + c = computer(size * 1.2) + ht = hat(size) + + cbb = c.bbox() + hbb = ht.bbox() + + # Determine the relative coordinates of the computer and hat. We + # do this by first centring one on the other, then adjusting by + # hand. + xrel = (cbb[0]+cbb[2]-hbb[0]-hbb[2])/2 + 2*size + yrel = (cbb[1]+cbb[3]-hbb[1]-hbb[3])/2 + 12*size + + both = SVGgroup([c, ht], [(0,0), (xrel,yrel)]) + + # Mostly-centre the result in the output rectangle. We want + # everything to fit in frame, but we also want to make it look as + # if the computer is more x-centred than the hat. + + # Coordinates that would centre the whole group. + bb = both.bbox() + assert bb[2]-bb[0] <= w and bb[3]-bb[1] <= h + grx, gry = (w-bb[0]-bb[2])/2, (h-bb[1]-bb[3])/2 + + # Coords that would centre just the computer. + bb = c.bbox() + crx, cry = (w-bb[0]-bb[2])/2, (h-bb[1]-bb[3])/2 + + # Use gry unchanged, but linear-combine grx with crx. + return SVGgroup([both], [(grx+0.6*(crx-grx), gry)]) + +# Test and output functions. + +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 halftone(col1, col2): + r1,g1,b1,a1 = col1 + r2,g2,b2,a2 = col2 + return ((r1+r2)//2, (g1+g2)//2, (b1+b2)//2, (a1+a2)//2) + +def drawicon(func, width, fname): + icon = func(width / 32.0) + minx, miny, maxx, maxy = icon.bbox() + #assert minx >= 0 and miny >= 0 and maxx <= width and maxy <= width + + svgroot = ET.Element("svg") + svgroot.attrib["xmlns"] = "http://www.w3.org/2000/svg" + svgroot.attrib["viewBox"] = "0 0 {w:d} {w:d}".format(w=width) + + defs = ET.Element("defs") + idents = ("iconid{:d}".format(n) for n in itertools.count()) + if icon.add_clip_paths(defs, idents, 0, width): + svgroot.append(defs) + + svgroot.append(icon.render(0,width)) + + ET.ElementTree(svgroot).write(fname) + +def main(): + parser = argparse.ArgumentParser(description='Generate PuTTY SVG icons.') + parser.add_argument("icon", help="Which icon to generate.") + parser.add_argument("-s", "--size", type=int, default=48, + help="Notional pixel size to base the SVG on.") + parser.add_argument("-o", "--output", required=True, + help="Output file name.") + args = parser.parse_args() + + drawicon(eval(args.icon), args.size, args.output) + +if __name__ == '__main__': + main() -- cgit v1.2.3 From be16a7bbe35d5fc4c251ecebc2a04937af22a7c0 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Tue, 29 Mar 2022 12:29:13 +0100 Subject: testcrypt: remove a redundant typedef. All the TD_consumed_foo types are defined by macro elsewhere in the file, so there's no need for an explicit one for TD_consumed_val_hash. --- test/testcrypt.c | 1 - 1 file changed, 1 deletion(-) diff --git a/test/testcrypt.c b/test/testcrypt.c index 5e0b73db..01b5c501 100644 --- a/test/testcrypt.c +++ b/test/testcrypt.c @@ -211,7 +211,6 @@ typedef const char *TD_opt_val_string_asciz; typedef char **TD_out_val_string_asciz; typedef char **TD_out_opt_val_string_asciz; typedef const char **TD_out_opt_val_string_asciz_const; -typedef ssh_hash *TD_consumed_val_hash; typedef const ssh_hashalg *TD_hashalg; typedef const ssh2_macalg *TD_macalg; typedef const ssh_keyalg *TD_keyalg; -- cgit v1.2.3 From bdab00341b9e2a48aeb329a40c9a1b8d521ab4a1 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Tue, 29 Mar 2022 18:05:11 +0100 Subject: Cancel drag-select when the context menu pops up. I got a pterm into a stuck state this morning by an accidental mouse action. I'd intended to press Ctrl + right-click to pop up the context menu, but I accidentally pressed down the left button first, starting a selection drag, and then while the left button was still held down, pressed down the right button as well, triggering the menu. The effect was that the context menu appeared while term->selstate was set to DRAGGING, in which state terminal output is suppressed, and which is only unset by a mouse-button release event. But then that release event went to the popup menu, and the terminal window never got it. So the terminal stayed stuck forever - or rather, until I guessed the cause and did another selection drag to reset it. This happened to me on GTK, but once I knew how I'd done it, I found I could reproduce the same misbehaviour on Windows by the same method. Added a simplistic fix, on both platforms, that cancels a selection drag if the popup menu is summoned part way through it. --- putty.h | 1 + terminal/terminal.c | 17 +++++++++++++++++ unix/window.c | 2 ++ windows/window.c | 3 +++ 4 files changed, 23 insertions(+) diff --git a/putty.h b/putty.h index 606ff178..fc5c2941 100644 --- a/putty.h +++ b/putty.h @@ -2137,6 +2137,7 @@ void term_pwron(Terminal *, bool); void term_clrsb(Terminal *); void term_mouse(Terminal *, Mouse_Button, Mouse_Button, Mouse_Action, int, int, bool, bool, bool); +void term_cancel_selection_drag(Terminal *); void term_key(Terminal *, Key_Sym, wchar_t *, size_t, unsigned int, unsigned int); void term_lost_clipboard_ownership(Terminal *, int clipboard); diff --git a/terminal/terminal.c b/terminal/terminal.c index 923ed03e..ae5e4144 100644 --- a/terminal/terminal.c +++ b/terminal/terminal.c @@ -7299,6 +7299,23 @@ void term_mouse(Terminal *term, Mouse_Button braw, Mouse_Button bcooked, term_schedule_update(term); } +void term_cancel_selection_drag(Terminal *term) +{ + /* + * In unusual circumstances, a mouse drag might be interrupted by + * something that steals the rest of the mouse gesture. An example + * is the GTK popup menu appearing. In that situation, we'll never + * receive the MA_RELEASE that finishes the DRAGGING state, which + * means terminal output could be suppressed indefinitely. Call + * this function from the front end in such situations to restore + * sensibleness. + */ + if (term->selstate == DRAGGING) + term->selstate = NO_SELECTION; + term_out(term, false); + term_schedule_update(term); +} + static int shift_bitmap(bool shift, bool ctrl, bool alt, bool *consumed_alt) { int bitmap = (shift ? 1 : 0) + (alt ? 2 : 0) + (ctrl ? 4 : 0); diff --git a/unix/window.c b/unix/window.c index a8e73789..d9d007b4 100644 --- a/unix/window.c +++ b/unix/window.c @@ -2162,6 +2162,8 @@ static gboolean button_internal(GtkFrontend *inst, GdkEventButton *event) } if (event->button == 3 && ctrl) { + /* Just in case this happened in mid-select */ + term_cancel_selection_drag(inst->term); #if GTK_CHECK_VERSION(3,22,0) gtk_menu_popup_at_pointer(GTK_MENU(inst->menu), (GdkEvent *)event); #else diff --git a/windows/window.c b/windows/window.c index c81755f2..cb9d5b1c 100644 --- a/windows/window.c +++ b/windows/window.c @@ -2641,6 +2641,9 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, (conf_get_int(conf, CONF_mouse_is_xterm) == 2))) { POINT cursorpos; + /* Just in case this happened in mid-select */ + term_cancel_selection_drag(term); + show_mouseptr(true); /* make sure pointer is visible */ GetCursorPos(&cursorpos); TrackPopupMenu(popup_menus[CTXMENU].menu, -- cgit v1.2.3 From 18896b662e4225b2b2185d49bc916891f1fdbef5 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Wed, 30 Mar 2022 18:15:43 +0100 Subject: sshproxy: call backend_unthrottle on unfreeze. If an SSH proxy socket is frozen for long enough, and the SSH server continues to send, then sooner or later the proxy SSH connection will end up having to freeze its underlying physical socket too. When the proxy socket is later unfrozen, it needs to pass that unfreezing on in turn. The way this should happen is that when the SshProxy begins to clear the backlog of data passed to it from the proxy SSH connection via seat_stdout, it should call backend_unthrottle to inform that proxy connection that the backlog is clearing. But there was no backlog_unthrottle call in the whole of sshproxy.c. Now there is. --- proxy/sshproxy.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/proxy/sshproxy.c b/proxy/sshproxy.c index 4f240d50..95daecb1 100644 --- a/proxy/sshproxy.c +++ b/proxy/sshproxy.c @@ -100,12 +100,20 @@ static void sshproxy_write_eof(Socket *s) static void try_send_ssh_to_socket(void *ctx); +static void try_send_ssh_to_socket_cb(void *ctx) +{ + SshProxy *sp = (SshProxy *)ctx; + try_send_ssh_to_socket(sp); + if (sp->backend) + backend_unthrottle(sp->backend, bufchain_size(&sp->ssh_to_socket)); +} + static void sshproxy_set_frozen(Socket *s, bool is_frozen) { SshProxy *sp = container_of(s, SshProxy, sock); sp->frozen = is_frozen; if (!sp->frozen) - queue_toplevel_callback(try_send_ssh_to_socket, sp); + queue_toplevel_callback(try_send_ssh_to_socket_cb, sp); } static const char *sshproxy_socket_error(Socket *s) -- cgit v1.2.3 From 896bcd506866d3f119e23ec264c9d2150318cdf9 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 2 Apr 2022 16:13:27 +0100 Subject: Resurrect the test backends. I've been keeping them up to date with API changes as far as making sure they still _compile_, but today I tried to actually run them, and found that they were making a couple of segfault-inducing mistakes: not filling in their vtable pointer, and not returning a 'realhost' string. Now fixed. --- otherbackends/testback.c | 25 +++++-------------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/otherbackends/testback.c b/otherbackends/testback.c index dbe282ec..f46d1d98 100644 --- a/otherbackends/testback.c +++ b/otherbackends/testback.c @@ -32,11 +32,8 @@ #include "putty.h" -static char *null_init(const BackendVtable *, Seat *, Backend **, LogContext *, - Conf *, const char *, int, char **, bool, bool); static char *loop_init(const BackendVtable *, Seat *, Backend **, LogContext *, Conf *, const char *, int, char **, bool, bool); -static void null_free(Backend *); static void loop_free(Backend *); static void null_reconfig(Backend *, Conf *); static void null_send(Backend *, const char *, size_t); @@ -55,8 +52,8 @@ static void null_unthrottle(Backend *, size_t); static int null_cfg_info(Backend *); const BackendVtable null_backend = { - .init = null_init, - .free = null_free, + .init = loop_init, + .free = loop_free, .reconfig = null_reconfig, .send = null_send, .sendbuffer = null_sendbuffer, @@ -106,17 +103,6 @@ struct loop_state { size_t sendbuffer; }; -static char *null_init(const BackendVtable *vt, Seat *seat, - Backend **backend_handle, LogContext *logctx, - Conf *conf, const char *host, int port, - char **realhost, bool nodelay, bool keepalive) { - /* No local authentication phase in this protocol */ - seat_set_trust_status(seat, false); - - *backend_handle = NULL; - return NULL; -} - static char *loop_init(const BackendVtable *vt, Seat *seat, Backend **backend_handle, LogContext *logctx, Conf *conf, const char *host, int port, @@ -127,13 +113,12 @@ static char *loop_init(const BackendVtable *vt, Seat *seat, seat_set_trust_status(seat, false); st->seat = seat; + st->backend.vt = vt; *backend_handle = &st->backend; - return NULL; -} -static void null_free(Backend *be) -{ + *realhost = dupstr(host); + return NULL; } static void loop_free(Backend *be) -- cgit v1.2.3 From 9294ee349617632a686e65ca7420e8cb3c759a85 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 2 Apr 2022 16:15:53 +0100 Subject: Windows PuTTYgen: saw load_key_file in half. Once we've actually loaded a key file, the job of updating the UI fields is now done by a subroutine update_ui_after_load(), so that I can call it from a different context in an upcoming commit. --- windows/puttygen.c | 119 +++++++++++++++++++++++++++-------------------------- 1 file changed, 60 insertions(+), 59 deletions(-) diff --git a/windows/puttygen.c b/windows/puttygen.c index 6cda5817..957169b7 100644 --- a/windows/puttygen.c +++ b/windows/puttygen.c @@ -992,6 +992,65 @@ void ui_set_fptype(HWND hwnd, struct MainDlgState *state, int option) } } +static void update_ui_after_load(HWND hwnd, struct MainDlgState *state, + const char *passphrase, int type, + RSAKey *newkey1, ssh2_userkey *newkey2) +{ + SetDlgItemText(hwnd, IDC_PASSPHRASE1EDIT, passphrase); + SetDlgItemText(hwnd, IDC_PASSPHRASE2EDIT, passphrase); + + if (type == SSH_KEYTYPE_SSH1) { + char *fingerprint, *savecomment; + + state->ssh2 = false; + state->commentptr = &state->key.comment; + state->key = *newkey1; /* structure copy */ + + /* + * Set the key fingerprint. + */ + savecomment = state->key.comment; + state->key.comment = NULL; + fingerprint = rsa_ssh1_fingerprint(&state->key); + state->key.comment = savecomment; + SetDlgItemText(hwnd, IDC_FINGERPRINT, fingerprint); + sfree(fingerprint); + + /* + * 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 = ssh2_fingerprint(state->ssh2key.key, state->fptype); + 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; +} + void load_key_file(HWND hwnd, struct MainDlgState *state, Filename *filename, bool was_import_cmd) { @@ -1081,65 +1140,7 @@ void load_key_file(HWND hwnd, struct MainDlgState *state, * 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 *fingerprint, *savecomment; - - state->ssh2 = false; - state->commentptr = &state->key.comment; - state->key = newkey1; - - /* - * Set the key fingerprint. - */ - savecomment = state->key.comment; - state->key.comment = NULL; - fingerprint = rsa_ssh1_fingerprint(&state->key); - state->key.comment = savecomment; - SetDlgItemText(hwnd, IDC_FINGERPRINT, fingerprint); - sfree(fingerprint); - - /* - * 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 = ssh2_fingerprint(state->ssh2key.key, state->fptype); - 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; + update_ui_after_load(hwnd, state, passphrase, type, &newkey1, newkey2); /* * If the user has imported a foreign key -- cgit v1.2.3 From bc7e06c49411a891fe5d0f6c6f33209b123c6030 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 2 Apr 2022 16:18:08 +0100 Subject: Windows tools: assorted '-demo' options. Using a new screenshot-taking module I just added in windows/utils, these new options allow me to start up one of the tools with demonstration window contents and automatically save a .BMP screenshot to disk. This will allow me to keep essentially the same set of demo images and update them easily to keep pace with the current appearance of the real tools as PuTTY - and Windows itself - both evolve. --- cmake/cmake.h.in | 1 + cmake/platforms/windows.cmake | 2 + settings.c | 18 ++++-- windows/CMakeLists.txt | 1 + windows/dialog.c | 18 ++++++ windows/platform.h | 3 + windows/pterm.c | 4 ++ windows/putty.c | 76 ++++++++++++++++++++++--- windows/puttygen.c | 40 ++++++++++++++ windows/utils/screenshot.c | 126 ++++++++++++++++++++++++++++++++++++++++++ windows/window.c | 2 + 11 files changed, 279 insertions(+), 12 deletions(-) create mode 100644 windows/utils/screenshot.c diff --git a/cmake/cmake.h.in b/cmake/cmake.h.in index 9de1386b..06f26176 100644 --- a/cmake/cmake.h.in +++ b/cmake/cmake.h.in @@ -13,6 +13,7 @@ #cmakedefine01 HAVE_GETNAMEDPIPECLIENTPROCESSID #cmakedefine01 HAVE_SETDEFAULTDLLDIRECTORIES #cmakedefine01 HAVE_STRTOUMAX +#cmakedefine01 HAVE_DWMAPI_H #cmakedefine NOT_X_WINDOWS diff --git a/cmake/platforms/windows.cmake b/cmake/platforms/windows.cmake index ef3f7825..e1da07dc 100644 --- a/cmake/platforms/windows.cmake +++ b/cmake/platforms/windows.cmake @@ -49,6 +49,8 @@ check_symbol_exists(GetNamedPipeClientProcessId "windows.h" HAVE_GETNAMEDPIPECLIENTPROCESSID) check_symbol_exists(CreatePseudoConsole "windows.h" HAVE_CONPTY) +check_include_files("windows.h;dwmapi.h" HAVE_DWMAPI_H) + check_c_source_compiles(" #include GCP_RESULTSW gcpw; diff --git a/settings.c b/settings.c index ff2bb6c4..98313d17 100644 --- a/settings.c +++ b/settings.c @@ -1307,6 +1307,8 @@ static int sessioncmp(const void *av, const void *bv) return strcmp(a, b); /* otherwise, compare normally */ } +bool sesslist_demo_mode = false; + void get_sesslist(struct sesslist *list, bool allocate) { int i; @@ -1316,12 +1318,18 @@ void get_sesslist(struct sesslist *list, bool allocate) if (allocate) { strbuf *sb = strbuf_new(); - if ((handle = enum_settings_start()) != NULL) { - while (enum_settings_next(handle, sb)) - put_byte(sb, '\0'); - enum_settings_finish(handle); + if (sesslist_demo_mode) { + put_asciz(sb, "demo-server"); + put_asciz(sb, "demo-server-2"); + } else { + if ((handle = enum_settings_start()) != NULL) { + while (enum_settings_next(handle, sb)) + put_byte(sb, '\0'); + enum_settings_finish(handle); + } + put_byte(sb, '\0'); } - put_byte(sb, '\0'); + list->buffer = strbuf_to_str(sb); /* diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt index aad2f5af..b988792a 100644 --- a/windows/CMakeLists.txt +++ b/windows/CMakeLists.txt @@ -28,6 +28,7 @@ add_sources_from_current_dir(utils utils/platform_get_x_display.c utils/registry_get_string.c utils/request_file.c + utils/screenshot.c utils/security.c utils/split_into_argv.c utils/version.c diff --git a/windows/dialog.c b/windows/dialog.c index 31bf19e6..38af0f9b 100644 --- a/windows/dialog.c +++ b/windows/dialog.c @@ -388,6 +388,8 @@ static void create_controls(HWND hwnd, char *path) } } +const char *dialog_box_demo_screenshot_filename = NULL; + /* * This function is the configuration box. * (Being a dialog procedure, in general it returns 0 if the default @@ -396,6 +398,7 @@ static void create_controls(HWND hwnd, char *path) static INT_PTR CALLBACK GenericMainDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { + const int DEMO_SCREENSHOT_TIMER_ID = 1230; HWND hw, treeview; struct treeview_faff tvfaff; int ret; @@ -565,6 +568,21 @@ static INT_PTR CALLBACK GenericMainDlgProc(HWND hwnd, UINT msg, * spurious firing during the above setup procedure. */ SetWindowLongPtr(hwnd, GWLP_USERDATA, 1); + + if (dialog_box_demo_screenshot_filename) + SetTimer(hwnd, DEMO_SCREENSHOT_TIMER_ID, TICKSPERSEC, NULL); + return 0; + case WM_TIMER: + if (dialog_box_demo_screenshot_filename && + (UINT_PTR)wParam == DEMO_SCREENSHOT_TIMER_ID) { + KillTimer(hwnd, DEMO_SCREENSHOT_TIMER_ID); + const char *err = save_screenshot( + hwnd, dialog_box_demo_screenshot_filename); + if (err) + MessageBox(hwnd, err, "Demo screenshot failure", + MB_OK | MB_ICONERROR); + SaneEndDialog(hwnd, 0); + } return 0; case WM_LBUTTONUP: /* diff --git a/windows/platform.h b/windows/platform.h index 5264e8f0..eff5de61 100644 --- a/windows/platform.h +++ b/windows/platform.h @@ -757,4 +757,7 @@ bool aux_match_arg(AuxMatchOpt *amo, char **val); bool aux_match_opt(AuxMatchOpt *amo, char **val, const char *optname, ...); bool aux_match_done(AuxMatchOpt *amo); +char *save_screenshot(HWND hwnd, const char *outfile); +void gui_terminal_ready(HWND hwnd, Seat *seat, Backend *backend); + #endif /* PUTTY_WINDOWS_PLATFORM_H */ diff --git a/windows/pterm.c b/windows/pterm.c index 2cdef30c..0df849f9 100644 --- a/windows/pterm.c +++ b/windows/pterm.c @@ -45,3 +45,7 @@ const wchar_t *get_app_user_model_id(void) { return L"SimonTatham.Pterm"; } + +void gui_terminal_ready(HWND hwnd, Seat *seat, Backend *backend) +{ +} diff --git a/windows/putty.c b/windows/putty.c index b17ad7dc..83594d61 100644 --- a/windows/putty.c +++ b/windows/putty.c @@ -1,10 +1,16 @@ #include "putty.h" #include "storage.h" +extern bool sesslist_demo_mode; +extern const char *dialog_box_demo_screenshot_filename; +static strbuf *demo_terminal_data = NULL; +static const char *terminal_demo_screenshot_filename; + void gui_term_process_cmdline(Conf *conf, char *cmdline) { char *p; bool special_launchable_argument = false; + bool demo_config_box = false; settings_set_default_protocol(be_default_protocol); /* Find the appropriate default port. */ @@ -81,6 +87,29 @@ void gui_term_process_cmdline(Conf *conf, char *cmdline) } else if (!strcmp(p, "-pgpfp")) { pgp_fingerprints_msgbox(NULL); exit(1); + } else if (!strcmp(p, "-demo-config-box")) { + if (i+1 >= argc) { + cmdline_error("%s expects an output filename", p); + } else { + demo_config_box = true; + dialog_box_demo_screenshot_filename = argv[++i]; + } + } else if (!strcmp(p, "-demo-terminal")) { + if (i+2 >= argc) { + cmdline_error("%s expects input and output filenames", p); + } else { + const char *infile = argv[++i]; + terminal_demo_screenshot_filename = argv[++i]; + FILE *fp = fopen(infile, "rb"); + if (!fp) + cmdline_error("can't open input file '%s'", infile); + demo_terminal_data = strbuf_new(); + char buf[4096]; + int retd; + while ((retd = fread(buf, 1, sizeof(buf), fp)) > 0) + put_data(demo_terminal_data, buf, retd); + fclose(fp); + } } else if (*p != '-') { cmdline_error("unexpected argument \"%s\"", p); } else { @@ -91,13 +120,26 @@ void gui_term_process_cmdline(Conf *conf, char *cmdline) cmdline_run_saved(conf); - /* - * Bring up the config dialog if the command line hasn't - * (explicitly) specified a launchable configuration. - */ - if (!(special_launchable_argument || cmdline_host_ok(conf))) { - if (!do_config(conf)) - cleanup_exit(0); + if (demo_config_box) { + sesslist_demo_mode = true; + load_open_settings(NULL, conf); + conf_set_str(conf, CONF_host, "demo-server.example.com"); + do_config(conf); + cleanup_exit(0); + } else if (demo_terminal_data) { + /* Ensure conf will cause an immediate session launch */ + load_open_settings(NULL, conf); + conf_set_str(conf, CONF_host, "demo-server.example.com"); + conf_set_int(conf, CONF_close_on_exit, FORCE_OFF); + } else { + /* + * Bring up the config dialog if the command line hasn't + * (explicitly) specified a launchable configuration. + */ + if (!(special_launchable_argument || cmdline_host_ok(conf))) { + if (!do_config(conf)) + cleanup_exit(0); + } } prepare_session(conf); @@ -105,6 +147,10 @@ void gui_term_process_cmdline(Conf *conf, char *cmdline) const struct BackendVtable *backend_vt_from_conf(Conf *conf) { + if (demo_terminal_data) { + return &null_backend; + } + /* * Select protocol. This is farmed out into a table in a * separate file to enable an ssh-free variant. @@ -125,3 +171,19 @@ const wchar_t *get_app_user_model_id(void) { return L"SimonTatham.PuTTY"; } + +static void demo_terminal_screenshot(void *ctx, unsigned long now) +{ + HWND hwnd = (HWND)ctx; + save_screenshot(hwnd, terminal_demo_screenshot_filename); + cleanup_exit(0); +} + +void gui_terminal_ready(HWND hwnd, Seat *seat, Backend *backend) +{ + if (demo_terminal_data) { + ptrlen data = ptrlen_from_strbuf(demo_terminal_data); + seat_stdout(seat, data.ptr, data.len); + schedule_timer(TICKSPERSEC, demo_terminal_screenshot, (void *)hwnd); + } +} diff --git a/windows/puttygen.c b/windows/puttygen.c index 957169b7..281d17c0 100644 --- a/windows/puttygen.c +++ b/windows/puttygen.c @@ -27,6 +27,8 @@ #define DEFAULT_EDCURVE_INDEX 0 static char *cmdline_keyfile = NULL; +static ptrlen cmdline_demo_keystr; +static const char *demo_screenshot_filename = NULL; /* * Print a modal (Really Bad) message box and perform a fatal exit. @@ -1206,6 +1208,7 @@ static void start_generating_key(HWND hwnd, struct MainDlgState *state) static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { + const int DEMO_SCREENSHOT_TIMER_ID = 1230; static const char entropy_msg[] = "Please generate some randomness by moving the mouse over the blank area."; struct MainDlgState *state; @@ -1429,9 +1432,30 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, Filename *fn = filename_from_str(cmdline_keyfile); load_key_file(hwnd, state, fn, false); filename_free(fn); + } else if (cmdline_demo_keystr.ptr) { + BinarySource src[1]; + BinarySource_BARE_INIT_PL(src, cmdline_demo_keystr); + const char *errmsg; + ssh2_userkey *k = ppk_load_s(src, NULL, &errmsg); + assert(!errmsg); + + update_ui_after_load(hwnd, state, "demo passphrase", + SSH_KEYTYPE_SSH2, NULL, k); + + SetTimer(hwnd, DEMO_SCREENSHOT_TIMER_ID, TICKSPERSEC, NULL); } return 1; + case WM_TIMER: + if ((UINT_PTR)wParam == DEMO_SCREENSHOT_TIMER_ID) { + KillTimer(hwnd, DEMO_SCREENSHOT_TIMER_ID); + const char *err = save_screenshot(hwnd, demo_screenshot_filename); + if (err) + MessageBox(hwnd, err, "Demo screenshot failure", + MB_OK | MB_ICONERROR); + EndDialog(hwnd, 0); + } + return 0; case WM_MOUSEMOVE: state = (struct MainDlgState *) GetWindowLongPtr(hwnd, GWLP_USERDATA); if (state->entropy && state->entropy_got < state->entropy_required) { @@ -2176,6 +2200,22 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) opt_error("unrecognised PPK parameter '%s'\n", val); } } + } else if (match_optval("-demo-screenshot")) { + demo_screenshot_filename = val; + cmdline_demo_keystr = PTRLEN_LITERAL( + "PuTTY-User-Key-File-3: ssh-ed25519\n" + "Encryption: none\n" + "Comment: ed25519-key-20220402\n" + "Public-Lines: 2\n" + "AAAAC3NzaC1lZDI1NTE5AAAAILzuIFwZ" + "8ZhgOlilcSb+9zPuCf/DmKJiloVlmWGy\n" + "xa/F\n" + "Private-Lines: 1\n" + "AAAAIPca6vLwtB2NJhZUpABQISR0gcQH8jjQLta19VyzA3wc\n" + "Private-MAC: 1159e9628259b35933b397379bbe8a14" + "a1f1d97fe91e446e45a9581a3408b70e\n"); + params->keybutton = IDC_KEYSSH2EDDSA; + argbits = 255; } else { opt_error("unrecognised option '%s'\n", amo.argv[amo.index]); } diff --git a/windows/utils/screenshot.c b/windows/utils/screenshot.c new file mode 100644 index 00000000..777520fd --- /dev/null +++ b/windows/utils/screenshot.c @@ -0,0 +1,126 @@ +#include "putty.h" + +#if HAVE_DWMAPI_H + +#include + +char *save_screenshot(HWND hwnd, const char *outfile) +{ + HDC dcWindow = NULL, dcSave = NULL; + HBITMAP bmSave = NULL; + uint8_t *buffer = NULL; + char *err = NULL; + + static HMODULE dwmapi_module; + DECL_WINDOWS_FUNCTION(static, HRESULT, DwmGetWindowAttribute, + (HWND, DWORD, PVOID, DWORD)); + + if (!dwmapi_module) { + dwmapi_module = load_system32_dll("dwmapi.dll"); + GET_WINDOWS_FUNCTION(dwmapi_module, DwmGetWindowAttribute); + } + + dcWindow = GetDC(NULL); + if (!dcWindow) { + err = dupprintf("GetDC(window): %s", win_strerror(GetLastError())); + goto out; + } + + int x, y, w, h; + RECT wr; + + /* Use DwmGetWindowAttribute in place of GetWindowRect to exclude + * drop shadow, otherwise we get a load of unwanted desktop + * background under the shadow */ + if (p_DwmGetWindowAttribute && + 0 <= p_DwmGetWindowAttribute(hwnd, DWMWA_EXTENDED_FRAME_BOUNDS, + &wr, sizeof(wr))) { + x = wr.left; + y = wr.top; + w = wr.right - wr.left; + h = wr.bottom - wr.top; + } else { + BITMAP bmhdr; + memset(&bmhdr, 0, sizeof(bmhdr)); + GetObject(GetCurrentObject(dcWindow, OBJ_BITMAP), + sizeof(bmhdr), &bmhdr); + x = y = 0; + w = bmhdr.bmWidth; + h = bmhdr.bmHeight; + } + + dcSave = CreateCompatibleDC(dcWindow); + if (!dcSave) { + err = dupprintf("CreateCompatibleDC(desktop window dc): %s", + win_strerror(GetLastError())); + goto out; + } + + bmSave = CreateCompatibleBitmap(dcWindow, w, h); + if (!bmSave) { + err = dupprintf("CreateCompatibleBitmap: %s", + win_strerror(GetLastError())); + goto out; + } + + if (!SelectObject(dcSave, bmSave)) { + err = dupprintf("SelectObject: %s", win_strerror(GetLastError())); + goto out; + } + + if (!BitBlt(dcSave, 0, 0, w, h, dcWindow, x, y, SRCCOPY)) { + err = dupprintf("BitBlt: %s", win_strerror(GetLastError())); + goto out; + } + + BITMAPINFO bmInfo; + memset(&bmInfo, 0, sizeof(bmInfo)); + bmInfo.bmiHeader.biSize = sizeof(bmInfo.bmiHeader); + bmInfo.bmiHeader.biWidth = w; + bmInfo.bmiHeader.biHeight = h; + bmInfo.bmiHeader.biPlanes = 1; + bmInfo.bmiHeader.biBitCount = 32; + bmInfo.bmiHeader.biCompression = BI_RGB; + + size_t bmPixels = (size_t)w*h, bmBytes = bmPixels * 4; + buffer = snewn(bmBytes, uint8_t); + + if (!GetDIBits(dcWindow, bmSave, 0, h, buffer, &bmInfo, DIB_RGB_COLORS)) + err = dupprintf("GetDIBits (get data): %s", + win_strerror(GetLastError())); + + FILE *fp = fopen(outfile, "wb"); + if (!fp) { + err = dupprintf("'%s': unable to open file", outfile); + goto out; + } + + BITMAPFILEHEADER bmFileHdr; + bmFileHdr.bfType = 'B' | ('M' << 8); + bmFileHdr.bfSize = sizeof(bmFileHdr) + sizeof(bmInfo.bmiHeader) + bmBytes; + bmFileHdr.bfOffBits = sizeof(bmFileHdr) + sizeof(bmInfo.bmiHeader); + fwrite((void *)&bmFileHdr, 1, sizeof(bmFileHdr), fp); + fwrite((void *)&bmInfo.bmiHeader, 1, sizeof(bmInfo.bmiHeader), fp); + fwrite((void *)buffer, 1, bmBytes, fp); + fclose(fp); + + out: + if (dcWindow) + ReleaseDC(NULL, dcWindow); + if (bmSave) + DeleteObject(bmSave); + if (dcSave) + DeleteObject(dcSave); + sfree(buffer); + return err; +} + +#else /* HAVE_DWMAPI_H */ + +/* Without we can't get the right window rectangle */ +char *save_screenshot(HWND hwnd, const char *outfile) +{ + return dupstr("Demo screenshots not compiled in to this build"); +} + +#endif /* HAVE_DWMAPI_H */ diff --git a/windows/window.c b/windows/window.c index cb9d5b1c..399829b6 100644 --- a/windows/window.c +++ b/windows/window.c @@ -816,6 +816,8 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) term_set_focus(term, GetForegroundWindow() == wgs.term_hwnd); UpdateWindow(wgs.term_hwnd); + gui_terminal_ready(wgs.term_hwnd, &wgs.seat, backend); + while (1) { int n; DWORD timeout; -- cgit v1.2.3 From c0fba758e60e2ff4c1bebd566d9a0e56276d07ec Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 2 Apr 2022 16:20:47 +0100 Subject: Standalone screenshot utility. I used this for testing the new windows/utils/screenshot.c, and who knows, it might come in useful again. --- windows/CMakeLists.txt | 4 ++++ windows/test_screenshot.c | 45 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 windows/test_screenshot.c diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt index b988792a..79a09eda 100644 --- a/windows/CMakeLists.txt +++ b/windows/CMakeLists.txt @@ -176,3 +176,7 @@ add_executable(test_split_into_argv utils/split_into_argv.c) target_compile_definitions(test_split_into_argv PRIVATE TEST) target_link_libraries(test_split_into_argv utils ${platform_libraries}) + +add_executable(test_screenshot + test_screenshot.c) +target_link_libraries(test_screenshot utils ${platform_libraries}) diff --git a/windows/test_screenshot.c b/windows/test_screenshot.c new file mode 100644 index 00000000..1e3a20d7 --- /dev/null +++ b/windows/test_screenshot.c @@ -0,0 +1,45 @@ +#include "putty.h" + +static NORETURN PRINTF_LIKE(1, 2) void fatal_error(const char *p, ...) +{ + va_list ap; + fprintf(stderr, "screenshot: "); + va_start(ap, p); + vfprintf(stderr, p, ap); + va_end(ap); + fputc('\n', stderr); + exit(1); +} + +void out_of_memory(void) { fatal_error("out of memory"); } + +int main(int argc, char **argv) +{ + const char *outfile = NULL; + + AuxMatchOpt amo = aux_match_opt_init(argc-1, argv+1, 0, fatal_error); + while (!aux_match_done(&amo)) { + char *val; + #define match_opt(...) aux_match_opt( \ + &amo, NULL, __VA_ARGS__, (const char *)NULL) + #define match_optval(...) aux_match_opt( \ + &amo, &val, __VA_ARGS__, (const char *)NULL) + + if (aux_match_arg(&amo, &val)) { + fatal_error("unexpected argument '%s'", val); + } else if (match_optval("-o", "--output")) { + outfile = val; + } else { + fatal_error("unrecognised option '%s'\n", amo.argv[amo.index]); + } + } + + if (!outfile) + fatal_error("expected an output file name"); + + char *err = save_screenshot(NULL, outfile); + if (err) + fatal_error("%s", err); + + return 0; +} -- cgit v1.2.3 From 1500da80f182a993cf0a7da7e143ab9bb623e2d3 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 15 Apr 2022 15:20:16 +0100 Subject: Move key_components management functions into utils. They're pretty much self-contained, and don't really need to be in the same module as sshpubk.c (which has other dependencies). Move them out into a utils module, where pulling them in won't pull in anything else unwanted. --- sshpubk.c | 44 -------------------------------------------- utils/CMakeLists.txt | 1 + utils/key_components.c | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 44 deletions(-) create mode 100644 utils/key_components.c diff --git a/sshpubk.c b/sshpubk.c index 003213ca..326fe31c 100644 --- a/sshpubk.c +++ b/sshpubk.c @@ -1936,47 +1936,3 @@ const char *key_type_to_str(int type) unreachable("bad key type in key_type_to_str"); } } - -key_components *key_components_new(void) -{ - key_components *kc = snew(key_components); - kc->ncomponents = 0; - kc->componentsize = 0; - kc->components = NULL; - return kc; -} - -void key_components_add_text(key_components *kc, - const char *name, const char *value) -{ - sgrowarray(kc->components, kc->componentsize, kc->ncomponents); - size_t n = kc->ncomponents++; - kc->components[n].name = dupstr(name); - kc->components[n].is_mp_int = false; - kc->components[n].text = dupstr(value); -} - -void key_components_add_mp(key_components *kc, - const char *name, mp_int *value) -{ - sgrowarray(kc->components, kc->componentsize, kc->ncomponents); - size_t n = kc->ncomponents++; - kc->components[n].name = dupstr(name); - kc->components[n].is_mp_int = true; - kc->components[n].mp = mp_copy(value); -} - -void key_components_free(key_components *kc) -{ - for (size_t i = 0; i < kc->ncomponents; i++) { - sfree(kc->components[i].name); - if (kc->components[i].is_mp_int) { - mp_free(kc->components[i].mp); - } else { - smemclr(kc->components[i].text, strlen(kc->components[i].text)); - sfree(kc->components[i].text); - } - } - sfree(kc->components); - sfree(kc); -} diff --git a/utils/CMakeLists.txt b/utils/CMakeLists.txt index 80fc20b8..787bcefc 100644 --- a/utils/CMakeLists.txt +++ b/utils/CMakeLists.txt @@ -28,6 +28,7 @@ add_sources_from_current_dir(utils host_strcspn.c host_strduptrim.c host_strrchr.c + key_components.c log_proxy_stderr.c make_spr_sw_abort_static.c marshal.c diff --git a/utils/key_components.c b/utils/key_components.c new file mode 100644 index 00000000..bb9ff40d --- /dev/null +++ b/utils/key_components.c @@ -0,0 +1,46 @@ +#include "ssh.h" +#include "mpint.h" + +key_components *key_components_new(void) +{ + key_components *kc = snew(key_components); + kc->ncomponents = 0; + kc->componentsize = 0; + kc->components = NULL; + return kc; +} + +void key_components_add_text(key_components *kc, + const char *name, const char *value) +{ + sgrowarray(kc->components, kc->componentsize, kc->ncomponents); + size_t n = kc->ncomponents++; + kc->components[n].name = dupstr(name); + kc->components[n].is_mp_int = false; + kc->components[n].text = dupstr(value); +} + +void key_components_add_mp(key_components *kc, + const char *name, mp_int *value) +{ + sgrowarray(kc->components, kc->componentsize, kc->ncomponents); + size_t n = kc->ncomponents++; + kc->components[n].name = dupstr(name); + kc->components[n].is_mp_int = true; + kc->components[n].mp = mp_copy(value); +} + +void key_components_free(key_components *kc) +{ + for (size_t i = 0; i < kc->ncomponents; i++) { + sfree(kc->components[i].name); + if (kc->components[i].is_mp_int) { + mp_free(kc->components[i].mp); + } else { + smemclr(kc->components[i].text, strlen(kc->components[i].text)); + sfree(kc->components[i].text); + } + } + sfree(kc->components); + sfree(kc); +} -- cgit v1.2.3 From 3adfb1aa5bb924aad5b753057f9cf7214f81e0e4 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 15 Apr 2022 15:31:30 +0100 Subject: testsc: add random_advance_counter(). In test_primegen, we loop round retrieving random data until we find some that will permit a successful prime generation, so that we can log only the successful attempts, and not the failures (which don't have to be time-safe). But this itself introduces a potential mismatch between logs, because the simplistic RNG used in testsc will have different control flow depending on how far through a buffer of hash data it is at the start of a given run. random_advance_counter() gives it a fresh buffer, so calling that at the start of a run should normalise this out. The code to do that was already in the middle of random_read(); I've just pulled it out into a separately callable function. This hasn't _actually_ caused failures in test_primegen, but I'm not sure why not. (Perhaps just luck.) But it did cause a failure in another test of a similar nature, so before I commit _that_ test (and the thing it's testing), I'd better fix this. --- test/testsc.c | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/test/testsc.c b/test/testsc.c index c9029662..4d8b55a4 100644 --- a/test/testsc.c +++ b/test/testsc.c @@ -114,19 +114,23 @@ static void random_seed(const char *seedstr) random_buf_limit = 0; } +static void random_advance_counter(void) +{ + ssh_hash_reset(random_hash); + put_asciz(random_hash, random_seedstr); + put_uint64(random_hash, random_counter); + random_counter++; + random_buf_limit = ssh_hash_alg(random_hash)->hlen; + ssh_hash_digest(random_hash, random_buf); +} + void random_read(void *vbuf, size_t size) { assert(random_seedstr); uint8_t *buf = (uint8_t *)vbuf; while (size-- > 0) { - if (random_buf_limit == 0) { - ssh_hash_reset(random_hash); - put_asciz(random_hash, random_seedstr); - put_uint64(random_hash, random_counter); - random_counter++; - random_buf_limit = ssh_hash_alg(random_hash)->hlen; - ssh_hash_digest(random_hash, random_buf); - } + if (random_buf_limit == 0) + random_advance_counter(); *buf++ = random_buf[random_buf_limit--]; } } @@ -1514,6 +1518,7 @@ static void test_primegen(const PrimeGenerationPolicy *policy) for (size_t i = 0; i < looplimit(2); i++) { while (true) { + random_advance_counter(); struct random_state st = random_get_state(); PrimeCandidateSource *pcs = pcs_new(128); -- cgit v1.2.3 From d5af33da533c54342006b6f4d96c5455bb7ac2e5 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 15 Apr 2022 17:18:23 +0100 Subject: Utility function mp_resize. This reallocs an existing mp_int to have a different physical size, e.g. to make sure there's enough space at the top of it. Trivial, but I'm a little surprised I haven't needed it until now! --- crypto/mpint.c | 8 ++++++++ mpint.h | 7 +++++++ 2 files changed, 15 insertions(+) diff --git a/crypto/mpint.c b/crypto/mpint.c index 544a18b5..437c7e8c 100644 --- a/crypto/mpint.c +++ b/crypto/mpint.c @@ -82,6 +82,14 @@ mp_int *mp_new(size_t maxbits) return mp_make_sized(words); } +mp_int *mp_resize(mp_int *mp, size_t newmaxbits) +{ + mp_int *copy = mp_new(newmaxbits); + mp_copy_into(copy, mp); + mp_free(mp); + return copy; +} + mp_int *mp_from_integer(uintmax_t n) { mp_int *x = mp_make_sized( diff --git a/mpint.h b/mpint.h index ae09a24f..4ddc0e64 100644 --- a/mpint.h +++ b/mpint.h @@ -42,6 +42,13 @@ mp_int *mp_new(size_t maxbits); void mp_free(mp_int *); void mp_clear(mp_int *x); +/* + * Resize the physical size of existing mp_int, e.g. so that you have + * room to transform it in place to a larger value. Destroys the old + * mp_int in the process. + */ +mp_int *mp_resize(mp_int *, size_t newmaxbits); + /* * Create mp_ints from various sources: little- and big-endian binary * data, an ordinary C unsigned integer type, a decimal or hex string -- cgit v1.2.3 From 31db2e67bb49fe838e39819394470867064624e6 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 15 Apr 2022 17:18:32 +0100 Subject: Make smemeq return unsigned, not bool. bool is dangerous in a time-safe context, because C compilers might insert a control flow divergence to implement the implicit normalisation of nonzero integers to 1 when you assign to a bool. Everywhere else time-safe, I avoid using it; but smemeq has been an exception until now, because the response to smemeq returning failure was to do an obvious protocol-level divergence _anyway_ (like disconnecting due to MAC mismatch). But I'm about to want to use smemeq in a context where I use the result _subtly_ and don't want to give away what it is, so now it's time to get rid of that bool and have smemeq return unsigned. --- misc.h | 6 +++--- utils/smemeq.c | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/misc.h b/misc.h index dea7190b..7acd8f41 100644 --- a/misc.h +++ b/misc.h @@ -207,9 +207,9 @@ void smemclr(void *b, size_t len); /* Compare two fixed-length chunks of memory for equality, without * data-dependent control flow (so an attacker with a very accurate * stopwatch can't try to guess where the first mismatching byte was). - * Returns false for mismatch or true for equality (unlike memcmp), - * hinted at by the 'eq' in the name. */ -bool smemeq(const void *av, const void *bv, size_t len); + * Returns 0 for mismatch or 1 for equality (unlike memcmp), hinted at + * by the 'eq' in the name. */ +unsigned smemeq(const void *av, const void *bv, size_t len); /* Encode a single UTF-8 character. Assumes that illegal characters * (such as things in the surrogate range, or > 0x10FFFF) have already diff --git a/utils/smemeq.c b/utils/smemeq.c index 2692d134..ce3761ec 100644 --- a/utils/smemeq.c +++ b/utils/smemeq.c @@ -8,7 +8,7 @@ #include "defs.h" #include "misc.h" -bool smemeq(const void *av, const void *bv, size_t len) +unsigned smemeq(const void *av, const void *bv, size_t len) { const unsigned char *a = (const unsigned char *)av; const unsigned char *b = (const unsigned char *)bv; -- cgit v1.2.3 From e66e1ebeae6fcd6671db2b41a4193cfb137cb16a Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 15 Apr 2022 17:18:58 +0100 Subject: testcrypt: permit multiple OO function prefixes for a type. This means if I have functions like foo_subfoo_bar and foo_baz that both operate on a foo, the Python testcrypt system can translate both into .bar() and .baz() methods on the object, even though they don't start with the same prefix. --- test/testcrypt.py | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/test/testcrypt.py b/test/testcrypt.py index 61196446..6c0e95ce 100644 --- a/test/testcrypt.py +++ b/test/testcrypt.py @@ -87,19 +87,20 @@ class ChildProcess(object): childprocess = ChildProcess() method_prefixes = { - 'val_wpoint': 'ecc_weierstrass_', - 'val_mpoint': 'ecc_montgomery_', - 'val_epoint': 'ecc_edwards_', - 'val_hash': 'ssh_hash_', - 'val_mac': 'ssh2_mac_', - 'val_key': 'ssh_key_', - 'val_cipher': 'ssh_cipher_', - 'val_dh': 'dh_', - 'val_ecdh': 'ssh_ecdhkex_', - 'val_rsakex': 'ssh_rsakex_', - 'val_prng': 'prng_', - 'val_pcs': 'pcs_', - 'val_pockle': 'pockle_', + 'val_wpoint': ['ecc_weierstrass_'], + 'val_mpoint': ['ecc_montgomery_'], + 'val_epoint': ['ecc_edwards_'], + 'val_hash': ['ssh_hash_'], + 'val_mac': ['ssh2_mac_'], + 'val_key': ['ssh_key_'], + 'val_cipher': ['ssh_cipher_'], + 'val_dh': ['dh_'], + 'val_ecdh': ['ssh_ecdhkex_'], + 'val_rsakex': ['ssh_rsakex_'], + 'val_prng': ['prng_'], + 'val_pcs': ['pcs_'], + 'val_pockle': ['pockle_'], + 'val_ntruencodeschedule': ['ntru_encode_schedule_', 'ntru_'], } method_lists = {t: [] for t in method_prefixes} @@ -420,10 +421,12 @@ def _setup(scope): scope[function] = func if len(argtypes) > 0: t = argtypes[0][0] - if (t in method_prefixes and - function.startswith(method_prefixes[t])): - methodname = function[len(method_prefixes[t]):] - method_lists[t].append((methodname, func)) + if t in method_prefixes: + for prefix in method_prefixes[t]: + if function.startswith(prefix): + methodname = function[len(prefix):] + method_lists[t].append((methodname, func)) + break _setup(globals()) del _setup -- cgit v1.2.3 From e103ab1fb6d6b3a41f2b53905f6eea26bdababe6 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 14 Apr 2022 06:23:11 +0100 Subject: Refactor handling of SSH kex shared secret. Until now, every kex method has represented the output as an mp_int. So we were storing it in the mp_int field s->K, and adding it to the exchange hash and key derivation hashes via put_mp_ssh2. But there's now going to be the first kex method that represents the output as a string (so that it might have the top bit set, or multiple leading zero bytes, without its length varying). So we now need to be more general. The most general thing it's sensible to do is to replace s->K with a strbuf containing _already-encoded_ data to become part of the hash, including length fields if necessary. So every existing kex method still derives an mp_int, but then immediately puts it into that strbuf using put_mp_ssh2 and frees it. --- ssh/kex2-client.c | 26 +++++++++++++++++++------- ssh/kex2-server.c | 18 +++++++++++++----- ssh/transport2.c | 28 ++++++++++++++++------------ ssh/transport2.h | 3 ++- 4 files changed, 50 insertions(+), 25 deletions(-) diff --git a/ssh/kex2-client.c b/ssh/kex2-client.c index 9a8f75e2..633360ec 100644 --- a/ssh/kex2-client.c +++ b/ssh/kex2-client.c @@ -156,7 +156,9 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) return; } } - s->K = dh_find_K(s->dh_ctx, s->f); + mp_int *K = dh_find_K(s->dh_ctx, s->f); + put_mp_ssh2(s->kex_shared_secret, K); + mp_free(K); /* We assume everything from now on will be quick, and it might * involve user interaction. */ @@ -230,13 +232,15 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) { ptrlen keydata = get_string(pktin); put_stringpl(s->exhash, keydata); - s->K = ssh_ecdhkex_getkey(s->ecdh_key, keydata); - if (!get_err(pktin) && !s->K) { + mp_int *K = ssh_ecdhkex_getkey(s->ecdh_key, keydata); + if (!get_err(pktin) && !K) { ssh_proto_error(s->ppl.ssh, "Received invalid elliptic curve " "point in ECDH reply"); *aborted = true; return; } + put_mp_ssh2(s->kex_shared_secret, K); + mp_free(K); } s->sigdata = get_string(pktin); @@ -485,7 +489,9 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) return; } } - s->K = dh_find_K(s->dh_ctx, s->f); + mp_int *K = dh_find_K(s->dh_ctx, s->f); + put_mp_ssh2(s->kex_shared_secret, K); + mp_free(K); /* We assume everything from now on will be quick, and it might * involve user interaction. */ @@ -584,15 +590,21 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) strbuf *buf, *outstr; mp_int *tmp = mp_random_bits(nbits - 1); - s->K = mp_power_2(nbits - 1); - mp_add_into(s->K, s->K, tmp); + mp_int *K = mp_power_2(nbits - 1); + mp_add_into(K, K, tmp); mp_free(tmp); /* * Encode this as an mpint. */ buf = strbuf_new_nm(); - put_mp_ssh2(buf, s->K); + put_mp_ssh2(buf, K); + + /* + * Store a copy as the output shared secret from the kex. + */ + put_mp_ssh2(s->kex_shared_secret, K); + mp_free(K); /* * Encrypt it with the given RSA key. diff --git a/ssh/kex2-server.c b/ssh/kex2-server.c index 3c017077..9657589b 100644 --- a/ssh/kex2-server.c +++ b/ssh/kex2-server.c @@ -161,7 +161,9 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) return; } } - s->K = dh_find_K(s->dh_ctx, s->f); + mp_int *K = dh_find_K(s->dh_ctx, s->f); + put_mp_ssh2(s->kex_shared_secret, K); + mp_free(K); if (dh_is_gex(s->kex_alg)) { if (s->dh_got_size_bounds) @@ -217,13 +219,15 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) ptrlen keydata = get_string(pktin); put_stringpl(s->exhash, keydata); - s->K = ssh_ecdhkex_getkey(s->ecdh_key, keydata); - if (!get_err(pktin) && !s->K) { + mp_int *K = ssh_ecdhkex_getkey(s->ecdh_key, keydata); + if (!get_err(pktin) && !K) { ssh_proto_error(s->ppl.ssh, "Received invalid elliptic curve " "point in ECDH initial packet"); *aborted = true; return; } + put_mp_ssh2(s->kex_shared_secret, K); + mp_free(K); } pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEX_ECDH_REPLY); @@ -301,19 +305,23 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) return; } + mp_int *K; { ptrlen encrypted_secret = get_string(pktin); put_stringpl(s->exhash, encrypted_secret); - s->K = ssh_rsakex_decrypt( + K = ssh_rsakex_decrypt( s->rsa_kex_key, s->kex_alg->hash, encrypted_secret); } - if (!s->K) { + if (!K) { ssh_proto_error(s->ppl.ssh, "Unable to decrypt RSA kex secret"); *aborted = true; return; } + put_mp_ssh2(s->kex_shared_secret, K); + mp_free(K); + if (s->rsa_kex_key_needs_freeing) { ssh_rsakex_freekey(s->rsa_kex_key); sfree(s->rsa_kex_key); diff --git a/ssh/transport2.c b/ssh/transport2.c index 2f9b02af..4ce4156d 100644 --- a/ssh/transport2.c +++ b/ssh/transport2.c @@ -219,7 +219,7 @@ static void ssh2_transport_free(PacketProtocolLayer *ppl) if (s->f) mp_free(s->f); if (s->p) mp_free(s->p); if (s->g) mp_free(s->g); - if (s->K) mp_free(s->K); + if (s->kex_shared_secret) strbuf_free(s->kex_shared_secret); if (s->dh_ctx) dh_cleanup(s->dh_ctx); if (s->rsa_kex_key_needs_freeing) { @@ -245,7 +245,7 @@ static void ssh2_transport_free(PacketProtocolLayer *ppl) */ static void ssh2_mkkey( struct ssh2_transport_state *s, strbuf *out, - mp_int *K, unsigned char *H, char chr, int keylen) + strbuf *kex_shared_secret, unsigned char *H, char chr, int keylen) { int hlen = s->kex_alg->hash->hlen; int keylen_padded; @@ -273,7 +273,7 @@ static void ssh2_mkkey( /* First hlen bytes. */ h = ssh_hash_new(s->kex_alg->hash); if (!(s->ppl.remote_bugs & BUG_SSH2_DERIVEKEY)) - put_mp_ssh2(h, K); + put_datapl(h, ptrlen_from_strbuf(kex_shared_secret)); put_data(h, H, hlen); put_byte(h, chr); put_data(h, s->session_id, s->session_id_len); @@ -285,7 +285,7 @@ static void ssh2_mkkey( ssh_hash_reset(h); if (!(s->ppl.remote_bugs & BUG_SSH2_DERIVEKEY)) - put_mp_ssh2(h, K); + put_datapl(h, ptrlen_from_strbuf(kex_shared_secret)); put_data(h, H, hlen); for (offset = hlen; offset < keylen_padded; offset += hlen) { @@ -1093,7 +1093,7 @@ static bool ssh2_scan_kexinits( void ssh2transport_finalise_exhash(struct ssh2_transport_state *s) { - put_mp_ssh2(s->exhash, s->K); + put_datapl(s->exhash, ptrlen_from_strbuf(s->kex_shared_secret)); assert(ssh_hash_alg(s->exhash)->hlen <= sizeof(s->exchange_hash)); ssh_hash_final(s->exhash, s->exchange_hash); s->exhash = NULL; @@ -1363,6 +1363,9 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl) * Actually perform the key exchange. */ s->exhash = ssh_hash_new(s->kex_alg->hash); + if (s->kex_shared_secret) + strbuf_free(s->kex_shared_secret); + s->kex_shared_secret = strbuf_new_nm(); put_stringz(s->exhash, s->client_greeting); put_stringz(s->exhash, s->server_greeting); put_string(s->exhash, s->client_kexinit->u, s->client_kexinit->len); @@ -1416,14 +1419,14 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl) strbuf *mac_key = strbuf_new_nm(); if (s->out.cipher) { - ssh2_mkkey(s, cipher_iv, s->K, s->exchange_hash, + ssh2_mkkey(s, cipher_iv, s->kex_shared_secret, s->exchange_hash, 'A' + s->out.mkkey_adjust, s->out.cipher->blksize); - ssh2_mkkey(s, cipher_key, s->K, s->exchange_hash, + ssh2_mkkey(s, cipher_key, s->kex_shared_secret, s->exchange_hash, 'C' + s->out.mkkey_adjust, s->out.cipher->padded_keybytes); } if (s->out.mac) { - ssh2_mkkey(s, mac_key, s->K, s->exchange_hash, + ssh2_mkkey(s, mac_key, s->kex_shared_secret, s->exchange_hash, 'E' + s->out.mkkey_adjust, s->out.mac->keylen); } @@ -1508,14 +1511,14 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl) strbuf *mac_key = strbuf_new_nm(); if (s->in.cipher) { - ssh2_mkkey(s, cipher_iv, s->K, s->exchange_hash, + ssh2_mkkey(s, cipher_iv, s->kex_shared_secret, s->exchange_hash, 'A' + s->in.mkkey_adjust, s->in.cipher->blksize); - ssh2_mkkey(s, cipher_key, s->K, s->exchange_hash, + ssh2_mkkey(s, cipher_key, s->kex_shared_secret, s->exchange_hash, 'C' + s->in.mkkey_adjust, s->in.cipher->padded_keybytes); } if (s->in.mac) { - ssh2_mkkey(s, mac_key, s->K, s->exchange_hash, + ssh2_mkkey(s, mac_key, s->kex_shared_secret, s->exchange_hash, 'E' + s->in.mkkey_adjust, s->in.mac->keylen); } @@ -1533,7 +1536,8 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl) /* * Free shared secret. */ - mp_free(s->K); s->K = NULL; + strbuf_free(s->kex_shared_secret); + s->kex_shared_secret = NULL; /* * Update the specials menu to list the remaining uncertified host diff --git a/ssh/transport2.h b/ssh/transport2.h index 5f0df80e..aaec91a8 100644 --- a/ssh/transport2.h +++ b/ssh/transport2.h @@ -167,7 +167,8 @@ struct ssh2_transport_state { int nbits, pbits; bool warn_kex, warn_hk, warn_cscipher, warn_sccipher; - mp_int *p, *g, *e, *f, *K; + mp_int *p, *g, *e, *f; + strbuf *kex_shared_secret; strbuf *outgoing_kexinit, *incoming_kexinit; strbuf *client_kexinit, *server_kexinit; /* aliases to the above */ int kex_init_value, kex_reply_value; -- cgit v1.2.3 From 422a89e208910523d654db5e670433c89730c8bb Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 14 Apr 2022 06:38:30 +0100 Subject: Use C99 named initialisers in all ssh_kex instances. No functional change, but this will allow me to add more fields to that structure without breaking the existing initialisers. --- crypto/diffie-hellman.c | 56 +++++++++++++++++++++++++++++++++++-------------- crypto/ecc-ssh.c | 36 ++++++++++++++++++++----------- crypto/rsa.c | 12 +++++++---- 3 files changed, 72 insertions(+), 32 deletions(-) diff --git a/crypto/diffie-hellman.c b/crypto/diffie-hellman.c index 914167bb..78e6fab2 100644 --- a/crypto/diffie-hellman.c +++ b/crypto/diffie-hellman.c @@ -40,8 +40,11 @@ static const struct dh_extra extra_group1 = { }; const ssh_kex ssh_diffiehellman_group1_sha1 = { - "diffie-hellman-group1-sha1", "group1", - KEXTYPE_DH, &ssh_sha1, &extra_group1, + .name = "diffie-hellman-group1-sha1", + .groupname = "group1", + .main_type = KEXTYPE_DH, + .hash = &ssh_sha1, + .extra = &extra_group1, }; static const ssh_kex *const group1_list[] = { @@ -55,13 +58,19 @@ static const struct dh_extra extra_group14 = { }; const ssh_kex ssh_diffiehellman_group14_sha256 = { - "diffie-hellman-group14-sha256", "group14", - KEXTYPE_DH, &ssh_sha256, &extra_group14, + .name = "diffie-hellman-group14-sha256", + .groupname = "group14", + .main_type = KEXTYPE_DH, + .hash = &ssh_sha256, + .extra = &extra_group14, }; const ssh_kex ssh_diffiehellman_group14_sha1 = { - "diffie-hellman-group14-sha1", "group14", - KEXTYPE_DH, &ssh_sha1, &extra_group14, + .name = "diffie-hellman-group14-sha1", + .groupname = "group14", + .main_type = KEXTYPE_DH, + .hash = &ssh_sha1, + .extra = &extra_group14, }; static const ssh_kex *const group14_list[] = { @@ -76,13 +85,19 @@ const ssh_kexes ssh_diffiehellman_group14 = { static const struct dh_extra extra_gex = { true }; static const ssh_kex ssh_diffiehellman_gex_sha256 = { - "diffie-hellman-group-exchange-sha256", NULL, - KEXTYPE_DH, &ssh_sha256, &extra_gex, + .name = "diffie-hellman-group-exchange-sha256", + .groupname = NULL, + .main_type = KEXTYPE_DH, + .hash = &ssh_sha256, + .extra = &extra_gex, }; static const ssh_kex ssh_diffiehellman_gex_sha1 = { - "diffie-hellman-group-exchange-sha1", NULL, - KEXTYPE_DH, &ssh_sha1, &extra_gex, + .name = "diffie-hellman-group-exchange-sha1", + .groupname = NULL, + .main_type = KEXTYPE_DH, + .hash = &ssh_sha1, + .extra = &extra_gex, }; static const ssh_kex *const gex_list[] = { @@ -107,18 +122,27 @@ const ssh_kexes ssh_diffiehellman_gex = { lenof(gex_list), gex_list }; #define GSS_KRB5_OID_HASH "toWM5Slw5Ew8Mqkay+al2g==" static const ssh_kex ssh_gssk5_diffiehellman_gex_sha1 = { - "gss-gex-sha1-" GSS_KRB5_OID_HASH, NULL, - KEXTYPE_GSS, &ssh_sha1, &extra_gex, + .name = "gss-gex-sha1-" GSS_KRB5_OID_HASH, + .groupname = NULL, + .main_type = KEXTYPE_GSS, + .hash = &ssh_sha1, + .extra = &extra_gex, }; static const ssh_kex ssh_gssk5_diffiehellman_group14_sha1 = { - "gss-group14-sha1-" GSS_KRB5_OID_HASH, "group14", - KEXTYPE_GSS, &ssh_sha1, &extra_group14, + .name = "gss-group14-sha1-" GSS_KRB5_OID_HASH, + .groupname = "group14", + .main_type = KEXTYPE_GSS, + .hash = &ssh_sha1, + .extra = &extra_group14, }; static const ssh_kex ssh_gssk5_diffiehellman_group1_sha1 = { - "gss-group1-sha1-" GSS_KRB5_OID_HASH, "group1", - KEXTYPE_GSS, &ssh_sha1, &extra_group1, + .name = "gss-group1-sha1-" GSS_KRB5_OID_HASH, + .groupname = "group1", + .main_type = KEXTYPE_GSS, + .hash = &ssh_sha1, + .extra = &extra_group1, }; static const ssh_kex *const gssk5_sha1_kex_list[] = { diff --git a/crypto/ecc-ssh.c b/crypto/ecc-ssh.c index d246d2b7..b2eb6e51 100644 --- a/crypto/ecc-ssh.c +++ b/crypto/ecc-ssh.c @@ -1563,13 +1563,17 @@ static const struct eckex_extra kex_extra_curve25519 = { ssh_ecdhkex_m_getkey, }; const ssh_kex ssh_ec_kex_curve25519 = { - "curve25519-sha256", NULL, KEXTYPE_ECDH, - &ssh_sha256, &kex_extra_curve25519, + .name = "curve25519-sha256", + .main_type = KEXTYPE_ECDH, + .hash = &ssh_sha256, + .extra = &kex_extra_curve25519, }; /* Pre-RFC alias */ const ssh_kex ssh_ec_kex_curve25519_libssh = { - "curve25519-sha256@libssh.org", NULL, KEXTYPE_ECDH, - &ssh_sha256, &kex_extra_curve25519, + .name = "curve25519-sha256@libssh.org", + .main_type = KEXTYPE_ECDH, + .hash = &ssh_sha256, + .extra = &kex_extra_curve25519, }; static const struct eckex_extra kex_extra_curve448 = { @@ -1580,8 +1584,10 @@ static const struct eckex_extra kex_extra_curve448 = { ssh_ecdhkex_m_getkey, }; const ssh_kex ssh_ec_kex_curve448 = { - "curve448-sha512", NULL, KEXTYPE_ECDH, - &ssh_sha512, &kex_extra_curve448, + .name = "curve448-sha512", + .main_type = KEXTYPE_ECDH, + .hash = &ssh_sha512, + .extra = &kex_extra_curve448, }; static const struct eckex_extra kex_extra_nistp256 = { @@ -1592,8 +1598,10 @@ static const struct eckex_extra kex_extra_nistp256 = { ssh_ecdhkex_w_getkey, }; const ssh_kex ssh_ec_kex_nistp256 = { - "ecdh-sha2-nistp256", NULL, KEXTYPE_ECDH, - &ssh_sha256, &kex_extra_nistp256, + .name = "ecdh-sha2-nistp256", + .main_type = KEXTYPE_ECDH, + .hash = &ssh_sha256, + .extra = &kex_extra_nistp256, }; static const struct eckex_extra kex_extra_nistp384 = { @@ -1604,8 +1612,10 @@ static const struct eckex_extra kex_extra_nistp384 = { ssh_ecdhkex_w_getkey, }; const ssh_kex ssh_ec_kex_nistp384 = { - "ecdh-sha2-nistp384", NULL, KEXTYPE_ECDH, - &ssh_sha384, &kex_extra_nistp384, + .name = "ecdh-sha2-nistp384", + .main_type = KEXTYPE_ECDH, + .hash = &ssh_sha384, + .extra = &kex_extra_nistp384, }; static const struct eckex_extra kex_extra_nistp521 = { @@ -1616,8 +1626,10 @@ static const struct eckex_extra kex_extra_nistp521 = { ssh_ecdhkex_w_getkey, }; const ssh_kex ssh_ec_kex_nistp521 = { - "ecdh-sha2-nistp521", NULL, KEXTYPE_ECDH, - &ssh_sha512, &kex_extra_nistp521, + .name = "ecdh-sha2-nistp521", + .main_type = KEXTYPE_ECDH, + .hash = &ssh_sha512, + .extra = &kex_extra_nistp521, }; static const ssh_kex *const ec_kex_list[] = { diff --git a/crypto/rsa.c b/crypto/rsa.c index ef832868..1afa766d 100644 --- a/crypto/rsa.c +++ b/crypto/rsa.c @@ -1092,13 +1092,17 @@ static const struct ssh_rsa_kex_extra ssh_rsa_kex_extra_sha1 = { 1024 }; static const struct ssh_rsa_kex_extra ssh_rsa_kex_extra_sha256 = { 2048 }; static const ssh_kex ssh_rsa_kex_sha1 = { - "rsa1024-sha1", NULL, KEXTYPE_RSA, - &ssh_sha1, &ssh_rsa_kex_extra_sha1, + .name = "rsa1024-sha1", + .main_type = KEXTYPE_RSA, + .hash = &ssh_sha1, + .extra = &ssh_rsa_kex_extra_sha1, }; static const ssh_kex ssh_rsa_kex_sha256 = { - "rsa2048-sha256", NULL, KEXTYPE_RSA, - &ssh_sha256, &ssh_rsa_kex_extra_sha256, + .name = "rsa2048-sha256", + .main_type = KEXTYPE_RSA, + .hash = &ssh_sha256, + .extra = &ssh_rsa_kex_extra_sha256, }; static const ssh_kex *const rsa_kex_list[] = { -- cgit v1.2.3 From e59ee9655453982fe8a99b57e34eb9eb7682bc4b Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 14 Apr 2022 07:04:33 +0100 Subject: Refactor ecdh_kex into an organised vtable. This is already slightly nice because it lets me separate the Weierstrass and Montgomery code more completely, without having to have a vtable tucked into dh->extra. But more to the point, it will allow completely different kex methods to fit into the same framework later. To that end, I've moved more of the descriptive message generation into the vtable, and also provided the constructor with a flag that will let it do different things in client and server. Also, following on from a previous commit, I've arranged that the new API returns arbitrary binary data for the exchange hash, rather than an mp_int. An upcoming implementation of this interface will want to return an encoded string instead of an encoded mp_int. --- crypto/ecc-ssh.c | 212 +++++++++++++++++++++++++------------------------- defs.h | 1 + ssh.h | 41 +++++++--- ssh/kex2-client.c | 22 +++--- ssh/kex2-server.c | 19 +++-- ssh/transport2.c | 2 +- test/cryptsuite.py | 30 +++---- test/testcrypt-func.h | 8 +- test/testcrypt.c | 14 +++- 9 files changed, 194 insertions(+), 155 deletions(-) diff --git a/crypto/ecc-ssh.c b/crypto/ecc-ssh.c index b2eb6e51..e57e8bb1 100644 --- a/crypto/ecc-ssh.c +++ b/crypto/ecc-ssh.c @@ -1367,121 +1367,131 @@ const ssh_keyalg ssh_ecdsa_nistp521 = { }; /* ---------------------------------------------------------------------- - * Exposed ECDH interface + * Exposed ECDH interfaces */ struct eckex_extra { struct ec_curve *(*curve)(void); - void (*setup)(ecdh_key *dh); - void (*cleanup)(ecdh_key *dh); - void (*getpublic)(ecdh_key *dh, BinarySink *bs); - mp_int *(*getkey)(ecdh_key *dh, ptrlen remoteKey); }; -struct ecdh_key { +typedef struct ecdh_key_w { const struct eckex_extra *extra; const struct ec_curve *curve; mp_int *private; - union { - WeierstrassPoint *w_public; - MontgomeryPoint *m_public; - }; -}; + WeierstrassPoint *w_public; + + ecdh_key ek; +} ecdh_key_w; -const char *ssh_ecdhkex_curve_textname(const ssh_kex *kex) +typedef struct ecdh_key_m { + const struct eckex_extra *extra; + const struct ec_curve *curve; + mp_int *private; + MontgomeryPoint *m_public; + + ecdh_key ek; +} ecdh_key_m; + +ecdh_key *ssh_ecdhkex_w_new(const ssh_kex *kex, bool is_server) { const struct eckex_extra *extra = (const struct eckex_extra *)kex->extra; - struct ec_curve *curve = extra->curve(); - return curve->textname; -} + const struct ec_curve *curve = extra->curve(); + + ecdh_key_w *dhw = snew(ecdh_key_w); + dhw->ek.vt = kex->ecdh_vt; + dhw->extra = extra; + dhw->curve = curve; -static void ssh_ecdhkex_w_setup(ecdh_key *dh) -{ mp_int *one = mp_from_integer(1); - dh->private = mp_random_in_range(one, dh->curve->w.G_order); + dhw->private = mp_random_in_range(one, dhw->curve->w.G_order); mp_free(one); - dh->w_public = ecc_weierstrass_multiply(dh->curve->w.G, dh->private); + dhw->w_public = ecc_weierstrass_multiply(dhw->curve->w.G, dhw->private); + + return &dhw->ek; } -static void ssh_ecdhkex_m_setup(ecdh_key *dh) +ecdh_key *ssh_ecdhkex_m_new(const ssh_kex *kex, bool is_server) { + const struct eckex_extra *extra = (const struct eckex_extra *)kex->extra; + const struct ec_curve *curve = extra->curve(); + + ecdh_key_m *dhm = snew(ecdh_key_m); + dhm->ek.vt = kex->ecdh_vt; + dhm->extra = extra; + dhm->curve = curve; + strbuf *bytes = strbuf_new_nm(); - random_read(strbuf_append(bytes, dh->curve->fieldBytes), - dh->curve->fieldBytes); + random_read(strbuf_append(bytes, dhm->curve->fieldBytes), + dhm->curve->fieldBytes); - dh->private = mp_from_bytes_le(ptrlen_from_strbuf(bytes)); + dhm->private = mp_from_bytes_le(ptrlen_from_strbuf(bytes)); /* Ensure the private key has the highest valid bit set, and no * bits _above_ the highest valid one */ - mp_reduce_mod_2to(dh->private, dh->curve->fieldBits); - mp_set_bit(dh->private, dh->curve->fieldBits - 1, 1); + mp_reduce_mod_2to(dhm->private, dhm->curve->fieldBits); + mp_set_bit(dhm->private, dhm->curve->fieldBits - 1, 1); /* Clear a curve-specific number of low bits */ - for (unsigned bit = 0; bit < dh->curve->m.log2_cofactor; bit++) - mp_set_bit(dh->private, bit, 0); + for (unsigned bit = 0; bit < dhm->curve->m.log2_cofactor; bit++) + mp_set_bit(dhm->private, bit, 0); strbuf_free(bytes); - dh->m_public = ecc_montgomery_multiply(dh->curve->m.G, dh->private); -} + dhm->m_public = ecc_montgomery_multiply(dhm->curve->m.G, dhm->private); -ecdh_key *ssh_ecdhkex_newkey(const ssh_kex *kex) -{ - const struct eckex_extra *extra = (const struct eckex_extra *)kex->extra; - const struct ec_curve *curve = extra->curve(); - - ecdh_key *dh = snew(ecdh_key); - dh->extra = extra; - dh->curve = curve; - dh->extra->setup(dh); - return dh; + return &dhm->ek; } static void ssh_ecdhkex_w_getpublic(ecdh_key *dh, BinarySink *bs) { - put_wpoint(bs, dh->w_public, dh->curve, true); + ecdh_key_w *dhw = container_of(dh, ecdh_key_w, ek); + put_wpoint(bs, dhw->w_public, dhw->curve, true); } static void ssh_ecdhkex_m_getpublic(ecdh_key *dh, BinarySink *bs) { + ecdh_key_m *dhm = container_of(dh, ecdh_key_m, ek); mp_int *x; - ecc_montgomery_get_affine(dh->m_public, &x); - for (size_t i = 0; i < dh->curve->fieldBytes; ++i) + ecc_montgomery_get_affine(dhm->m_public, &x); + for (size_t i = 0; i < dhm->curve->fieldBytes; ++i) put_byte(bs, mp_get_byte(x, i)); mp_free(x); } -void ssh_ecdhkex_getpublic(ecdh_key *dh, BinarySink *bs) +static bool ssh_ecdhkex_w_getkey(ecdh_key *dh, ptrlen remoteKey, + BinarySink *bs) { - dh->extra->getpublic(dh, bs); -} + ecdh_key_w *dhw = container_of(dh, ecdh_key_w, ek); -static mp_int *ssh_ecdhkex_w_getkey(ecdh_key *dh, ptrlen remoteKey) -{ - WeierstrassPoint *remote_p = ecdsa_decode(remoteKey, dh->curve); + WeierstrassPoint *remote_p = ecdsa_decode(remoteKey, dhw->curve); if (!remote_p) - return NULL; + return false; if (ecc_weierstrass_is_identity(remote_p)) { /* Not a sensible Diffie-Hellman input value */ ecc_weierstrass_point_free(remote_p); - return NULL; + return false; } - WeierstrassPoint *p = ecc_weierstrass_multiply(remote_p, dh->private); + WeierstrassPoint *p = ecc_weierstrass_multiply(remote_p, dhw->private); mp_int *x; ecc_weierstrass_get_affine(p, &x, NULL); + put_mp_ssh2(bs, x); + mp_free(x); ecc_weierstrass_point_free(remote_p); ecc_weierstrass_point_free(p); - return x; + return true; } -static mp_int *ssh_ecdhkex_m_getkey(ecdh_key *dh, ptrlen remoteKey) +static bool ssh_ecdhkex_m_getkey(ecdh_key *dh, ptrlen remoteKey, + BinarySink *bs) { + ecdh_key_m *dhm = container_of(dh, ecdh_key_m, ek); + mp_int *remote_x = mp_from_bytes_le(remoteKey); /* Per RFC 7748 section 5, discard any set bits of the other @@ -1489,18 +1499,18 @@ static mp_int *ssh_ecdhkex_m_getkey(ecdh_key *dh, ptrlen remoteKey) * to represent all valid values. However, an overlarge value that * still fits into the remaining number of bits is accepted, and * will be reduced mod p. */ - mp_reduce_mod_2to(remote_x, dh->curve->fieldBits); + mp_reduce_mod_2to(remote_x, dhm->curve->fieldBits); MontgomeryPoint *remote_p = ecc_montgomery_point_new( - dh->curve->m.mc, remote_x); + dhm->curve->m.mc, remote_x); mp_free(remote_x); - MontgomeryPoint *p = ecc_montgomery_multiply(remote_p, dh->private); + MontgomeryPoint *p = ecc_montgomery_multiply(remote_p, dhm->private); if (ecc_montgomery_is_identity(p)) { ecc_montgomery_point_free(remote_p); ecc_montgomery_point_free(p); - return NULL; + return false; } mp_int *x; @@ -1524,48 +1534,54 @@ static mp_int *ssh_ecdhkex_m_getkey(ecdh_key *dh, ptrlen remoteKey) * with the _low_ byte zero, i.e. a multiple of 256. */ strbuf *sb = strbuf_new(); - for (size_t i = 0; i < dh->curve->fieldBytes; ++i) + for (size_t i = 0; i < dhm->curve->fieldBytes; ++i) put_byte(sb, mp_get_byte(x, i)); mp_free(x); x = mp_from_bytes_be(ptrlen_from_strbuf(sb)); strbuf_free(sb); + put_mp_ssh2(bs, x); + mp_free(x); - return x; + return true; } -mp_int *ssh_ecdhkex_getkey(ecdh_key *dh, ptrlen remoteKey) +static void ssh_ecdhkex_w_free(ecdh_key *dh) { - return dh->extra->getkey(dh, remoteKey); + ecdh_key_w *dhw = container_of(dh, ecdh_key_w, ek); + mp_free(dhw->private); + ecc_weierstrass_point_free(dhw->w_public); + sfree(dhw); } -static void ssh_ecdhkex_w_cleanup(ecdh_key *dh) +static void ssh_ecdhkex_m_free(ecdh_key *dh) { - ecc_weierstrass_point_free(dh->w_public); + ecdh_key_m *dhm = container_of(dh, ecdh_key_m, ek); + mp_free(dhm->private); + ecc_montgomery_point_free(dhm->m_public); + sfree(dhm); } -static void ssh_ecdhkex_m_cleanup(ecdh_key *dh) +static char *ssh_ecdhkex_description(const ssh_kex *kex) { - ecc_montgomery_point_free(dh->m_public); + const struct eckex_extra *extra = (const struct eckex_extra *)kex->extra; + const struct ec_curve *curve = extra->curve(); + return dupprintf("ECDH key exchange with curve %s", curve->textname); } -void ssh_ecdhkex_freekey(ecdh_key *dh) -{ - mp_free(dh->private); - dh->extra->cleanup(dh); - sfree(dh); -} +static const struct eckex_extra kex_extra_curve25519 = { ec_curve25519 }; -static const struct eckex_extra kex_extra_curve25519 = { - ec_curve25519, - ssh_ecdhkex_m_setup, - ssh_ecdhkex_m_cleanup, - ssh_ecdhkex_m_getpublic, - ssh_ecdhkex_m_getkey, +static const ecdh_keyalg ssh_ecdhkex_m_alg = { + .new = ssh_ecdhkex_m_new, + .free = ssh_ecdhkex_m_free, + .getpublic = ssh_ecdhkex_m_getpublic, + .getkey = ssh_ecdhkex_m_getkey, + .description = ssh_ecdhkex_description, }; const ssh_kex ssh_ec_kex_curve25519 = { .name = "curve25519-sha256", .main_type = KEXTYPE_ECDH, .hash = &ssh_sha256, + .ecdh_vt = &ssh_ecdhkex_m_alg, .extra = &kex_extra_curve25519, }; /* Pre-RFC alias */ @@ -1573,62 +1589,50 @@ const ssh_kex ssh_ec_kex_curve25519_libssh = { .name = "curve25519-sha256@libssh.org", .main_type = KEXTYPE_ECDH, .hash = &ssh_sha256, + .ecdh_vt = &ssh_ecdhkex_m_alg, .extra = &kex_extra_curve25519, }; -static const struct eckex_extra kex_extra_curve448 = { - ec_curve448, - ssh_ecdhkex_m_setup, - ssh_ecdhkex_m_cleanup, - ssh_ecdhkex_m_getpublic, - ssh_ecdhkex_m_getkey, -}; +static const struct eckex_extra kex_extra_curve448 = { ec_curve448 }; const ssh_kex ssh_ec_kex_curve448 = { .name = "curve448-sha512", .main_type = KEXTYPE_ECDH, .hash = &ssh_sha512, + .ecdh_vt = &ssh_ecdhkex_m_alg, .extra = &kex_extra_curve448, }; -static const struct eckex_extra kex_extra_nistp256 = { - ec_p256, - ssh_ecdhkex_w_setup, - ssh_ecdhkex_w_cleanup, - ssh_ecdhkex_w_getpublic, - ssh_ecdhkex_w_getkey, +static const ecdh_keyalg ssh_ecdhkex_w_alg = { + .new = ssh_ecdhkex_w_new, + .free = ssh_ecdhkex_w_free, + .getpublic = ssh_ecdhkex_w_getpublic, + .getkey = ssh_ecdhkex_w_getkey, + .description = ssh_ecdhkex_description, }; +static const struct eckex_extra kex_extra_nistp256 = { ec_p256 }; const ssh_kex ssh_ec_kex_nistp256 = { .name = "ecdh-sha2-nistp256", .main_type = KEXTYPE_ECDH, .hash = &ssh_sha256, + .ecdh_vt = &ssh_ecdhkex_w_alg, .extra = &kex_extra_nistp256, }; -static const struct eckex_extra kex_extra_nistp384 = { - ec_p384, - ssh_ecdhkex_w_setup, - ssh_ecdhkex_w_cleanup, - ssh_ecdhkex_w_getpublic, - ssh_ecdhkex_w_getkey, -}; +static const struct eckex_extra kex_extra_nistp384 = { ec_p384 }; const ssh_kex ssh_ec_kex_nistp384 = { .name = "ecdh-sha2-nistp384", .main_type = KEXTYPE_ECDH, .hash = &ssh_sha384, + .ecdh_vt = &ssh_ecdhkex_w_alg, .extra = &kex_extra_nistp384, }; -static const struct eckex_extra kex_extra_nistp521 = { - ec_p521, - ssh_ecdhkex_w_setup, - ssh_ecdhkex_w_cleanup, - ssh_ecdhkex_w_getpublic, - ssh_ecdhkex_w_getkey, -}; +static const struct eckex_extra kex_extra_nistp521 = { ec_p521 }; const ssh_kex ssh_ec_kex_nistp521 = { .name = "ecdh-sha2-nistp521", .main_type = KEXTYPE_ECDH, .hash = &ssh_sha512, + .ecdh_vt = &ssh_ecdhkex_w_alg, .extra = &kex_extra_nistp521, }; diff --git a/defs.h b/defs.h index 354c208f..1edf3442 100644 --- a/defs.h +++ b/defs.h @@ -167,6 +167,7 @@ typedef struct ssh_cipher ssh_cipher; typedef struct ssh2_ciphers ssh2_ciphers; typedef struct dh_ctx dh_ctx; typedef struct ecdh_key ecdh_key; +typedef struct ecdh_keyalg ecdh_keyalg; typedef struct dlgparam dlgparam; diff --git a/ssh.h b/ssh.h index 8187b394..60880482 100644 --- a/ssh.h +++ b/ssh.h @@ -615,15 +615,6 @@ strbuf *ssh_rsakex_encrypt( mp_int *ssh_rsakex_decrypt( RSAKey *key, const ssh_hashalg *h, ptrlen ciphertext); -/* - * SSH2 ECDH key exchange functions - */ -const char *ssh_ecdhkex_curve_textname(const ssh_kex *kex); -ecdh_key *ssh_ecdhkex_newkey(const ssh_kex *kex); -void ssh_ecdhkex_freekey(ecdh_key *key); -void ssh_ecdhkex_getpublic(ecdh_key *key, BinarySink *bs); -mp_int *ssh_ecdhkex_getkey(ecdh_key *key, ptrlen remoteKey); - /* * Helper function for k generation in DSA, reused in ECDSA */ @@ -806,6 +797,9 @@ struct ssh_kex { const char *name, *groupname; enum { KEXTYPE_DH, KEXTYPE_RSA, KEXTYPE_ECDH, KEXTYPE_GSS } main_type; const ssh_hashalg *hash; + union { /* publicly visible data for each type */ + const ecdh_keyalg *ecdh_vt; /* for KEXTYPE_ECDH */ + }; const void *extra; /* private to the kex methods */ }; @@ -884,6 +878,35 @@ static inline const char *ssh_key_ssh_id(ssh_key *key) static inline const char *ssh_key_cache_id(ssh_key *key) { return key->vt->cache_id; } +/* + * SSH2 ECDH key exchange vtable + */ +struct ecdh_key { + const ecdh_keyalg *vt; +}; +struct ecdh_keyalg { + /* Unusually, the 'new' method here doesn't directly take a vt + * pointer, because it will also need the containing ssh_kex + * structure for top-level parameters, and since that contains a + * vt pointer anyway, we might as well _only_ pass that. */ + ecdh_key *(*new)(const ssh_kex *kex, bool is_server); + void (*free)(ecdh_key *key); + void (*getpublic)(ecdh_key *key, BinarySink *bs); + bool (*getkey)(ecdh_key *key, ptrlen remoteKey, BinarySink *bs); + char *(*description)(const ssh_kex *kex); +}; +static inline ecdh_key *ecdh_key_new(const ssh_kex *kex, bool is_server) +{ return kex->ecdh_vt->new(kex, is_server); } +static inline void ecdh_key_free(ecdh_key *key) +{ key->vt->free(key); } +static inline void ecdh_key_getpublic(ecdh_key *key, BinarySink *bs) +{ key->vt->getpublic(key, bs); } +static inline bool ecdh_key_getkey(ecdh_key *key, ptrlen remoteKey, + BinarySink *bs) +{ return key->vt->getkey(key, remoteKey, bs); } +static inline char *ecdh_keyalg_description(const ssh_kex *kex) +{ return kex->ecdh_vt->description(kex); } + /* * Enumeration of signature flags from draft-miller-ssh-agent-02 */ diff --git a/ssh/kex2-client.c b/ssh/kex2-client.c index 633360ec..ff78840a 100644 --- a/ssh/kex2-client.c +++ b/ssh/kex2-client.c @@ -185,13 +185,14 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) mp_free(s->p); s->p = NULL; } } else if (s->kex_alg->main_type == KEXTYPE_ECDH) { - - ppl_logevent("Doing ECDH key exchange with curve %s and hash %s", - ssh_ecdhkex_curve_textname(s->kex_alg), + char *desc = ecdh_keyalg_description(s->kex_alg); + ppl_logevent("Doing %s, using hash %s", desc, ssh_hash_alg(s->exhash)->text_name); + sfree(desc); + s->ppl.bpp->pls->kctx = SSH2_PKTCTX_ECDHKEX; - s->ecdh_key = ssh_ecdhkex_newkey(s->kex_alg); + s->ecdh_key = ecdh_key_new(s->kex_alg, false); if (!s->ecdh_key) { ssh_sw_abort(s->ppl.ssh, "Unable to generate key for ECDH"); *aborted = true; @@ -201,7 +202,7 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEX_ECDH_INIT); { strbuf *pubpoint = strbuf_new(); - ssh_ecdhkex_getpublic(s->ecdh_key, BinarySink_UPCAST(pubpoint)); + ecdh_key_getpublic(s->ecdh_key, BinarySink_UPCAST(pubpoint)); put_stringsb(pktout, pubpoint); } @@ -224,7 +225,7 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) { strbuf *pubpoint = strbuf_new(); - ssh_ecdhkex_getpublic(s->ecdh_key, BinarySink_UPCAST(pubpoint)); + ecdh_key_getpublic(s->ecdh_key, BinarySink_UPCAST(pubpoint)); put_string(s->exhash, pubpoint->u, pubpoint->len); strbuf_free(pubpoint); } @@ -232,15 +233,14 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) { ptrlen keydata = get_string(pktin); put_stringpl(s->exhash, keydata); - mp_int *K = ssh_ecdhkex_getkey(s->ecdh_key, keydata); - if (!get_err(pktin) && !K) { + bool ok = ecdh_key_getkey(s->ecdh_key, keydata, + BinarySink_UPCAST(s->kex_shared_secret)); + if (!get_err(pktin) && !ok) { ssh_proto_error(s->ppl.ssh, "Received invalid elliptic curve " "point in ECDH reply"); *aborted = true; return; } - put_mp_ssh2(s->kex_shared_secret, K); - mp_free(K); } s->sigdata = get_string(pktin); @@ -250,7 +250,7 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) return; } - ssh_ecdhkex_freekey(s->ecdh_key); + ecdh_key_free(s->ecdh_key); s->ecdh_key = NULL; #ifndef NO_GSSAPI } else if (s->kex_alg->main_type == KEXTYPE_GSS) { diff --git a/ssh/kex2-server.c b/ssh/kex2-server.c index 9657589b..570d7750 100644 --- a/ssh/kex2-server.c +++ b/ssh/kex2-server.c @@ -191,12 +191,12 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) mp_free(s->p); s->p = NULL; } } else if (s->kex_alg->main_type == KEXTYPE_ECDH) { - ppl_logevent("Doing ECDH key exchange with curve %s and hash %s", - ssh_ecdhkex_curve_textname(s->kex_alg), + char *desc = ecdh_keyalg_description(s->kex_alg); + ppl_logevent("Doing %s, using hash %s", desc, ssh_hash_alg(s->exhash)->text_name); - s->ppl.bpp->pls->kctx = SSH2_PKTCTX_ECDHKEX; + sfree(desc); - s->ecdh_key = ssh_ecdhkex_newkey(s->kex_alg); + s->ecdh_key = ecdh_key_new(s->kex_alg, true); if (!s->ecdh_key) { ssh_sw_abort(s->ppl.ssh, "Unable to generate key for ECDH"); *aborted = true; @@ -219,29 +219,28 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) ptrlen keydata = get_string(pktin); put_stringpl(s->exhash, keydata); - mp_int *K = ssh_ecdhkex_getkey(s->ecdh_key, keydata); - if (!get_err(pktin) && !K) { + bool ok = ecdh_key_getkey(s->ecdh_key, keydata, + BinarySink_UPCAST(s->kex_shared_secret)); + if (!get_err(pktin) && !ok) { ssh_proto_error(s->ppl.ssh, "Received invalid elliptic curve " "point in ECDH initial packet"); *aborted = true; return; } - put_mp_ssh2(s->kex_shared_secret, K); - mp_free(K); } pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEX_ECDH_REPLY); put_stringpl(pktout, s->hostkeydata); { strbuf *pubpoint = strbuf_new(); - ssh_ecdhkex_getpublic(s->ecdh_key, BinarySink_UPCAST(pubpoint)); + ecdh_key_getpublic(s->ecdh_key, BinarySink_UPCAST(pubpoint)); put_string(s->exhash, pubpoint->u, pubpoint->len); put_stringsb(pktout, pubpoint); } put_stringsb(pktout, finalise_and_sign_exhash(s)); pq_push(s->ppl.out_pq, pktout); - ssh_ecdhkex_freekey(s->ecdh_key); + ecdh_key_free(s->ecdh_key); s->ecdh_key = NULL; } else if (s->kex_alg->main_type == KEXTYPE_GSS) { ssh_sw_abort(s->ppl.ssh, "GSS key exchange not supported in server"); diff --git a/ssh/transport2.c b/ssh/transport2.c index 4ce4156d..e00507e1 100644 --- a/ssh/transport2.c +++ b/ssh/transport2.c @@ -227,7 +227,7 @@ static void ssh2_transport_free(PacketProtocolLayer *ppl) sfree(s->rsa_kex_key); } if (s->ecdh_key) - ssh_ecdhkex_freekey(s->ecdh_key); + ecdh_key_free(s->ecdh_key); if (s->exhash) ssh_hash_free(s->exhash); strbuf_free(s->outgoing_kexinit); diff --git a/test/cryptsuite.py b/test/cryptsuite.py index 4971a599..958980b9 100755 --- a/test/cryptsuite.py +++ b/test/cryptsuite.py @@ -1772,13 +1772,13 @@ class crypt(MyTestBase): ] with random_prng("doesn't matter"): - ecdh25519 = ssh_ecdhkex_newkey('curve25519') - ecdh448 = ssh_ecdhkex_newkey('curve448') + ecdh25519 = ecdh_key_new('curve25519', False) + ecdh448 = ecdh_key_new('curve448', False) for pub in bad_keys_25519: - key = ssh_ecdhkex_getkey(ecdh25519, unhex(pub)) + key = ecdh_key_getkey(ecdh25519, unhex(pub)) self.assertEqual(key, None) for pub in bad_keys_448: - key = ssh_ecdhkex_getkey(ecdh448, unhex(pub)) + key = ecdh_key_getkey(ecdh448, unhex(pub)) self.assertEqual(key, None) def testPRNG(self): @@ -3107,9 +3107,9 @@ class standard_test_vectors(MyTestBase): for method, priv, pub, expected in rfc7748s5_2: with queued_specific_random_data(unhex(priv)): - ecdh = ssh_ecdhkex_newkey(method) - key = ssh_ecdhkex_getkey(ecdh, unhex(pub)) - self.assertEqual(int(key), expected) + ecdh = ecdh_key_new(method, False) + key = ecdh_key_getkey(ecdh, unhex(pub)) + self.assertEqual(key, ssh2_mpint(expected)) # Bidirectional tests, consisting of the input random number # strings for both parties, and the expected public values and @@ -3131,15 +3131,15 @@ class standard_test_vectors(MyTestBase): for method, apriv, apub, bpriv, bpub, expected in rfc7748s6: with queued_specific_random_data(unhex(apriv)): - alice = ssh_ecdhkex_newkey(method) + alice = ecdh_key_new(method, False) with queued_specific_random_data(unhex(bpriv)): - bob = ssh_ecdhkex_newkey(method) - self.assertEqualBin(ssh_ecdhkex_getpublic(alice), unhex(apub)) - self.assertEqualBin(ssh_ecdhkex_getpublic(bob), unhex(bpub)) - akey = ssh_ecdhkex_getkey(alice, unhex(bpub)) - bkey = ssh_ecdhkex_getkey(bob, unhex(apub)) - self.assertEqual(int(akey), expected) - self.assertEqual(int(bkey), expected) + bob = ecdh_key_new(method, False) + self.assertEqualBin(ecdh_key_getpublic(alice), unhex(apub)) + self.assertEqualBin(ecdh_key_getpublic(bob), unhex(bpub)) + akey = ecdh_key_getkey(alice, unhex(bpub)) + bkey = ecdh_key_getkey(bob, unhex(apub)) + self.assertEqual(akey, ssh2_mpint(expected)) + self.assertEqual(bkey, ssh2_mpint(expected)) def testCRC32(self): self.assertEqual(crc32_rfc1662("123456789"), 0xCBF43926) diff --git a/test/testcrypt-func.h b/test/testcrypt-func.h index 2cb0b3dc..1188d2c4 100644 --- a/test/testcrypt-func.h +++ b/test/testcrypt-func.h @@ -346,11 +346,11 @@ FUNC(val_mpint, dh_find_K, ARG(val_dh, ctx), ARG(val_mpint, f)) /* * Elliptic-curve Diffie-Hellman. */ -FUNC(val_ecdh, ssh_ecdhkex_newkey, ARG(ecdh_alg, alg)) -FUNC(void, ssh_ecdhkex_getpublic, ARG(val_ecdh, key), +FUNC(val_ecdh, ecdh_key_new, ARG(ecdh_alg, alg), ARG(boolean, is_server)) +FUNC(void, ecdh_key_getpublic, ARG(val_ecdh, key), ARG(out_val_string_binarysink, pub)) -FUNC(opt_val_mpint, ssh_ecdhkex_getkey, ARG(val_ecdh, key), - ARG(val_string_ptrlen, pub)) +FUNC_WRAPPED(opt_val_string, ecdh_key_getkey, ARG(val_ecdh, key), + ARG(val_string_ptrlen, pub)) /* * RSA key exchange, and also the BinarySource get function diff --git a/test/testcrypt.c b/test/testcrypt.c index 01b5c501..cfe28ae2 100644 --- a/test/testcrypt.c +++ b/test/testcrypt.c @@ -87,7 +87,7 @@ uint64_t prng_reseed_time_ms(void) X(cipher, ssh_cipher *, ssh_cipher_free(v)) \ X(mac, ssh2_mac *, ssh2_mac_free(v)) \ X(dh, dh_ctx *, dh_cleanup(v)) \ - X(ecdh, ecdh_key *, ssh_ecdhkex_freekey(v)) \ + X(ecdh, ecdh_key *, ecdh_key_free(v)) \ X(rsakex, RSAKey *, ssh_rsakex_freekey(v)) \ X(rsa, RSAKey *, rsa_free(v)) \ X(prng, prng *, prng_free(v)) \ @@ -787,6 +787,18 @@ static RSAKey *rsa_new(void) return rsa; } +strbuf *ecdh_key_getkey_wrapper(ecdh_key *ek, ptrlen remoteKey) +{ + /* Fold the boolean return value in C into the string return value + * for this purpose, by returning NULL on failure */ + strbuf *sb = strbuf_new(); + if (!ecdh_key_getkey(ek, remoteKey, BinarySink_UPCAST(sb))) { + strbuf_free(sb); + return NULL; + } + return sb; +} + strbuf *rsa_ssh1_encrypt_wrapper(ptrlen input, RSAKey *key) { /* Fold the boolean return value in C into the string return value -- cgit v1.2.3 From faf1601a5549eda9298f72f7c0f68f39c8f97764 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 15 Apr 2022 17:19:47 +0100 Subject: Implement OpenSSH 9.x's NTRU Prime / Curve25519 kex. This consists of DJB's 'Streamlined NTRU Prime' quantum-resistant cryptosystem, currently in round 3 of the NIST post-quantum key exchange competition; it's run in parallel with ordinary Curve25519, and generates a shared secret combining the output of both systems. (Hence, even if you don't trust this newfangled NTRU Prime thing at all, it's at least no _less_ secure than the kex you were using already.) As the OpenSSH developers point out, key exchange is the most urgent thing to make quantum-resistant, even before working quantum computers big enough to break crypto become available, because a break of the kex algorithm can be applied retroactively to recordings of your past sessions. By contrast, authentication is a real-time protocol, and can only be broken by a quantum computer if there's one available to attack you _already_. I've implemented both sides of the mechanism, so that PuTTY and Uppity both support it. In my initial testing, the two sides can both interoperate with the appropriate half of OpenSSH, and also (of course, but it would be embarrassing to mess it up) with each other. --- config.c | 2 + crypto/CMakeLists.txt | 1 + crypto/ntru.c | 1915 +++++++++++++++++++++++++++++++++++++++++++++++++ crypto/ntru.h | 53 ++ defs.h | 2 + putty.h | 1 + settings.c | 1 + ssh.h | 1 + ssh/transport2.c | 4 + test/cryptsuite.py | 101 +++ test/testcrypt-func.h | 29 + test/testcrypt.c | 169 +++++ test/testcrypt.py | 9 + test/testsc.c | 70 ++ 14 files changed, 2358 insertions(+) create mode 100644 crypto/ntru.c create mode 100644 crypto/ntru.h diff --git a/config.c b/config.c index 8114a586..1fbbb3e8 100644 --- a/config.c +++ b/config.c @@ -567,6 +567,8 @@ static void kexlist_handler(union control *ctrl, dlgparam *dlg, { "Diffie-Hellman group exchange", KEX_DHGEX }, { "RSA-based key exchange", KEX_RSA }, { "ECDH key exchange", KEX_ECDH }, + { "NTRU Prime / Curve25519 hybrid kex" + " (quantum-resistant)", KEX_NTRU_HYBRID }, { "-- warn below here --", KEX_WARN } }; diff --git a/crypto/CMakeLists.txt b/crypto/CMakeLists.txt index c6420361..71083107 100644 --- a/crypto/CMakeLists.txt +++ b/crypto/CMakeLists.txt @@ -20,6 +20,7 @@ add_sources_from_current_dir(crypto mac_simple.c md5.c mpint.c + ntru.c prng.c pubkey-pem.c pubkey-ppk.c diff --git a/crypto/ntru.c b/crypto/ntru.c new file mode 100644 index 00000000..88d4f084 --- /dev/null +++ b/crypto/ntru.c @@ -0,0 +1,1915 @@ +/* + * Implementation of OpenSSH 9.x's hybrid key exchange protocol + * sntrup761x25519-sha512@openssh.com . + * + * This consists of the 'Streamlined NTRU Prime' quantum-resistant + * cryptosystem, run in parallel with ordinary Curve25519 to generate + * a shared secret combining the output of both systems. + * + * (Hence, even if you don't trust this newfangled NTRU Prime thing at + * all, it's at least no _less_ secure than the kex you were using + * already.) + * + * References for the NTRU Prime cryptosystem, up to and including + * binary encodings of public and private keys and the exact preimages + * of the hashes used in key exchange: + * + * https://ntruprime.cr.yp.to/ + * https://ntruprime.cr.yp.to/nist/ntruprime-20201007.pdf + * + * The SSH protocol layer is not documented anywhere I could find (as + * of 2022-04-15, not even in OpenSSH's PROTOCOL.* files). I had to + * read OpenSSH's source code to find out how it worked, and the + * answer is as follows: + * + * This hybrid kex method is treated for SSH purposes as a form of + * elliptic-curve Diffie-Hellman, and shares the same SSH message + * sequence: client sends SSH2_MSG_KEX_ECDH_INIT containing its public + * half, server responds with SSH2_MSG_KEX_ECDH_REPLY containing _its_ + * public half plus the host key and signature on the shared secret. + * + * (This is a bit of a fudge, because unlike actual ECDH, this kex + * method is asymmetric: one side sends a public key, and the other + * side encrypts something with it and sends the ciphertext back. So + * while the normal ECDH implementations can compute the two sides + * independently in parallel, this system reusing the same messages + * has to be serial. But the order of the messages _is_ firmly + * specified in SSH ECDH, so it works anyway.) + * + * For this kex method, SSH2_MSG_KEX_ECDH_INIT still contains a single + * SSH 'string', which consists of the concatenation of a Streamlined + * NTRU Prime public key with the Curve25519 public value. (Both of + * these have fixed length in bytes, so there's no ambiguity in the + * concatenation.) + * + * SSH2_MSG_KEX_ECDH_REPLY is mostly the same as usual. The only + * string in the packet that varies is the second one, which would + * normally contain the server's public elliptic curve point. Instead, + * it now contains the concatenation of + * + * - a Streamlined NTRU Prime ciphertext + * - the 'confirmation hash' specified in ntruprime-20201007.pdf, + * hashing the plaintext of that ciphertext together with the + * public key + * - the Curve25519 public point as usual. + * + * Again, all three of those elements have fixed lengths. + * + * The client decrypts the ciphertext, checks the confirmation hash, + * and if successful, generates the 'session hash' specified in + * ntruprime-20201007.pdf, which is 32 bytes long and is the ultimate + * output of the Streamlined NTRU Prime key exchange. + * + * The output of the hybrid kex method as a whole is an SSH 'string' + * of length 64 containing the SHA-512 hash of the concatenatio of + * + * - the Streamlined NTRU Prime session hash (32 bytes) + * - the Curve25519 shared secret (32 bytes). + * + * That string is included directly into the SSH exchange hash and key + * derivation hashes, in place of the mpint that comes out of most + * other kex methods. + */ + +#include +#include +#include + +#include "putty.h" +#include "ssh.h" +#include "mpint.h" +#include "ntru.h" + +/* ---------------------------------------------------------------------- + * Preliminaries: we're going to need to do modular arithmetic on + * small values (considerably smaller than 2^16), and we need to do it + * without using integer division which might not be time-safe. + * + * The strategy for this is the same as I used in + * mp_mod_known_integer: see there for the proofs. The basic idea is + * that we precompute the reciprocal of our modulus as a fixed-point + * number, and use that to get an approximate quotient which we + * subtract off. For these integer sizes, precomputing a fixed-point + * reciprocal of the form (2^48 / modulus) leaves us at most off by 1 + * in the quotient, so there's a single (time-safe) trial subtraction + * at the end. + * + * (It's possible that some speed could be gained by not reducing + * fully at every step. But then you'd have to carefully identify all + * the places in the algorithm where things are compared to zero. This + * was the easiest way to get it all working in the first place.) + */ + +/* Precompute the reciprocal */ +static uint64_t reciprocal_for_reduction(uint16_t q) +{ + return ((uint64_t)1 << 48) / q; +} + +/* Reduce x mod q, assuming qrecip == reciprocal_for_reduction(q) */ +static uint16_t reduce(uint32_t x, uint16_t q, uint64_t qrecip) +{ + uint64_t unshifted_quot = x * qrecip; + uint64_t quot = unshifted_quot >> 48; + uint16_t reduced = x - quot * q; + reduced -= q * (1 & ((q-1 - reduced) >> 15)); + return reduced; +} + +/* Reduce x mod q as above, but also return the quotient */ +static uint16_t reduce_with_quot(uint32_t x, uint32_t *quot_out, + uint16_t q, uint64_t qrecip) +{ + uint64_t unshifted_quot = x * qrecip; + uint64_t quot = unshifted_quot >> 48; + uint16_t reduced = x - quot * q; + uint64_t extraquot = (1 & ((q-1 - reduced) >> 15)); + reduced -= extraquot * q; + *quot_out = quot + extraquot; + return reduced; +} + +/* Invert x mod q, assuming it's nonzero. (For time-safety, no check + * is made for zero; it just returns 0.) */ +static uint16_t invert(uint16_t x, uint16_t q, uint64_t qrecip) +{ + /* Fermat inversion: compute x^(q-2), since x^(q-1) == 1. */ + uint32_t sq = x, bit = 1, acc = 1, exp = q-2; + while (1) { + if (exp & bit) { + acc = reduce(acc * sq, q, qrecip); + exp &= ~bit; + if (!exp) + return acc; + } + sq = reduce(sq * sq, q, qrecip); + bit <<= 1; + } +} + +/* Check whether x == 0, time-safely, and return 1 if it is or 0 otherwise. */ +static unsigned iszero(uint16_t x) +{ + return 1 & ~((x + 0xFFFF) >> 16); +} + +/* + * Handy macros to cut down on all those extra function parameters. In + * the common case where a function is working mod the same modulus + * throughout (and has called it q), you can just write 'SETUP;' at + * the top and then call REDUCE(...) and INVERT(...) without having to + * write out q and qrecip every time. + */ +#define SETUP uint64_t qrecip = reciprocal_for_reduction(q) +#define REDUCE(x) reduce(x, q, qrecip) +#define INVERT(x) invert(x, q, qrecip) + +/* ---------------------------------------------------------------------- + * Quotient-ring functions. + * + * NTRU Prime works with two similar but different quotient rings: + * + * Z_q[x] / where p,q are the prime parameters of the system + * Z_3[x] / with the same p, but coefficients mod 3. + * + * The former is a field (every nonzero element is invertible), + * because the system parameters are chosen such that x^p-x-1 is + * invertible over Z_q. The latter is not a field (or not necessarily, + * and in particular, not for the value of p we use here). + * + * In these core functions, you pass in the modulus you want as the + * parameter q, which is either the 'real' q specified in the system + * parameters, or 3 if you're doing one of the mod-3 parts of the + * algorithm. + */ + +/* + * Multiply two elements of a quotient ring. + * + * 'a' and 'b' are arrays of exactly p coefficients, with constant + * term first. 'out' is an array the same size to write the inverse + * into. + */ +void ntru_ring_multiply(uint16_t *out, const uint16_t *a, const uint16_t *b, + unsigned p, unsigned q) +{ + SETUP; + + /* + * Strategy: just compute the full product with 2p coefficients, + * and then reduce it mod x^p-x-1 by working downwards from the + * top coefficient replacing x^{p+k} with (x+1)x^k for k = ...,1,0. + * + * Possibly some speed could be gained here by doing the recursive + * Karatsuba optimisation for the initial multiplication? But I + * haven't tried it. + */ + uint32_t *unreduced = snewn(2*p, uint32_t); + for (unsigned i = 0; i < 2*p; i++) + unreduced[i] = 0; + for (unsigned i = 0; i < p; i++) + for (unsigned j = 0; j < p; j++) + unreduced[i+j] = REDUCE(unreduced[i+j] + a[i] * b[j]); + + for (unsigned i = 2*p - 1; i >= p; i--) { + unreduced[i-p] += unreduced[i]; + unreduced[i-p+1] += unreduced[i]; + unreduced[i] = 0; + } + + for (unsigned i = 0; i < p; i++) + out[i] = REDUCE(unreduced[i]); + + smemclr(unreduced, 2*p * sizeof(*unreduced)); + sfree(unreduced); +} + +/* + * Invert an element of the quotient ring. + * + * 'in' is an array of exactly p coefficients, with constant term + * first. 'out' is an array the same size to write the inverse into. + * + * Method: essentially Stein's gcd algorithm, taking the gcd of the + * input (regarded as an element of Z_q[x] proper) and x^p-x-1. Given + * two polynomials over a field which are not both divisible by x, you + * can find their gcd by iterating the following procedure: + * + * - if one is divisible by x, divide off x + * - otherwise, subtract from the higher-degree one whatever scalar + * multiple of the lower-degree one will make it divisible by x, + * and _then_ divide off x + * + * Neither of these types of step changes the gcd of the two + * polynomials. + * + * Each step reduces the sum of the two polynomials' degree by at + * least one, as long as at least one of the degrees is positive. + * (Maybe more than one if all the stars align in the second case, if + * the subtraction cancels the leading term as well as the constant + * term.) So in at most deg A + deg B steps, we must have reached the + * situation where both polys are constants, and in one more step + * after that, one of them will be zero. Or rather, that's what + * happens in the case where A,B are coprime; if not, then one hits + * zero while the other is still nonzero. + * + * Then unwind all the transformations, to find a linear combination + * of the two original polynomials that yields the nonzero one of the + * two outputs. (In fact we only need the coefficient of 'in' in that + * linear combination, but we have to compute both halves, because + * they keep swapping round during the unwinding.) + */ +unsigned ntru_ring_invert(uint16_t *out, const uint16_t *in, + unsigned p, unsigned q) +{ + SETUP; + + /* Size of the polynomial arrays we'll work with */ + const size_t SIZE = p+1; + + /* Number of steps of the algorithm is the max possible value of + * deg A + deg B + 1, where deg A <= p-1 and deg B = p */ + const size_t STEPS = 2*p; + + /* Our two working polynomials */ + uint16_t *A = snewn(SIZE, uint16_t); + uint16_t *B = snewn(SIZE, uint16_t); + + /* History of what we did */ + uint16_t *multipliers = snewn(STEPS, uint16_t); + uint8_t *swaps = snewn(STEPS, uint8_t); + + /* Initialise A to the input */ + memcpy(A, in, p*sizeof(uint16_t)); + A[p] = 0; + + /* And initialise B to the quotient polynomial of the ring, x^p-x-1 */ + B[0] = B[1] = q-1; + for (size_t i = 2; i < p; i++) + B[i] = 0; + B[p] = 1; + + /* Run the gcd-finding algorithm. */ + for (size_t i = 0; i < STEPS; i++) { + /* + * First swap round so that A is the one we'll be dividing by 2. + * + * In the case where one of the two polys has a zero constant + * term, it's that one. In the other case, it's the one of + * smaller degree. We must compute both, and choose between + * them in a side-channel-safe way. + */ + unsigned x_divides_A = iszero(A[0]); + unsigned x_divides_B = iszero(B[0]); + unsigned B_is_bigger = 0; + { + unsigned not_seen_top_term_of_A = 1, not_seen_top_term_of_B = 1; + for (size_t j = SIZE; j-- > 0 ;) { + not_seen_top_term_of_A &= iszero(A[j]); + not_seen_top_term_of_B &= iszero(B[j]); + B_is_bigger |= (~not_seen_top_term_of_B & + not_seen_top_term_of_A); + } + } + unsigned need_swap = x_divides_B | (~x_divides_A & B_is_bigger); + uint16_t swap_mask = -need_swap; + for (size_t j = 0; j < SIZE; j++) { + uint16_t diff = (A[j] ^ B[j]) & swap_mask; + A[j] ^= diff; + B[j] ^= diff; + } + + /* + * Add a multiple of B to A to make A's constant term zero. In + * one of the two cases, A's constant term is already zero, so + * this will do nothing but take the same length of time as + * doing something, which is just what we want. + * + * Also, shift down by one in the course of doing this. + */ + uint16_t mult = REDUCE((q - A[0]) * INVERT(B[0])); + for (size_t j = 1; j < SIZE; j++) + A[j-1] = REDUCE(A[j] + mult * B[j]); + A[SIZE-1] = 0; + + /* + * Record what we just did. + */ + swaps[i] = need_swap; + multipliers[i] = mult; + } + + /* + * Now we expect that one of the polynomials is zero, and the + * other is zero except for the constant term. If so, then they + * are coprime, and we're going to return success. If not, they + * have a common factor. + */ + unsigned success = iszero(A[0]) ^ iszero(B[0]); + for (size_t j = 1; j < SIZE; j++) + success &= iszero(A[j]) & iszero(B[j]); + + /* + * Now unwind to make a linear combination of the two original + * polynomials that equals 1 (assuming we're going to return + * success). + * + * We make two polynomials Ac,Bc, with the intention that we'll + * preserve the invariant Ac*A + Bc*B = 1 as we rewind through the + * steps. + * + * Initially, we set the coefficient of the zero one of A,B to + * zero, and the coefficient of the constant one to be its + * inverse. + */ + uint16_t *Ac = snewn(SIZE, uint16_t); + uint16_t *Bc = snewn(SIZE, uint16_t); + for (size_t i = 1; i < SIZE; i++) + Ac[i] = Bc[i] = 0; + Ac[0] = INVERT(A[0]); + Bc[0] = INVERT(B[0]); + + for (size_t i = STEPS; i-- > 0 ;) { + /* + * The last thing we did in our step was always to divide A by + * x. That is, we currently have 1 as a linear combination of + * A and B, and now we need it as a linear combination of A*x + * and B. + * + * We have Ac*A + Bc*B = (Ac+k*B)*A + (Bc-k*A)*B for any k. + * So choose k such that Ac+k*B has zero constant term + * (possible since B has nonzero constant term), and then we + * have 1 = (Ac+k*B)/x * (A*x) + (Bc-k*A) * B. + */ + uint16_t minusk = REDUCE(Ac[0] * INVERT(B[0])); + uint16_t k = q - minusk; + for (size_t j = 1; j < SIZE; j++) + Ac[j-1] = REDUCE(Ac[j] + k * B[j]); + Ac[SIZE-1] = 0; + for (size_t j = 0; j < SIZE; j++) + Bc[j] = REDUCE(Bc[j] + minusk * A[j]); + + /* And unwind the shift of A itself. */ + memmove(A+1, A, (SIZE-1) * sizeof(*A)); + A[0] = 0; + + /* + * Before that, we added m*B to A. So our new A will be A-m*B. + * So we have 1 = Ac*A + Bc*B = Ac*(A-m*B) + (Bc+m*Ac)*B. + */ + uint16_t m = multipliers[i]; + uint16_t minusm = q - m; + for (size_t j = 0; j < SIZE; j++) + Bc[j] = REDUCE(Bc[j] + m * Ac[j]); + for (size_t j = 0; j < SIZE; j++) + A[j] = REDUCE(A[j] + minusm * B[j]); + + /* + * And before that, we conditionally swapped A,B. + */ + uint16_t swap_mask = -swaps[i]; + for (size_t j = 0; j < SIZE; j++) { + uint16_t diff; + diff = (A[j] ^ B[j]) & swap_mask; + A[j] ^= diff; + B[j] ^= diff; + diff = (Ac[j] ^ Bc[j]) & swap_mask; + Ac[j] ^= diff; + Bc[j] ^= diff; + } + } + + /* Done! Our coefficient Ac is the inverse, if one exists. */ + memcpy(out, Ac, p * sizeof(*out)); + + smemclr(A, SIZE * sizeof(*A)); + sfree(A); + smemclr(B, SIZE * sizeof(*B)); + sfree(B); + smemclr(Ac, SIZE * sizeof(*A)); + sfree(Ac); + smemclr(Bc, SIZE * sizeof(*B)); + sfree(Bc); + smemclr(multipliers, STEPS * sizeof(*multipliers)); + sfree(multipliers); + smemclr(swaps, STEPS * sizeof(*swaps)); + sfree(swaps); + + return success; +} + +/* + * Given an array of values mod q, convert each one to its + * minimum-absolute-value representative, and then reduce mod 3. + * + * Output values are 0, 1 and 0xFFFF, representing -1. + * + * (Normally our arrays of uint16_t are in 'minimal non-negative + * residue' form, so the output of this function is unusual. But it's + * useful to have it in this form so that it can be reused by + * ntru_round3. You can put it back to the usual representation using + * ntru_normalise, below.) + */ +void ntru_mod3(uint16_t *out, const uint16_t *in, unsigned p, unsigned q) +{ + uint64_t qrecip = reciprocal_for_reduction(q); + uint64_t recip3 = reciprocal_for_reduction(3); + + unsigned bias = q/2; + uint16_t adjust = 3 - reduce(bias-1, 3, recip3); + + for (unsigned i = 0; i < p; i++) { + uint16_t val = reduce(in[i] + bias, q, qrecip); + uint16_t residue = reduce(val + adjust, 3, recip3); + out[i] = residue - 1; + } +} + +/* + * Given an array of values mod q, round each one to the nearest + * multiple of 3 to its minimum-absolute-value representative. + * + * Output values are signed integers coerced to uint16_t, so again, + * use ntru_normalise afterwards to put them back to normal. + */ +void ntru_round3(uint16_t *out, const uint16_t *in, unsigned p, unsigned q) +{ + SETUP; + unsigned bias = q/2; + ntru_mod3(out, in, p, q); + for (unsigned i = 0; i < p; i++) + out[i] = REDUCE(in[i] + bias) - bias - out[i]; +} + +/* + * Given an array of signed integers coerced to uint16_t in the range + * [-q/2,+q/2], normalise them back to mod q values. + */ +static void ntru_normalise(uint16_t *out, const uint16_t *in, + unsigned p, unsigned q) +{ + for (unsigned i = 0; i < p; i++) + out[i] = in[i] + q * (in[i] >> 15); +} + +/* + * Given an array of values mod q, add a constant to each one. + */ +void ntru_bias(uint16_t *out, const uint16_t *in, unsigned bias, + unsigned p, unsigned q) +{ + SETUP; + for (unsigned i = 0; i < p; i++) + out[i] = REDUCE(in[i] + bias); +} + +/* + * Given an array of values mod q, multiply each one by a constant. + */ +void ntru_scale(uint16_t *out, const uint16_t *in, uint16_t scale, + unsigned p, unsigned q) +{ + SETUP; + for (unsigned i = 0; i < p; i++) + out[i] = REDUCE(in[i] * scale); +} + +/* + * Given an array of values mod 3, convert them to values mod q in a + * way that maps -1,0,+1 to -1,0,+1. + */ +void ntru_expand(uint16_t *out, const uint16_t *in, unsigned p, unsigned q) +{ + for (size_t i = 0; i < p; i++) { + uint16_t v = in[i]; + /* Map 2 to q-1, and leave 0 and 1 unchanged */ + v += (v >> 1) * (q-3); + out[i] = v; + } +} + +/* ---------------------------------------------------------------------- + * Implement the binary encoding from ntruprime-20201007.pdf, which is + * used to encode public keys and ciphertexts (though not plaintexts, + * which are done in a much simpler way). + * + * The general idea is that your encoder takes as input a list of + * small non-negative integers (r_i), and a sequence of limits (m_i) + * such that 0 <= r_i < m_i, and emits a sequence of bytes that encode + * all of these as tightly as reasonably possible. + * + * That's more general than is really needed, because in both the + * actual uses of this encoding, the input m_i are all the same! But + * the array of (r_i,m_i) pairs evolves during encoding, so they don't + * _stay_ all the same, so you still have to have all the generality. + * + * The encoding process makes a number of passes along the list of + * inputs. In each step, pairs of adjacent numbers are combined into + * one larger one by turning (r_i,m_i) and (r_{i+1},m_{i+1}) into the + * pair (r_i + m_i r_{i+1}, m_i m_{i+1}), i.e. so that the original + * numbers could be recovered by taking the quotient and remaiinder of + * the new r value by m_i. Then, if the new m_i is at least 2^14, we + * emit the low 8 bits of r_i to the output stream and reduce r_i and + * its limit correspondingly. So at the end of the pass, we've got + * half as many numbers still to encode, they're all still not too + * big, and we've emitted some amount of data into the output. Then do + * another pass, keep going until there's only one number left, and + * emit it little-endian. + * + * That's all very well, but how do you decode it again? DJB exhibits + * a pair of recursive functions that are supposed to be mutually + * inverse, but I didn't have any confidence that I'd be able to debug + * them sensibly if they turned out not to be (or rather, if I + * implemented one of them wrong). So I came up with my own strategy + * instead. + * + * In my strategy, we start by processing just the (m_i) into an + * 'encoding schedule' consisting of a sequence of simple + * instructions. The instructions operate on a FIFO queue of numbers, + * initialised to the original (r_i). The three instruction types are: + * + * - 'COMBINE': consume two numbers a,b from the head of the queue, + * combine them by calculating a + m*b for some specified m, and + * push the result on the tail of the queue. + * + * - 'BYTE': divide the tail element of the queue by 2^8 and emit the + * low bits into the output stream. + * + * - 'COPY': pop a number from the head of the queue and push it + * straight back on the tail. (Used for handling the leftover + * element at the end of a pass if the input to the pass was a list + * of odd length.) + * + * So we effectively implement DJB's encoding process in simulation, + * and instead of actually processing a set of (r_i), we 'compile' the + * process into a sequence of instructions that can be handed just the + * (r_i) later and encode them in the right way. At the end of the + * instructions, the queue is expected to have been reduced to length + * 1 and contain the single integer 0. + * + * The nice thing about this system is that each of those three + * instructions is easy to reverse. So you can also use the same + * instructions for decoding: start with a queue containing 0, and + * process the instructions in reverse order and reverse sense. So + * BYTE means to _consume_ a byte from the encoded data (starting from + * the rightmost end) and use it to make a queue element bigger; and + * COMBINE run in reverse pops a single element from one end of the + * queue, divides it by m, and pushes the quotient and remainder on + * the other end. + * + * (So it's easy to debug, because the queue passes through the exact + * same sequence of states during decoding that it did during + * encoding, just in reverse order.) + * + * Also, the encoding schedule comes with information about the + * expected size of the encoded data, because you can find that out + * easily by just counting the BYTE commands. + */ + +enum { + /* + * Command values appearing in the 'ops' array. ENC_COPY and + * ENC_BYTE are single values; values of the form + * (ENC_COMBINE_BASE + m) represent a COMBINE command with + * parameter m. + */ + ENC_COPY, ENC_BYTE, ENC_COMBINE_BASE +}; +struct NTRUEncodeSchedule { + /* + * Object representing a compiled set of encoding instructions. + * + * 'nvals' is the number of r_i we expect to encode. 'nops' is the + * number of encoding commands in the 'ops' list; 'opsize' is the + * physical size of the array, used during construction. + * + * 'endpos' is used to avoid a last-minute faff during decoding. + * We implement our FIFO of integers as a ring buffer of size + * 'nvals'. Encoding cycles round it some number of times, and the + * final 0 element ends up at some random location in the array. + * If we know _where_ the 0 ends up during encoding, we can put + * the initial 0 there at the start of decoding, and then when we + * finish reversing all the instructions, we'll end up with the + * output numbers already arranged at their correct positions, so + * that there's no need to rotate the array at the last minute. + */ + size_t nvals, endpos, nops, opsize; + uint32_t *ops; +}; +static inline void sched_append(NTRUEncodeSchedule *sched, uint16_t op) +{ + /* Helper function to append an operation to the schedule, and + * update endpos. */ + sgrowarray(sched->ops, sched->opsize, sched->nops); + sched->ops[sched->nops++] = op; + if (op != ENC_BYTE) + sched->endpos = (sched->endpos + 1) % sched->nvals; +} + +/* + * Take in the list of limit values (m_i) and compute the encoding + * schedule. + */ +NTRUEncodeSchedule *ntru_encode_schedule(const uint16_t *ms_in, size_t n) +{ + NTRUEncodeSchedule *sched = snew(NTRUEncodeSchedule); + sched->nvals = n; + sched->endpos = n-1; + sched->nops = sched->opsize = 0; + sched->ops = NULL; + + assert(n != 0); + + /* + * 'ms' is the list of (m_i) on input to the current pass. + * 'ms_new' is the list output from the current pass. After each + * pass we swap the arrays round. + */ + uint32_t *ms = snewn(n, uint32_t); + uint32_t *msnew = snewn(n, uint32_t); + for (size_t i = 0; i < n; i++) + ms[i] = ms_in[i]; + + while (n > 1) { + size_t nnew = 0; + for (size_t i = 0; i < n; i += 2) { + if (i+1 == n) { + /* + * Odd element at the end of the input list: just copy + * it unchanged to the output. + */ + sched_append(sched, ENC_COPY); + msnew[nnew++] = ms[i]; + break; + } + + /* + * Normal case: consume two elements from the input list + * and combine them. + */ + uint32_t m1 = ms[i], m2 = ms[i+1], m = m1*m2; + sched_append(sched, ENC_COMBINE_BASE + m1); + + /* + * And then, as long as the combined limit is big enough, + * emit an output byte from the bottom of it. + */ + while (m >= (1<<14)) { + sched_append(sched, ENC_BYTE); + m = (m + 0xFF) >> 8; + } + + /* + * Whatever is left after that, we emit into the output + * list and append to the fifo. + */ + msnew[nnew++] = m; + } + + /* + * End of pass. The output list of (m_i) now becomes the input + * list. + */ + uint32_t *tmp = ms; + ms = msnew; + n = nnew; + msnew = tmp; + } + + /* + * When that loop terminates, it's because there's exactly one + * number left to encode. (Or, technically, _at most_ one - but we + * don't support encoding a completely empty list in this + * implementation, because what would be the point?) That number + * is just emitted little-endian until its limit is 1 (meaning its + * only possible actual value is 0). + */ + assert(n == 1); + uint32_t m = ms[0]; + while (m > 1) { + sched_append(sched, ENC_BYTE); + m = (m + 0xFF) >> 8; + } + + sfree(ms); + sfree(msnew); + + return sched; +} + +void ntru_encode_schedule_free(NTRUEncodeSchedule *sched) +{ + sfree(sched->ops); + sfree(sched); +} + +/* + * Calculate the output length of the encoded data in bytes. + */ +size_t ntru_encode_schedule_length(NTRUEncodeSchedule *sched) +{ + size_t len = 0; + for (size_t i = 0; i < sched->nops; i++) + if (sched->ops[i] == ENC_BYTE) + len++; + return len; +} + +/* + * Retrieve the number of items encoded. (Used by testcrypt.) + */ +size_t ntru_encode_schedule_nvals(NTRUEncodeSchedule *sched) +{ + return sched->nvals; +} + +/* + * Actually encode a sequence of (r_i), emitting the output bytes to + * an arbitrary BinarySink. + */ +void ntru_encode(NTRUEncodeSchedule *sched, const uint16_t *rs_in, + BinarySink *bs) +{ + size_t n = sched->nvals; + uint32_t *rs = snewn(n, uint32_t); + for (size_t i = 0; i < n; i++) + rs[i] = rs_in[i]; + + /* + * The head and tail pointers of the queue are both 'full'. That + * is, rs[head] is the first element actually in the queue, and + * rs[tail] is the last element. + * + * So you append to the queue by first advancing 'tail' and then + * writing to rs[tail], whereas you consume from the queue by + * first reading rs[head] and _then_ advancing 'head'. + * + * The more normal thing would be to make 'tail' point to the + * first empty slot instead of the last full one. But then you'd + * have to faff about with modular arithmetic to find the last + * full slot for the BYTE command, so in this case, it's easier to + * do it the less usual way. + */ + size_t head = 0, tail = n-1; + + for (size_t i = 0; i < sched->nops; i++) { + uint16_t op = sched->ops[i]; + switch (op) { + case ENC_BYTE: + put_byte(bs, rs[tail] & 0xFF); + rs[tail] >>= 8; + break; + case ENC_COPY: { + uint32_t r = rs[head]; + head = (head + 1) % n; + tail = (tail + 1) % n; + rs[tail] = r; + break; + } + default: { + uint32_t r1 = rs[head]; + head = (head + 1) % n; + uint32_t r2 = rs[head]; + head = (head + 1) % n; + tail = (tail + 1) % n; + rs[tail] = r1 + (op - ENC_COMBINE_BASE) * r2; + break; + } + } + } + + /* + * Expect that we've ended up with a single zero in the queue, at + * exactly the position that the setup-time analysis predicted it. + */ + assert(head == sched->endpos); + assert(tail == sched->endpos); + assert(rs[head] == 0); + + smemclr(rs, n * sizeof(*rs)); + sfree(rs); +} + +/* + * Decode a ptrlen of binary data into a sequence of (r_i). The data + * is expected to be of exactly the right length (on pain of assertion + * failure). + */ +void ntru_decode(NTRUEncodeSchedule *sched, uint16_t *rs_out, ptrlen data) +{ + size_t n = sched->nvals; + const uint8_t *base = (const uint8_t *)data.ptr; + const uint8_t *pos = base + data.len; + + /* + * Initialise the queue to a single zero, at the 'endpos' position + * that will mean the final output is correctly aligned. + * + * 'head' and 'tail' have the same meanings as in encoding. So + * 'tail' is the location that BYTE modifies and COPY and COMBINE + * consume from, and 'head' is the location that COPY and COMBINE + * push on to. As in encoding, they both point at the extremal + * full slots in the array. + */ + uint32_t *rs = snewn(n, uint32_t); + size_t head = sched->endpos, tail = head; + rs[tail] = 0; + + for (size_t i = sched->nops; i-- > 0 ;) { + uint16_t op = sched->ops[i]; + switch (op) { + case ENC_BYTE: { + assert(pos > base); + uint8_t byte = *--pos; + rs[tail] = (rs[tail] << 8) | byte; + break; + } + case ENC_COPY: { + uint32_t r = rs[tail]; + tail = (tail + n - 1) % n; + head = (head + n - 1) % n; + rs[head] = r; + break; + } + default: { + uint32_t r = rs[tail]; + tail = (tail + n - 1) % n; + + uint32_t m = op - ENC_COMBINE_BASE; + uint64_t mrecip = reciprocal_for_reduction(m); + + uint32_t r1, r2; + r1 = reduce_with_quot(r, &r2, m, mrecip); + + head = (head + n - 1) % n; + rs[head] = r2; + head = (head + n - 1) % n; + rs[head] = r1; + break; + } + } + } + + assert(pos == base); + assert(head == 0); + assert(tail == n-1); + + for (size_t i = 0; i < n; i++) + rs_out[i] = rs[i]; + smemclr(rs, n * sizeof(*rs)); + sfree(rs); +} + +/* ---------------------------------------------------------------------- + * The actual public-key cryptosystem. + */ + +struct NTRUKeyPair { + unsigned p, q, w; + uint16_t *h; /* public key */ + uint16_t *f3, *ginv; /* private key */ + uint16_t *rho; /* for implicit rejection */ +}; + +/* Helper function to free an array of uint16_t containing a ring + * element, clearing it on the way since some of them are sensitive. */ +static void ring_free(uint16_t *val, unsigned p) +{ + smemclr(val, p*sizeof(*val)); + sfree(val); +} + +void ntru_keypair_free(NTRUKeyPair *keypair) +{ + ring_free(keypair->h, keypair->p); + ring_free(keypair->f3, keypair->p); + ring_free(keypair->ginv, keypair->p); + ring_free(keypair->rho, keypair->p); + sfree(keypair); +} + +/* Trivial accessors used by test programs. */ +unsigned ntru_keypair_p(NTRUKeyPair *keypair) { return keypair->p; } +const uint16_t *ntru_pubkey(NTRUKeyPair *keypair) { return keypair->h; } + +/* + * Generate a value of the class DJB describes as 'Short': it consists + * of p terms that are all either 0 or +1 or -1, and exactly w of them + * are not zero. + * + * Values of this kind are used for several purposes: part of the + * private key, a plaintext, and the 'rho' fake-plaintext value used + * for deliberately returning a duff but non-revealing session hash if + * things go wrong. + */ +void ntru_gen_short(uint16_t *v, unsigned p, unsigned w) +{ + /* + * Get enough random data to generate a polynomial all of whose p + * terms are in {0,+1,-1}, and exactly w of them are nonzero. + * + * We're going to need w * random bits to choose the nonzero + * values, and then (doing it the simplest way) log2(p!) bits to + * shuffle them, plus say 128 bits to ensure any fluctuations in + * uniformity are negligible. + * + * log2(p!) is a pain to calculate, so we'll bound it above by + * p*log2(p), which we bound in turn by p*16. + */ + size_t randbitpos = 16 * p + w + 128; + mp_int *randdata = mp_resize(mp_random_bits(randbitpos), randbitpos + 32); + + /* + * Initial value before shuffling: w randomly chosen values in + * {1,q-1}, plus zeroes to pad to length p. + */ + for (size_t i = 0; i < w; i++) + v[i] = 1 + mp_get_bit(randdata, --randbitpos); + for (size_t i = w; i < p; i++) + v[i] = 0; + + /* + * Hereafter we're going to extract random bits by multiplication, + * treating randdata as a large fixed-point number. + */ + mp_reduce_mod_2to(randdata, randbitpos); + + /* + * Shuffle. + */ + mp_int *x = mp_new(64); + for (size_t i = p-1; i > 0; i--) { + /* + * Decide which element to swap with v[i], potentially + * including i itself. + */ + mp_mul_integer_into(randdata, randdata, i+1); + mp_rshift_fixed_into(x, randdata, randbitpos); + mp_reduce_mod_2to(randdata, randbitpos); + size_t j = mp_get_integer(x); + + /* + * Swap it, which involves a constant-time selection loop over + * the whole eligible part of the array. This makes the + * shuffling quadratic-time overall. I'd be interested in a + * nicer algorithm, but this will do for now. + */ + for (size_t k = 0; k <= i; k++) { + uint16_t mask = -iszero(k ^ j); + uint16_t diff = mask & (v[k] ^ v[i]); + v[k] ^= diff; + v[i] ^= diff; + } + } + + mp_free(x); + mp_free(randdata); +} + +/* + * Make a single attempt at generating a key pair. This involves + * inventing random elements of both our quotient rings and hoping + * they're both invertible. + * + * They may not be, if you're unlucky. The element of Z_q/ + * will _almost_ certainly be invertible, because that is a field, so + * invertibility can only fail if you were so unlucky as to choose the + * all-0s element. But the element of Z_3/ may fail to be + * invertible because it has a common factor with x^p-x-1 (which, over + * Z_3, is not irreducible). + * + * So we can't guarantee to generate a key pair in constant time, + * because there's no predicting how many retries we'll need. However, + * this isn't a failure of side-channel safety, because we completely + * discard all the random numbers and state from each failed attempt. + * So if there were a side-channel leakage from a failure, the only + * thing it would give away would be a bunch of random numbers that + * turned out not to be used anyway. + * + * But a _successful_ call to this function should execute in a + * secret-independent manner, and this 'make a single attempt' + * function is exposed in the API so that 'testsc' can check that. + */ +NTRUKeyPair *ntru_keygen_attempt(unsigned p, unsigned q, unsigned w) +{ + /* + * First invent g, which is the one more likely to fail to invert. + * This is simply a uniformly random polynomial with p terms over + * Z_3. So we need p*log2(3) random bits for it, plus 128 for + * uniformity. It's easiest to bound log2(3) above by 2. + */ + size_t randbitpos = 2 * p + 128; + mp_int *randdata = mp_resize(mp_random_bits(randbitpos), randbitpos + 32); + + /* + * Select p random values from {0,1,2}. + */ + uint16_t *g = snewn(p, uint16_t); + mp_int *x = mp_new(64); + for (size_t i = 0; i < p; i++) { + mp_mul_integer_into(randdata, randdata, 3); + mp_rshift_fixed_into(x, randdata, randbitpos); + mp_reduce_mod_2to(randdata, randbitpos); + g[i] = mp_get_integer(x); + } + mp_free(x); + mp_free(randdata); + + /* + * Try to invert g over Z_3, and fail if it isn't invertible. + */ + uint16_t *ginv = snewn(p, uint16_t); + if (!ntru_ring_invert(ginv, g, p, 3)) { + ring_free(g, p); + ring_free(ginv, p); + return NULL; + } + + /* + * Fine; we have g. Now make up an f, and convert it to a + * polynomial over q. + */ + uint16_t *f = snewn(p, uint16_t); + ntru_gen_short(f, p, w); + ntru_expand(f, f, p, q); + + /* + * Multiply f by 3. + */ + uint16_t *f3 = snewn(p, uint16_t); + ntru_scale(f3, f, 3, p, q); + + /* + * Try to invert 3*f over Z_q. This should be _almost_ guaranteed + * to succeed, since Z_q/ is a field, so the only + * non-invertible value is 0. Even so, there _is_ one, so check + * the return value! + */ + uint16_t *f3inv = snewn(p, uint16_t); + if (!ntru_ring_invert(f3inv, f3, p, q)) { + ring_free(f, p); + ring_free(f3, p); + ring_free(f3inv, p); + ring_free(g, p); + ring_free(ginv, p); + return NULL; + } + + /* + * Make the public key, by converting g to a polynomial over q and + * then multiplying by f3inv. + */ + uint16_t *g_q = snewn(p, uint16_t); + ntru_expand(g_q, g, p, q); + uint16_t *h = snewn(p, uint16_t); + ntru_ring_multiply(h, g_q, f3inv, p, q); + + /* + * Make up rho, used to substitute for the plaintext in the + * session hash in case of confirmation failure. + */ + uint16_t *rho = snewn(p, uint16_t); + ntru_gen_short(rho, p, w); + + /* + * And we're done! Free everything except the pieces we're + * returning. + */ + NTRUKeyPair *keypair = snew(NTRUKeyPair); + keypair->p = p; + keypair->q = q; + keypair->w = w; + keypair->h = h; + keypair->f3 = f3; + keypair->ginv = ginv; + keypair->rho = rho; + ring_free(f, p); + ring_free(f3inv, p); + ring_free(g, p); + ring_free(g_q, p); + return keypair; +} + +/* + * The top-level key generation function for real use (as opposed to + * testsc): keep trying to make a key until you succeed. + */ +NTRUKeyPair *ntru_keygen(unsigned p, unsigned q, unsigned w) +{ + while (1) { + NTRUKeyPair *keypair = ntru_keygen_attempt(p, q, w); + if (keypair) + return keypair; + } +} + +/* + * Public-key encryption. + */ +void ntru_encrypt(uint16_t *ciphertext, const uint16_t *plaintext, + uint16_t *pubkey, unsigned p, unsigned q) +{ + uint16_t *r_q = snewn(p, uint16_t); + ntru_expand(r_q, plaintext, p, q); + + uint16_t *unrounded = snewn(p, uint16_t); + ntru_ring_multiply(unrounded, r_q, pubkey, p, q); + + ntru_round3(ciphertext, unrounded, p, q); + ntru_normalise(ciphertext, ciphertext, p, q); + + ring_free(r_q, p); + ring_free(unrounded, p); +} + +/* + * Public-key decryption. + */ +void ntru_decrypt(uint16_t *plaintext, const uint16_t *ciphertext, + NTRUKeyPair *keypair) +{ + unsigned p = keypair->p, q = keypair->q, w = keypair->w; + uint16_t *tmp = snewn(p, uint16_t); + + ntru_ring_multiply(tmp, ciphertext, keypair->f3, p, q); + + ntru_mod3(tmp, tmp, p, q); + ntru_normalise(tmp, tmp, p, 3); + + ntru_ring_multiply(plaintext, tmp, keypair->ginv, p, 3); + ring_free(tmp, p); + + /* + * With luck, this should have recovered exactly the original + * plaintext. But, as per the spec, we check whether it has + * exactly w nonzero coefficients, and if not, then something has + * gone wrong - and in that situation we time-safely substitute a + * different output. + * + * (I don't know exactly why we do this, but I assume it's because + * otherwise the mis-decoded output could be made to disgorge a + * secret about the private key in some way.) + */ + + unsigned weight = p; + for (size_t i = 0; i < p; i++) + weight -= iszero(plaintext[i]); + unsigned ok = iszero(weight ^ w); + + /* + * The default failure return value consists of w 1s followed by + * 0s. + */ + unsigned mask = ok - 1; + for (size_t i = 0; i < w; i++) { + uint16_t diff = (1 ^ plaintext[i]) & mask; + plaintext[i] ^= diff; + } + for (size_t i = w; i < p; i++) { + uint16_t diff = (0 ^ plaintext[i]) & mask; + plaintext[i] ^= diff; + } +} + +/* ---------------------------------------------------------------------- + * Encode and decode public keys, ciphertexts and plaintexts. + * + * Public keys and ciphertexts use the complicated binary encoding + * system implemented above. In both cases, the inputs are regarded as + * symmetric about zero, and are first biased to map their most + * negative permitted value to 0, so that they become non-negative and + * hence suitable as inputs to the encoding system. In the case of a + * ciphertext, where the input coefficients have also been coerced to + * be multiples of 3, we divide by 3 as well, saving space by reducing + * the upper bounds (m_i) on all the encoded numbers. + */ + +/* + * Compute the encoding schedule for a public key. + */ +static NTRUEncodeSchedule *ntru_encode_pubkey_schedule(unsigned p, unsigned q) +{ + uint16_t *ms = snewn(p, uint16_t); + for (size_t i = 0; i < p; i++) + ms[i] = q; + NTRUEncodeSchedule *sched = ntru_encode_schedule(ms, p); + sfree(ms); + return sched; +} + +/* + * Encode a public key. + */ +void ntru_encode_pubkey(const uint16_t *pubkey, unsigned p, unsigned q, + BinarySink *bs) +{ + /* Compute the biased version for encoding */ + uint16_t *biased_pubkey = snewn(p, uint16_t); + ntru_bias(biased_pubkey, pubkey, q / 2, p, q); + + /* Encode it */ + NTRUEncodeSchedule *sched = ntru_encode_pubkey_schedule(p, q); + ntru_encode(sched, biased_pubkey, bs); + ntru_encode_schedule_free(sched); + + ring_free(biased_pubkey, p); +} + +/* + * Decode a public key and write it into 'pubkey'. We also return a + * ptrlen pointing at the chunk of data we removed from the + * BinarySource. + */ +ptrlen ntru_decode_pubkey(uint16_t *pubkey, unsigned p, unsigned q, + BinarySource *src) +{ + NTRUEncodeSchedule *sched = ntru_encode_pubkey_schedule(p, q); + + /* Retrieve the right number of bytes from the source */ + size_t len = ntru_encode_schedule_length(sched); + ptrlen encoded = get_data(src, len); + if (get_err(src)) { + /* If there wasn't enough data, give up and return all-zeroes + * purely for determinism. But that value should never be + * used, because the caller will also check get_err(src). */ + memset(pubkey, 0, p*sizeof(*pubkey)); + } else { + /* Do the decoding */ + ntru_decode(sched, pubkey, encoded); + ntru_encode_schedule_free(sched); + + /* Unbias the coefficients */ + ntru_bias(pubkey, pubkey, q-q/2, p, q); + } + + return encoded; +} + +/* + * For ciphertext biasing: work out the largest absolute value a + * ciphertext element can take, which is given by taking q/2 and + * rounding it to the nearest multiple of 3. + */ +static inline unsigned ciphertext_bias(unsigned q) +{ + return (q/2+1) / 3; +} + +/* + * The number of possible values of a ciphertext coefficient (for use + * as the m_i in encoding) ranges from +ciphertext_bias(q) to + * -ciphertext_bias(q) inclusive. + */ +static inline unsigned ciphertext_m(unsigned q) +{ + return 1 + 2 * ciphertext_bias(q); +} + +/* + * Compute the encoding schedule for a ciphertext. + */ +static NTRUEncodeSchedule *ntru_encode_ciphertext_schedule( + unsigned p, unsigned q) +{ + unsigned m = ciphertext_m(q); + uint16_t *ms = snewn(p, uint16_t); + for (size_t i = 0; i < p; i++) + ms[i] = m; + NTRUEncodeSchedule *sched = ntru_encode_schedule(ms, p); + sfree(ms); + return sched; +} + +/* + * Encode a ciphertext. + */ +void ntru_encode_ciphertext(const uint16_t *ciphertext, unsigned p, unsigned q, + BinarySink *bs) +{ + SETUP; + + /* + * Bias the ciphertext, and scale down by 1/3, which we do by + * modular multiplication by the inverse of 3 mod q. (That only + * works if we know the inputs are all _exact_ multiples of 3 + * - but we do!) + */ + uint16_t *biased_ciphertext = snewn(p, uint16_t); + ntru_bias(biased_ciphertext, ciphertext, 3 * ciphertext_bias(q), p, q); + ntru_scale(biased_ciphertext, biased_ciphertext, INVERT(3), p, q); + + /* Encode. */ + NTRUEncodeSchedule *sched = ntru_encode_ciphertext_schedule(p, q); + ntru_encode(sched, biased_ciphertext, bs); + ntru_encode_schedule_free(sched); + + ring_free(biased_ciphertext, p); +} + +ptrlen ntru_decode_ciphertext(uint16_t *ct, NTRUKeyPair *keypair, + BinarySource *src) +{ + unsigned p = keypair->p, q = keypair->q; + + NTRUEncodeSchedule *sched = ntru_encode_ciphertext_schedule(p, q); + + /* Retrieve the right number of bytes from the source */ + size_t len = ntru_encode_schedule_length(sched); + ptrlen encoded = get_data(src, len); + if (get_err(src)) { + /* As above, return deterministic nonsense on failure */ + memset(ct, 0, p*sizeof(*ct)); + } else { + /* Do the decoding */ + ntru_decode(sched, ct, encoded); + ntru_encode_schedule_free(sched); + + /* Undo the scaling and bias */ + ntru_scale(ct, ct, 3, p, q); + ntru_bias(ct, ct, q - 3 * ciphertext_bias(q), p, q); + } + + return encoded; /* also useful to the caller, optionally */ +} + +/* + * Encode a plaintext. + * + * This is a much simpler encoding than the NTRUEncodeSchedule system: + * since elements of a plaintext are mod 3, we just encode each one in + * 2 bits, applying the usual bias so that {-1,0,+1} map to {0,1,2} + * respectively. + * + * There's no corresponding decode function, because plaintexts are + * never transmitted on the wire (the whole point is that they're too + * secret!). Plaintexts are only encoded in order to put them into + * hash preimages. + */ +void ntru_encode_plaintext(const uint16_t *plaintext, unsigned p, + BinarySink *bs) +{ + unsigned byte = 0, bitpos = 0; + for (size_t i = 0; i < p; i++) { + unsigned encoding = (plaintext[i] + 1) * iszero(plaintext[i] >> 1); + byte |= encoding << bitpos; + bitpos += 2; + if (bitpos == 8 || i+1 == p) { + put_byte(bs, byte); + byte = 0; + bitpos = 0; + } + } +} + +/* ---------------------------------------------------------------------- + * Compute the hashes required by the key exchange layer of NTRU Prime. + * + * There are two of these. The 'confirmation hash' is sent by the + * server along with the ciphertext, and the client can recalculate it + * to check whether the ciphertext was decrypted correctly. Then, the + * 'session hash' is the actual output of key exchange, and if the + * confirmation hash doesn't match, it gets deliberately corrupted. + */ + +/* + * Make the confirmation hash, whose inputs are the plaintext and the + * public key. + * + * This is defined as H(2 || H(3 || r) || H(4 || K)), where r is the + * plaintext and K is the public key (as encoded by the above + * functions), and the constants 2,3,4 are single bytes. The choice of + * hash function (H itself) is SHA-512 truncated to 256 bits. + * + * (To be clear: that is _not_ the thing that FIPS 180-4 6.7 defines + * as "SHA-512/256", which varies the initialisation vector of the + * SHA-512 algorithm as well as truncating the output. _This_ + * algorithm uses the standard SHA-512 IV, and _just_ truncates the + * output, in the manner suggested by FIPS 180-4 section 7.) + * + * 'out' should therefore expect to receive 32 bytes of data. + */ +void ntru_confirmation_hash(uint8_t *out, const uint16_t *plaintext, + const uint16_t *pubkey, unsigned p, unsigned q) +{ + /* The outer hash object */ + ssh_hash *hconfirm = ssh_hash_new(&ssh_sha512); + put_byte(hconfirm, 2); /* initial byte 2 */ + + uint8_t hashdata[64]; + + /* Compute H(3 || r) and add it to the main hash */ + ssh_hash *h3r = ssh_hash_new(&ssh_sha512); + put_byte(h3r, 3); + ntru_encode_plaintext(plaintext, p, BinarySink_UPCAST(h3r)); + ssh_hash_final(h3r, hashdata); + put_data(hconfirm, hashdata, 32); + + /* Compute H(4 || K) and add it to the main hash */ + ssh_hash *h4K = ssh_hash_new(&ssh_sha512); + put_byte(h4K, 4); + ntru_encode_pubkey(pubkey, p, q, BinarySink_UPCAST(h4K)); + ssh_hash_final(h4K, hashdata); + put_data(hconfirm, hashdata, 32); + + /* Compute the full output of the main SHA-512 hash */ + ssh_hash_final(hconfirm, hashdata); + + /* And copy the first 32 bytes into the caller's output array */ + memcpy(out, hashdata, 32); + smemclr(hashdata, sizeof(hashdata)); +} + +/* + * Make the session hash, whose inputs are the plaintext, the + * ciphertext, and the confirmation hash (hence, transitively, a + * dependence on the public key as well). + * + * As computed by the server, and by the client if the confirmation + * hash matched, this is defined as + * + * H(1 || H(3 || r) || ciphertext || confirmation hash) + * + * but if the confirmation hash _didn't_ match, then the plaintext r + * is replaced with the dummy plaintext-shaped value 'rho' we invented + * during key generation (presumably to avoid leaking any information + * about our secrets), and the initial byte 1 is replaced with 0 (to + * ensure that the resulting hash preimage can't match any legitimate + * preimage). So in that case, you instead get + * + * H(0 || H(3 || rho) || ciphertext || confirmation hash) + * + * The inputs to this function include 'ok', which is the value to use + * as the initial byte (1 on success, 0 on failure), and 'plaintext' + * which should already have been substituted with rho in case of + * failure. + * + * The ciphertext is provided in already-encoded form. + */ +void ntru_session_hash(uint8_t *out, unsigned ok, const uint16_t *plaintext, + unsigned p, ptrlen ciphertext, ptrlen confirmation_hash) +{ + /* The outer hash object */ + ssh_hash *hsession = ssh_hash_new(&ssh_sha512); + put_byte(hsession, ok); /* initial byte 1 or 0 */ + + uint8_t hashdata[64]; + + /* Compute H(3 || r), or maybe H(3 || rho), and add it to the main hash */ + ssh_hash *h3r = ssh_hash_new(&ssh_sha512); + put_byte(h3r, 3); + ntru_encode_plaintext(plaintext, p, BinarySink_UPCAST(h3r)); + ssh_hash_final(h3r, hashdata); + put_data(hsession, hashdata, 32); + + /* Put the ciphertext and confirmation hash in */ + put_datapl(hsession, ciphertext); + put_datapl(hsession, confirmation_hash); + + /* Compute the full output of the main SHA-512 hash */ + ssh_hash_final(hsession, hashdata); + + /* And copy the first 32 bytes into the caller's output array */ + memcpy(out, hashdata, 32); + smemclr(hashdata, sizeof(hashdata)); +} + +/* ---------------------------------------------------------------------- + * Top-level key exchange and SSH integration. + * + * Although this system borrows the ECDH packet structure, it's unlike + * true ECDH in that it is completely asymmetric between client and + * server. So we have two separate vtables of methods for the two + * sides of the system, and a third vtable containing only the class + * methods, in particular a constructor which chooses which one to + * instantiate. + */ + +/* + * The parameters p,q,w for the system. There are other choices of + * these, but OpenSSH only specifies this set. (If that ever changes, + * we'll need to turn these into elements of the state structures.) + */ +#define p_LIVE 761 +#define q_LIVE 4591 +#define w_LIVE 286 + +static char *ssh_ntru_description(const ssh_kex *kex) +{ + return dupprintf("NTRU Prime / Curve25519 hybrid key exchange"); +} + +/* + * State structure for the client, which takes the role of inventing a + * key pair and decrypting a secret plaintext sent to it by the server. + */ +typedef struct ntru_client_key { + NTRUKeyPair *keypair; + ecdh_key *curve25519; + + ecdh_key ek; +} ntru_client_key; + +static void ssh_ntru_client_free(ecdh_key *dh); +static void ssh_ntru_client_getpublic(ecdh_key *dh, BinarySink *bs); +static bool ssh_ntru_client_getkey(ecdh_key *dh, ptrlen remoteKey, + BinarySink *bs); + +static const ecdh_keyalg ssh_ntru_client_vt = { + /* This vtable has no 'new' method, because it's constructed via + * the selector vt below */ + .free = ssh_ntru_client_free, + .getpublic = ssh_ntru_client_getpublic, + .getkey = ssh_ntru_client_getkey, + .description = ssh_ntru_description, +}; + +static ecdh_key *ssh_ntru_client_new(void) +{ + ntru_client_key *nk = snew(ntru_client_key); + nk->ek.vt = &ssh_ntru_client_vt; + + nk->keypair = ntru_keygen(p_LIVE, q_LIVE, w_LIVE); + nk->curve25519 = ecdh_key_new(&ssh_ec_kex_curve25519, false); + + return &nk->ek; +} + +static void ssh_ntru_client_free(ecdh_key *dh) +{ + ntru_client_key *nk = container_of(dh, ntru_client_key, ek); + ntru_keypair_free(nk->keypair); + ecdh_key_free(nk->curve25519); + sfree(nk); +} + +static void ssh_ntru_client_getpublic(ecdh_key *dh, BinarySink *bs) +{ + ntru_client_key *nk = container_of(dh, ntru_client_key, ek); + + /* + * The client's public information is a single SSH string + * containing the NTRU public key and the Curve25519 public point + * concatenated. So write both of those into the output + * BinarySink. + */ + ntru_encode_pubkey(nk->keypair->h, p_LIVE, q_LIVE, bs); + ecdh_key_getpublic(nk->curve25519, bs); +} + +static bool ssh_ntru_client_getkey(ecdh_key *dh, ptrlen remoteKey, + BinarySink *bs) +{ + ntru_client_key *nk = container_of(dh, ntru_client_key, ek); + + /* + * We expect the server to have sent us a string containing a + * ciphertext, a confirmation hash, and a Curve25519 public point. + * Extract all three. + */ + BinarySource src[1]; + BinarySource_BARE_INIT_PL(src, remoteKey); + + uint16_t *ciphertext = snewn(p_LIVE, uint16_t); + ptrlen ciphertext_encoded = ntru_decode_ciphertext( + ciphertext, nk->keypair, src); + ptrlen confirmation_hash = get_data(src, 32); + ptrlen curve25519_remoteKey = get_data(src, 32); + + if (get_err(src) || get_avail(src)) { + /* Hard-fail if the input wasn't exactly the right length */ + ring_free(ciphertext, p_LIVE); + return false; + } + + /* + * Main hash object which will combine the NTRU and Curve25519 + * outputs. + */ + ssh_hash *h = ssh_hash_new(&ssh_sha512); + + /* Reusable buffer for storing various hash outputs. */ + uint8_t hashdata[64]; + + /* + * NTRU side. + */ + { + /* Decrypt the ciphertext to recover the server's plaintext */ + uint16_t *plaintext = snewn(p_LIVE, uint16_t); + ntru_decrypt(plaintext, ciphertext, nk->keypair); + + /* Make the confirmation hash */ + ntru_confirmation_hash(hashdata, plaintext, nk->keypair->h, + p_LIVE, q_LIVE); + + /* Check it matches the one the server sent */ + unsigned ok = smemeq(hashdata, confirmation_hash.ptr, 32); + + /* If not, substitute in rho for the plaintext in the session hash */ + unsigned mask = ok-1; + for (size_t i = 0; i < p_LIVE; i++) + plaintext[i] ^= mask & (plaintext[i] ^ nk->keypair->rho[i]); + + /* Compute the session hash, whether or not we did that */ + ntru_session_hash(hashdata, ok, plaintext, p_LIVE, ciphertext_encoded, + confirmation_hash); + + /* Free temporary values */ + ring_free(plaintext, p_LIVE); + ring_free(ciphertext, p_LIVE); + + /* And put the NTRU session hash into the main hash object. */ + put_data(h, hashdata, 32); + } + + /* + * Curve25519 side. + */ + { + strbuf *otherkey = strbuf_new_nm(); + + /* Call out to Curve25519 to compute the shared secret from that + * kex method */ + bool ok = ecdh_key_getkey(nk->curve25519, curve25519_remoteKey, + BinarySink_UPCAST(otherkey)); + + /* If that failed (which only happens if the other end does + * something wrong, like sending a low-order curve point + * outside the subgroup it's supposed to), we might as well + * just abort and return failure. That's what we'd have done + * in standalone Curve25519. */ + if (!ok) { + ssh_hash_free(h); + smemclr(hashdata, sizeof(hashdata)); + return false; + } + + /* + * ecdh_key_getkey will have returned us a chunk of data + * containing an encoded mpint, which is how the Curve25519 + * output normally goes into the exchange hash. But in this + * context we want to treat it as a fixed big-endian 32 bytes, + * so extract it from its encoding and put it into the main + * hash object in the new format. + */ + BinarySource src[1]; + BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(otherkey)); + mp_int *curvekey = get_mp_ssh2(src); + + for (unsigned i = 32; i-- > 0 ;) + put_byte(h, mp_get_byte(curvekey, i)); + + mp_free(curvekey); + strbuf_free(otherkey); + } + + /* + * Finish up: compute the final output hash (full 64 bytes of + * SHA-512 this time), and return it encoded as a string. + */ + ssh_hash_final(h, hashdata); + put_stringpl(bs, make_ptrlen(hashdata, sizeof(hashdata))); + smemclr(hashdata, sizeof(hashdata)); + + return true; +} + +/* + * State structure for the server, which takes the role of inventing a + * secret plaintext and sending it to the client encrypted with the + * public key the client sent. + */ +typedef struct ntru_server_key { + uint16_t *plaintext; + strbuf *ciphertext_encoded, *confirmation_hash; + ecdh_key *curve25519; + + ecdh_key ek; +} ntru_server_key; + +static void ssh_ntru_server_free(ecdh_key *dh); +static void ssh_ntru_server_getpublic(ecdh_key *dh, BinarySink *bs); +static bool ssh_ntru_server_getkey(ecdh_key *dh, ptrlen remoteKey, + BinarySink *bs); + +static const ecdh_keyalg ssh_ntru_server_vt = { + /* This vtable has no 'new' method, because it's constructed via + * the selector vt below */ + .free = ssh_ntru_server_free, + .getpublic = ssh_ntru_server_getpublic, + .getkey = ssh_ntru_server_getkey, + .description = ssh_ntru_description, +}; + +static ecdh_key *ssh_ntru_server_new(void) +{ + ntru_server_key *nk = snew(ntru_server_key); + nk->ek.vt = &ssh_ntru_server_vt; + + nk->plaintext = snewn(p_LIVE, uint16_t); + nk->ciphertext_encoded = strbuf_new_nm(); + nk->confirmation_hash = strbuf_new_nm(); + ntru_gen_short(nk->plaintext, p_LIVE, w_LIVE); + + nk->curve25519 = ecdh_key_new(&ssh_ec_kex_curve25519, false); + + return &nk->ek; +} + +static void ssh_ntru_server_free(ecdh_key *dh) +{ + ntru_server_key *nk = container_of(dh, ntru_server_key, ek); + ring_free(nk->plaintext, p_LIVE); + strbuf_free(nk->ciphertext_encoded); + strbuf_free(nk->confirmation_hash); + ecdh_key_free(nk->curve25519); + sfree(nk); +} + +static bool ssh_ntru_server_getkey(ecdh_key *dh, ptrlen remoteKey, + BinarySink *bs) +{ + ntru_server_key *nk = container_of(dh, ntru_server_key, ek); + + /* + * In the server, getkey is called first, with the public + * information received from the client. We expect the client to + * have sent us a string containing a public key and a Curve25519 + * public point. + */ + BinarySource src[1]; + BinarySource_BARE_INIT_PL(src, remoteKey); + + uint16_t *pubkey = snewn(p_LIVE, uint16_t); + ntru_decode_pubkey(pubkey, p_LIVE, q_LIVE, src); + ptrlen curve25519_remoteKey = get_data(src, 32); + + if (get_err(src) || get_avail(src)) { + /* Hard-fail if the input wasn't exactly the right length */ + ring_free(pubkey, p_LIVE); + return false; + } + + /* + * Main hash object which will combine the NTRU and Curve25519 + * outputs. + */ + ssh_hash *h = ssh_hash_new(&ssh_sha512); + + /* Reusable buffer for storing various hash outputs. */ + uint8_t hashdata[64]; + + /* + * NTRU side. + */ + { + /* Encrypt the plaintext we generated at construction time, + * and encode the ciphertext into a strbuf so we can reuse it + * for both the session hash and sending to the client. */ + uint16_t *ciphertext = snewn(p_LIVE, uint16_t); + ntru_encrypt(ciphertext, nk->plaintext, pubkey, p_LIVE, q_LIVE); + ntru_encode_ciphertext(ciphertext, p_LIVE, q_LIVE, + BinarySink_UPCAST(nk->ciphertext_encoded)); + + /* Compute the confirmation hash, and write it into another + * strbuf. */ + ntru_confirmation_hash(hashdata, nk->plaintext, pubkey, + p_LIVE, q_LIVE); + put_data(nk->confirmation_hash, hashdata, 32); + + /* Compute the session hash (which is easy on the server side, + * requiring no conditional substitution). */ + ntru_session_hash(hashdata, 1, nk->plaintext, p_LIVE, + ptrlen_from_strbuf(nk->ciphertext_encoded), + ptrlen_from_strbuf(nk->confirmation_hash)); + + /* And put the NTRU session hash into the main hash object. */ + put_data(h, hashdata, 32); + } + + /* + * Curve25519 side. + */ + { + strbuf *otherkey = strbuf_new_nm(); + + /* Call out to Curve25519 to compute the shared secret from that + * kex method */ + bool ok = ecdh_key_getkey(nk->curve25519, curve25519_remoteKey, + BinarySink_UPCAST(otherkey)); + /* As on the client side, abort if Curve25519 reported failure */ + if (!ok) { + ssh_hash_free(h); + smemclr(hashdata, sizeof(hashdata)); + return false; + } + + /* As on the client side, decode Curve25519's mpint so we can + * re-encode it appropriately for our hash preimage */ + BinarySource src[1]; + BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(otherkey)); + mp_int *curvekey = get_mp_ssh2(src); + + for (unsigned i = 32; i-- > 0 ;) + put_byte(h, mp_get_byte(curvekey, i)); + + mp_free(curvekey); + strbuf_free(otherkey); + } + + /* + * Finish up: compute the final output hash (full 64 bytes of + * SHA-512 this time), and return it encoded as a string. + */ + ssh_hash_final(h, hashdata); + put_stringpl(bs, make_ptrlen(hashdata, sizeof(hashdata))); + smemclr(hashdata, sizeof(hashdata)); + + return true; +} + +static void ssh_ntru_server_getpublic(ecdh_key *dh, BinarySink *bs) +{ + ntru_server_key *nk = container_of(dh, ntru_server_key, ek); + + /* + * In the server, this function is called after getkey, so we + * already have all our pieces prepared. Just concatenate them all + * into the 'server's public data' string to go in ECDH_REPLY. + */ + put_datapl(bs, ptrlen_from_strbuf(nk->ciphertext_encoded)); + put_datapl(bs, ptrlen_from_strbuf(nk->confirmation_hash)); + ecdh_key_getpublic(nk->curve25519, bs); +} + +/* ---------------------------------------------------------------------- + * Selector vtable that instantiates the appropriate one of the above, + * depending on is_server. + */ +static ecdh_key *ssh_ntru_new(const ssh_kex *kex, bool is_server) +{ + if (is_server) + return ssh_ntru_server_new(); + else + return ssh_ntru_client_new(); +} + +static const ecdh_keyalg ssh_ntru_selector_vt = { + /* This is a never-instantiated vtable which only implements the + * functions that don't require an instance. */ + .new = ssh_ntru_new, + .description = ssh_ntru_description, +}; + +const ssh_kex ssh_ntru_curve25519 = { + .name = "sntrup761x25519-sha512@openssh.com", + .main_type = KEXTYPE_ECDH, + .hash = &ssh_sha512, + .ecdh_vt = &ssh_ntru_selector_vt, +}; + +static const ssh_kex *const hybrid_list[] = { + &ssh_ntru_curve25519, +}; + +const ssh_kexes ssh_ntru_hybrid_kex = { lenof(hybrid_list), hybrid_list }; diff --git a/crypto/ntru.h b/crypto/ntru.h new file mode 100644 index 00000000..4789491b --- /dev/null +++ b/crypto/ntru.h @@ -0,0 +1,53 @@ +/* + * Internal functions for the NTRU cryptosystem, exposed in a header + * that is expected to be included only by ntru.c and test programs. + */ + +#ifndef PUTTY_CRYPTO_NTRU_H +#define PUTTY_CRYPTO_NTRU_H + +unsigned ntru_ring_invert(uint16_t *out, const uint16_t *in, + unsigned p, unsigned q); +void ntru_ring_multiply(uint16_t *out, const uint16_t *a, const uint16_t *b, + unsigned p, unsigned q); +void ntru_mod3(uint16_t *out, const uint16_t *in, unsigned p, unsigned q); +void ntru_round3(uint16_t *out, const uint16_t *in, unsigned p, unsigned q); +void ntru_bias(uint16_t *out, const uint16_t *in, unsigned bias, + unsigned p, unsigned q); +void ntru_scale(uint16_t *out, const uint16_t *in, uint16_t scale, + unsigned p, unsigned q); + +NTRUEncodeSchedule *ntru_encode_schedule(const uint16_t *ms_in, size_t n); +void ntru_encode_schedule_free(NTRUEncodeSchedule *sched); +size_t ntru_encode_schedule_length(NTRUEncodeSchedule *sched); +size_t ntru_encode_schedule_nvals(NTRUEncodeSchedule *sched); +void ntru_encode(NTRUEncodeSchedule *sched, const uint16_t *rs_in, + BinarySink *bs); +void ntru_decode(NTRUEncodeSchedule *sched, uint16_t *rs_out, ptrlen data); + +void ntru_gen_short(uint16_t *v, unsigned p, unsigned w); + +NTRUKeyPair *ntru_keygen_attempt(unsigned p, unsigned q, unsigned w); +NTRUKeyPair *ntru_keygen(unsigned p, unsigned q, unsigned w); +void ntru_keypair_free(NTRUKeyPair *keypair); + +void ntru_encrypt(uint16_t *ciphertext, const uint16_t *plaintext, + uint16_t *pubkey, unsigned p, unsigned q); +void ntru_decrypt(uint16_t *plaintext, const uint16_t *ciphertext, + NTRUKeyPair *keypair); + +void ntru_encode_pubkey(const uint16_t *pubkey, unsigned p, unsigned q, + BinarySink *bs); +ptrlen ntru_decode_pubkey(uint16_t *pubkey, unsigned p, unsigned q, + BinarySource *src); +void ntru_encode_ciphertext(const uint16_t *ciphertext, unsigned p, unsigned q, + BinarySink *bs); +ptrlen ntru_decode_ciphertext(uint16_t *ct, NTRUKeyPair *keypair, + BinarySource *src); +void ntru_encode_plaintext(const uint16_t *plaintext, unsigned p, + BinarySink *bs); + +unsigned ntru_keypair_p(NTRUKeyPair *keypair); +const uint16_t *ntru_pubkey(NTRUKeyPair *keypair); + +#endif /* PUTTY_CRYPTO_NTRU_H */ diff --git a/defs.h b/defs.h index 1edf3442..37cc7979 100644 --- a/defs.h +++ b/defs.h @@ -168,6 +168,8 @@ typedef struct ssh2_ciphers ssh2_ciphers; typedef struct dh_ctx dh_ctx; typedef struct ecdh_key ecdh_key; typedef struct ecdh_keyalg ecdh_keyalg; +typedef struct NTRUKeyPair NTRUKeyPair; +typedef struct NTRUEncodeSchedule NTRUEncodeSchedule; typedef struct dlgparam dlgparam; diff --git a/putty.h b/putty.h index fc5c2941..84a7df31 100644 --- a/putty.h +++ b/putty.h @@ -426,6 +426,7 @@ enum { KEX_DHGEX, KEX_RSA, KEX_ECDH, + KEX_NTRU_HYBRID, KEX_MAX }; diff --git a/settings.c b/settings.c index 98313d17..09701618 100644 --- a/settings.c +++ b/settings.c @@ -28,6 +28,7 @@ static const struct keyvalwhere ciphernames[] = { * compatibility warts in load_open_settings(), and should be kept * in sync with those. */ static const struct keyvalwhere kexnames[] = { + { "ntru-curve25519", KEX_NTRU_HYBRID, -1, +1 }, { "ecdh", KEX_ECDH, -1, +1 }, /* This name is misleading: it covers both SHA-256 and SHA-1 variants */ { "dh-gex-sha1", KEX_DHGEX, -1, -1 }, diff --git a/ssh.h b/ssh.h index 60880482..fb86f4f0 100644 --- a/ssh.h +++ b/ssh.h @@ -1058,6 +1058,7 @@ extern const ssh_kex ssh_ec_kex_nistp256; extern const ssh_kex ssh_ec_kex_nistp384; extern const ssh_kex ssh_ec_kex_nistp521; extern const ssh_kexes ssh_ecdh_kex; +extern const ssh_kexes ssh_ntru_hybrid_kex; extern const ssh_keyalg ssh_dsa; extern const ssh_keyalg ssh_rsa; extern const ssh_keyalg ssh_rsa_sha256; diff --git a/ssh/transport2.c b/ssh/transport2.c index e00507e1..02289747 100644 --- a/ssh/transport2.c +++ b/ssh/transport2.c @@ -533,6 +533,10 @@ static void ssh2_write_kexinit_lists( preferred_kex[n_preferred_kex++] = &ssh_ecdh_kex; break; + case KEX_NTRU_HYBRID: + preferred_kex[n_preferred_kex++] = + &ssh_ntru_hybrid_kex; + break; case KEX_WARN: /* Flag for later. Don't bother if it's the last in * the list. */ diff --git a/test/cryptsuite.py b/test/cryptsuite.py index 958980b9..ef2f79a4 100755 --- a/test/cryptsuite.py +++ b/test/cryptsuite.py @@ -1274,6 +1274,107 @@ class keygen(MyTestBase): mr = miller_rabin_new(n) self.assertEqual(miller_rabin_test(mr, 0x251), "failed") +class ntru(MyTestBase): + def testMultiply(self): + self.assertEqual( + ntru_ring_multiply([1,1,1,1,1,1], [1,1,1,1,1,1], 11, 59), + [1,2,3,4,5,6,5,4,3,2,1]) + self.assertEqual(ntru_ring_multiply( + [1,0,1,2,0,0,1,2,0,1,2], [2,0,0,1,0,1,2,2,2,0,2], 11, 3), + [1,0,0,0,0,0,0,0,0,0,0]) + + def testInvert(self): + # Over GF(3), x^11-x-1 factorises as + # (x^3+x^2+2) * (x^8+2*x^7+x^6+2*x^4+2*x^3+x^2+x+1) + # so we expect that 2,0,1,1 has no inverse, being one of those factors. + self.assertEqual(ntru_ring_invert([0], 11, 3), None) + self.assertEqual(ntru_ring_invert([1], 11, 3), + [1,0,0,0,0,0,0,0,0,0,0]) + self.assertEqual(ntru_ring_invert([2,0,1,1], 11, 3), None) + self.assertEqual(ntru_ring_invert([1,0,1,2,0,0,1,2,0,1,2], 11, 3), + [2,0,0,1,0,1,2,2,2,0,2]) + + self.assertEqual(ntru_ring_invert([1,0,1,2,0,0,1,2,0,1,2], 11, 59), + [1,26,10,1,38,48,34,37,53,3,53]) + + def testMod3Round3(self): + # Try a prime congruent to 1 mod 3 + self.assertEqual(ntru_mod3([4,5,6,0,1,2,3], 7, 7), + [0,1,-1,0,1,-1,0]) + self.assertEqual(ntru_round3([4,5,6,0,1,2,3], 7, 7), + [-3,-3,0,0,0,3,3]) + + # And one congruent to 2 mod 3 + self.assertEqual(ntru_mod3([6,7,8,9,10,0,1,2,3,4,5], 11, 11), + [1,-1,0,1,-1,0,1,-1,0,1,-1]) + self.assertEqual(ntru_round3([6,7,8,9,10,0,1,2,3,4,5], 11, 11), + [-6,-3,-3,-3,0,0,0,3,3,3,6]) + + def testBiasScale(self): + self.assertEqual(ntru_bias([0,1,2,3,4,5,6,7,8,9,10], 4, 11, 11), + [4,5,6,7,8,9,10,0,1,2,3]) + self.assertEqual(ntru_scale([0,1,2,3,4,5,6,7,8,9,10], 4, 11, 11), + [0,4,8,1,5,9,2,6,10,3,7]) + + def testEncode(self): + # Test a small case. Worked through in detail: + # + # Pass 1: + # Input list is (89:123, 90:234, 344:345, 432:456, 222:567) + # (89:123, 90:234) -> (89+123*90 : 123*234) = (11159:28782) + # Emit low byte of 11159 = 0x97, and get (43:113) + # (344:345, 432:456) -> (344+345*432 : 345*456) = (149384:157320) + # Emit low byte of 149384 = 0x88, and get (583:615) + # Odd pair (222:567) is copied to end of new list + # Final list is (43:113, 583:615, 222:567) + # Pass 2: + # Input list is (43:113, 583:615, 222:567) + # (43:113, 583:615) -> (43+113*583, 113*615) = (65922:69495) + # Emit low byte of 65922 = 0x82, and get (257:272) + # Odd pair (222:567) is copied to end of new list + # Final list is (257:272, 222:567) + # Pass 3: + # Input list is (257:272, 222:567) + # (257:272, 222:567) -> (257+272*222, 272*567) = (60641:154224) + # Emit low byte of 60641 = 0xe1, and get (236:603) + # Final list is (236:603) + # Cleanup: + # Emit low byte of 236 = 0xec, and get (0:3) + # Emit low byte of 0 = 0x00, and get (0:1) + + ms = [123,234,345,456,567] + rs = [89,90,344,432,222] + encoding = unhex('978882e1ec00') + sched = ntru_encode_schedule(ms) + self.assertEqual(sched.encode(rs), encoding) + self.assertEqual(sched.decode(encoding), rs) + + # Encode schedules for sntrup761 public keys and ciphertexts + pubsched = ntru_encode_schedule([4591]*761) + self.assertEqual(pubsched.length(), 1158) + ciphersched = ntru_encode_schedule([1531]*761) + self.assertEqual(ciphersched.length(), 1007) + + # Test round-trip encoding using those schedules + testlist = list(range(761)) + pubtext = pubsched.encode(testlist) + self.assertEqual(pubsched.decode(pubtext), testlist) + ciphertext = ciphersched.encode(testlist) + self.assertEqual(ciphersched.decode(ciphertext), testlist) + + def testCore(self): + # My own set of NTRU Prime parameters, satisfying all the + # requirements and tiny enough for convenient testing + p, q, w = 11, 59, 3 + + with random_prng('ntru keygen seed'): + keypair = ntru_keygen(p, q, w) + plaintext = ntru_gen_short(p, w) + + ciphertext = ntru_encrypt(plaintext, ntru_pubkey(keypair), p, q) + recovered = ntru_decrypt(ciphertext, keypair) + self.assertEqual(plaintext, recovered) + class crypt(MyTestBase): def testSSH1Fingerprint(self): # Example key and reference fingerprint value generated by diff --git a/test/testcrypt-func.h b/test/testcrypt-func.h index 1188d2c4..54de4b88 100644 --- a/test/testcrypt-func.h +++ b/test/testcrypt-func.h @@ -352,6 +352,35 @@ FUNC(void, ecdh_key_getpublic, ARG(val_ecdh, key), FUNC_WRAPPED(opt_val_string, ecdh_key_getkey, ARG(val_ecdh, key), ARG(val_string_ptrlen, pub)) +/* + * NTRU and its subroutines. + */ +FUNC_WRAPPED(int16_list, ntru_ring_multiply, ARG(int16_list, a), + ARG(int16_list, b), ARG(uint, p), ARG(uint, q)) +FUNC_WRAPPED(opt_int16_list, ntru_ring_invert, ARG(int16_list, r), + ARG(uint, p), ARG(uint, q)) +FUNC_WRAPPED(int16_list, ntru_mod3, ARG(int16_list, r), + ARG(uint, p), ARG(uint, q)) +FUNC_WRAPPED(int16_list, ntru_round3, ARG(int16_list, r), + ARG(uint, p), ARG(uint, q)) +FUNC_WRAPPED(int16_list, ntru_bias, ARG(int16_list, r), + ARG(uint, bias), ARG(uint, p), ARG(uint, q)) +FUNC_WRAPPED(int16_list, ntru_scale, ARG(int16_list, r), + ARG(uint, scale), ARG(uint, p), ARG(uint, q)) +FUNC_WRAPPED(val_ntruencodeschedule, ntru_encode_schedule, ARG(int16_list, ms)) +FUNC(uint, ntru_encode_schedule_length, ARG(val_ntruencodeschedule, sched)) +FUNC_WRAPPED(void, ntru_encode, ARG(val_ntruencodeschedule, sched), + ARG(int16_list, rs), ARG(out_val_string_binarysink, data)) +FUNC_WRAPPED(opt_int16_list, ntru_decode, ARG(val_ntruencodeschedule, sched), + ARG(val_string_ptrlen, data)) +FUNC_WRAPPED(int16_list, ntru_gen_short, ARG(uint, p), ARG(uint, w)) +FUNC(val_ntrukeypair, ntru_keygen, ARG(uint, p), ARG(uint, q), ARG(uint, w)) +FUNC_WRAPPED(int16_list, ntru_pubkey, ARG(val_ntrukeypair, keypair)) +FUNC_WRAPPED(int16_list, ntru_encrypt, ARG(int16_list, plaintext), + ARG(int16_list, pubkey), ARG(uint, p), ARG(uint, q)) +FUNC_WRAPPED(int16_list, ntru_decrypt, ARG(int16_list, ciphertext), + ARG(val_ntrukeypair, keypair)) + /* * RSA key exchange, and also the BinarySource get function * get_ssh1_rsa_priv_agent, which is a convenient way to make an diff --git a/test/testcrypt.c b/test/testcrypt.c index cfe28ae2..3157ed54 100644 --- a/test/testcrypt.c +++ b/test/testcrypt.c @@ -35,6 +35,7 @@ #include "misc.h" #include "mpint.h" #include "crypto/ecc.h" +#include "crypto/ntru.h" #include "proxy/cproxy.h" static NORETURN PRINTF_LIKE(1, 2) void fatal_error(const char *p, ...) @@ -96,6 +97,8 @@ uint64_t prng_reseed_time_ms(void) X(pgc, PrimeGenerationContext *, primegen_free_context(v)) \ X(pockle, Pockle *, pockle_free(v)) \ X(millerrabin, MillerRabin *, miller_rabin_free(v)) \ + X(ntrukeypair, NTRUKeyPair *, ntru_keypair_free(v)) \ + X(ntruencodeschedule, NTRUEncodeSchedule *, ntru_encode_schedule_free(v)) \ /* end of list */ typedef struct Value Value; @@ -221,6 +224,7 @@ typedef RsaSsh1Order TD_rsaorder; typedef key_components *TD_keycomponents; typedef const PrimeGenerationPolicy *TD_primegenpolicy; typedef struct mpint_list TD_mpint_list; +typedef struct int16_list *TD_int16_list; typedef PockleStatus TD_pocklestatus; typedef struct mr_result TD_mr_result; typedef Argon2Flavour TD_argon2flavour; @@ -385,6 +389,46 @@ static struct mpint_list get_mpint_list(BinarySource *in) return mpl; } +typedef struct int16_list { + size_t n; + uint16_t *integers; +} int16_list; + +static void finaliser_int16_list_free(strbuf *out, void *vlist) +{ + int16_list *list = (int16_list *)vlist; + sfree(list->integers); + sfree(list); +} + +static int16_list *make_int16_list(size_t n) +{ + int16_list *list = snew(int16_list); + list->n = n; + list->integers = snewn(n, uint16_t); + add_finaliser(finaliser_int16_list_free, list); + return list; +} + +static int16_list *get_int16_list(BinarySource *in) +{ + size_t n = get_uint(in); + int16_list *list = make_int16_list(n); + for (size_t i = 0; i < n; i++) + list->integers[i] = get_uint(in); + return list; +} + +static void return_int16_list(strbuf *out, int16_list *list) +{ + for (size_t i = 0; i < list->n; i++) { + if (i > 0) + put_byte(out, ','); + put_fmt(out, "%d", (int)(int16_t)list->integers[i]); + } + put_byte(out, '\n'); +} + static void finaliser_return_uint(strbuf *out, void *ctx) { unsigned *uval = (unsigned *)ctx; @@ -543,6 +587,7 @@ NULLABLE_RETURN_WRAPPER(val_cipher, ssh_cipher *) NULLABLE_RETURN_WRAPPER(val_hash, ssh_hash *) NULLABLE_RETURN_WRAPPER(val_key, ssh_key *) NULLABLE_RETURN_WRAPPER(val_mpint, mp_int *) +NULLABLE_RETURN_WRAPPER(int16_list, int16_list *) static void handle_hello(BinarySource *in, strbuf *out) { @@ -799,6 +844,130 @@ strbuf *ecdh_key_getkey_wrapper(ecdh_key *ek, ptrlen remoteKey) return sb; } +static void int16_list_resize(int16_list *list, unsigned p) +{ + list->integers = sresize(list->integers, p, uint16_t); + for (size_t i = list->n; i < p; i++) + list->integers[i] = 0; +} + +#if 0 +static int16_list ntru_ring_to_list_and_free(uint16_t *out, unsigned p) +{ + struct mpint_list mpl; + mpl.n = p; + mpl->integers = snewn(p, mp_int *); + for (unsigned i = 0; i < p; i++) + mpl->integers[i] = mp_from_integer((int16_t)out[i]); + sfree(out); + add_finaliser(finaliser_sfree, mpl->integers); + return mpl; +} +#endif + +int16_list *ntru_ring_multiply_wrapper( + int16_list *a, int16_list *b, unsigned p, unsigned q) +{ + int16_list_resize(a, p); + int16_list_resize(b, p); + int16_list *out = make_int16_list(p); + ntru_ring_multiply(out->integers, a->integers, b->integers, p, q); + return out; +} + +int16_list *ntru_ring_invert_wrapper(int16_list *in, unsigned p, unsigned q) +{ + int16_list_resize(in, p); + int16_list *out = make_int16_list(p); + unsigned success = ntru_ring_invert(out->integers, in->integers, p, q); + if (!success) + return NULL; + return out; +} + +int16_list *ntru_mod3_wrapper(int16_list *in, unsigned p, unsigned q) +{ + int16_list_resize(in, p); + int16_list *out = make_int16_list(p); + ntru_mod3(out->integers, in->integers, p, q); + return out; +} + +int16_list *ntru_round3_wrapper(int16_list *in, unsigned p, unsigned q) +{ + int16_list_resize(in, p); + int16_list *out = make_int16_list(p); + ntru_round3(out->integers, in->integers, p, q); + return out; +} + +int16_list *ntru_bias_wrapper(int16_list *in, unsigned bias, + unsigned p, unsigned q) +{ + int16_list_resize(in, p); + int16_list *out = make_int16_list(p); + ntru_bias(out->integers, in->integers, bias, p, q); + return out; +} + +int16_list *ntru_scale_wrapper(int16_list *in, unsigned scale, + unsigned p, unsigned q) +{ + int16_list_resize(in, p); + int16_list *out = make_int16_list(p); + ntru_scale(out->integers, in->integers, scale, p, q); + return out; +} + +NTRUEncodeSchedule *ntru_encode_schedule_wrapper(int16_list *in) +{ + return ntru_encode_schedule(in->integers, in->n); +} + +void ntru_encode_wrapper(NTRUEncodeSchedule *sched, int16_list *rs, + BinarySink *bs) +{ + ntru_encode(sched, rs->integers, bs); +} + +int16_list *ntru_decode_wrapper(NTRUEncodeSchedule *sched, ptrlen data) +{ + int16_list *out = make_int16_list(ntru_encode_schedule_nvals(sched)); + ntru_decode(sched, out->integers, data); + return out; +} + +int16_list *ntru_gen_short_wrapper(unsigned p, unsigned w) +{ + int16_list *out = make_int16_list(p); + ntru_gen_short(out->integers, p, w); + return out; +} + +int16_list *ntru_pubkey_wrapper(NTRUKeyPair *keypair) +{ + unsigned p = ntru_keypair_p(keypair); + int16_list *out = make_int16_list(p); + memcpy(out->integers, ntru_pubkey(keypair), p*sizeof(uint16_t)); + return out; +} + +int16_list *ntru_encrypt_wrapper(int16_list *plaintext, int16_list *pubkey, + unsigned p, unsigned q) +{ + int16_list *out = make_int16_list(p); + ntru_encrypt(out->integers, plaintext->integers, pubkey->integers, p, q); + return out; +} + +int16_list *ntru_decrypt_wrapper(int16_list *ciphertext, NTRUKeyPair *keypair) +{ + unsigned p = ntru_keypair_p(keypair); + int16_list *out = make_int16_list(p); + ntru_decrypt(out->integers, ciphertext->integers, keypair); + return out; +} + strbuf *rsa_ssh1_encrypt_wrapper(ptrlen input, RSAKey *key) { /* Fold the boolean return value in C into the string return value diff --git a/test/testcrypt.py b/test/testcrypt.py index 6c0e95ce..66f63d5c 100644 --- a/test/testcrypt.py +++ b/test/testcrypt.py @@ -199,6 +199,13 @@ def make_argword(arg, argtype, fnname, argindex, argname, to_preserve): sublist.append(make_argword(val, ("val_mpint", False), fnname, argindex, argname, to_preserve)) return b" ".join(coerce_to_bytes(sub) for sub in sublist) + if typename == "int16_list": + sublist = [make_argword(len(arg), ("uint", False), + fnname, argindex, argname, to_preserve)] + for val in arg: + sublist.append(make_argword(val & 0xFFFF, ("uint", False), + fnname, argindex, argname, to_preserve)) + return b" ".join(coerce_to_bytes(sub) for sub in sublist) raise TypeError( "Can't convert {}() argument #{:d} ({}) to {} (value was {!r})".format( fnname, argindex, argname, typename, arg)) @@ -247,6 +254,8 @@ def make_retval(rettype, word, unpack_strings): return word == b"true" elif rettype in {"pocklestatus", "mr_result"}: return word.decode("ASCII") + elif rettype == "int16_list": + return list(map(int, word.split(b','))) raise TypeError("Can't deal with return value {!r} of type {!r}" .format(word, rettype)) diff --git a/test/testsc.c b/test/testsc.c index 4d8b55a4..55a64aba 100644 --- a/test/testsc.c +++ b/test/testsc.c @@ -81,6 +81,7 @@ #include "misc.h" #include "mpint.h" #include "crypto/ecc.h" +#include "crypto/ntru.h" static NORETURN PRINTF_LIKE(1, 2) void fatal_error(const char *p, ...) { @@ -395,6 +396,7 @@ VOLATILE_WRAPPED_DEFN(static, size_t, looplimit, (size_t x)) HASHES(HASH_TESTLIST, X) \ X(argon2) \ X(primegen_probabilistic) \ + X(ntru) \ /* end of list */ static void test_mp_get_nbits(void) @@ -1556,6 +1558,74 @@ static void test_primegen_probabilistic(void) test_primegen(&primegen_probabilistic); } +static void test_ntru(void) +{ + unsigned p = 11, q = 59, w = 3; + uint16_t *pubkey_orig = snewn(p, uint16_t); + uint16_t *pubkey_check = snewn(p, uint16_t); + uint16_t *pubkey = snewn(p, uint16_t); + uint16_t *plaintext = snewn(p, uint16_t); + uint16_t *ciphertext = snewn(p, uint16_t); + + strbuf *buffer = strbuf_new(); + strbuf_append(buffer, 16384); + BinarySource src[1]; + + for (size_t i = 0; i < looplimit(32); i++) { + while (true) { + random_advance_counter(); + struct random_state st = random_get_state(); + + NTRUKeyPair *keypair = ntru_keygen_attempt(p, q, w); + + if (keypair) { + memcpy(pubkey_orig, ntru_pubkey(keypair), + p*sizeof(*pubkey_orig)); + ntru_keypair_free(keypair); + + random_set_state(st); + + log_start(); + NTRUKeyPair *keypair = ntru_keygen_attempt(p, q, w); + memcpy(pubkey_check, ntru_pubkey(keypair), + p*sizeof(*pubkey_check)); + + ntru_gen_short(plaintext, p, w); + ntru_encrypt(ciphertext, plaintext, pubkey, p, w); + ntru_decrypt(plaintext, ciphertext, keypair); + + strbuf_clear(buffer); + ntru_encode_pubkey(ntru_pubkey(keypair), p, q, + BinarySink_UPCAST(buffer)); + BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(buffer)); + ntru_decode_pubkey(pubkey, p, q, src); + + strbuf_clear(buffer); + ntru_encode_ciphertext(ciphertext, p, q, + BinarySink_UPCAST(buffer)); + BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(buffer)); + ntru_decode_ciphertext(ciphertext, keypair, src); + + strbuf_clear(buffer); + ntru_encode_plaintext(plaintext, p, BinarySink_UPCAST(buffer)); + log_end(); + + break; + } + + assert(!memcmp(pubkey_orig, pubkey_check, + p*sizeof(*pubkey_check))); + } + } + + sfree(pubkey_orig); + sfree(pubkey_check); + sfree(pubkey); + sfree(plaintext); + sfree(ciphertext); + strbuf_free(buffer); +} + static const struct test tests[] = { #define STRUCT_TEST(X) { #X, test_##X }, TESTLIST(STRUCT_TEST) -- cgit v1.2.3 From 9aae695c62fd23e4832b84c3434f716f3ad2ec0f Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Wed, 20 Apr 2022 20:14:25 +0100 Subject: NTRU: speed up the polynomial inversion. I wasn't really satisfied with the previous version, but it was easiest to get Stein's algorithm working on polynomials by doing it exactly how I already knew to do it for integers. But now I've improved it in two ways. The first improvement I got from another implementation: instead of transforming A into A - kB for some k that makes the constant term zero, you can scale _both_ inputs, replacing A with mA - kB for some k,m. The advantage is that you can calculate m and k very easily, by making each one the constant term of the other polynomial, which means you don't need to invert something mod q in every step. (Rather like the projective-coordinates optimisations in elliptic curves, where instead of inverting in every step you accumulate the product of all the factors that need to be inverted, and invert the whole product once at the very end.) The second improvement is to abandon my cumbersome unwinding loop that builds up the output coefficients by reversing the steps in the original gcd-finding loop. Instead, I do the thing you do in normal Euclid's algorithm: keep track of the coefficients as you go through the original loop. I had wanted to do this before, but hadn't figured out how you could deal with dividing a coefficient by x when (unlike the associated real value) the coefficient isn't a multiple of x. But the answer is very simple: x is invertible in the ring we're working in (its inverse mod x^p-x-1 is just x^{p-1}-1), so you _can_ just divide your coefficient by x, and moreover, very easily! Together, these changes speed up the NTRU key generation by about a factor of 1.5. And they remove lots of complicated code as well, so everybody wins. --- crypto/ntru.c | 179 +++++++++++++++++++++++----------------------------------- 1 file changed, 72 insertions(+), 107 deletions(-) diff --git a/crypto/ntru.c b/crypto/ntru.c index 88d4f084..ca58bacd 100644 --- a/crypto/ntru.c +++ b/crypto/ntru.c @@ -248,16 +248,21 @@ void ntru_ring_multiply(uint16_t *out, const uint16_t *a, const uint16_t *b, * (Maybe more than one if all the stars align in the second case, if * the subtraction cancels the leading term as well as the constant * term.) So in at most deg A + deg B steps, we must have reached the - * situation where both polys are constants, and in one more step - * after that, one of them will be zero. Or rather, that's what - * happens in the case where A,B are coprime; if not, then one hits - * zero while the other is still nonzero. + * situation where both polys are constants; in one more step after + * that, one of them will be zero; and in one step after _that_, the + * zero one will reliably be the one we're dividing by x. Or rather, + * that's what happens in the case where A,B are coprime; if not, then + * one hits zero while the other is still nonzero. * - * Then unwind all the transformations, to find a linear combination - * of the two original polynomials that yields the nonzero one of the - * two outputs. (In fact we only need the coefficient of 'in' in that - * linear combination, but we have to compute both halves, because - * they keep swapping round during the unwinding.) + * In a normal gcd algorithm, you'd track a linear combination of the + * two original polynomials that yields each working value, and end up + * with a linear combination of the inputs that yields the gcd. In + * this algorithm, the 'divide off x' step makes that awkward - but we + * can solve that by instead multiplying by the inverse of x in the + * ring that we want our answer to be valid in! And since the modulus + * polynomial of the ring is x^p-x-1, the inverse of x is easy to + * calculate, because it's always just x^{p-1} - 1, which is also very + * easy to multiply by. */ unsigned ntru_ring_invert(uint16_t *out, const uint16_t *in, unsigned p, unsigned q) @@ -268,26 +273,32 @@ unsigned ntru_ring_invert(uint16_t *out, const uint16_t *in, const size_t SIZE = p+1; /* Number of steps of the algorithm is the max possible value of - * deg A + deg B + 1, where deg A <= p-1 and deg B = p */ - const size_t STEPS = 2*p; + * deg A + deg B + 2, where deg A <= p-1 and deg B = p */ + const size_t STEPS = 2*p + 1; /* Our two working polynomials */ uint16_t *A = snewn(SIZE, uint16_t); uint16_t *B = snewn(SIZE, uint16_t); - /* History of what we did */ - uint16_t *multipliers = snewn(STEPS, uint16_t); - uint8_t *swaps = snewn(STEPS, uint8_t); + /* Coefficient of the input value in each one */ + uint16_t *Ac = snewn(SIZE, uint16_t); + uint16_t *Bc = snewn(SIZE, uint16_t); - /* Initialise A to the input */ + /* Initialise A to the input, and Ac correspondingly to 1 */ memcpy(A, in, p*sizeof(uint16_t)); A[p] = 0; + Ac[0] = 1; + for (size_t i = 1; i < SIZE; i++) + Ac[i] = 0; - /* And initialise B to the quotient polynomial of the ring, x^p-x-1 */ + /* Initialise B to the quotient polynomial of the ring, x^p-x-1 + * And Bc = 0 */ B[0] = B[1] = q-1; for (size_t i = 2; i < p; i++) B[i] = 0; B[p] = 1; + for (size_t i = 0; i < SIZE; i++) + Bc[i] = 0; /* Run the gcd-finding algorithm. */ for (size_t i = 0; i < STEPS; i++) { @@ -318,109 +329,67 @@ unsigned ntru_ring_invert(uint16_t *out, const uint16_t *in, A[j] ^= diff; B[j] ^= diff; } + for (size_t j = 0; j < SIZE; j++) { + uint16_t diff = (Ac[j] ^ Bc[j]) & swap_mask; + Ac[j] ^= diff; + Bc[j] ^= diff; + } /* - * Add a multiple of B to A to make A's constant term zero. In - * one of the two cases, A's constant term is already zero, so - * this will do nothing but take the same length of time as - * doing something, which is just what we want. + * Replace A with a linear combination of both A and B that + * has constant term zero, which we do by calculating + * + * (constant term of B) * A - (constant term of A) * B * - * Also, shift down by one in the course of doing this. + * In one of the two cases, A's constant term is already zero, + * so the coefficient of B will be zero too; hence, this will + * do nothing useful (it will merely scale A by some scalar + * value), but it will take the same length of time as doing + * something, which is just what we want. */ - uint16_t mult = REDUCE((q - A[0]) * INVERT(B[0])); - for (size_t j = 1; j < SIZE; j++) - A[j-1] = REDUCE(A[j] + mult * B[j]); - A[SIZE-1] = 0; + uint16_t Amult = B[0], Bmult = q - A[0]; + for (size_t j = 0; j < SIZE; j++) + A[j] = REDUCE(Amult * A[j] + Bmult * B[j]); + /* And do the same transformation to Ac */ + for (size_t j = 0; j < SIZE; j++) + Ac[j] = REDUCE(Amult * Ac[j] + Bmult * Bc[j]); /* - * Record what we just did. + * Now divide A by x, and compensate by multiplying Ac by + * x^{p-1}-1 mod x^p-x-1. + * + * That multiplication is particularly easy, precisely because + * x^{p-1}-1 is the multiplicative inverse of x! Each x^n term + * for n>0 just moves down to the x^{n-1} term, and only the + * constant term has to be dealt with in an interesting way. */ - swaps[i] = need_swap; - multipliers[i] = mult; + for (size_t j = 1; j < SIZE; j++) + A[j-1] = A[j]; + A[SIZE-1] = 0; + uint16_t Ac0 = Ac[0]; + for (size_t j = 1; j < p; j++) + Ac[j-1] = Ac[j]; + Ac[p-1] = Ac0; + Ac[0] = REDUCE(Ac[0] + q - Ac0); } /* - * Now we expect that one of the polynomials is zero, and the - * other is zero except for the constant term. If so, then they - * are coprime, and we're going to return success. If not, they - * have a common factor. + * Now we expect that A is 0, and B is a constant. If so, then + * they are coprime, and we're going to return success. If not, + * they have a common factor. */ - unsigned success = iszero(A[0]) ^ iszero(B[0]); + unsigned success = iszero(A[0]) & (1 ^ iszero(B[0])); for (size_t j = 1; j < SIZE; j++) success &= iszero(A[j]) & iszero(B[j]); /* - * Now unwind to make a linear combination of the two original - * polynomials that equals 1 (assuming we're going to return - * success). - * - * We make two polynomials Ac,Bc, with the intention that we'll - * preserve the invariant Ac*A + Bc*B = 1 as we rewind through the - * steps. - * - * Initially, we set the coefficient of the zero one of A,B to - * zero, and the coefficient of the constant one to be its - * inverse. + * So we're going to return Bc, but first, scale it by the + * multiplicative inverse of the constant we ended up with in + * B[0]. */ - uint16_t *Ac = snewn(SIZE, uint16_t); - uint16_t *Bc = snewn(SIZE, uint16_t); - for (size_t i = 1; i < SIZE; i++) - Ac[i] = Bc[i] = 0; - Ac[0] = INVERT(A[0]); - Bc[0] = INVERT(B[0]); - - for (size_t i = STEPS; i-- > 0 ;) { - /* - * The last thing we did in our step was always to divide A by - * x. That is, we currently have 1 as a linear combination of - * A and B, and now we need it as a linear combination of A*x - * and B. - * - * We have Ac*A + Bc*B = (Ac+k*B)*A + (Bc-k*A)*B for any k. - * So choose k such that Ac+k*B has zero constant term - * (possible since B has nonzero constant term), and then we - * have 1 = (Ac+k*B)/x * (A*x) + (Bc-k*A) * B. - */ - uint16_t minusk = REDUCE(Ac[0] * INVERT(B[0])); - uint16_t k = q - minusk; - for (size_t j = 1; j < SIZE; j++) - Ac[j-1] = REDUCE(Ac[j] + k * B[j]); - Ac[SIZE-1] = 0; - for (size_t j = 0; j < SIZE; j++) - Bc[j] = REDUCE(Bc[j] + minusk * A[j]); - - /* And unwind the shift of A itself. */ - memmove(A+1, A, (SIZE-1) * sizeof(*A)); - A[0] = 0; - - /* - * Before that, we added m*B to A. So our new A will be A-m*B. - * So we have 1 = Ac*A + Bc*B = Ac*(A-m*B) + (Bc+m*Ac)*B. - */ - uint16_t m = multipliers[i]; - uint16_t minusm = q - m; - for (size_t j = 0; j < SIZE; j++) - Bc[j] = REDUCE(Bc[j] + m * Ac[j]); - for (size_t j = 0; j < SIZE; j++) - A[j] = REDUCE(A[j] + minusm * B[j]); - - /* - * And before that, we conditionally swapped A,B. - */ - uint16_t swap_mask = -swaps[i]; - for (size_t j = 0; j < SIZE; j++) { - uint16_t diff; - diff = (A[j] ^ B[j]) & swap_mask; - A[j] ^= diff; - B[j] ^= diff; - diff = (Ac[j] ^ Bc[j]) & swap_mask; - Ac[j] ^= diff; - Bc[j] ^= diff; - } - } - - /* Done! Our coefficient Ac is the inverse, if one exists. */ - memcpy(out, Ac, p * sizeof(*out)); + uint16_t scale = INVERT(B[0]); + for (size_t i = 0; i < p; i++) + out[i] = REDUCE(scale * Bc[i]); smemclr(A, SIZE * sizeof(*A)); sfree(A); @@ -430,10 +399,6 @@ unsigned ntru_ring_invert(uint16_t *out, const uint16_t *in, sfree(Ac); smemclr(Bc, SIZE * sizeof(*B)); sfree(Bc); - smemclr(multipliers, STEPS * sizeof(*multipliers)); - sfree(multipliers); - smemclr(swaps, STEPS * sizeof(*swaps)); - sfree(swaps); return success; } -- cgit v1.2.3 From 3a54f28a4eab33e322ac526bf8fc74b78c1013ea Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Wed, 20 Apr 2022 10:32:14 +0100 Subject: Extra utility function add_to_commasep_pl. Just like add_to_commasep, but takes a ptrlen. --- ssh.h | 1 + ssh/common.c | 9 +++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/ssh.h b/ssh.h index fb86f4f0..690d55c7 100644 --- a/ssh.h +++ b/ssh.h @@ -1732,6 +1732,7 @@ unsigned alloc_channel_id_general(tree234 *channels, size_t localid_offset); alloc_channel_id_general(tree, offsetof(type, localid))) void add_to_commasep(strbuf *buf, const char *data); +void add_to_commasep_pl(strbuf *buf, ptrlen data); bool get_commasep_word(ptrlen *list, ptrlen *word); SeatPromptResult verify_ssh_host_key( diff --git a/ssh/common.c b/ssh/common.c index 0c9f51e3..424c8f71 100644 --- a/ssh/common.c +++ b/ssh/common.c @@ -607,11 +607,16 @@ unsigned alloc_channel_id_general(tree234 *channels, size_t localid_offset) * lists of protocol identifiers in SSH-2. */ -void add_to_commasep(strbuf *buf, const char *data) +void add_to_commasep_pl(strbuf *buf, ptrlen data) { if (buf->len > 0) put_byte(buf, ','); - put_data(buf, data, strlen(data)); + put_datapl(buf, data); +} + +void add_to_commasep(strbuf *buf, const char *data) +{ + add_to_commasep_pl(buf, ptrlen_from_asciz(data)); } bool get_commasep_word(ptrlen *list, ptrlen *word) -- cgit v1.2.3 From 6a9e4ba24af07ae8a45699070a115a3a51df13c7 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Wed, 20 Apr 2022 10:33:08 +0100 Subject: kexinit_algorithm: switch to storing names as ptrlen. They're now also compared as strings, fixing the slight fragility where we depended on string-literal pointer equality. --- ssh/transport2.c | 28 +++++++++++++++++----------- ssh/transport2.h | 2 +- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/ssh/transport2.c b/ssh/transport2.c index 02289747..b95acaf0 100644 --- a/ssh/transport2.c +++ b/ssh/transport2.c @@ -302,17 +302,17 @@ static void ssh2_mkkey( * Find a slot in a KEXINIT algorithm list to use for a new algorithm. * If the algorithm is already in the list, return a pointer to its * entry, otherwise return an entry from the end of the list. - * This assumes that every time a particular name is passed in, it - * comes from the same string constant. If this isn't true, this - * function may need to be rewritten to use strcmp() instead. + * + * 'name' is expected to be a ptrlen which it's safe to keep a copy + * of. */ -static struct kexinit_algorithm *ssh2_kexinit_addalg(struct kexinit_algorithm - *list, const char *name) +static struct kexinit_algorithm *ssh2_kexinit_addalg_pl( + struct kexinit_algorithm *list, ptrlen name) { int i; for (i = 0; i < MAXKEXLIST; i++) - if (list[i].name == NULL || list[i].name == name) { + if (list[i].name.ptr == NULL || ptrlen_eq_ptrlen(list[i].name, name)) { list[i].name = name; return &list[i]; } @@ -320,6 +320,12 @@ static struct kexinit_algorithm *ssh2_kexinit_addalg(struct kexinit_algorithm unreachable("Should never run out of space in KEXINIT list"); } +static struct kexinit_algorithm *ssh2_kexinit_addalg( + struct kexinit_algorithm *list, const char *name) +{ + return ssh2_kexinit_addalg_pl(list, ptrlen_from_asciz(name)); +} + bool ssh2_common_filter_queue(PacketProtocolLayer *ppl) { static const char *const ssh2_disconnect_reasons[] = { @@ -606,7 +612,7 @@ static void ssh2_write_kexinit_lists( for (i = 0; i < NKEXLIST; i++) for (j = 0; j < MAXKEXLIST; j++) - kexlists[i][j].name = NULL; + kexlists[i][j].name = make_ptrlen(NULL, 0); /* List key exchange algorithms. */ warn = false; for (i = 0; i < n_preferred_kex; i++) { @@ -832,8 +838,8 @@ static void ssh2_write_kexinit_lists( put_datapl(list, ssc->kex_override[i]); } else { for (j = 0; j < MAXKEXLIST; j++) { - if (kexlists[i][j].name == NULL) break; - add_to_commasep(list, kexlists[i][j].name); + if (kexlists[i][j].name.ptr == NULL) break; + add_to_commasep_pl(list, kexlists[i][j].name); } } if (i == KEXLIST_KEX && first_time) { @@ -923,8 +929,8 @@ static bool ssh2_scan_kexinits( selected[i] = NULL; for (j = 0; j < MAXKEXLIST; j++) { - if (kexlists[i][j].name && - ptrlen_eq_string(found, kexlists[i][j].name)) { + if (kexlists[i][j].name.ptr && + ptrlen_eq_ptrlen(found, kexlists[i][j].name)) { selected[i] = &kexlists[i][j]; break; } diff --git a/ssh/transport2.h b/ssh/transport2.h index aaec91a8..8ca6993d 100644 --- a/ssh/transport2.h +++ b/ssh/transport2.h @@ -20,7 +20,7 @@ #define MAXKEXLIST 16 struct kexinit_algorithm { - const char *name; + ptrlen name; union { struct { const ssh_kex *kex; -- cgit v1.2.3 From 7d44e35bb3780c04a36ca6d383fe9ec4c0d37137 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 21 Apr 2022 05:11:58 +0100 Subject: transport2: make kexlists dynamically allocated. The list of kex methods recently ran out of space due to the addition of NTRU (at least, if you have GSSAPI enabled). It's time to stop having an arbitrary limit on those arrays and switch to doing it properly. --- ssh/transport2.c | 88 +++++++++++++++++++++++++++++--------------------------- ssh/transport2.h | 7 +++-- 2 files changed, 50 insertions(+), 45 deletions(-) diff --git a/ssh/transport2.c b/ssh/transport2.c index b95acaf0..d43f7e8a 100644 --- a/ssh/transport2.c +++ b/ssh/transport2.c @@ -216,6 +216,8 @@ static void ssh2_transport_free(PacketProtocolLayer *ppl) ssh_key_free(s->hkey); s->hkey = NULL; } + for (size_t i = 0; i < NKEXLIST; i++) + sfree(s->kexlists[i].algs); if (s->f) mp_free(s->f); if (s->p) mp_free(s->p); if (s->g) mp_free(s->g); @@ -307,21 +309,20 @@ static void ssh2_mkkey( * of. */ static struct kexinit_algorithm *ssh2_kexinit_addalg_pl( - struct kexinit_algorithm *list, ptrlen name) + struct kexinit_algorithm_list *list, ptrlen name) { - int i; - - for (i = 0; i < MAXKEXLIST; i++) - if (list[i].name.ptr == NULL || ptrlen_eq_ptrlen(list[i].name, name)) { - list[i].name = name; - return &list[i]; - } - - unreachable("Should never run out of space in KEXINIT list"); + for (size_t i = 0; i < list->nalgs; i++) + if (ptrlen_eq_ptrlen(list->algs[i].name, name)) + return &list->algs[i]; + + sgrowarray(list->algs, list->algsize, list->nalgs); + struct kexinit_algorithm *entry = &list->algs[list->nalgs++]; + entry->name = name; + return entry; } static struct kexinit_algorithm *ssh2_kexinit_addalg( - struct kexinit_algorithm *list, const char *name) + struct kexinit_algorithm_list *list, const char *name) { return ssh2_kexinit_addalg_pl(list, ptrlen_from_asciz(name)); } @@ -489,7 +490,7 @@ PktIn *ssh2_transport_pop(struct ssh2_transport_state *s) static void ssh2_write_kexinit_lists( BinarySink *pktout, - struct kexinit_algorithm kexlists[NKEXLIST][MAXKEXLIST], + struct kexinit_algorithm_list kexlists[NKEXLIST], Conf *conf, const SshServerConfig *ssc, int remote_bugs, const char *hk_host, int hk_port, const ssh_keyalg *hk_prev, ssh_transient_hostkey_cache *thc, @@ -611,15 +612,14 @@ static void ssh2_write_kexinit_lists( preferred_comp = &ssh_comp_none; for (i = 0; i < NKEXLIST; i++) - for (j = 0; j < MAXKEXLIST; j++) - kexlists[i][j].name = make_ptrlen(NULL, 0); + kexlists[i].nalgs = 0; /* List key exchange algorithms. */ warn = false; for (i = 0; i < n_preferred_kex; i++) { const ssh_kexes *k = preferred_kex[i]; if (!k) warn = true; else for (j = 0; j < k->nkexes; j++) { - alg = ssh2_kexinit_addalg(kexlists[KEXLIST_KEX], + alg = ssh2_kexinit_addalg(&kexlists[KEXLIST_KEX], k->list[j]->name); alg->u.kex.kex = k->list[j]; alg->u.kex.warn = warn; @@ -634,20 +634,20 @@ static void ssh2_write_kexinit_lists( for (i = 0; i < our_nhostkeys; i++) { const ssh_keyalg *keyalg = ssh_key_alg(our_hostkeys[i]); - alg = ssh2_kexinit_addalg(kexlists[KEXLIST_HOSTKEY], + alg = ssh2_kexinit_addalg(&kexlists[KEXLIST_HOSTKEY], keyalg->ssh_id); alg->u.hk.hostkey = keyalg; alg->u.hk.hkflags = 0; alg->u.hk.warn = false; if (keyalg == &ssh_rsa) { - alg = ssh2_kexinit_addalg(kexlists[KEXLIST_HOSTKEY], + alg = ssh2_kexinit_addalg(&kexlists[KEXLIST_HOSTKEY], "rsa-sha2-256"); alg->u.hk.hostkey = keyalg; alg->u.hk.hkflags = SSH_AGENT_RSA_SHA2_256; alg->u.hk.warn = false; - alg = ssh2_kexinit_addalg(kexlists[KEXLIST_HOSTKEY], + alg = ssh2_kexinit_addalg(&kexlists[KEXLIST_HOSTKEY], "rsa-sha2-512"); alg->u.hk.hostkey = keyalg; alg->u.hk.hkflags = SSH_AGENT_RSA_SHA2_512; @@ -678,7 +678,7 @@ static void ssh2_write_kexinit_lists( if (conf_get_bool(conf, CONF_ssh_prefer_known_hostkeys) && have_ssh_host_key(hk_host, hk_port, ssh2_hostkey_algs[j].alg->cache_id)) { - alg = ssh2_kexinit_addalg(kexlists[KEXLIST_HOSTKEY], + alg = ssh2_kexinit_addalg(&kexlists[KEXLIST_HOSTKEY], ssh2_hostkey_algs[j].alg->ssh_id); alg->u.hk.hostkey = ssh2_hostkey_algs[j].alg; alg->u.hk.warn = warn; @@ -692,7 +692,7 @@ static void ssh2_write_kexinit_lists( for (j = 0; j < lenof(ssh2_hostkey_algs); j++) { if (ssh2_hostkey_algs[j].id != preferred_hk[i]) continue; - alg = ssh2_kexinit_addalg(kexlists[KEXLIST_HOSTKEY], + alg = ssh2_kexinit_addalg(&kexlists[KEXLIST_HOSTKEY], ssh2_hostkey_algs[j].alg->ssh_id); alg->u.hk.hostkey = ssh2_hostkey_algs[j].alg; alg->u.hk.warn = warn; @@ -721,7 +721,7 @@ static void ssh2_write_kexinit_lists( continue; if (ssh_transient_hostkey_cache_has( thc, ssh2_hostkey_algs[j].alg)) { - alg = ssh2_kexinit_addalg(kexlists[KEXLIST_HOSTKEY], + alg = ssh2_kexinit_addalg(&kexlists[KEXLIST_HOSTKEY], ssh2_hostkey_algs[j].alg->ssh_id); alg->u.hk.hostkey = ssh2_hostkey_algs[j].alg; alg->u.hk.warn = warn; @@ -738,19 +738,19 @@ static void ssh2_write_kexinit_lists( * reverification. */ assert(hk_prev); - alg = ssh2_kexinit_addalg(kexlists[KEXLIST_HOSTKEY], hk_prev->ssh_id); + alg = ssh2_kexinit_addalg(&kexlists[KEXLIST_HOSTKEY], hk_prev->ssh_id); alg->u.hk.hostkey = hk_prev; alg->u.hk.warn = false; } if (can_gssapi_keyex) { - alg = ssh2_kexinit_addalg(kexlists[KEXLIST_HOSTKEY], "null"); + alg = ssh2_kexinit_addalg(&kexlists[KEXLIST_HOSTKEY], "null"); alg->u.hk.hostkey = NULL; } /* List encryption algorithms (client->server then server->client). */ for (k = KEXLIST_CSCIPHER; k <= KEXLIST_SCCIPHER; k++) { warn = false; #ifdef FUZZING - alg = ssh2_kexinit_addalg(kexlists[k], "none"); + alg = ssh2_kexinit_addalg(&kexlists[K], "none"); alg->u.cipher.cipher = NULL; alg->u.cipher.warn = warn; #endif /* FUZZING */ @@ -758,7 +758,7 @@ static void ssh2_write_kexinit_lists( const ssh2_ciphers *c = preferred_ciphers[i]; if (!c) warn = true; else for (j = 0; j < c->nciphers; j++) { - alg = ssh2_kexinit_addalg(kexlists[k], + alg = ssh2_kexinit_addalg(&kexlists[k], c->list[j]->ssh2_id); alg->u.cipher.cipher = c->list[j]; alg->u.cipher.warn = warn; @@ -785,7 +785,7 @@ static void ssh2_write_kexinit_lists( alg->u.mac.etm = false; #endif /* FUZZING */ for (i = 0; i < nmacs; i++) { - alg = ssh2_kexinit_addalg(kexlists[j], maclist[i]->name); + alg = ssh2_kexinit_addalg(&kexlists[j], maclist[i]->name); alg->u.mac.mac = maclist[i]; alg->u.mac.etm = false; } @@ -793,7 +793,7 @@ static void ssh2_write_kexinit_lists( /* For each MAC, there may also be an ETM version, * which we list second. */ if (maclist[i]->etm_name) { - alg = ssh2_kexinit_addalg(kexlists[j], maclist[i]->etm_name); + alg = ssh2_kexinit_addalg(&kexlists[j], maclist[i]->etm_name); alg->u.mac.mac = maclist[i]; alg->u.mac.etm = true; } @@ -806,22 +806,22 @@ static void ssh2_write_kexinit_lists( for (j = KEXLIST_CSCOMP; j <= KEXLIST_SCCOMP; j++) { assert(lenof(compressions) > 1); /* Prefer non-delayed versions */ - alg = ssh2_kexinit_addalg(kexlists[j], preferred_comp->name); + alg = ssh2_kexinit_addalg(&kexlists[j], preferred_comp->name); alg->u.comp.comp = preferred_comp; alg->u.comp.delayed = false; if (preferred_comp->delayed_name) { - alg = ssh2_kexinit_addalg(kexlists[j], + alg = ssh2_kexinit_addalg(&kexlists[j], preferred_comp->delayed_name); alg->u.comp.comp = preferred_comp; alg->u.comp.delayed = true; } for (i = 0; i < lenof(compressions); i++) { const ssh_compression_alg *c = compressions[i]; - alg = ssh2_kexinit_addalg(kexlists[j], c->name); + alg = ssh2_kexinit_addalg(&kexlists[j], c->name); alg->u.comp.comp = c; alg->u.comp.delayed = false; if (c->delayed_name) { - alg = ssh2_kexinit_addalg(kexlists[j], c->delayed_name); + alg = ssh2_kexinit_addalg(&kexlists[j], c->delayed_name); alg->u.comp.comp = c; alg->u.comp.delayed = true; } @@ -837,10 +837,8 @@ static void ssh2_write_kexinit_lists( if (ssc && ssc->kex_override[i].ptr) { put_datapl(list, ssc->kex_override[i]); } else { - for (j = 0; j < MAXKEXLIST; j++) { - if (kexlists[i][j].name.ptr == NULL) break; - add_to_commasep_pl(list, kexlists[i][j].name); - } + for (j = 0; j < kexlists[i].nalgs; j++) + add_to_commasep_pl(list, kexlists[i].algs[j].name); } if (i == KEXLIST_KEX && first_time) { if (our_hostkeys) /* we're the server */ @@ -858,12 +856,12 @@ static void ssh2_write_kexinit_lists( static bool ssh2_scan_kexinits( ptrlen client_kexinit, ptrlen server_kexinit, - struct kexinit_algorithm kexlists[NKEXLIST][MAXKEXLIST], + struct kexinit_algorithm_list kexlists[NKEXLIST], const ssh_kex **kex_alg, const ssh_keyalg **hostkey_alg, transport_direction *cs, transport_direction *sc, bool *warn_kex, bool *warn_hk, bool *warn_cscipher, bool *warn_sccipher, Ssh *ssh, bool *ignore_guess_cs_packet, bool *ignore_guess_sc_packet, - int *n_server_hostkeys, int server_hostkeys[MAXKEXLIST], unsigned *hkflags, + int *n_server_hostkeys, int *server_hostkeys, unsigned *hkflags, bool *can_send_ext_info) { BinarySource client[1], server[1]; @@ -928,10 +926,9 @@ static bool ssh2_scan_kexinits( found_match: selected[i] = NULL; - for (j = 0; j < MAXKEXLIST; j++) { - if (kexlists[i][j].name.ptr && - ptrlen_eq_ptrlen(found, kexlists[i][j].name)) { - selected[i] = &kexlists[i][j]; + for (j = 0; j < kexlists[i].nalgs; j++) { + if (ptrlen_eq_ptrlen(found, kexlists[i].algs[j].name)) { + selected[i] = &kexlists[i].algs[j]; break; } } @@ -1241,7 +1238,8 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl) * selected algorithm identifiers. */ { - int nhk, hks[MAXKEXLIST], i, j; + int nhk, i, j; + int *hks = snewn(s->kexlists[KEXLIST_HOSTKEY].nalgs, int); if (!ssh2_scan_kexinits( ptrlen_from_strbuf(s->client_kexinit), @@ -1249,8 +1247,10 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl) s->kexlists, &s->kex_alg, &s->hostkey_alg, s->cstrans, s->sctrans, &s->warn_kex, &s->warn_hk, &s->warn_cscipher, &s->warn_sccipher, s->ppl.ssh, NULL, &s->ignorepkt, &nhk, hks, - &s->hkflags, &s->can_send_ext_info)) + &s->hkflags, &s->can_send_ext_info)) { + sfree(hks); return; /* false means a fatal error function was called */ + } /* * In addition to deciding which host key we're actually going @@ -1272,6 +1272,8 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl) s->uncert_hostkeys[s->n_uncert_hostkeys++] = j; } } + + sfree(hks); } if (s->warn_kex) { diff --git a/ssh/transport2.h b/ssh/transport2.h index 8ca6993d..dc62f71f 100644 --- a/ssh/transport2.h +++ b/ssh/transport2.h @@ -18,7 +18,6 @@ #define DH_MIN_SIZE 1024 #define DH_MAX_SIZE 8192 -#define MAXKEXLIST 16 struct kexinit_algorithm { ptrlen name; union { @@ -45,6 +44,10 @@ struct kexinit_algorithm { } comp; } u; }; +struct kexinit_algorithm_list { + struct kexinit_algorithm *algs; + size_t nalgs, algsize; +}; #define HOSTKEY_ALGORITHMS(X) \ X(HK_ED25519, ssh_ecdsa_ed25519) \ @@ -190,7 +193,7 @@ struct ssh2_transport_state { SeatPromptResult spr; bool guessok; bool ignorepkt; - struct kexinit_algorithm kexlists[NKEXLIST][MAXKEXLIST]; + struct kexinit_algorithm_list kexlists[NKEXLIST]; #ifndef NO_GSSAPI Ssh_gss_buf gss_buf; Ssh_gss_buf gss_rcvtok, gss_sndtok; -- cgit v1.2.3 From 5388e5f7eed8f145493e221e3349c150e6081246 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 22 Apr 2022 15:19:25 +0100 Subject: Fix use-after-free in locale-based stripctrl. We call setlocale() at the start of the function to get the current LC_CTYPE locale, then set it to what we need during the function, and then call setlocale() at the end to put it back again. But the middle call is allowed to invalidate the pointer returned from the first, so we have to save it in our own allocated storage until the end of the function. This bit me during development just now, and I was surprised that it hadn't come up before! But I suppose this is one of those things that's only _allowed_ to fail, and need not in all circumstances - perhaps it depends on what your LC_CTYPE was set to before. --- utils/stripctrl.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/utils/stripctrl.c b/utils/stripctrl.c index 58289b10..d723a079 100644 --- a/utils/stripctrl.c +++ b/utils/stripctrl.c @@ -305,7 +305,7 @@ static void stripctrl_locale_BinarySink_write( container_of(sccpub, StripCtrlCharsImpl, public); const char *p = (const char *)vp; - const char *previous_locale = setlocale(LC_CTYPE, NULL); + char *previous_locale = dupstr(setlocale(LC_CTYPE, NULL)); setlocale(LC_CTYPE, ""); /* @@ -391,6 +391,7 @@ static void stripctrl_locale_BinarySink_write( out: setlocale(LC_CTYPE, previous_locale); + sfree(previous_locale); } static void stripctrl_term_BinarySink_write( -- cgit v1.2.3 From 38a5f59c7535538cf4e546bcfc99e436d15cedb1 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 22 Apr 2022 17:15:34 +0100 Subject: mainchan.c: defer a couple of ssh_sw_abort. When a subsidiary part of the SSH system wants to abort the whole connection, it's supposed to call ssh_sw_abort_deferred, on pain of free-order confusion. Elsewhere in mainchan.c I was getting this right, but I missed a couple. --- ssh/mainchan.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ssh/mainchan.c b/ssh/mainchan.c index 04993620..5c1769ea 100644 --- a/ssh/mainchan.c +++ b/ssh/mainchan.c @@ -296,8 +296,8 @@ static void mainchan_request_response(Channel *chan, bool success) * If there's no remote_cmd2 configured, then we have no * fallback command, so we've run out of options. */ - ssh_sw_abort(mc->ppl->ssh, - "Server refused to start a shell/command"); + ssh_sw_abort_deferred(mc->ppl->ssh, + "Server refused to start a shell/command"); } return; } @@ -310,8 +310,8 @@ static void mainchan_request_response(Channel *chan, bool success) ssh_got_fallback_cmd(mc->ppl->ssh); mainchan_ready(mc); } else { - ssh_sw_abort(mc->ppl->ssh, - "Server refused to start a shell/command"); + ssh_sw_abort_deferred(mc->ppl->ssh, + "Server refused to start a shell/command"); } return; } -- cgit v1.2.3 From 52f296b7e21176cd12ec5e7848b0983bf6bac0f7 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 22 Apr 2022 22:20:36 +0100 Subject: ntru.c: fix benign paste error. smemclr(array, ... * sizeof(*different_array)) was not what I meant to do, even though the two arrays have the same element size. --- crypto/ntru.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crypto/ntru.c b/crypto/ntru.c index ca58bacd..9cc11c80 100644 --- a/crypto/ntru.c +++ b/crypto/ntru.c @@ -395,9 +395,9 @@ unsigned ntru_ring_invert(uint16_t *out, const uint16_t *in, sfree(A); smemclr(B, SIZE * sizeof(*B)); sfree(B); - smemclr(Ac, SIZE * sizeof(*A)); + smemclr(Ac, SIZE * sizeof(*Ac)); sfree(Ac); - smemclr(Bc, SIZE * sizeof(*B)); + smemclr(Bc, SIZE * sizeof(*Bc)); sfree(Bc); return success; -- cgit v1.2.3 From de47ec2f5f7444f0aae1bfe8badc17785e1bf2f4 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Tue, 19 Apr 2022 10:07:11 +0100 Subject: cryptsuite.py: shorter idiom for base64 decoding. These days, the base64 module has 'b64decode', which can tolerate a str or a bytes as input. Switched to using that, and also, imported it under a nice short name 'b64'. In the process, removed the obsolete equivocation between base64.decodebytes and base64.decodestring. That was there to cope with Python 2 - but the assert statement right next to it has been enforcing P3 since commit 2ec2b796ed24cb5 two years ago! --- test/cryptsuite.py | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/test/cryptsuite.py b/test/cryptsuite.py index ef2f79a4..534b1aab 100755 --- a/test/cryptsuite.py +++ b/test/cryptsuite.py @@ -8,7 +8,7 @@ import functools import contextlib import hashlib import binascii -import base64 +from base64 import b64decode as b64 import json try: from math import gcd @@ -21,11 +21,6 @@ from ssh import * assert sys.version_info[:2] >= (3,0), "This is Python 3 code" -try: - base64decode = base64.decodebytes -except AttributeError: - base64decode = base64.decodestring - def unhex(s): return binascii.unhexlify(s.replace(" ", "").replace("\n", "")) @@ -1386,9 +1381,9 @@ class crypt(MyTestBase): def testSSH2Fingerprints(self): # A sensible key blob that we can make sense of. - sensible_blob = base64.decodebytes( - b'AAAAC3NzaC1lZDI1NTE5AAAAICWiV0VAD4lQ7taUN7vZ5Rkc' - b'SLJBW5ubn6ZINwCOzpn3') + sensible_blob = b64( + 'AAAAC3NzaC1lZDI1NTE5AAAAICWiV0VAD4lQ7taUN7vZ5Rkc' + 'SLJBW5ubn6ZINwCOzpn3') self.assertEqual(ssh2_fingerprint_blob(sensible_blob, "sha256"), b'ssh-ed25519 255 SHA256:' b'E4VmaHW0sUF7SUgSEOmMJ8WBtt0e/j3zbsKvyqfFnu4') @@ -2300,8 +2295,8 @@ culpa qui officia deserunt mollit anim id est laborum. for alg, pubb64, privb64, bits, cachestr, siglist in test_keys: # Decode the blobs in the above test data. - pubblob = base64decode(pubb64.encode('ASCII')) - privblob = base64decode(privb64.encode('ASCII')) + pubblob = b64(pubb64) + privblob = b64(privb64) # Check the method that examines a public blob directly # and returns an integer showing the key size. @@ -2335,7 +2330,7 @@ culpa qui officia deserunt mollit anim id est laborum. # value. for flags, sigb64 in siglist: # Decode the signature blob from the test data. - sigblob = base64decode(sigb64.encode('ASCII')) + sigblob = b64(sigb64) # Sign our test message, and check it produces exactly # the expected signature blob. @@ -3293,8 +3288,7 @@ class standard_test_vectors(MyTestBase): "7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v", "FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS", 1, "MD5", False] - cnonce = base64.decodebytes( - b'f2/wE4q74E6zIJEtWaHKaf5wv/H5QzzpXusqGemxURZJ') + cnonce = b64('f2/wE4q74E6zIJEtWaHKaf5wv/H5QzzpXusqGemxURZJ') with queued_specific_random_data(cnonce): self.assertEqual(http_digest_response(*params), b'username="Mufasa", ' @@ -3341,8 +3335,7 @@ class standard_test_vectors(MyTestBase): "5TsQWLVdgBdmrQ0XsxbDODV+57QdFR34I9HAbC/RVvkK", "HRPCssKJSGjCrkzDg8OhwpzCiGPChXYjwrI2QmXDnsOS", 1, "SHA-512-256", True] - cnonce = base64.decodebytes( - b'NTg6RKcb9boFIAS3KrFK9BGeh+iDa/sm6jUMp2wds69v') + cnonce = b64('NTg6RKcb9boFIAS3KrFK9BGeh+iDa/sm6jUMp2wds69v') with queued_specific_random_data(cnonce): self.assertEqual(http_digest_response(*params), b'username="488869477bf257147b804c45308cd62ac4e25eb717b12b298c79e62dcea254ec", ' -- cgit v1.2.3 From e7d51505c7b10640ccaf724da4266afe8a079c38 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Tue, 19 Apr 2022 10:53:44 +0100 Subject: Utility function strbuf_dup. If you already have a string (of potentially-binary data) in the form of a ptrlen reference to somewhere else, and you want to keep a copy somewhere, it's useful to copy it into a strbuf. But it takes a couple of lines of faff to do that, and it's nicer to wrap that up into a tiny helper function. This commit adds that helper function strbuf_dup, and its non-movable sibling strbuf_dup_nm for secret data. Also, gone through the existing code and found a bunch of cases where this makes things less verbose. --- misc.h | 4 ++++ pageant.c | 20 ++++++-------------- ssh/login1.c | 3 +-- ssh/userauth2-client.c | 6 ++---- sshpubk.c | 3 +-- test/testcrypt.c | 36 ++++++++++++------------------------ utils/strbuf.c | 14 ++++++++++++++ 7 files changed, 40 insertions(+), 46 deletions(-) diff --git a/misc.h b/misc.h index 7acd8f41..a2dee9ac 100644 --- a/misc.h +++ b/misc.h @@ -52,6 +52,10 @@ struct strbuf { strbuf *strbuf_new(void); strbuf *strbuf_new_nm(void); +/* Helpers to allocate a strbuf containing an existing string */ +strbuf *strbuf_dup(ptrlen string); +strbuf *strbuf_dup_nm(ptrlen string); + void strbuf_free(strbuf *buf); void *strbuf_append(strbuf *buf, size_t len); void strbuf_shrink_to(strbuf *buf, size_t new_len); diff --git a/pageant.c b/pageant.c index f64026d8..e97eacbe 100644 --- a/pageant.c +++ b/pageant.c @@ -518,11 +518,8 @@ void pageant_passphrase_request_success(PageantClientDialogId *dlgid, BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf( pk->encrypted_key_file)); - strbuf *ppsb = strbuf_new_nm(); - put_datapl(ppsb, passphrase); - + strbuf *ppsb = strbuf_dup_nm(passphrase); pk->skey = ppk_load_s(src, ppsb->s, &error); - strbuf_free(ppsb); if (!pk->skey) { @@ -840,8 +837,7 @@ static PageantAsyncOp *pageant_make_op( so->pao.reqid = reqid; so->pk = pk; so->pkr.prev = so->pkr.next = NULL; - so->data_to_sign = strbuf_new(); - put_datapl(so->data_to_sign, sigdata); + so->data_to_sign = strbuf_dup(sigdata); so->flags = flags; so->failure_type = failure_type; so->crLine = 0; @@ -1180,8 +1176,7 @@ static PageantAsyncOp *pageant_make_op( * existing record, if it doesn't have one already. */ if (!pk->encrypted_key_file) { - pk->encrypted_key_file = strbuf_new_nm(); - put_datapl(pk->encrypted_key_file, keyfile); + pk->encrypted_key_file = strbuf_dup_nm(keyfile); keylist_update(); put_byte(sb, SSH_AGENT_SUCCESS); @@ -1205,8 +1200,7 @@ static PageantAsyncOp *pageant_make_op( public_blob = NULL; pk->sort.public_blob = ptrlen_from_strbuf(pk->public_blob); pk->comment = dupstr(comment); - pk->encrypted_key_file = strbuf_new_nm(); - put_datapl(pk->encrypted_key_file, keyfile); + pk->encrypted_key_file = strbuf_dup_nm(keyfile); PageantKey *added = add234(keytree, pk); assert(added == pk); (void)added; @@ -2233,8 +2227,7 @@ int pageant_enum_keys(pageant_key_enum_fn_t callback, void *callback_ctx, if (kl1) { for (size_t i = 0; i < kl1->nkeys; i++) { - cbkey.blob = strbuf_new(); - put_datapl(cbkey.blob, kl1->keys[i].blob); + cbkey.blob = strbuf_dup(kl1->keys[i].blob); cbkey.comment = mkstr(kl1->keys[i].comment); cbkey.ssh_version = 1; @@ -2265,8 +2258,7 @@ int pageant_enum_keys(pageant_key_enum_fn_t callback, void *callback_ctx, if (kl2) { for (size_t i = 0; i < kl2->nkeys; i++) { - cbkey.blob = strbuf_new(); - put_datapl(cbkey.blob, kl2->keys[i].blob); + cbkey.blob = strbuf_dup(kl2->keys[i].blob); cbkey.comment = mkstr(kl2->keys[i].comment); cbkey.ssh_version = 2; diff --git a/ssh/login1.c b/ssh/login1.c index 7b345c78..d556f22a 100644 --- a/ssh/login1.c +++ b/ssh/login1.c @@ -521,8 +521,7 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl) RSA_SSH1_EXPONENT_FIRST); const char *blobend = get_ptr(s->asrc); - s->agent_keys[i].comment = strbuf_new(); - put_datapl(s->agent_keys[i].comment, get_string(s->asrc)); + s->agent_keys[i].comment = strbuf_dup(get_string(s->asrc)); s->agent_keys[i].blob = make_ptrlen( blobstart, blobend - blobstart); diff --git a/ssh/userauth2-client.c b/ssh/userauth2-client.c index ba97502f..3c760537 100644 --- a/ssh/userauth2-client.c +++ b/ssh/userauth2-client.c @@ -348,10 +348,8 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) s->agent_keys_len = nkeys; s->agent_keys = snewn(s->agent_keys_len, agent_key); for (size_t i = 0; i < nkeys; i++) { - s->agent_keys[i].blob = strbuf_new(); - put_datapl(s->agent_keys[i].blob, get_string(s->asrc)); - s->agent_keys[i].comment = strbuf_new(); - put_datapl(s->agent_keys[i].comment, get_string(s->asrc)); + s->agent_keys[i].blob = strbuf_dup(get_string(s->asrc)); + s->agent_keys[i].comment = strbuf_dup(get_string(s->asrc)); /* Also, extract the algorithm string from the start * of the public-key blob. */ diff --git a/sshpubk.c b/sshpubk.c index 326fe31c..70a64524 100644 --- a/sshpubk.c +++ b/sshpubk.c @@ -199,8 +199,7 @@ static int rsa1_load_s_internal(BinarySource *src, RSAKey *key, bool pub_only, if (enclen & 7) goto end; - buf = strbuf_new_nm(); - put_datapl(buf, get_data(src, enclen)); + buf = strbuf_dup_nm(get_data(src, enclen)); unsigned char keybuf[16]; hash_simple(&ssh_md5, ptrlen_from_asciz(passphrase), keybuf); diff --git a/test/testcrypt.c b/test/testcrypt.c index 3157ed54..a271459e 100644 --- a/test/testcrypt.c +++ b/test/testcrypt.c @@ -763,8 +763,7 @@ strbuf *ssh_cipher_encrypt_wrapper(ssh_cipher *c, ptrlen input) if (input.len % ssh_cipher_alg(c)->blksize) fatal_error("ssh_cipher_encrypt: needs a multiple of %d bytes", ssh_cipher_alg(c)->blksize); - strbuf *sb = strbuf_new(); - put_datapl(sb, input); + strbuf *sb = strbuf_dup(input); ssh_cipher_encrypt(c, sb->u, sb->len); return sb; } @@ -774,8 +773,7 @@ strbuf *ssh_cipher_decrypt_wrapper(ssh_cipher *c, ptrlen input) if (input.len % ssh_cipher_alg(c)->blksize) fatal_error("ssh_cipher_decrypt: needs a multiple of %d bytes", ssh_cipher_alg(c)->blksize); - strbuf *sb = strbuf_new(); - put_datapl(sb, input); + strbuf *sb = strbuf_dup(input); ssh_cipher_decrypt(c, sb->u, sb->len); return sb; } @@ -785,8 +783,7 @@ strbuf *ssh_cipher_encrypt_length_wrapper(ssh_cipher *c, ptrlen input, { if (input.len != 4) fatal_error("ssh_cipher_encrypt_length: needs exactly 4 bytes"); - strbuf *sb = strbuf_new(); - put_datapl(sb, input); + strbuf *sb = strbuf_dup(input); ssh_cipher_encrypt_length(c, sb->u, sb->len, seq); return sb; } @@ -796,8 +793,7 @@ strbuf *ssh_cipher_decrypt_length_wrapper(ssh_cipher *c, ptrlen input, { if (input.len % ssh_cipher_alg(c)->blksize) fatal_error("ssh_cipher_decrypt_length: needs exactly 4 bytes"); - strbuf *sb = strbuf_new(); - put_datapl(sb, input); + strbuf *sb = strbuf_dup(input); ssh_cipher_decrypt_length(c, sb->u, sb->len, seq); return sb; } @@ -997,8 +993,7 @@ strbuf *des_encrypt_xdmauth_wrapper(ptrlen key, ptrlen data) fatal_error("des_encrypt_xdmauth: key must be 7 bytes long"); if (data.len % 8 != 0) fatal_error("des_encrypt_xdmauth: data must be a multiple of 8 bytes"); - strbuf *sb = strbuf_new(); - put_datapl(sb, data); + strbuf *sb = strbuf_dup(data); des_encrypt_xdmauth(key.ptr, sb->u, sb->len); return sb; } @@ -1009,8 +1004,7 @@ strbuf *des_decrypt_xdmauth_wrapper(ptrlen key, ptrlen data) fatal_error("des_decrypt_xdmauth: key must be 7 bytes long"); if (data.len % 8 != 0) fatal_error("des_decrypt_xdmauth: data must be a multiple of 8 bytes"); - strbuf *sb = strbuf_new(); - put_datapl(sb, data); + strbuf *sb = strbuf_dup(data); des_decrypt_xdmauth(key.ptr, sb->u, sb->len); return sb; } @@ -1021,8 +1015,7 @@ strbuf *des3_encrypt_pubkey_wrapper(ptrlen key, ptrlen data) fatal_error("des3_encrypt_pubkey: key must be 16 bytes long"); if (data.len % 8 != 0) fatal_error("des3_encrypt_pubkey: data must be a multiple of 8 bytes"); - strbuf *sb = strbuf_new(); - put_datapl(sb, data); + strbuf *sb = strbuf_dup(data); des3_encrypt_pubkey(key.ptr, sb->u, sb->len); return sb; } @@ -1033,8 +1026,7 @@ strbuf *des3_decrypt_pubkey_wrapper(ptrlen key, ptrlen data) fatal_error("des3_decrypt_pubkey: key must be 16 bytes long"); if (data.len % 8 != 0) fatal_error("des3_decrypt_pubkey: data must be a multiple of 8 bytes"); - strbuf *sb = strbuf_new(); - put_datapl(sb, data); + strbuf *sb = strbuf_dup(data); des3_decrypt_pubkey(key.ptr, sb->u, sb->len); return sb; } @@ -1047,8 +1039,7 @@ strbuf *des3_encrypt_pubkey_ossh_wrapper(ptrlen key, ptrlen iv, ptrlen data) fatal_error("des3_encrypt_pubkey_ossh: iv must be 8 bytes long"); if (data.len % 8 != 0) fatal_error("des3_encrypt_pubkey_ossh: data must be a multiple of 8 bytes"); - strbuf *sb = strbuf_new(); - put_datapl(sb, data); + strbuf *sb = strbuf_dup(data); des3_encrypt_pubkey_ossh(key.ptr, iv.ptr, sb->u, sb->len); return sb; } @@ -1061,8 +1052,7 @@ strbuf *des3_decrypt_pubkey_ossh_wrapper(ptrlen key, ptrlen iv, ptrlen data) fatal_error("des3_encrypt_pubkey_ossh: iv must be 8 bytes long"); if (data.len % 8 != 0) fatal_error("des3_decrypt_pubkey_ossh: data must be a multiple of 8 bytes"); - strbuf *sb = strbuf_new(); - put_datapl(sb, data); + strbuf *sb = strbuf_dup(data); des3_decrypt_pubkey_ossh(key.ptr, iv.ptr, sb->u, sb->len); return sb; } @@ -1075,8 +1065,7 @@ strbuf *aes256_encrypt_pubkey_wrapper(ptrlen key, ptrlen iv, ptrlen data) fatal_error("aes256_encrypt_pubkey: iv must be 16 bytes long"); if (data.len % 16 != 0) fatal_error("aes256_encrypt_pubkey: data must be a multiple of 16 bytes"); - strbuf *sb = strbuf_new(); - put_datapl(sb, data); + strbuf *sb = strbuf_dup(data); aes256_encrypt_pubkey(key.ptr, iv.ptr, sb->u, sb->len); return sb; } @@ -1089,8 +1078,7 @@ strbuf *aes256_decrypt_pubkey_wrapper(ptrlen key, ptrlen iv, ptrlen data) fatal_error("aes256_encrypt_pubkey: iv must be 16 bytes long"); if (data.len % 16 != 0) fatal_error("aes256_decrypt_pubkey: data must be a multiple of 16 bytes"); - strbuf *sb = strbuf_new(); - put_datapl(sb, data); + strbuf *sb = strbuf_dup(data); aes256_decrypt_pubkey(key.ptr, iv.ptr, sb->u, sb->len); return sb; } diff --git a/utils/strbuf.c b/utils/strbuf.c index 636467a4..c636de47 100644 --- a/utils/strbuf.c +++ b/utils/strbuf.c @@ -112,3 +112,17 @@ void strbuf_finalise_agent_query(strbuf *buf_o) assert(buf->visible.len >= 5); PUT_32BIT_MSB_FIRST(buf->visible.u, buf->visible.len - 4); } + +strbuf *strbuf_dup(ptrlen string) +{ + strbuf *buf = strbuf_new(); + put_datapl(buf, string); + return buf; +} + +strbuf *strbuf_dup_nm(ptrlen string) +{ + strbuf *buf = strbuf_new_nm(); + put_datapl(buf, string); + return buf; +} -- cgit v1.2.3 From a5c0205b8715e99097fae3b4f754af1d8102e953 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 21 Apr 2022 11:00:16 +0100 Subject: Utility functions to get the algorithm from a public key. Every time I've had to do this before, I've always done the three-line dance of initialising a BinarySource and calling get_string on it. It's long past time I wrapped that up into a convenient subroutine. --- cmdgen.c | 5 ++--- ssh.h | 3 +++ ssh/userauth2-client.c | 6 ++---- sshpubk.c | 12 ++++++++++++ windows/pageant.c | 6 ++---- 5 files changed, 21 insertions(+), 11 deletions(-) diff --git a/cmdgen.c b/cmdgen.c index e0006efc..14af9642 100644 --- a/cmdgen.c +++ b/cmdgen.c @@ -1280,9 +1280,8 @@ int main(int argc, char **argv) } else { assert(ssh2blob); - BinarySource src[1]; - BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(ssh2blob)); - ptrlen algname = get_string(src); + ptrlen algname = pubkey_blob_to_alg_name( + ptrlen_from_strbuf(ssh2blob)); const ssh_keyalg *alg = find_pubkey_alg_len(algname); if (!alg) { fprintf(stderr, "puttygen: cannot extract key components " diff --git a/ssh.h b/ssh.h index 690d55c7..6d808cc1 100644 --- a/ssh.h +++ b/ssh.h @@ -1331,6 +1331,9 @@ extern const size_t n_keyalgs; const ssh_keyalg *find_pubkey_alg(const char *name); const ssh_keyalg *find_pubkey_alg_len(ptrlen name); +ptrlen pubkey_blob_to_alg_name(ptrlen blob); +const ssh_keyalg *pubkey_blob_to_alg(ptrlen blob); + /* Convenient wrappers on the LoadedFile mechanism suitable for key files */ LoadedFile *lf_load_keyfile(const Filename *filename, const char **errptr); LoadedFile *lf_load_keyfile_fp(FILE *fp, const char **errptr); diff --git a/ssh/userauth2-client.c b/ssh/userauth2-client.c index 3c760537..90421a8d 100644 --- a/ssh/userauth2-client.c +++ b/ssh/userauth2-client.c @@ -353,10 +353,8 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) /* Also, extract the algorithm string from the start * of the public-key blob. */ - BinarySource src[1]; - BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf( - s->agent_keys[i].blob)); - s->agent_keys[i].algorithm = get_string(src); + s->agent_keys[i].algorithm = pubkey_blob_to_alg_name( + ptrlen_from_strbuf(s->agent_keys[i].blob)); } ppl_logevent("Pageant has %"SIZEu" SSH-2 keys", nkeys); diff --git a/sshpubk.c b/sshpubk.c index 70a64524..42eaa1f1 100644 --- a/sshpubk.c +++ b/sshpubk.c @@ -585,6 +585,18 @@ const ssh_keyalg *find_pubkey_alg(const char *name) return find_pubkey_alg_len(ptrlen_from_asciz(name)); } +ptrlen pubkey_blob_to_alg_name(ptrlen blob) +{ + BinarySource src[1]; + BinarySource_BARE_INIT_PL(src, blob); + return get_string(src); +} + +const ssh_keyalg *pubkey_blob_to_alg(ptrlen blob) +{ + return find_pubkey_alg_len(pubkey_blob_to_alg_name(blob)); +} + struct ppk_cipher { const char *name; size_t blocklen, keylen, ivlen; diff --git a/windows/pageant.c b/windows/pageant.c index 0d082357..fd266b0b 100644 --- a/windows/pageant.c +++ b/windows/pageant.c @@ -368,10 +368,8 @@ static void keylist_update_callback( * overflow past the bit-count tab stop and leave out a tab * character. Urgh. */ - BinarySource src[1]; - BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(key->blob)); - ptrlen algname = get_string(src); - const ssh_keyalg *alg = find_pubkey_alg_len(algname); + const ssh_keyalg *alg = pubkey_blob_to_alg( + ptrlen_from_strbuf(key->blob)); bool include_bit_count = (alg == &ssh_dsa || alg == &ssh_rsa); -- cgit v1.2.3 From ffa25be185be5f27e8cebc3b7a5902a760671af6 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Wed, 20 Apr 2022 07:45:17 +0100 Subject: Fix error messages in ppk_loadpub_s. The function will accept a public key file or a PPK, but if it fails to parse as any of those, the error message says "not a PuTTY SSH-2 private key", which is particularly incongruous in situations where you're specifically _not_ after the private half of the key. Now says "not a public key or a PuTTY SSH-2 private key". --- sshpubk.c | 4 ++-- test/cryptsuite.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/sshpubk.c b/sshpubk.c index 42eaa1f1..4b332778 100644 --- a/sshpubk.c +++ b/sshpubk.c @@ -1237,7 +1237,7 @@ bool ppk_loadpub_s(BinarySource *src, char **algorithm, BinarySink *bs, bool ret = openssh_loadpub(src, algorithm, bs, commentptr, errorstr); return ret; } else if (type != SSH_KEYTYPE_SSH2) { - error = "not a PuTTY SSH-2 private key"; + error = "not a public key or a PuTTY SSH-2 private key"; goto error; } @@ -1249,7 +1249,7 @@ bool ppk_loadpub_s(BinarySource *src, char **algorithm, BinarySink *bs, if (0 == strncmp(header, "PuTTY-User-Key-File-", 20)) error = "PuTTY key format too new"; else - error = "not a PuTTY SSH-2 private key"; + error = "not a public key or a PuTTY SSH-2 private key"; goto error; } error = "file format error"; diff --git a/test/cryptsuite.py b/test/cryptsuite.py index 534b1aab..5ab7bf96 100755 --- a/test/cryptsuite.py +++ b/test/cryptsuite.py @@ -2418,7 +2418,7 @@ Private-MAC: 6f5e588e475e55434106ec2c3569695b03f423228b44993a9e97d52ffe7be5a8 (True, algorithm, public_blob, comment, None)) self.assertEqual(ppk_loadpub_s("not a key file"), (False, None, b'', None, - b'not a PuTTY SSH-2 private key')) + b'not a public key or a PuTTY SSH-2 private key')) k1, c, e = ppk_load_s(input_clear_key, None) self.assertEqual((c, e), (comment, None)) @@ -2480,7 +2480,7 @@ Private-MAC: 5b1f6f4cc43eb0060d2c3e181bc0129343adba2b (True, algorithm, public_blob, comment, None)) self.assertEqual(ppk_loadpub_s("not a key file"), (False, None, b'', None, - b'not a PuTTY SSH-2 private key')) + b'not a public key or a PuTTY SSH-2 private key')) k1, c, e = ppk_load_s(v2_clear_key, None) self.assertEqual((c, e), (comment, None)) -- cgit v1.2.3 From 6143a50ed228fdf5a72c2970629b4ff643d001fc Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 22 Apr 2022 10:01:01 +0100 Subject: windows/storage.c: factor out low-level Registry access. All the fiddly business where you have to check that a thing exists, make sure of its type, find its size, allocate some memory, and then read it again properly (or, alternatively, loop round dealing with ERROR_MORE_DATA) just doesn't belong at every call site. It's crying out to be moved out into some separate utility functions that present a more ergonomic API, so that the code that decides _which_ Registry entries to read and what to do with them can concentrate on that. So I've written a fresh set of registry API wrappers in windows/utils, and simplified windows/storage.c as a result. The jump-list handling code in particular is almost legible now! --- windows/CMakeLists.txt | 2 +- windows/help.c | 2 +- windows/platform.h | 15 +- windows/storage.c | 386 +++++++++++------------------------- windows/utils/registry.c | 184 +++++++++++++++++ windows/utils/registry_get_string.c | 43 ---- 6 files changed, 315 insertions(+), 317 deletions(-) create mode 100644 windows/utils/registry.c delete mode 100644 windows/utils/registry_get_string.c diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt index 79a09eda..9a6a4ec6 100644 --- a/windows/CMakeLists.txt +++ b/windows/CMakeLists.txt @@ -26,7 +26,7 @@ add_sources_from_current_dir(utils utils/open_for_write_would_lose_data.c utils/pgp_fingerprints_msgbox.c utils/platform_get_x_display.c - utils/registry_get_string.c + utils/registry.c utils/request_file.c utils/screenshot.c utils/security.c diff --git a/windows/help.c b/windows/help.c index bff2ee5c..d087c722 100644 --- a/windows/help.c +++ b/windows/help.c @@ -149,7 +149,7 @@ static bool find_chm_from_installation(void) }; for (size_t i = 0; i < lenof(reg_paths); i++) { - char *filename = registry_get_string( + char *filename = get_reg_sz_simple( HKEY_LOCAL_MACHINE, reg_paths[i], NULL); if (filename) { diff --git a/windows/platform.h b/windows/platform.h index 07a82f81..5760c7c6 100644 --- a/windows/platform.h +++ b/windows/platform.h @@ -716,7 +716,20 @@ char *get_jumplist_registry_entries(void); #define CLIPUI_DEFAULT_INS CLIPUI_EXPLICIT /* In utils */ -char *registry_get_string(HKEY root, const char *path, const char *leaf); +HKEY open_regkey_fn(bool create, HKEY base, const char *path, ...); +#define open_regkey(create, base, ...) \ + open_regkey_fn(create, base, __VA_ARGS__, (const char *)NULL) +void close_regkey(HKEY key); +void del_regkey(HKEY key, const char *name); +char *enum_regkey(HKEY key, int index); +bool get_reg_dword(HKEY key, const char *name, DWORD *out); +bool put_reg_dword(HKEY key, const char *name, DWORD value); +char *get_reg_sz(HKEY key, const char *name); +bool put_reg_sz(HKEY key, const char *name, const char *str); +strbuf *get_reg_multi_sz(HKEY key, const char *name); +bool put_reg_multi_sz(HKEY key, const char *name, strbuf *str); + +char *get_reg_sz_simple(HKEY key, const char *name, const char *leaf); /* In cliloop.c */ typedef bool (*cliloop_pre_t)(void *vctx, const HANDLE **extra_handles, diff --git a/windows/storage.c b/windows/storage.c index 077f0890..bf46cce0 100644 --- a/windows/storage.c +++ b/windows/storage.c @@ -33,28 +33,16 @@ struct settings_w { settings_w *open_settings_w(const char *sessionname, char **errmsg) { - HKEY subkey1, sesskey; - int ret; - strbuf *sb; - *errmsg = NULL; if (!sessionname || !*sessionname) sessionname = "Default Settings"; - sb = strbuf_new(); + strbuf *sb = strbuf_new(); escape_registry_key(sessionname, sb); - ret = RegCreateKey(HKEY_CURRENT_USER, puttystr, &subkey1); - if (ret != ERROR_SUCCESS) { - strbuf_free(sb); - *errmsg = dupprintf("Unable to create registry key\n" - "HKEY_CURRENT_USER\\%s", puttystr); - return NULL; - } - ret = RegCreateKey(subkey1, sb->s, &sesskey); - RegCloseKey(subkey1); - if (ret != ERROR_SUCCESS) { + HKEY sesskey = open_regkey(true, HKEY_CURRENT_USER, puttystr, sb->s); + if (!sesskey) { *errmsg = dupprintf("Unable to create registry key\n" "HKEY_CURRENT_USER\\%s\\%s", puttystr, sb->s); strbuf_free(sb); @@ -70,20 +58,18 @@ settings_w *open_settings_w(const char *sessionname, char **errmsg) void write_setting_s(settings_w *handle, const char *key, const char *value) { if (handle) - RegSetValueEx(handle->sesskey, key, 0, REG_SZ, (CONST BYTE *)value, - 1 + strlen(value)); + put_reg_sz(handle->sesskey, key, value); } void write_setting_i(settings_w *handle, const char *key, int value) { if (handle) - RegSetValueEx(handle->sesskey, key, 0, REG_DWORD, - (CONST BYTE *) &value, sizeof(value)); + put_reg_dword(handle->sesskey, key, value); } void close_settings_w(settings_w *handle) { - RegCloseKey(handle->sesskey); + close_regkey(handle->sesskey); sfree(handle); } @@ -93,24 +79,12 @@ struct settings_r { settings_r *open_settings_r(const char *sessionname) { - HKEY subkey1, sesskey; - strbuf *sb; - if (!sessionname || !*sessionname) sessionname = "Default Settings"; - sb = strbuf_new(); + strbuf *sb = strbuf_new(); escape_registry_key(sessionname, sb); - - if (RegOpenKey(HKEY_CURRENT_USER, puttystr, &subkey1) != ERROR_SUCCESS) { - sesskey = NULL; - } else { - if (RegOpenKey(subkey1, sb->s, &sesskey) != ERROR_SUCCESS) { - sesskey = NULL; - } - RegCloseKey(subkey1); - } - + HKEY sesskey = open_regkey(false, HKEY_CURRENT_USER, puttystr, sb->s); strbuf_free(sb); if (!sesskey) @@ -123,42 +97,15 @@ settings_r *open_settings_r(const char *sessionname) char *read_setting_s(settings_r *handle, const char *key) { - DWORD type, allocsize, size; - char *ret; - if (!handle) return NULL; - - /* Find out the type and size of the data. */ - if (RegQueryValueEx(handle->sesskey, key, 0, - &type, NULL, &size) != ERROR_SUCCESS || - type != REG_SZ) - return NULL; - - allocsize = size+1; /* allow for an extra NUL if needed */ - ret = snewn(allocsize, char); - if (RegQueryValueEx(handle->sesskey, key, 0, - &type, (BYTE *)ret, &size) != ERROR_SUCCESS || - type != REG_SZ) { - sfree(ret); - return NULL; - } - assert(size < allocsize); - ret[size] = '\0'; /* add an extra NUL in case RegQueryValueEx - * didn't supply one */ - - return ret; + return get_reg_sz(handle->sesskey, key); } int read_setting_i(settings_r *handle, const char *key, int defvalue) { - DWORD type, val, size; - size = sizeof(val); - - if (!handle || - RegQueryValueEx(handle->sesskey, key, 0, &type, - (BYTE *) &val, &size) != ERROR_SUCCESS || - size != sizeof(val) || type != REG_DWORD) + DWORD val; + if (!handle || !get_reg_dword(handle->sesskey, key, &val)) return defvalue; else return val; @@ -241,25 +188,23 @@ void write_setting_filename(settings_w *handle, void close_settings_r(settings_r *handle) { if (handle) { - RegCloseKey(handle->sesskey); + close_regkey(handle->sesskey); sfree(handle); } } void del_settings(const char *sessionname) { - HKEY subkey1; - strbuf *sb; - - if (RegOpenKey(HKEY_CURRENT_USER, puttystr, &subkey1) != ERROR_SUCCESS) + HKEY rkey = open_regkey(false, HKEY_CURRENT_USER, puttystr); + if (!rkey) return; - sb = strbuf_new(); + strbuf *sb = strbuf_new(); escape_registry_key(sessionname, sb); - RegDeleteKey(subkey1, sb->s); + del_regkey(rkey, sb->s); strbuf_free(sb); - RegCloseKey(subkey1); + close_regkey(rkey); remove_session_from_jumplist(sessionname); } @@ -271,13 +216,11 @@ struct settings_e { settings_e *enum_settings_start(void) { - settings_e *ret; - HKEY key; - - if (RegOpenKey(HKEY_CURRENT_USER, puttystr, &key) != ERROR_SUCCESS) + HKEY key = open_regkey(false, HKEY_CURRENT_USER, puttystr); + if (!key) return NULL; - ret = snew(settings_e); + settings_e *ret = snew(settings_e); if (ret) { ret->key = key; ret->i = 0; @@ -288,30 +231,19 @@ settings_e *enum_settings_start(void) bool enum_settings_next(settings_e *e, strbuf *sb) { - size_t regbuf_size = MAX_PATH + 1; - char *regbuf = snewn(regbuf_size, char); - bool success; - - while (1) { - DWORD retd = RegEnumKey(e->key, e->i, regbuf, regbuf_size); - if (retd != ERROR_MORE_DATA) { - success = (retd == ERROR_SUCCESS); - break; - } - sgrowarray(regbuf, regbuf_size, regbuf_size); - } - - if (success) - unescape_registry_key(regbuf, sb); + char *name = enum_regkey(e->key, e->i); + if (!name) + return false; + unescape_registry_key(name, sb); + sfree(name); e->i++; - sfree(regbuf); - return success; + return true; } void enum_settings_finish(settings_e *e) { - RegCloseKey(e->key); + close_regkey(e->key); sfree(e); } @@ -325,48 +257,30 @@ static void hostkey_regname(strbuf *sb, const char *hostname, int check_stored_host_key(const char *hostname, int port, const char *keytype, const char *key) { - char *otherstr; - strbuf *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. + * Read a saved key in from the registry and see what it says. */ - regname = strbuf_new(); + strbuf *regname = strbuf_new(); hostkey_regname(regname, hostname, port, keytype); - if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_POS "\\SshHostKeys", - &rkey) != ERROR_SUCCESS) { + HKEY rkey = open_regkey(false, HKEY_CURRENT_USER, + PUTTY_REG_POS "\\SshHostKeys"); + if (!rkey) { strbuf_free(regname); return 1; /* key does not exist in registry */ } - readlen = len; - otherstr = snewn(len, char); - ret = RegQueryValueEx(rkey, regname->s, NULL, - &type, (BYTE *)otherstr, &readlen); - - if (ret != ERROR_SUCCESS && ret != ERROR_MORE_DATA && - !strcmp(keytype, "rsa")) { + char *otherstr = get_reg_sz(rkey, regname->s); + if (!otherstr && !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->s + 1 + strcspn(regname->s, ":"); - char *oldstyle = snewn(len + 10, char); /* safety margin */ - readlen = len; - ret = RegQueryValueEx(rkey, justhost, NULL, &type, - (BYTE *)oldstyle, &readlen); + char *oldstyle = get_reg_sz(rkey, justhost); - if (ret == ERROR_SUCCESS && type == REG_SZ) { + if (oldstyle) { /* * The old format is two old-style bignums separated by * a slash. An old-style bignum is made of groups of @@ -410,25 +324,23 @@ int check_stored_host_key(const char *hostname, int port, * wrong, and hyper-cautiously do nothing. */ if (!strcmp(otherstr, key)) - RegSetValueEx(rkey, regname->s, 0, REG_SZ, (BYTE *)otherstr, - strlen(otherstr) + 1); + put_reg_sz(rkey, regname->s, otherstr); } sfree(oldstyle); } - RegCloseKey(rkey); + close_regkey(rkey); - compare = strcmp(otherstr, key); + int compare = otherstr ? strcmp(otherstr, key) : -1; sfree(otherstr); strbuf_free(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) + if (!otherstr) return 1; /* key does not exist in registry */ + else if (compare) + return 2; /* key is different in registry */ else return 0; /* key matched OK in registry */ } @@ -446,17 +358,14 @@ bool have_ssh_host_key(const char *hostname, int port, void store_host_key(const char *hostname, int port, const char *keytype, const char *key) { - strbuf *regname; - HKEY rkey; - - regname = strbuf_new(); + strbuf *regname = strbuf_new(); hostkey_regname(regname, hostname, port, keytype); - if (RegCreateKey(HKEY_CURRENT_USER, PUTTY_REG_POS "\\SshHostKeys", - &rkey) == ERROR_SUCCESS) { - RegSetValueEx(rkey, regname->s, 0, REG_SZ, - (BYTE *)key, strlen(key) + 1); - RegCloseKey(rkey); + HKEY rkey = open_regkey(true, HKEY_CURRENT_USER, + PUTTY_REG_POS "\\SshHostKeys"); + if (rkey) { + put_reg_sz(rkey, regname->s, key); + close_regkey(rkey); } /* else key does not exist in registry */ strbuf_free(regname); @@ -498,7 +407,6 @@ static bool try_random_seed_and_free(char *path, int action, HANDLE *hout) static HANDLE access_random_seed(int action) { - HKEY rkey; HANDLE rethandle; /* @@ -517,16 +425,16 @@ static HANDLE access_random_seed(int action) * Registry, if any. */ { - char regpath[MAX_PATH + 1]; - DWORD type, size = sizeof(regpath); - if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_POS, &rkey) == - ERROR_SUCCESS) { - int ret = RegQueryValueEx(rkey, "RandSeedFile", - 0, &type, (BYTE *)regpath, &size); - RegCloseKey(rkey); - if (ret == ERROR_SUCCESS && type == REG_SZ && - try_random_seed(regpath, action, &rethandle)) - return rethandle; + HKEY rkey = open_regkey(false, HKEY_CURRENT_USER, PUTTY_REG_POS); + if (rkey) { + char *regpath = get_reg_sz(rkey, "RandSeedFile"); + close_regkey(rkey); + if (regpath) { + bool success = try_random_seed(regpath, action, &rethandle); + sfree(regpath); + if (success) + return rethandle; + } } } @@ -644,129 +552,66 @@ void write_random_seed(void *data, int len) static int transform_jumplist_registry (const char *add, const char *rem, char **out) { - int ret; - HKEY pjumplist_key; - DWORD type; - DWORD 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) { + HKEY rkey = open_regkey(true, HKEY_CURRENT_USER, reg_jumplist_key); + if (!rkey) 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, - (BYTE *)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, - (BYTE *)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'; + strbuf *oldlist = get_reg_multi_sz(rkey, reg_jumplist_value); + if (!oldlist) { + /* Start again with the empty list. */ + oldlist = strbuf_new(); + put_data(oldlist, "\0\0", 2); } /* * Modify the list, if we're modifying. */ + bool write_failure = false; 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; + BinarySource src[1]; + BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(oldlist)); + strbuf *newlist = strbuf_new(); /* First add the new item to the beginning of the list. */ - if (add) { - strcpy(piterator_new, add); - piterator_new += strlen(piterator_new) + 1; - } + if (add) + put_asciz(newlist, add); + /* 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) { + while (true) { + const char *olditem = get_asciz(src); + if (get_err(src)) + break; + + if (!rem || strcmp(olditem, rem) != 0) { /* Check if this is a valid session, otherwise don't add. */ - settings_r *psettings_tmp = open_settings_r(piterator_old); + settings_r *psettings_tmp = open_settings_r(olditem); if (psettings_tmp != NULL) { close_settings_r(psettings_tmp); - strcpy(piterator_new, piterator_old); - piterator_new += strlen(piterator_new) + 1; + put_asciz(newlist, olditem); } } - 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, - (BYTE *)new_value, piterator_new - new_value); + write_failure = !put_reg_multi_sz(rkey, reg_jumplist_value, newlist); - sfree(old_value); - old_value = new_value; - } else - ret = ERROR_SUCCESS; + strbuf_free(oldlist); + oldlist = newlist; + } - /* - * Either return or free the result. - */ - if (out && ret == ERROR_SUCCESS) - *out = old_value; - else - sfree(old_value); + close_regkey(rkey); - /* Clean up and return. */ - RegCloseKey(pjumplist_key); + if (out && !write_failure) + *out = strbuf_to_str(oldlist); + else + strbuf_free(oldlist); - if (ret != ERROR_SUCCESS) { + if (write_failure) return JUMPLISTREG_ERROR_VALUEWRITE_FAILURE; - } else { + else return JUMPLISTREG_OK; - } } /* Adds a new entry to the jumplist entries in the registry. */ @@ -800,26 +645,22 @@ char *get_jumplist_registry_entries (void) */ static void registry_recursive_remove(HKEY key) { - DWORD i; - char name[MAX_PATH + 1]; - HKEY subkey; + char *name; - i = 0; - while (RegEnumKey(key, i, name, sizeof(name)) == ERROR_SUCCESS) { - if (RegOpenKey(key, name, &subkey) == ERROR_SUCCESS) { + DWORD i = 0; + while ((name = enum_regkey(key, i)) != NULL) { + HKEY subkey = open_regkey(false, key, name); + if (subkey) { registry_recursive_remove(subkey); - RegCloseKey(subkey); + close_regkey(subkey); } - RegDeleteKey(key, name); + del_regkey(key, name); + sfree(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. @@ -839,31 +680,34 @@ void cleanup_all(void) /* * Open the main PuTTY registry key and remove everything in it. */ - if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_POS, &key) == - ERROR_SUCCESS) { + HKEY key = open_regkey(false, HKEY_CURRENT_USER, PUTTY_REG_POS); + if (key) { registry_recursive_remove(key); - RegCloseKey(key); + close_regkey(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 ((key = open_regkey(false, HKEY_CURRENT_USER, + PUTTY_REG_PARENT)) != NULL) { + del_regkey(key, PUTTY_REG_PARENT_CHILD); + char *name = enum_regkey(key, 0); + close_regkey(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); + if (name) { + sfree(name); + } else { + if ((key = open_regkey(false, HKEY_CURRENT_USER, + PUTTY_REG_GPARENT)) != NULL) { + del_regkey(key, PUTTY_REG_GPARENT_CHILD); + close_regkey(key); } } } diff --git a/windows/utils/registry.c b/windows/utils/registry.c new file mode 100644 index 00000000..1f50e67a --- /dev/null +++ b/windows/utils/registry.c @@ -0,0 +1,184 @@ +/* + * Implement convenience wrappers on the awkward low-level functions + * for accessing the Windows registry. + */ + +#include "putty.h" + +HKEY open_regkey_fn(bool create, HKEY hk, const char *path, ...) +{ + HKEY toret = NULL; + bool hk_needs_close = false; + va_list ap; + va_start(ap, path); + + for (; path; path = va_arg(ap, const char *)) { + HKEY hk_sub = NULL; + + LONG status; + if (create) + status = RegCreateKeyEx( + hk, path, 0, NULL, REG_OPTION_NON_VOLATILE, + KEY_READ | KEY_WRITE, NULL, &hk_sub, NULL); + else + status = RegOpenKeyEx( + hk, path, 0, KEY_READ | KEY_WRITE, &hk_sub); + + if (status != ERROR_SUCCESS) + goto out; + + if (hk_needs_close) + RegCloseKey(hk); + hk = hk_sub; + hk_needs_close = true; + } + + toret = hk; + hk = NULL; + hk_needs_close = false; + + out: + va_end(ap); + if (hk_needs_close) + RegCloseKey(hk); + return toret; +} + +void close_regkey(HKEY key) +{ + RegCloseKey(key); +} + +void del_regkey(HKEY key, const char *name) +{ + RegDeleteKey(key, name); +} + +char *enum_regkey(HKEY key, int index) +{ + size_t regbuf_size = MAX_PATH + 1; + char *regbuf = snewn(regbuf_size, char); + + while (1) { + LONG status = RegEnumKey(key, index, regbuf, regbuf_size); + if (status == ERROR_SUCCESS) + return regbuf; + if (status != ERROR_MORE_DATA) { + sfree(regbuf); + return NULL; + } + sgrowarray(regbuf, regbuf_size, regbuf_size); + } +} + +bool get_reg_dword(HKEY key, const char *name, DWORD *out) +{ + DWORD type, size; + size = sizeof(*out); + + if (RegQueryValueEx(key, name, 0, &type, + (BYTE *)out, &size) != ERROR_SUCCESS || + size != sizeof(*out) || type != REG_DWORD) + return false; + else + return true; +} + +bool put_reg_dword(HKEY key, const char *name, DWORD value) +{ + return RegSetValueEx(key, name, 0, REG_DWORD, (CONST BYTE *) &value, + sizeof(value)) == ERROR_SUCCESS; +} + +char *get_reg_sz(HKEY key, const char *name) +{ + DWORD type, size; + + if (RegQueryValueEx(key, name, 0, &type, NULL, + &size) != ERROR_SUCCESS || type != REG_SZ) + return NULL; /* not a string */ + + size_t allocsize = size+1; /* allow for an extra NUL if needed */ + char *toret = snewn(allocsize, char); + if (RegQueryValueEx(key, name, 0, &type, (BYTE *)toret, + &size) != ERROR_SUCCESS || type != REG_SZ) { + sfree(toret); + return NULL; + } + assert(size < allocsize); + toret[size] = '\0'; /* add an extra NUL in case RegQueryValueEx + * didn't supply one */ + + return toret; +} + +bool put_reg_sz(HKEY key, const char *name, const char *str) +{ + /* You have to store the trailing NUL as well */ + return RegSetValueEx(key, name, 0, REG_SZ, (CONST BYTE *)str, + 1 + strlen(str)) == ERROR_SUCCESS; +} + +/* + * REG_MULTI_SZ items are stored as a concatenation of NUL-terminated + * strings, terminated in turn with an empty string, i.e. a second + * consecutive NUL. + * + * We represent these in their storage format, as a strbuf - but + * *without* the second consecutive NUL. + * + * So you can build up a new MULTI_SZ value in a strbuf by calling + * put_asciz once per output string and then put_reg_multi_sz; and you + * can consume one by initialising a BinarySource to the result of + * get_reg_multi_sz, and then calling get_asciz on it and assuming + * that !get_err(src) means you have a real output string. + * + * Also, calling strbuf_to_str on one of these will give you back a + * bare 'char *' with the same double-NUL termination, to pass back to + * a caller. + */ +strbuf *get_reg_multi_sz(HKEY key, const char *name) +{ + DWORD type, size; + + if (RegQueryValueEx(key, name, 0, &type, NULL, + &size) != ERROR_SUCCESS || type != REG_MULTI_SZ) + return NULL; /* not a string */ + + strbuf *toret = strbuf_new(); + void *ptr = strbuf_append(toret, (size_t)size + 2); + if (RegQueryValueEx(key, name, 0, &type, (BYTE *)ptr, + &size) != ERROR_SUCCESS || type != REG_MULTI_SZ) { + strbuf_free(toret); + return NULL; + } + strbuf_shrink_to(toret, size); + /* Ensure we end with exactly one \0 */ + while (strbuf_chomp(toret, '\0')); + put_byte(toret, '\0'); + return toret; +} + +bool put_reg_multi_sz(HKEY key, const char *name, strbuf *str) +{ + /* + * Of course, to write our string list into the registry, we _do_ + * have to include both trailing NULs. But this is easy, because a + * strbuf is also designed to hold a single string and make it + * conveniently accessible in NUL-terminated form, so it stores a + * NUL in its buffer just beyond its formal length. So we just + * include that extra byte in the data we write. + */ + return RegSetValueEx(key, name, 0, REG_MULTI_SZ, (CONST BYTE *)str->s, + str->len + 1) == ERROR_SUCCESS; +} + +char *get_reg_sz_simple(HKEY key, const char *name, const char *leaf) +{ + HKEY subkey = open_regkey(false, key, name); + if (!subkey) + return NULL; + char *toret = get_reg_sz(subkey, leaf); + RegCloseKey(subkey); + return toret; +} diff --git a/windows/utils/registry_get_string.c b/windows/utils/registry_get_string.c deleted file mode 100644 index c3745b92..00000000 --- a/windows/utils/registry_get_string.c +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Self-contained function to try to fetch a single string value from - * the Registry, and return it as a dynamically allocated C string. - */ - -#include "putty.h" - -char *registry_get_string(HKEY root, const char *path, const char *leaf) -{ - HKEY key = root; - bool need_close_key = false; - char *toret = NULL, *str = NULL; - - if (path) { - if (RegCreateKey(key, path, &key) != ERROR_SUCCESS) - goto out; - need_close_key = true; - } - - DWORD type, size; - if (RegQueryValueEx(key, leaf, 0, &type, NULL, &size) != ERROR_SUCCESS) - goto out; - if (type != REG_SZ) - goto out; - - str = snewn(size + 1, char); - DWORD size_got = size; - if (RegQueryValueEx(key, leaf, 0, &type, (LPBYTE)str, - &size_got) != ERROR_SUCCESS) - goto out; - if (type != REG_SZ || size_got > size) - goto out; - str[size_got] = '\0'; - - toret = str; - str = NULL; - - out: - if (need_close_key) - RegCloseKey(key); - sfree(str); - return toret; -} -- cgit v1.2.3 From f9775a7b676c8878239eafe8c88cc6b9240eda86 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Tue, 19 Apr 2022 17:05:36 +0100 Subject: Make ssh_keyalg's supported_flags a method. It's a class method rather than an object method, so it doesn't allow keys with the same algorithm to make different choices about what flags they support. But that's not what I wanted it for: the real purpose is to allow one key algorithm to delegate supported_flags to another, by having its method implementation call the one from the delegate class. (If only C's compile/link model permitted me to initialise a field of one global const struct variable to be a copy of that of another, I wouldn't need the runtime overhead of this method! But object file formats don't let you even specify that.) Most key algorithms support no flags at all, so they all want to use the same implementation of this method. So I've started a file of stubs utils/nullkey.c to contain the common stub version. --- crypto/dsa.c | 1 + crypto/ecc-ssh.c | 5 +++++ crypto/rsa.c | 11 ++++++++--- pageant.c | 2 +- ssh.h | 9 ++++++++- utils/CMakeLists.txt | 1 + utils/nullkey.c | 6 ++++++ 7 files changed, 30 insertions(+), 5 deletions(-) create mode 100644 utils/nullkey.c diff --git a/crypto/dsa.c b/crypto/dsa.c index 43b51c8c..db419141 100644 --- a/crypto/dsa.c +++ b/crypto/dsa.c @@ -498,6 +498,7 @@ const ssh_keyalg ssh_dsa = { .cache_str = dsa_cache_str, .components = dsa_components, .pubkey_bits = dsa_pubkey_bits, + .supported_flags = nullkey_supported_flags, .ssh_id = "ssh-dss", .cache_id = "dss", }; diff --git a/crypto/ecc-ssh.c b/crypto/ecc-ssh.c index e57e8bb1..529ef5a3 100644 --- a/crypto/ecc-ssh.c +++ b/crypto/ecc-ssh.c @@ -1257,6 +1257,7 @@ const ssh_keyalg ssh_ecdsa_ed25519 = { .cache_str = eddsa_cache_str, .components = eddsa_components, .pubkey_bits = ec_shared_pubkey_bits, + .supported_flags = nullkey_supported_flags, .ssh_id = "ssh-ed25519", .cache_id = "ssh-ed25519", .extra = &sign_extra_ed25519, @@ -1280,6 +1281,7 @@ const ssh_keyalg ssh_ecdsa_ed448 = { .cache_str = eddsa_cache_str, .components = eddsa_components, .pubkey_bits = ec_shared_pubkey_bits, + .supported_flags = nullkey_supported_flags, .ssh_id = "ssh-ed448", .cache_id = "ssh-ed448", .extra = &sign_extra_ed448, @@ -1307,6 +1309,7 @@ const ssh_keyalg ssh_ecdsa_nistp256 = { .cache_str = ecdsa_cache_str, .components = ecdsa_components, .pubkey_bits = ec_shared_pubkey_bits, + .supported_flags = nullkey_supported_flags, .ssh_id = "ecdsa-sha2-nistp256", .cache_id = "ecdsa-sha2-nistp256", .extra = &sign_extra_nistp256, @@ -1334,6 +1337,7 @@ const ssh_keyalg ssh_ecdsa_nistp384 = { .cache_str = ecdsa_cache_str, .components = ecdsa_components, .pubkey_bits = ec_shared_pubkey_bits, + .supported_flags = nullkey_supported_flags, .ssh_id = "ecdsa-sha2-nistp384", .cache_id = "ecdsa-sha2-nistp384", .extra = &sign_extra_nistp384, @@ -1361,6 +1365,7 @@ const ssh_keyalg ssh_ecdsa_nistp521 = { .cache_str = ecdsa_cache_str, .components = ecdsa_components, .pubkey_bits = ec_shared_pubkey_bits, + .supported_flags = nullkey_supported_flags, .ssh_id = "ecdsa-sha2-nistp521", .cache_id = "ecdsa-sha2-nistp521", .extra = &sign_extra_nistp521, diff --git a/crypto/rsa.c b/crypto/rsa.c index 1afa766d..b87f4bd6 100644 --- a/crypto/rsa.c +++ b/crypto/rsa.c @@ -839,6 +839,11 @@ static char *rsa2_invalid(ssh_key *key, unsigned flags) return NULL; } +static unsigned ssh_rsa_supported_flags(const ssh_keyalg *self) +{ + return SSH_AGENT_RSA_SHA2_256 | SSH_AGENT_RSA_SHA2_512; +} + static const struct ssh2_rsa_extra rsa_extra = { 0 }, rsa_sha256_extra = { SSH_AGENT_RSA_SHA2_256 }, @@ -863,21 +868,21 @@ static const struct ssh2_rsa_extra const ssh_keyalg ssh_rsa = { COMMON_KEYALG_FIELDS, .ssh_id = "ssh-rsa", - .supported_flags = SSH_AGENT_RSA_SHA2_256 | SSH_AGENT_RSA_SHA2_512, + .supported_flags = ssh_rsa_supported_flags, .extra = &rsa_extra, }; const ssh_keyalg ssh_rsa_sha256 = { COMMON_KEYALG_FIELDS, .ssh_id = "rsa-sha2-256", - .supported_flags = 0, + .supported_flags = nullkey_supported_flags, .extra = &rsa_sha256_extra, }; const ssh_keyalg ssh_rsa_sha512 = { COMMON_KEYALG_FIELDS, .ssh_id = "rsa-sha2-512", - .supported_flags = 0, + .supported_flags = nullkey_supported_flags, .extra = &rsa_sha512_extra, }; diff --git a/pageant.c b/pageant.c index e97eacbe..27d6ee91 100644 --- a/pageant.c +++ b/pageant.c @@ -432,7 +432,7 @@ static void signop_coroutine(PageantAsyncOp *pao) signop_unlink(so); } - uint32_t supported_flags = ssh_key_alg(so->pk->skey->key)->supported_flags; + uint32_t supported_flags = ssh_key_supported_flags(so->pk->skey->key); if (so->flags & ~supported_flags) { /* * We MUST reject any message containing flags we don't diff --git a/ssh.h b/ssh.h index 6d808cc1..68a91256 100644 --- a/ssh.h +++ b/ssh.h @@ -834,12 +834,12 @@ struct ssh_keyalg { /* 'Class methods' that don't deal with an ssh_key at all */ int (*pubkey_bits) (const ssh_keyalg *self, ptrlen blob); + unsigned (*supported_flags) (const ssh_keyalg *self); /* Constant data fields giving information about the key type */ const char *ssh_id; /* string identifier in the SSH protocol */ const char *cache_id; /* identifier used in PuTTY's host key cache */ const void *extra; /* private to the public key methods */ - const unsigned supported_flags; /* signature-type flags we understand */ }; static inline ssh_key *ssh_key_new_pub(const ssh_keyalg *self, ptrlen pub) @@ -877,6 +877,13 @@ static inline const char *ssh_key_ssh_id(ssh_key *key) { return key->vt->ssh_id; } static inline const char *ssh_key_cache_id(ssh_key *key) { return key->vt->cache_id; } +static inline const unsigned ssh_key_supported_flags(ssh_key *key) +{ return key->vt->supported_flags(key->vt); } +static inline const unsigned ssh_keyalg_supported_flags(const ssh_keyalg *self) +{ return self->supported_flags(self); } + +/* Stub functions shared between multiple key types */ +unsigned nullkey_supported_flags(const ssh_keyalg *self); /* * SSH2 ECDH key exchange vtable diff --git a/utils/CMakeLists.txt b/utils/CMakeLists.txt index 787bcefc..94192f98 100644 --- a/utils/CMakeLists.txt +++ b/utils/CMakeLists.txt @@ -35,6 +35,7 @@ add_sources_from_current_dir(utils memory.c memxor.c null_lp.c + nullkey.c nullseat.c nullstrcmp.c out_of_memory.c diff --git a/utils/nullkey.c b/utils/nullkey.c new file mode 100644 index 00000000..e8e77d50 --- /dev/null +++ b/utils/nullkey.c @@ -0,0 +1,6 @@ +#include "ssh.h" + +unsigned nullkey_supported_flags(const ssh_keyalg *self) +{ + return 0; +} -- cgit v1.2.3 From cf36b9215f972ef883ed6d10062e1cad0255e470 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Tue, 19 Apr 2022 17:27:54 +0100 Subject: ssh_keyalg: new method 'alternate_ssh_id'. Previously, the fact that "ssh-rsa" sometimes comes with two subtypes "rsa-sha2-256" and "rsa-sha2-512" was known to three different parts of the code - two in userauth and one in transport. Now the knowledge of what those ids are, which one goes with which signing flags, and which key types have subtypes at all, is centralised into a method of the key algorithm, and all those locations just query it. This will enable the introduction of further key algorithms that have a parallel upgrade system. --- crypto/dsa.c | 1 + crypto/ecc-ssh.c | 5 +++++ crypto/rsa.c | 12 +++++++++++ ssh.h | 5 +++++ ssh/transport2.c | 22 +++++++++++-------- ssh/userauth2-client.c | 58 +++++++++++++++++++++++++++++--------------------- utils/nullkey.c | 7 ++++++ 7 files changed, 77 insertions(+), 33 deletions(-) diff --git a/crypto/dsa.c b/crypto/dsa.c index db419141..638eaf42 100644 --- a/crypto/dsa.c +++ b/crypto/dsa.c @@ -499,6 +499,7 @@ const ssh_keyalg ssh_dsa = { .components = dsa_components, .pubkey_bits = dsa_pubkey_bits, .supported_flags = nullkey_supported_flags, + .alternate_ssh_id = nullkey_alternate_ssh_id, .ssh_id = "ssh-dss", .cache_id = "dss", }; diff --git a/crypto/ecc-ssh.c b/crypto/ecc-ssh.c index 529ef5a3..1f724ff6 100644 --- a/crypto/ecc-ssh.c +++ b/crypto/ecc-ssh.c @@ -1258,6 +1258,7 @@ const ssh_keyalg ssh_ecdsa_ed25519 = { .components = eddsa_components, .pubkey_bits = ec_shared_pubkey_bits, .supported_flags = nullkey_supported_flags, + .alternate_ssh_id = nullkey_alternate_ssh_id, .ssh_id = "ssh-ed25519", .cache_id = "ssh-ed25519", .extra = &sign_extra_ed25519, @@ -1282,6 +1283,7 @@ const ssh_keyalg ssh_ecdsa_ed448 = { .components = eddsa_components, .pubkey_bits = ec_shared_pubkey_bits, .supported_flags = nullkey_supported_flags, + .alternate_ssh_id = nullkey_alternate_ssh_id, .ssh_id = "ssh-ed448", .cache_id = "ssh-ed448", .extra = &sign_extra_ed448, @@ -1310,6 +1312,7 @@ const ssh_keyalg ssh_ecdsa_nistp256 = { .components = ecdsa_components, .pubkey_bits = ec_shared_pubkey_bits, .supported_flags = nullkey_supported_flags, + .alternate_ssh_id = nullkey_alternate_ssh_id, .ssh_id = "ecdsa-sha2-nistp256", .cache_id = "ecdsa-sha2-nistp256", .extra = &sign_extra_nistp256, @@ -1338,6 +1341,7 @@ const ssh_keyalg ssh_ecdsa_nistp384 = { .components = ecdsa_components, .pubkey_bits = ec_shared_pubkey_bits, .supported_flags = nullkey_supported_flags, + .alternate_ssh_id = nullkey_alternate_ssh_id, .ssh_id = "ecdsa-sha2-nistp384", .cache_id = "ecdsa-sha2-nistp384", .extra = &sign_extra_nistp384, @@ -1366,6 +1370,7 @@ const ssh_keyalg ssh_ecdsa_nistp521 = { .components = ecdsa_components, .pubkey_bits = ec_shared_pubkey_bits, .supported_flags = nullkey_supported_flags, + .alternate_ssh_id = nullkey_alternate_ssh_id, .ssh_id = "ecdsa-sha2-nistp521", .cache_id = "ecdsa-sha2-nistp521", .extra = &sign_extra_nistp521, diff --git a/crypto/rsa.c b/crypto/rsa.c index b87f4bd6..d9be3106 100644 --- a/crypto/rsa.c +++ b/crypto/rsa.c @@ -844,6 +844,15 @@ static unsigned ssh_rsa_supported_flags(const ssh_keyalg *self) return SSH_AGENT_RSA_SHA2_256 | SSH_AGENT_RSA_SHA2_512; } +const char *ssh_rsa_alternate_ssh_id(const ssh_keyalg *self, unsigned flags) +{ + if (flags & SSH_AGENT_RSA_SHA2_512) + return ssh_rsa_sha512.ssh_id; + if (flags & SSH_AGENT_RSA_SHA2_256) + return ssh_rsa_sha256.ssh_id; + return self->ssh_id; +} + static const struct ssh2_rsa_extra rsa_extra = { 0 }, rsa_sha256_extra = { SSH_AGENT_RSA_SHA2_256 }, @@ -869,6 +878,7 @@ const ssh_keyalg ssh_rsa = { COMMON_KEYALG_FIELDS, .ssh_id = "ssh-rsa", .supported_flags = ssh_rsa_supported_flags, + .alternate_ssh_id = ssh_rsa_alternate_ssh_id, .extra = &rsa_extra, }; @@ -876,6 +886,7 @@ const ssh_keyalg ssh_rsa_sha256 = { COMMON_KEYALG_FIELDS, .ssh_id = "rsa-sha2-256", .supported_flags = nullkey_supported_flags, + .alternate_ssh_id = nullkey_alternate_ssh_id, .extra = &rsa_sha256_extra, }; @@ -883,6 +894,7 @@ const ssh_keyalg ssh_rsa_sha512 = { COMMON_KEYALG_FIELDS, .ssh_id = "rsa-sha2-512", .supported_flags = nullkey_supported_flags, + .alternate_ssh_id = nullkey_alternate_ssh_id, .extra = &rsa_sha512_extra, }; diff --git a/ssh.h b/ssh.h index 68a91256..cb26b74b 100644 --- a/ssh.h +++ b/ssh.h @@ -835,6 +835,7 @@ struct ssh_keyalg { /* 'Class methods' that don't deal with an ssh_key at all */ int (*pubkey_bits) (const ssh_keyalg *self, ptrlen blob); unsigned (*supported_flags) (const ssh_keyalg *self); + const char *(*alternate_ssh_id) (const ssh_keyalg *self, unsigned flags); /* Constant data fields giving information about the key type */ const char *ssh_id; /* string identifier in the SSH protocol */ @@ -881,9 +882,13 @@ static inline const unsigned ssh_key_supported_flags(ssh_key *key) { return key->vt->supported_flags(key->vt); } static inline const unsigned ssh_keyalg_supported_flags(const ssh_keyalg *self) { return self->supported_flags(self); } +static inline const char *ssh_keyalg_alternate_ssh_id( + const ssh_keyalg *self, unsigned flags) +{ return self->alternate_ssh_id(self, flags); } /* Stub functions shared between multiple key types */ unsigned nullkey_supported_flags(const ssh_keyalg *self); +const char *nullkey_alternate_ssh_id(const ssh_keyalg *self, unsigned flags); /* * SSH2 ECDH key exchange vtable diff --git a/ssh/transport2.c b/ssh/transport2.c index d43f7e8a..d1231069 100644 --- a/ssh/transport2.c +++ b/ssh/transport2.c @@ -640,17 +640,21 @@ static void ssh2_write_kexinit_lists( alg->u.hk.hkflags = 0; alg->u.hk.warn = false; - if (keyalg == &ssh_rsa) { - alg = ssh2_kexinit_addalg(&kexlists[KEXLIST_HOSTKEY], - "rsa-sha2-256"); - alg->u.hk.hostkey = keyalg; - alg->u.hk.hkflags = SSH_AGENT_RSA_SHA2_256; - alg->u.hk.warn = false; + uint32_t supported_flags = ssh_keyalg_supported_flags(keyalg); + static const uint32_t try_flags[] = { + SSH_AGENT_RSA_SHA2_256, + SSH_AGENT_RSA_SHA2_512, + }; + for (size_t i = 0; i < lenof(try_flags); i++) { + if (try_flags[i] & ~supported_flags) + continue; /* these flags not supported */ + + alg = ssh2_kexinit_addalg( + &kexlists[KEXLIST_HOSTKEY], + ssh_keyalg_alternate_ssh_id(keyalg, try_flags[i])); - alg = ssh2_kexinit_addalg(&kexlists[KEXLIST_HOSTKEY], - "rsa-sha2-512"); alg->u.hk.hostkey = keyalg; - alg->u.hk.hkflags = SSH_AGENT_RSA_SHA2_512; + alg->u.hk.hkflags = try_flags[i]; alg->u.hk.warn = false; } } diff --git a/ssh/userauth2-client.c b/ssh/userauth2-client.c index 90421a8d..69547f14 100644 --- a/ssh/userauth2-client.c +++ b/ssh/userauth2-client.c @@ -247,6 +247,31 @@ static PktIn *ssh2_userauth_pop(struct ssh2_userauth_state *s) return pq_pop(s->ppl.in_pq); } +static bool ssh2_userauth_signflags(struct ssh2_userauth_state *s, + unsigned *signflags, const char **algname) +{ + *signflags = 0; /* default */ + + const ssh_keyalg *alg = find_pubkey_alg(*algname); + if (!alg) + return false; /* we don't know how to upgrade this */ + + unsigned supported_flags = ssh_keyalg_supported_flags(alg); + + if (s->ppl.bpp->ext_info_rsa_sha512_ok && + (supported_flags & SSH_AGENT_RSA_SHA2_512)) { + *signflags = SSH_AGENT_RSA_SHA2_512; + } else if (s->ppl.bpp->ext_info_rsa_sha256_ok && + (supported_flags & SSH_AGENT_RSA_SHA2_256)) { + *signflags = SSH_AGENT_RSA_SHA2_256; + } else { + return false; + } + + *algname = ssh_keyalg_alternate_ssh_id(alg, *signflags); + return true; +} + static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) { struct ssh2_userauth_state *s = @@ -712,18 +737,11 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) * Attempt public-key authentication using a key from Pageant. */ s->agent_keyalg = s->agent_keys[s->agent_key_index].algorithm; - s->signflags = 0; - if (ptrlen_eq_string(s->agent_keyalg, "ssh-rsa")) { - /* Try to upgrade ssh-rsa to one of the rsa-sha2-* family, - * if the server has announced support for them. */ - if (s->ppl.bpp->ext_info_rsa_sha512_ok) { - s->agent_keyalg = PTRLEN_LITERAL("rsa-sha2-512"); - s->signflags = SSH_AGENT_RSA_SHA2_512; - } else if (s->ppl.bpp->ext_info_rsa_sha256_ok) { - s->agent_keyalg = PTRLEN_LITERAL("rsa-sha2-256"); - s->signflags = SSH_AGENT_RSA_SHA2_256; - } - } + char *alg_tmp = mkstr(s->agent_keyalg); + const char *newalg = alg_tmp; + if (ssh2_userauth_signflags(s, &s->signflags, &newalg)) + s->agent_keyalg = ptrlen_from_asciz(newalg); + sfree(alg_tmp); s->ppl.bpp->pls->actx = SSH2_PKTCTX_PUBLICKEY; @@ -845,18 +863,10 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) * * First, try to upgrade its algorithm. */ - if (!strcmp(s->publickey_algorithm, "ssh-rsa")) { - /* Try to upgrade ssh-rsa to one of the rsa-sha2-* family, - * if the server has announced support for them. */ - if (s->ppl.bpp->ext_info_rsa_sha512_ok) { - sfree(s->publickey_algorithm); - s->publickey_algorithm = dupstr("rsa-sha2-512"); - s->signflags = SSH_AGENT_RSA_SHA2_512; - } else if (s->ppl.bpp->ext_info_rsa_sha256_ok) { - sfree(s->publickey_algorithm); - s->publickey_algorithm = dupstr("rsa-sha2-256"); - s->signflags = SSH_AGENT_RSA_SHA2_256; - } + const char *newalg = s->publickey_algorithm; + if (ssh2_userauth_signflags(s, &s->signflags, &newalg)) { + sfree(s->publickey_algorithm); + s->publickey_algorithm = dupstr(newalg); } /* diff --git a/utils/nullkey.c b/utils/nullkey.c index e8e77d50..d4f2a691 100644 --- a/utils/nullkey.c +++ b/utils/nullkey.c @@ -1,6 +1,13 @@ +#include "misc.h" #include "ssh.h" unsigned nullkey_supported_flags(const ssh_keyalg *self) { return 0; } + +const char *nullkey_alternate_ssh_id(const ssh_keyalg *self, unsigned flags) +{ + /* There are no alternate ids */ + return self->ssh_id; +} -- cgit v1.2.3 From 68514ac8a1fd6b588dedd6c88cd3003b88947783 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 18 Apr 2022 10:10:57 +0100 Subject: Refactor the key-components mechanism a bit. Having recently pulled it out into its own file, I think it could also do with a bit of tidying. In this rework: - the substructure for a single component now has a globally visible struct tag, so you can make a variable pointing at it, saving verbiage in every piece of code looping over a key_components - the 'is_mp_int' flag has been replaced with a type enum, so that more types can be added without further upheaval - the printing loop in cmdgen.c for puttygen --dump has factored out the initial 'name=' prefix on each line so that it isn't repeated per component type - the storage format for text components is now a strbuf rather than a plain char *, which I think is generally more useful. --- cmdgen.c | 21 ++++++++++++++------- ssh.h | 20 ++++++++++++-------- test/testcrypt-func.h | 2 +- test/testcrypt.c | 12 +++++++----- utils/key_components.c | 23 ++++++++++++++--------- 5 files changed, 48 insertions(+), 30 deletions(-) diff --git a/cmdgen.c b/cmdgen.c index 14af9642..e8a4ba4e 100644 --- a/cmdgen.c +++ b/cmdgen.c @@ -1312,16 +1312,23 @@ int main(int argc, char **argv) } for (size_t i = 0; i < kc->ncomponents; i++) { - if (kc->components[i].is_mp_int) { - char *hex = mp_get_hex(kc->components[i].mp); - fprintf(fp, "%s=0x%s\n", kc->components[i].name, hex); + key_component *comp = &kc->components[i]; + fprintf(fp, "%s=", comp->name); + switch (comp->type) { + case KCT_MPINT: { + char *hex = mp_get_hex(comp->mp); + fprintf(fp, "0x%s\n", hex); smemclr(hex, strlen(hex)); sfree(hex); - } else { - fprintf(fp, "%s=\"", kc->components[i].name); - write_c_string_literal(fp, ptrlen_from_asciz( - kc->components[i].text)); + break; + } + case KCT_TEXT: + fputs("\"", fp); + write_c_string_literal(fp, ptrlen_from_strbuf(comp->str)); fputs("\"\n", fp); + break; + default: + unreachable("bad key component type"); } } diff --git a/ssh.h b/ssh.h index cb26b74b..e0c45091 100644 --- a/ssh.h +++ b/ssh.h @@ -542,16 +542,20 @@ struct eddsa_key { WeierstrassPoint *ecdsa_public(mp_int *private_key, const ssh_keyalg *alg); EdwardsPoint *eddsa_public(mp_int *private_key, const ssh_keyalg *alg); +typedef enum KeyComponentType { + KCT_TEXT, KCT_MPINT +} KeyComponentType; +typedef struct key_component { + char *name; + KeyComponentType type; + union { + strbuf *str; /* used for KCT_TEXT */ + mp_int *mp; /* used for KCT_MPINT */ + }; +} key_component; typedef struct key_components { size_t ncomponents, componentsize; - struct { - char *name; - bool is_mp_int; - union { - char *text; - mp_int *mp; - }; - } *components; + key_component *components; } key_components; key_components *key_components_new(void); void key_components_add_text(key_components *kc, diff --git a/test/testcrypt-func.h b/test/testcrypt-func.h index 54de4b88..a7fad72e 100644 --- a/test/testcrypt-func.h +++ b/test/testcrypt-func.h @@ -309,7 +309,7 @@ FUNC(uint, ssh_key_public_bits, ARG(keyalg, self), ARG(val_string_ptrlen, blob)) FUNC(uint, key_components_count, ARG(val_keycomponents, kc)) FUNC(opt_val_string_asciz_const, key_components_nth_name, ARG(val_keycomponents, kc), ARG(uint, n)) -FUNC(opt_val_string_asciz_const, key_components_nth_str, +FUNC(opt_val_string, key_components_nth_str, ARG(val_keycomponents, kc), ARG(uint, n)) FUNC(opt_val_mpint, key_components_nth_mp, ARG(val_keycomponents, kc), ARG(uint, n)) diff --git a/test/testcrypt.c b/test/testcrypt.c index a271459e..78542348 100644 --- a/test/testcrypt.c +++ b/test/testcrypt.c @@ -1227,16 +1227,18 @@ const char *key_components_nth_name(key_components *kc, size_t n) return (n >= kc->ncomponents ? NULL : kc->components[n].name); } -const char *key_components_nth_str(key_components *kc, size_t n) +strbuf *key_components_nth_str(key_components *kc, size_t n) { - return (n >= kc->ncomponents ? NULL : - kc->components[n].is_mp_int ? NULL : - kc->components[n].text); + if (n >= kc->ncomponents) + return NULL; + if (kc->components[n].type != KCT_TEXT) + return NULL; + return strbuf_dup(ptrlen_from_strbuf(kc->components[n].str)); } mp_int *key_components_nth_mp(key_components *kc, size_t n) { return (n >= kc->ncomponents ? NULL : - !kc->components[n].is_mp_int ? NULL : + kc->components[n].type != KCT_MPINT ? NULL : mp_copy(kc->components[n].mp)); } diff --git a/utils/key_components.c b/utils/key_components.c index bb9ff40d..6c251517 100644 --- a/utils/key_components.c +++ b/utils/key_components.c @@ -16,8 +16,8 @@ void key_components_add_text(key_components *kc, sgrowarray(kc->components, kc->componentsize, kc->ncomponents); size_t n = kc->ncomponents++; kc->components[n].name = dupstr(name); - kc->components[n].is_mp_int = false; - kc->components[n].text = dupstr(value); + kc->components[n].type = KCT_TEXT; + kc->components[n].str = strbuf_dup_nm(ptrlen_from_asciz(value)); } void key_components_add_mp(key_components *kc, @@ -26,19 +26,24 @@ void key_components_add_mp(key_components *kc, sgrowarray(kc->components, kc->componentsize, kc->ncomponents); size_t n = kc->ncomponents++; kc->components[n].name = dupstr(name); - kc->components[n].is_mp_int = true; + kc->components[n].type = KCT_MPINT; kc->components[n].mp = mp_copy(value); } void key_components_free(key_components *kc) { for (size_t i = 0; i < kc->ncomponents; i++) { - sfree(kc->components[i].name); - if (kc->components[i].is_mp_int) { - mp_free(kc->components[i].mp); - } else { - smemclr(kc->components[i].text, strlen(kc->components[i].text)); - sfree(kc->components[i].text); + key_component *comp = &kc->components[i]; + sfree(comp->name); + switch (comp->type) { + case KCT_MPINT: + mp_free(comp->mp); + break; + case KCT_TEXT: + strbuf_free(comp->str); + break; + default: + unreachable("bad key component type"); } } sfree(kc->components); -- cgit v1.2.3 From 62bc6c5448514fc77386526b773fc4508945d9cd Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 18 Apr 2022 10:06:31 +0100 Subject: New key component type KCT_BINARY. This stores its data in the same format as the existing KCT_TEXT, but it displays differently in puttygen --dump, expecting that the data will be full of horrible control characters, invalid UTF-8, etc. The displayed data is of the form b64("..."), so you get a hint about what the encoding is, and can still paste into Python by defining the identifier 'b64' to be base64.b64decode or equivalent. --- cmdgen.c | 32 ++++++++++++++++++++++++++++++++ ssh.h | 6 ++++-- test/testcrypt.c | 3 ++- utils/key_components.c | 21 +++++++++++++++++---- 4 files changed, 55 insertions(+), 7 deletions(-) diff --git a/cmdgen.c b/cmdgen.c index e8a4ba4e..37b5cac6 100644 --- a/cmdgen.c +++ b/cmdgen.c @@ -1327,6 +1327,38 @@ int main(int argc, char **argv) write_c_string_literal(fp, ptrlen_from_strbuf(comp->str)); fputs("\"\n", fp); break; + case KCT_BINARY: { + /* + * Display format for binary key components is to show + * them as base64, with a wrapper so that the actual + * printed string is along the lines of + * 'b64("aGVsbG8sIHdvcmxkCg==")'. + * + * That's a compromise between not being too verbose + * for a human reader, and still being reasonably + * friendly to people pasting the output of this + * 'puttygen --dump' option into Python code (which + * the format is designed to permit in general). + * + * Python users pasting a dump containing one of these + * will have to define a function 'b64' in advance + * which takes a string, which you can do most easily + * using this import statement, as seen in + * cryptsuite.py: + * + * from base64 import b64decode as b64 + */ + fputs("b64(\"", fp); + char b64[4]; + for (size_t j = 0; j < comp->str->len; j += 3) { + size_t len = comp->str->len - j; + if (len > 3) len = 3; + base64_encode_atom(comp->str->u + j, len, b64); + fwrite(b64, 1, 4, fp); + } + fputs("\")\n", fp); + break; + } default: unreachable("bad key component type"); } diff --git a/ssh.h b/ssh.h index e0c45091..19f16916 100644 --- a/ssh.h +++ b/ssh.h @@ -543,13 +543,13 @@ WeierstrassPoint *ecdsa_public(mp_int *private_key, const ssh_keyalg *alg); EdwardsPoint *eddsa_public(mp_int *private_key, const ssh_keyalg *alg); typedef enum KeyComponentType { - KCT_TEXT, KCT_MPINT + KCT_TEXT, KCT_BINARY, KCT_MPINT } KeyComponentType; typedef struct key_component { char *name; KeyComponentType type; union { - strbuf *str; /* used for KCT_TEXT */ + strbuf *str; /* used for KCT_TEXT and KCT_BINARY */ mp_int *mp; /* used for KCT_MPINT */ }; } key_component; @@ -560,6 +560,8 @@ typedef struct key_components { key_components *key_components_new(void); void key_components_add_text(key_components *kc, const char *name, const char *value); +void key_components_add_binary(key_components *kc, + const char *name, ptrlen value); void key_components_add_mp(key_components *kc, const char *name, mp_int *value); void key_components_free(key_components *kc); diff --git a/test/testcrypt.c b/test/testcrypt.c index 78542348..566c95b9 100644 --- a/test/testcrypt.c +++ b/test/testcrypt.c @@ -1231,7 +1231,8 @@ strbuf *key_components_nth_str(key_components *kc, size_t n) { if (n >= kc->ncomponents) return NULL; - if (kc->components[n].type != KCT_TEXT) + if (kc->components[n].type != KCT_TEXT && + kc->components[n].type != KCT_BINARY) return NULL; return strbuf_dup(ptrlen_from_strbuf(kc->components[n].str)); } diff --git a/utils/key_components.c b/utils/key_components.c index 6c251517..d8af240e 100644 --- a/utils/key_components.c +++ b/utils/key_components.c @@ -10,14 +10,26 @@ key_components *key_components_new(void) return kc; } -void key_components_add_text(key_components *kc, - const char *name, const char *value) +static void key_components_add_str(key_components *kc, const char *name, + KeyComponentType type, ptrlen data) { sgrowarray(kc->components, kc->componentsize, kc->ncomponents); size_t n = kc->ncomponents++; kc->components[n].name = dupstr(name); - kc->components[n].type = KCT_TEXT; - kc->components[n].str = strbuf_dup_nm(ptrlen_from_asciz(value)); + kc->components[n].type = type; + kc->components[n].str = strbuf_dup_nm(data); +} + +void key_components_add_text(key_components *kc, + const char *name, const char *value) +{ + key_components_add_str(kc, name, KCT_TEXT, ptrlen_from_asciz(value)); +} + +void key_components_add_binary(key_components *kc, + const char *name, ptrlen value) +{ + key_components_add_str(kc, name, KCT_BINARY, value); } void key_components_add_mp(key_components *kc, @@ -40,6 +52,7 @@ void key_components_free(key_components *kc) mp_free(comp->mp); break; case KCT_TEXT: + case KCT_BINARY: strbuf_free(comp->str); break; default: -- cgit v1.2.3 From 180d1b78de90f2e3fd93939664322185ebbabc00 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 18 Apr 2022 10:08:10 +0100 Subject: Extra helper functions for adding key_components. In this commit, I provide further functions which generate the existing set of data types: - key_components_add_text_pl() adds a text component, but takes a ptrlen rather than a const char *, in case that was what you happened to have already. - key_components_add_uint() ends up adding an mp_int to the structure, but takes it as input in the form of an ordinary C integer, for the convenience of call sites which will want to do that a lot and don't enjoy repeating the mp_int construction boilerplate - key_components_add_copy() takes a pointer to one of the key_component sub-structs in an existing key_components, and copies it into the output key_components under a new name, handling whatever type it turns out to have. --- ssh.h | 6 ++++++ utils/key_components.c | 29 +++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/ssh.h b/ssh.h index 19f16916..3bd90dfc 100644 --- a/ssh.h +++ b/ssh.h @@ -560,10 +560,16 @@ typedef struct key_components { key_components *key_components_new(void); void key_components_add_text(key_components *kc, const char *name, const char *value); +void key_components_add_text_pl(key_components *kc, + const char *name, ptrlen value); void key_components_add_binary(key_components *kc, const char *name, ptrlen value); void key_components_add_mp(key_components *kc, const char *name, mp_int *value); +void key_components_add_uint(key_components *kc, + const char *name, uintmax_t value); +void key_components_add_copy(key_components *kc, + const char *name, const key_component *value); void key_components_free(key_components *kc); /* diff --git a/utils/key_components.c b/utils/key_components.c index d8af240e..b072ff51 100644 --- a/utils/key_components.c +++ b/utils/key_components.c @@ -26,6 +26,12 @@ void key_components_add_text(key_components *kc, key_components_add_str(kc, name, KCT_TEXT, ptrlen_from_asciz(value)); } +void key_components_add_text_pl(key_components *kc, + const char *name, ptrlen value) +{ + key_components_add_str(kc, name, KCT_TEXT, value); +} + void key_components_add_binary(key_components *kc, const char *name, ptrlen value) { @@ -42,6 +48,29 @@ void key_components_add_mp(key_components *kc, kc->components[n].mp = mp_copy(value); } +void key_components_add_uint(key_components *kc, + const char *name, uintmax_t value) +{ + mp_int *mpvalue = mp_from_integer(value); + key_components_add_mp(kc, name, mpvalue); + mp_free(mpvalue); +} + +void key_components_add_copy(key_components *kc, + const char *name, const key_component *value) +{ + switch (value->type) { + case KCT_TEXT: + case KCT_BINARY: + key_components_add_str(kc, name, value->type, + ptrlen_from_strbuf(value->str)); + break; + case KCT_MPINT: + key_components_add_mp(kc, name, value->mp); + break; + } +} + void key_components_free(key_components *kc) { for (size_t i = 0; i < kc->ncomponents; i++) { -- cgit v1.2.3 From c2f1a563a50bd611aaf330142e828dfb1b43f18e Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Wed, 20 Apr 2022 13:51:28 +0100 Subject: Utility function ssh_key_clone(). This makes a second independent copy of an existing ssh_key, for situations where one piece of code is going to want to keep it after its current owner frees it. In order to have it work on an arbitrary ssh_key, whether public-only or a full public+private key pair, I've had to add an ssh_key query method to ask whether a private key is known. I'm surprised I haven't found a need for that before! But I suppose in most situations in an SSH client you statically know which kind of key you're dealing with. --- crypto/dsa.c | 7 +++++++ crypto/ecc-ssh.c | 17 +++++++++++++++++ crypto/rsa.c | 7 +++++++ ssh.h | 6 ++++++ utils/CMakeLists.txt | 1 + utils/ssh_key_clone.c | 32 ++++++++++++++++++++++++++++++++ 6 files changed, 70 insertions(+) create mode 100644 utils/ssh_key_clone.c diff --git a/crypto/dsa.c b/crypto/dsa.c index 638eaf42..530bbe09 100644 --- a/crypto/dsa.c +++ b/crypto/dsa.c @@ -317,6 +317,12 @@ static void dsa_openssh_blob(ssh_key *key, BinarySink *bs) put_mp_ssh2(bs, dsa->x); } +static bool dsa_has_private(ssh_key *key) +{ + struct dsa_key *dsa = container_of(key, struct dsa_key, sshk); + return dsa->x != NULL; +} + static int dsa_pubkey_bits(const ssh_keyalg *self, ptrlen pub) { ssh_key *sshk; @@ -495,6 +501,7 @@ const ssh_keyalg ssh_dsa = { .public_blob = dsa_public_blob, .private_blob = dsa_private_blob, .openssh_blob = dsa_openssh_blob, + .has_private = dsa_has_private, .cache_str = dsa_cache_str, .components = dsa_components, .pubkey_bits = dsa_pubkey_bits, diff --git a/crypto/ecc-ssh.c b/crypto/ecc-ssh.c index 1f724ff6..9f637c2e 100644 --- a/crypto/ecc-ssh.c +++ b/crypto/ecc-ssh.c @@ -787,6 +787,12 @@ static void ecdsa_private_blob(ssh_key *key, BinarySink *bs) put_mp_ssh2(bs, ek->privateKey); } +static bool ecdsa_has_private(ssh_key *key) +{ + struct ecdsa_key *ek = container_of(key, struct ecdsa_key, sshk); + return ek->privateKey != NULL; +} + static void eddsa_private_blob(ssh_key *key, BinarySink *bs) { struct eddsa_key *ek = container_of(key, struct eddsa_key, sshk); @@ -796,6 +802,12 @@ static void eddsa_private_blob(ssh_key *key, BinarySink *bs) put_mp_le_fixedlen(bs, ek->privateKey, ek->curve->fieldBytes); } +static bool eddsa_has_private(ssh_key *key) +{ + struct eddsa_key *ek = container_of(key, struct eddsa_key, sshk); + return ek->privateKey != NULL; +} + static ssh_key *ecdsa_new_priv(const ssh_keyalg *alg, ptrlen pub, ptrlen priv) { ssh_key *sshk = ecdsa_new_pub(alg, pub); @@ -1254,6 +1266,7 @@ const ssh_keyalg ssh_ecdsa_ed25519 = { .public_blob = eddsa_public_blob, .private_blob = eddsa_private_blob, .openssh_blob = eddsa_openssh_blob, + .has_private = eddsa_has_private, .cache_str = eddsa_cache_str, .components = eddsa_components, .pubkey_bits = ec_shared_pubkey_bits, @@ -1279,6 +1292,7 @@ const ssh_keyalg ssh_ecdsa_ed448 = { .public_blob = eddsa_public_blob, .private_blob = eddsa_private_blob, .openssh_blob = eddsa_openssh_blob, + .has_private = eddsa_has_private, .cache_str = eddsa_cache_str, .components = eddsa_components, .pubkey_bits = ec_shared_pubkey_bits, @@ -1308,6 +1322,7 @@ const ssh_keyalg ssh_ecdsa_nistp256 = { .public_blob = ecdsa_public_blob, .private_blob = ecdsa_private_blob, .openssh_blob = ecdsa_openssh_blob, + .has_private = ecdsa_has_private, .cache_str = ecdsa_cache_str, .components = ecdsa_components, .pubkey_bits = ec_shared_pubkey_bits, @@ -1337,6 +1352,7 @@ const ssh_keyalg ssh_ecdsa_nistp384 = { .public_blob = ecdsa_public_blob, .private_blob = ecdsa_private_blob, .openssh_blob = ecdsa_openssh_blob, + .has_private = ecdsa_has_private, .cache_str = ecdsa_cache_str, .components = ecdsa_components, .pubkey_bits = ec_shared_pubkey_bits, @@ -1366,6 +1382,7 @@ const ssh_keyalg ssh_ecdsa_nistp521 = { .public_blob = ecdsa_public_blob, .private_blob = ecdsa_private_blob, .openssh_blob = ecdsa_openssh_blob, + .has_private = ecdsa_has_private, .cache_str = ecdsa_cache_str, .components = ecdsa_components, .pubkey_bits = ec_shared_pubkey_bits, diff --git a/crypto/rsa.c b/crypto/rsa.c index d9be3106..4087035d 100644 --- a/crypto/rsa.c +++ b/crypto/rsa.c @@ -522,6 +522,12 @@ static key_components *rsa2_components(ssh_key *key) return rsa_components(rsa); } +static bool rsa2_has_private(ssh_key *key) +{ + RSAKey *rsa = container_of(key, RSAKey, sshk); + return rsa->private_exponent != NULL; +} + static void rsa2_public_blob(ssh_key *key, BinarySink *bs) { RSAKey *rsa = container_of(key, RSAKey, sshk); @@ -869,6 +875,7 @@ static const struct ssh2_rsa_extra .public_blob = rsa2_public_blob, \ .private_blob = rsa2_private_blob, \ .openssh_blob = rsa2_openssh_blob, \ + .has_private = rsa2_has_private, \ .cache_str = rsa2_cache_str, \ .components = rsa2_components, \ .pubkey_bits = rsa2_pubkey_bits, \ diff --git a/ssh.h b/ssh.h index 3bd90dfc..b703945f 100644 --- a/ssh.h +++ b/ssh.h @@ -841,6 +841,7 @@ struct ssh_keyalg { void (*public_blob)(ssh_key *key, BinarySink *); void (*private_blob)(ssh_key *key, BinarySink *); void (*openssh_blob) (ssh_key *key, BinarySink *); + bool (*has_private) (ssh_key *key); char *(*cache_str) (ssh_key *key); key_components *(*components) (ssh_key *key); @@ -878,6 +879,8 @@ static inline void ssh_key_private_blob(ssh_key *key, BinarySink *bs) { key->vt->private_blob(key, bs); } static inline void ssh_key_openssh_blob(ssh_key *key, BinarySink *bs) { key->vt->openssh_blob(key, bs); } +static inline bool ssh_key_has_private(ssh_key *key) +{ return key->vt->has_private(key); } static inline char *ssh_key_cache_str(ssh_key *key) { return key->vt->cache_str(key); } static inline key_components *ssh_key_components(ssh_key *key) @@ -902,6 +905,9 @@ static inline const char *ssh_keyalg_alternate_ssh_id( unsigned nullkey_supported_flags(const ssh_keyalg *self); const char *nullkey_alternate_ssh_id(const ssh_keyalg *self, unsigned flags); +/* Utility functions implemented centrally */ +ssh_key *ssh_key_clone(ssh_key *key); + /* * SSH2 ECDH key exchange vtable */ diff --git a/utils/CMakeLists.txt b/utils/CMakeLists.txt index 94192f98..9e995e04 100644 --- a/utils/CMakeLists.txt +++ b/utils/CMakeLists.txt @@ -49,6 +49,7 @@ add_sources_from_current_dir(utils smemclr.c smemeq.c spr_get_error_message.c + ssh_key_clone.c ssh2_pick_fingerprint.c sshutils.c strbuf.c diff --git a/utils/ssh_key_clone.c b/utils/ssh_key_clone.c new file mode 100644 index 00000000..5824bdbf --- /dev/null +++ b/utils/ssh_key_clone.c @@ -0,0 +1,32 @@ +/* + * Make a copy of an existing ssh_key object, e.g. to survive after + * the original is freed. + */ + +#include "misc.h" +#include "ssh.h" + +ssh_key *ssh_key_clone(ssh_key *key) +{ + /* + * To avoid having to add a special method in the vtable API, we + * clone by round-tripping through public and private blobs. + */ + strbuf *pub = strbuf_new_nm(); + ssh_key_public_blob(key, BinarySink_UPCAST(pub)); + + ssh_key *copy; + + if (ssh_key_has_private(key)) { + strbuf *priv = strbuf_new_nm(); + ssh_key_private_blob(key, BinarySink_UPCAST(priv)); + copy = ssh_key_new_priv(ssh_key_alg(key), ptrlen_from_strbuf(pub), + ptrlen_from_strbuf(priv)); + strbuf_free(priv); + } else { + copy = ssh_key_new_pub(ssh_key_alg(key), ptrlen_from_strbuf(pub)); + } + + strbuf_free(pub); + return copy; +} -- cgit v1.2.3 From 69e8d471d1343f3532bef941aa6f25cd3679cdeb Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 25 Apr 2022 14:08:00 +0100 Subject: Move our DialogBox wrapper into windows/utils. It's self-contained enough not to really need to live in dialog.c as a set of static functions. Also, moving it means we can isolate the implementation details - which also makes it easy to change them. One such change is that I've added the ability to bake a context pointer into the dialog - unused so far, but it will be shortly. (Also, while I'm here, renamed the functions so they sound more as if they're adding features than working around bugs - not to mention not imputing mental illness to the usual versions.) --- windows/CMakeLists.txt | 1 + windows/dialog.c | 72 ++++---------------------- windows/platform.h | 8 +-- windows/utils/shinydialogbox.c | 111 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 127 insertions(+), 65 deletions(-) create mode 100644 windows/utils/shinydialogbox.c diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt index 9a6a4ec6..6c802183 100644 --- a/windows/CMakeLists.txt +++ b/windows/CMakeLists.txt @@ -30,6 +30,7 @@ add_sources_from_current_dir(utils utils/request_file.c utils/screenshot.c utils/security.c + utils/shinydialogbox.c utils/split_into_argv.c utils/version.c utils/win_strerror.c diff --git a/windows/dialog.c b/windows/dialog.c index 38af0f9b..e68ef2af 100644 --- a/windows/dialog.c +++ b/windows/dialog.c @@ -255,57 +255,6 @@ static INT_PTR CALLBACK AboutProc(HWND hwnd, UINT msg, 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. */ @@ -395,8 +344,8 @@ const char *dialog_box_demo_screenshot_filename = NULL; * (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_PTR CALLBACK GenericMainDlgProc(HWND hwnd, UINT msg, - WPARAM wParam, LPARAM lParam) +static INT_PTR GenericMainDlgProc(HWND hwnd, UINT msg, WPARAM wParam, + LPARAM lParam, void *ctx) { const int DEMO_SCREENSHOT_TIMER_ID = 1230; HWND hw, treeview; @@ -581,7 +530,7 @@ static INT_PTR CALLBACK GenericMainDlgProc(HWND hwnd, UINT msg, if (err) MessageBox(hwnd, err, "Demo screenshot failure", MB_OK | MB_ICONERROR); - SaneEndDialog(hwnd, 0); + ShinyEndDialog(hwnd, 0); } return 0; case WM_LBUTTONUP: @@ -591,7 +540,7 @@ static INT_PTR CALLBACK GenericMainDlgProc(HWND hwnd, UINT msg, */ ReleaseCapture(); if (dp.ended) - SaneEndDialog(hwnd, dp.endresult ? 1 : 0); + ShinyEndDialog(hwnd, dp.endresult ? 1 : 0); break; case WM_NOTIFY: if (LOWORD(wParam) == IDCX_TREEVIEW && @@ -657,7 +606,7 @@ static INT_PTR CALLBACK GenericMainDlgProc(HWND hwnd, UINT msg, 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); + ShinyEndDialog(hwnd, dp.endresult ? 1 : 0); } else ret = 0; return ret; @@ -668,7 +617,7 @@ static INT_PTR CALLBACK GenericMainDlgProc(HWND hwnd, UINT msg, break; case WM_CLOSE: quit_help(hwnd); - SaneEndDialog(hwnd, 0); + ShinyEndDialog(hwnd, 0); return 0; /* Grrr Explorer will maximize Dialogs! */ @@ -729,9 +678,8 @@ bool do_config(Conf *conf) dlg_auto_set_fixed_pitch_flag(&dp); dp.shortcuts['g'] = true; /* the treeview: `Cate&gory' */ - ret = - SaneDialogBox(hinst, MAKEINTRESOURCE(IDD_MAINBOX), NULL, - GenericMainDlgProc); + ret = ShinyDialogBox(hinst, MAKEINTRESOURCE(IDD_MAINBOX), "PuTTYConfigBox", + NULL, GenericMainDlgProc, NULL); ctrl_free_box(ctrlbox); winctrl_cleanup(&ctrls_panel); @@ -764,8 +712,8 @@ bool do_reconfig(HWND hwnd, Conf *conf, int protcfginfo) dlg_auto_set_fixed_pitch_flag(&dp); dp.shortcuts['g'] = true; /* the treeview: `Cate&gory' */ - ret = SaneDialogBox(hinst, MAKEINTRESOURCE(IDD_MAINBOX), NULL, - GenericMainDlgProc); + ret = ShinyDialogBox(hinst, MAKEINTRESOURCE(IDD_MAINBOX), "PuTTYConfigBox", + NULL, GenericMainDlgProc, NULL); ctrl_free_box(ctrlbox); winctrl_cleanup(&ctrls_base); diff --git a/windows/platform.h b/windows/platform.h index 5760c7c6..c6bf056a 100644 --- a/windows/platform.h +++ b/windows/platform.h @@ -109,9 +109,11 @@ static inline uintmax_t strtoumax(const char *nptr, char **endptr, int base) { return _strtoui64(nptr, endptr, base); } #endif -#define BOXFLAGS DLGWINDOWEXTRA -#define BOXRESULT (DLGWINDOWEXTRA + sizeof(LONG_PTR)) -#define DF_END 0x0001 +typedef INT_PTR (*ShinyDlgProc)(HWND hwnd, UINT msg, WPARAM wParam, + LPARAM lParam, void *ctx); +int ShinyDialogBox(HINSTANCE hinst, LPCTSTR tmpl, const char *winclass, + HWND hwndparent, ShinyDlgProc proc, void *ctx); +void ShinyEndDialog(HWND hwnd, int ret); #ifndef __WINE__ /* Up-to-date Windows headers warn that the unprefixed versions of diff --git a/windows/utils/shinydialogbox.c b/windows/utils/shinydialogbox.c new file mode 100644 index 00000000..5abb6676 --- /dev/null +++ b/windows/utils/shinydialogbox.c @@ -0,0 +1,111 @@ +/* + * PuTTY's own reimplementation of DialogBox() and EndDialog() which + * provide extra capabilities. + * + * Originally introduced in 2003 to work around a problem with our + * message loops, in which keystrokes pressed in the 'Change Settings' + * dialog in mid-session would _also_ be delivered to the main + * terminal window. + * + * But once we had our own wrapper it was convenient to put further + * things into it. In particular, this system allows you to provide a + * context pointer at setup time that's easy to retrieve from the + * window procedure. + */ + +#include "putty.h" + +struct ShinyDialogBoxState { + bool ended; + int result; + ShinyDlgProc proc; + void *ctx; +}; + +/* + * For use in re-entrant calls to the dialog procedure from + * CreateDialog itself, temporarily store the state pointer that we + * won't store in the usual window-memory slot until later. + * + * I don't _intend_ that this system will need to be used in multiple + * threads at all, let alone concurrently, but just in case, declaring + * sdb_tempstate as thread-local will protect against that possibility. + */ +static __declspec(thread) struct ShinyDialogBoxState *sdb_tempstate; + +static inline struct ShinyDialogBoxState *ShinyDialogGetState(HWND hwnd) +{ + if (sdb_tempstate) + return sdb_tempstate; + return (struct ShinyDialogBoxState *)GetWindowLongPtr( + hwnd, DLGWINDOWEXTRA); +} + +static INT_PTR CALLBACK ShinyRealDlgProc(HWND hwnd, UINT msg, + WPARAM wParam, LPARAM lParam) +{ + struct ShinyDialogBoxState *state = ShinyDialogGetState(hwnd); + return state->proc(hwnd, msg, wParam, lParam, state->ctx); +} + +int ShinyDialogBox(HINSTANCE hinst, LPCTSTR tmpl, const char *winclass, + HWND hwndparent, ShinyDlgProc proc, void *ctx) +{ + /* + * Register the window class ourselves in such a way that we + * allocate an extra word of window memory to store the state + * pointer. + * + * It would be nice in principle to load the dialog template + * resource and dig the class name out of it. But DLGTEMPLATEEX is + * a nasty variable-layout structure not declared conveniently in + * a header file, so I think that's too much effort. Callers of + * this function will just have to provide the right window class + * name to match their template resource via the 'winclass' + * parameter. + */ + WNDCLASS wc; + wc.style = CS_DBLCLKS | CS_SAVEBITS | CS_BYTEALIGNWINDOW; + wc.lpfnWndProc = DefDlgProc; + wc.cbClsExtra = 0; + wc.cbWndExtra = DLGWINDOWEXTRA + 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 = winclass; + RegisterClass(&wc); + + struct ShinyDialogBoxState state[1]; + state->ended = false; + state->proc = proc; + state->ctx = ctx; + + sdb_tempstate = state; + HWND hwnd = CreateDialog(hinst, tmpl, hwndparent, ShinyRealDlgProc); + SetWindowLongPtr(hwnd, DLGWINDOWEXTRA, (LONG_PTR)state); + sdb_tempstate = NULL; + + MSG msg; + int gm; + while ((gm = GetMessage(&msg, NULL, 0, 0)) > 0) { + if (!state->ended && !IsDialogMessage(hwnd, &msg)) + DispatchMessage(&msg); + if (state->ended) + break; + } + + if (gm == 0) + PostQuitMessage(msg.wParam); /* We got a WM_QUIT, pass it on */ + + DestroyWindow(hwnd); + return state->result; +} + +void ShinyEndDialog(HWND hwnd, int ret) +{ + struct ShinyDialogBoxState *state = ShinyDialogGetState(hwnd); + state->result = ret; + state->ended = true; +} -- cgit v1.2.3 From cccdab9ba6dbe20dd4a991a9ac787d8901132dfe Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 24 Apr 2022 13:34:15 +0100 Subject: Windows: utility function to centre a window. This was called from config box setup, and is obviously the kind of thing that ought to be a reusable utility function. --- windows/CMakeLists.txt | 1 + windows/dialog.c | 17 +++-------------- windows/platform.h | 2 ++ windows/utils/centre_window.c | 20 ++++++++++++++++++++ 4 files changed, 26 insertions(+), 14 deletions(-) create mode 100644 windows/utils/centre_window.c diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt index 6c802183..7bfa955c 100644 --- a/windows/CMakeLists.txt +++ b/windows/CMakeLists.txt @@ -5,6 +5,7 @@ add_sources_from_current_dir(utils utils/agent_named_pipe_name.c utils/arm_arch_queries.c utils/aux_match_opt.c + utils/centre_window.c utils/cryptoapi.c utils/defaults.c utils/dll_hijacking_protection.c diff --git a/windows/dialog.c b/windows/dialog.c index e68ef2af..65534815 100644 --- a/windows/dialog.c +++ b/windows/dialog.c @@ -348,7 +348,7 @@ static INT_PTR GenericMainDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, void *ctx) { const int DEMO_SCREENSHOT_TIMER_ID = 1230; - HWND hw, treeview; + HWND treeview; struct treeview_faff tvfaff; int ret; @@ -369,19 +369,8 @@ static INT_PTR GenericMainDlgProc(HWND hwnd, UINT msg, WPARAM wParam, } 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); - } + + centre_window(hwnd); /* * Create the tree view. diff --git a/windows/platform.h b/windows/platform.h index c6bf056a..25de278f 100644 --- a/windows/platform.h +++ b/windows/platform.h @@ -115,6 +115,8 @@ int ShinyDialogBox(HINSTANCE hinst, LPCTSTR tmpl, const char *winclass, HWND hwndparent, ShinyDlgProc proc, void *ctx); void ShinyEndDialog(HWND hwnd, int ret); +void centre_window(HWND hwnd); + #ifndef __WINE__ /* Up-to-date Windows headers warn that the unprefixed versions of * these names are deprecated. */ diff --git a/windows/utils/centre_window.c b/windows/utils/centre_window.c new file mode 100644 index 00000000..651fc279 --- /dev/null +++ b/windows/utils/centre_window.c @@ -0,0 +1,20 @@ +/* + * Centre a window on the screen. Used to position the main config box. + */ + +#include "putty.h" + +void centre_window(HWND win) +{ + RECT rd, rw; + + if (!GetWindowRect(GetDesktopWindow(), &rd)) + return; + if (!GetWindowRect(win, &rw)) + return; + + MoveWindow(win, + (rd.right + rd.left + rw.left - rw.right) / 2, + (rd.bottom + rd.top + rw.top - rw.bottom) / 2, + rw.right - rw.left, rw.bottom - rw.top, true); +} -- cgit v1.2.3 From 1bd2af1f875b8c7927906eadaf578013df8d999f Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 23 Apr 2022 15:01:29 +0100 Subject: Windows: refactor config-box creation code. I'm about to want to create a second entirely different dialog box whose contents are described using the same dialog.h API as the main config box. So I'm starting by moving as much handler code as possible out of GenericMainDlgProc and its callers, and into a set of reusable subroutines. In particular, this gets rid of the disgusting static variables that stored all the config-box state. Now they're stored in a more sensible struct, which lives in the new context-pointer field provided by the reworked ShinyDialogBox. --- windows/dialog.c | 408 ++++++++++++++++++++++++++++++++----------------------- 1 file changed, 235 insertions(+), 173 deletions(-) diff --git a/windows/dialog.c b/windows/dialog.c index 65534815..7e0c92ad 100644 --- a/windows/dialog.c +++ b/windows/dialog.c @@ -27,21 +27,190 @@ #define ICON_BIG 1 #endif +typedef struct PortableDialogStuff { + /* + * These are the various bits of data required to handle a dialog + * box that's been built up from the cross-platform dialog.c + * system. + */ + + /* The 'controlbox' that was returned from the portable setup function */ + struct controlbox *ctrlbox; + + /* The dlgparam that's passed to all the runtime dlg_* functions. + * Declared as an array of 1 so it's convenient to pass it as a pointer. */ + struct dlgparam dp[1]; + + /* + * Collections of instantiated controls. There can be more than + * one of these, because sometimes you want to destroy and + * recreate a subset of them - e.g. when switching panes in the + * main PuTTY config box, you delete and recreate _most_ of the + * controls, but not the OK and Cancel buttons at the bottom. + */ + size_t nctrltrees; + struct winctrls *ctrltrees; + + /* + * Flag indicating whether the dialog box has been initialised. + * Used to suppresss spurious firing of message handlers during + * setup. + */ + bool initialised; +} PortableDialogStuff; + /* - * 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. + * Initialise a PortableDialogStuff, before launching the dialog box. */ -static struct controlbox *ctrlbox; +static PortableDialogStuff *pds_new(size_t nctrltrees) +{ + PortableDialogStuff *pds = snew(PortableDialogStuff); + memset(pds, 0, sizeof(*pds)); + + pds->ctrlbox = ctrl_new_box(); + + dp_init(pds->dp); + + pds->nctrltrees = nctrltrees; + pds->ctrltrees = snewn(pds->nctrltrees, struct winctrls); + for (size_t i = 0; i < pds->nctrltrees; i++) { + winctrl_init(&pds->ctrltrees[i]); + dp_add_tree(pds->dp, &pds->ctrltrees[i]); + } + + pds->dp->errtitle = dupprintf("%s Error", appname); + + pds->initialised = false; + + return pds; +} + +static void pds_free(PortableDialogStuff *pds) +{ + ctrl_free_box(pds->ctrlbox); + + dp_cleanup(pds->dp); + + for (size_t i = 0; i < pds->nctrltrees; i++) + winctrl_cleanup(&pds->ctrltrees[i]); + sfree(pds->ctrltrees); + + sfree(pds); +} + +static INT_PTR pds_default_dlgproc(PortableDialogStuff *pds, HWND hwnd, + UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch (msg) { + case WM_LBUTTONUP: + /* + * Button release should trigger WM_OK if there was a + * previous double click on the host CA list. + */ + ReleaseCapture(); + if (pds->dp->ended) + ShinyEndDialog(hwnd, pds->dp->endresult ? 1 : 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. + */ + int ret; + if (pds->initialised) { + ret = winctrl_handle_command(pds->dp, msg, wParam, lParam); + if (pds->dp->ended && GetCapture() != hwnd) + ShinyEndDialog(hwnd, pds->dp->endresult ? 1 : 0); + } else + ret = 0; + return ret; + } + case WM_HELP: + if (!winctrl_context_help(pds->dp, + hwnd, ((LPHELPINFO)lParam)->iCtrlId)) + MessageBeep(0); + break; + case WM_CLOSE: + quit_help(hwnd); + ShinyEndDialog(hwnd, 0); + return 0; + + /* Grrr Explorer will maximize Dialogs! */ + case WM_SIZE: + if (wParam == SIZE_MAXIMIZED) + force_normal(hwnd); + return 0; + + } + return 0; +} + +static void pds_initdialog_start(PortableDialogStuff *pds, HWND hwnd) +{ + pds->dp->hwnd = hwnd; + + if (pds->dp->wintitle) /* apply override title, if provided */ + SetWindowText(hwnd, pds->dp->wintitle); + + /* The portable dialog system generally includes the ability to + * handle context help for particular controls. Enable the + * relevant window styles if we have a help file available. */ + if (has_help()) { + LONG_PTR style = GetWindowLongPtr(hwnd, GWL_EXSTYLE); + SetWindowLongPtr(hwnd, GWL_EXSTYLE, style | WS_EX_CONTEXTHELP); + } else { + /* If not, and if the dialog template provided a top-level + * Help button, delete it */ + HWND item = GetDlgItem(hwnd, IDC_HELPBTN); + if (item) + DestroyWindow(item); + } +} + /* - * 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. + * Create the panelfuls of controls in the configuration box. */ -static struct winctrls ctrls_base, ctrls_panel; -static struct dlgparam dp; +static void pds_create_controls( + PortableDialogStuff *pds, size_t which_tree, int base_id, + int left, int right, int top, char *path) +{ + struct ctlpos cp; + + ctlposinit(&cp, pds->dp->hwnd, left, right, top); + + for (int index = -1; (index = ctrl_find_path( + pds->ctrlbox, path, index)) >= 0 ;) { + struct controlset *s = pds->ctrlbox->ctrlsets[index]; + winctrl_layout(pds->dp, &pds->ctrltrees[which_tree], &cp, s, &base_id); + } +} + +static void pds_initdialog_finish(PortableDialogStuff *pds) +{ + /* + * Set focus into the first available control in ctrltree #0, + * which the caller was expected to set up to be the one + * containing the dialog controls likely to be used first. + */ + struct winctrl *c; + for (int i = 0; (c = winctrl_findbyindex(&pds->ctrltrees[0], i)) != NULL; + i++) { + if (c->ctrl) { + dlg_set_focus(c->ctrl, pds->dp); + break; + } + } + + /* + * Now we've finished creating our initial set of controls, + * it's safe to actually show the window without risking setup + * flicker. + */ + ShowWindow(pds->dp->hwnd, SW_SHOWNORMAL); + + pds->initialised = true; +} #define LOGEVENT_INITIAL_MAX 128 #define LOGEVENT_CIRCULAR_MAX 128 @@ -304,41 +473,14 @@ static HTREEITEM treeview_insert(struct treeview_faff *faff, 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); - } -} - const char *dialog_box_demo_screenshot_filename = NULL; +/* ctrltrees indices for the main dialog box */ +enum { + TREE_PANEL, /* things we swap out every time treeview selects a new pane */ + TREE_BASE, /* fixed things at the bottom like OK and Cancel buttons */ +}; + /* * This function is the configuration box. * (Being a dialog procedure, in general it returns 0 if the default @@ -347,26 +489,17 @@ const char *dialog_box_demo_screenshot_filename = NULL; static INT_PTR GenericMainDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, void *ctx) { + PortableDialogStuff *pds = (PortableDialogStuff *)ctx; const int DEMO_SCREENSHOT_TIMER_ID = 1230; HWND 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); - } + pds_initdialog_start(pds, hwnd); + + pds_create_controls(pds, TREE_BASE, IDCX_STDBASE, 3, 3, 235, ""); + SendMessage(hwnd, WM_SETICON, (WPARAM) ICON_BIG, (LPARAM) LoadIcon(hinst, MAKEINTRESOURCE(IDI_CFGICON))); @@ -423,8 +556,8 @@ static INT_PTR GenericMainDlgProc(HWND hwnd, UINT msg, WPARAM wParam, char *path = NULL; char *firstpath = NULL; - for (i = 0; i < ctrlbox->nctrlsets; i++) { - struct controlset *s = ctrlbox->ctrlsets[i]; + for (i = 0; i < pds->ctrlbox->nctrlsets; i++) { + struct controlset *s = pds->ctrlbox->ctrlsets[i]; HTREEITEM item; int j; char *c; @@ -473,43 +606,17 @@ static INT_PTR GenericMainDlgProc(HWND hwnd, UINT msg, WPARAM wParam, * match the initial treeview selection. */ assert(firstpath); /* config.c must have given us _something_ */ - create_controls(hwnd, firstpath); - dlg_refresh(NULL, &dp); /* and set up control values */ - } - - /* - * 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; - } - } + pds_create_controls(pds, TREE_PANEL, IDCX_PANELBASE, + 100, 3, 13, firstpath); + dlg_refresh(NULL, pds->dp); /* and set up control values */ } - /* - * Now we've finished creating our initial set of controls, - * it's safe to actually show the window without risking setup - * flicker. - */ - ShowWindow(hwnd, SW_SHOWNORMAL); - - /* - * Set the flag that activates a couple of the other message - * handlers below, which were disabled until now to avoid - * spurious firing during the above setup procedure. - */ - SetWindowLongPtr(hwnd, GWLP_USERDATA, 1); - if (dialog_box_demo_screenshot_filename) SetTimer(hwnd, DEMO_SCREENSHOT_TIMER_ID, TICKSPERSEC, NULL); + + pds_initdialog_finish(pds); return 0; + case WM_TIMER: if (dialog_box_demo_screenshot_filename && (UINT_PTR)wParam == DEMO_SCREENSHOT_TIMER_ID) { @@ -522,15 +629,7 @@ static INT_PTR GenericMainDlgProc(HWND hwnd, UINT msg, WPARAM wParam, ShinyEndDialog(hwnd, 0); } 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) - ShinyEndDialog(hwnd, dp.endresult ? 1 : 0); - break; + case WM_NOTIFY: if (LOWORD(wParam) == IDCX_TREEVIEW && ((LPNMHDR) lParam)->code == TVN_SELCHANGED) { @@ -545,7 +644,7 @@ static INT_PTR GenericMainDlgProc(HWND hwnd, UINT msg, WPARAM wParam, TVITEM item; char buffer[64]; - if (GetWindowLongPtr(hwnd, GWLP_USERDATA) != 1) + if (!pds->initialised) return 0; i = TreeView_GetSelection(((LPNMHDR) lParam)->hwndFrom); @@ -563,60 +662,34 @@ static INT_PTR GenericMainDlgProc(HWND hwnd, UINT msg, WPARAM wParam, HWND item; struct winctrl *c; - while ((c = winctrl_findbyindex(&ctrls_panel, 0)) != NULL) { + while ((c = winctrl_findbyindex( + &pds->ctrltrees[TREE_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); + winctrl_rem_shortcuts(pds->dp, c); + winctrl_remove(&pds->ctrltrees[TREE_PANEL], c); sfree(c->data); sfree(c); } } - create_controls(hwnd, (char *)item.lParam); + pds_create_controls(pds, TREE_PANEL, IDCX_PANELBASE, + 100, 3, 13, (char *)item.lParam); - dlg_refresh(NULL, &dp); /* set up control values */ + dlg_refresh(NULL, pds->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) - ShinyEndDialog(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); - ShinyEndDialog(hwnd, 0); - return 0; - - /* Grrr Explorer will maximize Dialogs! */ - case WM_SIZE: - if (wParam == SIZE_MAXIMIZED) - force_normal(hwnd); return 0; + default: + return pds_default_dlgproc(pds, hwnd, msg, wParam, lParam); } - return 0; } void modal_about_box(HWND hwnd) @@ -652,28 +725,22 @@ void defuse_showwindow(void) bool do_config(Conf *conf) { bool ret; + PortableDialogStuff *pds = pds_new(2); + + setup_config_box(pds->ctrlbox, false, 0, 0); + win_setup_config_box(pds->ctrlbox, &pds->dp->hwnd, has_help(), false, 0); - 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 = conf; - dlg_auto_set_fixed_pitch_flag(&dp); - dp.shortcuts['g'] = true; /* the treeview: `Cate&gory' */ + pds->dp->wintitle = dupprintf("%s Configuration", appname); + pds->dp->data = conf; + + dlg_auto_set_fixed_pitch_flag(pds->dp); + + pds->dp->shortcuts['g'] = true; /* the treeview: `Cate&gory' */ ret = ShinyDialogBox(hinst, MAKEINTRESOURCE(IDD_MAINBOX), "PuTTYConfigBox", - NULL, GenericMainDlgProc, NULL); + NULL, GenericMainDlgProc, pds); - ctrl_free_box(ctrlbox); - winctrl_cleanup(&ctrls_panel); - winctrl_cleanup(&ctrls_base); - dp_cleanup(&dp); + pds_free(pds); return ret; } @@ -683,31 +750,26 @@ bool do_reconfig(HWND hwnd, Conf *conf, int protcfginfo) Conf *backup_conf; bool ret; int protocol; + PortableDialogStuff *pds = pds_new(2); backup_conf = conf_copy(conf); - ctrlbox = ctrl_new_box(); protocol = conf_get_int(conf, CONF_protocol); - setup_config_box(ctrlbox, true, protocol, protcfginfo); - win_setup_config_box(ctrlbox, &dp.hwnd, has_help(), true, protocol); - dp_init(&dp); - winctrl_init(&ctrls_base); - winctrl_init(&ctrls_panel); - dp_add_tree(&dp, &ctrls_base); - dp_add_tree(&dp, &ctrls_panel); - dp.wintitle = dupprintf("%s Reconfiguration", appname); - dp.errtitle = dupprintf("%s Error", appname); - dp.data = conf; - dlg_auto_set_fixed_pitch_flag(&dp); - dp.shortcuts['g'] = true; /* the treeview: `Cate&gory' */ + setup_config_box(pds->ctrlbox, true, protocol, protcfginfo); + win_setup_config_box(pds->ctrlbox, &pds->dp->hwnd, has_help(), + true, protocol); + + pds->dp->wintitle = dupprintf("%s Reconfiguration", appname); + pds->dp->data = conf; + + dlg_auto_set_fixed_pitch_flag(pds->dp); + + pds->dp->shortcuts['g'] = true; /* the treeview: `Cate&gory' */ ret = ShinyDialogBox(hinst, MAKEINTRESOURCE(IDD_MAINBOX), "PuTTYConfigBox", - NULL, GenericMainDlgProc, NULL); + NULL, GenericMainDlgProc, pds); - ctrl_free_box(ctrlbox); - winctrl_cleanup(&ctrls_base); - winctrl_cleanup(&ctrls_panel); - dp_cleanup(&dp); + pds_free(pds); if (!ret) conf_copy_into(conf, backup_conf); -- cgit v1.2.3 From 043c24844a45a209697383e6847f35872232a287 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 22 Apr 2022 11:22:56 +0100 Subject: Improve the base64 utility functions. The low-level functions to handle a single atom of base64 at a time have been in 'utils' / misc.h for ages, but the higher-level family of base64_encode functions that handle a whole data block were hidden away in sshpubk.c, and there was no higher-level decode function at all. Now moved both into 'utils' modules and declared them in misc.h rather than ssh.h. Also, improved the APIs: they all take ptrlen in place of separate data and length arguments, their naming is more consistent and more explicit (the previous base64_encode which didn't name its destination is now base64_encode_fp), and the encode functions now accept cpl == 0 as a special case meaning that the output base64 data is wanted in the form of an unbroken single-line string with no trailing \n. --- import.c | 6 +++--- misc.h | 6 ++++++ ssh.h | 4 ---- sshpubk.c | 37 +++---------------------------------- utils/CMakeLists.txt | 2 ++ utils/base64_decode.c | 37 +++++++++++++++++++++++++++++++++++++ utils/base64_encode.c | 40 ++++++++++++++++++++++++++++++++++++++++ 7 files changed, 91 insertions(+), 41 deletions(-) create mode 100644 utils/base64_decode.c create mode 100644 utils/base64_encode.c diff --git a/import.c b/import.c index 41c06a9a..28e70b8a 100644 --- a/import.c +++ b/import.c @@ -1075,7 +1075,7 @@ static bool openssh_pem_write( fprintf(fp, "%02X", iv[i]); fprintf(fp, "\n\n"); } - base64_encode(fp, outblob->u, outblob->len, 64); + base64_encode_fp(fp, ptrlen_from_strbuf(outblob), 64); fputs(footer, fp); fclose(fp); ret = true; @@ -1606,7 +1606,7 @@ static bool openssh_new_write( if (!fp) goto error; fputs("-----BEGIN OPENSSH PRIVATE KEY-----\n", fp); - base64_encode(fp, cblob->u, cblob->len, 64); + base64_encode_fp(fp, ptrlen_from_strbuf(cblob), 64); fputs("-----END OPENSSH PRIVATE KEY-----\n", fp); fclose(fp); ret = true; @@ -2308,7 +2308,7 @@ static bool sshcom_write( } fprintf(fp, "%s\"\n", c); } - base64_encode(fp, outblob->u, outblob->len, 70); + base64_encode_fp(fp, ptrlen_from_strbuf(outblob), 70); fputs("---- END SSH2 ENCRYPTED PRIVATE KEY ----\n", fp); fclose(fp); ret = true; diff --git a/misc.h b/misc.h index a2dee9ac..eeed80c1 100644 --- a/misc.h +++ b/misc.h @@ -108,6 +108,12 @@ bool strendswith(const char *s, const char *t); void base64_encode_atom(const unsigned char *data, int n, char *out); int base64_decode_atom(const char *atom, unsigned char *out); +void base64_decode_bs(BinarySink *bs, ptrlen data); +void base64_decode_fp(FILE *fp, ptrlen data); +strbuf *base64_decode_sb(ptrlen data); +void base64_encode_bs(BinarySink *bs, ptrlen data, int cpl); +void base64_encode_fp(FILE *fp, ptrlen data, int cpl); +strbuf *base64_encode_sb(ptrlen data, int cpl); struct bufchain_granule; struct bufchain_tag { diff --git a/ssh.h b/ssh.h index b703945f..3465c196 100644 --- a/ssh.h +++ b/ssh.h @@ -1290,11 +1290,7 @@ static inline bool is_base64_char(char c) c == '+' || c == '/' || c == '='); } -extern int base64_decode_atom(const char *atom, unsigned char *out); extern int base64_lines(int datalen); -extern void base64_encode_atom(const unsigned char *data, int n, char *out); -extern void base64_encode(FILE *fp, const unsigned char *data, int datalen, - int cpl); /* ppk_load_* can return this as an error */ extern ssh2_userkey ssh2_wrong_passphrase; diff --git a/sshpubk.c b/sshpubk.c index 4b332778..2755df0f 100644 --- a/sshpubk.c +++ b/sshpubk.c @@ -1391,37 +1391,6 @@ int base64_lines(int datalen) return (datalen + 47) / 48; } -static void base64_encode_s(BinarySink *bs, const 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; - put_byte(bs, '\n'); - } - put_byte(bs, out[i]); - linelen++; - } - } - put_byte(bs, '\n'); -} - -void base64_encode(FILE *fp, const unsigned char *data, int datalen, int cpl) -{ - stdio_sink ss; - stdio_sink_init(&ss, fp); - base64_encode_s(BinarySink_UPCAST(&ss), data, datalen, cpl); -} - const ppk_save_parameters ppk_save_default_parameters = { .fmt_version = 3, @@ -1566,7 +1535,7 @@ strbuf *ppk_save_sb(ssh2_userkey *key, const char *passphrase, put_fmt(out, "Encryption: %s\n", cipherstr); put_fmt(out, "Comment: %s\n", key->comment); put_fmt(out, "Public-Lines: %d\n", base64_lines(pub_blob->len)); - base64_encode_s(BinarySink_UPCAST(out), pub_blob->u, pub_blob->len, 64); + base64_encode_bs(BinarySink_UPCAST(out), ptrlen_from_strbuf(pub_blob), 64); if (params.fmt_version == 3 && ciphertype->keylen != 0) { put_fmt(out, "Key-Derivation: %s\n", params.argon2_flavour == Argon2d ? "Argon2d" : @@ -1582,8 +1551,8 @@ strbuf *ppk_save_sb(ssh2_userkey *key, const char *passphrase, put_fmt(out, "\n"); } put_fmt(out, "Private-Lines: %d\n", base64_lines(priv_encrypted_len)); - base64_encode_s(BinarySink_UPCAST(out), - priv_blob_encrypted, priv_encrypted_len, 64); + base64_encode_bs(BinarySink_UPCAST(out), + make_ptrlen(priv_blob_encrypted, priv_encrypted_len), 64); put_fmt(out, "Private-MAC: "); for (i = 0; i < macalg->len; i++) put_fmt(out, "%02x", priv_mac[i]); diff --git a/utils/CMakeLists.txt b/utils/CMakeLists.txt index 9e995e04..74ce4d54 100644 --- a/utils/CMakeLists.txt +++ b/utils/CMakeLists.txt @@ -2,7 +2,9 @@ add_sources_from_current_dir(utils antispoof.c backend_socket_log.c base64_decode_atom.c + base64_decode.c base64_encode_atom.c + base64_encode.c bufchain.c buildinfo.c burnstr.c diff --git a/utils/base64_decode.c b/utils/base64_decode.c new file mode 100644 index 00000000..4f00f196 --- /dev/null +++ b/utils/base64_decode.c @@ -0,0 +1,37 @@ +#include "misc.h" + +void base64_decode_bs(BinarySink *bs, ptrlen input) +{ + BinarySource src[1]; + BinarySource_BARE_INIT_PL(src, input); + + while (get_avail(src)) { + char b64atom[4]; + unsigned char binatom[3]; + + for (size_t i = 0; i < 4 ;) { + char c = get_byte(src); + if (get_err(src)) + c = '='; + if (c == '\n' || c == '\r') + continue; + b64atom[i++] = c; + } + + put_data(bs, binatom, base64_decode_atom(b64atom, binatom)); + } +} + +void base64_decode_fp(FILE *fp, ptrlen input) +{ + stdio_sink ss; + stdio_sink_init(&ss, fp); + base64_decode_bs(BinarySink_UPCAST(&ss), input); +} + +strbuf *base64_decode_sb(ptrlen input) +{ + strbuf *sb = strbuf_new_nm(); + base64_decode_bs(BinarySink_UPCAST(sb), input); + return sb; +} diff --git a/utils/base64_encode.c b/utils/base64_encode.c new file mode 100644 index 00000000..3db85571 --- /dev/null +++ b/utils/base64_encode.c @@ -0,0 +1,40 @@ +#include "misc.h" + +void base64_encode_bs(BinarySink *bs, ptrlen input, int cpl) +{ + BinarySource src[1]; + BinarySource_BARE_INIT_PL(src, input); + int linelen = 0; + + while (get_avail(src)) { + size_t n = get_avail(src) < 3 ? get_avail(src) : 3; + ptrlen binatom = get_data(src, n); + + char b64atom[4]; + base64_encode_atom(binatom.ptr, binatom.len, b64atom); + for (size_t i = 0; i < 4; i++) { + if (cpl > 0 && linelen >= cpl) { + linelen = 0; + put_byte(bs, '\n'); + } + put_byte(bs, b64atom[i]); + linelen++; + } + } + if (cpl > 0) + put_byte(bs, '\n'); +} + +void base64_encode_fp(FILE *fp, ptrlen input, int cpl) +{ + stdio_sink ss; + stdio_sink_init(&ss, fp); + base64_encode_bs(BinarySink_UPCAST(&ss), input, cpl); +} + +strbuf *base64_encode_sb(ptrlen input, int cpl) +{ + strbuf *sb = strbuf_new_nm(); + base64_encode_bs(BinarySink_UPCAST(sb), input, cpl); + return sb; +} -- cgit v1.2.3 From 2a26ebd0d58a4adb6d5f2c3fde0db01525837d74 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 22 Apr 2022 14:55:44 +0100 Subject: Turn the proxy type radio buttons into a dropdown list. This makes room to add more entries without the Proxy panel overflowing. It also means we can put in a bit more explanation in some of the more cryptic one-word names! --- config.c | 76 +++++++++++++++++++++++++++++++++++++++--------------- putty.h | 7 +++++ unix/config-unix.c | 29 +++------------------ windows/config.c | 29 +++------------------ 4 files changed, 68 insertions(+), 73 deletions(-) diff --git a/config.c b/config.c index 1fbbb3e8..7b96c7b4 100644 --- a/config.c +++ b/config.c @@ -1708,6 +1708,58 @@ static void serial_flow_handler(union control *ctrl, dlgparam *dlg, } } +void proxy_type_handler(union control *ctrl, dlgparam *dlg, + void *data, int event) +{ + Conf *conf = (Conf *)data; + if (event == EVENT_REFRESH) { + /* + * We must fetch the previously configured value from the Conf + * before we start modifying the drop-down list, otherwise the + * spurious SELCHANGE we trigger in the process will overwrite + * the value we wanted to keep. + */ + int proxy_type = conf_get_int(conf, CONF_proxy_type); + + dlg_update_start(ctrl, dlg); + dlg_listbox_clear(ctrl, dlg); + + int index_to_select = 0, current_index = 0; + +#define ADD(id, title) do { \ + dlg_listbox_addwithid(ctrl, dlg, title, id); \ + if (id == proxy_type) \ + index_to_select = current_index; \ + current_index++; \ + } while (0) + + ADD(PROXY_NONE, "None"); + ADD(PROXY_SOCKS5, "SOCKS 5"); + ADD(PROXY_SOCKS4, "SOCKS 4"); + ADD(PROXY_HTTP, "HTTP CONNECT"); + if (ssh_proxy_supported) { + ADD(PROXY_SSH, "SSH to proxy and use port forwarding"); + } + if (ctrl->generic.context.i & PROXY_UI_FLAG_LOCAL) { + ADD(PROXY_CMD, "Local (run a subprogram to connect)"); + } + ADD(PROXY_TELNET, "'Telnet' (send an ad-hoc command)"); + +#undef ADD + + dlg_listbox_select(ctrl, dlg, index_to_select); + + 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); + conf_set_int(conf, CONF_proxy_type, i); + } +} + void setup_config_box(struct controlbox *b, bool midsession, int protocol, int protcfginfo) { @@ -2522,26 +2574,8 @@ void setup_config_box(struct controlbox *b, bool midsession, "Options controlling proxy usage"); s = ctrl_getset(b, "Connection/Proxy", "basics", NULL); - c = ctrl_radiobuttons(s, "Proxy type:", 't', 3, - HELPCTX(proxy_type), - conf_radiobutton_handler, - I(CONF_proxy_type), - "None", I(PROXY_NONE), - "SOCKS 4", I(PROXY_SOCKS4), - "SOCKS 5", I(PROXY_SOCKS5), - "HTTP", I(PROXY_HTTP), - "Telnet", I(PROXY_TELNET), - NULL); - if (ssh_proxy_supported) { - /* Add an extra radio button to the above list. */ - c->radio.nbuttons++; - c->radio.buttons = - sresize(c->radio.buttons, c->radio.nbuttons, char *); - c->radio.buttons[c->radio.nbuttons-1] = dupstr("SSH"); - c->radio.buttondata = - sresize(c->radio.buttondata, c->radio.nbuttons, intorptr); - c->radio.buttondata[c->radio.nbuttons-1] = I(PROXY_SSH); - } + c = ctrl_droplist(s, "Proxy type:", 't', 70, + HELPCTX(proxy_type), proxy_type_handler, I(0)); ctrl_columns(s, 2, 80, 20); c = ctrl_editbox(s, "Proxy hostname", 'y', 100, HELPCTX(proxy_main), @@ -2579,7 +2613,7 @@ void setup_config_box(struct controlbox *b, bool midsession, conf_editbox_handler, I(CONF_proxy_password), I(1)); c->editbox.password = true; - ctrl_editbox(s, "Telnet command", 'm', 100, + ctrl_editbox(s, "Command to send to proxy (for some types)", 'm', 100, HELPCTX(proxy_command), conf_editbox_handler, I(CONF_proxy_telnet_command), I(1)); diff --git a/putty.h b/putty.h index 84a7df31..c378da1e 100644 --- a/putty.h +++ b/putty.h @@ -2584,6 +2584,13 @@ void conf_fontsel_handler(union control *ctrl, dlgparam *dlg, void setup_config_box(struct controlbox *b, bool midsession, int protocol, int protcfginfo); +/* Visible outside config.c so that platforms can use it to recognise + * the proxy type control */ +void proxy_type_handler(union control *ctrl, dlgparam *dlg, + void *data, int event); +/* And then they'll set this flag in its generic.context.i */ +#define PROXY_UI_FLAG_LOCAL 1 /* has a local proxy */ + /* * Exports from bidi.c. */ diff --git a/unix/config-unix.c b/unix/config-unix.c index ee1b953d..349a643f 100644 --- a/unix/config-unix.c +++ b/unix/config-unix.c @@ -32,8 +32,7 @@ void unix_setup_config_box(struct controlbox *b, bool midsession, int protocol) s->ctrls[0]->editbox.has_list = false; /* - * Unix supports a local-command proxy. This also means we must - * adjust the text on the `Telnet command' control. + * Unix supports a local-command proxy. */ if (!midsession) { int i; @@ -41,30 +40,8 @@ void unix_setup_config_box(struct controlbox *b, bool midsession, int protocol) for (i = 0; i < s->ncontrols; i++) { c = s->ctrls[i]; if (c->generic.type == CTRL_RADIO && - c->generic.context.i == CONF_proxy_type) { - assert(c->generic.handler == conf_radiobutton_handler); - c->radio.nbuttons++; - c->radio.buttons = - sresize(c->radio.buttons, c->radio.nbuttons, char *); - c->radio.buttons[c->radio.nbuttons-1] = - dupstr("Local"); - c->radio.buttondata = - sresize(c->radio.buttondata, c->radio.nbuttons, intorptr); - c->radio.buttondata[c->radio.nbuttons-1] = I(PROXY_CMD); - if (c->radio.ncolumns < 4) - c->radio.ncolumns = 4; - break; - } - } - - for (i = 0; i < s->ncontrols; i++) { - c = s->ctrls[i]; - if (c->generic.type == CTRL_EDITBOX && - c->generic.context.i == CONF_proxy_telnet_command) { - assert(c->generic.handler == conf_editbox_handler); - sfree(c->generic.label); - c->generic.label = dupstr("Telnet command, or local" - " proxy command"); + c->generic.handler == proxy_type_handler) { + c->generic.context.i |= PROXY_UI_FLAG_LOCAL; break; } } diff --git a/windows/config.c b/windows/config.c index 9c107edd..bcac905a 100644 --- a/windows/config.c +++ b/windows/config.c @@ -355,8 +355,7 @@ void win_setup_config_box(struct controlbox *b, HWND *hwndp, bool has_help, I(CONF_fullscreenonaltenter)); /* - * Windows supports a local-command proxy. This also means we - * must adjust the text on the `Telnet command' control. + * Windows supports a local-command proxy. */ if (!midsession) { int i; @@ -364,30 +363,8 @@ void win_setup_config_box(struct controlbox *b, HWND *hwndp, bool has_help, for (i = 0; i < s->ncontrols; i++) { c = s->ctrls[i]; if (c->generic.type == CTRL_RADIO && - c->generic.context.i == CONF_proxy_type) { - assert(c->generic.handler == conf_radiobutton_handler); - c->radio.nbuttons++; - c->radio.buttons = - sresize(c->radio.buttons, c->radio.nbuttons, char *); - c->radio.buttons[c->radio.nbuttons-1] = - dupstr("Local"); - c->radio.buttondata = - sresize(c->radio.buttondata, c->radio.nbuttons, intorptr); - c->radio.buttondata[c->radio.nbuttons-1] = I(PROXY_CMD); - if (c->radio.ncolumns < 4) - c->radio.ncolumns = 4; - break; - } - } - - for (i = 0; i < s->ncontrols; i++) { - c = s->ctrls[i]; - if (c->generic.type == CTRL_EDITBOX && - c->generic.context.i == CONF_proxy_telnet_command) { - assert(c->generic.handler == conf_editbox_handler); - sfree(c->generic.label); - c->generic.label = dupstr("Telnet command, or local" - " proxy command"); + c->generic.handler == proxy_type_handler) { + c->generic.context.i |= PROXY_UI_FLAG_LOCAL; break; } } -- cgit v1.2.3 From 6f7c52dccee36f6593a3fa4498ce1cc5d9952c66 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 22 Apr 2022 14:12:15 +0100 Subject: Add exec/subsystem versions of SSH proxying. This is a simple tweak to the existing in-process SSH jump host support, where instead of opening a direct-tcpip channel to the destination host, we open a session channel and run a process in it to make the connection to the destination. So, where the existing jump host support replaced a local proxy command along the lines of "plink %proxyhost -nc %host %port", this one replaces "plink %proxyhost run-some-command". Also added a corresponding option to use a subsystem to make the connection. (Someone could configure an SSH server to support specific subsystem names for particular destinations, or a general schema of subsystem names that include the destination address in some standard format.) To avoid overflowing the already-full Proxy config panel with an extra subtype selector, I've put these in as additional top-level proxy types, so that instead of just PROXY_SSH we now have three PROXY_SSH_foo. --- config.c | 4 +++- proxy/proxy.c | 4 +++- proxy/sshproxy.c | 48 ++++++++++++++++++++++++++++++++++++++++++------ putty.h | 3 ++- 4 files changed, 50 insertions(+), 9 deletions(-) diff --git a/config.c b/config.c index 7b96c7b4..f48e6de7 100644 --- a/config.c +++ b/config.c @@ -1738,7 +1738,9 @@ void proxy_type_handler(union control *ctrl, dlgparam *dlg, ADD(PROXY_SOCKS4, "SOCKS 4"); ADD(PROXY_HTTP, "HTTP CONNECT"); if (ssh_proxy_supported) { - ADD(PROXY_SSH, "SSH to proxy and use port forwarding"); + ADD(PROXY_SSH_TCPIP, "SSH to proxy and use port forwarding"); + ADD(PROXY_SSH_EXEC, "SSH to proxy and execute a command"); + ADD(PROXY_SSH_SUBSYSTEM, "SSH to proxy and invoke a subsystem"); } if (ctrl->generic.context.i & PROXY_UI_FLAG_LOCAL) { ADD(PROXY_CMD, "Local (run a subprogram to connect)"); diff --git a/proxy/proxy.c b/proxy/proxy.c index 6bedcf9b..7e40dfc6 100644 --- a/proxy/proxy.c +++ b/proxy/proxy.c @@ -499,7 +499,9 @@ Socket *new_connection(SockAddr *addr, const char *hostname, char *proxy_canonical_name; Socket *sret; - if (type == PROXY_SSH && + if ((type == PROXY_SSH_TCPIP || + type == PROXY_SSH_EXEC || + type == PROXY_SSH_SUBSYSTEM) && (sret = sshproxy_new_connection(addr, hostname, port, privport, oobinline, nodelay, keepalive, plug, conf, itr)) != NULL) diff --git a/proxy/sshproxy.c b/proxy/sshproxy.c index 95daecb1..057cd4b6 100644 --- a/proxy/sshproxy.c +++ b/proxy/sshproxy.c @@ -10,6 +10,7 @@ #include "ssh.h" #include "network.h" #include "storage.h" +#include "proxy.h" const bool ssh_proxy_supported = true; @@ -636,12 +637,47 @@ Socket *sshproxy_new_connection(SockAddr *addr, const char *hostname, */ conf_set_bool(sp->conf, CONF_ssh_simple, true); - /* - * Configure the main channel of this SSH session to be a - * direct-tcpip connection to the destination host/port. - */ - conf_set_str(sp->conf, CONF_ssh_nc_host, hostname); - conf_set_int(sp->conf, CONF_ssh_nc_port, port); + int proxy_type = conf_get_int(clientconf, CONF_proxy_type); + switch (proxy_type) { + case PROXY_SSH_TCPIP: + /* + * Configure the main channel of this SSH session to be a + * direct-tcpip connection to the destination host/port. + */ + conf_set_str(sp->conf, CONF_ssh_nc_host, hostname); + conf_set_int(sp->conf, CONF_ssh_nc_port, port); + break; + + case PROXY_SSH_SUBSYSTEM: + case PROXY_SSH_EXEC: { + Conf *cmd_conf = conf_copy(clientconf); + + /* + * Unlike the Telnet and Local proxy types, we don't use the + * proxy username and password fields in the formatted + * command, because if we use them at all, it's for + * authenticating to the proxy SSH server. + */ + conf_set_str(cmd_conf, CONF_proxy_username, ""); + conf_set_str(cmd_conf, CONF_proxy_password, ""); + + char *cmd = format_telnet_command(sp->addr, sp->port, cmd_conf, NULL); + conf_free(cmd_conf); + + conf_set_str(sp->conf, CONF_remote_cmd, cmd); + sfree(cmd); + + conf_set_bool(sp->conf, CONF_nopty, true); + + if (proxy_type == PROXY_SSH_SUBSYSTEM) + conf_set_bool(sp->conf, CONF_ssh_subsys, true); + + break; + } + + default: + unreachable("bad SSH proxy type"); + } sp->logctx = log_init(&sp->logpolicy, sp->conf); diff --git a/putty.h b/putty.h index c378da1e..a7d7c002 100644 --- a/putty.h +++ b/putty.h @@ -475,7 +475,8 @@ enum { * Proxy types. */ PROXY_NONE, PROXY_SOCKS4, PROXY_SOCKS5, - PROXY_HTTP, PROXY_TELNET, PROXY_CMD, PROXY_SSH, + PROXY_HTTP, PROXY_TELNET, PROXY_CMD, PROXY_SSH_TCPIP, + PROXY_SSH_EXEC, PROXY_SSH_SUBSYSTEM, PROXY_FUZZ }; -- cgit v1.2.3 From 34d01e1b654d8c42ebd7f8abc05f5c2693f2a6a1 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Tue, 19 Apr 2022 14:48:31 +0100 Subject: Family of key types for OpenSSH certificates. This commit is groundwork for full certificate support, but doesn't complete the job by itself. It introduces the new key types, and adds a test in cryptsuite ensuring they work as expected, but nothing else. If you manually construct a PPK file for one of the new key types, so that it has a certificate in the public key field, then this commit enables PuTTY to present that key to a server for user authentication, either directly or via Pageant storing and using it. But I haven't yet provided any mechanism for making such a PPK, so by itself, this isn't much use. Also, these new key types are not yet included in the KEXINIT host keys list, because if they were, they'd just be treated as normal host keys, in that you'd be asked to manually confirm the SSH fingerprint of the certificate. I'll enable them for host keys once I add the missing pieces. --- crypto/CMakeLists.txt | 1 + crypto/openssh-certs.c | 660 +++++++++++++++++++++++++++++++++++++++++++++++++ ssh.h | 10 + sshpubk.c | 8 + test/testcrypt-enum.h | 6 + 5 files changed, 685 insertions(+) create mode 100644 crypto/openssh-certs.c diff --git a/crypto/CMakeLists.txt b/crypto/CMakeLists.txt index 71083107..08de141e 100644 --- a/crypto/CMakeLists.txt +++ b/crypto/CMakeLists.txt @@ -21,6 +21,7 @@ add_sources_from_current_dir(crypto md5.c mpint.c ntru.c + openssh-certs.c prng.c pubkey-pem.c pubkey-ppk.c diff --git a/crypto/openssh-certs.c b/crypto/openssh-certs.c new file mode 100644 index 00000000..bec7bca3 --- /dev/null +++ b/crypto/openssh-certs.c @@ -0,0 +1,660 @@ +/* + * Public key type for OpenSSH certificates. + */ + +#include "ssh.h" + +enum { + SSH_CERT_TYPE_USER = 1, + SSH_CERT_TYPE_HOST = 2, +}; + +typedef struct opensshcert_key { + strbuf *nonce; + uint64_t serial; + uint32_t type; + strbuf *key_id; + strbuf *valid_principals; + uint64_t valid_after, valid_before; + strbuf *critical_options; + strbuf *extensions; + strbuf *reserved; + strbuf *signature_key; + strbuf *signature; + + ssh_key *basekey; + + ssh_key sshk; +} opensshcert_key; + +typedef struct blob_fmt { + const unsigned *fmt; + size_t len; +} blob_fmt; + +typedef struct opensshcert_extra { + /* + * OpenSSH certificate formats aren't completely consistent about + * the relationship between the public+private blob uploaded to + * the agent for the certified key type, and the one for the base + * key type. Here we specify the mapping. + * + * Each of these foo_fmt strings indicates the layout of a + * particular version of the key, in the form of an array of + * integers together with a length, with each integer describing + * one of the components of the key. The integers are defined by + * enums, so that they're tightly packed; the general idea is that + * if you're converting from one form to another, then you use the + * format list for the source format to read out a succession of + * SSH strings from the source data and put them in an array + * indexed by the integer ids, and then use the list for the + * destination format to write the strings out to the destination + * in the right (maybe different) order. + * + * pub_fmt describes the format of the public-key blob for the + * base key type, not counting the initial string giving the key + * type identifier itself. As far as I know, this always matches + * the format of the public-key data appearing in the middle of + * the certificate. + * + * base_ossh_fmt describes the format of the full OpenSSH blob + * appearing in the ssh-agent protocol for the base key, + * containing the public and private key data. + * + * cert_ossh_fmt describes the format of the OpenSSH blob for the + * certificate key format, beginning just after the certificate + * string itself. + */ + blob_fmt pub_fmt, base_ossh_fmt, cert_ossh_fmt; +} opensshcert_extra; + +/* + * The actual integer arrays defining the per-key blob formats. + */ + +/* DSA is the most orthodox: only the obviously necessary public key + * info appears at all, it's in the same order everywhere, and none of + * it is repeated unnecessarily */ +enum { DSA_p, DSA_q, DSA_g, DSA_y, DSA_x }; +const unsigned dsa_pub_fmt[] = { DSA_p, DSA_q, DSA_g, DSA_y }; +const unsigned dsa_base_ossh_fmt[] = { DSA_p, DSA_q, DSA_g, DSA_y, DSA_x }; +const unsigned dsa_cert_ossh_fmt[] = { DSA_x }; + +/* ECDSA is almost as nice, except that it pointlessly mentions the + * curve name in the public data, which shouldn't be necessary given + * that the SSH key id has already implied it. But at least that's + * consistent everywhere. */ +enum { ECDSA_curve, ECDSA_point, ECDSA_exp }; +const unsigned ecdsa_pub_fmt[] = { ECDSA_curve, ECDSA_point }; +const unsigned ecdsa_base_ossh_fmt[] = { ECDSA_curve, ECDSA_point, ECDSA_exp }; +const unsigned ecdsa_cert_ossh_fmt[] = { ECDSA_exp }; + +/* Ed25519 has the oddity that the private data following the + * certificate in the OpenSSH blob is preceded by an extra copy of the + * public data, for no obviously necessary reason since that doesn't + * happen in any of the rest of these formats */ +enum { EDDSA_point, EDDSA_exp }; +const unsigned eddsa_pub_fmt[] = { EDDSA_point }; +const unsigned eddsa_base_ossh_fmt[] = { EDDSA_point, EDDSA_exp }; +const unsigned eddsa_cert_ossh_fmt[] = { EDDSA_point, EDDSA_exp }; + +/* And RSA has the quirk that the modulus and exponent are reversed in + * the base key type's OpenSSH blob! */ +enum { RSA_e, RSA_n, RSA_d, RSA_p, RSA_q, RSA_iqmp }; +const unsigned rsa_pub_fmt[] = { RSA_e, RSA_n }; +const unsigned rsa_base_ossh_fmt[] = { + RSA_n, RSA_e, RSA_d, RSA_p, RSA_q, RSA_iqmp }; +const unsigned rsa_cert_ossh_fmt[] = { RSA_d, RSA_p, RSA_q, RSA_iqmp }; + +/* + * Routines to transform one kind of blob into another based on those + * foo_fmt integer arrays. + */ +typedef struct BlobTransformer { + ptrlen *parts; + size_t nparts; +} BlobTransformer; + +#define BLOBTRANS_DECLARE(bt) BlobTransformer bt[1] = { { NULL, 0 } } + +static inline void blobtrans_clear(BlobTransformer *bt) +{ + sfree(bt->parts); + bt->parts = NULL; + bt->nparts = 0; +} + +static inline bool blobtrans_read(BlobTransformer *bt, BinarySource *src, + blob_fmt blob) +{ + size_t nparts = bt->nparts; + for (size_t i = 0; i < blob.len; i++) + if (nparts < blob.fmt[i]+1) + nparts = blob.fmt[i]+1; + + if (nparts > bt->nparts) { + bt->parts = sresize(bt->parts, nparts, ptrlen); + while (bt->nparts < nparts) + bt->parts[bt->nparts++] = make_ptrlen(NULL, 0); + } + + for (size_t i = 0; i < blob.len; i++) { + size_t j = blob.fmt[i]; + ptrlen part = get_string(src); + if (bt->parts[j].ptr) { + /* + * If the same string appears in both the public blob and + * the private data, check they match. (This happens in + * Ed25519: an extra copy of the public point string + * appears in the certified OpenSSH data after the + * certificate and before the private key.) + */ + if (!ptrlen_eq_ptrlen(bt->parts[j], part)) + return false; + } + bt->parts[j] = part; + } + + return true; +} + +static inline void blobtrans_write(BlobTransformer *bt, BinarySink *bs, + blob_fmt blob) +{ + for (size_t i = 0; i < blob.len; i++) { + assert(i < bt->nparts); + ptrlen part = bt->parts[blob.fmt[i]]; + assert(part.ptr); + put_stringpl(bs, part); + } +} + +/* + * Forward declarations. + */ +static ssh_key *opensshcert_new_pub(const ssh_keyalg *self, ptrlen pub); +static ssh_key *opensshcert_new_priv( + const ssh_keyalg *self, ptrlen pub, ptrlen priv); +static ssh_key *opensshcert_new_priv_openssh( + const ssh_keyalg *self, BinarySource *src); +static void opensshcert_freekey(ssh_key *key); +static char *opensshcert_invalid(ssh_key *key, unsigned flags); +static void opensshcert_sign(ssh_key *key, ptrlen data, unsigned flags, + BinarySink *bs); +static bool opensshcert_verify(ssh_key *key, ptrlen sig, ptrlen data); +static void opensshcert_public_blob(ssh_key *key, BinarySink *bs); +static void opensshcert_private_blob(ssh_key *key, BinarySink *bs); +static void opensshcert_openssh_blob(ssh_key *key, BinarySink *bs); +static bool opensshcert_has_private(ssh_key *key); +static char *opensshcert_cache_str(ssh_key *key); +static key_components *opensshcert_components(ssh_key *key); +static int opensshcert_pubkey_bits(const ssh_keyalg *self, ptrlen blob); +static unsigned opensshcert_supported_flags(const ssh_keyalg *self); +static const char *opensshcert_alternate_ssh_id(const ssh_keyalg *self, + unsigned flags); + +/* + * Top-level vtables for the certified key formats, defined via a list + * macro so I can also make an array of them all. + */ + +#define KEYALG_LIST(X) \ + X(ssh_dsa, "ssh-dss", dsa) \ + X(ssh_rsa, "ssh-rsa", rsa) \ + X(ssh_rsa_sha256, "rsa-sha2-256", rsa) \ + X(ssh_rsa_sha512, "rsa-sha2-512", rsa) \ + X(ssh_ecdsa_ed25519, "ssh-ed25519", eddsa) \ + X(ssh_ecdsa_nistp256, "ecdsa-sha2-nistp256", ecdsa) \ + X(ssh_ecdsa_nistp384, "ecdsa-sha2-nistp384", ecdsa) \ + X(ssh_ecdsa_nistp521, "ecdsa-sha2-nistp521", ecdsa) \ + /* end of list */ + +#define KEYALG_DEF(name, ssh_id_prefix, fmt_prefix) \ + const struct opensshcert_extra opensshcert_##name##_extra = { \ + .pub_fmt = { .fmt = fmt_prefix ## _pub_fmt, \ + .len = lenof(fmt_prefix ## _pub_fmt) }, \ + .base_ossh_fmt = { .fmt = fmt_prefix ## _base_ossh_fmt, \ + .len = lenof(fmt_prefix ## _base_ossh_fmt) }, \ + .cert_ossh_fmt = { .fmt = fmt_prefix ## _cert_ossh_fmt, \ + .len = lenof(fmt_prefix ## _cert_ossh_fmt) }, \ + }; \ + \ + const ssh_keyalg opensshcert_##name = { \ + .new_pub = opensshcert_new_pub, \ + .new_priv = opensshcert_new_priv, \ + .new_priv_openssh = opensshcert_new_priv_openssh, \ + .freekey = opensshcert_freekey, \ + .invalid = opensshcert_invalid, \ + .sign = opensshcert_sign, \ + .verify = opensshcert_verify, \ + .public_blob = opensshcert_public_blob, \ + .private_blob = opensshcert_private_blob, \ + .openssh_blob = opensshcert_openssh_blob, \ + .has_private = opensshcert_has_private, \ + .cache_str = opensshcert_cache_str, \ + .components = opensshcert_components, \ + .pubkey_bits = opensshcert_pubkey_bits, \ + .supported_flags = opensshcert_supported_flags, \ + .alternate_ssh_id = opensshcert_alternate_ssh_id, \ + .ssh_id = ssh_id_prefix "-cert-v01@openssh.com", \ + .cache_id = NULL, \ + .extra = &opensshcert_##name##_extra, \ + .is_certificate = true, \ + .base_alg = &name, \ + }; +KEYALG_LIST(KEYALG_DEF) +#undef KEYALG_DEF + +#define KEYALG_LIST_ENTRY(name, ssh_id_prefix, fmt_prefix) &opensshcert_##name, +static const ssh_keyalg *const opensshcert_all_keyalgs[] = { + KEYALG_LIST(KEYALG_LIST_ENTRY) +}; +#undef KEYALG_LIST_ENTRY + +static opensshcert_key *opensshcert_new_shared( + const ssh_keyalg *self, ptrlen blob, strbuf **basepub_out) +{ + const opensshcert_extra *extra = self->extra; + + BinarySource src[1]; + BinarySource_BARE_INIT_PL(src, blob); + + /* Check the initial key-type string */ + if (!ptrlen_eq_string(get_string(src), self->ssh_id)) + return NULL; + + opensshcert_key *ck = snew(opensshcert_key); + memset(ck, 0, sizeof(*ck)); + ck->sshk.vt = self; + + ck->nonce = strbuf_dup(get_string(src)); + strbuf *basepub = strbuf_new(); + { + put_stringz(basepub, self->base_alg->ssh_id); + + /* Make the base public key blob out of the public key + * material in the certificate. This invocation of the + * blobtrans system doesn't do any format translation, but it + * does ensure that the right amount of data is copied so that + * src ends up in the right position to read the remaining + * certificate fields. */ + BLOBTRANS_DECLARE(bt); + blobtrans_read(bt, src, extra->pub_fmt); + blobtrans_write(bt, BinarySink_UPCAST(basepub), extra->pub_fmt); + blobtrans_clear(bt); + } + ck->serial = get_uint64(src); + ck->type = get_uint32(src); + ck->key_id = strbuf_dup(get_string(src)); + ck->valid_principals = strbuf_dup(get_string(src)); + ck->valid_after = get_uint64(src); + ck->valid_before = get_uint64(src); + ck->critical_options = strbuf_dup(get_string(src)); + ck->extensions = strbuf_dup(get_string(src)); + ck->reserved = strbuf_dup(get_string(src)); + ck->signature_key = strbuf_dup(get_string(src)); + ck->signature = strbuf_dup(get_string(src)); + + + if (get_err(src)) { + ssh_key_free(&ck->sshk); + strbuf_free(basepub); + return NULL; + } + + *basepub_out = basepub; + return ck; +} + +static ssh_key *opensshcert_new_pub(const ssh_keyalg *self, ptrlen pub) +{ + strbuf *basepub; + opensshcert_key *ck = opensshcert_new_shared(self, pub, &basepub); + if (!ck) + return NULL; + + ck->basekey = ssh_key_new_pub(self->base_alg, ptrlen_from_strbuf(basepub)); + strbuf_free(basepub); + + if (!ck->basekey) { + ssh_key_free(&ck->sshk); + return NULL; + } + + return &ck->sshk; +} + +static ssh_key *opensshcert_new_priv( + const ssh_keyalg *self, ptrlen pub, ptrlen priv) +{ + strbuf *basepub; + opensshcert_key *ck = opensshcert_new_shared(self, pub, &basepub); + if (!ck) + return NULL; + + ck->basekey = ssh_key_new_priv(self->base_alg, + ptrlen_from_strbuf(basepub), priv); + strbuf_free(basepub); + + if (!ck->basekey) { + ssh_key_free(&ck->sshk); + return NULL; + } + + return &ck->sshk; +} + +static ssh_key *opensshcert_new_priv_openssh( + const ssh_keyalg *self, BinarySource *src) +{ + const opensshcert_extra *extra = self->extra; + + ptrlen cert = get_string(src); + + strbuf *basepub; + opensshcert_key *ck = opensshcert_new_shared(self, cert, &basepub); + if (!ck) + return NULL; + + strbuf *baseossh = strbuf_new(); + + /* Make the base OpenSSH key blob out of the public key blob + * returned from opensshcert_new_shared, and the trailing + * private data following the certificate */ + BLOBTRANS_DECLARE(bt); + + BinarySource pubsrc[1]; + BinarySource_BARE_INIT_PL(pubsrc, ptrlen_from_strbuf(basepub)); + get_string(pubsrc); /* skip key type id */ + + /* blobtrans_read might fail in this case, because we're reading + * from two sources and they might fail to match */ + bool success = blobtrans_read(bt, pubsrc, extra->pub_fmt) && + blobtrans_read(bt, src, extra->cert_ossh_fmt); + + blobtrans_write(bt, BinarySink_UPCAST(baseossh), extra->base_ossh_fmt); + blobtrans_clear(bt); + + if (!success) { + ssh_key_free(&ck->sshk); + strbuf_free(basepub); + strbuf_free(baseossh); + return NULL; + } + + strbuf_free(basepub); + + BinarySource osshsrc[1]; + BinarySource_BARE_INIT_PL(osshsrc, ptrlen_from_strbuf(baseossh)); + ck->basekey = ssh_key_new_priv_openssh(self->base_alg, osshsrc); + strbuf_free(baseossh); + + if (!ck->basekey) { + ssh_key_free(&ck->sshk); + return NULL; + } + + return &ck->sshk; +} + +static void opensshcert_freekey(ssh_key *key) +{ + opensshcert_key *ck = container_of(key, opensshcert_key, sshk); + + /* If this function is called from one of the above constructors + * because it failed part way through, we might not have managed + * to construct ck->basekey, so it might be NULL. */ + if (ck->basekey) + ssh_key_free(ck->basekey); + + strbuf_free(ck->nonce); + strbuf_free(ck->key_id); + strbuf_free(ck->valid_principals); + strbuf_free(ck->critical_options); + strbuf_free(ck->extensions); + strbuf_free(ck->reserved); + strbuf_free(ck->signature_key); + strbuf_free(ck->signature); + + sfree(ck); +} + +static ssh_key *opensshcert_ca_pub_key(opensshcert_key *ck, ptrlen *algname) +{ + ptrlen ca_keyblob = ptrlen_from_strbuf(ck->signature_key); + + if (algname) + *algname = pubkey_blob_to_alg_name(ca_keyblob); + + const ssh_keyalg *ca_alg = pubkey_blob_to_alg(ca_keyblob); + if (!ca_alg) + return NULL; /* don't even recognise the certifying key type */ + + return ssh_key_new_pub(ca_alg, ca_keyblob); +} + +static void opensshcert_signature_preimage(opensshcert_key *ck, BinarySink *bs) +{ + put_stringz(bs, ck->sshk.vt->ssh_id); + put_stringpl(bs, ptrlen_from_strbuf(ck->nonce)); + + strbuf *basepub = strbuf_new(); + ssh_key_public_blob(ck->basekey, BinarySink_UPCAST(basepub)); + BinarySource src[1]; + BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(basepub)); + get_string(src); /* skip initial key type string */ + put_data(bs, get_ptr(src), get_avail(src)); + strbuf_free(basepub); + + put_uint64(bs, ck->serial); + put_uint32(bs, ck->type); + put_stringpl(bs, ptrlen_from_strbuf(ck->key_id)); + put_stringpl(bs, ptrlen_from_strbuf(ck->valid_principals)); + put_uint64(bs, ck->valid_after); + put_uint64(bs, ck->valid_before); + put_stringpl(bs, ptrlen_from_strbuf(ck->critical_options)); + put_stringpl(bs, ptrlen_from_strbuf(ck->extensions)); + put_stringpl(bs, ptrlen_from_strbuf(ck->reserved)); + put_stringpl(bs, ptrlen_from_strbuf(ck->signature_key)); +} + +static void opensshcert_public_blob(ssh_key *key, BinarySink *bs) +{ + opensshcert_key *ck = container_of(key, opensshcert_key, sshk); + + opensshcert_signature_preimage(ck, bs); + put_stringpl(bs, ptrlen_from_strbuf(ck->signature)); +} + +static void opensshcert_private_blob(ssh_key *key, BinarySink *bs) +{ + opensshcert_key *ck = container_of(key, opensshcert_key, sshk); + ssh_key_private_blob(ck->basekey, bs); +} + +static void opensshcert_openssh_blob(ssh_key *key, BinarySink *bs) +{ + opensshcert_key *ck = container_of(key, opensshcert_key, sshk); + const opensshcert_extra *extra = key->vt->extra; + + strbuf *cert = strbuf_new(); + ssh_key_public_blob(key, BinarySink_UPCAST(cert)); + put_stringsb(bs, cert); + + strbuf *baseossh = strbuf_new_nm(); + ssh_key_openssh_blob(ck->basekey, BinarySink_UPCAST(baseossh)); + BinarySource basesrc[1]; + BinarySource_BARE_INIT_PL(basesrc, ptrlen_from_strbuf(baseossh)); + + BLOBTRANS_DECLARE(bt); + blobtrans_read(bt, basesrc, extra->base_ossh_fmt); + blobtrans_write(bt, bs, extra->cert_ossh_fmt); + blobtrans_clear(bt); + + strbuf_free(baseossh); +} + +static bool opensshcert_has_private(ssh_key *key) +{ + opensshcert_key *ck = container_of(key, opensshcert_key, sshk); + return ssh_key_has_private(ck->basekey); +} + +static char *opensshcert_cache_str(ssh_key *key) +{ + unreachable( + "Certificates are not expected to be stored in the host key cache"); +} + +static void opensshcert_time_to_iso8601(BinarySink *bs, uint64_t time) +{ + time_t t = time; + char buf[256]; + put_data(bs, buf, strftime(buf, sizeof(buf), + "%a %F %T %Z", localtime(&t))); +} + +static void opensshcert_string_list_key_components( + key_components *kc, strbuf *input, const char *title, const char *title2) +{ + BinarySource src[1]; + BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(input)); + + const char *titles[2] = { title, title2 }; + size_t ntitles = (title2 ? 2 : 1); + + unsigned index = 0; + while (get_avail(src)) { + for (size_t ti = 0; ti < ntitles; ti++) { + ptrlen value = get_string(src); + if (get_err(src)) + break; + char *name = dupprintf("%s_%u", titles[ti], index); + key_components_add_text_pl(kc, name, value); + sfree(name); + } + index++; + } +} + +static key_components *opensshcert_components(ssh_key *key) +{ + opensshcert_key *ck = container_of(key, opensshcert_key, sshk); + key_components *kc = ssh_key_components(ck->basekey); + key_components_add_binary(kc, "cert_nonce", ptrlen_from_strbuf(ck->nonce)); + key_components_add_uint(kc, "cert_serial", ck->serial); + switch (ck->type) { + case SSH_CERT_TYPE_HOST: + key_components_add_text(kc, "cert_type", "host"); + break; + case SSH_CERT_TYPE_USER: + key_components_add_text(kc, "cert_type", "user"); + break; + default: + key_components_add_uint(kc, "cert_type", ck->type); + break; + } + key_components_add_text(kc, "cert_key_id", ck->key_id->s); + opensshcert_string_list_key_components(kc, ck->valid_principals, + "cert_valid_principal", NULL); + key_components_add_uint(kc, "cert_valid_after", ck->valid_after); + key_components_add_uint(kc, "cert_valid_before", ck->valid_before); + /* Translate the validity period into human-legible dates, but + * only if they're not the min/max integer. Rationale: if you see + * "584554051223-11-09 07:00:15 UTC" as the expiry time you'll be + * as likely to think it's a weird buffer overflow as half a + * trillion years in the future! */ + if (ck->valid_after != 0) { + strbuf *date = strbuf_new(); + opensshcert_time_to_iso8601(BinarySink_UPCAST(date), ck->valid_after); + key_components_add_text_pl(kc, "cert_valid_after_date", + ptrlen_from_strbuf(date)); + strbuf_free(date); + } + if (ck->valid_before != 0xFFFFFFFFFFFFFFFF) { + strbuf *date = strbuf_new(); + opensshcert_time_to_iso8601(BinarySink_UPCAST(date), ck->valid_before); + key_components_add_text_pl(kc, "cert_valid_before_date", + ptrlen_from_strbuf(date)); + strbuf_free(date); + } + opensshcert_string_list_key_components(kc, ck->critical_options, + "cert_critical_option", + "cert_critical_option_data"); + opensshcert_string_list_key_components(kc, ck->extensions, + "cert_extension", + "cert_extension_data"); + key_components_add_binary(kc, "cert_ca_key", ptrlen_from_strbuf( + ck->signature_key)); + + ptrlen ca_algname; + ssh_key *ca_key = opensshcert_ca_pub_key(ck, &ca_algname); + key_components_add_text_pl(kc, "cert_ca_key_algorithm_id", ca_algname); + + if (ca_key) { + key_components *kc_ca_key = ssh_key_components(ca_key); + for (size_t i = 0; i < kc_ca_key->ncomponents; i++) { + key_component *comp = &kc_ca_key->components[i]; + char *subname = dupcat("cert_ca_key_", comp->name); + key_components_add_copy(kc, subname, comp); + sfree(subname); + } + key_components_free(kc_ca_key); + ssh_key_free(ca_key); + } + + key_components_add_binary(kc, "cert_ca_sig", ptrlen_from_strbuf( + ck->signature)); + return kc; +} + +static int opensshcert_pubkey_bits(const ssh_keyalg *self, ptrlen blob) +{ + BinarySource src[1]; + BinarySource_BARE_INIT_PL(src, blob); + + get_string(src); /* key type */ + get_string(src); /* nonce */ + return ssh_key_public_bits( + self->base_alg, make_ptrlen(get_ptr(src), get_avail(src))); +} + +static unsigned opensshcert_supported_flags(const ssh_keyalg *self) +{ + return ssh_keyalg_supported_flags(self->base_alg); +} + +static const char *opensshcert_alternate_ssh_id(const ssh_keyalg *self, + unsigned flags) +{ + const char *base_id = ssh_keyalg_alternate_ssh_id(self->base_alg, flags); + + for (size_t i = 0; i < lenof(opensshcert_all_keyalgs); i++) { + const ssh_keyalg *alg_i = opensshcert_all_keyalgs[i]; + if (!strcmp(base_id, alg_i->base_alg->ssh_id)) + return alg_i->ssh_id; + } + + return self->ssh_id; +} + +static char *opensshcert_invalid(ssh_key *key, unsigned flags) +{ + opensshcert_key *ck = container_of(key, opensshcert_key, sshk); + return ssh_key_invalid(ck->basekey, flags); +} + +static bool opensshcert_verify(ssh_key *key, ptrlen sig, ptrlen data) +{ + /* This method is pure *signature* verification; checking the + * certificate is done elsewhere. */ + opensshcert_key *ck = container_of(key, opensshcert_key, sshk); + return ssh_key_verify(ck->basekey, sig, data); +} + +static void opensshcert_sign(ssh_key *key, ptrlen data, unsigned flags, + BinarySink *bs) +{ + opensshcert_key *ck = container_of(key, opensshcert_key, sshk); + ssh_key_sign(ck->basekey, data, flags, bs); +} diff --git a/ssh.h b/ssh.h index 3465c196..a57e6328 100644 --- a/ssh.h +++ b/ssh.h @@ -854,6 +854,8 @@ struct ssh_keyalg { const char *ssh_id; /* string identifier in the SSH protocol */ const char *cache_id; /* identifier used in PuTTY's host key cache */ const void *extra; /* private to the public key methods */ + bool is_certificate; /* is this a certified key type? */ + const ssh_keyalg *base_alg; /* if so, for what underlying key alg? */ }; static inline ssh_key *ssh_key_new_pub(const ssh_keyalg *self, ptrlen pub) @@ -1098,6 +1100,14 @@ extern const ssh_keyalg ssh_ecdsa_ed448; extern const ssh_keyalg ssh_ecdsa_nistp256; extern const ssh_keyalg ssh_ecdsa_nistp384; extern const ssh_keyalg ssh_ecdsa_nistp521; +extern const ssh_keyalg opensshcert_ssh_dsa; +extern const ssh_keyalg opensshcert_ssh_rsa; +extern const ssh_keyalg opensshcert_ssh_rsa_sha256; +extern const ssh_keyalg opensshcert_ssh_rsa_sha512; +extern const ssh_keyalg opensshcert_ssh_ecdsa_ed25519; +extern const ssh_keyalg opensshcert_ssh_ecdsa_nistp256; +extern const ssh_keyalg opensshcert_ssh_ecdsa_nistp384; +extern const ssh_keyalg opensshcert_ssh_ecdsa_nistp521; extern const ssh2_macalg ssh_hmac_md5; extern const ssh2_macalg ssh_hmac_sha1; extern const ssh2_macalg ssh_hmac_sha1_buggy; diff --git a/sshpubk.c b/sshpubk.c index 2755df0f..e54b8a35 100644 --- a/sshpubk.c +++ b/sshpubk.c @@ -568,6 +568,14 @@ const ssh_keyalg *const all_keyalgs[] = { &ssh_ecdsa_nistp521, &ssh_ecdsa_ed25519, &ssh_ecdsa_ed448, + &opensshcert_ssh_dsa, + &opensshcert_ssh_rsa, + &opensshcert_ssh_rsa_sha256, + &opensshcert_ssh_rsa_sha512, + &opensshcert_ssh_ecdsa_ed25519, + &opensshcert_ssh_ecdsa_nistp256, + &opensshcert_ssh_ecdsa_nistp384, + &opensshcert_ssh_ecdsa_nistp521, }; const size_t n_keyalgs = lenof(all_keyalgs); diff --git a/test/testcrypt-enum.h b/test/testcrypt-enum.h index cf8f00c5..f1977c3e 100644 --- a/test/testcrypt-enum.h +++ b/test/testcrypt-enum.h @@ -46,6 +46,12 @@ BEGIN_ENUM_TYPE(keyalg) ENUM_VALUE("p256", &ssh_ecdsa_nistp256) ENUM_VALUE("p384", &ssh_ecdsa_nistp384) ENUM_VALUE("p521", &ssh_ecdsa_nistp521) + ENUM_VALUE("dsa-cert", &opensshcert_ssh_dsa) + ENUM_VALUE("rsa-cert", &opensshcert_ssh_rsa) + ENUM_VALUE("ed25519-cert", &opensshcert_ssh_ecdsa_ed25519) + ENUM_VALUE("p256-cert", &opensshcert_ssh_ecdsa_nistp256) + ENUM_VALUE("p384-cert", &opensshcert_ssh_ecdsa_nistp384) + ENUM_VALUE("p521-cert", &opensshcert_ssh_ecdsa_nistp521) END_ENUM_TYPE(keyalg) BEGIN_ENUM_TYPE(cipheralg) -- cgit v1.2.3 From 9f583c4fa8f632c349e6b335a4ab5cd69b0a76f9 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Wed, 20 Apr 2022 13:06:08 +0100 Subject: Certificate-specific ssh_key method suite. Certificate keys don't work the same as normal keys, so the rest of the code is going to have to pay attention to whether a key is a certificate, and if so, treat it differently and do cert-specific stuff to it. So here's a collection of methods for that purpose. With one exception, these methods of ssh_key are not expected to be implemented at all in non-certificate key types: they should only ever be called once you already know you're dealing with a certificate. So most of the new method pointers can be left out of the ssh_keyalg initialisers. The exception is the base_key method, which retrieves the base key of a certificate - the underlying one with the certificate stripped off. It's convenient for non-certificate keys to implement this too, and just return a pointer to themselves. So I've added an implementation in nullkey.c doing that. (The returned pointer doesn't transfer ownership; you have to use the new ssh_key_clone() if you want to keep the base key after freeing the certificate key.) The methods _only_ implemented in certificates: Query methods to return the public key of the CA (for looking up in a list of trusted ones), and to return the key id string (which exists to be written into log files). Obviously, we need a check_cert() method which will verify the CA's actual signature, not to mention checking all the other details like the principal and the validity period. And there's another fiddly method for dealing with the RSA upgrade system, called 'related_alg'. This is quite like alternate_ssh_id, in that its job is to upgrade one key algorithm to a related one with more modern RSA signing flags (or any other similar thing that might later reuse the same mechanism). But where alternate_ssh_id took the actual signing flags as an argument, this takes a pointer to the upgraded base algorithm. So it answers the question "What is to this key algorithm as you are to its base?" - if you call it on opensshcert_ssh_rsa and give it ssh_rsa_sha512, it'll give you back opensshcert_ssh_rsa_sha512. (It's awkward to have to have another of these fiddly methods, and in the longer term I'd like to try to clean up their proliferation a bit. But I even more dislike the alternative of just going through all_keyalgs looking for a cert algorithm with, say, ssh_rsa_sha512 as the base: that approach would work fine now but it would be a lurking time bomb for when all the -cert-v02@ methods appear one day. This way, each certificate type can upgrade itself to the appropriately related version. And at least related_alg is only needed if you _are_ a certificate key type - it's not adding yet another piece of null-method boilerplate to the rest.) --- crypto/dsa.c | 1 + crypto/ecc-ssh.c | 5 ++ crypto/openssh-certs.c | 221 ++++++++++++++++++++++++++++++++++++++++++++++++- crypto/rsa.c | 1 + ssh.h | 23 +++++ test/testcrypt-func.h | 8 ++ test/testcrypt.c | 33 ++++++++ utils/nullkey.c | 6 ++ 8 files changed, 294 insertions(+), 4 deletions(-) diff --git a/crypto/dsa.c b/crypto/dsa.c index 530bbe09..e304a38f 100644 --- a/crypto/dsa.c +++ b/crypto/dsa.c @@ -504,6 +504,7 @@ const ssh_keyalg ssh_dsa = { .has_private = dsa_has_private, .cache_str = dsa_cache_str, .components = dsa_components, + .base_key = nullkey_base_key, .pubkey_bits = dsa_pubkey_bits, .supported_flags = nullkey_supported_flags, .alternate_ssh_id = nullkey_alternate_ssh_id, diff --git a/crypto/ecc-ssh.c b/crypto/ecc-ssh.c index 9f637c2e..17fde8e9 100644 --- a/crypto/ecc-ssh.c +++ b/crypto/ecc-ssh.c @@ -1269,6 +1269,7 @@ const ssh_keyalg ssh_ecdsa_ed25519 = { .has_private = eddsa_has_private, .cache_str = eddsa_cache_str, .components = eddsa_components, + .base_key = nullkey_base_key, .pubkey_bits = ec_shared_pubkey_bits, .supported_flags = nullkey_supported_flags, .alternate_ssh_id = nullkey_alternate_ssh_id, @@ -1295,6 +1296,7 @@ const ssh_keyalg ssh_ecdsa_ed448 = { .has_private = eddsa_has_private, .cache_str = eddsa_cache_str, .components = eddsa_components, + .base_key = nullkey_base_key, .pubkey_bits = ec_shared_pubkey_bits, .supported_flags = nullkey_supported_flags, .alternate_ssh_id = nullkey_alternate_ssh_id, @@ -1325,6 +1327,7 @@ const ssh_keyalg ssh_ecdsa_nistp256 = { .has_private = ecdsa_has_private, .cache_str = ecdsa_cache_str, .components = ecdsa_components, + .base_key = nullkey_base_key, .pubkey_bits = ec_shared_pubkey_bits, .supported_flags = nullkey_supported_flags, .alternate_ssh_id = nullkey_alternate_ssh_id, @@ -1355,6 +1358,7 @@ const ssh_keyalg ssh_ecdsa_nistp384 = { .has_private = ecdsa_has_private, .cache_str = ecdsa_cache_str, .components = ecdsa_components, + .base_key = nullkey_base_key, .pubkey_bits = ec_shared_pubkey_bits, .supported_flags = nullkey_supported_flags, .alternate_ssh_id = nullkey_alternate_ssh_id, @@ -1385,6 +1389,7 @@ const ssh_keyalg ssh_ecdsa_nistp521 = { .has_private = ecdsa_has_private, .cache_str = ecdsa_cache_str, .components = ecdsa_components, + .base_key = nullkey_base_key, .pubkey_bits = ec_shared_pubkey_bits, .supported_flags = nullkey_supported_flags, .alternate_ssh_id = nullkey_alternate_ssh_id, diff --git a/crypto/openssh-certs.c b/crypto/openssh-certs.c index bec7bca3..7f61e55a 100644 --- a/crypto/openssh-certs.c +++ b/crypto/openssh-certs.c @@ -185,13 +185,21 @@ static bool opensshcert_verify(ssh_key *key, ptrlen sig, ptrlen data); static void opensshcert_public_blob(ssh_key *key, BinarySink *bs); static void opensshcert_private_blob(ssh_key *key, BinarySink *bs); static void opensshcert_openssh_blob(ssh_key *key, BinarySink *bs); +static void opensshcert_ca_public_blob(ssh_key *key, BinarySink *bs); +static void opensshcert_cert_id_string(ssh_key *key, BinarySink *bs); static bool opensshcert_has_private(ssh_key *key); static char *opensshcert_cache_str(ssh_key *key); static key_components *opensshcert_components(ssh_key *key); +static ssh_key *opensshcert_base_key(ssh_key *key); +static bool opensshcert_check_cert( + ssh_key *key, bool host, ptrlen principal, uint64_t time, + BinarySink *error); static int opensshcert_pubkey_bits(const ssh_keyalg *self, ptrlen blob); static unsigned opensshcert_supported_flags(const ssh_keyalg *self); static const char *opensshcert_alternate_ssh_id(const ssh_keyalg *self, unsigned flags); +static const ssh_keyalg *opensshcert_related_alg(const ssh_keyalg *self, + const ssh_keyalg *base); /* * Top-level vtables for the certified key formats, defined via a list @@ -233,9 +241,14 @@ static const char *opensshcert_alternate_ssh_id(const ssh_keyalg *self, .has_private = opensshcert_has_private, \ .cache_str = opensshcert_cache_str, \ .components = opensshcert_components, \ + .base_key = opensshcert_base_key, \ + .ca_public_blob = opensshcert_ca_public_blob, \ + .check_cert = opensshcert_check_cert, \ + .cert_id_string = opensshcert_cert_id_string, \ .pubkey_bits = opensshcert_pubkey_bits, \ .supported_flags = opensshcert_supported_flags, \ .alternate_ssh_id = opensshcert_alternate_ssh_id, \ + .related_alg = opensshcert_related_alg, \ .ssh_id = ssh_id_prefix "-cert-v01@openssh.com", \ .cache_id = NULL, \ .extra = &opensshcert_##name##_extra, \ @@ -419,14 +432,27 @@ static void opensshcert_freekey(ssh_key *key) sfree(ck); } -static ssh_key *opensshcert_ca_pub_key(opensshcert_key *ck, ptrlen *algname) +static ssh_key *opensshcert_base_key(ssh_key *key) +{ + opensshcert_key *ck = container_of(key, opensshcert_key, sshk); + return ck->basekey; +} + +/* + * Make a public key object from the CA public blob, potentially + * taking into account that the signature might override the algorithm + * name + */ +static ssh_key *opensshcert_ca_pub_key( + opensshcert_key *ck, ptrlen sig, ptrlen *algname) { ptrlen ca_keyblob = ptrlen_from_strbuf(ck->signature_key); + ptrlen alg_source = sig.ptr ? sig : ca_keyblob; if (algname) - *algname = pubkey_blob_to_alg_name(ca_keyblob); + *algname = pubkey_blob_to_alg_name(alg_source); - const ssh_keyalg *ca_alg = pubkey_blob_to_alg(ca_keyblob); + const ssh_keyalg *ca_alg = pubkey_blob_to_alg(alg_source); if (!ca_alg) return NULL; /* don't even recognise the certifying key type */ @@ -494,6 +520,18 @@ static void opensshcert_openssh_blob(ssh_key *key, BinarySink *bs) strbuf_free(baseossh); } +static void opensshcert_ca_public_blob(ssh_key *key, BinarySink *bs) +{ + opensshcert_key *ck = container_of(key, opensshcert_key, sshk); + put_datapl(bs, ptrlen_from_strbuf(ck->signature_key)); +} + +static void opensshcert_cert_id_string(ssh_key *key, BinarySink *bs) +{ + opensshcert_key *ck = container_of(key, opensshcert_key, sshk); + put_datapl(bs, ptrlen_from_strbuf(ck->key_id)); +} + static bool opensshcert_has_private(ssh_key *key) { opensshcert_key *ck = container_of(key, opensshcert_key, sshk); @@ -588,7 +626,8 @@ static key_components *opensshcert_components(ssh_key *key) ck->signature_key)); ptrlen ca_algname; - ssh_key *ca_key = opensshcert_ca_pub_key(ck, &ca_algname); + ssh_key *ca_key = opensshcert_ca_pub_key(ck, make_ptrlen(NULL, 0), + &ca_algname); key_components_add_text_pl(kc, "cert_ca_key_algorithm_id", ca_algname); if (ca_key) { @@ -638,12 +677,186 @@ static const char *opensshcert_alternate_ssh_id(const ssh_keyalg *self, return self->ssh_id; } +static const ssh_keyalg *opensshcert_related_alg(const ssh_keyalg *self, + const ssh_keyalg *base) +{ + for (size_t i = 0; i < lenof(opensshcert_all_keyalgs); i++) { + const ssh_keyalg *alg_i = opensshcert_all_keyalgs[i]; + if (base == alg_i->base_alg) + return alg_i; + } + + return self; +} + static char *opensshcert_invalid(ssh_key *key, unsigned flags) { opensshcert_key *ck = container_of(key, opensshcert_key, sshk); return ssh_key_invalid(ck->basekey, flags); } +static bool opensshcert_check_cert( + ssh_key *key, bool host, ptrlen principal, uint64_t time, + BinarySink *error) +{ + opensshcert_key *ck = container_of(key, opensshcert_key, sshk); + bool result = false; + ssh_key *ca_key = NULL; + strbuf *preimage = strbuf_new(); + BinarySource src[1]; + + ptrlen signature = ptrlen_from_strbuf(ck->signature); + /* FIXME: here we should check which signature algorithm is + * actually in use, because that might be a reason to reject the + * certificate (e.g. ssh-rsa when we wanted rsa-sha2-*) */ + + ca_key = opensshcert_ca_pub_key(ck, signature, NULL); + if (!ca_key) { + put_fmt(error, "Certificate's signing key is invalid"); + goto out; + } + + opensshcert_signature_preimage(ck, BinarySink_UPCAST(preimage)); + + if (!ssh_key_verify(ca_key, signature, ptrlen_from_strbuf(preimage))) { + put_fmt(error, "Certificate's signature is invalid"); + goto out; + } + + uint32_t expected_type = host ? SSH_CERT_TYPE_HOST : SSH_CERT_TYPE_USER; + if (ck->type != expected_type) { + put_fmt(error, "Certificate type is "); + switch (ck->type) { + case SSH_CERT_TYPE_HOST: + put_fmt(error, "host"); + break; + case SSH_CERT_TYPE_USER: + put_fmt(error, "user"); + break; + default: + put_fmt(error, "unknown value %" PRIu32, ck->type); + break; + } + put_fmt(error, "; expected %s", host ? "host" : "user"); + goto out; + } + + /* + * Check the time bounds on the certificate. + */ + if (time < ck->valid_after) { + put_fmt(error, "Certificate is not valid until "); + opensshcert_time_to_iso8601(BinarySink_UPCAST(error), time); + goto out; + } + if (time >= ck->valid_before) { + put_fmt(error, "Certificate expired at "); + opensshcert_time_to_iso8601(BinarySink_UPCAST(error), time); + goto out; + } + + /* + * Check that this certificate is for the right thing. + * + * If valid_principals is a zero-length string then this is + * specified to be a carte-blanche certificate valid for any + * principal (at least, provided you trust the CA that issued it). + */ + if (ck->valid_principals->len != 0) { + BinarySource_BARE_INIT_PL( + src, ptrlen_from_strbuf(ck->valid_principals)); + + while (get_avail(src)) { + ptrlen valid_principal = get_string(src); + if (get_err(src)) { + put_fmt(error, "Certificate's valid principals list is " + "incorrectly formatted"); + goto out; + } + if (ptrlen_eq_ptrlen(valid_principal, principal)) + goto principal_ok; + } + + /* + * No valid principal matched. Now go through the list a + * second time writing the cert contents into the error + * message, so that the user can see at a glance what went + * wrong. + * + * (If you've typed the wrong spelling of the host name, you + * really need to see "This cert is for 'foo.example.com' and + * I was trying to match it against 'foo'", rather than just + * "Computer says no".) + */ + put_fmt(error, "Certificate's %s list [", + host ? "hostname" : "username"); + BinarySource_BARE_INIT_PL( + src, ptrlen_from_strbuf(ck->valid_principals)); + const char *sep = ""; + while (get_avail(src)) { + ptrlen valid_principal = get_string(src); + put_fmt(error, "%s\"", sep); + put_c_string_literal(error, valid_principal); + put_fmt(error, "\""); + sep = ", "; + } + put_fmt(error, "] does not contain expected %s \"", + host ? "hostname" : "username"); + put_c_string_literal(error, principal); + put_fmt(error, "\""); + goto out; + principal_ok:; + } + + /* + * Check for critical options. + */ + { + BinarySource_BARE_INIT_PL( + src, ptrlen_from_strbuf(ck->critical_options)); + + while (get_avail(src)) { + ptrlen option = get_string(src); + ptrlen data = get_string(src); + if (get_err(src)) { + put_fmt(error, "Certificate's critical options list is " + "incorrectly formatted"); + goto out; + } + + /* + * If we ever do support any options, this will be where + * we insert code to recognise and validate them. + * + * At present, we implement no critical options at all. + * (For host certs, as of 2022-04-20, OpenSSH hasn't + * defined any. For user certs, the only SSH server using + * this is Uppity, which doesn't support key restrictions + * in general.) + */ + (void)data; /* no options supported => no use made of the data */ + + /* + * Report an unrecognised literal. + */ + put_fmt(error, "Certificate specifies an unsupported critical " + "option \""); + put_c_string_literal(error, option); + put_fmt(error, "\""); + goto out; + } + } + + /* If we get here without failing any check, accept the certificate! */ + result = true; + + out: + if (ca_key) + ssh_key_free(ca_key); + strbuf_free(preimage); + return result; +} + static bool opensshcert_verify(ssh_key *key, ptrlen sig, ptrlen data) { /* This method is pure *signature* verification; checking the diff --git a/crypto/rsa.c b/crypto/rsa.c index 4087035d..635c9efb 100644 --- a/crypto/rsa.c +++ b/crypto/rsa.c @@ -878,6 +878,7 @@ static const struct ssh2_rsa_extra .has_private = rsa2_has_private, \ .cache_str = rsa2_cache_str, \ .components = rsa2_components, \ + .base_key = nullkey_base_key, \ .pubkey_bits = rsa2_pubkey_bits, \ .cache_id = "rsa2" diff --git a/ssh.h b/ssh.h index a57e6328..ecb5074c 100644 --- a/ssh.h +++ b/ssh.h @@ -844,11 +844,20 @@ struct ssh_keyalg { bool (*has_private) (ssh_key *key); char *(*cache_str) (ssh_key *key); key_components *(*components) (ssh_key *key); + ssh_key *(*base_key) (ssh_key *key); /* does not confer ownership */ + /* The following methods can be NULL if !is_certificate */ + void (*ca_public_blob)(ssh_key *key, BinarySink *); + bool (*check_cert)(ssh_key *key, bool host, ptrlen principal, + uint64_t time, BinarySink *error); + void (*cert_id_string)(ssh_key *key, BinarySink *); /* 'Class methods' that don't deal with an ssh_key at all */ int (*pubkey_bits) (const ssh_keyalg *self, ptrlen blob); unsigned (*supported_flags) (const ssh_keyalg *self); const char *(*alternate_ssh_id) (const ssh_keyalg *self, unsigned flags); + /* The following methods can be NULL if !is_certificate */ + const ssh_keyalg *(*related_alg)(const ssh_keyalg *self, + const ssh_keyalg *base); /* Constant data fields giving information about the key type */ const char *ssh_id; /* string identifier in the SSH protocol */ @@ -887,6 +896,16 @@ static inline char *ssh_key_cache_str(ssh_key *key) { return key->vt->cache_str(key); } static inline key_components *ssh_key_components(ssh_key *key) { return key->vt->components(key); } +static inline ssh_key *ssh_key_base_key(ssh_key *key) +{ return key->vt->base_key(key); } +static inline void ssh_key_ca_public_blob(ssh_key *key, BinarySink *bs) +{ key->vt->ca_public_blob(key, bs); } +static inline void ssh_key_cert_id_string(ssh_key *key, BinarySink *bs) +{ key->vt->cert_id_string(key, bs); } +static inline bool ssh_key_check_cert( + ssh_key *key, bool host, ptrlen principal, uint64_t time, + BinarySink *error) +{ return key->vt->check_cert(key, host, principal, time, error); } static inline int ssh_key_public_bits(const ssh_keyalg *self, ptrlen blob) { return self->pubkey_bits(self, blob); } static inline const ssh_keyalg *ssh_key_alg(ssh_key *key) @@ -902,10 +921,14 @@ static inline const unsigned ssh_keyalg_supported_flags(const ssh_keyalg *self) static inline const char *ssh_keyalg_alternate_ssh_id( const ssh_keyalg *self, unsigned flags) { return self->alternate_ssh_id(self, flags); } +static inline const ssh_keyalg *ssh_keyalg_related_alg( + const ssh_keyalg *self, const ssh_keyalg *base) +{ return self->related_alg(self, base); } /* Stub functions shared between multiple key types */ unsigned nullkey_supported_flags(const ssh_keyalg *self); const char *nullkey_alternate_ssh_id(const ssh_keyalg *self, unsigned flags); +ssh_key *nullkey_base_key(ssh_key *key); /* Utility functions implemented centrally */ ssh_key *ssh_key_clone(ssh_key *key); diff --git a/test/testcrypt-func.h b/test/testcrypt-func.h index a7fad72e..0873c2ea 100644 --- a/test/testcrypt-func.h +++ b/test/testcrypt-func.h @@ -302,6 +302,14 @@ FUNC(void, ssh_key_openssh_blob, ARG(val_key, key), FUNC(val_string_asciz, ssh_key_cache_str, ARG(val_key, key)) FUNC(val_keycomponents, ssh_key_components, ARG(val_key, key)) FUNC(uint, ssh_key_public_bits, ARG(keyalg, self), ARG(val_string_ptrlen, blob)) +FUNC_WRAPPED(val_key, ssh_key_base_key, ARG(val_key, key)) +FUNC_WRAPPED(void, ssh_key_ca_public_blob, ARG(val_key, key), + ARG(out_val_string_binarysink, blob)) +FUNC_WRAPPED(void, ssh_key_cert_id_string, ARG(val_key, key), + ARG(out_val_string_binarysink, blob)) +FUNC_WRAPPED(boolean, ssh_key_check_cert, ARG(val_key, key), + ARG(boolean, host), ARG(val_string_ptrlen, principal), + ARG(uint, time), ARG(out_val_string_binarysink, error)) /* * Accessors to retrieve the innards of a 'key_components'. diff --git a/test/testcrypt.c b/test/testcrypt.c index 566c95b9..c3c74784 100644 --- a/test/testcrypt.c +++ b/test/testcrypt.c @@ -806,6 +806,39 @@ strbuf *ssh2_mac_genresult_wrapper(ssh2_mac *m) return sb; } +ssh_key *ssh_key_base_key_wrapper(ssh_key *key) +{ + /* To avoid having to explain the borrowed reference to Python, + * just clone the key unconditionally */ + return ssh_key_clone(ssh_key_base_key(key)); +} + +void ssh_key_ca_public_blob_wrapper(ssh_key *key, BinarySink *out) +{ + /* Wrap to avoid null-pointer dereference */ + if (!key->vt->is_certificate) + fatal_error("ssh_key_ca_public_blob: needs a certificate"); + ssh_key_ca_public_blob(key, out); +} + +void ssh_key_cert_id_string_wrapper(ssh_key *key, BinarySink *out) +{ + /* Wrap to avoid null-pointer dereference */ + if (!key->vt->is_certificate) + fatal_error("ssh_key_cert_id_string: needs a certificate"); + ssh_key_cert_id_string(key, out); +} + +static bool ssh_key_check_cert_wrapper( + ssh_key *key, bool host, ptrlen principal, uint64_t time, + BinarySink *error) +{ + /* Wrap to avoid null-pointer dereference */ + if (!key->vt->is_certificate) + fatal_error("ssh_key_cert_id_string: needs a certificate"); + return ssh_key_check_cert(key, host, principal, time, error); +} + bool dh_validate_f_wrapper(dh_ctx *dh, mp_int *f) { return dh_validate_f(dh, f) == NULL; diff --git a/utils/nullkey.c b/utils/nullkey.c index d4f2a691..1b01c891 100644 --- a/utils/nullkey.c +++ b/utils/nullkey.c @@ -11,3 +11,9 @@ const char *nullkey_alternate_ssh_id(const ssh_keyalg *self, unsigned flags) /* There are no alternate ids */ return self->ssh_id; } + +ssh_key *nullkey_base_key(ssh_key *key) +{ + /* When a key is not certified, it is its own base */ + return key; +} -- cgit v1.2.3 From 4cde00efc07297976a0fa391f51a07e34f355067 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 22 Apr 2022 16:53:30 +0100 Subject: OpenSSH key export: strip certificates. As far as I can tell, OpenSSH never stores private key files containing a certified key. I plan to provide that as an option in PuTTY (though not the only option); so if we get a certified key as input to the export functions, we need to strip it back to the base key to save it into either OpenSSH format. --- import.c | 51 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 30 insertions(+), 21 deletions(-) diff --git a/import.c b/import.c index 28e70b8a..c62dfb15 100644 --- a/import.c +++ b/import.c @@ -811,7 +811,7 @@ static ssh2_userkey *openssh_pem_read( } static bool openssh_pem_write( - const Filename *filename, ssh2_userkey *key, const char *passphrase) + const Filename *filename, ssh2_userkey *ukey, const char *passphrase) { strbuf *pubblob, *privblob, *outblob; unsigned char *spareblob; @@ -825,13 +825,17 @@ static bool openssh_pem_write( FILE *fp; BinarySource src[1]; + /* OpenSSH's private key files never contain a certificate, so + * revert to the underlying base key if necessary */ + ssh_key *key = ssh_key_base_key(ukey->key); + /* * Fetch the key blobs. */ pubblob = strbuf_new(); - ssh_key_public_blob(key->key, BinarySink_UPCAST(pubblob)); + ssh_key_public_blob(key, BinarySink_UPCAST(pubblob)); privblob = strbuf_new_nm(); - ssh_key_private_blob(key->key, BinarySink_UPCAST(privblob)); + ssh_key_private_blob(key, BinarySink_UPCAST(privblob)); spareblob = NULL; outblob = strbuf_new_nm(); @@ -840,8 +844,8 @@ static bool openssh_pem_write( * Encode the OpenSSH key blob, and also decide on the header * line. */ - if (ssh_key_alg(key->key) == &ssh_rsa || - ssh_key_alg(key->key) == &ssh_dsa) { + if (ssh_key_alg(key) == &ssh_rsa || + ssh_key_alg(key) == &ssh_dsa) { strbuf *seq; /* @@ -851,7 +855,7 @@ static bool openssh_pem_write( * bignums per key type and then construct the actual blob in * common code after that. */ - if (ssh_key_alg(key->key) == &ssh_rsa) { + if (ssh_key_alg(key) == &ssh_rsa) { ptrlen n, e, d, p, q, iqmp, dmp1, dmq1; mp_int *bd, *bp, *bq, *bdmp1, *bdmq1; @@ -947,11 +951,11 @@ static bool openssh_pem_write( put_ber_id_len(outblob, 16, seq->len, ASN1_CONSTRUCTED); put_data(outblob, seq->s, seq->len); strbuf_free(seq); - } else if (ssh_key_alg(key->key) == &ssh_ecdsa_nistp256 || - ssh_key_alg(key->key) == &ssh_ecdsa_nistp384 || - ssh_key_alg(key->key) == &ssh_ecdsa_nistp521) { + } else if (ssh_key_alg(key) == &ssh_ecdsa_nistp256 || + ssh_key_alg(key) == &ssh_ecdsa_nistp384 || + ssh_key_alg(key) == &ssh_ecdsa_nistp521) { const unsigned char *oid; - struct ecdsa_key *ec = container_of(key->key, struct ecdsa_key, sshk); + struct ecdsa_key *ec = container_of(key, struct ecdsa_key, sshk); int oidlen; int pointlen; strbuf *seq, *sub; @@ -966,7 +970,7 @@ static bool openssh_pem_write( * [1] * BIT STRING (0x00 public key point) */ - oid = ec_alg_oid(ssh_key_alg(key->key), &oidlen); + oid = ec_alg_oid(ssh_key_alg(key), &oidlen); pointlen = (ec->curve->fieldBits + 7) / 8 * 2; seq = strbuf_new_nm(); @@ -1499,7 +1503,7 @@ static ssh2_userkey *openssh_new_read( } static bool openssh_new_write( - const Filename *filename, ssh2_userkey *key, const char *passphrase) + const Filename *filename, ssh2_userkey *ukey, const char *passphrase) { strbuf *pubblob, *privblob, *cblob; int padvalue; @@ -1509,13 +1513,17 @@ static bool openssh_new_write( const int bcrypt_rounds = 16; FILE *fp; + /* OpenSSH's private key files never contain a certificate, so + * revert to the underlying base key if necessary */ + ssh_key *key = ssh_key_base_key(ukey->key); + /* * Fetch the key blobs and find out the lengths of things. */ pubblob = strbuf_new(); - ssh_key_public_blob(key->key, BinarySink_UPCAST(pubblob)); + ssh_key_public_blob(key, BinarySink_UPCAST(pubblob)); privblob = strbuf_new_nm(); - ssh_key_openssh_blob(key->key, BinarySink_UPCAST(privblob)); + ssh_key_openssh_blob(key, BinarySink_UPCAST(privblob)); /* * Construct the cleartext version of the blob. @@ -1562,11 +1570,11 @@ static bool openssh_new_write( /* Private key. The main private blob goes inline, with no string * wrapper. */ - put_stringz(cpblob, ssh_key_ssh_id(key->key)); + put_stringz(cpblob, ssh_key_ssh_id(key)); put_data(cpblob, privblob->s, privblob->len); /* Comment. */ - put_stringz(cpblob, key->comment); + put_stringz(cpblob, ukey->comment); /* Pad out the encrypted section. */ padvalue = 1; @@ -1633,11 +1641,12 @@ static bool openssh_auto_write( * assume that anything not in that fixed list is newer, and hence * will use the new format. */ - if (ssh_key_alg(key->key) == &ssh_dsa || - ssh_key_alg(key->key) == &ssh_rsa || - ssh_key_alg(key->key) == &ssh_ecdsa_nistp256 || - ssh_key_alg(key->key) == &ssh_ecdsa_nistp384 || - ssh_key_alg(key->key) == &ssh_ecdsa_nistp521) + const ssh_keyalg *alg = ssh_key_alg(ssh_key_base_key(key->key)); + if (alg == &ssh_dsa || + alg == &ssh_rsa || + alg == &ssh_ecdsa_nistp256 || + alg == &ssh_ecdsa_nistp384 || + alg == &ssh_ecdsa_nistp521) return openssh_pem_write(filename, key, passphrase); else return openssh_new_write(filename, key, passphrase); -- cgit v1.2.3 From 7cb3142a57ed01bf2a26975b5727a9a02a4e4e7a Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Tue, 19 Apr 2022 17:59:46 +0100 Subject: PuTTYgen: options to add and remove certificates. This allows you to actually use an OpenSSH user certificate for authentication, by combining the PPK you already had with the certificate from the CA to produce a new PPK whose public half contains the certificate. I don't intend that this should be the only way to do it. It's cumbersome to have to use the key passphrase in order to re-encrypt the modified PPK. But the flip side is that once you've done it you have everything you need in one convenient file, and also, the certificate itself is covered by the PPK's tamperproofing (in case you can think of any attacks in which the admin of your file server swaps out just the certificate for a different one on the same key). So this is *a* useful way to do it, even if not the only useful way. The new options to add and remove certificates are supported by both Windows GUI PuTTYgen and cmdgen. cmdgen can also operate on pure public keys, so you can say 'puttygen --remove-certificate foo-cert.pub' and get back the underlying foo.pub; Windows PuTTYgen doesn't support that mode, but only because it doesn't in general have any support for the loaded key not being a full public+private pair. --- cmdgen.c | 157 +++++++++++++++++++++++++++++++++++++++++- windows/puttygen.c | 195 +++++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 337 insertions(+), 15 deletions(-) diff --git a/cmdgen.c b/cmdgen.c index 37b5cac6..044d86aa 100644 --- a/cmdgen.c +++ b/cmdgen.c @@ -137,6 +137,8 @@ void help(void) " -L equivalent to `-O public-openssh'\n" " -p equivalent to `-O public'\n" " --dump equivalent to `-O text'\n" + " --certificate file incorporate a certificate into the key\n" + " --remove-certificate remove any certificate from the key\n" " --reencrypt load a key and save it with fresh " "encryption\n" " --old-passphrase file\n" @@ -250,6 +252,8 @@ int main(int argc, char **argv) char *old_passphrase = NULL, *new_passphrase = NULL; bool load_encrypted; const char *random_device = NULL; + char *certfile = NULL; + bool remove_cert = false; int exit_status = 0; const PrimeGenerationPolicy *primegen = &primegen_probabilistic; bool strong_rsa = false; @@ -392,6 +396,18 @@ int main(int argc, char **argv) } } else if (!strcmp(opt, "-strong-rsa")) { strong_rsa = true; + } else if (!strcmp(opt, "-certificate")) { + if (!val && argc > 1) + --argc, val = *++argv; + if (!val) { + errs = true; + fprintf(stderr, "puttygen: option `-%s'" + " expects an argument\n", opt); + } else { + certfile = val; + } + } else if (!strcmp(opt, "-remove-certificate")) { + remove_cert = true; } else if (!strcmp(opt, "-reencrypt")) { reencrypt = true; } else if (!strcmp(opt, "-ppk-param") || @@ -799,7 +815,8 @@ int main(int argc, char **argv) outfiletmp = dupcat(outfile, ".tmp"); } - if (!change_passphrase && !comment && !reencrypt) { + if (!change_passphrase && !comment && !reencrypt && !certfile && + !remove_cert) { fprintf(stderr, "puttygen: this command would perform no useful" " action\n"); RETURN(1); @@ -849,6 +866,26 @@ int main(int argc, char **argv) RETURN(1); } + /* + * Check consistency properties relating to certificates. + */ + if (certfile && !(sshver == 2 && intype_has_private && + outtype_has_private && infile)) { + fprintf(stderr, "puttygen: certificates can only be added to " + "existing SSH-2 private key files\n"); + RETURN(1); + } + if (remove_cert && !(sshver == 2 && infile)) { + fprintf(stderr, "puttygen: certificates can only be removed from " + "existing SSH-2 key files\n"); + RETURN(1); + } + if (certfile && remove_cert) { + fprintf(stderr, "puttygen: cannot both add and remove a " + "certificate\n"); + RETURN(1); + } + /* ------------------------------------------------------------------ * Now we're ready to actually do some stuff. */ @@ -1081,6 +1118,124 @@ int main(int argc, char **argv) } } + /* + * Swap out the public key for a different one, if asked to. + */ + if (certfile) { + Filename *certfilename = filename_from_str(certfile); + LoadedFile *certfile_lf; + const char *error = NULL; + + if (!strcmp(certfile, "-")) + certfile_lf = lf_load_keyfile_fp(stdin, &error); + else + certfile_lf = lf_load_keyfile(certfilename, &error); + + filename_free(certfilename); + + if (!certfile_lf) { + fprintf(stderr, "puttygen: unable to load certificate file `%s': " + "%s\n", certfile, error); + RETURN(1); + } + + char *algname = NULL; + char *comment = NULL; + strbuf *pub = strbuf_new(); + if (!ppk_loadpub_s(BinarySource_UPCAST(certfile_lf), &algname, + BinarySink_UPCAST(pub), &comment, &error)) { + fprintf(stderr, "puttygen: unable to load certificate file `%s': " + "%s\n", certfile, error); + strbuf_free(pub); + sfree(algname); + sfree(comment); + lf_free(certfile_lf); + RETURN(1); + } + + lf_free(certfile_lf); + sfree(comment); + + const ssh_keyalg *alg = find_pubkey_alg(algname); + if (!alg) { + fprintf(stderr, "puttygen: certificate file `%s' has unsupported " + "algorithm name `%s'\n", certfile, algname); + strbuf_free(pub); + sfree(algname); + RETURN(1); + } + + sfree(algname); + + /* Check the two public keys match apart from certificates */ + strbuf *old_basepub = strbuf_new(); + ssh_key_public_blob(ssh_key_base_key(ssh2key->key), + BinarySink_UPCAST(old_basepub)); + + ssh_key *new_pubkey = ssh_key_new_pub(alg, ptrlen_from_strbuf(pub)); + strbuf *new_basepub = strbuf_new(); + ssh_key_public_blob(ssh_key_base_key(new_pubkey), + BinarySink_UPCAST(new_basepub)); + ssh_key_free(new_pubkey); + + bool match = ptrlen_eq_ptrlen(ptrlen_from_strbuf(old_basepub), + ptrlen_from_strbuf(new_basepub)); + strbuf_free(old_basepub); + strbuf_free(new_basepub); + + if (!match) { + fprintf(stderr, "puttygen: certificate in `%s' does not match " + "public key in `%s'\n", certfile, infile); + strbuf_free(pub); + RETURN(1); + } + + strbuf *priv = strbuf_new_nm(); + ssh_key_private_blob(ssh2key->key, BinarySink_UPCAST(priv)); + ssh_key *newkey = ssh_key_new_priv( + alg, ptrlen_from_strbuf(pub), ptrlen_from_strbuf(priv)); + strbuf_free(pub); + strbuf_free(priv); + + if (!newkey) { + fprintf(stderr, "puttygen: unable to combine certificate in `%s' " + "with private key\n", certfile); + RETURN(1); + } + + ssh_key_free(ssh2key->key); + ssh2key->key = newkey; + } else if (remove_cert) { + /* + * Removing a certificate can be meaningfully done to a pure + * public key blob, as well as a full key pair. + */ + if (ssh2key) { + ssh_key *newkey = ssh_key_clone(ssh_key_base_key(ssh2key->key)); + ssh_key_free(ssh2key->key); + ssh2key->key = newkey; + } else if (ssh2blob) { + ptrlen algname = pubkey_blob_to_alg_name( + ptrlen_from_strbuf(ssh2blob)); + + const ssh_keyalg *alg = find_pubkey_alg_len(algname); + + if (!alg) { + fprintf(stderr, "puttygen: input file `%s' has unsupported " + "algorithm name `%.*s'\n", infile, + PTRLEN_PRINTF(algname)); + RETURN(1); + } + + ssh_key *tmpkey = ssh_key_new_pub( + alg, ptrlen_from_strbuf(ssh2blob)); + strbuf_clear(ssh2blob); + ssh_key_public_blob(ssh_key_base_key(tmpkey), + BinarySink_UPCAST(ssh2blob)); + ssh_key_free(tmpkey); + } + } + /* * Unless we're changing the passphrase, the old one (if any) is a * reasonable default. diff --git a/windows/puttygen.c b/windows/puttygen.c index 281d17c0..5beaed12 100644 --- a/windows/puttygen.c +++ b/windows/puttygen.c @@ -758,7 +758,8 @@ enum { IDC_GIVEHELP, IDC_IMPORT, IDC_EXPORT_OPENSSH_AUTO, IDC_EXPORT_OPENSSH_NEW, - IDC_EXPORT_SSHCOM + IDC_EXPORT_SSHCOM, + IDC_ADDCERT, IDC_REMCERT, }; static const int nokey_ids[] = { IDC_NOKEY, 0 }; @@ -812,6 +813,8 @@ void ui_set_state(HWND hwnd, struct MainDlgState *state, int status) MF_GRAYED|MF_BYCOMMAND); EnableMenuItem(state->cvtmenu, IDC_EXPORT_SSHCOM, MF_GRAYED|MF_BYCOMMAND); + EnableMenuItem(state->keymenu, IDC_ADDCERT, MF_GRAYED|MF_BYCOMMAND); + EnableMenuItem(state->keymenu, IDC_REMCERT, MF_GRAYED|MF_BYCOMMAND); break; case 1: /* generating key */ hidemany(hwnd, nokey_ids, true); @@ -845,6 +848,8 @@ void ui_set_state(HWND hwnd, struct MainDlgState *state, int status) MF_GRAYED|MF_BYCOMMAND); EnableMenuItem(state->cvtmenu, IDC_EXPORT_SSHCOM, MF_GRAYED|MF_BYCOMMAND); + EnableMenuItem(state->keymenu, IDC_ADDCERT, MF_GRAYED|MF_BYCOMMAND); + EnableMenuItem(state->keymenu, IDC_REMCERT, MF_GRAYED|MF_BYCOMMAND); break; case 2: hidemany(hwnd, nokey_ids, true); @@ -884,6 +889,35 @@ void ui_set_state(HWND hwnd, struct MainDlgState *state, int status) do_export_menuitem(IDC_EXPORT_OPENSSH_NEW, SSH_KEYTYPE_OPENSSH_NEW); do_export_menuitem(IDC_EXPORT_SSHCOM, SSH_KEYTYPE_SSHCOM); #undef do_export_menuitem + /* + * Enable certificate menu items similarly. + */ + { + bool add_cert_allowed = false, rem_cert_allowed = false; + + if (state->ssh2 && state->ssh2key.key) { + const ssh_keyalg *alg = ssh_key_alg(state->ssh2key.key); + if (alg->is_certificate) { + /* If there's a certificate, we can remove it */ + rem_cert_allowed = true; + /* And reset to the base algorithm for the next check */ + alg = alg->base_alg; + } + + /* Now, do we have any certified version of this alg? */ + for (size_t i = 0; i < n_keyalgs; i++) { + if (all_keyalgs[i]->base_alg == alg) { + add_cert_allowed = true; + break; + } + } + } + + EnableMenuItem(state->keymenu, IDC_ADDCERT, MF_BYCOMMAND | + (add_cert_allowed ? MF_ENABLED : MF_GRAYED)); + EnableMenuItem(state->keymenu, IDC_REMCERT, MF_BYCOMMAND | + (rem_cert_allowed ? MF_ENABLED : MF_GRAYED)); + } break; } } @@ -994,6 +1028,23 @@ void ui_set_fptype(HWND hwnd, struct MainDlgState *state, int option) } } +static void update_ui_after_ssh2_pubkey_change( + HWND hwnd, struct MainDlgState *state) +{ + /* Smaller version of update_ui_after_load which doesn't need to + * be told things like the passphrase, which we aren't changing + * anyway */ + char *savecomment = state->ssh2key.comment; + state->ssh2key.comment = NULL; + char *fp = ssh2_fingerprint(state->ssh2key.key, state->fptype); + state->ssh2key.comment = savecomment; + + SetDlgItemText(hwnd, IDC_FINGERPRINT, fp); + sfree(fp); + + setupbigedit2(hwnd, IDC_KEYDISPLAY, IDC_PKSTATIC, &state->ssh2key); +} + static void update_ui_after_load(HWND hwnd, struct MainDlgState *state, const char *passphrase, int type, RSAKey *newkey1, ssh2_userkey *newkey2) @@ -1024,24 +1075,12 @@ static void update_ui_after_load(HWND hwnd, struct MainDlgState *state, */ 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 = ssh2_fingerprint(state->ssh2key.key, state->fptype); - state->ssh2key.comment = savecomment; - - SetDlgItemText(hwnd, IDC_FINGERPRINT, fp); - sfree(fp); - - setupbigedit2(hwnd, IDC_KEYDISPLAY, - IDC_PKSTATIC, &state->ssh2key); + update_ui_after_ssh2_pubkey_change(hwnd, state); } SetDlgItemText(hwnd, IDC_COMMENTEDIT, *state->commentptr); @@ -1165,6 +1204,105 @@ void load_key_file(HWND hwnd, struct MainDlgState *state, burnstr(passphrase); } +void add_certificate(HWND hwnd, struct MainDlgState *state, + Filename *filename) +{ + int type = key_type(filename); + if (type != SSH_KEYTYPE_SSH2_PUBLIC_RFC4716 && + type != SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH) { + char *msg = dupprintf("Couldn't load certificate (%s)", + key_type_to_str(type)); + message_box(hwnd, msg, "PuTTYgen Error", MB_OK | MB_ICONERROR, + HELPCTXID(errors_cantloadkey)); + sfree(msg); + return; + } + + char *algname = NULL; + char *comment = NULL; + const char *error = NULL; + strbuf *pub = strbuf_new(); + if (!ppk_loadpub_f(filename, &algname, BinarySink_UPCAST(pub), &comment, + &error)) { + char *msg = dupprintf("Couldn't load certificate (%s)", error); + message_box(hwnd, msg, "PuTTYgen Error", MB_OK | MB_ICONERROR, + HELPCTXID(errors_cantloadkey)); + sfree(msg); + return; + } + + sfree(comment); + + const ssh_keyalg *alg = find_pubkey_alg(algname); + if (!alg) { + char *msg = dupprintf("Couldn't load certificate (unsupported " + "algorithm name '%s')", algname); + message_box(hwnd, msg, "PuTTYgen Error", MB_OK | MB_ICONERROR, + HELPCTXID(errors_cantloadkey)); + sfree(msg); + sfree(algname); + strbuf_free(pub); + return; + } + + sfree(algname); + + /* Check the two public keys match apart from certificates */ + strbuf *old_basepub = strbuf_new(); + ssh_key_public_blob(ssh_key_base_key(state->ssh2key.key), + BinarySink_UPCAST(old_basepub)); + + ssh_key *new_pubkey = ssh_key_new_pub(alg, ptrlen_from_strbuf(pub)); + strbuf *new_basepub = strbuf_new(); + ssh_key_public_blob(ssh_key_base_key(new_pubkey), + BinarySink_UPCAST(new_basepub)); + ssh_key_free(new_pubkey); + + bool match = ptrlen_eq_ptrlen(ptrlen_from_strbuf(old_basepub), + ptrlen_from_strbuf(new_basepub)); + strbuf_free(old_basepub); + strbuf_free(new_basepub); + + if (!match) { + char *msg = dupprintf("Certificate is for a different public key"); + message_box(hwnd, msg, "PuTTYgen Error", MB_OK | MB_ICONERROR, + HELPCTXID(errors_cantloadkey)); + sfree(msg); + strbuf_free(pub); + return; + } + + strbuf *priv = strbuf_new_nm(); + ssh_key_private_blob(state->ssh2key.key, BinarySink_UPCAST(priv)); + ssh_key *newkey = ssh_key_new_priv( + alg, ptrlen_from_strbuf(pub), ptrlen_from_strbuf(priv)); + strbuf_free(pub); + strbuf_free(priv); + + if (!newkey) { + char *msg = dupprintf("Couldn't combine certificate with key"); + message_box(hwnd, msg, "PuTTYgen Error", MB_OK | MB_ICONERROR, + HELPCTXID(errors_cantloadkey)); + sfree(msg); + return; + } + + ssh_key_free(state->ssh2key.key); + state->ssh2key.key = newkey; + + update_ui_after_ssh2_pubkey_change(hwnd, state); + ui_set_state(hwnd, state, 2); +} + +void remove_certificate(HWND hwnd, struct MainDlgState *state) +{ + ssh_key *newkey = ssh_key_clone(ssh_key_base_key(state->ssh2key.key)); + ssh_key_free(state->ssh2key.key); + state->ssh2key.key = newkey; + update_ui_after_ssh2_pubkey_change(hwnd, state); + ui_set_state(hwnd, state, 2); +} + static void start_generating_key(HWND hwnd, struct MainDlgState *state) { static const char generating_msg[] = @@ -1250,6 +1388,11 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, menu1 = CreateMenu(); AppendMenu(menu1, MF_ENABLED, IDC_GENERATE, "&Generate key pair"); AppendMenu(menu1, MF_SEPARATOR, 0, 0); + AppendMenu(menu1, MF_ENABLED, IDC_ADDCERT, + "Add &certificate to key"); + AppendMenu(menu1, MF_ENABLED, IDC_REMCERT, + "Remove certificate from key"); + 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"); @@ -1870,6 +2013,30 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, } } break; + case IDC_ADDCERT: + if (HIWORD(wParam) != BN_CLICKED) + break; + state = + (struct MainDlgState *) GetWindowLongPtr(hwnd, GWLP_USERDATA); + if (state->key_exists && !state->generation_thread_exists) { + char filename[FILENAME_MAX]; + if (prompt_keyfile(hwnd, "Load certificate:", filename, false, + false)) { + Filename *fn = filename_from_str(filename); + add_certificate(hwnd, state, fn); + filename_free(fn); + } + } + break; + case IDC_REMCERT: + if (HIWORD(wParam) != BN_CLICKED) + break; + state = + (struct MainDlgState *) GetWindowLongPtr(hwnd, GWLP_USERDATA); + if (state->key_exists && !state->generation_thread_exists) { + remove_certificate(hwnd, state); + } + break; } return 0; case WM_DONEKEY: -- cgit v1.2.3 From df3a21d97b5f1d022d561cd58da843bf6a87340b Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 21 Apr 2022 10:55:44 +0100 Subject: Support for detached certificates in userauth. This is triggered by a new config option, or alternatively a -cert command-line option. You provide a certificate file (i.e. a public key containing one of the cert key formats), and then, whenever you authenticate with a private key that matches the public key inside that certificate, the certificate will be sent to the server in place of whatever public key it would have used before. I expect this to be more convenient for some users than the approach of baking the certificate into a modified version of the PPK file - especially users who want to use different certificates on the same key, either in sequence (if a CA continually reissues certificates with short lifetimes) or in parallel (if different hosts trust different CAs). In particular, this substitution is applied consistently, even when doing authentication via an agent. So if your bare private key is held in Pageant, you can _still_ specify a detached certificate, and PuTTY will spot that the key it's picked from Pageant matches that certificate, and do the same substitution. The detached certificate also overrides an existing certificate, if there was one on the public key already. --- cmdline.c | 10 ++ config.c | 4 + putty.h | 1 + settings.c | 2 + ssh/ppl.h | 6 +- ssh/ssh.c | 1 + ssh/userauth2-client.c | 256 ++++++++++++++++++++++++++++++++++++++++++++++--- 7 files changed, 264 insertions(+), 16 deletions(-) diff --git a/cmdline.c b/cmdline.c index 9f37f2a0..b9bddeb3 100644 --- a/cmdline.c +++ b/cmdline.c @@ -739,6 +739,16 @@ int cmdline_process_param(const char *p, char *value, filename_free(fn); } + if (!strcmp(p, "-cert")) { + Filename *fn; + RETURN(2); + UNAVAILABLE_IN(TOOLTYPE_NONNETWORK); + SAVEABLE(0); + fn = filename_from_str(value); + conf_set_filename(conf, CONF_detached_cert, fn); + filename_free(fn); + } + if (!strcmp(p, "-4") || !strcmp(p, "-ipv4")) { RETURN(1); SAVEABLE(1); diff --git a/config.c b/config.c index f48e6de7..3ab92bb8 100644 --- a/config.c +++ b/config.c @@ -2896,6 +2896,10 @@ void setup_config_box(struct controlbox *b, bool midsession, FILTER_KEY_FILES, false, "Select private key file", HELPCTX(ssh_auth_privkey), conf_filesel_handler, I(CONF_keyfile)); + ctrl_filesel(s, "Certificate to use with the private key:", 'e', + NULL, false, "Select certificate file", + HELPCTX(ssh_auth_privkey), + conf_filesel_handler, I(CONF_detached_cert)); #ifndef NO_GSSAPI /* diff --git a/putty.h b/putty.h index a7d7c002..44c82a41 100644 --- a/putty.h +++ b/putty.h @@ -1780,6 +1780,7 @@ NORETURN void cleanup_exit(int); X(BOOL, NONE, change_username) /* allow username switching in SSH-2 */ \ X(INT, INT, ssh_cipherlist) \ X(FILENAME, NONE, keyfile) \ + X(FILENAME, NONE, detached_cert) \ /* \ * Which SSH protocol to use. \ * For historical reasons, the current legal values for CONF_sshprot \ diff --git a/settings.c b/settings.c index 09701618..40e26b8d 100644 --- a/settings.c +++ b/settings.c @@ -625,6 +625,7 @@ void save_open_settings(settings_w *sesskey, Conf *conf) write_setting_s(sesskey, "LogHost", conf_get_str(conf, CONF_loghost)); write_setting_b(sesskey, "SSH2DES", conf_get_bool(conf, CONF_ssh2_des_cbc)); write_setting_filename(sesskey, "PublicKeyFile", conf_get_filename(conf, CONF_keyfile)); + write_setting_filename(sesskey, "DetachedCertificate", conf_get_filename(conf, CONF_detached_cert)); write_setting_s(sesskey, "RemoteCommand", conf_get_str(conf, CONF_remote_cmd)); write_setting_b(sesskey, "RFCEnviron", conf_get_bool(conf, CONF_rfc_environ)); write_setting_b(sesskey, "PassiveTelnet", conf_get_bool(conf, CONF_passive_telnet)); @@ -1042,6 +1043,7 @@ void load_open_settings(settings_r *sesskey, Conf *conf) #endif gppb(sesskey, "SshNoShell", false, conf, CONF_ssh_no_shell); gppfile(sesskey, "PublicKeyFile", conf, CONF_keyfile); + gppfile(sesskey, "DetachedCertificate", conf, CONF_detached_cert); gpps(sesskey, "RemoteCommand", "", conf, CONF_remote_cmd); gppb(sesskey, "RFCEnviron", false, conf, CONF_rfc_environ); gppb(sesskey, "PassiveTelnet", false, conf, CONF_passive_telnet); diff --git a/ssh/ppl.h b/ssh/ppl.h index c3625166..025a5615 100644 --- a/ssh/ppl.h +++ b/ssh/ppl.h @@ -110,10 +110,10 @@ PacketProtocolLayer *ssh2_transport_new( PacketProtocolLayer *ssh2_userauth_new( PacketProtocolLayer *successor_layer, const char *hostname, const char *fullhostname, - Filename *keyfile, bool show_banner, bool tryagent, bool notrivialauth, + Filename *keyfile, Filename *detached_cert, + bool show_banner, bool tryagent, bool notrivialauth, const char *default_username, bool change_username, - bool try_ki_auth, - bool try_gssapi_auth, bool try_gssapi_kex_auth, + bool try_ki_auth, bool try_gssapi_auth, bool try_gssapi_kex_auth, bool gssapi_fwd, struct ssh_connection_shared_gss_state *shgss); PacketProtocolLayer *ssh2_connection_new( Ssh *ssh, ssh_sharing_state *connshare, bool is_simple, diff --git a/ssh/ssh.c b/ssh/ssh.c index 0db68c59..c7e03ff8 100644 --- a/ssh/ssh.c +++ b/ssh/ssh.c @@ -255,6 +255,7 @@ static void ssh_got_ssh_version(struct ssh_version_receiver *rcv, userauth_layer = ssh2_userauth_new( connection_layer, ssh->savedhost, ssh->fullhostname, conf_get_filename(ssh->conf, CONF_keyfile), + conf_get_filename(ssh->conf, CONF_detached_cert), conf_get_bool(ssh->conf, CONF_ssh_show_banner), conf_get_bool(ssh->conf, CONF_tryagent), conf_get_bool(ssh->conf, CONF_ssh_no_trivial_userauth), diff --git a/ssh/userauth2-client.c b/ssh/userauth2-client.c index 69547f14..de71af7a 100644 --- a/ssh/userauth2-client.c +++ b/ssh/userauth2-client.c @@ -27,7 +27,7 @@ struct ssh2_userauth_state { int crState; PacketProtocolLayer *transport_layer, *successor_layer; - Filename *keyfile; + Filename *keyfile, *detached_cert_file; bool show_banner, tryagent, notrivialauth, change_username; char *hostname, *fullhostname; char *default_username; @@ -67,7 +67,7 @@ struct ssh2_userauth_state { char *locally_allocated_username; char *password; bool got_username; - strbuf *publickey_blob; + strbuf *publickey_blob, *detached_cert_blob, *cert_pubkey_diagnosed; bool privatekey_available, privatekey_encrypted; char *publickey_algorithm; char *publickey_comment; @@ -108,6 +108,8 @@ static void ssh2_userauth_agent_query(struct ssh2_userauth_state *, strbuf *); static void ssh2_userauth_agent_callback(void *, void *, int); static void ssh2_userauth_add_sigblob( struct ssh2_userauth_state *s, PktOut *pkt, ptrlen pkblob, ptrlen sigblob); +static void ssh2_userauth_add_alg_and_publickey( + struct ssh2_userauth_state *s, PktOut *pkt, ptrlen alg, ptrlen pkblob); static void ssh2_userauth_add_session_id( struct ssh2_userauth_state *s, strbuf *sigdata); #ifndef NO_GSSAPI @@ -128,7 +130,8 @@ static const PacketProtocolLayerVtable ssh2_userauth_vtable = { PacketProtocolLayer *ssh2_userauth_new( PacketProtocolLayer *successor_layer, const char *hostname, const char *fullhostname, - Filename *keyfile, bool show_banner, bool tryagent, bool notrivialauth, + Filename *keyfile, Filename *detached_cert_file, + bool show_banner, bool tryagent, bool notrivialauth, const char *default_username, bool change_username, bool try_ki_auth, bool try_gssapi_auth, bool try_gssapi_kex_auth, bool gssapi_fwd, struct ssh_connection_shared_gss_state *shgss) @@ -141,6 +144,7 @@ PacketProtocolLayer *ssh2_userauth_new( s->hostname = dupstr(hostname); s->fullhostname = dupstr(fullhostname); s->keyfile = filename_copy(keyfile); + s->detached_cert_file = filename_copy(detached_cert_file); s->show_banner = show_banner; s->tryagent = tryagent; s->notrivialauth = notrivialauth; @@ -187,6 +191,7 @@ static void ssh2_userauth_free(PacketProtocolLayer *ppl) if (s->auth_agent_query) agent_cancel_query(s->auth_agent_query); filename_free(s->keyfile); + filename_free(s->detached_cert_file); sfree(s->default_username); sfree(s->locally_allocated_username); sfree(s->hostname); @@ -197,6 +202,10 @@ static void ssh2_userauth_free(PacketProtocolLayer *ppl) sfree(s->publickey_algorithm); if (s->publickey_blob) strbuf_free(s->publickey_blob); + if (s->detached_cert_blob) + strbuf_free(s->detached_cert_blob); + if (s->cert_pubkey_diagnosed) + strbuf_free(s->cert_pubkey_diagnosed); strbuf_free(s->last_methods_string); if (s->banner_scc) stripctrl_free(s->banner_scc); @@ -331,6 +340,70 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) } } + /* + * If the user provided a detached certificate file, load that. + */ + if (!filename_is_null(s->detached_cert_file)) { + char *cert_error = NULL; + strbuf *cert_blob = strbuf_new(); + char *algname = NULL; + char *comment = NULL; + + ppl_logevent("Reading certificate file \"%s\"", + filename_to_str(s->detached_cert_file)); + int keytype = key_type(s->detached_cert_file); + if (!(keytype == SSH_KEYTYPE_SSH2_PUBLIC_RFC4716 || + keytype == SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH)) { + cert_error = dupstr(key_type_to_str(keytype)); + goto cert_load_done; + } + + const char *error; + bool success = ppk_loadpub_f( + s->detached_cert_file, &algname, + BinarySink_UPCAST(cert_blob), &comment, &error); + + if (!success) { + cert_error = dupstr(error); + goto cert_load_done; + } + + const ssh_keyalg *certalg = find_pubkey_alg(algname); + if (!certalg) { + cert_error = dupprintf( + "unrecognised certificate type '%s'", algname); + goto cert_load_done; + } + + if (!certalg->is_certificate) { + cert_error = dupprintf( + "key type '%s' is not a certificate", certalg->ssh_id); + goto cert_load_done; + } + + /* OK, store the certificate blob to substitute for the + * public blob in all publickey auth packets. */ + if (s->detached_cert_blob) + strbuf_free(s->detached_cert_blob); + s->detached_cert_blob = cert_blob; + cert_blob = NULL; /* prevent free */ + + cert_load_done: + if (cert_error) { + ppl_logevent("Unable to use this certificate file (%s)", + cert_error); + ppl_printf( + "Unable to use certificate file \"%s\" (%s)\r\n", + filename_to_str(s->detached_cert_file), cert_error); + sfree(cert_error); + } + + if (cert_blob) + strbuf_free(cert_blob); + sfree(algname); + sfree(comment); + } + /* * Find out about any keys Pageant has (but if there's a public * key configured, filter out all others). @@ -755,9 +828,9 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) put_stringz(s->pktout, "publickey"); /* method */ put_bool(s->pktout, false); /* no signature included */ - put_stringpl(s->pktout, s->agent_keyalg); - put_stringpl(s->pktout, ptrlen_from_strbuf( - s->agent_keys[s->agent_key_index].blob)); + ssh2_userauth_add_alg_and_publickey( + s, s->pktout, s->agent_keyalg, ptrlen_from_strbuf( + s->agent_keys[s->agent_key_index].blob)); pq_push(s->ppl.out_pq, s->pktout); s->type = AUTH_TYPE_PUBLICKEY_OFFER_QUIET; @@ -789,8 +862,8 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) put_stringz(s->pktout, "publickey"); /* method */ put_bool(s->pktout, true); /* signature included */ - put_stringpl(s->pktout, s->agent_keyalg); - put_stringpl(s->pktout, ptrlen_from_strbuf( + ssh2_userauth_add_alg_and_publickey( + s, s->pktout, s->agent_keyalg, ptrlen_from_strbuf( s->agent_keys[s->agent_key_index].blob)); /* Ask agent for signature. */ @@ -880,9 +953,9 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) put_stringz(s->pktout, "publickey"); /* method */ put_bool(s->pktout, false); /* no signature included */ - put_stringz(s->pktout, s->publickey_algorithm); - put_string(s->pktout, s->publickey_blob->s, - s->publickey_blob->len); + ssh2_userauth_add_alg_and_publickey( + s, s->pktout, ptrlen_from_asciz(s->publickey_algorithm), + ptrlen_from_strbuf(s->publickey_blob)); pq_push(s->ppl.out_pq, s->pktout); ppl_logevent("Offered public key"); @@ -999,10 +1072,12 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) put_stringz(s->pktout, s->successor_layer->vt->name); put_stringz(s->pktout, "publickey"); /* method */ put_bool(s->pktout, true); /* signature follows */ - put_stringz(s->pktout, s->publickey_algorithm); pkblob = strbuf_new(); ssh_key_public_blob(key->key, BinarySink_UPCAST(pkblob)); - put_string(s->pktout, pkblob->s, pkblob->len); + ssh2_userauth_add_alg_and_publickey( + s, s->pktout, + ptrlen_from_asciz(s->publickey_algorithm), + ptrlen_from_strbuf(pkblob)); /* * The data to be signed is: @@ -1773,6 +1848,161 @@ static void ssh2_userauth_agent_callback(void *uav, void *reply, int replylen) queue_idempotent_callback(&s->ppl.ic_process_queue); } +/* + * Helper function to add the algorithm and public key strings to a + * "publickey" auth packet. Deals with overriding both strings if the + * user has provided a detached certificate which matches the public + * key in question. + */ +static void ssh2_userauth_add_alg_and_publickey( + struct ssh2_userauth_state *s, PktOut *pkt, ptrlen alg, ptrlen pkblob) +{ + PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ + + if (s->detached_cert_blob) { + ptrlen detached_cert_pl = ptrlen_from_strbuf(s->detached_cert_blob); + strbuf *certbase = NULL, *pkbase = NULL; + bool done = false; + const ssh_keyalg *pkalg = find_pubkey_alg_len(alg); + ssh_key *certkey = NULL, *pk = NULL; + strbuf *fail_reason = strbuf_new(); + bool verbose = true; + + /* + * Whether or not we send the certificate, we're likely to + * generate a log message about it. But we don't want to log + * once for the offer and once for the real auth attempt, so + * we de-duplicate by remembering the last public key this + * function saw. */ + if (!s->cert_pubkey_diagnosed) + s->cert_pubkey_diagnosed = strbuf_new(); + if (ptrlen_eq_ptrlen(ptrlen_from_strbuf(s->cert_pubkey_diagnosed), + pkblob)) { + verbose = false; + } else { + /* Log this time, but arrange that we don't mention it next time */ + strbuf_clear(s->cert_pubkey_diagnosed); + put_datapl(s->cert_pubkey_diagnosed, pkblob); + } + + /* + * Check that the public key we're replacing is compatible + * with the certificate, in that they should have the same + * base public key. + */ + + const ssh_keyalg *certalg = pubkey_blob_to_alg(detached_cert_pl); + assert(certalg); /* we checked this before setting s->detached_blob */ + assert(certalg->is_certificate); /* and this too */ + + certkey = ssh_key_new_pub(certalg, detached_cert_pl); + if (!certkey) { + put_fmt(fail_reason, "certificate key file is invalid"); + goto no_match; + } + + certbase = strbuf_new(); + ssh_key_public_blob(ssh_key_base_key(certkey), + BinarySink_UPCAST(certbase)); + if (ptrlen_eq_ptrlen(pkblob, ptrlen_from_strbuf(certbase))) + goto match; /* yes, a match! */ + + /* + * If we reach here, the certificate's base key was not + * identical to the key we're given. But it might still be + * identical to the _base_ key of the key we're given, if we + * were using a differently certified version of the same key. + * In that situation, the detached cert should still override. + */ + if (!pkalg) { + put_fmt(fail_reason, "unable to identify algorithm of base key"); + goto no_match; + } + + pk = ssh_key_new_pub(pkalg, pkblob); + if (!pk) { + put_fmt(fail_reason, "base public key is invalid"); + goto no_match; + } + + pkbase = strbuf_new(); + ssh_key_public_blob(ssh_key_base_key(pk), BinarySink_UPCAST(pkbase)); + if (ptrlen_eq_ptrlen(ptrlen_from_strbuf(pkbase), + ptrlen_from_strbuf(certbase))) + goto match; /* yes, a match on 2nd attempt! */ + + /* Give up; we've tried to match these keys up and failed. */ + put_fmt(fail_reason, "base public key does not match certificate"); + goto no_match; + + match: + /* + * The two keys match, so insert the detached certificate into + * the output packet in place of the public key we were given. + * + * However, we need to be a bit careful with the algorithm + * name: we might need to upgrade it to one that matches the + * original algorithm name. (If we were asked to add an + * ssh-rsa key but were given algorithm name "rsa-sha2-512", + * then instead of the certificate's algorithm name + * ssh-rsa-cert-v01@... we need to write the corresponding + * SHA-512 name rsa-sha2-512-cert-v01@... .) + */ + if (verbose) { + ppl_logevent("Sending public key with certificate from \"%s\"", + filename_to_str(s->detached_cert_file)); + } + put_stringz(pkt, ssh_keyalg_related_alg(certalg, pkalg)->ssh_id); + put_stringpl(pkt, ptrlen_from_strbuf(s->detached_cert_blob)); + done = true; + goto out; + + no_match: + /* Log that we didn't send the certificate, if this public key + * isn't the same one as last call to this function. (Need to + * avoid verbosely logging once for the offer and once for the + * real auth attempt.) */ + if (verbose) { + ppl_logevent("Not substituting certificate \"%s\" for public " + "key: %s", filename_to_str(s->detached_cert_file), + fail_reason->s); + if (s->publickey_blob) { + /* If the user provided a specific key file to use (i.e. + * this wasn't just a key we picked opportunistically out + * of an agent), then they probably _care_ that we didn't + * send the certificate, so we should make a loud error + * message about it as well as just commenting in the + * Event Log. */ + ppl_printf("Unable to use certificate \"%s\" with public " + "key \"%s\": %s\r\n", + filename_to_str(s->detached_cert_file), + filename_to_str(s->keyfile), + fail_reason->s); + } + } + + out: + /* Whether we did that or not, free our stuff. */ + if (certbase) + strbuf_free(certbase); + if (pkbase) + strbuf_free(pkbase); + if (certkey) + ssh_key_free(certkey); + if (pk) + ssh_key_free(pk); + strbuf_free(fail_reason); + + /* And if we did, don't fall through to the alternative below */ + if (done) + return; + } + + /* In all other cases, just put in what we were given. */ + put_stringpl(pkt, alg); + put_stringpl(pkt, pkblob); +} + /* * 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. -- cgit v1.2.3 From 21d4754b6a0c98c6d75f5a374ff71f108263f02f Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 22 Apr 2022 12:07:24 +0100 Subject: Initial support for host certificates. Now we offer the OpenSSH certificate key types in our KEXINIT host key algorithm list, so that if the server has a certificate, they can send it to us. There's a new storage.h abstraction for representing a list of trusted host CAs, and which ones are trusted to certify hosts for what domains. This is stored outside the normal saved session data, because the whole point of host certificates is to avoid per-host faffing. Configuring this set of trusted CAs is done via a new GUI dialog box, separate from the main PuTTY config box (because it modifies a single set of settings across all saved sessions), which you can launch by clicking a button in the 'Host keys' pane. The GUI is pretty crude for the moment, and very much at a 'just about usable' stage right now. It will want some polishing. If we have no CA configured that matches the hostname, we don't offer to receive certified host keys in the first place. So for existing users who haven't set any of this up yet, nothing will immediately change. Currently, if we do offer to receive certified host keys and the server presents one signed by a CA we don't trust, PuTTY will bomb out unconditionally with an error, instead of offering a confirmation box. That's an unfinished part which I plan to fix before this goes into a release. --- config.c | 379 +++++++++++++++++++++++++++++++++++++++++++++++ defs.h | 2 + putty.h | 5 + ssh/kex2-client.c | 101 ++++++++++--- ssh/transport2.c | 104 +++++++++++-- ssh/transport2.h | 31 ++-- storage.h | 23 +++ unix/dialog.c | 81 ++++++++++ unix/storage.c | 145 +++++++++++++++++- utils/CMakeLists.txt | 1 + utils/host_ca_free.c | 14 ++ windows/dialog.c | 38 +++++ windows/putty-common.rc2 | 9 ++ windows/putty-rc.h | 1 + windows/storage.c | 123 +++++++++++++++ 15 files changed, 1014 insertions(+), 43 deletions(-) create mode 100644 utils/host_ca_free.c diff --git a/config.c b/config.c index 3ab92bb8..01d58abd 100644 --- a/config.c +++ b/config.c @@ -9,6 +9,7 @@ #include "putty.h" #include "dialog.h" #include "storage.h" +#include "tree234.h" #define PRINTER_DISABLED_STRING "None (printing disabled)" @@ -1762,6 +1763,13 @@ void proxy_type_handler(union control *ctrl, dlgparam *dlg, } } +static void host_ca_button_handler(union control *ctrl, dlgparam *dp, + void *data, int event) +{ + if (event == EVENT_ACTION) + show_ca_config_box(dp); +} + void setup_config_box(struct controlbox *b, bool midsession, int protocol, int protcfginfo) { @@ -2826,6 +2834,16 @@ void setup_config_box(struct controlbox *b, bool midsession, ctrl_columns(s, 1, 100); } + /* + * But there's no reason not to forbid access to the host CA + * configuration box, which is common across sessions in any + * case. + */ + s = ctrl_getset(b, "Connection/SSH/Host keys", "ca", + "Configure trusted certification authorities"); + c = ctrl_pushbutton(s, "Configure host CAs", NO_SHORTCUT, + HELPCTX(no_help), host_ca_button_handler, I(0)); + if (!midsession || !(protcfginfo == 1 || protcfginfo == -1)) { /* * The Connection/SSH/Cipher panel. @@ -3298,3 +3316,364 @@ void setup_config_box(struct controlbox *b, bool midsession, I(CONF_supdup_scroll)); } } + +struct ca_state { + union control *ca_name_edit; + union control *ca_reclist; + union control *ca_pubkey_edit; + union control *ca_wclist; + union control *ca_wc_edit; + char *name, *pubkey, *wc; + tree234 *ca_names; /* stores plain 'char *' */ + tree234 *host_wcs; /* stores plain 'char *' */ +}; + +static int ca_name_compare(void *av, void *bv) +{ + return strcmp((const char *)av, (const char *)bv); +} + +static inline void clear_string_tree(tree234 *t) +{ + char *p; + while ((p = delpos234(t, 0)) != NULL) + sfree(p); +} + +static void ca_state_free(void *vctx) +{ + struct ca_state *st = (struct ca_state *)vctx; + clear_string_tree(st->ca_names); + freetree234(st->ca_names); + clear_string_tree(st->host_wcs); + freetree234(st->host_wcs); + sfree(st->name); + sfree(st->wc); + sfree(st); +} + +static void ca_refresh_name_list(struct ca_state *st) +{ + clear_string_tree(st->ca_names); + + host_ca_enum *hce = enum_host_ca_start(); + if (hce) { + strbuf *namebuf = strbuf_new(); + + while (strbuf_clear(namebuf), enum_host_ca_next(hce, namebuf)) { + char *name = dupstr(namebuf->s); + char *added = add234(st->ca_names, name); + /* Just imaginable that concurrent filesystem access might + * cause a repetition; avoid leaking memory if so */ + if (added != name) + sfree(name); + } + + strbuf_free(namebuf); + enum_host_ca_finish(hce); + } +} + +static void ca_load_selected_record(struct ca_state *st, dlgparam *dp) +{ + int i = dlg_listbox_index(st->ca_reclist, dp); + if (i < 0) { + dlg_beep(dp); + return; + } + const char *name = index234(st->ca_names, i); + if (!name) { /* in case the list box and the tree got out of sync */ + dlg_beep(dp); + return; + } + host_ca *hca = host_ca_load(name); + if (!hca) { + char *msg = dupprintf("Unable to load host CA record '%s'", name); + dlg_error_msg(dp, msg); + sfree(msg); + return; + } + + sfree(st->name); + st->name = dupstr(hca->name); + + sfree(st->pubkey); + st->pubkey = strbuf_to_str( + base64_encode_sb(ptrlen_from_strbuf(hca->ca_public_key), 0)); + + clear_string_tree(st->host_wcs); + for (size_t i = 0; i < hca->n_hostname_wildcards; i++) { + char *name = dupstr(hca->hostname_wildcards[i]); + char *added = add234(st->host_wcs, name); + if (added != name) + sfree(name); /* de-duplicate, just in case */ + } + + host_ca_free(hca); + + dlg_refresh(st->ca_name_edit, dp); + dlg_refresh(st->ca_pubkey_edit, dp); + dlg_refresh(st->ca_wclist, dp); +} + +static void ca_ok_handler(union control *ctrl, dlgparam *dp, + void *data, int event) +{ + if (event == EVENT_ACTION) + dlg_end(dp, 0); +} + +static void ca_name_handler(union control *ctrl, dlgparam *dp, + void *data, int event) +{ + struct ca_state *st = (struct ca_state *)ctrl->generic.context.p; + if (event == EVENT_REFRESH) { + dlg_editbox_set(ctrl, dp, st->name); + } else if (event == EVENT_VALCHANGE) { + sfree(st->name); + st->name = dlg_editbox_get(ctrl, dp); + + /* + * Try to auto-select the typed name in the list. + */ + int index; + if (!findrelpos234(st->ca_names, st->name, NULL, REL234_GE, &index)) + index = count234(st->ca_names) - 1; + if (index >= 0) + dlg_listbox_select(st->ca_reclist, dp, index); + } +} + +static void ca_reclist_handler(union control *ctrl, dlgparam *dp, + void *data, int event) +{ + struct ca_state *st = (struct ca_state *)ctrl->generic.context.p; + if (event == EVENT_REFRESH) { + dlg_update_start(ctrl, dp); + dlg_listbox_clear(ctrl, dp); + const char *name; + for (int i = 0; (name = index234(st->ca_names, i)) != NULL; i++) + dlg_listbox_add(ctrl, dp, name); + dlg_update_done(ctrl, dp); + } else if (event == EVENT_ACTION) { + /* Double-clicking a session loads it */ + ca_load_selected_record(st, dp); + } +} + +static void ca_load_handler(union control *ctrl, dlgparam *dp, + void *data, int event) +{ + struct ca_state *st = (struct ca_state *)ctrl->generic.context.p; + if (event == EVENT_ACTION) { + ca_load_selected_record(st, dp); + } +} + +static void ca_save_handler(union control *ctrl, dlgparam *dp, + void *data, int event) +{ + struct ca_state *st = (struct ca_state *)ctrl->generic.context.p; + if (event == EVENT_ACTION) { + host_ca *hca = snew(host_ca); + memset(hca, 0, sizeof(*hca)); + hca->name = dupstr(st->name); + hca->ca_public_key = base64_decode_sb(ptrlen_from_asciz(st->pubkey)); + hca->n_hostname_wildcards = count234(st->host_wcs); + hca->hostname_wildcards = snewn(hca->n_hostname_wildcards, char *); + for (size_t i = 0; i < hca->n_hostname_wildcards; i++) + hca->hostname_wildcards[i] = dupstr(index234(st->host_wcs, i)); + char *error = host_ca_save(hca); + host_ca_free(hca); + + if (error) { + dlg_error_msg(dp, error); + sfree(error); + } else { + ca_refresh_name_list(st); + dlg_refresh(st->ca_reclist, dp); + } + } +} + +static void ca_delete_handler(union control *ctrl, dlgparam *dp, + void *data, int event) +{ + struct ca_state *st = (struct ca_state *)ctrl->generic.context.p; + if (event == EVENT_ACTION) { + int i = dlg_listbox_index(st->ca_reclist, dp); + if (i < 0) { + dlg_beep(dp); + return; + } + const char *name = index234(st->ca_names, i); + if (!name) { /* in case the list box and the tree got out of sync */ + dlg_beep(dp); + return; + } + + char *error = host_ca_delete(name); + if (error) { + dlg_error_msg(dp, error); + sfree(error); + } else { + ca_refresh_name_list(st); + dlg_refresh(st->ca_reclist, dp); + } + } +} + +static void ca_pubkey_handler(union control *ctrl, dlgparam *dp, + void *data, int event) +{ + struct ca_state *st = (struct ca_state *)ctrl->generic.context.p; + if (event == EVENT_REFRESH) { + dlg_editbox_set(ctrl, dp, st->pubkey); + } else if (event == EVENT_VALCHANGE) { + sfree(st->pubkey); + st->pubkey = dlg_editbox_get(ctrl, dp); + } +} + +static void ca_wclist_handler(union control *ctrl, dlgparam *dp, + void *data, int event) +{ + struct ca_state *st = (struct ca_state *)ctrl->generic.context.p; + if (event == EVENT_REFRESH) { + dlg_update_start(ctrl, dp); + dlg_listbox_clear(ctrl, dp); + const char *name; + for (int i = 0; (name = index234(st->host_wcs, i)) != NULL; i++) + dlg_listbox_add(ctrl, dp, name); + dlg_update_done(ctrl, dp); + } +} + +static void ca_wc_edit_handler(union control *ctrl, dlgparam *dp, + void *data, int event) +{ + struct ca_state *st = (struct ca_state *)ctrl->generic.context.p; + if (event == EVENT_REFRESH) { + dlg_editbox_set(ctrl, dp, st->wc); + } else if (event == EVENT_VALCHANGE) { + sfree(st->wc); + st->wc = dlg_editbox_get(ctrl, dp); + } +} + +static void ca_wc_add_handler(union control *ctrl, dlgparam *dp, + void *data, int event) +{ + struct ca_state *st = (struct ca_state *)ctrl->generic.context.p; + if (event == EVENT_ACTION) { + if (!st->wc) { + dlg_beep(dp); + return; + } + + if (add234(st->host_wcs, st->wc) == st->wc) { + dlg_refresh(st->ca_wclist, dp); + } else { + sfree(st->wc); + } + + st->wc = dupstr(""); + dlg_refresh(st->ca_wc_edit, dp); + } +} + +static void ca_wc_rem_handler(union control *ctrl, dlgparam *dp, + void *data, int event) +{ + struct ca_state *st = (struct ca_state *)ctrl->generic.context.p; + if (event == EVENT_ACTION) { + int i = dlg_listbox_index(st->ca_wclist, dp); + if (i < 0) { + dlg_beep(dp); + return; + } + char *wc = delpos234(st->host_wcs, i); + if (!wc) { + dlg_beep(dp); + return; + } + + sfree(st->wc); + st->wc = wc; + dlg_refresh(st->ca_wclist, dp); + dlg_refresh(st->ca_wc_edit, dp); + } +} + +void setup_ca_config_box(struct controlbox *b) +{ + struct controlset *s; + union control *c; + + /* Internal state for manipulating the host CA system */ + struct ca_state *st = (struct ca_state *)ctrl_alloc_with_free( + b, sizeof(struct ca_state), ca_state_free); + memset(st, 0, sizeof(*st)); + st->name = dupstr(""); + st->pubkey = dupstr(""); + st->ca_names = newtree234(ca_name_compare); + st->host_wcs = newtree234(ca_name_compare); + ca_refresh_name_list(st); + + /* Action area, with the Done button in it */ + s = ctrl_getset(b, "", "", ""); + ctrl_columns(s, 5, 20, 20, 20, 20, 20); + c = ctrl_pushbutton(s, "Done", 'o', HELPCTX(no_help), + ca_ok_handler, P(st)); + c->button.isdefault = true; + c->generic.column = 4; + + /* Load/save box, as similar as possible to the main saved sessions one */ + s = ctrl_getset(b, "Main", "loadsave", + "Load, save or delete a host CA record"); + ctrl_columns(s, 2, 75, 25); + c = ctrl_editbox(s, "Name for this CA (shown in log messages)", + 'n', 100, HELPCTX(no_help), + ca_name_handler, P(st), P(NULL)); + c->generic.column = 0; + st->ca_name_edit = c; + /* 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); + c = ctrl_listbox(s, NULL, NO_SHORTCUT, HELPCTX(no_help), + ca_reclist_handler, P(st)); + c->generic.column = 0; + c->listbox.height = 6; + st->ca_reclist = c; + c = ctrl_pushbutton(s, "Load", 'l', HELPCTX(no_help), + ca_load_handler, P(st)); + c->generic.column = 1; + c = ctrl_pushbutton(s, "Save", 'v', HELPCTX(no_help), + ca_save_handler, P(st)); + c->generic.column = 1; + c = ctrl_pushbutton(s, "Delete", 'd', HELPCTX(no_help), + ca_delete_handler, P(st)); + c->generic.column = 1; + + /* Box containing the details of a specific CA record */ + s = ctrl_getset(b, "Main", "details", "Details of a host CA record"); + c = ctrl_editbox(s, "Public key of certification authority", 'k', 100, + HELPCTX(no_help), ca_pubkey_handler, P(st), P(NULL)); + st->ca_pubkey_edit = c; + c = ctrl_listbox(s, "Hostname patterns this key is trusted to certify", + NO_SHORTCUT, HELPCTX(no_help), ca_wclist_handler, P(st)); + c->listbox.height = 3; + st->ca_wclist = c; + ctrl_columns(s, 3, 70, 15, 15); + c = ctrl_editbox(s, "Hostname pattern to add", 'h', 100, + HELPCTX(no_help), ca_wc_edit_handler, P(st), P(NULL)); + c->generic.column = 0; + st->ca_wc_edit = c; + c = ctrl_pushbutton(s, "Add", NO_SHORTCUT, HELPCTX(no_help), + ca_wc_add_handler, P(st)); + c->generic.column = 1; + c = ctrl_pushbutton(s, "Remove", NO_SHORTCUT, HELPCTX(no_help), + ca_wc_rem_handler, P(st)); + c->generic.column = 2; +} diff --git a/defs.h b/defs.h index 37cc7979..d9f2e355 100644 --- a/defs.h +++ b/defs.h @@ -176,6 +176,8 @@ typedef struct dlgparam dlgparam; typedef struct settings_w settings_w; typedef struct settings_r settings_r; typedef struct settings_e settings_e; +typedef struct host_ca host_ca; +typedef struct host_ca_enum host_ca_enum; typedef struct SessionSpecial SessionSpecial; diff --git a/putty.h b/putty.h index 44c82a41..5ceedbe1 100644 --- a/putty.h +++ b/putty.h @@ -2586,6 +2586,11 @@ void conf_fontsel_handler(union control *ctrl, dlgparam *dlg, void setup_config_box(struct controlbox *b, bool midsession, int protocol, int protcfginfo); +void setup_ca_config_box(struct controlbox *b); + +/* Platforms provide this to be called from config.c */ +void show_ca_config_box(dlgparam *dlg); + /* Visible outside config.c so that platforms can use it to recognise * the proxy type control */ void proxy_type_handler(union control *ctrl, dlgparam *dlg, diff --git a/ssh/kex2-client.c b/ssh/kex2-client.c index ff78840a..c01bd0fe 100644 --- a/ssh/kex2-client.c +++ b/ssh/kex2-client.c @@ -718,7 +718,8 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) } } - s->keystr = (s->hkey ? ssh_key_cache_str(s->hkey) : NULL); + s->keystr = (s->hkey && !ssh_key_alg(s->hkey)->is_certificate ? + ssh_key_cache_str(s->hkey) : NULL); #ifndef NO_GSSAPI if (s->gss_kex_used) { /* @@ -851,19 +852,83 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) } } + ssh2_userkey uk = { .key = s->hkey, .comment = NULL }; + char **fingerprints = ssh2_all_fingerprints(s->hkey); + + FingerprintType fptype_default = + ssh2_pick_default_fingerprint(fingerprints); + ppl_logevent("Host key fingerprint is:"); + ppl_logevent("%s", fingerprints[fptype_default]); + /* - * Authenticate remote host: verify host key. (We've already - * checked the signature of the exchange hash.) + * Authenticate remote host: verify host key, either by + * certification or by the local host key cache. + * + * (We've already checked the signature of the exchange + * hash.) */ - { - ssh2_userkey uk = { .key = s->hkey, .comment = NULL }; - char *keydisp = ssh2_pubkey_openssh_str(&uk); - char **fingerprints = ssh2_all_fingerprints(s->hkey); + if (ssh_key_alg(s->hkey)->is_certificate) { + ssh2_free_all_fingerprints(fingerprints); - FingerprintType fptype_default = - ssh2_pick_default_fingerprint(fingerprints); - ppl_logevent("Host key fingerprint is:"); - ppl_logevent("%s", fingerprints[fptype_default]); + char *base_fp = ssh2_fingerprint(ssh_key_base_key(s->hkey), + fptype_default); + ppl_logevent("Host key is a certificate, whose base key has " + "fingerprint:"); + ppl_logevent("%s", base_fp); + sfree(base_fp); + + strbuf *id_string = strbuf_new(); + StripCtrlChars *id_string_scc = stripctrl_new( + BinarySink_UPCAST(id_string), false, L'\0'); + ssh_key_cert_id_string( + s->hkey, BinarySink_UPCAST(id_string_scc)); + stripctrl_free(id_string_scc); + ppl_logevent("Certificate ID string is \"%s\"", id_string->s); + strbuf_free(id_string); + + strbuf *ca_pub = strbuf_new(); + ssh_key_ca_public_blob(s->hkey, BinarySink_UPCAST(ca_pub)); + host_ca hca_search = { .ca_public_key = ca_pub }; + host_ca *hca_found = find234(s->host_cas, &hca_search, NULL); + + char *ca_fp = ssh2_fingerprint_blob(ptrlen_from_strbuf(ca_pub), + fptype_default); + ppl_logevent("Fingerprint of certification authority:"); + ppl_logevent("%s", ca_fp); + sfree(ca_fp); + + strbuf_free(ca_pub); + + strbuf *error = strbuf_new(); + bool cert_ok = false; + + if (!hca_found) { + put_fmt(error, "Certification authority is not trusted"); + } else { + ppl_logevent("Certification authority matches '%s'", + hca_found->name); + cert_ok = ssh_key_check_cert( + s->hkey, + true, /* host certificate */ + ptrlen_from_asciz(s->savedhost), + time(NULL), + BinarySink_UPCAST(error)); + } + if (cert_ok) { + strbuf_free(error); + ppl_logevent("Accepted certificate"); + } else { + ppl_logevent("Rejected host key certificate: %s", + error->s); + ssh_sw_abort(s->ppl.ssh, + "Rejected host key certificate: %s", + error->s); + *aborted = true; + strbuf_free(error); + return; + } + } else { + char *keydisp = ssh2_pubkey_openssh_str(&uk); s->spr = verify_ssh_host_key( ppl_get_iseat(&s->ppl), s->conf, s->savedhost, s->savedport, @@ -872,15 +937,15 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) ssh2_free_all_fingerprints(fingerprints); sfree(keydisp); - } #ifdef FUZZING - s->spr = SPR_OK; + s->spr = SPR_OK; #endif - crMaybeWaitUntilV(s->spr.kind != SPRK_INCOMPLETE); - if (spr_is_abort(s->spr)) { - *aborted = true; - ssh_spr_close(s->ppl.ssh, s->spr, "host key verification"); - return; + crMaybeWaitUntilV(s->spr.kind != SPRK_INCOMPLETE); + if (spr_is_abort(s->spr)) { + *aborted = true; + ssh_spr_close(s->ppl.ssh, s->spr, "host key verification"); + return; + } } /* diff --git a/ssh/transport2.c b/ssh/transport2.c index d1231069..ebb76be1 100644 --- a/ssh/transport2.c +++ b/ssh/transport2.c @@ -113,6 +113,7 @@ static const char *const kexlist_descr[NKEXLIST] = { }; static int weak_algorithm_compare(void *av, void *bv); +static int ca_blob_compare(void *av, void *bv); PacketProtocolLayer *ssh2_transport_new( Conf *conf, const char *host, int port, const char *fullhostname, @@ -134,6 +135,7 @@ PacketProtocolLayer *ssh2_transport_new( s->server_greeting = dupstr(server_greeting); s->stats = stats; s->hostkeyblob = strbuf_new(); + s->host_cas = newtree234(ca_blob_compare); pq_in_init(&s->pq_in_higher); pq_out_init(&s->pq_out_higher); @@ -212,6 +214,12 @@ static void ssh2_transport_free(PacketProtocolLayer *ppl) sfree(s->keystr); sfree(s->hostkey_str); strbuf_free(s->hostkeyblob); + { + host_ca *hca; + while ( (hca = delpos234(s->host_cas, 0)) ) + host_ca_free(hca); + freetree234(s->host_cas); + } if (s->hkey && !s->hostkeys) { ssh_key_free(s->hkey); s->hkey = NULL; @@ -493,7 +501,7 @@ static void ssh2_write_kexinit_lists( struct kexinit_algorithm_list kexlists[NKEXLIST], Conf *conf, const SshServerConfig *ssc, int remote_bugs, const char *hk_host, int hk_port, const ssh_keyalg *hk_prev, - ssh_transient_hostkey_cache *thc, + ssh_transient_hostkey_cache *thc, tree234 *host_cas, ssh_key *const *our_hostkeys, int our_nhostkeys, bool first_time, bool can_gssapi_keyex, bool transient_hostkey_mode) { @@ -672,33 +680,98 @@ static void ssh2_write_kexinit_lists( * they surely _do_ want to be alerted that a server * they're actually connecting to is using it. */ + + bool accept_certs = false; + { + host_ca_enum *handle = enum_host_ca_start(); + if (handle) { + strbuf *name = strbuf_new(); + while (strbuf_clear(name), enum_host_ca_next(handle, name)) { + host_ca *hca = host_ca_load(name->s); + if (!hca) + continue; + + bool match = false; + for (size_t i = 0, e = hca->n_hostname_wildcards; + i < e; i++) { + if (wc_match(hca->hostname_wildcards[i], hk_host)) { + match = true; + break; + } + } + + if (match && hca->ca_public_key) { + accept_certs = true; + add234(host_cas, hca); + } else { + host_ca_free(hca); + } + } + enum_host_ca_finish(handle); + strbuf_free(name); + } + } + + if (accept_certs) { + /* Add all the certificate algorithms first, in preference order */ + warn = false; + for (i = 0; i < n_preferred_hk; i++) { + if (preferred_hk[i] == HK_WARN) + warn = true; + for (j = 0; j < lenof(ssh2_hostkey_algs); j++) { + const struct ssh_signkey_with_user_pref_id *a = + &ssh2_hostkey_algs[j]; + if (!a->alg->is_certificate) + continue; + if (a->id != preferred_hk[i]) + continue; + alg = ssh2_kexinit_addalg(&kexlists[KEXLIST_HOSTKEY], + a->alg->ssh_id); + alg->u.hk.hostkey = a->alg; + alg->u.hk.warn = warn; + } + } + } + + /* Next, add uncertified algorithms we already know a key for + * (unless configured not to do that) */ warn = false; for (i = 0; i < n_preferred_hk; i++) { if (preferred_hk[i] == HK_WARN) warn = true; for (j = 0; j < lenof(ssh2_hostkey_algs); j++) { - if (ssh2_hostkey_algs[j].id != preferred_hk[i]) + const struct ssh_signkey_with_user_pref_id *a = + &ssh2_hostkey_algs[j]; + if (a->alg->is_certificate || !a->alg->cache_id) + continue; + if (a->id != preferred_hk[i]) continue; if (conf_get_bool(conf, CONF_ssh_prefer_known_hostkeys) && have_ssh_host_key(hk_host, hk_port, - ssh2_hostkey_algs[j].alg->cache_id)) { + a->alg->cache_id)) { alg = ssh2_kexinit_addalg(&kexlists[KEXLIST_HOSTKEY], - ssh2_hostkey_algs[j].alg->ssh_id); - alg->u.hk.hostkey = ssh2_hostkey_algs[j].alg; + a->alg->ssh_id); + alg->u.hk.hostkey = a->alg; alg->u.hk.warn = warn; } } } + + /* And finally, everything else */ warn = false; for (i = 0; i < n_preferred_hk; i++) { if (preferred_hk[i] == HK_WARN) warn = true; for (j = 0; j < lenof(ssh2_hostkey_algs); j++) { - if (ssh2_hostkey_algs[j].id != preferred_hk[i]) + const struct ssh_signkey_with_user_pref_id *a = + &ssh2_hostkey_algs[j]; + if (a->alg->is_certificate) + continue; + if (a->id != preferred_hk[i]) continue; alg = ssh2_kexinit_addalg(&kexlists[KEXLIST_HOSTKEY], - ssh2_hostkey_algs[j].alg->ssh_id); - alg->u.hk.hostkey = ssh2_hostkey_algs[j].alg; + a->alg->ssh_id); + alg->u.hk.hostkey = a->alg; alg->u.hk.warn = warn; } } @@ -1201,7 +1274,7 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl) ssh2_write_kexinit_lists( BinarySink_UPCAST(s->outgoing_kexinit), s->kexlists, s->conf, s->ssc, s->ppl.remote_bugs, - s->savedhost, s->savedport, s->hostkey_alg, s->thc, + s->savedhost, s->savedport, s->hostkey_alg, s->thc, s->host_cas, s->hostkeys, s->nhostkeys, !s->got_session_id, s->can_gssapi_keyex, s->gss_kex_used && !s->need_gss_transient_hostkey); @@ -1271,6 +1344,7 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl) for (i = 0; i < nhk; i++) { j = hks[i]; if (ssh2_hostkey_algs[j].alg != s->hostkey_alg && + ssh2_hostkey_algs[j].alg->cache_id && !have_ssh_host_key(s->savedhost, s->savedport, ssh2_hostkey_algs[j].alg->cache_id)) { s->uncert_hostkeys[s->n_uncert_hostkeys++] = j; @@ -2154,6 +2228,18 @@ static int weak_algorithm_compare(void *av, void *bv) return a < b ? -1 : a > b ? +1 : 0; } +static int ca_blob_compare(void *av, void *bv) +{ + host_ca *a = (host_ca *)av, *b = (host_ca *)bv; + strbuf *apk = a->ca_public_key, *bpk = b->ca_public_key; + /* Ordering by public key is arbitrary here, so do whatever is easiest */ + if (apk->len < bpk->len) + return -1; + if (apk->len > bpk->len) + return +1; + return memcmp(apk->u, bpk->u, apk->len); +} + /* * Wrapper on seat_confirm_weak_crypto_primitive(), which uses the * tree234 s->weak_algorithms_consented_to to ensure we ask at most diff --git a/ssh/transport2.h b/ssh/transport2.h index dc62f71f..72cd5fba 100644 --- a/ssh/transport2.h +++ b/ssh/transport2.h @@ -49,16 +49,25 @@ struct kexinit_algorithm_list { size_t nalgs, algsize; }; -#define HOSTKEY_ALGORITHMS(X) \ - X(HK_ED25519, ssh_ecdsa_ed25519) \ - X(HK_ED448, ssh_ecdsa_ed448) \ - X(HK_ECDSA, ssh_ecdsa_nistp256) \ - X(HK_ECDSA, ssh_ecdsa_nistp384) \ - X(HK_ECDSA, ssh_ecdsa_nistp521) \ - X(HK_DSA, ssh_dsa) \ - X(HK_RSA, ssh_rsa_sha512) \ - X(HK_RSA, ssh_rsa_sha256) \ - X(HK_RSA, ssh_rsa) \ +#define HOSTKEY_ALGORITHMS(X) \ + X(HK_ED25519, ssh_ecdsa_ed25519) \ + X(HK_ED448, ssh_ecdsa_ed448) \ + X(HK_ECDSA, ssh_ecdsa_nistp256) \ + X(HK_ECDSA, ssh_ecdsa_nistp384) \ + X(HK_ECDSA, ssh_ecdsa_nistp521) \ + X(HK_DSA, ssh_dsa) \ + X(HK_RSA, ssh_rsa_sha512) \ + X(HK_RSA, ssh_rsa_sha256) \ + X(HK_RSA, ssh_rsa) \ + X(HK_ED25519, opensshcert_ssh_ecdsa_ed25519) \ + /* OpenSSH defines no certified version of Ed448 */ \ + X(HK_ECDSA, opensshcert_ssh_ecdsa_nistp256) \ + X(HK_ECDSA, opensshcert_ssh_ecdsa_nistp384) \ + X(HK_ECDSA, opensshcert_ssh_ecdsa_nistp521) \ + X(HK_DSA, opensshcert_ssh_dsa) \ + X(HK_RSA, opensshcert_ssh_rsa_sha512) \ + X(HK_RSA, opensshcert_ssh_rsa_sha256) \ + X(HK_RSA, opensshcert_ssh_rsa) \ /* end of list */ #define COUNT_HOSTKEY_ALGORITHM(type, alg) +1 #define N_HOSTKEY_ALGORITHMS (0 HOSTKEY_ALGORITHMS(COUNT_HOSTKEY_ALGORITHM)) @@ -168,6 +177,8 @@ struct ssh2_transport_state { bool gss_kex_used; + tree234 *host_cas; + int nbits, pbits; bool warn_kex, warn_hk, warn_cscipher, warn_sccipher; mp_int *p, *g, *e, *f; diff --git a/storage.h b/storage.h index 3e03181a..df0c3e29 100644 --- a/storage.h +++ b/storage.h @@ -91,6 +91,29 @@ int check_stored_host_key(const char *hostname, int port, void store_host_key(const char *hostname, int port, const char *keytype, const char *key); +/* ---------------------------------------------------------------------- + * Functions to access PuTTY's configuration for trusted host + * certification authorities. This must be stored separately from the + * saved-session data, because the whole point is to avoid having to + * configure CAs separately per session. + */ + +struct host_ca { + char *name; + strbuf *ca_public_key; + char **hostname_wildcards; + size_t n_hostname_wildcards; +}; + +host_ca_enum *enum_host_ca_start(void); +bool enum_host_ca_next(host_ca_enum *handle, strbuf *out); +void enum_host_ca_finish(host_ca_enum *handle); + +host_ca *host_ca_load(const char *name); +char *host_ca_save(host_ca *); /* NULL on success, or dynamic error msg */ +char *host_ca_delete(const char *name); /* likewise */ +void host_ca_free(host_ca *); + /* ---------------------------------------------------------------------- * Functions to access PuTTY's random number seed file. */ diff --git a/unix/dialog.c b/unix/dialog.c index 7d1514fd..edd08314 100644 --- a/unix/dialog.c +++ b/unix/dialog.c @@ -4219,3 +4219,84 @@ int gtkdlg_askappend(Seat *seat, Filename *filename, return -1; /* dialog still in progress */ } + +struct ca_config_box { + GtkWidget *window; + struct controlbox *cb; + struct Shortcuts scs; + dlgparam dp; +}; +static struct ca_config_box *cacfg; /* one of these, cross-instance */ + +static void cacfg_destroy(GtkWidget *widget, gpointer data) +{ + cacfg->window = NULL; + dlg_cleanup(&cacfg->dp); + ctrl_free_box(cacfg->cb); + cacfg->cb = NULL; +} +void show_ca_config_box(dlgparam *dp) +{ + if (!cacfg) { + cacfg = snew(struct ca_config_box); + memset(cacfg, 0, sizeof(*cacfg)); + } + + if (cacfg->window) { + /* This dialog box is already displayed; re-focus it */ + gtk_widget_grab_focus(cacfg->window); + return; + } + + dlg_init(&cacfg->dp); + for (size_t i = 0; i < lenof(cacfg->scs.sc); i++) { + cacfg->scs.sc[i].action = SHORTCUT_EMPTY; + } + + cacfg->cb = ctrl_new_box(); + setup_ca_config_box(cacfg->cb); + + cacfg->window = our_dialog_new(); + gtk_window_set_title(GTK_WINDOW(cacfg->window), + "PuTTY trusted host certification authorities"); + gtk_widget_set_size_request( + cacfg->window, string_width( + "ecdsa-sha2-nistp256 256 SHA256:hsO5a8MYGzBoa2gW5" + "dLV2vl7bTnCPjw64x3kLkz6BY8"), -1); + + /* Set up everything else */ + for (int i = 0; i < cacfg->cb->nctrlsets; i++) { + struct controlset *s = cacfg->cb->ctrlsets[i]; + GtkWidget *w = layout_ctrls(&cacfg->dp, NULL, &cacfg->scs, s, + GTK_WINDOW(cacfg->window)); + gtk_container_set_border_width(GTK_CONTAINER(w), 10); + gtk_widget_show(w); + + if (!*s->pathname) { + our_dialog_set_action_area(GTK_WINDOW(cacfg->window), w); + } else { + our_dialog_add_to_content_area(GTK_WINDOW(cacfg->window), w, + true, true, 0); + } + } + + cacfg->dp.data = cacfg; + cacfg->dp.shortcuts = &cacfg->scs; + cacfg->dp.lastfocus = NULL; + cacfg->dp.retval = 0; + cacfg->dp.window = cacfg->window; + + dlg_refresh(NULL, &cacfg->dp); + + if (dp) { + set_transient_window_pos(dp->window, cacfg->window); + } else { + gtk_window_set_position(GTK_WINDOW(cacfg->window), GTK_WIN_POS_CENTER); + } + gtk_widget_show(cacfg->window); + + g_signal_connect(G_OBJECT(cacfg->window), "destroy", + G_CALLBACK(cacfg_destroy), NULL); + g_signal_connect(G_OBJECT(cacfg->window), "key_press_event", + G_CALLBACK(win_key_press), &cacfg->dp); +} diff --git a/unix/storage.c b/unix/storage.c index c61fb526..8be482cd 100644 --- a/unix/storage.c +++ b/unix/storage.c @@ -28,7 +28,7 @@ enum { INDEX_DIR, INDEX_HOSTKEYS, INDEX_HOSTKEYS_TMP, INDEX_RANDSEED, - INDEX_SESSIONDIR, INDEX_SESSION, + INDEX_SESSIONDIR, INDEX_SESSION, INDEX_HOSTCADIR, INDEX_HOSTCA }; static const char hex[16] = "0123456789ABCDEF"; @@ -202,6 +202,23 @@ static char *make_filename(int index, const char *subname) sfree(tmp); return ret; } + if (index == INDEX_HOSTCADIR) { + env = getenv("PUTTYSSHHOSTCAS"); + if (env) + return dupstr(env); + tmp = make_filename(INDEX_DIR, NULL); + ret = dupprintf("%s/sshhostcas", tmp); + sfree(tmp); + return ret; + } + if (index == INDEX_HOSTCA) { + strbuf *sb = strbuf_new(); + tmp = make_filename(INDEX_HOSTCADIR, NULL); + put_fmt(sb, "%s/", tmp); + sfree(tmp); + make_session_filename(subname, sb); + return strbuf_to_str(sb); + } tmp = make_filename(INDEX_DIR, NULL); ret = dupprintf("%s/ERROR", tmp); sfree(tmp); @@ -545,25 +562,25 @@ settings_e *enum_settings_start(void) return toret; } -bool enum_settings_next(settings_e *handle, strbuf *out) +static bool enum_dir_next(DIR *dp, int index, strbuf *out) { struct dirent *de; struct stat st; strbuf *fullpath; - if (!handle->dp) - return NULL; + if (!dp) + return false; fullpath = strbuf_new(); - char *sessiondir = make_filename(INDEX_SESSIONDIR, NULL); + char *sessiondir = make_filename(index, NULL); put_dataz(fullpath, sessiondir); sfree(sessiondir); put_byte(fullpath, '/'); size_t baselen = fullpath->len; - while ( (de = readdir(handle->dp)) != NULL ) { + while ( (de = readdir(dp)) != NULL ) { strbuf_shrink_to(fullpath, baselen); put_dataz(fullpath, de->d_name); @@ -579,6 +596,11 @@ bool enum_settings_next(settings_e *handle, strbuf *out) return false; } +bool enum_settings_next(settings_e *handle, strbuf *out) +{ + return enum_dir_next(handle->dp, INDEX_SESSIONDIR, out); +} + void enum_settings_finish(settings_e *handle) { if (handle->dp) @@ -586,6 +608,117 @@ void enum_settings_finish(settings_e *handle) sfree(handle); } +struct host_ca_enum { + DIR *dp; +}; + +host_ca_enum *enum_host_ca_start(void) +{ + host_ca_enum *handle = snew(host_ca_enum); + + char *filename = make_filename(INDEX_HOSTCADIR, NULL); + handle->dp = opendir(filename); + sfree(filename); + + return handle; +} + +bool enum_host_ca_next(host_ca_enum *handle, strbuf *out) +{ + return enum_dir_next(handle->dp, INDEX_HOSTCADIR, out); +} + +void enum_host_ca_finish(host_ca_enum *handle) +{ + if (handle->dp) + closedir(handle->dp); + sfree(handle); +} + +host_ca *host_ca_load(const char *name) +{ + char *filename = make_filename(INDEX_HOSTCA, name); + FILE *fp = fopen(filename, "r"); + sfree(filename); + if (!fp) + return NULL; + + host_ca *hca = snew(host_ca); + memset(hca, 0, sizeof(*hca)); + hca->name = dupstr(name); + + size_t wcsize = 0; + char *line; + + while ( (line = fgetline(fp)) ) { + char *value = strchr(line, '='); + + if (!value) { + sfree(line); + continue; + } + *value++ = '\0'; + value[strcspn(value, "\r\n")] = '\0'; /* trim trailing NL */ + + if (!strcmp(line, "PublicKey")) { + hca->ca_public_key = base64_decode_sb(ptrlen_from_asciz(value)); + } else if (!strcmp(line, "MatchHosts")) { + sgrowarray(hca->hostname_wildcards, wcsize, + hca->n_hostname_wildcards); + hca->hostname_wildcards[hca->n_hostname_wildcards++] = + dupstr(value); + } + + sfree(line); + } + + return hca; +} + +char *host_ca_save(host_ca *hca) +{ + if (!*hca->name) + return dupstr("CA record must have a name"); + + char *filename = make_filename(INDEX_HOSTCA, hca->name); + FILE *fp = fopen(filename, "w"); + if (!fp) + return dupprintf("Unable to open file '%s'", filename); + + fprintf(fp, "PublicKey="); + base64_encode_fp(fp, ptrlen_from_strbuf(hca->ca_public_key), 0); + fprintf(fp, "\n"); + + for (size_t i = 0; i < hca->n_hostname_wildcards; i++) + fprintf(fp, "MatchHosts=%s\n", hca->hostname_wildcards[i]); + + bool bad = ferror(fp); + if (fclose(fp) < 0) + bad = true; + + char *err = NULL; + if (bad) + err = dupprintf("Unable to write file '%s'", filename); + + sfree(filename); + return err; +} + +char *host_ca_delete(const char *name) +{ + if (!*name) + return dupstr("CA record must have a name"); + char *filename = make_filename(INDEX_HOSTCA, name); + bool bad = remove(filename) < 0; + + char *err = NULL; + if (bad) + err = dupprintf("Unable to delete file '%s'", filename); + + sfree(filename); + return err; +} + /* * Lines in the host keys file are of the form * diff --git a/utils/CMakeLists.txt b/utils/CMakeLists.txt index 74ce4d54..949166f5 100644 --- a/utils/CMakeLists.txt +++ b/utils/CMakeLists.txt @@ -25,6 +25,7 @@ add_sources_from_current_dir(utils encode_utf8.c encode_wide_string_as_utf8.c fgetline.c + host_ca_free.c host_strchr.c host_strchr_internal.c host_strcspn.c diff --git a/utils/host_ca_free.c b/utils/host_ca_free.c new file mode 100644 index 00000000..5fb3a46c --- /dev/null +++ b/utils/host_ca_free.c @@ -0,0 +1,14 @@ +#include "defs.h" +#include "misc.h" +#include "storage.h" + +void host_ca_free(host_ca *hca) +{ + sfree(hca->name); + if (hca->ca_public_key) + strbuf_free(hca->ca_public_key); + for (size_t i = 0; i < hca->n_hostname_wildcards; i++) + sfree(hca->hostname_wildcards[i]); + sfree(hca->hostname_wildcards); + sfree(hca); +} diff --git a/windows/dialog.c b/windows/dialog.c index 7e0c92ad..774214db 100644 --- a/windows/dialog.c +++ b/windows/dialog.c @@ -1171,3 +1171,41 @@ void old_keyfile_warning(void) sfree(msg); sfree(title); } + +static INT_PTR CAConfigProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, + void *ctx) +{ + PortableDialogStuff *pds = (PortableDialogStuff *)ctx; + + switch (msg) { + case WM_INITDIALOG: + pds_initdialog_start(pds, hwnd); + + SendMessage(hwnd, WM_SETICON, (WPARAM) ICON_BIG, + (LPARAM) LoadIcon(hinst, MAKEINTRESOURCE(IDI_CFGICON))); + + centre_window(hwnd); + + pds_create_controls(pds, 0, IDCX_PANELBASE, 3, 3, 13, "Main"); + pds_create_controls(pds, 0, IDCX_STDBASE, 3, 3, 235, ""); + dlg_refresh(NULL, pds->dp); /* and set up control values */ + + pds_initdialog_finish(pds); + return 0; + + default: + return pds_default_dlgproc(pds, hwnd, msg, wParam, lParam); + } +} + +void show_ca_config_box(dlgparam *dp) +{ + PortableDialogStuff *pds = pds_new(1); + + setup_ca_config_box(pds->ctrlbox); + + ShinyDialogBox(hinst, MAKEINTRESOURCE(IDD_CA_CONFIG), "PuTTYConfigBox", + dp ? dp->hwnd : NULL, CAConfigProc, pds); + + pds_free(pds); +} diff --git a/windows/putty-common.rc2 b/windows/putty-common.rc2 index f8df971f..be5202aa 100644 --- a/windows/putty-common.rc2 +++ b/windows/putty-common.rc2 @@ -130,4 +130,13 @@ BEGIN DEFPUSHBUTTON "&Close", IDOK, 176, 130, 48, 14 END +/* Accelerators used: aco */ +IDD_CA_CONFIG DIALOG DISCARDABLE 0, 0, 300, 252 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "PuTTY trusted host certification authorities" +FONT 8, "MS Shell Dlg" +CLASS "PuTTYConfigBox" +BEGIN +END + #include "version.rc2" diff --git a/windows/putty-rc.h b/windows/putty-rc.h index 003609fd..c1658e5d 100644 --- a/windows/putty-rc.h +++ b/windows/putty-rc.h @@ -16,6 +16,7 @@ #define IDD_HK_ABSENT 114 #define IDD_HK_WRONG 115 #define IDD_HK_MOREINFO 116 +#define IDD_CA_CONFIG 117 #define IDN_LIST 1001 #define IDN_COPY 1002 diff --git a/windows/storage.c b/windows/storage.c index bf46cce0..33b77388 100644 --- a/windows/storage.c +++ b/windows/storage.c @@ -21,6 +21,7 @@ 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 *const host_ca_key = PUTTY_REG_POS "\\SshHostCAs"; static bool tried_shgetfolderpath = false; static HMODULE shell32_module = NULL; @@ -371,6 +372,128 @@ void store_host_key(const char *hostname, int port, strbuf_free(regname); } +struct host_ca_enum { + HKEY key; + int i; +}; + +host_ca_enum *enum_host_ca_start(void) +{ + host_ca_enum *e; + HKEY key; + + if (!(key = open_regkey(false, HKEY_CURRENT_USER, host_ca_key))) + return NULL; + + e = snew(host_ca_enum); + e->key = key; + e->i = 0; + + return e; +} + +bool enum_host_ca_next(host_ca_enum *e, strbuf *sb) +{ + char *regbuf = enum_regkey(e->key, e->i); + if (!regbuf) + return false; + + unescape_registry_key(regbuf, sb); + sfree(regbuf); + e->i++; + return true; +} + +void enum_host_ca_finish(host_ca_enum *e) +{ + close_regkey(e->key); + sfree(e); +} + +host_ca *host_ca_load(const char *name) +{ + strbuf *sb; + const char *s; + + sb = strbuf_new(); + escape_registry_key(name, sb); + HKEY rkey = open_regkey(false, HKEY_CURRENT_USER, host_ca_key, sb->s); + strbuf_free(sb); + + if (!rkey) + return NULL; + + host_ca *hca = snew(host_ca); + memset(hca, 0, sizeof(*hca)); + hca->name = dupstr(name); + + if ((s = get_reg_sz(rkey, "PublicKey")) != NULL) + hca->ca_public_key = base64_decode_sb(ptrlen_from_asciz(s)); + + if ((sb = get_reg_multi_sz(rkey, "MatchHosts")) != NULL) { + BinarySource src[1]; + BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(sb)); + + const char *wc; + size_t wcsize = 0; + while (wc = get_asciz(src), !get_err(src)) { + sgrowarray(hca->hostname_wildcards, wcsize, + hca->n_hostname_wildcards); + hca->hostname_wildcards[hca->n_hostname_wildcards++] = dupstr(wc); + } + + strbuf_free(sb); + } + + close_regkey(rkey); + return hca; +} + +char *host_ca_save(host_ca *hca) +{ + if (!*hca->name) + return dupstr("CA record must have a name"); + + strbuf *sb = strbuf_new(); + escape_registry_key(hca->name, sb); + HKEY rkey = open_regkey(true, HKEY_CURRENT_USER, host_ca_key, sb->s); + if (!rkey) { + char *err = dupprintf("Unable to create registry key\n" + "HKEY_CURRENT_USER\\%s\\%s", host_ca_key, sb->s); + strbuf_free(sb); + return err; + } + strbuf_free(sb); + + strbuf *base64_pubkey = base64_encode_sb( + ptrlen_from_strbuf(hca->ca_public_key), 0); + put_reg_sz(rkey, "PublicKey", base64_pubkey->s); + strbuf_free(base64_pubkey); + + strbuf *wcs = strbuf_new(); + for (size_t i = 0; i < hca->n_hostname_wildcards; i++) + put_asciz(wcs, hca->hostname_wildcards[i]); + put_reg_multi_sz(rkey, "MatchHosts", wcs); + strbuf_free(wcs); + + close_regkey(rkey); + return NULL; +} + +char *host_ca_delete(const char *name) +{ + HKEY rkey = open_regkey(false, HKEY_CURRENT_USER, host_ca_key); + if (!rkey) + return NULL; + + strbuf *sb = strbuf_new(); + escape_registry_key(name, sb); + del_regkey(rkey, sb->s); + strbuf_free(sb); + + return NULL; +} + /* * Open (or delete) the random seed file. */ -- cgit v1.2.3 From 254635a2a1a5123e9053641f3418a5c46277d5a2 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 25 Apr 2022 11:03:24 +0100 Subject: Test implementation of a CA in Python. This is mostly intended to be invoked from cryptsuite, so that I can make test certificates with various features to check the validation function. But it also has a command-line interface, which currently contains just enough features that I was able to generate a certificate and actually make sure OpenSSH accepted it (proving that I got the format right in this script). You _could_ expand this script into a full production CA, with a couple more command-line options, if you didn't mind the slightly awkward requirement that in command-line mode it insists on doing its signing via an SSH agent. But for the moment it's only intended for test purposes. --- test/ca.py | 198 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ test/ssh.py | 7 +++ 2 files changed, 205 insertions(+) create mode 100755 test/ca.py diff --git a/test/ca.py b/test/ca.py new file mode 100755 index 00000000..2ec24a31 --- /dev/null +++ b/test/ca.py @@ -0,0 +1,198 @@ +#!/usr/bin/env python3 +# +# Implementation of OpenSSH certificate creation. Used in +# cryptsuite.py to construct certificates for test purposes. +# +# Can also be run standalone to function as an actual CA, though I +# don't currently know of any reason you'd want to use it in place of +# ssh-keygen. In that mode, it depends on having an SSH agent +# available to do the signing. + +import argparse +import base64 +import enum +import hashlib +import io +import os + +import ssh + +class Container: + pass + +class CertType(enum.Enum): + user = 1 + host = 2 + +def maybe_encode(s): + if isinstance(s, bytes): + return s + return s.encode('UTF-8') + +def make_signature_preimage( + key_to_certify, ca_key, certtype, keyid, serial, principals, + valid_after=0, valid_before=0xFFFFFFFFFFFFFFFF, + critical_options={}, extensions={}, + reserved=b'', nonce=None): + + alg, pubkeydata = ssh.ssh_decode_string(key_to_certify, True) + + if nonce is None: + nonce = os.urandom(32) + + buf = io.BytesIO() + buf.write(ssh.ssh_string(alg + b"-cert-v01@openssh.com")) + buf.write(ssh.ssh_string(nonce)) + buf.write(pubkeydata) + buf.write(ssh.ssh_uint64(serial)) + buf.write(ssh.ssh_uint32(certtype.value if isinstance(certtype, CertType) + else certtype)) + buf.write(ssh.ssh_string(maybe_encode(keyid))) + buf.write(ssh.ssh_string(b''.join( + ssh.ssh_string(maybe_encode(principal)) + for principal in principals))) + buf.write(ssh.ssh_uint64(valid_after)) + buf.write(ssh.ssh_uint64(valid_before)) + buf.write(ssh.ssh_string(b''.join( + ssh.ssh_string(opt) + ssh.ssh_string(val) + for opt, val in sorted([(maybe_encode(opt), maybe_encode(val)) + for opt, val in critical_options.items()])))) + buf.write(ssh.ssh_string(b''.join( + ssh.ssh_string(opt) + ssh.ssh_string(val) + for opt, val in sorted([(maybe_encode(opt), maybe_encode(val)) + for opt, val in extensions.items()])))) + buf.write(ssh.ssh_string(reserved)) + # The CA key here can be a raw 'bytes', or an ssh_key object + # exposed via testcrypt + if type(ca_key) != bytes: + ca_key = ca_key.public_blob() + buf.write(ssh.ssh_string(ca_key)) + + return buf.getvalue() + +def make_full_cert(preimage, signature): + return preimage + ssh.ssh_string(signature) + +def sign_cert_via_testcrypt(preimage, ca_key, signflags=None): + # Expects ca_key to be a testcrypt ssh_key object + signature = ca_key.sign(preimage, 0 if signflags is None else signflags) + return make_full_cert(preimage, signature) + +def sign_cert_via_agent(preimage, ca_key, signflags=None): + # Expects ca_key to be a binary public key blob, and for a + # currently running SSH agent to contain the corresponding private + # key. + import agenttest + sign_request = (ssh.ssh_byte(ssh.SSH2_AGENTC_SIGN_REQUEST) + + ssh.ssh_string(ca_key) + ssh.ssh_string(preimage)) + if signflags is not None: + sign_request += ssh.ssh_uint32(signflags) + sign_response = agenttest.agent_query(sign_request) + msgtype, sign_response = ssh.ssh_decode_byte(sign_response, True) + if msgtype == ssh.SSH2_AGENT_SIGN_RESPONSE: + signature, sign_response = ssh.ssh_decode_string(sign_response, True) + return make_full_cert(preimage, signature) + elif msgtype == ssh.SSH2_AGENT_FAILURE: + raise IOError("Agent refused to return a signature") + else: + raise IOError("Agent returned unexpecteed message type {:d}" + .format(msgtype)) + +def read_pubkey_file(fh): + b64buf = io.StringIO() + comment = None + + lines = (line.rstrip("\r\n") for line in iter(fh.readline, "")) + line = next(lines) + + if line == "---- BEGIN SSH2 PUBLIC KEY ----": + # RFC 4716 public key. Read headers like Comment: + line = next(lines) + while ":" in line: + key, val = line.split(":", 1) + if key == "Comment": + comment = val.strip("\r\n") + line = next(lines) + # Now expect lines of base64 data. + while line != "---- BEGIN SSH2 PUBLIC KEY ----": + b64buf.write(line) + line = next(lines) + + else: + # OpenSSH public key. Expect the b64buf blob to be the second word. + fields = line.split(" ", 2) + b64buf.write(fields[1]) + if len(fields) > 1: + comment = fields[2] + + return base64.b64decode(b64buf.getvalue()), comment + +def write_pubkey_file(fh, key, comment=None): + alg = ssh.ssh_decode_string(key) + fh.write(alg.decode('ASCII')) + fh.write(" " + base64.b64encode(key).decode('ASCII')) + if comment is not None: + fh.write(" " + comment) + fh.write("\n") + +def default_signflags(key): + alg = ssh.ssh_decode_string(key) + if alg == b'ssh-rsa': + return 4 # RSA-SHA-512 + +def main(): + parser = argparse.ArgumentParser( + description='Create and sign OpenSSH certificates.') + parser.add_argument("key_to_certify", help="Public key to be certified.") + parser.add_argument("--ca-key", required=True, + help="Public key of the CA. Must be present in a " + "currently accessible SSH agent.") + parser.add_argument("-o", "--output", required=True, + help="File to write output OpenSSH key to.") + parser.add_argument("--type", required=True, choices={'user', 'host'}, + help="Type of certificate to make.") + parser.add_argument("--principal", "--user", "--host", + required=True, action="append", + help="User names or host names to authorise.") + parser.add_argument("--key-id", "--keyid", required=True, + help="Human-readable key ID string for log files.") + parser.add_argument("--serial", type=int, required=True, + help="Serial number to write into certificate.") + parser.add_argument("--signflags", type=int, help="Signature flags " + "(e.g. 2 = RSA-SHA-256, 4 = RSA-SHA-512).") + args = parser.parse_args() + + with open(args.key_to_certify) as fh: + key_to_certify, comment = read_pubkey_file(fh) + with open(args.ca_key) as fh: + ca_key, _ = read_pubkey_file(fh) + + extensions = { + 'permit-X11-forwarding': '', + 'permit-agent-forwarding': '', + 'permit-port-forwarding': '', + 'permit-pty': '', + 'permit-user-rc': '', + } + + # FIXME: for a full-featured command-line CA we'd need to add + # command-line options for crit opts, extensions and validity + # period + preimage = make_signature_preimage( + key_to_certify = key_to_certify, + ca_key = ca_key, + certtype = getattr(CertType, args.type), + keyid = args.key_id, + serial = args.serial, + principals = args.principal, + extensions = extensions) + + signflags = (args.signflags if args.signflags is not None + else default_signflags(ca_key)) + cert = sign_cert_via_agent(preimage, ca_key, signflags) + + with open(args.output, "w") as fh: + write_pubkey_file(fh, cert, comment) + +if __name__ == '__main__': + main() diff --git a/test/ssh.py b/test/ssh.py index c4f2531f..53b15183 100644 --- a/test/ssh.py +++ b/test/ssh.py @@ -24,6 +24,9 @@ def ssh_byte(n): def ssh_uint32(n): return struct.pack(">L", n) +def ssh_uint64(n): + return struct.pack(">Q", n) + def ssh_string(s): return ssh_uint32(len(s)) + s @@ -53,6 +56,10 @@ def ssh_decode_byte(s): def ssh_decode_uint32(s): return struct.unpack_from(">L", s, 0)[0], 4 +@decoder +def ssh_decode_uint64(s): + return struct.unpack_from(">Q", s, 0)[0], 8 + @decoder def ssh_decode_string(s): length = ssh_decode_uint32(s) -- cgit v1.2.3 From 36d40febeddae0af8d38ed8e9f9b60eee69c50e4 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 25 Apr 2022 13:27:53 +0100 Subject: Add cryptsuite test of certificate handling. This uses the test-CA code to construct a series of certificates with various properties so as to check all the error cases of certificate validation. It also tests the various different key types, and all the RSA signature flags on both the certified key and the certifying one. --- test/cryptsuite.py | 218 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 218 insertions(+) diff --git a/test/cryptsuite.py b/test/cryptsuite.py index 5ab7bf96..b2548fbc 100755 --- a/test/cryptsuite.py +++ b/test/cryptsuite.py @@ -18,6 +18,7 @@ except ImportError: from eccref import * from testcrypt import * from ssh import * +from ca import CertType, make_signature_preimage, sign_cert_via_testcrypt assert sys.version_info[:2] >= (3,0), "This is Python 3 code" @@ -2557,6 +2558,223 @@ Private-MAC: 5b1f6f4cc43eb0060d2c3e181bc0129343adba2b self.assertEqual(rsa1_save_sb(k2, comment, pp), input_encrypted_key) + def testOpenSSHCert(self): + def per_base_keytype_tests(alg, run_validation_tests=False, + ca_signflags=None): + cert_pub = sign_cert_via_testcrypt( + make_signature_preimage( + key_to_certify = base_key.public_blob(), + ca_key = ca_key, + certtype = CertType.user, + keyid = b'id', + serial = 111, + principals = [b'username'], + valid_after = 1000, + valid_before = 2000), ca_key, signflags=ca_signflags) + + certified_key = ssh_key_new_priv(alg + '-cert', cert_pub, + base_key.private_blob()) + + # Check the simple certificate methods + self.assertEqual(certified_key.cert_id_string(), b'id') + self.assertEqual(certified_key.ca_public_blob(), + ca_key.public_blob()) + recovered_base_key = certified_key.base_key() + self.assertEqual(recovered_base_key.public_blob(), + base_key.public_blob()) + self.assertEqual(recovered_base_key.private_blob(), + base_key.private_blob()) + + # Check that an ordinary key also supports base_key() + redundant_base_key = base_key.base_key() + self.assertEqual(redundant_base_key.public_blob(), + base_key.public_blob()) + self.assertEqual(redundant_base_key.private_blob(), + base_key.private_blob()) + + # Test signing and verifying using the certified key type + test_string = b'hello, world' + base_sig = base_key.sign(test_string, 0) + certified_sig = certified_key.sign(test_string, 0) + self.assertEqual(base_sig, certified_sig) + self.assertEqual(certified_key.verify(base_sig, test_string), True) + + # Check a successful certificate verification + result, err = certified_key.check_cert(False, b'username', 1000) + self.assertEqual(result, True) + + # That's the end of the tests we need to repeat for all + # the key types. Now we move on to detailed tests of the + # validation, which are independent of key type, so we + # only need to test this part once. + if not run_validation_tests: + return + + # Check cert verification at the other end of the valid + # time range + result, err = certified_key.check_cert(False, b'username', 1999) + self.assertEqual(result, True) + + # Oops, wrong certificate type + result, err = certified_key.check_cert(True, b'username', 1000) + self.assertEqual(result, False) + self.assertEqual(err, b'Certificate type is user; expected host') + + # Oops, wrong username + result, err = certified_key.check_cert(False, b'someoneelse', 1000) + self.assertEqual(result, False) + self.assertEqual(err, b'Certificate\'s username list ["username"] ' + b'does not contain expected username "someoneelse"') + + # Oops, time is wrong. (But we can't check the full error + # message including the translated start/end times, because + # those vary with LC_TIME.) + result, err = certified_key.check_cert(False, b'someoneelse', 999) + self.assertEqual(result, False) + self.assertEqual(err[:30], b'Certificate is not valid until') + result, err = certified_key.check_cert(False, b'someoneelse', 2000) + self.assertEqual(result, False) + self.assertEqual(err[:22], b'Certificate expired at') + + # Modify the certificate so that the signature doesn't validate + username_position = cert_pub.index(b'username') + bytelist = list(cert_pub) + bytelist[username_position] ^= 1 + miscertified_key = ssh_key_new_priv(alg + '-cert', bytes(bytelist), + base_key.private_blob()) + result, err = miscertified_key.check_cert(False, b'username', 1000) + self.assertEqual(result, False) + self.assertEqual(err, b"Certificate's signature is invalid") + + # Make a certificate containing a critical option, to test we + # reject it + cert_pub = sign_cert_via_testcrypt( + make_signature_preimage( + key_to_certify = base_key.public_blob(), + ca_key = ca_key, + certtype = CertType.user, + keyid = b'id', + serial = 112, + principals = [b'username'], + critical_options = {b'unknown-option': b'yikes!'}), ca_key) + certified_key = ssh_key_new_priv(alg + '-cert', cert_pub, + base_key.private_blob()) + result, err = certified_key.check_cert(False, b'username', 1000) + self.assertEqual(result, False) + self.assertEqual(err, b'Certificate specifies an unsupported ' + b'critical option "unknown-option"') + + # Make a certificate containing a non-critical extension, to + # test we _accept_ it + cert_pub = sign_cert_via_testcrypt( + make_signature_preimage( + key_to_certify = base_key.public_blob(), + ca_key = ca_key, + certtype = CertType.user, + keyid = b'id', + serial = 113, + principals = [b'username'], + extensions = {b'unknown-ext': b'whatever, dude'}), ca_key) + certified_key = ssh_key_new_priv(alg + '-cert', cert_pub, + base_key.private_blob()) + result, err = certified_key.check_cert(False, b'username', 1000) + self.assertEqual(result, True) + + # Now try a host certificate. We don't need to do _all_ the + # checks over again, but at least make sure that setting + # CertType.host leads to the certificate validating with + # host=True and not with host=False. + # + # Also, in this test, give two hostnames. + cert_pub = sign_cert_via_testcrypt( + make_signature_preimage( + key_to_certify = base_key.public_blob(), + ca_key = ca_key, + certtype = CertType.host, + keyid = b'id', + serial = 114, + principals = [b'hostname.example.com', + b'hostname2.example.com'], + valid_after = 1000, + valid_before = 2000), ca_key) + + certified_key = ssh_key_new_priv(alg + '-cert', cert_pub, + base_key.private_blob()) + + # Check certificate type + result, err = certified_key.check_cert( + True, b'hostname.example.com', 1000) + self.assertEqual(result, True) + result, err = certified_key.check_cert( + False, b'hostname.example.com', 1000) + self.assertEqual(result, False) + self.assertEqual(err, b'Certificate type is host; expected user') + + # Check the second hostname and an unknown one + result, err = certified_key.check_cert( + True, b'hostname2.example.com', 1000) + self.assertEqual(result, True) + result, err = certified_key.check_cert( + True, b'hostname3.example.com', 1000) + self.assertEqual(result, False) + self.assertEqual(err, b'Certificate\'s hostname list [' + b'"hostname.example.com", "hostname2.example.com"] ' + b'does not contain expected hostname ' + b'"hostname3.example.com"') + + # And just for luck, try a totally unknown certificate type, + # making sure that it's rejected in both modes and gives the + # right error message + cert_pub = sign_cert_via_testcrypt( + make_signature_preimage( + key_to_certify = base_key.public_blob(), + ca_key = ca_key, + certtype = 12345, + keyid = b'id', + serial = 114, + principals = [b'username', b'hostname.example.com'], + valid_after = 1000, + valid_before = 2000), ca_key) + certified_key = ssh_key_new_priv(alg + '-cert', cert_pub, + base_key.private_blob()) + result, err = certified_key.check_cert( + False, b'username', 1000) + self.assertEqual(result, False) + self.assertEqual(err, b'Certificate type is unknown value 12345; ' + b'expected user') + result, err = certified_key.check_cert( + True, b'hostname.example.com', 1000) + self.assertEqual(result, False) + self.assertEqual(err, b'Certificate type is unknown value 12345; ' + b'expected host') + + ca_key = ssh_key_new_priv('ed25519', b64('AAAAC3NzaC1lZDI1NTE5AAAAIMUJEFAmSV/qtoxSmVOHUgTMKYjqkDy8fTfsfCKV+sN7'), b64('AAAAIK4STyaf63xHidqhvUop9/OKiYqSh/YEWLCp1lL5Vs4u')) + + base_key = ssh_key_new_priv('ed25519', b64('AAAAC3NzaC1lZDI1NTE5AAAAIMt0/CMBL+64GQ/r/JyGxo6oHs86i9bOHhMJYbDbxEJf'), b64('AAAAIB38jy02ZWYb4EXrJG9RIljEhqidrG5DdhZvMvoeOTZs')) + per_base_keytype_tests('ed25519', run_validation_tests=True) + + base_key = ssh_key_new_priv('p256', b64('AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBGc8VXplXScdWckJgAw6Hag5PP7g0JEVdLY5lP2ujvVxU5GwwquYLbX3yyj1zY5h2n9GoXrnRxzR5+5g8wsNjTA='), b64('AAAAICVRicPD5MyOHfKdnC/8IP84t+nQ4bqmMUyX7NHyCKjS')) + per_base_keytype_tests('p256') + + base_key = ssh_key_new_priv('p384', b64('AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBLITujAbKwHDEzVDFqWtA+CleAhN/Y+53mHbEoTpU0aof9L+2lHeUshXdxHDLxY69wO5+WfqWJCwSY58PuXIZzIisQkvIKq6LhpzK6C5JpWJ8Kbv7su+qZPf5sYoxx0xZg=='), b64('AAAAMHyQTQYcIA/bR4ZvWS86ohb5Lu0MhzjD8bUb3q8jnROOe3BrE9I8oJcx+l1lddPouA==')) + per_base_keytype_tests('p384') + + base_key = ssh_key_new_priv('p521', b64('AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBADButwMRGdLkFhWcSDsLhRhgyrLQq1/A0M8x4GgEmesh4iydo4tGKZR14GhHvx150IWTE1Tre4wyH+1FsTfAlpUBgBDQjsZE0D3u3SLp4qjjhzyrJGhEUDd9J6lsr6JrXbTefz5+LkM9m5l86y9PoAgT+F25OiTYlfvR5qx/pzIPoCnpA=='), b64('AAAAQgFV8xBXC7XZNxdW1oWg6yCZjys2AX4beZVehE9A2R/4m11dHnfqoE1FzbRxj9xqwKvHZRhMOJ//DYuhtcG6+6yHsA==')) + per_base_keytype_tests('p521') + + base_key = ssh_key_new_priv('dsa', b64('AAAAB3NzaC1kc3MAAABCAXgDrF9Fw/Ty+QcoljAGjGL/Ph5+NBQqUYADm4wxF+aazjQXLuZ0VW9OdYBisgDZlYDj/w7y9NxCBgax2BSkhDNxAAAAFQC/YwnFzcom6cRRHPXtOUDLi2I29QAAAEIAqGOUYpfFPwzhgAmYXwWKdK8ouSUplNE29FOpv6NYjyf7k+tLSWF3b8oZdtw6XP8lr4vcKXC9Ik0YpKYKM7iKfb8AAABCAUDCcojlDLQmLHg8HhFCtT/CpayNh4OfmSrP8XOwJnFD/eBaSGuPB5EvGd+m6gr+Pc0RSAlWP1aIzUbYkQ33Yk58'), b64('AAAAFQChVuOTNrCwLSJygxlRQhDwHozwSg==')) + per_base_keytype_tests('dsa') + + base_key = ssh_key_new_priv('rsa', b64('AAAAB3NzaC1yc2EAAAADAQABAAAAgQDXLnqGPQLL9byoHFQWPiF5Uzcd0KedMRRJmuwyCAWprlh8EN43mL2F7q27Uv54m/ztqW4DsVtiCN6cDYvB9QPNYFR5npwsEAJ06Ro4s9ZpFsZVOvitqeoYIs+jkS8vq5V8X4hwLlJ8vXYPD6rHJhOz6HFpImHmVu40Mu5lq+MCQQ=='), b64('AAAAgH5dBwrJzVilKHK4oBCnz9SFr7pMjAHdjoJi/g2rdFfe0IubBEQ16CY8sb1t0Y5WXEPc2YRFpNp/RurxcX8nOWFPzgNJXEtkKpKO9Juqu5hL4xcf8QKC2aJFk3EXrn/M6dXEdjqN4UhsT6iFTsHKU4b8T6VTtgKzwkOdic/YotaBAAAAQQD6liDTlzTKzLhbypI6l+y2BGA3Kkzz71Y2o7XH/6bZ6HJOFgHuJeL3eNQptzd8Q+ctfvR0fa2PItYydDOlVUeZAAAAQQDb1IsO1/fkflDZhPQT2XOxtrjgQhotKjr6CSmJtDNmo1mOCN+mOgxtDfJ0PNEEM1P9CO2Ia3njtkxt4Ep2EpjpAAAAQQClRxLEHsRK9nMPZ4HW45iyw5dHhYar9pYUql2VnixWQxrHy13ZIaWxi6xwWjuPglrdBgEQfYwH9KGmlFmZXT/Z')) + per_base_keytype_tests('rsa') + + # Now switch to an RSA certifying key, and test different RSA + # signature subtypes being used to sign the certificate + ca_key = ssh_key_new_priv('rsa', b64('AAAAB3NzaC1yc2EAAAADAQABAAAAgQCKHiavhtnAZQLUPtYlzlQmVTHSKq2ChCKZP0cLNtN2YSS0/f4D1hi8W04Qh/JuSXZAdUThTAVjxDmxpiOMNwa/2WDXMuqip47dzZSQxtSdvTfeL9TVC/M1NaOzy8bqFx6pzi37zPATETT4PP1Zt/Pd23ZJYhwjxSyTlqj7529v0w=='), b64('AAAAgCwTZyEIlaCyG28EBm7WI0CAW3/IIsrNxATHjrJjcqQKaB5iF5e90PL66DSaTaEoTFZRlgOXsPiffBHXBO0P+lTyZ2jlq2J2zgeofRH3Yong4BT4xDtqBKtxixgC1MAHmrOnRXjAcDUiLxIGgU0YKSv0uAlgARsUwDsk0GEvK+jBAAAAQQDMi7liRBQ4/Z6a4wDL/rVnIJ9x+2h2UPK9J8U7f97x/THIBtfkbf9O7nDP6onValuSr86tMR24DJZsEXaGPwjDAAAAQQCs3J3D3jNVwwk16oySRSjA5x3tKCEITYMluyXX06cvFew8ldgRCYl1sh8RYAfbBKXhnJD77qIxtVNaF1yl/guxAAAAQFTRdKRUF2wLu/K/Rr34trwKrV6aW0GWyHlLuWvF7FUB85aDmtqYI2BSk92mVCKHBNw2T3cJMabN9JOznjtADiM=')) + per_base_keytype_tests('rsa') + per_base_keytype_tests('rsa', ca_signflags=2) + per_base_keytype_tests('rsa', ca_signflags=4) + class standard_test_vectors(MyTestBase): def testAES(self): def vector(cipher, key, plaintext, ciphertext): -- cgit v1.2.3 From 3bb7e6ba9efcea074d44e500769d3ba74828919e Mon Sep 17 00:00:00 2001 From: Jacob Nevins Date: Tue, 26 Apr 2022 12:45:19 +0100 Subject: Fix duplicated shortcut on Windows SSH/Auth panel. As of df3a21d97b, this panel has two "Browse..." buttons, and is the first to do so. On Windows, the shortcut for this file chooser button was hardcoded to 'w', causing an assertion failure whenever switching to the Auth panel. I've just removed the shortcut -- there are others near enough that it's easy to reach the "Browse..." button with Tab. --- windows/controls.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/windows/controls.c b/windows/controls.c index 3c7896a4..51d0921b 100644 --- a/windows/controls.c +++ b/windows/controls.c @@ -1668,8 +1668,7 @@ void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, ctrl->fileselect.shortcut); shortcuts[nshortcuts++] = ctrl->fileselect.shortcut; editbutton(&pos, escaped, base_id, base_id+1, - "Bro&wse...", base_id+2); - shortcuts[nshortcuts++] = 'w'; + "Browse...", base_id+2); sfree(escaped); break; case CTRL_FONTSELECT: -- cgit v1.2.3 From de5f295b99cb727a663558563d37e819366ed780 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Wed, 27 Apr 2022 07:44:27 +0100 Subject: Fix handling of RSA + SHA-2 certified host keys. Initial live testing pointed out that the ssh_keyalg corresponding to the certified version of rsa-sha2-512 was expecting to see the SSH id string "rsa-sha2-512-cert-v01@openssh.com" at the start of the public key blob, whereas in fact, the _key_ type identifier is still "ssh-rsa-...", just as the key type for base rsa-sha2-512 is base ssh-rsa. Fixed inside openssh-certs.c, by adding a couple more strings to the 'extra' structure. --- crypto/openssh-certs.c | 49 ++++++++++++++++++++++++++++++++++--------------- 1 file changed, 34 insertions(+), 15 deletions(-) diff --git a/crypto/openssh-certs.c b/crypto/openssh-certs.c index 7f61e55a..3af2e3b5 100644 --- a/crypto/openssh-certs.c +++ b/crypto/openssh-certs.c @@ -66,6 +66,22 @@ typedef struct opensshcert_extra { * string itself. */ blob_fmt pub_fmt, base_ossh_fmt, cert_ossh_fmt; + + /* + * The RSA-SHA2 algorithm names have their SSH id set to names + * like "rsa-sha2-512-cert-...", which is what will be received in + * the KEXINIT algorithm list if a host key in one of those + * algorithms is presented. But the _key_ type id that will appear + * in the public key blob is "ssh-rsa-cert-...". So we need a + * separate field to indicate the key type id we expect to see in + * certified public keys, and also the one we want to put back + * into the artificial public blob we make to pass to the + * constructor for the underlying key. + * + * (In rsa.c this is managed much more simply, because everything + * sharing the same vtable wants the same key type id.) + */ + const char *cert_key_ssh_id, *base_key_ssh_id; } opensshcert_extra; /* @@ -206,18 +222,18 @@ static const ssh_keyalg *opensshcert_related_alg(const ssh_keyalg *self, * macro so I can also make an array of them all. */ -#define KEYALG_LIST(X) \ - X(ssh_dsa, "ssh-dss", dsa) \ - X(ssh_rsa, "ssh-rsa", rsa) \ - X(ssh_rsa_sha256, "rsa-sha2-256", rsa) \ - X(ssh_rsa_sha512, "rsa-sha2-512", rsa) \ - X(ssh_ecdsa_ed25519, "ssh-ed25519", eddsa) \ - X(ssh_ecdsa_nistp256, "ecdsa-sha2-nistp256", ecdsa) \ - X(ssh_ecdsa_nistp384, "ecdsa-sha2-nistp384", ecdsa) \ - X(ssh_ecdsa_nistp521, "ecdsa-sha2-nistp521", ecdsa) \ +#define KEYALG_LIST(X) \ + X(ssh_dsa, "ssh-dss", "ssh-dss", dsa) \ + X(ssh_rsa, "ssh-rsa", "ssh-rsa", rsa) \ + X(ssh_rsa_sha256, "rsa-sha2-256", "ssh-rsa", rsa) \ + X(ssh_rsa_sha512, "rsa-sha2-512", "ssh-rsa", rsa) \ + X(ssh_ecdsa_ed25519, "ssh-ed25519", "ssh-ed25519", eddsa) \ + X(ssh_ecdsa_nistp256, "ecdsa-sha2-nistp256","ecdsa-sha2-nistp256", ecdsa) \ + X(ssh_ecdsa_nistp384, "ecdsa-sha2-nistp384","ecdsa-sha2-nistp384", ecdsa) \ + X(ssh_ecdsa_nistp521, "ecdsa-sha2-nistp521","ecdsa-sha2-nistp521", ecdsa) \ /* end of list */ -#define KEYALG_DEF(name, ssh_id_prefix, fmt_prefix) \ +#define KEYALG_DEF(name, ssh_alg_id_prefix, ssh_key_id_prefix, fmt_prefix) \ const struct opensshcert_extra opensshcert_##name##_extra = { \ .pub_fmt = { .fmt = fmt_prefix ## _pub_fmt, \ .len = lenof(fmt_prefix ## _pub_fmt) }, \ @@ -225,6 +241,8 @@ static const ssh_keyalg *opensshcert_related_alg(const ssh_keyalg *self, .len = lenof(fmt_prefix ## _base_ossh_fmt) }, \ .cert_ossh_fmt = { .fmt = fmt_prefix ## _cert_ossh_fmt, \ .len = lenof(fmt_prefix ## _cert_ossh_fmt) }, \ + .cert_key_ssh_id = ssh_key_id_prefix "-cert-v01@openssh.com", \ + .base_key_ssh_id = ssh_key_id_prefix, \ }; \ \ const ssh_keyalg opensshcert_##name = { \ @@ -249,7 +267,7 @@ static const ssh_keyalg *opensshcert_related_alg(const ssh_keyalg *self, .supported_flags = opensshcert_supported_flags, \ .alternate_ssh_id = opensshcert_alternate_ssh_id, \ .related_alg = opensshcert_related_alg, \ - .ssh_id = ssh_id_prefix "-cert-v01@openssh.com", \ + .ssh_id = ssh_alg_id_prefix "-cert-v01@openssh.com", \ .cache_id = NULL, \ .extra = &opensshcert_##name##_extra, \ .is_certificate = true, \ @@ -258,7 +276,7 @@ static const ssh_keyalg *opensshcert_related_alg(const ssh_keyalg *self, KEYALG_LIST(KEYALG_DEF) #undef KEYALG_DEF -#define KEYALG_LIST_ENTRY(name, ssh_id_prefix, fmt_prefix) &opensshcert_##name, +#define KEYALG_LIST_ENTRY(name, algid, keyid, fmt) &opensshcert_##name, static const ssh_keyalg *const opensshcert_all_keyalgs[] = { KEYALG_LIST(KEYALG_LIST_ENTRY) }; @@ -273,7 +291,7 @@ static opensshcert_key *opensshcert_new_shared( BinarySource_BARE_INIT_PL(src, blob); /* Check the initial key-type string */ - if (!ptrlen_eq_string(get_string(src), self->ssh_id)) + if (!ptrlen_eq_string(get_string(src), extra->cert_key_ssh_id)) return NULL; opensshcert_key *ck = snew(opensshcert_key); @@ -283,7 +301,7 @@ static opensshcert_key *opensshcert_new_shared( ck->nonce = strbuf_dup(get_string(src)); strbuf *basepub = strbuf_new(); { - put_stringz(basepub, self->base_alg->ssh_id); + put_stringz(basepub, extra->base_key_ssh_id); /* Make the base public key blob out of the public key * material in the certificate. This invocation of the @@ -461,7 +479,8 @@ static ssh_key *opensshcert_ca_pub_key( static void opensshcert_signature_preimage(opensshcert_key *ck, BinarySink *bs) { - put_stringz(bs, ck->sshk.vt->ssh_id); + const opensshcert_extra *extra = ck->sshk.vt->extra; + put_stringz(bs, extra->cert_key_ssh_id); put_stringpl(bs, ptrlen_from_strbuf(ck->nonce)); strbuf *basepub = strbuf_new(); -- cgit v1.2.3 From 93fb65af6139db3dad4d34d6d8bab002a8ea0136 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Wed, 27 Apr 2022 16:33:23 +0100 Subject: Fix translation of legacy registry RSA key format. A user points out that in commit 6143a50ed228fdf, when I converted all use of the registry to functions that return a newly allocated buffer instead of allocating a buffer themselves beforehand, I overlooked that one use of the old idiom was reusing the preallocated buffer as work space. I _hope_ nobody still needs this code - the 'old-style' host key cache format it handles was replaced in 2000. If anyone has a PuTTY host key cache entry that's survived 22 years without either having to be reinitialised on a new system or changed when the machine's host key was upgraded, they're doing better than I am! But if it's still here, it should still work, obviously. Replaced the reused buffer with a strbuf, which is more robust anyway. --- windows/storage.c | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/windows/storage.c b/windows/storage.c index 33b77388..e386213c 100644 --- a/windows/storage.c +++ b/windows/storage.c @@ -294,29 +294,26 @@ int check_stored_host_key(const char *hostname, int port, * 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; + strbuf *new = strbuf_new(); + const char *q = oldstyle; int i, j; for (i = 0; i < 2; i++) { int ndigits, nwords; - *p++ = '0'; - *p++ = 'x'; + put_datapl(new, PTRLEN_LITERAL("0x")); 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; + for (j = ndigits; j-- > 0 ;) + put_byte(new, q[j ^ 3]); q += nwords * 4; if (*q) { - q++; /* eat the slash */ - *p++ = ','; /* add a comma */ + q++; /* eat the slash */ + put_byte(new, ','); /* add a comma */ } - *p = '\0'; /* terminate the string */ } /* @@ -324,8 +321,9 @@ int check_stored_host_key(const char *hostname, int port, * format. If not, we'll assume something odd went * wrong, and hyper-cautiously do nothing. */ - if (!strcmp(otherstr, key)) - put_reg_sz(rkey, regname->s, otherstr); + if (!strcmp(new->s, key)) + put_reg_sz(rkey, regname->s, new->s); + strbuf_free(new); } sfree(oldstyle); -- cgit v1.2.3 From 7b0292b2c359ab37811d658c4afe49d721b6253d Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 28 Apr 2022 12:50:00 +0100 Subject: Fix translation of legacy key format *again*. As well as eliminating the null-pointer dereference, I also now realise that the format-translation code depended on leaving the final translated string in 'otherstr' in order to pass the host key check afterwards (if they match). I've also now realised that this only applies to *SSH-1* RSA keys, so it's even more obsolete than I thought before. Perhaps I should just remove this code instead of spending all this effort on fixing it. But I've done the fix now, so I'll commit it, and then maybe we can remove it afterwards (and have a working version of it available to resurrect if ever needed!). --- windows/storage.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/windows/storage.c b/windows/storage.c index e386213c..7e74e392 100644 --- a/windows/storage.c +++ b/windows/storage.c @@ -321,9 +321,12 @@ int check_stored_host_key(const char *hostname, int port, * format. If not, we'll assume something odd went * wrong, and hyper-cautiously do nothing. */ - if (!strcmp(new->s, key)) + if (!strcmp(new->s, key)) { put_reg_sz(rkey, regname->s, new->s); - strbuf_free(new); + otherstr = strbuf_to_str(new); + } else { + strbuf_free(new); + } } sfree(oldstyle); -- cgit v1.2.3 From 42dcd465ab19d0dab1cc081b5f7b35c60be2577b Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 28 Apr 2022 13:02:00 +0100 Subject: ssh2_scan_kexinits: dynamically allocate server_hostkeys[]. In commit 7d44e35bb3780c0 I introduced a bug: we were providing an array of MAXKEXLIST ints to ssh2_scan_kexinits() to write a list of server-supplied host keys into, and when MAXKEXLIST stopped being a thing, I mindlessly replaced it with an array dynamically allocated to the number of host key types we'd offered the server. But we return a list of host key types the _server_ offered _us_ (and that we can speak at all), which isn't necessarily the same thing. In particular, if you deliberately ask to cache a new host key type from the specials menu, we send a KEXINIT offering just _one_ host key type, namely the one you've asked for. But that loop still writes down all the key types it gets back from the server, which is (almost certainly) more than one. So the array overflows. In that situation we don't really need the returned array of key types at all, but it's easier to just make it work than to add conditionals. Replaced it with a dynamically grown array in the usual sort of way. --- ssh/transport2.c | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/ssh/transport2.c b/ssh/transport2.c index ebb76be1..7265444f 100644 --- a/ssh/transport2.c +++ b/ssh/transport2.c @@ -931,6 +931,11 @@ static void ssh2_write_kexinit_lists( put_stringz(pktout, ""); } +struct server_hostkeys { + int *indices; + size_t n, size; +}; + static bool ssh2_scan_kexinits( ptrlen client_kexinit, ptrlen server_kexinit, struct kexinit_algorithm_list kexlists[NKEXLIST], @@ -938,7 +943,7 @@ static bool ssh2_scan_kexinits( transport_direction *cs, transport_direction *sc, bool *warn_kex, bool *warn_hk, bool *warn_cscipher, bool *warn_sccipher, Ssh *ssh, bool *ignore_guess_cs_packet, bool *ignore_guess_sc_packet, - int *n_server_hostkeys, int *server_hostkeys, unsigned *hkflags, + struct server_hostkeys *server_hostkeys, unsigned *hkflags, bool *can_send_ext_info) { BinarySource client[1], server[1]; @@ -1160,13 +1165,13 @@ static bool ssh2_scan_kexinits( * one or not. We return these as a list of indices into the * constant ssh2_hostkey_algs[] array. */ - *n_server_hostkeys = 0; - ptrlen list = slists[KEXLIST_HOSTKEY]; for (ptrlen word; get_commasep_word(&list, &word) ;) { for (i = 0; i < lenof(ssh2_hostkey_algs); i++) if (ptrlen_eq_string(word, ssh2_hostkey_algs[i].alg->ssh_id)) { - server_hostkeys[(*n_server_hostkeys)++] = i; + sgrowarray(server_hostkeys->indices, server_hostkeys->size, + server_hostkeys->n); + server_hostkeys->indices[server_hostkeys->n++] = i; break; } } @@ -1315,17 +1320,16 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl) * selected algorithm identifiers. */ { - int nhk, i, j; - int *hks = snewn(s->kexlists[KEXLIST_HOSTKEY].nalgs, int); + struct server_hostkeys hks = { NULL, 0, 0 }; if (!ssh2_scan_kexinits( ptrlen_from_strbuf(s->client_kexinit), ptrlen_from_strbuf(s->server_kexinit), s->kexlists, &s->kex_alg, &s->hostkey_alg, s->cstrans, s->sctrans, &s->warn_kex, &s->warn_hk, &s->warn_cscipher, - &s->warn_sccipher, s->ppl.ssh, NULL, &s->ignorepkt, &nhk, hks, + &s->warn_sccipher, s->ppl.ssh, NULL, &s->ignorepkt, &hks, &s->hkflags, &s->can_send_ext_info)) { - sfree(hks); + sfree(hks.indices); return; /* false means a fatal error function was called */ } @@ -1341,8 +1345,8 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl) */ s->n_uncert_hostkeys = 0; - for (i = 0; i < nhk; i++) { - j = hks[i]; + for (int i = 0; i < hks.n; i++) { + int j = hks.indices[i]; if (ssh2_hostkey_algs[j].alg != s->hostkey_alg && ssh2_hostkey_algs[j].alg->cache_id && !have_ssh_host_key(s->savedhost, s->savedport, @@ -1351,7 +1355,7 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl) } } - sfree(hks); + sfree(hks.indices); } if (s->warn_kex) { -- cgit v1.2.3 From e6df50ea6b1476d04caad64fc919974684a4a091 Mon Sep 17 00:00:00 2001 From: Jacob Nevins Date: Fri, 29 Apr 2022 11:39:04 +0100 Subject: Restore 'Local' proxy type in config UI. It was accidentally disabled in 2a26ebd0d5. --- unix/config-unix.c | 2 +- windows/config.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/unix/config-unix.c b/unix/config-unix.c index 349a643f..d832a18b 100644 --- a/unix/config-unix.c +++ b/unix/config-unix.c @@ -39,7 +39,7 @@ void unix_setup_config_box(struct controlbox *b, bool midsession, int protocol) 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 && + if (c->generic.type == CTRL_LISTBOX && c->generic.handler == proxy_type_handler) { c->generic.context.i |= PROXY_UI_FLAG_LOCAL; break; diff --git a/windows/config.c b/windows/config.c index bcac905a..17448260 100644 --- a/windows/config.c +++ b/windows/config.c @@ -362,7 +362,7 @@ void win_setup_config_box(struct controlbox *b, HWND *hwndp, bool has_help, 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 && + if (c->generic.type == CTRL_LISTBOX && c->generic.handler == proxy_type_handler) { c->generic.context.i |= PROXY_UI_FLAG_LOCAL; break; -- cgit v1.2.3 From f9bb1f49970eb9a41c517f387f46783c4c9f8b4c Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 29 Apr 2022 10:31:24 +0100 Subject: ssh/verstring.c: fix use of '\r' and '\n'. It's a thoroughly pedantic point, but I just spotted that the comparison of wire data against theoretically platform-dependent char escapes is a violation of \k{udp-portability}. --- ssh/verstring.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ssh/verstring.c b/ssh/verstring.c index 90814bc1..4671903a 100644 --- a/ssh/verstring.c +++ b/ssh/verstring.c @@ -309,8 +309,8 @@ void ssh_verstring_handle_input(BinaryPacketProtocol *bpp) * a NUL terminator. */ while (s->vstring->len > 0 && - (s->vstring->s[s->vstring->len-1] == '\r' || - s->vstring->s[s->vstring->len-1] == '\n')) + (s->vstring->s[s->vstring->len-1] == '\015' || + s->vstring->s[s->vstring->len-1] == '\012')) strbuf_shrink_by(s->vstring, 1); bpp_logevent("Remote version: %s", s->vstring->s); -- cgit v1.2.3 From 03cfda89d135576ba458e6da0b337aea198d00c7 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 29 Apr 2022 11:54:36 +0100 Subject: Windows: make SockAddr's error field const char *. We're assigning string literals into it all over the place, so it should have been const char * all along. No thanks to any of the compilers that didn't point that out! --- windows/network.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/windows/network.c b/windows/network.c index acc74034..24b14e7b 100644 --- a/windows/network.c +++ b/windows/network.c @@ -103,7 +103,7 @@ typedef enum SuperFamily { struct SockAddr { int refcount; - char *error; + const char *error; SuperFamily superfamily; #ifndef NO_IPV6 struct addrinfo *ais; /* Addresses IPv6 style. */ -- cgit v1.2.3 From 67204ffd0b46b277fb6c848cd1a4b17771996eb5 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 29 Apr 2022 11:55:56 +0100 Subject: Windows: stop trying to use gai_strerror. To begin with, Windows's own API documentation doesn't recommend using it (for thread-safety reasons), and promises that the error codes returned from getaddrinfo are aliases for the normal Windows error code enumeration. So it's safe, and quite likely preferable, to just use ordinary win_strerror instead. But more embarrassingly, my attempt to acquire and use gai_strerror from one or other Winsock DLL didn't even *work*! Because of course it's a function that handles strings, which means it comes in two variants, gai_strerrorA and gai_strerrorW, so in order to look it up using GetProcAddress, I should have specified which I wanted. And I didn't, so the lookup always failed. This should improve error reporting in cases of interesting kinds of DNS failure. --- windows/network.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/windows/network.c b/windows/network.c index 24b14e7b..517a335c 100644 --- a/windows/network.c +++ b/windows/network.c @@ -223,7 +223,6 @@ DECL_WINDOWS_FUNCTION(static, int, getnameinfo, (const struct sockaddr FAR * sa, socklen_t salen, char FAR * host, DWORD hostlen, char FAR * serv, DWORD servlen, int flags)); -DECL_WINDOWS_FUNCTION(static, char *, gai_strerror, (int ecode)); DECL_WINDOWS_FUNCTION(static, int, WSAAddressToStringA, (LPSOCKADDR, DWORD, LPWSAPROTOCOL_INFO, LPSTR, LPDWORD)); @@ -280,7 +279,6 @@ void sk_init(void) /* This function would fail its type-check if we did one, * because the VS header file provides an inline definition * which is __cdecl instead of WINAPI. */ - GET_WINDOWS_FUNCTION_NO_TYPECHECK(winsock_module, gai_strerror); } else { /* Fall back to wship6.dll for Windows 2000 */ wship6_module = load_system32_dll("wship6.dll"); @@ -289,7 +287,6 @@ void sk_init(void) GET_WINDOWS_FUNCTION(wship6_module, freeaddrinfo); /* See comment above about type check */ GET_WINDOWS_FUNCTION_NO_TYPECHECK(wship6_module, getnameinfo); - GET_WINDOWS_FUNCTION_NO_TYPECHECK(winsock_module, gai_strerror); } else { } } @@ -517,10 +514,7 @@ SockAddr *sk_namelookup(const char *host, char **canonicalname, 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"); + win_strerror(err)); } else { ret->error = NULL; -- cgit v1.2.3 From e22df7454511140b332b9ba4f9ce5d539e6d9717 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 29 Apr 2022 08:05:39 +0100 Subject: Reorganise sk_namelookup (on both platforms). I just tried to trace through the Windows version's control flow in response to a confusing bug report, and found that the control flow itself was so confusing I couldn't make sense of it. Why are we choosing between getaddrinfo and gethostbyname via #ifndef NO_IPV6, then re-converging control flow and diverging a second time to report the error? So I rewrote the whole thing to have completely separate sections of code dealing with the three resolution strategies, each with its own dedicated error reporting system. And then I checked the Unix version and found it was about as confusing, so I rewrote that too in the same style. Now the two are mostly the same, except for details: Unix has an override at the top for a Unix socket pathname, Windows has to cope with getaddrinfo maybe not being found at run time (so the other cases aren't in the #else clause), and Windows uses the same error reporting for both lookup functions whereas Unix has to use the appropriate gai_strerror or hstrerror. --- unix/network.c | 142 ++++++++++++++++++++------------------------ windows/network.c | 175 ++++++++++++++++++++++++------------------------------ 2 files changed, 140 insertions(+), 177 deletions(-) diff --git a/unix/network.c b/unix/network.c index 0f15796b..1734d61c 100644 --- a/unix/network.c +++ b/unix/network.c @@ -184,103 +184,89 @@ void sk_cleanup(void) } } -SockAddr *sk_namelookup(const char *host, char **canonicalname, int address_family) +SockAddr *sk_namelookup(const char *host, char **canonicalname, + int address_family) { + *canonicalname = NULL; + if (host[0] == '/') { *canonicalname = dupstr(host); return unix_sock_addr(host); } - SockAddr *ret = snew(SockAddr); -#ifndef NO_IPV6 - struct addrinfo hints; - int err; -#else - unsigned long a; - struct hostent *h = NULL; - int n; -#endif - strbuf *realhost = strbuf_new(); - - /* Clear the structure and default to IPv4. */ - memset(ret, 0, sizeof(SockAddr)); - ret->superfamily = UNRESOLVED; - ret->error = NULL; - ret->refcount = 1; + SockAddr *addr = snew(SockAddr); + memset(addr, 0, sizeof(SockAddr)); + addr->superfamily = UNRESOLVED; + addr->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; + /* + * Use getaddrinfo, as long as it's available. This should handle + * both IPv4 and IPv6 address literals, and hostnames, in one + * unified API. + */ { - char *trimmed_host = host_strduptrim(host); /* strip [] on literals */ - err = getaddrinfo(trimmed_host, NULL, &hints, &ret->ais); + struct addrinfo hints; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = (address_family == ADDRTYPE_IPV4 ? AF_INET : + address_family == ADDRTYPE_IPV6 ? AF_INET6 : + AF_UNSPEC); + hints.ai_flags = AI_CANONNAME; + hints.ai_socktype = SOCK_STREAM; + + /* strip [] on IPv6 address literals */ + char *trimmed_host = host_strduptrim(host); + int err = getaddrinfo(trimmed_host, NULL, &hints, &addr->ais); sfree(trimmed_host); + + if (addr->ais) { + addr->superfamily = IP; + if (addr->ais->ai_canonname) + *canonicalname = dupstr(addr->ais->ai_canonname); + else + *canonicalname = dupstr(host); + } else { + addr->error = gai_strerror(err); + } + return addr; } - if (err != 0) { - ret->error = gai_strerror(err); - strbuf_free(realhost); - return ret; - } - ret->superfamily = IP; - if (ret->ais->ai_canonname != NULL) - put_fmt(realhost, "%s", ret->ais->ai_canonname); - else - put_fmt(realhost, "%s", host); #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"); - strbuf_free(realhost); - return ret; - } - /* This way we are always sure the h->h_name is valid :) */ - strbuf_clear(realhost); - put_fmt(realhost, "%s", h->h_name); + /* + * Failing that (if IPv6 support was not compiled in), try the + * old-fashioned approach, which is to start by manually checking + * for an IPv4 literal and then use gethostbyname. + */ + unsigned long a = inet_addr(host); + if (a != (unsigned long) INADDR_NONE) { + addr->addresses = snew(unsigned long); + addr->naddresses = 1; + addr->addresses[0] = ntohl(a); + addr->superfamily = IP; + *canonicalname = dupstr(host); + return addr; + } + + struct hostent *h = gethostbyname(host); + if (h) { + addr->superfamily = IP; + + size_t 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++) { + addr->addresses = snewn(n, unsigned long); + addr->naddresses = n; + for (n = 0; n < addr->naddresses; n++) { + uint32_t a; memcpy(&a, h->h_addr_list[n], sizeof(a)); - ret->addresses[n] = ntohl(a); + addr->addresses[n] = ntohl(a); } + + *canonicalname = dupstr(h->h_name); } else { - /* - * This must be a numeric IPv4 address because it caused a - * success return from inet_addr. - */ - ret->superfamily = IP; - strbuf_clear(realhost); - put_fmt(realhost, "%s", host); - ret->addresses = snew(unsigned long); - ret->naddresses = 1; - ret->addresses[0] = ntohl(a); + addr->error = hstrerror(h_errno); } + return addr; #endif - *canonicalname = strbuf_to_str(realhost); - return ret; } SockAddr *sk_nonamelookup(const char *host) diff --git a/windows/network.c b/windows/network.c index 517a335c..a07b46bd 100644 --- a/windows/network.c +++ b/windows/network.c @@ -452,118 +452,95 @@ const char *winsock_error_string(int error) return win_strerror(error); } +static inline const char *namelookup_strerror(DWORD err) +{ + /* PuTTY has traditionally translated a few of the likely error + * messages into more concise strings than the standard Windows ones */ + return (err == WSAENETDOWN ? "Network is down" : + err == WSAHOST_NOT_FOUND ? "Host does not exist" : + err == WSATRY_AGAIN ? "Host not found" : + win_strerror(err)); +} + SockAddr *sk_namelookup(const char *host, char **canonicalname, int address_family) { - SockAddr *ret = snew(SockAddr); - unsigned long a; - char realhost[8192]; - int hint_family; + *canonicalname = NULL; - /* Default to IPv4. */ - hint_family = (address_family == ADDRTYPE_IPV4 ? AF_INET : -#ifndef NO_IPV6 - address_family == ADDRTYPE_IPV6 ? AF_INET6 : -#endif - AF_UNSPEC); + SockAddr *addr = snew(SockAddr); + memset(addr, 0, sizeof(SockAddr)); + addr->superfamily = UNRESOLVED; + addr->refcount = 1; - /* Clear the structure and default to IPv4. */ - memset(ret, 0, sizeof(SockAddr)); #ifndef NO_IPV6 - ret->ais = NULL; -#endif - ret->addresses = NULL; - ret->superfamily = UNRESOLVED; - ret->refcount = 1; - *realhost = '\0'; - - if ((a = p_inet_addr(host)) == (unsigned long) INADDR_NONE) { - struct hostent *h = NULL; - int err = 0; -#ifndef NO_IPV6 - /* - * Use getaddrinfo when it's available - */ - if (p_getaddrinfo) { - struct addrinfo hints; - memset(&hints, 0, sizeof(hints)); - hints.ai_family = hint_family; - hints.ai_flags = AI_CANONNAME; - { - /* strip [] on IPv6 address literals */ - char *trimmed_host = host_strduptrim(host); - err = p_getaddrinfo(trimmed_host, NULL, &hints, &ret->ais); - sfree(trimmed_host); - } - if (err == 0) - ret->superfamily = IP; - } else -#endif - { - /* - * Otherwise use the IPv4-only gethostbyname... - * (NOTE: we don't use gethostbyname as a fallback!) - */ - if ( (h = p_gethostbyname(host)) ) - ret->superfamily = IP; + /* + * Use getaddrinfo, as long as it's available. This should handle + * both IPv4 and IPv6 address literals, and hostnames, in one + * unified API. + */ + if (p_getaddrinfo) { + struct addrinfo hints; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = (address_family == ADDRTYPE_IPV4 ? AF_INET : + address_family == ADDRTYPE_IPV6 ? AF_INET6 : + AF_UNSPEC); + hints.ai_flags = AI_CANONNAME; + hints.ai_socktype = SOCK_STREAM; + + /* strip [] on IPv6 address literals */ + char *trimmed_host = host_strduptrim(host); + int err = p_getaddrinfo(trimmed_host, NULL, &hints, &addr->ais); + sfree(trimmed_host); + + if (addr->ais) { + addr->superfamily = IP; + if (addr->ais->ai_canonname) + *canonicalname = dupstr(addr->ais->ai_canonname); else - err = p_WSAGetLastError(); + *canonicalname = dupstr(host); + } else { + addr->error = namelookup_strerror(err); } + return addr; + } +#endif - if (ret->superfamily != IP) { - ret->error = (err == WSAENETDOWN ? "Network is down" : - err == WSAHOST_NOT_FOUND ? "Host does not exist" : - err == WSATRY_AGAIN ? "Host not found" : - win_strerror(err)); - } else { - ret->error = NULL; + /* + * Failing that (if IPv6 support was not compiled in, or if + * getaddrinfo turned out to be unavailable at run time), try the + * old-fashioned approach, which is to start by manually checking + * for an IPv4 literal and then use gethostbyname. + */ + unsigned long a = p_inet_addr(host); + if (a != (unsigned long) INADDR_NONE) { + addr->addresses = snew(unsigned long); + addr->naddresses = 1; + addr->addresses[0] = p_ntohl(a); + addr->superfamily = IP; + *canonicalname = dupstr(host); + return addr; + } -#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)); - } + struct hostent *h = p_gethostbyname(host); + if (h) { + addr->superfamily = IP; + + size_t n; + for (n = 0; h->h_addr_list[n]; n++); + addr->addresses = snewn(n, unsigned long); + addr->naddresses = n; + for (n = 0; n < addr->naddresses; n++) { + uint32_t a; + memcpy(&a, h->h_addr_list[n], sizeof(a)); + addr->addresses[n] = p_ntohl(a); } + + *canonicalname = dupstr(h->h_name); } 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->superfamily = IP; - strncpy(realhost, host, sizeof(realhost)); + DWORD err = p_WSAGetLastError(); + addr->error = namelookup_strerror(err); } - realhost[lenof(realhost)-1] = '\0'; - *canonicalname = dupstr(realhost); - return ret; + return addr; } static SockAddr *sk_special_addr(SuperFamily superfamily, const char *name) -- cgit v1.2.3 From a2ac5ec287354cddb5df681578bba5d55b2cc442 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 29 Apr 2022 16:28:58 +0100 Subject: SSH proxy: separate stdout from stderr. In the initial version of SSH proxying that only opened direct-tcpip channels, this wasn't important. But as of commit 6f7c52dccee36f6, we now support invoking a command or subsystem on the proxy SSH server, and those _can_ generate stderr data which we must now separate from stdout. Happily, we have a perfectly sensible thing to _do_ with it: the same thing we'd do with stderr coming from a local proxy subprocess, to wit, pass it to log_proxy_stderr so that it can appear in the terminal window (if configured to) and the Event Log. --- proxy/sshproxy.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/proxy/sshproxy.c b/proxy/sshproxy.c index 057cd4b6..7e217bd2 100644 --- a/proxy/sshproxy.c +++ b/proxy/sshproxy.c @@ -255,8 +255,15 @@ static size_t sshproxy_output(Seat *seat, SeatOutputType type, const void *data, size_t len) { SshProxy *sp = container_of(seat, SshProxy, seat); - bufchain_add(&sp->ssh_to_socket, data, len); - try_send_ssh_to_socket(sp); + switch (type) { + case SEAT_OUTPUT_STDOUT: + bufchain_add(&sp->ssh_to_socket, data, len); + try_send_ssh_to_socket(sp); + break; + case SEAT_OUTPUT_STDERR: + log_proxy_stderr(sp->plug, &sp->psb, data, len); + break; + } return bufchain_size(&sp->ssh_to_socket); } -- cgit v1.2.3 From 1cf4f509817b3c33cc75df3972cf553f2d939868 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 29 Apr 2022 16:41:18 +0100 Subject: sshproxy.c: remember to call prepare_session(). prepare_session() is the function that takes a Conf in the form it was loaded from the configuration, and normalises it into the form that backends can make sense of easily. In particular, if CONF_host contains something like "user@hostname", this is the place where the "user@" is stripped off the hostname field and moved into CONF_username where the backend will expect to find it. Therefore, you're _always_ supposed to call prepare_session() before launching a backend from a Conf you (potentially) got from a saved session. But the SSH proxy code forgot to. As a result, if you had a saved session with "user@hostname" in the hostname field, it would work fine to launch that session directly in PuTTY, but trying to use the same saved session as an SSH proxy would fail mysteriously after trying to pass "user@hostname" to getaddrinfo() and getting nothing useful back. (On Windows, the error message is especially opaque: an invalid string of that kind generates an error code that we weren't even tranlsating. And even if we _do_ translate it, it wouldn't be very meaningful, because the error in question is WSANO_RECOVERY, which FormatMessage renders as "A non-recoverable error occurred during a database lookup." I guess the error in question was that Windows couldn't even figure out how to translate that string into the format of a DNS request message!) --- proxy/sshproxy.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/proxy/sshproxy.c b/proxy/sshproxy.c index 95daecb1..a699cd7c 100644 --- a/proxy/sshproxy.c +++ b/proxy/sshproxy.c @@ -643,6 +643,12 @@ Socket *sshproxy_new_connection(SockAddr *addr, const char *hostname, conf_set_str(sp->conf, CONF_ssh_nc_host, hostname); conf_set_int(sp->conf, CONF_ssh_nc_port, port); + /* + * Do the usual normalisation of things in the Conf like a "user@" + * prefix on the hostname field. + */ + prepare_session(sp->conf); + sp->logctx = log_init(&sp->logpolicy, sp->conf); char *error, *realhost; -- cgit v1.2.3 From 1088080cddc952bbf4d8eb1788565550fdf5fe4b Mon Sep 17 00:00:00 2001 From: Jacob Nevins Date: Fri, 29 Apr 2022 18:35:24 +0100 Subject: Tweaks to proxy documentation. --- doc/config.but | 43 ++++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/doc/config.but b/doc/config.but index 813d243d..ed3839de 100644 --- a/doc/config.but +++ b/doc/config.but @@ -1956,10 +1956,11 @@ in \W{http://www.ietf.org/rfc/rfc2817.txt}{RFC 2817}. 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 +user can make a Telnet or TCP 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. +allows you to tell PuTTY to use this type of proxy, with the precise +command specified as described in \k{config-proxy-command}. \b Selecting \q{SSH} causes PuTTY to make a secondary SSH connection to the proxy host (sometimes called a \q{\i{jump host}} in this @@ -2069,13 +2070,18 @@ separate GSSAPI library, so PuTTY can't override or reconfigure it. \S{config-proxy-auth} \I{proxy username}Username and \I{proxy password}password -If your proxy requires \I{proxy authentication}authentication, you can -enter a username and a password in the \q{Username} and \q{Password} boxes. +You can enter a username and a password in the \q{Username} and +\q{Password} boxes, which will be used if your proxy requires +\I{proxy authentication}authentication. \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. +If PuTTY discovers that it needs a proxy username or password and you +have not specified one here, PuTTY will prompt for it interactively in +the terminal window. + Authentication is not fully supported for all forms of proxy: \b Username and password authentication is supported for HTTP @@ -2099,22 +2105,16 @@ passwords. \b SSH proxying can use all the same forms of SSH authentication supported by PuTTY for its main connection. If the SSH server requests -password authentication, the configured proxy password will be used, -but other authentication methods such as public keys will be tried -first, just as for a primary SSH connection. +password authentication, any configured proxy password will be used, +but other authentication methods such as public keys and GSSAPI will +be tried first, just as for a primary SSH connection, and if they +require credentials such as a key passphrase, PuTTY will interactively +prompt for these. \b You can specify a way to include a username and password in the -Telnet/Local proxy command (see \k{config-proxy-command}). - -If PuTTY discovers that it needs a proxy username or password and you -have not specified one in the configuration, it will prompt for it -interactively in the terminal. - -(For SSH proxying, this will also happen in the case of other -interactive SSH login prompts, such as SSH key passphrases or GSSAPI. -For the Telnet and Local proxy types, PuTTY will prompt for a username -or password if you included \c{%user} or \c{%pass} in the command -string and did not provide a corresponding configuration entry.) +Telnet/Local proxy command (see \k{config-proxy-command}). If you do +so, and don't also specify the actual username and/or password in the +configuration, PuTTY will interactively prompt for them. \S{config-proxy-command} Specifying the Telnet or Local proxy command @@ -2135,7 +2135,8 @@ 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} +password (which, if not specified in the configuration, will be +prompted for). 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{%%}. @@ -2148,8 +2149,8 @@ before commands can be sent, you can use a command such as: 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. +tokens in the Telnet command, then anything specified in \q{Username} +and \q{Password} configuration fields will be ignored. \S{config-proxy-logging} Controlling \i{proxy logging} -- cgit v1.2.3 From 1dfa0f538b269482cc42fdc02ff39d21370c2b92 Mon Sep 17 00:00:00 2001 From: Jacob Nevins Date: Fri, 29 Apr 2022 19:01:57 +0100 Subject: Update proxy docs to reflect recent changes. For new UI in 2a26ebd0d5, and new features added in 6f7c52dcce. --- doc/config.but | 52 ++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 36 insertions(+), 16 deletions(-) diff --git a/doc/config.but b/doc/config.but index ed3839de..66d57711 100644 --- a/doc/config.but +++ b/doc/config.but @@ -1943,14 +1943,14 @@ it must always be explicitly configured. \S{config-proxy-type} Setting the proxy type -The \q{Proxy type} radio buttons allow you to configure what type of +The \q{Proxy type} drop-down allows 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 \I{HTTP proxy}\q{HTTP CONNECT} 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}. @@ -1962,10 +1962,9 @@ through to an external host. Selecting \I{Telnet proxy}\q{Telnet} allows you to tell PuTTY to use this type of proxy, with the precise command specified as described in \k{config-proxy-command}. -\b Selecting \q{SSH} causes PuTTY to make a secondary SSH connection -to the proxy host (sometimes called a \q{\i{jump host}} in this -context), and then open a port-forwarding channel to the -final destination host. +\b There are several ways to use a SSH server as a proxy. All of +these cause PuTTY to make a secondary SSH connection to the proxy host +(sometimes called a \q{\i{jump host}} in this context). \lcont{ The \q{Proxy hostname} field will be interpreted as the name of a @@ -1973,6 +1972,20 @@ PuTTY saved session if one exists, or a hostname if not. This allows multi-hop jump paths, if the referenced saved session is itself configured to use an SSH proxy; and it allows combining SSH and non-SSH proxying. + +\b \q{SSH to proxy and use port forwarding} causes PuTTY to use the +secondary SSH connection to open a port-forwarding channel to the +final destination host (similar to OpenSSH's \cw{-J} option). + +\b \q{SSH to proxy and execute a command} causes PuTTY to run an +arbitrary remote command on the proxy SSH server and use that +command's standard input and output streams to run the primary +connection over. The remote command line is specified as described in +\k{config-proxy-command}. + +\b \q{SSH to proxy and invoke a subsystem} is similar but causes PuTTY +to start an SSH \q{\i{subsystem}} rather than an ordinary command line. +This might be useful with a specially set up SSH proxy server. } \b Selecting \I{Local proxy}\q{Local} allows you to specify an arbitrary @@ -2116,16 +2129,21 @@ Telnet/Local proxy command (see \k{config-proxy-command}). If you do so, and don't also specify the actual username and/or password in the configuration, PuTTY will interactively prompt for them. -\S{config-proxy-command} Specifying the Telnet or Local proxy command +\S{config-proxy-command} Specifying the Telnet, SSH, or Local 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. +you can enter an alternative in the \q{Command to send to proxy} box. If you are using the \i{Local proxy} type, the local command to run is specified here. +If you are using the \q{SSH to proxy and execute a command} type, the +command to run on the SSH proxy server is specified here. Similarly, if +you are using \q{SSH to proxy and invoke a subsystem}, the subsystem +name is constructed as 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 @@ -2133,13 +2151,15 @@ 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 (which, if not specified in the configuration, will be -prompted for). The strings \c{%proxyhost} and \c{%proxyport} +by the host name and port number you want to connect to. For Telnet +and Local proxy types, the strings \c{%user} and \c{%pass} will be +replaced by the proxy username and password (which, if not specified +in the configuration, will be prompted for) \dash this does not happen +with SSH proxy types (because the proxy username/password are used +for SSH authentication). 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 any (this is most likely to be useful for proxy types using a +local or remote command). 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: -- cgit v1.2.3 From 958304897d7bcb99438c4254c29dd18d0f20b61d Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 29 Apr 2022 22:44:40 +0100 Subject: Fix rekeying when using a certified host key. In a rekey, we expect to see the same host key again, which we enforce by comparing its cache string, which we happened to have handy. But certified host keys don't have cache strings, so this no longer works reliably - the 'assert(s->keystr)' fails. (This is what I get for making a zillion short-lived test connections and not leaving any of them running for more than 2 minutes!) Instead, we now keep the official public blob of the host key from the first key exchange, and compare that to the public blob of the one in the rekey. --- ssh/kex2-client.c | 16 ++++++++++------ ssh/transport2.c | 1 - ssh/transport2.h | 4 ++-- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/ssh/kex2-client.c b/ssh/kex2-client.c index c01bd0fe..41e41503 100644 --- a/ssh/kex2-client.c +++ b/ssh/kex2-client.c @@ -952,8 +952,8 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) * Save this host key, to check against the one presented in * subsequent rekeys. */ - s->hostkey_str = s->keystr; - s->keystr = NULL; + strbuf_clear(s->hostkeyblob); + ssh_key_public_blob(s->hkey, BinarySink_UPCAST(s->hostkeyblob)); } else if (s->cross_certifying) { assert(s->hkey); assert(ssh_key_alg(s->hkey) == s->cross_certifying); @@ -969,8 +969,8 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) * Don't forget to store the new key as the one we'll be * re-checking in future normal rekeys. */ - s->hostkey_str = s->keystr; - s->keystr = NULL; + strbuf_clear(s->hostkeyblob); + ssh_key_public_blob(s->hkey, BinarySink_UPCAST(s->hostkeyblob)); } else { /* * In a rekey, we never present an interactive host key @@ -978,8 +978,12 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) * enforce that the key we're seeing this time is identical to * the one we saw before. */ - assert(s->keystr); /* filled in by prior key exchange */ - if (strcmp(s->hostkey_str, s->keystr)) { + strbuf *thisblob = strbuf_new(); + ssh_key_public_blob(s->hkey, BinarySink_UPCAST(thisblob)); + bool match = ptrlen_eq_ptrlen(ptrlen_from_strbuf(thisblob), + ptrlen_from_strbuf(s->hostkeyblob)); + strbuf_free(thisblob); + if (!match) { #ifndef FUZZING ssh_sw_abort(s->ppl.ssh, "Host key was different in repeat key exchange"); diff --git a/ssh/transport2.c b/ssh/transport2.c index 7265444f..1d30f240 100644 --- a/ssh/transport2.c +++ b/ssh/transport2.c @@ -212,7 +212,6 @@ static void ssh2_transport_free(PacketProtocolLayer *ppl) sfree(s->client_greeting); sfree(s->server_greeting); sfree(s->keystr); - sfree(s->hostkey_str); strbuf_free(s->hostkeyblob); { host_ca *hca; diff --git a/ssh/transport2.h b/ssh/transport2.h index 72cd5fba..a5f85103 100644 --- a/ssh/transport2.h +++ b/ssh/transport2.h @@ -140,7 +140,6 @@ struct ssh2_transport_state { const ssh_kex *kex_alg; const ssh_keyalg *hostkey_alg; - char *hostkey_str; /* string representation, for easy checking in rekeys */ unsigned char session_id[MAX_HASH_LEN]; int session_id_len; int dh_min_size, dh_max_size; @@ -188,7 +187,8 @@ struct ssh2_transport_state { int kex_init_value, kex_reply_value; transport_direction in, out, *cstrans, *sctrans; ptrlen hostkeydata, sigdata; - strbuf *hostkeyblob; + strbuf *hostkeyblob; /* used in server to construct host key to + * send to client; in client to check in rekeys */ char *keystr; ssh_key *hkey; /* actual host key */ unsigned hkflags; /* signing flags, used in server */ -- cgit v1.2.3 From 77d15c46c308d262feca61698a74e59fe0c1f3f1 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 1 May 2022 09:48:38 +0100 Subject: New typedef 'dlgcontrol' wrapping 'union control'. I'm about to change my mind about whether its top-level nature is struct or union, and rather than change the key word 'union' to 'struct' at every point of use, it's nicer to just get rid of the keyword completely. So it has a shiny new name. --- config.c | 120 +++++++++++++++++++++---------------------- defs.h | 1 + dialog.c | 110 +++++++++++++++++++-------------------- dialog.h | 148 ++++++++++++++++++++++++++--------------------------- putty.h | 13 +++-- test/fuzzterm.c | 56 ++++++++++---------- unix/config-gtk.c | 6 +-- unix/config-unix.c | 2 +- unix/dialog.c | 80 ++++++++++++++--------------- windows/config.c | 12 ++--- windows/controls.c | 70 ++++++++++++------------- windows/platform.h | 10 ++-- 12 files changed, 313 insertions(+), 315 deletions(-) diff --git a/config.c b/config.c index 01d58abd..c4c932a9 100644 --- a/config.c +++ b/config.c @@ -16,7 +16,7 @@ #define HOST_BOX_TITLE "Host Name (or IP address)" #define PORT_BOX_TITLE "Port" -void conf_radiobutton_handler(union control *ctrl, dlgparam *dlg, +void conf_radiobutton_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { int button; @@ -44,7 +44,7 @@ void conf_radiobutton_handler(union control *ctrl, dlgparam *dlg, } } -void conf_radiobutton_bool_handler(union control *ctrl, dlgparam *dlg, +void conf_radiobutton_bool_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { int button; @@ -72,7 +72,7 @@ void conf_radiobutton_bool_handler(union control *ctrl, dlgparam *dlg, } #define CHECKBOX_INVERT (1<<30) -void conf_checkbox_handler(union control *ctrl, dlgparam *dlg, +void conf_checkbox_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { int key; @@ -104,7 +104,7 @@ void conf_checkbox_handler(union control *ctrl, dlgparam *dlg, } } -void conf_editbox_handler(union control *ctrl, dlgparam *dlg, +void conf_editbox_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { /* @@ -153,7 +153,7 @@ void conf_editbox_handler(union control *ctrl, dlgparam *dlg, } } -void conf_filesel_handler(union control *ctrl, dlgparam *dlg, +void conf_filesel_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { int key = ctrl->fileselect.context.i; @@ -169,7 +169,7 @@ void conf_filesel_handler(union control *ctrl, dlgparam *dlg, } } -void conf_fontsel_handler(union control *ctrl, dlgparam *dlg, +void conf_fontsel_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { int key = ctrl->fontselect.context.i; @@ -185,7 +185,7 @@ void conf_fontsel_handler(union control *ctrl, dlgparam *dlg, } } -static void config_host_handler(union control *ctrl, dlgparam *dlg, +static void config_host_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { Conf *conf = (Conf *)data; @@ -217,7 +217,7 @@ static void config_host_handler(union control *ctrl, dlgparam *dlg, } } -static void config_port_handler(union control *ctrl, dlgparam *dlg, +static void config_port_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { Conf *conf = (Conf *)data; @@ -258,7 +258,7 @@ static void config_port_handler(union control *ctrl, dlgparam *dlg, } struct hostport { - union control *host, *port, *protradio, *protlist; + dlgcontrol *host, *port, *protradio, *protlist; bool mid_refresh; }; @@ -269,7 +269,7 @@ struct hostport { * and refreshes both host and port boxes when switching to/from the * serial backend. */ -static void config_protocols_handler(union control *ctrl, dlgparam *dlg, +static void config_protocols_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { Conf *conf = (Conf *)data; @@ -422,7 +422,7 @@ static void config_protocols_handler(union control *ctrl, dlgparam *dlg, } } -static void loggingbuttons_handler(union control *ctrl, dlgparam *dlg, +static void loggingbuttons_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { int button; @@ -451,7 +451,7 @@ static void loggingbuttons_handler(union control *ctrl, dlgparam *dlg, } } -static void numeric_keypad_handler(union control *ctrl, dlgparam *dlg, +static void numeric_keypad_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { int button; @@ -482,7 +482,7 @@ static void numeric_keypad_handler(union control *ctrl, dlgparam *dlg, } } -static void cipherlist_handler(union control *ctrl, dlgparam *dlg, +static void cipherlist_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { Conf *conf = (Conf *)data; @@ -528,7 +528,7 @@ static void cipherlist_handler(union control *ctrl, dlgparam *dlg, } #ifndef NO_GSSAPI -static void gsslist_handler(union control *ctrl, dlgparam *dlg, +static void gsslist_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { Conf *conf = (Conf *)data; @@ -555,7 +555,7 @@ static void gsslist_handler(union control *ctrl, dlgparam *dlg, } #endif -static void kexlist_handler(union control *ctrl, dlgparam *dlg, +static void kexlist_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { Conf *conf = (Conf *)data; @@ -601,7 +601,7 @@ static void kexlist_handler(union control *ctrl, dlgparam *dlg, } } -static void hklist_handler(union control *ctrl, dlgparam *dlg, +static void hklist_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { Conf *conf = (Conf *)data; @@ -645,7 +645,7 @@ static void hklist_handler(union control *ctrl, dlgparam *dlg, } } -static void printerbox_handler(union control *ctrl, dlgparam *dlg, +static void printerbox_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { Conf *conf = (Conf *)data; @@ -681,7 +681,7 @@ static void printerbox_handler(union control *ctrl, dlgparam *dlg, } } -static void codepage_handler(union control *ctrl, dlgparam *dlg, +static void codepage_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { Conf *conf = (Conf *)data; @@ -705,7 +705,7 @@ static void codepage_handler(union control *ctrl, dlgparam *dlg, } } -static void sshbug_handler(union control *ctrl, dlgparam *dlg, +static void sshbug_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { Conf *conf = (Conf *)data; @@ -738,7 +738,7 @@ static void sshbug_handler(union control *ctrl, dlgparam *dlg, } } -static void sshbug_handler_manual_only(union control *ctrl, dlgparam *dlg, +static void sshbug_handler_manual_only(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { /* @@ -770,8 +770,8 @@ static void sshbug_handler_manual_only(union control *ctrl, dlgparam *dlg, } struct sessionsaver_data { - union control *editbox, *listbox, *loadbutton, *savebutton, *delbutton; - union control *okbutton, *cancelbutton; + dlgcontrol *editbox, *listbox, *loadbutton, *savebutton, *delbutton; + dlgcontrol *okbutton, *cancelbutton; struct sesslist sesslist; bool midsession; char *savedsession; /* the current contents of ssd->editbox */ @@ -813,7 +813,7 @@ static bool load_selected_session( return true; } -static void sessionsaver_handler(union control *ctrl, dlgparam *dlg, +static void sessionsaver_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { Conf *conf = (Conf *)data; @@ -951,10 +951,10 @@ static void sessionsaver_handler(union control *ctrl, dlgparam *dlg, } struct charclass_data { - union control *listbox, *editbox, *button; + dlgcontrol *listbox, *editbox, *button; }; -static void charclass_handler(union control *ctrl, dlgparam *dlg, +static void charclass_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { Conf *conf = (Conf *)data; @@ -992,7 +992,7 @@ static void charclass_handler(union control *ctrl, dlgparam *dlg, } struct colour_data { - union control *listbox, *redit, *gedit, *bedit, *button; + dlgcontrol *listbox, *redit, *gedit, *bedit, *button; }; /* Array of the user-visible colour names defined in the list macro in @@ -1003,7 +1003,7 @@ static const char *const colours[] = { #undef CONF_COLOUR_NAME_DECL }; -static void colour_handler(union control *ctrl, dlgparam *dlg, +static void colour_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { Conf *conf = (Conf *)data; @@ -1109,10 +1109,10 @@ static void colour_handler(union control *ctrl, dlgparam *dlg, } struct ttymodes_data { - union control *valradio, *valbox, *setbutton, *listbox; + dlgcontrol *valradio, *valbox, *setbutton, *listbox; }; -static void ttymodes_handler(union control *ctrl, dlgparam *dlg, +static void ttymodes_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { Conf *conf = (Conf *)data; @@ -1194,10 +1194,10 @@ static void ttymodes_handler(union control *ctrl, dlgparam *dlg, } struct environ_data { - union control *varbox, *valbox, *addbutton, *rembutton, *listbox; + dlgcontrol *varbox, *valbox, *addbutton, *rembutton, *listbox; }; -static void environ_handler(union control *ctrl, dlgparam *dlg, +static void environ_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { Conf *conf = (Conf *)data; @@ -1266,14 +1266,14 @@ static void environ_handler(union control *ctrl, dlgparam *dlg, } struct portfwd_data { - union control *addbutton, *rembutton, *listbox; - union control *sourcebox, *destbox, *direction; + dlgcontrol *addbutton, *rembutton, *listbox; + dlgcontrol *sourcebox, *destbox, *direction; #ifndef NO_IPV6 - union control *addressfamily; + dlgcontrol *addressfamily; #endif }; -static void portfwd_handler(union control *ctrl, dlgparam *dlg, +static void portfwd_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { Conf *conf = (Conf *)data; @@ -1434,10 +1434,10 @@ static void portfwd_handler(union control *ctrl, dlgparam *dlg, } struct manual_hostkey_data { - union control *addbutton, *rembutton, *listbox, *keybox; + dlgcontrol *addbutton, *rembutton, *listbox, *keybox; }; -static void manual_hostkey_handler(union control *ctrl, dlgparam *dlg, +static void manual_hostkey_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { Conf *conf = (Conf *)data; @@ -1500,7 +1500,7 @@ static void manual_hostkey_handler(union control *ctrl, dlgparam *dlg, } } -static void clipboard_selector_handler(union control *ctrl, dlgparam *dlg, +static void clipboard_selector_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { Conf *conf = (Conf *)data; @@ -1601,7 +1601,7 @@ static void clipboard_control(struct controlset *s, const char *label, #endif } -static void serial_parity_handler(union control *ctrl, dlgparam *dlg, +static void serial_parity_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { static const struct { @@ -1656,7 +1656,7 @@ static void serial_parity_handler(union control *ctrl, dlgparam *dlg, } } -static void serial_flow_handler(union control *ctrl, dlgparam *dlg, +static void serial_flow_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { static const struct { @@ -1709,7 +1709,7 @@ static void serial_flow_handler(union control *ctrl, dlgparam *dlg, } } -void proxy_type_handler(union control *ctrl, dlgparam *dlg, +void proxy_type_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { Conf *conf = (Conf *)data; @@ -1763,7 +1763,7 @@ void proxy_type_handler(union control *ctrl, dlgparam *dlg, } } -static void host_ca_button_handler(union control *ctrl, dlgparam *dp, +static void host_ca_button_handler(dlgcontrol *ctrl, dlgparam *dp, void *data, int event) { if (event == EVENT_ACTION) @@ -1782,7 +1782,7 @@ void setup_config_box(struct controlbox *b, bool midsession, struct environ_data *ed; struct portfwd_data *pfd; struct manual_hostkey_data *mh; - union control *c; + dlgcontrol *c; bool resize_forbidden = false; char *str; @@ -3318,11 +3318,11 @@ void setup_config_box(struct controlbox *b, bool midsession, } struct ca_state { - union control *ca_name_edit; - union control *ca_reclist; - union control *ca_pubkey_edit; - union control *ca_wclist; - union control *ca_wc_edit; + dlgcontrol *ca_name_edit; + dlgcontrol *ca_reclist; + dlgcontrol *ca_pubkey_edit; + dlgcontrol *ca_wclist; + dlgcontrol *ca_wc_edit; char *name, *pubkey, *wc; tree234 *ca_names; /* stores plain 'char *' */ tree234 *host_wcs; /* stores plain 'char *' */ @@ -3416,14 +3416,14 @@ static void ca_load_selected_record(struct ca_state *st, dlgparam *dp) dlg_refresh(st->ca_wclist, dp); } -static void ca_ok_handler(union control *ctrl, dlgparam *dp, +static void ca_ok_handler(dlgcontrol *ctrl, dlgparam *dp, void *data, int event) { if (event == EVENT_ACTION) dlg_end(dp, 0); } -static void ca_name_handler(union control *ctrl, dlgparam *dp, +static void ca_name_handler(dlgcontrol *ctrl, dlgparam *dp, void *data, int event) { struct ca_state *st = (struct ca_state *)ctrl->generic.context.p; @@ -3444,7 +3444,7 @@ static void ca_name_handler(union control *ctrl, dlgparam *dp, } } -static void ca_reclist_handler(union control *ctrl, dlgparam *dp, +static void ca_reclist_handler(dlgcontrol *ctrl, dlgparam *dp, void *data, int event) { struct ca_state *st = (struct ca_state *)ctrl->generic.context.p; @@ -3461,7 +3461,7 @@ static void ca_reclist_handler(union control *ctrl, dlgparam *dp, } } -static void ca_load_handler(union control *ctrl, dlgparam *dp, +static void ca_load_handler(dlgcontrol *ctrl, dlgparam *dp, void *data, int event) { struct ca_state *st = (struct ca_state *)ctrl->generic.context.p; @@ -3470,7 +3470,7 @@ static void ca_load_handler(union control *ctrl, dlgparam *dp, } } -static void ca_save_handler(union control *ctrl, dlgparam *dp, +static void ca_save_handler(dlgcontrol *ctrl, dlgparam *dp, void *data, int event) { struct ca_state *st = (struct ca_state *)ctrl->generic.context.p; @@ -3496,7 +3496,7 @@ static void ca_save_handler(union control *ctrl, dlgparam *dp, } } -static void ca_delete_handler(union control *ctrl, dlgparam *dp, +static void ca_delete_handler(dlgcontrol *ctrl, dlgparam *dp, void *data, int event) { struct ca_state *st = (struct ca_state *)ctrl->generic.context.p; @@ -3523,7 +3523,7 @@ static void ca_delete_handler(union control *ctrl, dlgparam *dp, } } -static void ca_pubkey_handler(union control *ctrl, dlgparam *dp, +static void ca_pubkey_handler(dlgcontrol *ctrl, dlgparam *dp, void *data, int event) { struct ca_state *st = (struct ca_state *)ctrl->generic.context.p; @@ -3535,7 +3535,7 @@ static void ca_pubkey_handler(union control *ctrl, dlgparam *dp, } } -static void ca_wclist_handler(union control *ctrl, dlgparam *dp, +static void ca_wclist_handler(dlgcontrol *ctrl, dlgparam *dp, void *data, int event) { struct ca_state *st = (struct ca_state *)ctrl->generic.context.p; @@ -3549,7 +3549,7 @@ static void ca_wclist_handler(union control *ctrl, dlgparam *dp, } } -static void ca_wc_edit_handler(union control *ctrl, dlgparam *dp, +static void ca_wc_edit_handler(dlgcontrol *ctrl, dlgparam *dp, void *data, int event) { struct ca_state *st = (struct ca_state *)ctrl->generic.context.p; @@ -3561,7 +3561,7 @@ static void ca_wc_edit_handler(union control *ctrl, dlgparam *dp, } } -static void ca_wc_add_handler(union control *ctrl, dlgparam *dp, +static void ca_wc_add_handler(dlgcontrol *ctrl, dlgparam *dp, void *data, int event) { struct ca_state *st = (struct ca_state *)ctrl->generic.context.p; @@ -3582,7 +3582,7 @@ static void ca_wc_add_handler(union control *ctrl, dlgparam *dp, } } -static void ca_wc_rem_handler(union control *ctrl, dlgparam *dp, +static void ca_wc_rem_handler(dlgcontrol *ctrl, dlgparam *dp, void *data, int event) { struct ca_state *st = (struct ca_state *)ctrl->generic.context.p; @@ -3608,7 +3608,7 @@ static void ca_wc_rem_handler(union control *ctrl, dlgparam *dp, void setup_ca_config_box(struct controlbox *b) { struct controlset *s; - union control *c; + dlgcontrol *c; /* Internal state for manipulating the host CA system */ struct ca_state *st = (struct ca_state *)ctrl_alloc_with_free( diff --git a/defs.h b/defs.h index d9f2e355..063c05cd 100644 --- a/defs.h +++ b/defs.h @@ -172,6 +172,7 @@ typedef struct NTRUKeyPair NTRUKeyPair; typedef struct NTRUEncodeSchedule NTRUEncodeSchedule; typedef struct dlgparam dlgparam; +typedef union control dlgcontrol; typedef struct settings_w settings_w; typedef struct settings_r settings_r; diff --git a/dialog.c b/dialog.c index 7409daaa..79ef7a10 100644 --- a/dialog.c +++ b/dialog.c @@ -204,11 +204,11 @@ void *ctrl_alloc(struct controlbox *b, size_t size) return ctrl_alloc_with_free(b, size, ctrl_default_free); } -static union control *ctrl_new(struct controlset *s, int type, - intorptr helpctx, handler_fn handler, - intorptr context) +static dlgcontrol *ctrl_new(struct controlset *s, int type, + intorptr helpctx, handler_fn handler, + intorptr context) { - union control *c = snew(union control); + dlgcontrol *c = snew(dlgcontrol); sgrowarray(s->ctrls, s->ctrlsize, s->ncontrols); s->ctrls[s->ncontrols++] = c; /* @@ -226,9 +226,9 @@ static union control *ctrl_new(struct controlset *s, int type, } /* `ncolumns' is followed by that many percentages, as integers. */ -union control *ctrl_columns(struct controlset *s, int ncolumns, ...) +dlgcontrol *ctrl_columns(struct controlset *s, int ncolumns, ...) { - union control *c = ctrl_new(s, CTRL_COLUMNS, P(NULL), NULL, P(NULL)); + dlgcontrol *c = ctrl_new(s, CTRL_COLUMNS, P(NULL), NULL, P(NULL)); assert(s->ncolumns == 1 || ncolumns == 1); c->columns.ncols = ncolumns; s->ncolumns = ncolumns; @@ -246,12 +246,12 @@ union control *ctrl_columns(struct controlset *s, int ncolumns, ...) return c; } -union control *ctrl_editbox(struct controlset *s, const char *label, - char shortcut, int percentage, - intorptr helpctx, handler_fn handler, - intorptr context, intorptr context2) +dlgcontrol *ctrl_editbox(struct controlset *s, const 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); + dlgcontrol *c = ctrl_new(s, CTRL_EDITBOX, helpctx, handler, context); c->editbox.label = label ? dupstr(label) : NULL; c->editbox.shortcut = shortcut; c->editbox.percentwidth = percentage; @@ -261,12 +261,12 @@ union control *ctrl_editbox(struct controlset *s, const char *label, return c; } -union control *ctrl_combobox(struct controlset *s, const char *label, - char shortcut, int percentage, - intorptr helpctx, handler_fn handler, - intorptr context, intorptr context2) +dlgcontrol *ctrl_combobox(struct controlset *s, const 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); + dlgcontrol *c = ctrl_new(s, CTRL_EDITBOX, helpctx, handler, context); c->editbox.label = label ? dupstr(label) : NULL; c->editbox.shortcut = shortcut; c->editbox.percentwidth = percentage; @@ -282,13 +282,13 @@ union control *ctrl_combobox(struct controlset *s, const char *label, * title is expected to be followed by a shortcut _iff_ `shortcut' * is NO_SHORTCUT. */ -union control *ctrl_radiobuttons(struct controlset *s, const char *label, - char shortcut, int ncolumns, intorptr helpctx, - handler_fn handler, intorptr context, ...) +dlgcontrol *ctrl_radiobuttons(struct controlset *s, const 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); + dlgcontrol *c = ctrl_new(s, CTRL_RADIO, helpctx, handler, context); c->radio.label = label ? dupstr(label) : NULL; c->radio.shortcut = shortcut; c->radio.ncolumns = ncolumns; @@ -328,11 +328,11 @@ union control *ctrl_radiobuttons(struct controlset *s, const char *label, return c; } -union control *ctrl_pushbutton(struct controlset *s, const char *label, - char shortcut, intorptr helpctx, - handler_fn handler, intorptr context) +dlgcontrol *ctrl_pushbutton(struct controlset *s, const char *label, + char shortcut, intorptr helpctx, + handler_fn handler, intorptr context) { - union control *c = ctrl_new(s, CTRL_BUTTON, helpctx, handler, context); + dlgcontrol *c = ctrl_new(s, CTRL_BUTTON, helpctx, handler, context); c->button.label = label ? dupstr(label) : NULL; c->button.shortcut = shortcut; c->button.isdefault = false; @@ -340,11 +340,11 @@ union control *ctrl_pushbutton(struct controlset *s, const char *label, return c; } -union control *ctrl_listbox(struct controlset *s, const char *label, - char shortcut, intorptr helpctx, - handler_fn handler, intorptr context) +dlgcontrol *ctrl_listbox(struct controlset *s, const char *label, + char shortcut, intorptr helpctx, + handler_fn handler, intorptr context) { - union control *c = ctrl_new(s, CTRL_LISTBOX, helpctx, handler, context); + dlgcontrol *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 */ @@ -357,11 +357,11 @@ union control *ctrl_listbox(struct controlset *s, const char *label, return c; } -union control *ctrl_droplist(struct controlset *s, const char *label, - char shortcut, int percentage, intorptr helpctx, - handler_fn handler, intorptr context) +dlgcontrol *ctrl_droplist(struct controlset *s, const char *label, + char shortcut, int percentage, intorptr helpctx, + handler_fn handler, intorptr context) { - union control *c = ctrl_new(s, CTRL_LISTBOX, helpctx, handler, context); + dlgcontrol *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 */ @@ -374,11 +374,11 @@ union control *ctrl_droplist(struct controlset *s, const char *label, return c; } -union control *ctrl_draglist(struct controlset *s, const char *label, - char shortcut, intorptr helpctx, - handler_fn handler, intorptr context) +dlgcontrol *ctrl_draglist(struct controlset *s, const char *label, + char shortcut, intorptr helpctx, + handler_fn handler, intorptr context) { - union control *c = ctrl_new(s, CTRL_LISTBOX, helpctx, handler, context); + dlgcontrol *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 */ @@ -391,12 +391,12 @@ union control *ctrl_draglist(struct controlset *s, const char *label, return c; } -union control *ctrl_filesel(struct controlset *s, const char *label, - char shortcut, const char *filter, bool write, - const char *title, intorptr helpctx, - handler_fn handler, intorptr context) +dlgcontrol *ctrl_filesel(struct controlset *s, const char *label, + char shortcut, const char *filter, bool write, + const char *title, intorptr helpctx, + handler_fn handler, intorptr context) { - union control *c = ctrl_new(s, CTRL_FILESELECT, helpctx, handler, context); + dlgcontrol *c = ctrl_new(s, CTRL_FILESELECT, helpctx, handler, context); c->fileselect.label = label ? dupstr(label) : NULL; c->fileselect.shortcut = shortcut; c->fileselect.filter = filter; @@ -405,42 +405,42 @@ union control *ctrl_filesel(struct controlset *s, const char *label, return c; } -union control *ctrl_fontsel(struct controlset *s, const char *label, - char shortcut, intorptr helpctx, - handler_fn handler, intorptr context) +dlgcontrol *ctrl_fontsel(struct controlset *s, const char *label, + char shortcut, intorptr helpctx, + handler_fn handler, intorptr context) { - union control *c = ctrl_new(s, CTRL_FONTSELECT, helpctx, handler, context); + dlgcontrol *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) +dlgcontrol *ctrl_tabdelay(struct controlset *s, dlgcontrol *ctrl) { - union control *c = ctrl_new(s, CTRL_TABDELAY, P(NULL), NULL, P(NULL)); + dlgcontrol *c = ctrl_new(s, CTRL_TABDELAY, P(NULL), NULL, P(NULL)); c->tabdelay.ctrl = ctrl; return c; } -union control *ctrl_text(struct controlset *s, const char *text, - intorptr helpctx) +dlgcontrol *ctrl_text(struct controlset *s, const char *text, + intorptr helpctx) { - union control *c = ctrl_new(s, CTRL_TEXT, helpctx, NULL, P(NULL)); + dlgcontrol *c = ctrl_new(s, CTRL_TEXT, helpctx, NULL, P(NULL)); c->text.label = dupstr(text); return c; } -union control *ctrl_checkbox(struct controlset *s, const char *label, - char shortcut, intorptr helpctx, - handler_fn handler, intorptr context) +dlgcontrol *ctrl_checkbox(struct controlset *s, const char *label, + char shortcut, intorptr helpctx, + handler_fn handler, intorptr context) { - union control *c = ctrl_new(s, CTRL_CHECKBOX, helpctx, handler, context); + dlgcontrol *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) +void ctrl_free(dlgcontrol *ctrl) { int i; diff --git a/dialog.h b/dialog.h index 86ebfc20..28d0916c 100644 --- a/dialog.h +++ b/dialog.h @@ -73,8 +73,6 @@ PREFIX intorptr P(void *p) { intorptr ret; ret.p = p; return ret; } #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 @@ -103,7 +101,7 @@ enum { EVENT_SELCHANGE, EVENT_CALLBACK }; -typedef void (*handler_fn)(union control *ctrl, dlgparam *dp, +typedef void (*handler_fn)(dlgcontrol *ctrl, dlgparam *dp, void *data, int event); #define STANDARD_PREFIX \ @@ -114,7 +112,7 @@ typedef void (*handler_fn)(union control *ctrl, dlgparam *dp, handler_fn handler; \ intorptr context; \ intorptr helpctx; \ - union control *align_next_to + dlgcontrol *align_next_to union control { /* @@ -191,11 +189,11 @@ union control { * instantiated, the first one is already there to be referred * to. */ - union control *align_next_to; + dlgcontrol *align_next_to; } generic; struct { STANDARD_PREFIX; - union control *ctrl; + dlgcontrol *ctrl; } tabdelay; struct { STANDARD_PREFIX; @@ -422,7 +420,7 @@ union control { #undef STANDARD_PREFIX /* - * `controlset' is a container holding an array of `union control' + * `controlset' is a container holding an array of `dlgcontrol' * 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. @@ -435,9 +433,9 @@ struct controlset { char *boxname; /* internal short name of controlset */ char *boxtitle; /* title of container box */ int ncolumns; /* current no. of columns at bottom */ - size_t ncontrols; /* number of `union control' in array */ + size_t ncontrols; /* number of `dlgcontrol' in array */ size_t ctrlsize; /* allocated size of array */ - union control **ctrls; /* actual array */ + dlgcontrol **ctrls; /* actual array */ }; typedef void (*ctrl_freefn_t)(void *); /* used by ctrl_alloc_with_free */ @@ -471,7 +469,7 @@ struct controlset *ctrl_getset(struct controlbox *, const char *path, const char *name, const char *boxtitle); void ctrl_free_set(struct controlset *); -void ctrl_free(union control *); +void ctrl_free(dlgcontrol *); /* * This function works like `malloc', but the memory it returns @@ -490,73 +488,73 @@ void *ctrl_alloc_with_free(struct controlbox *b, size_t size, ctrl_freefn_t freefunc); /* - * Individual routines to create `union control' structures in a controlset. + * Individual routines to create `dlgcontrol' 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 + * pointer to the `dlgcontrol' 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 *, const char *label, - char shortcut, int percentage, intorptr helpctx, - handler_fn handler, - intorptr context, intorptr context2); -union control *ctrl_combobox(struct controlset *, const char *label, - char shortcut, int percentage, intorptr helpctx, - handler_fn handler, - intorptr context, intorptr context2); +dlgcontrol *ctrl_columns(struct controlset *, int ncolumns, ...); +dlgcontrol *ctrl_editbox(struct controlset *, const char *label, + char shortcut, int percentage, intorptr helpctx, + handler_fn handler, + intorptr context, intorptr context2); +dlgcontrol *ctrl_combobox(struct controlset *, const 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 *, const char *label, - char shortcut, int ncolumns, intorptr helpctx, - handler_fn handler, intorptr context, ...); -union control *ctrl_pushbutton(struct controlset *, const char *label, - char shortcut, intorptr helpctx, - handler_fn handler, intorptr context); -union control *ctrl_listbox(struct controlset *, const char *label, - char shortcut, intorptr helpctx, - handler_fn handler, intorptr context); -union control *ctrl_droplist(struct controlset *, const char *label, - char shortcut, int percentage, intorptr helpctx, - handler_fn handler, intorptr context); -union control *ctrl_draglist(struct controlset *, const char *label, - char shortcut, intorptr helpctx, - handler_fn handler, intorptr context); -union control *ctrl_filesel(struct controlset *, const char *label, - char shortcut, const char *filter, bool write, - const char *title, intorptr helpctx, - handler_fn handler, intorptr context); -union control *ctrl_fontsel(struct controlset *, const char *label, +dlgcontrol *ctrl_radiobuttons(struct controlset *, const char *label, + char shortcut, int ncolumns, intorptr helpctx, + handler_fn handler, intorptr context, ...); +dlgcontrol *ctrl_pushbutton(struct controlset *, const char *label, char shortcut, intorptr helpctx, handler_fn handler, intorptr context); -union control *ctrl_text(struct controlset *, const char *text, - intorptr helpctx); -union control *ctrl_checkbox(struct controlset *, const char *label, - char shortcut, intorptr helpctx, - handler_fn handler, intorptr context); -union control *ctrl_tabdelay(struct controlset *, union control *); +dlgcontrol *ctrl_listbox(struct controlset *, const char *label, + char shortcut, intorptr helpctx, + handler_fn handler, intorptr context); +dlgcontrol *ctrl_droplist(struct controlset *, const char *label, + char shortcut, int percentage, intorptr helpctx, + handler_fn handler, intorptr context); +dlgcontrol *ctrl_draglist(struct controlset *, const char *label, + char shortcut, intorptr helpctx, + handler_fn handler, intorptr context); +dlgcontrol *ctrl_filesel(struct controlset *, const char *label, + char shortcut, const char *filter, bool write, + const char *title, intorptr helpctx, + handler_fn handler, intorptr context); +dlgcontrol *ctrl_fontsel(struct controlset *, const char *label, + char shortcut, intorptr helpctx, + handler_fn handler, intorptr context); +dlgcontrol *ctrl_text(struct controlset *, const char *text, + intorptr helpctx); +dlgcontrol *ctrl_checkbox(struct controlset *, const char *label, + char shortcut, intorptr helpctx, + handler_fn handler, intorptr context); +dlgcontrol *ctrl_tabdelay(struct controlset *, dlgcontrol *); /* * Routines the platform-independent dialog code can call to read * and write the values of controls. */ -void dlg_radiobutton_set(union control *ctrl, dlgparam *dp, int whichbutton); -int dlg_radiobutton_get(union control *ctrl, dlgparam *dp); -void dlg_checkbox_set(union control *ctrl, dlgparam *dp, bool checked); -bool dlg_checkbox_get(union control *ctrl, dlgparam *dp); -void dlg_editbox_set(union control *ctrl, dlgparam *dp, char const *text); -char *dlg_editbox_get(union control *ctrl, dlgparam *dp); /* result must be freed by caller */ +void dlg_radiobutton_set(dlgcontrol *ctrl, dlgparam *dp, int whichbutton); +int dlg_radiobutton_get(dlgcontrol *ctrl, dlgparam *dp); +void dlg_checkbox_set(dlgcontrol *ctrl, dlgparam *dp, bool checked); +bool dlg_checkbox_get(dlgcontrol *ctrl, dlgparam *dp); +void dlg_editbox_set(dlgcontrol *ctrl, dlgparam *dp, char const *text); +char *dlg_editbox_get(dlgcontrol *ctrl, dlgparam *dp); /* result must be freed by caller */ /* The `listbox' functions can also apply to combo boxes. */ -void dlg_listbox_clear(union control *ctrl, dlgparam *dp); -void dlg_listbox_del(union control *ctrl, dlgparam *dp, int index); -void dlg_listbox_add(union control *ctrl, dlgparam *dp, char const *text); +void dlg_listbox_clear(dlgcontrol *ctrl, dlgparam *dp); +void dlg_listbox_del(dlgcontrol *ctrl, dlgparam *dp, int index); +void dlg_listbox_add(dlgcontrol *ctrl, dlgparam *dp, 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 @@ -564,44 +562,44 @@ void dlg_listbox_add(union control *ctrl, dlgparam *dp, char const *text); * 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, dlgparam *dp, +void dlg_listbox_addwithid(dlgcontrol *ctrl, dlgparam *dp, char const *text, int id); -int dlg_listbox_getid(union control *ctrl, dlgparam *dp, int index); +int dlg_listbox_getid(dlgcontrol *ctrl, dlgparam *dp, int index); /* dlg_listbox_index returns <0 if no single element is selected. */ -int dlg_listbox_index(union control *ctrl, dlgparam *dp); -bool dlg_listbox_issel(union control *ctrl, dlgparam *dp, int index); -void dlg_listbox_select(union control *ctrl, dlgparam *dp, int index); -void dlg_text_set(union control *ctrl, dlgparam *dp, char const *text); -void dlg_filesel_set(union control *ctrl, dlgparam *dp, Filename *fn); -Filename *dlg_filesel_get(union control *ctrl, dlgparam *dp); -void dlg_fontsel_set(union control *ctrl, dlgparam *dp, FontSpec *fn); -FontSpec *dlg_fontsel_get(union control *ctrl, dlgparam *dp); +int dlg_listbox_index(dlgcontrol *ctrl, dlgparam *dp); +bool dlg_listbox_issel(dlgcontrol *ctrl, dlgparam *dp, int index); +void dlg_listbox_select(dlgcontrol *ctrl, dlgparam *dp, int index); +void dlg_text_set(dlgcontrol *ctrl, dlgparam *dp, char const *text); +void dlg_filesel_set(dlgcontrol *ctrl, dlgparam *dp, Filename *fn); +Filename *dlg_filesel_get(dlgcontrol *ctrl, dlgparam *dp); +void dlg_fontsel_set(dlgcontrol *ctrl, dlgparam *dp, FontSpec *fn); +FontSpec *dlg_fontsel_get(dlgcontrol *ctrl, dlgparam *dp); /* * 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, dlgparam *dp); -void dlg_update_done(union control *ctrl, dlgparam *dp); +void dlg_update_start(dlgcontrol *ctrl, dlgparam *dp); +void dlg_update_done(dlgcontrol *ctrl, dlgparam *dp); /* * Set input focus into a particular control. */ -void dlg_set_focus(union control *ctrl, dlgparam *dp); +void dlg_set_focus(dlgcontrol *ctrl, dlgparam *dp); /* * Change the label text on a control. */ -void dlg_label_change(union control *ctrl, dlgparam *dp, char const *text); +void dlg_label_change(dlgcontrol *ctrl, dlgparam *dp, 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, dlgparam *dp); +dlgcontrol *dlg_last_focused(dlgcontrol *ctrl, dlgparam *dp); /* * Find out whether a particular control is currently visible. */ -bool dlg_is_visible(union control *ctrl, dlgparam *dp); +bool dlg_is_visible(dlgcontrol *ctrl, dlgparam *dp); /* * During event processing, you might well want to give an error * indication to the user. dlg_beep() is a quick and easy generic @@ -629,9 +627,9 @@ void dlg_end(dlgparam *dp, int value); * 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, dlgparam *dp, +void dlg_coloursel_start(dlgcontrol *ctrl, dlgparam *dp, int r, int g, int b); -bool dlg_coloursel_results(union control *ctrl, dlgparam *dp, +bool dlg_coloursel_results(dlgcontrol *ctrl, dlgparam *dp, int *r, int *g, int *b); /* @@ -643,7 +641,7 @@ bool dlg_coloursel_results(union control *ctrl, dlgparam *dp, * 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, dlgparam *dp); +void dlg_refresh(dlgcontrol *ctrl, dlgparam *dp); /* * Standard helper functions for reading a controlbox structure. diff --git a/putty.h b/putty.h index 5ceedbe1..fd5eea44 100644 --- a/putty.h +++ b/putty.h @@ -2570,17 +2570,16 @@ void cmdline_error(const char *, ...) PRINTF_LIKE(1, 2); * Exports from config.c. */ struct controlbox; -union control; -void conf_radiobutton_handler(union control *ctrl, dlgparam *dlg, +void conf_radiobutton_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event); #define CHECKBOX_INVERT (1<<30) -void conf_checkbox_handler(union control *ctrl, dlgparam *dlg, +void conf_checkbox_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event); -void conf_editbox_handler(union control *ctrl, dlgparam *dlg, +void conf_editbox_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event); -void conf_filesel_handler(union control *ctrl, dlgparam *dlg, +void conf_filesel_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event); -void conf_fontsel_handler(union control *ctrl, dlgparam *dlg, +void conf_fontsel_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event); void setup_config_box(struct controlbox *b, bool midsession, @@ -2593,7 +2592,7 @@ void show_ca_config_box(dlgparam *dlg); /* Visible outside config.c so that platforms can use it to recognise * the proxy type control */ -void proxy_type_handler(union control *ctrl, dlgparam *dlg, +void proxy_type_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event); /* And then they'll set this flag in its generic.context.i */ #define PROXY_UI_FLAG_LOCAL 1 /* has a local proxy */ diff --git a/test/fuzzterm.c b/test/fuzzterm.c index faea2dd7..8d20bef9 100644 --- a/test/fuzzterm.c +++ b/test/fuzzterm.c @@ -134,44 +134,44 @@ void timer_change_notify(unsigned long next) { } /* needed by config.c */ -void dlg_radiobutton_set(union control *ctrl, dlgparam *dp, int whichbutton) { } -int dlg_radiobutton_get(union control *ctrl, dlgparam *dp) { return 0; } -void dlg_checkbox_set(union control *ctrl, dlgparam *dp, bool checked) { } -bool dlg_checkbox_get(union control *ctrl, dlgparam *dp) { return false; } -void dlg_editbox_set(union control *ctrl, dlgparam *dp, char const *text) { } -char *dlg_editbox_get(union control *ctrl, dlgparam *dp) +void dlg_radiobutton_set(dlgcontrol *ctrl, dlgparam *dp, int whichbutton) { } +int dlg_radiobutton_get(dlgcontrol *ctrl, dlgparam *dp) { return 0; } +void dlg_checkbox_set(dlgcontrol *ctrl, dlgparam *dp, bool checked) { } +bool dlg_checkbox_get(dlgcontrol *ctrl, dlgparam *dp) { return false; } +void dlg_editbox_set(dlgcontrol *ctrl, dlgparam *dp, char const *text) { } +char *dlg_editbox_get(dlgcontrol *ctrl, dlgparam *dp) { return dupstr("moo"); } -void dlg_listbox_clear(union control *ctrl, dlgparam *dp) { } -void dlg_listbox_del(union control *ctrl, dlgparam *dp, int index) { } -void dlg_listbox_add(union control *ctrl, dlgparam *dp, char const *text) { } -void dlg_listbox_addwithid(union control *ctrl, dlgparam *dp, +void dlg_listbox_clear(dlgcontrol *ctrl, dlgparam *dp) { } +void dlg_listbox_del(dlgcontrol *ctrl, dlgparam *dp, int index) { } +void dlg_listbox_add(dlgcontrol *ctrl, dlgparam *dp, char const *text) { } +void dlg_listbox_addwithid(dlgcontrol *ctrl, dlgparam *dp, char const *text, int id) { } -int dlg_listbox_getid(union control *ctrl, dlgparam *dp, int index) +int dlg_listbox_getid(dlgcontrol *ctrl, dlgparam *dp, int index) { return 0; } -int dlg_listbox_index(union control *ctrl, dlgparam *dp) { return -1; } -bool dlg_listbox_issel(union control *ctrl, dlgparam *dp, int index) +int dlg_listbox_index(dlgcontrol *ctrl, dlgparam *dp) { return -1; } +bool dlg_listbox_issel(dlgcontrol *ctrl, dlgparam *dp, int index) { return false; } -void dlg_listbox_select(union control *ctrl, dlgparam *dp, int index) { } -void dlg_text_set(union control *ctrl, dlgparam *dp, char const *text) { } -void dlg_filesel_set(union control *ctrl, dlgparam *dp, Filename *fn) { } -Filename *dlg_filesel_get(union control *ctrl, dlgparam *dp) { return NULL; } -void dlg_fontsel_set(union control *ctrl, dlgparam *dp, FontSpec *fn) { } -FontSpec *dlg_fontsel_get(union control *ctrl, dlgparam *dp) { return NULL; } -void dlg_update_start(union control *ctrl, dlgparam *dp) { } -void dlg_update_done(union control *ctrl, dlgparam *dp) { } -void dlg_set_focus(union control *ctrl, dlgparam *dp) { } -void dlg_label_change(union control *ctrl, dlgparam *dp, char const *text) { } -union control *dlg_last_focused(union control *ctrl, dlgparam *dp) +void dlg_listbox_select(dlgcontrol *ctrl, dlgparam *dp, int index) { } +void dlg_text_set(dlgcontrol *ctrl, dlgparam *dp, char const *text) { } +void dlg_filesel_set(dlgcontrol *ctrl, dlgparam *dp, Filename *fn) { } +Filename *dlg_filesel_get(dlgcontrol *ctrl, dlgparam *dp) { return NULL; } +void dlg_fontsel_set(dlgcontrol *ctrl, dlgparam *dp, FontSpec *fn) { } +FontSpec *dlg_fontsel_get(dlgcontrol *ctrl, dlgparam *dp) { return NULL; } +void dlg_update_start(dlgcontrol *ctrl, dlgparam *dp) { } +void dlg_update_done(dlgcontrol *ctrl, dlgparam *dp) { } +void dlg_set_focus(dlgcontrol *ctrl, dlgparam *dp) { } +void dlg_label_change(dlgcontrol *ctrl, dlgparam *dp, char const *text) { } +dlgcontrol *dlg_last_focused(dlgcontrol *ctrl, dlgparam *dp) { return NULL; } void dlg_beep(dlgparam *dp) { } void dlg_error_msg(dlgparam *dp, const char *msg) { } void dlg_end(dlgparam *dp, int value) { } -void dlg_coloursel_start(union control *ctrl, dlgparam *dp, +void dlg_coloursel_start(dlgcontrol *ctrl, dlgparam *dp, int r, int g, int b) { } -bool dlg_coloursel_results(union control *ctrl, dlgparam *dp, +bool dlg_coloursel_results(dlgcontrol *ctrl, dlgparam *dp, int *r, int *g, int *b) { return false; } -void dlg_refresh(union control *ctrl, dlgparam *dp) { } -bool dlg_is_visible(union control *ctrl, dlgparam *dp) { return false; } +void dlg_refresh(dlgcontrol *ctrl, dlgparam *dp) { } +bool dlg_is_visible(dlgcontrol *ctrl, dlgparam *dp) { return false; } const int ngsslibs = 0; const char *const gsslibnames[0] = { }; diff --git a/unix/config-gtk.c b/unix/config-gtk.c index 52e6e181..5f3d7e0b 100644 --- a/unix/config-gtk.c +++ b/unix/config-gtk.c @@ -10,7 +10,7 @@ #include "dialog.h" #include "storage.h" -static void about_handler(union control *ctrl, dlgparam *dlg, +static void about_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { if (event == EVENT_ACTION) { @@ -21,7 +21,7 @@ static void about_handler(union control *ctrl, dlgparam *dlg, void gtk_setup_config_box(struct controlbox *b, bool midsession, void *win) { struct controlset *s, *s2; - union control *c; + dlgcontrol *c; int i; if (!midsession) { @@ -59,7 +59,7 @@ void gtk_setup_config_box(struct controlbox *b, bool midsession, void *win) 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->ncontrols-i-2)*sizeof(dlgcontrol *)); s->ctrls[i+1] = c; } break; diff --git a/unix/config-unix.c b/unix/config-unix.c index d832a18b..43d43bbd 100644 --- a/unix/config-unix.c +++ b/unix/config-unix.c @@ -13,7 +13,7 @@ void unix_setup_config_box(struct controlbox *b, bool midsession, int protocol) { struct controlset *s; - union control *c; + dlgcontrol *c; /* * The Conf structure contains two Unix-specific elements which diff --git a/unix/dialog.c b/unix/dialog.c index edd08314..08572297 100644 --- a/unix/dialog.c +++ b/unix/dialog.c @@ -51,7 +51,7 @@ struct Shortcuts { struct selparam; struct uctrl { - union control *ctrl; + dlgcontrol *ctrl; GtkWidget *toplevel; GtkWidget **buttons; int nbuttons; /* for radio buttons */ GtkWidget *entry; /* for editbox, filesel, fontsel */ @@ -88,7 +88,7 @@ struct dlgparam { int flags; struct Shortcuts *shortcuts; GtkWidget *window, *cancelbutton; - union control *currfocus, *lastfocus; + dlgcontrol *currfocus, *lastfocus; #if !GTK_CHECK_VERSION(2,0,0) GtkWidget *currtreeitem, **treeitems; int ntreeitems; @@ -166,7 +166,7 @@ static int uctrl_cmp_byctrl(void *av, void *bv) static int uctrl_cmp_byctrl_find(void *av, void *bv) { - union control *a = (union control *)av; + dlgcontrol *a = (dlgcontrol *)av; struct uctrl *b = (struct uctrl *)bv; if (a < b->ctrl) return -1; @@ -236,7 +236,7 @@ static void dlg_add_uctrl(struct dlgparam *dp, struct uctrl *uc) add234(dp->bywidget, uc); } -static struct uctrl *dlg_find_byctrl(struct dlgparam *dp, union control *ctrl) +static struct uctrl *dlg_find_byctrl(struct dlgparam *dp, dlgcontrol *ctrl) { if (!dp->byctrl) return NULL; @@ -257,7 +257,7 @@ static struct uctrl *dlg_find_bywidget(struct dlgparam *dp, GtkWidget *w) return ret; } -union control *dlg_last_focused(union control *ctrl, dlgparam *dp) +dlgcontrol *dlg_last_focused(dlgcontrol *ctrl, dlgparam *dp) { if (dp->currfocus != ctrl) return dp->currfocus; @@ -265,7 +265,7 @@ union control *dlg_last_focused(union control *ctrl, dlgparam *dp) return dp->lastfocus; } -void dlg_radiobutton_set(union control *ctrl, dlgparam *dp, int which) +void dlg_radiobutton_set(dlgcontrol *ctrl, dlgparam *dp, int which) { struct uctrl *uc = dlg_find_byctrl(dp, ctrl); assert(uc->ctrl->generic.type == CTRL_RADIO); @@ -273,7 +273,7 @@ void dlg_radiobutton_set(union control *ctrl, dlgparam *dp, int which) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(uc->buttons[which]), true); } -int dlg_radiobutton_get(union control *ctrl, dlgparam *dp) +int dlg_radiobutton_get(dlgcontrol *ctrl, dlgparam *dp) { struct uctrl *uc = dlg_find_byctrl(dp, ctrl); int i; @@ -286,21 +286,21 @@ int dlg_radiobutton_get(union control *ctrl, dlgparam *dp) return 0; /* got to return something */ } -void dlg_checkbox_set(union control *ctrl, dlgparam *dp, bool checked) +void dlg_checkbox_set(dlgcontrol *ctrl, dlgparam *dp, bool checked) { 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); } -bool dlg_checkbox_get(union control *ctrl, dlgparam *dp) +bool dlg_checkbox_get(dlgcontrol *ctrl, dlgparam *dp) { 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, dlgparam *dp, char const *text) +void dlg_editbox_set(dlgcontrol *ctrl, dlgparam *dp, char const *text) { struct uctrl *uc = dlg_find_byctrl(dp, ctrl); GtkWidget *entry; @@ -338,7 +338,7 @@ void dlg_editbox_set(union control *ctrl, dlgparam *dp, char const *text) sfree(tmpstring); } -char *dlg_editbox_get(union control *ctrl, dlgparam *dp) +char *dlg_editbox_get(dlgcontrol *ctrl, dlgparam *dp) { struct uctrl *uc = dlg_find_byctrl(dp, ctrl); assert(uc->ctrl->generic.type == CTRL_EDITBOX); @@ -367,7 +367,7 @@ static void container_remove_and_destroy(GtkWidget *w, gpointer data) #endif /* The `listbox' functions can also apply to combo boxes. */ -void dlg_listbox_clear(union control *ctrl, dlgparam *dp) +void dlg_listbox_clear(dlgcontrol *ctrl, dlgparam *dp) { struct uctrl *uc = dlg_find_byctrl(dp, ctrl); @@ -395,7 +395,7 @@ void dlg_listbox_clear(union control *ctrl, dlgparam *dp) unreachable("bad control type in listbox_clear"); } -void dlg_listbox_del(union control *ctrl, dlgparam *dp, int index) +void dlg_listbox_del(dlgcontrol *ctrl, dlgparam *dp, int index) { struct uctrl *uc = dlg_find_byctrl(dp, ctrl); @@ -429,7 +429,7 @@ void dlg_listbox_del(union control *ctrl, dlgparam *dp, int index) unreachable("bad control type in listbox_del"); } -void dlg_listbox_add(union control *ctrl, dlgparam *dp, char const *text) +void dlg_listbox_add(dlgcontrol *ctrl, dlgparam *dp, char const *text) { dlg_listbox_addwithid(ctrl, dp, text, 0); } @@ -441,7 +441,7 @@ void dlg_listbox_add(union control *ctrl, dlgparam *dp, char const *text) * 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, dlgparam *dp, +void dlg_listbox_addwithid(dlgcontrol *ctrl, dlgparam *dp, char const *text, int id) { struct uctrl *uc = dlg_find_byctrl(dp, ctrl); @@ -589,7 +589,7 @@ void dlg_listbox_addwithid(union control *ctrl, dlgparam *dp, dp->flags &= ~FLAG_UPDATING_COMBO_LIST; } -int dlg_listbox_getid(union control *ctrl, dlgparam *dp, int index) +int dlg_listbox_getid(dlgcontrol *ctrl, dlgparam *dp, int index) { struct uctrl *uc = dlg_find_byctrl(dp, ctrl); @@ -628,7 +628,7 @@ int dlg_listbox_getid(union control *ctrl, dlgparam *dp, int index) } /* dlg_listbox_index returns <0 if no single element is selected. */ -int dlg_listbox_index(union control *ctrl, dlgparam *dp) +int dlg_listbox_index(dlgcontrol *ctrl, dlgparam *dp) { struct uctrl *uc = dlg_find_byctrl(dp, ctrl); @@ -712,7 +712,7 @@ int dlg_listbox_index(union control *ctrl, dlgparam *dp) return -1; /* placate dataflow analysis */ } -bool dlg_listbox_issel(union control *ctrl, dlgparam *dp, int index) +bool dlg_listbox_issel(dlgcontrol *ctrl, dlgparam *dp, int index) { struct uctrl *uc = dlg_find_byctrl(dp, ctrl); @@ -769,7 +769,7 @@ bool dlg_listbox_issel(union control *ctrl, dlgparam *dp, int index) return false; /* placate dataflow analysis */ } -void dlg_listbox_select(union control *ctrl, dlgparam *dp, int index) +void dlg_listbox_select(dlgcontrol *ctrl, dlgparam *dp, int index) { struct uctrl *uc = dlg_find_byctrl(dp, ctrl); @@ -837,7 +837,7 @@ void dlg_listbox_select(union control *ctrl, dlgparam *dp, int index) unreachable("bad control type in listbox_select"); } -void dlg_text_set(union control *ctrl, dlgparam *dp, char const *text) +void dlg_text_set(dlgcontrol *ctrl, dlgparam *dp, char const *text) { struct uctrl *uc = dlg_find_byctrl(dp, ctrl); @@ -847,7 +847,7 @@ void dlg_text_set(union control *ctrl, dlgparam *dp, char const *text) gtk_label_set_text(GTK_LABEL(uc->text), text); } -void dlg_label_change(union control *ctrl, dlgparam *dp, char const *text) +void dlg_label_change(dlgcontrol *ctrl, dlgparam *dp, char const *text) { struct uctrl *uc = dlg_find_byctrl(dp, ctrl); @@ -885,7 +885,7 @@ void dlg_label_change(union control *ctrl, dlgparam *dp, char const *text) } } -void dlg_filesel_set(union control *ctrl, dlgparam *dp, Filename *fn) +void dlg_filesel_set(dlgcontrol *ctrl, dlgparam *dp, Filename *fn) { struct uctrl *uc = dlg_find_byctrl(dp, ctrl); /* We must copy fn->path before passing it to gtk_entry_set_text. @@ -897,7 +897,7 @@ void dlg_filesel_set(union control *ctrl, dlgparam *dp, Filename *fn) sfree(duppath); } -Filename *dlg_filesel_get(union control *ctrl, dlgparam *dp) +Filename *dlg_filesel_get(dlgcontrol *ctrl, dlgparam *dp) { struct uctrl *uc = dlg_find_byctrl(dp, ctrl); assert(uc->ctrl->generic.type == CTRL_FILESELECT); @@ -905,7 +905,7 @@ Filename *dlg_filesel_get(union control *ctrl, dlgparam *dp) return filename_from_str(gtk_entry_get_text(GTK_ENTRY(uc->entry))); } -void dlg_fontsel_set(union control *ctrl, dlgparam *dp, FontSpec *fs) +void dlg_fontsel_set(dlgcontrol *ctrl, dlgparam *dp, FontSpec *fs) { struct uctrl *uc = dlg_find_byctrl(dp, ctrl); /* We must copy fs->name before passing it to gtk_entry_set_text. @@ -917,7 +917,7 @@ void dlg_fontsel_set(union control *ctrl, dlgparam *dp, FontSpec *fs) sfree(dupname); } -FontSpec *dlg_fontsel_get(union control *ctrl, dlgparam *dp) +FontSpec *dlg_fontsel_get(dlgcontrol *ctrl, dlgparam *dp) { struct uctrl *uc = dlg_find_byctrl(dp, ctrl); assert(uc->ctrl->generic.type == CTRL_FONTSELECT); @@ -930,7 +930,7 @@ FontSpec *dlg_fontsel_get(union control *ctrl, dlgparam *dp) * 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, dlgparam *dp) +void dlg_update_start(dlgcontrol *ctrl, dlgparam *dp) { /* * Apparently we can't do this at all in GTK. GtkCList supports @@ -938,7 +938,7 @@ void dlg_update_start(union control *ctrl, dlgparam *dp) */ } -void dlg_update_done(union control *ctrl, dlgparam *dp) +void dlg_update_done(dlgcontrol *ctrl, dlgparam *dp) { /* * Apparently we can't do this at all in GTK. GtkCList supports @@ -946,7 +946,7 @@ void dlg_update_done(union control *ctrl, dlgparam *dp) */ } -void dlg_set_focus(union control *ctrl, dlgparam *dp) +void dlg_set_focus(dlgcontrol *ctrl, dlgparam *dp) { struct uctrl *uc = dlg_find_byctrl(dp, ctrl); @@ -1077,7 +1077,7 @@ void dlg_end(dlgparam *dp, int value) gtk_widget_destroy(dp->window); } -void dlg_refresh(union control *ctrl, dlgparam *dp) +void dlg_refresh(dlgcontrol *ctrl, dlgparam *dp) { struct uctrl *uc; @@ -1096,7 +1096,7 @@ void dlg_refresh(union control *ctrl, dlgparam *dp) } } -void dlg_coloursel_start(union control *ctrl, dlgparam *dp, int r, int g, int b) +void dlg_coloursel_start(dlgcontrol *ctrl, dlgparam *dp, int r, int g, int b) { struct uctrl *uc = dlg_find_byctrl(dp, ctrl); @@ -1181,7 +1181,7 @@ void dlg_coloursel_start(union control *ctrl, dlgparam *dp, int r, int g, int b) gtk_widget_show(coloursel); } -bool dlg_coloursel_results(union control *ctrl, dlgparam *dp, +bool dlg_coloursel_results(dlgcontrol *ctrl, dlgparam *dp, int *r, int *g, int *b) { if (dp->coloursel_result.ok) { @@ -1202,7 +1202,7 @@ static gboolean widget_focus(GtkWidget *widget, GdkEventFocus *event, { struct dlgparam *dp = (struct dlgparam *)data; struct uctrl *uc = dlg_find_bywidget(dp, widget); - union control *focus; + dlgcontrol *focus; if (uc && uc->ctrl) focus = uc->ctrl; @@ -1877,7 +1877,7 @@ GtkWidget *layout_ctrls( * and add them to the Columns. */ for (i = 0; i < s->ncontrols; i++) { - union control *ctrl = s->ctrls[i]; + dlgcontrol *ctrl = s->ctrls[i]; struct uctrl *uc; bool left = false; GtkWidget *w = NULL; @@ -2576,7 +2576,7 @@ static void treeitem_sel(GtkItem *item, gpointer data) } #endif -bool dlg_is_visible(union control *ctrl, dlgparam *dp) +bool dlg_is_visible(dlgcontrol *ctrl, dlgparam *dp) { struct uctrl *uc = dlg_find_byctrl(dp, ctrl); /* @@ -3283,7 +3283,7 @@ static void dlgparam_destroy(GtkWidget *widget, gpointer data) sfree(dp); } -static void messagebox_handler(union control *ctrl, dlgparam *dp, +static void messagebox_handler(dlgcontrol *ctrl, dlgparam *dp, void *data, int event) { if (event == EVENT_ACTION) @@ -3312,7 +3312,7 @@ static GtkWidget *create_message_box_general( { GtkWidget *window, *w0, *w1; struct controlset *s0, *s1; - union control *c, *textctrl; + dlgcontrol *c, *textctrl; struct dlgparam *dp; struct Shortcuts scs; int i, index, ncols, min_type; @@ -3888,7 +3888,7 @@ struct eventlog_stuff { struct controlbox *eventbox; struct Shortcuts scs; struct dlgparam dp; - union control *listctrl; + dlgcontrol *listctrl; char **events_initial; char **events_circular; int ninitial, ncircular, circular_first; @@ -3905,13 +3905,13 @@ static void eventlog_destroy(GtkWidget *widget, gpointer data) dlg_cleanup(&es->dp); ctrl_free_box(es->eventbox); } -static void eventlog_ok_handler(union control *ctrl, dlgparam *dp, +static void eventlog_ok_handler(dlgcontrol *ctrl, dlgparam *dp, void *data, int event) { if (event == EVENT_ACTION) dlg_end(dp, 0); } -static void eventlog_list_handler(union control *ctrl, dlgparam *dp, +static void eventlog_list_handler(dlgcontrol *ctrl, dlgparam *dp, void *data, int event) { eventlog_stuff *es = (eventlog_stuff *)data; @@ -4004,7 +4004,7 @@ void showeventlog(eventlog_stuff *es, void *parentwin) GtkWidget *window, *w0, *w1; GtkWidget *parent = GTK_WIDGET(parentwin); struct controlset *s0, *s1; - union control *c; + dlgcontrol *c; int index; char *title; diff --git a/windows/config.c b/windows/config.c index 17448260..4f6e6c5c 100644 --- a/windows/config.c +++ b/windows/config.c @@ -10,7 +10,7 @@ #include "dialog.h" #include "storage.h" -static void about_handler(union control *ctrl, dlgparam *dlg, +static void about_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { HWND *hwndp = (HWND *)ctrl->generic.context.p; @@ -20,7 +20,7 @@ static void about_handler(union control *ctrl, dlgparam *dlg, } } -static void help_handler(union control *ctrl, dlgparam *dlg, +static void help_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { HWND *hwndp = (HWND *)ctrl->generic.context.p; @@ -30,7 +30,7 @@ static void help_handler(union control *ctrl, dlgparam *dlg, } } -static void variable_pitch_handler(union control *ctrl, dlgparam *dlg, +static void variable_pitch_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { if (event == EVENT_REFRESH) { @@ -46,7 +46,7 @@ void win_setup_config_box(struct controlbox *b, HWND *hwndp, bool has_help, const struct BackendVtable *backvt; bool resize_forbidden = false; struct controlset *s; - union control *c; + dlgcontrol *c; char *str; if (!midsession) { @@ -91,7 +91,7 @@ void win_setup_config_box(struct controlbox *b, HWND *hwndp, bool has_help, 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->ncontrols-i-2)*sizeof(dlgcontrol *)); s->ctrls[i+1] = c; } break; @@ -296,7 +296,7 @@ void win_setup_config_box(struct controlbox *b, HWND *hwndp, bool has_help, * 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 *)); + memmove(s->ctrls+1, s->ctrls, (s->ncontrols-1)*sizeof(dlgcontrol *)); s->ctrls[0] = c; /* diff --git a/windows/controls.c b/windows/controls.c index 51d0921b..7543eb13 100644 --- a/windows/controls.c +++ b/windows/controls.c @@ -1250,7 +1250,7 @@ static int winctrl_cmp_byid(void *av, void *bv) } static int winctrl_cmp_byctrl_find(void *av, void *bv) { - union control *a = (union control *)av; + dlgcontrol *a = (dlgcontrol *)av; struct winctrl *b = (struct winctrl *)bv; if (a < b->ctrl) return -1; @@ -1310,7 +1310,7 @@ void winctrl_remove(struct winctrls *wc, struct winctrl *c) assert(ret == c); } -struct winctrl *winctrl_findbyctrl(struct winctrls *wc, union control *ctrl) +struct winctrl *winctrl_findbyctrl(struct winctrls *wc, dlgcontrol *ctrl) { return find234(wc->byctrl, ctrl, winctrl_cmp_byctrl_find); } @@ -1354,7 +1354,7 @@ void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, int ncols, colstart, colspan; struct ctlpos tabdelays[16]; - union control *tabdelayed[16]; + dlgcontrol *tabdelayed[16]; int ntabdelays; struct ctlpos pos; @@ -1402,7 +1402,7 @@ void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, /* Loop over each control in the controlset. */ for (i = 0; i < s->ncontrols; i++) { - union control *ctrl = s->ctrls[i]; + dlgcontrol *ctrl = s->ctrls[i]; /* * Generic processing that pertains to all control types. @@ -1761,7 +1761,7 @@ void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, endbox(cp); } -static void winctrl_set_focus(union control *ctrl, struct dlgparam *dp, +static void winctrl_set_focus(dlgcontrol *ctrl, struct dlgparam *dp, bool has_focus) { if (has_focus) { @@ -1774,7 +1774,7 @@ static void winctrl_set_focus(union control *ctrl, struct dlgparam *dp, } } -union control *dlg_last_focused(union control *ctrl, dlgparam *dp) +dlgcontrol *dlg_last_focused(dlgcontrol *ctrl, dlgparam *dp) { return dp->focused == ctrl ? dp->lastfocused : dp->focused; } @@ -1787,7 +1787,7 @@ bool winctrl_handle_command(struct dlgparam *dp, UINT msg, WPARAM wParam, LPARAM lParam) { struct winctrl *c; - union control *ctrl; + dlgcontrol *ctrl; int i, id; bool ret; static UINT draglistmsg = WM_NULL; @@ -2107,7 +2107,7 @@ bool winctrl_context_help(struct dlgparam *dp, HWND hwnd, int id) * mechanism can call to access the dialog box entries. */ -static struct winctrl *dlg_findbyctrl(struct dlgparam *dp, union control *ctrl) +static struct winctrl *dlg_findbyctrl(struct dlgparam *dp, dlgcontrol *ctrl) { int i; @@ -2119,7 +2119,7 @@ static struct winctrl *dlg_findbyctrl(struct dlgparam *dp, union control *ctrl) return NULL; } -bool dlg_is_visible(union control *ctrl, dlgparam *dp) +bool dlg_is_visible(dlgcontrol *ctrl, dlgparam *dp) { /* * In this implementation of the dialog box, we physically @@ -2130,7 +2130,7 @@ bool dlg_is_visible(union control *ctrl, dlgparam *dp) return dlg_findbyctrl(dp, ctrl) != NULL; } -void dlg_radiobutton_set(union control *ctrl, dlgparam *dp, int whichbutton) +void dlg_radiobutton_set(dlgcontrol *ctrl, dlgparam *dp, int whichbutton) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); assert(c && c->ctrl->generic.type == CTRL_RADIO); @@ -2140,7 +2140,7 @@ void dlg_radiobutton_set(union control *ctrl, dlgparam *dp, int whichbutton) c->base_id + 1 + whichbutton); } -int dlg_radiobutton_get(union control *ctrl, dlgparam *dp) +int dlg_radiobutton_get(dlgcontrol *ctrl, dlgparam *dp) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); int i; @@ -2151,28 +2151,28 @@ int dlg_radiobutton_get(union control *ctrl, dlgparam *dp) unreachable("no radio button was checked"); } -void dlg_checkbox_set(union control *ctrl, dlgparam *dp, bool checked) +void dlg_checkbox_set(dlgcontrol *ctrl, dlgparam *dp, bool checked) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); assert(c && c->ctrl->generic.type == CTRL_CHECKBOX); CheckDlgButton(dp->hwnd, c->base_id, checked); } -bool dlg_checkbox_get(union control *ctrl, dlgparam *dp) +bool dlg_checkbox_get(dlgcontrol *ctrl, dlgparam *dp) { 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, dlgparam *dp, char const *text) +void dlg_editbox_set(dlgcontrol *ctrl, dlgparam *dp, char const *text) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); assert(c && c->ctrl->generic.type == CTRL_EDITBOX); SetDlgItemText(dp->hwnd, c->base_id+1, text); } -char *dlg_editbox_get(union control *ctrl, dlgparam *dp) +char *dlg_editbox_get(dlgcontrol *ctrl, dlgparam *dp) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); assert(c && c->ctrl->generic.type == CTRL_EDITBOX); @@ -2180,7 +2180,7 @@ char *dlg_editbox_get(union control *ctrl, dlgparam *dp) } /* The `listbox' functions can also apply to combo boxes. */ -void dlg_listbox_clear(union control *ctrl, dlgparam *dp) +void dlg_listbox_clear(dlgcontrol *ctrl, dlgparam *dp) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); int msg; @@ -2193,7 +2193,7 @@ void dlg_listbox_clear(union control *ctrl, dlgparam *dp) SendDlgItemMessage(dp->hwnd, c->base_id+1, msg, 0, 0); } -void dlg_listbox_del(union control *ctrl, dlgparam *dp, int index) +void dlg_listbox_del(dlgcontrol *ctrl, dlgparam *dp, int index) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); int msg; @@ -2206,7 +2206,7 @@ void dlg_listbox_del(union control *ctrl, dlgparam *dp, int index) SendDlgItemMessage(dp->hwnd, c->base_id+1, msg, index, 0); } -void dlg_listbox_add(union control *ctrl, dlgparam *dp, char const *text) +void dlg_listbox_add(dlgcontrol *ctrl, dlgparam *dp, char const *text) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); int msg; @@ -2226,7 +2226,7 @@ void dlg_listbox_add(union control *ctrl, dlgparam *dp, char const *text) * 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, dlgparam *dp, +void dlg_listbox_addwithid(dlgcontrol *ctrl, dlgparam *dp, char const *text, int id) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); @@ -2243,7 +2243,7 @@ void dlg_listbox_addwithid(union control *ctrl, dlgparam *dp, SendDlgItemMessage(dp->hwnd, c->base_id+1, msg2, index, (LPARAM)id); } -int dlg_listbox_getid(union control *ctrl, dlgparam *dp, int index) +int dlg_listbox_getid(dlgcontrol *ctrl, dlgparam *dp, int index) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); int msg; @@ -2254,7 +2254,7 @@ int dlg_listbox_getid(union control *ctrl, dlgparam *dp, int index) } /* dlg_listbox_index returns <0 if no single element is selected. */ -int dlg_listbox_index(union control *ctrl, dlgparam *dp) +int dlg_listbox_index(dlgcontrol *ctrl, dlgparam *dp) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); int msg, ret; @@ -2273,7 +2273,7 @@ int dlg_listbox_index(union control *ctrl, dlgparam *dp) return ret; } -bool dlg_listbox_issel(union control *ctrl, dlgparam *dp, int index) +bool dlg_listbox_issel(dlgcontrol *ctrl, dlgparam *dp, int index) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); assert(c && c->ctrl->generic.type == CTRL_LISTBOX && @@ -2283,7 +2283,7 @@ bool dlg_listbox_issel(union control *ctrl, dlgparam *dp, int index) SendDlgItemMessage(dp->hwnd, c->base_id+1, LB_GETSEL, index, 0); } -void dlg_listbox_select(union control *ctrl, dlgparam *dp, int index) +void dlg_listbox_select(dlgcontrol *ctrl, dlgparam *dp, int index) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); int msg; @@ -2293,14 +2293,14 @@ void dlg_listbox_select(union control *ctrl, dlgparam *dp, int index) SendDlgItemMessage(dp->hwnd, c->base_id+1, msg, index, 0); } -void dlg_text_set(union control *ctrl, dlgparam *dp, char const *text) +void dlg_text_set(dlgcontrol *ctrl, dlgparam *dp, char const *text) { 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, dlgparam *dp, char const *text) +void dlg_label_change(dlgcontrol *ctrl, dlgparam *dp, char const *text) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); char *escaped = NULL; @@ -2345,14 +2345,14 @@ void dlg_label_change(union control *ctrl, dlgparam *dp, char const *text) } } -void dlg_filesel_set(union control *ctrl, dlgparam *dp, Filename *fn) +void dlg_filesel_set(dlgcontrol *ctrl, dlgparam *dp, Filename *fn) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); assert(c && c->ctrl->generic.type == CTRL_FILESELECT); SetDlgItemText(dp->hwnd, c->base_id+1, fn->path); } -Filename *dlg_filesel_get(union control *ctrl, dlgparam *dp) +Filename *dlg_filesel_get(dlgcontrol *ctrl, dlgparam *dp) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); char *tmp; @@ -2364,7 +2364,7 @@ Filename *dlg_filesel_get(union control *ctrl, dlgparam *dp) return ret; } -void dlg_fontsel_set(union control *ctrl, dlgparam *dp, FontSpec *fs) +void dlg_fontsel_set(dlgcontrol *ctrl, dlgparam *dp, FontSpec *fs) { char *buf, *boldstr; struct winctrl *c = dlg_findbyctrl(dp, ctrl); @@ -2386,7 +2386,7 @@ void dlg_fontsel_set(union control *ctrl, dlgparam *dp, FontSpec *fs) dlg_auto_set_fixed_pitch_flag(dp); } -FontSpec *dlg_fontsel_get(union control *ctrl, dlgparam *dp) +FontSpec *dlg_fontsel_get(dlgcontrol *ctrl, dlgparam *dp) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); assert(c && c->ctrl->generic.type == CTRL_FONTSELECT); @@ -2398,7 +2398,7 @@ FontSpec *dlg_fontsel_get(union control *ctrl, dlgparam *dp) * 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, dlgparam *dp) +void dlg_update_start(dlgcontrol *ctrl, dlgparam *dp) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); if (c && c->ctrl->generic.type == CTRL_LISTBOX) { @@ -2406,7 +2406,7 @@ void dlg_update_start(union control *ctrl, dlgparam *dp) } } -void dlg_update_done(union control *ctrl, dlgparam *dp) +void dlg_update_done(dlgcontrol *ctrl, dlgparam *dp) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); if (c && c->ctrl->generic.type == CTRL_LISTBOX) { @@ -2416,7 +2416,7 @@ void dlg_update_done(union control *ctrl, dlgparam *dp) } } -void dlg_set_focus(union control *ctrl, dlgparam *dp) +void dlg_set_focus(dlgcontrol *ctrl, dlgparam *dp) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); int id; @@ -2474,7 +2474,7 @@ void dlg_end(dlgparam *dp, int value) dp->endresult = value; } -void dlg_refresh(union control *ctrl, dlgparam *dp) +void dlg_refresh(dlgcontrol *ctrl, dlgparam *dp) { int i, j; struct winctrl *c; @@ -2501,7 +2501,7 @@ void dlg_refresh(union control *ctrl, dlgparam *dp) } } -void dlg_coloursel_start(union control *ctrl, dlgparam *dp, int r, int g, int b) +void dlg_coloursel_start(dlgcontrol *ctrl, dlgparam *dp, int r, int g, int b) { dp->coloursel_wanted = true; dp->coloursel_result.r = r; @@ -2509,7 +2509,7 @@ void dlg_coloursel_start(union control *ctrl, dlgparam *dp, int r, int g, int b) dp->coloursel_result.b = b; } -bool dlg_coloursel_results(union control *ctrl, dlgparam *dp, +bool dlg_coloursel_results(dlgcontrol *ctrl, dlgparam *dp, int *r, int *g, int *b) { if (dp->coloursel_result.ok) { diff --git a/windows/platform.h b/windows/platform.h index 25de278f..11103511 100644 --- a/windows/platform.h +++ b/windows/platform.h @@ -413,7 +413,7 @@ struct dlgparam { 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 */ + dlgcontrol *focused, *lastfocused; /* which ctrl has focus now/before */ bool shortcuts[128]; /* track which shortcuts in use */ bool coloursel_wanted; /* has an event handler asked for * a colour selector? */ @@ -490,11 +490,11 @@ void dlg_set_fixed_pitch_flag(dlgparam *dlg, bool flag); #define MAX_SHORTCUTS_PER_CTRL 16 /* - * This structure is what's stored for each `union control' in the + * This structure is what's stored for each `dlgcontrol' in the * portable-dialog interface. */ struct winctrl { - union control *ctrl; + dlgcontrol *ctrl; /* * The control may have several components at the Windows * level, with different dialog IDs. To avoid needing N @@ -525,7 +525,7 @@ struct winctrl { }; /* * 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 + * tree234s so that it can find an item by `dlgcontrol' or by * dialog ID. */ struct winctrls { @@ -538,7 +538,7 @@ 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_findbyctrl(struct winctrls *, dlgcontrol *); struct winctrl *winctrl_findbyid(struct winctrls *, int); struct winctrl *winctrl_findbyindex(struct winctrls *, int); void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, -- cgit v1.2.3 From 89883bf158a4615b13047869755a71abbf91e204 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 1 May 2022 09:55:52 +0100 Subject: Restructure dlgcontrol as a struct with an anon union. This gets rid of that awkward STANDARD_PREFIX system in which every branch of the old 'union control' had to repeat all the generic fields, and then call sites had to make an arbitrary decision about which branch to access them through. That was the best we could do before accepting C99 features in this code base. But now we have anonymous unions, so we don't need to put up with that nonsense any more! 'dlgcontrol' is now a struct rather than a union, and the generic fields common to all control types are ordinary members of the struct, so you don't have to refer to them as ctrl->generic.foo at all, just ctrl->foo, which saves verbiage at the point of use. The extra per-control fields are still held in structures named after the control type, so you'll still say ctrl->listbox.height or whatever. But now those structures are themselves members of an anonymous union field following the generic fields, so those sub-structures don't have to reiterate all the standard stuff too. While I'm here, I've promoted 'context2' from an editbox-specific field to a generic one (it just seems silly _not_ to allow any control to have two context fields if it needs it). Also, I had to rename the boolean field 'tabdelay' to avoid it clashing with the subsidiary structure field 'tabdelay', now that the former isn't generic.tabdelay any more. --- config.c | 180 ++++++++-------- defs.h | 2 +- dialog.c | 46 ++--- dialog.h | 591 +++++++++++++++++++++++++---------------------------- unix/config-gtk.c | 8 +- unix/config-unix.c | 8 +- unix/dialog.c | 156 +++++++------- windows/config.c | 30 +-- windows/controls.c | 142 ++++++------- 9 files changed, 569 insertions(+), 594 deletions(-) diff --git a/config.c b/config.c index c4c932a9..062fe9b6 100644 --- a/config.c +++ b/config.c @@ -29,7 +29,7 @@ void conf_radiobutton_handler(dlgcontrol *ctrl, dlgparam *dlg, * is the one selected. */ if (event == EVENT_REFRESH) { - int val = conf_get_int(conf, ctrl->radio.context.i); + int val = conf_get_int(conf, ctrl->context.i); for (button = 0; button < ctrl->radio.nbuttons; button++) if (val == ctrl->radio.buttondata[button].i) break; @@ -39,7 +39,7 @@ void conf_radiobutton_handler(dlgcontrol *ctrl, dlgparam *dlg, } else if (event == EVENT_VALCHANGE) { button = dlg_radiobutton_get(ctrl, dlg); assert(button >= 0 && button < ctrl->radio.nbuttons); - conf_set_int(conf, ctrl->radio.context.i, + conf_set_int(conf, ctrl->context.i, ctrl->radio.buttondata[button].i); } } @@ -56,7 +56,7 @@ void conf_radiobutton_bool_handler(dlgcontrol *ctrl, dlgparam *dlg, * config option. */ if (event == EVENT_REFRESH) { - int val = conf_get_bool(conf, ctrl->radio.context.i); + int val = conf_get_bool(conf, ctrl->context.i); for (button = 0; button < ctrl->radio.nbuttons; button++) if (val == ctrl->radio.buttondata[button].i) break; @@ -66,7 +66,7 @@ void conf_radiobutton_bool_handler(dlgcontrol *ctrl, dlgparam *dlg, } else if (event == EVENT_VALCHANGE) { button = dlg_radiobutton_get(ctrl, dlg); assert(button >= 0 && button < ctrl->radio.nbuttons); - conf_set_bool(conf, ctrl->radio.context.i, + conf_set_bool(conf, ctrl->context.i, ctrl->radio.buttondata[button].i); } } @@ -83,7 +83,7 @@ void conf_checkbox_handler(dlgcontrol *ctrl, dlgparam *dlg, * For a standard checkbox, the context parameter gives the * primary key (CONF_foo), optionally ORed with CHECKBOX_INVERT. */ - key = ctrl->checkbox.context.i; + key = ctrl->context.i; if (key & CHECKBOX_INVERT) { key &= ~CHECKBOX_INVERT; invert = true; @@ -120,8 +120,8 @@ void conf_editbox_handler(dlgcontrol *ctrl, dlgparam *dlg, * context2 == -1000, then typing 1.2 into the box will set * the field to 1200.) */ - int key = ctrl->editbox.context.i; - int length = ctrl->editbox.context2.i; + int key = ctrl->context.i; + int length = ctrl->context2.i; Conf *conf = (Conf *)data; if (length > 0) { @@ -156,7 +156,7 @@ void conf_editbox_handler(dlgcontrol *ctrl, dlgparam *dlg, void conf_filesel_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { - int key = ctrl->fileselect.context.i; + int key = ctrl->context.i; Conf *conf = (Conf *)data; if (event == EVENT_REFRESH) { @@ -172,7 +172,7 @@ void conf_filesel_handler(dlgcontrol *ctrl, dlgparam *dlg, void conf_fontsel_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { - int key = ctrl->fontselect.context.i; + int key = ctrl->context.i; Conf *conf = (Conf *)data; if (event == EVENT_REFRESH) { @@ -274,7 +274,7 @@ static void config_protocols_handler(dlgcontrol *ctrl, dlgparam *dlg, { Conf *conf = (Conf *)data; int curproto = conf_get_int(conf, CONF_protocol); - struct hostport *hp = (struct hostport *)ctrl->generic.context.p; + struct hostport *hp = (struct hostport *)ctrl->context.p; if (event == EVENT_REFRESH) { /* @@ -716,7 +716,7 @@ static void sshbug_handler(dlgcontrol *ctrl, dlgparam *dlg, * spurious SELCHANGE we trigger in the process will overwrite * the value we wanted to keep. */ - int oldconf = conf_get_int(conf, ctrl->listbox.context.i); + int oldconf = conf_get_int(conf, ctrl->context.i); dlg_update_start(ctrl, dlg); dlg_listbox_clear(ctrl, dlg); dlg_listbox_addwithid(ctrl, dlg, "Auto", AUTO); @@ -734,7 +734,7 @@ static void sshbug_handler(dlgcontrol *ctrl, dlgparam *dlg, i = AUTO; else i = dlg_listbox_getid(ctrl, dlg, i); - conf_set_int(conf, ctrl->listbox.context.i, i); + conf_set_int(conf, ctrl->context.i, i); } } @@ -749,7 +749,7 @@ static void sshbug_handler_manual_only(dlgcontrol *ctrl, dlgparam *dlg, */ Conf *conf = (Conf *)data; if (event == EVENT_REFRESH) { - int oldconf = conf_get_int(conf, ctrl->listbox.context.i); + int oldconf = conf_get_int(conf, ctrl->context.i); dlg_update_start(ctrl, dlg); dlg_listbox_clear(ctrl, dlg); dlg_listbox_addwithid(ctrl, dlg, "Off", FORCE_OFF); @@ -765,7 +765,7 @@ static void sshbug_handler_manual_only(dlgcontrol *ctrl, dlgparam *dlg, i = FORCE_OFF; else i = dlg_listbox_getid(ctrl, dlg, i); - conf_set_int(conf, ctrl->listbox.context.i, i); + conf_set_int(conf, ctrl->context.i, i); } } @@ -818,7 +818,7 @@ static void sessionsaver_handler(dlgcontrol *ctrl, dlgparam *dlg, { Conf *conf = (Conf *)data; struct sessionsaver_data *ssd = - (struct sessionsaver_data *)ctrl->generic.context.p; + (struct sessionsaver_data *)ctrl->context.p; if (event == EVENT_REFRESH) { if (ctrl == ssd->editbox) { @@ -959,7 +959,7 @@ static void charclass_handler(dlgcontrol *ctrl, dlgparam *dlg, { Conf *conf = (Conf *)data; struct charclass_data *ccd = - (struct charclass_data *)ctrl->generic.context.p; + (struct charclass_data *)ctrl->context.p; if (event == EVENT_REFRESH) { if (ctrl == ccd->listbox) { @@ -1008,7 +1008,7 @@ static void colour_handler(dlgcontrol *ctrl, dlgparam *dlg, { Conf *conf = (Conf *)data; struct colour_data *cd = - (struct colour_data *)ctrl->generic.context.p; + (struct colour_data *)ctrl->context.p; bool update = false, clear = false; int r, g, b; @@ -1117,7 +1117,7 @@ static void ttymodes_handler(dlgcontrol *ctrl, dlgparam *dlg, { Conf *conf = (Conf *)data; struct ttymodes_data *td = - (struct ttymodes_data *)ctrl->generic.context.p; + (struct ttymodes_data *)ctrl->context.p; if (event == EVENT_REFRESH) { if (ctrl == td->listbox) { @@ -1202,7 +1202,7 @@ static void environ_handler(dlgcontrol *ctrl, dlgparam *dlg, { Conf *conf = (Conf *)data; struct environ_data *ed = - (struct environ_data *)ctrl->generic.context.p; + (struct environ_data *)ctrl->context.p; if (event == EVENT_REFRESH) { if (ctrl == ed->listbox) { @@ -1278,7 +1278,7 @@ static void portfwd_handler(dlgcontrol *ctrl, dlgparam *dlg, { Conf *conf = (Conf *)data; struct portfwd_data *pfd = - (struct portfwd_data *)ctrl->generic.context.p; + (struct portfwd_data *)ctrl->context.p; if (event == EVENT_REFRESH) { if (ctrl == pfd->listbox) { @@ -1442,7 +1442,7 @@ static void manual_hostkey_handler(dlgcontrol *ctrl, dlgparam *dlg, { Conf *conf = (Conf *)data; struct manual_hostkey_data *mh = - (struct manual_hostkey_data *)ctrl->generic.context.p; + (struct manual_hostkey_data *)ctrl->context.p; if (event == EVENT_REFRESH) { if (ctrl == mh->listbox) { @@ -1504,9 +1504,9 @@ static void clipboard_selector_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { Conf *conf = (Conf *)data; - int setting = ctrl->generic.context.i; + int setting = ctrl->context.i; #ifdef NAMED_CLIPBOARDS - int strsetting = ctrl->editbox.context2.i; + int strsetting = ctrl->context2.i; #endif static const struct { @@ -1614,7 +1614,7 @@ static void serial_parity_handler(dlgcontrol *ctrl, dlgparam *dlg, {"Mark", SER_PAR_MARK}, {"Space", SER_PAR_SPACE}, }; - int mask = ctrl->listbox.context.i; + int mask = ctrl->context.i; int i, j; Conf *conf = (Conf *)data; @@ -1668,7 +1668,7 @@ static void serial_flow_handler(dlgcontrol *ctrl, dlgparam *dlg, {"RTS/CTS", SER_FLOW_RTSCTS}, {"DSR/DTR", SER_FLOW_DSRDTR}, }; - int mask = ctrl->listbox.context.i; + int mask = ctrl->context.i; int i, j; Conf *conf = (Conf *)data; @@ -1743,7 +1743,7 @@ void proxy_type_handler(dlgcontrol *ctrl, dlgparam *dlg, ADD(PROXY_SSH_EXEC, "SSH to proxy and execute a command"); ADD(PROXY_SSH_SUBSYSTEM, "SSH to proxy and invoke a subsystem"); } - if (ctrl->generic.context.i & PROXY_UI_FLAG_LOCAL) { + if (ctrl->context.i & PROXY_UI_FLAG_LOCAL) { ADD(PROXY_CMD, "Local (run a subprogram to connect)"); } ADD(PROXY_TELNET, "'Telnet' (send an ad-hoc command)"); @@ -1805,11 +1805,11 @@ void setup_config_box(struct controlbox *b, bool midsession, HELPCTX(no_help), sessionsaver_handler, P(ssd)); ssd->okbutton->button.isdefault = true; - ssd->okbutton->generic.column = 3; + ssd->okbutton->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; + ssd->cancelbutton->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. */ @@ -1831,12 +1831,12 @@ void setup_config_box(struct controlbox *b, bool midsession, c = ctrl_editbox(s, HOST_BOX_TITLE, 'n', 100, HELPCTX(session_hostname), config_host_handler, I(0), I(0)); - c->generic.column = 0; + c->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; + c->column = 1; hp->port = c; ctrl_columns(s, 1, 100); @@ -1845,7 +1845,7 @@ void setup_config_box(struct controlbox *b, bool midsession, c = ctrl_radiobuttons(s, NULL, NO_SHORTCUT, 3, HELPCTX(session_hostname), config_protocols_handler, P(hp), NULL); - c->generic.column = 0; + c->column = 0; hp->protradio = c; c->radio.buttons = sresize(c->radio.buttons, PROTOCOL_LIMIT, char *); c->radio.shortcuts = sresize(c->radio.shortcuts, PROTOCOL_LIMIT, char); @@ -1880,10 +1880,10 @@ void setup_config_box(struct controlbox *b, bool midsession, config_protocols_handler, P(hp)); hp->protlist = c; /* droplist is populated in config_protocols_handler */ - c->generic.column = 1; + c->column = 1; /* Vertically centre the two protocol controls w.r.t. each other */ - hp->protlist->generic.align_next_to = hp->protradio; + hp->protlist->align_next_to = hp->protradio; ctrl_columns(s, 1, 100); } @@ -1899,7 +1899,7 @@ void setup_config_box(struct controlbox *b, bool midsession, ssd->editbox = ctrl_editbox(s, "Saved Sessions", 'e', 100, HELPCTX(session_saved), sessionsaver_handler, P(ssd), P(NULL)); - ssd->editbox->generic.column = 0; + ssd->editbox->column = 0; /* Reset columns so that the buttons are alongside the list, rather * than alongside that edit box. */ ctrl_columns(s, 1, 100); @@ -1907,13 +1907,13 @@ void setup_config_box(struct controlbox *b, bool midsession, ssd->listbox = ctrl_listbox(s, NULL, NO_SHORTCUT, HELPCTX(session_saved), sessionsaver_handler, P(ssd)); - ssd->listbox->generic.column = 0; + ssd->listbox->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; + ssd->loadbutton->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 @@ -1925,12 +1925,12 @@ void setup_config_box(struct controlbox *b, bool midsession, ssd->savebutton = ctrl_pushbutton(s, "Save", 'v', HELPCTX(session_saved), sessionsaver_handler, P(ssd)); - ssd->savebutton->generic.column = 1; + ssd->savebutton->column = 1; if (!midsession) { ssd->delbutton = ctrl_pushbutton(s, "Delete", 'd', HELPCTX(session_saved), sessionsaver_handler, P(ssd)); - ssd->delbutton->generic.column = 1; + ssd->delbutton->column = 1; } else { /* Disable the Delete button mid-session too, for UI consistency. */ ssd->delbutton = NULL; @@ -2207,11 +2207,11 @@ void setup_config_box(struct controlbox *b, bool midsession, c = ctrl_editbox(s, "Columns", 'm', 100, HELPCTX(window_size), conf_editbox_handler, I(CONF_width), I(-1)); - c->generic.column = 0; + c->column = 0; c = ctrl_editbox(s, "Rows", 'r', 100, HELPCTX(window_size), conf_editbox_handler, I(CONF_height),I(-1)); - c->generic.column = 1; + c->column = 1; ctrl_columns(s, 1, 100); } @@ -2394,11 +2394,11 @@ void setup_config_box(struct controlbox *b, bool midsession, ccd->editbox = ctrl_editbox(s, "Set to class", 't', 50, HELPCTX(copy_charclasses), charclass_handler, P(ccd), P(NULL)); - ccd->editbox->generic.column = 0; + ccd->editbox->column = 0; ccd->button = ctrl_pushbutton(s, "Set", 's', HELPCTX(copy_charclasses), charclass_handler, P(ccd)); - ccd->button->generic.column = 1; + ccd->button->column = 1; ctrl_columns(s, 1, 100); /* @@ -2435,22 +2435,22 @@ void setup_config_box(struct controlbox *b, bool midsession, 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->column = 0; cd->listbox->listbox.height = 7; c = ctrl_text(s, "RGB value:", HELPCTX(colours_config)); - c->generic.column = 1; + c->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->redit->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->gedit->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->bedit->column = 1; cd->button = ctrl_pushbutton(s, "Modify", 'm', HELPCTX(colours_config), colour_handler, P(cd)); - cd->button->generic.column = 1; + cd->button->column = 1; ctrl_columns(s, 1, 100); /* @@ -2550,19 +2550,19 @@ void setup_config_box(struct controlbox *b, bool midsession, ed->varbox = ctrl_editbox(s, "Variable", 'v', 60, HELPCTX(telnet_environ), environ_handler, P(ed), P(NULL)); - ed->varbox->generic.column = 0; + ed->varbox->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->valbox->column = 0; ed->addbutton = ctrl_pushbutton(s, "Add", 'd', HELPCTX(telnet_environ), environ_handler, P(ed)); - ed->addbutton->generic.column = 1; + ed->addbutton->column = 1; ed->rembutton = ctrl_pushbutton(s, "Remove", 'r', HELPCTX(telnet_environ), environ_handler, P(ed)); - ed->rembutton->generic.column = 1; + ed->rembutton->column = 1; ctrl_columns(s, 1, 100); ed->listbox = ctrl_listbox(s, NULL, NO_SHORTCUT, HELPCTX(telnet_environ), @@ -2591,13 +2591,13 @@ void setup_config_box(struct controlbox *b, bool midsession, HELPCTX(proxy_main), conf_editbox_handler, I(CONF_proxy_host), I(1)); - c->generic.column = 0; + c->column = 0; c = ctrl_editbox(s, "Port", 'p', 100, HELPCTX(proxy_main), conf_editbox_handler, I(CONF_proxy_port), I(-1)); - c->generic.column = 1; + c->column = 1; ctrl_columns(s, 1, 100); ctrl_editbox(s, "Exclude Hosts/IPs", 'e', 100, HELPCTX(proxy_exclude), @@ -2803,7 +2803,7 @@ void setup_config_box(struct controlbox *b, bool midsession, ctrl_columns(s, 2, 75, 25); c = ctrl_text(s, "Host keys or fingerprints to accept:", HELPCTX(ssh_kex_manual_hostkeys)); - c->generic.column = 0; + c->column = 0; /* You want to select from the list, _then_ hit Remove. So * tab order should be that way round. */ mh = (struct manual_hostkey_data *) @@ -2811,8 +2811,8 @@ void setup_config_box(struct controlbox *b, bool midsession, mh->rembutton = ctrl_pushbutton(s, "Remove", 'r', HELPCTX(ssh_kex_manual_hostkeys), manual_hostkey_handler, P(mh)); - mh->rembutton->generic.column = 1; - mh->rembutton->generic.tabdelay = true; + mh->rembutton->column = 1; + mh->rembutton->delay_taborder = true; mh->listbox = ctrl_listbox(s, NULL, NO_SHORTCUT, HELPCTX(ssh_kex_manual_hostkeys), manual_hostkey_handler, P(mh)); @@ -2826,11 +2826,11 @@ void setup_config_box(struct controlbox *b, bool midsession, mh->keybox = ctrl_editbox(s, "Key", 'k', 80, HELPCTX(ssh_kex_manual_hostkeys), manual_hostkey_handler, P(mh), P(NULL)); - mh->keybox->generic.column = 0; + mh->keybox->column = 0; mh->addbutton = ctrl_pushbutton(s, "Add key", 'y', HELPCTX(ssh_kex_manual_hostkeys), manual_hostkey_handler, P(mh)); - mh->addbutton->generic.column = 1; + mh->addbutton->column = 1; ctrl_columns(s, 1, 100); } @@ -3007,12 +3007,12 @@ void setup_config_box(struct controlbox *b, bool midsession, td->listbox->listbox.percentages[1] = 60; ctrl_columns(s, 2, 75, 25); c = ctrl_text(s, "For selected mode, send:", HELPCTX(ssh_ttymodes)); - c->generic.column = 0; + c->column = 0; td->setbutton = ctrl_pushbutton(s, "Set", 's', HELPCTX(ssh_ttymodes), ttymodes_handler, P(td)); - td->setbutton->generic.column = 1; - td->setbutton->generic.tabdelay = true; + td->setbutton->column = 1; + td->setbutton->delay_taborder = true; ctrl_columns(s, 1, 100); /* column break */ /* Bit of a hack to get the value radio buttons and * edit-box on the same row. */ @@ -3024,12 +3024,12 @@ void setup_config_box(struct controlbox *b, bool midsession, "Nothing", NO_SHORTCUT, P(NULL), "This:", NO_SHORTCUT, P(NULL), NULL); - td->valradio->generic.column = 0; + td->valradio->column = 0; td->valbox = ctrl_editbox(s, NULL, NO_SHORTCUT, 100, HELPCTX(ssh_ttymodes), ttymodes_handler, P(td), P(NULL)); - td->valbox->generic.column = 1; - td->valbox->generic.align_next_to = td->valradio; + td->valbox->column = 1; + td->valbox->align_next_to = td->valradio; ctrl_tabdelay(s, td->setbutton); } @@ -3074,15 +3074,15 @@ void setup_config_box(struct controlbox *b, bool midsession, ctrl_columns(s, 3, 55, 20, 25); c = ctrl_text(s, "Forwarded ports:", HELPCTX(ssh_tunnels_portfwd)); - c->generic.column = COLUMN_FIELD(0,2); + c->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 = true; + pfd->rembutton->column = 2; + pfd->rembutton->delay_taborder = true; pfd->listbox = ctrl_listbox(s, NULL, NO_SHORTCUT, HELPCTX(ssh_tunnels_portfwd), portfwd_handler, P(pfd)); @@ -3098,12 +3098,12 @@ void setup_config_box(struct controlbox *b, bool midsession, pfd->addbutton = ctrl_pushbutton(s, "Add", 'd', HELPCTX(ssh_tunnels_portfwd), portfwd_handler, P(pfd)); - pfd->addbutton->generic.column = 2; - pfd->addbutton->generic.tabdelay = true; + pfd->addbutton->column = 2; + pfd->addbutton->delay_taborder = true; 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->sourcebox->column = 0; pfd->destbox = ctrl_editbox(s, "Destination", 'i', 67, HELPCTX(ssh_tunnels_portfwd), portfwd_handler, P(pfd), P(NULL)); @@ -3426,7 +3426,7 @@ static void ca_ok_handler(dlgcontrol *ctrl, dlgparam *dp, static void ca_name_handler(dlgcontrol *ctrl, dlgparam *dp, void *data, int event) { - struct ca_state *st = (struct ca_state *)ctrl->generic.context.p; + struct ca_state *st = (struct ca_state *)ctrl->context.p; if (event == EVENT_REFRESH) { dlg_editbox_set(ctrl, dp, st->name); } else if (event == EVENT_VALCHANGE) { @@ -3447,7 +3447,7 @@ static void ca_name_handler(dlgcontrol *ctrl, dlgparam *dp, static void ca_reclist_handler(dlgcontrol *ctrl, dlgparam *dp, void *data, int event) { - struct ca_state *st = (struct ca_state *)ctrl->generic.context.p; + struct ca_state *st = (struct ca_state *)ctrl->context.p; if (event == EVENT_REFRESH) { dlg_update_start(ctrl, dp); dlg_listbox_clear(ctrl, dp); @@ -3464,7 +3464,7 @@ static void ca_reclist_handler(dlgcontrol *ctrl, dlgparam *dp, static void ca_load_handler(dlgcontrol *ctrl, dlgparam *dp, void *data, int event) { - struct ca_state *st = (struct ca_state *)ctrl->generic.context.p; + struct ca_state *st = (struct ca_state *)ctrl->context.p; if (event == EVENT_ACTION) { ca_load_selected_record(st, dp); } @@ -3473,7 +3473,7 @@ static void ca_load_handler(dlgcontrol *ctrl, dlgparam *dp, static void ca_save_handler(dlgcontrol *ctrl, dlgparam *dp, void *data, int event) { - struct ca_state *st = (struct ca_state *)ctrl->generic.context.p; + struct ca_state *st = (struct ca_state *)ctrl->context.p; if (event == EVENT_ACTION) { host_ca *hca = snew(host_ca); memset(hca, 0, sizeof(*hca)); @@ -3499,7 +3499,7 @@ static void ca_save_handler(dlgcontrol *ctrl, dlgparam *dp, static void ca_delete_handler(dlgcontrol *ctrl, dlgparam *dp, void *data, int event) { - struct ca_state *st = (struct ca_state *)ctrl->generic.context.p; + struct ca_state *st = (struct ca_state *)ctrl->context.p; if (event == EVENT_ACTION) { int i = dlg_listbox_index(st->ca_reclist, dp); if (i < 0) { @@ -3526,7 +3526,7 @@ static void ca_delete_handler(dlgcontrol *ctrl, dlgparam *dp, static void ca_pubkey_handler(dlgcontrol *ctrl, dlgparam *dp, void *data, int event) { - struct ca_state *st = (struct ca_state *)ctrl->generic.context.p; + struct ca_state *st = (struct ca_state *)ctrl->context.p; if (event == EVENT_REFRESH) { dlg_editbox_set(ctrl, dp, st->pubkey); } else if (event == EVENT_VALCHANGE) { @@ -3538,7 +3538,7 @@ static void ca_pubkey_handler(dlgcontrol *ctrl, dlgparam *dp, static void ca_wclist_handler(dlgcontrol *ctrl, dlgparam *dp, void *data, int event) { - struct ca_state *st = (struct ca_state *)ctrl->generic.context.p; + struct ca_state *st = (struct ca_state *)ctrl->context.p; if (event == EVENT_REFRESH) { dlg_update_start(ctrl, dp); dlg_listbox_clear(ctrl, dp); @@ -3552,7 +3552,7 @@ static void ca_wclist_handler(dlgcontrol *ctrl, dlgparam *dp, static void ca_wc_edit_handler(dlgcontrol *ctrl, dlgparam *dp, void *data, int event) { - struct ca_state *st = (struct ca_state *)ctrl->generic.context.p; + struct ca_state *st = (struct ca_state *)ctrl->context.p; if (event == EVENT_REFRESH) { dlg_editbox_set(ctrl, dp, st->wc); } else if (event == EVENT_VALCHANGE) { @@ -3564,7 +3564,7 @@ static void ca_wc_edit_handler(dlgcontrol *ctrl, dlgparam *dp, static void ca_wc_add_handler(dlgcontrol *ctrl, dlgparam *dp, void *data, int event) { - struct ca_state *st = (struct ca_state *)ctrl->generic.context.p; + struct ca_state *st = (struct ca_state *)ctrl->context.p; if (event == EVENT_ACTION) { if (!st->wc) { dlg_beep(dp); @@ -3585,7 +3585,7 @@ static void ca_wc_add_handler(dlgcontrol *ctrl, dlgparam *dp, static void ca_wc_rem_handler(dlgcontrol *ctrl, dlgparam *dp, void *data, int event) { - struct ca_state *st = (struct ca_state *)ctrl->generic.context.p; + struct ca_state *st = (struct ca_state *)ctrl->context.p; if (event == EVENT_ACTION) { int i = dlg_listbox_index(st->ca_wclist, dp); if (i < 0) { @@ -3626,7 +3626,7 @@ void setup_ca_config_box(struct controlbox *b) c = ctrl_pushbutton(s, "Done", 'o', HELPCTX(no_help), ca_ok_handler, P(st)); c->button.isdefault = true; - c->generic.column = 4; + c->column = 4; /* Load/save box, as similar as possible to the main saved sessions one */ s = ctrl_getset(b, "Main", "loadsave", @@ -3635,7 +3635,7 @@ void setup_ca_config_box(struct controlbox *b) c = ctrl_editbox(s, "Name for this CA (shown in log messages)", 'n', 100, HELPCTX(no_help), ca_name_handler, P(st), P(NULL)); - c->generic.column = 0; + c->column = 0; st->ca_name_edit = c; /* Reset columns so that the buttons are alongside the list, rather * than alongside that edit box. */ @@ -3643,18 +3643,18 @@ void setup_ca_config_box(struct controlbox *b) ctrl_columns(s, 2, 75, 25); c = ctrl_listbox(s, NULL, NO_SHORTCUT, HELPCTX(no_help), ca_reclist_handler, P(st)); - c->generic.column = 0; + c->column = 0; c->listbox.height = 6; st->ca_reclist = c; c = ctrl_pushbutton(s, "Load", 'l', HELPCTX(no_help), ca_load_handler, P(st)); - c->generic.column = 1; + c->column = 1; c = ctrl_pushbutton(s, "Save", 'v', HELPCTX(no_help), ca_save_handler, P(st)); - c->generic.column = 1; + c->column = 1; c = ctrl_pushbutton(s, "Delete", 'd', HELPCTX(no_help), ca_delete_handler, P(st)); - c->generic.column = 1; + c->column = 1; /* Box containing the details of a specific CA record */ s = ctrl_getset(b, "Main", "details", "Details of a host CA record"); @@ -3668,12 +3668,12 @@ void setup_ca_config_box(struct controlbox *b) ctrl_columns(s, 3, 70, 15, 15); c = ctrl_editbox(s, "Hostname pattern to add", 'h', 100, HELPCTX(no_help), ca_wc_edit_handler, P(st), P(NULL)); - c->generic.column = 0; + c->column = 0; st->ca_wc_edit = c; c = ctrl_pushbutton(s, "Add", NO_SHORTCUT, HELPCTX(no_help), ca_wc_add_handler, P(st)); - c->generic.column = 1; + c->column = 1; c = ctrl_pushbutton(s, "Remove", NO_SHORTCUT, HELPCTX(no_help), ca_wc_rem_handler, P(st)); - c->generic.column = 2; + c->column = 2; } diff --git a/defs.h b/defs.h index 063c05cd..cd7d0f4d 100644 --- a/defs.h +++ b/defs.h @@ -172,7 +172,7 @@ typedef struct NTRUKeyPair NTRUKeyPair; typedef struct NTRUEncodeSchedule NTRUEncodeSchedule; typedef struct dlgparam dlgparam; -typedef union control dlgcontrol; +typedef struct dlgcontrol dlgcontrol; typedef struct settings_w settings_w; typedef struct settings_r settings_r; diff --git a/dialog.c b/dialog.c index 79ef7a10..13317833 100644 --- a/dialog.c +++ b/dialog.c @@ -214,14 +214,14 @@ static dlgcontrol *ctrl_new(struct controlset *s, int type, /* * Fill in the standard fields. */ - c->generic.type = type; - c->generic.tabdelay = false; - c->generic.column = COLUMN_FIELD(0, s->ncolumns); - c->generic.helpctx = helpctx; - c->generic.handler = handler; - c->generic.context = context; - c->generic.label = NULL; - c->generic.align_next_to = NULL; + c->type = type; + c->delay_taborder = false; + c->column = COLUMN_FIELD(0, s->ncolumns); + c->helpctx = helpctx; + c->handler = handler; + c->context = context; + c->label = NULL; + c->align_next_to = NULL; return c; } @@ -252,12 +252,12 @@ dlgcontrol *ctrl_editbox(struct controlset *s, const char *label, intorptr context, intorptr context2) { dlgcontrol *c = ctrl_new(s, CTRL_EDITBOX, helpctx, handler, context); - c->editbox.label = label ? dupstr(label) : NULL; + c->label = label ? dupstr(label) : NULL; c->editbox.shortcut = shortcut; c->editbox.percentwidth = percentage; c->editbox.password = false; c->editbox.has_list = false; - c->editbox.context2 = context2; + c->context2 = context2; return c; } @@ -267,12 +267,12 @@ dlgcontrol *ctrl_combobox(struct controlset *s, const char *label, intorptr context, intorptr context2) { dlgcontrol *c = ctrl_new(s, CTRL_EDITBOX, helpctx, handler, context); - c->editbox.label = label ? dupstr(label) : NULL; + c->label = label ? dupstr(label) : NULL; c->editbox.shortcut = shortcut; c->editbox.percentwidth = percentage; c->editbox.password = false; c->editbox.has_list = true; - c->editbox.context2 = context2; + c->context2 = context2; return c; } @@ -289,7 +289,7 @@ dlgcontrol *ctrl_radiobuttons(struct controlset *s, const char *label, va_list ap; int i; dlgcontrol *c = ctrl_new(s, CTRL_RADIO, helpctx, handler, context); - c->radio.label = label ? dupstr(label) : NULL; + c->label = label ? dupstr(label) : NULL; c->radio.shortcut = shortcut; c->radio.ncolumns = ncolumns; /* @@ -333,7 +333,7 @@ dlgcontrol *ctrl_pushbutton(struct controlset *s, const char *label, handler_fn handler, intorptr context) { dlgcontrol *c = ctrl_new(s, CTRL_BUTTON, helpctx, handler, context); - c->button.label = label ? dupstr(label) : NULL; + c->label = label ? dupstr(label) : NULL; c->button.shortcut = shortcut; c->button.isdefault = false; c->button.iscancel = false; @@ -345,7 +345,7 @@ dlgcontrol *ctrl_listbox(struct controlset *s, const char *label, handler_fn handler, intorptr context) { dlgcontrol *c = ctrl_new(s, CTRL_LISTBOX, helpctx, handler, context); - c->listbox.label = label ? dupstr(label) : NULL; + c->label = label ? dupstr(label) : NULL; c->listbox.shortcut = shortcut; c->listbox.height = 5; /* *shrug* a plausible default */ c->listbox.draglist = false; @@ -362,7 +362,7 @@ dlgcontrol *ctrl_droplist(struct controlset *s, const char *label, handler_fn handler, intorptr context) { dlgcontrol *c = ctrl_new(s, CTRL_LISTBOX, helpctx, handler, context); - c->listbox.label = label ? dupstr(label) : NULL; + c->label = label ? dupstr(label) : NULL; c->listbox.shortcut = shortcut; c->listbox.height = 0; /* means it's a drop-down list */ c->listbox.draglist = false; @@ -379,7 +379,7 @@ dlgcontrol *ctrl_draglist(struct controlset *s, const char *label, handler_fn handler, intorptr context) { dlgcontrol *c = ctrl_new(s, CTRL_LISTBOX, helpctx, handler, context); - c->listbox.label = label ? dupstr(label) : NULL; + c->label = label ? dupstr(label) : NULL; c->listbox.shortcut = shortcut; c->listbox.height = 5; /* *shrug* a plausible default */ c->listbox.draglist = true; @@ -397,7 +397,7 @@ dlgcontrol *ctrl_filesel(struct controlset *s, const char *label, handler_fn handler, intorptr context) { dlgcontrol *c = ctrl_new(s, CTRL_FILESELECT, helpctx, handler, context); - c->fileselect.label = label ? dupstr(label) : NULL; + c->label = label ? dupstr(label) : NULL; c->fileselect.shortcut = shortcut; c->fileselect.filter = filter; c->fileselect.for_writing = write; @@ -410,7 +410,7 @@ dlgcontrol *ctrl_fontsel(struct controlset *s, const char *label, handler_fn handler, intorptr context) { dlgcontrol *c = ctrl_new(s, CTRL_FONTSELECT, helpctx, handler, context); - c->fontselect.label = label ? dupstr(label) : NULL; + c->label = label ? dupstr(label) : NULL; c->fontselect.shortcut = shortcut; return c; } @@ -426,7 +426,7 @@ dlgcontrol *ctrl_text(struct controlset *s, const char *text, intorptr helpctx) { dlgcontrol *c = ctrl_new(s, CTRL_TEXT, helpctx, NULL, P(NULL)); - c->text.label = dupstr(text); + c->label = dupstr(text); return c; } @@ -435,7 +435,7 @@ dlgcontrol *ctrl_checkbox(struct controlset *s, const char *label, handler_fn handler, intorptr context) { dlgcontrol *c = ctrl_new(s, CTRL_CHECKBOX, helpctx, handler, context); - c->checkbox.label = label ? dupstr(label) : NULL; + c->label = label ? dupstr(label) : NULL; c->checkbox.shortcut = shortcut; return c; } @@ -444,8 +444,8 @@ void ctrl_free(dlgcontrol *ctrl) { int i; - sfree(ctrl->generic.label); - switch (ctrl->generic.type) { + sfree(ctrl->label); + switch (ctrl->type) { case CTRL_RADIO: for (i = 0; i < ctrl->radio.nbuttons; i++) sfree(ctrl->radio.buttons[i]); diff --git a/dialog.h b/dialog.h index 28d0916c..51d481fa 100644 --- a/dialog.h +++ b/dialog.h @@ -104,317 +104,292 @@ enum { typedef void (*handler_fn)(dlgcontrol *ctrl, dlgparam *dp, void *data, int event); -#define STANDARD_PREFIX \ - int type; \ - char *label; \ - bool tabdelay; \ - int column; \ - handler_fn handler; \ - intorptr context; \ - intorptr helpctx; \ - dlgcontrol *align_next_to +struct dlgcontrol { + /* + * Generic fields shared by all the control types. + */ + 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 `delay_taborder' is true, it indicates that this particular + * control should not yet appear in the tab order. A subsequent + * CTRL_TABDELAY entry will place it. + */ + bool delay_taborder; + /* + * 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 `Conf' 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 one or two pieces of `void *' or `int' data. + */ + intorptr context, context2; + /* + * 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; + /* + * Setting this to non-NULL coerces two controls to have their + * y-coordinates adjusted so that they can sit alongside each + * other and look nicely aligned, even if they're different + * heights. + * + * Set this field on the _second_ control of the pair (in terms of + * order in the data structure), so that when it's instantiated, + * the first one is already there to be referred to. + */ + dlgcontrol *align_next_to; -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. + * Union of further fields specific to each control type. */ - 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. - */ - bool 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 `Conf' 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; - /* - * Setting this to non-NULL coerces two controls to have their - * y-coordinates adjusted so that they can sit alongside each - * other and look nicely aligned, even if they're different - * heights. - * - * Set this field on the _second_ control of the pair (in - * terms of order in the data structure), so that when it's - * instantiated, the first one is already there to be referred - * to. - */ - dlgcontrol *align_next_to; - } generic; - struct { - STANDARD_PREFIX; - dlgcontrol *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; - bool 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. - */ - bool 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. - */ - bool isdefault; - /* - * Also, the reverse of this: a default cancel-type button, - * which is implicitly pressed when you hit Escape. - */ - bool 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! - */ - bool 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 */ - /* - * Flag which can be set to false to suppress the horizontal - * scroll bar if a list box entry goes off the right-hand - * side. - */ - bool hscroll; - } 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). - */ - bool 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; + union { + struct { /* for CTRL_TABDELAY */ + dlgcontrol *ctrl; + } tabdelay; + struct { /* for CTRL_EDITBOX */ + 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; + bool 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. + */ + bool has_list; + } editbox; + struct { /* for CTRL_RADIO */ + /* + * `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 { /* for CTRL_CHECKBOX */ + char shortcut; + } checkbox; + struct { /* for CTRL_BUTTON */ + 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. + */ + bool isdefault; + /* + * Also, the reverse of this: a default cancel-type + * button, which is implicitly pressed when you hit + * Escape. + */ + bool iscancel; + } button; + struct { /* for CTRL_LISTBOX */ + 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! + */ + bool 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 */ + /* + * Flag which can be set to false to suppress the + * horizontal scroll bar if a list box entry goes off the + * right-hand side. + */ + bool hscroll; + } listbox; + struct { /* for CTRL_FILESELECT */ + 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). + */ + bool 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 { /* for CTRL_COLUMNS */ + /* In this variant, `label' MUST be NULL. */ + 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 { /* for CTRL_FONTSELECT */ + char shortcut; + } fontselect; + }; }; #undef STANDARD_PREFIX diff --git a/unix/config-gtk.c b/unix/config-gtk.c index 5f3d7e0b..0ffb1ad9 100644 --- a/unix/config-gtk.c +++ b/unix/config-gtk.c @@ -14,7 +14,7 @@ static void about_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { if (event == EVENT_ACTION) { - about_box(ctrl->generic.context.p); + about_box(ctrl->context.p); } } @@ -31,7 +31,7 @@ void gtk_setup_config_box(struct controlbox *b, bool midsession, void *win) s = ctrl_getset(b, "", "", ""); c = ctrl_pushbutton(s, "About", 'a', HELPCTX(no_help), about_handler, P(win)); - c->generic.column = 0; + c->column = 0; } /* @@ -50,8 +50,8 @@ void gtk_setup_config_box(struct controlbox *b, bool midsession, void *win) */ for (i = 0; i < s->ncontrols; i++) { c = s->ctrls[i]; - if (c->generic.type == CTRL_CHECKBOX && - c->generic.context.i == CONF_scrollbar) { + if (c->type == CTRL_CHECKBOX && + c->context.i == CONF_scrollbar) { /* * Control i is the scrollbar checkbox. * Control s->ncontrols-1 is the scrollbar-on-left one. diff --git a/unix/config-unix.c b/unix/config-unix.c index 43d43bbd..179d8a9c 100644 --- a/unix/config-unix.c +++ b/unix/config-unix.c @@ -28,7 +28,7 @@ void unix_setup_config_box(struct controlbox *b, bool midsession, int protocol) * control. */ s = ctrl_getset(b, "Terminal", "printing", "Remote-controlled printing"); - assert(s->ncontrols == 1 && s->ctrls[0]->generic.type == CTRL_EDITBOX); + assert(s->ncontrols == 1 && s->ctrls[0]->type == CTRL_EDITBOX); s->ctrls[0]->editbox.has_list = false; /* @@ -39,9 +39,9 @@ void unix_setup_config_box(struct controlbox *b, bool midsession, int protocol) s = ctrl_getset(b, "Connection/Proxy", "basics", NULL); for (i = 0; i < s->ncontrols; i++) { c = s->ctrls[i]; - if (c->generic.type == CTRL_LISTBOX && - c->generic.handler == proxy_type_handler) { - c->generic.context.i |= PROXY_UI_FLAG_LOCAL; + if (c->type == CTRL_LISTBOX && + c->handler == proxy_type_handler) { + c->context.i |= PROXY_UI_FLAG_LOCAL; break; } } diff --git a/unix/dialog.c b/unix/dialog.c index 08572297..f6c5ab54 100644 --- a/unix/dialog.c +++ b/unix/dialog.c @@ -268,7 +268,7 @@ dlgcontrol *dlg_last_focused(dlgcontrol *ctrl, dlgparam *dp) void dlg_radiobutton_set(dlgcontrol *ctrl, dlgparam *dp, int which) { struct uctrl *uc = dlg_find_byctrl(dp, ctrl); - assert(uc->ctrl->generic.type == CTRL_RADIO); + assert(uc->ctrl->type == CTRL_RADIO); assert(uc->buttons != NULL); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(uc->buttons[which]), true); } @@ -278,7 +278,7 @@ int dlg_radiobutton_get(dlgcontrol *ctrl, dlgparam *dp) struct uctrl *uc = dlg_find_byctrl(dp, ctrl); int i; - assert(uc->ctrl->generic.type == CTRL_RADIO); + assert(uc->ctrl->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]))) @@ -289,14 +289,14 @@ int dlg_radiobutton_get(dlgcontrol *ctrl, dlgparam *dp) void dlg_checkbox_set(dlgcontrol *ctrl, dlgparam *dp, bool checked) { struct uctrl *uc = dlg_find_byctrl(dp, ctrl); - assert(uc->ctrl->generic.type == CTRL_CHECKBOX); + assert(uc->ctrl->type == CTRL_CHECKBOX); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(uc->toplevel), checked); } bool dlg_checkbox_get(dlgcontrol *ctrl, dlgparam *dp) { struct uctrl *uc = dlg_find_byctrl(dp, ctrl); - assert(uc->ctrl->generic.type == CTRL_CHECKBOX); + assert(uc->ctrl->type == CTRL_CHECKBOX); return gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(uc->toplevel)); } @@ -305,7 +305,7 @@ void dlg_editbox_set(dlgcontrol *ctrl, dlgparam *dp, char const *text) struct uctrl *uc = dlg_find_byctrl(dp, ctrl); GtkWidget *entry; char *tmpstring; - assert(uc->ctrl->generic.type == CTRL_EDITBOX); + assert(uc->ctrl->type == CTRL_EDITBOX); #if GTK_CHECK_VERSION(2,4,0) if (uc->combo) @@ -341,7 +341,7 @@ void dlg_editbox_set(dlgcontrol *ctrl, dlgparam *dp, char const *text) char *dlg_editbox_get(dlgcontrol *ctrl, dlgparam *dp) { struct uctrl *uc = dlg_find_byctrl(dp, ctrl); - assert(uc->ctrl->generic.type == CTRL_EDITBOX); + assert(uc->ctrl->type == CTRL_EDITBOX); #if GTK_CHECK_VERSION(2,4,0) if (uc->combo) { @@ -371,8 +371,8 @@ void dlg_listbox_clear(dlgcontrol *ctrl, dlgparam *dp) { struct uctrl *uc = dlg_find_byctrl(dp, ctrl); - assert(uc->ctrl->generic.type == CTRL_EDITBOX || - uc->ctrl->generic.type == CTRL_LISTBOX); + assert(uc->ctrl->type == CTRL_EDITBOX || + uc->ctrl->type == CTRL_LISTBOX); #if !GTK_CHECK_VERSION(2,4,0) if (uc->menu) { @@ -399,8 +399,8 @@ void dlg_listbox_del(dlgcontrol *ctrl, dlgparam *dp, int index) { struct uctrl *uc = dlg_find_byctrl(dp, ctrl); - assert(uc->ctrl->generic.type == CTRL_EDITBOX || - uc->ctrl->generic.type == CTRL_LISTBOX); + assert(uc->ctrl->type == CTRL_EDITBOX || + uc->ctrl->type == CTRL_LISTBOX); #if !GTK_CHECK_VERSION(2,4,0) if (uc->menu) { @@ -446,8 +446,8 @@ void dlg_listbox_addwithid(dlgcontrol *ctrl, dlgparam *dp, { struct uctrl *uc = dlg_find_byctrl(dp, ctrl); - assert(uc->ctrl->generic.type == CTRL_EDITBOX || - uc->ctrl->generic.type == CTRL_LISTBOX); + assert(uc->ctrl->type == CTRL_EDITBOX || + uc->ctrl->type == CTRL_LISTBOX); /* * This routine is long and complicated in both GTK 1 and 2, @@ -569,7 +569,7 @@ void dlg_listbox_addwithid(dlgcontrol *ctrl, dlgparam *dp, * 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 = (uc->ctrl->type == CTRL_LISTBOX ? ctrl->listbox.ncols : 1); cols = cols ? cols : 1; for (i = 0; i < cols; i++) { int collen = strcspn(text, "\t"); @@ -593,8 +593,8 @@ int dlg_listbox_getid(dlgcontrol *ctrl, dlgparam *dp, int index) { struct uctrl *uc = dlg_find_byctrl(dp, ctrl); - assert(uc->ctrl->generic.type == CTRL_EDITBOX || - uc->ctrl->generic.type == CTRL_LISTBOX); + assert(uc->ctrl->type == CTRL_EDITBOX || + uc->ctrl->type == CTRL_LISTBOX); #if !GTK_CHECK_VERSION(2,4,0) if (uc->menu || uc->list) { @@ -632,8 +632,8 @@ int dlg_listbox_index(dlgcontrol *ctrl, dlgparam *dp) { struct uctrl *uc = dlg_find_byctrl(dp, ctrl); - assert(uc->ctrl->generic.type == CTRL_EDITBOX || - uc->ctrl->generic.type == CTRL_LISTBOX); + assert(uc->ctrl->type == CTRL_EDITBOX || + uc->ctrl->type == CTRL_LISTBOX); #if !GTK_CHECK_VERSION(2,4,0) if (uc->menu || uc->list) { @@ -716,16 +716,16 @@ bool dlg_listbox_issel(dlgcontrol *ctrl, dlgparam *dp, int index) { struct uctrl *uc = dlg_find_byctrl(dp, ctrl); - assert(uc->ctrl->generic.type == CTRL_EDITBOX || - uc->ctrl->generic.type == CTRL_LISTBOX); + assert(uc->ctrl->type == CTRL_EDITBOX || + uc->ctrl->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->ctrl->type == CTRL_EDITBOX || + uc->ctrl->type == CTRL_LISTBOX); assert(uc->menu != NULL || uc->list != NULL); children = gtk_container_children(GTK_CONTAINER(uc->menu ? uc->menu : @@ -773,8 +773,8 @@ void dlg_listbox_select(dlgcontrol *ctrl, dlgparam *dp, int index) { struct uctrl *uc = dlg_find_byctrl(dp, ctrl); - assert(uc->ctrl->generic.type == CTRL_EDITBOX || - uc->ctrl->generic.type == CTRL_LISTBOX); + assert(uc->ctrl->type == CTRL_EDITBOX || + uc->ctrl->type == CTRL_LISTBOX); #if !GTK_CHECK_VERSION(2,4,0) if (uc->optmenu) { @@ -841,7 +841,7 @@ void dlg_text_set(dlgcontrol *ctrl, dlgparam *dp, char const *text) { struct uctrl *uc = dlg_find_byctrl(dp, ctrl); - assert(uc->ctrl->generic.type == CTRL_TEXT); + assert(uc->ctrl->type == CTRL_TEXT); assert(uc->text != NULL); gtk_label_set_text(GTK_LABEL(uc->text), text); @@ -851,7 +851,7 @@ void dlg_label_change(dlgcontrol *ctrl, dlgparam *dp, char const *text) { struct uctrl *uc = dlg_find_byctrl(dp, ctrl); - switch (uc->ctrl->generic.type) { + switch (uc->ctrl->type) { case CTRL_BUTTON: gtk_label_set_text(GTK_LABEL(uc->toplevel), text); shortcut_highlight(uc->toplevel, ctrl->button.shortcut); @@ -891,7 +891,7 @@ void dlg_filesel_set(dlgcontrol *ctrl, dlgparam *dp, Filename *fn) /* We must copy fn->path before passing it to gtk_entry_set_text. * See comment in dlg_editbox_set() for the reasons. */ char *duppath = dupstr(fn->path); - assert(uc->ctrl->generic.type == CTRL_FILESELECT); + assert(uc->ctrl->type == CTRL_FILESELECT); assert(uc->entry != NULL); gtk_entry_set_text(GTK_ENTRY(uc->entry), duppath); sfree(duppath); @@ -900,7 +900,7 @@ void dlg_filesel_set(dlgcontrol *ctrl, dlgparam *dp, Filename *fn) Filename *dlg_filesel_get(dlgcontrol *ctrl, dlgparam *dp) { struct uctrl *uc = dlg_find_byctrl(dp, ctrl); - assert(uc->ctrl->generic.type == CTRL_FILESELECT); + assert(uc->ctrl->type == CTRL_FILESELECT); assert(uc->entry != NULL); return filename_from_str(gtk_entry_get_text(GTK_ENTRY(uc->entry))); } @@ -911,7 +911,7 @@ void dlg_fontsel_set(dlgcontrol *ctrl, dlgparam *dp, FontSpec *fs) /* We must copy fs->name before passing it to gtk_entry_set_text. * See comment in dlg_editbox_set() for the reasons. */ char *dupname = dupstr(fs->name); - assert(uc->ctrl->generic.type == CTRL_FONTSELECT); + assert(uc->ctrl->type == CTRL_FONTSELECT); assert(uc->entry != NULL); gtk_entry_set_text(GTK_ENTRY(uc->entry), dupname); sfree(dupname); @@ -920,7 +920,7 @@ void dlg_fontsel_set(dlgcontrol *ctrl, dlgparam *dp, FontSpec *fs) FontSpec *dlg_fontsel_get(dlgcontrol *ctrl, dlgparam *dp) { struct uctrl *uc = dlg_find_byctrl(dp, ctrl); - assert(uc->ctrl->generic.type == CTRL_FONTSELECT); + assert(uc->ctrl->type == CTRL_FONTSELECT); assert(uc->entry != NULL); return fontspec_new(gtk_entry_get_text(GTK_ENTRY(uc->entry))); } @@ -950,7 +950,7 @@ void dlg_set_focus(dlgcontrol *ctrl, dlgparam *dp) { struct uctrl *uc = dlg_find_byctrl(dp, ctrl); - switch (ctrl->generic.type) { + switch (ctrl->type) { case CTRL_CHECKBOX: case CTRL_BUTTON: /* Check boxes and buttons get the focus _and_ get toggled. */ @@ -1082,15 +1082,15 @@ void dlg_refresh(dlgcontrol *ctrl, dlgparam *dp) struct uctrl *uc; if (ctrl) { - if (ctrl->generic.handler != NULL) - ctrl->generic.handler(ctrl, dp, dp->data, EVENT_REFRESH); + if (ctrl->handler != NULL) + ctrl->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, + if (uc->ctrl->handler != NULL) + uc->ctrl->handler(uc->ctrl, dp, dp->data, EVENT_REFRESH); } } @@ -1221,14 +1221,14 @@ 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); + uc->ctrl->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); + uc->ctrl->handler(uc->ctrl, dp, dp->data, EVENT_VALCHANGE); } static gboolean editbox_key(GtkWidget *widget, GdkEventKey *event, @@ -1259,7 +1259,7 @@ 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); + uc->ctrl->handler(uc->ctrl, dp, dp->data, EVENT_VALCHANGE); } } @@ -1268,7 +1268,7 @@ static gboolean editbox_lostfocus(GtkWidget *ed, GdkEventFocus *event, { 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); + uc->ctrl->handler(uc->ctrl, dp, dp->data, EVENT_REFRESH); return false; } @@ -1404,7 +1404,7 @@ static gboolean listitem_button_release(GtkWidget *item, GdkEventButton *event, 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); + uc->ctrl->handler(uc->ctrl, dp, dp->data, EVENT_ACTION); return true; } return false; @@ -1415,7 +1415,7 @@ 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); + uc->ctrl->handler(uc->ctrl, dp, dp->data, EVENT_SELCHANGE); } static void draglist_move(struct dlgparam *dp, struct uctrl *uc, int direction) @@ -1440,7 +1440,7 @@ static void draglist_move(struct dlgparam *dp, struct uctrl *uc, int direction) 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); + uc->ctrl->handler(uc->ctrl, dp, dp->data, EVENT_VALCHANGE); } static void draglist_up(GtkButton *button, gpointer data) @@ -1469,7 +1469,7 @@ static void listbox_doubleclick(GtkTreeView *treeview, GtkTreePath *path, 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); + uc->ctrl->handler(uc->ctrl, dp, dp->data, EVENT_ACTION); } static void listbox_selchange(GtkTreeSelection *treeselection, @@ -1479,7 +1479,7 @@ static void listbox_selchange(GtkTreeSelection *treeselection, 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); + uc->ctrl->handler(uc->ctrl, dp, dp->data, EVENT_SELCHANGE); } struct draglist_valchange_ctx { @@ -1492,7 +1492,7 @@ 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->uc->ctrl->handler(ctx->uc->ctrl, ctx->dp, ctx->dp->data, EVENT_VALCHANGE); sfree(ctx); @@ -1547,7 +1547,7 @@ static void menuitem_activate(GtkMenuItem *item, gpointer data) GtkWidget *menushell = GTK_WIDGET(item)->parent; gpointer optmenu = g_object_get_data(G_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); + uc->ctrl->handler(uc->ctrl, dp, dp->data, EVENT_SELCHANGE); } #else @@ -1557,7 +1557,7 @@ 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); + uc->ctrl->handler(uc->ctrl, dp, dp->data, EVENT_SELCHANGE); } #endif /* !GTK_CHECK_VERSION(2,4,0) */ @@ -1631,7 +1631,7 @@ static void colourchoose_response(GtkDialog *dialog, dp->coloursel_result.ok = false; } - uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_CALLBACK); + uc->ctrl->handler(uc->ctrl, dp, dp->data, EVENT_CALLBACK); gtk_widget_destroy(GTK_WIDGET(dialog)); } @@ -1668,7 +1668,7 @@ static void coloursel_ok(GtkButton *button, gpointer data) } #endif dp->coloursel_result.ok = true; - uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_CALLBACK); + uc->ctrl->handler(uc->ctrl, dp, dp->data, EVENT_CALLBACK); } static void coloursel_cancel(GtkButton *button, gpointer data) @@ -1677,7 +1677,7 @@ static void coloursel_cancel(GtkButton *button, gpointer data) gpointer coloursel = g_object_get_data(G_OBJECT(button), "user-data"); struct uctrl *uc = g_object_get_data(G_OBJECT(coloursel), "user-data"); dp->coloursel_result.ok = false; - uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_CALLBACK); + uc->ctrl->handler(uc->ctrl, dp, dp->data, EVENT_CALLBACK); } #endif /* end of coloursel response handlers */ @@ -1687,7 +1687,7 @@ 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) { + if (uc->ctrl->type == CTRL_FILESELECT) { #ifdef USE_GTK_FILE_CHOOSER_DIALOG GtkWidget *filechoose = gtk_file_chooser_dialog_new (uc->ctrl->fileselect.title, GTK_WINDOW(dp->window), @@ -1723,7 +1723,7 @@ static void filefont_clicked(GtkButton *button, gpointer data) #endif } - if (uc->ctrl->generic.type == CTRL_FONTSELECT) { + if (uc->ctrl->type == CTRL_FONTSELECT) { const gchar *fontname = gtk_entry_get_text(GTK_ENTRY(uc->entry)); #if !GTK_CHECK_VERSION(2,0,0) @@ -1822,7 +1822,7 @@ static void label_sizealloc(GtkWidget *widget, GtkAllocation *alloc, struct uctrl *uc = dlg_find_bywidget(dp, widget); gtk_widget_set_size_request(uc->text, alloc->width, -1); - gtk_label_set_text(GTK_LABEL(uc->text), uc->ctrl->generic.label); + gtk_label_set_text(GTK_LABEL(uc->text), uc->ctrl->label); g_signal_handler_disconnect(G_OBJECT(uc->text), uc->textsig); } #endif @@ -1882,7 +1882,7 @@ GtkWidget *layout_ctrls( bool left = false; GtkWidget *w = NULL; - switch (ctrl->generic.type) { + switch (ctrl->type) { case CTRL_COLUMNS: { static const int simplecols[1] = { 100 }; columns_set_cols(cols, ctrl->columns.ncols, @@ -1916,9 +1916,9 @@ GtkWidget *layout_ctrls( uc->label = NULL; uc->nclicks = 0; - switch (ctrl->generic.type) { + switch (ctrl->type) { case CTRL_BUTTON: - w = gtk_button_new_with_label(ctrl->generic.label); + w = gtk_button_new_with_label(ctrl->label); if (win) { gtk_widget_set_can_default(w, true); if (ctrl->button.isdefault) @@ -1934,7 +1934,7 @@ GtkWidget *layout_ctrls( ctrl->button.shortcut, SHORTCUT_UCTRL, uc); break; case CTRL_CHECKBOX: - w = gtk_check_button_new_with_label(ctrl->generic.label); + w = gtk_check_button_new_with_label(ctrl->label); g_signal_connect(G_OBJECT(w), "toggled", G_CALLBACK(button_toggled), dp); g_signal_connect(G_OBJECT(w), "focus_in_event", @@ -1952,8 +1952,8 @@ GtkWidget *layout_ctrls( GSList *group; w = columns_new(0); - if (ctrl->generic.label) { - GtkWidget *label = gtk_label_new(ctrl->generic.label); + if (ctrl->label) { + GtkWidget *label = gtk_label_new(ctrl->label); columns_add(COLUMNS(w), label, 0, 1); columns_force_left_align(COLUMNS(w), label); gtk_widget_show(label); @@ -2069,10 +2069,10 @@ GtkWidget *layout_ctrls( gtk_entry_set_width_chars(GTK_ENTRY(w), 1); #endif - if (ctrl->generic.label) { + if (ctrl->label) { GtkWidget *label, *container; - label = gtk_label_new(ctrl->generic.label); + label = gtk_label_new(ctrl->label); shortcut_add(scs, label, ctrl->editbox.shortcut, SHORTCUT_FOCUS, uc->entry); @@ -2105,20 +2105,20 @@ GtkWidget *layout_ctrls( case CTRL_FONTSELECT: { GtkWidget *ww; const char *browsebtn = - (ctrl->generic.type == CTRL_FILESELECT ? + (ctrl->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); + if (ctrl->label) { + ww = gtk_label_new(ctrl->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->type == CTRL_FILESELECT ? ctrl->fileselect.shortcut : ctrl->fontselect.shortcut), SHORTCUT_UCTRL, uc); @@ -2404,10 +2404,10 @@ GtkWidget *layout_ctrls( #endif } - if (ctrl->generic.label) { + if (ctrl->label) { GtkWidget *label, *container; - label = gtk_label_new(ctrl->generic.label); + label = gtk_label_new(ctrl->label); #if GTK_CHECK_VERSION(3,0,0) gtk_label_set_width_chars(GTK_LABEL(label), 3); #endif @@ -2477,7 +2477,7 @@ GtkWidget *layout_ctrls( * wrapping labels behave sensibly. So now we can just do * the obvious thing. */ - uc->text = w = gtk_label_new(uc->ctrl->generic.label); + uc->text = w = gtk_label_new(uc->ctrl->label); #endif align_label_left(GTK_LABEL(w)); gtk_label_set_line_wrap(GTK_LABEL(w), true); @@ -2487,11 +2487,11 @@ GtkWidget *layout_ctrls( assert(w != NULL); columns_add(cols, w, - COLUMN_START(ctrl->generic.column), - COLUMN_SPAN(ctrl->generic.column)); + COLUMN_START(ctrl->column), + COLUMN_SPAN(ctrl->column)); if (left) columns_force_left_align(cols, w); - if (ctrl->generic.align_next_to) { + if (ctrl->align_next_to) { /* * Implement align_next_to by simply forcing the two * controls to have the same height of size allocation. At @@ -2502,7 +2502,7 @@ GtkWidget *layout_ctrls( * reasonably well. */ struct uctrl *uc2 = dlg_find_byctrl( - dp, ctrl->generic.align_next_to); + dp, ctrl->align_next_to); assert(uc2); columns_force_same_height(cols, w, uc2->toplevel); @@ -2657,7 +2657,7 @@ gint win_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data) * Precisely what this is depends on the type of * control. */ - switch (sc->uc->ctrl->generic.type) { + switch (sc->uc->ctrl->type) { case CTRL_CHECKBOX: case CTRL_BUTTON: /* Check boxes and buttons get the focus _and_ get toggled. */ @@ -3243,9 +3243,9 @@ GtkWidget *create_config_box(const char *title, Conf *conf, 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) { + if (s->ctrls[j]->type != CTRL_TABDELAY && + s->ctrls[j]->type != CTRL_COLUMNS && + s->ctrls[j]->type != CTRL_TEXT) { dlg_set_focus(s->ctrls[j], dp); dp->lastfocus = s->ctrls[j]; done = true; @@ -3287,7 +3287,7 @@ static void messagebox_handler(dlgcontrol *ctrl, dlgparam *dp, void *data, int event) { if (event == EVENT_ACTION) - dlg_end(dp, ctrl->generic.context.i); + dlg_end(dp, ctrl->context.i); } static const struct message_box_button button_array_yn[] = { @@ -3355,7 +3355,7 @@ static GtkWidget *create_message_box_general( c = ctrl_pushbutton(s0, button->title, button->shortcut, HELPCTX(no_help), messagebox_handler, I(button->value)); - c->generic.column = index++; + c->column = index++; if (button->type > 0) c->button.isdefault = true; @@ -4025,7 +4025,7 @@ void showeventlog(eventlog_stuff *es, void *parentwin) 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->column = 1; c->button.isdefault = true; s1 = ctrl_getset(es->eventbox, "x", "", ""); diff --git a/windows/config.c b/windows/config.c index 4f6e6c5c..d8915728 100644 --- a/windows/config.c +++ b/windows/config.c @@ -13,7 +13,7 @@ static void about_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { - HWND *hwndp = (HWND *)ctrl->generic.context.p; + HWND *hwndp = (HWND *)ctrl->context.p; if (event == EVENT_ACTION) { modal_about_box(*hwndp); @@ -23,7 +23,7 @@ static void about_handler(dlgcontrol *ctrl, dlgparam *dlg, static void help_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { - HWND *hwndp = (HWND *)ctrl->generic.context.p; + HWND *hwndp = (HWND *)ctrl->context.p; if (event == EVENT_ACTION) { show_help(*hwndp); @@ -56,11 +56,11 @@ void win_setup_config_box(struct controlbox *b, HWND *hwndp, bool has_help, s = ctrl_getset(b, "", "", ""); c = ctrl_pushbutton(s, "About", 'a', HELPCTX(no_help), about_handler, P(hwndp)); - c->generic.column = 0; + c->column = 0; if (has_help) { c = ctrl_pushbutton(s, "Help", 'h', HELPCTX(no_help), help_handler, P(hwndp)); - c->generic.column = 1; + c->column = 1; } } @@ -82,8 +82,8 @@ void win_setup_config_box(struct controlbox *b, HWND *hwndp, bool has_help, int i; for (i = 0; i < s->ncontrols; i++) { c = s->ctrls[i]; - if (c->generic.type == CTRL_CHECKBOX && - c->generic.context.i == CONF_scrollbar) { + if (c->type == CTRL_CHECKBOX && + c->context.i == CONF_scrollbar) { /* * Control i is the scrollbar checkbox. * Control s->ncontrols-1 is the scrollbar-in-FS one. @@ -134,9 +134,9 @@ void win_setup_config_box(struct controlbox *b, HWND *hwndp, bool has_help, int i; for (i = 0; i < s->ncontrols; i++) { c = s->ctrls[i]; - if (c->generic.type == CTRL_RADIO && - c->generic.context.i == CONF_beep) { - assert(c->generic.handler == conf_radiobutton_handler); + if (c->type == CTRL_RADIO && + c->context.i == CONF_beep) { + assert(c->handler == conf_radiobutton_handler); c->radio.nbuttons += 2; c->radio.buttons = sresize(c->radio.buttons, c->radio.nbuttons, char *); @@ -233,9 +233,9 @@ void win_setup_config_box(struct controlbox *b, HWND *hwndp, bool has_help, int i; for (i = 0; i < s->ncontrols; i++) { c = s->ctrls[i]; - if (c->generic.type == CTRL_RADIO && - c->generic.context.i == CONF_vtmode) { - assert(c->generic.handler == conf_radiobutton_handler); + if (c->type == CTRL_RADIO && + c->context.i == CONF_vtmode) { + assert(c->handler == conf_radiobutton_handler); c->radio.nbuttons += 3; c->radio.buttons = sresize(c->radio.buttons, c->radio.nbuttons, char *); @@ -362,9 +362,9 @@ void win_setup_config_box(struct controlbox *b, HWND *hwndp, bool has_help, s = ctrl_getset(b, "Connection/Proxy", "basics", NULL); for (i = 0; i < s->ncontrols; i++) { c = s->ctrls[i]; - if (c->generic.type == CTRL_LISTBOX && - c->generic.handler == proxy_type_handler) { - c->generic.context.i |= PROXY_UI_FLAG_LOCAL; + if (c->type == CTRL_LISTBOX && + c->handler == proxy_type_handler) { + c->context.i |= PROXY_UI_FLAG_LOCAL; break; } } diff --git a/windows/controls.c b/windows/controls.c index 7543eb13..d458496c 100644 --- a/windows/controls.c +++ b/windows/controls.c @@ -1415,7 +1415,7 @@ void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, * CTRL_COLUMNS and doesn't require any control creation at * all. */ - if (ctrl->generic.type == CTRL_COLUMNS) { + if (ctrl->type == CTRL_COLUMNS) { assert((ctrl->columns.ncols == 1) ^ (ncols == 1)); if (ncols == 1) { @@ -1455,10 +1455,10 @@ void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, } continue; - } else if (ctrl->generic.type == CTRL_TABDELAY) { + } else if (ctrl->type == CTRL_TABDELAY) { int i; - assert(!ctrl->generic.tabdelay); + assert(!ctrl->delay_taborder); ctrl = ctrl->tabdelay.ctrl; for (i = 0; i < ntabdelays; i++) @@ -1478,8 +1478,8 @@ void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, */ int col; - colstart = COLUMN_START(ctrl->generic.column); - colspan = COLUMN_SPAN(ctrl->generic.column); + colstart = COLUMN_START(ctrl->column); + colspan = COLUMN_SPAN(ctrl->column); pos = columns[colstart]; /* structure copy */ pos.width = columns[colstart+colspan-1].width + @@ -1494,7 +1494,7 @@ void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, * tabdelay list, and unset pos.hwnd to inhibit actual * control creation. */ - if (ctrl->generic.tabdelay) { + if (ctrl->delay_taborder) { assert(ntabdelays < lenof(tabdelays)); tabdelays[ntabdelays] = pos; /* structure copy */ tabdelayed[ntabdelays] = ctrl; @@ -1522,13 +1522,13 @@ void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, * Now we're ready to actually create the control, by * switching on its type. */ - switch (ctrl->generic.type) { + switch (ctrl->type) { case CTRL_TEXT: { char *wrapped, *escaped; int lines; num_ids = 1; wrapped = staticwrap(&pos, cp->hwnd, - ctrl->generic.label, &lines); + ctrl->label, &lines); escaped = shortcut_escape(wrapped, NO_SHORTCUT); statictext(&pos, escaped, lines, base_id); sfree(escaped); @@ -1537,7 +1537,7 @@ void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, } case CTRL_EDITBOX: num_ids = 2; /* static, edit */ - escaped = shortcut_escape(ctrl->editbox.label, + escaped = shortcut_escape(ctrl->label, ctrl->editbox.shortcut); shortcuts[nshortcuts++] = ctrl->editbox.shortcut; if (ctrl->editbox.percentwidth == 100) { @@ -1564,7 +1564,7 @@ void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, struct radio *buttons; int i; - escaped = shortcut_escape(ctrl->radio.label, + escaped = shortcut_escape(ctrl->label, ctrl->radio.shortcut); shortcuts[nshortcuts++] = ctrl->radio.shortcut; @@ -1596,14 +1596,14 @@ void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, } case CTRL_CHECKBOX: num_ids = 1; - escaped = shortcut_escape(ctrl->checkbox.label, + escaped = shortcut_escape(ctrl->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, + escaped = shortcut_escape(ctrl->label, ctrl->button.shortcut); shortcuts[nshortcuts++] = ctrl->button.shortcut; if (ctrl->button.iscancel) @@ -1614,7 +1614,7 @@ void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, break; case CTRL_LISTBOX: num_ids = 2; - escaped = shortcut_escape(ctrl->listbox.label, + escaped = shortcut_escape(ctrl->label, ctrl->listbox.shortcut); shortcuts[nshortcuts++] = ctrl->listbox.shortcut; if (ctrl->listbox.draglist) { @@ -1664,7 +1664,7 @@ void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, break; case CTRL_FILESELECT: num_ids = 3; - escaped = shortcut_escape(ctrl->fileselect.label, + escaped = shortcut_escape(ctrl->label, ctrl->fileselect.shortcut); shortcuts[nshortcuts++] = ctrl->fileselect.shortcut; editbutton(&pos, escaped, base_id, base_id+1, @@ -1673,7 +1673,7 @@ void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, break; case CTRL_FONTSELECT: num_ids = 3; - escaped = shortcut_escape(ctrl->fontselect.label, + escaped = shortcut_escape(ctrl->label, ctrl->fontselect.shortcut); shortcuts[nshortcuts++] = ctrl->fontselect.shortcut; statictext(&pos, escaped, 1, base_id); @@ -1708,7 +1708,7 @@ void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, if (actual_base_id == base_id) base_id += num_ids; - if (ctrl->generic.align_next_to) { + if (ctrl->align_next_to) { /* * Implement align_next_to by looking at the y extents * of the two controls now that both are created, and @@ -1716,7 +1716,7 @@ void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, * centred on a common horizontal line. */ struct winctrl *c2 = winctrl_findbyctrl( - wc, ctrl->generic.align_next_to); + wc, ctrl->align_next_to); HWND win1 = GetDlgItem(pos.hwnd, c->align_id); HWND win2 = GetDlgItem(pos.hwnd, c2->align_id); RECT rect1, rect2; @@ -1839,7 +1839,7 @@ bool winctrl_handle_command(struct dlgparam *dp, UINT msg, ctrl = c->ctrl; id = LOWORD(wParam) - c->base_id; - if (!ctrl || !ctrl->generic.handler) + if (!ctrl || !ctrl->handler) return false; /* nothing we can do here */ /* @@ -1855,7 +1855,7 @@ bool winctrl_handle_command(struct dlgparam *dp, UINT msg, /* * Now switch on the control type and the message. */ - switch (ctrl->generic.type) { + switch (ctrl->type) { case CTRL_EDITBOX: if (msg == WM_COMMAND && !ctrl->editbox.has_list && (HIWORD(wParam) == EN_SETFOCUS || HIWORD(wParam) == EN_KILLFOCUS)) @@ -1866,7 +1866,7 @@ bool winctrl_handle_command(struct dlgparam *dp, UINT msg, if (msg == WM_COMMAND && !ctrl->editbox.has_list && HIWORD(wParam) == EN_CHANGE) - ctrl->generic.handler(ctrl, dp, dp->data, EVENT_VALCHANGE); + ctrl->handler(ctrl, dp, dp->data, EVENT_VALCHANGE); if (msg == WM_COMMAND && ctrl->editbox.has_list) { if (HIWORD(wParam) == CBN_SELCHANGE) { @@ -1882,11 +1882,11 @@ bool winctrl_handle_command(struct dlgparam *dp, UINT msg, index, (LPARAM)text); SetDlgItemText(dp->hwnd, c->base_id+1, text); sfree(text); - ctrl->generic.handler(ctrl, dp, dp->data, EVENT_VALCHANGE); + ctrl->handler(ctrl, dp, dp->data, EVENT_VALCHANGE); } else if (HIWORD(wParam) == CBN_EDITCHANGE) { - ctrl->generic.handler(ctrl, dp, dp->data, EVENT_VALCHANGE); + ctrl->handler(ctrl, dp, dp->data, EVENT_VALCHANGE); } else if (HIWORD(wParam) == CBN_KILLFOCUS) { - ctrl->generic.handler(ctrl, dp, dp->data, EVENT_REFRESH); + ctrl->handler(ctrl, dp, dp->data, EVENT_REFRESH); } } @@ -1906,7 +1906,7 @@ bool winctrl_handle_command(struct dlgparam *dp, UINT msg, (HIWORD(wParam) == BN_CLICKED || HIWORD(wParam) == BN_DOUBLECLICKED) && IsDlgButtonChecked(dp->hwnd, LOWORD(wParam))) { - ctrl->generic.handler(ctrl, dp, dp->data, EVENT_VALCHANGE); + ctrl->handler(ctrl, dp, dp->data, EVENT_VALCHANGE); } break; case CTRL_CHECKBOX: @@ -1916,7 +1916,7 @@ bool winctrl_handle_command(struct dlgparam *dp, UINT msg, if (msg == WM_COMMAND && (HIWORD(wParam) == BN_CLICKED || HIWORD(wParam) == BN_DOUBLECLICKED)) { - ctrl->generic.handler(ctrl, dp, dp->data, EVENT_VALCHANGE); + ctrl->handler(ctrl, dp, dp->data, EVENT_VALCHANGE); } break; case CTRL_BUTTON: @@ -1926,7 +1926,7 @@ bool winctrl_handle_command(struct dlgparam *dp, UINT msg, if (msg == WM_COMMAND && (HIWORD(wParam) == BN_CLICKED || HIWORD(wParam) == BN_DOUBLECLICKED)) { - ctrl->generic.handler(ctrl, dp, dp->data, EVENT_ACTION); + ctrl->handler(ctrl, dp, dp->data, EVENT_ACTION); } break; case CTRL_LISTBOX: @@ -1944,14 +1944,14 @@ bool winctrl_handle_command(struct dlgparam *dp, UINT msg, 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); + ctrl->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); + ctrl->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); + ctrl->handler(ctrl, dp, dp->data, EVENT_SELCHANGE); } } break; @@ -1963,7 +1963,7 @@ bool winctrl_handle_command(struct dlgparam *dp, UINT msg, (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); + ctrl->handler(ctrl, dp, dp->data, EVENT_VALCHANGE); if (id == 2 && (msg == WM_COMMAND && (HIWORD(wParam) == BN_CLICKED || @@ -1988,7 +1988,7 @@ bool winctrl_handle_command(struct dlgparam *dp, UINT msg, 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); + ctrl->handler(ctrl, dp, dp->data, EVENT_VALCHANGE); } } break; @@ -2033,7 +2033,7 @@ bool winctrl_handle_command(struct dlgparam *dp, UINT msg, dlg_fontsel_set(ctrl, dp, fs); fontspec_free(fs); - ctrl->generic.handler(ctrl, dp, dp->data, EVENT_VALCHANGE); + ctrl->handler(ctrl, dp, dp->data, EVENT_VALCHANGE); } } break; @@ -2064,7 +2064,7 @@ bool winctrl_handle_command(struct dlgparam *dp, UINT msg, dp->coloursel_result.ok = true; } else dp->coloursel_result.ok = false; - ctrl->generic.handler(ctrl, dp, dp->data, EVENT_CALLBACK); + ctrl->handler(ctrl, dp, dp->data, EVENT_CALLBACK); } return ret; @@ -2095,10 +2095,10 @@ bool winctrl_context_help(struct dlgparam *dp, HWND hwnd, int id) * 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) + if (!c->ctrl || !c->ctrl->helpctx.p) return false; /* no help available for this ctrl */ - launch_help(hwnd, c->ctrl->generic.helpctx.p); + launch_help(hwnd, c->ctrl->helpctx.p); return true; } @@ -2133,7 +2133,7 @@ bool dlg_is_visible(dlgcontrol *ctrl, dlgparam *dp) void dlg_radiobutton_set(dlgcontrol *ctrl, dlgparam *dp, int whichbutton) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); - assert(c && c->ctrl->generic.type == CTRL_RADIO); + assert(c && c->ctrl->type == CTRL_RADIO); CheckRadioButton(dp->hwnd, c->base_id + 1, c->base_id + c->ctrl->radio.nbuttons, @@ -2144,7 +2144,7 @@ int dlg_radiobutton_get(dlgcontrol *ctrl, dlgparam *dp) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); int i; - assert(c && c->ctrl->generic.type == CTRL_RADIO); + assert(c && c->ctrl->type == CTRL_RADIO); for (i = 0; i < c->ctrl->radio.nbuttons; i++) if (IsDlgButtonChecked(dp->hwnd, c->base_id + 1 + i)) return i; @@ -2154,28 +2154,28 @@ int dlg_radiobutton_get(dlgcontrol *ctrl, dlgparam *dp) void dlg_checkbox_set(dlgcontrol *ctrl, dlgparam *dp, bool checked) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); - assert(c && c->ctrl->generic.type == CTRL_CHECKBOX); + assert(c && c->ctrl->type == CTRL_CHECKBOX); CheckDlgButton(dp->hwnd, c->base_id, checked); } bool dlg_checkbox_get(dlgcontrol *ctrl, dlgparam *dp) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); - assert(c && c->ctrl->generic.type == CTRL_CHECKBOX); + assert(c && c->ctrl->type == CTRL_CHECKBOX); return 0 != IsDlgButtonChecked(dp->hwnd, c->base_id); } void dlg_editbox_set(dlgcontrol *ctrl, dlgparam *dp, char const *text) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); - assert(c && c->ctrl->generic.type == CTRL_EDITBOX); + assert(c && c->ctrl->type == CTRL_EDITBOX); SetDlgItemText(dp->hwnd, c->base_id+1, text); } char *dlg_editbox_get(dlgcontrol *ctrl, dlgparam *dp) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); - assert(c && c->ctrl->generic.type == CTRL_EDITBOX); + assert(c && c->ctrl->type == CTRL_EDITBOX); return GetDlgItemText_alloc(dp->hwnd, c->base_id+1); } @@ -2185,10 +2185,10 @@ void dlg_listbox_clear(dlgcontrol *ctrl, dlgparam *dp) 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->type == CTRL_LISTBOX || + (c->ctrl->type == CTRL_EDITBOX && c->ctrl->editbox.has_list))); - msg = (c->ctrl->generic.type==CTRL_LISTBOX && c->ctrl->listbox.height!=0 ? + msg = (c->ctrl->type==CTRL_LISTBOX && c->ctrl->listbox.height!=0 ? LB_RESETCONTENT : CB_RESETCONTENT); SendDlgItemMessage(dp->hwnd, c->base_id+1, msg, 0, 0); } @@ -2198,10 +2198,10 @@ void dlg_listbox_del(dlgcontrol *ctrl, dlgparam *dp, int index) 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->type == CTRL_LISTBOX || + (c->ctrl->type == CTRL_EDITBOX && c->ctrl->editbox.has_list))); - msg = (c->ctrl->generic.type==CTRL_LISTBOX && c->ctrl->listbox.height!=0 ? + msg = (c->ctrl->type==CTRL_LISTBOX && c->ctrl->listbox.height!=0 ? LB_DELETESTRING : CB_DELETESTRING); SendDlgItemMessage(dp->hwnd, c->base_id+1, msg, index, 0); } @@ -2211,10 +2211,10 @@ void dlg_listbox_add(dlgcontrol *ctrl, dlgparam *dp, char const *text) 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->type == CTRL_LISTBOX || + (c->ctrl->type == CTRL_EDITBOX && c->ctrl->editbox.has_list))); - msg = (c->ctrl->generic.type==CTRL_LISTBOX && c->ctrl->listbox.height!=0 ? + msg = (c->ctrl->type==CTRL_LISTBOX && c->ctrl->listbox.height!=0 ? LB_ADDSTRING : CB_ADDSTRING); SendDlgItemMessage(dp->hwnd, c->base_id+1, msg, 0, (LPARAM)text); } @@ -2232,12 +2232,12 @@ void dlg_listbox_addwithid(dlgcontrol *ctrl, dlgparam *dp, 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->type == CTRL_LISTBOX || + (c->ctrl->type == CTRL_EDITBOX && c->ctrl->editbox.has_list))); - msg = (c->ctrl->generic.type==CTRL_LISTBOX && c->ctrl->listbox.height!=0 ? + msg = (c->ctrl->type==CTRL_LISTBOX && c->ctrl->listbox.height!=0 ? LB_ADDSTRING : CB_ADDSTRING); - msg2 = (c->ctrl->generic.type==CTRL_LISTBOX && c->ctrl->listbox.height!=0 ? + msg2 = (c->ctrl->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); @@ -2247,7 +2247,7 @@ int dlg_listbox_getid(dlgcontrol *ctrl, dlgparam *dp, int index) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); int msg; - assert(c && c->ctrl->generic.type == CTRL_LISTBOX); + assert(c && c->ctrl->type == CTRL_LISTBOX); msg = (c->ctrl->listbox.height != 0 ? LB_GETITEMDATA : CB_GETITEMDATA); return SendDlgItemMessage(dp->hwnd, c->base_id+1, msg, index, 0); @@ -2258,7 +2258,7 @@ int dlg_listbox_index(dlgcontrol *ctrl, dlgparam *dp) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); int msg, ret; - assert(c && c->ctrl->generic.type == CTRL_LISTBOX); + assert(c && c->ctrl->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); @@ -2276,7 +2276,7 @@ int dlg_listbox_index(dlgcontrol *ctrl, dlgparam *dp) bool dlg_listbox_issel(dlgcontrol *ctrl, dlgparam *dp, int index) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); - assert(c && c->ctrl->generic.type == CTRL_LISTBOX && + assert(c && c->ctrl->type == CTRL_LISTBOX && c->ctrl->listbox.multisel && c->ctrl->listbox.height != 0); return @@ -2287,7 +2287,7 @@ void dlg_listbox_select(dlgcontrol *ctrl, dlgparam *dp, int index) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); int msg; - assert(c && c->ctrl->generic.type == CTRL_LISTBOX && + assert(c && c->ctrl->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); @@ -2296,7 +2296,7 @@ void dlg_listbox_select(dlgcontrol *ctrl, dlgparam *dp, int index) void dlg_text_set(dlgcontrol *ctrl, dlgparam *dp, char const *text) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); - assert(c && c->ctrl->generic.type == CTRL_TEXT); + assert(c && c->ctrl->type == CTRL_TEXT); SetDlgItemText(dp->hwnd, c->base_id, text); } @@ -2307,7 +2307,7 @@ void dlg_label_change(dlgcontrol *ctrl, dlgparam *dp, char const *text) int id = -1; assert(c); - switch (c->ctrl->generic.type) { + switch (c->ctrl->type) { case CTRL_EDITBOX: escaped = shortcut_escape(text, c->ctrl->editbox.shortcut); id = c->base_id; @@ -2348,7 +2348,7 @@ void dlg_label_change(dlgcontrol *ctrl, dlgparam *dp, char const *text) void dlg_filesel_set(dlgcontrol *ctrl, dlgparam *dp, Filename *fn) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); - assert(c && c->ctrl->generic.type == CTRL_FILESELECT); + assert(c && c->ctrl->type == CTRL_FILESELECT); SetDlgItemText(dp->hwnd, c->base_id+1, fn->path); } @@ -2357,7 +2357,7 @@ Filename *dlg_filesel_get(dlgcontrol *ctrl, dlgparam *dp) struct winctrl *c = dlg_findbyctrl(dp, ctrl); char *tmp; Filename *ret; - assert(c && c->ctrl->generic.type == CTRL_FILESELECT); + assert(c && c->ctrl->type == CTRL_FILESELECT); tmp = GetDlgItemText_alloc(dp->hwnd, c->base_id+1); ret = filename_from_str(tmp); sfree(tmp); @@ -2368,7 +2368,7 @@ void dlg_fontsel_set(dlgcontrol *ctrl, dlgparam *dp, FontSpec *fs) { char *buf, *boldstr; struct winctrl *c = dlg_findbyctrl(dp, ctrl); - assert(c && c->ctrl->generic.type == CTRL_FONTSELECT); + assert(c && c->ctrl->type == CTRL_FONTSELECT); fontspec_free((FontSpec *)c->data); c->data = fontspec_copy(fs); @@ -2389,7 +2389,7 @@ void dlg_fontsel_set(dlgcontrol *ctrl, dlgparam *dp, FontSpec *fs) FontSpec *dlg_fontsel_get(dlgcontrol *ctrl, dlgparam *dp) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); - assert(c && c->ctrl->generic.type == CTRL_FONTSELECT); + assert(c && c->ctrl->type == CTRL_FONTSELECT); return fontspec_copy((FontSpec *)c->data); } @@ -2401,7 +2401,7 @@ FontSpec *dlg_fontsel_get(dlgcontrol *ctrl, dlgparam *dp) void dlg_update_start(dlgcontrol *ctrl, dlgparam *dp) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); - if (c && c->ctrl->generic.type == CTRL_LISTBOX) { + if (c && c->ctrl->type == CTRL_LISTBOX) { SendDlgItemMessage(dp->hwnd, c->base_id+1, WM_SETREDRAW, false, 0); } } @@ -2409,7 +2409,7 @@ void dlg_update_start(dlgcontrol *ctrl, dlgparam *dp) void dlg_update_done(dlgcontrol *ctrl, dlgparam *dp) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); - if (c && c->ctrl->generic.type == CTRL_LISTBOX) { + if (c && c->ctrl->type == CTRL_LISTBOX) { HWND hw = GetDlgItem(dp->hwnd, c->base_id+1); SendMessage(hw, WM_SETREDRAW, true, 0); InvalidateRect(hw, NULL, true); @@ -2423,7 +2423,7 @@ void dlg_set_focus(dlgcontrol *ctrl, dlgparam *dp) HWND ctl; if (!c) return; - switch (ctrl->generic.type) { + switch (ctrl->type) { case CTRL_EDITBOX: id = c->base_id + 1; break; case CTRL_RADIO: for (id = c->base_id + ctrl->radio.nbuttons; id > 1; id--) @@ -2487,8 +2487,8 @@ void dlg_refresh(dlgcontrol *ctrl, dlgparam *dp) 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, + if (c->ctrl && c->ctrl->handler != NULL) + c->ctrl->handler(c->ctrl, dp, dp->data, EVENT_REFRESH); } } @@ -2496,8 +2496,8 @@ void dlg_refresh(dlgcontrol *ctrl, dlgparam *dp) /* * Send EVENT_REFRESH to a specific control. */ - if (ctrl->generic.handler != NULL) - ctrl->generic.handler(ctrl, dp, dp->data, EVENT_REFRESH); + if (ctrl->handler != NULL) + ctrl->handler(ctrl, dp, dp->data, EVENT_REFRESH); } } -- cgit v1.2.3 From 259e877b92ba81dec58aa4ce6a2bdbe8fc320e46 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 1 May 2022 08:22:44 +0100 Subject: New command-line option: 'putty --host-ca'. This causes PuTTY to bring up just the host CA configuration dialog box, and shut down once that box is dismissed. I can imagine it potentially being useful to users, but in the first instance, I expect it to be useful to _me_, because it will greatly streamline testing changes to the UI of that dialog! --- unix/dialog.c | 21 ++++++++++++++++++--- unix/main-gtk-simple.c | 5 +++++ unix/platform.h | 1 + windows/putty.c | 4 ++++ 4 files changed, 28 insertions(+), 3 deletions(-) diff --git a/unix/dialog.c b/unix/dialog.c index f6c5ab54..1d9944c5 100644 --- a/unix/dialog.c +++ b/unix/dialog.c @@ -4224,6 +4224,7 @@ struct ca_config_box { GtkWidget *window; struct controlbox *cb; struct Shortcuts scs; + bool quit_main; dlgparam dp; }; static struct ca_config_box *cacfg; /* one of these, cross-instance */ @@ -4234,8 +4235,10 @@ static void cacfg_destroy(GtkWidget *widget, gpointer data) dlg_cleanup(&cacfg->dp); ctrl_free_box(cacfg->cb); cacfg->cb = NULL; + if (cacfg->quit_main) + gtk_main_quit(); } -void show_ca_config_box(dlgparam *dp) +static void make_ca_config_box(GtkWidget *spawning_window) { if (!cacfg) { cacfg = snew(struct ca_config_box); @@ -4288,8 +4291,8 @@ void show_ca_config_box(dlgparam *dp) dlg_refresh(NULL, &cacfg->dp); - if (dp) { - set_transient_window_pos(dp->window, cacfg->window); + if (spawning_window) { + set_transient_window_pos(spawning_window, cacfg->window); } else { gtk_window_set_position(GTK_WINDOW(cacfg->window), GTK_WIN_POS_CENTER); } @@ -4300,3 +4303,15 @@ void show_ca_config_box(dlgparam *dp) g_signal_connect(G_OBJECT(cacfg->window), "key_press_event", G_CALLBACK(win_key_press), &cacfg->dp); } + +void show_ca_config_box(dlgparam *dp) +{ + make_ca_config_box(dp ? dp->window : NULL); +} + +void show_ca_config_box_synchronously(void) +{ + make_ca_config_box(NULL); + cacfg->quit_main = true; + gtk_main(); +} diff --git a/unix/main-gtk-simple.c b/unix/main-gtk-simple.c index e52a18d0..b08a0acc 100644 --- a/unix/main-gtk-simple.c +++ b/unix/main-gtk-simple.c @@ -532,6 +532,11 @@ bool do_cmdline(int argc, char **argv, bool do_everything, Conf *conf) pgp_fingerprints(); exit(1); + } else if (!strcmp(p, "-host-ca") || !strcmp(p, "--host-ca") || + !strcmp(p, "-host_ca") || !strcmp(p, "--host_ca")) { + show_ca_config_box_synchronously(); + exit(0); + } else if (p[0] != '-') { /* Non-option arguments not handled by cmdline.c are errors. */ if (do_everything) { diff --git a/unix/platform.h b/unix/platform.h index 7cdf4d11..6a8c7ba3 100644 --- a/unix/platform.h +++ b/unix/platform.h @@ -245,6 +245,7 @@ GtkWidget *create_message_box( bool selectable, const struct message_box_buttons *buttons, post_dialog_fn_t after, void *afterctx); #endif +void show_ca_config_box_synchronously(void); /* window.c needs this special function in utils */ int keysym_to_unicode(int keysym); diff --git a/windows/putty.c b/windows/putty.c index 83594d61..fca1d4bb 100644 --- a/windows/putty.c +++ b/windows/putty.c @@ -87,6 +87,10 @@ void gui_term_process_cmdline(Conf *conf, char *cmdline) } else if (!strcmp(p, "-pgpfp")) { pgp_fingerprints_msgbox(NULL); exit(1); + } else if (!strcmp(p, "-host-ca") || !strcmp(p, "--host-ca") || + !strcmp(p, "-host_ca") || !strcmp(p, "--host_ca")) { + show_ca_config_box(NULL); + exit(0); } else if (!strcmp(p, "-demo-config-box")) { if (i+1 >= argc) { cmdline_error("%s expects an output filename", p); -- cgit v1.2.3 From 694d5184b72505a6e22f6c359db882789cb9ec7b Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 1 May 2022 08:39:49 +0100 Subject: Permit button-only file selectors. Instead of an edit box together with a Browse button that pops up a sub-dialog, this is _just_ the browse button, only now it has a user-defined title. I'm about to want to use this for loading CA public keys from files. --- dialog.h | 31 +++++++++++++ unix/dialog.c | 127 ++++++++++++++++++++++++++++++++--------------------- windows/controls.c | 57 +++++++++++++++++------- 3 files changed, 150 insertions(+), 65 deletions(-) diff --git a/dialog.h b/dialog.h index 51d481fa..b85bdcd5 100644 --- a/dialog.h +++ b/dialog.h @@ -369,6 +369,37 @@ struct dlgcontrol { * This value _is_ expected to require freeing. */ char *title; + /* + * Reduce the file selector to just a single browse + * button. + * + * Normally, a file selector is used to set a config + * option that consists of a file name, so that that file + * will be read or written at run time. In that situation, + * it makes sense to have an edit box showing the + * currently selected file name, and a button to change it + * interactively. + * + * But occasionally a file selector is used to load a file + * _during_ configuration. For example, host CA public + * keys are entered directly into the configuration as + * strings, not stored by reference to a filename; but if + * you have one in a file, you want to be able to load it + * during the lifetime of the CA config box rather than + * awkwardly copy-pasting it. So in that case you just + * want a 'pop up a file chooser' button, and when that + * delivers a file name, you'll deal with it there and + * then and write some other thing (like the file's + * contents) into a nearby edit box. + * + * If you set this flag, then you may not call + * dlg_filesel_set on the file selector at all, because it + * doesn't store a filename. And you can only call + * dlg_filesel_get on it in the handler for EVENT_ACTION, + * which is what will be sent to you when the user has + * used it to choose a filename. + */ + bool just_button; } fileselect; struct { /* for CTRL_COLUMNS */ /* In this variant, `label' MUST be NULL. */ diff --git a/unix/dialog.c b/unix/dialog.c index 1d9944c5..bd5d535f 100644 --- a/unix/dialog.c +++ b/unix/dialog.c @@ -74,6 +74,7 @@ struct uctrl { guint entrysig; guint textsig; int nclicks; + const char *textvalue; /* temporary, for button-only file selectors */ }; struct dlgparam { @@ -869,8 +870,10 @@ void dlg_label_change(dlgcontrol *ctrl, dlgparam *dp, char const *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); + if (uc->label) { + 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); @@ -901,8 +904,12 @@ Filename *dlg_filesel_get(dlgcontrol *ctrl, dlgparam *dp) { struct uctrl *uc = dlg_find_byctrl(dp, ctrl); assert(uc->ctrl->type == CTRL_FILESELECT); - assert(uc->entry != NULL); - return filename_from_str(gtk_entry_get_text(GTK_ENTRY(uc->entry))); + if (!uc->entry) { + assert(uc->textvalue); + return filename_from_str(uc->textvalue); + } else { + return filename_from_str(gtk_entry_get_text(GTK_ENTRY(uc->entry))); + } } void dlg_fontsel_set(dlgcontrol *ctrl, dlgparam *dp, FontSpec *fs) @@ -1562,15 +1569,27 @@ static void droplist_selchange(GtkComboBox *combo, gpointer data) #endif /* !GTK_CHECK_VERSION(2,4,0) */ +static void filechoose_emit_value(struct dlgparam *dp, struct uctrl *uc, + const char *name) +{ + if (uc->entry) { + gtk_entry_set_text(GTK_ENTRY(uc->entry), name); + } else { + uc->textvalue = name; + uc->ctrl->handler(uc->ctrl, dp, dp->data, EVENT_ACTION); + uc->textvalue = NULL; + } +} + #ifdef USE_GTK_FILE_CHOOSER_DIALOG static void filechoose_response(GtkDialog *dialog, gint response, gpointer data) { - /* struct dlgparam *dp = (struct dlgparam *)data; */ + struct dlgparam *dp = (struct dlgparam *)data; struct uctrl *uc = g_object_get_data(G_OBJECT(dialog), "user-data"); if (response == GTK_RESPONSE_ACCEPT) { gchar *name = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); - gtk_entry_set_text(GTK_ENTRY(uc->entry), name); + filechoose_emit_value(dp, uc, name); g_free(name); } gtk_widget_destroy(GTK_WIDGET(dialog)); @@ -1578,12 +1597,12 @@ static void filechoose_response(GtkDialog *dialog, gint response, #else static void filesel_ok(GtkButton *button, gpointer data) { - /* struct dlgparam *dp = (struct dlgparam *)data; */ + struct dlgparam *dp = (struct dlgparam *)data; gpointer filesel = g_object_get_data(G_OBJECT(button), "user-data"); struct uctrl *uc = g_object_get_data(G_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); + filechoose_emit_value(dp, uc, name); } #endif @@ -2104,56 +2123,65 @@ GtkWidget *layout_ctrls( case CTRL_FILESELECT: case CTRL_FONTSELECT: { GtkWidget *ww; - const char *browsebtn = - (ctrl->type == CTRL_FILESELECT ? - "Browse..." : "Change..."); - - gint percentages[] = { 75, 25 }; - w = columns_new(4); - columns_set_cols(COLUMNS(w), 2, percentages); - if (ctrl->label) { - ww = gtk_label_new(ctrl->label); - columns_add(COLUMNS(w), ww, 0, 2); - columns_force_left_align(COLUMNS(w), ww); - gtk_widget_show(ww); - shortcut_add(scs, ww, - (ctrl->type == CTRL_FILESELECT ? - ctrl->fileselect.shortcut : - ctrl->fontselect.shortcut), - SHORTCUT_UCTRL, uc); - uc->label = ww; - } + if (!ctrl->fileselect.just_button) { + const char *browsebtn = + (ctrl->type == CTRL_FILESELECT ? + "Browse..." : "Change..."); + + gint percentages[] = { 75, 25 }; + w = columns_new(4); + columns_set_cols(COLUMNS(w), 2, percentages); + + if (ctrl->label) { + ww = gtk_label_new(ctrl->label); + columns_add(COLUMNS(w), ww, 0, 2); + columns_force_left_align(COLUMNS(w), ww); + gtk_widget_show(ww); + shortcut_add(scs, ww, + (ctrl->type == CTRL_FILESELECT ? + ctrl->fileselect.shortcut : + ctrl->fontselect.shortcut), + SHORTCUT_UCTRL, uc); + uc->label = ww; + } - uc->entry = ww = gtk_entry_new(); + uc->entry = ww = gtk_entry_new(); #if !GTK_CHECK_VERSION(3,0,0) - { - GtkRequisition req; - gtk_widget_size_request(ww, &req); - gtk_widget_set_size_request(ww, 10, req.height); - } + { + GtkRequisition req; + gtk_widget_size_request(ww, &req); + gtk_widget_set_size_request(ww, 10, req.height); + } #else - gtk_entry_set_width_chars(GTK_ENTRY(ww), 1); + gtk_entry_set_width_chars(GTK_ENTRY(ww), 1); #endif - columns_add(COLUMNS(w), ww, 0, 1); - gtk_widget_show(ww); + 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); + uc->button = ww = gtk_button_new_with_label(browsebtn); + columns_add(COLUMNS(w), ww, 1, 1); + gtk_widget_show(ww); - columns_force_same_height(COLUMNS(w), uc->entry, uc->button); + columns_force_same_height(COLUMNS(w), uc->entry, uc->button); - g_signal_connect(G_OBJECT(uc->entry), "key_press_event", - G_CALLBACK(editbox_key), dp); - uc->entrysig = - g_signal_connect(G_OBJECT(uc->entry), "changed", - G_CALLBACK(editbox_changed), dp); - g_signal_connect(G_OBJECT(uc->entry), "focus_in_event", - G_CALLBACK(widget_focus), dp); + g_signal_connect(G_OBJECT(uc->entry), "key_press_event", + G_CALLBACK(editbox_key), dp); + uc->entrysig = + g_signal_connect(G_OBJECT(uc->entry), "changed", + G_CALLBACK(editbox_changed), dp); + g_signal_connect(G_OBJECT(uc->entry), "focus_in_event", + G_CALLBACK(widget_focus), dp); + } else { + uc->button = w = gtk_button_new_with_label(ctrl->label); + shortcut_add(scs, gtk_bin_get_child(GTK_BIN(w)), + ctrl->fileselect.shortcut, SHORTCUT_UCTRL, uc); + gtk_widget_show(w); + + } g_signal_connect(G_OBJECT(uc->button), "focus_in_event", G_CALLBACK(widget_focus), dp); - g_signal_connect(G_OBJECT(ww), "clicked", + g_signal_connect(G_OBJECT(uc->button), "clicked", G_CALLBACK(filefont_clicked), dp); break; } @@ -2669,7 +2697,8 @@ gint win_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data) /* File/font selectors have their buttons pressed (ooer), * and focus transferred to the edit box. */ g_signal_emit_by_name(G_OBJECT(sc->uc->button), "clicked"); - gtk_widget_grab_focus(sc->uc->entry); + if (sc->uc->entry) + gtk_widget_grab_focus(sc->uc->entry); break; case CTRL_RADIO: /* diff --git a/windows/controls.c b/windows/controls.c index d458496c..e82d215d 100644 --- a/windows/controls.c +++ b/windows/controls.c @@ -1663,12 +1663,15 @@ void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, sfree(escaped); break; case CTRL_FILESELECT: - num_ids = 3; - escaped = shortcut_escape(ctrl->label, - ctrl->fileselect.shortcut); + escaped = shortcut_escape(ctrl->label, ctrl->fileselect.shortcut); shortcuts[nshortcuts++] = ctrl->fileselect.shortcut; - editbutton(&pos, escaped, base_id, base_id+1, - "Browse...", base_id+2); + num_ids = 3; + if (!ctrl->fileselect.just_button) { + editbutton(&pos, escaped, base_id, base_id+1, + "Browse...", base_id+2); + } else { + button(&pos, escaped, base_id+2, false); + } sfree(escaped); break; case CTRL_FONTSELECT: @@ -1980,15 +1983,27 @@ bool winctrl_handle_command(struct dlgparam *dp, UINT msg, of.lpstrCustomFilter = NULL; of.nFilterIndex = 1; of.lpstrFile = filename; - GetDlgItemText(dp->hwnd, c->base_id+1, filename, lenof(filename)); - filename[lenof(filename)-1] = '\0'; + if (!ctrl->fileselect.just_button) { + GetDlgItemText(dp->hwnd, c->base_id+1, + filename, lenof(filename)); + filename[lenof(filename)-1] = '\0'; + } else { + *filename = '\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->handler(ctrl, dp, dp->data, EVENT_VALCHANGE); + if (!ctrl->fileselect.just_button) { + SetDlgItemText(dp->hwnd, c->base_id + 1, filename); + ctrl->handler(ctrl, dp, dp->data, EVENT_VALCHANGE); + } else { + assert(!c->data); + c->data = filename; + ctrl->handler(ctrl, dp, dp->data, EVENT_ACTION); + c->data = NULL; + } } } break; @@ -2330,7 +2345,10 @@ void dlg_label_change(dlgcontrol *ctrl, dlgparam *dp, char const *text) break; case CTRL_FILESELECT: escaped = shortcut_escape(text, ctrl->fileselect.shortcut); - id = c->base_id; + if (ctrl->fileselect.just_button) + id = c->base_id + 2; /* the button */ + else + id = c->base_id; /* the label */ break; case CTRL_FONTSELECT: escaped = shortcut_escape(text, ctrl->fontselect.shortcut); @@ -2348,7 +2366,9 @@ void dlg_label_change(dlgcontrol *ctrl, dlgparam *dp, char const *text) void dlg_filesel_set(dlgcontrol *ctrl, dlgparam *dp, Filename *fn) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); - assert(c && c->ctrl->type == CTRL_FILESELECT); + assert(c); + assert(c->ctrl->type == CTRL_FILESELECT); + assert(!c->ctrl->fileselect.just_button); SetDlgItemText(dp->hwnd, c->base_id+1, fn->path); } @@ -2357,11 +2377,16 @@ Filename *dlg_filesel_get(dlgcontrol *ctrl, dlgparam *dp) struct winctrl *c = dlg_findbyctrl(dp, ctrl); char *tmp; Filename *ret; - assert(c && c->ctrl->type == CTRL_FILESELECT); - tmp = GetDlgItemText_alloc(dp->hwnd, c->base_id+1); - ret = filename_from_str(tmp); - sfree(tmp); - return ret; + assert(c); + assert(c->ctrl->type == CTRL_FILESELECT); + if (!c->ctrl->fileselect.just_button) { + tmp = GetDlgItemText_alloc(dp->hwnd, c->base_id+1); + ret = filename_from_str(tmp); + sfree(tmp); + return ret; + } else { + return filename_from_str(c->data); + } } void dlg_fontsel_set(dlgcontrol *ctrl, dlgparam *dp, FontSpec *fs) -- cgit v1.2.3 From 4fcb3bbe818b681edb49caa3264106238b889368 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 1 May 2022 09:16:46 +0100 Subject: Move host CA config box out into its own source file. In the course of polishing up this dialog box, I'm going to want it to actually do cryptographic things (such as checking validity of a public key blob and printing its fingerprint), which means it will need to link against SSH utility functions. So I've moved the dialog-box setup and handling code out of config.c into a new file in the ssh subdirectory and in the ssh library, where those facilities will be conveniently available. This also means that dialog-box setup code _won't_ be linked into PuTTYtel or pterm (on either platform), so I've added a stub source file to provide its entry-point function in those tools. Also, provided a const bool to indicate whether that dialog is available, which we use to decide whether to recognise that command-line option. --- config.c | 361 ----------------------------------------------- putty.h | 1 + ssh/CMakeLists.txt | 1 + ssh/ca-config.c | 372 +++++++++++++++++++++++++++++++++++++++++++++++++ stubs/no-ca-config.c | 14 ++ unix/CMakeLists.txt | 3 + unix/main-gtk-simple.c | 5 +- windows/CMakeLists.txt | 2 + windows/putty.c | 5 +- 9 files changed, 399 insertions(+), 365 deletions(-) create mode 100644 ssh/ca-config.c create mode 100644 stubs/no-ca-config.c diff --git a/config.c b/config.c index 062fe9b6..94b434a7 100644 --- a/config.c +++ b/config.c @@ -3316,364 +3316,3 @@ void setup_config_box(struct controlbox *b, bool midsession, I(CONF_supdup_scroll)); } } - -struct ca_state { - dlgcontrol *ca_name_edit; - dlgcontrol *ca_reclist; - dlgcontrol *ca_pubkey_edit; - dlgcontrol *ca_wclist; - dlgcontrol *ca_wc_edit; - char *name, *pubkey, *wc; - tree234 *ca_names; /* stores plain 'char *' */ - tree234 *host_wcs; /* stores plain 'char *' */ -}; - -static int ca_name_compare(void *av, void *bv) -{ - return strcmp((const char *)av, (const char *)bv); -} - -static inline void clear_string_tree(tree234 *t) -{ - char *p; - while ((p = delpos234(t, 0)) != NULL) - sfree(p); -} - -static void ca_state_free(void *vctx) -{ - struct ca_state *st = (struct ca_state *)vctx; - clear_string_tree(st->ca_names); - freetree234(st->ca_names); - clear_string_tree(st->host_wcs); - freetree234(st->host_wcs); - sfree(st->name); - sfree(st->wc); - sfree(st); -} - -static void ca_refresh_name_list(struct ca_state *st) -{ - clear_string_tree(st->ca_names); - - host_ca_enum *hce = enum_host_ca_start(); - if (hce) { - strbuf *namebuf = strbuf_new(); - - while (strbuf_clear(namebuf), enum_host_ca_next(hce, namebuf)) { - char *name = dupstr(namebuf->s); - char *added = add234(st->ca_names, name); - /* Just imaginable that concurrent filesystem access might - * cause a repetition; avoid leaking memory if so */ - if (added != name) - sfree(name); - } - - strbuf_free(namebuf); - enum_host_ca_finish(hce); - } -} - -static void ca_load_selected_record(struct ca_state *st, dlgparam *dp) -{ - int i = dlg_listbox_index(st->ca_reclist, dp); - if (i < 0) { - dlg_beep(dp); - return; - } - const char *name = index234(st->ca_names, i); - if (!name) { /* in case the list box and the tree got out of sync */ - dlg_beep(dp); - return; - } - host_ca *hca = host_ca_load(name); - if (!hca) { - char *msg = dupprintf("Unable to load host CA record '%s'", name); - dlg_error_msg(dp, msg); - sfree(msg); - return; - } - - sfree(st->name); - st->name = dupstr(hca->name); - - sfree(st->pubkey); - st->pubkey = strbuf_to_str( - base64_encode_sb(ptrlen_from_strbuf(hca->ca_public_key), 0)); - - clear_string_tree(st->host_wcs); - for (size_t i = 0; i < hca->n_hostname_wildcards; i++) { - char *name = dupstr(hca->hostname_wildcards[i]); - char *added = add234(st->host_wcs, name); - if (added != name) - sfree(name); /* de-duplicate, just in case */ - } - - host_ca_free(hca); - - dlg_refresh(st->ca_name_edit, dp); - dlg_refresh(st->ca_pubkey_edit, dp); - dlg_refresh(st->ca_wclist, dp); -} - -static void ca_ok_handler(dlgcontrol *ctrl, dlgparam *dp, - void *data, int event) -{ - if (event == EVENT_ACTION) - dlg_end(dp, 0); -} - -static void ca_name_handler(dlgcontrol *ctrl, dlgparam *dp, - void *data, int event) -{ - struct ca_state *st = (struct ca_state *)ctrl->context.p; - if (event == EVENT_REFRESH) { - dlg_editbox_set(ctrl, dp, st->name); - } else if (event == EVENT_VALCHANGE) { - sfree(st->name); - st->name = dlg_editbox_get(ctrl, dp); - - /* - * Try to auto-select the typed name in the list. - */ - int index; - if (!findrelpos234(st->ca_names, st->name, NULL, REL234_GE, &index)) - index = count234(st->ca_names) - 1; - if (index >= 0) - dlg_listbox_select(st->ca_reclist, dp, index); - } -} - -static void ca_reclist_handler(dlgcontrol *ctrl, dlgparam *dp, - void *data, int event) -{ - struct ca_state *st = (struct ca_state *)ctrl->context.p; - if (event == EVENT_REFRESH) { - dlg_update_start(ctrl, dp); - dlg_listbox_clear(ctrl, dp); - const char *name; - for (int i = 0; (name = index234(st->ca_names, i)) != NULL; i++) - dlg_listbox_add(ctrl, dp, name); - dlg_update_done(ctrl, dp); - } else if (event == EVENT_ACTION) { - /* Double-clicking a session loads it */ - ca_load_selected_record(st, dp); - } -} - -static void ca_load_handler(dlgcontrol *ctrl, dlgparam *dp, - void *data, int event) -{ - struct ca_state *st = (struct ca_state *)ctrl->context.p; - if (event == EVENT_ACTION) { - ca_load_selected_record(st, dp); - } -} - -static void ca_save_handler(dlgcontrol *ctrl, dlgparam *dp, - void *data, int event) -{ - struct ca_state *st = (struct ca_state *)ctrl->context.p; - if (event == EVENT_ACTION) { - host_ca *hca = snew(host_ca); - memset(hca, 0, sizeof(*hca)); - hca->name = dupstr(st->name); - hca->ca_public_key = base64_decode_sb(ptrlen_from_asciz(st->pubkey)); - hca->n_hostname_wildcards = count234(st->host_wcs); - hca->hostname_wildcards = snewn(hca->n_hostname_wildcards, char *); - for (size_t i = 0; i < hca->n_hostname_wildcards; i++) - hca->hostname_wildcards[i] = dupstr(index234(st->host_wcs, i)); - char *error = host_ca_save(hca); - host_ca_free(hca); - - if (error) { - dlg_error_msg(dp, error); - sfree(error); - } else { - ca_refresh_name_list(st); - dlg_refresh(st->ca_reclist, dp); - } - } -} - -static void ca_delete_handler(dlgcontrol *ctrl, dlgparam *dp, - void *data, int event) -{ - struct ca_state *st = (struct ca_state *)ctrl->context.p; - if (event == EVENT_ACTION) { - int i = dlg_listbox_index(st->ca_reclist, dp); - if (i < 0) { - dlg_beep(dp); - return; - } - const char *name = index234(st->ca_names, i); - if (!name) { /* in case the list box and the tree got out of sync */ - dlg_beep(dp); - return; - } - - char *error = host_ca_delete(name); - if (error) { - dlg_error_msg(dp, error); - sfree(error); - } else { - ca_refresh_name_list(st); - dlg_refresh(st->ca_reclist, dp); - } - } -} - -static void ca_pubkey_handler(dlgcontrol *ctrl, dlgparam *dp, - void *data, int event) -{ - struct ca_state *st = (struct ca_state *)ctrl->context.p; - if (event == EVENT_REFRESH) { - dlg_editbox_set(ctrl, dp, st->pubkey); - } else if (event == EVENT_VALCHANGE) { - sfree(st->pubkey); - st->pubkey = dlg_editbox_get(ctrl, dp); - } -} - -static void ca_wclist_handler(dlgcontrol *ctrl, dlgparam *dp, - void *data, int event) -{ - struct ca_state *st = (struct ca_state *)ctrl->context.p; - if (event == EVENT_REFRESH) { - dlg_update_start(ctrl, dp); - dlg_listbox_clear(ctrl, dp); - const char *name; - for (int i = 0; (name = index234(st->host_wcs, i)) != NULL; i++) - dlg_listbox_add(ctrl, dp, name); - dlg_update_done(ctrl, dp); - } -} - -static void ca_wc_edit_handler(dlgcontrol *ctrl, dlgparam *dp, - void *data, int event) -{ - struct ca_state *st = (struct ca_state *)ctrl->context.p; - if (event == EVENT_REFRESH) { - dlg_editbox_set(ctrl, dp, st->wc); - } else if (event == EVENT_VALCHANGE) { - sfree(st->wc); - st->wc = dlg_editbox_get(ctrl, dp); - } -} - -static void ca_wc_add_handler(dlgcontrol *ctrl, dlgparam *dp, - void *data, int event) -{ - struct ca_state *st = (struct ca_state *)ctrl->context.p; - if (event == EVENT_ACTION) { - if (!st->wc) { - dlg_beep(dp); - return; - } - - if (add234(st->host_wcs, st->wc) == st->wc) { - dlg_refresh(st->ca_wclist, dp); - } else { - sfree(st->wc); - } - - st->wc = dupstr(""); - dlg_refresh(st->ca_wc_edit, dp); - } -} - -static void ca_wc_rem_handler(dlgcontrol *ctrl, dlgparam *dp, - void *data, int event) -{ - struct ca_state *st = (struct ca_state *)ctrl->context.p; - if (event == EVENT_ACTION) { - int i = dlg_listbox_index(st->ca_wclist, dp); - if (i < 0) { - dlg_beep(dp); - return; - } - char *wc = delpos234(st->host_wcs, i); - if (!wc) { - dlg_beep(dp); - return; - } - - sfree(st->wc); - st->wc = wc; - dlg_refresh(st->ca_wclist, dp); - dlg_refresh(st->ca_wc_edit, dp); - } -} - -void setup_ca_config_box(struct controlbox *b) -{ - struct controlset *s; - dlgcontrol *c; - - /* Internal state for manipulating the host CA system */ - struct ca_state *st = (struct ca_state *)ctrl_alloc_with_free( - b, sizeof(struct ca_state), ca_state_free); - memset(st, 0, sizeof(*st)); - st->name = dupstr(""); - st->pubkey = dupstr(""); - st->ca_names = newtree234(ca_name_compare); - st->host_wcs = newtree234(ca_name_compare); - ca_refresh_name_list(st); - - /* Action area, with the Done button in it */ - s = ctrl_getset(b, "", "", ""); - ctrl_columns(s, 5, 20, 20, 20, 20, 20); - c = ctrl_pushbutton(s, "Done", 'o', HELPCTX(no_help), - ca_ok_handler, P(st)); - c->button.isdefault = true; - c->column = 4; - - /* Load/save box, as similar as possible to the main saved sessions one */ - s = ctrl_getset(b, "Main", "loadsave", - "Load, save or delete a host CA record"); - ctrl_columns(s, 2, 75, 25); - c = ctrl_editbox(s, "Name for this CA (shown in log messages)", - 'n', 100, HELPCTX(no_help), - ca_name_handler, P(st), P(NULL)); - c->column = 0; - st->ca_name_edit = c; - /* 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); - c = ctrl_listbox(s, NULL, NO_SHORTCUT, HELPCTX(no_help), - ca_reclist_handler, P(st)); - c->column = 0; - c->listbox.height = 6; - st->ca_reclist = c; - c = ctrl_pushbutton(s, "Load", 'l', HELPCTX(no_help), - ca_load_handler, P(st)); - c->column = 1; - c = ctrl_pushbutton(s, "Save", 'v', HELPCTX(no_help), - ca_save_handler, P(st)); - c->column = 1; - c = ctrl_pushbutton(s, "Delete", 'd', HELPCTX(no_help), - ca_delete_handler, P(st)); - c->column = 1; - - /* Box containing the details of a specific CA record */ - s = ctrl_getset(b, "Main", "details", "Details of a host CA record"); - c = ctrl_editbox(s, "Public key of certification authority", 'k', 100, - HELPCTX(no_help), ca_pubkey_handler, P(st), P(NULL)); - st->ca_pubkey_edit = c; - c = ctrl_listbox(s, "Hostname patterns this key is trusted to certify", - NO_SHORTCUT, HELPCTX(no_help), ca_wclist_handler, P(st)); - c->listbox.height = 3; - st->ca_wclist = c; - ctrl_columns(s, 3, 70, 15, 15); - c = ctrl_editbox(s, "Hostname pattern to add", 'h', 100, - HELPCTX(no_help), ca_wc_edit_handler, P(st), P(NULL)); - c->column = 0; - st->ca_wc_edit = c; - c = ctrl_pushbutton(s, "Add", NO_SHORTCUT, HELPCTX(no_help), - ca_wc_add_handler, P(st)); - c->column = 1; - c = ctrl_pushbutton(s, "Remove", NO_SHORTCUT, HELPCTX(no_help), - ca_wc_rem_handler, P(st)); - c->column = 2; -} diff --git a/putty.h b/putty.h index fd5eea44..e16a5d04 100644 --- a/putty.h +++ b/putty.h @@ -2589,6 +2589,7 @@ void setup_ca_config_box(struct controlbox *b); /* Platforms provide this to be called from config.c */ void show_ca_config_box(dlgparam *dlg); +extern const bool has_ca_config_box; /* false if, e.g., we're PuTTYtel */ /* Visible outside config.c so that platforms can use it to recognise * the proxy type control */ diff --git a/ssh/CMakeLists.txt b/ssh/CMakeLists.txt index 4b0e03fe..d2b35311 100644 --- a/ssh/CMakeLists.txt +++ b/ssh/CMakeLists.txt @@ -2,6 +2,7 @@ add_library(sshcommon OBJECT bpp1.c bpp2.c bpp-bare.c + ca-config.c censor1.c censor2.c common.c diff --git a/ssh/ca-config.c b/ssh/ca-config.c new file mode 100644 index 00000000..cc902db9 --- /dev/null +++ b/ssh/ca-config.c @@ -0,0 +1,372 @@ +/* + * Define and handle the configuration dialog box for SSH host CAs, + * using the same portable dialog specification API as config.c. + */ + +#include "putty.h" +#include "dialog.h" +#include "storage.h" +#include "tree234.h" + +const bool has_ca_config_box = true; + +struct ca_state { + dlgcontrol *ca_name_edit; + dlgcontrol *ca_reclist; + dlgcontrol *ca_pubkey_edit; + dlgcontrol *ca_wclist; + dlgcontrol *ca_wc_edit; + char *name, *pubkey, *wc; + tree234 *ca_names; /* stores plain 'char *' */ + tree234 *host_wcs; /* stores plain 'char *' */ +}; + +static int ca_name_compare(void *av, void *bv) +{ + return strcmp((const char *)av, (const char *)bv); +} + +static inline void clear_string_tree(tree234 *t) +{ + char *p; + while ((p = delpos234(t, 0)) != NULL) + sfree(p); +} + +static void ca_state_free(void *vctx) +{ + struct ca_state *st = (struct ca_state *)vctx; + clear_string_tree(st->ca_names); + freetree234(st->ca_names); + clear_string_tree(st->host_wcs); + freetree234(st->host_wcs); + sfree(st->name); + sfree(st->wc); + sfree(st); +} + +static void ca_refresh_name_list(struct ca_state *st) +{ + clear_string_tree(st->ca_names); + + host_ca_enum *hce = enum_host_ca_start(); + if (hce) { + strbuf *namebuf = strbuf_new(); + + while (strbuf_clear(namebuf), enum_host_ca_next(hce, namebuf)) { + char *name = dupstr(namebuf->s); + char *added = add234(st->ca_names, name); + /* Just imaginable that concurrent filesystem access might + * cause a repetition; avoid leaking memory if so */ + if (added != name) + sfree(name); + } + + strbuf_free(namebuf); + enum_host_ca_finish(hce); + } +} + +static void ca_load_selected_record(struct ca_state *st, dlgparam *dp) +{ + int i = dlg_listbox_index(st->ca_reclist, dp); + if (i < 0) { + dlg_beep(dp); + return; + } + const char *name = index234(st->ca_names, i); + if (!name) { /* in case the list box and the tree got out of sync */ + dlg_beep(dp); + return; + } + host_ca *hca = host_ca_load(name); + if (!hca) { + char *msg = dupprintf("Unable to load host CA record '%s'", name); + dlg_error_msg(dp, msg); + sfree(msg); + return; + } + + sfree(st->name); + st->name = dupstr(hca->name); + + sfree(st->pubkey); + st->pubkey = strbuf_to_str( + base64_encode_sb(ptrlen_from_strbuf(hca->ca_public_key), 0)); + + clear_string_tree(st->host_wcs); + for (size_t i = 0; i < hca->n_hostname_wildcards; i++) { + char *name = dupstr(hca->hostname_wildcards[i]); + char *added = add234(st->host_wcs, name); + if (added != name) + sfree(name); /* de-duplicate, just in case */ + } + + host_ca_free(hca); + + dlg_refresh(st->ca_name_edit, dp); + dlg_refresh(st->ca_pubkey_edit, dp); + dlg_refresh(st->ca_wclist, dp); +} + +static void ca_ok_handler(dlgcontrol *ctrl, dlgparam *dp, + void *data, int event) +{ + if (event == EVENT_ACTION) + dlg_end(dp, 0); +} + +static void ca_name_handler(dlgcontrol *ctrl, dlgparam *dp, + void *data, int event) +{ + struct ca_state *st = (struct ca_state *)ctrl->context.p; + if (event == EVENT_REFRESH) { + dlg_editbox_set(ctrl, dp, st->name); + } else if (event == EVENT_VALCHANGE) { + sfree(st->name); + st->name = dlg_editbox_get(ctrl, dp); + + /* + * Try to auto-select the typed name in the list. + */ + int index; + if (!findrelpos234(st->ca_names, st->name, NULL, REL234_GE, &index)) + index = count234(st->ca_names) - 1; + if (index >= 0) + dlg_listbox_select(st->ca_reclist, dp, index); + } +} + +static void ca_reclist_handler(dlgcontrol *ctrl, dlgparam *dp, + void *data, int event) +{ + struct ca_state *st = (struct ca_state *)ctrl->context.p; + if (event == EVENT_REFRESH) { + dlg_update_start(ctrl, dp); + dlg_listbox_clear(ctrl, dp); + const char *name; + for (int i = 0; (name = index234(st->ca_names, i)) != NULL; i++) + dlg_listbox_add(ctrl, dp, name); + dlg_update_done(ctrl, dp); + } else if (event == EVENT_ACTION) { + /* Double-clicking a session loads it */ + ca_load_selected_record(st, dp); + } +} + +static void ca_load_handler(dlgcontrol *ctrl, dlgparam *dp, + void *data, int event) +{ + struct ca_state *st = (struct ca_state *)ctrl->context.p; + if (event == EVENT_ACTION) { + ca_load_selected_record(st, dp); + } +} + +static void ca_save_handler(dlgcontrol *ctrl, dlgparam *dp, + void *data, int event) +{ + struct ca_state *st = (struct ca_state *)ctrl->context.p; + if (event == EVENT_ACTION) { + host_ca *hca = snew(host_ca); + memset(hca, 0, sizeof(*hca)); + hca->name = dupstr(st->name); + hca->ca_public_key = base64_decode_sb(ptrlen_from_asciz(st->pubkey)); + hca->n_hostname_wildcards = count234(st->host_wcs); + hca->hostname_wildcards = snewn(hca->n_hostname_wildcards, char *); + for (size_t i = 0; i < hca->n_hostname_wildcards; i++) + hca->hostname_wildcards[i] = dupstr(index234(st->host_wcs, i)); + char *error = host_ca_save(hca); + host_ca_free(hca); + + if (error) { + dlg_error_msg(dp, error); + sfree(error); + } else { + ca_refresh_name_list(st); + dlg_refresh(st->ca_reclist, dp); + } + } +} + +static void ca_delete_handler(dlgcontrol *ctrl, dlgparam *dp, + void *data, int event) +{ + struct ca_state *st = (struct ca_state *)ctrl->context.p; + if (event == EVENT_ACTION) { + int i = dlg_listbox_index(st->ca_reclist, dp); + if (i < 0) { + dlg_beep(dp); + return; + } + const char *name = index234(st->ca_names, i); + if (!name) { /* in case the list box and the tree got out of sync */ + dlg_beep(dp); + return; + } + + char *error = host_ca_delete(name); + if (error) { + dlg_error_msg(dp, error); + sfree(error); + } else { + ca_refresh_name_list(st); + dlg_refresh(st->ca_reclist, dp); + } + } +} + +static void ca_pubkey_handler(dlgcontrol *ctrl, dlgparam *dp, + void *data, int event) +{ + struct ca_state *st = (struct ca_state *)ctrl->context.p; + if (event == EVENT_REFRESH) { + dlg_editbox_set(ctrl, dp, st->pubkey); + } else if (event == EVENT_VALCHANGE) { + sfree(st->pubkey); + st->pubkey = dlg_editbox_get(ctrl, dp); + } +} + +static void ca_wclist_handler(dlgcontrol *ctrl, dlgparam *dp, + void *data, int event) +{ + struct ca_state *st = (struct ca_state *)ctrl->context.p; + if (event == EVENT_REFRESH) { + dlg_update_start(ctrl, dp); + dlg_listbox_clear(ctrl, dp); + const char *name; + for (int i = 0; (name = index234(st->host_wcs, i)) != NULL; i++) + dlg_listbox_add(ctrl, dp, name); + dlg_update_done(ctrl, dp); + } +} + +static void ca_wc_edit_handler(dlgcontrol *ctrl, dlgparam *dp, + void *data, int event) +{ + struct ca_state *st = (struct ca_state *)ctrl->context.p; + if (event == EVENT_REFRESH) { + dlg_editbox_set(ctrl, dp, st->wc); + } else if (event == EVENT_VALCHANGE) { + sfree(st->wc); + st->wc = dlg_editbox_get(ctrl, dp); + } +} + +static void ca_wc_add_handler(dlgcontrol *ctrl, dlgparam *dp, + void *data, int event) +{ + struct ca_state *st = (struct ca_state *)ctrl->context.p; + if (event == EVENT_ACTION) { + if (!st->wc) { + dlg_beep(dp); + return; + } + + if (add234(st->host_wcs, st->wc) == st->wc) { + dlg_refresh(st->ca_wclist, dp); + } else { + sfree(st->wc); + } + + st->wc = dupstr(""); + dlg_refresh(st->ca_wc_edit, dp); + } +} + +static void ca_wc_rem_handler(dlgcontrol *ctrl, dlgparam *dp, + void *data, int event) +{ + struct ca_state *st = (struct ca_state *)ctrl->context.p; + if (event == EVENT_ACTION) { + int i = dlg_listbox_index(st->ca_wclist, dp); + if (i < 0) { + dlg_beep(dp); + return; + } + char *wc = delpos234(st->host_wcs, i); + if (!wc) { + dlg_beep(dp); + return; + } + + sfree(st->wc); + st->wc = wc; + dlg_refresh(st->ca_wclist, dp); + dlg_refresh(st->ca_wc_edit, dp); + } +} + +void setup_ca_config_box(struct controlbox *b) +{ + struct controlset *s; + dlgcontrol *c; + + /* Internal state for manipulating the host CA system */ + struct ca_state *st = (struct ca_state *)ctrl_alloc_with_free( + b, sizeof(struct ca_state), ca_state_free); + memset(st, 0, sizeof(*st)); + st->name = dupstr(""); + st->pubkey = dupstr(""); + st->ca_names = newtree234(ca_name_compare); + st->host_wcs = newtree234(ca_name_compare); + ca_refresh_name_list(st); + + /* Action area, with the Done button in it */ + s = ctrl_getset(b, "", "", ""); + ctrl_columns(s, 5, 20, 20, 20, 20, 20); + c = ctrl_pushbutton(s, "Done", 'o', HELPCTX(no_help), + ca_ok_handler, P(st)); + c->button.isdefault = true; + c->column = 4; + + /* Load/save box, as similar as possible to the main saved sessions one */ + s = ctrl_getset(b, "Main", "loadsave", + "Load, save or delete a host CA record"); + ctrl_columns(s, 2, 75, 25); + c = ctrl_editbox(s, "Name for this CA (shown in log messages)", + 'n', 100, HELPCTX(no_help), + ca_name_handler, P(st), P(NULL)); + c->column = 0; + st->ca_name_edit = c; + /* 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); + c = ctrl_listbox(s, NULL, NO_SHORTCUT, HELPCTX(no_help), + ca_reclist_handler, P(st)); + c->column = 0; + c->listbox.height = 6; + st->ca_reclist = c; + c = ctrl_pushbutton(s, "Load", 'l', HELPCTX(no_help), + ca_load_handler, P(st)); + c->column = 1; + c = ctrl_pushbutton(s, "Save", 'v', HELPCTX(no_help), + ca_save_handler, P(st)); + c->column = 1; + c = ctrl_pushbutton(s, "Delete", 'd', HELPCTX(no_help), + ca_delete_handler, P(st)); + c->column = 1; + + /* Box containing the details of a specific CA record */ + s = ctrl_getset(b, "Main", "details", "Details of a host CA record"); + c = ctrl_editbox(s, "Public key of certification authority", 'k', 100, + HELPCTX(no_help), ca_pubkey_handler, P(st), P(NULL)); + st->ca_pubkey_edit = c; + c = ctrl_listbox(s, "Hostname patterns this key is trusted to certify", + NO_SHORTCUT, HELPCTX(no_help), ca_wclist_handler, P(st)); + c->listbox.height = 3; + st->ca_wclist = c; + ctrl_columns(s, 3, 70, 15, 15); + c = ctrl_editbox(s, "Hostname pattern to add", 'h', 100, + HELPCTX(no_help), ca_wc_edit_handler, P(st), P(NULL)); + c->column = 0; + st->ca_wc_edit = c; + c = ctrl_pushbutton(s, "Add", NO_SHORTCUT, HELPCTX(no_help), + ca_wc_add_handler, P(st)); + c->column = 1; + c = ctrl_pushbutton(s, "Remove", NO_SHORTCUT, HELPCTX(no_help), + ca_wc_rem_handler, P(st)); + c->column = 2; +} diff --git a/stubs/no-ca-config.c b/stubs/no-ca-config.c new file mode 100644 index 00000000..573f770f --- /dev/null +++ b/stubs/no-ca-config.c @@ -0,0 +1,14 @@ +/* + * Stub version of setup_ca_config_box, for tools that don't have SSH + * code linked in. + */ + +#include "putty.h" +#include "dialog.h" + +const bool has_ca_config_box = false; + +void setup_ca_config_box(struct controlbox *b) +{ + unreachable("should never call setup_ca_config_box in this application"); +} diff --git a/unix/CMakeLists.txt b/unix/CMakeLists.txt index 6bb275d9..248ccf27 100644 --- a/unix/CMakeLists.txt +++ b/unix/CMakeLists.txt @@ -134,6 +134,7 @@ if(GTK_FOUND) pterm.c main-gtk-simple.c ${CMAKE_SOURCE_DIR}/stubs/nogss.c + ${CMAKE_SOURCE_DIR}/stubs/no-ca-config.c ${CMAKE_SOURCE_DIR}/proxy/nosshproxy.c pty.c) be_list(pterm pterm) @@ -148,6 +149,7 @@ if(GTK_FOUND) main-gtk-application.c ${CMAKE_SOURCE_DIR}/stubs/nocmdline.c ${CMAKE_SOURCE_DIR}/stubs/nogss.c + ${CMAKE_SOURCE_DIR}/stubs/no-ca-config.c ${CMAKE_SOURCE_DIR}/proxy/nosshproxy.c pty.c) be_list(ptermapp pterm) @@ -184,6 +186,7 @@ if(GTK_FOUND) putty.c main-gtk-simple.c ${CMAKE_SOURCE_DIR}/stubs/nogss.c + ${CMAKE_SOURCE_DIR}/stubs/no-ca-config.c ${CMAKE_SOURCE_DIR}/stubs/norand.c ${CMAKE_SOURCE_DIR}/proxy/nocproxy.c ${CMAKE_SOURCE_DIR}/proxy/nosshproxy.c) diff --git a/unix/main-gtk-simple.c b/unix/main-gtk-simple.c index b08a0acc..2f09cfbc 100644 --- a/unix/main-gtk-simple.c +++ b/unix/main-gtk-simple.c @@ -532,8 +532,9 @@ bool do_cmdline(int argc, char **argv, bool do_everything, Conf *conf) pgp_fingerprints(); exit(1); - } else if (!strcmp(p, "-host-ca") || !strcmp(p, "--host-ca") || - !strcmp(p, "-host_ca") || !strcmp(p, "--host_ca")) { + } else if (has_ca_config_box && + (!strcmp(p, "-host-ca") || !strcmp(p, "--host-ca") || + !strcmp(p, "-host_ca") || !strcmp(p, "--host_ca"))) { show_ca_config_box_synchronously(); exit(0); diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt index 7bfa955c..5f829ba8 100644 --- a/windows/CMakeLists.txt +++ b/windows/CMakeLists.txt @@ -117,6 +117,7 @@ add_executable(puttytel putty.c help.c ${CMAKE_SOURCE_DIR}/stubs/nogss.c + ${CMAKE_SOURCE_DIR}/stubs/no-ca-config.c ${CMAKE_SOURCE_DIR}/stubs/norand.c ${CMAKE_SOURCE_DIR}/proxy/nocproxy.c ${CMAKE_SOURCE_DIR}/proxy/nosshproxy.c @@ -158,6 +159,7 @@ if(HAVE_CONPTY) help.c conpty.c ${CMAKE_SOURCE_DIR}/stubs/nogss.c + ${CMAKE_SOURCE_DIR}/stubs/no-ca-config.c ${CMAKE_SOURCE_DIR}/stubs/norand.c ${CMAKE_SOURCE_DIR}/proxy/nosshproxy.c pterm.rc) diff --git a/windows/putty.c b/windows/putty.c index fca1d4bb..53c8b4a5 100644 --- a/windows/putty.c +++ b/windows/putty.c @@ -87,8 +87,9 @@ void gui_term_process_cmdline(Conf *conf, char *cmdline) } else if (!strcmp(p, "-pgpfp")) { pgp_fingerprints_msgbox(NULL); exit(1); - } else if (!strcmp(p, "-host-ca") || !strcmp(p, "--host-ca") || - !strcmp(p, "-host_ca") || !strcmp(p, "--host_ca")) { + } else if (has_ca_config_box && + (!strcmp(p, "-host-ca") || !strcmp(p, "--host-ca") || + !strcmp(p, "-host_ca") || !strcmp(p, "--host_ca"))) { show_ca_config_box(NULL); exit(0); } else if (!strcmp(p, "-demo-config-box")) { -- cgit v1.2.3 From ddcd93ab1230b2bf4b34f007e356b40780b50c8f Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 1 May 2022 09:31:20 +0100 Subject: CA config box: add a 'Read from file' button. This allows you to load a CA public key from a disk file (in any format acceptable to ppk_load_pub, which means OpenSSH one-line public keys and also RFC4716 ones). --- ssh/ca-config.c | 42 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/ssh/ca-config.c b/ssh/ca-config.c index cc902db9..2e14f79d 100644 --- a/ssh/ca-config.c +++ b/ssh/ca-config.c @@ -7,6 +7,7 @@ #include "dialog.h" #include "storage.h" #include "tree234.h" +#include "ssh.h" const bool has_ca_config_box = true; @@ -216,8 +217,8 @@ static void ca_delete_handler(dlgcontrol *ctrl, dlgparam *dp, } } -static void ca_pubkey_handler(dlgcontrol *ctrl, dlgparam *dp, - void *data, int event) +static void ca_pubkey_edit_handler(dlgcontrol *ctrl, dlgparam *dp, + void *data, int event) { struct ca_state *st = (struct ca_state *)ctrl->context.p; if (event == EVENT_REFRESH) { @@ -228,6 +229,33 @@ static void ca_pubkey_handler(dlgcontrol *ctrl, dlgparam *dp, } } +static void ca_pubkey_file_handler(dlgcontrol *ctrl, dlgparam *dp, + void *data, int event) +{ + struct ca_state *st = (struct ca_state *)ctrl->context.p; + if (event == EVENT_ACTION) { + Filename *filename = dlg_filesel_get(ctrl, dp); + strbuf *keyblob = strbuf_new(); + const char *load_error; + bool ok = ppk_loadpub_f(filename, NULL, BinarySink_UPCAST(keyblob), + NULL, &load_error); + if (!ok) { + char *message = dupprintf( + "Unable to load public key from '%s': %s", + filename_to_str(filename), load_error); + dlg_error_msg(dp, message); + sfree(message); + } else { + sfree(st->pubkey); + st->pubkey = strbuf_to_str( + base64_encode_sb(ptrlen_from_strbuf(keyblob), 0)); + dlg_refresh(st->ca_pubkey_edit, dp); + } + filename_free(filename); + strbuf_free(keyblob); + } +} + static void ca_wclist_handler(dlgcontrol *ctrl, dlgparam *dp, void *data, int event) { @@ -351,9 +379,17 @@ void setup_ca_config_box(struct controlbox *b) /* Box containing the details of a specific CA record */ s = ctrl_getset(b, "Main", "details", "Details of a host CA record"); + ctrl_columns(s, 2, 75, 25); c = ctrl_editbox(s, "Public key of certification authority", 'k', 100, - HELPCTX(no_help), ca_pubkey_handler, P(st), P(NULL)); + HELPCTX(no_help), ca_pubkey_edit_handler, P(st), P(NULL)); + c->column = 0; st->ca_pubkey_edit = c; + c = ctrl_filesel(s, "Read from file", NO_SHORTCUT, NULL, false, + "Select public key file of certification authority", + HELPCTX(no_help), ca_pubkey_file_handler, P(st)); + c->fileselect.just_button = true; + c->column = 1; + ctrl_columns(s, 1, 100); c = ctrl_listbox(s, "Hostname patterns this key is trusted to certify", NO_SHORTCUT, HELPCTX(no_help), ca_wclist_handler, P(st)); c->listbox.height = 3; -- cgit v1.2.3 From 2a44b6354f5a251afe3dd3a1b835cea4517ee629 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 1 May 2022 09:33:28 +0100 Subject: CA config: make the 'Done' button cancel and not default. This means that, on the one hand, an absentminded press of Return doesn't dismiss the entire CA config box, which would be pretty annoying if you were half way through entering a load of fiddly stuff. And on the other hand, you _can_ press Escape to dismiss the box, which is less likely to happen by accident. --- ssh/ca-config.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ssh/ca-config.c b/ssh/ca-config.c index 2e14f79d..e8802a4c 100644 --- a/ssh/ca-config.c +++ b/ssh/ca-config.c @@ -346,7 +346,7 @@ void setup_ca_config_box(struct controlbox *b) ctrl_columns(s, 5, 20, 20, 20, 20, 20); c = ctrl_pushbutton(s, "Done", 'o', HELPCTX(no_help), ca_ok_handler, P(st)); - c->button.isdefault = true; + c->button.iscancel = true; c->column = 4; /* Load/save box, as similar as possible to the main saved sessions one */ -- cgit v1.2.3 From d06ae2f5c345741192a0e3f9086765382690e37b Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 1 May 2022 11:19:10 +0100 Subject: New utility function base64_valid(). For when you want to tell the difference between a base64-encoded string and some other kind of string that might replace it. --- misc.h | 1 + utils/CMakeLists.txt | 1 + utils/base64_valid.c | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+) create mode 100644 utils/base64_valid.c diff --git a/misc.h b/misc.h index eeed80c1..fe8eb06a 100644 --- a/misc.h +++ b/misc.h @@ -114,6 +114,7 @@ strbuf *base64_decode_sb(ptrlen data); void base64_encode_bs(BinarySink *bs, ptrlen data, int cpl); void base64_encode_fp(FILE *fp, ptrlen data, int cpl); strbuf *base64_encode_sb(ptrlen data, int cpl); +bool base64_valid(ptrlen data); struct bufchain_granule; struct bufchain_tag { diff --git a/utils/CMakeLists.txt b/utils/CMakeLists.txt index 949166f5..9d079ecb 100644 --- a/utils/CMakeLists.txt +++ b/utils/CMakeLists.txt @@ -5,6 +5,7 @@ add_sources_from_current_dir(utils base64_decode.c base64_encode_atom.c base64_encode.c + base64_valid.c bufchain.c buildinfo.c burnstr.c diff --git a/utils/base64_valid.c b/utils/base64_valid.c new file mode 100644 index 00000000..8eb1f3a0 --- /dev/null +++ b/utils/base64_valid.c @@ -0,0 +1,54 @@ +/* + * Determine whether a string looks like valid base64-encoded data. + */ + +#include "misc.h" + +static inline bool valid_char_main(char c) +{ + return ((c >= 'A' && c <= 'Z') || + (c >= 'a' && c <= 'z') || + (c >= '0' && c <= '9') || + c == '+' || c == '/'); +} + +bool base64_valid(ptrlen data) +{ + size_t blocklen = 0, nequals = 0; + + for (size_t i = 0; i < data.len; i++) { + char c = ((const char *)data.ptr)[i]; + + if (c == '\n' || c == '\r') + continue; + + if (valid_char_main(c)) { + if (nequals) /* can't go back to data after = */ + return false; + blocklen++; + if (blocklen == 4) + blocklen = 0; + continue; + } + + if (c == '=') { + if (blocklen == 0 && nequals) /* started a fresh block */ + return false; + + nequals++; + blocklen++; + if (blocklen == 4) { + if (nequals > 2) + return false; /* nonsensical final block */ + blocklen = 0; + } + continue; + } + + return false; /* bad character */ + } + + if (blocklen == 0 || blocklen == 2 || blocklen == 3) + return true; /* permit eliding the trailing = */ + return false; +} -- cgit v1.2.3 From 6472b5ded76c76ac388598b6998effc73861368b Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 1 May 2022 11:27:46 +0100 Subject: CA config: permit pasting a whole OpenSSH public key. Now, we try putting the contents of the public-key edit box through ppk_load_s if it isn't a plain base64-encoded string. --- ssh/ca-config.c | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/ssh/ca-config.c b/ssh/ca-config.c index e8802a4c..0f82bb35 100644 --- a/ssh/ca-config.c +++ b/ssh/ca-config.c @@ -164,15 +164,47 @@ static void ca_load_handler(dlgcontrol *ctrl, dlgparam *dp, } } +static strbuf *decode_pubkey(ptrlen data, const char **error) +{ + /* + * See if we have a plain base64-encoded public key blob. + */ + if (base64_valid(data)) + return base64_decode_sb(data); + + /* + * Otherwise, try to decode as if it was a public key _file_. + */ + BinarySource src[1]; + BinarySource_BARE_INIT_PL(src, data); + strbuf *blob = strbuf_new(); + if (ppk_loadpub_s(src, NULL, BinarySink_UPCAST(blob), NULL, error)) + return blob; + + return NULL; +} + static void ca_save_handler(dlgcontrol *ctrl, dlgparam *dp, void *data, int event) { struct ca_state *st = (struct ca_state *)ctrl->context.p; if (event == EVENT_ACTION) { + strbuf *pubkey; + { + const char *error; + pubkey = decode_pubkey(ptrlen_from_asciz(st->pubkey), &error); + if (!pubkey) { + char *msg = dupprintf("CA public key invalid: %s", error); + dlg_error_msg(dp, msg); + sfree(msg); + return; + } + } + host_ca *hca = snew(host_ca); memset(hca, 0, sizeof(*hca)); hca->name = dupstr(st->name); - hca->ca_public_key = base64_decode_sb(ptrlen_from_asciz(st->pubkey)); + hca->ca_public_key = pubkey; hca->n_hostname_wildcards = count234(st->host_wcs); hca->hostname_wildcards = snewn(hca->n_hostname_wildcards, char *); for (size_t i = 0; i < hca->n_hostname_wildcards; i++) -- cgit v1.2.3 From 8d2c643fcb3b475a1bb3c7540b9d634cf16b9f05 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 1 May 2022 11:29:54 +0100 Subject: CA config: protect against saving a key with no wildcards. --- ssh/ca-config.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ssh/ca-config.c b/ssh/ca-config.c index 0f82bb35..056622a2 100644 --- a/ssh/ca-config.c +++ b/ssh/ca-config.c @@ -189,6 +189,11 @@ static void ca_save_handler(dlgcontrol *ctrl, dlgparam *dp, { struct ca_state *st = (struct ca_state *)ctrl->context.p; if (event == EVENT_ACTION) { + if (!count234(st->host_wcs)) { + dlg_error_msg(dp, "No hostnames configured for this key"); + return; + } + strbuf *pubkey; { const char *error; -- cgit v1.2.3 From 619bb441ec12d85d72405acb88f188256d9c7f27 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 2 May 2022 11:05:54 +0100 Subject: contrib/gdb.py: add a pretty-printer for ptrlen. I mostly really like the use of 'ptrlen' in place of zero-terminated strings in PuTTY, but one place it's awkward is when debuggging through string-handling code, because gdb won't automatically show me exactly what a ptrlen points to. Now it does. --- contrib/gdb.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/contrib/gdb.py b/contrib/gdb.py index 4c41edcc..fb7413ec 100644 --- a/contrib/gdb.py +++ b/contrib/gdb.py @@ -30,12 +30,29 @@ class PuTTYMpintPrettyPrinter(gdb.printing.PrettyPrinter): return "mp_int(NULL)".format(address) return "mp_int(invalid @ {:#x})".format(address) +class PuTTYPtrlenPrettyPrinter(gdb.printing.PrettyPrinter): + "Pretty-print strings in PuTTY's ptrlen type." + name = "ptrlen" + + def __init__(self, val): + super(PuTTYPtrlenPrettyPrinter, self).__init__(self.name) + self.val = val + + def to_string(self): + length = int(self.val["len"]) + char_array_ptr_type = gdb.lookup_type( + "char").const().array(length).pointer() + line = self.val["ptr"].cast(char_array_ptr_type).dereference() + return repr(bytes(int(line[i]) for i in range(length))).lstrip('b') + class PuTTYPrinterSelector(gdb.printing.PrettyPrinter): def __init__(self): super(PuTTYPrinterSelector, self).__init__("PuTTY") def __call__(self, val): if str(val.type) == "mp_int *": return PuTTYMpintPrettyPrinter(val) + if str(val.type) == "ptrlen": + return PuTTYPtrlenPrettyPrinter(val) return None gdb.printing.register_pretty_printer(None, PuTTYPrinterSelector()) -- cgit v1.2.3 From e34e0220abd1b327a088c9a68632545d33569b41 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 2 May 2022 07:40:52 +0100 Subject: Centralise creation of a host_ca structure. This will allow the central host_ca_new function to pre-populate the structure with default values for the fields, so that once I add more options to CA configuration they can take their default values when loading a saved record from a previous PuTTY version. --- storage.h | 2 ++ unix/storage.c | 3 +-- utils/CMakeLists.txt | 2 +- utils/host_ca_free.c | 14 -------------- utils/host_ca_new_free.c | 21 +++++++++++++++++++++ windows/storage.c | 3 +-- 6 files changed, 26 insertions(+), 19 deletions(-) delete mode 100644 utils/host_ca_free.c create mode 100644 utils/host_ca_new_free.c diff --git a/storage.h b/storage.h index df0c3e29..0487bf7c 100644 --- a/storage.h +++ b/storage.h @@ -112,6 +112,8 @@ void enum_host_ca_finish(host_ca_enum *handle); host_ca *host_ca_load(const char *name); char *host_ca_save(host_ca *); /* NULL on success, or dynamic error msg */ char *host_ca_delete(const char *name); /* likewise */ + +host_ca *host_ca_new(void); /* initialises to default settings */ void host_ca_free(host_ca *); /* ---------------------------------------------------------------------- diff --git a/unix/storage.c b/unix/storage.c index 8be482cd..b3a9e8f1 100644 --- a/unix/storage.c +++ b/unix/storage.c @@ -643,8 +643,7 @@ host_ca *host_ca_load(const char *name) if (!fp) return NULL; - host_ca *hca = snew(host_ca); - memset(hca, 0, sizeof(*hca)); + host_ca *hca = host_ca_new(); hca->name = dupstr(name); size_t wcsize = 0; diff --git a/utils/CMakeLists.txt b/utils/CMakeLists.txt index 9d079ecb..dfd28efd 100644 --- a/utils/CMakeLists.txt +++ b/utils/CMakeLists.txt @@ -26,7 +26,7 @@ add_sources_from_current_dir(utils encode_utf8.c encode_wide_string_as_utf8.c fgetline.c - host_ca_free.c + host_ca_new_free.c host_strchr.c host_strchr_internal.c host_strcspn.c diff --git a/utils/host_ca_free.c b/utils/host_ca_free.c deleted file mode 100644 index 5fb3a46c..00000000 --- a/utils/host_ca_free.c +++ /dev/null @@ -1,14 +0,0 @@ -#include "defs.h" -#include "misc.h" -#include "storage.h" - -void host_ca_free(host_ca *hca) -{ - sfree(hca->name); - if (hca->ca_public_key) - strbuf_free(hca->ca_public_key); - for (size_t i = 0; i < hca->n_hostname_wildcards; i++) - sfree(hca->hostname_wildcards[i]); - sfree(hca->hostname_wildcards); - sfree(hca); -} diff --git a/utils/host_ca_new_free.c b/utils/host_ca_new_free.c new file mode 100644 index 00000000..f77c84d8 --- /dev/null +++ b/utils/host_ca_new_free.c @@ -0,0 +1,21 @@ +#include "defs.h" +#include "misc.h" +#include "storage.h" + +host_ca *host_ca_new(void) +{ + host_ca *hca = snew(host_ca); + memset(hca, 0, sizeof(*hca)); + return hca; +} + +void host_ca_free(host_ca *hca) +{ + sfree(hca->name); + if (hca->ca_public_key) + strbuf_free(hca->ca_public_key); + for (size_t i = 0; i < hca->n_hostname_wildcards; i++) + sfree(hca->hostname_wildcards[i]); + sfree(hca->hostname_wildcards); + sfree(hca); +} diff --git a/windows/storage.c b/windows/storage.c index 7e74e392..c58f6d85 100644 --- a/windows/storage.c +++ b/windows/storage.c @@ -424,8 +424,7 @@ host_ca *host_ca_load(const char *name) if (!rkey) return NULL; - host_ca *hca = snew(host_ca); - memset(hca, 0, sizeof(*hca)); + host_ca *hca = host_ca_new(); hca->name = dupstr(name); if ((s = get_reg_sz(rkey, "PublicKey")) != NULL) -- cgit v1.2.3 From dc7ba12253c68d1893f75652a53436de56eaec2a Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 2 May 2022 10:18:16 +0100 Subject: Permit configuring RSA signature types in certificates. As distinct from the type of signature generated by the SSH server itself from the host key, this lets you exclude (and by default does exclude) the old "ssh-rsa" SHA-1 signature type from the signature of the CA on the certificate. --- crypto/openssh-certs.c | 19 +++++++--- defs.h | 11 ++++++ ssh.h | 7 ++-- ssh/ca-config.c | 91 +++++++++++++++++++++++++++++++++++++++--------- ssh/kex2-client.c | 1 + storage.h | 3 ++ test/cryptsuite.py | 73 ++++++++++++++++++++++++++++---------- test/testcrypt-func.h | 3 +- test/testcrypt.c | 28 +++++++++++++-- unix/storage.c | 10 ++++++ utils/host_ca_new_free.c | 3 ++ windows/storage.c | 13 +++++++ 12 files changed, 215 insertions(+), 47 deletions(-) diff --git a/crypto/openssh-certs.c b/crypto/openssh-certs.c index 3af2e3b5..78be4f15 100644 --- a/crypto/openssh-certs.c +++ b/crypto/openssh-certs.c @@ -209,7 +209,7 @@ static key_components *opensshcert_components(ssh_key *key); static ssh_key *opensshcert_base_key(ssh_key *key); static bool opensshcert_check_cert( ssh_key *key, bool host, ptrlen principal, uint64_t time, - BinarySink *error); + const ca_options *opts, BinarySink *error); static int opensshcert_pubkey_bits(const ssh_keyalg *self, ptrlen blob); static unsigned opensshcert_supported_flags(const ssh_keyalg *self); static const char *opensshcert_alternate_ssh_id(const ssh_keyalg *self, @@ -716,7 +716,7 @@ static char *opensshcert_invalid(ssh_key *key, unsigned flags) static bool opensshcert_check_cert( ssh_key *key, bool host, ptrlen principal, uint64_t time, - BinarySink *error) + const ca_options *opts, BinarySink *error) { opensshcert_key *ck = container_of(key, opensshcert_key, sshk); bool result = false; @@ -725,9 +725,6 @@ static bool opensshcert_check_cert( BinarySource src[1]; ptrlen signature = ptrlen_from_strbuf(ck->signature); - /* FIXME: here we should check which signature algorithm is - * actually in use, because that might be a reason to reject the - * certificate (e.g. ssh-rsa when we wanted rsa-sha2-*) */ ca_key = opensshcert_ca_pub_key(ck, signature, NULL); if (!ca_key) { @@ -735,6 +732,18 @@ static bool opensshcert_check_cert( goto out; } + /* Check which signature algorithm is actually in use, because + * that might be a reason to reject the certificate (e.g. ssh-rsa + * when we wanted rsa-sha2-*). */ + const ssh_keyalg *sig_alg = ssh_key_alg(ca_key); + if ((sig_alg == &ssh_rsa && !opts->permit_rsa_sha1) || + (sig_alg == &ssh_rsa_sha256 && !opts->permit_rsa_sha256) || + (sig_alg == &ssh_rsa_sha512 && !opts->permit_rsa_sha512)) { + put_fmt(error, "Certificate signature uses '%s' signature type " + "(forbidden by user configuration)", sig_alg->ssh_id); + goto out; + } + opensshcert_signature_preimage(ck, BinarySink_UPCAST(preimage)); if (!ssh_key_verify(ca_key, signature, ptrlen_from_strbuf(preimage))) { diff --git a/defs.h b/defs.h index cd7d0f4d..17bd62e4 100644 --- a/defs.h +++ b/defs.h @@ -177,6 +177,7 @@ typedef struct dlgcontrol dlgcontrol; typedef struct settings_w settings_w; typedef struct settings_r settings_r; typedef struct settings_e settings_e; +typedef struct ca_options ca_options; typedef struct host_ca host_ca; typedef struct host_ca_enum host_ca_enum; @@ -247,4 +248,14 @@ struct unicode_data; #define CAT_INNER(x,y) x ## y #define CAT(x,y) CAT_INNER(x,y) +/* + * Structure shared between ssh.h and storage.h, giving strictness + * options relating to checking of an OpenSSH certificate. It's a bit + * cheaty to put something so specific in here, but more painful to + * put it in putty.h. + */ +struct ca_options { + bool permit_rsa_sha1, permit_rsa_sha256, permit_rsa_sha512; +}; + #endif /* PUTTY_DEFS_H */ diff --git a/ssh.h b/ssh.h index ecb5074c..e628638c 100644 --- a/ssh.h +++ b/ssh.h @@ -848,7 +848,8 @@ struct ssh_keyalg { /* The following methods can be NULL if !is_certificate */ void (*ca_public_blob)(ssh_key *key, BinarySink *); bool (*check_cert)(ssh_key *key, bool host, ptrlen principal, - uint64_t time, BinarySink *error); + uint64_t time, const ca_options *opts, + BinarySink *error); void (*cert_id_string)(ssh_key *key, BinarySink *); /* 'Class methods' that don't deal with an ssh_key at all */ @@ -904,8 +905,8 @@ static inline void ssh_key_cert_id_string(ssh_key *key, BinarySink *bs) { key->vt->cert_id_string(key, bs); } static inline bool ssh_key_check_cert( ssh_key *key, bool host, ptrlen principal, uint64_t time, - BinarySink *error) -{ return key->vt->check_cert(key, host, principal, time, error); } + const ca_options *opts, BinarySink *error) +{ return key->vt->check_cert(key, host, principal, time, opts, error); } static inline int ssh_key_public_bits(const ssh_keyalg *self, ptrlen blob) { return self->pubkey_bits(self, blob); } static inline const ssh_keyalg *ssh_key_alg(ssh_key *key) diff --git a/ssh/ca-config.c b/ssh/ca-config.c index 056622a2..db104542 100644 --- a/ssh/ca-config.c +++ b/ssh/ca-config.c @@ -11,15 +11,19 @@ const bool has_ca_config_box = true; +#define NRSATYPES 3 + struct ca_state { dlgcontrol *ca_name_edit; dlgcontrol *ca_reclist; dlgcontrol *ca_pubkey_edit; dlgcontrol *ca_wclist; dlgcontrol *ca_wc_edit; + dlgcontrol *rsa_type_checkboxes[NRSATYPES]; char *name, *pubkey, *wc; tree234 *ca_names; /* stores plain 'char *' */ tree234 *host_wcs; /* stores plain 'char *' */ + ca_options opts; }; static int ca_name_compare(void *av, void *bv) @@ -68,6 +72,29 @@ static void ca_refresh_name_list(struct ca_state *st) } } +static void set_from_hca(struct ca_state *st, host_ca *hca) +{ + sfree(st->name); + st->name = dupstr(hca->name); + + sfree(st->pubkey); + if (hca->ca_public_key) + st->pubkey = strbuf_to_str( + base64_encode_sb(ptrlen_from_strbuf(hca->ca_public_key), 0)); + else + st->pubkey = dupstr(""); + + clear_string_tree(st->host_wcs); + for (size_t i = 0; i < hca->n_hostname_wildcards; i++) { + char *name = dupstr(hca->hostname_wildcards[i]); + char *added = add234(st->host_wcs, name); + if (added != name) + sfree(name); /* de-duplicate, just in case */ + } + + st->opts = hca->opts; /* structure copy */ +} + static void ca_load_selected_record(struct ca_state *st, dlgparam *dp) { int i = dlg_listbox_index(st->ca_reclist, dp); @@ -88,26 +115,14 @@ static void ca_load_selected_record(struct ca_state *st, dlgparam *dp) return; } - sfree(st->name); - st->name = dupstr(hca->name); - - sfree(st->pubkey); - st->pubkey = strbuf_to_str( - base64_encode_sb(ptrlen_from_strbuf(hca->ca_public_key), 0)); - - clear_string_tree(st->host_wcs); - for (size_t i = 0; i < hca->n_hostname_wildcards; i++) { - char *name = dupstr(hca->hostname_wildcards[i]); - char *added = add234(st->host_wcs, name); - if (added != name) - sfree(name); /* de-duplicate, just in case */ - } - + set_from_hca(st, hca); host_ca_free(hca); dlg_refresh(st->ca_name_edit, dp); dlg_refresh(st->ca_pubkey_edit, dp); dlg_refresh(st->ca_wclist, dp); + for (size_t i = 0; i < NRSATYPES; i++) + dlg_refresh(st->rsa_type_checkboxes[i], dp); } static void ca_ok_handler(dlgcontrol *ctrl, dlgparam *dp, @@ -214,6 +229,8 @@ static void ca_save_handler(dlgcontrol *ctrl, dlgparam *dp, hca->hostname_wildcards = snewn(hca->n_hostname_wildcards, char *); for (size_t i = 0; i < hca->n_hostname_wildcards; i++) hca->hostname_wildcards[i] = dupstr(index234(st->host_wcs, i)); + hca->opts = st->opts; /* structure copy */ + char *error = host_ca_save(hca); host_ca_free(hca); @@ -363,6 +380,20 @@ static void ca_wc_rem_handler(dlgcontrol *ctrl, dlgparam *dp, } } +static void ca_rsa_type_handler(dlgcontrol *ctrl, dlgparam *dp, + void *data, int event) +{ + struct ca_state *st = (struct ca_state *)ctrl->context.p; + size_t offset = ctrl->context2.i; + bool *option = (bool *)((char *)&st->opts + offset); + + if (event == EVENT_REFRESH) { + dlg_checkbox_set(ctrl, dp, *option); + } else if (event == EVENT_VALCHANGE) { + *option = dlg_checkbox_get(ctrl, dp); + } +} + void setup_ca_config_box(struct controlbox *b) { struct controlset *s; @@ -372,12 +403,17 @@ void setup_ca_config_box(struct controlbox *b) struct ca_state *st = (struct ca_state *)ctrl_alloc_with_free( b, sizeof(struct ca_state), ca_state_free); memset(st, 0, sizeof(*st)); - st->name = dupstr(""); - st->pubkey = dupstr(""); st->ca_names = newtree234(ca_name_compare); st->host_wcs = newtree234(ca_name_compare); ca_refresh_name_list(st); + /* Initialise the settings to a default blank host_ca */ + { + host_ca *hca = host_ca_new(); + set_from_hca(st, hca); + host_ca_free(hca); + } + /* Action area, with the Done button in it */ s = ctrl_getset(b, "", "", ""); ctrl_columns(s, 5, 20, 20, 20, 20, 20); @@ -442,4 +478,25 @@ void setup_ca_config_box(struct controlbox *b) c = ctrl_pushbutton(s, "Remove", NO_SHORTCUT, HELPCTX(no_help), ca_wc_rem_handler, P(st)); c->column = 2; + ctrl_columns(s, 1, 100); + + ctrl_columns(s, 4, 44, 18, 18, 18); + c = ctrl_text(s, "Signature types (RSA keys only):", HELPCTX(no_help)); + c->column = 0; + c = ctrl_checkbox(s, "SHA-1", NO_SHORTCUT, HELPCTX(no_help), + ca_rsa_type_handler, P(st)); + c->column = 1; + c->context2 = I(offsetof(ca_options, permit_rsa_sha1)); + st->rsa_type_checkboxes[0] = c; + c = ctrl_checkbox(s, "SHA-256", NO_SHORTCUT, HELPCTX(no_help), + ca_rsa_type_handler, P(st)); + c->column = 2; + c->context2 = I(offsetof(ca_options, permit_rsa_sha256)); + st->rsa_type_checkboxes[1] = c; + c = ctrl_checkbox(s, "SHA-512", NO_SHORTCUT, HELPCTX(no_help), + ca_rsa_type_handler, P(st)); + c->column = 3; + c->context2 = I(offsetof(ca_options, permit_rsa_sha512)); + st->rsa_type_checkboxes[2] = c; + ctrl_columns(s, 1, 100); } diff --git a/ssh/kex2-client.c b/ssh/kex2-client.c index 41e41503..a67f9e13 100644 --- a/ssh/kex2-client.c +++ b/ssh/kex2-client.c @@ -912,6 +912,7 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) true, /* host certificate */ ptrlen_from_asciz(s->savedhost), time(NULL), + &hca_found->opts, BinarySink_UPCAST(error)); } if (cert_ok) { diff --git a/storage.h b/storage.h index 0487bf7c..f581256e 100644 --- a/storage.h +++ b/storage.h @@ -6,6 +6,8 @@ #ifndef PUTTY_STORAGE_H #define PUTTY_STORAGE_H +#include "defs.h" + /* ---------------------------------------------------------------------- * Functions to save and restore PuTTY sessions. Note that this is * only the low-level code to do the reading and writing. The @@ -103,6 +105,7 @@ struct host_ca { strbuf *ca_public_key; char **hostname_wildcards; size_t n_hostname_wildcards; + ca_options opts; }; host_ca_enum *enum_host_ca_start(void); diff --git a/test/cryptsuite.py b/test/cryptsuite.py index b2548fbc..ff8cf1ed 100755 --- a/test/cryptsuite.py +++ b/test/cryptsuite.py @@ -2560,7 +2560,7 @@ Private-MAC: 5b1f6f4cc43eb0060d2c3e181bc0129343adba2b def testOpenSSHCert(self): def per_base_keytype_tests(alg, run_validation_tests=False, - ca_signflags=None): + run_ca_rsa_tests=False, ca_signflags=None): cert_pub = sign_cert_via_testcrypt( make_signature_preimage( key_to_certify = base_key.public_blob(), @@ -2600,9 +2600,36 @@ Private-MAC: 5b1f6f4cc43eb0060d2c3e181bc0129343adba2b self.assertEqual(certified_key.verify(base_sig, test_string), True) # Check a successful certificate verification - result, err = certified_key.check_cert(False, b'username', 1000) + result, err = certified_key.check_cert( + False, b'username', 1000, '') self.assertEqual(result, True) + # If the key type is RSA, check that the validator rejects + # wrong kinds of CA signature + if run_ca_rsa_tests: + forbid_all = ",".join(["permit_rsa_sha1=false", + "permit_rsa_sha256=false," + "permit_rsa_sha512=false"]) + result, err = certified_key.check_cert( + False, b'username', 1000, forbid_all) + self.assertEqual(result, False) + + algname = ("rsa-sha2-512" if ca_signflags == 4 else + "rsa-sha2-256" if ca_signflags == 2 else + "ssh-rsa") + self.assertEqual(err, ( + "Certificate signature uses '{}' signature type " + "(forbidden by user configuration)".format(algname) + .encode("ASCII"))) + + permitflag = ("permit_rsa_sha512" if ca_signflags == 4 else + "permit_rsa_sha256" if ca_signflags == 2 else + "permit_rsa_sha1") + result, err = certified_key.check_cert( + False, b'username', 1000, "{},{}=true".format( + forbid_all, permitflag)) + self.assertEqual(result, True) + # That's the end of the tests we need to repeat for all # the key types. Now we move on to detailed tests of the # validation, which are independent of key type, so we @@ -2612,16 +2639,19 @@ Private-MAC: 5b1f6f4cc43eb0060d2c3e181bc0129343adba2b # Check cert verification at the other end of the valid # time range - result, err = certified_key.check_cert(False, b'username', 1999) + result, err = certified_key.check_cert( + False, b'username', 1999, '') self.assertEqual(result, True) # Oops, wrong certificate type - result, err = certified_key.check_cert(True, b'username', 1000) + result, err = certified_key.check_cert( + True, b'username', 1000, '') self.assertEqual(result, False) self.assertEqual(err, b'Certificate type is user; expected host') # Oops, wrong username - result, err = certified_key.check_cert(False, b'someoneelse', 1000) + result, err = certified_key.check_cert( + False, b'someoneelse', 1000, '') self.assertEqual(result, False) self.assertEqual(err, b'Certificate\'s username list ["username"] ' b'does not contain expected username "someoneelse"') @@ -2629,10 +2659,12 @@ Private-MAC: 5b1f6f4cc43eb0060d2c3e181bc0129343adba2b # Oops, time is wrong. (But we can't check the full error # message including the translated start/end times, because # those vary with LC_TIME.) - result, err = certified_key.check_cert(False, b'someoneelse', 999) + result, err = certified_key.check_cert( + False, b'someoneelse', 999, '') self.assertEqual(result, False) self.assertEqual(err[:30], b'Certificate is not valid until') - result, err = certified_key.check_cert(False, b'someoneelse', 2000) + result, err = certified_key.check_cert( + False, b'someoneelse', 2000, '') self.assertEqual(result, False) self.assertEqual(err[:22], b'Certificate expired at') @@ -2642,7 +2674,8 @@ Private-MAC: 5b1f6f4cc43eb0060d2c3e181bc0129343adba2b bytelist[username_position] ^= 1 miscertified_key = ssh_key_new_priv(alg + '-cert', bytes(bytelist), base_key.private_blob()) - result, err = miscertified_key.check_cert(False, b'username', 1000) + result, err = miscertified_key.check_cert( + False, b'username', 1000, '') self.assertEqual(result, False) self.assertEqual(err, b"Certificate's signature is invalid") @@ -2659,7 +2692,8 @@ Private-MAC: 5b1f6f4cc43eb0060d2c3e181bc0129343adba2b critical_options = {b'unknown-option': b'yikes!'}), ca_key) certified_key = ssh_key_new_priv(alg + '-cert', cert_pub, base_key.private_blob()) - result, err = certified_key.check_cert(False, b'username', 1000) + result, err = certified_key.check_cert( + False, b'username', 1000, '') self.assertEqual(result, False) self.assertEqual(err, b'Certificate specifies an unsupported ' b'critical option "unknown-option"') @@ -2677,7 +2711,8 @@ Private-MAC: 5b1f6f4cc43eb0060d2c3e181bc0129343adba2b extensions = {b'unknown-ext': b'whatever, dude'}), ca_key) certified_key = ssh_key_new_priv(alg + '-cert', cert_pub, base_key.private_blob()) - result, err = certified_key.check_cert(False, b'username', 1000) + result, err = certified_key.check_cert( + False, b'username', 1000, '') self.assertEqual(result, True) # Now try a host certificate. We don't need to do _all_ the @@ -2703,19 +2738,19 @@ Private-MAC: 5b1f6f4cc43eb0060d2c3e181bc0129343adba2b # Check certificate type result, err = certified_key.check_cert( - True, b'hostname.example.com', 1000) + True, b'hostname.example.com', 1000, '') self.assertEqual(result, True) result, err = certified_key.check_cert( - False, b'hostname.example.com', 1000) + False, b'hostname.example.com', 1000, '') self.assertEqual(result, False) self.assertEqual(err, b'Certificate type is host; expected user') # Check the second hostname and an unknown one result, err = certified_key.check_cert( - True, b'hostname2.example.com', 1000) + True, b'hostname2.example.com', 1000, '') self.assertEqual(result, True) result, err = certified_key.check_cert( - True, b'hostname3.example.com', 1000) + True, b'hostname3.example.com', 1000, '') self.assertEqual(result, False) self.assertEqual(err, b'Certificate\'s hostname list [' b'"hostname.example.com", "hostname2.example.com"] ' @@ -2738,12 +2773,12 @@ Private-MAC: 5b1f6f4cc43eb0060d2c3e181bc0129343adba2b certified_key = ssh_key_new_priv(alg + '-cert', cert_pub, base_key.private_blob()) result, err = certified_key.check_cert( - False, b'username', 1000) + False, b'username', 1000, '') self.assertEqual(result, False) self.assertEqual(err, b'Certificate type is unknown value 12345; ' b'expected user') result, err = certified_key.check_cert( - True, b'hostname.example.com', 1000) + True, b'hostname.example.com', 1000, '') self.assertEqual(result, False) self.assertEqual(err, b'Certificate type is unknown value 12345; ' b'expected host') @@ -2771,9 +2806,9 @@ Private-MAC: 5b1f6f4cc43eb0060d2c3e181bc0129343adba2b # Now switch to an RSA certifying key, and test different RSA # signature subtypes being used to sign the certificate ca_key = ssh_key_new_priv('rsa', b64('AAAAB3NzaC1yc2EAAAADAQABAAAAgQCKHiavhtnAZQLUPtYlzlQmVTHSKq2ChCKZP0cLNtN2YSS0/f4D1hi8W04Qh/JuSXZAdUThTAVjxDmxpiOMNwa/2WDXMuqip47dzZSQxtSdvTfeL9TVC/M1NaOzy8bqFx6pzi37zPATETT4PP1Zt/Pd23ZJYhwjxSyTlqj7529v0w=='), b64('AAAAgCwTZyEIlaCyG28EBm7WI0CAW3/IIsrNxATHjrJjcqQKaB5iF5e90PL66DSaTaEoTFZRlgOXsPiffBHXBO0P+lTyZ2jlq2J2zgeofRH3Yong4BT4xDtqBKtxixgC1MAHmrOnRXjAcDUiLxIGgU0YKSv0uAlgARsUwDsk0GEvK+jBAAAAQQDMi7liRBQ4/Z6a4wDL/rVnIJ9x+2h2UPK9J8U7f97x/THIBtfkbf9O7nDP6onValuSr86tMR24DJZsEXaGPwjDAAAAQQCs3J3D3jNVwwk16oySRSjA5x3tKCEITYMluyXX06cvFew8ldgRCYl1sh8RYAfbBKXhnJD77qIxtVNaF1yl/guxAAAAQFTRdKRUF2wLu/K/Rr34trwKrV6aW0GWyHlLuWvF7FUB85aDmtqYI2BSk92mVCKHBNw2T3cJMabN9JOznjtADiM=')) - per_base_keytype_tests('rsa') - per_base_keytype_tests('rsa', ca_signflags=2) - per_base_keytype_tests('rsa', ca_signflags=4) + per_base_keytype_tests('rsa', run_ca_rsa_tests=True) + per_base_keytype_tests('rsa', run_ca_rsa_tests=True, ca_signflags=2) + per_base_keytype_tests('rsa', run_ca_rsa_tests=True, ca_signflags=4) class standard_test_vectors(MyTestBase): def testAES(self): diff --git a/test/testcrypt-func.h b/test/testcrypt-func.h index 0873c2ea..02b2eb4d 100644 --- a/test/testcrypt-func.h +++ b/test/testcrypt-func.h @@ -309,7 +309,8 @@ FUNC_WRAPPED(void, ssh_key_cert_id_string, ARG(val_key, key), ARG(out_val_string_binarysink, blob)) FUNC_WRAPPED(boolean, ssh_key_check_cert, ARG(val_key, key), ARG(boolean, host), ARG(val_string_ptrlen, principal), - ARG(uint, time), ARG(out_val_string_binarysink, error)) + ARG(uint, time), ARG(val_string_ptrlen, options), + ARG(out_val_string_binarysink, error)) /* * Accessors to retrieve the innards of a 'key_components'. diff --git a/test/testcrypt.c b/test/testcrypt.c index c3c74784..3d845673 100644 --- a/test/testcrypt.c +++ b/test/testcrypt.c @@ -830,13 +830,37 @@ void ssh_key_cert_id_string_wrapper(ssh_key *key, BinarySink *out) } static bool ssh_key_check_cert_wrapper( - ssh_key *key, bool host, ptrlen principal, uint64_t time, + ssh_key *key, bool host, ptrlen principal, uint64_t time, ptrlen optstr, BinarySink *error) { /* Wrap to avoid null-pointer dereference */ if (!key->vt->is_certificate) fatal_error("ssh_key_cert_id_string: needs a certificate"); - return ssh_key_check_cert(key, host, principal, time, error); + + ca_options opts; + opts.permit_rsa_sha1 = true; + opts.permit_rsa_sha256 = true; + opts.permit_rsa_sha512 = true; + + while (optstr.len) { + ptrlen word = ptrlen_get_word(&optstr, ","); + ptrlen key = word, value = PTRLEN_LITERAL(""); + const char *comma = memchr(word.ptr, '=', word.len); + if (comma) { + key.len = comma - (const char *)word.ptr; + value.ptr = comma + 1; + value.len = word.len - key.len - 1; + } + + if (ptrlen_eq_string(key, "permit_rsa_sha1")) + opts.permit_rsa_sha1 = ptrlen_eq_string(value, "true"); + if (ptrlen_eq_string(key, "permit_rsa_sha256")) + opts.permit_rsa_sha256 = ptrlen_eq_string(value, "true"); + if (ptrlen_eq_string(key, "permit_rsa_sha512")) + opts.permit_rsa_sha512 = ptrlen_eq_string(value, "true"); + } + + return ssh_key_check_cert(key, host, principal, time, &opts, error); } bool dh_validate_f_wrapper(dh_ctx *dh, mp_int *f) diff --git a/unix/storage.c b/unix/storage.c index b3a9e8f1..b18c5166 100644 --- a/unix/storage.c +++ b/unix/storage.c @@ -666,6 +666,12 @@ host_ca *host_ca_load(const char *name) hca->n_hostname_wildcards); hca->hostname_wildcards[hca->n_hostname_wildcards++] = dupstr(value); + } else if (!strcmp(line, "PermitRSASHA1")) { + hca->opts.permit_rsa_sha1 = atoi(value); + } else if (!strcmp(line, "PermitRSASHA256")) { + hca->opts.permit_rsa_sha256 = atoi(value); + } else if (!strcmp(line, "PermitRSASHA512")) { + hca->opts.permit_rsa_sha512 = atoi(value); } sfree(line); @@ -691,6 +697,10 @@ char *host_ca_save(host_ca *hca) for (size_t i = 0; i < hca->n_hostname_wildcards; i++) fprintf(fp, "MatchHosts=%s\n", hca->hostname_wildcards[i]); + fprintf(fp, "PermitRSASHA1=%d\n", (int)hca->opts.permit_rsa_sha1); + fprintf(fp, "PermitRSASHA256=%d\n", (int)hca->opts.permit_rsa_sha256); + fprintf(fp, "PermitRSASHA512=%d\n", (int)hca->opts.permit_rsa_sha512); + bool bad = ferror(fp); if (fclose(fp) < 0) bad = true; diff --git a/utils/host_ca_new_free.c b/utils/host_ca_new_free.c index f77c84d8..8ae158ba 100644 --- a/utils/host_ca_new_free.c +++ b/utils/host_ca_new_free.c @@ -6,6 +6,9 @@ host_ca *host_ca_new(void) { host_ca *hca = snew(host_ca); memset(hca, 0, sizeof(*hca)); + hca->opts.permit_rsa_sha1 = false; + hca->opts.permit_rsa_sha256 = true; + hca->opts.permit_rsa_sha512 = true; return hca; } diff --git a/windows/storage.c b/windows/storage.c index c58f6d85..6a48aee6 100644 --- a/windows/storage.c +++ b/windows/storage.c @@ -427,6 +427,8 @@ host_ca *host_ca_load(const char *name) host_ca *hca = host_ca_new(); hca->name = dupstr(name); + DWORD val; + if ((s = get_reg_sz(rkey, "PublicKey")) != NULL) hca->ca_public_key = base64_decode_sb(ptrlen_from_asciz(s)); @@ -445,6 +447,13 @@ host_ca *host_ca_load(const char *name) strbuf_free(sb); } + if (get_reg_dword(rkey, "PermitRSASHA1", &val)) + hca->opts.permit_rsa_sha1 = val; + if (get_reg_dword(rkey, "PermitRSASHA256", &val)) + hca->opts.permit_rsa_sha256 = val; + if (get_reg_dword(rkey, "PermitRSASHA512", &val)) + hca->opts.permit_rsa_sha512 = val; + close_regkey(rkey); return hca; } @@ -476,6 +485,10 @@ char *host_ca_save(host_ca *hca) put_reg_multi_sz(rkey, "MatchHosts", wcs); strbuf_free(wcs); + put_reg_dword(rkey, "PermitRSASHA1", hca->opts.permit_rsa_sha1); + put_reg_dword(rkey, "PermitRSASHA256", hca->opts.permit_rsa_sha256); + put_reg_dword(rkey, "PermitRSASHA512", hca->opts.permit_rsa_sha512); + close_regkey(rkey); return NULL; } -- cgit v1.2.3 From c6e40f67852e39fc4acd17a03b9d994e4c43e285 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 2 May 2022 10:19:11 +0100 Subject: Add some blank lines in setup_ca_config_box. It's becoming hard to see what's going on in all that control setup. --- ssh/ca-config.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ssh/ca-config.c b/ssh/ca-config.c index db104542..961baa7f 100644 --- a/ssh/ca-config.c +++ b/ssh/ca-config.c @@ -452,6 +452,7 @@ void setup_ca_config_box(struct controlbox *b) /* Box containing the details of a specific CA record */ s = ctrl_getset(b, "Main", "details", "Details of a host CA record"); + ctrl_columns(s, 2, 75, 25); c = ctrl_editbox(s, "Public key of certification authority", 'k', 100, HELPCTX(no_help), ca_pubkey_edit_handler, P(st), P(NULL)); @@ -463,10 +464,12 @@ void setup_ca_config_box(struct controlbox *b) c->fileselect.just_button = true; c->column = 1; ctrl_columns(s, 1, 100); + c = ctrl_listbox(s, "Hostname patterns this key is trusted to certify", NO_SHORTCUT, HELPCTX(no_help), ca_wclist_handler, P(st)); c->listbox.height = 3; st->ca_wclist = c; + ctrl_columns(s, 3, 70, 15, 15); c = ctrl_editbox(s, "Hostname pattern to add", 'h', 100, HELPCTX(no_help), ca_wc_edit_handler, P(st), P(NULL)); -- cgit v1.2.3 From 8c4524aa9127eddf798d182cb962fe345d214e3f Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 2 May 2022 18:59:26 +0100 Subject: Fix null-pointer dereferences in CA config. Introduced in dc7ba12253c68d1 earlier today. On GTK these caused no problems worse than a GTK warning, but I'd better fix them before they (potentially) do worse on Windows! --- ssh/ca-config.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ssh/ca-config.c b/ssh/ca-config.c index 961baa7f..dc047039 100644 --- a/ssh/ca-config.c +++ b/ssh/ca-config.c @@ -75,7 +75,7 @@ static void ca_refresh_name_list(struct ca_state *st) static void set_from_hca(struct ca_state *st, host_ca *hca) { sfree(st->name); - st->name = dupstr(hca->name); + st->name = dupstr(hca->name ? hca->name : ""); sfree(st->pubkey); if (hca->ca_public_key) @@ -405,6 +405,7 @@ void setup_ca_config_box(struct controlbox *b) memset(st, 0, sizeof(*st)); st->ca_names = newtree234(ca_name_compare); st->host_wcs = newtree234(ca_name_compare); + st->wc = dupstr(""); ca_refresh_name_list(st); /* Initialise the settings to a default blank host_ca */ -- cgit v1.2.3 From 03e71efcc513c9f13eb25b693b011b43f1f4a2d3 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Wed, 4 May 2022 12:46:06 +0100 Subject: Fix linked-list mismanagement in global request queue. When we linked a new entry on to the global request queue, we forgot to set its next pointer to NULL, so that when it was removed again, s->globreq_head could end up pointing to nonsense. In addition, even if the next pointer happened to be NULL by luck, we also did not notice that s->globreq_head had become NULL and respond by nulling out s->globreq_tail, which would leave s->globreq_tail as a stale pointer to the just-freed list element, causing a memory access error on the next attempt to link something on to the list. This could come up in the situation where you open Change Settings and configure a remote port forwarding, close it (so that the global request is sent, queued, replied to, and unqueued again), and then reopen Change Settings and configure a second one (so that the linked list in the confused state actually gets used). --- ssh/connection2.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ssh/connection2.c b/ssh/connection2.c index fb24b7c6..ec330927 100644 --- a/ssh/connection2.c +++ b/ssh/connection2.c @@ -178,6 +178,7 @@ void ssh2_queue_global_request_handler( snew(struct outstanding_global_request); ogr->handler = handler; ogr->ctx = ctx; + ogr->next = NULL; if (s->globreq_tail) s->globreq_tail->next = ogr; else @@ -372,6 +373,8 @@ static bool ssh2_connection_filter_queue(struct ssh2_connection_state *s) s->globreq_head = s->globreq_head->next; sfree(tmp); } + if (!s->globreq_head) + s->globreq_tail = NULL; pq_pop(s->ppl.in_pq); break; -- cgit v1.2.3 From af3520d245c4c027eacb8e4efa2542f7f1e3a411 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Wed, 4 May 2022 19:57:47 +0100 Subject: Windows Pageant: fix off-by-one in -c option. Apparently I never re-tested that option when I revamped Pageant's command-line option parsing in commit dc183e1649b429a, because it's now off by one in figuring out which argument to treat as the start of the command to be run. (The new code in that commit is the same shape as the old code but with variables renamed, and that was the mistake, because in the old code, the argument index i pointed to the -c option, whereas in the new code, match_opt has already advanced amo.index to the next word. So the two index variables _shouldn't_ be treated the same.) --- windows/pageant.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/windows/pageant.c b/windows/pageant.c index 5ba16801..dd8db40b 100644 --- a/windows/pageant.c +++ b/windows/pageant.c @@ -1504,8 +1504,8 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) * If we see `-c', then the rest of the command line * should be treated as a command to be spawned. */ - if (amo.index < amo.argc-1) - command = argstart[amo.index + 1]; + if (amo.index < amo.argc) + command = argstart[amo.index]; else command = ""; break; -- cgit v1.2.3 From b5ab90143a2df7ffcbae00a36b4db1367c07918f Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 2 May 2022 14:37:11 +0100 Subject: Improve the align_next_to mechanism. Various alignments I want to do in the host CA box have shown up deficiencies in this system, so I've reworked it a bit. Firstly, you can now specify more than two controls to be tied together with an align_next_to (e.g. multiple checkboxes alongside something else). Secondly, as well as forcing the controls to be the same height as each other, the layout algorithm will also move the later controls further _downward_, so that their top y positions also line up. Until now that hasn't been necessary, because they lined up already. In the GTK implementation of this via the Columns class, I've renamed 'columns_force_same_height' to 'columns_align_next_to', and similarly for some of the internal fields, since the latter change makes the previous names a misnomer. In the Windows implementation, I found it most convenient to set this up by following a linked list of align_next_to fields backwards. But it won't always be convenient to initialise them that way, so I've also written a crude normaliser that will rewrite those links into a canonical form. But I only call that on Windows; it's unnecessary in GTK, where the Columns class provides plenty of per-widget extra storage so I just keep each alignment class as a circular list. --- dialog.h | 24 ++++++++++++++----- unix/columns.c | 58 +++++++++++++++++++++++++++++++--------------- unix/columns.h | 10 ++++++-- unix/dialog.c | 19 ++++----------- utils/CMakeLists.txt | 1 + utils/ctrlset_normalise.c | 59 +++++++++++++++++++++++++++++++++++++++++++++++ windows/controls.c | 47 +++++++++++++++++++++++++------------ 7 files changed, 162 insertions(+), 56 deletions(-) create mode 100644 utils/ctrlset_normalise.c diff --git a/dialog.h b/dialog.h index b85bdcd5..45b4cde7 100644 --- a/dialog.h +++ b/dialog.h @@ -163,14 +163,20 @@ struct dlgcontrol { */ intorptr helpctx; /* - * Setting this to non-NULL coerces two controls to have their - * y-coordinates adjusted so that they can sit alongside each - * other and look nicely aligned, even if they're different + * Setting this to non-NULL coerces two or more controls to have + * their y-coordinates adjusted so that they can sit alongside + * each other and look nicely aligned, even if they're different * heights. * - * Set this field on the _second_ control of the pair (in terms of - * order in the data structure), so that when it's instantiated, - * the first one is already there to be referred to. + * Set this field on later controls (in terms of order in the data + * structure), pointing back to earlier ones, so that when each + * control is instantiated, the referred-to one is already there + * to be referred to. + * + * Don't expect this to change the position of the _first_ + * control. Currently, the layout is done one control at a time, + * so that once the first control has been placed, the second one + * can't cause the first one to be retrospectively moved. */ dlgcontrol *align_next_to; @@ -667,3 +673,9 @@ int ctrl_path_elements(const 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(const char *p1, const char *p2); + +/* + * Normalise the align_next_to fields in a controlset so that they + * form a backwards linked list. + */ +void ctrlset_normalise_aligns(struct controlset *s); diff --git a/unix/columns.c b/unix/columns.c index a1049b47..917b39e6 100644 --- a/unix/columns.c +++ b/unix/columns.c @@ -332,7 +332,6 @@ static void columns_remove(GtkContainer *container, GtkWidget *widget) ColumnsChild *child; GtkWidget *childw; GList *children; - bool was_visible; g_return_if_fail(container != NULL); g_return_if_fail(IS_COLUMNS(container)); @@ -346,23 +345,28 @@ static void columns_remove(GtkContainer *container, GtkWidget *widget) if (child->widget != widget) continue; - was_visible = gtk_widget_get_visible(widget); + bool need_layout = false; + if (gtk_widget_get_visible(widget)) + need_layout = true; gtk_widget_unparent(widget); cols->children = g_list_remove_link(cols->children, children); g_list_free(children); - if (child->same_height_as) { - g_return_if_fail(child->same_height_as->same_height_as == child); - child->same_height_as->same_height_as = NULL; - if (gtk_widget_get_visible(child->same_height_as->widget)) - gtk_widget_queue_resize(GTK_WIDGET(container)); - } + /* Unlink this widget from its valign list, and if anything + * else on the list is still visible, ensure we recompute our + * layout */ + for (ColumnsChild *ch = child->valign_next; ch != child; + ch = ch->valign_next) + if (gtk_widget_get_visible(ch->widget)) + need_layout = true; + child->valign_next->valign_prev = child->valign_prev; + child->valign_prev->valign_next = child->valign_next; if (cols->vexpand == child) cols->vexpand = NULL; g_free(child); - if (was_visible) + if (need_layout) gtk_widget_queue_resize(GTK_WIDGET(container)); break; } @@ -465,7 +469,8 @@ void columns_add(Columns *cols, GtkWidget *child, childdata->colstart = colstart; childdata->colspan = colspan; childdata->force_left = false; - childdata->same_height_as = NULL; + childdata->valign_next = childdata; + childdata->valign_prev = childdata; childdata->percentages = NULL; cols->children = g_list_append(cols->children, childdata); @@ -516,7 +521,7 @@ void columns_force_left_align(Columns *cols, GtkWidget *widget) gtk_widget_queue_resize(GTK_WIDGET(cols)); } -void columns_force_same_height(Columns *cols, GtkWidget *cw1, GtkWidget *cw2) +void columns_align_next_to(Columns *cols, GtkWidget *cw1, GtkWidget *cw2) { ColumnsChild *child1, *child2; @@ -530,8 +535,13 @@ void columns_force_same_height(Columns *cols, GtkWidget *cw1, GtkWidget *cw2) child2 = columns_find_child(cols, cw2); g_return_if_fail(child2 != NULL); - child1->same_height_as = child2; - child2->same_height_as = child1; + ColumnsChild *child1prev = child1->valign_prev; + ColumnsChild *child2prev = child2->valign_prev; + child1prev->valign_next = child2; + child2->valign_prev = child1prev; + child2prev->valign_next = child1; + child1->valign_prev = child2prev; + if (gtk_widget_get_visible(cw1) || gtk_widget_get_visible(cw2)) gtk_widget_queue_resize(GTK_WIDGET(cols)); } @@ -843,8 +853,9 @@ static gint columns_compute_height(Columns *cols, widget_dim_fn_t get_height) continue; childheight = get_height(child); - if (child->same_height_as) { - gint childheight2 = get_height(child->same_height_as); + for (ColumnsChild *ch = child->valign_next; ch != child; + ch = ch->valign_next) { + gint childheight2 = get_height(ch); if (childheight < childheight2) childheight = childheight2; } @@ -902,6 +913,11 @@ static void columns_alloc_vert(Columns *cols, gint ourheight, colypos = g_new(gint, 1); colypos[0] = 0; + for (children = cols->children; + children && (child = children->data); + children = children->next) + child->visited = false; + for (children = cols->children; children && (child = children->data); children = children->next) { @@ -922,14 +938,19 @@ static void columns_alloc_vert(Columns *cols, gint ourheight, if (!gtk_widget_get_visible(child->widget)) continue; + int ymin = 0; + realheight = get_height(child); if (child == cols->vexpand) realheight += vexpand_extra; fakeheight = realheight; - if (child->same_height_as) { - gint childheight2 = get_height(child->same_height_as); + for (ColumnsChild *ch = child->valign_next; ch != child; + ch = ch->valign_next) { + gint childheight2 = get_height(ch); if (fakeheight < childheight2) fakeheight = childheight2; + if (ch->visited && ymin < ch->y) + ymin = ch->y; } colspan = child->colspan ? child->colspan : ncols-child->colstart; @@ -943,13 +964,14 @@ static void columns_alloc_vert(Columns *cols, gint ourheight, { int topy, boty; - topy = 0; + topy = ymin; for (i = 0; i < colspan; i++) { if (topy < colypos[child->colstart+i]) topy = colypos[child->colstart+i]; } child->y = topy + fakeheight/2 - realheight/2; child->h = realheight; + child->visited = true; boty = topy + fakeheight + cols->spacing; for (i = 0; i < colspan; i++) { colypos[child->colstart+i] = boty; diff --git a/unix/columns.h b/unix/columns.h index d2d58c4a..bca306b4 100644 --- a/unix/columns.h +++ b/unix/columns.h @@ -43,11 +43,17 @@ struct ColumnsChild_tag { GtkWidget *widget; gint colstart, colspan; bool force_left; /* for recalcitrant GtkLabels */ - ColumnsChild *same_height_as; /* Otherwise, this entry represents a change in the column setup. */ gint ncols; gint *percentages; gint x, y, w, h; /* used during an individual size computation */ + + /* Circularly linked list of children that are vertically aligned + * with each other. */ + ColumnsChild *valign_next, *valign_prev; + + /* Temporary space used within some methods */ + bool visited; }; GType columns_get_type(void); @@ -57,7 +63,7 @@ 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); -void columns_force_same_height(Columns *cols, GtkWidget *ch1, GtkWidget *ch2); +void columns_align_next_to(Columns *cols, GtkWidget *ch1, GtkWidget *ch2); void columns_vexpand(Columns *cols, GtkWidget *child); #ifdef __cplusplus diff --git a/unix/dialog.c b/unix/dialog.c index bd5d535f..5b205201 100644 --- a/unix/dialog.c +++ b/unix/dialog.c @@ -2109,8 +2109,7 @@ GtkWidget *layout_ctrls( columns_add(COLUMNS(container), label, 0, 1); columns_force_left_align(COLUMNS(container), label); columns_add(COLUMNS(container), w, 1, 1); - columns_force_same_height(COLUMNS(container), - label, w); + columns_align_next_to(COLUMNS(container), label, w); } gtk_widget_show(label); gtk_widget_show(w); @@ -2163,7 +2162,7 @@ GtkWidget *layout_ctrls( columns_add(COLUMNS(w), ww, 1, 1); gtk_widget_show(ww); - columns_force_same_height(COLUMNS(w), uc->entry, uc->button); + columns_align_next_to(COLUMNS(w), uc->entry, uc->button); g_signal_connect(G_OBJECT(uc->entry), "key_press_event", G_CALLBACK(editbox_key), dp); @@ -2456,8 +2455,7 @@ GtkWidget *layout_ctrls( columns_add(COLUMNS(container), label, 0, 1); columns_force_left_align(COLUMNS(container), label); columns_add(COLUMNS(container), w, 1, 1); - columns_force_same_height(COLUMNS(container), - label, w); + columns_align_next_to(COLUMNS(container), label, w); } gtk_widget_show(label); gtk_widget_show(w); @@ -2520,19 +2518,10 @@ GtkWidget *layout_ctrls( if (left) columns_force_left_align(cols, w); if (ctrl->align_next_to) { - /* - * Implement align_next_to by simply forcing the two - * controls to have the same height of size allocation. At - * least for the controls we're currently doing this with, - * the GTK layout system will automatically vertically - * centre each control within its allocation, which will - * get the two controls aligned alongside each other - * reasonably well. - */ struct uctrl *uc2 = dlg_find_byctrl( dp, ctrl->align_next_to); assert(uc2); - columns_force_same_height(cols, w, uc2->toplevel); + columns_align_next_to(cols, w, uc2->toplevel); #if GTK_CHECK_VERSION(3, 10, 0) /* Slightly nicer to align baselines than just vertically diff --git a/utils/CMakeLists.txt b/utils/CMakeLists.txt index dfd28efd..2c99217a 100644 --- a/utils/CMakeLists.txt +++ b/utils/CMakeLists.txt @@ -14,6 +14,7 @@ add_sources_from_current_dir(utils conf_dest.c conf_launchable.c ctrlparse.c + ctrlset_normalise.c debug.c decode_utf8.c decode_utf8_to_wchar.c diff --git a/utils/ctrlset_normalise.c b/utils/ctrlset_normalise.c new file mode 100644 index 00000000..46f5f19f --- /dev/null +++ b/utils/ctrlset_normalise.c @@ -0,0 +1,59 @@ +/* + * Helper function from the dialog.h mechanism. + */ + +#include "misc.h" +#include "dialog.h" + +void ctrlset_normalise_aligns(struct controlset *s) +{ + /* + * The algorithm in here is quadratic time. Never on very much data, but + * even so, let's avoid bothering to use it where possible. In most + * controlsets, there's no use of align_next_to in any case, so we have + * nothing to do. + */ + for (size_t j = 0; j < s->ncontrols; j++) + if (s->ctrls[j]->align_next_to) + goto must_do_something; + /* If we fell out of this loop, there's nothing to do here */ + return; + must_do_something:; + + size_t *idx = snewn(s->ncontrols, size_t); + + /* + * Follow align_next_to links to identify, for each control, the least + * index within this controlset of things it's linked to. That way, + * controls with the same idx[j] will be in the same alignment class. + */ + for (size_t j = 0; j < s->ncontrols; j++) { + dlgcontrol *c = s->ctrls[j]; + idx[j] = j; + if (c->align_next_to) { + for (size_t k = 0; k < j; k++) { + if (s->ctrls[k] == c->align_next_to) { + idx[j] = idx[k]; + break; + } + } + } + } + + /* + * Having done that, re-link each control to the most recent one in its + * class, so that the links form a backward linked list. + */ + for (size_t j = 0; j < s->ncontrols; j++) { + dlgcontrol *c = s->ctrls[j]; + c->align_next_to = NULL; + for (size_t k = j; k-- > 0 ;) { + if (idx[k] == idx[j]) { + c->align_next_to = s->ctrls[k]; + break; + } + } + } + + sfree(idx); +} diff --git a/windows/controls.c b/windows/controls.c index e82d215d..d0de4674 100644 --- a/windows/controls.c +++ b/windows/controls.c @@ -1367,6 +1367,8 @@ void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, base_id = *id; + ctrlset_normalise_aligns(s); + /* Start a containing box, if we have a boxname. */ if (s->boxname && *s->boxname) { struct winctrl *c = snew(struct winctrl); @@ -1718,21 +1720,36 @@ void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, * moving one or the other downwards so that they're * centred on a common horizontal line. */ - struct winctrl *c2 = winctrl_findbyctrl( - wc, ctrl->align_next_to); - HWND win1 = GetDlgItem(pos.hwnd, c->align_id); - HWND win2 = GetDlgItem(pos.hwnd, c2->align_id); - RECT rect1, rect2; - if (win1 && win2 && - GetWindowRect(win1, &rect1) && - GetWindowRect(win2, &rect2)) { - LONG top = (rect1.top < rect2.top ? rect1.top : rect2.top); - LONG bottom = (rect1.bottom > rect2.bottom ? - rect1.bottom : rect2.bottom); - move_windows(pos.hwnd, c->base_id, c->num_ids, - (top + bottom - rect1.top - rect1.bottom)/2); - move_windows(pos.hwnd, c2->base_id, c2->num_ids, - (top + bottom - rect2.top - rect2.bottom)/2); + LONG mid2 = 0; + for (dlgcontrol *thisctrl = ctrl; thisctrl; + thisctrl = thisctrl->align_next_to) { + struct winctrl *thisc = winctrl_findbyctrl(wc, thisctrl); + assert(thisc); + + HWND win = GetDlgItem(pos.hwnd, thisc->align_id); + assert(win); + + RECT rect; + if (!GetWindowRect(win, &rect)) + continue; + if (mid2 < rect.top + rect.bottom) + mid2 = rect.top + rect.bottom; + } + + for (dlgcontrol *thisctrl = ctrl; thisctrl; + thisctrl = thisctrl->align_next_to) { + struct winctrl *thisc = winctrl_findbyctrl(wc, thisctrl); + assert(thisc); + + HWND win = GetDlgItem(pos.hwnd, thisc->align_id); + assert(win); + + RECT rect; + if (!GetWindowRect(win, &rect)) + continue; + + LONG dy = (mid2 - (rect.top + rect.bottom)) / 2; + move_windows(pos.hwnd, c->base_id, c->num_ids, dy); } } } else { -- cgit v1.2.3 From 22a80a234dcff892aece29042029f52b65f44292 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 2 May 2022 15:40:44 +0100 Subject: GTK: change implementation of 100%-width editboxes. Previously, in the code that instantiated the dialog.h portable control spec, an edit control with width=100 would be implemented as a small Columns object containing the label and the edit control atop each other. Now, instead, the two controls are placed separately into the containing Columns. Combined with the changes just made to the align_next_to system, this means that you can put buttons to the right of such an edit box and have them line up with the actual edit box, instead of trying to line up with the combination of the box and its label. (The Windows alignment system already identified the specific edit box control as the relevant one, so this was already working there.) --- unix/dialog.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/unix/dialog.c b/unix/dialog.c index 5b205201..8038a2e5 100644 --- a/unix/dialog.c +++ b/unix/dialog.c @@ -2089,19 +2089,20 @@ GtkWidget *layout_ctrls( #endif if (ctrl->label) { - GtkWidget *label, *container; + GtkWidget *label; label = gtk_label_new(ctrl->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); + columns_add(cols, label, + COLUMN_START(ctrl->column), + COLUMN_SPAN(ctrl->column)); + columns_force_left_align(cols, label); } else { + GtkWidget *container = columns_new(4); gint percentages[2]; percentages[1] = ctrl->editbox.percentwidth; percentages[0] = 100 - ctrl->editbox.percentwidth; @@ -2110,11 +2111,11 @@ GtkWidget *layout_ctrls( columns_force_left_align(COLUMNS(container), label); columns_add(COLUMNS(container), w, 1, 1); columns_align_next_to(COLUMNS(container), label, w); + gtk_widget_show(w); + w = container; } - gtk_widget_show(label); - gtk_widget_show(w); - w = container; + gtk_widget_show(label); uc->label = label; } break; -- cgit v1.2.3 From 5ca78237edf7873fdec01fb657c023d9777faf5d Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 5 May 2022 18:07:09 +0100 Subject: CA config box: add some align_next_to. Now the RSA signature-type checkboxes should be aligned with their label; the 'Add' and 'Remove' buttons for wildcards should align with the edit box for entering a wildcard; and the 'Load from file' button for a public key aligns with the edit box for that. --- ssh/ca-config.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ssh/ca-config.c b/ssh/ca-config.c index dc047039..afabbc95 100644 --- a/ssh/ca-config.c +++ b/ssh/ca-config.c @@ -463,6 +463,7 @@ void setup_ca_config_box(struct controlbox *b) "Select public key file of certification authority", HELPCTX(no_help), ca_pubkey_file_handler, P(st)); c->fileselect.just_button = true; + c->align_next_to = st->ca_pubkey_edit; c->column = 1; ctrl_columns(s, 1, 100); @@ -478,28 +479,34 @@ void setup_ca_config_box(struct controlbox *b) st->ca_wc_edit = c; c = ctrl_pushbutton(s, "Add", NO_SHORTCUT, HELPCTX(no_help), ca_wc_add_handler, P(st)); + c->align_next_to = st->ca_wc_edit; c->column = 1; c = ctrl_pushbutton(s, "Remove", NO_SHORTCUT, HELPCTX(no_help), ca_wc_rem_handler, P(st)); + c->align_next_to = st->ca_wc_edit; c->column = 2; ctrl_columns(s, 1, 100); ctrl_columns(s, 4, 44, 18, 18, 18); c = ctrl_text(s, "Signature types (RSA keys only):", HELPCTX(no_help)); c->column = 0; + dlgcontrol *sigtypelabel = c; c = ctrl_checkbox(s, "SHA-1", NO_SHORTCUT, HELPCTX(no_help), ca_rsa_type_handler, P(st)); c->column = 1; + c->align_next_to = sigtypelabel; c->context2 = I(offsetof(ca_options, permit_rsa_sha1)); st->rsa_type_checkboxes[0] = c; c = ctrl_checkbox(s, "SHA-256", NO_SHORTCUT, HELPCTX(no_help), ca_rsa_type_handler, P(st)); c->column = 2; + c->align_next_to = sigtypelabel; c->context2 = I(offsetof(ca_options, permit_rsa_sha256)); st->rsa_type_checkboxes[1] = c; c = ctrl_checkbox(s, "SHA-512", NO_SHORTCUT, HELPCTX(no_help), ca_rsa_type_handler, P(st)); c->column = 3; + c->align_next_to = sigtypelabel; c->context2 = I(offsetof(ca_options, permit_rsa_sha512)); st->rsa_type_checkboxes[2] = c; ctrl_columns(s, 1, 100); -- cgit v1.2.3 From ab70bda4c72c0eb245aa65fdcd5e487c82f90477 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 6 May 2022 17:51:13 +0100 Subject: ntru_gen_short: remove quadratic-time shuffle. This function has to make an array containing a specific number of random values that are +1 or -1, and all the rest zero. The simplest way I could think of to do it at first was to make the array with all the zeroes at the end and then shuffle the array. But I couldn't think of a time-safe algorithm to shuffle an array in such a way that all orders come out equiprobable, that was better than quadratic time. In fact I still can't think of one. (Making a random Benes network is the best idea I've come up with: it arranges that every output order is _possible_, and runs in O(N log N) time, but it skews the probabilities, which makes it unacceptable.) However, there's no need to shuffle an array in this application anyway: we're not actually trying to generate a random _permutation_, only a random element of (n choose w). So we can just walk linearly along the array remembering how many nonzero elements we have yet to output, and using an appropriately chosen random number at each step to decide whether this will be one of them. This isn't a significant improvement in the performance of NTRU overall, but it satisfies my sense of rightness a little, and at least means I don't have to have a comment in the code apologising for the terrible algorithm any more. --- crypto/ntru.c | 48 ++++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/crypto/ntru.c b/crypto/ntru.c index 9cc11c80..c8f6dfeb 100644 --- a/crypto/ntru.c +++ b/crypto/ntru.c @@ -906,32 +906,36 @@ const uint16_t *ntru_pubkey(NTRUKeyPair *keypair) { return keypair->h; } * private key, a plaintext, and the 'rho' fake-plaintext value used * for deliberately returning a duff but non-revealing session hash if * things go wrong. + * + * -1 is represented as 2 in the output array. So if you want these + * numbers mod 3, then they come out already in the right form. + * Otherwise, use ntru_expand. */ void ntru_gen_short(uint16_t *v, unsigned p, unsigned w) { /* * Get enough random data to generate a polynomial all of whose p * terms are in {0,+1,-1}, and exactly w of them are nonzero. + * We'll do this by making up a completely random sequence of + * {+1,-1} and then setting a random subset of them to 0. * - * We're going to need w * random bits to choose the nonzero - * values, and then (doing it the simplest way) log2(p!) bits to - * shuffle them, plus say 128 bits to ensure any fluctuations in - * uniformity are negligible. + * So we'll need p random bits to choose the nonzero values, and + * then (doing it the simplest way) log2(p!) bits to shuffle them, + * plus say 128 bits to ensure any fluctuations in uniformity are + * negligible. * * log2(p!) is a pain to calculate, so we'll bound it above by * p*log2(p), which we bound in turn by p*16. */ - size_t randbitpos = 16 * p + w + 128; + size_t randbitpos = 17 * p + 128; mp_int *randdata = mp_resize(mp_random_bits(randbitpos), randbitpos + 32); /* - * Initial value before shuffling: w randomly chosen values in - * {1,q-1}, plus zeroes to pad to length p. + * Initial value before zeroing out some terms: p randomly chosen + * values in {1,2}. */ - for (size_t i = 0; i < w; i++) + for (size_t i = 0; i < p; i++) v[i] = 1 + mp_get_bit(randdata, --randbitpos); - for (size_t i = w; i < p; i++) - v[i] = 0; /* * Hereafter we're going to extract random bits by multiplication, @@ -940,13 +944,14 @@ void ntru_gen_short(uint16_t *v, unsigned p, unsigned w) mp_reduce_mod_2to(randdata, randbitpos); /* - * Shuffle. + * Zero out some terms, leaving a randomly selected w of them + * nonzero. */ + uint32_t nonzeros_left = w; mp_int *x = mp_new(64); - for (size_t i = p-1; i > 0; i--) { + for (size_t i = p; i-- > 0 ;) { /* - * Decide which element to swap with v[i], potentially - * including i itself. + * Pick a random number out of the number of terms remaning. */ mp_mul_integer_into(randdata, randdata, i+1); mp_rshift_fixed_into(x, randdata, randbitpos); @@ -954,17 +959,12 @@ void ntru_gen_short(uint16_t *v, unsigned p, unsigned w) size_t j = mp_get_integer(x); /* - * Swap it, which involves a constant-time selection loop over - * the whole eligible part of the array. This makes the - * shuffling quadratic-time overall. I'd be interested in a - * nicer algorithm, but this will do for now. + * If that's less than nonzeros_left, then we're leaving this + * number nonzero. Otherwise we're zeroing it out. */ - for (size_t k = 0; k <= i; k++) { - uint16_t mask = -iszero(k ^ j); - uint16_t diff = mask & (v[k] ^ v[i]); - v[k] ^= diff; - v[i] ^= diff; - } + uint32_t keep = (uint32_t)(j - nonzeros_left) >> 31; + v[i] &= -keep; /* clear this field if keep == 0 */ + nonzeros_left -= keep; /* decrement counter if keep == 1 */ } mp_free(x); -- cgit v1.2.3 From 5390aef3fc7deca225dd76db15021dc5c4ab53f2 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 7 May 2022 11:00:19 +0100 Subject: GTK: make explicit text controls selectable. This doesn't apply to every GtkLabel I instantiate: only the ones constructed as part of implementing the cross-platform CTRL_TEXT. Those labels contain information that the dialog box is deliberately communicating to the user, so it seems a sensible idea to make sure they can be copy-pasted. By default, this also seems to cause them to become able to take the input focus, so I've reverted that. You can select them with the mouse, but I think having them appear in the tab order is an awkwardness too far, since they're not active in any other way. --- unix/dialog.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/unix/dialog.c b/unix/dialog.c index 8038a2e5..aa547aa7 100644 --- a/unix/dialog.c +++ b/unix/dialog.c @@ -2506,6 +2506,8 @@ GtkWidget *layout_ctrls( */ uc->text = w = gtk_label_new(uc->ctrl->label); #endif + gtk_label_set_selectable(GTK_LABEL(w), true); + gtk_widget_set_can_focus(w, false); align_label_left(GTK_LABEL(w)); gtk_label_set_line_wrap(GTK_LABEL(w), true); break; -- cgit v1.2.3 From cd094b28a3b17793d689f1fd7ea66e6b82c9f413 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 7 May 2022 08:23:38 +0100 Subject: Allow CTRL_TEXT controls to be non-wrapping. This is for cases where they're presenting information to the user that wouldn't wrap sensibly anyway (such as an SSH key fingerprint which is mostly all one word), and in which newlines might be significant. On GTK, the implementing widget is still a GtkLabel, but without the wrap flag set, and wrapped in a GtkScrolledWindow in case the text is too wide to fit. On Windows, I've switched to using an edit box instead of a static text control, making it readonly, and borderless via my existing MakeDlgItemBorderless helper function. This doesn't get you an actual scrollbar, but it does mean you can scroll left and right by dragging with the mouse. --- dialog.c | 1 + dialog.h | 8 ++++++++ unix/dialog.c | 12 +++++++++++- windows/controls.c | 34 ++++++++++++++++++++-------------- windows/platform.h | 2 +- 5 files changed, 41 insertions(+), 16 deletions(-) diff --git a/dialog.c b/dialog.c index 13317833..04bc0d3c 100644 --- a/dialog.c +++ b/dialog.c @@ -427,6 +427,7 @@ dlgcontrol *ctrl_text(struct controlset *s, const char *text, { dlgcontrol *c = ctrl_new(s, CTRL_TEXT, helpctx, NULL, P(NULL)); c->label = dupstr(text); + c->text.wrap = true; return c; } diff --git a/dialog.h b/dialog.h index 45b4cde7..cc483328 100644 --- a/dialog.h +++ b/dialog.h @@ -426,6 +426,14 @@ struct dlgcontrol { struct { /* for CTRL_FONTSELECT */ char shortcut; } fontselect; + struct { /* for CTRL_TEXT */ + /* + * If this is true (the default), the text will wrap on to + * multiple lines. If false, it will stay on the same + * line, with a horizontal scrollbar if necessary. + */ + bool wrap; + } text; }; }; diff --git a/unix/dialog.c b/unix/dialog.c index aa547aa7..9f1d061d 100644 --- a/unix/dialog.c +++ b/unix/dialog.c @@ -2509,7 +2509,17 @@ GtkWidget *layout_ctrls( gtk_label_set_selectable(GTK_LABEL(w), true); gtk_widget_set_can_focus(w, false); align_label_left(GTK_LABEL(w)); - gtk_label_set_line_wrap(GTK_LABEL(w), true); + gtk_label_set_line_wrap(GTK_LABEL(w), ctrl->text.wrap); + if (!ctrl->text.wrap) { + gtk_widget_show(uc->text); + w = gtk_scrolled_window_new(NULL, NULL); + gtk_container_set_border_width(GTK_CONTAINER(w), 0); + gtk_container_add(GTK_CONTAINER(w), uc->text); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_NEVER); + gtk_widget_set_can_focus(w, false); + } break; } diff --git a/windows/controls.c b/windows/controls.c index d0de4674..c0159510 100644 --- a/windows/controls.c +++ b/windows/controls.c @@ -165,7 +165,7 @@ void endbox(struct ctlpos *cp) /* * A static line, followed by a full-width edit box. */ -void editboxfw(struct ctlpos *cp, bool password, char *text, +void editboxfw(struct ctlpos *cp, bool password, bool readonly, char *text, int staticid, int editid) { RECT r; @@ -183,7 +183,8 @@ void editboxfw(struct ctlpos *cp, bool password, char *text, r.bottom = EDITHEIGHT; doctl(cp, r, "EDIT", WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL | - (password ? ES_PASSWORD : 0), + (password ? ES_PASSWORD : 0) | + (readonly ? ES_READONLY : 0), WS_EX_CLIENTEDGE, "", editid); cp->ypos += EDITHEIGHT + GAPBETWEEN; } @@ -1525,18 +1526,23 @@ void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, * switching on its type. */ switch (ctrl->type) { - case CTRL_TEXT: { - char *wrapped, *escaped; - int lines; - num_ids = 1; - wrapped = staticwrap(&pos, cp->hwnd, - ctrl->label, &lines); - escaped = shortcut_escape(wrapped, NO_SHORTCUT); - statictext(&pos, escaped, lines, base_id); - sfree(escaped); - sfree(wrapped); + case CTRL_TEXT: + if (ctrl->text.wrap) { + char *wrapped, *escaped; + int lines; + num_ids = 1; + wrapped = staticwrap(&pos, cp->hwnd, + ctrl->label, &lines); + escaped = shortcut_escape(wrapped, NO_SHORTCUT); + statictext(&pos, escaped, lines, base_id); + sfree(escaped); + sfree(wrapped); + } else { + editboxfw(&pos, false, true, NULL, 0, base_id); + SetDlgItemText(pos.hwnd, base_id, ctrl->label); + MakeDlgItemBorderless(pos.hwnd, base_id); + } break; - } case CTRL_EDITBOX: num_ids = 2; /* static, edit */ escaped = shortcut_escape(ctrl->label, @@ -1547,7 +1553,7 @@ void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, combobox(&pos, escaped, base_id, base_id+1); else - editboxfw(&pos, ctrl->editbox.password, escaped, + editboxfw(&pos, ctrl->editbox.password, false, escaped, base_id, base_id+1); } else { if (ctrl->editbox.has_list) { diff --git a/windows/platform.h b/windows/platform.h index 11103511..4e3b63f1 100644 --- a/windows/platform.h +++ b/windows/platform.h @@ -437,7 +437,7 @@ HWND doctl(struct ctlpos *cp, RECT r, 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, bool password, char *text, +void editboxfw(struct ctlpos *cp, bool password, bool readonly, char *text, int staticid, int editid); void radioline(struct ctlpos *cp, char *text, int id, int nacross, ...); void bareradioline(struct ctlpos *cp, int nacross, ...); -- cgit v1.2.3 From 4b0e54c22aaed9f6250325d8d949231b5aa678ea Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 5 May 2022 20:26:05 +0100 Subject: CA config box: fully validate the CA public key. Now we check that we can actually make an ssh_key out of it, and moreover, that the key is of a sensible kind (i.e. not a certificate in turn). If that's not true, we report something about the problem in a new CTRL_TEXT below the public key input box. If the key _is_ valid, that same text control is used to show its type, length and fingerprint. On Windows, I've widened the dialog box a little to make fingerprints fit sensibly in it. --- ssh/ca-config.c | 119 ++++++++++++++++++++++++++++++++++------------- windows/dialog.c | 4 +- windows/putty-common.rc2 | 2 +- 3 files changed, 89 insertions(+), 36 deletions(-) diff --git a/ssh/ca-config.c b/ssh/ca-config.c index afabbc95..6c84b68b 100644 --- a/ssh/ca-config.c +++ b/ssh/ca-config.c @@ -17,6 +17,7 @@ struct ca_state { dlgcontrol *ca_name_edit; dlgcontrol *ca_reclist; dlgcontrol *ca_pubkey_edit; + dlgcontrol *ca_pubkey_info; dlgcontrol *ca_wclist; dlgcontrol *ca_wc_edit; dlgcontrol *rsa_type_checkboxes[NRSATYPES]; @@ -24,6 +25,7 @@ struct ca_state { tree234 *ca_names; /* stores plain 'char *' */ tree234 *host_wcs; /* stores plain 'char *' */ ca_options opts; + strbuf *ca_pubkey_blob; }; static int ca_name_compare(void *av, void *bv) @@ -95,6 +97,78 @@ static void set_from_hca(struct ca_state *st, host_ca *hca) st->opts = hca->opts; /* structure copy */ } +static void ca_refresh_pubkey_info(struct ca_state *st, dlgparam *dp) +{ + char *text = NULL; + ssh_key *key = NULL; + strbuf *blob = strbuf_new(); + + ptrlen data = ptrlen_from_asciz(st->pubkey); + + if (st->ca_pubkey_blob) + strbuf_free(st->ca_pubkey_blob); + st->ca_pubkey_blob = NULL; + + if (!data.len) { + text = dupstr(" "); + goto out; + } + + /* + * See if we have a plain base64-encoded public key blob. + */ + if (base64_valid(data)) { + base64_decode_bs(BinarySink_UPCAST(blob), data); + } else { + /* + * Otherwise, try to decode as if it was a public key _file_. + */ + BinarySource src[1]; + BinarySource_BARE_INIT_PL(src, data); + const char *error; + if (!ppk_loadpub_s(src, NULL, BinarySink_UPCAST(blob), NULL, &error)) { + text = dupprintf("Cannot decode key: %s", error); + goto out; + } + } + + ptrlen alg_name = pubkey_blob_to_alg_name(ptrlen_from_strbuf(blob)); + if (!alg_name.len) { + text = dupstr("Invalid key (no key type)"); + goto out; + } + + const ssh_keyalg *alg = find_pubkey_alg_len(alg_name); + if (!alg) { + text = dupprintf("Unrecognised key type '%.*s'", + PTRLEN_PRINTF(alg_name)); + goto out; + } + if (alg->is_certificate) { + text = dupprintf("CA key may not be a certificate (type is '%.*s')", + PTRLEN_PRINTF(alg_name)); + goto out; + } + + key = ssh_key_new_pub(alg, ptrlen_from_strbuf(blob)); + if (!key) { + text = dupprintf("Invalid '%.*s' key data", PTRLEN_PRINTF(alg_name)); + goto out; + } + + text = ssh2_fingerprint(key, SSH_FPTYPE_DEFAULT); + st->ca_pubkey_blob = blob; + blob = NULL; /* prevent free */ + + out: + dlg_text_set(st->ca_pubkey_info, dp, text); + if (key) + ssh_key_free(key); + sfree(text); + if (blob) + strbuf_free(blob); +} + static void ca_load_selected_record(struct ca_state *st, dlgparam *dp) { int i = dlg_listbox_index(st->ca_reclist, dp); @@ -123,6 +197,7 @@ static void ca_load_selected_record(struct ca_state *st, dlgparam *dp) dlg_refresh(st->ca_wclist, dp); for (size_t i = 0; i < NRSATYPES; i++) dlg_refresh(st->rsa_type_checkboxes[i], dp); + ca_refresh_pubkey_info(st, dp); } static void ca_ok_handler(dlgcontrol *ctrl, dlgparam *dp, @@ -179,26 +254,6 @@ static void ca_load_handler(dlgcontrol *ctrl, dlgparam *dp, } } -static strbuf *decode_pubkey(ptrlen data, const char **error) -{ - /* - * See if we have a plain base64-encoded public key blob. - */ - if (base64_valid(data)) - return base64_decode_sb(data); - - /* - * Otherwise, try to decode as if it was a public key _file_. - */ - BinarySource src[1]; - BinarySource_BARE_INIT_PL(src, data); - strbuf *blob = strbuf_new(); - if (ppk_loadpub_s(src, NULL, BinarySink_UPCAST(blob), NULL, error)) - return blob; - - return NULL; -} - static void ca_save_handler(dlgcontrol *ctrl, dlgparam *dp, void *data, int event) { @@ -209,22 +264,16 @@ static void ca_save_handler(dlgcontrol *ctrl, dlgparam *dp, return; } - strbuf *pubkey; - { - const char *error; - pubkey = decode_pubkey(ptrlen_from_asciz(st->pubkey), &error); - if (!pubkey) { - char *msg = dupprintf("CA public key invalid: %s", error); - dlg_error_msg(dp, msg); - sfree(msg); - return; - } + if (!st->ca_pubkey_blob) { + dlg_error_msg(dp, "No valid CA public key entered"); + return; } host_ca *hca = snew(host_ca); memset(hca, 0, sizeof(*hca)); hca->name = dupstr(st->name); - hca->ca_public_key = pubkey; + hca->ca_public_key = strbuf_dup(ptrlen_from_strbuf( + st->ca_pubkey_blob)); hca->n_hostname_wildcards = count234(st->host_wcs); hca->hostname_wildcards = snewn(hca->n_hostname_wildcards, char *); for (size_t i = 0; i < hca->n_hostname_wildcards; i++) @@ -280,6 +329,7 @@ static void ca_pubkey_edit_handler(dlgcontrol *ctrl, dlgparam *dp, } else if (event == EVENT_VALCHANGE) { sfree(st->pubkey); st->pubkey = dlg_editbox_get(ctrl, dp); + ca_refresh_pubkey_info(st, dp); } } @@ -451,8 +501,7 @@ void setup_ca_config_box(struct controlbox *b) ca_delete_handler, P(st)); c->column = 1; - /* Box containing the details of a specific CA record */ - s = ctrl_getset(b, "Main", "details", "Details of a host CA record"); + s = ctrl_getset(b, "Main", "pubkey", "Public key for this CA record"); ctrl_columns(s, 2, 75, 25); c = ctrl_editbox(s, "Public key of certification authority", 'k', 100, @@ -466,6 +515,10 @@ void setup_ca_config_box(struct controlbox *b) c->align_next_to = st->ca_pubkey_edit; c->column = 1; ctrl_columns(s, 1, 100); + st->ca_pubkey_info = c = ctrl_text(s, " ", HELPCTX(no_help)); + c->text.wrap = false; + + s = ctrl_getset(b, "Main", "options", "What this CA is trusted to do"); c = ctrl_listbox(s, "Hostname patterns this key is trusted to certify", NO_SHORTCUT, HELPCTX(no_help), ca_wclist_handler, P(st)); diff --git a/windows/dialog.c b/windows/dialog.c index 774214db..747b5336 100644 --- a/windows/dialog.c +++ b/windows/dialog.c @@ -1186,8 +1186,8 @@ static INT_PTR CAConfigProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, centre_window(hwnd); - pds_create_controls(pds, 0, IDCX_PANELBASE, 3, 3, 13, "Main"); - pds_create_controls(pds, 0, IDCX_STDBASE, 3, 3, 235, ""); + pds_create_controls(pds, 0, IDCX_PANELBASE, 3, 3, 3, "Main"); + pds_create_controls(pds, 0, IDCX_STDBASE, 3, 3, 243, ""); dlg_refresh(NULL, pds->dp); /* and set up control values */ pds_initdialog_finish(pds); diff --git a/windows/putty-common.rc2 b/windows/putty-common.rc2 index be5202aa..cb2e177a 100644 --- a/windows/putty-common.rc2 +++ b/windows/putty-common.rc2 @@ -131,7 +131,7 @@ BEGIN END /* Accelerators used: aco */ -IDD_CA_CONFIG DIALOG DISCARDABLE 0, 0, 300, 252 +IDD_CA_CONFIG DIALOG DISCARDABLE 0, 0, 350, 260 STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "PuTTY trusted host certification authorities" FONT 8, "MS Shell Dlg" -- cgit v1.2.3 From b753cf6e3b7e439ed95b8e6df4a0aefaa5e27bed Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 7 May 2022 12:25:54 +0100 Subject: Reject multilayer certificates in check_cert. Rejecting them in the CA config box reminded me that the main checking code also ought to do the same thing. --- crypto/openssh-certs.c | 31 ++++++++++++++++++++++++++++++- test/cryptsuite.py | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/crypto/openssh-certs.c b/crypto/openssh-certs.c index 78be4f15..c27118a4 100644 --- a/crypto/openssh-certs.c +++ b/crypto/openssh-certs.c @@ -726,11 +726,40 @@ static bool opensshcert_check_cert( ptrlen signature = ptrlen_from_strbuf(ck->signature); - ca_key = opensshcert_ca_pub_key(ck, signature, NULL); + /* + * The OpenSSH certificate spec is one-layer only: it explicitly + * forbids using a certified key in turn as the CA. + * + * If it did not, then we'd also have to recursively verify + * everything up the CA chain until we reached the ultimate root, + * and then make sure _that_ was something we trusted. (Not to + * mention that there'd probably be an additional SSH_CERT_TYPE_CA + * or some such, and certificate options saying what kinds of + * certificate a CA was trusted to sign for, and ...) + */ + ca_key = opensshcert_ca_pub_key(ck, make_ptrlen(NULL, 0), NULL); if (!ca_key) { put_fmt(error, "Certificate's signing key is invalid"); goto out; } + if (ssh_key_alg(ca_key)->is_certificate) { + put_fmt(error, "Certificate is signed with a certified key " + "(forbidden by OpenSSH certificate specification)"); + goto out; + } + + /* + * Now re-instantiate the key in a way that matches the signature + * (i.e. so that if the key is an RSA one we get the right subtype + * of RSA). + */ + ssh_key_free(ca_key); + ca_key = opensshcert_ca_pub_key(ck, signature, NULL); + if (!ca_key) { + put_fmt(error, "Certificate's signing key does not match " + "signature type"); + goto out; + } /* Check which signature algorithm is actually in use, because * that might be a reason to reject the certificate (e.g. ssh-rsa diff --git a/test/cryptsuite.py b/test/cryptsuite.py index ff8cf1ed..c23bd00b 100755 --- a/test/cryptsuite.py +++ b/test/cryptsuite.py @@ -2715,6 +2715,43 @@ Private-MAC: 5b1f6f4cc43eb0060d2c3e181bc0129343adba2b False, b'username', 1000, '') self.assertEqual(result, True) + # Make a certificate on the CA key, and re-sign the main + # key using that, to ensure that two-level certs are rejected + ca_self_certificate = sign_cert_via_testcrypt( + make_signature_preimage( + key_to_certify = ca_key.public_blob(), + ca_key = ca_key, + certtype = CertType.user, + keyid = b'id', + serial = 111, + principals = [b"doesn't matter"], + valid_after = 1000, + valid_before = 2000), ca_key, signflags=ca_signflags) + import base64 + print(base64.b64encode(ca_self_certificate)) + self_signed_ca_key = ssh_key_new_pub( + alg + '-cert', ca_self_certificate) + print(self_signed_ca_key) + cert_pub = sign_cert_via_testcrypt( + make_signature_preimage( + key_to_certify = base_key.public_blob(), + ca_key = self_signed_ca_key, + certtype = CertType.user, + keyid = b'id', + serial = 111, + principals = [b'username'], + valid_after = 1000, + valid_before = 2000), ca_key, signflags=ca_signflags) + print(base64.b64encode(cert_pub)) + certified_key = ssh_key_new_priv(alg + '-cert', cert_pub, + base_key.private_blob()) + result, err = certified_key.check_cert( + False, b'username', 1500, '') + self.assertEqual(result, False) + self.assertEqual( + err, b'Certificate is signed with a certified key ' + b'(forbidden by OpenSSH certificate specification)') + # Now try a host certificate. We don't need to do _all_ the # checks over again, but at least make sure that setting # CertType.host leads to the certificate validating with -- cgit v1.2.3 From ac3ebcc827983aa54f3d7e3a45e9bf55b89b24aa Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 8 May 2022 08:33:53 +0100 Subject: Fixes for Winelib builds. In Winelib, you have to be careful not to say 'unsigned long' where the API expects ULONG, because Winelib doesn't have the Windows LLP64 nature - its unsigned long is 64 bits, whereas ULONG is 32. Also, my local Winelib has (used in the new demo-screenshot system), but doesn't contain some of the definitions inside it. So I've expanded the cmake test of HAVE_DWMAPI_H so that it actually checks the things we need, instead of just the existence of the containing header. --- cmake/platforms/windows.cmake | 17 +++++++++++++++-- windows/gss.c | 2 +- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/cmake/platforms/windows.cmake b/cmake/platforms/windows.cmake index e1da07dc..607506e9 100644 --- a/cmake/platforms/windows.cmake +++ b/cmake/platforms/windows.cmake @@ -49,14 +49,27 @@ check_symbol_exists(GetNamedPipeClientProcessId "windows.h" HAVE_GETNAMEDPIPECLIENTPROCESSID) check_symbol_exists(CreatePseudoConsole "windows.h" HAVE_CONPTY) -check_include_files("windows.h;dwmapi.h" HAVE_DWMAPI_H) - check_c_source_compiles(" #include GCP_RESULTSW gcpw; int main(void) { return 0; } " HAVE_GCP_RESULTSW) +function(dwmapi_test_wrapper) + set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} dwmapi.lib) + check_c_source_compiles(" +#include +#include +volatile HWND hwnd; +int main(void) { + RECT r; + DwmGetWindowAttribute(hwnd, DWMWA_EXTENDED_FRAME_BOUNDS, &r, sizeof(r)); +} +" HAVE_DWMAPI_H) + set(HAVE_DWMAPI_H ${HAVE_DWMAPI_H} PARENT_SCOPE) +endfunction() +dwmapi_test_wrapper() + set(NO_SECURITY ${PUTTY_NO_SECURITY}) add_compile_definitions( diff --git a/windows/gss.c b/windows/gss.c index ebe5698d..cadb1d5b 100644 --- a/windows/gss.c +++ b/windows/gss.c @@ -465,7 +465,7 @@ static Ssh_gss_stat ssh_sspi_init_sec_context(struct ssh_gss_library *lib, 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; + ULONG ret_flags=0; TimeStamp localexp; /* check if we have to delegate ... */ -- cgit v1.2.3 From e29b6eb5d2f81298a1d214cf1c3dc0653d6b900b Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 8 May 2022 08:51:45 +0100 Subject: Memory handling fixes when taking demo screenshots. save_screenshot() returns a dynamically allocated error message in case of failure, and Coverity complained of a memory leak when it was ignored in putty.c. The memory leak is trivial, because we were about to terminate the process with an error anyway. But it's a good point that I forgot to report the error! Not critical enough to fix on 0.77 (where Coverity found it), but we might as well make it look sensible on main. --- windows/dialog.c | 6 ++++-- windows/putty.c | 6 +++++- windows/puttygen.c | 6 ++++-- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/windows/dialog.c b/windows/dialog.c index 747b5336..f65c8a61 100644 --- a/windows/dialog.c +++ b/windows/dialog.c @@ -621,11 +621,13 @@ static INT_PTR GenericMainDlgProc(HWND hwnd, UINT msg, WPARAM wParam, if (dialog_box_demo_screenshot_filename && (UINT_PTR)wParam == DEMO_SCREENSHOT_TIMER_ID) { KillTimer(hwnd, DEMO_SCREENSHOT_TIMER_ID); - const char *err = save_screenshot( + char *err = save_screenshot( hwnd, dialog_box_demo_screenshot_filename); - if (err) + if (err) { MessageBox(hwnd, err, "Demo screenshot failure", MB_OK | MB_ICONERROR); + sfree(err); + } ShinyEndDialog(hwnd, 0); } return 0; diff --git a/windows/putty.c b/windows/putty.c index 53c8b4a5..823d0a30 100644 --- a/windows/putty.c +++ b/windows/putty.c @@ -180,7 +180,11 @@ const wchar_t *get_app_user_model_id(void) static void demo_terminal_screenshot(void *ctx, unsigned long now) { HWND hwnd = (HWND)ctx; - save_screenshot(hwnd, terminal_demo_screenshot_filename); + char *err = save_screenshot(hwnd, terminal_demo_screenshot_filename); + if (err) { + MessageBox(hwnd, err, "Demo screenshot failure", MB_OK | MB_ICONERROR); + sfree(err); + } cleanup_exit(0); } diff --git a/windows/puttygen.c b/windows/puttygen.c index 5beaed12..ea3557b0 100644 --- a/windows/puttygen.c +++ b/windows/puttygen.c @@ -1592,10 +1592,12 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, case WM_TIMER: if ((UINT_PTR)wParam == DEMO_SCREENSHOT_TIMER_ID) { KillTimer(hwnd, DEMO_SCREENSHOT_TIMER_ID); - const char *err = save_screenshot(hwnd, demo_screenshot_filename); - if (err) + char *err = save_screenshot(hwnd, demo_screenshot_filename); + if (err) { MessageBox(hwnd, err, "Demo screenshot failure", MB_OK | MB_ICONERROR); + sfree(err); + } EndDialog(hwnd, 0); } return 0; -- cgit v1.2.3 From e9de549e7e0b4f31683f8815e1b402a2f63e0fa3 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 8 May 2022 11:06:14 +0100 Subject: More assorted Winelib warning fixes. The previous fix on pre-0.77 was non-disruptive and just enough to get through my Coverity build (which uses winelib); but now that I look at the rest of the Winelib build output, there are some further warnings I should fix on main. Most of them are more long/LONG confusion (specific to Winelib, rather than real Windows); also, there's a multiple macro definition in jump-list.c because Winelib defines _PROPVARIANT_INIT_DEFINED_ in place of _PROPVARIANTINIT_DEFINED_ which we were testing for. (Bah.) And in windows/window.c I used wcscmp without including . In spite of long vs LONG I still had to turn off one or two more DLL-loading typechecks. --- windows/gss.c | 3 ++- windows/jump-list.c | 2 +- windows/network.c | 22 ++++++++++++---------- windows/platform.h | 4 ++-- windows/window.c | 1 + 5 files changed, 18 insertions(+), 14 deletions(-) diff --git a/windows/gss.c b/windows/gss.c index cadb1d5b..646e17fc 100644 --- a/windows/gss.c +++ b/windows/gss.c @@ -227,7 +227,8 @@ struct ssh_gss_liblist *ssh_gss_setup(Conf *conf) lib->gsslogmsg = "Using SSPI from SECUR32.DLL"; lib->handle = (void *)module; - GET_WINDOWS_FUNCTION(module, AcquireCredentialsHandleA); + /* No typecheck because Winelib thinks one PVOID is a PLUID */ + GET_WINDOWS_FUNCTION_NO_TYPECHECK(module, AcquireCredentialsHandleA); GET_WINDOWS_FUNCTION(module, InitializeSecurityContextA); GET_WINDOWS_FUNCTION(module, FreeContextBuffer); GET_WINDOWS_FUNCTION(module, FreeCredentialsHandle); diff --git a/windows/jump-list.c b/windows/jump-list.c index 43c1e66d..37a17cc9 100644 --- a/windows/jump-list.c +++ b/windows/jump-list.c @@ -41,7 +41,7 @@ typedef struct _tagpropertykey { typedef PROPVARIANT *REFPROPVARIANT; #endif /* MinGW doesn't define this yet: */ -#ifndef _PROPVARIANTINIT_DEFINED_ +#if !defined _PROPVARIANTINIT_DEFINED_ && !defined _PROPVARIANT_INIT_DEFINED_ #define _PROPVARIANTINIT_DEFINED_ #define PropVariantInit(pvar) memset((pvar),0,sizeof(PROPVARIANT)) #endif diff --git a/windows/network.c b/windows/network.c index a07b46bd..4bb8dde3 100644 --- a/windows/network.c +++ b/windows/network.c @@ -180,16 +180,16 @@ static int cmpforsearch(void *av, void *bv) 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, ULONG, ntohl, (ULONG)); +DECL_WINDOWS_FUNCTION(static, ULONG, htonl, (ULONG)); +DECL_WINDOWS_FUNCTION(static, USHORT, htons, (USHORT)); +DECL_WINDOWS_FUNCTION(static, USHORT, ntohs, (USHORT)); 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, ULONG, inet_addr, (const char FAR *)); DECL_WINDOWS_FUNCTION(static, char FAR *, inet_ntoa, (struct in_addr)); DECL_WINDOWS_FUNCTION(static, const char FAR *, inet_ntop, (int, void FAR *, char *, size_t)); @@ -204,7 +204,7 @@ DECL_WINDOWS_FUNCTION(static, int, listen, (SOCKET, int)); DECL_WINDOWS_FUNCTION(static, int, send, (SOCKET, const char FAR *, int, int)); DECL_WINDOWS_FUNCTION(static, int, shutdown, (SOCKET, int)); DECL_WINDOWS_FUNCTION(static, int, ioctlsocket, - (SOCKET, long, u_long FAR *)); + (SOCKET, LONG, ULONG FAR *)); DECL_WINDOWS_FUNCTION(static, SOCKET, accept, (SOCKET, struct sockaddr FAR *, int FAR *)); DECL_WINDOWS_FUNCTION(static, int, getpeername, @@ -305,10 +305,12 @@ void sk_init(void) 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); + /* Winelib maps ntohl and friends to things like + * __wine_ulong_swap, which fail these type checks hopelessly */ + GET_WINDOWS_FUNCTION_NO_TYPECHECK(winsock_module, ntohl); + GET_WINDOWS_FUNCTION_NO_TYPECHECK(winsock_module, htonl); + GET_WINDOWS_FUNCTION_NO_TYPECHECK(winsock_module, htons); + GET_WINDOWS_FUNCTION_NO_TYPECHECK(winsock_module, ntohs); GET_WINDOWS_FUNCTION_NO_TYPECHECK(winsock_module, gethostname); GET_WINDOWS_FUNCTION(winsock_module, gethostbyname); GET_WINDOWS_FUNCTION(winsock_module, getservbyname); diff --git a/windows/platform.h b/windows/platform.h index 4e3b63f1..c33ca1c4 100644 --- a/windows/platform.h +++ b/windows/platform.h @@ -306,9 +306,9 @@ Socket *sk_newlistener_unix(const char *socketpath, Plug *plug); * here they are. */ DECL_WINDOWS_FUNCTION(extern, int, WSAAsyncSelect, - (SOCKET, HWND, u_int, long)); + (SOCKET, HWND, u_int, LONG)); DECL_WINDOWS_FUNCTION(extern, int, WSAEventSelect, - (SOCKET, WSAEVENT, long)); + (SOCKET, WSAEVENT, LONG)); DECL_WINDOWS_FUNCTION(extern, int, WSAGetLastError, (void)); DECL_WINDOWS_FUNCTION(extern, int, WSAEnumNetworkEvents, (SOCKET, WSAEVENT, LPWSANETWORKEVENTS)); diff --git a/windows/window.c b/windows/window.c index bd13d228..59c56ee1 100644 --- a/windows/window.c +++ b/windows/window.c @@ -9,6 +9,7 @@ #include #include #include +#include #define COMPILE_MULTIMON_STUBS -- cgit v1.2.3 From de66b0313a23d04312235cbf2054b882f554d21f Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Wed, 11 May 2022 20:07:31 +0100 Subject: Allow terminating OSC sequences during setup. A user reports that the xterm OSC 112 sequence (reset cursor colour) is sometimes sent as simply OSC 112 BEL, rather than OSC 112 ; BEL. When xterm parses this, the BEL still acts as an OSC terminator, even though it appears before the separating semicolon that shifts into the 'absorb the notional command string' state. PuTTY doesn't support that sequence at all. But currently, the way it doesn't support it is by treating the BEL completely normally, so that you get an annoying beep when a client application sends that abbreviated sequence. Now we recognise all the OSC terminator sequences even in the OSC setup termstates, as well as the final OSC_STRING state. That goes equally for BEL, ST in the form of ESC \, ST in the form of single-byte 0x9C, and ST in the UTF-8 encoding. --- terminal/terminal.c | 41 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/terminal/terminal.c b/terminal/terminal.c index ae5e4144..ca36e4f9 100644 --- a/terminal/terminal.c +++ b/terminal/terminal.c @@ -3822,6 +3822,19 @@ static void term_out(Terminal *term, bool called_from_term_data) } break; case '\007': { /* BEL: Bell */ + if (term->termstate == SEEN_OSC || + term->termstate == SEEN_OSC_W) { + /* + * In an OSC context, BEL is one of the ways to terminate + * the whole sequence. We process it as such even if we + * haven't got into the final OSC_STRING state yet, so that + * OSC sequences without a string will be handled cleanly. + */ + do_osc(term); + term->termstate = TOPLEVEL; + break; + } + struct beeptime *newbeep; unsigned long ticks; @@ -3907,7 +3920,11 @@ static void term_out(Terminal *term, bool called_from_term_data) case '\033': /* ESC: Escape */ if (term->vt52_mode) term->termstate = VT52_ESC; - else { + else if (term->termstate == SEEN_OSC || + term->termstate == SEEN_OSC_W) { + /* Be prepared to terminate an OSC early */ + term->termstate = OSC_MAYBE_ST; + } else { compatibility(ANSIMIN); term->termstate = SEEN_ESC; term->esc_query = 0; @@ -5156,6 +5173,17 @@ static void term_out(Terminal *term, bool called_from_term_data) else term->esc_args[term->esc_nargs-1] = UINT_MAX; break; + case 0x9C: + /* Terminate even though we aren't in OSC_STRING yet */ + do_osc(term); + term->termstate = TOPLEVEL; + break; + case 0xC2: + if (in_utf(term)) { + /* Or be prepared for the UTF-8 version of that */ + term->termstate = OSC_MAYBE_ST_UTF8; + } + break; default: /* * _Most_ other characters here terminate the @@ -5325,6 +5353,17 @@ static void term_out(Terminal *term, bool called_from_term_data) else term->esc_args[0] = UINT_MAX; break; + case 0x9C: + /* Terminate even though we aren't in OSC_STRING yet */ + do_osc(term); + term->termstate = TOPLEVEL; + break; + case 0xC2: + if (in_utf(term)) { + /* Or be prepared for the UTF-8 version of that */ + term->termstate = OSC_MAYBE_ST_UTF8; + } + break; default: term->termstate = OSC_STRING; term->osc_strlen = 0; -- cgit v1.2.3 From cc10b68d313ec9e10e3186c4c953d2699f7de079 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 12 May 2022 18:01:42 +0100 Subject: Allow BEL to terminate OSC sequences during setup. This is a partial cherry-pick of commit de66b0313a23d04 from main, which allows all the forms of OSC sequence termination to apply in the preliminary states as well as OSC_STRING. The reporting user only mentioned the case of OSC 112 BEL, and not the various forms of ST. So the former is actually known to be occurring in the wild, and is also the least complicated part of the full patch on main. Therefore I think this part is worthwhile and reasonably safe to cherry-pick to 0.77 just before a release, whereas I'd be uncomfortable making the rest of the changes at this late stage. --- terminal/terminal.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/terminal/terminal.c b/terminal/terminal.c index ae5e4144..59a3a96a 100644 --- a/terminal/terminal.c +++ b/terminal/terminal.c @@ -3822,6 +3822,19 @@ static void term_out(Terminal *term, bool called_from_term_data) } break; case '\007': { /* BEL: Bell */ + if (term->termstate == SEEN_OSC || + term->termstate == SEEN_OSC_W) { + /* + * In an OSC context, BEL is one of the ways to terminate + * the whole sequence. We process it as such even if we + * haven't got into the final OSC_STRING state yet, so that + * OSC sequences without a string will be handled cleanly. + */ + do_osc(term); + term->termstate = TOPLEVEL; + break; + } + struct beeptime *newbeep; unsigned long ticks; -- cgit v1.2.3 From 4da67d8fa6cf056cb3c9527c59e5a2559bd73e97 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 12 May 2022 18:16:56 +0100 Subject: Move window resize timeouts into the GTK frontend. In the changes around commit 420fe75552afa94, I made the terminal suspend output processing while it waited for a term_size() callback in response to a resize request. Because on X11 there are unusual circumstances in which you never receive that callback, I also added a last-ditch 5-second timeout, so that eventually we'll resume terminal output processing regardless. But the timeout lives in terminal.c, in the cross-platform code. This is pointless on Windows (where resize processing is synchronous, so we always finish it before the timer code next gets called anyway), but I decided it was easier to keep the whole mechanism in terminal.c in the absence of a good reason not to. Now I've found that reason. We _also_ generate window resizes locally to the GTK front end, in response to the key combinations that change the font size, and _those_ still have an asynchrony problem. So, to begin with, I'm refactoring the request_resize system so that now there's an explicit callback from the frontend to the terminal to say 'Your resize request has now been processed, whether or not you've received a term_size() call'. On Windows, this simplifies matters greatly because we always know exactly when to call that, and don't have to keep a 'have we called term_size() already?' flag. On GTK, the timing complexity previously in terminal.c has moved into window.c. No functional change (I hope). The payoff will be in the next commit. --- putty.h | 12 ++++++++++++ terminal/terminal.c | 23 +++++++---------------- terminal/terminal.h | 18 ------------------ unix/window.c | 38 ++++++++++++++++++++++++++++++++++++++ windows/window.c | 24 +----------------------- 5 files changed, 58 insertions(+), 57 deletions(-) diff --git a/putty.h b/putty.h index e16a5d04..b15425a9 100644 --- a/putty.h +++ b/putty.h @@ -1636,6 +1636,17 @@ struct TermWinVtable { void (*refresh)(TermWin *); + /* request_resize asks the front end if the terminal can please be + * resized to (w,h) in characters. The front end MAY call + * term_size() in response to tell the terminal its new size + * (which MAY be the requested size, or some other size if the + * requested one can't be achieved). The front end MAY also not + * call term_size() at all. But the front end MUST reply to this + * request by calling term_resize_request_completed(), after the + * responding resize event has taken place (if any). + * + * The calls to term_size and term_resize_request_completed may be + * synchronous callbacks from within the call to request_resize(). */ void (*request_resize)(TermWin *, int w, int h); void (*set_title)(TermWin *, const char *title, int codepage); @@ -2133,6 +2144,7 @@ FontSpec *platform_default_fontspec(const char *name); Terminal *term_init(Conf *, struct unicode_data *, TermWin *); void term_free(Terminal *); void term_size(Terminal *, int, int, int); +void term_resize_request_completed(Terminal *); void term_paint(Terminal *, int, int, int, int, bool); void term_scroll(Terminal *, int, int); void term_scroll_to_selection(Terminal *, int); diff --git a/terminal/terminal.c b/terminal/terminal.c index ca36e4f9..fbaa619b 100644 --- a/terminal/terminal.c +++ b/terminal/terminal.c @@ -1220,12 +1220,6 @@ static void term_timer(void *ctx, unsigned long now) if (term->window_update_pending) term_update_callback(term); - - if (term->win_resize_pending == WIN_RESIZE_AWAIT_REPLY && - now == term->win_resize_timeout) { - term->win_resize_pending = WIN_RESIZE_NO; - queue_toplevel_callback(term_out_cb, term); - } } static void term_update_callback(void *ctx) @@ -1426,8 +1420,6 @@ void term_update(Terminal *term) term->win_resize_pending = WIN_RESIZE_AWAIT_REPLY; win_request_resize(term->win, term->win_resize_pending_w, term->win_resize_pending_h); - term->win_resize_timeout = schedule_timer( - WIN_RESIZE_TIMEOUT, term_timer, term); } if (term->win_zorder_pending) { win_set_zorder(term->win, term->win_zorder_top); @@ -2151,14 +2143,6 @@ void term_size(Terminal *term, int newrows, int newcols, int newsavelines) int sblen; int save_alt_which = term->alt_which; - /* If we were holding buffered terminal data because we were - * waiting for confirmation of a resize, queue a callback to start - * processing it again. */ - if (term->win_resize_pending == WIN_RESIZE_AWAIT_REPLY) { - term->win_resize_pending = WIN_RESIZE_NO; - queue_toplevel_callback(term_out_cb, term); - } - if (newrows == term->rows && newcols == term->cols && newsavelines == term->savelines) return; /* nothing to do */ @@ -2336,6 +2320,13 @@ void term_size(Terminal *term, int newrows, int newcols, int newsavelines) backend_size(term->backend, term->cols, term->rows); } +void term_resize_request_completed(Terminal *term) +{ + assert(term->win_resize_pending == WIN_RESIZE_AWAIT_REPLY); + term->win_resize_pending = WIN_RESIZE_NO; + queue_toplevel_callback(term_out_cb, term); +} + /* * Hand a backend to the terminal, so it can be notified of resizes. */ diff --git a/terminal/terminal.h b/terminal/terminal.h index b2347f9a..3f918b22 100644 --- a/terminal/terminal.h +++ b/terminal/terminal.h @@ -427,24 +427,6 @@ struct terminal_tag { WIN_RESIZE_NO, WIN_RESIZE_NEED_SEND, WIN_RESIZE_AWAIT_REPLY } win_resize_pending; int win_resize_pending_w, win_resize_pending_h; - - /* - * Not every frontend / TermWin implementation can be relied on - * 100% to reply to a resize request in a timely manner. (In X11 - * it's all asynchronous and goes via the window manager, and if - * your window manager is seriously unwell, you'd rather not have - * terminal windows start becoming unusable as a knock-on effect, - * since those are just the thing you might need to use for - * emergency WM maintenance!) So when we enter AWAIT_REPLY status, - * we also set a 5-second timer, after which we'll regretfully - * conclude that a resize is probably not going to happen after - * all. - * - * However, in non-emergency cases, the plan is that this - * shouldn't be needed, for one reason or another. - */ - long win_resize_timeout; - #define WIN_RESIZE_TIMEOUT (TICKSPERSEC*5) }; static inline bool in_utf(Terminal *term) diff --git a/unix/window.c b/unix/window.c index d9d007b4..4fca971a 100644 --- a/unix/window.c +++ b/unix/window.c @@ -189,6 +189,24 @@ struct GtkFrontend { #endif int trust_sigil_w, trust_sigil_h; + /* + * Not every GDK backend can be relied on 100% to reply to a + * resize request in a timely manner. (In X11 it's all + * asynchronous and goes via the window manager, and if your + * window manager is seriously unwell, you'd rather not have + * terminal windows start becoming unusable as a knock-on effect, + * since those are just the thing you might need to use for + * emergency WM maintenance!) + * + * So when we ask GTK to resize our terminal window, we also set a + * 5-second timer, after which we'll regretfully conclude that a + * resize (or ConfigureNotify telling us no resize took place) is + * probably not going to happen after all. + */ + bool win_resize_pending, term_resize_notification_required; + long win_resize_timeout; + #define WIN_RESIZE_TIMEOUT (TICKSPERSEC*5) + Seat seat; TermWin termwin; LogPolicy logpolicy; @@ -759,6 +777,10 @@ static void drawing_area_setup(GtkFrontend *inst, int width, int height) inst->drawing_area_setup_called = true; if (inst->term) term_size(inst->term, h, w, conf_get_int(inst->conf, CONF_savelines)); + if (inst->term_resize_notification_required) + term_resize_request_completed(inst->term); + if (inst->win_resize_pending) + inst->win_resize_pending = false; if (!inst->drawing_area_setup_needed) return; @@ -2480,6 +2502,17 @@ static void gtkwin_deny_term_resize(void *vctx) drawing_area_setup_simple(inst); } +static void gtkwin_timer(void *vctx, unsigned long now) +{ + GtkFrontend *inst = (GtkFrontend *)vctx; + + if (inst->win_resize_pending && now == inst->win_resize_timeout) { + if (inst->term_resize_notification_required) + term_resize_request_completed(inst->term); + inst->win_resize_pending = false; + } +} + static void gtkwin_request_resize(TermWin *tw, int w, int h) { GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); @@ -2525,6 +2558,7 @@ static void gtkwin_request_resize(TermWin *tw, int w, int h) #endif 0)) { queue_toplevel_callback(gtkwin_deny_term_resize, inst); + term_resize_request_completed(inst->term); return; } } @@ -2613,6 +2647,10 @@ static void gtkwin_request_resize(TermWin *tw, int w, int h) #endif + inst->win_resize_pending = true; + inst->term_resize_notification_required = true; + inst->win_resize_timeout = schedule_timer( + WIN_RESIZE_TIMEOUT, gtkwin_timer, inst); } #if GTK_CHECK_VERSION(3,0,0) diff --git a/windows/window.c b/windows/window.c index 59c56ee1..3d6420d0 100644 --- a/windows/window.c +++ b/windows/window.c @@ -1667,8 +1667,6 @@ static void deinit_fonts(void) trust_icon = INVALID_HANDLE_VALUE; } -static bool sent_term_size; /* only live during wintw_request_resize() */ - static void wintw_request_resize(TermWin *tw, int w, int h) { const struct BackendVtable *vt; @@ -1716,28 +1714,12 @@ static void wintw_request_resize(TermWin *tw, int w, int h) if (conf_get_int(conf, CONF_resize_action) != RESIZE_FONT && !IsZoomed(wgs.term_hwnd)) { - /* - * We want to send exactly one term_size() to the terminal, - * telling it what size it ended up after this operation. - * - * If we don't get the size we asked for in SetWindowPos, then - * we'll be sent a WM_SIZE message, whose handler will make - * that call, all before SetWindowPos even returns to here. - * - * But if that _didn't_ happen, we'll need to call term_size - * ourselves afterwards. - */ - sent_term_size = false; - width = extra_width + font_width * w; height = extra_height + font_height * h; SetWindowPos(wgs.term_hwnd, NULL, 0, 0, width, height, SWP_NOACTIVATE | SWP_NOCOPYBITS | SWP_NOMOVE | SWP_NOZORDER); - - if (!sent_term_size) - term_size(term, h, w, conf_get_int(conf, CONF_savelines)); } else { /* * If we're resizing by changing the font, we must tell the @@ -1748,6 +1730,7 @@ static void wintw_request_resize(TermWin *tw, int w, int h) reset_window(0); } + term_resize_request_completed(term); InvalidateRect(wgs.term_hwnd, NULL, true); } @@ -2188,11 +2171,6 @@ static void wm_size_resize_term(LPARAM lParam, bool border) } else { term_size(term, h, w, conf_get_int(conf, CONF_savelines)); - /* If this is happening re-entrantly during the call to - * SetWindowPos in wintw_request_resize, let it know that - * we've already done a term_size() so that it doesn't have - * to. */ - sent_term_size = true; } } -- cgit v1.2.3 From 80a87df618af3072dbf8a0a74828a4f14d16f937 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 12 May 2022 19:38:45 +0100 Subject: GTK: don't try to change font size in mid-window-resize. If the user holds down Alt-> so that the key repeats, then a second call to change_font_size can occur while the window resize from the previous one has yet to complete. This leads to the new pixel size of the window from resize #1 being interpreted in the light of the font size from reesize #2, so that the two get out of step and the _character_ size of the terminal changes as a result. The simplest fix is to disallow starting a second font-size-based window resize while the first is still in flight - which, now that the 'win_resize_pending' flag lives in window.c and not terminal.c, is easy to achieve. --- unix/window.c | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/unix/window.c b/unix/window.c index 4fca971a..167020b4 100644 --- a/unix/window.c +++ b/unix/window.c @@ -1278,7 +1278,8 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) #ifdef KEY_EVENT_DIAGNOSTICS debug(" - Ctrl->: increase font size\n"); #endif - change_font_size(inst, +1); + if (!inst->win_resize_pending) + change_font_size(inst, +1); return true; } if (event->keyval == GDK_KEY_less && @@ -1286,7 +1287,8 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) #ifdef KEY_EVENT_DIAGNOSTICS debug(" - Ctrl-<: increase font size\n"); #endif - change_font_size(inst, -1); + if (!inst->win_resize_pending) + change_font_size(inst, -1); return true; } @@ -2513,10 +2515,9 @@ static void gtkwin_timer(void *vctx, unsigned long now) } } -static void gtkwin_request_resize(TermWin *tw, int w, int h) +static void request_resize_internal(GtkFrontend *inst, bool from_terminal, + int w, int h) { - GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); - #if GTK_CHECK_VERSION(2,0,0) /* * Initial check: don't even try to resize a window if it's in one @@ -2648,11 +2649,17 @@ static void gtkwin_request_resize(TermWin *tw, int w, int h) #endif inst->win_resize_pending = true; - inst->term_resize_notification_required = true; + inst->term_resize_notification_required = from_terminal; inst->win_resize_timeout = schedule_timer( WIN_RESIZE_TIMEOUT, gtkwin_timer, inst); } +static void gtkwin_request_resize(TermWin *tw, int w, int h) +{ + GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); + request_resize_internal(inst, true, w, h); +} + #if GTK_CHECK_VERSION(3,0,0) char *colour_to_css(const GdkColor *col) { @@ -4834,9 +4841,9 @@ static void after_change_settings_dialog(void *vctx, int retval) conf_get_int(newconf, CONF_window_border) || need_size) { set_geom_hints(inst); - win_request_resize(&inst->termwin, - conf_get_int(newconf, CONF_width), - conf_get_int(newconf, CONF_height)); + request_resize_internal(inst, false, + conf_get_int(newconf, CONF_width), + conf_get_int(newconf, CONF_height)); } else { /* * The above will have caused a call to term_size() for @@ -4909,8 +4916,8 @@ static void change_font_size(GtkFrontend *inst, int increment) } set_geom_hints(inst); - win_request_resize(&inst->termwin, conf_get_int(inst->conf, CONF_width), - conf_get_int(inst->conf, CONF_height)); + request_resize_internal(inst, false, conf_get_int(inst->conf, CONF_width), + conf_get_int(inst->conf, CONF_height)); term_invalidate(inst->term); gtk_widget_queue_draw(inst->area); -- cgit v1.2.3 From 386b094e3f6351d4c81eaffa3e7f385291ae5fad Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 12 May 2022 19:57:10 +0100 Subject: Fix GTK1 build. Commit 5390aef3fc7deca broke it, because GTK1 has neither gtk_label_set_selectable nor gtk_widget_set_can_focus. Happily, those are both more or less optional (only a minor UI awkwardness arises from not having them), so I'll just condition them out. --- unix/dialog.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/unix/dialog.c b/unix/dialog.c index 9f1d061d..35c912e7 100644 --- a/unix/dialog.c +++ b/unix/dialog.c @@ -2506,8 +2506,10 @@ GtkWidget *layout_ctrls( */ uc->text = w = gtk_label_new(uc->ctrl->label); #endif +#if GTK_CHECK_VERSION(2,0,0) gtk_label_set_selectable(GTK_LABEL(w), true); gtk_widget_set_can_focus(w, false); +#endif align_label_left(GTK_LABEL(w)); gtk_label_set_line_wrap(GTK_LABEL(w), ctrl->text.wrap); if (!ctrl->text.wrap) { @@ -2518,7 +2520,9 @@ GtkWidget *layout_ctrls( gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w), GTK_POLICY_AUTOMATIC, GTK_POLICY_NEVER); +#if GTK_CHECK_VERSION(2,0,0) gtk_widget_set_can_focus(w, false); +#endif } break; } -- cgit v1.2.3 From 0a877e9df5def14da30ab00046b501da20c9112a Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 14 May 2022 13:48:35 +0100 Subject: Fix build failure with -DNOT_X_WINDOWS. The recent window resize fixes introduced an unchecked use of GDK_IS_X11_DISPLAY. --- unix/window.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unix/window.c b/unix/window.c index d9d007b4..c2e79bf0 100644 --- a/unix/window.c +++ b/unix/window.c @@ -4506,7 +4506,7 @@ void set_geom_hints(GtkFrontend *inst) * So instead, I simply avoid setting geometry hints at all on any * GDK backend other than X11, and hopefully that's a workaround. */ -#if GTK_CHECK_VERSION(3,0,0) +#if GTK_CHECK_VERSION(3,0,0) && !defined NOT_X_WINDOWS if (!GDK_IS_X11_DISPLAY(gdk_display_get_default())) return; #endif -- cgit v1.2.3 From 81dcbd6267db063367056cb0cf94eca4eb161991 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Wed, 18 May 2022 12:41:44 +0100 Subject: Proxy: discard buffered input data on reconnection. When talking to a web proxy which requires a password, our HTTP proxy code sends an initial attempt to connect without authentication, receives the 407 status indicating that authentication was required (and which kind), and then closes and reopens the connection (if given "Connection: close"). Then, on the next attempt, we try again with authentication, and expect the first thing in the input bufchain to be the HTTP status response to the revised request. But what happened about the error document that followed those HTTP headers? It - or at least some of it - would already have been in the input bufchain. With an HTTP/1.1 proxy, we already read it and discarded it (either via a Content-length header or chunked transfer encoding), before we set the 'reconnect' flag. So, assuming the proxy HTTP server is behaving sensibly, our input bufchain ought to be empty at the point when we start the fresh connection. But if the proxy only speaks HTTP/1.0 (which does still happen - 'tinyproxy' is a still-current example), then we didn't get a Content-length header either, so we didn't run any of the code that skips over the error document. (And HTTP/1.0 implicitly has "Connection: close" semantics, which is why that doesn't matter.) As a result, some of it would still be in the input bufchain, and never got cleared out, and we'd try to parse _that_ as if it was the HTTP response from the second network connection. The simple solution is that when we close and reopen the proxy network connection, we also clear the input bufchain, so that the fresh connection starts from a clean slate. --- proxy/proxy.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/proxy/proxy.c b/proxy/proxy.c index 6bedcf9b..801c28f9 100644 --- a/proxy/proxy.c +++ b/proxy/proxy.c @@ -232,6 +232,13 @@ static void proxy_negotiate(ProxySocket *ps) ps->proxy_nodelay, ps->proxy_keepalive, &ps->plugimpl); ps->pn->reconnect = false; + /* If the negotiator has asked us to reconnect, they are + * expecting that on the next call their input queue will + * consist entirely of data from the _new_ connection, without + * any remaining data buffered from the old one. (If they'd + * wanted the latter, they could have read it out of the input + * queue before asking us to close the connection.) */ + bufchain_clear(&ps->pending_input_data); } while (bufchain_size(&ps->output_from_negotiator)) { -- cgit v1.2.3 From 787c358d373bbef8383b385770747b1a6a4ab3ca Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Wed, 18 May 2022 13:04:56 +0100 Subject: Fix command-line password handling in Restart Session. When the user provides a password on the PuTTY command line, via -pw or -pwfile, the flag 'tried_once' inside cmdline_get_passwd_input() is intended to arrange that we only try sending that password once, and after we've sent it, we don't try again. But this plays badly with the 'Restart Session' operation. If the connection is lost and then restarted at user request, we _do_ want to send that password again! So this commit moves that static variable out into a small state structure held by the client of cmdline_get_passwd_input. Each client can decide how to manage that state itself. Clients that support 'Restart Session' - i.e. just GUI PuTTY itself - will initialise the state at the same time as instantiating the backend, so that every time the session is restarted, we return to (correctly) believing that we _haven't_ yet tried the password provided on the command line. But clients that don't support 'Restart Session' - i.e. Plink and file transfer tools - can do the same thing that cmdline.c was doing before: just keep the state in a static variable. This also means that the GUI login tools will now retain the command-line password in memory, whereas previously they'd have wiped it out once it was used. But the other tools will still wipe and free the password, because I've also added a 'bool restartable' flag to cmdline_get_passwd_input to let it know when it _is_ allowed to do that. In the GUI tools, I don't see any way to get round that, because if the session is restarted you _have_ to still have the password to use again. (And you can't infer that that will never happen from the CONF_close_on_exit setting, because that too could be changed in mid-session.) On the other hand, I think it's not all that worrying, because the use of either -pw or -pwfile means that a persistent copy of your password is *already* stored somewhere, so another one isn't too big a stretch. (Due to the change of -pw policy in 0.77, the effect of this bug was that an attempt to reconnect in a session set up this way would lead to "Configured password was not accepted". In 0.76, the failure mode was different: PuTTY would interactively prompt for the password, having wiped it out of memory after it was used the first time round.) --- cmdline.c | 28 ++++++++++++++++++---------- defs.h | 2 ++ putty.h | 7 ++++++- stubs/nocmdline.c | 3 ++- unix/plink.c | 7 ++++++- unix/sftp.c | 8 +++++++- unix/window.c | 5 ++++- utils/CMakeLists.txt | 1 + utils/cmdline_get_passwd_input_state_new.c | 9 +++++++++ windows/plink.c | 7 ++++++- windows/sftp.c | 8 +++++++- windows/window.c | 6 +++++- 12 files changed, 73 insertions(+), 18 deletions(-) create mode 100644 utils/cmdline_get_passwd_input_state_new.c diff --git a/cmdline.c b/cmdline.c index 9f37f2a0..8e48b358 100644 --- a/cmdline.c +++ b/cmdline.c @@ -81,10 +81,9 @@ void cmdline_cleanup(void) * -1 return means that we aren't capable of processing the prompt and * someone else should do it. */ -SeatPromptResult cmdline_get_passwd_input(prompts_t *p) +SeatPromptResult cmdline_get_passwd_input( + prompts_t *p, cmdline_get_passwd_input_state *state, bool restartable) { - static bool tried_once = false; - /* * We only handle prompts which don't echo (which we assume to be * passwords), and (currently) we only cope with a password prompt @@ -98,23 +97,32 @@ SeatPromptResult cmdline_get_passwd_input(prompts_t *p) * If we've tried once, return utter failure (no more passwords left * to try). */ - if (tried_once) + if (state->tried) return SPR_SW_ABORT("Configured password was not accepted"); /* * If we never had a password available in the first place, we * can't do anything in any case. (But we delay this test until - * after tried_once, so that after we free cmdline_password below, - * we'll still remember that we _used_ to have one.) + * after trying once, so that even if we free cmdline_password + * below, we'll still remember that we _used_ to have one.) */ if (!cmdline_password) return SPR_INCOMPLETE; prompt_set_result(p->prompts[0], cmdline_password); - smemclr(cmdline_password, strlen(cmdline_password)); - sfree(cmdline_password); - cmdline_password = NULL; - tried_once = true; + state->tried = true; + + if (!restartable) { + /* + * If there's no possibility of needing to do this again after + * a 'Restart Session' event, then wipe our copy of the + * password out of memory. + */ + smemclr(cmdline_password, strlen(cmdline_password)); + sfree(cmdline_password); + cmdline_password = NULL; + } + return SPR_OK; } diff --git a/defs.h b/defs.h index 354c208f..f1bbf51a 100644 --- a/defs.h +++ b/defs.h @@ -118,6 +118,8 @@ typedef struct Seat Seat; typedef struct SeatVtable SeatVtable; typedef struct SeatPromptResult SeatPromptResult; +typedef struct cmdline_get_passwd_input_state cmdline_get_passwd_input_state; + typedef struct TermWin TermWin; typedef struct TermWinVtable TermWinVtable; diff --git a/putty.h b/putty.h index fc5c2941..9a8c4a1f 100644 --- a/putty.h +++ b/putty.h @@ -2525,10 +2525,15 @@ void printer_finish_job(printer_job *); * zero out password arguments in the hope of not having them show up * avoidably in Unix 'ps'. */ +struct cmdline_get_passwd_input_state { bool tried; }; +#define CMDLINE_GET_PASSWD_INPUT_STATE_INIT { .tried = false } +extern const cmdline_get_passwd_input_state cmdline_get_passwd_input_state_new; + int cmdline_process_param(const char *, char *, int, Conf *); void cmdline_run_saved(Conf *); void cmdline_cleanup(void); -SeatPromptResult cmdline_get_passwd_input(prompts_t *p); +SeatPromptResult cmdline_get_passwd_input( + prompts_t *p, cmdline_get_passwd_input_state *state, bool restartable); bool cmdline_host_ok(Conf *); bool cmdline_verbose(void); bool cmdline_loaded_session(void); diff --git a/stubs/nocmdline.c b/stubs/nocmdline.c index de3cfd0d..60e2cb6b 100644 --- a/stubs/nocmdline.c +++ b/stubs/nocmdline.c @@ -15,7 +15,8 @@ * handling, then there is no such option, so that function always * returns failure. */ -SeatPromptResult cmdline_get_passwd_input(prompts_t *p) +SeatPromptResult cmdline_get_passwd_input( + prompts_t *p, cmdline_get_passwd_input_state *state, bool restartable) { return SPR_INCOMPLETE; } diff --git a/unix/plink.c b/unix/plink.c index 8f53ceda..9e109f01 100644 --- a/unix/plink.c +++ b/unix/plink.c @@ -373,8 +373,13 @@ static bool plink_eof(Seat *seat) static SeatPromptResult plink_get_userpass_input(Seat *seat, prompts_t *p) { + /* Plink doesn't support Restart Session, so we can just have a + * single static cmdline_get_passwd_input_state that's never reset */ + static cmdline_get_passwd_input_state cmdline_state = + CMDLINE_GET_PASSWD_INPUT_STATE_INIT; + SeatPromptResult spr; - spr = cmdline_get_passwd_input(p); + spr = cmdline_get_passwd_input(p, &cmdline_state, false); if (spr.kind == SPRK_INCOMPLETE) spr = console_get_userpass_input(p); return spr; diff --git a/unix/sftp.c b/unix/sftp.c index 17a83a89..9d099f55 100644 --- a/unix/sftp.c +++ b/unix/sftp.c @@ -65,8 +65,14 @@ Filename *platform_default_filename(const char *name) SeatPromptResult filexfer_get_userpass_input(Seat *seat, prompts_t *p) { + /* The file transfer tools don't support Restart Session, so we + * can just have a single static cmdline_get_passwd_input_state + * that's never reset */ + static cmdline_get_passwd_input_state cmdline_state = + CMDLINE_GET_PASSWD_INPUT_STATE_INIT; + SeatPromptResult spr; - spr = cmdline_get_passwd_input(p); + spr = cmdline_get_passwd_input(p, &cmdline_state, false); if (spr.kind == SPRK_INCOMPLETE) spr = console_get_userpass_input(p); return spr; diff --git a/unix/window.c b/unix/window.c index c2e79bf0..173943cb 100644 --- a/unix/window.c +++ b/unix/window.c @@ -160,6 +160,7 @@ struct GtkFrontend { Ldisc *ldisc; Backend *backend; Terminal *term; + cmdline_get_passwd_input_state cmdline_get_passwd_state; LogContext *logctx; bool exited; struct unicode_data ucsdata; @@ -343,7 +344,7 @@ static SeatPromptResult gtk_seat_get_userpass_input(Seat *seat, prompts_t *p) { GtkFrontend *inst = container_of(seat, GtkFrontend, seat); SeatPromptResult spr; - spr = cmdline_get_passwd_input(p); + spr = cmdline_get_passwd_input(p, &inst->cmdline_get_passwd_state, true); if (spr.kind == SPRK_INCOMPLETE) spr = term_get_userpass_input(inst->term, p); return spr; @@ -5105,6 +5106,8 @@ static void start_backend(GtkFrontend *inst) const struct BackendVtable *vt; char *error, *realhost; + inst->cmdline_get_passwd_state = cmdline_get_passwd_input_state_new; + vt = select_backend(inst->conf); seat_set_trust_status(&inst->seat, true); diff --git a/utils/CMakeLists.txt b/utils/CMakeLists.txt index 80fc20b8..2e1296eb 100644 --- a/utils/CMakeLists.txt +++ b/utils/CMakeLists.txt @@ -7,6 +7,7 @@ add_sources_from_current_dir(utils buildinfo.c burnstr.c chomp.c + cmdline_get_passwd_input_state_new.c conf.c conf_dest.c conf_launchable.c diff --git a/utils/cmdline_get_passwd_input_state_new.c b/utils/cmdline_get_passwd_input_state_new.c new file mode 100644 index 00000000..cd39bfa1 --- /dev/null +++ b/utils/cmdline_get_passwd_input_state_new.c @@ -0,0 +1,9 @@ +/* + * A preinitialised cmdline_get_passwd_input_state which makes it easy + * to assign by structure copy. + */ + +#include "putty.h" + +const cmdline_get_passwd_input_state cmdline_get_passwd_input_state_new = + CMDLINE_GET_PASSWD_INPUT_STATE_INIT; diff --git a/windows/plink.c b/windows/plink.c index af95c2b9..94bf4f0c 100644 --- a/windows/plink.c +++ b/windows/plink.c @@ -67,8 +67,13 @@ static bool plink_eof(Seat *seat) static SeatPromptResult plink_get_userpass_input(Seat *seat, prompts_t *p) { + /* Plink doesn't support Restart Session, so we can just have a + * single static cmdline_get_passwd_input_state that's never reset */ + static cmdline_get_passwd_input_state cmdline_state = + CMDLINE_GET_PASSWD_INPUT_STATE_INIT; + SeatPromptResult spr; - spr = cmdline_get_passwd_input(p); + spr = cmdline_get_passwd_input(p, &cmdline_state, false); if (spr.kind == SPRK_INCOMPLETE) spr = console_get_userpass_input(p); return spr; diff --git a/windows/sftp.c b/windows/sftp.c index ad2df4fb..fc8e711b 100644 --- a/windows/sftp.c +++ b/windows/sftp.c @@ -14,8 +14,14 @@ SeatPromptResult filexfer_get_userpass_input(Seat *seat, prompts_t *p) { + /* The file transfer tools don't support Restart Session, so we + * can just have a single static cmdline_get_passwd_input_state + * that's never reset */ + static cmdline_get_passwd_input_state cmdline_state = + CMDLINE_GET_PASSWD_INPUT_STATE_INIT; + SeatPromptResult spr; - spr = cmdline_get_passwd_input(p); + spr = cmdline_get_passwd_input(p, &cmdline_state, false); if (spr.kind == SPRK_INCOMPLETE) spr = console_get_userpass_input(p); return spr; diff --git a/windows/window.c b/windows/window.c index 399829b6..5a70098c 100644 --- a/windows/window.c +++ b/windows/window.c @@ -130,6 +130,8 @@ static int kbd_codepage; static Ldisc *ldisc; static Backend *backend; +static cmdline_get_passwd_input_state cmdline_get_passwd_state; + static struct unicode_data ucsdata; static bool session_closed; static bool reconfiguring = false; @@ -371,6 +373,8 @@ static void start_backend(void) char *error, *realhost; int i; + cmdline_get_passwd_state = cmdline_get_passwd_input_state_new; + vt = backend_vt_from_conf(conf); seat_set_trust_status(&wgs.seat, true); @@ -5909,7 +5913,7 @@ static bool win_seat_eof(Seat *seat) static SeatPromptResult win_seat_get_userpass_input(Seat *seat, prompts_t *p) { SeatPromptResult spr; - spr = cmdline_get_passwd_input(p); + spr = cmdline_get_passwd_input(p, &cmdline_get_passwd_state, true); if (spr.kind == SPRK_INCOMPLETE) spr = term_get_userpass_input(term, p); return spr; -- cgit v1.2.3 From 868d309779df118001e8c173f367aa1dba957014 Mon Sep 17 00:00:00 2001 From: Jacob Nevins Date: Wed, 18 May 2022 18:46:40 +0100 Subject: Minimally document how to install. --- README | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README b/README index 252fc6be..85b645d1 100644 --- a/README +++ b/README @@ -8,6 +8,10 @@ the source directory: cmake . cmake --build . +Then, to install in the simplest way on Linux or Mac: + + cmake --build . --target install + 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 -- cgit v1.2.3 From 97b3db34b2fb94c68ed723fa363a16d4e41d77f6 Mon Sep 17 00:00:00 2001 From: Jacob Nevins Date: Wed, 18 May 2022 18:47:01 +0100 Subject: Add missing HAVE_SETRESGID to cmake.h. Without this, we were always falling back to the setuid()/setgid() privilege-dropping code in the utmp helper. --- cmake/cmake.h.in | 1 + 1 file changed, 1 insertion(+) diff --git a/cmake/cmake.h.in b/cmake/cmake.h.in index 06f26176..ed44b2ca 100644 --- a/cmake/cmake.h.in +++ b/cmake/cmake.h.in @@ -28,6 +28,7 @@ #cmakedefine01 HAVE_POSIX_OPENPT #cmakedefine01 HAVE_PTSNAME #cmakedefine01 HAVE_SETRESUID +#cmakedefine01 HAVE_SETRESGID #cmakedefine01 HAVE_STRSIGNAL #cmakedefine01 HAVE_UPDWTMPX #cmakedefine01 HAVE_FSTATAT -- cgit v1.2.3 From 92881f2066c03175f91e1ccc2ad687a10f867293 Mon Sep 17 00:00:00 2001 From: Jacob Nevins Date: Wed, 18 May 2022 18:48:28 +0100 Subject: Define OMIT_UTMP if there's no utmpx.h. Without this, the build of e.g. psusan would fail on systems without that header (such as Termux on Android). This is similar to how things were pre-cmake, but not identical. We used to treat lack of updwtmpx() as a reason to OMIT_UTMP (as of f0dfa73982), but usage of that function got conditionalised in c19e7215dd, so I haven't restored that exclusion. --- cmake/cmake.h.in | 1 + cmake/platforms/unix.cmake | 7 +++++++ unix/pty.c | 2 ++ 3 files changed, 10 insertions(+) diff --git a/cmake/cmake.h.in b/cmake/cmake.h.in index ed44b2ca..2041f096 100644 --- a/cmake/cmake.h.in +++ b/cmake/cmake.h.in @@ -16,6 +16,7 @@ #cmakedefine01 HAVE_DWMAPI_H #cmakedefine NOT_X_WINDOWS +#cmakedefine OMIT_UTMP #cmakedefine01 HAVE_ASM_HWCAP_H #cmakedefine01 HAVE_SYS_AUXV_H diff --git a/cmake/platforms/unix.cmake b/cmake/platforms/unix.cmake index cb47caf9..6a788cb4 100644 --- a/cmake/platforms/unix.cmake +++ b/cmake/platforms/unix.cmake @@ -19,6 +19,7 @@ check_include_file(sys/sysctl.h HAVE_SYS_SYSCTL_H) check_include_file(sys/types.h HAVE_SYS_TYPES_H) check_include_file(glob.h HAVE_GLOB_H) check_include_file(utmp.h HAVE_UTMP_H) +check_include_file(utmpx.h HAVE_UTMPX_H) check_symbol_exists(futimes "sys/time.h" HAVE_FUTIMES) check_symbol_exists(getaddrinfo "sys/types.h;sys/socket.h;netdb.h" @@ -56,6 +57,12 @@ else() set(NO_IPV6 ON) endif() +if(HAVE_UTMPX_H) + set(OMIT_UTMP OFF) +else() + set(OMIT_UTMP ON) +endif() + include(cmake/gtk.cmake) # See if we have X11 available. This requires libX11 itself, and also diff --git a/unix/pty.c b/unix/pty.c index 0747b584..625f1bb1 100644 --- a/unix/pty.c +++ b/unix/pty.c @@ -227,6 +227,8 @@ static void setup_utmp(char *ttyname, char *location) endutxent(); #if HAVE_UPDWTMPX + /* Reportedly, AIX 5.1 has and pututxline(), but no + * updwtmpx(). */ updwtmpx(WTMPX_FILE, &utmp_entry); #endif -- cgit v1.2.3 From d163b37cafc46ab24412e4cf92d14f54e2aa97a6 Mon Sep 17 00:00:00 2001 From: Jacob Nevins Date: Wed, 18 May 2022 18:57:45 +0100 Subject: Squash NDEBUG for RelWithDebInfo build type too. --- cmake/setup.cmake | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmake/setup.cmake b/cmake/setup.cmake index 26079f31..0ad84df9 100644 --- a/cmake/setup.cmake +++ b/cmake/setup.cmake @@ -4,6 +4,8 @@ # give a #error if this manoeuvre doesn't do what it needs to. string(REPLACE "/DNDEBUG" "" CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}") string(REPLACE "-DNDEBUG" "" CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}") +string(REPLACE "/DNDEBUG" "" CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELEASE}") +string(REPLACE "-DNDEBUG" "" CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELEASE}") set(PUTTY_IPV6 ON CACHE BOOL "Build PuTTY with IPv6 support if possible") -- cgit v1.2.3 From e45c6b76dad062e1e2c492524f2c05e27913b382 Mon Sep 17 00:00:00 2001 From: Jacob Nevins Date: Thu, 19 May 2022 10:52:56 +0100 Subject: Restore advice about making pterm set[ug]id. cmake doesn't have convincing facilities for doing this in its install step, so the new advice is to do it manually (we've provided no equivalent to the autotools --enable-setuid or --enable-setgid options, nor UTMP_USER/GROUP). --- README | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README b/README index 85b645d1..c996c3a8 100644 --- a/README +++ b/README @@ -12,6 +12,16 @@ Then, to install in the simplest way on Linux or Mac: cmake --build . --target install +On Unix, pterm would like to be setuid or setgid, as appropriate, to +permit it to write records of user logins to /var/run/utmp and +/var/log/wtmp. (Of course it will not use this privilege for +anything else, and in particular it will drop all privileges before +starting up complex subsystems like GTK.) The cmake install step +doesn't attempt to add these privileges, so if you want user login +recording to work, you should manually ch{own,grp} and chmod the +pterm binary yourself after installation. If you don't do this, +pterm will still work, but not update the user login databases. + 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 -- cgit v1.2.3 From b8a51419ef55eba354a49e05cd90d934b878bbaf Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 19 May 2022 17:44:39 +0100 Subject: Move contents of win_seat_notify_remote_exit into a callback. This matches the way it's done in the GTK backend: the only thing that happens inside seat_notify_remote_exit is that we schedule a toplevel callback, and all the real work happens later on when that callback is called. The particular situation where this makes a difference is if you perform a user abort during proxy authentication (e.g. hit ^D at a proxy password prompt), so that the main SSH backend gets PLUGCLOSE_USER_ABORT and calls ssh_user_close. That doesn't immediately close the socket; it just sets ssh->pending_close, schedules a run of ssh_bpp_output_raw_data_callback, and returns. So if seat_notify_remote_exit calls back _synchronously_ to ssh_return_exitcode, it will see that the socket is still open and return -1. But if it schedules a callback and waits, then the callback to ssh_bpp_output_raw_data_callback will happen first, which will close the socket, and then the exit processing will get the right answer. So the user-visible effect is that if you ^D a proxy auth prompt, GTK PuTTY will close the whole window (assuming you didn't set close-on- exit to 'never'), whereas Windows PuTTY will leave the window open, and not even know that the connection is now shut (in that it'll still ask 'Are you sure you want to close this session?' if you try to close it by hand). With this fix, Windows PuTTY behaves the same as GTK in this respect. --- windows/window.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/windows/window.c b/windows/window.c index 5a70098c..4b1e4f05 100644 --- a/windows/window.c +++ b/windows/window.c @@ -2106,7 +2106,7 @@ static bool is_alt_pressed(void) static bool resizing; -static void win_seat_notify_remote_exit(Seat *seat) +static void exit_callback(void *vctx) { int exitcode, close_on_exit; @@ -2133,6 +2133,11 @@ static void win_seat_notify_remote_exit(Seat *seat) } } +static void win_seat_notify_remote_exit(Seat *seat) +{ + queue_toplevel_callback(exit_callback, NULL); +} + void timer_change_notify(unsigned long next) { unsigned long now = GETTICKCOUNT(); -- cgit v1.2.3 From 02c745e1860f593f993fd141938fd53f90327be1 Mon Sep 17 00:00:00 2001 From: Jacob Nevins Date: Thu, 19 May 2022 23:23:14 +0100 Subject: Use correct flags for RelWithDebInfo. Bug introduced in d163b37caf. (Ahem.) --- cmake/setup.cmake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake/setup.cmake b/cmake/setup.cmake index 0ad84df9..7a650771 100644 --- a/cmake/setup.cmake +++ b/cmake/setup.cmake @@ -4,8 +4,8 @@ # give a #error if this manoeuvre doesn't do what it needs to. string(REPLACE "/DNDEBUG" "" CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}") string(REPLACE "-DNDEBUG" "" CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}") -string(REPLACE "/DNDEBUG" "" CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELEASE}") -string(REPLACE "-DNDEBUG" "" CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELEASE}") +string(REPLACE "/DNDEBUG" "" CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO}") +string(REPLACE "-DNDEBUG" "" CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO}") set(PUTTY_IPV6 ON CACHE BOOL "Build PuTTY with IPv6 support if possible") -- cgit v1.2.3 From 98a213e832a20dde79a7a51676ece2949fe5d004 Mon Sep 17 00:00:00 2001 From: Jacob Nevins Date: Fri, 20 May 2022 13:28:01 +0100 Subject: Document Windows PuTTYgen's -E option. This was missed in 11aa9ab8f3. --- doc/pubkey.but | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/doc/pubkey.but b/doc/pubkey.but index 57282455..80382076 100644 --- a/doc/pubkey.but +++ b/doc/pubkey.but @@ -549,6 +549,11 @@ key. } +\dt \cw{\-E} \e{fptype} + +\dd Algorithm to use when displaying key fingerprints. You can +select \c{sha256} or \c{md5}. See \k{puttygen-fingerprint}. + \H{pubkey-gettingready} Getting ready for public key authentication Connect to your SSH server using PuTTY with the SSH protocol. When the -- cgit v1.2.3 From 2985383c0bc4c40c18f573ebaf1209727f8f8531 Mon Sep 17 00:00:00 2001 From: Jacob Nevins Date: Fri, 20 May 2022 19:33:31 +0100 Subject: Indexing for Windows Pageant command-line options. --- doc/index.but | 5 +++++ doc/pageant.but | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/doc/index.but b/doc/index.but index afd74167..4c76bb05 100644 --- a/doc/index.but +++ b/doc/index.but @@ -867,6 +867,11 @@ saved sessions from \IM{authentication agent} agent, authentication \IM{-c-pageant} \c{-c} Pageant command-line option +\IM{--keylist} \c{--keylist} Pageant command-line option +\IM{--openssh-config} \c{--openssh-config} Pageant command-line option + +\IM{Windows OpenSSH} Windows OpenSSH +\IM{Windows OpenSSH} OpenSSH, on Windows \IM{FAQ} FAQ \IM{FAQ} Frequently Asked Questions diff --git a/doc/pageant.but b/doc/pageant.but index 97d41087..1f6b7c21 100644 --- a/doc/pageant.but +++ b/doc/pageant.but @@ -170,7 +170,7 @@ by the command, like this: \c C:\PuTTY\pageant.exe d:\main.ppk -c C:\PuTTY\putty.exe -\S{pageant-cmdline-openssh} Integrating with Windows OpenSSH +\S{pageant-cmdline-openssh} Integrating with \i{Windows OpenSSH} Windows's own port of OpenSSH uses the same mechanism as Pageant to talk to its SSH agent (Windows named pipes). This means that Windows @@ -184,7 +184,7 @@ configuration, then \c{ssh.exe} should automatically use Pageant as its agent, so that you can keep your keys in one place and have both SSH clients able to use them. -The option is \c{--openssh-config}, and you follow it with a filename. +The option is \i\c{--openssh-config}, and you follow it with a filename. To refer to this file from your main OpenSSH configuration, you can use the \cq{Include} directive. For example, you might run Pageant -- cgit v1.2.3 From 176f01ea7cc1a848179e09b93820acf839c68f52 Mon Sep 17 00:00:00 2001 From: Jacob Nevins Date: Fri, 20 May 2022 19:33:56 +0100 Subject: Ensure Unix putty and pterm have correct icons. I noticed that my pterm had the same icon as my putty. --- unix/CMakeLists.txt | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/unix/CMakeLists.txt b/unix/CMakeLists.txt index 342ca14f..c2d5e2eb 100644 --- a/unix/CMakeLists.txt +++ b/unix/CMakeLists.txt @@ -19,14 +19,16 @@ add_sources_from_current_dir(utils utils/pollwrap.c utils/signal.c utils/x11_ignore_error.c - # Compiled icon pixmap files - putty-xpm.c - putty-config-xpm.c - pterm-xpm.c - pterm-config-xpm.c # We want the ISO C implementation of ltime(), because we don't have # a local better alternative ../utils/ltime.c) +# Compiled icon pixmap files +add_library(puttyxpms STATIC + putty-xpm.c + putty-config-xpm.c) +add_library(ptermxpms STATIC + pterm-xpm.c + pterm-config-xpm.c) add_sources_from_current_dir(eventloop cliloop.c uxsel.c) add_sources_from_current_dir(console @@ -152,7 +154,7 @@ if(GTK_FOUND) pty.c) be_list(pterm pterm) target_link_libraries(pterm - guiterminal eventloop settings charset utils + guiterminal eventloop settings charset utils ptermxpms ${GTK_LIBRARIES} ${X11_LIBRARIES}) installed_program(pterm) @@ -166,7 +168,7 @@ if(GTK_FOUND) pty.c) be_list(ptermapp pterm) target_link_libraries(ptermapp - guiterminal eventloop settings charset utils + guiterminal eventloop settings charset utils ptermxpms ${GTK_LIBRARIES} ${X11_LIBRARIES}) endif() @@ -176,7 +178,7 @@ if(GTK_FOUND) be_list(putty PuTTY SSH SERIAL OTHERBACKENDS) target_link_libraries(putty guiterminal eventloop sshclient otherbackends settings - network crypto charset utils + network crypto charset utils puttyxpms ${GTK_LIBRARIES} ${X11_LIBRARIES}) set_target_properties(putty PROPERTIES LINK_INTERFACE_MULTIPLICITY 2) @@ -190,7 +192,7 @@ if(GTK_FOUND) be_list(puttyapp PuTTY SSH SERIAL OTHERBACKENDS) target_link_libraries(puttyapp guiterminal eventloop sshclient otherbackends settings - network crypto charset utils + network crypto charset utils puttyxpms ${GTK_LIBRARIES} ${X11_LIBRARIES}) endif() @@ -204,5 +206,6 @@ if(GTK_FOUND) be_list(puttytel PuTTYtel SERIAL OTHERBACKENDS) target_link_libraries(puttytel guiterminal eventloop otherbackends settings network charset utils + puttyxpms ${GTK_LIBRARIES} ${X11_LIBRARIES}) endif() -- cgit v1.2.3 From 0ff0e6203720464b1de216e430d604c967e24bb3 Mon Sep 17 00:00:00 2001 From: Jacob Nevins Date: Fri, 20 May 2022 19:35:59 +0100 Subject: Better header comment for noaskpass.c. --- unix/noaskpass.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unix/noaskpass.c b/unix/noaskpass.c index 3d77b51c..de646c55 100644 --- a/unix/noaskpass.c +++ b/unix/noaskpass.c @@ -1,5 +1,5 @@ /* - * GTK implementation of a GUI password/passphrase prompt. + * Dummy (lack-of-)implementation of a GUI password/passphrase prompt. */ #include "putty.h" -- cgit v1.2.3 From b42f9c2cf75742d3fcb611b6f2132da506ef2d6b Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 21 May 2022 09:07:42 +0100 Subject: Fix a type warning in conpty.c. Turns out that standard C 'size_t' and the Win32 API's 'SIZE_T' aren't the same integer type in all cases! They have the same _size_, but in 32-bit, one of them is a typedef for 'unsigned int' and the other for 'unsigned long', leading to annoying pointer-type mismatch warnings. --- windows/conpty.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/windows/conpty.c b/windows/conpty.c index ca907020..385a7da3 100644 --- a/windows/conpty.c +++ b/windows/conpty.c @@ -190,7 +190,7 @@ static char *conpty_init(const BackendVtable *vt, Seat *seat, si.StartupInfo.cb = sizeof(si); - size_t attrsize = 0; + SIZE_T attrsize = 0; InitializeProcThreadAttributeList(NULL, 1, 0, &attrsize); si.lpAttributeList = smalloc(attrsize); if (!InitializeProcThreadAttributeList( -- cgit v1.2.3 From 4ae8b742ab3b916d549f74c05bce84c5e91453ad Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 21 May 2022 09:34:38 +0100 Subject: pterm.exe: load the ConPTY API at run time. This allows a better error message if you try to run pterm.exe on a Windows machine too old for kernel32.dll to contain the required API functions. --- windows/conpty.c | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/windows/conpty.c b/windows/conpty.c index 385a7da3..c23c81e1 100644 --- a/windows/conpty.c +++ b/windows/conpty.c @@ -25,6 +25,27 @@ struct ConPTY { Backend backend; }; +DECL_WINDOWS_FUNCTION(static, HRESULT, CreatePseudoConsole, + (COORD, HANDLE, HANDLE, DWORD, HPCON *)); +DECL_WINDOWS_FUNCTION(static, void, ClosePseudoConsole, (HPCON)); +DECL_WINDOWS_FUNCTION(static, HRESULT, ResizePseudoConsole, (HPCON, COORD)); + +static bool init_conpty_api(void) +{ + static bool tried = false; + if (!tried) { + tried = true; + HMODULE kernel32_module = load_system32_dll("kernel32.dll"); + GET_WINDOWS_FUNCTION(kernel32_module, CreatePseudoConsole); + GET_WINDOWS_FUNCTION(kernel32_module, ClosePseudoConsole); + GET_WINDOWS_FUNCTION(kernel32_module, ResizePseudoConsole); + } + + return (p_CreatePseudoConsole != NULL && + p_ClosePseudoConsole != NULL && + p_ResizePseudoConsole != NULL); +} + static void conpty_terminate(ConPTY *conpty) { if (conpty->out) { @@ -49,7 +70,7 @@ static void conpty_terminate(ConPTY *conpty) conpty->hprocess = INVALID_HANDLE_VALUE; } if (conpty->pseudoconsole != INVALID_HANDLE_VALUE) { - ClosePseudoConsole(conpty->pseudoconsole); + p_ClosePseudoConsole(conpty->pseudoconsole); conpty->pseudoconsole = INVALID_HANDLE_VALUE; } } @@ -78,7 +99,7 @@ static void conpty_process_wait_callback(void *vctx) * as things clean themselves up. */ if (conpty->pseudoconsole != INVALID_HANDLE_VALUE) { - ClosePseudoConsole(conpty->pseudoconsole); + p_ClosePseudoConsole(conpty->pseudoconsole); conpty->pseudoconsole = INVALID_HANDLE_VALUE; } } @@ -158,6 +179,12 @@ static char *conpty_init(const BackendVtable *vt, Seat *seat, STARTUPINFOEX si; memset(&si, 0, sizeof(si)); + if (!init_conpty_api()) { + err = dupprintf("Pseudo-console API is not available on this " + "Windows system"); + goto out; + } + if (!CreatePipe(&in_r, &in_w, NULL, 0)) { err = dupprintf("CreatePipe: %s", win_strerror(GetLastError())); goto out; @@ -171,7 +198,7 @@ static char *conpty_init(const BackendVtable *vt, Seat *seat, size.X = conf_get_int(conf, CONF_width); size.Y = conf_get_int(conf, CONF_height); - HRESULT result = CreatePseudoConsole(size, in_r, out_w, 0, &pcon); + HRESULT result = p_CreatePseudoConsole(size, in_r, out_w, 0, &pcon); if (FAILED(result)) { if (HRESULT_FACILITY(result) == FACILITY_WIN32) err = dupprintf("CreatePseudoConsole: %s", @@ -271,7 +298,7 @@ static char *conpty_init(const BackendVtable *vt, Seat *seat, if (out_w != INVALID_HANDLE_VALUE) CloseHandle(out_w); if (pcon_needs_cleanup) - ClosePseudoConsole(pcon); + p_ClosePseudoConsole(pcon); sfree(si.lpAttributeList); return err; } @@ -311,7 +338,7 @@ static void conpty_size(Backend *be, int width, int height) COORD size; size.X = width; size.Y = height; - ResizePseudoConsole(conpty->pseudoconsole, size); + p_ResizePseudoConsole(conpty->pseudoconsole, size); } static void conpty_special(Backend *be, SessionSpecialCode code, int arg) -- cgit v1.2.3 From 1a0d076dfb4ecdf19eb7c31f28dc92d74f423156 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 21 May 2022 10:23:01 +0100 Subject: Disallow -4 and -6 in TOOLTYPE_NONNETWORK. They were accepted by Unix pterm, by mistake, and then totally ignored because nothing in pterm ever makes a network connection, so nothing cares whether you configured it to use IPv4 or IPv6. --- cmdline.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmdline.c b/cmdline.c index 8e48b358..68eafc8e 100644 --- a/cmdline.c +++ b/cmdline.c @@ -749,11 +749,13 @@ int cmdline_process_param(const char *p, char *value, if (!strcmp(p, "-4") || !strcmp(p, "-ipv4")) { RETURN(1); + UNAVAILABLE_IN(TOOLTYPE_NONNETWORK); SAVEABLE(1); conf_set_int(conf, CONF_addressfamily, ADDRTYPE_IPV4); } if (!strcmp(p, "-6") || !strcmp(p, "-ipv6")) { RETURN(1); + UNAVAILABLE_IN(TOOLTYPE_NONNETWORK); SAVEABLE(1); conf_set_int(conf, CONF_addressfamily, ADDRTYPE_IPV6); } -- cgit v1.2.3 From e06a3dda4505dfd0fdeec5b183f21e79e13da0d1 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 21 May 2022 10:32:32 +0100 Subject: pterm.exe: run command-line options through cmdline.c. This makes pterm.exe support the same (very small) subset of the standard option collection that Unix pterm does. Namely, -load (which won't do anything useful with a hostname to connect to, but is still useful if you have a saved session containing configuration like colours or default size or what have you), and also -sessionlog. To make this work, I've had to move the 'tooltype' definition out of window.c into {putty,pterm}.c, so that it can be defined differently in the two. --- windows/pterm.c | 18 ++++++++++++++++-- windows/putty.c | 5 +++++ windows/window.c | 5 ----- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/windows/pterm.c b/windows/pterm.c index 0df849f9..bb68245d 100644 --- a/windows/pterm.c +++ b/windows/pterm.c @@ -1,6 +1,10 @@ #include "putty.h" #include "storage.h" +const unsigned cmdline_tooltype = + TOOLTYPE_NONNETWORK | + TOOLTYPE_NO_VERBOSE_OPTION; + void gui_term_process_cmdline(Conf *conf, char *cmdline) { do_defaults(NULL, conf); @@ -16,8 +20,16 @@ void gui_term_process_cmdline(Conf *conf, char *cmdline) split_into_argv(cmdline, &argc, &argv, &argstart); for (int i = 0; i < argc; i++) { - const char *arg = argv[i]; - if (!strcmp(arg, "-e")) { + char *arg = argv[i]; + int retd = cmdline_process_param( + arg, i+1 Date: Sat, 21 May 2022 17:16:40 +0100 Subject: A couple of release-checklist updates. I thought of a couple more things it's worth double-checking at release time, and also, moved the 'merge to main' instructions from the RC build step to the final release phase, because that way they don't have to be pointlessly redone if commits have appeared on main in between. --- CHECKLST.txt | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/CHECKLST.txt b/CHECKLST.txt index 82fc9d6a..f3edafb6 100644 --- a/CHECKLST.txt +++ b/CHECKLST.txt @@ -76,9 +76,6 @@ Making a release candidate build - Make the release tag, pointing at the version-update commit we just generated. - - If the release is on a branch (which I expect it generally will - be), merge that branch to main. - - Make a release-candidate build from the release tag, and put the build.out and build.log files somewhere safe. Normally I store these inside the ~/src/putty/X.YZ directory, alongside the git @@ -119,6 +116,10 @@ Making a release candidate build also try Debian sid + test-build with all of GTK 1, 2 and 3 + test-build with -DNOT_X_WINDOWS + * test that the Windows source builds with Visual Studio (just in + case there's an unguarded clangism that would prevent it) + * quick check of the outlying network protocols (Telnet, SUPDUP + etc) * feed the release-candidate source to Coverity and make sure it didn't turn up any last-minute problems * make sure we have a clean run of testsc @@ -194,12 +195,18 @@ locally, this is the procedure for putting it up on the web. correctly and work: ../putty/release.pl --version=X.YZ --postcheck + - If the release is on a branch (which I expect it generally will + be), merge that branch to main, so that the 'update version number' + change appears on main and the snapshots start announcing + themselves as post-X.YZ. + - Push all the git repositories: * run 'git push' in the website checkout * run 'git push' in the wishlist checkout * push from the main PuTTY checkout. Typically this one will be - pushing both the release tag and an update to the main branch, - plus removing the pre-release branch, so you'll want some + pushing both the release tag and the merge we just made to the + main branch, plus removing the pre-release branch, so you'll + want some commands along these lines: git push origin main # update the main branch git push origin --tags # should push the new release tag -- cgit v1.2.3 From c5d837c14a0f5c7a9a96b32454a5b92bc1358f48 Mon Sep 17 00:00:00 2001 From: Jacob Nevins Date: Tue, 24 May 2022 13:32:55 +0100 Subject: Special backend init error handling for pterm. Fixes a cosmetic issue where the new ConPTY error added in 4ae8b742ab had an ugly "Unable to open connection to". (Arguably this ought to test a backend property rather than cmdline_tooltype.) --- unix/window.c | 13 ++++++++++--- windows/window.c | 14 ++++++++++---- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/unix/window.c b/unix/window.c index f4a50007..811ffc17 100644 --- a/unix/window.c +++ b/unix/window.c @@ -5165,9 +5165,16 @@ static void start_backend(GtkFrontend *inst) conf_get_bool(inst->conf, CONF_tcp_keepalives)); if (error) { - seat_connection_fatal(&inst->seat, - "Unable to open connection to %s:\n%s", - conf_dest(inst->conf), error); + if (cmdline_tooltype & TOOLTYPE_NONNETWORK) { + /* Special case for pterm. */ + seat_connection_fatal(&inst->seat, + "Unable to open terminal:\n%s", + error); + } else { + seat_connection_fatal(&inst->seat, + "Unable to open connection to %s:\n%s", + conf_dest(inst->conf), error); + } sfree(error); inst->exited = true; return; diff --git a/windows/window.c b/windows/window.c index 8230ab6f..164e3a90 100644 --- a/windows/window.c +++ b/windows/window.c @@ -1,6 +1,6 @@ /* - * window.c - the PuTTY(tel) main program, which runs a PuTTY terminal - * emulator and backend in a window. + * window.c - the PuTTY(tel)/pterm main program, which runs a PuTTY + * terminal emulator and backend in a window. */ #include @@ -381,8 +381,14 @@ static void start_backend(void) conf_get_bool(conf, CONF_tcp_keepalives)); if (error) { char *str = dupprintf("%s Error", appname); - char *msg = dupprintf("Unable to open connection to\n%s\n%s", - conf_dest(conf), error); + char *msg; + if (cmdline_tooltype & TOOLTYPE_NONNETWORK) { + /* Special case for pterm. */ + msg = dupprintf("Unable to open terminal:\n%s", error); + } else { + msg = dupprintf("Unable to open connection to\n%s\n%s", + conf_dest(conf), error); + } sfree(error); MessageBox(NULL, msg, str, MB_ICONERROR | MB_OK); sfree(str); -- cgit v1.2.3 From 01d85614464a8405a427669b73142c281f3a7faf Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Tue, 24 May 2022 17:43:48 +0100 Subject: do_bidi: initialise paragraphOverride correctly. I'd forgotten to initialise it at all, which meant it was set to zero by the initial memset of the whole BidiContext on creation. But in our enumeration of bidi character types, zero corresponds to L (the most common left-to-right alphabetic character class), and as a value for paragraphOverride, that is not neutral. As a result, a command such as this (assuming UTF-8) echo -e '\xD7\x90\xD7\x91' would produce Hebrew aleph and beth in the correct display order (aleph on the right), but aligned to the left margin of the terminal instead of the right margin, because the overall direction of the line was taken to be forcibly overridden to "left-to-right" instead of being inferred dynamically from the line contents. do_bidi() is a tiny wrapper on the inner function that does all the real work. And the inner function has been subjected to the whole Unicode 14 bidi conformance test. So naturally, the "trivial" but untested function just outside it is where the embarrassing bug was. --- terminal/bidi.c | 1 + 1 file changed, 1 insertion(+) diff --git a/terminal/bidi.c b/terminal/bidi.c index a56570fd..128f84c5 100644 --- a/terminal/bidi.c +++ b/terminal/bidi.c @@ -3602,6 +3602,7 @@ void do_bidi(BidiContext *ctx, bidi_char *text, size_t textlen) #ifdef REMOVE_FORMATTING_CHARACTERS abort(); /* can't use the standard algorithm in a live terminal */ #else + ctx->paragraphOverride = ON; do_bidi_new(ctx, text, textlen); #endif } -- cgit v1.2.3 From 61ab33efe48cfd1cea77e249e5598a89f3387fa3 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Tue, 24 May 2022 17:53:18 +0100 Subject: Update version number for 0.77 release. --- Buildscr | 2 +- LATEST.VER | 2 +- doc/plink.but | 2 +- doc/pscp.but | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Buildscr b/Buildscr index a920b2f0..bd3aad20 100644 --- a/Buildscr +++ b/Buildscr @@ -35,7 +35,7 @@ module putty ifeq "$(RELEASE)" "" set Ndate $(!builddate) ifneq "$(Ndate)" "" in . do echo $(Ndate) | perl -pe 's/(....)(..)(..)/$$1-$$2-$$3/' > date ifneq "$(Ndate)" "" read Date date -set Epoch 17818 # update this at every release +set Epoch 18136 # update this at every release ifneq "$(Ndate)" "" in . do echo $(Ndate) | perl -ne 'use Time::Local; /(....)(..)(..)/ and print timegm(0,0,0,$$3,$$2-1,$$1) / 86400 - $(Epoch)' > days ifneq "$(Ndate)" "" read Days days diff --git a/LATEST.VER b/LATEST.VER index 07b41f7d..9e1e206c 100644 --- a/LATEST.VER +++ b/LATEST.VER @@ -1 +1 @@ -0.76 +0.77 diff --git a/doc/plink.but b/doc/plink.but index b132b6eb..44b29c52 100644 --- a/doc/plink.but +++ b/doc/plink.but @@ -41,7 +41,7 @@ use Plink: \c C:\>plink \c Plink: command-line connection utility -\c Release 0.76 +\c Release 0.77 \c Usage: plink [options] [user@]host [command] \c ("host" can also be a PuTTY saved session name) \c Options: diff --git a/doc/pscp.but b/doc/pscp.but index abf8f597..23b17a6a 100644 --- a/doc/pscp.but +++ b/doc/pscp.but @@ -39,7 +39,7 @@ use PSCP: \c C:\>pscp \c PuTTY Secure Copy client -\c Release 0.76 +\c Release 0.77 \c Usage: pscp [options] [user@]host:source target \c pscp [options] source [source...] [user@]host:target \c pscp [options] -ls [user@]host:filespec -- cgit v1.2.3 From e94699ae56a35754e3b41ee78eb0d60d30fff565 Mon Sep 17 00:00:00 2001 From: Jacob Nevins Date: Wed, 25 May 2022 00:39:21 +0100 Subject: Remove reference to Bugtraq. (Rather showing our age...) --- doc/faq.but | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/faq.but b/doc/faq.but index e9dddb66..6678dd26 100644 --- a/doc/faq.but +++ b/doc/faq.but @@ -150,7 +150,7 @@ 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 +applying a little intelligence and keeping an eye on oss-security, 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. -- cgit v1.2.3 From 7e65b705f157dd2a529b687ecd85b0334ef415c4 Mon Sep 17 00:00:00 2001 From: Jacob Nevins Date: Wed, 25 May 2022 00:41:58 +0100 Subject: Acknowledge existence of Windows 11. (PuTTY has been seen running on it.) --- doc/faq.but | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/faq.but b/doc/faq.but index 6678dd26..11b93775 100644 --- a/doc/faq.but +++ b/doc/faq.but @@ -220,7 +220,7 @@ Currently, release versions of PuTTY tools only run on Windows systems and Unix. As of 0.68, the supplied PuTTY executables run on versions of Windows -from XP onwards, up to and including Windows 10; and we know of no +from XP onwards, up to and including Windows 11; and we know of no reason why PuTTY should not continue to work on future versions of Windows. We provide 32-bit and 64-bit Windows executables for the common x86 processor family; see \k{faq-32bit-64bit} for discussion -- cgit v1.2.3 From 55b53923d6a77b088e62fae242c5f42f143e6c90 Mon Sep 17 00:00:00 2001 From: Jacob Nevins Date: Wed, 25 May 2022 01:16:31 +0100 Subject: Tweak the "PSCP filenames with spaces" FAQ. These days it's overwhelmingly likely that SFTP will be in use, so deal with that case first. --- doc/faq.but | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/doc/faq.but b/doc/faq.but index 11b93775..df5524ea 100644 --- a/doc/faq.but +++ b/doc/faq.but @@ -607,9 +607,16 @@ To use PSCP properly, run it from a Command Prompt window. See \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: +If PSCP is using the newer SFTP protocol (which is usual with most +modern servers), this is straightforward; 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" . + +However, if PSCP is using the older SCP protocol for some reason, +things are more 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" @@ -633,13 +640,6 @@ 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" . - \S{faq-32bit-64bit}{Question} Should I run the 32-bit or the 64-bit version? -- cgit v1.2.3 From 7b2b116f27d35bc2a5d4efae85dac73efaf90eae Mon Sep 17 00:00:00 2001 From: Jacob Nevins Date: Wed, 25 May 2022 01:17:57 +0100 Subject: FAQ: cross-reference command-line docs. --- doc/faq.but | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/faq.but b/doc/faq.but index df5524ea..60f2e5e6 100644 --- a/doc/faq.but +++ b/doc/faq.but @@ -585,7 +585,7 @@ You can also paste by pressing Shift-Ins. 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. +through command line options. See \k{using-general-opts}. 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 -- cgit v1.2.3 From 3af153b6fb2b506169ed8acdc128cb8d18affbdf Mon Sep 17 00:00:00 2001 From: Jacob Nevins Date: Wed, 25 May 2022 01:18:21 +0100 Subject: We're unlikely to spend donations on Windows XP. --- doc/faq.but | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/faq.but b/doc/faq.but index 60f2e5e6..2e702a41 100644 --- a/doc/faq.but +++ b/doc/faq.but @@ -1287,7 +1287,7 @@ 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 +(perhaps new hardware, or a new version of Windows), 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, -- cgit v1.2.3 From b3e2c3eccc7b5db829732a921f70edc2c6f8d9ce Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Wed, 25 May 2022 22:55:56 +0100 Subject: Buildscr: fix escaping in version.but. In the echo "\\versionid foo" statement, the double \ turns into a single \ during dash's expansion phase, and the remaining '\v' turns into a vertical tab when dash's 'echo' builtin processes it. We need twice as many \ to generate a literal \ in the actual output. --- Buildscr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Buildscr b/Buildscr index a920b2f0..b0cf9a50 100644 --- a/Buildscr +++ b/Buildscr @@ -55,7 +55,7 @@ ifneq "$(SNAPSHOT)" "" set Puttytextver PuTTY development snapshot $(Date).$(vcs ifneq "$(SNAPSHOT)" "" set Textver Development snapshot $(Date).$(vcsid) ifeq "$(RELEASE)$(PRERELEASE)$(SNAPSHOT)" "" set Puttytextver PuTTY custom build $(Date).$(vcsid) ifeq "$(RELEASE)$(PRERELEASE)$(SNAPSHOT)" "" set Textver Custom build $(Date).$(vcsid) -in putty/doc do echo "\\versionid $(Puttytextver)" > version.but +in putty/doc do echo "\\\\versionid $(Puttytextver)" > version.but # Set up the version string for use in the SSH connection greeting. # -- cgit v1.2.3 From 55407f03700892f3c161203dbdeb0616a811ef47 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 27 May 2022 09:55:14 +0100 Subject: Checklist update: add Windows Store instructions. This is the first release for which I've had to submit a revised Store entry, and now I've worked out how to do it, I should write it down for next time. --- CHECKLST.txt | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHECKLST.txt b/CHECKLST.txt index f3edafb6..f0154948 100644 --- a/CHECKLST.txt +++ b/CHECKLST.txt @@ -226,6 +226,17 @@ locally, this is the procedure for putting it up on the web. subdirectory for the new version and that the links from its latest.html point into that subdirectory. + - Start the process of updating our Windows Store entry: + + log into partner.microsoft.com and go to Partner Center + + start editing the existing app submission, which should + automatically create a new submission + * provide a new set of installer URLs, then click "save all" + which actually uploads them + * change the "what's new in this release" text in the store + listing + * upload revised screenshots, if necessary + + press Publish to submit that to the actual upload process + - Announce the release! + Construct a release announcement email whose message body is the announcement written above, and which includes the following -- cgit v1.2.3 From 1952519c601f0bb5eb30fa7e0e21a97dedcd747d Mon Sep 17 00:00:00 2001 From: Jacob Nevins Date: Fri, 27 May 2022 11:07:04 +0100 Subject: Update Unix build instructions in FAQ. (Bit late, but never mind.) --- doc/faq.but | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/doc/faq.but b/doc/faq.but index 2e702a41..f3642fd5 100644 --- a/doc/faq.but +++ b/doc/faq.but @@ -250,8 +250,7 @@ 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} +subdirectory. You need \c{cmake} to build it; see the file \c{README} in the source distribution. This should build you: \b Unix ports of PuTTY, Plink, PSCP, and PSFTP, which work pretty much -- cgit v1.2.3 From eb7f5aff5c2234296b9ec69675e02aefa6bb773d Mon Sep 17 00:00:00 2001 From: Jacob Nevins Date: Sat, 28 May 2022 12:56:50 +0100 Subject: Fix Unix builds with PUTTY_GSSAPI=OFF. --- cmake/platforms/unix.cmake | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cmake/platforms/unix.cmake b/cmake/platforms/unix.cmake index 6a788cb4..291d1e64 100644 --- a/cmake/platforms/unix.cmake +++ b/cmake/platforms/unix.cmake @@ -123,6 +123,10 @@ cannot provide static GSSAPI support") endif() endif() +if(PUTTY_GSSAPI STREQUAL OFF) + set(NO_GSSAPI ON) +endif() + if(STRICT AND (CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_C_COMPILER_ID MATCHES "Clang")) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Werror -Wpointer-arith -Wvla") -- cgit v1.2.3 From a216d86106d40c38f05f1ffc03996be54d590aa6 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Tue, 31 May 2022 13:13:57 +0100 Subject: Windows mb_to_wc: support internal SBCSes. A user points out that the new charset-aware window title setting doesn't work if the configured character set is one of the entries in cp_list[] based on a hard-coded Unicode translation table, such as the ISO 8859 family. That's because the Windows mb_to_wc() function assumes that the code page it's given will always be OK to pass to the Windows API function MultiByteToWideChar, forgetting that for those internally implemented single-byte character sets are not. This commit adds a manual implementation of SBCS -> Unicode based on those tables, which restores the ability to set a window title specified in ISO 8859. However, it's not a full fix to windows/unicode.c in general, because wc_to_mb has a similar blind spot: it's only prepared to convert Unicode to an internally implemented SBCS if that SBCS happens to be the one currently set in ucsdata->line_codepage, because that's when we've already prepared the reverse lookup table. Probably we ought to sort that out, and arrange that it can make the reverse lookup table if suddenly called on to do a different conversion. But that needs more refactoring, so I haven't done it in this commit. --- windows/unicode.c | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/windows/unicode.c b/windows/unicode.c index 943c3c2d..c507a941 100644 --- a/windows/unicode.c +++ b/windows/unicode.c @@ -1240,6 +1240,35 @@ int wc_to_mb(int codepage, int flags, const wchar_t *wcstr, int wclen, int mb_to_wc(int codepage, int flags, const char *mbstr, int mblen, wchar_t *wcstr, int wclen) { + if (codepage >= 65536) { + /* Character set not known to Windows, so we'll have to + * translate it ourself */ + size_t index = codepage - 65536; + if (index >= lenof(cp_list)) + return 0; + const struct cp_list_item *cp = &cp_list[index]; + if (!cp->cp_table) + return 0; + + size_t remaining = wclen; + wchar_t *p = wcstr; + unsigned tablebase = 256 - cp->cp_size; + + while (mblen > 0) { + mblen--; + unsigned c = 0xFF & *mbstr++; + wchar_t wc = (c < tablebase ? c : cp->cp_table[c - tablebase]); + if (remaining > 0) { + remaining--; + *p++ = wc; + } else { + return p - wcstr; + } + } + + return p - wcstr; + } + int ret = MultiByteToWideChar(codepage, flags, mbstr, mblen, wcstr, wclen); if (ret) return ret; -- cgit v1.2.3 From a647296d51f876c2302c5f14be526f2c3db3b6ad Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Tue, 31 May 2022 13:28:50 +0100 Subject: buildinfo: add cases to recognise MSVC 17.1 and 17.2. --- utils/buildinfo.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/utils/buildinfo.c b/utils/buildinfo.c index c72765d5..c5d7255a 100644 --- a/utils/buildinfo.c +++ b/utils/buildinfo.c @@ -44,6 +44,10 @@ char *buildinfo(const char *newline) * cases, two different compiler versions have the same _MSC_VER * value, and have to be distinguished by _MSC_FULL_VER. */ +#elif _MSC_VER == 1932 + put_fmt(buf, " 2022 (17.2)"); +#elif _MSC_VER == 1931 + put_fmt(buf, " 2022 (17.1)"); #elif _MSC_VER == 1930 put_fmt(buf, " 2022 (17.0)"); #elif _MSC_VER == 1929 && _MSC_FULL_VER >= 192930100 -- cgit v1.2.3 From 8a907510dd8fa184f49b367c8207fc58c4e04e9f Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Wed, 1 Jun 2022 08:29:29 +0100 Subject: decode_codepage(): add missing const in prototype. --- putty.h | 2 +- unix/unicode.c | 2 +- windows/unicode.c | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/putty.h b/putty.h index 13c186cf..eddc7b56 100644 --- a/putty.h +++ b/putty.h @@ -2440,7 +2440,7 @@ int wc_to_mb(int codepage, int flags, const wchar_t *wcstr, int wclen, struct unicode_data *ucsdata); wchar_t xlat_uskbd2cyrllic(int ch); int check_compose(int first, int second); -int decode_codepage(char *cp_name); +int decode_codepage(const 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); diff --git a/unix/unicode.c b/unix/unicode.c index 4eaa45f4..1db17ef4 100644 --- a/unix/unicode.c +++ b/unix/unicode.c @@ -264,7 +264,7 @@ const char *cp_enumerate(int index) return charset_to_localenc(charset); } -int decode_codepage(char *cp_name) +int decode_codepage(const char *cp_name) { if (!cp_name || !*cp_name) return CS_UTF8; diff --git a/windows/unicode.c b/windows/unicode.c index c507a941..ed9a3050 100644 --- a/windows/unicode.c +++ b/windows/unicode.c @@ -1000,9 +1000,9 @@ int check_compose(int first, int second) return check_compose_internal(first, second, 0); } -int decode_codepage(char *cp_name) +int decode_codepage(const char *cp_name) { - char *s, *d; + const char *s, *d; const struct cp_list_item *cpi; int codepage = -1; CPINFO cpinfo; -- cgit v1.2.3 From 5a28658a6d815a69df75166115fb2c1e20b5c19a Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Wed, 1 Jun 2022 08:35:12 +0100 Subject: Remove uni_tbl from struct unicode_data. Instead of maintaining a single sparse table mapping Unicode to the currently selected code page, we now maintain a collection of such tables mapping Unicode to any code page we've so far found a need to work with, and we add code pages to that list as necessary, and never throw them away (since there are a limited number of them). This means that the wc_to_mb family of functions are effectively stateless: they no longer depend on a 'struct unicode_data' corresponding to the current terminal settings. So I've removed that parameter from all of them. This fills in the missing piece of yesterday's commit a216d86106d40c3: now wc_to_mb too should be able to handle internally-implemented character sets, by hastily making their reverse mapping table if it doesn't already have it. (That was only a _latent_ bug, because the only use of wc_to_mb in the cross-platform or Windows code _did_ want to convert to the currently selected code page, so the old strategy worked in that case. But there was no protection against an unworkable use of it being added later.) --- misc.h | 4 +- putty.h | 4 +- terminal/terminal.c | 2 +- unix/unicode.c | 3 +- unix/unifont.c | 7 ++- unix/window.c | 3 +- utils/dup_wc_to_mb.c | 9 ++-- windows/unicode.c | 149 +++++++++++++++++++++++++++++++++++++++------------ windows/window.c | 2 +- 9 files changed, 130 insertions(+), 53 deletions(-) diff --git a/misc.h b/misc.h index fe8eb06a..b4b9bd57 100644 --- a/misc.h +++ b/misc.h @@ -72,9 +72,9 @@ void strbuf_finalise_agent_query(strbuf *buf); wchar_t *dup_mb_to_wc_c(int codepage, int flags, const char *string, int len); wchar_t *dup_mb_to_wc(int codepage, int flags, const char *string); char *dup_wc_to_mb_c(int codepage, int flags, const wchar_t *string, int len, - const char *defchr, struct unicode_data *ucsdata); + const char *defchr); char *dup_wc_to_mb(int codepage, int flags, const wchar_t *string, - const char *defchr, struct unicode_data *ucsdata); + const char *defchr); static inline int toint(unsigned u) { diff --git a/putty.h b/putty.h index eddc7b56..d7f33369 100644 --- a/putty.h +++ b/putty.h @@ -266,7 +266,6 @@ struct sesslist { }; struct unicode_data { - char **uni_tbl; bool dbcs_screenfont; int font_codepage; int line_codepage; @@ -2436,8 +2435,7 @@ bool is_dbcs_leadbyte(int codepage, char byte); int mb_to_wc(int codepage, int flags, const char *mbstr, int mblen, wchar_t *wcstr, int wclen); int wc_to_mb(int codepage, int flags, const wchar_t *wcstr, int wclen, - char *mbstr, int mblen, const char *defchr, - struct unicode_data *ucsdata); + char *mbstr, int mblen, const char *defchr); wchar_t xlat_uskbd2cyrllic(int ch); int check_compose(int first, int second); int decode_codepage(const char *cp_name); diff --git a/terminal/terminal.c b/terminal/terminal.c index fbaa619b..96bc98f8 100644 --- a/terminal/terminal.c +++ b/terminal/terminal.c @@ -3416,7 +3416,7 @@ static strbuf *term_input_data_from_unicode( char *bufptr = strbuf_append(buf, len + 1); int rv; rv = wc_to_mb(term->ucsdata->line_codepage, 0, widebuf, len, - bufptr, len + 1, NULL, term->ucsdata); + bufptr, len + 1, NULL); strbuf_shrink_to(buf, rv < 0 ? 0 : rv); } diff --git a/unix/unicode.c b/unix/unicode.c index 1db17ef4..a98c8d3b 100644 --- a/unix/unicode.c +++ b/unix/unicode.c @@ -61,8 +61,7 @@ int mb_to_wc(int codepage, int flags, const char *mbstr, int mblen, } int wc_to_mb(int codepage, int flags, const wchar_t *wcstr, int wclen, - char *mbstr, int mblen, const char *defchr, - struct unicode_data *ucsdata) + char *mbstr, int mblen, const char *defchr) { if (codepage == DEFAULT_CODEPAGE) { char output[MB_LEN_MAX]; diff --git a/unix/unifont.c b/unix/unifont.c index 62445db2..5eaded54 100644 --- a/unix/unifont.c +++ b/unix/unifont.c @@ -600,7 +600,7 @@ static bool x11font_has_glyph(unifont *font, wchar_t glyph) */ char sbstring[2]; int sblen = wc_to_mb(xfont->real_charset, 0, &glyph, 1, - sbstring, 2, "", NULL); + sbstring, 2, ""); if (sblen == 0 || !sbstring[0]) return false; /* not even in the charset */ @@ -956,7 +956,7 @@ static void x11font_draw_text(unifont_drawctx *ctx, unifont *font, */ char *sbstring = snewn(len+1, char); int sblen = wc_to_mb(xfont->real_charset, 0, string, len, - sbstring, len+1, ".", NULL); + sbstring, len+1, "."); x11font_really_draw_text(x11font_drawfuncs + index + 0, ctx, &xfont->fonts[sfid], xfont->disp, x, y, sbstring, sblen, shadowoffset, @@ -1644,8 +1644,7 @@ static void pangofont_draw_internal(unifont_drawctx *ctx, unifont *font, * string to UTF-8. */ utfstring = snewn(len*6+1, char); /* UTF-8 has max 6 bytes/char */ - utflen = wc_to_mb(CS_UTF8, 0, string, len, - utfstring, len*6+1, ".", NULL); + utflen = wc_to_mb(CS_UTF8, 0, string, len, utfstring, len*6+1, "."); utfptr = utfstring; while (utflen > 0) { diff --git a/unix/window.c b/unix/window.c index 811ffc17..9d33904a 100644 --- a/unix/window.c +++ b/unix/window.c @@ -3056,8 +3056,7 @@ static void gtkwin_clip_write( state->pasteout_data_len = len*6; state->pasteout_data_len = wc_to_mb(inst->ucsdata.line_codepage, 0, data, len, state->pasteout_data, - state->pasteout_data_len, - NULL, NULL); + state->pasteout_data_len, NULL); if (state->pasteout_data_len == 0) { sfree(state->pasteout_data); state->pasteout_data = NULL; diff --git a/utils/dup_wc_to_mb.c b/utils/dup_wc_to_mb.c index e91a8916..36088196 100644 --- a/utils/dup_wc_to_mb.c +++ b/utils/dup_wc_to_mb.c @@ -11,14 +11,14 @@ #include "misc.h" char *dup_wc_to_mb_c(int codepage, int flags, const wchar_t *string, int len, - const char *defchr, struct unicode_data *ucsdata) + const char *defchr) { size_t outsize = len+1; char *out = snewn(outsize, char); while (true) { size_t outlen = wc_to_mb(codepage, flags, string, len, out, outsize, - defchr, ucsdata); + defchr); /* We can only be sure we've consumed the whole input if the * output is not within a multibyte-character-length of the * end of the buffer! */ @@ -32,8 +32,7 @@ char *dup_wc_to_mb_c(int codepage, int flags, const wchar_t *string, int len, } char *dup_wc_to_mb(int codepage, int flags, const wchar_t *string, - const char *defchr, struct unicode_data *ucsdata) + const char *defchr) { - return dup_wc_to_mb_c(codepage, flags, string, wcslen(string), - defchr, ucsdata); + return dup_wc_to_mb_c(codepage, flags, string, wcslen(string), defchr); } diff --git a/windows/unicode.c b/windows/unicode.c index ed9a3050..09bce095 100644 --- a/windows/unicode.c +++ b/windows/unicode.c @@ -437,9 +437,114 @@ static const struct cp_list_item cp_list[] = { static void link_font(WCHAR * line_tbl, WCHAR * font_tbl, WCHAR attr); +/* + * We keep a collection of reverse mappings from Unicode back to code pages, + * in the form of array[256] of array[256] of char. These live forever in a + * local tree234, and we just make a new one whenever we find a need. + */ +typedef struct reverse_mapping { + int codepage; + char **blocks; +} reverse_mapping; +static tree234 *reverse_mappings = NULL; + +static int reverse_mapping_cmp(void *av, void *bv) +{ + const reverse_mapping *a = (const reverse_mapping *)av; + const reverse_mapping *b = (const reverse_mapping *)bv; + if (a->codepage < b->codepage) + return -1; + if (a->codepage > b->codepage) + return +1; + return 0; +} + +static int reverse_mapping_find(void *av, void *bv) +{ + const reverse_mapping *a = (const reverse_mapping *)av; + int b_codepage = *(const int *)bv; + if (a->codepage < b_codepage) + return -1; + if (a->codepage > b_codepage) + return +1; + return 0; +} + +static reverse_mapping *get_existing_reverse_mapping(int codepage) +{ + if (!reverse_mappings) + return NULL; + return find234(reverse_mappings, &codepage, reverse_mapping_find); +} + +static reverse_mapping *make_reverse_mapping_inner( + int codepage, const wchar_t *mapping) +{ + if (!reverse_mappings) + reverse_mappings = newtree234(reverse_mapping_cmp); + + reverse_mapping *rmap = snew(reverse_mapping); + rmap->blocks = snewn(256, char *); + memset(rmap->blocks, 0, 256 * sizeof(char *)); + + for (size_t i = 0; i < 256; i++) { + /* These special kinds of value correspond to no Unicode character */ + if (DIRECT_CHAR(mapping[i])) + continue; + if (DIRECT_FONT(mapping[i])) + continue; + + size_t chr = mapping[i]; + size_t block = chr >> 8, index = chr & 0xFF; + + if (!rmap->blocks[block]) { + rmap->blocks[block] = snewn(256, char); + memset(rmap->blocks[block], 0, 256); + } + rmap->blocks[block][index] = i; + } + + rmap->codepage = codepage; + reverse_mapping *added = add234(reverse_mappings, rmap); + assert(added == rmap); /* we already checked it wasn't already in there */ + return added; +} + +static void make_reverse_mapping(int codepage, const wchar_t *mapping) +{ + if (get_existing_reverse_mapping(codepage)) + return; /* we've already got this one */ + make_reverse_mapping_inner(codepage, mapping); +} + +static reverse_mapping *get_reverse_mapping(int codepage) +{ + /* + * Try harder to get a reverse mapping for a codepage we implement + * internally via a translation table, by hastily making it if it doesn't + * already exist. + */ + + reverse_mapping *rmap = get_existing_reverse_mapping(codepage); + if (rmap) + return rmap; + + if (codepage < 65536) + return NULL; + if (codepage > 65536 + lenof(cp_list)) + return NULL; + const struct cp_list_item *cp = &cp_list[codepage - 65536]; + if (!cp->cp_table) + return NULL; + + wchar_t mapping[256]; + get_unitab(codepage, mapping, 0); + return make_reverse_mapping_inner(codepage, mapping); +} + void init_ucs(Conf *conf, struct unicode_data *ucsdata) { - int i, j; + int i; bool used_dtf = false; int vtmode; @@ -522,31 +627,9 @@ void init_ucs(Conf *conf, struct unicode_data *ucsdata) 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; - } + /* Make sure a reverse mapping exists for this code page. */ + make_reverse_mapping(ucsdata->line_codepage, ucsdata->unitab_line); } /* Find the line control characters. */ @@ -1156,20 +1239,21 @@ void get_unitab(int codepage, wchar_t * unitab, int ftype) } int wc_to_mb(int codepage, int flags, const wchar_t *wcstr, int wclen, - char *mbstr, int mblen, const char *defchr, - struct unicode_data *ucsdata) + char *mbstr, int mblen, const char *defchr) { - char *p; - int i; - if (ucsdata && codepage == ucsdata->line_codepage && ucsdata->uni_tbl) { + reverse_mapping *rmap = get_reverse_mapping(codepage); + + if (rmap) { /* Do this by array lookup if we can. */ if (wclen < 0) { for (wclen = 0; wcstr[wclen++] ;); /* will include the NUL */ } + char *p; + int i; for (p = mbstr, i = 0; i < wclen; i++) { wchar_t ch = wcstr[i]; int by; - char *p1; + const char *p1; #define WRITECH(chr) do \ { \ @@ -1177,8 +1261,7 @@ int wc_to_mb(int codepage, int flags, const wchar_t *wcstr, int wclen, *p++ = (char)(chr); \ } while (0) - if (ucsdata->uni_tbl && - (p1 = ucsdata->uni_tbl[(ch >> 8) & 0xFF]) != NULL && + if ((p1 = rmap->blocks[(ch >> 8) & 0xFF]) != NULL && (by = p1[ch & 0xFF]) != '\0') WRITECH(by); else if (ch < 0x80) diff --git a/windows/window.c b/windows/window.c index 164e3a90..40eb5c14 100644 --- a/windows/window.c +++ b/windows/window.c @@ -473,7 +473,7 @@ static void sw_SetWindowText(HWND hwnd, wchar_t *text) if (unicode_window) { SetWindowTextW(hwnd, text); } else { - char *mb = dup_wc_to_mb(DEFAULT_CODEPAGE, 0, text, "?", &ucsdata); + char *mb = dup_wc_to_mb(DEFAULT_CODEPAGE, 0, text, "?"); SetWindowTextA(hwnd, mb); sfree(mb); } -- cgit v1.2.3 From e0959d46471aeb94b3db4de662e124041f7d10f3 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Wed, 1 Jun 2022 11:14:21 +0100 Subject: Macro wrapper on ctrl_radiobuttons to fill in the NULL. ctrl_radiobuttons has a variadic argument list that it expects to be terminated by a null pointer where a const char * should be. So the terminating NULL at each call site ought to be cast to const char *, for the usual reason (NULL might expand literally to something not the same size as a pointer and get mis-marshalled in the variadic argument list). It wasn't being. Fixed in the same way as dupcat: I've turned ctrl_radiobuttons into a macro wrapper on the underlying function, which adds the NULL automatically with its correct cast. This also saves verbiage at every call site, because now I can leave out all the NULLs there. --- config.c | 80 +++++++++++++++++++++++++------------------------------- dialog.c | 6 ++--- dialog.h | 8 +++--- windows/config.c | 8 +++--- 4 files changed, 47 insertions(+), 55 deletions(-) diff --git a/config.c b/config.c index 94b434a7..cca76b29 100644 --- a/config.c +++ b/config.c @@ -1844,7 +1844,7 @@ void setup_config_box(struct controlbox *b, bool midsession, ctrl_columns(s, 2, 62, 38); c = ctrl_radiobuttons(s, NULL, NO_SHORTCUT, 3, HELPCTX(session_hostname), - config_protocols_handler, P(hp), NULL); + config_protocols_handler, P(hp)); c->column = 0; hp->protradio = c; c->radio.buttons = sresize(c->radio.buttons, PROTOCOL_LIMIT, char *); @@ -1944,7 +1944,7 @@ void setup_config_box(struct controlbox *b, bool midsession, I(CONF_close_on_exit), "Always", I(FORCE_ON), "Never", I(FORCE_OFF), - "Only on clean exit", I(AUTO), NULL); + "Only on clean exit", I(AUTO)); /* * The Session/Logging panel. @@ -1974,8 +1974,7 @@ void setup_config_box(struct controlbox *b, bool midsession, "Printable output", 'p', I(LGTYP_ASCII), "All session output", 'l', I(LGTYP_DEBUG), sshlogname, 's', I(LGTYP_PACKETS), - sshrawlogname, 'r', I(LGTYP_SSHRAW), - NULL); + sshrawlogname, 'r', I(LGTYP_SSHRAW)); } ctrl_filesel(s, "Log file name:", 'f', NULL, true, "Select session log file name", @@ -1989,7 +1988,7 @@ void setup_config_box(struct controlbox *b, bool midsession, conf_radiobutton_handler, I(CONF_logxfovr), "Always overwrite it", I(LGXF_OVR), "Always append to the end of it", I(LGXF_APN), - "Ask the user every time", I(LGXF_ASK), NULL); + "Ask the user every time", I(LGXF_ASK)); ctrl_checkbox(s, "Flush log file frequently", 'u', HELPCTX(logging_flush), conf_checkbox_handler, I(CONF_logflush)); @@ -2043,13 +2042,13 @@ void setup_config_box(struct controlbox *b, bool midsession, conf_radiobutton_handler,I(CONF_localecho), "Auto", I(AUTO), "Force on", I(FORCE_ON), - "Force off", I(FORCE_OFF), NULL); + "Force off", I(FORCE_OFF)); ctrl_radiobuttons(s, "Local line editing:", 't', 3, HELPCTX(terminal_localedit), conf_radiobutton_handler,I(CONF_localedit), "Auto", I(AUTO), "Force on", I(FORCE_ON), - "Force off", I(FORCE_OFF), NULL); + "Force off", I(FORCE_OFF)); s = ctrl_getset(b, "Terminal", "printing", "Remote-controlled printing"); ctrl_combobox(s, "Printer to send ANSI printer output to:", 'p', 100, @@ -2068,12 +2067,12 @@ void setup_config_box(struct controlbox *b, bool midsession, HELPCTX(keyboard_backspace), conf_radiobutton_bool_handler, I(CONF_bksp_is_delete), - "Control-H", I(0), "Control-? (127)", I(1), NULL); + "Control-H", I(0), "Control-? (127)", I(1)); ctrl_radiobuttons(s, "The Home and End keys", 'e', 2, HELPCTX(keyboard_homeend), conf_radiobutton_bool_handler, I(CONF_rxvt_homeend), - "Standard", I(false), "rxvt", I(true), NULL); + "Standard", I(false), "rxvt", I(true)); ctrl_radiobuttons(s, "The Function keys and keypad", 'f', 4, HELPCTX(keyboard_funkeys), conf_radiobutton_handler, @@ -2084,14 +2083,13 @@ void setup_config_box(struct controlbox *b, bool midsession, "VT400", I(FUNKY_VT400), "VT100+", I(FUNKY_VT100P), "SCO", I(FUNKY_SCO), - "Xterm 216+", I(FUNKY_XTERM_216), - NULL); + "Xterm 216+", I(FUNKY_XTERM_216)); ctrl_radiobuttons(s, "Shift/Ctrl/Alt with the arrow keys", 'w', 2, HELPCTX(keyboard_sharrow), conf_radiobutton_handler, I(CONF_sharrow_type), "Ctrl toggles app mode", I(SHARROW_APPLICATION), - "xterm-style bitmap", I(SHARROW_BITMAP), NULL); + "xterm-style bitmap", I(SHARROW_BITMAP)); s = ctrl_getset(b, "Terminal/Keyboard", "appkeypad", "Application keypad settings:"); @@ -2099,12 +2097,11 @@ void setup_config_box(struct controlbox *b, bool midsession, HELPCTX(keyboard_appcursor), conf_radiobutton_bool_handler, I(CONF_app_cursor), - "Normal", I(0), "Application", I(1), NULL); + "Normal", I(0), "Application", I(1)); 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); + "Normal", I(0), "Application", I(1), "NetHack", I(2)); /* * The Terminal/Bell panel. @@ -2118,7 +2115,7 @@ void setup_config_box(struct controlbox *b, bool midsession, conf_radiobutton_handler, I(CONF_beep), "None (bell disabled)", I(BELL_DISABLED), "Make default system alert sound", I(BELL_DEFAULT), - "Visual bell (flash window)", I(BELL_VISUAL), NULL); + "Visual bell (flash window)", I(BELL_VISUAL)); s = ctrl_getset(b, "Terminal/Bell", "overload", "Control the bell overload behaviour"); @@ -2172,7 +2169,7 @@ void setup_config_box(struct controlbox *b, bool midsession, I(CONF_remote_qtitle_action), "None", I(TITLE_NONE), "Empty string", I(TITLE_EMPTY), - "Window title", I(TITLE_REAL), NULL); + "Window title", I(TITLE_REAL)); ctrl_checkbox(s, "Disable remote-controlled clearing of scrollback", 'e', HELPCTX(features_clearscroll), conf_checkbox_handler, @@ -2249,7 +2246,7 @@ void setup_config_box(struct controlbox *b, bool midsession, I(CONF_cursor_type), "Block", 'l', I(0), "Underline", 'u', I(1), - "Vertical line", 'v', I(2), NULL); + "Vertical line", 'v', I(2)); ctrl_checkbox(s, "Cursor blinks", 'b', HELPCTX(appearance_cursor), conf_checkbox_handler, I(CONF_blink_cur)); @@ -2315,13 +2312,12 @@ void setup_config_box(struct controlbox *b, bool midsession, 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), - conf_radiobutton_handler, - I(CONF_vtmode), - "Use Unicode line drawing code points",'u',I(VT_UNICODE), - "Poor man's line drawing (+, - and |)",'p',I(VT_POORMAN), - NULL); + ctrl_radiobuttons( + s, "Handling of line drawing characters:", NO_SHORTCUT,1, + HELPCTX(translation_linedraw), + conf_radiobutton_handler, I(CONF_vtmode), + "Use Unicode line drawing code points",'u',I(VT_UNICODE), + "Poor man's line drawing (+, - and |)",'p',I(VT_POORMAN)); ctrl_checkbox(s, "Copy and paste line drawing characters as lqqqk",'d', HELPCTX(selection_linedraw), conf_checkbox_handler, I(CONF_rawcnp)); @@ -2346,7 +2342,7 @@ void setup_config_box(struct controlbox *b, bool midsession, conf_radiobutton_bool_handler, I(CONF_rect_select), "Normal", 'n', I(false), - "Rectangular block", 'r', I(true), NULL); + "Rectangular block", 'r', I(true)); s = ctrl_getset(b, "Window/Selection", "clipboards", "Assign copy/paste actions to clipboards"); @@ -2422,8 +2418,7 @@ void setup_config_box(struct controlbox *b, bool midsession, conf_radiobutton_handler, I(CONF_bold_style), "The font", I(1), "The colour", I(2), - "Both", I(3), - NULL); + "Both", I(3)); str = dupprintf("Adjust the precise colours %s displays", appname); s = ctrl_getset(b, "Window/Colours", "adjust", str); @@ -2488,8 +2483,7 @@ void setup_config_box(struct controlbox *b, bool midsession, I(CONF_addressfamily), "Auto", 'u', I(ADDRTYPE_UNSPEC), "IPv4", '4', I(ADDRTYPE_IPV4), - "IPv6", '6', I(ADDRTYPE_IPV6), - NULL); + "IPv6", '6', I(ADDRTYPE_IPV6)); #endif { @@ -2528,8 +2522,7 @@ void setup_config_box(struct controlbox *b, bool midsession, conf_radiobutton_bool_handler, I(CONF_username_from_env), "Prompt", I(false), - userlabel, I(true), - NULL); + userlabel, I(true)); sfree(userlabel); } @@ -2613,7 +2606,7 @@ void setup_config_box(struct controlbox *b, bool midsession, I(CONF_proxy_dns), "No", I(FORCE_OFF), "Auto", I(AUTO), - "Yes", I(FORCE_ON), NULL); + "Yes", I(FORCE_ON)); ctrl_editbox(s, "Username", 'u', 60, HELPCTX(proxy_auth), conf_editbox_handler, @@ -2635,7 +2628,7 @@ void setup_config_box(struct controlbox *b, bool midsession, I(CONF_proxy_log_to_term), "No", I(FORCE_OFF), "Yes", I(FORCE_ON), - "Only until session starts", I(AUTO), NULL); + "Only until session starts", I(AUTO)); } /* @@ -2722,7 +2715,7 @@ void setup_config_box(struct controlbox *b, bool midsession, conf_radiobutton_handler, I(CONF_sshprot), "2", '2', I(3), - "1 (INSECURE)", '1', I(0), NULL); + "1 (INSECURE)", '1', I(0)); } /* @@ -3022,8 +3015,7 @@ void setup_config_box(struct controlbox *b, bool midsession, ttymodes_handler, P(td), "Auto", NO_SHORTCUT, P(NULL), "Nothing", NO_SHORTCUT, P(NULL), - "This:", NO_SHORTCUT, P(NULL), - NULL); + "This:", NO_SHORTCUT, P(NULL)); td->valradio->column = 0; td->valbox = ctrl_editbox(s, NULL, NO_SHORTCUT, 100, HELPCTX(ssh_ttymodes), @@ -3052,7 +3044,7 @@ void setup_config_box(struct controlbox *b, bool midsession, conf_radiobutton_handler, I(CONF_x11_auth), "MIT-Magic-Cookie-1", I(X11_MIT), - "XDM-Authorization-1", I(X11_XDM), NULL); + "XDM-Authorization-1", I(X11_XDM)); } /* @@ -3112,8 +3104,7 @@ void setup_config_box(struct controlbox *b, bool midsession, portfwd_handler, P(pfd), "Local", 'l', P(NULL), "Remote", 'm', P(NULL), - "Dynamic", 'y', P(NULL), - NULL); + "Dynamic", 'y', P(NULL)); #ifndef NO_IPV6 pfd->addressfamily = ctrl_radiobuttons(s, NULL, NO_SHORTCUT, 3, @@ -3121,8 +3112,7 @@ void setup_config_box(struct controlbox *b, bool midsession, portfwd_handler, P(pfd), "Auto", 'u', I(ADDRTYPE_UNSPEC), "IPv4", '4', I(ADDRTYPE_IPV4), - "IPv6", '6', I(ADDRTYPE_IPV6), - NULL); + "IPv6", '6', I(ADDRTYPE_IPV6)); #endif ctrl_tabdelay(s, pfd->addbutton); ctrl_columns(s, 1, 100); @@ -3251,12 +3241,12 @@ void setup_config_box(struct controlbox *b, bool midsession, conf_radiobutton_bool_handler, I(CONF_rfc_environ), "BSD (commonplace)", 'b', I(false), - "RFC 1408 (unusual)", 'f', I(true), NULL); + "RFC 1408 (unusual)", 'f', I(true)); ctrl_radiobuttons(s, "Telnet negotiation mode:", 't', 2, HELPCTX(telnet_passive), conf_radiobutton_bool_handler, I(CONF_passive_telnet), - "Passive", I(true), "Active", I(false), NULL); + "Passive", I(true), "Active", I(false)); } ctrl_checkbox(s, "Keyboard sends Telnet special commands", 'k', HELPCTX(telnet_specialkeys), @@ -3303,7 +3293,7 @@ void setup_config_box(struct controlbox *b, bool midsession, I(CONF_supdup_ascii_set), "None", I(SUPDUP_CHARSET_ASCII), "ITS", I(SUPDUP_CHARSET_ITS), - "WAITS", I(SUPDUP_CHARSET_WAITS), NULL); + "WAITS", I(SUPDUP_CHARSET_WAITS)); ctrl_checkbox(s, "**MORE** processing", 'm', HELPCTX(supdup_more), diff --git a/dialog.c b/dialog.c index 04bc0d3c..ce2d0eb2 100644 --- a/dialog.c +++ b/dialog.c @@ -282,9 +282,9 @@ dlgcontrol *ctrl_combobox(struct controlset *s, const char *label, * title is expected to be followed by a shortcut _iff_ `shortcut' * is NO_SHORTCUT. */ -dlgcontrol *ctrl_radiobuttons(struct controlset *s, const char *label, - char shortcut, int ncolumns, intorptr helpctx, - handler_fn handler, intorptr context, ...) +dlgcontrol *ctrl_radiobuttons_fn(struct controlset *s, const char *label, + char shortcut, int ncolumns, intorptr helpctx, + handler_fn handler, intorptr context, ...) { va_list ap; int i; diff --git a/dialog.h b/dialog.h index cc483328..90b995c1 100644 --- a/dialog.h +++ b/dialog.h @@ -532,9 +532,11 @@ dlgcontrol *ctrl_combobox(struct controlset *, const char *label, * title is expected to be followed by a shortcut _iff_ `shortcut' * is NO_SHORTCUT. */ -dlgcontrol *ctrl_radiobuttons(struct controlset *, const char *label, - char shortcut, int ncolumns, intorptr helpctx, - handler_fn handler, intorptr context, ...); +dlgcontrol *ctrl_radiobuttons_fn(struct controlset *, const char *label, + char shortcut, int ncolumns, intorptr helpctx, + handler_fn handler, intorptr context, ...); +#define ctrl_radiobuttons(...) \ + ctrl_radiobuttons_fn(__VA_ARGS__, (const char *)NULL) dlgcontrol *ctrl_pushbutton(struct controlset *, const char *label, char shortcut, intorptr helpctx, handler_fn handler, intorptr context); diff --git a/windows/config.c b/windows/config.c index d8915728..fc9070bf 100644 --- a/windows/config.c +++ b/windows/config.c @@ -173,7 +173,7 @@ void win_setup_config_box(struct controlbox *b, HWND *hwndp, bool has_help, I(CONF_beep_ind), "Disabled", I(B_IND_DISABLED), "Flashing", I(B_IND_FLASH), - "Steady", I(B_IND_STEADY), NULL); + "Steady", I(B_IND_STEADY)); /* * The sunken-edge border is a Windows GUI feature. @@ -198,7 +198,7 @@ void win_setup_config_box(struct controlbox *b, HWND *hwndp, bool has_help, "Antialiased", I(FQ_ANTIALIASED), "Non-Antialiased", I(FQ_NONANTIALIASED), "ClearType", I(FQ_CLEARTYPE), - "Default", I(FQ_DEFAULT), NULL); + "Default", I(FQ_DEFAULT)); /* * Cyrillic Lock is a horrid misfeature even on Windows, and @@ -289,7 +289,7 @@ void win_setup_config_box(struct controlbox *b, HWND *hwndp, bool has_help, I(CONF_mouse_is_xterm), "Windows (Middle extends, Right brings up menu)", I(2), "Compromise (Middle extends, Right pastes)", I(0), - "xterm (Right extends, Middle pastes)", I(1), NULL); + "xterm (Right extends, Middle pastes)", I(1)); /* * 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 @@ -328,7 +328,7 @@ void win_setup_config_box(struct controlbox *b, HWND *hwndp, bool has_help, "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); + "Forbid resizing completely", I(RESIZE_DISABLED)); } /* -- cgit v1.2.3 From 0d4af8e1c4889ab7bf1cd00e91bb28987a7d1d05 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 11 Jun 2022 12:46:29 +0100 Subject: Pageant: fix concurrent attempts to use an encrypted key. If you had a key stored encrypted in Pageant, and you launched two PuTTY sessions both trying to generate a signature with the key, and then didn't type the passphrase into the async passphrase prompt until after both sessions had made requests, then only one of the requests would be replied to once you decrypted the key. The other would sit there waiting for a response that Pageant never got round to sending. This would also happen in the case where you launched two PuTTY sessions each trying to use a _different_ encrypted key in Pageant, in which case Pageant should have presented the next GUI passphrase box after the first one was dismissed. That didn't happen either. This was the result of two separate bugs in the code. Firstly, when signop_coroutine() noticed that a GUI request was already in progress, it would crReturn without making any arrangement to be called back. Now there's a queue of 'requests blocked on waiting for some GUI prompt to finish'. Secondly, if multiple requests were blocked on the same key, the code that went over the linked list of them had a bug in which it would unblock the _first_ list item in every iteration, instead of unblocking all the items once each. --- pageant.c | 39 ++++++++++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/pageant.c b/pageant.c index 27d6ee91..72e61f8c 100644 --- a/pageant.c +++ b/pageant.c @@ -124,6 +124,8 @@ struct PageantSignOp { /* Master lock that indicates whether a GUI request is currently in * progress */ static bool gui_request_in_progress = false; +static PageantKeyRequestNode requests_blocked_on_gui = + { &requests_blocked_on_gui, &requests_blocked_on_gui }; static void failure(PageantClient *pc, PageantClientRequestId *reqid, strbuf *sb, unsigned char type, const char *fmt, ...); @@ -359,7 +361,7 @@ static PRINTF_LIKE(5, 6) void failure( } } -static void signop_link(PageantSignOp *so) +static void signop_link_to_key(PageantSignOp *so) { assert(!so->pkr.prev); assert(!so->pkr.next); @@ -370,12 +372,24 @@ static void signop_link(PageantSignOp *so) so->pkr.next->prev = &so->pkr; } +static void signop_link_to_pending_gui_request(PageantSignOp *so) +{ + assert(!so->pkr.prev); + assert(!so->pkr.next); + + so->pkr.prev = requests_blocked_on_gui.prev; + so->pkr.next = &requests_blocked_on_gui; + so->pkr.prev->next = &so->pkr; + so->pkr.next->prev = &so->pkr; +} + static void signop_unlink(PageantSignOp *so) { if (so->pkr.next) { assert(so->pkr.prev); so->pkr.next->prev = so->pkr.prev; so->pkr.prev->next = so->pkr.next; + so->pkr.prev = so->pkr.next = NULL; } else { assert(!so->pkr.prev); } @@ -413,8 +427,11 @@ static void signop_coroutine(PageantAsyncOp *pao) crBegin(so->crLine); - while (!so->pk->skey && gui_request_in_progress) + while (!so->pk->skey && gui_request_in_progress) { + signop_link_to_pending_gui_request(so); crReturnV; + signop_unlink(so); + } if (!so->pk->skey) { assert(so->pk->encrypted_key_file); @@ -427,7 +444,7 @@ static void signop_coroutine(PageantAsyncOp *pao) goto respond; } - signop_link(so); + signop_link_to_key(so); crReturnV; signop_unlink(so); } @@ -496,8 +513,16 @@ static void unblock_requests_for_key(PageantKey *pk) { for (PageantKeyRequestNode *pkr = pk->blocked_requests.next; pkr != &pk->blocked_requests; pkr = pkr->next) { - PageantSignOp *so = container_of(pk->blocked_requests.next, - PageantSignOp, pkr); + PageantSignOp *so = container_of(pkr, PageantSignOp, pkr); + queue_toplevel_callback(pageant_async_op_callback, &so->pao); + } +} + +static void unblock_pending_gui_requests(void) +{ + for (PageantKeyRequestNode *pkr = requests_blocked_on_gui.next; + pkr != &requests_blocked_on_gui; pkr = pkr->next) { + PageantSignOp *so = container_of(pkr, PageantSignOp, pkr); queue_toplevel_callback(pageant_async_op_callback, &so->pao); } } @@ -557,6 +582,8 @@ void pageant_passphrase_request_success(PageantClientDialogId *dlgid, } unblock_requests_for_key(pk); + + unblock_pending_gui_requests(); } void pageant_passphrase_request_refused(PageantClientDialogId *dlgid) @@ -568,6 +595,8 @@ void pageant_passphrase_request_refused(PageantClientDialogId *dlgid) pk->decryption_prompt_active = false; fail_requests_for_key(pk, "user refused to supply passphrase"); + + unblock_pending_gui_requests(); } typedef struct PageantImmOp PageantImmOp; -- cgit v1.2.3 From bc61acc53da1e5d780f4840aeef66d79e2044207 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 11 Jun 2022 13:12:33 +0100 Subject: NTRU: fix copy-paste error in comment. The polynomial Stein's algorithm in that code was adapted from the binary Stein in mpint.c. One of the comments which originally said 'dividing by 2' should have been updated to say 'dividing by x' in the polynomial case, and didn't. --- crypto/ntru.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crypto/ntru.c b/crypto/ntru.c index c8f6dfeb..f1ba118f 100644 --- a/crypto/ntru.c +++ b/crypto/ntru.c @@ -303,7 +303,7 @@ unsigned ntru_ring_invert(uint16_t *out, const uint16_t *in, /* Run the gcd-finding algorithm. */ for (size_t i = 0; i < STEPS; i++) { /* - * First swap round so that A is the one we'll be dividing by 2. + * First swap round so that A is the one we'll be dividing by x. * * In the case where one of the two polys has a zero constant * term, it's that one. In the other case, it's the one of -- cgit v1.2.3 From 3bef6b63f0803623b649d151a142c4a137895b63 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 23 Jun 2022 18:44:07 +0100 Subject: Reindent unix/dialog.c. Large chunks of the GTK setup code had a 2-space indent for some reason, in place of the usual 4-space in this code base. I've been meaning to sort it out for ages, because it makes it hard to have a single set of editor settings suitable for the whole code base. --- unix/dialog.c | 208 +++++++++++++++++++++++++++++----------------------------- 1 file changed, 104 insertions(+), 104 deletions(-) diff --git a/unix/dialog.c b/unix/dialog.c index 35c912e7..c30c79e8 100644 --- a/unix/dialog.c +++ b/unix/dialog.c @@ -313,7 +313,7 @@ void dlg_editbox_set(dlgcontrol *ctrl, dlgparam *dp, char const *text) entry = gtk_bin_get_child(GTK_BIN(uc->combo)); else #endif - entry = uc->entry; + entry = uc->entry; assert(entry != NULL); @@ -586,7 +586,7 @@ void dlg_listbox_addwithid(dlgcontrol *ctrl, dlgparam *dp, } #endif unreachable("bad control type in listbox_addwithid"); - done: + done: dp->flags &= ~FLAG_UPDATING_COMBO_LIST; } @@ -985,7 +985,7 @@ void dlg_set_focus(dlgcontrol *ctrl, dlgparam *dp) for (int 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]); + gtk_widget_grab_focus(uc->buttons[i]); } break; case CTRL_LISTBOX: @@ -1098,7 +1098,7 @@ void dlg_refresh(dlgcontrol *ctrl, dlgparam *dp) assert(uc->ctrl != NULL); if (uc->ctrl->handler != NULL) uc->ctrl->handler(uc->ctrl, dp, - dp->data, EVENT_REFRESH); + dp->data, EVENT_REFRESH); } } } @@ -1171,9 +1171,9 @@ void dlg_coloursel_start(dlgcontrol *ctrl, dlgparam *dp, int r, int g, int b) cancelbutton = ccs->cancel_button; #endif g_object_set_data(G_OBJECT(okbutton), "user-data", - (gpointer)coloursel); + (gpointer)coloursel); g_object_set_data(G_OBJECT(cancelbutton), "user-data", - (gpointer)coloursel); + (gpointer)coloursel); g_signal_connect(G_OBJECT(okbutton), "clicked", G_CALLBACK(coloursel_ok), (gpointer)dp); g_signal_connect(G_OBJECT(cancelbutton), "clicked", @@ -1317,7 +1317,7 @@ static gboolean listitem_key(GtkWidget *item, GdkEventKey *event, if (!multiple && GTK_WIDGET_STATE(item) != GTK_STATE_SELECTED) { - gtk_list_select_child(GTK_LIST(list), item); + gtk_list_select_child(GTK_LIST(list), item); } else { int direction = (event->keyval==GDK_Up || event->keyval==GDK_KP_Up || @@ -1397,10 +1397,10 @@ static gboolean listitem_button_press(GtkWidget *item, GdkEventButton *event, 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; + 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; } @@ -1500,7 +1500,7 @@ static gboolean draglist_valchange(gpointer data) (struct draglist_valchange_ctx *)data; ctx->uc->ctrl->handler(ctx->uc->ctrl, ctx->dp, - ctx->dp->data, EVENT_VALCHANGE); + ctx->dp->data, EVENT_VALCHANGE); sfree(ctx); @@ -1781,7 +1781,7 @@ static void filefont_clicked(GtkButton *button, gpointer data) char *name = XGetAtomName(disp, (Atom)ret); if (name) gtk_font_selection_dialog_set_font_name - (GTK_FONT_SELECTION_DIALOG(fontsel), name); + (GTK_FONT_SELECTION_DIALOG(fontsel), name); } gdk_font_unref(font); } @@ -1972,19 +1972,19 @@ GtkWidget *layout_ctrls( w = columns_new(0); if (ctrl->label) { - GtkWidget *label = gtk_label_new(ctrl->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; + GtkWidget *label = gtk_label_new(ctrl->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); + percentages[i] = + ((100 * (i+1) / ctrl->radio.ncolumns) - + 100 * i / ctrl->radio.ncolumns); } columns_set_cols(COLUMNS(w), ctrl->radio.ncolumns, percentages); @@ -1995,28 +1995,28 @@ GtkWidget *layout_ctrls( 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_get_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); - g_signal_connect(G_OBJECT(b), "toggled", - G_CALLBACK(button_toggled), dp); - g_signal_connect(G_OBJECT(b), "focus_in_event", - G_CALLBACK(widget_focus), dp); - if (ctrl->radio.shortcuts) { - shortcut_add(scs, gtk_bin_get_child(GTK_BIN(b)), - ctrl->radio.shortcuts[i], - SHORTCUT_UCTRL, uc); - } + GtkWidget *b; + gint colstart; + + b = (gtk_radio_button_new_with_label + (group, ctrl->radio.buttons[i])); + uc->buttons[i] = b; + group = gtk_radio_button_get_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); + g_signal_connect(G_OBJECT(b), "toggled", + G_CALLBACK(button_toggled), dp); + g_signal_connect(G_OBJECT(b), "focus_in_event", + G_CALLBACK(widget_focus), dp); + if (ctrl->radio.shortcuts) { + shortcut_add(scs, gtk_bin_get_child(GTK_BIN(b)), + ctrl->radio.shortcuts[i], + SHORTCUT_UCTRL, uc); + } } break; } @@ -2025,35 +2025,35 @@ GtkWidget *layout_ctrls( 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; + /* + * 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_new_with_model_and_entry - (GTK_TREE_MODEL(uc->listmodel)); - g_object_set(G_OBJECT(w), "entry-text-column", 1, - (const char *)NULL); - /* We cannot support password combo boxes. */ - assert(!ctrl->editbox.password); - uc->combo = w; - signalobject = uc->combo; + /* + * GTK 2 combo box. + */ + uc->listmodel = gtk_list_store_new(2, G_TYPE_INT, + G_TYPE_STRING); + w = gtk_combo_box_new_with_model_and_entry + (GTK_TREE_MODEL(uc->listmodel)); + g_object_set(G_OBJECT(w), "entry-text-column", 1, + (const char *)NULL); + /* 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; + w = gtk_entry_new(); + if (ctrl->editbox.password) + gtk_entry_set_visibility(GTK_ENTRY(w), false); + uc->entry = w; + signalobject = w; } uc->entrysig = g_signal_connect(G_OBJECT(signalobject), "changed", @@ -2075,9 +2075,9 @@ GtkWidget *layout_ctrls( * from the column layout of the rest of the box. */ { - GtkRequisition req; - gtk_widget_size_request(w, &req); - gtk_widget_set_size_request(w, 10, req.height); + GtkRequisition req; + gtk_widget_size_request(w, &req); + gtk_widget_set_size_request(w, 10, req.height); } #else /* @@ -2089,34 +2089,34 @@ GtkWidget *layout_ctrls( #endif if (ctrl->label) { - GtkWidget *label; - - label = gtk_label_new(ctrl->label); - - shortcut_add(scs, label, ctrl->editbox.shortcut, - SHORTCUT_FOCUS, uc->entry); - - if (ctrl->editbox.percentwidth == 100) { - columns_add(cols, label, - COLUMN_START(ctrl->column), - COLUMN_SPAN(ctrl->column)); - columns_force_left_align(cols, label); - } else { - GtkWidget *container = columns_new(4); - 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); - columns_align_next_to(COLUMNS(container), label, w); - gtk_widget_show(w); - w = container; - } + GtkWidget *label; - gtk_widget_show(label); - uc->label = label; + label = gtk_label_new(ctrl->label); + + shortcut_add(scs, label, ctrl->editbox.shortcut, + SHORTCUT_FOCUS, uc->entry); + + if (ctrl->editbox.percentwidth == 100) { + columns_add(cols, label, + COLUMN_START(ctrl->column), + COLUMN_SPAN(ctrl->column)); + columns_force_left_align(cols, label); + } else { + GtkWidget *container = columns_new(4); + 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); + columns_align_next_to(COLUMNS(container), label, w); + gtk_widget_show(w); + w = container; + } + + gtk_widget_show(label); + uc->label = label; } break; } @@ -2312,9 +2312,9 @@ GtkWidget *layout_ctrls( { int edge; edge = GTK_WIDGET(uc->list)->style->klass->ythickness; - gtk_widget_set_size_request(w, 10, - 2*edge + (ctrl->listbox.height * - get_listitemheight(w))); + gtk_widget_set_size_request( + w, 10, 2*edge + (ctrl->listbox.height * + get_listitemheight(w))); } if (ctrl->listbox.draglist) { -- cgit v1.2.3 From e8a8c2535dc198b16569f405caa96f895fce6824 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 23 Jun 2022 19:04:43 +0100 Subject: GTK: remove 'entrysig' in struct uctrl. The only *use* of it was removed in commit 6a743399b03e3d6, where instead of blocking the GTK signal that caused a string to be overwritten, I switched to making a temporary copy of the string. But I didn't notice that the declaration and assignments could be cleaned up too. --- unix/dialog.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/unix/dialog.c b/unix/dialog.c index c30c79e8..eda622f1 100644 --- a/unix/dialog.c +++ b/unix/dialog.c @@ -71,7 +71,6 @@ struct uctrl { GtkWidget *label; /* for dlg_label_change */ GtkAdjustment *adj; /* for the scrollbar in a list box */ struct selparam *sp; /* which switchable pane of the box we're in */ - guint entrysig; guint textsig; int nclicks; const char *textvalue; /* temporary, for button-only file selectors */ @@ -2055,9 +2054,8 @@ GtkWidget *layout_ctrls( uc->entry = w; signalobject = w; } - uc->entrysig = - g_signal_connect(G_OBJECT(signalobject), "changed", - G_CALLBACK(editbox_changed), dp); + g_signal_connect(G_OBJECT(signalobject), "changed", + G_CALLBACK(editbox_changed), dp); g_signal_connect(G_OBJECT(signalobject), "key_press_event", G_CALLBACK(editbox_key), dp); g_signal_connect(G_OBJECT(signalobject), "focus_in_event", @@ -2167,9 +2165,8 @@ GtkWidget *layout_ctrls( g_signal_connect(G_OBJECT(uc->entry), "key_press_event", G_CALLBACK(editbox_key), dp); - uc->entrysig = - g_signal_connect(G_OBJECT(uc->entry), "changed", - G_CALLBACK(editbox_changed), dp); + g_signal_connect(G_OBJECT(uc->entry), "changed", + G_CALLBACK(editbox_changed), dp); g_signal_connect(G_OBJECT(uc->entry), "focus_in_event", G_CALLBACK(widget_focus), dp); } else { -- cgit v1.2.3 From 1a568e3535a19b75647a7b9e4ac2192b5e9be536 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 25 Jun 2022 13:00:06 +0100 Subject: New function dlg_editbox_select_range. This manipulates the selection inside an edit box, to select a specific range of characters in the contained text. The idea is that you can use it as a means of error highlighting, if the user has entered something invalid in that edit box and you want to draw their attention to the specific location of the part you're unhappy with. --- dialog.h | 2 ++ unix/dialog.c | 21 +++++++++++++++++++++ windows/controls.c | 8 ++++++++ 3 files changed, 31 insertions(+) diff --git a/dialog.h b/dialog.h index 90b995c1..fad78d16 100644 --- a/dialog.h +++ b/dialog.h @@ -573,6 +573,8 @@ void dlg_checkbox_set(dlgcontrol *ctrl, dlgparam *dp, bool checked); bool dlg_checkbox_get(dlgcontrol *ctrl, dlgparam *dp); void dlg_editbox_set(dlgcontrol *ctrl, dlgparam *dp, char const *text); char *dlg_editbox_get(dlgcontrol *ctrl, dlgparam *dp); /* result must be freed by caller */ +void dlg_editbox_select_range(dlgcontrol *ctrl, dlgparam *dp, + size_t start, size_t len); /* The `listbox' functions can also apply to combo boxes. */ void dlg_listbox_clear(dlgcontrol *ctrl, dlgparam *dp); void dlg_listbox_del(dlgcontrol *ctrl, dlgparam *dp, int index); diff --git a/unix/dialog.c b/unix/dialog.c index eda622f1..161f393d 100644 --- a/unix/dialog.c +++ b/unix/dialog.c @@ -357,6 +357,27 @@ char *dlg_editbox_get(dlgcontrol *ctrl, dlgparam *dp) unreachable("bad control type in editbox_get"); } +void dlg_editbox_select_range(dlgcontrol *ctrl, dlgparam *dp, + size_t start, size_t len) +{ + struct uctrl *uc = dlg_find_byctrl(dp, ctrl); + assert(uc->ctrl->type == CTRL_EDITBOX); + + GtkWidget *entry = NULL; + +#if GTK_CHECK_VERSION(2,4,0) + if (uc->combo) + entry = gtk_bin_get_child(GTK_BIN(uc->combo)); +#endif + + if (uc->entry) + entry = uc->entry; + + assert(entry && "we should have a GtkEntry one way or another"); + + gtk_editable_select_region(GTK_EDITABLE(entry), start, start + len); +} + #if !GTK_CHECK_VERSION(2,4,0) static void container_remove_and_destroy(GtkWidget *w, gpointer data) { diff --git a/windows/controls.c b/windows/controls.c index c0159510..7065b7be 100644 --- a/windows/controls.c +++ b/windows/controls.c @@ -2217,6 +2217,14 @@ char *dlg_editbox_get(dlgcontrol *ctrl, dlgparam *dp) return GetDlgItemText_alloc(dp->hwnd, c->base_id+1); } +void dlg_editbox_select_range(dlgcontrol *ctrl, dlgparam *dp, + size_t start, size_t len) +{ + struct winctrl *c = dlg_findbyctrl(dp, ctrl); + assert(c && c->ctrl->type == CTRL_EDITBOX); + SendDlgItemMessage(dp->hwnd, c->base_id+1, EM_SETSEL, start, start+len); +} + /* The `listbox' functions can also apply to combo boxes. */ void dlg_listbox_clear(dlgcontrol *ctrl, dlgparam *dp) { -- cgit v1.2.3 From 76205b89e2a7889eb557bb4d86761e21bf04606c Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 12 Jun 2022 11:27:27 +0100 Subject: A few more ptrlen functions. ptrlen_contains and ptrlen_contains_only are useful for checking that a string is composed entirely of certain characters, or avoids them. ptrlen_end makes a pointer to the byte just past the end of the specified string. And it can be used with make_ptrlen_startend, which makes a ptrlen out of two pointers instead of a pointer and a length. --- misc.h | 17 +++++++++++++++++ utils/ptrlen.c | 16 ++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/misc.h b/misc.h index b4b9bd57..afde1502 100644 --- a/misc.h +++ b/misc.h @@ -169,6 +169,21 @@ static inline ptrlen make_ptrlen(const void *ptr, size_t len) return pl; } +static inline const void *ptrlen_end(ptrlen pl) +{ + return (const char *)pl.ptr + pl.len; +} + +static inline ptrlen make_ptrlen_startend(const void *startv, const void *endv) +{ + const char *start = (const char *)startv, *end = (const char *)endv; + assert(end >= start); + ptrlen pl; + pl.ptr = start; + pl.len = end - start; + return pl; +} + static inline ptrlen ptrlen_from_asciz(const char *str) { return make_ptrlen(str, strlen(str)); @@ -190,6 +205,8 @@ int ptrlen_strcmp(ptrlen pl1, ptrlen pl2); bool ptrlen_startswith(ptrlen whole, ptrlen prefix, ptrlen *tail); bool ptrlen_endswith(ptrlen whole, ptrlen suffix, ptrlen *tail); ptrlen ptrlen_get_word(ptrlen *input, const char *separators); +bool ptrlen_contains(ptrlen input, const char *characters); +bool ptrlen_contains_only(ptrlen input, const char *characters); char *mkstr(ptrlen pl); int string_length_for_printf(size_t); /* Derive two printf arguments from a ptrlen, suitable for "%.*s" */ diff --git a/utils/ptrlen.c b/utils/ptrlen.c index 7d4e12ec..37972e49 100644 --- a/utils/ptrlen.c +++ b/utils/ptrlen.c @@ -54,6 +54,22 @@ bool ptrlen_endswith(ptrlen whole, ptrlen suffix, ptrlen *tail) return false; } +bool ptrlen_contains(ptrlen input, const char *characters) +{ + for (const char *p = input.ptr, *end = p + input.len; p < end; p++) + if (strchr(characters, *p)) + return true; + return false; +} + +bool ptrlen_contains_only(ptrlen input, const char *characters) +{ + for (const char *p = input.ptr, *end = p + input.len; p < end; p++) + if (!strchr(characters, *p)) + return false; + return true; +} + ptrlen ptrlen_get_word(ptrlen *input, const char *separators) { const char *p = input->ptr, *end = p + input->len; -- cgit v1.2.3 From 08d58fe13e84a929c72c5d4aa98001279463a79f Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 19 Jun 2022 10:36:50 +0100 Subject: Routines for %-encoding and %-decoding. These make a good storage format for mostly-textual data in configuration, if it can't afford to reserve any character as a delimiter. Assuming very few characters need to be escaped, the space cost is lower than base64, and also you can read it by eye. --- misc.h | 7 +++++++ utils/CMakeLists.txt | 2 ++ utils/percent_decode.c | 41 +++++++++++++++++++++++++++++++++++++++++ utils/percent_encode.c | 34 ++++++++++++++++++++++++++++++++++ 4 files changed, 84 insertions(+) create mode 100644 utils/percent_decode.c create mode 100644 utils/percent_encode.c diff --git a/misc.h b/misc.h index afde1502..9cf656fd 100644 --- a/misc.h +++ b/misc.h @@ -116,6 +116,13 @@ void base64_encode_fp(FILE *fp, ptrlen data, int cpl); strbuf *base64_encode_sb(ptrlen data, int cpl); bool base64_valid(ptrlen data); +void percent_encode_bs(BinarySink *bs, ptrlen data, const char *badchars); +void percent_encode_fp(FILE *fp, ptrlen data, const char *badchars); +strbuf *percent_encode_sb(ptrlen data, const char *badchars); +void percent_decode_bs(BinarySink *bs, ptrlen data); +void percent_decode_fp(FILE *fp, ptrlen data); +strbuf *percent_decode_sb(ptrlen data); + struct bufchain_granule; struct bufchain_tag { struct bufchain_granule *head, *tail; diff --git a/utils/CMakeLists.txt b/utils/CMakeLists.txt index 957b861c..56cc3c1b 100644 --- a/utils/CMakeLists.txt +++ b/utils/CMakeLists.txt @@ -46,6 +46,8 @@ add_sources_from_current_dir(utils nullstrcmp.c out_of_memory.c parse_blocksize.c + percent_decode.c + percent_encode.c prompts.c ptrlen.c read_file_into.c diff --git a/utils/percent_decode.c b/utils/percent_decode.c new file mode 100644 index 00000000..dff2c233 --- /dev/null +++ b/utils/percent_decode.c @@ -0,0 +1,41 @@ +/* + * Decode %-encoding in URL style. + */ + +#include + +#include "misc.h" + +void percent_decode_bs(BinarySink *bs, ptrlen data) +{ + for (const char *p = data.ptr, *e = ptrlen_end(data); p < e; p++) { + char c = *p; + if (c == '%' && e-p >= 3 && + isxdigit((unsigned char)p[1]) && + isxdigit((unsigned char)p[2])) { + char hex[3]; + hex[0] = p[1]; + hex[1] = p[2]; + hex[2] = '\0'; + put_byte(bs, strtoul(hex, NULL, 16)); + p += 2; + } else { + put_byte(bs, c); + } + } + +} + +void percent_decode_fp(FILE *fp, ptrlen data) +{ + stdio_sink ss; + stdio_sink_init(&ss, fp); + percent_decode_bs(BinarySink_UPCAST(&ss), data); +} + +strbuf *percent_decode_sb(ptrlen data) +{ + strbuf *sb = strbuf_new(); + percent_decode_bs(BinarySink_UPCAST(sb), data); + return sb; +} diff --git a/utils/percent_encode.c b/utils/percent_encode.c new file mode 100644 index 00000000..ea04b0ac --- /dev/null +++ b/utils/percent_encode.c @@ -0,0 +1,34 @@ +/* + * %-encoding in URL style. + * + * Defaults to escaping % itself (necessary for decoding to even + * work), and any C0 escape character. Further bad characters can be + * provided in 'badchars'. + */ + +#include "misc.h" + +void percent_encode_bs(BinarySink *bs, ptrlen data, const char *badchars) +{ + for (const char *p = data.ptr, *e = ptrlen_end(data); p < e; p++) { + char c = *p; + if (c == '%' || c < ' ' || (badchars && strchr(badchars, c))) + put_fmt(bs, "%%%02X", (unsigned char)c); + else + put_byte(bs, c); + } +} + +void percent_encode_fp(FILE *fp, ptrlen data, const char *badchars) +{ + stdio_sink ss; + stdio_sink_init(&ss, fp); + percent_encode_bs(BinarySink_UPCAST(&ss), data, badchars); +} + +strbuf *percent_encode_sb(ptrlen data, const char *badchars) +{ + strbuf *sb = strbuf_new(); + percent_encode_bs(BinarySink_UPCAST(sb), data, badchars); + return sb; +} -- cgit v1.2.3 From f579b3c01e2bfd439d493be06b934404e13d3376 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 12 Jun 2022 10:04:26 +0100 Subject: Certificate trust scope: change to a boolean-expression system. This replaces the previous placeholder scheme of having a list of hostname wildcards with implicit logical-OR semantics (if any wildcard matched then the certificate would be trusted to sign for that host). That scheme didn't allow for exceptions within a domain ('everything in example.com except extra-high-security-machine.example.com'), and also had no way to specify port numbers. In the new system, you can still write a hostname wildcard by itself in the simple case, but now those are just atomic subexpressions in a boolean-logic domain-specific language I've made up. So if you want multiple wildcards, you can separate them with || in a single longer expression, and also you can use && and ! to impose exceptions on top of that. Full details of the expression language are in the comment at the top of utils/cert-expr.c. It'll need documenting properly before release, of course. For the sake of backwards compatibility for early adopters who've already set up configuration in the old system, I've put in some code that will read the old MatchHosts configuration and automatically translate it into the equivalent boolean expression (by simply stringing together the list of wildcards with || between them). --- CMakeLists.txt | 5 + defs.h | 2 + misc.h | 14 + ssh/ca-config.c | 138 ++----- ssh/transport2.c | 13 +- storage.h | 3 +- unix/storage.c | 24 +- utils/CMakeLists.txt | 1 + utils/cert-expr.c | 967 +++++++++++++++++++++++++++++++++++++++++++++++ utils/host_ca_new_free.c | 4 +- windows/storage.c | 26 +- 11 files changed, 1057 insertions(+), 140 deletions(-) create mode 100644 utils/cert-expr.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 0eb4cf1c..deba83f7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -94,6 +94,11 @@ add_executable(test_wildcard target_compile_definitions(test_wildcard PRIVATE TEST) target_link_libraries(test_wildcard utils ${platform_libraries}) +add_executable(test_cert_expr + utils/cert-expr.c) +target_compile_definitions(test_cert_expr PRIVATE TEST) +target_link_libraries(test_cert_expr utils ${platform_libraries}) + add_executable(bidi_gettype terminal/bidi_gettype.c) target_link_libraries(bidi_gettype guiterminal utils ${platform_libraries}) diff --git a/defs.h b/defs.h index 14e378ab..48cbaf23 100644 --- a/defs.h +++ b/defs.h @@ -143,6 +143,8 @@ typedef struct Channel Channel; typedef struct SshChannel SshChannel; typedef struct mainchan mainchan; +typedef struct CertExprBuilder CertExprBuilder; + typedef struct ssh_sharing_state ssh_sharing_state; typedef struct ssh_sharing_connstate ssh_sharing_connstate; typedef struct share_channel share_channel; diff --git a/misc.h b/misc.h index 9cf656fd..bdfa8c2e 100644 --- a/misc.h +++ b/misc.h @@ -507,4 +507,18 @@ static inline ptrlen ptrlen_from_lf(LoadedFile *lf) * is made to handle difficult overlap cases. */ void memxor(uint8_t *out, const uint8_t *in1, const uint8_t *in2, size_t size); +/* Boolean expressions used in OpenSSH certificate configuration */ +bool cert_expr_valid(const char *expression, + char **error_msg, ptrlen *error_loc); +bool cert_expr_match_str(const char *expression, + const char *hostname, unsigned port); +/* Build a certificate expression out of hostname wildcards. Required + * to handle legacy configuration from early in development, when + * multiple wildcards were stored separately in config, implicitly + * ORed together. */ +CertExprBuilder *cert_expr_builder_new(); +void cert_expr_builder_free(CertExprBuilder *eb); +void cert_expr_builder_add(CertExprBuilder *eb, const char *wildcard); +char *cert_expr_expression(CertExprBuilder *eb); + #endif diff --git a/ssh/ca-config.c b/ssh/ca-config.c index 6c84b68b..350613df 100644 --- a/ssh/ca-config.c +++ b/ssh/ca-config.c @@ -18,12 +18,10 @@ struct ca_state { dlgcontrol *ca_reclist; dlgcontrol *ca_pubkey_edit; dlgcontrol *ca_pubkey_info; - dlgcontrol *ca_wclist; - dlgcontrol *ca_wc_edit; + dlgcontrol *ca_validity_edit; dlgcontrol *rsa_type_checkboxes[NRSATYPES]; - char *name, *pubkey, *wc; + char *name, *pubkey, *validity; tree234 *ca_names; /* stores plain 'char *' */ - tree234 *host_wcs; /* stores plain 'char *' */ ca_options opts; strbuf *ca_pubkey_blob; }; @@ -45,10 +43,8 @@ static void ca_state_free(void *vctx) struct ca_state *st = (struct ca_state *)vctx; clear_string_tree(st->ca_names); freetree234(st->ca_names); - clear_string_tree(st->host_wcs); - freetree234(st->host_wcs); sfree(st->name); - sfree(st->wc); + sfree(st->validity); sfree(st); } @@ -86,13 +82,8 @@ static void set_from_hca(struct ca_state *st, host_ca *hca) else st->pubkey = dupstr(""); - clear_string_tree(st->host_wcs); - for (size_t i = 0; i < hca->n_hostname_wildcards; i++) { - char *name = dupstr(hca->hostname_wildcards[i]); - char *added = add234(st->host_wcs, name); - if (added != name) - sfree(name); /* de-duplicate, just in case */ - } + st->validity = dupstr(hca->validity_expression ? + hca->validity_expression : ""); st->opts = hca->opts; /* structure copy */ } @@ -194,7 +185,7 @@ static void ca_load_selected_record(struct ca_state *st, dlgparam *dp) dlg_refresh(st->ca_name_edit, dp); dlg_refresh(st->ca_pubkey_edit, dp); - dlg_refresh(st->ca_wclist, dp); + dlg_refresh(st->ca_validity_edit, dp); for (size_t i = 0; i < NRSATYPES; i++) dlg_refresh(st->rsa_type_checkboxes[i], dp); ca_refresh_pubkey_info(st, dp); @@ -259,8 +250,23 @@ static void ca_save_handler(dlgcontrol *ctrl, dlgparam *dp, { struct ca_state *st = (struct ca_state *)ctrl->context.p; if (event == EVENT_ACTION) { - if (!count234(st->host_wcs)) { - dlg_error_msg(dp, "No hostnames configured for this key"); + if (!*st->validity) { + dlg_error_msg(dp, "No validity expression configured " + "for this key"); + return; + } + + char *error_msg; + ptrlen error_loc; + if (!cert_expr_valid(st->validity, &error_msg, &error_loc)) { + char *error_full = dupprintf("Error in expression: %s", error_msg); + dlg_error_msg(dp, error_full); + dlg_set_focus(st->ca_validity_edit, dp); + dlg_editbox_select_range( + st->ca_validity_edit, dp, + (const char *)error_loc.ptr - st->validity, error_loc.len); + sfree(error_msg); + sfree(error_full); return; } @@ -274,10 +280,7 @@ static void ca_save_handler(dlgcontrol *ctrl, dlgparam *dp, hca->name = dupstr(st->name); hca->ca_public_key = strbuf_dup(ptrlen_from_strbuf( st->ca_pubkey_blob)); - hca->n_hostname_wildcards = count234(st->host_wcs); - hca->hostname_wildcards = snewn(hca->n_hostname_wildcards, char *); - for (size_t i = 0; i < hca->n_hostname_wildcards; i++) - hca->hostname_wildcards[i] = dupstr(index234(st->host_wcs, i)); + hca->validity_expression = dupstr(st->validity); hca->opts = st->opts; /* structure copy */ char *error = host_ca_save(hca); @@ -360,73 +363,15 @@ static void ca_pubkey_file_handler(dlgcontrol *ctrl, dlgparam *dp, } } -static void ca_wclist_handler(dlgcontrol *ctrl, dlgparam *dp, - void *data, int event) -{ - struct ca_state *st = (struct ca_state *)ctrl->context.p; - if (event == EVENT_REFRESH) { - dlg_update_start(ctrl, dp); - dlg_listbox_clear(ctrl, dp); - const char *name; - for (int i = 0; (name = index234(st->host_wcs, i)) != NULL; i++) - dlg_listbox_add(ctrl, dp, name); - dlg_update_done(ctrl, dp); - } -} - -static void ca_wc_edit_handler(dlgcontrol *ctrl, dlgparam *dp, - void *data, int event) +static void ca_validity_handler(dlgcontrol *ctrl, dlgparam *dp, + void *data, int event) { struct ca_state *st = (struct ca_state *)ctrl->context.p; if (event == EVENT_REFRESH) { - dlg_editbox_set(ctrl, dp, st->wc); + dlg_editbox_set(ctrl, dp, st->validity); } else if (event == EVENT_VALCHANGE) { - sfree(st->wc); - st->wc = dlg_editbox_get(ctrl, dp); - } -} - -static void ca_wc_add_handler(dlgcontrol *ctrl, dlgparam *dp, - void *data, int event) -{ - struct ca_state *st = (struct ca_state *)ctrl->context.p; - if (event == EVENT_ACTION) { - if (!st->wc) { - dlg_beep(dp); - return; - } - - if (add234(st->host_wcs, st->wc) == st->wc) { - dlg_refresh(st->ca_wclist, dp); - } else { - sfree(st->wc); - } - - st->wc = dupstr(""); - dlg_refresh(st->ca_wc_edit, dp); - } -} - -static void ca_wc_rem_handler(dlgcontrol *ctrl, dlgparam *dp, - void *data, int event) -{ - struct ca_state *st = (struct ca_state *)ctrl->context.p; - if (event == EVENT_ACTION) { - int i = dlg_listbox_index(st->ca_wclist, dp); - if (i < 0) { - dlg_beep(dp); - return; - } - char *wc = delpos234(st->host_wcs, i); - if (!wc) { - dlg_beep(dp); - return; - } - - sfree(st->wc); - st->wc = wc; - dlg_refresh(st->ca_wclist, dp); - dlg_refresh(st->ca_wc_edit, dp); + sfree(st->validity); + st->validity = dlg_editbox_get(ctrl, dp); } } @@ -454,8 +399,7 @@ void setup_ca_config_box(struct controlbox *b) b, sizeof(struct ca_state), ca_state_free); memset(st, 0, sizeof(*st)); st->ca_names = newtree234(ca_name_compare); - st->host_wcs = newtree234(ca_name_compare); - st->wc = dupstr(""); + st->validity = dupstr(""); ca_refresh_name_list(st); /* Initialise the settings to a default blank host_ca */ @@ -520,25 +464,9 @@ void setup_ca_config_box(struct controlbox *b) s = ctrl_getset(b, "Main", "options", "What this CA is trusted to do"); - c = ctrl_listbox(s, "Hostname patterns this key is trusted to certify", - NO_SHORTCUT, HELPCTX(no_help), ca_wclist_handler, P(st)); - c->listbox.height = 3; - st->ca_wclist = c; - - ctrl_columns(s, 3, 70, 15, 15); - c = ctrl_editbox(s, "Hostname pattern to add", 'h', 100, - HELPCTX(no_help), ca_wc_edit_handler, P(st), P(NULL)); - c->column = 0; - st->ca_wc_edit = c; - c = ctrl_pushbutton(s, "Add", NO_SHORTCUT, HELPCTX(no_help), - ca_wc_add_handler, P(st)); - c->align_next_to = st->ca_wc_edit; - c->column = 1; - c = ctrl_pushbutton(s, "Remove", NO_SHORTCUT, HELPCTX(no_help), - ca_wc_rem_handler, P(st)); - c->align_next_to = st->ca_wc_edit; - c->column = 2; - ctrl_columns(s, 1, 100); + c = ctrl_editbox(s, "Valid hosts this key is trusted to certify", 'h', 100, + HELPCTX(no_help), ca_validity_handler, P(st), P(NULL)); + st->ca_validity_edit = c; ctrl_columns(s, 4, 44, 18, 18, 18); c = ctrl_text(s, "Signature types (RSA keys only):", HELPCTX(no_help)); diff --git a/ssh/transport2.c b/ssh/transport2.c index 1d30f240..4383bb98 100644 --- a/ssh/transport2.c +++ b/ssh/transport2.c @@ -690,16 +690,9 @@ static void ssh2_write_kexinit_lists( if (!hca) continue; - bool match = false; - for (size_t i = 0, e = hca->n_hostname_wildcards; - i < e; i++) { - if (wc_match(hca->hostname_wildcards[i], hk_host)) { - match = true; - break; - } - } - - if (match && hca->ca_public_key) { + if (hca->ca_public_key && + cert_expr_match_str(hca->validity_expression, + hk_host, hk_port)) { accept_certs = true; add234(host_cas, hca); } else { diff --git a/storage.h b/storage.h index f581256e..e9138f40 100644 --- a/storage.h +++ b/storage.h @@ -103,8 +103,7 @@ void store_host_key(const char *hostname, int port, struct host_ca { char *name; strbuf *ca_public_key; - char **hostname_wildcards; - size_t n_hostname_wildcards; + char *validity_expression; ca_options opts; }; diff --git a/unix/storage.c b/unix/storage.c index b18c5166..83e1c19c 100644 --- a/unix/storage.c +++ b/unix/storage.c @@ -646,8 +646,8 @@ host_ca *host_ca_load(const char *name) host_ca *hca = host_ca_new(); hca->name = dupstr(name); - size_t wcsize = 0; char *line; + CertExprBuilder *eb = NULL; while ( (line = fgetline(fp)) ) { char *value = strchr(line, '='); @@ -662,10 +662,12 @@ host_ca *host_ca_load(const char *name) if (!strcmp(line, "PublicKey")) { hca->ca_public_key = base64_decode_sb(ptrlen_from_asciz(value)); } else if (!strcmp(line, "MatchHosts")) { - sgrowarray(hca->hostname_wildcards, wcsize, - hca->n_hostname_wildcards); - hca->hostname_wildcards[hca->n_hostname_wildcards++] = - dupstr(value); + if (!eb) + eb = cert_expr_builder_new(); + cert_expr_builder_add(eb, value); + } else if (!strcmp(line, "Validity")) { + hca->validity_expression = strbuf_to_str( + percent_decode_sb(ptrlen_from_asciz(value))); } else if (!strcmp(line, "PermitRSASHA1")) { hca->opts.permit_rsa_sha1 = atoi(value); } else if (!strcmp(line, "PermitRSASHA256")) { @@ -677,6 +679,13 @@ host_ca *host_ca_load(const char *name) sfree(line); } + if (eb) { + if (!hca->validity_expression) { + hca->validity_expression = cert_expr_expression(eb); + } + cert_expr_builder_free(eb); + } + return hca; } @@ -694,8 +703,9 @@ char *host_ca_save(host_ca *hca) base64_encode_fp(fp, ptrlen_from_strbuf(hca->ca_public_key), 0); fprintf(fp, "\n"); - for (size_t i = 0; i < hca->n_hostname_wildcards; i++) - fprintf(fp, "MatchHosts=%s\n", hca->hostname_wildcards[i]); + fprintf(fp, "Validity="); + percent_encode_fp(fp, ptrlen_from_asciz(hca->validity_expression), NULL); + fprintf(fp, "\n"); fprintf(fp, "PermitRSASHA1=%d\n", (int)hca->opts.permit_rsa_sha1); fprintf(fp, "PermitRSASHA256=%d\n", (int)hca->opts.permit_rsa_sha256); diff --git a/utils/CMakeLists.txt b/utils/CMakeLists.txt index 56cc3c1b..4f5479a6 100644 --- a/utils/CMakeLists.txt +++ b/utils/CMakeLists.txt @@ -9,6 +9,7 @@ add_sources_from_current_dir(utils bufchain.c buildinfo.c burnstr.c + cert-expr.c chomp.c cmdline_get_passwd_input_state_new.c conf.c diff --git a/utils/cert-expr.c b/utils/cert-expr.c new file mode 100644 index 00000000..8fe8df7c --- /dev/null +++ b/utils/cert-expr.c @@ -0,0 +1,967 @@ +/* + * Parser for the boolean expression language used to configure what + * host names an OpenSSH certificate will be trusted to sign for. + */ + +/* + +Language specification +====================== + +Outer lexical layer: the input expression is broken up into tokens, +with any whitespace between them discarded and ignored. The following +tokens are special: + + ( ) && || ! + +and the remaining token type is an 'atom', which is any non-empty +sequence of characters from the following set: + + ABCDEFGHIJKLMNOPQRSTUVWXYZ + abcdefghijklmnopqrstuvwxyz + 0123456789 + .-_*?[]/: + +Inner lexical layer: once the boundaries of an 'atom' token have been +determined by the outer lex layer, each atom is further classified +into one of the following subtypes: + + - If it contains no ':' or '/', it's taken to be a wildcard matching + hostnames, e.g. "*.example.com". + + - If it begins with 'port:' followed by digits, it's taken to be a + single port number specification, e.g. "port:22". + + - If it begins with 'port:' followed by two digit sequences separated + by '-', it's taken to be a port number range, e.g. "port:0-1023". + + - Any other atom is reserved for future expansion. (See Rationale.) + +Syntax layer: all of those types of atom are interpreted as predicates +applied to the (hostname, port) data configured for the SSH connection +for which the certificate is being validated. + +Wildcards are handled using the syntax in wildcard.c. + +More complex boolean expressions can be made by combining those +predicates using the boolean operators and parentheses, in the obvious +way: && and || are infix operators representing logical AND and OR, ! +is a prefix operator representing logical NOT, and parentheses +indicate grouping. + +Each of && and || can associate freely with itself (that is, you can +write "a && b && c" without having to parenthesise one or the other +subexpression). But they are forbidden to associate with _each other_. +That is, if you write "a && b || c" or "a || b && c", it's a syntax +error, and you must add parentheses to indicate which operator was +intended to have the higher priority. + +Rationale +========= + +Atoms: restrictions +------------------- + +The characters permitted in the 'atom' token don't include \, even +though it's a special character defined by wildcard.c. That's because +in this restricted context wildcards will never need it: no hostname +contains a literal \, and neither does any hostname contain a literal +instance of any of the wildcard characters that wildcard.c allows you +to use \ to escape. + +Atoms: future extension +----------------------- + +The specification of the 'atom' token is intended to leave space for +more than one kind of future extension. + +Most obviously, additional special predicates similar to "port:", with +different disambiguating prefixes. I don't know what things of that +kind we might need, but space is left for them just in case. + +Also, the unused '/' in the permitted-characters spec is intended to +leave open the possibility of allowing certificate acceptance to be +based on IP address, because the usual CIDR syntax for specifying IP +ranges (e.g. "192.168.1.0/24" or "2345:6789:abcd:ef01::/128") would be +lexed as a single atom under these rules. + +For the moment, certificate acceptance rules based on IP address are +not supported, because it's not clear what the semantics ought to be. +There are two problems with using IP addresses for this purpose: + + 1. Sometimes they come from the DNS, which means you can't trust + them. The whole idea of SSH is to end-to-end authenticate the host + key against only the input given _by the user_ to the client. Any + additional data provided by the network, such as the result of a + DNS lookup, is suspect. + + On the other hand, sometimes the IP address *is* part of the user + input, because the user can provide an IP address rather than a + hostname as the intended connection destination. So there are two + kinds of IP address, and they should very likely be treated + differently. + + 2. Sometimes the server's IP address is not even *known* by the + client, if you're connecting via a proxy and leaving DNS lookups + to the proxy. + +So, what should a boolean expression do if it's asked to accept or +reject based on an IP address, and the IP address is unknown or +untrustworthy? I'm not sure, and therefore, in the initial version of +this expression system, I haven't implemented them at all. + +But the syntax is still available for a future extension to use, if we +come up with good answers to these questions. + +(One possibility would be to evaluate the whole expression in Kleene +three-valued logic, so that every subexpression has the possible +answers TRUE, FALSE and UNKNOWN. If a definite IP address is not +available, IP address predicates evaluate to UNKNOWN. Then, once the +expression as a whole is evaluated, fail closed, by interpreting +UNKNOWN as 'reject'. The effect would be that a positive _or_ negative +constraint on the IP address would cause rejection if the IP address +is not reliably known, because once the predicate itself has returned +UNKNOWN, negating it still gives UNKNOWN. The only way you could still +accept a certificate in that situation would be if the overall +structure of the expression meant that the test of the IP address +couldn't affect the result anyway, e.g. if it was ANDed with another +subexpression that definitely evaluated to FALSE, or ORed with one +that evaluated to TRUE. This system seems conceptually elegant to me, +but the argument against it is that it's complicated and +counterintuitive, which is not a property you want in something a user +is writing for security purposes!) + +Operator precedence +------------------- + +Why did I choose to make && and || refuse to associate with each +other, instead of applying the usual C precedence rule that && beats +||? Because I think the C precedence rule is essentially arbitrary, in +the sense that when people are writing boolean expressions in practice +based on predicates from the rest of their program, it's about equally +common to want to nest an && within an || and vice versa. So the +default precedence rule only gives the user what they actually wanted +about 50% of the time, and leads to absent-minded errors about as +often as it conveniently allows you to omit a pair of parens. + +With my mathematician hat on, it's not so arbitrary. I agree that if +you're *going* to give || and && a relative priority then it makes +more sense to make && the higher-priority one, because if you're +thinking algebraically, && is more multiplicative and || is more +additive. But the pure-maths contexts in which that's convenient have +nothing to do with general boolean expressions in if statements. + +This boolean syntax is still close enough to that of C and its +derivatives to allow easy enough expression interchange (not counting +the fact that atoms would need rewriting). Any boolean expression +structure accepted by this syntax is also legal C and means the same +thing; any expression structure accepted by C is either legal and +equivalent in this syntax, or will fail with an error. In no case is +anything accepted but mapped to a different meaning. + + */ + +#include "putty.h" + +typedef enum Token { + TOK_LPAR, TOK_RPAR, + TOK_AND, TOK_OR, TOK_NOT, + TOK_ATOM, + TOK_END, TOK_ERROR +} Token; + +static inline bool is_space(char c) +{ + return (c == ' ' || c == '\n' || c == '\r' || c == '\t' || + c == '\f' || c == '\v'); +} + +static inline bool is_operator_char(char c) +{ + return (c == '(' || c == ')' || c == '&' || c == '|' || c == '!'); +} + +static inline bool is_atom_char(char c) +{ + return (('A' <= c && c <= 'Z') || + ('a' <= c && c <= 'z') || + ('0' <= c && c <= '9') || + c == '.' || c == '-' || c == '_' || c == '*' || c == '?' || + c == '[' || c == ']' || c == '/' || c == ':'); +} + +static Token lex(ptrlen *text, ptrlen *token, char **err) +{ + const char *p = text->ptr, *e = p + text->len; + Token type = TOK_ERROR; + + /* Skip whitespace */ + while (p < e && is_space(*p)) + p++; + + const char *start = p; + + if (!(p < e)) { + type = TOK_END; + goto out; + } + + if (is_operator_char(*p)) { + /* Match boolean-expression tokens */ + static const struct operator { + ptrlen text; + Token type; + } operators[] = { + {PTRLEN_DECL_LITERAL("("), TOK_LPAR}, + {PTRLEN_DECL_LITERAL(")"), TOK_RPAR}, + {PTRLEN_DECL_LITERAL("&&"), TOK_AND}, + {PTRLEN_DECL_LITERAL("||"), TOK_OR}, + {PTRLEN_DECL_LITERAL("!"), TOK_NOT}, + }; + + for (size_t i = 0; i < lenof(operators); i++) { + const struct operator *op = &operators[i]; + if (e - p >= op->text.len && + ptrlen_eq_ptrlen(op->text, make_ptrlen(p, op->text.len))) { + p += op->text.len; + type = op->type; + goto out; + } + } + + /* + * Report an error if one of the operator characters is used + * in a way that doesn't match something in that table (e.g. a + * single &). + */ + p++; + type = TOK_ERROR; + *err = dupstr("unrecognised boolean operator"); + goto out; + } else if (is_atom_char(*p)) { + /* + * Match an 'atom' token, which is any non-empty sequence of + * characters from the combined set that allows hostname + * wildcards, IP address ranges and special predicates like + * port numbers. + */ + do { + p++; + } while (p < e && is_atom_char(*p)); + + type = TOK_ATOM; + goto out; + } else { + /* + * Otherwise, report an error. + */ + p++; + type = TOK_ERROR; + *err = dupstr("unexpected character in expression"); + goto out; + } + + out: + *token = make_ptrlen(start, p - start); + text->ptr = p; + text->len = e - p; + return type; +} + +typedef enum Operator { + OP_AND, OP_OR, OP_NOT, + OP_HOSTNAME_WC, OP_PORT_RANGE +} Operator; + +typedef struct ExprNode ExprNode; +struct ExprNode { + Operator op; + ptrlen text; + union { + struct { + /* OP_AND, OP_OR */ + ExprNode **subexprs; + size_t nsubexprs; + }; + struct { + /* OP_NOT */ + ExprNode *subexpr; + }; + struct { + /* OP_HOSTNAME_WC */ + char *wc; + }; + struct { + /* OP_PORT_RANGE */ + unsigned lo, hi; /* both inclusive */ + }; + }; +}; + +static ExprNode *exprnode_new(Operator op, ptrlen text) +{ + ExprNode *en = snew(ExprNode); + memset(en, 0, sizeof(*en)); + en->op = op; + en->text = text; + return en; +} + +static void exprnode_free(ExprNode *en) +{ + switch (en->op) { + case OP_AND: + case OP_OR: + for (size_t i = 0; i < en->nsubexprs; i++) + exprnode_free(en->subexprs[i]); + sfree(en->subexprs); + break; + case OP_NOT: + exprnode_free(en->subexpr); + break; + case OP_HOSTNAME_WC: + sfree(en->wc); + break; + case OP_PORT_RANGE: + break; + default: + unreachable("unhandled node type in exprnode_free"); + } + + sfree(en); +} + +static unsigned ptrlen_to_port_number(ptrlen input) +{ + unsigned val = 0; + for (const char *p = input.ptr, *end = p + input.len; p < end; p++) { + assert('0' <= *p && *p <= '9'); /* expect parser to have checked */ + val = 10 * val + (*p - '0'); + if (val >= 65536) + val = 65536; /* normalise 'too large' to avoid integer overflow */ + } + return val; +} + +typedef struct ParserState ParserState; +struct ParserState { + ptrlen currtext; + Token tok; + ptrlen toktext, lasttoktext; + char *err; + ptrlen errloc; +}; + +static void error(ParserState *ps, char *errtext, ptrlen errloc) +{ + if (!ps->err) { + ps->err = errtext; + ps->errloc = errloc; + } else { + sfree(errtext); + } +} + +static void advance(ParserState *ps) +{ + char *err = NULL; + ps->lasttoktext = ps->toktext; + ps->tok = lex(&ps->currtext, &ps->toktext, &err); + if (ps->tok == TOK_ERROR) + error(ps, err, ps->toktext); +} + +static ExprNode *parse_atom(ParserState *ps); +static ExprNode *parse_expr(ParserState *ps); + +static bool atom_is_hostname_wc(ptrlen toktext) +{ + return !ptrlen_contains(toktext, ":/"); +} + +static ExprNode *parse_atom(ParserState *ps) +{ + if (ps->tok == TOK_LPAR) { + ptrlen openpar = ps->toktext; + advance(ps); /* eat the ( */ + + ExprNode *subexpr = parse_expr(ps); + if (!subexpr) + return NULL; + + if (ps->tok != TOK_RPAR) { + error(ps, dupstr("expected ')' after parenthesised subexpression"), + subexpr->text); + exprnode_free(subexpr); + return NULL; + } + + ptrlen closepar = ps->toktext; + advance(ps); /* eat the ) */ + + /* We can reuse the existing AST node, but we need to extend + * its bounds within the input expression to include the + * parentheses */ + subexpr->text = make_ptrlen_startend( + openpar.ptr, ptrlen_end(closepar)); + return subexpr; + } + + if (ps->tok == TOK_NOT) { + ptrlen notloc = ps->toktext; + advance(ps); /* eat the ! */ + + ExprNode *subexpr = parse_atom(ps); + if (!subexpr) + return NULL; + + ExprNode *en = exprnode_new( + OP_NOT, make_ptrlen_startend( + notloc.ptr, ptrlen_end(subexpr->text))); + en->subexpr = subexpr; + return en; + } + + if (ps->tok == TOK_ATOM) { + if (atom_is_hostname_wc(ps->toktext)) { + /* Hostname wildcard. */ + ExprNode *en = exprnode_new(OP_HOSTNAME_WC, ps->toktext); + en->wc = mkstr(ps->toktext); + advance(ps); + return en; + } + + ptrlen tail; + if (ptrlen_startswith(ps->toktext, PTRLEN_LITERAL("port:"), &tail)) { + /* Port number (single or range). */ + unsigned lo, hi; + char *minus; + static const char DIGITS[] = "0123456789\0"; + bool parse_ok = false; + + if (tail.len > 0 && ptrlen_contains_only(tail, DIGITS)) { + lo = ptrlen_to_port_number(tail); + if (lo >= 65536) { + error(ps, dupstr("port number too large"), tail); + return NULL; + } + hi = lo; + parse_ok = true; + } else if ((minus = memchr(tail.ptr, '-', tail.len)) != NULL) { + ptrlen pl_lo = make_ptrlen_startend(tail.ptr, minus); + ptrlen pl_hi = make_ptrlen_startend(minus+1, ptrlen_end(tail)); + if (pl_lo.len > 0 && ptrlen_contains_only(pl_lo, DIGITS) && + pl_hi.len > 0 && ptrlen_contains_only(pl_hi, DIGITS)) { + + lo = ptrlen_to_port_number(pl_lo); + if (lo >= 65536) { + error(ps, dupstr("port number too large"), pl_lo); + return NULL; + } + + hi = ptrlen_to_port_number(pl_hi); + if (hi >= 65536) { + error(ps, dupstr("port number too large"), pl_hi); + return NULL; + } + + if (hi < lo) { + error(ps, dupstr("port number range is backwards"), + make_ptrlen_startend(pl_lo.ptr, + ptrlen_end(pl_hi))); + return NULL; + } + + parse_ok = true; + } + } + + if (!parse_ok) { + error(ps, dupstr("unable to parse port number specification"), + ps->toktext); + return NULL; + } + + + ExprNode *en = exprnode_new(OP_PORT_RANGE, ps->toktext); + en->lo = lo; + en->hi = hi; + advance(ps); + return en; + } + } + + error(ps, dupstr("expected a predicate or a parenthesised subexpression"), + ps->toktext); + return NULL; +} + +static ExprNode *parse_expr(ParserState *ps) +{ + ExprNode *subexpr = parse_atom(ps); + if (!subexpr) + return NULL; + + if (ps->tok != TOK_AND && ps->tok != TOK_OR) + return subexpr; + + Token operator = ps->tok; + ExprNode *en = exprnode_new(ps->tok == TOK_AND ? OP_AND : OP_OR, + subexpr->text); + size_t subexprs_size = 0; + + sgrowarray(en->subexprs, subexprs_size, en->nsubexprs); + en->subexprs[en->nsubexprs++] = subexpr; + + while (true) { + advance(ps); /* eat the operator */ + + subexpr = parse_atom(ps); + if (!subexpr) { + exprnode_free(en); + return NULL; + } + sgrowarray(en->subexprs, subexprs_size, en->nsubexprs); + en->subexprs[en->nsubexprs++] = subexpr; + en->text = make_ptrlen_startend( + en->text.ptr, ptrlen_end(subexpr->text)); + + if (ps->tok != TOK_AND && ps->tok != TOK_OR) + return en; + + if (ps->tok != operator) { + error(ps, dupstr("expected parentheses to disambiguate && and || " + "on either side of expression"), subexpr->text); + exprnode_free(en); + return NULL; + } + } +} + +static ExprNode *parse(ptrlen expr, char **error_msg, ptrlen *error_loc) +{ + ParserState ps[1]; + ps->currtext = expr; + ps->lasttoktext = make_ptrlen(ps->currtext.ptr, 0); + ps->err = NULL; + advance(ps); + + ExprNode *en = parse_expr(ps); + if (en && ps->tok != TOK_END) { + error(ps, dupstr("unexpected text at end of expression"), + make_ptrlen_startend(ps->toktext.ptr, ptrlen_end(expr))); + exprnode_free(en); + en = NULL; + } + + if (!en) { + if (error_msg) + *error_msg = ps->err; + else + sfree(ps->err); + if (error_loc) + *error_loc = ps->errloc; + return NULL; + } + + return en; +} + +static bool eval(ExprNode *en, const char *hostname, unsigned port) +{ + switch (en->op) { + case OP_AND: + for (size_t i = 0; i < en->nsubexprs; i++) + if (!eval(en->subexprs[i], hostname, port)) + return false; + return true; + + case OP_OR: + for (size_t i = 0; i < en->nsubexprs; i++) + if (eval(en->subexprs[i], hostname, port)) + return true; + return false; + + case OP_NOT: + return !eval(en->subexpr, hostname, port); + + case OP_HOSTNAME_WC: + return wc_match(en->wc, hostname); + + case OP_PORT_RANGE: + return en->lo <= port && port <= en->hi; + + default: + unreachable("unhandled node type in eval"); + } +} + +bool cert_expr_match_str(const char *expression, + const char *hostname, unsigned port) +{ + ExprNode *en = parse(ptrlen_from_asciz(expression), NULL, NULL); + if (!en) + return false; + + bool matched = eval(en, hostname, port); + exprnode_free(en); + return matched; +} + +bool cert_expr_valid(const char *expression, + char **error_msg, ptrlen *error_loc) +{ + ExprNode *en = parse(ptrlen_from_asciz(expression), error_msg, error_loc); + if (en) { + exprnode_free(en); + return true; + } else { + return false; + } +} + +struct CertExprBuilder { + char **wcs; + size_t nwcs, wcsize; +}; + +CertExprBuilder *cert_expr_builder_new(void) +{ + CertExprBuilder *eb = snew(CertExprBuilder); + eb->wcs = NULL; + eb->nwcs = eb->wcsize = 0; + return eb; +} + +void cert_expr_builder_free(CertExprBuilder *eb) +{ + for (size_t i = 0; i < eb->nwcs; i++) + sfree(eb->wcs[i]); + sfree(eb->wcs); + sfree(eb); +} + +void cert_expr_builder_add(CertExprBuilder *eb, const char *wildcard) +{ + /* Check this wildcard is lexically valid as an atom */ + ptrlen orig = ptrlen_from_asciz(wildcard), pl = orig; + ptrlen toktext; + char *err; + Token tok = lex(&pl, &toktext, &err); + if (!(tok == TOK_ATOM && + toktext.ptr == orig.ptr && + toktext.len == orig.len && + atom_is_hostname_wc(toktext))) { + if (tok == TOK_ERROR) + sfree(err); + return; + } + + sgrowarray(eb->wcs, eb->wcsize, eb->nwcs); + eb->wcs[eb->nwcs++] = mkstr(orig); +} + +char *cert_expr_expression(CertExprBuilder *eb) +{ + strbuf *sb = strbuf_new(); + for (size_t i = 0; i < eb->nwcs; i++) { + if (i) + put_dataz(sb, " || "); + put_dataz(sb, eb->wcs[i]); + } + return strbuf_to_str(sb); +} + +#ifdef TEST + +void out_of_memory(void) { fprintf(stderr, "out of memory\n"); abort(); } + +static void exprnode_dump(BinarySink *bs, ExprNode *en, const char *origtext) +{ + put_fmt(bs, "(%zu:%zu ", + (size_t)((const char *)en->text.ptr - origtext), + (size_t)((const char *)ptrlen_end(en->text) - origtext)); + switch (en->op) { + case OP_AND: + case OP_OR: + put_dataz(bs, en->op == OP_AND ? "and" : "or"); + for (size_t i = 0; i < en->nsubexprs; i++) { + put_byte(bs, ' '); + exprnode_dump(bs, en->subexprs[i], origtext); + } + break; + case OP_NOT: + put_dataz(bs, "not "); + exprnode_dump(bs, en->subexpr, origtext); + break; + case OP_HOSTNAME_WC: + put_dataz(bs, "host-wc '"); + put_dataz(bs, en->wc); + put_byte(bs, '\''); + break; + case OP_PORT_RANGE: + put_fmt(bs, "port-range %u %u", en->lo, en->hi); + break; + default: + unreachable("unhandled node type in exprnode_dump"); + } + put_byte(bs, ')'); +} + +static const struct ParseTest { + const char *file; + int line; + const char *expr, *output; +} parsetests[] = { +#define T(expr_, output_) { \ + .file=__FILE__, .line=__LINE__, .expr=expr_, .output=output_} + + T("*.example.com", "(0:13 host-wc '*.example.com')"), + T("port:0", "(0:6 port-range 0 0)"), + T("port:22", "(0:7 port-range 22 22)"), + T("port:22-22", "(0:10 port-range 22 22)"), + T("port:65535", "(0:10 port-range 65535 65535)"), + T("port:0-1023", "(0:11 port-range 0 1023)"), + + T("&", "ERR:0:1:unrecognised boolean operator"), + T("|", "ERR:0:1:unrecognised boolean operator"), + T(";", "ERR:0:1:unexpected character in expression"), + T("port:", "ERR:0:5:unable to parse port number specification"), + T("port:abc", "ERR:0:8:unable to parse port number specification"), + T("port:65536", "ERR:5:10:port number too large"), + T("port:65536-65537", "ERR:5:10:port number too large"), + T("port:0-65536", "ERR:7:12:port number too large"), + T("port:23-22", "ERR:5:10:port number range is backwards"), + + T("a", "(0:1 host-wc 'a')"), + T("(a)", "(0:3 host-wc 'a')"), + T("((a))", "(0:5 host-wc 'a')"), + T(" (\n(\ra\t)\f)\v", "(1:10 host-wc 'a')"), + T("a&&b", "(0:4 and (0:1 host-wc 'a') (3:4 host-wc 'b'))"), + T("a||b", "(0:4 or (0:1 host-wc 'a') (3:4 host-wc 'b'))"), + T("a&&b&&c", "(0:7 and (0:1 host-wc 'a') (3:4 host-wc 'b') (6:7 host-wc 'c'))"), + T("a||b||c", "(0:7 or (0:1 host-wc 'a') (3:4 host-wc 'b') (6:7 host-wc 'c'))"), + T("a&&(b||c)", "(0:9 and (0:1 host-wc 'a') (3:9 or (4:5 host-wc 'b') (7:8 host-wc 'c')))"), + T("a||(b&&c)", "(0:9 or (0:1 host-wc 'a') (3:9 and (4:5 host-wc 'b') (7:8 host-wc 'c')))"), + T("(a&&b)||c", "(0:9 or (0:6 and (1:2 host-wc 'a') (4:5 host-wc 'b')) (8:9 host-wc 'c'))"), + T("(a||b)&&c", "(0:9 and (0:6 or (1:2 host-wc 'a') (4:5 host-wc 'b')) (8:9 host-wc 'c'))"), + T("!a&&b", "(0:5 and (0:2 not (1:2 host-wc 'a')) (4:5 host-wc 'b'))"), + T("a&&!b&&c", "(0:8 and (0:1 host-wc 'a') (3:5 not (4:5 host-wc 'b')) (7:8 host-wc 'c'))"), + T("!a||b", "(0:5 or (0:2 not (1:2 host-wc 'a')) (4:5 host-wc 'b'))"), + T("a||!b||c", "(0:8 or (0:1 host-wc 'a') (3:5 not (4:5 host-wc 'b')) (7:8 host-wc 'c'))"), + + T("", "ERR:0:0:expected a predicate or a parenthesised subexpression"), + T("a &&", "ERR:4:4:expected a predicate or a parenthesised subexpression"), + T("a ||", "ERR:4:4:expected a predicate or a parenthesised subexpression"), + T("a b c d", "ERR:2:7:unexpected text at end of expression"), + T("(", "ERR:1:1:expected a predicate or a parenthesised subexpression"), + T("(a", "ERR:1:2:expected ')' after parenthesised subexpression"), + T("(a b", "ERR:1:2:expected ')' after parenthesised subexpression"), + T("a&&b&&c||d||e", "ERR:6:7:expected parentheses to disambiguate && and || on either side of expression"), + T("a||b||c&&d&&e", "ERR:6:7:expected parentheses to disambiguate && and || on either side of expression"), + T("!", "ERR:1:1:expected a predicate or a parenthesised subexpression"), + + T("!a", "(0:2 not (1:2 host-wc 'a'))"), + +#undef T +}; + +static const struct EvalTest { + const char *file; + int line; + const char *expr; + const char *host; + unsigned port; + bool output; +} evaltests[] = { +#define T(expr_, host_, port_, output_) { \ + .file=__FILE__, .line=__LINE__, \ + .expr=expr_, .host=host_, .port=port_, .output=output_} + + T("*.example.com", "hostname.example.com", 22, true), + T("*.example.com", "hostname.example.org", 22, false), + T("*.example.com && port:22", "hostname.example.com", 21, false), + T("*.example.com && port:22", "hostname.example.com", 22, true), + T("*.example.com && port:22", "hostname.example.com", 23, false), + T("*.example.com && port:22-24", "hostname.example.com", 21, false), + T("*.example.com && port:22-24", "hostname.example.com", 22, true), + T("*.example.com && port:22-24", "hostname.example.com", 23, true), + T("*.example.com && port:22-24", "hostname.example.com", 24, true), + T("*.example.com && port:22-24", "hostname.example.com", 25, false), + + T("*a* && *b* && *c*", "", 22, false), + T("*a* && *b* && *c*", "a", 22, false), + T("*a* && *b* && *c*", "b", 22, false), + T("*a* && *b* && *c*", "c", 22, false), + T("*a* && *b* && *c*", "ab", 22, false), + T("*a* && *b* && *c*", "ac", 22, false), + T("*a* && *b* && *c*", "bc", 22, false), + T("*a* && *b* && *c*", "abc", 22, true), + + T("*a* || *b* || *c*", "", 22, false), + T("*a* || *b* || *c*", "a", 22, true), + T("*a* || *b* || *c*", "b", 22, true), + T("*a* || *b* || *c*", "c", 22, true), + T("*a* || *b* || *c*", "ab", 22, true), + T("*a* || *b* || *c*", "ac", 22, true), + T("*a* || *b* || *c*", "bc", 22, true), + T("*a* || *b* || *c*", "abc", 22, true), + + T("*a* && !*b* && *c*", "", 22, false), + T("*a* && !*b* && *c*", "a", 22, false), + T("*a* && !*b* && *c*", "b", 22, false), + T("*a* && !*b* && *c*", "c", 22, false), + T("*a* && !*b* && *c*", "ab", 22, false), + T("*a* && !*b* && *c*", "ac", 22, true), + T("*a* && !*b* && *c*", "bc", 22, false), + T("*a* && !*b* && *c*", "abc", 22, false), + + T("*a* || !*b* || *c*", "", 22, true), + T("*a* || !*b* || *c*", "a", 22, true), + T("*a* || !*b* || *c*", "b", 22, false), + T("*a* || !*b* || *c*", "c", 22, true), + T("*a* || !*b* || *c*", "ab", 22, true), + T("*a* || !*b* || *c*", "ac", 22, true), + T("*a* || !*b* || *c*", "bc", 22, true), + T("*a* || !*b* || *c*", "abc", 22, true), + +#undef T +}; + +int main(int argc, char **argv) +{ + if (argc > 1) { + /* + * Parse an expression from the command line. + */ + + ptrlen expr = ptrlen_from_asciz(argv[1]); + char *error_msg; + ptrlen error_loc; + ExprNode *en = parse(expr, &error_msg, &error_loc); + if (!en) { + fprintf(stderr, "ERR:%zu:%zu:%s\n", + (size_t)((const char *)error_loc.ptr - argv[1]), + (size_t)((const char *)ptrlen_end(error_loc) - argv[1]), + error_msg); + fprintf(stderr, "%.*s\n", PTRLEN_PRINTF(expr)); + for (const char *p = expr.ptr, *e = error_loc.ptr; p 2) { + /* + * Test-evaluate against a host/port pair given on the + * command line. + */ + const char *host = argv[2]; + unsigned port = (argc > 3 ? strtoul(argv[3], NULL, 0) : 22); + bool result = eval(en, host, port); + printf("%s\n", result ? "accept" : "reject"); + } else { + /* + * Just dump the result of parsing the expression. + */ + stdio_sink ss[1]; + stdio_sink_init(ss, stdout); + exprnode_dump(BinarySink_UPCAST(ss), en, expr.ptr); + put_byte(ss, '\n'); + } + + exprnode_free(en); + + return 0; + } else { + /* + * Run our automated tests. + */ + size_t pass = 0, fail = 0; + + for (size_t i = 0; i < lenof(parsetests); i++) { + const struct ParseTest *test = &parsetests[i]; + + ptrlen expr = ptrlen_from_asciz(test->expr); + char *error_msg; + ptrlen error_loc; + ExprNode *en = parse(expr, &error_msg, &error_loc); + + strbuf *output = strbuf_new(); + if (!en) { + put_fmt(output, "ERR:%zu:%zu:%s", + (size_t)((const char *)error_loc.ptr - test->expr), + (size_t)((const char *)ptrlen_end(error_loc) - + test->expr), + error_msg); + sfree(error_msg); + } else { + exprnode_dump(BinarySink_UPCAST(output), en, expr.ptr); + exprnode_free(en); + } + + if (ptrlen_eq_ptrlen(ptrlen_from_strbuf(output), + ptrlen_from_asciz(test->output))) { + pass++; + } else { + fprintf(stderr, "FAIL: parsetests[%zu] @ %s:%d:\n" + " expression: %s\n" + " expected: %s\n" + " actual: %s\n", + i, test->file, test->line, test->expr, + test->output, output->s); + fail++; + } + + strbuf_free(output); + } + + for (size_t i = 0; i < lenof(evaltests); i++) { + const struct EvalTest *test = &evaltests[i]; + + ptrlen expr = ptrlen_from_asciz(test->expr); + char *error_msg; + ptrlen error_loc; + ExprNode *en = parse(expr, &error_msg, &error_loc); + + if (!en) { + fprintf(stderr, "FAIL: evaltests[%zu] @ %s:%d:\n" + " expression: %s\n" + " parse error: %zu:%zu:%s\n", + i, test->file, test->line, test->expr, + (size_t)((const char *)error_loc.ptr - test->expr), + (size_t)((const char *)ptrlen_end(error_loc) - + test->expr), + error_msg); + sfree(error_msg); + } else { + bool output = eval(en, test->host, test->port); + if (output == test->output) { + pass++; + } else { + fprintf(stderr, "FAIL: evaltests[%zu] @ %s:%d:\n" + " expression: %s\n" + " host: %s\n" + " port: %u\n" + " expected: %s\n" + " actual: %s\n", + i, test->file, test->line, test->expr, + test->host, test->port, + test->output ? "accept" : "reject", + output ? "accept" : "reject"); + fail++; + } + exprnode_free(en); + } + } + + fprintf(stderr, "pass %zu fail %zu total %zu\n", + pass, fail, pass+fail); + return fail != 0; + } +} + +#endif // TEST diff --git a/utils/host_ca_new_free.c b/utils/host_ca_new_free.c index 8ae158ba..4c91c320 100644 --- a/utils/host_ca_new_free.c +++ b/utils/host_ca_new_free.c @@ -15,10 +15,8 @@ host_ca *host_ca_new(void) void host_ca_free(host_ca *hca) { sfree(hca->name); + sfree(hca->validity_expression); if (hca->ca_public_key) strbuf_free(hca->ca_public_key); - for (size_t i = 0; i < hca->n_hostname_wildcards; i++) - sfree(hca->hostname_wildcards[i]); - sfree(hca->hostname_wildcards); sfree(hca); } diff --git a/windows/storage.c b/windows/storage.c index 6a48aee6..30147a45 100644 --- a/windows/storage.c +++ b/windows/storage.c @@ -432,19 +432,20 @@ host_ca *host_ca_load(const char *name) if ((s = get_reg_sz(rkey, "PublicKey")) != NULL) hca->ca_public_key = base64_decode_sb(ptrlen_from_asciz(s)); - if ((sb = get_reg_multi_sz(rkey, "MatchHosts")) != NULL) { + if ((s = get_reg_sz(rkey, "Validity")) != NULL) { + hca->validity_expression = strbuf_to_str( + percent_decode_sb(ptrlen_from_asciz(s))); + } else if ((sb = get_reg_multi_sz(rkey, "MatchHosts")) != NULL) { BinarySource src[1]; BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(sb)); + CertExprBuilder *eb = cert_expr_builder_new(); const char *wc; - size_t wcsize = 0; - while (wc = get_asciz(src), !get_err(src)) { - sgrowarray(hca->hostname_wildcards, wcsize, - hca->n_hostname_wildcards); - hca->hostname_wildcards[hca->n_hostname_wildcards++] = dupstr(wc); - } + while (wc = get_asciz(src), !get_err(src)) + cert_expr_builder_add(eb, wc); - strbuf_free(sb); + hca->validity_expression = cert_expr_expression(eb); + cert_expr_builder_free(eb); } if (get_reg_dword(rkey, "PermitRSASHA1", &val)) @@ -479,11 +480,10 @@ char *host_ca_save(host_ca *hca) put_reg_sz(rkey, "PublicKey", base64_pubkey->s); strbuf_free(base64_pubkey); - strbuf *wcs = strbuf_new(); - for (size_t i = 0; i < hca->n_hostname_wildcards; i++) - put_asciz(wcs, hca->hostname_wildcards[i]); - put_reg_multi_sz(rkey, "MatchHosts", wcs); - strbuf_free(wcs); + strbuf *validity = percent_encode_sb( + ptrlen_from_asciz(hca->validity_expression), NULL); + put_reg_sz(rkey, "Validity", validity->s); + strbuf_free(validity); put_reg_dword(rkey, "PermitRSASHA1", hca->opts.permit_rsa_sha1); put_reg_dword(rkey, "PermitRSASHA256", hca->opts.permit_rsa_sha256); -- cgit v1.2.3 From a77040afa18f85622df4f001da2f133dd79d62eb Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Tue, 5 Jul 2022 18:37:15 +0100 Subject: Windows: add keyboard accelerators in the host key prompt. These went away when we moved from a standard MessageBox to a custom dialog, impairing accessibility. Now put some back by hand. --- windows/putty-common.rc2 | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/windows/putty-common.rc2 b/windows/putty-common.rc2 index cb2e177a..955a1cb3 100644 --- a/windows/putty-common.rc2 +++ b/windows/putty-common.rc2 @@ -73,11 +73,11 @@ BEGIN ICON "", IDC_HK_ICON, 10, 18, 0, 0 - PUSHBUTTON "Cancel", IDCANCEL, 288, 140, 40, 14 - PUSHBUTTON "Accept", IDC_HK_ACCEPT, 168, 140, 40, 14 - PUSHBUTTON "Connect Once", IDC_HK_ONCE, 216, 140, 64, 14 - PUSHBUTTON "More info...", IDC_HK_MOREINFO, 60, 140, 64, 14 - PUSHBUTTON "Help", IDHELP, 12, 140, 40, 14 + PUSHBUTTON "&Cancel", IDCANCEL, 288, 140, 40, 14 + PUSHBUTTON "&Accept", IDC_HK_ACCEPT, 168, 140, 40, 14 + PUSHBUTTON "Connect &Once", IDC_HK_ONCE, 216, 140, 64, 14 + PUSHBUTTON "More &info...", IDC_HK_MOREINFO, 60, 140, 64, 14 + PUSHBUTTON "&Help", IDHELP, 12, 140, 40, 14 EDITTEXT IDC_HK_HOST, 40, 28, 300, 12, ES_READONLY | ES_LEFT, 0 EDITTEXT IDC_HK_FINGERPRINT, 40, 60, 300, 12, ES_READONLY | ES_LEFT, 0 -- cgit v1.2.3 From d8f8c8972aecd96d9b2ec552f21421087dd7828c Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Tue, 5 Jul 2022 18:11:54 +0100 Subject: Make HelpCtx a per-platform type, not an intorptr. Partly, this just seems more sensible, since it may well vary per platform beyond the ability of intorptr to specify. But more immediately it means the definition of the HELPCTX macro doesn't have to use the P() function from dialog.h, which isn't defined in any circumstances outside the config subsystem. And I'm about to want to put a help context well outside that subsystem. --- config.c | 2 +- dialog.c | 28 ++++++++++++++-------------- dialog.h | 24 ++++++++++++------------ unix/platform.h | 4 +++- utils/ctrlset_normalise.c | 1 + windows/controls.c | 6 +++--- windows/help.h | 4 +++- 7 files changed, 37 insertions(+), 32 deletions(-) diff --git a/config.c b/config.c index cca76b29..a2537dbb 100644 --- a/config.c +++ b/config.c @@ -1588,7 +1588,7 @@ static void clipboard_selector_handler(dlgcontrol *ctrl, dlgparam *dlg, } static void clipboard_control(struct controlset *s, const char *label, - char shortcut, int percentage, intorptr helpctx, + char shortcut, int percentage, HelpCtx helpctx, int setting, int strsetting) { #ifdef NAMED_CLIPBOARDS diff --git a/dialog.c b/dialog.c index ce2d0eb2..26a0ff49 100644 --- a/dialog.c +++ b/dialog.c @@ -205,7 +205,7 @@ void *ctrl_alloc(struct controlbox *b, size_t size) } static dlgcontrol *ctrl_new(struct controlset *s, int type, - intorptr helpctx, handler_fn handler, + HelpCtx helpctx, handler_fn handler, intorptr context) { dlgcontrol *c = snew(dlgcontrol); @@ -228,7 +228,7 @@ static dlgcontrol *ctrl_new(struct controlset *s, int type, /* `ncolumns' is followed by that many percentages, as integers. */ dlgcontrol *ctrl_columns(struct controlset *s, int ncolumns, ...) { - dlgcontrol *c = ctrl_new(s, CTRL_COLUMNS, P(NULL), NULL, P(NULL)); + dlgcontrol *c = ctrl_new(s, CTRL_COLUMNS, NULL_HELPCTX, NULL, P(NULL)); assert(s->ncolumns == 1 || ncolumns == 1); c->columns.ncols = ncolumns; s->ncolumns = ncolumns; @@ -248,7 +248,7 @@ dlgcontrol *ctrl_columns(struct controlset *s, int ncolumns, ...) dlgcontrol *ctrl_editbox(struct controlset *s, const char *label, char shortcut, int percentage, - intorptr helpctx, handler_fn handler, + HelpCtx helpctx, handler_fn handler, intorptr context, intorptr context2) { dlgcontrol *c = ctrl_new(s, CTRL_EDITBOX, helpctx, handler, context); @@ -263,7 +263,7 @@ dlgcontrol *ctrl_editbox(struct controlset *s, const char *label, dlgcontrol *ctrl_combobox(struct controlset *s, const char *label, char shortcut, int percentage, - intorptr helpctx, handler_fn handler, + HelpCtx helpctx, handler_fn handler, intorptr context, intorptr context2) { dlgcontrol *c = ctrl_new(s, CTRL_EDITBOX, helpctx, handler, context); @@ -283,7 +283,7 @@ dlgcontrol *ctrl_combobox(struct controlset *s, const char *label, * is NO_SHORTCUT. */ dlgcontrol *ctrl_radiobuttons_fn(struct controlset *s, const char *label, - char shortcut, int ncolumns, intorptr helpctx, + char shortcut, int ncolumns, HelpCtx helpctx, handler_fn handler, intorptr context, ...) { va_list ap; @@ -329,7 +329,7 @@ dlgcontrol *ctrl_radiobuttons_fn(struct controlset *s, const char *label, } dlgcontrol *ctrl_pushbutton(struct controlset *s, const char *label, - char shortcut, intorptr helpctx, + char shortcut, HelpCtx helpctx, handler_fn handler, intorptr context) { dlgcontrol *c = ctrl_new(s, CTRL_BUTTON, helpctx, handler, context); @@ -341,7 +341,7 @@ dlgcontrol *ctrl_pushbutton(struct controlset *s, const char *label, } dlgcontrol *ctrl_listbox(struct controlset *s, const char *label, - char shortcut, intorptr helpctx, + char shortcut, HelpCtx helpctx, handler_fn handler, intorptr context) { dlgcontrol *c = ctrl_new(s, CTRL_LISTBOX, helpctx, handler, context); @@ -358,7 +358,7 @@ dlgcontrol *ctrl_listbox(struct controlset *s, const char *label, } dlgcontrol *ctrl_droplist(struct controlset *s, const char *label, - char shortcut, int percentage, intorptr helpctx, + char shortcut, int percentage, HelpCtx helpctx, handler_fn handler, intorptr context) { dlgcontrol *c = ctrl_new(s, CTRL_LISTBOX, helpctx, handler, context); @@ -375,7 +375,7 @@ dlgcontrol *ctrl_droplist(struct controlset *s, const char *label, } dlgcontrol *ctrl_draglist(struct controlset *s, const char *label, - char shortcut, intorptr helpctx, + char shortcut, HelpCtx helpctx, handler_fn handler, intorptr context) { dlgcontrol *c = ctrl_new(s, CTRL_LISTBOX, helpctx, handler, context); @@ -393,7 +393,7 @@ dlgcontrol *ctrl_draglist(struct controlset *s, const char *label, dlgcontrol *ctrl_filesel(struct controlset *s, const char *label, char shortcut, const char *filter, bool write, - const char *title, intorptr helpctx, + const char *title, HelpCtx helpctx, handler_fn handler, intorptr context) { dlgcontrol *c = ctrl_new(s, CTRL_FILESELECT, helpctx, handler, context); @@ -406,7 +406,7 @@ dlgcontrol *ctrl_filesel(struct controlset *s, const char *label, } dlgcontrol *ctrl_fontsel(struct controlset *s, const char *label, - char shortcut, intorptr helpctx, + char shortcut, HelpCtx helpctx, handler_fn handler, intorptr context) { dlgcontrol *c = ctrl_new(s, CTRL_FONTSELECT, helpctx, handler, context); @@ -417,13 +417,13 @@ dlgcontrol *ctrl_fontsel(struct controlset *s, const char *label, dlgcontrol *ctrl_tabdelay(struct controlset *s, dlgcontrol *ctrl) { - dlgcontrol *c = ctrl_new(s, CTRL_TABDELAY, P(NULL), NULL, P(NULL)); + dlgcontrol *c = ctrl_new(s, CTRL_TABDELAY, NULL_HELPCTX, NULL, P(NULL)); c->tabdelay.ctrl = ctrl; return c; } dlgcontrol *ctrl_text(struct controlset *s, const char *text, - intorptr helpctx) + HelpCtx helpctx) { dlgcontrol *c = ctrl_new(s, CTRL_TEXT, helpctx, NULL, P(NULL)); c->label = dupstr(text); @@ -432,7 +432,7 @@ dlgcontrol *ctrl_text(struct controlset *s, const char *text, } dlgcontrol *ctrl_checkbox(struct controlset *s, const char *label, - char shortcut, intorptr helpctx, + char shortcut, HelpCtx helpctx, handler_fn handler, intorptr context) { dlgcontrol *c = ctrl_new(s, CTRL_CHECKBOX, helpctx, handler, context); diff --git a/dialog.h b/dialog.h index fad78d16..ab933b9a 100644 --- a/dialog.h +++ b/dialog.h @@ -161,7 +161,7 @@ struct dlgcontrol { * platform-specific driver can use to ensure it brings up the * right piece of help text. */ - intorptr helpctx; + HelpCtx helpctx; /* * Setting this to non-NULL coerces two or more controls to have * their y-coordinates adjusted so that they can sit alongside @@ -519,11 +519,11 @@ void *ctrl_alloc_with_free(struct controlbox *b, size_t size, /* `ncolumns' is followed by that many percentages, as integers. */ dlgcontrol *ctrl_columns(struct controlset *, int ncolumns, ...); dlgcontrol *ctrl_editbox(struct controlset *, const char *label, - char shortcut, int percentage, intorptr helpctx, + char shortcut, int percentage, HelpCtx helpctx, handler_fn handler, intorptr context, intorptr context2); dlgcontrol *ctrl_combobox(struct controlset *, const char *label, - char shortcut, int percentage, intorptr helpctx, + char shortcut, int percentage, HelpCtx helpctx, handler_fn handler, intorptr context, intorptr context2); /* @@ -533,33 +533,33 @@ dlgcontrol *ctrl_combobox(struct controlset *, const char *label, * is NO_SHORTCUT. */ dlgcontrol *ctrl_radiobuttons_fn(struct controlset *, const char *label, - char shortcut, int ncolumns, intorptr helpctx, + char shortcut, int ncolumns, HelpCtx helpctx, handler_fn handler, intorptr context, ...); #define ctrl_radiobuttons(...) \ ctrl_radiobuttons_fn(__VA_ARGS__, (const char *)NULL) dlgcontrol *ctrl_pushbutton(struct controlset *, const char *label, - char shortcut, intorptr helpctx, + char shortcut, HelpCtx helpctx, handler_fn handler, intorptr context); dlgcontrol *ctrl_listbox(struct controlset *, const char *label, - char shortcut, intorptr helpctx, + char shortcut, HelpCtx helpctx, handler_fn handler, intorptr context); dlgcontrol *ctrl_droplist(struct controlset *, const char *label, - char shortcut, int percentage, intorptr helpctx, + char shortcut, int percentage, HelpCtx helpctx, handler_fn handler, intorptr context); dlgcontrol *ctrl_draglist(struct controlset *, const char *label, - char shortcut, intorptr helpctx, + char shortcut, HelpCtx helpctx, handler_fn handler, intorptr context); dlgcontrol *ctrl_filesel(struct controlset *, const char *label, char shortcut, const char *filter, bool write, - const char *title, intorptr helpctx, + const char *title, HelpCtx helpctx, handler_fn handler, intorptr context); dlgcontrol *ctrl_fontsel(struct controlset *, const char *label, - char shortcut, intorptr helpctx, + char shortcut, HelpCtx helpctx, handler_fn handler, intorptr context); dlgcontrol *ctrl_text(struct controlset *, const char *text, - intorptr helpctx); + HelpCtx helpctx); dlgcontrol *ctrl_checkbox(struct controlset *, const char *label, - char shortcut, intorptr helpctx, + char shortcut, HelpCtx helpctx, handler_fn handler, intorptr context); dlgcontrol *ctrl_tabdelay(struct controlset *, dlgcontrol *); diff --git a/unix/platform.h b/unix/platform.h index 6a8c7ba3..31da1294 100644 --- a/unix/platform.h +++ b/unix/platform.h @@ -78,7 +78,9 @@ extern const struct BackendVtable pty_backend; /* * Under GTK, there is no context help available. */ -#define HELPCTX(x) P(NULL) +typedef void *HelpCtx; +#define NULL_HELPCTX ((HelpCtx)NULL) +#define HELPCTX(x) NULL #define FILTER_KEY_FILES NULL /* FIXME */ #define FILTER_DYNLIB_FILES NULL /* FIXME */ diff --git a/utils/ctrlset_normalise.c b/utils/ctrlset_normalise.c index 46f5f19f..3d922ebb 100644 --- a/utils/ctrlset_normalise.c +++ b/utils/ctrlset_normalise.c @@ -2,6 +2,7 @@ * Helper function from the dialog.h mechanism. */ +#include "putty.h" #include "misc.h" #include "dialog.h" diff --git a/windows/controls.c b/windows/controls.c index 7065b7be..22b70c93 100644 --- a/windows/controls.c +++ b/windows/controls.c @@ -2131,12 +2131,12 @@ bool winctrl_context_help(struct dlgparam *dp, HWND hwnd, int id) /* * This is the Windows front end, so we're allowed to assume - * `helpctx.p' is a context string. + * `helpctx' is a context string. */ - if (!c->ctrl || !c->ctrl->helpctx.p) + if (!c->ctrl || !c->ctrl->helpctx) return false; /* no help available for this ctrl */ - launch_help(hwnd, c->ctrl->helpctx.p); + launch_help(hwnd, c->ctrl->helpctx); return true; } diff --git a/windows/help.h b/windows/help.h index 0ab0e050..d0f76496 100644 --- a/windows/help.h +++ b/windows/help.h @@ -9,7 +9,9 @@ /* These are used in the cross-platform configuration dialog code. */ -#define HELPCTX(x) P(WINHELP_CTX_ ## x) +typedef const char *HelpCtx; +#define NULL_HELPCTX NULL +#define HELPCTX(x) WINHELP_CTX_ ## x #define WINHELP_CTX_no_help NULL -- cgit v1.2.3 From d155009ded353e840f1f1480bb55fde9230b866c Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Wed, 6 Jul 2022 19:01:15 +0100 Subject: Utility function to do terminal word wrapping. I'm planning to use this to replace some of the manually wrapped lines in console messages. --- misc.h | 2 ++ utils/CMakeLists.txt | 1 + utils/wordwrap.c | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 39 insertions(+) create mode 100644 utils/wordwrap.c diff --git a/misc.h b/misc.h index bdfa8c2e..f9e59b50 100644 --- a/misc.h +++ b/misc.h @@ -232,6 +232,8 @@ int string_length_for_printf(size_t); /* Make a ptrlen out of a constant byte array. */ #define PTRLEN_FROM_CONST_BYTES(a) make_ptrlen(a, sizeof(a)) +void wordwrap(BinarySink *bs, ptrlen input, size_t maxwid); + /* Wipe sensitive data out of memory that's about to be freed. Simpler * than memset because we don't need the fill char parameter; also * attempts (by fiddly use of volatile) to inhibit the compiler from diff --git a/utils/CMakeLists.txt b/utils/CMakeLists.txt index 4f5479a6..34644cbd 100644 --- a/utils/CMakeLists.txt +++ b/utils/CMakeLists.txt @@ -70,6 +70,7 @@ add_sources_from_current_dir(utils version.c wcwidth.c wildcard.c + wordwrap.c write_c_string_literal.c x11authfile.c x11authnames.c diff --git a/utils/wordwrap.c b/utils/wordwrap.c new file mode 100644 index 00000000..330b6a03 --- /dev/null +++ b/utils/wordwrap.c @@ -0,0 +1,36 @@ +/* + * Function to wrap text to a fixed number of columns. + * + * Currently, assumes the text is in a single-byte character set, + * because it's only used to display host key prompt messages. + * Extending to Unicode and using wcwidth() could be an extension. + */ + +#include "misc.h" + +void wordwrap(BinarySink *bs, ptrlen input, size_t maxwid) +{ + size_t col = 0; + while (true) { + ptrlen word = ptrlen_get_word(&input, " "); + if (!word.len) + break; + + /* At the start of a line, any word is legal, even if it's + * overlong, because we have to display it _somehow_ and + * wrapping to the next line won't make it any better. */ + if (col > 0) { + size_t newcol = col + 1 + word.len; + if (newcol <= maxwid) { + put_byte(bs, ' '); + col++; + } else { + put_byte(bs, '\n'); + col = 0; + } + } + + put_datapl(bs, word); + col += word.len; + } +} -- cgit v1.2.3 From 46332db26e43569ccc2e4d71de54317d6ce5eca1 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 7 Jul 2022 17:23:27 +0100 Subject: Move host key dialogs over to using ShinyDialogBox. They were previously using an ad-hoc system for getting hold of their context structure, which had to be different between the WM_INITDIALOG handler and everything else. That's exactly what ShinyDialogBox is good for preventing, so let's use it, to save effort. --- windows/dialog.c | 43 ++++++++++++++++++------------------------- windows/putty-common.rc2 | 2 ++ 2 files changed, 20 insertions(+), 25 deletions(-) diff --git a/windows/dialog.c b/windows/dialog.c index f65c8a61..f2c309b8 100644 --- a/windows/dialog.c +++ b/windows/dialog.c @@ -853,14 +853,13 @@ struct hostkey_dialog_ctx { const char *helpctx; }; -static INT_PTR CALLBACK HostKeyMoreInfoProc(HWND hwnd, UINT msg, - WPARAM wParam, LPARAM lParam) +static INT_PTR HostKeyMoreInfoProc(HWND hwnd, UINT msg, WPARAM wParam, + LPARAM lParam, void *vctx) { + struct hostkey_dialog_ctx *ctx = (struct hostkey_dialog_ctx *)vctx; + switch (msg) { case WM_INITDIALOG: { - const struct hostkey_dialog_ctx *ctx = - (const struct hostkey_dialog_ctx *)lParam; - SetWindowLongPtr(hwnd, GWLP_USERDATA, (INT_PTR)ctx); if (ctx->fingerprints[SSH_FPTYPE_SHA256]) SetDlgItemText(hwnd, IDC_HKI_SHA256, @@ -876,26 +875,25 @@ static INT_PTR CALLBACK HostKeyMoreInfoProc(HWND hwnd, UINT msg, case WM_COMMAND: switch (LOWORD(wParam)) { case IDOK: - EndDialog(hwnd, 0); + ShinyEndDialog(hwnd, 0); return 0; } return 0; case WM_CLOSE: - EndDialog(hwnd, 0); + ShinyEndDialog(hwnd, 0); return 0; } return 0; } -static INT_PTR CALLBACK HostKeyDialogProc(HWND hwnd, UINT msg, - WPARAM wParam, LPARAM lParam) +static INT_PTR HostKeyDialogProc(HWND hwnd, UINT msg, + WPARAM wParam, LPARAM lParam, void *vctx) { + struct hostkey_dialog_ctx *ctx = (struct hostkey_dialog_ctx *)vctx; + switch (msg) { case WM_INITDIALOG: { strbuf *sb = strbuf_new(); - const struct hostkey_dialog_ctx *ctx = - (const struct hostkey_dialog_ctx *)lParam; - SetWindowLongPtr(hwnd, GWLP_USERDATA, (INT_PTR)ctx); for (int id = 100;; id++) { char buf[256]; @@ -970,26 +968,21 @@ static INT_PTR CALLBACK HostKeyDialogProc(HWND hwnd, UINT msg, case IDC_HK_ACCEPT: case IDC_HK_ONCE: case IDCANCEL: - EndDialog(hwnd, LOWORD(wParam)); + ShinyEndDialog(hwnd, LOWORD(wParam)); return 0; case IDHELP: { - const struct hostkey_dialog_ctx *ctx = - (const struct hostkey_dialog_ctx *) - GetWindowLongPtr(hwnd, GWLP_USERDATA); launch_help(hwnd, ctx->helpctx); return 0; } case IDC_HK_MOREINFO: { - const struct hostkey_dialog_ctx *ctx = - (const struct hostkey_dialog_ctx *) - GetWindowLongPtr(hwnd, GWLP_USERDATA); - DialogBoxParam(hinst, MAKEINTRESOURCE(IDD_HK_MOREINFO), - hwnd, HostKeyMoreInfoProc, (LPARAM)ctx); + ShinyDialogBox(hinst, MAKEINTRESOURCE(IDD_HK_MOREINFO), + "PuTTYHostKeyMoreInfo", hwnd, + HostKeyMoreInfoProc, ctx); } } return 0; case WM_CLOSE: - EndDialog(hwnd, IDCANCEL); + ShinyEndDialog(hwnd, IDCANCEL); return 0; } return 0; @@ -1021,9 +1014,9 @@ SeatPromptResult win_seat_confirm_ssh_host_key( ctx->host = host; ctx->port = port; int dlgid = (mismatch ? IDD_HK_WRONG : IDD_HK_ABSENT); - int mbret = DialogBoxParam( - hinst, MAKEINTRESOURCE(dlgid), wgs->term_hwnd, - HostKeyDialogProc, (LPARAM)ctx); + int mbret = ShinyDialogBox( + hinst, MAKEINTRESOURCE(dlgid), "PuTTYHostKeyDialog", + wgs->term_hwnd, HostKeyDialogProc, ctx); assert(mbret==IDC_HK_ACCEPT || mbret==IDC_HK_ONCE || mbret==IDCANCEL); if (mbret == IDC_HK_ACCEPT) { store_host_key(host, port, keytype, keystr); diff --git a/windows/putty-common.rc2 b/windows/putty-common.rc2 index 955a1cb3..b6ccb3d8 100644 --- a/windows/putty-common.rc2 +++ b/windows/putty-common.rc2 @@ -61,6 +61,7 @@ IDD_HK_ABSENT DIALOG DISCARDABLE 50, 50, 340, 160 STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "PuTTY Security Alert" FONT 8, "MS Shell Dlg" +CLASS "PuTTYHostKeyDialog" BEGIN LTEXT "The host key is not cached for this server:", 100, 40, 20, 300, 8 LTEXT "You have no guarantee that the server is the computer you think it is.", 101, 40, 40, 300, 8 @@ -120,6 +121,7 @@ IDD_HK_MOREINFO DIALOG DISCARDABLE 140, 40, 400, 156 STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "PuTTY: information about the server's host key" FONT 8, "MS Shell Dlg" +CLASS "PuTTYHostKeyMoreInfo" BEGIN LTEXT "SHA256 fingerprint:", 100, 12, 12, 80, 8 EDITTEXT IDC_HKI_SHA256, 100, 10, 288, 12, ES_READONLY -- cgit v1.2.3 From f1c82980007f9afa1ab5a5fefeab5dcaf12e8803 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 7 Jul 2022 17:25:15 +0100 Subject: Centralise most details of host-key prompting. The text of the host key warnings was replicated in three places: the Windows rc file, the GTK dialog setup function, and the console.c shared between both platforms' CLI tools. Now it lives in just one place, namely ssh/common.c where the rest of the centralised host-key checking is done, so it'll be easier to adjust the wording in future. This comes with some extra automation. Paragraph wrapping is no longer done by hand in any version of these prompts. (Previously we let GTK do the wrapping on GTK, but on Windows the resource file contained a bunch of pre-wrapped LTEXT lines, and console.c had pre-wrapped terminal messages.) And the dialog heights in Windows are determined automatically based on the amount of stuff in the window. The main idea of all this is that it'll be easier to set up more elaborate kinds of host key prompt that deal with certificates (if, e.g., a server sends us a certified host key which we don't trust the CA for). But there are side benefits of this refactoring too: each tool now reliably inserts its own appname in the prompts, and also, on Windows the entire prompt text is copy-pastable. Details of implementation: there's a new type SeatDialogText which holds a set of (type, string) pairs describing the contents of a prompt. Type codes distinguish ordinary text paragraphs, paragraphs to be displayed prominently (like key fingerprints), the extra-bold scary title at the top of the 'host key changed' version of the dialog, and the various information that lives in the subsidiary 'more info' box. ssh/common.c constructs this, and passes it to the Seat to present the actual prompt. In order to deal with the different UI for answering the prompt, I've added an extra Seat method 'prompt_descriptions' which returns some snippets of text to interpolate into the messages. ssh/common.c calls that while it's still constructing the text, and incorporates the resulting snippets into the SeatDialogText. For the moment, this refactoring only affects the host key prompts. The warnings about outmoded crypto are still done the old-fashioned way; they probably ought to be similarly refactored to use this new SeatDialogText system, but it's not immediately critical for the purpose I have right now. --- console.c | 61 ++--------- defs.h | 3 + proxy/sshproxy.c | 21 +++- pscp.c | 1 + psftp.c | 1 + putty.h | 49 +++++++-- ssh/common.c | 102 +++++++++++++++++- ssh/server.c | 1 + ssh/sesschan.c | 1 + unix/console.c | 87 +++++++++------ unix/dialog.c | 133 ++++++++++++----------- unix/platform.h | 3 +- unix/plink.c | 1 + unix/window.c | 1 + utils/CMakeLists.txt | 1 + utils/nullseat.c | 13 ++- utils/seat_dialog_text.c | 41 +++++++ utils/tempseat.c | 14 ++- windows/console.c | 87 +++++++++------ windows/dialog.c | 272 +++++++++++++++++++++++++++++++++++------------ windows/platform.h | 3 +- windows/plink.c | 1 + windows/putty-common.rc2 | 64 ++--------- windows/putty-rc.h | 4 +- windows/window.c | 1 + 25 files changed, 644 insertions(+), 322 deletions(-) create mode 100644 utils/seat_dialog_text.c diff --git a/console.c b/console.c index 465fdfa7..62c64aff 100644 --- a/console.c +++ b/console.c @@ -9,56 +9,6 @@ #include "misc.h" #include "console.h" -char *hk_absentmsg_common(const char *host, int port, - const char *keytype, const char *fingerprint) -{ - return dupprintf( - "The host key is not cached for this server:\n" - " %s (port %d)\n" - "You have no guarantee that the server is the computer\n" - "you think it is.\n" - "The server's %s key fingerprint is:\n" - " %s\n", host, port, keytype, fingerprint); -} - -const char hk_absentmsg_interactive_intro[] = - "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"; -const char hk_absentmsg_interactive_prompt[] = - "Store key in cache? (y/n, Return cancels connection, " - "i for more info) "; - -char *hk_wrongmsg_common(const char *host, int port, - const char *keytype, const char *fingerprint) -{ - return dupprintf( - "WARNING - POTENTIAL SECURITY BREACH!\n" - "The host key does not match the one PuTTY has cached\n" - "for this server:\n" - " %s (port %d)\n" - "This means that either the server administrator has\n" - "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", host, port, keytype, fingerprint); -} - -const char hk_wrongmsg_interactive_intro[] = - "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"; -const char hk_wrongmsg_interactive_prompt[] = - "Update cached key? (y/n, Return cancels connection, " - "i for more info) "; - const char weakcrypto_msg_common_fmt[] = "The first %s supported by the server is\n" "%s, which is below the configured warning threshold.\n"; @@ -73,6 +23,17 @@ const char weakhk_msg_common_fmt[] = const char console_continue_prompt[] = "Continue with connection? (y/n) "; const char console_abandoned_msg[] = "Connection abandoned.\n"; +const SeatDialogPromptDescriptions *console_prompt_descriptions(Seat *seat) +{ + static const SeatDialogPromptDescriptions descs = { + .hk_accept_action = "enter \"y\"", + .hk_connect_once_action = "enter \"n\"", + .hk_cancel_action = "press Return", + .hk_cancel_action_Participle = "Pressing Return", + }; + return &descs; +} + bool console_batch_mode = false; /* diff --git a/defs.h b/defs.h index 48cbaf23..286e0c96 100644 --- a/defs.h +++ b/defs.h @@ -116,6 +116,9 @@ typedef struct LogPolicyVtable LogPolicyVtable; typedef struct Seat Seat; typedef struct SeatVtable SeatVtable; +typedef struct SeatDialogText SeatDialogText; +typedef struct SeatDialogTextItem SeatDialogTextItem; +typedef struct SeatDialogPromptDescriptions SeatDialogPromptDescriptions; typedef struct SeatPromptResult SeatPromptResult; typedef struct cmdline_get_passwd_input_state cmdline_get_passwd_input_state; diff --git a/proxy/sshproxy.c b/proxy/sshproxy.c index 3e5a696f..2aed2b6b 100644 --- a/proxy/sshproxy.c +++ b/proxy/sshproxy.c @@ -407,7 +407,7 @@ static void sshproxy_connection_fatal(Seat *seat, const char *message) static SeatPromptResult sshproxy_confirm_ssh_host_key( Seat *seat, const char *host, int port, const char *keytype, - char *keystr, const char *keydisp, char **key_fingerprints, bool mismatch, + char *keystr, SeatDialogText *text, HelpCtx helpctx, void (*callback)(void *ctx, SeatPromptResult result), void *ctx) { SshProxy *sp = container_of(seat, SshProxy, seat); @@ -418,8 +418,8 @@ static SeatPromptResult sshproxy_confirm_ssh_host_key( * request on to it. */ return seat_confirm_ssh_host_key( - wrap(sp->clientseat), host, port, keytype, keystr, keydisp, - key_fingerprints, mismatch, callback, ctx); + wrap(sp->clientseat), host, port, keytype, keystr, text, + helpctx, callback, ctx); } /* @@ -482,6 +482,20 @@ static SeatPromptResult sshproxy_confirm_weak_cached_hostkey( "weak cached host key"); } +static const SeatDialogPromptDescriptions *sshproxy_prompt_descriptions( + Seat *seat) +{ + SshProxy *sp = container_of(seat, SshProxy, seat); + + /* If we have a client seat, return their prompt descriptions, so + * that prompts passed on to them will make sense. */ + if (sp->clientseat) + return seat_prompt_descriptions(sp->clientseat); + + /* Otherwise, it doesn't matter what we return, so do the easiest thing. */ + return nullseat_prompt_descriptions(NULL); +} + static StripCtrlChars *sshproxy_stripctrl_new( Seat *seat, BinarySink *bs_out, SeatInteractionContext sic) { @@ -533,6 +547,7 @@ static const SeatVtable SshProxy_seat_vt = { .confirm_ssh_host_key = sshproxy_confirm_ssh_host_key, .confirm_weak_crypto_primitive = sshproxy_confirm_weak_crypto_primitive, .confirm_weak_cached_hostkey = sshproxy_confirm_weak_cached_hostkey, + .prompt_descriptions = sshproxy_prompt_descriptions, .is_utf8 = nullseat_is_never_utf8, .echoedit_update = nullseat_echoedit_update, .get_x_display = nullseat_get_x_display, diff --git a/pscp.c b/pscp.c index da9e9aaa..4df8cb95 100644 --- a/pscp.c +++ b/pscp.c @@ -77,6 +77,7 @@ static const SeatVtable pscp_seat_vt = { .confirm_ssh_host_key = console_confirm_ssh_host_key, .confirm_weak_crypto_primitive = console_confirm_weak_crypto_primitive, .confirm_weak_cached_hostkey = console_confirm_weak_cached_hostkey, + .prompt_descriptions = console_prompt_descriptions, .is_utf8 = nullseat_is_never_utf8, .echoedit_update = nullseat_echoedit_update, .get_x_display = nullseat_get_x_display, diff --git a/psftp.c b/psftp.c index bddc18a2..a1600cd9 100644 --- a/psftp.c +++ b/psftp.c @@ -58,6 +58,7 @@ static const SeatVtable psftp_seat_vt = { .confirm_ssh_host_key = console_confirm_ssh_host_key, .confirm_weak_crypto_primitive = console_confirm_weak_crypto_primitive, .confirm_weak_cached_hostkey = console_confirm_weak_cached_hostkey, + .prompt_descriptions = console_prompt_descriptions, .is_utf8 = nullseat_is_never_utf8, .echoedit_update = nullseat_echoedit_update, .get_x_display = nullseat_get_x_display, diff --git a/putty.h b/putty.h index d7f33369..6f177474 100644 --- a/putty.h +++ b/putty.h @@ -1085,6 +1085,24 @@ typedef enum SeatOutputType { SEAT_OUTPUT_STDOUT, SEAT_OUTPUT_STDERR } SeatOutputType; +typedef enum SeatDialogTextType { + SDT_PARA, SDT_DISPLAY, SDT_SCARY_HEADING, + SDT_TITLE, SDT_PROMPT, SDT_BATCH_ABORT, + SDT_MORE_INFO_KEY, SDT_MORE_INFO_VALUE_SHORT, SDT_MORE_INFO_VALUE_BLOB +} SeatDialogTextType; +struct SeatDialogTextItem { + SeatDialogTextType type; + char *text; +}; +struct SeatDialogText { + size_t nitems, itemsize; + SeatDialogTextItem *items; +}; +SeatDialogText *seat_dialog_text_new(void); +void seat_dialog_text_free(SeatDialogText *sdt); +PRINTF_LIKE(3, 4) void seat_dialog_text_append( + SeatDialogText *sdt, SeatDialogTextType type, const char *fmt, ...); + /* * Data type 'Seat', which is an API intended to contain essentially * everything that a back end might need to talk to its client for: @@ -1259,9 +1277,8 @@ struct SeatVtable { */ SeatPromptResult (*confirm_ssh_host_key)( Seat *seat, const char *host, int port, const char *keytype, - char *keystr, const char *keydisp, char **key_fingerprints, - bool mismatch, void (*callback)(void *ctx, SeatPromptResult result), - void *ctx); + char *keystr, SeatDialogText *text, HelpCtx helpctx, + void (*callback)(void *ctx, SeatPromptResult result), void *ctx); /* * Check with the seat whether it's OK to use a cryptographic @@ -1288,6 +1305,13 @@ struct SeatVtable { Seat *seat, const char *algname, const char *betteralgs, void (*callback)(void *ctx, SeatPromptResult result), void *ctx); + /* + * Some snippets of text describing the UI actions in host key + * prompts / dialog boxes, to be used in ssh/common.c when it + * assembles the full text of those prompts. + */ + const SeatDialogPromptDescriptions *(*prompt_descriptions)(Seat *seat); + /* * Indicates whether the seat is expecting to interact with the * user in the UTF-8 character set. (Affects e.g. visual erase @@ -1409,10 +1433,10 @@ static inline void seat_set_busy_status(Seat *seat, BusyStatus status) { seat->vt->set_busy_status(seat, status); } static inline SeatPromptResult seat_confirm_ssh_host_key( InteractionReadySeat iseat, const char *h, int p, const char *ktyp, - char *kstr, const char *kdsp, char **fps, bool mis, + char *kstr, SeatDialogText *text, HelpCtx helpctx, void (*cb)(void *ctx, SeatPromptResult result), void *ctx) { return iseat.seat->vt->confirm_ssh_host_key( - iseat.seat, h, p, ktyp, kstr, kdsp, fps, mis, cb, ctx); } + iseat.seat, h, p, ktyp, kstr, text, helpctx, cb, ctx); } static inline SeatPromptResult seat_confirm_weak_crypto_primitive( InteractionReadySeat iseat, const char *atyp, const char *aname, void (*cb)(void *ctx, SeatPromptResult result), void *ctx) @@ -1423,6 +1447,9 @@ static inline SeatPromptResult seat_confirm_weak_cached_hostkey( void (*cb)(void *ctx, SeatPromptResult result), void *ctx) { return iseat.seat->vt->confirm_weak_cached_hostkey( iseat.seat, aname, better, cb, ctx); } +static inline const SeatDialogPromptDescriptions *seat_prompt_descriptions( + Seat *seat) +{ return seat->vt->prompt_descriptions(seat); } static inline bool seat_is_utf8(Seat *seat) { return seat->vt->is_utf8(seat); } static inline void seat_echoedit_update(Seat *seat, bool ec, bool ed) @@ -1468,6 +1495,12 @@ static inline size_t seat_stderr_pl(Seat *seat, ptrlen data) static inline size_t seat_banner_pl(InteractionReadySeat iseat, ptrlen data) { return iseat.seat->vt->banner(iseat.seat, data.ptr, data.len); } +struct SeatDialogPromptDescriptions { + const char *hk_accept_action; + const char *hk_connect_once_action; + const char *hk_cancel_action, *hk_cancel_action_Participle; +}; + /* In the utils subdir: print a message to the Seat which can't be * spoofed by server-supplied auth-time output such as SSH banners */ void seat_antispoof_msg(InteractionReadySeat iseat, const char *msg); @@ -1495,7 +1528,7 @@ char *nullseat_get_ttymode(Seat *seat, const char *mode); void nullseat_set_busy_status(Seat *seat, BusyStatus status); SeatPromptResult nullseat_confirm_ssh_host_key( Seat *seat, const char *host, int port, const char *keytype, - char *keystr, const char *keydisp, char **key_fingerprints, bool mismatch, + char *keystr, SeatDialogText *text, HelpCtx helpctx, void (*callback)(void *ctx, SeatPromptResult result), void *ctx); SeatPromptResult nullseat_confirm_weak_crypto_primitive( Seat *seat, const char *algtype, const char *algname, @@ -1503,6 +1536,7 @@ SeatPromptResult nullseat_confirm_weak_crypto_primitive( SeatPromptResult nullseat_confirm_weak_cached_hostkey( Seat *seat, const char *algname, const char *betteralgs, void (*callback)(void *ctx, SeatPromptResult result), void *ctx); +const SeatDialogPromptDescriptions *nullseat_prompt_descriptions(Seat *seat); bool nullseat_is_never_utf8(Seat *seat); bool nullseat_is_always_utf8(Seat *seat); void nullseat_echoedit_update(Seat *seat, bool echoing, bool editing); @@ -1530,7 +1564,7 @@ bool nullseat_get_cursor_position(Seat *seat, int *x, int *y); void console_connection_fatal(Seat *seat, const char *message); SeatPromptResult console_confirm_ssh_host_key( Seat *seat, const char *host, int port, const char *keytype, - char *keystr, const char *keydisp, char **key_fingerprints, bool mismatch, + char *keystr, SeatDialogText *text, HelpCtx helpctx, void (*callback)(void *ctx, SeatPromptResult result), void *ctx); SeatPromptResult console_confirm_weak_crypto_primitive( Seat *seat, const char *algtype, const char *algname, @@ -1543,6 +1577,7 @@ StripCtrlChars *console_stripctrl_new( void console_set_trust_status(Seat *seat, bool trusted); bool console_can_set_trust_status(Seat *seat); bool console_has_mixed_input_stream(Seat *seat); +const SeatDialogPromptDescriptions *console_prompt_descriptions(Seat *seat); /* * Other centralised seat functions. diff --git a/ssh/common.c b/ssh/common.c index 424c8f71..d5e1d55f 100644 --- a/ssh/common.c +++ b/ssh/common.c @@ -924,10 +924,104 @@ SeatPromptResult verify_ssh_host_key( * The key is either missing from the cache, or does not match. * Either way, fall back to an interactive prompt from the Seat. */ - bool mismatch = (storage_status != 1); - return seat_confirm_ssh_host_key( - iseat, host, port, keytype, keystr, keydisp, fingerprints, mismatch, - callback, ctx); + SeatDialogText *text = seat_dialog_text_new(); + const SeatDialogPromptDescriptions *pds = + seat_prompt_descriptions(iseat.seat); + + FingerprintType fptype_default = + ssh2_pick_default_fingerprint(fingerprints); + + seat_dialog_text_append( + text, SDT_TITLE, "%s Security Alert", appname); + + if (storage_status == 1) { + seat_dialog_text_append( + text, SDT_PARA, "The host key is not cached for this server:"); + seat_dialog_text_append( + text, SDT_DISPLAY, "%s (port %d)", host, port); + seat_dialog_text_append( + text, SDT_PARA, "You have no guarantee that the server is the " + "computer you think it is."); + seat_dialog_text_append( + text, SDT_PARA, "The server's %s key fingerprint is:", keytype); + seat_dialog_text_append( + text, SDT_DISPLAY, "%s", fingerprints[fptype_default]); + } else { + seat_dialog_text_append( + text, SDT_SCARY_HEADING, "WARNING - POTENTIAL SECURITY BREACH!"); + seat_dialog_text_append( + text, SDT_PARA, "The host key does not match the one %s has " + "cached for this server:", appname); + seat_dialog_text_append( + text, SDT_DISPLAY, "%s (port %d)", host, port); + seat_dialog_text_append( + text, SDT_PARA, "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."); + seat_dialog_text_append( + text, SDT_PARA, "The new %s key fingerprint is:", keytype); + seat_dialog_text_append( + text, SDT_DISPLAY, "%s", fingerprints[fptype_default]); + } + + /* The above text is printed even in batch mode. Here's where we stop if + * we can't present interactive prompts. */ + seat_dialog_text_append( + text, SDT_BATCH_ABORT, "Connection abandoned."); + + HelpCtx helpctx; + + if (storage_status == 1) { + seat_dialog_text_append( + text, SDT_PARA, "If you trust this host, %s to add the key to " + "%s's cache and carry on connecting.", + pds->hk_accept_action, appname); + seat_dialog_text_append( + text, SDT_PARA, "If you want to carry on connecting just once, " + "without adding the key to the cache, %s.", + pds->hk_connect_once_action); + seat_dialog_text_append( + text, SDT_PARA, "If you do not trust this host, %s to abandon the " + "connection.", pds->hk_cancel_action); + seat_dialog_text_append( + text, SDT_PROMPT, "Store key in cache?"); + helpctx = HELPCTX(errors_hostkey_absent); + } else { + seat_dialog_text_append( + text, SDT_PARA, "If you were expecting this change and trust the " + "new key, %s to update %s's cache and carry on connecting.", + pds->hk_accept_action, appname); + seat_dialog_text_append( + text, SDT_PARA, "If you want to carry on connecting but without " + "updating the cache, %s.", pds->hk_connect_once_action); + seat_dialog_text_append( + text, SDT_PARA, "If you want to abandon the connection " + "completely, %s to cancel. %s is the ONLY guaranteed safe choice.", + pds->hk_cancel_action, pds->hk_cancel_action_Participle); + seat_dialog_text_append( + text, SDT_PROMPT, "Update cached key?"); + helpctx = HELPCTX(errors_hostkey_changed); + } + + seat_dialog_text_append(text, SDT_MORE_INFO_KEY, + "Full text of host's public key"); + seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_BLOB, "%s", keydisp); + + if (fingerprints[SSH_FPTYPE_SHA256]) { + seat_dialog_text_append(text, SDT_MORE_INFO_KEY, "SHA256 fingerprint"); + seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "%s", + fingerprints[SSH_FPTYPE_SHA256]); + } + if (fingerprints[SSH_FPTYPE_MD5]) { + seat_dialog_text_append(text, SDT_MORE_INFO_KEY, "MD5 fingerprint"); + seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "%s", + fingerprints[SSH_FPTYPE_MD5]); + } + + SeatPromptResult toret = seat_confirm_ssh_host_key( + iseat, host, port, keytype, keystr, text, helpctx, callback, ctx); + seat_dialog_text_free(text); + return toret; } /* ---------------------------------------------------------------------- diff --git a/ssh/server.c b/ssh/server.c index f69bdd83..188426b3 100644 --- a/ssh/server.c +++ b/ssh/server.c @@ -122,6 +122,7 @@ static const SeatVtable server_seat_vt = { .confirm_ssh_host_key = nullseat_confirm_ssh_host_key, .confirm_weak_crypto_primitive = server_confirm_weak_crypto_primitive, .confirm_weak_cached_hostkey = server_confirm_weak_cached_hostkey, + .prompt_descriptions = nullseat_prompt_descriptions, .is_utf8 = nullseat_is_never_utf8, .echoedit_update = nullseat_echoedit_update, .get_x_display = nullseat_get_x_display, diff --git a/ssh/sesschan.c b/ssh/sesschan.c index 978ffb9a..b8f9311e 100644 --- a/ssh/sesschan.c +++ b/ssh/sesschan.c @@ -199,6 +199,7 @@ static const SeatVtable sesschan_seat_vt = { .confirm_ssh_host_key = nullseat_confirm_ssh_host_key, .confirm_weak_crypto_primitive = nullseat_confirm_weak_crypto_primitive, .confirm_weak_cached_hostkey = nullseat_confirm_weak_cached_hostkey, + .prompt_descriptions = nullseat_prompt_descriptions, .is_utf8 = nullseat_is_never_utf8, .echoedit_update = nullseat_echoedit_update, .get_x_display = nullseat_get_x_display, diff --git a/unix/console.c b/unix/console.c index cc6c9853..75e51a7f 100644 --- a/unix/console.c +++ b/unix/console.c @@ -104,43 +104,53 @@ static int block_and_read(int fd, void *buf, size_t len) SeatPromptResult console_confirm_ssh_host_key( Seat *seat, const char *host, int port, const char *keytype, - char *keystr, const char *keydisp, char **fingerprints, bool mismatch, + char *keystr, SeatDialogText *text, HelpCtx helpctx, void (*callback)(void *ctx, SeatPromptResult result), void *ctx) { char line[32]; struct termios cf; - char *common; - const char *intro, *prompt; - - FingerprintType fptype_default = - ssh2_pick_default_fingerprint(fingerprints); - - if (mismatch) { /* key was different */ - common = hk_wrongmsg_common(host, port, keytype, - fingerprints[fptype_default]); - intro = hk_wrongmsg_interactive_intro; - prompt = hk_wrongmsg_interactive_prompt; - } else { /* key was absent */ - common = hk_absentmsg_common(host, port, keytype, - fingerprints[fptype_default]); - intro = hk_absentmsg_interactive_intro; - prompt = hk_absentmsg_interactive_prompt; - } + const char *prompt; + + stdio_sink errsink[1]; + stdio_sink_init(errsink, stderr); premsg(&cf); - fputs(common, stderr); - sfree(common); - if (console_batch_mode) { - fputs(console_abandoned_msg, stderr); - postmsg(&cf); - return SPR_SW_ABORT("Cannot confirm a host key in batch mode"); + for (SeatDialogTextItem *item = text->items, + *end = item+text->nitems; item < end; item++) { + switch (item->type) { + case SDT_PARA: + wordwrap(BinarySink_UPCAST(errsink), + ptrlen_from_asciz(item->text), 60); + fputc('\n', stderr); + break; + case SDT_DISPLAY: + fprintf(stderr, " %s\n", item->text); + break; + case SDT_SCARY_HEADING: + /* Can't change font size or weight in this context */ + fprintf(stderr, "%s\n", item->text); + break; + case SDT_BATCH_ABORT: + if (console_batch_mode) { + fprintf(stderr, "%s\n", item->text); + fflush(stderr); + postmsg(&cf); + return SPR_SW_ABORT("Cannot confirm a host key in batch mode"); + } + break; + case SDT_PROMPT: + prompt = item->text; + break; + default: + break; + } } - fputs(intro, stderr); - fflush(stderr); while (true) { - fputs(prompt, stderr); + fprintf(stderr, + "%s (y/n, Return cancels connection, i for more info) ", + prompt); fflush(stderr); struct termios oldmode, newmode; @@ -154,13 +164,22 @@ SeatPromptResult console_confirm_ssh_host_key( tcsetattr(0, TCSANOW, &oldmode); if (line[0] == 'i' || line[0] == 'I') { - fprintf(stderr, "Full public key:\n%s\n", keydisp); - if (fingerprints[SSH_FPTYPE_SHA256]) - fprintf(stderr, "SHA256 key fingerprint:\n%s\n", - fingerprints[SSH_FPTYPE_SHA256]); - if (fingerprints[SSH_FPTYPE_MD5]) - fprintf(stderr, "MD5 key fingerprint:\n%s\n", - fingerprints[SSH_FPTYPE_MD5]); + for (SeatDialogTextItem *item = text->items, + *end = item+text->nitems; item < end; item++) { + switch (item->type) { + case SDT_MORE_INFO_KEY: + fprintf(stderr, "%s", item->text); + break; + case SDT_MORE_INFO_VALUE_SHORT: + fprintf(stderr, ": %s\n", item->text); + break; + case SDT_MORE_INFO_VALUE_BLOB: + fprintf(stderr, ":\n%s\n", item->text); + break; + default: + break; + } + } } else { break; } diff --git a/unix/dialog.c b/unix/dialog.c index 161f393d..602991f1 100644 --- a/unix/dialog.c +++ b/unix/dialog.c @@ -3592,41 +3592,22 @@ static void more_info_button_clicked(GtkButton *button, gpointer vctx) &buttons_ok, more_info_closed, ctx); } +const SeatDialogPromptDescriptions *gtk_seat_prompt_descriptions(Seat *seat) +{ + static const SeatDialogPromptDescriptions descs = { + .hk_accept_action = "press \"Accept\"", + .hk_connect_once_action = "press \"Connect Once\"", + .hk_cancel_action = "press \"Cancel\"", + .hk_cancel_action_Participle = "Pressing \"Cancel\"", + }; + return &descs; +} + SeatPromptResult gtk_seat_confirm_ssh_host_key( Seat *seat, const char *host, int port, const char *keytype, - char *keystr, const char *keydisp, char **fingerprints, bool mismatch, + char *keystr, SeatDialogText *text, HelpCtx helpctx, void (*callback)(void *ctx, SeatPromptResult result), void *ctx) { - static const char absenttxt[] = - "The host key is not cached for this server:\n\n" - "%s (port %d)\n\n" - "You have no guarantee that the server is the computer " - "you think it is.\n" - "The server's %s key fingerprint is:\n\n" - "%s\n\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 host key does not match the one PuTTY has cached " - "for this server:\n\n" - "%s (port %d)\n\n" - "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\n" - "%s\n\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."; static const struct message_box_button button_array_hostkey[] = { {"Accept", 'a', 0, 2}, {"Connect Once", 'o', 0, 1}, @@ -3636,17 +3617,40 @@ SeatPromptResult gtk_seat_confirm_ssh_host_key( button_array_hostkey, lenof(button_array_hostkey), }; - char *text; - struct confirm_ssh_host_key_dialog_ctx *result_ctx; - GtkWidget *mainwin, *msgbox; + const char *dlg_title = NULL; + strbuf *dlg_text = strbuf_new(); + int width = string_width("default dialog width determination string"); - FingerprintType fptype_default = - ssh2_pick_default_fingerprint(fingerprints); + for (SeatDialogTextItem *item = text->items, + *end = item + text->nitems; item < end; item++) { + switch (item->type) { + case SDT_PARA: + put_fmt(dlg_text, "%s\n\n", item->text); + break; + case SDT_DISPLAY: { + put_fmt(dlg_text, "%s\n\n", item->text); + int thiswidth = string_width(item->text); + if (width < thiswidth) + width = thiswidth; + break; + } + case SDT_SCARY_HEADING: + /* Can't change font size or weight in this context */ + put_fmt(dlg_text, "%s\n\n", item->text); + break; + case SDT_TITLE: + dlg_title = item->text; + break; + default: + break; + } + } + while (strbuf_chomp(dlg_text, '\n')); - text = dupprintf((mismatch ? wrongtxt : absenttxt), host, port, - keytype, fingerprints[fptype_default]); + GtkWidget *mainwin, *msgbox; - result_ctx = snew(struct confirm_ssh_host_key_dialog_ctx); + struct confirm_ssh_host_key_dialog_ctx *result_ctx = + snew(struct confirm_ssh_host_key_dialog_ctx); result_ctx->callback = callback; result_ctx->callback_ctx = ctx; result_ctx->host = dupstr(host); @@ -3658,41 +3662,48 @@ SeatPromptResult gtk_seat_confirm_ssh_host_key( mainwin = GTK_WIDGET(gtk_seat_get_window(seat)); GtkWidget *more_info_button = NULL; msgbox = create_message_box_general( - mainwin, "PuTTY Security Alert", text, - string_width(fingerprints[fptype_default]), true, + mainwin, dlg_title, dlg_text->s, width, true, &buttons_hostkey, confirm_ssh_host_key_result_callback, result_ctx, add_more_info_button, &more_info_button); result_ctx->main_dialog = msgbox; result_ctx->more_info_dialog = NULL; - strbuf *sb = strbuf_new(); - if (fingerprints[SSH_FPTYPE_SHA256]) - put_fmt(sb, "SHA256 fingerprint: %s\n", - fingerprints[SSH_FPTYPE_SHA256]); - if (fingerprints[SSH_FPTYPE_MD5]) - put_fmt(sb, "MD5 fingerprint: %s\n", - fingerprints[SSH_FPTYPE_MD5]); - put_fmt(sb, "Full text of host's public key:"); - /* We have to manually wrap the public key, or else the GtkLabel - * will resize itself to accommodate the longest word, which will - * lead to a hilariously wide message box. */ - for (const char *p = keydisp, *q = p + strlen(p); p < q ;) { - size_t linelen = q-p; - if (linelen > 72) - linelen = 72; - put_byte(sb, '\n'); - put_data(sb, p, linelen); - p += linelen; + strbuf *moreinfo = strbuf_new(); + for (SeatDialogTextItem *item = text->items, + *end = item + text->nitems; item < end; item++) { + switch (item->type) { + case SDT_MORE_INFO_KEY: + put_fmt(moreinfo, "%s", item->text); + break; + case SDT_MORE_INFO_VALUE_SHORT: + put_fmt(moreinfo, ": %s\n", item->text); + break; + case SDT_MORE_INFO_VALUE_BLOB: + /* We have to manually wrap the public key, or else the GtkLabel + * will resize itself to accommodate the longest word, which will + * lead to a hilariously wide message box. */ + for (const char *p = item->text, *q = p + strlen(p); p < q ;) { + size_t linelen = q-p; + if (linelen > 72) + linelen = 72; + put_byte(moreinfo, '\n'); + put_data(moreinfo, p, linelen); + p += linelen; + } + break; + default: + break; + } } - result_ctx->more_info = strbuf_to_str(sb); + result_ctx->more_info = strbuf_to_str(moreinfo); g_signal_connect(G_OBJECT(more_info_button), "clicked", G_CALLBACK(more_info_button_clicked), result_ctx); register_dialog(seat, DIALOG_SLOT_NETWORK_PROMPT, msgbox); - sfree(text); + strbuf_free(dlg_text); return SPR_INCOMPLETE; /* dialog still in progress */ } diff --git a/unix/platform.h b/unix/platform.h index 31da1294..a3cbfd13 100644 --- a/unix/platform.h +++ b/unix/platform.h @@ -222,7 +222,7 @@ int gtkdlg_askappend(Seat *seat, Filename *filename, void (*callback)(void *ctx, int result), void *ctx); SeatPromptResult gtk_seat_confirm_ssh_host_key( Seat *seat, const char *host, int port, const char *keytype, - char *keystr, const char *keydisp, char **fingerprints, bool mismatch, + char *keystr, SeatDialogText *text, HelpCtx helpctx, void (*callback)(void *ctx, SeatPromptResult result), void *ctx); SeatPromptResult gtk_seat_confirm_weak_crypto_primitive( Seat *seat, const char *algtype, const char *algname, @@ -230,6 +230,7 @@ SeatPromptResult gtk_seat_confirm_weak_crypto_primitive( SeatPromptResult gtk_seat_confirm_weak_cached_hostkey( Seat *seat, const char *algname, const char *betteralgs, void (*callback)(void *ctx, SeatPromptResult result), void *ctx); +const SeatDialogPromptDescriptions *gtk_seat_prompt_descriptions(Seat *seat); #ifdef MAY_REFER_TO_GTK_IN_HEADERS struct message_box_button { const char *title; diff --git a/unix/plink.c b/unix/plink.c index 9e109f01..4ab371a0 100644 --- a/unix/plink.c +++ b/unix/plink.c @@ -408,6 +408,7 @@ static const SeatVtable plink_seat_vt = { .confirm_ssh_host_key = console_confirm_ssh_host_key, .confirm_weak_crypto_primitive = console_confirm_weak_crypto_primitive, .confirm_weak_cached_hostkey = console_confirm_weak_cached_hostkey, + .prompt_descriptions = console_prompt_descriptions, .is_utf8 = nullseat_is_never_utf8, .echoedit_update = plink_echoedit_update, .get_x_display = nullseat_get_x_display, diff --git a/unix/window.c b/unix/window.c index 9d33904a..f8232e9a 100644 --- a/unix/window.c +++ b/unix/window.c @@ -429,6 +429,7 @@ static const SeatVtable gtk_seat_vt = { .confirm_ssh_host_key = gtk_seat_confirm_ssh_host_key, .confirm_weak_crypto_primitive = gtk_seat_confirm_weak_crypto_primitive, .confirm_weak_cached_hostkey = gtk_seat_confirm_weak_cached_hostkey, + .prompt_descriptions = gtk_seat_prompt_descriptions, .is_utf8 = gtk_seat_is_utf8, .echoedit_update = nullseat_echoedit_update, .get_x_display = gtk_seat_get_x_display, diff --git a/utils/CMakeLists.txt b/utils/CMakeLists.txt index 34644cbd..e717af49 100644 --- a/utils/CMakeLists.txt +++ b/utils/CMakeLists.txt @@ -53,6 +53,7 @@ add_sources_from_current_dir(utils ptrlen.c read_file_into.c seat_connection_fatal.c + seat_dialog_text.c sessprep.c sk_free_peer_info.c smemclr.c diff --git a/utils/nullseat.c b/utils/nullseat.c index ba09839e..37cb0f4c 100644 --- a/utils/nullseat.c +++ b/utils/nullseat.c @@ -22,7 +22,7 @@ char *nullseat_get_ttymode(Seat *seat, const char *mode) { return NULL; } void nullseat_set_busy_status(Seat *seat, BusyStatus status) {} SeatPromptResult nullseat_confirm_ssh_host_key( Seat *seat, const char *host, int port, const char *keytype, - char *keystr, const char *keydisp, char **key_fingerprints, bool mismatch, + char *keystr, SeatDialogText *text, HelpCtx helpctx, void (*callback)(void *ctx, SeatPromptResult result), void *ctx) { return SPR_SW_ABORT("this seat can't handle interactive prompts"); } SeatPromptResult nullseat_confirm_weak_crypto_primitive( @@ -52,3 +52,14 @@ bool nullseat_verbose_yes(Seat *seat) { return true; } bool nullseat_interactive_no(Seat *seat) { return false; } bool nullseat_interactive_yes(Seat *seat) { return true; } bool nullseat_get_cursor_position(Seat *seat, int *x, int *y) { return false; } + +const SeatDialogPromptDescriptions *nullseat_prompt_descriptions(Seat *seat) +{ + static const SeatDialogPromptDescriptions descs = { + .hk_accept_action = "", + .hk_connect_once_action = "", + .hk_cancel_action = "", + .hk_cancel_action_Participle = "", + }; + return &descs; +} diff --git a/utils/seat_dialog_text.c b/utils/seat_dialog_text.c new file mode 100644 index 00000000..03890b93 --- /dev/null +++ b/utils/seat_dialog_text.c @@ -0,0 +1,41 @@ +/* + * Helper routines for dealing with SeatDialogText structures. + */ + +#include + +#include "putty.h" + +SeatDialogText *seat_dialog_text_new(void) +{ + SeatDialogText *sdt = snew(SeatDialogText); + sdt->nitems = sdt->itemsize = 0; + sdt->items = NULL; + return sdt; +} + +void seat_dialog_text_free(SeatDialogText *sdt) +{ + for (size_t i = 0; i < sdt->nitems; i++) + sfree(sdt->items[i].text); + sfree(sdt->items); + sfree(sdt); +} + +static void seat_dialog_text_append_v( + SeatDialogText *sdt, SeatDialogTextType type, const char *fmt, va_list ap) +{ + sgrowarray(sdt->items, sdt->itemsize, sdt->nitems); + SeatDialogTextItem *item = &sdt->items[sdt->nitems++]; + item->type = type; + item->text = dupvprintf(fmt, ap); +} + +void seat_dialog_text_append(SeatDialogText *sdt, SeatDialogTextType type, + const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + seat_dialog_text_append_v(sdt, type, fmt, ap); + va_end(ap); +} diff --git a/utils/tempseat.c b/utils/tempseat.c index 785c00c2..ad37b0e3 100644 --- a/utils/tempseat.c +++ b/utils/tempseat.c @@ -204,6 +204,17 @@ static bool tempseat_has_mixed_input_stream(Seat *seat) return seat_has_mixed_input_stream(ts->realseat); } +static const SeatDialogPromptDescriptions *tempseat_prompt_descriptions( + Seat *seat) +{ + /* It might be OK to put this in the 'unreachable' category, but I + * think it's equally good to put it here, which allows for + * someone _preparing_ a prompt right now that they intend to + * present once the TempSeat has given way to the real one. */ + TempSeat *ts = container_of(seat, TempSeat, seat); + return seat_prompt_descriptions(ts->realseat); +} + /* ---------------------------------------------------------------------- * Methods that should never be called on a TempSeat, so we can put an * unreachable() in them. @@ -237,7 +248,7 @@ static size_t tempseat_banner(Seat *seat, const void *data, size_t len) static SeatPromptResult tempseat_confirm_ssh_host_key( Seat *seat, const char *host, int port, const char *keytype, - char *keystr, const char *keydisp, char **key_fingerprints, bool mismatch, + char *keystr, SeatDialogText *text, HelpCtx helpctx, void (*callback)(void *ctx, SeatPromptResult result), void *ctx) { unreachable("confirm_ssh_host_key should never be called on TempSeat"); @@ -322,6 +333,7 @@ static const struct SeatVtable tempseat_vt = { .confirm_ssh_host_key = tempseat_confirm_ssh_host_key, .confirm_weak_crypto_primitive = tempseat_confirm_weak_crypto_primitive, .confirm_weak_cached_hostkey = tempseat_confirm_weak_cached_hostkey, + .prompt_descriptions = tempseat_prompt_descriptions, .is_utf8 = tempseat_is_utf8, .echoedit_update = tempseat_echoedit_update, .get_x_display = tempseat_get_x_display, diff --git a/windows/console.c b/windows/console.c index 81ab7d06..3cc41364 100644 --- a/windows/console.c +++ b/windows/console.c @@ -34,44 +34,52 @@ void console_print_error_msg(const char *prefix, const char *msg) SeatPromptResult console_confirm_ssh_host_key( Seat *seat, const char *host, int port, const char *keytype, - char *keystr, const char *keydisp, char **fingerprints, bool mismatch, + char *keystr, SeatDialogText *text, HelpCtx helpctx, void (*callback)(void *ctx, SeatPromptResult result), void *ctx) { HANDLE hin; DWORD savemode, i; - char *common; - const char *intro, *prompt; + const char *prompt; - char line[32]; - - FingerprintType fptype_default = - ssh2_pick_default_fingerprint(fingerprints); - - if (mismatch) { /* key was different */ - common = hk_wrongmsg_common(host, port, keytype, - fingerprints[fptype_default]); - intro = hk_wrongmsg_interactive_intro; - prompt = hk_wrongmsg_interactive_prompt; - } else { /* key was absent */ - common = hk_absentmsg_common(host, port, keytype, - fingerprints[fptype_default]); - intro = hk_absentmsg_interactive_intro; - prompt = hk_absentmsg_interactive_prompt; - } + stdio_sink errsink[1]; + stdio_sink_init(errsink, stderr); - fputs(common, stderr); - sfree(common); + char line[32]; - if (console_batch_mode) { - fputs(console_abandoned_msg, stderr); - return SPR_SW_ABORT("Cannot confirm a host key in batch mode"); + for (SeatDialogTextItem *item = text->items, + *end = item+text->nitems; item < end; item++) { + switch (item->type) { + case SDT_PARA: + wordwrap(BinarySink_UPCAST(errsink), + ptrlen_from_asciz(item->text), 60); + fputc('\n', stderr); + break; + case SDT_DISPLAY: + fprintf(stderr, " %s\n", item->text); + break; + case SDT_SCARY_HEADING: + /* Can't change font size or weight in this context */ + fprintf(stderr, "%s\n", item->text); + break; + case SDT_BATCH_ABORT: + if (console_batch_mode) { + fprintf(stderr, "%s\n", item->text); + fflush(stderr); + return SPR_SW_ABORT("Cannot confirm a host key in batch mode"); + } + break; + case SDT_PROMPT: + prompt = item->text; + break; + default: + break; + } } - fputs(intro, stderr); - fflush(stderr); - while (true) { - fputs(prompt, stderr); + fprintf(stderr, + "%s (y/n, Return cancels connection, i for more info) ", + prompt); fflush(stderr); line[0] = '\0'; /* fail safe if ReadFile returns no data */ @@ -84,13 +92,22 @@ SeatPromptResult console_confirm_ssh_host_key( SetConsoleMode(hin, savemode); if (line[0] == 'i' || line[0] == 'I') { - fprintf(stderr, "Full public key:\n%s\n", keydisp); - if (fingerprints[SSH_FPTYPE_SHA256]) - fprintf(stderr, "SHA256 key fingerprint:\n%s\n", - fingerprints[SSH_FPTYPE_SHA256]); - if (fingerprints[SSH_FPTYPE_MD5]) - fprintf(stderr, "MD5 key fingerprint:\n%s\n", - fingerprints[SSH_FPTYPE_MD5]); + for (SeatDialogTextItem *item = text->items, + *end = item+text->nitems; item < end; item++) { + switch (item->type) { + case SDT_MORE_INFO_KEY: + fprintf(stderr, "%s", item->text); + break; + case SDT_MORE_INFO_VALUE_SHORT: + fprintf(stderr, ": %s\n", item->text); + break; + case SDT_MORE_INFO_VALUE_BLOB: + fprintf(stderr, ":\n%s\n", item->text); + break; + default: + break; + } + } } else { break; } diff --git a/windows/dialog.c b/windows/dialog.c index f2c309b8..85f16348 100644 --- a/windows/dialog.c +++ b/windows/dialog.c @@ -842,14 +842,8 @@ void showabout(HWND hwnd) } struct hostkey_dialog_ctx { - const char *const *keywords; - const char *const *values; - const char *host; - int port; - FingerprintType fptype_default; - char **fingerprints; - const char *keydisp; - LPCTSTR iconid; + SeatDialogText *text; + bool has_title; const char *helpctx; }; @@ -860,16 +854,99 @@ static INT_PTR HostKeyMoreInfoProc(HWND hwnd, UINT msg, WPARAM wParam, switch (msg) { case WM_INITDIALOG: { + int index = 100, y = 12; - if (ctx->fingerprints[SSH_FPTYPE_SHA256]) - SetDlgItemText(hwnd, IDC_HKI_SHA256, - ctx->fingerprints[SSH_FPTYPE_SHA256]); - if (ctx->fingerprints[SSH_FPTYPE_MD5]) - SetDlgItemText(hwnd, IDC_HKI_MD5, - ctx->fingerprints[SSH_FPTYPE_MD5]); + WPARAM font = SendMessage(hwnd, WM_GETFONT, 0, 0); - SetDlgItemText(hwnd, IDA_TEXT, ctx->keydisp); + const char *key = NULL; + for (SeatDialogTextItem *item = ctx->text->items, + *end = item + ctx->text->nitems; item < end; item++) { + switch (item->type) { + case SDT_MORE_INFO_KEY: + key = item->text; + break; + case SDT_MORE_INFO_VALUE_SHORT: + case SDT_MORE_INFO_VALUE_BLOB: { + RECT rk, rv; + DWORD editstyle = WS_CHILD | WS_VISIBLE | WS_TABSTOP | + ES_AUTOHSCROLL | ES_READONLY; + if (item->type == SDT_MORE_INFO_VALUE_BLOB) { + rk.left = 12; + rk.right = 376; + rk.top = y; + rk.bottom = 8; + y += 10; + + editstyle |= ES_MULTILINE; + rv.left = 12; + rv.right = 376; + rv.top = y; + rv.bottom = 64; + y += 68; + } else { + rk.left = 12; + rk.right = 80; + rk.top = y+2; + rk.bottom = 8; + + rv.left = 100; + rv.right = 288; + rv.top = y; + rv.bottom = 12; + y += 16; + } + + MapDialogRect(hwnd, &rk); + HWND ctl = CreateWindowEx( + 0, "STATIC", key, WS_CHILD | WS_VISIBLE, + rk.left, rk.top, rk.right, rk.bottom, + hwnd, (HMENU)(ULONG_PTR)index++, hinst, NULL); + SendMessage(ctl, WM_SETFONT, font, MAKELPARAM(true, 0)); + + MapDialogRect(hwnd, &rv); + ctl = CreateWindowEx( + WS_EX_CLIENTEDGE, "EDIT", item->text, editstyle, + rv.left, rv.top, rv.right, rv.bottom, + hwnd, (HMENU)(ULONG_PTR)index++, hinst, NULL); + SendMessage(ctl, WM_SETFONT, font, MAKELPARAM(true, 0)); + break; + } + default: + break; + } + } + + /* + * Now resize the overall window, and move the Close button at + * the bottom. + */ + RECT r; + r.left = 176; + r.top = y + 10; + r.right = r.bottom = 0; + MapDialogRect(hwnd, &r); + HWND ctl = GetDlgItem(hwnd, IDOK); + SetWindowPos(ctl, NULL, r.left, r.top, 0, 0, + SWP_NOSIZE | SWP_NOREDRAW | SWP_NOZORDER); + + r.left = r.top = r.right = 0; + r.bottom = 300; + MapDialogRect(hwnd, &r); + int oldheight = r.bottom; + + r.left = r.top = r.right = 0; + r.bottom = y + 30; + MapDialogRect(hwnd, &r); + int newheight = r.bottom; + + GetWindowRect(hwnd, &r); + + SetWindowPos(hwnd, NULL, 0, 0, r.right - r.left, + r.bottom - r.top + newheight - oldheight, + SWP_NOMOVE | SWP_NOREDRAW | SWP_NOZORDER); + + ShowWindow(hwnd, SW_SHOWNORMAL); return 1; } case WM_COMMAND: @@ -893,44 +970,106 @@ static INT_PTR HostKeyDialogProc(HWND hwnd, UINT msg, switch (msg) { case WM_INITDIALOG: { - strbuf *sb = strbuf_new(); - for (int id = 100;; id++) { - char buf[256]; - - if (!GetDlgItemText(hwnd, id, buf, (int)lenof(buf))) + strbuf *dlg_text = strbuf_new(); + const char *dlg_title = ""; + ctx->has_title = false; + LPCTSTR iconid = IDI_QUESTION; + ctx->helpctx = WINHELP_CTX_errors_hostkey_absent; + + for (SeatDialogTextItem *item = ctx->text->items, + *end = item + ctx->text->nitems; item < end; item++) { + switch (item->type) { + case SDT_PARA: + put_fmt(dlg_text, "%s\r\n\r\n", item->text); + break; + case SDT_DISPLAY: + put_fmt(dlg_text, "%s\r\n\r\n", item->text); + break; + case SDT_SCARY_HEADING: + SetDlgItemText(hwnd, IDC_HK_TITLE, item->text); + iconid = IDI_WARNING; + ctx->helpctx = WINHELP_CTX_errors_hostkey_changed; + ctx->has_title = true; + break; + case SDT_TITLE: + dlg_title = item->text; + break; + default: break; - - strbuf_clear(sb); - for (const char *p = buf; *p ;) { - if (*p == '{') { - for (size_t i = 0; ctx->keywords[i]; i++) { - if (strstartswith(p, ctx->keywords[i])) { - p += strlen(ctx->keywords[i]); - put_dataz(sb, ctx->values[i]); - goto matched; - } - } - } else { - put_byte(sb, *p++); - } - matched:; } + } + while (strbuf_chomp(dlg_text, '\r') || strbuf_chomp(dlg_text, '\n')); + + SetDlgItemText(hwnd, IDC_HK_TEXT, dlg_text->s); + MakeDlgItemBorderless(hwnd, IDC_HK_TEXT); + strbuf_free(dlg_text); + + SetWindowText(hwnd, dlg_title); + + if (!ctx->has_title) { + HWND item = GetDlgItem(hwnd, IDC_HK_TITLE); + if (item) + DestroyWindow(item); + } - SetDlgItemText(hwnd, id, sb->s); + /* + * Find out how tall the text in the edit control really ended + * up (after line wrapping), and adjust the height of the + * whole box to match it. + */ + int height = SendDlgItemMessage(hwnd, IDC_HK_TEXT, + EM_GETLINECOUNT, 0, 0); + height *= 8; /* height of a text line, by definition of dialog units */ + + int edittop = ctx->has_title ? 40 : 20; + + RECT r; + r.left = 40; + r.top = edittop; + r.right = 290; + r.bottom = height; + MapDialogRect(hwnd, &r); + SetWindowPos(GetDlgItem(hwnd, IDC_HK_TEXT), NULL, + r.left, r.top, r.right, r.bottom, + SWP_NOREDRAW | SWP_NOZORDER); + + static const struct { + int id, x; + } buttons[] = { + { IDCANCEL, 288 }, + { IDC_HK_ACCEPT, 168 }, + { IDC_HK_ONCE, 216 }, + { IDC_HK_MOREINFO, 60 }, + { IDHELP, 12 }, + }; + for (size_t i = 0; i < lenof(buttons); i++) { + HWND ctl = GetDlgItem(hwnd, buttons[i].id); + r.left = buttons[i].x; + r.top = edittop + height + 20; + r.right = r.bottom = 0; + MapDialogRect(hwnd, &r); + SetWindowPos(ctl, NULL, r.left, r.top, 0, 0, + SWP_NOSIZE | SWP_NOREDRAW | SWP_NOZORDER); } - strbuf_free(sb); - char *hostport = dupprintf("%s (port %d)", ctx->host, ctx->port); - SetDlgItemText(hwnd, IDC_HK_HOST, hostport); - sfree(hostport); - MakeDlgItemBorderless(hwnd, IDC_HK_HOST); + r.left = r.top = r.right = 0; + r.bottom = 240; + MapDialogRect(hwnd, &r); + int oldheight = r.bottom; + + r.left = r.top = r.right = 0; + r.bottom = edittop + height + 40; + MapDialogRect(hwnd, &r); + int newheight = r.bottom; + + GetWindowRect(hwnd, &r); - SetDlgItemText(hwnd, IDC_HK_FINGERPRINT, - ctx->fingerprints[ctx->fptype_default]); - MakeDlgItemBorderless(hwnd, IDC_HK_FINGERPRINT); + SetWindowPos(hwnd, NULL, 0, 0, r.right - r.left, + r.bottom - r.top + newheight - oldheight, + SWP_NOMOVE | SWP_NOREDRAW | SWP_NOZORDER); HANDLE icon = LoadImage( - NULL, ctx->iconid, IMAGE_ICON, + NULL, iconid, IMAGE_ICON, GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON), LR_SHARED); SendDlgItemMessage(hwnd, IDC_HK_ICON, STM_SETICON, (WPARAM)icon, 0); @@ -941,13 +1080,16 @@ static INT_PTR HostKeyDialogProc(HWND hwnd, UINT msg, DestroyWindow(item); } + ShowWindow(hwnd, SW_SHOWNORMAL); + return 1; } case WM_CTLCOLORSTATIC: { HDC hdc = (HDC)wParam; HWND control = (HWND)lParam; - if (GetWindowLongPtr(control, GWLP_ID) == IDC_HK_TITLE) { + if (GetWindowLongPtr(control, GWLP_ID) == IDC_HK_TITLE && + ctx->has_title) { SetBkMode(hdc, TRANSPARENT); HFONT prev_font = (HFONT)SelectObject( hdc, (HFONT)GetStockObject(SYSTEM_FONT)); @@ -988,34 +1130,30 @@ static INT_PTR HostKeyDialogProc(HWND hwnd, UINT msg, return 0; } +const SeatDialogPromptDescriptions *win_seat_prompt_descriptions(Seat *seat) +{ + static const SeatDialogPromptDescriptions descs = { + .hk_accept_action = "press \"Accept\"", + .hk_connect_once_action = "press \"Connect Once\"", + .hk_cancel_action = "press \"Cancel\"", + .hk_cancel_action_Participle = "Pressing \"Cancel\"", + }; + return &descs; +} + SeatPromptResult win_seat_confirm_ssh_host_key( Seat *seat, const char *host, int port, const char *keytype, - char *keystr, const char *keydisp, char **fingerprints, bool mismatch, - void (*callback)(void *ctx, SeatPromptResult result), void *vctx) + char *keystr, SeatDialogText *text, HelpCtx helpctx, + void (*callback)(void *ctx, SeatPromptResult result), void *cbctx) { WinGuiSeat *wgs = container_of(seat, WinGuiSeat, seat); - static const char *const keywords[] = - { "{KEYTYPE}", "{APPNAME}", NULL }; - - const char *values[2]; - values[0] = keytype; - values[1] = appname; - struct hostkey_dialog_ctx ctx[1]; - ctx->keywords = keywords; - ctx->values = values; - ctx->fingerprints = fingerprints; - ctx->fptype_default = ssh2_pick_default_fingerprint(fingerprints); - ctx->keydisp = keydisp; - ctx->iconid = (mismatch ? IDI_WARNING : IDI_QUESTION); - ctx->helpctx = (mismatch ? WINHELP_CTX_errors_hostkey_changed : - WINHELP_CTX_errors_hostkey_absent); - ctx->host = host; - ctx->port = port; - int dlgid = (mismatch ? IDD_HK_WRONG : IDD_HK_ABSENT); + ctx->text = text; + ctx->helpctx = helpctx; + int mbret = ShinyDialogBox( - hinst, MAKEINTRESOURCE(dlgid), "PuTTYHostKeyDialog", + hinst, MAKEINTRESOURCE(IDD_HOSTKEY), "PuTTYHostKeyDialog", wgs->term_hwnd, HostKeyDialogProc, ctx); assert(mbret==IDC_HK_ACCEPT || mbret==IDC_HK_ONCE || mbret==IDCANCEL); if (mbret == IDC_HK_ACCEPT) { diff --git a/windows/platform.h b/windows/platform.h index c33ca1c4..d0510faf 100644 --- a/windows/platform.h +++ b/windows/platform.h @@ -226,7 +226,7 @@ int has_embedded_chm(void); /* 1 = yes, 0 = no, -1 = N/A */ */ SeatPromptResult win_seat_confirm_ssh_host_key( Seat *seat, const char *host, int port, const char *keytype, - char *keystr, const char *keydisp, char **key_fingerprints, bool mismatch, + char *keystr, SeatDialogText *text, HelpCtx helpctx, void (*callback)(void *ctx, SeatPromptResult result), void *ctx); SeatPromptResult win_seat_confirm_weak_crypto_primitive( Seat *seat, const char *algtype, const char *algname, @@ -234,6 +234,7 @@ SeatPromptResult win_seat_confirm_weak_crypto_primitive( SeatPromptResult win_seat_confirm_weak_cached_hostkey( Seat *seat, const char *algname, const char *betteralgs, void (*callback)(void *ctx, SeatPromptResult result), void *ctx); +const SeatDialogPromptDescriptions *win_seat_prompt_descriptions(Seat *seat); /* * Windows-specific clipboard helper function shared with dialog.c, diff --git a/windows/plink.c b/windows/plink.c index 94bf4f0c..4ae6cf4d 100644 --- a/windows/plink.c +++ b/windows/plink.c @@ -102,6 +102,7 @@ static const SeatVtable plink_seat_vt = { .confirm_ssh_host_key = console_confirm_ssh_host_key, .confirm_weak_crypto_primitive = console_confirm_weak_crypto_primitive, .confirm_weak_cached_hostkey = console_confirm_weak_cached_hostkey, + .prompt_descriptions = console_prompt_descriptions, .is_utf8 = nullseat_is_never_utf8, .echoedit_update = plink_echoedit_update, .get_x_display = nullseat_get_x_display, diff --git a/windows/putty-common.rc2 b/windows/putty-common.rc2 index b6ccb3d8..c6d403a5 100644 --- a/windows/putty-common.rc2 +++ b/windows/putty-common.rc2 @@ -57,78 +57,32 @@ BEGIN END /* No accelerators used */ -IDD_HK_ABSENT DIALOG DISCARDABLE 50, 50, 340, 160 +IDD_HOSTKEY DIALOG DISCARDABLE 50, 50, 340, 240 STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "PuTTY Security Alert" FONT 8, "MS Shell Dlg" CLASS "PuTTYHostKeyDialog" BEGIN - LTEXT "The host key is not cached for this server:", 100, 40, 20, 300, 8 - LTEXT "You have no guarantee that the server is the computer you think it is.", 101, 40, 40, 300, 8 - LTEXT "The server's {KEYTYPE} key fingerprint is:", 102, 40, 52, 300, 8 - LTEXT "If you trust this host, press ""Accept"" to add the key to {APPNAME}'s", 103, 40, 72, 300, 8 - LTEXT "cache and carry on connecting.", 104, 40, 80, 300, 8 - LTEXT "If you want to carry on connecting just once, without adding the key", 105, 40, 92, 300, 8 - LTEXT "to the cache, press ""Connect Once"".", 106, 40, 100, 300, 8 - LTEXT "If you do not trust this host, press ""Cancel"" to abandon the connection.", 107, 40, 112, 300, 8 - ICON "", IDC_HK_ICON, 10, 18, 0, 0 - PUSHBUTTON "&Cancel", IDCANCEL, 288, 140, 40, 14 - PUSHBUTTON "&Accept", IDC_HK_ACCEPT, 168, 140, 40, 14 - PUSHBUTTON "Connect &Once", IDC_HK_ONCE, 216, 140, 64, 14 - PUSHBUTTON "More &info...", IDC_HK_MOREINFO, 60, 140, 64, 14 - PUSHBUTTON "&Help", IDHELP, 12, 140, 40, 14 - - EDITTEXT IDC_HK_HOST, 40, 28, 300, 12, ES_READONLY | ES_LEFT, 0 - EDITTEXT IDC_HK_FINGERPRINT, 40, 60, 300, 12, ES_READONLY | ES_LEFT, 0 -END - -/* No accelerators used */ -IDD_HK_WRONG DIALOG DISCARDABLE 50, 50, 340, 200 -STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU -CAPTION "PuTTY Security Alert" -FONT 8, "MS Shell Dlg" -BEGIN - LTEXT "WARNING - POTENTIAL SECURITY BREACH!", IDC_HK_TITLE, 40, 20, 300, 12 - - LTEXT "The host key does not match the one {APPNAME} has cached for this server:", 100, 40, 36, 300, 8 - LTEXT "This means that either the server administrator has changed the", 101, 40, 56, 300, 8 - LTEXT "host key, or you have actually connected to another computer", 102, 40, 64, 300, 8 - LTEXT "pretending to be the server.", 103, 40, 72, 300, 8 - LTEXT "The new {KEYTYPE} key fingerprint is:", 104, 40, 84, 300, 8 - LTEXT "If you were expecting this change and trust the new key, press", 105, 40, 104, 300, 8 - LTEXT """Accept"" to update {APPNAME}'s cache and continue connecting.", 106, 40, 112, 300, 8 - LTEXT "If you want to carry on connecting but without updating the cache,", 107, 40, 124, 300, 8 - LTEXT "press ""Connect Once"".", 108, 40, 132, 300, 8 - LTEXT "If you want to abandon the connection completely, press ""Cancel"".", 109, 40, 144, 300, 8 - LTEXT "Pressing ""Cancel"" is the ONLY guaranteed safe choice.", 110, 40, 152, 300, 8 - - ICON "", IDC_HK_ICON, 10, 16, 0, 0 + PUSHBUTTON "Cancel", IDCANCEL, 288, 220, 40, 14 + PUSHBUTTON "&Accept", IDC_HK_ACCEPT, 168, 220, 40, 14 + PUSHBUTTON "Connect &Once", IDC_HK_ONCE, 216, 220, 64, 14 + PUSHBUTTON "More &info...", IDC_HK_MOREINFO, 60, 220, 64, 14 + PUSHBUTTON "&Help", IDHELP, 12, 220, 40, 14 - PUSHBUTTON "Cancel", IDCANCEL, 288, 180, 40, 14 - PUSHBUTTON "Accept", IDC_HK_ACCEPT, 168, 180, 40, 14 - PUSHBUTTON "Connect Once", IDC_HK_ONCE, 216, 180, 64, 14 - PUSHBUTTON "More info...", IDC_HK_MOREINFO, 60, 180, 64, 14 - PUSHBUTTON "Help", IDHELP, 12, 180, 40, 14 + LTEXT "", IDC_HK_TITLE, 40, 20, 300, 12 - EDITTEXT IDC_HK_HOST, 40, 44, 300, 12, ES_READONLY | ES_LEFT, 0 - EDITTEXT IDC_HK_FINGERPRINT, 40, 92, 300, 12, ES_READONLY | ES_LEFT, 0 + EDITTEXT IDC_HK_TEXT, 40, 20, 290, 200, ES_READONLY | ES_MULTILINE | ES_LEFT, WS_EX_STATICEDGE END /* Accelerators used: clw */ -IDD_HK_MOREINFO DIALOG DISCARDABLE 140, 40, 400, 156 +IDD_HK_MOREINFO DIALOG DISCARDABLE 140, 40, 400, 300 STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "PuTTY: information about the server's host key" FONT 8, "MS Shell Dlg" CLASS "PuTTYHostKeyMoreInfo" BEGIN - LTEXT "SHA256 fingerprint:", 100, 12, 12, 80, 8 - EDITTEXT IDC_HKI_SHA256, 100, 10, 288, 12, ES_READONLY - LTEXT "MD5 fingerprint:", 101, 12, 28, 80, 8 - EDITTEXT IDC_HKI_MD5, 100, 26, 288, 12, ES_READONLY - LTEXT "Full public key:", 102, 12, 44, 376, 8 - EDITTEXT IDC_HKI_PUBKEY, 12, 54, 376, 64, ES_READONLY | ES_MULTILINE | ES_LEFT | ES_AUTOVSCROLL, WS_EX_STATICEDGE DEFPUSHBUTTON "&Close", IDOK, 176, 130, 48, 14 END diff --git a/windows/putty-rc.h b/windows/putty-rc.h index c1658e5d..35d9bcda 100644 --- a/windows/putty-rc.h +++ b/windows/putty-rc.h @@ -13,8 +13,7 @@ #define IDD_ABOUTBOX 111 #define IDD_RECONF 112 #define IDD_LICENCEBOX 113 -#define IDD_HK_ABSENT 114 -#define IDD_HK_WRONG 115 +#define IDD_HOSTKEY 114 #define IDD_HK_MOREINFO 116 #define IDD_CA_CONFIG 117 @@ -35,6 +34,7 @@ #define IDC_HK_ICON 98 #define IDC_HK_TITLE 99 +#define IDC_HK_TEXT 100 #define IDC_HK_ACCEPT 1001 #define IDC_HK_ONCE 1000 #define IDC_HK_HOST 1002 diff --git a/windows/window.c b/windows/window.c index 40eb5c14..12936808 100644 --- a/windows/window.c +++ b/windows/window.c @@ -346,6 +346,7 @@ static const SeatVtable win_seat_vt = { .confirm_ssh_host_key = win_seat_confirm_ssh_host_key, .confirm_weak_crypto_primitive = win_seat_confirm_weak_crypto_primitive, .confirm_weak_cached_hostkey = win_seat_confirm_weak_cached_hostkey, + .prompt_descriptions = win_seat_prompt_descriptions, .is_utf8 = win_seat_is_utf8, .echoedit_update = nullseat_echoedit_update, .get_x_display = nullseat_get_x_display, -- cgit v1.2.3 From a50178eba79ecbbeb9949f2c9827fe14b50c1941 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 16 Jul 2022 11:24:59 +0100 Subject: Fix typo in #undef. In the macro automation for ssh2_bpp_check_unimplemented, I #defined SSH2_BITMAP_WORD, and 20 lines later, tried to #undef it by the wrong spelling. Of course this gave no error, so I didn't notice! But I spotted it just now, so let's fix it. --- ssh/common.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ssh/common.c b/ssh/common.c index d5e1d55f..04b341ae 100644 --- a/ssh/common.c +++ b/ssh/common.c @@ -840,7 +840,7 @@ bool ssh2_bpp_check_unimplemented(BinaryPacketProtocol *bpp, PktIn *pktin) #undef BITMAP_UNIVERSAL #undef BITMAP_CONDITIONAL -#undef SSH1_BITMAP_WORD +#undef SSH2_BITMAP_WORD /* ---------------------------------------------------------------------- * Centralised component of SSH host key verification. -- cgit v1.2.3 From 42740a54550476e47b8f68981f24ac455c1daa51 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 16 Jul 2022 11:23:13 +0100 Subject: Allow manually confirming and caching certified keys. In the case where a server presents a host key signed by a different certificate from the one you've configured, it need not _always_ be evidence of wrongdoing. I can imagine situations in which two CAs cover overlapping sets of things, and you don't want to blanket-trust one of them, but you do want to connect to a specific host signed by that one. Accordingly, PuTTY's previous policy of unconditionally aborting the connection if certificate validation fails (which was always intended as a stopgap until I thought through what I wanted to replace it with) is now replaced by fallback handling: we present the host key fingerprint to the user and give them the option to accept and/or cache it based on the public key itself. This means that the certified key types have to have a representation in the host key cache. So I've assigned each one a type id, and generate the cache string itself by simply falling back to the base key. (Rationale for the latter: re-signing a public key with a different certificate doesn't change the _private_ key, or the set of valid signatures generated with it. So if you've been convinced for reasons other than the certificate that a particular private key is in the possession of $host, then proof of ownership of that private key should be enough to convince you you're talking to $host no matter what CA has signed the public half this week.) We now offer to receive a given certified host key type if _either_ we have at least one CA configured to trust that host, _or_ we have a certified key of that type cached. (So once you've decided manually that you trust a particular key, we can still receive that key and authenticate the host with it, even if you later delete the CA record that it didn't match anyway.) One change from normal (uncertified) host key handling is that for certified key types _all_ the host key prompts use the stronger language, with "WARNING - POTENTIAL SECURITY BREACH!" rather than the mild 'hmm, we haven't seen this host before'. Rationale: if you expected this CA key and got that one, it _could_ be a bold-as-brass MITM attempt in which someone hoped you'd accept their entire CA key. The mild wording is only for the case where we had no previous expectations _at all_ for the host to violate: not a CA _or_ a cached key. --- crypto/openssh-certs.c | 6 +++--- ssh.h | 4 ++-- ssh/common.c | 50 +++++++++++++++++++++++++++++++++++++++++++++++--- ssh/kex2-client.c | 37 +++++++++++++++++++++++++------------ ssh/login1.c | 2 +- ssh/transport2.c | 8 ++++---- 6 files changed, 82 insertions(+), 25 deletions(-) diff --git a/crypto/openssh-certs.c b/crypto/openssh-certs.c index c27118a4..73218e7c 100644 --- a/crypto/openssh-certs.c +++ b/crypto/openssh-certs.c @@ -268,7 +268,7 @@ static const ssh_keyalg *opensshcert_related_alg(const ssh_keyalg *self, .alternate_ssh_id = opensshcert_alternate_ssh_id, \ .related_alg = opensshcert_related_alg, \ .ssh_id = ssh_alg_id_prefix "-cert-v01@openssh.com", \ - .cache_id = NULL, \ + .cache_id = "opensshcert-" ssh_key_id_prefix, \ .extra = &opensshcert_##name##_extra, \ .is_certificate = true, \ .base_alg = &name, \ @@ -559,8 +559,8 @@ static bool opensshcert_has_private(ssh_key *key) static char *opensshcert_cache_str(ssh_key *key) { - unreachable( - "Certificates are not expected to be stored in the host key cache"); + opensshcert_key *ck = container_of(key, opensshcert_key, sshk); + return ssh_key_cache_str(ck->basekey); } static void opensshcert_time_to_iso8601(BinarySink *bs, uint64_t time) diff --git a/ssh.h b/ssh.h index e628638c..454c5449 100644 --- a/ssh.h +++ b/ssh.h @@ -1801,8 +1801,8 @@ bool get_commasep_word(ptrlen *list, ptrlen *word); SeatPromptResult verify_ssh_host_key( InteractionReadySeat iseat, Conf *conf, const char *host, int port, ssh_key *key, const char *keytype, char *keystr, const char *keydisp, - char **fingerprints, void (*callback)(void *ctx, SeatPromptResult result), - void *ctx); + char **fingerprints, int ca_count, + void (*callback)(void *ctx, SeatPromptResult result), void *ctx); typedef struct ssh_transient_hostkey_cache ssh_transient_hostkey_cache; ssh_transient_hostkey_cache *ssh_transient_hostkey_cache_new(void); diff --git a/ssh/common.c b/ssh/common.c index 04b341ae..e9823a6d 100644 --- a/ssh/common.c +++ b/ssh/common.c @@ -855,8 +855,8 @@ bool ssh2_bpp_check_unimplemented(BinaryPacketProtocol *bpp, PktIn *pktin) SeatPromptResult verify_ssh_host_key( InteractionReadySeat iseat, Conf *conf, const char *host, int port, ssh_key *key, const char *keytype, char *keystr, const char *keydisp, - char **fingerprints, void (*callback)(void *ctx, SeatPromptResult result), - void *ctx) + char **fingerprints, int ca_count, + void (*callback)(void *ctx, SeatPromptResult result), void *ctx) { /* * First, check if the Conf includes a manual specification of the @@ -934,7 +934,51 @@ SeatPromptResult verify_ssh_host_key( seat_dialog_text_append( text, SDT_TITLE, "%s Security Alert", appname); - if (storage_status == 1) { + if (key && ssh_key_alg(key)->is_certificate) { + seat_dialog_text_append( + text, SDT_SCARY_HEADING, "WARNING - POTENTIAL SECURITY BREACH!"); + seat_dialog_text_append( + text, SDT_PARA, "This server presented a certified host key:"); + seat_dialog_text_append( + text, SDT_DISPLAY, "%s (port %d)", host, port); + if (ca_count) { + seat_dialog_text_append( + text, SDT_PARA, "which was signed by a different " + "certification authority from the %s %s is configured to " + "trust for this server.", ca_count > 1 ? "ones" : "one", + appname); + if (storage_status == 2) { + seat_dialog_text_append( + text, SDT_PARA, "ALSO, that key does not match the key " + "%s had previously cached for this server.", appname); + seat_dialog_text_append( + text, SDT_PARA, "This means that either another " + "certification authority is operating in this realm AND " + "the server administrator has changed the host key, or " + "you have actually connected to another computer " + "pretending to be the server."); + } else { + seat_dialog_text_append( + text, SDT_PARA, "This means that either another " + "certification authority is operating in this realm, or " + "you have actually connected to another computer " + "pretending to be the server."); + } + } else { + assert(storage_status == 2); + seat_dialog_text_append( + text, SDT_PARA, "which does not match the certified key %s " + "had previously cached for this server.", appname); + seat_dialog_text_append( + text, SDT_PARA, "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."); + } + seat_dialog_text_append( + text, SDT_PARA, "The new %s key fingerprint is:", keytype); + seat_dialog_text_append( + text, SDT_DISPLAY, "%s", fingerprints[fptype_default]); + } else if (storage_status == 1) { seat_dialog_text_append( text, SDT_PARA, "The host key is not cached for this server:"); seat_dialog_text_append( diff --git a/ssh/kex2-client.c b/ssh/kex2-client.c index a67f9e13..ccc62c3c 100644 --- a/ssh/kex2-client.c +++ b/ssh/kex2-client.c @@ -718,8 +718,7 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) } } - s->keystr = (s->hkey && !ssh_key_alg(s->hkey)->is_certificate ? - ssh_key_cache_str(s->hkey) : NULL); + s->keystr = ssh_key_cache_str(s->hkey); #ifndef NO_GSSAPI if (s->gss_kex_used) { /* @@ -868,8 +867,6 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) * hash.) */ if (ssh_key_alg(s->hkey)->is_certificate) { - ssh2_free_all_fingerprints(fingerprints); - char *base_fp = ssh2_fingerprint(ssh_key_base_key(s->hkey), fptype_default); ppl_logevent("Host key is a certificate, whose base key has " @@ -917,24 +914,26 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) } if (cert_ok) { strbuf_free(error); + ssh2_free_all_fingerprints(fingerprints); ppl_logevent("Accepted certificate"); + goto host_key_ok; } else { ppl_logevent("Rejected host key certificate: %s", error->s); - ssh_sw_abort(s->ppl.ssh, - "Rejected host key certificate: %s", - error->s); - *aborted = true; - strbuf_free(error); - return; + /* now fall through into normal host key checking */ } - } else { + } + + { char *keydisp = ssh2_pubkey_openssh_str(&uk); + int ca_count = ssh_key_alg(s->hkey)->is_certificate ? + count234(s->host_cas) : 0; + s->spr = verify_ssh_host_key( ppl_get_iseat(&s->ppl), s->conf, s->savedhost, s->savedport, s->hkey, ssh_key_cache_id(s->hkey), s->keystr, keydisp, - fingerprints, ssh2_transport_dialog_callback, s); + fingerprints, ca_count, ssh2_transport_dialog_callback, s); ssh2_free_all_fingerprints(fingerprints); sfree(keydisp); @@ -947,8 +946,22 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) ssh_spr_close(s->ppl.ssh, s->spr, "host key verification"); return; } + + if (ssh_key_alg(s->hkey)->is_certificate) { + /* + * Explain what's going on in the Event Log: if we + * got here by way of a certified key whose + * certificate we didn't like, then we should + * explain why we chose to continue with the + * connection anyway! + */ + ppl_logevent("Accepting certified host key anyway based " + "on cache"); + } } + host_key_ok: + /* * Save this host key, to check against the one presented in * subsequent rekeys. diff --git a/ssh/login1.c b/ssh/login1.c index d556f22a..d9f3883e 100644 --- a/ssh/login1.c +++ b/ssh/login1.c @@ -243,7 +243,7 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl) s->spr = verify_ssh_host_key( ppl_get_iseat(&s->ppl), s->conf, s->savedhost, s->savedport, NULL, - "rsa", keystr, keydisp, fingerprints, + "rsa", keystr, keydisp, fingerprints, 0, ssh1_login_dialog_callback, s); ssh2_free_all_fingerprints(fingerprints); diff --git a/ssh/transport2.c b/ssh/transport2.c index 4383bb98..ab5902b9 100644 --- a/ssh/transport2.c +++ b/ssh/transport2.c @@ -725,8 +725,8 @@ static void ssh2_write_kexinit_lists( } } - /* Next, add uncertified algorithms we already know a key for - * (unless configured not to do that) */ + /* Next, add algorithms we already know a key for (unless + * configured not to do that) */ warn = false; for (i = 0; i < n_preferred_hk; i++) { if (preferred_hk[i] == HK_WARN) @@ -734,8 +734,8 @@ static void ssh2_write_kexinit_lists( for (j = 0; j < lenof(ssh2_hostkey_algs); j++) { const struct ssh_signkey_with_user_pref_id *a = &ssh2_hostkey_algs[j]; - if (a->alg->is_certificate || !a->alg->cache_id) - continue; + if (a->alg->is_certificate && accept_certs) + continue; /* already added this one */ if (a->id != preferred_hk[i]) continue; if (conf_get_bool(conf, CONF_ssh_prefer_known_hostkeys) && -- cgit v1.2.3 From 810e21de8234474ca606804c317a7dd5b6c686ab Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 21 Jul 2022 18:37:58 +0100 Subject: Unix Plink: handle stdout/stderr backlog consistently. Whenever we successfully send some data to standard output/error, we're supposed to notify the backend that this has happened, and tell it how much backlog still remains, by calling backend_unthrottle(). In Unix Plink, the call to backend_unthrottle() was happening on some but not all calls to try_output(). In particular, it was happening when we called try_output() as a result of stdout or stderr having just been reported writable by poll(), but not when we called it from plink_output() after the backend had just sent us some more data. Of course that _normally_ works - if you were polling stdout for writability at all then it's because a previous call had returned EAGAIN, so that's when you _have_ backlog to dispose of. But it's also possible, by an accident of timing, that before you get round to doing that poll, the seat passes you further data and you call try_output() anyway, and by chance, the blockage has cleared. In that situation, you end up having cleared your backlog but forgotten to tell the backend about it - which might mean the backend never unfreezes the channel or (in 'simple' mode) the entire SSH socket. A user reported (and I reproduced) that when Plink is compiled on MacOS, running an interactive session through it and doing output-intensive activity like scrolling around in htop(1) can quite easily get it into what turned out to be that stuck state. (I don't know why MacOS and not any other platform, but since it's a race condition, that seems like a plausible enough cause of a difference in timing.) Also, we were inconsistently computing the backlog size: sometimes it was the total size of the stdout and stderr bufchains, and sometimes it was just the size of the one we'd made an effort to empty. Now the backlog size is consistently stdout+stderr (the same as it is in Windows Plink), and the call to backend_unthrottle() happens _inside_ try_output(), so that I don't have to remember it at every call site. --- unix/plink.c | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/unix/plink.c b/unix/plink.c index 4ab371a0..a02bbe17 100644 --- a/unix/plink.c +++ b/unix/plink.c @@ -322,7 +322,12 @@ static BinarySink *stdout_bs, *stderr_bs; static enum { EOF_NO, EOF_PENDING, EOF_SENT } outgoingeof; -size_t try_output(bool is_stderr) +static size_t output_backlog(void) +{ + return bufchain_size(&stdout_data) + bufchain_size(&stderr_data); +} + +void try_output(bool is_stderr) { bufchain *chain = (is_stderr ? &stderr_data : &stdout_data); int fd = (is_stderr ? STDERR_FILENO : STDOUT_FILENO); @@ -343,12 +348,13 @@ size_t try_output(bool is_stderr) perror(is_stderr ? "stderr: write" : "stdout: write"); exit(1); } + + backend_unthrottle(backend, output_backlog()); } if (outgoingeof == EOF_PENDING && bufchain_size(&stdout_data) == 0) { close(STDOUT_FILENO); outgoingeof = EOF_SENT; } - return bufchain_size(&stdout_data) + bufchain_size(&stderr_data); } static size_t plink_output( @@ -360,7 +366,8 @@ static size_t plink_output( BinarySink *bs = is_stderr ? stderr_bs : stdout_bs; put_data(bs, data, len); - return try_output(is_stderr); + try_output(is_stderr); + return output_backlog(); } static bool plink_eof(Seat *seat) @@ -650,13 +657,11 @@ static void plink_pw_check(void *vctx, pollwrapper *pw) } } - if (pollwrap_check_fd_rwx(pw, STDOUT_FILENO, SELECT_W)) { - backend_unthrottle(backend, try_output(false)); - } + if (pollwrap_check_fd_rwx(pw, STDOUT_FILENO, SELECT_W)) + try_output(false); - if (pollwrap_check_fd_rwx(pw, STDERR_FILENO, SELECT_W)) { - backend_unthrottle(backend, try_output(true)); - } + if (pollwrap_check_fd_rwx(pw, STDERR_FILENO, SELECT_W)) + try_output(true); } static bool plink_continue(void *vctx, bool found_any_fd, -- cgit v1.2.3 From c88b6d18535e71ee156e6fdfa2367f1892bc579d Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 24 Jul 2022 13:54:32 +0100 Subject: Send xterm 216+ modifiers in small-keypad key escape sequences. In the 'xterm 216+' function key mode, a function key pressed with a combination of Shift, Ctrl and Alt has its usual sequence like ESC[n~ (for some integer n) turned into ESC[n;m~ where m-1 is a 3-bit bitmap of currently pressed modifier keys. This mode now also applies to the keys on the small keypad above the arrow keys (Ins, Home, PgUp etc). If xterm 216+ mode is selected, those keys are modified in the same way as the function keys. As with the function keys, this doesn't guarantee that PuTTY will _receive_ any particular shifted key of this kind, and not repurpose it. Just as Alt+F4 still closes the window (at least on Windows) rather than sending a modified F4 sequence, Shift+Ins will still perform a paste action rather than sending a modified Ins sequence, Shift-PgUp will still scroll the scrollback, etc. But the keys not already used by PuTTY for other purposes should now have their modern-xterm behaviour in modern-xterm mode. Thanks to H.Merijn Brand for developing and testing a version of this patch. --- putty.h | 4 +++- terminal/terminal.c | 16 ++++++++++++++-- unix/window.c | 8 +++++++- windows/window.c | 6 +++++- 4 files changed, 29 insertions(+), 5 deletions(-) diff --git a/putty.h b/putty.h index 6f177474..364297c3 100644 --- a/putty.h +++ b/putty.h @@ -2225,7 +2225,9 @@ int format_arrow_key(char *buf, Terminal *term, int xkey, bool shift, bool ctrl, bool alt, bool *consumed_alt); int format_function_key(char *buf, Terminal *term, int key_number, bool shift, bool ctrl, bool alt, bool *consumed_alt); -int format_small_keypad_key(char *buf, Terminal *term, SmallKeypadKey key); +int format_small_keypad_key(char *buf, Terminal *term, SmallKeypadKey key, + bool shift, bool ctrl, bool alt, + bool *consumed_alt); int format_numeric_keypad_key(char *buf, Terminal *term, char key, bool shift, bool ctrl); diff --git a/terminal/terminal.c b/terminal/terminal.c index 96bc98f8..04109415 100644 --- a/terminal/terminal.c +++ b/terminal/terminal.c @@ -7477,7 +7477,9 @@ int format_function_key(char *buf, Terminal *term, int key_number, return p - buf; } -int format_small_keypad_key(char *buf, Terminal *term, SmallKeypadKey key) +int format_small_keypad_key(char *buf, Terminal *term, SmallKeypadKey key, + bool shift, bool ctrl, bool alt, + bool *consumed_alt) { char *p = buf; @@ -7508,7 +7510,17 @@ int format_small_keypad_key(char *buf, Terminal *term, SmallKeypadKey key) } else if ((code == 1 || code == 4) && term->rxvt_homeend) { p += sprintf(p, code == 1 ? "\x1B[H" : "\x1BOw"); } else { - p += sprintf(p, "\x1B[%d~", code); + if (term->vt52_mode) { + p += sprintf(p, "\x1B[%d~", code); + } else { + int bitmap = 0; + if (term->funky_type == FUNKY_XTERM_216) + bitmap = shift_bitmap(shift, ctrl, alt, consumed_alt); + if (bitmap) + p += sprintf(p, "\x1B[%d;%d~", code, bitmap); + else + p += sprintf(p, "\x1B[%d~", code); + } } return p - buf; diff --git a/unix/window.c b/unix/window.c index f8232e9a..bdf9b598 100644 --- a/unix/window.c +++ b/unix/window.c @@ -1914,7 +1914,13 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) if (event->state & GDK_CONTROL_MASK) break; - end = 1 + format_small_keypad_key(output+1, inst->term, sk_key); + end = 1 + format_small_keypad_key(output+1, inst->term, sk_key, + event->state & GDK_SHIFT_MASK, + event->state & GDK_CONTROL_MASK, + event->state & inst->meta_mod_mask, + &consumed_meta_key); + if (consumed_meta_key) + start = 1; /* supersedes the usual prefixing of Esc */ #ifdef KEY_EVENT_DIAGNOSTICS debug(" - small keypad key"); #endif diff --git a/windows/window.c b/windows/window.c index 12936808..b291c2a3 100644 --- a/windows/window.c +++ b/windows/window.c @@ -4627,7 +4627,11 @@ static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam, if (shift_state & 2) break; - p += format_small_keypad_key((char *)p, term, sk_key); + p += format_small_keypad_key((char *)p, term, sk_key, + shift_state & 1, shift_state & 2, + left_alt, &consumed_alt); + if (consumed_alt) + left_alt = false; /* supersedes the usual prefixing of Esc */ return p - output; char xkey; -- cgit v1.2.3 From a33cf2240ef44c68537b116440c3184460e62bff Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 24 Jul 2022 14:08:56 +0100 Subject: Fix uninitialised field in ctrl_fileselect. In commit 694d5184b72505a, I introduced the 'just_button' flag for CTRL_FILESELECT controls, and in commit ddcd93ab1230b2b I added a use of such a control with the flag set to true. But I forgot to set it to false everywhere else, which caused an assertion failure when selecting the Bell pane in Windows PuTTY. Oops. --- dialog.c | 1 + 1 file changed, 1 insertion(+) diff --git a/dialog.c b/dialog.c index 26a0ff49..b9306982 100644 --- a/dialog.c +++ b/dialog.c @@ -402,6 +402,7 @@ dlgcontrol *ctrl_filesel(struct controlset *s, const char *label, c->fileselect.filter = filter; c->fileselect.for_writing = write; c->fileselect.title = dupstr(title); + c->fileselect.just_button = false; return c; } -- cgit v1.2.3 From 138df73e81ddc732d71d66d8a7af2db2f0d826c4 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Tue, 26 Jul 2022 12:42:17 +0100 Subject: Windows printing: handle failure of EnumPrinters. A user reports that if the Print Spooler service is disabled via services.msc, then PuTTY can report 'Out of memory!' when you try to open the Terminal config pane, which is the one containing the combo box enumerating the available printers. Apparently this is because the call to EnumPrinters failed with the error code other than the expected ERROR_INSUFFICIENT_BUFFER, and in the process, left garbage in the pcbNeeded output parameter. That wouldn't be too surprising if it had simply _not written_ to that parameter and therefore it was never initialised at all in the calling function printer_add_enum. But in fact, printer_add_enum *does* precautionarily initialise needed=0 before the initial call to EnumPrinters. So EnumPrinters must have actively written one of its *own* uninitialised variables into it! Anyway, the obvious fix is to distinguish ERROR_INSUFFICIENT_BUFFER from any other kind of EnumPrinters failure (in fact turning off Print Spooler seems to lead to RPC_S_SERVER_UNAVAILABLE), and not attempt to proceed in the case of other failures. --- windows/printing.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/windows/printing.c b/windows/printing.c index e6b3531d..0ec81376 100644 --- a/windows/printing.c +++ b/windows/printing.c @@ -67,11 +67,15 @@ static bool printer_add_enum(int param, DWORD level, char **buffer, /* * 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. + * we'll need for the output. + * + * If we get ERROR_INSUFFICIENT_BUFFER, that's fine, we're + * prepared to deal with it. Any other error, we return failure. */ - p_EnumPrinters(param, NULL, level, (LPBYTE)((*buffer)+offset), 512, - &needed, &nprinters); + if (p_EnumPrinters(param, NULL, level, (LPBYTE)((*buffer)+offset), 512, + &needed, &nprinters) == 0 && + GetLastError() != ERROR_INSUFFICIENT_BUFFER) + return false; if (needed < 512) needed = 512; -- cgit v1.2.3 From 10f47902e5ccadf3929e65c3c67c54716483c6d6 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 30 Jul 2022 14:12:32 +0100 Subject: windows/controls.c API: add lots of missing 'const'. Most of the Windows-specific dialog control construction functions were passing their string parameters as 'char *' even though they were string literals. Apparently none of our previous giant constification patches spotted that. --- windows/controls.c | 82 ++++++++++++++++++++++++++++-------------------------- windows/platform.h | 79 +++++++++++++++++++++++++++------------------------- 2 files changed, 83 insertions(+), 78 deletions(-) diff --git a/windows/controls.c b/windows/controls.c index 22b70c93..a340902d 100644 --- a/windows/controls.c +++ b/windows/controls.c @@ -71,8 +71,8 @@ void ctlposinit(struct ctlpos *cp, HWND hwnd, cp->width -= leftborder + rightborder; } -HWND doctl(struct ctlpos *cp, RECT r, - char *wclass, int wstyle, int exstyle, char *wtext, int wid) +HWND doctl(struct ctlpos *cp, RECT r, const char *wclass, int wstyle, + int exstyle, const char *wtext, int wid) { HWND ctl; /* @@ -115,7 +115,7 @@ HWND doctl(struct ctlpos *cp, RECT r, /* * A title bar across the top of a sub-dialog. */ -void bartitle(struct ctlpos *cp, char *name, int id) +void bartitle(struct ctlpos *cp, const char *name, int id) { RECT r; @@ -130,7 +130,7 @@ void bartitle(struct ctlpos *cp, char *name, int id) /* * Begin a grouping box, with or without a group title. */ -void beginbox(struct ctlpos *cp, char *name, int idbox) +void beginbox(struct ctlpos *cp, const char *name, int idbox) { cp->boxystart = cp->ypos; if (!name) @@ -165,8 +165,8 @@ void endbox(struct ctlpos *cp) /* * A static line, followed by a full-width edit box. */ -void editboxfw(struct ctlpos *cp, bool password, bool readonly, char *text, - int staticid, int editid) +void editboxfw(struct ctlpos *cp, bool password, bool readonly, + const char *text, int staticid, int editid) { RECT r; @@ -192,7 +192,7 @@ void editboxfw(struct ctlpos *cp, bool password, bool readonly, char *text, /* * A static line, followed by a full-width combo box. */ -void combobox(struct ctlpos *cp, char *text, int staticid, int listid) +void combobox(struct ctlpos *cp, const char *text, int staticid, int listid) { RECT r; @@ -213,9 +213,9 @@ void combobox(struct ctlpos *cp, char *text, int staticid, int listid) cp->ypos += COMBOHEIGHT + GAPBETWEEN; } -struct radio { char *text; int id; }; +struct radio { const char *text; int id; }; -static void radioline_common(struct ctlpos *cp, char *text, int id, +static void radioline_common(struct ctlpos *cp, const char *text, int id, int nacross, struct radio *buttons, int nbuttons) { RECT r; @@ -237,7 +237,7 @@ static void radioline_common(struct ctlpos *cp, char *text, int id, group = WS_GROUP; i = 0; for (j = 0; j < nbuttons; j++) { - char *btext = buttons[j].text; + const char *btext = buttons[j].text; int bid = buttons[j].id; if (i == nacross) { @@ -275,7 +275,7 @@ static void radioline_common(struct ctlpos *cp, char *text, int id, * * (*) Button1 (*) Button2 (*) ButtonWithReallyLongTitle */ -void radioline(struct ctlpos *cp, char *text, int id, int nacross, ...) +void radioline(struct ctlpos *cp, const char *text, int id, int nacross, ...) { va_list ap; struct radio *buttons; @@ -284,7 +284,7 @@ void radioline(struct ctlpos *cp, char *text, int id, int nacross, ...) va_start(ap, nacross); nbuttons = 0; while (1) { - char *btext = va_arg(ap, char *); + const char *btext = va_arg(ap, const char *); if (!btext) break; (void) va_arg(ap, int); /* id */ @@ -294,7 +294,7 @@ void radioline(struct ctlpos *cp, char *text, int id, int nacross, ...) buttons = snewn(nbuttons, struct radio); va_start(ap, nacross); for (i = 0; i < nbuttons; i++) { - buttons[i].text = va_arg(ap, char *); + buttons[i].text = va_arg(ap, const char *); buttons[i].id = va_arg(ap, int); } va_end(ap); @@ -315,7 +315,7 @@ void bareradioline(struct ctlpos *cp, int nacross, ...) va_start(ap, nacross); nbuttons = 0; while (1) { - char *btext = va_arg(ap, char *); + const char *btext = va_arg(ap, const char *); if (!btext) break; (void) va_arg(ap, int); /* id */ @@ -325,7 +325,7 @@ void bareradioline(struct ctlpos *cp, int nacross, ...) buttons = snewn(nbuttons, struct radio); va_start(ap, nacross); for (i = 0; i < nbuttons; i++) { - buttons[i].text = va_arg(ap, char *); + buttons[i].text = va_arg(ap, const char *); buttons[i].id = va_arg(ap, int); } va_end(ap); @@ -337,7 +337,7 @@ void bareradioline(struct ctlpos *cp, int nacross, ...) * A set of radio buttons on multiple lines, with a static above * them. */ -void radiobig(struct ctlpos *cp, char *text, int id, ...) +void radiobig(struct ctlpos *cp, const char *text, int id, ...) { va_list ap; struct radio *buttons; @@ -346,7 +346,7 @@ void radiobig(struct ctlpos *cp, char *text, int id, ...) va_start(ap, id); nbuttons = 0; while (1) { - char *btext = va_arg(ap, char *); + const char *btext = va_arg(ap, const char *); if (!btext) break; (void) va_arg(ap, int); /* id */ @@ -356,7 +356,7 @@ void radiobig(struct ctlpos *cp, char *text, int id, ...) buttons = snewn(nbuttons, struct radio); va_start(ap, id); for (i = 0; i < nbuttons; i++) { - buttons[i].text = va_arg(ap, char *); + buttons[i].text = va_arg(ap, const char *); buttons[i].id = va_arg(ap, int); } va_end(ap); @@ -367,7 +367,7 @@ void radiobig(struct ctlpos *cp, char *text, int id, ...) /* * A single standalone checkbox. */ -void checkbox(struct ctlpos *cp, char *text, int id) +void checkbox(struct ctlpos *cp, const char *text, int id) { RECT r; @@ -386,13 +386,14 @@ void checkbox(struct ctlpos *cp, char *text, int id) * 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) +char *staticwrap(struct ctlpos *cp, HWND hwnd, const char *text, int *lines) { HDC hdc = GetDC(hwnd); int width, nlines, j; INT *pwidths, nfit; SIZE size; - char *ret, *p, *q; + const char *p; + char *ret, *q; RECT r; HFONT oldfont, newfont; @@ -472,7 +473,7 @@ char *staticwrap(struct ctlpos *cp, HWND hwnd, char *text, int *lines) /* * A single standalone static text control. */ -void statictext(struct ctlpos *cp, char *text, int lines, int id) +void statictext(struct ctlpos *cp, const char *text, int lines, int id) { RECT r; @@ -505,8 +506,8 @@ void paneltitle(struct ctlpos *cp, int 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) +void staticbtn(struct ctlpos *cp, const char *stext, int sid, + const char *btext, int bid) { const int height = (PUSHBTNHEIGHT > STATICHEIGHT ? PUSHBTNHEIGHT : STATICHEIGHT); @@ -537,7 +538,7 @@ void staticbtn(struct ctlpos *cp, char *stext, int sid, /* * A simple push button. */ -void button(struct ctlpos *cp, char *btext, int bid, bool defbtn) +void button(struct ctlpos *cp, const char *btext, int bid, bool defbtn) { RECT r; @@ -562,8 +563,8 @@ void button(struct ctlpos *cp, char *btext, int bid, bool defbtn) /* * Like staticbtn, but two buttons. */ -void static2btn(struct ctlpos *cp, char *stext, int sid, - char *btext1, int bid1, char *btext2, int bid2) +void static2btn(struct ctlpos *cp, const char *stext, int sid, + const char *btext1, int bid1, const char *btext2, int bid2) { const int height = (PUSHBTNHEIGHT > STATICHEIGHT ? PUSHBTNHEIGHT : STATICHEIGHT); @@ -604,7 +605,7 @@ void static2btn(struct ctlpos *cp, char *stext, int sid, /* * An edit control on the right hand side, with a static to its left. */ -static void staticedit_internal(struct ctlpos *cp, char *stext, +static void staticedit_internal(struct ctlpos *cp, const char *stext, int sid, int eid, int percentedit, int style) { @@ -635,13 +636,13 @@ static void staticedit_internal(struct ctlpos *cp, char *stext, cp->ypos += height + GAPBETWEEN; } -void staticedit(struct ctlpos *cp, char *stext, +void staticedit(struct ctlpos *cp, const char *stext, int sid, int eid, int percentedit) { staticedit_internal(cp, stext, sid, eid, percentedit, 0); } -void staticpassedit(struct ctlpos *cp, char *stext, +void staticpassedit(struct ctlpos *cp, const char *stext, int sid, int eid, int percentedit) { staticedit_internal(cp, stext, sid, eid, percentedit, ES_PASSWORD); @@ -651,7 +652,7 @@ void staticpassedit(struct ctlpos *cp, char *stext, * A drop-down list box on the right hand side, with a static to * its left. */ -void staticddl(struct ctlpos *cp, char *stext, +void staticddl(struct ctlpos *cp, const char *stext, int sid, int lid, int percentlist) { const int height = (COMBOHEIGHT > STATICHEIGHT ? @@ -684,7 +685,7 @@ void staticddl(struct ctlpos *cp, char *stext, /* * A combo box on the right hand side, with a static to its left. */ -void staticcombo(struct ctlpos *cp, char *stext, +void staticcombo(struct ctlpos *cp, const char *stext, int sid, int lid, int percentlist) { const int height = (COMBOHEIGHT > STATICHEIGHT ? @@ -717,7 +718,7 @@ void staticcombo(struct ctlpos *cp, char *stext, /* * A static, with a full-width drop-down list box below it. */ -void staticddlbig(struct ctlpos *cp, char *stext, +void staticddlbig(struct ctlpos *cp, const char *stext, int sid, int lid) { RECT r; @@ -744,7 +745,7 @@ void staticddlbig(struct ctlpos *cp, char *stext, /* * A big multiline edit control with a static labelling it. */ -void bigeditctrl(struct ctlpos *cp, char *stext, +void bigeditctrl(struct ctlpos *cp, const char *stext, int sid, int eid, int lines) { RECT r; @@ -771,7 +772,7 @@ void bigeditctrl(struct ctlpos *cp, char *stext, /* * A list box with a static labelling it. */ -void listbox(struct ctlpos *cp, char *stext, +void listbox(struct ctlpos *cp, const char *stext, int sid, int lid, int lines, bool multi) { RECT r; @@ -800,7 +801,8 @@ void listbox(struct ctlpos *cp, char *stext, /* * A tab-control substitute when a real tab control is unavailable. */ -void ersatztab(struct ctlpos *cp, char *stext, int sid, int lid, int s2id) +void ersatztab(struct ctlpos *cp, const char *stext, int sid, int lid, + int s2id) { const int height = (COMBOHEIGHT > STATICHEIGHT ? COMBOHEIGHT : STATICHEIGHT); @@ -843,8 +845,8 @@ void ersatztab(struct ctlpos *cp, char *stext, int sid, int lid, int 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) +void editbutton(struct ctlpos *cp, const char *stext, int sid, + int eid, const char *btext, int bid) { const int height = (EDITHEIGHT > PUSHBTNHEIGHT ? EDITHEIGHT : PUSHBTNHEIGHT); @@ -887,7 +889,7 @@ void editbutton(struct ctlpos *cp, char *stext, int sid, * 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 char *stext, int sid, int listid, int upbid, int dnbid) { const static int percents[] = { 5, 75, 20 }; RECT r; @@ -1596,7 +1598,7 @@ void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, buttons, ctrl->radio.nbuttons); for (i = 0; i < ctrl->radio.nbuttons; i++) { - sfree(buttons[i].text); + sfree((char *)buttons[i].text); } sfree(buttons); sfree(escaped); diff --git a/windows/platform.h b/windows/platform.h index d0510faf..1e0cd1bc 100644 --- a/windows/platform.h +++ b/windows/platform.h @@ -374,7 +374,7 @@ struct ctlpos { int ypos, width; int xoff; int boxystart, boxid; - char *boxtext; + const char *boxtext; }; void init_common_controls(void); /* also does some DLL-loading */ @@ -433,56 +433,59 @@ struct dlgparam { */ 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); +HWND doctl(struct ctlpos *cp, RECT r, const char *wclass, int wstyle, + int exstyle, const char *wtext, int wid); +void bartitle(struct ctlpos *cp, const char *name, int id); +void beginbox(struct ctlpos *cp, const char *name, int idbox); void endbox(struct ctlpos *cp); -void editboxfw(struct ctlpos *cp, bool password, bool readonly, char *text, - int staticid, int editid); -void radioline(struct ctlpos *cp, char *text, int id, int nacross, ...); +void editboxfw(struct ctlpos *cp, bool password, bool readonly, + const char *text, int staticid, int editid); +void radioline(struct ctlpos *cp, const 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, +void radiobig(struct ctlpos *cp, const char *text, int id, ...); +void checkbox(struct ctlpos *cp, const char *text, int id); +void statictext(struct ctlpos *cp, const char *text, int lines, int id); +void staticbtn(struct ctlpos *cp, const char *stext, int sid, + const char *btext, int bid); +void static2btn(struct ctlpos *cp, const char *stext, int sid, + const char *btext1, int bid1, const char *btext2, int bid2); +void staticedit(struct ctlpos *cp, const char *stext, int sid, int eid, int percentedit); -void staticddl(struct ctlpos *cp, char *stext, +void staticddl(struct ctlpos *cp, const 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, +void combobox(struct ctlpos *cp, const char *text, int staticid, int listid); +void staticpassedit(struct ctlpos *cp, const char *stext, int sid, int eid, int percentedit); -void bigeditctrl(struct ctlpos *cp, char *stext, +void bigeditctrl(struct ctlpos *cp, const 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, +void ersatztab(struct ctlpos *cp, const char *stext, int sid, int lid, + int s2id); +void editbutton(struct ctlpos *cp, const char *stext, int sid, + int eid, const char *btext, int bid); +void sesssaver(struct ctlpos *cp, const 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 envsetter(struct ctlpos *cp, const char *stext, int sid, + const char *e1stext, int e1sid, int e1id, + const char *e2stext, int e2sid, int e2id, + int listid, const char *b1text, int b1id, + const char *b2text, int b2id); +void charclass(struct ctlpos *cp, const char *stext, int sid, int listid, + const char *btext, int bid, int eid, const char *s2text, + int s2id); +void colouredit(struct ctlpos *cp, const char *stext, int sid, int listid, + const char *btext, int bid, ...); void prefslist(struct prefslist *hdl, struct ctlpos *cp, int lines, - char *stext, int sid, int listid, int upbid, int dnbid); + const char *stext, int sid, int listid, int upbid, int dnbid); int handle_prefslist(struct prefslist *hdl, int *array, int maxmemb, bool 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 fwdsetter(struct ctlpos *cp, int listid, const char *stext, int sid, + const char *e1stext, int e1sid, int e1id, + const char *e2stext, int e2sid, int e2id, + const char *btext, int bid, + const char *r1text, int r1id, const char *r2text, int r2id); void dlg_auto_set_fixed_pitch_flag(dlgparam *dlg); bool dlg_get_fixed_pitch_flag(dlgparam *dlg); -- cgit v1.2.3 From 68985ecb1e5f2579d3922064c78058545add300b Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 30 Jul 2022 14:44:09 +0100 Subject: windows/controls.c: fix some nonstandard indentation. Happened to spot this while I was fixing the const issues in the previous commit. --- windows/controls.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/windows/controls.c b/windows/controls.c index a340902d..37efc9be 100644 --- a/windows/controls.c +++ b/windows/controls.c @@ -1581,16 +1581,16 @@ void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, 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]; - } + 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, @@ -1598,7 +1598,7 @@ void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, buttons, ctrl->radio.nbuttons); for (i = 0; i < ctrl->radio.nbuttons; i++) { - sfree((char *)buttons[i].text); + sfree((char *)buttons[i].text); } sfree(buttons); sfree(escaped); -- cgit v1.2.3 From 71f43af5470b731470d2376661eabb5e40629ea8 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 30 Jul 2022 14:49:11 +0100 Subject: test/ca.py: fix handling of RFC4716 public key files. I must have dashed off that branch of the key reading function without ever testing it, or I'd have noticed by now that it was looking for the wrong string to terminate the file. Ahem. --- test/ca.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/ca.py b/test/ca.py index 2ec24a31..ebd80599 100755 --- a/test/ca.py +++ b/test/ca.py @@ -114,7 +114,7 @@ def read_pubkey_file(fh): comment = val.strip("\r\n") line = next(lines) # Now expect lines of base64 data. - while line != "---- BEGIN SSH2 PUBLIC KEY ----": + while line != "---- END SSH2 PUBLIC KEY ----": b64buf.write(line) line = next(lines) -- cgit v1.2.3 From 6737a19072f39942a67708fe7353a38af35a787a Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 30 Jul 2022 16:10:45 +0100 Subject: cmdgen: human-readable certificate info dump. The recently added SeatDialogText type was just what I needed to add a method to the ssh_key vtable for dumping certificate information in a human-readable format. It will be good for displaying in a Windows dialog box as well as in cmdgen's text format. This commit introduces and implements the new method, and adds a --cert-info mode to command-line Unix PuTTYgen that uses it. The Windows side will follow shortly. --- cmdgen.c | 85 ++++++++++++++++++++- crypto/openssh-certs.c | 202 +++++++++++++++++++++++++++++++++++++++++++++++++ ssh.h | 3 + 3 files changed, 289 insertions(+), 1 deletion(-) diff --git a/cmdgen.c b/cmdgen.c index 044d86aa..963464ea 100644 --- a/cmdgen.c +++ b/cmdgen.c @@ -237,7 +237,7 @@ int main(int argc, char **argv) enum { NOKEYGEN, RSA1, RSA2, DSA, ECDSA, EDDSA } keytype = NOKEYGEN; char *outfile = NULL, *outfiletmp = NULL; enum { PRIVATE, PUBLIC, PUBLICO, FP, OPENSSH_AUTO, - OPENSSH_NEW, SSHCOM, TEXT } outtype = PRIVATE; + OPENSSH_NEW, SSHCOM, TEXT, CERTINFO } outtype = PRIVATE; int bits = -1; const char *comment = NULL; char *origcomment = NULL; @@ -368,6 +368,10 @@ int main(int argc, char **argv) } } else if (!strcmp(opt, "-dump")) { outtype = TEXT; + } else if (!strcmp(opt, "-cert-info") || + !strcmp(opt, "-certinfo") || + !strcmp(opt, "-cert_info")) { + outtype = CERTINFO; } else if (!strcmp(opt, "-primes")) { if (!val && argc > 1) --argc, val = *++argv; @@ -594,6 +598,8 @@ int main(int argc, char **argv) outtype = SSHCOM, sshver = 2; else if (!strcmp(p, "text")) outtype = TEXT; + else if (!strcmp(p, "cert-info")) + outtype = CERTINFO; else { fprintf(stderr, "puttygen: unknown output type `%s'\n", p); @@ -1524,6 +1530,83 @@ int main(int argc, char **argv) key_components_free(kc); break; } + + case CERTINFO: { + if (sshver == 1) { + fprintf(stderr, "puttygen: SSH-1 keys cannot contain " + "certificates\n"); + RETURN(1); + } + + const ssh_keyalg *alg; + ssh_key *sk; + bool sk_allocated = false; + + if (ssh2key) { + sk = ssh2key->key; + alg = ssh_key_alg(sk); + } else { + assert(ssh2blob); + ptrlen algname = pubkey_blob_to_alg_name( + ptrlen_from_strbuf(ssh2blob)); + alg = find_pubkey_alg_len(algname); + if (!alg) { + fprintf(stderr, "puttygen: cannot extract certificate info " + "from public key of unknown type '%.*s'\n", + PTRLEN_PRINTF(algname)); + RETURN(1); + } + sk = ssh_key_new_pub(alg, ptrlen_from_strbuf(ssh2blob)); + if (!sk) { + fprintf(stderr, "puttygen: unable to decode public key\n"); + RETURN(1); + } + sk_allocated = true; + } + + if (!alg->is_certificate) { + fprintf(stderr, "puttygen: key is not a certificate\n"); + } else { + SeatDialogText *text = ssh_key_cert_info(sk); + + FILE *fp; + if (outfile) { + fp = f_open(outfilename, "w", false); + if (!fp) { + fprintf(stderr, "unable to open output file\n"); + exit(1); + } + } else { + fp = stdout; + } + + for (SeatDialogTextItem *item = text->items, + *end = item+text->nitems; item < end; item++) { + switch (item->type) { + case SDT_MORE_INFO_KEY: + fprintf(fp, "%s", item->text); + break; + case SDT_MORE_INFO_VALUE_SHORT: + fprintf(fp, ": %s\n", item->text); + break; + case SDT_MORE_INFO_VALUE_BLOB: + fprintf(fp, ":\n%s\n", item->text); + break; + default: + break; + } + } + + if (outfile) + fclose(fp); + + seat_dialog_text_free(text); + } + + if (sk_allocated) + ssh_key_free(sk); + break; + } } out: diff --git a/crypto/openssh-certs.c b/crypto/openssh-certs.c index 73218e7c..5e86f928 100644 --- a/crypto/openssh-certs.c +++ b/crypto/openssh-certs.c @@ -3,6 +3,7 @@ */ #include "ssh.h" +#include "putty.h" enum { SSH_CERT_TYPE_USER = 1, @@ -203,6 +204,7 @@ static void opensshcert_private_blob(ssh_key *key, BinarySink *bs); static void opensshcert_openssh_blob(ssh_key *key, BinarySink *bs); static void opensshcert_ca_public_blob(ssh_key *key, BinarySink *bs); static void opensshcert_cert_id_string(ssh_key *key, BinarySink *bs); +static SeatDialogText *opensshcert_cert_info(ssh_key *key); static bool opensshcert_has_private(ssh_key *key); static char *opensshcert_cache_str(ssh_key *key); static key_components *opensshcert_components(ssh_key *key); @@ -263,6 +265,7 @@ static const ssh_keyalg *opensshcert_related_alg(const ssh_keyalg *self, .ca_public_blob = opensshcert_ca_public_blob, \ .check_cert = opensshcert_check_cert, \ .cert_id_string = opensshcert_cert_id_string, \ + .cert_info = opensshcert_cert_info, \ .pubkey_bits = opensshcert_pubkey_bits, \ .supported_flags = opensshcert_supported_flags, \ .alternate_ssh_id = opensshcert_alternate_ssh_id, \ @@ -666,6 +669,205 @@ static key_components *opensshcert_components(ssh_key *key) return kc; } +static SeatDialogText *opensshcert_cert_info(ssh_key *key) +{ + opensshcert_key *ck = container_of(key, opensshcert_key, sshk); + SeatDialogText *text = seat_dialog_text_new(); + strbuf *tmp = strbuf_new(); + + seat_dialog_text_append(text, SDT_MORE_INFO_KEY, + "Certificate type"); + switch (ck->type) { + case SSH_CERT_TYPE_HOST: + seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, + "host key"); + seat_dialog_text_append(text, SDT_MORE_INFO_KEY, + "Valid host names"); + break; + case SSH_CERT_TYPE_USER: + seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, + "user authentication key"); + seat_dialog_text_append(text, SDT_MORE_INFO_KEY, + "Valid user names"); + break; + default: + seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, + "unknown type %" PRIu32, ck->type); + seat_dialog_text_append(text, SDT_MORE_INFO_KEY, + "Valid principals"); + break; + } + + { + BinarySource src[1]; + BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf( + ck->valid_principals)); + const char *sep = ""; + strbuf_clear(tmp); + while (get_avail(src)) { + ptrlen principal = get_string(src); + if (get_err(src)) + break; + put_dataz(tmp, sep); + sep = ","; + put_datapl(tmp, principal); + } + seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, + "%s", tmp->s); + } + + seat_dialog_text_append(text, SDT_MORE_INFO_KEY, + "Validity period"); + strbuf_clear(tmp); + if (ck->valid_after == 0) { + if (ck->valid_before == 0xFFFFFFFFFFFFFFFF) { + put_dataz(tmp, "forever"); + } else { + put_dataz(tmp, "until "); + opensshcert_time_to_iso8601(BinarySink_UPCAST(tmp), + ck->valid_before); + } + } else { + if (ck->valid_before == 0xFFFFFFFFFFFFFFFF) { + put_dataz(tmp, "after "); + opensshcert_time_to_iso8601(BinarySink_UPCAST(tmp), + ck->valid_after); + } else { + opensshcert_time_to_iso8601(BinarySink_UPCAST(tmp), + ck->valid_after); + put_dataz(tmp, " - "); + opensshcert_time_to_iso8601(BinarySink_UPCAST(tmp), + ck->valid_before); + } + } + seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "%s", tmp->s); + + /* + * List critical options we know about. (This is everything listed + * in PROTOCOL.certkeys that isn't specific to U2F/FIDO key types + * that PuTTY doesn't currently support.) + */ + { + BinarySource src[1]; + BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf( + ck->critical_options)); + strbuf_clear(tmp); + while (get_avail(src)) { + ptrlen key = get_string(src); + ptrlen value = get_string(src); + if (get_err(src)) + break; + if (ck->type == SSH_CERT_TYPE_USER && + ptrlen_eq_string(key, "source-address")) { + BinarySource src2[1]; + BinarySource_BARE_INIT_PL(src2, value); + ptrlen addresslist = get_string(src2); + seat_dialog_text_append(text, SDT_MORE_INFO_KEY, + "Permitted client IP addresses"); + seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, + "%.*s", PTRLEN_PRINTF(addresslist)); + } else if (ck->type == SSH_CERT_TYPE_USER && + ptrlen_eq_string(key, "force-command")) { + BinarySource src2[1]; + BinarySource_BARE_INIT_PL(src2, value); + ptrlen command = get_string(src2); + seat_dialog_text_append(text, SDT_MORE_INFO_KEY, + "Forced remote command"); + seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, + "%.*s", PTRLEN_PRINTF(command)); + } + } + } + + /* + * List certificate extensions. Again, we go through everything in + * PROTOCOL.certkeys that isn't specific to U2F/FIDO key types. + * But we also flip the sense round for user-readability: I think + * it's more likely that the typical key will permit all these + * things, so we emit no output in that case, and only mention the + * things that _aren't_ enabled. + */ + + bool x11_ok = false, agent_ok = false, portfwd_ok = false; + bool pty_ok = false, user_rc_ok = false; + + { + BinarySource src[1]; + BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf( + ck->extensions)); + while (get_avail(src)) { + ptrlen key = get_string(src); + /* ptrlen value = */ get_string(src); // nothing needs this yet + if (get_err(src)) + break; + if (ptrlen_eq_string(key, "permit-X11-forwarding")) { + x11_ok = true; + } else if (ptrlen_eq_string(key, "permit-agent-forwarding")) { + agent_ok = true; + } else if (ptrlen_eq_string(key, "permit-port-forwarding")) { + portfwd_ok = true; + } else if (ptrlen_eq_string(key, "permit-pty")) { + pty_ok = true; + } else if (ptrlen_eq_string(key, "permit-user-rc")) { + user_rc_ok = true; + } + } + } + + if (ck->type == SSH_CERT_TYPE_USER) { + if (!x11_ok) { + seat_dialog_text_append(text, SDT_MORE_INFO_KEY, + "X11 forwarding permitted"); + seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "no"); + } + if (!agent_ok) { + seat_dialog_text_append(text, SDT_MORE_INFO_KEY, + "Agent forwarding permitted"); + seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "no"); + } + if (!portfwd_ok) { + seat_dialog_text_append(text, SDT_MORE_INFO_KEY, + "Port forwarding permitted"); + seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "no"); + } + if (!pty_ok) { + seat_dialog_text_append(text, SDT_MORE_INFO_KEY, + "PTY allocation permitted"); + seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "no"); + } + if (!user_rc_ok) { + seat_dialog_text_append(text, SDT_MORE_INFO_KEY, + "Running user ~/.ssh.rc permitted"); + seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "no"); + } + } + + seat_dialog_text_append(text, SDT_MORE_INFO_KEY, + "Certificate ID string"); + seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, + "%s", ck->key_id->s); + seat_dialog_text_append(text, SDT_MORE_INFO_KEY, + "Certificate serial number"); + seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, + "%" PRIu64, ck->serial); + + char *fp = ssh2_fingerprint_blob(ptrlen_from_strbuf(ck->signature_key), + SSH_FPTYPE_DEFAULT); + seat_dialog_text_append(text, SDT_MORE_INFO_KEY, + "Fingerprint of signing CA key"); + seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "%s", fp); + sfree(fp); + + fp = ssh2_fingerprint(ck->basekey, SSH_FPTYPE_DEFAULT); + seat_dialog_text_append(text, SDT_MORE_INFO_KEY, + "Fingerprint of underlying key"); + seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "%s", fp); + sfree(fp); + + strbuf_free(tmp); + return text; +} + static int opensshcert_pubkey_bits(const ssh_keyalg *self, ptrlen blob) { BinarySource src[1]; diff --git a/ssh.h b/ssh.h index 454c5449..bf4f04d7 100644 --- a/ssh.h +++ b/ssh.h @@ -851,6 +851,7 @@ struct ssh_keyalg { uint64_t time, const ca_options *opts, BinarySink *error); void (*cert_id_string)(ssh_key *key, BinarySink *); + SeatDialogText *(*cert_info)(ssh_key *key); /* 'Class methods' that don't deal with an ssh_key at all */ int (*pubkey_bits) (const ssh_keyalg *self, ptrlen blob); @@ -903,6 +904,8 @@ static inline void ssh_key_ca_public_blob(ssh_key *key, BinarySink *bs) { key->vt->ca_public_blob(key, bs); } static inline void ssh_key_cert_id_string(ssh_key *key, BinarySink *bs) { key->vt->cert_id_string(key, bs); } +static inline SeatDialogText *ssh_key_cert_info(ssh_key *key) +{ return key->vt->cert_info(key); } static inline bool ssh_key_check_cert( ssh_key *key, bool host, ptrlen principal, uint64_t time, const ca_options *opts, BinarySink *error) -- cgit v1.2.3 From 2bd2560a60f5f6c4a03bb645bfcf283f8cf47c83 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 30 Jul 2022 14:23:43 +0100 Subject: windows/puttygen.c: move control id enum further up the file. I'm about to want setupbigedit1 and setupbigedit2 to know the control ids themselves, and also add more controls to the enum, and it keeps the diffs more legible if I move the entire enum around unchanged _first_ and then start making small changes in the middle of it. --- windows/puttygen.c | 72 +++++++++++++++++++++++++++--------------------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/windows/puttygen.c b/windows/puttygen.c index ea3557b0..db7c5c87 100644 --- a/windows/puttygen.c +++ b/windows/puttygen.c @@ -687,6 +687,42 @@ static void hidemany(HWND hwnd, const int *ids, bool hideit) } } +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_KEYSSH2ECDSA, IDC_KEYSSH2EDDSA, + IDC_PRIMEGEN_PROB, IDC_PRIMEGEN_MAURER_SIMPLE, IDC_PRIMEGEN_MAURER_COMPLEX, + IDC_RSA_STRONG, + IDC_FPTYPE_SHA256, IDC_FPTYPE_MD5, + IDC_PPK_PARAMS, + IDC_BITSSTATIC, IDC_BITS, + IDC_ECCURVESTATIC, IDC_ECCURVE, + IDC_EDCURVESTATIC, IDC_EDCURVE, + IDC_NOTHINGSTATIC, + IDC_ABOUT, + IDC_GIVEHELP, + IDC_IMPORT, + IDC_EXPORT_OPENSSH_AUTO, IDC_EXPORT_OPENSSH_NEW, + IDC_EXPORT_SSHCOM, + IDC_ADDCERT, IDC_REMCERT, +}; + static void setupbigedit1(HWND hwnd, int id, int idstatic, RSAKey *key) { char *buffer = ssh1_pubkey_str(key); @@ -726,42 +762,6 @@ void old_keyfile_warning(void) MessageBox(NULL, message, mbtitle, MB_OK); } -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_KEYSSH2ECDSA, IDC_KEYSSH2EDDSA, - IDC_PRIMEGEN_PROB, IDC_PRIMEGEN_MAURER_SIMPLE, IDC_PRIMEGEN_MAURER_COMPLEX, - IDC_RSA_STRONG, - IDC_FPTYPE_SHA256, IDC_FPTYPE_MD5, - IDC_PPK_PARAMS, - IDC_BITSSTATIC, IDC_BITS, - IDC_ECCURVESTATIC, IDC_ECCURVE, - IDC_EDCURVESTATIC, IDC_EDCURVE, - IDC_NOTHINGSTATIC, - IDC_ABOUT, - IDC_GIVEHELP, - IDC_IMPORT, - IDC_EXPORT_OPENSSH_AUTO, IDC_EXPORT_OPENSSH_NEW, - IDC_EXPORT_SSHCOM, - IDC_ADDCERT, IDC_REMCERT, -}; - static const int nokey_ids[] = { IDC_NOKEY, 0 }; static const int generating_ids[] = { IDC_GENERATING, IDC_PROGRESS, 0 }; static const int gotkey_ids[] = { -- cgit v1.2.3 From b66c56f44129817540086c1c64e4d3e6631c31f4 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 30 Jul 2022 14:41:08 +0100 Subject: Windows PuTTYgen: also display certificate info. When PuTTYgen is holding a certified key, I don't think there's any sensible use for pasting around the full public key in authorized_keys format, because the whole point is that what you put in authorized_keys is 'please trust this CA' rather than the specific key. So instead I've reused the space in the dialog box to indicate that it's a certificate, and provide a 'more info' sub-dialog. --- windows/platform.h | 1 + windows/puttygen.c | 229 ++++++++++++++++++++++++++++++++++++++++++++++------ windows/puttygen.rc | 10 +++ 3 files changed, 214 insertions(+), 26 deletions(-) diff --git a/windows/platform.h b/windows/platform.h index 1e0cd1bc..60b8db77 100644 --- a/windows/platform.h +++ b/windows/platform.h @@ -444,6 +444,7 @@ void radioline(struct ctlpos *cp, const char *text, int id, int nacross, ...); void bareradioline(struct ctlpos *cp, int nacross, ...); void radiobig(struct ctlpos *cp, const char *text, int id, ...); void checkbox(struct ctlpos *cp, const char *text, int id); +void button(struct ctlpos *cp, const char *btext, int bid, bool defbtn); void statictext(struct ctlpos *cp, const char *text, int lines, int id); void staticbtn(struct ctlpos *cp, const char *stext, int sid, const char *btext, int bid); diff --git a/windows/puttygen.c b/windows/puttygen.c index db7c5c87..c7b39c97 100644 --- a/windows/puttygen.c +++ b/windows/puttygen.c @@ -696,6 +696,7 @@ enum { IDC_GENERATING, IDC_PROGRESS, IDC_PKSTATIC, IDC_KEYDISPLAY, + IDC_CERTSTATIC, IDC_CERTMOREINFO, IDC_FPSTATIC, IDC_FINGERPRINT, IDC_COMMENTSTATIC, IDC_COMMENTEDIT, IDC_PASSPHRASE1STATIC, IDC_PASSPHRASE1EDIT, @@ -723,23 +724,44 @@ enum { IDC_ADDCERT, IDC_REMCERT, }; -static void setupbigedit1(HWND hwnd, int id, int idstatic, RSAKey *key) +static void setupbigedit1(HWND hwnd, RSAKey *key) { - char *buffer = ssh1_pubkey_str(key); - SetDlgItemText(hwnd, id, buffer); - SetDlgItemText(hwnd, idstatic, + ShowWindow(GetDlgItem(hwnd, IDC_CERTSTATIC), SW_HIDE); + ShowWindow(GetDlgItem(hwnd, IDC_CERTMOREINFO), SW_HIDE); + ShowWindow(GetDlgItem(hwnd, IDC_PKSTATIC), SW_SHOW); + ShowWindow(GetDlgItem(hwnd, IDC_KEYDISPLAY), SW_SHOW); + + SetDlgItemText(hwnd, IDC_PKSTATIC, "&Public key for pasting into authorized_keys file:"); + + char *buffer = ssh1_pubkey_str(key); + SetDlgItemText(hwnd, IDC_KEYDISPLAY, buffer); sfree(buffer); } -static void setupbigedit2(HWND hwnd, int id, int idstatic, - ssh2_userkey *key) +static void setupbigedit2(HWND hwnd, ssh2_userkey *key) { - char *buffer = ssh2_pubkey_openssh_str(key); - SetDlgItemText(hwnd, id, buffer); - SetDlgItemText(hwnd, idstatic, "&Public key for pasting into " - "OpenSSH authorized_keys file:"); - sfree(buffer); + if (ssh_key_alg(key->key)->is_certificate) { + ShowWindow(GetDlgItem(hwnd, IDC_CERTSTATIC), SW_SHOW); + ShowWindow(GetDlgItem(hwnd, IDC_CERTMOREINFO), SW_SHOW); + ShowWindow(GetDlgItem(hwnd, IDC_PKSTATIC), SW_HIDE); + ShowWindow(GetDlgItem(hwnd, IDC_KEYDISPLAY), SW_HIDE); + + SetDlgItemText(hwnd, IDC_CERTSTATIC, + "This public key contains an OpenSSH certificate."); + } else { + ShowWindow(GetDlgItem(hwnd, IDC_CERTSTATIC), SW_HIDE); + ShowWindow(GetDlgItem(hwnd, IDC_CERTMOREINFO), SW_HIDE); + ShowWindow(GetDlgItem(hwnd, IDC_PKSTATIC), SW_SHOW); + ShowWindow(GetDlgItem(hwnd, IDC_KEYDISPLAY), SW_SHOW); + + SetDlgItemText(hwnd, IDC_PKSTATIC, "&Public key for pasting into " + "OpenSSH authorized_keys file:"); + + char *buffer = ssh2_pubkey_openssh_str(key); + SetDlgItemText(hwnd, IDC_KEYDISPLAY, buffer); + sfree(buffer); + } } /* @@ -764,13 +786,16 @@ void old_keyfile_warning(void) 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, +static const int gotkey_ids_unconditional[] = { IDC_FPSTATIC, IDC_FINGERPRINT, IDC_COMMENTSTATIC, IDC_COMMENTEDIT, IDC_PASSPHRASE1STATIC, IDC_PASSPHRASE1EDIT, IDC_PASSPHRASE2STATIC, IDC_PASSPHRASE2EDIT, 0 }; +static const int gotkey_ids_conditional[] = { + IDC_PKSTATIC, IDC_KEYDISPLAY, + IDC_CERTSTATIC, IDC_CERTMOREINFO, +}; /* * Small UI helper function to switch the state of the main dialog @@ -784,7 +809,8 @@ void ui_set_state(HWND hwnd, struct MainDlgState *state, int status) case 0: /* no key */ hidemany(hwnd, nokey_ids, false); hidemany(hwnd, generating_ids, true); - hidemany(hwnd, gotkey_ids, true); + hidemany(hwnd, gotkey_ids_unconditional, true); + hidemany(hwnd, gotkey_ids_conditional, true); EnableWindow(GetDlgItem(hwnd, IDC_GENERATE), 1); EnableWindow(GetDlgItem(hwnd, IDC_LOAD), 1); EnableWindow(GetDlgItem(hwnd, IDC_SAVE), 0); @@ -819,7 +845,8 @@ void ui_set_state(HWND hwnd, struct MainDlgState *state, int status) case 1: /* generating key */ hidemany(hwnd, nokey_ids, true); hidemany(hwnd, generating_ids, false); - hidemany(hwnd, gotkey_ids, true); + hidemany(hwnd, gotkey_ids_unconditional, true); + hidemany(hwnd, gotkey_ids_conditional, true); EnableWindow(GetDlgItem(hwnd, IDC_GENERATE), 0); EnableWindow(GetDlgItem(hwnd, IDC_LOAD), 0); EnableWindow(GetDlgItem(hwnd, IDC_SAVE), 0); @@ -854,7 +881,8 @@ void ui_set_state(HWND hwnd, struct MainDlgState *state, int status) case 2: hidemany(hwnd, nokey_ids, true); hidemany(hwnd, generating_ids, true); - hidemany(hwnd, gotkey_ids, false); + hidemany(hwnd, gotkey_ids_unconditional, false); + // gotkey_ids_conditional will be unhidden by setupbigedit2 EnableWindow(GetDlgItem(hwnd, IDC_GENERATE), 1); EnableWindow(GetDlgItem(hwnd, IDC_LOAD), 1); EnableWindow(GetDlgItem(hwnd, IDC_SAVE), 1); @@ -1042,7 +1070,7 @@ static void update_ui_after_ssh2_pubkey_change( SetDlgItemText(hwnd, IDC_FINGERPRINT, fp); sfree(fp); - setupbigedit2(hwnd, IDC_KEYDISPLAY, IDC_PKSTATIC, &state->ssh2key); + setupbigedit2(hwnd, &state->ssh2key); } static void update_ui_after_load(HWND hwnd, struct MainDlgState *state, @@ -1073,7 +1101,7 @@ static void update_ui_after_load(HWND hwnd, struct MainDlgState *state, * 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); + setupbigedit1(hwnd, &state->key); } else { state->ssh2 = true; state->commentptr = &state->ssh2key.comment; @@ -1340,6 +1368,130 @@ static void start_generating_key(HWND hwnd, struct MainDlgState *state) } } +/* + * Dialog-box function and context structure for the 'Certificate + * info' button. + */ +struct certinfo_dialog_ctx { + SeatDialogText *text; +}; + +static INT_PTR CertInfoProc(HWND hwnd, UINT msg, WPARAM wParam, + LPARAM lParam, void *vctx) +{ + struct certinfo_dialog_ctx *ctx = (struct certinfo_dialog_ctx *)vctx; + + switch (msg) { + case WM_INITDIALOG: { + int index = 100, y = 12; + + WPARAM font = SendMessage(hwnd, WM_GETFONT, 0, 0); + + const char *key = NULL; + for (SeatDialogTextItem *item = ctx->text->items, + *end = item + ctx->text->nitems; item < end; item++) { + switch (item->type) { + case SDT_MORE_INFO_KEY: + key = item->text; + break; + case SDT_MORE_INFO_VALUE_SHORT: + case SDT_MORE_INFO_VALUE_BLOB: { + RECT rk, rv; + DWORD editstyle = WS_CHILD | WS_VISIBLE | WS_TABSTOP | + ES_AUTOHSCROLL | ES_READONLY; + if (item->type == SDT_MORE_INFO_VALUE_BLOB) { + rk.left = 12; + rk.right = 426; + rk.top = y; + rk.bottom = 8; + y += 10; + + editstyle |= ES_MULTILINE; + rv.left = 12; + rv.right = 426; + rv.top = y; + rv.bottom = 64; + y += 68; + } else { + rk.left = 12; + rk.right = 130; + rk.top = y+2; + rk.bottom = 8; + + rv.left = 150; + rv.right = 438; + rv.top = y; + rv.bottom = 12; + + y += 16; + } + + MapDialogRect(hwnd, &rk); + HWND ctl = CreateWindowEx( + 0, "STATIC", key, WS_CHILD | WS_VISIBLE, + rk.left, rk.top, rk.right, rk.bottom, + hwnd, (HMENU)(ULONG_PTR)index++, hinst, NULL); + SendMessage(ctl, WM_SETFONT, font, MAKELPARAM(true, 0)); + + MapDialogRect(hwnd, &rv); + ctl = CreateWindowEx( + WS_EX_CLIENTEDGE, "EDIT", item->text, editstyle, + rv.left, rv.top, rv.right, rv.bottom, + hwnd, (HMENU)(ULONG_PTR)index++, hinst, NULL); + SendMessage(ctl, WM_SETFONT, font, MAKELPARAM(true, 0)); + break; + } + default: + break; + } + } + + /* + * Now resize the overall window, and move the Close button at + * the bottom. + */ + RECT r; + r.left = 176; + r.top = y + 10; + r.right = r.bottom = 0; + MapDialogRect(hwnd, &r); + HWND ctl = GetDlgItem(hwnd, IDOK); + SetWindowPos(ctl, NULL, r.left, r.top, 0, 0, + SWP_NOSIZE | SWP_NOREDRAW | SWP_NOZORDER); + + r.left = r.top = r.right = 0; + r.bottom = 300; + MapDialogRect(hwnd, &r); + int oldheight = r.bottom; + + r.left = r.top = r.right = 0; + r.bottom = y + 30; + MapDialogRect(hwnd, &r); + int newheight = r.bottom; + + GetWindowRect(hwnd, &r); + + SetWindowPos(hwnd, NULL, 0, 0, r.right - r.left, + r.bottom - r.top + newheight - oldheight, + SWP_NOMOVE | SWP_NOREDRAW | SWP_NOZORDER); + + ShowWindow(hwnd, SW_SHOWNORMAL); + return 1; + } + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDOK: + ShinyEndDialog(hwnd, 0); + return 0; + } + return 0; + case WM_CLOSE: + ShinyEndDialog(hwnd, 0); + return 0; + } + return 0; +} + /* * Dialog-box function for the main PuTTYgen dialog box. */ @@ -1469,6 +1621,18 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, cp2 = cp; statictext(&cp2, "", 1, IDC_GENERATING); progressbar(&cp2, IDC_PROGRESS); + cp2 = cp; + bigeditctrl(&cp2, NULL, -1, IDC_CERTSTATIC, 3); + { + HWND child = GetDlgItem(hwnd, IDC_CERTSTATIC); + LONG_PTR style = GetWindowLongPtr(child, GWL_STYLE); + style &= ~WS_VSCROLL; + SetWindowLongPtr(child, GWL_STYLE, style); + SendMessage(child, EM_SETREADONLY, true, 0); + } + MakeDlgItemBorderless(hwnd, IDC_CERTSTATIC); + cp2.xoff = cp2.width = cp2.width / 3; + button(&cp2, "Certificate info...", IDC_CERTMOREINFO, false); bigeditctrl(&cp, "&Public key for pasting into authorized_keys file:", IDC_PKSTATIC, IDC_KEYDISPLAY, 5); @@ -1695,11 +1859,9 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, *state->commentptr = snewn(len + 1, char); GetWindowText(editctl, *state->commentptr, len + 1); if (state->ssh2) { - setupbigedit2(hwnd, IDC_KEYDISPLAY, IDC_PKSTATIC, - &state->ssh2key); + setupbigedit2(hwnd, &state->ssh2key); } else { - setupbigedit1(hwnd, IDC_KEYDISPLAY, IDC_PKSTATIC, - &state->key); + setupbigedit1(hwnd, &state->key); } } } @@ -2039,6 +2201,23 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, remove_certificate(hwnd, state); } break; + case IDC_CERTMOREINFO: { + if (HIWORD(wParam) != BN_CLICKED) + break; + state = + (struct MainDlgState *) GetWindowLongPtr(hwnd, GWLP_USERDATA); + if (!state->key_exists || !state->ssh2 || !state->ssh2key.key) + break; + if (!ssh_key_alg(state->ssh2key.key)->is_certificate) + break; + + struct certinfo_dialog_ctx ctx[1]; + ctx->text = ssh_key_cert_info(state->ssh2key.key); + ShinyDialogBox(hinst, MAKEINTRESOURCE(216), + "PuTTYgenCertInfo", hwnd, CertInfoProc, ctx); + seat_dialog_text_free(ctx->text); + break; + } } return 0; case WM_DONEKEY: @@ -2116,11 +2295,9 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, * .ssh/authorized_keys2 on a Unix box. */ if (state->ssh2) { - setupbigedit2(hwnd, IDC_KEYDISPLAY, - IDC_PKSTATIC, &state->ssh2key); + setupbigedit2(hwnd, &state->ssh2key); } else { - setupbigedit1(hwnd, IDC_KEYDISPLAY, - IDC_PKSTATIC, &state->key); + setupbigedit1(hwnd, &state->key); } } /* diff --git a/windows/puttygen.rc b/windows/puttygen.rc index d1cef50d..fbe14fc3 100644 --- a/windows/puttygen.rc +++ b/windows/puttygen.rc @@ -82,6 +82,16 @@ BEGIN PUSHBUTTON "&Cancel", IDCANCEL, 134, 80, 40, 14 END +/* Accelerators used: clw */ +216 DIALOG DISCARDABLE 140, 40, 450, 300 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "PuTTYgen: certificate information" +FONT 8, "MS Shell Dlg" +CLASS "PuTTYgenCertInfo" +BEGIN + DEFPUSHBUTTON "&Close", IDOK, 201, 130, 48, 14 +END + #include "version.rc2" #ifndef NO_MANIFESTS -- cgit v1.2.3 From 932f6f5387fd3e7fe84d00a30d91d9105d212f42 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 1 Aug 2022 17:32:40 +0100 Subject: windows/pageant.rc: add missing symbolic constant. The main list box in the Pageant key list window was identified by a numeric control id, even though pageant-rc.h has a nice meaningful macro name for it (and pageant.c uses that). --- windows/pageant.rc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/windows/pageant.rc b/windows/pageant.rc index 5bea40e7..186eb314 100644 --- a/windows/pageant.rc +++ b/windows/pageant.rc @@ -51,7 +51,7 @@ STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "Pageant Key List" FONT 8, "MS Shell Dlg" BEGIN - LISTBOX 100, 10, 10, 420, 155, + LISTBOX IDC_KEYLIST_LISTBOX, 10, 10, 420, 155, LBS_EXTENDEDSEL | LBS_HASSTRINGS | LBS_USETABSTOPS | WS_VSCROLL | WS_TABSTOP PUSHBUTTON "&Add Key", IDC_KEYLIST_ADDKEY, 10, 187, 60, 14 PUSHBUTTON "Add Key (&encrypted)", IDC_KEYLIST_ADDKEY_ENC, 75, 187, 80, 14 -- cgit v1.2.3 From 3e7274fdad91069022994c1a3aa43c4aef6ef713 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 1 Aug 2022 17:45:55 +0100 Subject: Windows Pageant: use an owner-drawn list box for the key list. The main key list control in the Pageant window was previously an ordinary LBS_HASSTRINGS list box, with tab characters aligning the various parts of the key information into different columns. This was fragile because any mistake in the font metrics could have overflowed a tab stop and forced the text to move on to the next one. Now I've switched the list box into LBS_OWNERDRAWFIXED mode, which means that in place of a string for each list item I store a struct of my choice, and I have to draw the list-box entries myself by responding to WM_DRAWITEM. So now I'm drawing each component of the key information as a separate call to ExtTextOut (plus one TabbedTextOut to put the '(encrypted)' suffix on the end), which means that the tab stops are now guaranteed to appear where I tell them to. No functional change, for the moment: this is pure refactoring. As closely as I can tell, the appearance of the list box is pixel-for-pixel what it was before this commit. But it opens the door for two further improvements (neither one done in this commit): I can dynamically choose the tab stop locations based on querying the text metrics of the strings that will actually need to fit in the columns, and also, whatever reorganisation I need to do to make certificates fit sensibly in this list box can now be done without worrying about breaking anything terribly fragile. --- windows/pageant.c | 215 +++++++++++++++++++++++++++++++++++++++-------------- windows/pageant.rc | 2 +- 2 files changed, 159 insertions(+), 58 deletions(-) diff --git a/windows/pageant.c b/windows/pageant.c index c91d80c6..47992c28 100644 --- a/windows/pageant.c +++ b/windows/pageant.c @@ -317,14 +317,24 @@ struct keylist_update_ctx { bool enable_reencrypt_controls; }; +struct keylist_display_data { + strbuf *alg, *bits, *hash, *comment, *info; +}; + static void keylist_update_callback( void *vctx, char **fingerprints, const char *comment, uint32_t ext_flags, struct pageant_pubkey *key) { struct keylist_update_ctx *ctx = (struct keylist_update_ctx *)vctx; FingerprintType this_type = ssh2_pick_fingerprint(fingerprints, fptype); - const char *fingerprint = fingerprints[this_type]; - strbuf *listentry = strbuf_new(); + ptrlen fingerprint = ptrlen_from_asciz(fingerprints[this_type]); + + struct keylist_display_data *disp = snew(struct keylist_display_data); + disp->alg = strbuf_new(); + disp->bits = strbuf_new(); + disp->hash = strbuf_new(); + disp->comment = strbuf_new(); + disp->info = strbuf_new(); /* There is at least one key, so the controls for removing keys * should be enabled */ @@ -332,76 +342,55 @@ static void keylist_update_callback( switch (key->ssh_version) { case 1: { - put_fmt(listentry, "ssh1\t%s\t%s", fingerprint, comment); - /* - * Replace the space in the fingerprint (between bit count and - * hash) with a tab, for nice alignment in the box. + * Expect the fingerprint to contain two words: bit count and + * hash. */ - char *p = strchr(listentry->s, ' '); - if (p) - *p = '\t'; + put_dataz(disp->alg, "ssh1"); + put_datapl(disp->bits, ptrlen_get_word(&fingerprint, " ")); + put_datapl(disp->hash, ptrlen_get_word(&fingerprint, " ")); break; } case 2: { /* - * For nice alignment in the list box, we would ideally want - * every entry to align to the tab stop settings, and have a - * column for algorithm name, one for bit count, one for hex - * fingerprint, and one for key comment. - * - * Unfortunately, some of the algorithm names are so long that - * they overflow into the bit-count field. Fortunately, at the - * moment, those are _precisely_ the algorithm names that - * don't need a bit count displayed anyway (because for - * NIST-style ECDSA the bit count is mentioned in the - * algorithm name, and for ssh-ed25519 there is only one - * possible value anyway). So we fudge this by simply omitting - * the bit count field in that situation. - * - * This is fragile not only in the face of further key types - * that don't follow this pattern, but also in the face of - * font metrics changes - the Windows semantics for list box - * tab stops is that \t aligns to the next one you haven't - * already exceeded, so I have to guess when the key type will - * overflow past the bit-count tab stop and leave out a tab - * character. Urgh. + * Expect the fingerprint to contain three words: algorithm + * name, bit count, hash. + */ + put_datapl(disp->alg, ptrlen_get_word(&fingerprint, " ")); + put_datapl(disp->bits, ptrlen_get_word(&fingerprint, " ")); + put_datapl(disp->hash, ptrlen_get_word(&fingerprint, " ")); + + /* + * But we don't display the bit count if the algorithm isn't + * one of the ones where it can vary. That way, those + * algorithm names (which are generally longer) can safely + * overlap into the bits column without colliding with + * pointless text. */ const ssh_keyalg *alg = pubkey_blob_to_alg( ptrlen_from_strbuf(key->blob)); - - bool include_bit_count = (alg == &ssh_dsa || alg == &ssh_rsa); - - int wordnumber = 0; - for (const char *p = fingerprint; *p; p++) { - char c = *p; - if (c == ' ') { - if (wordnumber < 2) - c = '\t'; - wordnumber++; - } - if (include_bit_count || wordnumber != 1) - put_byte(listentry, c); - } - - put_fmt(listentry, "\t%s", comment); - break; + if (!(alg == &ssh_dsa || alg == &ssh_rsa)) + strbuf_clear(disp->bits); } } + put_dataz(disp->comment, comment); + if (ext_flags & LIST_EXTENDED_FLAG_HAS_NO_CLEARTEXT_KEY) { - put_fmt(listentry, "\t(encrypted)"); + put_fmt(disp->info, "(encrypted)"); } else if (ext_flags & LIST_EXTENDED_FLAG_HAS_ENCRYPTED_KEY_FILE) { - put_fmt(listentry, "\t(re-encryptable)"); + put_fmt(disp->info, "(re-encryptable)"); /* At least one key can be re-encrypted */ ctx->enable_reencrypt_controls = true; } + /* This list box is owner-drawn but doesn't have LBS_HASSTRINGS, + * so we can use LB_ADDSTRING to hand the list box our display + * info pointer */ SendDlgItemMessage(keylist, IDC_KEYLIST_LISTBOX, - LB_ADDSTRING, 0, (LPARAM)listentry->s); - strbuf_free(listentry); + LB_ADDSTRING, 0, (LPARAM)disp); } /* @@ -410,6 +399,25 @@ static void keylist_update_callback( void keylist_update(void) { if (keylist) { + /* + * Clear the previous list box content and free their display + * structures. + */ + { + int nitems = SendDlgItemMessage(keylist, IDC_KEYLIST_LISTBOX, + LB_GETCOUNT, 0, 0); + for (int i = 0; i < nitems; i++) { + struct keylist_display_data *disp = + (struct keylist_display_data *)SendDlgItemMessage( + keylist, IDC_KEYLIST_LISTBOX, LB_GETITEMDATA, i, 0); + strbuf_free(disp->alg); + strbuf_free(disp->bits); + strbuf_free(disp->hash); + strbuf_free(disp->comment); + strbuf_free(disp->info); + sfree(disp); + } + } SendDlgItemMessage(keylist, IDC_KEYLIST_LISTBOX, LB_RESETCONTENT, 0, 0); @@ -582,12 +590,6 @@ static INT_PTR CALLBACK KeyListProc(HWND hwnd, UINT msg, } keylist = hwnd; - { - static int tabs[] = { 35, 75, 300 }; - SendDlgItemMessage(hwnd, IDC_KEYLIST_LISTBOX, LB_SETTABSTOPS, - sizeof(tabs) / sizeof(*tabs), - (LPARAM) tabs); - } int selection = 0; for (size_t i = 0; i < lenof(fptypes); i++) { @@ -602,6 +604,105 @@ static INT_PTR CALLBACK KeyListProc(HWND hwnd, UINT msg, keylist_update(); return 0; } + case WM_MEASUREITEM: { + assert(wParam == IDC_KEYLIST_LISTBOX); + + MEASUREITEMSTRUCT *mi = (MEASUREITEMSTRUCT *)lParam; + + /* + * Our list box is owner-drawn, but we put normal text in it. + * So the line height is the same as it would normally be, + * which is 8 dialog units. + */ + RECT r; + r.left = r.right = r.top = 0; + r.bottom = 8; + MapDialogRect(hwnd, &r); + mi->itemHeight = r.bottom; + + return 0; + } + case WM_DRAWITEM: { + assert(wParam == IDC_KEYLIST_LISTBOX); + + DRAWITEMSTRUCT *di = (DRAWITEMSTRUCT *)lParam; + + if (di->itemAction == ODA_FOCUS) { + /* Just toggle the focus rectangle either on or off. This + * is an XOR-type function, so it's the same call in + * either case. */ + DrawFocusRect(di->hDC, &di->rcItem); + } else { + /* Draw the full text. */ + bool selected = (di->itemState & ODS_SELECTED); + COLORREF newfg = GetSysColor( + selected ? COLOR_HIGHLIGHTTEXT : COLOR_WINDOWTEXT); + COLORREF newbg = GetSysColor( + selected ? COLOR_HIGHLIGHT : COLOR_WINDOW); + COLORREF oldfg = SetTextColor(di->hDC, newfg); + COLORREF oldbg = SetBkColor(di->hDC, newbg); + + HFONT font = (HFONT)SendMessage(hwnd, WM_GETFONT, 0, 0); + HFONT oldfont = SelectObject(di->hDC, font); + + /* ExtTextOut("") is an easy way to just draw the + * background rectangle */ + ExtTextOut(di->hDC, di->rcItem.left, di->rcItem.top, + ETO_OPAQUE | ETO_CLIPPED, &di->rcItem, "", 0, NULL); + + struct keylist_display_data *disp = + (struct keylist_display_data *)di->itemData; + + RECT r; + + /* Apparently real list boxes start drawing at x=1, not x=0 */ + r.left = r.top = r.bottom = 0; + r.right = 1; + MapDialogRect(hwnd, &r); + ExtTextOut(di->hDC, di->rcItem.left + r.right, di->rcItem.top, + ETO_CLIPPED, &di->rcItem, disp->alg->s, + disp->alg->len, NULL); + + if (disp->bits->len) { + r.left = r.top = r.bottom = 0; + r.right = 35; + MapDialogRect(hwnd, &r); + ExtTextOut(di->hDC, di->rcItem.left + r.right, di->rcItem.top, + ETO_CLIPPED, &di->rcItem, disp->bits->s, + disp->bits->len, NULL); + } + + r.left = r.top = r.bottom = 0; + r.right = 75; + MapDialogRect(hwnd, &r); + ExtTextOut(di->hDC, di->rcItem.left + r.right, di->rcItem.top, + ETO_CLIPPED, &di->rcItem, disp->hash->s, + disp->hash->len, NULL); + + strbuf *sb = strbuf_new(); + put_datapl(sb, ptrlen_from_strbuf(disp->comment)); + if (disp->info->len) { + put_byte(sb, '\t'); + put_datapl(sb, ptrlen_from_strbuf(disp->info)); + } + + r.left = r.top = r.bottom = 0; + r.right = 300; + MapDialogRect(hwnd, &r); + TabbedTextOut(di->hDC, di->rcItem.left + r.right, di->rcItem.top, + sb->s, sb->len, 0, NULL, 0); + + strbuf_free(sb); + + SetTextColor(di->hDC, oldfg); + SetBkColor(di->hDC, oldbg); + SelectObject(di->hDC, oldfont); + + if (di->itemState & ODS_FOCUS) + DrawFocusRect(di->hDC, &di->rcItem); + } + return 0; + } case WM_COMMAND: switch (LOWORD(wParam)) { case IDOK: diff --git a/windows/pageant.rc b/windows/pageant.rc index 186eb314..57fdbcf4 100644 --- a/windows/pageant.rc +++ b/windows/pageant.rc @@ -52,7 +52,7 @@ CAPTION "Pageant Key List" FONT 8, "MS Shell Dlg" BEGIN LISTBOX IDC_KEYLIST_LISTBOX, 10, 10, 420, 155, - LBS_EXTENDEDSEL | LBS_HASSTRINGS | LBS_USETABSTOPS | WS_VSCROLL | WS_TABSTOP + LBS_EXTENDEDSEL | LBS_OWNERDRAWFIXED | WS_VSCROLL | WS_TABSTOP PUSHBUTTON "&Add Key", IDC_KEYLIST_ADDKEY, 10, 187, 60, 14 PUSHBUTTON "Add Key (&encrypted)", IDC_KEYLIST_ADDKEY_ENC, 75, 187, 80, 14 PUSHBUTTON "Re-e&ncrypt", IDC_KEYLIST_REENCRYPT, 315, 187, 60, 14 -- cgit v1.2.3 From fea08bb24499dc76e3fb58895ca0f015714a1d53 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Tue, 2 Aug 2022 17:54:47 +0100 Subject: Windows Pageant: use nicer key-type strings. If you load a certified key into Windows Pageant, the official SSH id for the key type is so long that it overflows its space in the list box and overlaps the key fingerprint hash. This commit introduces yet another footling little ssh_keyalg method which returns a shorter human-readable description of the key type, and uses that in the Windows Pageant list box only. (Not in the Unix Pageant list, though, because being output to stdout, that seems like something people are more likely to want to machine-read, which firstly means we shouldn't change it lightly, and secondly, if we did change it we'd want to avoid having a variable number of spaces in the replacement key type text.) --- crypto/dsa.c | 3 +++ crypto/ecc-ssh.c | 25 ++++++++++++++++++++----- crypto/openssh-certs.c | 10 ++++++++++ crypto/rsa.c | 3 +++ ssh.h | 3 +++ windows/pageant.c | 20 ++++++++++++++++---- 6 files changed, 55 insertions(+), 9 deletions(-) diff --git a/crypto/dsa.c b/crypto/dsa.c index e304a38f..c1514ac0 100644 --- a/crypto/dsa.c +++ b/crypto/dsa.c @@ -490,6 +490,8 @@ static void dsa_sign(ssh_key *key, ptrlen data, unsigned flags, BinarySink *bs) mp_free(s); } +static char *dsa_alg_desc(const ssh_keyalg *self) { return dupstr("DSA"); } + const ssh_keyalg ssh_dsa = { .new_pub = dsa_new_pub, .new_priv = dsa_new_priv, @@ -508,6 +510,7 @@ const ssh_keyalg ssh_dsa = { .pubkey_bits = dsa_pubkey_bits, .supported_flags = nullkey_supported_flags, .alternate_ssh_id = nullkey_alternate_ssh_id, + .alg_desc = dsa_alg_desc, .ssh_id = "ssh-dss", .cache_id = "dss", }; diff --git a/crypto/ecc-ssh.c b/crypto/ecc-ssh.c index 17fde8e9..feab9d01 100644 --- a/crypto/ecc-ssh.c +++ b/crypto/ecc-ssh.c @@ -325,6 +325,9 @@ struct ecsign_extra { const unsigned char *oid; int oidlen; + /* Human-readable algorithm description */ + const char *alg_desc; + /* Some EdDSA instances prefix a string to all hash preimages, to * disambiguate which signature variant they're being used with */ ptrlen hash_prefix; @@ -1251,9 +1254,16 @@ static void eddsa_sign(ssh_key *key, ptrlen data, mp_free(s); } +static char *ec_alg_desc(const ssh_keyalg *self) +{ + const struct ecsign_extra *extra = + (const struct ecsign_extra *)self->extra; + return dupstr(extra->alg_desc); +} + static const struct ecsign_extra sign_extra_ed25519 = { ec_ed25519, &ssh_sha512, - NULL, 0, PTRLEN_DECL_LITERAL(""), + NULL, 0, "Ed25519", PTRLEN_DECL_LITERAL(""), }; const ssh_keyalg ssh_ecdsa_ed25519 = { .new_pub = eddsa_new_pub, @@ -1273,6 +1283,7 @@ const ssh_keyalg ssh_ecdsa_ed25519 = { .pubkey_bits = ec_shared_pubkey_bits, .supported_flags = nullkey_supported_flags, .alternate_ssh_id = nullkey_alternate_ssh_id, + .alg_desc = ec_alg_desc, .ssh_id = "ssh-ed25519", .cache_id = "ssh-ed25519", .extra = &sign_extra_ed25519, @@ -1280,7 +1291,7 @@ const ssh_keyalg ssh_ecdsa_ed25519 = { static const struct ecsign_extra sign_extra_ed448 = { ec_ed448, &ssh_shake256_114bytes, - NULL, 0, PTRLEN_DECL_LITERAL("SigEd448\0\0"), + NULL, 0, "Ed448", PTRLEN_DECL_LITERAL("SigEd448\0\0"), }; const ssh_keyalg ssh_ecdsa_ed448 = { .new_pub = eddsa_new_pub, @@ -1300,6 +1311,7 @@ const ssh_keyalg ssh_ecdsa_ed448 = { .pubkey_bits = ec_shared_pubkey_bits, .supported_flags = nullkey_supported_flags, .alternate_ssh_id = nullkey_alternate_ssh_id, + .alg_desc = ec_alg_desc, .ssh_id = "ssh-ed448", .cache_id = "ssh-ed448", .extra = &sign_extra_ed448, @@ -1311,7 +1323,7 @@ static const unsigned char nistp256_oid[] = { }; static const struct ecsign_extra sign_extra_nistp256 = { ec_p256, &ssh_sha256, - nistp256_oid, lenof(nistp256_oid), + nistp256_oid, lenof(nistp256_oid), "NIST p256", }; const ssh_keyalg ssh_ecdsa_nistp256 = { .new_pub = ecdsa_new_pub, @@ -1331,6 +1343,7 @@ const ssh_keyalg ssh_ecdsa_nistp256 = { .pubkey_bits = ec_shared_pubkey_bits, .supported_flags = nullkey_supported_flags, .alternate_ssh_id = nullkey_alternate_ssh_id, + .alg_desc = ec_alg_desc, .ssh_id = "ecdsa-sha2-nistp256", .cache_id = "ecdsa-sha2-nistp256", .extra = &sign_extra_nistp256, @@ -1342,7 +1355,7 @@ static const unsigned char nistp384_oid[] = { }; static const struct ecsign_extra sign_extra_nistp384 = { ec_p384, &ssh_sha384, - nistp384_oid, lenof(nistp384_oid), + nistp384_oid, lenof(nistp384_oid), "NIST p384", }; const ssh_keyalg ssh_ecdsa_nistp384 = { .new_pub = ecdsa_new_pub, @@ -1362,6 +1375,7 @@ const ssh_keyalg ssh_ecdsa_nistp384 = { .pubkey_bits = ec_shared_pubkey_bits, .supported_flags = nullkey_supported_flags, .alternate_ssh_id = nullkey_alternate_ssh_id, + .alg_desc = ec_alg_desc, .ssh_id = "ecdsa-sha2-nistp384", .cache_id = "ecdsa-sha2-nistp384", .extra = &sign_extra_nistp384, @@ -1373,7 +1387,7 @@ static const unsigned char nistp521_oid[] = { }; static const struct ecsign_extra sign_extra_nistp521 = { ec_p521, &ssh_sha512, - nistp521_oid, lenof(nistp521_oid), + nistp521_oid, lenof(nistp521_oid), "NIST p521", }; const ssh_keyalg ssh_ecdsa_nistp521 = { .new_pub = ecdsa_new_pub, @@ -1393,6 +1407,7 @@ const ssh_keyalg ssh_ecdsa_nistp521 = { .pubkey_bits = ec_shared_pubkey_bits, .supported_flags = nullkey_supported_flags, .alternate_ssh_id = nullkey_alternate_ssh_id, + .alg_desc = ec_alg_desc, .ssh_id = "ecdsa-sha2-nistp521", .cache_id = "ecdsa-sha2-nistp521", .extra = &sign_extra_nistp521, diff --git a/crypto/openssh-certs.c b/crypto/openssh-certs.c index 5e86f928..351b17d6 100644 --- a/crypto/openssh-certs.c +++ b/crypto/openssh-certs.c @@ -216,6 +216,7 @@ static int opensshcert_pubkey_bits(const ssh_keyalg *self, ptrlen blob); static unsigned opensshcert_supported_flags(const ssh_keyalg *self); static const char *opensshcert_alternate_ssh_id(const ssh_keyalg *self, unsigned flags); +static char *opensshcert_alg_desc(const ssh_keyalg *self); static const ssh_keyalg *opensshcert_related_alg(const ssh_keyalg *self, const ssh_keyalg *base); @@ -269,6 +270,7 @@ static const ssh_keyalg *opensshcert_related_alg(const ssh_keyalg *self, .pubkey_bits = opensshcert_pubkey_bits, \ .supported_flags = opensshcert_supported_flags, \ .alternate_ssh_id = opensshcert_alternate_ssh_id, \ + .alg_desc = opensshcert_alg_desc, \ .related_alg = opensshcert_related_alg, \ .ssh_id = ssh_alg_id_prefix "-cert-v01@openssh.com", \ .cache_id = "opensshcert-" ssh_key_id_prefix, \ @@ -898,6 +900,14 @@ static const char *opensshcert_alternate_ssh_id(const ssh_keyalg *self, return self->ssh_id; } +static char *opensshcert_alg_desc(const ssh_keyalg *self) +{ + char *base_desc = ssh_keyalg_desc(self->base_alg); + char *our_desc = dupcat(base_desc, " cert"); + sfree(base_desc); + return our_desc; +} + static const ssh_keyalg *opensshcert_related_alg(const ssh_keyalg *self, const ssh_keyalg *base) { diff --git a/crypto/rsa.c b/crypto/rsa.c index 635c9efb..88748288 100644 --- a/crypto/rsa.c +++ b/crypto/rsa.c @@ -859,6 +859,8 @@ const char *ssh_rsa_alternate_ssh_id(const ssh_keyalg *self, unsigned flags) return self->ssh_id; } +static char *rsa2_alg_desc(const ssh_keyalg *self) { return dupstr("RSA"); } + static const struct ssh2_rsa_extra rsa_extra = { 0 }, rsa_sha256_extra = { SSH_AGENT_RSA_SHA2_256 }, @@ -880,6 +882,7 @@ static const struct ssh2_rsa_extra .components = rsa2_components, \ .base_key = nullkey_base_key, \ .pubkey_bits = rsa2_pubkey_bits, \ + .alg_desc = rsa2_alg_desc, \ .cache_id = "rsa2" const ssh_keyalg ssh_rsa = { diff --git a/ssh.h b/ssh.h index bf4f04d7..fb9d7453 100644 --- a/ssh.h +++ b/ssh.h @@ -857,6 +857,7 @@ struct ssh_keyalg { int (*pubkey_bits) (const ssh_keyalg *self, ptrlen blob); unsigned (*supported_flags) (const ssh_keyalg *self); const char *(*alternate_ssh_id) (const ssh_keyalg *self, unsigned flags); + char *(*alg_desc)(const ssh_keyalg *self); /* The following methods can be NULL if !is_certificate */ const ssh_keyalg *(*related_alg)(const ssh_keyalg *self, const ssh_keyalg *base); @@ -925,6 +926,8 @@ static inline const unsigned ssh_keyalg_supported_flags(const ssh_keyalg *self) static inline const char *ssh_keyalg_alternate_ssh_id( const ssh_keyalg *self, unsigned flags) { return self->alternate_ssh_id(self, flags); } +static inline char *ssh_keyalg_desc(const ssh_keyalg *self) +{ return self->alg_desc(self); } static inline const ssh_keyalg *ssh_keyalg_related_alg( const ssh_keyalg *self, const ssh_keyalg *base) { return self->related_alg(self, base); } diff --git a/windows/pageant.c b/windows/pageant.c index 47992c28..aea98389 100644 --- a/windows/pageant.c +++ b/windows/pageant.c @@ -346,7 +346,7 @@ static void keylist_update_callback( * Expect the fingerprint to contain two words: bit count and * hash. */ - put_dataz(disp->alg, "ssh1"); + put_dataz(disp->alg, "SSH-1"); put_datapl(disp->bits, ptrlen_get_word(&fingerprint, " ")); put_datapl(disp->hash, ptrlen_get_word(&fingerprint, " ")); break; @@ -357,7 +357,21 @@ static void keylist_update_callback( * Expect the fingerprint to contain three words: algorithm * name, bit count, hash. */ - put_datapl(disp->alg, ptrlen_get_word(&fingerprint, " ")); + const ssh_keyalg *alg = pubkey_blob_to_alg( + ptrlen_from_strbuf(key->blob)); + + ptrlen keytype_word = ptrlen_get_word(&fingerprint, " "); + if (alg) { + /* Use our own human-legible algorithm names if available, + * because they fit better in the space. (Certificate key + * algorithm names in particular are terribly long.) */ + char *alg_desc = ssh_keyalg_desc(alg); + put_dataz(disp->alg, alg_desc); + sfree(alg_desc); + } else { + put_datapl(disp->alg, keytype_word); + } + put_datapl(disp->bits, ptrlen_get_word(&fingerprint, " ")); put_datapl(disp->hash, ptrlen_get_word(&fingerprint, " ")); @@ -368,8 +382,6 @@ static void keylist_update_callback( * overlap into the bits column without colliding with * pointless text. */ - const ssh_keyalg *alg = pubkey_blob_to_alg( - ptrlen_from_strbuf(key->blob)); if (!(alg == &ssh_dsa || alg == &ssh_rsa)) strbuf_clear(disp->bits); } -- cgit v1.2.3 From ff2ffa539c4ce6051fc9010abacb05510bded46f Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Tue, 2 Aug 2022 18:14:06 +0100 Subject: Windows Pageant: display RSA/DSA cert bit counts. The test in the Pageant list box code for whether we should display the bit count of a key was done by checking specifically for ssh_rsa or ssh_dsa, which of course meant that it didn't catch the certified versions of those keys. Now there's yet another footling ssh_keyalg method that asks the question 'is it worth displaying the bit count?', to which RSA and DSA answer yes, and the opensshcert family delegates to its base key type, so that RSA and DSA certified keys also answer yes. (This isn't the same as ssh_key_public_bits(alg, blob) >= 0. All supported public key algorithms _can_ display a bit count if called on. But only in RSA and DSA is it configurable, and therefore worth bothering to print in the list box.) Also in this commit, I've fixed a bug in the certificate implementation of public_bits, which was passing a wrongly formatted public blob to the underlying key. (Done by factoring out the code from opensshcert_new_shared which constructed the _correct_ public blob, and reusing it in public_bits to do the same job.) --- crypto/dsa.c | 1 + crypto/ecc-ssh.c | 5 +++++ crypto/openssh-certs.c | 50 +++++++++++++++++++++++++++++++++----------------- crypto/rsa.c | 1 + ssh.h | 5 +++++ utils/nullkey.c | 3 +++ windows/pageant.c | 15 ++++----------- 7 files changed, 52 insertions(+), 28 deletions(-) diff --git a/crypto/dsa.c b/crypto/dsa.c index c1514ac0..bd005957 100644 --- a/crypto/dsa.c +++ b/crypto/dsa.c @@ -511,6 +511,7 @@ const ssh_keyalg ssh_dsa = { .supported_flags = nullkey_supported_flags, .alternate_ssh_id = nullkey_alternate_ssh_id, .alg_desc = dsa_alg_desc, + .variable_size = nullkey_variable_size_yes, .ssh_id = "ssh-dss", .cache_id = "dss", }; diff --git a/crypto/ecc-ssh.c b/crypto/ecc-ssh.c index feab9d01..6f232c79 100644 --- a/crypto/ecc-ssh.c +++ b/crypto/ecc-ssh.c @@ -1284,6 +1284,7 @@ const ssh_keyalg ssh_ecdsa_ed25519 = { .supported_flags = nullkey_supported_flags, .alternate_ssh_id = nullkey_alternate_ssh_id, .alg_desc = ec_alg_desc, + .variable_size = nullkey_variable_size_no, .ssh_id = "ssh-ed25519", .cache_id = "ssh-ed25519", .extra = &sign_extra_ed25519, @@ -1312,6 +1313,7 @@ const ssh_keyalg ssh_ecdsa_ed448 = { .supported_flags = nullkey_supported_flags, .alternate_ssh_id = nullkey_alternate_ssh_id, .alg_desc = ec_alg_desc, + .variable_size = nullkey_variable_size_no, .ssh_id = "ssh-ed448", .cache_id = "ssh-ed448", .extra = &sign_extra_ed448, @@ -1344,6 +1346,7 @@ const ssh_keyalg ssh_ecdsa_nistp256 = { .supported_flags = nullkey_supported_flags, .alternate_ssh_id = nullkey_alternate_ssh_id, .alg_desc = ec_alg_desc, + .variable_size = nullkey_variable_size_no, .ssh_id = "ecdsa-sha2-nistp256", .cache_id = "ecdsa-sha2-nistp256", .extra = &sign_extra_nistp256, @@ -1376,6 +1379,7 @@ const ssh_keyalg ssh_ecdsa_nistp384 = { .supported_flags = nullkey_supported_flags, .alternate_ssh_id = nullkey_alternate_ssh_id, .alg_desc = ec_alg_desc, + .variable_size = nullkey_variable_size_no, .ssh_id = "ecdsa-sha2-nistp384", .cache_id = "ecdsa-sha2-nistp384", .extra = &sign_extra_nistp384, @@ -1408,6 +1412,7 @@ const ssh_keyalg ssh_ecdsa_nistp521 = { .supported_flags = nullkey_supported_flags, .alternate_ssh_id = nullkey_alternate_ssh_id, .alg_desc = ec_alg_desc, + .variable_size = nullkey_variable_size_no, .ssh_id = "ecdsa-sha2-nistp521", .cache_id = "ecdsa-sha2-nistp521", .extra = &sign_extra_nistp521, diff --git a/crypto/openssh-certs.c b/crypto/openssh-certs.c index 351b17d6..e37e42b2 100644 --- a/crypto/openssh-certs.c +++ b/crypto/openssh-certs.c @@ -217,6 +217,7 @@ static unsigned opensshcert_supported_flags(const ssh_keyalg *self); static const char *opensshcert_alternate_ssh_id(const ssh_keyalg *self, unsigned flags); static char *opensshcert_alg_desc(const ssh_keyalg *self); +static bool opensshcert_variable_size(const ssh_keyalg *self); static const ssh_keyalg *opensshcert_related_alg(const ssh_keyalg *self, const ssh_keyalg *base); @@ -271,6 +272,7 @@ static const ssh_keyalg *opensshcert_related_alg(const ssh_keyalg *self, .supported_flags = opensshcert_supported_flags, \ .alternate_ssh_id = opensshcert_alternate_ssh_id, \ .alg_desc = opensshcert_alg_desc, \ + .variable_size = opensshcert_variable_size, \ .related_alg = opensshcert_related_alg, \ .ssh_id = ssh_alg_id_prefix "-cert-v01@openssh.com", \ .cache_id = "opensshcert-" ssh_key_id_prefix, \ @@ -287,6 +289,26 @@ static const ssh_keyalg *const opensshcert_all_keyalgs[] = { }; #undef KEYALG_LIST_ENTRY +static strbuf *get_base_public_blob(BinarySource *src, + const opensshcert_extra *extra) +{ + strbuf *basepub = strbuf_new(); + put_stringz(basepub, extra->base_key_ssh_id); + + /* Make the base public key blob out of the public key + * material in the certificate. This invocation of the + * blobtrans system doesn't do any format translation, but it + * does ensure that the right amount of data is copied so that + * src ends up in the right position to read the remaining + * certificate fields. */ + BLOBTRANS_DECLARE(bt); + blobtrans_read(bt, src, extra->pub_fmt); + blobtrans_write(bt, BinarySink_UPCAST(basepub), extra->pub_fmt); + blobtrans_clear(bt); + + return basepub; +} + static opensshcert_key *opensshcert_new_shared( const ssh_keyalg *self, ptrlen blob, strbuf **basepub_out) { @@ -304,21 +326,7 @@ static opensshcert_key *opensshcert_new_shared( ck->sshk.vt = self; ck->nonce = strbuf_dup(get_string(src)); - strbuf *basepub = strbuf_new(); - { - put_stringz(basepub, extra->base_key_ssh_id); - - /* Make the base public key blob out of the public key - * material in the certificate. This invocation of the - * blobtrans system doesn't do any format translation, but it - * does ensure that the right amount of data is copied so that - * src ends up in the right position to read the remaining - * certificate fields. */ - BLOBTRANS_DECLARE(bt); - blobtrans_read(bt, src, extra->pub_fmt); - blobtrans_write(bt, BinarySink_UPCAST(basepub), extra->pub_fmt); - blobtrans_clear(bt); - } + strbuf *basepub = get_base_public_blob(src, extra); ck->serial = get_uint64(src); ck->type = get_uint32(src); ck->key_id = strbuf_dup(get_string(src)); @@ -877,8 +885,11 @@ static int opensshcert_pubkey_bits(const ssh_keyalg *self, ptrlen blob) get_string(src); /* key type */ get_string(src); /* nonce */ - return ssh_key_public_bits( - self->base_alg, make_ptrlen(get_ptr(src), get_avail(src))); + strbuf *basepub = get_base_public_blob(src, self->extra); + int bits = ssh_key_public_bits( + self->base_alg, ptrlen_from_strbuf(basepub)); + strbuf_free(basepub); + return bits; } static unsigned opensshcert_supported_flags(const ssh_keyalg *self) @@ -908,6 +919,11 @@ static char *opensshcert_alg_desc(const ssh_keyalg *self) return our_desc; } +static bool opensshcert_variable_size(const ssh_keyalg *self) +{ + return ssh_keyalg_variable_size(self->base_alg); +} + static const ssh_keyalg *opensshcert_related_alg(const ssh_keyalg *self, const ssh_keyalg *base) { diff --git a/crypto/rsa.c b/crypto/rsa.c index 88748288..2196d1ec 100644 --- a/crypto/rsa.c +++ b/crypto/rsa.c @@ -883,6 +883,7 @@ static const struct ssh2_rsa_extra .base_key = nullkey_base_key, \ .pubkey_bits = rsa2_pubkey_bits, \ .alg_desc = rsa2_alg_desc, \ + .variable_size = nullkey_variable_size_yes, \ .cache_id = "rsa2" const ssh_keyalg ssh_rsa = { diff --git a/ssh.h b/ssh.h index fb9d7453..bd639566 100644 --- a/ssh.h +++ b/ssh.h @@ -858,6 +858,7 @@ struct ssh_keyalg { unsigned (*supported_flags) (const ssh_keyalg *self); const char *(*alternate_ssh_id) (const ssh_keyalg *self, unsigned flags); char *(*alg_desc)(const ssh_keyalg *self); + bool (*variable_size)(const ssh_keyalg *self); /* The following methods can be NULL if !is_certificate */ const ssh_keyalg *(*related_alg)(const ssh_keyalg *self, const ssh_keyalg *base); @@ -928,6 +929,8 @@ static inline const char *ssh_keyalg_alternate_ssh_id( { return self->alternate_ssh_id(self, flags); } static inline char *ssh_keyalg_desc(const ssh_keyalg *self) { return self->alg_desc(self); } +static inline bool ssh_keyalg_variable_size(const ssh_keyalg *self) +{ return self->variable_size(self); } static inline const ssh_keyalg *ssh_keyalg_related_alg( const ssh_keyalg *self, const ssh_keyalg *base) { return self->related_alg(self, base); } @@ -936,6 +939,8 @@ static inline const ssh_keyalg *ssh_keyalg_related_alg( unsigned nullkey_supported_flags(const ssh_keyalg *self); const char *nullkey_alternate_ssh_id(const ssh_keyalg *self, unsigned flags); ssh_key *nullkey_base_key(ssh_key *key); +bool nullkey_variable_size_no(const ssh_keyalg *self); +bool nullkey_variable_size_yes(const ssh_keyalg *self); /* Utility functions implemented centrally */ ssh_key *ssh_key_clone(ssh_key *key); diff --git a/utils/nullkey.c b/utils/nullkey.c index 1b01c891..dae5c1bb 100644 --- a/utils/nullkey.c +++ b/utils/nullkey.c @@ -17,3 +17,6 @@ ssh_key *nullkey_base_key(ssh_key *key) /* When a key is not certified, it is its own base */ return key; } + +bool nullkey_variable_size_no(const ssh_keyalg *self) { return false; } +bool nullkey_variable_size_yes(const ssh_keyalg *self) { return true; } diff --git a/windows/pageant.c b/windows/pageant.c index aea98389..05447f5f 100644 --- a/windows/pageant.c +++ b/windows/pageant.c @@ -372,18 +372,11 @@ static void keylist_update_callback( put_datapl(disp->alg, keytype_word); } - put_datapl(disp->bits, ptrlen_get_word(&fingerprint, " ")); - put_datapl(disp->hash, ptrlen_get_word(&fingerprint, " ")); + ptrlen bits_word = ptrlen_get_word(&fingerprint, " "); + if (ssh_keyalg_variable_size(alg)) + put_datapl(disp->bits, bits_word); - /* - * But we don't display the bit count if the algorithm isn't - * one of the ones where it can vary. That way, those - * algorithm names (which are generally longer) can safely - * overlap into the bits column without colliding with - * pointless text. - */ - if (!(alg == &ssh_dsa || alg == &ssh_rsa)) - strbuf_clear(disp->bits); + put_datapl(disp->hash, ptrlen_get_word(&fingerprint, " ")); } } -- cgit v1.2.3 From b6d7c81d43a7d54cbdd4dcee75099248227627f9 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Tue, 2 Aug 2022 18:34:18 +0100 Subject: Windows Pageant: dynamically size list box columns. The fixed tab stops that we used to use in the old LBS_HASSTRINGS list box, and that I carefully replicated in the new owner-drawn version, are no more! Now, every time we refresh the key list, we actually _measure_ the maximum size of string that needs to fit into each column, and size the columns based on that. Now I don't have to worry any more about whether the set of algorithm names might one day overflow the fixed column width, or whether a particularly unlucky choice of key with lots of wide letters like M and W in its base64-encoded SHA256 hash might do the same. Also, the previous column sizes were pessimistic (for reason of exactly that worry), so this change generally moves things over towards the left of the list box - which means there's now room for longer key comments, and more chance of the suffixes '(encrypted)' or '(re-encryptable)' being visible on the right. --- windows/pageant.c | 58 +++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 41 insertions(+), 17 deletions(-) diff --git a/windows/pageant.c b/windows/pageant.c index 05447f5f..accf0ccb 100644 --- a/windows/pageant.c +++ b/windows/pageant.c @@ -313,6 +313,8 @@ void old_keyfile_warning(void) } struct keylist_update_ctx { + HDC hdc; + int algbitswidth, algwidth, bitswidth, hashwidth; bool enable_remove_controls; bool enable_reencrypt_controls; }; @@ -382,6 +384,19 @@ static void keylist_update_callback( put_dataz(disp->comment, comment); + SIZE sz; + if (disp->bits->len) { + GetTextExtentPoint32(ctx->hdc, disp->alg->s, disp->alg->len, &sz); + if (ctx->algwidth < sz.cx) ctx->algwidth = sz.cx; + GetTextExtentPoint32(ctx->hdc, disp->bits->s, disp->bits->len, &sz); + if (ctx->bitswidth < sz.cx) ctx->bitswidth = sz.cx; + } else { + GetTextExtentPoint32(ctx->hdc, disp->alg->s, disp->alg->len, &sz); + if (ctx->algbitswidth < sz.cx) ctx->algbitswidth = sz.cx; + } + GetTextExtentPoint32(ctx->hdc, disp->hash->s, disp->hash->len, &sz); + if (ctx->hashwidth < sz.cx) ctx->hashwidth = sz.cx; + if (ext_flags & LIST_EXTENDED_FLAG_HAS_NO_CLEARTEXT_KEY) { put_fmt(disp->info, "(encrypted)"); } else if (ext_flags & LIST_EXTENDED_FLAG_HAS_ENCRYPTED_KEY_FILE) { @@ -398,6 +413,9 @@ static void keylist_update_callback( LB_ADDSTRING, 0, (LPARAM)disp); } +/* Column start positions for the list box, in pixels (not dialog units). */ +static int colpos_bits, colpos_hash, colpos_comment; + /* * Update the visible key list. */ @@ -430,7 +448,22 @@ void keylist_update(void) struct keylist_update_ctx ctx[1]; ctx->enable_remove_controls = false; ctx->enable_reencrypt_controls = false; + ctx->algbitswidth = ctx->algwidth = 0; + ctx->bitswidth = ctx->hashwidth = 0; + ctx->hdc = GetDC(keylist); + SelectObject(ctx->hdc, (HFONT)SendMessage(keylist, WM_GETFONT, 0, 0)); int status = pageant_enum_keys(keylist_update_callback, ctx, &errmsg); + + SIZE sz; + GetTextExtentPoint32(ctx->hdc, "MM", 2, &sz); + int gutter = sz.cx; + + DeleteDC(ctx->hdc); + colpos_hash = ctx->algwidth + ctx->bitswidth + 2*gutter; + if (colpos_hash < ctx->algbitswidth + gutter) + colpos_hash = ctx->algbitswidth + gutter; + colpos_bits = colpos_hash - ctx->bitswidth - gutter; + colpos_comment = colpos_hash + ctx->hashwidth + gutter; assert(status == PAGEANT_ACTION_OK); assert(!errmsg); @@ -669,20 +702,14 @@ static INT_PTR CALLBACK KeyListProc(HWND hwnd, UINT msg, disp->alg->len, NULL); if (disp->bits->len) { - r.left = r.top = r.bottom = 0; - r.right = 35; - MapDialogRect(hwnd, &r); - ExtTextOut(di->hDC, di->rcItem.left + r.right, di->rcItem.top, - ETO_CLIPPED, &di->rcItem, disp->bits->s, - disp->bits->len, NULL); + ExtTextOut(di->hDC, di->rcItem.left + r.right + colpos_bits, + di->rcItem.top, ETO_CLIPPED, &di->rcItem, + disp->bits->s, disp->bits->len, NULL); } - r.left = r.top = r.bottom = 0; - r.right = 75; - MapDialogRect(hwnd, &r); - ExtTextOut(di->hDC, di->rcItem.left + r.right, di->rcItem.top, - ETO_CLIPPED, &di->rcItem, disp->hash->s, - disp->hash->len, NULL); + ExtTextOut(di->hDC, di->rcItem.left + r.right + colpos_hash, + di->rcItem.top, ETO_CLIPPED, &di->rcItem, + disp->hash->s, disp->hash->len, NULL); strbuf *sb = strbuf_new(); put_datapl(sb, ptrlen_from_strbuf(disp->comment)); @@ -691,11 +718,8 @@ static INT_PTR CALLBACK KeyListProc(HWND hwnd, UINT msg, put_datapl(sb, ptrlen_from_strbuf(disp->info)); } - r.left = r.top = r.bottom = 0; - r.right = 300; - MapDialogRect(hwnd, &r); - TabbedTextOut(di->hDC, di->rcItem.left + r.right, di->rcItem.top, - sb->s, sb->len, 0, NULL, 0); + TabbedTextOut(di->hDC, di->rcItem.left + r.right + colpos_comment, + di->rcItem.top, sb->s, sb->len, 0, NULL, 0); strbuf_free(sb); -- cgit v1.2.3 From 3a42a09dadcb38e4f3481a4dd8b4c2bad5b6de4b Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Wed, 3 Aug 2022 20:48:46 +0100 Subject: Formatting: normalise back to 4-space indentation. In several pieces of development recently I've run across the occasional code block in the middle of a function which suddenly switched to 2-space indent from this code base's usual 4. I decided I was tired of it, so I ran the whole code base through a re-indenter, which made a huge mess, and then manually sifted out the changes that actually made sense from that pass. Indeed, this caught quite a few large sections with 2-space indent level, a couple with 8, and a handful of even weirder things like 3 spaces or 12. This commit fixes them all. --- charset/localenc.c | 2 +- charset/mimeenc.c | 2 +- charset/xenc.c | 2 +- cmdgen.c | 202 +++++++++++------------ crypto/blowfish.c | 2 +- import.c | 4 +- otherbackends/supdup.c | 18 +-- otherbackends/telnet.c | 2 +- proxy/sshproxy.c | 8 +- proxy/telnet.c | 48 +++--- pscp.c | 4 +- psftp.c | 8 +- putty.h | 4 +- ssh/channel.h | 4 +- ssh/connection1-server.c | 10 +- ssh/connection2.c | 4 +- terminal/terminal.c | 2 +- test/fuzzterm.c | 52 +++--- test/sclog/sclog.c | 28 ++-- unix/network.c | 10 +- unix/plink.c | 96 +++++------ windows/dialog.c | 4 +- windows/jump-list.c | 8 +- windows/network.c | 12 +- windows/pageant.c | 58 +++---- windows/puttygen.c | 8 +- windows/utils/security.c | 2 +- windows/window.c | 410 +++++++++++++++++++++++------------------------ 28 files changed, 507 insertions(+), 507 deletions(-) diff --git a/charset/localenc.c b/charset/localenc.c index 03340d6d..49719fbe 100644 --- a/charset/localenc.c +++ b/charset/localenc.c @@ -103,7 +103,7 @@ int charset_from_localenc(const char *name) p = name; q = localencs[i].name; while (*p || *q) { - if (tolower((unsigned char)*p) != tolower((unsigned char)*q)) + if (tolower((unsigned char)*p) != tolower((unsigned char)*q)) break; p++; q++; } diff --git a/charset/mimeenc.c b/charset/mimeenc.c index 2fec6d26..8a0203b3 100644 --- a/charset/mimeenc.c +++ b/charset/mimeenc.c @@ -207,7 +207,7 @@ int charset_from_mimeenc(const char *name) p = name; q = mimeencs[i].name; while (*p || *q) { - if (tolower((unsigned char)*p) != tolower((unsigned char)*q)) + if (tolower((unsigned char)*p) != tolower((unsigned char)*q)) break; p++; q++; } diff --git a/charset/xenc.c b/charset/xenc.c index 964ca6ff..24592ad1 100644 --- a/charset/xenc.c +++ b/charset/xenc.c @@ -82,7 +82,7 @@ int charset_from_xenc(const char *name) p = name; q = xencs[i].name; while (*p || *q) { - if (tolower((unsigned char)*p) != tolower((unsigned char)*q)) + if (tolower((unsigned char)*p) != tolower((unsigned char)*q)) break; p++; q++; } diff --git a/cmdgen.c b/cmdgen.c index 963464ea..8d82362f 100644 --- a/cmdgen.c +++ b/cmdgen.c @@ -299,73 +299,73 @@ int main(int argc, char **argv) while (*p && *p != '=') p++; /* find end of option */ if (*p == '=') { - *p++ = '\0'; - val = 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; - } + 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; - } + 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; - } + if (val) { + errs = true; + fprintf(stderr, "puttygen: option `-%s'" + " expects no argument\n", opt); + } else { + /* support --pgpfp for consistency */ + pgp_fingerprints(); + nogo = true; + } } else if (!strcmp(opt, "-old-passphrase")) { - if (!val && argc > 1) - --argc, val = *++argv; - if (!val) { - errs = true; - fprintf(stderr, "puttygen: option `-%s'" - " expects an argument\n", opt); - } else { - old_passphrase = readpassphrase(val); - if (!old_passphrase) + if (!val && argc > 1) + --argc, val = *++argv; + if (!val) { errs = true; - } + fprintf(stderr, "puttygen: option `-%s'" + " expects an argument\n", opt); + } else { + old_passphrase = readpassphrase(val); + if (!old_passphrase) + errs = true; + } } else if (!strcmp(opt, "-new-passphrase")) { - if (!val && argc > 1) - --argc, val = *++argv; - if (!val) { - errs = true; - fprintf(stderr, "puttygen: option `-%s'" - " expects an argument\n", opt); - } else { - new_passphrase = readpassphrase(val); - if (!new_passphrase) + if (!val && argc > 1) + --argc, val = *++argv; + if (!val) { errs = true; - } + fprintf(stderr, "puttygen: option `-%s'" + " expects an argument\n", opt); + } else { + new_passphrase = readpassphrase(val); + if (!new_passphrase) + errs = true; + } } else if (!strcmp(opt, "-random-device")) { - if (!val && argc > 1) - --argc, val = *++argv; - if (!val) { - errs = true; - fprintf(stderr, "puttygen: option `-%s'" - " expects an argument\n", opt); - } else { - random_device = val; - } + if (!val && argc > 1) + --argc, val = *++argv; + if (!val) { + errs = true; + fprintf(stderr, "puttygen: option `-%s'" + " expects an argument\n", opt); + } else { + random_device = val; + } } else if (!strcmp(opt, "-dump")) { outtype = TEXT; } else if (!strcmp(opt, "-cert-info") || @@ -401,15 +401,15 @@ int main(int argc, char **argv) } else if (!strcmp(opt, "-strong-rsa")) { strong_rsa = true; } else if (!strcmp(opt, "-certificate")) { - if (!val && argc > 1) - --argc, val = *++argv; - if (!val) { - errs = true; - fprintf(stderr, "puttygen: option `-%s'" - " expects an argument\n", opt); - } else { - certfile = val; - } + if (!val && argc > 1) + --argc, val = *++argv; + if (!val) { + errs = true; + fprintf(stderr, "puttygen: option `-%s'" + " expects an argument\n", opt); + } else { + certfile = val; + } } else if (!strcmp(opt, "-remove-certificate")) { remove_cert = true; } else if (!strcmp(opt, "-reencrypt")) { @@ -490,9 +490,9 @@ int main(int argc, char **argv) } } } else { - errs = true; - fprintf(stderr, - "puttygen: no such option `-%s'\n", opt); + errs = true; + fprintf(stderr, + "puttygen: no such option `-%s'\n", opt); } p = NULL; break; @@ -1334,29 +1334,29 @@ int main(int argc, char **argv) FILE *fp; if (outfile) { - fp = f_open(outfilename, "w", false); - if (!fp) { - fprintf(stderr, "unable to open output file\n"); - exit(1); - } + fp = f_open(outfilename, "w", false); + if (!fp) { + fprintf(stderr, "unable to open output file\n"); + exit(1); + } } else { - fp = stdout; + fp = stdout; } if (sshver == 1) { - ssh1_write_pubkey(fp, ssh1key); + ssh1_write_pubkey(fp, ssh1key); } else { - if (!ssh2blob) { - assert(ssh2key); - ssh2blob = strbuf_new(); - ssh_key_public_blob(ssh2key->key, BinarySink_UPCAST(ssh2blob)); - } - - ssh2_write_pubkey(fp, ssh2key ? ssh2key->comment : origcomment, - ssh2blob->s, ssh2blob->len, - (outtype == PUBLIC ? - SSH_KEYTYPE_SSH2_PUBLIC_RFC4716 : - SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH)); + if (!ssh2blob) { + assert(ssh2key); + ssh2blob = strbuf_new(); + ssh_key_public_blob(ssh2key->key, BinarySink_UPCAST(ssh2blob)); + } + + ssh2_write_pubkey(fp, ssh2key ? ssh2key->comment : origcomment, + ssh2blob->s, ssh2blob->len, + (outtype == PUBLIC ? + SSH_KEYTYPE_SSH2_PUBLIC_RFC4716 : + SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH)); } if (outfile) @@ -1370,26 +1370,26 @@ int main(int argc, char **argv) char *fingerprint; if (sshver == 1) { - assert(ssh1key); - fingerprint = rsa_ssh1_fingerprint(ssh1key); + assert(ssh1key); + fingerprint = rsa_ssh1_fingerprint(ssh1key); } else { - if (ssh2key) { - fingerprint = ssh2_fingerprint(ssh2key->key, fptype); - } else { - assert(ssh2blob); - fingerprint = ssh2_fingerprint_blob( - ptrlen_from_strbuf(ssh2blob), fptype); - } + if (ssh2key) { + fingerprint = ssh2_fingerprint(ssh2key->key, fptype); + } else { + assert(ssh2blob); + fingerprint = ssh2_fingerprint_blob( + ptrlen_from_strbuf(ssh2blob), fptype); + } } if (outfile) { - fp = f_open(outfilename, "w", false); - if (!fp) { - fprintf(stderr, "unable to open output file\n"); - exit(1); - } + fp = f_open(outfilename, "w", false); + if (!fp) { + fprintf(stderr, "unable to open output file\n"); + exit(1); + } } else { - fp = stdout; + fp = stdout; } fprintf(fp, "%s\n", fingerprint); if (outfile) diff --git a/crypto/blowfish.c b/crypto/blowfish.c index e8886898..f86cb9a1 100644 --- a/crypto/blowfish.c +++ b/crypto/blowfish.c @@ -430,7 +430,7 @@ static void blowfish_msb_decrypt_cbc(unsigned char *blk, int len, } static void blowfish_msb_sdctr(unsigned char *blk, int len, - BlowfishContext * ctx) + BlowfishContext * ctx) { uint32_t b[2], iv0, iv1, tmp; diff --git a/import.c b/import.c index c62dfb15..926c98a9 100644 --- a/import.c +++ b/import.c @@ -1249,8 +1249,8 @@ static struct openssh_new_key *load_openssh_new_key(BinarySource *filesrc, ret->kdfopts.bcrypt.rounds = get_uint32(opts); if (get_err(opts)) { - errmsg = "failed to parse bcrypt options string"; - goto error; + errmsg = "failed to parse bcrypt options string"; + goto error; } break; } diff --git a/otherbackends/supdup.c b/otherbackends/supdup.c index a272b256..54cfbef9 100644 --- a/otherbackends/supdup.c +++ b/otherbackends/supdup.c @@ -717,15 +717,15 @@ static char *supdup_init(const BackendVtable *x, Seat *seat, *backend_handle = &supdup->backend; switch (conf_get_int(supdup->conf, CONF_supdup_ascii_set)) { - case SUPDUP_CHARSET_ASCII: - supdup->print = print_ascii; - break; - case SUPDUP_CHARSET_ITS: - supdup->print = print_its; - break; - case SUPDUP_CHARSET_WAITS: - supdup->print = print_waits; - break; + case SUPDUP_CHARSET_ASCII: + supdup->print = print_ascii; + break; + case SUPDUP_CHARSET_ITS: + supdup->print = print_its; + break; + case SUPDUP_CHARSET_WAITS: + supdup->print = print_waits; + break; } /* diff --git a/otherbackends/telnet.c b/otherbackends/telnet.c index 1c0f5d68..73e8f0c4 100644 --- a/otherbackends/telnet.c +++ b/otherbackends/telnet.c @@ -452,7 +452,7 @@ static void process_subneg(Telnet *telnet) eval != NULL; eval = conf_get_str_strs(telnet->conf, CONF_environmt, ekey, &ekey)) - bsize += strlen(ekey) + strlen(eval) + 2; + bsize += strlen(ekey) + strlen(eval) + 2; user = get_remote_username(telnet->conf); if (user) bsize += 6 + strlen(user); diff --git a/proxy/sshproxy.c b/proxy/sshproxy.c index 2aed2b6b..165c6c0a 100644 --- a/proxy/sshproxy.c +++ b/proxy/sshproxy.c @@ -431,8 +431,8 @@ static SeatPromptResult sshproxy_confirm_ssh_host_key( } static SeatPromptResult sshproxy_confirm_weak_crypto_primitive( - Seat *seat, const char *algtype, const char *algname, - void (*callback)(void *ctx, SeatPromptResult result), void *ctx) + Seat *seat, const char *algtype, const char *algname, + void (*callback)(void *ctx, SeatPromptResult result), void *ctx) { SshProxy *sp = container_of(seat, SshProxy, seat); @@ -457,8 +457,8 @@ static SeatPromptResult sshproxy_confirm_weak_crypto_primitive( } static SeatPromptResult sshproxy_confirm_weak_cached_hostkey( - Seat *seat, const char *algname, const char *betteralgs, - void (*callback)(void *ctx, SeatPromptResult result), void *ctx) + Seat *seat, const char *algname, const char *betteralgs, + void (*callback)(void *ctx, SeatPromptResult result), void *ctx) { SshProxy *sp = container_of(seat, SshProxy, seat); diff --git a/proxy/telnet.c b/proxy/telnet.c index 242c4025..a2efb7b4 100644 --- a/proxy/telnet.c +++ b/proxy/telnet.c @@ -85,31 +85,31 @@ char *format_telnet_command(SockAddr *addr, int port, Conf *conf, int i = 0; for (;;) { - eo++; - if (fmt[eo] >= '0' && fmt[eo] <= '9') - v += fmt[eo] - '0'; - else if (fmt[eo] >= 'a' && fmt[eo] <= 'f') - v += fmt[eo] - 'a' + 10; - else if (fmt[eo] >= 'A' && fmt[eo] <= 'F') - v += fmt[eo] - 'A' + 10; - else { - /* non hex character, so we abort and just - * send the whole thing unescaped (including \x) - */ - put_byte(buf, '\\'); - eo = so + 1; - break; - } - - /* we only extract two hex characters */ - if (i == 1) { - put_byte(buf, v); eo++; - break; - } - - i++; - v <<= 4; + if (fmt[eo] >= '0' && fmt[eo] <= '9') + v += fmt[eo] - '0'; + else if (fmt[eo] >= 'a' && fmt[eo] <= 'f') + v += fmt[eo] - 'a' + 10; + else if (fmt[eo] >= 'A' && fmt[eo] <= 'F') + v += fmt[eo] - 'A' + 10; + else { + /* non hex character, so we abort and just + * send the whole thing unescaped (including \x) + */ + put_byte(buf, '\\'); + eo = so + 1; + break; + } + + /* we only extract two hex characters */ + if (i == 1) { + put_byte(buf, v); + eo++; + break; + } + + i++; + v <<= 4; } break; } diff --git a/pscp.c b/pscp.c index 4df8cb95..d6cc1250 100644 --- a/pscp.c +++ b/pscp.c @@ -645,8 +645,8 @@ void scp_sftp_listdir(const char *dirname) dirh = fxp_opendir_recv(pktin, req); if (dirh == NULL) { - tell_user(stderr, "Unable to open %s: %s\n", dirname, fxp_error()); - errs++; + tell_user(stderr, "Unable to open %s: %s\n", dirname, fxp_error()); + errs++; } else { struct list_directory_from_sftp_ctx *ctx = list_directory_from_sftp_new(); diff --git a/psftp.c b/psftp.c index a1600cd9..db57d89d 100644 --- a/psftp.c +++ b/psftp.c @@ -2565,10 +2565,10 @@ static void usage(void) static void version(void) { - char *buildinfo_text = buildinfo("\n"); - printf("psftp: %s\n%s\n", ver, buildinfo_text); - sfree(buildinfo_text); - exit(0); + char *buildinfo_text = buildinfo("\n"); + printf("psftp: %s\n%s\n", ver, buildinfo_text); + sfree(buildinfo_text); + exit(0); } /* diff --git a/putty.h b/putty.h index 364297c3..c5d73e9d 100644 --- a/putty.h +++ b/putty.h @@ -1544,7 +1544,7 @@ const char *nullseat_get_x_display(Seat *seat); bool nullseat_get_windowid(Seat *seat, long *id_out); bool nullseat_get_window_pixel_size(Seat *seat, int *width, int *height); StripCtrlChars *nullseat_stripctrl_new( - Seat *seat, BinarySink *bs_out, SeatInteractionContext sic); + Seat *seat, BinarySink *bs_out, SeatInteractionContext sic); void nullseat_set_trust_status(Seat *seat, bool trusted); bool nullseat_can_set_trust_status_yes(Seat *seat); bool nullseat_can_set_trust_status_no(Seat *seat); @@ -1573,7 +1573,7 @@ SeatPromptResult console_confirm_weak_cached_hostkey( Seat *seat, const char *algname, const char *betteralgs, void (*callback)(void *ctx, SeatPromptResult result), void *ctx); StripCtrlChars *console_stripctrl_new( - Seat *seat, BinarySink *bs_out, SeatInteractionContext sic); + Seat *seat, BinarySink *bs_out, SeatInteractionContext sic); void console_set_trust_status(Seat *seat, bool trusted); bool console_can_set_trust_status(Seat *seat); bool console_has_mixed_input_stream(Seat *seat); diff --git a/ssh/channel.h b/ssh/channel.h index 14dfccb5..d4eb78aa 100644 --- a/ssh/channel.h +++ b/ssh/channel.h @@ -84,10 +84,10 @@ static inline bool chan_want_close(Channel *ch, bool leof, bool reof) static inline bool chan_rcvd_exit_status(Channel *ch, int status) { return ch->vt->rcvd_exit_status(ch, status); } static inline bool chan_rcvd_exit_signal( - Channel *ch, ptrlen sig, bool core, ptrlen msg) + Channel *ch, ptrlen sig, bool core, ptrlen msg) { return ch->vt->rcvd_exit_signal(ch, sig, core, msg); } static inline bool chan_rcvd_exit_signal_numeric( - Channel *ch, int sig, bool core, ptrlen msg) + Channel *ch, int sig, bool core, ptrlen msg) { return ch->vt->rcvd_exit_signal_numeric(ch, sig, core, msg); } static inline bool chan_run_shell(Channel *ch) { return ch->vt->run_shell(ch); } diff --git a/ssh/connection1-server.c b/ssh/connection1-server.c index 1123327c..cc69bdb3 100644 --- a/ssh/connection1-server.c +++ b/ssh/connection1-server.c @@ -113,15 +113,15 @@ bool ssh1_handle_direction_specific_packet( BinarySource_UPCAST(pktin), 1); if (get_err(pktin)) { - ppl_logevent("Unable to decode pty request packet"); - success = false; + ppl_logevent("Unable to decode pty request packet"); + success = false; } else if (!chan_allocate_pty( s->mainchan_chan, termtype, width, height, pixwidth, pixheight, modes)) { - ppl_logevent("Unable to allocate a pty"); - success = false; + ppl_logevent("Unable to allocate a pty"); + success = false; } else { - success = true; + success = true; } pktout = ssh_bpp_new_pktout( diff --git a/ssh/connection2.c b/ssh/connection2.c index ec330927..f2f07b17 100644 --- a/ssh/connection2.c +++ b/ssh/connection2.c @@ -1565,8 +1565,8 @@ static void ssh2_delete_sharing_channel(ConnectionLayer *cl, unsigned localid) } static void ssh2_send_packet_from_downstream( - ConnectionLayer *cl, unsigned id, int type, - const void *data, int datalen, const char *additional_log_text) + ConnectionLayer *cl, unsigned id, int type, + const void *data, int datalen, const char *additional_log_text) { struct ssh2_connection_state *s = container_of(cl, struct ssh2_connection_state, cl); diff --git a/terminal/terminal.c b/terminal/terminal.c index 04109415..b6d97c4b 100644 --- a/terminal/terminal.c +++ b/terminal/terminal.c @@ -1073,7 +1073,7 @@ static int sblines(Terminal *term) int sblines = count234(term->scrollback); if (term->erase_to_scrollback && term->alt_which && term->alt_screen) { - sblines += term->alt_sblines; + sblines += term->alt_sblines; } return sblines; } diff --git a/test/fuzzterm.c b/test/fuzzterm.c index 8d20bef9..0d4597b1 100644 --- a/test/fuzzterm.c +++ b/test/fuzzterm.c @@ -10,34 +10,34 @@ static const TermWinVtable fuzz_termwin_vt; int main(int argc, char **argv) { - char blk[512]; - size_t len; - Terminal *term; - Conf *conf; - struct unicode_data ucsdata; - TermWin termwin; - - termwin.vt = &fuzz_termwin_vt; - - conf = conf_new(); - do_defaults(NULL, conf); - init_ucs(&ucsdata, conf_get_str(conf, CONF_line_codepage), - conf_get_bool(conf, CONF_utf8_override), - CS_NONE, conf_get_int(conf, CONF_vtmode)); - - term = term_init(conf, &ucsdata, &termwin); - term_size(term, 24, 80, 10000); - term->ldisc = NULL; - /* Tell american fuzzy lop that this is a good place to fork. */ + char blk[512]; + size_t len; + Terminal *term; + Conf *conf; + struct unicode_data ucsdata; + TermWin termwin; + + termwin.vt = &fuzz_termwin_vt; + + conf = conf_new(); + do_defaults(NULL, conf); + init_ucs(&ucsdata, conf_get_str(conf, CONF_line_codepage), + conf_get_bool(conf, CONF_utf8_override), + CS_NONE, conf_get_int(conf, CONF_vtmode)); + + term = term_init(conf, &ucsdata, &termwin); + term_size(term, 24, 80, 10000); + term->ldisc = NULL; + /* Tell american fuzzy lop that this is a good place to fork. */ #ifdef __AFL_HAVE_MANUAL_CONTROL - __AFL_INIT(); + __AFL_INIT(); #endif - while (!feof(stdin)) { - len = fread(blk, 1, sizeof(blk), stdin); - term_data(term, blk, len); - } - term_update(term); - return 0; + while (!feof(stdin)) { + len = fread(blk, 1, sizeof(blk), stdin); + term_data(term, blk, len); + } + term_update(term); + return 0; } /* functions required by terminal.c */ diff --git a/test/sclog/sclog.c b/test/sclog/sclog.c index f12a0280..d8e61a05 100644 --- a/test/sclog/sclog.c +++ b/test/sclog/sclog.c @@ -479,20 +479,20 @@ static dr_emit_flags_t instrument_instr( */ opnd_t shiftcount = instr_get_src(instr, 0); if (!opnd_is_immed(shiftcount)) { - reg_id_t r0; - drreg_status_t st; - st = drreg_reserve_register(drcontext, bb, instr, NULL, &r0); - DR_ASSERT(st == DRREG_SUCCESS); - opnd_t op_r0 = opnd_create_reg(r0); - instr_t *movzx = INSTR_CREATE_movzx(drcontext, op_r0, shiftcount); - instr_set_translation(movzx, instr_get_app_pc(instr)); - instrlist_preinsert(bb, instr, movzx); - instr_format_location(instr, &loc); - dr_insert_clean_call( - drcontext, bb, instr, (void *)log_var_shift, false, - 2, op_r0, OPND_CREATE_INTPTR(loc)); - st = drreg_unreserve_register(drcontext, bb, instr, r0); - DR_ASSERT(st == DRREG_SUCCESS); + reg_id_t r0; + drreg_status_t st; + st = drreg_reserve_register(drcontext, bb, instr, NULL, &r0); + DR_ASSERT(st == DRREG_SUCCESS); + opnd_t op_r0 = opnd_create_reg(r0); + instr_t *movzx = INSTR_CREATE_movzx(drcontext, op_r0, shiftcount); + instr_set_translation(movzx, instr_get_app_pc(instr)); + instrlist_preinsert(bb, instr, movzx); + instr_format_location(instr, &loc); + dr_insert_clean_call( + drcontext, bb, instr, (void *)log_var_shift, false, + 2, op_r0, OPND_CREATE_INTPTR(loc)); + st = drreg_unreserve_register(drcontext, bb, instr, r0); + DR_ASSERT(st == DRREG_SUCCESS); } break; } diff --git a/unix/network.c b/unix/network.c index 1734d61c..e95af9f7 100644 --- a/unix/network.c +++ b/unix/network.c @@ -150,9 +150,9 @@ static int cmpfortree(void *av, void *bv) if (as > bs) return +1; if (a < b) - return -1; + return -1; if (a > b) - return +1; + return +1; return 0; } @@ -655,14 +655,14 @@ static int try_connect(NetSocket *sock) } else { err = errno; if (err != EADDRINUSE) /* failed, for a bad reason */ - break; + break; } if (localport == 0) - break; /* we're only looping once */ + break; /* we're only looping once */ localport--; if (localport == 0) - break; /* we might have got to the end */ + break; /* we might have got to the end */ } if (err) diff --git a/unix/plink.c b/unix/plink.c index a02bbe17..f0c73754 100644 --- a/unix/plink.c +++ b/unix/plink.c @@ -442,57 +442,57 @@ static void from_tty(void *vbuf, unsigned len) 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; - backend_send(backend, p, q - p); - p = q; - } - break; - case FF: - if (*p == '\xff') { - backend_send(backend, p, 1); - p++; - state = NORMAL; - } else if (*p == '\0') { - p++; - state = FF00; - } else abort(); - break; - case FF00: - if (*p == '\0') { - backend_special(backend, SS_BRK, 0); - } 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; - backend_send(backend, p, 1); - } - } else { - /* INPCK not set. Assume we got a parity error. */ + case NORMAL: + if (*p == '\xff') { + p++; + state = FF; + } else { + q = memchr(p, '\xff', end - p); + if (q == NULL) q = end; + backend_send(backend, p, q - p); + p = q; + } + break; + case FF: + if (*p == '\xff') { + backend_send(backend, p, 1); + p++; + state = NORMAL; + } else if (*p == '\0') { + p++; + state = FF00; + } else abort(); + break; + case FF00: + if (*p == '\0') { + backend_special(backend, SS_BRK, 0); + } 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; backend_send(backend, p, 1); } + } else { + /* INPCK not set. Assume we got a parity error. */ + backend_send(backend, p, 1); } - p++; - state = NORMAL; + } + p++; + state = NORMAL; } } } diff --git a/windows/dialog.c b/windows/dialog.c index 85f16348..3666bfbc 100644 --- a/windows/dialog.c +++ b/windows/dialog.c @@ -582,9 +582,9 @@ static INT_PTR GenericMainDlgProc(HWND hwnd, UINT msg, WPARAM wParam, c = strrchr(s->pathname, '/'); if (!c) - c = s->pathname; + c = s->pathname; else - c++; + c++; item = treeview_insert(&tvfaff, j, c, s->pathname); if (!hfirst) { diff --git a/windows/jump-list.c b/windows/jump-list.c index 37a17cc9..dd3143f9 100644 --- a/windows/jump-list.c +++ b/windows/jump-list.c @@ -716,10 +716,10 @@ void remove_session_from_jumplist(const char * const sessionname) bool set_explicit_app_user_model_id(void) { - DECL_WINDOWS_FUNCTION(static, HRESULT, SetCurrentProcessExplicitAppUserModelID, - (PCWSTR)); + DECL_WINDOWS_FUNCTION( + static, HRESULT, SetCurrentProcessExplicitAppUserModelID, (PCWSTR)); - static HMODULE shell32_module = 0; + static HMODULE shell32_module = 0; if (!shell32_module) { @@ -738,7 +738,7 @@ bool set_explicit_app_user_model_id(void) const wchar_t *id = get_app_user_model_id(); if (p_SetCurrentProcessExplicitAppUserModelID(id) == S_OK) { - return true; + return true; } return false; } diff --git a/windows/network.c b/windows/network.c index 4bb8dde3..72afbe37 100644 --- a/windows/network.c +++ b/windows/network.c @@ -927,7 +927,7 @@ static DWORD try_connect(NetSocket *sock) goto ret; } - SetHandleInformation((HANDLE)s, HANDLE_FLAG_INHERIT, 0); + SetHandleInformation((HANDLE)s, HANDLE_FLAG_INHERIT, 0); if (sock->oobinline) { BOOL b = true; @@ -1708,9 +1708,9 @@ void select_result(WPARAM wParam, LPARAM lParam) t = p_accept(s->s,(struct sockaddr *)&isa,&addrlen); if (t == INVALID_SOCKET) { - err = p_WSAGetLastError(); - if (err == WSATRY_AGAIN) - break; + err = p_WSAGetLastError(); + if (err == WSATRY_AGAIN) + break; } actx.p = (void *)t; @@ -1723,9 +1723,9 @@ void select_result(WPARAM wParam, LPARAM lParam) if (s->localhost_only && !ipv4_is_local_addr(isa.sin_addr)) #endif { - p_closesocket(t); /* dodgy WinSock let nonlocal through */ + p_closesocket(t); /* dodgy WinSock let nonlocal through */ } else if (plug_accepting(s->plug, sk_net_accept, actx)) { - p_closesocket(t); /* denied or error */ + p_closesocket(t); /* denied or error */ } break; } diff --git a/windows/pageant.c b/windows/pageant.c index accf0ccb..47423b2a 100644 --- a/windows/pageant.c +++ b/windows/pageant.c @@ -622,9 +622,9 @@ static INT_PTR CALLBACK KeyListProc(HWND hwnd, UINT msg, GetWindowLongPtr(hwnd, GWL_EXSTYLE) | WS_EX_CONTEXTHELP); else { - HWND item = GetDlgItem(hwnd, IDC_KEYLIST_HELP); - if (item) - DestroyWindow(item); + HWND item = GetDlgItem(hwnd, IDC_KEYLIST_HELP); + if (item) + DestroyWindow(item); } keylist = hwnd; @@ -852,9 +852,9 @@ static INT_PTR CALLBACK KeyListProc(HWND hwnd, UINT msg, topic = WINHELP_CTX_pageant_deferred; break; } if (topic) { - launch_help(hwnd, topic); + launch_help(hwnd, topic); } else { - MessageBeep(0); + MessageBeep(0); } break; } @@ -1312,8 +1312,8 @@ static LRESULT CALLBACK TrayWndProc(HWND hwnd, UINT message, if((INT_PTR)ShellExecute(hwnd, NULL, putty_path, cmdline, _T(""), SW_SHOW) <= 32) { - MessageBox(NULL, "Unable to execute PuTTY!", - "Error", MB_OK | MB_ICONERROR); + MessageBox(NULL, "Unable to execute PuTTY!", + "Error", MB_OK | MB_ICONERROR); } break; } @@ -1371,25 +1371,25 @@ static LRESULT CALLBACK TrayWndProc(HWND hwnd, UINT message, 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); - param[0] = '\0'; - if (restrict_putty_acl) - strcat(param, "&R"); - strcat(param, "@"); - strcat(param, mii.dwTypeData); - if((INT_PTR)ShellExecute(hwnd, NULL, putty_path, param, - _T(""), SW_SHOW) <= 32) { - MessageBox(NULL, "Unable to execute PuTTY!", "Error", - MB_OK | MB_ICONERROR); - } + 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); + param[0] = '\0'; + if (restrict_putty_acl) + strcat(param, "&R"); + strcat(param, "@"); + strcat(param, mii.dwTypeData); + if((INT_PTR)ShellExecute(hwnd, NULL, putty_path, param, + _T(""), SW_SHOW) <= 32) { + MessageBox(NULL, "Unable to execute PuTTY!", "Error", + MB_OK | MB_ICONERROR); + } } break; } @@ -1425,10 +1425,10 @@ static LRESULT CALLBACK wm_copydata_WndProc(HWND hwnd, UINT message, err = answer_filemapping_message(mapname); if (err) { #ifdef DEBUG_IPC - debug("IPC failed: %s\n", err); + debug("IPC failed: %s\n", err); #endif - sfree(err); - return 0; + sfree(err); + return 0; } return 1; } diff --git a/windows/puttygen.c b/windows/puttygen.c index c7b39c97..4c836873 100644 --- a/windows/puttygen.c +++ b/windows/puttygen.c @@ -438,9 +438,9 @@ static INT_PTR CALLBACK PPKParamsProc(HWND hwnd, UINT msg, topic = WINHELP_CTX_puttygen_kdfparam; break; } if (topic) { - launch_help(hwnd, topic); + launch_help(hwnd, topic); } else { - MessageBeep(0); + MessageBeep(0); } break; } @@ -2353,9 +2353,9 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, topic = WINHELP_CTX_puttygen_conversions; break; } if (topic) { - launch_help(hwnd, topic); + launch_help(hwnd, topic); } else { - MessageBeep(0); + MessageBeep(0); } break; } diff --git a/windows/utils/security.c b/windows/utils/security.c index 1a4770b6..14e55090 100644 --- a/windows/utils/security.c +++ b/windows/utils/security.c @@ -175,7 +175,7 @@ bool make_private_security_descriptor(DWORD permissions, *error = NULL; if (!getsids(error)) - goto cleanup; + goto cleanup; memset(ea, 0, sizeof(ea)); ea[0].grfAccessPermissions = permissions; diff --git a/windows/window.c b/windows/window.c index b291c2a3..d810719b 100644 --- a/windows/window.c +++ b/windows/window.c @@ -1923,7 +1923,7 @@ static void reset_window(int reinit) { static RECT ss; int width, height; - get_fullscreen_rect(&ss); + get_fullscreen_rect(&ss); width = (ss.right - ss.left - extra_width) / font_width; height = (ss.bottom - ss.top - extra_height) / font_height; @@ -2283,52 +2283,52 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, argprefix = ""; if (wParam == IDM_DUPSESS) { - /* - * Allocate a file-mapping memory chunk for the - * config structure. - */ - SECURITY_ATTRIBUTES sa; - strbuf *serbuf; - void *p; - int size; - - serbuf = strbuf_new(); - conf_serialise(BinarySink_UPCAST(serbuf), conf); - size = serbuf->len; - - sa.nLength = sizeof(sa); - sa.lpSecurityDescriptor = NULL; - sa.bInheritHandle = true; - filemap = CreateFileMapping(INVALID_HANDLE_VALUE, - &sa, - PAGE_READWRITE, - 0, size, NULL); - if (filemap && filemap != INVALID_HANDLE_VALUE) { - p = MapViewOfFile(filemap, FILE_MAP_WRITE, 0, 0, size); - if (p) { - memcpy(p, serbuf->s, size); - UnmapViewOfFile(p); + /* + * Allocate a file-mapping memory chunk for the + * config structure. + */ + SECURITY_ATTRIBUTES sa; + strbuf *serbuf; + void *p; + int size; + + serbuf = strbuf_new(); + conf_serialise(BinarySink_UPCAST(serbuf), conf); + size = serbuf->len; + + sa.nLength = sizeof(sa); + sa.lpSecurityDescriptor = NULL; + sa.bInheritHandle = true; + filemap = CreateFileMapping(INVALID_HANDLE_VALUE, + &sa, + PAGE_READWRITE, + 0, size, NULL); + if (filemap && filemap != INVALID_HANDLE_VALUE) { + p = MapViewOfFile(filemap, FILE_MAP_WRITE, 0, 0, size); + if (p) { + memcpy(p, serbuf->s, size); + UnmapViewOfFile(p); + } } - } - strbuf_free(serbuf); - inherit_handles = true; - cl = dupprintf("putty %s&%p:%u", argprefix, - filemap, (unsigned)size); + strbuf_free(serbuf); + inherit_handles = true; + cl = dupprintf("putty %s&%p:%u", argprefix, + filemap, (unsigned)size); } else if (wParam == IDM_SAVEDSESS) { - unsigned int sessno = ((lParam - IDM_SAVED_MIN) - / MENU_SAVED_STEP) + 1; - if (sessno < (unsigned)sesslist.nsessions) { - const char *session = sesslist.sessions[sessno]; - cl = dupprintf("putty %s@%s", argprefix, session); - inherit_handles = false; - } else - break; + unsigned int sessno = ((lParam - IDM_SAVED_MIN) + / MENU_SAVED_STEP) + 1; + if (sessno < (unsigned)sesslist.nsessions) { + const char *session = sesslist.sessions[sessno]; + cl = dupprintf("putty %s@%s", argprefix, session); + inherit_handles = false; + } else + break; } else /* IDM_NEWSESS */ { - cl = dupprintf("putty%s%s", - *argprefix ? " " : "", - argprefix); - inherit_handles = false; + cl = dupprintf("putty%s%s", + *argprefix ? " " : "", + argprefix); + inherit_handles = false; } GetModuleFileName(NULL, b, sizeof(b) - 1); @@ -2374,24 +2374,24 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, hwnd, conf, backend ? backend_cfg_info(backend) : 0); reconfiguring = false; if (!reconfig_result) { - conf_free(prev_conf); - break; + conf_free(prev_conf); + break; } conf_cache_data(); resize_action = conf_get_int(conf, CONF_resize_action); { - /* Disable full-screen if resizing forbidden */ - int i; - for (i = 0; i < lenof(popup_menus); i++) - EnableMenuItem(popup_menus[i].menu, IDM_FULLSCREEN, - MF_BYCOMMAND | - (resize_action == RESIZE_DISABLED - ? MF_GRAYED : MF_ENABLED)); - /* Gracefully unzoom if necessary */ - if (IsZoomed(hwnd) && (resize_action == RESIZE_DISABLED)) - ShowWindow(hwnd, SW_RESTORE); + /* 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 | + (resize_action == RESIZE_DISABLED + ? MF_GRAYED : MF_ENABLED)); + /* Gracefully unzoom if necessary */ + if (IsZoomed(hwnd) && (resize_action == RESIZE_DISABLED)) + ShowWindow(hwnd, SW_RESTORE); } /* Pass new config data to the logging module */ @@ -2403,8 +2403,8 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, * case where local editing has just been disabled. */ if (ldisc) { - ldisc_configure(ldisc, conf); - ldisc_echoedit_update(ldisc); + ldisc_configure(ldisc, conf); + ldisc_echoedit_update(ldisc); } if (conf_get_bool(conf, CONF_system_colour) != @@ -2443,90 +2443,90 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, /* Enable or disable the scroll bar, etc */ { - LONG nflg, flag = GetWindowLongPtr(hwnd, GWL_STYLE); - LONG nexflag, exflag = - GetWindowLongPtr(hwnd, GWL_EXSTYLE); - - nexflag = exflag; - if (conf_get_bool(conf, CONF_alwaysontop) != - conf_get_bool(prev_conf, CONF_alwaysontop)) { - if (conf_get_bool(conf, CONF_alwaysontop)) { - nexflag |= WS_EX_TOPMOST; - SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, - SWP_NOMOVE | SWP_NOSIZE); - } else { - nexflag &= ~(WS_EX_TOPMOST); - SetWindowPos(hwnd, HWND_NOTOPMOST, 0, 0, 0, 0, - SWP_NOMOVE | SWP_NOSIZE); + LONG nflg, flag = GetWindowLongPtr(hwnd, GWL_STYLE); + LONG nexflag, exflag = + GetWindowLongPtr(hwnd, GWL_EXSTYLE); + + nexflag = exflag; + if (conf_get_bool(conf, CONF_alwaysontop) != + conf_get_bool(prev_conf, CONF_alwaysontop)) { + if (conf_get_bool(conf, CONF_alwaysontop)) { + nexflag |= WS_EX_TOPMOST; + SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE); + } else { + nexflag &= ~(WS_EX_TOPMOST); + SetWindowPos(hwnd, HWND_NOTOPMOST, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE); + } } - } - if (conf_get_bool(conf, CONF_sunken_edge)) - nexflag |= WS_EX_CLIENTEDGE; - else - nexflag &= ~(WS_EX_CLIENTEDGE); - - nflg = flag; - if (conf_get_bool(conf, is_full_screen() ? - CONF_scrollbar_in_fullscreen : - CONF_scrollbar)) - nflg |= WS_VSCROLL; - else - nflg &= ~WS_VSCROLL; - - if (resize_action == RESIZE_DISABLED || - is_full_screen()) - nflg &= ~WS_THICKFRAME; - else - nflg |= WS_THICKFRAME; - - if (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); + if (conf_get_bool(conf, CONF_sunken_edge)) + nexflag |= WS_EX_CLIENTEDGE; + else + nexflag &= ~(WS_EX_CLIENTEDGE); - init_lvl = 2; - } + nflg = flag; + if (conf_get_bool(conf, is_full_screen() ? + CONF_scrollbar_in_fullscreen : + CONF_scrollbar)) + nflg |= WS_VSCROLL; + else + nflg &= ~WS_VSCROLL; + + if (resize_action == RESIZE_DISABLED || + is_full_screen()) + nflg &= ~WS_THICKFRAME; + else + nflg |= WS_THICKFRAME; + + if (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 (resize_action == RESIZE_DISABLED && IsZoomed(hwnd)) { - force_normal(hwnd); - init_lvl = 2; + force_normal(hwnd); + init_lvl = 2; } { - FontSpec *font = conf_get_fontspec(conf, CONF_font); - FontSpec *prev_font = conf_get_fontspec(prev_conf, - CONF_font); - - if (!strcmp(font->name, prev_font->name) || - !strcmp(conf_get_str(conf, CONF_line_codepage), - conf_get_str(prev_conf, CONF_line_codepage)) || - font->isbold != prev_font->isbold || - font->height != prev_font->height || - font->charset != prev_font->charset || - conf_get_int(conf, CONF_font_quality) != - conf_get_int(prev_conf, CONF_font_quality) || - conf_get_int(conf, CONF_vtmode) != - conf_get_int(prev_conf, CONF_vtmode) || - conf_get_int(conf, CONF_bold_style) != - conf_get_int(prev_conf, CONF_bold_style) || - resize_action == RESIZE_DISABLED || - resize_action == RESIZE_EITHER || - resize_action != conf_get_int(prev_conf, - CONF_resize_action)) - init_lvl = 2; + FontSpec *font = conf_get_fontspec(conf, CONF_font); + FontSpec *prev_font = conf_get_fontspec(prev_conf, + CONF_font); + + if (!strcmp(font->name, prev_font->name) || + !strcmp(conf_get_str(conf, CONF_line_codepage), + conf_get_str(prev_conf, CONF_line_codepage)) || + font->isbold != prev_font->isbold || + font->height != prev_font->height || + font->charset != prev_font->charset || + conf_get_int(conf, CONF_font_quality) != + conf_get_int(prev_conf, CONF_font_quality) || + conf_get_int(conf, CONF_vtmode) != + conf_get_int(prev_conf, CONF_vtmode) || + conf_get_int(conf, CONF_bold_style) != + conf_get_int(prev_conf, CONF_bold_style) || + resize_action == RESIZE_DISABLED || + resize_action == RESIZE_EITHER || + resize_action != conf_get_int(prev_conf, + CONF_resize_action)) + init_lvl = 2; } InvalidateRect(hwnd, NULL, true); @@ -2734,9 +2734,9 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, static LPARAM lp = 0; if (wParam != wp || lParam != lp || last_mousemove != WM_MOUSEMOVE) { - show_mouseptr(true); - wp = wParam; lp = lParam; - last_mousemove = WM_MOUSEMOVE; + show_mouseptr(true); + wp = wParam; lp = lParam; + last_mousemove = WM_MOUSEMOVE; } /* * Add the mouse position and message time to the random @@ -2765,9 +2765,9 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, static LPARAM lp = 0; if (wParam != wp || lParam != lp || last_mousemove != WM_NCMOUSEMOVE) { - show_mouseptr(true); - wp = wParam; lp = lParam; - last_mousemove = WM_NCMOUSEMOVE; + show_mouseptr(true); + wp = wParam; lp = lParam; + last_mousemove = WM_NCMOUSEMOVE; } noise_ultralight(NOISE_SOURCE_MOUSEPOS, lParam); break; @@ -2786,8 +2786,8 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, HideCaret(hwnd); hdc = BeginPaint(hwnd, &p); if (pal) { - SelectPalette(hdc, pal, true); - RealizePalette(hdc); + SelectPalette(hdc, pal, true); + RealizePalette(hdc); } /* @@ -2838,40 +2838,40 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, 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); + 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)); @@ -3314,33 +3314,33 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, 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 - * term_keyinputw covering the whole of buff. So - * instead we send the characters one by one. - */ - /* don't divide SURROGATE PAIR */ - if (ldisc) { - for (i = 0; i < n; i += 2) { - WCHAR hs = *(unsigned short *)(buff+i); - if (IS_HIGH_SURROGATE(hs) && i+2 < n) { - WCHAR ls = *(unsigned short *)(buff+i+2); - if (IS_LOW_SURROGATE(ls)) { - term_keyinputw( - term, (unsigned short *)(buff+i), 2); - i += 2; - continue; + 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 + * term_keyinputw covering the whole of buff. So + * instead we send the characters one by one. + */ + /* don't divide SURROGATE PAIR */ + if (ldisc) { + for (i = 0; i < n; i += 2) { + WCHAR hs = *(unsigned short *)(buff+i); + if (IS_HIGH_SURROGATE(hs) && i+2 < n) { + WCHAR ls = *(unsigned short *)(buff+i+2); + if (IS_LOW_SURROGATE(ls)) { + term_keyinputw( + term, (unsigned short *)(buff+i), 2); + i += 2; + continue; + } + } + term_keyinputw( + term, (unsigned short *)(buff+i), 1); } - } - term_keyinputw( - term, (unsigned short *)(buff+i), 1); } - } - free(buff); + free(buff); } ImmReleaseContext(hwnd, hIMC); return 1; @@ -5688,7 +5688,7 @@ static void wintw_move(TermWin *tw, int x, int y) if (resize_action == RESIZE_DISABLED || resize_action == RESIZE_FONT || IsZoomed(wgs.term_hwnd)) - return; + return; SetWindowPos(wgs.term_hwnd, NULL, x, y, 0, 0, SWP_NOSIZE | SWP_NOZORDER); } @@ -5763,7 +5763,7 @@ static bool get_fullscreen_rect(RECT * ss) ss->right = GetSystemMetrics(SM_CXSCREEN); ss->bottom = GetSystemMetrics(SM_CYSCREEN); */ - return GetClientRect(GetDesktopWindow(), ss); + return GetClientRect(GetDesktopWindow(), ss); } @@ -5774,12 +5774,12 @@ static bool get_fullscreen_rect(RECT * ss) static void make_full_screen() { DWORD style; - RECT ss; + RECT ss; assert(IsZoomed(wgs.term_hwnd)); - if (is_full_screen()) - return; + if (is_full_screen()) + return; /* Remove the window furniture. */ style = GetWindowLongPtr(wgs.term_hwnd, GWL_STYLE); @@ -5791,7 +5791,7 @@ static void make_full_screen() SetWindowLongPtr(wgs.term_hwnd, GWL_STYLE, style); /* Resize ourselves to exactly cover the nearest monitor. */ - get_fullscreen_rect(&ss); + get_fullscreen_rect(&ss); SetWindowPos(wgs.term_hwnd, HWND_TOP, ss.left, ss.top, ss.right - ss.left, ss.bottom - ss.top, SWP_FRAMECHANGED); -- cgit v1.2.3 From 4fa3480444a64a5be3055cf27d96d8e68d0a1d12 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Wed, 3 Aug 2022 20:48:46 +0100 Subject: Formatting: realign run-on parenthesised stuff. My bulk indentation check also turned up a lot of cases where a run-on function call or if statement didn't have its later lines aligned correctly relative to the open paren. I think this is quite easy to do by getting things out of sync (editing the first line of the function call and forgetting to update the rest, perhaps even because you never _saw_ the rest during a search-replace). But a few didn't quite fit into that pattern, in particular an outright misleading case in unix/askpass.c where the second line of a call was aligned neatly below the _wrong_ one of the open parens on the opening line. Restored as many alignments as I could easily find. --- config.c | 24 ++++++++++++------------ crypto/aes-neon.c | 2 +- crypto/des.c | 2 +- crypto/dsa.c | 4 ++-- crypto/ecc-ssh.c | 2 +- crypto/ntru.c | 2 +- crypto/rsa.c | 2 +- import.c | 4 ++-- marshal.h | 2 +- otherbackends/telnet.c | 12 ++++++------ proxy/proxy.c | 12 ++++++------ ssh.h | 10 +++++----- ssh/common.c | 2 +- ssh/login1-server.c | 2 +- ssh/login1.c | 4 ++-- ssh/mainchan.c | 2 +- ssh/sftp.c | 2 +- ssh/sharing.c | 2 +- ssh/verstring.c | 2 +- ssh/x11fwd.c | 2 +- ssh/zlib.c | 6 +++--- sshpubk.c | 2 +- terminal/terminal.c | 2 +- test/testcrypt.c | 6 +++--- unix/askpass.c | 2 +- unix/main-gtk-simple.c | 2 +- unix/sftpserver.c | 2 +- unix/unifont.c | 2 +- unix/window.c | 4 ++-- utils/conf.c | 2 +- utils/debug.c | 4 ++-- windows/controls.c | 6 +++--- windows/dialog.c | 2 +- windows/gss.c | 4 ++-- windows/pageant.c | 10 +++++----- windows/puttygen.c | 10 +++++----- windows/storage.c | 2 +- windows/window.c | 20 ++++++++++---------- 38 files changed, 92 insertions(+), 92 deletions(-) diff --git a/config.c b/config.c index a2537dbb..f77384d5 100644 --- a/config.c +++ b/config.c @@ -602,7 +602,7 @@ static void kexlist_handler(dlgcontrol *ctrl, dlgparam *dlg, } static void hklist_handler(dlgcontrol *ctrl, dlgparam *dlg, - void *data, int event) + void *data, int event) { Conf *conf = (Conf *)data; if (event == EVENT_REFRESH) { @@ -1990,11 +1990,11 @@ void setup_config_box(struct controlbox *b, bool midsession, "Always append to the end of it", I(LGXF_APN), "Ask the user every time", I(LGXF_ASK)); ctrl_checkbox(s, "Flush log file frequently", 'u', - HELPCTX(logging_flush), - conf_checkbox_handler, I(CONF_logflush)); + HELPCTX(logging_flush), + conf_checkbox_handler, I(CONF_logflush)); ctrl_checkbox(s, "Include header", 'i', - HELPCTX(logging_header), - conf_checkbox_handler, I(CONF_logheader)); + HELPCTX(logging_header), + conf_checkbox_handler, I(CONF_logheader)); if ((midsession && protocol == PROT_SSH) || (!midsession && backend_vt_from_proto(PROT_SSH))) { @@ -2476,14 +2476,14 @@ void setup_config_box(struct controlbox *b, bool midsession, I(CONF_tcp_keepalives)); #ifndef NO_IPV6 s = ctrl_getset(b, "Connection", "ipversion", - "Internet protocol version"); + "Internet protocol version"); ctrl_radiobuttons(s, NULL, NO_SHORTCUT, 3, - HELPCTX(connection_ipversion), - conf_radiobutton_handler, - I(CONF_addressfamily), - "Auto", 'u', I(ADDRTYPE_UNSPEC), - "IPv4", '4', I(ADDRTYPE_IPV4), - "IPv6", '6', I(ADDRTYPE_IPV6)); + HELPCTX(connection_ipversion), + conf_radiobutton_handler, + I(CONF_addressfamily), + "Auto", 'u', I(ADDRTYPE_UNSPEC), + "IPv4", '4', I(ADDRTYPE_IPV4), + "IPv6", '6', I(ADDRTYPE_IPV6)); #endif { diff --git a/crypto/aes-neon.c b/crypto/aes-neon.c index d47d2fd3..f3b92832 100644 --- a/crypto/aes-neon.c +++ b/crypto/aes-neon.c @@ -211,7 +211,7 @@ static void aes_neon_setkey(ssh_cipher *ciph, const void *vkey) const unsigned char *key = (const unsigned char *)vkey; aes_neon_key_expand(key, ctx->ciph.vt->real_keybits / 32, - ctx->keysched_e, ctx->keysched_d); + ctx->keysched_e, ctx->keysched_d); } static void aes_neon_setiv_cbc(ssh_cipher *ciph, const void *iv) diff --git a/crypto/des.c b/crypto/des.c index e8a26f0a..045b00c0 100644 --- a/crypto/des.c +++ b/crypto/des.c @@ -296,7 +296,7 @@ static inline uint32_t des_S(uint32_t si6420, uint32_t si7531) s73 ^= c73 & t->t73; c73 += 0x00080008; } debug("S out: s40=%08"PRIx32" s62=%08"PRIx32 - " s51=%08"PRIx32" s73=%08"PRIx32"\n", s40, s62, s51, s73); + " s51=%08"PRIx32" s73=%08"PRIx32"\n", s40, s62, s51, s73); /* Final selection within each pair */ s40 ^= (s40 << 4) & ((0xf000/0x004) * (c40 & 0x00040004)); diff --git a/crypto/dsa.c b/crypto/dsa.c index bd005957..71fcd94a 100644 --- a/crypto/dsa.c +++ b/crypto/dsa.c @@ -341,8 +341,8 @@ static int dsa_pubkey_bits(const ssh_keyalg *self, ptrlen pub) } mp_int *dsa_gen_k(const char *id_string, mp_int *modulus, - mp_int *private_key, - unsigned char *digest, int digest_len) + mp_int *private_key, + unsigned char *digest, int digest_len) { /* * The basic DSA signing algorithm is: diff --git a/crypto/ecc-ssh.c b/crypto/ecc-ssh.c index 6f232c79..b51caab0 100644 --- a/crypto/ecc-ssh.c +++ b/crypto/ecc-ssh.c @@ -1705,7 +1705,7 @@ const ssh_kexes ssh_ecdh_kex = { lenof(ec_kex_list), ec_kex_list }; */ const ssh_keyalg *ec_alg_by_oid(int len, const void *oid, - const struct ec_curve **curve) + const struct ec_curve **curve) { static const ssh_keyalg *algs_with_oid[] = { &ssh_ecdsa_nistp256, diff --git a/crypto/ntru.c b/crypto/ntru.c index f1ba118f..c313c0cc 100644 --- a/crypto/ntru.c +++ b/crypto/ntru.c @@ -472,7 +472,7 @@ void ntru_bias(uint16_t *out, const uint16_t *in, unsigned bias, * Given an array of values mod q, multiply each one by a constant. */ void ntru_scale(uint16_t *out, const uint16_t *in, uint16_t scale, - unsigned p, unsigned q) + unsigned p, unsigned q) { SETUP; for (unsigned i = 0; i < p; i++) diff --git a/crypto/rsa.c b/crypto/rsa.c index 2196d1ec..4c4243b8 100644 --- a/crypto/rsa.c +++ b/crypto/rsa.c @@ -548,7 +548,7 @@ static void rsa2_private_blob(ssh_key *key, BinarySink *bs) } static ssh_key *rsa2_new_priv(const ssh_keyalg *self, - ptrlen pub, ptrlen priv) + ptrlen pub, ptrlen priv) { BinarySource src[1]; ssh_key *sshk; diff --git a/import.c b/import.c index 926c98a9..7e0d7b74 100644 --- a/import.c +++ b/import.c @@ -1002,7 +1002,7 @@ static bool openssh_pem_write( /* Append the BIT STRING to the sequence */ put_ber_id_len(seq, 1, sub->len, - ASN1_CLASS_CONTEXT_SPECIFIC | ASN1_CONSTRUCTED); + ASN1_CLASS_CONTEXT_SPECIFIC | ASN1_CONSTRUCTED); put_data(seq, sub->s, sub->len); strbuf_free(sub); @@ -1988,7 +1988,7 @@ static ssh2_userkey *sshcom_read( !memcmp(str.ptr, prefix_rsa, sizeof(prefix_rsa) - 1)) { type = RSA; } else if (str.len > sizeof(prefix_dsa) - 1 && - !memcmp(str.ptr, prefix_dsa, sizeof(prefix_dsa) - 1)) { + !memcmp(str.ptr, prefix_dsa, sizeof(prefix_dsa) - 1)) { type = DSA; } else { errmsg = "key is of unknown type"; diff --git a/marshal.h b/marshal.h index 4d5b0075..b9136292 100644 --- a/marshal.h +++ b/marshal.h @@ -316,7 +316,7 @@ static inline void BinarySource_INIT__(BinarySource *src, ptrlen data) #define get_err(src) (BinarySource_UPCAST(src)->err) #define get_avail(src) (BinarySource_UPCAST(src)->len - \ - BinarySource_UPCAST(src)->pos) + BinarySource_UPCAST(src)->pos) #define get_ptr(src) \ ((const void *)( \ (const unsigned char *)(BinarySource_UPCAST(src)->data) + \ diff --git a/otherbackends/telnet.c b/otherbackends/telnet.c index 73e8f0c4..50945b86 100644 --- a/otherbackends/telnet.c +++ b/otherbackends/telnet.c @@ -448,10 +448,10 @@ static void process_subneg(Telnet *telnet) } bsize = 20; for (eval = conf_get_str_strs(telnet->conf, CONF_environmt, - NULL, &ekey); + NULL, &ekey); eval != NULL; eval = conf_get_str_strs(telnet->conf, CONF_environmt, - ekey, &ekey)) + ekey, &ekey)) bsize += strlen(ekey) + strlen(eval) + 2; user = get_remote_username(telnet->conf); if (user) @@ -464,10 +464,10 @@ static void process_subneg(Telnet *telnet) b[3] = TELQUAL_IS; n = 4; for (eval = conf_get_str_strs(telnet->conf, CONF_environmt, - NULL, &ekey); + NULL, &ekey); eval != NULL; eval = conf_get_str_strs(telnet->conf, CONF_environmt, - ekey, &ekey)) { + ekey, &ekey)) { b[n++] = var; for (e = ekey; *e; e++) b[n++] = *e; @@ -496,10 +496,10 @@ static void process_subneg(Telnet *telnet) logeventf(telnet->logctx, "client subnegotiation: SB %s IS:", telopt(telnet->sb_opt)); for (eval = conf_get_str_strs(telnet->conf, CONF_environmt, - NULL, &ekey); + NULL, &ekey); eval != NULL; eval = conf_get_str_strs(telnet->conf, CONF_environmt, - ekey, &ekey)) { + ekey, &ekey)) { logeventf(telnet->logctx, " %s=%s", ekey, eval); } if (user) diff --git a/proxy/proxy.c b/proxy/proxy.c index 9535a371..b1731c87 100644 --- a/proxy/proxy.c +++ b/proxy/proxy.c @@ -393,8 +393,8 @@ static char *dns_log_msg(const char *host, int addressfamily, } SockAddr *name_lookup(const char *host, int port, char **canonicalname, - Conf *conf, int addressfamily, LogContext *logctx, - const char *reason) + Conf *conf, int addressfamily, LogContext *logctx, + const char *reason) { if (conf_get_int(conf, CONF_proxy_type) != PROXY_NONE && do_proxy_dns(conf) && @@ -581,10 +581,10 @@ Socket *new_connection(SockAddr *addr, const char *hostname, { char *logmsg = dupprintf("Will use %s proxy at %s:%d to connect" - " to %s:%d", vt->type, - conf_get_str(conf, CONF_proxy_host), - conf_get_int(conf, CONF_proxy_port), - hostname, port); + " to %s:%d", vt->type, + conf_get_str(conf, CONF_proxy_host), + conf_get_int(conf, CONF_proxy_port), + hostname, port); plug_log(plug, PLUGLOG_PROXY_MSG, NULL, 0, logmsg, 0); sfree(logmsg); } diff --git a/ssh.h b/ssh.h index bd639566..139acb20 100644 --- a/ssh.h +++ b/ssh.h @@ -515,7 +515,7 @@ struct ec_curve { }; const ssh_keyalg *ec_alg_by_oid(int len, const void *oid, - const struct ec_curve **curve); + const struct ec_curve **curve); const unsigned char *ec_alg_oid(const ssh_keyalg *alg, int *oidlen); extern const int ec_nist_curve_lengths[], n_ec_nist_curve_lengths; extern const int ec_ed_curve_lengths[], n_ec_ed_curve_lengths; @@ -631,8 +631,8 @@ mp_int *ssh_rsakex_decrypt( * Helper function for k generation in DSA, reused in ECDSA */ mp_int *dsa_gen_k(const char *id_string, - mp_int *modulus, mp_int *private_key, - unsigned char *digest, int digest_len); + mp_int *modulus, mp_int *private_key, + unsigned char *digest, int digest_len); struct ssh_cipher { const ssh_cipheralg *vt; @@ -1484,7 +1484,7 @@ bool import_possible(int type); int import_target_type(int type); bool import_encrypted(const Filename *filename, int type, char **comment); bool import_encrypted_s(const Filename *filename, BinarySource *src, - int type, char **comment); + int type, char **comment); int import_ssh1(const Filename *filename, int type, RSAKey *key, char *passphrase, const char **errmsg_p); int import_ssh1_s(BinarySource *src, int type, @@ -1492,7 +1492,7 @@ int import_ssh1_s(BinarySource *src, int type, ssh2_userkey *import_ssh2(const Filename *filename, int type, char *passphrase, const char **errmsg_p); ssh2_userkey *import_ssh2_s(BinarySource *src, int type, - char *passphrase, const char **errmsg_p); + char *passphrase, const char **errmsg_p); bool export_ssh1(const Filename *filename, int type, RSAKey *key, char *passphrase); bool export_ssh2(const Filename *filename, int type, diff --git a/ssh/common.c b/ssh/common.c index e9823a6d..fa5d48d0 100644 --- a/ssh/common.c +++ b/ssh/common.c @@ -1034,7 +1034,7 @@ SeatPromptResult verify_ssh_host_key( seat_dialog_text_append( text, SDT_PARA, "If you were expecting this change and trust the " "new key, %s to update %s's cache and carry on connecting.", - pds->hk_accept_action, appname); + pds->hk_accept_action, appname); seat_dialog_text_append( text, SDT_PARA, "If you want to carry on connecting but without " "updating the cache, %s.", pds->hk_connect_once_action); diff --git a/ssh/login1-server.c b/ssh/login1-server.c index 30ff9026..d01c7f4c 100644 --- a/ssh/login1-server.c +++ b/ssh/login1-server.c @@ -50,7 +50,7 @@ static bool ssh1_login_server_get_specials( PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx) { return false; } static void ssh1_login_server_special_cmd(PacketProtocolLayer *ppl, - SessionSpecialCode code, int arg) {} + SessionSpecialCode code, int arg) {} static void ssh1_login_server_reconfigure( PacketProtocolLayer *ppl, Conf *conf) {} diff --git a/ssh/login1.c b/ssh/login1.c index d9f3883e..52aaea0b 100644 --- a/ssh/login1.c +++ b/ssh/login1.c @@ -866,8 +866,8 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl) return; } } else if (conf_get_bool(s->conf, CONF_try_tis_auth) && - (s->supported_auths_mask & (1 << SSH1_AUTH_CCARD)) && - !s->ccard_auth_refused) { + (s->supported_auths_mask & (1 << SSH1_AUTH_CCARD)) && + !s->ccard_auth_refused) { ssh1_login_setup_tis_scc(s); s->pwpkt_type = SSH1_CMSG_AUTH_CCARD_RESPONSE; ppl_logevent("Requested CryptoCard authentication"); diff --git a/ssh/mainchan.c b/ssh/mainchan.c index 5c1769ea..52492ff6 100644 --- a/ssh/mainchan.c +++ b/ssh/mainchan.c @@ -344,7 +344,7 @@ static void mainchan_open_failure(Channel *chan, const char *errtext) } static size_t mainchan_send(Channel *chan, bool is_stderr, - const void *data, size_t length) + const void *data, size_t length) { assert(chan->vt == &mainchan_channelvt); mainchan *mc = container_of(chan, mainchan, chan); diff --git a/ssh/sftp.c b/ssh/sftp.c index a76702f8..b92f1fe7 100644 --- a/ssh/sftp.c +++ b/ssh/sftp.c @@ -575,7 +575,7 @@ static bool fxp_got_attrs(struct sftp_packet *pktin, struct fxp_attrs *attrs) } bool fxp_stat_recv(struct sftp_packet *pktin, struct sftp_request *req, - struct fxp_attrs *attrs) + struct fxp_attrs *attrs) { sfree(req); if (pktin->type == SSH_FXP_ATTRS) { diff --git a/ssh/sharing.c b/ssh/sharing.c index 7b52dc09..fae1d0d2 100644 --- a/ssh/sharing.c +++ b/ssh/sharing.c @@ -664,7 +664,7 @@ static struct share_xchannel *share_find_xchannel_by_server } static void share_remove_xchannel(struct ssh_sharing_connstate *cs, - struct share_xchannel *xc) + struct share_xchannel *xc) { del234(cs->xchannels_by_us, xc); del234(cs->xchannels_by_server, xc); diff --git a/ssh/verstring.c b/ssh/verstring.c index 4671903a..567a8e7d 100644 --- a/ssh/verstring.c +++ b/ssh/verstring.c @@ -43,7 +43,7 @@ static void ssh_verstring_handle_input(BinaryPacketProtocol *bpp); static void ssh_verstring_handle_output(BinaryPacketProtocol *bpp); static PktOut *ssh_verstring_new_pktout(int type); static void ssh_verstring_queue_disconnect(BinaryPacketProtocol *bpp, - const char *msg, int category); + const char *msg, int category); static const BinaryPacketProtocolVtable ssh_verstring_vtable = { .free = ssh_verstring_free, diff --git a/ssh/x11fwd.c b/ssh/x11fwd.c index 4a2073e6..c5698f9b 100644 --- a/ssh/x11fwd.c +++ b/ssh/x11fwd.c @@ -495,7 +495,7 @@ static size_t x11_send( while (len > 0 && xconn->data_read < 12 + xconn->auth_psize + xconn->auth_dsize) xconn->auth_data[xconn->data_read++ - 12 - - xconn->auth_psize] = (unsigned char) (len--, *data++); + xconn->auth_psize] = (unsigned char) (len--, *data++); if (xconn->data_read < 12 + xconn->auth_psize + xconn->auth_dsize) return 0; diff --git a/ssh/zlib.c b/ssh/zlib.c index 9ad04ed2..09b4afde 100644 --- a/ssh/zlib.c +++ b/ssh/zlib.c @@ -946,7 +946,7 @@ void zlib_decompress_cleanup(ssh_decompressor *dc) } static int zlib_huflookup(unsigned long *bitsp, int *nbitsp, - struct zlib_table *tab) + struct zlib_table *tab) { unsigned long bits = *bitsp; int nbits = *nbitsp; @@ -1094,7 +1094,7 @@ bool zlib_decompress_block(ssh_decompressor *dc, 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); + dctx->hdist); zlib_freetable(&dctx->lenlentable); dctx->lenlentable = NULL; dctx->state = INBLK; @@ -1112,7 +1112,7 @@ bool zlib_decompress_block(ssh_decompressor *dc, 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->lengths[dctx->lenptr - 1] : 0); dctx->state = TREES_LENREP; } break; diff --git a/sshpubk.c b/sshpubk.c index e54b8a35..efc3f2e5 100644 --- a/sshpubk.c +++ b/sshpubk.c @@ -1857,7 +1857,7 @@ static int key_type_s_internal(BinarySource *src) if (find_pubkey_alg_len(get_nonchars(src, " \n")) > 0 && get_chars(src, " ").len == 1 && get_chars(src, "0123456789ABCDEFGHIJKLMNOPQRSTUV" - "WXYZabcdefghijklmnopqrstuvwxyz+/=").len > 0 && + "WXYZabcdefghijklmnopqrstuvwxyz+/=").len > 0 && get_nonchars(src, " \n").len == 0) return SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH; diff --git a/terminal/terminal.c b/terminal/terminal.c index b6d97c4b..5310c650 100644 --- a/terminal/terminal.c +++ b/terminal/terminal.c @@ -647,7 +647,7 @@ static void makeliteral_truecolour(strbuf *b, termchar *c, unsigned long *state) * Put the used parts of the colour info into the buffer. */ put_byte(b, ((c->truecolour.fg.enabled ? 1 : 0) | - (c->truecolour.bg.enabled ? 2 : 0))); + (c->truecolour.bg.enabled ? 2 : 0))); if (c->truecolour.fg.enabled) { put_byte(b, c->truecolour.fg.r); put_byte(b, c->truecolour.fg.g); diff --git a/test/testcrypt.c b/test/testcrypt.c index 3d845673..fcc116f0 100644 --- a/test/testcrypt.c +++ b/test/testcrypt.c @@ -779,7 +779,7 @@ strbuf *ssh_cipher_decrypt_wrapper(ssh_cipher *c, ptrlen input) } strbuf *ssh_cipher_encrypt_length_wrapper(ssh_cipher *c, ptrlen input, - unsigned long seq) + unsigned long seq) { if (input.len != 4) fatal_error("ssh_cipher_encrypt_length: needs exactly 4 bytes"); @@ -789,7 +789,7 @@ strbuf *ssh_cipher_encrypt_length_wrapper(ssh_cipher *c, ptrlen input, } strbuf *ssh_cipher_decrypt_length_wrapper(ssh_cipher *c, ptrlen input, - unsigned long seq) + unsigned long seq) { if (input.len % ssh_cipher_alg(c)->blksize) fatal_error("ssh_cipher_decrypt_length: needs exactly 4 bytes"); @@ -964,7 +964,7 @@ int16_list *ntru_bias_wrapper(int16_list *in, unsigned bias, } int16_list *ntru_scale_wrapper(int16_list *in, unsigned scale, - unsigned p, unsigned q) + unsigned p, unsigned q) { int16_list_resize(in, p); int16_list *out = make_int16_list(p); diff --git a/unix/askpass.c b/unix/askpass.c index 841912d9..f2cbafac 100644 --- a/unix/askpass.c +++ b/unix/askpass.c @@ -438,7 +438,7 @@ static const char *gtk_askpass_setup(struct askpass_ctx *ctx, gtk_window_set_title(GTK_WINDOW(ctx->dialog), window_title); gtk_window_set_position(GTK_WINDOW(ctx->dialog), GTK_WIN_POS_CENTER); g_signal_connect(G_OBJECT(ctx->dialog), "delete-event", - G_CALLBACK(askpass_dialog_closed), ctx); + G_CALLBACK(askpass_dialog_closed), ctx); ctx->promptlabel = gtk_label_new(prompt_text); align_label_left(GTK_LABEL(ctx->promptlabel)); gtk_widget_show(ctx->promptlabel); diff --git a/unix/main-gtk-simple.c b/unix/main-gtk-simple.c index 2f09cfbc..4cbb5a31 100644 --- a/unix/main-gtk-simple.c +++ b/unix/main-gtk-simple.c @@ -468,7 +468,7 @@ bool do_cmdline(int argc, char **argv, bool do_everything, Conf *conf) break; /* finished command-line processing */ } else err = true, fprintf(stderr, "%s: -e expects an argument\n", - appname); + appname); } else if (!strcmp(p, "-title")) { EXPECTS_ARG; diff --git a/unix/sftpserver.c b/unix/sftpserver.c index 7257c5c9..453a3ee0 100644 --- a/unix/sftpserver.c +++ b/unix/sftpserver.c @@ -545,7 +545,7 @@ static void uss_read(SftpServer *srv, SftpReplyBuilder *reply, } static void uss_write(SftpServer *srv, SftpReplyBuilder *reply, - ptrlen handle, uint64_t offset, ptrlen data) + ptrlen handle, uint64_t offset, ptrlen data) { UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv); int fd; diff --git a/unix/unifont.c b/unix/unifont.c index 5eaded54..144ec87a 100644 --- a/unix/unifont.c +++ b/unix/unifont.c @@ -2573,7 +2573,7 @@ static void unifontsel_setup_stylelist(unifontsel_internal *fs, continue; /* we're filtering out this font */ } if (!info || !started || strnullcasecmp(currcs, info->charset) || - strnullcasecmp(currstyle, info->style)) { + strnullcasecmp(currstyle, info->style)) { /* * We've either finished a style/charset, or started a * new one, or both. diff --git a/unix/window.c b/unix/window.c index bdf9b598..d1c4bfff 100644 --- a/unix/window.c +++ b/unix/window.c @@ -3062,8 +3062,8 @@ static void gtkwin_clip_write( state->pasteout_data = snewn(len*6, char); state->pasteout_data_len = len*6; state->pasteout_data_len = wc_to_mb(inst->ucsdata.line_codepage, 0, - data, len, state->pasteout_data, - state->pasteout_data_len, NULL); + data, len, state->pasteout_data, + state->pasteout_data_len, NULL); if (state->pasteout_data_len == 0) { sfree(state->pasteout_data); state->pasteout_data = NULL; diff --git a/utils/conf.c b/utils/conf.c index ecd26cd0..246dc3eb 100644 --- a/utils/conf.c +++ b/utils/conf.c @@ -336,7 +336,7 @@ char *conf_get_str_str(Conf *conf, int primary, const char *secondary) } char *conf_get_str_strs(Conf *conf, int primary, - char *subkeyin, char **subkeyout) + char *subkeyin, char **subkeyout) { struct constkey key; struct conf_entry *entry; diff --git a/utils/debug.c b/utils/debug.c index 806b250a..79e437a2 100644 --- a/utils/debug.c +++ b/utils/debug.c @@ -43,8 +43,8 @@ void debug_memdump(const void *buf, int len, bool L) foo[i] = ' '; } else { debug_printf("%c%2.2x", - &p[i] != (unsigned char *) buf - && i % 4 ? '.' : ' ', p[i] + &p[i] != (unsigned char *) buf + && i % 4 ? '.' : ' ', p[i] ); if (p[i] >= ' ' && p[i] <= '~') foo[i] = (char) p[i]; diff --git a/windows/controls.c b/windows/controls.c index 37efc9be..e02e3e3d 100644 --- a/windows/controls.c +++ b/windows/controls.c @@ -1854,7 +1854,7 @@ bool winctrl_handle_command(struct dlgparam *dp, UINT msg, SetMapMode(hdc, MM_TEXT); /* ensure logical units == pixels */ GetTextExtentPoint32(hdc, (char *)c->data, - strlen((char *)c->data), &s); + 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, @@ -2286,7 +2286,7 @@ void dlg_listbox_addwithid(dlgcontrol *ctrl, dlgparam *dp, msg = (c->ctrl->type==CTRL_LISTBOX && c->ctrl->listbox.height!=0 ? LB_ADDSTRING : CB_ADDSTRING); msg2 = (c->ctrl->type==CTRL_LISTBOX && c->ctrl->listbox.height!=0 ? - LB_SETITEMDATA : CB_SETITEMDATA); + 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); } @@ -2547,7 +2547,7 @@ void dlg_refresh(dlgcontrol *ctrl, dlgparam *dp) i++) { if (c->ctrl && c->ctrl->handler != NULL) c->ctrl->handler(c->ctrl, dp, - dp->data, EVENT_REFRESH); + dp->data, EVENT_REFRESH); } } } else { diff --git a/windows/dialog.c b/windows/dialog.c index 3666bfbc..48e188a6 100644 --- a/windows/dialog.c +++ b/windows/dialog.c @@ -928,7 +928,7 @@ static INT_PTR HostKeyMoreInfoProc(HWND hwnd, UINT msg, WPARAM wParam, MapDialogRect(hwnd, &r); HWND ctl = GetDlgItem(hwnd, IDOK); SetWindowPos(ctl, NULL, r.left, r.top, 0, 0, - SWP_NOSIZE | SWP_NOREDRAW | SWP_NOZORDER); + SWP_NOSIZE | SWP_NOREDRAW | SWP_NOZORDER); r.left = r.top = r.right = 0; r.bottom = 300; diff --git a/windows/gss.c b/windows/gss.c index 646e17fc..8f467cf2 100644 --- a/windows/gss.c +++ b/windows/gss.c @@ -665,8 +665,8 @@ static Ssh_gss_stat ssh_sspi_verify_mic(struct ssh_gss_library *lib, InputSecurityToken[1].pvBuffer = mic->value; winctx->maj_stat = p_VerifySignature(&winctx->context, - &InputBufferDescriptor, - 0, &qop); + &InputBufferDescriptor, + 0, &qop); return winctx->maj_stat; } diff --git a/windows/pageant.c b/windows/pageant.c index 47423b2a..077a6812 100644 --- a/windows/pageant.c +++ b/windows/pageant.c @@ -103,7 +103,7 @@ struct PassphraseProcStruct { * Dialog-box function for the Licence box. */ static INT_PTR CALLBACK LicenceProc(HWND hwnd, UINT msg, - WPARAM wParam, LPARAM lParam) + WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_INITDIALOG: @@ -128,7 +128,7 @@ static INT_PTR CALLBACK LicenceProc(HWND hwnd, UINT msg, * Dialog-box function for the About box. */ static INT_PTR CALLBACK AboutProc(HWND hwnd, UINT msg, - WPARAM wParam, LPARAM lParam) + WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_INITDIALOG: { @@ -214,7 +214,7 @@ static void end_passphrase_dialog(HWND hwnd, INT_PTR result) * Dialog-box function for the passphrase box. */ static INT_PTR CALLBACK PassphraseProc(HWND hwnd, UINT msg, - WPARAM wParam, LPARAM lParam) + WPARAM wParam, LPARAM lParam) { struct PassphraseProcStruct *p; @@ -592,7 +592,7 @@ static void prompt_add_keyfile(bool encrypted) * Dialog-box function for the key list box. */ static INT_PTR CALLBACK KeyListProc(HWND hwnd, UINT msg, - WPARAM wParam, LPARAM lParam) + WPARAM wParam, LPARAM lParam) { static const struct { const char *name; @@ -1865,7 +1865,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) AppendMenu(systray_menu, MF_SEPARATOR, 0, 0); } AppendMenu(systray_menu, MF_ENABLED, IDM_VIEWKEYS, - "&View Keys"); + "&View Keys"); AppendMenu(systray_menu, MF_ENABLED, IDM_ADDKEY, "Add &Key"); AppendMenu(systray_menu, MF_ENABLED, IDM_ADDKEY_ENCRYPTED, "Add key (encrypted)"); diff --git a/windows/puttygen.c b/windows/puttygen.c index 4c836873..74d5a7c2 100644 --- a/windows/puttygen.c +++ b/windows/puttygen.c @@ -203,7 +203,7 @@ struct PassphraseProcStruct { * Dialog-box function for the passphrase box. */ static INT_PTR CALLBACK PassphraseProc(HWND hwnd, UINT msg, - WPARAM wParam, LPARAM lParam) + WPARAM wParam, LPARAM lParam) { static char **passphrase = NULL; struct PassphraseProcStruct *p; @@ -483,7 +483,7 @@ static bool prompt_keyfile(HWND hwnd, char *dlgtitle, * Dialog-box function for the Licence box. */ static INT_PTR CALLBACK LicenceProc(HWND hwnd, UINT msg, - WPARAM wParam, LPARAM lParam) + WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_INITDIALOG: { @@ -522,7 +522,7 @@ static INT_PTR CALLBACK LicenceProc(HWND hwnd, UINT msg, * Dialog-box function for the About box. */ static INT_PTR CALLBACK AboutProc(HWND hwnd, UINT msg, - WPARAM wParam, LPARAM lParam) + WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_INITDIALOG: @@ -1457,7 +1457,7 @@ static INT_PTR CertInfoProc(HWND hwnd, UINT msg, WPARAM wParam, MapDialogRect(hwnd, &r); HWND ctl = GetDlgItem(hwnd, IDOK); SetWindowPos(ctl, NULL, r.left, r.top, 0, 0, - SWP_NOSIZE | SWP_NOREDRAW | SWP_NOZORDER); + SWP_NOSIZE | SWP_NOREDRAW | SWP_NOZORDER); r.left = r.top = r.right = 0; r.bottom = 300; @@ -1496,7 +1496,7 @@ static INT_PTR CertInfoProc(HWND hwnd, UINT msg, WPARAM wParam, * Dialog-box function for the main PuTTYgen dialog box. */ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, - WPARAM wParam, LPARAM lParam) + WPARAM wParam, LPARAM lParam) { const int DEMO_SCREENSHOT_TIMER_ID = 1230; static const char entropy_msg[] = diff --git a/windows/storage.c b/windows/storage.c index 30147a45..7ac299a9 100644 --- a/windows/storage.c +++ b/windows/storage.c @@ -348,7 +348,7 @@ int check_stored_host_key(const char *hostname, int port, } bool have_ssh_host_key(const char *hostname, int port, - const char *keytype) + const char *keytype) { /* * If we have a host key, check_stored_host_key will return 0 or 2. diff --git a/windows/window.c b/windows/window.c index d810719b..ee6ed580 100644 --- a/windows/window.c +++ b/windows/window.c @@ -1724,8 +1724,8 @@ static void wintw_request_resize(TermWin *tw, int w, int h) height = extra_height + font_height * h; SetWindowPos(wgs.term_hwnd, NULL, 0, 0, width, height, - SWP_NOACTIVATE | SWP_NOCOPYBITS | - SWP_NOMOVE | SWP_NOZORDER); + SWP_NOACTIVATE | SWP_NOCOPYBITS | + SWP_NOMOVE | SWP_NOZORDER); } else { /* * If we're resizing by changing the font, we must tell the @@ -1864,10 +1864,10 @@ static void reset_window(int reinit) { rect.right += (window_border * 2); rect.bottom += (window_border * 2); OffsetRect(&dpi_info.new_wnd_rect, - ((dpi_info.new_wnd_rect.right - dpi_info.new_wnd_rect.left) - - (rect.right - rect.left)) / 2, - ((dpi_info.new_wnd_rect.bottom - dpi_info.new_wnd_rect.top) - - (rect.bottom - rect.top)) / 2); + ((dpi_info.new_wnd_rect.right - dpi_info.new_wnd_rect.left) - + (rect.right - rect.left)) / 2, + ((dpi_info.new_wnd_rect.bottom - dpi_info.new_wnd_rect.top) - + (rect.bottom - rect.top)) / 2); SetWindowPos(wgs.term_hwnd, NULL, dpi_info.new_wnd_rect.left, dpi_info.new_wnd_rect.top, rect.right - rect.left, rect.bottom - rect.top, @@ -1912,7 +1912,7 @@ static void reset_window(int reinit) { */ if ((resize_action == RESIZE_TERM && reinit<=0) || (resize_action == RESIZE_EITHER && reinit<0) || - reinit>0) { + reinit>0) { offset_width = offset_height = window_border; extra_width = wr.right - wr.left - cr.right + cr.left + offset_width*2; extra_height = wr.bottom - wr.top - cr.bottom + cr.top +offset_height*2; @@ -4628,8 +4628,8 @@ static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam, break; p += format_small_keypad_key((char *)p, term, sk_key, - shift_state & 1, shift_state & 2, - left_alt, &consumed_alt); + shift_state & 1, shift_state & 2, + left_alt, &consumed_alt); if (consumed_alt) left_alt = false; /* supersedes the usual prefixing of Esc */ return p - output; @@ -5627,7 +5627,7 @@ static void wintw_bell(TermWin *tw, int mode) } else if (mode == BELL_WAVEFILE) { Filename *bell_wavefile = conf_get_filename(conf, CONF_bell_wavefile); if (!p_PlaySound || !p_PlaySound(bell_wavefile->path, NULL, - SND_ASYNC | SND_FILENAME)) { + SND_ASYNC | SND_FILENAME)) { char *buf, *otherbuf; show_mouseptr(true); buf = dupprintf( -- cgit v1.2.3 From 04c1617f207241a543d9f0084c4df577d65f1cf4 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Wed, 3 Aug 2022 20:48:46 +0100 Subject: Formatting: realign labels and case/default statements. My aim has always been to have those back-dented by 2 spaces (half an indent level) compared to the statements around them, so that in particular switch statements have distinct alignment for the statement, the cases and the interior code without consuming two whole indent levels. This patch sweeps up all the violations of that principle found by my bulk-reindentation exercise. --- import.c | 20 +++++------ ldisc.c | 2 +- otherbackends/supdup.c | 94 ++++++++++++++++++++++++------------------------ unix/agent-client.c | 2 +- unix/network.c | 4 +-- unix/pty.c | 2 +- unix/sftp.c | 8 ++--- unix/storage.c | 2 +- unix/window.c | 2 +- windows/network.c | 2 +- windows/printing.c | 4 +-- windows/sftp.c | 8 ++--- windows/utils/security.c | 2 +- windows/window.c | 2 +- 14 files changed, 77 insertions(+), 77 deletions(-) diff --git a/import.c b/import.c index 7e0d7b74..79a4359d 100644 --- a/import.c +++ b/import.c @@ -498,7 +498,7 @@ static struct openssh_pem_key *load_openssh_pem_key(BinarySource *src, if (errmsg_p) *errmsg_p = NULL; return ret; - error: + error: if (line) { smemclr(line, strlen(line)); sfree(line); @@ -801,7 +801,7 @@ static ssh2_userkey *openssh_pem_read( errmsg = NULL; /* no error */ retval = retkey; - error: + error: strbuf_free(blob); strbuf_free(key->keyblob); smemclr(key, sizeof(*key)); @@ -1084,7 +1084,7 @@ static bool openssh_pem_write( fclose(fp); ret = true; - error: + error: if (outblob) strbuf_free(outblob); if (spareblob) { @@ -1298,7 +1298,7 @@ static struct openssh_new_key *load_openssh_new_key(BinarySource *filesrc, if (errmsg_p) *errmsg_p = NULL; return ret; - error: + error: if (line) { smemclr(line, strlen(line)); sfree(line); @@ -1488,7 +1488,7 @@ static ssh2_userkey *openssh_new_read( retval = retkey; retkey = NULL; /* prevent the free */ - error: + error: if (retkey) { sfree(retkey->comment); if (retkey->key) @@ -1619,7 +1619,7 @@ static bool openssh_new_write( fclose(fp); ret = true; - error: + error: if (cblob) strbuf_free(cblob); if (privblob) @@ -1854,7 +1854,7 @@ static struct sshcom_key *load_sshcom_key(BinarySource *src, if (errmsg_p) *errmsg_p = NULL; return ret; - error: + error: if (line) { smemclr(line, strlen(line)); sfree(line); @@ -1892,7 +1892,7 @@ static bool sshcom_encrypted(BinarySource *filesrc, char **comment) if (!ptrlen_eq_string(str, "none")) answer = true; - done: + done: if (key) { *comment = dupstr(key->comment); strbuf_free(key->keyblob); @@ -2143,7 +2143,7 @@ static ssh2_userkey *sshcom_read( errmsg = NULL; /* no error */ ret = retkey; - error: + error: if (blob) { strbuf_free(blob); } @@ -2322,7 +2322,7 @@ static bool sshcom_write( fclose(fp); ret = true; - error: + error: if (outblob) strbuf_free(outblob); if (privblob) diff --git a/ldisc.c b/ldisc.c index f0e59658..caff52d0 100644 --- a/ldisc.c +++ b/ldisc.c @@ -522,7 +522,7 @@ void ldisc_send(Ldisc *ldisc, const void *vbuf, int len, bool interactive) } /* FALLTHROUGH */ default: /* get to this label from ^V handler */ - default_case: + default_case: sgrowarray(ldisc->buf, ldisc->bufsiz, ldisc->buflen); ldisc->buf[ldisc->buflen++] = c; if (ECHOING) diff --git a/otherbackends/supdup.c b/otherbackends/supdup.c index 54cfbef9..3479f0f9 100644 --- a/otherbackends/supdup.c +++ b/otherbackends/supdup.c @@ -202,49 +202,49 @@ static void do_toplevel(Supdup *supdup, strbuf *outbuf, int c) supdup->td_argindex = 0; supdup->td_code = c; switch (c) { - case TDMOV: + case TDMOV: // %TD codes using 4 arguments supdup->td_argcount = 4; supdup->tdstate = TD_ARGS; break; - case TDMV0: - case TDMV1: + case TDMV0: + case TDMV1: // %TD codes using 2 arguments supdup->td_argcount = 2; supdup->tdstate = TD_ARGS; break; - case TDQOT: - case TDILP: - case TDDLP: - case TDICP: - case TDDCP: + case TDQOT: + case TDILP: + case TDDLP: + case TDICP: + case TDDCP: // %TD codes using 1 argument supdup->td_argcount = 1; supdup->tdstate = TD_ARGS; break; - case TDEOF: - case TDEOL: - case TDDLF: - case TDCRL: - case TDNOP: - case TDORS: - case TDFS: - case TDCLR: - case TDBEL: - case TDBOW: - case TDRST: - case TDBS: - case TDCR: - case TDLF: + case TDEOF: + case TDEOL: + case TDDLF: + case TDCRL: + case TDNOP: + case TDORS: + case TDFS: + case TDCLR: + case TDBEL: + case TDBOW: + case TDRST: + case TDBS: + case TDCR: + case TDLF: // %TD codes using 0 arguments supdup->td_argcount = 0; supdup->tdstate = TD_ARGSDONE; break; - default: + default: // Unhandled, ignore break; } @@ -279,7 +279,7 @@ static void do_argsdone(Supdup *supdup, strbuf *outbuf, int c) // Arguments for %TD code have been collected; dispatch based // on the %TD code we're handling. switch (supdup->td_code) { - case TDMOV: + case TDMOV: /* General cursor position code. Followed by four bytes; the first two are the "old" vertical and horizontal @@ -292,8 +292,8 @@ static void do_argsdone(Supdup *supdup, strbuf *outbuf, int c) put_fmt(outbuf, "\033[%d;%dH", supdup->td_args[2]+1, supdup->td_args[3]+1); break; - case TDMV0: - case TDMV1: + case TDMV0: + case TDMV1: /* General cursor position code. Followed by two bytes; the new vertical and horizontal positions. @@ -301,7 +301,7 @@ static void do_argsdone(Supdup *supdup, strbuf *outbuf, int c) put_fmt(outbuf, "\033[%d;%dH", supdup->td_args[0]+1, supdup->td_args[1]+1); break; - case TDEOF: + case TDEOF: /* Erase to end of screen. This is an optional function since many terminals do not support this. If the @@ -315,7 +315,7 @@ static void do_argsdone(Supdup *supdup, strbuf *outbuf, int c) put_fmt(outbuf, "\033[J"); break; - case TDEOL: + case TDEOL: /* Erase to end of line. This erases the character position the cursor is at and all positions to the right @@ -324,7 +324,7 @@ static void do_argsdone(Supdup *supdup, strbuf *outbuf, int c) put_fmt(outbuf, "\033[K"); break; - case TDDLF: + case TDDLF: /* Clear the character position the cursor is on. The cursor does not move. @@ -332,7 +332,7 @@ static void do_argsdone(Supdup *supdup, strbuf *outbuf, int c) put_fmt(outbuf, "\033[X"); break; - case TDCRL: + case TDCRL: /* If the cursor is not on the bottom line of the screen, move cursor to the beginning of the next line and clear @@ -342,13 +342,13 @@ static void do_argsdone(Supdup *supdup, strbuf *outbuf, int c) put_fmt(outbuf, "\015\012"); break; - case TDNOP: + case TDNOP: /* No-op; should be ignored. */ break; - case TDORS: + case TDORS: /* Output reset. This code serves as a data mark for aborting output much as IAC DM does in the ordinary @@ -364,7 +364,7 @@ static void do_argsdone(Supdup *supdup, strbuf *outbuf, int c) sk_write(supdup->s, buf, 4); break; - case TDQOT: + case TDQOT: /* Quotes the following character. This is used when sending 8-bit codes which are not %TD codes, for @@ -376,7 +376,7 @@ static void do_argsdone(Supdup *supdup, strbuf *outbuf, int c) put_byte(outbuf, supdup->td_args[0]); break; - case TDFS: + case TDFS: /* Non-destructive forward space. The cursor moves right one position; this code will not be sent at the end of a @@ -386,7 +386,7 @@ static void do_argsdone(Supdup *supdup, strbuf *outbuf, int c) put_fmt(outbuf, "\033[C"); break; - case TDCLR: + case TDCLR: /* Erase the screen. Home the cursor to the top left hand corner of the screen. @@ -394,7 +394,7 @@ static void do_argsdone(Supdup *supdup, strbuf *outbuf, int c) put_fmt(outbuf, "\033[2J\033[H"); break; - case TDBEL: + case TDBEL: /* Generate an audio tone, bell, whatever. */ @@ -402,7 +402,7 @@ static void do_argsdone(Supdup *supdup, strbuf *outbuf, int c) put_fmt(outbuf, "\007"); break; - case TDILP: + case TDILP: /* Insert blank lines at the cursor; followed by a byte containing a count of the number of blank lines to @@ -413,7 +413,7 @@ static void do_argsdone(Supdup *supdup, strbuf *outbuf, int c) put_fmt(outbuf, "\033[%dL", supdup->td_args[0]); break; - case TDDLP: + case TDDLP: /* Delete lines at the cursor; followed by a count. The cursor is unmoved. The first line deleted is the one @@ -424,7 +424,7 @@ static void do_argsdone(Supdup *supdup, strbuf *outbuf, int c) put_fmt(outbuf, "\033[%dM", supdup->td_args[0]); break; - case TDICP: + case TDICP: /* Insert blank character positions at the cursor; followed by a count. The cursor is unmoved. The character the @@ -435,7 +435,7 @@ static void do_argsdone(Supdup *supdup, strbuf *outbuf, int c) put_fmt(outbuf, "\033[%d@", supdup->td_args[0]); break; - case TDDCP: + case TDDCP: /* Delete characters at the cursor; followed by a count. The cursor is unmoved. The first character deleted is @@ -445,8 +445,8 @@ static void do_argsdone(Supdup *supdup, strbuf *outbuf, int c) put_fmt(outbuf, "\033[%dP", supdup->td_args[0]); break; - case TDBOW: - case TDRST: + case TDBOW: + case TDRST: /* Display black characters on white screen. HIGHLY OPTIONAL. @@ -463,7 +463,7 @@ static void do_argsdone(Supdup *supdup, strbuf *outbuf, int c) * official documentation, behavior is based on UNIX * SUPDUP implementation from MIT. */ - case TDBS: + case TDBS: /* * Backspace -- move cursor back one character (does not * appear to wrap...) @@ -471,14 +471,14 @@ static void do_argsdone(Supdup *supdup, strbuf *outbuf, int c) put_byte(outbuf, '\010'); break; - case TDLF: + case TDLF: /* * Linefeed -- move cursor down one line (again, no wrapping) */ put_byte(outbuf, '\012'); break; - case TDCR: + case TDCR: /* * Carriage return -- move cursor to start of current line. */ @@ -512,7 +512,7 @@ static void do_supdup_read(Supdup *supdup, const char *buf, size_t len) while (len--) { int c = (unsigned char)*buf++; switch (supdup->state) { - case CONNECTING: + case CONNECTING: // "Following the transmission of the terminal options by // the user, the server should respond with an ASCII // greeting message, terminated with a %TDNOP code..." @@ -528,7 +528,7 @@ static void do_supdup_read(Supdup *supdup, const char *buf, size_t len) } break; - case CONNECTED: + case CONNECTED: // "All transmissions from the server after the %TDNOP // [see above] are either printing characters or virtual // terminal display codes." Forward these on to the diff --git a/unix/agent-client.c b/unix/agent-client.c index e4d671d2..6d7a3662 100644 --- a/unix/agent-client.c +++ b/unix/agent-client.c @@ -219,7 +219,7 @@ agent_pending_query *agent_query( uxsel_set(sock, SELECT_R, agent_select_result); return conn; - failure: + failure: *out = NULL; *outlen = 0; return NULL; diff --git a/unix/network.c b/unix/network.c index e95af9f7..31c754bd 100644 --- a/unix/network.c +++ b/unix/network.c @@ -731,7 +731,7 @@ static int try_connect(NetSocket *sock) uxsel_tell(sock); - ret: + ret: /* * No matter what happened, put the socket back in the tree. @@ -1043,7 +1043,7 @@ void *sk_getxdmdata(Socket *sock, int *lenp) PUT_16BIT_MSB_FIRST(buf+4, ntohs(u.sin.sin_port)); break; #ifndef NO_IPV6 - case AF_INET6: + case AF_INET6: *lenp = 6; buf = snewn(*lenp, char); if (IN6_IS_ADDR_V4MAPPED(&u.sin6.sin6_addr)) { diff --git a/unix/pty.c b/unix/pty.c index 625f1bb1..05c58929 100644 --- a/unix/pty.c +++ b/unix/pty.c @@ -354,7 +354,7 @@ static void pty_open_master(Pty *pty) fprintf(stderr, "pterm: unable to open a pseudo-terminal device\n"); exit(1); - got_one: + got_one: /* We need to chown/chmod the /dev/ttyXX device. */ gp = getgrnam("tty"); diff --git a/unix/sftp.c b/unix/sftp.c index 9d099f55..023f41ee 100644 --- a/unix/sftp.c +++ b/unix/sftp.c @@ -265,16 +265,16 @@ int seek_file(WFile *f, uint64_t offset, int whence) int lseek_whence; switch (whence) { - case FROM_START: + case FROM_START: lseek_whence = SEEK_SET; break; - case FROM_CURRENT: + case FROM_CURRENT: lseek_whence = SEEK_CUR; break; - case FROM_END: + case FROM_END: lseek_whence = SEEK_END; break; - default: + default: return -1; } diff --git a/unix/storage.c b/unix/storage.c index 83e1c19c..a2456fc4 100644 --- a/unix/storage.c +++ b/unix/storage.c @@ -806,7 +806,7 @@ int check_stored_host_key(const char *hostname, int port, else ret = 2; /* key mismatch */ - done: + done: sfree(line); if (ret != 1) break; diff --git a/unix/window.c b/unix/window.c index d1c4bfff..d35b1106 100644 --- a/unix/window.c +++ b/unix/window.c @@ -1978,7 +1978,7 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) goto done; } - done: + done: if (end-start > 0) { if (special) { diff --git a/windows/network.c b/windows/network.c index 72afbe37..d0ad14a9 100644 --- a/windows/network.c +++ b/windows/network.c @@ -1068,7 +1068,7 @@ static DWORD try_connect(NetSocket *sock) err = 0; - ret: + ret: /* * No matter what happened, put the socket back in the tree. diff --git a/windows/printing.c b/windows/printing.c index 0ec81376..2286c236 100644 --- a/windows/printing.c +++ b/windows/printing.c @@ -131,7 +131,7 @@ printer_enum *printer_start_enum(int *nprinters_ptr) return ret; - error: + error: sfree(buffer); sfree(ret); *nprinters_ptr = 0; @@ -195,7 +195,7 @@ printer_job *printer_start_job(char *printer) return ret; - error: + error: if (pagestarted) p_EndPagePrinter(ret->hprinter); if (jobstarted) diff --git a/windows/sftp.c b/windows/sftp.c index fc8e711b..a6546269 100644 --- a/windows/sftp.c +++ b/windows/sftp.c @@ -222,16 +222,16 @@ int seek_file(WFile *f, uint64_t offset, int whence) DWORD movemethod; switch (whence) { - case FROM_START: + case FROM_START: movemethod = FILE_BEGIN; break; - case FROM_CURRENT: + case FROM_CURRENT: movemethod = FILE_CURRENT; break; - case FROM_END: + case FROM_END: movemethod = FILE_END; break; - default: + default: return -1; } diff --git a/windows/utils/security.c b/windows/utils/security.c index 14e55090..48330437 100644 --- a/windows/utils/security.c +++ b/windows/utils/security.c @@ -155,7 +155,7 @@ static bool getsids(char **error) ret = true; - cleanup: + cleanup: return ret; } diff --git a/windows/window.c b/windows/window.c index ee6ed580..6be9cbe5 100644 --- a/windows/window.c +++ b/windows/window.c @@ -892,7 +892,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) run_toplevel_callbacks(); } - finished: + finished: cleanup_exit(msg.wParam); /* this doesn't return... */ return msg.wParam; /* ... but optimiser doesn't know */ } -- cgit v1.2.3 From 14203bc54ff09fad1f4c0e568ee77ea86aedaf34 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Wed, 3 Aug 2022 20:48:46 +0100 Subject: Formatting: standardise on "func(\n", not "func\n(". If the function name (or expression) in a function call or declaration is itself so long that even the first argument doesn't fit after it on the same line, or if that would leave so little space that it would be silly to try to wrap all the run-on lines into a tall thin column, then I used to do this ludicrously_long_function_name (arg1, arg2, arg3); and now prefer this ludicrously_long_function_name( arg1, arg2, arg3); I picked up the habit from Python, where the latter idiom is required by Python's syntactic significance of newlines (you can write the former if you use a backslash-continuation, but pretty much everyone seems to agree that that's much uglier). But I've found it works well in C as well: it makes it more obvious that the previous line is incomplete, it gives you a tiny bit more space to wrap the following lines into (the old idiom indents the _third_ line one space beyond the second), and I generally turn out to agree with the knock-on indentation decisions made by at least Emacs if you do it in the middle of a complex expression. Plus, of course, using the _same_ idiom between C and Python means less state-switching. So, while I'm making annoying indentation changes in general, this seems like a good time to dig out all the cases of the old idiom in this code, and switch them over to the new. --- pscp.c | 4 +- ssh/sftp.c | 4 +- ssh/sharing.c | 64 +++++++------ ssh/userauth2-client.c | 18 ++-- unix/askpass.c | 8 +- unix/dialog.c | 219 ++++++++++++++++++++++---------------------- unix/unifont.c | 91 +++++++++--------- unix/utils/our_dialog.c | 6 +- unix/window.c | 9 +- windows/agent-client.c | 4 +- windows/dialog.c | 8 +- windows/jump-list.c | 24 ++--- windows/named-pipe-server.c | 38 ++++---- windows/pageant.c | 8 +- windows/puttygen.c | 22 ++--- windows/storage.c | 4 +- windows/utils/security.c | 8 +- 17 files changed, 268 insertions(+), 271 deletions(-) diff --git a/pscp.c b/pscp.c index d6cc1250..7e26fa40 100644 --- a/pscp.c +++ b/pscp.c @@ -2189,8 +2189,8 @@ 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] 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"); diff --git a/ssh/sftp.c b/ssh/sftp.c index b92f1fe7..7debf94c 100644 --- a/ssh/sftp.c +++ b/ssh/sftp.c @@ -283,8 +283,8 @@ bool fxp_init(void) return false; } if (remotever > SFTP_PROTO_VERSION) { - fxp_internal_error - ("remote protocol is more advanced than we support"); + fxp_internal_error( + "remote protocol is more advanced than we support"); sftp_pkt_free(pktin); return false; } diff --git a/ssh/sharing.c b/ssh/sharing.c index fae1d0d2..827830f5 100644 --- a/ssh/sharing.c +++ b/ssh/sharing.c @@ -530,8 +530,8 @@ void sharestate_free(ssh_sharing_state *sharestate) sfree(sharestate); } -static struct share_halfchannel *share_add_halfchannel - (struct ssh_sharing_connstate *cs, unsigned server_id) +static struct share_halfchannel *share_add_halfchannel( + struct ssh_sharing_connstate *cs, unsigned server_id) { struct share_halfchannel *hc = snew(struct share_halfchannel); hc->server_id = server_id; @@ -544,8 +544,8 @@ static struct share_halfchannel *share_add_halfchannel } } -static struct share_halfchannel *share_find_halfchannel - (struct ssh_sharing_connstate *cs, unsigned server_id) +static struct share_halfchannel *share_find_halfchannel( + struct ssh_sharing_connstate *cs, unsigned server_id) { struct share_halfchannel dummyhc; dummyhc.server_id = server_id; @@ -559,9 +559,9 @@ static void share_remove_halfchannel(struct ssh_sharing_connstate *cs, sfree(hc); } -static struct share_channel *share_add_channel - (struct ssh_sharing_connstate *cs, unsigned downstream_id, - unsigned upstream_id, unsigned server_id, int state, int maxpkt) +static struct share_channel *share_add_channel( + struct ssh_sharing_connstate *cs, unsigned downstream_id, + unsigned upstream_id, unsigned server_id, int state, int maxpkt) { struct share_channel *chan = snew(struct share_channel); chan->downstream_id = downstream_id; @@ -598,16 +598,16 @@ static void share_channel_set_server_id(struct ssh_sharing_connstate *cs, add234(cs->channels_by_server, chan); } -static struct share_channel *share_find_channel_by_upstream - (struct ssh_sharing_connstate *cs, unsigned upstream_id) +static struct share_channel *share_find_channel_by_upstream( + struct ssh_sharing_connstate *cs, unsigned upstream_id) { struct share_channel dummychan; dummychan.upstream_id = upstream_id; return find234(cs->channels_by_us, &dummychan, NULL); } -static struct share_channel *share_find_channel_by_server - (struct ssh_sharing_connstate *cs, unsigned server_id) +static struct share_channel *share_find_channel_by_server( + struct ssh_sharing_connstate *cs, unsigned server_id) { struct share_channel dummychan; dummychan.server_id = server_id; @@ -626,9 +626,8 @@ static void share_remove_channel(struct ssh_sharing_connstate *cs, sfree(chan); } -static struct share_xchannel *share_add_xchannel - (struct ssh_sharing_connstate *cs, - unsigned upstream_id, unsigned server_id) +static struct share_xchannel *share_add_xchannel( + struct ssh_sharing_connstate *cs, unsigned upstream_id, unsigned server_id) { struct share_xchannel *xc = snew(struct share_xchannel); xc->upstream_id = upstream_id; @@ -647,16 +646,16 @@ static struct share_xchannel *share_add_xchannel return xc; } -static struct share_xchannel *share_find_xchannel_by_upstream - (struct ssh_sharing_connstate *cs, unsigned upstream_id) +static struct share_xchannel *share_find_xchannel_by_upstream( + struct ssh_sharing_connstate *cs, unsigned upstream_id) { struct share_xchannel dummyxc; dummyxc.upstream_id = upstream_id; return find234(cs->xchannels_by_us, &dummyxc, NULL); } -static struct share_xchannel *share_find_xchannel_by_server - (struct ssh_sharing_connstate *cs, unsigned server_id) +static struct share_xchannel *share_find_xchannel_by_server( + struct ssh_sharing_connstate *cs, unsigned server_id) { struct share_xchannel dummyxc; dummyxc.server_id = server_id; @@ -671,9 +670,8 @@ static void share_remove_xchannel(struct ssh_sharing_connstate *cs, share_xchannel_free(xc); } -static struct share_forwarding *share_add_forwarding - (struct ssh_sharing_connstate *cs, - const char *host, int port) +static struct share_forwarding *share_add_forwarding( + struct ssh_sharing_connstate *cs, const char *host, int port) { struct share_forwarding *fwd = snew(struct share_forwarding); fwd->host = dupstr(host); @@ -687,8 +685,8 @@ static struct share_forwarding *share_add_forwarding return fwd; } -static struct share_forwarding *share_find_forwarding - (struct ssh_sharing_connstate *cs, const char *host, int port) +static struct share_forwarding *share_find_forwarding( + struct ssh_sharing_connstate *cs, const char *host, int port) { struct share_forwarding dummyfwd, *ret; dummyfwd.host = dupstr(host); @@ -1013,10 +1011,10 @@ void share_dead_xchannel_respond(struct ssh_sharing_connstate *cs, if (get_bool(src)) { strbuf *packet = strbuf_new(); put_uint32(packet, xc->server_id); - ssh_send_packet_from_downstream - (cs->parent->cl, cs->id, SSH2_MSG_CHANNEL_FAILURE, - packet->s, packet->len, - "downstream refused X channel open"); + ssh_send_packet_from_downstream( + cs->parent->cl, cs->id, SSH2_MSG_CHANNEL_FAILURE, + packet->s, packet->len, + "downstream refused X channel open"); strbuf_free(packet); } } else if (msg->type == SSH2_MSG_CHANNEL_CLOSE) { @@ -1380,9 +1378,9 @@ static void share_got_pkt_from_downstream(struct ssh_sharing_connstate *cs, * cleaned up if downstream goes away. */ pkt[wantreplypos] = 1; - ssh_send_packet_from_downstream - (cs->parent->cl, cs->id, type, pkt, pktlen, - orig_wantreply ? NULL : "upstream added want_reply flag"); + ssh_send_packet_from_downstream( + cs->parent->cl, cs->id, type, pkt, pktlen, + orig_wantreply ? NULL : "upstream added want_reply flag"); fwd = share_add_forwarding(cs, host, port); ssh_sharing_queue_global_request(cs->parent->cl, cs); @@ -1443,9 +1441,9 @@ static void share_got_pkt_from_downstream(struct ssh_sharing_connstate *cs, * deleted even if downstream doesn't want to know. */ pkt[wantreplypos] = 1; - ssh_send_packet_from_downstream - (cs->parent->cl, cs->id, type, pkt, pktlen, - orig_wantreply ? NULL : "upstream added want_reply flag"); + ssh_send_packet_from_downstream( + cs->parent->cl, cs->id, type, pkt, pktlen, + orig_wantreply ? NULL : "upstream added want_reply flag"); ssh_sharing_queue_global_request(cs->parent->cl, cs); /* diff --git a/ssh/userauth2-client.c b/ssh/userauth2-client.c index de71af7a..a7cb803f 100644 --- a/ssh/userauth2-client.c +++ b/ssh/userauth2-client.c @@ -1208,15 +1208,15 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) * When acquire_cred yields no useful expiration, go with * the service ticket expiration. */ - s->gss_stat = s->shgss->lib->init_sec_context - (s->shgss->lib, - &s->shgss->ctx, - s->shgss->srv_name, - s->gssapi_fwd, - &s->gss_rcvtok, - &s->gss_sndtok, - NULL, - NULL); + s->gss_stat = s->shgss->lib->init_sec_context( + s->shgss->lib, + &s->shgss->ctx, + s->shgss->srv_name, + s->gssapi_fwd, + &s->gss_rcvtok, + &s->gss_sndtok, + NULL, + NULL); if (s->gss_stat!=SSH_GSS_S_COMPLETE && s->gss_stat!=SSH_GSS_S_CONTINUE_NEEDED) { diff --git a/unix/askpass.c b/unix/askpass.c index f2cbafac..b45a1c23 100644 --- a/unix/askpass.c +++ b/unix/askpass.c @@ -292,8 +292,8 @@ static gboolean try_grab_keyboard(gpointer vctx) if (!GDK_IS_WINDOW(gdkw) || !gdk_window_is_visible(gdkw)) goto fail; - seat = gdk_display_get_default_seat - (gtk_widget_get_display(ctx->dialog)); + seat = gdk_display_get_default_seat( + gtk_widget_get_display(ctx->dialog)); if (!seat) goto fail; @@ -317,8 +317,8 @@ static gboolean try_grab_keyboard(gpointer vctx) GdkDeviceManager *dm; GdkDevice *pointer, *keyboard; - dm = gdk_display_get_device_manager - (gtk_widget_get_display(ctx->dialog)); + dm = gdk_display_get_device_manager( + gtk_widget_get_display(ctx->dialog)); if (!dm) goto fail; diff --git a/unix/dialog.c b/unix/dialog.c index 602991f1..0d9d9573 100644 --- a/unix/dialog.c +++ b/unix/dialog.c @@ -345,8 +345,8 @@ char *dlg_editbox_get(dlgcontrol *ctrl, dlgparam *dp) #if GTK_CHECK_VERSION(2,4,0) if (uc->combo) { - return dupstr(gtk_entry_get_text - (GTK_ENTRY(gtk_bin_get_child(GTK_BIN(uc->combo))))); + return dupstr(gtk_entry_get_text( + GTK_ENTRY(gtk_bin_get_child(GTK_BIN(uc->combo))))); } #endif @@ -425,9 +425,9 @@ void dlg_listbox_del(dlgcontrol *ctrl, dlgparam *dp, int index) #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)); + gtk_container_remove( + GTK_CONTAINER(uc->menu), + g_list_nth_data(GTK_MENU_SHELL(uc->menu)->children, index)); return; } if (uc->list) { @@ -1003,8 +1003,8 @@ void dlg_set_focus(dlgcontrol *ctrl, dlgparam *dp) * focus it. */ for (int i = 0; i < ctrl->radio.nbuttons; i++) - if (gtk_toggle_button_get_active - (GTK_TOGGLE_BUTTON(uc->buttons[i]))) { + if (gtk_toggle_button_get_active( + GTK_TOGGLE_BUTTON(uc->buttons[i]))) { gtk_widget_grab_focus(uc->buttons[i]); } break; @@ -1137,8 +1137,8 @@ void dlg_coloursel_start(dlgcontrol *ctrl, dlgparam *dp, int r, int g, int b) GtkWidget *coloursel = gtk_color_selection_dialog_new("Select a colour"); GtkColorSelectionDialog *ccs = GTK_COLOR_SELECTION_DIALOG(coloursel); - GtkColorSelection *cs = GTK_COLOR_SELECTION - (gtk_color_selection_dialog_get_color_selection(ccs)); + GtkColorSelection *cs = GTK_COLOR_SELECTION( + gtk_color_selection_dialog_get_color_selection(ccs)); gtk_color_selection_set_has_opacity_control(cs, false); #endif @@ -1385,10 +1385,10 @@ static gboolean listitem_key(GtkWidget *item, GdkEventKey *event, 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); + 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); @@ -1620,8 +1620,8 @@ static void filesel_ok(GtkButton *button, gpointer data) struct dlgparam *dp = (struct dlgparam *)data; gpointer filesel = g_object_get_data(G_OBJECT(button), "user-data"); struct uctrl *uc = g_object_get_data(G_OBJECT(filesel), "user-data"); - const char *name = gtk_file_selection_get_filename - (GTK_FILE_SELECTION(filesel)); + const char *name = gtk_file_selection_get_filename( + GTK_FILE_SELECTION(filesel)); filechoose_emit_value(dp, uc, name); } #endif @@ -1634,14 +1634,14 @@ static void fontsel_ok(GtkButton *button, gpointer data) gpointer fontsel = g_object_get_data(G_OBJECT(button), "user-data"); struct uctrl *uc = g_object_get_data(G_OBJECT(fontsel), "user-data"); - const char *name = gtk_font_selection_dialog_get_font_name - (GTK_FONT_SELECTION_DIALOG(fontsel)); + 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 *)g_object_get_data - (G_OBJECT(button), "user-data"); + unifontsel *fontsel = (unifontsel *)g_object_get_data( + G_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 */ @@ -1685,9 +1685,9 @@ static void coloursel_ok(GtkButton *button, gpointer data) #if GTK_CHECK_VERSION(2,0,0) { - GtkColorSelection *cs = GTK_COLOR_SELECTION - (gtk_color_selection_dialog_get_color_selection - (GTK_COLOR_SELECTION_DIALOG(coloursel))); + GtkColorSelection *cs = GTK_COLOR_SELECTION( + gtk_color_selection_dialog_get_color_selection( + GTK_COLOR_SELECTION_DIALOG(coloursel))); GdkColor col; gtk_color_selection_get_current_color(cs, &col); dp->coloursel_result.r = col.red / 0x0100; @@ -1696,9 +1696,9 @@ static void coloursel_ok(GtkButton *button, gpointer data) } #else { - GtkColorSelection *cs = GTK_COLOR_SELECTION - (gtk_color_selection_dialog_get_color_selection - (GTK_COLOR_SELECTION_DIALOG(coloursel))); + GtkColorSelection *cs = GTK_COLOR_SELECTION( + gtk_color_selection_dialog_get_color_selection( + GTK_COLOR_SELECTION_DIALOG(coloursel))); gdouble cvals[4]; gtk_color_selection_get_color(cs, cvals); dp->coloursel_result.r = (int) (255 * cvals[0]); @@ -1728,14 +1728,14 @@ static void filefont_clicked(GtkButton *button, gpointer data) if (uc->ctrl->type == CTRL_FILESELECT) { #ifdef USE_GTK_FILE_CHOOSER_DIALOG - GtkWidget *filechoose = gtk_file_chooser_dialog_new - (uc->ctrl->fileselect.title, GTK_WINDOW(dp->window), - (uc->ctrl->fileselect.for_writing ? - GTK_FILE_CHOOSER_ACTION_SAVE : - GTK_FILE_CHOOSER_ACTION_OPEN), - STANDARD_CANCEL_LABEL, GTK_RESPONSE_CANCEL, - STANDARD_OPEN_LABEL, GTK_RESPONSE_ACCEPT, - (const gchar *)NULL); + GtkWidget *filechoose = gtk_file_chooser_dialog_new( + uc->ctrl->fileselect.title, GTK_WINDOW(dp->window), + (uc->ctrl->fileselect.for_writing ? + GTK_FILE_CHOOSER_ACTION_SAVE : + GTK_FILE_CHOOSER_ACTION_OPEN), + STANDARD_CANCEL_LABEL, GTK_RESPONSE_CANCEL, + STANDARD_OPEN_LABEL, GTK_RESPONSE_ACCEPT, + (const gchar *)NULL); gtk_window_set_modal(GTK_WINDOW(filechoose), true); g_object_set_data(G_OBJECT(filechoose), "user-data", (gpointer)uc); g_signal_connect(G_OBJECT(filechoose), "response", @@ -1745,19 +1745,19 @@ static void filefont_clicked(GtkButton *button, gpointer data) GtkWidget *filesel = gtk_file_selection_new(uc->ctrl->fileselect.title); gtk_window_set_modal(GTK_WINDOW(filesel), true); - g_object_set_data - (G_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "user-data", - (gpointer)filesel); + g_object_set_data( + G_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "user-data", + (gpointer)filesel); g_object_set_data(G_OBJECT(filesel), "user-data", (gpointer)uc); - g_signal_connect - (G_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "clicked", - G_CALLBACK(filesel_ok), (gpointer)dp); - g_signal_connect_swapped - (G_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "clicked", - G_CALLBACK(gtk_widget_destroy), (gpointer)filesel); - g_signal_connect_swapped - (G_OBJECT(GTK_FILE_SELECTION(filesel)->cancel_button), "clicked", - G_CALLBACK(gtk_widget_destroy), (gpointer)filesel); + g_signal_connect( + G_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "clicked", + G_CALLBACK(filesel_ok), (gpointer)dp); + g_signal_connect_swapped( + G_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "clicked", + G_CALLBACK(gtk_widget_destroy), (gpointer)filesel); + g_signal_connect_swapped( + G_OBJECT(GTK_FILE_SELECTION(filesel)->cancel_button), "clicked", + G_CALLBACK(gtk_widget_destroy), (gpointer)filesel); gtk_widget_show(filesel); #endif } @@ -1775,12 +1775,12 @@ static void filefont_clicked(GtkButton *button, gpointer data) 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)) { + 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 @@ -1800,27 +1800,27 @@ static void filefont_clicked(GtkButton *button, gpointer data) 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); + gtk_font_selection_dialog_set_font_name( + GTK_FONT_SELECTION_DIALOG(fontsel), name); } gdk_font_unref(font); } } - g_object_set_data - (G_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->ok_button), - "user-data", (gpointer)fontsel); + g_object_set_data( + G_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->ok_button), + "user-data", (gpointer)fontsel); g_object_set_data(G_OBJECT(fontsel), "user-data", (gpointer)uc); - g_signal_connect - (G_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->ok_button), - "clicked", G_CALLBACK(fontsel_ok), (gpointer)dp); - g_signal_connect_swapped - (G_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->ok_button), - "clicked", G_CALLBACK(gtk_widget_destroy), - (gpointer)fontsel); - g_signal_connect_swapped - (G_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->cancel_button), - "clicked", G_CALLBACK(gtk_widget_destroy), - (gpointer)fontsel); + g_signal_connect( + G_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->ok_button), + "clicked", G_CALLBACK(fontsel_ok), (gpointer)dp); + g_signal_connect_swapped( + G_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->ok_button), + "clicked", G_CALLBACK(gtk_widget_destroy), + (gpointer)fontsel); + g_signal_connect_swapped( + G_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->cancel_button), + "clicked", G_CALLBACK(gtk_widget_destroy), + (gpointer)fontsel); gtk_widget_show(fontsel); #else /* !GTK_CHECK_VERSION(2,0,0) */ @@ -2018,8 +2018,8 @@ GtkWidget *layout_ctrls( GtkWidget *b; gint colstart; - b = (gtk_radio_button_new_with_label - (group, ctrl->radio.buttons[i])); + b = gtk_radio_button_new_with_label( + group, ctrl->radio.buttons[i]); uc->buttons[i] = b; group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(b)); colstart = i % ctrl->radio.ncolumns; @@ -2059,8 +2059,8 @@ GtkWidget *layout_ctrls( */ uc->listmodel = gtk_list_store_new(2, G_TYPE_INT, G_TYPE_STRING); - w = gtk_combo_box_new_with_model_and_entry - (GTK_TREE_MODEL(uc->listmodel)); + w = gtk_combo_box_new_with_model_and_entry( + GTK_TREE_MODEL(uc->listmodel)); g_object_set(G_OBJECT(w), "entry-text-column", 1, (const char *)NULL); /* We cannot support password combo boxes. */ @@ -2261,8 +2261,8 @@ GtkWidget *layout_ctrls( * Create a non-editable GtkComboBox (that is, not * its subclass GtkComboBoxEntry). */ - w = gtk_combo_box_new_with_model - (GTK_TREE_MODEL(uc->listmodel)); + w = gtk_combo_box_new_with_model( + GTK_TREE_MODEL(uc->listmodel)); uc->combo = w; /* @@ -2306,8 +2306,8 @@ GtkWidget *layout_ctrls( 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)); + uc->adj = gtk_scrolled_window_get_vadjustment( + GTK_SCROLLED_WINDOW(w)); gtk_widget_show(uc->list); g_signal_connect(G_OBJECT(uc->list), "selection-changed", @@ -2377,15 +2377,15 @@ GtkWidget *layout_ctrls( * Create the list box itself, its columns, and * its containing scrolled window. */ - w = gtk_tree_view_new_with_model - (GTK_TREE_MODEL(uc->listmodel)); + 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); + gtk_tree_selection_set_mode( + sel, ctrl->listbox.multisel ? GTK_SELECTION_MULTIPLE : + GTK_SELECTION_SINGLE); uc->treeview = w; g_signal_connect(G_OBJECT(w), "row-activated", G_CALLBACK(listbox_doubleclick), dp); @@ -2422,10 +2422,10 @@ GtkWidget *layout_ctrls( "ellipsize-set", true, (const char *)NULL); } - column = gtk_tree_view_column_new_with_attributes - ("heading", cellrend, "text", i+1, (char *)NULL); - gtk_tree_view_column_set_sizing - (column, GTK_TREE_VIEW_COLUMN_GROW_ONLY); + column = gtk_tree_view_column_new_with_attributes( + "heading", cellrend, "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); } } @@ -2434,16 +2434,16 @@ GtkWidget *layout_ctrls( GtkWidget *scroll; scroll = gtk_scrolled_window_new(NULL, NULL); - gtk_scrolled_window_set_shadow_type - (GTK_SCROLLED_WINDOW(scroll), GTK_SHADOW_IN); + 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)); + gtk_widget_set_size_request( + scroll, -1, + ctrl->listbox.height * get_listitemheight(w)); w = scroll; } @@ -2737,8 +2737,8 @@ gint win_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data) 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]))) { + 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) { @@ -2746,8 +2746,8 @@ gint win_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data) 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]); - g_signal_emit_by_name - (G_OBJECT(sc->uc->buttons[i]), "clicked"); + g_signal_emit_by_name( + G_OBJECT(sc->uc->buttons[i]), "clicked"); } } break; @@ -3036,13 +3036,13 @@ GtkWidget *create_config_box(const char *title, Conf *conf, 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); + 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); + 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); @@ -3170,9 +3170,8 @@ GtkWidget *create_config_box(const char *title, Conf *conf, 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])); + gtk_tree_item_set_subtree( + treeitemlevels[j-1], GTK_WIDGET(treelevels[j-1])); if (j < 2) gtk_tree_item_expand(treeitemlevels[j-1]); else @@ -3910,10 +3909,10 @@ void about_box(void *window) { char *buildinfo_text = buildinfo("\n"); - char *label_text = dupprintf - ("%s\n\n%s\n\n%s\n\n%s", - appname, ver, buildinfo_text, - "Copyright " SHORT_COPYRIGHT_DETAILS ". All rights reserved"); + char *label_text = dupprintf( + "%s\n\n%s\n\n%s\n\n%s", + appname, ver, buildinfo_text, + "Copyright " SHORT_COPYRIGHT_DETAILS ". All rights reserved"); w = gtk_label_new(label_text); gtk_label_set_justify(GTK_LABEL(w), GTK_JUSTIFY_CENTER); #if GTK_CHECK_VERSION(2,0,0) @@ -4055,8 +4054,8 @@ gint eventlog_selection_clear(GtkWidget *widget, GdkEventSelection *seldata, 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))); + gtk_tree_selection_unselect_all( + gtk_tree_view_get_selection(GTK_TREE_VIEW(uc->treeview))); #endif es->ignore_selchange = false; @@ -4112,10 +4111,10 @@ void showeventlog(eventlog_stuff *es, void *parentwin) gtk_widget_show(w0); w1 = layout_ctrls(&es->dp, NULL, &es->scs, s1, GTK_WINDOW(window)); gtk_container_set_border_width(GTK_CONTAINER(w1), 10); - gtk_widget_set_size_request(w1, 20 + string_width - ("LINE OF TEXT GIVING WIDTH OF EVENT LOG IS " - "QUITE LONG 'COS SSH LOG ENTRIES ARE WIDE"), - -1); + gtk_widget_set_size_request( + w1, 20 + string_width("LINE OF TEXT GIVING WIDTH OF EVENT LOG IS " + "QUITE LONG 'COS SSH LOG ENTRIES ARE WIDE"), + -1); our_dialog_add_to_content_area(GTK_WINDOW(window), w1, true, true, 0); { struct uctrl *uc = dlg_find_byctrl(&es->dp, es->listctrl); diff --git a/unix/unifont.c b/unix/unifont.c index 144ec87a..e9f8623a 100644 --- a/unix/unifont.c +++ b/unix/unifont.c @@ -574,8 +574,8 @@ static void x11font_destroy(unifont *font) static void x11_alloc_subfont(struct x11font *xfont, int sfid) { Display *disp = xfont->disp; - char *derived_name = x11_guess_derived_font_name - (disp, xfont->fonts[0].xfs, sfid & 1, !!(sfid & 2)); + char *derived_name = x11_guess_derived_font_name( + disp, xfont->fonts[0].xfs, sfid & 1, !!(sfid & 2)); xfont->fonts[sfid].xfs = XLoadQueryFont(disp, derived_name); xfont->fonts[sfid].allocated = true; sfree(derived_name); @@ -691,10 +691,9 @@ static void x11font_cairo_setup( xfi->indexflip = (*((char *) &endianness_test) == 1) ? 0 : 7; } - xfi->pixmap = XCreatePixmap - (disp, - GDK_DRAWABLE_XID(gtk_widget_get_window(ctx->u.cairo.widget)), - xfi->pixwidth, xfi->pixheight, 1); + xfi->pixmap = XCreatePixmap( + disp, GDK_DRAWABLE_XID(gtk_widget_get_window(ctx->u.cairo.widget)), + xfi->pixwidth, xfi->pixheight, 1); gcvals.foreground = WhitePixel(disp, widgetscr); gcvals.background = BlackPixel(disp, widgetscr); gcvals.font = xfi->xfs->fid; @@ -747,8 +746,8 @@ static void x11font_cairo_cache_glyph( } } xfi->glyphcache[glyphindex].bitmap = bitmap; - xfi->glyphcache[glyphindex].surface = cairo_image_surface_create_for_data - (bitmap, CAIRO_FORMAT_A1, xfi->pixwidth, xfi->pixheight, xfi->rowsize); + xfi->glyphcache[glyphindex].surface = cairo_image_surface_create_for_data( + bitmap, CAIRO_FORMAT_A1, xfi->pixwidth, xfi->pixheight, xfi->rowsize); } static void x11font_cairo_draw_glyph(unifont_drawctx *ctx, @@ -2226,9 +2225,9 @@ unifont *multifont_create(GtkWidget *widget, const char *name, if (font->want_fallback) { for (i = 0; i < lenof(unifont_types); i++) { if (unifont_types[i]->create_fallback) { - fallback = unifont_types[i]->create_fallback - (widget, font->height, wide, bold, - shadowoffset, shadowalways); + fallback = unifont_types[i]->create_fallback( + widget, font->height, wide, bold, + shadowoffset, shadowalways); if (fallback) break; } @@ -2663,9 +2662,9 @@ static void unifontsel_set_filter_buttons(unifontsel_internal *fs) int i; for (i = 0; i < fs->n_filter_buttons; i++) { - int flagbit = GPOINTER_TO_INT(g_object_get_data - (G_OBJECT(fs->filter_buttons[i]), - "user-data")); + int flagbit = GPOINTER_TO_INT(g_object_get_data( + G_OBJECT(fs->filter_buttons[i]), + "user-data")); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(fs->filter_buttons[i]), !!(fs->filter_flags & flagbit)); } @@ -2679,8 +2678,8 @@ static void unifontsel_draw_preview_text_inner(unifont_drawctx *dctx, fontinfo *info = fs->selected; if (info) { - sizename = info->fontclass->scale_fontname - (GTK_WIDGET(fs->u.window), info->realname, fs->selsize); + 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); @@ -2867,9 +2866,9 @@ static void unifontsel_select_font(unifontsel_internal *fs, */ 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_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); success = gtk_tree_model_get_iter(GTK_TREE_MODEL(fs->family_model), @@ -2891,9 +2890,9 @@ static void unifontsel_select_font(unifontsel_internal *fs, 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_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), @@ -2914,9 +2913,9 @@ static void unifontsel_select_font(unifontsel_internal *fs, 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_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); @@ -3236,8 +3235,8 @@ static void alias_resolve(GtkTreeView *treeview, GtkTreePath *path, int flags; struct fontinfo_realname_find f; - newname = info->fontclass->canonify_fontname - (GTK_WIDGET(fs->u.window), info->realname, &newsize, &flags, true); + newname = info->fontclass->canonify_fontname( + GTK_WIDGET(fs->u.window), info->realname, &newsize, &flags, true); f.realname = newname; f.flags = flags; @@ -3363,10 +3362,10 @@ unifontsel *unifontsel_new(const char *wintitle) 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), STANDARD_CANCEL_LABEL, GTK_RESPONSE_CANCEL); - fs->u.ok_button = gtk_dialog_add_button - (GTK_DIALOG(fs->u.window), STANDARD_OK_LABEL, GTK_RESPONSE_OK); + fs->u.cancel_button = gtk_dialog_add_button( + GTK_DIALOG(fs->u.window), STANDARD_CANCEL_LABEL, GTK_RESPONSE_CANCEL); + fs->u.ok_button = gtk_dialog_add_button( + GTK_DIALOG(fs->u.window), STANDARD_OK_LABEL, GTK_RESPONSE_OK); gtk_widget_grab_default(fs->u.ok_button); /* @@ -3398,8 +3397,8 @@ unifontsel *unifontsel_new(const char *wintitle) w = table; #endif - gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area - (GTK_DIALOG(fs->u.window))), + gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area( + GTK_DIALOG(fs->u.window))), w, true, true, 0); label = gtk_label_new_with_mnemonic("_Font:"); @@ -3422,9 +3421,9 @@ unifontsel *unifontsel_new(const char *wintitle) 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); + 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))), @@ -3473,9 +3472,9 @@ unifontsel *unifontsel_new(const char *wintitle) 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, "weight", 4, (char *)NULL); + column = gtk_tree_view_column_new_with_attributes( + "Style", gtk_cell_renderer_text_new(), + "text", 0, "sensitive", 3, "weight", 4, (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))), @@ -3531,9 +3530,9 @@ unifontsel *unifontsel_new(const char *wintitle) 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); + 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))), @@ -3740,8 +3739,8 @@ void unifontsel_set_name(unifontsel *fontsel, const char *fontname) */ 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); + fontname2 = unifont_types[i]->canonify_fontname( + GTK_WIDGET(fs->u.window), fontname, &size, &flags, false); if (fontname2) break; } @@ -3791,8 +3790,8 @@ char *unifontsel_get_name(unifontsel *fontsel) return NULL; if (fs->selected->size == 0) { - name = fs->selected->fontclass->scale_fontname - (GTK_WIDGET(fs->u.window), fs->selected->realname, fs->selsize); + 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); sfree(name); diff --git a/unix/utils/our_dialog.c b/unix/utils/our_dialog.c index 5b9995d4..0ca8a7d2 100644 --- a/unix/utils/our_dialog.c +++ b/unix/utils/our_dialog.c @@ -128,8 +128,8 @@ void our_dialog_add_to_content_area(GtkWindow *dlg, GtkWidget *w, gtk_box_pack_start(vbox, w, expand, fill, padding); #else - gtk_box_pack_start - (GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dlg))), - w, expand, fill, padding); + gtk_box_pack_start( + GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dlg))), + w, expand, fill, padding); #endif } diff --git a/unix/window.c b/unix/window.c index d35b1106..624ecc48 100644 --- a/unix/window.c +++ b/unix/window.c @@ -1675,10 +1675,11 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) */ guint new_keyval; GdkModifierType consumed; - if (gdk_keymap_translate_keyboard_state - (gdk_keymap_get_for_display(gdk_display_get_default()), - event->hardware_keycode, event->state & ~META_MANUAL_MASK, - 0, &new_keyval, NULL, NULL, &consumed)) { + if (gdk_keymap_translate_keyboard_state( + gdk_keymap_get_for_display(gdk_display_get_default()), + event->hardware_keycode, + event->state & ~META_MANUAL_MASK, + 0, &new_keyval, NULL, NULL, &consumed)) { ucsoutput[0] = '\033'; ucsoutput[1] = gdk_keyval_to_unicode(new_keyval); #ifdef KEY_EVENT_DIAGNOSTICS diff --git a/windows/agent-client.c b/windows/agent-client.c index 1183bff8..168465e5 100644 --- a/windows/agent-client.c +++ b/windows/agent-client.c @@ -62,8 +62,8 @@ static void wm_copydata_agent_query(strbuf *query, void **out, int *outlen) psd = (PSECURITY_DESCRIPTOR) LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH); if (psd) { - if (p_InitializeSecurityDescriptor - (psd, SECURITY_DESCRIPTOR_REVISION) && + if (p_InitializeSecurityDescriptor( + psd, SECURITY_DESCRIPTOR_REVISION) && p_SetSecurityDescriptorOwner(psd, usersid, false)) { sa.nLength = sizeof(sa); sa.bInheritHandle = true; diff --git a/windows/dialog.c b/windows/dialog.c index 48e188a6..98540201 100644 --- a/windows/dialog.c +++ b/windows/dialog.c @@ -385,10 +385,10 @@ static INT_PTR CALLBACK AboutProc(HWND hwnd, UINT msg, SetWindowText(hwnd, str); sfree(str); char *buildinfo_text = buildinfo("\r\n"); - char *text = dupprintf - ("%s\r\n\r\n%s\r\n\r\n%s\r\n\r\n%s", - appname, ver, buildinfo_text, - "\251 " SHORT_COPYRIGHT_DETAILS ". All rights reserved."); + char *text = dupprintf( + "%s\r\n\r\n%s\r\n\r\n%s\r\n\r\n%s", + appname, ver, buildinfo_text, + "\251 " SHORT_COPYRIGHT_DETAILS ". All rights reserved."); sfree(buildinfo_text); SetDlgItemText(hwnd, IDA_TEXT, text); MakeDlgItemBorderless(hwnd, IDA_TEXT); diff --git a/windows/jump-list.c b/windows/jump-list.c index dd3143f9..66df3510 100644 --- a/windows/jump-list.c +++ b/windows/jump-list.c @@ -544,13 +544,13 @@ static void update_jumplist_from_registry(void) */ for (i = 0, found = false; i < nremoved && !found; i++) { IShellLink *rlink; - if (SUCCEEDED(pRemoved->lpVtbl->GetAt - (pRemoved, i, COMPTR(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)) && + if (SUCCEEDED(link->lpVtbl->GetDescription( + link, desc1, sizeof(desc1)-1)) && + SUCCEEDED(rlink->lpVtbl->GetDescription( + rlink, desc2, sizeof(desc2)-1)) && !strcmp(desc1, desc2)) { found = true; } @@ -575,8 +575,8 @@ static void update_jumplist_from_registry(void) * 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)))) + if (!SUCCEEDED(collection->lpVtbl->QueryInterface( + collection, COMPTR(IObjectArray, &array)))) goto cleanup; pCDL->lpVtbl->AppendCategory(pCDL, L"Recent Sessions", array); @@ -608,8 +608,8 @@ static void update_jumplist_from_registry(void) * 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)))) + if (!SUCCEEDED(collection->lpVtbl->QueryInterface( + collection, COMPTR(IObjectArray, &array)))) goto cleanup; pCDL->lpVtbl->AddUserTasks(pCDL, array); @@ -636,8 +636,8 @@ static void update_jumplist_from_registry(void) * 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)))) + if (!SUCCEEDED(collection->lpVtbl->QueryInterface( + collection, COMPTR(IObjectArray, &array)))) goto cleanup; pCDL->lpVtbl->AddUserTasks(pCDL, array); diff --git a/windows/named-pipe-server.c b/windows/named-pipe-server.c index d6d8d1c8..87adb940 100644 --- a/windows/named-pipe-server.c +++ b/windows/named-pipe-server.c @@ -77,33 +77,33 @@ static bool create_named_pipe(NamedPipeServerSocket *ps, bool first_instance) sa.lpSecurityDescriptor = ps->psd; sa.bInheritHandle = false; - ps->pipehandle = CreateNamedPipe - (/* lpName */ - ps->pipename, + ps->pipehandle = CreateNamedPipe( + /* lpName */ + ps->pipename, - /* dwOpenMode */ - PIPE_ACCESS_DUPLEX | - FILE_FLAG_OVERLAPPED | - (first_instance ? FILE_FLAG_FIRST_PIPE_INSTANCE : 0), + /* dwOpenMode */ + PIPE_ACCESS_DUPLEX | + FILE_FLAG_OVERLAPPED | + (first_instance ? FILE_FLAG_FIRST_PIPE_INSTANCE : 0), - /* dwPipeMode */ - PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT + /* dwPipeMode */ + PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT #ifdef PIPE_REJECT_REMOTE_CLIENTS - | PIPE_REJECT_REMOTE_CLIENTS + | PIPE_REJECT_REMOTE_CLIENTS #endif - , + , - /* nMaxInstances */ - PIPE_UNLIMITED_INSTANCES, + /* nMaxInstances */ + PIPE_UNLIMITED_INSTANCES, - /* nOutBufferSize, nInBufferSize */ - 4096, 4096, /* FIXME: think harder about buffer sizes? */ + /* nOutBufferSize, nInBufferSize */ + 4096, 4096, /* FIXME: think harder about buffer sizes? */ - /* nDefaultTimeOut */ - 0 /* default timeout */, + /* nDefaultTimeOut */ + 0 /* default timeout */, - /* lpSecurityAttributes */ - &sa); + /* lpSecurityAttributes */ + &sa); return ps->pipehandle != INVALID_HANDLE_VALUE; } diff --git a/windows/pageant.c b/windows/pageant.c index 077a6812..fb2a1fec 100644 --- a/windows/pageant.c +++ b/windows/pageant.c @@ -133,10 +133,10 @@ static INT_PTR CALLBACK AboutProc(HWND hwnd, UINT msg, switch (msg) { case WM_INITDIALOG: { char *buildinfo_text = buildinfo("\r\n"); - char *text = dupprintf - ("Pageant\r\n\r\n%s\r\n\r\n%s\r\n\r\n%s", - ver, buildinfo_text, - "\251 " SHORT_COPYRIGHT_DETAILS ". All rights reserved."); + char *text = dupprintf( + "Pageant\r\n\r\n%s\r\n\r\n%s\r\n\r\n%s", + ver, buildinfo_text, + "\251 " SHORT_COPYRIGHT_DETAILS ". All rights reserved."); sfree(buildinfo_text); SetDlgItemText(hwnd, IDC_ABOUT_TEXTBOX, text); MakeDlgItemBorderless(hwnd, IDC_ABOUT_TEXTBOX); diff --git a/windows/puttygen.c b/windows/puttygen.c index 74d5a7c2..52b1a9cb 100644 --- a/windows/puttygen.c +++ b/windows/puttygen.c @@ -543,10 +543,10 @@ static INT_PTR CALLBACK AboutProc(HWND hwnd, UINT msg, { char *buildinfo_text = buildinfo("\r\n"); - char *text = dupprintf - ("PuTTYgen\r\n\r\n%s\r\n\r\n%s\r\n\r\n%s", - ver, buildinfo_text, - "\251 " SHORT_COPYRIGHT_DETAILS ". All rights reserved."); + char *text = dupprintf( + "PuTTYgen\r\n\r\n%s\r\n\r\n%s\r\n\r\n%s", + ver, buildinfo_text, + "\251 " SHORT_COPYRIGHT_DETAILS ". All rights reserved."); sfree(buildinfo_text); SetDlgItemText(hwnd, 1000, text); MakeDlgItemBorderless(hwnd, 1000); @@ -1922,10 +1922,10 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, if ((state->keytype == RSA || state->keytype == DSA) && state->key_bits < 256) { - char *message = dupprintf - ("PuTTYgen will not generate a key smaller than 256" - " bits.\nKey length reset to default %d. Continue?", - DEFAULT_KEY_BITS); + char *message = dupprintf( + "PuTTYgen will not generate a key smaller than 256" + " bits.\nKey length reset to default %d. Continue?", + DEFAULT_KEY_BITS); int ret = MessageBox(hwnd, message, "PuTTYgen Warning", MB_ICONWARNING | MB_OKCANCEL); sfree(message); @@ -1935,9 +1935,9 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, SetDlgItemInt(hwnd, IDC_BITS, DEFAULT_KEY_BITS, false); } else if ((state->keytype == RSA || state->keytype == DSA) && state->key_bits < DEFAULT_KEY_BITS) { - char *message = dupprintf - ("Keys shorter than %d bits are not recommended. " - "Really generate this key?", DEFAULT_KEY_BITS); + char *message = dupprintf( + "Keys shorter than %d bits are not recommended. " + "Really generate this key?", DEFAULT_KEY_BITS); int ret = MessageBox(hwnd, message, "PuTTYgen Warning", MB_ICONWARNING | MB_OKCANCEL); sfree(message); diff --git a/windows/storage.c b/windows/storage.c index 7ac299a9..f2c33feb 100644 --- a/windows/storage.c +++ b/windows/storage.c @@ -685,8 +685,8 @@ void write_random_seed(void *data, int len) * 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) +static int transform_jumplist_registry( + const char *add, const char *rem, char **out) { HKEY rkey = open_regkey(true, HKEY_CURRENT_USER, reg_jumplist_key); if (!rkey) diff --git a/windows/utils/security.c b/windows/utils/security.c index 48330437..52299420 100644 --- a/windows/utils/security.c +++ b/windows/utils/security.c @@ -290,10 +290,10 @@ static bool really_restrict_process_acl(char **error) goto cleanup; } - if (ERROR_SUCCESS != p_SetSecurityInfo - (GetCurrentProcess(), SE_KERNEL_OBJECT, - OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION, - usersid, NULL, acl, NULL)) { + if (ERROR_SUCCESS != p_SetSecurityInfo( + GetCurrentProcess(), SE_KERNEL_OBJECT, + OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION, + usersid, NULL, acl, NULL)) { *error = dupprintf("Unable to set process ACL: %s", win_strerror(GetLastError())); goto cleanup; -- cgit v1.2.3 From 4b8dc562844a4ea0fdfda53a05a275c50a23d94a Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Wed, 3 Aug 2022 20:48:46 +0100 Subject: Formatting: remove spurious spaces in 'type * var'. I think a lot of these were inserted by a prior run through GNU indent many years ago. I noticed in a more recent experiment that that tool doesn't always correctly distinguish which instances of 'id * id' are pointer variable declarations and which are multiplications, so it spaces some of the former as if they were the latter. --- crypto/blowfish.c | 18 +++++++++--------- proxy/proxy.c | 2 +- putty.h | 4 ++-- ssh/pgssapi.h | 6 +++--- ssh/zlib.c | 4 ++-- terminal/terminal.c | 2 +- tree234.h | 22 +++++++++++----------- utils/tree234.c | 32 ++++++++++++++++---------------- windows/network.c | 4 ++-- windows/unicode.c | 6 +++--- windows/window.c | 4 ++-- 11 files changed, 52 insertions(+), 52 deletions(-) diff --git a/crypto/blowfish.c b/crypto/blowfish.c index f86cb9a1..07b989ed 100644 --- a/crypto/blowfish.c +++ b/crypto/blowfish.c @@ -234,7 +234,7 @@ static const uint32_t sbox3[] = { #define ROUND(n) ( xL ^= P[n], t = xL, xL = F(xL) ^ xR, xR = t ) static void blowfish_encrypt(uint32_t xL, uint32_t xR, uint32_t *output, - BlowfishContext * ctx) + BlowfishContext *ctx) { uint32_t *S0 = ctx->S0; uint32_t *S1 = ctx->S1; @@ -267,7 +267,7 @@ static void blowfish_encrypt(uint32_t xL, uint32_t xR, uint32_t *output, } static void blowfish_decrypt(uint32_t xL, uint32_t xR, uint32_t *output, - BlowfishContext * ctx) + BlowfishContext *ctx) { uint32_t *S0 = ctx->S0; uint32_t *S1 = ctx->S1; @@ -300,7 +300,7 @@ static void blowfish_decrypt(uint32_t xL, uint32_t xR, uint32_t *output, } static void blowfish_lsb_encrypt_cbc(unsigned char *blk, int len, - BlowfishContext * ctx) + BlowfishContext *ctx) { uint32_t xL, xR, out[2], iv0, iv1; @@ -327,7 +327,7 @@ static void blowfish_lsb_encrypt_cbc(unsigned char *blk, int len, ctx->iv1 = iv1; } -void blowfish_lsb_encrypt_ecb(void *vblk, int len, BlowfishContext * ctx) +void blowfish_lsb_encrypt_ecb(void *vblk, int len, BlowfishContext *ctx) { unsigned char *blk = (unsigned char *)vblk; uint32_t xL, xR, out[2]; @@ -346,7 +346,7 @@ void blowfish_lsb_encrypt_ecb(void *vblk, int len, BlowfishContext * ctx) } static void blowfish_lsb_decrypt_cbc(unsigned char *blk, int len, - BlowfishContext * ctx) + BlowfishContext *ctx) { uint32_t xL, xR, out[2], iv0, iv1; @@ -374,7 +374,7 @@ static void blowfish_lsb_decrypt_cbc(unsigned char *blk, int len, } static void blowfish_msb_encrypt_cbc(unsigned char *blk, int len, - BlowfishContext * ctx) + BlowfishContext *ctx) { uint32_t xL, xR, out[2], iv0, iv1; @@ -402,7 +402,7 @@ static void blowfish_msb_encrypt_cbc(unsigned char *blk, int len, } static void blowfish_msb_decrypt_cbc(unsigned char *blk, int len, - BlowfishContext * ctx) + BlowfishContext *ctx) { uint32_t xL, xR, out[2], iv0, iv1; @@ -430,7 +430,7 @@ static void blowfish_msb_decrypt_cbc(unsigned char *blk, int len, } static void blowfish_msb_sdctr(unsigned char *blk, int len, - BlowfishContext * ctx) + BlowfishContext *ctx) { uint32_t b[2], iv0, iv1, tmp; @@ -471,7 +471,7 @@ void blowfish_initkey(BlowfishContext *ctx) } } -void blowfish_expandkey(BlowfishContext * ctx, +void blowfish_expandkey(BlowfishContext *ctx, const void *vkey, short keybytes, const void *vsalt, short saltbytes) { diff --git a/proxy/proxy.c b/proxy/proxy.c index b1731c87..056dcf19 100644 --- a/proxy/proxy.c +++ b/proxy/proxy.c @@ -178,7 +178,7 @@ static void sk_proxy_set_frozen (Socket *s, bool is_frozen) sk_set_frozen(ps->sub_socket, is_frozen); } -static const char * sk_proxy_socket_error (Socket *s) +static const char *sk_proxy_socket_error (Socket *s) { ProxySocket *ps = container_of(s, ProxySocket, sock); if (ps->error != NULL || ps->sub_socket == NULL) { diff --git a/putty.h b/putty.h index c5d73e9d..727a07f7 100644 --- a/putty.h +++ b/putty.h @@ -2209,7 +2209,7 @@ char *term_get_ttymode(Terminal *term, const char *mode); SeatPromptResult term_get_userpass_input(Terminal *term, prompts_t *p); void term_set_trust_status(Terminal *term, bool trusted); void term_keyinput(Terminal *, int codepage, const void *buf, int len); -void term_keyinputw(Terminal *, const wchar_t * widebuf, int len); +void term_keyinputw(Terminal *, const wchar_t *widebuf, int len); void term_get_cursor_position(Terminal *term, int *x, int *y); void term_setup_window_titles(Terminal *term, const char *title_hostname); void term_notify_minimised(Terminal *term, bool minimised); @@ -2478,7 +2478,7 @@ int check_compose(int first, int second); int decode_codepage(const 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); +void get_unitab(int codepage, wchar_t *unitab, int ftype); /* * Exports from wcwidth.c diff --git a/ssh/pgssapi.h b/ssh/pgssapi.h index 53d8cb61..3f21e0ff 100644 --- a/ssh/pgssapi.h +++ b/ssh/pgssapi.h @@ -53,9 +53,9 @@ typedef struct gss_channel_bindings_struct { 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 void *gss_ctx_id_t; +typedef void *gss_name_t; +typedef void *gss_cred_id_t; typedef OM_uint32 gss_qop_t; typedef int gss_cred_usage_t; diff --git a/ssh/zlib.c b/ssh/zlib.c index 09b4afde..e64d37a9 100644 --- a/ssh/zlib.c +++ b/ssh/zlib.c @@ -54,8 +54,8 @@ 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); + void (*literal) (struct LZ77Context *ctx, unsigned char c); + void (*match) (struct LZ77Context *ctx, int distance, int len); }; /* diff --git a/terminal/terminal.c b/terminal/terminal.c index 5310c650..d4d0bde3 100644 --- a/terminal/terminal.c +++ b/terminal/terminal.c @@ -2341,7 +2341,7 @@ void term_provide_backend(Terminal *term, Backend *backend) * 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) +static int find_last_nonempty_line(Terminal *term, tree234 *screen) { int i; for (i = count234(screen) - 1; i >= 0; i--) { diff --git a/tree234.h b/tree234.h index 0f2012d0..c7e3f641 100644 --- a/tree234.h +++ b/tree234.h @@ -45,13 +45,13 @@ tree234 *newtree234(cmpfn234 cmp); /* * Free a 2-3-4 tree (not including freeing the elements). */ -void freetree234(tree234 * t); +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); +void *add234(tree234 *t, void *e); /* * Add an element e to an unsorted 2-3-4 tree t. Returns e on @@ -61,7 +61,7 @@ void *add234(tree234 * t, void *e); * Index range can be from 0 to the tree's current element count, * inclusive. */ -void *addpos234(tree234 * t, void *e, int index); +void *addpos234(tree234 *t, void *e, int index); /* * Look up the element at a given numeric index in a 2-3-4 tree. @@ -81,7 +81,7 @@ void *addpos234(tree234 * t, void *e, int index); * consume(p); * } */ -void *index234(tree234 * t, int index); +void *index234(tree234 *t, int index); /* * Find an element e in a sorted 2-3-4 tree t. Returns NULL if not @@ -126,10 +126,10 @@ void *index234(tree234 * t, int index); 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, +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); /* @@ -184,12 +184,12 @@ void search234_step(search234_state *state, int direction); * 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); +void *del234(tree234 *t, void *e); +void *delpos234(tree234 *t, int index); /* * Return the total element count of a tree234. */ -int count234(tree234 * t); +int count234(tree234 *t); #endif /* TREE234_H */ diff --git a/utils/tree234.c b/utils/tree234.c index 83683f13..6440f871 100644 --- a/utils/tree234.c +++ b/utils/tree234.c @@ -73,7 +73,7 @@ tree234 *newtree234(cmpfn234 cmp) /* * Free a 2-3-4 tree (not including freeing the elements). */ -static void freenode234(node234 * n) +static void freenode234(node234 *n) { if (!n) return; @@ -84,7 +84,7 @@ static void freenode234(node234 * n) sfree(n); } -void freetree234(tree234 * t) +void freetree234(tree234 *t) { freenode234(t->root); sfree(t); @@ -93,7 +93,7 @@ void freetree234(tree234 * t) /* * Internal function to count a node. */ -static int countnode234(node234 * n) +static int countnode234(node234 *n) { int count = 0; int i; @@ -122,7 +122,7 @@ static int elements234(node234 *n) /* * Count the elements in a tree. */ -int count234(tree234 * t) +int count234(tree234 *t) { if (t->root) return countnode234(t->root); @@ -134,7 +134,7 @@ int count234(tree234 * t) * 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) +static void *add234_internal(tree234 *t, void *e, int index) { node234 *n, **np, *left, *right; void *orig_e = e; @@ -463,14 +463,14 @@ static void *add234_internal(tree234 * t, void *e, int index) return orig_e; } -void *add234(tree234 * t, void *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) +void *addpos234(tree234 *t, void *e, int index) { if (index < 0 || /* index out of range */ t->cmp) /* tree is sorted */ @@ -483,7 +483,7 @@ 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. */ -void *index234(tree234 * t, int index) +void *index234(tree234 *t, int index) { node234 *n; @@ -523,7 +523,7 @@ void *index234(tree234 * t, int index) * as NULL, in which case the compare function from the tree proper * will be used. */ -void *findrelpos234(tree234 * t, void *e, cmpfn234 cmp, +void *findrelpos234(tree234 *t, void *e, cmpfn234 cmp, int relation, int *index) { search234_state ss; @@ -598,15 +598,15 @@ void *findrelpos234(tree234 * t, void *e, cmpfn234 cmp, *index = ss.index; return toret; } -void *find234(tree234 * t, void *e, cmpfn234 cmp) +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) +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) +void *findpos234(tree234 *t, void *e, cmpfn234 cmp, int *index) { return findrelpos234(t, e, cmp, REL234_EQ, index); } @@ -686,7 +686,7 @@ void search234_step(search234_state *state, int direction) * 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) +static void *delpos234_internal(tree234 *t, int index) { node234 *n; void *retval; @@ -1024,13 +1024,13 @@ static void *delpos234_internal(tree234 * t, int index) } } } -void *delpos234(tree234 * t, int index) +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) +void *del234(tree234 *t, void *e) { int index; if (!findrelpos234(t, e, NULL, REL234_EQ, &index)) @@ -1095,7 +1095,7 @@ typedef struct { int elemcount; } chkctx; -int chknode(chkctx * ctx, int level, node234 * node, +int chknode(chkctx *ctx, int level, node234 *node, void *lowbound, void *highbound) { int nkids, nelems; diff --git a/windows/network.c b/windows/network.c index d0ad14a9..5dd728cd 100644 --- a/windows/network.c +++ b/windows/network.c @@ -220,8 +220,8 @@ DECL_WINDOWS_FUNCTION(static, int, getaddrinfo, 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, DWORD hostlen, char FAR * serv, + (const struct sockaddr FAR *sa, socklen_t salen, + char FAR *host, DWORD hostlen, char FAR *serv, DWORD servlen, int flags)); DECL_WINDOWS_FUNCTION(static, int, WSAAddressToStringA, (LPSOCKADDR, DWORD, LPWSAPROTOCOL_INFO, diff --git a/windows/unicode.c b/windows/unicode.c index 09bce095..eaa6190d 100644 --- a/windows/unicode.c +++ b/windows/unicode.c @@ -435,7 +435,7 @@ static const struct cp_list_item cp_list[] = { {0, 0} }; -static void link_font(WCHAR * line_tbl, WCHAR * font_tbl, WCHAR attr); +static void link_font(WCHAR *line_tbl, WCHAR *font_tbl, WCHAR attr); /* * We keep a collection of reverse mappings from Unicode back to code pages, @@ -689,7 +689,7 @@ void init_ucs(Conf *conf, struct unicode_data *ucsdata) } } -static void link_font(WCHAR * line_tbl, WCHAR * font_tbl, WCHAR attr) +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++) { @@ -1200,7 +1200,7 @@ const char *cp_enumerate(int index) return cp_list[index].name; } -void get_unitab(int codepage, wchar_t * unitab, int ftype) +void get_unitab(int codepage, wchar_t *unitab, int ftype) { char tbuf[4]; int i, max = 256, flg = MB_ERR_INVALID_CHARS; diff --git a/windows/window.c b/windows/window.c index 6be9cbe5..dad6648c 100644 --- a/windows/window.c +++ b/windows/window.c @@ -122,7 +122,7 @@ static int prev_rows, prev_cols; static void flash_window(int mode); static void sys_cursor_update(void); -static bool get_fullscreen_rect(RECT * ss); +static bool get_fullscreen_rect(RECT *ss); static int caret_x = -1, caret_y = -1; @@ -5743,7 +5743,7 @@ static bool is_full_screen() /* 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 bool get_fullscreen_rect(RECT * ss) +static bool get_fullscreen_rect(RECT *ss) { #if defined(MONITOR_DEFAULTTONEAREST) && !defined(NO_MULTIMON) if (p_GetMonitorInfoA && p_MonitorFromWindow) { -- cgit v1.2.3 From 9cac27946ac59e5954db1f6d4a41f63accc9bcb6 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Wed, 3 Aug 2022 20:48:46 +0100 Subject: Formatting: miscellaneous. This patch fixes a few other whitespace and formatting issues which were pointed out by the bulk-reindent or which I spotted in passing, some involving manual editing to break lines more nicely. I think the weirdest hunk in here is the one in windows/window.c TranslateKey() where _half_ of an assignment statement inside an 'if' was on the same line as the trailing paren of the if condition. No idea at all how that one managed to happen! --- otherbackends/supdup.c | 44 ++++++++++++++++++++++---------------------- otherbackends/telnet.c | 2 +- pscp.c | 3 +-- ssh/pgssapi.c | 4 ++-- ssh/portfwd.c | 2 +- ssh/sftp.c | 3 +-- ssh/transport2.c | 6 +++--- ssh/userauth2-client.c | 7 ++++--- terminal/terminal.c | 6 +++--- test/sclog/sclog.c | 2 +- test/testcrypt.c | 2 +- unix/uppity.c | 2 +- unix/window.c | 18 ++++++++---------- utils/conf.c | 2 +- windows/jump-list.c | 9 +++------ windows/pageant.c | 3 +-- windows/unicode.c | 4 ++-- windows/window.c | 21 +++++++++------------ 18 files changed, 65 insertions(+), 75 deletions(-) diff --git a/otherbackends/supdup.c b/otherbackends/supdup.c index 3479f0f9..688befd2 100644 --- a/otherbackends/supdup.c +++ b/otherbackends/supdup.c @@ -1,6 +1,6 @@ /* -* Supdup backend -*/ + * Supdup backend + */ #include #include @@ -10,7 +10,7 @@ /* * TTYOPT FUNCTION BITS (36-bit bitmasks) -*/ + */ #define TOALT 0200000000000LL // Characters 0175 and 0176 are converted to altmode (0033) on input #define TOCLC 0100000000000LL // (user option bit) Convert lower-case input to upper-case #define TOERS 0040000000000LL // Selective erase is supported @@ -671,13 +671,13 @@ static const InteractorVtable Supdup_interactorvt = { }; /* -* Called to set up the Supdup connection. -* -* Returns an error message, or NULL on success. -* -* Also places the canonical host name into `realhost'. It must be -* freed by the caller. -*/ + * Called to set up the Supdup 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 char *supdup_init(const BackendVtable *x, Seat *seat, Backend **backend_handle, LogContext *logctx, Conf *conf, @@ -837,16 +837,16 @@ static void supdup_free(Backend *be) } /* -* Reconfigure the Supdup backend. -*/ + * Reconfigure the Supdup backend. + */ static void supdup_reconfig(Backend *be, Conf *conf) { /* Nothing to do; SUPDUP cannot be reconfigured while running. */ } /* -* Called to send data down the Supdup connection. -*/ + * Called to send data down the Supdup connection. + */ static void supdup_send(Backend *be, const char *buf, size_t len) { Supdup *supdup = container_of(be, Supdup, backend); @@ -867,8 +867,8 @@ static void supdup_send(Backend *be, const char *buf, size_t len) } /* -* Called to query the current socket sendability status. -*/ + * Called to query the current socket sendability status. + */ static size_t supdup_sendbuffer(Backend *be) { Supdup *supdup = container_of(be, Supdup, backend); @@ -876,8 +876,8 @@ static size_t supdup_sendbuffer(Backend *be) } /* -* Called to set the size of the window from Supdup's POV. -*/ + * Called to set the size of the window from Supdup's POV. + */ static void supdup_size(Backend *be, int width, int height) { Supdup *supdup = container_of(be, Supdup, backend); @@ -892,8 +892,8 @@ static void supdup_size(Backend *be, int width, int height) } /* -* Send Telnet special codes. -*/ + * Send Telnet special codes. + */ static void supdup_special(Backend *be, SessionSpecialCode code, int arg) { } @@ -946,8 +946,8 @@ static int supdup_exitcode(Backend *be) } /* -* cfg_info for Dupdup does nothing at all. -*/ + * cfg_info for Dupdup does nothing at all. + */ static int supdup_cfg_info(Backend *be) { return 0; diff --git a/otherbackends/telnet.c b/otherbackends/telnet.c index 50945b86..23b7fc9e 100644 --- a/otherbackends/telnet.c +++ b/otherbackends/telnet.c @@ -191,7 +191,7 @@ struct Telnet { enum { TOP_LEVEL, SEENIAC, SEENWILL, SEENWONT, SEENDO, SEENDONT, - SEENSB, SUBNEGOT, SUBNEG_IAC, SEENCR + SEENSB, SUBNEGOT, SUBNEG_IAC, SEENCR } state; Conf *conf; diff --git a/pscp.c b/pscp.c index 7e26fa40..77de1cd9 100644 --- a/pscp.c +++ b/pscp.c @@ -2189,8 +2189,7 @@ 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] 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"); diff --git a/ssh/pgssapi.c b/ssh/pgssapi.c index 9d33220f..1f54d805 100644 --- a/ssh/pgssapi.c +++ b/ssh/pgssapi.c @@ -56,7 +56,7 @@ 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\x04"}, + {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 @@ -75,7 +75,7 @@ static const gss_OID_desc oids[] = { * 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 + /* 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 diff --git a/ssh/portfwd.c b/ssh/portfwd.c index 11544564..b4eea3c9 100644 --- a/ssh/portfwd.c +++ b/ssh/portfwd.c @@ -514,7 +514,7 @@ void portfwd_raw_setup(Channel *pfchan, Socket *s, SshChannel *sc) } /* - called when someone connects to the local port + * called when someone connects to the local port */ static int pfl_accepting(Plug *p, accept_fn_t constructor, accept_ctx_t ctx) diff --git a/ssh/sftp.c b/ssh/sftp.c index 7debf94c..00072f13 100644 --- a/ssh/sftp.c +++ b/ssh/sftp.c @@ -283,8 +283,7 @@ bool fxp_init(void) return false; } if (remotever > SFTP_PROTO_VERSION) { - fxp_internal_error( - "remote protocol is more advanced than we support"); + fxp_internal_error("remote protocol is more advanced than we support"); sftp_pkt_free(pktin); return false; } diff --git a/ssh/transport2.c b/ssh/transport2.c index ab5902b9..705df466 100644 --- a/ssh/transport2.c +++ b/ssh/transport2.c @@ -2192,9 +2192,9 @@ static void ssh2_transport_reconfigure(PacketProtocolLayer *ppl, Conf *conf) for (i = 0; i < CIPHER_MAX; i++) if (conf_get_int_int(s->conf, CONF_ssh_cipherlist, i) != conf_get_int_int(conf, CONF_ssh_cipherlist, i)) { - rekey_reason = "cipher settings changed"; - rekey_mandatory = true; - } + rekey_reason = "cipher settings changed"; + rekey_mandatory = true; + } if (conf_get_bool(s->conf, CONF_ssh2_des_cbc) != conf_get_bool(conf, CONF_ssh2_des_cbc)) { rekey_reason = "cipher settings changed"; diff --git a/ssh/userauth2-client.c b/ssh/userauth2-client.c index a7cb803f..5f7a9b52 100644 --- a/ssh/userauth2-client.c +++ b/ssh/userauth2-client.c @@ -870,7 +870,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) agentreq = strbuf_new_for_agent_query(); put_byte(agentreq, SSH2_AGENTC_SIGN_REQUEST); put_stringpl(agentreq, ptrlen_from_strbuf( - s->agent_keys[s->agent_key_index].blob)); + s->agent_keys[s->agent_key_index].blob)); /* Now the data to be signed... */ sigdata = strbuf_new(); ssh2_userauth_add_session_id(s, sigdata); @@ -1223,8 +1223,9 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) ppl_logevent("GSSAPI authentication initialisation " "failed"); - if (s->shgss->lib->display_status(s->shgss->lib, - s->shgss->ctx, &s->gss_buf) == SSH_GSS_OK) { + if (s->shgss->lib->display_status( + s->shgss->lib, s->shgss->ctx, &s->gss_buf) + == SSH_GSS_OK) { ppl_logevent("%s", (char *)s->gss_buf.value); sfree(s->gss_buf.value); } diff --git a/terminal/terminal.c b/terminal/terminal.c index d4d0bde3..4ecfcab5 100644 --- a/terminal/terminal.c +++ b/terminal/terminal.c @@ -3128,8 +3128,8 @@ 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]; + 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]) { @@ -6028,7 +6028,7 @@ static void do_paint(Terminal *term) if (!term->ansi_colour) tattr = (tattr & ~(ATTR_FGMASK | ATTR_BGMASK)) | - ATTR_DEFFG | ATTR_DEFBG; + ATTR_DEFFG | ATTR_DEFBG; if (!term->xterm_256_colour) { int colour; diff --git a/test/sclog/sclog.c b/test/sclog/sclog.c index d8e61a05..d5304a01 100644 --- a/test/sclog/sclog.c +++ b/test/sclog/sclog.c @@ -586,7 +586,7 @@ static void load_module( #define TRY_WRAP(fn, pre, post) do \ { \ static bool done_this_one = false; \ - try_wrap_fn(module, fn, pre, post, &done_this_one); \ + try_wrap_fn(module, fn, pre, post, &done_this_one); \ } while (0) if (loaded) { diff --git a/test/testcrypt.c b/test/testcrypt.c index fcc116f0..cb4e6b81 100644 --- a/test/testcrypt.c +++ b/test/testcrypt.c @@ -1575,7 +1575,7 @@ OPTIONAL_PTR_FUNC(string) static void handle_##fname(BinarySource *_in, strbuf *_out) { \ ARGS_##fname _args = get_args_##fname(_in); \ (void)_args; /* suppress warning if no actual arguments */ \ - return_##outtype(_out, JUXTAPOSE2(realname, (__VA_ARGS__))); \ + return_##outtype(_out, JUXTAPOSE2(realname, (__VA_ARGS__))); \ } #include "testcrypt-func.h" #undef FUNC_INNER diff --git a/unix/uppity.c b/unix/uppity.c index 84d332c0..11fead95 100644 --- a/unix/uppity.c +++ b/unix/uppity.c @@ -353,7 +353,7 @@ static void show_help(FILE *fp) " --rsakexkey KEY key for SSH-2 RSA key exchange " "(in SSH-1 format)\n" " --userkey KEY public key" - " acceptable for user authentication\n" + " acceptable for user authentication\n" " --sessiondir DIR cwd for session subprocess (default $HOME)\n" " --bannertext TEXT send TEXT as SSH-2 auth banner\n" " --bannerfile FILE send contents of FILE as SSH-2 auth " diff --git a/unix/window.c b/unix/window.c index 624ecc48..1958edde 100644 --- a/unix/window.c +++ b/unix/window.c @@ -1915,11 +1915,10 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) if (event->state & GDK_CONTROL_MASK) break; - end = 1 + format_small_keypad_key(output+1, inst->term, sk_key, - event->state & GDK_SHIFT_MASK, - event->state & GDK_CONTROL_MASK, - event->state & inst->meta_mod_mask, - &consumed_meta_key); + end = 1 + format_small_keypad_key( + output+1, inst->term, sk_key, event->state & GDK_SHIFT_MASK, + event->state & GDK_CONTROL_MASK, + event->state & inst->meta_mod_mask, &consumed_meta_key); if (consumed_meta_key) start = 1; /* supersedes the usual prefixing of Esc */ #ifdef KEY_EVENT_DIAGNOSTICS @@ -1941,11 +1940,10 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) xkey = 'G'; goto arrow_key; arrow_key: consumed_meta_key = false; - end = 1 + format_arrow_key(output+1, inst->term, xkey, - event->state & GDK_SHIFT_MASK, - event->state & GDK_CONTROL_MASK, - event->state & inst->meta_mod_mask, - &consumed_meta_key); + end = 1 + format_arrow_key( + output+1, inst->term, xkey, event->state & GDK_SHIFT_MASK, + event->state & GDK_CONTROL_MASK, + event->state & inst->meta_mod_mask, &consumed_meta_key); if (consumed_meta_key) start = 1; /* supersedes the usual prefixing of Esc */ #ifdef KEY_EVENT_DIAGNOSTICS diff --git a/utils/conf.c b/utils/conf.c index 246dc3eb..53195180 100644 --- a/utils/conf.c +++ b/utils/conf.c @@ -476,7 +476,7 @@ void conf_del_str_str(Conf *conf, int primary, const char *secondary) del234(conf->tree, entry); free_entry(entry); } - } +} void conf_set_filename(Conf *conf, int primary, const Filename *value) { diff --git a/windows/jump-list.c b/windows/jump-list.c index 66df3510..47c41a04 100644 --- a/windows/jump-list.c +++ b/windows/jump-list.c @@ -721,8 +721,7 @@ bool set_explicit_app_user_model_id(void) static HMODULE shell32_module = 0; - if (!shell32_module) - { + if (!shell32_module) { shell32_module = load_system32_dll("Shell32.dll"); /* * We can't typecheck this function here, because it's defined @@ -733,11 +732,9 @@ bool set_explicit_app_user_model_id(void) shell32_module, SetCurrentProcessExplicitAppUserModelID); } - if (p_SetCurrentProcessExplicitAppUserModelID) - { + if (p_SetCurrentProcessExplicitAppUserModelID) { const wchar_t *id = get_app_user_model_id(); - if (p_SetCurrentProcessExplicitAppUserModelID(id) == S_OK) - { + if (p_SetCurrentProcessExplicitAppUserModelID(id) == S_OK) { return true; } return false; diff --git a/windows/pageant.c b/windows/pageant.c index fb2a1fec..507e5c96 100644 --- a/windows/pageant.c +++ b/windows/pageant.c @@ -1166,8 +1166,7 @@ static char *answer_filemapping_message(const char *mapname) err = dupstr("wrong owning SID of file mapping"); goto cleanup; } - } else - { + } else { #ifdef DEBUG_IPC debug("security APIs not present\n"); #endif diff --git a/windows/unicode.c b/windows/unicode.c index eaa6190d..72c35ea6 100644 --- a/windows/unicode.c +++ b/windows/unicode.c @@ -681,7 +681,7 @@ void init_ucs(Conf *conf, struct unicode_data *ucsdata) for (i = 96; i < 127; i++) if (!DIRECT_FONT(ucsdata->unitab_xterm[i])) ucsdata->unitab_xterm[i] = - (WCHAR) (CSET_ACP + poorman_vt100[i - 96]); + (WCHAR) (CSET_ACP + poorman_vt100[i - 96]); for(i=128;i<256;i++) if (!DIRECT_FONT(ucsdata->unitab_scoacs[i])) ucsdata->unitab_scoacs[i] = @@ -724,7 +724,7 @@ wchar_t xlat_uskbd2cyrllic(int ch) 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]; } diff --git a/windows/window.c b/windows/window.c index dad6648c..99acdbf2 100644 --- a/windows/window.c +++ b/windows/window.c @@ -1494,10 +1494,9 @@ static void init_fonts(int pick_width, int pick_height) /* !!! Yes the next line is right */ if (cset == OEM_CHARSET) ucsdata.font_codepage = GetOEMCP(); - else - if (TranslateCharsetInfo ((DWORD *)(ULONG_PTR)cset, - &info, TCI_SRCCHARSET)) - ucsdata.font_codepage = info.ciACP; + else if (TranslateCharsetInfo ((DWORD *)(ULONG_PTR)cset, + &info, TCI_SRCCHARSET)) + ucsdata.font_codepage = info.ciACP; else ucsdata.font_codepage = -1; @@ -2040,10 +2039,10 @@ static Mouse_Button translate_button(Mouse_Button button) return MBT_SELECT; if (button == MBT_MIDDLE) return conf_get_int(conf, CONF_mouse_is_xterm) == 1 ? - MBT_PASTE : MBT_EXTEND; + MBT_PASTE : MBT_EXTEND; if (button == MBT_RIGHT) return conf_get_int(conf, CONF_mouse_is_xterm) == 1 ? - MBT_EXTEND : MBT_PASTE; + MBT_EXTEND : MBT_PASTE; return 0; /* shouldn't happen */ } @@ -3819,9 +3818,7 @@ static void do_text_internal( MultiByteToWideChar(ucsdata.font_codepage, MB_USEGLYPHCHARS, dbcstext, 2, uni_buf+nlen, 1); mptr++; - } - else - { + } else { char dbcstext[1]; dbcstext[0] = text[mptr] & 0xFF; MultiByteToWideChar(ucsdata.font_codepage, MB_USEGLYPHCHARS, @@ -4301,9 +4298,9 @@ static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam, } if (wParam == compose_keycode) { - if (compose_state == 0 - && (HIWORD(lParam) & (KF_UP | KF_REPEAT)) == 0) compose_state = - 1; + 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 -- cgit v1.2.3 From 61a877cce40467cd30cfc1178af3d157cc19ea10 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Wed, 3 Aug 2022 20:48:46 +0100 Subject: Comment typo in supdup.c. Spotted in passing during all this indentation work: it had misspelled its own protocol name :-) --- otherbackends/supdup.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/otherbackends/supdup.c b/otherbackends/supdup.c index 688befd2..9aaf1d4f 100644 --- a/otherbackends/supdup.c +++ b/otherbackends/supdup.c @@ -946,7 +946,7 @@ static int supdup_exitcode(Backend *be) } /* - * cfg_info for Dupdup does nothing at all. + * cfg_info for Supdup does nothing at all. */ static int supdup_cfg_info(Backend *be) { -- cgit v1.2.3 From e711a08dafa678f800a2d70846ef5d36f770f48b Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 5 Aug 2022 12:42:06 +0100 Subject: cryptsuite.py: remove some rogue diagnostics. I must have left these in by mistake while I was still trying to make the certificate tests pass. --- test/cryptsuite.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/cryptsuite.py b/test/cryptsuite.py index c23bd00b..1512444c 100755 --- a/test/cryptsuite.py +++ b/test/cryptsuite.py @@ -2728,10 +2728,8 @@ Private-MAC: 5b1f6f4cc43eb0060d2c3e181bc0129343adba2b valid_after = 1000, valid_before = 2000), ca_key, signflags=ca_signflags) import base64 - print(base64.b64encode(ca_self_certificate)) self_signed_ca_key = ssh_key_new_pub( alg + '-cert', ca_self_certificate) - print(self_signed_ca_key) cert_pub = sign_cert_via_testcrypt( make_signature_preimage( key_to_certify = base_key.public_blob(), @@ -2742,7 +2740,6 @@ Private-MAC: 5b1f6f4cc43eb0060d2c3e181bc0129343adba2b principals = [b'username'], valid_after = 1000, valid_before = 2000), ca_key, signflags=ca_signflags) - print(base64.b64encode(cert_pub)) certified_key = ssh_key_new_priv(alg + '-cert', cert_pub, base_key.private_blob()) result, err = certified_key.check_cert( -- cgit v1.2.3 From cd7f6c4407ff0df2ee7c9ced4e0ffea3c3dff969 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 5 Aug 2022 18:08:59 +0100 Subject: Certificate-aware handling of key fingerprints. OpenSSH, when called on to give the fingerprint of a certified public key, will in many circumstances generate the hash of the public blob of the _underlying_ key, rather than the hash of the full certificate. I think the hash of the certificate is also potentially useful (if nothing else, it provides a way to tell apart multiple certificates on the same key). But I can also see that it's useful to be able to recognise a key as the same one 'really' (since all certificates on the same key share a private key, so they're unavoidably related). So I've dealt with this by introducing an extra pair of fingerprint types, giving the cross product of {MD5, SHA-256} x {base key only, full certificate}. You can manually select which one you want to see in some circumstances (notably PuTTYgen), and in others (such as diagnostics) both fingerprints will be emitted side by side via the new functions ssh2_double_fingerprint[_blob]. The default, following OpenSSH, is to just fingerprint the base key. --- cmdgen.c | 4 ++++ crypto/openssh-certs.c | 4 ++-- pageant.c | 12 ++++++------ ssh.h | 28 ++++++++++++++++++++++++++- ssh/kex2-client.c | 16 +++++++++------- sshpubk.c | 51 +++++++++++++++++++++++++++++++++++++++++++++++++- test/cryptsuite.py | 29 ++++++++++++++++++++++++++++ test/testcrypt-enum.h | 2 ++ unix/pageant.c | 18 ++++++++++++++++++ windows/pageant.c | 2 ++ windows/pageant.rc | 2 +- 11 files changed, 150 insertions(+), 18 deletions(-) diff --git a/cmdgen.c b/cmdgen.c index 8d82362f..25078b9a 100644 --- a/cmdgen.c +++ b/cmdgen.c @@ -614,6 +614,10 @@ int main(int argc, char **argv) fptype = SSH_FPTYPE_MD5; else if (!strcmp(p, "sha256")) fptype = SSH_FPTYPE_SHA256; + else if (!strcmp(p, "md5-cert")) + fptype = SSH_FPTYPE_MD5_CERT; + else if (!strcmp(p, "sha256-cert")) + fptype = SSH_FPTYPE_SHA256_CERT; else { fprintf(stderr, "puttygen: unknown fingerprint " "type `%s'\n", p); diff --git a/crypto/openssh-certs.c b/crypto/openssh-certs.c index e37e42b2..07f412b9 100644 --- a/crypto/openssh-certs.c +++ b/crypto/openssh-certs.c @@ -868,9 +868,9 @@ static SeatDialogText *opensshcert_cert_info(ssh_key *key) seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "%s", fp); sfree(fp); - fp = ssh2_fingerprint(ck->basekey, SSH_FPTYPE_DEFAULT); + fp = ssh2_fingerprint(key, ssh_fptype_to_cert(SSH_FPTYPE_DEFAULT)); seat_dialog_text_append(text, SDT_MORE_INFO_KEY, - "Fingerprint of underlying key"); + "Fingerprint including certificate"); seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "%s", fp); sfree(fp); diff --git a/pageant.c b/pageant.c index 72e61f8c..425e7a19 100644 --- a/pageant.c +++ b/pageant.c @@ -729,7 +729,7 @@ static PageantAsyncOp *pageant_make_op( int i; PageantKey *pk; for (i = 0; NULL != (pk = pageant_nth_key(2, i)); i++) { - char *fingerprint = ssh2_fingerprint_blob( + char *fingerprint = ssh2_double_fingerprint_blob( ptrlen_from_strbuf(pk->public_blob), SSH_FPTYPE_DEFAULT); pageant_client_log(pc, reqid, "returned key: %s %s", fingerprint, pk->comment); @@ -839,7 +839,7 @@ static PageantAsyncOp *pageant_make_op( have_flags = true; if (!pc->suppress_logging) { - char *fingerprint = ssh2_fingerprint_blob( + char *fingerprint = ssh2_double_fingerprint_blob( keyblob, SSH_FPTYPE_DEFAULT); pageant_client_log(pc, reqid, "requested key: %s", fingerprint); sfree(fingerprint); @@ -1047,7 +1047,7 @@ static PageantAsyncOp *pageant_make_op( } if (!pc->suppress_logging) { - char *fingerprint = ssh2_fingerprint_blob( + char *fingerprint = ssh2_double_fingerprint_blob( blob, SSH_FPTYPE_DEFAULT); pageant_client_log(pc, reqid, "unwanted key: %s", fingerprint); sfree(fingerprint); @@ -1160,7 +1160,7 @@ static PageantAsyncOp *pageant_make_op( } if (!pc->suppress_logging) { - char *fingerprint = ssh2_fingerprint_blob( + char *fingerprint = ssh2_double_fingerprint_blob( ptrlen_from_strbuf(public_blob), SSH_FPTYPE_DEFAULT); pageant_client_log(pc, reqid, "add-ppk: %s %s", fingerprint, comment); @@ -1263,7 +1263,7 @@ static PageantAsyncOp *pageant_make_op( } if (!pc->suppress_logging) { - char *fingerprint = ssh2_fingerprint_blob( + char *fingerprint = ssh2_double_fingerprint_blob( blob, SSH_FPTYPE_DEFAULT); pageant_client_log(pc, reqid, "key to re-encrypt: %s", fingerprint); @@ -1348,7 +1348,7 @@ static PageantAsyncOp *pageant_make_op( int i; PageantKey *pk; for (i = 0; NULL != (pk = pageant_nth_key(2, i)); i++) { - char *fingerprint = ssh2_fingerprint_blob( + char *fingerprint = ssh2_double_fingerprint_blob( ptrlen_from_strbuf(pk->public_blob), SSH_FPTYPE_DEFAULT); pageant_client_log(pc, reqid, "returned key: %s %s", diff --git a/ssh.h b/ssh.h index 139acb20..bd2e6961 100644 --- a/ssh.h +++ b/ssh.h @@ -1454,12 +1454,36 @@ enum { }; typedef enum { + /* Default fingerprint types strip off a certificate to show you + * the fingerprint of the underlying public key */ SSH_FPTYPE_MD5, SSH_FPTYPE_SHA256, + /* Non-default version of each fingerprint type which is 'raw', + * giving you the true hash of the public key blob even if it + * includes a certificate */ + SSH_FPTYPE_MD5_CERT, + SSH_FPTYPE_SHA256_CERT, } FingerprintType; +static inline bool ssh_fptype_is_cert(FingerprintType fptype) +{ + return fptype >= SSH_FPTYPE_MD5_CERT; +} +static inline FingerprintType ssh_fptype_from_cert(FingerprintType fptype) +{ + if (ssh_fptype_is_cert(fptype)) + fptype -= (SSH_FPTYPE_MD5_CERT - SSH_FPTYPE_MD5); + return fptype; +} +static inline FingerprintType ssh_fptype_to_cert(FingerprintType fptype) +{ + if (!ssh_fptype_is_cert(fptype)) + fptype += (SSH_FPTYPE_MD5_CERT - SSH_FPTYPE_MD5); + return fptype; +} + +#define SSH_N_FPTYPES (SSH_FPTYPE_SHA256_CERT + 1) #define SSH_FPTYPE_DEFAULT SSH_FPTYPE_SHA256 -#define SSH_N_FPTYPES (SSH_FPTYPE_SHA256 + 1) FingerprintType ssh2_pick_fingerprint(char **fingerprints, FingerprintType preferred_type); @@ -1473,6 +1497,8 @@ void ssh2_write_pubkey(FILE *fp, const char *comment, int keytype); char *ssh2_fingerprint_blob(ptrlen, FingerprintType); char *ssh2_fingerprint(ssh_key *key, FingerprintType); +char *ssh2_double_fingerprint_blob(ptrlen, FingerprintType); +char *ssh2_double_fingerprint(ssh_key *key, FingerprintType); char **ssh2_all_fingerprints_for_blob(ptrlen); char **ssh2_all_fingerprints(ssh_key *key); void ssh2_free_all_fingerprints(char **); diff --git a/ssh/kex2-client.c b/ssh/kex2-client.c index ccc62c3c..5935ef29 100644 --- a/ssh/kex2-client.c +++ b/ssh/kex2-client.c @@ -732,7 +732,7 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) * host key, store it. */ if (s->hkey) { - char *fingerprint = ssh2_fingerprint( + char *fingerprint = ssh2_double_fingerprint( s->hkey, SSH_FPTYPE_DEFAULT); ppl_logevent("GSS kex provided fallback host key:"); ppl_logevent("%s", fingerprint); @@ -791,7 +791,8 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) * triggered on purpose to populate the transient cache. */ assert(s->hkey); /* only KEXTYPE_GSS lets this be null */ - char *fingerprint = ssh2_fingerprint(s->hkey, SSH_FPTYPE_DEFAULT); + char *fingerprint = ssh2_double_fingerprint( + s->hkey, SSH_FPTYPE_DEFAULT); if (s->need_gss_transient_hostkey) { ppl_logevent("Post-GSS rekey provided fallback host key:"); @@ -867,10 +868,10 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) * hash.) */ if (ssh_key_alg(s->hkey)->is_certificate) { - char *base_fp = ssh2_fingerprint(ssh_key_base_key(s->hkey), - fptype_default); - ppl_logevent("Host key is a certificate, whose base key has " - "fingerprint:"); + char *base_fp = ssh2_fingerprint( + s->hkey, ssh_fptype_to_cert(fptype_default)); + ppl_logevent("Host key is a certificate. " + "Hash including certificate:"); ppl_logevent("%s", base_fp); sfree(base_fp); @@ -972,7 +973,8 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) assert(s->hkey); assert(ssh_key_alg(s->hkey) == s->cross_certifying); - char *fingerprint = ssh2_fingerprint(s->hkey, SSH_FPTYPE_DEFAULT); + char *fingerprint = ssh2_double_fingerprint( + s->hkey, SSH_FPTYPE_DEFAULT); ppl_logevent("Storing additional host key for this host:"); ppl_logevent("%s", fingerprint); sfree(fingerprint); diff --git a/sshpubk.c b/sshpubk.c index efc3f2e5..0648b4c4 100644 --- a/sshpubk.c +++ b/sshpubk.c @@ -1752,6 +1752,7 @@ static void ssh2_fingerprint_blob_sha256(ptrlen blob, strbuf *sb) char *ssh2_fingerprint_blob(ptrlen blob, FingerprintType fptype) { strbuf *sb = strbuf_new(); + strbuf *tmp = NULL; /* * Identify the key algorithm, if possible. @@ -1767,23 +1768,62 @@ char *ssh2_fingerprint_blob(ptrlen blob, FingerprintType fptype) if (alg) { int bits = ssh_key_public_bits(alg, blob); put_fmt(sb, "%.*s %d ", PTRLEN_PRINTF(algname), bits); + + if (!ssh_fptype_is_cert(fptype) && alg->is_certificate) { + ssh_key *key = ssh_key_new_pub(alg, blob); + if (key) { + tmp = strbuf_new(); + ssh_key_public_blob(ssh_key_base_key(key), + BinarySink_UPCAST(tmp)); + blob = ptrlen_from_strbuf(tmp); + ssh_key_free(key); + } + } } else { put_fmt(sb, "%.*s ", PTRLEN_PRINTF(algname)); } } - switch (fptype) { + switch (ssh_fptype_from_cert(fptype)) { case SSH_FPTYPE_MD5: ssh2_fingerprint_blob_md5(blob, sb); break; case SSH_FPTYPE_SHA256: ssh2_fingerprint_blob_sha256(blob, sb); break; + default: + unreachable("ssh_fptype_from_cert ruled out the other values"); } + if (tmp) + strbuf_free(tmp); + return strbuf_to_str(sb); } +char *ssh2_double_fingerprint_blob(ptrlen blob, FingerprintType fptype) +{ + if (ssh_fptype_is_cert(fptype)) + fptype = ssh_fptype_from_cert(fptype); + + char *fp = ssh2_fingerprint_blob(blob, fptype); + char *p = strrchr(fp, ' '); + char *hash = p ? p + 1 : fp; + + char *fpc = ssh2_fingerprint_blob(blob, ssh_fptype_to_cert(fptype)); + char *pc = strrchr(fpc, ' '); + char *hashc = pc ? pc + 1 : fpc; + + if (strcmp(hash, hashc)) { + char *tmp = dupprintf("%s (with certificate: %s)", fp, hashc); + sfree(fp); + fp = tmp; + } + + sfree(fpc); + return fp; +} + char **ssh2_all_fingerprints_for_blob(ptrlen blob) { char **fps = snewn(SSH_N_FPTYPES, char *); @@ -1801,6 +1841,15 @@ char *ssh2_fingerprint(ssh_key *data, FingerprintType fptype) return ret; } +char *ssh2_double_fingerprint(ssh_key *data, FingerprintType fptype) +{ + strbuf *blob = strbuf_new(); + ssh_key_public_blob(data, BinarySink_UPCAST(blob)); + char *ret = ssh2_double_fingerprint_blob(ptrlen_from_strbuf(blob), fptype); + strbuf_free(blob); + return ret; +} + char **ssh2_all_fingerprints(ssh_key *data) { strbuf *blob = strbuf_new(); diff --git a/test/cryptsuite.py b/test/cryptsuite.py index 1512444c..04450dd0 100755 --- a/test/cryptsuite.py +++ b/test/cryptsuite.py @@ -1410,6 +1410,35 @@ class crypt(MyTestBase): self.assertEqual(ssh2_fingerprint_blob(very_silly_blob, "md5"), b'ac:bd:18:db:4c:c2:f8:5c:ed:ef:65:4f:cc:c4:a4:d8') + # A certified key. + cert_blob = b64( + 'AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIJ4Ds9YwRHxs' + 'xdtUitRbZGz0MgKGZSBVrTHI1AbvetofAAAAIMt0/CMBL+64GQ/r/JyGxo6oHs86' + 'i9bOHhMJYbDbxEJfAAAAAAAAAG8AAAABAAAAAmlkAAAADAAAAAh1c2VybmFtZQAA' + 'AAAAAAPoAAAAAAAAB9AAAAAAAAAAAAAAAAAAAAE+AAAAIHNzaC1lZDI1NTE5LWNl' + 'cnQtdjAxQG9wZW5zc2guY29tAAAAICl5MiUNt8hoAAHT0v00JYOkWe2UW31+Qq5Q' + 'HYKWGyVjAAAAIMUJEFAmSV/qtoxSmVOHUgTMKYjqkDy8fTfsfCKV+sN7AAAAAAAA' + 'AG8AAAABAAAAAmlkAAAAEgAAAA5kb2Vzbid0IG1hdHRlcgAAAAAAAAPoAAAAAAAA' + 'B9AAAAAAAAAAAAAAAAAAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIMUJEFAmSV/qtoxS' + 'mVOHUgTMKYjqkDy8fTfsfCKV+sN7AAAAUwAAAAtzc2gtZWQyNTUxOQAAAEAXbRz3' + 'lBmoU4FVge29jn04MfubF6U0CoPG1nbeZSgDN2iz7qtZ75XIk5O/Z/W9nA8jwsiz' + 'iSEMItjvR7HEN8MIAAAAUwAAAAtzc2gtZWQyNTUxOQAAAECszhkY8bUbSCjmHEMP' + 'LjcOX6OaeBzPIYYYXJzpLn+m+CIwDXRIxyvON5/d/TomgAFNJutfOEsqIzy5OAvl' + 'p5IO') + self.assertEqual(ssh2_fingerprint_blob(cert_blob, "sha256"), + b'ssh-ed25519-cert-v01@openssh.com 255 ' + b'SHA256:42JaqhHUNa5CoKxGWqtKXF0Awz7b0aPrtgBZ9VLLHfY') + self.assertEqual(ssh2_fingerprint_blob(cert_blob, "md5"), + b'ssh-ed25519-cert-v01@openssh.com 255 ' + b'8e:40:00:e0:1f:4a:9c:b3:c8:e9:05:59:04:03:44:b3') + self.assertEqual(ssh2_fingerprint_blob(cert_blob, "sha256-cert"), + b'ssh-ed25519-cert-v01@openssh.com 255 ' + b'SHA256:W/+SDEg7S+/dAn4SrodJ2c8bYvt13XXA7YYlQ6E8R5U') + self.assertEqual(ssh2_fingerprint_blob(cert_blob, "md5-cert"), + b'ssh-ed25519-cert-v01@openssh.com 255 ' + b'03:cf:aa:8e:aa:c3:a0:97:bb:2e:7e:57:9d:08:b5:be') + + def testAES(self): # My own test cases, generated by a mostly independent # reference implementation of AES in Python. ('Mostly' diff --git a/test/testcrypt-enum.h b/test/testcrypt-enum.h index f1977c3e..ac73a766 100644 --- a/test/testcrypt-enum.h +++ b/test/testcrypt-enum.h @@ -136,6 +136,8 @@ END_ENUM_TYPE(argon2flavour) BEGIN_ENUM_TYPE(fptype) ENUM_VALUE("md5", SSH_FPTYPE_MD5) ENUM_VALUE("sha256", SSH_FPTYPE_SHA256) + ENUM_VALUE("md5-cert", SSH_FPTYPE_MD5_CERT) + ENUM_VALUE("sha256-cert", SSH_FPTYPE_SHA256_CERT) END_ENUM_TYPE(fptype) /* diff --git a/unix/pageant.c b/unix/pageant.c index 5db797a8..cef57b5b 100644 --- a/unix/pageant.c +++ b/unix/pageant.c @@ -612,6 +612,7 @@ static bool match_fingerprint_string( switch (fptype) { case SSH_FPTYPE_MD5: + case SSH_FPTYPE_MD5_CERT: /* MD5 fingerprints are in hex, so disregard case differences. */ case_sensitive = false; /* And we don't really need to force the user to type the @@ -620,6 +621,7 @@ static bool match_fingerprint_string( ignore_chars = ":"; break; case SSH_FPTYPE_SHA256: + case SSH_FPTYPE_SHA256_CERT: /* Skip over the "SHA256:" prefix, which we don't really * want to force the user to type. On the other hand, * tolerate it on the input string. */ @@ -713,6 +715,18 @@ struct pageant_pubkey *find_key(const char *string, char **retstr) try_comment = false; try_all_fptypes = false; fptype = SSH_FPTYPE_SHA256; + } else if (!strnicmp(string, "md5-cert:", 9)) { + string += 9; + try_file = false; + try_comment = false; + try_all_fptypes = false; + fptype = SSH_FPTYPE_MD5_CERT; + } else if (!strncmp(string, "sha256-cert:", 12)) { + string += 12; + try_file = false; + try_comment = false; + try_all_fptypes = false; + fptype = SSH_FPTYPE_SHA256_CERT; } /* @@ -1402,6 +1416,10 @@ int main(int argc, char **argv) key_list_fptype = SSH_FPTYPE_MD5; else if (!strcmp(keyword, "sha256")) key_list_fptype = SSH_FPTYPE_SHA256; + else if (!strcmp(keyword, "md5-cert")) + key_list_fptype = SSH_FPTYPE_MD5_CERT; + else if (!strcmp(keyword, "sha256-cert")) + key_list_fptype = SSH_FPTYPE_SHA256_CERT; else { fprintf(stderr, "pageant: unknown fingerprint type `%s'\n", keyword); diff --git a/windows/pageant.c b/windows/pageant.c index 507e5c96..60040310 100644 --- a/windows/pageant.c +++ b/windows/pageant.c @@ -600,6 +600,8 @@ static INT_PTR CALLBACK KeyListProc(HWND hwnd, UINT msg, } fptypes[] = { {"SHA256", SSH_FPTYPE_SHA256}, {"MD5", SSH_FPTYPE_MD5}, + {"SHA256 including certificate", SSH_FPTYPE_SHA256_CERT}, + {"MD5 including certificate", SSH_FPTYPE_MD5_CERT}, }; switch (msg) { diff --git a/windows/pageant.rc b/windows/pageant.rc index 57fdbcf4..1bea78b4 100644 --- a/windows/pageant.rc +++ b/windows/pageant.rc @@ -60,7 +60,7 @@ BEGIN PUSHBUTTON "&Help", IDC_KEYLIST_HELP, 10, 212, 50, 14 DEFPUSHBUTTON "&Close", IDOK, 390, 212, 50, 14 LTEXT "&Fingerprint type:", IDC_KEYLIST_FPTYPE_STATIC, 10, 172, 60, 8 - COMBOBOX IDC_KEYLIST_FPTYPE, 70, 170, 60, 12, CBS_DROPDOWNLIST + COMBOBOX IDC_KEYLIST_FPTYPE, 70, 170, 100, 12, CBS_DROPDOWNLIST END /* Accelerators used: cl */ -- cgit v1.2.3 From 423ce20ffbf0f9366cb184c7ad792143f0726e4e Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 6 Aug 2022 10:41:41 +0100 Subject: Pageant core: separate public and private key storage. Previously, we had a single data structure 'keytree' containing records each involving a public and private key (the latter maybe in clear, or as an encrypted key file, or both). Now, we have separate 'pubkeytree' and 'privkeytree', the former storing public keys indexed by their full public blob (including certificate, if any), and the latter storing private keys, indexed by the _base_ public blob only (i.e. with no certificate included). The effect of this is that deferred decryption interacts more sensibly with certificates. Now, if you load certified and uncertified versions of the same key into Pageant, or two or more differently certified versions, then the separate public key records will all share the same private key record, and hence, a single state of decryption. So the first time you enter a passphrase that unlocks that private key, it will unlock it for all public keys that share the same private half. Conversely, re-encrypting any one of them will cause all of them to become re-encrypted, eliminating the risk that you deliberately re-encrypt a key you really care about and forget that another equally valuble copy of it is still in clear. The most subtle part of this turned out to be the question of what key comment you present in a deferred decryption prompt. It's very tempting to imagine that it should be the comment that goes with whichever _public_ key was involved in the signing request that triggered the prompt. But in fact, it _must_ be the comment that goes with whichever version of the encrypted key file is stored in Pageant - because what if the user chose different passphrases for their uncertified and certified PPKs? Then the decryption prompt will have to indicate which passphrase they should be typing, so it's vital to present the comment that goes with the _file we're decrypting_. (Of course, if the user has selected different passphrases for those two PPKs but the _same_ comment, they're still going to end up confused. But at least once they realise they've done that, they have a workaround.) --- crypto/rsa.c | 15 ++ pageant.c | 752 ++++++++++++++++++++++++++++++++++++++--------------------- ssh.h | 1 + 3 files changed, 505 insertions(+), 263 deletions(-) diff --git a/crypto/rsa.c b/crypto/rsa.c index 4c4243b8..ad4d9541 100644 --- a/crypto/rsa.c +++ b/crypto/rsa.c @@ -76,6 +76,21 @@ RSAKey *BinarySource_get_rsa_ssh1_priv_agent(BinarySource *src) return rsa; } +void duprsakey(RSAKey *dst, const RSAKey *src) +{ + dst->bits = src->bits; + dst->bytes = src->bytes; + dst->modulus = mp_copy(src->modulus); + dst->exponent = mp_copy(src->exponent); + dst->private_exponent = src->private_exponent ? + mp_copy(src->private_exponent) : NULL; + dst->p = mp_copy(src->p); + dst->q = mp_copy(src->q); + dst->iqmp = mp_copy(src->iqmp); + dst->comment = src->comment ? dupstr(src->comment) : NULL; + dst->sshk.vt = src->sshk.vt; +} + bool rsa_ssh1_encrypt(unsigned char *data, int length, RSAKey *key) { mp_int *b1, *b2; diff --git a/pageant.c b/pageant.c index 425e7a19..269dd005 100644 --- a/pageant.c +++ b/pageant.c @@ -33,8 +33,10 @@ struct PageantClientDialogId { int dummy; }; -typedef struct PageantKeySort PageantKeySort; -typedef struct PageantKey PageantKey; +typedef struct PageantPrivateKeySort PageantPrivateKeySort; +typedef struct PageantPublicKeySort PageantPublicKeySort; +typedef struct PageantPrivateKey PageantPrivateKey; +typedef struct PageantPublicKey PageantPublicKey; typedef struct PageantAsyncOp PageantAsyncOp; typedef struct PageantAsyncOpVtable PageantAsyncOpVtable; typedef struct PageantClientRequestNode PageantClientRequestNode; @@ -85,33 +87,106 @@ static void pageant_async_op_callback(void *vctx) } /* - * Master list of all the keys we have stored, in any form at all. + * Master lists of all the keys we have stored, in any form at all. + * + * We store private and public keys in separate lists, because + * multiple public keys can share the same private key (due to one + * having a certificate and the other not, or having more than one + * different certificate). And when we decrypt or re-encrypt a private + * key, we don't really want to faff about doing it multiple times if + * there's more than one public key it goes with. If someone tries to + * re-encrypt a key to make their machine safer against unattended + * access, then it would be embarrassing to find they'd forgotten to + * re-encrypt the _other_ copy of it; conversely, once you've + * decrypted a key, it's pointless to make someone type yet another + * passphrase. + * + * (Causing multiple keys to become decrypted in one go isn't a + * security hole in its own right, because the signatures generated by + * certified and uncertified keys are identical. So an attacker + * gaining access to an agent containing one encrypted and one + * cleartext key with the same private half would still be *able* to + * generate signatures that went with the encrypted one, even if the + * agent refused to hand them out in response to the most obvious kind + * of request.) */ -static tree234 *keytree; -struct PageantKeySort { - /* Prefix of the main PageantKey structure which contains all the - * data that the sorting order depends on. Also simple enough that - * you can construct one for lookup purposes. */ +struct PageantPrivateKeySort { + /* + * Information used by the sorting criterion for the private key + * tree. + */ int ssh_version; /* 1 or 2; primary sort key */ - ptrlen public_blob; /* secondary sort key */ + ptrlen base_pub; /* secondary sort key; never includes a certificate */ +}; +static int privkey_cmpfn(void *av, void *bv) +{ + PageantPrivateKeySort *a = (PageantPrivateKeySort *)av; + PageantPrivateKeySort *b = (PageantPrivateKeySort *)bv; + + if (a->ssh_version != b->ssh_version) + return a->ssh_version < b->ssh_version ? -1 : +1; + else + return ptrlen_strcmp(a->base_pub, b->base_pub); +} + +struct PageantPublicKeySort { + /* + * Information used by the sorting criterion for the public key + * tree. Begins with the private key sorting criterion, so that + * all the public keys sharing a private key appear adjacent in + * the tree. That's a reasonably sensible order to list them in + * for the user, and more importantly, it makes it easy to + * discover when we're deleting the last public key that goes with + * a particular private one, so as to delete that too. Easier than + * messing about with fragile reference counts. + */ + PageantPrivateKeySort priv; + ptrlen full_pub; /* may match priv.base_pub, or may include a cert */ }; -struct PageantKey { - PageantKeySort sort; - strbuf *public_blob; /* the true owner of sort.public_blob */ - char *comment; /* stored separately, whether or not in rkey/skey */ +static int pubkey_cmpfn(void *av, void *bv) +{ + PageantPublicKeySort *a = (PageantPublicKeySort *)av; + PageantPublicKeySort *b = (PageantPublicKeySort *)bv; + + int c = privkey_cmpfn(&a->priv, &b->priv); + if (c) + return c; + else + return ptrlen_strcmp(a->full_pub, b->full_pub); +} + +struct PageantPrivateKey { + PageantPrivateKeySort sort; + strbuf *base_pub; /* the true owner of sort.base_pub */ union { - RSAKey *rkey; /* if ssh_version == 1 */ - ssh2_userkey *skey; /* if ssh_version == 2 */ + RSAKey *rkey; /* if sort.priv.ssh_version == 1 */ + ssh_key *skey; /* if sort.priv.ssh_version == 2 */ }; strbuf *encrypted_key_file; + /* encrypted_key_comment stores the comment belonging to the + * encrypted key file. This is used when presenting deferred + * decryption prompts, because if the user had encrypted their + * uncert and cert keys with different passphrases, the passphrase + * prompt must reliably signal which file they're supposed to be + * entering the passphrase for. */ + char *encrypted_key_comment; bool decryption_prompt_active; PageantKeyRequestNode blocked_requests; PageantClientDialogId dlgid; }; +static tree234 *privkeytree; + +struct PageantPublicKey { + PageantPublicKeySort sort; + strbuf *base_pub; /* the true owner of sort.priv.base_pub */ + strbuf *full_pub; /* the true owner of sort.full_pub */ + char *comment; +}; +static tree234 *pubkeytree; typedef struct PageantSignOp PageantSignOp; struct PageantSignOp { - PageantKey *pk; + PageantPrivateKey *priv; strbuf *data_to_sign; unsigned flags; int crLine; @@ -129,44 +204,35 @@ static PageantKeyRequestNode requests_blocked_on_gui = static void failure(PageantClient *pc, PageantClientRequestId *reqid, strbuf *sb, unsigned char type, const char *fmt, ...); -static void fail_requests_for_key(PageantKey *pk, const char *reason); -static PageantKey *pageant_nth_key(int ssh_version, int i); +static void fail_requests_for_key(PageantPrivateKey *priv, const char *reason); +static PageantPublicKey *pageant_nth_pubkey(int ssh_version, int i); -static void pk_free(PageantKey *pk) +static void pk_priv_free(PageantPrivateKey *priv) { - if (pk->public_blob) strbuf_free(pk->public_blob); - sfree(pk->comment); - if (pk->sort.ssh_version == 1 && pk->rkey) { - freersakey(pk->rkey); - sfree(pk->rkey); + if (priv->base_pub) + strbuf_free(priv->base_pub); + if (priv->sort.ssh_version == 1 && priv->rkey) { + freersakey(priv->rkey); + sfree(priv->rkey); } - if (pk->sort.ssh_version == 2 && pk->skey) { - sfree(pk->skey->comment); - ssh_key_free(pk->skey->key); - sfree(pk->skey); + if (priv->sort.ssh_version == 2 && priv->skey) { + ssh_key_free(priv->skey); } - if (pk->encrypted_key_file) strbuf_free(pk->encrypted_key_file); - fail_requests_for_key(pk, "key deleted from Pageant while signing " + if (priv->encrypted_key_file) + strbuf_free(priv->encrypted_key_file); + if (priv->encrypted_key_comment) + sfree(priv->encrypted_key_comment); + fail_requests_for_key(priv, "key deleted from Pageant while signing " "request was pending"); - sfree(pk); + sfree(priv); } -static int cmpkeys(void *av, void *bv) +static void pk_pub_free(PageantPublicKey *pub) { - PageantKeySort *a = (PageantKeySort *)av, *b = (PageantKeySort *)bv; - - if (a->ssh_version != b->ssh_version) - return a->ssh_version < b->ssh_version ? -1 : +1; - else - return ptrlen_strcmp(a->public_blob, b->public_blob); -} - -static inline PageantKeySort keysort(int version, ptrlen blob) -{ - PageantKeySort sort; - sort.ssh_version = version; - sort.public_blob = blob; - return sort; + if (pub->full_pub) + strbuf_free(pub->full_pub); + sfree(pub->comment); + sfree(pub); } static strbuf *makeblob1(RSAKey *rkey) @@ -177,126 +243,308 @@ static strbuf *makeblob1(RSAKey *rkey) return blob; } -static strbuf *makeblob2(ssh2_userkey *skey) +static strbuf *makeblob2full(ssh_key *key) +{ + strbuf *blob = strbuf_new(); + ssh_key_public_blob(key, BinarySink_UPCAST(blob)); + return blob; +} + +static strbuf *makeblob2base(ssh_key *key) { strbuf *blob = strbuf_new(); - ssh_key_public_blob(skey->key, BinarySink_UPCAST(blob)); + ssh_key_public_blob(ssh_key_base_key(key), BinarySink_UPCAST(blob)); return blob; } -static PageantKey *findkey1(RSAKey *reqkey) +static PageantPrivateKey *pub_to_priv(PageantPublicKey *pub) +{ + PageantPrivateKey *priv = find234(privkeytree, &pub->sort.priv, NULL); + assert(priv && "Public and private trees out of sync!"); + return priv; +} + +static PageantPublicKey *findpubkey1(RSAKey *reqkey) { strbuf *blob = makeblob1(reqkey); - PageantKeySort sort = keysort(1, ptrlen_from_strbuf(blob)); - PageantKey *toret = find234(keytree, &sort, NULL); + PageantPublicKeySort sort; + sort.priv.ssh_version = 1; + sort.priv.base_pub = ptrlen_from_strbuf(blob); + sort.full_pub = ptrlen_from_strbuf(blob); + PageantPublicKey *toret = find234(pubkeytree, &sort, NULL); strbuf_free(blob); return toret; } -static PageantKey *findkey2(ptrlen blob) +/* + * Constructs the base_pub element of a PageantPublicKeySort, starting + * from full_pub. This may involve allocating a strbuf to store it in, + * which must survive until after you've finished using the resulting + * PageantPublicKeySort. Hence, the strbuf (if any) is returned from + * this function, and if it's non-NULL then the caller must eventually + * free it. + */ +static strbuf *make_base_pub_2(PageantPublicKeySort *sort) +{ + /* Start with the fallback option of making base_pub equal full_pub */ + sort->priv.base_pub = sort->full_pub; + + /* Now reconstruct a distinct base_pub without a cert, if possible + * and necessary */ + strbuf *base_pub = NULL; + BinarySource src[1]; + BinarySource_BARE_INIT_PL(src, sort->full_pub); + ptrlen algname = get_string(src); + const ssh_keyalg *alg = find_pubkey_alg_len(algname); + if (alg && alg->is_certificate) { + ssh_key *key = ssh_key_new_pub(alg, sort->full_pub); + if (key) { + base_pub = strbuf_new(); + ssh_key_public_blob(ssh_key_base_key(key), + BinarySink_UPCAST(base_pub)); + sort->priv.base_pub = ptrlen_from_strbuf(base_pub); + ssh_key_free(key); + } + } + + return base_pub; /* caller must free once they're done with sort */ +} + +static PageantPublicKey *findpubkey2(ptrlen full_pub) { - PageantKeySort sort = keysort(2, blob); - return find234(keytree, &sort, NULL); + PageantPublicKeySort sort; + sort.priv.ssh_version = 2; + sort.full_pub = full_pub; + strbuf *base_pub = make_base_pub_2(&sort); + PageantPublicKey *toret = find234(pubkeytree, &sort, NULL); + if (base_pub) + strbuf_free(base_pub); + return toret; } -static int find_first_key_for_version(int ssh_version) +static int find_first_pubkey_for_version(int ssh_version) { - PageantKeySort sort = keysort(ssh_version, PTRLEN_LITERAL("")); + PageantPublicKeySort sort; + sort.priv.ssh_version = ssh_version; + sort.priv.base_pub = PTRLEN_LITERAL(""); + sort.full_pub = PTRLEN_LITERAL(""); int pos; - if (findrelpos234(keytree, &sort, NULL, REL234_GE, &pos)) + if (findrelpos234(pubkeytree, &sort, NULL, REL234_GE, &pos)) return pos; - return count234(keytree); + return count234(pubkeytree); } static int count_keys(int ssh_version) { - return (find_first_key_for_version(ssh_version + 1) - - find_first_key_for_version(ssh_version)); + return (find_first_pubkey_for_version(ssh_version + 1) - + find_first_pubkey_for_version(ssh_version)); } int pageant_count_ssh1_keys(void) { return count_keys(1); } int pageant_count_ssh2_keys(void) { return count_keys(2); } -static bool pageant_add_ssh1_key(RSAKey *rkey) +/* + * Common code to add a key to the trees. We fill in as many fields + * here as we can share between SSH versions: the ptrlens in the + * sorting field, the whole of pub->sort.priv, and the linked list of + * blocked requests. + */ +static bool pageant_add_key_common(PageantPublicKey *pub, + PageantPrivateKey *priv) { - PageantKey *pk = snew(PageantKey); - memset(pk, 0, sizeof(PageantKey)); - pk->sort.ssh_version = 1; - pk->public_blob = makeblob1(rkey); - pk->sort.public_blob = ptrlen_from_strbuf(pk->public_blob); - pk->blocked_requests.next = pk->blocked_requests.prev = - &pk->blocked_requests; - - if (add234(keytree, pk) == pk) { - pk->rkey = rkey; - if (rkey->comment) - pk->comment = dupstr(rkey->comment); + int ssh_version = priv->sort.ssh_version; + + priv->sort.base_pub = ptrlen_from_strbuf(priv->base_pub); + + pub->base_pub = strbuf_dup(priv->sort.base_pub); + pub->sort.priv.ssh_version = priv->sort.ssh_version; + pub->sort.priv.base_pub = ptrlen_from_strbuf(pub->base_pub); + pub->sort.full_pub = ptrlen_from_strbuf(pub->full_pub); + priv->blocked_requests.next = priv->blocked_requests.prev = + &priv->blocked_requests; + + /* + * Try to add the private key to privkeytree, or combine new parts + * of it with what's already there. + */ + PageantPrivateKey *priv_in_tree = add234(privkeytree, priv); + if (priv_in_tree == priv) { + /* The key wasn't in the tree at all, and we've just added it. */ + } else { + /* The key was already in the tree, so we'll be freeing priv. */ + + if (ssh_version == 2 && priv->skey && !priv_in_tree->skey) { + /* The key was only stored encrypted, and now we have an + * unencrypted version to add to the existing record. */ + priv_in_tree->skey = priv->skey; + priv->skey = NULL; /* so pk_priv_free won't free it */ + } + + if (ssh_version == 2 && priv->encrypted_key_file && + !priv_in_tree->encrypted_key_file) { + /* Conversely, the key was only stored in clear, and now + * we have an encrypted version to add to it. */ + priv_in_tree->encrypted_key_file = priv->encrypted_key_file; + priv->encrypted_key_file = NULL; + priv_in_tree->encrypted_key_comment = priv->encrypted_key_comment; + priv->encrypted_key_comment = NULL; + } + + pk_priv_free(priv); + } + + /* + * Try to add the public key. + */ + PageantPublicKey *pub_in_tree = add234(pubkeytree, pub); + if (pub_in_tree == pub) { + /* Successfully added a new key. */ return true; } else { - pk_free(pk); + /* This public key was already there. */ + pk_pub_free(pub); return false; } } +static bool pageant_add_ssh1_key(RSAKey *rkey) +{ + PageantPublicKey *pub = snew(PageantPublicKey); + memset(pub, 0, sizeof(PageantPublicKey)); + PageantPrivateKey *priv = snew(PageantPrivateKey); + memset(priv, 0, sizeof(PageantPrivateKey)); + + priv->sort.ssh_version = 1; + priv->base_pub = makeblob1(rkey); + pub->full_pub = makeblob1(rkey); + + if (rkey->comment) + pub->comment = dupstr(rkey->comment); + + priv->rkey = snew(RSAKey); + duprsakey(priv->rkey, rkey); + + return pageant_add_key_common(pub, priv); +} + static bool pageant_add_ssh2_key(ssh2_userkey *skey) { - PageantKey *pk = snew(PageantKey); - memset(pk, 0, sizeof(PageantKey)); - pk->sort.ssh_version = 2; - pk->public_blob = makeblob2(skey); - pk->sort.public_blob = ptrlen_from_strbuf(pk->public_blob); - pk->blocked_requests.next = pk->blocked_requests.prev = - &pk->blocked_requests; + PageantPublicKey *pub = snew(PageantPublicKey); + memset(pub, 0, sizeof(PageantPublicKey)); + PageantPrivateKey *priv = snew(PageantPrivateKey); + memset(priv, 0, sizeof(PageantPrivateKey)); - PageantKey *pk_in_tree = add234(keytree, pk); - if (pk_in_tree == pk) { - /* The key wasn't in the tree at all, and we've just added it. */ - pk->skey = skey; - if (skey->comment) - pk->comment = dupstr(skey->comment); - return true; - } else if (!pk_in_tree->skey) { - /* The key was only stored encrypted, and now we have an - * unencrypted version to add to the existing record. */ - pk_in_tree->skey = skey; - pk_free(pk); - return true; + priv->sort.ssh_version = 2; + priv->base_pub = makeblob2base(skey->key); + pub->full_pub = makeblob2full(skey->key); + + if (skey->comment) + pub->comment = dupstr(skey->comment); + + /* Duplicate the ssh_key to go in priv */ + { + strbuf *tmp = strbuf_new_nm(); + ssh_key_openssh_blob(skey->key, BinarySink_UPCAST(tmp)); + BinarySource src[1]; + BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(tmp)); + priv->skey = ssh_key_new_priv_openssh(ssh_key_alg(skey->key), src); + strbuf_free(tmp); + } + + return pageant_add_key_common(pub, priv); +} + +static bool pageant_add_ssh2_key_encrypted(PageantPublicKeySort sort, + const char *comment, ptrlen keyfile) +{ + PageantPublicKey *pub = snew(PageantPublicKey); + memset(pub, 0, sizeof(PageantPublicKey)); + PageantPrivateKey *priv = snew(PageantPrivateKey); + memset(priv, 0, sizeof(PageantPrivateKey)); + + assert(sort.priv.ssh_version == 2); + priv->sort.ssh_version = sort.priv.ssh_version; + priv->base_pub = strbuf_dup(sort.priv.base_pub); + pub->full_pub = strbuf_dup(sort.full_pub); + + pub->comment = dupstr(comment); + + priv->encrypted_key_file = strbuf_dup_nm(keyfile); + priv->encrypted_key_comment = dupstr(comment); + + return pageant_add_key_common(pub, priv); +} + +static void remove_pubkey_cleanup(PageantPublicKey *pub) +{ + /* Common function called when we've just removed a public key + * from pubkeytree: we must also check whether that was the last + * public key sharing a private half, and if so, remove the + * corresponding private entry too. */ + + PageantPublicKeySort pubsearch; + pubsearch.priv = pub->sort.priv; + pubsearch.full_pub = PTRLEN_LITERAL(""); + PageantPublicKey *pubfound = findrel234( + pubkeytree, &pubsearch, NULL, REL234_GE); + + if (pubfound && !privkey_cmpfn(&pub->sort.priv, &pubfound->sort.priv)) { + /* There's still a public key which has the same sort.priv as + * the one we've just removed. We're good. */ } else { - /* The key was already in the tree in full. */ - pk_free(pk); - return false; + /* We've just removed the last public key of the family, so + * delete the private half as well. */ + PageantPrivateKey *priv = del234(privkeytree, &pub->sort.priv); + assert(priv); + assert(!privkey_cmpfn(&priv->sort, &pub->sort.priv)); + pk_priv_free(priv); } } +static PageantPublicKey *del_pubkey_pos(int pos) +{ + PageantPublicKey *deleted = delpos234(pubkeytree, pos); + remove_pubkey_cleanup(deleted); + return deleted; +} + +static void del_pubkey(PageantPublicKey *to_delete) +{ + PageantPublicKey *deleted = del234(pubkeytree, to_delete); + remove_pubkey_cleanup(deleted); +} + static void remove_all_keys(int ssh_version) { - int start = find_first_key_for_version(ssh_version); - int end = find_first_key_for_version(ssh_version + 1); + int start = find_first_pubkey_for_version(ssh_version); + int end = find_first_pubkey_for_version(ssh_version + 1); while (end > start) { - PageantKey *pk = delpos234(keytree, --end); - assert(pk->sort.ssh_version == ssh_version); - pk_free(pk); + PageantPublicKey *pub = del_pubkey_pos(--end); + assert(pub->sort.priv.ssh_version == ssh_version); + pk_pub_free(pub); } } static void list_keys(BinarySink *bs, int ssh_version, bool extended) { int i; - PageantKey *pk; + PageantPublicKey *pub; put_uint32(bs, count_keys(ssh_version)); - for (i = find_first_key_for_version(ssh_version); - NULL != (pk = index234(keytree, i)); i++) { - if (pk->sort.ssh_version != ssh_version) + for (i = find_first_pubkey_for_version(ssh_version); + NULL != (pub = index234(pubkeytree, i)); i++) { + if (pub->sort.priv.ssh_version != ssh_version) break; if (ssh_version > 1) - put_stringpl(bs, pk->sort.public_blob); + put_stringpl(bs, pub->sort.full_pub); else - put_datapl(bs, pk->sort.public_blob); /* no header */ + put_datapl(bs, pub->sort.full_pub); /* no header */ - put_stringpl(bs, ptrlen_from_asciz(pk->comment)); + put_stringpl(bs, ptrlen_from_asciz(pub->comment)); if (extended) { + assert(ssh_version == 2); /* extended lists not supported in v1 */ + /* * Append to each key entry a string containing extension * data. This string begins with a flags word, and may in @@ -305,12 +553,14 @@ static void list_keys(BinarySink *bs, int ssh_version, bool extended) * string, so that clients that only partially understand * it can still find the parts they do understand. */ + PageantPrivateKey *priv = pub_to_priv(pub); + strbuf *sb = strbuf_new(); uint32_t flags = 0; - if (!pk->skey) + if (!priv->skey) flags |= LIST_EXTENDED_FLAG_HAS_NO_CLEARTEXT_KEY; - if (pk->encrypted_key_file) + if (priv->encrypted_key_file) flags |= LIST_EXTENDED_FLAG_HAS_ENCRYPTED_KEY_FILE; put_uint32(sb, flags); @@ -366,8 +616,8 @@ static void signop_link_to_key(PageantSignOp *so) assert(!so->pkr.prev); assert(!so->pkr.next); - so->pkr.prev = so->pk->blocked_requests.prev; - so->pkr.next = &so->pk->blocked_requests; + so->pkr.prev = so->priv->blocked_requests.prev; + so->pkr.next = &so->priv->blocked_requests; so->pkr.prev->next = &so->pkr; so->pkr.next->prev = &so->pkr; } @@ -402,19 +652,19 @@ static void signop_free(PageantAsyncOp *pao) sfree(so); } -static bool request_passphrase(PageantClient *pc, PageantKey *pk) +static bool request_passphrase(PageantClient *pc, PageantPrivateKey *priv) { - if (!pk->decryption_prompt_active) { + if (!priv->decryption_prompt_active) { assert(!gui_request_in_progress); bool created_dlg = pageant_client_ask_passphrase( - pc, &pk->dlgid, pk->comment); + pc, &priv->dlgid, priv->encrypted_key_comment); if (!created_dlg) return false; gui_request_in_progress = true; - pk->decryption_prompt_active = true; + priv->decryption_prompt_active = true; } return true; @@ -427,16 +677,16 @@ static void signop_coroutine(PageantAsyncOp *pao) crBegin(so->crLine); - while (!so->pk->skey && gui_request_in_progress) { + while (!so->priv->skey && gui_request_in_progress) { signop_link_to_pending_gui_request(so); crReturnV; signop_unlink(so); } - if (!so->pk->skey) { - assert(so->pk->encrypted_key_file); + if (!so->priv->skey) { + assert(so->priv->encrypted_key_file); - if (!request_passphrase(so->pao.info->pc, so->pk)) { + if (!request_passphrase(so->pao.info->pc, so->priv)) { response = strbuf_new(); failure(so->pao.info->pc, so->pao.reqid, response, so->failure_type, "on-demand decryption could not " @@ -449,7 +699,7 @@ static void signop_coroutine(PageantAsyncOp *pao) signop_unlink(so); } - uint32_t supported_flags = ssh_key_supported_flags(so->pk->skey->key); + uint32_t supported_flags = ssh_key_supported_flags(so->priv->skey); if (so->flags & ~supported_flags) { /* * We MUST reject any message containing flags we don't @@ -462,7 +712,7 @@ static void signop_coroutine(PageantAsyncOp *pao) goto respond; } - char *invalid = ssh_key_invalid(so->pk->skey->key, so->flags); + char *invalid = ssh_key_invalid(so->priv->skey, so->flags); if (invalid) { response = strbuf_new(); failure(so->pao.info->pc, so->pao.reqid, response, so->failure_type, @@ -472,7 +722,7 @@ static void signop_coroutine(PageantAsyncOp *pao) } strbuf *signature = strbuf_new(); - ssh_key_sign(so->pk->skey->key, ptrlen_from_strbuf(so->data_to_sign), + ssh_key_sign(so->priv->skey, ptrlen_from_strbuf(so->data_to_sign), so->flags, BinarySink_UPCAST(signature)); response = strbuf_new(); @@ -493,10 +743,10 @@ static const PageantAsyncOpVtable signop_vtable = { .free = signop_free, }; -static void fail_requests_for_key(PageantKey *pk, const char *reason) +static void fail_requests_for_key(PageantPrivateKey *priv, const char *reason) { - while (pk->blocked_requests.next != &pk->blocked_requests) { - PageantSignOp *so = container_of(pk->blocked_requests.next, + while (priv->blocked_requests.next != &priv->blocked_requests) { + PageantSignOp *so = container_of(priv->blocked_requests.next, PageantSignOp, pkr); signop_unlink(so); strbuf *sb = strbuf_new(); @@ -509,10 +759,10 @@ static void fail_requests_for_key(PageantKey *pk, const char *reason) } } -static void unblock_requests_for_key(PageantKey *pk) +static void unblock_requests_for_key(PageantPrivateKey *priv) { - for (PageantKeyRequestNode *pkr = pk->blocked_requests.next; - pkr != &pk->blocked_requests; pkr = pkr->next) { + for (PageantKeyRequestNode *pkr = priv->blocked_requests.next; + pkr != &priv->blocked_requests; pkr = pkr->next) { PageantSignOp *so = container_of(pkr, PageantSignOp, pkr); queue_toplevel_callback(pageant_async_op_callback, &so->pao); } @@ -530,35 +780,33 @@ static void unblock_pending_gui_requests(void) void pageant_passphrase_request_success(PageantClientDialogId *dlgid, ptrlen passphrase) { - PageantKey *pk = container_of(dlgid, PageantKey, dlgid); + PageantPrivateKey *priv = container_of(dlgid, PageantPrivateKey, dlgid); assert(gui_request_in_progress); gui_request_in_progress = false; - pk->decryption_prompt_active = false; + priv->decryption_prompt_active = false; - if (!pk->skey) { + if (!priv->skey) { const char *error; BinarySource src[1]; BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf( - pk->encrypted_key_file)); + priv->encrypted_key_file)); strbuf *ppsb = strbuf_dup_nm(passphrase); - pk->skey = ppk_load_s(src, ppsb->s, &error); + ssh2_userkey *skey = ppk_load_s(src, ppsb->s, &error); strbuf_free(ppsb); - if (!pk->skey) { - fail_requests_for_key(pk, "unable to decrypt key"); + if (!skey) { + fail_requests_for_key(priv, "unable to decrypt key"); return; - } else if (pk->skey == SSH2_WRONG_PASSPHRASE) { - pk->skey = NULL; - + } else if (skey == SSH2_WRONG_PASSPHRASE) { /* * Find a PageantClient to use for another attempt at * request_passphrase. */ - PageantKeyRequestNode *pkr = pk->blocked_requests.next; - if (pkr == &pk->blocked_requests) { + PageantKeyRequestNode *pkr = priv->blocked_requests.next; + if (pkr == &priv->blocked_requests) { /* * Special case: if all the requests have gone away at * this point, we need not bother putting up a request @@ -567,34 +815,37 @@ void pageant_passphrase_request_success(PageantClientDialogId *dlgid, return; } - PageantSignOp *so = container_of(pk->blocked_requests.next, + PageantSignOp *so = container_of(priv->blocked_requests.next, PageantSignOp, pkr); - pk->decryption_prompt_active = false; - if (!request_passphrase(so->pao.info->pc, pk)) { - fail_requests_for_key(pk, "unable to continue creating " + priv->decryption_prompt_active = false; + if (!request_passphrase(so->pao.info->pc, so->priv)) { + fail_requests_for_key(priv, "unable to continue creating " "passphrase prompts"); } return; } else { + priv->skey = skey->key; + sfree(skey->comment); + sfree(skey); keylist_update(); } } - unblock_requests_for_key(pk); + unblock_requests_for_key(priv); unblock_pending_gui_requests(); } void pageant_passphrase_request_refused(PageantClientDialogId *dlgid) { - PageantKey *pk = container_of(dlgid, PageantKey, dlgid); + PageantPrivateKey *priv = container_of(dlgid, PageantPrivateKey, dlgid); assert(gui_request_in_progress); gui_request_in_progress = false; - pk->decryption_prompt_active = false; + priv->decryption_prompt_active = false; - fail_requests_for_key(pk, "user refused to supply passphrase"); + fail_requests_for_key(priv, "user refused to supply passphrase"); unblock_pending_gui_requests(); } @@ -634,9 +885,11 @@ static const PageantAsyncOpVtable immop_vtable = { .free = immop_free, }; -static bool reencrypt_key(PageantKey *pk) +static bool reencrypt_key(PageantPublicKey *pub) { - if (pk->sort.ssh_version != 2) { + PageantPrivateKey *priv = pub_to_priv(pub); + + if (priv->sort.ssh_version != 2) { /* * We don't support storing SSH-1 keys in encrypted form at * all. @@ -644,7 +897,7 @@ static bool reencrypt_key(PageantKey *pk) return false; } - if (!pk->encrypted_key_file) { + if (!priv->encrypted_key_file) { /* * We can't re-encrypt a key if it doesn't have an encrypted * form. (We could make one up, of course - but with what @@ -653,14 +906,12 @@ static bool reencrypt_key(PageantKey *pk) return false; } - /* Only actually free pk->skey if it exists. But we return success + /* Only actually free priv->skey if it exists. But we return success * regardless, so that 'please ensure this key isn't stored * decrypted' is idempotent. */ - if (pk->skey) { - sfree(pk->skey->comment); - ssh_key_free(pk->skey->key); - sfree(pk->skey); - pk->skey = NULL; + if (priv->skey) { + ssh_key_free(priv->skey); + priv->skey = NULL; } return true; @@ -704,9 +955,10 @@ static PageantAsyncOp *pageant_make_op( "reply: SSH1_AGENT_RSA_IDENTITIES_ANSWER"); if (!pc->suppress_logging) { int i; - PageantKey *pk; - for (i = 0; NULL != (pk = pageant_nth_key(1, i)); i++) { - char *fingerprint = rsa_ssh1_fingerprint(pk->rkey); + PageantPublicKey *pub; + for (i = 0; NULL != (pub = pageant_nth_pubkey(1, i)); i++) { + PageantPrivateKey *priv = pub_to_priv(pub); + char *fingerprint = rsa_ssh1_fingerprint(priv->rkey); pageant_client_log(pc, reqid, "returned key: %s", fingerprint); sfree(fingerprint); @@ -727,12 +979,12 @@ static PageantAsyncOp *pageant_make_op( pageant_client_log(pc, reqid, "reply: SSH2_AGENT_IDENTITIES_ANSWER"); if (!pc->suppress_logging) { int i; - PageantKey *pk; - for (i = 0; NULL != (pk = pageant_nth_key(2, i)); i++) { + PageantPublicKey *pub; + for (i = 0; NULL != (pub = pageant_nth_pubkey(2, i)); i++) { char *fingerprint = ssh2_double_fingerprint_blob( - ptrlen_from_strbuf(pk->public_blob), SSH_FPTYPE_DEFAULT); + pub->sort.full_pub, SSH_FPTYPE_DEFAULT); pageant_client_log(pc, reqid, "returned key: %s %s", - fingerprint, pk->comment); + fingerprint, pub->comment); sfree(fingerprint); } } @@ -745,7 +997,8 @@ static PageantAsyncOp *pageant_make_op( * or not. */ RSAKey reqkey; - PageantKey *pk; + PageantPublicKey *pub; + PageantPrivateKey *priv; mp_int *challenge, *response; ptrlen session_id; unsigned response_type; @@ -779,11 +1032,12 @@ static PageantAsyncOp *pageant_make_op( sfree(fingerprint); } - if ((pk = findkey1(&reqkey)) == NULL) { + if ((pub = findpubkey1(&reqkey)) == NULL) { fail("key not found"); goto challenge1_cleanup; } - response = rsa_ssh1_decrypt(challenge, pk->rkey); + priv = pub_to_priv(pub); + response = rsa_ssh1_decrypt(challenge, priv->rkey); { ssh_hash *h = ssh_hash_new(&ssh_md5); @@ -811,7 +1065,7 @@ static PageantAsyncOp *pageant_make_op( * SSH_AGENT_FAILURE, depending on whether we have that key * or not. */ - PageantKey *pk; + PageantPublicKey *pub; ptrlen keyblob, sigdata; uint32_t flags; @@ -844,7 +1098,7 @@ static PageantAsyncOp *pageant_make_op( pageant_client_log(pc, reqid, "requested key: %s", fingerprint); sfree(fingerprint); } - if ((pk = findkey2(keyblob)) == NULL) { + if ((pub = findpubkey2(keyblob)) == NULL) { fail("key not found"); goto responded; } @@ -864,7 +1118,7 @@ static PageantAsyncOp *pageant_make_op( so->pao.cr.next = &pc->info->head; so->pao.cr.prev->next = so->pao.cr.next->prev = &so->pao.cr; so->pao.reqid = reqid; - so->pk = pk; + so->priv = pub_to_priv(pub); so->pkr.prev = so->pkr.next = NULL; so->data_to_sign = strbuf_dup(sigdata); so->flags = flags; @@ -989,7 +1243,7 @@ static PageantAsyncOp *pageant_make_op( * start with. */ RSAKey reqkey; - PageantKey *pk; + PageantPublicKey *pub; pageant_client_log(pc, reqid, "request: SSH1_AGENTC_REMOVE_RSA_IDENTITY"); @@ -1011,15 +1265,15 @@ static PageantAsyncOp *pageant_make_op( sfree(fingerprint); } - pk = findkey1(&reqkey); + pub = findpubkey1(&reqkey); freersakey(&reqkey); - if (pk) { + if (pub) { pageant_client_log(pc, reqid, "found with comment: %s", - pk->rkey->comment); + pub->comment); - del234(keytree, pk); + del_pubkey(pub); keylist_update(); - pk_free(pk); + pk_pub_free(pub); put_byte(sb, SSH_AGENT_SUCCESS); pageant_client_log(pc, reqid, "reply: SSH_AGENT_SUCCESS"); @@ -1034,7 +1288,7 @@ static PageantAsyncOp *pageant_make_op( * perhaps SSH_AGENT_FAILURE if it wasn't in the list to * start with. */ - PageantKey *pk; + PageantPublicKey *pub; ptrlen blob; pageant_client_log(pc, reqid, "request: SSH2_AGENTC_REMOVE_IDENTITY"); @@ -1053,17 +1307,17 @@ static PageantAsyncOp *pageant_make_op( sfree(fingerprint); } - pk = findkey2(blob); - if (!pk) { + pub = findpubkey2(blob); + if (!pub) { fail("key not found"); goto responded; } - pageant_client_log(pc, reqid, "found with comment: %s", pk->comment); + pageant_client_log(pc, reqid, "found with comment: %s", pub->comment); - del234(keytree, pk); + del_pubkey(pub); keylist_update(); - pk_free(pk); + pk_pub_free(pub); put_byte(sb, SSH_AGENT_SUCCESS); pageant_client_log(pc, reqid, "reply: SSH_AGENT_SUCCESS"); @@ -1146,14 +1400,16 @@ static PageantAsyncOp *pageant_make_op( goto responded; } + strbuf *base_pub = NULL; + strbuf *full_pub = NULL; BinarySource src[1]; const char *error; - strbuf *public_blob = strbuf_new(); + full_pub = strbuf_new(); char *comment; BinarySource_BARE_INIT_PL(src, keyfile); - if (!ppk_loadpub_s(src, NULL, BinarySink_UPCAST(public_blob), + if (!ppk_loadpub_s(src, NULL, BinarySink_UPCAST(full_pub), &comment, &error)) { fail("failed to extract public key blob: %s", error); goto add_ppk_cleanup; @@ -1161,7 +1417,7 @@ static PageantAsyncOp *pageant_make_op( if (!pc->suppress_logging) { char *fingerprint = ssh2_double_fingerprint_blob( - ptrlen_from_strbuf(public_blob), SSH_FPTYPE_DEFAULT); + ptrlen_from_strbuf(full_pub), SSH_FPTYPE_DEFAULT); pageant_client_log(pc, reqid, "add-ppk: %s %s", fingerprint, comment); sfree(fingerprint); @@ -1194,55 +1450,21 @@ static PageantAsyncOp *pageant_make_op( goto add_ppk_cleanup; } - PageantKeySort sort = - keysort(2, ptrlen_from_strbuf(public_blob)); - - PageantKey *pk = find234(keytree, &sort, NULL); - if (pk) { - /* - * This public key blob already exists in the - * keytree. Add the encrypted key file to the - * existing record, if it doesn't have one already. - */ - if (!pk->encrypted_key_file) { - pk->encrypted_key_file = strbuf_dup_nm(keyfile); - - keylist_update(); - put_byte(sb, SSH_AGENT_SUCCESS); - pageant_client_log( - pc, reqid, "reply: SSH_AGENT_SUCCESS (added encrypted" - " PPK to existing key record)"); - } else { - fail("key already present"); - } - } else { - /* - * We're adding a new key record containing only - * an encrypted key file. - */ - PageantKey *pk = snew(PageantKey); - memset(pk, 0, sizeof(PageantKey)); - pk->blocked_requests.next = pk->blocked_requests.prev = - &pk->blocked_requests; - pk->sort.ssh_version = 2; - pk->public_blob = public_blob; - public_blob = NULL; - pk->sort.public_blob = ptrlen_from_strbuf(pk->public_blob); - pk->comment = dupstr(comment); - pk->encrypted_key_file = strbuf_dup_nm(keyfile); - - PageantKey *added = add234(keytree, pk); - assert(added == pk); (void)added; + PageantPublicKeySort sort; + sort.priv.ssh_version = 2; + sort.full_pub = ptrlen_from_strbuf(full_pub); + base_pub = make_base_pub_2(&sort); - keylist_update(); - put_byte(sb, SSH_AGENT_SUCCESS); - pageant_client_log(pc, reqid, "reply: SSH_AGENT_SUCCESS (made" - " new encrypted-only key record)"); - } + pageant_add_ssh2_key_encrypted(sort, comment, keyfile); + keylist_update(); + put_byte(sb, SSH_AGENT_SUCCESS); + pageant_client_log(pc, reqid, "reply: SSH_AGENT_SUCCESS"); - add_ppk_cleanup: - if (public_blob) - strbuf_free(public_blob); + add_ppk_cleanup: + if (full_pub) + strbuf_free(full_pub); + if (base_pub) + strbuf_free(base_pub); sfree(comment); break; } @@ -1270,16 +1492,16 @@ static PageantAsyncOp *pageant_make_op( sfree(fingerprint); } - PageantKey *pk = findkey2(blob); - if (!pk) { + PageantPublicKey *pub = findpubkey2(blob); + if (!pub) { fail("key not found"); goto responded; } pageant_client_log(pc, reqid, - "found with comment: %s", pk->comment); + "found with comment: %s", pub->comment); - if (!reencrypt_key(pk)) { + if (!reencrypt_key(pub)) { fail("this key couldn't be re-encrypted"); goto responded; } @@ -1306,10 +1528,10 @@ static PageantAsyncOp *pageant_make_op( * having made a state change.) */ unsigned nfailures = 0, nsuccesses = 0; - PageantKey *pk; + PageantPublicKey *pub; - for (int i = 0; (pk = index234(keytree, i)) != NULL; i++) { - if (reencrypt_key(pk)) + for (int i = 0; (pub = index234(pubkeytree, i)) != NULL; i++) { + if (reencrypt_key(pub)) nsuccesses++; else nfailures++; @@ -1346,13 +1568,13 @@ static PageantAsyncOp *pageant_make_op( "reply: SSH2_AGENT_SUCCESS + key list"); if (!pc->suppress_logging) { int i; - PageantKey *pk; - for (i = 0; NULL != (pk = pageant_nth_key(2, i)); i++) { + PageantPublicKey *pub; + for (i = 0; NULL != (pub = pageant_nth_pubkey(2, i)); i++) { char *fingerprint = ssh2_double_fingerprint_blob( - ptrlen_from_strbuf(pk->public_blob), + ptrlen_from_strbuf(pub->full_pub), SSH_FPTYPE_DEFAULT); pageant_client_log(pc, reqid, "returned key: %s %s", - fingerprint, pk->comment); + fingerprint, pub->comment); sfree(fingerprint); } } @@ -1394,43 +1616,47 @@ void pageant_handle_msg(PageantClient *pc, PageantClientRequestId *reqid, void pageant_init(void) { pageant_local = true; - keytree = newtree234(cmpkeys); + pubkeytree = newtree234(pubkey_cmpfn); + privkeytree = newtree234(privkey_cmpfn); } -static PageantKey *pageant_nth_key(int ssh_version, int i) +static PageantPublicKey *pageant_nth_pubkey(int ssh_version, int i) { - PageantKey *pk = index234( - keytree, find_first_key_for_version(ssh_version) + i); - if (pk && pk->sort.ssh_version == ssh_version) - return pk; + PageantPublicKey *pub = index234( + pubkeytree, find_first_pubkey_for_version(ssh_version) + i); + if (pub && pub->sort.priv.ssh_version == ssh_version) + return pub; else return NULL; } bool pageant_delete_nth_ssh1_key(int i) { - PageantKey *pk = delpos234(keytree, find_first_key_for_version(1) + i); - if (!pk) + PageantPublicKey *pub = del_pubkey_pos( + find_first_pubkey_for_version(1) + i); + if (!pub) return false; - pk_free(pk); + pk_pub_free(pub); return true; } bool pageant_delete_nth_ssh2_key(int i) { - PageantKey *pk = delpos234(keytree, find_first_key_for_version(2) + i); - if (!pk) + PageantPublicKey *pub = del_pubkey_pos( + find_first_pubkey_for_version(2) + i); + if (!pub) return false; - pk_free(pk); + pk_pub_free(pub); return true; } bool pageant_reencrypt_nth_ssh2_key(int i) { - PageantKey *pk = index234(keytree, find_first_key_for_version(2) + i); - if (!pk) + PageantPublicKey *pub = index234( + pubkeytree, find_first_pubkey_for_version(2) + i); + if (!pub) return false; - return reencrypt_key(pk); + return reencrypt_key(pub); } void pageant_delete_all(void) @@ -1441,9 +1667,9 @@ void pageant_delete_all(void) void pageant_reencrypt_all(void) { - PageantKey *pk; - for (int i = 0; (pk = index234(keytree, i)) != NULL; i++) - reencrypt_key(pk); + PageantPublicKey *pub; + for (int i = 0; (pub = index234(pubkeytree, i)) != NULL; i++) + reencrypt_key(pub); } /* ---------------------------------------------------------------------- diff --git a/ssh.h b/ssh.h index bd2e6961..56eb7049 100644 --- a/ssh.h +++ b/ssh.h @@ -597,6 +597,7 @@ bool rsa_verify(RSAKey *key); void rsa_ssh1_public_blob(BinarySink *bs, RSAKey *key, RsaSsh1Order order); int rsa_ssh1_public_blob_len(ptrlen data); void rsa_ssh1_private_blob_agent(BinarySink *bs, RSAKey *key); +void duprsakey(RSAKey *dst, const RSAKey *src); void freersapriv(RSAKey *key); void freersakey(RSAKey *key); key_components *rsa_components(RSAKey *key); -- cgit v1.2.3 From 42bbb58e1b5d8be1a49f6cc9d874528db176e09c Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 7 Aug 2022 18:35:11 +0100 Subject: Remove redundant setup of host key prompt help contexts. We're now setting the help context centrally in ssh/common.c - but I forgot to remove the _old_ assignment statements, which overwrite whatever that asks for. Oops. --- windows/dialog.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/windows/dialog.c b/windows/dialog.c index 98540201..5e49cca9 100644 --- a/windows/dialog.c +++ b/windows/dialog.c @@ -974,7 +974,6 @@ static INT_PTR HostKeyDialogProc(HWND hwnd, UINT msg, const char *dlg_title = ""; ctx->has_title = false; LPCTSTR iconid = IDI_QUESTION; - ctx->helpctx = WINHELP_CTX_errors_hostkey_absent; for (SeatDialogTextItem *item = ctx->text->items, *end = item + ctx->text->nitems; item < end; item++) { @@ -988,7 +987,6 @@ static INT_PTR HostKeyDialogProc(HWND hwnd, UINT msg, case SDT_SCARY_HEADING: SetDlgItemText(hwnd, IDC_HK_TITLE, item->text); iconid = IDI_WARNING; - ctx->helpctx = WINHELP_CTX_errors_hostkey_changed; ctx->has_title = true; break; case SDT_TITLE: -- cgit v1.2.3 From 426901b891c15ccac7d1d8fbcbb0cab50d27ed8b Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 7 Aug 2022 18:41:57 +0100 Subject: Formatting: another handful of mis-indented labels. These were indented 2 spaces _further_ than the surrounding code, instead of 2 spaces less. My bulk-reindentation the other day didn't detect them because apparently my Emacs configuration can make this mistake all by itself, so it thought they were right! --- pageant.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pageant.c b/pageant.c index 269dd005..455e434f 100644 --- a/pageant.c +++ b/pageant.c @@ -1052,7 +1052,7 @@ static PageantAsyncOp *pageant_make_op( pageant_client_log(pc, reqid, "reply: SSH1_AGENT_RSA_RESPONSE"); - challenge1_cleanup: + challenge1_cleanup: if (response) mp_free(response); mp_free(challenge); @@ -1165,7 +1165,7 @@ static PageantAsyncOp *pageant_make_op( fail("key already present"); } - add1_cleanup: + add1_cleanup: if (key) { freersakey(key); sfree(key); @@ -1226,7 +1226,7 @@ static PageantAsyncOp *pageant_make_op( fail("key already present"); } - add2_cleanup: + add2_cleanup: if (key) { if (key->key) ssh_key_free(key->key); -- cgit v1.2.3 From e52087719c4e185e433f7b5b1fb8383b4d910c2e Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 7 Aug 2022 12:06:36 +0100 Subject: Documentation for OpenSSH certificates. Also I've filled in the help contexts in all the new GUI controls. --- config.c | 5 +- doc/config.but | 164 +++++++++++++++++++++++++++++++++++++++++++++++++++ doc/errors.but | 33 +++++++++++ doc/index.but | 5 ++ doc/man-pageant.but | 6 ++ doc/man-puttygen.but | 36 ++++++++++- doc/pageant.but | 9 +++ doc/pubkey.but | 42 +++++++++++++ doc/using.but | 22 +++++++ ssh/ca-config.c | 31 +++++----- ssh/common.c | 9 +-- windows/help.h | 5 ++ 12 files changed, 346 insertions(+), 21 deletions(-) diff --git a/config.c b/config.c index f77384d5..59b6976a 100644 --- a/config.c +++ b/config.c @@ -2835,7 +2835,8 @@ void setup_config_box(struct controlbox *b, bool midsession, s = ctrl_getset(b, "Connection/SSH/Host keys", "ca", "Configure trusted certification authorities"); c = ctrl_pushbutton(s, "Configure host CAs", NO_SHORTCUT, - HELPCTX(no_help), host_ca_button_handler, I(0)); + HELPCTX(ssh_kex_cert), + host_ca_button_handler, I(0)); if (!midsession || !(protcfginfo == 1 || protcfginfo == -1)) { /* @@ -2909,7 +2910,7 @@ void setup_config_box(struct controlbox *b, bool midsession, conf_filesel_handler, I(CONF_keyfile)); ctrl_filesel(s, "Certificate to use with the private key:", 'e', NULL, false, "Select certificate file", - HELPCTX(ssh_auth_privkey), + HELPCTX(ssh_auth_cert), conf_filesel_handler, I(CONF_detached_cert)); #ifndef NO_GSSAPI diff --git a/doc/config.but b/doc/config.but index 66d57711..28da9638 100644 --- a/doc/config.but +++ b/doc/config.but @@ -2610,6 +2610,142 @@ neither read \e{nor written}, unless you explicitly do so. If the box is empty (as it usually is), then PuTTY's automated host key management will work as normal. +\S{config-ssh-kex-cert} Configuring PuTTY to accept host \i{certificates} + +In some environments, the SSH host keys for a lot of servers will all +be signed in turn by a central \q{certification authority} (\q{CA} for +short). This simplifies host key configuration for users, because if +they configure their SSH client to accept host keys certified by that +CA, then they don't need to individually confirm each host key the +first time they connect to that server. + +In order to do this, press the \q{Configure host CAs} button in the +\q{Host keys} configuration panel. This will launch a secondary +configuration dialog box where you can configure what CAs PuTTY will +accept signatures from. + +\s{Note that this configuration is common to all saved sessions}. +Everything in the main PuTTY configuration is specific to one saved +session, and you can prepare a separate session with all the +configuration different. But there's only one copy of the host CA +configuration, and it applies to all sessions PuTTY runs, whether +saved or not. + +(Otherwise, it would be useless \dash configuring a CA by hand for +each new host wouldn't be any more convenient than pressing the +\q{confirm} button for each new host's host key.) + +To set up a new CA using this config box: + +First, load the CA's public key from a file, or paste it directly into +the \q{Public key of certification authority} edit box. If your +organisation signs its host keys in this way, they will publish the +public key of their CA so that SSH users can include it in their +configuration. + +Next, in the \q{Valid hosts this key is trusted to certify} box, +configure at least one hostname wildcard to say what servers PuTTY +should trust this CA to speak for. For example, suppose you work for +Example Corporation (\cw{example.com}), and the Example Corporation IT +department has advertised a CA that signs all the Example internal +machines' host keys. Then probably you want to trust that CA to sign +host keys for machines in the domain \cw{example.com}, but not for +anything else. So you might enter \cq{*.example.com} into the \q{Valid +hosts} box. + +\s{It's important to limit what the CA key is allowed to sign}. Don't +just enter \cq{*} in that box! If you do that, you're saying that +Example Corporation IT department is authorised to sign a host key for +\e{anything at all} you might decide to connect to \dash even if +you're connecting out of the company network to a machine somewhere +else, such as your own personal server. So that configuration would +enable the Example IT department to act as a \q{man-in-the-middle} +between your PuTTY process and your server, and listen in to your +communications \dash exactly the thing SSH is supposed to avoid. + +So, if the CA was provided to you by the sysadmins responsible for +\cw{example.com} (or whatever), make sure PuTTY will \e{only} trust it +for machines in the \cw{example.com} domain. + +For the full syntax of the \q{Valid hosts} expression, see +\k{config-ssh-cert-valid-expr}. + +Finally, choose an identifying name for this CA; enter that name in +the \q{Name for this CA} edit box at the top of the window, and press +\q{Save} to record the CA in your configuration. The name you chose +will appear in the list of saved CAs to the left of the \q{Save} +button. + +The identifying name can be anything you like. It's there so that if +you store multiple certificates you can tell which is which later when +you want to edit or delete them. It also appears in the PuTTY Event +Log when a server presents a certificate signed by that CA. + +To reload an existing CA configuration, select it in the list box and +press \q{Load}. Then you can make changes, and save it again. + +To remove a CA from your configuration completely, select it in the +list and press \q{Delete}. + +\S2{config-ssh-cert-valid-expr} Expressions you can enter in \q{Valid +hosts} + +The simplest thing you can enter in the \q{Valid hosts this key is +trusted to certify} edit box is just a hostname wildcard such as +\cq{*.example.com}. But you can also enter multiple host name +wildcards, and port number ranges, and make complicated Boolean +expressions out of them using the operators \cq{&&} for \q{and}, +\cq{||} for \q{or}, \cq{!} for \q{not}, and parentheses. + +For example, here are some other things you could enter. + +\b \cq{*.foo.example.com || *.bar.example.com}. This means the CA is +trusted to sign the host key for a connection if the host name matches +\q{*.foo.example.com} \e{or} it matches \q{*.bar.example.com}. In +other words, the CA has authority over those two particular subdomains +of \cw{example.com}, but not for anything else, like +\cw{www.example.com}. + +\b \cq{*.example.com && ! *.extrasecure.example.com}. This means the +CA is trusted to sign the host key for a connection if the host name +matches \q{*.example.com} \e{but does not} match +\q{*.extrasecure.example.com}. (Imagine if there was one top-secret +set of servers in your company that the main IT department didn't have +security clearance to administer.) + +\b \cq{*.example.com && port:22}. This means the CA is trusted to sign +the host key for a connection if the host name matches +\q{*.example.com} \e{and} the port number is 22. SSH servers running +on other ports would not be covered. + +\b \cq{(*.foo.example.com || *.bar.example.com) && port:0-1023}. This +matches two subdomains of \cw{example.com}, as before, but \e{also} +restricts the port number to the range 0-1023. + +A certificate configuration expression consists of one or more +individual requirements which can each be a hostname wildcard, a +single port number, or a port number range, combined together with +these Boolean operators. + +Unlike other languages such as C, there is no implied priority between +\cq{&&} and \cq{||}. If you write \cq{A && B || C} (where \cw{A}, +\cw{B} and \cw{C} are some particular requirements), then PuTTY will +report a syntax error, because you haven't said which of the \cq{&&} +and \cq{||} takes priority tightly. You will have to write either +\cq{(A && B) || C}, meaning \q{both of \cw{A} and \cw{B}, or +alternatively just \cw{C}}, or \cq{A && (B || C)} (\q{\cw{A}, and also +at least one of \cw{B} and \cw{C}}), to make it clear. + +\S2{config-ssh-cert-rsa-hash} RSA signature types in certificates + +RSA keys can be used to generate signatures with a choice of secure +hash function. Typically, any version of OpenSSH new enough to support +certificates at all will also be new enough to avoid using SHA-1, so +the default settings of accepting the more modern SHA-256 and SHA-512 +should be suitable for nearly all cases. For completeness, however, +you can configure which types of RSA signature PuTTY will accept in a +certificate from a CA using an RSA key. + \H{config-ssh-encryption} The Cipher panel PuTTY supports a variety of different \i{encryption algorithm}s, and @@ -2849,6 +2985,34 @@ in this case (in RFC 4716 or OpenSSH format), as that's sufficient to identify the key to Pageant, but of course if Pageant isn't present PuTTY can't fall back to using this file itself. +\S{config-ssh-cert} \q{\ii{Certificate} to use with the private key} + +In some environments, user authentication keys can be signed in turn +by a \q{certifying authority} (\q{CA} for short), and user accounts on +an SSH server can be configured to automatically trust any key that's +certified by the right signature. + +This can be a convenient setup if you have a very large number of +servers. When you change your key pair, you might otherwise have to +edit the \cw{authorized_keys} file on every server individually, to +make them all accept the new key. But if instead you configure all +those servers \e{once} to accept keys signed as yours by a CA, then +when you change your public key, all you have to do is to get the new +key certified by the same CA as before, and then all your servers will +automatically accept it without needing individual reconfiguration. + +One way to use a certificate is to incorporate it into your private +key file. \K{puttygen-cert} explains how to do that using PuTTYgen. +But another approach is to tell PuTTY itself where to find the public +certificate file, and then it will automatically present that +certificate when authenticating with the corresponding private key. + +To do this, enter the pathname of the certificate file into the +\q{Certificate to use with the private key} file selector. + +When this setting is configured, PuTTY will honour it no matter +whether the private key is found in a file, or loaded into Pageant. + \H{config-ssh-auth-gssapi} The \i{GSSAPI} panel The \q{GSSAPI} subpanel of the \q{Auth} panel controls the use of diff --git a/doc/errors.but b/doc/errors.but index 27e2ae32..cea3201c 100644 --- a/doc/errors.but +++ b/doc/errors.but @@ -52,6 +52,39 @@ in the same way as you would if it was new. See \k{gs-hostkey} for more information on host keys. +\H{errors-cert-mismatch} \q{This server presented a certified host key +which was signed by a different certification authority ...} + +If you've configured PuTTY to trust at least one +\I{certificate}certification authority for signing host keys (see +\k{config-ssh-kex-cert}), then it will ask the SSH server to send it +any available certified host keys. If the server sends back a +certified key signed by a \e{different} certification authority, PuTTY +will present this variant of the host key prompt. + +One reason why this can happen is a deliberate attack. Just like an +ordinary man-in-the-middle attack which substitutes a wrong host key, +a particularly ambitious attacker might substitute an entire wrong +certification authority, and hope that you connect anyway. + +But it's also possible in some situations that this error might arise +legitimately. For example, if your organisation's IT department has +just rolled out a new CA key which you haven't yet entered in PuTTY's +configuration, or if your CA configuration involves two overlapping +domains, or something similar. + +So, unfortunately, you'll have to work out what to do about it +yourself: install a new CA key (if you're really sure you trust it), +or edit your configuration in some other way, or abandon the +connection. + +If you're convinced that this particular server is legitimate even +though the CA is not one you trust, PuTTY will let you cache the +certified host key in the same way as an uncertified one. Then that +particular certificate will be accepted on the next connection, even +though other certificates signed by the same CA will still be +rejected. + \H{errors-ssh-protocol} \q{SSH protocol version 2 required by our configuration but remote only provides (old, insecure) SSH-1} diff --git a/doc/index.but b/doc/index.but index 4c76bb05..cc043d5a 100644 --- a/doc/index.but +++ b/doc/index.but @@ -942,3 +942,8 @@ saved sessions from \IM{shifted arrow keys} arrow keys, shifted \IM{shifted arrow keys} shifted arrow keys + +\IM{certificate}{certificates} certificates, SSH +\IM{certificate}{certificates} SSH certificates +\IM{certificate}{certificates} OpenSSH certificates +\IM{certificate}{certificates} CA (certification authority) diff --git a/doc/man-pageant.but b/doc/man-pageant.but index 3f407a47..d202f166 100644 --- a/doc/man-pageant.but +++ b/doc/man-pageant.but @@ -256,6 +256,12 @@ be matched \dd to indicate that it is a fingerprint of a specific format +\dt \cq{sha256-cert:} or \cq{md5-cert:} + +\dd to indicate that it is a fingerprint of a specific format, and +specifically matches the fingerprint of the public key \e{including} a +certificate if any + } \dt \cw{--public-openssh} \e{key-identifiers}, \cw{-L} \e{key-identifiers} diff --git a/doc/man-puttygen.but b/doc/man-puttygen.but index 021af205..092f05c0 100644 --- a/doc/man-puttygen.but +++ b/doc/man-puttygen.but @@ -143,6 +143,18 @@ to type). automatic when you are generating a new key, but not when you are modifying an existing key. +\dt \cw{\-\-certificate} \e{certificate-file} + +\dd Adds an OpenSSH-style certificate to the public half of the key, +so that the output file contains a certified public key with the same +private key. If the input file already contained a certificate, it +will be replaced with the new one. + +\dt \cw{\-\-remove\-certificate} + +\dd Removes any certificate that was part of the key, to recover the +uncertified version of the underlying key. + \dt \cw{\-\-reencrypt} \dd For an existing private key saved with a passphrase, refresh the @@ -260,6 +272,13 @@ newer format even for RSA, DSA, and ECDSA keys. \dd Save an SSH-2 private key in ssh.com's format. This option is not permitted for SSH-1 keys. +\dt \cw{cert-info} + +\dd Save a textual dump of information about the certificate on the +key, if any: whether it's a host or a user certificate, what host(s) +or user(s) it's certified to be, its validity period, ID and serial +number, and the fingerprint of the signing CA. + \dt \cw{text} \dd Save a textual dump of the numeric components comprising the key @@ -298,6 +317,10 @@ fingerprint. Otherwise, the \c{\-o} option is required. \dd Synonym for \q{\cw{-O public}}. +\dt \cw{\-\-cert\-info} + +\dd Synonym for \q{\cw{-O cert-info}}. + \dt \cw{\-\-dump} \dd Synonym for \q{\cw{-O text}}. @@ -305,7 +328,18 @@ fingerprint. Otherwise, the \c{\-o} option is required. \dt \cw{-E} \e{fptype} \dd Specify the algorithm to use if generating a fingerprint. The -options are \cw{sha256} (the default) and \cw{md5}. +available algorithms are are \cw{sha256} (the default) and \cw{md5}. + +\lcont{ + +By default, when showing the fingerprint of a public key that includes +a certificate, \c{puttygen} will not include the certificate, so that +the fingerprint shown will be the same as the underlying public key. +If you want the fingerprint including the certificate (for example, so +as to tell two certified keys apart), you can specify \cw{sha256-cert} +or \cw{md5-cert} as the fingerprint type. + +} \dt \cw{\-\-new\-passphrase} \e{file} diff --git a/doc/pageant.but b/doc/pageant.but index c94b88d6..206811f5 100644 --- a/doc/pageant.but +++ b/doc/pageant.but @@ -90,6 +90,15 @@ By default this is shown in the \q{SHA256} format. You can change to the older \q{MD5} format (which looks like \c{aa:bb:cc:...}) with the \q{Fingerprint type} drop-down, but bear in mind that this format is less secure and should be avoided for comparison purposes where possible. + +If some of the keys loaded into Pageant have certificates attached, +then Pageant will default to showing the fingerprint of the underlying +key. This way, a certified and uncertified version of the same key +will have the same fingerprint, so you can see that they match. You +can instead use the \q{Fingerprint type} drop-down to ask for a +different fingerprint to be shown for certified keys, which includes +the certificate as part of the fingerprinted data. That way you can +tell two certificates apart. } \b The comment attached to the key. diff --git a/doc/pubkey.but b/doc/pubkey.but index 80382076..539b55b4 100644 --- a/doc/pubkey.but +++ b/doc/pubkey.but @@ -307,6 +307,48 @@ a result. \e{Do not forget your passphrase}. There is no way to recover it. +\S{puttygen-cert} Adding a \i{certificate} to your key + +In some environments, user authentication keys can be signed in turn +by a \q{certifying authority} (\q{CA} for short), and user accounts on +an SSH server can be configured to automatically trust any key that's +certified by the right signature. + +This can be a convenient setup if you have a very large number of +servers. When you change your key pair, you might otherwise have to +edit the \cw{authorized_keys} file on every server individually, to +make them all accept the new key. But if instead you configure all +those servers \e{once} to accept keys signed as yours by a CA, then +when you change your public key, all you have to do is to get the new +key certified by the same CA as before, and then all your servers will +automatically accept it without needing individual reconfiguration. + +To get your key signed by a CA, you'll probably send the CA the new +\e{public} key (not the private half), and get back a modified version +of the public key with the certificate included. + +If you want to incorporate the certificate into your PPK file for +convenience, you can use the \q{Add certificate to key} menu option in +PuTTYgen's \q{Key} menu. This will give you a single file containing +your private key and the certificate, which is everything you need to +authenticate to a server prepared to accept that certificate. + +To remove the certificate again and restore the uncertified PPK file, +there's also a \q{Remove certificate from key} option. + +(However, you don't \e{have} to incorporate the certificate into your +PPK file. You can equally well use it separately, via the +\q{Certificate to use with the private key} option in PuTTY itself. +See \k{config-ssh-cert}. It's up to you which you find more +convenient.) + +When the currently loaded key in PuTTYgen contains a certificate, the +large \q{Public key for pasting} edit box (see \k{puttygen-pastekey}) +is replaced by a button that brings up an information box telling you +about the certificate, such as who it certifies your key as belonging +to, when it expires (if ever), and the fingerprint of the CA key that +signed it in turn. + \S{puttygen-savepriv} Saving your private key to a disk file Once you have generated a key, set a comment field and set a diff --git a/doc/using.but b/doc/using.but index d5cf41c4..5865ac95 100644 --- a/doc/using.but +++ b/doc/using.but @@ -1024,6 +1024,19 @@ 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-cert} \i\c{-cert}: specify an SSH \i{certificate} + +The \c{-cert} option allows you to specify the name of a certificate +file containing a signed version of your public key. If you specify +this option, PuTTY will present that certificate in place of the plain +public key, whenever it tries to authenticate with a key that matches. +(This applies whether the key is stored in Pageant or loaded directly +from a file by PuTTY.) + +This option is equivalent to the \q{Certificate to use with the +private key} box in the Auth panel of the PuTTY configuration box (see +\k{config-ssh-cert}). + \S2{using-cmdline-no-trivial-auth} \i\c{-no-trivial-auth}: disconnect if SSH authentication succeeds trivially @@ -1162,3 +1175,12 @@ the extra protection), so it's reasonable to want to run Pageant but not PuTTY with the ACL restrictions. You can force Pageant to start subsidiary PuTTY processes with a restricted ACL if you also pass the \i\c{-restrict-putty-acl} option. + +\S2{using-cmdline-host-ca} \i{\c{-host-ca}}: launch the +\I{certificate}host CA configuration + +If you start PuTTY with the \c{-host-ca} option, it will not launch a +session at all. Instead, it will just display the configuration dialog +box for host certification authorities, as described in +\k{config-ssh-kex-cert}. When you dismiss that dialog box, PuTTY will +terminate. diff --git a/ssh/ca-config.c b/ssh/ca-config.c index 350613df..8c180b36 100644 --- a/ssh/ca-config.c +++ b/ssh/ca-config.c @@ -412,7 +412,7 @@ void setup_ca_config_box(struct controlbox *b) /* Action area, with the Done button in it */ s = ctrl_getset(b, "", "", ""); ctrl_columns(s, 5, 20, 20, 20, 20, 20); - c = ctrl_pushbutton(s, "Done", 'o', HELPCTX(no_help), + c = ctrl_pushbutton(s, "Done", 'o', HELPCTX(ssh_kex_cert), ca_ok_handler, P(st)); c->button.iscancel = true; c->column = 4; @@ -422,7 +422,7 @@ void setup_ca_config_box(struct controlbox *b) "Load, save or delete a host CA record"); ctrl_columns(s, 2, 75, 25); c = ctrl_editbox(s, "Name for this CA (shown in log messages)", - 'n', 100, HELPCTX(no_help), + 'n', 100, HELPCTX(ssh_kex_cert), ca_name_handler, P(st), P(NULL)); c->column = 0; st->ca_name_edit = c; @@ -430,18 +430,18 @@ void setup_ca_config_box(struct controlbox *b) * than alongside that edit box. */ ctrl_columns(s, 1, 100); ctrl_columns(s, 2, 75, 25); - c = ctrl_listbox(s, NULL, NO_SHORTCUT, HELPCTX(no_help), + c = ctrl_listbox(s, NULL, NO_SHORTCUT, HELPCTX(ssh_kex_cert), ca_reclist_handler, P(st)); c->column = 0; c->listbox.height = 6; st->ca_reclist = c; - c = ctrl_pushbutton(s, "Load", 'l', HELPCTX(no_help), + c = ctrl_pushbutton(s, "Load", 'l', HELPCTX(ssh_kex_cert), ca_load_handler, P(st)); c->column = 1; - c = ctrl_pushbutton(s, "Save", 'v', HELPCTX(no_help), + c = ctrl_pushbutton(s, "Save", 'v', HELPCTX(ssh_kex_cert), ca_save_handler, P(st)); c->column = 1; - c = ctrl_pushbutton(s, "Delete", 'd', HELPCTX(no_help), + c = ctrl_pushbutton(s, "Delete", 'd', HELPCTX(ssh_kex_cert), ca_delete_handler, P(st)); c->column = 1; @@ -449,42 +449,45 @@ void setup_ca_config_box(struct controlbox *b) ctrl_columns(s, 2, 75, 25); c = ctrl_editbox(s, "Public key of certification authority", 'k', 100, - HELPCTX(no_help), ca_pubkey_edit_handler, P(st), P(NULL)); + HELPCTX(ssh_kex_cert), ca_pubkey_edit_handler, + P(st), P(NULL)); c->column = 0; st->ca_pubkey_edit = c; c = ctrl_filesel(s, "Read from file", NO_SHORTCUT, NULL, false, "Select public key file of certification authority", - HELPCTX(no_help), ca_pubkey_file_handler, P(st)); + HELPCTX(ssh_kex_cert), ca_pubkey_file_handler, P(st)); c->fileselect.just_button = true; c->align_next_to = st->ca_pubkey_edit; c->column = 1; ctrl_columns(s, 1, 100); - st->ca_pubkey_info = c = ctrl_text(s, " ", HELPCTX(no_help)); + st->ca_pubkey_info = c = ctrl_text(s, " ", HELPCTX(ssh_kex_cert)); c->text.wrap = false; s = ctrl_getset(b, "Main", "options", "What this CA is trusted to do"); c = ctrl_editbox(s, "Valid hosts this key is trusted to certify", 'h', 100, - HELPCTX(no_help), ca_validity_handler, P(st), P(NULL)); + HELPCTX(ssh_cert_valid_expr), ca_validity_handler, + P(st), P(NULL)); st->ca_validity_edit = c; ctrl_columns(s, 4, 44, 18, 18, 18); - c = ctrl_text(s, "Signature types (RSA keys only):", HELPCTX(no_help)); + c = ctrl_text(s, "Signature types (RSA keys only):", + HELPCTX(ssh_cert_rsa_hash)); c->column = 0; dlgcontrol *sigtypelabel = c; - c = ctrl_checkbox(s, "SHA-1", NO_SHORTCUT, HELPCTX(no_help), + c = ctrl_checkbox(s, "SHA-1", NO_SHORTCUT, HELPCTX(ssh_cert_rsa_hash), ca_rsa_type_handler, P(st)); c->column = 1; c->align_next_to = sigtypelabel; c->context2 = I(offsetof(ca_options, permit_rsa_sha1)); st->rsa_type_checkboxes[0] = c; - c = ctrl_checkbox(s, "SHA-256", NO_SHORTCUT, HELPCTX(no_help), + c = ctrl_checkbox(s, "SHA-256", NO_SHORTCUT, HELPCTX(ssh_cert_rsa_hash), ca_rsa_type_handler, P(st)); c->column = 2; c->align_next_to = sigtypelabel; c->context2 = I(offsetof(ca_options, permit_rsa_sha256)); st->rsa_type_checkboxes[1] = c; - c = ctrl_checkbox(s, "SHA-512", NO_SHORTCUT, HELPCTX(no_help), + c = ctrl_checkbox(s, "SHA-512", NO_SHORTCUT, HELPCTX(ssh_cert_rsa_hash), ca_rsa_type_handler, P(st)); c->column = 3; c->align_next_to = sigtypelabel; diff --git a/ssh/common.c b/ssh/common.c index fa5d48d0..161bebbd 100644 --- a/ssh/common.c +++ b/ssh/common.c @@ -934,6 +934,8 @@ SeatPromptResult verify_ssh_host_key( seat_dialog_text_append( text, SDT_TITLE, "%s Security Alert", appname); + HelpCtx helpctx; + if (key && ssh_key_alg(key)->is_certificate) { seat_dialog_text_append( text, SDT_SCARY_HEADING, "WARNING - POTENTIAL SECURITY BREACH!"); @@ -978,6 +980,7 @@ SeatPromptResult verify_ssh_host_key( text, SDT_PARA, "The new %s key fingerprint is:", keytype); seat_dialog_text_append( text, SDT_DISPLAY, "%s", fingerprints[fptype_default]); + helpctx = HELPCTX(errors_cert_mismatch); } else if (storage_status == 1) { seat_dialog_text_append( text, SDT_PARA, "The host key is not cached for this server:"); @@ -990,6 +993,7 @@ SeatPromptResult verify_ssh_host_key( text, SDT_PARA, "The server's %s key fingerprint is:", keytype); seat_dialog_text_append( text, SDT_DISPLAY, "%s", fingerprints[fptype_default]); + helpctx = HELPCTX(errors_hostkey_absent); } else { seat_dialog_text_append( text, SDT_SCARY_HEADING, "WARNING - POTENTIAL SECURITY BREACH!"); @@ -1006,6 +1010,7 @@ SeatPromptResult verify_ssh_host_key( text, SDT_PARA, "The new %s key fingerprint is:", keytype); seat_dialog_text_append( text, SDT_DISPLAY, "%s", fingerprints[fptype_default]); + helpctx = HELPCTX(errors_hostkey_changed); } /* The above text is printed even in batch mode. Here's where we stop if @@ -1013,8 +1018,6 @@ SeatPromptResult verify_ssh_host_key( seat_dialog_text_append( text, SDT_BATCH_ABORT, "Connection abandoned."); - HelpCtx helpctx; - if (storage_status == 1) { seat_dialog_text_append( text, SDT_PARA, "If you trust this host, %s to add the key to " @@ -1029,7 +1032,6 @@ SeatPromptResult verify_ssh_host_key( "connection.", pds->hk_cancel_action); seat_dialog_text_append( text, SDT_PROMPT, "Store key in cache?"); - helpctx = HELPCTX(errors_hostkey_absent); } else { seat_dialog_text_append( text, SDT_PARA, "If you were expecting this change and trust the " @@ -1044,7 +1046,6 @@ SeatPromptResult verify_ssh_host_key( pds->hk_cancel_action, pds->hk_cancel_action_Participle); seat_dialog_text_append( text, SDT_PROMPT, "Update cached key?"); - helpctx = HELPCTX(errors_hostkey_changed); } seat_dialog_text_append(text, SDT_MORE_INFO_KEY, diff --git a/windows/help.h b/windows/help.h index d0f76496..cdd55a11 100644 --- a/windows/help.h +++ b/windows/help.h @@ -113,10 +113,14 @@ typedef const char *HelpCtx; #define WINHELP_CTX_ssh_gssapi_kex_delegation "config-ssh-kex-gssapi-delegation" #define WINHELP_CTX_ssh_kex_repeat "config-ssh-kex-rekey" #define WINHELP_CTX_ssh_kex_manual_hostkeys "config-ssh-kex-manual-hostkeys" +#define WINHELP_CTX_ssh_kex_cert "config-ssh-kex-cert" +#define WINHELP_CTX_ssh_cert_valid_expr "config-ssh-cert-valid-expr" +#define WINHELP_CTX_ssh_cert_rsa_hash "config-ssh-cert-rsa-hash" #define WINHELP_CTX_ssh_auth_bypass "config-ssh-noauth" #define WINHELP_CTX_ssh_no_trivial_userauth "config-ssh-notrivialauth" #define WINHELP_CTX_ssh_auth_banner "config-ssh-banner" #define WINHELP_CTX_ssh_auth_privkey "config-ssh-privkey" +#define WINHELP_CTX_ssh_auth_cert "config-ssh-cert" #define WINHELP_CTX_ssh_auth_agentfwd "config-ssh-agentfwd" #define WINHELP_CTX_ssh_auth_changeuser "config-ssh-changeuser" #define WINHELP_CTX_ssh_auth_pageant "config-ssh-tryagent" @@ -193,6 +197,7 @@ typedef const char *HelpCtx; #define WINHELP_CTX_puttygen_conversions "puttygen-conversions" #define WINHELP_CTX_puttygen_ppkver "puttygen-save-ppk-version" #define WINHELP_CTX_puttygen_kdfparam "puttygen-save-passphrase-hashing" +#define WINHELP_CTX_errors_cert_mismatch "errors-cert-mismatch" /* These are used in Windows-specific bits of the frontend. * We (ab)use "help context identifiers" (dwContextId) to identify them. */ -- cgit v1.2.3 From dbc77dbd7a247957602aa8d23bdf92dff5a4f35a Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Tue, 16 Aug 2022 18:22:29 +0100 Subject: Change the rules for how we free a linked cipher and MAC. In the situation where a MAC and cipher implementation are tied together by being facets of the same underlying object (used by the inseparable ChaCha20 + Poly1305 pair), previously we freed them by having the cipher_free function actually do the freeing, having the mac_free function do nothing, and taking great care to call those in the right order. (Otherwise, the mac_free function dereferences a no-longer-valid vtable pointer and doesn't get as far as _finding out_ that it doesn't have to do anything.) That's a time bomb in general, and especially awkward in situations like testcrypt where we don't get precise control over freeing order in any case. So I've replaced that system with one in which there are two flags in the ChaCha20-Poly1305 structure, saying whether each of the cipher and MAC facets is currently considered to be allocated. When the last of those flags is cleared, the object is actually freed. So now they can be freed in either order. --- crypto/chacha20-poly1305.c | 25 ++++++++++++++++++++----- ssh/bpp2.c | 9 --------- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/crypto/chacha20-poly1305.c b/crypto/chacha20-poly1305.c index dd25b997..f06f7dfe 100644 --- a/crypto/chacha20-poly1305.c +++ b/crypto/chacha20-poly1305.c @@ -869,6 +869,7 @@ struct ccp_context { BinarySink_IMPLEMENTATION; ssh_cipher ciph; ssh2_mac mac_if; + bool ciph_allocated, mac_allocated; }; static ssh2_mac *poly_ssh2_new( @@ -876,13 +877,27 @@ static ssh2_mac *poly_ssh2_new( { struct ccp_context *ctx = container_of(cipher, struct ccp_context, ciph); ctx->mac_if.vt = alg; + ctx->mac_allocated = true; BinarySink_DELEGATE_INIT(&ctx->mac_if, ctx); return &ctx->mac_if; } +static void ccp_common_free(struct ccp_context *ctx) +{ + if (ctx->ciph_allocated || ctx->mac_allocated) + return; + + smemclr(&ctx->a_cipher, sizeof(ctx->a_cipher)); + smemclr(&ctx->b_cipher, sizeof(ctx->b_cipher)); + smemclr(&ctx->mac, sizeof(ctx->mac)); + sfree(ctx); +} + static void poly_ssh2_free(ssh2_mac *mac) { - /* Not allocated, just forwarded, no need to free */ + struct ccp_context *ctx = container_of(mac, struct ccp_context, mac_if); + ctx->mac_allocated = false; + ccp_common_free(ctx); } static void poly_setkey(ssh2_mac *mac, ptrlen key) @@ -963,16 +978,16 @@ static ssh_cipher *ccp_new(const ssh_cipheralg *alg) BinarySink_INIT(ctx, poly_BinarySink_write); poly1305_init(&ctx->mac); ctx->ciph.vt = alg; + ctx->ciph_allocated = true; + ctx->mac_allocated = false; return &ctx->ciph; } static void ccp_free(ssh_cipher *cipher) { struct ccp_context *ctx = container_of(cipher, struct ccp_context, ciph); - smemclr(&ctx->a_cipher, sizeof(ctx->a_cipher)); - smemclr(&ctx->b_cipher, sizeof(ctx->b_cipher)); - smemclr(&ctx->mac, sizeof(ctx->mac)); - sfree(ctx); + ctx->ciph_allocated = false; + ccp_common_free(ctx); } static void ccp_iv(ssh_cipher *cipher, const void *iv) diff --git a/ssh/bpp2.c b/ssh/bpp2.c index dc98e27c..240101e3 100644 --- a/ssh/bpp2.c +++ b/ssh/bpp2.c @@ -73,15 +73,6 @@ BinaryPacketProtocol *ssh2_bpp_new( static void ssh2_bpp_free_outgoing_crypto(struct ssh2_bpp_state *s) { - /* - * We must free the MAC before the cipher, because sometimes the - * MAC is not actually separately allocated but just a different - * facet of the same object as the cipher, in which case - * ssh2_mac_free does nothing and ssh_cipher_free does the actual - * freeing. So if we freed the cipher first and then tried to - * dereference the MAC's vtable pointer to find out how to free - * that too, we'd be accessing freed memory. - */ if (s->out.mac) ssh2_mac_free(s->out.mac); if (s->out.cipher) -- cgit v1.2.3 From 48708def8430e8ae346bfb9a33a92c1cff60ebe7 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Tue, 16 Aug 2022 18:23:15 +0100 Subject: testcrypt: fix cut-and-paste goof in decrypt_length. The length test was pasted from the ordinary decrypt function, when it should have been pasted from encrypt_length (which got this right). I've never tried to test those functions before, so I never noticed. --- test/testcrypt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/testcrypt.c b/test/testcrypt.c index cb4e6b81..de09af33 100644 --- a/test/testcrypt.c +++ b/test/testcrypt.c @@ -791,7 +791,7 @@ strbuf *ssh_cipher_encrypt_length_wrapper(ssh_cipher *c, ptrlen input, strbuf *ssh_cipher_decrypt_length_wrapper(ssh_cipher *c, ptrlen input, unsigned long seq) { - if (input.len % ssh_cipher_alg(c)->blksize) + if (input.len != 4) fatal_error("ssh_cipher_decrypt_length: needs exactly 4 bytes"); strbuf *sb = strbuf_dup(input); ssh_cipher_decrypt_length(c, sb->u, sb->len, seq); -- cgit v1.2.3 From 3198995ef38769a518fc00e5569f691bfb1f08f1 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Tue, 16 Aug 2022 18:23:52 +0100 Subject: cryptsuite: add a test of ChaCha20-Poly1305. Not a very profound test, but it's at least enough to answer the question 'is it still returning the same results?' after I change things. --- test/cryptsuite.py | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/test/cryptsuite.py b/test/cryptsuite.py index 04450dd0..35114a16 100755 --- a/test/cryptsuite.py +++ b/test/cryptsuite.py @@ -1799,6 +1799,66 @@ class crypt(MyTestBase): ssh_cipher_decrypt(cipher, iv[:ivlen]) self.assertEqualBin(ssh_cipher_decrypt(cipher, c), p) + def testChaCha20Poly1305(self): + # A test case of this cipher taken from a real connection to + # OpenSSH. + key = unhex('49e67c5ae596ea7f230e266538d0e373' + '177cc8fe08ff7b642c22d736ca975655' + 'c3fb639010fd297ca03c36b20a182ef4' + '0e1272f0c54251c175546ee00b150805') + len_p = unhex('00000128') + len_c = unhex('3ff3677b') + msg_p = unhex('0807000000020000000f736572766572' + '2d7369672d616c6773000000db737368' + '2d656432353531392c736b2d7373682d' + '65643235353139406f70656e7373682e' + '636f6d2c7373682d7273612c7273612d' + '736861322d3235362c7273612d736861' + '322d3531322c7373682d6473732c6563' + '6473612d736861322d6e697374703235' + '362c65636473612d736861322d6e6973' + '74703338342c65636473612d73686132' + '2d6e697374703532312c736b2d656364' + '73612d736861322d6e69737470323536' + '406f70656e7373682e636f6d2c776562' + '617574686e2d736b2d65636473612d73' + '6861322d6e69737470323536406f7065' + '6e7373682e636f6d0000001f7075626c' + '69636b65792d686f7374626f756e6440' + '6f70656e7373682e636f6d0000000130' + 'c34aaefcafae6fc2') + msg_c = unhex('bf587eabf385b1281fa9c755d8515dfd' + 'c40cb5e993b346e722dce48b1741b4e5' + 'ce9ae075f6df0a1d2f72f94f73570125' + '7011630bbb0c7febd767184c0d5aa810' + '47cbce82972129a234b8ac5fc5f2b5be' + '9264baca6d13ff3c9813a61e1f23468f' + '31964b60fc3f0888a227f02c737b2d27' + 'b7ae3cd60ede17533863a5bb6bb2d60a' + 'c998ccd27e8ba56259f676ed04749fad' + '4114678fb871add3a40625110637947c' + 'e91459811622fd3d1fa7eb7efad4b1e8' + '97f3e860473935d3d8df0679a8b0df85' + 'aa4124f2d9ac7207abd10719f465c9ed' + '859d2b03bde55315b9024f660ba8d63a' + '64e0beb81e532201df830a52cf221484' + '18d0c4c7da242346161d7320ac534cb5' + 'c6b6fec905ee5f424becb9f97c3afbc5' + '5ef4ba369e61bce847158f0dc5bd7227' + '3b8693642db36f87') + mac = unhex('09757178642dfc9f2c38ac5999e0fcfd') + seqno = 3 + c = ssh_cipher_new('chacha20_poly1305') + m = ssh2_mac_new('poly1305', c) + c.setkey(key) + self.assertEqualBin(c.encrypt_length(len_p, seqno), len_c) + self.assertEqualBin(c.encrypt(msg_p), msg_c) + m.start() + m.update(ssh_uint32(seqno) + len_c + msg_c) + self.assertEqualBin(m.genresult(), mac) + self.assertEqualBin(c.decrypt_length(len_c, seqno), len_p) + self.assertEqualBin(c.decrypt(msg_c), msg_p) + def testRSAKex(self): # Round-trip test of the RSA key exchange functions, plus a # hardcoded plain/ciphertext pair to guard against the -- cgit v1.2.3 From 83ecb0729600cf4eed2fdcdaefd18870317410f9 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Tue, 16 Aug 2022 18:15:59 +0100 Subject: sclog: add a 'project' line in CMakeLists.txt. This causes cmake to stop whinging that there isn't one. More usefully, by specifying the LANGUAGES keyword as just C (rather than the default of both C and CXX), the cmake configure step is sped up by not having to faff about finding a C++ compiler. --- test/sclog/CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/sclog/CMakeLists.txt b/test/sclog/CMakeLists.txt index 6ac0ddfa..8a8ac570 100644 --- a/test/sclog/CMakeLists.txt +++ b/test/sclog/CMakeLists.txt @@ -4,6 +4,8 @@ cmake_minimum_required(VERSION 3.5) +project(sclog LANGUAGES C) + find_package(DynamoRIO) if (NOT DynamoRIO_FOUND) message(FATAL_ERROR "DynamoRIO not found") -- cgit v1.2.3 From 99dd370503b3ab86028544d0bc36b2ff59ab3675 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Tue, 16 Aug 2022 18:24:20 +0100 Subject: testsc: fix memory leak in test_ntru. We forgot to free the key pair at the end of the test, which is harmless except that it makes Leak Sanitiser complain loudly. --- test/testsc.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/testsc.c b/test/testsc.c index 55a64aba..85f7de21 100644 --- a/test/testsc.c +++ b/test/testsc.c @@ -1610,6 +1610,8 @@ static void test_ntru(void) ntru_encode_plaintext(plaintext, p, BinarySink_UPCAST(buffer)); log_end(); + ntru_keypair_free(keypair); + break; } -- cgit v1.2.3 From 3b9cbaca8e416b2dd4309bc97504dd2c2ee3d5a1 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Tue, 16 Aug 2022 18:25:21 +0100 Subject: testsc: refactor platform-specific conditionalisation. Instead of having separate subsidiary list macros for all the AES-NI or NEON accelerated ciphers, the main list macro now contains each individual thing conditionalised under an IF_FOO macro defined at the top. Makes relatively little difference in the current state of things, but it will make it easier to do lots of differently conditionalised single entries in a list, which will be coming up shortly. --- test/testsc.c | 74 ++++++++++++++++++++++++++++------------------------------- 1 file changed, 35 insertions(+), 39 deletions(-) diff --git a/test/testsc.c b/test/testsc.c index 85f7de21..2e625cb1 100644 --- a/test/testsc.c +++ b/test/testsc.c @@ -248,28 +248,27 @@ VOLATILE_WRAPPED_DEFN(static, size_t, looplimit, (size_t x)) } #if HAVE_AES_NI -#define CIPHERS_AES_NI(X, Y) \ - X(Y, ssh_aes256_sdctr_ni) \ - X(Y, ssh_aes256_cbc_ni) \ - X(Y, ssh_aes192_sdctr_ni) \ - X(Y, ssh_aes192_cbc_ni) \ - X(Y, ssh_aes128_sdctr_ni) \ - X(Y, ssh_aes128_cbc_ni) \ - /* end of list */ +#define IF_AES_NI(x) x +#else +#define IF_AES_NI(x) +#endif + +#if HAVE_SHA_NI +#define IF_SHA_NI(x) x #else -#define CIPHERS_AES_NI(X, Y) +#define IF_SHA_NI(x) #endif + #if HAVE_NEON_CRYPTO -#define CIPHERS_AES_NEON(X, Y) \ - X(Y, ssh_aes256_sdctr_neon) \ - X(Y, ssh_aes256_cbc_neon) \ - X(Y, ssh_aes192_sdctr_neon) \ - X(Y, ssh_aes192_cbc_neon) \ - X(Y, ssh_aes128_sdctr_neon) \ - X(Y, ssh_aes128_cbc_neon) \ - /* end of list */ +#define IF_NEON_CRYPTO(x) x +#else +#define IF_NEON_CRYPTO(x) +#endif + +#if HAVE_NEON_SHA512 +#define IF_NEON_SHA512(x) x #else -#define CIPHERS_AES_NEON(X, Y) +#define IF_NEON_SHA512(x) #endif /* Ciphers that we expect to pass this test. Blowfish and Arcfour are @@ -292,8 +291,18 @@ VOLATILE_WRAPPED_DEFN(static, size_t, looplimit, (size_t x)) X(Y, ssh_aes192_cbc_sw) \ X(Y, ssh_aes128_sdctr_sw) \ X(Y, ssh_aes128_cbc_sw) \ - CIPHERS_AES_NI(X, Y) \ - CIPHERS_AES_NEON(X, Y) \ + IF_AES_NI(X(Y, ssh_aes256_sdctr_ni)) \ + IF_AES_NI(X(Y, ssh_aes256_cbc_ni)) \ + IF_AES_NI(X(Y, ssh_aes192_sdctr_ni)) \ + IF_AES_NI(X(Y, ssh_aes192_cbc_ni)) \ + IF_AES_NI(X(Y, ssh_aes128_sdctr_ni)) \ + IF_AES_NI(X(Y, ssh_aes128_cbc_ni)) \ + IF_NEON_CRYPTO(X(Y, ssh_aes256_sdctr_neon)) \ + IF_NEON_CRYPTO(X(Y, ssh_aes256_cbc_neon)) \ + IF_NEON_CRYPTO(X(Y, ssh_aes192_sdctr_neon)) \ + IF_NEON_CRYPTO(X(Y, ssh_aes192_cbc_neon)) \ + IF_NEON_CRYPTO(X(Y, ssh_aes128_sdctr_neon)) \ + IF_NEON_CRYPTO(X(Y, ssh_aes128_cbc_neon)) \ X(Y, ssh2_chacha20_poly1305) \ /* end of list */ @@ -310,22 +319,6 @@ VOLATILE_WRAPPED_DEFN(static, size_t, looplimit, (size_t x)) #define MAC_TESTLIST(X, name) X(mac_ ## name) -#if HAVE_SHA_NI -#define HASH_SHA_NI(X, Y) X(Y, ssh_sha256_ni) X(Y, ssh_sha1_ni) -#else -#define HASH_SHA_NI(X, Y) -#endif -#if HAVE_NEON_CRYPTO -#define HASH_SHA_NEON(X, Y) X(Y, ssh_sha256_neon) X(Y, ssh_sha1_neon) -#else -#define HASH_SHA_NEON(X, Y) -#endif -#if HAVE_NEON_SHA512 -#define HASH_SHA512_NEON(X, Y) X(Y, ssh_sha384_neon) X(Y, ssh_sha512_neon) -#else -#define HASH_SHA512_NEON(X, Y) -#endif - #define HASHES(X, Y) \ X(Y, ssh_md5) \ X(Y, ssh_sha1) \ @@ -336,9 +329,12 @@ VOLATILE_WRAPPED_DEFN(static, size_t, looplimit, (size_t x)) X(Y, ssh_sha512) \ X(Y, ssh_sha384_sw) \ X(Y, ssh_sha512_sw) \ - HASH_SHA_NI(X, Y) \ - HASH_SHA_NEON(X, Y) \ - HASH_SHA512_NEON(X, Y) \ + IF_SHA_NI(X(Y, ssh_sha256_ni)) \ + IF_SHA_NI(X(Y, ssh_sha1_ni)) \ + IF_NEON_CRYPTO(X(Y, ssh_sha256_neon)) \ + IF_NEON_CRYPTO(X(Y, ssh_sha1_neon)) \ + IF_NEON_SHA512(X(Y, ssh_sha384_neon)) \ + IF_NEON_SHA512(X(Y, ssh_sha512_neon)) \ X(Y, ssh_sha3_224) \ X(Y, ssh_sha3_256) \ X(Y, ssh_sha3_384) \ -- cgit v1.2.3 From 9160c41e7bd1cd265d70e43b62a6902b004f927d Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Tue, 16 Aug 2022 18:26:28 +0100 Subject: testsc: add side-channel test of Poly1305. Not sure how I missed this! I tested ChaCha20, but not the MAC that goes with it. Happily, it passes, so no harm done. This also involved adding a general framework for testing MACs that are tied to a specific cipher: we have to allocate, key and IV the cipher before attempting to use the MAC, and free it all afterwards. --- test/testsc.c | 47 +++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/test/testsc.c b/test/testsc.c index 2e625cb1..6068dd86 100644 --- a/test/testsc.c +++ b/test/testsc.c @@ -308,7 +308,7 @@ VOLATILE_WRAPPED_DEFN(static, size_t, looplimit, (size_t x)) #define CIPHER_TESTLIST(X, name) X(cipher_ ## name) -#define MACS(X, Y) \ +#define SIMPLE_MACS(X, Y) \ X(Y, ssh_hmac_md5) \ X(Y, ssh_hmac_sha1) \ X(Y, ssh_hmac_sha1_buggy) \ @@ -317,6 +317,11 @@ VOLATILE_WRAPPED_DEFN(static, size_t, looplimit, (size_t x)) X(Y, ssh_hmac_sha256) \ /* end of list */ +#define ALL_MACS(X, Y) \ + SIMPLE_MACS(X, Y) \ + X(Y, poly1305) \ + /* end of list */ + #define MAC_TESTLIST(X, name) X(mac_ ## name) #define HASHES(X, Y) \ @@ -388,7 +393,7 @@ VOLATILE_WRAPPED_DEFN(static, size_t, looplimit, (size_t x)) X(ecc_edwards_get_affine) \ X(ecc_edwards_decompress) \ CIPHERS(CIPHER_TESTLIST, X) \ - MACS(MAC_TESTLIST, X) \ + ALL_MACS(MAC_TESTLIST, X) \ HASHES(HASH_TESTLIST, X) \ X(argon2) \ X(primegen_probabilistic) \ @@ -1402,20 +1407,37 @@ static void test_cipher(const ssh_cipheralg *calg) static void test_cipher_##cipher(void) { test_cipher(&cipher); } CIPHERS(CIPHER_TESTFN, Y_unused) -static void test_mac(const ssh2_macalg *malg) +static void test_mac(const ssh2_macalg *malg, const ssh_cipheralg *calg) { - ssh2_mac *m = ssh2_mac_new(malg, NULL); + ssh_cipher *c = NULL; + if (calg) { + c = ssh_cipher_new(calg); + if (!c) { + test_skipped = true; + return; + } + } + + ssh2_mac *m = ssh2_mac_new(malg, c); if (!m) { test_skipped = true; + if (c) + ssh_cipher_free(c); return; } + size_t ckeylen = calg ? calg->padded_keybytes : 0; + size_t civlen = calg ? calg->blksize : 0; + uint8_t *ckey = snewn(ckeylen, uint8_t); + uint8_t *civ = snewn(civlen, uint8_t); uint8_t *mkey = snewn(malg->keylen, uint8_t); size_t datalen = 256; size_t maclen = malg->len; uint8_t *data = snewn(datalen + maclen, uint8_t); for (size_t i = 0; i < looplimit(16); i++) { + random_read(ckey, ckeylen); + random_read(civ, civlen); random_read(mkey, malg->keylen); random_read(data, datalen); uint8_t seqbuf[4]; @@ -1423,20 +1445,33 @@ static void test_mac(const ssh2_macalg *malg) uint32_t seq = GET_32BIT_MSB_FIRST(seqbuf); log_start(); + if (c) { + ssh_cipher_setkey(c, ckey); + ssh_cipher_setiv(c, civ); + } ssh2_mac_setkey(m, make_ptrlen(mkey, malg->keylen)); ssh2_mac_generate(m, data, datalen, seq); ssh2_mac_verify(m, data, datalen, seq); log_end(); } + sfree(ckey); + sfree(civ); sfree(mkey); sfree(data); ssh2_mac_free(m); + if (c) + ssh_cipher_free(c); } #define MAC_TESTFN(Y_unused, mac) \ - static void test_mac_##mac(void) { test_mac(&mac); } -MACS(MAC_TESTFN, Y_unused) + static void test_mac_##mac(void) { test_mac(&mac, NULL); } +SIMPLE_MACS(MAC_TESTFN, Y_unused) + +static void test_mac_poly1305(void) +{ + test_mac(&ssh2_poly1305, &ssh2_chacha20_poly1305); +} static void test_hash(const ssh_hashalg *halg) { -- cgit v1.2.3 From 840043f06e5f67adfdf3ccd16f433bbcc2f989b7 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Tue, 16 Aug 2022 18:27:06 +0100 Subject: Add 'next_message' methods to cipher and MAC vtables. This provides a convenient hook to be called between SSH messages, for the crypto components to do any per-message processing like incrementing a sequence number. --- crypto/aes.h | 2 ++ crypto/arcfour.c | 2 ++ crypto/blowfish.c | 3 +++ crypto/chacha20-poly1305.c | 2 ++ crypto/des.c | 5 +++++ crypto/hmac.c | 6 ++++++ ssh.h | 12 ++++++++++++ ssh/bpp2.c | 8 ++++++++ test/testcrypt-func.h | 2 ++ utils/CMakeLists.txt | 2 ++ utils/nullcipher.c | 11 +++++++++++ utils/nullmac.c | 11 +++++++++++ 12 files changed, 66 insertions(+) create mode 100644 utils/nullcipher.c create mode 100644 utils/nullmac.c diff --git a/crypto/aes.h b/crypto/aes.h index 1960713a..433306ab 100644 --- a/crypto/aes.h +++ b/crypto/aes.h @@ -52,6 +52,7 @@ static inline bool check_availability(const struct aes_extra *extra) .setkey = aes ## impl_c ## _setkey, \ .encrypt = aes ## bits ## impl_c ## _cbc_encrypt, \ .decrypt = aes ## bits ## impl_c ## _cbc_decrypt, \ + .next_message = nullcipher_next_message, \ .ssh2_id = "aes" #bits "-cbc", \ .blksize = 16, \ .real_keybits = bits, \ @@ -69,6 +70,7 @@ static inline bool check_availability(const struct aes_extra *extra) .setkey = aes ## impl_c ## _setkey, \ .encrypt = aes ## bits ## impl_c ## _sdctr, \ .decrypt = aes ## bits ## impl_c ## _sdctr, \ + .next_message = nullcipher_next_message, \ .ssh2_id = "aes" #bits "-ctr", \ .blksize = 16, \ .real_keybits = bits, \ diff --git a/crypto/arcfour.c b/crypto/arcfour.c index 53821473..87d59022 100644 --- a/crypto/arcfour.c +++ b/crypto/arcfour.c @@ -110,6 +110,7 @@ const ssh_cipheralg ssh_arcfour128_ssh2 = { .setkey = arcfour_ssh2_setkey, .encrypt = arcfour_ssh2_block, .decrypt = arcfour_ssh2_block, + .next_message = nullcipher_next_message, .ssh2_id = "arcfour128", .blksize = 1, .real_keybits = 128, @@ -125,6 +126,7 @@ const ssh_cipheralg ssh_arcfour256_ssh2 = { .setkey = arcfour_ssh2_setkey, .encrypt = arcfour_ssh2_block, .decrypt = arcfour_ssh2_block, + .next_message = nullcipher_next_message, .ssh2_id = "arcfour256", .blksize = 1, .real_keybits = 256, diff --git a/crypto/blowfish.c b/crypto/blowfish.c index 07b989ed..a4e44652 100644 --- a/crypto/blowfish.c +++ b/crypto/blowfish.c @@ -654,6 +654,7 @@ const ssh_cipheralg ssh_blowfish_ssh1 = { .setkey = blowfish_ssh_setkey, .encrypt = blowfish_ssh1_encrypt_blk, .decrypt = blowfish_ssh1_decrypt_blk, + .next_message = nullcipher_next_message, .blksize = 8, .real_keybits = 128, .padded_keybytes = SSH1_SESSION_KEY_LENGTH, @@ -668,6 +669,7 @@ const ssh_cipheralg ssh_blowfish_ssh2 = { .setkey = blowfish_ssh_setkey, .encrypt = blowfish_ssh2_encrypt_blk, .decrypt = blowfish_ssh2_decrypt_blk, + .next_message = nullcipher_next_message, .ssh2_id = "blowfish-cbc", .blksize = 8, .real_keybits = 128, @@ -683,6 +685,7 @@ const ssh_cipheralg ssh_blowfish_ssh2_ctr = { .setkey = blowfish_ssh_setkey, .encrypt = blowfish_ssh2_sdctr, .decrypt = blowfish_ssh2_sdctr, + .next_message = nullcipher_next_message, .ssh2_id = "blowfish-ctr", .blksize = 8, .real_keybits = 256, diff --git a/crypto/chacha20-poly1305.c b/crypto/chacha20-poly1305.c index f06f7dfe..d765e7fc 100644 --- a/crypto/chacha20-poly1305.c +++ b/crypto/chacha20-poly1305.c @@ -964,6 +964,7 @@ const ssh2_macalg ssh2_poly1305 = { .setkey = poly_setkey, .start = poly_start, .genresult = poly_genresult, + .next_message = nullmac_next_message, .text_name = poly_text_name, .name = "", .etm_name = "", /* Not selectable individually, just part of @@ -1061,6 +1062,7 @@ const ssh_cipheralg ssh2_chacha20_poly1305 = { .decrypt = ccp_decrypt, .encrypt_length = ccp_encrypt_length, .decrypt_length = ccp_decrypt_length, + .next_message = nullcipher_next_message, // FIXME: can we use this? .ssh2_id = "chacha20-poly1305@openssh.com", .blksize = 1, .real_keybits = 512, diff --git a/crypto/des.c b/crypto/des.c index 045b00c0..1cbec8fa 100644 --- a/crypto/des.c +++ b/crypto/des.c @@ -689,6 +689,7 @@ const ssh_cipheralg ssh_des = { .setkey = des_cbc_setkey, .encrypt = des_cbc_encrypt, .decrypt = des_cbc_decrypt, + .next_message = nullcipher_next_message, .ssh2_id = "des-cbc", .blksize = 8, .real_keybits = 56, @@ -705,6 +706,7 @@ const ssh_cipheralg ssh_des_sshcom_ssh2 = { .setkey = des_cbc_setkey, .encrypt = des_cbc_encrypt, .decrypt = des_cbc_decrypt, + .next_message = nullcipher_next_message, .ssh2_id = "des-cbc@ssh.com", .blksize = 8, .real_keybits = 56, @@ -808,6 +810,7 @@ const ssh_cipheralg ssh_3des_ssh2 = { .setkey = des3_cbc1_setkey, .encrypt = des3_cbc1_cbc_encrypt, .decrypt = des3_cbc1_cbc_decrypt, + .next_message = nullcipher_next_message, .ssh2_id = "3des-cbc", .blksize = 8, .real_keybits = 168, @@ -905,6 +908,7 @@ const ssh_cipheralg ssh_3des_ssh2_ctr = { .setkey = des3_sdctr_setkey, .encrypt = des3_sdctr_encrypt_decrypt, .decrypt = des3_sdctr_encrypt_decrypt, + .next_message = nullcipher_next_message, .ssh2_id = "3des-ctr", .blksize = 8, .real_keybits = 168, @@ -1040,6 +1044,7 @@ const ssh_cipheralg ssh_3des_ssh1 = { .setkey = des3_cbc3_setkey, .encrypt = des3_cbc3_cbc_encrypt, .decrypt = des3_cbc3_cbc_decrypt, + .next_message = nullcipher_next_message, .blksize = 8, .real_keybits = 168, .padded_keybytes = 24, diff --git a/crypto/hmac.c b/crypto/hmac.c index f04d74b5..adeccd29 100644 --- a/crypto/hmac.c +++ b/crypto/hmac.c @@ -167,6 +167,7 @@ const ssh2_macalg ssh_hmac_sha256 = { .setkey = hmac_key, .start = hmac_start, .genresult = hmac_genresult, + .next_message = nullmac_next_message, .text_name = hmac_text_name, .name = "hmac-sha2-256", .etm_name = "hmac-sha2-256-etm@openssh.com", @@ -182,6 +183,7 @@ const ssh2_macalg ssh_hmac_md5 = { .setkey = hmac_key, .start = hmac_start, .genresult = hmac_genresult, + .next_message = nullmac_next_message, .text_name = hmac_text_name, .name = "hmac-md5", .etm_name = "hmac-md5-etm@openssh.com", @@ -198,6 +200,7 @@ const ssh2_macalg ssh_hmac_sha1 = { .setkey = hmac_key, .start = hmac_start, .genresult = hmac_genresult, + .next_message = nullmac_next_message, .text_name = hmac_text_name, .name = "hmac-sha1", .etm_name = "hmac-sha1-etm@openssh.com", @@ -214,6 +217,7 @@ const ssh2_macalg ssh_hmac_sha1_96 = { .setkey = hmac_key, .start = hmac_start, .genresult = hmac_genresult, + .next_message = nullmac_next_message, .text_name = hmac_text_name, .name = "hmac-sha1-96", .etm_name = "hmac-sha1-96-etm@openssh.com", @@ -232,6 +236,7 @@ const ssh2_macalg ssh_hmac_sha1_buggy = { .setkey = hmac_key, .start = hmac_start, .genresult = hmac_genresult, + .next_message = nullmac_next_message, .text_name = hmac_text_name, .name = "hmac-sha1", .len = 20, @@ -249,6 +254,7 @@ const ssh2_macalg ssh_hmac_sha1_96_buggy = { .setkey = hmac_key, .start = hmac_start, .genresult = hmac_genresult, + .next_message = nullmac_next_message, .text_name = hmac_text_name, .name = "hmac-sha1-96", .len = 12, diff --git a/ssh.h b/ssh.h index 56eb7049..e14b44c6 100644 --- a/ssh.h +++ b/ssh.h @@ -651,6 +651,9 @@ struct ssh_cipheralg { unsigned long seq); void (*decrypt_length)(ssh_cipher *, void *blk, int len, unsigned long seq); + /* For ciphers that update their state per logical message + * (typically, per unit independently MACed) */ + void (*next_message)(ssh_cipher *); const char *ssh2_id; int blksize; /* real_keybits is the number of bits of entropy genuinely used by @@ -695,9 +698,13 @@ static inline void ssh_cipher_encrypt_length( static inline void ssh_cipher_decrypt_length( ssh_cipher *c, void *blk, int len, unsigned long seq) { c->vt->decrypt_length(c, blk, len, seq); } +static inline void ssh_cipher_next_message(ssh_cipher *c) +{ c->vt->next_message(c); } static inline const struct ssh_cipheralg *ssh_cipher_alg(ssh_cipher *c) { return c->vt; } +void nullcipher_next_message(ssh_cipher *); + struct ssh2_ciphers { int nciphers; const ssh_cipheralg *const *list; @@ -715,6 +722,7 @@ struct ssh2_macalg { void (*setkey)(ssh2_mac *, ptrlen key); void (*start)(ssh2_mac *); void (*genresult)(ssh2_mac *, unsigned char *); + void (*next_message)(ssh2_mac *); const char *(*text_name)(ssh2_mac *); const char *name, *etm_name; int len, keylen; @@ -734,6 +742,8 @@ static inline void ssh2_mac_start(ssh2_mac *m) { m->vt->start(m); } static inline void ssh2_mac_genresult(ssh2_mac *m, unsigned char *out) { m->vt->genresult(m, out); } +static inline void ssh2_mac_next_message(ssh2_mac *m) +{ m->vt->next_message(m); } static inline const char *ssh2_mac_text_name(ssh2_mac *m) { return m->vt->text_name(m); } static inline const ssh2_macalg *ssh2_mac_alg(ssh2_mac *m) @@ -746,6 +756,8 @@ bool ssh2_mac_verresult(ssh2_mac *, const void *); void ssh2_mac_generate(ssh2_mac *, void *, int, unsigned long seq); bool ssh2_mac_verify(ssh2_mac *, const void *, int, unsigned long seq); +void nullmac_next_message(ssh2_mac *m); + /* Use a MAC in its raw form, outside SSH-2 context, to MAC a given * string with a given key in the most obvious way. */ void mac_simple(const ssh2_macalg *alg, ptrlen key, ptrlen data, void *output); diff --git a/ssh/bpp2.c b/ssh/bpp2.c index 240101e3..a3ab99f9 100644 --- a/ssh/bpp2.c +++ b/ssh/bpp2.c @@ -513,6 +513,10 @@ static void ssh2_bpp_handle_input(BinaryPacketProtocol *bpp) dts_consume(&s->stats->in, s->packetlen); s->pktin->sequence = s->in.sequence++; + if (s->in.cipher) + ssh_cipher_next_message(s->in.cipher); + if (s->in.mac) + ssh2_mac_next_message(s->in.mac); s->length = s->packetlen - s->pad; assert(s->length >= 0); @@ -819,6 +823,10 @@ static void ssh2_bpp_format_packet_inner(struct ssh2_bpp_state *s, PktOut *pkt) } s->out.sequence++; /* whether or not we MACed */ + if (s->out.cipher) + ssh_cipher_next_message(s->out.cipher); + if (s->out.mac) + ssh2_mac_next_message(s->out.mac); dts_consume(&s->stats->out, origlen + padding); } diff --git a/test/testcrypt-func.h b/test/testcrypt-func.h index 02b2eb4d..f79c966e 100644 --- a/test/testcrypt-func.h +++ b/test/testcrypt-func.h @@ -273,6 +273,7 @@ FUNC(val_mac, ssh2_mac_new, ARG(macalg, alg), ARG(opt_val_cipher, cipher)) FUNC(void, ssh2_mac_setkey, ARG(val_mac, m), ARG(val_string_ptrlen, key)) FUNC(void, ssh2_mac_start, ARG(val_mac, m)) FUNC(void, ssh2_mac_update, ARG(val_mac, m), ARG(val_string_ptrlen, data)) +FUNC(void, ssh2_mac_next_message, ARG(val_mac, m)) FUNC_WRAPPED(val_string, ssh2_mac_genresult, ARG(val_mac, m)) FUNC(val_string_asciz_const, ssh2_mac_text_name, ARG(val_mac, m)) @@ -341,6 +342,7 @@ FUNC_WRAPPED(val_string, ssh_cipher_encrypt_length, ARG(val_cipher, c), ARG(val_string_ptrlen, blk), ARG(uint, seq)) FUNC_WRAPPED(val_string, ssh_cipher_decrypt_length, ARG(val_cipher, c), ARG(val_string_ptrlen, blk), ARG(uint, seq)) +FUNC(void, ssh_cipher_next_message, ARG(val_cipher, c)) /* * Integer Diffie-Hellman. diff --git a/utils/CMakeLists.txt b/utils/CMakeLists.txt index e717af49..19cbc737 100644 --- a/utils/CMakeLists.txt +++ b/utils/CMakeLists.txt @@ -42,7 +42,9 @@ add_sources_from_current_dir(utils memory.c memxor.c null_lp.c + nullcipher.c nullkey.c + nullmac.c nullseat.c nullstrcmp.c out_of_memory.c diff --git a/utils/nullcipher.c b/utils/nullcipher.c new file mode 100644 index 00000000..e11c7bbc --- /dev/null +++ b/utils/nullcipher.c @@ -0,0 +1,11 @@ +/* + * Implementation of shared trivial routines that ssh_cipher + * implementations might use. + */ + +#include "ssh.h" + +void nullcipher_next_message(ssh_cipher *cipher) +{ + /* Most ciphers don't do anything at all with this */ +} diff --git a/utils/nullmac.c b/utils/nullmac.c new file mode 100644 index 00000000..4d836704 --- /dev/null +++ b/utils/nullmac.c @@ -0,0 +1,11 @@ +/* + * Implementation of shared trivial routines that ssh2_mac + * implementations might use. + */ + +#include "ssh.h" + +void nullmac_next_message(ssh2_mac *m) +{ + /* Most MACs don't do anything at all with this */ +} -- cgit v1.2.3 From fd840f0dfef5af558f78b9c668b4b71fe64b2ff9 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Tue, 16 Aug 2022 18:39:12 +0100 Subject: Add CPU feature checks on M1 macOS. I booted my M1 Mac into macOS rather than Asahi for the first time in a while, and discovered that an OS update seems to have added some sysctl flags indicating the presence of the CPU extensions that I previously knew of no way to check for! Added them checks to arm_arch_queries.c, though I've also retained backwards compat with the previous OS version which didn't have them at all. --- unix/utils/arm_arch_queries.c | 28 +++++++++++++++++++--------- unix/utils/arm_arch_queries.h | 12 ++++++++---- 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/unix/utils/arm_arch_queries.c b/unix/utils/arm_arch_queries.c index cc3e4125..d6dc97bc 100644 --- a/unix/utils/arm_arch_queries.c +++ b/unix/utils/arm_arch_queries.c @@ -17,10 +17,11 @@ bool platform_aes_neon_available(void) #elif defined HWCAP2_AES return getauxval(AT_HWCAP2) & HWCAP2_AES; #elif defined __APPLE__ - /* M1 macOS defines no optional sysctl flag indicating presence of - * the AES extension, which I assume to be because it's always - * present */ - return true; + SysctlResult res = test_sysctl_flag("hw.optional.arm.FEAT_AES"); + /* Older M1 macOS didn't provide this flag, but as far as I know + * implemented the crypto extension anyway, so treat 'feature + * missing' as 'implemented' */ + return res != SYSCTL_OFF; #else return false; #endif @@ -33,8 +34,9 @@ bool platform_sha256_neon_available(void) #elif defined HWCAP2_SHA2 return getauxval(AT_HWCAP2) & HWCAP2_SHA2; #elif defined __APPLE__ - /* Assume always present on M1 macOS, similarly to AES */ - return true; + SysctlResult res = test_sysctl_flag("hw.optional.arm.FEAT_SHA256"); + /* As above, treat 'missing' as enabled */ + return res != SYSCTL_OFF; #else return false; #endif @@ -47,8 +49,9 @@ bool platform_sha1_neon_available(void) #elif defined HWCAP2_SHA1 return getauxval(AT_HWCAP2) & HWCAP2_SHA1; #elif defined __APPLE__ - /* Assume always present on M1 macOS, similarly to AES */ - return true; + SysctlResult res = test_sysctl_flag("hw.optional.arm.FEAT_SHA1"); + /* As above, treat 'missing' as enabled */ + return res != SYSCTL_OFF; #else return false; #endif @@ -61,7 +64,14 @@ bool platform_sha512_neon_available(void) #elif defined HWCAP2_SHA512 return getauxval(AT_HWCAP2) & HWCAP2_SHA512; #elif defined __APPLE__ - return test_sysctl_flag("hw.optional.armv8_2_sha512"); + /* There are two sysctl flags for this, apparently invented at + * different times. Try both, falling back to the older one. */ + SysctlResult res = test_sysctl_flag("hw.optional.arm.FEAT_SHA512"); + if (res != SYSCTL_MISSING) + return res == SYSCTL_ON; + + res = test_sysctl_flag("hw.optional.armv8_2_sha512"); + return res == SYSCTL_ON; #else return false; #endif diff --git a/unix/utils/arm_arch_queries.h b/unix/utils/arm_arch_queries.h index bd055687..fa46c622 100644 --- a/unix/utils/arm_arch_queries.h +++ b/unix/utils/arm_arch_queries.h @@ -49,15 +49,19 @@ static inline u_long getauxval(int which) { return 0; } #endif /* defined __arm__ || defined __aarch64__ */ #if defined __APPLE__ -static inline bool test_sysctl_flag(const char *flagname) +typedef enum { SYSCTL_MISSING, SYSCTL_OFF, SYSCTL_ON } SysctlResult; + +static inline SysctlResult test_sysctl_flag(const char *flagname) { #if HAVE_SYSCTLBYNAME int value; size_t size = sizeof(value); - return (sysctlbyname(flagname, &value, &size, NULL, 0) == 0 && - size == sizeof(value) && value != 0); + if (sysctlbyname(flagname, &value, &size, NULL, 0) == 0 && + size == sizeof(value)) { + return value != 0 ? SYSCTL_ON : SYSCTL_OFF; + } #else /* HAVE_SYSCTLBYNAME */ - return false; + return SYSCTL_MISSING; #endif /* HAVE_SYSCTLBYNAME */ } #endif /* defined __APPLE__ */ -- cgit v1.2.3 From c1a2114b28125572cf54c393bd51a6a39c4f00bd Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Tue, 16 Aug 2022 18:36:58 +0100 Subject: Implement AES-GCM using the @openssh.com protocol IDs. I only recently found out that OpenSSH defined their own protocol IDs for AES-GCM, defined to work the same as the standard ones except that they fixed the semantics for how you select the linked cipher+MAC pair during key exchange. (RFC 5647 defines protocol ids for AES-GCM in both the cipher and MAC namespaces, and requires that you MUST select both or neither - but this contradicts the selection policy set out in the base SSH RFCs, and there's no discussion of how you resolve a conflict between them! OpenSSH's answer is to do it the same way ChaCha20-Poly1305 works, because that will ensure the two suites don't fight.) People do occasionally ask us for this linked cipher/MAC pair, and now I know it's actually feasible, I've implemented it, including a pair of vector implementations for x86 and Arm using their respective architecture extensions for multiplying polynomials over GF(2). Unlike ChaCha20-Poly1305, I've kept the cipher and MAC implementations in separate objects, with an arm's-length link between them that the MAC uses when it needs to encrypt single cipher blocks to use as the inputs to the MAC algorithm. That enables the cipher and the MAC to be independently selected from their hardware-accelerated versions, just in case someone runs on a system that has polynomial multiplication instructions but not AES acceleration, or vice versa. There's a fourth implementation of the GCM MAC, which is a pure software implementation of the same algorithm used in the vectorised versions. It's too slow to use live, but I've kept it in the code for future testing needs, and because it's a convenient place to dump my design comments. The vectorised implementations are fairly crude as far as optimisation goes. I'm sure serious x86 _or_ Arm optimisation engineers would look at them and laugh. But GCM is a fast MAC compared to HMAC-SHA-256 (indeed compared to HMAC-anything-at-all), so it should at least be good enough to use. And we've got a working version with some tests now, so if someone else wants to improve them, they can. --- cmake/cmake.h.in | 2 + config.c | 1 + crypto/CMakeLists.txt | 25 +++ crypto/aes-common.c | 6 + crypto/aes-neon.c | 65 +++++++ crypto/aes-ni.c | 60 +++++++ crypto/aes-select.c | 23 ++- crypto/aes-sw.c | 95 +++++++++- crypto/aes.h | 59 ++++++- crypto/aesgcm-clmul.c | 180 +++++++++++++++++++ crypto/aesgcm-common.c | 8 + crypto/aesgcm-footer.h | 368 +++++++++++++++++++++++++++++++++++++++ crypto/aesgcm-neon.c | 156 +++++++++++++++++ crypto/aesgcm-ref-poly.c | 364 ++++++++++++++++++++++++++++++++++++++ crypto/aesgcm-select.c | 38 ++++ crypto/aesgcm-sw.c | 145 +++++++++++++++ crypto/aesgcm.h | 44 +++++ putty.h | 1 + settings.c | 1 + ssh.h | 22 +++ ssh/bpp2.c | 7 + ssh/transport2.c | 3 + test/cryptsuite.py | 355 +++++++++++++++++++++++++++++++++++++ test/list-accel.py | 4 + test/testcrypt-enum.h | 22 +++ test/testcrypt-func.h | 3 + test/testcrypt.c | 11 +- test/testsc.c | 90 +++++++++- unix/utils/arm_arch_queries.c | 15 ++ windows/utils/arm_arch_queries.c | 5 + 30 files changed, 2167 insertions(+), 11 deletions(-) create mode 100644 crypto/aesgcm-clmul.c create mode 100644 crypto/aesgcm-common.c create mode 100644 crypto/aesgcm-footer.h create mode 100644 crypto/aesgcm-neon.c create mode 100644 crypto/aesgcm-ref-poly.c create mode 100644 crypto/aesgcm-select.c create mode 100644 crypto/aesgcm-sw.c create mode 100644 crypto/aesgcm.h diff --git a/cmake/cmake.h.in b/cmake/cmake.h.in index 6ed24b51..4ce869f4 100644 --- a/cmake/cmake.h.in +++ b/cmake/cmake.h.in @@ -49,7 +49,9 @@ #cmakedefine01 HAVE_AES_NI #cmakedefine01 HAVE_SHA_NI #cmakedefine01 HAVE_SHAINTRIN_H +#cmakedefine01 HAVE_CLMUL #cmakedefine01 HAVE_NEON_CRYPTO +#cmakedefine01 HAVE_NEON_PMULL #cmakedefine01 HAVE_NEON_SHA512 #cmakedefine01 HAVE_NEON_SHA512_INTRINSICS #cmakedefine01 USE_ARM64_NEON_H diff --git a/config.c b/config.c index 59b6976a..747af814 100644 --- a/config.c +++ b/config.c @@ -491,6 +491,7 @@ static void cipherlist_handler(dlgcontrol *ctrl, dlgparam *dlg, static const struct { const char *s; int c; } ciphers[] = { { "ChaCha20 (SSH-2 only)", CIPHER_CHACHA20 }, + { "AES-GCM (SSH-2 only)", CIPHER_AESGCM }, { "3DES", CIPHER_3DES }, { "Blowfish", CIPHER_BLOWFISH }, { "DES", CIPHER_DES }, diff --git a/crypto/CMakeLists.txt b/crypto/CMakeLists.txt index 08de141e..ff04efb5 100644 --- a/crypto/CMakeLists.txt +++ b/crypto/CMakeLists.txt @@ -2,6 +2,10 @@ add_sources_from_current_dir(crypto aes-common.c aes-select.c aes-sw.c + aesgcm-common.c + aesgcm-select.c + aesgcm-sw.c + aesgcm-ref-poly.c arcfour.c argon2.c bcrypt.c @@ -123,6 +127,16 @@ if(HAVE_WMMINTRIN_H) volatile __m128i r, a, b, c; int main(void) { r = _mm_sha256rnds2_epu32(a, b, c); }" ADD_SOURCES_IF_SUCCESSFUL sha256-ni.c sha1-ni.c) + + test_compile_with_flags(HAVE_CLMUL + GNU_FLAGS -msse4.1 -mpclmul + TEST_SOURCE " + #include + #include + volatile __m128i r, a, b; + int main(void) { r = _mm_clmulepi64_si128(a, b, 5); + r = _mm_shuffle_epi8(r, a); }" + ADD_SOURCES_IF_SUCCESSFUL aesgcm-clmul.c) endif() # ---------------------------------------------------------------------- @@ -170,6 +184,17 @@ if(neon) int main(void) { r = vaeseq_u8(a, b); s = vsha256hq_u32(x, y, z); }" ADD_SOURCES_IF_SUCCESSFUL aes-neon.c sha256-neon.c sha1-neon.c) + test_compile_with_flags(HAVE_NEON_PMULL + GNU_FLAGS -march=armv8-a+crypto + MSVC_FLAGS -D_ARM_USE_NEW_NEON_INTRINSICS + TEST_SOURCE " + #include <${neon_header}> + volatile poly128_t r; + volatile poly64_t a, b; + volatile poly64x2_t u, v; + int main(void) { r = vmull_p64(a, b); r = vmull_high_p64(u, v); }" + ADD_SOURCES_IF_SUCCESSFUL aesgcm-neon.c) + # The 'sha3' architecture extension, despite the name, includes # support for SHA-512 (from the SHA-2 standard) as well as SHA-3 # proper. diff --git a/crypto/aes-common.c b/crypto/aes-common.c index e1c41ddf..3bed2af1 100644 --- a/crypto/aes-common.c +++ b/crypto/aes-common.c @@ -12,3 +12,9 @@ const uint8_t aes_key_setup_round_constants[10] = { * regardless of the key. */ 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, }; + +void aesgcm_cipher_crypt_length( + ssh_cipher *cipher, void *blk, int len, unsigned long seq) +{ + /* Do nothing: lengths are sent in clear for this cipher. */ +} diff --git a/crypto/aes-neon.c b/crypto/aes-neon.c index f3b92832..5cd9f2d1 100644 --- a/crypto/aes-neon.c +++ b/crypto/aes-neon.c @@ -176,6 +176,18 @@ static inline uint8x16_t aes_neon_sdctr_increment(uint8x16_t in) vsubq_u64(vreinterpretq_u64_u8(in), subtrahend)); } +/* + * Much simpler auxiliary routine to increment the counter for GCM + * mode. This only has to increment the low word. + */ +static inline uint8x16_t aes_neon_gcm_increment(uint8x16_t in) +{ + uint32x4_t inw = vreinterpretq_u32_u8(in); + uint32x4_t ONE = vcombine_u32(vcreate_u32(0), vcreate_u32(1)); + inw = vaddq_u32(inw, ONE); + return vreinterpretq_u8_u32(inw); +} + /* * The SSH interface and the cipher modes. */ @@ -227,6 +239,28 @@ static void aes_neon_setiv_sdctr(ssh_cipher *ciph, const void *iv) ctx->iv = aes_neon_sdctr_reverse(counter); } +static void aes_neon_setiv_gcm(ssh_cipher *ciph, const void *iv) +{ + aes_neon_context *ctx = container_of(ciph, aes_neon_context, ciph); + uint8x16_t counter = vld1q_u8(iv); + ctx->iv = aes_neon_sdctr_reverse(counter); + ctx->iv = vreinterpretq_u8_u32(vsetq_lane_u32( + 1, vreinterpretq_u32_u8(ctx->iv), 2)); +} + +static void aes_neon_next_message_gcm(ssh_cipher *ciph) +{ + aes_neon_context *ctx = container_of(ciph, aes_neon_context, ciph); + uint32x4_t iv = vreinterpretq_u32_u8(ctx->iv); + uint64_t msg_counter = vgetq_lane_u32(iv, 0); + msg_counter = (msg_counter << 32) | vgetq_lane_u32(iv, 3); + msg_counter++; + iv = vsetq_lane_u32(msg_counter >> 32, iv, 0); + iv = vsetq_lane_u32(msg_counter, iv, 3); + iv = vsetq_lane_u32(1, iv, 2); + ctx->iv = vreinterpretq_u8_u32(iv); +} + typedef uint8x16_t (*aes_neon_fn)(uint8x16_t v, const uint8x16_t *keysched); static inline void aes_cbc_neon_encrypt( @@ -275,6 +309,31 @@ static inline void aes_sdctr_neon( } } +static inline void aes_encrypt_ecb_block_neon( + ssh_cipher *ciph, void *blk, aes_neon_fn encrypt) +{ + aes_neon_context *ctx = container_of(ciph, aes_neon_context, ciph); + uint8x16_t plaintext = vld1q_u8(blk); + uint8x16_t ciphertext = encrypt(plaintext, ctx->keysched_e); + vst1q_u8(blk, ciphertext); +} + +static inline void aes_gcm_neon( + ssh_cipher *ciph, void *vblk, int blklen, aes_neon_fn encrypt) +{ + aes_neon_context *ctx = container_of(ciph, aes_neon_context, ciph); + + for (uint8_t *blk = (uint8_t *)vblk, *finish = blk + blklen; + blk < finish; blk += 16) { + uint8x16_t counter = aes_neon_sdctr_reverse(ctx->iv); + uint8x16_t keystream = encrypt(counter, ctx->keysched_e); + uint8x16_t input = vld1q_u8(blk); + uint8x16_t output = veorq_u8(input, keystream); + vst1q_u8(blk, output); + ctx->iv = aes_neon_gcm_increment(ctx->iv); + } +} + #define NEON_ENC_DEC(len) \ static void aes##len##_neon_cbc_encrypt( \ ssh_cipher *ciph, void *vblk, int blklen) \ @@ -285,6 +344,12 @@ static inline void aes_sdctr_neon( static void aes##len##_neon_sdctr( \ ssh_cipher *ciph, void *vblk, int blklen) \ { aes_sdctr_neon(ciph, vblk, blklen, aes_neon_##len##_e); } \ + static void aes##len##_neon_gcm( \ + ssh_cipher *ciph, void *vblk, int blklen) \ + { aes_gcm_neon(ciph, vblk, blklen, aes_neon_##len##_e); } \ + static void aes##len##_neon_encrypt_ecb_block( \ + ssh_cipher *ciph, void *vblk) \ + { aes_encrypt_ecb_block_neon(ciph, vblk, aes_neon_##len##_e); } NEON_ENC_DEC(128) NEON_ENC_DEC(192) diff --git a/crypto/aes-ni.c b/crypto/aes-ni.c index 22348de4..67d82b86 100644 --- a/crypto/aes-ni.c +++ b/crypto/aes-ni.c @@ -137,6 +137,16 @@ static inline __m128i aes_ni_sdctr_increment(__m128i v) return v; } +/* + * Much simpler auxiliary routine to increment the counter for GCM + * mode. This only has to increment the low word. + */ +static inline __m128i aes_ni_gcm_increment(__m128i v) +{ + const __m128i ONE = _mm_setr_epi32(1,0,0,0); + return _mm_add_epi32(v, ONE); +} + /* * Auxiliary routine to reverse the byte order of a vector, so that * the SDCTR IV can be made big-endian for feeding to the cipher. @@ -214,6 +224,25 @@ static void aes_ni_setiv_sdctr(ssh_cipher *ciph, const void *iv) ctx->iv = aes_ni_sdctr_reverse(counter); } +static void aes_ni_setiv_gcm(ssh_cipher *ciph, const void *iv) +{ + aes_ni_context *ctx = container_of(ciph, aes_ni_context, ciph); + __m128i counter = _mm_loadu_si128(iv); + ctx->iv = aes_ni_sdctr_reverse(counter); + ctx->iv = _mm_insert_epi32(ctx->iv, 1, 0); +} + +static void aes_ni_next_message_gcm(ssh_cipher *ciph) +{ + aes_ni_context *ctx = container_of(ciph, aes_ni_context, ciph); + uint32_t fixed = _mm_extract_epi32(ctx->iv, 3); + uint64_t msg_counter = _mm_extract_epi32(ctx->iv, 2); + msg_counter <<= 32; + msg_counter |= (uint32_t)_mm_extract_epi32(ctx->iv, 1); + msg_counter++; + ctx->iv = _mm_set_epi32(fixed, msg_counter >> 32, msg_counter, 1); +} + typedef __m128i (*aes_ni_fn)(__m128i v, const __m128i *keysched); static inline void aes_cbc_ni_encrypt( @@ -262,6 +291,31 @@ static inline void aes_sdctr_ni( } } +static inline void aes_encrypt_ecb_block_ni( + ssh_cipher *ciph, void *blk, aes_ni_fn encrypt) +{ + aes_ni_context *ctx = container_of(ciph, aes_ni_context, ciph); + __m128i plaintext = _mm_loadu_si128(blk); + __m128i ciphertext = encrypt(plaintext, ctx->keysched_e); + _mm_storeu_si128(blk, ciphertext); +} + +static inline void aes_gcm_ni( + ssh_cipher *ciph, void *vblk, int blklen, aes_ni_fn encrypt) +{ + aes_ni_context *ctx = container_of(ciph, aes_ni_context, ciph); + + for (uint8_t *blk = (uint8_t *)vblk, *finish = blk + blklen; + blk < finish; blk += 16) { + __m128i counter = aes_ni_sdctr_reverse(ctx->iv); + __m128i keystream = encrypt(counter, ctx->keysched_e); + __m128i input = _mm_loadu_si128((const __m128i *)blk); + __m128i output = _mm_xor_si128(input, keystream); + _mm_storeu_si128((__m128i *)blk, output); + ctx->iv = aes_ni_gcm_increment(ctx->iv); + } +} + #define NI_ENC_DEC(len) \ static void aes##len##_ni_cbc_encrypt( \ ssh_cipher *ciph, void *vblk, int blklen) \ @@ -272,6 +326,12 @@ static inline void aes_sdctr_ni( static void aes##len##_ni_sdctr( \ ssh_cipher *ciph, void *vblk, int blklen) \ { aes_sdctr_ni(ciph, vblk, blklen, aes_ni_##len##_e); } \ + static void aes##len##_ni_gcm( \ + ssh_cipher *ciph, void *vblk, int blklen) \ + { aes_gcm_ni(ciph, vblk, blklen, aes_ni_##len##_e); } \ + static void aes##len##_ni_encrypt_ecb_block( \ + ssh_cipher *ciph, void *vblk) \ + { aes_encrypt_ecb_block_ni(ciph, vblk, aes_ni_##len##_e); } NI_ENC_DEC(128) NI_ENC_DEC(192) diff --git a/crypto/aes-select.c b/crypto/aes-select.c index f0c5031f..892e7b58 100644 --- a/crypto/aes-select.c +++ b/crypto/aes-select.c @@ -39,7 +39,7 @@ static ssh_cipher *aes_select(const ssh_cipheralg *alg) #define IF_NEON(...) #endif -#define AES_SELECTOR_VTABLE(mode_c, mode_protocol, mode_display, bits) \ +#define AES_SELECTOR_VTABLE(mode_c, mode_protocol, mode_display, bits, ...) \ static const ssh_cipheralg * \ ssh_aes ## bits ## _ ## mode_c ## _impls[] = { \ IF_NI(&ssh_aes ## bits ## _ ## mode_c ## _ni,) \ @@ -56,6 +56,7 @@ static ssh_cipher *aes_select(const ssh_cipheralg *alg) .text_name = "AES-" #bits " " mode_display \ " (dummy selector vtable)", \ .extra = ssh_aes ## bits ## _ ## mode_c ## _impls, \ + __VA_ARGS__ \ } AES_SELECTOR_VTABLE(cbc, "cbc", "CBC", 128); @@ -64,6 +65,17 @@ AES_SELECTOR_VTABLE(cbc, "cbc", "CBC", 256); AES_SELECTOR_VTABLE(sdctr, "ctr", "SDCTR", 128); AES_SELECTOR_VTABLE(sdctr, "ctr", "SDCTR", 192); AES_SELECTOR_VTABLE(sdctr, "ctr", "SDCTR", 256); +AES_SELECTOR_VTABLE(gcm, "gcm@openssh.com", "GCM", 128, + .required_mac = &ssh2_aesgcm_mac); +AES_SELECTOR_VTABLE(gcm, "gcm@openssh.com", "GCM", 256, + .required_mac = &ssh2_aesgcm_mac); + +/* 192-bit AES-GCM is included only so that testcrypt can run standard + * test vectors against it. OpenSSH doesn't define a protocol id for + * it. Hence the silly macro trick here to set its ssh2_id to 0, and + * more importantly, leaving it out of aesgcm_list[] below. */ +AES_SELECTOR_VTABLE(gcm, ?NULL:NULL, "GCM", 192, + .required_mac = &ssh2_aesgcm_mac); static const ssh_cipheralg ssh_rijndael_lysator = { /* Same as aes256_cbc, but with a different protocol ID */ @@ -87,3 +99,12 @@ static const ssh_cipheralg *const aes_list[] = { }; const ssh2_ciphers ssh2_aes = { lenof(aes_list), aes_list }; + +static const ssh_cipheralg *const aesgcm_list[] = { + /* OpenSSH only defines protocol ids for 128- and 256-bit AES-GCM, + * not 192-bit. */ + &ssh_aes128_gcm, + &ssh_aes256_gcm, +}; + +const ssh2_ciphers ssh2_aesgcm = { lenof(aesgcm_list), aesgcm_list }; diff --git a/crypto/aes-sw.c b/crypto/aes-sw.c index f8512388..aaa3c475 100644 --- a/crypto/aes-sw.c +++ b/crypto/aes-sw.c @@ -827,6 +827,18 @@ struct aes_sw_context { uint8_t keystream[SLICE_PARALLELISM * 16]; uint8_t *keystream_pos; } sdctr; + struct { + /* In GCM mode, the cipher preimage consists of three + * sections: one fixed, one that increments per message + * sent and MACed, and one that increments per cipher + * block. */ + uint64_t msg_counter; + uint32_t fixed_iv, block_counter; + /* But we keep the precomputed keystream chunks just like + * SDCTR mode. */ + uint8_t keystream[SLICE_PARALLELISM * 16]; + uint8_t *keystream_pos; + } gcm; } iv; ssh_cipher ciph; }; @@ -874,6 +886,31 @@ static void aes_sw_setiv_sdctr(ssh_cipher *ciph, const void *viv) ctx->iv.sdctr.keystream + sizeof(ctx->iv.sdctr.keystream); } +static void aes_sw_setiv_gcm(ssh_cipher *ciph, const void *viv) +{ + aes_sw_context *ctx = container_of(ciph, aes_sw_context, ciph); + const uint8_t *iv = (const uint8_t *)viv; + + ctx->iv.gcm.fixed_iv = GET_32BIT_MSB_FIRST(iv); + ctx->iv.gcm.msg_counter = GET_64BIT_MSB_FIRST(iv + 4); + ctx->iv.gcm.block_counter = 1; + + /* Set keystream_pos to indicate that the keystream cache is + * currently empty */ + ctx->iv.gcm.keystream_pos = + ctx->iv.gcm.keystream + sizeof(ctx->iv.gcm.keystream); +} + +static void aes_sw_next_message_gcm(ssh_cipher *ciph) +{ + aes_sw_context *ctx = container_of(ciph, aes_sw_context, ciph); + + ctx->iv.gcm.msg_counter++; + ctx->iv.gcm.block_counter = 1; + ctx->iv.gcm.keystream_pos = + ctx->iv.gcm.keystream + sizeof(ctx->iv.gcm.keystream); +} + typedef void (*aes_sw_fn)(uint32_t v[4], const uint32_t *keysched); static inline void memxor16(void *vout, const void *vlhs, const void *vrhs) @@ -1021,6 +1058,56 @@ static inline void aes_sdctr_sw( } } +static inline void aes_encrypt_ecb_block_sw(ssh_cipher *ciph, void *blk) +{ + aes_sw_context *ctx = container_of(ciph, aes_sw_context, ciph); + aes_sliced_e_serial(blk, blk, &ctx->sk); +} + +static inline void aes_gcm_sw( + ssh_cipher *ciph, void *vblk, int blklen) +{ + aes_sw_context *ctx = container_of(ciph, aes_sw_context, ciph); + + /* + * GCM encrypt/decrypt looks just like SDCTR, except that the + * method of generating more keystream varies slightly. + */ + + uint8_t *keystream_end = + ctx->iv.gcm.keystream + sizeof(ctx->iv.gcm.keystream); + + for (uint8_t *blk = (uint8_t *)vblk, *finish = blk + blklen; + blk < finish; blk += 16) { + + if (ctx->iv.gcm.keystream_pos == keystream_end) { + /* + * Generate some keystream. + */ + for (uint8_t *block = ctx->iv.gcm.keystream; + block < keystream_end; block += 16) { + /* Format the counter value into the buffer. */ + PUT_32BIT_MSB_FIRST(block, ctx->iv.gcm.fixed_iv); + PUT_64BIT_MSB_FIRST(block + 4, ctx->iv.gcm.msg_counter); + PUT_32BIT_MSB_FIRST(block + 12, ctx->iv.gcm.block_counter); + + /* Increment the counter. */ + ctx->iv.gcm.block_counter++; + } + + /* Encrypt all those counter blocks. */ + aes_sliced_e_parallel(ctx->iv.gcm.keystream, + ctx->iv.gcm.keystream, &ctx->sk); + + /* Reset keystream_pos to the start of the buffer. */ + ctx->iv.gcm.keystream_pos = ctx->iv.gcm.keystream; + } + + memxor16(blk, blk, ctx->iv.gcm.keystream_pos); + ctx->iv.gcm.keystream_pos += 16; + } +} + #define SW_ENC_DEC(len) \ static void aes##len##_sw_cbc_encrypt( \ ssh_cipher *ciph, void *vblk, int blklen) \ @@ -1030,7 +1117,13 @@ static inline void aes_sdctr_sw( { aes_cbc_sw_decrypt(ciph, vblk, blklen); } \ static void aes##len##_sw_sdctr( \ ssh_cipher *ciph, void *vblk, int blklen) \ - { aes_sdctr_sw(ciph, vblk, blklen); } + { aes_sdctr_sw(ciph, vblk, blklen); } \ + static void aes##len##_sw_gcm( \ + ssh_cipher *ciph, void *vblk, int blklen) \ + { aes_gcm_sw(ciph, vblk, blklen); } \ + static void aes##len##_sw_encrypt_ecb_block( \ + ssh_cipher *ciph, void *vblk) \ + { aes_encrypt_ecb_block_sw(ciph, vblk); } SW_ENC_DEC(128) SW_ENC_DEC(192) diff --git a/crypto/aes.h b/crypto/aes.h index 433306ab..cab5b989 100644 --- a/crypto/aes.h +++ b/crypto/aes.h @@ -15,6 +15,11 @@ struct aes_extra { /* Point to a writable substructure. */ struct aes_extra_mutable *mut; + + /* Extra API function specific to AES, to encrypt a single block + * in ECB mode without touching the IV. Used by AES-GCM MAC + * setup. */ + void (*encrypt_ecb_block)(ssh_cipher *, void *); }; struct aes_extra_mutable { bool checked_availability; @@ -30,6 +35,17 @@ static inline bool check_availability(const struct aes_extra *extra) return extra->mut->is_available; } +/* Shared stub function for all the AES-GCM vtables. */ +void aesgcm_cipher_crypt_length( + ssh_cipher *cipher, void *blk, int len, unsigned long seq); + +/* External entry point for the encrypt_ecb_block function. */ +static inline void aes_encrypt_ecb_block(ssh_cipher *ciph, void *blk) +{ + const struct aes_extra *extra = ciph->vt->extra; + extra->encrypt_ecb_block(ciph, blk); +} + /* * Macros to define vtables for AES variants. There are a lot of * these, because of the cross product between cipher modes, key @@ -37,13 +53,19 @@ static inline bool check_availability(const struct aes_extra *extra) * some effort here to reduce the boilerplate in the sub-files. */ -#define AES_EXTRA(impl_c) \ +#define AES_EXTRA_BITS(impl_c, bits) \ static struct aes_extra_mutable aes ## impl_c ## _extra_mut; \ - static const struct aes_extra aes ## impl_c ## _extra = { \ + static const struct aes_extra aes ## bits ## impl_c ## _extra = { \ .check_available = aes ## impl_c ## _available, \ .mut = &aes ## impl_c ## _extra_mut, \ + .encrypt_ecb_block = &aes ## bits ## impl_c ## _encrypt_ecb_block, \ } +#define AES_EXTRA(impl_c) \ + AES_EXTRA_BITS(impl_c, 128); \ + AES_EXTRA_BITS(impl_c, 192); \ + AES_EXTRA_BITS(impl_c, 256) + #define AES_CBC_VTABLE(impl_c, impl_display, bits) \ const ssh_cipheralg ssh_aes ## bits ## _cbc ## impl_c = { \ .new = aes ## impl_c ## _new, \ @@ -59,7 +81,7 @@ static inline bool check_availability(const struct aes_extra *extra) .padded_keybytes = bits/8, \ .flags = SSH_CIPHER_IS_CBC, \ .text_name = "AES-" #bits " CBC (" impl_display ")", \ - .extra = &aes ## impl_c ## _extra, \ + .extra = &aes ## bits ## impl_c ## _extra, \ } #define AES_SDCTR_VTABLE(impl_c, impl_display, bits) \ @@ -77,7 +99,31 @@ static inline bool check_availability(const struct aes_extra *extra) .padded_keybytes = bits/8, \ .flags = 0, \ .text_name = "AES-" #bits " SDCTR (" impl_display ")", \ - .extra = &aes ## impl_c ## _extra, \ + .extra = &aes ## bits ## impl_c ## _extra, \ + } + +#define AES_GCM_VTABLE(impl_c, impl_display, bits) \ + const ssh_cipheralg ssh_aes ## bits ## _gcm ## impl_c = { \ + .new = aes ## impl_c ## _new, \ + .free = aes ## impl_c ## _free, \ + .setiv = aes ## impl_c ## _setiv_gcm, \ + .setkey = aes ## impl_c ## _setkey, \ + .encrypt = aes ## bits ## impl_c ## _gcm, \ + .decrypt = aes ## bits ## impl_c ## _gcm, \ + .encrypt_length = aesgcm_cipher_crypt_length, \ + .decrypt_length = aesgcm_cipher_crypt_length, \ + .next_message = aes ## impl_c ## _next_message_gcm, \ + /* 192-bit AES-GCM is included only so that testcrypt can run \ + * standard test vectors against it. OpenSSH doesn't define a \ + * protocol id for it. So we set its ssh2_id to NULL. */ \ + .ssh2_id = bits==192 ? NULL : "aes" #bits "-gcm@openssh.com", \ + .blksize = 16, \ + .real_keybits = bits, \ + .padded_keybytes = bits/8, \ + .flags = SSH_CIPHER_SEPARATE_LENGTH, \ + .text_name = "AES-" #bits " GCM (" impl_display ")", \ + .required_mac = &ssh2_aesgcm_mac, \ + .extra = &aes ## bits ## impl_c ## _extra, \ } #define AES_ALL_VTABLES(impl_c, impl_display) \ @@ -86,7 +132,10 @@ static inline bool check_availability(const struct aes_extra *extra) AES_CBC_VTABLE(impl_c, impl_display, 256); \ AES_SDCTR_VTABLE(impl_c, impl_display, 128); \ AES_SDCTR_VTABLE(impl_c, impl_display, 192); \ - AES_SDCTR_VTABLE(impl_c, impl_display, 256) + AES_SDCTR_VTABLE(impl_c, impl_display, 256); \ + AES_GCM_VTABLE(impl_c, impl_display, 128); \ + AES_GCM_VTABLE(impl_c, impl_display, 192); \ + AES_GCM_VTABLE(impl_c, impl_display, 256) /* * Macros to repeat a piece of code particular numbers of times that diff --git a/crypto/aesgcm-clmul.c b/crypto/aesgcm-clmul.c new file mode 100644 index 00000000..cfb72e26 --- /dev/null +++ b/crypto/aesgcm-clmul.c @@ -0,0 +1,180 @@ +/* + * Implementation of the GCM polynomial hash using the x86 CLMUL + * extension, which provides 64x64->128 polynomial multiplication (or + * 'carry-less', which is what the CL stands for). + * + * Follows the reference implementation in aesgcm-ref-poly.c; see + * there for comments on the underlying technique. Here the comments + * just discuss the x86-specific details. + */ + +#include +#include + +#if defined(__clang__) || defined(__GNUC__) +#include +#define GET_CPU_ID(out) __cpuid(1, (out)[0], (out)[1], (out)[2], (out)[3]) +#else +#define GET_CPU_ID(out) __cpuid(out, 1) +#endif + +#include "ssh.h" +#include "aesgcm.h" + +typedef struct aesgcm_clmul { + AESGCM_COMMON_FIELDS; + __m128i var, acc, mask; + void *ptr_to_free; +} aesgcm_clmul; + +static bool aesgcm_clmul_available(void) +{ + /* + * Determine if CLMUL is available on this CPU. + */ + unsigned int CPUInfo[4]; + GET_CPU_ID(CPUInfo); + return (CPUInfo[2] & (1 << 1)); +} + +/* + * __m128i has to be aligned to 16 bytes, and x86 mallocs may not + * guarantee that, so we must over-allocate to make sure a large + * enough 16-byte region can be found, and ensure the aesgcm_clmul + * struct pointer is at least that well aligned. + */ +#define SPECIAL_ALLOC +static aesgcm_clmul *aesgcm_clmul_alloc(void) +{ + char *p = smalloc(sizeof(aesgcm_clmul) + 15); + uintptr_t ip = (uintptr_t)p; + ip = (ip + 15) & ~15; + aesgcm_clmul *ctx = (aesgcm_clmul *)ip; + memset(ctx, 0, sizeof(aesgcm_clmul)); + ctx->ptr_to_free = p; + return ctx; +} + +#define SPECIAL_FREE +static void aesgcm_clmul_free(aesgcm_clmul *ctx) +{ + void *ptf = ctx->ptr_to_free; + smemclr(ctx, sizeof(*ctx)); + sfree(ptf); +} + +/* Helper function to reverse the 16 bytes in a 128-bit vector */ +static inline __m128i mm_byteswap(__m128i vec) +{ + const __m128i reverse = _mm_set_epi64x( + 0x0001020304050607ULL, 0x08090a0b0c0d0e0fULL); + return _mm_shuffle_epi8(vec, reverse); +} + +/* Helper function to swap the two 64-bit words in a 128-bit vector */ +static inline __m128i mm_wordswap(__m128i vec) +{ + return _mm_shuffle_epi32(vec, 0x4E); +} + +/* Load and store a 128-bit vector in big-endian fashion */ +static inline __m128i mm_load_be(const void *p) +{ + return mm_byteswap(_mm_loadu_si128(p)); +} +static inline void mm_store_be(void *p, __m128i vec) +{ + _mm_storeu_si128(p, mm_byteswap(vec)); +} + +/* + * Key setup is just like in aesgcm-ref-poly.c. There's no point using + * vector registers to accelerate this, because it happens rarely. + */ +static void aesgcm_clmul_setkey_impl(aesgcm_clmul *ctx, + const unsigned char *var) +{ + uint64_t hi = GET_64BIT_MSB_FIRST(var); + uint64_t lo = GET_64BIT_MSB_FIRST(var + 8); + + uint64_t bit = 1 & (hi >> 63); + hi = (hi << 1) ^ (lo >> 63); + lo = (lo << 1) ^ bit; + hi ^= 0xC200000000000000 & -bit; + + ctx->var = _mm_set_epi64x(hi, lo); +} + +static inline void aesgcm_clmul_setup(aesgcm_clmul *ctx, + const unsigned char *mask) +{ + ctx->mask = mm_load_be(mask); + ctx->acc = _mm_set_epi64x(0, 0); +} + +/* + * Folding a coefficient into the accumulator is done by essentially + * the algorithm in aesgcm-ref-poly.c. I don't speak these intrinsics + * all that well, so in the parts where I needed to XOR half of one + * vector into half of another, I did a lot of faffing about with + * masks like 0xFFFFFFFFFFFFFFFF0000000000000000. Very likely this can + * be streamlined by a better x86-speaker than me. Patches welcome. + */ +static inline void aesgcm_clmul_coeff(aesgcm_clmul *ctx, + const unsigned char *coeff) +{ + ctx->acc = _mm_xor_si128(ctx->acc, mm_load_be(coeff)); + + /* Compute ah^al and bh^bl by word-swapping each of a and b and + * XORing with the original. That does more work than necessary - + * you end up with each of the desired values repeated twice - + * but I don't know of a neater way. */ + __m128i aswap = mm_wordswap(ctx->acc); + __m128i vswap = mm_wordswap(ctx->var); + aswap = _mm_xor_si128(ctx->acc, aswap); + vswap = _mm_xor_si128(ctx->var, vswap); + + /* Do the three multiplications required by Karatsuba */ + __m128i md = _mm_clmulepi64_si128(aswap, vswap, 0x00); + __m128i lo = _mm_clmulepi64_si128(ctx->acc, ctx->var, 0x00); + __m128i hi = _mm_clmulepi64_si128(ctx->acc, ctx->var, 0x11); + /* Combine lo and hi into md */ + md = _mm_xor_si128(md, lo); + md = _mm_xor_si128(md, hi); + + /* Now we must XOR the high half of md into the low half of hi, + * and the low half of md into the high half of hi. Simplest thing + * is to swap the words of md (so that each one lines up with the + * register it's going to end up in), and then mask one off in + * each case. */ + md = mm_wordswap(md); + lo = _mm_xor_si128(lo, _mm_and_si128(md, _mm_set_epi64x(~0ULL, 0ULL))); + hi = _mm_xor_si128(hi, _mm_and_si128(md, _mm_set_epi64x(0ULL, ~0ULL))); + + /* The reduction stage is transformed similarly from the version + * in aesgcm-ref-poly.c. */ + __m128i r1 = _mm_clmulepi64_si128(_mm_set_epi64x(0, 0xC200000000000000), + lo, 0x00); + r1 = mm_wordswap(r1); + r1 = _mm_xor_si128(r1, lo); + hi = _mm_xor_si128(hi, _mm_and_si128(r1, _mm_set_epi64x(~0ULL, 0ULL))); + + __m128i r2 = _mm_clmulepi64_si128(_mm_set_epi64x(0, 0xC200000000000000), + r1, 0x10); + hi = _mm_xor_si128(hi, r2); + hi = _mm_xor_si128(hi, _mm_and_si128(r1, _mm_set_epi64x(0ULL, ~0ULL))); + + ctx->acc = hi; +} + +static inline void aesgcm_clmul_output(aesgcm_clmul *ctx, + unsigned char *output) +{ + mm_store_be(output, _mm_xor_si128(ctx->acc, ctx->mask)); + smemclr(&ctx->acc, 16); + smemclr(&ctx->mask, 16); +} + +#define AESGCM_FLAVOUR clmul +#define AESGCM_NAME "CLMUL accelerated" +#include "aesgcm-footer.h" diff --git a/crypto/aesgcm-common.c b/crypto/aesgcm-common.c new file mode 100644 index 00000000..1e20c87b --- /dev/null +++ b/crypto/aesgcm-common.c @@ -0,0 +1,8 @@ +#include "ssh.h" +#include "aesgcm.h" + +void aesgcm_set_prefix_lengths(ssh2_mac *mac, size_t skip, size_t aad) +{ + const struct aesgcm_extra *extra = mac->vt->extra; + extra->set_prefix_lengths(mac, skip, aad); +} diff --git a/crypto/aesgcm-footer.h b/crypto/aesgcm-footer.h new file mode 100644 index 00000000..981905da --- /dev/null +++ b/crypto/aesgcm-footer.h @@ -0,0 +1,368 @@ +/* + * Common footer included by every implementation of the AES-GCM MAC. + * + * The difficult part of AES-GCM, which is done differently depending + * on what hardware acceleration is available, is the actual + * evaluation of a polynomial over GF(2^128) whose coefficients are + * 128-bit chunks of data. But preparing those chunks in the first + * place (out of the ciphertext, associated data, and an + * administrative block containing the lengths of both) is done in the + * same way no matter what technique is used for the evaluation, so + * that's centralised into this file, along with as much of the other + * functionality as posible. + * + * This footer file is #included by each implementation, but each one + * will define its own struct type for the state, so that each alloc + * function will test sizeof() a different structure, and similarly + * for free when it zeroes out the state on cleanup. + * + * The functions in the source file may be defined as 'inline' so that + * the functions in here can inline them. The 'coeff' function in + * particular probably should be, because that's called once per + * 16-byte block, so eliminating function call overheads is especially + * useful there. + * + * This footer has the following expectations from the source file + * that #includes it: + * + * - define AESGCM_FLAVOUR to be a fragment of a C identifier that + * will be included in all the function names (both the ones + * defined in the implementation source file and those in here). + * For example purposes below I'll suppose that this is 'foo'. + * + * - define AESGCM_NAME to be a string literal that will be included + * in the display name of the implementation. + * + * - define a typedef 'aesgcm_foo' to be the state structure for the + * implementation, and inside that structure, expand the macro + * AESGCM_COMMON_FIELDS defined in aesgcm.h + * + * - define the following functions: + * + * // Determine whether this implementation is available at run time + * static bool aesgcm_foo_available(void); + * + * // Set up the 'key' of the polynomial part of the MAC, that is, + * // the value at which the polynomial will be evaluated. 'var' is + * // a 16-byte data block in the byte order it comes out of AES. + * static void aesgcm_foo_setkey_impl(aesgcm_foo *ctx, + * const unsigned char *var); + * + * // Set up at the start of evaluating an individual polynomial. + * // 'mask' is the 16-byte data block that will be XORed into the + * // output value of the polynomial, also in AES byte order. This + * // function should store 'mask' in whatever form is most + * // convenient, and initialise an accumulator to zero. + * static void aesgcm_foo_setup(aesgcm_foo *ctx, + * const unsigned char *mask); + * + * // Fold in a coefficient of the polynomial, by means of XORing + * // it into the accumulator and then multiplying the accumulator + * // by the variable passed to setkey_impl() above. + * // + * // 'coeff' points to the 16-byte block of data that the + * // polynomial coefficient will be made out of. + * // + * // You probably want to mark this function 'inline'. + * static void aesgcm_foo_coeff(aesgcm_foo *ctx, + * const unsigned char *coeff); + * + * // Generate the output MAC, by XORing the accumulator's final + * // value with the mask passed to setup() above. + * // + * // 'output' points to a 16-byte region of memory to write the + * // result to. + * static void aesgcm_foo_output(aesgcm_foo *ctx, + * unsigned char *output); + * + * - if allocation of the state structure must be done in a + * non-standard way (e.g. x86 needs this to force greater alignment + * than standard malloc provides), then #define SPECIAL_ALLOC and + * define this additional function: + * + * // Allocate a state structure, zero out its contents, and return it. + * static aesgcm_foo *aesgcm_foo_alloc(void); + * + * - if freeing must also be done in an unusual way, #define + * SPECIAL_FREE and define this function: + * + * // Zero out the state structure to avoid information leaks if the + * // memory is reused, and then free it. + * static void aesgcm_foo_free(aesgcm_foo *ctx); + */ + +#ifndef AESGCM_FLAVOUR +#error AESGCM_FLAVOUR must be defined by any module including this footer +#endif +#ifndef AESGCM_NAME +#error AESGCM_NAME must be defined by any module including this footer +#endif + +#define CONTEXT CAT(aesgcm_, AESGCM_FLAVOUR) +#define PREFIX(name) CAT(CAT(aesgcm_, AESGCM_FLAVOUR), CAT(_, name)) + +#include "aes.h" // for aes_encrypt_ecb_block + +static const char *PREFIX(mac_text_name)(ssh2_mac *mac) +{ + return "AES-GCM (" AESGCM_NAME ")"; +} + +static void PREFIX(mac_next_message)(ssh2_mac *mac) +{ + CONTEXT *ctx = container_of(mac, CONTEXT, mac); + + /* + * Make the mask value for a single MAC instance, by encrypting + * the all-zeroes word using the associated AES instance in its + * ordinary GCM fashion. This consumes the first block of + * keystream (with per-block counter equal to 1), leaving the + * second block of keystream ready to be used on the first block + * of plaintext. + */ + unsigned char buf[16]; + memset(buf, 0, 16); + ssh_cipher_encrypt(ctx->cipher, buf, 16); + PREFIX(setup)(ctx, buf); /* give it to the implementation to store */ + smemclr(buf, sizeof(buf)); +} + +static void PREFIX(mac_setkey)(ssh2_mac *mac, ptrlen key) +{ + CONTEXT *ctx = container_of(mac, CONTEXT, mac); + + /* + * Make the value of the polynomial variable, by encrypting the + * all-zeroes word using the associated AES instance in the + * special ECB mode. This is done via the special AES-specific API + * function encrypt_ecb_block, which doesn't touch the counter + * state at all. + */ + unsigned char var[16]; + memset(var, 0, 16); + aes_encrypt_ecb_block(ctx->cipher, var); + PREFIX(setkey_impl)(ctx, var); + smemclr(var, sizeof(var)); + + PREFIX(mac_next_message)(mac); /* set up mask */ +} + +static void PREFIX(mac_start)(ssh2_mac *mac) +{ + CONTEXT *ctx = container_of(mac, CONTEXT, mac); + + ctx->skipgot = ctx->aadgot = ctx->ciphertextlen = ctx->partlen = 0; +} + +/* + * Handle receiving data via the BinarySink API and turning it into a + * collection of 16-byte blocks to use as polynomial coefficients. + * + * This code is written in a fully general way, which is able to + * handle an arbitrary number of bytes at the start of the data to + * ignore completely (necessary for PuTTY integration), and an + * arbitrary number to treat as associated data, and the rest will be + * regarded as ciphertext. The stream can be interrupted at any byte + * position and resumed later; a partial block will be stored as + * necessary. + * + * At the time of writing this comment, in live use most of that + * generality isn't required: the full data is passed to this function + * in just one call. But there's no guarantee of that staying true in + * future, so we do the full deal here just in case, and the test + * vectors in cryptsuite.py will test it. (And they'll use + * set_prefix_lengths to set up different configurations from the SSH + * usage.) + */ +static void PREFIX(mac_BinarySink_write)( + BinarySink *bs, const void *blkv, size_t len) +{ + CONTEXT *ctx = BinarySink_DOWNCAST(bs, CONTEXT); + const unsigned char *blk = (const unsigned char *)blkv; + + /* + * Skip the prefix sequence number used as implicit extra data in + * SSH MACs. This is not included in the associated data field for + * GCM, because the IV incrementation policy provides its own + * sequence numbering. + */ + if (ctx->skipgot < ctx->skiplen) { + size_t n = ctx->skiplen - ctx->skipgot; + if (n > len) + n = len; + blk += n; + len -= n; + ctx->skipgot += n; + + if (len == 0) + return; + } + + /* + * Read additional authenticated data and fold it in to the MAC. + */ + while (ctx->aadgot < ctx->aadlen) { + size_t n = ctx->aadlen - ctx->aadgot; + if (n > len) + n = len; + + if (ctx->partlen || n < 16) { + /* + * Fold data into the partial block. + */ + if (n > 16 - ctx->partlen) + n = 16 - ctx->partlen; + memcpy(ctx->partblk + ctx->partlen, blk, n); + ctx->partlen += n; + } else if (n >= 16) { + /* + * Consume a whole block of AAD. + */ + PREFIX(coeff)(ctx, blk); + n = 16; + } + blk += n; + len -= n; + ctx->aadgot += n; + + if (ctx->partlen == 16) { + PREFIX(coeff)(ctx, ctx->partblk); + ctx->partlen = 0; + } + + if (ctx->aadgot == ctx->aadlen && ctx->partlen) { + memset(ctx->partblk + ctx->partlen, 0, 16 - ctx->partlen); + PREFIX(coeff)(ctx, ctx->partblk); + ctx->partlen = 0; + } + + if (len == 0) + return; + } + + /* + * Read the main ciphertext and fold it in to the MAC. + */ + while (len > 0) { + size_t n = len; + + if (ctx->partlen || n < 16) { + /* + * Fold data into the partial block. + */ + if (n > 16 - ctx->partlen) + n = 16 - ctx->partlen; + memcpy(ctx->partblk + ctx->partlen, blk, n); + ctx->partlen += n; + } else if (n >= 16) { + /* + * Consume a whole block of ciphertext. + */ + PREFIX(coeff)(ctx, blk); + n = 16; + } + blk += n; + len -= n; + ctx->ciphertextlen += n; + + if (ctx->partlen == 16) { + PREFIX(coeff)(ctx, ctx->partblk); + ctx->partlen = 0; + } + } +} + +static void PREFIX(mac_genresult)(ssh2_mac *mac, unsigned char *output) +{ + CONTEXT *ctx = container_of(mac, CONTEXT, mac); + + /* + * Consume any partial block of ciphertext remaining. + */ + if (ctx->partlen) { + memset(ctx->partblk + ctx->partlen, 0, 16 - ctx->partlen); + PREFIX(coeff)(ctx, ctx->partblk); + } + + /* + * Consume the final block giving the lengths of the AAD and ciphertext. + */ + unsigned char blk[16]; + memset(blk, 0, 16); + PUT_64BIT_MSB_FIRST(blk, ctx->aadlen * 8); + PUT_64BIT_MSB_FIRST(blk + 8, ctx->ciphertextlen * 8); + PREFIX(coeff)(ctx, blk); + + /* + * And call the implementation's output function. + */ + PREFIX(output)(ctx, output); + + smemclr(blk, sizeof(blk)); + smemclr(ctx->partblk, 16); +} + +static ssh2_mac *PREFIX(mac_new)(const ssh2_macalg *alg, ssh_cipher *cipher) +{ + const struct aesgcm_extra *extra = alg->extra; + if (!check_aesgcm_availability(extra)) + return NULL; + +#ifdef SPECIAL_ALLOC + CONTEXT *ctx = PREFIX(alloc)(); +#else + CONTEXT *ctx = snew(CONTEXT); + memset(ctx, 0, sizeof(CONTEXT)); +#endif + + ctx->mac.vt = alg; + ctx->cipher = cipher; + /* Default values for SSH-2, overridable by set_prefix_lengths for + * testcrypt purposes */ + ctx->skiplen = 4; + ctx->aadlen = 4; + BinarySink_INIT(ctx, PREFIX(mac_BinarySink_write)); + BinarySink_DELEGATE_INIT(&ctx->mac, ctx); + return &ctx->mac; +} + +static void PREFIX(set_prefix_lengths)(ssh2_mac *mac, size_t skip, size_t aad) +{ + CONTEXT *ctx = container_of(mac, CONTEXT, mac); + ctx->skiplen = skip; + ctx->aadlen = aad; +} + +static void PREFIX(mac_free)(ssh2_mac *mac) +{ + CONTEXT *ctx = container_of(mac, CONTEXT, mac); +#ifdef SPECIAL_FREE + PREFIX(free)(ctx); +#else + smemclr(ctx, sizeof(*ctx)); + sfree(ctx); +#endif +} + +static struct aesgcm_extra_mutable PREFIX(extra_mut); + +static const struct aesgcm_extra PREFIX(extra) = { + .check_available = PREFIX(available), + .mut = &PREFIX(extra_mut), + .set_prefix_lengths = PREFIX(set_prefix_lengths), +}; + +const ssh2_macalg CAT(ssh2_aesgcm_mac_, AESGCM_FLAVOUR) = { + .new = PREFIX(mac_new), + .free = PREFIX(mac_free), + .setkey = PREFIX(mac_setkey), + .start = PREFIX(mac_start), + .genresult = PREFIX(mac_genresult), + .next_message = PREFIX(mac_next_message), + .text_name = PREFIX(mac_text_name), + .name = "", + .etm_name = "", /* Not selectable independently */ + .len = 16, + .keylen = 0, + .extra = &PREFIX(extra), +}; diff --git a/crypto/aesgcm-neon.c b/crypto/aesgcm-neon.c new file mode 100644 index 00000000..dd7b83cc --- /dev/null +++ b/crypto/aesgcm-neon.c @@ -0,0 +1,156 @@ +/* + * Implementation of the GCM polynomial hash using Arm NEON vector + * intrinsics, in particular the multiplication operation for + * polynomials over GF(2). + * + * Follows the reference implementation in aesgcm-ref-poly.c; see + * there for comments on the underlying technique. Here the comments + * just discuss the NEON-specific details. + */ + +#include "ssh.h" +#include "aesgcm.h" + +#if USE_ARM64_NEON_H +#include +#else +#include +#endif + +typedef struct aesgcm_neon { + AESGCM_COMMON_FIELDS; + poly128_t var, acc, mask; +} aesgcm_neon; + +static bool aesgcm_neon_available(void) +{ + return platform_pmull_neon_available(); +} + +/* + * The NEON types involved are: + * + * 'poly128_t' is a type that lives in a 128-bit vector register and + * represents a 128-bit polynomial over GF(2) + * + * 'poly64x2_t' is a type that lives in a 128-bit vector register and + * represents a vector of two 64-bit polynomials. These appear as + * intermediate results in some of the helper functions below, but we + * never need to actually have a variable of that type. + * + * 'poly64x1_t' is a type that lives in a 128-bit vector register and + * represents a vector of one 64-bit polynomial. + * + * That is distinct from 'poly64_t', which is a type that lives in + * ordinary scalar registers and is a typedef for an integer type. + * + * Generally here we try to work in terms of poly128_t and 64-bit + * integer types, and let everything else be handled as internal + * details of these helper functions. + */ + +/* Make a poly128_t from two halves */ +static inline poly128_t create_p128(poly64_t hi, poly64_t lo) +{ + return vreinterpretq_p128_p64( + vcombine_p64(vcreate_p64(lo), vcreate_p64(hi))); +} + +/* Retrieve the high and low halves of a poly128_t */ +static inline poly64_t hi_half(poly128_t v) +{ + return vgetq_lane_p64(vreinterpretq_p64_p128(v), 1); +} +static inline poly64_t lo_half(poly128_t v) +{ + return vgetq_lane_p64(vreinterpretq_p64_p128(v), 0); +} + +/* 64x64 -> 128 bit polynomial multiplication, the largest we can do + * in one CPU operation */ +static inline poly128_t pmul(poly64_t v, poly64_t w) +{ + return vmull_p64(v, w); +} + +/* Load and store a poly128_t in the form of big-endian bytes. This + * involves separately swapping the halves of the register and + * reversing the bytes within each half. */ +static inline poly128_t load_p128_be(const void *p) +{ + poly128_t swapped = vreinterpretq_p128_u8(vrev64q_u8(vld1q_u8(p))); + return create_p128(lo_half(swapped), hi_half(swapped)); +} +static inline void store_p128_be(void *p, poly128_t v) +{ + poly128_t swapped = create_p128(lo_half(v), hi_half(v)); + vst1q_u8(p, vrev64q_u8(vreinterpretq_u8_p128(swapped))); +} + +/* + * Key setup is just like in aesgcm-ref-poly.c. There's no point using + * vector registers to accelerate this, because it happens rarely. + */ +static void aesgcm_neon_setkey_impl(aesgcm_neon *ctx, const unsigned char *var) +{ + uint64_t hi = GET_64BIT_MSB_FIRST(var); + uint64_t lo = GET_64BIT_MSB_FIRST(var + 8); + + uint64_t bit = 1 & (hi >> 63); + hi = (hi << 1) ^ (lo >> 63); + lo = (lo << 1) ^ bit; + hi ^= 0xC200000000000000 & -bit; + + ctx->var = create_p128(hi, lo); +} + +static inline void aesgcm_neon_setup(aesgcm_neon *ctx, + const unsigned char *mask) +{ + ctx->mask = load_p128_be(mask); + ctx->acc = create_p128(0, 0); +} + +/* + * Folding a coefficient into the accumulator is done by exactly the + * algorithm in aesgcm-ref-poly.c, translated line by line. + * + * It's possible that this could be improved by some clever manoeuvres + * that avoid having to break vectors in half and put them together + * again. Patches welcome if anyone has better ideas. + */ +static inline void aesgcm_neon_coeff(aesgcm_neon *ctx, + const unsigned char *coeff) +{ + ctx->acc = vaddq_p128(ctx->acc, load_p128_be(coeff)); + + poly64_t ah = hi_half(ctx->acc), al = lo_half(ctx->acc); + poly64_t bh = hi_half(ctx->var), bl = lo_half(ctx->var); + poly128_t md = pmul(ah ^ al, bh ^ bl); + poly128_t lo = pmul(al, bl); + poly128_t hi = pmul(ah, bh); + md = vaddq_p128(md, vaddq_p128(hi, lo)); + hi = create_p128(hi_half(hi), lo_half(hi) ^ hi_half(md)); + lo = create_p128(hi_half(lo) ^ lo_half(md), lo_half(lo)); + + poly128_t r1 = pmul((poly64_t)0xC200000000000000, lo_half(lo)); + hi = create_p128(hi_half(hi), lo_half(hi) ^ lo_half(lo) ^ hi_half(r1)); + lo = create_p128(hi_half(lo) ^ lo_half(r1), lo_half(lo)); + + poly128_t r2 = pmul((poly64_t)0xC200000000000000, hi_half(lo)); + hi = vaddq_p128(hi, r2); + hi = create_p128(hi_half(hi) ^ hi_half(lo), lo_half(hi)); + + ctx->acc = hi; +} + +static inline void aesgcm_neon_output(aesgcm_neon *ctx, unsigned char *output) +{ + store_p128_be(output, vaddq_p128(ctx->acc, ctx->mask)); + ctx->acc = create_p128(0, 0); + ctx->mask = create_p128(0, 0); +} + +#define AESGCM_FLAVOUR neon +#define AESGCM_NAME "NEON accelerated" +#include "aesgcm-footer.h" diff --git a/crypto/aesgcm-ref-poly.c b/crypto/aesgcm-ref-poly.c new file mode 100644 index 00000000..f6ca0fa5 --- /dev/null +++ b/crypto/aesgcm-ref-poly.c @@ -0,0 +1,364 @@ +/* + * Implementation of the GCM polynomial hash in pure software, but + * based on a primitive that performs 64x64->128 bit polynomial + * multiplication over GF(2). + * + * This implementation is technically correct (should even be + * side-channel safe as far as I can see), but it's hopelessly slow, + * so no live SSH connection should ever use it. Therefore, it's + * deliberately not included in the lists in aesgcm-select.c. For pure + * software GCM in live use, you want aesgcm-sw.c, and that's what the + * selection system will choose. + * + * However, this implementation _is_ made available to testcrypt, so + * all the GCM tests in cryptsuite.py are run over this as well as the + * other implementations. + * + * The reason why this code exists at all is to act as a reference for + * GCM implementations that use a CPU-specific polynomial multiply + * intrinsic or asm statement. This version will run on whatever + * platform you're trying to port to, and will generate all the same + * intermediate results you expect the CPU-specific one to go through. + * So you can insert parallel diagnostics in this version and in your + * new version, to see where the two diverge. + * + * Also, this version is a good place to put long comments explaining + * the entire strategy of this implementation and its rationale. That + * avoids those comments having to be duplicated in the multiple + * platform-specific implementations, which can focus on commenting + * the way the local platform's idioms match up to this version, and + * refer to this file for the explanation of the underlying technique. + */ + +#include "ssh.h" +#include "aesgcm.h" + +/* + * Store a 128-bit value in the most convenient form standard C will + * let us, namely two uint64_t giving its most and least significant + * halves. + */ +typedef struct { + uint64_t hi, lo; +} value128_t; + +typedef struct aesgcm_ref_poly { + AESGCM_COMMON_FIELDS; + + /* + * The state of our GCM implementation is represented entirely by + * three 128-bit values: + */ + + /* + * The value at which we're evaluating the polynomial. The GCM + * spec calls this 'H'. It's defined once at GCM key setup time, + * by encrypting the all-zeroes value with our block cipher. + */ + value128_t var; + + /* + * Accumulator containing the result of evaluating the polynomial + * so far. + */ + value128_t acc; + + /* + * The mask value that is XORed into the final value of 'acc' to + * produce the output MAC. This is different for every MAC + * generated, because its purpose is to ensure that no information + * gathered from a legal MAC can be used to help the forgery of + * another one, and that comparing two legal MACs gives you no + * useful information about the text they cover, because in each + * case, the masks are different and pseudorandom. + */ + value128_t mask; +} aesgcm_ref_poly; + +static bool aesgcm_ref_poly_available(void) +{ + return true; /* pure software implementation, always available */ +} + +/* + * Primitive function that takes two uint64_t values representing + * polynomials, multiplies them, and returns a value128_t struct + * containing the full product. + * + * Because the input polynomials have maximum degree 63, the output + * has max degree 63+63 = 127, not 128. As a result, the topmost bit + * of the output is always zero. + * + * The inside of this function is implemented in the simplest way, + * with no attention paid to performance. The important feature of + * this implementation is not what's _inside_ this function, but + * what's _outside_ it: aesgcm_ref_poly_coeff() tries to minimise the + * number of these operations. + */ +static value128_t pmul(uint64_t x, uint64_t y) +{ + value128_t r; + r.hi = r.lo = 0; + + uint64_t bit = 1 & y; + r.lo ^= x & -bit; + + for (unsigned i = 1; i < 64; i++) { + bit = 1 & (y >> i); + uint64_t z = x & -bit; + r.lo ^= z << i; + r.hi ^= z >> (64-i); + } + + return r; +} + +/* + * OK, I promised a long comment explaining what's going on in this + * implementation, and now it's time. + * + * The way AES-GCM _itself_ is defined by its own spec, its finite + * field consists of polynomials over GF(2), constrained to be 128 + * bits long by reducing them modulo P = x^128 + x^7 + x^2 + x + 1. + * Using the usual binary representation in which bit i is the + * coefficient of x^i, that's 0x100000000000000000000000000000087. + * + * That is, whenever you multiply two polynomials and find a term + * x^128, you can replace it with x^7+x^2+x+1. More generally, + * x^(128+n) can be replaced with x^(7+n)+x^(2+n)+x^(1+n)+x^n. In + * binary terms, a 1 bit at the 128th position or above is replaced by + * 0x87 exactly 128 bits further down. + * + * So you'd think that multiplying two 128-bit polynomials mod P would + * be a matter of generating their full 256-bit product in the form of + * four words HI:HU:LU:LO, and then reducing it mod P by a two-stage + * process of computing HI * 0x87 and XORing it into HU:LU, then + * computing HU * 0x87 and XORing it into LU:LO. + * + * But it's not! + * + * The reason why not is because when AES-GCM is applied to SSH, + * somehow the _bit_ order got reversed. A 16-byte block of data in + * memory is converted into a polynomial by regarding bit 7 of the + * first byte as the constant term, bit 0 of the first byte as the x^7 + * coefficient, ..., bit 0 of the last byte as the x^127 coefficient. + * So if we load that 16-byte block as a big-endian 128-bit integer, + * we end up with it representing a polynomial back to front, with the + * constant term at the top and the x^127 bit at the bottom. + * + * Well, that _shouldn't_ be a problem, right? The nice thing about + * polynomial multiplication is that it's essentially reversible. If + * you reverse the order of the coefficients of two polynomials, then + * the product of the reversed polys is exactly the reversal of the + * product of the original ones. So we bit-reverse our modulo + * polynomial to get 0x1c2000000000000000000000000000001, and we just + * pretend we're working mod that instead. + * + * And that is basically what we're about to do. But there's one + * complication, that arises from the semantics of the polynomial + * multiplication function we're using as our primitive operation. + * + * That function multiplies two polynomials of degree at most 63, to + * give one with degree at most 127. So it returns a 128-bit integer + * whose low bit is the constant term, and its very highest bit is 0, + * and its _next_ highest bit is the product of the high bits of the + * two inputs. + * + * That operation is _not_ symmetric in bit-reversal. If you give it + * the 64-bit-wise reversals of two polynomials P,Q, then its output + * is not the 128-bit-wise reversal of their product PQ, because that + * would require the constant term of PQ to appear in bit 127 of the + * output, and in fact it appears in bit 126. So in fact, what we get + * is offset by one bit from where we'd like it: it's the bit-reversal + * of PQx, not of PQ. + * + * There's more than one way we could fix this. One approach would be + * to work around this off-by-one error by taking the 128-bit output + * of pmul() and shifting it left by a bit. Then it _is_ the bitwise + * reversal of the 128-bit value we'd have wanted to get, and we could + * implement the exact algorithm described above, in fully + * bit-reversed form. + * + * But a 128-bit left shift is not a trivial operation in the vector + * architectures that this code is acting as a reference for. So we'd + * prefer to find a fix that doesn't need compensation during the + * actual per-block multiplication step. + * + * If we did the obvious thing anyway - compute the unshifted 128-bit + * product representing the bit-reversal of PQx, and reduce it mod + * 0x1c2000000000000000000000000000001 - then we'd get a result which + * is exactly what we want, except that it's got a factor of x in it + * that we need to get rid of. The obvious answer is to divide by x + * (which is legal and safe, since mod P, x is invertible). + * + * Dividing a 128-bit polynomial by x is easy in principle. Shift left + * (because we're still bit-reversed), and if that shifted a 1 bit off + * the top, XOR 0xc2000000000000000000000000000001 into the remaining + * 128 bits. + * + * But we're back to having that expensive left shift. What can we do + * about that? + * + * Happily, one of the two input values to our per-block multiply + * operation is fixed! It's given to us at key setup stage, and never + * changed until the next rekey. So if at key setup time we do this + * left shift business _once_, replacing the logical value Q with Q/x, + * then that exactly cancels out the unwanted factor of x that shows + * up in our multiply operation. And now it doesn't matter that it's + * expensive (in the sense of 'a few more instructions than you'd + * like'), because it only happens once per SSH key exchange, not once + * per 16 bytes of data transferred. + */ + +static void aesgcm_ref_poly_setkey_impl(aesgcm_ref_poly *ctx, + const unsigned char *var) +{ + /* + * Key setup function. We copy the provided 16-byte 'var' + * value into our polynomial. But, as discussed above, we also + * need to divide it by x. + */ + + ctx->var.hi = GET_64BIT_MSB_FIRST(var); + ctx->var.lo = GET_64BIT_MSB_FIRST(var + 8); + + uint64_t bit = 1 & (ctx->var.hi >> 63); + ctx->var.hi = (ctx->var.hi << 1) ^ (ctx->var.lo >> 63); + ctx->var.lo = (ctx->var.lo << 1) ^ bit; + ctx->var.hi ^= 0xC200000000000000 & -bit; +} + +static inline void aesgcm_ref_poly_setup(aesgcm_ref_poly *ctx, + const unsigned char *mask) +{ + /* + * Set up to start evaluating a particular MAC. Copy in the mask + * value for this packet, and initialise acc to zero. + */ + + ctx->mask.hi = GET_64BIT_MSB_FIRST(mask); + ctx->mask.lo = GET_64BIT_MSB_FIRST(mask + 8); + ctx->acc.hi = ctx->acc.lo = 0; +} + +static inline void aesgcm_ref_poly_coeff(aesgcm_ref_poly *ctx, + const unsigned char *coeff) +{ + /* + * One step of Horner's-rule polynomial evaluation (with each + * coefficient of the polynomial being an element of GF(2^128), + * itself composed of polynomials over GF(2) mod P). + * + * We take our accumulator value, add the incoming coefficient + * (which means XOR, by GF(2) rules), and multiply by x (that is, + * 'var'). + */ + + /* + * The addition first, which is easy. + */ + ctx->acc.hi ^= GET_64BIT_MSB_FIRST(coeff); + ctx->acc.lo ^= GET_64BIT_MSB_FIRST(coeff + 8); + + /* + * First, create the 256-bit product of the two 128-bit + * polynomials over GF(2) stored in ctx->acc and ctx->var. + * + * The obvious way to do this is by four smaller multiplications + * of 64x64 -> 128 bits. But we can do better using a single + * iteration of the Karatsuba technique, which is actually more + * convenient in polynomials over GF(2) than it is in integers, + * because there aren't all those awkward carries complicating + * things. + * + * Letting B denote x^64, and imagining our two inputs are split + * up into 64-bit chunks ah,al,bh,bl, the product we want is + * + * (ah B + al) (bh B + bl) + * = (ah bh) B^2 + (al bh + ah bl) B + (al bl) + * + * which looks like four smaller multiplications of each of ah,al + * with each of bh,bl. But Karatsuba's trick is to first compute + * + * (ah + al) (bh + bl) + * = ah bh + al bh + ah bl + al bl + * + * and then subtract the terms (ah bh) and (al bl), which we had + * to compute anyway, to get the middle two terms (al bh + ah bl) + * which are our coefficient of B. + * + * This involves more bookkeeping instructions like XORs, but with + * any luck those are faster than the main multiplication. + */ + uint64_t ah = ctx->acc.hi, al = ctx->acc.lo; + uint64_t bh = ctx->var.hi, bl = ctx->var.lo; + /* Compute the outer two terms */ + value128_t lo = pmul(al, bl); + value128_t hi = pmul(ah, bh); + /* Compute the trick product (ah+al)(bh+bl) */ + value128_t md = pmul(ah ^ al, bh ^ bl); + /* Subtract off the outer two terms to get md = al bh + ah bl */ + md.hi ^= lo.hi ^ hi.hi; + md.lo ^= lo.lo ^ hi.lo; + /* And add that into the 256-bit value given by hi * x^128 + lo */ + lo.hi ^= md.lo; + hi.lo ^= md.hi; + + /* + * OK. Now hi and lo together make up the 256-bit full product. + * Now reduce it mod the reversal of the GCM modulus polynomial. + * As discussed above, that's 0x1c2000000000000000000000000000001. + * + * We want the _topmost_ 128 bits of this, because we're working + * in a bit-reversed world. So what we fundamentally want to do is + * to take our 256-bit product, and add to it the product of its + * low 128 bits with 0x1c2000000000000000000000000000001. Then the + * top 128 bits will be the output we want. + * + * Since there's no carrying in this arithmetic, it's enough to + * discard the 1 bit at the bottom of that, because it won't + * affect anything in the half we're keeping. So it's enough to + * add 0x1c2000000000000000000000000000000 * lo to (hi:lo). + * + * We can only work with 64 bits at a time, so the first thing we + * do is to break that up: + * + * - add 0x1c200000000000000 * lo.lo to (hi.lo : lo.hi) + * - add 0x1c200000000000000 * lo.hi to (hi.hi : hi.lo) + * + * But there's still a problem: 0x1c200000000000000 is just too + * _big_ to fit in 64 bits. So we have to break it up into the low + * 64 bits 0xc200000000000000, and its leading 1. So each of those + * steps of the form 'add 0x1c200000000000000 * x to y:z' becomes + * 'add 0xc200000000000000 * x to y:z' followed by 'add x to y', + * the latter step dealing with the leading 1. + */ + + /* First step, adding to the middle two words of our number. After + * this the lowest word (in lo.lo) is ignored. */ + value128_t r1 = pmul(0xC200000000000000, lo.lo); + hi.lo ^= r1.hi ^ lo.lo; + lo.hi ^= r1.lo; + + /* Second of those steps, adding to the top two words, and + * discarding lo.hi. */ + value128_t r2 = pmul(0xC200000000000000, lo.hi); + hi.hi ^= r2.hi ^ lo.hi; + hi.lo ^= r2.lo; + + /* Now 'hi' is precisely what we have left. */ + ctx->acc = hi; +} + +static inline void aesgcm_ref_poly_output(aesgcm_ref_poly *ctx, + unsigned char *output) +{ + PUT_64BIT_MSB_FIRST(output, ctx->acc.hi ^ ctx->mask.hi); + PUT_64BIT_MSB_FIRST(output + 8, ctx->acc.lo ^ ctx->mask.lo); + smemclr(&ctx->acc, 16); + smemclr(&ctx->mask, 16); +} + +#define AESGCM_FLAVOUR ref_poly +#define AESGCM_NAME "reference polynomial-based implementation" +#include "aesgcm-footer.h" diff --git a/crypto/aesgcm-select.c b/crypto/aesgcm-select.c new file mode 100644 index 00000000..eefe7148 --- /dev/null +++ b/crypto/aesgcm-select.c @@ -0,0 +1,38 @@ +#include "ssh.h" +#include "aesgcm.h" + +static ssh2_mac *aesgcm_mac_selector_new(const ssh2_macalg *alg, + ssh_cipher *cipher) +{ + static const ssh2_macalg *const real_algs[] = { +#if HAVE_CLMUL + &ssh2_aesgcm_mac_clmul, +#endif +#if HAVE_NEON_PMULL + &ssh2_aesgcm_mac_neon, +#endif + &ssh2_aesgcm_mac_sw, + NULL, + }; + + for (size_t i = 0; real_algs[i]; i++) { + const ssh2_macalg *alg = real_algs[i]; + const struct aesgcm_extra *alg_extra = + (const struct aesgcm_extra *)alg->extra; + if (check_aesgcm_availability(alg_extra)) + return ssh2_mac_new(alg, cipher); + } + + /* We should never reach the NULL at the end of the list, because + * the last non-NULL entry should be software-only GCM, which is + * always available. */ + unreachable("aesgcm_select ran off the end of its list"); +} + +const ssh2_macalg ssh2_aesgcm_mac = { + .new = aesgcm_mac_selector_new, + .name = "", + .etm_name = "", /* Not selectable independently */ + .len = 16, + .keylen = 0, +}; diff --git a/crypto/aesgcm-sw.c b/crypto/aesgcm-sw.c new file mode 100644 index 00000000..f322ae30 --- /dev/null +++ b/crypto/aesgcm-sw.c @@ -0,0 +1,145 @@ +/* + * Implementation of the GCM polynomial hash in pure software. + * + * I don't know of a faster way to do this in a side-channel safe + * manner than by precomputing a giant table and iterating over the + * whole thing. + * + * The original GCM reference suggests that you precompute the effects + * of multiplying a 128-bit value by the fixed key, in the form of a + * table indexed by some number of bits of the input value, so that + * you end up computing something of the form + * + * table1[x & 0xFF] ^ table2[(x>>8) & 0xFF] ^ ... ^ table15[(x>>120) & 0xFF] + * + * But that was obviously written before cache and timing leaks were + * known about. What's a time-safe approach? + * + * Well, the above technique isn't fixed to 8 bits of input per table. + * You could trade off the number of tables against the size of each + * table. At one extreme of this tradeoff, you have 128 tables each + * indexed by a single input bit - which is to say, you have 128 + * values, each 128 bits wide, and you XOR together the subset of + * those values corresponding to the input bits, which you can do by + * making a bitmask out of each input bit using standard constant- + * time-coding bit twiddling techniques. + * + * That's pretty unpleasant when GCM is supposed to be a fast + * algorithm, but I don't know of a better approach that meets current + * security standards! Suggestions welcome, if they can get through + * testsc. + */ + +#include "ssh.h" +#include "aesgcm.h" + +/* + * Store a 128-bit value in the most convenient form standard C will + * let us, namely two uint64_t giving its most and least significant + * halves. + */ +typedef struct { + uint64_t hi, lo; +} value128_t; + +typedef struct aesgcm_sw { + AESGCM_COMMON_FIELDS; + + /* Accumulator for the current evaluation, and mask that will be + * XORed in at the end. High */ + value128_t acc, mask; + + /* + * Table of values to XOR in for each bit, representing the effect + * of multiplying by the fixed key. The key itself doesn't need to + * be stored separately, because it's never used. (However, it is + * also the first entry in the table, so if you _do_ need it, + * there it is.) + * + * Table is indexed from the low bit of the input upwards. + */ + value128_t table[128]; +} aesgcm_sw; + +static bool aesgcm_sw_available(void) +{ + return true; /* pure software implementation, always available */ +} + +static void aesgcm_sw_setkey_impl(aesgcm_sw *gcm, const unsigned char *var) +{ + value128_t v; + v.hi = GET_64BIT_MSB_FIRST(var); + v.lo = GET_64BIT_MSB_FIRST(var + 8); + + /* + * Prepare the table. This has to be done in reverse order, so + * that the original value of the variable corresponds to + * table[127], because AES-GCM works in the bit-reversal of its + * logical specification so that's where the logical constant term + * lives. (See more detailed comment in aesgcm-ref-poly.c.) + */ + for (size_t i = 0; i < 128; i++) { + gcm->table[127 - i] = v; + + /* Multiply v by x, which means shifting right (bit reversal + * again) and then adding 0xE1 at the top if we shifted a 1 out. */ + uint64_t lobit = v.lo & 1; + v.lo = (v.lo >> 1) ^ (v.hi << 63); + v.hi = (v.hi >> 1) ^ (0xE100000000000000ULL & -lobit); + } +} + +static inline void aesgcm_sw_setup(aesgcm_sw *gcm, const unsigned char *mask) +{ + gcm->mask.hi = GET_64BIT_MSB_FIRST(mask); + gcm->mask.lo = GET_64BIT_MSB_FIRST(mask + 8); + gcm->acc.hi = gcm->acc.lo = 0; +} + +static inline void aesgcm_sw_coeff(aesgcm_sw *gcm, const unsigned char *coeff) +{ + /* XOR in the new coefficient */ + gcm->acc.hi ^= GET_64BIT_MSB_FIRST(coeff); + gcm->acc.lo ^= GET_64BIT_MSB_FIRST(coeff + 8); + + /* And now just loop over the bits of acc, making up a new value + * by XORing together the entries of 'table' corresponding to set + * bits. */ + + value128_t out; + out.lo = out.hi = 0; + + const value128_t *tableptr = gcm->table; + + for (size_t i = 0; i < 64; i++) { + uint64_t bit = 1 & gcm->acc.lo; + gcm->acc.lo >>= 1; + uint64_t mask = -bit; + out.hi ^= mask & tableptr->hi; + out.lo ^= mask & tableptr->lo; + tableptr++; + } + for (size_t i = 0; i < 64; i++) { + uint64_t bit = 1 & gcm->acc.hi; + gcm->acc.hi >>= 1; + uint64_t mask = -bit; + out.hi ^= mask & tableptr->hi; + out.lo ^= mask & tableptr->lo; + tableptr++; + } + + gcm->acc = out; +} + +static inline void aesgcm_sw_output(aesgcm_sw *gcm, unsigned char *output) +{ + PUT_64BIT_MSB_FIRST(output, gcm->acc.hi ^ gcm->mask.hi); + PUT_64BIT_MSB_FIRST(output + 8, gcm->acc.lo ^ gcm->mask.lo); + smemclr(&gcm->acc, 16); + smemclr(&gcm->mask, 16); +} + +#define AESGCM_FLAVOUR sw +#define AESGCM_NAME "unaccelerated" +#include "aesgcm-footer.h" diff --git a/crypto/aesgcm.h b/crypto/aesgcm.h new file mode 100644 index 00000000..48077004 --- /dev/null +++ b/crypto/aesgcm.h @@ -0,0 +1,44 @@ +/* + * Common parts of the state structure for AESGCM MAC implementations. + */ +#define AESGCM_COMMON_FIELDS \ + ssh_cipher *cipher; \ + unsigned char partblk[16]; \ + size_t skiplen, aadlen, ciphertextlen; \ + size_t skipgot, aadgot, partlen; \ + BinarySink_IMPLEMENTATION; \ + ssh2_mac mac + +/* + * The 'extra' structure is used to include information about how to + * check if a given implementation is available at run time, and + * whether we've already checked. + */ +struct aesgcm_extra_mutable; +struct aesgcm_extra { + /* Function to check availability. Might be expensive, so we don't + * want to call it more than once. */ + bool (*check_available)(void); + + /* Point to a writable substructure. */ + struct aesgcm_extra_mutable *mut; + + /* + * Extra API function specific to this MAC type that allows + * testcrypt to set more general lengths for skiplen and aadlen. + */ + void (*set_prefix_lengths)(ssh2_mac *mac, size_t skip, size_t aad); +}; +struct aesgcm_extra_mutable { + bool checked_availability; + bool is_available; +}; +static inline bool check_aesgcm_availability(const struct aesgcm_extra *extra) +{ + if (!extra->mut->checked_availability) { + extra->mut->is_available = extra->check_available(); + extra->mut->checked_availability = true; + } + + return extra->mut->is_available; +} diff --git a/putty.h b/putty.h index 727a07f7..175f5969 100644 --- a/putty.h +++ b/putty.h @@ -453,6 +453,7 @@ enum { CIPHER_DES, CIPHER_ARCFOUR, CIPHER_CHACHA20, + CIPHER_AESGCM, CIPHER_MAX /* no. ciphers (inc warn) */ }; diff --git a/settings.c b/settings.c index 40e26b8d..cc2176ce 100644 --- a/settings.c +++ b/settings.c @@ -17,6 +17,7 @@ static const struct keyvalwhere ciphernames[] = { { "aes", CIPHER_AES, -1, -1 }, { "chacha20", CIPHER_CHACHA20, CIPHER_AES, +1 }, + { "aesgcm", CIPHER_AESGCM, CIPHER_CHACHA20, +1 }, { "3des", CIPHER_3DES, -1, -1 }, { "WARN", CIPHER_WARN, -1, -1 }, { "des", CIPHER_DES, -1, -1 }, diff --git a/ssh.h b/ssh.h index e14b44c6..d3ee5065 100644 --- a/ssh.h +++ b/ssh.h @@ -1072,6 +1072,10 @@ extern const ssh_cipheralg ssh_aes256_sdctr; extern const ssh_cipheralg ssh_aes256_sdctr_ni; extern const ssh_cipheralg ssh_aes256_sdctr_neon; extern const ssh_cipheralg ssh_aes256_sdctr_sw; +extern const ssh_cipheralg ssh_aes256_gcm; +extern const ssh_cipheralg ssh_aes256_gcm_ni; +extern const ssh_cipheralg ssh_aes256_gcm_neon; +extern const ssh_cipheralg ssh_aes256_gcm_sw; extern const ssh_cipheralg ssh_aes256_cbc; extern const ssh_cipheralg ssh_aes256_cbc_ni; extern const ssh_cipheralg ssh_aes256_cbc_neon; @@ -1080,6 +1084,10 @@ extern const ssh_cipheralg ssh_aes192_sdctr; extern const ssh_cipheralg ssh_aes192_sdctr_ni; extern const ssh_cipheralg ssh_aes192_sdctr_neon; extern const ssh_cipheralg ssh_aes192_sdctr_sw; +extern const ssh_cipheralg ssh_aes192_gcm; +extern const ssh_cipheralg ssh_aes192_gcm_ni; +extern const ssh_cipheralg ssh_aes192_gcm_neon; +extern const ssh_cipheralg ssh_aes192_gcm_sw; extern const ssh_cipheralg ssh_aes192_cbc; extern const ssh_cipheralg ssh_aes192_cbc_ni; extern const ssh_cipheralg ssh_aes192_cbc_neon; @@ -1088,6 +1096,10 @@ extern const ssh_cipheralg ssh_aes128_sdctr; extern const ssh_cipheralg ssh_aes128_sdctr_ni; extern const ssh_cipheralg ssh_aes128_sdctr_neon; extern const ssh_cipheralg ssh_aes128_sdctr_sw; +extern const ssh_cipheralg ssh_aes128_gcm; +extern const ssh_cipheralg ssh_aes128_gcm_ni; +extern const ssh_cipheralg ssh_aes128_gcm_neon; +extern const ssh_cipheralg ssh_aes128_gcm_sw; extern const ssh_cipheralg ssh_aes128_cbc; extern const ssh_cipheralg ssh_aes128_cbc_ni; extern const ssh_cipheralg ssh_aes128_cbc_neon; @@ -1103,6 +1115,7 @@ extern const ssh2_ciphers ssh2_aes; extern const ssh2_ciphers ssh2_blowfish; extern const ssh2_ciphers ssh2_arcfour; extern const ssh2_ciphers ssh2_ccp; +extern const ssh2_ciphers ssh2_aesgcm; extern const ssh_hashalg ssh_md5; extern const ssh_hashalg ssh_sha1; extern const ssh_hashalg ssh_sha1_ni; @@ -1163,12 +1176,20 @@ extern const ssh2_macalg ssh_hmac_sha1_96; extern const ssh2_macalg ssh_hmac_sha1_96_buggy; extern const ssh2_macalg ssh_hmac_sha256; extern const ssh2_macalg ssh2_poly1305; +extern const ssh2_macalg ssh2_aesgcm_mac; +extern const ssh2_macalg ssh2_aesgcm_mac_sw; +extern const ssh2_macalg ssh2_aesgcm_mac_ref_poly; +extern const ssh2_macalg ssh2_aesgcm_mac_clmul; +extern const ssh2_macalg ssh2_aesgcm_mac_neon; extern const ssh_compression_alg ssh_zlib; /* Special constructor: BLAKE2b can be instantiated with any hash * length up to 128 bytes */ ssh_hash *blake2b_new_general(unsigned hashlen); +/* Special test function for AES-GCM */ +void aesgcm_set_prefix_lengths(ssh2_mac *mac, size_t skip, size_t aad); + /* * On some systems, you have to detect hardware crypto acceleration by * asking the local OS API rather than OS-agnostically asking the CPU @@ -1176,6 +1197,7 @@ ssh_hash *blake2b_new_general(unsigned hashlen); * platform subdirectory. */ bool platform_aes_neon_available(void); +bool platform_pmull_neon_available(void); bool platform_sha256_neon_available(void); bool platform_sha1_neon_available(void); bool platform_sha512_neon_available(void); diff --git a/ssh/bpp2.c b/ssh/bpp2.c index a3ab99f9..e019dd2e 100644 --- a/ssh/bpp2.c +++ b/ssh/bpp2.c @@ -132,6 +132,12 @@ void ssh2_bpp_new_outgoing_crypto( s->out.etm_mode = etm_mode; if (mac) { s->out.mac = ssh2_mac_new(mac, s->out.cipher); + /* + * Important that mac_setkey comes after cipher_setkey, + * because in the case where the MAC makes use of the cipher + * (e.g. AES-GCM), it will need the cipher to be keyed + * already. + */ ssh2_mac_setkey(s->out.mac, make_ptrlen(mac_key, mac->keylen)); bpp_logevent("Initialised %s outbound MAC algorithm%s%s", @@ -189,6 +195,7 @@ void ssh2_bpp_new_incoming_crypto( s->in.etm_mode = etm_mode; if (mac) { s->in.mac = ssh2_mac_new(mac, s->in.cipher); + /* MAC setkey has to follow cipher, just as in outgoing_crypto above */ ssh2_mac_setkey(s->in.mac, make_ptrlen(mac_key, mac->keylen)); bpp_logevent("Initialised %s inbound MAC algorithm%s%s", diff --git a/ssh/transport2.c b/ssh/transport2.c index 705df466..aba8cd0b 100644 --- a/ssh/transport2.c +++ b/ssh/transport2.c @@ -600,6 +600,9 @@ static void ssh2_write_kexinit_lists( case CIPHER_CHACHA20: preferred_ciphers[n_preferred_ciphers++] = &ssh2_ccp; break; + case CIPHER_AESGCM: + preferred_ciphers[n_preferred_ciphers++] = &ssh2_aesgcm; + break; case CIPHER_WARN: /* Flag for later. Don't bother if it's the last in * the list. */ diff --git a/test/cryptsuite.py b/test/cryptsuite.py index 35114a16..69b492e8 100755 --- a/test/cryptsuite.py +++ b/test/cryptsuite.py @@ -145,6 +145,11 @@ def get_aes_impls(): for impl in get_implementations("aes128_cbc") if impl.startswith("aes128_cbc_")] +def get_aesgcm_impls(): + return [impl.split("_", 1)[1] + for impl in get_implementations("aesgcm") + if impl.startswith("aesgcm_")] + class MyTestBase(unittest.TestCase): "Intermediate class that adds useful helper methods." def assertEqualBin(self, x, y): @@ -2933,6 +2938,184 @@ Private-MAC: 5b1f6f4cc43eb0060d2c3e181bc0129343adba2b per_base_keytype_tests('rsa', run_ca_rsa_tests=True, ca_signflags=2) per_base_keytype_tests('rsa', run_ca_rsa_tests=True, ca_signflags=4) + def testAESGCMBlockBoundaries(self): + # For standard AES-GCM test vectors, see the separate tests in + # standard_test_vectors.testAESGCM. This function will test + # the local interface, including the skip length and the + # machinery for incremental MAC update. + + def aesgcm(key, iv, aes_impl, gcm_impl): + c = ssh_cipher_new('aes{:d}_gcm_{}'.format(8*len(key), aes_impl)) + m = ssh2_mac_new('aesgcm_{}'.format(gcm_impl), c) + if m is None: return # skip test if HW GCM not available + c.setkey(key) + c.setiv(iv + b'\0'*4) + m.setkey(b'') + return c, m + + def test_one(aes_impl, gcm_impl): + # An actual test from a session with OpenSSH, which + # demonstrates that the implementation in practice matches up + # to what the test vectors say. This is its SSH2_MSG_EXT_INFO + # packet. + key = unhex('dbf98b2f56c83fb2f9476aa876511225') + iv = unhex('9af15ecccf2bacaaa9625a6a') + plain = unhex('1007000000020000000f736572766572' + '2d7369672d616c6773000000db737368' + '2d656432353531392c736b2d7373682d' + '65643235353139406f70656e7373682e' + '636f6d2c7373682d7273612c7273612d' + '736861322d3235362c7273612d736861' + '322d3531322c7373682d6473732c6563' + '6473612d736861322d6e697374703235' + '362c65636473612d736861322d6e6973' + '74703338342c65636473612d73686132' + '2d6e697374703532312c736b2d656364' + '73612d736861322d6e69737470323536' + '406f70656e7373682e636f6d2c776562' + '617574686e2d736b2d65636473612d73' + '6861322d6e69737470323536406f7065' + '6e7373682e636f6d0000001f7075626c' + '69636b65792d686f7374626f756e6440' + '6f70656e7373682e636f6d0000000130' + '5935130804ad4b19ed2789210290c438') + aad = unhex('00000130') + cipher = unhex('c4b88f35c1ef8aa6225033c3f185d648' + '3c485d84930d5846f7851daacbff49d5' + '8cf72169fca7ab3c170376df65dd69de' + 'c40a94c6b8e3da6d61161ab19be27466' + '02e0dfa3330faae291ef4173a20e87a4' + 'd40728c645baa72916c1958531ef7b54' + '27228513e53005e6d17b9bb384b8d8c1' + '92b8a10b731459eed5a0fb120c283412' + 'e34445981df1257f1c35a06196731fed' + '1b3115f419e754de0b634bf68768cb02' + '29e70bb2259cedb5101ff6a4ac19aaad' + '46f1c30697361b45d6c152c3069cee6b' + 'd46e9785d65ea6bf7fca41f0ac3c8e93' + 'ce940b0059c39d51e49c17f60d48d633' + '5bae4402faab61d8d65221b24b400e65' + '89f941ff48310231a42641851ea00832' + '2c2d188f4cc6a4ec6002161c407d0a92' + 'f1697bb319fbec1ca63fa8e7ac171c85' + '5b60142bfcf4e5b0a9ada3451799866e') + + c, m = aesgcm(key, iv, aes_impl, gcm_impl) + len_dec = c.decrypt_length(aad, 123) + self.assertEqual(len_dec, aad) # length not actually encrypted + m.start() + # We expect 4 bytes skipped (the sequence number that + # ChaCha20-Poly1305 wants at the start of its MAC), and 4 + # bytes AAD. These were initialised by the call to + # encrypt_length. + m.update(b'fake' + aad + cipher) + self.assertEqualBin(m.genresult(), + unhex('4a5a6d57d54888b4e58c57a96e00b73a')) + self.assertEqualBin(c.decrypt(cipher), plain) + + c, m = aesgcm(key, iv, aes_impl, gcm_impl) + len_enc = c.encrypt_length(aad, 123) + self.assertEqual(len_enc, aad) # length not actually encrypted + self.assertEqualBin(c.encrypt(plain), cipher) + + # Test incremental update. + def testIncremental(skiplen, aad, plain): + key, iv = b'SomeRandomKeyVal', b'SomeRandomIV' + mac_input = b'x' * skiplen + aad + plain + + c, m = aesgcm(key, iv, aes_impl, gcm_impl) + aesgcm_set_prefix_lengths(m, skiplen, len(aad)) + + m.start() + m.update(mac_input) + reference_mac = m.genresult() + + # Break the input just once, at each possible byte + # position. + for i in range(1, len(mac_input)): + c.setiv(iv + b'\0'*4) + m.setkey(b'') + aesgcm_set_prefix_lengths(m, skiplen, len(aad)) + m.start() + m.update(mac_input[:i]) + m.update(mac_input[i:]) + self.assertEqualBin(m.genresult(), reference_mac) + + # Feed the entire input in a byte at a time. + c.setiv(iv + b'\0'*4) + m.setkey(b'') + aesgcm_set_prefix_lengths(m, skiplen, len(aad)) + m.start() + for i in range(len(mac_input)): + m.update(mac_input[i:i+1]) + self.assertEqualBin(m.genresult(), reference_mac) + + # Incremental test with more than a full block of each thing + testIncremental(23, b'abcdefghijklmnopqrst', + b'Lorem ipsum dolor sit amet') + + # Incremental test with exactly a full block of each thing + testIncremental(16, b'abcdefghijklmnop', + b'Lorem ipsum dolo') + + # Incremental test with less than a full block of each thing + testIncremental(7, b'abcdefghij', + b'Lorem ipsum') + + for aes_impl in get_aes_impls(): + for gcm_impl in get_aesgcm_impls(): + with self.subTest(aes_impl=aes_impl, gcm_impl=gcm_impl): + test_one(aes_impl, gcm_impl) + + def testAESGCMIV(self): + key = b'SomeRandomKeyVal' + + def test(gcm, cbc, iv_fixed, iv_msg): + gcm.setiv(ssh_uint32(iv_fixed) + ssh_uint64(iv_msg) + b'fake') + + cbc.setiv(b'\0' * 16) + preimage = cbc.decrypt(gcm.encrypt(b'\0' * 16)) + self.assertEqualBin(preimage, ssh_uint32(iv_fixed) + + ssh_uint64(iv_msg) + ssh_uint32(1)) + cbc.setiv(b'\0' * 16) + preimage = cbc.decrypt(gcm.encrypt(b'\0' * 16)) + self.assertEqualBin(preimage, ssh_uint32(iv_fixed) + + ssh_uint64(iv_msg) + ssh_uint32(2)) + + gcm.next_message() + iv_msg = (iv_msg + 1) & ((1<<64)-1) + + cbc.setiv(b'\0' * 16) + preimage = cbc.decrypt(gcm.encrypt(b'\0' * 16)) + self.assertEqualBin(preimage, ssh_uint32(iv_fixed) + + ssh_uint64(iv_msg) + ssh_uint32(1)) + cbc.setiv(b'\0' * 16) + preimage = cbc.decrypt(gcm.encrypt(b'\0' * 16)) + self.assertEqualBin(preimage, ssh_uint32(iv_fixed) + + ssh_uint64(iv_msg) + ssh_uint32(2)) + + + for impl in get_aes_impls(): + with self.subTest(aes_impl=impl): + gcm = ssh_cipher_new('aes{:d}_gcm_{}'.format(8*len(key), impl)) + gcm.setkey(key) + + cbc = ssh_cipher_new('aes{:d}_cbc_{}'.format(8*len(key), impl)) + cbc.setkey(key) + + # A simple test to ensure the low word gets + # incremented and that the whole IV looks basically + # the way we expect it to + test(gcm, cbc, 0x27182818, 0x3141592653589793) + + # Test that carries are propagated into the high word + test(gcm, cbc, 0x27182818, 0x00000000FFFFFFFF) + + # Test that carries _aren't_ propagated out of the + # high word of the message counter into the fixed word + # at the top + test(gcm, cbc, 0x27182818, 0xFFFFFFFFFFFFFFFF) + class standard_test_vectors(MyTestBase): def testAES(self): def vector(cipher, key, plaintext, ciphertext): @@ -3726,6 +3909,178 @@ class standard_test_vectors(MyTestBase): b'opaque="HRPCssKJSGjCrkzDg8OhwpzCiGPChXYjwrI2QmXDnsOS", ' b'userhash=true') + def testAESGCM(self): + def test(key, iv, plaintext, aad, ciphertext, mac): + c = ssh_cipher_new('aes{:d}_gcm'.format(8*len(key))) + m = ssh2_mac_new('aesgcm_{}'.format(impl), c) + if m is None: return # skip test if HW GCM not available + c.setkey(key) + c.setiv(iv + b'\0'*4) + m.setkey(b'') + aesgcm_set_prefix_lengths(m, 0, len(aad)) + + # Some test cases have plaintext/ciphertext that is not a + # multiple of the cipher block size. Our MAC + # implementation supports this, but the cipher + # implementation expects block-granular input. + padlen = 15 & -len(plaintext) + ciphertext_got = c.encrypt(plaintext + b'0' * padlen)[ + :len(plaintext)] + + m.start() + m.update(aad + ciphertext) + mac_got = m.genresult() + + self.assertEqualBin(ciphertext_got, ciphertext) + self.assertEqualBin(mac_got, mac) + + c.setiv(iv + b'\0'*4) + + for impl in get_aesgcm_impls(): + # 'The Galois/Counter Mode of Operation', McGrew and + # Viega, Appendix B. All the tests except the ones whose + # IV is the wrong length, because handling that requires + # an extra evaluation of the polynomial hash, which is + # never used in an SSH context, so I didn't implement it + # just for the sake of test vectors. + + # Test Case 1 + test(unhex('00000000000000000000000000000000'), + unhex('000000000000000000000000'), + unhex(''), unhex(''), unhex(''), + unhex('58e2fccefa7e3061367f1d57a4e7455a')) + + # Test Case 2 + test(unhex('00000000000000000000000000000000'), + unhex('000000000000000000000000'), + unhex('00000000000000000000000000000000'), + unhex(''), + unhex('0388dace60b6a392f328c2b971b2fe78'), + unhex('ab6e47d42cec13bdf53a67b21257bddf')) + + # Test Case 3 + test(unhex('feffe9928665731c6d6a8f9467308308'), + unhex('cafebabefacedbaddecaf888'), + unhex('d9313225f88406e5a55909c5aff5269a' + '86a7a9531534f7da2e4c303d8a318a72' + '1c3c0c95956809532fcf0e2449a6b525' + 'b16aedf5aa0de657ba637b391aafd255'), + unhex(''), + unhex('42831ec2217774244b7221b784d0d49c' + 'e3aa212f2c02a4e035c17e2329aca12e' + '21d514b25466931c7d8f6a5aac84aa05' + '1ba30b396a0aac973d58e091473f5985'), + unhex('4d5c2af327cd64a62cf35abd2ba6fab4')) + + # Test Case 4 + test(unhex('feffe9928665731c6d6a8f9467308308'), + unhex('cafebabefacedbaddecaf888'), + unhex('d9313225f88406e5a55909c5aff5269a' + '86a7a9531534f7da2e4c303d8a318a72' + '1c3c0c95956809532fcf0e2449a6b525' + 'b16aedf5aa0de657ba637b39'), + unhex('feedfacedeadbeeffeedfacedeadbeef' + 'abaddad2'), + unhex('42831ec2217774244b7221b784d0d49c' + 'e3aa212f2c02a4e035c17e2329aca12e' + '21d514b25466931c7d8f6a5aac84aa05' + '1ba30b396a0aac973d58e091'), + unhex('5bc94fbc3221a5db94fae95ae7121a47')) + + # Test Case 7 + test(unhex('00000000000000000000000000000000' + '0000000000000000'), + unhex('000000000000000000000000'), + unhex(''), unhex(''), unhex(''), + unhex('cd33b28ac773f74ba00ed1f312572435')) + + # Test Case 8 + test(unhex('00000000000000000000000000000000' + '0000000000000000'), + unhex('000000000000000000000000'), + unhex('00000000000000000000000000000000'), + unhex(''), + unhex('98e7247c07f0fe411c267e4384b0f600'), + unhex('2ff58d80033927ab8ef4d4587514f0fb')) + + # Test Case 9 + test(unhex('feffe9928665731c6d6a8f9467308308' + 'feffe9928665731c'), + unhex('cafebabefacedbaddecaf888'), + unhex('d9313225f88406e5a55909c5aff5269a' + '86a7a9531534f7da2e4c303d8a318a72' + '1c3c0c95956809532fcf0e2449a6b525' + 'b16aedf5aa0de657ba637b391aafd255'), + unhex(''), + unhex('3980ca0b3c00e841eb06fac4872a2757' + '859e1ceaa6efd984628593b40ca1e19c' + '7d773d00c144c525ac619d18c84a3f47' + '18e2448b2fe324d9ccda2710acade256'), + unhex('9924a7c8587336bfb118024db8674a14')) + + # Test Case 10 + test(unhex('feffe9928665731c6d6a8f9467308308' + 'feffe9928665731c'), + unhex('cafebabefacedbaddecaf888'), + unhex('d9313225f88406e5a55909c5aff5269a' + '86a7a9531534f7da2e4c303d8a318a72' + '1c3c0c95956809532fcf0e2449a6b525' + 'b16aedf5aa0de657ba637b39'), + unhex('feedfacedeadbeeffeedfacedeadbeef' + 'abaddad2'), + unhex('3980ca0b3c00e841eb06fac4872a2757' + '859e1ceaa6efd984628593b40ca1e19c' + '7d773d00c144c525ac619d18c84a3f47' + '18e2448b2fe324d9ccda2710'), + unhex('2519498e80f1478f37ba55bd6d27618c')) + + # Test Case 13 + test(unhex('00000000000000000000000000000000' + '00000000000000000000000000000000'), + unhex('000000000000000000000000'), + unhex(''), unhex(''), unhex(''), + unhex('530f8afbc74536b9a963b4f1c4cb738b')) + + # Test Case 14 + test(unhex('00000000000000000000000000000000' + '00000000000000000000000000000000'), + unhex('000000000000000000000000'), + unhex('00000000000000000000000000000000'), + unhex(''), + unhex('cea7403d4d606b6e074ec5d3baf39d18'), + unhex('d0d1c8a799996bf0265b98b5d48ab919')) + + # Test Case 15 + test(unhex('feffe9928665731c6d6a8f9467308308' + 'feffe9928665731c6d6a8f9467308308'), + unhex('cafebabefacedbaddecaf888'), + unhex('d9313225f88406e5a55909c5aff5269a' + '86a7a9531534f7da2e4c303d8a318a72' + '1c3c0c95956809532fcf0e2449a6b525' + 'b16aedf5aa0de657ba637b391aafd255'), + unhex(''), + unhex('522dc1f099567d07f47f37a32a84427d' + '643a8cdcbfe5c0c97598a2bd2555d1aa' + '8cb08e48590dbb3da7b08b1056828838' + 'c5f61e6393ba7a0abcc9f662898015ad'), + unhex('b094dac5d93471bdec1a502270e3cc6c')) + + # Test Case 16 + test(unhex('feffe9928665731c6d6a8f9467308308' + 'feffe9928665731c6d6a8f9467308308'), + unhex('cafebabefacedbaddecaf888'), + unhex('d9313225f88406e5a55909c5aff5269a' + '86a7a9531534f7da2e4c303d8a318a72' + '1c3c0c95956809532fcf0e2449a6b525' + 'b16aedf5aa0de657ba637b39'), + unhex('feedfacedeadbeeffeedfacedeadbeef' + 'abaddad2'), + unhex('522dc1f099567d07f47f37a32a84427d' + '643a8cdcbfe5c0c97598a2bd2555d1aa' + '8cb08e48590dbb3da7b08b1056828838' + 'c5f61e6393ba7a0abcc9f662'), + unhex('76fc6ece0f4e1768cddf8853bb2d551b')) + if __name__ == "__main__": # Run the tests, suppressing automatic sys.exit and collecting the # unittest.TestProgram instance returned by unittest.main instead. diff --git a/test/list-accel.py b/test/list-accel.py index af93d420..ac92d376 100755 --- a/test/list-accel.py +++ b/test/list-accel.py @@ -25,10 +25,14 @@ def list_implementations(alg, checkfn): def list_cipher_implementations(alg): list_implementations(alg, lambda impl: ssh_cipher_new(impl) is not None) +def list_mac_implementations(alg): + list_implementations(alg, lambda impl: ssh2_mac_new(impl, None) is not None) + def list_hash_implementations(alg): list_implementations(alg, lambda impl: ssh_hash_new(impl) is not None) list_cipher_implementations("aes256_cbc") +list_mac_implementations("aesgcm") list_hash_implementations("sha1") list_hash_implementations("sha256") list_hash_implementations("sha512") diff --git a/test/testcrypt-enum.h b/test/testcrypt-enum.h index ac73a766..90458fc0 100644 --- a/test/testcrypt-enum.h +++ b/test/testcrypt-enum.h @@ -36,6 +36,16 @@ BEGIN_ENUM_TYPE(macalg) ENUM_VALUE("hmac_sha1_96_buggy", &ssh_hmac_sha1_96_buggy) ENUM_VALUE("hmac_sha256", &ssh_hmac_sha256) ENUM_VALUE("poly1305", &ssh2_poly1305) + ENUM_VALUE("aesgcm", &ssh2_aesgcm_mac) + ENUM_VALUE("aesgcm", &ssh2_aesgcm_mac) + ENUM_VALUE("aesgcm_sw", &ssh2_aesgcm_mac_sw) + ENUM_VALUE("aesgcm_ref_poly", &ssh2_aesgcm_mac_ref_poly) +#if HAVE_CLMUL + ENUM_VALUE("aesgcm_clmul", &ssh2_aesgcm_mac_clmul) +#endif +#if HAVE_NEON_PMULL + ENUM_VALUE("aesgcm_neon", &ssh2_aesgcm_mac_neon) +#endif END_ENUM_TYPE(macalg) BEGIN_ENUM_TYPE(keyalg) @@ -60,31 +70,43 @@ BEGIN_ENUM_TYPE(cipheralg) ENUM_VALUE("3des_ssh1", &ssh_3des_ssh1) ENUM_VALUE("des_cbc", &ssh_des) ENUM_VALUE("aes256_ctr", &ssh_aes256_sdctr) + ENUM_VALUE("aes256_gcm", &ssh_aes256_gcm) ENUM_VALUE("aes256_cbc", &ssh_aes256_cbc) ENUM_VALUE("aes192_ctr", &ssh_aes192_sdctr) + ENUM_VALUE("aes192_gcm", &ssh_aes192_gcm) ENUM_VALUE("aes192_cbc", &ssh_aes192_cbc) ENUM_VALUE("aes128_ctr", &ssh_aes128_sdctr) + ENUM_VALUE("aes128_gcm", &ssh_aes128_gcm) ENUM_VALUE("aes128_cbc", &ssh_aes128_cbc) ENUM_VALUE("aes256_ctr_sw", &ssh_aes256_sdctr_sw) + ENUM_VALUE("aes256_gcm_sw", &ssh_aes256_gcm_sw) ENUM_VALUE("aes256_cbc_sw", &ssh_aes256_cbc_sw) ENUM_VALUE("aes192_ctr_sw", &ssh_aes192_sdctr_sw) + ENUM_VALUE("aes192_gcm_sw", &ssh_aes192_gcm_sw) ENUM_VALUE("aes192_cbc_sw", &ssh_aes192_cbc_sw) ENUM_VALUE("aes128_ctr_sw", &ssh_aes128_sdctr_sw) + ENUM_VALUE("aes128_gcm_sw", &ssh_aes128_gcm_sw) ENUM_VALUE("aes128_cbc_sw", &ssh_aes128_cbc_sw) #if HAVE_AES_NI ENUM_VALUE("aes256_ctr_ni", &ssh_aes256_sdctr_ni) + ENUM_VALUE("aes256_gcm_ni", &ssh_aes256_gcm_ni) ENUM_VALUE("aes256_cbc_ni", &ssh_aes256_cbc_ni) ENUM_VALUE("aes192_ctr_ni", &ssh_aes192_sdctr_ni) + ENUM_VALUE("aes192_gcm_ni", &ssh_aes192_gcm_ni) ENUM_VALUE("aes192_cbc_ni", &ssh_aes192_cbc_ni) ENUM_VALUE("aes128_ctr_ni", &ssh_aes128_sdctr_ni) + ENUM_VALUE("aes128_gcm_ni", &ssh_aes128_gcm_ni) ENUM_VALUE("aes128_cbc_ni", &ssh_aes128_cbc_ni) #endif #if HAVE_NEON_CRYPTO ENUM_VALUE("aes256_ctr_neon", &ssh_aes256_sdctr_neon) + ENUM_VALUE("aes256_gcm_neon", &ssh_aes256_gcm_neon) ENUM_VALUE("aes256_cbc_neon", &ssh_aes256_cbc_neon) ENUM_VALUE("aes192_ctr_neon", &ssh_aes192_sdctr_neon) + ENUM_VALUE("aes192_gcm_neon", &ssh_aes192_gcm_neon) ENUM_VALUE("aes192_cbc_neon", &ssh_aes192_cbc_neon) ENUM_VALUE("aes128_ctr_neon", &ssh_aes128_sdctr_neon) + ENUM_VALUE("aes128_gcm_neon", &ssh_aes128_gcm_neon) ENUM_VALUE("aes128_cbc_neon", &ssh_aes128_cbc_neon) #endif ENUM_VALUE("blowfish_ctr", &ssh_blowfish_ssh2_ctr) diff --git a/test/testcrypt-func.h b/test/testcrypt-func.h index f79c966e..bd007293 100644 --- a/test/testcrypt-func.h +++ b/test/testcrypt-func.h @@ -277,6 +277,9 @@ FUNC(void, ssh2_mac_next_message, ARG(val_mac, m)) FUNC_WRAPPED(val_string, ssh2_mac_genresult, ARG(val_mac, m)) FUNC(val_string_asciz_const, ssh2_mac_text_name, ARG(val_mac, m)) +FUNC(void, aesgcm_set_prefix_lengths, + ARG(val_mac, m), ARG(uint, skip), ARG(uint, aad)) + /* * The ssh_key abstraction. All the uses of BinarySink and * BinarySource in parameters are replaced with ordinary strings for diff --git a/test/testcrypt.c b/test/testcrypt.c index de09af33..3755ae72 100644 --- a/test/testcrypt.c +++ b/test/testcrypt.c @@ -1329,7 +1329,16 @@ strbuf *get_implementations_commasep(ptrlen alg) strbuf *out = strbuf_new(); put_datapl(out, alg); - if (ptrlen_startswith(alg, PTRLEN_LITERAL("aes"), NULL)) { + if (ptrlen_startswith(alg, PTRLEN_LITERAL("aesgcm"), NULL)) { + put_fmt(out, ",%.*s_sw", PTRLEN_PRINTF(alg)); + put_fmt(out, ",%.*s_ref_poly", PTRLEN_PRINTF(alg)); +#if HAVE_CLMUL + put_fmt(out, ",%.*s_clmul", PTRLEN_PRINTF(alg)); +#endif +#if HAVE_NEON_PMULL + put_fmt(out, ",%.*s_neon", PTRLEN_PRINTF(alg)); +#endif + } else if (ptrlen_startswith(alg, PTRLEN_LITERAL("aes"), NULL)) { put_fmt(out, ",%.*s_sw", PTRLEN_PRINTF(alg)); #if HAVE_AES_NI put_fmt(out, ",%.*s_ni", PTRLEN_PRINTF(alg)); diff --git a/test/testsc.c b/test/testsc.c index 6068dd86..0a643e97 100644 --- a/test/testsc.c +++ b/test/testsc.c @@ -259,6 +259,12 @@ VOLATILE_WRAPPED_DEFN(static, size_t, looplimit, (size_t x)) #define IF_SHA_NI(x) #endif +#if HAVE_CLMUL +#define IF_CLMUL(x) x +#else +#define IF_CLMUL(x) +#endif + #if HAVE_NEON_CRYPTO #define IF_NEON_CRYPTO(x) x #else @@ -271,6 +277,12 @@ VOLATILE_WRAPPED_DEFN(static, size_t, looplimit, (size_t x)) #define IF_NEON_SHA512(x) #endif +#if HAVE_NEON_PMULL +#define IF_NEON_PMULL(x) x +#else +#define IF_NEON_PMULL(x) +#endif + /* Ciphers that we expect to pass this test. Blowfish and Arcfour are * intentionally omitted, because we already know they don't. */ #define CIPHERS(X, Y) \ @@ -280,28 +292,40 @@ VOLATILE_WRAPPED_DEFN(static, size_t, looplimit, (size_t x)) X(Y, ssh_des) \ X(Y, ssh_des_sshcom_ssh2) \ X(Y, ssh_aes256_sdctr) \ + X(Y, ssh_aes256_gcm) \ X(Y, ssh_aes256_cbc) \ X(Y, ssh_aes192_sdctr) \ + X(Y, ssh_aes192_gcm) \ X(Y, ssh_aes192_cbc) \ X(Y, ssh_aes128_sdctr) \ + X(Y, ssh_aes128_gcm) \ X(Y, ssh_aes128_cbc) \ X(Y, ssh_aes256_sdctr_sw) \ + X(Y, ssh_aes256_gcm_sw) \ X(Y, ssh_aes256_cbc_sw) \ X(Y, ssh_aes192_sdctr_sw) \ + X(Y, ssh_aes192_gcm_sw) \ X(Y, ssh_aes192_cbc_sw) \ X(Y, ssh_aes128_sdctr_sw) \ + X(Y, ssh_aes128_gcm_sw) \ X(Y, ssh_aes128_cbc_sw) \ IF_AES_NI(X(Y, ssh_aes256_sdctr_ni)) \ + IF_AES_NI(X(Y, ssh_aes256_gcm_ni)) \ IF_AES_NI(X(Y, ssh_aes256_cbc_ni)) \ IF_AES_NI(X(Y, ssh_aes192_sdctr_ni)) \ + IF_AES_NI(X(Y, ssh_aes192_gcm_ni)) \ IF_AES_NI(X(Y, ssh_aes192_cbc_ni)) \ IF_AES_NI(X(Y, ssh_aes128_sdctr_ni)) \ + IF_AES_NI(X(Y, ssh_aes128_gcm_ni)) \ IF_AES_NI(X(Y, ssh_aes128_cbc_ni)) \ IF_NEON_CRYPTO(X(Y, ssh_aes256_sdctr_neon)) \ + IF_NEON_CRYPTO(X(Y, ssh_aes256_gcm_neon)) \ IF_NEON_CRYPTO(X(Y, ssh_aes256_cbc_neon)) \ IF_NEON_CRYPTO(X(Y, ssh_aes192_sdctr_neon)) \ + IF_NEON_CRYPTO(X(Y, ssh_aes192_gcm_neon)) \ IF_NEON_CRYPTO(X(Y, ssh_aes192_cbc_neon)) \ IF_NEON_CRYPTO(X(Y, ssh_aes128_sdctr_neon)) \ + IF_NEON_CRYPTO(X(Y, ssh_aes128_gcm_neon)) \ IF_NEON_CRYPTO(X(Y, ssh_aes128_cbc_neon)) \ X(Y, ssh2_chacha20_poly1305) \ /* end of list */ @@ -317,9 +341,17 @@ VOLATILE_WRAPPED_DEFN(static, size_t, looplimit, (size_t x)) X(Y, ssh_hmac_sha256) \ /* end of list */ -#define ALL_MACS(X, Y) \ - SIMPLE_MACS(X, Y) \ - X(Y, poly1305) \ +#define ALL_MACS(X, Y) \ + SIMPLE_MACS(X, Y) \ + X(Y, poly1305) \ + X(Y, aesgcm_sw_sw) \ + X(Y, aesgcm_sw_refpoly) \ + IF_AES_NI(X(Y, aesgcm_ni_sw)) \ + IF_NEON_CRYPTO(X(Y, aesgcm_neon_sw)) \ + IF_CLMUL(X(Y, aesgcm_sw_clmul)) \ + IF_NEON_PMULL(X(Y, aesgcm_sw_neon)) \ + IF_AES_NI(IF_CLMUL(X(Y, aesgcm_ni_clmul))) \ + IF_NEON_CRYPTO(IF_NEON_PMULL(X(Y, aesgcm_neon_neon))) \ /* end of list */ #define MAC_TESTLIST(X, name) X(mac_ ## name) @@ -1473,6 +1505,58 @@ static void test_mac_poly1305(void) test_mac(&ssh2_poly1305, &ssh2_chacha20_poly1305); } +static void test_mac_aesgcm_sw_sw(void) +{ + test_mac(&ssh2_aesgcm_mac_sw, &ssh_aes128_gcm_sw); +} + +static void test_mac_aesgcm_sw_refpoly(void) +{ + test_mac(&ssh2_aesgcm_mac_ref_poly, &ssh_aes128_gcm_sw); +} + +#if HAVE_AES_NI +static void test_mac_aesgcm_ni_sw(void) +{ + test_mac(&ssh2_aesgcm_mac_sw, &ssh_aes128_gcm_ni); +} +#endif + +#if HAVE_NEON_CRYPTO +static void test_mac_aesgcm_neon_sw(void) +{ + test_mac(&ssh2_aesgcm_mac_sw, &ssh_aes128_gcm_neon); +} +#endif + +#if HAVE_CLMUL +static void test_mac_aesgcm_sw_clmul(void) +{ + test_mac(&ssh2_aesgcm_mac_clmul, &ssh_aes128_gcm_sw); +} +#endif + +#if HAVE_NEON_PMULL +static void test_mac_aesgcm_sw_neon(void) +{ + test_mac(&ssh2_aesgcm_mac_neon, &ssh_aes128_gcm_sw); +} +#endif + +#if HAVE_AES_NI && HAVE_CLMUL +static void test_mac_aesgcm_ni_clmul(void) +{ + test_mac(&ssh2_aesgcm_mac_clmul, &ssh_aes128_gcm_ni); +} +#endif + +#if HAVE_NEON_CRYPTO && HAVE_NEON_PMULL +static void test_mac_aesgcm_neon_neon(void) +{ + test_mac(&ssh2_aesgcm_mac_neon, &ssh_aes128_gcm_neon); +} +#endif + static void test_hash(const ssh_hashalg *halg) { ssh_hash *h = ssh_hash_new(halg); diff --git a/unix/utils/arm_arch_queries.c b/unix/utils/arm_arch_queries.c index d6dc97bc..c3dc286b 100644 --- a/unix/utils/arm_arch_queries.c +++ b/unix/utils/arm_arch_queries.c @@ -27,6 +27,21 @@ bool platform_aes_neon_available(void) #endif } +bool platform_pmull_neon_available(void) +{ +#if defined HWCAP_PMULL + return getauxval(AT_HWCAP) & HWCAP_PMULL; +#elif defined HWCAP2_PMULL + return getauxval(AT_HWCAP2) & HWCAP2_PMULL; +#elif defined __APPLE__ + SysctlResult res = test_sysctl_flag("hw.optional.arm.FEAT_PMULL"); + /* As above, treat 'missing' as enabled */ + return res != SYSCTL_OFF; +#else + return false; +#endif +} + bool platform_sha256_neon_available(void) { #if defined HWCAP_SHA2 diff --git a/windows/utils/arm_arch_queries.c b/windows/utils/arm_arch_queries.c index 439a59fb..b683ac15 100644 --- a/windows/utils/arm_arch_queries.c +++ b/windows/utils/arm_arch_queries.c @@ -20,6 +20,11 @@ bool platform_aes_neon_available(void) return IsProcessorFeaturePresent(PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE); } +bool platform_pmull_neon_available(void) +{ + return IsProcessorFeaturePresent(PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE); +} + bool platform_sha256_neon_available(void) { return IsProcessorFeaturePresent(PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE); -- cgit v1.2.3 From fb41eec4c1e2cbef0bf0f78e0a54c58cd4aef2f2 Mon Sep 17 00:00:00 2001 From: Jacob Nevins Date: Fri, 19 Aug 2022 13:33:52 +0100 Subject: Docs: acknowledge AES-GCM. --- doc/config.but | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/config.but b/doc/config.but index 28da9638..918b63d8 100644 --- a/doc/config.but +++ b/doc/config.but @@ -2760,7 +2760,8 @@ PuTTY currently supports the following algorithms: \b \i{ChaCha20-Poly1305}, a combined cipher and \i{MAC} (SSH-2 only) -\b \i{AES} (Rijndael) - 256, 192, or 128-bit SDCTR or CBC (SSH-2 only) +\b \i{AES} (Rijndael) - 256, 192, or 128-bit SDCTR or CBC, or +256 or 128-bit GCM (SSH-2 only) \b \i{Arcfour} (RC4) - 256 or 128-bit stream cipher (SSH-2 only) -- cgit v1.2.3 From 55d19f62951f6fd91350728fd3573bad87e663c6 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 21 Aug 2022 12:58:01 +0100 Subject: Fix session channel unthrottling in psusan and Uppity. I ran 'ls /usr/share/doc' in a psusan session the other day and the output hung part way through the long directory listing. This turned out to be because the ssh-connection channel window had run out temporarily, and PuTTY had sent a WINDOW_ADJUST extending it again, which the connection layer acted on by calling chan_set_input_wanted ... which sesschan was ignoring with a comment saying /* I don't think we need to do anything here */. Well, turns out we do need to. Implemented the simplest possible unblocking action. --- ssh/sesschan.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ssh/sesschan.c b/ssh/sesschan.c index b8f9311e..9776eaf2 100644 --- a/ssh/sesschan.c +++ b/ssh/sesschan.c @@ -292,7 +292,11 @@ static char *sesschan_log_close_msg(Channel *chan) static void sesschan_set_input_wanted(Channel *chan, bool wanted) { - /* I don't think we need to do anything here */ + sesschan *sess = container_of(chan, sesschan, chan); + /* Request the back end to resume sending input, if it had become + * throttled by the channel window shortening */ + if (wanted && sess->backend) + backend_unthrottle(sess->backend, 0); } static void sesschan_start_backend(sesschan *sess, const char *cmd) -- cgit v1.2.3 From baea34a5b2d9cd5734c1f4ebf8f4dc121d227820 Mon Sep 17 00:00:00 2001 From: Jacob Nevins Date: Mon, 29 Aug 2022 13:00:56 +0100 Subject: Reinstate __USE_MINGW_ANSI_STDIO for MinGW builds. This was lost in the mkfiles.pl->cmake transition (c19e7215dd). Without this, MinGW builds were providing format strings like %zu to a version of vsnprintf that didn't support them at runtime, so you'd get messages like "Pageant has zu SSH-2 keys". (-Wformat would have complained about the unknown %z format specifier, but even STRICT MinGW builds don't get those warnings, hm.) Now the runtime version understands %zu. I've reviewed the other compile-time definitions that were unique to the old Makefile.mgw, and decided not to reinstate any of them: WIN32S_COMPAT: leave it out. This came in in bd4b8c1285. Rationale from Joris van Rantwijk in email 2000-01-24: "Use -DWIN32S_COMPAT to avoid a linking error about SystemPowerStatus". But that problem was solved another way within 8 months, and WIN32S_COMPAT removed from the code, in 76746a7d61, so this wart had been redundant since then. _NO_OLDNAMES: decided not to add anything back for this. This actually does nothing with the mingw-w64 fork (which seems to spell it NO_OLDNAMES), although current versions of original-mingw do also still spell it _NO_OLDNAMES. They both seem to be about suppressing a behaviour where a load of "non-ANSI" names like strdup get redirected to invoke _strdup in MS' libraries. Again, original rationale is from Joris van Rantwijk: "Compile and link with -mno-cygwin (and -D_NO_OLDNAMES) to get executables that don't need the Cygwin DLL file." Since I don't know of any behavioural differences that this causes (unlike vsnprintf/_vsnprintf), and it's not obviously causing trouble for me, continue to leave things in the default state. --- cmake/toolchain-mingw.cmake | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmake/toolchain-mingw.cmake b/cmake/toolchain-mingw.cmake index 013dbeb5..2e0bc669 100644 --- a/cmake/toolchain-mingw.cmake +++ b/cmake/toolchain-mingw.cmake @@ -8,3 +8,5 @@ set(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc) set(CMAKE_RC_COMPILER x86_64-w64-mingw32-windres) set(CMAKE_AR x86_64-w64-mingw32-ar) set(CMAKE_RANLIB x86_64-w64-mingw32-ranlib) + +add_compile_definitions(__USE_MINGW_ANSI_STDIO) -- cgit v1.2.3 From d2e982efa727544b54628e37916c2497fe72cf21 Mon Sep 17 00:00:00 2001 From: Jacob Nevins Date: Tue, 30 Aug 2022 17:27:50 +0100 Subject: openssh-certs: Avoid C99 strftime() specifiers. These were introduced in 34d01e1b65 to pretty-print certificate validity ranges. But Microsoft's C runtime took a while to catch up with C99 -- stackoverflow claims that VS2013 and earlier don't support these specifiers -- so it's possible to end up with PuTTY executables that misdisplay these dates. Also, the mingw-w64 toolchain's -Wformat complains about these specifiers, at least on Debian buster, presumably for the same reason. Since the specifiers in question have exact pre-C99 replacements, it seems easiest just to use those. --- crypto/openssh-certs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crypto/openssh-certs.c b/crypto/openssh-certs.c index 07f412b9..979b5488 100644 --- a/crypto/openssh-certs.c +++ b/crypto/openssh-certs.c @@ -581,7 +581,7 @@ static void opensshcert_time_to_iso8601(BinarySink *bs, uint64_t time) time_t t = time; char buf[256]; put_data(bs, buf, strftime(buf, sizeof(buf), - "%a %F %T %Z", localtime(&t))); + "%a %Y-%m-%d %H:%M:%S %Z", localtime(&t))); } static void opensshcert_string_list_key_components( -- cgit v1.2.3 From c6d7ffda68ee9ec82c4235efa5c6559889ba6d86 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 29 Aug 2022 13:43:07 +0100 Subject: Fix crash in GSSAPI key exchange. Introduced recently by commit 42740a54550476e, in which I decided to call ssh_key_cache_str() even on certified host keys. But that call was conditional on s->hkey being non-NULL (which happens in GSS KEX) as well as on it not being certified, and I managed to absentmindedly remove _both_ conditions. As a result we got a null-pointer dereference on any GSS kex. --- ssh/kex2-client.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ssh/kex2-client.c b/ssh/kex2-client.c index 5935ef29..a437d92e 100644 --- a/ssh/kex2-client.c +++ b/ssh/kex2-client.c @@ -718,7 +718,7 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) } } - s->keystr = ssh_key_cache_str(s->hkey); + s->keystr = s->hkey ? ssh_key_cache_str(s->hkey) : NULL; #ifndef NO_GSSAPI if (s->gss_kex_used) { /* -- cgit v1.2.3 From b88057d09dd84d8b8bba1af391d397ab28106d50 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 29 Aug 2022 11:28:31 +0100 Subject: ECDH kex: remove pointless NULL check. None of the constructors can fail and return NULL. I think this must have come in with a lot of other unnecessary null-pointer checks when ECC support was first added. I've got rid of most of them since then, but that one apparently escaped my notice. --- ssh/kex2-client.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/ssh/kex2-client.c b/ssh/kex2-client.c index a437d92e..0f24881a 100644 --- a/ssh/kex2-client.c +++ b/ssh/kex2-client.c @@ -193,11 +193,6 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) s->ppl.bpp->pls->kctx = SSH2_PKTCTX_ECDHKEX; s->ecdh_key = ecdh_key_new(s->kex_alg, false); - if (!s->ecdh_key) { - ssh_sw_abort(s->ppl.ssh, "Unable to generate key for ECDH"); - *aborted = true; - return; - } pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEX_ECDH_INIT); { -- cgit v1.2.3 From 031d86ed5ba4dd4f7b61af483a20f48f7811f2ab Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 29 Aug 2022 07:44:39 +0100 Subject: Add RFC8268 / RFC3126 Diffie-Hellman group{15,16,17,18}. These are a new set of larger integer Diffie-Hellman fixed groups, using SHA-512 as the hash. --- config.c | 18 ++++-- crypto/diffie-hellman.c | 168 ++++++++++++++++++++++++++++++++++++++++++++++++ putty.h | 4 ++ settings.c | 8 ++- ssh.h | 19 +++++- ssh/transport2.c | 22 ++++++- test/testcrypt-enum.h | 4 ++ 7 files changed, 230 insertions(+), 13 deletions(-) diff --git a/config.c b/config.c index 747af814..03043912 100644 --- a/config.c +++ b/config.c @@ -564,14 +564,18 @@ static void kexlist_handler(dlgcontrol *ctrl, dlgparam *dlg, int i; static const struct { const 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 }, - { "ECDH key exchange", KEX_ECDH }, + { "Diffie-Hellman group 1 (1024-bit)", KEX_DHGROUP1 }, + { "Diffie-Hellman group 14 (2048-bit)", KEX_DHGROUP14 }, + { "Diffie-Hellman group 15 (3072-bit)", KEX_DHGROUP15 }, + { "Diffie-Hellman group 16 (4096-bit)", KEX_DHGROUP16 }, + { "Diffie-Hellman group 17 (6144-bit)", KEX_DHGROUP17 }, + { "Diffie-Hellman group 18 (8192-bit)", KEX_DHGROUP18 }, + { "Diffie-Hellman group exchange", KEX_DHGEX }, + { "RSA-based key exchange", KEX_RSA }, + { "ECDH key exchange", KEX_ECDH }, { "NTRU Prime / Curve25519 hybrid kex" - " (quantum-resistant)", KEX_NTRU_HYBRID }, - { "-- warn below here --", KEX_WARN } + " (quantum-resistant)", KEX_NTRU_HYBRID }, + { "-- warn below here --", KEX_WARN } }; /* Set up the "kex preference" box. */ diff --git a/crypto/diffie-hellman.c b/crypto/diffie-hellman.c index 78e6fab2..772f297a 100644 --- a/crypto/diffie-hellman.c +++ b/crypto/diffie-hellman.c @@ -35,6 +35,42 @@ spigot -B16 '2^2048 - 2^1984 - 1 + 2^64 * ( floor(2^1918 pi) + 124476 )' ctx->g = mp_from_integer(2); } +static void dh_group15_construct(dh_ctx *ctx) +{ + /* Command to recompute, from the expression in RFC 3526 section 4: +spigot -B16 '2^3072 - 2^3008 - 1 + 2^64 * ( floor(2^2942 pi) + 1690314 )' + */ + ctx->p = MP_LITERAL(0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF); + ctx->g = mp_from_integer(2); +} + +static void dh_group16_construct(dh_ctx *ctx) +{ + /* Command to recompute, from the expression in RFC 3526 section 5: +spigot -B16 '2^4096 - 2^4032 - 1 + 2^64 * ( floor(2^3966 pi) + 240904 )' + */ + ctx->p = MP_LITERAL(0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199FFFFFFFFFFFFFFFF); + ctx->g = mp_from_integer(2); +} + +static void dh_group17_construct(dh_ctx *ctx) +{ + /* Command to recompute, from the expression in RFC 3526 section 6: +spigot -B16 '2^6144 - 2^6080 - 1 + 2^64 * ( floor(2^6014 pi) + 929484 )' + */ + ctx->p = MP_LITERAL(0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AACC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DCC4024FFFFFFFFFFFFFFFF); + ctx->g = mp_from_integer(2); +} + +static void dh_group18_construct(dh_ctx *ctx) +{ + /* Command to recompute, from the expression in RFC 3526 section 7: +spigot -B16 '2^8192 - 2^8128 - 1 + 2^64 * ( floor(2^8062 pi) + 4743158 )' + */ + ctx->p = MP_LITERAL(0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AACC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DBE115974A3926F12FEE5E438777CB6A932DF8CD8BEC4D073B931BA3BC832B68D9DD300741FA7BF8AFC47ED2576F6936BA424663AAB639C5AE4F5683423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD922222E04A4037C0713EB57A81A23F0C73473FC646CEA306B4BCBC8862F8385DDFA9D4B7FA2C087E879683303ED5BDD3A062B3CF5B3A278A66D2A13F83F44F82DDF310EE074AB6A364597E899A0255DC164F31CC50846851DF9AB48195DED7EA1B1D510BD7EE74D73FAF36BC31ECFA268359046F4EB879F924009438B481C6CD7889A002ED5EE382BC9190DA6FC026E479558E4475677E9AA9E3050E2765694DFC81F56E880B96E7160C980DD98EDD3DFFFFFFFFFFFFFFFFF); + ctx->g = mp_from_integer(2); +} + static const struct dh_extra extra_group1 = { false, dh_group1_construct, }; @@ -53,6 +89,86 @@ static const ssh_kex *const group1_list[] = { const ssh_kexes ssh_diffiehellman_group1 = { lenof(group1_list), group1_list }; +static const struct dh_extra extra_group18 = { + false, dh_group18_construct, +}; + +const ssh_kex ssh_diffiehellman_group18_sha512 = { + .name = "diffie-hellman-group18-sha512", + .groupname = "group18", + .main_type = KEXTYPE_DH, + .hash = &ssh_sha512, + .extra = &extra_group18, +}; + +static const ssh_kex *const group18_list[] = { + &ssh_diffiehellman_group18_sha512, +}; + +const ssh_kexes ssh_diffiehellman_group18 = { + lenof(group18_list), group18_list +}; + +static const struct dh_extra extra_group17 = { + false, dh_group17_construct, +}; + +const ssh_kex ssh_diffiehellman_group17_sha512 = { + .name = "diffie-hellman-group17-sha512", + .groupname = "group17", + .main_type = KEXTYPE_DH, + .hash = &ssh_sha512, + .extra = &extra_group17, +}; + +static const ssh_kex *const group17_list[] = { + &ssh_diffiehellman_group17_sha512, +}; + +const ssh_kexes ssh_diffiehellman_group17 = { + lenof(group17_list), group17_list +}; + +static const struct dh_extra extra_group16 = { + false, dh_group16_construct, +}; + +const ssh_kex ssh_diffiehellman_group16_sha512 = { + .name = "diffie-hellman-group16-sha512", + .groupname = "group16", + .main_type = KEXTYPE_DH, + .hash = &ssh_sha512, + .extra = &extra_group16, +}; + +static const ssh_kex *const group16_list[] = { + &ssh_diffiehellman_group16_sha512, +}; + +const ssh_kexes ssh_diffiehellman_group16 = { + lenof(group16_list), group16_list +}; + +static const struct dh_extra extra_group15 = { + false, dh_group15_construct, +}; + +const ssh_kex ssh_diffiehellman_group15_sha512 = { + .name = "diffie-hellman-group15-sha512", + .groupname = "group15", + .main_type = KEXTYPE_DH, + .hash = &ssh_sha512, + .extra = &extra_group15, +}; + +static const ssh_kex *const group15_list[] = { + &ssh_diffiehellman_group15_sha512, +}; + +const ssh_kexes ssh_diffiehellman_group15 = { + lenof(group15_list), group15_list +}; + static const struct dh_extra extra_group14 = { false, dh_group14_construct, }; @@ -129,6 +245,58 @@ static const ssh_kex ssh_gssk5_diffiehellman_gex_sha1 = { .extra = &extra_gex, }; +static const ssh_kex ssh_gssk5_diffiehellman_group18_sha512 = { + .name = "gss-group18-sha512-" GSS_KRB5_OID_HASH, + .groupname = "group18", + .main_type = KEXTYPE_GSS, + .hash = &ssh_sha512, + .extra = &extra_group18, +}; + +static const ssh_kex ssh_gssk5_diffiehellman_group17_sha512 = { + .name = "gss-group17-sha512-" GSS_KRB5_OID_HASH, + .groupname = "group17", + .main_type = KEXTYPE_GSS, + .hash = &ssh_sha512, + .extra = &extra_group17, +}; + +static const ssh_kex ssh_gssk5_diffiehellman_group16_sha512 = { + .name = "gss-group16-sha512-" GSS_KRB5_OID_HASH, + .groupname = "group16", + .main_type = KEXTYPE_GSS, + .hash = &ssh_sha512, + .extra = &extra_group16, +}; + +static const ssh_kex ssh_gssk5_diffiehellman_group15_sha512 = { + .name = "gss-group15-sha512-" GSS_KRB5_OID_HASH, + .groupname = "group15", + .main_type = KEXTYPE_GSS, + .hash = &ssh_sha512, + .extra = &extra_group15, +}; + +static const ssh_kex ssh_gssk5_diffiehellman_group14_sha256 = { + .name = "gss-group14-sha256-" GSS_KRB5_OID_HASH, + .groupname = "group14", + .main_type = KEXTYPE_GSS, + .hash = &ssh_sha256, + .extra = &extra_group14, +}; + +static const ssh_kex *const gssk5_sha2_kex_list[] = { + &ssh_gssk5_diffiehellman_group18_sha512, + &ssh_gssk5_diffiehellman_group17_sha512, + &ssh_gssk5_diffiehellman_group16_sha512, + &ssh_gssk5_diffiehellman_group15_sha512, + &ssh_gssk5_diffiehellman_group14_sha256, +}; + +const ssh_kexes ssh_gssk5_sha2_kex = { + lenof(gssk5_sha2_kex_list), gssk5_sha2_kex_list +}; + static const ssh_kex ssh_gssk5_diffiehellman_group14_sha1 = { .name = "gss-group14-sha1-" GSS_KRB5_OID_HASH, .groupname = "group14", diff --git a/putty.h b/putty.h index 175f5969..9ebf4aed 100644 --- a/putty.h +++ b/putty.h @@ -422,6 +422,10 @@ enum { KEX_WARN, KEX_DHGROUP1, KEX_DHGROUP14, + KEX_DHGROUP15, + KEX_DHGROUP16, + KEX_DHGROUP17, + KEX_DHGROUP18, KEX_DHGEX, KEX_RSA, KEX_ECDH, diff --git a/settings.c b/settings.c index cc2176ce..d2cd6a7e 100644 --- a/settings.c +++ b/settings.c @@ -33,6 +33,10 @@ static const struct keyvalwhere kexnames[] = { { "ecdh", KEX_ECDH, -1, +1 }, /* This name is misleading: it covers both SHA-256 and SHA-1 variants */ { "dh-gex-sha1", KEX_DHGEX, -1, -1 }, + { "dh-group18-sha512", KEX_DHGROUP18, -1, -1 }, + { "dh-group17-sha512", KEX_DHGROUP17, -1, -1 }, + { "dh-group16-sha512", KEX_DHGROUP16, -1, -1 }, + { "dh-group15-sha512", KEX_DHGROUP15, -1, -1 }, { "dh-group14-sha1", KEX_DHGROUP14, -1, -1 }, { "dh-group1-sha1", KEX_DHGROUP1, KEX_WARN, +1 }, { "rsa", KEX_RSA, KEX_WARN, -1 }, @@ -971,9 +975,9 @@ void load_open_settings(settings_r *sesskey, Conf *conf) * a server which offered it then choked, but we never got * a server version string or any other reports. */ const char *default_kexes, - *normal_default = "ecdh,dh-gex-sha1,dh-group14-sha1,rsa," + *normal_default = "ecdh,dh-gex-sha1,dh-group18-sha512,dh-group17-sha512,dh-group16-sha512,dh-group15-sha512,dh-group14-sha1,rsa," "WARN,dh-group1-sha1", - *bugdhgex2_default = "ecdh,dh-group14-sha1,rsa," + *bugdhgex2_default = "ecdh,dh-group18-sha512,dh-group17-sha512,dh-group16-sha512,dh-group15-sha512,dh-group14-sha1,rsa," "WARN,dh-group1-sha1,dh-gex-sha1"; char *raw; i = 2 - gppi_raw(sesskey, "BugDHGEx2", 0); diff --git a/ssh.h b/ssh.h index d3ee5065..b2955e6a 100644 --- a/ssh.h +++ b/ssh.h @@ -820,14 +820,20 @@ void hash_simple(const ssh_hashalg *alg, ptrlen data, void *output); struct ssh_kex { const char *name, *groupname; - enum { KEXTYPE_DH, KEXTYPE_RSA, KEXTYPE_ECDH, KEXTYPE_GSS } main_type; + enum { KEXTYPE_DH, KEXTYPE_RSA, KEXTYPE_ECDH, + KEXTYPE_GSS, KEXTYPE_GSS_ECDH } main_type; const ssh_hashalg *hash; union { /* publicly visible data for each type */ - const ecdh_keyalg *ecdh_vt; /* for KEXTYPE_ECDH */ + const ecdh_keyalg *ecdh_vt; /* for KEXTYPE_ECDH, KEXTYPE_GSS_ECDH */ }; const void *extra; /* private to the kex methods */ }; +static inline bool kex_is_gss(const struct ssh_kex *kex) +{ + return kex->main_type == KEXTYPE_GSS || kex->main_type == KEXTYPE_GSS_ECDH; +} + struct ssh_kexes { int nkexes; const ssh_kex *const *list; @@ -1139,11 +1145,20 @@ extern const ssh_hashalg ssh_shake256_114bytes; extern const ssh_hashalg ssh_blake2b; extern const ssh_kexes ssh_diffiehellman_group1; extern const ssh_kexes ssh_diffiehellman_group14; +extern const ssh_kexes ssh_diffiehellman_group15; +extern const ssh_kexes ssh_diffiehellman_group16; +extern const ssh_kexes ssh_diffiehellman_group17; +extern const ssh_kexes ssh_diffiehellman_group18; extern const ssh_kexes ssh_diffiehellman_gex; extern const ssh_kex ssh_diffiehellman_group1_sha1; extern const ssh_kex ssh_diffiehellman_group14_sha256; extern const ssh_kex ssh_diffiehellman_group14_sha1; +extern const ssh_kex ssh_diffiehellman_group15_sha512; +extern const ssh_kex ssh_diffiehellman_group16_sha512; +extern const ssh_kex ssh_diffiehellman_group17_sha512; +extern const ssh_kex ssh_diffiehellman_group18_sha512; extern const ssh_kexes ssh_gssk5_sha1_kex; +extern const ssh_kexes ssh_gssk5_sha2_kex; extern const ssh_kexes ssh_rsa_kex; extern const ssh_kex ssh_ec_kex_curve25519; extern const ssh_kex ssh_ec_kex_curve448; diff --git a/ssh/transport2.c b/ssh/transport2.c index aba8cd0b..2fd8f65d 100644 --- a/ssh/transport2.c +++ b/ssh/transport2.c @@ -508,7 +508,7 @@ static void ssh2_write_kexinit_lists( bool warn; int n_preferred_kex; - const ssh_kexes *preferred_kex[KEX_MAX + 1]; /* +1 for GSSAPI */ + const ssh_kexes *preferred_kex[KEX_MAX + 2]; /* +2 for GSSAPI */ int n_preferred_hk; int preferred_hk[HK_MAX]; int n_preferred_ciphers; @@ -523,14 +523,32 @@ static void ssh2_write_kexinit_lists( * Set up the preferred key exchange. (NULL => warn below here) */ n_preferred_kex = 0; - if (can_gssapi_keyex) + if (can_gssapi_keyex) { + preferred_kex[n_preferred_kex++] = &ssh_gssk5_sha2_kex; preferred_kex[n_preferred_kex++] = &ssh_gssk5_sha1_kex; + } for (i = 0; i < KEX_MAX; i++) { switch (conf_get_int_int(conf, CONF_ssh_kexlist, i)) { case KEX_DHGEX: preferred_kex[n_preferred_kex++] = &ssh_diffiehellman_gex; break; + case KEX_DHGROUP18: + preferred_kex[n_preferred_kex++] = + &ssh_diffiehellman_group18; + break; + case KEX_DHGROUP17: + preferred_kex[n_preferred_kex++] = + &ssh_diffiehellman_group17; + break; + case KEX_DHGROUP16: + preferred_kex[n_preferred_kex++] = + &ssh_diffiehellman_group16; + break; + case KEX_DHGROUP15: + preferred_kex[n_preferred_kex++] = + &ssh_diffiehellman_group15; + break; case KEX_DHGROUP14: preferred_kex[n_preferred_kex++] = &ssh_diffiehellman_group14; diff --git a/test/testcrypt-enum.h b/test/testcrypt-enum.h index 90458fc0..d81f46c7 100644 --- a/test/testcrypt-enum.h +++ b/test/testcrypt-enum.h @@ -120,6 +120,10 @@ END_ENUM_TYPE(cipheralg) BEGIN_ENUM_TYPE(dh_group) ENUM_VALUE("group1", &ssh_diffiehellman_group1_sha1) ENUM_VALUE("group14", &ssh_diffiehellman_group14_sha256) + ENUM_VALUE("group15", &ssh_diffiehellman_group15_sha512) + ENUM_VALUE("group16", &ssh_diffiehellman_group16_sha512) + ENUM_VALUE("group17", &ssh_diffiehellman_group17_sha512) + ENUM_VALUE("group18", &ssh_diffiehellman_group18_sha512) END_ENUM_TYPE(dh_group) BEGIN_ENUM_TYPE(ecdh_alg) -- cgit v1.2.3 From cec8c87626b3433907d214c91a072f75fbd06c91 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 29 Aug 2022 11:35:34 +0100 Subject: Support elliptic-curve Diffie-Hellman GSS KEX. This is surprisingly simple, because it wasn't necessary to touch the GSS parts at all. Nothing changes about the message formats between integer DH and ECDH in GSS KEX, except that the mpints sent back and forth as part of integer DH are replaced by the opaque strings used in ECDH. So I've invented a new KEXTYPE and made it control a bunch of small conditionals in the middle of the GSS KEX code, leaving the rest unchanged. --- crypto/diffie-hellman.c | 14 ------- crypto/ecc-ssh.c | 43 ++++++++++++++++++++ ssh.h | 15 +++++++ ssh/kex2-client.c | 104 ++++++++++++++++++++++++++++++++++-------------- ssh/transport2.c | 5 ++- ssh/transport2.h | 1 + 6 files changed, 138 insertions(+), 44 deletions(-) diff --git a/crypto/diffie-hellman.c b/crypto/diffie-hellman.c index 772f297a..bf262f2d 100644 --- a/crypto/diffie-hellman.c +++ b/crypto/diffie-hellman.c @@ -223,20 +223,6 @@ static const ssh_kex *const gex_list[] = { const ssh_kexes ssh_diffiehellman_gex = { lenof(gex_list), gex_list }; -/* - * Suffix on GSSAPI SSH protocol identifiers that indicates Kerberos 5 - * as the mechanism. - * - * This suffix is the base64-encoded MD5 hash of the byte sequence - * 06 09 2A 86 48 86 F7 12 01 02 02, which in turn is the ASN.1 DER - * encoding of the object ID 1.2.840.113554.1.2.2 which designates - * Kerberos v5. - * - * (The same encoded OID, minus the two-byte DER header, is defined in - * ssh/pgssapi.c as GSS_MECH_KRB5.) - */ -#define GSS_KRB5_OID_HASH "toWM5Slw5Ew8Mqkay+al2g==" - static const ssh_kex ssh_gssk5_diffiehellman_gex_sha1 = { .name = "gss-gex-sha1-" GSS_KRB5_OID_HASH, .groupname = NULL, diff --git a/crypto/ecc-ssh.c b/crypto/ecc-ssh.c index b51caab0..a43cb841 100644 --- a/crypto/ecc-ssh.c +++ b/crypto/ecc-ssh.c @@ -1644,6 +1644,14 @@ const ssh_kex ssh_ec_kex_curve25519_libssh = { .ecdh_vt = &ssh_ecdhkex_m_alg, .extra = &kex_extra_curve25519, }; +/* GSSAPI variant */ +static const ssh_kex ssh_ec_kex_curve25519_gss = { + .name = "gss-curve25519-sha256-" GSS_KRB5_OID_HASH, + .main_type = KEXTYPE_GSS_ECDH, + .hash = &ssh_sha256, + .ecdh_vt = &ssh_ecdhkex_m_alg, + .extra = &kex_extra_curve25519, +}; static const struct eckex_extra kex_extra_curve448 = { ec_curve448 }; const ssh_kex ssh_ec_kex_curve448 = { @@ -1669,6 +1677,14 @@ const ssh_kex ssh_ec_kex_nistp256 = { .ecdh_vt = &ssh_ecdhkex_w_alg, .extra = &kex_extra_nistp256, }; +/* GSSAPI variant */ +static const ssh_kex ssh_ec_kex_nistp256_gss = { + .name = "gss-nistp256-sha256-" GSS_KRB5_OID_HASH, + .main_type = KEXTYPE_GSS_ECDH, + .hash = &ssh_sha256, + .ecdh_vt = &ssh_ecdhkex_w_alg, + .extra = &kex_extra_nistp256, +}; static const struct eckex_extra kex_extra_nistp384 = { ec_p384 }; const ssh_kex ssh_ec_kex_nistp384 = { @@ -1678,6 +1694,14 @@ const ssh_kex ssh_ec_kex_nistp384 = { .ecdh_vt = &ssh_ecdhkex_w_alg, .extra = &kex_extra_nistp384, }; +/* GSSAPI variant */ +static const ssh_kex ssh_ec_kex_nistp384_gss = { + .name = "gss-nistp384-sha384-" GSS_KRB5_OID_HASH, + .main_type = KEXTYPE_GSS_ECDH, + .hash = &ssh_sha384, + .ecdh_vt = &ssh_ecdhkex_w_alg, + .extra = &kex_extra_nistp384, +}; static const struct eckex_extra kex_extra_nistp521 = { ec_p521 }; const ssh_kex ssh_ec_kex_nistp521 = { @@ -1687,6 +1711,14 @@ const ssh_kex ssh_ec_kex_nistp521 = { .ecdh_vt = &ssh_ecdhkex_w_alg, .extra = &kex_extra_nistp521, }; +/* GSSAPI variant */ +static const ssh_kex ssh_ec_kex_nistp521_gss = { + .name = "gss-nistp521-sha512-" GSS_KRB5_OID_HASH, + .main_type = KEXTYPE_GSS_ECDH, + .hash = &ssh_sha512, + .ecdh_vt = &ssh_ecdhkex_w_alg, + .extra = &kex_extra_nistp521, +}; static const ssh_kex *const ec_kex_list[] = { &ssh_ec_kex_curve448, @@ -1699,6 +1731,17 @@ static const ssh_kex *const ec_kex_list[] = { const ssh_kexes ssh_ecdh_kex = { lenof(ec_kex_list), ec_kex_list }; +static const ssh_kex *const ec_gss_kex_list[] = { + &ssh_ec_kex_curve25519_gss, + &ssh_ec_kex_nistp521_gss, + &ssh_ec_kex_nistp384_gss, + &ssh_ec_kex_nistp256_gss, +}; + +const ssh_kexes ssh_gssk5_ecdh_kex = { + lenof(ec_gss_kex_list), ec_gss_kex_list +}; + /* ---------------------------------------------------------------------- * Helper functions for finding key algorithms and returning auxiliary * data. diff --git a/ssh.h b/ssh.h index b2955e6a..dbdd7eb1 100644 --- a/ssh.h +++ b/ssh.h @@ -993,6 +993,20 @@ static inline bool ecdh_key_getkey(ecdh_key *key, ptrlen remoteKey, static inline char *ecdh_keyalg_description(const ssh_kex *kex) { return kex->ecdh_vt->description(kex); } +/* + * Suffix on GSSAPI SSH protocol identifiers that indicates Kerberos 5 + * as the mechanism. + * + * This suffix is the base64-encoded MD5 hash of the byte sequence + * 06 09 2A 86 48 86 F7 12 01 02 02, which in turn is the ASN.1 DER + * encoding of the object ID 1.2.840.113554.1.2.2 which designates + * Kerberos v5. + * + * (The same encoded OID, minus the two-byte DER header, is defined in + * ssh/pgssapi.c as GSS_MECH_KRB5.) + */ +#define GSS_KRB5_OID_HASH "toWM5Slw5Ew8Mqkay+al2g==" + /* * Enumeration of signature flags from draft-miller-ssh-agent-02 */ @@ -1159,6 +1173,7 @@ extern const ssh_kex ssh_diffiehellman_group17_sha512; extern const ssh_kex ssh_diffiehellman_group18_sha512; extern const ssh_kexes ssh_gssk5_sha1_kex; extern const ssh_kexes ssh_gssk5_sha2_kex; +extern const ssh_kexes ssh_gssk5_ecdh_kex; extern const ssh_kexes ssh_rsa_kex; extern const ssh_kex ssh_ec_kex_curve25519; extern const ssh_kex ssh_ec_kex_curve448; diff --git a/ssh/kex2-client.c b/ssh/kex2-client.c index 0f24881a..3fca162e 100644 --- a/ssh/kex2-client.c +++ b/ssh/kex2-client.c @@ -248,7 +248,7 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) ecdh_key_free(s->ecdh_key); s->ecdh_key = NULL; #ifndef NO_GSSAPI - } else if (s->kex_alg->main_type == KEXTYPE_GSS) { + } else if (kex_is_gss(s->kex_alg)) { ptrlen data; s->ppl.bpp->pls->kctx = SSH2_PKTCTX_GSSKEX; @@ -276,14 +276,25 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) if (s->nbits > s->kex_alg->hash->hlen * 8) s->nbits = s->kex_alg->hash->hlen * 8; - if (dh_is_gex(s->kex_alg)) { + assert(!s->ecdh_key); + assert(!s->dh_ctx); + + if (s->kex_alg->main_type == KEXTYPE_GSS_ECDH) { + s->ecdh_key = ecdh_key_new(s->kex_alg, false); + + char *desc = ecdh_keyalg_description(s->kex_alg); + ppl_logevent("Doing GSSAPI (with Kerberos V5) %s with hash %s", + desc, ssh_hash_alg(s->exhash)->text_name); + sfree(desc); + } else if (dh_is_gex(s->kex_alg)) { /* * Work out how big a DH group we will need to allow that * much data. */ s->pbits = 512 << ((s->nbits - 1) / 64); ppl_logevent("Doing GSSAPI (with Kerberos V5) Diffie-Hellman " - "group exchange, with minimum %d bits", s->pbits); + "group exchange, with minimum %d bits, and hash %s", + s->pbits, ssh_hash_alg(s->exhash)->text_name); pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEXGSS_GROUPREQ); put_uint32(pktout, s->pbits); /* min */ put_uint32(pktout, s->pbits); /* preferred */ @@ -314,14 +325,19 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) } else { s->dh_ctx = dh_setup_group(s->kex_alg); ppl_logevent("Using GSSAPI (with Kerberos V5) Diffie-Hellman with" - " standard group \"%s\"", s->kex_alg->groupname); + " standard group \"%s\" and hash %s", + s->kex_alg->groupname, + ssh_hash_alg(s->exhash)->text_name); } - ppl_logevent("Doing GSSAPI (with Kerberos V5) Diffie-Hellman key " - "exchange with hash %s", ssh_hash_alg(s->exhash)->text_name); /* Now generate e for Diffie-Hellman. */ seat_set_busy_status(s->ppl.seat, BUSY_CPU); - s->e = dh_create_e(s->dh_ctx); + if (s->ecdh_key) { + s->ebuf = strbuf_new_nm(); + ecdh_key_getpublic(s->ecdh_key, BinarySink_UPCAST(s->ebuf)); + } else { + s->e = dh_create_e(s->dh_ctx); + } if (s->shgss->lib->gsslogmsg) ppl_logevent("%s", s->shgss->lib->gsslogmsg); @@ -385,7 +401,11 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) } put_string(pktout, s->gss_sndtok.value, s->gss_sndtok.length); - put_mp_ssh2(pktout, s->e); + if (s->ecdh_key) { + put_stringpl(pktout, ptrlen_from_strbuf(s->ebuf)); + } else { + put_mp_ssh2(pktout, s->e); + } pq_push(s->ppl.out_pq, pktout); s->shgss->lib->free_tok(s->shgss->lib, &s->gss_sndtok); ppl_logevent("GSSAPI key exchange initialised"); @@ -412,7 +432,11 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) continue; case SSH2_MSG_KEXGSS_COMPLETE: s->complete_rcvd = true; - s->f = get_mp_ssh2(pktin); + if (s->ecdh_key) { + s->fbuf = strbuf_dup_nm(get_string(pktin)); + } else { + s->f = get_mp_ssh2(pktin); + } data = get_string(pktin); s->mic.value = (char *)data.ptr; s->mic.length = data.len; @@ -475,7 +499,16 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) s->gss_stat == SSH_GSS_S_CONTINUE_NEEDED || !s->complete_rcvd); - { + if (s->ecdh_key) { + bool ok = ecdh_key_getkey(s->ecdh_key, ptrlen_from_strbuf(s->fbuf), + BinarySink_UPCAST(s->kex_shared_secret)); + if (!ok) { + ssh_proto_error(s->ppl.ssh, "Received invalid elliptic curve " + "point in GSSAPI ECDH reply"); + *aborted = true; + return; + } + } else { const char *err = dh_validate_f(s->dh_ctx, s->f); if (err) { ssh_proto_error(s->ppl.ssh, "GSSAPI reply failed " @@ -483,10 +516,10 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) *aborted = true; return; } + mp_int *K = dh_find_K(s->dh_ctx, s->f); + put_mp_ssh2(s->kex_shared_secret, K); + mp_free(K); } - mp_int *K = dh_find_K(s->dh_ctx, s->f); - put_mp_ssh2(s->kex_shared_secret, K); - mp_free(K); /* We assume everything from now on will be quick, and it might * involve user interaction. */ @@ -494,26 +527,39 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) if (!s->hkey) put_stringz(s->exhash, ""); - if (dh_is_gex(s->kex_alg)) { - /* min, preferred, max */ - put_uint32(s->exhash, s->pbits); - put_uint32(s->exhash, s->pbits); - put_uint32(s->exhash, s->pbits * 2); - put_mp_ssh2(s->exhash, s->p); - put_mp_ssh2(s->exhash, s->g); + if (s->ecdh_key) { + put_stringpl(s->exhash, ptrlen_from_strbuf(s->ebuf)); + put_stringpl(s->exhash, ptrlen_from_strbuf(s->fbuf)); + } else { + if (dh_is_gex(s->kex_alg)) { + /* min, preferred, max */ + put_uint32(s->exhash, s->pbits); + put_uint32(s->exhash, s->pbits); + put_uint32(s->exhash, s->pbits * 2); + + put_mp_ssh2(s->exhash, s->p); + put_mp_ssh2(s->exhash, s->g); + } + put_mp_ssh2(s->exhash, s->e); + put_mp_ssh2(s->exhash, s->f); } - put_mp_ssh2(s->exhash, s->e); - put_mp_ssh2(s->exhash, s->f); /* * MIC verification is done below, after we compute the hash * used as the MIC input. */ - dh_cleanup(s->dh_ctx); - s->dh_ctx = NULL; - mp_free(s->f); s->f = NULL; + if (s->ecdh_key) { + ecdh_key_free(s->ecdh_key); + s->ecdh_key = NULL; + strbuf_free(s->ebuf); s->ebuf = NULL; + strbuf_free(s->fbuf); s->fbuf = NULL; + } else { + dh_cleanup(s->dh_ctx); + s->dh_ctx = NULL; + mp_free(s->f); s->f = NULL; + } if (dh_is_gex(s->kex_alg)) { mp_free(s->g); s->g = NULL; mp_free(s->p); s->p = NULL; @@ -646,7 +692,7 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) ssh2transport_finalise_exhash(s); #ifndef NO_GSSAPI - if (s->kex_alg->main_type == KEXTYPE_GSS) { + if (kex_is_gss(s->kex_alg)) { Ssh_gss_buf gss_buf; SSH_GSS_CLEAR_BUF(&s->gss_buf); @@ -694,7 +740,7 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) s->dh_ctx = NULL; /* In GSS keyex there's no hostkey signature to verify */ - if (s->kex_alg->main_type != KEXTYPE_GSS) { + if (!kex_is_gss(s->kex_alg)) { if (!s->hkey) { ssh_proto_error(s->ppl.ssh, "Server's host key is invalid"); *aborted = true; @@ -720,7 +766,7 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) * In a GSS-based session, check the host key (if any) against * the transient host key cache. */ - if (s->kex_alg->main_type == KEXTYPE_GSS) { + if (kex_is_gss(s->kex_alg)) { /* * We've just done a GSS key exchange. If it gave us a @@ -785,7 +831,7 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) * An exception is if this was the non-GSS key exchange we * triggered on purpose to populate the transient cache. */ - assert(s->hkey); /* only KEXTYPE_GSS lets this be null */ + assert(s->hkey); /* only KEXTYPE_GSS* lets this be null */ char *fingerprint = ssh2_double_fingerprint( s->hkey, SSH_FPTYPE_DEFAULT); diff --git a/ssh/transport2.c b/ssh/transport2.c index 2fd8f65d..37093978 100644 --- a/ssh/transport2.c +++ b/ssh/transport2.c @@ -228,6 +228,8 @@ static void ssh2_transport_free(PacketProtocolLayer *ppl) if (s->f) mp_free(s->f); if (s->p) mp_free(s->p); if (s->g) mp_free(s->g); + if (s->ebuf) strbuf_free(s->ebuf); + if (s->fbuf) strbuf_free(s->fbuf); if (s->kex_shared_secret) strbuf_free(s->kex_shared_secret); if (s->dh_ctx) dh_cleanup(s->dh_ctx); @@ -508,7 +510,7 @@ static void ssh2_write_kexinit_lists( bool warn; int n_preferred_kex; - const ssh_kexes *preferred_kex[KEX_MAX + 2]; /* +2 for GSSAPI */ + const ssh_kexes *preferred_kex[KEX_MAX + 3]; /* +3 for GSSAPI */ int n_preferred_hk; int preferred_hk[HK_MAX]; int n_preferred_ciphers; @@ -524,6 +526,7 @@ static void ssh2_write_kexinit_lists( */ n_preferred_kex = 0; if (can_gssapi_keyex) { + preferred_kex[n_preferred_kex++] = &ssh_gssk5_ecdh_kex; preferred_kex[n_preferred_kex++] = &ssh_gssk5_sha2_kex; preferred_kex[n_preferred_kex++] = &ssh_gssk5_sha1_kex; } diff --git a/ssh/transport2.h b/ssh/transport2.h index a5f85103..7ea39cbb 100644 --- a/ssh/transport2.h +++ b/ssh/transport2.h @@ -181,6 +181,7 @@ struct ssh2_transport_state { int nbits, pbits; bool warn_kex, warn_hk, warn_cscipher, warn_sccipher; mp_int *p, *g, *e, *f; + strbuf *ebuf, *fbuf; strbuf *kex_shared_secret; strbuf *outgoing_kexinit, *incoming_kexinit; strbuf *client_kexinit, *server_kexinit; /* aliases to the above */ -- cgit v1.2.3 From 5e2acd9af7033b681023e026d4607e554f7ab984 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Tue, 30 Aug 2022 18:51:33 +0100 Subject: New bug workaround: KEXINIT filtering. We've occasionally had reports of SSH servers disconnecting as soon as they receive PuTTY's KEXINIT. I think all such reports have involved the kind of simple ROM-based SSH server software you find in small embedded devices. I've never been able to prove it, but I've always suspected that one possible cause of this is simply that PuTTY's KEXINIT is _too long_, either in number of algorithms listed or in total length (especially given all the ones that end in @very.long.domain.name suffixes). If I'm right about either of those being the cause, then it's just become even more likely to happen, because of all the extra Diffie-Hellman groups and GSSAPI algorithms we just threw into our already-long list in the previous few commits. A workaround I've had in mind for ages is to wait for the server's KEXINIT, and then filter our own down to just the algorithms the server also mentioned. Then our KEXINIT is no longer than that of the server, and hence, presumably fits in whatever buffer it has. So I've implemented that workaround, in anticipation of it being needed in the near future. (Well ... it's not _quite_ true that our KEXINIT is at most the same length as the server. In fact I had to leave in one KEXINIT item that won't match anything in the server's list, namely "ext-info-c" which gates access to SHA-2 based RSA. So if we turn out to support absolutely everything on all the server's lists, then our KEXINIT would be a few bytes longer than the server's, even with this workaround. But that would only cause trouble if the server's outgoing KEXINIT was skating very close to whatever buffer size it has for the incoming one, and I'm guessing that's not very likely.) ((Another possible cause of this kind of disconnection would be a server that simply objects to seeing any KEXINIT string it doesn't know how to speak. But _surely_ no such server would have survived initial testing against any full-featured client at all!)) --- config.c | 4 +++ doc/config.but | 26 ++++++++++++++ putty.h | 1 + settings.c | 2 ++ ssh.h | 1 + ssh/transport2.c | 102 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- ssh/verstring.c | 6 ++++ windows/help.h | 1 + 8 files changed, 138 insertions(+), 5 deletions(-) diff --git a/config.c b/config.c index 03043912..12c6767c 100644 --- a/config.c +++ b/config.c @@ -3154,6 +3154,10 @@ void setup_config_box(struct controlbox *b, bool midsession, HELPCTX(ssh_bugs_dropstart), sshbug_handler_manual_only, I(CONF_sshbug_dropstart)); + ctrl_droplist(s, "Chokes on PuTTY's full KEXINIT", 'p', 20, + HELPCTX(ssh_bugs_filter_kexinit), + sshbug_handler_manual_only, + I(CONF_sshbug_filter_kexinit)); ctrl_settitle(b, "Connection/SSH/More bugs", "Further workarounds for SSH server bugs"); diff --git a/doc/config.but b/doc/config.but index 918b63d8..c2f8d0cb 100644 --- a/doc/config.but +++ b/doc/config.but @@ -3573,6 +3573,32 @@ auto-detection relies on the version string in the server's greeting, and PuTTY has to decide whether to expect this bug \e{before} it sees the server's greeting. So this is a manual workaround only. +\S{config-ssh-bug-filter-kexinit} \q{Chokes on PuTTY's full \cw{KEXINIT}} + +At the start of an SSH connection, the client and server exchange long +messages of type \cw{SSH_MSG_KEXINIT}, containing lists of all the +cryptographic algorithms they're prepared to use. This is used to +negotiate a set of algorithms that both ends can speak. + +Occasionally, a badly written server might have a length limit on the +list it's prepared to receive, and refuse to make a connection simply +because PuTTY is giving it too many choices. + +A workaround is to enable this flag, which will make PuTTY wait to +send \cw{KEXINIT} until after it receives the one from the server, and +then filter its own \cw{KEXINIT} to leave out any algorithm the server +doesn't also announce support for. This will generally make PuTTY's +\cw{KEXINIT} at most the size of the server's, and will otherwise make +no difference to the algorithm negotiation. + +This flag is a minor violation of the SSH protocol, because both sides +are supposed to send \cw{KEXINIT} proactively. It still works provided +\e{one} side sends its \cw{KEXINIT} without waiting, but if both +client and server waited for the other one to speak first, the +connection would deadlock. We don't know of any servers that do this, +but if there is one, then this flag will make PuTTY unable to speak to +them at all. + \S{config-ssh-bug-sig} \q{Requires padding on SSH-2 \i{RSA} \i{signatures}} Versions below 3.3 of \i{OpenSSH} require SSH-2 RSA signatures to be diff --git a/putty.h b/putty.h index 9ebf4aed..74fbe6f2 100644 --- a/putty.h +++ b/putty.h @@ -2022,6 +2022,7 @@ NORETURN void cleanup_exit(int); X(INT, NONE, sshbug_winadj) \ X(INT, NONE, sshbug_chanreq) \ X(INT, NONE, sshbug_dropstart) \ + X(INT, NONE, sshbug_filter_kexinit) \ /* \ * ssh_simple means that we promise never to open any channel \ * other than the main one, which means it can safely use a very \ diff --git a/settings.c b/settings.c index d2cd6a7e..c6c81562 100644 --- a/settings.c +++ b/settings.c @@ -778,6 +778,7 @@ void save_open_settings(settings_w *sesskey, Conf *conf) write_setting_i(sesskey, "BugWinadj", 2-conf_get_int(conf, CONF_sshbug_winadj)); write_setting_i(sesskey, "BugChanReq", 2-conf_get_int(conf, CONF_sshbug_chanreq)); write_setting_i(sesskey, "BugDropStart", 2-conf_get_int(conf, CONF_sshbug_dropstart)); + write_setting_i(sesskey, "BugFilterKexinit", 2-conf_get_int(conf, CONF_sshbug_filter_kexinit)); write_setting_b(sesskey, "StampUtmp", conf_get_bool(conf, CONF_stamp_utmp)); write_setting_b(sesskey, "LoginShell", conf_get_bool(conf, CONF_login_shell)); write_setting_b(sesskey, "ScrollbarOnLeft", conf_get_bool(conf, CONF_scrollbar_on_left)); @@ -1257,6 +1258,7 @@ void load_open_settings(settings_r *sesskey, Conf *conf) i = gppi_raw(sesskey, "BugWinadj", 0); conf_set_int(conf, CONF_sshbug_winadj, 2-i); i = gppi_raw(sesskey, "BugChanReq", 0); conf_set_int(conf, CONF_sshbug_chanreq, 2-i); i = gppi_raw(sesskey, "BugDropStart", 1); conf_set_int(conf, CONF_sshbug_dropstart, 2-i); + i = gppi_raw(sesskey, "BugFilterKexinit", 1); conf_set_int(conf, CONF_sshbug_filter_kexinit, 2-i); conf_set_bool(conf, CONF_ssh_simple, false); gppb(sesskey, "StampUtmp", true, conf, CONF_stamp_utmp); gppb(sesskey, "LoginShell", true, conf, CONF_login_shell); diff --git a/ssh.h b/ssh.h index dbdd7eb1..79f1f431 100644 --- a/ssh.h +++ b/ssh.h @@ -1880,6 +1880,7 @@ void old_keyfile_warning(void); X(BUG_CHOKES_ON_WINADJ) \ X(BUG_SENDS_LATE_REQUEST_REPLY) \ X(BUG_SSH2_OLDGEX) \ + X(BUG_REQUIRES_FILTERED_KEXINIT) \ /* end of list */ #define TMP_DECLARE_LOG2_ENUM(thing) log2_##thing, enum { SSH_IMPL_BUG_LIST(TMP_DECLARE_LOG2_ENUM) }; diff --git a/ssh/transport2.c b/ssh/transport2.c index 37093978..f7b08f15 100644 --- a/ssh/transport2.c +++ b/ssh/transport2.c @@ -1196,6 +1196,75 @@ static bool ssh2_scan_kexinits( return true; } +static strbuf *write_filtered_kexinit(struct ssh2_transport_state *s) +{ + strbuf *pktout = strbuf_new(); + BinarySource osrc[1], isrc[1]; + BinarySource_BARE_INIT( + osrc, s->outgoing_kexinit->u, s->outgoing_kexinit->len); + BinarySource_BARE_INIT( + isrc, s->incoming_kexinit->u, s->incoming_kexinit->len); + + /* Skip the packet type bytes from both packets */ + get_byte(osrc); + get_byte(isrc); + + /* Copy our cookie into the real output packet; skip their cookie */ + put_datapl(pktout, get_data(osrc, 16)); + get_data(isrc, 16); + + /* + * Now we expect NKEXLIST+2 name-lists. We write into the outgoing + * packet a subset of our intended outgoing one, containing only + * names mentioned in the incoming out. + * + * NKEXLIST+2 because for this purpose we treat the 'languages' + * lists the same as the rest. In the rest of this code base we + * ignore those. + */ + strbuf *out = strbuf_new(); + for (size_t i = 0; i < NKEXLIST+2; i++) { + strbuf_clear(out); + ptrlen olist = get_string(osrc), ilist = get_string(isrc); + for (ptrlen oword; get_commasep_word(&olist, &oword) ;) { + ptrlen ilist_copy = ilist; + bool add = false; + for (ptrlen iword; get_commasep_word(&ilist_copy, &iword) ;) { + if (ptrlen_eq_ptrlen(oword, iword)) { + /* Found this word in the incoming list. */ + add = true; + break; + } + } + + if (i == KEXLIST_KEX && ptrlen_eq_string(oword, "ext-info-c")) { + /* Special case: this will _never_ match anything from the + * server, and we need it to enable SHA-2 based RSA. + * + * If this ever turns out to confuse any server all by + * itself then I suppose we'll need an even more + * draconian bug flag to exclude that too. (Obv, such + * a server wouldn't be able to speak SHA-2 RSA + * anyway.) */ + add = true; + } + + if (add) + add_to_commasep_pl(out, oword); + } + put_stringpl(pktout, ptrlen_from_strbuf(out)); + } + strbuf_free(out); + + /* + * Finally, copy the remaining parts of our intended KEXINIT. + */ + put_bool(pktout, get_bool(osrc)); /* first-kex-packet-follows */ + put_uint32(pktout, get_uint32(osrc)); /* reserved word */ + + return pktout; +} + void ssh2transport_finalise_exhash(struct ssh2_transport_state *s) { put_datapl(s->exhash, ptrlen_from_strbuf(s->kex_shared_secret)); @@ -1304,12 +1373,14 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl) put_uint32(s->outgoing_kexinit, 0); /* reserved */ /* - * Send our KEXINIT. + * Send our KEXINIT (in the normal case). */ - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEXINIT); - put_data(pktout, s->outgoing_kexinit->u + 1, - s->outgoing_kexinit->len - 1); /* omit initial packet type byte */ - pq_push(s->ppl.out_pq, pktout); + if (!(s->ppl.remote_bugs & BUG_REQUIRES_FILTERED_KEXINIT)) { + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEXINIT); + put_data(pktout, s->outgoing_kexinit->u + 1, + s->outgoing_kexinit->len - 1); /* omit type byte */ + pq_push(s->ppl.out_pq, pktout); + } /* * Flag that KEX is in progress. @@ -1331,6 +1402,27 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl) put_byte(s->incoming_kexinit, SSH2_MSG_KEXINIT); put_data(s->incoming_kexinit, get_ptr(pktin), get_avail(pktin)); + /* + * If we've delayed sending our KEXINIT so as to filter it down to + * only things the server won't choke on, send ours now. + */ + if (s->ppl.remote_bugs & BUG_REQUIRES_FILTERED_KEXINIT) { + strbuf *sb = write_filtered_kexinit(s); + + /* Send that data as a packet */ + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEXINIT); + put_datapl(pktout, ptrlen_from_strbuf(sb)); + pq_push(s->ppl.out_pq, pktout); + + /* And also replace our previous outgoing KEXINIT, since the + * host key signature will be validated against this reduced + * one. */ + strbuf_shrink_to(s->outgoing_kexinit, 1); /* keep the type byte */ + put_datapl(s->outgoing_kexinit, ptrlen_from_strbuf(sb)); + + strbuf_free(sb); + } + /* * Work through the two KEXINIT packets in parallel to find the * selected algorithm identifiers. diff --git a/ssh/verstring.c b/ssh/verstring.c index 567a8e7d..aa4c2c20 100644 --- a/ssh/verstring.c +++ b/ssh/verstring.c @@ -606,6 +606,12 @@ static void ssh_detect_bugs(struct ssh_verstring_state *s) bpp_logevent("We believe remote version has SSH-2 " "channel request bug"); } + + if (conf_get_int(s->conf, CONF_sshbug_filter_kexinit) == FORCE_ON) { + s->remote_bugs |= BUG_REQUIRES_FILTERED_KEXINIT; + bpp_logevent("We believe remote version requires us to " + "filter our KEXINIT"); + } } const char *ssh_verstring_get_remote(BinaryPacketProtocol *bpp) diff --git a/windows/help.h b/windows/help.h index cdd55a11..799e6240 100644 --- a/windows/help.h +++ b/windows/help.h @@ -170,6 +170,7 @@ typedef const char *HelpCtx; #define WINHELP_CTX_ssh_bugs_chanreq "config-ssh-bug-chanreq" #define WINHELP_CTX_ssh_bugs_oldgex2 "config-ssh-bug-oldgex2" #define WINHELP_CTX_ssh_bugs_dropstart "config-ssh-bug-dropstart" +#define WINHELP_CTX_ssh_bugs_filter_kexinit "config-ssh-bug-filter-kexinit" #define WINHELP_CTX_serial_line "config-serial-line" #define WINHELP_CTX_serial_speed "config-serial-speed" #define WINHELP_CTX_serial_databits "config-serial-databits" -- cgit v1.2.3 From d862d8d60dcc5646f4b43c72f05536bfb7cd7f85 Mon Sep 17 00:00:00 2001 From: Jacob Nevins Date: Wed, 31 Aug 2022 20:47:48 +0100 Subject: Comment misleading string "dh-group14-sha1". Like "dh-gex-sha1", this string used in session storage really covers both SHA-256 and SHA-1 variants (since a624786333), with the former preferred; but backward-compatibility makes it fiddly to change (and it's mostly not visible to users). --- settings.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/settings.c b/settings.c index c6c81562..c1119702 100644 --- a/settings.c +++ b/settings.c @@ -37,7 +37,9 @@ static const struct keyvalwhere kexnames[] = { { "dh-group17-sha512", KEX_DHGROUP17, -1, -1 }, { "dh-group16-sha512", KEX_DHGROUP16, -1, -1 }, { "dh-group15-sha512", KEX_DHGROUP15, -1, -1 }, + /* Again, this covers both SHA-256 and SHA-1, despite the name: */ { "dh-group14-sha1", KEX_DHGROUP14, -1, -1 }, + /* This one really is only SHA-1, though: */ { "dh-group1-sha1", KEX_DHGROUP1, KEX_WARN, +1 }, { "rsa", KEX_RSA, KEX_WARN, -1 }, { "WARN", KEX_WARN, -1, -1 } -- cgit v1.2.3 From 6a1b713e135a7db1f57017b5bbbb7fcba365204b Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Tue, 23 Aug 2022 18:57:58 +0100 Subject: Reorganise the stubs collection. I made a specific subdirectory 'stubs' to keep all the link-time stub modules in, like notiming.c. And I put _one_ run-time stub in it, namely nullplug.c. But the rest of the runtime stubs went into utils. I think it's better to keep all the stubs together, so I've moved all the null*.c in utils into stubs (with the exception of nullstrcmp.c, which means the 'null' in a different sense). Also, fiddled with the naming to be a bit more consistent, and stated in the new CMakeLists the naming policy that distinguishes no-*.c from null-*.c. --- CMakeLists.txt | 7 +++--- stubs/CMakeLists.txt | 30 +++++++++++++++++++++++ stubs/no-cmdline.c | 22 +++++++++++++++++ stubs/no-gss.c | 11 +++++++++ stubs/no-print.c | 38 +++++++++++++++++++++++++++++ stubs/no-rand.c | 22 +++++++++++++++++ stubs/no-term.c | 16 +++++++++++++ stubs/no-timing.c | 21 ++++++++++++++++ stubs/nocmdline.c | 22 ----------------- stubs/nogss.c | 11 --------- stubs/noprint.c | 38 ----------------------------- stubs/norand.c | 22 ----------------- stubs/noterm.c | 16 ------------- stubs/notiming.c | 21 ---------------- stubs/null-cipher.c | 11 +++++++++ stubs/null-key.c | 22 +++++++++++++++++ stubs/null-lp.c | 8 +++++++ stubs/null-mac.c | 11 +++++++++ stubs/null-plug.c | 40 +++++++++++++++++++++++++++++++ stubs/null-seat.c | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++ stubs/nullplug.c | 40 ------------------------------- unix/CMakeLists.txt | 22 ++++++++--------- utils/CMakeLists.txt | 5 ---- utils/null_lp.c | 8 ------- utils/nullcipher.c | 11 --------- utils/nullkey.c | 22 ----------------- utils/nullmac.c | 11 --------- utils/nullseat.c | 65 -------------------------------------------------- windows/CMakeLists.txt | 10 ++++---- 29 files changed, 337 insertions(+), 311 deletions(-) create mode 100644 stubs/CMakeLists.txt create mode 100644 stubs/no-cmdline.c create mode 100644 stubs/no-gss.c create mode 100644 stubs/no-print.c create mode 100644 stubs/no-rand.c create mode 100644 stubs/no-term.c create mode 100644 stubs/no-timing.c delete mode 100644 stubs/nocmdline.c delete mode 100644 stubs/nogss.c delete mode 100644 stubs/noprint.c delete mode 100644 stubs/norand.c delete mode 100644 stubs/noterm.c delete mode 100644 stubs/notiming.c create mode 100644 stubs/null-cipher.c create mode 100644 stubs/null-key.c create mode 100644 stubs/null-lp.c create mode 100644 stubs/null-mac.c create mode 100644 stubs/null-plug.c create mode 100644 stubs/null-seat.c delete mode 100644 stubs/nullplug.c delete mode 100644 utils/null_lp.c delete mode 100644 utils/nullcipher.c delete mode 100644 utils/nullkey.c delete mode 100644 utils/nullmac.c delete mode 100644 utils/nullseat.c diff --git a/CMakeLists.txt b/CMakeLists.txt index deba83f7..b94d9b60 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,6 +15,7 @@ add_library(utils STATIC ${GENERATED_COMMIT_C}) add_dependencies(utils cmake_commit_c) add_subdirectory(utils) +add_subdirectory(stubs) add_library(logging OBJECT logging.c) @@ -33,7 +34,7 @@ add_library(crypto STATIC add_subdirectory(crypto) add_library(network STATIC - stubs/nullplug.c errsock.c logging.c x11disp.c + errsock.c logging.c x11disp.c proxy/proxy.c proxy/http.c proxy/socks4.c @@ -55,7 +56,7 @@ add_library(guiterminal STATIC $) add_library(noterminal STATIC - stubs/noterm.c ldisc.c) + stubs/no-term.c ldisc.c) add_library(all-backends OBJECT pinger.c) @@ -140,7 +141,7 @@ installed_program(psftp) add_executable(psocks ${platform}/psocks.c psocks.c - stubs/norand.c + stubs/no-rand.c proxy/nocproxy.c proxy/nosshproxy.c ssh/portfwd.c) diff --git a/stubs/CMakeLists.txt b/stubs/CMakeLists.txt new file mode 100644 index 00000000..e6b2fc31 --- /dev/null +++ b/stubs/CMakeLists.txt @@ -0,0 +1,30 @@ +# This subdirectory is generally full of 'stubs' in the sense of +# functions and types that don't do anything interesting, and are +# substituted in some contexts for ones that do. +# +# Some of the files here, with names beginning 'no-', are substituted +# at link time, conditional on the application. For example, a program +# that doesn't use the timing subsystem but still includes a module +# that makes a passing reference to it (say, in a context that never +# turns out to be called) can link against no-timing.c in place of the +# real timing.c. +# +# Other files, with names beginning 'null-', provide non-functional +# implementations of a particular internal API, or a selection of +# non-functional methods for that API that real implementations can +# selectively use. Those are linked in to a program _alongside_ real +# implementations of the same API. +# +# So the cmake setup for this directory puts all the 'null-' files +# into the utils library (at the end of the link, where they'll be +# available everywhere), but doesn't mention the 'no-' files, because +# those will be selected manually by add_executable() commands +# elsewhere. + +add_sources_from_current_dir(utils + null-lp.c + null-cipher.c + null-key.c + null-mac.c + null-plug.c + null-seat.c) diff --git a/stubs/no-cmdline.c b/stubs/no-cmdline.c new file mode 100644 index 00000000..2476354e --- /dev/null +++ b/stubs/no-cmdline.c @@ -0,0 +1,22 @@ +/* + * no-cmdline.c - stubs in applications which don't do the + * standard(ish) PuTTY tools' command-line parsing + */ + +#include +#include +#include +#include "putty.h" + +/* + * Stub version of the function in cmdline.c which provides the + * password to SSH authentication by remembering it having been passed + * as a command-line option. If we're not doing normal command-line + * handling, then there is no such option, so that function always + * returns failure. + */ +SeatPromptResult cmdline_get_passwd_input( + prompts_t *p, cmdline_get_passwd_input_state *state, bool restartable) +{ + return SPR_INCOMPLETE; +} diff --git a/stubs/no-gss.c b/stubs/no-gss.c new file mode 100644 index 00000000..844a1323 --- /dev/null +++ b/stubs/no-gss.c @@ -0,0 +1,11 @@ +/* + * 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 } }; diff --git a/stubs/no-print.c b/stubs/no-print.c new file mode 100644 index 00000000..941da68c --- /dev/null +++ b/stubs/no-print.c @@ -0,0 +1,38 @@ +/* + * Stub implementation of the printing interface for PuTTY, for the + * benefit of non-printing terminal applications. + */ + +#include +#include +#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, const void *data, size_t 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) +{ +} diff --git a/stubs/no-rand.c b/stubs/no-rand.c new file mode 100644 index 00000000..2ad9f661 --- /dev/null +++ b/stubs/no-rand.c @@ -0,0 +1,22 @@ +/* + * Stub implementations of RNG functions for applications without an RNG. + */ + +#include "putty.h" + +void random_read(void *out, size_t size) +{ + unreachable("Random numbers are not available in this application"); +} + +void random_save_seed(void) +{ +} + +void random_destroy_seed(void) +{ +} + +void noise_ultralight(NoiseSourceId id, unsigned long data) +{ +} diff --git a/stubs/no-term.c b/stubs/no-term.c new file mode 100644 index 00000000..c2e534b2 --- /dev/null +++ b/stubs/no-term.c @@ -0,0 +1,16 @@ +/* + * Stubs of functions in terminal.c, for use in programs that don't + * have a terminal. + */ + +#include "putty.h" +#include "terminal.h" + +void term_nopaste(Terminal *term) +{ +} + +SeatPromptResult term_get_userpass_input(Terminal *term, prompts_t *p) +{ + return SPR_SW_ABORT("No terminal to send interactive prompts to"); +} diff --git a/stubs/no-timing.c b/stubs/no-timing.c new file mode 100644 index 00000000..d1a0ef9f --- /dev/null +++ b/stubs/no-timing.c @@ -0,0 +1,21 @@ +/* + * no-timing.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" + +unsigned long schedule_timer(int ticks, timer_fn_t fn, void *ctx) +{ + return 0; +} + +void expire_timer_context(void *ctx) +{ +} diff --git a/stubs/nocmdline.c b/stubs/nocmdline.c deleted file mode 100644 index 60e2cb6b..00000000 --- a/stubs/nocmdline.c +++ /dev/null @@ -1,22 +0,0 @@ -/* - * nocmdline.c - stubs in applications which don't do the - * standard(ish) PuTTY tools' command-line parsing - */ - -#include -#include -#include -#include "putty.h" - -/* - * Stub version of the function in cmdline.c which provides the - * password to SSH authentication by remembering it having been passed - * as a command-line option. If we're not doing normal command-line - * handling, then there is no such option, so that function always - * returns failure. - */ -SeatPromptResult cmdline_get_passwd_input( - prompts_t *p, cmdline_get_passwd_input_state *state, bool restartable) -{ - return SPR_INCOMPLETE; -} diff --git a/stubs/nogss.c b/stubs/nogss.c deleted file mode 100644 index 844a1323..00000000 --- a/stubs/nogss.c +++ /dev/null @@ -1,11 +0,0 @@ -/* - * 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 } }; diff --git a/stubs/noprint.c b/stubs/noprint.c deleted file mode 100644 index 941da68c..00000000 --- a/stubs/noprint.c +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Stub implementation of the printing interface for PuTTY, for the - * benefit of non-printing terminal applications. - */ - -#include -#include -#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, const void *data, size_t 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) -{ -} diff --git a/stubs/norand.c b/stubs/norand.c deleted file mode 100644 index 2ad9f661..00000000 --- a/stubs/norand.c +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Stub implementations of RNG functions for applications without an RNG. - */ - -#include "putty.h" - -void random_read(void *out, size_t size) -{ - unreachable("Random numbers are not available in this application"); -} - -void random_save_seed(void) -{ -} - -void random_destroy_seed(void) -{ -} - -void noise_ultralight(NoiseSourceId id, unsigned long data) -{ -} diff --git a/stubs/noterm.c b/stubs/noterm.c deleted file mode 100644 index c2e534b2..00000000 --- a/stubs/noterm.c +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Stubs of functions in terminal.c, for use in programs that don't - * have a terminal. - */ - -#include "putty.h" -#include "terminal.h" - -void term_nopaste(Terminal *term) -{ -} - -SeatPromptResult term_get_userpass_input(Terminal *term, prompts_t *p) -{ - return SPR_SW_ABORT("No terminal to send interactive prompts to"); -} diff --git a/stubs/notiming.c b/stubs/notiming.c deleted file mode 100644 index 3feb5cdf..00000000 --- a/stubs/notiming.c +++ /dev/null @@ -1,21 +0,0 @@ -/* - * 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" - -unsigned long schedule_timer(int ticks, timer_fn_t fn, void *ctx) -{ - return 0; -} - -void expire_timer_context(void *ctx) -{ -} diff --git a/stubs/null-cipher.c b/stubs/null-cipher.c new file mode 100644 index 00000000..e11c7bbc --- /dev/null +++ b/stubs/null-cipher.c @@ -0,0 +1,11 @@ +/* + * Implementation of shared trivial routines that ssh_cipher + * implementations might use. + */ + +#include "ssh.h" + +void nullcipher_next_message(ssh_cipher *cipher) +{ + /* Most ciphers don't do anything at all with this */ +} diff --git a/stubs/null-key.c b/stubs/null-key.c new file mode 100644 index 00000000..dae5c1bb --- /dev/null +++ b/stubs/null-key.c @@ -0,0 +1,22 @@ +#include "misc.h" +#include "ssh.h" + +unsigned nullkey_supported_flags(const ssh_keyalg *self) +{ + return 0; +} + +const char *nullkey_alternate_ssh_id(const ssh_keyalg *self, unsigned flags) +{ + /* There are no alternate ids */ + return self->ssh_id; +} + +ssh_key *nullkey_base_key(ssh_key *key) +{ + /* When a key is not certified, it is its own base */ + return key; +} + +bool nullkey_variable_size_no(const ssh_keyalg *self) { return false; } +bool nullkey_variable_size_yes(const ssh_keyalg *self) { return true; } diff --git a/stubs/null-lp.c b/stubs/null-lp.c new file mode 100644 index 00000000..193c3392 --- /dev/null +++ b/stubs/null-lp.c @@ -0,0 +1,8 @@ +/* + * Stub methods usable by LogPolicy implementations. + */ + +#include "putty.h" + +bool null_lp_verbose_no(LogPolicy *lp) { return false; } +bool null_lp_verbose_yes(LogPolicy *lp) { return true; } diff --git a/stubs/null-mac.c b/stubs/null-mac.c new file mode 100644 index 00000000..4d836704 --- /dev/null +++ b/stubs/null-mac.c @@ -0,0 +1,11 @@ +/* + * Implementation of shared trivial routines that ssh2_mac + * implementations might use. + */ + +#include "ssh.h" + +void nullmac_next_message(ssh2_mac *m) +{ + /* Most MACs don't do anything at all with this */ +} diff --git a/stubs/null-plug.c b/stubs/null-plug.c new file mode 100644 index 00000000..d583d156 --- /dev/null +++ b/stubs/null-plug.c @@ -0,0 +1,40 @@ +/* + * nullplug.c: provide a null implementation of the Plug vtable which + * ignores all calls. Occasionally useful in cases where we want to + * make a network connection just to see if it works, but not do + * anything with it afterwards except close it again. + */ + +#include "putty.h" + +void nullplug_log(Plug *plug, PlugLogType type, SockAddr *addr, + int port, const char *err_msg, int err_code) +{ +} + +void nullplug_closing(Plug *plug, PlugCloseType type, const char *error_msg) +{ +} + +void nullplug_receive(Plug *plug, int urgent, const char *data, size_t len) +{ +} + +void nullplug_sent(Plug *plug, size_t bufsize) +{ +} + +static const PlugVtable nullplug_plugvt = { + .log = nullplug_log, + .closing = nullplug_closing, + .receive = nullplug_receive, + .sent = nullplug_sent, +}; + +static Plug nullplug_plug = { &nullplug_plugvt }; + +/* + * There's a singleton instance of nullplug, because it's not + * interesting enough to worry about making more than one of them. + */ +Plug *const nullplug = &nullplug_plug; diff --git a/stubs/null-seat.c b/stubs/null-seat.c new file mode 100644 index 00000000..37cb0f4c --- /dev/null +++ b/stubs/null-seat.c @@ -0,0 +1,65 @@ +/* + * Stub methods usable by Seat implementations. + */ + +#include "putty.h" + +size_t nullseat_output( + Seat *seat, SeatOutputType type, const void *data, size_t len) {return 0;} +bool nullseat_eof(Seat *seat) { return true; } +void nullseat_sent(Seat *seat, size_t bufsize) {} +size_t nullseat_banner(Seat *seat, const void *data, size_t len) {return 0;} +size_t nullseat_banner_to_stderr(Seat *seat, const void *data, size_t len) +{ return seat_output(seat, SEAT_OUTPUT_STDERR, data, len); } +SeatPromptResult nullseat_get_userpass_input(Seat *seat, prompts_t *p) +{ return SPR_SW_ABORT("this seat can't handle interactive prompts"); } +void nullseat_notify_session_started(Seat *seat) {} +void nullseat_notify_remote_exit(Seat *seat) {} +void nullseat_notify_remote_disconnect(Seat *seat) {} +void nullseat_connection_fatal(Seat *seat, const char *message) {} +void nullseat_update_specials_menu(Seat *seat) {} +char *nullseat_get_ttymode(Seat *seat, const char *mode) { return NULL; } +void nullseat_set_busy_status(Seat *seat, BusyStatus status) {} +SeatPromptResult nullseat_confirm_ssh_host_key( + Seat *seat, const char *host, int port, const char *keytype, + char *keystr, SeatDialogText *text, HelpCtx helpctx, + void (*callback)(void *ctx, SeatPromptResult result), void *ctx) +{ return SPR_SW_ABORT("this seat can't handle interactive prompts"); } +SeatPromptResult nullseat_confirm_weak_crypto_primitive( + Seat *seat, const char *algtype, const char *algname, + void (*callback)(void *ctx, SeatPromptResult result), void *ctx) +{ return SPR_SW_ABORT("this seat can't handle interactive prompts"); } +SeatPromptResult nullseat_confirm_weak_cached_hostkey( + Seat *seat, const char *algname, const char *betteralgs, + void (*callback)(void *ctx, SeatPromptResult result), void *ctx) +{ return SPR_SW_ABORT("this seat can't handle interactive prompts"); } +bool nullseat_is_never_utf8(Seat *seat) { return false; } +bool nullseat_is_always_utf8(Seat *seat) { return true; } +void nullseat_echoedit_update(Seat *seat, bool echoing, bool editing) {} +const char *nullseat_get_x_display(Seat *seat) { return NULL; } +bool nullseat_get_windowid(Seat *seat, long *id_out) { return false; } +bool nullseat_get_window_pixel_size( + Seat *seat, int *width, int *height) { return false; } +StripCtrlChars *nullseat_stripctrl_new( + Seat *seat, BinarySink *bs_out, SeatInteractionContext sic) {return NULL;} +void nullseat_set_trust_status(Seat *seat, bool trusted) {} +bool nullseat_can_set_trust_status_yes(Seat *seat) { return true; } +bool nullseat_can_set_trust_status_no(Seat *seat) { return false; } +bool nullseat_has_mixed_input_stream_yes(Seat *seat) { return true; } +bool nullseat_has_mixed_input_stream_no(Seat *seat) { return false; } +bool nullseat_verbose_no(Seat *seat) { return false; } +bool nullseat_verbose_yes(Seat *seat) { return true; } +bool nullseat_interactive_no(Seat *seat) { return false; } +bool nullseat_interactive_yes(Seat *seat) { return true; } +bool nullseat_get_cursor_position(Seat *seat, int *x, int *y) { return false; } + +const SeatDialogPromptDescriptions *nullseat_prompt_descriptions(Seat *seat) +{ + static const SeatDialogPromptDescriptions descs = { + .hk_accept_action = "", + .hk_connect_once_action = "", + .hk_cancel_action = "", + .hk_cancel_action_Participle = "", + }; + return &descs; +} diff --git a/stubs/nullplug.c b/stubs/nullplug.c deleted file mode 100644 index d583d156..00000000 --- a/stubs/nullplug.c +++ /dev/null @@ -1,40 +0,0 @@ -/* - * nullplug.c: provide a null implementation of the Plug vtable which - * ignores all calls. Occasionally useful in cases where we want to - * make a network connection just to see if it works, but not do - * anything with it afterwards except close it again. - */ - -#include "putty.h" - -void nullplug_log(Plug *plug, PlugLogType type, SockAddr *addr, - int port, const char *err_msg, int err_code) -{ -} - -void nullplug_closing(Plug *plug, PlugCloseType type, const char *error_msg) -{ -} - -void nullplug_receive(Plug *plug, int urgent, const char *data, size_t len) -{ -} - -void nullplug_sent(Plug *plug, size_t bufsize) -{ -} - -static const PlugVtable nullplug_plugvt = { - .log = nullplug_log, - .closing = nullplug_closing, - .receive = nullplug_receive, - .sent = nullplug_sent, -}; - -static Plug nullplug_plug = { &nullplug_plugvt }; - -/* - * There's a singleton instance of nullplug, because it's not - * interesting enough to worry about making more than one of them. - */ -Plug *const nullplug = &nullplug_plug; diff --git a/unix/CMakeLists.txt b/unix/CMakeLists.txt index 480c2162..d4de28df 100644 --- a/unix/CMakeLists.txt +++ b/unix/CMakeLists.txt @@ -53,7 +53,7 @@ add_sources_from_current_dir(agent add_executable(fuzzterm ${CMAKE_SOURCE_DIR}/test/fuzzterm.c ${CMAKE_SOURCE_DIR}/logging.c - ${CMAKE_SOURCE_DIR}/stubs/noprint.c + ${CMAKE_SOURCE_DIR}/stubs/no-print.c unicode.c no-gtk.c) be_list(fuzzterm FuZZterm) @@ -71,7 +71,7 @@ add_sources_from_current_dir(psocks no-gtk.c) add_executable(psusan psusan.c - ${CMAKE_SOURCE_DIR}/stubs/nogss.c + ${CMAKE_SOURCE_DIR}/stubs/no-gss.c ${CMAKE_SOURCE_DIR}/ssh/scpserver.c no-gtk.c pty.c) @@ -81,7 +81,7 @@ target_link_libraries(psusan installed_program(psusan) add_library(puttygen-common OBJECT - ${CMAKE_SOURCE_DIR}/stubs/notiming.c + ${CMAKE_SOURCE_DIR}/stubs/no-timing.c keygen-noise.c no-gtk.c noise.c @@ -114,7 +114,7 @@ add_executable(uppity ${CMAKE_SOURCE_DIR}/ssh/scpserver.c no-gtk.c pty.c - ${CMAKE_SOURCE_DIR}/stubs/nogss.c) + ${CMAKE_SOURCE_DIR}/stubs/no-gss.c) be_list(uppity Uppity) target_link_libraries(uppity eventloop sshserver keygen settings network crypto utils) @@ -135,7 +135,7 @@ if(GTK_FOUND) add_executable(pterm pterm.c main-gtk-simple.c - ${CMAKE_SOURCE_DIR}/stubs/nogss.c + ${CMAKE_SOURCE_DIR}/stubs/no-gss.c ${CMAKE_SOURCE_DIR}/stubs/no-ca-config.c ${CMAKE_SOURCE_DIR}/proxy/nosshproxy.c pty.c) @@ -149,8 +149,8 @@ if(GTK_FOUND) add_executable(ptermapp pterm.c main-gtk-application.c - ${CMAKE_SOURCE_DIR}/stubs/nocmdline.c - ${CMAKE_SOURCE_DIR}/stubs/nogss.c + ${CMAKE_SOURCE_DIR}/stubs/no-cmdline.c + ${CMAKE_SOURCE_DIR}/stubs/no-gss.c ${CMAKE_SOURCE_DIR}/stubs/no-ca-config.c ${CMAKE_SOURCE_DIR}/proxy/nosshproxy.c pty.c) @@ -176,7 +176,7 @@ if(GTK_FOUND) add_executable(puttyapp putty.c main-gtk-application.c - ${CMAKE_SOURCE_DIR}/stubs/nocmdline.c) + ${CMAKE_SOURCE_DIR}/stubs/no-cmdline.c) be_list(puttyapp PuTTY SSH SERIAL OTHERBACKENDS) target_link_libraries(puttyapp guiterminal eventloop sshclient otherbackends settings @@ -187,9 +187,9 @@ if(GTK_FOUND) add_executable(puttytel putty.c main-gtk-simple.c - ${CMAKE_SOURCE_DIR}/stubs/nogss.c + ${CMAKE_SOURCE_DIR}/stubs/no-gss.c ${CMAKE_SOURCE_DIR}/stubs/no-ca-config.c - ${CMAKE_SOURCE_DIR}/stubs/norand.c + ${CMAKE_SOURCE_DIR}/stubs/no-rand.c ${CMAKE_SOURCE_DIR}/proxy/nocproxy.c ${CMAKE_SOURCE_DIR}/proxy/nosshproxy.c) be_list(puttytel PuTTYtel SERIAL OTHERBACKENDS) @@ -210,7 +210,7 @@ else() endif() add_executable(pageant pageant.c - ${CMAKE_SOURCE_DIR}/stubs/nogss.c + ${CMAKE_SOURCE_DIR}/stubs/no-gss.c x11.c noise.c ${CMAKE_SOURCE_DIR}/ssh/x11fwd.c diff --git a/utils/CMakeLists.txt b/utils/CMakeLists.txt index 19cbc737..6ef33c8d 100644 --- a/utils/CMakeLists.txt +++ b/utils/CMakeLists.txt @@ -41,11 +41,6 @@ add_sources_from_current_dir(utils marshal.c memory.c memxor.c - null_lp.c - nullcipher.c - nullkey.c - nullmac.c - nullseat.c nullstrcmp.c out_of_memory.c parse_blocksize.c diff --git a/utils/null_lp.c b/utils/null_lp.c deleted file mode 100644 index 193c3392..00000000 --- a/utils/null_lp.c +++ /dev/null @@ -1,8 +0,0 @@ -/* - * Stub methods usable by LogPolicy implementations. - */ - -#include "putty.h" - -bool null_lp_verbose_no(LogPolicy *lp) { return false; } -bool null_lp_verbose_yes(LogPolicy *lp) { return true; } diff --git a/utils/nullcipher.c b/utils/nullcipher.c deleted file mode 100644 index e11c7bbc..00000000 --- a/utils/nullcipher.c +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Implementation of shared trivial routines that ssh_cipher - * implementations might use. - */ - -#include "ssh.h" - -void nullcipher_next_message(ssh_cipher *cipher) -{ - /* Most ciphers don't do anything at all with this */ -} diff --git a/utils/nullkey.c b/utils/nullkey.c deleted file mode 100644 index dae5c1bb..00000000 --- a/utils/nullkey.c +++ /dev/null @@ -1,22 +0,0 @@ -#include "misc.h" -#include "ssh.h" - -unsigned nullkey_supported_flags(const ssh_keyalg *self) -{ - return 0; -} - -const char *nullkey_alternate_ssh_id(const ssh_keyalg *self, unsigned flags) -{ - /* There are no alternate ids */ - return self->ssh_id; -} - -ssh_key *nullkey_base_key(ssh_key *key) -{ - /* When a key is not certified, it is its own base */ - return key; -} - -bool nullkey_variable_size_no(const ssh_keyalg *self) { return false; } -bool nullkey_variable_size_yes(const ssh_keyalg *self) { return true; } diff --git a/utils/nullmac.c b/utils/nullmac.c deleted file mode 100644 index 4d836704..00000000 --- a/utils/nullmac.c +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Implementation of shared trivial routines that ssh2_mac - * implementations might use. - */ - -#include "ssh.h" - -void nullmac_next_message(ssh2_mac *m) -{ - /* Most MACs don't do anything at all with this */ -} diff --git a/utils/nullseat.c b/utils/nullseat.c deleted file mode 100644 index 37cb0f4c..00000000 --- a/utils/nullseat.c +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Stub methods usable by Seat implementations. - */ - -#include "putty.h" - -size_t nullseat_output( - Seat *seat, SeatOutputType type, const void *data, size_t len) {return 0;} -bool nullseat_eof(Seat *seat) { return true; } -void nullseat_sent(Seat *seat, size_t bufsize) {} -size_t nullseat_banner(Seat *seat, const void *data, size_t len) {return 0;} -size_t nullseat_banner_to_stderr(Seat *seat, const void *data, size_t len) -{ return seat_output(seat, SEAT_OUTPUT_STDERR, data, len); } -SeatPromptResult nullseat_get_userpass_input(Seat *seat, prompts_t *p) -{ return SPR_SW_ABORT("this seat can't handle interactive prompts"); } -void nullseat_notify_session_started(Seat *seat) {} -void nullseat_notify_remote_exit(Seat *seat) {} -void nullseat_notify_remote_disconnect(Seat *seat) {} -void nullseat_connection_fatal(Seat *seat, const char *message) {} -void nullseat_update_specials_menu(Seat *seat) {} -char *nullseat_get_ttymode(Seat *seat, const char *mode) { return NULL; } -void nullseat_set_busy_status(Seat *seat, BusyStatus status) {} -SeatPromptResult nullseat_confirm_ssh_host_key( - Seat *seat, const char *host, int port, const char *keytype, - char *keystr, SeatDialogText *text, HelpCtx helpctx, - void (*callback)(void *ctx, SeatPromptResult result), void *ctx) -{ return SPR_SW_ABORT("this seat can't handle interactive prompts"); } -SeatPromptResult nullseat_confirm_weak_crypto_primitive( - Seat *seat, const char *algtype, const char *algname, - void (*callback)(void *ctx, SeatPromptResult result), void *ctx) -{ return SPR_SW_ABORT("this seat can't handle interactive prompts"); } -SeatPromptResult nullseat_confirm_weak_cached_hostkey( - Seat *seat, const char *algname, const char *betteralgs, - void (*callback)(void *ctx, SeatPromptResult result), void *ctx) -{ return SPR_SW_ABORT("this seat can't handle interactive prompts"); } -bool nullseat_is_never_utf8(Seat *seat) { return false; } -bool nullseat_is_always_utf8(Seat *seat) { return true; } -void nullseat_echoedit_update(Seat *seat, bool echoing, bool editing) {} -const char *nullseat_get_x_display(Seat *seat) { return NULL; } -bool nullseat_get_windowid(Seat *seat, long *id_out) { return false; } -bool nullseat_get_window_pixel_size( - Seat *seat, int *width, int *height) { return false; } -StripCtrlChars *nullseat_stripctrl_new( - Seat *seat, BinarySink *bs_out, SeatInteractionContext sic) {return NULL;} -void nullseat_set_trust_status(Seat *seat, bool trusted) {} -bool nullseat_can_set_trust_status_yes(Seat *seat) { return true; } -bool nullseat_can_set_trust_status_no(Seat *seat) { return false; } -bool nullseat_has_mixed_input_stream_yes(Seat *seat) { return true; } -bool nullseat_has_mixed_input_stream_no(Seat *seat) { return false; } -bool nullseat_verbose_no(Seat *seat) { return false; } -bool nullseat_verbose_yes(Seat *seat) { return true; } -bool nullseat_interactive_no(Seat *seat) { return false; } -bool nullseat_interactive_yes(Seat *seat) { return true; } -bool nullseat_get_cursor_position(Seat *seat, int *x, int *y) { return false; } - -const SeatDialogPromptDescriptions *nullseat_prompt_descriptions(Seat *seat) -{ - static const SeatDialogPromptDescriptions descs = { - .hk_accept_action = "", - .hk_connect_once_action = "", - .hk_cancel_action = "", - .hk_cancel_action_Participle = "", - }; - return &descs; -} diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt index 5f829ba8..d34b4106 100644 --- a/windows/CMakeLists.txt +++ b/windows/CMakeLists.txt @@ -116,9 +116,9 @@ add_executable(puttytel window.c putty.c help.c - ${CMAKE_SOURCE_DIR}/stubs/nogss.c + ${CMAKE_SOURCE_DIR}/stubs/no-gss.c ${CMAKE_SOURCE_DIR}/stubs/no-ca-config.c - ${CMAKE_SOURCE_DIR}/stubs/norand.c + ${CMAKE_SOURCE_DIR}/stubs/no-rand.c ${CMAKE_SOURCE_DIR}/proxy/nocproxy.c ${CMAKE_SOURCE_DIR}/proxy/nosshproxy.c puttytel.rc) @@ -134,7 +134,7 @@ installed_program(puttytel) add_executable(puttygen puttygen.c - ${CMAKE_SOURCE_DIR}/stubs/notiming.c + ${CMAKE_SOURCE_DIR}/stubs/no-timing.c noise.c no-jump-list.c storage.c @@ -158,9 +158,9 @@ if(HAVE_CONPTY) pterm.c help.c conpty.c - ${CMAKE_SOURCE_DIR}/stubs/nogss.c + ${CMAKE_SOURCE_DIR}/stubs/no-gss.c ${CMAKE_SOURCE_DIR}/stubs/no-ca-config.c - ${CMAKE_SOURCE_DIR}/stubs/norand.c + ${CMAKE_SOURCE_DIR}/stubs/no-rand.c ${CMAKE_SOURCE_DIR}/proxy/nosshproxy.c pterm.rc) be_list(pterm pterm) -- cgit v1.2.3 From 761df2fca63ad4ac2badec4e0d359ae18a94608d Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Wed, 24 Aug 2022 07:56:45 +0100 Subject: Replace integer context2 encoding in conf_editbox_handler. I was just about to add another ordinary edit box control, and found I couldn't remember what went in the context2 argument to conf_editbox. When I looked it up, I realised it was one of those horrid integer encodings of the form '1 means this, -1 means that, less than -1 means some parametrised property where the parameter is obtained by negating the encoded integer'. Those are always awkward to remember, and worse to extend. So I've replaced the integer context2 used with conf_editbox_handler with a pointer to a small struct type in which the types and parameters have sensible names and are documented. (To avoid annoying const warnings everywhere, this also meant extending the 'intorptr' union to have a const void * branch as well as a 'void *'. Surprised I haven't needed that before. But if I introduce any more of these parameter structures, it'll come in useful again.) --- config.c | 103 ++++++++++++++++++++++++++++-------------------------- dialog.h | 4 ++- putty.h | 35 +++++++++++++++++++ unix/config-gtk.c | 4 +-- 4 files changed, 93 insertions(+), 53 deletions(-) diff --git a/config.c b/config.c index 12c6767c..c8e72d13 100644 --- a/config.c +++ b/config.c @@ -104,27 +104,23 @@ void conf_checkbox_handler(dlgcontrol *ctrl, dlgparam *dlg, } } +const struct conf_editbox_handler_type conf_editbox_str = {.type = EDIT_STR}; +const struct conf_editbox_handler_type conf_editbox_int = {.type = EDIT_INT}; + void conf_editbox_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { /* - * The standard edit-box handler expects the main `context' - * field to contain the primary key. The secondary `context2' - * field indicates the type of this field: - * - * - if context2 > 0, the field is a string. - * - if context2 == -1, the field is an int and the edit box - * is numeric. - * - if context2 < -1, the field is an int and the edit box is - * _floating_, and (-context2) gives the scale. (E.g. if - * context2 == -1000, then typing 1.2 into the box will set - * the field to 1200.) + * The standard edit-box handler expects the main `context' field + * to contain the primary key. The secondary `context2' field is a + * pointer to the struct conf_editbox_handler_type defined in + * putty.h. */ int key = ctrl->context.i; - int length = ctrl->context2.i; + const struct conf_editbox_handler_type *type = ctrl->context2.cp; Conf *conf = (Conf *)data; - if (length > 0) { + if (type->type == EDIT_STR) { if (event == EVENT_REFRESH) { char *field = conf_get_str(conf, key); dlg_editbox_set(ctrl, dlg, field); @@ -133,21 +129,21 @@ void conf_editbox_handler(dlgcontrol *ctrl, dlgparam *dlg, conf_set_str(conf, key, field); sfree(field); } - } else if (length < 0) { + } else { if (event == EVENT_REFRESH) { char str[80]; int value = conf_get_int(conf, key); - if (length == -1) + if (type->type == EDIT_INT) sprintf(str, "%d", value); else - sprintf(str, "%g", (double)value / (double)(-length)); + sprintf(str, "%g", (double)value / type->denominator); dlg_editbox_set(ctrl, dlg, str); } else if (event == EVENT_VALCHANGE) { char *str = dlg_editbox_get(ctrl, dlg); - if (length == -1) + if (type->type == EDIT_INT) conf_set_int(conf, key, atoi(str)); else - conf_set_int(conf, key, (int)((-length) * atof(str))); + conf_set_int(conf, key, (int)(type->denominator * atof(str))); sfree(str); } } @@ -2039,7 +2035,7 @@ void setup_config_box(struct controlbox *b, bool midsession, conf_checkbox_handler, I(CONF_blinktext)); ctrl_editbox(s, "Answerback to ^E:", 's', 100, HELPCTX(terminal_answerback), - conf_editbox_handler, I(CONF_answerback), I(1)); + conf_editbox_handler, I(CONF_answerback), ED_STR); s = ctrl_getset(b, "Terminal", "ldisc", "Line discipline options"); ctrl_radiobuttons(s, "Local echo:", 'l', 3, @@ -2129,17 +2125,21 @@ void setup_config_box(struct controlbox *b, bool midsession, conf_checkbox_handler, I(CONF_bellovl)); ctrl_editbox(s, "Over-use means this many bells...", 'm', 20, HELPCTX(bell_overload), - conf_editbox_handler, I(CONF_bellovl_n), I(-1)); + conf_editbox_handler, I(CONF_bellovl_n), ED_INT); + + static const struct conf_editbox_handler_type conf_editbox_tickspersec = { + .type = EDIT_FIXEDPOINT, .denominator = TICKSPERSEC}; + ctrl_editbox(s, "... in this many seconds", 't', 20, HELPCTX(bell_overload), conf_editbox_handler, I(CONF_bellovl_t), - I(-TICKSPERSEC)); + CP(&conf_editbox_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), conf_editbox_handler, I(CONF_bellovl_s), - I(-TICKSPERSEC)); + CP(&conf_editbox_tickspersec)); /* * The Terminal/Features panel. @@ -2208,11 +2208,11 @@ void setup_config_box(struct controlbox *b, bool midsession, ctrl_columns(s, 2, 50, 50); c = ctrl_editbox(s, "Columns", 'm', 100, HELPCTX(window_size), - conf_editbox_handler, I(CONF_width), I(-1)); + conf_editbox_handler, I(CONF_width), ED_INT); c->column = 0; c = ctrl_editbox(s, "Rows", 'r', 100, HELPCTX(window_size), - conf_editbox_handler, I(CONF_height),I(-1)); + conf_editbox_handler, I(CONF_height),ED_INT); c->column = 1; ctrl_columns(s, 1, 100); } @@ -2221,7 +2221,7 @@ void setup_config_box(struct controlbox *b, bool midsession, "Control the scrollback in the window"); ctrl_editbox(s, "Lines of scrollback", 's', 50, HELPCTX(window_scrollback), - conf_editbox_handler, I(CONF_savelines), I(-1)); + conf_editbox_handler, I(CONF_savelines), ED_INT); ctrl_checkbox(s, "Display scrollbar", 'd', HELPCTX(window_scrollback), conf_checkbox_handler, I(CONF_scrollbar)); @@ -2273,7 +2273,7 @@ void setup_config_box(struct controlbox *b, bool midsession, ctrl_editbox(s, "Gap between text and window edge:", 'e', 20, HELPCTX(appearance_border), conf_editbox_handler, - I(CONF_window_border), I(-1)); + I(CONF_window_border), ED_INT); /* * The Window/Behaviour panel. @@ -2286,7 +2286,7 @@ void setup_config_box(struct controlbox *b, bool midsession, "Adjust the behaviour of the window title"); ctrl_editbox(s, "Window title:", 't', 100, HELPCTX(appearance_title), - conf_editbox_handler, I(CONF_wintitle), I(1)); + conf_editbox_handler, I(CONF_wintitle), ED_STR); ctrl_checkbox(s, "Separate window and icon titles", 'i', HELPCTX(appearance_title), conf_checkbox_handler, @@ -2465,8 +2465,7 @@ void setup_config_box(struct controlbox *b, bool midsession, "Sending of null packets to keep session active"); ctrl_editbox(s, "Seconds between keepalives (0 to turn off)", 'k', 20, HELPCTX(connection_keepalive), - conf_editbox_handler, I(CONF_ping_interval), - I(-1)); + conf_editbox_handler, I(CONF_ping_interval), ED_INT); if (!midsession) { s = ctrl_getset(b, "Connection", "tcp", @@ -2499,7 +2498,7 @@ void setup_config_box(struct controlbox *b, bool midsession, "Logical name of remote host"); ctrl_editbox(s, label, 'm', 100, HELPCTX(connection_loghost), - conf_editbox_handler, I(CONF_loghost), I(1)); + conf_editbox_handler, I(CONF_loghost), ED_STR); } } @@ -2514,7 +2513,7 @@ void setup_config_box(struct controlbox *b, bool midsession, "Login details"); ctrl_editbox(s, "Auto-login username", 'u', 50, HELPCTX(connection_username), - conf_editbox_handler, I(CONF_username), I(1)); + conf_editbox_handler, I(CONF_username), ED_STR); { /* We assume the local username is sufficiently stable * to include on the dialog box. */ @@ -2535,10 +2534,10 @@ void setup_config_box(struct controlbox *b, bool midsession, "Terminal details"); ctrl_editbox(s, "Terminal-type string", 't', 50, HELPCTX(connection_termtype), - conf_editbox_handler, I(CONF_termtype), I(1)); + conf_editbox_handler, I(CONF_termtype), ED_STR); ctrl_editbox(s, "Terminal speeds", 's', 50, HELPCTX(connection_termspeed), - conf_editbox_handler, I(CONF_termspeed), I(1)); + conf_editbox_handler, I(CONF_termspeed), ED_STR); s = ctrl_getset(b, "Connection/Data", "env", "Environment variables"); @@ -2588,19 +2587,19 @@ void setup_config_box(struct controlbox *b, bool midsession, c = ctrl_editbox(s, "Proxy hostname", 'y', 100, HELPCTX(proxy_main), conf_editbox_handler, - I(CONF_proxy_host), I(1)); + I(CONF_proxy_host), ED_STR); c->column = 0; c = ctrl_editbox(s, "Port", 'p', 100, HELPCTX(proxy_main), conf_editbox_handler, I(CONF_proxy_port), - I(-1)); + ED_INT); c->column = 1; ctrl_columns(s, 1, 100); ctrl_editbox(s, "Exclude Hosts/IPs", 'e', 100, HELPCTX(proxy_exclude), conf_editbox_handler, - I(CONF_proxy_exclude_list), I(1)); + I(CONF_proxy_exclude_list), ED_STR); ctrl_checkbox(s, "Consider proxying local host connections", 'x', HELPCTX(proxy_exclude), conf_checkbox_handler, @@ -2615,16 +2614,16 @@ void setup_config_box(struct controlbox *b, bool midsession, ctrl_editbox(s, "Username", 'u', 60, HELPCTX(proxy_auth), conf_editbox_handler, - I(CONF_proxy_username), I(1)); + I(CONF_proxy_username), ED_STR); c = ctrl_editbox(s, "Password", 'w', 60, HELPCTX(proxy_auth), conf_editbox_handler, - I(CONF_proxy_password), I(1)); + I(CONF_proxy_password), ED_STR); c->editbox.password = true; ctrl_editbox(s, "Command to send to proxy (for some types)", 'm', 100, HELPCTX(proxy_command), conf_editbox_handler, - I(CONF_proxy_telnet_command), I(1)); + I(CONF_proxy_telnet_command), ED_STR); ctrl_radiobuttons(s, "Print proxy diagnostics " "in the terminal window", 'r', 5, @@ -2674,7 +2673,7 @@ void setup_config_box(struct controlbox *b, bool midsession, "Data to send to the server"); ctrl_editbox(s, "Remote command:", 'r', 100, HELPCTX(ssh_command), - conf_editbox_handler, I(CONF_remote_cmd), I(1)); + conf_editbox_handler, I(CONF_remote_cmd), ED_STR); s = ctrl_getset(b, "Connection/SSH", "protocol", "Protocol options"); ctrl_checkbox(s, "Don't start a shell or command at all", 'n', @@ -2753,19 +2752,19 @@ void setup_config_box(struct controlbox *b, bool midsession, HELPCTX(ssh_kex_repeat), conf_editbox_handler, I(CONF_ssh_rekey_time), - I(-1)); + ED_INT); #ifndef NO_GSSAPI ctrl_editbox(s, "Minutes between GSS checks (0 for never)", NO_SHORTCUT, 20, HELPCTX(ssh_kex_repeat), conf_editbox_handler, I(CONF_gssapirekey), - I(-1)); + ED_INT); #endif ctrl_editbox(s, "Max data before rekey (0 for no limit)", 'x', 20, HELPCTX(ssh_kex_repeat), conf_editbox_handler, I(CONF_ssh_rekey_data), - I(16)); + ED_STR); ctrl_text(s, "(Use 1M for 1 megabyte, 1G for 1 gigabyte etc)", HELPCTX(ssh_kex_repeat)); } @@ -3044,7 +3043,7 @@ void setup_config_box(struct controlbox *b, bool midsession, conf_checkbox_handler,I(CONF_x11_forward)); ctrl_editbox(s, "X display location", 'x', 50, HELPCTX(ssh_tunnels_x11), - conf_editbox_handler, I(CONF_x11_display), I(1)); + conf_editbox_handler, I(CONF_x11_display), ED_STR); ctrl_radiobuttons(s, "Remote X11 authentication protocol", 'u', 2, HELPCTX(ssh_tunnels_x11auth), conf_radiobutton_handler, @@ -3210,22 +3209,26 @@ void setup_config_box(struct controlbox *b, bool midsession, "Select a serial line"); ctrl_editbox(s, "Serial line to connect to", 'l', 40, HELPCTX(serial_line), - conf_editbox_handler, I(CONF_serline), I(1)); + conf_editbox_handler, I(CONF_serline), ED_STR); } s = ctrl_getset(b, "Connection/Serial", "sercfg", "Configure the serial line"); ctrl_editbox(s, "Speed (baud)", 's', 40, HELPCTX(serial_speed), - conf_editbox_handler, I(CONF_serspeed), I(-1)); + conf_editbox_handler, I(CONF_serspeed), ED_INT); ctrl_editbox(s, "Data bits", 'b', 40, HELPCTX(serial_databits), - conf_editbox_handler, I(CONF_serdatabits), I(-1)); + conf_editbox_handler, I(CONF_serdatabits), ED_INT); /* * Stop bits come in units of one half. */ + static const struct conf_editbox_handler_type conf_editbox_stopbits = { + .type = EDIT_FIXEDPOINT, .denominator = 2}; + ctrl_editbox(s, "Stop bits", 't', 40, HELPCTX(serial_stopbits), - conf_editbox_handler, I(CONF_serstopbits), I(-2)); + conf_editbox_handler, I(CONF_serstopbits), + CP(&conf_editbox_stopbits)); ctrl_droplist(s, "Parity", 'p', 40, HELPCTX(serial_parity), serial_parity_handler, I(ser_vt->serial_parity_mask)); @@ -3279,7 +3282,7 @@ void setup_config_box(struct controlbox *b, bool midsession, "Data to send to the server"); ctrl_editbox(s, "Local username:", 'l', 50, HELPCTX(rlogin_localuser), - conf_editbox_handler, I(CONF_localusername), I(1)); + conf_editbox_handler, I(CONF_localusername), ED_STR); } @@ -3295,7 +3298,7 @@ void setup_config_box(struct controlbox *b, bool midsession, ctrl_editbox(s, "Location string", 'l', 70, HELPCTX(supdup_location), conf_editbox_handler, I(CONF_supdup_location), - I(1)); + ED_STR); ctrl_radiobuttons(s, "Extended ASCII Character set:", 'e', 4, HELPCTX(supdup_ascii), diff --git a/dialog.h b/dialog.h index ab933b9a..ef9a9dfe 100644 --- a/dialog.h +++ b/dialog.h @@ -43,11 +43,12 @@ enum { * included with DEFINE_INTORPTR_FNS defined. This is a total pain, * but such is life. */ -typedef union { void *p; int i; } intorptr; +typedef union { void *p; const void *cp; int i; } intorptr; #ifndef INLINE intorptr I(int i); intorptr P(void *p); +intorptr CP(const void *p); #endif #if defined DEFINE_INTORPTR_FNS || defined INLINE @@ -58,6 +59,7 @@ intorptr P(void *p); #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; } +PREFIX intorptr CP(const void *p) { intorptr ret; ret.cp = p; return ret; } #undef PREFIX #endif diff --git a/putty.h b/putty.h index 74fbe6f2..4cdc9ae8 100644 --- a/putty.h +++ b/putty.h @@ -2640,6 +2640,41 @@ void conf_filesel_handler(dlgcontrol *ctrl, dlgparam *dlg, void conf_fontsel_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event); +struct conf_editbox_handler_type { + /* Structure passed as context2 to conf_editbox_handler */ + enum { EDIT_STR, EDIT_INT, EDIT_FIXEDPOINT } type; + union { + /* + * EDIT_STR means the edit box is connected to a string + * field in Conf. No further parameters needed. + */ + + /* + * EDIT_INT means the edit box is connected to an int field in + * Conf, and the input string is interpreted as decimal. No + * further parameters needed. (But we could add one here later + * if for some reason we wanted int fields in hex.) + */ + + /* + * EDIT_FIXEDPOINT means the edit box is connected to an int + * field in Conf, but the input string is interpreted as + * _floating point_, and converted to/from the output int by + * means of a fixed denominator. That is, + * + * (floating value in edit box) * denominator = value in Conf + */ + struct { + double denominator; + }; + }; +}; + +extern const struct conf_editbox_handler_type conf_editbox_str; +extern const struct conf_editbox_handler_type conf_editbox_int; +#define ED_STR CP(&conf_editbox_str) +#define ED_INT CP(&conf_editbox_int) + void setup_config_box(struct controlbox *b, bool midsession, int protocol, int protcfginfo); diff --git a/unix/config-gtk.c b/unix/config-gtk.c index 0ffb1ad9..be70f5cb 100644 --- a/unix/config-gtk.c +++ b/unix/config-gtk.c @@ -112,7 +112,7 @@ void gtk_setup_config_box(struct controlbox *b, bool midsession, void *win) HELPCTX(no_help)); ctrl_editbox(s, "Horizontal offset for shadow bold:", 'z', 20, HELPCTX(no_help), conf_editbox_handler, - I(CONF_shadowboldoffset), I(-1)); + I(CONF_shadowboldoffset), ED_INT); /* * Markus Kuhn feels, not totally unreasonably, that it's good @@ -155,6 +155,6 @@ void gtk_setup_config_box(struct controlbox *b, bool midsession, void *win) "X Window System settings"); ctrl_editbox(s, "Window class name:", 'z', 50, HELPCTX(no_help), conf_editbox_handler, - I(CONF_winclass), I(1)); + I(CONF_winclass), ED_STR); } } -- cgit v1.2.3 From a92aeca1115cbaa787e5d1e46b9357d922cf2f5b Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Wed, 24 Aug 2022 19:21:39 +0100 Subject: Pass port through to userauth. I'm going to want to use it in an upcoming commit, because together with 'savedhost', it forms the identification of an SSH server (at least as far as the host key cache is concerned, and therefore it's appropriate for other uses too). We were already passing the hostname through for use in user-facing prompts (not to mention the FQDN version for use in GSSAPI). --- ssh/ppl.h | 2 +- ssh/ssh.c | 3 ++- ssh/userauth2-client.c | 4 +++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/ssh/ppl.h b/ssh/ppl.h index 025a5615..4ffeffe9 100644 --- a/ssh/ppl.h +++ b/ssh/ppl.h @@ -109,7 +109,7 @@ PacketProtocolLayer *ssh2_transport_new( const SshServerConfig *ssc); PacketProtocolLayer *ssh2_userauth_new( PacketProtocolLayer *successor_layer, - const char *hostname, const char *fullhostname, + const char *hostname, int port, const char *fullhostname, Filename *keyfile, Filename *detached_cert, bool show_banner, bool tryagent, bool notrivialauth, const char *default_username, bool change_username, diff --git a/ssh/ssh.c b/ssh/ssh.c index c7e03ff8..aba09769 100644 --- a/ssh/ssh.c +++ b/ssh/ssh.c @@ -253,7 +253,8 @@ static void ssh_got_ssh_version(struct ssh_version_receiver *rcv, char *username = get_remote_username(ssh->conf); userauth_layer = ssh2_userauth_new( - connection_layer, ssh->savedhost, ssh->fullhostname, + connection_layer, ssh->savedhost, ssh->savedport, + ssh->fullhostname, conf_get_filename(ssh->conf, CONF_keyfile), conf_get_filename(ssh->conf, CONF_detached_cert), conf_get_bool(ssh->conf, CONF_ssh_show_banner), diff --git a/ssh/userauth2-client.c b/ssh/userauth2-client.c index 5f7a9b52..836e89fb 100644 --- a/ssh/userauth2-client.c +++ b/ssh/userauth2-client.c @@ -30,6 +30,7 @@ struct ssh2_userauth_state { Filename *keyfile, *detached_cert_file; bool show_banner, tryagent, notrivialauth, change_username; char *hostname, *fullhostname; + int port; char *default_username; bool try_ki_auth, try_gssapi_auth, try_gssapi_kex_auth, gssapi_fwd; @@ -129,7 +130,7 @@ static const PacketProtocolLayerVtable ssh2_userauth_vtable = { PacketProtocolLayer *ssh2_userauth_new( PacketProtocolLayer *successor_layer, - const char *hostname, const char *fullhostname, + const char *hostname, int port, const char *fullhostname, Filename *keyfile, Filename *detached_cert_file, bool show_banner, bool tryagent, bool notrivialauth, const char *default_username, bool change_username, @@ -142,6 +143,7 @@ PacketProtocolLayer *ssh2_userauth_new( s->successor_layer = successor_layer; s->hostname = dupstr(hostname); + s->port = port; s->fullhostname = dupstr(fullhostname); s->keyfile = filename_copy(keyfile); s->detached_cert_file = filename_copy(detached_cert_file); -- cgit v1.2.3 From eec350c38b0ea604a4904538bdba19dcaa37e383 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 22 Aug 2022 18:46:32 +0100 Subject: New facility, platform_start_subprocess. We already have the ability to start a subprocess and hook it up to a Socket, for running local proxy commands. Now the same facility is available as an auxiliary feature, so that a backend can start another subcommand for a different purpose, and make a separate Socket to communicate with it. Just like the local proxy system, this facility captures the subprocess's stderr, and passes it back to the caller via plug_log. To make that not look silly, I had to add a system where the "proxy:" prefix on the usual plug_log messages is reconfigurable, and when you call platform_start_subprocess(), you get to pass the prefix you want to use in this case. --- network.h | 4 ++++ putty.h | 10 ++++++++++ stubs/CMakeLists.txt | 1 + stubs/null-opener.c | 20 ++++++++++++++++++++ unix/fd-socket.c | 7 +++++++ unix/local-proxy.c | 18 ++++++++++++++++++ unix/platform.h | 1 + utils/log_proxy_stderr.c | 11 +++++++++-- windows/handle-socket.c | 7 +++++++ windows/local-proxy.c | 18 ++++++++++++++++++ windows/platform.h | 1 + 11 files changed, 96 insertions(+), 2 deletions(-) create mode 100644 stubs/null-opener.c diff --git a/network.h b/network.h index 4c0b0332..57e6662d 100644 --- a/network.h +++ b/network.h @@ -397,8 +397,10 @@ void backend_socket_log(Seat *seat, LogContext *logctx, typedef struct ProxyStderrBuf { char buf[8192]; size_t size; + const char *prefix; /* must be statically allocated */ } ProxyStderrBuf; void psb_init(ProxyStderrBuf *psb); +void psb_set_prefix(ProxyStderrBuf *psb, const char *prefix); void log_proxy_stderr( Plug *plug, ProxyStderrBuf *psb, const void *vdata, size_t len); @@ -429,4 +431,6 @@ struct DeferredSocketOpenerVtable { static inline void deferred_socket_opener_free(DeferredSocketOpener *dso) { dso->vt->free(dso); } +DeferredSocketOpener *null_deferred_socket_opener(void); + #endif diff --git a/putty.h b/putty.h index 4cdc9ae8..8f506fb6 100644 --- a/putty.h +++ b/putty.h @@ -2893,6 +2893,16 @@ typedef void (*toplevel_callback_notify_fn_t)(void *ctx); void request_callback_notifications(toplevel_callback_notify_fn_t notify, void *ctx); +/* + * Facility provided by the platform to spawn a parallel subprocess + * and present its stdio via a Socket. + * + * 'prefix' indicates the prefix that should appear on messages passed + * to plug_log to provide stderr output from the process. + */ +Socket *platform_start_subprocess(const char *cmd, Plug *plug, + const char *prefix); + /* * 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 diff --git a/stubs/CMakeLists.txt b/stubs/CMakeLists.txt index e6b2fc31..dc02aca3 100644 --- a/stubs/CMakeLists.txt +++ b/stubs/CMakeLists.txt @@ -26,5 +26,6 @@ add_sources_from_current_dir(utils null-cipher.c null-key.c null-mac.c + null-opener.c null-plug.c null-seat.c) diff --git a/stubs/null-opener.c b/stubs/null-opener.c new file mode 100644 index 00000000..6fdb7c28 --- /dev/null +++ b/stubs/null-opener.c @@ -0,0 +1,20 @@ +/* + * Null implementation of DeferredSocketOpener. Doesn't even bother to + * allocate and free itself: there's just one static implementation + * which we hand out to any caller. + */ + +#include "putty.h" + +static void null_opener_free(DeferredSocketOpener *opener) {} + +static const DeferredSocketOpenerVtable NullOpener_vt = { + .free = null_opener_free, +}; + +static DeferredSocketOpener null_opener = { .vt = &NullOpener_vt }; + +DeferredSocketOpener *null_deferred_socket_opener(void) +{ + return &null_opener; +} diff --git a/unix/fd-socket.c b/unix/fd-socket.c index 036979d3..9758a17b 100644 --- a/unix/fd-socket.c +++ b/unix/fd-socket.c @@ -371,6 +371,13 @@ void setup_fd_socket(Socket *s, int infd, int outfd, int inerrfd) queue_toplevel_callback(fdsocket_connect_success_callback, fds); } +void fd_socket_set_psb_prefix(Socket *s, const char *prefix) +{ + FdSocket *fds = container_of(s, FdSocket, sock); + assert(fds->sock.vt == &FdSocket_sockvt); + psb_set_prefix(&fds->psb, prefix); +} + static FdSocket *make_fd_socket_internal(SockAddr *addr, int port, Plug *plug) { FdSocket *fds; diff --git a/unix/local-proxy.c b/unix/local-proxy.c index 92f2a501..157f9207 100644 --- a/unix/local-proxy.c +++ b/unix/local-proxy.c @@ -96,3 +96,21 @@ Socket *platform_new_connection(SockAddr *addr, const char *hostname, return NULL; } } + +Socket *platform_start_subprocess(const char *cmd, Plug *plug, + const char *prefix) +{ + Socket *socket = make_deferred_fd_socket( + null_deferred_socket_opener(), + sk_nonamelookup(""), 0, plug); + char *err = platform_setup_local_proxy(socket, cmd); + fd_socket_set_psb_prefix(socket, prefix); + + if (err) { + sk_close(socket); + socket = new_error_socket_fmt(plug, "%s", err); + sfree(err); + } + + return socket; +} diff --git a/unix/platform.h b/unix/platform.h index a3cbfd13..3b1db9ba 100644 --- a/unix/platform.h +++ b/unix/platform.h @@ -389,6 +389,7 @@ Socket *make_fd_socket(int infd, int outfd, int inerrfd, Socket *make_deferred_fd_socket(DeferredSocketOpener *opener, SockAddr *addr, int port, Plug *plug); void setup_fd_socket(Socket *s, int infd, int outfd, int inerrfd); +void fd_socket_set_psb_prefix(Socket *s, const char *prefix); /* * Default font setting, which can vary depending on NOT_X_WINDOWS. diff --git a/utils/log_proxy_stderr.c b/utils/log_proxy_stderr.c index 90025e08..2a84173a 100644 --- a/utils/log_proxy_stderr.c +++ b/utils/log_proxy_stderr.c @@ -7,6 +7,12 @@ void psb_init(ProxyStderrBuf *psb) { psb->size = 0; + psb->prefix = "proxy"; +} + +void psb_set_prefix(ProxyStderrBuf *psb, const char *prefix) +{ + psb->prefix = prefix; } void log_proxy_stderr(Plug *plug, ProxyStderrBuf *psb, @@ -58,7 +64,7 @@ void log_proxy_stderr(Plug *plug, ProxyStderrBuf *psb, psb->buf[endpos-1] == '\r')) endpos--; char *msg = dupprintf( - "proxy: %.*s", (int)(endpos - pos), psb->buf + pos); + "%s: %.*s", psb->prefix, (int)(endpos - pos), psb->buf + pos); plug_log(plug, PLUGLOG_PROXY_MSG, NULL, 0, msg, 0); sfree(msg); @@ -73,7 +79,8 @@ void log_proxy_stderr(Plug *plug, ProxyStderrBuf *psb, */ if (pos == 0 && psb->size == lenof(psb->buf)) { char *msg = dupprintf( - "proxy (partial line): %.*s", (int)psb->size, psb->buf); + "%s (partial line): %.*s", psb->prefix, (int)psb->size, + psb->buf); plug_log(plug, PLUGLOG_PROXY_MSG, NULL, 0, msg, 0); sfree(msg); diff --git a/windows/handle-socket.c b/windows/handle-socket.c index 0b0e60a9..2820975c 100644 --- a/windows/handle-socket.c +++ b/windows/handle-socket.c @@ -388,6 +388,13 @@ Socket *make_handle_socket(HANDLE send_H, HANDLE recv_H, HANDLE stderr_H, return &hs->sock; } +void handle_socket_set_psb_prefix(Socket *s, const char *prefix) +{ + HandleSocket *hs = container_of(s, HandleSocket, sock); + assert(hs->sock.vt == &HandleSocket_sockvt); + psb_set_prefix(&hs->psb, prefix); +} + static void sk_handle_deferred_close(Socket *s) { HandleSocket *hs = container_of(s, HandleSocket, sock); diff --git a/windows/local-proxy.c b/windows/local-proxy.c index 93baad6b..55e9cbf3 100644 --- a/windows/local-proxy.c +++ b/windows/local-proxy.c @@ -98,3 +98,21 @@ Socket *platform_new_connection(SockAddr *addr, const char *hostname, local_proxy_opener_set_socket(opener, socket); return socket; } + +Socket *platform_start_subprocess(const char *cmd, Plug *plug, + const char *prefix) +{ + Socket *socket = make_deferred_handle_socket( + null_deferred_socket_opener(), + sk_nonamelookup(""), 0, plug); + char *err = platform_setup_local_proxy(socket, cmd); + handle_socket_set_psb_prefix(socket, prefix); + + if (err) { + sk_close(socket); + socket = new_error_socket_fmt(plug, "%s", err); + sfree(err); + } + + return socket; +} diff --git a/windows/platform.h b/windows/platform.h index 60b8db77..bb190ad2 100644 --- a/windows/platform.h +++ b/windows/platform.h @@ -354,6 +354,7 @@ Socket *make_deferred_handle_socket(DeferredSocketOpener *opener, SockAddr *addr, int port, Plug *plug); void setup_handle_socket(Socket *s, HANDLE send_H, HANDLE recv_H, HANDLE stderr_H, bool overlapped); +void handle_socket_set_psb_prefix(Socket *s, const char *prefix); Socket *new_named_pipe_client(const char *pipename, Plug *plug); /* winnpc */ Socket *new_named_pipe_listener(const char *pipename, Plug *plug); /* winnps */ -- cgit v1.2.3 From 1f32a16dc85bb30754f0d8dd2cb1931454cfe20c Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 22 Aug 2022 17:48:22 +0100 Subject: userauth: factor out the keyboard-interactive code. No functional change, but I've pulled the bulk of the k-i setup and prompting code out of ssh2_userauth_process_queue and into subroutines, in preparation for wanting to do the same work in more than one place in the main coroutine's control flow. --- ssh/userauth2-client.c | 271 +++++++++++++++++++++++++------------------------ 1 file changed, 139 insertions(+), 132 deletions(-) diff --git a/ssh/userauth2-client.c b/ssh/userauth2-client.c index 836e89fb..12910dbb 100644 --- a/ssh/userauth2-client.c +++ b/ssh/userauth2-client.c @@ -117,6 +117,11 @@ static void ssh2_userauth_add_session_id( static PktOut *ssh2_userauth_gss_packet( struct ssh2_userauth_state *s, const char *authtype); #endif +static bool ssh2_userauth_ki_setup_prompts( + struct ssh2_userauth_state *s, BinarySource *src); +static bool ssh2_userauth_ki_run_prompts(struct ssh2_userauth_state *s); +static void ssh2_userauth_ki_write_responses( + struct ssh2_userauth_state *s, BinarySink *bs); static const PacketProtocolLayerVtable ssh2_userauth_vtable = { .free = ssh2_userauth_free, @@ -1362,126 +1367,11 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) * Loop while the server continues to send INFO_REQUESTs. */ while (pktin->type == SSH2_MSG_USERAUTH_INFO_REQUEST) { - ptrlen name, inst; - strbuf *sb; - - /* - * We've got a fresh USERAUTH_INFO_REQUEST. - * Get the preamble and start building a prompt. - */ - name = get_string(pktin); - inst = get_string(pktin); - get_string(pktin); /* skip language tag */ - s->cur_prompt = ssh_ppl_new_prompts(&s->ppl); - s->cur_prompt->to_server = true; - s->cur_prompt->from_server = true; - - /* - * Get any prompt(s) from the packet. - */ - s->num_prompts = get_uint32(pktin); - for (uint32_t i = 0; i < s->num_prompts; i++) { - s->is_trivial_auth = false; - ptrlen prompt = get_string(pktin); - bool echo = get_bool(pktin); - - if (get_err(pktin)) { - ssh_proto_error( - s->ppl.ssh, "Server sent truncated " - "SSH_MSG_USERAUTH_INFO_REQUEST packet"); - return; - } - - sb = strbuf_new(); - if (!prompt.len) { - put_datapl(sb, PTRLEN_LITERAL( - ": ")); - } else if (s->ki_scc) { - stripctrl_retarget( - s->ki_scc, BinarySink_UPCAST(sb)); - put_datapl(s->ki_scc, prompt); - stripctrl_retarget(s->ki_scc, NULL); - } else { - put_datapl(sb, prompt); - } - add_prompt(s->cur_prompt, strbuf_to_str(sb), echo); - } - - /* - * Make the header strings. This includes the - * 'name' (optional dialog-box title) and - * 'instruction' from the server. - * - * First, display our disambiguating header line - * if this is the first time round the loop - - * _unless_ the server has sent a completely empty - * k-i packet with no prompts _or_ text, which - * apparently some do. In that situation there's - * no need to alert the user that the following - * text is server- supplied, because, well, _what_ - * text? - * - * We also only do this if we got a stripctrl, - * because if we didn't, that suggests this is all - * being done via dialog boxes anyway. - */ - if (!s->ki_printed_header && s->ki_scc && - (s->num_prompts || name.len || inst.len)) { - seat_antispoof_msg( - ppl_get_iseat(&s->ppl), "Keyboard-interactive " - "authentication prompts from server:"); - s->ki_printed_header = true; - seat_set_trust_status(s->ppl.seat, false); - } - - sb = strbuf_new(); - if (name.len) { - if (s->ki_scc) { - stripctrl_retarget(s->ki_scc, - BinarySink_UPCAST(sb)); - put_datapl(s->ki_scc, name); - stripctrl_retarget(s->ki_scc, NULL); - } else { - put_datapl(sb, name); - } - s->cur_prompt->name_reqd = true; - } else { - put_datapl(sb, PTRLEN_LITERAL( - "SSH server authentication")); - s->cur_prompt->name_reqd = false; - } - s->cur_prompt->name = strbuf_to_str(sb); - - sb = strbuf_new(); - if (inst.len) { - if (s->ki_scc) { - stripctrl_retarget(s->ki_scc, - BinarySink_UPCAST(sb)); - put_datapl(s->ki_scc, inst); - stripctrl_retarget(s->ki_scc, NULL); - } else { - put_datapl(sb, inst); - } - s->cur_prompt->instr_reqd = true; - } else { - s->cur_prompt->instr_reqd = false; - } - if (sb->len) - s->cur_prompt->instruction = strbuf_to_str(sb); - else - strbuf_free(sb); + if (!ssh2_userauth_ki_setup_prompts( + s, BinarySource_UPCAST(pktin))) + return; + crMaybeWaitUntilV(ssh2_userauth_ki_run_prompts(s)); - /* - * Our prompts_t is fully constructed now. Get the - * user's response(s). - */ - s->spr = seat_get_userpass_input( - ppl_get_iseat(&s->ppl), s->cur_prompt); - while (s->spr.kind == SPRK_INCOMPLETE) { - crReturnV; - s->spr = seat_get_userpass_input( - ppl_get_iseat(&s->ppl), s->cur_prompt); - } if (spr_is_abort(s->spr)) { /* * Failed to get responses. Terminate. @@ -1501,22 +1391,11 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) */ s->pktout = ssh_bpp_new_pktout( s->ppl.bpp, SSH2_MSG_USERAUTH_INFO_RESPONSE); - put_uint32(s->pktout, s->num_prompts); - for (uint32_t i = 0; i < s->num_prompts; i++) { - put_stringz(s->pktout, prompt_get_result_ref( - s->cur_prompt->prompts[i])); - } + ssh2_userauth_ki_write_responses( + s, BinarySink_UPCAST(s->pktout)); s->pktout->minlen = 256; pq_push(s->ppl.out_pq, s->pktout); - /* - * Free the prompts structure from this iteration. - * If there's another, a new one will be allocated - * when we return to the top of this while loop. - */ - free_prompts(s->cur_prompt); - s->cur_prompt = NULL; - /* * Get the next packet in case it's another * INFO_REQUEST. @@ -1815,6 +1694,134 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) crFinishV; } +static bool ssh2_userauth_ki_setup_prompts( + struct ssh2_userauth_state *s, BinarySource *src) +{ + ptrlen name, inst; + strbuf *sb; + + /* + * We've got a fresh USERAUTH_INFO_REQUEST. Get the preamble and + * start building a prompt. + */ + name = get_string(src); + inst = get_string(src); + get_string(src); /* skip language tag */ + s->cur_prompt = ssh_ppl_new_prompts(&s->ppl); + s->cur_prompt->to_server = true; + s->cur_prompt->from_server = true; + + /* + * Get any prompt(s) from the packet. + */ + s->num_prompts = get_uint32(src); + for (uint32_t i = 0; i < s->num_prompts; i++) { + s->is_trivial_auth = false; + ptrlen prompt = get_string(src); + bool echo = get_bool(src); + + if (get_err(src)) { + ssh_proto_error(s->ppl.ssh, "Server sent truncated " + "SSH_MSG_USERAUTH_INFO_REQUEST packet"); + return false; + } + + sb = strbuf_new(); + if (!prompt.len) { + put_datapl(sb, PTRLEN_LITERAL(": ")); + } else if (s->ki_scc) { + stripctrl_retarget(s->ki_scc, BinarySink_UPCAST(sb)); + put_datapl(s->ki_scc, prompt); + stripctrl_retarget(s->ki_scc, NULL); + } else { + put_datapl(sb, prompt); + } + add_prompt(s->cur_prompt, strbuf_to_str(sb), echo); + } + + /* + * Make the header strings. This includes the 'name' (optional + * dialog-box title) and 'instruction' from the server. + * + * First, display our disambiguating header line if this is the + * first time round the loop - _unless_ the server has sent a + * completely empty k-i packet with no prompts _or_ text, which + * apparently some do. In that situation there's no need to alert + * the user that the following text is server- supplied, because, + * well, _what_ text? + * + * We also only do this if we got a stripctrl, because if we + * didn't, that suggests this is all being done via dialog boxes + * anyway. + */ + if (!s->ki_printed_header && s->ki_scc && + (s->num_prompts || name.len || inst.len)) { + seat_antispoof_msg(ppl_get_iseat(&s->ppl), "Keyboard-interactive " + "authentication prompts from server:"); + s->ki_printed_header = true; + seat_set_trust_status(s->ppl.seat, false); + } + + sb = strbuf_new(); + if (name.len) { + if (s->ki_scc) { + stripctrl_retarget(s->ki_scc, BinarySink_UPCAST(sb)); + put_datapl(s->ki_scc, name); + stripctrl_retarget(s->ki_scc, NULL); + } else { + put_datapl(sb, name); + } + s->cur_prompt->name_reqd = true; + } else { + put_datapl(sb, PTRLEN_LITERAL("SSH server authentication")); + s->cur_prompt->name_reqd = false; + } + s->cur_prompt->name = strbuf_to_str(sb); + + sb = strbuf_new(); + if (inst.len) { + if (s->ki_scc) { + stripctrl_retarget(s->ki_scc, BinarySink_UPCAST(sb)); + put_datapl(s->ki_scc, inst); + stripctrl_retarget(s->ki_scc, NULL); + } else { + put_datapl(sb, inst); + } + s->cur_prompt->instr_reqd = true; + } else { + s->cur_prompt->instr_reqd = false; + } + if (sb->len) + s->cur_prompt->instruction = strbuf_to_str(sb); + else + strbuf_free(sb); + + return true; +} + +static bool ssh2_userauth_ki_run_prompts(struct ssh2_userauth_state *s) +{ + s->spr = seat_get_userpass_input( + ppl_get_iseat(&s->ppl), s->cur_prompt); + return s->spr.kind != SPRK_INCOMPLETE; +} + +static void ssh2_userauth_ki_write_responses( + struct ssh2_userauth_state *s, BinarySink *bs) +{ + put_uint32(bs, s->num_prompts); + for (uint32_t i = 0; i < s->num_prompts; i++) + put_stringz(bs, prompt_get_result_ref(s->cur_prompt->prompts[i])); + + /* + * Free the prompts structure from this iteration. If there's + * another, a new one will be allocated when we return to the top + * of this while loop. + */ + free_prompts(s->cur_prompt); + s->cur_prompt = NULL; +} + static void ssh2_userauth_add_session_id( struct ssh2_userauth_state *s, strbuf *sigdata) { -- cgit v1.2.3 From 15f097f3997c3d0f4720423af9b478a66e844e1d Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 1 Sep 2022 19:38:46 +0100 Subject: New feature: k-i authentication helper plugins. In recent months I've had two requests from different people to build support into PuTTY for automatically handling complicated third-party auth protocols layered on top of keyboard-interactive - the kind of thing where you're asked to enter some auth response, and you have to refer to some external source like a web server to find out what the right response _is_, which is a pain to do by hand, so you'd prefer it to be automated in the SSH client. That seems like a reasonable thing for an end user to want, but I didn't think it was a good idea to build support for specific protocols of that kind directly into PuTTY, where there would no doubt be an ever-lengthening list, and maintenance needed on all of them. So instead, in collaboration with one of my correspondents, I've designed and implemented a protocol to be spoken between PuTTY and a plugin running as a subprocess. The plugin can opt to handle the keyboard-interactive authentication loop on behalf of the user, in which case PuTTY passes on all the INFO_REQUEST packets to it, and lets it make up responses. It can also ask questions of the user if necessary. The protocol spec is provided in a documentation appendix. The entire configuration for the end user consists of providing a full command line to use as the subprocess. In the contrib directory I've provided an example plugin written in Python. It gives a set of fixed responses suitable for getting through Uppity's made-up k-i system, because that was a reasonable thing I already had lying around to test against. But it also provides example code that someone else could pick up and insert their own live response-provider into the middle of, assuming they were happy with it being in Python. --- config.c | 15 +- contrib/authplugin-example.py | 287 +++++++++++++++++++++++ doc/CMakeLists.txt | 1 + doc/authplugin.but | 519 ++++++++++++++++++++++++++++++++++++++++++ doc/config.but | 26 +++ putty.h | 1 + settings.c | 2 + ssh.h | 32 +++ ssh/ppl.h | 3 +- ssh/ssh.c | 6 +- ssh/userauth2-client.c | 462 +++++++++++++++++++++++++++++++++---- windows/help.h | 1 + 12 files changed, 1309 insertions(+), 46 deletions(-) create mode 100755 contrib/authplugin-example.py create mode 100644 doc/authplugin.but diff --git a/config.c b/config.c index c8e72d13..3885c7be 100644 --- a/config.c +++ b/config.c @@ -2899,8 +2899,8 @@ void setup_config_box(struct controlbox *b, bool midsession, conf_checkbox_handler, I(CONF_try_ki_auth)); - s = ctrl_getset(b, "Connection/SSH/Auth", "params", - "Authentication parameters"); + s = ctrl_getset(b, "Connection/SSH/Auth", "aux", + "Other authentication-related options"); ctrl_checkbox(s, "Allow agent forwarding", 'f', HELPCTX(ssh_auth_agentfwd), conf_checkbox_handler, I(CONF_agentfwd)); @@ -2908,6 +2908,12 @@ void setup_config_box(struct controlbox *b, bool midsession, HELPCTX(ssh_auth_changeuser), conf_checkbox_handler, I(CONF_change_username)); + + ctrl_settitle(b, "Connection/SSH/Auth/Credentials", + "Credentials to authenticate with"); + + s = ctrl_getset(b, "Connection/SSH/Auth/Credentials", "publickey", + "Public-key authentication"); ctrl_filesel(s, "Private key file for authentication:", 'k', FILTER_KEY_FILES, false, "Select private key file", HELPCTX(ssh_auth_privkey), @@ -2917,6 +2923,11 @@ void setup_config_box(struct controlbox *b, bool midsession, HELPCTX(ssh_auth_cert), conf_filesel_handler, I(CONF_detached_cert)); + s = ctrl_getset(b, "Connection/SSH/Auth/Credentials", "plugin", + "Plugin to provide authentication responses"); + ctrl_editbox(s, "Plugin command to run", NO_SHORTCUT, 100, + HELPCTX(ssh_auth_plugin), + conf_editbox_handler, I(CONF_auth_plugin), ED_STR); #ifndef NO_GSSAPI /* * Connection/SSH/Auth/GSSAPI, which sadly won't fit on diff --git a/contrib/authplugin-example.py b/contrib/authplugin-example.py new file mode 100755 index 00000000..395bd2c8 --- /dev/null +++ b/contrib/authplugin-example.py @@ -0,0 +1,287 @@ +#!/usr/bin/env python3 + +# This is a demonstration example of how to write a +# keyboard-interactive authentication helper plugin using PuTTY's +# protocol for involving it in SSH connection setup. + +import io +import os +import struct +import sys + +# Exception class we'll use to get a clean exit on EOF. +class PluginEOF(Exception): pass + +# ---------------------------------------------------------------------- +# +# Marshalling and unmarshalling routines to write and read the +# necessary SSH data types to/from a binary file handle (which can +# include an io.BytesIO if you need to encode/decode in-process). +# +# Error handling is a totally ad-hoc mixture of 'assert' and just +# assuming things will have the right type, or be the right length of +# tuple, or be valid UTF-8. So it should be _robust_, in the sense +# that you'll get a Python exception if anything fails. But no +# sensible error reporting or recovery is implemented. +# +# That should be good enough, because PuTTY will log the plugin's +# standard error in its Event Log, so if the plugin crashes, you'll be +# able to retrieve the traceback. + +def wr_byte(fh, b): + assert 0 <= b < 0x100 + fh.write(bytes([b])) + +def wr_boolean(fh, b): + wr_byte(fh, 1 if b else 0) + +def wr_uint32(fh, u): + assert 0 <= u < 0x100000000 + fh.write(struct.pack(">I", u)) + +def wr_string(fh, s): + wr_uint32(fh, len(s)) + fh.write(s) + +def wr_string_utf8(fh, s): + wr_string(fh, s.encode("UTF-8")) + +def rd_n(fh, n): + data = fh.read(n) + if len(data) < n: + raise PluginEOF() + return data + +def rd_byte(fh): + return rd_n(fh, 1)[0] + +def rd_boolean(fh): + return rd_byte(fh) != 0 + +def rd_uint32(fh): + return struct.unpack(">I", rd_n(fh, 4))[0] + +def rd_string(fh): + length = rd_uint32(fh) + return rd_n(fh, length) + +def rd_string_utf8(fh): + return rd_string(fh).decode("UTF-8") + +# ---------------------------------------------------------------------- +# +# Protocol definitions. + +our_max_version = 2 + +PLUGIN_INIT = 1 +PLUGIN_INIT_RESPONSE = 2 +PLUGIN_PROTOCOL = 3 +PLUGIN_PROTOCOL_ACCEPT = 4 +PLUGIN_PROTOCOL_REJECT = 5 +PLUGIN_AUTH_SUCCESS = 6 +PLUGIN_AUTH_FAILURE = 7 +PLUGIN_INIT_FAILURE = 8 +PLUGIN_KI_SERVER_REQUEST = 20 +PLUGIN_KI_SERVER_RESPONSE = 21 +PLUGIN_KI_USER_REQUEST = 22 +PLUGIN_KI_USER_RESPONSE = 23 + +# ---------------------------------------------------------------------- +# +# Classes to make it easy to construct and receive messages. +# +# OutMessage is constructed with the message type; then you use the +# wr_foo() routines to add fields to it, and finally call its send() +# method. +# +# InMessage is constructed via the expect() class method, to which you +# give a list of message types you expect to see one of at this stage. +# Once you've got one, you can rd_foo() fields from it. + +class OutMessage: + def __init__(self, msgtype): + self.buf = io.BytesIO() + wr_byte(self.buf, msgtype) + self.write = self.buf.write + + def send(self, fh=sys.stdout.buffer): + wr_string(fh, self.buf.getvalue()) + fh.flush() + +class InMessage: + @classmethod + def expect(cls, expected_types, fh=sys.stdin.buffer): + self = cls() + self.buf = io.BytesIO(rd_string(fh)) + self.msgtype = rd_byte(self.buf) + self.read = self.buf.read + + if self.msgtype not in expected_types: + raise ValueError("received packet type {:d}, expected {}".format( + self.msgtype, ",".join(map("{:d}".format, + sorted(expected_types))))) + return self + +# ---------------------------------------------------------------------- +# +# The main implementation of the protocol. + +def protocol(): + # Start by expecting PLUGIN_INIT. + msg = InMessage.expect({PLUGIN_INIT}) + their_version = rd_uint32(msg) + hostname = rd_string_utf8(msg) + port = rd_uint32(msg) + username = rd_string_utf8(msg) + print(f"Got hostname {hostname!r}, port {port!r}", file=sys.stderr) + + # Decide which protocol version we're speaking. + version = min(their_version, our_max_version) + assert version != 0, "Protocol version 0 does not exist" + + if "TESTPLUGIN_INIT_FAIL" in os.environ: + # Test the plugin failing at startup time. + msg = OutMessage(PLUGIN_INIT_FAILURE) + wr_string_utf8(msg, os.environ["TESTPLUGIN_INIT_FAIL"]) + msg.send() + return + + # Send INIT_RESPONSE, with our protocol version and an overridden + # username. + # + # By default this test plugin doesn't override the username, but + # you can make it do so by setting TESTPLUGIN_USERNAME in the + # environment. + msg = OutMessage(PLUGIN_INIT_RESPONSE) + wr_uint32(msg, version) + wr_string_utf8(msg, os.environ.get("TESTPLUGIN_USERNAME", "")) + msg.send() + + # Outer loop run once per authentication protocol. + while True: + # Expect a message telling us what the protocol is. + msg = InMessage.expect({PLUGIN_PROTOCOL}) + method = rd_string(msg) + + if "TESTPLUGIN_PROTO_REJECT" in os.environ: + # Test the plugin failing at PLUGIN_PROTOCOL time. + msg = OutMessage(PLUGIN_PROTOCOL_REJECT) + wr_string_utf8(msg, os.environ["TESTPLUGIN_PROTO_REJECT"]) + msg.send() + continue + + # We only support keyboard-interactive. If we supported other + # auth methods, this would be the place to add further clauses + # to this if statement for them. + if method == b"keyboard-interactive": + msg = OutMessage(PLUGIN_PROTOCOL_ACCEPT) + msg.send() + + # Inner loop run once per keyboard-interactive exchange + # with the SSH server. + while True: + # Expect a set of prompts from the server, or + # terminate the loop on SUCCESS or FAILURE. + # + # (We could also respond to SUCCESS or FAILURE by + # updating caches of our own, if we had any that were + # useful.) + msg = InMessage.expect({PLUGIN_KI_SERVER_REQUEST, + PLUGIN_AUTH_SUCCESS, + PLUGIN_AUTH_FAILURE}) + if (msg.msgtype == PLUGIN_AUTH_SUCCESS or + msg.msgtype == PLUGIN_AUTH_FAILURE): + break + + # If we didn't just break, we're sitting on a + # PLUGIN_KI_SERVER_REQUEST message. Get all its bits + # and pieces out. + name = rd_string_utf8(msg) + instructions = rd_string_utf8(msg) + language = rd_string(msg) + nprompts = rd_uint32(msg) + prompts = [] + for i in range(nprompts): + prompt = rd_string_utf8(msg) + echo = rd_boolean(msg) + prompts.append((prompt, echo)) + + # Actually make up some answers for the prompts. This + # is the part that a non-example implementation would + # do very differently, of course! + # + # Here, we answer "foo" to every prompt, except that + # if there are exactly two prompts in the packet then + # we answer "stoat" to the first and "weasel" to the + # second. + # + # (These answers are consistent with the ones required + # by PuTTY's test SSH server Uppity in its own + # keyboard-interactive test implementation: that + # presents a two-prompt packet and expects + # "stoat","weasel" as the answers, and then presents a + # zero-prompt packet. So this test plugin will get you + # through Uppity's k-i in a one-touch manner. The + # "foo" in this code isn't used by Uppity at all; I + # just include it because I had to have _some_ + # handling for the else clause.) + # + # If TESTPLUGIN_PROMPTS is set in the environment, we + # ask the user questions of our own by sending them + # back to PuTTY as USER_REQUEST messages. + if nprompts == 2: + if "TESTPLUGIN_PROMPTS" in os.environ: + for i in range(2): + # Make up some questions to ask. + msg = OutMessage(PLUGIN_KI_USER_REQUEST) + wr_string_utf8( + msg, "Plugin request #{:d} (name)".format(i)) + wr_string_utf8( + msg, "Plugin request #{:d} (instructions)" + .format(i)) + wr_string(msg, b"") + wr_uint32(msg, 2) + wr_string_utf8(msg, "Prompt 1 of 2 (echo): ") + wr_boolean(msg, True) + wr_string_utf8(msg, "Prompt 2 of 2 (no echo): ") + wr_boolean(msg, False) + msg.send() + + # Expect the answers. + msg = InMessage.expect({PLUGIN_KI_USER_RESPONSE}) + user_nprompts = rd_uint32(msg) + assert user_nprompts == 2, ( + "Should match what we just sent") + for i in range(nprompts): + user_response = rd_string_utf8(msg) + # We don't actually check these + # responses for anything. + + answers = ["stoat", "weasel"] + + else: + answers = ["foo"] * nprompts + + # Send the answers to the SSH server's questions. + msg = OutMessage(PLUGIN_KI_SERVER_RESPONSE) + wr_uint32(msg, len(answers)) + for answer in answers: + wr_string_utf8(msg, answer) + msg.send() + + else: + # Default handler if we don't speak the offered protocol + # at all. + msg = OutMessage(PLUGIN_PROTOCOL_REJECT) + wr_string_utf8(msg, "") + msg.send() + +# Demonstration write to stderr, to prove that it shows up in PuTTY's +# Event Log. +print("Hello from test plugin's stderr", file=sys.stderr) + +try: + protocol() +except PluginEOF: + pass diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index 709df3de..2852d9c5 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -81,6 +81,7 @@ if(HALIBUT AND PERL_EXECUTABLE) ${CMAKE_CURRENT_SOURCE_DIR}/udp.but ${CMAKE_CURRENT_SOURCE_DIR}/pgpkeys.but ${CMAKE_CURRENT_SOURCE_DIR}/sshnames.but + ${CMAKE_CURRENT_SOURCE_DIR}/authplugin.but ${CMAKE_CURRENT_SOURCE_DIR}/index.but ${VERSION_BUT}) diff --git a/doc/authplugin.but b/doc/authplugin.but new file mode 100644 index 00000000..5ea4255f --- /dev/null +++ b/doc/authplugin.but @@ -0,0 +1,519 @@ +\A{authplugin} PuTTY authentication plugin protocol + +This appendix contains the specification for the protocol spoken over +local IPC between PuTTY and an authentication helper plugin. + +If you already have an authentication plugin and want to configure +PuTTY to use it, see \k{config-ssh-authplugin} for how to do that. +This appendix is for people writing new authentication plugins. + +\H{authplugin-req} Requirements + +The following requirements informed the specification of this protocol. + +\s{Automate keyboard-interactive authentication.} We're motivated in +the first place by the observation that the general SSH userauth +method \cq{keyboard-interactive} (defined in \k{authplugin-ref-ki}) +can be used for many kinds of challenge/response or one-time-password +styles of authentication, and in more than one of those, the necessary +responses might be obtained from an auxiliary network connection, such +as an HTTPS transaction. So it's useful if a user doesn't have to +manually copy-type or copy-paste from their web browser into their SSH +client, but instead, the process can be automated. + +\s{Be able to pass prompts on to the user.} On the other hand, some +userauth methods can be only \e{partially} automated; some of the +server's prompts might still require human input. Also, the plugin +automating the authentication might need to ask its own questions that +are not provided by the SSH server. (For example, \q{please enter the +master key that the real response will be generated by hashing}.) So +after the plugin intercepts the server's questions, it needs to be +able to ask its own questions of the user, which may or may not be the +same questions sent by the server. + +\s{Allow automatic generation of the username.} Sometimes, the +authentication method comes with a mechanism for discovering the +username to be used in the SSH login. So the plugin has to start up +early enough that the client hasn't committed to a username yet. + +\s{Future expansion route to other SSH userauth flavours.} The initial +motivation for this protocol is specific to keyboard-interactive. But +other SSH authentication methods exist, and they may also benefit from +automation in future. We're making no attempt here to predict what +those methods might be or how they might be automated, but we do need +to leave a space where they can be slotted in later if necessary. + +\s{Minimal information loss.} Keyboard-interactive prompts and replies +should be passed to and from the plugin in a form as close as possible +to the way they look on the wire in SSH itself. Therefore, the +protocol resembles SSH in its data formats and marshalling (instead +of, for example, translating from SSH binary packet style to another +well-known format such as JSON, which would introduce edge cases in +character encoding). + +\s{Half-duplex.} Simultaneously trying to read one I/O stream and +write another adds a lot of complexity to software. It becomes +necessary to have an organised event loop containing \cw{select} or +\cw{WaitForMultipleObjects} or similar, which can invoke the handler +for whichever event happens soonest. There's no need to add that +complexity in an application like this, which isn't transferring large +amounts of bulk data or multiplexing unrelated activities. So, to keep +life simple for plugin authors, we set the ground rule that it must +always be 100% clear which side is supposed to be sending a message +next. That way, the plugin can be written as sequential code +progressing through the protocol, making simple read and write calls +to receive or send each message. + +\s{Communicate success/failure, to facilitate caching in the plugin.} +A plugin might want to cache recently used data for next time, but +only in the case where authentication using that data was actually +successful. So the client has to tell the plugin what the outcome was, +if it's known. (But this is best-effort only. Obviously the plugin +cannot \e{depend} on hearing the answer, because any IPC protocol at +all carries the risk that the other end might crash or be killed by +things outside its control.) + +\H{authplugin-transport} Transport and configuration + +Plugins are executable programs on the client platform. + +The SSH client must be manually configured to use a plugin for a +particular connection. The configuration takes the form of a command +line, including the location of the plugin executable, and optionally +command-line arguments that are meaningful to the particular plugin. + +The client invokes the plugin as a subprocess, passing it a pair of +8-bit-clean pipes as its standard input and output. On those pipes, +the client and plugin will communicate via the protocol specified +below. + +\H{authplugin-formats} Data formats and marshalling + +This protocol borrows the low-level data formatting from SSH itself, +in particular the following wire encodings from +\k{authplugin-ref-arch} section 5: + +\dt \s{byte} + +\dd An integer between 0 and 0xFF inclusive, transmitted as a single +byte of binary data. + +\dt \s{boolean} + +\dd The values \q{true} or \q{false}, transmitted as the bytes 1 and 0 +respectively. + +\dt \s{uint32} + +\dd An integer between 0 and 0xFFFFFFFF inclusive, transmitted as 4 +bytes of binary data, in big-endian (\q{network}) byte order. + +\dt \s{string} + +\dd A sequence of bytes, preceded by a \s{uint32} giving the number of +bytes in the sequence. The length field does not include itself. For +example, the empty string is represented by four zero bytes (the +\s{uint32} encoding of 0); the string "AB" is represented by the six +bytes 0,0,0,2,'A','B'. + +Unlike SSH itself, the protocol spoken between the client and the +plugin is unencrypted, because local inter-process pipes are assumed +to be secured by the OS kernel. So the binary packet protocol is much +simpler than SSH proper, and is similar to SFTP and the OpenSSH agent +protocol. + +The data sent in each direction of the conversation consists of a +sequence of \s{messages} exchanged between the SSH client and the +plugin. Each message is encoded as a \s{string}. The contents of the +string begin with a \s{byte} giving the message type, which determines +the format of the rest of the message. + +\H{authplugin-version} Protocol versioning + +This protocol itself is versioned. At connection setup, the client +states the highest version number it knows how to speak, and then the +plugin responds by choosing the version number that will actually be +spoken (which may not be higher than the client's value). + +Including a version number makes it possible to make breaking changes +to the protocol later. + +Even version numbers represent released versions of this spec. Odd +numbers represent drafts or development versions in between releases. +A client and plugin negotiating an odd version number are not +guaranteed to interoperate; the developer testing the combination is +responsible for ensuring the two are compatible. + +This document describes version 2 of the protocol, the first released +version. (The initial drafts had version 1.) + +\H{authplugin-overview} Overview and sequence of events + +At the very beginning of the user authentication phase of SSH, the +client launches the plugin subprocess, if one is configured. It +immediately sends the \cw{PLUGIN_INIT} message, telling the plugin +some initial information about where the SSH connection is to. + +The plugin responds with \cw{PLUGIN_INIT_RESPONSE}, which may +optionally tell the SSH client what username to use. + +The client begins trying to authenticate with the SSH server in the +usual way, using the username provided by the plugin (if any) or +alternatively one obtained via its normal (non-plugin) policy. + +The client follows its normal policy for selecting authentication +methods to attempt. If it chooses a method that this protocol does not +cover, then the client will perform that method in its own way without +consulting the plugin. + +However, if the client and server decide to attempt a method that this +protocol \e{does} cover, then the client sends \cw{PLUGIN_PROTOCOL} +specifying the SSH protocol id for the authentication method being +used. The plugin responds with \cw{PLUGIN_PROTOCOL_ACCEPT} if it's +willing to assist with this auth method, or +\cw{PLUGIN_PROTOCOL_REJECT} if it isn't. + +If the plugin sends \cw{PLUGIN_PROTOCOL_REJECT}, then the client will +proceed as if the plugin were not present. Later, if another auth +method is negotiated (either because this one failed, or because it +succeeded but the server wants multiple auth methods), the client may +send a further \cw{PLUGIN_PROTOCOL} and try again. + +If the plugin sends \cw{PLUGIN_PROTOCOL_ACCEPT}, then a protocol +segment begins that is specific to that auth method, terminating in +either \cw{PLUGIN_AUTH_SUCCESS} or \cw{PLUGIN_AUTH_FAILURE}. After +that, again, the client may send a further \cw{PLUGIN_PROTOCOL}. + +Currently the only supported method is \cq{keyboard-interactive}, +defined in \k{authplugin-ref-ki}. Once the client has announced this +to the server, the followup protocol is as follows: + +Each time the server sends an \cw{SSH_MSG_USERAUTH_INFO_REQUEST} +message requesting authentication responses from the user, the SSH +client translates the message into \cw{PLUGIN_KI_SERVER_REQUEST} and +passes it on to the plugin. + +At this point, the plugin may optionally send back +\cw{PLUGIN_KI_USER_REQUEST} containing prompts to be presented to the +actual user. The client will reply with a matching +\cw{PLUGIN_KI_USER_RESPONSE} after asking the user to reply to the +question(s) in the request message. The plugin can repeat this cycle +multiple times. + +Once the plugin has all the information it needs to respond to the +server's authentication prompts, it sends \cw{PLUGIN_KI_SERVER_RESPONSE} +back to the client, which translates it into +\cw{SSH_MSG_USERAUTH_INFO_RESPONSE} to send on to the server. + +After that, as described in \k{authplugin-ref-ki}, the server is free +to accept authentication, reject it, or send another +\cw{SSH_MSG_USERAUTH_INFO_REQUEST}. Each +\cw{SSH_MSG_USERAUTH_INFO_REQUEST} is dealt with in the same way as +above. + +If the server terminates keyboard-interactive authentication with +\cw{SSH_MSG_USERAUTH_SUCCESS} or \cw{SSH_MSG_USERAUTH_FAILURE}, the +client informs the plugin by sending either \cw{PLUGIN_AUTH_SUCCESS} +or \cw{PLUGIN_AUTH_FAILURE}. \cw{PLUGIN_AUTH_SUCCESS} is sent when +\e{that particular authentication method} was successful, regardless +of whether the SSH server chooses to request further authentication +afterwards: in particular, \cw{SSH_MSG_USERAUTH_FAILURE} with the +\q{partial success} flag (see \k{authplugin-ref-userauth} section 5.1) translates +into \cw{PLUGIN_AUTH_SUCCESS}. + +The plugin's standard input will close when the client no longer +requires the plugin's services, for any reason. This could be because +authentication is complete (with overall success or overall failure), +or because the user has manually aborted the session in +mid-authentication, or because the client crashed. + +\H{authplugin-messages} Message formats + +This section describes the format of every message in the protocol. + +As described in \k{authplugin-formats}, every message starts with the same two +fields: + +\b \s{uint32}: overall length of the message + +\b \s{byte}: message type. + +The length field does not include itself, but does include the type +code. + +The following subsections each give the format of the remainder of the +message, after the type code. + +The type codes themselves are defined here: + +\c #define PLUGIN_INIT 1 +\c #define PLUGIN_INIT_RESPONSE 2 +\c #define PLUGIN_PROTOCOL 3 +\c #define PLUGIN_PROTOCOL_ACCEPT 4 +\c #define PLUGIN_PROTOCOL_REJECT 5 +\c #define PLUGIN_AUTH_SUCCESS 6 +\c #define PLUGIN_AUTH_FAILURE 7 +\c #define PLUGIN_INIT_FAILURE 8 +\c +\c #define PLUGIN_KI_SERVER_REQUEST 20 +\c #define PLUGIN_KI_SERVER_RESPONSE 21 +\c #define PLUGIN_KI_USER_REQUEST 22 +\c #define PLUGIN_KI_USER_RESPONSE 23 + +If this protocol is extended to be able to assist with further auth +methods, their message type codes will also begin from 20, overlapping +the codes for keyboard-interactive. + +\S{PLUGIN_INIT} \cw{PLUGIN_INIT} + +\s{Direction}: client to plugin + +\s{When}: the first message sent at connection startup + +\s{What happens next}: the plugin will send \cw{PLUGIN_INIT_RESPONSE} +or \cw{PLUGIN_INIT_FAILURE} + +\s{Message contents after the type code}: + +\b \s{uint32}: the highest version number of this protocol that the +client knows how to speak. + +\b \s{string}: the hostname of the server. This will be the \e{logical} +hostname, in cases where it differs from the physical destination of +the network connection. Whatever name would be used by the SSH client +to cache the server's host key, that's the same name passed in this +message. + +\b \s{uint32}: the port number on the server. (Together with the host +name, this forms a primary key identifying a particular server. Port +numbers may be vital because a single host can run two unrelated SSH +servers with completely different authentication requirements, e.g. +system sshd on port 22 and Gerrit on port 29418.) + +\b \s{string}: the username that the client will use to log in, if the +plugin chooses not to override it. An empty string means that the +client has no opinion about this (and might, for example, prompt the +user). + +\S{PLUGIN_INIT_RESPONSE} \cw{PLUGIN_INIT_RESPONSE} + +\s{Direction}: plugin to client + +\s{When}: response to \cw{PLUGIN_INIT} + +\s{What happens next}: the client will send \cw{PLUGIN_PROTOCOL}, or +perhaps terminate the session (if no auth method is ever negotiated +that the plugin can help with) + +\s{Message contents after the type code}: + +\b \s{uint32}: the version number of this protocol that the connection +will use. Must be no greater than the max version number sent by the +client in \cw{PLUGIN_INIT}. + +\b \s{string}: the username that the plugin suggests the client use. An +empty string means that the plugin has no opinion and the client +should stick with the username it already had (or prompt the user, if +it had none). + +\S{PLUGIN_INIT_FAILURE} \cw{PLUGIN_INIT_FAILURE} + +\s{Direction}: plugin to client + +\s{When}: response to \cw{PLUGIN_INIT} + +\s{What happens next}: the session is over + +\s{Message contents after the type code}: + +\b \s{string}: an error message to present to the user indicating why +the plugin was unable to start up. + +\S{PLUGIN_PROTOCOL} \cw{PLUGIN_PROTOCOL} + +\s{Direction}: client to plugin + +\s{When}: sent after \cw{PLUGIN_INIT_RESPONSE}, or after a previous +auth phase terminates with \cw{PLUGIN_AUTH_SUCCESS} or +\cw{PLUGIN_AUTH_FAILURE} + +\s{What happens next}: the plugin will send +\cw{PLUGIN_PROTOCOL_ACCEPT} or \cw{PLUGIN_PROTOCOL_REJECT} + +\s{Message contents after the type code}: + +\b \s{string}: the SSH protocol id of the auth method the client +intends to attempt. Currently the only method specified for use in +this protocol is \cq{keyboard-interactive}. + +\S{PLUGIN_PROTOCOL_REJECT} \cw{PLUGIN_PROTOCOL_REJECT} + +\s{Direction}: plugin to client + +\s{When}: sent after \cw{PLUGIN_PROTOCOL} + +\s{What happens next}: the client will either send another +\cw{PLUGIN_PROTOCOL} or terminate the session + +\s{Message contents after the type code}: + +\b \s{string}: an error message to present to the user, explaining why +the plugin cannot help with this authentication protocol. + +\lcont{ + +An example might be \q{unable to open : }, if the plugin depends on some configuration that the user +has not set up. + +If the plugin does not support this this particular authentication +protocol at all, this string should be left blank, so that no message +will be presented to the user at all. + +} + +\S{PLUGIN_PROTOCOL_ACCEPT} \cw{PLUGIN_PROTOCOL_ACCEPT} + +\s{Direction}: plugin to client + +\s{When}: sent after \cw{PLUGIN_PROTOCOL} + +\s{What happens next}: depends on the auth protocol agreed on. For +keyboard-interactive, the client will send +\cw{PLUGIN_KI_SERVER_REQUEST} or \cw{PLUGIN_AUTH_SUCCESS} or +\cw{PLUGIN_AUTH_FAILURE}. No other method is specified. + +\s{Message contents after the type code}: none. + +\S{PLUGIN_KI_SERVER_REQUEST} \cw{PLUGIN_KI_SERVER_REQUEST} + +\s{Direction}: client to plugin + +\s{When}: sent after \cw{PLUGIN_PROTOCOL}, or after a previous +\cw{PLUGIN_KI_SERVER_RESPONSE}, when the SSH server has sent +\cw{SSH_MSG_USERAUTH_INFO_REQUEST} + +\s{What happens next}: the plugin will send either +\cw{PLUGIN_KI_USER_REQUEST} or \cw{PLUGIN_KI_SERVER_RESPONSE} + +\s{Message contents after the type code}: the exact contents of the +\cw{SSH_MSG_USERAUTH_INFO_REQUEST} just sent by the server. See +\k{authplugin-ref-ki} section 3.2 for details. The summary: + +\b \s{string}: name of this prompt collection (e.g. to use as a +dialog-box title) + +\b \s{string}: instructions to be displayed before this prompt +collection + +\b \s{string}: language tag (deprecated) + +\b \s{uint32}: number of prompts in this collection + +\b That many copies of: + +\lcont{ + +\b \s{string}: prompt (in UTF-8) + +\b \s{boolean}: whether the response to this prompt is safe to echo to +the screen + +} + +\S{PLUGIN_KI_SERVER_RESPONSE} \cw{PLUGIN_KI_SERVER_RESPONSE} + +\s{Direction}: plugin to client + +\s{When}: response to \cw{PLUGIN_KI_SERVER_REQUEST}, perhaps after one +or more intervening pairs of \cw{PLUGIN_KI_USER_REQUEST} and +\cw{PLUGIN_KI_USER_RESPONSE} + +\s{What happens next}: the client will send a further +\cw{PLUGIN_KI_SERVER_REQUEST}, or \cw{PLUGIN_AUTH_SUCCESS} or +\cw{PLUGIN_AUTH_FAILURE} + +\s{Message contents after the type code}: the exact contents of the +\cw{SSH_MSG_USERAUTH_INFO_RESPONSE} that the client should send back +to the server. See \k{authplugin-ref-ki} section 3.4 for details. The +summary: + +\b \s{uint32}: number of responses (must match the \q{number of +prompts} field from the corresponding server request) + +\b That many copies of: + +\lcont{ + +\b \s{string}: response to the \e{n}th prompt (in UTF-8) + +} + +\S{PLUGIN_KI_USER_REQUEST} \cw{PLUGIN_KI_USER_REQUEST} + +\s{Direction}: plugin to client + +\s{When}: response to \cw{PLUGIN_KI_SERVER_REQUEST}, if the plugin +cannot answer the server's auth prompts without presenting prompts of +its own to the user + +\s{What happens next}: the client will send \cw{PLUGIN_KI_USER_RESPONSE} + +\s{Message contents after the type code}: exactly the same as in +\cw{PLUGIN_KI_SERVER_REQUEST} (see \k{PLUGIN_KI_SERVER_REQUEST}). + +\S{PLUGIN_KI_USER_RESPONSE} \cw{PLUGIN_KI_USER_RESPONSE} + +\s{Direction}: client to plugin + +\s{When}: response to \cw{PLUGIN_KI_USER_REQUEST} + +\s{What happens next}: the plugin will send +\cw{PLUGIN_KI_SERVER_RESPONSE}, or another \cw{PLUGIN_KI_USER_REQUEST} + +\s{Message contents after the type code}: exactly the same as in +\cw{PLUGIN_KI_SERVER_RESPONSE} (see \k{PLUGIN_KI_SERVER_RESPONSE}). + +\S{PLUGIN_AUTH_SUCCESS} \cw{PLUGIN_AUTH_SUCCESS} + +\s{Direction}: client to plugin + +\s{When}: sent after \cw{PLUGIN_KI_SERVER_RESPONSE}, or (in unusual +cases) after \cw{PLUGIN_PROTOCOL_ACCEPT} + +\s{What happens next}: the client will either send another +\cw{PLUGIN_PROTOCOL} or terminate the session + +\s{Message contents after the type code}: none + +\S{PLUGIN_AUTH_FAILURE} \cw{PLUGIN_AUTH_FAILURE} + +\s{Direction}: client to plugin + +\s{When}: sent after \cw{PLUGIN_KI_SERVER_RESPONSE}, or (in unusual +cases) after \cw{PLUGIN_PROTOCOL_ACCEPT} + +\s{What happens next}: the client will either send another +\cw{PLUGIN_PROTOCOL} or terminate the session + +\s{Message contents after the type code}: none + +\H{authplugin-refs} References + +\B{authplugin-ref-arch} \W{https://datatracker.ietf.org/doc/html/rfc4251}{RFC 4251}, \q{The Secure Shell (SSH) Protocol +Architecture}. + +\B{authplugin-ref-userauth} \W{https://datatracker.ietf.org/doc/html/rfc4252}{RFC +4252}, \q{The Secure Shell (SSH) Authentication Protocol}. + +\B{authplugin-ref-ki} +\W{https://datatracker.ietf.org/doc/html/rfc4256}{RFC 4256}, +\q{Generic Message Exchange Authentication for the Secure Shell +Protocol (SSH)} (better known by its wire id +\q{keyboard-interactive}). + +\BR{authplugin-ref-arch} [RFC4251] + +\BR{authplugin-ref-userauth} [RFC4252] + +\BR{authplugin-ref-ki} [RFC4256] diff --git a/doc/config.but b/doc/config.but index c2f8d0cb..786b4c5c 100644 --- a/doc/config.but +++ b/doc/config.but @@ -2965,6 +2965,12 @@ 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. +\H{config-ssh-auth-creds} The Credentials panel + +This subpane of the Auth panel contains configuration options that +specify actual \e{credentials} to present to the server: key files and +certificates. + \S{config-ssh-privkey} \q{\ii{Private key} file for authentication} This box is where you enter the name of your private key file if you @@ -3014,6 +3020,26 @@ To do this, enter the pathname of the certificate file into the When this setting is configured, PuTTY will honour it no matter whether the private key is found in a file, or loaded into Pageant. +\S{config-ssh-authplugin} \q{\ii{Plugin} to provide authentication responses} + +An SSH server can use the \q{keyboard-interactive} protocol to present +a series of arbitrary questions and answers. Sometimes this is used +for ordinary passwords, but sometimes the server will use the same +mechanism for something more complicated, such as a one-time password +system. + +Some of these systems can be automated. For this purpose, PuTTY allows +you to provide a separate program to act as a \q{plugin} which will +take over the authentication and send answers to the questions on your +behalf. + +If you have been provided with a plugin of this type, you can +configure it here, by entering a full command line in the \q{Plugin +command to run} box. + +(If you want to \e{write} a plugin of this type, see \k{authplugin} +for the full specification of how the plugin is expected to behave.) + \H{config-ssh-auth-gssapi} The \i{GSSAPI} panel The \q{GSSAPI} subpanel of the \q{Auth} panel controls the use of diff --git a/putty.h b/putty.h index 8f506fb6..5c3adfe9 100644 --- a/putty.h +++ b/putty.h @@ -1831,6 +1831,7 @@ NORETURN void cleanup_exit(int); X(INT, INT, ssh_cipherlist) \ X(FILENAME, NONE, keyfile) \ X(FILENAME, NONE, detached_cert) \ + X(STR, NONE, auth_plugin) \ /* \ * Which SSH protocol to use. \ * For historical reasons, the current legal values for CONF_sshprot \ diff --git a/settings.c b/settings.c index c1119702..44ec1978 100644 --- a/settings.c +++ b/settings.c @@ -633,6 +633,7 @@ void save_open_settings(settings_w *sesskey, Conf *conf) write_setting_b(sesskey, "SSH2DES", conf_get_bool(conf, CONF_ssh2_des_cbc)); write_setting_filename(sesskey, "PublicKeyFile", conf_get_filename(conf, CONF_keyfile)); write_setting_filename(sesskey, "DetachedCertificate", conf_get_filename(conf, CONF_detached_cert)); + write_setting_s(sesskey, "AuthPlugin", conf_get_str(conf, CONF_auth_plugin)); write_setting_s(sesskey, "RemoteCommand", conf_get_str(conf, CONF_remote_cmd)); write_setting_b(sesskey, "RFCEnviron", conf_get_bool(conf, CONF_rfc_environ)); write_setting_b(sesskey, "PassiveTelnet", conf_get_bool(conf, CONF_passive_telnet)); @@ -1052,6 +1053,7 @@ void load_open_settings(settings_r *sesskey, Conf *conf) gppb(sesskey, "SshNoShell", false, conf, CONF_ssh_no_shell); gppfile(sesskey, "PublicKeyFile", conf, CONF_keyfile); gppfile(sesskey, "DetachedCertificate", conf, CONF_detached_cert); + gpps(sesskey, "AuthPlugin", "", conf, CONF_auth_plugin); gpps(sesskey, "RemoteCommand", "", conf, CONF_remote_cmd); gppb(sesskey, "RFCEnviron", false, conf, CONF_rfc_environ); gppb(sesskey, "PassiveTelnet", false, conf, CONF_passive_telnet); diff --git a/ssh.h b/ssh.h index 79f1f431..e447665b 100644 --- a/ssh.h +++ b/ssh.h @@ -1917,3 +1917,35 @@ bool ssh_transient_hostkey_cache_verify( bool ssh_transient_hostkey_cache_has( ssh_transient_hostkey_cache *thc, const ssh_keyalg *alg); bool ssh_transient_hostkey_cache_non_empty(ssh_transient_hostkey_cache *thc); + +/* + * Protocol definitions for authentication helper plugins + */ + +#define AUTHPLUGIN_MSG_NAMES(X) \ + X(PLUGIN_INIT, 1) \ + X(PLUGIN_INIT_RESPONSE, 2) \ + X(PLUGIN_PROTOCOL, 3) \ + X(PLUGIN_PROTOCOL_ACCEPT, 4) \ + X(PLUGIN_PROTOCOL_REJECT, 5) \ + X(PLUGIN_AUTH_SUCCESS, 6) \ + X(PLUGIN_AUTH_FAILURE, 7) \ + X(PLUGIN_INIT_FAILURE, 8) \ + X(PLUGIN_KI_SERVER_REQUEST, 20) \ + X(PLUGIN_KI_SERVER_RESPONSE, 21) \ + X(PLUGIN_KI_USER_REQUEST, 22) \ + X(PLUGIN_KI_USER_RESPONSE, 23) \ + /* end of list */ + +#define PLUGIN_PROTOCOL_MAX_VERSION 2 /* the highest version we speak */ + +enum { + #define ENUMDECL(name, value) name = value, + AUTHPLUGIN_MSG_NAMES(ENUMDECL) + #undef ENUMDECL + + /* Error codes internal to this implementation, indicating failure + * to receive a meaningful packet at all */ + PLUGIN_NOTYPE = 256, /* packet too short to have a type */ + PLUGIN_EOF = 257 /* EOF from auth plugin */ +}; diff --git a/ssh/ppl.h b/ssh/ppl.h index 4ffeffe9..78b08efa 100644 --- a/ssh/ppl.h +++ b/ssh/ppl.h @@ -114,7 +114,8 @@ PacketProtocolLayer *ssh2_userauth_new( bool show_banner, bool tryagent, bool notrivialauth, const char *default_username, bool change_username, bool try_ki_auth, bool try_gssapi_auth, bool try_gssapi_kex_auth, - bool gssapi_fwd, struct ssh_connection_shared_gss_state *shgss); + bool gssapi_fwd, struct ssh_connection_shared_gss_state *shgss, + const char *auth_plugin); PacketProtocolLayer *ssh2_connection_new( Ssh *ssh, ssh_sharing_state *connshare, bool is_simple, Conf *conf, const char *peer_verstring, bufchain *user_input, diff --git a/ssh/ssh.c b/ssh/ssh.c index aba09769..dbd6dde6 100644 --- a/ssh/ssh.c +++ b/ssh/ssh.c @@ -267,14 +267,14 @@ static void ssh_got_ssh_version(struct ssh_version_receiver *rcv, conf_get_bool(ssh->conf, CONF_try_gssapi_auth), conf_get_bool(ssh->conf, CONF_try_gssapi_kex), conf_get_bool(ssh->conf, CONF_gssapifwd), - &ssh->gss_state + &ssh->gss_state, #else false, false, false, - NULL + NULL, #endif - ); + conf_get_str(ssh->conf, CONF_auth_plugin)); ssh_connect_ppl(ssh, userauth_layer); transport_child_layer = userauth_layer; diff --git a/ssh/userauth2-client.c b/ssh/userauth2-client.c index 12910dbb..6bcc651a 100644 --- a/ssh/userauth2-client.c +++ b/ssh/userauth2-client.c @@ -90,6 +90,15 @@ struct ssh2_userauth_state { StripCtrlChars *banner_scc; bool banner_scc_initialised; + char *authplugin_cmd; + Socket *authplugin; + uint32_t authplugin_version; + Plug authplugin_plug; + bufchain authplugin_bc; + strbuf *authplugin_incoming_msg; + bool authplugin_eof; + bool authplugin_ki_active; + StripCtrlChars *ki_scc; bool ki_scc_initialised; bool ki_printed_header; @@ -118,7 +127,7 @@ static PktOut *ssh2_userauth_gss_packet( struct ssh2_userauth_state *s, const char *authtype); #endif static bool ssh2_userauth_ki_setup_prompts( - struct ssh2_userauth_state *s, BinarySource *src); + struct ssh2_userauth_state *s, BinarySource *src, bool plugin); static bool ssh2_userauth_ki_run_prompts(struct ssh2_userauth_state *s); static void ssh2_userauth_ki_write_responses( struct ssh2_userauth_state *s, BinarySink *bs); @@ -140,7 +149,8 @@ PacketProtocolLayer *ssh2_userauth_new( bool show_banner, bool tryagent, bool notrivialauth, const char *default_username, bool change_username, bool try_ki_auth, bool try_gssapi_auth, bool try_gssapi_kex_auth, - bool gssapi_fwd, struct ssh_connection_shared_gss_state *shgss) + bool gssapi_fwd, struct ssh_connection_shared_gss_state *shgss, + const char *authplugin_cmd) { struct ssh2_userauth_state *s = snew(struct ssh2_userauth_state); memset(s, 0, sizeof(*s)); @@ -166,6 +176,8 @@ PacketProtocolLayer *ssh2_userauth_new( s->is_trivial_auth = true; bufchain_init(&s->banner); bufchain_sink_init(&s->banner_bs, &s->banner); + s->authplugin_cmd = dupstr(authplugin_cmd); + bufchain_init(&s->authplugin_bc); return &s->ppl; } @@ -218,6 +230,12 @@ static void ssh2_userauth_free(PacketProtocolLayer *ppl) stripctrl_free(s->banner_scc); if (s->ki_scc) stripctrl_free(s->ki_scc); + sfree(s->authplugin_cmd); + if (s->authplugin) + sk_close(s->authplugin); + bufchain_clear(&s->authplugin_bc); + if (s->authplugin_incoming_msg) + strbuf_free(s->authplugin_incoming_msg); sfree(s); } @@ -288,6 +306,125 @@ static bool ssh2_userauth_signflags(struct ssh2_userauth_state *s, return true; } +static void authplugin_plug_log(Plug *plug, PlugLogType type, SockAddr *addr, + int port, const char *err_msg, int err_code) +{ + struct ssh2_userauth_state *s = container_of( + plug, struct ssh2_userauth_state, authplugin_plug); + PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ + + if (type == PLUGLOG_PROXY_MSG) + ppl_logevent("%s", err_msg); +} + +static void authplugin_plug_closing( + Plug *plug, PlugCloseType type, const char *error_msg) +{ + struct ssh2_userauth_state *s = container_of( + plug, struct ssh2_userauth_state, authplugin_plug); + s->authplugin_eof = true; + queue_idempotent_callback(&s->ppl.ic_process_queue); +} + +static void authplugin_plug_receive( + Plug *plug, int urgent, const char *data, size_t len) +{ + struct ssh2_userauth_state *s = container_of( + plug, struct ssh2_userauth_state, authplugin_plug); + bufchain_add(&s->authplugin_bc, data, len); + queue_idempotent_callback(&s->ppl.ic_process_queue); +} + +static const PlugVtable authplugin_plugvt = { + .log = authplugin_plug_log, + .closing = authplugin_plug_closing, + .receive = authplugin_plug_receive, + .sent = nullplug_sent, +}; + +static strbuf *authplugin_newmsg(uint8_t type) +{ + strbuf *amsg = strbuf_new_nm(); + put_uint32(amsg, 0); /* fill in later */ + put_byte(amsg, type); + return amsg; +} + +static void authplugin_send_free(struct ssh2_userauth_state *s, strbuf *amsg) +{ + PUT_32BIT_MSB_FIRST(amsg->u, amsg->len - 4); + assert(s->authplugin); + sk_write(s->authplugin, amsg->u, amsg->len); + strbuf_free(amsg); +} + +static bool authplugin_expect_msg(struct ssh2_userauth_state *s, + unsigned *type, BinarySource *src) +{ + if (s->authplugin_eof) { + *type = PLUGIN_EOF; + return true; + } + uint8_t len[4]; + if (!bufchain_try_fetch(&s->authplugin_bc, len, 4)) + return false; + size_t size = GET_32BIT_MSB_FIRST(len); + if (bufchain_size(&s->authplugin_bc) - 4 < size) + return false; + if (s->authplugin_incoming_msg) { + strbuf_clear(s->authplugin_incoming_msg); + } else { + s->authplugin_incoming_msg = strbuf_new_nm(); + } + bufchain_consume(&s->authplugin_bc, 4); /* eat length field */ + bufchain_fetch_consume( + &s->authplugin_bc, strbuf_append(s->authplugin_incoming_msg, size), + size); + BinarySource_BARE_INIT_PL( + src, ptrlen_from_strbuf(s->authplugin_incoming_msg)); + *type = get_byte(src); + if (get_err(src)) + *type = PLUGIN_NOTYPE; + return true; +} + +static void authplugin_bad_packet(struct ssh2_userauth_state *s, + unsigned type, const char *fmt, ...) +{ + strbuf *msg = strbuf_new(); + switch (type) { + case PLUGIN_EOF: + put_dataz(msg, "Unexpected end of file from auth helper plugin"); + break; + case PLUGIN_NOTYPE: + put_dataz(msg, "Received malformed packet from auth helper plugin " + "(too short to have a type code)"); + break; + default: + put_fmt(msg, "Received unknown message type %u " + "from auth helper plugin", type); + break; + + #define CASEDECL(name, value) \ + case name: \ + put_fmt(msg, "Received unexpected %s message from auth helper " \ + "plugin", #name); \ + break; + AUTHPLUGIN_MSG_NAMES(CASEDECL); + #undef CASEDECL + } + if (fmt) { + put_dataz(msg, " ("); + va_list ap; + va_start(ap, fmt); + put_fmt(msg, fmt, ap); + va_end(ap); + put_dataz(msg, ")"); + } + ssh_sw_abort(s->ppl.ssh, "%s", msg->s); + strbuf_free(msg); +} + static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) { struct ssh2_userauth_state *s = @@ -502,6 +639,74 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) done_agent_query:; } + s->got_username = false; + + if (*s->authplugin_cmd) { + s->authplugin_plug.vt = &authplugin_plugvt; + s->authplugin = platform_start_subprocess( + s->authplugin_cmd, &s->authplugin_plug, "plugin"); + ppl_logevent("Started authentication plugin: %s", s->authplugin_cmd); + } + + if (s->authplugin) { + strbuf *amsg = authplugin_newmsg(PLUGIN_INIT); + put_uint32(amsg, PLUGIN_PROTOCOL_MAX_VERSION); + put_stringz(amsg, s->hostname); + put_uint32(amsg, s->port); + put_stringz(amsg, s->username ? s->username : ""); + authplugin_send_free(s, amsg); + + BinarySource src[1]; + unsigned type; + crMaybeWaitUntilV(authplugin_expect_msg(s, &type, src)); + switch (type) { + case PLUGIN_INIT_RESPONSE: { + s->authplugin_version = get_uint32(src); + ptrlen username = get_string(src); + if (get_err(src)) { + ssh_sw_abort(s->ppl.ssh, "Received malformed " + "PLUGIN_INIT_RESPONSE from auth helper plugin"); + return; + } + if (s->authplugin_version > PLUGIN_PROTOCOL_MAX_VERSION) { + ssh_sw_abort(s->ppl.ssh, "Auth helper plugin announced " + "unsupported version number %"PRIu32, + s->authplugin_version); + return; + } + if (username.len) { + sfree(s->default_username); + s->default_username = mkstr(username); + ppl_logevent("Authentication plugin set username '%s'", + s->default_username); + } + break; + } + case PLUGIN_INIT_FAILURE: { + ptrlen message = get_string(src); + if (get_err(src)) { + ssh_sw_abort(s->ppl.ssh, "Received malformed " + "PLUGIN_INIT_FAILURE from auth helper plugin"); + return; + } + /* This is a controlled error, so we need not completely + * abandon the connection. Instead, inform the user, and + * proceed as if the plugin was not present */ + ppl_printf("Authentication plugin failed to initialise:\r\n"); + seat_set_trust_status(s->ppl.seat, false); + ppl_printf("%.*s\r\n", PTRLEN_PRINTF(message)); + seat_set_trust_status(s->ppl.seat, true); + sk_close(s->authplugin); + s->authplugin = NULL; + break; + } + default: + authplugin_bad_packet(s, type, "expected PLUGIN_INIT_RESPONSE or " + "PLUGIN_INIT_FAILURE"); + return; + } + } + /* * We repeat this whole loop, including the username prompt, * until we manage a successful authentication. If the user @@ -526,7 +731,6 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) * the username they will want to be able to get back and * retype it! */ - s->got_username = false; while (1) { /* * Get a username. @@ -1341,6 +1545,64 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) ppl_logevent("Attempting keyboard-interactive authentication"); + if (s->authplugin) { + strbuf *amsg = authplugin_newmsg(PLUGIN_PROTOCOL); + put_stringz(amsg, "keyboard-interactive"); + authplugin_send_free(s, amsg); + + BinarySource src[1]; + unsigned type; + crMaybeWaitUntilV(authplugin_expect_msg(s, &type, src)); + switch (type) { + case PLUGIN_PROTOCOL_REJECT: { + ptrlen message = PTRLEN_LITERAL(""); + if (s->authplugin_version >= 2) { + /* draft protocol didn't include a message here */ + message = get_string(src); + } + if (get_err(src)) { + ssh_sw_abort(s->ppl.ssh, "Received malformed " + "PLUGIN_PROTOCOL_REJECT from auth " + "helper plugin"); + return; + } + if (message.len) { + /* If the plugin sent a message about + * _why_ it didn't want to do k-i, pass + * that message on to the user. (It might + * say, for example, what went wrong when + * it tried to open its config file.) */ + ppl_printf("Authentication plugin failed to set " + "up keyboard-interactive " + "authentication:\r\n"); + seat_set_trust_status(s->ppl.seat, false); + ppl_printf("%.*s\r\n", PTRLEN_PRINTF(message)); + seat_set_trust_status(s->ppl.seat, true); + ppl_logevent("Authentication plugin declined to " + "help with keyboard-interactive: " + "%.*s", PTRLEN_PRINTF(message)); + } else { + ppl_logevent("Authentication plugin declined to " + "help with keyboard-interactive"); + } + s->authplugin_ki_active = false; + break; + } + case PLUGIN_PROTOCOL_ACCEPT: + s->authplugin_ki_active = true; + ppl_logevent("Authentication plugin agreed to help " + "with keyboard-interactive"); + break; + default: + authplugin_bad_packet( + s, type, "expected PLUGIN_PROTOCOL_ACCEPT or " + "PLUGIN_PROTOCOL_REJECT"); + return; + } + } else { + s->authplugin_ki_active = false; + } + if (!s->ki_scc_initialised) { s->ki_scc = seat_stripctrl_new( s->ppl.seat, NULL, SIC_KI_PROMPTS); @@ -1364,44 +1626,123 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) s->ki_printed_header = false; /* - * Loop while the server continues to send INFO_REQUESTs. + * Loop while we still have prompts to send to the user. */ - while (pktin->type == SSH2_MSG_USERAUTH_INFO_REQUEST) { - if (!ssh2_userauth_ki_setup_prompts( - s, BinarySource_UPCAST(pktin))) - return; - crMaybeWaitUntilV(ssh2_userauth_ki_run_prompts(s)); + if (!s->authplugin_ki_active) { + /* + * The simple case: INFO_REQUESTs are passed on to + * the user, and responses are sent straight back + * to the SSH server. + */ + while (pktin->type == SSH2_MSG_USERAUTH_INFO_REQUEST) { + if (!ssh2_userauth_ki_setup_prompts( + s, BinarySource_UPCAST(pktin), false)) + return; + crMaybeWaitUntilV(ssh2_userauth_ki_run_prompts(s)); + + if (spr_is_abort(s->spr)) { + /* + * Failed to get responses. Terminate. + */ + free_prompts(s->cur_prompt); + s->cur_prompt = NULL; + ssh_bpp_queue_disconnect( + s->ppl.bpp, "Unable to authenticate", + SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER); + ssh_spr_close(s->ppl.ssh, s->spr, "keyboard-" + "interactive authentication prompt"); + return; + } - if (spr_is_abort(s->spr)) { /* - * Failed to get responses. Terminate. + * Send the response(s) to the server. */ - free_prompts(s->cur_prompt); - s->cur_prompt = NULL; - ssh_bpp_queue_disconnect( - s->ppl.bpp, "Unable to authenticate", - SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER); - ssh_spr_close(s->ppl.ssh, s->spr, "keyboard-" - "interactive authentication prompt"); - return; - } + s->pktout = ssh_bpp_new_pktout( + s->ppl.bpp, SSH2_MSG_USERAUTH_INFO_RESPONSE); + ssh2_userauth_ki_write_responses( + s, BinarySink_UPCAST(s->pktout)); + s->pktout->minlen = 256; + pq_push(s->ppl.out_pq, s->pktout); + /* + * Get the next packet in case it's another + * INFO_REQUEST. + */ + crMaybeWaitUntilV( + (pktin = ssh2_userauth_pop(s)) != NULL); + } + } else { /* - * Send the response(s) to the server. + * The case where a plugin is involved: + * INFO_REQUEST from the server is sent to the + * plugin, which sends responses that we hand back + * to the server. But in the meantime, the plugin + * might send USER_REQUEST for us to pass to the + * user, and then we send responses to that. */ - s->pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH2_MSG_USERAUTH_INFO_RESPONSE); - ssh2_userauth_ki_write_responses( - s, BinarySink_UPCAST(s->pktout)); - s->pktout->minlen = 256; - pq_push(s->ppl.out_pq, s->pktout); + while (pktin->type == SSH2_MSG_USERAUTH_INFO_REQUEST) { + strbuf *amsg = authplugin_newmsg( + PLUGIN_KI_SERVER_REQUEST); + put_datapl(amsg, get_data(pktin, get_avail(pktin))); + authplugin_send_free(s, amsg); - /* - * Get the next packet in case it's another - * INFO_REQUEST. - */ - crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL); + BinarySource src[1]; + unsigned type; + while (true) { + crMaybeWaitUntilV(authplugin_expect_msg( + s, &type, src)); + if (type != PLUGIN_KI_USER_REQUEST) + break; + + if (!ssh2_userauth_ki_setup_prompts(s, src, true)) + return; + crMaybeWaitUntilV(ssh2_userauth_ki_run_prompts(s)); + + if (spr_is_abort(s->spr)) { + /* + * Failed to get responses. Terminate. + */ + free_prompts(s->cur_prompt); + s->cur_prompt = NULL; + ssh_bpp_queue_disconnect( + s->ppl.bpp, "Unable to authenticate", + SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER); + ssh_spr_close( + s->ppl.ssh, s->spr, "keyboard-" + "interactive authentication prompt"); + return; + } + + /* + * Send the responses on to the plugin. + */ + strbuf *amsg = authplugin_newmsg( + PLUGIN_KI_USER_RESPONSE); + ssh2_userauth_ki_write_responses( + s, BinarySink_UPCAST(amsg)); + authplugin_send_free(s, amsg); + } + + if (type != PLUGIN_KI_SERVER_RESPONSE) { + authplugin_bad_packet( + s, type, "expected PLUGIN_KI_SERVER_RESPONSE " + "or PLUGIN_PROTOCOL_USER_REQUEST"); + return; + } + s->pktout = ssh_bpp_new_pktout( + s->ppl.bpp, SSH2_MSG_USERAUTH_INFO_RESPONSE); + put_datapl(s->pktout, get_data(src, get_avail(src))); + s->pktout->minlen = 256; + pq_push(s->ppl.out_pq, s->pktout); + + /* + * Get the next packet in case it's another + * INFO_REQUEST. + */ + crMaybeWaitUntilV( + (pktin = ssh2_userauth_pop(s)) != NULL); + } } /* @@ -1411,7 +1752,9 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) seat_set_trust_status(s->ppl.seat, true); seat_antispoof_msg( ppl_get_iseat(&s->ppl), - "End of keyboard-interactive prompts from server"); + (s->authplugin_ki_active ? + "End of keyboard-interactive prompts from plugin" : + "End of keyboard-interactive prompts from server")); } /* @@ -1419,6 +1762,35 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) */ pq_push_front(s->ppl.in_pq, pktin); + if (s->authplugin_ki_active) { + /* + * As our last communication with the plugin, tell + * it whether the k-i authentication succeeded. + */ + int plugin_msg = -1; + if (pktin->type == SSH2_MSG_USERAUTH_SUCCESS) { + plugin_msg = PLUGIN_AUTH_SUCCESS; + } else if (pktin->type == SSH2_MSG_USERAUTH_FAILURE) { + /* + * Peek in the failure packet to see if it's a + * partial success. + */ + BinarySource src[1]; + BinarySource_BARE_INIT( + src, get_ptr(pktin), get_avail(pktin)); + get_string(pktin); /* skip methods */ + bool partial_success = get_bool(pktin); + if (!get_err(src)) { + plugin_msg = partial_success ? + PLUGIN_AUTH_SUCCESS : PLUGIN_AUTH_FAILURE; + } + } + + if (plugin_msg >= 0) { + strbuf *amsg = authplugin_newmsg(plugin_msg); + authplugin_send_free(s, amsg); + } + } } else if (s->can_passwd) { s->is_trivial_auth = false; /* @@ -1695,7 +2067,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) } static bool ssh2_userauth_ki_setup_prompts( - struct ssh2_userauth_state *s, BinarySource *src) + struct ssh2_userauth_state *s, BinarySource *src, bool plugin) { ptrlen name, inst; strbuf *sb; @@ -1721,14 +2093,17 @@ static bool ssh2_userauth_ki_setup_prompts( bool echo = get_bool(src); if (get_err(src)) { - ssh_proto_error(s->ppl.ssh, "Server sent truncated " - "SSH_MSG_USERAUTH_INFO_REQUEST packet"); + ssh_proto_error(s->ppl.ssh, "%s sent truncated %s packet", + plugin ? "Plugin" : "Server", + plugin ? "PLUGIN_KI_USER_REQUEST" : + "SSH_MSG_USERAUTH_INFO_REQUEST"); return false; } sb = strbuf_new(); if (!prompt.len) { - put_datapl(sb, PTRLEN_LITERAL(": ")); + put_fmt(sb, "<%s failed to send prompt>: ", + plugin ? "plugin" : "server"); } else if (s->ki_scc) { stripctrl_retarget(s->ki_scc, BinarySink_UPCAST(sb)); put_datapl(s->ki_scc, prompt); @@ -1756,8 +2131,11 @@ static bool ssh2_userauth_ki_setup_prompts( */ if (!s->ki_printed_header && s->ki_scc && (s->num_prompts || name.len || inst.len)) { - seat_antispoof_msg(ppl_get_iseat(&s->ppl), "Keyboard-interactive " - "authentication prompts from server:"); + seat_antispoof_msg( + ppl_get_iseat(&s->ppl), + (plugin ? + "Keyboard-interactive authentication prompts from plugin:" : + "Keyboard-interactive authentication prompts from server:")); s->ki_printed_header = true; seat_set_trust_status(s->ppl.seat, false); } @@ -1773,7 +2151,11 @@ static bool ssh2_userauth_ki_setup_prompts( } s->cur_prompt->name_reqd = true; } else { - put_datapl(sb, PTRLEN_LITERAL("SSH server authentication")); + if (plugin) + put_datapl(sb, PTRLEN_LITERAL( + "Communication with authentication plugin")); + else + put_datapl(sb, PTRLEN_LITERAL("SSH server authentication")); s->cur_prompt->name_reqd = false; } s->cur_prompt->name = strbuf_to_str(sb); diff --git a/windows/help.h b/windows/help.h index 799e6240..de6ec0be 100644 --- a/windows/help.h +++ b/windows/help.h @@ -120,6 +120,7 @@ typedef const char *HelpCtx; #define WINHELP_CTX_ssh_no_trivial_userauth "config-ssh-notrivialauth" #define WINHELP_CTX_ssh_auth_banner "config-ssh-banner" #define WINHELP_CTX_ssh_auth_privkey "config-ssh-privkey" +#define WINHELP_CTX_ssh_auth_plugin "config-ssh-authplugin" #define WINHELP_CTX_ssh_auth_cert "config-ssh-cert" #define WINHELP_CTX_ssh_auth_agentfwd "config-ssh-agentfwd" #define WINHELP_CTX_ssh_auth_changeuser "config-ssh-changeuser" -- cgit v1.2.3 From b01173c6b7f2321ab4514561e4db1eaf34fb3555 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 1 Sep 2022 19:33:44 +0100 Subject: Fix cyclic dependency in docs build. If Halibut is not available to build the docs, but on the other hand pre-built man pages already exist (e.g. because you unpacked a source zip file with them already provided), then docs/CMakeLists.txt creates a set of build rules that copy the pre-built man pages from the source directory to the build directory. However, if the source and build directories are the _same_, this creates a set of cyclic dependencies, i.e. files which depend directly on themselves. Some build tools (in particular 'ninja') will report this as an error. In that situation, the simple fix is to leave off the build rules completely: if the man pages are already where the build will want them to end up, there need not be any build rule to do anything about them. --- doc/CMakeLists.txt | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index 2852d9c5..fb4c0c70 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -119,6 +119,18 @@ macro(register_manpage title section) endif() endmacro() +if(NOT HALIBUT) + # If we don't have Halibut available to rebuild the man pages from + # source, we must check whether the build and source directories + # correspond, so as to suppress the build rules that copy them from + # the source dir to the build dir. (Otherwise, someone unpacking + # putty-src.zip and building on a system without Halibut will find + # that there's a circular dependency in the makefile, which at least + # Ninja complains about.) + get_filename_component(DOCBUILDDIR ${CMAKE_CURRENT_BINARY_DIR} REALPATH) + get_filename_component(DOCSRCDIR ${CMAKE_CURRENT_SOURCE_DIR} REALPATH) +endif() + macro(manpage title section) if(HALIBUT) add_custom_command(OUTPUT ${title}.${section} @@ -128,7 +140,8 @@ macro(manpage title section) DEPENDS mancfg.but man-${title}.but) register_manpage(${title} ${section}) - elseif(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${title}.${section}) + elseif(NOT (DOCBUILDDIR STREQUAL DOCSRCDIR) + AND EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${title}.${section}) add_custom_command(OUTPUT ${title}.${section} COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_SOURCE_DIR}/${title}.${section} ${title}.${section} -- cgit v1.2.3 From a01deea1b15e98b3c77c24f4faa1d9e439094acd Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 1 Sep 2022 19:35:42 +0100 Subject: Updates to mksrcarc.sh. .dsp and .dsw files are no longer provided in the source archive (they went out with mkfiles.pl), so there's no need to include an exception to treat them as binary files. On the other hand, the source archives _do_ contain a .chm help file and a .cur mouse pointer image, which _should_ be treated as binary. (That was a benign omission: Info-Zip detected by itself that the files were binary, and didn't mangle them. But it did print an annoying warning, which this commit fixes.) While I'm here, add .git to the list of version control subdirectories to exclude. --- mksrcarc.sh | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/mksrcarc.sh b/mksrcarc.sh index 22337677..4d53e80a 100755 --- a/mksrcarc.sh +++ b/mksrcarc.sh @@ -6,11 +6,12 @@ set -e text=`{ find . -name CVS -prune -o \ -name .cvsignore -prune -o \ -name .svn -prune -o \ + -name .git -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 \ + -name '*.chm' -prune -o \ + -name '*.cur' -prune -o \ -type f -print | sed 's/^\.\///'; } | \ grep -ivE 'test/.*\.txt|MODULE|website.url' | grep -vF .ico | grep -vF .icns` # These are files which I'm _sure_ should be treated as text, but @@ -20,7 +21,7 @@ text=`{ find . -name CVS -prune -o \ bintext=test/*.txt # These are actual binary files which we don't want transforming. bin=`{ ls -1 windows/*.ico windows/website.url; \ - find . -name '*.dsp' -print -o -name '*.dsw' -print; }` + find . -name '*.chm' -print -o -name '*.cur' -print; }` verbosely() { echo "$@" -- cgit v1.2.3 From e6f9df92085d9f5679feff8cec6f44588fb44de0 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 1 Sep 2022 19:37:17 +0100 Subject: sbcsgen.pl: handle \r\n line endings. These show up if you build from the Windows source archive on Unix, which is an odd thing to be trying to do, but I managed it myself the other day by accident :-) --- charset/sbcsgen.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charset/sbcsgen.pl b/charset/sbcsgen.pl index bedf6b36..6012c933 100755 --- a/charset/sbcsgen.pl +++ b/charset/sbcsgen.pl @@ -38,7 +38,7 @@ my @charsetnames = (); my @sortpriority = (); while () { - chomp; + chomp; y/\r//d; if (/^charset (.*)$/) { $charsetname = $1; @vals = (); -- cgit v1.2.3 From c8b66101ee57f1932498861a418be033a181d1dd Mon Sep 17 00:00:00 2001 From: Jacob Nevins Date: Fri, 2 Sep 2022 16:09:40 +0100 Subject: Thread-local support for more Windows toolchains. Use of thread-local storage on Windows (introduced recently in 69e8d471d1) could cause a -Wattributes warning in mingw-w64 builds, since that toolchain doesn't understand __declspec(thread). Define a portability macro THREADLOCAL, which should resolve to something appropriate for at least: - MSVC, which understands the Microsoft syntax __declspec(thread); - GCC (e.g., mingw-w64) which understands the GNU syntax __thread (GCC only implements __declspec() to the extent of understanding the arguments 'dllexport' and 'dllimport'); - Clang, which supports both syntaxes. (It's possible there's some more obscure Windows toolchain which will now hit the #error as a result of this change.) I haven't attempted to try to detect and use the C11 syntax 'thread_local'. And this is all still Windows-only, since that's all we need for now and it avoids opening the can of worms that is TLS on other platforms. (I considered delegating all this to cmake, but as well as being fiddly, it seems even the latest versions of cmake don't know about thread-local storage for C, as opposed to C++ (cxx_thread_local).) --- windows/platform.h | 8 ++++++++ windows/utils/shinydialogbox.c | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/windows/platform.h b/windows/platform.h index bb190ad2..959a207c 100644 --- a/windows/platform.h +++ b/windows/platform.h @@ -35,6 +35,14 @@ #define BUILDINFO_PLATFORM "Windows" #endif +#if defined __GNUC__ || defined __clang__ +#define THREADLOCAL __thread +#elif defined _MSC_VER +#define THREADLOCAL __declspec(thread) +#else +#error Do not know how to declare thread-local storage with this toolchain +#endif + /* Randomly-chosen dwData value identifying a WM_COPYDATA message as * being a Pageant transaction */ #define AGENT_COPYDATA_ID 0x804e50ba diff --git a/windows/utils/shinydialogbox.c b/windows/utils/shinydialogbox.c index 5abb6676..ab3201c1 100644 --- a/windows/utils/shinydialogbox.c +++ b/windows/utils/shinydialogbox.c @@ -31,7 +31,7 @@ struct ShinyDialogBoxState { * threads at all, let alone concurrently, but just in case, declaring * sdb_tempstate as thread-local will protect against that possibility. */ -static __declspec(thread) struct ShinyDialogBoxState *sdb_tempstate; +static THREADLOCAL struct ShinyDialogBoxState *sdb_tempstate; static inline struct ShinyDialogBoxState *ShinyDialogGetState(HWND hwnd) { -- cgit v1.2.3 From fbb979aa985cdcdbc6838a996da1420ba691b70f Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 2 Sep 2022 18:14:21 +0100 Subject: Fix AES build on real Visual Studio. Apparently a nasty trick I did in one of the selector vtable macros was not acceptable to VS, which thinks that "string" ? NULL : NULL is not a constant expression - it can't tell that the string literal has a non-null value _or_ that it doesn't matter whether the value is null or not. Redone the vtable name construction in a way that depends only on the actual preprocessor, not on the followup C expression semantics. --- crypto/aes-select.c | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/crypto/aes-select.c b/crypto/aes-select.c index 892e7b58..f054415a 100644 --- a/crypto/aes-select.c +++ b/crypto/aes-select.c @@ -39,7 +39,7 @@ static ssh_cipher *aes_select(const ssh_cipheralg *alg) #define IF_NEON(...) #endif -#define AES_SELECTOR_VTABLE(mode_c, mode_protocol, mode_display, bits, ...) \ +#define AES_SELECTOR_VTABLE(mode_c, namemaker, mode_display, bits, ...) \ static const ssh_cipheralg * \ ssh_aes ## bits ## _ ## mode_c ## _impls[] = { \ IF_NI(&ssh_aes ## bits ## _ ## mode_c ## _ni,) \ @@ -49,7 +49,7 @@ static ssh_cipher *aes_select(const ssh_cipheralg *alg) }; \ const ssh_cipheralg ssh_aes ## bits ## _ ## mode_c = { \ .new = aes_select, \ - .ssh2_id = "aes" #bits "-" mode_protocol, \ + .ssh2_id = namemaker(bits), \ .blksize = 16, \ .real_keybits = bits, \ .padded_keybytes = bits/8, \ @@ -59,22 +59,27 @@ static ssh_cipher *aes_select(const ssh_cipheralg *alg) __VA_ARGS__ \ } -AES_SELECTOR_VTABLE(cbc, "cbc", "CBC", 128); -AES_SELECTOR_VTABLE(cbc, "cbc", "CBC", 192); -AES_SELECTOR_VTABLE(cbc, "cbc", "CBC", 256); -AES_SELECTOR_VTABLE(sdctr, "ctr", "SDCTR", 128); -AES_SELECTOR_VTABLE(sdctr, "ctr", "SDCTR", 192); -AES_SELECTOR_VTABLE(sdctr, "ctr", "SDCTR", 256); -AES_SELECTOR_VTABLE(gcm, "gcm@openssh.com", "GCM", 128, +#define cbc_namemaker(bits) "aes" #bits "-cbc" +#define ctr_namemaker(bits) "aes" #bits "-ctr" +#define gcm_namemaker(bits) "aes" #bits "-gcm@openssh.com" + +AES_SELECTOR_VTABLE(cbc, cbc_namemaker, "CBC", 128); +AES_SELECTOR_VTABLE(cbc, cbc_namemaker, "CBC", 192); +AES_SELECTOR_VTABLE(cbc, cbc_namemaker, "CBC", 256); +AES_SELECTOR_VTABLE(sdctr, ctr_namemaker, "SDCTR", 128); +AES_SELECTOR_VTABLE(sdctr, ctr_namemaker, "SDCTR", 192); +AES_SELECTOR_VTABLE(sdctr, ctr_namemaker, "SDCTR", 256); +AES_SELECTOR_VTABLE(gcm, gcm_namemaker, "GCM", 128, .required_mac = &ssh2_aesgcm_mac); -AES_SELECTOR_VTABLE(gcm, "gcm@openssh.com", "GCM", 256, +AES_SELECTOR_VTABLE(gcm, gcm_namemaker, "GCM", 256, .required_mac = &ssh2_aesgcm_mac); /* 192-bit AES-GCM is included only so that testcrypt can run standard * test vectors against it. OpenSSH doesn't define a protocol id for - * it. Hence the silly macro trick here to set its ssh2_id to 0, and - * more importantly, leaving it out of aesgcm_list[] below. */ -AES_SELECTOR_VTABLE(gcm, ?NULL:NULL, "GCM", 192, + * it. Hence the use of null_namemaker here to set its ssh2_id to NULL, + * and more importantly, leaving it out of aesgcm_list[] below. */ +#define null_namemaker(bits) NULL +AES_SELECTOR_VTABLE(gcm, null_namemaker, "GCM", 192, .required_mac = &ssh2_aesgcm_mac); static const ssh_cipheralg ssh_rijndael_lysator = { -- cgit v1.2.3 From 1d75ad4c9359d4d6754101b30d09a0a5980e6f65 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 2 Sep 2022 18:18:29 +0100 Subject: Auth plugin: fix early socket closure. My correspondent on the new authentication-plugin feature reports that their plugin is not reliably receiving the final PLUGIN_AUTH_SUCCESS message on Windows. I _think_ this is because the whole userauth layer is being dismissed, leading to sk_close() of the Socket talking to the plugin, before the data has actually been written to the outgoing pipe. This should fix it: track the Socket's backlog, and immediately after sending that message, wait until we receive a notification that the backlog has decreased to size 0. That stops us from terminating the userauth layer until the message has left our process. --- ssh/userauth2-client.c | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/ssh/userauth2-client.c b/ssh/userauth2-client.c index 6bcc651a..b7e36371 100644 --- a/ssh/userauth2-client.c +++ b/ssh/userauth2-client.c @@ -96,6 +96,7 @@ struct ssh2_userauth_state { Plug authplugin_plug; bufchain authplugin_bc; strbuf *authplugin_incoming_msg; + size_t authplugin_backlog; bool authplugin_eof; bool authplugin_ki_active; @@ -335,11 +336,19 @@ static void authplugin_plug_receive( queue_idempotent_callback(&s->ppl.ic_process_queue); } +static void authplugin_plug_sent(Plug *plug, size_t bufsize) +{ + struct ssh2_userauth_state *s = container_of( + plug, struct ssh2_userauth_state, authplugin_plug); + s->authplugin_backlog = bufsize; + queue_idempotent_callback(&s->ppl.ic_process_queue); +} + static const PlugVtable authplugin_plugvt = { .log = authplugin_plug_log, .closing = authplugin_plug_closing, .receive = authplugin_plug_receive, - .sent = nullplug_sent, + .sent = authplugin_plug_sent, }; static strbuf *authplugin_newmsg(uint8_t type) @@ -354,7 +363,7 @@ static void authplugin_send_free(struct ssh2_userauth_state *s, strbuf *amsg) { PUT_32BIT_MSB_FIRST(amsg->u, amsg->len - 4); assert(s->authplugin); - sk_write(s->authplugin, amsg->u, amsg->len); + s->authplugin_backlog = sk_write(s->authplugin, amsg->u, amsg->len); strbuf_free(amsg); } @@ -1789,6 +1798,12 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) if (plugin_msg >= 0) { strbuf *amsg = authplugin_newmsg(plugin_msg); authplugin_send_free(s, amsg); + + /* Wait until we've actually sent it, in case + * we close the connection to the plugin + * before that outgoing message has left our + * own buffers */ + crMaybeWaitUntilV(s->authplugin_backlog == 0); } } } else if (s->can_passwd) { -- cgit v1.2.3 From 71f409f088046059ac025836ad0993adc957231a Mon Sep 17 00:00:00 2001 From: Jacob Nevins Date: Fri, 2 Sep 2022 22:30:59 +0100 Subject: Enable -Wall in Windows STRICT builds. This has been missing since the cmake transition (c19e7215dd) -- mkfiles.pl generally used at least -Wall -Werror -Wvla with GCC-like compilers. After that, Windows STRICT gained -Wpointer-arith, but lost -Wall and hence a lot of other warnings (such as the -Wformat I muttered about in baea34a5b2). My mingw-w64 build survives this (after my recent warning fixes), and apparently an official Bob build does too. --- cmake/platforms/windows.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/platforms/windows.cmake b/cmake/platforms/windows.cmake index 0e1e6150..f8ab8597 100644 --- a/cmake/platforms/windows.cmake +++ b/cmake/platforms/windows.cmake @@ -104,7 +104,7 @@ endif() if(STRICT AND (CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_C_COMPILER_ID MATCHES "Clang")) - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Werror -Wpointer-arith -Wvla") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Werror -Wpointer-arith -Wvla") endif() if(CMAKE_C_COMPILER_ID MATCHES "MSVC") -- cgit v1.2.3 From 1dcf1a41c5734845b452ae915a18f2fae2465698 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 3 Sep 2022 11:31:49 +0100 Subject: Turn -Wall back off for clang-based Windows builds. Unfortunately, it gives an absolutely huge number of warnings, and it wouldn't be feasible to fix them all without risking introducing further bugs. Perhaps _after_ the next release branch it might be worth looking at some of them, but I don't think fixing them right now is viable. I've left it on for actual gcc, though, since the MinGW build seems OK with it. --- cmake/platforms/windows.cmake | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/cmake/platforms/windows.cmake b/cmake/platforms/windows.cmake index f8ab8597..52d2607d 100644 --- a/cmake/platforms/windows.cmake +++ b/cmake/platforms/windows.cmake @@ -102,9 +102,12 @@ else() set(LFLAG_MANIFEST_NO "") endif() -if(STRICT AND (CMAKE_C_COMPILER_ID MATCHES "GNU" OR - CMAKE_C_COMPILER_ID MATCHES "Clang")) - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Werror -Wpointer-arith -Wvla") +if(STRICT) + if(CMAKE_C_COMPILER_ID MATCHES "GNU") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Werror -Wpointer-arith -Wvla") + elseif(CMAKE_C_COMPILER_ID MATCHES "Clang") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Werror -Wpointer-arith -Wvla") + endif() endif() if(CMAKE_C_COMPILER_ID MATCHES "MSVC") -- cgit v1.2.3 From 19ab0e34d60421ee6f95c58aef1097bf4d145832 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 3 Sep 2022 11:33:38 +0100 Subject: Turn on -DSTRICT in the bob Windows builds. Now we should get warned if we do anything that breaks the new stricter MinGW warning level, not to mention anything generating warnings in the clang-cl builds. --- Buildscr | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Buildscr b/Buildscr index a3e1a2cd..76cc7320 100644 --- a/Buildscr +++ b/Buildscr @@ -150,7 +150,7 @@ delegate - # library list), and no other Windows toolchain we build with is that # picky. So this ensures the Windows library structure continues to # work in the most difficult circumstance we expect it to encounter. -in putty do cmake . -DCMAKE_TOOLCHAIN_FILE=cmake/toolchain-mingw.cmake +in putty do cmake . -DCMAKE_TOOLCHAIN_FILE=cmake/toolchain-mingw.cmake -DSTRICT=ON in putty do make -j$(nproc) VERBOSE=1 enddelegate @@ -172,15 +172,15 @@ mkdir putty/windows/abuild64 # # For the 32-bit ones, we set a subsystem version of 5.01, which # allows the resulting files to still run on Windows XP. -in putty/windows/build32 with cmake_at_least_3.20 do cmake ../.. -DCMAKE_TOOLCHAIN_FILE=$(cmake_toolchain_clangcl32) -DCMAKE_BUILD_TYPE=Release -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DPUTTY_LINK_MAPS=ON -DCMAKE_C_FLAGS_RELEASE="/MT /O2" +in putty/windows/build32 with cmake_at_least_3.20 do cmake ../.. -DCMAKE_TOOLCHAIN_FILE=$(cmake_toolchain_clangcl32) -DCMAKE_BUILD_TYPE=Release -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DPUTTY_LINK_MAPS=ON -DCMAKE_C_FLAGS_RELEASE="/MT /O2" -DSTRICT=ON in putty/windows/build32 with cmake_at_least_3.20 do make -j$(nproc) VERBOSE=1 -in putty/windows/build64 with cmake_at_least_3.20 do cmake ../.. -DCMAKE_TOOLCHAIN_FILE=$(cmake_toolchain_clangcl64) -DCMAKE_BUILD_TYPE=Release -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DPUTTY_LINK_MAPS=ON -DCMAKE_C_FLAGS_RELEASE="/MT /O2" +in putty/windows/build64 with cmake_at_least_3.20 do cmake ../.. -DCMAKE_TOOLCHAIN_FILE=$(cmake_toolchain_clangcl64) -DCMAKE_BUILD_TYPE=Release -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DPUTTY_LINK_MAPS=ON -DCMAKE_C_FLAGS_RELEASE="/MT /O2" -DSTRICT=ON in putty/windows/build64 with cmake_at_least_3.20 do make -j$(nproc) VERBOSE=1 # Build experimental Arm Windows binaries. -in putty/windows/abuild32 with cmake_at_least_3.20 do cmake ../.. -DCMAKE_TOOLCHAIN_FILE=$(cmake_toolchain_clangcl_a32) -DCMAKE_BUILD_TYPE=Release -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DPUTTY_LINK_MAPS=ON -DCMAKE_C_FLAGS_RELEASE="/MT /O2" +in putty/windows/abuild32 with cmake_at_least_3.20 do cmake ../.. -DCMAKE_TOOLCHAIN_FILE=$(cmake_toolchain_clangcl_a32) -DCMAKE_BUILD_TYPE=Release -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DPUTTY_LINK_MAPS=ON -DCMAKE_C_FLAGS_RELEASE="/MT /O2" -DSTRICT=ON in putty/windows/abuild32 with cmake_at_least_3.20 do make -j$(nproc) VERBOSE=1 -in putty/windows/abuild64 with cmake_at_least_3.20 do cmake ../.. -DCMAKE_TOOLCHAIN_FILE=$(cmake_toolchain_clangcl_a64) -DCMAKE_BUILD_TYPE=Release -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DPUTTY_LINK_MAPS=ON -DCMAKE_C_FLAGS_RELEASE="/MT /O2" +in putty/windows/abuild64 with cmake_at_least_3.20 do cmake ../.. -DCMAKE_TOOLCHAIN_FILE=$(cmake_toolchain_clangcl_a64) -DCMAKE_BUILD_TYPE=Release -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DPUTTY_LINK_MAPS=ON -DCMAKE_C_FLAGS_RELEASE="/MT /O2" -DSTRICT=ON in putty/windows/abuild64 with cmake_at_least_3.20 do make -j$(nproc) VERBOSE=1 # Make a list of the Windows binaries we're going to ship, so that we @@ -245,7 +245,7 @@ in putty/windows/abuild64 with cmake_at_least_3.20 do make -j$(nproc) VERBOSE=1 # # There's no installer to go with these, so they must also embed the # help file. -in putty/windows/buildold with cmake_at_least_3.20 do cmake ../.. -DCMAKE_TOOLCHAIN_FILE=$(cmake_toolchain_clangcl32_2003) -DCMAKE_BUILD_TYPE=Release -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DPUTTY_LINK_MAPS=ON +in putty/windows/buildold with cmake_at_least_3.20 do cmake ../.. -DCMAKE_TOOLCHAIN_FILE=$(cmake_toolchain_clangcl32_2003) -DCMAKE_BUILD_TYPE=Release -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DPUTTY_LINK_MAPS=ON -DSTRICT=ON in putty/windows/buildold with cmake_at_least_3.20 do make -j$(nproc) VERBOSE=1 # Regenerate to-sign.txt with the 'old' binaries included. -- cgit v1.2.3 From 23245fb4185171c1e687a8bc64f4e1f148e6e7ea Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 3 Sep 2022 11:35:33 +0100 Subject: Reset the diagnostic syntax in clang-cl builds. I've only just found out that you can set it back to the Unix-like syntax, which I find more convenient. --- cmake/platforms/windows.cmake | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/cmake/platforms/windows.cmake b/cmake/platforms/windows.cmake index 52d2607d..c4299b2c 100644 --- a/cmake/platforms/windows.cmake +++ b/cmake/platforms/windows.cmake @@ -110,6 +110,16 @@ if(STRICT) endif() endif() +if(CMAKE_C_COMPILER_ID MATCHES "Clang") + # Switch back from MSVC-style error message format + # "file.c(line,col)" to clang's native style "file.c:line:col:". I + # find the latter more convenient because it matches other Unixy + # tools like grep, and I have tooling to parse that format and jump + # to the sites of error messages. + set(CMAKE_C_FLAGS + "${CMAKE_C_FLAGS} -Xclang -fdiagnostics-format -Xclang clang") +endif() + if(CMAKE_C_COMPILER_ID MATCHES "MSVC") # Turn off some warnings that I've just found too noisy. # -- cgit v1.2.3 From 10d3645a9319bca828aacc89638a3af28a110d64 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 3 Sep 2022 11:39:15 +0100 Subject: Remove an unused helper function. I was wondering what this was doing here at all when strbuf_chomp is a better choice. The answer turned out to be 'nothing' - it wasn't even used any more. --- import.c | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/import.c b/import.c index 79a4359d..f5d6045d 100644 --- a/import.c +++ b/import.c @@ -174,17 +174,6 @@ bool export_ssh2(const Filename *filename, int type, return false; } -/* - * 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.) */ -- cgit v1.2.3 From 1b851758bd02c03fa9ae1736bc2646ac46d81df0 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 3 Sep 2022 11:43:46 +0100 Subject: Add some missing #includes. My experimental build with clang-cl at -Wall did show up a few things that are safe enough to fix right now. One was this list of missing includes, which was causing a lot of -Wmissing-prototype warnings, and is a real risk because it means the declarations in headers weren't being type-checked against the actual function definitions. Happily, no actual mismatches. --- cmake/gitcommit.cmake | 1 + keygen/mpunsafe.c | 1 + psocks.c | 1 + utils/x11_dehexify.c | 1 + utils/x11_identify_auth_proto.c | 1 + utils/x11_parse_ip.c | 1 + windows/no-jump-list.c | 2 ++ windows/plink.c | 1 + windows/utils/arm_arch_queries.c | 1 + windows/utils/minefield.c | 1 + windows/utils/platform_get_x_display.c | 1 + windows/window.c | 1 + 12 files changed, 13 insertions(+) diff --git a/cmake/gitcommit.cmake b/cmake/gitcommit.cmake index adb644bc..0aa8a095 100644 --- a/cmake/gitcommit.cmake +++ b/cmake/gitcommit.cmake @@ -45,6 +45,7 @@ if(OUTPUT_TYPE STREQUAL header) * Generated by cmake/gitcommit.cmake. */ +#include \"putty.h\" const char commitid[] = \"${commit}\"; ") elseif(OUTPUT_TYPE STREQUAL halibut) diff --git a/keygen/mpunsafe.c b/keygen/mpunsafe.c index 6265d40f..2cd7a37a 100644 --- a/keygen/mpunsafe.c +++ b/keygen/mpunsafe.c @@ -7,6 +7,7 @@ #include "puttymem.h" #include "mpint.h" +#include "mpunsafe.h" #include "crypto/mpint_i.h" /* diff --git a/psocks.c b/psocks.c index 64c46953..eb7c8f01 100644 --- a/psocks.c +++ b/psocks.c @@ -7,6 +7,7 @@ #include #include "putty.h" +#include "storage.h" #include "misc.h" #include "ssh.h" #include "ssh/channel.h" diff --git a/utils/x11_dehexify.c b/utils/x11_dehexify.c index 13ffb0d0..080f8c91 100644 --- a/utils/x11_dehexify.c +++ b/utils/x11_dehexify.c @@ -4,6 +4,7 @@ */ #include "putty.h" +#include "ssh.h" void *x11_dehexify(ptrlen hexpl, int *outlen) { diff --git a/utils/x11_identify_auth_proto.c b/utils/x11_identify_auth_proto.c index 14075a3f..46e26dc0 100644 --- a/utils/x11_identify_auth_proto.c +++ b/utils/x11_identify_auth_proto.c @@ -4,6 +4,7 @@ */ #include "putty.h" +#include "ssh.h" int x11_identify_auth_proto(ptrlen protoname) { diff --git a/utils/x11_parse_ip.c b/utils/x11_parse_ip.c index 12649b49..8f6a7bfb 100644 --- a/utils/x11_parse_ip.c +++ b/utils/x11_parse_ip.c @@ -6,6 +6,7 @@ #include #include "putty.h" +#include "ssh.h" bool x11_parse_ip(const char *addr_string, unsigned long *ip) { diff --git a/windows/no-jump-list.c b/windows/no-jump-list.c index 7c76f668..beabd107 100644 --- a/windows/no-jump-list.c +++ b/windows/no-jump-list.c @@ -3,6 +3,8 @@ * that don't update the jump list. */ +#include "putty.h" + void add_session_to_jumplist(const char * const sessionname) {} void remove_session_from_jumplist(const char * const sessionname) {} void clear_jumplist(void) {} diff --git a/windows/plink.c b/windows/plink.c index 4ae6cf4d..05c25073 100644 --- a/windows/plink.c +++ b/windows/plink.c @@ -8,6 +8,7 @@ #include #include "putty.h" +#include "ssh.h" #include "storage.h" #include "tree234.h" #include "security-api.h" diff --git a/windows/utils/arm_arch_queries.c b/windows/utils/arm_arch_queries.c index b683ac15..b0193276 100644 --- a/windows/utils/arm_arch_queries.c +++ b/windows/utils/arm_arch_queries.c @@ -4,6 +4,7 @@ */ #include "putty.h" +#include "ssh.h" #if !(defined _M_ARM || defined _M_ARM64) /* diff --git a/windows/utils/minefield.c b/windows/utils/minefield.c index de4d4836..c5262ae6 100644 --- a/windows/utils/minefield.c +++ b/windows/utils/minefield.c @@ -11,6 +11,7 @@ */ #include "putty.h" +#include "puttymem.h" #define PAGESIZE 4096 diff --git a/windows/utils/platform_get_x_display.c b/windows/utils/platform_get_x_display.c index b3d11afc..e2a9ddc6 100644 --- a/windows/utils/platform_get_x_display.c +++ b/windows/utils/platform_get_x_display.c @@ -4,6 +4,7 @@ */ #include "putty.h" +#include "ssh.h" char *platform_get_x_display(void) { diff --git a/windows/window.c b/windows/window.c index 99acdbf2..059a123c 100644 --- a/windows/window.c +++ b/windows/window.c @@ -14,6 +14,7 @@ #define COMPILE_MULTIMON_STUBS #include "putty.h" +#include "ssh.h" #include "terminal.h" #include "storage.h" #include "putty-rc.h" -- cgit v1.2.3 From 40dfbeba41926260fe3f419197e7050823565e64 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 3 Sep 2022 10:43:48 +0100 Subject: Fix aes-select.c macros again. I decided that the 'namemaker' system introduced recently in commit fbb979aa985cdcd was just too marginal to be sensible, and it's easier to simply quote the full SSH id for each protocol. Also, included an empty argument at the end of each macro invocation, so that the variadic "..." is never completely missing. --- crypto/aes-select.c | 31 +++++++++++++------------------ 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/crypto/aes-select.c b/crypto/aes-select.c index f054415a..62b4ab01 100644 --- a/crypto/aes-select.c +++ b/crypto/aes-select.c @@ -39,7 +39,7 @@ static ssh_cipher *aes_select(const ssh_cipheralg *alg) #define IF_NEON(...) #endif -#define AES_SELECTOR_VTABLE(mode_c, namemaker, mode_display, bits, ...) \ +#define AES_SELECTOR_VTABLE(mode_c, id, mode_display, bits, ...) \ static const ssh_cipheralg * \ ssh_aes ## bits ## _ ## mode_c ## _impls[] = { \ IF_NI(&ssh_aes ## bits ## _ ## mode_c ## _ni,) \ @@ -49,7 +49,7 @@ static ssh_cipher *aes_select(const ssh_cipheralg *alg) }; \ const ssh_cipheralg ssh_aes ## bits ## _ ## mode_c = { \ .new = aes_select, \ - .ssh2_id = namemaker(bits), \ + .ssh2_id = id, \ .blksize = 16, \ .real_keybits = bits, \ .padded_keybytes = bits/8, \ @@ -59,27 +59,22 @@ static ssh_cipher *aes_select(const ssh_cipheralg *alg) __VA_ARGS__ \ } -#define cbc_namemaker(bits) "aes" #bits "-cbc" -#define ctr_namemaker(bits) "aes" #bits "-ctr" -#define gcm_namemaker(bits) "aes" #bits "-gcm@openssh.com" - -AES_SELECTOR_VTABLE(cbc, cbc_namemaker, "CBC", 128); -AES_SELECTOR_VTABLE(cbc, cbc_namemaker, "CBC", 192); -AES_SELECTOR_VTABLE(cbc, cbc_namemaker, "CBC", 256); -AES_SELECTOR_VTABLE(sdctr, ctr_namemaker, "SDCTR", 128); -AES_SELECTOR_VTABLE(sdctr, ctr_namemaker, "SDCTR", 192); -AES_SELECTOR_VTABLE(sdctr, ctr_namemaker, "SDCTR", 256); -AES_SELECTOR_VTABLE(gcm, gcm_namemaker, "GCM", 128, +AES_SELECTOR_VTABLE(cbc, "aes128-cbc", "CBC", 128, ); +AES_SELECTOR_VTABLE(cbc, "aes192-cbc", "CBC", 192, ); +AES_SELECTOR_VTABLE(cbc, "aes256-cbc", "CBC", 256, ); +AES_SELECTOR_VTABLE(sdctr, "aes128-ctr", "SDCTR", 128, ); +AES_SELECTOR_VTABLE(sdctr, "aes192-ctr", "SDCTR", 192, ); +AES_SELECTOR_VTABLE(sdctr, "aes256-ctr", "SDCTR", 256, ); +AES_SELECTOR_VTABLE(gcm, "aes128-gcm@openssh.com", "GCM", 128, .required_mac = &ssh2_aesgcm_mac); -AES_SELECTOR_VTABLE(gcm, gcm_namemaker, "GCM", 256, +AES_SELECTOR_VTABLE(gcm, "aes256-gcm@openssh.com", "GCM", 256, .required_mac = &ssh2_aesgcm_mac); /* 192-bit AES-GCM is included only so that testcrypt can run standard * test vectors against it. OpenSSH doesn't define a protocol id for - * it. Hence the use of null_namemaker here to set its ssh2_id to NULL, - * and more importantly, leaving it out of aesgcm_list[] below. */ -#define null_namemaker(bits) NULL -AES_SELECTOR_VTABLE(gcm, null_namemaker, "GCM", 192, + * it. Hence setting its ssh2_id to NULL here, and more importantly, + * leaving it out of aesgcm_list[] below. */ +AES_SELECTOR_VTABLE(gcm, NULL, "GCM", 192, .required_mac = &ssh2_aesgcm_mac); static const ssh_cipheralg ssh_rijndael_lysator = { -- cgit v1.2.3 From a8981212f513dadd4954173a4cb117d96c32e85c Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 3 Sep 2022 10:54:10 +0100 Subject: Add a missing prototype. Too much C++, I expect - 'void foo()' in C++ means what I wanted it to mean! --- misc.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc.h b/misc.h index f9e59b50..1b3d324a 100644 --- a/misc.h +++ b/misc.h @@ -518,7 +518,7 @@ bool cert_expr_match_str(const char *expression, * to handle legacy configuration from early in development, when * multiple wildcards were stored separately in config, implicitly * ORed together. */ -CertExprBuilder *cert_expr_builder_new(); +CertExprBuilder *cert_expr_builder_new(void); void cert_expr_builder_free(CertExprBuilder *eb); void cert_expr_builder_add(CertExprBuilder *eb, const char *wildcard); char *cert_expr_expression(CertExprBuilder *eb); -- cgit v1.2.3 From ed94aa5058c0c4c99723dbd86a14d67172df015a Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 3 Sep 2022 10:54:42 +0100 Subject: Remove spurious 'const' on return types. --- ssh.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ssh.h b/ssh.h index e447665b..5ecef0cb 100644 --- a/ssh.h +++ b/ssh.h @@ -939,9 +939,9 @@ static inline const char *ssh_key_ssh_id(ssh_key *key) { return key->vt->ssh_id; } static inline const char *ssh_key_cache_id(ssh_key *key) { return key->vt->cache_id; } -static inline const unsigned ssh_key_supported_flags(ssh_key *key) +static inline unsigned ssh_key_supported_flags(ssh_key *key) { return key->vt->supported_flags(key->vt); } -static inline const unsigned ssh_keyalg_supported_flags(const ssh_keyalg *self) +static inline unsigned ssh_keyalg_supported_flags(const ssh_keyalg *self) { return self->supported_flags(self); } static inline const char *ssh_keyalg_alternate_ssh_id( const ssh_keyalg *self, unsigned flags) -- cgit v1.2.3 From c12cde1bea7a530d9771dccdaa907a65aa6a44ed Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 3 Sep 2022 11:12:29 +0100 Subject: Fix an uninitialised variable. This looks like a real error! And recently introduced, in commit cd094b28a3b1779. --- windows/controls.c | 1 + 1 file changed, 1 insertion(+) diff --git a/windows/controls.c b/windows/controls.c index e02e3e3d..ae5a81fc 100644 --- a/windows/controls.c +++ b/windows/controls.c @@ -1540,6 +1540,7 @@ void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, sfree(escaped); sfree(wrapped); } else { + num_ids = 1; editboxfw(&pos, false, true, NULL, 0, base_id); SetDlgItemText(pos.hwnd, base_id, ctrl->label); MakeDlgItemBorderless(pos.hwnd, base_id); -- cgit v1.2.3 From 9a84a89c32e94918a7a98518cd2683473851dc9b Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 3 Sep 2022 12:02:48 +0100 Subject: Add a batch of missing 'static's. --- crypto/bcrypt.c | 18 +++++++++--------- crypto/ecc-ssh.c | 6 +++--- crypto/ntru.c | 15 +++++++++------ crypto/openssh-certs.c | 28 +++++++++++++++------------- crypto/rsa.c | 3 ++- import.c | 4 ++-- proxy/cproxy.c | 3 ++- proxy/proxy.c | 2 +- ssh/sftp.c | 2 +- ssh/sharing.c | 17 ++++++++--------- ssh/zlib.c | 21 ++++++++++----------- terminal/bidi.c | 7 ++++--- terminal/terminal.c | 2 +- utils/x11authfile.c | 4 ++-- windows/network.c | 7 ++++--- windows/unicode.c | 2 +- 16 files changed, 74 insertions(+), 67 deletions(-) diff --git a/crypto/bcrypt.c b/crypto/bcrypt.c index a4eb384a..1ccd4756 100644 --- a/crypto/bcrypt.c +++ b/crypto/bcrypt.c @@ -11,8 +11,8 @@ #include "ssh.h" #include "blowfish.h" -BlowfishContext *bcrypt_setup(const unsigned char *key, int keybytes, - const unsigned char *salt, int saltbytes) +static BlowfishContext *bcrypt_setup(const unsigned char *key, int keybytes, + const unsigned char *salt, int saltbytes) { int i; BlowfishContext *ctx; @@ -32,9 +32,9 @@ BlowfishContext *bcrypt_setup(const unsigned char *key, int keybytes, return ctx; } -void bcrypt_hash(const unsigned char *key, int keybytes, - const unsigned char *salt, int saltbytes, - unsigned char output[32]) +static void bcrypt_hash(const unsigned char *key, int keybytes, + const unsigned char *salt, int saltbytes, + unsigned char output[32]) { BlowfishContext *ctx; int i; @@ -49,10 +49,10 @@ void bcrypt_hash(const unsigned char *key, int keybytes, blowfish_free_context(ctx); } -void bcrypt_genblock(int counter, - const unsigned char hashed_passphrase[64], - const unsigned char *salt, int saltbytes, - unsigned char output[32]) +static void bcrypt_genblock(int counter, + const unsigned char hashed_passphrase[64], + const unsigned char *salt, int saltbytes, + unsigned char output[32]) { unsigned char hashed_salt[64]; diff --git a/crypto/ecc-ssh.c b/crypto/ecc-ssh.c index a43cb841..26a04815 100644 --- a/crypto/ecc-ssh.c +++ b/crypto/ecc-ssh.c @@ -1444,7 +1444,7 @@ typedef struct ecdh_key_m { ecdh_key ek; } ecdh_key_m; -ecdh_key *ssh_ecdhkex_w_new(const ssh_kex *kex, bool is_server) +static ecdh_key *ssh_ecdhkex_w_new(const ssh_kex *kex, bool is_server) { const struct eckex_extra *extra = (const struct eckex_extra *)kex->extra; const struct ec_curve *curve = extra->curve(); @@ -1463,7 +1463,7 @@ ecdh_key *ssh_ecdhkex_w_new(const ssh_kex *kex, bool is_server) return &dhw->ek; } -ecdh_key *ssh_ecdhkex_m_new(const ssh_kex *kex, bool is_server) +static ecdh_key *ssh_ecdhkex_m_new(const ssh_kex *kex, bool is_server) { const struct eckex_extra *extra = (const struct eckex_extra *)kex->extra; const struct ec_curve *curve = extra->curve(); @@ -1637,7 +1637,7 @@ const ssh_kex ssh_ec_kex_curve25519 = { .extra = &kex_extra_curve25519, }; /* Pre-RFC alias */ -const ssh_kex ssh_ec_kex_curve25519_libssh = { +static const ssh_kex ssh_ec_kex_curve25519_libssh = { .name = "curve25519-sha256@libssh.org", .main_type = KEXTYPE_ECDH, .hash = &ssh_sha256, diff --git a/crypto/ntru.c b/crypto/ntru.c index c313c0cc..fb802694 100644 --- a/crypto/ntru.c +++ b/crypto/ntru.c @@ -483,7 +483,8 @@ void ntru_scale(uint16_t *out, const uint16_t *in, uint16_t scale, * Given an array of values mod 3, convert them to values mod q in a * way that maps -1,0,+1 to -1,0,+1. */ -void ntru_expand(uint16_t *out, const uint16_t *in, unsigned p, unsigned q) +static void ntru_expand( + uint16_t *out, const uint16_t *in, unsigned p, unsigned q) { for (size_t i = 0; i < p; i++) { uint16_t v = in[i]; @@ -1393,8 +1394,9 @@ void ntru_encode_plaintext(const uint16_t *plaintext, unsigned p, * * 'out' should therefore expect to receive 32 bytes of data. */ -void ntru_confirmation_hash(uint8_t *out, const uint16_t *plaintext, - const uint16_t *pubkey, unsigned p, unsigned q) +static void ntru_confirmation_hash( + uint8_t *out, const uint16_t *plaintext, + const uint16_t *pubkey, unsigned p, unsigned q) { /* The outer hash object */ ssh_hash *hconfirm = ssh_hash_new(&ssh_sha512); @@ -1450,8 +1452,9 @@ void ntru_confirmation_hash(uint8_t *out, const uint16_t *plaintext, * * The ciphertext is provided in already-encoded form. */ -void ntru_session_hash(uint8_t *out, unsigned ok, const uint16_t *plaintext, - unsigned p, ptrlen ciphertext, ptrlen confirmation_hash) +static void ntru_session_hash( + uint8_t *out, unsigned ok, const uint16_t *plaintext, + unsigned p, ptrlen ciphertext, ptrlen confirmation_hash) { /* The outer hash object */ ssh_hash *hsession = ssh_hash_new(&ssh_sha512); @@ -1866,7 +1869,7 @@ static const ecdh_keyalg ssh_ntru_selector_vt = { .description = ssh_ntru_description, }; -const ssh_kex ssh_ntru_curve25519 = { +static const ssh_kex ssh_ntru_curve25519 = { .name = "sntrup761x25519-sha512@openssh.com", .main_type = KEXTYPE_ECDH, .hash = &ssh_sha512, diff --git a/crypto/openssh-certs.c b/crypto/openssh-certs.c index 979b5488..01bc5cc6 100644 --- a/crypto/openssh-certs.c +++ b/crypto/openssh-certs.c @@ -93,35 +93,37 @@ typedef struct opensshcert_extra { * info appears at all, it's in the same order everywhere, and none of * it is repeated unnecessarily */ enum { DSA_p, DSA_q, DSA_g, DSA_y, DSA_x }; -const unsigned dsa_pub_fmt[] = { DSA_p, DSA_q, DSA_g, DSA_y }; -const unsigned dsa_base_ossh_fmt[] = { DSA_p, DSA_q, DSA_g, DSA_y, DSA_x }; -const unsigned dsa_cert_ossh_fmt[] = { DSA_x }; +static const unsigned dsa_pub_fmt[] = { DSA_p, DSA_q, DSA_g, DSA_y }; +static const unsigned dsa_base_ossh_fmt[] = { + DSA_p, DSA_q, DSA_g, DSA_y, DSA_x }; +static const unsigned dsa_cert_ossh_fmt[] = { DSA_x }; /* ECDSA is almost as nice, except that it pointlessly mentions the * curve name in the public data, which shouldn't be necessary given * that the SSH key id has already implied it. But at least that's * consistent everywhere. */ enum { ECDSA_curve, ECDSA_point, ECDSA_exp }; -const unsigned ecdsa_pub_fmt[] = { ECDSA_curve, ECDSA_point }; -const unsigned ecdsa_base_ossh_fmt[] = { ECDSA_curve, ECDSA_point, ECDSA_exp }; -const unsigned ecdsa_cert_ossh_fmt[] = { ECDSA_exp }; +static const unsigned ecdsa_pub_fmt[] = { ECDSA_curve, ECDSA_point }; +static const unsigned ecdsa_base_ossh_fmt[] = { + ECDSA_curve, ECDSA_point, ECDSA_exp }; +static const unsigned ecdsa_cert_ossh_fmt[] = { ECDSA_exp }; /* Ed25519 has the oddity that the private data following the * certificate in the OpenSSH blob is preceded by an extra copy of the * public data, for no obviously necessary reason since that doesn't * happen in any of the rest of these formats */ enum { EDDSA_point, EDDSA_exp }; -const unsigned eddsa_pub_fmt[] = { EDDSA_point }; -const unsigned eddsa_base_ossh_fmt[] = { EDDSA_point, EDDSA_exp }; -const unsigned eddsa_cert_ossh_fmt[] = { EDDSA_point, EDDSA_exp }; +static const unsigned eddsa_pub_fmt[] = { EDDSA_point }; +static const unsigned eddsa_base_ossh_fmt[] = { EDDSA_point, EDDSA_exp }; +static const unsigned eddsa_cert_ossh_fmt[] = { EDDSA_point, EDDSA_exp }; /* And RSA has the quirk that the modulus and exponent are reversed in * the base key type's OpenSSH blob! */ enum { RSA_e, RSA_n, RSA_d, RSA_p, RSA_q, RSA_iqmp }; -const unsigned rsa_pub_fmt[] = { RSA_e, RSA_n }; -const unsigned rsa_base_ossh_fmt[] = { +static const unsigned rsa_pub_fmt[] = { RSA_e, RSA_n }; +static const unsigned rsa_base_ossh_fmt[] = { RSA_n, RSA_e, RSA_d, RSA_p, RSA_q, RSA_iqmp }; -const unsigned rsa_cert_ossh_fmt[] = { RSA_d, RSA_p, RSA_q, RSA_iqmp }; +static const unsigned rsa_cert_ossh_fmt[] = { RSA_d, RSA_p, RSA_q, RSA_iqmp }; /* * Routines to transform one kind of blob into another based on those @@ -238,7 +240,7 @@ static const ssh_keyalg *opensshcert_related_alg(const ssh_keyalg *self, /* end of list */ #define KEYALG_DEF(name, ssh_alg_id_prefix, ssh_key_id_prefix, fmt_prefix) \ - const struct opensshcert_extra opensshcert_##name##_extra = { \ + static const struct opensshcert_extra opensshcert_##name##_extra = { \ .pub_fmt = { .fmt = fmt_prefix ## _pub_fmt, \ .len = lenof(fmt_prefix ## _pub_fmt) }, \ .base_ossh_fmt = { .fmt = fmt_prefix ## _base_ossh_fmt, \ diff --git a/crypto/rsa.c b/crypto/rsa.c index ad4d9541..aa0e08a6 100644 --- a/crypto/rsa.c +++ b/crypto/rsa.c @@ -865,7 +865,8 @@ static unsigned ssh_rsa_supported_flags(const ssh_keyalg *self) return SSH_AGENT_RSA_SHA2_256 | SSH_AGENT_RSA_SHA2_512; } -const char *ssh_rsa_alternate_ssh_id(const ssh_keyalg *self, unsigned flags) +static const char *ssh_rsa_alternate_ssh_id( + const ssh_keyalg *self, unsigned flags) { if (flags & SSH_AGENT_RSA_SHA2_512) return ssh_rsa_sha512.ssh_id; diff --git a/import.c b/import.c index f5d6045d..918de50e 100644 --- a/import.c +++ b/import.c @@ -317,7 +317,7 @@ struct openssh_pem_key { strbuf *keyblob; }; -void BinarySink_put_mp_ssh2_from_string(BinarySink *bs, ptrlen str) +static void BinarySink_put_mp_ssh2_from_string(BinarySink *bs, ptrlen str) { const unsigned char *bytes = (const unsigned char *)str.ptr; size_t nbytes = str.len; @@ -1893,7 +1893,7 @@ static bool sshcom_encrypted(BinarySource *filesrc, char **comment) return answer; } -void BinarySink_put_mp_sshcom_from_string(BinarySink *bs, ptrlen str) +static void BinarySink_put_mp_sshcom_from_string(BinarySink *bs, ptrlen str) { const unsigned char *bytes = (const unsigned char *)str.ptr; size_t nbytes = str.len; diff --git a/proxy/cproxy.c b/proxy/cproxy.c index 38e6eef9..40a2f609 100644 --- a/proxy/cproxy.c +++ b/proxy/cproxy.c @@ -26,7 +26,8 @@ strbuf *chap_response(ptrlen challenge, ptrlen password) return sb; } -void BinarySink_put_hex_data(BinarySink *bs, const void *vptr, size_t len) +static void BinarySink_put_hex_data(BinarySink *bs, const void *vptr, + size_t len) { const unsigned char *p = (const unsigned char *)vptr; const char *hexdigits = "0123456789abcdef"; diff --git a/proxy/proxy.c b/proxy/proxy.c index 056dcf19..bca60a35 100644 --- a/proxy/proxy.c +++ b/proxy/proxy.c @@ -35,7 +35,7 @@ static void proxy_negotiator_cleanup(ProxySocket *ps) * Call this when proxy negotiation is complete, so that this * socket can begin working normally. */ -void proxy_activate(ProxySocket *ps) +static void proxy_activate(ProxySocket *ps) { size_t output_before, output_after; diff --git a/ssh/sftp.c b/ssh/sftp.c index 00072f13..1f98c5ec 100644 --- a/ssh/sftp.c +++ b/ssh/sftp.c @@ -21,7 +21,7 @@ static void fxp_internal_error(const char *msg); * Client-specific parts of the send- and receive-packet system. */ -bool sftp_send(struct sftp_packet *pkt) +static bool sftp_send(struct sftp_packet *pkt) { bool ret; sftp_send_prepare(pkt); diff --git a/ssh/sharing.c b/ssh/sharing.c index 827830f5..97337229 100644 --- a/ssh/sharing.c +++ b/ssh/sharing.c @@ -987,8 +987,8 @@ static void share_xchannel_add_message( xc->msgtail = msg; } -void share_dead_xchannel_respond(struct ssh_sharing_connstate *cs, - struct share_xchannel *xc) +static void share_dead_xchannel_respond(struct ssh_sharing_connstate *cs, + struct share_xchannel *xc) { /* * Handle queued incoming messages from the server destined for an @@ -1033,10 +1033,9 @@ void share_dead_xchannel_respond(struct ssh_sharing_connstate *cs, } } -void share_xchannel_confirmation(struct ssh_sharing_connstate *cs, - struct share_xchannel *xc, - struct share_channel *chan, - unsigned downstream_window) +static void share_xchannel_confirmation( + struct ssh_sharing_connstate *cs, struct share_xchannel *xc, + struct share_channel *chan, unsigned downstream_window) { strbuf *packet; @@ -1070,8 +1069,8 @@ void share_xchannel_confirmation(struct ssh_sharing_connstate *cs, strbuf_free(packet); } -void share_xchannel_failure(struct ssh_sharing_connstate *cs, - struct share_xchannel *xc) +static void share_xchannel_failure(struct ssh_sharing_connstate *cs, + struct share_xchannel *xc) { /* * If downstream refuses to open our X channel at all for some @@ -1986,7 +1985,7 @@ static int share_listen_accepting(Plug *plug, * configurations which return the same string from this function will * be treated as potentially shareable with each other. */ -char *ssh_share_sockname(const char *host, int port, Conf *conf) +static char *ssh_share_sockname(const char *host, int port, Conf *conf) { char *username = NULL; char *sockname; diff --git a/ssh/zlib.c b/ssh/zlib.c index e64d37a9..0841faa3 100644 --- a/ssh/zlib.c +++ b/ssh/zlib.c @@ -573,7 +573,7 @@ struct ssh_zlib_compressor { ssh_compressor sc; }; -ssh_compressor *zlib_compress_init(void) +static ssh_compressor *zlib_compress_init(void) { struct Outbuf *out; struct ssh_zlib_compressor *comp = snew(struct ssh_zlib_compressor); @@ -592,7 +592,7 @@ ssh_compressor *zlib_compress_init(void) return &comp->sc; } -void zlib_compress_cleanup(ssh_compressor *sc) +static void zlib_compress_cleanup(ssh_compressor *sc) { struct ssh_zlib_compressor *comp = container_of(sc, struct ssh_zlib_compressor, sc); @@ -604,10 +604,9 @@ void zlib_compress_cleanup(ssh_compressor *sc) sfree(comp); } -void zlib_compress_block(ssh_compressor *sc, - const unsigned char *block, int len, - unsigned char **outblock, int *outlen, - int minlen) +static void zlib_compress_block( + ssh_compressor *sc, const unsigned char *block, int len, + unsigned char **outblock, int *outlen, int minlen) { struct ssh_zlib_compressor *comp = container_of(sc, struct ssh_zlib_compressor, sc); @@ -904,7 +903,7 @@ struct zlib_decompress_ctx { ssh_decompressor dc; }; -ssh_decompressor *zlib_decompress_init(void) +static ssh_decompressor *zlib_decompress_init(void) { struct zlib_decompress_ctx *dctx = snew(struct zlib_decompress_ctx); unsigned char lengths[288]; @@ -927,7 +926,7 @@ ssh_decompressor *zlib_decompress_init(void) return &dctx->dc; } -void zlib_decompress_cleanup(ssh_decompressor *dc) +static void zlib_decompress_cleanup(ssh_decompressor *dc) { struct zlib_decompress_ctx *dctx = container_of(dc, struct zlib_decompress_ctx, dc); @@ -986,9 +985,9 @@ static void zlib_emit_char(struct zlib_decompress_ctx *dctx, int c) #define EATBITS(n) ( dctx->nbits -= (n), dctx->bits >>= (n) ) -bool zlib_decompress_block(ssh_decompressor *dc, - const unsigned char *block, int len, - unsigned char **outblock, int *outlen) +static bool zlib_decompress_block( + ssh_decompressor *dc, const unsigned char *block, int len, + unsigned char **outblock, int *outlen) { struct zlib_decompress_ctx *dctx = container_of(dc, struct zlib_decompress_ctx, dc); diff --git a/terminal/bidi.c b/terminal/bidi.c index 128f84c5..c17671b6 100644 --- a/terminal/bidi.c +++ b/terminal/bidi.c @@ -3558,13 +3558,14 @@ static void reverse_sequences(BidiContext *ctx) } /* - * The Main Bidi Function, and the only function that should be used - * by the outside world. + * The Main Bidi Function. The two wrappers below it present different + * external APIs for different purposes, but everything comes through + * here. * * text: a buffer of size textlen containing text to apply the * Bidirectional algorithm to. */ -void do_bidi_new(BidiContext *ctx, bidi_char *text, size_t textlen) +static void do_bidi_new(BidiContext *ctx, bidi_char *text, size_t textlen) { ensure_arrays(ctx, textlen); ctx->text = text; diff --git a/terminal/terminal.c b/terminal/terminal.c index 4ecfcab5..ee227190 100644 --- a/terminal/terminal.c +++ b/terminal/terminal.c @@ -1537,7 +1537,7 @@ static void set_erase_char(Terminal *term) * lookups which would be involved in fetching them from the former * every time. */ -void term_copy_stuff_from_conf(Terminal *term) +static void term_copy_stuff_from_conf(Terminal *term) { term->ansi_colour = conf_get_bool(term->conf, CONF_ansi_colour); term->no_arabicshaping = conf_get_bool(term->conf, CONF_no_arabicshaping); diff --git a/utils/x11authfile.c b/utils/x11authfile.c index 4fc84ab5..e2358a9d 100644 --- a/utils/x11authfile.c +++ b/utils/x11authfile.c @@ -9,7 +9,7 @@ #include "putty.h" #include "ssh.h" -ptrlen BinarySource_get_string_xauth(BinarySource *src) +static ptrlen BinarySource_get_string_xauth(BinarySource *src) { size_t len = get_uint16(src); return get_data(src, len); @@ -17,7 +17,7 @@ ptrlen BinarySource_get_string_xauth(BinarySource *src) #define get_string_xauth(src) \ BinarySource_get_string_xauth(BinarySource_UPCAST(src)) -void BinarySink_put_stringpl_xauth(BinarySink *bs, ptrlen pl) +static void BinarySink_put_stringpl_xauth(BinarySink *bs, ptrlen pl) { assert((pl.len >> 16) == 0); put_uint16(bs, pl.len); diff --git a/windows/network.c b/windows/network.c index 5dd728cd..2c087b69 100644 --- a/windows/network.c +++ b/windows/network.c @@ -1124,8 +1124,9 @@ Socket *sk_new(SockAddr *addr, int port, bool privport, bool oobinline, return &ret->sock; } -Socket *sk_newlistener_internal(const char *srcaddr, int port, Plug *plug, - bool local_host_only, int orig_address_family) +static Socket *sk_newlistener_internal( + const char *srcaddr, int port, Plug *plug, + bool local_host_only, int orig_address_family) { SOCKET s; SOCKADDR_IN a; @@ -1413,7 +1414,7 @@ static void socket_error_callback(void *vs) * The function which tries to send on a socket once it's deemed * writable. */ -void try_send(NetSocket *s) +static void try_send(NetSocket *s) { while (s->sending_oob || bufchain_size(&s->output_data) > 0) { int nsent; diff --git a/windows/unicode.c b/windows/unicode.c index 72c35ea6..7f0542d8 100644 --- a/windows/unicode.c +++ b/windows/unicode.c @@ -728,7 +728,7 @@ wchar_t xlat_uskbd2cyrllic(int ch) return cyrtab[ch&0x7F]; } -int check_compose_internal(int first, int second, int recurse) +static int check_compose_internal(int first, int second, int recurse) { static const struct { -- cgit v1.2.3 From 26f220a1a0b095336af61af6c5bfac5d4f038ddc Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 3 Sep 2022 12:02:58 +0100 Subject: Remove a completely unused global variable. --- windows/gss.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/windows/gss.c b/windows/gss.c index 8f467cf2..724eeec1 100644 --- a/windows/gss.c +++ b/windows/gss.c @@ -91,8 +91,6 @@ typedef struct 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); static tree234 *libraries_to_never_unload; -- cgit v1.2.3 From 9e7d4c53d80b6ebb9598610ab706faf2218c8a7b Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 4 Sep 2022 10:54:34 +0100 Subject: Rename confusing variables in psftp_main(). Another of this weekend's warnings pointed out that this function contained a pattern I now regard as a cardinal sin: variables called 'ret' that aren't clear whether they've _been_ returned from a subroutine, or whether they're _planned_ to be returned from the containing function. Worse, psftp_main had both: two of the former kind shadowing a case of the latter in sub-scopes. --- psftp.c | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/psftp.c b/psftp.c index db57d89d..d8b5c400 100644 --- a/psftp.c +++ b/psftp.c @@ -2790,7 +2790,7 @@ const unsigned cmdline_tooltype = TOOLTYPE_FILETRANSFER; */ int psftp_main(int argc, char *argv[]) { - int i, ret; + int i, toret; int portnumber = 0; char *userhost, *user; int mode = 0; @@ -2807,7 +2807,7 @@ int psftp_main(int argc, char *argv[]) do_defaults(NULL, conf); for (i = 1; i < argc; i++) { - int ret; + int retd; if (argv[i][0] != '-') { if (userhost) usage(); @@ -2815,12 +2815,13 @@ int psftp_main(int argc, char *argv[]) userhost = dupstr(argv[i]); continue; } - ret = cmdline_process_param(argv[i], i+1 Date: Tue, 6 Sep 2022 10:42:19 +0100 Subject: Windows: move the right control for align_next_to. We had carefully calculated, for each control in an aligned group, how much _that control_ should move downwards by. But then, because I carelessly referred to the wrong variable name, we actually moved the wrong one - not the control we'd just calculated the offset for, but always the _last_ one in the group, which was the one the top-level alignment code was processing at the point we began this loop. As a result, the dropdown list in the front-page protocol selector was hilariously misaligned. Now it's back where it should be. --- windows/controls.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/windows/controls.c b/windows/controls.c index ae5a81fc..ce3638e4 100644 --- a/windows/controls.c +++ b/windows/controls.c @@ -1758,7 +1758,7 @@ void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, continue; LONG dy = (mid2 - (rect.top + rect.bottom)) / 2; - move_windows(pos.hwnd, c->base_id, c->num_ids, dy); + move_windows(pos.hwnd, thisc->base_id, thisc->num_ids, dy); } } } else { -- cgit v1.2.3 From 93e6da65acfd6f5e646eb07572671955b9992791 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Tue, 6 Sep 2022 10:59:03 +0100 Subject: buildinfo.c: add another Visual Studio version. It's not listed on the docs web page yet, but my Windows machine just installed it, so I was able to observe myself what value of _MSC_VER it defines. --- utils/buildinfo.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/utils/buildinfo.c b/utils/buildinfo.c index c5d7255a..5a837e01 100644 --- a/utils/buildinfo.c +++ b/utils/buildinfo.c @@ -44,6 +44,8 @@ char *buildinfo(const char *newline) * cases, two different compiler versions have the same _MSC_VER * value, and have to be distinguished by _MSC_FULL_VER. */ +#elif _MSC_VER == 1933 + put_fmt(buf, " 2022 (17.3)"); #elif _MSC_VER == 1932 put_fmt(buf, " 2022 (17.2)"); #elif _MSC_VER == 1931 -- cgit v1.2.3 From 16d5bb726972df6b5329aa1654f649c0dce31ef0 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Tue, 6 Sep 2022 11:13:50 +0100 Subject: GTK: fix y computation in align_next_to. The protocol selector widgets were misaligned in GTK as well as on Windows, but for a completely different reason. (I guess both bugs must have been introduced at the same time when I reworked the system to tolerate more than two aligned widgets in commit b5ab90143a2df7f.) To vertically align N widgets, you have to first figure out what range of y-coordinates they jointly occupy, and then centre each one within that range. We were trying to do both jobs in the same pass, which meant trying to place the first widget before finding out where the last one will be. To do this, we were separately computing the y-range's start and width, the former by taking max of the y-coordinates _seen so far_, and the latter by taking max of _all_ the widgets' heights. This has two problems. One is that if you later find out that the y-coordinate of the top of the range needs to be lower than you'd previously realised, it's too late to go back and reposition the widgets you've already placed. But that's a theoretical issue that would only come up with more complicated column layouts than we've actually used. (And probably more complicated than would even be _sensible_ to use.) The other, more immediate, problem: the y-coordinates we were using for already-placed widgets in the set were the ones _after_ we adjusted each one for vertical centring. So if the first widget is short and the second taller (say, heights 20 and 30 pixels), then the first widget will be offset downwards by 5 pixels, but the second widget will use that offset y-coordinate as the _top_ of the range to fit itself into, and hence, will also be 5 pixels downward from where it should have been. I think only the second of those problems is immediately concerning, but it's easier to fix both at once. I've removed the y-adjustment for vertical centring from the main layout loop, and put it in a separate pass run after the main layout finishes. --- unix/columns.c | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/unix/columns.c b/unix/columns.c index 917b39e6..3dbed9c3 100644 --- a/unix/columns.c +++ b/unix/columns.c @@ -918,6 +918,12 @@ static void columns_alloc_vert(Columns *cols, gint ourheight, children = children->next) child->visited = false; + /* + * Main layout loop. In this loop, vertically aligned controls are + * only half dealt with: we assign each one enough _height_ to + * match the others in its group, but we don't adjust its y + * coordinates yet. + */ for (children = cols->children; children && (child = children->data); children = children->next) { @@ -969,7 +975,7 @@ static void columns_alloc_vert(Columns *cols, gint ourheight, if (topy < colypos[child->colstart+i]) topy = colypos[child->colstart+i]; } - child->y = topy + fakeheight/2 - realheight/2; + child->y = topy; child->h = realheight; child->visited = true; boty = topy + fakeheight + cols->spacing; @@ -979,6 +985,28 @@ static void columns_alloc_vert(Columns *cols, gint ourheight, } } + /* + * Now make a separate pass that deals with vertical alignment by + * moving controls downwards based on the difference between their + * own height and the largest height of anything in their group. + */ + for (children = cols->children; + children && (child = children->data); + children = children->next) { + if (!child->widget) + continue; + if (!gtk_widget_get_visible(child->widget)) + continue; + + fakeheight = realheight = child->h; + for (ColumnsChild *ch = child->valign_next; ch != child; + ch = ch->valign_next) { + if (fakeheight < ch->h) + fakeheight = ch->h; + } + child->y += fakeheight/2 - realheight/2; + } + g_free(colypos); } -- cgit v1.2.3 From 1f6d93f0c87a478e10591960e71fe9dce0a0ecf0 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Wed, 7 Sep 2022 13:52:58 +0100 Subject: Fix a batch of resource leaks spotted by Coverity. --- crypto/ntru.c | 10 ++++++++-- ssh/kex2-client.c | 1 + unix/storage.c | 2 ++ windows/puttygen.c | 1 + 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/crypto/ntru.c b/crypto/ntru.c index fb802694..edc57a91 100644 --- a/crypto/ntru.c +++ b/crypto/ntru.c @@ -1242,12 +1242,12 @@ ptrlen ntru_decode_pubkey(uint16_t *pubkey, unsigned p, unsigned q, } else { /* Do the decoding */ ntru_decode(sched, pubkey, encoded); - ntru_encode_schedule_free(sched); /* Unbias the coefficients */ ntru_bias(pubkey, pubkey, q-q/2, p, q); } + ntru_encode_schedule_free(sched); return encoded; } @@ -1328,13 +1328,13 @@ ptrlen ntru_decode_ciphertext(uint16_t *ct, NTRUKeyPair *keypair, } else { /* Do the decoding */ ntru_decode(sched, ct, encoded); - ntru_encode_schedule_free(sched); /* Undo the scaling and bias */ ntru_scale(ct, ct, 3, p, q); ntru_bias(ct, ct, q - 3 * ciphertext_bias(q), p, q); } + ntru_encode_schedule_free(sched); return encoded; /* also useful to the caller, optionally */ } @@ -1649,6 +1649,7 @@ static bool ssh_ntru_client_getkey(ecdh_key *dh, ptrlen remoteKey, if (!ok) { ssh_hash_free(h); smemclr(hashdata, sizeof(hashdata)); + strbuf_free(otherkey); return false; } @@ -1778,6 +1779,7 @@ static bool ssh_ntru_server_getkey(ecdh_key *dh, ptrlen remoteKey, ntru_encrypt(ciphertext, nk->plaintext, pubkey, p_LIVE, q_LIVE); ntru_encode_ciphertext(ciphertext, p_LIVE, q_LIVE, BinarySink_UPCAST(nk->ciphertext_encoded)); + ring_free(ciphertext, p_LIVE); /* Compute the confirmation hash, and write it into another * strbuf. */ @@ -1793,6 +1795,9 @@ static bool ssh_ntru_server_getkey(ecdh_key *dh, ptrlen remoteKey, /* And put the NTRU session hash into the main hash object. */ put_data(h, hashdata, 32); + + /* Now we can free the public key */ + ring_free(pubkey, p_LIVE); } /* @@ -1809,6 +1814,7 @@ static bool ssh_ntru_server_getkey(ecdh_key *dh, ptrlen remoteKey, if (!ok) { ssh_hash_free(h); smemclr(hashdata, sizeof(hashdata)); + strbuf_free(otherkey); return false; } diff --git a/ssh/kex2-client.c b/ssh/kex2-client.c index 3fca162e..ca9fa505 100644 --- a/ssh/kex2-client.c +++ b/ssh/kex2-client.c @@ -962,6 +962,7 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) } else { ppl_logevent("Rejected host key certificate: %s", error->s); + strbuf_free(error); /* now fall through into normal host key checking */ } } diff --git a/unix/storage.c b/unix/storage.c index a2456fc4..ca225732 100644 --- a/unix/storage.c +++ b/unix/storage.c @@ -679,6 +679,8 @@ host_ca *host_ca_load(const char *name) sfree(line); } + fclose(fp); + if (eb) { if (!hca->validity_expression) { hca->validity_expression = cert_expr_expression(eb); diff --git a/windows/puttygen.c b/windows/puttygen.c index 52b1a9cb..143e7b5f 100644 --- a/windows/puttygen.c +++ b/windows/puttygen.c @@ -1256,6 +1256,7 @@ void add_certificate(HWND hwnd, struct MainDlgState *state, message_box(hwnd, msg, "PuTTYgen Error", MB_OK | MB_ICONERROR, HELPCTXID(errors_cantloadkey)); sfree(msg); + strbuf_free(pub); return; } -- cgit v1.2.3 From 3442fb1aeba03778478f9ef1a4406ab7bb2777f4 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Wed, 7 Sep 2022 14:18:21 +0100 Subject: windows/unicode.c: tighten up a bounds check. Coverity points out that if we refer to cp_list[codepage - 65536], we ought to have ensured that codepage - 65536 was _less_ than lenof(cp_list), not just less or equal. --- windows/unicode.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/windows/unicode.c b/windows/unicode.c index 7f0542d8..b3f6d802 100644 --- a/windows/unicode.c +++ b/windows/unicode.c @@ -531,7 +531,7 @@ static reverse_mapping *get_reverse_mapping(int codepage) if (codepage < 65536) return NULL; - if (codepage > 65536 + lenof(cp_list)) + if (codepage >= 65536 + lenof(cp_list)) return NULL; const struct cp_list_item *cp = &cp_list[codepage - 65536]; if (!cp->cp_table) -- cgit v1.2.3 From ebaa37e1590e77644aa5d0c9a85506f18b9bf38b Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Wed, 7 Sep 2022 14:23:36 +0100 Subject: utils/cert-expr.c: remove 'lasttoktext' field. Coverity spotted me copying an uninitialised variable into it, which made me wonder how I hadn't noticed. The answer is that nothing actually _uses_ that variable - it's written, but never read. I must have put it in during development, thinking I was going to need it for something, and then didn't end up using it after all. --- utils/cert-expr.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/utils/cert-expr.c b/utils/cert-expr.c index 8fe8df7c..880239e4 100644 --- a/utils/cert-expr.c +++ b/utils/cert-expr.c @@ -347,7 +347,7 @@ typedef struct ParserState ParserState; struct ParserState { ptrlen currtext; Token tok; - ptrlen toktext, lasttoktext; + ptrlen toktext; char *err; ptrlen errloc; }; @@ -365,7 +365,6 @@ static void error(ParserState *ps, char *errtext, ptrlen errloc) static void advance(ParserState *ps) { char *err = NULL; - ps->lasttoktext = ps->toktext; ps->tok = lex(&ps->currtext, &ps->toktext, &err); if (ps->tok == TOK_ERROR) error(ps, err, ps->toktext); @@ -542,7 +541,6 @@ static ExprNode *parse(ptrlen expr, char **error_msg, ptrlen *error_loc) { ParserState ps[1]; ps->currtext = expr; - ps->lasttoktext = make_ptrlen(ps->currtext.ptr, 0); ps->err = NULL; advance(ps); -- cgit v1.2.3 From 8c72a9daa4fffe989d6d342838f650a923058a86 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Wed, 7 Sep 2022 14:45:35 +0100 Subject: Windows Pageant: add a missing null-pointer check (maybe). Coverity complained in keylist_update_callback that in one if statement I was allowing for the possibility that alg == NULL, and in the next, I was assuming it would always be non-null. Right now I'm not actually convinced that _either_ check is necessary - it would make sense in an agent _client_, where you might be talking to an agent that knows key algorithms you don't, but this is the GUI built into Pageant itself, so any key it can store internally ought to have a known algorithm name. Still, this fix is certainly _correct_ even if not optimal, and it'll do for now. --- windows/pageant.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/windows/pageant.c b/windows/pageant.c index 60040310..d1368903 100644 --- a/windows/pageant.c +++ b/windows/pageant.c @@ -375,7 +375,7 @@ static void keylist_update_callback( } ptrlen bits_word = ptrlen_get_word(&fingerprint, " "); - if (ssh_keyalg_variable_size(alg)) + if (alg && ssh_keyalg_variable_size(alg)) put_datapl(disp->bits, bits_word); put_datapl(disp->hash, ptrlen_get_word(&fingerprint, " ")); -- cgit v1.2.3 From d2165448021d434e8762c117cafd65cc689557b7 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Wed, 7 Sep 2022 14:01:51 +0100 Subject: windows/console.c: add an assertion to pacify Coverity. It complained in console_confirm_ssh_host_key that if the caller passed in a SeatDialogText containing no SDT_PROMPT record, then 'prompt' would be uninitialised. The answer is "don't do that, then", but fair enough that Coverity didn't know that. Added an assertion, which should keep it happy, and also cause better error handling if we ever _do_ make that mistake. --- windows/console.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/windows/console.c b/windows/console.c index 3cc41364..2fd572b4 100644 --- a/windows/console.c +++ b/windows/console.c @@ -39,7 +39,7 @@ SeatPromptResult console_confirm_ssh_host_key( { HANDLE hin; DWORD savemode, i; - const char *prompt; + const char *prompt = NULL; stdio_sink errsink[1]; stdio_sink_init(errsink, stderr); @@ -75,6 +75,7 @@ SeatPromptResult console_confirm_ssh_host_key( break; } } + assert(prompt); /* something in the SeatDialogText should have set this */ while (true) { fprintf(stderr, -- cgit v1.2.3 From 8590b7f2e20d40678901438a2482837dac491851 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Wed, 7 Sep 2022 14:01:51 +0100 Subject: unix/console.c: add the same assertion again. Somehow I missed that Coverity reported that complaint about a (theoretically) uninitialised pointer twice, against the two platforms' console.c files. Now fixed the same way in the other one. --- unix/console.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/unix/console.c b/unix/console.c index 75e51a7f..286ecf29 100644 --- a/unix/console.c +++ b/unix/console.c @@ -109,7 +109,7 @@ SeatPromptResult console_confirm_ssh_host_key( { char line[32]; struct termios cf; - const char *prompt; + const char *prompt = NULL; stdio_sink errsink[1]; stdio_sink_init(errsink, stderr); @@ -146,6 +146,7 @@ SeatPromptResult console_confirm_ssh_host_key( break; } } + assert(prompt); /* something in the SeatDialogText should have set this */ while (true) { fprintf(stderr, -- cgit v1.2.3 From dc875ca0dc07a492a3707e1ac47b95d90e92edd4 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 10 Sep 2022 10:15:27 +0100 Subject: Make rekeys work when KEXINIT filtering is enabled. I only realised this bug while writing up the feature for the wishlist: It's one thing _at connection startup_ to delay sending your KEXINIT until the server has sent its: the server is very likely to send it anyway (unless it's attempting the same workaround against us), so probably nothing goes wrong. But if we want to initiate a rekey, we do that _by_ sending a KEXINIT. In that situation we can't just wait until the server sends one, because it has no idea it's supposed to be doing so! Happily, in that situation, we already have a KEXINIT from the server, left over from the previous key exchange. So we can filter against that, and still have the intended effect of not spending KEXINIT space on algorithms the server doesn't know about. --- ssh/transport2.c | 62 ++++++++++++++++++++++++++++++++++++++++---------------- ssh/transport2.h | 2 +- 2 files changed, 46 insertions(+), 18 deletions(-) diff --git a/ssh/transport2.c b/ssh/transport2.c index f7b08f15..62ad6ffd 100644 --- a/ssh/transport2.c +++ b/ssh/transport2.c @@ -1196,7 +1196,16 @@ static bool ssh2_scan_kexinits( return true; } -static strbuf *write_filtered_kexinit(struct ssh2_transport_state *s) +static inline bool delay_outgoing_kexinit(struct ssh2_transport_state *s) +{ + if (!(s->ppl.remote_bugs & BUG_REQUIRES_FILTERED_KEXINIT)) + return false; /* bug flag not enabled => no need to delay */ + if (s->incoming_kexinit->len) + return false; /* already got a remote KEXINIT we can filter against */ + return true; +} + +static void filter_outgoing_kexinit(struct ssh2_transport_state *s) { strbuf *pktout = strbuf_new(); BinarySource osrc[1], isrc[1]; @@ -1262,7 +1271,15 @@ static strbuf *write_filtered_kexinit(struct ssh2_transport_state *s) put_bool(pktout, get_bool(osrc)); /* first-kex-packet-follows */ put_uint32(pktout, get_uint32(osrc)); /* reserved word */ - return pktout; + /* + * Dump this data into s->outgoing_kexinit in place of what we had + * there before. We need to remember the KEXINIT we _really_ sent, + * not the one we'd have liked to send, since the host key + * signature will be validated against the former. + */ + strbuf_shrink_to(s->outgoing_kexinit, 1); /* keep the type byte */ + put_datapl(s->outgoing_kexinit, ptrlen_from_strbuf(pktout)); + strbuf_free(pktout); } void ssh2transport_finalise_exhash(struct ssh2_transport_state *s) @@ -1373,9 +1390,29 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl) put_uint32(s->outgoing_kexinit, 0); /* reserved */ /* - * Send our KEXINIT (in the normal case). + * Send our KEXINIT, most of the time. + * + * An exception: in BUG_REQUIRES_FILTERED_KEXINIT mode, we have to + * have seen at least one KEXINIT from the server first, so that + * we can filter our own KEXINIT down to contain only algorithms + * the server mentioned. + * + * But we only need to do this on the _first_ key exchange, when + * we've never seen a KEXINIT from the server before. In rekeys, + * we still have the server's previous KEXINIT lying around, so we + * can filter based on that. + * + * (And a good thing too, since the way you _initiate_ a rekey is + * by sending your KEXINIT, so we'd have no way to prod the server + * into sending its first!) */ - if (!(s->ppl.remote_bugs & BUG_REQUIRES_FILTERED_KEXINIT)) { + s->kexinit_delayed = delay_outgoing_kexinit(s); + if (!s->kexinit_delayed) { + if (s->ppl.remote_bugs & BUG_REQUIRES_FILTERED_KEXINIT) { + /* Filter based on the KEXINIT from the previous exchange */ + filter_outgoing_kexinit(s); + } + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEXINIT); put_data(pktout, s->outgoing_kexinit->u + 1, s->outgoing_kexinit->len - 1); /* omit type byte */ @@ -1406,21 +1443,12 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl) * If we've delayed sending our KEXINIT so as to filter it down to * only things the server won't choke on, send ours now. */ - if (s->ppl.remote_bugs & BUG_REQUIRES_FILTERED_KEXINIT) { - strbuf *sb = write_filtered_kexinit(s); - - /* Send that data as a packet */ + if (s->kexinit_delayed) { + filter_outgoing_kexinit(s); pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEXINIT); - put_datapl(pktout, ptrlen_from_strbuf(sb)); + put_data(pktout, s->outgoing_kexinit->u + 1, + s->outgoing_kexinit->len - 1); /* omit type byte */ pq_push(s->ppl.out_pq, pktout); - - /* And also replace our previous outgoing KEXINIT, since the - * host key signature will be validated against this reduced - * one. */ - strbuf_shrink_to(s->outgoing_kexinit, 1); /* keep the type byte */ - put_datapl(s->outgoing_kexinit, ptrlen_from_strbuf(sb)); - - strbuf_free(sb); } /* diff --git a/ssh/transport2.h b/ssh/transport2.h index 7ea39cbb..204573fb 100644 --- a/ssh/transport2.h +++ b/ssh/transport2.h @@ -153,7 +153,7 @@ struct ssh2_transport_state { char *client_greeting, *server_greeting; - bool kex_in_progress; + bool kex_in_progress, kexinit_delayed; unsigned long next_rekey, last_rekey; const char *deferred_rekey_reason; bool higher_layer_ok; -- cgit v1.2.3 From 9af705352d2ca149cc7c2200af82b72b4fdc4d45 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 10 Sep 2022 10:19:03 +0100 Subject: Uppity: clear the right KEXINIT packet at kex startup! Just spotted this in eyeball review: we're about to construct our new outgoing KEXINIT and write it into the strbuf s->outgoing_kexinit. So we should clear that strbuf first. But in fact we were clearing s->client_kexinit, which aliases s->outgoing_kexinit in an SSH client, but in a server, aliases s->incoming_kexinit. This was harmless in PuTTY (since the strbuf we cleared was the right one anyway). And it was harmless in Uppity's initial kex (since the strbuf we _meant_ to clear was empty anyway). But if Uppity had ever initiated a rekey, this would have exploded messily. --- ssh/transport2.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ssh/transport2.c b/ssh/transport2.c index 62ad6ffd..ce6318aa 100644 --- a/ssh/transport2.c +++ b/ssh/transport2.c @@ -1375,7 +1375,7 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl) * Construct our KEXINIT packet, in a strbuf so we can refer to it * later. */ - strbuf_clear(s->client_kexinit); + strbuf_clear(s->outgoing_kexinit); put_byte(s->outgoing_kexinit, SSH2_MSG_KEXINIT); random_read(strbuf_append(s->outgoing_kexinit, 16), 16); ssh2_write_kexinit_lists( -- cgit v1.2.3 From 08584cdb853d1392f9e5ade0f0c75effd81e58bb Mon Sep 17 00:00:00 2001 From: Jacob Nevins Date: Sat, 10 Sep 2022 20:46:14 +0100 Subject: docs: Reference GSSAPI pane from GSSAPI-kex. --- doc/config.but | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/config.but b/doc/config.but index 786b4c5c..d98a81c2 100644 --- a/doc/config.but +++ b/doc/config.but @@ -2400,7 +2400,8 @@ support GSSAPI in the SSH user authentication phase. This will still let you log in using your Kerberos credentials, but will only allow you to delegate the credentials that are active at the beginning of the session; they can't be refreshed automatically later, in a -long-running session. +long-running session. See \k{config-ssh-auth-gssapi} for how to +control GSSAPI user authentication in PuTTY. Another effect of GSSAPI key exchange is that it replaces the usual SSH mechanism of permanent host keys described in \k{gs-hostkey}. -- cgit v1.2.3 From 75ebbb3bc06471a49413e5fa67eb4c893440dbda Mon Sep 17 00:00:00 2001 From: Jacob Nevins Date: Sat, 10 Sep 2022 18:54:35 +0100 Subject: docs: GSS kex preferences aren't configurable. --- doc/config.but | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/config.but b/doc/config.but index d98a81c2..c8fbcf46 100644 --- a/doc/config.but +++ b/doc/config.but @@ -2386,6 +2386,8 @@ when using Kerberos V5, and not other GSSAPI mechanisms. If the user running PuTTY has current Kerberos V5 credentials, then PuTTY will select the GSSAPI key exchange methods in preference to any of the ordinary SSH key exchange methods configured in the preference list. +(PuTTY's preference order for GSSAPI-authenticated key exchange +methods is fixed, not controlled by the preference list.) The advantage of doing GSSAPI authentication as part of the SSH key exchange is apparent when you are using credential delegation (see -- cgit v1.2.3 From 0ef56759b8dd51d55ae3a3f94c85496783c30f52 Mon Sep 17 00:00:00 2001 From: Jacob Nevins Date: Sat, 10 Sep 2022 20:47:16 +0100 Subject: docs: Document the new ECDH/DH kex methods. And provide more detail on what kex methods actually involve, notably the hashes. --- doc/config.but | 47 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/doc/config.but b/doc/config.but index c8fbcf46..0be4a227 100644 --- a/doc/config.but +++ b/doc/config.but @@ -2347,24 +2347,45 @@ cipher selection (see \k{config-ssh-encryption}). PuTTY currently supports the following key exchange methods: -\b \q{ECDH}: \i{elliptic curve} \i{Diffie-Hellman key exchange}. +\b \q{ECDH}: \i{elliptic curve} \i{Diffie-Hellman key exchange}, +with a variety of standard curves and hash algorithms. -\b \q{Group 14}: Diffie-Hellman key exchange with a well-known -2048-bit group. +\b \q{Diffie-Hellman} key exchange with a variety of well-known groups +and hashes: -\b \q{Group 1}: Diffie-Hellman key exchange with a well-known -1024-bit group. We no longer recommend using this method, and it's -not used by default in new installations; however, it may be the -only method supported by very old server software. +\lcont{ +\b \q{Group 18}, a well-known 8192-bit group, used with the SHA-512 +hash function. + +\b \q{Group 17}, a well-known 6144-bit group, used with the SHA-512 +hash function. + +\b \q{Group 16}, a well-known 4096-bit group, used with the SHA-512 +hash function. + +\b \q{Group 15}, a well-known 3072-bit group, used with the SHA-512 +hash function. + +\b \q{Group 14}: a well-known 2048-bit group, used with the SHA-256 +hash function or, if the server doesn't support that, SHA-1. + +\b \q{Group 1}: a well-known 1024-bit group, used with the SHA-1 +hash function. Neither we nor current SSH standards recommend using +this method any longer, and it's not used by default in new +installations; however, it may be the only method supported by very +old 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 instead of the -well-known groups, if possible. +configuration. This key exchange method uses the SHA-256 hash or, +if the server doesn't support that, SHA-1. \#{FIXME: still true?:} +We recommend use of this method instead of the well-known groups, +if possible. -\b \q{\i{RSA key exchange}}: this requires much less computational +\b \q{\i{RSA-based key exchange}}: this 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. @@ -2386,8 +2407,10 @@ when using Kerberos V5, and not other GSSAPI mechanisms. If the user running PuTTY has current Kerberos V5 credentials, then PuTTY will select the GSSAPI key exchange methods in preference to any of the ordinary SSH key exchange methods configured in the preference list. -(PuTTY's preference order for GSSAPI-authenticated key exchange -methods is fixed, not controlled by the preference list.) +There's a GSSAPI-based equivalent to most of the ordinary methods +listed in \k{config-ssh-kex-order}; server support determines which +one will be used. (PuTTY's preference order for GSSAPI-authenticated +key exchange methods is fixed, not controlled by the preference list.) The advantage of doing GSSAPI authentication as part of the SSH key exchange is apparent when you are using credential delegation (see -- cgit v1.2.3 From 1489528a1fecbcd9b308d3df0fa02028bcd89595 Mon Sep 17 00:00:00 2001 From: Jacob Nevins Date: Sat, 10 Sep 2022 20:55:00 +0100 Subject: docs: Mention NTRU-Prime/Curve25519 kex. --- doc/config.but | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/doc/config.but b/doc/config.but index 0be4a227..1f9b5937 100644 --- a/doc/config.but +++ b/doc/config.but @@ -2347,6 +2347,13 @@ cipher selection (see \k{config-ssh-encryption}). PuTTY currently supports the following key exchange methods: +\b \q{NTRU Prime / Curve25519 hybrid}: NTRU Prime is a lattice-based +algorithm intended to resist quantum attacks. In this key exchange +method, it is run in parallel with a conventional Curve25519-based +method (one of those included in \q{ECDH}), in such a way that it +should be no \e{less} secure than that commonly-used method, and +hopefully also resistant to a new class of attacks. + \b \q{ECDH}: \i{elliptic curve} \i{Diffie-Hellman key exchange}, with a variety of standard curves and hash algorithms. -- cgit v1.2.3 From f8165649a1d2cef637853bfdfc8966912fbc77cf Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 11 Sep 2022 14:49:16 +0100 Subject: 32-bit Windows x86: reinstate subsystem version of 5.01. This went missing in the migration to CMake, and broke compatibility of the standard 32-bit builds with Windows XP. (Of course, the 'buildold' versions should still have run.) There doesn't seem to be a convenient CMake option to configure it cleanly, so I had to do a bodgy string-replace on the variable containing the linker flags, which I found by source-diving in CMake. That's fragile enough that I've also put in a check after the fact, so that we'll find out if it ever stops working. --- Buildscr | 12 +++++++++++- cmake/platforms/windows.cmake | 9 +++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/Buildscr b/Buildscr index 76cc7320..a2e23d9d 100644 --- a/Buildscr +++ b/Buildscr @@ -172,11 +172,21 @@ mkdir putty/windows/abuild64 # # For the 32-bit ones, we set a subsystem version of 5.01, which # allows the resulting files to still run on Windows XP. -in putty/windows/build32 with cmake_at_least_3.20 do cmake ../.. -DCMAKE_TOOLCHAIN_FILE=$(cmake_toolchain_clangcl32) -DCMAKE_BUILD_TYPE=Release -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DPUTTY_LINK_MAPS=ON -DCMAKE_C_FLAGS_RELEASE="/MT /O2" -DSTRICT=ON +in putty/windows/build32 with cmake_at_least_3.20 do cmake ../.. -DCMAKE_TOOLCHAIN_FILE=$(cmake_toolchain_clangcl32) -DCMAKE_BUILD_TYPE=Release -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DPUTTY_LINK_MAPS=ON -DCMAKE_C_FLAGS_RELEASE="/MT /O2" -DPUTTY_SUBSYSTEM_VERSION=5.01 -DSTRICT=ON in putty/windows/build32 with cmake_at_least_3.20 do make -j$(nproc) VERBOSE=1 in putty/windows/build64 with cmake_at_least_3.20 do cmake ../.. -DCMAKE_TOOLCHAIN_FILE=$(cmake_toolchain_clangcl64) -DCMAKE_BUILD_TYPE=Release -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DPUTTY_LINK_MAPS=ON -DCMAKE_C_FLAGS_RELEASE="/MT /O2" -DSTRICT=ON in putty/windows/build64 with cmake_at_least_3.20 do make -j$(nproc) VERBOSE=1 +# The cmake mechanism used to set the subsystem version is a bit of a +# bodge (it depends on knowing how cmake set up all its build command +# variables), so just in case it breaks in future, double-check we +# really did get the subsystem version we wanted. +in putty/windows/build32 do objdump -x putty.exe > exe-headers.txt +in putty/windows/build32 do grep -Ex 'MajorOSystemVersion[[:space:]]+5' exe-headers.txt +in putty/windows/build32 do grep -Ex 'MinorOSystemVersion[[:space:]]+1' exe-headers.txt +in putty/windows/build32 do grep -Ex 'MajorSubsystemVersion[[:space:]]+5' exe-headers.txt +in putty/windows/build32 do grep -Ex 'MinorSubsystemVersion[[:space:]]+1' exe-headers.txt + # Build experimental Arm Windows binaries. in putty/windows/abuild32 with cmake_at_least_3.20 do cmake ../.. -DCMAKE_TOOLCHAIN_FILE=$(cmake_toolchain_clangcl_a32) -DCMAKE_BUILD_TYPE=Release -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DPUTTY_LINK_MAPS=ON -DCMAKE_C_FLAGS_RELEASE="/MT /O2" -DSTRICT=ON in putty/windows/abuild32 with cmake_at_least_3.20 do make -j$(nproc) VERBOSE=1 diff --git a/cmake/platforms/windows.cmake b/cmake/platforms/windows.cmake index c4299b2c..a7ed7c7c 100644 --- a/cmake/platforms/windows.cmake +++ b/cmake/platforms/windows.cmake @@ -7,6 +7,15 @@ set(PUTTY_LINK_MAPS OFF set(PUTTY_EMBEDDED_CHM_FILE "" CACHE FILEPATH "Path to a .chm help file to embed in the binaries") +if(PUTTY_SUBSYSTEM_VERSION) + string(REPLACE + "subsystem:windows" "subsystem:windows,${PUTTY_SUBSYSTEM_VERSION}" + CMAKE_C_CREATE_WIN32_EXE ${CMAKE_C_CREATE_WIN32_EXE}) + string(REPLACE + "subsystem:console" "subsystem:console,${PUTTY_SUBSYSTEM_VERSION}" + CMAKE_C_CREATE_CONSOLE_EXE ${CMAKE_C_CREATE_CONSOLE_EXE}) +endif() + function(define_negation newvar oldvar) if(${oldvar}) set(${newvar} OFF PARENT_SCOPE) -- cgit v1.2.3 From 25ef6a233aa1291134987500a23323aa35f44e70 Mon Sep 17 00:00:00 2001 From: Jacob Nevins Date: Sun, 11 Sep 2022 22:17:46 +0100 Subject: Remove a stray FIXME, added in 840043f06e. Simon tells me he was pondering whether chacha20-poly1305 could be reworked to use the new facilities, but on reflection there's no way to use it to improve matters. --- crypto/chacha20-poly1305.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crypto/chacha20-poly1305.c b/crypto/chacha20-poly1305.c index d765e7fc..4216a64d 100644 --- a/crypto/chacha20-poly1305.c +++ b/crypto/chacha20-poly1305.c @@ -1062,7 +1062,7 @@ const ssh_cipheralg ssh2_chacha20_poly1305 = { .decrypt = ccp_decrypt, .encrypt_length = ccp_encrypt_length, .decrypt_length = ccp_decrypt_length, - .next_message = nullcipher_next_message, // FIXME: can we use this? + .next_message = nullcipher_next_message, .ssh2_id = "chacha20-poly1305@openssh.com", .blksize = 1, .real_keybits = 512, -- cgit v1.2.3 From 3f3f1987aa0fae78de76b5276bff3ed018e8ffb7 Mon Sep 17 00:00:00 2001 From: Jacob Nevins Date: Sun, 11 Sep 2022 22:37:47 +0100 Subject: docs: Stop recommending DH gex over fixed groups. With the new larger fixed-group methods, it's less clearly always the right answer. (Really it seems more sensible to use ECDH over any of the integer DH, these days.) Also, reword other kex descriptions a bit. --- doc/config.but | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/doc/config.but b/doc/config.but index 1f9b5937..bd8147ae 100644 --- a/doc/config.but +++ b/doc/config.but @@ -2357,8 +2357,8 @@ hopefully also resistant to a new class of attacks. \b \q{ECDH}: \i{elliptic curve} \i{Diffie-Hellman key exchange}, with a variety of standard curves and hash algorithms. -\b \q{Diffie-Hellman} key exchange with a variety of well-known groups -and hashes: +\b The original form of \q{Diffie-Hellman} key exchange, with a +variety of well-known groups and hashes: \lcont{ \b \q{Group 18}, a well-known 8192-bit group, used with the SHA-512 @@ -2383,14 +2383,13 @@ installations; however, it may be the only method supported by very old 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. This key exchange method uses the SHA-256 hash or, -if the server doesn't support that, SHA-1. \#{FIXME: still true?:} -We recommend use of this method instead of the well-known groups, -if possible. +\b \q{Diffie-Hellman \i{group exchange}}: with this method, instead +of using a fixed group, PuTTY requests that the server suggest a group +to use for a subsequent Diffie-Hellman 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. This key +exchange method uses the SHA-256 hash or, if the server doesn't +support that, SHA-1. \b \q{\i{RSA-based key exchange}}: this requires much less computational effort on the part of the client, and somewhat less on the part of -- cgit v1.2.3 From 5fdfe5ac8364f386f35b85d3adc4963ed00a6100 Mon Sep 17 00:00:00 2001 From: Jacob Nevins Date: Sun, 11 Sep 2022 23:59:12 +0100 Subject: Standardise RFC URLs in docs and comments. (Plus one internet-draft URL.) --- crypto/ecc-ssh.c | 2 +- doc/authplugin.but | 6 +++--- doc/config.but | 2 +- doc/pubkeyfmt.but | 4 ++-- doc/sshnames.but | 2 +- ssh/kex2-client.c | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/crypto/ecc-ssh.c b/crypto/ecc-ssh.c index 26a04815..d3197866 100644 --- a/crypto/ecc-ssh.c +++ b/crypto/ecc-ssh.c @@ -6,7 +6,7 @@ * References: * * Elliptic curves in SSH are specified in RFC 5656: - * http://tools.ietf.org/html/rfc5656 + * https://www.rfc-editor.org/rfc/rfc5656 * * That specification delegates details of public key formatting and a * lot of underlying mechanism to SEC 1: diff --git a/doc/authplugin.but b/doc/authplugin.but index 5ea4255f..41bc0daa 100644 --- a/doc/authplugin.but +++ b/doc/authplugin.but @@ -500,14 +500,14 @@ cases) after \cw{PLUGIN_PROTOCOL_ACCEPT} \H{authplugin-refs} References -\B{authplugin-ref-arch} \W{https://datatracker.ietf.org/doc/html/rfc4251}{RFC 4251}, \q{The Secure Shell (SSH) Protocol +\B{authplugin-ref-arch} \W{https://www.rfc-editor.org/rfc/rfc4251}{RFC 4251}, \q{The Secure Shell (SSH) Protocol Architecture}. -\B{authplugin-ref-userauth} \W{https://datatracker.ietf.org/doc/html/rfc4252}{RFC +\B{authplugin-ref-userauth} \W{https://www.rfc-editor.org/rfc/rfc4252}{RFC 4252}, \q{The Secure Shell (SSH) Authentication Protocol}. \B{authplugin-ref-ki} -\W{https://datatracker.ietf.org/doc/html/rfc4256}{RFC 4256}, +\W{https://www.rfc-editor.org/rfc/rfc4256}{RFC 4256}, \q{Generic Message Exchange Authentication for the Secure Shell Protocol (SSH)} (better known by its wire id \q{keyboard-interactive}). diff --git a/doc/config.but b/doc/config.but index bd8147ae..540d6a93 100644 --- a/doc/config.but +++ b/doc/config.but @@ -1950,7 +1950,7 @@ connection. \b Selecting \I{HTTP proxy}\q{HTTP CONNECT} 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}. +as documented in \W{https://www.rfc-editor.org/rfc/rfc2817}{RFC 2817}. \b Selecting \q{SOCKS 4} or \q{SOCKS 5} allows you to proxy your connections through a \i{SOCKS server}. diff --git a/doc/pubkeyfmt.but b/doc/pubkeyfmt.but index 78da4885..51954c53 100644 --- a/doc/pubkeyfmt.but +++ b/doc/pubkeyfmt.but @@ -7,7 +7,7 @@ In this appendix, binary data structures are described using data type representations such as \cq{uint32}, \cq{string} and \cq{mpint} as used in the SSH protocol standards themselves. These are defined authoritatively by -\W{https://tools.ietf.org/html/rfc4251#section-5}{RFC 4251 section 5}, +\W{https://www.rfc-editor.org/rfc/rfc4251#section-5}{RFC 4251 section 5}, \q{Data Type Representations Used in the SSH Protocols}. \H{ppk-overview} Overview @@ -86,7 +86,7 @@ can contain any byte values other than 13 and 10 (CR and LF). The next part of the file gives the public key. This is stored unencrypted but base64-encoded -(\W{https://tools.ietf.org/html/rfc4648}{RFC 4648}), and is preceded +(\W{https://www.rfc-editor.org/rfc/rfc4648}{RFC 4648}), and is preceded by a header line saying how many lines of base64 data are shown, looking like this: diff --git a/doc/sshnames.but b/doc/sshnames.but index 6cf82f73..a00ac678 100644 --- a/doc/sshnames.but +++ b/doc/sshnames.but @@ -70,7 +70,7 @@ They have been superseded by \cw{arcfour128} and \cw{arcfour256}. The SSH agent protocol, which is only specified in an Internet-Draft at the time of writing -(\W{https://tools.ietf.org/html/draft-miller-ssh-agent}\cw{draft-miller-ssh-agent}), +(\W{https://datatracker.ietf.org/doc/html/draft-miller-ssh-agent}\cw{draft-miller-ssh-agent}), defines an extension mechanism. These names can be sent in an \cw{SSH_AGENTC_EXTENSION} message. diff --git a/ssh/kex2-client.c b/ssh/kex2-client.c index ca9fa505..b890c023 100644 --- a/ssh/kex2-client.c +++ b/ssh/kex2-client.c @@ -721,7 +721,7 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) * If this the first KEX, save the GSS context for "gssapi-keyex" * authentication. * - * http://tools.ietf.org/html/rfc4462#section-4 + * https://www.rfc-editor.org/rfc/rfc4462#section-4 * * This method may be used only if the initial key exchange was * performed using a GSS-API-based key exchange method defined in -- cgit v1.2.3 From bbd46afd91b324a83d6c4ccaa9de32842a678ba9 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 12 Sep 2022 07:50:56 +0100 Subject: opensshcert_components: switch expiry times to UTC. Jacob points out that the output of 'puttygen --dump', where the key_components are used, is much more likely to need to be machine- than human-readable, and so it makes more sense to use a date/time format that's invariant under external changes such as locale. (He also points out that Windows's time zone description strings are overly verbose!) --- crypto/openssh-certs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crypto/openssh-certs.c b/crypto/openssh-certs.c index 01bc5cc6..cf0c2af3 100644 --- a/crypto/openssh-certs.c +++ b/crypto/openssh-certs.c @@ -583,7 +583,7 @@ static void opensshcert_time_to_iso8601(BinarySink *bs, uint64_t time) time_t t = time; char buf[256]; put_data(bs, buf, strftime(buf, sizeof(buf), - "%a %Y-%m-%d %H:%M:%S %Z", localtime(&t))); + "%Y-%m-%d %H:%M:%S UTC", gmtime(&t))); } static void opensshcert_string_list_key_components( -- cgit v1.2.3 From 258a36be315436d050d1096d97ed4b668ece4593 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 12 Sep 2022 09:11:37 +0100 Subject: Change priority of new Diffie-Hellman groups. In the initial commit 031d86ed5ba4dd4 that introduced them, I accidentally put them below the 'warn about insecurity' line, which I didn't mean to. Moved them up to just above the existing group14. Also, I've arranged them in a slightly weird order, so that the most preferred group of this collection is the medium-sized group16, followed by the larger ones (17 and 18) and then the smaller 15. Rationale: larger is better _until_ it starts costing way too much CPU time, and group18 can grind quite painfully on a slow machine. (And of course users are free to reconfigure if they have different preferences.) This isn't really ideal, of course. The idea that you might not want to use group18 *because it's slow* contradicts the basic concept of PuTTY's current crypto-preferences UI, which assumes that you rank things by security, which is why there's a dividing line below which things are assumed insecure. I hope that in a future release we'll rework the UI so that you can express more subtle ideas of what crypto you do and don't like. But this will do for the moment. The GSS versions of the same DH methods are reordered similarly. --- crypto/diffie-hellman.c | 4 ++-- settings.c | 13 +++++++++---- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/crypto/diffie-hellman.c b/crypto/diffie-hellman.c index bf262f2d..4da2d471 100644 --- a/crypto/diffie-hellman.c +++ b/crypto/diffie-hellman.c @@ -272,9 +272,9 @@ static const ssh_kex ssh_gssk5_diffiehellman_group14_sha256 = { }; static const ssh_kex *const gssk5_sha2_kex_list[] = { - &ssh_gssk5_diffiehellman_group18_sha512, - &ssh_gssk5_diffiehellman_group17_sha512, &ssh_gssk5_diffiehellman_group16_sha512, + &ssh_gssk5_diffiehellman_group17_sha512, + &ssh_gssk5_diffiehellman_group18_sha512, &ssh_gssk5_diffiehellman_group15_sha512, &ssh_gssk5_diffiehellman_group14_sha256, }; diff --git a/settings.c b/settings.c index 44ec1978..cd286eb4 100644 --- a/settings.c +++ b/settings.c @@ -33,15 +33,20 @@ static const struct keyvalwhere kexnames[] = { { "ecdh", KEX_ECDH, -1, +1 }, /* This name is misleading: it covers both SHA-256 and SHA-1 variants */ { "dh-gex-sha1", KEX_DHGEX, -1, -1 }, - { "dh-group18-sha512", KEX_DHGROUP18, -1, -1 }, - { "dh-group17-sha512", KEX_DHGROUP17, -1, -1 }, - { "dh-group16-sha512", KEX_DHGROUP16, -1, -1 }, - { "dh-group15-sha512", KEX_DHGROUP15, -1, -1 }, /* Again, this covers both SHA-256 and SHA-1, despite the name: */ { "dh-group14-sha1", KEX_DHGROUP14, -1, -1 }, /* This one really is only SHA-1, though: */ { "dh-group1-sha1", KEX_DHGROUP1, KEX_WARN, +1 }, { "rsa", KEX_RSA, KEX_WARN, -1 }, + /* Larger fixed DH groups: prefer the larger 15 and 16 over 14, + * but by default the even larger 17 and 18 go below 16. + * Rationale: diminishing returns of improving the DH strength are + * outweighed by increased CPU cost. Group 18 is painful on a slow + * machine. Users can override if they need to. */ + { "dh-group15-sha512", KEX_DHGROUP15, KEX_DHGROUP14, -1 }, + { "dh-group16-sha512", KEX_DHGROUP16, KEX_DHGROUP15, -1 }, + { "dh-group17-sha512", KEX_DHGROUP17, KEX_DHGROUP16, +1 }, + { "dh-group18-sha512", KEX_DHGROUP18, KEX_DHGROUP17, +1 }, { "WARN", KEX_WARN, -1, -1 } }; -- cgit v1.2.3 From 49aa6c2b0899e322ca8acebd2a51b6a3f43028df Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 12 Sep 2022 09:18:37 +0100 Subject: Remove FTP from release machinery. We withdrew our FTP download links in July, when chiark's OS upgrade made its previous ftpd go away. We've had no complaints at all about that, so I think it's time to decide that FTP is officially obsolete, and remove it from the script that does the uploads, and from the release checklist. --- CHECKLST.txt | 3 --- release.pl | 24 ++++-------------------- 2 files changed, 4 insertions(+), 23 deletions(-) diff --git a/CHECKLST.txt b/CHECKLST.txt index f0154948..a0611bec 100644 --- a/CHECKLST.txt +++ b/CHECKLST.txt @@ -184,12 +184,9 @@ locally, this is the procedure for putting it up on the web. - Check that downloads via version-numbered URLs all work: ../putty/release.pl --version=X.YZ --precheck - If this has trouble accessing chiark's ftp server, that is - unfortunately normal; add --no-ftp and try again. - Switch the 'latest' links over to the new release: * Update the HTTP redirect at the:www/putty/htaccess . - * Update the FTP symlink at chiark:ftp/putty-latest . - Now verify that downloads via the 'latest' URLs are all redirected correctly and work: diff --git a/release.pl b/release.pl index 122cc027..f9507a13 100755 --- a/release.pl +++ b/release.pl @@ -15,13 +15,11 @@ my $setver = 0; my $upload = 0; my $precheck = 0; my $postcheck = 0; -my $skip_ftp = 0; GetOptions("version=s" => \$version, "setver" => \$setver, "upload" => \$upload, "precheck" => \$precheck, - "postcheck" => \$postcheck, - "no-ftp" => \$skip_ftp) + "postcheck" => \$postcheck) or &usage(); # --setver: construct a local commit which updates the version @@ -76,8 +74,7 @@ if ($upload) { or die "could not upload link maps"; for my $location (["thyestes", "www/putty/$version"], - ["the", "www/putty/$version"], - ["chiark", "ftp/putty-$version"]) { + ["the", "www/putty/$version"]) { my ($host, $path) = @$location; 0 == system("rsync", "-av", "putty/", "$host:$path") or die "could not upload release to $host"; @@ -109,7 +106,7 @@ if ($upload) { } # --precheck and --postcheck: attempt to download the release from its -# various web and FTP locations. +# various locations. if ($precheck || $postcheck) { defined $version or die "use --version"; @@ -118,7 +115,6 @@ if ($precheck || $postcheck) { -d "putty" or die "no putty directory in cwd"; my $httpprefix = "https://the.earth.li/~sgtatham/putty/"; - my $ftpprefix = "ftp://ftp.chiark.greenend.org.uk/users/sgtatham/putty-"; # Go through all the files in build.out. find({ wanted => sub @@ -140,35 +136,23 @@ if ($precheck || $postcheck) { my $http_numbered = "${httpprefix}$version/$path"; my $http_latest = "${httpprefix}latest/$path"; - my $ftp_numbered = "${ftpprefix}$version/$path"; - my $ftp_latest = "${ftpprefix}latest/$path"; - my ($http_uri, $ftp_uri); + my $http_uri; if ($precheck) { # Before the 'latest' links/redirects update, # we just download from explicitly version- # numbered URLs. $http_uri = $http_numbered; - $ftp_uri = $ftp_numbered; } if ($postcheck) { # After 'latest' is updated, we're testing that # the redirects work, so we download from the # URLs with 'latest' in them. $http_uri = $http_latest; - $ftp_uri = $ftp_latest; } # Now test-download the files themselves. - unless ($skip_ftp) { - my $ftpdata = `curl -s $ftp_uri`; - printf " got %d bytes via FTP", length $ftpdata; - die "FTP download for $ftp_uri did not match" - if $ftpdata ne $real_content; - print ", ok\n"; - } - my $ua = LWP::UserAgent->new; my $httpresponse = $ua->get($http_uri); my $httpdata = $httpresponse->{_content}; -- cgit v1.2.3 From b8473f0c11e3356cdf84d38ee5edf2062fc32824 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Tue, 13 Sep 2022 11:11:59 +0100 Subject: Windows: remove static variables in wintw_request_resize. Those have been there since around 2001. They're in a piece of code that calls get_fullscreen_rect to find the overall screen size, and then prevents attempts to resize the window larger than that. The static variables were arranging that we don't have to call get_fullscreen_rect more than once. But, firstly, computers are faster 20 years on; secondly, remote window-resize requests are intentionally rate-limited (as of commit d74308e90e3813a), so this shouldn't be the limiting factor anyway; and thirdly, multi-monitor support has appeared since then, which means that if the window has been dragged from one monitor to another then get_fullscreen_rect might _legitimately_ return a different bounding rectangle when called a second time. So we should just do the full check every time unconditionally. (cherry picked from commit 4b3a8cbf61ce4ee19227784ba27c52a9e47774fb) --- windows/window.c | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/windows/window.c b/windows/window.c index 059a123c..6ed581cd 100644 --- a/windows/window.c +++ b/windows/window.c @@ -1692,20 +1692,9 @@ static void wintw_request_resize(TermWin *tw, int w, int h) /* 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 */ + RECT ss; + if (get_fullscreen_rect(&ss)) { + /* Make sure the values aren't too big */ width = (ss.right - ss.left - extra_width) / 4; height = (ss.bottom - ss.top - extra_height) / 6; -- cgit v1.2.3 From 3037244132fc1799a31ef42cbd02a9c3c8a2077c Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Tue, 13 Sep 2022 12:30:42 +0100 Subject: wintw_request_resize: add missing NACKs. In cases where we refuse a resize request, either because it's too large or because the window is not currently resizable due to being maximised, we were failing to communicate that back to the Terminal so that it could stop waiting for the resize and resume processing input. --- windows/window.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/windows/window.c b/windows/window.c index 6ed581cd..6a17d54b 100644 --- a/windows/window.c +++ b/windows/window.c @@ -1680,8 +1680,10 @@ static void wintw_request_resize(TermWin *tw, int w, int h) /* If the window is maximized suppress resizing attempts */ if (IsZoomed(wgs.term_hwnd)) { - if (conf_get_int(conf, CONF_resize_action) == RESIZE_TERM) + if (conf_get_int(conf, CONF_resize_action) == RESIZE_TERM) { + term_resize_request_completed(term); return; + } } if (conf_get_int(conf, CONF_resize_action) == RESIZE_DISABLED) return; @@ -1698,8 +1700,10 @@ static void wintw_request_resize(TermWin *tw, int w, int h) width = (ss.right - ss.left - extra_width) / 4; height = (ss.bottom - ss.top - extra_height) / 6; - if (w > width || h > height) + if (w > width || h > height) { + term_resize_request_completed(term); return; + } if (w < 15) w = 15; if (h < 1) -- cgit v1.2.3 From 2fbc122e0e3c1528c15f7064b01a61701f1d4da9 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 12 Sep 2022 12:55:47 +0100 Subject: windows/window.c: make random_save_seed call unconditional. The conditionalisation of that call on 'protocol == PROT_SSH' has been around since the beginning of our git history. But in those days, random_save_seed() was unconditional _internally_ - it would always create and write to the seed file regardless of whether the random pool had even been initialised, let alone used. Now random_save_seed() has its own internal condition which prevents it doing anything if the random subsystem was never started up in the first place. So it's better to call it unconditionally from cleanup_exit, and then it'll be able to do its thing whenever needed, without having to second-guess based on the top-level protocol. (In fact, that's what all the other implementations of cleanup_exit() have done all along. On Unix, and in Windows console apps, we do call random_save_seed() unconditionally, and expect it to uncomplainingly do nothing if there's nothing to do.) (cherry picked from commit 260aad5fcac562702a799d945d48ad8b0e26d29c) --- windows/window.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/windows/window.c b/windows/window.c index 6a17d54b..70548a5d 100644 --- a/windows/window.c +++ b/windows/window.c @@ -1018,9 +1018,7 @@ void cleanup_exit(int code) DeleteObject(pal); sk_cleanup(); - if (conf_get_int(conf, CONF_protocol) == PROT_SSH) { - random_save_seed(); - } + random_save_seed(); shutdown_help(); /* Clean up COM. */ -- cgit v1.2.3 From c1a4eda9f6ec5301eeb83def1ecb453f659fa7d3 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Tue, 13 Sep 2022 20:53:03 +0100 Subject: GSSAPI kex: don't call dh_is_gex() on ECDH algorithms. dh_is_gex() expects to find a 'struct dh_extra' in the 'extra' field of the kex_alg you pass in, and won't look kindly on finding an instance of some totally different structure type. We were being careful about that everywhere in the GSSAPI kex code except for the final free step. --- ssh/kex2-client.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ssh/kex2-client.c b/ssh/kex2-client.c index b890c023..26159bb5 100644 --- a/ssh/kex2-client.c +++ b/ssh/kex2-client.c @@ -559,10 +559,10 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) dh_cleanup(s->dh_ctx); s->dh_ctx = NULL; mp_free(s->f); s->f = NULL; - } - if (dh_is_gex(s->kex_alg)) { - mp_free(s->g); s->g = NULL; - mp_free(s->p); s->p = NULL; + if (dh_is_gex(s->kex_alg)) { + mp_free(s->g); s->g = NULL; + mp_free(s->p); s->p = NULL; + } } #endif } else { -- cgit v1.2.3 From e1b73f0d54b878ee6c02d2eb76028732a8510dc3 Mon Sep 17 00:00:00 2001 From: Jacob Nevins Date: Tue, 13 Sep 2022 23:51:26 +0100 Subject: New FAQ entry about the Microsoft Store. --- doc/faq.but | 16 ++++++++++++++++ doc/index.but | 3 +++ 2 files changed, 19 insertions(+) diff --git a/doc/faq.but b/doc/faq.but index f3642fd5..9a8deea2 100644 --- a/doc/faq.but +++ b/doc/faq.but @@ -1152,6 +1152,22 @@ 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. +\S{faq-windowsstore}{Question} Is the version of PuTTY in the +\i{Microsoft Store} legit? + +The free-of-charge \q{PuTTY} application at +\W{https://apps.microsoft.com/store/detail/putty/XPFNZKSKLBP7RJ}{this link} +is published and maintained by us. The copy there is the latest +release, usually updated within a few days of us publishing it on our +own website. + +There have been other copies of PuTTY on the store, some looking quite +similar, and some charging money. Those were uploaded by other people, +and we can't guarantee anything about them. + +The first version we published to the Microsoft Store was 0.76 (some +time after its initial release on our website). + \H{faq-admin} Administrative questions \S{faq-putty-org}{Question} Is \cw{putty.org} your website? diff --git a/doc/index.but b/doc/index.but index cc043d5a..28303ee2 100644 --- a/doc/index.but +++ b/doc/index.but @@ -947,3 +947,6 @@ saved sessions from \IM{certificate}{certificates} SSH certificates \IM{certificate}{certificates} OpenSSH certificates \IM{certificate}{certificates} CA (certification authority) + +\IM{Microsoft Store} Microsoft Store +\IM{Microsoft Store} Windows Store -- cgit v1.2.3 From b0a61849efb3cbf0f1c0fead0f422341a969458c Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 17 Sep 2022 07:53:43 +0100 Subject: Unix GSSAPI: support krb5-config as well as pkg-config. On FreeBSD, I'm told, you can't configure Kerberos via pkg-config. So we need a fallback. Here's some manual code to run krb5-config and pick apart the result, similar to what I already did with gtk-config for our (still not dead!) GTK 1 support. --- cmake/platforms/unix.cmake | 63 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/cmake/platforms/unix.cmake b/cmake/platforms/unix.cmake index 291d1e64..95339f22 100644 --- a/cmake/platforms/unix.cmake +++ b/cmake/platforms/unix.cmake @@ -108,16 +108,77 @@ if(PUTTY_GSSAPI STREQUAL DYNAMIC) endif() if(PUTTY_GSSAPI STREQUAL STATIC) + set(KRB5_CFLAGS) + set(KRB5_LDFLAGS) + + # First try using pkg-config find_package(PkgConfig) pkg_check_modules(KRB5 krb5-gssapi) + + # Failing that, try the dedicated krb5-config + if(NOT KRB5_FOUND) + find_program(KRB5_CONFIG krb5-config) + if(KRB5_CONFIG) + execute_process(COMMAND ${KRB5_CONFIG} --cflags gssapi + OUTPUT_VARIABLE krb5_config_cflags + OUTPUT_STRIP_TRAILING_WHITESPACE + RESULT_VARIABLE krb5_config_cflags_result) + execute_process(COMMAND ${KRB5_CONFIG} --libs gssapi + OUTPUT_VARIABLE krb5_config_libs + OUTPUT_STRIP_TRAILING_WHITESPACE + RESULT_VARIABLE krb5_config_libs_result) + + if(krb5_config_cflags_result EQUAL 0 AND krb5_config_libs_result EQUAL 0) + set(KRB5_INCLUDE_DIRS) + set(KRB5_LIBRARY_DIRS) + set(KRB5_LIBRARIES) + + # We can safely put krb5-config's cflags directly into cmake's + # cflags, without bothering to extract the include directories. + set(KRB5_CFLAGS ${krb5_config_cflags}) + + # But krb5-config --libs isn't so simple. It will actually + # deliver a mix of libraries and other linker options. We have + # to separate them for cmake purposes, because if we pass the + # whole lot to add_link_options then they'll appear too early + # in the command line (so that by the time our own code refers + # to GSSAPI functions it'll be too late to search these + # libraries for them), and if we pass the whole lot to + # link_libraries then it'll get confused about options that + # aren't libraries. + separate_arguments(krb5_config_libs NATIVE_COMMAND + ${krb5_config_libs}) + foreach(opt ${krb5_config_libs}) + string(REGEX MATCH "^-l" ok ${opt}) + if(ok) + list(APPEND KRB5_LIBRARIES ${opt}) + continue() + endif() + string(REGEX MATCH "^-L" ok ${opt}) + if(ok) + string(REGEX REPLACE "^-L" "" optval ${opt}) + list(APPEND KRB5_LIBRARY_DIRS ${optval}) + continue() + endif() + list(APPEND KRB5_LDFLAGS ${opt}) + endforeach() + + message(STATUS "Found Kerberos via krb5-config") + set(KRB5_FOUND YES) + endif() + endif() + endif() + if(KRB5_FOUND) include_directories(${KRB5_INCLUDE_DIRS}) link_directories(${KRB5_LIBRARY_DIRS}) link_libraries(${KRB5_LIBRARIES}) + add_compile_options(${KRB5_CFLAGS}) + add_link_options(${KRB5_LDFLAGS}) set(STATIC_GSSAPI ON) else() message(WARNING - "Could not find krb5 via pkg-config -- \ + "Could not find krb5 via pkg-config or krb5-config -- \ cannot provide static GSSAPI support") set(NO_GSSAPI ON) endif() -- cgit v1.2.3 From 374107eb1e2ae576c10cdd538f45f18918df8c4b Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 17 Sep 2022 07:09:29 +0100 Subject: Unix static GSSAPI: fix an uninitialised structure field. When linking statically against Kerberos, the setup code in ssh_got_ssh_version() was trying to look up want_id==0 in the list of one GSSAPI library, but unfortunately, the id field of that record was not initialised at all, so if it happened to be nonzero nonsense, the loop wouldn't find a library at all and would fail an assertion. --- unix/gss.c | 1 + 1 file changed, 1 insertion(+) diff --git a/unix/gss.c b/unix/gss.c index cd9971c7..bd599fcc 100644 --- a/unix/gss.c +++ b/unix/gss.c @@ -140,6 +140,7 @@ struct ssh_gss_liblist *ssh_gss_setup(Conf *conf) list->libraries = snew(struct ssh_gss_library); list->nlibraries = 1; + list->libraries[0].id = 0; list->libraries[0].gsslogmsg = "Using statically linked GSSAPI"; #define BIND_GSS_FN(name) \ -- cgit v1.2.3 From 35a87984f67ebc2db3f670cb1431f08991853a5e Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 17 Sep 2022 07:28:46 +0100 Subject: Unix GSSAPI: support static linking against Heimdal. Heimdal provides its own definitions of OIDs like GSS_C_NT_USER_NAME in the form of macros, which conflict with our attempt to redefine them as variables - the macro gets expanded into the middle of the variable declaration, leaving the poor C compiler trying to parse a non-declaration along the lines of const_gss_OID (&__gss_c_nt_anonymous_oid_desc) = oids+5; Easily fixed by just not redefining these at all if they're already defined as macros. To make that easier, I've broken up the oids[] array into individual gss_OID_desc declarations, so I can put each one inside the appropriate ifdef. In the process, I've removed the 'const' from the gss_OID_desc declarations. That's on purpose! The problem is that not all implementations of the GSSAPI headers make const_gss_OID a pointer to a *const* gss_OID_desc; sometimes it's just a plain one and the 'const' prefix is just a comment to the user. So removing that const prevents compiler warnings (or worse) about address-taking a const thing and assigning it into a non-const pointer. --- ssh/pgssapi.c | 106 +++++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 68 insertions(+), 38 deletions(-) diff --git a/ssh/pgssapi.c b/ssh/pgssapi.c index 1f54d805..1730444d 100644 --- a/ssh/pgssapi.c +++ b/ssh/pgssapi.c @@ -9,38 +9,63 @@ #ifndef NO_LIBDL -/* Reserved static storage for GSS_oids. Comments are quotes from RFC 2744. */ -static const gss_OID_desc oids[] = { +/* Reserved static storage for GSS_oids. + * Constants of the form GSS_C_NT_* are specified by rfc 2744. + * Comments are quotes from RFC 2744 itself. + * + * These may be #defined to complex expressions by the local header + * file, if we're including one in static-GSSAPI mode. (For example, + * Heimdal defines them to things like + * (&__gss_c_nt_user_name_oid_desc).) So we only define them if + * needed. */ + +#ifndef GSS_C_NT_USER_NAME +static gss_OID_desc oid_GSS_C_NT_USER_NAME = { /* 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"}, + 10, "\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. + * to that gss_OID_desc. */ +}; +const_gss_OID GSS_C_NT_USER_NAME = &oid_GSS_C_NT_USER_NAME; +#endif - * The implementation must reserve static storage for a +#ifndef GSS_C_NT_MACHINE_UID_NAME +static gss_OID_desc oid_GSS_C_NT_MACHINE_UID_NAME = { + /* 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"}, + 10, "\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. + * initialized to point to that gss_OID_desc. */ +}; +const_gss_OID GSS_C_NT_MACHINE_UID_NAME = &oid_GSS_C_NT_MACHINE_UID_NAME; +#endif - * The implementation must reserve static storage for a +#ifndef GSS_C_NT_STRING_UID_NAME +static gss_OID_desc oid_GSS_C_NT_STRING_UID_NAME = { + /* 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"}, + 10, "\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 + * initialized to point to that gss_OID_desc. */ +}; +const_gss_OID GSS_C_NT_STRING_UID_NAME = &oid_GSS_C_NT_STRING_UID_NAME; +#endif + +#ifndef GSS_C_NT_HOSTBASED_SERVICE_X +static gss_OID_desc oid_GSS_C_NT_HOSTBASED_SERVICE_X = { + /* The implementation must reserve static storage for a * gss_OID_desc object containing the value */ - {6, (void *)"\x2b\x06\x01\x05\x06\x02"}, + 6, "\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 @@ -52,29 +77,44 @@ static const gss_OID_desc oids[] = { * 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 + * implementations */ +}; +const_gss_OID GSS_C_NT_HOSTBASED_SERVICE_X = &oid_GSS_C_NT_HOSTBASED_SERVICE_X; +#endif + +#ifndef GSS_C_NT_HOSTBASED_SERVICE +static gss_OID_desc oid_GSS_C_NT_HOSTBASED_SERVICE = { + /* 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"}, + 10, "\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 + * to point to that gss_OID_desc. */ +}; +const_gss_OID GSS_C_NT_HOSTBASED_SERVICE = &oid_GSS_C_NT_HOSTBASED_SERVICE; +#endif + +#ifndef GSS_C_NT_ANONYMOUS +static gss_OID_desc oid_GSS_C_NT_ANONYMOUS = { + /* The implementation must reserve static storage for a * gss_OID_desc object containing the value */ - {6, (void *)"\x2b\x06\01\x05\x06\x03"}, + 6, "\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 + * to that gss_OID_desc. */ +}; +const_gss_OID GSS_C_NT_ANONYMOUS = &oid_GSS_C_NT_ANONYMOUS; +#endif + +#ifndef GSS_C_NT_EXPORT_NAME +static gss_OID_desc oid_GSS_C_NT_EXPORT_NAME = { + /* The implementation must reserve static storage for a * gss_OID_desc object containing the value */ - {6, (void *)"\x2b\x06\x01\x05\x06\x04"}, + 6, "\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 @@ -82,23 +122,13 @@ static const gss_OID_desc oids[] = { * 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; +const_gss_OID GSS_C_NT_EXPORT_NAME = &oid_GSS_C_NT_EXPORT_NAME; +#endif #endif /* NO_LIBDL */ static gss_OID_desc gss_mech_krb5_desc = -{ 9, (void *)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x02" }; +{ 9, "\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; -- cgit v1.2.3 From a95e38e9b18ce69b542a9a8c0f18ea8f4c7abb3a Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 17 Sep 2022 07:50:55 +0100 Subject: GSSAPI fix: don't pass GSS_C_NO_NAME to inquire_cred_by_mech. This was pointed out by another compiler warning. The 'name' parameter of inquire_cred_by_mech is not a gss_name_t (which is the type of GSS_C_NO_NAME); it's a gss_name_t *, because it's an _output_ parameter. We're not telling the library that we aren't _passing_ a name: we're telling it that we don't need it to _return_ us a name. So the appropriate null pointer representation is just NULL. (This was harmless apart from a compiler warning, because gss_name_t is a pointer type in turn and GSS_C_NO_NAME expands to a null pointer anyway. It was just a wrongly-typed null pointer.) --- ssh/gssc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ssh/gssc.c b/ssh/gssc.c index 0224afe2..d10caf8b 100644 --- a/ssh/gssc.c +++ b/ssh/gssc.c @@ -75,7 +75,7 @@ static Ssh_gss_stat ssh_gssapi_acquire_cred(struct ssh_gss_library *lib, gssctx->maj_stat = gss->inquire_cred_by_mech(&gssctx->min_stat, cred, (gss_OID) GSS_MECH_KRB5, - GSS_C_NO_NAME, + NULL, &time_rec, NULL, NULL); -- cgit v1.2.3 From 732ec31a17a7feac9c24c427f79734e9a97b922f Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 18 Sep 2022 15:02:32 +0100 Subject: Add explicit cmake setting for 'build without GTK'. If you have GTK installed on your system but want to build without it anyway (e.g. if you're buliding a package suitable for headless systems), it's useful to be able to explicitly instruct PuTTY's build system not to use GTK even if it's there. This would already work if you unilaterally set PUTTY_GTK_VERSION to some value other than 1, 2, 3 or ANY. Added NONE as an officially supported option, and included it in the list that cmake-gui will present. Also, made the check for libX11 conditional on having GTK, since there's no need to bother with it otherwise. --- cmake/gtk.cmake | 2 +- cmake/platforms/unix.cmake | 32 +++++++++++++++++--------------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/cmake/gtk.cmake b/cmake/gtk.cmake index 85ecb7de..34396d2f 100644 --- a/cmake/gtk.cmake +++ b/cmake/gtk.cmake @@ -3,7 +3,7 @@ set(PUTTY_GTK_VERSION "ANY" CACHE STRING "Which major version of GTK to build with") set_property(CACHE PUTTY_GTK_VERSION - PROPERTY STRINGS ANY 3 2 1) + PROPERTY STRINGS ANY 3 2 1 NONE) set(GTK_FOUND FALSE) diff --git a/cmake/platforms/unix.cmake b/cmake/platforms/unix.cmake index 95339f22..04db8129 100644 --- a/cmake/platforms/unix.cmake +++ b/cmake/platforms/unix.cmake @@ -65,21 +65,23 @@ endif() include(cmake/gtk.cmake) -# See if we have X11 available. This requires libX11 itself, and also -# the GDK integration to X11. -find_package(X11) - -function(check_x11) - list(APPEND CMAKE_REQUIRED_INCLUDES ${GTK_INCLUDE_DIRS}) - check_include_file(gdk/gdkx.h HAVE_GDK_GDKX_H) - - if(X11_FOUND AND HAVE_GDK_GDKX_H) - set(NOT_X_WINDOWS OFF PARENT_SCOPE) - else() - set(NOT_X_WINDOWS ON PARENT_SCOPE) - endif() -endfunction() -check_x11() +if(GTK_FOUND) + # See if we have X11 available. This requires libX11 itself, and also + # the GDK integration to X11. + find_package(X11) + + function(check_x11) + list(APPEND CMAKE_REQUIRED_INCLUDES ${GTK_INCLUDE_DIRS}) + check_include_file(gdk/gdkx.h HAVE_GDK_GDKX_H) + + if(X11_FOUND AND HAVE_GDK_GDKX_H) + set(NOT_X_WINDOWS OFF PARENT_SCOPE) + else() + set(NOT_X_WINDOWS ON PARENT_SCOPE) + endif() + endfunction() + check_x11() +endif() include_directories(${CMAKE_SOURCE_DIR}/charset ${GTK_INCLUDE_DIRS} ${X11_INCLUDE_DIR}) link_directories(${GTK_LIBRARY_DIRS}) -- cgit v1.2.3 From fda41e199093b41f026fb8c06e8e2cddcace4d83 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 18 Sep 2022 15:08:31 +0100 Subject: Add cmake check for whether setpgrp takes arguments. FreeBSD declares setpgrp() as taking two arguments, like Linux's setpgid(). Detect that at configure time and adjust the call in Pageant appropriately. --- cmake/cmake.h.in | 2 ++ cmake/platforms/unix.cmake | 15 +++++++++++++++ unix/pageant.c | 4 ++++ 3 files changed, 21 insertions(+) diff --git a/cmake/cmake.h.in b/cmake/cmake.h.in index 4ce869f4..91d52d78 100644 --- a/cmake/cmake.h.in +++ b/cmake/cmake.h.in @@ -43,6 +43,8 @@ #cmakedefine01 HAVE_CLOCK_MONOTONIC #cmakedefine01 HAVE_CLOCK_GETTIME #cmakedefine01 HAVE_SO_PEERCRED +#cmakedefine01 HAVE_NULLARY_SETPGRP +#cmakedefine01 HAVE_BINARY_SETPGRP #cmakedefine01 HAVE_PANGO_FONT_FAMILY_IS_MONOSPACE #cmakedefine01 HAVE_PANGO_FONT_MAP_LIST_FAMILIES diff --git a/cmake/platforms/unix.cmake b/cmake/platforms/unix.cmake index 04db8129..98474485 100644 --- a/cmake/platforms/unix.cmake +++ b/cmake/platforms/unix.cmake @@ -51,6 +51,21 @@ int main(int argc, char **argv) { cr.pid + cr.uid + cr.gid; }" HAVE_SO_PEERCRED) +check_c_source_compiles(" +#include +#include + +int main(int argc, char **argv) { + setpgrp(); +}" HAVE_NULLARY_SETPGRP) +check_c_source_compiles(" +#include +#include + +int main(int argc, char **argv) { + setpgrp(0, 0); +}" HAVE_BINARY_SETPGRP) + if(HAVE_GETADDRINFO AND PUTTY_IPV6) set(NO_IPV6 OFF) else() diff --git a/unix/pageant.c b/unix/pageant.c index cef57b5b..6d125fce 100644 --- a/unix/pageant.c +++ b/unix/pageant.c @@ -330,7 +330,11 @@ void pageant_fork_and_print_env(bool retain_tty) /* Get out of our previous process group, to avoid being * blasted by passing signals. But keep our controlling tty, * so we can keep checking to see if we still have one. */ +#if defined HAVE_NULLARY_SETPGRP setpgrp(); +#elif defined HAVE_BINARY_SETPGRP + setpgrp(0, 0); +#endif } else { /* Do that, but also leave our entire session and detach from * the controlling tty (if any). */ -- cgit v1.2.3 From ae2c0d40ae61f56994c8d0fc4613ec9758dc2f08 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 19 Sep 2022 11:31:27 +0000 Subject: setpgrp cmake check: use #if, not #if defined. I still haven't got out of the habit of doing this the autotools way, which doesn't work in cmake. cmake's HAVE_FOO variables are always defined, and they take values 0 or 1, so testing them with 'defined' will return the wrong value. --- unix/pageant.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/unix/pageant.c b/unix/pageant.c index 6d125fce..4558cd37 100644 --- a/unix/pageant.c +++ b/unix/pageant.c @@ -330,9 +330,9 @@ void pageant_fork_and_print_env(bool retain_tty) /* Get out of our previous process group, to avoid being * blasted by passing signals. But keep our controlling tty, * so we can keep checking to see if we still have one. */ -#if defined HAVE_NULLARY_SETPGRP +#if HAVE_NULLARY_SETPGRP setpgrp(); -#elif defined HAVE_BINARY_SETPGRP +#elif HAVE_BINARY_SETPGRP setpgrp(0, 0); #endif } else { -- cgit v1.2.3 From 864b4c27fa67a95b2daa095923878bdba9fc6fcf Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 19 Sep 2022 11:34:21 +0000 Subject: Use GTK_LDFLAGS when testing for Pango. On FreeBSD, the GTK libraries aren't stored on the standard library path, so pkg-config has to emit a -L option as well as -l options. This worked fine during the main build, but the -L option wasn't being passed through to check_symbol_exists() for the tests of Pango API function availability. --- cmake/gtk.cmake | 1 + 1 file changed, 1 insertion(+) diff --git a/cmake/gtk.cmake b/cmake/gtk.cmake index 34396d2f..13ff7705 100644 --- a/cmake/gtk.cmake +++ b/cmake/gtk.cmake @@ -74,6 +74,7 @@ if(GTK_FOUND) # Check for some particular Pango functions. function(pango_check_subscope) set(CMAKE_REQUIRED_INCLUDES ${GTK_INCLUDE_DIRS}) + set(CMAKE_REQUIRED_LINK_OPTIONS ${GTK_LDFLAGS}) set(CMAKE_REQUIRED_LIBRARIES ${GTK_LIBRARIES}) check_symbol_exists(pango_font_family_is_monospace "pango/pango.h" HAVE_PANGO_FONT_FAMILY_IS_MONOSPACE) -- cgit v1.2.3 From 9fcfd679b49a015e95f70b3f0380243e0db8b035 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 19 Sep 2022 20:21:01 +0100 Subject: Stop trying to include X11 headers in non-GTK builds. In commit 732ec31a17a7fea I made the check for libX11 conditional on GTK - but I forgot that if we're building without GTK, I should _define_ NOT_X_WINDOWS, rather than leaving it undefined. As a result, the build would fail on files like unix/utils/x11_ignore_error.c. --- cmake/platforms/unix.cmake | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cmake/platforms/unix.cmake b/cmake/platforms/unix.cmake index 98474485..4d056d0a 100644 --- a/cmake/platforms/unix.cmake +++ b/cmake/platforms/unix.cmake @@ -96,6 +96,10 @@ if(GTK_FOUND) endif() endfunction() check_x11() +else() + # If we didn't even have GTK, behave as if X11 is not available. + # (There's nothing useful we could do with it even if there was.) + set(NOT_X_WINDOWS ON) endif() include_directories(${CMAKE_SOURCE_DIR}/charset ${GTK_INCLUDE_DIRS} ${X11_INCLUDE_DIR}) -- cgit v1.2.3 From 0615767224a5a5234b1916d45eca29cec1fe4a59 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Wed, 12 Oct 2022 19:42:09 +0100 Subject: Windows installer: remove explicit InstallScope setting. It turns out this isn't actually necessary after all to make the installer behave in the expected way in the default case (giving a UAC prompt and installing systemwide). And I'm told it has undesirable consequences in more complicated cases, which I'm not expert enough in MSI to fully understand. --- windows/installer.wxs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/windows/installer.wxs b/windows/installer.wxs index c7231b02..65a7f379 100644 --- a/windows/installer.wxs +++ b/windows/installer.wxs @@ -121,12 +121,6 @@ Language="1033" Codepage="1252" Version="$(var.Winver)">